diff options
author | Peter Kim <pk15950@gmail.com> | 2021-09-23 05:19:40 +0300 |
---|---|---|
committer | Peter Kim <pk15950@gmail.com> | 2021-09-23 05:19:40 +0300 |
commit | a85360cbdfbbee2bb46bcb93900f597a989bd33b (patch) | |
tree | 23e6f38da0ef3c3e5147afd2ef8226d1b3a7bb8c | |
parent | 3433c06c4ca5253533c34a88955dabbcbb51f3a0 (diff) | |
parent | c0942837ab5a48482b1e901712f24cb1e1f04fac (diff) |
Merge branch 'master' into xr-controller-support
25 files changed, 203 insertions, 86 deletions
diff --git a/blenderkit/ui_panels.py b/blenderkit/ui_panels.py index e2b8d700..25fa5927 100644 --- a/blenderkit/ui_panels.py +++ b/blenderkit/ui_panels.py @@ -1836,8 +1836,8 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingsProperties): row.emboss = 'NONE' op = row.operator('wm.blenderkit_tooltip', text=str(s), icon_value=pcoll['trophy'].icon_id) - op.tooltip = 'Asset score calculated from averaged user ratings. \n\n' \ - 'Score = quality × complexity × 10*\n\n *Happiness multiplier' + op.tooltip = 'Asset score calculated from user ratings. \n\n' \ + 'Score = average quality × median complexity × 10*\n\n *Happiness multiplier' row.label(text=' ') tooltip_extension = f'.\n\nRatings results are shown for assets with more than {show_rating_threshold} ratings' @@ -1847,7 +1847,7 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingsProperties): row.label(text=' ') op = row.operator('wm.blenderkit_tooltip', text=str(c), icon_value=pcoll['dumbbell'].icon_id) - op.tooltip = f"Complexity, average from {rc['workingHours']} ratings" \ + op.tooltip = f"Complexity, median from {rc['workingHours']} ratings" \ f"{tooltip_extension if rcount <= show_rating_threshold else ''}" if rcount <= show_rating_prompt_threshold: @@ -2029,9 +2029,9 @@ class SetCategoryOperator(bpy.types.Operator): class ClosePopupButton(bpy.types.Operator): - """Visit subcategory""" + """Close popup window""" bl_idname = "view3d.close_popup_button" - bl_label = "BlenderKit close popup" + bl_label = "Close popup" bl_options = {'REGISTER', 'INTERNAL'} @classmethod diff --git a/blenderkit/utils.py b/blenderkit/utils.py index 096688be..42e3d331 100644 --- a/blenderkit/utils.py +++ b/blenderkit/utils.py @@ -692,7 +692,12 @@ def automap(target_object=None, target_slot=None, tex_size=1, bg_exception=False bpy.ops.object.material_slot_select() scale = (scale.x + scale.y + scale.z) / 3.0 + + if tex_size == 0:# prevent division by zero, it's possible to have 0 in tex size by unskilled uploaders + tex_size = 1 + if not just_scale: + bpy.ops.uv.cube_project( cube_size=scale * 2.0 / (tex_size), correct_aspect=False) # it's * 2.0 because blender can't tell size of a unit cube :) diff --git a/io_scene_fbx/__init__.py b/io_scene_fbx/__init__.py index 01bc5421..495e62d2 100644 --- a/io_scene_fbx/__init__.py +++ b/io_scene_fbx/__init__.py @@ -21,7 +21,7 @@ bl_info = { "name": "FBX format", "author": "Campbell Barton, Bastien Montagne, Jens Restemeier", - "version": (4, 23, 0), + "version": (4, 24, 0), "blender": (2, 90, 0), "location": "File > Import-Export", "description": "FBX IO meshes, UV's, vertex colors, materials, textures, cameras, lamps and actions", diff --git a/io_scene_fbx/fbx_utils.py b/io_scene_fbx/fbx_utils.py index c35399b2..3a421423 100644 --- a/io_scene_fbx/fbx_utils.py +++ b/io_scene_fbx/fbx_utils.py @@ -150,7 +150,8 @@ FBX_FRAMERATES = ( # ##### Misc utilities ##### -DO_PERFMON = True +# Enable performance reports (measuring time used to perform various steps of importing or exporting). +DO_PERFMON = False if DO_PERFMON: class PerfMon(): diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py index 08f5cbd1..9a89cfbe 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, 23), + "version": (1, 7, 28), 'blender': (2, 91, 0), 'location': 'File > Import-Export', 'description': 'Import-Export as glTF 2.0', diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_extract.py b/io_scene_gltf2/blender/exp/gltf2_blender_extract.py index dc508d18..f7318c2b 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_extract.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_extract.py @@ -387,7 +387,7 @@ def __get_positions(blender_mesh, key_blocks, armature, blender_object, export_s # Transform for skinning if armature and blender_object: - apply_matrix = armature.matrix_world.inverted() @ blender_object.matrix_world + apply_matrix = armature.matrix_world.inverted_safe() @ blender_object.matrix_world loc_transform = armature.matrix_world @ apply_matrix loc_transform = blender_object.matrix_world @@ -427,8 +427,8 @@ def __get_normals(blender_mesh, key_blocks, armature, blender_object, export_set # Transform for skinning if armature and blender_object: - apply_matrix = (armature.matrix_world.inverted() @ blender_object.matrix_world) - apply_matrix = apply_matrix.to_3x3().inverted().transposed() + apply_matrix = (armature.matrix_world.inverted_safe() @ blender_object.matrix_world) + apply_matrix = apply_matrix.to_3x3().inverted_safe().transposed() normal_transform = armature.matrix_world.to_3x3() @ apply_matrix normals[:] = __apply_mat_to_all(normal_transform, normals) @@ -463,7 +463,7 @@ def __get_tangents(blender_mesh, armature, blender_object, export_settings): # Transform for skinning if armature and blender_object: - apply_matrix = armature.matrix_world.inverted() @ blender_object.matrix_world + apply_matrix = armature.matrix_world.inverted_safe() @ blender_object.matrix_world tangent_transform = apply_matrix.to_quaternion().to_matrix() tangents = __apply_mat_to_all(tangent_transform, tangents) __normalize_vecs(tangents) @@ -482,7 +482,7 @@ def __get_bitangent_signs(blender_mesh, armature, blender_object, export_setting if armature and blender_object: # Bitangent signs should flip when handedness changes # TODO: confirm - apply_matrix = armature.matrix_world.inverted() @ blender_object.matrix_world + apply_matrix = armature.matrix_world.inverted_safe() @ blender_object.matrix_world tangent_transform = apply_matrix.to_quaternion().to_matrix() flipped = tangent_transform.determinant() < 0 if flipped: diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py index 932ea25e..df069f79 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py @@ -317,14 +317,14 @@ def gather_keyframes(blender_object_if_armature: typing.Optional[bpy.types.Objec # We can ignore this keyframes # if there are some fcurve, we can keep only 2 keyframes, first and last if blender_object_if_armature is not None: - std = np.ptp(np.ptp([[k.value[i] for i in range(len(keyframes[0].value))] for k in keyframes], axis=0)) + cst = all([j < 0.0001 for j in np.ptp([[k.value[i] for i in range(len(keyframes[0].value))] for k in keyframes], axis=0)]) if node_channel_is_animated is True: # fcurve on this bone for this property # Keep animation, but keep only 2 keyframes if data are not changing - return [keyframes[0], keyframes[-1]] if std < 0.0001 and len(keyframes) >= 2 else keyframes + return [keyframes[0], keyframes[-1]] if cst is True and len(keyframes) >= 2 else keyframes else: # bone is not animated (no fcurve) # Not keeping if not changing property - return None if std < 0.0001 else keyframes + return None if cst is True else keyframes return keyframes diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py index d3b9e022..aca85e07 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py @@ -370,7 +370,7 @@ def __gather_output(channels: typing.Tuple[bpy.types.FCurve], correction_matrix_local = axis_basis_change @ bone.bone.matrix_local else: correction_matrix_local = ( - bone.parent.bone.matrix_local.inverted() @ + bone.parent.bone.matrix_local.inverted_safe() @ bone.bone.matrix_local ) diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_joints.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_joints.py index 2acf56dd..1abe9ec1 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_joints.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_joints.py @@ -40,7 +40,7 @@ def gather_joint(blender_object, blender_bone, export_settings): correction_matrix_local = axis_basis_change @ blender_bone.bone.matrix_local else: correction_matrix_local = ( - blender_bone.parent.bone.matrix_local.inverted() @ + blender_bone.parent.bone.matrix_local.inverted_safe() @ blender_bone.bone.matrix_local ) diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py index a2fc57be..28a19c9e 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py @@ -49,7 +49,7 @@ def gather_node(blender_object, library, blender_scene, dupli_object_parent, exp @cached def __gather_node(blender_object, library, blender_scene, dupli_object_parent, export_settings): - children = __gather_children(blender_object, blender_scene, export_settings) + children, only_bone_children = __gather_children(blender_object, blender_scene, export_settings) camera = None mesh = None @@ -64,6 +64,12 @@ def __gather_node(blender_object, library, blender_scene, dupli_object_parent, e # This node should be filtered out, but has un-filtered children present. # So, export this node, excluding its camera, mesh, skin, and weights. # The transformations and animations on this node will have visible effects on children. + + # Armature always have children node(s) (that are bone(s)) + # We have to check if children are only bones or not for armatures + if blender_object.type == "ARMATURE" and only_bone_children is True: + return None + pass else: # This node is filtered out, and has no un-filtered children or descendants. @@ -158,6 +164,7 @@ def __gather_camera(blender_object, export_settings): def __gather_children(blender_object, blender_scene, export_settings): children = [] + only_bone_children = True # True by default, will be set to False if needed # standard children for _child_object in blender_object.children: if _child_object.parent_bone: @@ -173,6 +180,7 @@ def __gather_children(blender_object, blender_scene, export_settings): blender_scene, None, export_settings) if node is not None: children.append(node) + only_bone_children = False # blender dupli objects if blender_object.instance_type == 'COLLECTION' and blender_object.instance_collection: for dupli_object in blender_object.instance_collection.objects: @@ -185,6 +193,7 @@ def __gather_children(blender_object, blender_scene, export_settings): blender_scene, blender_object.name, export_settings) if node is not None: children.append(node) + only_bone_children = False # blender bones if blender_object.type == "ARMATURE": @@ -201,6 +210,8 @@ def __gather_children(blender_object, blender_scene, export_settings): root_joints.append(joint) # handle objects directly parented to bones direct_bone_children = [child for child in blender_object.children if child.parent_bone] + if len(direct_bone_children) != 0: + only_bone_children = False def find_parent_joint(joints, name): for joint in joints: if joint.name == name: @@ -246,7 +257,7 @@ def __gather_children(blender_object, blender_scene, export_settings): parent_joint.children.append(child_node) - return children + return children, only_bone_children def __gather_extensions(blender_object, export_settings): @@ -292,6 +303,9 @@ def __gather_mesh(blender_object, library, export_settings): if blender_object.type != "MESH": return None + # Be sure that object is valid (no NaN for example) + blender_object.data.validate() + # If not using vertex group, they are irrelevant for caching --> ensure that they do not trigger a cache miss vertex_groups = blender_object.vertex_groups modifiers = blender_object.modifiers diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py index 003a4d6f..5d9e31ed 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py @@ -90,7 +90,7 @@ def __gather_inverse_bind_matrices(blender_object, export_settings): blender_object.matrix_world @ bone.bone.matrix_local ) - ).inverted() + ).inverted_safe() matrices.append(inverse_bind_matrix) if export_settings['gltf_def_bones'] is False: diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_node.py b/io_scene_gltf2/blender/imp/gltf2_blender_node.py index 041bf1eb..8e0d7cbb 100755 --- a/io_scene_gltf2/blender/imp/gltf2_blender_node.py +++ b/io_scene_gltf2/blender/imp/gltf2_blender_node.py @@ -184,6 +184,10 @@ class BlenderNode(): @staticmethod def create_mesh_object(gltf, vnode): pynode = gltf.data.nodes[vnode.mesh_node_idx] + if not (0 <= pynode.mesh < len(gltf.data.meshes)): + # Avoid traceback for invalid gltf file: invalid reference to meshes array + # So return an empty blender object) + return bpy.data.objects.new(vnode.name or mesh.name, None) pymesh = gltf.data.meshes[pynode.mesh] # Key to cache the Blender mesh by. diff --git a/presets/interface_theme/Deep_Grey.xml b/presets/interface_theme/Deep_Grey.xml index 4d0c2df1..15d87f4f 100644 --- a/presets/interface_theme/Deep_Grey.xml +++ b/presets/interface_theme/Deep_Grey.xml @@ -1533,7 +1533,6 @@ <panel_title> <ThemeFontStyle points="11" - font_kerning_style="FITTED" shadow="0" shadow_offset_x="0" shadow_offset_y="-1" @@ -1545,7 +1544,6 @@ <widget_label> <ThemeFontStyle points="11" - font_kerning_style="FITTED" shadow="0" shadow_offset_x="0" shadow_offset_y="-1" @@ -1557,7 +1555,6 @@ <widget> <ThemeFontStyle points="11" - font_kerning_style="FITTED" shadow="0" shadow_offset_x="0" shadow_offset_y="0" diff --git a/presets/interface_theme/Maya.xml b/presets/interface_theme/Maya.xml index 844605b6..35470fa1 100644 --- a/presets/interface_theme/Maya.xml +++ b/presets/interface_theme/Maya.xml @@ -1454,7 +1454,6 @@ <panel_title> <ThemeFontStyle points="12" - font_kerning_style="FITTED" shadow="1" shadow_offset_x="0" shadow_offset_y="-1" @@ -1466,7 +1465,6 @@ <widget_label> <ThemeFontStyle points="11" - font_kerning_style="FITTED" shadow="3" shadow_offset_x="0" shadow_offset_y="-1" @@ -1478,7 +1476,6 @@ <widget> <ThemeFontStyle points="11" - font_kerning_style="FITTED" shadow="0" shadow_offset_x="0" shadow_offset_y="0" diff --git a/presets/interface_theme/Minimal_Dark.xml b/presets/interface_theme/Minimal_Dark.xml index c83b7742..9f3fe83c 100644 --- a/presets/interface_theme/Minimal_Dark.xml +++ b/presets/interface_theme/Minimal_Dark.xml @@ -1533,7 +1533,6 @@ <panel_title> <ThemeFontStyle points="11" - font_kerning_style="FITTED" shadow="1" shadow_offset_x="0" shadow_offset_y="-1" @@ -1545,7 +1544,6 @@ <widget_label> <ThemeFontStyle points="10" - font_kerning_style="FITTED" shadow="3" shadow_offset_x="0" shadow_offset_y="-1" @@ -1557,7 +1555,6 @@ <widget> <ThemeFontStyle points="10" - font_kerning_style="FITTED" shadow="0" shadow_offset_x="0" shadow_offset_y="-1" diff --git a/presets/interface_theme/Modo.xml b/presets/interface_theme/Modo.xml index 8e7b1f47..c762e49f 100644 --- a/presets/interface_theme/Modo.xml +++ b/presets/interface_theme/Modo.xml @@ -1454,7 +1454,6 @@ <panel_title> <ThemeFontStyle points="11" - font_kerning_style="FITTED" shadow="1" shadow_offset_x="0" shadow_offset_y="-1" @@ -1466,7 +1465,6 @@ <widget_label> <ThemeFontStyle points="11" - font_kerning_style="FITTED" shadow="3" shadow_offset_x="0" shadow_offset_y="-1" @@ -1478,7 +1476,6 @@ <widget> <ThemeFontStyle points="10" - font_kerning_style="FITTED" shadow="1" shadow_offset_x="0" shadow_offset_y="-1" diff --git a/presets/interface_theme/Print_Friendly.xml b/presets/interface_theme/Print_Friendly.xml index 752c2ef4..9e6d8593 100644 --- a/presets/interface_theme/Print_Friendly.xml +++ b/presets/interface_theme/Print_Friendly.xml @@ -1533,7 +1533,6 @@ <panel_title> <ThemeFontStyle points="12" - font_kerning_style="FITTED" shadow="3" shadow_offset_x="0" shadow_offset_y="-1" @@ -1545,7 +1544,6 @@ <widget_label> <ThemeFontStyle points="11" - font_kerning_style="FITTED" shadow="3" shadow_offset_x="0" shadow_offset_y="-1" @@ -1557,7 +1555,6 @@ <widget> <ThemeFontStyle points="11" - font_kerning_style="FITTED" shadow="1" shadow_offset_x="0" shadow_offset_y="-1" diff --git a/presets/interface_theme/White.xml b/presets/interface_theme/White.xml index ad33a755..dcbf545d 100644 --- a/presets/interface_theme/White.xml +++ b/presets/interface_theme/White.xml @@ -1454,7 +1454,6 @@ <panel_title> <ThemeFontStyle points="12" - font_kerning_style="FITTED" shadow="0" shadow_offset_x="0" shadow_offset_y="-1" @@ -1466,7 +1465,6 @@ <widget_label> <ThemeFontStyle points="11" - font_kerning_style="FITTED" shadow="0" shadow_offset_x="0" shadow_offset_y="-1" @@ -1478,7 +1476,6 @@ <widget> <ThemeFontStyle points="11" - font_kerning_style="FITTED" shadow="0" shadow_offset_x="0" shadow_offset_y="-1" diff --git a/presets/interface_theme/XSI.xml b/presets/interface_theme/XSI.xml index ab901413..79bba314 100644 --- a/presets/interface_theme/XSI.xml +++ b/presets/interface_theme/XSI.xml @@ -1454,7 +1454,6 @@ <panel_title> <ThemeFontStyle points="12" - font_kerning_style="FITTED" shadow="2" shadow_offset_x="0" shadow_offset_y="-1" @@ -1466,7 +1465,6 @@ <widget_label> <ThemeFontStyle points="11" - font_kerning_style="UNFITTED" shadow="3" shadow_offset_x="0" shadow_offset_y="-1" @@ -1478,7 +1476,6 @@ <widget> <ThemeFontStyle points="11" - font_kerning_style="UNFITTED" shadow="0" shadow_offset_x="0" shadow_offset_y="0" diff --git a/rigify/__init__.py b/rigify/__init__.py index 1bb633f6..4b926c48 100644 --- a/rigify/__init__.py +++ b/rigify/__init__.py @@ -140,6 +140,11 @@ from bpy.props import ( ) +def get_generator(): + """Returns the currently active generator instance.""" + return base_generate.BaseGenerator.instance + + class RigifyFeatureSets(bpy.types.PropertyGroup): name: bpy.props.StringProperty() module_name: bpy.props.StringProperty() @@ -525,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", @@ -534,6 +543,10 @@ def register(): name="Rigify Target Rig UI", description="Defines the UI to overwrite. If unset, 'rig_ui.py' will be used") + 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="") diff --git a/rigify/base_generate.py b/rigify/base_generate.py index 16242262..7bdb8b0e 100644 --- a/rigify/base_generate.py +++ b/rigify/base_generate.py @@ -189,6 +189,8 @@ class LegacyRig(base_rig.BaseRig): class BaseGenerator: """Base class for the main generator object. Contains rig and plugin management code.""" + instance = None + def __init__(self, context, metarig): self.context = context self.scene = context.scene diff --git a/rigify/generate.py b/rigify/generate.py index 5e95bd99..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] @@ -574,6 +596,14 @@ class Generator(base_generate.BaseGenerator): refresh_all_drivers() #---------------------------------- + # Execute the finalize script + + if metarig.data.rigify_finalize_script: + bpy.ops.object.mode_set(mode='OBJECT') + exec(metarig.data.rigify_finalize_script.as_string(), {}) + bpy.ops.object.mode_set(mode='OBJECT') + + #---------------------------------- # Restore active collection view_layer.active_layer_collection = self.layer_collection @@ -587,7 +617,11 @@ def generate_rig(context, metarig): metarig.data.pose_position = 'REST' try: - Generator(context, metarig).generate() + generator = Generator(context, metarig) + + base_generate.BaseGenerator.instance = generator + + generator.generate() metarig.data.pose_position = rest_backup @@ -601,6 +635,9 @@ def generate_rig(context, metarig): # Continue the exception raise e + finally: + base_generate.BaseGenerator.instance = None + def create_selection_set_for_rig_layer( rig: bpy.types.Object, 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 6ba455da..c801ac25 100644 --- a/rigify/ui.py +++ b/rigify/ui.py @@ -177,6 +177,9 @@ 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': # Build types list build_type_list(context, id_store.rigify_types) 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 |