Merge pull request #78 from Yusarina/mmd-options-improvements
Mmd options improvements
This commit is contained in:
+69
-16
@@ -74,20 +74,63 @@ def clean_material_names(mesh: Mesh) -> None:
|
|||||||
def fix_uv_coordinates(context: Context) -> None:
|
def fix_uv_coordinates(context: Context) -> None:
|
||||||
obj = context.object
|
obj = context.object
|
||||||
|
|
||||||
# Check if the object is in Edit Mode
|
# Store current mode and selection
|
||||||
if obj.mode != 'EDIT':
|
current_mode = context.mode
|
||||||
bpy.ops.object.mode_set(mode='EDIT')
|
current_active = context.view_layer.objects.active
|
||||||
|
current_selected = context.selected_objects.copy()
|
||||||
|
|
||||||
|
# Ensure we're in object mode and select the object
|
||||||
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
obj.select_set(True)
|
||||||
|
context.view_layer.objects.active = obj
|
||||||
|
|
||||||
# Check if the object has any mesh data
|
# Check if the object has any mesh data
|
||||||
if obj.type == 'MESH' and obj.data:
|
if obj.type == 'MESH' and obj.data:
|
||||||
bpy.context.view_layer.objects.active = obj
|
|
||||||
|
# Switch to Edit Mode
|
||||||
|
bpy.ops.object.mode_set(mode='EDIT')
|
||||||
|
|
||||||
|
# Select all UVs
|
||||||
bpy.ops.mesh.select_all(action='SELECT')
|
bpy.ops.mesh.select_all(action='SELECT')
|
||||||
bpy.ops.uv.average_islands_scale()
|
|
||||||
|
# Try to find UV Editor area, fall back to 3D View if not found
|
||||||
|
area = next((area for area in context.screen.areas if area.type == 'UV_EDITOR'), None)
|
||||||
|
if not area:
|
||||||
|
area = next((area for area in context.screen.areas if area.type == 'VIEW_3D'), None)
|
||||||
|
|
||||||
|
# Get the region and space data
|
||||||
|
region = next((region for region in area.regions if region.type == 'WINDOW'), None)
|
||||||
|
space_data = area.spaces.active
|
||||||
|
|
||||||
|
# Create a context override
|
||||||
|
override = {
|
||||||
|
'area': area,
|
||||||
|
'region': region,
|
||||||
|
'space_data': space_data,
|
||||||
|
'edit_object': obj,
|
||||||
|
'active_object': obj,
|
||||||
|
'selected_objects': [obj],
|
||||||
|
'mode': 'EDIT_MESH',
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Ensure UVs are selected
|
||||||
|
bpy.ops.uv.select_all(override, action='SELECT')
|
||||||
|
# Average UV island scales
|
||||||
|
bpy.ops.uv.average_islands_scale(override)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"UV Fix - Error during UV scaling: {str(e)}")
|
||||||
|
|
||||||
# Switch back to Object Mode
|
# Switch back to Object Mode
|
||||||
bpy.ops.object.mode_set(mode='OBJECT')
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
print("UV Fix - Switched back to Object Mode")
|
||||||
|
|
||||||
|
# Restore previous selection and active object
|
||||||
|
for sel_obj in current_selected:
|
||||||
|
sel_obj.select_set(True)
|
||||||
|
context.view_layer.objects.active = current_active
|
||||||
else:
|
else:
|
||||||
print("Object is not a valid mesh with UV data")
|
print("UV Fix - Object is not a valid mesh with UV data")
|
||||||
|
|
||||||
def has_shapekeys(mesh_obj: Object) -> bool:
|
def has_shapekeys(mesh_obj: Object) -> bool:
|
||||||
return mesh_obj.data.shape_keys is not None
|
return mesh_obj.data.shape_keys is not None
|
||||||
@@ -424,23 +467,33 @@ def finish_progress(context):
|
|||||||
context.area.header_text_set(None)
|
context.area.header_text_set(None)
|
||||||
|
|
||||||
def transfer_vertex_weights(context: Context, obj: bpy.types.Object, source_group: str, target_group: str, delete_source_group: bool = True) -> bool:
|
def transfer_vertex_weights(context: Context, obj: bpy.types.Object, source_group: str, target_group: str, delete_source_group: bool = True) -> bool:
|
||||||
|
# Create and configure the Vertex Weight Mix modifier
|
||||||
modifier: bpy.types.VertexWeightMixModifier = obj.modifiers.new(name="merge_weights",type="VERTEX_WEIGHT_MIX")
|
modifier = obj.modifiers.new(name="merge_weights", type="VERTEX_WEIGHT_MIX")
|
||||||
|
modifier.show_viewport = True
|
||||||
modifier.mix_set = 'B'
|
modifier.show_render = True
|
||||||
|
modifier.mix_set = 'B' # Replace weights in A with weights from B
|
||||||
modifier.vertex_group_a = target_group
|
modifier.vertex_group_a = target_group
|
||||||
modifier.vertex_group_b = source_group
|
modifier.vertex_group_b = source_group
|
||||||
modifier.mask_constant = 1.0
|
modifier.mask_constant = 1.0
|
||||||
|
|
||||||
|
# Ensure we're in Object Mode
|
||||||
bpy.ops.object.mode_set(mode='OBJECT')
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
prev_obj: bpy.types.Object = context.view_layer.objects.active
|
|
||||||
|
# Deselect all objects and select only our target object
|
||||||
|
bpy.ops.object.select_all(action='DESELECT')
|
||||||
|
obj.select_set(True)
|
||||||
context.view_layer.objects.active = obj
|
context.view_layer.objects.active = obj
|
||||||
|
|
||||||
|
# Move modifier to the top of the stack if necessary
|
||||||
|
if len(obj.modifiers) > 1:
|
||||||
|
obj.modifiers.move(obj.modifiers.find(modifier.name), 0)
|
||||||
|
|
||||||
|
# Apply modifier
|
||||||
bpy.ops.object.modifier_apply(modifier=modifier.name)
|
bpy.ops.object.modifier_apply(modifier=modifier.name)
|
||||||
if delete_source_group:
|
|
||||||
obj.vertex_groups.remove(obj.vertex_groups.get(source_group))
|
# Clean up
|
||||||
bpy.ops.object.mode_set(mode='EDIT')
|
if delete_source_group and source_group in obj.vertex_groups:
|
||||||
bpy.ops.object.mode_set(mode='OBJECT')
|
obj.vertex_groups.remove(obj.vertex_groups[source_group])
|
||||||
context.view_layer.objects.active = prev_obj
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
+104
-33
@@ -117,7 +117,6 @@ class AvatarToolkit_OT_ApplyPoseAsRest(Operator):
|
|||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
@register_wrap
|
@register_wrap
|
||||||
class AvatarToolkit_OT_RemoveZeroWeightBones(Operator):
|
class AvatarToolkit_OT_RemoveZeroWeightBones(Operator):
|
||||||
bl_idname = "avatar_toolkit.remove_zero_weight_bones"
|
bl_idname = "avatar_toolkit.remove_zero_weight_bones"
|
||||||
@@ -144,10 +143,22 @@ class AvatarToolkit_OT_RemoveZeroWeightBones(Operator):
|
|||||||
|
|
||||||
weighted_bones: list[str] = []
|
weighted_bones: list[str] = []
|
||||||
|
|
||||||
|
|
||||||
bpy.ops.object.mode_set(mode='OBJECT')
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
bpy.ops.object.select_all(action='DESELECT')
|
bpy.ops.object.select_all(action='DESELECT')
|
||||||
|
|
||||||
|
# Store initial transforms
|
||||||
|
initial_transforms = {}
|
||||||
|
bpy.ops.object.mode_set(mode='EDIT')
|
||||||
|
for bone in armature.data.edit_bones:
|
||||||
|
initial_transforms[bone.name] = {
|
||||||
|
'head': bone.head.copy(),
|
||||||
|
'tail': bone.tail.copy(),
|
||||||
|
'roll': bone.roll,
|
||||||
|
'matrix': bone.matrix.copy(),
|
||||||
|
'parent': bone.parent.name if bone.parent else None
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get weighted bones
|
||||||
armature.select_set(True)
|
armature.select_set(True)
|
||||||
context.view_layer.objects.active = armature
|
context.view_layer.objects.active = armature
|
||||||
|
|
||||||
@@ -157,26 +168,56 @@ class AvatarToolkit_OT_RemoveZeroWeightBones(Operator):
|
|||||||
for vertex in mesh_data.vertices:
|
for vertex in mesh_data.vertices:
|
||||||
for group in vertex.groups:
|
for group in vertex.groups:
|
||||||
if group.weight > self.threshold:
|
if group.weight > self.threshold:
|
||||||
weighted_bones.append(mesh.vertex_groups[group.group].name) #add bone name to list of bones that are greater than the weight threshold
|
weighted_bones.append(mesh.vertex_groups[group.group].name)
|
||||||
|
|
||||||
bpy.ops.object.mode_set(mode='EDIT')
|
bpy.ops.object.mode_set(mode='EDIT')
|
||||||
amature_data: Armature = armature.data
|
amature_data: Armature = armature.data
|
||||||
unweighted_bones: list[str] = []
|
unweighted_bones: list[str] = []
|
||||||
|
|
||||||
#doing 2 loops to prevent modification of array during iteration
|
# Identify unweighted bones
|
||||||
for bone in amature_data.edit_bones:
|
for bone in amature_data.edit_bones:
|
||||||
if bone.name not in weighted_bones:
|
if bone.name not in weighted_bones:
|
||||||
unweighted_bones.append(bone.name) #add bones that arent in the list of bones that have weight into the list of bones that don't
|
unweighted_bones.append(bone.name)
|
||||||
|
|
||||||
|
# Process bone removal while preserving positions
|
||||||
for bone_name in unweighted_bones:
|
for bone_name in unweighted_bones:
|
||||||
for edit_bone in amature_data.edit_bones[bone_name].children:
|
bone = amature_data.edit_bones[bone_name]
|
||||||
edit_bone.use_connect = False #to fix randomly moving bones
|
|
||||||
edit_bone.parent = amature_data.edit_bones[bone_name].parent #to fix unparented bones.
|
# Store children data
|
||||||
amature_data.edit_bones.remove(amature_data.edit_bones[bone_name]) #delete list of unweighted bones from the armature
|
children = bone.children
|
||||||
|
children_data = {}
|
||||||
|
for child in children:
|
||||||
|
children_data[child.name] = initial_transforms[child.name]
|
||||||
|
|
||||||
|
# Reparent children
|
||||||
|
for child in children:
|
||||||
|
child.use_connect = False
|
||||||
|
if bone.parent:
|
||||||
|
child.parent = bone.parent
|
||||||
|
|
||||||
|
# Remove bone
|
||||||
|
amature_data.edit_bones.remove(bone)
|
||||||
|
|
||||||
|
# Restore children positions
|
||||||
|
for child_name, data in children_data.items():
|
||||||
|
if child_name in amature_data.edit_bones:
|
||||||
|
child = amature_data.edit_bones[child_name]
|
||||||
|
child.head = data['head']
|
||||||
|
child.tail = data['tail']
|
||||||
|
child.roll = data['roll']
|
||||||
|
child.matrix = data['matrix']
|
||||||
|
|
||||||
|
# Final position verification
|
||||||
|
for bone_name, transform in initial_transforms.items():
|
||||||
|
if bone_name in amature_data.edit_bones:
|
||||||
|
bone = amature_data.edit_bones[bone_name]
|
||||||
|
bone.matrix = transform['matrix']
|
||||||
|
|
||||||
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
self.report({'INFO'}, t("Tools.remove_zero_weight_bones.success"))
|
self.report({'INFO'}, t("Tools.remove_zero_weight_bones.success"))
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
@register_wrap
|
@register_wrap
|
||||||
class AvatarToolkit_OT_MergeBonesToActive(Operator):
|
class AvatarToolkit_OT_MergeBonesToActive(Operator):
|
||||||
bl_idname = "avatar_toolkit.merge_bones_to_active"
|
bl_idname = "avatar_toolkit.merge_bones_to_active"
|
||||||
@@ -233,44 +274,74 @@ class AvatarToolkit_OT_MergeBonesToParents(Operator):
|
|||||||
bl_description = t("Tools.merge_bones_to_parents.desc")
|
bl_description = t("Tools.merge_bones_to_parents.desc")
|
||||||
bl_options = {'REGISTER', 'UNDO'}
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
delete_old: bpy.props.BoolProperty(name=t("Tools.merge_bones_to_parents.delete_old.label"), description=t("Tools.merge_bones_to_parents.delete_old.desc"), default=False)
|
delete_old: bpy.props.BoolProperty(
|
||||||
|
name=t("Tools.merge_bones_to_parents.delete_old.label"),
|
||||||
|
description=t("Tools.merge_bones_to_parents.delete_old.desc"),
|
||||||
|
default=False
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context: Context) -> bool:
|
def poll(cls, context: Context) -> bool:
|
||||||
if common.get_selected_armature(context) is not None:
|
armature = common.get_selected_armature(context)
|
||||||
if common.get_selected_armature(context) == context.view_layer.objects.active:
|
if armature and armature == context.view_layer.objects.active:
|
||||||
if context.mode == "POSE":
|
if context.mode == "POSE":
|
||||||
return len(context.selected_pose_bones) > 0
|
return len(context.selected_pose_bones) > 0
|
||||||
elif context.mode == "EDIT_ARMATURE":
|
elif context.mode == "EDIT_ARMATURE":
|
||||||
return len(context.selected_bones) > 0
|
return len(context.selected_editable_bones) > 0
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def execute(cls, context: Context) -> set[str]:
|
def execute(self, context: Context) -> set[str]:
|
||||||
|
prev_mode = context.mode
|
||||||
|
|
||||||
prev_mode: str = "EDIT"
|
# Map 'EDIT_ARMATURE' to 'EDIT' for bpy.ops.object.mode_set
|
||||||
if context.mode == "POSE":
|
if prev_mode == 'EDIT_ARMATURE':
|
||||||
prev_mode = "POSE"
|
prev_mode = 'EDIT'
|
||||||
#get active bone and a list of all other selected bones
|
|
||||||
|
# Switch to Edit Mode
|
||||||
bpy.ops.object.mode_set(mode='EDIT')
|
bpy.ops.object.mode_set(mode='EDIT')
|
||||||
armature_data: Armature = context.view_layer.objects.active.data
|
armature_data: Armature = context.view_layer.objects.active.data
|
||||||
|
|
||||||
|
# Get selected bones in Edit Mode
|
||||||
|
selected_bones = context.selected_editable_bones
|
||||||
|
selected_bone_names = [bone.name for bone in selected_bones]
|
||||||
|
|
||||||
|
if not selected_bone_names:
|
||||||
|
self.report({'ERROR'}, t("No bones selected"))
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
for obj in common.get_all_meshes(context):
|
for obj in common.get_all_meshes(context):
|
||||||
for bone in [i.name for i in context.selected_bones]:
|
for bone_name in selected_bone_names:
|
||||||
if armature_data.edit_bones[bone].parent != None:
|
bone = armature_data.edit_bones.get(bone_name)
|
||||||
bone_name: str = armature_data.edit_bones[bone].name
|
if bone and bone.parent:
|
||||||
common.transfer_vertex_weights(context=context,obj=obj,source_group=bone_name,target_group=armature_data.edit_bones[bone].parent.name)
|
# Transfer weights from bone to its parent
|
||||||
|
common.transfer_vertex_weights(
|
||||||
|
context=context,
|
||||||
|
obj=obj,
|
||||||
|
source_group=bone_name,
|
||||||
|
target_group=bone.parent.name
|
||||||
|
)
|
||||||
|
# Ensure we're in Edit Mode after transfer
|
||||||
bpy.ops.object.mode_set(mode='EDIT')
|
bpy.ops.object.mode_set(mode='EDIT')
|
||||||
|
else:
|
||||||
for bone in [i.name for i in context.selected_bones]:
|
self.report({'WARNING'}, f"Bone '{bone_name}' has no parent or not found; skipping")
|
||||||
if cls.delete_old:
|
|
||||||
for bone_child in armature_data.edit_bones[bone].children:
|
# Optionally delete old bones
|
||||||
bone_child.parent = armature_data.edit_bones[bone].parent
|
if self.delete_old:
|
||||||
armature_data.edit_bones.remove(armature_data.edit_bones[bone])
|
for bone_name in selected_bone_names:
|
||||||
|
bone = armature_data.edit_bones.get(bone_name)
|
||||||
|
if bone:
|
||||||
|
# Reassign children to the parent of the bone being deleted
|
||||||
|
for child in bone.children:
|
||||||
|
child.parent = bone.parent
|
||||||
|
# Remove the bone
|
||||||
|
armature_data.edit_bones.remove(bone)
|
||||||
|
else:
|
||||||
|
self.report({'WARNING'}, f"Bone '{bone_name}' not found in armature; cannot delete")
|
||||||
|
|
||||||
|
# Return to previous mode
|
||||||
bpy.ops.object.mode_set(mode=prev_mode)
|
bpy.ops.object.mode_set(mode=prev_mode)
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
@register_wrap
|
@register_wrap
|
||||||
class AvatarToolkit_OT_MergeArmatures(Operator):
|
class AvatarToolkit_OT_MergeArmatures(Operator):
|
||||||
bl_idname = "avatar_toolkit.merge_armatures"
|
bl_idname = "avatar_toolkit.merge_armatures"
|
||||||
|
|||||||
@@ -104,10 +104,13 @@ class AvatarToolKit_OT_JoinAllMeshes(Operator):
|
|||||||
raise ValueError(t("Optimization.no_armature_selected"))
|
raise ValueError(t("Optimization.no_armature_selected"))
|
||||||
|
|
||||||
armature = get_selected_armature(context)
|
armature = get_selected_armature(context)
|
||||||
|
|
||||||
bpy.ops.object.mode_set(mode='OBJECT')
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
|
||||||
bpy.ops.object.select_all(action='DESELECT')
|
bpy.ops.object.select_all(action='DESELECT')
|
||||||
|
|
||||||
meshes: List[Object] = get_all_meshes(context)
|
meshes: List[Object] = get_all_meshes(context)
|
||||||
|
|
||||||
if not meshes:
|
if not meshes:
|
||||||
raise ValueError(t("Optimization.no_meshes_found"))
|
raise ValueError(t("Optimization.no_meshes_found"))
|
||||||
|
|
||||||
@@ -133,6 +136,7 @@ class AvatarToolKit_OT_JoinAllMeshes(Operator):
|
|||||||
raise RuntimeError(f"{t('Optimization.transform_apply_failed')}: {str(e)}")
|
raise RuntimeError(f"{t('Optimization.transform_apply_failed')}: {str(e)}")
|
||||||
|
|
||||||
update_progress(self, context, t("Optimization.fixing_uv_coordinates"))
|
update_progress(self, context, t("Optimization.fixing_uv_coordinates"))
|
||||||
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
fix_uv_coordinates(context)
|
fix_uv_coordinates(context)
|
||||||
|
|
||||||
update_progress(self, context, t("Optimization.finalizing"))
|
update_progress(self, context, t("Optimization.finalizing"))
|
||||||
@@ -145,6 +149,7 @@ class AvatarToolKit_OT_JoinAllMeshes(Operator):
|
|||||||
context.view_layer.objects.active = armature
|
context.view_layer.objects.active = armature
|
||||||
finish_progress(context)
|
finish_progress(context)
|
||||||
|
|
||||||
|
|
||||||
@register_wrap
|
@register_wrap
|
||||||
class AvatarToolKit_OT_JoinSelectedMeshes(Operator):
|
class AvatarToolKit_OT_JoinSelectedMeshes(Operator):
|
||||||
bl_idname = "avatar_toolkit.join_selected_meshes"
|
bl_idname = "avatar_toolkit.join_selected_meshes"
|
||||||
|
|||||||
+33
-12
@@ -123,19 +123,23 @@ class AvatarToolKit_OT_OptimizeArmature(Operator):
|
|||||||
|
|
||||||
init_progress(context, 9)
|
init_progress(context, 9)
|
||||||
|
|
||||||
update_progress(self, context, t("MMDOptions.fixing_bone_rolls"))
|
# Ensure proper object selection and mode
|
||||||
bpy.ops.object.mode_set(mode='EDIT')
|
|
||||||
for bone in armature.data.edit_bones:
|
|
||||||
bone.roll = 0
|
|
||||||
|
|
||||||
update_progress(self, context, t("MMDOptions.aligning_bones"))
|
|
||||||
for bone in armature.data.edit_bones:
|
|
||||||
if bone.parent:
|
|
||||||
bone.head = bone.parent.tail
|
|
||||||
|
|
||||||
update_progress(self, context, t("MMDOptions.connecting_bones"))
|
|
||||||
bpy.ops.object.mode_set(mode='OBJECT')
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
bpy.ops.avatar_toolkit.connect_bones('EXEC_DEFAULT')
|
bpy.ops.object.select_all(action='DESELECT')
|
||||||
|
armature.select_set(True)
|
||||||
|
context.view_layer.objects.active = armature
|
||||||
|
|
||||||
|
# Store initial transforms
|
||||||
|
bpy.ops.object.mode_set(mode='EDIT')
|
||||||
|
initial_transforms = {}
|
||||||
|
for bone in armature.data.edit_bones:
|
||||||
|
initial_transforms[bone.name] = {
|
||||||
|
'head': bone.head.copy(),
|
||||||
|
'tail': bone.tail.copy(),
|
||||||
|
'roll': bone.roll,
|
||||||
|
'matrix': bone.matrix.copy(),
|
||||||
|
'parent': bone.parent.name if bone.parent else None
|
||||||
|
}
|
||||||
|
|
||||||
update_progress(self, context, t("MMDOptions.deleting_bone_constraints"))
|
update_progress(self, context, t("MMDOptions.deleting_bone_constraints"))
|
||||||
bpy.ops.avatar_toolkit.delete_bone_constraints('EXEC_DEFAULT')
|
bpy.ops.avatar_toolkit.delete_bone_constraints('EXEC_DEFAULT')
|
||||||
@@ -160,7 +164,24 @@ class AvatarToolKit_OT_OptimizeArmature(Operator):
|
|||||||
update_progress(self, context, t("MMDOptions.renaming_bones"))
|
update_progress(self, context, t("MMDOptions.renaming_bones"))
|
||||||
self.rename_bones(armature)
|
self.rename_bones(armature)
|
||||||
|
|
||||||
|
# Restore original bone transforms
|
||||||
|
bpy.ops.object.mode_set(mode='EDIT')
|
||||||
|
for bone_name, transform in initial_transforms.items():
|
||||||
|
if bone_name in armature.data.edit_bones:
|
||||||
|
bone = armature.data.edit_bones[bone_name]
|
||||||
|
bone.head = transform['head']
|
||||||
|
bone.tail = transform['tail']
|
||||||
|
bone.roll = transform['roll']
|
||||||
|
bone.matrix = transform['matrix']
|
||||||
|
|
||||||
update_progress(self, context, t("MMDOptions.armature_optimization_complete"))
|
update_progress(self, context, t("MMDOptions.armature_optimization_complete"))
|
||||||
|
|
||||||
|
# Ensure we end in object mode with proper selection
|
||||||
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
bpy.ops.object.select_all(action='DESELECT')
|
||||||
|
armature.select_set(True)
|
||||||
|
context.view_layer.objects.active = armature
|
||||||
|
|
||||||
finish_progress(context)
|
finish_progress(context)
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user