Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Gavrilov <angavrilov@gmail.com>2022-11-06 18:14:22 +0300
committerAlexander Gavrilov <angavrilov@gmail.com>2022-11-06 18:14:22 +0300
commitc1dac65a8422c8f2e62391e578485c18ea58c0e1 (patch)
tree7a4609b7d80194f0ffc769399951bb98b3671280
parent85c414a2023c1fdf16b6f3c9dc462fe242a625b9 (diff)
Rigify: annotate and cleanup PyCharm warnings in utils and generation.
-rw-r--r--rigify/base_generate.py150
-rw-r--r--rigify/base_rig.py51
-rw-r--r--rigify/generate.py230
-rw-r--r--rigify/rig_ui_template.py142
-rw-r--r--rigify/ui.py4
-rw-r--r--rigify/utils/__init__.py2
-rw-r--r--rigify/utils/animation.py93
-rw-r--r--rigify/utils/bones.py165
-rw-r--r--rigify/utils/collections.py18
-rw-r--r--rigify/utils/components.py39
-rw-r--r--rigify/utils/errors.py8
-rw-r--r--rigify/utils/layers.py68
-rw-r--r--rigify/utils/mechanism.py152
-rw-r--r--rigify/utils/metaclass.py71
-rw-r--r--rigify/utils/misc.py129
-rw-r--r--rigify/utils/naming.py116
-rw-r--r--rigify/utils/node_merger.py76
-rw-r--r--rigify/utils/rig.py185
-rw-r--r--rigify/utils/switch_parent.py222
-rw-r--r--rigify/utils/widgets.py162
-rw-r--r--rigify/utils/widgets_basic.py212
-rw-r--r--rigify/utils/widgets_special.py177
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),