Overview
minkowski() in bcad implements a simplified Minkowski sum: the first non-sphere child is the base shape, and all sphere(r) children contribute to a rounding offset. Non-sphere geometry children beyond the first are ignored with a warning.
offset(r) / offset(delta) is a separate module that shares the same core engine (_offset_3d / _offset_2d). It auto-detects 2D inputs (single-face or wire) and routes to _offset_2d (planar wire offset via BRepOffsetAPI_MakeOffset), or 3D inputs to _offset_3d.
Both use BRepOffset_MakeOffset with GeomAbs_Arc join mode for rounded offsets (when r is specified), or GeomAbs_Intersection for sharp (delta-mode) offsets.
Algorithm
minkowski() { <base>; sphere(r1); sphere(r2); ... }
Collect children. The base shape is the first child that is not a sphere. All
sphere()radii are summed intooffset.Micro-sphere patch.
_place_micro_spheres()inserts tiny spheres at every vertex with valency ≥ 4 (see below). The spheres are fused into the base shape viaBRepAlgoAPI_Fuse.Offset.
BRepOffset_MakeOffsetwithBRepOffset_Skin,GeomAbs_Arcjoin,RemoveIntEdges=Falseapplies the rounding.Solid conversion.
_ensure_solid()converts the offset result to aTopAbs_SOLID:TopAbs_SHELL→BRepBuilderAPI_Sewing+BRepBuilderAPI_MakeSolidTopAbs_COMPOUND→ extract SOLIDs (largest by volume), or convert SHELLs → MakeSolid → Fuse
Fallback. If the micro-sphere fix produces a non-SOLID result (e.g., a COMPOUND with zero-volume degenerate shells),
_offset_3dretries the offset on the original base shape (without micro-spheres). This trades perfect degneracy handling for a valid solid.Face color propagation.
_map_offset_colors()maps source face colors through the offset result usingmaker.Generated(face)(requiresRemoveIntEdges=False).
Degeneracy at valency-4+ vertices
Root cause
Some boolean intersection geometries produce a shape with vertices where 4 or more edges meet (vertex valency ≥ 4). This happens in particular when:
- A cylindrical or conical hole is cut through a cube at exactly 45° rotation around the X axis.
- The periodic surface seam (a parametric boundary on cylinder/cone surfaces) passes through the same point as the intersection of the hole boundary with the cube edge.
At such a vertex, the following edges coincide:
- 2 elliptical intersection edges (from the cylinder intersecting two adjacent cube faces)
- 1 cube edge (the outer edge between those two cube faces)
- 1 cylinder seam edge (the periodic surface boundary)
→ 4 edges, 4 faces meeting at a single point.
A block-shaped hole (no periodic seam) can also produce valency ≥ 4 when its own edges line up with the outer shape's edges:
// valency-5 example:
difference() {
cube(10, center=true);
rotate([45, 0, 0]) cube(7.07, center=true);
}
Here the inner cube corner (3 faces) meets the outer cube edge (2 faces), giving valency 5.
Why BRepOffset_MakeOffset fails
BRepOffset_MakeOffset with GeomAbs_Arc joint type computes a rolling-ball fillet along the edge graph. When a vertex has valency ≥ 4, the algorithm:
- Cannot determine the unique offset direction at that vertex
- Produces a shape with inverted normals — the volume becomes negative
- In other cases produces a null shape or crashes silently
The result is not a valid offset and cannot be used as a Minkowski sum.
The fix: micro-spheres
_place_micro_spheres() places a sphere of radius offset / 1000 (minimum 1e-7) at every valency-4+ vertex and fuses it into the base shape. This:
- Replaces the single degenerate vertex with a smooth spherical patch
- Distributes the high vertex valency across several normal (valency-3) vertices on the sphere surface
- Allows
BRepOffset_MakeOffsetto compute the rolling-ball fillet successfully
The spheres are small enough (< 0.1% of offset) that they do not measurably affect the final geometry.
Known limitations
For some complex geometries (e.g., a union of cylinders cut by a rotated plane), the micro-sphere fix can cause BRepOffset_MakeOffset to produce zero-volume degenerate shells instead of a valid solid. The fallback mechanism handles this by retrying the offset without micro-spheres, accepting the original vertex degeneracy.
Tuning
The sphere size ratio is hardcoded at 1000. If artifacts appear (holes near the vertices), the ratio can be adjusted. A future $minkowski_ratio variable may expose this to the user.
_ensure_solid() details
Defined in scl_context.py. Converts non-SOLID offset results:
| Input type | Handling |
|---|---|
TopAbs_SOLID |
Return as-is |
TopAbs_SHELL |
Sew + MakeSolid |
TopAbs_COMPOUND |
1. Extract SOLIDs, pick largest by volume 2. If none → extract SHELLs, MakeSolid each, fuse non-empty ones 3. If still no SOLID → return original |