XelToFab
Guides

Pipeline Overview

How the XelToFab pipeline processes scalar fields into meshes

The pipeline

XelToFab processes design optimization output through six main stages, with an optional SDF evaluation intake path:

XelToFab pipeline with optional SDF Function → SDF Evaluate intake feeding Field, then Preprocess → Extract → Smooth → Repair → Remesh → Decimate Pipeline progression: raw field → binary threshold → extracted mesh → smoothed mesh

The orchestrator in pipeline.py chains these stages:

from xeltofab.pipeline import process

result = process(state)  # preprocess -> extract -> smooth -> repair -> remesh -> decimate

For SDF functions (neural models, analytical formulas), use process_from_sdf to evaluate the function on a grid first:

from xeltofab.pipeline import process_from_sdf

result = process_from_sdf(my_sdf, bounds=(-1, -1, -1, 1, 1, 1), resolution=128)

This evaluates the SDF on a uniform grid (or an adaptive octree grid with adaptive=True), then feeds into the same pipeline stages. See the SDF Functions guide for details.

PipelineState threading pattern

All stage functions follow the same signature:

def stage(state: PipelineState) -> PipelineState:

PipelineState is a Pydantic model that carries both the data and configuration through the pipeline. Stage functions never mutate the input state. Instead, they return a new state using model_copy(update={...}):

# Inside a stage function:
return state.model_copy(update={"vertices": new_verts, "faces": new_faces})

This creates a shallow copy with the specified fields replaced. Because copies share numpy arrays with the original, stage functions must always create new arrays rather than modifying existing ones in-place.

State fields

FieldTypeSet by
fieldndarrayUser input (loaded from file) or generated by process_from_sdf()
ndimintAuto-computed from field.ndim (2 or 3)
paramsPipelineParamsUser input (or defaults)
binaryndarray | Nonepreprocess()
volume_fractionfloat | Nonepreprocess()
contourslist[ndarray] | Noneextract() (2D only)
verticesndarray | Noneextract() (3D only)
facesndarray | Noneextract() (3D only)
smoothed_verticesndarray | Nonesmooth() (3D only)

The best_vertices property

PipelineState provides a best_vertices property that returns smoothed_vertices if smoothing has been applied, otherwise falls back to the raw vertices from extraction:

vertices = state.best_vertices  # smoothed if available, raw otherwise

This is used by save_mesh() and the visualization functions, so you always get the best available mesh quality without checking manually.

Direct extraction mode

When direct_extraction=True, the preprocessing stage is skipped entirely. Extraction operates on the continuous input field instead of a binarized version:

from xeltofab.state import PipelineParams

# Skip preprocessing, extract at level 0.3
params = PipelineParams(direct_extraction=True, extraction_level=0.3)

This is the default behavior for SDF fields (field_type="sdf"), where the input is already a clean continuous field and binarization would degrade quality. For density fields, direct extraction can be useful when the solver output is already well-converged and does not need cleanup.

In direct mode, the pipeline skips preprocessing and runs: extractsmoothrepairremeshdecimate.

2D vs 3D paths

The pipeline automatically detects whether the input is 2D or 3D based on field.ndim:

DimensionPreprocessingExtractionSmoothingOutput
2DSame (Gaussian, threshold, morphology with disk kernel)find_contours (marching squares)No-op for smooth/repair/remesh/decimateContour arrays in state.contours
3DSame (Gaussian, threshold, morphology with ball kernel)marching_cubesTaubin or bilateral smoothing, repair, remesh, decimateTriangle mesh in state.vertices / state.faces

For 2D fields, save_mesh() is not supported (raises an error). Use the visualization functions (plot_result, plot_comparison) to render contour output.

Stage details

SDF Evaluate (sdf_eval.py) — optional intake

When using process_from_sdf(), the SDF function is evaluated on a grid before entering the main pipeline. This stage is skipped entirely when using process() with pre-evaluated grid data.

ModeBehavior
adaptive=False (default)Uniform grid evaluation, one Z-slab at a time
adaptive=TrueOctree coarse-to-fine refinement, ~O(N²) evaluations

The output is a dense numpy array that feeds into the same extraction pipeline as any grid-loaded field. See the SDF Functions guide for details on the evaluation modes.

