ShapeScript Guide

From a plain-language idea to a validated, parametric, printable model

Back to Editor

1. Quick Start

Scripts are normal JavaScript. Return one model. Units are millimeters. Rotations use degrees.

Tip

The build plate is Z=0. Solids default to bottom-center, so they grow upward into positive Z.

Your first model

return model;

Return a shape. That is the whole contract.

Code
return roundedBox({
  size: [80, 40, 12],
  radius: 4,
  color: "#3b82f6",
});
Preview

Scroll to load preview…

Parametric from day one

slider({ name, default, min, max, step, unit })

Sliders appear in the editor UI and re-run the script instantly.

Code
const width = slider({
  name: "Width",
  default: 100,
  min: 40,
  max: 200,
  step: 1,
  unit: "mm",
});

return roundedBox({
  size: [width, 60, 6],
  radius: 4,
});
Preview

Scroll to load preview…

2. Primitives

Build solids from primitives. Most accept anchor, name, color, segments, and quality.

Tip

Call setQuality("high") once when curved surfaces look faceted. Prefer quality presets over huge segment counts.

Boxes

box({ size, anchor })cube({ size, anchor })

Cuboids and cubes. By default their bottom face rests on Z=0.

Code
return group([
  box({ size: [80, 40, 12], color: "#3b82f6" }),
  cube({ size: 24, color: "#f59e0b" }).move([0, 0, 18]),
]);
Preview

Scroll to load preview…

Curved solids

sphere({ radius })cylinder({ radius, height })cone({ topRadius, bottomRadius, height })torus({ majorRadius, tubeRadius })

Spheres, cylinders, cones, and tori.

Code
setQuality("high");

return union(
  torus({ majorRadius: 18, tubeRadius: 4 }),
  cylinder({ radius: 4, height: 16 })
);
Preview

Scroll to load preview…

Custom polyhedron

polyhedron({ points, faces })

Faces are arrays of point indexes.

Code
return polyhedron({
  points: [
    [0, 0, 0], [30, 0, 0], [0, 30, 0], [0, 0, 25],
  ],
  faces: [
    [0, 2, 1], [0, 1, 3], [1, 2, 3], [2, 0, 3],
  ],
  anchor: "center",
  color: "#8b5cf6",
});
Preview

Scroll to load preview…

3. Anchors and Placement

Anchors pick which point of a shape sits at the origin. 3D solids default to bottom-center, keeping them above the build plate.

Tip

Omit anchor for normal bottom-up modeling. Use anchor: "center" for symmetric parts and cutters, or top-center and corner anchors for placement.

Anchor and place

shape.anchor(name)shape.center()shape.place({ at, anchor })

Re-anchor or drop a part onto a known point.

Code
const post = cylinder({
  radius: 5,
  height: 40,
  color: "#64748b",
});

const cap = sphere({ radius: 8, color: "#f59e0b" })
  .place({ at: [0, 0, 40], anchor: "center" });

return union(post, cap);
Preview

Scroll to load preview…

4. Transforms

Shapes are immutable. Every transform returns a new shape you can chain.

Tip

Reuse one part many times: const arm = box(...); then arm.rotate(...), arm.mirror("x").

Move, rotate, scale, mirror

shape.move([x, y, z])shape.rotate([xDeg, yDeg, zDeg])shape.scale(n | [x, y, z])shape.mirror("x" | "y" | "z")

Rotations are degrees. Mirror is the fastest path to symmetry.

Code
const arm = box({
  size: [60, 8, 8],
  anchor: "center",
  color: "#38bdf8",
});

return group([
  arm,
  arm.rotate([0, 0, 90]).color("#f59e0b"),
  arm.scale([1, 1, 1.5]).move([0, 0, 12]).color("#22c55e"),
  arm.mirror("x").move([0, 20, 0]).color("#a78bfa"),
]);
Preview

Scroll to load preview…

5. Booleans

Booleans merge geometry into one physical solid. Use them for holes, cavities, and printable bodies.

Tip

Cutters should stick out past the part on both sides so the hole is truly through.

Union, subtract, intersect

union(a, b, ...)subtract(base, cutter, ...)intersect(a, b, ...)

