BCad fork

≺code≻minkowski()≺/code≻ / ≺code≻offset()≺/code≻ Implementation
Login

≺code≻minkowski()≺/code≻ / ≺code≻offset()≺/code≻ Implementation

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); ... }
  1. Collect children. The base shape is the first child that is not a sphere. All sphere() radii are summed into offset.

  2. 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 via BRepAlgoAPI_Fuse.

  3. Offset. BRepOffset_MakeOffset with BRepOffset_Skin, GeomAbs_Arc join, RemoveIntEdges=False applies the rounding.

  4. Solid conversion. _ensure_solid() converts the offset result to a TopAbs_SOLID:

    • TopAbs_SHELLBRepBuilderAPI_Sewing + BRepBuilderAPI_MakeSolid
    • TopAbs_COMPOUND → extract SOLIDs (largest by volume), or convert SHELLs → MakeSolid → Fuse
  5. Fallback. If the micro-sphere fix produces a non-SOLID result (e.g., a COMPOUND with zero-volume degenerate shells), _offset_3d retries the offset on the original base shape (without micro-spheres). This trades perfect degneracy handling for a valid solid.

  6. Face color propagation. _map_offset_colors() maps source face colors through the offset result using maker.Generated(face) (requires RemoveIntEdges=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:

At such a vertex, the following edges coincide:

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:

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:

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