Skip to content

feat: snap implicit cuts to existing vertices, add does_self_intersect - #25

Merged
lachlangrose merged 1 commit into
fix/mesh-review-followupsfrom
fix/implicit-cut-snap-and-diagnostics
Sep 28, 2026
Merged

lachlangrose merged 1 commit into
fix/mesh-review-followupsfrom
fix/implicit-cut-snap-and-diagnostics

Conversation

@lachlangrose

Copy link
Copy Markdown
Member

Stack 2 of 3. Based on #24, and #22 is based on this. Merge order: #24 → this → #22.

What & why

Follow-on work to the mesh review.

Implicit-cut snapping. cut_with_implicit_function now snaps a crossing onto an
existing vertex when it lands within snap_tol (as a fraction of the edge) instead of
emitting a sliver an infinitesimal distance from it, and a per-edge dedup guard stops the
second visit to a shared edge pushing a duplicate crossing vertex — that was tearing the
mesh along every seam and compounding over sequential cuts.

The snap is recorded per slot, not by writing the isovalue into the shared per-vertex
array. The per-vertex form leaks a per-edge fact into every sub-triangle incident to that
vertex: a legitimate keep-side sub-triangle then reads as entirely on-seam, and an
inclusive keep/discard test drops it from both sides. Measured on a 2-triangle case, the
per-vertex form kept 0.25 of a correct 0.375 — a third of the retained surface silently
deleted. The filter now asks whether any original (non-crossing) vertex lies strictly on
the keep side, and applies only to the one-sided modes so PRESERVE_INTERSECTION still
keeps everything.

does_self_intersect() exposes PMP::does_self_intersect. A self-intersecting
surface is outside the domain of the boolean operations, so this is the check to run
before handing a mesh to them.

Threading contract documented. Most methods release the GIL, so concurrent access to
one TriMesh is a data race in C++ — corrupt memory, not merely a wrong answer. corefine
additionally mutates its other argument, which was undocumented.

perf on the kernel converters: flat vector remap keyed on the dense Vertex_index
instead of std::map, preallocation to the source's live counts, an rvalue set_mesh so
the converters' temporaries move rather than deep-copy, and interior_only on the
constraint snapshot to skip border edges the caller re-derives anyway.

How to verify

cmake -S . -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build -j
pytest

82 passing. New: the snap partition (both sides sum to the input area; snapping does not
move the cut) and three for does_self_intersect including that it does not mutate.

Risk

  • Geometry — implicit-cut output changes where snapping now fires or where the
    duplicate-crossing tear used to happen. That is the point of the change; both directions
    are covered by tests.
  • Kernel paths — the converter perf work is on the exact round trip; behaviour is
    unchanged, only allocation.
  • API — does_self_intersect is new public surface. No signatures changed.

Checklist

  • A test that fails without this change and passes with it
  • pytest passes locally
  • Both kernels covered where the change touches clipping or conversion

🤖 Generated with Claude Code

@lachlangrose
lachlangrose added this pull request to stack #26 September 28, 2026 11:58
@lachlangrose
lachlangrose force-pushed the fix/implicit-cut-snap-and-diagnostics branch from dbcc111 to a47e56f Compare September 28, 2026 11:59
Follow-on work to the mesh review, sitting between the review follow-ups and the
clip fix.

- cut_with_implicit_function now snaps a crossing to an existing vertex when it
  lands within snap_tol (as a fraction of the edge), instead of emitting a sliver
  an infinitesimal distance from it. A per-edge dedup guard stops the second
  visit to a shared edge pushing a duplicate crossing vertex, which was tearing
  the mesh along every seam and compounding over sequential cuts.

  The snap is recorded per slot (newtris_on_seam), not by writing the isovalue
  into the shared per-vertex array. The per-vertex form leaks a per-edge fact
  into every sub-triangle incident to that vertex: a legitimate keep-side
  sub-triangle then reads as entirely on-seam and an inclusive keep/discard test
  drops it from BOTH sides, silently deleting kept surface wherever the seam runs
  through a vertex. The filter now asks whether any original (non-crossing)
  vertex lies strictly on the keep side, and applies only to the one-sided modes
  so PRESERVE_INTERSECTION still keeps the whole surface.

- does_self_intersect() exposes PMP::does_self_intersect. A self-intersecting
  surface is outside the domain of the boolean operations, so this is the check
  to run before handing a mesh to them.

- Document the threading contract: most methods release the GIL, so concurrent
  access to one TriMesh is a data race in C++, not just a wrong answer. corefine
  additionally mutates its `other` argument, which was undocumented.

- perf: convert_to_exact / convert_to_double_mesh use a flat vector remap keyed
  on the dense Vertex_index instead of std::map, and preallocate to the source's
  live counts; set_mesh gains an rvalue overload so the converters' temporaries
  move rather than deep-copy; snapshot_constraint_coords(interior_only) skips
  border edges the caller is about to re-derive anyway.

Tests: the snap partition (both sides sum to the input area, and snapping does
not move the cut) and does_self_intersect. 82 passing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@lachlangrose
lachlangrose force-pushed the fix/implicit-cut-snap-and-diagnostics branch from a47e56f to 5ed79bf Compare September 28, 2026 12:11
@lachlangrose
lachlangrose merged commit 6ac4e99 into main Sep 28, 2026
18 checks passed
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