Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
a09df1b
TEST: add surface meshing test helpers for cylinder-in-box and watert…
imikejackson Aug 10, 2026
f24529e
TEST: add coverage for CreateFullyIndexedPolycrystal and CreateAllBac…
imikejackson Aug 10, 2026
4f5e37b
TEST: reproduce SurfaceNets Face Label conflation of Feature 0 with e…
imikejackson Aug 10, 2026
5ab2faa
TEST: report all Face Label pairs in the SurfaceNets reproduction
imikejackson Aug 10, 2026
f8b0e92
FIX: stop SurfaceNets conflating Feature Id 0 with the exterior Face …
imikejackson Aug 10, 2026
6824672
FIX: emit SurfaceNets Node Types in the shared NodeType convention
imikejackson Aug 10, 2026
3db4a1f
FEAT: add Omit Bounding Box Skin to QuickSurfaceMesh
imikejackson Aug 10, 2026
662dd61
FIX: bump QuickSurfaceMeshFilter parametersVersion for new bool key
imikejackson Aug 10, 2026
6fd218a
FEAT: add Omit Bounding Box Skin to SurfaceNets
imikejackson Aug 10, 2026
b92813d
FEAT: add Omit Bounding Box Skin to M3CSurfaceMeshing
imikejackson Aug 10, 2026
0c541b5
FEAT: warn on empty mesh and prove Omit Bounding Box Skin is a no-op
imikejackson Aug 11, 2026
54831e1
FEAT: add a Face Labels criterion to ExtractInternalSurfaces
imikejackson Aug 11, 2026
7e74bbe
DOC: document Omit Bounding Box Skin and correct the Node Type tables
imikejackson Aug 11, 2026
850203d
DOC: clarify Node Type formula and document the all-background degene…
imikejackson Aug 11, 2026
336548c
FIX: rename the criterion key to satisfy the _index convention
imikejackson Aug 11, 2026
a426663
FIX: address final whole-branch review findings for Omit Bounding Box…
imikejackson Aug 11, 2026
9a2d1d8
TEST: regenerate SurfaceNets exemplars for the corrected Face Labels …
imikejackson Aug 11, 2026
8843fdd
BUG: Fix pre-existing SurfaceNets Z offset and QuickSurfaceMesh wall …
imikejackson Aug 12, 2026
4f5fcbf
ENH: Convert Omit Bounding Box Skin from bool to a two-value ChoicesP…
imikejackson Aug 12, 2026
658f186
ENH: De-duplicate skin-removal warning, warn on no-op prune, narrow M…
imikejackson Aug 12, 2026
3e6fbc0
TEST: fix stale Bounding Box Skin label, test hygiene, and cross-mesh…
imikejackson Aug 12, 2026
988107d
DOC: Correct M3C orphan-vertex claim, document sentinel validation/wa…
imikejackson Aug 12, 2026
f8f68c0
DOC: Fix incorrect Unused NodeType constant and inverted bisection de…
imikejackson Aug 12, 2026
d754c27
FIX: correct wall-face warning wording, add cancellable sentinel scan…
imikejackson Aug 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,10 @@
},
{
"args": {
"bounding_box_skin_mode_index": {
"value": 0,
"version": 1
},
"face_data_group_name": {
"value": "Face Data",
"version": 1
Expand Down Expand Up @@ -288,7 +292,7 @@
"value": "TriangleDataContainer",
"version": 1
},
"parameters_version": 2,
"parameters_version": 3,
"repair_triangle_winding": {
"value": true,
"version": 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@
},
{
"args": {
"bounding_box_skin_mode_index": {
"value": 0,
"version": 1
},
"face_data_group_name": {
"value": "Face Data",
"version": 1
Expand Down Expand Up @@ -164,7 +168,7 @@
"value": "TriangleDataContainer",
"version": 1
},
"parameters_version": 2,
"parameters_version": 3,
"repair_triangle_winding": {
"value": true,
"version": 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,44 @@ Geometry

## Description

This **Filter** extracts any **Triangles** from the supplied **Triangle Geometry** that contain any *internal nodes*, then uses these extracted **Triangles** to create a new **Data Container** with the reduced **Triangle Geometry**. This operation is the same as removing all **Triangles** that only lie of the outer surface of the supplied **Triangle Geometry**. The user must supply a "Node Type" **Vertex Attribute Array** that defines the type for each node of the **Triangle Geometry**. Node types may take the following values:
This **Filter** extracts any **Triangles** from the supplied **Triangle Geometry** that contain any *internal nodes*, then uses these extracted **Triangles** to create a new **Data Container** with the reduced **Triangle Geometry**. This operation is the same as removing all **Triangles** that only lie of the outer surface of the supplied **Triangle Geometry**. Internal surfaces can be identified two ways, chosen with the **Internal Surface Criterion** parameter below.

### Quick Surface Mesh
### Node Type Range Criterion

| Id Value | Node Type |
|----------|-----------|
| 2 | Normal **Vertex |
| 3 | Triple Line |
| 4 | Quadruple Point |
| 12 | Normal **Vertex** on the outer surface |
| 13 | Triple Line on the outer surface |
| 14 | Quadruple Point on the outer surface |

### SurfaceNets Mesh
With the **Node Type Range** criterion, the user must supply a "Node Type" **Vertex Attribute Array** that defines the type for each node of the **Triangle Geometry**. All three surface meshing **Filters** (Create Surface Mesh (QuickMesh), Create Surface Mesh (Surface Nets), and Create Surface Mesh (M3C)) emit Node Types in the same convention:

| Id Value | Node Type |
|----------|-----------|
| 0 | Normal **Vertex |
| 2 | Normal **Vertex** |
| 3 | Triple Line |
| 4-8 | Quadruple Point |
| 13 | Normal **Vertex** on the outer surface |
| 14 | Triple Line on the outer surface |
| 15-18 | Quadruple Point on the outer surface |
| 4 | Quadruple Point (four or more Features meet) |
| 12 | Normal **Vertex** on the outer surface of the bounding box |
| 13 | Triple Line on the outer surface of the bounding box |
| 14 | Quadruple Point on the outer surface of the bounding box |

The value is the number of distinct Features meeting at the node, capped at 4, plus 10 when
the node lies on the bounding box wall. The region outside the volume counts as one of those
owners, which is why an ordinary vertex on the wall between the exterior and a single Feature
is `12` — two owners, one of them the exterior — rather than `11`.

This **Filter** has the effect of removing any **Triangles** that only contain **Vertices** whose node Id values fall outside of the min and max that the user sets. In general, this *node type* array is created when the original surface mesh is created.

It is unknown until runtime how the **Geometry** will be changed by removing certain **Vertices** and **Triangles**.

### Choosing a Criterion

**Node Type Range** keeps a triangle only when all three of its nodes have a Node Type
inside the given range. Because every node on the bounding box wall is promoted by 10, this
also discards internal boundary triangles that merely *touch* the wall — leaving a
one-triangle-wide gap wherever an internal surface meets the box. A Feature flush with the
box wall will come out open.

**Face Labels** instead discards only the faces whose Face Labels are `{-1, 0}`: the
bounding box wall where it borders the background. Faces where the wall caps a real Feature
are kept, so Features flush with the box stay closed, and no rim is eroded. This is the
same rule as the **Bounding Box Skin** option's **Background-Backed Walls Only** mode on the
surface meshing filters, and is the better choice for meshes that already exist.

% Auto generated parameter table will be inserted here

## Example Pipelines
Expand Down
16 changes: 11 additions & 5 deletions src/Plugins/SimplnxCore/docs/HierarchicalSmoothFilter.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ This **Filter** applies hierarchical smoothing to a triangle surface mesh repres

This hierarchical approach preserves the topology of the grain boundary network while producing smooth surfaces. The smoothing parameter is optimized via interval bisection to balance smoothness against displacement from the original mesh.

Because every stage above is driven by the Node Type value, smoothing a mesh produced by Create
Surface Mesh (Surface Nets) now behaves differently than it did previously: that **Filter**'s Node
Types were corrected to follow the `2`/`3`/`4`/`12`/`13`/`14` convention this **Filter** expects,
so vertices that were previously misclassified (and therefore held fixed or smoothed in the wrong
stage) are now classified correctly. This is a correction, not a regression.

Nodes that are displaced beyond the error threshold (a multiple of a reference edge length) are rejected and reset to their original positions.

## Algorithm Overview
Expand Down Expand Up @@ -48,12 +54,12 @@ where `I` is the identity matrix, `L` is the reduced graph Laplacian, `y_origina
The algorithm uses **interval bisection** to find the optimal `epsilon`:


1. Start at `epsilon = 0.5`.
2. Compute a numerical derivative of the objective function (the total Laplacian residual energy).
3. If the derivative is near zero (flat region), the current `epsilon` is not in the active tradeoff zone. Halve `epsilon` and try again.
4. Repeat until either a significant slope is found or the iteration limit is reached.
1. Start at `epsilon = 0.5`, with a step size of `epsilon / 2`.
2. Compute a numerical derivative (slope) of the objective function (the total Laplacian residual energy) at the current `epsilon`.
3. If the slope's magnitude is still significant, step `epsilon` up or down (in the direction that reduces the slope) by the current step size, then halve the step size.
4. Repeat until the slope's magnitude falls below the convergence threshold (a flat region — the optimal `epsilon` has been found) or the iteration limit is reached.

The **Max Bisection Iterations** parameter controls how many halvings are attempted. The default value of **53** comes from `log2(10^16) ~ 53`, which is the number of bisection steps needed to resolve a double-precision floating point value to machine epsilon. In practice, the search often converges in far fewer iterations because it terminates early once a significant slope is detected.
The **Max Bisection Iterations** parameter controls how many halvings are attempted. The default value of **53** comes from `log2(10^16) ~ 53`, which is the number of bisection steps needed to resolve a double-precision floating point value to machine epsilon. In practice, the search often converges in far fewer iterations because it stops as soon as the slope flattens out below the convergence threshold, well before the iteration limit is reached.

**Practical guidance:**

Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
23 changes: 15 additions & 8 deletions src/Plugins/SimplnxCore/docs/LaplacianSmoothingFilter.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,27 @@ At the conclusion of the filter these extra internal arrays will be reclaimed by

The values for the *Node Type* array can take one of the following values.

namespace SurfaceMesh {
namespace nx::core {
namespace NodeType {
const int8_t Unused = -1;
const int8_t Default = 2;
const int8_t TriplePoint = 3;
const int8_t QuadPoint = 4;
const int8_t SurfaceDefault = 12;
const int8_t SurfaceTriplePoint = 13;
const int8_t SurfaceQuadPoint = 14;
inline constexpr int8_t Unused = 0;
inline constexpr int8_t Default = 2;
inline constexpr int8_t TriplePoint = 3;
inline constexpr int8_t QuadPoint = 4;
inline constexpr int8_t SurfaceDefault = 12;
inline constexpr int8_t SurfaceTriplePoint = 13;
inline constexpr int8_t SurfaceQuadPoint = 14;
}
}

If your surface mesh is lacking a `Node Type` array, you can simply create a DataArray inside the Vertex Data Attribute Matrix. The type should be "int8" and have an initialization value of 3. This will allow **all** nodes to move.

Any Node Type value not listed above (including `0`/`Unused`) leaves that vertex's lambda at `0.0`,
so the vertex does not move. Smoothing a mesh produced by Create Surface Mesh (Surface Nets) now
behaves differently than it did previously, because that **Filter**'s Node Types were corrected to
follow this convention: most vertices in a Surface Nets mesh were previously pinned in place
because their Node Type value did not match any of the cases above. This is a correction, not a
regression — those vertices are now free to move as their Node Type intends.

For more information on surface meshing, visit the tutorial.

% Auto generated parameter table will be inserted here
Expand Down
66 changes: 65 additions & 1 deletion src/Plugins/SimplnxCore/docs/M3CSurfaceMeshingFilter.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ modeling and simulation workflows.
### Node Types

The **Node Types** array uses the same convention as the Surface Nets **Filter**. Interior values
denote how many **Features** meet at the vertex; the exterior (volume-boundary) variants add 10.
denote how many **Features** meet at the vertex, capped at 4; the exterior (volume-boundary)
variants add 10. The region outside the volume counts as one of those owners, which is why an
ordinary vertex on the wall between the exterior and a single Feature is `12` — two owners, one
of them the exterior — rather than `11`.

| Node Type | Meaning |
|-----------|---------|
Expand All @@ -64,6 +67,67 @@ heuristic, which does not guarantee globally consistent normals across the whole
consistent across connected faces. This is recommended for meshes that will be used for normal- or
curvature-dependent analysis.

### Omitting the Bounding Box Skin

By default this filter generates triangles covering all six outer walls of the Image
Geometry's bounding box. These faces are artifacts of where the volume was cropped rather
than real interfaces, and they receive a Face Label of `-1` on the exterior side.

Enabling the **Bounding Box Skin** option's **Background-Backed Walls Only** mode suppresses a
wall face when the voxel behind it is background (Feature Id 0) — that is, when its Face Labels would be `{-1, 0}`. Wall faces
that cap a *real* Feature are still generated, because that cut plane is the only possible
closure for a Feature flush with the box. A cylinder sitting flush with the box floor
therefore comes out as a closed surface with no surrounding box.

On a fully-indexed volume with no Feature Id 0 voxels, nothing is dropped and the option
has no effect — every boundary Feature already needs its wall cap to stay closed. The same warning
also fires on a volume whose background is fully enclosed as interior porosity: no bounding-box
wall voxel is background-backed even though the volume is full of Feature Id 0 internally, so the
option again drops nothing. Rather than silently doing nothing, the **Filter** reports this with a
warning (code `-56342`): no bounding-box **wall** face is backed by background, so the option has
nothing to prune on this input. The warning says nothing about whether the volume contains
background elsewhere — only that none of it borders a wall.

Because the test is per-face rather than per-vertex, no triangles are lost along the rim
where an internal boundary meets the box wall. With the option **off**, wherever an internal
Feature-Feature boundary meets the bounding box wall, three faces share that edge: the internal
boundary quad and the two wall quads on either side of it — a non-manifold T-junction that is
inherent to including the full box skin. With the option **on**, the background-backed wall quad
on that edge is dropped, leaving exactly two faces sharing it, which is manifold. The option
therefore does not merely remove unwanted geometry: in the configurations exercised by this
**Filter**'s cross-mesher conformance test (a cylinder **Feature** flush with the box wall) and by
a separate corner-Feature test (a **Feature** occupying a box corner where three suppressed wall
planes meet), enabling it produces a watertight mesh where leaving it off does not. This is not a
universal guarantee of watertightness for arbitrary input.

If every voxel in the volume is background (Feature Id 0), every face is a background-backed
wall face and the option removes all of them. The **Filter** reports a warning (code `-56340`)
and creates the **Triangle Geometry** with zero faces. Unlike Create Surface Mesh (QuickMesh) and
Surface Nets, this is **not** zero vertices for M3C: as described in the note below, M3C's
candidate-node generation leaves a handful of pre-existing orphan vertices that the option's
pruning does not clear, so they remain in the output. This is treated as success, not an error,
because the input is legal — it simply contains no internal interface and no Feature to cap.

### Feature Id Validation

Independently of the **Bounding Box Skin** setting, this **Filter** always rejects a **Feature
Ids** array that contains a negative value or a value equal to `INT32_MAX`, because both collide
with sentinel values M3C's algorithm uses internally to represent ghost cells and the outside of
the volume. A **Feature Id** must therefore be in the range `0` to `INT32_MAX - 1`. A rejected
value produces an error (code `-56343`) naming the offending value, its tuple index, and the
array's **Data Path**. This is a mitigation for the underlying sentinel-collision design (tracked
as issue #1705), not a fix for it.

**Note:** M3C's candidate-node generation always produces a handful of node entries near the
volume boundary that no triangle references, even with the option disabled — these pre-existing
orphan vertices are present in stock M3C output regardless of the **Bounding Box Skin** setting
(tracked as issue #1706). The **Bounding Box Skin** option's **Background-Backed Walls Only** mode
does not touch them: it clears only the vertices that its own pruning orphans (vertices that were
referenced exclusively by a dropped wall face), and leaves every pre-existing orphan exactly as it
was. Consequently, on an all-background volume the pruned output is zero faces with those
pre-existing orphan vertices still present — which is why the `-56340` warning above reports the
remaining vertex count rather than assuming it is zero.

### Notes and Limitations

- The volume is automatically wrapped in a temporary ghost layer so that **Features** touching the
Expand Down
Loading
Loading