Solid backends: CSG and SDF¶
bosl2 can realize a solid two different ways, and the choice is a switchable backend:
CSG (the default) — exact constructive solid geometry via PythonSCAD’s native primitives. Booleans are exact, the attachment/anchoring system is available, and shapes carry their BOSL2 metadata. This is today’s
Bosl2Solid.SDF — an F-Rep / signed-distance-field engine built on libfive (the merged
pysolidfivecode). Shapes are implicit surfaces, so they round and blend smoothly and mesh at any resolution, at the cost of the CSG-only attachment features. Its solids arePyShapeobjects.
Both backends expose the same shared constructors, so the same code builds either one.
The bosl2.solid facade¶
bosl2.solid is the backend-neutral entry point. Each constructor dispatches to whichever
backend is active and returns a common Solid — a Bosl2Solid on CSG,
a PyShape on SDF:
from bosl2.solid import sphere, use_backend
a = sphere(radius=10) # CSG (default) -> Bosl2Solid
with use_backend("sdf"):
b = sphere(radius=10) # libfive SDF -> PyShape
The shared 3-D surface both backends provide:
|
|
|
plus the n-ary booleans union, difference and intersection. The backend-specific
modules (bosl2.shapes3d, bosl2._sdf) stay directly importable for anything not yet
unified in the facade.
Selecting a backend¶
Call |
Effect |
|---|---|
|
The backend active in this context ( |
|
A context manager making its argument the active backend for the |
|
Change the process-wide default (outside any |
All three are re-exported from bosl2.solid for convenience.
Combining and converting between backends¶
A boolean or transform requires its operands to share the active backend. Combining solids from
two different backends raises CrossBackendError rather than producing
nonsense:
from bosl2.solid import cube, sphere, use_backend
c = cube(10) # csg
with use_backend("sdf"):
s = sphere(radius=6) # sdf
c | s # raises CrossBackendError
Convert first with the bridge methods:
Method |
Result |
|---|---|
|
Mesh the SDF field and wrap it as a |
|
Identity (already on that backend). |
|
Raises |
So the CSG → SDF direction is deliberately not automatic: build with the SDF backend from the start when you want an implicit surface.
Capability map: what each backend can do¶
Most of the surface is shared, but a few features belong to one backend only. Calling one on the
wrong backend raises UnsupportedByBackend (with a hint) instead of a
confusing AttributeError — and, on the SDF side, instead of meshing via libfive just to fail:
Backend |
Exclusive features |
Why |
|---|---|---|
CSG only |
|
BOSL2’s attachment / anchoring system has no signed-distance equivalent. |
CSG only |
|
Only the CSG backend has a 2-D shape object; an SDF is a field over 3-space, with no 2-D shadow to project and no outline to fill. See below. |
SDF only |
|
Implicit-surface edge treatments. On CSG use the |
Query support directly with supports(backend, feature):
from bosl2._backend import supports
supports("csg", "attach") # True
supports("sdf", "attach") # False
supports("sdf", "round") # True
supports("csg", "sphere") # True -- shared surface
2-D on the two backends¶
2-D geometry is a CSG-only notion: Bosl2Shape2D and every
bosl2.shapes2d constructor build exact 2-D shapes, and stay on the CSG backend even inside a
use_backend("sdf") block (they do not silently change meaning). A path, on the other hand,
is just points and so is backend-neutral — and the operations that take a path to a 3-D solid
dispatch on the active backend:
Call |
CSG |
SDF |
|---|---|---|
|
native |
|
|
exact native |
polyhedral hull of the children’s support points |
|
a tube of Bosl2Solid cylinders/spheres |
the same tube as one distance field |
|
|
So the same source builds on either backend as long as it goes path → solid:
outline = Path([[0, 0], [40, 0], [40, 25], [0, 25]]).round_corners(radius=5)
plate = outline.linear_extrude(height=4) # -> Bosl2Solid
with use_backend("sdf"):
field = outline.linear_extrude(height=4) # -> PyShape
Sweeping a profile along a path (SDF)¶
The SDF backend can sweep a 2-D profile (convex or concave) along a 3-D path directly as a
distance field (bosl2._sdf.shapes3d.path_sweep / bezier_sweep). The profile is placed in a
rotation-minimizing frame at each path sample and unioned; the cross-section itself is evaluated with
the convex-deficiency decomposition (the same one polygon_prism uses over the convex-only
polygon_extrude), so concave outlines are handled correctly. The result is a true SDF — it can be
.round()/.chamfer()``ed, meshed at any resolution, or bridged to CSG with ``.to_csg(). Bezier
generation stays bosl2’s canonical Bezier; the sweep just consumes the
sampled curve:
import math, numpy as np
from bosl2._sdf.shapes3d import bezier_sweep
circle = [[2 * math.cos(t), 2 * math.sin(t)] for t in np.linspace(0, 2 * math.pi, 24, endpoint=False)]
tube = bezier_sweep(circle, [[0, 0, 0], [0, 0, 20], [25, 12, 15], [30, 4, 6]])
This is distinct from the CSG sweeps (bosl2.beziers.Bezier.sweep(), skin, offset_sweep),
which build a VNF/polyhedron mesh rather than a distance field. Denser paths give a smoother lateral
surface; the ends cap perpendicular to the path.