Rigify: annotate and cleanup PyCharm warnings in utils and generation.
This commit is contained in:
parent
85c414a202
commit
c1dac65a84
|
@ -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."""
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -7,43 +7,48 @@ import time
|
|||
from .utils.errors import MetarigError
|
||||
from .utils.bones import new_bone
|
||||
from .utils.layers import ORG_LAYER, MCH_LAYER, DEF_LAYER, ROOT_LAYER
|
||||
from .utils.naming import ORG_PREFIX, MCH_PREFIX, DEF_PREFIX, ROOT_NAME, make_original_name, change_name_side, get_name_side, Side
|
||||
from .utils.naming import (ORG_PREFIX, MCH_PREFIX, DEF_PREFIX, ROOT_NAME, make_original_name,
|
||||
change_name_side, get_name_side, Side)
|
||||
from .utils.widgets import WGT_PREFIX
|
||||
from .utils.widgets_special import create_root_widget
|
||||
from .utils.mechanism import refresh_all_drivers
|
||||
from .utils.misc import gamma_correct, select_object
|
||||
from .utils.collections import ensure_collection, list_layer_collections, filter_layer_collections_by_object
|
||||
from .utils.rig import get_rigify_type
|
||||
from .utils.misc import gamma_correct, select_object, ArmatureObject, verify_armature_obj
|
||||
from .utils.collections import (ensure_collection, list_layer_collections,
|
||||
filter_layer_collections_by_object)
|
||||
from .utils.rig import get_rigify_type, get_rigify_layers
|
||||
|
||||
from . import base_generate
|
||||
from . import rig_ui_template
|
||||
from . import rig_lists
|
||||
|
||||
|
||||
RIG_MODULE = "rigs"
|
||||
|
||||
|
||||
class Timer:
|
||||
def __init__(self):
|
||||
self.timez = time.time()
|
||||
self.time_val = time.time()
|
||||
|
||||
def tick(self, string):
|
||||
t = time.time()
|
||||
print(string + "%.3f" % (t - self.timez))
|
||||
self.timez = t
|
||||
print(string + "%.3f" % (t - self.time_val))
|
||||
self.time_val = t
|
||||
|
||||
|
||||
class Generator(base_generate.BaseGenerator):
|
||||
usable_collections: list[bpy.types.LayerCollection]
|
||||
action_layers: ActionLayerBuilder
|
||||
|
||||
def __init__(self, context, metarig):
|
||||
super().__init__(context, metarig)
|
||||
|
||||
self.id_store = context.window_manager
|
||||
|
||||
|
||||
def find_rig_class(self, rig_type):
|
||||
rig_module = rig_lists.rigs[rig_type]["module"]
|
||||
|
||||
return rig_module.Rig
|
||||
|
||||
|
||||
def __switch_to_usable_collection(self, obj, fallback=False):
|
||||
collections = filter_layer_collections_by_object(self.usable_collections, obj)
|
||||
|
||||
|
@ -54,8 +59,7 @@ class Generator(base_generate.BaseGenerator):
|
|||
|
||||
self.collection = self.layer_collection.collection
|
||||
|
||||
|
||||
def ensure_rig_object(self) -> bpy.types.Object:
|
||||
def ensure_rig_object(self) -> ArmatureObject:
|
||||
"""Check if the generated rig already exists, so we can
|
||||
regenerate in the same object. If not, create a new
|
||||
object to generate the rig in.
|
||||
|
@ -63,10 +67,14 @@ class Generator(base_generate.BaseGenerator):
|
|||
print("Fetch rig.")
|
||||
meta_data = self.metarig.data
|
||||
|
||||
target_rig = meta_data.rigify_target_rig
|
||||
target_rig: ArmatureObject = meta_data.rigify_target_rig
|
||||
|
||||
if not target_rig:
|
||||
if meta_data.rigify_rig_basename:
|
||||
rig_new_name = meta_data.rigify_rig_basename
|
||||
# noinspection PyUnresolvedReferences
|
||||
rig_basename = meta_data.rigify_rig_basename
|
||||
|
||||
if rig_basename:
|
||||
rig_new_name = rig_basename
|
||||
elif "metarig" in self.metarig.name:
|
||||
rig_new_name = self.metarig.name.replace("metarig", "rig")
|
||||
elif "META" in self.metarig.name:
|
||||
|
@ -74,7 +82,8 @@ class Generator(base_generate.BaseGenerator):
|
|||
else:
|
||||
rig_new_name = "RIG-" + self.metarig.name
|
||||
|
||||
target_rig = bpy.data.objects.new(rig_new_name, bpy.data.armatures.new(rig_new_name))
|
||||
arm = bpy.data.armatures.new(rig_new_name)
|
||||
target_rig = verify_armature_obj(bpy.data.objects.new(rig_new_name, arm))
|
||||
target_rig.display_type = 'WIRE'
|
||||
|
||||
# If the object is already added to the scene, switch to its collection
|
||||
|
@ -83,7 +92,7 @@ class Generator(base_generate.BaseGenerator):
|
|||
else:
|
||||
# Otherwise, add to the selected collection or the metarig collection if unusable
|
||||
if (self.layer_collection not in self.usable_collections
|
||||
or self.layer_collection == self.view_layer.layer_collection):
|
||||
or self.layer_collection == self.view_layer.layer_collection):
|
||||
self.__switch_to_usable_collection(self.metarig, True)
|
||||
|
||||
self.collection.objects.link(target_rig)
|
||||
|
@ -94,8 +103,7 @@ class Generator(base_generate.BaseGenerator):
|
|||
|
||||
return target_rig
|
||||
|
||||
|
||||
def __unhide_rig_object(self, obj):
|
||||
def __unhide_rig_object(self, obj: bpy.types.Object):
|
||||
# Ensure the object is visible and selectable
|
||||
obj.hide_set(False, view_layer=self.view_layer)
|
||||
obj.hide_viewport = False
|
||||
|
@ -111,13 +119,13 @@ class Generator(base_generate.BaseGenerator):
|
|||
if self.layer_collection not in self.usable_collections:
|
||||
raise Exception('Could not generate: Could not find a usable collection.')
|
||||
|
||||
|
||||
def __find_legacy_collection(self) -> bpy.types.Collection:
|
||||
"""For backwards comp, matching by name to find a legacy collection.
|
||||
(For before there was a Widget Collection PointerProperty)
|
||||
"""
|
||||
wgts_group_name = "WGTS_" + self.obj.name
|
||||
old_collection = bpy.data.collections.get(wgts_group_name)
|
||||
# noinspection SpellCheckingInspection
|
||||
widgets_group_name = "WGTS_" + self.obj.name
|
||||
old_collection = bpy.data.collections.get(widgets_group_name)
|
||||
|
||||
if old_collection and old_collection.library:
|
||||
old_collection = None
|
||||
|
@ -126,13 +134,14 @@ class Generator(base_generate.BaseGenerator):
|
|||
# Update the old 'Widgets' collection
|
||||
legacy_collection = bpy.data.collections.get('Widgets')
|
||||
|
||||
if legacy_collection and wgts_group_name in legacy_collection.objects and not legacy_collection.library:
|
||||
legacy_collection.name = wgts_group_name
|
||||
if legacy_collection and widgets_group_name in legacy_collection.objects\
|
||||
and not legacy_collection.library:
|
||||
legacy_collection.name = widgets_group_name
|
||||
old_collection = legacy_collection
|
||||
|
||||
if old_collection:
|
||||
# Rename the collection
|
||||
old_collection.name = wgts_group_name
|
||||
old_collection.name = widgets_group_name
|
||||
|
||||
return old_collection
|
||||
|
||||
|
@ -142,11 +151,14 @@ class Generator(base_generate.BaseGenerator):
|
|||
if not self.widget_collection:
|
||||
self.widget_collection = self.__find_legacy_collection()
|
||||
if not self.widget_collection:
|
||||
wgts_group_name = "WGTS_" + self.obj.name.replace("RIG-", "")
|
||||
self.widget_collection = ensure_collection(self.context, wgts_group_name, hidden=True)
|
||||
# noinspection SpellCheckingInspection
|
||||
widgets_group_name = "WGTS_" + self.obj.name.replace("RIG-", "")
|
||||
self.widget_collection = ensure_collection(
|
||||
self.context, widgets_group_name, hidden=True)
|
||||
|
||||
self.metarig.data.rigify_widgets_collection = self.widget_collection
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
self.use_mirror_widgets = self.metarig.data.rigify_mirror_widgets
|
||||
|
||||
# Build tables for existing widgets
|
||||
|
@ -154,6 +166,7 @@ class Generator(base_generate.BaseGenerator):
|
|||
self.new_widget_table = {}
|
||||
self.widget_mirror_mesh = {}
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
if self.metarig.data.rigify_force_widget_update:
|
||||
# Remove widgets if force update is set
|
||||
for obj in list(self.widget_collection.objects):
|
||||
|
@ -176,16 +189,17 @@ class Generator(base_generate.BaseGenerator):
|
|||
|
||||
# If the mesh name is the same as the object, rename it too
|
||||
if widget.data.name == old_data_name:
|
||||
widget.data.name = change_name_side(widget.name, get_name_side(widget.data.name))
|
||||
widget.data.name = change_name_side(
|
||||
widget.name, get_name_side(widget.data.name))
|
||||
|
||||
# Find meshes for mirroring
|
||||
if self.use_mirror_widgets:
|
||||
for bone_name, widget in self.old_widget_table.items():
|
||||
mid_name = change_name_side(bone_name, Side.MIDDLE)
|
||||
if bone_name != mid_name:
|
||||
assert isinstance(widget.data, bpy.types.Mesh)
|
||||
self.widget_mirror_mesh[mid_name] = widget.data
|
||||
|
||||
|
||||
def __duplicate_rig(self):
|
||||
obj = self.obj
|
||||
metarig = self.metarig
|
||||
|
@ -203,7 +217,7 @@ class Generator(base_generate.BaseGenerator):
|
|||
bpy.ops.object.duplicate()
|
||||
|
||||
# Rename org bones in the temporary object
|
||||
temp_obj = context.view_layer.objects.active
|
||||
temp_obj = verify_armature_obj(context.view_layer.objects.active)
|
||||
|
||||
assert temp_obj and temp_obj != metarig
|
||||
|
||||
|
@ -230,8 +244,8 @@ class Generator(base_generate.BaseGenerator):
|
|||
for track in obj.animation_data.nla_tracks:
|
||||
obj.animation_data.nla_tracks.remove(track)
|
||||
|
||||
|
||||
def __freeze_driver_vars(self, obj):
|
||||
@staticmethod
|
||||
def __freeze_driver_vars(obj: bpy.types.Object):
|
||||
if obj.animation_data:
|
||||
# Freeze drivers referring to custom properties
|
||||
for d in obj.animation_data.drivers:
|
||||
|
@ -239,13 +253,12 @@ class Generator(base_generate.BaseGenerator):
|
|||
for tar in var.targets:
|
||||
# If a custom property
|
||||
if var.type == 'SINGLE_PROP' \
|
||||
and re.match(r'^pose.bones\["[^"\]]*"\]\["[^"\]]*"\]$', tar.data_path):
|
||||
and re.match(r'^pose.bones\["[^"\]]*"]\["[^"\]]*"]$',
|
||||
tar.data_path):
|
||||
tar.data_path = "RIGIFY-" + tar.data_path
|
||||
|
||||
|
||||
def __rename_org_bones(self, obj):
|
||||
#----------------------------------
|
||||
# Make a list of the original bones so we can keep track of them.
|
||||
def __rename_org_bones(self, obj: ArmatureObject):
|
||||
# Make a list of the original bones, so we can keep track of them.
|
||||
original_bones = [bone.name for bone in obj.data.bones]
|
||||
|
||||
# Add the ORG_PREFIX to the original bones.
|
||||
|
@ -267,7 +280,6 @@ class Generator(base_generate.BaseGenerator):
|
|||
|
||||
self.original_bones = original_bones
|
||||
|
||||
|
||||
def __create_root_bone(self):
|
||||
obj = self.obj
|
||||
metarig = self.metarig
|
||||
|
@ -289,7 +301,6 @@ class Generator(base_generate.BaseGenerator):
|
|||
self.bone_owners[root_bone] = None
|
||||
self.noparent_bones.add(root_bone)
|
||||
|
||||
|
||||
def __parent_bones_to_root(self):
|
||||
eb = self.obj.data.edit_bones
|
||||
|
||||
|
@ -301,7 +312,6 @@ class Generator(base_generate.BaseGenerator):
|
|||
bone.use_connect = False
|
||||
bone.parent = eb[self.root_bone]
|
||||
|
||||
|
||||
def __lock_transforms(self):
|
||||
# Lock transforms on all non-control bones
|
||||
r = re.compile("[A-Z][A-Z][A-Z]-")
|
||||
|
@ -312,15 +322,14 @@ class Generator(base_generate.BaseGenerator):
|
|||
pb.lock_rotation_w = True
|
||||
pb.lock_scale = (True, True, True)
|
||||
|
||||
|
||||
def __assign_layers(self):
|
||||
pbones = self.obj.pose.bones
|
||||
pose_bones = self.obj.pose.bones
|
||||
|
||||
pbones[self.root_bone].bone.layers = ROOT_LAYER
|
||||
pose_bones[self.root_bone].bone.layers = ROOT_LAYER
|
||||
|
||||
# Every bone that has a name starting with "DEF-" make deforming. All the
|
||||
# others make non-deforming.
|
||||
for pbone in pbones:
|
||||
for pbone in pose_bones:
|
||||
bone = pbone.bone
|
||||
name = bone.name
|
||||
layers = None
|
||||
|
@ -345,7 +354,6 @@ class Generator(base_generate.BaseGenerator):
|
|||
|
||||
bone.bbone_x = bone.bbone_z = bone.length * 0.05
|
||||
|
||||
|
||||
def __restore_driver_vars(self):
|
||||
obj = self.obj
|
||||
|
||||
|
@ -355,16 +363,15 @@ class Generator(base_generate.BaseGenerator):
|
|||
for v in d.driver.variables:
|
||||
for tar in v.targets:
|
||||
if tar.data_path.startswith("RIGIFY-"):
|
||||
temp, bone, prop = tuple([x.strip('"]') for x in tar.data_path.split('["')])
|
||||
if bone in obj.data.bones \
|
||||
and prop in obj.pose.bones[bone].keys():
|
||||
temp, bone, prop = tuple(
|
||||
[x.strip('"]') for x in tar.data_path.split('["')])
|
||||
if bone in obj.data.bones and prop in obj.pose.bones[bone].keys():
|
||||
tar.data_path = tar.data_path[7:]
|
||||
else:
|
||||
org_name = make_original_name(bone)
|
||||
org_name = self.org_rename_table.get(org_name, org_name)
|
||||
tar.data_path = 'pose.bones["%s"]["%s"]' % (org_name, prop)
|
||||
|
||||
|
||||
def __assign_widgets(self):
|
||||
obj_table = {obj.name: obj for obj in self.scene.objects}
|
||||
|
||||
|
@ -382,10 +389,9 @@ class Generator(base_generate.BaseGenerator):
|
|||
if wgt_name in obj_table:
|
||||
bone.custom_shape = obj_table[wgt_name]
|
||||
|
||||
|
||||
def __compute_visible_layers(self):
|
||||
# Reveal all the layers with control bones on them
|
||||
vis_layers = [False for n in range(0, 32)]
|
||||
vis_layers = [False for _ in range(0, 32)]
|
||||
|
||||
for bone in self.obj.data.bones:
|
||||
for i in range(0, 32):
|
||||
|
@ -396,20 +402,18 @@ class Generator(base_generate.BaseGenerator):
|
|||
|
||||
self.obj.data.layers = vis_layers
|
||||
|
||||
|
||||
def generate(self):
|
||||
context = self.context
|
||||
metarig = self.metarig
|
||||
scene = self.scene
|
||||
id_store = self.id_store
|
||||
view_layer = self.view_layer
|
||||
t = Timer()
|
||||
|
||||
self.usable_collections = list_layer_collections(view_layer.layer_collection, selectable=True)
|
||||
self.usable_collections = list_layer_collections(
|
||||
view_layer.layer_collection, selectable=True)
|
||||
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
#------------------------------------------
|
||||
###########################################
|
||||
# Create/find the rig object and set it up
|
||||
self.obj = obj = self.ensure_rig_object()
|
||||
|
||||
|
@ -426,19 +430,21 @@ class Generator(base_generate.BaseGenerator):
|
|||
|
||||
select_object(context, obj, deselect_all=True)
|
||||
|
||||
#------------------------------------------
|
||||
###########################################
|
||||
# Create Widget Collection
|
||||
self.ensure_widget_collection()
|
||||
|
||||
t.tick("Create main WGTS: ")
|
||||
t.tick("Create widgets collection: ")
|
||||
|
||||
#------------------------------------------
|
||||
###########################################
|
||||
# Get parented objects to restore later
|
||||
childs = {} # {object: bone}
|
||||
for child in obj.children:
|
||||
childs[child] = child.parent_bone
|
||||
|
||||
#------------------------------------------
|
||||
child_parent_bones = {} # {object: bone}
|
||||
|
||||
for child in obj.children:
|
||||
child_parent_bones[child] = child.parent_bone
|
||||
|
||||
###########################################
|
||||
# Copy bones from metarig to obj (adds ORG_PREFIX)
|
||||
self.__duplicate_rig()
|
||||
|
||||
|
@ -446,34 +452,34 @@ class Generator(base_generate.BaseGenerator):
|
|||
|
||||
t.tick("Duplicate rig: ")
|
||||
|
||||
#------------------------------------------
|
||||
###########################################
|
||||
# Put the rig_name in the armature custom properties
|
||||
obj.data["rig_id"] = self.rig_id
|
||||
|
||||
self.script = rig_ui_template.ScriptGenerator(self)
|
||||
|
||||
#------------------------------------------
|
||||
###########################################
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
self.instantiate_rig_tree()
|
||||
|
||||
t.tick("Instantiate rigs: ")
|
||||
|
||||
#------------------------------------------
|
||||
###########################################
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
self.invoke_initialize()
|
||||
|
||||
t.tick("Initialize rigs: ")
|
||||
|
||||
#------------------------------------------
|
||||
###########################################
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
|
||||
self.invoke_prepare_bones()
|
||||
|
||||
t.tick("Prepare bones: ")
|
||||
|
||||
#------------------------------------------
|
||||
###########################################
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
|
||||
|
@ -483,7 +489,7 @@ class Generator(base_generate.BaseGenerator):
|
|||
|
||||
t.tick("Generate bones: ")
|
||||
|
||||
#------------------------------------------
|
||||
###########################################
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
|
||||
|
@ -493,35 +499,35 @@ class Generator(base_generate.BaseGenerator):
|
|||
|
||||
t.tick("Parent bones: ")
|
||||
|
||||
#------------------------------------------
|
||||
###########################################
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
self.invoke_configure_bones()
|
||||
|
||||
t.tick("Configure bones: ")
|
||||
|
||||
#------------------------------------------
|
||||
###########################################
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
self.invoke_preapply_bones()
|
||||
|
||||
t.tick("Preapply bones: ")
|
||||
|
||||
#------------------------------------------
|
||||
###########################################
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
|
||||
self.invoke_apply_bones()
|
||||
|
||||
t.tick("Apply bones: ")
|
||||
|
||||
#------------------------------------------
|
||||
###########################################
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
self.invoke_rig_bones()
|
||||
|
||||
t.tick("Rig bones: ")
|
||||
|
||||
#------------------------------------------
|
||||
###########################################
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
self.invoke_generate_widgets()
|
||||
|
@ -531,7 +537,7 @@ class Generator(base_generate.BaseGenerator):
|
|||
|
||||
t.tick("Generate widgets: ")
|
||||
|
||||
#------------------------------------------
|
||||
###########################################
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
self.__lock_transforms()
|
||||
|
@ -541,14 +547,14 @@ class Generator(base_generate.BaseGenerator):
|
|||
|
||||
t.tick("Assign layers: ")
|
||||
|
||||
#------------------------------------------
|
||||
###########################################
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
self.invoke_finalize()
|
||||
|
||||
t.tick("Finalize: ")
|
||||
|
||||
#------------------------------------------
|
||||
###########################################
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
self.__assign_widgets()
|
||||
|
@ -561,13 +567,14 @@ class Generator(base_generate.BaseGenerator):
|
|||
|
||||
t.tick("The rest: ")
|
||||
|
||||
#----------------------------------
|
||||
# Deconfigure
|
||||
###########################################
|
||||
# Restore state
|
||||
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
obj.data.pose_position = 'POSE'
|
||||
|
||||
# Restore parent to bones
|
||||
for child, sub_parent in childs.items():
|
||||
for child, sub_parent in child_parent_bones.items():
|
||||
if sub_parent in obj.pose.bones:
|
||||
mat = child.matrix_world.copy()
|
||||
child.parent_bone = sub_parent
|
||||
|
@ -576,15 +583,18 @@ class Generator(base_generate.BaseGenerator):
|
|||
# Clear any transient errors in drivers
|
||||
refresh_all_drivers()
|
||||
|
||||
#----------------------------------
|
||||
###########################################
|
||||
# Execute the finalize script
|
||||
|
||||
if metarig.data.rigify_finalize_script:
|
||||
# noinspection PyUnresolvedReferences
|
||||
finalize_script = metarig.data.rigify_finalize_script
|
||||
|
||||
if finalize_script:
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
exec(metarig.data.rigify_finalize_script.as_string(), {})
|
||||
exec(finalize_script.as_string(), {})
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
#----------------------------------
|
||||
###########################################
|
||||
# Restore active collection
|
||||
view_layer.active_layer_collection = self.layer_collection
|
||||
|
||||
|
@ -620,26 +630,24 @@ def generate_rig(context, metarig):
|
|||
base_generate.BaseGenerator.instance = None
|
||||
|
||||
|
||||
def create_selection_set_for_rig_layer(
|
||||
rig: bpy.types.Object,
|
||||
set_name: str,
|
||||
layer_idx: int
|
||||
) -> None:
|
||||
def create_selection_set_for_rig_layer(rig: ArmatureObject, set_name: str, layer_idx: int) -> None:
|
||||
"""Create a single selection set on a rig.
|
||||
|
||||
The set will contain all bones on the rig layer with the given index.
|
||||
"""
|
||||
selset = rig.selection_sets.add()
|
||||
selset.name = set_name
|
||||
# noinspection PyUnresolvedReferences
|
||||
sel_set = rig.selection_sets.add()
|
||||
sel_set.name = set_name
|
||||
|
||||
for b in rig.pose.bones:
|
||||
if not b.bone.layers[layer_idx] or b.name in selset.bone_ids:
|
||||
if not b.bone.layers[layer_idx] or b.name in sel_set.bone_ids:
|
||||
continue
|
||||
|
||||
bone_id = selset.bone_ids.add()
|
||||
bone_id = sel_set.bone_ids.add()
|
||||
bone_id.name = b.name
|
||||
|
||||
def create_selection_sets(obj, metarig):
|
||||
|
||||
def create_selection_sets(obj: ArmatureObject, metarig: ArmatureObject):
|
||||
"""Create selection sets if the Selection Sets addon is enabled.
|
||||
|
||||
Whether a selection set for a rig layer is created is controlled in the
|
||||
|
@ -650,17 +658,20 @@ def create_selection_sets(obj, metarig):
|
|||
and 'bone_selection_sets' not in bpy.context.preferences.addons:
|
||||
return
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
obj.selection_sets.clear()
|
||||
|
||||
for i, name in enumerate(metarig.data.rigify_layers.keys()):
|
||||
if name == '' or not metarig.data.rigify_layers[i].selset:
|
||||
rigify_layers = get_rigify_layers(metarig.data)
|
||||
|
||||
for i, layer in enumerate(rigify_layers):
|
||||
if layer.name == '' or not layer.selset:
|
||||
continue
|
||||
|
||||
create_selection_set_for_rig_layer(obj, name, i)
|
||||
create_selection_set_for_rig_layer(obj, layer.name, i)
|
||||
|
||||
|
||||
# noinspection PyDefaultArgument
|
||||
def create_bone_groups(obj, metarig, priorities={}):
|
||||
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
pb = obj.pose.bones
|
||||
layers = metarig.data.rigify_layers
|
||||
|
@ -668,10 +679,10 @@ def create_bone_groups(obj, metarig, priorities={}):
|
|||
dummy = {}
|
||||
|
||||
# Create BGs
|
||||
for l in layers:
|
||||
if l.group == 0:
|
||||
for layer in layers:
|
||||
if layer.group == 0:
|
||||
continue
|
||||
g_id = l.group - 1
|
||||
g_id = layer.group - 1
|
||||
name = groups[g_id].name
|
||||
if name not in obj.pose.bone_groups.keys():
|
||||
bg = obj.pose.bone_groups.new(name=name)
|
||||
|
@ -682,9 +693,9 @@ def create_bone_groups(obj, metarig, priorities={}):
|
|||
|
||||
for b in pb:
|
||||
try:
|
||||
prios = priorities.get(b.name, dummy)
|
||||
enabled = [ i for i, v in enumerate(b.bone.layers) if v ]
|
||||
layer_index = max(enabled, key=lambda i: prios.get(i, 0))
|
||||
bone_priorities = priorities.get(b.name, dummy)
|
||||
enabled = [i for i, v in enumerate(b.bone.layers) if v]
|
||||
layer_index = max(enabled, key=lambda i: bone_priorities.get(i, 0))
|
||||
except ValueError:
|
||||
continue
|
||||
if layer_index > len(layers) - 1: # bone is on reserved layers
|
||||
|
@ -703,18 +714,3 @@ def get_xy_spread(bones):
|
|||
y_max = max((y_max, abs(b.head[1]), abs(b.tail[1])))
|
||||
|
||||
return max((x_max, y_max))
|
||||
|
||||
|
||||
def param_matches_type(param_name, rig_type):
|
||||
""" Returns True if the parameter name is consistent with the rig type.
|
||||
"""
|
||||
if param_name.rsplit(".", 1)[0] == rig_type:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def param_name(param_name, rig_type):
|
||||
""" Get the actual parameter name, sans-rig-type.
|
||||
"""
|
||||
return param_name[len(rig_type) + 1:]
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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"""
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -2,15 +2,18 @@
|
|||
|
||||
import bpy
|
||||
import math
|
||||
from mathutils import Vector, Matrix, Color
|
||||
|
||||
from mathutils import Vector, Matrix
|
||||
from typing import Optional, Callable
|
||||
|
||||
from .errors import MetarigError
|
||||
from .naming import get_name, make_derived_name, is_control_bone
|
||||
from .misc import pairwise
|
||||
from .misc import pairwise, ArmatureObject
|
||||
|
||||
#=======================
|
||||
|
||||
########################
|
||||
# Bone collection
|
||||
#=======================
|
||||
########################
|
||||
|
||||
class BoneDict(dict):
|
||||
"""
|
||||
|
@ -18,23 +21,22 @@ class BoneDict(dict):
|
|||
|
||||
Allows access to contained items as attributes, and only
|
||||
accepts certain types of values.
|
||||
|
||||
@DynamicAttrs
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def __sanitize_attr(key, value):
|
||||
if hasattr(BoneDict, key):
|
||||
raise KeyError("Invalid BoneDict key: %s" % (key))
|
||||
raise KeyError(f"Invalid BoneDict key: {key}")
|
||||
|
||||
if (value is None or
|
||||
isinstance(value, str) or
|
||||
isinstance(value, list) or
|
||||
isinstance(value, BoneDict)):
|
||||
if value is None or isinstance(value, (str, list, BoneDict)):
|
||||
return value
|
||||
|
||||
if isinstance(value, dict):
|
||||
return BoneDict(value)
|
||||
|
||||
raise ValueError("Invalid BoneDict value: %r" % (value))
|
||||
raise ValueError(f"Invalid BoneDict value: {repr(value)}")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__()
|
||||
|
@ -71,13 +73,14 @@ class BoneDict(dict):
|
|||
|
||||
return all_bones
|
||||
|
||||
#=======================
|
||||
|
||||
########################
|
||||
# Bone manipulation
|
||||
#=======================
|
||||
########################
|
||||
#
|
||||
# NOTE: PREFER USING BoneUtilityMixin IN NEW STYLE RIGS!
|
||||
|
||||
def get_bone(obj, bone_name):
|
||||
def get_bone(obj: ArmatureObject, bone_name: Optional[str]):
|
||||
"""Get EditBone or PoseBone by name, depending on the current mode."""
|
||||
if not bone_name:
|
||||
return None
|
||||
|
@ -87,7 +90,7 @@ def get_bone(obj, bone_name):
|
|||
return bones[bone_name]
|
||||
|
||||
|
||||
def new_bone(obj, bone_name):
|
||||
def new_bone(obj: ArmatureObject, bone_name: str):
|
||||
""" Adds a new bone to the given armature object.
|
||||
Returns the resulting bone's name.
|
||||
"""
|
||||
|
@ -102,11 +105,13 @@ def new_bone(obj, bone_name):
|
|||
raise MetarigError("Can't add new bone '%s' outside of edit mode" % bone_name)
|
||||
|
||||
|
||||
def copy_bone(obj, bone_name, assign_name='', *, parent=False, inherit_scale=False, bbone=False, length=None, scale=None):
|
||||
def copy_bone(obj: ArmatureObject, bone_name: str, assign_name='', *,
|
||||
parent=False, inherit_scale=False, bbone=False,
|
||||
length: Optional[float] = None, scale: Optional[float] = None):
|
||||
""" Makes a copy of the given bone in the given armature object.
|
||||
Returns the resulting bone's name.
|
||||
"""
|
||||
#if bone_name not in obj.data.bones:
|
||||
|
||||
if bone_name not in obj.data.edit_bones:
|
||||
raise MetarigError("copy_bone(): bone '%s' not found, cannot copy it" % bone_name)
|
||||
|
||||
|
@ -116,7 +121,6 @@ def copy_bone(obj, bone_name, assign_name='', *, parent=False, inherit_scale=Fal
|
|||
# Copy the edit bone
|
||||
edit_bone_1 = obj.data.edit_bones[bone_name]
|
||||
edit_bone_2 = obj.data.edit_bones.new(assign_name)
|
||||
bone_name_1 = bone_name
|
||||
bone_name_2 = edit_bone_2.name
|
||||
|
||||
# Copy edit bone attributes
|
||||
|
@ -137,6 +141,7 @@ def copy_bone(obj, bone_name, assign_name='', *, parent=False, inherit_scale=Fal
|
|||
edit_bone_2.inherit_scale = edit_bone_1.inherit_scale
|
||||
|
||||
if bbone:
|
||||
# noinspection SpellCheckingInspection
|
||||
for name in ['bbone_segments',
|
||||
'bbone_easein', 'bbone_easeout',
|
||||
'bbone_rollin', 'bbone_rollout',
|
||||
|
@ -155,9 +160,10 @@ def copy_bone(obj, bone_name, assign_name='', *, parent=False, inherit_scale=Fal
|
|||
raise MetarigError("Cannot copy bones outside of edit mode")
|
||||
|
||||
|
||||
def copy_bone_properties(obj, bone_name_1, bone_name_2, transforms=True, props=True, widget=True):
|
||||
def copy_bone_properties(obj: ArmatureObject, bone_name_1: str, bone_name_2: str,
|
||||
transforms=True, props=True, widget=True):
|
||||
""" Copy transform and custom properties from bone 1 to bone 2. """
|
||||
if obj.mode in {'OBJECT','POSE'}:
|
||||
if obj.mode in {'OBJECT', 'POSE'}:
|
||||
# Get the pose bones
|
||||
pose_bone_1 = obj.pose.bones[bone_name_1]
|
||||
pose_bone_2 = obj.pose.bones[bone_name_2]
|
||||
|
@ -197,7 +203,7 @@ def _legacy_copy_bone(obj, bone_name, assign_name=''):
|
|||
return new_name
|
||||
|
||||
|
||||
def flip_bone(obj, bone_name):
|
||||
def flip_bone(obj: ArmatureObject, bone_name: str):
|
||||
""" Flips an edit bone.
|
||||
"""
|
||||
if bone_name not in obj.data.edit_bones:
|
||||
|
@ -214,11 +220,11 @@ def flip_bone(obj, bone_name):
|
|||
raise MetarigError("Cannot flip bones outside of edit mode")
|
||||
|
||||
|
||||
def flip_bone_chain(obj, bone_names):
|
||||
def flip_bone_chain(obj: ArmatureObject, bone_names: list[str]):
|
||||
"""Flips a connected bone chain."""
|
||||
assert obj.mode == 'EDIT'
|
||||
|
||||
bones = [ obj.data.edit_bones[name] for name in bone_names ]
|
||||
bones = [obj.data.edit_bones[name] for name in bone_names]
|
||||
|
||||
# Verify chain and unparent
|
||||
for prev_bone, bone in pairwise(bones):
|
||||
|
@ -242,7 +248,9 @@ def flip_bone_chain(obj, bone_names):
|
|||
bone.use_connect = True
|
||||
|
||||
|
||||
def put_bone(obj, bone_name, pos, *, matrix=None, length=None, scale=None):
|
||||
def put_bone(obj: ArmatureObject, bone_name: str, pos: Optional[Vector], *,
|
||||
matrix: Optional[Matrix] = None,
|
||||
length: Optional[float] = None, scale: Optional[float] = None):
|
||||
""" Places a bone at the given position.
|
||||
"""
|
||||
if bone_name not in obj.data.edit_bones:
|
||||
|
@ -274,13 +282,14 @@ def put_bone(obj, bone_name, pos, *, matrix=None, length=None, scale=None):
|
|||
raise MetarigError("Cannot 'put' bones outside of edit mode")
|
||||
|
||||
|
||||
def disable_bbones(obj, bone_names):
|
||||
def disable_bbones(obj: ArmatureObject, bone_names: list[str]):
|
||||
"""Disables B-Bone segments on the specified bones."""
|
||||
assert(obj.mode != 'EDIT')
|
||||
for bone in bone_names:
|
||||
obj.data.bones[bone].bbone_segments = 1
|
||||
|
||||
|
||||
# noinspection SpellCheckingInspection
|
||||
def _legacy_make_nonscaling_child(obj, bone_name, location, child_name_postfix=""):
|
||||
""" Takes the named bone and creates a non-scaling child of it at
|
||||
the given location. The returned bone (returned by name) is not
|
||||
|
@ -345,48 +354,65 @@ def _legacy_make_nonscaling_child(obj, bone_name, location, child_name_postfix="
|
|||
raise MetarigError("Cannot make nonscaling child outside of edit mode")
|
||||
|
||||
|
||||
#===================================
|
||||
####################################
|
||||
# Bone manipulation as rig methods
|
||||
#===================================
|
||||
####################################
|
||||
|
||||
|
||||
class BoneUtilityMixin(object):
|
||||
obj: ArmatureObject
|
||||
register_new_bone: Callable[[str, Optional[str]], None]
|
||||
|
||||
"""
|
||||
Provides methods for more convenient creation of bones.
|
||||
|
||||
Requires self.obj to be the armature object being worked on.
|
||||
"""
|
||||
def register_new_bone(self, new_name, old_name=None):
|
||||
def register_new_bone(self, new_name: str, old_name: Optional[str] = None):
|
||||
"""Registers creation or renaming of a bone based on old_name"""
|
||||
pass
|
||||
|
||||
def new_bone(self, new_name):
|
||||
def new_bone(self, new_name: str) -> str:
|
||||
"""Create a new bone with the specified name."""
|
||||
name = new_bone(self.obj, new_name)
|
||||
self.register_new_bone(name)
|
||||
self.register_new_bone(name, None)
|
||||
return name
|
||||
|
||||
def copy_bone(self, bone_name, new_name='', *, parent=False, inherit_scale=False, bbone=False, length=None, scale=None):
|
||||
def copy_bone(self, bone_name: str, new_name='', *,
|
||||
parent=False, inherit_scale=False, bbone=False,
|
||||
length: Optional[float] = None,
|
||||
scale: Optional[float] = None) -> str:
|
||||
"""Copy the bone with the given name, returning the new name."""
|
||||
name = copy_bone(self.obj, bone_name, new_name, parent=parent, inherit_scale=inherit_scale, bbone=bbone, length=length, scale=scale)
|
||||
name = copy_bone(self.obj, bone_name, new_name,
|
||||
parent=parent, inherit_scale=inherit_scale,
|
||||
bbone=bbone, length=length, scale=scale)
|
||||
self.register_new_bone(name, bone_name)
|
||||
return name
|
||||
|
||||
def copy_bone_properties(self, src_name, tgt_name, *, props=True, ui_controls=None, **kwargs):
|
||||
"""Copy pose-mode properties of the bone."""
|
||||
def copy_bone_properties(self, src_name: str, tgt_name: str, *,
|
||||
props=True,
|
||||
ui_controls: list[str] | bool | None = None,
|
||||
**kwargs):
|
||||
"""Copy pose-mode properties of the bone. For using ui_controls, self must be a Rig."""
|
||||
|
||||
if ui_controls:
|
||||
from ..base_rig import BaseRig
|
||||
assert isinstance(self, BaseRig)
|
||||
|
||||
if props:
|
||||
if ui_controls is None and is_control_bone(tgt_name) and hasattr(self, 'script'):
|
||||
ui_controls = [tgt_name]
|
||||
elif ui_controls is True:
|
||||
ui_controls = self.bones.flatten('ctrl')
|
||||
|
||||
copy_bone_properties(self.obj, src_name, tgt_name, props=props and not ui_controls, **kwargs)
|
||||
copy_bone_properties(
|
||||
self.obj, src_name, tgt_name, props=props and not ui_controls, **kwargs)
|
||||
|
||||
if props and ui_controls:
|
||||
from .mechanism import copy_custom_properties_with_ui
|
||||
copy_custom_properties_with_ui(self, src_name, tgt_name, ui_controls=ui_controls)
|
||||
|
||||
def rename_bone(self, old_name, new_name):
|
||||
def rename_bone(self, old_name: str, new_name: str) -> str:
|
||||
"""Rename the bone, returning the actual new name."""
|
||||
bone = self.get_bone(old_name)
|
||||
bone.name = new_name
|
||||
|
@ -394,15 +420,17 @@ class BoneUtilityMixin(object):
|
|||
self.register_new_bone(bone.name, old_name)
|
||||
return bone.name
|
||||
|
||||
def get_bone(self, bone_name):
|
||||
def get_bone(self, bone_name: Optional[str])\
|
||||
-> Optional[bpy.types.EditBone | bpy.types.PoseBone]:
|
||||
"""Get EditBone or PoseBone by name, depending on the current mode."""
|
||||
return get_bone(self.obj, bone_name)
|
||||
|
||||
def get_bone_parent(self, bone_name):
|
||||
def get_bone_parent(self, bone_name: str) -> Optional[str]:
|
||||
"""Get the name of the parent bone, or None."""
|
||||
return get_name(self.get_bone(bone_name).parent)
|
||||
|
||||
def set_bone_parent(self, bone_name, parent_name, use_connect=False, inherit_scale=None):
|
||||
def set_bone_parent(self, bone_name: str, parent_name: Optional[str],
|
||||
use_connect=False, inherit_scale: Optional[str] = None):
|
||||
"""Set the parent of the bone."""
|
||||
eb = self.obj.data.edit_bones
|
||||
bone = eb[bone_name]
|
||||
|
@ -412,16 +440,20 @@ class BoneUtilityMixin(object):
|
|||
bone.inherit_scale = inherit_scale
|
||||
bone.parent = (eb[parent_name] if parent_name else None)
|
||||
|
||||
def parent_bone_chain(self, bone_names, use_connect=None, inherit_scale=None):
|
||||
def parent_bone_chain(self, bone_names: list[str],
|
||||
use_connect: Optional[bool] = None,
|
||||
inherit_scale: Optional[str] = None):
|
||||
"""Link bones into a chain with parenting. First bone may be None."""
|
||||
for parent, child in pairwise(bone_names):
|
||||
self.set_bone_parent(child, parent, use_connect=use_connect, inherit_scale=inherit_scale)
|
||||
self.set_bone_parent(
|
||||
child, parent, use_connect=use_connect, inherit_scale=inherit_scale)
|
||||
|
||||
#=============================================
|
||||
|
||||
##############################################
|
||||
# B-Bones
|
||||
#=============================================
|
||||
##############################################
|
||||
|
||||
def connect_bbone_chain_handles(obj, bone_names):
|
||||
def connect_bbone_chain_handles(obj: ArmatureObject, bone_names: list[str]):
|
||||
assert obj.mode == 'EDIT'
|
||||
|
||||
for prev_name, next_name in pairwise(bone_names):
|
||||
|
@ -434,26 +466,28 @@ def connect_bbone_chain_handles(obj, bone_names):
|
|||
next_bone.bbone_handle_type_start = 'ABSOLUTE'
|
||||
next_bone.bbone_custom_handle_start = prev_bone
|
||||
|
||||
#=============================================
|
||||
|
||||
##############################################
|
||||
# Math
|
||||
#=============================================
|
||||
##############################################
|
||||
|
||||
|
||||
def is_same_position(obj, bone_name1, bone_name2):
|
||||
def is_same_position(obj: ArmatureObject, bone_name1: str, bone_name2: str):
|
||||
head1 = get_bone(obj, bone_name1).head
|
||||
head2 = get_bone(obj, bone_name2).head
|
||||
|
||||
return (head1 - head2).length < 1e-5
|
||||
|
||||
|
||||
def is_connected_position(obj, bone_name1, bone_name2):
|
||||
def is_connected_position(obj: ArmatureObject, bone_name1: str, bone_name2: str):
|
||||
tail1 = get_bone(obj, bone_name1).tail
|
||||
head2 = get_bone(obj, bone_name2).head
|
||||
|
||||
return (tail1 - head2).length < 1e-5
|
||||
|
||||
|
||||
def copy_bone_position(obj, bone_name, target_bone_name, *, length=None, scale=None):
|
||||
def copy_bone_position(obj: ArmatureObject, bone_name: str, target_bone_name: str, *,
|
||||
length: Optional[float] = None,
|
||||
scale: Optional[float] = None):
|
||||
""" Completely copies the position and orientation of the bone. """
|
||||
bone1_e = obj.data.edit_bones[bone_name]
|
||||
bone2_e = obj.data.edit_bones[target_bone_name]
|
||||
|
@ -469,7 +503,7 @@ def copy_bone_position(obj, bone_name, target_bone_name, *, length=None, scale=N
|
|||
bone2_e.length *= scale
|
||||
|
||||
|
||||
def align_bone_orientation(obj, bone_name, target_bone_name):
|
||||
def align_bone_orientation(obj: ArmatureObject, bone_name: str, target_bone_name: str):
|
||||
""" Aligns the orientation of bone to target bone. """
|
||||
bone1_e = obj.data.edit_bones[bone_name]
|
||||
bone2_e = obj.data.edit_bones[target_bone_name]
|
||||
|
@ -480,7 +514,7 @@ def align_bone_orientation(obj, bone_name, target_bone_name):
|
|||
bone1_e.roll = bone2_e.roll
|
||||
|
||||
|
||||
def set_bone_orientation(obj, bone_name, orientation):
|
||||
def set_bone_orientation(obj: ArmatureObject, bone_name: str, orientation: str | Matrix):
|
||||
""" Aligns the orientation of bone to target bone or matrix. """
|
||||
if isinstance(orientation, str):
|
||||
align_bone_orientation(obj, bone_name, orientation)
|
||||
|
@ -494,7 +528,7 @@ def set_bone_orientation(obj, bone_name, orientation):
|
|||
bone_e.matrix = matrix
|
||||
|
||||
|
||||
def align_bone_roll(obj, bone1, bone2):
|
||||
def align_bone_roll(obj: ArmatureObject, bone1: str, bone2: str):
|
||||
""" Aligns the roll of two bones.
|
||||
"""
|
||||
bone1_e = obj.data.edit_bones[bone1]
|
||||
|
@ -539,7 +573,7 @@ def align_bone_roll(obj, bone1, bone2):
|
|||
bone1_e.roll = -roll
|
||||
|
||||
|
||||
def align_bone_x_axis(obj, bone, vec):
|
||||
def align_bone_x_axis(obj: ArmatureObject, bone: str, vec: Vector):
|
||||
""" Rolls the bone to align its x-axis as closely as possible to
|
||||
the given vector.
|
||||
Must be in edit mode.
|
||||
|
@ -564,7 +598,7 @@ def align_bone_x_axis(obj, bone, vec):
|
|||
bone_e.roll += angle * 2
|
||||
|
||||
|
||||
def align_bone_z_axis(obj, bone, vec):
|
||||
def align_bone_z_axis(obj: ArmatureObject, bone: str, vec: Vector):
|
||||
""" Rolls the bone to align its z-axis as closely as possible to
|
||||
the given vector.
|
||||
Must be in edit mode.
|
||||
|
@ -589,7 +623,7 @@ def align_bone_z_axis(obj, bone, vec):
|
|||
bone_e.roll += angle * 2
|
||||
|
||||
|
||||
def align_bone_y_axis(obj, bone, vec):
|
||||
def align_bone_y_axis(obj: ArmatureObject, bone: str, vec: Vector):
|
||||
""" Matches the bone y-axis to
|
||||
the given vector.
|
||||
Must be in edit mode.
|
||||
|
@ -597,14 +631,15 @@ def align_bone_y_axis(obj, bone, vec):
|
|||
|
||||
bone_e = obj.data.edit_bones[bone]
|
||||
vec.normalize()
|
||||
|
||||
vec = vec * bone_e.length
|
||||
|
||||
bone_e.tail = bone_e.head + vec
|
||||
|
||||
|
||||
def compute_chain_x_axis(obj, bone_names):
|
||||
def compute_chain_x_axis(obj: ArmatureObject, bone_names: list[str]):
|
||||
"""
|
||||
Compute the x axis of all bones to be perpendicular
|
||||
Compute the X axis of all bones to be perpendicular
|
||||
to the primary plane in which the bones lie.
|
||||
"""
|
||||
eb = obj.data.edit_bones
|
||||
|
@ -615,6 +650,7 @@ def compute_chain_x_axis(obj, bone_names):
|
|||
|
||||
# Compute normal to the plane defined by the first bone,
|
||||
# and the end of the last bone in the chain
|
||||
|
||||
chain_y_axis = last_bone.tail - first_bone.head
|
||||
chain_rot_axis = first_bone.y_axis.cross(chain_y_axis)
|
||||
|
||||
|
@ -624,9 +660,9 @@ def compute_chain_x_axis(obj, bone_names):
|
|||
return chain_rot_axis.normalized()
|
||||
|
||||
|
||||
def align_chain_x_axis(obj, bone_names):
|
||||
def align_chain_x_axis(obj: ArmatureObject, bone_names: list[str]):
|
||||
"""
|
||||
Aligns the x axis of all bones to be perpendicular
|
||||
Aligns the X axis of all bones to be perpendicular
|
||||
to the primary plane in which the bones lie.
|
||||
"""
|
||||
chain_rot_axis = compute_chain_x_axis(obj, bone_names)
|
||||
|
@ -635,7 +671,10 @@ def align_chain_x_axis(obj, bone_names):
|
|||
align_bone_x_axis(obj, name, chain_rot_axis)
|
||||
|
||||
|
||||
def align_bone_to_axis(obj, bone_name, axis, *, length=None, roll=0, flip=False):
|
||||
def align_bone_to_axis(obj: ArmatureObject, bone_name: str, axis: str, *,
|
||||
length: Optional[float] = None,
|
||||
roll: Optional[float] = 0.0,
|
||||
flip=False):
|
||||
"""
|
||||
Aligns the Y axis of the bone to the global axis (x,y,z,-x,-y,-z),
|
||||
optionally adjusting length and initially flipping the bone.
|
||||
|
@ -651,7 +690,7 @@ def align_bone_to_axis(obj, bone_name, axis, *, length=None, roll=0, flip=False)
|
|||
length = -length
|
||||
axis = axis[1:]
|
||||
|
||||
vec = Vector((0,0,0))
|
||||
vec = Vector((0, 0, 0))
|
||||
setattr(vec, axis, length)
|
||||
|
||||
if flip:
|
||||
|
@ -664,7 +703,9 @@ def align_bone_to_axis(obj, bone_name, axis, *, length=None, roll=0, flip=False)
|
|||
bone_e.roll = roll
|
||||
|
||||
|
||||
def set_bone_widget_transform(obj, bone_name, transform_bone, use_size=True, scale=1.0, target_size=False):
|
||||
def set_bone_widget_transform(obj: ArmatureObject, bone_name: str,
|
||||
transform_bone: Optional[str], *,
|
||||
use_size=True, scale=1.0, target_size=False):
|
||||
assert obj.mode != 'EDIT'
|
||||
|
||||
bone = obj.pose.bones[bone_name]
|
||||
|
|
|
@ -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.
|
||||
"""
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -3,31 +3,49 @@
|
|||
import bpy
|
||||
import re
|
||||
|
||||
from bpy.types import bpy_prop_collection, Material
|
||||
from typing import TYPE_CHECKING, Optional, Any, Collection
|
||||
|
||||
from bpy.types import (bpy_prop_collection, Material, Object, PoseBone, Driver, FCurve,
|
||||
DriverTarget, ID, bpy_struct, FModifierGenerator, Constraint, AnimData,
|
||||
ArmatureConstraint)
|
||||
|
||||
from rna_prop_ui import rna_idprop_ui_create
|
||||
from rna_prop_ui import rna_idprop_quote_path as quote_property
|
||||
|
||||
from .misc import force_lazy
|
||||
from .misc import force_lazy, ArmatureObject, Lazy
|
||||
|
||||
#=============================================
|
||||
if TYPE_CHECKING:
|
||||
from ..base_rig import BaseRig
|
||||
|
||||
|
||||
##############################################
|
||||
# Constraint creation utilities
|
||||
#=============================================
|
||||
##############################################
|
||||
|
||||
_TRACK_AXIS_MAP = {
|
||||
_TRACK_AXIS_MAP = {
|
||||
'X': 'TRACK_X', '-X': 'TRACK_NEGATIVE_X',
|
||||
'Y': 'TRACK_Y', '-Y': 'TRACK_NEGATIVE_Y',
|
||||
'Z': 'TRACK_Z', '-Z': 'TRACK_NEGATIVE_Z',
|
||||
}
|
||||
|
||||
|
||||
def _set_default_attr(obj, options, attr, value):
|
||||
if hasattr(obj, attr):
|
||||
options.setdefault(attr, value)
|
||||
|
||||
|
||||
def make_constraint(
|
||||
owner, con_type, target=None, subtarget=None, *, insert_index=None,
|
||||
space=None, track_axis=None, use_xyz=None, use_limit_xyz=None, invert_xyz=None,
|
||||
targets=None, **options):
|
||||
owner: Object | PoseBone, con_type: str,
|
||||
target: Optional[Object] = None,
|
||||
subtarget: Optional[str] = None, *,
|
||||
insert_index: Optional[int] = None,
|
||||
space: Optional[str] = None,
|
||||
track_axis: Optional[str] = None,
|
||||
use_xyz: Optional[Collection[bool]] = None,
|
||||
use_limit_xyz: Optional[Collection[bool]] = None,
|
||||
invert_xyz: Optional[Collection[bool]] = None,
|
||||
targets: list[Lazy[str | tuple | dict]] = None,
|
||||
**options):
|
||||
"""
|
||||
Creates and initializes constraint of the specified type for the owner bone.
|
||||
|
||||
|
@ -52,7 +70,7 @@ def make_constraint(
|
|||
|
||||
# For Armature constraints, allow passing a "targets" list as a keyword argument.
|
||||
if targets is not None:
|
||||
assert con.type == 'ARMATURE'
|
||||
assert isinstance(con, ArmatureConstraint)
|
||||
for target_info in targets:
|
||||
con_target = con.targets.new()
|
||||
con_target.target = owner.id_data
|
||||
|
@ -66,6 +84,7 @@ def make_constraint(
|
|||
else:
|
||||
con_target.target, con_target.subtarget, con_target.weight = map(force_lazy, target_info)
|
||||
else:
|
||||
assert isinstance(target_info, dict)
|
||||
for key, val in target_info.items():
|
||||
setattr(con_target, key, force_lazy(val))
|
||||
|
||||
|
@ -104,13 +123,15 @@ def make_constraint(
|
|||
|
||||
return con
|
||||
|
||||
#=============================================
|
||||
# Custom property creation utilities
|
||||
#=============================================
|
||||
|
||||
##############################################
|
||||
# Custom property creation utilities
|
||||
##############################################
|
||||
|
||||
# noinspection PyShadowingBuiltins
|
||||
def make_property(
|
||||
owner, name, default, *, min=0.0, max=1.0, soft_min=None, soft_max=None,
|
||||
description=None, overridable=True, **options):
|
||||
owner, name: str, default, *, min=0.0, max=1.0, soft_min=None, soft_max=None,
|
||||
description: Optional[str] = None, overridable=True, **options):
|
||||
"""
|
||||
Creates and initializes a custom property of owner.
|
||||
|
||||
|
@ -120,18 +141,19 @@ def make_property(
|
|||
|
||||
# Some keyword argument defaults differ
|
||||
rna_idprop_ui_create(
|
||||
owner, name, default = default,
|
||||
min = min, max = max, soft_min = soft_min, soft_max = soft_max,
|
||||
description = description or name,
|
||||
overridable = overridable,
|
||||
owner, name, default=default,
|
||||
min=min, max=max, soft_min=soft_min, soft_max=soft_max,
|
||||
description=description or name,
|
||||
overridable=overridable,
|
||||
**options
|
||||
)
|
||||
|
||||
#=============================================
|
||||
# Driver creation utilities
|
||||
#=============================================
|
||||
|
||||
def _init_driver_target(drv_target, var_info, target_id):
|
||||
##############################################
|
||||
# Driver creation utilities
|
||||
##############################################
|
||||
|
||||
def _init_driver_target(drv_target: DriverTarget, var_info, target_id: Optional[ID]):
|
||||
"""Initialize a driver variable target from a specification."""
|
||||
|
||||
# Parse the simple list format for the common case.
|
||||
|
@ -140,9 +162,9 @@ def _init_driver_target(drv_target, var_info, target_id):
|
|||
|
||||
# If target_id is supplied as parameter, allow omitting it
|
||||
if target_id is None or isinstance(var_info[0], bpy.types.ID):
|
||||
target_id,subtarget,*refs = var_info
|
||||
target_id, subtarget, *refs = var_info
|
||||
else:
|
||||
subtarget,*refs = var_info
|
||||
subtarget, *refs = var_info
|
||||
|
||||
subtarget = force_lazy(subtarget)
|
||||
|
||||
|
@ -165,7 +187,7 @@ def _init_driver_target(drv_target, var_info, target_id):
|
|||
if isinstance(item, str):
|
||||
path += item if item[0] == '.' else quote_property(item)
|
||||
else:
|
||||
path += '[%r]' % (item)
|
||||
path += f'[{repr(item)}]'
|
||||
|
||||
if path[0] == '.':
|
||||
path = path[1:]
|
||||
|
@ -184,7 +206,7 @@ def _init_driver_target(drv_target, var_info, target_id):
|
|||
setattr(drv_target, tp, force_lazy(tv))
|
||||
|
||||
|
||||
def _add_driver_variable(drv, var_name, var_info, target_id):
|
||||
def _add_driver_variable(drv: Driver, var_name: str, var_info, target_id: Optional[ID]):
|
||||
"""Add and initialize a driver variable."""
|
||||
|
||||
var = drv.variables.new()
|
||||
|
@ -209,7 +231,13 @@ def _add_driver_variable(drv, var_name, var_info, target_id):
|
|||
elif p != 'type':
|
||||
setattr(var, p, force_lazy(v))
|
||||
|
||||
def make_driver(owner, prop, *, index=-1, type='SUM', expression=None, variables={}, polynomial=None, target_id=None):
|
||||
|
||||
# noinspection PyIncorrectDocstring,PyShadowingBuiltins,PyDefaultArgument
|
||||
def make_driver(owner: bpy_struct, prop: str, *, index=-1, type='SUM',
|
||||
expression: Optional[str] = None,
|
||||
variables: list | dict = {},
|
||||
polynomial: Optional[list[float]] = None,
|
||||
target_id: Optional[ID] = None) -> FCurve:
|
||||
"""
|
||||
Creates and initializes a driver for the 'prop' property of owner.
|
||||
|
||||
|
@ -222,7 +250,7 @@ def make_driver(owner, prop, *, index=-1, type='SUM', expression=None, variables
|
|||
|
||||
Specification format:
|
||||
If the variables argument is a dictionary, keys specify variable names.
|
||||
Otherwise names are set to var, var1, var2, ... etc:
|
||||
Otherwise, names are set to var, var1, var2, ... etc:
|
||||
|
||||
variables = [ ..., ..., ... ]
|
||||
variables = { 'var': ..., 'var1': ..., 'var2': ... }
|
||||
|
@ -288,20 +316,22 @@ def make_driver(owner, prop, *, index=-1, type='SUM', expression=None, variables
|
|||
|
||||
if polynomial is not None:
|
||||
drv_modifier = fcu.modifiers.new('GENERATOR')
|
||||
assert isinstance(drv_modifier, FModifierGenerator)
|
||||
drv_modifier.mode = 'POLYNOMIAL'
|
||||
drv_modifier.poly_order = len(polynomial)-1
|
||||
for i,v in enumerate(polynomial):
|
||||
for i, v in enumerate(polynomial):
|
||||
drv_modifier.coefficients[i] = v
|
||||
|
||||
return fcu
|
||||
|
||||
|
||||
#=============================================
|
||||
##############################################
|
||||
# Driver variable utilities
|
||||
#=============================================
|
||||
##############################################
|
||||
|
||||
|
||||
def driver_var_transform(target, bone=None, *, type='LOC_X', space='WORLD', rotation_mode='AUTO'):
|
||||
# noinspection PyShadowingBuiltins
|
||||
def driver_var_transform(target: ID, bone: Optional[str] = None, *,
|
||||
type='LOC_X', space='WORLD', rotation_mode='AUTO'):
|
||||
"""
|
||||
Create a Transform Channel driver variable specification.
|
||||
|
||||
|
@ -323,10 +353,14 @@ def driver_var_transform(target, bone=None, *, type='LOC_X', space='WORLD', rota
|
|||
if bone is not None:
|
||||
target_map['bone_target'] = bone
|
||||
|
||||
return { 'type': 'TRANSFORMS', 'targets': [ target_map ] }
|
||||
return {'type': 'TRANSFORMS', 'targets': [target_map]}
|
||||
|
||||
|
||||
def driver_var_distance(target, *, bone1=None, target2=None, bone2=None, space1='WORLD', space2='WORLD'):
|
||||
def driver_var_distance(target: ID, *,
|
||||
bone1: Optional[str] = None,
|
||||
target2: Optional[ID] = None,
|
||||
bone2: Optional[str] = None,
|
||||
space1='WORLD', space2='WORLD'):
|
||||
"""
|
||||
Create a Distance driver variable specification.
|
||||
|
||||
|
@ -358,11 +392,11 @@ def driver_var_distance(target, *, bone1=None, target2=None, bone2=None, space1=
|
|||
return {'type': 'LOC_DIFF', 'targets': [target1_map, target2_map]}
|
||||
|
||||
|
||||
#=============================================
|
||||
##############################################
|
||||
# Constraint management
|
||||
#=============================================
|
||||
##############################################
|
||||
|
||||
def move_constraint(source, target, con):
|
||||
def move_constraint(source: Object | PoseBone, target: Object | PoseBone | str, con: Constraint):
|
||||
"""
|
||||
Move a constraint from one owner to another, together with drivers.
|
||||
"""
|
||||
|
@ -385,7 +419,11 @@ def move_constraint(source, target, con):
|
|||
|
||||
source.constraints.remove(con)
|
||||
|
||||
def move_all_constraints(obj, source, target, *, prefix=''):
|
||||
|
||||
def move_all_constraints(obj: Object,
|
||||
source: Object | PoseBone | str,
|
||||
target: Object | PoseBone | str, *,
|
||||
prefix=''):
|
||||
"""
|
||||
Move all constraints with the specified name prefix from one bone to another.
|
||||
"""
|
||||
|
@ -400,11 +438,11 @@ def move_all_constraints(obj, source, target, *, prefix=''):
|
|||
move_constraint(source, target, con)
|
||||
|
||||
|
||||
#=============================================
|
||||
##############################################
|
||||
# Custom property management
|
||||
#=============================================
|
||||
##############################################
|
||||
|
||||
def deactivate_custom_properties(obj, *, reset=True):
|
||||
def deactivate_custom_properties(obj: bpy_struct, *, reset=True):
|
||||
"""Disable drivers on custom properties and reset values to default."""
|
||||
|
||||
prefix = '["'
|
||||
|
@ -420,14 +458,14 @@ def deactivate_custom_properties(obj, *, reset=True):
|
|||
|
||||
if reset:
|
||||
for key, value in obj.items():
|
||||
valtype = type(value)
|
||||
if valtype in {int, float}:
|
||||
val_type = type(value)
|
||||
if val_type in {int, float}:
|
||||
ui_data = obj.id_properties_ui(key)
|
||||
rna_data = ui_data.as_dict()
|
||||
obj[key] = valtype(rna_data.get("default", 0))
|
||||
obj[key] = val_type(rna_data.get("default", 0))
|
||||
|
||||
|
||||
def reactivate_custom_properties(obj):
|
||||
def reactivate_custom_properties(obj: bpy_struct):
|
||||
"""Re-enable drivers on custom properties."""
|
||||
|
||||
prefix = '["'
|
||||
|
@ -442,7 +480,8 @@ def reactivate_custom_properties(obj):
|
|||
fcu.mute = False
|
||||
|
||||
|
||||
def copy_custom_properties(src, dest, *, prefix='', dest_prefix='', link_driver=False, overridable=True):
|
||||
def copy_custom_properties(src, dest, *, prefix='', dest_prefix='',
|
||||
link_driver=False, overridable=True) -> list[tuple[str, str, Any]]:
|
||||
"""Copy custom properties with filtering by prefix. Optionally link using drivers."""
|
||||
res = []
|
||||
|
||||
|
@ -456,7 +495,7 @@ def copy_custom_properties(src, dest, *, prefix='', dest_prefix='', link_driver=
|
|||
try:
|
||||
ui_data_src = src.id_properties_ui(key)
|
||||
except TypeError:
|
||||
# Some property types, eg. Python dictionaries
|
||||
# Some property types, e.g. Python dictionaries
|
||||
# don't support id_properties_ui.
|
||||
continue
|
||||
|
||||
|
@ -476,18 +515,18 @@ def copy_custom_properties(src, dest, *, prefix='', dest_prefix='', link_driver=
|
|||
return res
|
||||
|
||||
|
||||
def copy_custom_properties_with_ui(rig, src, dest_bone, *, ui_controls=None, **options):
|
||||
def copy_custom_properties_with_ui(rig: 'BaseRig', src, dest_bone, *, ui_controls=None, **options):
|
||||
"""Copy custom properties, and create rig UI for them."""
|
||||
if isinstance(src, str):
|
||||
src = rig.get_bone(src)
|
||||
|
||||
bone = rig.get_bone(dest_bone)
|
||||
bone: PoseBone = rig.get_bone(dest_bone)
|
||||
mapping = copy_custom_properties(src, bone, **options)
|
||||
|
||||
if mapping:
|
||||
panel = rig.script.panel_with_selected_check(rig, ui_controls or rig.bones.flatten('ctrl'))
|
||||
|
||||
for key,new_key,value in sorted(mapping, key=lambda item: item[1]):
|
||||
for key, new_key, value in sorted(mapping, key=lambda item: item[1]):
|
||||
name = new_key
|
||||
|
||||
# Replace delimiters with spaces
|
||||
|
@ -508,15 +547,15 @@ def copy_custom_properties_with_ui(rig, src, dest_bone, *, ui_controls=None, **o
|
|||
return mapping
|
||||
|
||||
|
||||
#=============================================
|
||||
##############################################
|
||||
# Driver management
|
||||
#=============================================
|
||||
##############################################
|
||||
|
||||
def refresh_drivers(obj):
|
||||
"""Cause all drivers belonging to the object to be re-evaluated, clearing any errors."""
|
||||
|
||||
# Refresh object's own drivers if any
|
||||
anim_data = getattr(obj, 'animation_data', None)
|
||||
anim_data: Optional[AnimData] = getattr(obj, 'animation_data', None)
|
||||
|
||||
if anim_data:
|
||||
for fcu in anim_data.drivers:
|
||||
|
@ -531,7 +570,7 @@ def refresh_drivers(obj):
|
|||
def refresh_all_drivers():
|
||||
"""Cause all drivers in the file to be re-evaluated, clearing any errors."""
|
||||
|
||||
# Iterate over all datablocks in the file
|
||||
# Iterate over all data blocks in the file
|
||||
for attr in dir(bpy.data):
|
||||
coll = getattr(bpy.data, attr, None)
|
||||
|
||||
|
@ -540,11 +579,13 @@ def refresh_all_drivers():
|
|||
refresh_drivers(item)
|
||||
|
||||
|
||||
#=============================================
|
||||
##############################################
|
||||
# Utility mixin
|
||||
#=============================================
|
||||
##############################################
|
||||
|
||||
class MechanismUtilityMixin(object):
|
||||
obj: ArmatureObject
|
||||
|
||||
"""
|
||||
Provides methods for more convenient creation of constraints, properties
|
||||
and drivers within an armature (by implicitly providing context).
|
||||
|
@ -552,6 +593,7 @@ class MechanismUtilityMixin(object):
|
|||
Requires self.obj to be the armature object being worked on.
|
||||
"""
|
||||
|
||||
# noinspection PyShadowingBuiltins
|
||||
def make_constraint(self, bone, type, subtarget=None, **args):
|
||||
assert(self.obj.mode == 'OBJECT')
|
||||
return make_constraint(self.obj.pose.bones[bone], type, self.obj, subtarget, **args)
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -3,24 +3,26 @@
|
|||
import bpy
|
||||
import math
|
||||
import collections
|
||||
import typing
|
||||
|
||||
from itertools import tee, chain, islice, repeat, permutations
|
||||
from mathutils import Vector, Matrix, Color
|
||||
from rna_prop_ui import rna_idprop_value_to_python
|
||||
|
||||
|
||||
#=============================================
|
||||
# Math
|
||||
#=============================================
|
||||
T = typing.TypeVar('T')
|
||||
|
||||
##############################################
|
||||
# Math
|
||||
##############################################
|
||||
|
||||
axis_vectors = {
|
||||
'x': (1,0,0),
|
||||
'y': (0,1,0),
|
||||
'z': (0,0,1),
|
||||
'-x': (-1,0,0),
|
||||
'-y': (0,-1,0),
|
||||
'-z': (0,0,-1),
|
||||
'x': (1, 0, 0),
|
||||
'y': (0, 1, 0),
|
||||
'z': (0, 0, 1),
|
||||
'-x': (-1, 0, 0),
|
||||
'-y': (0, -1, 0),
|
||||
'-z': (0, 0, -1),
|
||||
}
|
||||
|
||||
|
||||
|
@ -36,7 +38,7 @@ shuffle_matrix = {
|
|||
}
|
||||
|
||||
|
||||
def angle_on_plane(plane, vec1, vec2):
|
||||
def angle_on_plane(plane: Vector, vec1: Vector, vec2: Vector):
|
||||
""" Return the angle between two vectors projected onto a plane.
|
||||
"""
|
||||
plane.normalize()
|
||||
|
@ -69,7 +71,7 @@ matrix_from_axis_roll = bpy.types.Bone.MatrixFromAxisRoll
|
|||
axis_roll_from_matrix = bpy.types.Bone.AxisRollFromMatrix
|
||||
|
||||
|
||||
def matrix_from_axis_pair(y_axis, other_axis, axis_name):
|
||||
def matrix_from_axis_pair(y_axis: Vector, other_axis: Vector, axis_name: str):
|
||||
assert axis_name in 'xz'
|
||||
|
||||
y_axis = Vector(y_axis).normalized()
|
||||
|
@ -84,12 +86,12 @@ def matrix_from_axis_pair(y_axis, other_axis, axis_name):
|
|||
return Matrix((x_axis, y_axis, z_axis)).transposed()
|
||||
|
||||
|
||||
#=============================================
|
||||
##############################################
|
||||
# Color correction functions
|
||||
#=============================================
|
||||
##############################################
|
||||
|
||||
|
||||
def linsrgb_to_srgb (linsrgb):
|
||||
# noinspection SpellCheckingInspection
|
||||
def linsrgb_to_srgb(linsrgb: float):
|
||||
"""Convert physically linear RGB values into sRGB ones. The transform is
|
||||
uniform in the components, so *linsrgb* can be of any shape.
|
||||
|
||||
|
@ -105,44 +107,45 @@ def linsrgb_to_srgb (linsrgb):
|
|||
return scale
|
||||
|
||||
|
||||
def gamma_correct(color):
|
||||
|
||||
# noinspection PyUnresolvedReferences,PyTypeChecker
|
||||
def gamma_correct(color: Color):
|
||||
corrected_color = Color()
|
||||
for i, component in enumerate(color):
|
||||
corrected_color[i] = linsrgb_to_srgb(color[i])
|
||||
return corrected_color
|
||||
|
||||
|
||||
#=============================================
|
||||
##############################################
|
||||
# Iterators
|
||||
#=============================================
|
||||
|
||||
##############################################
|
||||
|
||||
# noinspection SpellCheckingInspection
|
||||
def padnone(iterable, pad=None):
|
||||
return chain(iterable, repeat(pad))
|
||||
|
||||
|
||||
# noinspection SpellCheckingInspection
|
||||
def pairwise_nozip(iterable):
|
||||
"s -> (s0,s1), (s1,s2), (s2,s3), ..."
|
||||
"""s -> (s0,s1), (s1,s2), (s2,s3), ..."""
|
||||
a, b = tee(iterable)
|
||||
next(b, None)
|
||||
return a, b
|
||||
|
||||
|
||||
def pairwise(iterable):
|
||||
"s -> (s0,s1), (s1,s2), (s2,s3), ..."
|
||||
"""s -> (s0,s1), (s1,s2), (s2,s3), ..."""
|
||||
a, b = tee(iterable)
|
||||
next(b, None)
|
||||
return zip(a, b)
|
||||
|
||||
|
||||
def map_list(func, *inputs):
|
||||
"[func(a0,b0...), func(a1,b1...), ...]"
|
||||
"""[func(a0,b0...), func(a1,b1...), ...]"""
|
||||
return list(map(func, *inputs))
|
||||
|
||||
|
||||
def skip(n, iterable):
|
||||
"Returns an iterator skipping first n elements of an iterable."
|
||||
"""Returns an iterator skipping first n elements of an iterable."""
|
||||
iterator = iter(iterable)
|
||||
if n == 1:
|
||||
next(iterator, None)
|
||||
|
@ -152,17 +155,21 @@ def skip(n, iterable):
|
|||
|
||||
|
||||
def map_apply(func, *inputs):
|
||||
"Apply the function to inputs like map for side effects, discarding results."
|
||||
"""Apply the function to inputs like map for side effects, discarding results."""
|
||||
collections.deque(map(func, *inputs), maxlen=0)
|
||||
|
||||
|
||||
#=============================================
|
||||
##############################################
|
||||
# Lazy references
|
||||
#=============================================
|
||||
##############################################
|
||||
|
||||
Lazy: typing.TypeAlias = T | typing.Callable[[], T]
|
||||
OptionalLazy: typing.TypeAlias = typing.Optional[T | typing.Callable[[], T]]
|
||||
|
||||
|
||||
def force_lazy(value):
|
||||
"""If the argument is callable, invokes it without arguments. Otherwise returns the argument as is."""
|
||||
def force_lazy(value: OptionalLazy[T]) -> T:
|
||||
"""If the argument is callable, invokes it without arguments.
|
||||
Otherwise, returns the argument as is."""
|
||||
if callable(value):
|
||||
return value()
|
||||
else:
|
||||
|
@ -171,7 +178,7 @@ def force_lazy(value):
|
|||
|
||||
class LazyRef:
|
||||
"""Hashable lazy reference. When called, evaluates (foo, 'a', 'b'...) as foo('a','b')
|
||||
if foo is callable. Otherwise the remaining arguments are used as attribute names or
|
||||
if foo is callable. Otherwise, the remaining arguments are used as attribute names or
|
||||
keys, like foo.a.b or foo.a[b] etc."""
|
||||
|
||||
def __init__(self, first, *args):
|
||||
|
@ -180,7 +187,7 @@ class LazyRef:
|
|||
self.first_hashable = first.__hash__ is not None
|
||||
|
||||
def __repr__(self):
|
||||
return 'LazyRef{}'.format(tuple(self.first, *self.args))
|
||||
return 'LazyRef{}'.format((self.first, *self.args))
|
||||
|
||||
def __eq__(self, other):
|
||||
return (
|
||||
|
@ -190,7 +197,8 @@ class LazyRef:
|
|||
)
|
||||
|
||||
def __hash__(self):
|
||||
return (hash(self.first) if self.first_hashable else hash(id(self.first))) ^ hash(self.args)
|
||||
return (hash(self.first) if self.first_hashable
|
||||
else hash(id(self.first))) ^ hash(self.args)
|
||||
|
||||
def __call__(self):
|
||||
first = self.first
|
||||
|
@ -206,31 +214,27 @@ class LazyRef:
|
|||
return first
|
||||
|
||||
|
||||
#=============================================
|
||||
##############################################
|
||||
# Misc
|
||||
#=============================================
|
||||
|
||||
##############################################
|
||||
|
||||
def copy_attributes(a, b):
|
||||
keys = dir(a)
|
||||
for key in keys:
|
||||
if not key.startswith("_") \
|
||||
and not key.startswith("error_") \
|
||||
and key != "group" \
|
||||
and key != "is_valid" \
|
||||
and key != "rna_type" \
|
||||
and key != "bl_rna":
|
||||
if not (key.startswith("_") or
|
||||
key.startswith("error_") or
|
||||
key in ("group", "is_valid", "is_valid", "bl_rna")):
|
||||
try:
|
||||
setattr(b, key, getattr(a, key))
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
def property_to_python(value):
|
||||
def property_to_python(value) -> typing.Any:
|
||||
value = rna_idprop_value_to_python(value)
|
||||
|
||||
if isinstance(value, dict):
|
||||
return { k: property_to_python(v) for k, v in value.items() }
|
||||
return {k: property_to_python(v) for k, v in value.items()}
|
||||
elif isinstance(value, list):
|
||||
return map_list(property_to_python, value)
|
||||
else:
|
||||
|
@ -246,7 +250,7 @@ def assign_parameters(target, val_dict=None, **params):
|
|||
for key in list(target.keys()):
|
||||
del target[key]
|
||||
|
||||
data = { **val_dict, **params }
|
||||
data = {**val_dict, **params}
|
||||
else:
|
||||
data = params
|
||||
|
||||
|
@ -254,15 +258,40 @@ def assign_parameters(target, val_dict=None, **params):
|
|||
try:
|
||||
target[key] = value
|
||||
except Exception as e:
|
||||
raise Exception("Couldn't set {} to {}: {}".format(key,value,e))
|
||||
raise Exception(f"Couldn't set {key} to {value}: {e}")
|
||||
|
||||
|
||||
def select_object(context, object, deselect_all=False):
|
||||
def select_object(context: bpy.types.Context, obj: bpy.types.Object, deselect_all=False):
|
||||
view_layer = context.view_layer
|
||||
|
||||
if deselect_all:
|
||||
for objt in view_layer.objects:
|
||||
objt.select_set(False) # deselect all objects
|
||||
for layer_obj in view_layer.objects:
|
||||
layer_obj.select_set(False) # deselect all objects
|
||||
|
||||
object.select_set(True)
|
||||
view_layer.objects.active = object
|
||||
obj.select_set(True)
|
||||
view_layer.objects.active = obj
|
||||
|
||||
|
||||
##############################################
|
||||
# Typing
|
||||
##############################################
|
||||
|
||||
class TypedObject(bpy.types.Object, typing.Generic[T]):
|
||||
data: T
|
||||
|
||||
|
||||
ArmatureObject = TypedObject[bpy.types.Armature]
|
||||
MeshObject = TypedObject[bpy.types.Mesh]
|
||||
AnyVector = Vector | typing.Sequence[float]
|
||||
|
||||
|
||||
def verify_armature_obj(obj: bpy.types.Object) -> ArmatureObject:
|
||||
assert obj and obj.type == 'ARMATURE'
|
||||
# noinspection PyTypeChecker
|
||||
return obj
|
||||
|
||||
|
||||
def verify_mesh_obj(obj: bpy.types.Object) -> MeshObject:
|
||||
assert obj and obj.type == 'MESH'
|
||||
# noinspection PyTypeChecker
|
||||
return obj
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -1,23 +1,19 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import bpy
|
||||
|
||||
import re
|
||||
import itertools
|
||||
import bisect
|
||||
import json
|
||||
|
||||
from .errors import MetarigError
|
||||
from .naming import strip_prefix, make_derived_name
|
||||
from .bones import set_bone_orientation
|
||||
from .mechanism import MechanismUtilityMixin
|
||||
from .rig import rig_is_child
|
||||
from .misc import map_list, map_apply, force_lazy
|
||||
from .misc import OptionalLazy, force_lazy, Lazy
|
||||
|
||||
from ..base_generate import GeneratorPlugin
|
||||
from ..base_rig import BaseRig
|
||||
from ..base_generate import GeneratorPlugin, BaseGenerator
|
||||
|
||||
from typing import Optional, Any
|
||||
from collections import defaultdict
|
||||
from itertools import count, repeat, chain
|
||||
from itertools import chain
|
||||
from mathutils import Matrix
|
||||
|
||||
|
||||
|
@ -27,7 +23,14 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
|
|||
Allows all rigs to register their bones as possible parents for other rigs.
|
||||
"""
|
||||
|
||||
def __init__(self, generator):
|
||||
global_parents: list[dict[str, Any]]
|
||||
local_parents: dict[int, list[dict[str, Any]]]
|
||||
parent_list: list[dict[str, Any]]
|
||||
|
||||
child_list: list[dict[str, Any]]
|
||||
child_map: dict[str, dict[str, Any]]
|
||||
|
||||
def __init__(self, generator: BaseGenerator):
|
||||
super().__init__(generator)
|
||||
|
||||
self.child_list = []
|
||||
|
@ -38,22 +41,25 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
|
|||
|
||||
self.register_parent(None, 'root', name='Root', is_global=True)
|
||||
|
||||
|
||||
##############################
|
||||
# API
|
||||
|
||||
def register_parent(self, rig, bone, *, name=None, is_global=False, exclude_self=False, inject_into=None, tags=None):
|
||||
def register_parent(self, rig: Optional[BaseRig], bone: Lazy[str], *,
|
||||
name: Optional[str] = None,
|
||||
is_global=False, exclude_self=False,
|
||||
inject_into: Optional[BaseRig] = None,
|
||||
tags: Optional[set[str]] = None):
|
||||
"""
|
||||
Registers a bone of the specified rig as a possible parent.
|
||||
|
||||
Parameters:
|
||||
rig Owner of the bone.
|
||||
bone Actual name of the parent bone.
|
||||
name Name of the parent for mouse-over hint.
|
||||
is_global The parent is accessible to all rigs, instead of just children of owner.
|
||||
exclude_self The parent is invisible to the owner rig itself.
|
||||
inject_into Make this parent available to children of the specified rig.
|
||||
tags Set of tags to use for default parent selection.
|
||||
rig: Owner of the bone (can be None if is_global).
|
||||
bone: Actual name of the parent bone.
|
||||
name: Name of the parent for mouse-over hint.
|
||||
is_global: The parent is accessible to all rigs, instead of just children of owner.
|
||||
exclude_self: The parent is invisible to the owner rig itself.
|
||||
inject_into: Make this parent available to children of the specified rig.
|
||||
tags: Set of tags to use for default parent selection.
|
||||
|
||||
Lazy creation:
|
||||
The bone parameter may be a function creating the bone on demand and
|
||||
|
@ -61,6 +67,7 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
|
|||
"""
|
||||
|
||||
assert not self.frozen
|
||||
assert is_global or rig
|
||||
assert isinstance(bone, str) or callable(bone)
|
||||
assert callable(bone) or rig_is_child(rig, self.generator.bone_owners[bone])
|
||||
assert rig_is_child(rig, inject_into)
|
||||
|
@ -82,35 +89,66 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
|
|||
else:
|
||||
self.local_parents[id(rig)].append(entry)
|
||||
|
||||
|
||||
def build_child(self, rig, bone, *, use_parent_mch=True, mch_orientation=None, **options):
|
||||
def build_child(self, rig: BaseRig, bone: str, *,
|
||||
use_parent_mch: bool = True,
|
||||
mch_orientation: Optional[str | Matrix] = None,
|
||||
# Options below must be in child_option_table and can be used in amend_child
|
||||
extra_parents: OptionalLazy[list[str | tuple[str, str]]] = None,
|
||||
select_parent: OptionalLazy[str] = None,
|
||||
select_tags: OptionalLazy[list[str | set[str]]] = None,
|
||||
ignore_global: bool = False,
|
||||
exclude_self: bool = False,
|
||||
allow_self: bool = False,
|
||||
context_rig: Optional[BaseRig] = None,
|
||||
no_implicit: bool = False,
|
||||
only_selected: bool = False,
|
||||
prop_bone: OptionalLazy[str] = None,
|
||||
prop_id: Optional[str] = None,
|
||||
prop_name: Optional[str] = None,
|
||||
controls: OptionalLazy[list[str]] = None,
|
||||
ctrl_bone: Optional[str] = None,
|
||||
no_fix_location: bool = False,
|
||||
no_fix_rotation: bool = False,
|
||||
no_fix_scale: bool = False,
|
||||
copy_location: OptionalLazy[str] = None,
|
||||
copy_rotation: OptionalLazy[str] = None,
|
||||
copy_scale: OptionalLazy[str] = None,
|
||||
inherit_scale: str = 'AVERAGE'):
|
||||
"""
|
||||
Build a switchable parent mechanism for the specified bone.
|
||||
|
||||
Parameters:
|
||||
rig Owner of the child bone.
|
||||
bone Name of the child bone.
|
||||
extra_parents List of bone names or (name, user_name) pairs to use as additional parents.
|
||||
use_parent_mch Create an intermediate MCH bone for the constraints and parent the child to it.
|
||||
mch_orientation Orientation matrix or bone name to align the MCH bone to; defaults to world.
|
||||
select_parent Select the specified bone instead of the last one.
|
||||
select_tags List of parent tags to try for default selection.
|
||||
ignore_global Ignore the is_global flag of potential parents.
|
||||
exclude_self Ignore parents registered by the rig itself.
|
||||
allow_self Ignore the 'exclude_self' setting of the parent.
|
||||
context_rig Rig to use for selecting parents; defaults to rig.
|
||||
no_implicit Only use parents listed as extra_parents.
|
||||
only_selected Like no_implicit, but allow the 'default' selected parent.
|
||||
rig: Owner of the child bone.
|
||||
bone: Name of the child bone.
|
||||
extra_parents: List of bone names or (name, user_name) pairs to use as
|
||||
additional parents.
|
||||
use_parent_mch: Create an intermediate MCH bone for the constraints and
|
||||
parent the child to it.
|
||||
mch_orientation: Orientation matrix or bone name to align the MCH bone to;
|
||||
defaults to world.
|
||||
select_parent: Select the specified bone instead of the last one.
|
||||
select_tags: List of parent tags to try for default selection.
|
||||
ignore_global: Ignore the is_global flag of potential parents.
|
||||
exclude_self: Ignore parents registered by the rig itself.
|
||||
allow_self: Ignore the 'exclude_self' setting of the parent.
|
||||
context_rig: Rig to use for selecting parents; defaults to rig.
|
||||
no_implicit: Only use parents listed as extra_parents.
|
||||
only_selected: Like no_implicit, but allow the 'default' selected parent.
|
||||
|
||||
prop_bone Name of the bone to add the property to.
|
||||
prop_id Actual name of the control property.
|
||||
prop_name Name of the property to use in the UI script.
|
||||
controls Collection of controls to bind property UI to.
|
||||
prop_bone: Name of the bone to add the property to.
|
||||
prop_id: Actual name of the control property.
|
||||
prop_name: Name of the property to use in the UI script.
|
||||
controls: Collection of controls to bind property UI to.
|
||||
|
||||
ctrl_bone User visible control bone that depends on this parent (for switch & keep transform)
|
||||
no_fix_* Disable "Switch and Keep Transform" correction for specific channels.
|
||||
copy_* Override the specified components by copying from another bone.
|
||||
inherit_scale Inherit scale mode for the child bone (default: AVERAGE).
|
||||
ctrl_bone: User visible control bone that depends on this parent
|
||||
(for switch & keep transform)
|
||||
no_fix_location: Disable "Switch and Keep Transform" correction for location.
|
||||
no_fix_rotation: Disable "Switch and Keep Transform" correction for rotation.
|
||||
no_fix_scale: Disable "Switch and Keep Transform" correction for scale.
|
||||
copy_location: Override the location by copying from another bone.
|
||||
copy_rotation: Override the rotation by copying from another bone.
|
||||
copy_scale: Override the scale by copying from another bone.
|
||||
inherit_scale: Inherit scale mode for the child bone (default: AVERAGE).
|
||||
|
||||
Lazy parameters:
|
||||
'extra_parents', 'select_parent', 'prop_bone', 'controls', 'copy_*'
|
||||
|
@ -131,16 +169,14 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
|
|||
mch_bone = bone
|
||||
|
||||
child = {
|
||||
**self.child_option_table,
|
||||
'rig':rig, 'bone': bone, 'mch_bone': mch_bone,
|
||||
'rig': rig, 'bone': bone, 'mch_bone': mch_bone,
|
||||
'is_done': False, 'is_configured': False,
|
||||
}
|
||||
self.assign_child_options(child, options)
|
||||
self.assign_child_options(child, self.child_option_table, locals())
|
||||
self.child_list.append(child)
|
||||
self.child_map[bone] = child
|
||||
|
||||
|
||||
def amend_child(self, rig, bone, **options):
|
||||
def amend_child(self, rig: BaseRig, bone: str, **options):
|
||||
"""
|
||||
Change parameters assigned in a previous build_child call.
|
||||
|
||||
|
@ -149,10 +185,9 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
|
|||
assert self.generator.stage == 'generate_bones' and not self.frozen
|
||||
child = self.child_map[bone]
|
||||
assert child['rig'] == rig
|
||||
self.assign_child_options(child, options)
|
||||
self.assign_child_options(child, set(options.keys()), options)
|
||||
|
||||
|
||||
def rig_child_now(self, bone):
|
||||
def rig_child_now(self, bone: str):
|
||||
"""Create the constraints immediately."""
|
||||
assert self.generator.stage == 'rig_bones'
|
||||
child = self.child_map[bone]
|
||||
|
@ -163,29 +198,29 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
|
|||
# Implementation
|
||||
|
||||
child_option_table = {
|
||||
'extra_parents': None,
|
||||
'prop_bone': None, 'prop_id': None, 'prop_name': None, 'controls': None,
|
||||
'select_parent': None, 'ignore_global': False,
|
||||
'exclude_self': False, 'allow_self': False,
|
||||
'context_rig': None, 'select_tags': None,
|
||||
'no_implicit': False, 'only_selected': False,
|
||||
'ctrl_bone': None,
|
||||
'no_fix_location': False, 'no_fix_rotation': False, 'no_fix_scale': False,
|
||||
'copy_location': None, 'copy_rotation': None, 'copy_scale': None,
|
||||
'inherit_scale': 'AVERAGE',
|
||||
'extra_parents',
|
||||
'prop_bone', 'prop_id', 'prop_name', 'controls',
|
||||
'select_parent', 'ignore_global',
|
||||
'exclude_self', 'allow_self',
|
||||
'context_rig', 'select_tags',
|
||||
'no_implicit', 'only_selected',
|
||||
'ctrl_bone',
|
||||
'no_fix_location', 'no_fix_rotation', 'no_fix_scale',
|
||||
'copy_location', 'copy_rotation', 'copy_scale',
|
||||
'inherit_scale',
|
||||
}
|
||||
|
||||
def assign_child_options(self, child, options):
|
||||
if 'context_rig' in options:
|
||||
def assign_child_options(self, child, names: set[str], options: dict[str, Any]):
|
||||
if 'context_rig' in names:
|
||||
assert rig_is_child(child['rig'], options['context_rig'])
|
||||
|
||||
for name, value in options.items():
|
||||
for name in names:
|
||||
if name not in self.child_option_table:
|
||||
raise AttributeError('invalid child option: '+name)
|
||||
raise AttributeError('invalid child option: ' + name)
|
||||
|
||||
child[name] = value
|
||||
child[name] = options[name]
|
||||
|
||||
def get_rig_parent_candidates(self, rig):
|
||||
def get_rig_parent_candidates(self, rig: Optional[BaseRig]):
|
||||
candidates = []
|
||||
|
||||
# Build a list in parent hierarchy order
|
||||
|
@ -199,7 +234,8 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
|
|||
|
||||
def generate_bones(self):
|
||||
self.frozen = True
|
||||
self.parent_list = self.global_parents + list(chain.from_iterable(self.local_parents.values()))
|
||||
self.parent_list = (self.global_parents +
|
||||
list(chain.from_iterable(self.local_parents.values())))
|
||||
|
||||
# Link children to parents
|
||||
for child in self.child_list:
|
||||
|
@ -215,7 +251,8 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
|
|||
continue
|
||||
|
||||
if parent['rig'] is child_rig:
|
||||
if (parent['exclude_self'] and not child['allow_self']) or child['exclude_self']:
|
||||
if (parent['exclude_self'] and not child['allow_self'])\
|
||||
or child['exclude_self']:
|
||||
continue
|
||||
elif parent['is_global'] and not child['ignore_global']:
|
||||
# Can't use parents from own children, even if global (cycle risk)
|
||||
|
@ -293,7 +330,7 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
|
|||
|
||||
if child['no_implicit']:
|
||||
assert len(extra_parents) > 0
|
||||
parent_bones = [ item for item in parent_bones if item[0] in extra_parents ]
|
||||
parent_bones = [item for item in parent_bones if item[0] in extra_parents]
|
||||
if last_main_parent_bone not in extra_parents:
|
||||
last_main_parent_bone = parent_bones[-1][0]
|
||||
|
||||
|
@ -308,15 +345,17 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
|
|||
break
|
||||
|
||||
if select_bone not in parent_map:
|
||||
print("RIGIFY ERROR: Can't find bone '%s' to select as default parent of '%s'\n" % (select_bone, bone))
|
||||
print(f"RIGIFY ERROR: Can't find bone '{select_bone}' "
|
||||
f"to select as default parent of '{bone}'\n")
|
||||
select_bone = last_main_parent_bone
|
||||
|
||||
if child['only_selected']:
|
||||
filter_set = { select_bone, *extra_parents }
|
||||
parent_bones = [ item for item in parent_bones if item[0] in filter_set ]
|
||||
filter_set = {select_bone, *extra_parents}
|
||||
parent_bones = [item for item in parent_bones if item[0] in filter_set]
|
||||
|
||||
try:
|
||||
select_index = 1 + next(i for i, (bone, _) in enumerate(parent_bones) if bone == select_bone)
|
||||
select_index = 1 + next(i for i, (bone, _) in enumerate(parent_bones)
|
||||
if bone == select_bone)
|
||||
except StopIteration:
|
||||
select_index = len(parent_bones)
|
||||
print("RIGIFY ERROR: Invalid default parent '%s' of '%s'\n" % (select_bone, bone))
|
||||
|
@ -328,8 +367,9 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
|
|||
prop_name = child['prop_name'] or child['prop_id'] or 'Parent Switch'
|
||||
prop_id = child['prop_id'] = child['prop_id'] or 'parent_switch'
|
||||
|
||||
parent_names = [ parent[1] or strip_prefix(parent[0]) for parent in [(None, 'None'), *parent_bones] ]
|
||||
parent_str = ', '.join([ '%s (%d)' % (name, i) for i, name in enumerate(parent_names) ])
|
||||
parent_names = [parent[1] or strip_prefix(parent[0])
|
||||
for parent in [(None, 'None'), *parent_bones]]
|
||||
parent_str = ', '.join(['%s (%d)' % (name, i) for i, name in enumerate(parent_names)])
|
||||
|
||||
ctrl_bone = child['ctrl_bone'] or bone
|
||||
|
||||
|
@ -341,14 +381,15 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
|
|||
|
||||
# Find which channels don't depend on the parent
|
||||
|
||||
no_fix = [ child[n] for n in ['no_fix_location', 'no_fix_rotation', 'no_fix_scale'] ]
|
||||
no_fix = [child[n] for n in ['no_fix_location', 'no_fix_rotation', 'no_fix_scale']]
|
||||
|
||||
child['copy'] = [ force_lazy(child[n]) for n in ['copy_location', 'copy_rotation', 'copy_scale'] ]
|
||||
child['copy'] = [force_lazy(child[n])
|
||||
for n in ['copy_location', 'copy_rotation', 'copy_scale']]
|
||||
|
||||
locks = tuple(bool(nofix or copy) for nofix, copy in zip(no_fix, child['copy']))
|
||||
locks = tuple(bool(n_fix or copy) for n_fix, copy in zip(no_fix, child['copy']))
|
||||
|
||||
# Create the script for the property
|
||||
controls = force_lazy(child['controls']) or set([prop_bone, bone])
|
||||
controls = force_lazy(child['controls']) or {prop_bone, bone}
|
||||
|
||||
script = self.generator.script
|
||||
panel = script.panel_with_selected_check(child['rig'], controls)
|
||||
|
@ -363,10 +404,13 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
|
|||
}
|
||||
|
||||
row = panel.row(align=True)
|
||||
lsplit = row.split(factor=0.75, align=True)
|
||||
lsplit.operator('pose.rigify_switch_parent_{rig_id}', text=prop_name, icon='DOWNARROW_HLT', properties=op_props)
|
||||
lsplit.custom_prop(prop_bone, prop_id, text='')
|
||||
row.operator('pose.rigify_switch_parent_bake_{rig_id}', text='', icon='ACTION_TWEAK', properties=op_props)
|
||||
left_split = row.split(factor=0.75, align=True)
|
||||
# noinspection SpellCheckingInspection
|
||||
left_split.operator('pose.rigify_switch_parent_{rig_id}', text=prop_name,
|
||||
icon='DOWNARROW_HLT', properties=op_props)
|
||||
left_split.custom_prop(prop_bone, prop_id, text='')
|
||||
row.operator('pose.rigify_switch_parent_bake_{rig_id}', text='',
|
||||
icon='ACTION_TWEAK', properties=op_props)
|
||||
|
||||
def rig_bones(self):
|
||||
for child in self.child_list:
|
||||
|
@ -382,12 +426,12 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
|
|||
mch = child['mch_bone']
|
||||
con = self.make_constraint(
|
||||
mch, 'ARMATURE', name='SWITCH_PARENT',
|
||||
targets=[ (parent, 0.0) for parent, _ in child['parent_bones'] ]
|
||||
targets=[(parent, 0.0) for parent, _ in child['parent_bones']]
|
||||
)
|
||||
|
||||
prop_var = [(child['prop_bone'], child['prop_id'])]
|
||||
|
||||
for i, (parent, parent_name) in enumerate(child['parent_bones']):
|
||||
for i, (_parent, _parent_name) in enumerate(child['parent_bones']):
|
||||
expr = 'var == %d' % (i+1)
|
||||
self.make_driver(con.targets[i], 'weight', expression=expr, variables=prop_var)
|
||||
|
||||
|
@ -402,8 +446,10 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
|
|||
self.make_constraint(mch, 'COPY_SCALE', copy[2])
|
||||
|
||||
|
||||
SCRIPT_REGISTER_OP_SWITCH_PARENT = ['POSE_OT_rigify_switch_parent', 'POSE_OT_rigify_switch_parent_bake']
|
||||
SCRIPT_REGISTER_OP_SWITCH_PARENT = ['POSE_OT_rigify_switch_parent',
|
||||
'POSE_OT_rigify_switch_parent_bake']
|
||||
|
||||
# noinspection SpellCheckingInspection
|
||||
SCRIPT_UTILITIES_OP_SWITCH_PARENT = ['''
|
||||
################################
|
||||
## Switchable Parent operator ##
|
||||
|
|
|
@ -5,21 +5,25 @@ import math
|
|||
import inspect
|
||||
import functools
|
||||
|
||||
from typing import Optional, Callable
|
||||
from bpy.types import Mesh, Object, UILayout
|
||||
from mathutils import Matrix, Vector, Euler
|
||||
from itertools import count
|
||||
|
||||
from .errors import MetarigError
|
||||
from .collections import ensure_collection
|
||||
from .misc import ArmatureObject, MeshObject, AnyVector, verify_mesh_obj
|
||||
from .naming import change_name_side, get_name_side, Side
|
||||
|
||||
WGT_PREFIX = "WGT-" # Prefix for widget objects
|
||||
|
||||
#=============================================
|
||||
|
||||
##############################################
|
||||
# Widget creation
|
||||
#=============================================
|
||||
##############################################
|
||||
|
||||
|
||||
def obj_to_bone(obj, rig, bone_name, bone_transform_name=None):
|
||||
def obj_to_bone(obj: Object, rig: ArmatureObject, bone_name: str,
|
||||
bone_transform_name: Optional[str] = None):
|
||||
""" Places an object at the location/rotation/scale of the given bone.
|
||||
"""
|
||||
if bpy.context.mode == 'EDIT_ARMATURE':
|
||||
|
@ -45,8 +49,13 @@ def obj_to_bone(obj, rig, bone_name, bone_transform_name=None):
|
|||
obj.matrix_basis = rig.matrix_world @ bone.bone.matrix_local @ shape_mat
|
||||
|
||||
|
||||
def create_widget(rig, bone_name, bone_transform_name=None, *, widget_name=None, widget_force_new=False, subsurf=0):
|
||||
""" Creates an empty widget object for a bone, and returns the object.
|
||||
def create_widget(rig: ArmatureObject, bone_name: str,
|
||||
bone_transform_name: Optional[str] = None, *,
|
||||
widget_name: Optional[str] = None,
|
||||
widget_force_new=False, subsurf=0) -> Optional[MeshObject]:
|
||||
"""
|
||||
Creates an empty widget object for a bone, and returns the object.
|
||||
If the object already existed, returns None.
|
||||
"""
|
||||
assert rig.mode != 'EDIT'
|
||||
|
||||
|
@ -61,16 +70,17 @@ def create_widget(rig, bone_name, bone_transform_name=None, *, widget_name=None,
|
|||
if generator:
|
||||
collection = generator.widget_collection
|
||||
else:
|
||||
# noinspection SpellCheckingInspection
|
||||
collection = ensure_collection(bpy.context, 'WGTS_' + rig.name, hidden=True)
|
||||
|
||||
use_mirror = generator and generator.use_mirror_widgets
|
||||
|
||||
if use_mirror:
|
||||
bone_mid_name = change_name_side(bone_name, Side.MIDDLE)
|
||||
bone_mid_name = change_name_side(bone_name, Side.MIDDLE) if use_mirror else bone_name
|
||||
|
||||
obj_name = widget_name or WGT_PREFIX + rig.name + '_' + bone_name
|
||||
reuse_mesh = None
|
||||
|
||||
obj: Optional[MeshObject]
|
||||
|
||||
# Check if it already exists in the scene
|
||||
if not widget_force_new:
|
||||
obj = None
|
||||
|
@ -87,7 +97,9 @@ def create_widget(rig, bone_name, bone_transform_name=None, *, widget_name=None,
|
|||
# Search the scene by name
|
||||
obj = scene.objects.get(obj_name)
|
||||
if obj and obj.library:
|
||||
local_objs = [obj for obj in scene.objects if obj.name == obj_name and not obj.library]
|
||||
# Second brute force try if the first result is linked
|
||||
local_objs = [obj for obj in scene.objects
|
||||
if obj.name == obj_name and not obj.library]
|
||||
obj = local_objs[0] if local_objs else None
|
||||
|
||||
if obj:
|
||||
|
@ -100,7 +112,7 @@ def create_widget(rig, bone_name, bone_transform_name=None, *, widget_name=None,
|
|||
collection.objects.link(obj)
|
||||
|
||||
# Flip scale for originally mirrored widgets
|
||||
if obj.scale.x < 0 and bone.custom_shape_scale_xyz.x > 0:
|
||||
if obj.scale.x < 0 < bone.custom_shape_scale_xyz.x:
|
||||
bone.custom_shape_scale_xyz.x *= -1
|
||||
|
||||
# Move object to bone position, in case it changed
|
||||
|
@ -132,7 +144,7 @@ def create_widget(rig, bone_name, bone_transform_name=None, *, widget_name=None,
|
|||
mesh = bpy.data.meshes.new(obj_name)
|
||||
|
||||
# Create the object
|
||||
obj = bpy.data.objects.new(obj_name, mesh)
|
||||
obj = verify_mesh_obj(bpy.data.objects.new(obj_name, mesh))
|
||||
collection.objects.link(obj)
|
||||
|
||||
# Add the subdivision surface modifier
|
||||
|
@ -158,9 +170,9 @@ def create_widget(rig, bone_name, bone_transform_name=None, *, widget_name=None,
|
|||
return obj
|
||||
|
||||
|
||||
#=============================================
|
||||
##############################################
|
||||
# Widget choice dropdown
|
||||
#=============================================
|
||||
##############################################
|
||||
|
||||
_registered_widgets = {}
|
||||
|
||||
|
@ -170,7 +182,7 @@ def _get_valid_args(callback, skip):
|
|||
return set(spec.args[skip:] + spec.kwonlyargs)
|
||||
|
||||
|
||||
def register_widget(name, callback, **default_args):
|
||||
def register_widget(name: str, callback, **default_args):
|
||||
unwrapped = inspect.unwrap(callback)
|
||||
if unwrapped != callback:
|
||||
valid_args = _get_valid_args(unwrapped, 1)
|
||||
|
@ -180,10 +192,11 @@ def register_widget(name, callback, **default_args):
|
|||
_registered_widgets[name] = (callback, valid_args, default_args)
|
||||
|
||||
|
||||
def layout_widget_dropdown(layout, props, prop_name, **kwargs):
|
||||
"Create a UI dropdown to select a widget from the known list."
|
||||
def layout_widget_dropdown(layout: UILayout, props, prop_name: str, **kwargs):
|
||||
"""Create a UI dropdown to select a widget from the known list."""
|
||||
|
||||
id_store = bpy.context.window_manager
|
||||
# noinspection PyUnresolvedReferences
|
||||
rigify_widgets = id_store.rigify_widgets
|
||||
|
||||
rigify_widgets.clear()
|
||||
|
@ -195,7 +208,7 @@ def layout_widget_dropdown(layout, props, prop_name, **kwargs):
|
|||
layout.prop_search(props, prop_name, id_store, "rigify_widgets", **kwargs)
|
||||
|
||||
|
||||
def create_registered_widget(obj, bone_name, widget_id, **kwargs):
|
||||
def create_registered_widget(obj: ArmatureObject, bone_name: str, widget_id: str, **kwargs):
|
||||
try:
|
||||
callback, valid_args, default_args = _registered_widgets[widget_id]
|
||||
except KeyError:
|
||||
|
@ -210,26 +223,27 @@ def create_registered_widget(obj, bone_name, widget_id, **kwargs):
|
|||
if 'size' in valid_args and not kwargs.get('size'):
|
||||
kwargs['size'] = kwargs['radius'] * 2
|
||||
|
||||
args = { **default_args, **kwargs }
|
||||
args = {**default_args, **kwargs}
|
||||
|
||||
return callback(obj, bone_name, **{ k:v for k,v in args.items() if k in valid_args})
|
||||
return callback(obj, bone_name, **{k: v for k, v in args.items() if k in valid_args})
|
||||
|
||||
|
||||
#=============================================
|
||||
##############################################
|
||||
# Widget geometry
|
||||
#=============================================
|
||||
##############################################
|
||||
|
||||
class GeometryData:
|
||||
verts: list[AnyVector]
|
||||
edges: list[tuple[int, int]]
|
||||
faces: list[tuple[int, ...]]
|
||||
|
||||
def __init__(self):
|
||||
self.verts = []
|
||||
self.edges = []
|
||||
self.faces = []
|
||||
|
||||
|
||||
def widget_generator(generate_func=None, *, register=None, subsurf=0):
|
||||
if generate_func is None:
|
||||
return functools.partial(widget_generator, register=register, subsurf=subsurf)
|
||||
|
||||
def widget_generator(generate_func=None, *, register=None, subsurf=0) -> Callable:
|
||||
"""
|
||||
Decorator that encapsulates a call to create_widget, and only requires
|
||||
the actual function to fill the provided vertex and edge lists.
|
||||
|
@ -237,15 +251,21 @@ def widget_generator(generate_func=None, *, register=None, subsurf=0):
|
|||
Accepts parameters of create_widget, plus any keyword arguments the
|
||||
wrapped function has.
|
||||
"""
|
||||
if generate_func is None:
|
||||
return functools.partial(widget_generator, register=register, subsurf=subsurf)
|
||||
|
||||
@functools.wraps(generate_func)
|
||||
def wrapper(rig, bone_name, bone_transform_name=None, widget_name=None, widget_force_new=False, **kwargs):
|
||||
obj = create_widget(rig, bone_name, bone_transform_name, widget_name=widget_name, widget_force_new=widget_force_new, subsurf=subsurf)
|
||||
def wrapper(rig: ArmatureObject, bone_name: str, bone_transform_name=None,
|
||||
widget_name=None, widget_force_new=False, **kwargs):
|
||||
obj = create_widget(rig, bone_name, bone_transform_name,
|
||||
widget_name=widget_name, widget_force_new=widget_force_new,
|
||||
subsurf=subsurf)
|
||||
if obj is not None:
|
||||
geom = GeometryData()
|
||||
|
||||
generate_func(geom, **kwargs)
|
||||
|
||||
mesh = obj.data
|
||||
mesh: Mesh = obj.data
|
||||
mesh.from_pydata(geom.verts, geom.edges, geom.faces)
|
||||
mesh.update()
|
||||
|
||||
|
@ -259,7 +279,9 @@ def widget_generator(generate_func=None, *, register=None, subsurf=0):
|
|||
return wrapper
|
||||
|
||||
|
||||
def generate_lines_geometry(geom, points, *, matrix=None, closed_loop=False):
|
||||
def generate_lines_geometry(geom: GeometryData,
|
||||
points: list[AnyVector], *,
|
||||
matrix: Optional[Matrix] = None, closed_loop=False):
|
||||
"""
|
||||
Generates a polyline using given points, optionally closing the loop.
|
||||
"""
|
||||
|
@ -282,13 +304,15 @@ def generate_lines_geometry(geom, points, *, matrix=None, closed_loop=False):
|
|||
geom.edges.append((len(geom.verts) - 1, base))
|
||||
|
||||
|
||||
def generate_circle_geometry(geom, center, radius, *, matrix=None, angle_range=None,
|
||||
steps=24, radius_x=None, depth_x=0):
|
||||
def generate_circle_geometry(geom: GeometryData, center: AnyVector, radius: float, *,
|
||||
matrix: Optional[Matrix] = None,
|
||||
angle_range: Optional[tuple[float, float]] = None,
|
||||
steps=24, radius_x: Optional[float] = None, depth_x=0):
|
||||
"""
|
||||
Generates a circle, adding vertices and edges to the lists.
|
||||
center, radius: parameters of the circle
|
||||
matrix: transformation matrix (by default the circle is in the XY plane)
|
||||
angle_range: pair of angles to generate an arc of the circle
|
||||
angle_range: a pair of angles to generate an arc of the circle
|
||||
steps: number of edges to cover the whole circle (reduced for arcs)
|
||||
"""
|
||||
assert steps >= 3
|
||||
|
@ -319,7 +343,9 @@ def generate_circle_geometry(geom, center, radius, *, matrix=None, angle_range=N
|
|||
generate_lines_geometry(geom, points, matrix=matrix, closed_loop=not angle_range)
|
||||
|
||||
|
||||
def generate_circle_hull_geometry(geom, points, radius, gap, *, matrix=None, steps=24):
|
||||
def generate_circle_hull_geometry(geom: GeometryData, points: list[AnyVector],
|
||||
radius: float, gap: float, *,
|
||||
matrix: Optional[Matrix] = None, steps=24):
|
||||
"""
|
||||
Given a list of 2D points forming a convex hull, generate a contour around
|
||||
it, with each point being circumscribed with a circle arc of given radius,
|
||||
|
@ -337,28 +363,28 @@ def generate_circle_hull_geometry(geom, points, radius, gap, *, matrix=None, ste
|
|||
|
||||
base = len(geom.verts)
|
||||
points_ex = [points[-1], *points, points[0]]
|
||||
agap = math.asin(gap / radius)
|
||||
angle_gap = math.asin(gap / radius)
|
||||
|
||||
for i, pprev, pcur, pnext in zip(count(0), points_ex[0:], points_ex[1:], points_ex[2:]):
|
||||
vprev = pprev - pcur
|
||||
vnext = pnext - pcur
|
||||
for i, pt_prev, pt_cur, pt_next in zip(count(0), points_ex[0:], points_ex[1:], points_ex[2:]):
|
||||
vec_prev = pt_prev - pt_cur
|
||||
vec_next = pt_next - pt_cur
|
||||
|
||||
# Compute bearings to adjacent points
|
||||
aprev = math.atan2(vprev.y, vprev.x)
|
||||
anext = math.atan2(vnext.y, vnext.x)
|
||||
if anext <= aprev:
|
||||
anext += math.pi * 2
|
||||
angle_prev = math.atan2(vec_prev.y, vec_prev.x)
|
||||
angle_next = math.atan2(vec_next.y, vec_next.x)
|
||||
if angle_next <= angle_prev:
|
||||
angle_next += math.pi * 2
|
||||
|
||||
# Adjust gap for circles that are too close
|
||||
aprev += max(agap, math.acos(min(1, vprev.length/radius/2)))
|
||||
anext -= max(agap, math.acos(min(1, vnext.length/radius/2)))
|
||||
angle_prev += max(angle_gap, math.acos(min(1, vec_prev.length/radius/2)))
|
||||
angle_next -= max(angle_gap, math.acos(min(1, vec_next.length/radius/2)))
|
||||
|
||||
if anext > aprev:
|
||||
if angle_next > angle_prev:
|
||||
if len(geom.verts) > base:
|
||||
geom.edges.append((len(geom.verts)-1, len(geom.verts)))
|
||||
|
||||
generate_circle_geometry(
|
||||
geom, pcur, radius, angle_range=(aprev, anext),
|
||||
geom, pt_cur, radius, angle_range=(angle_prev, angle_next),
|
||||
matrix=matrix, steps=steps
|
||||
)
|
||||
|
||||
|
@ -366,7 +392,7 @@ def generate_circle_hull_geometry(geom, points, radius, gap, *, matrix=None, ste
|
|||
geom.edges.append((len(geom.verts)-1, base))
|
||||
|
||||
|
||||
def create_circle_polygon(number_verts, axis, radius=1.0, head_tail=0.0):
|
||||
def create_circle_polygon(number_verts: int, axis: str, radius=1.0, head_tail=0.0):
|
||||
""" Creates a basic circle around of an axis selected.
|
||||
number_verts: number of vertices of the polygon
|
||||
axis: axis normal to the circle
|
||||
|
@ -380,7 +406,7 @@ def create_circle_polygon(number_verts, axis, radius=1.0, head_tail=0.0):
|
|||
|
||||
assert(axis in 'XYZ')
|
||||
|
||||
while i < (number_verts):
|
||||
while i < number_verts:
|
||||
a = math.cos(i * angle)
|
||||
b = math.sin(i * angle)
|
||||
|
||||
|
@ -392,7 +418,7 @@ def create_circle_polygon(number_verts, axis, radius=1.0, head_tail=0.0):
|
|||
verts.append((a * radius, b * radius, head_tail))
|
||||
|
||||
if i < (number_verts - 1):
|
||||
edges.append((i , i + 1))
|
||||
edges.append((i, i + 1))
|
||||
|
||||
i += 1
|
||||
|
||||
|
@ -401,12 +427,13 @@ def create_circle_polygon(number_verts, axis, radius=1.0, head_tail=0.0):
|
|||
return verts, edges
|
||||
|
||||
|
||||
#=============================================
|
||||
##############################################
|
||||
# Widget transformation
|
||||
#=============================================
|
||||
##############################################
|
||||
|
||||
def adjust_widget_axis(obj, axis='y', offset=0.0):
|
||||
def adjust_widget_axis(obj: Object, axis='y', offset=0.0):
|
||||
mesh = obj.data
|
||||
assert isinstance(mesh, Mesh)
|
||||
|
||||
if axis[0] == '-':
|
||||
s = -1.0
|
||||
|
@ -431,31 +458,35 @@ def adjust_widget_axis(obj, axis='y', offset=0.0):
|
|||
vert.co = matrix @ vert.co
|
||||
|
||||
|
||||
def adjust_widget_transform_mesh(obj, matrix, local=None):
|
||||
def adjust_widget_transform_mesh(obj: Optional[Object], matrix: Matrix,
|
||||
local: bool | None = None):
|
||||
"""Adjust the generated widget by applying a correction matrix to the mesh.
|
||||
If local is false, the matrix is in world space.
|
||||
If local is True, it's in the local space of the widget.
|
||||
If local is a bone, it's in the local space of the bone.
|
||||
"""
|
||||
if obj:
|
||||
mesh = obj.data
|
||||
assert isinstance(mesh, Mesh)
|
||||
|
||||
if local is not True:
|
||||
if local:
|
||||
assert isinstance(local, bpy.types.PoseBone)
|
||||
bonemat = local.id_data.matrix_world @ local.bone.matrix_local
|
||||
matrix = bonemat @ matrix @ bonemat.inverted()
|
||||
bone_mat = local.id_data.matrix_world @ local.bone.matrix_local
|
||||
matrix = bone_mat @ matrix @ bone_mat.inverted()
|
||||
|
||||
obmat = obj.matrix_basis
|
||||
matrix = obmat.inverted() @ matrix @ obmat
|
||||
obj_mat = obj.matrix_basis
|
||||
matrix = obj_mat.inverted() @ matrix @ obj_mat
|
||||
|
||||
obj.data.transform(matrix)
|
||||
mesh.transform(matrix)
|
||||
|
||||
|
||||
def write_widget(obj, name='thing', use_size=True):
|
||||
def write_widget(obj: Object, name='thing', use_size=True):
|
||||
""" Write a mesh object as a python script for widget use.
|
||||
"""
|
||||
script = ""
|
||||
script += "@widget_generator\n"
|
||||
script += "def create_"+name+"_widget(geom";
|
||||
script += "def create_"+name+"_widget(geom"
|
||||
if use_size:
|
||||
script += ", *, size=1.0"
|
||||
script += "):\n"
|
||||
|
@ -464,23 +495,26 @@ def write_widget(obj, name='thing', use_size=True):
|
|||
szs = "*size" if use_size else ""
|
||||
width = 2 if use_size else 3
|
||||
|
||||
mesh = obj.data
|
||||
assert isinstance(mesh, Mesh)
|
||||
|
||||
script += " geom.verts = ["
|
||||
for i, v in enumerate(obj.data.vertices):
|
||||
for i, v in enumerate(mesh.vertices):
|
||||
script += "({:g}{}, {:g}{}, {:g}{}),".format(v.co[0], szs, v.co[1], szs, v.co[2], szs)
|
||||
script += "\n " if i % width == (width - 1) else " "
|
||||
script += "]\n"
|
||||
|
||||
# Edges
|
||||
script += " geom.edges = ["
|
||||
for i, e in enumerate(obj.data.edges):
|
||||
for i, e in enumerate(mesh.edges):
|
||||
script += "(" + str(e.vertices[0]) + ", " + str(e.vertices[1]) + "),"
|
||||
script += "\n " if i % 10 == 9 else " "
|
||||
script += "]\n"
|
||||
|
||||
# Faces
|
||||
if obj.data.polygons:
|
||||
if mesh.polygons:
|
||||
script += " geom.faces = ["
|
||||
for i, f in enumerate(obj.data.polygons):
|
||||
for i, f in enumerate(mesh.polygons):
|
||||
script += "(" + ", ".join(str(v) for v in f.vertices) + "),"
|
||||
script += "\n " if i % 10 == 9 else " "
|
||||
script += "]\n"
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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),
|
||||
|
|
Loading…
Reference in New Issue