Getting started

This page builds one real part from nothing — a rounded bracket with a bore, a boss and a chamfer — and saves it to a file you can slice. Every step is a working example; copy them in order and you will have an STL at the end.

If you have not installed pybosl2 yet:

pip install pybosl2          # the library
pip install pythonscad       # the geometry kernel it drives

Everything on this page except the final render works in plain CPython.

1. A solid

Shapes are functions; they return an object you keep working on. Only the size is required — everything else has a sensible default, so the shortest useful call is one argument.

from pybosl2 import cuboid

body = cuboid([60, 40, 12])
body.show()
Loading 3-D preview…

⬇ Download STL mesh

.show() hands the shape to the renderer and gives it back, so it closes a chain without swallowing the value.

2. Round its edges

Shaping options are keyword arguments on the constructor, and edges= says which edges to treat using the anchor language — the same vocabulary used everywhere a face, edge or corner is named.

from pybosl2 import Anchor, cuboid

body = cuboid([60, 40, 12], rounding=4, edges=Anchor.Z)
body.show()
Loading 3-D preview…

⬇ Download STL mesh

The same treatment is available on a shape you have already built, which is what you want when the rounding is not part of how the shape was made:

from pybosl2 import Anchor, cuboid

body = cuboid([60, 40, 12]).round_edges(Anchor.Z, radius=4)
body.show()
Loading 3-D preview…

⬇ Download STL mesh

chamfer_edges() and cove_edges() are its siblings. Each takes the treatment and works out the rest from the shape it is applied to — you never restate the part’s own dimensions.

3. Cut a hole

Booleans are operators: - difference, | union, & intersection. Each returns a new shape, so nothing you built is modified.

from pybosl2 import Anchor, cuboid, cyl

body = cuboid([60, 40, 12], rounding=4, edges=Anchor.Z)
bore = cyl(diameter=10, height=20)
bracket = body - bore
bracket.show()
Loading 3-D preview…

⬇ Download STL mesh

A hole is just a solid you subtract. cyl takes radius or diameter — both spellings are accepted and neither is required, but giving both is an error rather than a silent preference.

4. Put something somewhere

Directional moves read as English, and attach places a child by the anchor of its parent, so you rarely have to compute a position.

from pybosl2 import Anchor, cuboid, cyl

body = cuboid([60, 40, 12], rounding=4, edges=Anchor.Z)
boss = cyl(diameter=16, height=6)
bracket = body.attach(Anchor.TOP, boss) - cyl(diameter=10, height=40)
bracket.show()
Loading 3-D preview…

⬇ Download STL mesh

One thing to know about attach: it records the child rather than merging it immediately, so the pieces can be tagged and resolved together later. show() and export() resolve it for you. If you want the combined shape in hand — to measure it, as in the next step — ask for it with realize().

5. Measure it

bounds() answers a box, without rendering anything. It carries every spelling of itself, so you never do the arithmetic.

from pybosl2 import Anchor, cuboid, cyl

body = cuboid([60, 40, 12], rounding=4, edges=Anchor.Z)

print(body.bounds().size)        # (60.0, 40.0, 12.0)
print(body.bounds().max_z)       # 6.0 -- half the height, since it is centred
print(tuple(body.bounds().center))

# the boss is attached, so measure the resolved shape
bracket = body.attach(Anchor.TOP, cyl(diameter=16, height=6)).realize()
print(bracket.bounds().size)     # (60.0, 40.0, 18.0) -- 12 of body plus 6 of boss
print(bracket.bounds().max_z)    # 12.0 -- the top of the boss
bracket.show()
Loading 3-D preview…

⬇ Download STL mesh

6. Save it

from pybosl2 import Anchor, cuboid, cyl

body = cuboid([60, 40, 12], rounding=4, edges=Anchor.Z)
bracket = body.attach(Anchor.TOP, cyl(diameter=16, height=6)) - cyl(diameter=10, height=40)

bracket.export("bracket.stl")
bracket.show()
Loading 3-D preview…

⬇ Download STL mesh

The suffix picks the format — .stl, .obj, .off, .ply. Before writing, pybosl2 checks that the mesh is closed and wound the right way out, so a part that would fail in your slicer fails here instead, with a message saying what is wrong.

Where to go next

Curves and paths. A Path2D or Path3D is an ordered point list that owns its own measurement, sampling and cleanup — and it becomes geometry by extruding or sweeping:

from pybosl2 import Path2D

profile = Path2D([[0, 0], [30, 0], [30, 8], [8, 8], [8, 24], [0, 24]], closed=True)
rail = profile.round_corners(radius=2).linear_sweep(height=40, twist=45)
rail.show()
Loading 3-D preview…

⬇ Download STL mesh

A sweep returns a solid, so it composes with -/|/& like anything else; its mesh is on .vnf if you want to measure or export that directly.

Ready-made parts. The parts library is driven by trade-size names rather than measurements, and every part exposes its derived dimensions as properties, so you can measure one without building it:

from pybosl2.parts import Screw

screw = Screw("M6", length=20)
print(screw.pitch)          # 1.0 -- derived from the "M6" spec
screw.export("m6x20.stl")

Smoothness. Anything that draws a curve takes fn/fa/fs, and you can set them once for a block instead of threading them through every call:

from pybosl2 import cuboid, use_defaults

with use_defaults(fn=64):
    smooth = cuboid([20, 20, 20], rounding=4)

The other backend. The same code builds on an exact-CSG kernel or on signed-distance fields:

from pybosl2 import cuboid, use_backend

with use_backend("sdf"):
    field = cuboid([20, 20, 20], rounding=4)

Anything one backend cannot express raises and says so, rather than quietly building something else.

From here, the API reference below is organised by role: Foundational for the primitives and transforms most models start from, Paths, regions & surfaces for the modelling toolkit, Math & geometry for the numeric helpers, and Parts library for the mechanical catalogue.