Skip to content

tetwild, triwild: tagged mesh input held to a passed envelope; IO keeps data fields - #1157

Merged
udaykusupati merged 8 commits into
mainfrom
uday/tetwild-tagged-input
Nov 5, 2026
Merged

udaykusupati merged 8 commits into
mainfrom
uday/tetwild-tagged-input

Conversation

@udaykusupati

Copy link
Copy Markdown
Collaborator

tetwild and triwild take a tagged mesh as their input and hold it to a passed envelope, so a chain of components keeps one guarantee (within eps of the first component's input) instead of one eps per stage. Built on #1156.

Commits

  1. Engine: simwild's envelope-reference checks (require_reference_face_labels, init_reference_feature_tubes, check_reference_containment, and the 2D twins) move unchanged into TetOptimizerMesh / TriOptimizerMesh, for the tagged routes below.
  2. tetwild, triwild: a tagged mesh that fills its box. The tagged route skips simplification and insertion; face tags become the tracked surface, feature edges and points stay tracked, and envelope holds the run to a passed reference. A mesh with a volume tag throws ("use simwild").
  3. tetwild, triwild: filter_inputs names the inputs that bound the domain a filter keeps.
  4. simwild: a tagged mesh must be the only input, and its tracked faces must lie inside it.
  5. Component IO + triwild winding numbers: a reader keeps the tags and the shared data fields (io::ComponentIO::data_fields) and drops every other field, naming the dropped fields in the log. triwild writes each input's winding number as wn_<name>, as tetwild does. They are one commit because neither stands without the other: triwild's new field would break triwild → simwild chains.
  6. simwild: simplification_runs() states that the quality check is on, and when the envelope is rebuilt.

Behaviour changes

Input Before Now
A tagged mesh with tracked faces on its outer boundary (a filtered output), in tetwild or triwild — (no tagged route) throws, naming the count; take a filter "none" output
A tagged mesh with untracked boundary faces in no box plane, in tetwild or triwild — (no tagged route) throws
simwild, a tagged mesh with tracked faces on its outer boundary remeshed as is throws
simwild, a tagged mesh plus other inputs the other inputs dropped silently throws
Any reader, a field it does not use Unknown mesh attribute thrown dropped and named in the log
topological_offset's result eleven diagnostic fields tags, data fields, amips, distance_over_target

Known and left as is (TODO): simwild accepts a tagged mesh whose untracked outer boundary is in no box plane, as main does; nothing holds that boundary during smoothing (by reading, not run).

Tests

ctest 255/255 and python/tests 62 passed on the merged tree (macOS arm64, Release); each commit compiles. New cases: the tagged route on both wilds, the boundary throws, simwild's only-input throw, wn_ fields on both routes, fields dropped on every reader, and the offset's trimmed output.

🤖 Generated with Claude Code

udaykusupati and others added 8 commits November 5, 2026 13:05
…and TriOptimizerMesh

tetwild's and triwild's tagged routes (next commit) hold a mesh to a passed reference as simwild
does, by the same code, so the code simwild runs moves out of simwild, unchanged:
- TetOptimizerMesh, from SimWildMesh: m_has_reference, m_reference_curves, m_feature_edge_names,
  label_name() (now virtual), require_reference_face_labels(), init_reference_feature_tubes() and
  check_reference_containment(), with their helpers bit_names() and counts_by_name().
- TriOptimizerMesh, from SimWildMeshTri: m_has_reference, label_name() (virtual),
  require_reference_edge_labels() and check_reference_containment().
- io (tagged_mesh_data.hpp): read_labelled_envelope(), a labelled reference as a receiving mesh
  reads it, from simwild's read_mesh_data(); held_rows(), the reference's rows of the mesh's
  labels, from simwild.cpp.
The checks that read simwild's members take them as arguments: the reference's labels
(m_F_envelope_labels, m_E_envelope_labels) and, in 3D, the eps. simwild calls the base versions.
No behaviour change: the same checks, errors and log lines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…envelope is rebuilt

The comment said SimWildMesh::simplify() collapses with the quality check off and then rebuilds
the envelope around the result. The check is on since 16cbe2a (2026-08; EdgeCollapsing.cpp
says why), and the envelope is rebuilt around the result only with envelope_reference
"simplified"; with "input" it is restored around the input. Comment only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… to a passed reference

The tagged route: one tagged mesh as the input -- on the memory route a named mesh with tets
(triwild: triangles), on the file route a .msh with them -- continued from as it is, its cells
kept, so that a chain of components can go on through tetwild and triwild (tetwild ->
topological_offset -> ... -> tetwild). Skipped on it: the reading of surfaces (curves), the
simplification, the insertion (the arrangement) and the input winding numbers. Until now a tet
mesh threw on tetwild's memory route ("Expected triangle input"), and on the file route each
triangle of the .msh became one input surface, its tets and tags dropped.
- read_tagged_mesh() (tetwild, triwild) reads the mesh as simwild's read_mesh_data() reads one:
  the triangle block's tags (2D: the edge block's) are the tracked faces (curves), one input per
  label, each row oriented by its vertex order; in 3D the edge block's tags are feature curves;
  the point block is input points. Only `tags` is read, every other field dropped, so a result goes
  back in as it is. Refused: tets (2D: triangles) tagged other than ambient -- volumes and regions
  are simwild's to remesh --, inverted cells, rows labelled EnvelopeSurface, and the rest listed in
  read_tagged_mesh.hpp.
