Skip to content

feat(booleans): union and intersection of planar polyhedra, with containment - #58

Merged
aka-blackboots merged 10 commits into
mainfrom
task/planar-solid-union
Nov 15, 2026
Merged

aka-blackboots merged 10 commits into
mainfrom
task/planar-solid-union

Conversation

@aka-blackboots

@aka-blackboots aka-blackboots commented Nov 15, 2026 •

Copy link
Copy Markdown
Contributor

What

Union and intersection of planar polyhedra, with the containment cases a building model needs:

  • Union and intersection of planar polyhedra, including bodies that only touch.
  • Containment decided from a boundary point of each body, and only when every shell of a body agrees.
  • A contained solid becomes a cavity of the solid that holds it.
  • Subtracting a planar host that lies wholly inside the tool gives nothing; subtracting a hollow cutter that lies wholly inside the host leaves a cavity.
  • Planes within an angular resolution are treated as parallel.

Why

Floorkit's roof joins (valleys kept as true edges) and its seam unions need planar union and intersection of non-box solids.

Tests

Note

This PR and #59 both start from main. They conflict in boolean/dispatch/table.rs and in the golden fixture tests/fixtures/golden/aarch64-apple-darwin.json. The second one to merge needs those two files resolved and the golden fixture regenerated.

New creating tests cover crossing, nested, touching and non-convex pairs of
flat-faced solids. Union and intersection of the crossing and non-convex
pairs are refused today with CoverageGap, and subtracting a box that stands
on a turned cuboid fails with InvalidTopology, so those tests are red. The
nested turned boxes, the crossing-gable subtraction and the edge-only and
corner-only contacts pass as guards: touching bodies union into two separate
solids and intersect into an empty body through
generic_non_intersecting_boolean, and their subtraction leaves the host
unchanged.

The multi-tool failure test now uses a tilted cylinder, whose union stays
refused. The rotated-box matrix test now expects both rows to build through
boolean_planar_polyhedra, and the step oracle expects 17 exported rows;
both are red until the change and its fixture update land.
The general planar handler takes the operation. Host pieces are kept
outside the other body for union and subtraction and inside it for
intersection; cutter pieces are kept outside for union and inside for
the other two, and are turned inside out only for subtraction. Union and
intersection run under the new handler name boolean_planar_polyhedra;
subtraction keeps subtract_planar_polyhedra and its results. The report
carries the real operation.

The rotated-box union and intersection matrix rows now build; their
stored results and golden keys are rewritten from this kernel, and the
golden hashes of the handler-name sets gain the new name.
Coplanar faces of the two bodies that face the same way become one
coincident face naming the input faces of both bodies; faces of one
body in one plane stay apart. Bodies that touch only at edges or
corners, or dip into each other by less than the tolerance, are
contact: their union is left to the non-intersecting route as before,
and their intersection is the empty body that route builds for
separate bodies. A body standing on a face unites into one solid, and
subtracting it leaves the host whole: the convex shortcut makes no cut
on a face the cutter lies wholly outside of.

An arrangement that cannot close a cell below the classification
tolerance is a coverage gap for union and intersection, so the generic
route runs; subtraction keeps its error.
The migration guide gains an Unreleased entry: union and intersection of
planar solids now build, how touching bodies behave, and the changed
families of a planar refusal. Node tests check a crossing gable pair and
a turned cube through the SDK.
When the host lies inside the tool and touches it on a face, an edge or a
corner, every host piece is removed and the planar handler threw
CoverageGap. The result is empty.
Faces of a polyhedron whose points are turned about a slanted axis can be
parallel except for a normal cross product of about 1e-16. The plane
intersection either failed with a singular system or returned a line far
outside both faces, so a solid wholly inside another threw
UnresolvedIntersection. Planes whose normals differ by a sine of at most
1e-11 are now parallel: coincident or apart by their distance.
The generic route for bodies whose boundaries do not cross tested a point
moved inward from one face of each body. When the other body lay closer to
that face than the step, the point fell inside it and both bodies looked
inside each other. A point on one body's boundary, away from any contact,
is inside or outside the other body, and that decides the containment.
One boundary point decides containment only for a body with one shell. A
body with a cavity, or with several separate solids, can have shells on
both sides of the other body, and the generic route then built a wrong
result. Each shell now gives its own side; when they differ the route
refuses, and a later handler may still build the pair.
The containment route sampled every shell in the topology, open sheets
included, so a sheet could pass as a solid and a later subtraction indexed
past the end of the solids. Shells now come from the solids only.

Subtracting a contained body made a cavity from its last solid only and put
it into the first solid of the host. Every solid of the contained body now
becomes a cavity of the host solid that holds it; a contained body with a
cavity of its own is refused.
The material left inside each cavity of the cutter becomes its own solid,
and each cavity goes to the innermost solid that holds it.
@aka-blackboots aka-blackboots changed the title feat(booleans): union and intersection of planar polyhedra, with containment (OPE-158) feat(booleans): union and intersection of planar polyhedra, with containment Nov 15, 2026
@aka-blackboots
aka-blackboots merged commit 121cb48 into main Nov 15, 2026
0 of 4 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