diff options
-rwxr-xr-x | io_scene_gltf2/__init__.py | 2 | ||||
-rw-r--r-- | rigify/operators/copy_mirror_parameters.py | 150 | ||||
-rw-r--r-- | rigify/rigs/limbs/super_finger.py | 6 | ||||
-rw-r--r-- | rigify/utils/layers.py | 6 | ||||
-rw-r--r-- | rigify/utils/mechanism.py | 12 | ||||
-rw-r--r-- | space_view3d_copy_attributes.py | 101 |
6 files changed, 269 insertions, 8 deletions
diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py index babf37fb..069596fa 100755 --- a/io_scene_gltf2/__init__.py +++ b/io_scene_gltf2/__init__.py @@ -15,7 +15,7 @@ bl_info = { 'name': 'glTF 2.0 format', 'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors', - "version": (1, 7, 33), + "version": (1, 8, 2), 'blender': (3, 0, 0), 'location': 'File > Import-Export', 'description': 'Import-Export as glTF 2.0', diff --git a/rigify/operators/copy_mirror_parameters.py b/rigify/operators/copy_mirror_parameters.py index 95818ba8..a844f8da 100644 --- a/rigify/operators/copy_mirror_parameters.py +++ b/rigify/operators/copy_mirror_parameters.py @@ -22,6 +22,7 @@ import bpy import importlib from ..utils.naming import Side, get_name_base_and_sides, mirror_name +from ..utils.misc import property_to_python from ..utils.rig import get_rigify_type from ..rig_lists import get_rig_class @@ -109,11 +110,157 @@ def make_copy_parameter_button(layout, property_name, *, base_class, mirror_bone props.class_name = base_class.__name__ +def recursive_mirror(value): + """Mirror strings(.L/.R) in any mixed structure of dictionaries/lists.""" + + if isinstance(value, dict): + return { key: recursive_mirror(val) for key, val in value.items() } + + elif isinstance(value, list): + return [recursive_mirror(elem) for elem in value] + + elif isinstance(value, str): + return mirror_name(value) + + else: + return value + + +def copy_rigify_params(from_bone: bpy.types.PoseBone, to_bone: bpy.types.PoseBone, *, match_type=False, x_mirror=False) -> bool: + rig_type = to_bone.rigify_type + if match_type and to_bone.rigify_type != from_bone.rigify_type: + return False + else: + rig_type = to_bone.rigify_type = get_rigify_type(from_bone) + + from_params = from_bone.get('rigify_parameters') + if from_params and rig_type: + param_dict = property_to_python(from_params) + if x_mirror: + param_dict = recursive_mirror(param_dict) + to_bone['rigify_parameters'] = param_dict + else: + try: + del to_bone['rigify_parameters'] + except KeyError: + pass + return True + + +class POSE_OT_rigify_mirror_parameters(bpy.types.Operator): + """Mirror Rigify type and parameters of selected bones to the opposite side. Names should end in L/R""" + + bl_idname = "pose.rigify_mirror_parameters" + bl_label = "Mirror Rigify Parameters" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + obj = context.object + if not obj or obj.type != 'ARMATURE' or obj.mode != 'POSE': + return False + sel_bones = context.selected_pose_bones + if not sel_bones: + return False + for pb in sel_bones: + mirrored_name = mirror_name(pb.name) + if mirrored_name != pb.name and mirrored_name in obj.pose.bones: + return True + return False + + def execute(self, context): + rig = context.object + + num_mirrored = 0 + + # First make sure that all selected bones can be mirrored unambiguously. + for pb in context.selected_pose_bones: + flip_bone = rig.pose.bones.get(mirror_name(pb.name)) + if not flip_bone: + # Bones without an opposite will just be ignored. + continue + if flip_bone != pb and flip_bone.bone.select: + self.report( + {'ERROR'}, f"Bone {pb.name} selected on both sides, mirroring would be ambiguous, aborting. Only select the left or right side, not both!") + return {'CANCELLED'} + + # Then mirror the parameters. + for pb in context.selected_pose_bones: + flip_bone = rig.pose.bones.get(mirror_name(pb.name)) + if flip_bone == pb or not flip_bone: + # Bones without an opposite will just be ignored. + continue + + num_mirrored += copy_rigify_params(pb, flip_bone, match_type=False, x_mirror=True) + + self.report({'INFO'}, f"Mirrored parameters of {num_mirrored} bones.") + + return {'FINISHED'} + + +class POSE_OT_rigify_copy_parameters(bpy.types.Operator): + """Copy Rigify type and parameters from active to selected bones""" + + bl_idname = "pose.rigify_copy_parameters" + bl_label = "Copy Rigify Parameters to Selected" + bl_options = {'REGISTER', 'UNDO'} + + match_type: bpy.props.BoolProperty( + name = "Match Type", + description = "Only mirror rigify parameters to selected bones which have the same rigify type as the active bone", + default = False + ) + + @classmethod + def poll(cls, context): + obj = context.object + if not obj or obj.type != 'ARMATURE' or obj.mode != 'POSE': + return False + + active = context.active_pose_bone + if not active or not active.rigify_type: + return False + + select = context.selected_pose_bones + if len(select) < 2 or active not in select: + return False + + return True + + def execute(self, context): + active_bone = context.active_pose_bone + + num_copied = 0 + for pb in context.selected_pose_bones: + if pb == active_bone: + continue + num_copied += copy_rigify_params(active_bone, pb, match_type=self.match_type) + + self.report({'INFO'}, f"Copied {active_bone.rigify_type} parameters to {num_copied} bones.") + + return {'FINISHED'} + + +def draw_copy_mirror_ops(self, context): + layout = self.layout + if context.mode == 'POSE': + layout.separator() + op = layout.operator(POSE_OT_rigify_copy_parameters.bl_idname, + icon='DUPLICATE', text="Copy Only Parameters") + op.match_type = True + op = layout.operator(POSE_OT_rigify_copy_parameters.bl_idname, + icon='DUPLICATE', text="Copy Type & Parameters") + op.match_type = False + layout.operator(POSE_OT_rigify_mirror_parameters.bl_idname, + icon='MOD_MIRROR', text="Mirror Type & Parameters") + # ============================================= # Registration classes = ( POSE_OT_rigify_copy_single_parameter, + POSE_OT_rigify_mirror_parameters, + POSE_OT_rigify_copy_parameters ) @@ -122,8 +269,11 @@ def register(): for cls in classes: register_class(cls) + bpy.types.VIEW3D_MT_rigify.append(draw_copy_mirror_ops) def unregister(): from bpy.utils import unregister_class for cls in classes: unregister_class(cls) + + bpy.types.VIEW3D_MT_rigify.remove(draw_copy_mirror_ops) diff --git a/rigify/rigs/limbs/super_finger.py b/rigify/rigs/limbs/super_finger.py index b50d54f9..a6e329bb 100644 --- a/rigify/rigs/limbs/super_finger.py +++ b/rigify/rigs/limbs/super_finger.py @@ -174,6 +174,8 @@ class Rig(SimpleChainRig): bone.lock_rotation_w = True bone.lock_scale = True, True, True + ControlLayersOption.EXTRA_IK.assign_rig(self, [self.bones.ctrl.ik]) + @stage.configure_bones def configure_ik_control_properties(self): if self.make_ik: @@ -379,6 +381,7 @@ class Rig(SimpleChainRig): ) ControlLayersOption.TWEAK.add_parameters(params) + ControlLayersOption.EXTRA_IK.add_parameters(params) @classmethod def parameters_ui(self, layout, params): @@ -393,6 +396,9 @@ class Rig(SimpleChainRig): ControlLayersOption.TWEAK.parameters_ui(layout, params) + if params.make_extra_ik_control: + ControlLayersOption.EXTRA_IK.parameters_ui(layout, params) + ############################# # Finger FK to IK operator ## ############################# diff --git a/rigify/utils/layers.py b/rigify/utils/layers.py index bc5a8c56..27c67f0b 100644 --- a/rigify/utils/layers.py +++ b/rigify/utils/layers.py @@ -157,6 +157,12 @@ class ControlLayersOption: 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, + toggle_name="Extra IK Layers", + description="Layers for the optional IK controls to be on", +) + # 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") diff --git a/rigify/utils/mechanism.py b/rigify/utils/mechanism.py index 00aef154..0b5c8d93 100644 --- a/rigify/utils/mechanism.py +++ b/rigify/utils/mechanism.py @@ -463,14 +463,20 @@ def reactivate_custom_properties(obj): def copy_custom_properties(src, dest, *, prefix='', dest_prefix='', link_driver=False, overridable=True): """Copy custom properties with filtering by prefix. Optionally link using drivers.""" res = [] - exclude = {'rigify_parameters', 'rigify_type'} + + # Exclude addon-defined properties. + exclude = {prop.identifier for prop in src.bl_rna.properties if prop.is_runtime} for key, value in src.items(): if key.startswith(prefix) and key not in exclude: new_key = dest_prefix + key[len(prefix):] - ui_data_src = src.id_properties_ui(key) - + try: + ui_data_src = src.id_properties_ui(key) + except TypeError: + # Some property types, eg. Python dictionaries + # don't support id_properties_ui. + continue if src != dest or new_key != key: dest[new_key] = value diff --git a/space_view3d_copy_attributes.py b/space_view3d_copy_attributes.py index 29da77ab..9496ed1d 100644 --- a/space_view3d_copy_attributes.py +++ b/space_view3d_copy_attributes.py @@ -20,11 +20,11 @@ bl_info = { "name": "Copy Attributes Menu", - "author": "Bassam Kurdali, Fabian Fricke, Adam Wiseman", - "version": (0, 4, 9), - "blender": (2, 80, 0), + "author": "Bassam Kurdali, Fabian Fricke, Adam Wiseman, Demeter Dzadik", + "version": (0, 5, 0), + "blender": (3, 0, 0), "location": "View3D > Ctrl-C", - "description": "Copy Attributes Menu from Blender 2.4", + "description": "Copy Attributes Menu", "doc_url": "{BLENDER_MANUAL_URL}/addons/interface/copy_attributes.html", "category": "Interface", } @@ -248,6 +248,79 @@ def pose_invoke_func(self, context, event): return {'RUNNING_MODAL'} +CustomPropSelectionBoolsProperty = BoolVectorProperty( + size=32, + options={'SKIP_SAVE'} +) + +class CopySelection: + """Base class for copying properties from active to selected based on a selection.""" + + selection: CustomPropSelectionBoolsProperty + + def draw_bools(self, button_names): + """Draws the boolean toggle list with a list of strings for the button texts.""" + layout = self.layout + for idx, name in enumerate(button_names): + layout.prop(self, "selection", index=idx, text=name, + toggle=True) + +def copy_custom_property(source, destination, prop_name): + """Copy a custom property called prop_name, from source to destination. + source and destination must be a Blender data type that can hold custom properties. + For a list of such data types, see: + https://docs.blender.org/manual/en/latest/files/data_blocks.html#files-data-blocks-custom-properties + """ + + # Create the property. + destination[prop_name] = source[prop_name] + # Copy the settings of the property. + try: + dst_prop_manager = destination.id_properties_ui(prop_name) + except TypeError: + # Python values like lists or dictionaries don't have any settings to copy. + # They just consist of a value and nothing else. + return + + src_prop_manager = source.id_properties_ui(prop_name) + assert src_prop_manager, f'Property "{prop_name}" not found in {source}' + + dst_prop_manager.update_from(src_prop_manager) + + # Copy the Library Overridable flag, which is stored elsewhere. + prop_rna_path = f'["{prop_name}"]' + is_lib_overridable = source.is_property_overridable_library(prop_rna_path) + destination.property_overridable_library_set(prop_rna_path, is_lib_overridable) + +class CopyCustomProperties(CopySelection): + """Base class for copying a selection of custom properties.""" + + def copy_selected_custom_props(self, active, selected): + keys = list(active.keys()) + for item in selected: + if item == active: + continue + for index, is_selected in enumerate(self.selection): + if is_selected: + copy_custom_property(active, item, keys[index]) + +class CopySelectedBoneCustomProperties(CopyCustomProperties, Operator): + """Copy Chosen custom properties from active to selected""" + bl_idname = "pose.copy_selected_custom_props" + bl_label = "Copy Selected Custom Properties" + bl_options = {'REGISTER', 'UNDO'} + + poll = pose_poll_func + invoke = pose_invoke_func + + def draw(self, context): + self.draw_bools(context.active_pose_bone.keys()) + + def execute(self, context): + self.copy_selected_custom_props(context.active_pose_bone, context.selected_pose_bones) + return {'FINISHED'} + + class CopySelectedPoseConstraints(Operator): """Copy Chosen constraints from active to selected""" bl_idname = "pose.copy_selected_constraints" @@ -291,6 +364,7 @@ class VIEW3D_MT_posecopypopup(Menu): for op in pose_copies: layout.operator("pose.copy_" + op[0]) layout.operator("pose.copy_selected_constraints") + layout.operator("pose.copy_selected_custom_props") layout.operator("pose.copy", text="copy pose") @@ -614,6 +688,22 @@ class CopySelectedObjectModifiers(Operator): return{'FINISHED'} +class CopySelectedObjectCustomProperties(CopyCustomProperties, Operator): + """Copy Chosen custom properties from active to selected objects""" + bl_idname = "object.copy_selected_custom_props" + bl_label = "Copy Selected Custom Properties" + bl_options = {'REGISTER', 'UNDO'} + + poll = object_poll_func + invoke = object_invoke_func + + def draw(self, context): + self.draw_bools(context.object.keys()) + + def execute(self, context): + self.copy_selected_custom_props(context.object, context.selected_objects) + return {'FINISHED'} + object_ops = [] genops(object_copies, object_ops, "object.copy_", object_poll_func, obLoopExec) @@ -638,6 +728,7 @@ class VIEW3D_MT_copypopup(Menu): layout.operator("object.copy_" + op[0]) layout.operator("object.copy_selected_constraints") layout.operator("object.copy_selected_modifiers") + layout.operator("object.copy_selected_custom_props") # Begin Mesh copy settings: @@ -810,9 +901,11 @@ class MESH_OT_CopyFaceSettings(Operator): classes = ( CopySelectedPoseConstraints, + CopySelectedBoneCustomProperties, VIEW3D_MT_posecopypopup, CopySelectedObjectConstraints, CopySelectedObjectModifiers, + CopySelectedObjectCustomProperties, VIEW3D_MT_copypopup, MESH_MT_CopyFaceSettings, MESH_MT_CopyUVCoordsFromLayer, |