Language Reference
Overview
PolyScript is a pipe-based language for parametric CAD modeling. Shapes are created with primitives and transformed through a chain of pipe operations.
box 80 60 10 | fillet 2 | diff cylinder 10 10
When you write multiple shapes on separate lines, they are automatically combined (unioned) into a single shape.
box 10 10 10
sphere 8
# → the two shapes are automatically unioned
To combine shapes explicitly, use union [...].
Primitives
3D
box width height depth
cylinder radius height
sphere radius
cone r1 r2 height
torus r1 r2
wedge dx dy dz ltx
cone r1 r2 h creates a frustum. r1 is the bottom radius, r2 is the top radius, h is the height. Setting r2 to 0 produces a full cone.
cone 10 5 20 # frustum (bottom R10, top R5, height 20)
cone 10 0 20 # full cone
torus r1 r2 creates a donut shape. r1 is the major radius (center to tube center), r2 is the minor (tube) radius.
torus 20 5 # major radius 20, tube radius 5
wedge dx dy dz ltx creates a wedge (tapered box). The base is dx x dz, the top narrows to ltx x dz, and dy is the height.
wedge 20 10 15 5 # base 20x15, top 5x15, height 10
Position Convention
3D primitives and extrude follow different placement rules along the Z axis. Mixing them without care is a classic source of misalignment.
| Operation | Z-axis placement | Example (height 10) |
|---|---|---|
box, cylinder, sphere, cone, torus |
Centered at origin | z = -5..+5 |
extrude h |
Bottom-aligned (z=0 upward) | z = 0..10 |
In other words, the bottom face of box 10 10 10 sits at z=-5, while rect 10 10 | extrude 10 starts at z=0. When you put both in the same scene, their bottoms don’t line up.
Three ways to fix this:
| floor– Snap the bottom to z=0 (the easiest)center:(true,true,false)– Disable centering on the Z axis only| translate 0 0 h/2– Manually shift upward
# WRONG: want to stack a lid (extrude) on a body (box), but bottoms mismatch
$body = box 80 60 100 # z = -50..+50
$lid = rect 80 60 | extrude 20 # z = 0..20
# $lid floats near the middle of the body
# RIGHT: use floor to align the body, then stack
$body = box 80 60 100 | floor # z = 0..100
$lid = rect 80 60 | extrude 20 # z = 0..20
$body | union ($lid | translate 0 0 100) # lid on top
2D
rect width height
circle radius
ellipse rx ry
polygon n r
polyline points
text content size
sketch [segments]
2D primitives produce faces. Use | extrude, | cut, | revolve, or | loft to create solids.
2D shapes come in two types:
| Type | Produced by | Meaning |
|---|---|---|
| Face | rect circle ellipse polygon polyline text sketch [...], the result of a 2D boolean or offset |
A region with area. May contain holes |
| Wire | wire [...] line arc bezier spline helix |
A curve. Has no area even when closed |
The type is fixed by the command and never changes afterwards. extrude, cut, revolve, loft and the 2D booleans (diff union inter) require a Face and raise an error on a Wire. The only way to turn a Wire into a Face is offset d, which gives the line a thickness of 2|d|. Conversely, wherever a curve is needed – the spine or profile of sweep – a Face stands in for its outline.
text converts a text string into 2D faces; the inner contours of glyphs such as “O” and “A” become holes. The CLI picks up a system font (DejaVu Sans, Liberation Sans, Noto Sans, Roboto or Arial, in that order); in the browser the host supplies the font. Without one, text falls back to a rectangular placeholder.
polygon n r creates a regular n-gon inscribed in a circle of radius r. polyline points creates a closed wire from a vertex list.
polygon 6 10 # regular hexagon (inscribed circle radius 10)
polyline [(0,0), (10,0), (5,10)] # closed wire from vertices
sketch – Composite wire
Combine lines, arcs, and Bezier curves to create a closed 2D profile of any shape. Useful for shapes that rect or circle cannot express.
sketch [
(5, 0), # start point (tuple)
arc (5, 0) (0, -5) (-5, 0), # arc: start → through → end
(0, 7), # line (tuple only)
(5, 0) # line (returns to start, auto-close)
]
Segment types:
| Syntax | Meaning |
|---|---|
(x, y) |
Line from the previous point |
arc (sx, sy) (mx, my) (ex, ey) |
3-point arc (start, through, end) |
arc (sx, sy) (ex, ey) center:(cx, cy) |
Center arc (start, end, center) |
arc (sx, sy) (ex, ey) radius:radius |
Radius arc (start, end, radius; center is computed automatically) |
bezier [(x1,y1), ...] |
Bezier curve (control point list; start point is implicit from the preceding segment, last element is the end point) |
The first element must be a tuple (the start point). If the last segment returns to the start point, the wire closes automatically. When the start point of an arc does not match the end point of the preceding segment, a straight line is automatically inserted to bridge the gap. The result can be turned into a solid with extrude or cut.
Which arc should I use?
| Goal | Recommended |
|---|---|
| Specify three points (start, through, end) | arc start through end |
| Draw a precise arc given center and end point | arc start end center:(cx,cy) |
| Round a corner with just radius and end point | arc start end radius:radius |
# Extrude a teardrop shape
sketch [
(5, 0),
arc (5, 0) (0, -5) (-5, 0),
(0, 7),
(5, 0)
] | extrude 10
# D-shaped hole
$r = 3
sketch [
(-$r, $r),
(-$r, -$r),
arc (-$r, -$r) (0, -$r - 1) ($r, -$r),
($r, $r),
(-$r, $r)
]
# True quarter arc (center specified)
sketch [
($r, 0),
arc ($r, 0) (0, $r) center:(0, 0),
(0, 0),
($r, 0)
]
# Rounded rectangle (radius form is the most concise)
$w = 20
$h = 10
$cr = 2
sketch [
($w/2 - $cr, -$h/2),
arc ($w/2 - $cr, -$h/2) ($w/2, -$h/2 + $cr) radius:$cr,
($w/2, $h/2 - $cr),
arc ($w/2, $h/2 - $cr) ($w/2 - $cr, $h/2) radius:$cr,
(-$w/2 + $cr, $h/2),
arc (-$w/2 + $cr, $h/2) (-$w/2, $h/2 - $cr) radius:$cr,
(-$w/2, -$h/2 + $cr),
arc (-$w/2, -$h/2 + $cr) (-$w/2 + $cr, -$h/2) radius:$cr
] | extrude 5
Paths
line start end
arc start through end
arc start end center:(cx,cy)
arc start end radius:radius
bezier points
helix pitch height radius
spline points
spline creates a smooth B-spline curve that passes through the specified points. It can be used as the spine of sweep.
bezier treats the specified points as control points. The curve passes near the control points but does not go through any of them except the start and end points. If you need a smooth curve that passes exactly through your points, use spline.
spline [(0,0,0), (10,5,5), (20,0,10)] | sweep (circle 3)
Note: The list convention for
bezier/splinediffers by context. As standalone commands (bezier [...] | sweep, etc.), the list includes the start point as its first element. Insidesketch/wiresegments, however, the start point is implicitly inherited from the preceding segment’s endpoint (the current point), so the list contains only the control/through-points and the end point. This follows the industry-standard convention used by CadQuery and SVG paths.
Pipe Operations
Operations are chained with |:
box 50 50 10 | fillet 2 | diff cylinder 5 10
Modifiers
| fillet expr # fillet all edges
| chamfer expr # chamfer all edges
| shell expr # hollow out; select face first to remove it
| offset d # offset wire or face outline (positive = outward, negative = inward)
box 100 60 40
| edges =Z | fillet 3
| faces >Z | shell 2
fillet also works on 2D wires, letting you create rounded 2D profiles:
rect 40 20 | fillet 3 | extrude 10 # extrude a rounded rectangle
offset works in two contexts:
- Face selection context: Extracts the outline of the selected face, creates a workplane, and offsets the outline. → Face
- Face: Scales the outline similarly (
+outward,-inward). A face with holes cannot be offset. → Face - Wire: Gives the line a thickness. An open wire becomes a band of width
2|d|(round ends by default,cap:"square"for flat ends); a closed wire becomes a ring of width2|d|. → Face
# Face selection → offset → cut
box 80 60 10
| faces >Z
| offset -10
| cut 3
# Face → offset
rect 80 60 | offset -10 | extrude 5
# Wire → offset: turn a polyline into a band 4 wide, then extrude
wire [(0,0), (40,0), (40,30)] | offset 2 | extrude 5
Color
Apply color to a shape. Colors are reflected per-part on export (glTF/GLB/STEP/OFF).
| color "red" # named color
| color "#FF0000" # hex color (string literal)
| color "#F00" # hex shorthand
| color 0.8 0.2 0.1 # RGB float (0..1)
| color 255 128 0 # RGB int (0..255, auto-normalized)
| color "red" alpha:0.5 # with transparency
There are three ways to specify colors:
- Named colors:
"red","blue","steel", etc. - Hex colors:
"#FF0000"or shorthand"#F00"(must be written as a string literal since#is the comment character) - RGB values: Three numbers. If all are 1 or below, they are treated as 0..1 range; if any exceeds 1, they are auto-normalized as 0..255 range
The alpha: keyword argument sets transparency (0..1, default 1.0).
You can also assign different colors to individual parts:
union [
box 10 10 10 | color "red"
cylinder 15 3 | color "blue"
]
Named color palette
Three tiers of color names are available:
| Tier | Color names |
|---|---|
| Basic (16 colors) | red, green, blue, yellow, cyan, magenta, orange, purple, white, black, gray/grey, brown, pink, lime, navy, teal |
| CAD material colors | silver, gold, steel, copper, brass, aluminum, darkgray/darkgrey, lightgray/lightgrey |
| CSS Named Colors | steelblue, coral, darkslategray, and all other CSS-compliant color names |
An unknown color name will produce an error.
Boolean
| diff shape # subtract shape
| union shape # add shape
| inter shape # intersect with shape
Shapes can be placed with at::
box 50 50 10
| diff cylinder 3 10 at:15 15
Shape operators + - *
Inside an expression, shapes can be combined with + (union), - (diff) and * (inter). They mean exactly what the pipe operations union/diff/inter mean (a list on the right is applied in order; two Faces give a 2D boolean). Both operands must be solids, or both Faces; mixing them, as in (rect 10 10) + (box 5 5 5), is a type error in every spelling. Precedence follows arithmetic – * first – and all three bind tighter than the pipe |. Set-operation precedence differs between tools, and a - (b * c) is a different shape from (a - b) * c, so parenthesise whenever * is mixed with +/-.
base = box 80 60 10
holes = [cylinder 3 20 at:(20, 15), cylinder 3 20 at:(-20, 15)]
base - holes | edges >Z | fillet 2 # (base - holes) | fillet 2
lid = (body | faces >Z | offset -2 | extrude 3) - pins
plate = (rect 40 20) - ((circle 4) * (rect 10 10)) # rect minus (circle ∩ rect)
The operators only work between variables and parenthesised expressions. They cannot follow a command’s arguments: box 10 10 10 - x is argument arithmetic, box 10 10 (10 - x). Right after a command, use the pipe form.
box 10 10 10 - cylinder 3 20 # ✗ parse error
(box 10 10 10) - (cylinder 3 20) # ✓
box 10 10 10 | diff (cylinder 3 20) # ✓ reads better
Place
Place a 2D shape stored in a variable into the pipeline. Like diff/union/inter, it accepts variable references or inline 2D primitives as arguments.
| place $shape # place a 2D shape from a variable
| place (circle 3) # inline 2D primitive
After selecting a face, you can place a predefined profile for cutting or extruding. Useful when reusing the same shape multiple times.
$s = sketch [(5,0), arc (5,0) (0,-5) (-5,0), (0,7), (5,0)]
box 10 10 10 | faces >Z | place $s | cut
box 10 10 10 | faces >Z | place $s | extrude 5
$profile = rect 5 5
box 10 10 10 | faces >Z | place $profile | cut
box 10 10 10 | faces >Z | place (circle 3) | cut
Extrude / Revolve / Sweep / Loft
| extrude height draft:angle
| revolve axis [deg] # axis: X/Y/Z, deg defaults to 360
| sweep profile # pipeline subject is the spine (a wire); argument is the profile (a wire)
| loft [sections] height
| loft [sections] [offsets]
| loft [sections] height ruled:true
rect 60 40 | extrude 15
rect 10 30 at:(15, 0) | revolve Y
circle 5 | loft [rect 8 8] 10
circle 5 | loft [rect 8 8, circle 3] [5, 15]
draft: specifies a draft angle (taper) in degrees. A positive value widens outward in the extrusion direction.
ruled:true interpolates between sections with straight lines (the default is a smooth surface).
revolve – Solid of revolution
Revolves a 2D profile around the specified axis to create a solid.
| revolve axis # full 360° revolution (axis: X, Y, or Z)
| revolve axis deg # revolve by specified angle
The profile must be placed entirely on one side of the rotation axis. A profile that straddles the axis (e.g., circle 20 centered at the origin) will produce a degenerate shape error. Use at:(x, y) to offset the profile from the origin, or draw a wire on one side of the axis with sketch.
# Ring (torus-like)
rect 10 30 at:(15, 0) | revolve Y
# Vase / bowl (half revolution)
sketch [(0, 0), (15, 0), (15, 3), (3, 20), (0, 20)]
| revolve Y 180
# Revolution around the X axis
circle 5 at:(0, 20) | revolve X
sweep – Path sweep
Sweeps the argument profile (cross-section) along the pipeline spine (trajectory) to create a 3D shape. The spine is a Wire (open or closed); the profile may be a Face (closed cross-section) or a Wire (open cross-section). When a Face is given, its outline is used (a face with holes is not accepted). “Spine” and “profile” name the role a shape plays; there is no path type.
Syntax: spine | sweep profile
- Open wires:
line,arc,bezier,spline,helix,wire [...] - Closed outlines (Faces):
circle,rect,ellipse,polygon,polyline,sketch [...]
The open/closed combination of spine and profile determines the resulting shape:
| spine | profile | Result |
|---|---|---|
| open | closed | Tubular solid (primary use case) |
| closed | closed | Closed-loop tube (torus-like) |
| open | open | Strip surface (thread grooves, etc.) |
# Tubular solid: sweep a circle along a curved path
arc (0, -25) (25, 0) center:(0, 0) | sweep (circle 5)
# Torus: major radius 50, minor radius 10
circle 50 | sweep (circle 10)
# Pipe along a spline
spline [(0,0,0), (10,5,5), (20,0,10)] | sweep (circle 3)
Note:
sketch [...]always auto-closes (it produces a closed wire), so it cannot be used as an open spine for sweep. When you need an open multi-segment wire, use thewire [...]literal or path primitives such asline/arc/spline.
wire – Open wire literal
The open counterpart to sketch [...]. Joins multiple segments into an open wire (no auto-close). Perfect for building a complex sweep spine in a single expression.
wire [
segment1,
segment2,
...
]
Segments use the same syntax as sketch (except tarc). Both 2D and 3D coordinates are supported.
| Syntax | Meaning |
|---|---|
(x, y) or (x, y, z) |
Line from the previous point |
line (start) (end) |
Line segment |
arc (start) (through) (end) |
3-point arc |
arc (start) (end) center:(cx,cy) |
Center arc |
arc (start) (end) radius:radius |
Radius arc |
bezier [...] |
Bezier curve (start point is implicit from the preceding segment; list contains control points and the end point) |
spline [...] |
B-spline curve (start point is implicit from the preceding segment; list contains through-points and the end point) |
How wire differs from sketch:
| sketch | wire | |
|---|---|---|
| Auto-close | yes | no |
| Type | Face | Wire (curve) |
| Output | A face; can be extruded/cut | An open curve; never a face even when closed |
| Coordinates | 2D | 2D/3D |
| Primary use | extrude/revolve/cut | sweep spine |
# Basic: line + arc combined into a path
wire [
(0, 0),
(10, 0),
arc (10, 0) (15, 5) radius:5,
(15, 20)
]
# Combined with sweep: L-shaped pipe (wire is the spine, circle is the profile)
wire [
(0, 0),
(10, 0),
arc (10, 0) (15, 5) radius:5
] | sweep (circle 5)
# 3D wire -- just use 3D tuples
wire [(0, 0, 0), (10, 0, 5), (20, 10, 10)]
# Workplane + wire: draw a wire on the XZ plane, then sweep with a circle profile
workplane XZ | wire [(0,0),(10,0),(10,10),(0,10),(0,0)] | sweep (circle 2)
Like sketch, wire can receive a workplane via pipe. This lets you draw wires on any plane, not just the default XY.
Sweep usage guide
| Purpose | Recommended |
|---|---|
| Spine, single segment | line/arc/helix/spline (path primitives) |
| Spine, multiple segments | wire [...] literal |
| Profile (closed cross-section) | circle/rect/… or sketch [...] |
| Profile (open cross-section) | wire [...] |
Cut / Hole
| cut depth # cut through or to depth
| hole radius depth:d # drill hole (radius, not diameter)
hole can be used from both FaceSelection and PointSelection.
- From FaceSelection: Drills a hole at the center of the selected face.
faces >Z | hole 5is equivalent tofaces >Z | circle 5 | cut. When multiple faces are selected, a hole is drilled at the center of each. - From PointSelection: Drills a hole at each point position (the traditional behavior).
Omitting depth: creates a through-hole.
Use at: to specify the hole position. Use origin: to control the coordinate reference (see “Coordinate Reference” below).
# From face selection: hole at face center
box 80 60 10 | faces >Z | hole 5
# From point selection: hole at each point
box 80 60 10 | faces >Z | points (polar 4 15) | hole 5
# Hole at workplane coordinate (10,20)
box 80 60 10 | faces >Z | hole 5 at: 10 20
# Hole at world origin, projected onto face
box 80 60 10 | faces >Z | hole 5 at: 0 0 origin:"world"
# Hole at world coordinate (10,20,0) — 3 components = world
box 80 60 10 | faces >Z | hole 5 at: 10 20 0
rect 60 40 | extrude 15
| faces top
| rect 40 20 | cut 10
Face / Edge / Vertex Selection
| faces selector # select faces
| edges selector # select edges
| verts selector # select vertices
Selectors use short symbols or name aliases:
| Symbol | Meaning | Name alias |
|---|---|---|
>Z |
maximum (top) | top |
<Z |
minimum (bottom) | bottom |
>X |
maximum (right) | right |
<X |
minimum (left) | left |
>Y |
maximum (back) | back |
<Y |
minimum (front) | front |
=Z |
parallel to Z | |
=X |
parallel to X | |
=Y |
parallel to Y | |
+Z |
perpendicular to Z (= side faces) | |
+X |
perpendicular to X | |
+Y |
perpendicular to Y |
Note:
+Zselects faces whose normal is perpendicular to Z – i.e., side faces, not the top or bottom. To select the top or bottom face, use>Z/<Z.
Compound selectors
Multiple selectors separated by spaces act as AND (intersection):
| edges >Z >X # edges at both Z-max and X-max
Wrap selectors in list syntax for OR (union):
| edges [>Z, <Z] # edges at Z-max or Z-min
Tagging with as
You can name faces or edges with as for later reference:
| faces <X as $left
| edges =Z as $top_edges
Face selection creates an implicit workplane, so you can pipe 2D primitives directly:
box 50 50 10
| faces top
| circle 5 | cut
| edges <Z | fillet 1
In a Face/Wire context, verts returns the vertices of the current shape as a Vertex selection.
Vertex selection supports both 2D and 3D primitives – each primitive is placed at every vertex position:
box 80 60 10
| faces top
| rect 70 50 | verts | circle 1 | cut
3D primitives work the same way. Each vertex becomes a placement point:
rect 100 100 | verts | box 1 1 1 # place a box at each of the 4 vertices
rect 100 100 | verts | sphere 2 # place a sphere at each of the 4 vertices
Vertex selection supports translate to offset all vertex positions while staying in the selection context. Points selection (points) also supports this:
rect 80 60 | verts | translate 10 10 10 | cone 2 0 6
box 80 60 10 | faces >Z | points (polar 4 15) | translate 5 5 0 | hole 3
Workplane / Points
Face selection implicitly creates a workplane for 2D operations. Use explicit workplane only when you need a specific orientation:
| workplane XZ # explicit workplane with axis
| points (polar 6 20) # arrange points in circle
| points (grid nx ny 20) # arrange points in grid (3rd arg = pitch)
As a source command, workplane sets the initial plane for 2D primitives, sketch, and wire:
workplane XZ | circle 10 | extrude 10 # circle on the XZ plane
workplane XZ | sketch [...] | extrude 5 # sketch on the XZ plane
workplane XZ | wire [...] | sweep (circle 2) # wire (spine) on the XZ plane
The plane names are XY XZ YZ and their negations -XY -XZ -YZ. AB reads “local x is world A, local y is world B”; -AB is the same plane with the same local axes and the normal flipped. The drawing lands in the same place; only extrude/cut/hole go the other way, so use the negated name to extrude toward the negative side of an axis. The - must touch the name (workplane - XY is a subtraction). The reversed spellings (ZX etc.) are errors that point to the signed name.
| name | local x | local y | normal (extrude direction) |
|---|---|---|---|
XY (default) |
+X | +Y | +Z |
-XY |
+X | +Y | −Z |
XZ |
+X | +Z | +Y |
-XZ |
+X | +Z | −Y |
YZ |
+Y | +Z | +X |
-YZ |
+Y | +Z | −X |
workplane XZ | circle 10 | extrude 10 # extrudes toward +Y
workplane -XZ | circle 10 | extrude 10 # extrudes toward -Y (same drawing, normal flipped)
There is no syntax for a named plane with an arbitrary normal. To draw on a tilted surface, select a face of a solid (faces hands the face normal to the workplane).
# hole from face selection (hole at face center)
cylinder 30 5 | faces top | hole 3
# hole from point selection (hole at each point)
cylinder 30 5
| faces top
| points (polar 4 10)
| hole 3
polar / grid shorthand
In a FaceSelection or PointSelection context, you can pipe polar / grid directly, omitting points.
# shorthand (usable after face selection)
cylinder 30 5 | faces top | polar 4 10 | hole 3
box 80 60 10 | faces top | grid 3 2 15 | circle 2 | cut
The above is equivalent to:
cylinder 30 5 | faces top | points (polar 4 10) | hole 3
box 80 60 10 | faces top | points (grid 3 2 15) | circle 2 | cut
In a 3D context, polar / grid behave as array duplication as before. Note that the meaning changes depending on the context.
Transform
| translate x y z
| rotate rx ry rz # solid: 3 angles (world axes), none may be omitted
| rotate a # 2D shape (Face/Wire): 1 angle, about the plane normal
| scale sx sy sz # non-uniform scale (per axis)
| scale s # uniform scale (all axes)
| mirror "X"/"Y"/"Z" # reflect; keep:true fuses with the original
| floor # align bottom face to z=0
| move dx dy # 2D offset on workplane
| moveto x y # 2D absolute position
mirror reflects across the plane perpendicular to the specified axis. "X" mirrors across the YZ plane, "Y" across the XZ plane, "Z" across the XY plane. The original is not kept; keep:true returns the original fused with its reflection, and origin: sets a point on the mirror plane (same forms as rotate). On 2D shapes on a workplane only "X" / "Y" apply, and the shape is mirrored within its plane.
box 30 20 10 | mirror "X" # reflect across the YZ plane
half | mirror "Z" keep:true # a symmetric whole from one half
box 30 20 10 | mirror "X" origin:(15, 0, 0) # reflect across the plane x = 15
floor – Align bottom to z=0
Translates any 3D shape so that its bounding box minimum Z (zmin) sits at z=0. Takes no arguments.
| floor
Internally this is equivalent to translate 0 0 -bbox.zmin. When you want to bottom-align a centered primitive, | floor is a one-pipe alternative to center:(true,true,false).
# Bottom-align a centered box
box 10 10 10 | floor # z=-5..+5 → z=0..10
# Works on spheres too
sphere 15 | floor # z=-15..+15 → z=0..30
# Works on compound shapes
union [box 20 20 10, cylinder 5 20] | floor
Note:
flooris a pipe operation (it moves a shape). It is not the same as the math functionfloor(x)(which truncates a number toward negative infinity).
move offsets the drawing position relative to the current point on the workplane. moveto moves to an absolute position. Use origin: to change the coordinate reference (see “Coordinate Reference” below).
box 80 60 10
| faces top
| move 10 10 | circle 5 | cut # hole offset (10,10) from face center
| moveto 30 20 | rect 5 5 | cut # pocket at (30,20) from workplane origin
# World XY coordinates projected onto face
| moveto 10 20 origin:"world"
# Arbitrary world point as new origin, then relative move
| moveto 5 5 origin:(10,20,0)
# World XY delta
| move 10 0 origin:"world"
How many arguments rotate takes
The count depends on what is being rotated and is never padded. A solid takes exactly
three angles, rotate rx ry rz (write rotate 0 90 0 to turn about one axis); a 2D
shape on a workplane takes exactly one, rotate a, turning in its plane (it cannot be
tilted out of it). rotate 0 90 is an error. A workplane with nothing drawn on it
cannot be rotated.
rect 10 5 | rotate 45 # 2D: 45 degrees in the plane
faces >Z | rect 10 5 | rotate 30 | cut 3 # turn a sketch on a face, then cut
box 10 10 10 | rotate 0 0 45 # 3D: 45 degrees about Z
A 2D shape never leaves the workplane it was drawn on: rotate 90 0 0 cannot stand it
up and translate cannot lift it (poly check reports both). To put a path or an
outline in a vertical plane (XZ / YZ), draw it there from the start, or use 3D points:
workplane XZ | wire [arc (0,-25) (25,0) center:(0,0)] | sweep (circle 5) # arc path in the XZ plane
arc (0,0,-25) (25,0,0) center:(0,0,0) | sweep (circle 5) # the same path in 3D points
move / moveto do not move a drawn shape either: they move the cursor for the next
drawing. Place a shape when drawing it, e.g. rect 10 10 at:(5, 5).
origin: keyword argument
translate, rotate, and scale accept the origin: keyword argument to specify the reference point.
For a 2D rotate the default is the workplane origin, "local" is the centre of the 2D
shape, and a 2-component point is in workplane coordinates.
| Value | Meaning | Default |
|---|---|---|
"world" |
Use world origin (0,0,0) | yes |
"local" |
Use object bounding box center | |
(x, y, z) |
Use an arbitrary point |
# Rotate around world origin (default)
box 10 10 10 | translate 20 0 0 | rotate 0 0 45
# Rotate around object center
box 10 10 10 | translate 20 0 0 | rotate 0 0 45 origin:"local"
# Rotate around an arbitrary point
box 10 10 10 | rotate 0 0 45 origin:(10, 20, 0)
# Uniform scale (2x from origin)
box 10 10 10 | scale 2
# Non-uniform scale
box 10 10 10 | scale 2 1 0.5
# Scale from object center
box 10 10 10 | translate 20 0 0 | scale 3 origin:"local"
move, moveto, hole, and primitive at: also accept origin:. See the next section for details.
Coordinate Reference (at: and origin:)
When specifying a position with at:, the coordinate interpretation depends on the number of components by default. Use origin: to override explicitly.
Default reference (when origin: is omitted):
| Components | Reference | Example |
|---|---|---|
2-component at: x y |
Workplane coordinates (WP origin) | circle 5 at: 10 20 |
3-component at: x y z |
World coordinates (projected onto face normal) | circle 5 at: 10 20 0 |
Explicit override:
origin: value |
Meaning |
|---|---|
"world" |
Use world origin (0,0,0). Even 2-component coordinates are interpreted as world coordinates |
"local" |
Use workplane origin (same as the 2-component default) |
(ox, oy, oz) |
Use an arbitrary world point as the new origin; at: is interpreted as relative WP coordinates from that point |
World coordinates (3-component, or 2-component with origin:"world") are projected onto the current workplane face along its normal direction.
origin: can be used with the following operations:
- Primitives with
at::circle,rect,ellipse,polygon,box,cylinder,sphere,cone,torus,wedge - Hole drilling:
hole(combined withat:) - Cursor movement:
moveto,move - Transform:
translate,rotate,scale
# 2-component at: uses workplane origin (default)
faces >Z | circle 5 at: 10 20 # placed at WP (10,20)
# 3-component at: uses world coordinates (default)
faces >Z | circle 5 at: 10 20 0 # world (10,20,0) projected
# origin:"world" makes 2-component world-relative too
faces >Z | circle 5 at: 10 20 origin:"world" # world XY (10,20) projected
# origin:(ox,oy,oz) sets an arbitrary world point as origin
faces >Z | circle 5 at: 5 5 origin:(10,20,0) # relative WP (5,5) from (10,20,0)
# Same applies to hole
faces >Z | hole 5 at: 0 0 origin:"world" # hole at world origin
Variables
The $ prefix on variable names is optional. Both $w = 80 and w = 80 work. Reserved words (box, cylinder, if, for, etc.) cannot be used as variable names.
w = 80
h = 60
box w h 10
# $-prefixed form also works (backward compatible)
$w = 80
box $w $h 10
@param Annotation
Adding @param to a variable turns it into a parameter that can be controlled via a GUI customizer as a slider or dropdown. Write it on the line immediately before the variable declaration.
@param 10..200 step:5 desc:"Box width"
$width = 80
box $width 60 10
This alone displays a slider in the GUI ranging from 10 to 200 in increments of 5.
Basic syntax
@param options...
$variable_name = default_value
Range shorthand
A concise way to write common min/max/step patterns:
| Notation | Meaning |
|---|---|
@param 1..100 |
min:1 max:100 |
@param 1..100..0.5 |
min:1 max:100 step:0.5 |
@param -10..10..1 |
min:-10 max:10 step:1 |
You can also append options after the shorthand:
@param 1..100 desc:"Height"
Option reference
| Key | Type | Description |
|---|---|---|
min |
number | Minimum value (slider lower bound) |
max |
number | Maximum value (slider upper bound) |
step |
number | Step size (slider increment) |
label |
string | Display label (defaults to variable name if omitted) |
desc |
string | Description text (tooltip) |
choices |
list | Choice list (dropdown) |
group |
string | GUI group name (default: "General") |
type |
string | Type hint: "int", "float", "string", "bool" |
hidden |
boolean | true to hide from GUI |
If type is omitted, it is inferred from the default value (42 → int, 2.5 → float, "PLA" → string, true → bool).
Choices (dropdown)
Use choices to display a dropdown menu:
@param choices:["M3", "M4", "M5", "M6"] desc:"Bolt size"
$bolt = "M4"
Practical example
@param 40..200 step:5 group:"Dimensions" desc:"Case width"
$case_w = 100
@param 30..150 step:5 group:"Dimensions" desc:"Case depth"
$case_d = 60
@param 1..5 step:0.5 group:"Wall" desc:"Wall thickness"
$wall = 2
@param type:"bool" group:"Features" desc:"Add ventilation holes"
$vents = true
box $case_w $case_d 40
| color "steel"
| faces >Z | shell $wall
CLI Override
The poly build command’s -D key=value option lets you override parameters at build time. Use --params-file to load values from a JSON file.
poly build model.poly -D width=100 -D height=50
poly build model.poly --params-file presets/large.json
-D values are type-inferred (100 -> int, 1.5 -> float, true -> bool, PLA -> string). When the same key is specified in multiple sources, the precedence order is:
- CLI
-D --params-file(flat key-value JSON)@paramdefault values
Passing an unknown parameter name via -D produces a warning but does not stop execution.
See Getting Started for a hands-on walkthrough.
Functions
Single-expression functions with def:
def standoff($r, $h, $hole_r) = cylinder $r $h | diff cylinder $hole_r $h
box 80 60 3
| union standoff 4 10 1.5 at:10 10
Import
Load function definitions from another .poly file:
import "gear"
spur_gear 12 2 | extrude 8
Imports are resolved relative to the importing file’s directory.
Expressions
Arithmetic
+ - * / // % **
Note: In greedy argument parsing,
+/-are interpreted based on whitespace.-5(attached) is a negative number,10 - 5(spaces on both sides) is binary subtraction (= 5), and10 -5(space before, attached after) is two arguments (10 and -5). When in doubt, use parentheses.
Comparison and Logic
== != < > <= >=
and or
Conditional
if condition then expr else expr
if/then/else is an expression, so it can be used inline:
$use_round = true
$shape = if $use_round then circle 10 else rect 20 15
$shape | extrude 5
List Comprehension
[$expr for $var in range($n)]
Practical examples:
# Generate 6 boxes spaced 15mm apart
union [box 10 10 10 at:($i * 15, 0) for $i in range(6)]
# Concentric circles
[$r * 5 for $r in range(1, 6)]
Nested comprehensions are also supported:
# Generate a 2D grid
[[$i + $j * 10 for $i in range(3)] for $j in range(2)]
# → [[0, 1, 2], [10, 11, 12]]
Math Functions
sin, cos, tan, asin, acos, atan, atan2, sqrt, radians (alias: rad), degrees (alias: deg), floor, ceil
Constant: pi
Tuples and Lists
(10, 20, 30) # tuple
[(0,0), (10,5), (20,0)] # list
Boolean Operations (Source Commands)
union, diff, and inter can be used as source commands (without a pipe) to combine multiple shapes:
union [box 10 10 10, sphere 7] # combine shapes
diff [box 10 10 10, sphere 7] # subtract second from first
inter [box 10 10 10, sphere 7] # keep only the intersection
They also work as pipe operations, as before:
box 10 10 10 | union (sphere 7)
box 10 10 10 | diff (cylinder 3 20)
Note: [] is always a list literal. To combine shapes, use union [...] explicitly. Inside expressions the shape operators a + b / a - b / a * b are available too (see “Shape operators”).
Placement
at: is a named argument that places a shape at the specified position. Write it with a colon : immediately after at.
Parentheses rules:
- Simple coordinates (literals/variables): parentheses can be omitted (recommended).
at:20 0 0 - With expressions: parentheses required.
at:($x+1, $y+1) - Coordinate list: brackets required.
at:[(0,0), (10,0)] - Parenthesized
at:(x, y)is always valid. - For array duplication, use the
| gridor| polarpipe operations.
sphere 5 at:20 0 0 # simple position (omit parentheses)
sphere 5 at:$x $y # variables are fine too
sphere 5 at:($x+1, $y+1) # expressions require parentheses
sphere 5 at:[(0,0), (10,0), (20,0)] # multiple positions (brackets required)
sphere 5 | polar 6 20 # circular array (pipe operation)
sphere 5 | grid 3 3 20 # grid array (pipe operation)
Similarly, the angle: named argument can be used to rotate a shape:
rect 10 5 angle:45 # rectangle rotated 45 degrees
Comments
# This is a comment