Solid backends: CSG and SDF¶
pybosl2 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
pybosl2.shapes3d.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 pybosl2.solid facade¶
pybosl2.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 pybosl2.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 (pybosl2.shapes3d, pybosl2._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 pybosl2.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 exceptions rather than producing
nonsense:
from pybosl2.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 UnsupportedByBackendError (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 pybosl2._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: shapes2d and every
pybosl2.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 |
|---|---|---|
|
|
|
|
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 (pybosl2._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 pybosl2’s canonical beziers; the sweep just consumes the
sampled curve:
import math, numpy as np
from pybosl2._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 (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.
Rendering the docs examples with PythonSCAD¶
Every docstring example in this reference is built and mesh-exported by the real PythonSCAD
binary (the pythonscad-example Sphinx directive in docs/_ext/pybosl2_example.py). This
means the PythonSCAD process must be able to import numpy and shapely — pybosl2’s core
dependencies. Without them every rendered example silently falls back to a source-only listing.
The PythonSCAD AppImage bundles its own isolated Python interpreter; pip install on your host
machine does not put packages where that interpreter can find them. You need to install
numpy and shapely into the AppImage’s own Python.
macOS (PythonSCAD-dev.app or PythonSCAD.app):
# The bundled Python lives inside the .app bundle:
/Applications/PythonSCAD-dev.app/Contents/Frameworks/Python.framework/Versions/*/bin/python3 \
-m pip install --user numpy shapely
If the python3 binary inside the framework has a broken library path, install to the
per-user site‑packages that the AppImage Python already has on sys.path:
python3 -m pip install --target ~/Library/Python/3.*/lib/python/site-packages numpy shapely
Linux (AppImage extracted):
# Extract the AppImage first, then find its Python:
PYBASE=$(find /opt/pythonscad -path '*/bin/python3*' -not -path '*/__pycache__/*' | head -1)
$PYBASE -m pip install --user numpy shapely
Check: Run a minimal script through the binary to confirm both libraries import:
BIN=/path/to/PythonSCAD
cat > /tmp/check.py << 'PYEOF'
import sys, site, os
usp = site.getusersitepackages()
if os.path.isdir(usp) and usp not in sys.path:
sys.path.insert(0, usp)
import numpy as np
import shapely
print(f"numpy {np.__version__} shapely {shapely.__version__}", file=sys.stderr, flush=True)
from pythonscad import cube
cube([1,1,1]).show()
PYEOF
"$BIN" --trust-python --enable python-engine -o /tmp/test.stl --backend Manifold /tmp/check.py
ls -la /tmp/test.stl # should be a non-zero STL file
See also
- PythonSCAD documentation — getting started
Official install and usage docs.
- PythonSCAD — Python libraries
How to install third‑party packages into PythonSCAD’s bundled Python.