# Pegmatite Script Guide

Pegmatite Script (`.pegm`) builds 3D models from solids placed against other solids and their faces. This guide alone covers common printed parts: plates, boxes, brackets, knobs, hooks. The [topic index](https://pegmatite.dev/guide/start.md#topics) at the end says what every further topic adds; fetch one only when a task needs it.

## How a script works

- Z is up, X right, Y back; the front is −Y. Lengths are millimeters, angles degrees.
- A script is a sequence of solids. Each solid is built centered on X and Y with its base at Z 0, then placed against the previous volume: **on top of it by default**.
- Build the way a part is assembled: a base, then each feature attached to the volume or face it belongs to. Prefer anchors, faces and alignment to calculated coordinates.
- Comments run from `//` to the end of the line or inside `/* … */`. Whitespace separates words; indentation means nothing. Keep prefixes attached: `$width`, `%boss`, `@top`, `#front`, `!color`, `base:`.
- Numbers are `10`, `0.5` or `-2.5`; `.5` and `2pi` are errors. Arithmetic needs parentheses, `($width / 2)`; a sign alone needs none, `-$width`. Functions: `sin cos tan sqrt abs min max round`, and the constant `pi`. Strings use double quotes.

## Variables

```pegm
set $width 40
set $wall 2
block $width 30 ($wall * 3)
```

`set $name value` before use. Keep design dimensions as variables at the top and derive the rest in parentheses.

## Solids

| Command                                                                            | Makes                                                                     |
| ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| **`cube`** _size_                                                                  | cube                                                                      |
| **`block`** _width_ _depth_ _height_                                               | box along X, Y and Z                                                      |
| **`cylinder`** _diameter_ _height_                                                 | upright cylinder                                                          |
| **`sphere`** _diameter_                                                            | sphere                                                                    |
| **`tube`** _outer-diameter_ _inner-diameter_ _height_                              | hollow cylinder                                                           |
| **`prism`** _sides_ [**`diameter`** \| **`edge`** \| **`across`**] _size_ _height_ | regular prism, a flat facing front; `prism 6 across 13 7` is a nut pocket |
| **`torus`** _outer-diameter_ _inner-diameter_                                      | ring lying flat                                                           |

## Placement

```pegm
block 40 30 10
@right cube 10      // beside the block, touching its right side
@right 5 cube 10    // beside the previous cube, with a 5 mm gap
```

Anchors `@left @right @front @back @top @bottom` name the side of the previous volume the new one goes to, with an optional gap. `@center` places it centered inside. An axis setter aligns and offsets on one axis, independently of the anchor:

| Setter                       | Points                    |
| ---------------------------- | ------------------------- |
| **`x`** [_align_] [_offset_] | `left`, `center`, `right` |
| **`y`** [_align_] [_offset_] | `front`, `center`, `back` |
| **`z`** [_align_] [_offset_] | `bottom`, `center`, `top` |

- `x right` makes the right sides flush. An offset runs along the positive axis whatever it follows: `x left 4` is 4 mm inward from the left edge, `x right 4` 4 mm outward past the right edge.
- **An alignment lines up edges, not centers.** To place a solid's center, name both points as `reference:volume`: `x left:center 8` puts the center 8 mm in from the left edge, `x right:center -8` 8 mm in from the right edge; the same with `y front:center` and `y back:center`. This is how a dimensioned hole position is written.
- A setter applies to the next solid only and stands directly before it. `!` makes it persistent, `!@right 2` builds a row; `unset` clears it, `!x unset`. `color` takes an HTML color name or a hex value such as `#ff0000` and follows the same rules.

## Faces

```pegm
block 50 50 10
on #top x left 5 y front 5 cube 10   // on the top face, near its front-left corner
on #front x right -4 cylinder 6 4    // standing out of the front face
```

- **`on`** _reference_ builds the next solid against a face of the previous volume, `on #top`, `#bottom`, `#left`, `#right`, `#front`, `#back`, or of a labelled one, `on base #front`. The solid stands on that face and grows outward, centered unless aligned with `x` and `y`.
- On a face, `x` and `y` are the face's own axes as seen from outside: `x left` and `x right` are its left and right edges, `y front` and `y back` its lower and upper edges.
- The first solid on a face takes no side anchor; align it with `x` and `y`. Solids after it follow each other as usual.
- Faces are the faces of the solid's **bounding box**. On an L-shape a feature aligned on a face can land over empty space: build such parts from labelled blocks and attach each feature to the block it belongs to.

## Lists and labels

```pegm
base: block 60 40 5 [
  on #top x left 6 cylinder 8 20
  on #top x right -6 cylinder 8 20
]
@right cube 10               // still placed against the base block
on base #front cylinder 4 6  // a label reaches the base from anywhere
```

Every item of `[ … ]` starts from the volume before the bracket, and after `]` that volume is the reference again. A nested list builds on its item. A label, `base:`, names a volume so `on base #face` and the measure `base.width` can come back to it.

## Holes and cuts

```pegm
block 60 20 10
on #top x left:center 10 -cylinder 5 10    // a 5 mm hole, 10 mm from the left end
on #top x right:center -10 -cylinder 5 10
```

- A sign before a solid combines it with the material built so far: `-` subtracts, `&` keeps the intersection, `^` keeps what does not overlap. A plain solid adds.
- A signed solid placed on a face reaches **into** the material from that face. Make a through hole at least as long as the material is thick.
- A cut applies to everything built before it in the same context; braces open a context of their own. To cut one piece only, put that piece and its cut in braces. A signed solid cannot be the first one in a script, braces or part.
- After a cut, the cut's space is the reference for the next solid; return to the body with a list or a label. A subtraction that removes no material warns: the hole is in the wrong place.

A hole in a side face selects the face and positions along its axes. A cylinder on a side face already points along the face's normal:

```pegm
plate: block 20 4 60
on plate #front y front:center 12 -cylinder 4.5 4   // center 12 mm above the lower edge
on plate #front y back:center -12 -cylinder 4.5 4   // center 12 mm below the upper edge
```

Holes set symmetrically a margin in from the edges are one pattern with `inset`. It spans the face, the outermost centers the margin in from its edges and the rest evenly between:

```pegm
block 95 60 10
on #top grid 2 2 inset 9 -cylinder 5.5 10   // four corner holes, centers 9 mm from the edges
```

| Pattern                                                   | Holes                                        |
| --------------------------------------------------------- | -------------------------------------------- |
| **`grid`** _columns_ _rows_ [**`inset`** _margin_] _body_ | columns × rows, corners included             |
| **`linear inset`** _margin_ _count_ _body_                | a row along X, the end centers the margin in |
| **`mirror`** _axis_ [**`inset`** _margin_] _body_         | a pair, each center the margin from its edge |

Without `inset`, the patterns space centers by a distance or gap; see [patterns](https://pegmatite.dev/guide/patterns.md).

## Compounds and parts

```pegm
{
  block 40 30 4
  on #top x left block 4 30 30
} | fillet 1 at parallel y
```

Braces fuse several solids into one, with its own placement and its own cuts, placed and modified as a whole. Use them to scope a cut and before a modifier that must apply to the whole group. `part "Lid"` divides the script into separately exported pieces, each with its own cuts; placement carries on across parts.

## Shapes and sweeps

| Shape or sweep                                                                                | Makes                                                                     |
| --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| **`circle`** _diameter_                                                                       | circle, centered on its origin                                            |
| **`rect`** _width_ _height_                                                                   | rectangle, centered on its origin                                         |
| **`polygon points [`**_x_ _y_**`]`**…                                                         | outline in mm, Y up; values may be variables or expressions               |
| **`path`** _SVG-path_                                                                         | SVG path string, numbers only; SVG Y points down                          |
| **`text`** _text_                                                                             | lettering; `font-size` sets the capital height, 10 by default             |
| **`\| round`** _radius_                                                                       | round the corners of an outline, written before the sweep                 |
| **`\| offset`** _distance_                                                                    | grow an outline, or shrink it by a negative distance                      |
| _2d-shape_ **`\| extrude`** _height_ [**`wall`** _thickness_] [**`inside`** \| **`outside`**] | raise a shape; `wall` raises only its outline, an open box or frame       |
| _2d-shape_ **`\| revolve`**                                                                   | turn a profile about Z: X is the radius, Y the height; draw it with X ≥ 0 |

```pegm
set $w 40
set $h 20
polygon points [0 0] [$w 0] [$w 4] [4 4] [4 $h] [0 $h] | extrude 15
@right 10 { circle 30 -circle 10 } | extrude 4
```

A hook or clip that is not made of blocks is drawn as its side outline with `polygon points` and extruded by its width; it then lies on its side, which prints well. A plain plate is better as `block … | fillet r at parallel z` than `rect | round | extrude`: the same look, exact in STEP.

## Modifiers

Written after `|`, in order, before the solid is placed.

| Modifier                                               | Effect                                             |
| ------------------------------------------------------ | -------------------------------------------------- |
| **`\| fillet`** _radius_ [**`at`** _edge-selector_]    | round edges, all of them without `at`              |
| **`\| chamfer`** _distance_ [**`at`** _edge-selector_] | bevel edges                                        |
| **`\| shell`** _thickness_                             | hollow out, keeping the bottom and opening the top |
| **`\| rotate`** _angle_ **`about`** _axis_             | turn about `x`, `y` or `z`, then place             |
| **`\| flip`** _axis_                                   | mirror                                             |
| **`\| cut`** _lower-level_ _upper-level_               | keep the slab between two heights, `cut 0 50%`     |

Edge selectors after `at`: `#top` and the other faces select the edges around that face, `parallel z` the upright edges, `#top and #front` the one edge two faces share, `z > 5` edges above a height; combine with `and`, `or`, `not`. A selector that matches nothing warns and is skipped.

```pegm
block 60 40 20 | fillet 4 at parallel z | shell 2 | fillet 0.8 at #top
```

A rounded open box: round the upright edges, hollow it with 2 mm walls, then soften the rim. Round and bevel before rotating, and shell before rounding the rim.

## Patterns

| Pattern                                    | Arrangement                                                      |
| ------------------------------------------ | ---------------------------------------------------------------- |
| **`repeat`** _count_ _body_                | continues the sequence: each copy is placed against the previous |
| **`linear`** _distance_ _count_ _body_     | centers spaced along X                                           |
| **`circular`** _diameter_ _count_ _body_   | around a circle, each copy turned to face out                    |
| **`grid`** _columns_ _rows_ [_gap_] _body_ | rows and columns                                                 |
| **`mirror`** _axis_ [_gap_] _body_         | the body and its mirror image side by side                       |

The body is one statement; use braces for several solids. `with index $i` before the body names the iteration number, from 0. The pattern leaves the box over all its copies as the reference for the next solid.

```pegm
block 80 20 6
on #top linear 15 4 -cylinder 5 6     // 4 holes, centers 15 apart, along X
```

```pegm
cylinder 60 8
on #top circular 44 6 -cylinder 5 8   // 6 holes on a 44 mm circle
```

## Macros and conditions

```pegm
def %boss $d {
  cylinder $d 4
  cylinder ($d / 2) 8
}

block 40 30 20 [
  on #top %boss 12
  on #front %boss 8
]
```

`def %name $parameter … { … }` defines a reusable compound; a one-statement body needs no braces. Call it by name with its arguments. Written upright in its own frame, a macro works on any face. `if $n == 1 statement` builds its statement when a comparison holds, `else statement` otherwise.

## Check, inspect and export

With Node.js 22 or newer, check the script before rendering anything:

```sh
npx --yes pegmatite --check --strict model.pegm
```

The report gives the model size and part count, then one row per **labelled** volume with its size, its X, Y and Z spans and its line. A hole's center is the middle of its spans. Label the pieces and holes whose position matters, compare their rows with the request, and fix the script before rendering. A cut that removes nothing is a warning, and strict mode fails on it.

```pegm
base: block 40 20 4
on base #top x right:center -10 hole: -cylinder 4.5 4
```

reports `hole: 4.5 × 4.5 × 4  x 7.75..12.25 …`: the hole is centered at x 10, 10 mm from the right edge at x 20. Render an image only when a question about shape or appearance remains: `-o model.png --view top`, or `front`, `iso`. Export by naming the output, `npx --yes pegmatite model.pegm -o model.3mf`; `.stl` and `.step` work the same way.

## Recipes

Plate with rounded corners and four screw holes:

```pegm
set $w 80
set $d 50
set $t 4
set $hole 4.5
set $inset 8
block $w $d $t | fillet 6 at parallel z
on #top [
  x left:center $inset y front:center $inset -cylinder $hole $t
  x right:center -$inset y front:center $inset -cylinder $hole $t
  x left:center $inset y back:center -$inset -cylinder $hole $t
  x right:center -$inset y back:center -$inset -cylinder $hole $t
]
```

Open box with rounded corners:

```pegm
block 60 40 25 | fillet 5 at parallel z | shell 2
```

L-bracket with a screw hole in each leg, each leg its own labelled block:

```pegm
set $leg 40
set $t 4
set $width 20
base: block $leg $width $t
on base #top x left upright: block $t $width ($leg - $t)
on base #top x right:center -10 hole1: -cylinder 4.5 $t
on upright #right y back:center -10 hole2: -cylinder 4.5 $t
```

Knob with a hex nut pocket and a bolt hole:

```pegm
knob: cylinder 40 12 | fillet 2 at #top
on knob #bottom -prism 6 across 13.2 6.5
on knob #bottom -cylinder 8.4 12
```

Wall hook: a plate with two screw holes and a J-hook made from half a tube turned to lie across the width:

```pegm
set $width 20
set $t 4
set $hook 30
plate: block $width $t 60
on plate #front y back:center -10 -cylinder 4.5 $t
on plate #front y back:center -32 -cylinder 4.5 $t
on plate @bottom y back:back tube $hook ($hook - 2 * $t) $width | rotate 90 about y | cut 0 50%
```

## Common mistakes

- Forgetting that each solid sits on the previous one: a second `block` lands on top of the first. Use a list, a label or an anchor.
- A cut that reaches further than intended: everything earlier in the same context is cut. Scope it with braces.
- Arithmetic without parentheses: `$w / 2` must be `($w / 2)`.
- Placing a hole by its edge: `x left 8` puts the hole's edge 8 mm in; for its center write `x left:center 8`. The report shows where it landed.
- Expecting `path` to take variables: use `polygon points` for sized profiles.
- A `-` on the first solid of a script, braces or part.
- Rounding the rim before `shell`: shell first, then fillet the top edges.

## Formatting

One solid per line with its setters and modifiers. Indent the contents of `[ … ]` and `{ … }` by two spaces; the closing bracket aligns with its opening statement.

## Topics

Each topic is the complete reference for its area; its row lists what it holds.

| Topic                                                 | Contents                                                                                                                                                                                                                                                                               |
| ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Syntax, settings and scope](https://pegmatite.dev/guide/syntax.md)               | name and string rules, escapes, percentages, statement forms; `!setter`, `unset`, the reach of a setting through lists, braces, macros and parts; `color` names and hex, export by color                                                                                               |
| [Placement, faces and frames](https://pegmatite.dev/guide/placement.md)           | `@anchor [gap]`, `@center`; `x\|y\|z point[:point] [offset]`, `0% 50% 100%`; `on [label] #face`, `on root`, `on [#a #b]`; face axes per face; build planes; `#bounds.top`; face names after `rotate`; labels on compounds; list and compound isolation                                 |
| [Booleans, slots and parts](https://pegmatite.dev/guide/booleans.md)              | `- & ^` in full, which end meets, boolean scope, the slot as next reference, `slot solid`, zero-sized volumes; `part "Name"`: own cuts, duplicate names, empty parts, export objects                                                                                                   |
| [Expressions, functions and measures](https://pegmatite.dev/guide/expressions.md) | `set`, variable scope, operators, `and or not`; `sin cos tan asin acos atan atan2 sqrt exp log abs min max round floor ceil lerp curve`, `pi`; `label.width\|depth\|height`; `width(shape)`, `height(shape)`                                                                           |
| [Solids reference](https://pegmatite.dev/guide/solids.md)                         | `cube block cylinder sphere cone tube torus`; `prism\|pyramid n [diameter\|edge\|across] size height`; `starprism spikes d1 d2 height`; `tetrahedron octahedron dodecahedron icosahedron [edge\|diameter\|across] size`; `mesh vertices [x y z]… faces [i j k]…`; numbered faces `#f1` |
| [Solid modifiers and edge selection](https://pegmatite.dev/guide/modifiers.md)    | `rotate angle about axis`, `scale f\|x y z`, `flip axis`, `shell t [open\|closed]`, `cut low high`; `fillet r [at sel]`, `chamfer d1 [d2] [at sel]`; selectors `#face`, `#a and #b`, `parallel axis`, `x\|y\|z op value`, `nearest x y z`, `tangent sel`, `and or not ( )`             |
| [Macros and conditionals](https://pegmatite.dev/guide/macros.md)                  | `def %name $p … { … }`, braceless bodies, shape macros, define before use, variables fixed at definition, recursion; `if test stmt [else stmt]`                                                                                                                                        |
| [Patterns](https://pegmatite.dev/guide/patterns.md)                               | `repeat n`, `linear d n`, `circular d n [angle a] [start a]`, `linear inset m n`, `grid cols rows [gap\|inset m [my]]`, `mirror axis [gap\|inset m]`, all with `with index $i`; centers not gaps; what each pattern leaves; grid cells; two mirror planes                              |
| [2D shapes](https://pegmatite.dev/guide/shapes.md)                                | `circle d`, `rect w h`, `ellipse dx dy`, `star n d1 d2`, `polygon points [x y]…` or `polygon n [diameter\|edge\|across] size`, `path "svg"`; faces as shapes `#top`, `label #top`                                                                                                      |
| [2D operations and groups](https://pegmatite.dev/guide/shape-operations.md)       | `offset d [round]`, `round r`, `fill`, `rotate deg`, `flip axis`, `scale f [fy]`; `{ shape … }` groups with `- & ^`, placing a shape against the previous one                                                                                                                          |
| [Extrude and revolve](https://pegmatite.dev/guide/extrude.md)                     | `extrude h [wall t [inside\|outside]] [twist deg] [size f] [apex [x y [z]] \| to shape]`; `revolve [d] [angle a] [start a] [about axis] [shrink]`                                                                                                                                      |
| [Helix, sweep and varying profiles](https://pegmatite.dev/guide/sweeps.md)        | `helix d [d2] [pitch p] [turns n \| height h] [start a] [about axis] [shrink]`; `sweep along trajectory [about axis] [round]`; varying profiles `%macro args \| sweep`, `% $t shape \| sweep`, `curve()`                                                                               |
| [Text and fonts](https://pegmatite.dev/guide/text.md)                             | `text "…"`, `font-size s`, `font family [weight]`, Google Fonts and font files, lettering as cutter or relief                                                                                                                                                                          |
| [Tilings](https://pegmatite.dev/guide/tilings.md)                                 | `honeycomb\|squares\|triangles d strut [angle a] [rim w] in shape`, `stripes w strut …`, `voronoi strut cells n [seed s] [relax p] [rim w] in shape` or `voronoi strut points [x y]… …`                                                                                                |
| [CLI](https://pegmatite.dev/guide/cli.md)                                         | `-o --check --strict --slow --quiet --view --size`, stdin; reading the report, spans as placement checks; previews; 3MF, STL and STEP; fonts from disk; exit codes                                                                                                                     |