subtract() is how you cut holes. Array input works too.

Code
const plate = roundedBox({
  size: [90, 50, 6],
  radius: 4,
  color: "#3b82f6",
});

const holes = gridPattern({
  item: throughHole({ radius: 3, height: 14 }),
  rows: 2,
  columns: 3,
  spacing: [24, 24],
});

return subtract(plate, solidify(holes));
Preview

Scroll to load preview…

Chainable booleans

base.subtract(cutter)base.union(detail)base.intersect(mask)

Same ops as methods when you prefer a pipeline style.

Code
return box({ size: [40, 40, 10], color: "#64748b" })
  .subtract(cylinder({ radius: 8, height: 20 }))
  .union(
    text3d({ text: "TOP", size: 6, height: 1, color: "#f8fafc" })
      .move([0, 0, 6])
  );
Preview

Scroll to load preview…

6. Groups vs Union

group() is visual assembly. union() is one merged solid. Prefer groups until you need a single body.

Tip

Patterns return groups. Call solidify(pattern) or pattern.asUnion() before using them as cutters.

Group for assemblies

group({ name, children })group([a, b])

Keeps child colors/names, transforms together, and stays fast.

Code
const leg = cylinder({
  radius: 3,
  height: 35,
  anchor: "bottom-center",
  color: "#8b5a2b",
});

return group({
  name: "Simple Table",
  children: [
    box({ size: [70, 50, 5], color: "#c08457" }).move([0, 0, 35]),
    leg.move([-28, -18, 0]),
    leg.move([28, -18, 0]),
    leg.move([-28, 18, 0]),
    leg.move([28, 18, 0]),
  ],
});
Preview

Scroll to load preview…

Solidify when you need one body

solidify(group)group(...).asUnion()

Required before subtracting a pattern, or when export must be one solid.

Code
const pins = linearPattern({
  item: cylinder({ radius: 2, height: 10, color: "#38bdf8" }),
  count: 5,
  spacing: [12, 0, 0],
});

return solidify(pins);
Preview

Scroll to load preview…

7. Patterns

Repeat parts in lines, grids, or rings. Patterns return groups by default.

Tip

Build the item once, pattern it, then solidify only when cutting or exporting a merged body.

Linear, grid, radial

linearPattern({ item, count, spacing })gridPattern({ item, rows, columns, spacing })radialPattern({ item, count, radius, rotateItems })

spacing is [x, y] or [x, y, z] depending on the helper.

Code
const vent = slot({ width: 4, length: 24, height: 8 });

const vents = linearPattern({
  item: vent,
  count: 6,
  spacing: [0, 10, 0],
});

return subtract(
  roundedBox({ size: [70, 80, 4], radius: 4, color: "#0ea5e9" }),
  solidify(vents)
);
Preview

Scroll to load preview…

Radial layout

radialPattern({ item, count, radius, rotateItems })

rotateItems aims each copy outward — great for spokes and bolt circles.

Code
const spoke = box({
  size: [28, 4, 4],
  anchor: "center",
  color: "#64748b",
});

const hub = cylinder({
  radius: 8,
  height: 6,
  color: "#334155",
});

return union(
  hub,
  solidify(radialPattern({
    item: spoke,
    count: 6,
    radius: 14,
    rotateItems: true,
  }))
);
Preview

Scroll to load preview…

8. Rounded, Chamfered, and 2D

Use dedicated helpers for rounded/chamfered boxes. Sketch in 2D, then extrude.

Tip

There is no generic mesh fillet. Prefer roundedBox / chamferedBox / roundedRect + extrude.

Rounded and chamfered

roundedBox({ size, radius })chamferedBox({ size, chamfer })taperedBox({ height, topSize, bottomSize })

Print-friendly edges without fake fillets.

Code
return group([
  roundedBox({ size: [50, 30, 10], radius: 4, color: "#3b82f6" }).move([-35, 0, 0]),
  chamferedBox({ size: [50, 30, 10], chamfer: 3, color: "#f59e0b" }).move([35, 0, 0]),
  taperedBox({
    height: 24,
    topSize: [20, 20],
    bottomSize: [36, 36],
    color: "#22c55e",
  }).move([0, 50, 0]),
]);
Preview

