Skip to content
Merged
18 changes: 16 additions & 2 deletions mesh-doctor/src/geos/mesh_doctor/actions/euler.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class SurfaceComponent:
numBoundaryEdges: int
numNonManifoldEdges: int
interpretation: str
nonManifoldEdgeEndpoints: tuple[ tuple[ int, int ], ...] = field( default_factory=tuple )


@dataclass( frozen=True )
Expand Down Expand Up @@ -224,11 +225,22 @@ def __interpretSurface( chi: int, boundaryEdges: int, nonManifoldEdges: int ) ->
return f"surface with multiple boundaries / holes (chi={chi})"
return f"open (chi={chi}, unusual)"

def _toGlobalEdgeId( mesh: vtk.vtkUnstructuredGrid, edge: tuple[ int, int ] ) -> tuple[ int, int ]:
"""Return a global edge ID for a pair of point IDs."""
p0, p1 = edge
ids = mesh.GetPointData().GetGlobalIds()
if ids is not None:
ids = vtk_to_numpy( ids ).astype( np.int64, copy=False )
return ( ids[p0], ids[p1] )
else:
setupLogger.warning( "No globalIds found. Falling back to local id for non-manifold edge detection.", stacklevel=2 )
return edge

def __surfaceComponentsFromColored( colored: vtk.vtkUnstructuredGrid ) -> list[ SurfaceComponent ]:
"""Compute (V, E, F, chi, boundary, non-manifold) per RegionId of a colored 2D mesh."""
rid = vtk_to_numpy( colored.GetCellData().GetArray( "RegionId" ) ).astype( np.int64 )
cells = colored.GetCells()
# GetConnectivityArray / GetOffsetsArray require VTK 9+
conn = vtk_to_numpy( cells.GetConnectivityArray() ).astype( np.int64, copy=False )
off = vtk_to_numpy( cells.GetOffsetsArray() ).astype( np.int64, copy=False )

Expand All @@ -251,7 +263,8 @@ def __surfaceComponentsFromColored( colored: vtk.vtkUnstructuredGrid ) -> list[
E = len( edgeCount )
F = int( len( sel ) )
bE = sum( 1 for c in edgeCount.values() if c == 1 )
nm = sum( 1 for c in edgeCount.values() if c > 2 )
nmEdges = tuple( _toGlobalEdgeId( colored, ek ) for ek, c in edgeCount.items() if c > 2 )
nm = len( nmEdges )
Comment thread
bd713 marked this conversation as resolved.
chi = V - E + F
components.append(
SurfaceComponent( componentId=int( regionId ),
Expand All @@ -262,7 +275,8 @@ def __surfaceComponentsFromColored( colored: vtk.vtkUnstructuredGrid ) -> list[
eulerCharacteristic=chi,
numBoundaryEdges=bE,
numNonManifoldEdges=nm,
interpretation=__interpretSurface( chi, bE, nm ) ) )
interpretation=__interpretSurface( chi, bE, nm ),
nonManifoldEdgeEndpoints=nmEdges ) )
return components


Expand Down
7 changes: 7 additions & 0 deletions mesh-doctor/src/geos/mesh_doctor/parsing/eulerParsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,13 @@ def __displaySurfaceGroup( g: SurfaceGroup ) -> None:
setupLogger.results( " " + f"{c.componentId:>5} {c.numCells:>9,} {c.numVertices:>8,} {c.numEdges:>8,} "
f"{c.numFaces:>8,} {c.eulerCharacteristic:>5} {c.numBoundaryEdges:>6,} "
f"{c.numNonManifoldEdges:>4,} {c.interpretation}" )
if c.nonManifoldEdgeEndpoints:
_MAX_DISPLAY = 10
shown = c.nonManifoldEdgeEndpoints[ :_MAX_DISPLAY ]
setupLogger.results( " non-manifold edge endpoints (point id pairs): "
+ ", ".join( f"({a},{b})" for a, b in shown )
+ ( f" … (+{len(c.nonManifoldEdgeEndpoints)-_MAX_DISPLAY} more)"
if len( c.nonManifoldEdgeEndpoints ) > _MAX_DISPLAY else "" ) )
if len( g.components ) > 1:
setupLogger.results( f" WARNING: {len(g.components)} components — verify isolated cells" )

Expand Down
Loading