Topological Offset
/ (object)
/
Description
Required
/application (string)
/application
Description
Application name must be topological_offset.Options: ['topological_offset']
/input (string)
/input
Description
Input mesh, .msh: a triangle or a tetrahedral mesh. The two pipelines are the same algorithm one dimension apart and read every key below the same way.Optional
/output (string)
/output
Description
Output file name (without extension).Default: 'out'
/input_dir (string)
/input_dir
Description
Directory where the input files are located. This is injected by the application and should not be set by the user.Default: ''
/offset_selection (string)
/offset_selection
Description
Boolean expression for input simplicial complex to offset. If a single tag (ie ‘tag_0’) is given, single body mode is used.Default: '!_'
/offset_output_tags (list)
/offset_output_tags
Description
Tags to add to elements in the resulting offset region./offset_output_tags/* (string)
/offset_output_tags/*
Description
A tag./protected_tags (list)
/protected_tags
Description
Set of tags that will not be overwritten by the offset./protected_tags/* (string)
/protected_tags/*
Description
A tag./offset_in (bool)
/offset_in
Description
Only relevant for single body mode. Whether to create offset inside bodyDefault: False
/offset_out (bool)
/offset_out
Description
Only relevant for single body mode. Whether to create offset outside bodyDefault: True
/target_distance (float)
/target_distance
Description
Target distance for offset. If < 0, the relative target distance is used to compute this one.Default: -1.0
/target_distance_rel (float)
/target_distance_rel
Description
Target offset distance relative to bounding box of mesh.Default: 0.01
/throw_on_nonconvergence (bool)
/throw_on_nonconvergence
Description
If true, a run that finishes without meeting the convergence criteria raises an error instead of logging a warning. Default false: a non-converged offset is still a usable one, and the warnings already name which criterion failed. Integration tests set it true so a convergence regression fails the run rather than passing with a warning.Default: False
/optimize_offset (bool)
/optimize_offset
Description
Run the optimization (optimize_offset: pre-loop setup, the single-phase turns, the final pass and the convergence verdict) after the offset is constructed. Default true. false: the run stops at the constructed offset – the simplicial embedding, the marching and the tag assignment still run – and writes it as the result. The constructed band lies on the background mesh’s own cell boundaries, so its distance to the input complex is set by the input mesh’s resolution, not by target_distance; the report then carries converged = false and no optimization metrics, and throw_on_nonconvergence is not consulted.Default: True
/envelope_size (float)
/envelope_size
Description
Absolute half-width of the tag-region boundary envelope. If < 0, computed from envelope_size_rel.Default: -1.0
/envelope_size_rel (float)
/envelope_size_rel
Description
Half-width, relative to the bounding box diagonal, of the envelope containing every tag-region boundary during optimization. Any operation that would push a region boundary outside it is rejected. Ignored if envelope_size >= 0.Default: 0.001
/offset_field (string)
/offset_field
Description
Which field defines the offset, and so what the front is placed on, what the criterion measures and what the sizing field refines by. ‘smooth’ is the offset geometric contact potential Phi, with the front on the level set Phi = c: C^2, analytic derivatives, and a level set that bulges outward at reentrant features, so it is a smoothed offset rather than the exact one. ‘euclidean’ (the default) is the exact distance to the input complex, which makes the residual exact but introduces a gradient discontinuity across the medial axis and a crease at every reentrant feature that no refinement resolves; offset_dhat_factor and the outside-the-support guard stop meaning anything, since it has no compact support.Default: 'euclidean'
Options: ['smooth', 'euclidean']
/offset_dhat_factor (float)
/offset_dhat_factor
Description
Support radius of the smooth offset potential, as a multiple of target_distance. Must be > 1. The offset level set lies strictly inside the support; a band vertex that leaves the support is a hard error.Default: 2.0
/DEBUG_manual_dhat (float)
/DEBUG_manual_dhat
Description
[2D ONLY] Debugging override for the smooth potential’s support radius dhat, as an ABSOLUTE length. Negative (the default) leaves the automatic sizing alone: dhat = max(offset_dhat_factor x target_distance, 2 x the distance of the furthest constructed offset vertex). A value >= 0 replaces that outright, so the effective factor is DEBUG_manual_dhat / target_distance and must still exceed 1 for the level set to lie strictly inside the support. For asking what a run does under a support it would not have chosen: outside the support Phi is identically zero with a zero gradient, so a front vertex that leaves it gets no direction back and drifts on the quality term alone. Ignored by the euclidean field, which has no support. Read only by the 2D construction; 3D ignores it until the port.Default: -1.0
/front_conv (float)
/front_conv
Description
THE convergence epsilon, as an ABSOLUTE length in model units; < 0 (the default) means use front_conv_rel instead. Absolute-or-relative exactly as envelope_size / envelope_size_rel, and against the same reference, the bounding box diagonal – deliberately NOT a fraction of target_distance, so changing the offset distance no longer silently changes the accuracy. Startup refuses a value above target_distance: an epsilon coarser than the offset it measures decides nothing, since every front face then reads as resolved from the first turn. IN 3D THIS IS THE ONLY BAR. The measure it gates is one quantity everywhere: over a face’s stencil_order stencil, the ROOT MEAN SQUARE of the distance to the level set along the field, over target_distance (for ‘euclidean’ the relative error (Phi - c)/c = (d - target_distance)/target_distance; for ‘smooth’ the root of Phi = c along grad Phi, since there (Phi - c)/c is not a length and read 3.44x the distance error at offset_dhat_factor 2), taken as a length. A face is resolved when that RMS is <= front_conv, i.e. when the MEAN SQUARED relative error is <= (front_conv/target_distance)^2; a vertex is placed when the same measure at the vertex alone – the order-0 stencil – is within it. So the vertex measure and the face measure are ONE measure at two sample counts, which is why vertex_conv and sag_conv, which split them apart on 2026-09-23, no longer exist. The same number is also what the front smoothing objective drives to zero and what the offset surface debug field f_err reports. THE LOOP EXITS ON THE FACE MEASURE ALONE since 2026-09-25: every offset face resolved and nothing unmeasurable. The vertex measure is reported as a diagnostic and decides nothing about termination. A face over the bar that refinement cannot take, because no chord target lies below the sizing scalar its corners carry (the sizing floor), therefore blocks the exit, and the loop names such faces in a warning every turn they exist. 2D still has its own two-part criterion and reads this key for both halves; its loop likewise exits on the chord half alone, the midpoint sag, which does not sample the chord’s two ends.Default: -1.0
/front_conv_rel (float)
/front_conv_rel
Description
THE convergence epsilon as a fraction of the BOUNDING BOX DIAGONAL, used when front_conv is < 0. The default 2.5e-4 is the old front_conv_rel default 0.025 times the default target_distance_rel 0.01, so a fully default run keeps the accuracy it has always had; at any other target_distance the default accuracy now differs from the pre-2026-09-23 behaviour, which is the point of referencing the box rather than the offset. See front_conv for what the bar actually gates.Default: 0.00025
/front_conv_criterion (string)
/front_conv_criterion
Description
2D ONLY since the 3D criteria were unified on 2026-09-24; 3D ignores it. What ‘converged’ means for a front vertex, in the placement stop, in the refinement gate and in the vertex measure the loop reports (a diagnostic since 2026-09-25: the loop exits on the chord measure alone), measured against the vertex convergence epsilon vertex_conv. F is the vertex’s front objective, g its gradient, H its Gauss-Newton Hessian; under front_normal_projection both are taken along the vertex’s move direction n. ‘step_size_rel’: the remaining 1-D Newton step, |n . g| / (n^T H n), against vertex_conv. ‘decrement’: the Newton decrement, half of (n . g)^2 / (n^T H n), against (vertex_conv / target_distance) x F(x). ‘gradient_norm_rel’: |n . g| against (vertex_conv / target_distance) x a reference measured once on the band as constructed. That fraction is what front_conv_rel used to be, so the two criteria whose bar is not a length are unchanged by the split into vertex_conv / sag_conv. Prefer the step or the decrement: the gradient is stiffness times remaining displacement, so the sliver between two meeting fronts reads far above the bar while sitting at its minimum. ‘residual_error’ (the default) is the odd one out and measures where the vertex IS rather than how far it still wants to move: the field’s own residual as a length (for ‘euclidean’ exactly |d - target_distance|), against vertex_conv. This is the one criterion that is a distance on both sides, and it is the default; set sag_conv equal to vertex_conv to get the single bar both halves of the criterion shared before the two were split. It builds no objective, so front_normal_projection does not enter, and a vertex wedged at a stationary point it cannot leave is reported by how wrong it is rather than by how still it is. Two costs: under ‘smooth’ the residual is the distance to the smooth level set along the field, not a Euclidean distance to the complex, and it is unmeasurable outside the support; and a vertex drifting across the level set at a stationary point of the objective can read placed on one turn and unplaced on the next. IN 3D there are no variants: the one measure is the stencil RMS of the relative distance to the level set along the field (for ‘euclidean’ (Phi - c)/c) against front_conv, which is not a stationarity test at all.Default: 'residual_error'
Options: ['gradient_norm_rel', 'step_size_rel', 'decrement', 'residual_error']
/stencil_order (int)
/stencil_order
Description
Order of the sampling stencil on an offset-surface face. In 3D it sets BOTH the convergence measure and the front smoothing energy, which sample the same points. Order 0 is the 3 CORNERS alone, so the measure is exactly the three vertices’ own placement error. Order k >= 1 is the vertices of the triangle subdivided k-1 times by 4-way midpoint refinement, plus the centroid of each of its 4^(k-1) sub-triangles: with n = 2^(k-1) segments per side that is (n+1)(n+2)/2 + n^2 points, i.e. 4 at order 1 (the corners and the centroid), 10 at order 2 (the corners, the 3 edge midpoints and the 4 sub-triangle centroids), 31 at order 3, 109 at order 4. THE CORNERS ARE IN THE STENCIL, unlike the strictly interior lattice sag_num_samples used, and they have to be: the quantity sampled is a distance to the level set, which at a corner is that vertex’s own placement error rather than the identically-zero interpolation error the old measure saw there. That is what lets one stencil replace the separate vertex and sag criteria. COST: every sample is a Phi value and, in the energy and the guard, a gradient too, so the order is paid in the ops guard’s hot path and in every smoothing solve as well as in the criterion – 4 points at the default against 6 for the old default. 2D reads this key for its residual diagnostics only; its chord test is still the midpoint.Default: 1
Range: [0, inf]
/front_measure (string)
/front_measure
Description
Which measure the single-phase loop exits on and refines by, in 3D and in 2D. ‘vertex_ring’ (the default): at each front vertex v the RING MEASURE r_v = sqrt(sum_f A_f m_f^2 / sum_f A_f), over the offset faces f incident to v whose three corners are front vertices, m_f the face measure face_conv_ratio() (the RMS over the face’s stencil_order stencil of the distance to the level set along the field over target_distance – for ‘euclidean’ the relative error (Phi - c)/c – over the bar front_conv / target_distance) and A_f the face’s area from its current corners. The loop exits when every ring measure is within the bar and nothing is unmeasurable; a vertex with an unmeasurable incident face, or whose incident faces have zero total area, has no ring measure and blocks the exit. The face and vertex measures are then reported as diagnostics. The halving lowers the sizing scalar at each vertex whose ring measure is over the bar, that vertex alone, under the same floor (max of min_sizing_scalar and min_edge_length / l); a vertex over the bar already at the floor blocks the exit and is named by the [sizing floor] warning. EXPERIMENTAL_aggresive_refine and the chord rule are not read in this mode. The ops guards compare, before against after, the largest ring measure over the vertices whose rings an operation changes. The front smoother’s stencil energy weights each incident face’s stencil mean by A_f / A_mean, A_mean the mean area of the vertex’s incident offset faces, both taken at the start of the vertex’s visit and held fixed for its solve, so the energy’s scale at the vertex is unchanged while faces vote by area: the error is the surface integral of r^2, and weighting the criterion without the smoother, or the reverse, would recreate the mismatch this mode removes (Uday’s decision, 2026-09-25). In 2D the same criterion runs on the front chords: r_v = sqrt(sum_e L_e m_e^2 / sum_e L_e) over the front chords e incident to v, m_e the chord measure edge_conv_ratio() and L_e the chord’s length, with the same exit and the same per-vertex halving; 2D’s front smoother (OffsetEnergy2D) minimises the vertex’s own residual and has no per-chord term to weight, so it is unchanged, and 2D’s ops guard still compares chords. ‘face’: the rule of 2026-09-25, kept for comparison. The loop exits when every offset face’s (2D: chord’s) measure is within the bar and nothing is unmeasurable; the halving lowers the sizing scalar at the corners of every face over the bar whose chord rule still has a target below its corners’ scalars; the ops guards compare the changed faces; every face weight in the smoother is 1. Why the ring measure: the smoother minimises a sum over each vertex’s ring while the face exit and refinement are per face, and the two disagree at the margin. Measured on the deliverable cube at target_distance_rel 1e-3 / front_conv_rel 1e-5 under ‘face’, turns 12-15: the loop never exits because a handful of faces end each turn at 1.00 to 1.19 times the bar, the smoothing having pushed faces from 0.99 to just over the bar while lowering the ring they belong to. On the cube at target_distance_rel 1e-2 / front_conv_rel 1e-4 (EXPERIMENTAL_nonoverlapping_gates true, interleaved_smoothing false, 2 smoothing passes, front_alignment_energy false, serial): ‘vertex_ring’ exits after 7 turns with max AMIPS 8.784 and 20724 front faces, 60 of them over the bar (the largest 1.355x) while every ring measure is within it (the largest 0.963x); ‘face’ exits after 7 turns with max AMIPS 7.814 and 23476 front faces. The debug frames carry the ring measure as point data, front_ring_ratio, in both modes (2D: on the _front.vtu chord mesh).Default: 'vertex_ring'
Options: ['vertex_ring', 'face']
/sorted_marching (bool)
/sorted_marching
Description
Execute the marching in decreasing order of edge length. Increases run time, may increase output mesh quality.Default: False
/sphere_trace_initialization (bool)
/sphere_trace_initialization
Description
How the marching places the vertex it inserts on each edge it splits (an edge with one endpoint in the input complex and the other in the background). false: the plain edge midpoint; target_distance does not enter construction, and the optimization carries the front to the level set. true (the default): sphere tracing along the edge from the complex endpoint for the point where d(x) = target_distance, d(x) the distance from the point to the input complex through its BVH: evaluate d at the current point, step forward along the edge by target_distance - d (the largest step that cannot cross the level set), repeat until |d - target_distance| <= sphere_trace_target_rel_tol x target_distance. If the current point reaches or passes the background endpoint the level set is not on the edge and the vertex goes to the midpoint. No snapping away from the endpoints. The simplicial embedding’s splits always use the midpoint.Default: True
/sphere_trace_target_rel_tol (float)
/sphere_trace_target_rel_tol
Description
In (0, 1). The trace under sphere_trace_initialization stops at the first point where |d(x) - target_distance| <= this x target_distance; the placed vertex is that close to the target level set along the edge. Smaller is tighter and takes more steps (each step is longer than the tolerance, so at most edge length / (tol x target_distance) of them). Only read when sphere_trace_initialization is true.Default: 0.01
/EXPERIMENTAL_consistent_construction_split (bool)
/EXPERIMENTAL_consistent_construction_split
Description
EXPERIMENTAL, and a no-op unless sphere_trace_initialization is true. Makes the marching construction all or nothing. Normally each marched edge is placed independently: the trace runs from the complex end and, on an edge the trace leaves (the level set is not on that edge), that ONE edge falls back to its midpoint – so a single construction can mix vertices sitting on the level set with vertices sitting at edge midpoints, which is the inconsistency this flag exists to remove. With it on, the whole march is probed before any edge is split: if every marched edge can be traced, every edge is traced as usual; if even one cannot, EVERY edge is split at its midpoint instead and the construction is uniformly a midpoint march. The probe repeats the trace the split would do – it reads only the endpoint positions and the input-complex BVH, neither of which a split changes – so a construction that does trace everywhere pays for two full trace passes. The construction log says which way it went and how many edges could not be traced.Default: True
/EXPERIMENTAL_aggresive_refine (bool)
/EXPERIMENTAL_aggresive_refine
Description
EXPERIMENTAL, 3D only. DEFAULT TRUE since 2026-09-24. Drops the placement gate on refinement. With it FALSE the end-of-turn resolution check hands a face to the halving only when all THREE of its corners are already placed – the safeguard that keeps refinement from chasing a front that is still travelling. True (the default) refines EVERY offset face whose RMS relative error is over front_conv, whether or not its corners are placed, so the sizing scalar is halved at every vertex of every unresolved face. Unchanged either way: the sizing floor (max of min_sizing_scalar and min_edge_length / l), the once-per-vertex-per-turn rule in refine_front_by_halving(), the gradation that follows it, and the convergence test, which reads the face measure alone and not which faces are refinable, so this cannot change what counts as converged. The turn’s [resolution] line and the reported n_faces_over_placed / max_face_placed still describe the PLACED subset, so the refined count and the reported worst face come apart under this flag; the line says which rule it used. Why it exists: with one unified measure the criterion can deadlock – a face chording a feature of radius target_distance sits with its centroid far inside the level set, that sample’s pull very nearly cancels the corners’ own placement pull, so the corners never place, and refinement (the only thing that would shorten the chord) is gated on exactly those corners. Measured on the deliverable cube at target_distance_rel 1e-2 / front_conv_rel 1e-4: 98% cancellation along the normal, a 1-D Newton step 1-2% of the move needed, and 600+ faces over the bar with ZERO refinable for all 40 turns.Default: True
/EXPERIMENTAL_nonoverlapping_gates (bool)
/EXPERIMENTAL_nonoverlapping_gates
Description
EXPERIMENTAL, default false pending more runs. Separates the two edge-length gates so that a split can never hand the collapse pass its own halves. Both passes measure the same ratio r = L / (l x the mean of the two endpoints’ sizing scalars): the split pass fires when r > 4/3 (splitting_l2 = (4/3 l)^2) and the collapse pass offers a candidate when r < ⅘ (collapsing_l2 = (⅘ l)^2). The two windows overlap, so an edge with 4/3 < r < 8/5 is split into two halves of ratio r/2 < ⅘, which the collapse pass in the same turn merges back into the edge that was just split. With this key true, splitting_l2 becomes (8/5 l)^2 – exactly twice the collapse gate – and collapsing_l2 is unchanged, so every half a split produces has ratio at or above ⅘ and is never a collapse candidate. Nothing else changes: a split is still never refused on quality, this is a length rule alone. Measured on the deliverable cube at target_distance_rel 1e-2 / front_conv_rel 1e-4 with front_alignment_energy false: 29068 of 35865 split candidates at the end of turn 7 (81%) lay in the 4/3 to 8/5 band, and turns 6, 7 and 8 each performed about 36k splits and 29k collapses while refinement halved the sizing scalar at 0 vertices. With the key on: operations in the last turn 66826 -> 19263, turns 8 -> 7, wall 223 -> 126 s, final max AMIPS 8.69 -> 10.64, front faces 25816 -> 21042, both runs meeting the same convergence test. false, the default, keeps the TetWild gates.Default: False
/check_manifoldness (bool)
/check_manifoldness
Description
After performing offset, check if offset region is manifoldDefault: True
/save_vtu (bool)
/save_vtu
Description
Save .vtu of output meshDefault: False
/phi_grid_resolution (int)
/phi_grid_resolution
Description
Samples per side of the grid the smooth offset potential is written on, as/DEBUG_output (bool)
/DEBUG_output
Description
Write debug VTU frames of the evolving mesh, in both dimensions: one frame after every operation pass and every smoothing pass, as sequential/log_file (string)
/log_file
Description
Logs are not just printed on the terminal but also saved in this file.Default: ''
/report (string)
/report
Description
A JSON file that stores information about the result and the method execution, e.g., runtime.Default: ''
/preallocation_factor (float)
/preallocation_factor
Description
Mesh storage (connectivity and attributes) is preallocated to this factor times the live element count at init and at consolidation. Operations take fresh slots from that headroom and are retried once it is exhausted. Lower it for pure-decimation runs and raise it for aggressive refinement; the offset refines aggressively, so a run whose log carries [slots] warnings is dropping the very splits the criterion asked for.Default: 6.0
Range: [1.0, inf]
/num_threads (int)
/num_threads
Description
Number of threads used for parallel execution (smoothing, edge collapse). 0 means single-threaded.Default: 0
/num_smoothing_passes (int)
/num_smoothing_passes
Description
Smoothing passes after the ONE combined split+collapse+swap round when interleaved_smoothing is OFF, this component’s default – in the 3D single-phase loop since 2026-09-25 (there only when adaptive_smoothing is also off), and in the shared optimization loop, which in this component is the frozen-front finishing pass, 2D and 3D alike. The default turn is therefore split, collapse, swap, then 2 smoothing passes. Not read when interleaving is on: the count is then interleaved_smoothing_passes, everywhere. TriWild’s key and TriWild’s default.Default: 2
/interleaved_smoothing (bool)
/interleaved_smoothing
Description
Run split, collapse and swap as three separate rounds with smoothing after each, instead of one combined round. DEFAULT FALSE in this component (TriWild’s and TetWild’s default is true), so the default turn is split, collapse, swap back to back, then num_smoothing_passes smoothing passes. Read since 2026-09-25 by the 3D single-phase loop: with it false a turn is one combined split+collapse+swap round followed by ONE smoothing block – num_smoothing_passes fixed passes, or under adaptive_smoothing passes until the front and the background settle – instead of three rounds each followed by interleaved_smoothing_passes. Also read by the shared optimization loop, which in this component is the frozen-front finishing pass, 2D and 3D alike, so the finishing pass takes the same turn shape. Why it exists in the loop: measured on the deliverable cube at target_distance_rel 1e-2 / front_conv_rel 1e-4, of the six smoothing passes per turn only the first after the split moved the front from turn 5 on; the other five never moved a vertex across the bar and cost 63% of the turn. Why false is the default: on the same cube with EXPERIMENTAL_nonoverlapping_gates true and front_alignment_energy false, serial, the combined turn with 2 smoothing passes converged in 7 turns, 85 s, final max AMIPS 8.37, 25358 front faces, against 7 turns, 124 s, max AMIPS 10.64, 21042 front faces with the three interleaved rounds of 2 passes each. 2D’s single-phase loop still hard-codes the three interleaved rounds.Default: False
/interleaved_smoothing_passes (int)
/interleaved_smoothing_passes
Description
Smoothing passes after each operation group (split, collapse, swap) when interleaved_smoothing is on – in the shared TriWild/TetWild loop (the frozen-front finishing pass) and in the single-phase loop. Default 2, against the shared engine’s own 1: each smoothing pass is one placement sweep of the front, so at one pass per group the front contracts about half as fast for the same final result. In the single-phase loop this fixed count is only used when adaptive_smoothing is false.Default: 2
/colored_smoothing (bool)
/colored_smoothing
Description
Parallel smoothing (num_threads > 0) by color class: the vertices are split into classes of pairwise non-adjacent vertices, smoothed one class after another, each class fully in parallel without locks. The result does not depend on the number of threads. false: the locked, partitioned smoothing pass, whose result depends on the thread schedule. Serial runs (num_threads 0) are the same either way.Default: True
/adaptive_smoothing (bool)
/adaptive_smoothing
Description
Single-phase loop only. Run the smoothing after each operation group pass by pass until it has converged or stalled, instead of a fixed count: in 3D after the one combined group under interleaved_smoothing false (the default) in place of num_smoothing_passes, and after each of the three groups (split, collapse, swap) under true in place of interleaved_smoothing_passes; in 2D always after each of the three. After every pass two things are measured against the positions before it. Front vertices (on the offset): the convergence ratio front_vertex_conv_ratio, the same Newton-step ratio the turn’s criterion tests – converged when its maximum is at or below 1, stalled when the maximum fell by less than adaptive_smoothing_stall_rel of its previous value, since a stalled front needs an operation and not another sweep. Background vertices: the step each vertex made divided by its target edge length s_v * l – settled when the maximum is at or below adaptive_smoothing_step_rel. The group’s smoothing stops when the front is converged or stalled AND the background has settled, or at adaptive_smoothing_max_passes. Front vertices whose ratio is not measurable (a non-positive curvature of the objective along the move direction) are counted and left out of the maximum. The offset tube is rebuilt after the group as before, not after each pass.Default: False
/adaptive_smoothing_max_passes (int)
/adaptive_smoothing_max_passes
Description
Upper bound on the smoothing passes of one operation group under adaptive_smoothing; at least 1 pass always runs.Default: 20
/adaptive_smoothing_stall_rel (float)
/adaptive_smoothing_stall_rel
Description
Under adaptive_smoothing the front counts as stalled when a pass lowered the maximum convergence ratio by less than this fraction of its value before the pass (0.1 = less than 10 percent). Never applied to the first pass of a group.Default: 0.1
/adaptive_smoothing_step_rel (float)
/adaptive_smoothing_step_rel
Description
Under adaptive_smoothing the background counts as settled when no non-front vertex moved more than this fraction of its own target edge length s_v * l in the pass.Default: 0.01
/pre_smooth (bool)
/pre_smooth
Description
Single-phase loop only. true: before the first turn, run one smoothing block on the mesh as constructed – the same block each operation group is followed by: in 3D num_smoothing_passes sweeps under interleaved_smoothing false (the default) and interleaved_smoothing_passes under true, in 2D always interleaved_smoothing_passes; or the adaptive smoothing when adaptive_smoothing is on – with the plastic rests stamped and the offset tube rebuilt after it, so the front is carried toward the level set once before the first split pass measures it. false (the default): the loop starts with turn 1’s split pass on the constructed mesh.Default: False
/max_iterations (int)
/max_iterations
Description
Cap on the shared TriWild/TetWild loop, which now runs in exactly one place: the finishing pass that runs with the front frozen. The loop exits as soon as max AMIPS is under stop_energy, so this is an upper bound.Default: 80
/offset_envelope (float)
/offset_envelope
Description
Half-width of the corridor the operation passes must keep the front inside, as an ABSOLUTE length in model units; < 0 (the default) means use offset_envelope_rel instead. Absolute-or-relative exactly as envelope_size / envelope_size_rel. It is a leash, not an accuracy knob: front_conv sets the accuracy, and startup requires this to be <= front_conv, because a wider leash lets the operations dent the front past the resolution threshold and mint new refinable faces every turn. Tighter over-constrains the passes and starts refusing operations that were not straddling the boundary at all. It also feeds the derived sizing floor when min_edge_length_rel is negative, as this length expressed in units of target_distance.Default: -1.0
/offset_envelope_rel (float)
/offset_envelope_rel
Description
Half-width of the corridor the operation passes must keep the front inside, as a fraction of the BOUNDING BOX DIAGONAL, used when offset_envelope is < 0. Rebuilt after every smoothing pass. THE REFERENCE CHANGED on 2026-09-24: it was a fraction of target_distance. A fully default run is NOT unchanged – the old default 0.025 x the default target_distance_rel 0.01 came to 2.5e-4 of the diagonal, and this default is 1e-4, so the leash is 2.5x TIGHTER at defaults and the operation passes are correspondingly more constrained. At other target distances it moves the other way: at target_distance_rel 1e-3 the old key gave 2.5e-5 of the diagonal where this gives 1e-4, a leash four times wider. See offset_envelope for what the corridor is and what startup requires of it.Default: 0.0001
/length_rel (float)
/length_rel
Description
Target edge length relative to the bounding box diagonal. Used to bound which edges edge collapse is allowed to touch (only edges shorter than ⅘ of this length are collapsed). Ignored if length >= 0.Default: 0.05
/length (float)
/length
Description
Target edge length (absolute). If < 0, computed from length_rel.Default: -1.0
/stop_energy (float)
/stop_energy
Description
Target AMIPS quality for the elements. Default 100, matching the tetwild component – the frozen-front finishing pass is TriWild/TetWild and stops where they stop. Do not lower it far: a target the operation set cannot deliver turns the split pass into an unbounded refiner. Edge collapse will not push an already-on-target region’s quality back above this.Default: 100.0
/min_edge_length (float)
/min_edge_length
Description
l_min: the shortest edge the sizing field may ask for, in absolute units. If < 0, derived as min_edge_length_rel * target_distance – tied to the offset distance rather than the bounding box, because the offset is what has to be resolved. This is a floor on REFINEMENT, so raising it makes the offset coarser (paper Fig. 18).Default: -1.0
/min_edge_length_rel (float)
/min_edge_length_rel
Description
l_min as a multiple of target_distance, used when min_edge_length is negative. Negative (the default) derives it from the Phase A offset envelope epsilon, which is just offset_envelope (as a multiple of target_distance): TetWild’s own floor (cap the sizing field below by the envelope eps, Sec 3.2) stated in the offset’s units, since the front is only held to within eps and edges shorter than eps cannot buy fidelity. A pure anti-runaway rail, far below the chord length any tolerance actually needs.Default: -1.0
/min_sizing_scalar (float)
/min_sizing_scalar
Description
Lower bound for the per-vertex sizing field (see max_sizing_scalar). Refinement stops once a vertex’s sizing scalar reaches this fraction of the base target length.Default: 0.01
/max_sizing_scalar (float)
/max_sizing_scalar
Description
Upper bound for the per-vertex sizing field, as a multiplier on the target edge length (splitting_l2/collapsing_l2). 1.0 means never coarser than the base target length.Default: 1.0
/sizing_gradation (float)
/sizing_gradation
Description
Gradation cap for the sizing field: neighbouring vertices’ sizing scalars may differ by at most this factor. After each refinement pass the refined vertices’ lower sizing scalar is propagated outward – monotone, only ever lowering a neighbour’s scalar – so the mesh does not jump straight from fine to coarse. <= 1 disables gradation. Only read when sizing_gradation_mode is ‘ring’.Default: 2.0
/sizing_gradation_mode (string)
/sizing_gradation_mode
Description
How a lowered sizing scalar spreads to the vertices around it, at every place the offset lowers the field (the sag rule, the stuck refine). ‘ring’ (the default): the base gradation_smooth_sizing – walk outward over mesh neighbours, capping each neighbour at sizing_gradation times the scalar it came from, so the field grades by mesh rings and a fine seed in a coarse mesh drags a shell of rings down with it. ‘distance’: TetWild’s adjust_sizing_field gradation – walk outward from the seeds and multiply every vertex within R = 1.8 l of its nearest seed by the linear ramp 0.5 + 0.5 dist / R (halved next to a seed, untouched at R), stopping at R; the seeds keep the value they were just given and nothing outside the ball changes. The refinement then reaches a seed geometrically through the split pass (each split child takes the mean of its parents’ scalars) instead of by rings, so a fine target stays local to the seed however coarse the mesh around it is.Default: 'ring'
Options: ['ring', 'distance']
/split_high_valence_threshold (int)
/split_high_valence_threshold
Description
Incident-cell count above which a vertex in a split edge’s link accepts only one valence-increasing split per pass, or 0 to disable. Spreads refinement instead of letting it pile onto one vertex.Default: 0
/coarsen_pass (bool)
/coarsen_pass
Description
Run the post-optimization coarsening pass, as TriWild and TetWild do. It trades elements for nothing but the guarantee that the result is still good, so topological_offset holds it to an absolute bar – both AMIPS and the offset residual inside tolerance after each collapse – rather than the non-degrading bar the main loop uses. Off by default: the pass can dominate total runtime, and being the last thing to run before Phase A checks convergence, anything it degrades is reported as a Phase A failure rather than a coarsening one.Default: False
/coarsen_unbounded (bool)
/coarsen_unbounded
Description
Coarsen as far as the quality guarantee allows, instead of stopping at the target edge length. The pass then answers how few elements can hold this max energy, which is more aggressive than it sounds because the answer ignores how big the elements become; measured over the registered models it roughly doubles the cell reduction while raising max energy on none of them. On by default. Turned off, it leaves alone any edge already at or past the collapse threshold, since collapsing it only makes its neighbours longer. The sizing field is deliberately not applied either way: honouring it here would leave the pass unable to undo refinement that turned out to be unnecessary.Default: True
/coarsen_max_rounds (int)
/coarsen_max_rounds
Description
Cap on the coarsen/smooth alternation; the pass also stops as soon as a round accepts no collapse. A round does not exist to finish what the previous one started: within a round the collapse pass already runs to a fixed point, so repeating the collapse alone finds nothing. What a round adds is the global smoothing in between, which moves that fixed point. Returns decay fast, and the default of two carries the large majority of the coarsening for a fraction of what the later rounds would cost.Default: 2
/coarsen_global_smoothing_passes (int)
/coarsen_global_smoothing_passes
Description
Ordinary whole-mesh smoothing passes run between coarsening rounds. This is what makes a second round worth running at all – see coarsen_max_rounds – so setting it to 0 collapses the rounds to one.Default: 1
/coarsen_smooth_ring (int)
/coarsen_smooth_ring
Description
How far the local smoothing after each coarsening collapse reaches, in rings; the pass locks one ring wider than this. Deliberately 1 rather than TriWild’s and TetWild’s 2: a smaller ball perturbs less per collapse, so more collapses clear the offset’s absolute coarsening bar.Default: 1
/coarsen_local_smoothing_passes (int)
/coarsen_local_smoothing_passes
Description
Smoothing sweeps over the ring inside each candidate coarsening collapse, before its max energy is measured, so the collapse is judged on a relaxed neighbourhood rather than on the configuration it lands in. 0 by default: the ring is large enough that even two sweeps cost hundreds of nonlinear solves per candidate, and turning it off costs almost no cell reduction because the pass runs to a fixed point and finds another candidate instead. At 0 the only relaxation is the global smoothing between rounds, which has no per-operation accept test of its own – see the forced quality veto in TetOptimizerMesh::coarsen_mesh().Default: 0
/stuck_refine_stall_eps (float)
/stuck_refine_stall_eps
Description
Fire the sizing refinement when an iteration’s improvement is small next to the distance the metric still has to cover: (prev - cur) <= stall_eps * (cur - target). 0 means only when it does not improve at all.Default: 0.1
/stuck_refine_cooldown (int)
/stuck_refine_cooldown
Description
Iterations to skip after a sizing refinement before another may fire. Default 1, matching the tetwild component: the refined sizing needs one split/collapse round to act before the stall detector can fairly judge it. 0 allows one every iteration.Default: 1
/stuck_refine_num_worst (int)
/stuck_refine_num_worst
Description
How many worst faces seed the refinement. 0 means every face above the filter threshold, which is TriWild’s default.Default: 0
/stuck_refine_rings (int)
/stuck_refine_rings
Description
Vertex rings grown around each seeded face before the sizing scalar is lowered.Default: 0
/stuck_refine_factor (float)
/stuck_refine_factor
Description
Multiplicative reduction of the per-vertex sizing scalar per refinement.Default: 0.5
/stuck_refine_min_scalar (float)
/stuck_refine_min_scalar
Description
Floor on the per-vertex sizing scalar.Default: 0.001
/stuck_refine_gradation (float)
/stuck_refine_gradation
Description
Neighbouring sizing scalars may differ by at most this factor. The smoothing only ever lowers a scalar, spreading refinement outward. <= 1 disables it.Default: 2.0
/stuck_refine_force_split (bool)
/stuck_refine_force_split
Description
On a stall, also split each seeded face’s longest edge once, bypassing the length gate and without touching the sizing field.Default: True
/front_normal_projection (bool)
/front_normal_projection
Description
The front is placed by a one-dimensional solve along its field normal n = grad Phi/|grad Phi|: the same objective restricted to the line x0 + s n, with the same solver, line search and accept test, and the vertex test reads the step along that same n. Where a vertex sits along the front carries no information about the offset, and in the free solve that tangential motion is driven by AMIPS alone, which made fronts slide and fold where two of them meet. Spacing along the front is left to the operation passes’ smoother. A front vertex an input envelope also holds moves along that boundary instead: the field normal projected into the boundary (or onto its crease).Default: True
/front_alignment_energy (bool)
/front_alignment_energy
Description
Whether the front objective carries the alignment term: the sum over the vertex’s live front simplices of (1 - n_e . ghat(m_e))^2, with n_e the simplex normal and ghat the field’s unit gradient at its midpoint / centroid, weighted like the offset term. It is what acts on the front’s normals under front_normal_projection, and what lets a pressed seam settle in a few turns rather than tens. On by default, with a known cost: under normal-only placement it can act only along the normal, so it biases a vertex a few percent of target_distance off its level set on curved fronts. Each term is weighted by the gradient agreement at its corners, max(0, ghat_a . ghat_b) (the minimum over the corner pairs), frozen per visit, so a simplex spanning a concave corner’s bisector gets no direction target instead of an impossible one.Default: True
/sizing_collapse_min (bool)
/sizing_collapse_min
Description
What the surviving vertex of a collapse keeps as its sizing scalar. true (the default): the smaller of the two, which is the shared engine’s rule. false: its own scalar, restored after the base collapse. The min rule carries substantially more vertices and lengthens the placement loop where the front travels, because the offset’s sizing follows a moving front and has to relax behind it, which a scalar that only ever falls cannot do.Default: True
/max_rounds (int)
/max_rounds
Description
The outer loop’s budget, in turns, one turn being split, collapse and swap with smoothing after each, followed by the front test. An upper bound rather than a schedule – exhausting it is a warning and not an error, because a front that needs more turns is a legitimate outcome to inspect.Default: 40
/w_amips (float)
/w_amips
Description
Relative weight of the AMIPS quality term against the envelope term during smoothing of envelope-held vertices, and of the AMIPS term against the offset term in the front objective. The envelope weight, and the front objective’s offset weight, are both derived as 1 - w_amips. In the 3D front objective the AMIPS term is a RAW SUM over the one-ring, of order 3 x valence at best, which is why the default is small: it is what keeps that sum comparable to the offset term beside it. Read in both dimensions.Default: 0.0001
/smoothing_mode (string)
/smoothing_mode
Description
How smoothing places a surface vertex. ‘projected’: smooth with AMIPS alone as if interior, then walk back toward the start projecting each candidate onto the input; accept the first projected candidate that does not invert and strictly lowers the worst incident element, else do not move. Lands exactly on the input; no weights. ‘exact’: minimize w_amips * AMIPS + (1-w_amips) * (d/eps)^2 with the true region-wise Hessian of the distance to the piecewise-linear input; sliding is free where the input is flat, held at corners, constrained along edges and curve segments. Rests a w_amips-proportional distance off the input.Default: 'projected'
Options: ['projected', 'exact']
/project_line_search_steps (int)
/project_line_search_steps
Description
Bisections tried by the projected line search before it gives up on a vertex: the step along the Newton direction is halved this many times, each candidate projected onto the input, and the first that does not invert and lowers the worst incident element is taken.Default: 12
/project_line_search_nested_steps (int)
/project_line_search_nested_steps
Description
After the projected line search gives up, revisit each candidate and bisect between the interpolated point and its projection this many times, taking the longest step toward the input that still does not invert and still lowers the worst element. Lets a vertex whose one-ring cannot tolerate a full projection still travel toward the input, instead of being refused every pass from the same place. 0 disables the pass.Default: 0
/smooth_quality_veto (bool)
/smooth_quality_veto
Description
Whether smoothing refuses a move that raises the worst incident element’s quality. True is TriWild’s, SimWild’s and TetWild’s behaviour and the default here. The front is placed by its own solver, which never consults this flag, and every other vertex is smoothed by AMIPS alone inside its envelope, so the operation passes take the shared engines’ veto. The key stays settable for experiments.Default: True
/perform_sanity_checks (bool)
/perform_sanity_checks
Description
Check after every operation pass that no element is inverted and no tracked-surface simplex left its envelope. Slow; for debugging.Default: False
/deform_others (bool)
/deform_others
Description
Whether the other objects in the scene – tagged regions with no input-complex simplex and no domain-wall contact – deform when the optimization pushes them, instead of being held by their per-tag envelopes. true (the default): their envelopes are dropped at the top of optimize_offset(), every cell of theirs is stamped with its rest shape, and smoothing carries a rest-shape AMIPS term for those cells weighted like the smoother’s own. A cell keeps that rest shape until an operation touches it; an accepted split, collapse or swap re-stamps the cells it changed, which is what makes the scheme indifferent to remeshing. protected_tags opts a region out, and the offset’s own complex, the domain wall and the envelope curve / surface group are always held.Default: True