Scroll to load preview…

2D sketch → extrude

rect({ size })roundedRect({ size, radius })circle({ radius })polygon({ points })extrude(profile, { height })

Any 2D profile becomes a solid with extrude().

Code
const profile = roundedRect({
  size: [50, 20],
  radius: 5,
});

return extrude(profile, {
  height: 8,
  color: "#14b8a6",
});
Preview

Scroll to load preview…

Custom outline

polygon({ points })extrude(profile, { height })

Point lists are [x, y] in the XY plane.

Code
const wing = polygon({
  points: [
    [0, 0], [40, 8], [40, 16], [0, 28], [-8, 14],
  ],
});

return extrude(wing, {
  height: 4,
  anchor: "center",
  color: "#a78bfa",
});
Preview

Scroll to load preview…

9. Mechanical Helpers

Ready-made cutters and printable features for brackets, enclosures, and hardware.

Tip

Add clearance (about 0.2–0.4 mm) for screws, nuts, and magnets so parts fit after printing.

Holes and traps

throughHole({ radius, height, clearance })slot({ width, length, height })screwHole({ ... })counterboreHole({ ... })countersinkHole({ ... })hexNutTrap({ flatWidth, height, clearance })magnetPocket({ radius, depth, clearance })

Always subtract cutters from a solid base.

Code
const block = roundedBox({
  size: [60, 35, 16],
  radius: 3,
  color: "#64748b",
});

return subtract(
  block,
  hexNutTrap({ flatWidth: 5.5, height: 8, clearance: 0.25 }),
  throughHole({ radius: 1.8, height: 40 }).rotate([90, 0, 0])
);
Preview

Scroll to load preview…

Standoffs, ribs, clips, hinges

standoff({ outerRadius, innerRadius, height })boss({ radius, height })rib({ length, height, thickness })snapClip({ width, length, thickness, hookHeight })hinge({ length, radius, pinRadius, knuckleCount })

Building blocks for mounts and moving parts.

Code
const mount = roundedBox({
  size: [80, 45, 5],
  radius: 4,
  color: "#475569",
});

const posts = gridPattern({
  item: standoff({
    outerRadius: 5,
    innerRadius: 2,
    height: 14,
    anchor: "bottom-center",
    color: "#334155",
  }),
  rows: 2,
  columns: 2,
  spacing: [55, 25],
}).move([0, 0, 2.5]);

return union(mount, solidify(posts));
Preview

Scroll to load preview…

10. Containers and Trays

Hollow printable boxes and compartment trays — real walls, not visual shells.

Tip

Keep wallThickness ≥ 2 mm for FDM. Floor can be a bit thinner than walls.

Open box, tray, lid, grid storage

openBox({ size, wallThickness, floorThickness, radius })tray({ size, compartments })lidBox({ size, lidClearance })gridStorageTray({ size, rows, columns })

Parametric storage in a few lines.

Code
return gridStorageTray({
  size: [140, 80, 28],
  rows: 2,
  columns: 4,
  wallThickness: 2.5,
  floorThickness: 2,
  radius: 4,
  anchor: "bottom-center",
  color: "#0ea5e9",
});
Preview

Scroll to load preview…

11. Text and Labels

text3d() makes raised or recessed labels that survive STL export.

Tip

Raise text with union(), or sink it with subtract(). Keep stroke height ≥ 0.8 mm for FDM.

Raised label

text3d({ text, size, height, font })

Emboss on top of a plate.

Code
const base = roundedBox({
  size: [60, 30, 4],
  radius: 3,
  color: "#334155",
});

const label = text3d({
  text: "BH35",
  size: 8,
  height: 1,
  color: "#f8fafc",
}).move([0, 0, 2.5]);

return union(base, label);
Preview

Scroll to load preview…

Debossed label

subtract(base, text3d(...))

Cut text into the surface.

Code
const tag = roundedBox({
  size: [70, 28, 5],
  radius: 4,
  color: "#1e293b",
});

const stamp = text3d({
  text: "TOOL",
  size: 9,
  height: 2,
}).move([0, 0, 2]);

return subtract(tag, stamp);
Preview

Scroll to load preview…

12. Parametric Controls

