ShapeScript Tutorial

Learn to generate 3D models with code

← Back to Editor

1. The Core Philosophy

ShapeScript is a lightweight browser modeling tool. Instead of clicking and dragging shapes, you write standard JavaScript code. Every script must return the final 3D model that you want to render and export to STL.

Note: The default unit of measurement is millimeters (mm), standard for 3D slicers.

2. 3D Primitives

Create basic solids using the following global functions:

Cubes and Boxes

cube(size)box(width, depth, height)

cube(size) creates a symmetric cube. box(width, depth, height) creates a cuboid. Both are anchored in the positive octant, stretching from 0 to the defined dimension on all axes.

// A 20mm x 20mm x 20mm cube
return cube(20);

// A rectangular box (Width=40mm, Depth=30mm, Height=10mm)
return box(40, 30, 10);

Spheres

sphere(radius)

sphere(radius) creates a sphere centered at the origin [0, 0, 0].

// A sphere of radius 15mm (diameter 30mm)
return sphere(15);

Cylinders

cylinder(radius, height)

Centered on the XY plane, extends along the positive Z axis from z = 0 to z = height.

// Cylinder with radius 10mm and height 40mm
return cylinder(10, 40);

Cones

cone(radiusTop, radiusBottom, height)

A truncated cone (frustum). Set radiusTop to 0 for a sharp point. Extends along the positive Z axis.

// Cone tapering to a point
return cone(0, 15, 30);

// Truncated cone (both radii non-zero)
return cone(8, 15, 30);

Torus

torus(radius, tubeRadius)

A donut ring centered at the origin, lying flat on the XY plane. radius is the distance from the center to the tube center; tubeRadius is the thickness of the tube.

// Major ring radius 25mm, tube thickness radius 4mm
return torus(25, 4);

3. Chainable Transformations

Apply moves, rotations, scaling, mirroring, or colors by chaining methods directly on any geometry object. All transformations return a new object so they can be chained freely.

Move (Translation)

.move(dx, dy, dz)

Shift a shape by offset values along X, Y, and Z axes.

// Move a 20mm cube by 15mm on X, and 5mm on Z
return cube(20).move(15, 0, 5);

Rotate

.rotate(ax, ay, az)

Rotate a shape around X, Y, and Z axes. Angles are in degrees.

// Rotate a box 45 degrees around the Z axis
return box(30, 10, 10).rotate(0, 0, 45);

Scale

.scale(s).scale(sx, sy, sz)

Resize shapes. A single number scales uniformly; three numbers scale X, Y, and Z independently.

// Double in X, shrink by half on Y
return sphere(10).scale(2, 0.5, 1);

Mirror

.mirror('x').mirror('y').mirror('z')

Flip geometry across a coordinate plane. Pass 'x', 'y', or 'z'.

// Mirror a cone across the X-axis plane
return cone(0, 10, 20).mirror('x');

4. Boolean Operations

Solid modeling allows you to combine shapes in three ways:

Union

union(shape1, shape2, ...)

Joins multiple shapes into a single solid body.

const block = box(40, 20, 10);
const peg   = cylinder(5, 20).move(20, 10, 0);

return union(block, peg);

Subtract

subtract(base, tool1, tool2, ...)

Cuts tool shapes out of a base shape. Ideal for holes, hollow boxes, or slots.

const base     = cube(30);
const drillBit = cylinder(5, 40).move(15, 15, -5);

return subtract(base, drillBit);

Intersect

intersect(shape1, shape2, ...)

Finds the common volume shared by all shapes.

return intersect(
  cube(30),
  sphere(20).move(15, 15, 15)
);

5. Grouping and Imports

group() is ShapeScript's equivalent of TinkerCAD's Group button. It bundles shapes into a unit so you can transform them together — without merging geometry via BSP trees.

Key differences from union(): each shape keeps its own color; no BSP computation (much faster for complex scenes); overlapping parts do not clip each other; groups can be nested.

group()

group(shape1, shape2, ...)

Creates a group from any number of CSG shapes or other groups. The returned Group object supports the same chainable transforms as individual shapes: .move(), .rotate(), .scale(), .mirror(), and .color().

// A reusable leg
const leg = cylinder(3, 20).color('#8b4513');

// Bundle tabletop + 4 legs into a group
const table = group(
  box(60, 60, 4).move(0, 0, 20).color('#deb887'),
  leg.move( 5,  5, 0),
  leg.move(52,  5, 0),
  leg.move( 5, 52, 0),
  leg.move(52, 52, 0),
);

// Rotate the whole group as one unit
return table.rotate(0, 0, 30);

Nested Groups

group(group1, group2, ...)

Groups can contain other groups for hierarchical assemblies. Each sub-group can be positioned before being nested.

const slatColor = colorPicker('Slat Color', '#c8a46e');
const slat = box(28, 2, 6).color(slatColor);

// Chair back as a sub-group
const back = group(
  slat.move(0, 0, 20),
  slat.move(0, 0, 28),
  slat.move(0, 0, 36),
);

const seat = box(32, 32, 3).color('#deb887');
const leg  = box(3, 3, 17).color('#8b4513');

// Compose into a chair
return group(
  seat,
  back.move(29, 1, 0),
  leg.move( 1,  1, 0),
  leg.move(28,  1, 0),
  leg.move( 1, 28, 0),
  leg.move(28, 28, 0),
);

