Skip to content

Envelope IO: components write and read what they hold as tags - #1156

Merged
udaykusupati merged 7 commits into
mainfrom
uday/envelope-io
Nov 5, 2026
Merged

udaykusupati merged 7 commits into
mainfrom
uday/envelope-io

Conversation

@udaykusupati

@udaykusupati udaykusupati commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

Follows #1155 (envelope reference).

What this does

Tags become the one way components hand each other what they hold. Each component writes its tracked faces, tracked curves and feature curves as tags, on both routes (memory and file). A reader turns the tags back into tracked simplices with their input sets. Before this PR, several of these were lost between components.

Component Writes
tetwild Oriented face tags, one per input (input_names, else tag_<i>), as triangle blocks of the memory mesh and as physical groups of _final.msh. The winding numbers wn_<i> stay fields. No volume tags.
tetwild, simwild 3D Feature curves (the tet edges that tile each input_edges file) as an edge block, feature_<i> per file. The order2 kind is not written; a reader derives it again.
triwild Each input's curves as edge tags, named as its triangle tag and oriented as the input.
Reader (read_mesh_data(), read_image_msh()) Reads
2D edge block Tracked curves, with their input sets and orientation. EnvelopeSurface rows (or a block without tags) are the envelope, as before.
3D edge block Feature curves, held as input_edges are.
File route Keeps corr_input_vid (and vid), as the memory route always did. A chain of offsets keeps its correspondence through files.

Envelope passing

A pipeline (tetwild → simwild remeshing → …) used to keep its guarantee only against the previous stage's output: each stage built its envelopes around the mesh it received, so the bound against the original input grew by one eps per stage. Now the first component's reference (the input, or the simplified input, per envelope_reference) is passed on with its labels, and a receiver holds to it. The bound stays one eps of the input.

Memory route File route
Producers (tetwild, triwild, simwild) outputs["envelope"], now labelled <output>_envelope.msh (new)
envelope key of a receiver a mesh name a path (new; it was ignored on the file route)
  • The envelope: a mesh of the reference, labelled as the tags are: triangles (2D: segments) with one column per input; the feature curves' segments as an edge block; input points as an untagged point block. No eps.
  • simwild remeshing with an envelope: holds each face to its labels' reference envelopes and each feature edge to tubes around the reference's curves, with its own eps. At the start it checks, exactly, that every face and feature edge it received lies inside those envelopes; otherwise it throws with the label, the count outside and the eps. A label the envelope lacks throws.
  • Pass-through: a receiver hands on the envelope it was given, unchanged. topological_offset and manifold_extraction read and hand it on; neither holds to it yet.
  • Not checked: order-2 tubes and curve-end anchors. The engine holds an edge the reference lacks around itself, which the mesh cannot tell apart from a wider sender.

Behaviour changes

  • tetwild with several inputs and per_input_envelopes off: each tracked face's input set now comes from the arrangement, because the face tags need it. Before, every face was input 0.
  • 2D edge blocks with labels other than EnvelopeSurface: these are now tracked curves. Before, any such label made the whole block the envelope. No data model relies on the old behaviour: the only 2D edge group in data2 is EnvelopeSurface (models/topo_annots_groups.msh).
  • topological_offset and manifold_extraction: given an input with feature curves, they now refuse it instead of silently dropping the curves. Neither holds feature curves yet.
  • tetwild's memory mesh can no longer be written to .vtu: the VTU writer refuses labelled fields, as it already did for simwild's and triwild's meshes. The quickstart example now writes .msh. Making the VTU writer skip tag blocks is a follow-up.

Tests

  • tetwild writes each input's face tags and its feature curves, on both routes.
  • triwild writes each input's curve tags, on both routes.
  • A tagged mesh's edge block reads as tagged curves (2D) and as feature curves (3D).
  • simwild keeps a tagged mesh's curves, on both routes.
  • Tagged mesh files carry an earlier offset's correspondence.
  • Two tests assumed tetwild writes no triangles of its own. They now match faces by their corners.

Verification

Run on this branch, whose base has the same tree as main at ab4fe46, with the envelope passing:

  • All 9 C++ suites pass: wmtk_tests, wmtk_component_io_tests, tetwild, simwild, triwild, tracking, topological_offset, shortest_edge_collapse, manifold_extraction.
  • The 15 docs examples pass.
  • python/tests: 61 passed, 4 skipped (the PolyFEM tests, which need POLYFEM_BIN), including the envelope chain on both routes. One test asserted that triwild's file holds no edge block, which stood for "the file holds no envelope"; it now checks for no EnvelopeSurface rows.
  • tests/python: 110 passed, including the 62 docs-example subtests. One collection error appears on both this branch and main: pytest collects the helper tested_elsewhere() as a test.

🤖 Generated with Claude Code

udaykusupati and others added 7 commits November 3, 2026 22:00
… tags it writes and reads

Tags are the one currency between components: what a component holds -- each input's tracked
faces or curves, the feature curves -- it writes as tags, on both routes, and a reader turns them
back into tracked simplices with their input sets. Until now several were lost on the way.

- tetwild writes oriented face tags, one per input (input_names, else tag_<i>; io::input_labels(),
  io::tag_labels() moved out of collect_tags()), as triangle blocks of the memory route's mesh and
  as physical groups of _final.msh; no volumetric tags (the winding numbers wn_<i> stay fields).
  With several inputs each tracked face's inputs now come from the arrangement whether or not
  per_input_envelopes is on, since they are the tags; before, without it, every face was input 0.
- tetwild and simwild 3D write their feature curves, the tet edges tiling each input_edges file,
  as an edge block, feature_<i> per file (io::collect_feature_edge_tags(),
  utils::feature_input_name()); the order2 kind is not written, a reader derives it again.