Declare UI controls in code. Values are plain JS — use them like any variable.

Tip

Group related controls with category. Keep step coarse enough that dragging feels snappy.

Slider, checkbox, select, color

slider({ name, default, min, max, step, unit, category })checkbox({ name, default, category })select({ name, default, options })colorPicker({ name, default })

Controls drive dimensions, features, and colors.

Code
const wall = slider({
  name: "Wall Thickness",
  default: 3,
  min: 1.5,
  max: 6,
  step: 0.5,
  unit: "mm",
  category: "Dimensions",
});

const addHoles = checkbox({
  name: "Add Mounting Holes",
  default: true,
  category: "Features",
});

const color = colorPicker({
  name: "Body Color",
  default: "#3b82f6",
});

const body = openBox({
  size: [80, 60, 30],
  wallThickness: wall,
  floorThickness: 3,
  radius: 4,
  color,
});

if (!addHoles) return body;

const holes = gridPattern({
  item: throughHole({ radius: 2.2, height: 20 }),
  rows: 1,
  columns: 2,
  spacing: [50, 0],
}).move([0, 0, -10]);

return subtract(body, solidify(holes));
Preview

Scroll to load preview…

13. Colors and Materials

Colors show in the preview. STL export is geometry-only — no color or hierarchy.

Tip

Use group() for multi-color previews. union() merges materials into one body.

Color and material

shape.color("#3b82f6")shape.color([255, 80, 40])shape.material({ color, name })

Hex, 0–255 RGB, or 0–1 floats all work.

Code
const body = box({ size: [50, 30, 12] }).material({
  name: "Blue PLA",
  color: "#3b82f6",
});

const cap = cylinder({ radius: 8, height: 14 })
  .color([255, 180, 40]);

return group([body, cap.move([0, 0, 13])]);
Preview

Scroll to load preview…

14. Compose Complex Shapes

Real models are functions, reuse, and a clear build order: parts → assemble → cut → label.

Tip

Write small helpers (createLeg, createScrewBoss). Compose with group/union. Cut last.

Reusable components

function createPart(options) { ... }

Plain JS functions are your component system.

Code
function createLeg({ height = 70, top = 8, bottom = 5 } = {}) {
  return taperedBox({
    height,
    topSize: [top, top],
    bottomSize: [bottom, bottom],
    anchor: "center",
    color: "#8b5a2b",
  });
}

const leg = createLeg();

return group({
  name: "Table",
  children: [
    roundedBox({
      size: [110, 70, 6],
      radius: 3,
      color: "#c08457",
    }).move([0, 0, 70]),
    leg.move([-45, -25, 35]),
    leg.move([45, -25, 35]),
    leg.move([-45, 25, 35]),
    leg.move([45, 25, 35]),
  ],
});
Preview

Scroll to load preview…

Bracket recipe

union(...)subtract(...)rib(...)counterboreHole(...)

Plate + shelf + ribs + hardware cutters + label.

Code
const back = roundedBox({
  size: [80, 8, 90],
  radius: 4,
  color: "#64748b",
});

const shelf = roundedBox({
  size: [80, 55, 8],
  radius: 3,
  color: "#475569",
}).move([0, -27, -20]);

const brace = rib({
  length: 45,
  height: 45,
  thickness: 6,
  color: "#334155",
}).move([-28, -8, -40]);

const screw = counterboreHole({
  shaftRadius: 2.2,
  shaftHeight: 16,
  headRadius: 5,
  headDepth: 4,
}).rotate([90, 0, 0]);

const screws = linearPattern({
  item: screw,
  count: 2,
  spacing: [0, 0, 42],
});

const label = text3d({
  text: "MAX 2KG",
  size: 7,
  height: 1,
  color: "#f8fafc",
}).rotate([90, 0, 0]).move([0, -4.5, 25]);

return union(
  subtract(
    union(back, shelf, brace, brace.mirror("x")),
    solidify(screws)
  ),
  label
).anchor("bottom-center");
Preview

Scroll to load preview…

15. Inspect and Validate

Measure parts in code. Debug overlays help in preview and are skipped on STL export.

Tip

printability() is conservative — treat warnings as hints, not hard fails.

Measurements

