@@ -0,0 +1,101 @@
|
|||||||
|
import bpy
|
||||||
|
import numpy as np
|
||||||
|
from bpy.types import Operator, Context
|
||||||
|
from typing import Set
|
||||||
|
from ...core.translations import t
|
||||||
|
from ...core.logging_setup import logger
|
||||||
|
from ...core.common import get_active_armature, get_all_meshes
|
||||||
|
from ...core.armature_validation import validate_armature
|
||||||
|
|
||||||
|
import bmesh
|
||||||
|
|
||||||
|
|
||||||
|
class MapItem():
|
||||||
|
length: int
|
||||||
|
current_node: bmesh.types.BMVert
|
||||||
|
marched_paths: list[bmesh.types.BMEdge]
|
||||||
|
|
||||||
|
class AvatarToolkit_OT_SelectShortestSeamPath(Operator):
|
||||||
|
"""Find the shortest seam path between two vertices."""
|
||||||
|
bl_idname = "avatar_toolkit.find_shortest_seam_path"
|
||||||
|
bl_label = t("Tools.find_shortest_seam_path")
|
||||||
|
bl_description = t("Tools.find_shortest_seam_path_desc")
|
||||||
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context: Context) -> bool:
|
||||||
|
if context.mode != "EDIT_MESH":
|
||||||
|
return False
|
||||||
|
mesh_data: bpy.types.Mesh = context.active_object.data
|
||||||
|
mesh = bmesh.from_edit_mesh(mesh_data)
|
||||||
|
selected: int = 0
|
||||||
|
for vert in mesh.verts:
|
||||||
|
if vert.select == True:
|
||||||
|
selected = selected+1
|
||||||
|
if selected > 2:
|
||||||
|
return False
|
||||||
|
found_seam: bool = False
|
||||||
|
for edge in vert.link_edges:
|
||||||
|
if edge.seam:
|
||||||
|
found_seam = True
|
||||||
|
if not found_seam:
|
||||||
|
return False
|
||||||
|
if selected < 2:
|
||||||
|
return False
|
||||||
|
armature = get_active_armature(context)
|
||||||
|
if not armature:
|
||||||
|
return False
|
||||||
|
valid, _, _ = validate_armature(armature)
|
||||||
|
return valid
|
||||||
|
|
||||||
|
def execute(self, context: Context) -> Set[str]:
|
||||||
|
mesh_data: bpy.types.Mesh = context.active_object.data
|
||||||
|
mesh = bmesh.from_edit_mesh(mesh_data)
|
||||||
|
vert1: bmesh.types.BMVert = None
|
||||||
|
vert2: bmesh.types.BMVert = None
|
||||||
|
for vert in mesh.verts:
|
||||||
|
if vert.select == True:
|
||||||
|
if vert1 == None:
|
||||||
|
vert1 = vert
|
||||||
|
else:
|
||||||
|
vert2 = vert
|
||||||
|
|
||||||
|
current_verts: list[MapItem] = []
|
||||||
|
|
||||||
|
first_item: MapItem = MapItem()
|
||||||
|
first_item.current_node = vert1
|
||||||
|
first_item.length = 0
|
||||||
|
first_item.marched_paths = []
|
||||||
|
current_verts.append(first_item)
|
||||||
|
|
||||||
|
def find_next_edge() -> list[bmesh.types.BMEdge]:
|
||||||
|
if len(current_verts) == 0: #all paths have been exausted.
|
||||||
|
return []
|
||||||
|
for mapeditem in current_verts:
|
||||||
|
current_verts.remove(mapeditem)
|
||||||
|
for edge in mapeditem.current_node.link_edges:
|
||||||
|
if edge.seam and (edge not in mapeditem.marched_paths):
|
||||||
|
for vert_new in edge.verts:
|
||||||
|
if vert_new != mapeditem.current_node:
|
||||||
|
if vert_new == vert2:
|
||||||
|
mapeditem.marched_paths.append(edge)
|
||||||
|
return mapeditem.marched_paths
|
||||||
|
first_item: MapItem = MapItem()
|
||||||
|
first_item.current_node = vert_new
|
||||||
|
first_item.length = mapeditem.length+1
|
||||||
|
first_item.marched_paths = []
|
||||||
|
first_item.marched_paths.extend(mapeditem.marched_paths)
|
||||||
|
first_item.marched_paths.append(edge)
|
||||||
|
current_verts.append(first_item)
|
||||||
|
return find_next_edge()
|
||||||
|
|
||||||
|
mesh.select_flush(False)
|
||||||
|
path: list[bmesh.types.BMEdge] = find_next_edge()
|
||||||
|
for edge in path:
|
||||||
|
edge.select = True
|
||||||
|
for vert in edge.verts:
|
||||||
|
vert.select = True
|
||||||
|
bpy.ops.mesh.select_mode(type='EDGE')
|
||||||
|
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
@@ -167,6 +167,7 @@
|
|||||||
"Optimization.remove_doubles_completed": "Remove doubles completed successfully",
|
"Optimization.remove_doubles_completed": "Remove doubles completed successfully",
|
||||||
|
|
||||||
"Tools.label": "Tools",
|
"Tools.label": "Tools",
|
||||||
|
"Tools.mesh_title": "Mesh Tools",
|
||||||
"Tools.general_title": "General Tools",
|
"Tools.general_title": "General Tools",
|
||||||
"Tools.select_armature": "Select an Armature",
|
"Tools.select_armature": "Select an Armature",
|
||||||
"Tools.convert_resonite": "Convert to Resonite",
|
"Tools.convert_resonite": "Convert to Resonite",
|
||||||
@@ -216,6 +217,8 @@
|
|||||||
"Tools.clean_weights_success": "Removed {count} zero-weight bones",
|
"Tools.clean_weights_success": "Removed {count} zero-weight bones",
|
||||||
"Tools.clean_weights_threshold": "Weight Threshold",
|
"Tools.clean_weights_threshold": "Weight Threshold",
|
||||||
"Tools.clean_weights_threshold_desc": "Minimum weight value to consider a bone as weighted",
|
"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.merge_title": "Merge Tools",
|
"Tools.merge_title": "Merge Tools",
|
||||||
"Tools.merge_to_active": "Merge to Active",
|
"Tools.merge_to_active": "Merge to Active",
|
||||||
"Tools.merge_to_active_desc": "Merge selected bones to active bone",
|
"Tools.merge_to_active_desc": "Merge selected bones to active bone",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from ..functions.tools.bone_tools import (
|
|||||||
from ..functions.tools.standardize_armature import AvatarToolkit_OT_StandardizeArmature
|
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.merge_tools import AvatarToolkit_OT_MergeToActive, AvatarToolkit_OT_MergeToParent, AvatarToolkit_OT_ConnectBones
|
||||||
from ..functions.tools.rigify_converter import AvatarToolkit_OT_ConvertRigifyToUnity
|
from ..functions.tools.rigify_converter import AvatarToolkit_OT_ConvertRigifyToUnity
|
||||||
|
from ..functions.tools.general_mesh_tools import AvatarToolkit_OT_SelectShortestSeamPath
|
||||||
|
|
||||||
class AvatarToolKit_PT_ToolsPanel(Panel):
|
class AvatarToolKit_PT_ToolsPanel(Panel):
|
||||||
"""Panel containing various tools for avatar customization and optimization"""
|
"""Panel containing various tools for avatar customization and optimization"""
|
||||||
@@ -59,6 +60,13 @@ class AvatarToolKit_PT_ToolsPanel(Panel):
|
|||||||
col.operator(AvatarToolKit_OT_CreateDigitigradeLegs.bl_idname, text=t("Tools.create_digitigrade"), icon='BONE_DATA')
|
col.operator(AvatarToolKit_OT_CreateDigitigradeLegs.bl_idname, text=t("Tools.create_digitigrade"), icon='BONE_DATA')
|
||||||
col.operator(AvatarToolKit_OT_FlipCurrentKeyFrames.bl_idname,text=t("Tools.flip_pose_frames"),icon="ACTION")
|
col.operator(AvatarToolKit_OT_FlipCurrentKeyFrames.bl_idname,text=t("Tools.flip_pose_frames"),icon="ACTION")
|
||||||
|
|
||||||
|
# Mesh Tools
|
||||||
|
mesh_box: UILayout = layout.box()
|
||||||
|
col = mesh_box.column(align=True)
|
||||||
|
col.label(text=t("Tools.mesh_title"), icon='MESH_DATA')
|
||||||
|
col.separator(factor=0.5)
|
||||||
|
col.operator(AvatarToolkit_OT_SelectShortestSeamPath.bl_idname,text=t("Tools.find_shortest_seam_path"),icon="MESH_DATA")
|
||||||
|
|
||||||
|
|
||||||
# Standardization Tools
|
# Standardization Tools
|
||||||
standardize_box: UILayout = bone_box.box()
|
standardize_box: UILayout = bone_box.box()
|
||||||
|
|||||||
Reference in New Issue
Block a user