diff --git a/rigify/__init__.py b/rigify/__init__.py index 019adb48f6f90dfb1faa3c54a457d82ae4c33875..20e18a42224d39d38fc0b89c137276a00f14533a 100644 --- a/rigify/__init__.py +++ b/rigify/__init__.py @@ -512,20 +512,6 @@ def register(): IDStore.rigify_types = CollectionProperty(type=RigifyName) IDStore.rigify_active_type = IntProperty(name="Rigify Active Type", description="The selected rig type") - bpy.types.Armature.rigify_advanced_generation = BoolProperty(name="Advanced Options", - description="Enables/disables advanced options for Rigify rig generation", - default=False) - - def update_mode(self, context): - if self.rigify_generate_mode == 'new': - self.rigify_force_widget_update = False - - bpy.types.Armature.rigify_generate_mode = EnumProperty(name="Rigify Generate Rig Mode", - description="'Generate Rig' mode. In 'overwrite' mode the features of the target rig will be updated as defined by the metarig. In 'new' mode a new rig will be created as defined by the metarig. Current mode", - update=update_mode, - items=( ('overwrite', 'overwrite', ''), - ('new', 'new', ''))) - bpy.types.Armature.rigify_force_widget_update = BoolProperty(name="Force Widget Update", description="Forces Rigify to delete and rebuild all the rig widgets. if unset, only missing widgets will be created", default=False) @@ -533,6 +519,9 @@ def register(): 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_widgets_collection = PointerProperty(type=bpy.types.Collection, + name="Widgets Collection", + description="Defines which collection to place widget objects in. If unset, a new one will be created based on the name of the rig") bpy.types.Armature.rigify_target_rig = PointerProperty(type=bpy.types.Object, name="Rigify Target Rig", @@ -546,11 +535,6 @@ def register(): bpy.types.Armature.rigify_finalize_script = PointerProperty(type=bpy.types.Text, name="Finalize Script", description="Run this script after generation to apply user-specific changes") - - bpy.types.Armature.rigify_rig_basename = StringProperty(name="Rigify Rig Name", - description="Defines the name of the Rig. If unset, in 'new' mode 'rig' will be used, in 'overwrite' mode the target rig name will be used", - default="") - IDStore.rigify_transfer_only_selected = BoolProperty( name="Transfer Only Selected", description="Transfer selected bones only", default=True) @@ -592,12 +576,9 @@ def unregister(): del ArmStore.rigify_colors_index del ArmStore.rigify_colors_lock del ArmStore.rigify_theme_to_add - del ArmStore.rigify_advanced_generation - del ArmStore.rigify_generate_mode del ArmStore.rigify_force_widget_update del ArmStore.rigify_target_rig del ArmStore.rigify_rig_ui - del ArmStore.rigify_rig_basename IDStore = bpy.types.WindowManager del IDStore.rigify_collection diff --git a/rigify/generate.py b/rigify/generate.py index 501c335fadfcaded7baaa6bc51a612d32de0f574..a674ade447c81ec714c786d62b0b01af1d017ce1 100644 --- a/rigify/generate.py +++ b/rigify/generate.py @@ -30,7 +30,7 @@ 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_widget_collection, list_layer_collections, filter_layer_collections_by_object +from .utils.collections import ensure_collection, list_layer_collections, filter_layer_collections_by_object from .utils.rig import get_rigify_type from . import base_generate @@ -55,9 +55,6 @@ class Generator(base_generate.BaseGenerator): self.id_store = context.window_manager - self.rig_new_name = "" - self.rig_old_name = "" - def find_rig_class(self, rig_type): rig_module = rig_lists.rigs[rig_type]["module"] @@ -76,55 +73,42 @@ class Generator(base_generate.BaseGenerator): self.collection = self.layer_collection.collection - def __create_rig_object(self): - scene = self.scene - id_store = self.id_store - meta_data = self.metarig.data - - # 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. + def ensure_rig_object(self) -> bpy.types.Object: + """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. + """ print("Fetch rig.") + meta_data = self.metarig.data - self.rig_new_name = name = meta_data.rigify_rig_basename or "rig" - - obj = None - - # Try existing object if overwriting - if meta_data.rigify_generate_mode == 'overwrite': - obj = meta_data.rigify_target_rig - - if obj: - self.rig_old_name = obj.name - - obj.name = name - obj.data.name = obj.name - - elif name in bpy.data.objects: - obj = bpy.data.objects[name] + target_rig = meta_data.rigify_target_rig + if not target_rig: + if "metarig" in self.metarig.name: + rig_new_name = self.metarig.name.replace("metarig", "rig") + elif "META" in self.metarig.name: + rig_new_name = self.metarig.name.replace("META", "RIG") + else: + rig_new_name = "RIG-" + self.metarig.name - # Create a new object if not found - if not obj: - obj = bpy.data.objects.new(name, bpy.data.armatures.new(name)) - obj.display_type = 'WIRE' + target_rig = bpy.data.objects.new(rig_new_name, bpy.data.armatures.new(rig_new_name)) + target_rig.display_type = 'WIRE' # If the object is already added to the scene, switch to its collection - if obj.name in self.context.scene.collection.all_objects: - self.__switch_to_usable_collection(obj) + if target_rig.name in self.context.scene.collection.all_objects: + self.__switch_to_usable_collection(target_rig) 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): self.__switch_to_usable_collection(self.metarig, True) - self.collection.objects.link(obj) + self.collection.objects.link(target_rig) # Configure and remember the object - meta_data.rigify_target_rig = obj - obj.data.pose_position = 'POSE' + meta_data.rigify_target_rig = target_rig + target_rig.data.pose_position = 'POSE' - self.obj = obj - return obj + return target_rig def __unhide_rig_object(self, obj): @@ -144,11 +128,11 @@ class Generator(base_generate.BaseGenerator): raise Exception('Could not generate: Could not find a usable collection.') - def __create_widget_group(self): - new_group_name = "WGTS_" + self.obj.name - wgts_group_name = "WGTS_" + (self.rig_old_name or self.obj.name) - - # Find the old widgets 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) if not old_collection: @@ -160,16 +144,22 @@ class Generator(base_generate.BaseGenerator): old_collection = legacy_collection if old_collection: - # Remove widgets if force update is set - if self.metarig.data.rigify_force_widget_update: - for obj in list(old_collection.objects): - bpy.data.objects.remove(obj) - # Rename the collection - old_collection.name = new_group_name + old_collection.name = wgts_group_name + + return old_collection + def ensure_widget_collection(self): # Create/find widget collection - self.widget_collection = ensure_widget_collection(self.context, new_group_name) + self.widget_collection = self.metarig.data.rigify_widgets_collection + 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) + + self.metarig.data.rigify_widgets_collection = self.widget_collection + self.use_mirror_widgets = self.metarig.data.rigify_mirror_widgets # Build tables for existing widgets @@ -177,7 +167,11 @@ class Generator(base_generate.BaseGenerator): self.new_widget_table = {} self.widget_mirror_mesh = {} - if not self.metarig.data.rigify_force_widget_update and self.obj.pose: + if self.metarig.data.rigify_force_widget_update: + # Remove widgets if force update is set + for obj in list(self.widget_collection.objects): + bpy.data.objects.remove(obj) + elif 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) @@ -430,7 +424,7 @@ class Generator(base_generate.BaseGenerator): #------------------------------------------ # Create/find the rig object and set it up - obj = self.__create_rig_object() + self.obj = obj = self.ensure_rig_object() self.__unhide_rig_object(obj) @@ -446,8 +440,8 @@ class Generator(base_generate.BaseGenerator): select_object(context, obj, deselect_all=True) #------------------------------------------ - # Create Group widget - self.__create_widget_group() + # Create Widget Collection + self.ensure_widget_collection() t.tick("Create main WGTS: ") diff --git a/rigify/rig_ui_template.py b/rigify/rig_ui_template.py index c83dd02b9302c11f9ee7a596d1f349270c03e5f3..8780461de0162354e3033df13f306af29cddc410 100644 --- a/rigify/rig_ui_template.py +++ b/rigify/rig_ui_template.py @@ -1167,27 +1167,10 @@ class ScriptGenerator(base_generate.GeneratorPlugin): layer_layout += [(l.name, l.row)] # Generate the UI script - if metarig.data.rigify_rig_basename: - rig_ui_name = metarig.data.rigify_rig_basename + '_ui.py' - else: - rig_ui_name = 'rig_ui.py' - - script = None - - if metarig.data.rigify_generate_mode == 'overwrite': - script = metarig.data.rigify_rig_ui - - if not script and rig_ui_name in bpy.data.texts: - script = bpy.data.texts[rig_ui_name] - - if script: - script.clear() - script.name = rig_ui_name - - if script is None: - script = bpy.data.texts.new(rig_ui_name) - - metarig.data.rigify_rig_ui = script + script = metarig.data.rigify_rig_ui + if not script: + script = bpy.data.texts.new("rig_ui.py") + metarig.data.rigify_rig_ui = script for s in OrderedDict.fromkeys(self.ui_imports): script.write(s + "\n") diff --git a/rigify/ui.py b/rigify/ui.py index c801ac25c1ae3e4181c1ae2744805615203d5e7c..59dbf9b60809e47dbbb13226d467757915b20d8b 100644 --- a/rigify/ui.py +++ b/rigify/ui.py @@ -60,27 +60,27 @@ def build_type_list(context, rigify_types): a.name = r -class DATA_PT_rigify_buttons(bpy.types.Panel): - bl_label = "Rigify Buttons" +class DATA_PT_rigify_generate(bpy.types.Panel): + bl_label = "Rigify Generation" bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = "data" @classmethod def poll(cls, context): + obj = context.object if not context.object: return False - return context.object.type == 'ARMATURE' and context.active_object.data.get("rig_id") is None + return obj.type == 'ARMATURE' \ + and obj.data.get("rig_id") is None \ + and obj.mode in {'POSE', 'OBJECT'} def draw(self, context): C = context layout = self.layout - obj = context.object - id_store = C.window_manager + obj = C.object if obj.mode in {'POSE', 'OBJECT'}: - armature_id_store = C.object.data - WARNING = "Warning: Some features may change after generation" show_warning = False show_update_metarig = False @@ -110,7 +110,7 @@ class DATA_PT_rigify_buttons(bpy.types.Panel): if show_warning: layout.label(text=WARNING, icon='ERROR') - enable_generate_and_advanced = not (show_not_updatable or show_update_metarig) + enable_generate = not (show_not_updatable or show_update_metarig) if show_not_updatable: layout.label(text="WARNING: This metarig contains deprecated rigify rig-types and cannot be upgraded automatically.", icon='ERROR') @@ -131,71 +131,74 @@ class DATA_PT_rigify_buttons(bpy.types.Panel): col.separator() row = col.row() - row.operator("pose.rigify_generate", text="Generate Rig", icon='POSE_HLT') + text = "Re-Generate Rig" if obj.data.rigify_target_rig else "Generate Rig" + row.operator("pose.rigify_generate", text=text, icon='POSE_HLT') + row.enabled = enable_generate - row.enabled = enable_generate_and_advanced - if armature_id_store.rigify_advanced_generation: - icon = 'UNLOCKED' - else: - icon = 'LOCKED' +class DATA_PT_rigify_generate_advanced(bpy.types.Panel): + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = "data" + bl_label = "Advanced" + bl_parent_id = 'DATA_PT_rigify_generate' + bl_options = {'DEFAULT_CLOSED'} - col = layout.column() - col.enabled = enable_generate_and_advanced - row = col.row() - row.prop(armature_id_store, "rigify_advanced_generation", toggle=True, icon=icon) + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False - if armature_id_store.rigify_advanced_generation: + armature_id_store = context.object.data - row = col.row(align=True) - row.prop(armature_id_store, "rigify_generate_mode", expand=True) - - main_row = col.row(align=True).split(factor=0.3) - col1 = main_row.column() - col2 = main_row.column() - col1.label(text="Rig Name") - row = col1.row() - row.label(text="Target Rig") - row.enabled = (armature_id_store.rigify_generate_mode == "overwrite") - row = col1.row() - row.label(text="Target UI") - row.enabled = (armature_id_store.rigify_generate_mode == "overwrite") - - row = col2.row(align=True) - row.prop(armature_id_store, "rigify_rig_basename", text="", icon="SORTALPHA") - - row = col2.row(align=True) - row.prop(armature_id_store, "rigify_target_rig", text="") - row.enabled = (armature_id_store.rigify_generate_mode == "overwrite") - - row = col2.row() - row.prop(armature_id_store, "rigify_rig_ui", text="", icon='TEXT') - row.enabled = (armature_id_store.rigify_generate_mode == "overwrite") - - row = col.row() - row.prop(armature_id_store, "rigify_force_widget_update") - 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': - # Build types list - build_type_list(context, id_store.rigify_types) - - if id_store.rigify_active_type > len(id_store.rigify_types): - id_store.rigify_active_type = 0 - - # Rig type list - if len(feature_set_list.get_installed_list()) > 0: - row = layout.row() - row.prop(context.object.data, "active_feature_set") + col = layout.column() + col.row().prop(armature_id_store, "rigify_target_rig", text="Target Rig") + col.row().prop(armature_id_store, "rigify_rig_ui", text="Rig UI Script") + col.separator() + col.row().prop(armature_id_store, "rigify_widgets_collection") + col.row().prop(armature_id_store, "rigify_force_widget_update") + col.row().prop(armature_id_store, "rigify_mirror_widgets") + col.separator() + col.row().prop(armature_id_store, "rigify_finalize_script", text="Run Script") + + +class DATA_PT_rigify_samples(bpy.types.Panel): + bl_label = "Rigify Samples" + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = "data" + + @classmethod + def poll(cls, context): + obj = context.object + if not obj: + return False + return obj.type == 'ARMATURE' \ + and obj.data.get("rig_id") is None \ + and obj.mode == 'EDIT' + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + obj = context.object + id_store = context.window_manager + + # Build types list + build_type_list(context, id_store.rigify_types) + + if id_store.rigify_active_type > len(id_store.rigify_types): + id_store.rigify_active_type = 0 + + # Rig type list + if len(feature_set_list.get_installed_list()) > 0: row = layout.row() - row.template_list("UI_UL_list", "rigify_types", id_store, "rigify_types", id_store, 'rigify_active_type') + row.prop(context.object.data, "active_feature_set") + row = layout.row() + row.template_list("UI_UL_list", "rigify_types", id_store, "rigify_types", id_store, 'rigify_active_type') - props = layout.operator("armature.metarig_sample_add", text="Add sample") - props.metarig_type = id_store.rigify_types[id_store.rigify_active_type].name + props = layout.operator("armature.metarig_sample_add", text="Add sample") + props.metarig_type = id_store.rigify_types[id_store.rigify_active_type].name class DATA_PT_rigify_layer_names(bpy.types.Panel): @@ -791,8 +794,9 @@ class Generate(bpy.types.Operator): return is_metarig(context.object) def execute(self, context): + metarig = context.object try: - generate.generate_rig(context, context.object) + generate.generate_rig(context, metarig) except MetarigError as rig_exception: import traceback traceback.print_exc() @@ -803,6 +807,8 @@ class Generate(bpy.types.Operator): traceback.print_exc() self.report({'ERROR'}, 'Generation has thrown an exception: ' + str(rig_exception)) + else: + self.report({'INFO'}, 'Successfully generated: "' + metarig.data.rigify_target_rig.name + '"') finally: bpy.ops.object.mode_set(mode='OBJECT') @@ -930,8 +936,10 @@ class VIEW3D_MT_rigify(bpy.types.Menu): def draw(self, context): layout = self.layout + obj = context.object - layout.operator(Generate.bl_idname, text="Generate") + text = "Re-Generate Rig" if obj.data.rigify_target_rig else "Generate Rig" + layout.operator(Generate.bl_idname, text=text) if context.mode == 'EDIT_ARMATURE': layout.separator() @@ -1381,7 +1389,9 @@ classes = ( DATA_MT_rigify_bone_groups_context_menu, DATA_PT_rigify_bone_groups, DATA_PT_rigify_layer_names, - DATA_PT_rigify_buttons, + DATA_PT_rigify_generate, + DATA_PT_rigify_generate_advanced, + DATA_PT_rigify_samples, BONE_PT_rigify_buttons, VIEW3D_PT_rigify_animation_tools, VIEW3D_PT_tools_rigify_dev, diff --git a/rigify/utils/collections.py b/rigify/utils/collections.py index a172b9849f4c9b83d26b9e3eea7f4acab45ba7f2..a19b9da1d9ac9668dba757a420ee52890ae1e3f5 100644 --- a/rigify/utils/collections.py +++ b/rigify/utils/collections.py @@ -19,9 +19,6 @@ # <pep8 compliant> import bpy -import math - -from .errors import MetarigError #============================================= @@ -65,30 +62,32 @@ def filter_layer_collections_by_object(layer_collections, obj): return [lc for lc in layer_collections if obj in lc.collection.objects.values()] -def ensure_widget_collection(context, wgts_collection_name): +def ensure_collection(context, collection_name, hidden=False) -> bpy.types.Collection: + """Check if a collection with a certain name exists. + If yes, return it, if not, create it in the scene root collection. + """ view_layer = context.view_layer - layer_collection = bpy.context.layer_collection - collection = layer_collection.collection + active_layer_coll = bpy.context.layer_collection + active_collection = active_layer_coll.collection - widget_collection = bpy.data.collections.get(wgts_collection_name) - if not widget_collection: - # ------------------------------------------ - # Create the widget collection - widget_collection = bpy.data.collections.new(wgts_collection_name) - widget_collection.hide_viewport = True - widget_collection.hide_render = True + collection = bpy.data.collections.get(collection_name) + if not collection: + # Create the collection + collection = bpy.data.collections.new(collection_name) + collection.hide_viewport = hidden + collection.hide_render = hidden - widget_layer_collection = None + layer_collection = None else: - widget_layer_collection = find_layer_collection_by_collection(view_layer.layer_collection, widget_collection) + layer_collection = find_layer_collection_by_collection(view_layer.layer_collection, collection) - if not widget_layer_collection: - # Add the widget collection to the tree - collection.children.link(widget_collection) - widget_layer_collection = [c for c in layer_collection.children if c.collection == widget_collection][0] + if not layer_collection: + # Let the new collection be a child of the active one. + active_collection.children.link(collection) + layer_collection = [c for c in active_layer_coll.children if c.collection == collection][0] - widget_layer_collection.exclude = True + layer_collection.exclude = True - # Make the widget the active collection for the upcoming added (widget) objects - view_layer.active_layer_collection = widget_layer_collection - return widget_collection + # Make the new collection active. + view_layer.active_layer_collection = layer_collection + return collection diff --git a/rigify/utils/widgets.py b/rigify/utils/widgets.py index 3f0bf25224a67ec5cc15319c41b5533d8432d56e..b3715f807a3bb72c6fff0e90fec0a4c6e92d651a 100644 --- a/rigify/utils/widgets.py +++ b/rigify/utils/widgets.py @@ -27,7 +27,7 @@ from mathutils import Matrix, Vector, Euler from itertools import count from .errors import MetarigError -from .collections import ensure_widget_collection +from .collections import ensure_collection from .naming import change_name_side, get_name_side, Side WGT_PREFIX = "WGT-" # Prefix for widget objects @@ -79,7 +79,7 @@ def create_widget(rig, bone_name, bone_transform_name=None, *, widget_name=None, if generator: collection = generator.widget_collection else: - collection = ensure_widget_collection(bpy.context, 'WGTS_' + rig.name) + collection = ensure_collection(bpy.context, 'WGTS_' + rig.name, hidden=True) use_mirror = generator and generator.use_mirror_widgets