- TetWildMesh::init_from_tagged_mesh(), and triwild's init_mesh() on the mesh's own triangles,
  initialize as after an insertion: every vertex rounded, the tagged faces (curves) tracked with
  their labels and orientation, the feature curves and points tagged and anchored.
- The mesh must fill its bounding box, as the insertion's background box (the arrangement's padded
  box) does: its boundary faces (2D: edges) are the wall, held by the box rule. A tracked boundary
  face, the boundary of a filtered mesh, throws: the operations hold a tracked face from the cell
  across it, which a boundary face has none of (measured on two filtered unit squares held by their
  envelopes: a collapse left an edge 0.5 off its square). So does an untracked boundary face in no
  plane (line) of the box, which nothing would hold.
- `envelope`: the reference an earlier component returned, as simwild takes it. The envelopes,
  tubes and anchors are built around it at this run's eps, label by label, its labels matched to
  the mesh's by name, and before the optimization the faces (edges) received must lie in it,
  checked exactly by the base's checks (previous commit). It is handed on unchanged. Without it
  the envelopes are built around the tagged faces as received, and those are handed on.
- Refused with a tagged mesh: any other input, input_names, input_points, tetwild's input_edges and
  use_legacy_code, filter "input" (there is no input surface), and with `envelope`
  use_sample_envelope or an envelope without labels.
tetwild writes no tet tags, so its output goes back into its tagged route as it is. triwild still
writes each input's region as triangle tags, which the route refuses: its own output goes back in
without them (skip_winding_number under filter "none").

Tests: tetwild and triwild continue from a tagged mesh on both routes with one result, the
reference they hand on being the rows received; tetwild takes its own output back in, fields and
all; volume and region tags, an offset's band, a filtered mesh and the inputs the route has no use
for are refused; a tagged mesh is held to a passed envelope on both routes, at a tenth of its eps
refused, as is an envelope without a label of the mesh; the wall's checks (test_tagged_input.cpp);
the Python memory route gives the file route's result.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n a filter keeps

