feat(cam-geom): NestedRegion validated multi-loop region #135
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "patman/nested-region"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Island lane I-b. NestedRegion (single/classify/outer/islands/to_stock) with RegionError refusals: depth-2 nesting, multiple outer loops, no outer loop, ambiguity passthrough. The validated input type plan_rings_region consumes. Opus review PASS.
Adds `cam_geom::region`, a new module holding `NestedRegion` and `RegionError`: the validated outer-plus-islands region type the island-clearing design puts in cam-geom rather than cam-clear (docs/superpowers/island-clearing-design.md, step I-b / ID-2, ID-3). cam-clear consumes a validated region instead of a raw `&[Contour]`, so the winding normalization and depth-shape checks live once, next to the `classify_nesting` classifier they are built on, and stay reusable by any future consumer (a cam-depth island relaxation, for instance). What it adds: - `NestedRegion { outer, islands }` with private fields and no public constructor other than the two validating ones, so every live instance satisfies the outer-CCW / islands-CW convention by construction. - `NestedRegion::single(outer)`, the no-island case, normalizing via `orient_ccw`. Returns `Result` purely so both constructors stay interchangeable at call sites. - `NestedRegion::classify(loops)`, which runs `classify_nesting`, requires exactly one depth-0 loop, requires every other loop at depth 1, and normalizes winding regardless of input orientation — outer to CCW, islands to CW. - `NestedRegion::outer` / `islands` accessors and `to_stock()`, which hands the region to the existing multi-boundary machinery: `StockState`'s exact engagement oracle and `largest_inscribed_circle`'s seed search are already island-aware given CW island boundaries, so the seed avoids islands with no new code (design ID-10). - `RegionError` with `NestingTooDeep { depth }` (depth >= 2, a pocket cut into an island: refused in v1 rather than recursed, ID-3), `MultipleOuterLoops` (multi-pocket, out of scope), `NoOuterLoop` (empty input — not in the design's list, added to fail closed rather than panic on an absent outer loop), and `Nesting(NestingError)` propagating ambiguous-containment / crossing-loops / query failures verbatim through a `From` impl. Invariants: construction is the only path to a `NestedRegion`, so outer is always CCW and every island always CW; a wrongly-wound input island is normalized, not rejected, and its interior is correctly excluded from `to_stock()`; refusals here are geometry-shape refusals only — whether a validated region can actually be cleared with a given tool is a cam-clear concern and is deliberately not decided here. Tests: single-loop identity; square-with-island classification with orientation normalization asserted on both loops; depth-2 rejected as `NestingTooDeep`; two depth-0 loops rejected as `MultipleOuterLoops`; empty input rejected as `NoOuterLoop`; ambiguous containment propagated; a CCW (wrong-winding) island normalized to CW with `to_stock()` excluding its interior; `to_stock()` agreeing with `region_contains` on the raw loop set; a polygonal island's interior excluded; and a proptest checking `to_stock()` containment against the hand-computed answer over randomly placed islands. Co-authored-by: patman-assist <patrick-ai@kgroo.co>