diff options
-rw-r--r-- | rigify/__init__.py | 4 | ||||
-rw-r--r-- | rigify/generate.py | 58 | ||||
-rw-r--r-- | rigify/rigs/widgets.py | 8 | ||||
-rw-r--r-- | rigify/ui.py | 1 | ||||
-rw-r--r-- | rigify/utils/widgets.py | 101 |
5 files changed, 129 insertions, 43 deletions
diff --git a/rigify/__init__.py b/rigify/__init__.py index dd55be04..4b926c48 100644 --- a/rigify/__init__.py +++ b/rigify/__init__.py @@ -530,6 +530,10 @@ def register(): description="Forces Rigify to delete and rebuild all the rig widgets. if unset, only missing widgets will be created", default=False) + bpy.types.Armature.rigify_mirror_widgets = BoolProperty(name="Mirror Widgets", + description="Make widgets for left and right side bones linked duplicates with negative X scale for the right side, based on bone name symmetry", + default=True) + bpy.types.Armature.rigify_target_rig = PointerProperty(type=bpy.types.Object, name="Rigify Target Rig", description="Defines which rig to overwrite. If unset, a new one called 'rig' will be created", diff --git a/rigify/generate.py b/rigify/generate.py index ad8f43b5..a2d9a5d1 100644 --- a/rigify/generate.py +++ b/rigify/generate.py @@ -25,7 +25,7 @@ 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 +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 @@ -165,27 +165,44 @@ class Generator(base_generate.BaseGenerator): for obj in list(old_collection.objects): bpy.data.objects.remove(obj) - # Rename widgets and collection if renaming - if self.rig_old_name: - old_prefix = WGT_PREFIX + self.rig_old_name + "_" - new_prefix = WGT_PREFIX + self.obj.name + "_" + # Rename the collection + old_collection.name = new_group_name - for obj in list(old_collection.objects): - if obj.name.startswith(old_prefix): - new_name = new_prefix + obj.name[len(old_prefix):] - elif obj.name == wgts_group_name: - new_name = new_group_name - else: - continue + # Create/find widget collection + self.widget_collection = ensure_widget_collection(self.context, new_group_name) + self.use_mirror_widgets = self.metarig.data.rigify_mirror_widgets - obj.data.name = new_name - obj.name = new_name + # Build tables for existing widgets + self.old_widget_table = {} + self.new_widget_table = {} + self.widget_mirror_mesh = {} - old_collection.name = new_group_name + if not self.metarig.data.rigify_force_widget_update and self.obj.pose: + # Find all widgets from the collection referenced by the old rig + known_widgets = set(obj.name for obj in self.widget_collection.objects) - # Create/find widget collection - self.widget_collection = ensure_widget_collection(self.context, new_group_name) - self.wgts_group_name = new_group_name + for bone in self.obj.pose.bones: + if bone.custom_shape and bone.custom_shape.name in known_widgets: + self.old_widget_table[bone.name] = bone.custom_shape + + # Rename widgets in case the rig was renamed + name_prefix = WGT_PREFIX + self.obj.name + "_" + + for bone_name, widget in self.old_widget_table.items(): + old_data_name = change_name_side(widget.name, get_name_side(widget.data.name)) + + widget.name = name_prefix + bone_name + + # 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)) + + # 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: + self.widget_mirror_mesh[mid_name] = widget.data def __duplicate_rig(self): @@ -373,6 +390,11 @@ class Generator(base_generate.BaseGenerator): # Assign shapes to bones # Object's with name WGT-<bone_name> get used as that bone's shape. for bone in self.obj.pose.bones: + # First check the table built by create_widget + if bone.name in self.new_widget_table: + bone.custom_shape = self.new_widget_table[bone.name] + continue + # Object names are limited to 63 characters... arg wgt_name = (WGT_PREFIX + self.obj.name + '_' + bone.name)[:63] diff --git a/rigify/rigs/widgets.py b/rigify/rigs/widgets.py index ef0bb544..0c434593 100644 --- a/rigify/rigs/widgets.py +++ b/rigify/rigs/widgets.py @@ -97,7 +97,7 @@ def create_ikarrow_widget(rig, bone_name, size=1.0, bone_transform_name=None, ro def create_hand_widget(rig, bone_name, size=1.0, bone_transform_name=None): # Create hand widget - obj = create_widget(rig, bone_name, bone_transform_name) + obj = create_widget(rig, bone_name, bone_transform_name, subsurf=2) if obj is not None: verts = [(0.0*size, 1.5*size, -0.7000000476837158*size), (1.1920928955078125e-07*size, -0.25*size, -0.6999999284744263*size), (0.0*size, -0.25*size, 0.7000000476837158*size), (-1.1920928955078125e-07*size, 1.5*size, 0.6999999284744263*size), (5.960464477539063e-08*size, 0.7229999899864197*size, -0.699999988079071*size), (-5.960464477539063e-08*size, 0.7229999899864197*size, 0.699999988079071*size), (1.1920928955078125e-07*size, -2.9802322387695312e-08*size, -0.699999988079071*size), (0.0*size, 2.9802322387695312e-08*size, 0.699999988079071*size), ] edges = [(1, 2), (0, 3), (0, 4), (3, 5), (4, 6), (1, 6), (5, 7), (2, 7)] @@ -107,8 +107,6 @@ def create_hand_widget(rig, bone_name, size=1.0, bone_transform_name=None): mesh.from_pydata(verts, edges, faces) mesh.update() - mod = obj.modifiers.new("subsurf", 'SUBSURF') - mod.levels = 2 return obj else: return None @@ -116,7 +114,7 @@ def create_hand_widget(rig, bone_name, size=1.0, bone_transform_name=None): def create_foot_widget(rig, bone_name, size=1.0, bone_transform_name=None): # Create hand widget - obj = create_widget(rig, bone_name, bone_transform_name) + obj = create_widget(rig, bone_name, bone_transform_name, subsurf=2) if obj is not None: verts = [(-0.6999998688697815*size, -0.5242648720741272*size, 0.0*size), (-0.7000001072883606*size, 1.2257349491119385*size, 0.0*size), (0.6999998688697815*size, 1.2257351875305176*size, 0.0*size), (0.7000001072883606*size, -0.5242648720741272*size, 0.0*size), (-0.6999998688697815*size, 0.2527350187301636*size, 0.0*size), (0.7000001072883606*size, 0.2527352571487427*size, 0.0*size), (-0.7000001072883606*size, 0.975735068321228*size, 0.0*size), (0.6999998688697815*size, 0.9757352471351624*size, 0.0*size), ] edges = [(1, 2), (0, 3), (0, 4), (3, 5), (4, 6), (1, 6), (5, 7), (2, 7), ] @@ -126,8 +124,6 @@ def create_foot_widget(rig, bone_name, size=1.0, bone_transform_name=None): mesh.from_pydata(verts, edges, faces) mesh.update() - mod = obj.modifiers.new("subsurf", 'SUBSURF') - mod.levels = 2 return obj else: return None diff --git a/rigify/ui.py b/rigify/ui.py index c9592677..c801ac25 100644 --- a/rigify/ui.py +++ b/rigify/ui.py @@ -177,6 +177,7 @@ class DATA_PT_rigify_buttons(bpy.types.Panel): if armature_id_store.rigify_generate_mode == 'new': row.enabled = False + col.prop(armature_id_store, "rigify_mirror_widgets") col.prop(armature_id_store, "rigify_finalize_script", text="Run Script") elif obj.mode == 'EDIT': diff --git a/rigify/utils/widgets.py b/rigify/utils/widgets.py index a8691349..3f0bf252 100644 --- a/rigify/utils/widgets.py +++ b/rigify/utils/widgets.py @@ -28,6 +28,7 @@ from itertools import count from .errors import MetarigError from .collections import ensure_widget_collection +from .naming import change_name_side, get_name_side, Side WGT_PREFIX = "WGT-" # Prefix for widget objects @@ -56,47 +57,113 @@ def obj_to_bone(obj, rig, bone_name, bone_transform_name=None): elif bone.custom_shape_transform: bone = bone.custom_shape_transform - shape_mat = Matrix.Translation(loc) @ (Euler(rot).to_matrix() @ Matrix.Diagonal(scale)).to_4x4() + shape_mat = Matrix.LocRotScale(loc, Euler(rot), scale) obj.rotation_mode = 'XYZ' 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): +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. """ assert rig.mode != 'EDIT' - obj_name = widget_name or WGT_PREFIX + rig.name + '_' + bone_name + from ..base_generate import BaseGenerator + scene = bpy.context.scene - collection = ensure_widget_collection(bpy.context, 'WGTS_' + rig.name) + bone = rig.pose.bones[bone_name] + + # Access the current generator instance when generating (ugh, globals) + generator = BaseGenerator.instance + + if generator: + collection = generator.widget_collection + else: + collection = ensure_widget_collection(bpy.context, 'WGTS_' + rig.name) + + use_mirror = generator and generator.use_mirror_widgets + + if use_mirror: + bone_mid_name = change_name_side(bone_name, Side.MIDDLE) + + obj_name = widget_name or WGT_PREFIX + rig.name + '_' + bone_name reuse_mesh = None # Check if it already exists in the scene if not widget_force_new: - if obj_name in scene.objects: + obj = None + + if generator: + # Check if the widget was already generated + if bone_name in generator.new_widget_table: + return None + + # If re-generating, check widgets used by the previous rig + obj = generator.old_widget_table.get(bone_name) + + if not obj: + # Search the scene by name + obj = scene.objects.get(obj_name) + + if obj: + # Record the generated widget + if generator: + generator.new_widget_table[bone_name] = obj + + # Re-add to the collection if not there for some reason + if obj.name not in collection.objects: + collection.objects.link(obj) + + # Flip scale for originally mirrored widgets + if obj.scale.x < 0 and bone.custom_shape_scale_xyz.x > 0: + bone.custom_shape_scale_xyz.x *= -1 + # Move object to bone position, in case it changed - obj = scene.objects[obj_name] obj_to_bone(obj, rig, bone_name, bone_transform_name) return None - # Delete object if it exists in blend data but not scene data. - # This is necessary so we can then create the object without - # name conflicts. - if obj_name in bpy.data.objects: - bpy.data.objects.remove(bpy.data.objects[obj_name]) - # Create a linked duplicate of the widget assigned in the metarig reuse_widget = rig.pose.bones[bone_name].custom_shape if reuse_widget: + subsurf = 0 reuse_mesh = reuse_widget.data - # Create mesh object - mesh = reuse_mesh or bpy.data.meshes.new(obj_name) + # Create a linked duplicate with the mirror widget + if not reuse_mesh and use_mirror and bone_mid_name != bone_name: + reuse_mesh = generator.widget_mirror_mesh.get(bone_mid_name) + + # Create an empty mesh datablock if not linking + if reuse_mesh: + mesh = reuse_mesh + + elif use_mirror and bone_mid_name != bone_name: + # When mirroring, untag side from mesh name, and remember it + mesh = bpy.data.meshes.new(change_name_side(obj_name, Side.MIDDLE)) + + generator.widget_mirror_mesh[bone_mid_name] = mesh + + else: + mesh = bpy.data.meshes.new(obj_name) + + # Create the object obj = bpy.data.objects.new(obj_name, mesh) collection.objects.link(obj) + # Add the subdivision surface modifier + if subsurf > 0: + mod = obj.modifiers.new("subsurf", 'SUBSURF') + mod.levels = subsurf + + # Record the generated widget + if generator: + generator.new_widget_table[bone_name] = obj + + # Flip scale for right side if mirroring widgets + if use_mirror and get_name_side(bone_name) == Side.RIGHT: + if bone.custom_shape_scale_xyz.x > 0: + bone.custom_shape_scale_xyz.x *= -1 + # Move object to bone position and set layers obj_to_bone(obj, rig, bone_name, bone_transform_name) @@ -187,7 +254,7 @@ def widget_generator(generate_func=None, *, register=None, subsurf=0): """ @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) + 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() @@ -197,10 +264,6 @@ def widget_generator(generate_func=None, *, register=None, subsurf=0): mesh.from_pydata(geom.verts, geom.edges, geom.faces) mesh.update() - if subsurf: - mod = obj.modifiers.new("subsurf", 'SUBSURF') - mod.levels = subsurf - return obj else: return None |