select_outer_edges()
.py file (e.g., hard_edge_selector.py).Documents\maya\<version>\scripts
~/Library/Preferences/Autodesk/maya/<version>/scripts
~/maya/<version>/scripts
import hard_edge_selector hard_edge_selector.select_outer_edges()
import and function call lines above.import hard_edge_selector hard_edge_selector.select_outer_edges()
import hard_edge_selector hard_edge_selector.select_outer_edges()
With these options, you can integrate the script seamlessly into your Maya workflow and run it however you prefer. If you have any questions, feel free to reach out. Happy modeling!
import maya.api.OpenMaya as om, math
def calculate_angle(v1, v2):
return math.degrees(math.acos(min(max(sum(a * b for a, b in zip(v1, v2)) /
(math.sqrt(sum(a ** 2 for a in v1)) * math.sqrt(sum(b ** 2 for b in v2))), -1.0), 1.0)))
def store_edge_positions(obj, edges):
positions = [[list(obj.getPoint(v1, om.MSpace.kWorld)), list(obj.getPoint(v2, om.MSpace.kWorld))] for v1, v2 in edges]
if not cmds.attributeQuery("edgePositions", node=obj.fullPathName(), exists=True):
cmds.addAttr(obj.fullPathName(), longName="edgePositions", dataType="string")
cmds.setAttr(f"{obj.fullPathName()}.edgePositions", str(positions), type="string")
def retrieve_edge_positions(obj):
if not cmds.attributeQuery("edgePositions", node=obj.fullPathName(), exists=True): return []
return eval(cmds.getAttr(f"{obj.fullPathName()}.edgePositions") or "[]")
def match_edges_by_position(mesh, stored_positions):
matched_edges = []
for edge_index in range(mesh.numEdges):
v1, v2 = mesh.getEdgeVertices(edge_index)
pos1, pos2 = list(mesh.getPoint(v1, om.MSpace.kWorld)), list(mesh.getPoint(v2, om.MSpace.kWorld))
if [pos1, pos2] in stored_positions or [pos2, pos1] in stored_positions:
matched_edges.append(edge_index)
return matched_edges
def select_outer_edges(threshold=35):
selection = om.MGlobal.getActiveSelectionList()
if selection.length() == 0: return om.MGlobal.displayWarning("No objects selected.")
for i in range(selection.length()):
try:
obj = selection.getDagPath(i)
if not obj.hasFn(om.MFn.kMesh):
om.MGlobal.displayWarning(f"{obj.fullPathName()} is not a polygonal mesh. Skipping."); continue
mesh = om.MFnMesh(obj)
stored_positions = retrieve_edge_positions(obj)
if stored_positions:
matched_edges = match_edges_by_position(mesh, stored_positions)
if matched_edges:
[om.MGlobal.selectByName(f"{obj.fullPathName()}.e[{edge}]", om.MGlobal.kAddToList) for edge in matched_edges]
continue
edge_itr, selected_edges = om.MItMeshEdge(obj), []
while not edge_itr.isDone():
faces = edge_itr.getConnectedFaces()
if len(faces) == 2 and calculate_angle(mesh.getPolygonNormal(faces[0], om.MSpace.kWorld),
mesh.getPolygonNormal(faces[1], om.MSpace.kWorld)) >= threshold:
selected_edges.append((edge_itr.vertexId(0), edge_itr.vertexId(1)))
om.MGlobal.selectByName(f"{obj.fullPathName()}.e[{edge_itr.index()}]", om.MGlobal.kAddToList)
edge_itr.next()
if selected_edges: store_edge_positions(mesh, selected_edges)
except Exception as e:
om.MGlobal.displayError(f"Error processing {obj.fullPathName()}: {e}")
select_outer_edges()