Rigify: annotate and cleanup PyCharm warnings in utils and generation.

This commit is contained in:
Alexander Gavrilov 2022-11-06 17:14:22 +02:00
parent 85c414a202
commit c1dac65a84
22 changed files with 1553 additions and 921 deletions

View File

@ -5,19 +5,26 @@ import sys
import traceback
import collections
from typing import Optional, TYPE_CHECKING, Collection, List
from bpy.types import PoseBone, Bone
from .utils.errors import MetarigError, RaiseErrorMixin
from .utils.naming import random_id
from .utils.metaclass import SingletonPluginMetaclass
from .utils.rig import list_bone_names_depth_first_sorted, get_rigify_type
from .utils.misc import clone_parameters, assign_parameters
from .utils.rig import list_bone_names_depth_first_sorted, get_rigify_type, get_rigify_params
from .utils.misc import clone_parameters, assign_parameters, ArmatureObject
from . import base_rig
from itertools import count
#=============================================
if TYPE_CHECKING:
from .rig_ui_template import ScriptGenerator
##############################################
# Generator Plugin
#=============================================
##############################################
class GeneratorPlugin(base_rig.GenerateCallbackHost, metaclass=SingletonPluginMetaclass):
@ -39,68 +46,68 @@ class GeneratorPlugin(base_rig.GenerateCallbackHost, metaclass=SingletonPluginMe
priority = 0
def __init__(self, generator):
def __init__(self, generator: 'BaseGenerator'):
self.generator = generator
self.obj = generator.obj
def register_new_bone(self, new_name, old_name=None):
def register_new_bone(self, new_name: str, old_name: Optional[str] = None):
self.generator.bone_owners[new_name] = None
if old_name:
self.generator.derived_bones[old_name].add(new_name)
#=============================================
##############################################
# Rig Substitution Mechanism
#=============================================
##############################################
class SubstitutionRig(RaiseErrorMixin):
"""A proxy rig that replaces itself with one or more different rigs."""
def __init__(self, generator, pose_bone):
def __init__(self, generator: 'BaseGenerator', pose_bone: PoseBone):
self.generator = generator
self.obj = generator.obj
self.base_bone = pose_bone.name
self.params = pose_bone.rigify_parameters
self.params = get_rigify_params(pose_bone)
self.params_copy = clone_parameters(self.params)
def substitute(self):
# return [rig1, rig2...]
raise NotImplementedException()
raise NotImplementedError
# Utility methods
def register_new_bone(self, new_name, old_name=None):
def register_new_bone(self, new_name: str, old_name: Optional[str] = None):
pass
def get_params(self, bone_name):
return self.obj.pose.bones[bone_name].rigify_parameters
def get_params(self, bone_name: str):
return get_rigify_params(self.obj.pose.bones[bone_name])
def assign_params(self, bone_name, param_dict=None, **params):
def assign_params(self, bone_name: str, param_dict=None, **params):
assign_parameters(self.get_params(bone_name), param_dict, **params)
def instantiate_rig(self, rig_class, bone_name):
def instantiate_rig(self, rig_class: str | type, bone_name: str):
if isinstance(rig_class, str):
rig_class = self.generator.find_rig_class(rig_class)
return self.generator.instantiate_rig(rig_class, self.obj.pose.bones[bone_name])
#=============================================
##############################################
# Legacy Rig Wrapper
#=============================================
##############################################
class LegacyRig(base_rig.BaseRig):
"""Wrapper around legacy style rigs without a common base class"""
def __init__(self, generator, pose_bone, wrapped_class):
def __init__(self, generator: 'BaseGenerator', pose_bone: PoseBone, wrapped_class: type):
self.wrapped_rig = None
self.wrapped_class = wrapped_class
super().__init__(generator, pose_bone)
def find_org_bones(self, pose_bone):
def find_org_bones(self, pose_bone: PoseBone):
bone_name = pose_bone.name
if not self.wrapped_rig:
@ -163,15 +170,41 @@ class LegacyRig(base_rig.BaseRig):
bpy.ops.object.mode_set(mode='OBJECT')
#=============================================
##############################################
# Base Generate Engine
#=============================================
##############################################
class BaseGenerator:
"""Base class for the main generator object. Contains rig and plugin management code."""
instance = None
instance: Optional['BaseGenerator'] = None # static
context: bpy.types.Context
scene: bpy.types.Scene
view_layer: bpy.types.ViewLayer
layer_collection: bpy.types.LayerCollection
collection: bpy.types.Collection
metarig: ArmatureObject
obj: ArmatureObject
script: 'ScriptGenerator'
rig_list: List[base_rig.BaseRig]
root_rigs: List[base_rig.BaseRig]
bone_owners: dict[str, Optional[base_rig.BaseRig]]
derived_bones: dict[str, set[str]]
stage: Optional[str]
rig_id: str
widget_collection: bpy.types.Collection
use_mirror_widgets: bool
old_widget_table: dict[str, bpy.types.Object]
new_widget_table: dict[str, bpy.types.Object]
widget_mirror_mesh: dict[str, bpy.types.Mesh]
def __init__(self, context, metarig):
self.context = context
@ -180,7 +213,6 @@ class BaseGenerator:
self.layer_collection = context.layer_collection
self.collection = self.layer_collection.collection
self.metarig = metarig
self.obj = None
# List of all rig instances
self.rig_list = []
@ -210,18 +242,16 @@ class BaseGenerator:
# Table of renamed ORG bones
self.org_rename_table = dict()
def disable_auto_parent(self, bone_name):
def disable_auto_parent(self, bone_name: str):
"""Prevent automatically parenting the bone to root if parentless."""
self.noparent_bones.add(bone_name)
def find_derived_bones(self, bone_name, *, by_owner=False, recursive=True):
def find_derived_bones(self, bone_name: str, *, by_owner=False, recursive=True) -> set[str]:
"""Find which bones were copied from the specified one."""
if by_owner:
owner = self.bone_owners.get(bone_name, None)
if not owner:
return {}
return set()
table = owner.rigify_derived_bones
else:
@ -231,7 +261,7 @@ class BaseGenerator:
result = set()
def rec(name):
for child in table.get(name, {}):
for child in table.get(name, []):
result.add(child)
rec(child)
@ -239,16 +269,15 @@ class BaseGenerator:
return result
else:
return set(table.get(bone_name, {}))
return set(table.get(bone_name, []))
def set_layer_group_priority(self, bone_name, layers, priority):
def set_layer_group_priority(self, bone_name: str,
layers: Collection[bool], priority: float):
for i, val in enumerate(layers):
if val:
self.layer_group_priorities[bone_name][i] = priority
def rename_org_bone(self, old_name, new_name):
def rename_org_bone(self, old_name: str, new_name: str) -> str:
assert self.stage == 'instantiate'
assert old_name == self.org_rename_table.get(old_name, None)
assert old_name not in self.bone_owners
@ -261,8 +290,8 @@ class BaseGenerator:
self.org_rename_table[old_name] = new_name
return new_name
def __run_object_stage(self, method_name):
def __run_object_stage(self, method_name: str):
"""Run a generation stage in Object mode."""
assert(self.context.active_object == self.obj)
assert(self.obj.mode == 'OBJECT')
num_bones = len(self.obj.data.bones)
@ -287,8 +316,8 @@ class BaseGenerator:
assert(self.obj.mode == 'OBJECT')
assert(num_bones == len(self.obj.data.bones))
def __run_edit_stage(self, method_name):
def __run_edit_stage(self, method_name: str):
"""Run a generation stage in Edit mode."""
assert(self.context.active_object == self.obj)
assert(self.obj.mode == 'EDIT')
num_bones = len(self.obj.data.edit_bones)
@ -313,15 +342,12 @@ class BaseGenerator:
assert(self.obj.mode == 'EDIT')
assert(num_bones == len(self.obj.data.edit_bones))
def invoke_initialize(self):
self.__run_object_stage('initialize')
def invoke_prepare_bones(self):
self.__run_edit_stage('prepare_bones')
def __auto_register_bones(self, bones, rig, plugin=None):
"""Find bones just added and not registered by this rig."""
for bone in bones:
@ -332,10 +358,10 @@ class BaseGenerator:
rig.rigify_new_bones[name] = None
if not isinstance(rig, LegacyRig):
print("WARNING: rig %s didn't register bone %s\n" % (self.describe_rig(rig), name))
print(f"WARNING: rig {self.describe_rig(rig)} "
f"didn't register bone {name}\n")
else:
print("WARNING: plugin %s didn't register bone %s\n" % (plugin, name))
print(f"WARNING: plugin {plugin} didn't register bone {name}\n")
def invoke_generate_bones(self):
assert(self.context.active_object == self.obj)
@ -363,36 +389,28 @@ class BaseGenerator:
self.__auto_register_bones(self.obj.data.edit_bones, None, plugin=self.plugin_list[i])
def invoke_parent_bones(self):
self.__run_edit_stage('parent_bones')
def invoke_configure_bones(self):
self.__run_object_stage('configure_bones')
def invoke_preapply_bones(self):
self.__run_object_stage('preapply_bones')
def invoke_apply_bones(self):
self.__run_edit_stage('apply_bones')
def invoke_rig_bones(self):
self.__run_object_stage('rig_bones')
def invoke_generate_widgets(self):
self.__run_object_stage('generate_widgets')
def invoke_finalize(self):
self.__run_object_stage('finalize')
def instantiate_rig(self, rig_class, pose_bone):
def instantiate_rig(self, rig_class: type, pose_bone: PoseBone) -> base_rig.BaseRig:
assert not issubclass(rig_class, SubstitutionRig)
if issubclass(rig_class, base_rig.BaseRig):
@ -400,12 +418,14 @@ class BaseGenerator:
else:
return LegacyRig(self, pose_bone, rig_class)
def find_rig_class(self, rig_type: str) -> type:
raise NotImplementedError
def instantiate_rig_by_type(self, rig_type, pose_bone):
def instantiate_rig_by_type(self, rig_type: str, pose_bone: PoseBone):
return self.instantiate_rig(self.find_rig_class(rig_type), pose_bone)
def describe_rig(self, rig):
# noinspection PyMethodMayBeStatic
def describe_rig(self, rig: base_rig.BaseRig) -> str:
base_bone = rig.base_bone
if isinstance(rig, LegacyRig):
@ -413,7 +433,6 @@ class BaseGenerator:
return "%s (%s)" % (rig.__class__, base_bone)
def __create_rigs(self, bone_name, halt_on_missing):
"""Recursively walk bones and create rig instances."""
@ -440,12 +459,14 @@ class BaseGenerator:
if org_name in self.bone_owners:
old_rig = self.describe_rig(self.bone_owners[org_name])
new_rig = self.describe_rig(rig)
print("CONFLICT: bone %s is claimed by rigs %s and %s\n" % (org_name, old_rig, new_rig))
print(f"CONFLICT: bone {org_name} is claimed by rigs "
f"{old_rig} and {new_rig}\n")
self.bone_owners[org_name] = rig
except ImportError:
message = "Rig Type Missing: python module for type '%s' not found (bone: %s)" % (rig_type, bone_name)
message = f"Rig Type Missing: python module for type '{rig_type}' "\
f"not found (bone: {bone_name})"
if halt_on_missing:
raise MetarigError(message)
else:
@ -453,8 +474,8 @@ class BaseGenerator:
print('print_exc():')
traceback.print_exc(file=sys.stdout)
def __build_rig_tree_rec(self, bone, current_rig, handled):
def __build_rig_tree_rec(self, bone: Bone, current_rig: Optional[base_rig.BaseRig],
handled: dict[base_rig.BaseRig, str]):
"""Recursively walk bones and connect rig instances into a tree."""
rig = self.bone_owners.get(bone.name)
@ -474,8 +495,8 @@ class BaseGenerator:
handled[rig] = bone.name
elif rig.rigify_parent is not current_rig:
raise MetarigError("CONFLICT: bone %s owned by rig %s has different parent rig from %s\n" %
(bone.name, rig.base_bone, handled[rig]))
raise MetarigError("CONFLICT: bone {bone.name} owned by rig {rig.base_bone} "
f"has different parent rig from {handled[rig]}")
current_rig = rig
else:
@ -487,7 +508,6 @@ class BaseGenerator:
for child in bone.children:
self.__build_rig_tree_rec(child, current_rig, handled)
def instantiate_rig_tree(self, halt_on_missing=False):
"""Create rig instances and connect them into a tree."""

View File

@ -1,12 +1,20 @@
# SPDX-License-Identifier: GPL-2.0-or-later
import collections
import typing
from bpy.types import PoseBone
from typing import TYPE_CHECKING, Any, Callable, Optional
from .utils.errors import RaiseErrorMixin
from .utils.bones import BoneDict, BoneUtilityMixin
from .utils.mechanism import MechanismUtilityMixin
from .utils.metaclass import BaseStagedClass
from .utils.misc import ArmatureObject
from .utils.rig import get_rigify_params
if TYPE_CHECKING:
from .base_generate import BaseGenerator
from .rig_ui_template import ScriptGenerator
##############################################
@ -137,6 +145,21 @@ class GenerateCallbackHost(BaseStagedClass, define_stages=True):
class BaseRig(GenerateCallbackHost, RaiseErrorMixin, BoneUtilityMixin, MechanismUtilityMixin):
generator: 'BaseGenerator'
obj: ArmatureObject
script: 'ScriptGenerator'
base_bone: str
params: Any
bones: BoneDict
rigify_parent: Optional['BaseRig']
rigify_children: list['BaseRig']
rigify_org_bones: set[str]
rigify_child_bones: set[str]
rigify_new_bones: dict[str, Optional[str]]
rigify_derived_bones: dict[str, set[str]]
"""
Base class for all rigs.
@ -150,13 +173,13 @@ class BaseRig(GenerateCallbackHost, RaiseErrorMixin, BoneUtilityMixin, Mechanism
and the common generator object. The generation process is also
split into multiple stages.
"""
def __init__(self, generator, pose_bone):
def __init__(self, generator: 'BaseGenerator', pose_bone: PoseBone):
self.generator = generator
self.obj = generator.obj
self.script = generator.script
self.base_bone = pose_bone.name
self.params = pose_bone.rigify_parameters
self.params = get_rigify_params(pose_bone)
# Collection of bone names for use in implementing the rig
self.bones = BoneDict(
@ -193,7 +216,7 @@ class BaseRig(GenerateCallbackHost, RaiseErrorMixin, BoneUtilityMixin, Mechanism
###########################################################
# Bone ownership
def find_org_bones(self, pose_bone):
def find_org_bones(self, pose_bone: PoseBone) -> str | list[str] | BoneDict:
"""
Select bones directly owned by the rig. Returning the
same bone from multiple rigs is an error.
@ -277,13 +300,13 @@ class RigComponent(LazyRigComponent):
@GenerateCallbackHost.stage_decorator_container
class stage:
# Declare stages for auto-completion - doesn't affect execution.
initialize: typing.Callable
prepare_bones: typing.Callable
generate_bones: typing.Callable
parent_bones: typing.Callable
configure_bones: typing.Callable
preapply_bones: typing.Callable
apply_bones: typing.Callable
rig_bones: typing.Callable
generate_widgets: typing.Callable
finalize: typing.Callable
initialize: Callable
prepare_bones: Callable
generate_bones: Callable
parent_bones: Callable
configure_bones: Callable
preapply_bones: Callable
apply_bones: Callable
rig_bones: Callable
generate_widgets: Callable
finalize: Callable

View File

@ -7,43 +7,48 @@ import time
from .utils.errors import MetarigError
from .utils.bones import new_bone
from .utils.layers import ORG_LAYER, MCH_LAYER, DEF_LAYER, ROOT_LAYER
from .utils.naming import ORG_PREFIX, MCH_PREFIX, DEF_PREFIX, ROOT_NAME, make_original_name, change_name_side, get_name_side, Side
from .utils.naming import (ORG_PREFIX, MCH_PREFIX, DEF_PREFIX, ROOT_NAME, make_original_name,
change_name_side, get_name_side, Side)
from .utils.widgets import WGT_PREFIX
from .utils.widgets_special import create_root_widget
from .utils.mechanism import refresh_all_drivers
from .utils.misc import gamma_correct, select_object
from .utils.collections import ensure_collection, list_layer_collections, filter_layer_collections_by_object
from .utils.rig import get_rigify_type
from .utils.misc import gamma_correct, select_object, ArmatureObject, verify_armature_obj
from .utils.collections import (ensure_collection, list_layer_collections,
filter_layer_collections_by_object)
from .utils.rig import get_rigify_type, get_rigify_layers
from . import base_generate
from . import rig_ui_template
from . import rig_lists
RIG_MODULE = "rigs"
class Timer:
def __init__(self):
self.timez = time.time()
self.time_val = time.time()
def tick(self, string):
t = time.time()
print(string + "%.3f" % (t - self.timez))
self.timez = t
print(string + "%.3f" % (t - self.time_val))
self.time_val = t
class Generator(base_generate.BaseGenerator):
usable_collections: list[bpy.types.LayerCollection]
action_layers: ActionLayerBuilder
def __init__(self, context, metarig):
super().__init__(context, metarig)
self.id_store = context.window_manager
def find_rig_class(self, rig_type):
rig_module = rig_lists.rigs[rig_type]["module"]
return rig_module.Rig
def __switch_to_usable_collection(self, obj, fallback=False):
collections = filter_layer_collections_by_object(self.usable_collections, obj)
@ -54,8 +59,7 @@ class Generator(base_generate.BaseGenerator):
self.collection = self.layer_collection.collection
def ensure_rig_object(self) -> bpy.types.Object:
def ensure_rig_object(self) -> ArmatureObject:
"""Check if the generated rig already exists, so we can
regenerate in the same object. If not, create a new
object to generate the rig in.
@ -63,10 +67,14 @@ class Generator(base_generate.BaseGenerator):
print("Fetch rig.")
meta_data = self.metarig.data
target_rig = meta_data.rigify_target_rig
target_rig: ArmatureObject = meta_data.rigify_target_rig
if not target_rig:
if meta_data.rigify_rig_basename:
rig_new_name = meta_data.rigify_rig_basename
# noinspection PyUnresolvedReferences
rig_basename = meta_data.rigify_rig_basename
if rig_basename:
rig_new_name = rig_basename
elif "metarig" in self.metarig.name:
rig_new_name = self.metarig.name.replace("metarig", "rig")
elif "META" in self.metarig.name:
@ -74,7 +82,8 @@ class Generator(base_generate.BaseGenerator):
else:
rig_new_name = "RIG-" + self.metarig.name
target_rig = bpy.data.objects.new(rig_new_name, bpy.data.armatures.new(rig_new_name))
arm = bpy.data.armatures.new(rig_new_name)
target_rig = verify_armature_obj(bpy.data.objects.new(rig_new_name, arm))
target_rig.display_type = 'WIRE'
# If the object is already added to the scene, switch to its collection
@ -83,7 +92,7 @@ class Generator(base_generate.BaseGenerator):
else:
# Otherwise, add to the selected collection or the metarig collection if unusable
if (self.layer_collection not in self.usable_collections
or self.layer_collection == self.view_layer.layer_collection):
or self.layer_collection == self.view_layer.layer_collection):
self.__switch_to_usable_collection(self.metarig, True)
self.collection.objects.link(target_rig)
@ -94,8 +103,7 @@ class Generator(base_generate.BaseGenerator):
return target_rig
def __unhide_rig_object(self, obj):
def __unhide_rig_object(self, obj: bpy.types.Object):
# Ensure the object is visible and selectable
obj.hide_set(False, view_layer=self.view_layer)
obj.hide_viewport = False
@ -111,13 +119,13 @@ class Generator(base_generate.BaseGenerator):
if self.layer_collection not in self.usable_collections:
raise Exception('Could not generate: Could not find a usable collection.')
def __find_legacy_collection(self) -> bpy.types.Collection:
"""For backwards comp, matching by name to find a legacy collection.
(For before there was a Widget Collection PointerProperty)
"""
wgts_group_name = "WGTS_" + self.obj.name
old_collection = bpy.data.collections.get(wgts_group_name)
# noinspection SpellCheckingInspection
widgets_group_name = "WGTS_" + self.obj.name
old_collection = bpy.data.collections.get(widgets_group_name)
if old_collection and old_collection.library:
old_collection = None
@ -126,13 +134,14 @@ class Generator(base_generate.BaseGenerator):
# Update the old 'Widgets' collection
legacy_collection = bpy.data.collections.get('Widgets')
if legacy_collection and wgts_group_name in legacy_collection.objects and not legacy_collection.library:
legacy_collection.name = wgts_group_name
if legacy_collection and widgets_group_name in legacy_collection.objects\
and not legacy_collection.library:
legacy_collection.name = widgets_group_name
old_collection = legacy_collection
if old_collection:
# Rename the collection
old_collection.name = wgts_group_name
old_collection.name = widgets_group_name
return old_collection
@ -142,11 +151,14 @@ class Generator(base_generate.BaseGenerator):
if not self.widget_collection:
self.widget_collection = self.__find_legacy_collection()
if not self.widget_collection:
wgts_group_name = "WGTS_" + self.obj.name.replace("RIG-", "")
self.widget_collection = ensure_collection(self.context, wgts_group_name, hidden=True)
# noinspection SpellCheckingInspection
widgets_group_name = "WGTS_" + self.obj.name.replace("RIG-", "")
self.widget_collection = ensure_collection(
self.context, widgets_group_name, hidden=True)
self.metarig.data.rigify_widgets_collection = self.widget_collection
# noinspection PyUnresolvedReferences
self.use_mirror_widgets = self.metarig.data.rigify_mirror_widgets
# Build tables for existing widgets
@ -154,6 +166,7 @@ class Generator(base_generate.BaseGenerator):
self.new_widget_table = {}
self.widget_mirror_mesh = {}
# noinspection PyUnresolvedReferences
if self.metarig.data.rigify_force_widget_update:
# Remove widgets if force update is set
for obj in list(self.widget_collection.objects):
@ -176,16 +189,17 @@ class Generator(base_generate.BaseGenerator):
# If the mesh name is the same as the object, rename it too
if widget.data.name == old_data_name:
widget.data.name = change_name_side(widget.name, get_name_side(widget.data.name))
widget.data.name = change_name_side(
widget.name, get_name_side(widget.data.name))
# Find meshes for mirroring
if self.use_mirror_widgets:
for bone_name, widget in self.old_widget_table.items():
mid_name = change_name_side(bone_name, Side.MIDDLE)
if bone_name != mid_name:
assert isinstance(widget.data, bpy.types.Mesh)
self.widget_mirror_mesh[mid_name] = widget.data
def __duplicate_rig(self):
obj = self.obj
metarig = self.metarig
@ -203,7 +217,7 @@ class Generator(base_generate.BaseGenerator):
bpy.ops.object.duplicate()
# Rename org bones in the temporary object
temp_obj = context.view_layer.objects.active
temp_obj = verify_armature_obj(context.view_layer.objects.active)
assert temp_obj and temp_obj != metarig
@ -230,8 +244,8 @@ class Generator(base_generate.BaseGenerator):
for track in obj.animation_data.nla_tracks:
obj.animation_data.nla_tracks.remove(track)
def __freeze_driver_vars(self, obj):
@staticmethod
def __freeze_driver_vars(obj: bpy.types.Object):
if obj.animation_data:
# Freeze drivers referring to custom properties
for d in obj.animation_data.drivers:
@ -239,13 +253,12 @@ class Generator(base_generate.BaseGenerator):
for tar in var.targets:
# If a custom property
if var.type == 'SINGLE_PROP' \
and re.match(r'^pose.bones\["[^"\]]*"\]\["[^"\]]*"\]$', tar.data_path):
and re.match(r'^pose.bones\["[^"\]]*"]\["[^"\]]*"]$',
tar.data_path):
tar.data_path = "RIGIFY-" + tar.data_path
def __rename_org_bones(self, obj):
#----------------------------------
# Make a list of the original bones so we can keep track of them.
def __rename_org_bones(self, obj: ArmatureObject):
# Make a list of the original bones, so we can keep track of them.
original_bones = [bone.name for bone in obj.data.bones]
# Add the ORG_PREFIX to the original bones.
@ -267,7 +280,6 @@ class Generator(base_generate.BaseGenerator):
self.original_bones = original_bones
def __create_root_bone(self):
obj = self.obj
metarig = self.metarig
@ -289,7 +301,6 @@ class Generator(base_generate.BaseGenerator):
self.bone_owners[root_bone] = None
self.noparent_bones.add(root_bone)
def __parent_bones_to_root(self):
eb = self.obj.data.edit_bones
@ -301,7 +312,6 @@ class Generator(base_generate.BaseGenerator):
bone.use_connect = False
bone.parent = eb[self.root_bone]
def __lock_transforms(self):
# Lock transforms on all non-control bones
r = re.compile("[A-Z][A-Z][A-Z]-")
@ -312,15 +322,14 @@ class Generator(base_generate.BaseGenerator):
pb.lock_rotation_w = True
pb.lock_scale = (True, True, True)
def __assign_layers(self):
pbones = self.obj.pose.bones
pose_bones = self.obj.pose.bones
pbones[self.root_bone].bone.layers = ROOT_LAYER
pose_bones[self.root_bone].bone.layers = ROOT_LAYER
# Every bone that has a name starting with "DEF-" make deforming. All the
# others make non-deforming.
for pbone in pbones:
for pbone in pose_bones:
bone = pbone.bone
name = bone.name
layers = None
@ -345,7 +354,6 @@ class Generator(base_generate.BaseGenerator):
bone.bbone_x = bone.bbone_z = bone.length * 0.05
def __restore_driver_vars(self):
obj = self.obj
@ -355,16 +363,15 @@ class Generator(base_generate.BaseGenerator):
for v in d.driver.variables:
for tar in v.targets:
if tar.data_path.startswith("RIGIFY-"):
temp, bone, prop = tuple([x.strip('"]') for x in tar.data_path.split('["')])
if bone in obj.data.bones \
and prop in obj.pose.bones[bone].keys():
temp, bone, prop = tuple(
[x.strip('"]') for x in tar.data_path.split('["')])
if bone in obj.data.bones and prop in obj.pose.bones[bone].keys():
tar.data_path = tar.data_path[7:]
else:
org_name = make_original_name(bone)
org_name = self.org_rename_table.get(org_name, org_name)
tar.data_path = 'pose.bones["%s"]["%s"]' % (org_name, prop)
def __assign_widgets(self):
obj_table = {obj.name: obj for obj in self.scene.objects}
@ -382,10 +389,9 @@ class Generator(base_generate.BaseGenerator):
if wgt_name in obj_table:
bone.custom_shape = obj_table[wgt_name]
def __compute_visible_layers(self):
# Reveal all the layers with control bones on them
vis_layers = [False for n in range(0, 32)]
vis_layers = [False for _ in range(0, 32)]
for bone in self.obj.data.bones:
for i in range(0, 32):
@ -396,20 +402,18 @@ class Generator(base_generate.BaseGenerator):
self.obj.data.layers = vis_layers
def generate(self):
context = self.context
metarig = self.metarig
scene = self.scene
id_store = self.id_store
view_layer = self.view_layer
t = Timer()
self.usable_collections = list_layer_collections(view_layer.layer_collection, selectable=True)
self.usable_collections = list_layer_collections(
view_layer.layer_collection, selectable=True)
bpy.ops.object.mode_set(mode='OBJECT')
#------------------------------------------
###########################################
# Create/find the rig object and set it up
self.obj = obj = self.ensure_rig_object()
@ -426,19 +430,21 @@ class Generator(base_generate.BaseGenerator):
select_object(context, obj, deselect_all=True)
#------------------------------------------
###########################################
# Create Widget Collection
self.ensure_widget_collection()
t.tick("Create main WGTS: ")
t.tick("Create widgets collection: ")
#------------------------------------------
###########################################
# Get parented objects to restore later
childs = {} # {object: bone}
for child in obj.children:
childs[child] = child.parent_bone
#------------------------------------------
child_parent_bones = {} # {object: bone}
for child in obj.children:
child_parent_bones[child] = child.parent_bone
###########################################
# Copy bones from metarig to obj (adds ORG_PREFIX)
self.__duplicate_rig()
@ -446,34 +452,34 @@ class Generator(base_generate.BaseGenerator):
t.tick("Duplicate rig: ")
#------------------------------------------
###########################################
# Put the rig_name in the armature custom properties
obj.data["rig_id"] = self.rig_id
self.script = rig_ui_template.ScriptGenerator(self)
#------------------------------------------
###########################################
bpy.ops.object.mode_set(mode='OBJECT')
self.instantiate_rig_tree()
t.tick("Instantiate rigs: ")
#------------------------------------------
###########################################
bpy.ops.object.mode_set(mode='OBJECT')
self.invoke_initialize()
t.tick("Initialize rigs: ")
#------------------------------------------
###########################################
bpy.ops.object.mode_set(mode='EDIT')
self.invoke_prepare_bones()
t.tick("Prepare bones: ")
#------------------------------------------
###########################################
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='EDIT')
@ -483,7 +489,7 @@ class Generator(base_generate.BaseGenerator):
t.tick("Generate bones: ")
#------------------------------------------
###########################################
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='EDIT')
@ -493,35 +499,35 @@ class Generator(base_generate.BaseGenerator):
t.tick("Parent bones: ")
#------------------------------------------
###########################################
bpy.ops.object.mode_set(mode='OBJECT')
self.invoke_configure_bones()
t.tick("Configure bones: ")
#------------------------------------------
###########################################
bpy.ops.object.mode_set(mode='OBJECT')
self.invoke_preapply_bones()
t.tick("Preapply bones: ")
#------------------------------------------
###########################################
bpy.ops.object.mode_set(mode='EDIT')
self.invoke_apply_bones()
t.tick("Apply bones: ")
#------------------------------------------
###########################################
bpy.ops.object.mode_set(mode='OBJECT')
self.invoke_rig_bones()
t.tick("Rig bones: ")
#------------------------------------------
###########################################
bpy.ops.object.mode_set(mode='OBJECT')
self.invoke_generate_widgets()
@ -531,7 +537,7 @@ class Generator(base_generate.BaseGenerator):
t.tick("Generate widgets: ")
#------------------------------------------
###########################################
bpy.ops.object.mode_set(mode='OBJECT')
self.__lock_transforms()
@ -541,14 +547,14 @@ class Generator(base_generate.BaseGenerator):
t.tick("Assign layers: ")
#------------------------------------------
###########################################
bpy.ops.object.mode_set(mode='OBJECT')
self.invoke_finalize()
t.tick("Finalize: ")
#------------------------------------------
###########################################
bpy.ops.object.mode_set(mode='OBJECT')
self.__assign_widgets()
@ -561,13 +567,14 @@ class Generator(base_generate.BaseGenerator):
t.tick("The rest: ")
#----------------------------------
# Deconfigure
###########################################
# Restore state
bpy.ops.object.mode_set(mode='OBJECT')
obj.data.pose_position = 'POSE'
# Restore parent to bones
for child, sub_parent in childs.items():
for child, sub_parent in child_parent_bones.items():
if sub_parent in obj.pose.bones:
mat = child.matrix_world.copy()
child.parent_bone = sub_parent
@ -576,15 +583,18 @@ class Generator(base_generate.BaseGenerator):
# Clear any transient errors in drivers
refresh_all_drivers()
#----------------------------------
###########################################
# Execute the finalize script
if metarig.data.rigify_finalize_script:
# noinspection PyUnresolvedReferences
finalize_script = metarig.data.rigify_finalize_script
if finalize_script:
bpy.ops.object.mode_set(mode='OBJECT')
exec(metarig.data.rigify_finalize_script.as_string(), {})
exec(finalize_script.as_string(), {})
bpy.ops.object.mode_set(mode='OBJECT')
#----------------------------------
###########################################
# Restore active collection
view_layer.active_layer_collection = self.layer_collection
@ -620,26 +630,24 @@ def generate_rig(context, metarig):
base_generate.BaseGenerator.instance = None
def create_selection_set_for_rig_layer(
rig: bpy.types.Object,
set_name: str,
layer_idx: int
) -> None:
def create_selection_set_for_rig_layer(rig: ArmatureObject, set_name: str, layer_idx: int) -> None:
"""Create a single selection set on a rig.
The set will contain all bones on the rig layer with the given index.
"""
selset = rig.selection_sets.add()
selset.name = set_name
# noinspection PyUnresolvedReferences
sel_set = rig.selection_sets.add()
sel_set.name = set_name
for b in rig.pose.bones:
if not b.bone.layers[layer_idx] or b.name in selset.bone_ids:
if not b.bone.layers[layer_idx] or b.name in sel_set.bone_ids:
continue
bone_id = selset.bone_ids.add()
bone_id = sel_set.bone_ids.add()
bone_id.name = b.name
def create_selection_sets(obj, metarig):
def create_selection_sets(obj: ArmatureObject, metarig: ArmatureObject):
"""Create selection sets if the Selection Sets addon is enabled.
Whether a selection set for a rig layer is created is controlled in the
@ -650,17 +658,20 @@ def create_selection_sets(obj, metarig):
and 'bone_selection_sets' not in bpy.context.preferences.addons:
return
# noinspection PyUnresolvedReferences
obj.selection_sets.clear()
for i, name in enumerate(metarig.data.rigify_layers.keys()):
if name == '' or not metarig.data.rigify_layers[i].selset:
rigify_layers = get_rigify_layers(metarig.data)
for i, layer in enumerate(rigify_layers):
if layer.name == '' or not layer.selset:
continue
create_selection_set_for_rig_layer(obj, name, i)
create_selection_set_for_rig_layer(obj, layer.name, i)
# noinspection PyDefaultArgument
def create_bone_groups(obj, metarig, priorities={}):
bpy.ops.object.mode_set(mode='OBJECT')
pb = obj.pose.bones
layers = metarig.data.rigify_layers
@ -668,10 +679,10 @@ def create_bone_groups(obj, metarig, priorities={}):
dummy = {}
# Create BGs
for l in layers:
if l.group == 0:
for layer in layers:
if layer.group == 0:
continue
g_id = l.group - 1
g_id = layer.group - 1
name = groups[g_id].name
if name not in obj.pose.bone_groups.keys():
bg = obj.pose.bone_groups.new(name=name)
@ -682,9 +693,9 @@ def create_bone_groups(obj, metarig, priorities={}):
for b in pb:
try:
prios = priorities.get(b.name, dummy)
enabled = [ i for i, v in enumerate(b.bone.layers) if v ]
layer_index = max(enabled, key=lambda i: prios.get(i, 0))
bone_priorities = priorities.get(b.name, dummy)
enabled = [i for i, v in enumerate(b.bone.layers) if v]
layer_index = max(enabled, key=lambda i: bone_priorities.get(i, 0))
except ValueError:
continue
if layer_index > len(layers) - 1: # bone is on reserved layers
@ -703,18 +714,3 @@ def get_xy_spread(bones):
y_max = max((y_max, abs(b.head[1]), abs(b.tail[1])))
return max((x_max, y_max))
def param_matches_type(param_name, rig_type):
""" Returns True if the parameter name is consistent with the rig type.
"""
if param_name.rsplit(".", 1)[0] == rig_type:
return True
else:
return False
def param_name(param_name, rig_type):
""" Get the actual parameter name, sans-rig-type.
"""
return param_name[len(rig_type) + 1:]

View File

@ -3,6 +3,7 @@
import bpy
from collections import OrderedDict
from typing import Union, Optional, Any
from .utils.animation import SCRIPT_REGISTER_BAKE, SCRIPT_UTILITIES_BAKE
@ -10,7 +11,9 @@ from . import base_generate
from rna_prop_ui import rna_idprop_quote_path
from .utils.rig import get_rigify_layers
# noinspection SpellCheckingInspection
UI_IMPORTS = [
'import bpy',
'import math',
@ -23,6 +26,7 @@ UI_IMPORTS = [
'from rna_prop_ui import rna_idprop_quote_path',
]
UI_BASE_UTILITIES = '''
rig_id = "%s"
@ -44,7 +48,7 @@ def perpendicular_vector(v):
else:
tv = Vector((0,1,0))
# Use cross prouct to generate a vector perpendicular to
# Use cross product to generate a vector perpendicular to
# both tv and (more importantly) v.
return v.cross(tv)
@ -76,7 +80,7 @@ def find_min_range(f,start_angle,delta=pi/8):
def ternarySearch(f, left, right, absolutePrecision):
"""
Find minimum of unimodal function f() within [left, right]
Find minimum of uni-modal function f() within [left, right]
To find the maximum, revert the if/else statement or revert the comparison.
"""
while True:
@ -93,6 +97,7 @@ def ternarySearch(f, left, right, absolutePrecision):
right = rightThird
'''
# noinspection SpellCheckingInspection
UTILITIES_FUNC_COMMON_IKFK = ['''
#########################################
## "Visual Transform" helper functions ##
@ -292,6 +297,7 @@ def parse_bone_names(names_string):
''']
# noinspection SpellCheckingInspection
UTILITIES_FUNC_OLD_ARM_FKIK = ['''
######################
## IK Arm functions ##
@ -409,6 +415,7 @@ def ik2fk_arm(obj, fk, ik):
correct_scale(view_layer, uarmi, uarm.matrix)
''']
# noinspection SpellCheckingInspection
UTILITIES_FUNC_OLD_LEG_FKIK = ['''
######################
## IK Leg functions ##
@ -551,6 +558,7 @@ def ik2fk_leg(obj, fk, ik):
correct_scale(view_layer, thighi, thigh.matrix)
''']
# noinspection SpellCheckingInspection
UTILITIES_FUNC_OLD_POLE = ['''
################################
## IK Rotation-Pole functions ##
@ -606,8 +614,8 @@ def rotPoleToggle(rig, limb_type, controls, ik_ctrl, fk_ctrl, parent, pole):
'foot_ik': ik_ctrl[2], 'mfoot_ik': ik_ctrl[2]}
kwargs2 = {'thigh_fk': controls[1], 'shin_fk': controls[2], 'foot_fk': controls[3],
'mfoot_fk': controls[7], 'thigh_ik': controls[0], 'shin_ik': ik_ctrl[1],
'foot_ik': controls[6], 'pole': pole, 'footroll': controls[5], 'mfoot_ik': ik_ctrl[2],
'main_parent': parent}
'foot_ik': controls[6], 'pole': pole, 'footroll': controls[5],
'mfoot_ik': ik_ctrl[2], 'main_parent': parent}
func1(**kwargs1)
rig.pose.bones[parent]['pole_vector'] = new_pole_vector_value
@ -616,8 +624,10 @@ def rotPoleToggle(rig, limb_type, controls, ik_ctrl, fk_ctrl, parent, pole):
bpy.ops.pose.select_all(action='DESELECT')
''']
# noinspection SpellCheckingInspection
REGISTER_OP_OLD_ARM_FKIK = ['Rigify_Arm_FK2IK', 'Rigify_Arm_IK2FK']
# noinspection SpellCheckingInspection
UTILITIES_OP_OLD_ARM_FKIK = ['''
##################################
## IK/FK Arm snapping operators ##
@ -643,7 +653,8 @@ class Rigify_Arm_FK2IK(bpy.types.Operator):
return (context.active_object != None and context.mode == 'POSE')
def execute(self, context):
fk2ik_arm(context.active_object, fk=[self.uarm_fk, self.farm_fk, self.hand_fk], ik=[self.uarm_ik, self.farm_ik, self.hand_ik])
fk2ik_arm(context.active_object, fk=[self.uarm_fk, self.farm_fk, self.hand_fk],
ik=[self.uarm_ik, self.farm_ik, self.hand_ik])
return {'FINISHED'}
@ -670,12 +681,15 @@ class Rigify_Arm_IK2FK(bpy.types.Operator):
return (context.active_object != None and context.mode == 'POSE')
def execute(self, context):
ik2fk_arm(context.active_object, fk=[self.uarm_fk, self.farm_fk, self.hand_fk], ik=[self.uarm_ik, self.farm_ik, self.hand_ik, self.pole, self.main_parent])
ik2fk_arm(context.active_object, fk=[self.uarm_fk, self.farm_fk, self.hand_fk],
ik=[self.uarm_ik, self.farm_ik, self.hand_ik, self.pole, self.main_parent])
return {'FINISHED'}
''']
# noinspection SpellCheckingInspection
REGISTER_OP_OLD_LEG_FKIK = ['Rigify_Leg_FK2IK', 'Rigify_Leg_IK2FK']
# noinspection SpellCheckingInspection
UTILITIES_OP_OLD_LEG_FKIK = ['''
##################################
## IK/FK Leg snapping operators ##
@ -703,7 +717,9 @@ class Rigify_Leg_FK2IK(bpy.types.Operator):
return (context.active_object != None and context.mode == 'POSE')
def execute(self, context):
fk2ik_leg(context.active_object, fk=[self.thigh_fk, self.shin_fk, self.foot_fk, self.mfoot_fk], ik=[self.thigh_ik, self.shin_ik, self.foot_ik, self.mfoot_ik])
fk2ik_leg(context.active_object,
fk=[self.thigh_fk, self.shin_fk, self.foot_fk, self.mfoot_fk],
ik=[self.thigh_ik, self.shin_ik, self.foot_ik, self.mfoot_ik])
return {'FINISHED'}
@ -732,7 +748,10 @@ class Rigify_Leg_IK2FK(bpy.types.Operator):
return (context.active_object != None and context.mode == 'POSE')
def execute(self, context):
ik2fk_leg(context.active_object, fk=[self.thigh_fk, self.shin_fk, self.mfoot_fk, self.foot_fk], ik=[self.thigh_ik, self.shin_ik, self.foot_ik, self.footroll, self.pole, self.mfoot_ik, self.main_parent])
ik2fk_leg(context.active_object,
fk=[self.thigh_fk, self.shin_fk, self.mfoot_fk, self.foot_fk],
ik=[self.thigh_ik, self.shin_ik, self.foot_ik, self.footroll, self.pole,
self.mfoot_ik, self.main_parent])
return {'FINISHED'}
''']
@ -763,7 +782,8 @@ class Rigify_Rot2PoleSwitch(bpy.types.Operator):
bpy.ops.pose.select_all(action='DESELECT')
rig.pose.bones[self.bone_name].bone.select = True
rotPoleToggle(rig, self.limb_type, self.controls, self.ik_ctrl, self.fk_ctrl, self.parent, self.pole)
rotPoleToggle(rig, self.limb_type, self.controls, self.ik_ctrl, self.fk_ctrl,
self.parent, self.pole)
return {'FINISHED'}
''']
@ -787,9 +807,9 @@ UTILITIES_RIG_OLD_LEG = [
*UTILITIES_OP_OLD_POLE,
]
##############################
## Default set of utilities ##
##############################
############################
# Default set of utilities #
############################
UI_REGISTER = [
'RigUI',
@ -799,6 +819,7 @@ UI_REGISTER = [
UI_UTILITIES = [
]
# noinspection SpellCheckingInspection
UI_SLIDERS = '''
###################
## Rig UI Panels ##
@ -847,6 +868,7 @@ class RigUI(bpy.types.Panel):
UI_REGISTER_BAKE_SETTINGS = ['RigBakeSettings']
# noinspection SpellCheckingInspection
UI_BAKE_SETTINGS = '''
class RigBakeSettings(bpy.types.Panel):
bl_space_type = 'VIEW_3D'
@ -863,10 +885,12 @@ class RigBakeSettings(bpy.types.Panel):
RigifyBakeKeyframesMixin.draw_common_bake_ui(context, self.layout)
'''
def layers_ui(layers, layout):
""" Turn a list of booleans + a list of names into a layer UI.
"""
# noinspection SpellCheckingInspection
code = '''
class RigLayers(bpy.types.Panel):
bl_space_type = 'VIEW_3D'
@ -899,11 +923,12 @@ class RigLayers(bpy.types.Panel):
for key in keys:
code += "\n row = col.row()\n"
i = 0
for l in rows[key]:
for layer in rows[key]:
if i > 3:
code += "\n row = col.row()\n"
i = 0
code += " row.prop(context.active_object.data, 'layers', index=%s, toggle=True, text='%s')\n" % (str(l[1]), l[0])
code += f" row.prop(context.active_object.data, 'layers', "\
f"index={layer[1]}, toggle=True, text='{layer[0]}')\n"
i += 1
# Root layer
@ -912,21 +937,23 @@ class RigLayers(bpy.types.Panel):
code += "\n row = col.row()"
code += "\n row.separator()\n"
code += "\n row = col.row()\n"
code += " row.prop(context.active_object.data, 'layers', index=28, toggle=True, text='Root')\n"
code += " row.prop(context.active_object.data, 'layers', "\
"index=28, toggle=True, text='Root')\n"
return code
def quote_parameters(positional, named):
def quote_parameters(positional: list[Any], named: dict[str, Any]):
"""Quote the given positional and named parameters as a code string."""
positional_list = [ repr(v) for v in positional ]
named_list = [ "%s=%r" % (k, v) for k, v in named.items() ]
positional_list = [repr(v) for v in positional]
named_list = ["%s=%r" % (k, v) for k, v in named.items()]
return ', '.join(positional_list + named_list)
def indent_lines(lines, indent=4):
def indent_lines(lines: list[str], indent=4):
if indent > 0:
prefix = ' ' * indent
return [ prefix + line for line in lines ]
return [prefix + line for line in lines]
else:
return lines
@ -934,7 +961,13 @@ def indent_lines(lines, indent=4):
class PanelLayout(object):
"""Utility class that builds code for creating a layout."""
def __init__(self, parent, index=0):
parent: Optional['PanelLayout']
script: 'ScriptGenerator'
header: list[str]
items: list[Union[str, 'PanelLayout']]
def __init__(self, parent: Union['PanelLayout', 'ScriptGenerator'], index=0):
if isinstance(parent, PanelLayout):
self.parent = parent
self.script = parent.script
@ -959,7 +992,7 @@ class PanelLayout(object):
if self.parent:
self.parent.clear_empty()
def get_lines(self):
def get_lines(self) -> list[str]:
lines = []
for item in self.items:
@ -976,7 +1009,7 @@ class PanelLayout(object):
def wrap_lines(self, lines):
return self.header + indent_lines(lines, self.indent)
def add_line(self, line):
def add_line(self, line: str):
assert isinstance(line, str)
self.items.append(line)
@ -988,29 +1021,31 @@ class PanelLayout(object):
"""This panel contains operators that need the common Bake settings."""
self.parent.use_bake_settings()
def custom_prop(self, bone_name, prop_name, **params):
def custom_prop(self, bone_name: str, prop_name: str, **params):
"""Add a custom property input field to the panel."""
param_str = quote_parameters([ rna_idprop_quote_path(prop_name) ], params)
param_str = quote_parameters([rna_idprop_quote_path(prop_name)], params)
self.add_line(
"%s.prop(pose_bones[%r], %s)" % (self.layout, bone_name, param_str)
)
def operator(self, operator_name, *, properties=None, **params):
def operator(self, operator_name: str, *,
properties: Optional[dict[str, Any]] = None,
**params):
"""Add an operator call button to the panel."""
name = operator_name.format_map(self.script.format_args)
param_str = quote_parameters([ name ], params)
param_str = quote_parameters([name], params)
call_str = "%s.operator(%s)" % (self.layout, param_str)
if properties:
self.add_line("props = " + call_str)
for k, v in properties.items():
self.add_line("props.%s = %r" % (k,v))
self.add_line("props.%s = %r" % (k, v))
else:
self.add_line(call_str)
def add_nested_layout(self, name, params):
def add_nested_layout(self, method_name: str, params: dict[str, Any]) -> 'PanelLayout':
param_str = quote_parameters([], params)
sub_panel = PanelLayout(self, self.index + 1)
sub_panel.header.append('%s = %s.%s(%s)' % (sub_panel.layout, self.layout, name, param_str))
sub_panel.header.append(f'{sub_panel.layout} = {self.layout}.{method_name}({param_str})')
self.items.append(sub_panel)
return sub_panel
@ -1030,7 +1065,9 @@ class PanelLayout(object):
class BoneSetPanelLayout(PanelLayout):
"""Panel restricted to a certain set of bones."""
def __init__(self, rig_panel, bones):
parent: 'RigPanelLayout'
def __init__(self, rig_panel: 'RigPanelLayout', bones: frozenset[str]):
assert isinstance(bones, frozenset)
super().__init__(rig_panel)
self.bones = bones
@ -1059,24 +1096,24 @@ class BoneSetPanelLayout(PanelLayout):
class RigPanelLayout(PanelLayout):
"""Panel owned by a certain rig."""
def __init__(self, script, rig):
def __init__(self, script: 'ScriptGenerator', _rig):
super().__init__(script)
self.bones = set()
self.subpanels = OrderedDict()
self.sub_panels = OrderedDict()
def wrap_lines(self, lines):
header = [ "if is_selected(%r):" % (set(self.bones)) ]
prefix = [ "emit_rig_separator()" ]
header = ["if is_selected(%r):" % (set(self.bones))]
prefix = ["emit_rig_separator()"]
return header + indent_lines(prefix + lines)
def panel_with_selected_check(self, control_names):
selected_set = frozenset(control_names)
if selected_set in self.subpanels:
return self.subpanels[selected_set]
if selected_set in self.sub_panels:
return self.sub_panels[selected_set]
else:
panel = BoneSetPanelLayout(self, selected_set)
self.subpanels[selected_set] = panel
self.sub_panels[selected_set] = panel
self.items.append(panel)
return panel
@ -1086,6 +1123,8 @@ class ScriptGenerator(base_generate.GeneratorPlugin):
priority = -100
format_args: dict[str, str]
def __init__(self, generator):
super().__init__(generator)
@ -1114,23 +1153,23 @@ class ScriptGenerator(base_generate.GeneratorPlugin):
return panel.panel_with_selected_check(control_names)
# Raw output
def add_panel_code(self, str_list):
def add_panel_code(self, str_list: list[str]):
"""Add raw code to the panel."""
self.ui_scripts += str_list
def add_imports(self, str_list):
def add_imports(self, str_list: list[str]):
self.ui_imports += str_list
def add_utilities(self, str_list):
def add_utilities(self, str_list: list[str]):
self.ui_utilities += str_list
def register_classes(self, str_list):
def register_classes(self, str_list: list[str]):
self.ui_register += str_list
def register_driver_functions(self, str_list):
def register_driver_functions(self, str_list: list[str]):
self.ui_register_drivers += str_list
def register_property(self, name, definition):
def register_property(self, name: str, definition):
self.ui_register_props.append((name, definition))
def initialize(self):
@ -1145,13 +1184,16 @@ class ScriptGenerator(base_generate.GeneratorPlugin):
vis_layers = self.obj.data.layers
# Ensure the collection of layer names exists
for i in range(1 + len(metarig.data.rigify_layers), 29):
metarig.data.rigify_layers.add()
rigify_layers = get_rigify_layers(metarig.data)
for i in range(1 + len(rigify_layers), 29):
# noinspection PyUnresolvedReferences
rigify_layers.add()
# Create list of layer name/row pairs
layer_layout = []
for l in metarig.data.rigify_layers:
layer_layout += [(l.name, l.row)]
for layer in rigify_layers:
layer_layout += [(layer.name, layer.row)]
# Generate the UI script
script = metarig.data.rigify_rig_ui
@ -1201,8 +1243,8 @@ class ScriptGenerator(base_generate.GeneratorPlugin):
script.write(" bpy.app.driver_namespace['"+s+"'] = "+s+"\n")
ui_register_props = OrderedDict.fromkeys(self.ui_register_props)
for s in ui_register_props:
script.write(" bpy.types.%s = %s\n " % (*s,))
for classname, text in ui_register_props:
script.write(f" bpy.types.{classname} = {text}\n ")
script.write("\ndef unregister():\n")

View File

@ -14,7 +14,7 @@ from .utils.errors import MetarigError
from .utils.rig import write_metarig
from .utils.widgets import write_widget
from .utils.naming import unique_name
from .utils.rig import upgradeMetarigTypes, outdated_types
from .utils.rig import upgrade_metarig_types, outdated_types
from .rigs.utils import get_limb_generated_names
@ -825,7 +825,7 @@ class UpgradeMetarigTypes(bpy.types.Operator):
def execute(self, context):
for obj in bpy.data.objects:
if type(obj.data) == bpy.types.Armature:
upgradeMetarigTypes(obj)
upgrade_metarig_types(obj)
return {'FINISHED'}
class Sample(bpy.types.Operator):
"""Create a sample metarig to be modified before generating the final rig"""

View File

@ -26,7 +26,7 @@ from .widgets_basic import create_sphere_widget, create_limb_widget, create_bone
from .widgets_special import create_compass_widget, create_root_widget
from .widgets_special import create_neck_bend_widget, create_neck_tweak_widget
from .rig import RIG_DIR, METARIG_DIR, TEMPLATE_DIR, outdated_types, upgradeMetarigTypes
from .rig import RIG_DIR, METARIG_DIR, TEMPLATE_DIR, outdated_types, upgrade_metarig_types
from .rig import write_metarig, get_resource
from .rig import connected_children_names, has_connected_children

View File

@ -1,18 +1,23 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# noinspection PyUnresolvedReferences
import bpy
# noinspection PyUnresolvedReferences
import math
import json
# noinspection PyUnresolvedReferences
from mathutils import Matrix, Vector
from typing import Callable, Any, Collection, Iterator
from bpy.types import Action, bpy_struct, FCurve
import json
rig_id = None
#=============================================
# Keyframing functions
#=============================================
##############################################
# Keyframing functions
##############################################
def get_keyed_frames_in_range(context, rig):
action = find_action(rig)
@ -34,11 +39,11 @@ def bones_in_frame(f, rig, *args):
"""
if rig.animation_data and rig.animation_data.action:
fcus = rig.animation_data.action.fcurves
fcurves = rig.animation_data.action.fcurves
else:
return False
for fc in fcus:
for fc in fcurves:
animated_frames = [kp.co[0] for kp in fc.keyframe_points]
for bone in args:
if bone in fc.data_path.split('"') and f in animated_frames:
@ -68,10 +73,12 @@ def overwrite_prop_animation(rig, bone, prop_name, value, frames):
if kp.co[0] in frames:
kp.co[1] = value
################################################################
# Utilities for inserting keyframes and/or setting transforms ##
################################################################
# noinspection SpellCheckingInspection
SCRIPT_UTILITIES_KEYING = ['''
######################
## Keyframing tools ##
@ -118,7 +125,8 @@ def get_4d_rotlock(bone):
else:
return [all(bone.lock_rotation)] * 4
def keyframe_transform_properties(obj, bone_name, keyflags, *, ignore_locks=False, no_loc=False, no_rot=False, no_scale=False):
def keyframe_transform_properties(obj, bone_name, keyflags, *,
ignore_locks=False, no_loc=False, no_rot=False, no_scale=False):
"Keyframe transformation properties, taking flags and mode into account, and avoiding keying locked channels."
bone = obj.pose.bones[bone_name]
@ -155,7 +163,8 @@ def get_constraint_target_matrix(con):
if target.type == 'ARMATURE' and con.subtarget:
if con.subtarget in target.pose.bones:
bone = target.pose.bones[con.subtarget]
return target.convert_space(pose_bone=bone, matrix=bone.matrix, from_space='POSE', to_space=con.target_space)
return target.convert_space(
pose_bone=bone, matrix=bone.matrix, from_space='POSE', to_space=con.target_space)
else:
return target.convert_space(matrix=target.matrix_world, from_space='WORLD', to_space=con.target_space)
return Matrix.Identity(4)
@ -224,8 +233,10 @@ def get_transform_matrix(obj, bone_name, *, space='POSE', with_constraints=True)
def get_chain_transform_matrices(obj, bone_names, **options):
return [get_transform_matrix(obj, name, **options) for name in bone_names]
def set_transform_from_matrix(obj, bone_name, matrix, *, space='POSE', undo_copy_scale=False, ignore_locks=False, no_loc=False, no_rot=False, no_scale=False, keyflags=None):
"Apply the matrix to the transformation of the bone, taking locked channels, mode and certain constraints into account, and optionally keyframe it."
def set_transform_from_matrix(obj, bone_name, matrix, *, space='POSE', undo_copy_scale=False,
ignore_locks=False, no_loc=False, no_rot=False, no_scale=False, keyflags=None):
"""Apply the matrix to the transformation of the bone, taking locked channels, mode and certain
constraints into account, and optionally keyframe it."""
bone = obj.pose.bones[bone_name]
def restore_channels(prop, old_vec, locks, extra_lock):
@ -294,6 +305,7 @@ exec(SCRIPT_UTILITIES_KEYING[-1])
# Utilities for managing animation curves ##
############################################
# noinspection SpellCheckingInspection
SCRIPT_UTILITIES_CURVES = ['''
###########################
## Animation curve tools ##
@ -433,6 +445,24 @@ class DriverCurveTable(FCurveTable):
self.index_curves(self.anim_data.drivers)
''']
AnyCurveSet = None | FCurve | dict | Collection
flatten_curve_set: Callable[[AnyCurveSet], Iterator[FCurve]]
flatten_curve_key_set: Callable[..., set[float]]
get_curve_frame_set: Callable[..., set[float]]
set_curve_key_interpolation: Callable[..., None]
delete_curve_keys_in_range: Callable[..., None]
nla_tweak_to_scene: Callable
find_action: Callable[[bpy_struct], Action]
clean_action_empty_curves: Callable[[bpy_struct], None]
TRANSFORM_PROPS_LOCATION: frozenset[str]
TRANSFORM_PROPS_ROTATION = frozenset[str]
TRANSFORM_PROPS_SCALE = frozenset[str]
TRANSFORM_PROPS_ALL = frozenset[str]
transform_props_with_locks: Callable[[bool, bool, bool], set[str]]
FCurveTable: Any
ActionCurveTable: Any
DriverCurveTable: Any
exec(SCRIPT_UTILITIES_CURVES[-1])
################################################
@ -441,7 +471,9 @@ exec(SCRIPT_UTILITIES_CURVES[-1])
_SCRIPT_REGISTER_WM_PROPS = '''
bpy.types.WindowManager.rigify_transfer_use_all_keys = bpy.props.BoolProperty(
name="Bake All Keyed Frames", description="Bake on every frame that has a key for any of the bones, as opposed to just the relevant ones", default=False
name="Bake All Keyed Frames",
description="Bake on every frame that has a key for any of the bones, as opposed to just the relevant ones",
default=False
)
bpy.types.WindowManager.rigify_transfer_use_frame_range = bpy.props.BoolProperty(
name="Limit Frame Range", description="Only bake keyframes in a certain frame range", default=False
@ -461,6 +493,7 @@ del bpy.types.WindowManager.rigify_transfer_start_frame
del bpy.types.WindowManager.rigify_transfer_end_frame
'''
# noinspection SpellCheckingInspection
_SCRIPT_UTILITIES_BAKE_OPS = '''
class RIGIFY_OT_get_frame_range(bpy.types.Operator):
bl_idname = "rigify.get_frame_range" + ('_'+rig_id if rig_id else '')
@ -497,6 +530,8 @@ class RIGIFY_OT_get_frame_range(bpy.types.Operator):
row.operator(self.bl_idname, icon='TIME', text='')
'''
RIGIFY_OT_get_frame_range: Any
exec(_SCRIPT_UTILITIES_BAKE_OPS)
################################################
@ -505,6 +540,7 @@ exec(_SCRIPT_UTILITIES_BAKE_OPS)
SCRIPT_REGISTER_BAKE = ['RIGIFY_OT_get_frame_range']
# noinspection SpellCheckingInspection
SCRIPT_UTILITIES_BAKE = SCRIPT_UTILITIES_KEYING + SCRIPT_UTILITIES_CURVES + ['''
##################################
# Common bake operator settings ##
@ -756,6 +792,10 @@ class RigifySingleUpdateMixin(RigifyOperatorMixinBase):
return self.execute(context)
''']
RigifyOperatorMixinBase: Any
RigifyBakeKeyframesMixin: Any
RigifySingleUpdateMixin: Any
exec(SCRIPT_UTILITIES_BAKE[-1])
#####################################
@ -764,6 +804,7 @@ exec(SCRIPT_UTILITIES_BAKE[-1])
SCRIPT_REGISTER_OP_CLEAR_KEYS = ['POSE_OT_rigify_clear_keyframes']
# noinspection SpellCheckingInspection
SCRIPT_UTILITIES_OP_CLEAR_KEYS = ['''
#############################
## Generic Clear Keyframes ##
@ -806,14 +847,17 @@ class POSE_OT_rigify_clear_keyframes(bpy.types.Operator):
return {'FINISHED'}
''']
# noinspection PyDefaultArgument,PyUnusedLocal
def add_clear_keyframes_button(panel, *, bones=[], label='', text=''):
panel.use_bake_settings()
panel.script.add_utilities(SCRIPT_UTILITIES_OP_CLEAR_KEYS)
panel.script.register_classes(SCRIPT_REGISTER_OP_CLEAR_KEYS)
op_props = { 'bones': json.dumps(bones) }
op_props = {'bones': json.dumps(bones)}
panel.operator('pose.rigify_clear_keyframes_{rig_id}', text=text, icon='CANCEL', properties=op_props)
panel.operator('pose.rigify_clear_keyframes_{rig_id}', text=text, icon='CANCEL',
properties=op_props)
###################################
@ -822,6 +866,7 @@ def add_clear_keyframes_button(panel, *, bones=[], label='', text=''):
SCRIPT_REGISTER_OP_SNAP = ['POSE_OT_rigify_generic_snap', 'POSE_OT_rigify_generic_snap_bake']
# noinspection SpellCheckingInspection
SCRIPT_UTILITIES_OP_SNAP = ['''
#############################
## Generic Snap (FK to IK) ##
@ -875,11 +920,13 @@ class POSE_OT_rigify_generic_snap_bake(RigifyGenericSnapBase, RigifyBakeKeyframe
return self.bake_get_all_bone_curves(self.output_bone_list, props)
''']
def add_fk_ik_snap_buttons(panel, op_single, op_bake, *, label=None, rig_name='', properties=None, clear_bones=None, compact=None):
def add_fk_ik_snap_buttons(panel, op_single, op_bake, *, label=None, rig_name='', properties=None,
clear_bones=None, compact=None):
assert label and properties
if rig_name:
label += ' (%s)' % (rig_name)
label += ' (%s)' % rig_name
if compact or not clear_bones:
row = panel.row(align=True)
@ -895,7 +942,10 @@ def add_fk_ik_snap_buttons(panel, op_single, op_bake, *, label=None, rig_name=''
row.operator(op_bake, text='Action', icon='ACTION_TWEAK', properties=properties)
add_clear_keyframes_button(row, bones=clear_bones, text='Clear')
def add_generic_snap(panel, *, output_bones=[], input_bones=[], input_ctrl_bones=[], label='Snap', rig_name='', undo_copy_scale=False, compact=None, clear=True, locks=None, tooltip=None):
# noinspection PyDefaultArgument
def add_generic_snap(panel, *, output_bones=[], input_bones=[], input_ctrl_bones=[], label='Snap',
rig_name='', undo_copy_scale=False, compact=None, clear=True, locks=None, tooltip=None):
panel.use_bake_settings()
panel.script.add_utilities(SCRIPT_UTILITIES_OP_SNAP)
panel.script.register_classes(SCRIPT_REGISTER_OP_SNAP)
@ -920,12 +970,16 @@ def add_generic_snap(panel, *, output_bones=[], input_bones=[], input_ctrl_bones
label=label, rig_name=rig_name, properties=op_props, clear_bones=clear_bones, compact=compact,
)
def add_generic_snap_fk_to_ik(panel, *, fk_bones=[], ik_bones=[], ik_ctrl_bones=[], label='FK->IK', rig_name='', undo_copy_scale=False, compact=None, clear=True):
# noinspection PyDefaultArgument
def add_generic_snap_fk_to_ik(panel, *, fk_bones=[], ik_bones=[], ik_ctrl_bones=[], label='FK->IK',
rig_name='', undo_copy_scale=False, compact=None, clear=True):
add_generic_snap(
panel, output_bones=fk_bones, input_bones=ik_bones, input_ctrl_bones=ik_ctrl_bones,
label=label, rig_name=rig_name, undo_copy_scale=undo_copy_scale, compact=compact, clear=clear
)
###############################
# Module register/unregister ##
###############################
@ -937,6 +991,7 @@ def register():
register_class(RIGIFY_OT_get_frame_range)
def unregister():
from bpy.utils import unregister_class

View File

@ -2,15 +2,18 @@
import bpy
import math
from mathutils import Vector, Matrix, Color
from mathutils import Vector, Matrix
from typing import Optional, Callable
from .errors import MetarigError
from .naming import get_name, make_derived_name, is_control_bone
from .misc import pairwise
from .misc import pairwise, ArmatureObject
#=======================
########################
# Bone collection
#=======================
########################
class BoneDict(dict):
"""
@ -18,23 +21,22 @@ class BoneDict(dict):
Allows access to contained items as attributes, and only
accepts certain types of values.
@DynamicAttrs
"""
@staticmethod
def __sanitize_attr(key, value):
if hasattr(BoneDict, key):
raise KeyError("Invalid BoneDict key: %s" % (key))
raise KeyError(f"Invalid BoneDict key: {key}")
if (value is None or
isinstance(value, str) or
isinstance(value, list) or
isinstance(value, BoneDict)):
if value is None or isinstance(value, (str, list, BoneDict)):
return value
if isinstance(value, dict):
return BoneDict(value)
raise ValueError("Invalid BoneDict value: %r" % (value))
raise ValueError(f"Invalid BoneDict value: {repr(value)}")
def __init__(self, *args, **kwargs):
super().__init__()
@ -71,13 +73,14 @@ class BoneDict(dict):
return all_bones
#=======================
########################
# Bone manipulation
#=======================
########################
#
# NOTE: PREFER USING BoneUtilityMixin IN NEW STYLE RIGS!
def get_bone(obj, bone_name):
def get_bone(obj: ArmatureObject, bone_name: Optional[str]):
"""Get EditBone or PoseBone by name, depending on the current mode."""
if not bone_name:
return None
@ -87,7 +90,7 @@ def get_bone(obj, bone_name):
return bones[bone_name]
def new_bone(obj, bone_name):
def new_bone(obj: ArmatureObject, bone_name: str):
""" Adds a new bone to the given armature object.
Returns the resulting bone's name.
"""
@ -102,11 +105,13 @@ def new_bone(obj, bone_name):
raise MetarigError("Can't add new bone '%s' outside of edit mode" % bone_name)
def copy_bone(obj, bone_name, assign_name='', *, parent=False, inherit_scale=False, bbone=False, length=None, scale=None):
def copy_bone(obj: ArmatureObject, bone_name: str, assign_name='', *,
parent=False, inherit_scale=False, bbone=False,
length: Optional[float] = None, scale: Optional[float] = None):
""" Makes a copy of the given bone in the given armature object.
Returns the resulting bone's name.
"""
#if bone_name not in obj.data.bones:
if bone_name not in obj.data.edit_bones:
raise MetarigError("copy_bone(): bone '%s' not found, cannot copy it" % bone_name)
@ -116,7 +121,6 @@ def copy_bone(obj, bone_name, assign_name='', *, parent=False, inherit_scale=Fal
# Copy the edit bone
edit_bone_1 = obj.data.edit_bones[bone_name]
edit_bone_2 = obj.data.edit_bones.new(assign_name)
bone_name_1 = bone_name
bone_name_2 = edit_bone_2.name
# Copy edit bone attributes
@ -137,6 +141,7 @@ def copy_bone(obj, bone_name, assign_name='', *, parent=False, inherit_scale=Fal
edit_bone_2.inherit_scale = edit_bone_1.inherit_scale
if bbone:
# noinspection SpellCheckingInspection
for name in ['bbone_segments',
'bbone_easein', 'bbone_easeout',
'bbone_rollin', 'bbone_rollout',
@ -155,9 +160,10 @@ def copy_bone(obj, bone_name, assign_name='', *, parent=False, inherit_scale=Fal
raise MetarigError("Cannot copy bones outside of edit mode")
def copy_bone_properties(obj, bone_name_1, bone_name_2, transforms=True, props=True, widget=True):
def copy_bone_properties(obj: ArmatureObject, bone_name_1: str, bone_name_2: str,
transforms=True, props=True, widget=True):
""" Copy transform and custom properties from bone 1 to bone 2. """
if obj.mode in {'OBJECT','POSE'}:
if obj.mode in {'OBJECT', 'POSE'}:
# Get the pose bones
pose_bone_1 = obj.pose.bones[bone_name_1]
pose_bone_2 = obj.pose.bones[bone_name_2]
@ -197,7 +203,7 @@ def _legacy_copy_bone(obj, bone_name, assign_name=''):
return new_name
def flip_bone(obj, bone_name):
def flip_bone(obj: ArmatureObject, bone_name: str):
""" Flips an edit bone.
"""
if bone_name not in obj.data.edit_bones:
@ -214,11 +220,11 @@ def flip_bone(obj, bone_name):
raise MetarigError("Cannot flip bones outside of edit mode")
def flip_bone_chain(obj, bone_names):
def flip_bone_chain(obj: ArmatureObject, bone_names: list[str]):
"""Flips a connected bone chain."""
assert obj.mode == 'EDIT'
bones = [ obj.data.edit_bones[name] for name in bone_names ]
bones = [obj.data.edit_bones[name] for name in bone_names]
# Verify chain and unparent
for prev_bone, bone in pairwise(bones):
@ -242,7 +248,9 @@ def flip_bone_chain(obj, bone_names):
bone.use_connect = True
def put_bone(obj, bone_name, pos, *, matrix=None, length=None, scale=None):
def put_bone(obj: ArmatureObject, bone_name: str, pos: Optional[Vector], *,
matrix: Optional[Matrix] = None,
length: Optional[float] = None, scale: Optional[float] = None):
""" Places a bone at the given position.
"""
if bone_name not in obj.data.edit_bones:
@ -274,13 +282,14 @@ def put_bone(obj, bone_name, pos, *, matrix=None, length=None, scale=None):
raise MetarigError("Cannot 'put' bones outside of edit mode")
def disable_bbones(obj, bone_names):
def disable_bbones(obj: ArmatureObject, bone_names: list[str]):
"""Disables B-Bone segments on the specified bones."""
assert(obj.mode != 'EDIT')
for bone in bone_names:
obj.data.bones[bone].bbone_segments = 1
# noinspection SpellCheckingInspection
def _legacy_make_nonscaling_child(obj, bone_name, location, child_name_postfix=""):
""" Takes the named bone and creates a non-scaling child of it at
the given location. The returned bone (returned by name) is not
@ -345,48 +354,65 @@ def _legacy_make_nonscaling_child(obj, bone_name, location, child_name_postfix="
raise MetarigError("Cannot make nonscaling child outside of edit mode")
#===================================
####################################
# Bone manipulation as rig methods
#===================================
####################################
class BoneUtilityMixin(object):
obj: ArmatureObject
register_new_bone: Callable[[str, Optional[str]], None]
"""
Provides methods for more convenient creation of bones.
Requires self.obj to be the armature object being worked on.
"""
def register_new_bone(self, new_name, old_name=None):
def register_new_bone(self, new_name: str, old_name: Optional[str] = None):
"""Registers creation or renaming of a bone based on old_name"""
pass
def new_bone(self, new_name):
def new_bone(self, new_name: str) -> str:
"""Create a new bone with the specified name."""
name = new_bone(self.obj, new_name)
self.register_new_bone(name)
self.register_new_bone(name, None)
return name
def copy_bone(self, bone_name, new_name='', *, parent=False, inherit_scale=False, bbone=False, length=None, scale=None):
def copy_bone(self, bone_name: str, new_name='', *,
parent=False, inherit_scale=False, bbone=False,
length: Optional[float] = None,
scale: Optional[float] = None) -> str:
"""Copy the bone with the given name, returning the new name."""
name = copy_bone(self.obj, bone_name, new_name, parent=parent, inherit_scale=inherit_scale, bbone=bbone, length=length, scale=scale)
name = copy_bone(self.obj, bone_name, new_name,
parent=parent, inherit_scale=inherit_scale,
bbone=bbone, length=length, scale=scale)
self.register_new_bone(name, bone_name)
return name
def copy_bone_properties(self, src_name, tgt_name, *, props=True, ui_controls=None, **kwargs):
"""Copy pose-mode properties of the bone."""
def copy_bone_properties(self, src_name: str, tgt_name: str, *,
props=True,
ui_controls: list[str] | bool | None = None,
**kwargs):
"""Copy pose-mode properties of the bone. For using ui_controls, self must be a Rig."""
if ui_controls:
from ..base_rig import BaseRig
assert isinstance(self, BaseRig)
if props:
if ui_controls is None and is_control_bone(tgt_name) and hasattr(self, 'script'):
ui_controls = [tgt_name]
elif ui_controls is True:
ui_controls = self.bones.flatten('ctrl')
copy_bone_properties(self.obj, src_name, tgt_name, props=props and not ui_controls, **kwargs)
copy_bone_properties(
self.obj, src_name, tgt_name, props=props and not ui_controls, **kwargs)
if props and ui_controls:
from .mechanism import copy_custom_properties_with_ui
copy_custom_properties_with_ui(self, src_name, tgt_name, ui_controls=ui_controls)
def rename_bone(self, old_name, new_name):
def rename_bone(self, old_name: str, new_name: str) -> str:
"""Rename the bone, returning the actual new name."""
bone = self.get_bone(old_name)
bone.name = new_name
@ -394,15 +420,17 @@ class BoneUtilityMixin(object):
self.register_new_bone(bone.name, old_name)
return bone.name
def get_bone(self, bone_name):
def get_bone(self, bone_name: Optional[str])\
-> Optional[bpy.types.EditBone | bpy.types.PoseBone]:
"""Get EditBone or PoseBone by name, depending on the current mode."""
return get_bone(self.obj, bone_name)
def get_bone_parent(self, bone_name):
def get_bone_parent(self, bone_name: str) -> Optional[str]:
"""Get the name of the parent bone, or None."""
return get_name(self.get_bone(bone_name).parent)
def set_bone_parent(self, bone_name, parent_name, use_connect=False, inherit_scale=None):
def set_bone_parent(self, bone_name: str, parent_name: Optional[str],
use_connect=False, inherit_scale: Optional[str] = None):
"""Set the parent of the bone."""
eb = self.obj.data.edit_bones
bone = eb[bone_name]
@ -412,16 +440,20 @@ class BoneUtilityMixin(object):
bone.inherit_scale = inherit_scale
bone.parent = (eb[parent_name] if parent_name else None)
def parent_bone_chain(self, bone_names, use_connect=None, inherit_scale=None):
def parent_bone_chain(self, bone_names: list[str],
use_connect: Optional[bool] = None,
inherit_scale: Optional[str] = None):
"""Link bones into a chain with parenting. First bone may be None."""
for parent, child in pairwise(bone_names):
self.set_bone_parent(child, parent, use_connect=use_connect, inherit_scale=inherit_scale)
self.set_bone_parent(
child, parent, use_connect=use_connect, inherit_scale=inherit_scale)
#=============================================
##############################################
# B-Bones
#=============================================
##############################################
def connect_bbone_chain_handles(obj, bone_names):
def connect_bbone_chain_handles(obj: ArmatureObject, bone_names: list[str]):
assert obj.mode == 'EDIT'
for prev_name, next_name in pairwise(bone_names):
@ -434,26 +466,28 @@ def connect_bbone_chain_handles(obj, bone_names):
next_bone.bbone_handle_type_start = 'ABSOLUTE'
next_bone.bbone_custom_handle_start = prev_bone
#=============================================
##############################################
# Math
#=============================================
##############################################
def is_same_position(obj, bone_name1, bone_name2):
def is_same_position(obj: ArmatureObject, bone_name1: str, bone_name2: str):
head1 = get_bone(obj, bone_name1).head
head2 = get_bone(obj, bone_name2).head
return (head1 - head2).length < 1e-5
def is_connected_position(obj, bone_name1, bone_name2):
def is_connected_position(obj: ArmatureObject, bone_name1: str, bone_name2: str):
tail1 = get_bone(obj, bone_name1).tail
head2 = get_bone(obj, bone_name2).head
return (tail1 - head2).length < 1e-5
def copy_bone_position(obj, bone_name, target_bone_name, *, length=None, scale=None):
def copy_bone_position(obj: ArmatureObject, bone_name: str, target_bone_name: str, *,
length: Optional[float] = None,
scale: Optional[float] = None):
""" Completely copies the position and orientation of the bone. """
bone1_e = obj.data.edit_bones[bone_name]
bone2_e = obj.data.edit_bones[target_bone_name]
@ -469,7 +503,7 @@ def copy_bone_position(obj, bone_name, target_bone_name, *, length=None, scale=N
bone2_e.length *= scale
def align_bone_orientation(obj, bone_name, target_bone_name):
def align_bone_orientation(obj: ArmatureObject, bone_name: str, target_bone_name: str):
""" Aligns the orientation of bone to target bone. """
bone1_e = obj.data.edit_bones[bone_name]
bone2_e = obj.data.edit_bones[target_bone_name]
@ -480,7 +514,7 @@ def align_bone_orientation(obj, bone_name, target_bone_name):
bone1_e.roll = bone2_e.roll
def set_bone_orientation(obj, bone_name, orientation):
def set_bone_orientation(obj: ArmatureObject, bone_name: str, orientation: str | Matrix):
""" Aligns the orientation of bone to target bone or matrix. """
if isinstance(orientation, str):
align_bone_orientation(obj, bone_name, orientation)
@ -494,7 +528,7 @@ def set_bone_orientation(obj, bone_name, orientation):
bone_e.matrix = matrix
def align_bone_roll(obj, bone1, bone2):
def align_bone_roll(obj: ArmatureObject, bone1: str, bone2: str):
""" Aligns the roll of two bones.
"""
bone1_e = obj.data.edit_bones[bone1]
@ -539,7 +573,7 @@ def align_bone_roll(obj, bone1, bone2):
bone1_e.roll = -roll
def align_bone_x_axis(obj, bone, vec):
def align_bone_x_axis(obj: ArmatureObject, bone: str, vec: Vector):
""" Rolls the bone to align its x-axis as closely as possible to
the given vector.
Must be in edit mode.
@ -564,7 +598,7 @@ def align_bone_x_axis(obj, bone, vec):
bone_e.roll += angle * 2
def align_bone_z_axis(obj, bone, vec):
def align_bone_z_axis(obj: ArmatureObject, bone: str, vec: Vector):
""" Rolls the bone to align its z-axis as closely as possible to
the given vector.
Must be in edit mode.
@ -589,7 +623,7 @@ def align_bone_z_axis(obj, bone, vec):
bone_e.roll += angle * 2
def align_bone_y_axis(obj, bone, vec):
def align_bone_y_axis(obj: ArmatureObject, bone: str, vec: Vector):
""" Matches the bone y-axis to
the given vector.
Must be in edit mode.
@ -597,14 +631,15 @@ def align_bone_y_axis(obj, bone, vec):
bone_e = obj.data.edit_bones[bone]
vec.normalize()
vec = vec * bone_e.length
bone_e.tail = bone_e.head + vec
def compute_chain_x_axis(obj, bone_names):
def compute_chain_x_axis(obj: ArmatureObject, bone_names: list[str]):
"""
Compute the x axis of all bones to be perpendicular
Compute the X axis of all bones to be perpendicular
to the primary plane in which the bones lie.
"""
eb = obj.data.edit_bones
@ -615,6 +650,7 @@ def compute_chain_x_axis(obj, bone_names):
# Compute normal to the plane defined by the first bone,
# and the end of the last bone in the chain
chain_y_axis = last_bone.tail - first_bone.head
chain_rot_axis = first_bone.y_axis.cross(chain_y_axis)
@ -624,9 +660,9 @@ def compute_chain_x_axis(obj, bone_names):
return chain_rot_axis.normalized()
def align_chain_x_axis(obj, bone_names):
def align_chain_x_axis(obj: ArmatureObject, bone_names: list[str]):
"""
Aligns the x axis of all bones to be perpendicular
Aligns the X axis of all bones to be perpendicular
to the primary plane in which the bones lie.
"""
chain_rot_axis = compute_chain_x_axis(obj, bone_names)
@ -635,7 +671,10 @@ def align_chain_x_axis(obj, bone_names):
align_bone_x_axis(obj, name, chain_rot_axis)
def align_bone_to_axis(obj, bone_name, axis, *, length=None, roll=0, flip=False):
def align_bone_to_axis(obj: ArmatureObject, bone_name: str, axis: str, *,
length: Optional[float] = None,
roll: Optional[float] = 0.0,
flip=False):
"""
Aligns the Y axis of the bone to the global axis (x,y,z,-x,-y,-z),
optionally adjusting length and initially flipping the bone.
@ -651,7 +690,7 @@ def align_bone_to_axis(obj, bone_name, axis, *, length=None, roll=0, flip=False)
length = -length
axis = axis[1:]
vec = Vector((0,0,0))
vec = Vector((0, 0, 0))
setattr(vec, axis, length)
if flip:
@ -664,7 +703,9 @@ def align_bone_to_axis(obj, bone_name, axis, *, length=None, roll=0, flip=False)
bone_e.roll = roll
def set_bone_widget_transform(obj, bone_name, transform_bone, use_size=True, scale=1.0, target_size=False):
def set_bone_widget_transform(obj: ArmatureObject, bone_name: str,
transform_bone: Optional[str], *,
use_size=True, scale=1.0, target_size=False):
assert obj.mode != 'EDIT'
bone = obj.pose.bones[bone_name]

View File

@ -2,12 +2,16 @@
import bpy
from typing import Optional, Sequence
from bpy.types import LayerCollection, Collection, Object, Context
#=============================================
##############################################
# Collection management
#=============================================
##############################################
def find_layer_collection_by_collection(layer_collection, collection):
def find_layer_collection_by_collection(layer_collection: LayerCollection,
collection: Collection) -> Optional[LayerCollection]:
if collection == layer_collection.collection:
return layer_collection
@ -18,7 +22,8 @@ def find_layer_collection_by_collection(layer_collection, collection):
return layer_collection
def list_layer_collections(layer_collection, visible=False, selectable=False):
def list_layer_collections(layer_collection: LayerCollection,
visible=False, selectable=False) -> list[LayerCollection]:
"""Returns a list of the collection and its children, with optional filtering by settings."""
if layer_collection.exclude:
@ -39,12 +44,13 @@ def list_layer_collections(layer_collection, visible=False, selectable=False):
return found
def filter_layer_collections_by_object(layer_collections, obj):
def filter_layer_collections_by_object(layer_collections: Sequence[LayerCollection],
obj: Object) -> list[LayerCollection]:
"""Returns a subset of collections that contain the given object."""
return [lc for lc in layer_collections if obj in lc.collection.objects.values()]
def ensure_collection(context, collection_name, hidden=False) -> bpy.types.Collection:
def ensure_collection(context: Context, collection_name: str, hidden=False) -> Collection:
"""Check if a collection with a certain name exists.
If yes, return it, if not, create it in the scene root collection.
"""

View File

@ -1,28 +1,34 @@
# SPDX-License-Identifier: GPL-2.0-or-later
import bpy
from typing import Optional
from mathutils import Vector, Matrix
from .naming import make_derived_name
from .bones import put_bone, copy_bone_position, align_bone_orientation
from .widgets_basic import create_pivot_widget
from .misc import force_lazy
from .misc import force_lazy, OptionalLazy
from ..base_rig import RigComponent, stage
from ..base_rig import BaseRig, RigComponent
class CustomPivotControl(RigComponent):
"""
A utility that generates a pivot control with a custom position.
Generates a control bone, and a MCH output bone.
Generates a control bone, and an MCH output bone.
"""
ctrl: str
mch: str
def __init__(
self, rig, id_name, org_bone, *,
name=None, parent=None, position=None, matrix=None,
scale=1.0, scale_mch=None,
move_to=None, align_to=None, snap_to=None,
widget_axis=1.5, widget_cap=1.0, widget_square=True,
self, rig: BaseRig, id_name: str, org_bone: str, *,
name: Optional[str] = None, parent: OptionalLazy[str] = None,
position: Optional[Vector] = None, matrix: Optional[Matrix] = None,
scale: float = 1.0, scale_mch: Optional[float] = None,
move_to: OptionalLazy[str] = None, align_to: OptionalLazy[str] = None,
snap_to: OptionalLazy[str] = None,
widget_axis: float = 1.5, widget_cap: float = 1.0, widget_square: bool = True,
):
super().__init__(rig)
@ -53,9 +59,12 @@ class CustomPivotControl(RigComponent):
def output(self):
return self.mch
def do_make_bones(self, org, name, position, matrix):
self.bones.ctrl[self.id_name] = self.ctrl = self.copy_bone(org, name, parent=not self.parent, scale=self.scale)
self.bones.mch[self.id_name] = self.mch = self.copy_bone(org, make_derived_name(name, 'mch'), scale=self.scale_mch)
def do_make_bones(self, org: str, name: str,
position: Optional[Vector], matrix: Optional[Matrix]):
self.bones.ctrl[self.id_name] = self.ctrl =\
self.copy_bone(org, name, parent=not self.parent, scale=self.scale)
self.bones.mch[self.id_name] = self.mch =\
self.copy_bone(org, make_derived_name(name, 'mch'), scale=self.scale_mch)
if position or matrix:
put_bone(self.obj, self.ctrl, position, matrix=matrix)
@ -83,7 +92,9 @@ class CustomPivotControl(RigComponent):
self.set_bone_parent(self.mch, self.ctrl)
def rig_bones(self):
self.make_constraint(self.mch, 'COPY_LOCATION', self.ctrl, space='LOCAL', invert_xyz=(True,)*3)
self.make_constraint(
self.mch, 'COPY_LOCATION', self.ctrl, space='LOCAL', invert_xyz=(True,)*3)
def generate_widgets(self):
create_pivot_widget(self.obj, self.ctrl, axis_size=self.widget_axis, cap_size=self.widget_cap, square=self.widget_square)
create_pivot_widget(self.obj, self.ctrl, axis_size=self.widget_axis,
cap_size=self.widget_cap, square=self.widget_square)

View File

@ -1,9 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-or-later
#=======================================================================
# Error handling
#=======================================================================
class MetarigError(Exception):
""" Exception raised for errors.
@ -16,7 +12,9 @@ class MetarigError(Exception):
class RaiseErrorMixin(object):
def raise_error(self, message, *args, **kwargs):
base_bone: str
def raise_error(self, message: str, *args, **kwargs):
from .naming import strip_org
message = message.format(*args, **kwargs)

View File

@ -2,6 +2,12 @@
import bpy
from typing import TYPE_CHECKING, Sequence, Optional, Mapping
from bpy.types import Bone, UILayout, Object, PoseBone, Armature
if TYPE_CHECKING:
from ..base_rig import BaseRig
ORG_LAYER = [n == 31 for n in range(0, 32)] # Armature layer that original bones should be moved to.
MCH_LAYER = [n == 30 for n in range(0, 32)] # Armature layer that mechanism bones should be moved to.
@ -9,20 +15,20 @@ DEF_LAYER = [n == 29 for n in range(0, 32)] # Armature layer that deformation b
ROOT_LAYER = [n == 28 for n in range(0, 32)] # Armature layer that root bone should be moved to.
def get_layers(layers):
def get_layers(layers) -> list[bool]:
""" Does its best to extract a set of layers from any data thrown at it.
"""
if type(layers) == int:
return [x == layers for x in range(0, 32)]
elif type(layers) == str:
s = layers.split(",")
l = []
for i in s:
items = layers.split(",")
layers = []
for i in items:
try:
l += [int(float(i))]
layers += [int(float(i))]
except ValueError:
pass
return [x in l for x in range(0, 32)]
return [x in layers for x in range(0, 32)]
elif type(layers) == tuple or type(layers) == list:
return [x in layers for x in range(0, 32)]
else:
@ -34,20 +40,20 @@ def get_layers(layers):
return [x in layers for x in range(0, 32)]
def set_bone_layers(bone, layers, combine=False):
def set_bone_layers(bone: Bone, layers: Sequence[bool], combine=False):
if combine:
bone.layers = [ a or b for a, b in zip(bone.layers, layers) ]
bone.layers = [a or b for a, b in zip(bone.layers, layers)]
else:
bone.layers = layers
#=============================================
##############################################
# UI utilities
#=============================================
##############################################
def layout_layer_buttons(layout, params, option, active_layers):
"Draw a layer selection button UI with certain layers marked with dots."
def layout_layer_buttons(layout: UILayout, params, option: str, active_layers: Sequence[bool]):
"""Draw a layer selection button UI with certain layers marked with dots."""
outer = layout.row()
for x in [0, 8]:
@ -64,36 +70,46 @@ def layout_layer_buttons(layout, params, option, active_layers):
class ControlLayersOption:
def __init__(self, name, toggle_name=None, toggle_default=True, description="Set of control layers"):
def __init__(self, name: str,
toggle_name: Optional[str] = None,
toggle_default=True, description="Set of control layers"):
self.name = name
self.toggle_default = toggle_default
self.description = description
self.toggle_option = self.name+'_layers_extra'
self.layers_option = self.name+'_layers'
self.toggle_name = toggle_name if toggle_name else "Assign " + self.name.title() + " Layers"
def get(self, params):
if toggle_name:
self.toggle_name = toggle_name
else:
self.toggle_name = "Assign " + self.name.title() + " Layers"
def get(self, params) -> Optional[list[bool]]:
if getattr(params, self.toggle_option):
return list(getattr(params, self.layers_option))
else:
return None
def assign(self, params, bone_set, bone_list, combine=False):
def assign(self, params,
bone_set: Object | Mapping[str, Bone | PoseBone],
bone_list: Sequence[str],
combine=False):
layers = self.get(params)
if isinstance(bone_set, bpy.types.Object):
if isinstance(bone_set, Object):
assert isinstance(bone_set.data, Armature)
bone_set = bone_set.data.bones
if layers:
for name in bone_list:
bone = bone_set[name]
if isinstance(bone, bpy.types.PoseBone):
if isinstance(bone, PoseBone):
bone = bone.bone
set_bone_layers(bone, layers, combine)
def assign_rig(self, rig, bone_list, combine=False, priority=None):
def assign_rig(self, rig: 'BaseRig', bone_list: Sequence[str], combine=False, priority=None):
layers = self.get(rig.params)
bone_set = rig.obj.data.bones
@ -122,7 +138,7 @@ class ControlLayersOption:
setattr(params, self.layers_option, prop_layers)
def parameters_ui(self, layout, params):
def parameters_ui(self, layout: UILayout, params):
box = layout.box()
box.prop(params, self.toggle_option)
@ -136,8 +152,10 @@ class ControlLayersOption:
layout_layer_buttons(box, params, self.layers_option, active_layers)
ControlLayersOption.FK = ControlLayersOption('fk', description="Layers for the FK controls to be on")
ControlLayersOption.TWEAK = ControlLayersOption('tweak', description="Layers for the tweak controls to be on")
ControlLayersOption.FK = ControlLayersOption(
'fk', description="Layers for the FK controls to be on")
ControlLayersOption.TWEAK = ControlLayersOption(
'tweak', description="Layers for the tweak controls to be on")
ControlLayersOption.EXTRA_IK = ControlLayersOption(
'extra_ik', toggle_default=False,
@ -146,8 +164,10 @@ ControlLayersOption.EXTRA_IK = ControlLayersOption(
)
# Layer parameters used by the super_face rig.
ControlLayersOption.FACE_PRIMARY = ControlLayersOption('primary', description="Layers for the primary controls to be on")
ControlLayersOption.FACE_SECONDARY = ControlLayersOption('secondary', description="Layers for the secondary controls to be on")
ControlLayersOption.FACE_PRIMARY = ControlLayersOption(
'primary', description="Layers for the primary controls to be on")
ControlLayersOption.FACE_SECONDARY = ControlLayersOption(
'secondary', description="Layers for the secondary controls to be on")
# Layer parameters used by the skin rigs
ControlLayersOption.SKIN_PRIMARY = ControlLayersOption(

View File

@ -3,31 +3,49 @@
import bpy
import re
from bpy.types import bpy_prop_collection, Material
from typing import TYPE_CHECKING, Optional, Any, Collection
from bpy.types import (bpy_prop_collection, Material, Object, PoseBone, Driver, FCurve,
DriverTarget, ID, bpy_struct, FModifierGenerator, Constraint, AnimData,
ArmatureConstraint)
from rna_prop_ui import rna_idprop_ui_create
from rna_prop_ui import rna_idprop_quote_path as quote_property
from .misc import force_lazy
from .misc import force_lazy, ArmatureObject, Lazy
#=============================================
if TYPE_CHECKING:
from ..base_rig import BaseRig
##############################################
# Constraint creation utilities
#=============================================
##############################################
_TRACK_AXIS_MAP = {
_TRACK_AXIS_MAP = {
'X': 'TRACK_X', '-X': 'TRACK_NEGATIVE_X',
'Y': 'TRACK_Y', '-Y': 'TRACK_NEGATIVE_Y',
'Z': 'TRACK_Z', '-Z': 'TRACK_NEGATIVE_Z',
}
def _set_default_attr(obj, options, attr, value):
if hasattr(obj, attr):
options.setdefault(attr, value)
def make_constraint(
owner, con_type, target=None, subtarget=None, *, insert_index=None,
space=None, track_axis=None, use_xyz=None, use_limit_xyz=None, invert_xyz=None,
targets=None, **options):
owner: Object | PoseBone, con_type: str,
target: Optional[Object] = None,
subtarget: Optional[str] = None, *,
insert_index: Optional[int] = None,
space: Optional[str] = None,
track_axis: Optional[str] = None,
use_xyz: Optional[Collection[bool]] = None,
use_limit_xyz: Optional[Collection[bool]] = None,
invert_xyz: Optional[Collection[bool]] = None,
targets: list[Lazy[str | tuple | dict]] = None,
**options):
"""
Creates and initializes constraint of the specified type for the owner bone.
@ -52,7 +70,7 @@ def make_constraint(
# For Armature constraints, allow passing a "targets" list as a keyword argument.
if targets is not None:
assert con.type == 'ARMATURE'
assert isinstance(con, ArmatureConstraint)
for target_info in targets:
con_target = con.targets.new()
con_target.target = owner.id_data
@ -66,6 +84,7 @@ def make_constraint(
else:
con_target.target, con_target.subtarget, con_target.weight = map(force_lazy, target_info)
else:
assert isinstance(target_info, dict)
for key, val in target_info.items():
setattr(con_target, key, force_lazy(val))
@ -104,13 +123,15 @@ def make_constraint(
return con
#=============================================
# Custom property creation utilities
#=============================================
##############################################
# Custom property creation utilities
##############################################
# noinspection PyShadowingBuiltins
def make_property(
owner, name, default, *, min=0.0, max=1.0, soft_min=None, soft_max=None,
description=None, overridable=True, **options):
owner, name: str, default, *, min=0.0, max=1.0, soft_min=None, soft_max=None,
description: Optional[str] = None, overridable=True, **options):
"""
Creates and initializes a custom property of owner.
@ -120,18 +141,19 @@ def make_property(
# Some keyword argument defaults differ
rna_idprop_ui_create(
owner, name, default = default,
min = min, max = max, soft_min = soft_min, soft_max = soft_max,
description = description or name,
overridable = overridable,
owner, name, default=default,
min=min, max=max, soft_min=soft_min, soft_max=soft_max,
description=description or name,
overridable=overridable,
**options
)
#=============================================
# Driver creation utilities
#=============================================
def _init_driver_target(drv_target, var_info, target_id):
##############################################
# Driver creation utilities
##############################################
def _init_driver_target(drv_target: DriverTarget, var_info, target_id: Optional[ID]):
"""Initialize a driver variable target from a specification."""
# Parse the simple list format for the common case.
@ -140,9 +162,9 @@ def _init_driver_target(drv_target, var_info, target_id):
# If target_id is supplied as parameter, allow omitting it
if target_id is None or isinstance(var_info[0], bpy.types.ID):
target_id,subtarget,*refs = var_info
target_id, subtarget, *refs = var_info
else:
subtarget,*refs = var_info
subtarget, *refs = var_info
subtarget = force_lazy(subtarget)
@ -165,7 +187,7 @@ def _init_driver_target(drv_target, var_info, target_id):
if isinstance(item, str):
path += item if item[0] == '.' else quote_property(item)
else:
path += '[%r]' % (item)
path += f'[{repr(item)}]'
if path[0] == '.':
path = path[1:]
@ -184,7 +206,7 @@ def _init_driver_target(drv_target, var_info, target_id):
setattr(drv_target, tp, force_lazy(tv))
def _add_driver_variable(drv, var_name, var_info, target_id):
def _add_driver_variable(drv: Driver, var_name: str, var_info, target_id: Optional[ID]):
"""Add and initialize a driver variable."""
var = drv.variables.new()
@ -209,7 +231,13 @@ def _add_driver_variable(drv, var_name, var_info, target_id):
elif p != 'type':
setattr(var, p, force_lazy(v))
def make_driver(owner, prop, *, index=-1, type='SUM', expression=None, variables={}, polynomial=None, target_id=None):
# noinspection PyIncorrectDocstring,PyShadowingBuiltins,PyDefaultArgument
def make_driver(owner: bpy_struct, prop: str, *, index=-1, type='SUM',
expression: Optional[str] = None,
variables: list | dict = {},
polynomial: Optional[list[float]] = None,
target_id: Optional[ID] = None) -> FCurve:
"""
Creates and initializes a driver for the 'prop' property of owner.
@ -222,7 +250,7 @@ def make_driver(owner, prop, *, index=-1, type='SUM', expression=None, variables
Specification format:
If the variables argument is a dictionary, keys specify variable names.
Otherwise names are set to var, var1, var2, ... etc:
Otherwise, names are set to var, var1, var2, ... etc:
variables = [ ..., ..., ... ]
variables = { 'var': ..., 'var1': ..., 'var2': ... }
@ -288,20 +316,22 @@ def make_driver(owner, prop, *, index=-1, type='SUM', expression=None, variables
if polynomial is not None:
drv_modifier = fcu.modifiers.new('GENERATOR')
assert isinstance(drv_modifier, FModifierGenerator)
drv_modifier.mode = 'POLYNOMIAL'
drv_modifier.poly_order = len(polynomial)-1
for i,v in enumerate(polynomial):
for i, v in enumerate(polynomial):
drv_modifier.coefficients[i] = v
return fcu
#=============================================
##############################################
# Driver variable utilities
#=============================================
##############################################
def driver_var_transform(target, bone=None, *, type='LOC_X', space='WORLD', rotation_mode='AUTO'):
# noinspection PyShadowingBuiltins
def driver_var_transform(target: ID, bone: Optional[str] = None, *,
type='LOC_X', space='WORLD', rotation_mode='AUTO'):
"""
Create a Transform Channel driver variable specification.
@ -323,10 +353,14 @@ def driver_var_transform(target, bone=None, *, type='LOC_X', space='WORLD', rota
if bone is not None:
target_map['bone_target'] = bone
return { 'type': 'TRANSFORMS', 'targets': [ target_map ] }
return {'type': 'TRANSFORMS', 'targets': [target_map]}
def driver_var_distance(target, *, bone1=None, target2=None, bone2=None, space1='WORLD', space2='WORLD'):
def driver_var_distance(target: ID, *,
bone1: Optional[str] = None,
target2: Optional[ID] = None,
bone2: Optional[str] = None,
space1='WORLD', space2='WORLD'):
"""
Create a Distance driver variable specification.
@ -358,11 +392,11 @@ def driver_var_distance(target, *, bone1=None, target2=None, bone2=None, space1=
return {'type': 'LOC_DIFF', 'targets': [target1_map, target2_map]}
#=============================================
##############################################
# Constraint management
#=============================================
##############################################
def move_constraint(source, target, con):
def move_constraint(source: Object | PoseBone, target: Object | PoseBone | str, con: Constraint):
"""
Move a constraint from one owner to another, together with drivers.
"""
@ -385,7 +419,11 @@ def move_constraint(source, target, con):
source.constraints.remove(con)
def move_all_constraints(obj, source, target, *, prefix=''):
def move_all_constraints(obj: Object,
source: Object | PoseBone | str,
target: Object | PoseBone | str, *,
prefix=''):
"""
Move all constraints with the specified name prefix from one bone to another.
"""
@ -400,11 +438,11 @@ def move_all_constraints(obj, source, target, *, prefix=''):
move_constraint(source, target, con)
#=============================================
##############################################
# Custom property management
#=============================================
##############################################
def deactivate_custom_properties(obj, *, reset=True):
def deactivate_custom_properties(obj: bpy_struct, *, reset=True):
"""Disable drivers on custom properties and reset values to default."""
prefix = '["'
@ -420,14 +458,14 @@ def deactivate_custom_properties(obj, *, reset=True):
if reset:
for key, value in obj.items():
valtype = type(value)
if valtype in {int, float}:
val_type = type(value)
if val_type in {int, float}:
ui_data = obj.id_properties_ui(key)
rna_data = ui_data.as_dict()
obj[key] = valtype(rna_data.get("default", 0))
obj[key] = val_type(rna_data.get("default", 0))
def reactivate_custom_properties(obj):
def reactivate_custom_properties(obj: bpy_struct):
"""Re-enable drivers on custom properties."""
prefix = '["'
@ -442,7 +480,8 @@ def reactivate_custom_properties(obj):
fcu.mute = False
def copy_custom_properties(src, dest, *, prefix='', dest_prefix='', link_driver=False, overridable=True):
def copy_custom_properties(src, dest, *, prefix='', dest_prefix='',
link_driver=False, overridable=True) -> list[tuple[str, str, Any]]:
"""Copy custom properties with filtering by prefix. Optionally link using drivers."""
res = []
@ -456,7 +495,7 @@ def copy_custom_properties(src, dest, *, prefix='', dest_prefix='', link_driver=
try:
ui_data_src = src.id_properties_ui(key)
except TypeError:
# Some property types, eg. Python dictionaries
# Some property types, e.g. Python dictionaries
# don't support id_properties_ui.
continue
@ -476,18 +515,18 @@ def copy_custom_properties(src, dest, *, prefix='', dest_prefix='', link_driver=
return res
def copy_custom_properties_with_ui(rig, src, dest_bone, *, ui_controls=None, **options):
def copy_custom_properties_with_ui(rig: 'BaseRig', src, dest_bone, *, ui_controls=None, **options):
"""Copy custom properties, and create rig UI for them."""
if isinstance(src, str):
src = rig.get_bone(src)
bone = rig.get_bone(dest_bone)
bone: PoseBone = rig.get_bone(dest_bone)
mapping = copy_custom_properties(src, bone, **options)
if mapping:
panel = rig.script.panel_with_selected_check(rig, ui_controls or rig.bones.flatten('ctrl'))
for key,new_key,value in sorted(mapping, key=lambda item: item[1]):
for key, new_key, value in sorted(mapping, key=lambda item: item[1]):
name = new_key
# Replace delimiters with spaces
@ -508,15 +547,15 @@ def copy_custom_properties_with_ui(rig, src, dest_bone, *, ui_controls=None, **o
return mapping
#=============================================
##############################################
# Driver management
#=============================================
##############################################
def refresh_drivers(obj):
"""Cause all drivers belonging to the object to be re-evaluated, clearing any errors."""
# Refresh object's own drivers if any
anim_data = getattr(obj, 'animation_data', None)
anim_data: Optional[AnimData] = getattr(obj, 'animation_data', None)
if anim_data:
for fcu in anim_data.drivers:
@ -531,7 +570,7 @@ def refresh_drivers(obj):
def refresh_all_drivers():
"""Cause all drivers in the file to be re-evaluated, clearing any errors."""
# Iterate over all datablocks in the file
# Iterate over all data blocks in the file
for attr in dir(bpy.data):
coll = getattr(bpy.data, attr, None)
@ -540,11 +579,13 @@ def refresh_all_drivers():
refresh_drivers(item)
#=============================================
##############################################
# Utility mixin
#=============================================
##############################################
class MechanismUtilityMixin(object):
obj: ArmatureObject
"""
Provides methods for more convenient creation of constraints, properties
and drivers within an armature (by implicitly providing context).
@ -552,6 +593,7 @@ class MechanismUtilityMixin(object):
Requires self.obj to be the armature object being worked on.
"""
# noinspection PyShadowingBuiltins
def make_constraint(self, bone, type, subtarget=None, **args):
assert(self.obj.mode == 'OBJECT')
return make_constraint(self.obj.pose.bones[bone], type, self.obj, subtarget, **args)

View File

@ -4,16 +4,16 @@ import collections
from types import FunctionType
from itertools import chain
from typing import Collection, Callable
#=============================================
##############################################
# Class With Stages
#=============================================
##############################################
def rigify_stage(stage):
"""Decorates the method with the specified stage."""
def process(method):
def process(method: FunctionType):
if not isinstance(method, FunctionType):
raise ValueError("Stage decorator must be applied to a method definition")
method._rigify_stage = stage
@ -29,12 +29,12 @@ class StagedMetaclass(type):
method names from that definition as valid stages. After that, subclasses can
register methods to those stages, to be called via rigify_invoke_stage.
"""
def __new__(metacls, class_name, bases, namespace, define_stages=None, **kwds):
def __new__(mcs, class_name, bases, namespace, define_stages=None, **kwargs):
# suppress keyword args to avoid issues with __init_subclass__
return super().__new__(metacls, class_name, bases, namespace, **kwds)
return super().__new__(mcs, class_name, bases, namespace, **kwargs)
def __init__(self, class_name, bases, namespace, define_stages=None, **kwds):
super().__init__(class_name, bases, namespace, **kwds)
def __init__(cls, class_name, bases, namespace, define_stages=None, **kwargs):
super().__init__(class_name, bases, namespace, **kwargs)
# Compute the set of stages defined by this class
if not define_stages:
@ -46,12 +46,12 @@ class StagedMetaclass(type):
if name[0] != '_' and isinstance(item, FunctionType)
]
self.rigify_own_stages = frozenset(define_stages)
cls.rigify_own_stages = frozenset(define_stages)
# Compute complete set of inherited stages
staged_bases = [ cls for cls in reversed(self.__mro__) if isinstance(cls, StagedMetaclass) ]
staged_bases = [cls for cls in reversed(cls.__mro__) if isinstance(cls, StagedMetaclass)]
self.rigify_stages = stages = frozenset(chain.from_iterable(
cls.rigify_stages = stages = frozenset(chain.from_iterable(
cls.rigify_own_stages for cls in staged_bases
))
@ -60,20 +60,22 @@ class StagedMetaclass(type):
own_stage_map = collections.defaultdict(collections.OrderedDict)
method_map = {}
self.rigify_own_stage_map = own_stage_map
cls.rigify_own_stage_map = own_stage_map
for base in staged_bases:
for stage_name, methods in base.rigify_own_stage_map.items():
for method_name, method_class in methods.items():
if method_name in stages:
raise ValueError("Stage method '%s' inherited @stage.%s in class %s (%s)" %
(method_name, stage_name, class_name, self.__module__))
raise ValueError(
f"Stage method '{method_name}' inherited @stage.{stage_name} "
f"in class {class_name} ({cls.__module__})")
# Check consistency of inherited stage assignment to methods
if method_name in method_map:
if method_map[method_name] != stage_name:
print("RIGIFY CLASS %s (%s): method '%s' has inherited both @stage.%s and @stage.%s\n" %
(class_name, self.__module__, method_name, method_map[method_name], stage_name))
print(f"RIGIFY CLASS {class_name} ({cls.__module__}): "
f"method '{method_name}' has inherited both "
f"@stage.{method_map[method_name]} and @stage.{stage_name}\n")
else:
method_map[method_name] = stage_name
@ -85,33 +87,37 @@ class StagedMetaclass(type):
stage = getattr(item, '_rigify_stage', None)
if stage and method_name in stages:
print("RIGIFY CLASS %s (%s): cannot use stage decorator on the stage method '%s' (@stage.%s ignored)" %
(class_name, self.__module__, method_name, stage))
print(f"RIGIFY CLASS {class_name} ({cls.__module__}): "
f"cannot use stage decorator on the stage method '{method_name}' "
f"(@stage.{stage} ignored)")
continue
# Ensure that decorators aren't lost when redefining methods
if method_name in method_map:
if not stage:
stage = method_map[method_name]
print("RIGIFY CLASS %s (%s): missing stage decorator on method '%s' (should be @stage.%s)" %
(class_name, self.__module__, method_name, stage))
print(f"RIGIFY CLASS {class_name} ({cls.__module__}): "
f"missing stage decorator on method '{method_name}' "
f"(should be @stage.{stage})")
# Check that the method is assigned to only one stage
elif stage != method_map[method_name]:
print("RIGIFY CLASS %s (%s): method '%s' has decorator @stage.%s, but inherited base has @stage.%s" %
(class_name, self.__module__, method_name, stage, method_map[method_name]))
print(f"RIGIFY CLASS {class_name} ({cls.__module__}): "
f"method '{method_name}' has decorator @stage.{stage}, "
f"but inherited base has @stage.{method_map[method_name]}")
# Assign the method to the stage, verifying that it's valid
if stage:
if stage not in stages:
raise ValueError("Invalid stage name '%s' for method '%s' in class %s (%s)" %
(stage, method_name, class_name, self.__module__))
raise ValueError(
f"Invalid stage name '{stage}' for method '{method_name}' "
f"in class {class_name} ({cls.__module__})")
else:
stage_map[stage][method_name] = self
own_stage_map[stage][method_name] = self
stage_map[stage][method_name] = cls
own_stage_map[stage][method_name] = cls
self.rigify_stage_map = stage_map
cls.rigify_stage_map = stage_map
def make_stage_decorators(self):
def make_stage_decorators(self) -> list[tuple[str, Callable]]:
return [(name, rigify_stage(name)) for name in self.rigify_stages]
def stage_decorator_container(self, cls):
@ -121,10 +127,10 @@ class StagedMetaclass(type):
class BaseStagedClass(object, metaclass=StagedMetaclass):
rigify_sub_objects = tuple()
rigify_sub_objects: Collection['BaseStagedClass'] = tuple()
rigify_sub_object_run_late = False
def rigify_invoke_stage(self, stage):
def rigify_invoke_stage(self, stage: str):
"""Call all methods decorated with the given stage, followed by the callback."""
cls = self.__class__
assert isinstance(cls, StagedMetaclass)
@ -144,10 +150,9 @@ class BaseStagedClass(object, metaclass=StagedMetaclass):
sub.rigify_invoke_stage(stage)
#=============================================
##############################################
# Per-owner singleton class
#=============================================
##############################################
class SingletonPluginMetaclass(StagedMetaclass):
"""Metaclass for maintaining one instance per owner object per constructor arg set."""

View File

@ -3,24 +3,26 @@
import bpy
import math
import collections
import typing
from itertools import tee, chain, islice, repeat, permutations
from mathutils import Vector, Matrix, Color
from rna_prop_ui import rna_idprop_value_to_python
#=============================================
# Math
#=============================================
T = typing.TypeVar('T')
##############################################
# Math
##############################################
axis_vectors = {
'x': (1,0,0),
'y': (0,1,0),
'z': (0,0,1),
'-x': (-1,0,0),
'-y': (0,-1,0),
'-z': (0,0,-1),
'x': (1, 0, 0),
'y': (0, 1, 0),
'z': (0, 0, 1),
'-x': (-1, 0, 0),
'-y': (0, -1, 0),
'-z': (0, 0, -1),
}
@ -36,7 +38,7 @@ shuffle_matrix = {
}
def angle_on_plane(plane, vec1, vec2):
def angle_on_plane(plane: Vector, vec1: Vector, vec2: Vector):
""" Return the angle between two vectors projected onto a plane.
"""
plane.normalize()
@ -69,7 +71,7 @@ matrix_from_axis_roll = bpy.types.Bone.MatrixFromAxisRoll
axis_roll_from_matrix = bpy.types.Bone.AxisRollFromMatrix
def matrix_from_axis_pair(y_axis, other_axis, axis_name):
def matrix_from_axis_pair(y_axis: Vector, other_axis: Vector, axis_name: str):
assert axis_name in 'xz'
y_axis = Vector(y_axis).normalized()
@ -84,12 +86,12 @@ def matrix_from_axis_pair(y_axis, other_axis, axis_name):
return Matrix((x_axis, y_axis, z_axis)).transposed()
#=============================================
##############################################
# Color correction functions
#=============================================
##############################################
def linsrgb_to_srgb (linsrgb):
# noinspection SpellCheckingInspection
def linsrgb_to_srgb(linsrgb: float):
"""Convert physically linear RGB values into sRGB ones. The transform is
uniform in the components, so *linsrgb* can be of any shape.
@ -105,44 +107,45 @@ def linsrgb_to_srgb (linsrgb):
return scale
def gamma_correct(color):
# noinspection PyUnresolvedReferences,PyTypeChecker
def gamma_correct(color: Color):
corrected_color = Color()
for i, component in enumerate(color):
corrected_color[i] = linsrgb_to_srgb(color[i])
return corrected_color
#=============================================
##############################################
# Iterators
#=============================================
##############################################
# noinspection SpellCheckingInspection
def padnone(iterable, pad=None):
return chain(iterable, repeat(pad))
# noinspection SpellCheckingInspection
def pairwise_nozip(iterable):
"s -> (s0,s1), (s1,s2), (s2,s3), ..."
"""s -> (s0,s1), (s1,s2), (s2,s3), ..."""
a, b = tee(iterable)
next(b, None)
return a, b
def pairwise(iterable):
"s -> (s0,s1), (s1,s2), (s2,s3), ..."
"""s -> (s0,s1), (s1,s2), (s2,s3), ..."""
a, b = tee(iterable)
next(b, None)
return zip(a, b)
def map_list(func, *inputs):
"[func(a0,b0...), func(a1,b1...), ...]"
"""[func(a0,b0...), func(a1,b1...), ...]"""
return list(map(func, *inputs))
def skip(n, iterable):
"Returns an iterator skipping first n elements of an iterable."
"""Returns an iterator skipping first n elements of an iterable."""
iterator = iter(iterable)
if n == 1:
next(iterator, None)
@ -152,17 +155,21 @@ def skip(n, iterable):
def map_apply(func, *inputs):
"Apply the function to inputs like map for side effects, discarding results."
"""Apply the function to inputs like map for side effects, discarding results."""
collections.deque(map(func, *inputs), maxlen=0)
#=============================================
##############################################
# Lazy references
#=============================================
##############################################
Lazy: typing.TypeAlias = T | typing.Callable[[], T]
OptionalLazy: typing.TypeAlias = typing.Optional[T | typing.Callable[[], T]]
def force_lazy(value):
"""If the argument is callable, invokes it without arguments. Otherwise returns the argument as is."""
def force_lazy(value: OptionalLazy[T]) -> T:
"""If the argument is callable, invokes it without arguments.
Otherwise, returns the argument as is."""
if callable(value):
return value()
else:
@ -171,7 +178,7 @@ def force_lazy(value):
class LazyRef:
"""Hashable lazy reference. When called, evaluates (foo, 'a', 'b'...) as foo('a','b')
if foo is callable. Otherwise the remaining arguments are used as attribute names or
if foo is callable. Otherwise, the remaining arguments are used as attribute names or
keys, like foo.a.b or foo.a[b] etc."""
def __init__(self, first, *args):
@ -180,7 +187,7 @@ class LazyRef:
self.first_hashable = first.__hash__ is not None
def __repr__(self):
return 'LazyRef{}'.format(tuple(self.first, *self.args))
return 'LazyRef{}'.format((self.first, *self.args))
def __eq__(self, other):
return (
@ -190,7 +197,8 @@ class LazyRef:
)
def __hash__(self):
return (hash(self.first) if self.first_hashable else hash(id(self.first))) ^ hash(self.args)
return (hash(self.first) if self.first_hashable
else hash(id(self.first))) ^ hash(self.args)
def __call__(self):
first = self.first
@ -206,31 +214,27 @@ class LazyRef:
return first
#=============================================
##############################################
# Misc
#=============================================
##############################################
def copy_attributes(a, b):
keys = dir(a)
for key in keys:
if not key.startswith("_") \
and not key.startswith("error_") \
and key != "group" \
and key != "is_valid" \
and key != "rna_type" \
and key != "bl_rna":
if not (key.startswith("_") or
key.startswith("error_") or
key in ("group", "is_valid", "is_valid", "bl_rna")):
try:
setattr(b, key, getattr(a, key))
except AttributeError:
pass
def property_to_python(value):
def property_to_python(value) -> typing.Any:
value = rna_idprop_value_to_python(value)
if isinstance(value, dict):
return { k: property_to_python(v) for k, v in value.items() }
return {k: property_to_python(v) for k, v in value.items()}
elif isinstance(value, list):
return map_list(property_to_python, value)
else:
@ -246,7 +250,7 @@ def assign_parameters(target, val_dict=None, **params):
for key in list(target.keys()):
del target[key]
data = { **val_dict, **params }
data = {**val_dict, **params}
else:
data = params
@ -254,15 +258,40 @@ def assign_parameters(target, val_dict=None, **params):
try:
target[key] = value
except Exception as e:
raise Exception("Couldn't set {} to {}: {}".format(key,value,e))
raise Exception(f"Couldn't set {key} to {value}: {e}")
def select_object(context, object, deselect_all=False):
def select_object(context: bpy.types.Context, obj: bpy.types.Object, deselect_all=False):
view_layer = context.view_layer
if deselect_all:
for objt in view_layer.objects:
objt.select_set(False) # deselect all objects
for layer_obj in view_layer.objects:
layer_obj.select_set(False) # deselect all objects
object.select_set(True)
view_layer.objects.active = object
obj.select_set(True)
view_layer.objects.active = obj
##############################################
# Typing
##############################################
class TypedObject(bpy.types.Object, typing.Generic[T]):
data: T
ArmatureObject = TypedObject[bpy.types.Armature]
MeshObject = TypedObject[bpy.types.Mesh]
AnyVector = Vector | typing.Sequence[float]
def verify_armature_obj(obj: bpy.types.Object) -> ArmatureObject:
assert obj and obj.type == 'ARMATURE'
# noinspection PyTypeChecker
return obj
def verify_mesh_obj(obj: bpy.types.Object) -> MeshObject:
assert obj and obj.type == 'MESH'
# noinspection PyTypeChecker
return obj

View File

@ -6,34 +6,41 @@ import re
import collections
import enum
from typing import Optional, TYPE_CHECKING
if TYPE_CHECKING:
from ..base_generate import BaseGenerator
ORG_PREFIX = "ORG-" # Prefix of original bones.
MCH_PREFIX = "MCH-" # Prefix of mechanism bones.
DEF_PREFIX = "DEF-" # Prefix of deformation bones.
ROOT_NAME = "root" # Name of the root bone.
_PREFIX_TABLE = { 'org': "ORG", 'mch': "MCH", 'def': "DEF", 'ctrl': '' }
_PREFIX_TABLE = {'org': "ORG", 'mch': "MCH", 'def': "DEF", 'ctrl': ''}
#=======================================================================
########################################################################
# Name structure
#=======================================================================
########################################################################
NameParts = collections.namedtuple('NameParts', ['prefix', 'base', 'side_z', 'side', 'number'])
def split_name(name):
name_parts = re.match(r'^(?:(ORG|MCH|DEF)-)?(.*?)([._-][tTbB])?([._-][lLrR])?(?:\.(\d+))?$', name)
def split_name(name: str):
name_parts = re.match(
r'^(?:(ORG|MCH|DEF)-)?(.*?)([._-][tTbB])?([._-][lLrR])?(?:\.(\d+))?$', name)
return NameParts(*name_parts.groups())
def is_control_bone(name):
def is_control_bone(name: str):
return not split_name(name).prefix
def combine_name(parts, *, prefix=None, base=None, side_z=None, side=None, number=None):
def combine_name(parts: NameParts, *, prefix=None, base=None, side_z=None, side=None, number=None):
eff_prefix = prefix if prefix is not None else parts.prefix
eff_number = number if number is not None else parts.number
if isinstance(eff_number, int):
eff_number = '%03d' % (eff_number)
eff_number = '%03d' % eff_number
return ''.join([
eff_prefix+'-' if eff_prefix else '',
@ -44,7 +51,7 @@ def combine_name(parts, *, prefix=None, base=None, side_z=None, side=None, numbe
])
def insert_before_lr(name, text):
def insert_before_lr(name: str, text: str) -> str:
parts = split_name(name)
if parts.side:
@ -53,7 +60,7 @@ def insert_before_lr(name, text):
return name + text
def make_derived_name(name, subtype, suffix=None):
def make_derived_name(name: str, subtype: str, suffix: Optional[str] = None):
""" Replaces the name prefix, and optionally adds the suffix (before .LR if found).
"""
assert(subtype in _PREFIX_TABLE)
@ -64,9 +71,9 @@ def make_derived_name(name, subtype, suffix=None):
return combine_name(parts, prefix=_PREFIX_TABLE[subtype], base=new_base)
#=======================================================================
########################################################################
# Name mirroring
#=======================================================================
########################################################################
class Side(enum.IntEnum):
LEFT = -1
@ -74,7 +81,7 @@ class Side(enum.IntEnum):
RIGHT = 1
@staticmethod
def from_parts(parts):
def from_parts(parts: NameParts):
if parts.side:
if parts.side[1].lower() == 'l':
return Side.LEFT
@ -84,14 +91,14 @@ class Side(enum.IntEnum):
return Side.MIDDLE
@staticmethod
def to_string(parts, side):
def to_string(parts: NameParts, side: 'Side'):
if side != Side.MIDDLE:
side_char = 'L' if side == Side.LEFT else 'R'
side_str = parts.side or parts.side_z
if side_str:
sep, schar = side_str[0:2]
if schar.lower() == schar:
sep, side_char2 = side_str[0:2]
if side_char2.lower() == side_char2:
side_char = side_char.lower()
else:
sep = '.'
@ -101,7 +108,7 @@ class Side(enum.IntEnum):
return ''
@staticmethod
def to_name(parts, side):
def to_name(parts: NameParts, side: 'Side'):
new_side = Side.to_string(parts, side)
return combine_name(parts, side=new_side)
@ -112,7 +119,7 @@ class SideZ(enum.IntEnum):
BOTTOM = -2
@staticmethod
def from_parts(parts):
def from_parts(parts: NameParts):
if parts.side_z:
if parts.side_z[1].lower() == 't':
return SideZ.TOP
@ -122,14 +129,14 @@ class SideZ(enum.IntEnum):
return SideZ.MIDDLE
@staticmethod
def to_string(parts, side):
def to_string(parts: NameParts, side: 'SideZ'):
if side != SideZ.MIDDLE:
side_char = 'T' if side == SideZ.TOP else 'B'
side_str = parts.side_z or parts.side
if side_str:
sep, schar = side_str[0:2]
if schar.lower() == schar:
sep, side_char2 = side_str[0:2]
if side_char2.lower() == side_char2:
side_char = side_char.lower()
else:
sep = '.'
@ -139,7 +146,7 @@ class SideZ(enum.IntEnum):
return ''
@staticmethod
def to_name(parts, side):
def to_name(parts: NameParts, side: 'SideZ'):
new_side = SideZ.to_string(parts, side)
return combine_name(parts, side_z=new_side)
@ -147,28 +154,30 @@ class SideZ(enum.IntEnum):
NameSides = collections.namedtuple('NameSides', ['base', 'side', 'side_z'])
def get_name_side(name):
def get_name_side(name: str):
return Side.from_parts(split_name(name))
def get_name_side_z(name):
def get_name_side_z(name: str):
return SideZ.from_parts(split_name(name))
def get_name_base_and_sides(name):
def get_name_base_and_sides(name: str):
parts = split_name(name)
base = combine_name(parts, side='', side_z='')
return NameSides(base, Side.from_parts(parts), SideZ.from_parts(parts))
def change_name_side(name, side=None, *, side_z=None):
def change_name_side(name: str,
side: Optional[Side] = None, *,
side_z: Optional[SideZ] = None):
parts = split_name(name)
new_side = None if side is None else Side.to_string(parts, side)
new_side_z = None if side_z is None else SideZ.to_string(parts, side_z)
return combine_name(parts, side=new_side, side_z=new_side_z)
def mirror_name(name):
def mirror_name(name: str):
parts = split_name(name)
side = Side.from_parts(parts)
@ -178,7 +187,7 @@ def mirror_name(name):
return name
def mirror_name_z(name):
def mirror_name_z(name: str):
parts = split_name(name)
side = SideZ.from_parts(parts)
@ -188,23 +197,23 @@ def mirror_name_z(name):
return name
#=======================================================================
########################################################################
# Name manipulation
#=======================================================================
########################################################################
def get_name(bone):
def get_name(bone) -> Optional[str]:
return bone.name if bone else None
def strip_trailing_number(name):
def strip_trailing_number(name: str):
return combine_name(split_name(name), number='')
def strip_prefix(name):
def strip_prefix(name: str):
return combine_name(split_name(name), prefix='')
def unique_name(collection, base_name):
def unique_name(collection, base_name: str):
parts = split_name(base_name)
name = combine_name(parts, number='')
count = 1
@ -216,17 +225,19 @@ def unique_name(collection, base_name):
return name
def strip_org(name):
def strip_org(name: str):
""" Returns the name with ORG_PREFIX stripped from it.
"""
if name.startswith(ORG_PREFIX):
return name[len(ORG_PREFIX):]
else:
return name
org_name = strip_org
def strip_mch(name):
def strip_mch(name: str):
""" Returns the name with MCH_PREFIX stripped from it.
"""
if name.startswith(MCH_PREFIX):
@ -234,7 +245,8 @@ def strip_mch(name):
else:
return name
def strip_def(name):
def strip_def(name: str):
""" Returns the name with DEF_PREFIX stripped from it.
"""
if name.startswith(DEF_PREFIX):
@ -242,7 +254,8 @@ def strip_def(name):
else:
return name
def org(name):
def org(name: str):
""" Prepends the ORG_PREFIX to a name if it doesn't already have
it, and returns it.
"""
@ -250,10 +263,12 @@ def org(name):
return name
else:
return ORG_PREFIX + name
make_original_name = org
def mch(name):
def mch(name: str):
""" Prepends the MCH_PREFIX to a name if it doesn't already have
it, and returns it.
"""
@ -261,10 +276,12 @@ def mch(name):
return name
else:
return MCH_PREFIX + name
make_mechanism_name = mch
def deformer(name):
def deformer(name: str):
""" Prepends the DEF_PREFIX to a name if it doesn't already have
it, and returns it.
"""
@ -272,24 +289,29 @@ def deformer(name):
return name
else:
return DEF_PREFIX + name
make_deformer_name = deformer
def random_id(length=8):
""" Generates a random alphanumeric id string.
"""
tlength = int(length / 2)
rlength = int(length / 2) + int(length % 2)
t_length = int(length / 2)
r_length = int(length / 2) + int(length % 2)
chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
'x', 'y', 'z']
text = ""
for i in range(0, rlength):
for i in range(0, r_length):
text += random.choice(chars)
text += str(hex(int(time.time())))[2:][-tlength:].rjust(tlength, '0')[::-1]
text += str(hex(int(time.time())))[2:][-t_length:].rjust(t_length, '0')[::-1]
return text
def choose_derived_bone(generator, original, subtype, *, by_owner=True, recursive=True):
def choose_derived_bone(generator: 'BaseGenerator', original: str, subtype: str, *,
by_owner=True, recursive=True):
bones = generator.obj.pose.bones
names = generator.find_derived_bones(original, by_owner=by_owner, recursive=recursive)
@ -298,7 +320,7 @@ def choose_derived_bone(generator, original, subtype, *, by_owner=True, recursiv
return direct
prefix = _PREFIX_TABLE[subtype] + '-'
matching = [ name for name in names if name.startswith(prefix) and name in bones ]
matching = [name for name in names if name.startswith(prefix) and name in bones]
if len(matching) > 0:
return matching[0]

View File

@ -1,15 +1,13 @@
# SPDX-License-Identifier: GPL-2.0-or-later
import bpy
import collections
import heapq
import operator
from typing import Any, Sequence, Optional
from mathutils import Vector
from mathutils.kdtree import KDTree
from .errors import MetarigError
from ..base_rig import stage, GenerateCallbackHost
from ..base_rig import BaseRig, GenerateCallbackHost
from ..base_generate import GeneratorPlugin
@ -31,7 +29,11 @@ class NodeMerger(GeneratorPlugin):
epsilon = 1e-5
def __init__(self, generator, domain):
nodes: list['BaseMergeNode']
final_nodes: list['BaseMergeNode']
groups: list['MergeGroup']
def __init__(self, generator, domain: Any):
super().__init__(generator)
assert domain is not None
@ -43,7 +45,7 @@ class NodeMerger(GeneratorPlugin):
self.groups = []
self.frozen = False
def register_node(self, node):
def register_node(self, node: 'BaseMergeNode'):
assert not self.frozen
node.generator_plugin = self
self.nodes.append(node)
@ -74,7 +76,7 @@ class NodeMerger(GeneratorPlugin):
added = set()
for j in pending:
point = nodes[j].point
eps = max(1, point.length) * self.epsilon
eps = max(1.0, point.length) * self.epsilon
for co, idx, dist in tree.find_range(point, eps):
added.add(idx)
pending = added.difference(merge_set)
@ -124,17 +126,19 @@ class MergeGroup(object):
The master nodes of the chosen clusters, plus query nodes, become 'final'.
"""
def __init__(self, nodes):
main_nodes: list['MainMergeNode']
query_nodes: list['QueryMergeNode']
final_nodes: list['MainMergeNode']
def __init__(self, nodes: list['BaseMergeNode']):
self.nodes = nodes
for node in nodes:
assert isinstance(node, (MainMergeNode, QueryMergeNode))
node.group = self
def is_main(node):
return isinstance(node, MainMergeNode)
self.main_nodes = [n for n in nodes if is_main(n)]
self.query_nodes = [n for n in nodes if not is_main(n)]
self.main_nodes = [n for n in nodes if isinstance(n, MainMergeNode)]
self.query_nodes = [n for n in nodes if isinstance(n, QueryMergeNode)]
def build(self, final_nodes):
main_nodes = self.main_nodes
@ -162,7 +166,7 @@ class MergeGroup(object):
pending = set(main_nodes)
while pending:
# Find largest group
# Find the largest group
nodes = [n for n in main_nodes if n in pending]
max_len = max(len(merge_table[n]) for n in nodes)
@ -181,7 +185,7 @@ class MergeGroup(object):
max_weight = max(wn[1] for wn in weighted_nodes)
nodes = [wn[0] for wn in weighted_nodes if wn[1] == max_weight]
# Final tie breaker is the name
# Final tiebreaker is the name
best = min(nodes, key=lambda n: n.name)
child_set = merge_table[best]
@ -213,13 +217,17 @@ class MergeGroup(object):
class BaseMergeNode(GenerateCallbackHost):
"""Base class of mergeable nodes."""
"""Base class of merge-able nodes."""
merge_domain = None
merge_domain: Any = None
merger = NodeMerger
group_class = MergeGroup
def __init__(self, rig, name, point, *, domain=None):
generator_plugin: NodeMerger
group: MergeGroup
def __init__(self, rig: BaseRig, name: str, point: Vector | Sequence[float], *,
domain: Any = None):
self.rig = rig
self.obj = rig.obj
self.name = name
@ -228,24 +236,28 @@ class BaseMergeNode(GenerateCallbackHost):
merger = self.merger(rig.generator, domain or self.merge_domain)
merger.register_node(self)
def register_new_bone(self, new_name, old_name=None):
def register_new_bone(self, new_name: str, old_name: Optional[str] = None):
self.generator_plugin.register_new_bone(new_name, old_name)
def can_merge_into(self, other):
raise NotImplementedError()
def can_merge_into(self, other: 'MainMergeNode'):
raise NotImplementedError
def get_merge_priority(self, other):
"Rank candidates to merge into."
def get_merge_priority(self, other: 'BaseMergeNode'):
"""Rank candidates to merge into."""
return 0
class MainMergeNode(BaseMergeNode):
"""
Base class of standard mergeable nodes. Each node can either be
Base class of standard merge-able nodes. Each node can either be
a master of its cluster or a merged child node. Children become
sub-objects of their master to receive callbacks in defined order.
"""
merged_master: 'MainMergeNode'
merged_into: Optional['MainMergeNode']
merged: list['MainMergeNode']
def __init__(self, rig, name, point, *, domain=None):
super().__init__(rig, name, point, domain=domain)
@ -256,20 +268,21 @@ class MainMergeNode(BaseMergeNode):
master = self.merged_master
return [master, *master.merged]
def is_better_cluster(self, other):
"Compare with the other node to choose between cluster masters."
def is_better_cluster(self, other: 'MainMergeNode'):
"""Compare with the other node to choose between cluster masters."""
return False
def can_merge_from(self, other):
# noinspection PyMethodMayBeStatic
def can_merge_from(self, _other: 'MainMergeNode'):
return True
def can_merge_into(self, other):
def can_merge_into(self, other: 'MainMergeNode'):
return other.can_merge_from(self)
def merge_into(self, other):
def merge_into(self, other: 'MainMergeNode'):
self.merged_into = other
def merge_from(self, other):
def merge_from(self, other: 'MainMergeNode'):
self.merged.append(other)
@property
@ -284,12 +297,15 @@ class MainMergeNode(BaseMergeNode):
child.merge_done()
# noinspection PyAbstractClass
class QueryMergeNode(BaseMergeNode):
"""Base class for special nodes used only to query which nodes are at a certain location."""
is_master_node = False
require_match = True
matched_nodes: list['MainMergeNode']
def merge_done(self):
self.matched_nodes = [
n for n in self.group.final_nodes if self.can_merge_into(n)

View File

@ -3,18 +3,27 @@
import bpy
import importlib
import importlib.util
import os
import re
from itertools import count
from typing import TYPE_CHECKING, Any, Optional, Sequence, Mapping
from bpy.types import bpy_struct, Constraint, Object, PoseBone, Bone, Armature
# noinspection PyUnresolvedReferences
from bpy.types import bpy_prop_array
from .misc import ArmatureObject
if TYPE_CHECKING:
from ..base_rig import BaseRig
from .. import RigifyColorSet, RigifyArmatureLayer
from bpy.types import bpy_struct, bpy_prop_array, Constraint
RIG_DIR = "rigs" # Name of the directory where rig types are kept
METARIG_DIR = "metarigs" # Name of the directory where metarigs are kept
TEMPLATE_DIR = "ui_templates" # Name of the directory where ui templates are kept
# noinspection SpellCheckingInspection
outdated_types = {"pitchipoy.limbs.super_limb": "limbs.super_limb",
"pitchipoy.limbs.super_arm": "limbs.super_limb",
"pitchipoy.limbs.super_leg": "limbs.super_limb",
@ -37,16 +46,37 @@ outdated_types = {"pitchipoy.limbs.super_limb": "limbs.super_limb",
"spine": ""
}
def get_rigify_type(pose_bone):
def get_rigify_type(pose_bone: PoseBone) -> str:
# noinspection PyUnresolvedReferences
return pose_bone.rigify_type.replace(" ", "")
def is_rig_base_bone(obj, name):
def get_rigify_params(pose_bone: PoseBone) -> Any:
# noinspection PyUnresolvedReferences
return pose_bone.rigify_parameters
def get_rigify_colors(arm: Armature) -> Sequence['RigifyColorSet']:
# noinspection PyUnresolvedReferences
return arm.rigify_colors
def get_rigify_layers(arm: Armature) -> Sequence['RigifyArmatureLayer']:
# noinspection PyUnresolvedReferences
return arm.rigify_layers
def is_rig_base_bone(obj: Object, name):
return bool(get_rigify_type(obj.pose.bones[name]))
def upgradeMetarigTypes(metarig, revert=False):
"""Replaces rigify_type properties from old versions with their current names
:param revert: revert types to previous version (if old type available)
def upgrade_metarig_types(metarig: Object, revert=False):
"""
Replaces rigify_type properties from old versions with their current names.
metarig: rig to update.
revert: revert types to previous version (if old type available)
"""
if revert:
@ -59,22 +89,24 @@ def upgradeMetarigTypes(metarig, revert=False):
rig_type = bone.rigify_type
if rig_type in rig_defs:
bone.rigify_type = rig_defs[rig_type]
parameters = get_rigify_params(bone)
if 'leg' in rig_type:
bone.rigfy_parameters.limb_type = 'leg'
parameters.limb_type = 'leg'
if 'arm' in rig_type:
bone.rigfy_parameters.limb_type = 'arm'
parameters.limb_type = 'arm'
if 'paw' in rig_type:
bone.rigfy_parameters.limb_type = 'paw'
parameters.limb_type = 'paw'
if rig_type == "basic.copy":
bone.rigify_parameters.make_widget = False
parameters.make_widget = False
#=============================================
##############################################
# Misc
#=============================================
##############################################
def rig_is_child(rig, parent, *, strict=False):
def rig_is_child(rig: 'BaseRig', parent: Optional['BaseRig'], *, strict=False):
"""
Checks if the rig is a child of the parent.
Unless strict is True, returns true if the rig and parent are the same.
@ -94,7 +126,7 @@ def rig_is_child(rig, parent, *, strict=False):
return False
def get_parent_rigs(rig):
def get_parent_rigs(rig: 'BaseRig') -> list['BaseRig']:
"""Returns a list containing the rig and all of its parents."""
result = []
while rig:
@ -106,13 +138,12 @@ def get_parent_rigs(rig):
def get_resource(resource_name):
""" Fetches a rig module by name, and returns it.
"""
module = importlib.import_module(resource_name)
importlib.reload(module)
return module
def connected_children_names(obj, bone_name):
def connected_children_names(obj: ArmatureObject, bone_name: str) -> list[str]:
""" Returns a list of bone names (in order) of the bones that form a single
connected chain starting with the given bone as a parent.
If there is a connected branch, the list stops there.
@ -138,7 +169,7 @@ def connected_children_names(obj, bone_name):
return names
def has_connected_children(bone):
def has_connected_children(bone: Bone):
""" Returns true/false whether a bone has connected children or not.
"""
t = False
@ -147,13 +178,14 @@ def has_connected_children(bone):
return t
def _list_bone_names_depth_first_sorted_rec(result_list, bone):
def _list_bone_names_depth_first_sorted_rec(result_list: list[str], bone: Bone):
result_list.append(bone.name)
for child in sorted(list(bone.children), key=lambda b: b.name):
_list_bone_names_depth_first_sorted_rec(result_list, child)
def list_bone_names_depth_first_sorted(obj):
def list_bone_names_depth_first_sorted(obj: ArmatureObject):
"""Returns a list of bone names in depth first name sorted order."""
result_list = []
@ -164,16 +196,25 @@ def list_bone_names_depth_first_sorted(obj):
return result_list
def _get_property_value(obj, name):
def _get_property_value(obj, name: str):
value = getattr(obj, name, None)
if isinstance(value, bpy_prop_array):
value = tuple(value)
return value
def _generate_properties(lines, prefix, obj, base_class, *, defaults={}, objects={}):
block_props = set(prop.identifier for prop in base_class.bl_rna.properties) - set(defaults.keys())
for prop in type(obj).bl_rna.properties:
# noinspection PyDefaultArgument
def _generate_properties(lines, prefix, obj: bpy_struct, base_class: type, *,
defaults: dict[str, Any] = {},
objects: dict[Any, str] = {}):
# noinspection PyUnresolvedReferences
obj_rna: bpy.types.Struct = type(obj).bl_rna
# noinspection PyUnresolvedReferences
base_rna: bpy.types.Struct = base_class.bl_rna
block_props = set(prop.identifier for prop in base_rna.properties) - set(defaults.keys())
for prop in obj_rna.properties:
if prop.identifier not in block_props and not prop.is_readonly:
cur_value = _get_property_value(obj, prop.identifier)
@ -188,7 +229,7 @@ def _generate_properties(lines, prefix, obj, base_class, *, defaults={}, objects
lines.append('%s.%s = %r' % (prefix, prop.identifier, cur_value))
def write_metarig_widgets(obj):
def write_metarig_widgets(obj: Object):
from .widgets import write_widget
widget_set = set()
@ -217,15 +258,17 @@ def write_metarig_widgets(obj):
return widget_map, code
def write_metarig(obj, layers=False, func_name="create", groups=False, widgets=False):
# noinspection SpellCheckingInspection
def write_metarig(obj: ArmatureObject, layers=False, func_name="create",
groups=False, widgets=False):
"""
Write a metarig as a python script, this rig is to have all info needed for
generating the real rig with rigify.
"""
code = []
code.append("import bpy\n")
code.append("from mathutils import Color\n")
code = [
"import bpy\n",
"from mathutils import Color\n",
]
# Widget object creation functions if requested
if widgets:
@ -234,6 +277,8 @@ def write_metarig(obj, layers=False, func_name="create", groups=False, widgets=F
if widget_map:
code.append("from rigify.utils.widgets import widget_generator\n\n")
code += widget_code
else:
widget_map = {}
# Start of the metarig function
code.append("def %s(obj):" % func_name)
@ -245,32 +290,40 @@ def write_metarig(obj, layers=False, func_name="create", groups=False, widgets=F
arm = obj.data
# Rigify bone group colors info
if groups and len(arm.rigify_colors) > 0:
code.append("\n for i in range(" + str(len(arm.rigify_colors)) + "):")
rigify_colors = get_rigify_colors(arm)
if groups and len(rigify_colors) > 0:
code.append("\n for i in range(" + str(len(rigify_colors)) + "):")
code.append(" arm.rigify_colors.add()\n")
for i in range(len(arm.rigify_colors)):
name = arm.rigify_colors[i].name
active = arm.rigify_colors[i].active
normal = arm.rigify_colors[i].normal
select = arm.rigify_colors[i].select
standard_colors_lock = arm.rigify_colors[i].standard_colors_lock
for i in range(len(rigify_colors)):
name = rigify_colors[i].name
active = rigify_colors[i].active
normal = rigify_colors[i].normal
select = rigify_colors[i].select
standard_colors_lock = rigify_colors[i].standard_colors_lock
code.append(' arm.rigify_colors[' + str(i) + '].name = "' + name + '"')
code.append(' arm.rigify_colors[' + str(i) + '].active = Color(' + str(active[:]) + ')')
code.append(' arm.rigify_colors[' + str(i) + '].normal = Color(' + str(normal[:]) + ')')
code.append(' arm.rigify_colors[' + str(i) + '].select = Color(' + str(select[:]) + ')')
code.append(' arm.rigify_colors[' + str(i) + '].standard_colors_lock = ' + str(standard_colors_lock))
code.append(' arm.rigify_colors[' + str(i)
+ '].active = Color(' + str(active[:]) + ')')
code.append(' arm.rigify_colors[' + str(i)
+ '].normal = Color(' + str(normal[:]) + ')')
code.append(' arm.rigify_colors[' + str(i)
+ '].select = Color(' + str(select[:]) + ')')
code.append(' arm.rigify_colors[' + str(i)
+ '].standard_colors_lock = ' + str(standard_colors_lock))
# Rigify layer layout info
if layers and len(arm.rigify_layers) > 0:
code.append("\n for i in range(" + str(len(arm.rigify_layers)) + "):")
rigify_layers = get_rigify_layers(arm)
if layers and len(rigify_layers) > 0:
code.append("\n for i in range(" + str(len(rigify_layers)) + "):")
code.append(" arm.rigify_layers.add()\n")
for i in range(len(arm.rigify_layers)):
name = arm.rigify_layers[i].name
row = arm.rigify_layers[i].row
selset = arm.rigify_layers[i].selset
group = arm.rigify_layers[i].group
for i in range(len(rigify_layers)):
name = rigify_layers[i].name
row = rigify_layers[i].row
selset = rigify_layers[i].selset
group = rigify_layers[i].group
code.append(' arm.rigify_layers[' + str(i) + '].name = "' + name + '"')
code.append(' arm.rigify_layers[' + str(i) + '].row = ' + str(row))
code.append(' arm.rigify_layers[' + str(i) + '].selset = ' + str(selset))
@ -307,8 +360,11 @@ def write_metarig(obj, layers=False, func_name="create", groups=False, widgets=F
for bone_name in bones:
pbone = obj.pose.bones[bone_name]
rigify_type = get_rigify_type(pbone)
rigify_parameters = get_rigify_params(pbone)
code.append(" pbone = obj.pose.bones[bones[%r]]" % bone_name)
code.append(" pbone.rigify_type = %r" % pbone.rigify_type)
code.append(" pbone.rigify_type = %r" % rigify_type)
code.append(" pbone.lock_location = %s" % str(tuple(pbone.lock_location)))
code.append(" pbone.lock_rotation = %s" % str(tuple(pbone.lock_rotation)))
code.append(" pbone.lock_rotation_w = %s" % str(pbone.lock_rotation_w))
@ -316,9 +372,10 @@ def write_metarig(obj, layers=False, func_name="create", groups=False, widgets=F
code.append(" pbone.rotation_mode = %r" % pbone.rotation_mode)
if layers:
code.append(" pbone.bone.layers = %s" % str(list(pbone.bone.layers)))
# Rig type parameters
for param_name in pbone.rigify_parameters.keys():
param = getattr(pbone.rigify_parameters, param_name, '')
for param_name in rigify_parameters.keys():
param = getattr(rigify_parameters, param_name, '')
if str(type(param)) == "<class 'bpy_prop_array'>":
param = list(param)
if type(param) == str:
@ -327,17 +384,18 @@ def write_metarig(obj, layers=False, func_name="create", groups=False, widgets=F
code.append(" pbone.rigify_parameters.%s = %s" % (param_name, str(param)))
code.append(" except AttributeError:")
code.append(" pass")
# Constraints
for con in pbone.constraints:
code.append(" con = pbone.constraints.new(%r)" % (con.type))
code.append(" con.name = %r" % (con.name))
code.append(" con = pbone.constraints.new(%r)" % con.type)
code.append(" con.name = %r" % con.name)
# Add target first because of target_space handling
if con.type == 'ARMATURE':
for tgt in con.targets:
code.append(" tgt = con.targets.new()")
code.append(" tgt.target = obj")
code.append(" tgt.subtarget = %r" % (tgt.subtarget))
code.append(" tgt.weight = %.3f" % (tgt.weight))
code.append(" tgt.subtarget = %r" % tgt.subtarget)
code.append(" tgt.weight = %.3f" % tgt.weight)
elif getattr(con, 'target', None) == obj:
code.append(" con.target = obj")
# Generic properties
@ -353,9 +411,11 @@ def write_metarig(obj, layers=False, func_name="create", groups=False, widgets=F
# Custom widgets
if widgets and pbone.custom_shape:
widget_id = widget_map[pbone.custom_shape]
code.append(" if %r not in widget_map:" % (widget_id))
code.append(" widget_map[%r] = create_%s_widget(obj, pbone.name, widget_name=%r, widget_force_new=True)" % (widget_id, widget_id, pbone.custom_shape.name))
code.append(" pbone.custom_shape = widget_map[%r]" % (widget_id))
code.append(" if %r not in widget_map:" % widget_id)
code.append((" widget_map[%r] = create_%s_widget(obj, pbone.name, "
"widget_name=%r, widget_force_new=True)")
% (widget_id, widget_id, pbone.custom_shape.name))
code.append(" pbone.custom_shape = widget_map[%r]" % widget_id)
code.append("\n bpy.ops.object.mode_set(mode='EDIT')")
code.append(" for bone in arm.edit_bones:")
@ -383,7 +443,8 @@ def write_metarig(obj, layers=False, func_name="create", groups=False, widgets=F
active_layers.append(i)
active_layers.sort()
code.append("\n arm.layers = [(x in " + str(active_layers) + ") for x in range(" + str(len(arm.layers)) + ")]")
code.append("\n arm.layers = [(x in " + str(active_layers) +
") for x in range(" + str(len(arm.layers)) + ")]")
code.append("\n return bones")

View File

@ -1,23 +1,19 @@
# SPDX-License-Identifier: GPL-2.0-or-later
import bpy
import re
import itertools
import bisect
import json
from .errors import MetarigError
from .naming import strip_prefix, make_derived_name
from .bones import set_bone_orientation
from .mechanism import MechanismUtilityMixin
from .rig import rig_is_child
from .misc import map_list, map_apply, force_lazy
from .misc import OptionalLazy, force_lazy, Lazy
from ..base_generate import GeneratorPlugin
from ..base_rig import BaseRig
from ..base_generate import GeneratorPlugin, BaseGenerator
from typing import Optional, Any
from collections import defaultdict
from itertools import count, repeat, chain
from itertools import chain
from mathutils import Matrix
@ -27,7 +23,14 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
Allows all rigs to register their bones as possible parents for other rigs.
"""
def __init__(self, generator):
global_parents: list[dict[str, Any]]
local_parents: dict[int, list[dict[str, Any]]]
parent_list: list[dict[str, Any]]
child_list: list[dict[str, Any]]
child_map: dict[str, dict[str, Any]]
def __init__(self, generator: BaseGenerator):
super().__init__(generator)
self.child_list = []
@ -38,22 +41,25 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
self.register_parent(None, 'root', name='Root', is_global=True)
##############################
# API
def register_parent(self, rig, bone, *, name=None, is_global=False, exclude_self=False, inject_into=None, tags=None):
def register_parent(self, rig: Optional[BaseRig], bone: Lazy[str], *,
name: Optional[str] = None,
is_global=False, exclude_self=False,
inject_into: Optional[BaseRig] = None,
tags: Optional[set[str]] = None):
"""
Registers a bone of the specified rig as a possible parent.
Parameters:
rig Owner of the bone.
bone Actual name of the parent bone.
name Name of the parent for mouse-over hint.
is_global The parent is accessible to all rigs, instead of just children of owner.
exclude_self The parent is invisible to the owner rig itself.
inject_into Make this parent available to children of the specified rig.
tags Set of tags to use for default parent selection.
rig: Owner of the bone (can be None if is_global).
bone: Actual name of the parent bone.
name: Name of the parent for mouse-over hint.
is_global: The parent is accessible to all rigs, instead of just children of owner.
exclude_self: The parent is invisible to the owner rig itself.
inject_into: Make this parent available to children of the specified rig.
tags: Set of tags to use for default parent selection.
Lazy creation:
The bone parameter may be a function creating the bone on demand and
@ -61,6 +67,7 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
"""
assert not self.frozen
assert is_global or rig
assert isinstance(bone, str) or callable(bone)
assert callable(bone) or rig_is_child(rig, self.generator.bone_owners[bone])
assert rig_is_child(rig, inject_into)
@ -82,35 +89,66 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
else:
self.local_parents[id(rig)].append(entry)
def build_child(self, rig, bone, *, use_parent_mch=True, mch_orientation=None, **options):
def build_child(self, rig: BaseRig, bone: str, *,
use_parent_mch: bool = True,
mch_orientation: Optional[str | Matrix] = None,
# Options below must be in child_option_table and can be used in amend_child
extra_parents: OptionalLazy[list[str | tuple[str, str]]] = None,
select_parent: OptionalLazy[str] = None,
select_tags: OptionalLazy[list[str | set[str]]] = None,
ignore_global: bool = False,
exclude_self: bool = False,
allow_self: bool = False,
context_rig: Optional[BaseRig] = None,
no_implicit: bool = False,
only_selected: bool = False,
prop_bone: OptionalLazy[str] = None,
prop_id: Optional[str] = None,
prop_name: Optional[str] = None,
controls: OptionalLazy[list[str]] = None,
ctrl_bone: Optional[str] = None,
no_fix_location: bool = False,
no_fix_rotation: bool = False,
no_fix_scale: bool = False,
copy_location: OptionalLazy[str] = None,
copy_rotation: OptionalLazy[str] = None,
copy_scale: OptionalLazy[str] = None,
inherit_scale: str = 'AVERAGE'):
"""
Build a switchable parent mechanism for the specified bone.
Parameters:
rig Owner of the child bone.
bone Name of the child bone.
extra_parents List of bone names or (name, user_name) pairs to use as additional parents.
use_parent_mch Create an intermediate MCH bone for the constraints and parent the child to it.
mch_orientation Orientation matrix or bone name to align the MCH bone to; defaults to world.
select_parent Select the specified bone instead of the last one.
select_tags List of parent tags to try for default selection.
ignore_global Ignore the is_global flag of potential parents.
exclude_self Ignore parents registered by the rig itself.
allow_self Ignore the 'exclude_self' setting of the parent.
context_rig Rig to use for selecting parents; defaults to rig.
no_implicit Only use parents listed as extra_parents.
only_selected Like no_implicit, but allow the 'default' selected parent.
rig: Owner of the child bone.
bone: Name of the child bone.
extra_parents: List of bone names or (name, user_name) pairs to use as
additional parents.
use_parent_mch: Create an intermediate MCH bone for the constraints and
parent the child to it.
mch_orientation: Orientation matrix or bone name to align the MCH bone to;
defaults to world.
select_parent: Select the specified bone instead of the last one.
select_tags: List of parent tags to try for default selection.
ignore_global: Ignore the is_global flag of potential parents.
exclude_self: Ignore parents registered by the rig itself.
allow_self: Ignore the 'exclude_self' setting of the parent.
context_rig: Rig to use for selecting parents; defaults to rig.
no_implicit: Only use parents listed as extra_parents.
only_selected: Like no_implicit, but allow the 'default' selected parent.
prop_bone Name of the bone to add the property to.
prop_id Actual name of the control property.
prop_name Name of the property to use in the UI script.
controls Collection of controls to bind property UI to.
prop_bone: Name of the bone to add the property to.
prop_id: Actual name of the control property.
prop_name: Name of the property to use in the UI script.
controls: Collection of controls to bind property UI to.
ctrl_bone User visible control bone that depends on this parent (for switch & keep transform)
no_fix_* Disable "Switch and Keep Transform" correction for specific channels.
copy_* Override the specified components by copying from another bone.
inherit_scale Inherit scale mode for the child bone (default: AVERAGE).
ctrl_bone: User visible control bone that depends on this parent
(for switch & keep transform)
no_fix_location: Disable "Switch and Keep Transform" correction for location.
no_fix_rotation: Disable "Switch and Keep Transform" correction for rotation.
no_fix_scale: Disable "Switch and Keep Transform" correction for scale.
copy_location: Override the location by copying from another bone.
copy_rotation: Override the rotation by copying from another bone.
copy_scale: Override the scale by copying from another bone.
inherit_scale: Inherit scale mode for the child bone (default: AVERAGE).
Lazy parameters:
'extra_parents', 'select_parent', 'prop_bone', 'controls', 'copy_*'
@ -131,16 +169,14 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
mch_bone = bone
child = {
**self.child_option_table,
'rig':rig, 'bone': bone, 'mch_bone': mch_bone,
'rig': rig, 'bone': bone, 'mch_bone': mch_bone,
'is_done': False, 'is_configured': False,
}
self.assign_child_options(child, options)
self.assign_child_options(child, self.child_option_table, locals())
self.child_list.append(child)
self.child_map[bone] = child
def amend_child(self, rig, bone, **options):
def amend_child(self, rig: BaseRig, bone: str, **options):
"""
Change parameters assigned in a previous build_child call.
@ -149,10 +185,9 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
assert self.generator.stage == 'generate_bones' and not self.frozen
child = self.child_map[bone]
assert child['rig'] == rig
self.assign_child_options(child, options)
self.assign_child_options(child, set(options.keys()), options)
def rig_child_now(self, bone):
def rig_child_now(self, bone: str):
"""Create the constraints immediately."""
assert self.generator.stage == 'rig_bones'
child = self.child_map[bone]
@ -163,29 +198,29 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
# Implementation
child_option_table = {
'extra_parents': None,
'prop_bone': None, 'prop_id': None, 'prop_name': None, 'controls': None,
'select_parent': None, 'ignore_global': False,
'exclude_self': False, 'allow_self': False,
'context_rig': None, 'select_tags': None,
'no_implicit': False, 'only_selected': False,
'ctrl_bone': None,
'no_fix_location': False, 'no_fix_rotation': False, 'no_fix_scale': False,
'copy_location': None, 'copy_rotation': None, 'copy_scale': None,
'inherit_scale': 'AVERAGE',
'extra_parents',
'prop_bone', 'prop_id', 'prop_name', 'controls',
'select_parent', 'ignore_global',
'exclude_self', 'allow_self',
'context_rig', 'select_tags',
'no_implicit', 'only_selected',
'ctrl_bone',
'no_fix_location', 'no_fix_rotation', 'no_fix_scale',
'copy_location', 'copy_rotation', 'copy_scale',
'inherit_scale',
}
def assign_child_options(self, child, options):
if 'context_rig' in options:
def assign_child_options(self, child, names: set[str], options: dict[str, Any]):
if 'context_rig' in names:
assert rig_is_child(child['rig'], options['context_rig'])
for name, value in options.items():
for name in names:
if name not in self.child_option_table:
raise AttributeError('invalid child option: '+name)
raise AttributeError('invalid child option: ' + name)
child[name] = value
child[name] = options[name]
def get_rig_parent_candidates(self, rig):
def get_rig_parent_candidates(self, rig: Optional[BaseRig]):
candidates = []
# Build a list in parent hierarchy order
@ -199,7 +234,8 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
def generate_bones(self):
self.frozen = True
self.parent_list = self.global_parents + list(chain.from_iterable(self.local_parents.values()))
self.parent_list = (self.global_parents +
list(chain.from_iterable(self.local_parents.values())))
# Link children to parents
for child in self.child_list:
@ -215,7 +251,8 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
continue
if parent['rig'] is child_rig:
if (parent['exclude_self'] and not child['allow_self']) or child['exclude_self']:
if (parent['exclude_self'] and not child['allow_self'])\
or child['exclude_self']:
continue
elif parent['is_global'] and not child['ignore_global']:
# Can't use parents from own children, even if global (cycle risk)
@ -293,7 +330,7 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
if child['no_implicit']:
assert len(extra_parents) > 0
parent_bones = [ item for item in parent_bones if item[0] in extra_parents ]
parent_bones = [item for item in parent_bones if item[0] in extra_parents]
if last_main_parent_bone not in extra_parents:
last_main_parent_bone = parent_bones[-1][0]
@ -308,15 +345,17 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
break
if select_bone not in parent_map:
print("RIGIFY ERROR: Can't find bone '%s' to select as default parent of '%s'\n" % (select_bone, bone))
print(f"RIGIFY ERROR: Can't find bone '{select_bone}' "
f"to select as default parent of '{bone}'\n")
select_bone = last_main_parent_bone
if child['only_selected']:
filter_set = { select_bone, *extra_parents }
parent_bones = [ item for item in parent_bones if item[0] in filter_set ]
filter_set = {select_bone, *extra_parents}
parent_bones = [item for item in parent_bones if item[0] in filter_set]
try:
select_index = 1 + next(i for i, (bone, _) in enumerate(parent_bones) if bone == select_bone)
select_index = 1 + next(i for i, (bone, _) in enumerate(parent_bones)
if bone == select_bone)
except StopIteration:
select_index = len(parent_bones)
print("RIGIFY ERROR: Invalid default parent '%s' of '%s'\n" % (select_bone, bone))
@ -328,8 +367,9 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
prop_name = child['prop_name'] or child['prop_id'] or 'Parent Switch'
prop_id = child['prop_id'] = child['prop_id'] or 'parent_switch'
parent_names = [ parent[1] or strip_prefix(parent[0]) for parent in [(None, 'None'), *parent_bones] ]
parent_str = ', '.join([ '%s (%d)' % (name, i) for i, name in enumerate(parent_names) ])
parent_names = [parent[1] or strip_prefix(parent[0])
for parent in [(None, 'None'), *parent_bones]]
parent_str = ', '.join(['%s (%d)' % (name, i) for i, name in enumerate(parent_names)])
ctrl_bone = child['ctrl_bone'] or bone
@ -341,14 +381,15 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
# Find which channels don't depend on the parent
no_fix = [ child[n] for n in ['no_fix_location', 'no_fix_rotation', 'no_fix_scale'] ]
no_fix = [child[n] for n in ['no_fix_location', 'no_fix_rotation', 'no_fix_scale']]
child['copy'] = [ force_lazy(child[n]) for n in ['copy_location', 'copy_rotation', 'copy_scale'] ]
child['copy'] = [force_lazy(child[n])
for n in ['copy_location', 'copy_rotation', 'copy_scale']]
locks = tuple(bool(nofix or copy) for nofix, copy in zip(no_fix, child['copy']))
locks = tuple(bool(n_fix or copy) for n_fix, copy in zip(no_fix, child['copy']))
# Create the script for the property
controls = force_lazy(child['controls']) or set([prop_bone, bone])
controls = force_lazy(child['controls']) or {prop_bone, bone}
script = self.generator.script
panel = script.panel_with_selected_check(child['rig'], controls)
@ -363,10 +404,13 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
}
row = panel.row(align=True)
lsplit = row.split(factor=0.75, align=True)
lsplit.operator('pose.rigify_switch_parent_{rig_id}', text=prop_name, icon='DOWNARROW_HLT', properties=op_props)
lsplit.custom_prop(prop_bone, prop_id, text='')
row.operator('pose.rigify_switch_parent_bake_{rig_id}', text='', icon='ACTION_TWEAK', properties=op_props)
left_split = row.split(factor=0.75, align=True)
# noinspection SpellCheckingInspection
left_split.operator('pose.rigify_switch_parent_{rig_id}', text=prop_name,
icon='DOWNARROW_HLT', properties=op_props)
left_split.custom_prop(prop_bone, prop_id, text='')
row.operator('pose.rigify_switch_parent_bake_{rig_id}', text='',
icon='ACTION_TWEAK', properties=op_props)
def rig_bones(self):
for child in self.child_list:
@ -382,12 +426,12 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
mch = child['mch_bone']
con = self.make_constraint(
mch, 'ARMATURE', name='SWITCH_PARENT',
targets=[ (parent, 0.0) for parent, _ in child['parent_bones'] ]
targets=[(parent, 0.0) for parent, _ in child['parent_bones']]
)
prop_var = [(child['prop_bone'], child['prop_id'])]
for i, (parent, parent_name) in enumerate(child['parent_bones']):
for i, (_parent, _parent_name) in enumerate(child['parent_bones']):
expr = 'var == %d' % (i+1)
self.make_driver(con.targets[i], 'weight', expression=expr, variables=prop_var)
@ -402,8 +446,10 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
self.make_constraint(mch, 'COPY_SCALE', copy[2])
SCRIPT_REGISTER_OP_SWITCH_PARENT = ['POSE_OT_rigify_switch_parent', 'POSE_OT_rigify_switch_parent_bake']
SCRIPT_REGISTER_OP_SWITCH_PARENT = ['POSE_OT_rigify_switch_parent',
'POSE_OT_rigify_switch_parent_bake']
# noinspection SpellCheckingInspection
SCRIPT_UTILITIES_OP_SWITCH_PARENT = ['''
################################
## Switchable Parent operator ##

View File

@ -5,21 +5,25 @@ import math
import inspect
import functools
from typing import Optional, Callable
from bpy.types import Mesh, Object, UILayout
from mathutils import Matrix, Vector, Euler
from itertools import count
from .errors import MetarigError
from .collections import ensure_collection
from .misc import ArmatureObject, MeshObject, AnyVector, verify_mesh_obj
from .naming import change_name_side, get_name_side, Side
WGT_PREFIX = "WGT-" # Prefix for widget objects
#=============================================
##############################################
# Widget creation
#=============================================
##############################################
def obj_to_bone(obj, rig, bone_name, bone_transform_name=None):
def obj_to_bone(obj: Object, rig: ArmatureObject, bone_name: str,
bone_transform_name: Optional[str] = None):
""" Places an object at the location/rotation/scale of the given bone.
"""
if bpy.context.mode == 'EDIT_ARMATURE':
@ -45,8 +49,13 @@ def obj_to_bone(obj, rig, bone_name, bone_transform_name=None):
obj.matrix_basis = rig.matrix_world @ bone.bone.matrix_local @ shape_mat
def create_widget(rig, bone_name, bone_transform_name=None, *, widget_name=None, widget_force_new=False, subsurf=0):
""" Creates an empty widget object for a bone, and returns the object.
def create_widget(rig: ArmatureObject, bone_name: str,
bone_transform_name: Optional[str] = None, *,
widget_name: Optional[str] = None,
widget_force_new=False, subsurf=0) -> Optional[MeshObject]:
"""
Creates an empty widget object for a bone, and returns the object.
If the object already existed, returns None.
"""
assert rig.mode != 'EDIT'
@ -61,16 +70,17 @@ def create_widget(rig, bone_name, bone_transform_name=None, *, widget_name=None,
if generator:
collection = generator.widget_collection
else:
# noinspection SpellCheckingInspection
collection = ensure_collection(bpy.context, 'WGTS_' + rig.name, hidden=True)
use_mirror = generator and generator.use_mirror_widgets
if use_mirror:
bone_mid_name = change_name_side(bone_name, Side.MIDDLE)
bone_mid_name = change_name_side(bone_name, Side.MIDDLE) if use_mirror else bone_name
obj_name = widget_name or WGT_PREFIX + rig.name + '_' + bone_name
reuse_mesh = None
obj: Optional[MeshObject]
# Check if it already exists in the scene
if not widget_force_new:
obj = None
@ -87,7 +97,9 @@ def create_widget(rig, bone_name, bone_transform_name=None, *, widget_name=None,
# Search the scene by name
obj = scene.objects.get(obj_name)
if obj and obj.library:
local_objs = [obj for obj in scene.objects if obj.name == obj_name and not obj.library]
# Second brute force try if the first result is linked
local_objs = [obj for obj in scene.objects
if obj.name == obj_name and not obj.library]
obj = local_objs[0] if local_objs else None
if obj:
@ -100,7 +112,7 @@ def create_widget(rig, bone_name, bone_transform_name=None, *, widget_name=None,
collection.objects.link(obj)
# Flip scale for originally mirrored widgets
if obj.scale.x < 0 and bone.custom_shape_scale_xyz.x > 0:
if obj.scale.x < 0 < bone.custom_shape_scale_xyz.x:
bone.custom_shape_scale_xyz.x *= -1
# Move object to bone position, in case it changed
@ -132,7 +144,7 @@ def create_widget(rig, bone_name, bone_transform_name=None, *, widget_name=None,
mesh = bpy.data.meshes.new(obj_name)
# Create the object
obj = bpy.data.objects.new(obj_name, mesh)
obj = verify_mesh_obj(bpy.data.objects.new(obj_name, mesh))
collection.objects.link(obj)
# Add the subdivision surface modifier
@ -158,9 +170,9 @@ def create_widget(rig, bone_name, bone_transform_name=None, *, widget_name=None,
return obj
#=============================================
##############################################
# Widget choice dropdown
#=============================================
##############################################
_registered_widgets = {}
@ -170,7 +182,7 @@ def _get_valid_args(callback, skip):
return set(spec.args[skip:] + spec.kwonlyargs)
def register_widget(name, callback, **default_args):
def register_widget(name: str, callback, **default_args):
unwrapped = inspect.unwrap(callback)
if unwrapped != callback:
valid_args = _get_valid_args(unwrapped, 1)
@ -180,10 +192,11 @@ def register_widget(name, callback, **default_args):
_registered_widgets[name] = (callback, valid_args, default_args)
def layout_widget_dropdown(layout, props, prop_name, **kwargs):
"Create a UI dropdown to select a widget from the known list."
def layout_widget_dropdown(layout: UILayout, props, prop_name: str, **kwargs):
"""Create a UI dropdown to select a widget from the known list."""
id_store = bpy.context.window_manager
# noinspection PyUnresolvedReferences
rigify_widgets = id_store.rigify_widgets
rigify_widgets.clear()
@ -195,7 +208,7 @@ def layout_widget_dropdown(layout, props, prop_name, **kwargs):
layout.prop_search(props, prop_name, id_store, "rigify_widgets", **kwargs)
def create_registered_widget(obj, bone_name, widget_id, **kwargs):
def create_registered_widget(obj: ArmatureObject, bone_name: str, widget_id: str, **kwargs):
try:
callback, valid_args, default_args = _registered_widgets[widget_id]
except KeyError:
@ -210,26 +223,27 @@ def create_registered_widget(obj, bone_name, widget_id, **kwargs):
if 'size' in valid_args and not kwargs.get('size'):
kwargs['size'] = kwargs['radius'] * 2
args = { **default_args, **kwargs }
args = {**default_args, **kwargs}
return callback(obj, bone_name, **{ k:v for k,v in args.items() if k in valid_args})
return callback(obj, bone_name, **{k: v for k, v in args.items() if k in valid_args})
#=============================================
##############################################
# Widget geometry
#=============================================
##############################################
class GeometryData:
verts: list[AnyVector]
edges: list[tuple[int, int]]
faces: list[tuple[int, ...]]
def __init__(self):
self.verts = []
self.edges = []
self.faces = []
def widget_generator(generate_func=None, *, register=None, subsurf=0):
if generate_func is None:
return functools.partial(widget_generator, register=register, subsurf=subsurf)
def widget_generator(generate_func=None, *, register=None, subsurf=0) -> Callable:
"""
Decorator that encapsulates a call to create_widget, and only requires
the actual function to fill the provided vertex and edge lists.
@ -237,15 +251,21 @@ def widget_generator(generate_func=None, *, register=None, subsurf=0):
Accepts parameters of create_widget, plus any keyword arguments the
wrapped function has.
"""
if generate_func is None:
return functools.partial(widget_generator, register=register, subsurf=subsurf)
@functools.wraps(generate_func)
def wrapper(rig, bone_name, bone_transform_name=None, widget_name=None, widget_force_new=False, **kwargs):
obj = create_widget(rig, bone_name, bone_transform_name, widget_name=widget_name, widget_force_new=widget_force_new, subsurf=subsurf)
def wrapper(rig: ArmatureObject, bone_name: str, bone_transform_name=None,
widget_name=None, widget_force_new=False, **kwargs):
obj = create_widget(rig, bone_name, bone_transform_name,
widget_name=widget_name, widget_force_new=widget_force_new,
subsurf=subsurf)
if obj is not None:
geom = GeometryData()
generate_func(geom, **kwargs)
mesh = obj.data
mesh: Mesh = obj.data
mesh.from_pydata(geom.verts, geom.edges, geom.faces)
mesh.update()
@ -259,7 +279,9 @@ def widget_generator(generate_func=None, *, register=None, subsurf=0):
return wrapper
def generate_lines_geometry(geom, points, *, matrix=None, closed_loop=False):
def generate_lines_geometry(geom: GeometryData,
points: list[AnyVector], *,
matrix: Optional[Matrix] = None, closed_loop=False):
"""
Generates a polyline using given points, optionally closing the loop.
"""
@ -282,13 +304,15 @@ def generate_lines_geometry(geom, points, *, matrix=None, closed_loop=False):
geom.edges.append((len(geom.verts) - 1, base))
def generate_circle_geometry(geom, center, radius, *, matrix=None, angle_range=None,
steps=24, radius_x=None, depth_x=0):
def generate_circle_geometry(geom: GeometryData, center: AnyVector, radius: float, *,
matrix: Optional[Matrix] = None,
angle_range: Optional[tuple[float, float]] = None,
steps=24, radius_x: Optional[float] = None, depth_x=0):
"""
Generates a circle, adding vertices and edges to the lists.
center, radius: parameters of the circle
matrix: transformation matrix (by default the circle is in the XY plane)
angle_range: pair of angles to generate an arc of the circle
angle_range: a pair of angles to generate an arc of the circle
steps: number of edges to cover the whole circle (reduced for arcs)
"""
assert steps >= 3
@ -319,7 +343,9 @@ def generate_circle_geometry(geom, center, radius, *, matrix=None, angle_range=N
generate_lines_geometry(geom, points, matrix=matrix, closed_loop=not angle_range)
def generate_circle_hull_geometry(geom, points, radius, gap, *, matrix=None, steps=24):
def generate_circle_hull_geometry(geom: GeometryData, points: list[AnyVector],
radius: float, gap: float, *,
matrix: Optional[Matrix] = None, steps=24):
"""
Given a list of 2D points forming a convex hull, generate a contour around
it, with each point being circumscribed with a circle arc of given radius,
@ -337,28 +363,28 @@ def generate_circle_hull_geometry(geom, points, radius, gap, *, matrix=None, ste
base = len(geom.verts)
points_ex = [points[-1], *points, points[0]]
agap = math.asin(gap / radius)
angle_gap = math.asin(gap / radius)
for i, pprev, pcur, pnext in zip(count(0), points_ex[0:], points_ex[1:], points_ex[2:]):
vprev = pprev - pcur
vnext = pnext - pcur
for i, pt_prev, pt_cur, pt_next in zip(count(0), points_ex[0:], points_ex[1:], points_ex[2:]):
vec_prev = pt_prev - pt_cur
vec_next = pt_next - pt_cur
# Compute bearings to adjacent points
aprev = math.atan2(vprev.y, vprev.x)
anext = math.atan2(vnext.y, vnext.x)
if anext <= aprev:
anext += math.pi * 2
angle_prev = math.atan2(vec_prev.y, vec_prev.x)
angle_next = math.atan2(vec_next.y, vec_next.x)
if angle_next <= angle_prev:
angle_next += math.pi * 2
# Adjust gap for circles that are too close
aprev += max(agap, math.acos(min(1, vprev.length/radius/2)))
anext -= max(agap, math.acos(min(1, vnext.length/radius/2)))
angle_prev += max(angle_gap, math.acos(min(1, vec_prev.length/radius/2)))
angle_next -= max(angle_gap, math.acos(min(1, vec_next.length/radius/2)))
if anext > aprev:
if angle_next > angle_prev:
if len(geom.verts) > base:
geom.edges.append((len(geom.verts)-1, len(geom.verts)))
generate_circle_geometry(
geom, pcur, radius, angle_range=(aprev, anext),
geom, pt_cur, radius, angle_range=(angle_prev, angle_next),
matrix=matrix, steps=steps
)
@ -366,7 +392,7 @@ def generate_circle_hull_geometry(geom, points, radius, gap, *, matrix=None, ste
geom.edges.append((len(geom.verts)-1, base))
def create_circle_polygon(number_verts, axis, radius=1.0, head_tail=0.0):
def create_circle_polygon(number_verts: int, axis: str, radius=1.0, head_tail=0.0):
""" Creates a basic circle around of an axis selected.
number_verts: number of vertices of the polygon
axis: axis normal to the circle
@ -380,7 +406,7 @@ def create_circle_polygon(number_verts, axis, radius=1.0, head_tail=0.0):
assert(axis in 'XYZ')
while i < (number_verts):
while i < number_verts:
a = math.cos(i * angle)
b = math.sin(i * angle)
@ -392,7 +418,7 @@ def create_circle_polygon(number_verts, axis, radius=1.0, head_tail=0.0):
verts.append((a * radius, b * radius, head_tail))
if i < (number_verts - 1):
edges.append((i , i + 1))
edges.append((i, i + 1))
i += 1
@ -401,12 +427,13 @@ def create_circle_polygon(number_verts, axis, radius=1.0, head_tail=0.0):
return verts, edges
#=============================================
##############################################
# Widget transformation
#=============================================
##############################################
def adjust_widget_axis(obj, axis='y', offset=0.0):
def adjust_widget_axis(obj: Object, axis='y', offset=0.0):
mesh = obj.data
assert isinstance(mesh, Mesh)
if axis[0] == '-':
s = -1.0
@ -431,31 +458,35 @@ def adjust_widget_axis(obj, axis='y', offset=0.0):
vert.co = matrix @ vert.co
def adjust_widget_transform_mesh(obj, matrix, local=None):
def adjust_widget_transform_mesh(obj: Optional[Object], matrix: Matrix,
local: bool | None = None):
"""Adjust the generated widget by applying a correction matrix to the mesh.
If local is false, the matrix is in world space.
If local is True, it's in the local space of the widget.
If local is a bone, it's in the local space of the bone.
"""
if obj:
mesh = obj.data
assert isinstance(mesh, Mesh)
if local is not True:
if local:
assert isinstance(local, bpy.types.PoseBone)
bonemat = local.id_data.matrix_world @ local.bone.matrix_local
matrix = bonemat @ matrix @ bonemat.inverted()
bone_mat = local.id_data.matrix_world @ local.bone.matrix_local
matrix = bone_mat @ matrix @ bone_mat.inverted()
obmat = obj.matrix_basis
matrix = obmat.inverted() @ matrix @ obmat
obj_mat = obj.matrix_basis
matrix = obj_mat.inverted() @ matrix @ obj_mat
obj.data.transform(matrix)
mesh.transform(matrix)
def write_widget(obj, name='thing', use_size=True):
def write_widget(obj: Object, name='thing', use_size=True):
""" Write a mesh object as a python script for widget use.
"""
script = ""
script += "@widget_generator\n"
script += "def create_"+name+"_widget(geom";
script += "def create_"+name+"_widget(geom"
if use_size:
script += ", *, size=1.0"
script += "):\n"
@ -464,23 +495,26 @@ def write_widget(obj, name='thing', use_size=True):
szs = "*size" if use_size else ""
width = 2 if use_size else 3
mesh = obj.data
assert isinstance(mesh, Mesh)
script += " geom.verts = ["
for i, v in enumerate(obj.data.vertices):
for i, v in enumerate(mesh.vertices):
script += "({:g}{}, {:g}{}, {:g}{}),".format(v.co[0], szs, v.co[1], szs, v.co[2], szs)
script += "\n " if i % width == (width - 1) else " "
script += "]\n"
# Edges
script += " geom.edges = ["
for i, e in enumerate(obj.data.edges):
for i, e in enumerate(mesh.edges):
script += "(" + str(e.vertices[0]) + ", " + str(e.vertices[1]) + "),"
script += "\n " if i % 10 == 9 else " "
script += "]\n"
# Faces
if obj.data.polygons:
if mesh.polygons:
script += " geom.faces = ["
for i, f in enumerate(obj.data.polygons):
for i, f in enumerate(mesh.polygons):
script += "(" + ", ".join(str(v) for v in f.vertices) + "),"
script += "\n " if i % 10 == 9 else " "
script += "]\n"

View File

@ -4,6 +4,7 @@ from .misc import shuffle_matrix
from .widgets import (create_widget, widget_generator, register_widget,
generate_circle_geometry)
# Common Widgets
@widget_generator(register="line")
@ -30,7 +31,8 @@ def create_circle_widget(geom, *, radius=1.0, head_tail=0.0, radius_x=None, head
matrix=shuffle_matrix['xzy'], steps=32
)
if with_line:
geom.edges.append((8, 24)) # Z axis line
geom.edges.append((8, 24)) # Z axis line
register_widget("circle", create_circle_widget, radius=0.5)
@ -56,7 +58,7 @@ def create_diamond_widget(geom, *, radius=0.5):
def create_truncated_cube_widget(geom, *, radius=0.5):
"""Creates a basic truncated cube widget"""
r = radius
r3 = radius/3
r3 = radius / 3
geom.verts = [(r, r3, r), (r, -r3, r), (r3, -r, r), (-r3, -r, r), (-r, -r3, r), (-r, r3, r),
(-r3, r, r), (r3, r, r), (r, r3, -r), (r, -r3, -r), (r3, -r, -r), (-r3, -r, -r),
(-r, -r3, -r), (-r, r3, -r), (-r3, r, -r), (r3, r, -r), (r, r, r3), (r, r, -r3),
@ -78,7 +80,8 @@ def create_cuboctahedron_widget(geom, *, radius=0.5):
(4, 7), (4, 5), (5, 6), (6, 7)]
def create_chain_widget(rig, bone_name, cube=False, radius=0.5, invert=False, bone_transform_name=None, axis="y", offset=0.0):
def create_chain_widget(rig, bone_name, cube=False, radius=0.5, invert=False,
bone_transform_name=None, axis="y", offset=0.0):
"""Creates a basic chain widget
"""
obj = create_widget(rig, bone_name, bone_transform_name)
@ -87,12 +90,15 @@ def create_chain_widget(rig, bone_name, cube=False, radius=0.5, invert=False, bo
if cube:
rh = r
else:
rh = radius/2
rh = radius / 2
if invert:
verts = [(rh, rh, rh), (r, -r, r), (-r, -r, r), (-rh, rh, rh), (rh, rh, -rh), (r, -r, -r), (-r, -r, -r), (-rh, rh, -rh)]
verts = [(rh, rh, rh), (r, -r, r), (-r, -r, r), (-rh, rh, rh), (rh, rh, -rh),
(r, -r, -r), (-r, -r, -r), (-rh, rh, -rh)]
else:
verts = [(r, r, r), (rh, -rh, rh), (-rh, -rh, rh), (-r, r, r), (r, r, -r), (rh, -rh, -rh), (-rh, -rh, -rh), (-r, r, -r)]
edges = [(0, 1), (1, 2), (2, 3), (3, 0), (4, 5), (5, 6), (6, 7), (7, 4), (0, 4), (1, 5), (2, 6), (3, 7)]
verts = [(r, r, r), (rh, -rh, rh), (-rh, -rh, rh), (-r, r, r), (r, r, -r),
(rh, -rh, -rh), (-rh, -rh, -rh), (-r, r, -r)]
edges = [(0, 1), (1, 2), (2, 3), (3, 0), (4, 5), (5, 6), (6, 7), (7, 4), (0, 4),
(1, 5), (2, 6), (3, 7)]
mesh = obj.data
mesh.from_pydata(verts, edges, [])
mesh.update()
@ -103,13 +109,65 @@ def create_chain_widget(rig, bone_name, cube=False, radius=0.5, invert=False, bo
@widget_generator(register="sphere")
def create_sphere_widget(geom, *, radius=0.5):
""" Creates a basic sphere widget, three pependicular overlapping circles.
""" Creates a basic sphere widget, three perpendicular overlapping circles.
"""
geom.verts = [(0.3535533845424652, 0.3535533845424652, 0.0), (0.4619397521018982, 0.19134171307086945, 0.0), (0.5, -2.1855694143368964e-08, 0.0), (0.4619397521018982, -0.19134175777435303, 0.0), (0.3535533845424652, -0.3535533845424652, 0.0), (0.19134174287319183, -0.4619397521018982, 0.0), (7.549790126404332e-08, -0.5, 0.0), (-0.1913416087627411, -0.46193981170654297, 0.0), (-0.35355329513549805, -0.35355350375175476, 0.0), (-0.4619397521018982, -0.19134178757667542, 0.0), (-0.5, 5.962440319251527e-09, 0.0), (-0.4619397222995758, 0.1913418024778366, 0.0), (-0.35355326533317566, 0.35355350375175476, 0.0), (-0.19134148955345154, 0.46193987131118774, 0.0), (3.2584136988589307e-07, 0.5, 0.0), (0.1913420855998993, 0.46193960309028625, 0.0), (7.450580596923828e-08, 0.46193960309028625, 0.19134199619293213), (5.9254205098113744e-08, 0.5, 2.323586443253589e-07), (4.470348358154297e-08, 0.46193987131118774, -0.1913415789604187), (2.9802322387695312e-08, 0.35355350375175476, -0.3535533547401428), (2.9802322387695312e-08, 0.19134178757667542, -0.46193981170654297), (5.960464477539063e-08, -1.1151834122813398e-08, -0.5000000596046448), (5.960464477539063e-08, -0.1913418024778366, -0.46193984150886536), (5.960464477539063e-08, -0.35355350375175476, -0.3535533845424652), (7.450580596923828e-08, -0.46193981170654297, -0.19134166836738586), (9.348272556053416e-08, -0.5, 1.624372103492533e-08), (1.043081283569336e-07, -0.4619397521018982, 0.19134168326854706), (1.1920928955078125e-07, -0.3535533845424652, 0.35355329513549805), (1.1920928955078125e-07, -0.19134174287319183, 0.46193966269493103), (1.1920928955078125e-07, -4.7414250303745575e-09, 0.49999991059303284), (1.1920928955078125e-07, 0.19134172797203064, 0.46193966269493103), (8.940696716308594e-08, 0.3535533845424652, 0.35355329513549805), (0.3535534739494324, 0.0, 0.35355329513549805), (0.1913418173789978, -2.9802322387695312e-08, 0.46193966269493103), (8.303572940349113e-08, -5.005858838558197e-08, 0.49999991059303284), (-0.19134165346622467, -5.960464477539063e-08, 0.46193966269493103), (-0.35355329513549805, -8.940696716308594e-08, 0.35355329513549805), (-0.46193963289260864, -5.960464477539063e-08, 0.19134168326854706), (-0.49999991059303284, -5.960464477539063e-08, 1.624372103492533e-08), (-0.4619397521018982, -2.9802322387695312e-08, -0.19134166836738586), (-0.3535534143447876, -2.9802322387695312e-08, -0.3535533845424652), (-0.19134171307086945, 0.0, -0.46193984150886536), (7.662531942287387e-08, 9.546055501630235e-09, -0.5000000596046448), (0.19134187698364258, 5.960464477539063e-08, -0.46193981170654297), (0.3535535931587219, 5.960464477539063e-08, -0.3535533547401428), (0.4619399905204773, 5.960464477539063e-08, -0.1913415789604187), (0.5000000596046448, 5.960464477539063e-08, 2.323586443253589e-07), (0.4619396924972534, 2.9802322387695312e-08, 0.19134199619293213)]
geom.verts = [(0.3535533845424652, 0.3535533845424652, 0.0),
(0.4619397521018982, 0.19134171307086945, 0.0),
(0.5, -2.1855694143368964e-08, 0.0),
(0.4619397521018982, -0.19134175777435303, 0.0),
(0.3535533845424652, -0.3535533845424652, 0.0),
(0.19134174287319183, -0.4619397521018982, 0.0),
(7.549790126404332e-08, -0.5, 0.0),
(-0.1913416087627411, -0.46193981170654297, 0.0),
(-0.35355329513549805, -0.35355350375175476, 0.0),
(-0.4619397521018982, -0.19134178757667542, 0.0),
(-0.5, 5.962440319251527e-09, 0.0),
(-0.4619397222995758, 0.1913418024778366, 0.0),
(-0.35355326533317566, 0.35355350375175476, 0.0),
(-0.19134148955345154, 0.46193987131118774, 0.0),
(3.2584136988589307e-07, 0.5, 0.0),
(0.1913420855998993, 0.46193960309028625, 0.0),
(7.450580596923828e-08, 0.46193960309028625, 0.19134199619293213),
(5.9254205098113744e-08, 0.5, 2.323586443253589e-07),
(4.470348358154297e-08, 0.46193987131118774, -0.1913415789604187),
(2.9802322387695312e-08, 0.35355350375175476, -0.3535533547401428),
(2.9802322387695312e-08, 0.19134178757667542, -0.46193981170654297),
(5.960464477539063e-08, -1.1151834122813398e-08, -0.5000000596046448),
(5.960464477539063e-08, -0.1913418024778366, -0.46193984150886536),
(5.960464477539063e-08, -0.35355350375175476, -0.3535533845424652),
(7.450580596923828e-08, -0.46193981170654297, -0.19134166836738586),
(9.348272556053416e-08, -0.5, 1.624372103492533e-08),
(1.043081283569336e-07, -0.4619397521018982, 0.19134168326854706),
(1.1920928955078125e-07, -0.3535533845424652, 0.35355329513549805),
(1.1920928955078125e-07, -0.19134174287319183, 0.46193966269493103),
(1.1920928955078125e-07, -4.7414250303745575e-09, 0.49999991059303284),
(1.1920928955078125e-07, 0.19134172797203064, 0.46193966269493103),
(8.940696716308594e-08, 0.3535533845424652, 0.35355329513549805),
(0.3535534739494324, 0.0, 0.35355329513549805),
(0.1913418173789978, -2.9802322387695312e-08, 0.46193966269493103),
(8.303572940349113e-08, -5.005858838558197e-08, 0.49999991059303284),
(-0.19134165346622467, -5.960464477539063e-08, 0.46193966269493103),
(-0.35355329513549805, -8.940696716308594e-08, 0.35355329513549805),
(-0.46193963289260864, -5.960464477539063e-08, 0.19134168326854706),
(-0.49999991059303284, -5.960464477539063e-08, 1.624372103492533e-08),
(-0.4619397521018982, -2.9802322387695312e-08, -0.19134166836738586),
(-0.3535534143447876, -2.9802322387695312e-08, -0.3535533845424652),
(-0.19134171307086945, 0.0, -0.46193984150886536),
(7.662531942287387e-08, 9.546055501630235e-09, -0.5000000596046448),
(0.19134187698364258, 5.960464477539063e-08, -0.46193981170654297),
(0.3535535931587219, 5.960464477539063e-08, -0.3535533547401428),
(0.4619399905204773, 5.960464477539063e-08, -0.1913415789604187),
(0.5000000596046448, 5.960464477539063e-08, 2.323586443253589e-07),
(0.4619396924972534, 2.9802322387695312e-08, 0.19134199619293213)]
if radius != 0.5:
radius /= 0.5
geom.verts = [(a[0] * radius, a[1] * radius, a[2] * radius) for a in geom.verts]
geom.edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9), (9, 10), (10, 11), (11, 12), (12, 13), (13, 14), (14, 15), (0, 15), (16, 31), (16, 17), (17, 18), (18, 19), (19, 20), (20, 21), (21, 22), (22, 23), (23, 24), (24, 25), (25, 26), (26, 27), (27, 28), (28, 29), (29, 30), (30, 31), (32, 33), (33, 34), (34, 35), (35, 36), (36, 37), (37, 38), (38, 39), (39, 40), (40, 41), (41, 42), (42, 43), (43, 44), (44, 45), (45, 46), (46, 47), (32, 47)]
geom.edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9), (9, 10),
(10, 11), (11, 12), (12, 13), (13, 14), (14, 15), (0, 15), (16, 31), (16, 17),
(17, 18), (18, 19), (19, 20), (20, 21), (21, 22), (22, 23), (23, 24), (24, 25),
(25, 26), (26, 27), (27, 28), (28, 29), (29, 30), (30, 31), (32, 33), (33, 34),
(34, 35), (35, 36), (36, 37), (37, 38), (38, 39), (39, 40), (40, 41), (41, 42),
(42, 43), (43, 44), (44, 45), (45, 46), (46, 47), (32, 47)]
@widget_generator(register="limb")
@ -117,16 +175,54 @@ def create_limb_widget(geom):
""" Creates a basic limb widget, a line that spans the length of the
bone, with a circle around the center.
"""
geom.verts = [(-1.1920928955078125e-07, 1.7881393432617188e-07, 0.0), (3.5762786865234375e-07, 1.0000004768371582, 0.0), (0.1767769455909729, 0.5000001192092896, 0.17677664756774902), (0.20786768198013306, 0.5000001192092896, 0.1388925313949585), (0.23097014427185059, 0.5000001192092896, 0.09567084908485413), (0.24519658088684082, 0.5000001192092896, 0.048772573471069336), (0.2500002384185791, 0.5000001192092896, -2.545945676502015e-09), (0.24519658088684082, 0.5000001192092896, -0.048772573471069336), (0.23097014427185059, 0.5000001192092896, -0.09567084908485413), (0.20786768198013306, 0.5000001192092896, -0.13889259099960327), (0.1767769455909729, 0.5000001192092896, -0.1767767071723938), (0.13889282941818237, 0.5000001192092896, -0.20786744356155396), (0.09567105770111084, 0.5000001192092896, -0.23096990585327148), (0.04877278208732605, 0.5000001192092896, -0.24519634246826172), (1.7279069197684294e-07, 0.5000000596046448, -0.25), (-0.0487724244594574, 0.5000001192092896, -0.24519634246826172), (-0.09567070007324219, 0.5000001192092896, -0.2309698462486267), (-0.13889241218566895, 0.5000001192092896, -0.20786738395690918), (-0.17677652835845947, 0.5000001192092896, -0.17677664756774902), (-0.20786726474761963, 0.5000001192092896, -0.13889244198799133), (-0.23096972703933716, 0.5000001192092896, -0.09567070007324219), (-0.24519610404968262, 0.5000001192092896, -0.04877239465713501), (-0.2499997615814209, 0.5000001192092896, 2.1997936983098043e-07), (-0.24519598484039307, 0.5000001192092896, 0.04877282679080963), (-0.23096948862075806, 0.5000001192092896, 0.09567108750343323), (-0.20786696672439575, 0.5000001192092896, 0.1388927698135376), (-0.1767762303352356, 0.5000001192092896, 0.17677688598632812), (-0.13889199495315552, 0.5000001192092896, 0.2078675627708435), (-0.09567028284072876, 0.5000001192092896, 0.23097002506256104), (-0.048771947622299194, 0.5000001192092896, 0.24519634246826172), (6.555903269145347e-07, 0.5000001192092896, 0.25), (0.04877324402332306, 0.5000001192092896, 0.24519622325897217), (0.09567153453826904, 0.5000001192092896, 0.23096966743469238), (0.13889318704605103, 0.5000001192092896, 0.20786714553833008)]
geom.edges = [(0, 1), (2, 3), (4, 3), (5, 4), (5, 6), (6, 7), (8, 7), (8, 9), (10, 9), (10, 11), (11, 12), (13, 12), (14, 13), (14, 15), (16, 15), (16, 17), (17, 18), (19, 18), (19, 20), (21, 20), (21, 22), (22, 23), (24, 23), (25, 24), (25, 26), (27, 26), (27, 28), (29, 28), (29, 30), (30, 31), (32, 31), (32, 33), (2, 33)]
geom.verts = [(-1.1920928955078125e-07, 1.7881393432617188e-07, 0.0),
(3.5762786865234375e-07, 1.0000004768371582, 0.0),
(0.1767769455909729, 0.5000001192092896, 0.17677664756774902),
(0.20786768198013306, 0.5000001192092896, 0.1388925313949585),
(0.23097014427185059, 0.5000001192092896, 0.09567084908485413),
(0.24519658088684082, 0.5000001192092896, 0.048772573471069336),
(0.2500002384185791, 0.5000001192092896, -2.545945676502015e-09),
(0.24519658088684082, 0.5000001192092896, -0.048772573471069336),
(0.23097014427185059, 0.5000001192092896, -0.09567084908485413),
(0.20786768198013306, 0.5000001192092896, -0.13889259099960327),
(0.1767769455909729, 0.5000001192092896, -0.1767767071723938),
(0.13889282941818237, 0.5000001192092896, -0.20786744356155396),
(0.09567105770111084, 0.5000001192092896, -0.23096990585327148),
(0.04877278208732605, 0.5000001192092896, -0.24519634246826172),
(1.7279069197684294e-07, 0.5000000596046448, -0.25),
(-0.0487724244594574, 0.5000001192092896, -0.24519634246826172),
(-0.09567070007324219, 0.5000001192092896, -0.2309698462486267),
(-0.13889241218566895, 0.5000001192092896, -0.20786738395690918),
(-0.17677652835845947, 0.5000001192092896, -0.17677664756774902),
(-0.20786726474761963, 0.5000001192092896, -0.13889244198799133),
(-0.23096972703933716, 0.5000001192092896, -0.09567070007324219),
(-0.24519610404968262, 0.5000001192092896, -0.04877239465713501),
(-0.2499997615814209, 0.5000001192092896, 2.1997936983098043e-07),
(-0.24519598484039307, 0.5000001192092896, 0.04877282679080963),
(-0.23096948862075806, 0.5000001192092896, 0.09567108750343323),
(-0.20786696672439575, 0.5000001192092896, 0.1388927698135376),
(-0.1767762303352356, 0.5000001192092896, 0.17677688598632812),
(-0.13889199495315552, 0.5000001192092896, 0.2078675627708435),
(-0.09567028284072876, 0.5000001192092896, 0.23097002506256104),
(-0.048771947622299194, 0.5000001192092896, 0.24519634246826172),
(6.555903269145347e-07, 0.5000001192092896, 0.25),
(0.04877324402332306, 0.5000001192092896, 0.24519622325897217),
(0.09567153453826904, 0.5000001192092896, 0.23096966743469238),
(0.13889318704605103, 0.5000001192092896, 0.20786714553833008)]
geom.edges = [(0, 1), (2, 3), (4, 3), (5, 4), (5, 6), (6, 7), (8, 7), (8, 9), (10, 9),
(10, 11), (11, 12), (13, 12), (14, 13), (14, 15), (16, 15), (16, 17), (17, 18),
(19, 18), (19, 20), (21, 20), (21, 22), (22, 23), (24, 23), (25, 24), (25, 26),
(27, 26), (27, 28), (29, 28), (29, 30), (30, 31), (32, 31), (32, 33), (2, 33)]
@widget_generator(register="bone")
def create_bone_widget(geom, *, r1=0.1, l1=0.0, r2=0.04, l2=1.0):
""" Creates a basic bone widget, a simple obolisk-esk shape.
""" Creates a basic bone widget, a simple obelisk-esk shape.
"""
geom.verts = [(r2, l2, -r2), (r1, l1, -r1), (-r1, l1, -r1), (-r2, l2, -r2), (r2, l2, r2), (r1, l1, r1), (-r1, l1, r1), (-r2, l2, r2)]
geom.edges = [(1, 2), (0, 1), (0, 3), (2, 3), (4, 5), (5, 6), (6, 7), (4, 7), (1, 5), (0, 4), (2, 6), (3, 7)]
geom.verts = [(r2, l2, -r2), (r1, l1, -r1), (-r1, l1, -r1), (-r2, l2, -r2), (r2, l2, r2),
(r1, l1, r1), (-r1, l1, r1), (-r2, l2, r2)]
geom.edges = [(1, 2), (0, 1), (0, 3), (2, 3), (4, 5), (5, 6), (6, 7), (4, 7), (1, 5), (0, 4),
(2, 6), (3, 7)]
@widget_generator(register="pivot")
@ -137,23 +233,33 @@ def create_pivot_widget(geom, *, radius=0.5, axis_size=1.0, cap_size=1.0, square
axis = radius * axis_size
cap = 0.1 * radius * cap_size
if square:
geom.verts = [(0, 0, -axis), (-axis, 0, 0), (0, 0, axis), (axis, 0, 0), (axis, cap, -cap), (axis, cap, cap),
(0, -axis, 0), (0, axis, 0), (cap, axis, cap), (cap, axis, -cap), (axis, -cap, -cap), (axis, -cap, cap),
(-cap, axis, cap), (-cap, axis, -cap), (-axis, cap, cap), (-axis, cap, -cap), (-axis, -cap, cap), (-axis, -cap, -cap),
(-cap, -axis, cap), (-cap, -axis, -cap), (cap, -axis, cap), (cap, -axis, -cap), (-cap, -cap, -axis), (-cap, cap, -axis),
(cap, -cap, -axis), (cap, cap, -axis), (-cap, cap, axis), (-cap, -cap, axis), (cap, cap, axis), (cap, -cap, axis) ]
geom.edges = [(10, 4), (4, 5), (8, 9), (0, 2), (12, 8), (6, 7), (11, 10), (13, 12), (5, 11), (9, 13),
(3, 1), (14, 15), (16, 14), (17, 16), (15, 17), (18, 19), (20, 18), (21, 20), (19, 21), (22, 23),
(24, 22), (25, 24), (23, 25), (26, 27), (28, 26), (29, 28), (27, 29) ]
geom.verts = [(0, 0, -axis), (-axis, 0, 0), (0, 0, axis), (axis, 0, 0), (axis, cap, -cap),
(axis, cap, cap), (0, -axis, 0), (0, axis, 0), (cap, axis, cap),
(cap, axis, -cap), (axis, -cap, -cap), (axis, -cap, cap),
(-cap, axis, cap), (-cap, axis, -cap), (-axis, cap, cap), (-axis, cap, -cap),
(-axis, -cap, cap), (-axis, -cap, -cap), (-cap, -axis, cap),
(-cap, -axis, -cap), (cap, -axis, cap), (cap, -axis, -cap),
(-cap, -cap, -axis), (-cap, cap, -axis),
(cap, -cap, -axis), (cap, cap, -axis), (-cap, cap, axis), (-cap, -cap, axis),
(cap, cap, axis), (cap, -cap, axis)]
geom.edges = [(10, 4), (4, 5), (8, 9), (0, 2), (12, 8), (6, 7), (11, 10), (13, 12),
(5, 11), (9, 13), (3, 1), (14, 15), (16, 14), (17, 16), (15, 17), (18, 19),
(20, 18), (21, 20), (19, 21), (22, 23), (24, 22), (25, 24), (23, 25),
(26, 27), (28, 26), (29, 28), (27, 29)]
else:
geom.verts = [(0, 0, -axis), (-axis, 0, 0), (0, 0, axis), (axis, 0, 0), (-cap, 0, -axis), (-axis, 0, -cap),
(-axis, 0, cap), (-cap, 0, axis), (cap, 0, axis), (axis, 0, cap), (axis, 0, -cap), (cap, 0, -axis),
(0, -axis, 0), (0, axis, 0), (0, -cap, -axis), (0, -axis, -cap), (0, -axis, cap), (0, -cap, axis),
(0, cap, axis), (0, axis, cap), (0, axis, -cap), (0, cap, -axis), (-axis, -cap, 0), (-cap, -axis, 0),
(cap, -axis, 0), (axis, -cap, 0), (axis, cap, 0), (cap, axis, 0), (-cap, axis, 0), (-axis, cap, 0) ]
geom.edges = [(4, 0), (6, 1), (8, 2), (10, 3), (1, 5), (2, 7), (3, 9), (0, 11), (16, 12), (0, 21),
(2, 17), (20, 13), (12, 15), (0, 2), (18, 2), (13, 19), (12, 13), (1, 29), (22, 1), (3, 25),
(13, 27), (14, 0), (26, 3), (28, 13), (24, 12), (12, 23), (3, 1) ]
geom.verts = [(0, 0, -axis), (-axis, 0, 0), (0, 0, axis), (axis, 0, 0), (-cap, 0, -axis),
(-axis, 0, -cap), (-axis, 0, cap), (-cap, 0, axis), (cap, 0, axis),
(axis, 0, cap), (axis, 0, -cap), (cap, 0, -axis), (0, -axis, 0),
(0, axis, 0), (0, -cap, -axis), (0, -axis, -cap), (0, -axis, cap),
(0, -cap, axis), (0, cap, axis), (0, axis, cap), (0, axis, -cap),
(0, cap, -axis), (-axis, -cap, 0), (-cap, -axis, 0), (cap, -axis, 0),
(axis, -cap, 0), (axis, cap, 0), (cap, axis, 0), (-cap, axis, 0),
(-axis, cap, 0)]
geom.edges = [(4, 0), (6, 1), (8, 2), (10, 3), (1, 5), (2, 7), (3, 9), (0, 11), (16, 12),
(0, 21), (2, 17), (20, 13), (12, 15), (0, 2), (18, 2), (13, 19), (12, 13),
(1, 29), (22, 1), (3, 25), (13, 27), (14, 0), (26, 3), (28, 13), (24, 12),
(12, 23), (3, 1)]
register_widget("pivot_cross", create_pivot_widget, square=False)
@ -162,22 +268,34 @@ register_widget("pivot_cross", create_pivot_widget, square=False)
def create_shoulder_widget(geom, *, radius=0.5):
r = radius * 2
geom.verts = [(0, 0, 0), (0, 1, 0),
(0.41214*r, 0.5+(0.276111-0.5)*r, 0.282165*r), (0.469006*r, 0.5+(0.31436-0.5)*r, 0.168047*r),
(0.492711*r, 0.5+(0.370708-0.5)*r, 0.0740018*r), (0.498419*r, 0.5+(0.440597-0.5)*r, 0.0160567*r),
(0.5*r, 0.5, 0), (0.498419*r, 0.5+(0.559402-0.5)*r, 0.0160563*r),
(0.492712*r, 0.5+(0.629291-0.5)*r, 0.074001*r), (0.469006*r, 0.5+(0.68564-0.5)*r, 0.168046*r),
(0.412141*r, 0.5+(0.723889-0.5)*r, 0.282164*r), (0.316952*r, 0.5+(0.742335-0.5)*r, 0.383591*r),
(0.207152*r, 0.5+(0.74771-0.5)*r, 0.453489*r), (0.0999976*r, 0.5+(0.74949-0.5)*r, 0.489649*r),
(0, 0.5+(0.75-0.5)*r, 0.5*r), (-0.099997*r, 0.5+(0.74949-0.5)*r, 0.489649*r),
(-0.207152*r, 0.5+(0.74771-0.5)*r, 0.453489*r), (-0.316951*r, 0.5+(0.742335-0.5)*r, 0.383592*r),
(-0.412141*r, 0.5+(0.723889-0.5)*r, 0.282165*r), (-0.469006*r, 0.5+(0.68564-0.5)*r, 0.168046*r),
(-0.492711*r, 0.5+(0.629291-0.5)*r, 0.0740011*r), (-0.498419*r, 0.5+(0.559402-0.5)*r, 0.0160563*r),
(-0.5*r, 0.5, 0), (-0.498419*r, 0.5+(0.440598-0.5)*r, 0.0160563*r),
(-0.492711*r, 0.5+(0.370709-0.5)*r, 0.0740012*r), (-0.469006*r, 0.5+(0.31436-0.5)*r, 0.168047*r),
(-0.41214*r, 0.5+(0.276111-0.5)*r, 0.282165*r), (-0.316951*r, 0.5+(0.257665-0.5)*r, 0.383592*r),
(-0.207151*r, 0.5+(0.25229-0.5)*r, 0.453489*r), (-0.0999959*r, 0.5+(0.25051-0.5)*r, 0.489649*r),
(0, 0.5+(0.25-0.5)*r, 0.5*r), (0.0999986*r, 0.5+(0.25051-0.5)*r, 0.489648*r),
(0.207153*r, 0.5+(0.25229-0.5)*r, 0.453488*r), (0.316953*r, 0.5+(0.257665-0.5)*r, 0.38359*r),
(0.41214 * r, 0.5 + (0.276111 - 0.5) * r, 0.282165 * r),
(0.469006 * r, 0.5 + (0.31436 - 0.5) * r, 0.168047 * r),
(0.492711 * r, 0.5 + (0.370708 - 0.5) * r, 0.0740018 * r),
(0.498419 * r, 0.5 + (0.440597 - 0.5) * r, 0.0160567 * r),
(0.5 * r, 0.5, 0), (0.498419 * r, 0.5 + (0.559402 - 0.5) * r, 0.0160563 * r),
(0.492712 * r, 0.5 + (0.629291 - 0.5) * r, 0.074001 * r),
(0.469006 * r, 0.5 + (0.68564 - 0.5) * r, 0.168046 * r),
(0.412141 * r, 0.5 + (0.723889 - 0.5) * r, 0.282164 * r),
(0.316952 * r, 0.5 + (0.742335 - 0.5) * r, 0.383591 * r),
(0.207152 * r, 0.5 + (0.74771 - 0.5) * r, 0.453489 * r),
(0.0999976 * r, 0.5 + (0.74949 - 0.5) * r, 0.489649 * r),
(0, 0.5 + (0.75 - 0.5) * r, 0.5 * r), (-0.099997 * r, 0.5 + (0.74949 - 0.5) * r, 0.489649 * r),
(-0.207152 * r, 0.5 + (0.74771 - 0.5) * r, 0.453489 * r),
(-0.316951 * r, 0.5 + (0.742335 - 0.5) * r, 0.383592 * r),
(-0.412141 * r, 0.5 + (0.723889 - 0.5) * r, 0.282165 * r),
(-0.469006 * r, 0.5 + (0.68564 - 0.5) * r, 0.168046 * r),
(-0.492711 * r, 0.5 + (0.629291 - 0.5) * r, 0.0740011 * r),
(-0.498419 * r, 0.5 + (0.559402 - 0.5) * r, 0.0160563 * r),
(-0.5 * r, 0.5, 0), (-0.498419 * r, 0.5 + (0.440598 - 0.5) * r, 0.0160563 * r),
(-0.492711 * r, 0.5 + (0.370709 - 0.5) * r, 0.0740012 * r),
(-0.469006 * r, 0.5 + (0.31436 - 0.5) * r, 0.168047 * r),
(-0.41214 * r, 0.5 + (0.276111 - 0.5) * r, 0.282165 * r),
(-0.316951 * r, 0.5 + (0.257665 - 0.5) * r, 0.383592 * r),
(-0.207151 * r, 0.5 + (0.25229 - 0.5) * r, 0.453489 * r),
(-0.0999959 * r, 0.5 + (0.25051 - 0.5) * r, 0.489649 * r),
(0, 0.5 + (0.25 - 0.5) * r, 0.5 * r), (0.0999986 * r, 0.5 + (0.25051 - 0.5) * r, 0.489648 * r),
(0.207153 * r, 0.5 + (0.25229 - 0.5) * r, 0.453488 * r),
(0.316953 * r, 0.5 + (0.257665 - 0.5) * r, 0.38359 * r),
]
geom.edges = [(0, 1), (2, 3), (4, 3), (5, 4), (5, 6), (6, 7), (8, 7), (8, 9), (10, 9), (10, 11),
(11, 12), (13, 12), (14, 13), (14, 15), (16, 15), (16, 17), (17, 18), (19, 18), (19, 20), (21, 20),

View File

@ -7,9 +7,27 @@ def create_compass_widget(rig, bone_name, bone_transform_name=None):
""" Creates a compass-shaped widget.
"""
obj = create_widget(rig, bone_name, bone_transform_name)
if obj != None:
verts = [(0.0, 1.2000000476837158, 0.0), (0.19509032368659973, 0.9807852506637573, 0.0), (0.3826834559440613, 0.9238795042037964, 0.0), (0.5555702447891235, 0.8314695954322815, 0.0), (0.7071067690849304, 0.7071067690849304, 0.0), (0.8314696550369263, 0.5555701851844788, 0.0), (0.9238795042037964, 0.3826834261417389, 0.0), (0.9807852506637573, 0.19509035348892212, 0.0), (1.2000000476837158, 7.549790126404332e-08, 0.0), (0.9807853102684021, -0.19509020447731018, 0.0), (0.9238795638084412, -0.38268327713012695, 0.0), (0.8314696550369263, -0.5555701851844788, 0.0), (0.7071067690849304, -0.7071067690849304, 0.0), (0.5555701851844788, -0.8314696550369263, 0.0), (0.38268327713012695, -0.9238796234130859, 0.0), (0.19509008526802063, -0.9807853102684021, 0.0), (-3.2584136988589307e-07, -1.2999999523162842, 0.0), (-0.19509072601795197, -0.9807851910591125, 0.0), (-0.3826838731765747, -0.9238793253898621, 0.0), (-0.5555707216262817, -0.8314692974090576, 0.0), (-0.7071072459220886, -0.707106351852417, 0.0), (-0.8314700126647949, -0.5555696487426758, 0.0), (-0.923879861831665, -0.3826826810836792, 0.0), (-0.9807854294776917, -0.1950894594192505, 0.0), (-1.2000000476837158, 9.655991561885457e-07, 0.0), (-0.980785071849823, 0.1950913518667221, 0.0), (-0.923879086971283, 0.38268446922302246, 0.0), (-0.831468939781189, 0.5555712580680847, 0.0), (-0.7071058750152588, 0.707107663154602, 0.0), (-0.5555691123008728, 0.8314703702926636, 0.0), (-0.38268208503723145, 0.9238801002502441, 0.0), (-0.19508881866931915, 0.9807855486869812, 0.0)]
edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9), (9, 10), (10, 11), (11, 12), (12, 13), (13, 14), (14, 15), (15, 16), (16, 17), (17, 18), (18, 19), (19, 20), (20, 21), (21, 22), (22, 23), (23, 24), (24, 25), (25, 26), (26, 27), (27, 28), (28, 29), (29, 30), (30, 31), (0, 31)]
if obj is not None:
verts = [(0.0, 1.2000000476837158, 0.0), (0.19509032368659973, 0.9807852506637573, 0.0),
(0.3826834559440613, 0.9238795042037964, 0.0), (0.5555702447891235, 0.8314695954322815, 0.0),
(0.7071067690849304, 0.7071067690849304, 0.0), (0.8314696550369263, 0.5555701851844788, 0.0),
(0.9238795042037964, 0.3826834261417389, 0.0), (0.9807852506637573, 0.19509035348892212, 0.0),
(1.2000000476837158, 7.549790126404332e-08, 0.0), (0.9807853102684021, -0.19509020447731018, 0.0),
(0.9238795638084412, -0.38268327713012695, 0.0), (0.8314696550369263, -0.5555701851844788, 0.0),
(0.7071067690849304, -0.7071067690849304, 0.0), (0.5555701851844788, -0.8314696550369263, 0.0),
(0.38268327713012695, -0.9238796234130859, 0.0), (0.19509008526802063, -0.9807853102684021, 0.0),
(-3.2584136988589307e-07, -1.2999999523162842, 0.0), (-0.19509072601795197, -0.9807851910591125, 0.0),
(-0.3826838731765747, -0.9238793253898621, 0.0), (-0.5555707216262817, -0.8314692974090576, 0.0),
(-0.7071072459220886, -0.707106351852417, 0.0), (-0.8314700126647949, -0.5555696487426758, 0.0),
(-0.923879861831665, -0.3826826810836792, 0.0), (-0.9807854294776917, -0.1950894594192505, 0.0),
(-1.2000000476837158, 9.655991561885457e-07, 0.0), (-0.980785071849823, 0.1950913518667221, 0.0),
(-0.923879086971283, 0.38268446922302246, 0.0), (-0.831468939781189, 0.5555712580680847, 0.0),
(-0.7071058750152588, 0.707107663154602, 0.0), (-0.5555691123008728, 0.8314703702926636, 0.0),
(-0.38268208503723145, 0.9238801002502441, 0.0), (-0.19508881866931915, 0.9807855486869812, 0.0)]
edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9), (9, 10),
(10, 11), (11, 12), (12, 13), (13, 14), (14, 15), (15, 16), (16, 17), (17, 18),
(18, 19), (19, 20), (20, 21), (21, 22), (22, 23), (23, 24), (24, 25), (25, 26),
(26, 27), (27, 28), (28, 29), (29, 30), (30, 31), (0, 31)]
mesh = obj.data
mesh.from_pydata(verts, edges, [])
mesh.update()
@ -19,9 +37,37 @@ def create_root_widget(rig, bone_name, bone_transform_name=None):
""" Creates a widget for the root bone.
"""
obj = create_widget(rig, bone_name, bone_transform_name)
if obj != None:
verts = [(0.7071067690849304, 0.7071067690849304, 0.0), (0.7071067690849304, -0.7071067690849304, 0.0), (-0.7071067690849304, 0.7071067690849304, 0.0), (-0.7071067690849304, -0.7071067690849304, 0.0), (0.8314696550369263, 0.5555701851844788, 0.0), (0.8314696550369263, -0.5555701851844788, 0.0), (-0.8314696550369263, 0.5555701851844788, 0.0), (-0.8314696550369263, -0.5555701851844788, 0.0), (0.9238795042037964, 0.3826834261417389, 0.0), (0.9238795042037964, -0.3826834261417389, 0.0), (-0.9238795042037964, 0.3826834261417389, 0.0), (-0.9238795042037964, -0.3826834261417389, 0.0), (0.9807852506637573, 0.19509035348892212, 0.0), (0.9807852506637573, -0.19509035348892212, 0.0), (-0.9807852506637573, 0.19509035348892212, 0.0), (-0.9807852506637573, -0.19509035348892212, 0.0), (0.19509197771549225, 0.9807849526405334, 0.0), (0.19509197771549225, -0.9807849526405334, 0.0), (-0.19509197771549225, 0.9807849526405334, 0.0), (-0.19509197771549225, -0.9807849526405334, 0.0), (0.3826850652694702, 0.9238788485527039, 0.0), (0.3826850652694702, -0.9238788485527039, 0.0), (-0.3826850652694702, 0.9238788485527039, 0.0), (-0.3826850652694702, -0.9238788485527039, 0.0), (0.5555717945098877, 0.8314685821533203, 0.0), (0.5555717945098877, -0.8314685821533203, 0.0), (-0.5555717945098877, 0.8314685821533203, 0.0), (-0.5555717945098877, -0.8314685821533203, 0.0), (0.19509197771549225, 1.2807848453521729, 0.0), (0.19509197771549225, -1.2807848453521729, 0.0), (-0.19509197771549225, 1.2807848453521729, 0.0), (-0.19509197771549225, -1.2807848453521729, 0.0), (1.280785322189331, 0.19509035348892212, 0.0), (1.280785322189331, -0.19509035348892212, 0.0), (-1.280785322189331, 0.19509035348892212, 0.0), (-1.280785322189331, -0.19509035348892212, 0.0), (0.3950919806957245, 1.2807848453521729, 0.0), (0.3950919806957245, -1.2807848453521729, 0.0), (-0.3950919806957245, 1.2807848453521729, 0.0), (-0.3950919806957245, -1.2807848453521729, 0.0), (1.280785322189331, 0.39509034156799316, 0.0), (1.280785322189331, -0.39509034156799316, 0.0), (-1.280785322189331, 0.39509034156799316, 0.0), (-1.280785322189331, -0.39509034156799316, 0.0), (0.0, 1.5807849168777466, 0.0), (0.0, -1.5807849168777466, 0.0), (1.5807852745056152, 0.0, 0.0), (-1.5807852745056152, 0.0, 0.0)]
edges = [(0, 4), (1, 5), (2, 6), (3, 7), (4, 8), (5, 9), (6, 10), (7, 11), (8, 12), (9, 13), (10, 14), (11, 15), (16, 20), (17, 21), (18, 22), (19, 23), (20, 24), (21, 25), (22, 26), (23, 27), (0, 24), (1, 25), (2, 26), (3, 27), (16, 28), (17, 29), (18, 30), (19, 31), (12, 32), (13, 33), (14, 34), (15, 35), (28, 36), (29, 37), (30, 38), (31, 39), (32, 40), (33, 41), (34, 42), (35, 43), (36, 44), (37, 45), (38, 44), (39, 45), (40, 46), (41, 46), (42, 47), (43, 47)]
if obj is not None:
verts = [(0.7071067690849304, 0.7071067690849304, 0.0), (0.7071067690849304, -0.7071067690849304, 0.0),
(-0.7071067690849304, 0.7071067690849304, 0.0), (-0.7071067690849304, -0.7071067690849304, 0.0),
(0.8314696550369263, 0.5555701851844788, 0.0), (0.8314696550369263, -0.5555701851844788, 0.0),
(-0.8314696550369263, 0.5555701851844788, 0.0), (-0.8314696550369263, -0.5555701851844788, 0.0),
(0.9238795042037964, 0.3826834261417389, 0.0), (0.9238795042037964, -0.3826834261417389, 0.0),
(-0.9238795042037964, 0.3826834261417389, 0.0), (-0.9238795042037964, -0.3826834261417389, 0.0),
(0.9807852506637573, 0.19509035348892212, 0.0), (0.9807852506637573, -0.19509035348892212, 0.0),
(-0.9807852506637573, 0.19509035348892212, 0.0), (-0.9807852506637573, -0.19509035348892212, 0.0),
(0.19509197771549225, 0.9807849526405334, 0.0), (0.19509197771549225, -0.9807849526405334, 0.0),
(-0.19509197771549225, 0.9807849526405334, 0.0), (-0.19509197771549225, -0.9807849526405334, 0.0),
(0.3826850652694702, 0.9238788485527039, 0.0), (0.3826850652694702, -0.9238788485527039, 0.0),
(-0.3826850652694702, 0.9238788485527039, 0.0), (-0.3826850652694702, -0.9238788485527039, 0.0),
(0.5555717945098877, 0.8314685821533203, 0.0), (0.5555717945098877, -0.8314685821533203, 0.0),
(-0.5555717945098877, 0.8314685821533203, 0.0), (-0.5555717945098877, -0.8314685821533203, 0.0),
(0.19509197771549225, 1.2807848453521729, 0.0), (0.19509197771549225, -1.2807848453521729, 0.0),
(-0.19509197771549225, 1.2807848453521729, 0.0), (-0.19509197771549225, -1.2807848453521729, 0.0),
(1.280785322189331, 0.19509035348892212, 0.0), (1.280785322189331, -0.19509035348892212, 0.0),
(-1.280785322189331, 0.19509035348892212, 0.0), (-1.280785322189331, -0.19509035348892212, 0.0),
(0.3950919806957245, 1.2807848453521729, 0.0), (0.3950919806957245, -1.2807848453521729, 0.0),
(-0.3950919806957245, 1.2807848453521729, 0.0), (-0.3950919806957245, -1.2807848453521729, 0.0),
(1.280785322189331, 0.39509034156799316, 0.0), (1.280785322189331, -0.39509034156799316, 0.0),
(-1.280785322189331, 0.39509034156799316, 0.0), (-1.280785322189331, -0.39509034156799316, 0.0),
(0.0, 1.5807849168777466, 0.0), (0.0, -1.5807849168777466, 0.0), (1.5807852745056152, 0.0, 0.0),
(-1.5807852745056152, 0.0, 0.0)]
edges = [(0, 4), (1, 5), (2, 6), (3, 7), (4, 8), (5, 9), (6, 10), (7, 11), (8, 12),
(9, 13), (10, 14), (11, 15), (16, 20), (17, 21), (18, 22), (19, 23), (20, 24),
(21, 25), (22, 26), (23, 27), (0, 24), (1, 25), (2, 26), (3, 27), (16, 28),
(17, 29), (18, 30), (19, 31), (12, 32), (13, 33), (14, 34), (15, 35), (28, 36),
(29, 37), (30, 38), (31, 39), (32, 40), (33, 41), (34, 42), (35, 43), (36, 44),
(37, 45), (38, 44), (39, 45), (40, 46), (41, 46), (42, 47), (43, 47)]
mesh = obj.data
mesh.from_pydata(verts, edges, [])
mesh.update()
@ -30,65 +76,66 @@ def create_root_widget(rig, bone_name, bone_transform_name=None):
def create_neck_bend_widget(rig, bone_name, radius=1.0, head_tail=0.0, bone_transform_name=None):
obj = create_widget(rig, bone_name, bone_transform_name)
size = 2.0
if obj != None:
if obj is not None:
v = [(-0.08855080604553223 * size, 0.7388765811920166 * size, -0.3940150737762451 * size),
(0.08855044841766357 * size, 0.7388765811920166 * size, -0.3940150737762451 * size),
(0.17710095643997192 * size, 0.5611097812652588 * size, -0.6478927135467529 * size),
(-4.0892032870942785e-07 * size, 0.4087378978729248 * size, -0.865501880645752 * size),
(-0.17710143327713013 * size, 0.5611097812652588 * size, -0.6478922367095947 * size),
(0.08855026960372925 * size, 0.5611097812652588 * size, -0.6478924751281738 * size),
(-0.08855092525482178 * size, 0.5611097812652588 * size, -0.6478927135467529 * size),
(-0.6478927135467529 * size, 0.5611097812652588 * size, 0.08855098485946655 * size),
(-0.6478927135467529 * size, 0.5611097812652588 * size, -0.08855020999908447 * size),
(-0.6478924751281738 * size, 0.5611097812652588 * size, 0.17710155248641968 * size),
(-0.865501880645752 * size, 0.4087378978729248 * size, 4.6876743908796925e-07 * size),
(-0.647892951965332 * size, 0.5611097812652588 * size, -0.17710083723068237 * size),
(-0.39401543140411377 * size, 0.7388765811920166 * size, -0.08855029940605164 * size),
(-0.39401543140411377 * size, 0.7388765811920166 * size, 0.08855095505714417 * size),
(0.6478927135467529 * size, 0.5611097812652588 * size, -0.08855059742927551 * size),
(0.6478927135467529 * size, 0.5611097812652588 * size, 0.08855065703392029 * size),
(0.6478924751281738 * size, 0.5611097812652588 * size, -0.17710113525390625 * size),
(0.865501880645752 * size, 0.4087378978729248 * size, -3.264514703005261e-08 * size),
(0.647892951965332 * size, 0.5611097812652588 * size, 0.1771012544631958 * size),
(0.08855065703392029 * size, 0.7388765811920166 * size, 0.3940155506134033 * size),
(-0.08855056762695312 * size, 0.7388765811920166 * size, 0.3940155506134033 * size),
(-0.17710107564926147 * size, 0.5611097812652588 * size, 0.647892951965332 * size),
(2.244429140318971e-07 * size, 0.4087378978729248 * size, 0.865502119064331 * size),
(0.17710131406784058 * size, 0.5611097812652588 * size, 0.6478927135467529 * size),
(-0.08855044841766357 * size, 0.5611097812652588 * size, 0.647892951965332 * size),
(0.08855074644088745 * size, 0.5611097812652588 * size, 0.647892951965332 * size),
(0.3940153121948242 * size, 0.7388765811920166 * size, 0.08855071663856506 * size),
(0.39401519298553467 * size, 0.7388765811920166 * size, -0.08855047821998596 * size),
(-8.416645869147032e-08 * size, 0.8255770206451416 * size, -0.2656517028808594 * size),
(-0.06875583529472351 * size, 0.8255770206451416 * size, -0.2565997838973999 * size),
(-0.13282597064971924 * size, 0.8255770206451416 * size, -0.2300611138343811 * size),
(-0.18784427642822266 * size, 0.8255770206451416 * size, -0.18784409761428833 * size),
(-0.2300613522529602 * size, 0.8255770206451416 * size, -0.1328257918357849 * size),
(-0.256600022315979 * size, 0.8255770206451416 * size, -0.06875564157962799 * size),
(-0.2656519412994385 * size, 0.8255770206451416 * size, 9.328307726264029e-08 * size),
(-0.25660014152526855 * size, 0.8255770206451416 * size, 0.06875583529472351 * size),
(-0.2300613522529602 * size, 0.8255770206451416 * size, 0.13282597064971924 * size),
(-0.18784433603286743 * size, 0.8255770206451416 * size, 0.18784421682357788 * size),
(-0.1328260898590088 * size, 0.8255770206451416 * size, 0.23006129264831543 * size),
(-0.06875592470169067 * size, 0.8255770206451416 * size, 0.256600022315979 * size),
(-1.8761508613351907e-07 * size, 0.8255770206451416 * size, 0.2656519412994385 * size),
(0.06875556707382202 * size, 0.8255770206451416 * size, 0.2566000819206238 * size),
(0.13282573223114014 * size, 0.8255770206451416 * size, 0.23006141185760498 * size),
(0.18784403800964355 * size, 0.8255770206451416 * size, 0.1878443956375122 * size),
(0.23006105422973633 * size, 0.8255770206451416 * size, 0.1328260898590088 * size),
(0.25659990310668945 * size, 0.8255770206451416 * size, 0.06875596940517426 * size),
(0.2656517028808594 * size, 0.8255770206451416 * size, 2.3684407324253698e-07 * size),
(0.25659990310668945 * size, 0.8255770206451416 * size, -0.06875550746917725 * size),
(0.23006117343902588 * size, 0.8255770206451416 * size, -0.13282567262649536 * size),
(0.18784427642822266 * size, 0.8255770206451416 * size, -0.18784397840499878 * size),
(0.13282597064971924 * size, 0.8255770206451416 * size, -0.23006099462509155 * size),
(0.0687558501958847 * size, 0.8255770206451416 * size, -0.2565997838973999 * size), ]
edges = [(1, 0), (3, 2), (5, 2), (4, 3), (6, 4), (1, 5), (0, 6), (13, 7), (12, 8), (7, 9), (9, 10), (8, 11),
(27, 14), (26, 15), (14, 16), (16, 17), (15, 18), (17, 18), (10, 11), (12, 13), (20, 19), (22, 21),
(24, 21), (23, 22), (29, 28), (30, 29), (31, 30), (32, 31), (33, 32), (34, 33), (35, 34), (36, 35),
(37, 36), (38, 37), (39, 38), (40, 39), (41, 40), (42, 41), (43, 42), (44, 43), (45, 44), (46, 45),
(47, 46), (48, 47), (49, 48), (50, 49), (51, 50), (28, 51), (26, 27), (25, 23), (20, 24),
(19, 25), ]
(0.08855044841766357 * size, 0.7388765811920166 * size, -0.3940150737762451 * size),
(0.17710095643997192 * size, 0.5611097812652588 * size, -0.6478927135467529 * size),
(-4.0892032870942785e-07 * size, 0.4087378978729248 * size, -0.865501880645752 * size),
(-0.17710143327713013 * size, 0.5611097812652588 * size, -0.6478922367095947 * size),
(0.08855026960372925 * size, 0.5611097812652588 * size, -0.6478924751281738 * size),
(-0.08855092525482178 * size, 0.5611097812652588 * size, -0.6478927135467529 * size),
(-0.6478927135467529 * size, 0.5611097812652588 * size, 0.08855098485946655 * size),
(-0.6478927135467529 * size, 0.5611097812652588 * size, -0.08855020999908447 * size),
(-0.6478924751281738 * size, 0.5611097812652588 * size, 0.17710155248641968 * size),
(-0.865501880645752 * size, 0.4087378978729248 * size, 4.6876743908796925e-07 * size),
(-0.647892951965332 * size, 0.5611097812652588 * size, -0.17710083723068237 * size),
(-0.39401543140411377 * size, 0.7388765811920166 * size, -0.08855029940605164 * size),
(-0.39401543140411377 * size, 0.7388765811920166 * size, 0.08855095505714417 * size),
(0.6478927135467529 * size, 0.5611097812652588 * size, -0.08855059742927551 * size),
(0.6478927135467529 * size, 0.5611097812652588 * size, 0.08855065703392029 * size),
(0.6478924751281738 * size, 0.5611097812652588 * size, -0.17710113525390625 * size),
(0.865501880645752 * size, 0.4087378978729248 * size, -3.264514703005261e-08 * size),
(0.647892951965332 * size, 0.5611097812652588 * size, 0.1771012544631958 * size),
(0.08855065703392029 * size, 0.7388765811920166 * size, 0.3940155506134033 * size),
(-0.08855056762695312 * size, 0.7388765811920166 * size, 0.3940155506134033 * size),
(-0.17710107564926147 * size, 0.5611097812652588 * size, 0.647892951965332 * size),
(2.244429140318971e-07 * size, 0.4087378978729248 * size, 0.865502119064331 * size),
(0.17710131406784058 * size, 0.5611097812652588 * size, 0.6478927135467529 * size),
(-0.08855044841766357 * size, 0.5611097812652588 * size, 0.647892951965332 * size),
(0.08855074644088745 * size, 0.5611097812652588 * size, 0.647892951965332 * size),
(0.3940153121948242 * size, 0.7388765811920166 * size, 0.08855071663856506 * size),
(0.39401519298553467 * size, 0.7388765811920166 * size, -0.08855047821998596 * size),
(-8.416645869147032e-08 * size, 0.8255770206451416 * size, -0.2656517028808594 * size),
(-0.06875583529472351 * size, 0.8255770206451416 * size, -0.2565997838973999 * size),
(-0.13282597064971924 * size, 0.8255770206451416 * size, -0.2300611138343811 * size),
(-0.18784427642822266 * size, 0.8255770206451416 * size, -0.18784409761428833 * size),
(-0.2300613522529602 * size, 0.8255770206451416 * size, -0.1328257918357849 * size),
(-0.256600022315979 * size, 0.8255770206451416 * size, -0.06875564157962799 * size),
(-0.2656519412994385 * size, 0.8255770206451416 * size, 9.328307726264029e-08 * size),
(-0.25660014152526855 * size, 0.8255770206451416 * size, 0.06875583529472351 * size),
(-0.2300613522529602 * size, 0.8255770206451416 * size, 0.13282597064971924 * size),
(-0.18784433603286743 * size, 0.8255770206451416 * size, 0.18784421682357788 * size),
(-0.1328260898590088 * size, 0.8255770206451416 * size, 0.23006129264831543 * size),
(-0.06875592470169067 * size, 0.8255770206451416 * size, 0.256600022315979 * size),
(-1.8761508613351907e-07 * size, 0.8255770206451416 * size, 0.2656519412994385 * size),
(0.06875556707382202 * size, 0.8255770206451416 * size, 0.2566000819206238 * size),
(0.13282573223114014 * size, 0.8255770206451416 * size, 0.23006141185760498 * size),
(0.18784403800964355 * size, 0.8255770206451416 * size, 0.1878443956375122 * size),
(0.23006105422973633 * size, 0.8255770206451416 * size, 0.1328260898590088 * size),
(0.25659990310668945 * size, 0.8255770206451416 * size, 0.06875596940517426 * size),
(0.2656517028808594 * size, 0.8255770206451416 * size, 2.3684407324253698e-07 * size),
(0.25659990310668945 * size, 0.8255770206451416 * size, -0.06875550746917725 * size),
(0.23006117343902588 * size, 0.8255770206451416 * size, -0.13282567262649536 * size),
(0.18784427642822266 * size, 0.8255770206451416 * size, -0.18784397840499878 * size),
(0.13282597064971924 * size, 0.8255770206451416 * size, -0.23006099462509155 * size),
(0.0687558501958847 * size, 0.8255770206451416 * size, -0.2565997838973999 * size), ]
edges = [(1, 0), (3, 2), (5, 2), (4, 3), (6, 4), (1, 5), (0, 6), (13, 7), (12, 8), (7, 9),
(9, 10), (8, 11), (27, 14), (26, 15), (14, 16), (16, 17), (15, 18), (17, 18),
(10, 11), (12, 13), (20, 19), (22, 21), (24, 21), (23, 22), (29, 28), (30, 29),
(31, 30), (32, 31), (33, 32), (34, 33), (35, 34), (36, 35), (37, 36), (38, 37),
(39, 38), (40, 39), (41, 40), (42, 41), (43, 42), (44, 43), (45, 44), (46, 45),
(47, 46), (48, 47), (49, 48), (50, 49), (51, 50), (28, 51), (26, 27), (25, 23),
(20, 24), (19, 25), ]
verts = [(a[0] * radius, head_tail, a[2] * radius) for a in v]
mesh = obj.data
@ -99,7 +146,7 @@ def create_neck_bend_widget(rig, bone_name, radius=1.0, head_tail=0.0, bone_tran
def create_neck_tweak_widget(rig, bone_name, size=1.0, bone_transform_name=None):
obj = create_widget(rig, bone_name, bone_transform_name)
if obj != None:
if obj is not None:
verts = [(0.3535533845424652 * size, 0.3535533845424652 * size, 0.0 * size),
(0.4619397521018982 * size, 0.19134171307086945 * size, 0.0 * size),
(0.5 * size, -2.1855694143368964e-08 * size, 0.0 * size),