With several inputs every filter keeps the domain all of them bound. filter_inputs (default: all)
names the inputs that do, by their labels (input_names, else tag_<i>, or a tagged mesh's face-tag
or edge-tag labels); the others -- an open sheet through a solid, a second solid outside it --
neither cut nor add to the domain, and stay in the output as tracked faces (2D: edges), with
their tags, wherever they lie.
- "input": the winding number of the named inputs only (tetwild's compute_input_winding_numbers()
  sums theirs; triwild's filter_with_input_winding_number() keeps a face inside one of them).
- "tracked": the winding number of the tracked faces carrying a named input only. A tracked face
  carrying a named input and another one (two solids touching face to face) throws before the
  filter: its orientation is one count for all its inputs, so the named inputs' own is not known
  there; "flood" needs no orientation.
- "flood": the fill crosses the tracked faces of the other inputs.
A name that is not an input throws, and so does the key with filter "none", or in tetwild with
use_legacy_code under "tracked" or "flood". The field the filter reads (winding_number_input,
winding_number_tracked, part) is then of the named inputs.

Tests: two unit tets (2D: squares) 3 apart -- every filter keeps both by default, and with
filter_inputs a only a, its volume 1/6 (area 1) exactly, on the surface and the tagged route; two
unit boxes (squares) touching -- "tracked" refused, "flood" keeping a alone; an unknown name and
the key with filter "none" refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nside it

- A tracked face on the boundary of a tagged mesh (2D: a tracked edge) throws, in 3D and 2D. A
  filtered mesh's boundary is its tracked faces, and the operations hold a tracked face from the
  cell across it (collapse_edge_before() checks a merged tracked face in the tet beyond it), which a
  boundary face has none of; tetwild's and triwild's tagged routes refuse it too (previous commits).
  The insertion's padded box puts no tracked face there. An untracked boundary face in no plane of
  the box is taken as before: simwild remeshes an object's tet mesh too (held by nothing there:
  smooth_before() freezes only the vertices on the box's planes).
- A tagged mesh with any other input throws: the route reads input[0] alone, and until now dropped
  the others without a word.

Tests: the 2D mesh of "SimWild keeps a tagged mesh's curves on both routes" gains the two triangles
below its square, so that its curve is inside it; "TetWild hands on its envelope and SimWild holds
a tagged mesh to it" and its Python twin run tetwild with filter "none", a filtered mesh being
refused now; a tagged mesh with a surface input is refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…winding numbers are fields

Mesh fields come in two kinds. Data is what a component reads: `tags`, the envelope mesh, and the
named fields of io::ComponentIO::data_fields (vid, corr_input_vid, labels, is_cone, vertex_id,
offset_tag, tag_0, edge_id, dofs, uv, normal), each listed with its reader. Every other field is
an annotation, written for people -- tetwild's energy, triwild's quality, the inputs' wn_<name>,
the offset's diagnostics: every reader now drops it, on both routes, and names the fields it
dropped once per read, at info (ComponentIO::dropped_fields(), log_dropped()). A field a reader
requires (c1's dofs) is still an error when missing. Until now each reader listed the fields it
took and threw `Unknown mesh attribute` on any other, so a new field broke a chain until every
list had it: triwild's wn_<name> (below) broke triwild -> simwild on the memory route; one commit, so no
state has one without the other.
- Readers: ComponentIO's raw_triangles(), triangles() and edges(); simwild's read_mesh_data()
  (topological_offset's and manifold_extraction's too) and its .msh route; the labelled envelope
  (read_labelled_envelope()); tetwild's and triwild's tagged routes, which dropped silently;
  isotropic_remeshing's features; prismatic_mesh; c1_simplification; tracking's transfer, whose
  vertex fields it warned about or, strict, refused. reader_vertex_attributes and the
  vertex_attributes argument of raw_triangles() and triangles() go.
- topological_offset's result -- the memory meshes, the .msh, the save_vtu file -- carries the
  tags, the data fields and two annotations, amips and distance_over_target (DataWriter's `kept`
  filter); on the mesh it carried eleven annotations besides, which every reader had to list. The
  DEBUG_output frames carry every field, as before; the stencil file is a frame's only.

Tests: a field no reader reads goes through tetwild's, triwild's and simwild's tagged routes,
qslim, isotropic_remeshing without feature_fields, ComponentIO::triangles() and the Python memory
route as without it; triwild's mesh, fields and all, goes into simwild with the result of its tags
alone; the offset's result carries exactly the tags, the data fields and the two annotations, and
a debug frame every field.

Triwild: triwild kept each input's winding number only as triangle tags, one region per input named as its
curve. Regions are simwild's, as volumes are (tetwild writes none), and its tagged route (previous
commits) refuses them, so triwild's output could not go back into triwild. Now:
- each input's winding number at each triangle's centroid is kept (FaceFinalizeAttributes,
  registered as tetwild's TetFinalizeAttributes) and written as a field: wn_<name> on the memory
  route and in the VTU (wn_<i> without input_names), wn_<i> in the .msh, as tetwild's _final.msh;
- the inside bits still decide filter "input", and are then cleared: the triangles carry no tag but
  ambient. The curves stay the edge tags.
So triwild's output goes back into its tagged route as it is. skip_winding_number leaves the
fields out under filter "none".

Tests: each triangle's wn_a and wn_b are 1 inside their square and 0 outside, wn_0 and wn_1 in
the .msh with or without names, left out with skip_winding_number under filter "none", none on
the tagged route; triwild's output, fields and all, goes back in on both routes; "TriWild writes
each input's curve tags on both routes" finds no region tags. The docs examples print each input's
tracked edges.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@udaykusupati
udaykusupati merged commit 3b110d8 into main Nov 5, 2026
12 checks passed
@udaykusupati
udaykusupati deleted the uday/tetwild-tagged-input branch November 5, 2026 21:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant