cadcode

Reference

Every function, method and command cadcode has, with each parameter and its default. Shapes never change in place: every method returns a new shape. Wherever a list of shapes is accepted, it may hold nested arrays and falsy values, so loops and conditions read naturally.

Writing a program

A program is an ES module. It can import its own helper files and npm packages, and TypeScript files work too. Everything below is imported from cadcode.

export default model | parts | (params) => model | parts

What the program builds. The CLI, the dev viewer and the playground all read it the same way.

modelSolid | Shape2 | arrays of them
A solid or a 2D shape, or an array of them. Arrays may nest. The program then has one part, named default.
parts{ [name]: model | (params) => model }
Named parts. The first one is the default. A function value is only called for the part being shown or exported, so expensive parts cost nothing until you pick them.
paramsobject
When the default export is a function, it is called with params merged with any --set overrides.

A 2D shape in a part is drawn in the viewer and in PNGs as a sketch on Z=0 (a see-through blue fill with a bold outline), to check a profile before extruding it. STL leaves it out, and a part with no solids at all fails to export.

The STL of a part merges all its solids into one body. In the viewer and in PNGs each solid keeps its own color and transparency.

Functions may be async.

export default (p) => ({
  assembly: () => [box(p).color('steelblue'), lid(p).color('orange', 0.6)],
  box: () => box(p),
  lid: () => lid(p),
});

export const params = { name: value, ... }

The parameters a program declares. They reach the default export and its part functions; --set may override them, and so may the Parameters panel of cadcode dev.

namestring
A parameter name. --set only accepts names declared here, so a typo is an error instead of being silently ignored.
valueany
The default value. Overrides from --set are parsed as JSON, and kept as strings when they are not valid JSON.
export const params = { width: 60, depth: 40, wall: 2 };

console.log(...values)

Prints from the program, like OpenSCAD's echo. The CLI prints to the terminal; the dev viewer and the playground show it in a corner of the page.

3D primitives

Units are whatever your slicer assumes, usually millimeters. Angles are in degrees.

cube(size, options): Solid

A box with one corner at the origin, extending into +X, +Y and +Z.

sizenumber | [x, y, z]= 1
A number makes a cube with equal sides.
options.centerboolean= false
Center the box on the origin instead.
cube([40, 20, 5], { center: true })

cylinder(options): Solid

A cylinder or a cone standing on the XY plane along +Z, like OpenSCAD's cylinder().

options.hnumber
Height. Required.
options.rnumber= 1
Radius of both ends.
options.dnumber
Diameter of both ends, instead of r. r wins when both are given.
options.r1number
Bottom radius, for a cone. Wins over d1, r and d.
options.r2number
Top radius, for a cone. Wins over d2, r and d. 0 makes a pointed cone.
options.d1number
Bottom diameter, instead of r1.
options.d2number
Top diameter, instead of r2.
options.centerboolean= false
Center it on the origin along Z, from -h/2 to h/2.
options.fnnumber
Segments for this curve only, like OpenSCAD's $fn. Without it the defaults from setResolution apply.

The segment count follows the larger of the two radii.

cylinder({ d: 3.2, h: 10, center: true, fn: 32 })
cylinder({ h: 8, r1: 6, r2: 0 }) // a cone

sphere(options): Solid

A sphere centered on the origin. sphere(5) is sphere({ r: 5 }).

optionsnumber | object
A number is the radius.
options.rnumber= 1
Radius.
options.dnumber
Diameter, instead of r. r wins when both are given.
options.fnnumber
Segments for this curve only, like OpenSCAD's $fn. Without it the defaults from setResolution apply.
sphere({ d: 20, fn: 64 })

polyhedron(points, faces): Solid

A solid from explicit vertices and faces, like OpenSCAD's polyhedron().

points[x, y, z][]
The vertices. A missing coordinate is 0.
facesnumber[][]
Each face lists indices into points, clockwise when seen from outside, as in OpenSCAD. Faces with more than three points are split into triangles, so keep them flat and convex.

Throws when the faces do not close into a solid, and says why.

polyhedron(
  [[0, 0, 0], [10, 0, 0], [0, 10, 0], [0, 0, 10]],
  [[0, 1, 2], [0, 3, 1], [0, 2, 3], [1, 3, 2]],
)

2D primitives

Flat shapes in the XY plane. Extrude or revolve them to get a solid.

square(size, options): Shape2

A rectangle with one corner at the origin, extending into +X and +Y.

sizenumber | [x, y]= 1
A number makes a square.
options.centerboolean= false
Center it on the origin instead.
square([30, 12], { center: true })

circle(options): Shape2

A circle centered on the origin. circle(5) is circle({ r: 5 }).

optionsnumber | object
A number is the radius.
options.rnumber= 1
Radius.
options.dnumber
Diameter, instead of r. r wins when both are given.
options.fnnumber
Segments for this curve only, like OpenSCAD's $fn. Without it the defaults from setResolution apply.
circle({ d: 8, fn: 6 }) // a hexagon

polygon(points, paths): Shape2

A polygon from points, like OpenSCAD's polygon().

points[x, y][]
The corners. Without paths they are one outline, in order.
pathsnumber[][]= one path through all points
Optional. Each path lists indices into points. A path inside another becomes a hole (even-odd fill).
// A square frame: the inner path is a hole.
polygon(
  [[0, 0], [20, 0], [20, 20], [0, 20], [5, 5], [15, 5], [15, 15], [5, 15]],
  [[0, 1, 2, 3], [4, 5, 6, 7]],
)

Functions

The OpenSCAD nesting style. Each one takes any mix of shapes and nested arrays, unites them, and applies the operation, so translate(v, a, b) is union(a, b).translate(v). They work on solids or on 2D shapes, never both mixed. With no shapes they return an empty solid.

union(...shapes): Solid | Shape2

Joins shapes into one.

...shapesSolid | Shape2 | arrays of them
Any mix of shapes and nested arrays, as loops produce them. Falsy entries are skipped, so cond && shape works. All solids or all 2D shapes: mixing them throws.
union(cube(10), sphere(6).translate([10, 10, 10]))

difference(base, ...cutters): Solid | Shape2

Cuts every following shape out of the first one. The same as base.subtract(...cutters).

baseSolid | Shape2
The shape to cut from.
...cuttersSolid | Shape2 | arrays of them
Shapes to remove. Nested arrays and falsy entries are allowed. With none, base comes back unchanged.
difference(
  cube([40, 20, 4]),
  holes.map(([x, y]) => cylinder({ d: 3, h: 10 }).translate([x, y, -1])),
  withSlot && slot,
)

intersection(...shapes): Solid | Shape2

Keeps only the space that all shapes share.

...shapesSolid | Shape2 | arrays of them
Any mix of shapes and nested arrays, as loops produce them. Falsy entries are skipped, so cond && shape works. All solids or all 2D shapes: mixing them throws.
intersection(cube(20, { center: true }), sphere(13))

hull(...shapes): Solid | Shape2

The convex hull of all shapes, like OpenSCAD's hull(): as if they were shrink-wrapped together.

...shapesSolid | Shape2 | arrays of them
Any mix of shapes and nested arrays, as loops produce them. Falsy entries are skipped, so cond && shape works. All solids or all 2D shapes: mixing them throws.
// A box with rounded vertical edges.
hull(product([3, 37], [3, 17]).map(([x, y]) => cylinder({ r: 3, h: 10 }).translate([x, y, 0])))

translate(v, ...shapes): Solid | Shape2

Moves shapes. The same as union(...shapes).translate(v).

v[x, y, z] | [x, y]
The offset. Missing components are 0.
...shapesSolid | Shape2 | arrays of them
Any mix of shapes and nested arrays, as loops produce them. Falsy entries are skipped, so cond && shape works. All solids or all 2D shapes: mixing them throws.

rotate(angles, options, ...shapes): Solid | Shape2

Rotates shapes. The same as union(...shapes).rotate(angles, options).

angles[x, y, z] | number
Degrees. Solids take [x, y, z], applied around X, then Y, then Z, or one number around Z. 2D shapes take one number only.
options.aroundPoint | Solid | Shape2 | [x, y, z]
Optional; recognized when the second argument is a plain object. Turns around this instead of the origin: a point, the center of a shape's bounding box, or plain coordinates.
...shapesSolid | Shape2 | arrays of them
Any mix of shapes and nested arrays, as loops produce them. Falsy entries are skipped, so cond && shape works. All solids or all 2D shapes: mixing them throws.

To turn around an arbitrary axis, use the method: shape.rotate(a, axis).

rotate(90, { around: point([10, 0, 0]) }, arm, hand)

