diff options
author | Alexander Gavrilov <angavrilov@gmail.com> | 2022-11-06 18:14:22 +0300 |
---|---|---|
committer | Alexander Gavrilov <angavrilov@gmail.com> | 2022-11-06 18:14:22 +0300 |
commit | c1dac65a8422c8f2e62391e578485c18ea58c0e1 (patch) | |
tree | 7a4609b7d80194f0ffc769399951bb98b3671280 | |
parent | 85c414a2023c1fdf16b6f3c9dc462fe242a625b9 (diff) |
Rigify: annotate and cleanup PyCharm warnings in utils and generation.
-rw-r--r-- | rigify/base_generate.py | 150 | ||||
-rw-r--r-- | rigify/base_rig.py | 51 | ||||
-rw-r--r-- | rigify/generate.py | 230 | ||||
-rw-r--r-- | rigify/rig_ui_template.py | 142 | ||||
-rw-r--r-- | rigify/ui.py | 4 | ||||
-rw-r--r-- | rigify/utils/__init__.py | 2 | ||||
-rw-r--r-- | rigify/utils/animation.py | 93 | ||||
-rw-r--r-- | rigify/utils/bones.py | 165 | ||||
-rw-r--r-- | rigify/utils/collections.py | 18 | ||||
-rw-r--r-- | rigify/utils/components.py | 39 | ||||
-rw-r--r-- | rigify/utils/errors.py | 8 | ||||
-rw-r--r-- | rigify/utils/layers.py | 68 | ||||
-rw-r--r-- | rigify/utils/mechanism.py | 152 | ||||
-rw-r--r-- | rigify/utils/metaclass.py | 71 | ||||
-rw-r--r-- | rigify/utils/misc.py | 129 | ||||
-rw-r--r-- | rigify/utils/naming.py | 116 | ||||
-rw-r--r-- | rigify/utils/node_merger.py | 76 | ||||
-rw-r--r-- | rigify/utils/rig.py | 185 | ||||
-rw-r--r-- | rigify/utils/switch_parent.py | 222 | ||||
-rw-r--r-- | rigify/utils/widgets.py | 162 | ||||
-rw-r--r-- | rigify/utils/widgets_basic.py | 212 | ||||
-rw-r--r-- | rigify/utils/widgets_special.py | 177 |
22 files changed, 1552 insertions, 920 deletions
diff --git a/rigify/base_generate.py b/rigify/base_generate.py index 0765cc17..4894d931 100644 --- a/rigify/base_generate.py +++ b/rigify/base_generate.py @@ -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.""" diff --git a/rigify/base_rig.py b/rigify/base_rig.py index 41430996..b0bcc027 100644 --- a/rigify/base_rig.py +++ b/rigify/base_rig.py @@ -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 diff --git a/rigify/generate.py b/rigify/generate.py index 8a2aa942..1515680b 100644 --- a/rigify/generate.py +++ b/rigify/generate.py @@ -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} + + child_parent_bones = {} # {object: bone} + for child in obj.children: - childs[child] = child.parent_bone + 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:] diff --git a/rigify/rig_ui_template.py b/rigify/rig_ui_template.py index d581805f..0f1e1b83 100644 --- a/rigify/rig_ui_template.py +++ b/rigify/rig_ui_template.py @@ -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") diff --git a/rigify/ui.py b/rigify/ui.py index 8b719a3f..141a8597 100644 --- a/rigify/ui.py +++ b/rigify/ui.py @@ -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""" diff --git a/rigify/utils/__init__.py b/rigify/utils/__init__.py index 8a2dee1d..c11573ba 100644 --- a/rigify/utils/__init__.py +++ b/rigify/utils/__init__.py @@ -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 diff --git a/rigify/utils/animation.py b/rigify/utils/animation.py index b8f3725a..21aa2a89 100644 --- a/rigify/utils/animation.py +++ b/rigify/utils/animation.py @@ -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 diff --git a/rigify/utils/bones.py b/rigify/utils/bones.py index 82b9451f..8419eb90 100644 --- a/rigify/utils/bones.py +++ b/rigify/utils/bones.py @@ -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 -#============================================= +############################################## +# 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] diff --git a/rigify/utils/collections.py b/rigify/utils/collections.py index 9eeaac51..4275fc79 100644 --- a/rigify/utils/collections.py +++ b/rigify/utils/collections.py @@ -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. """ diff --git a/rigify/utils/components.py b/rigify/utils/components.py index c1d01c90..54c191ba 100644 --- a/rigify/utils/components.py +++ b/rigify/utils/components.py @@ -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) diff --git a/rigify/utils/errors.py b/rigify/utils/errors.py index 5ee1b9f7..5e5c9533 100644 --- a/rigify/utils/errors.py +++ b/rigify/utils/errors.py @@ -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) diff --git a/rigify/utils/layers.py b/rigify/utils/layers.py index d5c229af..d1f56e90 100644 --- a/rigify/utils/layers.py +++ b/rigify/utils/layers.py @@ -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( diff --git a/rigify/utils/mechanism.py b/rigify/utils/mechanism.py index ac5f8790..53c9cedb 100644 --- a/rigify/utils/mechanism.py +++ b/rigify/utils/mechanism.py @@ -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 -#============================================= +############################################## +# 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): +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) diff --git a/rigify/utils/metaclass.py b/rigify/utils/metaclass.py index 1b10f385..41905101 100644 --- a/rigify/utils/metaclass.py +++ b/rigify/utils/metaclass.py @@ -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.""" diff --git a/rigify/utils/misc.py b/rigify/utils/misc.py index fbf1ac02..34f0bf38 100644 --- a/rigify/utils/misc.py +++ b/rigify/utils/misc.py @@ -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 + + 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 + - object.select_set(True) - view_layer.objects.active = object +def verify_mesh_obj(obj: bpy.types.Object) -> MeshObject: + assert obj and obj.type == 'MESH' + # noinspection PyTypeChecker + return obj diff --git a/rigify/utils/naming.py b/rigify/utils/naming.py index b3a160bb..98417abb 100644 --- a/rigify/utils/naming.py +++ b/rigify/utils/naming.py @@ -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] diff --git a/rigify/utils/node_merger.py b/rigify/utils/node_merger.py index 2da48ada..00237694 100644 --- a/rigify/utils/node_merger.py +++ b/rigify/utils/node_merger.py @@ -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) diff --git a/rigify/utils/rig.py b/rigify/utils/rig.py index 6340e4d1..d8f88430 100644 --- a/rigify/utils/rig.py +++ b/rigify/utils/rig.py @@ -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") diff --git a/rigify/utils/switch_parent.py b/rigify/utils/switch_parent.py index 8d5fcc4e..d49e6a26 100644 --- a/rigify/utils/switch_parent.py +++ b/rigify/utils/switch_parent.py @@ -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. - - 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). + 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. + + 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 ## diff --git a/rigify/utils/widgets.py b/rigify/utils/widgets.py index 5a16065b..46300197 100644 --- a/rigify/utils/widgets.py +++ b/rigify/utils/widgets.py @@ -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 -#============================================= +############################################## +# 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" diff --git a/rigify/utils/widgets_basic.py b/rigify/utils/widgets_basic.py index 55e8719e..2ab2204c 100644 --- a/rigify/utils/widgets_basic.py +++ b/rigify/utils/widgets_basic.py @@ -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), diff --git a/rigify/utils/widgets_special.py b/rigify/utils/widgets_special.py index 7b79d246..42cd0439 100644 --- a/rigify/utils/widgets_special.py +++ b/rigify/utils/widgets_special.py @@ -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), |