---
name: implement
description: Use when authoring or editing a brepjs `.brep.ts` part — writing the geometry with the functional API (box, cylinder, fuse, cut, fillet, sketch→extrude…), declaring an `expected` block, and following the hard rules (import every function, unwrap Results, select edges, coordinate semantics). Also covers buildings/BIM/IFC via the declarative family layer (references/families-bim.md). This is the authoring step; pair it with brepjs:verify to check the result.
version: 0.1.0
---

# Author a brepjs part

You write a `.brep.ts` part; the `brep` CLI runs it on a geometry kernel and reports what it
measured. Judge the part by the report (see `brepjs:verify`), not by how the code reads. This skill
is **self-contained** — everything needed to author correctly is here or in `references/`.

The CLI ships in the `brepjs-cad` package as `brep`. Installed: `brep verify part.brep.ts …`.
Otherwise: `npx -y -p brepjs-cad brep verify …`.

## Authoring contract

- `export default () => <shape>` (or `async () => {…}` to `await loadFont`/`importSTEP`).
- Short functional API (`box`, `cylinder`, `fuse`, `cut`, `fillet`, …), named consts at the top.
- Scaffold with `brep init <name>`. **Edit source, never generated artifacts** (STEP/STL/GLB derive
  from the `.brep.ts`).

## Realize the designed object — not just a valid one

`--check` passing means **buildable**, not **correct**. The bar is the part the brief names, recognisable
as _that_ designed object — and the way that fails is **simplification**: a valid, generic version that
drops the one feature that makes it itself.

1. Decompose the brief into named features, then mark the **ONE defining feature** — the geometry without
   which it's a generic blob. `GT2 pulley` → the belt-tooth profile (a smooth groove is not a GT2 pulley);
   `fluted knob` → full-height flutes around the **whole** perimeter (scattered scallops are not flutes);
   `twisted impeller`/`swept fan` → a **cambered** airfoil section extruded **radially** with a pitch twist
   (`references/airfoils.md`), not flat blades or `twistAngle` on a paddle; `scroll chuck` →
   the spiral face groove; `involute gear` → the tooth flank (`references/gears.md`).
2. Build that feature **to spec**, not an eyeballed approximation. Each of the above passes `--check` while
   missing its headline feature — a blob that verifies. If the feature needs real math (gear/thread/involute/
   scroll), use the reference recipe; don't substitute a smooth or sparse stand-in.
3. Before finishing, re-read the brief noun by noun and confirm each named feature is actually in the
   geometry — including count (e.g. _four_ mount holes, _N_ teeth), not just present-ish.

## Choose the operation (reliability tiers)

Prefer ops that succeed first-try; lean on the report and small steps for advanced ops. Full table:
**`references/operation-tiers.md`**. In short: primitives, booleans, `compound`, sketch→extrude,
`fillet`, `shell`/`offset`, transforms are reliable; sweeps/lofts/revolves/`fuseAll`/text are
advanced; `chamfer` is the fragile exception (prefer `fillet`).

## Declare intent — the `expected` block

Add `export const expected = { … }` from the brief; the CLI asserts it, catching valid-but-wrong
sizing. The **only** authorable keys are `volume`, `area`, `bounds`, `tolerancePct` (each optional;
`tolerancePct` sets the match window) — `TOP_LEVEL_KEYS` in `src/verify/expected.ts:45`. Bounds
shape is exactly `{ xMin, xMax, yMin, yMax, zMin, zMax }` (any subset) — **not** `{ min, max }` or
`{ x, y, z }` (a wrong shape reports `EXPECTED_UNKNOWN_KEY`). **`shapeType` is report-only, not
authorable**: the report tells you whether the part measured as a `solid`/`compound`/etc., but
putting `shapeType` (or any other field) in `expected` also reports `EXPECTED_UNKNOWN_KEY` — assert
the body count or shape via `volume`/`bounds`, never a `shapeType` key.

**Prefer `bounds`** over a hand-computed `volume` (a wrong number fails a _correct_ part). Predict
only extents you **place directly** — a footprint, where each body sits, the flat face of a body you
placed there — these read off your datums and catch a dropped/misplaced body. An extent **governed by a
rotation, a part's orientation, a proud sub-feature, a half-space clip, or the outer top/bottom of a
deep multi-body stack** is not a datum: bound it generously or measure-first (run once, copy the
report's measured value). That last one is the #1 `EXPECTED_ASSERTION_FAILED` on assemblies — a
stack's extreme z is usually crowned by a rounded/proud feature (a carrier hub, a ball cap) and sums
every body's placement error, so **measure it; don't hand-add the stack**. A flat lid-on-base height
you place is fine; the moment a curved or proud sub-feature defines the extreme, it's governed. This
was the #1 first-try failure across the corpus (rotated handles, articulated yokes, flange discs,
clipped balls): when an operand is **rotated**, a **disc/sphere** crowns an axis, or a cut **clips** an
extreme, measure that one axis — don't predict it.