scale(v, ...shapes): Solid | Shape2

Scales shapes from the origin. The same as union(...shapes).scale(v).

vnumber | [x, y, z] | [x, y]
One factor for every axis, or one per axis. Missing components are 1.
...shapesSolid | Shape2 | arrays of them
Any mix of shapes and nested arrays, as loops produce them. Falsy entries are skipped, so cond && shape works. All solids or all 2D shapes: mixing them throws.

mirror(normal, ...shapes): Solid | Shape2

Mirrors shapes. The same as union(...shapes).mirror(normal).

normal[x, y, z] | [x, y]
The normal of the mirror plane (a line, in 2D) through the origin. [1, 0, 0] flips X.
...shapesSolid | Shape2 | arrays of them
Any mix of shapes and nested arrays, as loops produce them. Falsy entries are skipped, so cond && shape works. All solids or all 2D shapes: mixing them throws.

color(c, alpha, ...shapes): Solid

Colors solids. The same as union(...shapes).color(c, alpha). Write it as color('orange', lid) or color('orange', 0.35, lid).

cstring | [r, g, b] | [r, g, b, a]
A CSS color name, #rgb, #rrggbb, #rrggbbaa, or channels from 0 to 1. See parseColor.
alphanumber
Optional opacity from 0 to 1. Recognized when the second argument is a number; it overrides the alpha in c.
...shapesSolid | arrays of them
Solids only: 2D shapes have no color.

offset(options, ...shapes): Shape2

Grows or shrinks 2D outlines. The same as union(...shapes).offset(options). See Shape2.offset for the options.

optionsnumber | { r, fn } | { delta, chamfer }
How far, and what to do with the corners.
...shapesShape2 | arrays of them
2D shapes only.

linearExtrude(height, options, ...shapes): Solid

Extrudes 2D shapes along +Z, like OpenSCAD's linear_extrude(). The same as union(...shapes).extrude(height, options).

heightnumber
How tall.
optionsobject
Required in this position; pass {} for none. See Shape2.extrude for each option.
...shapesShape2 | arrays of them
2D shapes only.
linearExtrude(30, { twist: 90, divisions: 30 }, square(10, { center: true }))

Solid methods

Every 3D shape is a Solid. Methods never change the solid they are called on; each returns a new one, so chains read top to bottom.

Solid.translate(v): Solid

Moves the solid.

v[x, y, z]
The offset. Missing components are 0, so [5] moves along X only.

Solid.rotate(angles, axis, options): Solid

Rotates the solid, in degrees, around the origin or around options.around.

angles[x, y, z] | number
[x, y, z] turns around X, then Y, then Z, like OpenSCAD. One number turns around Z, or around axis when it is given.
axis[x, y, z]
Optional. An axis through the pivot to turn around; it need not be unit length.
options.aroundPoint | Solid | Shape2 | [x, y, z]
Optional; may come right after angles when there is no axis. Turns around this instead of the origin: a point, the center of a shape's bounding box, or plain coordinates.
peg.rotate([0, 90, 0])
peg.rotate(45, [1, 1, 0])
lid.rotate([-110, 0, 0], { around: hinge })
peg.rotate(30, [1, 0, 0], { around: base })

Solid.scale(v): Solid

Scales the solid from the origin.

vnumber | [x, y, z]
One factor for every axis, or one per axis. Missing components are 1.

Solid.mirror(normal): Solid

Mirrors the solid across the plane through the origin with this normal.

normal[x, y, z]
[1, 0, 0] flips X, [0, 0, 1] flips Z.

Solid.transform(matrix): Solid

Applies an affine transform.

matrixnumber[16]
A 4×4 matrix in column-major order.

Solid.color(color, alpha): Solid

Colors the whole solid. Colors survive booleans: a union of colored parts keeps each face's color. They show in the viewer and in PNGs; STL files have no color.

colorstring | [r, g, b] | [r, g, b, a]
A CSS color name, #rgb, #rrggbb, #rrggbbaa, or channels from 0 to 1.
alphanumber
Optional opacity from 0 to 1. Overrides the alpha in color.
lid.color('orange', 0.35)

Solid.union(...others): Solid

Joins this solid with the others.

...othersSolid | arrays of them
Solids to join. Nested arrays and falsy entries are allowed.

Solid.subtract(...others): Solid

Cuts the others out of this solid.

