Repository navigation
topological_offset 3D: curve offsets, every feature curve held - #1161
Draft
udaykusupati wants to merge 6 commits into
Draft
udaykusupati wants to merge 6 commits into
udaykusupati wants to merge 6 commits into
Conversation
…actly The exact band integral (EXPERIMENTAL_band_volume_rule "exact", the default, and "exact_swept") took triangles only: with segments alone the constructor returned with no features, and every value was 0. A curve offset's input is segments and points, so the integral now takes them: - Input: the triangles, or the segments and the points (one kind, as the envelope whose box queries it reads holds one; a complex of both throws). A segment's features are its interior (an edge with no incident face, whose region is the slab between its ends) and its two ends; a point is a vertex, whose region is all of space. The distance bound U and the candidate search run over the primitives of either kind, through an Edges3d envelope for segments and points (points as degenerate segments, as the offset's euclidean_query_envelope() holds them). Triangle inputs build the same features in the same order as before. - Pieces whose members are all edges are split exactly by the bisectors of the pairs whose distances cross on them, planes for edges meeting at a point or parallel (bisector_planes()), each part labelled with its nearest edge by resolve(), the function the front form already labels a face's pieces with. Until now such a piece took rule (a), an upper bound. On a rod the two segments' slabs overlap on the inside of every bend and at every junction, so this is what makes a rod exact; value() and the front form's face labels now cut alike there, which "exact_swept"'s search model relies on. A crossing pair of skew edges still takes rule (a). The split is in value(), value_grad_hess() and max_distance(), counted by Stats::edge_split_pieces / edge_split_vol and MaxInfo::edge_split_pieces. Triangle inputs change only on cells that had such pieces. MEASURED on the 100 stored cells of the five real cases: 15 cells change (cylinder_out 3, slot 7, t100026 5), all of them rule-(a) cells in the Python reference; every new value is at or below the Python's bound, at most 3.0e-7 delta vol below it; the rule-(a) volume falls to 0 on 11 of them. Additivity over the red children now holds on slot's 20 cells (12 before) and t100026's 16 (11), to 2.3e-14 and 1.1e-13; the front form's identity (oint G.n + D_t = J) now covers slot's 20 cells (12) and t100026's 16 (11), to 2.3e-14 and 7.2e-14 delta vol. Tests: - Parity with the Python on cells and faces with split pieces: the Python's value is an upper bound there, and its rule-(a) volume and piece count are this port's plus the split pieces' (the partition is the same). Derivative parity is skipped on those cells only. - The finite-difference errors of the gradient and Hessian are measured in the cell's own unit, S / L and S / L^2 with S = I + delta vol (the two terms J is the difference of), instead of relative to |G| and |H|: a gradient near 0 read ordinary rounding as a failure (slot cell 7: |G| L = 3e-3 S, rounding 3e-15 S in J, relative error 1.7e-7 against the bound 1e-7). The kink classification stays relative to |H|; every corner keeps its class on every case. - New: segments and points (straight, bend, acute corner, planar and non-planar junctions, a non-planar polyline, two points; random band cells and cells inside every bend): no rule (a) inside bends and junctions; parity with the triangle path through a vertical fence below the planar curves (to 1.3e-15); quadrature at 32768 children (its own error at most 7.7e-7 S, the bound 1e-5; rule (a) would be 1.1e-2 to 3.4e-2 S off on the bend cells); additivity; the front form; finite differences at every corner; the elementary-piece cache; max_distance() against a grid and exact at its argmax; the one-kind refusal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The same change as the offset PR's (uday/offset-hold-region-boundaries), byte for byte, so the two merge without a conflict; its test, swap-face-keeps-edge-tags, comes with that PR. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ts, the curve held An input complex of segments and points (an offset_selection without triangles: a rod, a curve network, points) was refused by the band term. Now: - The band term takes it, under every EXPERIMENTAL_band_volume_rule: the exact integral over segments and points (ExactBandIntegral, previous commit), with no offset side, since a curve bounds no solid. offset_in is refused for such a complex: it encloses no volume. - The curve is held as tetwild holds its input_edges (hold_curves_and_points()): the edges tiling each input segment tagged with the feature kind, held in the tube around the segments as extracted, of half-width feature_envelope_ratio x envelope_size (new key, default 1, as tetwild's and simwild's), its ends and junctions, and the complex's points, anchored at that radius (anchor_feature_points(), once, after both kinds are tagged). A vertex on such an edge is pulled toward the tube by the base's rule in smoothing, whether or not a held face reaches it. After the loop audit_input_curves() reports tagged edges outside their tube, untagged edges of the complex and anchors lost, as a warning iff there is one. MEASURED (the 2026-10-16 rod fixture, the rod where three blocks meet, exact rule, 7 turns): before, none of the rod's 6-9 edges tagged, its vertices 0.43 target_distance off the input, moved by the smoothing; after, under exact, exact_swept and corner_bound alike, every vertex on the input to 3e-16, no tagged edge outside its tube, both end anchors held (7 and 15 turns). - The march's split (EdgeSplittingTet, the construction's own) carries the engine's edge tags as the shared split does: it carried m_edge_attribute only, and the tags, an edge collection of their own, stayed at the old edge numbers, on other edges. This was live for every offset with order-2 edges (hold_curves_and_points() tags them before the march). MEASURED (rod fixture): before, neither of the rod's 2 edges tagged after the march, and 12 of the 14 order-2 tags still on order-2 edges; after, both, and all 20 (with the halves of the split ones). construct_offset() now checks a curve complex's edges kept their tags, rather than assuming it. Tests: curve-offset-rod-held (the rod on a 384-tet grid, every rule, one turn: no throw, the complex in its tube, both ends kept), curve-offset-refuses-offset-in. The offset suite: 117 of 117. Not resolved on the rod fixture at 15 turns: 9 front vertices stay over the bar, at the rod's two ends, where the fixture's blocks end on the ambient region and the junction curves of their boundaries, held in order-2 tubes, cross the front's caps (the double_sphere junction's class). MEASURED: every turned face and fold at |z| = 0.50-0.54; with those tubes widened past the domain (order2_envelope_ratio 1e4, the only change), 0 folds, 0 turned faces, the front at 0.996-1.021 target_distance. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…very one held A tet mesh's edge block, the feature curves tetwild and simwild write, was refused: the offset held no feature curve, and they would have been dropped. Now: - Load: each edge tag is a name offset_selection can use, as a face tag is, its edges carrying the tag's bit (EdgeAttributes::feature_inputs) through the construction's own splits, which copy the attribute whole. - Selection: an edge tag selected alone is the input complex, the band growing all around it (a curve offset); within an expression it is refused (one tag at a time). - Hold: hold_curves_and_points() tags every edge tag, selected or not, with the feature kind and its own input bit, held in the tube around its curves as read, one tube per tag, its ends and junctions anchored (tetwild's rule); the curve a tag expression selects (the previous commit's) is one more input. From there on the engine's tags carry them (edge_has_tag()), the per-edge bits cleared. construct_offset() checks after the march that every feature-tagged edge is still in its tube, as well as that a curve complex kept its tags (input_curve_counts(), which the after-loop audit shares). - Output: the edge tags written back as the result's edge block (io::collect_feature_edge_tags(), as tetwild and simwild write it). - Feature points (a point block) are refused: nothing in a tet mesh tags them. MEASURED (the 2026-10-16 rod fixture's mesh, 665 tets, the rod given as an edge tag with every tet ambient instead of as the meeting of three blocks; exact rule, 7 turns, the same config): the front at 0.997-1.018 target_distance (blocks: 0.877-1.020), 0 turned faces and 0 folds (17 and 2), ring max 1.12x the bar (6.45x), max AMIPS 8.4 (26). The blocks' junction curves, held in order-2 tubes, were what the front's end caps stuck on. Tests: curve-offset-edge-tag (an edge tag selected, a second one held: both in their tubes in the result's edge block, end to end), curve-offset-edge-tag-alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
curve-offset-converges, hidden ([.]) for the runs before a merge: three curves given as edge tags on an all-ambient grid -- a corner, three arms meeting out of a plane, a line with a second edge tag held inside its band -- each offset to the settle exit (throw_on_nonconvergence), every edge tag in its tube in the result. MEASURED (exact rule, 1 thread, Release, laptop): resolved at turns 6, 5 and 11, 22 s, 21 s and 47 s. The grid's cubes and tets (cube_grid()) are shared with rod_grid(). Similar shapes through tetwild (a 90 and a 30 degree corner, three arms meeting, a second curve 0.15 away inside the band at target_distance 0.2), and a sweep of target_distance 0.05-0.4 on tetwild's feature curve (2026-11-05): every curve-only case resolved, within 15 turns but target_distance 0.05, at turn 16 (312 s; its front refines longer, marked vertices 810, 393, 10, 0 over turns 13-16); no curve edge outside its tube, no anchor lost. Not resolved: a curve 0.1 from a sheet that crosses its band -- a tracked surface, its rims held in order-2 tubes (on main the loop holds no surface but the input complex's boundary): the front meeting another tracked surface, the double_sphere junction's class. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both sides kept in the five files that conflict (TopoOffsetTetMesh.cpp and .h, topological_offset.cpp, its spec, the tests), and three changes the two branches need together: - TopoOffsetTetMesh's own m_feature_edge_names is dropped: #1159 puts the list in the engine base (TetOptimizerMesh), which the reference checks and the output's edge block read. - With a reference (an `envelope` input that is a labelled reference), hold_curves_and_points() rebuilds the feature tubes around the reference's feature curves after tagging them (init_reference_feature_tubes()) and checks every feature edge received lies in them (check_reference_containment()), as simwild and tetwild hold a reference's curves. #1159 built no feature tube, the offset then taking no feature curve. A curve a tag expression selects is this mesh's, with no curve in the reference: its tube stays around it as extracted. MEASURED (cube, sphere and square with a 16-segment curve through simwild, then three offsets, each holding the envelope the stage before returned): every offset built the tubes around the reference's 16 segments and passed the check; the handed-on envelope keeps the curve's 16 rows through every stage. - untrack_covered_boundaries() recomputes m_is_on_region for the corners of the faces it untracks: a vertex is on a region boundary iff a face of it is still tracked. A curve complex has no faces of its own, so where the band covers every face around one of its vertices, the vertex keeps the flag with no tracked face, and TetMesh::compute_vertex_order() throws for it. MEASURED: curve-offset-rod-held (the rod where three blocks meet) threw "Vertex must have incindent surface faces" without it; with it, 3 corners, the rod's vertices, are left on no region boundary. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Draft, stacked on #1159 (uday/offset-hold-region-boundaries); its base becomes main when #1159 merges.
What this does
A curve offset: topological_offset 3D grows a band (a tube) of
target_distancearound a feature curve, as it grows one around a surface or a volume. Also, every feature curve is held during any offset, as tetwild and simwild hold theirs.offset_selectioncan name an edge tag alone; the band then grows all around that curve. A tag expression whose input complex has segments but no triangles (for example the curve where three regions meet) is a curve offset too.EXPERIMENTAL_band_volume_ruleexact, the default, andexact_swept) now takes segments and points. Where two segments' regions overlap (inside a bend, at a junction), the cell is split exactly by their bisector planes; before, such a piece took rule (a), an upper bound.corner_boundalso works on curves.feature_envelope_ratioxenvelope_size.feature_envelope_ratiois a new key, default 1, as in tetwild and simwild. The ends and junctions of each curve are anchored. With a labelled reference envelope (topological_offset 3D: hold every region boundary and a passed envelope; the march carries the edge tags #1159), the tubes are built around the reference's curves.Commits
ExactBandIntegral: segment and point inputs; pieces of edges split exactly.curve-offset-converges.untrack_covered_boundaries()recomputes the region flag of the vertices it leaves on no tracked face (a curve has no faces of its own).Measurements
target_distanceoff the input. After, they stay on it to 3e-16 underexact,exact_sweptandcorner_bound.exact: resolved at turn 9, front at 0.9986–1.009target_distance, 0 folds.exact_sweptresolved at turn 10.target_distancesweep 0.1, 0.2 and 0.4 resolved within 15 turns; 0.05 resolved at turn 16.curve-offset-converges: resolved at turns 6, 5 and 11.Known limit
A front that meets another tracked surface does not resolve: a curve 0.1 from a sheet that crosses its band, or the rod's caps where the blocks' junction curves are held. This is the same class as the double_sphere junction (#1159's hidden test
double-sphere-other-region-held-or-protected). A reproducer is saved.Not in this PR
[.]cases, this branch vs topological_offset 3D: hold every region boundary and a passed envelope; the march carries the edge tags #1159) is still to do.🤖 Generated with Claude Code