Preprocess (preprocess.py)

  1. Gaussian smoothing -- scipy.ndimage.gaussian_filter with configurable smooth_sigma
  2. Thresholding -- Binarize at the configured threshold value
  3. Morphological cleanup -- Opening then closing with a disk (2D) or ball (3D) structuring element of radius morph_radius
  4. Small component removal -- skimage.morphology.remove_small_objects removes components smaller than 0.5% of the total field size (minimum 8 pixels/voxels)

Sets state.binary and state.volume_fraction.

Extract (extract.py)

Dispatches to the configured extraction_method:

MethodBackendBest for
mc (default for density)scikit-image marching cubesDensity fields, general use
dc (default for SDF)Vendored sdftoolbox (CPU) or isoext (GPU)SDF fields with sharp features
surfnetsVendored sdftoolboxSDF fields, smooth output
manifoldmanifold3dWatertight output for FEA/CAD
  • 2D: Always uses skimage.measure.find_contours regardless of extraction_method
  • Extraction level defaults to threshold for density and 0.0 for SDF, overridable with extraction_level

In preprocessed mode, extraction operates on the binary field. In direct mode, it operates on the original continuous scalar field.

See the Extraction Methods guide for details, GPU benchmarks, and examples.

Smooth (smooth.py)

XelToFab provides two mesh smoothing methods, selectable via smoothing_method:

Taubin smoothing (default for MC) applies alternating shrink/inflate passes using trimesh's filter_taubin. This band-pass filter removes high-frequency extraction artifacts while preserving low-frequency shape and volume. Best for general-purpose smoothing where uniform treatment of the surface is acceptable.

Smart smoothing defaults: When extraction_method is dc or surfnets, the pipeline auto-selects bilateral smoothing with 5 iterations (vs 20 for MC) to preserve sharp features. Override with explicit smoothing_method and taubin_iterations.

Configurable via taubin_iterations (default: 20) and taubin_lambda (default: 0.5).

Bilateral filtering (smoothing_method="bilateral") weights each vertex displacement by both spatial proximity and normal similarity of its neighbors. Neighbors across sharp edges have divergent normals and receive near-zero weight, so the filter smooths flat regions while preserving structural features like corners, ridges, and thin walls. Per-iteration volume correction counters the inherent shrinkage of Laplacian-family smoothers.

Configurable via bilateral_iterations (default: 10), bilateral_sigma_s (spatial reach; default: auto from average edge length), and bilateral_sigma_n (normal similarity threshold in radians; default: 0.35).

For 2D contours, both methods are a no-op.

Repair (repair.py)

Fixes non-manifold edges and vertices using pymeshlab. This ensures the mesh is valid for downstream operations (remeshing, decimation, FEA simulation). Enabled by default; disable with repair=False or --no-repair.

For 2D contours, this is a no-op.

Remesh (remesh.py)

Isotropic remeshing via gpytoolbox.remesh_botsch (Botsch & Kobbelt algorithm). Replaces the extraction triangulation with uniform, well-shaped triangles. After remeshing, 99%+ of faces meet FEA quality targets (min angle >20°).

Configurable via target_edge_length (default: auto from mesh bounding box) and remesh_iterations (default: 10). Enabled by default; disable with remesh=False or --no-remesh.

For 2D contours, this is a no-op.

Decimate (decimate.py)

QEM (Quadric Error Metrics) mesh decimation via pyfqmr. Reduces face count while minimizing geometric error through intelligent edge collapse. Runs after remeshing to optimize the uniform triangle mesh for file size and simulation efficiency.

Control the target with either target_faces (absolute face count) or decimate_ratio (proportional reduction, default 0.5 = 50%). The decimate_aggressiveness parameter (1--10, default 7) controls error tolerance. Boundary edges are preserved to protect domain boundaries.

Enabled by default; disable with decimate=False or --no-decimate.

For 2D contours, this is a no-op.

Interactive examples

A mesh extracted from a 3D heat conduction topology optimization result:

InputOutputParams
51×51×51 density field7,087 vertices, 13,910 facesdefault pipeline

A synthetic sphere generated from a density field after full pipeline processing:

InputOutputParams
100×100×100 density field1,307 vertices, 2,610 facesdefault pipeline

Outline