...othersSolid | arrays of them
Solids to remove. With none, the solid comes back unchanged. Nested arrays and falsy entries are allowed.
plate.subtract(holes.map(([x, y]) => cylinder({ d: 3, h: 10 }).translate([x, y, -1])))

Solid.intersect(...others): Solid

Keeps only the space this solid shares with all the others.

...othersSolid | arrays of them
Solids to intersect with. Nested arrays and falsy entries are allowed.

Solid.hull(...others): Solid

The convex hull of this solid together with the others.

...othersSolid | arrays of them
Solids to include. With none, the hull of this solid alone. Nested arrays and falsy entries are allowed.

Solid.slice(z): Shape2

The cross-section at a height, as a 2D shape.

znumber= 0
Height of the cutting plane.

Shape2 methods

Every 2D shape is a Shape2. Like solids, they are never changed in place.

Shape2.translate(v): Shape2

Moves the shape.

v[x, y]
The offset. Missing components are 0.

Shape2.rotate(degrees, options): Shape2

Rotates the shape counter-clockwise, around the origin or around options.around.

degreesnumber
The angle.
options.aroundPoint | Solid | Shape2 | [x, y]
Optional. Turns around this instead of the origin: a point (its Z is ignored), the center of a shape's bounding box, or plain coordinates.

Shape2.scale(v): Shape2

Scales the shape from the origin.

vnumber | [x, y]
One factor for both axes, or one per axis. Missing components are 1.

Shape2.mirror(axis): Shape2

Mirrors the shape across the line through the origin with this normal.

axis[x, y]
The normal of the mirror line. [1, 0] flips X.

Shape2.offset(options): Shape2

Grows the outline, or shrinks it with a negative distance, like OpenSCAD's offset().

optionsnumber | object
A number means { r: number }.
options.rnumber
Distance with rounded corners.
options.fnnumber
Segments for the rounded corners, with r only.
options.deltanumber
Distance with sharp corners, instead of r.
options.chamferboolean= false
With delta: cut the corners flat instead of keeping them sharp.
square(20, { center: true }).offset(3)          // rounded
square(20, { center: true }).offset({ delta: 3 }) // sharp

Shape2.union(...others): Shape2

Joins this shape with the others.

...othersShape2 | arrays of them
Shapes to join. Nested arrays and falsy entries are allowed.

Shape2.subtract(...others): Shape2

Cuts the others out of this shape.

...othersShape2 | arrays of them
Shapes to remove. With none, the shape comes back unchanged. Nested arrays and falsy entries are allowed.

Shape2.intersect(...others): Shape2

Keeps only the area this shape shares with all the others.

...othersShape2 | arrays of them
Shapes to intersect with. Nested arrays and falsy entries are allowed.

Shape2.hull(...others): Shape2

The convex hull of this shape together with the others.

...othersShape2 | arrays of them
Shapes to include. With none, the hull of this shape alone. Nested arrays and falsy entries are allowed.

Shape2.extrude(height, options): Solid

Extrudes the shape along +Z into a solid, like OpenSCAD's linear_extrude().

heightnumber
How tall.
options.centerboolean= false
Center the solid on the XY plane, from -height/2 to height/2.
options.twistnumber= 0
Degrees the top is turned relative to the bottom.
options.scalenumber | [x, y]= 1
Size of the top relative to the bottom. 0 makes a point, for a pyramid or a cone.
options.divisionsnumber= 0
Extra slices between bottom and top. A twist needs them to look smooth.
circle({ r: 20, fn: 6 }).extrude(60, { twist: 120, scale: 0.6, divisions: 40 })

Shape2.revolve(options): Solid

Spins the shape around the Y axis into a solid around Z, like OpenSCAD's rotate_extrude(): the shape's X becomes the radius and its Y becomes the height. Draw the profile on the positive-X side.

options.anglenumber= 360
How far to spin, in degrees. Less than 360 leaves a wedge open.
options.fnnumber
Segments around. Without it the defaults from setResolution apply, using the shape's widest X.
// A ring.
circle(2).translate([10, 0]).revolve({ fn: 64 })

Helpers

setResolution(options)

Sets how many segments circles, cylinders, spheres, rounded offsets and revolves get, with the same rules as OpenSCAD's $fn, $fa and $fs. It applies to every shape made after the call.

