Mesh naked boundary - any method to make faster and accurate?

I’m trying to get the naked boundary (closed polyline) from two meshes with multiple open faces… I’m getting satisfactory results within geometry, but as I increase the mesh resolution, the time processing rises a lot.

Computing with more straightforward methods or some plugin components is much faster. Still, it doesn’t get the completely accurate naked boundary polyline, or they don’t appear in the same topology for later processes like lofting them.

(The following steps to consider…I’m doing a mesh loft in between the two sets of polylines and joining the meshes to get a close mesh)

in the IMG below you can visualize the black segements which are missing in the faster process to get the polylines.


import Rhino
from collections import defaultdict

def pt3f_pt3d(pt3f):
    return Rhino.Geometry.Point3d(pt3f.X, pt3f.Y, pt3f.Z)

def naked_boundary(mesh):
    e_dict = defaultdict(int)
    naked_e = []
    
    for face in mesh.Faces:
        indices = [face.A, face.B, face.C]
        if face.IsQuad:
            indices.append(face.D)
        num_vx = 4 if face.IsQuad else 3
        for i in range(num_vx):
            v1 = indices[i]
            v2 = indices[(i + 1) % num_vx]
            e = tuple(sorted((v1, v2)))
            e_dict[e] += 1
    
    for e, count in e_dict.iteritems():
        if count == 1:
            v1, v2 = e
            pt1_f = mesh.Vertices[v1]
            pt2_f = mesh.Vertices[v2]
            pt1 = pt3f_pt3d(pt1_f)
            pt2 = pt3f_pt3d(pt2_f)
            naked_e.append((pt1, pt2))
            
    edges = naked_e[:]
    pl = []
    
    while edges:
        current_pl = []
        current_e = edges.pop(0)
        current_pl.extend(current_e)
        extended = True
        while extended:
            extended = False
            tol = Rhino.RhinoMath.SqrtEpsilon
                
            st_pt = current_pl[0]
            for i, edge in enumerate(edges):
                if edge[1].DistanceTo(st_pt) <= tol:
                    current_pl.insert(0, edge[0])
                    del edges[i]
                    extended = True
                    break
                elif edge[0].DistanceTo(st_pt) <= tol:
                    current_pl.insert(0, edge[1])
                    del edges[i]
                    extended = True
                    break
            if extended:
                continue
            end_pt = current_pl[-1]
            for i, edge in enumerate(edges):
                if edge[0].DistanceTo(end_pt) <= tol:
                    current_pl.append(edge[1])
                    del edges[i]
                    extended = True
                    break
                elif edge[1].DistanceTo(end_pt) <= tol:
                    current_pl.append(edge[0])
                    del edges[i]
                    extended = True
                    break
        unique_pl = [current_pl[0]]
        for pt in current_pl[1:]:
            if not pt.Equals(unique_pl[-1]):
                unique_pl.append(pt)
        
        if len(unique_pl) >= 2:
            polyline = Rhino.Geometry.Polyline(unique_pl)
            if polyline.IsValid:
                pl_curve = Rhino.Geometry.PolylineCurve(polyline)
                pl.append(pl_curve)
    
    return pl

if mesh and mesh.IsValid:
    nb = naked_boundary(mesh)
    crv = nb
else:
    crv = "Invalid or NO input"

241124_MESH NAKED BOUNDARY POLYLINE.gh (1.8 MB)

I’m wondering which is the correct one :slight_smile:

241124_MESH NAKED BOUNDARY POLYLINE_Re.gh (1.8 MB)

Hi Uri, that seems like a lot of code. Try using Mesh.GetNakedEdges instead, if I understood the problem correctly.

Edit: Could be faster, but it certainly is simpler:


241125_MESH NAKED BOUNDARY POLYLINE_Anders.gh (1.8 MB)

1 Like

Me too

Mesh_NakedVertices_V1.gh (1.4 MB)

BTW: Optional VV is using TopologyVertices conn (NOT Vertices) meaning that indexing in V is also MTV indexing. Same for naked TV.

BTW: As a challenge find the MTV indices (on a per naked Poly basis) and/or use the NakedStatus (and connectivity + recursion + tracking the visited Vertices) to extract the naked Poly (per disjoined Mesh). Note: NakedStatus refers to MV Indexing (see code inside the 3rd Method for the “remap” - from MTV to MV).

2 Likes

So, looking at @PeterFotiadis script, disjointing the meshes before computing the naked boundary speeds up all the calculations for different options, including the one I shared (from 13 sec to 876ms), which is already Great; thanks!

To check if the naked boundary is working correctly, I extended the script for the mesh loft and joined them to calculate the volume. If the final mesh is closed, I consider it the correct geometry.

When I try simple geometry, almost all the options work perfectly, so they are all good processes. However, in this example, mesh in gh still doesn’t allow me to get a closed mesh; the three options have the same result, with open mesh and the same volume.

BTW Thanks a lot


What example? The one that you posted in the first place? If yes what exactly is the goal? (from the snaps above I can’t understand a thing or two - plus I never work with native components).

BTW: if you have many meshes (disjoined from some collection and supposedly valid and manifold) consider a vis “help” thing that displays one (by index) plus the boundary plus the vertices indices … blah, blah (an auto Zoom - scale : user controlled - option could be handy). And what means mesh loft ??? (you mean “thicken” ?).

BTW: Mesh is a class using floats. Meaning the obvious for Meshes faaaar and away from global origin etc etc. Anyway before doing anything mastermind a way to check any Mesh (is it valid? is it manifold? - if not Connectivity could yield bananas).

BTW: As a challenge do a thing that extracts disjoined (if any) meshes. Provited that you understand fully what Connectivity (Vertices, Edges, Faces, Cats and Dogs) is all about.

BTW: A clothed TopoVertex, given the adjacent Vertices, is the one where these Vertices can yield a closed loop. A naked TopoEdge is the one that has one adjacent Face. A naked Face is the one that has at least a naked Edge (but what if has a naked Vertex ? you tell me). If any Edge has more than two adjacent Faces … this means bananas.

1 Like

That’s a great logic optimisation, thanks @PeterFotiadis. Definitely going in the ol’ goodie bag:

Next step: after disjoining, count vertices and do some “equal” load strategy (*) for a thread safe // thing (but advantages could be rather marginal).

(*) kinda having a List with N rnd integers and the task is to find M subLists ( M : num of CPU cores) where their Sums are “as equal” as possible.

Forgot to mention this:

See some NakedEdges Polys (from some intermediate state of solving a 3D Convex Hull thing [as Mesh] - quite challenging if you are after absolute speed). The Red (i.e non planar) one should been “splitted” in 2 (since we are talking about 2 “discreet” Polylines). Sometimes Rhino does that (see the 2 Greens (i.e. planar)) sometimes not. BTW: per state the Vertices.CombineIdentical(true,true) Method is used … so the N of Vertices equals the N of TopoVertices.


So for the general case … you’ll need a thing that checks (and splits) the Polylines (use a classic Recursion). Like (all used Polys shown are a single closed Poly):

Or (better) write a thing that extracts NakedEdges the correct way (use also Recursion on VV Conn + a NakedStatus bool Array).

1 Like