Repository navigation
fix: make clip_with_plane idempotent and deduplicate the kernel round trip - #22
Merged
lachlangrose merged 1 commit intoSep 28, 2026
Conversation
3 tasks
lachlangrose
changed the base branch from
main
to
fix/mesh-review-followups
September 28, 2026 10:26
lachlangrose
force-pushed
the
fix/clip-plane-idempotence-dedup
branch
from
September 28, 2026 11:20
78c1d48 to
661ab61
Compare
3 tasks done
lachlangrose
changed the base branch from
fix/mesh-review-followups
to
fix/implicit-cut-snap-and-diagnostics
September 28, 2026 11:20
lachlangrose
added this pull request to stack #26
September 28, 2026 11:58
lachlangrose
force-pushed
the
fix/clip-plane-idempotence-dedup
branch
from
September 28, 2026 11:59
661ab61 to
b3428d6
Compare
… trip Fixes #14 ("BUG - long runtime/seg fault"), both halves. PMP::clip is not idempotent. Re-clipping a mesh that already lies entirely inside the halfspace removes no face, but still corefines against the plane and splits every edge along the existing seam. Measured on a 250-point surface, 60 identical clips grew it from 192 to 1,754,578 vertices -- ~1.14x per clip, every new vertex exactly on the seam, with nothing more than 1.4e-15 past the plane after the first clip. It happens with both kernels, so it is not the exact round trip; this is the "long runtime" the issue title names. That growth is also the generator for the segfault. convert_to_double_mesh rebuilt with no deduplication, so the surplus seam points rounded onto coincident doubles, and PMP::clip degenerates on the next clip along an edge joining such a pair. convert_to_exact had the same gap in the other direction: write_to_file / read_from_file bypass export_mesh's dedup, so a reloaded mesh entered the exact kernel already carrying duplicates. Three changes: - clipWithPlane early-outs when no vertex lies more than 1e-12 * max|coord| outside the halfspace, returning the documented 0 = no-op and skipping the round trip entirely. It also now rejects a degenerate (a=b=c=0) normal. - convert_to_exact and convert_to_double_mesh merge vertices on exact double equality -- lossless, because the two vertices are one point -- and drop faces that collapse to fewer than three distinct corners as a result. - the two converters differed only in how a point is converted, so they are now one templated rebuild_unique walk (net 42 lines out of meshutils.cpp). Verified against the reported failure: replaying the campaign's captured clip_054 -> clip_055 pair, the unpatched build emits 6 coincident vertices then 7, and the patched build 0 then 0. The existing 76 tests pass unchanged, 40 random single clips are byte-identical to unpatched, and a sliver sweep is identical down to 1e-11 thickness. Behaviour change: a clip shaving less than ~1e-12 of the model extent is now declined rather than performed. Those calls were already removing no faces -- they returned a negative "faces removed" count, having only subdivided. Verified on Linux/x86-64, both commits built in the same container from the same CGAL: before, the captured clip_055 mesh segfaults 3/3 (rc=139) while clip_054 exits clean 2/2; after, clip_055 exits clean 3/3. PyPI 0.1.8 in that container reproduces the crash identically. Note it does NOT reproduce on macOS/arm64 even with the crashing mesh -- only the mesh state is portable, so this class of bug has to be checked on Linux. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
lachlangrose
force-pushed
the
fix/clip-plane-idempotence-dedup
branch
from
September 28, 2026 12:11
b3428d6 to
b3acde1
Compare
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.
What & why
Fixes #14 ("BUG - long runtime/seg fault"), both halves.
PMP::clipis not idempotent. Re-clipping a mesh that already lies entirely inside thehalfspace removes no face, but still corefines against the plane and splits every edge
along the existing seam. On a 250-point surface, 60 identical clips grew it from 192 to
1,754,578 vertices — ~1.14x per clip, every new vertex exactly on the seam, with
nothing more than
1.4e-15past the plane after the first clip. It happens with bothkernels, so it is not the exact round trip. That is the "long runtime" in the title.
That growth is also the generator for the segfault.
convert_to_double_meshrebuilt withno deduplication, so surplus seam points rounded onto coincident doubles, and
PMP::clipdegenerates on the next clip along an edge joining such a pair.
convert_to_exacthad thesame gap in the other direction:
write_to_file/read_from_filebypassexport_mesh'sdedup, so a reloaded mesh entered the exact kernel already carrying duplicates.
Three changes: an early-out in
clipWithPlanewhen nothing lies outside the halfspace(returning the documented
0= no-op); dedup on exact double equality in both converters;and the two converters folded into one templated
rebuild_uniquewalk, since they differedonly in how a point is converted (net 42 lines out of
meshutils.cpp).How to verify
76 tests before, 84 after. The 8 new ones in
tests/test_clip_idempotence.py: 7 failwithout this change and pass with it. The 8th passes both ways by design — it is the
negative control proving the guard does not suppress legitimate clips.
Against the reported crash, both builds compiled in the same container from the same CGAL:
clip_055(crashing mesh)clip_054(control)rc=139x3rc=0x2rc=0x3rc=0x2Note it does not reproduce on macOS/arm64 even with the crashing mesh — only the mesh
state is portable. This class of bug has to be checked on Linux/x86-64.
Downstream, replaying captured failing members through the real caller, 36 before/after
pairs: 30 segfaulted before, 0 after.
Risk
use_exact_kerneltrue andfalse; the new tests are parametrised over both.
produce byte-identical output. On real downstream data, two of three surfaces are
byte-identical and the third has the same point count, cell count, unique point set,
area to the last digit and zero bound shift — only PLY vertex ordering changes, because
dedup changes insertion order.
1e-11thickness.1e-12of the model extent is now declined rather thanperformed. Those calls already removed no faces — they returned a negative "faces
removed" count, having only subdivided.
clipWithPlanenow raisesValueErroron a degenerate (a=b=c=0) plane normalinstead of proceeding.
Checklist
pytestpasses locally🤖 Generated with Claude Code