options.fnnumber= 0
A fixed segment count (at least 3). When it is above 0, fa and fs are ignored.
options.fanumber= 12
The largest angle one segment may span, in degrees.
options.fsnumber= 2
The largest length one segment may have.

With fn at 0, a curve of radius r gets ceil(max(min(360 / fa, 2πr / fs), 5)) segments. A primitive's own fn wins over all of this.

setResolution({ fn: 48 });

range(start, end, step): number[]

Numbers from start to end, including end, like OpenSCAD's [start : step : end].

startnumber
The first number.
endnumber
The last number, included when a step lands on it.
stepnumber= 1
Negative counts down. 0 throws.
range(0, 10, 2.5) // [0, 2.5, 5, 7.5, 10]

product(...lists): any[][]

Every combination of the lists, like OpenSCAD's for (x = xs, y = ys).

...listsany[][]
The lists to combine, the first one varying slowest.
product([1, 2], ['a', 'b']) // [[1, 'a'], [1, 'b'], [2, 'a'], [2, 'b']]
product(xs, ys).map(([x, y]) => peg.translate([x, y, 0]))

parseColor(input, alpha): [r, g, b, a]

Turns any color color() accepts into channels from 0 to 1.

inputstring | [r, g, b] | [r, g, b, a]
A CSS color name (the same names OpenSCAD accepts), #rgb, #rrggbb, #rrggbbaa, or channels from 0 to 1. A missing alpha is 1. An unknown name throws.
alphanumber
Optional. Overrides the alpha.
parseColor('#f80')        // [1, 0.53, 0, 1]
parseColor('steelblue', 0.5)

point(position): Point

A point in space: no geometry, never in an STL. It marks a place, such as a pivot for rotate(..., { around }), and moves with the same transforms as a solid, so a pivot can travel with its part.

position[x, y, z]
Where it is. Missing components are 0.

Methods, each returning a new point: .translate(v), .rotate(angles, axis, options) (the same arguments as Solid.rotate), .scale(v), .mirror(normal), .transform(m).

Read it with .x, .y, .z or .toArray().

const hinge = point([0, 40, 30])
const lid = lidShape.rotate([-110, 0, 0], { around: hinge })

Point

The class of points, exported for instanceof checks. Make points with point, not with new.

Solid

The class of every 3D shape, exported for instanceof checks. Make solids with the primitives, not with new.

if (shape instanceof Solid) ...

Shape2

The class of every 2D shape, exported for instanceof checks. Make shapes with the 2D primitives, not with new.

Command line

Installed with the package as cadcode, or run with npx cadcode. Every command takes the program file as its argument.

cadcode dev <file> [--part] [--set] [--port] [--host] [--open]

A live preview in the browser. It watches the program and every file it imports, and on each save swaps the new model in without moving the camera. A program with params gets a Parameters panel to change them in the page; the values outlive edits to the code and restarts of the server, and one the program stops declaring is dropped.

--portnumber= 5173
The port to serve on.
--hostaddress= 127.0.0.1
The address to bind. 0.0.0.0 makes it reachable from other devices.
--openflag
Open the browser.
--partname
The part to show first. The page has a Part menu for the rest.
--setkey=value
Override a parameter. Repeatable. It wins over a value remembered from the Parameters panel.

cadcode stl <file> [--part | --all] [--set] [--out]

Writes a part as a binary STL, its solids merged into one body.

--partname= the first part
Short form -p. The part to export.
--allflag
Short form -a. Export every part, one file each.
--setkey=value
Short form -s. Override a parameter declared in params. Repeatable. The value is parsed as JSON, and kept as a string otherwise.
--outpath= <name>.stl
Short form -o. The output file, or a directory with --all. A program with several parts writes <name>-<part>.stl.
cadcode stl case.js --part box --set wall=2.4
cadcode stl case.js --all -o out/

cadcode png <file> [--part | --all] [--set] [--out] [--view] [--size]

Renders a part to a PNG, each solid in its own color. Takes --part, --all, --set and --out like cadcode stl, plus:

--viewname= iso
iso, iso-back, front, back, left, right, top, bottom, or sheet for iso, front, right and top in one image.
--sizeWxH= 800x600
The image size in pixels.
cadcode png case.js --part assembly --view sheet --size 1600x1200

cadcode --help

Prints the commands and options. Short form -h; running cadcode with no command does the same.

Errors name the line in your program that caused them. Set the environment variable CADCODE_DEBUG=1 to print the full stack trace as well.