A **`chamfer`/`fillet` only REMOVES material** — it never grows the bounding box. A beveled or rounded
outer corner keeps the original face plane as its bound, so the extent stays at the un-chamfered face:
predict `xMin = 0` for a corner chamfered at `x = 0`, never `xMin = -chamfer`.

An extent is a datum only if you place that **face** directly. A **derived** extent is not — and these
are the other half of the `EXPECTED_ASSERTION_FAILED`s: a body **translated** beside another (its far
edge is `offset ± its own half-extent`, not the offset), a face/foot **widened to overlap** a neighbour
for fusing (its outer edge is the widened size, not the nominal feature length), or a `cylinder`/`cone`
given a non-default **`axis`** (its far end is `base + axis·length`, e.g. base `x=-15`, axis `-X`,
length `14` → `xMin=-29`). Compute these from the SAME const that places the geometry, or measure-first.

## Hard rules

- **Import every function you call.** No globals — every op is a named export from `'brepjs'`. A
  used-but-unimported symbol is `TS2304: Cannot find name` and fails `--check` before geometry runs
  (the #1 first-attempt failure). Re-scan the body before finishing.
- **Transforms are free functions, shape-first** — `translate(shape, [x,y,z])`, `rotate(shape, deg, { axis })`,
  `mirror`, `scale` (angles in **degrees**), the same shape-first form as booleans. They are NOT methods:
  `shape.translate(...)` is `TS2339: Property 'translate' does not exist`. Placing assembly parts needs
  these even when the brief doesn't shout "transform". (`references/transforms.md`.)
- **Unwrap Results.** Booleans and `measureVolume`/`measureArea` return `Result`: `unwrap(cut(...))`
  and check the `Err` branch before chaining. `TS2322: Result<X> is not assignable to X` (on an
  assignment/return) — or **`TS2345`** when you feed an un-unwrapped `Result` straight into another
  op's argument (e.g. `fuse(a, cut(b, c))`) — means an op (`cut`/`fuse`/`fillet`/`chamfer`/`shell`/…)
  was used without `unwrap()`. Unwrap at every step, **including the final return** — a `Result`
  default export (`export default cut(...)`) slips past `verify` (it auto-unwraps a `Result` default
  export, so `--check` is green) but renders **nothing** in a viewer/mesh path. Always `unwrap()` the
  returned shape.
- **`fuse` welds only where solids overlap.** Bodies merely touching on a coplanar face/ring may
  return a loose `Compound` (`ok:true`, not one watertight solid). Overlap the operands +
  `fuseAll(shapes, { unsafe: true })` to weld; use `compound` for a distinct-bodies assembly. **For
  MANY operands** (a grille, a stud grid, a space frame), `fuseAll` unsafe routinely leaves a loose
  N-solid compound even with overlap — fold with a **pairwise `fuse()` reduce** over real overlaps and
  confirm `getSolids(part).length === 1`.
  (`references/booleans.md`.) **But a `Compound` result is NOT a failure to chase:** even genuinely
  overlapping operands often fuse to `shapeType:Compound` (`ok:true`, correct geometry) rather than a
  single `Solid` — and that's fine, because `shapeType` is report-only/non-authorable and
  bounds/volume/validity still pass. Don't burn attempts trying to force a `Solid`; only do so (and
  then only worry) when a **downstream `fillet`/`shell`/`offset`** needs a `ValidSolid` (next rule).
  The report's `notes` flags a **multi-body Compound and its solid count** — if a part you meant as
  ONE piece comes back as N bodies, the weld failed (overlap + `fuseAll` unsafe); a count matching a
  deliberate assembly is fine.
- **`fillet`/`chamfer`/`shell`/`offset` need a `ValidSolid`.** Primitives already are one and
  booleans preserve it, so a primitive-rooted chain feeds them directly. A shape from a 2D-sketch
  `.extrude()`/`.revolve()` (or `loft`/`sweep`) is typed `Shape3D`; passing it to these ops is
  `TS2345`. Lift in two steps: `if (!isSolid(x)) throw …; const solid = unwrap(validSolid(x));`. Or
  build the prism from a primitive when you know you'll `fillet`/`shell` it. (`references/modifiers.md`.)