shape.bounds()shape.size()shape.width()shape.depth()shape.height()shape.centerPoint()

Drive labels or dependent geometry from real sizes.

Code
const part = roundedBox({
  size: [70, 30, 12],
  radius: 3,
  color: "#3b82f6",
});

return union(
  part,
  text3d({
    text: String(part.height()),
    size: 6,
    height: 1,
    color: "#f8fafc",
  }).move([0, 0, 7])
);
Preview

Scroll to load preview…

Debug overlays

debugBounds(shape)debugAxes({ size })debugGrid({ size, step })validate(model)printability(model)

Preview-only helpers for orientation and fit checks.

Code
const model = openBox({
  size: [80, 50, 30],
  wallThickness: 3,
  floorThickness: 3,
  radius: 4,
  color: "#0ea5e9",
});

return group([model, debugBounds(model)]);
Preview

Scroll to load preview…

16. Import and Export

Reuse saved projects as shapes. Export triangle meshes for printing.

Tip

STL has no color or groups. For multi-color preview, keep a group; solidify only for the printable body.

Import a saved model

importModel({ name })importModel({ id })

Imported models behave like normal shapes. Preview needs a saved project named Gear — fallback shown here.

Code
function fallbackGear() {
  const tooth = box({
    size: [6, 3, 6],
    color: "#64748b",
  });

  return union(
    cylinder({ radius: 12, height: 6, color: "#475569" }),
    solidify(radialPattern({
      item: tooth,
      count: 12,
      radius: 14,
      rotateItems: true,
    }))
  );
}

let gear;
try {
  gear = importModel({ name: "Gear" });
} catch {
  gear = fallbackGear();
}

return group([
  gear.move([-22, 0, 0]),
  gear.move([22, 0, 0]).rotate([0, 0, 15]),
]);
Preview

Scroll to load preview…

STL export

toSTL(model, { binary })exportSTL(model, { binary, filename })

In the editor, use the Export button. In code, toSTL returns a buffer.

Code
const model = roundedBox({
  size: [40, 30, 12],
  radius: 3,
  color: "#3b82f6",
});

// In the editor UI: click Export STL.
// Programmatically:
// const buffer = toSTL(model, { binary: true });

return model;
Preview

Scroll to load preview…

17. Create with AI

Start with the result you need. ShapeScript turns the brief into code, renders it in an isolated geometry worker, and applies it only after validation.

Tip

A strong brief includes purpose, overall size, interfaces such as screws or shafts, and the dimensions you want as live controls.

Describe the engineering intent

purpose + dimensions + features + constraints

Example: “A wall-mounted phone shelf, 90 mm wide, with a charging slot, two countersunk screw holes, 3 mm walls, and sliders for width and depth.”

Refine instead of restarting

New modelChange currentFix error

Use New model for a fresh design. Use Change current for requests such as “make the base 8 mm thicker and move the holes 10 mm apart.” Fix error sends the current worker error back to the modeling agent.

Inspect the generation loop

Generation detailsUndo AI change

Generation details shows whether the draft is being designed, validated, repaired, or applied. AI edits keep the previous script available for undo.

18. Tips and Tricks

Short patterns that separate toy demos from printable designs.

Build order

Parts → place → union/group → cut → label. Cut late so you do not redo holes.

Code
const plate = roundedBox({ size: [80, 50, 6], radius: 3, color: "#64748b" });
const post = cylinder({ radius: 8, height: 12, color: "#475569" }).move([0, 0, 9]);
const hole = throughHole({ radius: 2.5, height: 30 });

return union(
  subtract(union(plate, post), hole),
  text3d({ text: "A1", size: 5, height: 1, color: "#f8fafc" }).move([0, 18, 3.5])
);
Preview

Scroll to load preview…

Clearance for fit

Printed holes shrink. Add 0.2–0.4 mm clearance for screws and nuts.

Code
const screwR = 1.5;
const clearance = 0.3;

return subtract(
  roundedBox({ size: [40, 40, 10], radius: 3, color: "#334155" }),
  throughHole({ radius: screwR + clearance, height: 20 })
);
Preview

Scroll to load preview…

Through-cutters must overshoot

Make cutters taller/longer than the wall so boolean edges stay clean.

