fix(algo): trim plane-plane section curves to mutual face overlap#754
Conversation
WASM Binary Size
✅ Size decreased — nice! |
There was a problem hiding this comment.
Pull request overview
This PR fixes incorrect trimming of plane–plane face/face (FF) section curves by shrinking Line intersection curves to the mutual overlap of the two faces’ clipped ranges, improving downstream face partitioning and enabling previously-ignored coplanar/operations examples to pass.
Changes:
- Trim plane–plane
LineFF curves to the intersection of per-face clip ranges (trim_raw_line), recomputing endpoints/bbox/t-range. - Make
clip_line_to_facebuild boundary polygons by chaining edges via shared vertex IDs (instead of trusting potentially inconsistentis_forwardflags). - Improve coplanar/same-domain robustness: reject boundary-centroid “interior points”, treat containment-through-holes as non-overlap, and un-ignore two tests now passing.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| crates/operations/tests/examples.rs | Un-ignores example_custom_profile_extrusion. |
| crates/operations/src/boolean/tests.rs | Un-ignores coplanar_box_cut_d1a2. |
| crates/algo/src/pave_filler/tests.rs | Updates box wire orientations; updates expected overlapping-fuse face counts. |
| crates/algo/src/pave_filler/phase_ff.rs | Implements plane–plane Line trimming to mutual overlap; rebuilds clip polygon via vertex chaining. |
| crates/algo/src/diagnostic.rs | Adjusts test box wire orientations. |
| crates/algo/src/classifier/ray_cast.rs | Adjusts test box wire orientations. |
| crates/algo/src/builder/same_domain.rs | Refines planar overlap logic to exclude containment through holes. |
| crates/algo/src/builder/classify_2d.rs | Makes interior-point sampling reject centroids that land on polygon boundaries. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| match (range_a, range_b) { | ||
| (None, None) => false, // Outside both faces | ||
| (Some(_), None) | (None, Some(_)) => true, // Inside one face | ||
| (None, None) => None, | ||
| (Some(_), None) | (None, Some(_)) => Some(raw), | ||
| (Some(a), Some(b)) => { |
| // For plane-plane Line curves with all-straight-edge faces, trim | ||
| // each curve to the mutual overlap of the two faces' clipped | ||
| // ranges. Without this the section curve spans the union of both | ||
| // face extents, producing over-long chords that cross face | ||
| // boundaries mid-edge and corrupt the downstream face partition. | ||
| let raw_curves: Vec<RawCurve> = if matches!(surf_a, FaceSurface::Plane { .. }) |
Greptile SummaryFixes plane-plane face-face section curves being trimmed to the union of the two faces' extents instead of their intersection, which caused over-long chords that split faces incorrectly during coplanar boolean operations. The trimming is now done up-front in
Confidence Score: 5/5The fix is logically sound and all corpus tests pass; the two observations are edge cases (sub-millimetre geometry scale and inner-wire flag inconsistency) unlikely to fire in the existing test suite. The core trimming logic, the polygon-chaining wire builder, the No files require blocking attention; the two observations are in Important Files Changed
Prompt To Fix All With AIFix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
crates/algo/src/pave_filler/phase_ff.rs:1157-1173
**`polygon_is_convex` uses an absolute cross-product threshold that breaks at small scale**
The hardcoded `1e-12` tolerance for the 2D cross product in `polygon_is_convex` is an area-unit quantity (UV length²). For faces whose projected UV coordinates are in the sub-millimetre range (e.g. a 10μm × 10μm planar patch), edge vectors are ~1e-5 long and the cross product for any non-collinear turn is ~1e-10 — below this threshold — so every vertex is treated as collinear and every polygon passes the convex test. Cyrus-Beck clipping then runs on a non-convex outline and can return an over-trimmed interval, producing a section curve that's too short rather than discarded.
### Issue 2 of 2
crates/algo/src/builder/same_domain.rs:465-474
**`in_hole` still relies on `is_forward` for hole-wire vertex ordering**
The new `in_hole` closure calls `wire_points(wid)` for each inner-wire ID. `wire_points` uses `oe.is_forward()` to pick the vertex that starts each oriented edge, which is precisely the ordering path that `clip_line_to_face` was refactored away from in this PR (with the comment "wires from external builders may carry inconsistent `is_forward` flags"). If an inner-wire's oriented edges have inconsistent flags the collected vertices form a self-intersecting polygon, and the even-odd `point_in_polygon_2d` test will give unpredictable results — a point in a hole might be reported as outside it, and a face pair that should be rejected as overlapping-through-hole passes through to the SD list.
Reviews (2): Last reviewed commit: "fix(algo): disambiguate empty vs degrade..." | Re-trigger Greptile |
…olygons The plane-plane FF mutual-overlap trim treated a `None` clip from one face as a conservative keep regardless of cause. Split the two causes: a built convex polygon that the line lies outside now yields `FaceClip::Empty` and the section curve is dropped, while a polygon that could not be built (or is non-convex) yields `FaceClip::Indeterminate` and keeps the raw curve. Cyrus-Beck clipping is only valid for convex polygons, so gate the active trim with a signed-cross-product convexity check; non-convex outlines fall back to keeping the raw curve. Strengthen `planar_faces_overlap`'s hole suppression: in addition to the single interior-sample test, reject a pair when every sampled point of the candidate inside the container's outer boundary falls inside a hole, so a non-convex face straddling a hole boundary no longer slips through. The common convex case is unchanged.
🤖 I have created a release *beep* *boop* --- ## [2.102.0](v2.101.3...v2.102.0) (2026-06-09) ### Features * **algo:** split u-periodic faces into bands at internal section circles ([#756](#756)) ([39e9425](39e9425)) ### Bug Fixes * **algo:** adopt existing boundary vertices as seams for closed section curves ([#755](#755)) ([3342271](3342271)) * **algo:** trim plane-plane section curves to mutual face overlap ([#754](#754)) ([e692c9c](e692c9c)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: brepkit[bot] <265643962+brepkit[bot]@users.noreply.github.com>
Plane-plane FF section curves were trimmed to the combined extent of the two faces (min-of-mins/max-of-maxes in
trim_t_range_to_aabb) instead of their mutual overlap. The per-face clip ranges were already computed byclip_line_to_facebut only used to reject disjoint curves. Over-long chords crossed mid-edge with no vertices at the crossings, so coplanar cuts produced overlapping band sub-faces instead of a proper partition (e.g.coplanar_box_cut_d1a2: volume 0.667 vs 0.75 and zero same-domain pairs).Changes
trim_raw_lineshrinks each plane-plane section curve to the intersection of the two faces' clip ranges, recomputing endpoints/bbox/t_range; existing vertex snapping preserved.clip_line_to_facebuilds the face boundary polygon by chaining edges on shared vertex IDs instead of trusting storedis_forwardflags (mis-ordered polygons silently truncated clip ranges).classify_2d::sample_interior_pointrejects centroids lying on the opposing face boundary.coplanar_box_cut_d1a2(correct frame+square partition, 2 SD pairs, volume 0.75) andexample_custom_profile_extrusion(3/3).Remaining coplanar failures (
coincident_planesnon-manifold cases, thin-shell fuse) trace to a separate defect — collinear split-edge images are not propagated to unsplit neighbor faces — and stay ignored.Verification
Full corpus green: algo 94, operations 630 lib + 18 integration targets, wasm 206; fmt, clippy
-D warnings, boundary check.