importModel()

importModel(nameOrId)

Loads another saved project as a reusable model. Pass the project name or id, then transform, color, group, union, or subtract it like any other ShapeScript object. This is useful for parts such as gears, knobs, hinges, and fixtures that you want to reuse across models.

// In another saved project named "Gear", return the gear model.
// Then reuse it in this model:
const gear = importModel('Gear');

return group(
  gear,
  gear.move(60, 0, 0).rotate(0, 0, 15),
  cylinder(4, 12).move(30, 0, -2)
);

6. Colors

Assign colors to shapes using the chainable .color() method or the interactive colorPicker() control. Colors survive Boolean operations — each part of a union or subtraction keeps its own color. Colors are visible in the 3D viewport but are not stored in exported STL files (STL is geometry-only).

.color() — Hex String

.color(hexString)

Pass a CSS hex string. Both #rrggbb (6-digit) and #rgb (3-digit shorthand) are supported.

// 6-digit hex
return cube(20).color('#1db48f');

// 3-digit shorthand (#f40 expands to #ff4400)
return sphere(15).color('#f40');

// Chains with other transforms
return box(50, 30, 20).rotate(0, 0, 30).color('#a855f7');

.color() — RGB Numbers

.color(r, g, b)

Pass three numbers. ShapeScript auto-detects the range: 0.0-1.0 floats or 0-255 integers.

// Floats 0.0-1.0
return cube(20).color(0.18, 0.71, 0.69);  // teal

// Integers 0-255 (auto-detected because values > 1)
return sphere(15).color(255, 80, 40);     // coral

// Multi-part model, each part a different color
const body = box(60, 40, 20).color(0.22, 0.47, 0.80);
const peg  = cylinder(6, 30).move(30, 20, 20).color('#f59e0b');
return union(body, peg);

colorPicker() — Interactive Color Swatch

colorPicker(name, defaultHex)

Registers an interactive color-picker swatch in the controls panel. Returns a hex string (e.g. '#3b82f6') that you can pass directly to .color(). Changing the picker instantly re-renders the model — no typing needed.

// Declare two color pickers — they appear in the controls panel
const bodyColor   = colorPicker('Body Color',   '#3b82f6');
const detailColor = colorPicker('Detail Color', '#f59e0b');

// Use the returned hex strings with .color()
const body   = box(60, 40, 20).color(bodyColor);
const detail = cylinder(6, 25).move(30, 20, 20).color(detailColor);

return union(body, detail);

7. Dynamic Parametric Controls

You can create sliders, checkboxes, select menus, and color pickers that appear in the controls panel on the fly. Adjusting any control instantly rebuilds the model without re-typing code.

Slider

slider(name, default, min, max)

Returns a number. Renders as a range slider in the controls panel.

const size = slider('Cube Size', 25, 10, 80);
return cube(size).color('#1db48f');

Checkbox

checkbox(name, default)

Returns true or false.

const hollow = checkbox('Hollow Out', true);
const outer  = box(50, 40, 30).color('#3b82f6');

if (hollow) {
  const inner = box(44, 34, 28).move(3, 3, 2);
  return subtract(outer, inner);
}
return outer;

Select

select(name, default, optionsArray)

Returns the selected string option. Renders as a dropdown.

const shape = select('Shape', 'cube', ['cube', 'sphere', 'cylinder']);

if (shape === 'cube')     return cube(25).color('#ef4444');
if (shape === 'sphere')   return sphere(15).color('#22c55e');
if (shape === 'cylinder') return cylinder(12, 30).color('#3b82f6');

Color Picker

colorPicker(name, defaultHex)

Returns a hex string. Renders as a native color swatch in the controls panel — click it to open the OS color chooser. See the Colors section above for full usage.

const col = colorPicker('Shape Color', '#6366f1');
return torus(20, 5).color(col);

8. Advanced Modeling: Loops and Math

Because ShapeScript is pure JavaScript, you can use loops, variables, arrays, and the full Math library to create complex mechanical or decorative items.

Circular Pattern

Place shapes radially around a center using a loop and .rotate().

const teethCount = 12;
const coreRadius = 20;
const thick = 8;

const gearCore = cylinder(coreRadius, thick).color(0.75, 0.78, 0.82);
let gear = gearCore;

for (let i = 0; i < teethCount; i++) {
  const angle = (i * 360) / teethCount;
  const tooth = box(4, 8, thick)
    .move(-2, coreRadius - 2, 0)
    .rotate(0, 0, angle)
    .color(0.62, 0.65, 0.70);
  gear = union(gear, tooth);
}

return gear;

Math and Variables

Use Math.sin(), Math.cos(), and any standard JavaScript expression. All angles in ShapeScript's own API use degrees, but raw Math functions use radians as usual.

// Place 8 spheres in a rainbow ring
const col = colorPicker('Ring Color', '#6366f1');
let scene = cylinder(5, 5).color(col);

for (let i = 0; i < 8; i++) {
  const angle = (i / 8) * Math.PI * 2;
  const x = Math.cos(angle) * 30;
  const y = Math.sin(angle) * 30;
  const ball = sphere(5).move(x, y, 5).color(i / 8, 0.4, 1 - i / 8);
  scene = union(scene, ball);
}

return scene;