Code
const wall = box({ size: [60, 8, 40], color: "#64748b" });
const cutter = cylinder({ radius: 5, height: 20 }); // taller than 8 mm wall

return subtract(wall, cutter.rotate([90, 0, 0]));
Preview

Scroll to load preview…

Symmetry with mirror

Model one side, mirror the rest. One source of truth.

Code
const wing = roundedBox({
  size: [40, 16, 4],
  radius: 2,
  color: "#38bdf8",
}).move([30, 0, 0]);

const fuselage = cylinder({
  radius: 6,
  height: 50,
  color: "#1e293b",
}).rotate([0, 90, 0]);

return group([fuselage, wing, wing.mirror("x")]);
Preview

Scroll to load preview…

group for speed, union for print

Iterate with group(). solidify() or union() when you need one printable body.

Code
const peg = cylinder({ radius: 3, height: 12, color: "#f59e0b" });

const layout = linearPattern({
  item: peg,
  count: 4,
  spacing: [14, 0, 0],
});

// Preview / layout: return layout;
// Printable merged body:
return solidify(layout);
Preview

Scroll to load preview…

Dependent dimensions

Derive sizes from other sizes so the model stays consistent when sliders move.

Code
const width = slider({ name: "Width", default: 80, min: 50, max: 120, step: 1 });
const depth = width * 0.6;
const wall = 2.5;

return openBox({
  size: [width, depth, 28],
  wallThickness: wall,
  floorThickness: wall,
  radius: 3,
  color: "#0ea5e9",
});
Preview

Scroll to load preview…

Quality vs speed

Default quality is fine while designing. Raise it before export if curves look blocky.

Code
setQuality("high");

return torus({
  majorRadius: 20,
  tubeRadius: 5,
  color: "#a78bfa",
});
Preview

Scroll to load preview…

Full scripts from examples/. Same side-by-side viewer — study these when you are ready to combine techniques.

Basic Centered Box

01-basic-centered-box.js
Code
// Basic centered box: object-options primitives and center anchoring.

const color = colorPicker({
  name: "Body Color",
  default: "#3b82f6",
  category: "Appearance",
});

return box({
  size: [60, 40, 18],
  anchor: "center",
  color,
});
Preview

Scroll to load preview…

Parametric Rounded Plate

02-parametric-rounded-plate.js
Code
// Parametric rounded plate: dimensions are exposed as live controls.

const width = slider({
  name: "Width",
  default: 100,
  min: 50,
  max: 180,
  step: 1,
  unit: "mm",
  category: "Dimensions",
});

const radius = slider({
  name: "Corner Radius",
  default: 6,
  min: 0,
  max: 16,
  step: 0.5,
  unit: "mm",
  category: "Dimensions",
});

return roundedBox({
  size: [width, 50, 5],
  radius,
  anchor: "center",
  color: "#2563eb",
});
Preview

Scroll to load preview…

Grid Hole Plate

03-grid-hole-plate.js
Code
// Grid hole plate: gridPattern() creates cutter placement, solidify() merges cutters.

const base = roundedBox({
  size: [120, 70, 6],
  radius: 5,
  anchor: "center",
  color: "#0f766e",
});

const holes = gridPattern({
  item: throughHole({ radius: 3, height: 16, anchor: "center" }),
  rows: 2,
  columns: 4,
  spacing: [25, 28],
  anchor: "center",
});

return subtract(base, solidify(holes));
Preview

Scroll to load preview…

Phone Stand

04-phone-stand.js
Code
// Phone stand: printable base, angled back, and front lip.

const width = slider({
  name: "Width",
  default: 70,
  min: 50,
  max: 100,
  step: 1,
  unit: "mm",
});

const base = roundedBox({
  size: [width, 75, 7],
  radius: 4,
  anchor: "center",
  color: "#1d4ed8",
}).move([0, 0, 3.5]);

const back = roundedBox({
  size: [width, 7, 78],
  radius: 3,
  anchor: "center",
  color: "#2563eb",
}).rotate([18, 0, 0]).move([0, 24, 40]);

const lip = roundedBox({
  size: [width, 12, 18],
  radius: 3,
  anchor: "center",
  color: "#60a5fa",
}).move([0, -31, 12]);

