API for LLMs

The whole API, for a prompt

Everything a model needs to write sand3d: the rules of the vocabulary, every signature with one line on it, and worked examples. 3 643 tokens (o200k_base). Paste it in a system prompt as it is.

api.md, rawllms.txt

import sand3d as s3d; mm, or units("m") first. Makers take a name, return the solid; edits return it. Edges and faces go by their maker's names (top bottom side rim seam start end cut, tool names, plate.top).

  1. A placement is a frame plus values. at sets the values it is given and keeps the others; on= changes the frame, and x, y, z then run along its axes; transform moves the whole thing, as a parent would, mirror included.
  2. To repeat, pass a list of frames to on=: once per frame, in at, hole and pocket.
  3. A list of objects is worth its elements, wherever a verb takes objects.
  4. What a verb uses up no longer exists. copy keeps it; always use what at returns.
  5. Every solid starts at z = 0, centred in x and y: box is the extrude of its rect; extrude(both=True) centres one on its plane. A face's frame starts where the origin projects onto it: a vertical face's, at its foot. A frame's Z is the face's normal; on a face whose normal is ±Z its X is +X, on any other X is Z × normal: on a face looking +X, X is +Y and Y is up. A side, an edge or a corner of a box is named by directions: bbox(ob, "+X") is the centre of that side of its bounding box, "+X-Y" the middle of an edge, "+X-Y+Z" a corner, with the axes of a face looking the first way named, on a part or a group; at's anchor takes the same ("-Z", "+Y-Z"; its words top bottom left right front back are +Z -Z -X +X -Y +Y).
  6. A joint lets a part move on another. Place the part first: joint does not move it, it says how it moves — on the part its frame on was read on (ob.face, bbox, local, an assembly's anchor; it stays read on it through .move, .turn, .offset), about or along axis: a direction ("+Z", "-X"…) in the space the parent lives in, as in face, or "normal", on's Z. A pivot given as numbers — a source's joint origin, xyz and rpy — is local(parent, x, y, z, rx, ry, rz), in the parent's own space: at(child, on=…) places the child there, joint hangs it there. Positive values slide along the axis, or turn by the right hand about it: about +Z, +X turns toward +Y — a door hinged on the -Y edge of a +X face opens outward about "-Z". The range contains 0; a joint that follows another takes its range from it. Everything is built at rest, every joint at 0; pose(child, value) moves the mechanism. A part with no joint moves with its group; a jointed part moves by its joint, whatever group it is in; on is never read on the child itself. Joints may come before or after group/assembly. copy copies a jointed part's joints (it hangs on the same parent); transform moves the part with its pivot (a mirror image moves as the image of the motion); at refuses it. Two joints on one child compose in order (a screw: a turn, and a slide that follows it with ratio=pitch / 360).

Signatures

Profiles

rect(w, d, r=0, chamfer=0)  # Centred; corners rounded or cut
circle(d)  # Centred
ellipse(w, d)  # Centred
slot(l, d)  # Round-ended slot along X
polygon(n, d)  # Regular polygon, corner on +X
poly(pts, fillet=0, chamfer=0)  # Outline through points, arcs, splines
arc(center=(0, 0), r=1, start=0, end=360, *, through=None, to=None)  # Arc from start to end, anticlockwise if end > start: a path, or a poly run
tangent_arc(to)  # Poly run: arc leaving tangent
spline(pts, closed=False, tangents=None, loop=False)  # Smooth curve; closed: a profile; loop: a ring path
superellipse(w, d, n=2, taper=0)  # Ellipse (n=2) to box (n=4); taper narrows its +Y side, an egg
station(z, w, d, x=0, y=0, n=2, taper=0)  # Loft section at height z; n and taper as superellipse
text(s, size, font=None)  # Centred outline
offset(p, t)  # Grown by t
stroke(path, t)  # A band t wide along an open path, centred on it: a closed outline
wrap(*profiles)  # Convex outline round profiles
mirror(p, flip='x')  # Plus its image, flip negated

16 lines · 348 tokens (o200k_base)

Solids, paths

extrude(name, profile, h=None, taper=0, twist=0, both=False, h2=None, on=None, until=None)  # Prism up Z (taper > 0 narrows it upward), or until a plane or solid; on a face, by on
revolve(name, profile, angle=360)  # X radius, Y height, turned about Z
sweep(name, profile, path, scale=1, twist=0, frenet=False)  # Profile along a path
loft(name, *sections, follow=False, loop=False)  # Through placed sections, capped; loop: a ring
body(name, plan, top, bottom, n=2)  # Solid along Y from its views, where they draw it: plan (y, width), side top and bottom (y, z) or numbers; superellipse sections
wire(name, path, d, closed=None, fillet=0)  # Round wire along a path: (x, y, z) points, a spline, an arc, a helix; fillet rounds the corners of points, d / 2 at least
helix(d, pitch, h, left=False)  # Coil path up Z
box(name, w, d, h)  # On Z = 0, centred in X and Y
cylinder(name, d, h)  # Along Z, from Z = 0
tube(name, d, d_in, h)  # Along Z, from Z = 0
cone(name, d, h, d2=0)  # Along Z, from Z = 0
sphere(name, d)  # On Z = 0
wedge(name, w, d, h, top=0)  # Box narrowed on top
torus(name, d, d_wire)  # Ring about Z, on Z = 0
dome(name, d, h)  # Cap on Z = 0

15 lines · 416 tokens (o200k_base)

Edits

cut(ob, *tools)  # Subtract tools
join(ob, *others)  # Fuse others in; ob's material wins: group keeps each its own
intersect(ob, tool)  # Keep the common part
split(ob, frame, keep='above')  # By a frame's plane
delete(ob)  # Remove it
copy(ob)  # An independent copy, its joints with it
hole(ob, d, on=(0, 0), depth=None, cbore=None, csk=None)  # On frames or points: through, or depth deep
pocket(ob, profile, depth=None, on=None)  # Profile cut in from a face
merge(*obs)  # No boolean
scale(ob, s)  # About its origin
fillet(ob, r, edges='all')  # Round named edges
chamfer(ob, d, edges='all', d2=None, angle=None)  # Bevel named edges
soften(ob, r, angle=30)  # Round all sharp edges
shell(ob, t, open=None)  # Hollow; open named faces, or the flat faces looking "+Y", "-X +X"
dent(ob, points, r, depth)  # Press hollows at points

15 lines · 267 tokens (o200k_base)

Frames, placement

Frame(origin=(0, 0, 0), normal=(0, 0, 1), up=None)  # A place to build on
.offset(d) .turn(rx=0, ry=0, rz=0) .move(x=0, y=0, z=0) .point(x=0, y=0, z=0)
ob.face(which)  # Frame of a named face, or furthest along "+Z"
ob.edge(which)  # Path along the edges named which, in the space ob is in: the longest run (as long: the one reaching furthest -X, then -Y, -Z), closed if it closes
bbox(ob, which)  # Frame on its bounding box: a side "+X", an edge "+X-Y", a corner "+X-Y+Z"
local(ob, x=0, y=0, z=0, rx=0, ry=0, rz=0)  # Frame at (x, y, z) of its own space, turned (rx, ry, rz) degrees
at(ob, x=None, y=None, z=None, rx=None, ry=None, rz=None, on=None, anchor=None)  # Place it: set what is given, keep the rest; anchor: "-Z", "+Y-Z"…
transform(ob, x=0, y=0, z=0, rx=0, ry=0, rz=0, mirror=None)  # Move it by, as a parent would; its placement, not its mesh
grid(nx, ny=1, px=0, py=0, stagger=False, on=None)  # On a grid
ring(n, r, start=0, angle=360, on=None)  # On a circle
along(path, pitch=None, count=None)  # Along a path
snap(targets, points, direction=None)  # On a surface

12 lines · 405 tokens (o200k_base)

Parts

gear(name, teeth, module, width, bore=0, angle=20)  # Involute spur gear
knurl(name, d, h, pitch=None, straight=False)  # Knurled grip; pitch 1 mm by default
thread(name, d, length, pitch, left=False)  # Threaded rod

3 lines · 70 tokens (o200k_base)

Materials

material(ob, kind, color=None, roughness=None, metallic=None, opacity=None, emission=None)  # Kind: oak walnut pine birch steel brushed_steel chrome aluminium brass copper painted_metal plastic glossy_plastic rubber glass ceramic fabric velvet leather marble concrete light; color "#rrggbb"

1 lines · 61 tokens (o200k_base)

Articulation

joint(name, child, on, kind='revolute', axis='normal', range=None, follows=None, ratio=1)  # How child moves on the part on was read on: revolute (range in degrees), continuous (no range), prismatic (range in the unit), free (no axis, no range); follows: a child or a joint name, and this joint's value is ratio × its (degrees per unit, or units per degree), no range; returns child
pose(ob, value, joint=None)  # Move the mechanism: that joint to value (degrees or the unit), an error outside its range; what it carries follows; joint= when child has two
seat(host, part)  # Take out of host the volume part fills, as it sits at rest; part untouched

3 lines · 168 tokens (o200k_base)

Assembly

group(name, *children)  # Parent of children
assembly(name, *children, **anchors)  # Parts and anchors
attach(handle, name, *children)  # A detail, under a handle
Handle(root, **anchors)  # Root and anchors
export(root, path)  # root and what hangs on it by its joints, in metres: .glb at the current pose, joints in extras; .urdf a link per moving part, at rest, meshes beside it; .usda the URDF's tree as OpenUSD text, with the materials; .obj and .stl its meshes at the current pose
empty_scene()  # Fresh scene
units(name=None)  # Length unit: "mm" (default), "cm", "m"
fingerprint()  # Digest of the source

8 lines · 172 tokens (o200k_base)

Examples

def bracket(w=80, d=40, t=6, hole_d=6.6, pitch=50):
    plate = s3d.extrude("plate", s3d.rect(w, d, r=4), t)
    s3d.hole(plate, hole_d, on=s3d.grid(2, 1, pitch, 0))
    s3d.fillet(plate, 1, edges="top")
    return plate

def lamp(stem_h=300):
    base = s3d.revolve("base", [(0, 0), (70, 0), (60, 20), (0, 20)])
    stem = s3d.at(s3d.cylinder("stem", 12, stem_h), on=base.face("top"))
    lamp = s3d.assembly("lamp", base, stem, head=s3d.Frame((0, 0, 20 + stem_h)))
    add_shade(lamp)
    return lamp

def add_shade(lamp, d=180):
    shade = s3d.shell(s3d.cone("shade", d, 120, d2=d / 2), 1.5, open="top bottom")
    s3d.attach(lamp, "shade", s3d.at(shade, on=lamp.head))

def rack(n=4, pitch=30, depth=40, t=5):
    base = s3d.extrude("base", s3d.rect(n * pitch, depth, r=3), t)
    pegs = s3d.at(s3d.cylinder("peg", 6, 20), on=s3d.grid(n, 1, pitch), z=t)         # a copy per frame
    hook = s3d.at(s3d.extrude("hook", s3d.rect(4, 12), 12), x=n * pitch / 2 - 4, z=t)
    image = s3d.transform(s3d.copy(hook), mirror="x")                                # its image across x = 0
    s3d.join(base, pegs, hook, image)                                                # a list is its elements
    s3d.hole(base, 4, on=s3d.grid(2, 1, (n - 1) * pitch).move(y=depth / 2 - 6))
    s3d.chamfer(base, 0.6, edges="peg")
    return base

def gate(w=750, h=1200, post=100):
    l_post = s3d.at(s3d.box("post", post, post, h + 300), x=-w - post / 2)
    r_post = s3d.transform(s3d.copy(l_post), mirror="x")
    board = s3d.at(s3d.box("board", w, 40, h), x=-w / 2, z=150)                    # from the post to x = 0
    handle = s3d.at(s3d.cylinder("handle", 20, 60), on=s3d.bbox(board, "+Y").move(x=60 - w / 2))  # near x = 0
    leaf = s3d.group("leaf", board, handle)                                        # the handle moves with it
    s3d.joint("hinge", leaf, s3d.bbox(l_post, "+X+Y"), axis="+Z", range=(0, 90))   # the post's edge it opens toward: +X turns toward +Y
    twin = s3d.transform(s3d.copy(leaf), mirror="x")                               # its image, hinged at x = +w: opens toward +Y too
    gate = s3d.assembly("gate", l_post, r_post, leaf, twin)
    s3d.pose(leaf, 60)
    return gate

38 lines · 837 tokens (o200k_base)

sand3d 1.6.2 · Apache-2.0 · source Sandflow