- **A loose-`Compound` boolean can't be lifted to `ValidSolid` — fix the boolean, don't lift.** When
  `fuse`/`fuseAll` only touch (don't overlap) they return a `Compound` (`ok:true`), and `isSolid`
  returns `false` on it (`shapeType()==='compound'`, not `'solid'` — `src/core/shapeTypes.ts:248`).
  There is **no lift** from a multi-body `Compound` to a `ValidSolid`: `validSolid()` needs one
  `Solid`, so `if (!isSolid(x)) throw …` just throws, and feeding the Compound onward
  `KERNEL_FAILED`s. Make the operands actually overlap and `fuseAll(shapes, { unsafe: true })` so the
  weld yields a single solid **first**, then `fillet`/`shell`. (`references/booleans.md`.)
- **Select edges/faces; don't fillet/chamfer everything.** `fillet(solid, radius)` with no edge
  list rounds EVERY edge and frequently `FILLET_FAILED`s. Pass `edgeFinder().inDirection('Z').findAll(solid)`.
  `inDirection` matches BOTH ± orientations; discriminate by position with
  `.when(f => getBounds(f).zMax > t)` (`getBounds` is its own import). Finders take a **direction**
  (`'X'`/`'Y'`/`'Z'`/`Vec3`), never a plane: a top face is `faceFinder().inDirection('Z')`;
  **`parallelTo('XY')` fails `--check`** — use `'Z'`. (`references/modifiers.md`.)
- **`chamfer` is kernel-fragile.** `CHAMFER_FAILED` is common even with a correct edge list. Prefer
  `fillet`, model the bevel additively (`cut` with an angled tool), or drop it. Re-running the same
  chamfer rarely helps.
- **A _through_ hole/slot/mortise needs a tool proud of BOTH faces.** Size the cutting
  `cylinder`/`box` LONGER than the body and place it so it pokes out each end (e.g. height = wall + 2,
  positioned past both faces) — a tool flush with or short of a face leaves a **blind pocket**, not a
  through feature, and `--check` can't catch it (the part is still valid). The brief word "through"
  (or "bore", "passage") is the cue. Confirm it actually passes through with the `section`/xray view,
  not just the exterior.
- **`revolve` angle is RADIANS** (`Math.PI * 2` = full turn). Build a revolve profile with
  `polygon(points3D)`, not `draw().close().sketchOnPlane('XZ').face()` (fails `--check`).
  (`references/sketching-2d.md`.)
- **`box(width, depth, height)`** — depth is Y, height is Z, mm. **`at` sets the geometric CENTER**:
  bare `box(w,d,h)` is corner-at-origin, `{ centered: true }` centers on origin, `{ at:[x,y,z] }`
  centers there. `cylinder`/`cone` `at` is the **base** center; `sphere` `at` is its center.
- **No half-sphere primitive:** clip a full `sphere` to a half-space with `intersect` (a `box` over
  the half you want). Fusing a whole sphere bulges past a cap face. See the `dome-cap` example.
- **Pattern angles are DEGREES** (`circularPattern`/`rectangularPattern` `fullAngle`), _unlike_
  `revolve` (radians). Check the unit per op.
- **Parts may be `async`**: `export default async () => {…}` is awaited — `await loadFont(...)`
  (required before any `sketchText`/`drawText`) or `await importSTEP(...)`. `--check` type-checks
  Node built-ins, so a part may `import { readFile } from 'node:fs/promises'`.
- Author in ESM (the tool's default) so the kernel loads.

## Reference index (load only what the task needs)

`references/getting-started.md` · `primitives.md` · `sketching-2d.md` · `booleans.md` ·
`modifiers.md` · `transforms.md` · `measurement-validation.md` · `assemblies-motion.md` ·
`operation-tiers.md`. Maker recipes: `fdm-conventions.md` · `mechanical-joints.md` ·
`gridfinity.md` · `gears.md` · `threads.md` · `airfoils.md` (fans/props/impellers/vanes).
Buildings/BIM/IFC: `families-bim.md` (declarative components → viewport meshes + IFC export).
**Backstop:** any symbol not covered →
`reference/llms-full.txt` (every export with signatures), bundled in the package.

## Examples index (read the closest before authoring)

Each is a complete `examples/<name>.brep.ts` + `<name>.expected.json` baseline.

- **Primitives + booleans:** `mounting-bracket` · `flanged-coupler` · `transform-bracket` · `dome-cap`.
- **2D sketch → solid:** `extruded-bracket` · `revolved-pulley` · `swept-gasket`.
- **Modifiers:** `rounded-block` (fillet) · `chamfered-block` (API shape only; chamfer is fragile) ·
  `hollow-enclosure` (shelled).
- **Mechanical:** `spur-gear` (polygon→extrude, BOSL2-faithful) · `threaded-rod` (loft sections).
- **Gridfinity:** `gridfinity-baseplate` · `gridfinity-bin` · `gridfinity-divider`.