return union(base, back, lip).anchor("bottom-center");
Preview

Scroll to load preview…

Wall Bracket With Ribs

05-wall-bracket-with-ribs.js
Code
// Wall bracket with ribs: counterbore cutters, ribs, and a raised label.

const backPlate = roundedBox({
  size: [80, 8, 90],
  radius: 4,
  anchor: "center",
  color: "#64748b",
});

const shelf = roundedBox({
  size: [80, 55, 8],
  radius: 3,
  anchor: "center",
  color: "#475569",
}).move([0, -27, -20]);

const ribShape = rib({
  length: 45,
  height: 45,
  thickness: 6,
  anchor: "center",
  color: "#334155",
}).move([-28, -8, -40]);

const screw = counterboreHole({
  shaftRadius: 2.2,
  shaftHeight: 16,
  headRadius: 5,
  headDepth: 4,
  anchor: "center",
}).rotate([90, 0, 0]);

const screws = linearPattern({
  item: screw,
  count: 2,
  spacing: [0, 0, 42],
  anchor: "center",
});

const label = text3d({
  text: "MAX 2KG",
  size: 7,
  height: 1,
  anchor: "center",
  color: "#f8fafc",
}).rotate([90, 0, 0]).move([0, -4.5, 25]);

return union(
  subtract(union(backPlate, shelf, ribShape, ribShape.mirror("x")), solidify(screws)),
  label
).anchor("bottom-center");
Preview

Scroll to load preview…

Open Box

06-open-box.js
Code
// Open box: a real hollow printable container.

const wall = slider({
  name: "Wall Thickness",
  default: 3,
  min: 1.5,
  max: 6,
  step: 0.5,
  unit: "mm",
});

return openBox({
  size: [90, 60, 35],
  wallThickness: wall,
  floorThickness: 3,
  radius: 5,
  anchor: "bottom-center",
  color: "#0891b2",
});
Preview

Scroll to load preview…

Grid Storage Tray

07-grid-storage-tray.js
Code
// Grid storage tray: rows and columns are parametric.

const rows = slider({ name: "Rows", default: 2, min: 1, max: 4, step: 1 });
const columns = slider({ name: "Columns", default: 4, min: 2, max: 6, step: 1 });

return gridStorageTray({
  size: [140, 80, 28],
  rows,
  columns,
  wallThickness: 2.5,
  floorThickness: 2,
  radius: 4,
  anchor: "bottom-center",
  color: "#16a34a",
});
Preview

Scroll to load preview…

Hinge

08-hinge.js
Code
// Hinge: printable knuckles with a subtracted pin hole.

const knuckleCount = slider({
  name: "Knuckles",
  default: 5,
  min: 3,
  max: 9,
  step: 2,
});

return hinge({
  length: 90,
  radius: 6,
  pinRadius: 2,
  knuckleCount,
  clearance: 0.35,
  anchor: "center",
  color: "#71717a",
});
Preview

Scroll to load preview…

Table With Reused Leg

09-table-with-reused-leg.js
Code
// Table with reused leg: components are plain JavaScript functions.

function createTableLeg(options = {}) {
  const { height = 70, topWidth = 8, bottomWidth = 5 } = options;
  return taperedBox({
    height,
    topSize: [topWidth, topWidth],
    bottomSize: [bottomWidth, bottomWidth],
    anchor: "center",
    color: "#8b5a2b",
  });
}

const leg = createTableLeg();

return group({
  name: "Parametric Table",
  children: [
    roundedBox({ size: [110, 70, 6], radius: 3, anchor: "center", color: "#c08457" }).move([0, 0, 70]),
    leg.move([-45, -25, 35]),
    leg.move([45, -25, 35]),
    leg.move([-45, 25, 35]),
    leg.move([45, 25, 35]),
  ],
});
Preview

Scroll to load preview…

Text Label Tag

10-text-label-tag.js
Code
// Text label tag: raised text is real geometry and exports to STL.

const base = roundedBox({
  size: [64, 28, 4],
  radius: 4,
  anchor: "center",
  color: "#f59e0b",
});

