diff --git a/functions/tools/general_mesh_tools.py b/functions/tools/general_mesh_tools.py index 0ac6d3c..5695f15 100644 --- a/functions/tools/general_mesh_tools.py +++ b/functions/tools/general_mesh_tools.py @@ -1,7 +1,7 @@ import bpy import numpy as np from bpy.types import Operator, Context -from typing import Set +from typing import Set, Literal from ...core.translations import t from ...core.logging_setup import logger from ...core.common import get_active_armature, get_all_meshes @@ -99,3 +99,96 @@ class AvatarToolkit_OT_SelectShortestSeamPath(Operator): return {'FINISHED'} +class AvatarToolkit_OT_ExplodeMesh(Operator): + """Explodes the mesh for use with painting programs, or painting inside blender.""" + bl_idname = "avatar_toolkit.explode_mesh" + bl_label = t("Tools.explode_mesh") + bl_description = t("Tools.explode_mesh_desc") + bl_options = {'REGISTER', 'UNDO'} + distance: bpy.props.FloatProperty(default=2.0,name=t("Tools.explode_mesh.distance"),description=t("Tools.explode_mesh.distance_desc")) + split_on_seams: bpy.props.BoolProperty(default=True,name=t("Tools.explode_mesh.split_on_seams"),description=t("Tools.explode_mesh.split_on_seams_desc")) + + def draw(self, context: Context) -> None: + """Draw the operator's UI""" + layout = self.layout + layout.prop(self, "distance") + + def invoke(self, context: Context, event: bpy.types.Event) -> set[str]: + """Initialize the operator""" + return context.window_manager.invoke_props_dialog(self) + + @classmethod + def poll(cls, context: Context) -> bool: + + return context.view_layer.objects.active.type == "MESH" and len(context.view_layer.objects.selected) == 1 + + + + def execute(self, context: Context) -> Set[str]: + + mesh_obj: bpy.types.Object = context.view_layer.objects.active.type + mesh: bpy.types.Mesh = context.view_layer.objects.active.data + if(self.split_on_seams): + + #set to correct mode + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_mode(type='EDGE') + + #mark seams by islands + bpy.ops.mesh.select_all(action="SELECT") + bpy.ops.uv.select_all(action="SELECT") + bpy.ops.uv.seams_from_islands(mark_seams=True,mark_sharp=False) + + #clear selection + bpy.ops.mesh.select_all(action="DESELECT") + bpy.ops.object.mode_set(mode='OBJECT') + bm = bmesh.new() # create an empty BMesh + bm.from_mesh(mesh) # fill it in from active mesh + + #select seam edges + for idx,edge in enumerate(bm.edges): + edge.select = edge.seam + bm.to_mesh(mesh) + bm.free() + + #split edges. + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.edge_split() + + #separate by loose. + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_mode(type='FACE') + + bpy.ops.mesh.select_all(action="SELECT") + + bpy.ops.mesh.separate(type='LOOSE') + + + distance: float = self.distance + + + #set origins to geometry + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.origin_set(type="ORIGIN_GEOMETRY",center="BOUNDS") + + #store original settings + origin_only_orig: bool = context.scene.tool_settings.use_transform_data_origin + pos_only_orig: bool = context.scene.tool_settings.use_transform_pivot_point_align + parents_only_orig: bool = context.scene.tool_settings.use_transform_skip_children + original_pivot: Literal['BOUNDING_BOX_CENTER', 'CURSOR', 'INDIVIDUAL_ORIGINS', 'MEDIAN_POINT', 'ACTIVE_ELEMENT'] = context.scene.tool_settings.transform_pivot_point + + #set scene settings correctly. + context.scene.tool_settings.use_transform_data_origin = False + context.scene.tool_settings.use_transform_pivot_point_align = True + context.scene.tool_settings.use_transform_skip_children = False + context.scene.tool_settings.transform_pivot_point = 'MEDIAN_POINT' + + #spread out separated objects + bpy.ops.transform.resize(value=(self.distance, self.distance, self.distance), orient_type='GLOBAL') + + #restore settings. + context.scene.tool_settings.use_transform_data_origin = origin_only_orig + context.scene.tool_settings.use_transform_pivot_point_align = pos_only_orig + context.scene.tool_settings.use_transform_skip_children = parents_only_orig + context.scene.tool_settings.transform_pivot_point = original_pivot + return {'FINISHED'} \ No newline at end of file diff --git a/resources/translations/en_US.json b/resources/translations/en_US.json index cb7a600..e29ef01 100644 --- a/resources/translations/en_US.json +++ b/resources/translations/en_US.json @@ -215,6 +215,12 @@ "Tools.clean_weights_threshold_desc": "Minimum weight value to consider a bone as weighted", "Tools.find_shortest_seam_path": "Find Shortest Seam Path", "Tools.find_shortest_seam_path_desc": "Find shortest path of seams between two selected vertices connected to seams.", + "Tools.explode_mesh":"Explode Mesh for Painting", + "Tools.explode_mesh_desc": "Explodes the mesh for use with painting programs, or painting inside blender.", + "Tools.explode_mesh.distance": "Distance", + "Tools.explode_mesh.distance_desc": "Scale factor for distance between exploded items on model.", + "Tools.explode_mesh.split_on_seams_desc":"Split model on UV seams to separate islands from each other.", + "Tools.explode_mesh.split_on_seams":"Split on Seams", "Tools.apply_modifier_on_shapekey_obj":"Apply Modifier on Shapekey Object", "Tools.apply_modifier_on_shapekey_obj_desc":"Applies a modifier on an object regardless of it having shapekeys.", "Tools.merge_title": "Merge Tools", diff --git a/ui/tools_panel.py b/ui/tools_panel.py index b8aa933..fd4f25c 100644 --- a/ui/tools_panel.py +++ b/ui/tools_panel.py @@ -18,7 +18,7 @@ from ..functions.tools.bone_tools import ( from ..functions.tools.standardize_armature import AvatarToolkit_OT_StandardizeArmature from ..functions.tools.merge_tools import AvatarToolkit_OT_MergeToActive, AvatarToolkit_OT_MergeToParent, AvatarToolkit_OT_ConnectBones from ..functions.tools.rigify_converter import AvatarToolkit_OT_ConvertRigifyToUnity -from ..functions.tools.general_mesh_tools import AvatarToolkit_OT_SelectShortestSeamPath +from ..functions.tools.general_mesh_tools import AvatarToolkit_OT_SelectShortestSeamPath, AvatarToolkit_OT_ExplodeMesh from ..functions.custom_tools.force_apply_modifier import AvatarToolkit_OT_ApplyModifierForShapkeyObj class AvatarToolKit_PT_ToolsPanel(Panel): @@ -68,6 +68,7 @@ class AvatarToolKit_PT_ToolsPanel(Panel): col.separator(factor=0.5) col.operator(AvatarToolkit_OT_SelectShortestSeamPath.bl_idname,text=t("Tools.find_shortest_seam_path"),icon="MESH_DATA") col.operator(AvatarToolkit_OT_ApplyModifierForShapkeyObj.bl_idname,text=t("Tools.apply_modifier_on_shapekey_obj"),icon="SHAPEKEY_DATA") + col.operator(AvatarToolkit_OT_ExplodeMesh.bl_idname,text=t("Tools.explode_mesh"),icon="MOD_EXPLODE") # Standardization Tools