Hi @maje90,
Here’s a shot at trying to extract and visualize edge chains based on an angle value and a minimum edge chain length.
I get “decent” results but it could be improved of course. Open to ideas… this is a bit out of my “wheel house” as I don’t deal with mesh topology often.
This does not currently handle overlapping segments FYI, nor does it clean or join them
Angle: 3
Min Length: 10
Angle: 5
Min Length: 10
Angle: 3
Min Length: 2
Code:
#! python 2
import Rhino
import rhinoscriptsyntax as rs
import scriptcontext
import System
import collections
from math import degrees
# Tolerance for considering edges to be in the same chain
angle_tol = 3.0 # degrees
min_chain_length = 3 # Minimum number of edges in a chain to be considered
def GetEdgeVector(mesh, edge_index):
"""Get normalized vector for an edge"""
vert_indices = mesh.TopologyEdges.GetTopologyVertices(edge_index)
start = mesh.TopologyVertices.Item[vert_indices[0]]
end = mesh.TopologyVertices.Item[vert_indices[1]]
edge_vector = Rhino.Geometry.Vector3d(end - start)
edge_vector.Unitize()
return edge_vector, start, end
def AngleBetweenEdges(vector1, vector2):
"""Compute smallest angle in degrees between two vectors"""
rad_angle = Rhino.Geometry.Vector3d.VectorAngle(vector1, vector2)
angle = degrees(rad_angle)
# Take the smaller angle (considering that vectors could point in opposite directions)
if angle > 90:
angle = 180 - angle
return angle
def FindConnectedEdges(mesh, edge_index):
"""Find all edges connected to the endpoints of the given edge"""
edge_vert_indices = mesh.TopologyEdges.GetTopologyVertices(edge_index)
connected_edges = set()
# For each vertex of the edge
for vert_index in edge_vert_indices:
# Get all edges connected to this vertex
edges = mesh.TopologyVertices.ConnectedEdges(vert_index)
for e in edges:
if e != edge_index: # Don't include the original edge
connected_edges.add(e)
return connected_edges
def FindNextEdgeInChain(mesh, current_edge, current_vector, visited_edges):
"""Find the next edge in the chain that has the smallest angle difference"""
connected_edges = FindConnectedEdges(mesh, current_edge)
best_edge = None
best_angle = float('inf')
for edge in connected_edges:
if edge in visited_edges:
continue
edge_vector, _, _ = GetEdgeVector(mesh, edge)
# Try both directions of the vector
angle1 = AngleBetweenEdges(current_vector, edge_vector)
angle2 = AngleBetweenEdges(current_vector, -edge_vector)
angle = min(angle1, angle2)
if angle < angle_tol and angle < best_angle:
best_edge = edge
best_angle = angle
return best_edge
def FindEdgeChains(mesh, min_length=2):
"""Find all chains of connected edges within the angle tolerance"""
chains = []
visited_edges = set()
# Try starting from each edge
for start_edge in range(mesh.TopologyEdges.Count):
if start_edge in visited_edges:
continue
# Start a new chain
current_chain = [start_edge]
visited_edges.add(start_edge)
# Get the edge vector
current_vector, _, _ = GetEdgeVector(mesh, start_edge)
# Find the next edge in the chain
next_edge = FindNextEdgeInChain(mesh, start_edge, current_vector, visited_edges)
# Continue adding edges to the chain
while next_edge is not None:
current_chain.append(next_edge)
visited_edges.add(next_edge)
current_vector, _, _ = GetEdgeVector(mesh, next_edge)
next_edge = FindNextEdgeInChain(mesh, next_edge, current_vector, visited_edges)
# Only consider chains with at least the minimum length
if len(current_chain) >= min_length:
chains.append(current_chain)
return chains
def FindEdgeLoops(mesh, chains):
"""Try to connect chains to form loops"""
loops = []
remaining_chains = chains[:]
while remaining_chains:
current_loop = remaining_chains.pop(0)
# Try to extend the current loop with other chains
changed = True
while changed:
changed = False
for i, chain in enumerate(remaining_chains):
# Check if the first edge of the chain connects to the last edge of the loop
if chain[0] in FindConnectedEdges(mesh, current_loop[-1]):
current_loop.extend(chain)
del remaining_chains[i]
changed = True
break
# Check if the last edge of the chain connects to the first edge of the loop
elif chain[-1] in FindConnectedEdges(mesh, current_loop[0]):
current_loop = chain + current_loop
del remaining_chains[i]
changed = True
break
# Check if the loop is closed (first and last edges are connected)
if current_loop[0] in FindConnectedEdges(mesh, current_loop[-1]):
loops.append(current_loop)
else:
# It's an open chain
loops.append(current_loop)
return loops
def VisualizeEdgeChains(mesh, chains):
"""Visualize each edge chain with a different color"""
colors = [
System.Drawing.Color.Red,
System.Drawing.Color.Blue,
System.Drawing.Color.Green,
System.Drawing.Color.Yellow,
System.Drawing.Color.Magenta,
System.Drawing.Color.Cyan,
System.Drawing.Color.Orange,
System.Drawing.Color.Purple,
System.Drawing.Color.Brown,
System.Drawing.Color.Pink
]
for i, chain in enumerate(chains):
color = colors[i % len(colors)]
for edge in chain:
# Get edge geometry
line = mesh.TopologyEdges.EdgeLine(edge)
# Add the line to the document with the chain's color
attr = Rhino.DocObjects.ObjectAttributes()
attr.ColorSource = Rhino.DocObjects.ObjectColorSource.ColorFromObject
attr.ObjectColor = color
scriptcontext.doc.Objects.AddLine(line, attr)
scriptcontext.doc.Views.Redraw()
def GetMeshEdgeChains():
mesh_id = rs.GetObject("Mesh", 32, True, False)
if mesh_id is None:
return
if not rs.IsObject(mesh_id): return
mesh_obj = rs.coercerhinoobject(mesh_id)
mesh = mesh_obj.Geometry
if mesh.Faces.QuadCount != 0:
print "Triangulate mesh first"
return
# Let the user input the minimum chain length
min_length = rs.GetInteger("Minimum number of edges in a chain", min_chain_length, 2, 100)
if min_length is None: min_length = min_chain_length # Use default if canceled
# Let the user input the angle tolerance
user_angle_tol = rs.GetReal("Angle tolerance in degrees", angle_tol, 0.1, 45.0)
if user_angle_tol is not None:
global angle_tol
angle_tol = user_angle_tol
# Find all edge chains
chains = FindEdgeChains(mesh, min_length)
# Try to form loops
loops = FindEdgeLoops(mesh, chains)
print "Found {} edge chains with at least {} edges (angle tolerance: {:.1f} degrees)".format(
len(chains), min_length, angle_tol)
# Visualize the chains
VisualizeEdgeChains(mesh, chains)
# Optionally, also visualize the loops with different colors
print "Found {} edge loops".format(len(loops))
if loops:
# Only visualize loops if they have at least the minimum length
filtered_loops = [loop for loop in loops if len(loop) >= min_length]
print "Found {} edge loops with at least {} edges".format(len(filtered_loops), min_length)
if filtered_loops:
VisualizeEdgeChains(mesh, filtered_loops)
GetMeshEdgeChains()