const ringHole = throughHole({ radius: 3, height: 10, anchor: "center" }).move([-24, 0, 0]);

const label = text3d({
  text: "BH35",
  size: 8,
  height: 1,
  anchor: "center",
  color: "#111827",
}).move([8, 0, 2.5]);

return union(subtract(base, ringHole), label).anchor("center");
Preview

Scroll to load preview…

Lego Sorting Tray

11-lego-sorting-tray.js
Code
// Lego sorting tray: practical tray plus raised label.

const trayBody = tray({
  size: [150, 95, 24],
  wallThickness: 2.5,
  floorThickness: 2,
  radius: 5,
  compartments: { rows: 2, columns: 3, wallThickness: 2.5 },
  anchor: "bottom-center",
  color: "#dc2626",
});

const label = text3d({
  text: "BRICKS",
  size: 7,
  height: 1,
  anchor: "center",
  color: "#fef2f2",
}).move([0, -36, 24.5]);

return union(trayBody, label);
Preview

Scroll to load preview…

Ai Generated Model

12-ai-generated-model.js
Code
// AI-generated model target: wall organizer with holes, hooks, and label.

const plate = roundedBox({
  size: [130, 8, 46],
  radius: 4,
  anchor: "center",
  color: "#0f172a",
});

const hook = snapClip({
  width: 14,
  length: 34,
  thickness: 5,
  hookHeight: 12,
  anchor: "center",
  color: "#2563eb",
}).rotate([90, 0, 0]);

const hooks = linearPattern({
  item: hook,
  count: 4,
  spacing: [24, 0, 0],
  anchor: "center",
}).move([0, -19, -10]);

const screws = gridPattern({
  item: counterboreHole({ shaftRadius: 2, shaftHeight: 18, headRadius: 4.5, headDepth: 3 }).rotate([90, 0, 0]),
  rows: 1,
  columns: 2,
  spacing: [104, 1],
  anchor: "center",
});

return union(subtract(plate, solidify(screws)), solidify(hooks)).anchor("bottom-center");
Preview

Scroll to load preview…

Imported Gear Assembly

13-imported-gear-assembly.js
Code
// Imported gear assembly: use a saved project named "Gear", with a local fallback.

function fallbackGear() {
  const core = cylinder({ radius: 16, height: 6, segments: 64, anchor: "center" });
  const tooth = box({ size: [5, 8, 6], anchor: "center" }).move([0, 19, 0]);
  const teeth = radialPattern({ item: tooth, count: 16, radius: 0.01, rotateItems: true }).asUnion();
  return subtract(union(core, teeth), throughHole({ radius: 4, height: 10, anchor: "center" }));
}

let gear;
try {
  gear = importModel({ name: "Gear" });
} catch (error) {
  gear = fallbackGear();
}

return group({
  name: "Gear Assembly",
  children: [
    gear.color("#94a3b8").move([-22, 0, 0]),
    gear.color("#cbd5e1").move([22, 0, 0]).rotate([0, 0, 11.25]),
  ],
});
Preview

Scroll to load preview…

Snap Clip

14-snap-clip.js
Code
// Snap clip: flexible printable clip with a relief slot.

const clip = snapClip({
  width: 18,
  length: 48,
  thickness: 3,
  hookHeight: 8,
  clearance: 0.4,
  anchor: "center",
  color: "#7c3aed",
});

const flexSlot = slot({
  width: 4,
  length: 28,
  height: 8,
  anchor: "center",
}).move([0, -4, 0]);

return subtract(clip, flexSlot).anchor("bottom-center");
Preview

Scroll to load preview…

Nut Trap Demo

15-nut-trap-demo.js
Code
// Nut trap demo: hex trap plus through screw hole.

const block = roundedBox({
  size: [55, 32, 16],
  radius: 3,
  anchor: "center",
  color: "#0ea5e9",
});

const nut = hexNutTrap({
  flatWidth: 5.5,
  height: 8,
  clearance: 0.25,
  anchor: "center",
}).move([0, 0, 2]);

const screw = throughHole({ radius: 1.8, height: 28, anchor: "center" }).rotate([90, 0, 0]);

return subtract(block, nut, screw).anchor("bottom-center");
Preview

Scroll to load preview…