- triwild writes each input's curves as edge tags named as its triangle tag, oriented as the input.
- Readers (read_mesh_data(), read_image_msh()): a 2D edge block is tracked curves with their input
  sets and orientation, its EnvelopeSurface rows (or a block without tags) the envelope, as before;
  every other label used to make the whole block the envelope, which no data model relies on (the
  one 2D edge group in data2 is EnvelopeSurface). A 3D edge block is feature curves, held as
  input_edges are. The file route keeps corr_input_vid (and vid), as the memory route always did,
  so a chain of offsets keeps its correspondence through files.
- topological_offset and manifold_extraction refuse an input with feature curves instead of
  dropping them silently: they hold none yet.
- tetwild's memory mesh can no longer be written to .vtu: the VTU writer refuses labelled fields,
  as it already did for simwild's and triwild's meshes; the quickstart example writes .msh.

Tests: TetWild writes each input's face tags and its feature curves on both routes; TriWild writes
each input's curve tags on both routes; tagged meshes read an edge block as tagged curves (2D) and
as feature curves (3D); SimWild keeps a tagged mesh's curves on both routes; tagged mesh files carry
an earlier offset's correspondence. Two tests that assumed tetwild writes no triangles of its own
match faces by corners instead.

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

_same_as_file() asserted that a .msh output has no block of the envelope's cell type (edges in
2D, triangles in 3D). That stood for "the file holds no envelope" while the envelope was the only
such block a component wrote. triwild now writes each input's curves as an edge block of tags,
and a reader takes only that block's EnvelopeSurface rows (or a block without tags) as an
envelope; the check now asks exactly that.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t one's reference

A pipeline (tetwild -> simwild remeshing -> ...) kept its guarantee only against the previous
stage's output: each stage built its envelopes around the mesh it received, so the bound against
the original input grew by one eps per stage. Now the first component's reference -- the geometry
its envelopes were built around, the input or the simplified input per envelope_reference -- is
passed on with its labels, and a receiver holds to it, so the bound stays one eps of the input.

- The envelope is a mesh of the reference, labelled as the tags are: triangles (2D: segments)
  with one tags column per input, named as the output mesh names that input; the feature curves'
  segments as an edge block (feature_<i>); the input points as an untagged point block. No eps.
- Producers (tetwild, triwild, simwild) return it as outputs["envelope"] on the memory route and
  write it as <output>_envelope.msh on the file route (its own vertices, and label names that
  would collide with the mesh's tags in one file).
- The `envelope` key is a mesh name on the memory route and a path on the file route (new; it was
  ignored there). simwild remeshing given one holds each face to its labels' reference envelopes,
  its feature edges to tubes around the reference's curves, with its own eps; a label of the mesh
  the envelope lacks throws, naming it.
- The containment check, at the start: every held face and feature edge received must lie inside
  the reference's envelopes of its labels at the receiver's eps, exactly; otherwise it throws
  with the label, the count outside and the eps (the sender held them to a wider envelope). With
  a reference simwild's old sanity check, which silently rebuilt the envelope from the mesh, is
  skipped. Order-2 tubes and curve-end anchors are built from the reference but not checked: the
  engine holds an edge the reference lacks around itself, which the mesh cannot tell apart from a
  wider sender.
- A receiver hands on the envelope it was given, unchanged. topological_offset and
  manifold_extraction read it on both routes and hand it on; neither holds to it yet.
- simwild sets its feature-curve names before the initialization, so the checks name a label by
  its name.

Tests: tetwild -> simwild and triwild -> simwild chains on both routes (labels and blocks of the
envelope, the output within eps of the original input, both routes alike, the envelope handed on
unchanged, the throws at a smaller eps and on a missing face or feature label); the Python chain
on both routes with a relative envelope path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the input, as the union is

A tagged mesh input given no envelope has its envelope built from the mesh's tracked faces as
read (init_surfaces_and_boundaries()), which cleared the rows' labels. With no row labels the
engine's init_input_envelopes() builds each label's envelope from the mesh's faces as they are
when it is called; simplify() calls it again after collapsing, so under envelope_reference
"input" the union was rebuilt around the input while every per-label envelope was rebuilt around
the simplified faces. The rows now keep their labels (each face's input set as read), so every
per-label envelope is built from the input after the simplification too. Both places that build
the envelope from the mesh (no envelope given, and the sanity check's rebuild) do so.

The simplify spec doc said a tet mesh input is never simplified; in 3D a tagged mesh is, under
remeshing with preserve_topology.

Test simwild-simplification-label-envelopes-around-the-input: a tagged slab whose two-label
interface has a spike of 2 eps the simplification removes; after simplify() the spike's tip, an
input vertex, lies in the union and in both labels' envelopes, on both routes (no envelope given,
and the sanity check's rebuild). Before this commit both labels' envelopes miss it (4 failed
checks, the tip 2.0 eps from the simplified surface); after it they contain it. simwild and
component IO suites pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@udaykusupati
udaykusupati merged commit d5baf7b into main Nov 5, 2026
19 of 20 checks passed
@udaykusupati
udaykusupati deleted the uday/envelope-io branch November 5, 2026 18:05
udaykusupati added a commit that referenced this pull request Nov 5, 2026
…ope it hands on

Since #1156 simwild hands on the envelope it held to, its edges labelled by their inputs
(m_E_envelope_edge_sets), and the 3D simplification refreshes those labels when it rebuilds the
envelope around the simplified surface (m_F_envelope_face_sets). The 2D twin rebuilt the
envelope without them, so under envelope_reference "simplified" the envelope handed on carried
no labels at all: a 64-edge straight interface simplified to 23 edges and came out unlabelled;
now 23 edges, 23 label rows (tag_0).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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