TriWild

/ (object)

/

Description

Required

/application (string)

/application

Description

Application name must be triwild.

Options: ['triwild']

/input (list)

/input

Description

List of triangular input meshes.

/input/* (string)

/input/*

Description

Triangular input mesh.

Optional

/output (string)

/output

Description

Output file name (without extension).

Default: 'out'

/input_names (list)

/input_names

Description

List of names for the input meshes. If no names are assigned, the meshes will be named tag_0, tag_1, etc.

/input_names/* (string)

/input_names/*

Description

Name for one input mesh.

/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: ''

/num_threads (int)

/num_threads

Description

Number of threads used by the application

Default: 0

/max_iterations (int)

/max_iterations

Description

Maximum iterations before stopping.

Default: 80

/max_expected_iterations (int)

/max_expected_iterations

Description

Fail the run if mesh_improvement needed more than this many iterations, even if it did reach stop_energy. 0 disables the check. This is a regression guard rather than a quality target: an input that normally converges in a handful of iterations and suddenly needs the whole budget means the sizing-refinement trigger stopped firing when it should, which is a silent slowdown that no energy or envelope assertion catches.

Default: 0

/skip_simplify (bool)

/skip_simplify

Description

If true, input simplification will be skipped. The input curves are otherwise coarsened first, by collapsing their shortest segments while staying inside the simplification envelope, so the exact arrangement and the initial mesh do not carry input detail far below the target edge length.

Default: False

/simplify_use_link_condition (bool)

/simplify_use_link_condition

Description

Require the link condition when simplifying the input curves. When true the simplification cannot change the topology of the curve network: junctions and vertices shared between two inputs are frozen, and any collapse that would leave a degenerate or duplicated segment is rejected. When false those collapses are allowed – the segment that becomes degenerate or duplicated is dropped instead – so a dirty input simplifies much further, at the cost of merging junctions and separate curves that pass within the envelope. Open endpoints stay frozen either way: the envelope is one-sided, so nothing would notice a curve eroding inwards from its own tip.

Default: False

/simplify_envelope_ratio (float)

/simplify_envelope_ratio

Description

Envelope thickness used for the input simplification, as a fraction of the one used for the triangulation. Simplifying inside a tighter envelope reserves the remainder as headroom: if the simplification is allowed to place vertices at the full limit, the optimizer starts with a mesh where almost every move already lies outside and most operations are vetoed. 1.0 restores the old behaviour of sharing a single envelope.

Default: 0.5

/simplify_use_sample_envelope (bool)

/simplify_use_sample_envelope

Description

Use the sampled envelope instead of the exact one for the input simplification only; the triangulation keeps whatever use_sample_envelope selects. The exact envelope dominates the cost of simplifying a large curve network, which is why this defaults the other way round from use_sample_envelope – the same split tetwild makes. Both backends guarantee the same thing, that everything they accept lies within simplify_envelope_ratio * eps of the input; they differ only in how much of that budget they manage to use, so this does not change what simplify_envelope_ratio means.

Default: True

/use_sample_envelope (bool)

/use_sample_envelope

Description

Use the sampled envelope instead of the exact one. Both describe the same envelope – the eps-neighbourhood of the input – and both are given the same eps; neither is wider than the other. They differ in how much of that eps they can use. The sampled test places points along a query and asks whether each is within some radius of the input; since it cannot see what happens between two samples, it shrinks that radius to stay conservative, and so rejects some geometry that is genuinely inside. The exact one decides coverage by the union of the input’s eps-neighbourhoods directly and spends almost nothing on conservatism. The difference that matters in practice: a segment bridging a gap between two input curves is rejected by the exact envelope and accepted by the sampled one whenever the gap falls between samples.

Default: False

/filter (string)

/filter

Description

Remove the outside region based on different criteria. ‘flood’: flood fill; the region that dominates the mesh boundary is dropped. ‘input’: winding number w.r.t. the inputs; faces inside no input are dropped. ‘none’: Do not filter. Both filters need closed input curves – on an open polyline ‘inside’ is not defined and the result is arbitrary.

Default: 'none'

Options: ['flood', 'input', 'none']

/skip_winding_number (bool)

/skip_winding_number

Description

Skip the finalize-phase winding-number and flood-fill computation. These per-face quantities are needed to filter the outside region (filter != ‘none’) and to tag the faces per input, which is what the MSH groups are built from – so skipping them also empties the output groups. Ignored (with a warning) when filter != ‘none’, which requires them.

Default: False

/eps_rel (float)

/eps_rel

Description

Envelope thickness relative to the bounding box

Default: 0.001

/length_rel (float)

/length_rel

Description

Target edge length relative to the bounding box

Default: 0.05

/stop_energy (float)

/stop_energy

Description

Target energy. If all triangles have an energy below this, triwild will stop. Held numerically equal to tetwild’s for parity, but note the two numbers are not the same quantity: this is the raw AMIPS2D energy, whose floor (an equilateral triangle) is 2, while tetwild compares the cube root of AMIPS3D, whose floor is 3. So 100 is 50x the floor here against ~33x there, i.e. a looser target in 2D than the same number buys in 3D. Making the two comparable would mean normalizing both energies by their dimension, which would change every logged figure and break comparison with existing runs; parity of the knob was chosen over parity of the target. At 100 a 300-model trial finished with a median max energy of 88.9 and a median iteration count of 1, i.e. one pass and stop; the 19686-model sweep converged 100% with a median max energy of 19.8 because most of it ran at the older target of 20.

Default: 100

/split_high_valence_threshold (int)

/split_high_valence_threshold

Description

Incident-triangle count above which a vertex accepts only one valence-increasing split per split pass, or 0 to disable. A well-shaped triangle mesh has vertex valence around 6; a split cascade can drive one vertex much higher, at which point every operation touching it is O(valence) and the pass stalls. Splitting an edge leaves its endpoints’ counts unchanged and adds one to each vertex opposite the edge, so the gate applies to those. Expect this to fire far less often than in 3D, where the edge’s link is a whole ring rather than one or two vertices.

Default: 200

/w_amips (float)

/w_amips

Description

Relative weight of the AMIPS quality term against the envelope term during smoothing; the envelope weight is 1 - w_amips. The small default makes smoothing primarily about staying on the input curves, with quality as a secondary preference.

Default: 0.0001

/num_smoothing_passes (int)

/num_smoothing_passes

Description

Number of smoothing passes per optimization iteration. Smoothing is the only phase that improves quality without changing connectivity, so when split, collapse and swap have run out of useful moves it is the only thing that can still lower the energy. Was 10; lowered to 2 once surface vertices stopped being skipped by skip_good_regions, which made each pass do real work – on Thingi10K 240280 a pass costs 41-51s and moves fewer vertices each time (accepted 18606 -> 17077 over five passes, envelope rejections 11316 -> 12237), so the later passes in a run of 10 buy very little for their cost.

Default: 2

/interleaved_smoothing (bool)

/interleaved_smoothing

Description

Interleave smoothing between the topology passes: split + smoothing, collapse + smoothing, swaps + smoothing, instead of split, collapse, swaps followed by num_smoothing_passes smoothing passes. On by default in triwild: per iteration it reaches a given energy sooner than the batched schedule (on 122839, 102 vs 535 at iteration 5 and 25 vs 69 at iteration 13), at the cost of roughly 3x the work per iteration since each topology pass gets its own smoothing. tetwild still defaults to off, pending the same measurement there.

Default: True

/interleaved_smoothing_passes (int)

/interleaved_smoothing_passes

Description

Smoothing passes to run after each topology pass when interleaved_smoothing is set. One is enough with interleaving on, since there are three of them per iteration.

Default: 1

/preserve_topology (bool)

/preserve_topology

Description

Preserve the topology of the input surface.

Default: False

/preserve_feature_points (bool)

/preserve_feature_points

Description

Keep the curve network’s 0-dimensional features – the endpoints of open polylines, and junctions – within eps of where the arrangement placed them. Each such vertex is bound to a specific feature point, so a collapse is refused when it would leave that point unrepresented or move it further than eps, and smoothing may move the vertex anywhere inside that ball but no further. Without this the collapse pass deletes open polylines outright: a polyline erodes into its own tip until a single segment is left, and that segment carries a feature at BOTH ends, which the surface order test does not refuse (it only forbids collapsing a feature into a non-feature). Measured on the 2D dataset, model 215292 went from 28 open components after the arrangement to 0 in the output. Turn off only to reproduce the old behaviour.

Default: True

/remove_duplicate_eps (float)

/remove_duplicate_eps

Description

Merge vertices in the input meshes that are closer than this fraction of the bounding box diagonal, dropping the edges that become degenerate or duplicated. This is applied to each input mesh separately. 0 merges only exactly coincident vertices, which is still needed to build the segment connectivity; negative skips the pass entirely.

Default: 0

/throw_on_fail (bool)

/throw_on_fail

Description

Throw exception if the output does not fulfil the desired criteria. No output will be generated.

Default: False

/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: ''

/write_vtu (bool)

/write_vtu

Description

Write not just MSH but also VTU output.

Default: True

/preallocation_factor (float)

/preallocation_factor

Description

Mesh storage (connectivity + attributes) is preallocated to this factor times the live element count at init and consolidation. Operations take fresh slots from that headroom and are retried once it is exhausted. Lower it for pure-decimation runs, raise it for aggressive refinement.

Default: 6.0

Range: [1.0, inf]

/DEBUG_output (bool)

/DEBUG_output

Description

Write the mesh as debug_{}.vtu after every operation.

Default: False

/DEBUG_sanity_checks (bool)

/DEBUG_sanity_checks

Description

Perform sanity checks after every operation. This can be very slow and should only be used for debugging.

Default: False

/DEBUG_hausdorff (bool)

/DEBUG_hausdorff

Description

Sanity Check: Compute and report the deviation between the input curves and the output’s tracked edges, in both directions. Containment, d(output -> input), is the envelope invariant and should always be smaller than eps. Coverage, d(input -> output), is reported as a diagnostic and is NOT bounded by anything: the simplification is allowed to remove detail and the arrangement may drop segments, so a large value means the output no longer covers part of the input, not that the envelope was violated.

Default: False

/DEBUG_euler (bool)

/DEBUG_euler

Description

Sanity Check: Compute and report the Euler characteristic of each connected component of the input curve network and of the output’s tracked edges, and warn if they differ. For a graph this is V - E, i.e. 1 - (number of independent cycles) per component: 1 for an open polyline, 0 for a closed loop, -1 for a figure eight. It therefore detects curves merged or split, loops opened or closed, and whole components lost – the changes the simplification is allowed to make with simplify_use_link_condition off, and the ones a collapse can make that the envelope cannot see (the envelope only bounds output-inside-input, so a curve eroding along itself is invisible to it). Off by default because on a network with many components it is a per-run cost with no effect on the result.

Default: False

/DEBUG_feature_retention (bool)

/DEBUG_feature_retention

Description

Sanity Check: count how many of the curve network’s 0-dimensional features – open polyline endpoints and junctions – still have a mesh vertex within eps of them, and how far the worst one that does not is. Reports features_retained, features_total and features_worst_ratio. Off by default because it is pure diagnosis and the count is proportional to the input’s feature count, which can be enormous: model 177574 of the 2D dataset has 821,954 of them. It costs one kd-tree build over the live vertices plus one nearest-neighbour query per feature. Note this measures a property the pipeline does not guarantee – two anchors closer than eps may legitimately merge, and such merges cascade – so a value below 100% is information, not a failure.

Default: False

/DEBUG_disable_envelope (bool)

/DEBUG_disable_envelope

Description

Diagnostic only. Disables every envelope containment check during the triangulation, after the input simplification has run. The envelope is the only thing that rejects an operation for geometric rather than combinatorial reasons, so this answers whether a stalled optimization is blocked by the envelope or by the mesh. The output has no containment guarantee and must not be used.

Default: False

/DEBUG_envelope_sanity_check (bool)

/DEBUG_envelope_sanity_check

Description

Sanity Check: verify at init that every constrained edge of the arrangement already lies inside the envelope, and abort if one does not. The invariant is real and not trivially true – the envelope is built around the input curves at eps, while the constrained edges come from curves the simplification was free to move by simplify_envelope_ratio * eps – so this asks whether the simplification stayed inside its share of the budget. Everything downstream assumes it did: an operation vetoed by the envelope is only meaningful if the starting mesh was inside it. Off by default because it costs one sampled segment query per constrained edge, serially – 22s on a 3.5M-edge input, paid on every run – to catch a condition that has fired once in 15665 models, and that time is charged against the run’s own budget. Turn it on when changing the simplification, the envelope, or their two eps values.

Default: False

/stuck_refine_stall_eps (float)

/stuck_refine_stall_eps

Description

Refine when the last iteration’s improvement was at most this fraction of the distance the max energy still has to cover: (prev_max - max) <= eps * (max - stop_energy). Equivalently, refine unless the mesh is on course to reach the target within about 1/eps more iterations. 0 => only when it does not improve at all.

Default: 0.1

/stuck_refine_cooldown (int)

/stuck_refine_cooldown

Description

After a refinement, skip this many improvement iterations before refining again (let the operations act on the new sizing field).

Default: 0

/stuck_refine_num_worst (int)

/stuck_refine_num_worst

Description

Number of worst triangles whose neighborhoods are refined when stuck. 0 => refine all triangles with energy above the filter_energy, which is max(max_energy / 100, stop_energy). Note that the AMIPS2D energy of an equilateral triangle is 2, so with a small stop_energy the filter catches nearly the whole mesh; set this to a positive value there.

Default: 0

Range: [0, inf]

/stuck_refine_rings (int)

/stuck_refine_rings

Description

Graph rings around each worst triangle included in the stuck refinement. 0 => only the worst triangle itself, 1 => its neighbors, 2 => neighbors of neighbors, etc.

Default: 0

Range: [0, inf]

/stuck_refine_factor (float)

/stuck_refine_factor

Description

Multiplicative reduction of the sizing scalar per stuck refinement (0.5 => halve).

Default: 0.5

/stuck_refine_min_scalar (float)

/stuck_refine_min_scalar

Description

Lower bound on the sizing scalar during stuck refinement.

Default: 0.001

/stuck_refine_gradation (float)

/stuck_refine_gradation

Description

Gradation cap for the monotone sizing smoothing: adjacent sizings may differ by at most this factor (smoothing only lowers values).

Default: 2.0

/stuck_refine_force_split (bool)

/stuck_refine_force_split

Description

When the max energy stalls, split each worst triangle’s longest edge once, bypassing the split length gate, to unstick a sliver without changing the sizing field. Adds at most one split per worst triangle per stall – but ‘worst’ means every triangle above the filter energy when stuck_refine_num_worst is 0, so on a mesh whose stop_energy sits close to the AMIPS2D floor of 2 this can force thousands of splits in one pass and blow up the element count. Bound stuck_refine_num_worst or turn this off in that regime.

Default: True

/skip_good_regions (bool)

/skip_good_regions

Description

Only smooth vertices incident to a triangle whose energy is at least skip_good_regions_margin * stop_energy; skip smoothing already-good regions. OFF by default: smoothing is the only phase that improves quality without changing connectivity, and the premise that a vertex surrounded by good elements has nothing to gain is not reliable – for surface vertices the move is driven by the envelope term rather than by element quality, which is why active_vertices() has to append every surface vertex unconditionally to keep the filter from freezing them outright. With the filter off that special case stops carrying the correctness of the pass. Measured in 2D on 122839 at stop_energy 20, filtering also cost more than it saved: 60 iterations ending at max energy 21.03 with it on, against convergence to 19.9998 in 54 iterations and less wall time (875s vs 947s) with it off. Note the threshold scales with stop_energy, so the filter skips far more at 100 than at 20.

Default: False

/skip_good_regions_margin (float)

/skip_good_regions_margin

Description

A triangle is ‘active’ (worth smoothing around) when its energy is at least this fraction of stop_energy. The AMIPS2D energy of an equilateral triangle is 2, so the filter only skips anything once skip_good_regions_margin * stop_energy exceeds 2.

Default: 0.9