Your first model
Return a shape. That is the whole contract.
return roundedBox({
size: [80, 40, 12],
radius: 4,
color: "#3b82f6",
});Scroll to load preview…
From a plain-language idea to a validated, parametric, printable model
Scripts are normal JavaScript. Return one model. Units are millimeters. Rotations use degrees.
The build plate is Z=0. Solids default to bottom-center, so they grow upward into positive Z.
Return a shape. That is the whole contract.
return roundedBox({
size: [80, 40, 12],
radius: 4,
color: "#3b82f6",
});Scroll to load preview…
Sliders appear in the editor UI and re-run the script instantly.
const width = slider({
name: "Width",
default: 100,
min: 40,
max: 200,
step: 1,
unit: "mm",
});
return roundedBox({
size: [width, 60, 6],
radius: 4,
});Scroll to load preview…
Build solids from primitives. Most accept anchor, name, color, segments, and quality.
Call setQuality("high") once when curved surfaces look faceted. Prefer quality presets over huge segment counts.
Cuboids and cubes. By default their bottom face rests on Z=0.
return group([
box({ size: [80, 40, 12], color: "#3b82f6" }),
cube({ size: 24, color: "#f59e0b" }).move([0, 0, 18]),
]);Scroll to load preview…
Spheres, cylinders, cones, and tori.
setQuality("high");
return union(
torus({ majorRadius: 18, tubeRadius: 4 }),
cylinder({ radius: 4, height: 16 })
);Scroll to load preview…
Faces are arrays of point indexes.
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",
});Scroll to load preview…
Anchors pick which point of a shape sits at the origin. 3D solids default to bottom-center, keeping them above the build plate.
Omit anchor for normal bottom-up modeling. Use anchor: "center" for symmetric parts and cutters, or top-center and corner anchors for placement.
Re-anchor or drop a part onto a known point.
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);Scroll to load preview…
Shapes are immutable. Every transform returns a new shape you can chain.
Reuse one part many times: const arm = box(...); then arm.rotate(...), arm.mirror("x").
Rotations are degrees. Mirror is the fastest path to symmetry.
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"),
]);Scroll to load preview…
Booleans merge geometry into one physical solid. Use them for holes, cavities, and printable bodies.
Cutters should stick out past the part on both sides so the hole is truly through.
subtract() is how you cut holes. Array input works too.
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));Scroll to load preview…
Same ops as methods when you prefer a pipeline style.
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])
);Scroll to load preview…
group() is visual assembly. union() is one merged solid. Prefer groups until you need a single body.
Patterns return groups. Call solidify(pattern) or pattern.asUnion() before using them as cutters.
Keeps child colors/names, transforms together, and stays fast.
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]),
],
});Scroll to load preview…
Required before subtracting a pattern, or when export must be one solid.
const pins = linearPattern({
item: cylinder({ radius: 2, height: 10, color: "#38bdf8" }),
count: 5,
spacing: [12, 0, 0],
});
return solidify(pins);Scroll to load preview…
Repeat parts in lines, grids, or rings. Patterns return groups by default.
Build the item once, pattern it, then solidify only when cutting or exporting a merged body.
spacing is [x, y] or [x, y, z] depending on the helper.
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)
);Scroll to load preview…
rotateItems aims each copy outward — great for spokes and bolt circles.
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,
}))
);Scroll to load preview…
Use dedicated helpers for rounded/chamfered boxes. Sketch in 2D, then extrude.
There is no generic mesh fillet. Prefer roundedBox / chamferedBox / roundedRect + extrude.
Print-friendly edges without fake fillets.
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]),
]);Scroll to load preview…
Any 2D profile becomes a solid with extrude().
const profile = roundedRect({
size: [50, 20],
radius: 5,
});
return extrude(profile, {
height: 8,
color: "#14b8a6",
});Scroll to load preview…
Point lists are [x, y] in the XY plane.
const wing = polygon({
points: [
[0, 0], [40, 8], [40, 16], [0, 28], [-8, 14],
],
});
return extrude(wing, {
height: 4,
anchor: "center",
color: "#a78bfa",
});Scroll to load preview…
Ready-made cutters and printable features for brackets, enclosures, and hardware.
Add clearance (about 0.2–0.4 mm) for screws, nuts, and magnets so parts fit after printing.
Always subtract cutters from a solid base.
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])
);Scroll to load preview…
Building blocks for mounts and moving parts.
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));Scroll to load preview…
Hollow printable boxes and compartment trays — real walls, not visual shells.
Keep wallThickness ≥ 2 mm for FDM. Floor can be a bit thinner than walls.
Parametric storage in a few lines.
return gridStorageTray({
size: [140, 80, 28],
rows: 2,
columns: 4,
wallThickness: 2.5,
floorThickness: 2,
radius: 4,
anchor: "bottom-center",
color: "#0ea5e9",
});Scroll to load preview…
text3d() makes raised or recessed labels that survive STL export.
Raise text with union(), or sink it with subtract(). Keep stroke height ≥ 0.8 mm for FDM.
Emboss on top of a plate.
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);Scroll to load preview…
Cut text into the surface.
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);Scroll to load preview…
Declare UI controls in code. Values are plain JS — use them like any variable.
Group related controls with category. Keep step coarse enough that dragging feels snappy.
Controls drive dimensions, features, and colors.
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));Scroll to load preview…
Colors show in the preview. STL export is geometry-only — no color or hierarchy.
Use group() for multi-color previews. union() merges materials into one body.
Hex, 0–255 RGB, or 0–1 floats all work.
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])]);Scroll to load preview…
Real models are functions, reuse, and a clear build order: parts → assemble → cut → label.
Write small helpers (createLeg, createScrewBoss). Compose with group/union. Cut last.
Plain JS functions are your component system.
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]),
],
});Scroll to load preview…
Plate + shelf + ribs + hardware cutters + label.
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");Scroll to load preview…
Measure parts in code. Debug overlays help in preview and are skipped on STL export.
printability() is conservative — treat warnings as hints, not hard fails.
Drive labels or dependent geometry from real sizes.
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])
);Scroll to load preview…
Preview-only helpers for orientation and fit checks.
const model = openBox({
size: [80, 50, 30],
wallThickness: 3,
floorThickness: 3,
radius: 4,
color: "#0ea5e9",
});
return group([model, debugBounds(model)]);Scroll to load preview…
Reuse saved projects as shapes. Export triangle meshes for printing.
STL has no color or groups. For multi-color preview, keep a group; solidify only for the printable body.
Imported models behave like normal shapes. Preview needs a saved project named Gear — fallback shown here.
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]),
]);Scroll to load preview…
In the editor, use the Export button. In code, toSTL returns a buffer.
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;Scroll to load preview…
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.
A strong brief includes purpose, overall size, interfaces such as screws or shafts, and the dimensions you want as live controls.
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.”
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.
Generation details shows whether the draft is being designed, validated, repaired, or applied. AI edits keep the previous script available for undo.
Short patterns that separate toy demos from printable designs.
Parts → place → union/group → cut → label. Cut late so you do not redo holes.
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])
);Scroll to load preview…
Printed holes shrink. Add 0.2–0.4 mm clearance for screws and nuts.
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 })
);Scroll to load preview…
Make cutters taller/longer than the wall so boolean edges stay clean.
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]));Scroll to load preview…
Model one side, mirror the rest. One source of truth.
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")]);Scroll to load preview…
Iterate with group(). solidify() or union() when you need one printable body.
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);Scroll to load preview…
Derive sizes from other sizes so the model stays consistent when sliders move.
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",
});Scroll to load preview…
Default quality is fine while designing. Raise it before export if curves look blocky.
setQuality("high");
return torus({
majorRadius: 20,
tubeRadius: 5,
color: "#a78bfa",
});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: 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,
});Scroll to load preview…
// 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",
});Scroll to load preview…
// 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));Scroll to load preview…
// 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");Scroll to load preview…
// 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");Scroll to load preview…
// 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",
});Scroll to load preview…
// 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",
});Scroll to load preview…
// 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",
});Scroll to load preview…
// 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]),
],
});Scroll to load preview…
// 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");Scroll to load preview…
// 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);Scroll to load preview…
// 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");Scroll to load preview…
// 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]),
],
});Scroll to load preview…
// 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");Scroll to load preview…
// 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");Scroll to load preview…