Skip to main content

Module lower

Module lower 

Source
Expand description

Lower an EdgeFeatureData into pure-CSG masks against its child.

Chamfers and fillets are not primitives anywhere in the pipeline — an EdgeFeature node lowers to booleans over ordinary Cube/Cylinder/Cone/Revolve solids, so every backend (Manifold, BSP, SDF ray-march, projection) renders them through variants it already supports. Subtractive masks cut as Difference(child, Union(sub)); additive masks (concave ring fillets) union on top of that.

Edges are re-resolved against the current child on every lowering (resolve_edge), so features follow parameter edits; selections that no longer resolve are skipped (callers surface the count from LowerReport). Masks are built in the child’s local frame — callers apply outer transforms above the feature node as usual.

§Chamfer masks

  • Straight convex edge: the cut plane passes through the two setback lines P_F = edge + s·d̂_F; the mask is an oriented box with one face on that plane, deep enough to swallow the edge, emitted as Translate ∘ Rotate ∘ Cube. Extending past the setback lines is safe — beyond them the box sits on the air side of both faces (convexity) — and the tiny ε overhang along the edge keeps corners watertight (adjacent chamfers stay un-mitered in v1).
  • Circular convex rim: the meridian setback points revolve into a cut cone (a cylinder when the setbacks share a radius). The mask is Difference(slab, cone) when the rim’s material lies radially inside (outer rims) or Intersection(slab, cone) when it lies outside (hole-rim countersinks). The slab is clamped axially to the setback band, so blind holes are never deepened and walls below the chamfer are untouched; radial/axial ε only ever extends into air.

§Fillet masks

For faces meeting at interior angle γ (cos γ = d̂_A·d̂_B), a radius-r arc tangent to both faces has its center a distance h = r/sin(γ/2) down the bisector and touches each face at setback s_T = r/tan(γ/2) from the edge. The clamp below applies to s_T (that is what must fit on the faces), and r rescales with it.

  • Straight convex edge: the chamfer box through the s_T setbacks, minus the tangent cylinder along the edge — the cylinder wall becomes the fillet surface. Fully SDF-visible.
  • Circular rim (convex or concave): the meridian wedge between the setbacks minus the tangent circle, sampled into a polygon (data.segments arc chords) and revolved around the rim axis. Convex rims subtract the ring; concave rims (boss-base junctions) add the corner-fill ring. Revolve masks are invisible in the SDF preview (existing sentinel) and ignored by the analytic picking walk — mesh backends render them exactly.

Feature size is clamped per edge to 0.45 × the smaller adjacent face extent, so a slider can’t push a mask through the far side of a face.

Rim masks tessellate at the curved leaf’s own segment count and angular phase ([rim_segments]/[place_on_rim]): where a mask surface runs along the leaf’s wall (countersink tangency, fillet tangent bands), coincident chords make the boolean junction a clean polygon ring instead of an interleaved sliver band (wobbly seams, streaky shading).

§Frame-mapped (elliptical) rims

Rims of anisotropically scaled leaves resolve as circles in the leaf’s own frame (CircularEdge::frame). Their masks are built in that frame by the same meridian constructions and wrapped with the frame transform (Translate∘Rotate∘Scale∘Rotate from an SVD split), so the cut is the exact affine image of the circular one: the feature stretches with the shape, and size/extents are frame-local for those edges. A constant-width cut along an ellipse is not expressible with the primitive vocabulary (its offset curves are not conics), so stretching is the exact-CSG semantic choice.

Structs§

LowerReport
What a lowering pass did — callers log/report the skip count.
LoweredMasks
Masks for one lowering pass, in the child’s frame: sub cuts material (chamfers, convex fillets), add fills it in (concave ring fillets).

Functions§

lower
Lower data against child: subtract the cut masks, union the fill masks, or pass the child through unchanged when nothing resolves.
lower_masks
The masks for data against child, in the child’s frame. Unresolvable/unsupported edges are skipped and counted.