brepjs-verify 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2052 @@
1
+ # brepjs
2
+
3
+ > Web CAD library with a layered architecture and pluggable kernel abstraction layer (supports OpenCascade and brepkit WASM backends). Create 2D sketches, extrude/revolve/loft/sweep into 3D solids, apply booleans/fillets/chamfers/shells, query topology, measure geometry, heal shapes, manage assemblies, and import/export STEP, STL, IGES, glTF, DXF, 3MF, OBJ, and SVG.
4
+
5
+ ## API Reference & Discoverability
6
+
7
+ - **Hosted API docs**: https://andymai.github.io/brepjs/ — searchable TypeDoc reference for all exports
8
+ - **Function lookup table**: `docs/function-lookup.md` — alphabetical index mapping every symbol to its sub-path
9
+ - **Which API guide**: `docs/which-api.md` — choosing between Sketcher, functional API, Drawing
10
+
11
+ ## Authoring with an AI Agent (brepjs-verify)
12
+
13
+ If you are an LLM generating brepjs CAD, use `brepjs-verify` — a CLI that runs your model on a real kernel and returns a deterministic report. **Judge a part by the report, never by how the code reads.** Published on npm as `brepjs-verify`; the companion Claude Code skill installs via `/plugin marketplace add andymai/brepjs` then `/plugin install brepjs-verify@brepjs`.
14
+
15
+ Install the runtime where `brepjs` resolves (it also bundles its own `brepjs` + `occt-wasm`, so it runs standalone):
16
+ ```bash
17
+ npm i -D brepjs-verify
18
+ ```
19
+
20
+ A model is a `.brep.ts` module: a default-exported zero-arg function returning a shape (or `Result<shape>`), optionally with an `expected` block that the CLI asserts:
21
+ ```ts
22
+ import { box } from 'brepjs';
23
+ export const expected = { volume: 8000, tolerancePct: 1 }; // optional: volume|area|bounds + tolerancePct
24
+ export default () => box(40, 20, 10, { centered: true });
25
+ ```
26
+
27
+ The loop, in order: brief → author `.brep.ts` → declare `expected` → `brepjs-verify verify part.brep.ts --check --json report.json` → review `--snapshot shots/` → repair the smallest responsible section → `--step part.step` and hand off. Use `npx -y brepjs-verify …` if not installed.
28
+
29
+ The report (stdout JSON) is the source of truth:
30
+ - `ok` — `true` only if valid AND every assertion passes. `false` → not done.
31
+ - `checks` — kernel validity (manifold solid, positive volume). A failed check = broken geometry.
32
+ - `measurements` — measured `volume`/`area`/`bounds`.
33
+ - `assertions` — declared intent vs reality. A failed assertion = valid-but-wrong (off dimensions).
34
+ - `hints` — actionable fix + next step keyed on an error `code`; read before guessing.
35
+ - `errorInfos` — raw `{code,message}` failures (authoring/kernel/export). Cite the `code` when repairing.
36
+
37
+ CLI subcommands: `verify <file>` (default; flags `--check`, `--json`, `--step`, `--glb`, `--snapshot <dir>`, `--serve`), `init <name>` (scaffold), `watch <file>`, `export <file>` (`--step`/`--glb`/`--stl`/`--all` behind a validity gate), `measure <a> [b]`, `diff <a> <b>`. Exits non-zero when not `ok`.
38
+
39
+ Reliable first-try: primitives, booleans, 2D sketch → extrude, fillet/chamfer, shell/offset, transforms. Advanced (verify carefully, expect iteration): sweeps, lofts, revolves, multi-section, assemblies, text. Author parts in ESM (a CJS project needs `"type":"module"` or a `.mts` file); Node 24+ strips the part's types natively.
40
+
41
+ ## Setup & Initialization
42
+
43
+ Install:
44
+ ```bash
45
+ npm install brepjs occt-wasm
46
+ ```
47
+
48
+ ### Quick start (zero ceremony, ESM only)
49
+
50
+ ```typescript
51
+ import { box } from 'brepjs/quick';
52
+ const myBox = box(10, 10, 10); // just works
53
+ ```
54
+
55
+ `brepjs/quick` auto-initializes the WASM kernel via top-level await. No setup code needed. Requires ESM (top-level await is incompatible with CJS).
56
+
57
+ ### Standard (explicit registration, works with CJS)
58
+
59
+ ```typescript
60
+ import { OcctKernel } from 'occt-wasm';
61
+ import { registerKernel, OcctWasmAdapter } from 'brepjs';
62
+
63
+ const kernel = await OcctKernel.init();
64
+ registerKernel('occt-wasm', OcctWasmAdapter.fromKernel(kernel));
65
+ ```
66
+
67
+ The kernel must be registered once before using any brepjs API. All shape-creating functions require the kernel to be initialized.
68
+
69
+ ### Custom or alternative WASM build
70
+
71
+ `initFromOC(oc)` accepts any OpenCascade WASM instance — it is not tied to `brepjs-opencascade`. You can use a custom or alternative WASM build as long as it exposes the standard OpenCascade API:
72
+
73
+ ```typescript
74
+ import { initFromOC, box } from 'brepjs';
75
+
76
+ // Use a custom OpenCascade WASM build
77
+ import customOC from 'my-custom-opencascade';
78
+ const oc = await customOC({ locateFile: (file) => `/custom-wasm/${file}` });
79
+ initFromOC(oc);
80
+
81
+ // All brepjs functions now use the custom kernel
82
+ const b = box(10, 10, 10);
83
+ ```
84
+
85
+ The kernel abstraction layer (`src/kernel/`) translates all brepjs calls into OCCT operations. Any WASM build exposing the standard OpenCascade C++ API (via Emscripten bindings) is compatible. This enables: custom WASM builds with additional OCCT modules, builds optimized for specific use cases (smaller footprint, specific features), and self-hosted WASM files served from your own CDN.
86
+
87
+ ### Quick reference
88
+
89
+ See `docs/cheat-sheet.md` for a single-page reference covering all common operations (primitives, booleans, transforms, fillets, measurement, export, memory management, error handling).
90
+
91
+ Works in both Node.js and browsers. For browser setup with Vite, see `docs/getting-started.md` (Browser Setup section). Try the [interactive playground](https://brepjs.dev/playground) for live experimentation.
92
+
93
+ ## API Style
94
+
95
+ brepjs uses a functional API with branded types (`Solid`, `Face`, `Edge`, etc.) and `Result<T>` for error handling. Functions like `fuse()`, `translate()`, `fillet()` are pure — they never dispose inputs. Use `unwrap()` to extract values from `Result<T>`, or `isOk()`/`isErr()` to check success.
96
+
97
+ ### Immutability
98
+
99
+ All brepjs operations are immutable — they return new shapes and never modify the original:
100
+
101
+ ```typescript
102
+ import { box, translate, rotate, fuse, cylinder, measureVolume, unwrap } from 'brepjs';
103
+
104
+ const original = box(30, 20, 10);
105
+ const moved = translate(original, [100, 0, 0]);
106
+ const rotated = rotate(moved, 45, { axis: [0, 0, 1] });
107
+ const combined = unwrap(fuse(original, cylinder(5, 15)));
108
+
109
+ // original is unchanged after all operations
110
+ console.log(measureVolume(original)); // still the original box volume
111
+ console.log(measureVolume(moved)); // same volume, different position
112
+ console.log(measureVolume(rotated)); // same volume, different orientation
113
+ ```
114
+
115
+ This means you can safely chain multiple transformations while preserving access to intermediate and original shapes. Each call produces a new independent shape.
116
+
117
+ ### Branded Shape Types
118
+
119
+ Shape types are lightweight branded handles (not classes) with a phantom `D extends Dimension` parameter for compile-time 2D/3D safety (default `'3D'`):
120
+ - `Vertex<D>`, `Edge<D>`, `Wire<D>`, `Face<D>`, `Compound<D>` — dimension-parameterized (default `'3D'`)
121
+ - `Shell`, `Solid`, `CompSolid` — always 3D (no dimension parameter)
122
+ - `AnyShape<D>` — union of all shape types
123
+ - `Shape3D` — union of Shell | Solid | CompSolid | Compound<'3D'>
124
+ - `Shape1D<D>` — Edge<D> | Wire<D>
125
+
126
+ #### Validity Brands
127
+
128
+ Layered on top of base types to encode topological invariants at compile time:
129
+ - `ClosedWire<D>` — wire proven to form a closed loop. Required by `face()`.
130
+ - `OrientedFace<D>` — face with consistent normal. Required by `extrude()`, `revolve()`.
131
+ - `ManifoldShell` — shell proven to be watertight.
132
+ - `ValidSolid` — solid passing BRepCheck validation. Returned by `box()`, `cylinder()`, etc.
133
+
134
+ Smart constructors prove validity at runtime: `closedWire(w)`, `orientedFace(f)`, `manifoldShell(s)`, `validSolid(s)` → `Result<T, string>` (check with `isOk`, extract with `unwrap` — there is no `.valid`/`.value` field).
135
+ Type guards narrow in-place: `isClosedWire(w)`, `isOrientedFace(f)`, `isManifoldShell(s)`, `isValidSolid(s)`.
136
+ Convenience: `wireLoop(edges)` assembles edges and verifies closure → `Result<ClosedWire>`.
137
+
138
+ All shapes have `.wrapped` (kernel handle) and `.delete()`. Use `using` syntax for auto-cleanup.
139
+
140
+ Also exported: `Sketch`, `CompoundSketch`, `Sketches` (multi-profile container), `Drawing`, `DrawingPen`, `Blueprint`, `CompoundBlueprint`, `Blueprints`
141
+
142
+ ### Type Conversion Helpers
143
+
144
+ ```typescript
145
+ import { toVec3, toVec2, resolveDirection } from 'brepjs';
146
+
147
+ toVec3([x, y]): Vec3 // 2D → 3D (z=0)
148
+ toVec3([x, y, z]): Vec3 // identity
149
+ toVec2([x, y, z]): Vec2 // 3D → 2D (drop z)
150
+ resolveDirection('X'): Vec3 // → [1, 0, 0]
151
+ resolveDirection('Y'): Vec3 // → [0, 1, 0]
152
+ resolveDirection('Z'): Vec3 // → [0, 0, 1]
153
+ resolveDirection([a, b, c]): Vec3 // identity
154
+ ```
155
+
156
+ ### Shape Serialization
157
+
158
+ ```typescript
159
+ import { toBREP, fromBREP } from 'brepjs';
160
+
161
+ const brep = toBREP(shape); // shape → BREP string
162
+ const restored = fromBREP(brep); // BREP string → AnyShape
163
+ ```
164
+
165
+ ## Drawing & Sketching (Primary Entry Point)
166
+
167
+ Most workflows start by drawing a 2D profile, then extruding or revolving it into 3D.
168
+
169
+ ### DrawingPen (2D builder)
170
+
171
+ ```typescript
172
+ import { draw, drawRectangle, drawCircle, drawPolysides } from 'brepjs';
173
+
174
+ // Freeform drawing with a pen
175
+ const shape2d = draw()
176
+ .movePointerTo([0, 0])
177
+ .lineTo([10, 0])
178
+ .lineTo([10, 5])
179
+ .hLine(-5)
180
+ .vLine(5)
181
+ .close();
182
+
183
+ // Canned shapes
184
+ const rect = drawRectangle(100, 50);
185
+ const circle = drawCircle(25);
186
+ const hex = drawPolysides(10, 6);
187
+ ```
188
+
189
+ DrawingPen methods:
190
+ - `movePointerTo(point)` — Move without drawing
191
+ - `lineTo(point)`, `line(dx, dy)`, `hLine(d)`, `vLine(d)` — Straight lines
192
+ - `hLineTo(xPos)`, `vLineTo(yPos)` — Absolute line targets
193
+ - `polarLine(distance, angle)`, `polarLineTo(point)` — Polar coordinates
194
+ - `tangentLine(distance)` — Tangent continuation
195
+ - `threePointsArcTo(end, innerPoint)`, `threePointsArc(dx, dy, viaX, viaY)` — 3-point arcs
196
+ - `tangentArcTo(end)`, `tangentArc(dx, dy)` — Tangent arcs
197
+ - `sagittaArcTo(end, sagitta)`, `sagittaArc(dx, dy, sagitta)` — Sagitta arcs
198
+ - `vSagittaArc(d, sagitta)`, `hSagittaArc(d, sagitta)` — Vertical/horizontal sagitta arcs
199
+ - `bulgeArcTo(end, bulge)`, `bulgeArc(dx, dy, bulge)` — Bulge arcs
200
+ - `vBulgeArc(d, bulge)`, `hBulgeArc(d, bulge)` — Vertical/horizontal bulge arcs
201
+ - `ellipseTo(end, hRadius, vRadius, rotation?, longAxis?, sweep?)` — Elliptical arcs
202
+ - `halfEllipseTo(end, vRadius, sweep?)` — Half-ellipse arcs
203
+ - `bezierCurveTo(end, controlPoints)` — General Bezier
204
+ - `quadraticBezierCurveTo(end, control)` — Quadratic Bezier
205
+ - `cubicBezierCurveTo(end, c1, c2)` — Cubic Bezier
206
+ - `smoothSplineTo(end, config?)`, `smoothSpline(dx, dy, config?)` — Smooth splines
207
+ - `fillet(radius)`, `chamfer(radius)` — Corner treatments (call before next segment)
208
+ - `close()` — Close the path and return a Drawing
209
+ - `closeWithMirror()` — Close by mirroring the path
210
+ - `closeWithCustomCorner(radius, mode?)` — Close with a fillet or chamfer at the join. `mode`: `'fillet'` (default) or `'chamfer'`
211
+ - `done()` — Leave path open and return a Drawing
212
+
213
+ Drawing factory functions:
214
+ - `draw(origin?)` — Start a new pen at optional `[x, y]` point
215
+ - `drawRectangle(w, h, r?)` — Rectangle centered at origin, optional corner radius `r: number` or `{rx?, ry?}`
216
+ - `drawRoundedRectangle(w, h, r?)` — Alias for `drawRectangle`
217
+ - `drawCircle(radius)` — Circle centered at origin (built from two sagitta arcs)
218
+ - `drawSingleCircle(radius)` — Circle as a single curve primitive (more efficient, less compatible with corner ops)
219
+ - `drawEllipse(major, minor)` — Ellipse centered at origin (built from two half-ellipse arcs)
220
+ - `drawSingleEllipse(major, minor)` — Ellipse as a single curve primitive
221
+ - `drawPolysides(radius, sides, sagitta?)` — Regular polygon. `sagitta` curves the sides (positive = outward)
222
+ - `drawText(text, {startX?, startY?, fontSize?, fontFamily?})` — Text outline (requires `loadFont()` first)
223
+ - `drawPointsInterpolation(points, approxConfig?, {closeShape?})` — BSpline through Point2D[]. `approxConfig`: `{tolerance?, degMax?, degMin?, smoothing?}`
224
+ - `drawParametricFunction(fn, {pointsCount?, start?, stop?, closeShape?}, approxConfig?)` — Parametric curve `fn: (t: number) => Point2D`
225
+ - `drawProjection(shape, camera?)` — Project 3D shape to 2D. `camera`: `ProjectionPlane | Camera` (default `'front'`). Returns `{visible: Drawing, hidden: Drawing}`
226
+ - `drawFaceOutline(face)` — Extract face outer wire as a 2D Drawing
227
+ - `deserializeDrawing(data)` — Reconstruct from string produced by `drawing.serialize()`
228
+
229
+ Drawing instance methods:
230
+ - `clone()`, `serialize()` — Copy and serialization
231
+ - `boundingBox` — Get `BoundingBox2d`
232
+ - `repr` — String representation of the drawing
233
+ - `blueprint` — Access the underlying `Blueprint` (throws if compound)
234
+ - `translate(dx, dy)` or `translate([dx, dy])` — Move the drawing
235
+ - `rotate(angle, center?)` — Rotate in degrees around optional center point
236
+ - `scale(factor, center?)` — Uniform scale around optional center
237
+ - `mirror(dirOrCenter, origin?, mode?)` — Mirror. `mode`: `'center'` (default) or `'plane'`
238
+ - `stretch(ratio, direction, origin)` — Non-uniform scaling along a direction
239
+ - `cut(other)`, `fuse(other)`, `intersect(other)` — 2D boolean operations (return new Drawing)
240
+ - `fillet(radius, filter?)`, `chamfer(radius, filter?)` — Corner treatments. `filter`: `(c: CornerFinderFn) => CornerFinderFn`
241
+ - `offset(distance, config?)` — Offset all curves by distance
242
+ - `approximate('svg', options?)` — Approximate curves for SVG compatibility
243
+ - `sketchOnPlane(plane?, origin?)` — Convert to 3D Sketch/Sketches. Returns `SketchInterface | Sketches`
244
+ - `sketchOnFace(face, scaleMode)` — Project onto a face. `scaleMode`: controls UV mapping behavior
245
+ - `punchHole(shape, faceFinder, {height?, origin?, draftAngle?}?)` — Punch this 2D profile through a 3D shape
246
+ - `toSVG(margin?)` — Full SVG string with `<svg>` tag
247
+ - `toSVGViewBox(margin?)` — SVG string with viewBox attribute
248
+ - `toSVGPaths()` — Array of SVG path `d` strings
249
+
250
+ ### Sketcher (3D sketching on a plane)
251
+
252
+ ```typescript
253
+ import { Sketcher, sketchCircle, sketchRectangle } from 'brepjs';
254
+
255
+ // Sketch on XY plane, then extrude
256
+ const box = new Sketcher('XY')
257
+ .movePointerTo([-5, -5])
258
+ .lineTo([5, -5])
259
+ .lineTo([5, 5])
260
+ .lineTo([-5, 5])
261
+ .close()
262
+ .extrude(10);
263
+
264
+ // Canned sketch shortcuts
265
+ const cylinder = sketchCircle(10).extrude(20);
266
+ const prism = sketchRectangle(30, 20).extrude(15);
267
+
268
+ // Sketching on offset planes
269
+ const top = sketchCircle(5, { plane: 'XY', origin: 20 }); // XY plane at Z=20
270
+ const angled = sketchCircle(5, { plane: myCustomPlane }); // custom Plane object
271
+ ```
272
+
273
+ Sketcher has the same drawing methods as DrawingPen plus arc/ellipse/spline methods, closing with `.close()` or `.done()` to produce a `Sketch`.
274
+
275
+ Canned sketch functions — all accept an optional last argument `PlaneConfig = { plane?: PlaneName | Plane, origin?: PointInput | number }`. When `origin` is a number, it offsets the named plane along its normal by that distance:
276
+ - `sketchCircle(radius, planeConfig?)` — Circle sketch
277
+ - `sketchRectangle(w, h, planeConfig?)` — Rectangle sketch
278
+ - `sketchRoundedRectangle(w, h, r?, planeConfig?)` — Rounded rectangle. `r`: `number` or `{rx?, ry?}`
279
+ - `sketchPolysides(radius, sides, sagitta?, planeConfig?)` — Regular polygon
280
+ - `sketchEllipse(xRadius?, yRadius?, planeConfig?)` — Ellipse (defaults: xRadius=1, yRadius=2)
281
+ - `sketchHelix(pitch, height, radius, center?, dir?, lefthand?)` — Helix curve (no PlaneConfig; uses center/dir directly)
282
+ - `sketchFaceOffset(face, offset)` — Offset a face boundary (negative = inward, positive = outward)
283
+ - `sketchParametricFunction(fn, planeConfig?, {pointsCount?, start?, stop?}?, approxConfig?)` — Parametric curve on plane
284
+ - `polysideInnerRadius(outerRadius, sidesCount, sagitta?)` — Helper: compute inner radius of a polyside
285
+
286
+ Sketch instance methods:
287
+ - `extrude(distance, options?)` — Options: `{extrusionDirection?, extrusionProfile?, twistAngle?, origin?}`
288
+ - `revolve(axis?, {origin?})` — Revolve around axis
289
+ - `loftWith(otherSketches, config?, returnShell?)` — Loft between profiles
290
+ - `sweepSketch(sketchOnPlane, config?)` — Sweep along a spine
291
+ - `face()` — Convert to face
292
+ - `wires()` — Get wire(s)
293
+ - `clone()`, `delete()` — Copy and cleanup
294
+
295
+ ### Drawing to 3D
296
+
297
+ Extrude a 2D rectangular sketch into a 3D solid:
298
+ ```typescript
299
+ import { drawRectangle, drawCircle, drawingCut, drawingToSketchOnPlane, shape } from 'brepjs';
300
+
301
+ // Simple rectangle → extrude to box
302
+ const rect = drawRectangle(50, 30);
303
+ const sketch = rect.sketchOnPlane('XY');
304
+ const solid = sketch.extrude(20); // 20mm height → Solid
305
+
306
+ // Complex profile: rectangle with circular cutout → extrude
307
+ const profile = drawingCut(drawRectangle(50, 30), drawCircle(8).translate([25, 15]));
308
+ const profileSketch = drawingToSketchOnPlane(profile, 'XY');
309
+ const complexSolid = shape(profileSketch.face()).extrude(20).val;
310
+
311
+ // Or use the sketchRectangle shortcut
312
+ import { sketchRectangle } from 'brepjs';
313
+ const quickBox = sketchRectangle(50, 30).extrude(20);
314
+ ```
315
+
316
+ Plane names: `'XY'`, `'XZ'`, `'YZ'`, `'ZX'`, `'YX'`, `'ZY'`, `'front'`, `'back'`, `'top'`, `'bottom'`, `'left'`, `'right'`
317
+
318
+ ## 3D Operations
319
+
320
+ ### Extrude & Revolve
321
+
322
+ ```typescript
323
+ // Extrude a sketch into a solid
324
+ const box = sketchRectangle(10, 10).extrude(20);
325
+
326
+ // Twist extrude
327
+ const twisted = sketchRectangle(10, 10).extrude(20, { twistAngle: 45 });
328
+
329
+ // Revolve around an axis (default direction: sketch's defaultDirection)
330
+ const sphere = sketchCircle(5, { plane: 'XZ' }).revolve();
331
+ const halfTorus = sketchCircle(2, { plane: 'XZ' }).revolve([10, 0, 0], { origin: [0, 0, 0] });
332
+ ```
333
+
334
+ Functional API:
335
+ - `extrude(face: OrientedFace, height: number | Vec3): Result<Solid>` — Extrude a face. Pass a number for Z-axis extrusion or a Vec3 for arbitrary direction (vector length = distance)
336
+ - `revolve(face: OrientedFace, { axis?, at?, angle? }?): Result<Shape3D>` — Revolve a face. `at` default `[0,0,0]`, `axis` default `[0,0,1]` (Z). **`angle` is in RADIANS** — a full turn is `Math.PI * 2`; if omitted it defaults to a HALF turn (`Math.PI`), so always pass it explicitly. (Note: pattern `fullAngle` is in degrees — brepjs is not uniform, so don't assume degrees here.)
337
+ - `sweep(wire, spine, config?, shellMode?): Result<Shape3D | [Shape3D, Wire, Wire]>` — Sweep profile along spine
338
+ - `complexExtrude(wire, center, normal, profileShape?, shellMode?): Result<Shape3D>` — Extrude with scaling profile
339
+ - `twistExtrude(wire, angleDeg, center, normal, profileShape?, shellMode?): Result<Shape3D>` — Twist extrude with rotation
340
+ - `supportExtrude(wire, center, normal, support): Result<Shape3D>` — Extrude constrained to a support surface
341
+
342
+ `ExtrusionProfile`: `{ profile?: 's-curve' | 'linear', endFactor?: number }` — Controls scaling along extrusion path. `endFactor` 1 = same size, 0.5 = half size at end.
343
+
344
+ `SweepOptions`: `{ frenet?, auxiliarySpine?, law?, transitionMode?: 'right' | 'transformed' | 'round', withContact?, support?, forceProfileSpineOthogonality? }`
345
+
346
+ ### Loft & Sweep
347
+
348
+ ```typescript
349
+ import { sketchCircle, sketchRectangle } from 'brepjs';
350
+
351
+ // Loft between profiles
352
+ const bottom = sketchCircle(10);
353
+ const top = sketchCircle(5, { plane: 'XY', origin: 20 }); // XY plane offset to Z=20
354
+ const lofted = bottom.loftWith([top]);
355
+
356
+ // Sweep a profile along a spine (sweepSketch takes a function that builds the profile)
357
+ const spine = sketchHelix(10, 50, 20);
358
+ const coil = spine.sweepSketch((plane, origin) =>
359
+ new Sketcher(plane).movePointerTo([-2, -2]).lineTo([2, -2]).lineTo([2, 2]).lineTo([-2, 2]).close()
360
+ );
361
+ ```
362
+
363
+ Functional API:
364
+ - `loft(wires, config?): Result<Shape3D>` — Loft config: `{ruled?: boolean (default true), startPoint?: PointInput, endPoint?: PointInput}`
365
+ - `sketchLoft(sketch, otherSketches, config?, returnShell?): Shape3D`
366
+ - `sketchSweep(sketch, sketchOnPlane, sweepConfig?): Shape3D`
367
+ - `sketchExtrude(sketch, height, config?): Shape3D` — Functional version of `sketch.extrude()`
368
+ - `sketchRevolve(sketch, axis?, {origin?}?): Shape3D` — Functional version of `sketch.revolve()`
369
+ - `sketchFace(sketch): Face` — Get the face from a closed sketch
370
+ - `sketchWires(sketch): Wire` — Get the wire from a sketch
371
+
372
+ CompoundSketch functions (for multi-contour profiles like text):
373
+ - `compoundSketchExtrude(sketch, height, config?): Shape3D`
374
+ - `compoundSketchRevolve(sketch, axis?, {origin?}?): Shape3D`
375
+ - `compoundSketchFace(sketch): Face`
376
+ - `compoundSketchLoft(sketch, other, loftConfig): Shape3D`
377
+
378
+ Drawing functional API:
379
+ - `drawingToSketchOnPlane(drawing, plane?, origin?): SketchInterface | Sketches`
380
+ - `drawingFuse(a, b): Drawing`
381
+ - `drawingCut(a, b): Drawing`
382
+ - `drawingIntersect(a, b): Drawing`
383
+ - `drawingFillet(drawing, radius, filter?): Drawing`
384
+ - `drawingChamfer(drawing, radius, filter?): Drawing`
385
+ - `translateDrawing(drawing, dx, dy): Drawing` or `translateDrawing(drawing, [dx, dy])`
386
+ - `rotateDrawing(drawing, angle, center?): Drawing`
387
+ - `scaleDrawing(drawing, factor, center?): Drawing`
388
+ - `mirrorDrawing(drawing, dir, origin?, mode?): Drawing`
389
+
390
+ ### Boolean Operations
391
+
392
+ ```typescript
393
+ import { fuse, cut, intersect, unwrap } from 'brepjs';
394
+
395
+ const myBox = sketchRectangle(20, 20).extrude(20);
396
+ const hole = sketchCircle(5).extrude(30);
397
+
398
+ const withHole = unwrap(cut(myBox, hole)); // Subtraction → Result<Shape3D>
399
+ const merged = unwrap(fuse(myBox, hole)); // Union → Result<Shape3D>
400
+ const common = unwrap(intersect(myBox, hole)); // Intersection → Result<Shape3D>
401
+ ```
402
+
403
+ Full boolean API:
404
+ ```typescript
405
+ import { fuse, cut, intersect, section, split, slice } from 'brepjs';
406
+
407
+ fuse(a, b, options?): Result<Shape3D>
408
+ cut(base, tool, options?): Result<Shape3D>
409
+ intersect(a: Shape3D, b: Shape3D, options?): Result<Shape3D>
410
+ section(shape, plane, {approximation?, planeSize?}?): Result<AnyShape>
411
+ split(shape, tools): Result<AnyShape>
412
+ slice(shape, planes, options?): Result<AnyShape[]>
413
+ ```
414
+
415
+ All boolean operations validate inputs before calling OCCT: null shapes return `VALIDATION` errors with code `NULL_SHAPE_INPUT` and a message identifying which operand was invalid.
416
+
417
+ Boolean options: `{ optimisation?: 'none' | 'commonFace' | 'sameFace', simplify?: boolean, strategy?: 'native' | 'pairwise', signal?: AbortSignal }`
418
+
419
+ Batch: `fuseAll(shapes, options?): Result<Shape3D>`, `cutAll(base, tools, options?): Result<Shape3D>`
420
+
421
+ ### Fillet & Chamfer
422
+
423
+ ```typescript
424
+ import { fillet, chamfer, chamferDistAngleShape, getEdges, edgeFinder } from 'brepjs';
425
+
426
+ // Fillet all edges — returns Result<Shape3D>
427
+ const rounded = unwrap(fillet(myBox, getEdges(myBox), 2));
428
+
429
+ // Fillet specific edges using edgeFinder
430
+ const selective = unwrap(fillet(myBox, edgeFinder().ofLength(20).findAll(myBox), 2));
431
+
432
+ // Chamfer
433
+ const chamfered = unwrap(chamfer(myBox, getEdges(myBox), 1));
434
+ ```
435
+
436
+ Additional functional API:
437
+ ```typescript
438
+ import { fillet, chamfer, chamferDistAngleShape } from 'brepjs';
439
+
440
+ fillet(shape, radius): Result<Shape3D> // All edges
441
+ fillet(shape, edges, radius): Result<Shape3D> // Selected edges
442
+ // edges: Edge[] | FinderFn<Edge> | ShapeFinder<Edge>
443
+ // radius: number | [r1, r2] | (edge => number | [r1, r2] | null)
444
+
445
+ chamfer(shape, distance): Result<Shape3D> // All edges
446
+ chamfer(shape, edges, distance): Result<Shape3D> // Selected edges
447
+ // distance: number | [d1, d2] | (edge => number | [d1, d2] | null)
448
+
449
+ chamferDistAngleShape(shape, edges, distance, angleDeg): Result<Shape3D>
450
+ ```
451
+
452
+ ### Shell (Hollow Out)
453
+
454
+ ```typescript
455
+ import { shell, faceFinder, unwrap } from 'brepjs';
456
+
457
+ // Remove top face and shell to 1mm thickness — returns Result<Shape3D>
458
+ const topFaces = faceFinder().parallelTo('XY').findAll(b);
459
+ const hollowed = unwrap(shell(b, topFaces, 1));
460
+ ```
461
+
462
+ `shell(shape, faces, thickness, {tolerance?}?): Result<Shape3D>`
463
+
464
+ All modifier operations (`fillet`, `chamfer`, `shell`, `offset`, `thicken`) validate that the input shape is not null before calling OCCT, returning `NULL_SHAPE_INPUT` validation errors. Error messages from OCCT failures include operation name and parameter metadata (edge count, radius, distance).
465
+
466
+ ### Offset & Thicken
467
+
468
+ ```typescript
469
+ import { offset, thicken } from 'brepjs';
470
+
471
+ offset(shape: Shape3D, distance, {tolerance?}?): Result<Shape3D>
472
+ thicken(shape: Face | Shell, thickness): Result<Solid>
473
+ ```
474
+
475
+ ### Transformations
476
+
477
+ All transforms return new shapes — the original is never modified.
478
+
479
+ ```typescript
480
+ import { translate, rotate, mirror, scale } from 'brepjs';
481
+
482
+ translate(shape, [10, 0, 0]): T
483
+ rotate(shape, angle, { at?, axis? }?): T
484
+ mirror(shape, { normal?, at? }?): T
485
+ scale(shape, factor, { center? }?): T
486
+ ```
487
+
488
+ Sequential transforms (functional API):
489
+ ```typescript
490
+ import { box, translate, rotate, scale } from 'brepjs';
491
+
492
+ const b = box(30, 20, 10);
493
+ const moved = translate(b, [50, 0, 0]); // translate first
494
+ const rotated = rotate(moved, 45, { axis: [0, 0, 1] }); // then rotate
495
+ const scaled = scale(rotated, 2); // then scale
496
+ // b is unchanged; each step produces a new shape
497
+ ```
498
+
499
+ Sequential transforms (wrapper API):
500
+ ```typescript
501
+ import { box, shape } from 'brepjs';
502
+
503
+ const result = shape(box(30, 20, 10))
504
+ .translate([50, 0, 0])
505
+ .rotate(45, { axis: [0, 0, 1] })
506
+ .scale(2)
507
+ .val;
508
+ ```
509
+
510
+ ### Patterns
511
+
512
+ ```typescript
513
+ import { linearPattern, circularPattern } from 'brepjs';
514
+
515
+ // 5 total copies along X with 10mm spacing (original + 4 copies, fused together)
516
+ const row = linearPattern(shape, [1, 0, 0], 5, 10);
517
+
518
+ // 8 copies in a full circle around Z axis (fused together)
519
+ const ring = circularPattern(shape, [0, 0, 1], 8);
520
+
521
+ // 6 copies in 180-degree arc around Z, centered at [10, 0, 0]
522
+ const arc = circularPattern(shape, [0, 0, 1], 6, 180, [10, 0, 0]);
523
+ ```
524
+
525
+ `linearPattern(shape, direction, count, spacing, options?)`: `count` includes the original. Returns `Result<Shape3D>` (all copies fused).
526
+
527
+ `circularPattern(shape, axis, count, fullAngle?, center?, options?)`: `fullAngle` default 360 degrees. `center` default `[0,0,0]`. Returns `Result<Shape3D>`.
528
+
529
+ `rectangularPattern(shape, { xDir, xCount, xSpacing, yDir, yCount, ySpacing })`: 2D grid pattern. Returns `Result<Shape3D>`.
530
+
531
+ ### Compound Operations
532
+
533
+ High-level operations that combine primitives with booleans:
534
+
535
+ ```typescript
536
+ import { drill, pocket, boss, mirrorJoin } from 'brepjs';
537
+
538
+ // Drill a hole into a shape
539
+ const drilled = unwrap(drill(myBox, { at: [10, 10], radius: 3, depth: 15 }));
540
+
541
+ // Cut a pocket (shaped recess)
542
+ const pocketed = unwrap(pocket(myBox, { profile: drawRectangle(20, 10), depth: 5 }));
543
+
544
+ // Add a boss (raised feature)
545
+ const bossed = unwrap(boss(myBox, { profile: drawCircle(8), height: 10 }));
546
+
547
+ // Mirror and fuse with the original
548
+ const symmetric = unwrap(mirrorJoin(halfShape, { normal: [1, 0, 0] }));
549
+ ```
550
+
551
+ ```typescript
552
+ drill(shape, { at, radius, depth?, axis? }): Result<Shape3D>
553
+ pocket(shape, { profile, face?, depth }): Result<Shape3D>
554
+ boss(shape, { profile, face?, height }): Result<Shape3D>
555
+ mirrorJoin(shape, { normal?, at? }?): Result<Shape3D>
556
+ ```
557
+
558
+ ### Matrix Transforms
559
+
560
+ ```typescript
561
+ import { applyMatrix, composeTransforms, transformCopy } from 'brepjs';
562
+
563
+ applyMatrix(shape, matrix): T // Apply a 4x4 matrix transform
564
+ composeTransforms(ops): ComposedTransform // Pre-compose multiple transforms
565
+ transformCopy(shape, composed): T // Apply composed transform (fast for repeated use)
566
+ ```
567
+
568
+ `TransformOp`: `{ type: 'translate', v: Vec3 } | { type: 'rotate', angle: number, axis?: Vec3, center?: Vec3 }`
569
+
570
+ ## Shape Queries
571
+
572
+ ### Shape Introspection
573
+
574
+ ```typescript
575
+ import { clone, describe, getBounds, isEmpty, isSameShape, isEqualShape, simplify, toBREP } from 'brepjs';
576
+
577
+ clone(shape): T // Deep clone
578
+ describe(shape): ShapeDescription // {kind, faceCount, edgeCount, wireCount, vertexCount, valid, bounds}
579
+ getBounds(shape): Bounds3D // {xMin, xMax, yMin, yMax, zMin, zMax}
580
+ isEmpty(shape): boolean // Check if shape is null/empty
581
+ isSameShape(a, b): boolean // Same topology reference
582
+ isEqualShape(a, b): boolean // Geometrically equal
583
+ simplify(shape): T
584
+ toBREP(shape): string // Serialize to BREP format
585
+ getHashCode(shape): number
586
+ ```
587
+
588
+ ### Topology Traversal
589
+
590
+ ```typescript
591
+ import { getEdges, getFaces, getWires, getVertices, vertexPosition } from 'brepjs';
592
+
593
+ getEdges(shape): Edge[]
594
+ getFaces(shape): Face[]
595
+ getWires(shape): Wire[]
596
+ getVertices(shape): Vertex[]
597
+ vertexPosition(vertex): Vec3
598
+
599
+ // Iterator versions (lazy)
600
+ iterEdges(shape): Generator<Edge>
601
+ iterFaces(shape): Generator<Face>
602
+ iterWires(shape): Generator<Wire>
603
+ iterVertices(shape): Generator<Vertex>
604
+ ```
605
+
606
+ ### Adjacency Queries
607
+
608
+ ```typescript
609
+ import { facesOfEdge, edgesOfFace, wiresOfFace, verticesOfEdge, adjacentFaces, sharedEdges } from 'brepjs';
610
+
611
+ facesOfEdge(parent, edge): Face[]
612
+ edgesOfFace(face): Edge[]
613
+ wiresOfFace(face): Wire[]
614
+ verticesOfEdge(edge): Vertex[]
615
+ adjacentFaces(parent, face): Face[]
616
+ sharedEdges(face1, face2): Edge[]
617
+ ```
618
+
619
+ ### Immutable Finders (Functional API)
620
+
621
+ ```typescript
622
+ import { edgeFinder, faceFinder, wireFinder, vertexFinder } from 'brepjs';
623
+
624
+ // Composable, immutable chain — findAll returns T[]
625
+ const topEdges = edgeFinder()
626
+ .inDirection([0, 0, 1])
627
+ .ofLength(10, tolerance?)
628
+ .ofCurveType('LINE')
629
+ .findAll(shape);
630
+
631
+ const topFaces = faceFinder()
632
+ .inDirection('Z') // Faces whose normal aligns with Z (shorthand for [0,0,1])
633
+ .ofSurfaceType('PLANE')
634
+ .ofArea(100, tolerance?)
635
+ .findAll(shape);
636
+ // faceFinder also has: .parallelTo(dir) (alias for inDirection(dir, 0)), .atDistance(dist, point?)
637
+
638
+ const closedWires = wireFinder()
639
+ .isClosed()
640
+ .ofEdgeCount(4)
641
+ .findAll(shape);
642
+
643
+ const cornerVerts = vertexFinder()
644
+ .atPosition([0, 0, 0], tolerance?)
645
+ .nearestTo([10, 0, 0])
646
+ .withinBox([0, 0, 0], [10, 10, 10])
647
+ .findAll(shape);
648
+
649
+ // findUnique — returns Result<T>, errors if 0 or >1 matches
650
+ const uniqueEdge = edgeFinder().ofLength(10).findUnique(shape);
651
+
652
+ // Combinators (available on all finders)
653
+ edgeFinder().not(f => f.ofCurveType('LINE')).findAll(shape);
654
+ edgeFinder().either([f => f.ofLength(10), f => f.ofLength(20)]).findAll(shape);
655
+ edgeFinder().when(edge => customPredicate(edge)).findAll(shape);
656
+ edgeFinder().inList(knownEdges).findAll(shape);
657
+ ```
658
+
659
+ ### cornerFinder (2D)
660
+
661
+ ```typescript
662
+ import { cornerFinder } from 'brepjs';
663
+
664
+ // Immutable builder pattern — each method returns a new finder
665
+ cornerFinder().inList(points).find(blueprint);
666
+ cornerFinder().atDistance(dist, point?).find(blueprint);
667
+ cornerFinder().atPoint(point).find(blueprint);
668
+ cornerFinder().inBox(corner1, corner2).find(blueprint);
669
+ cornerFinder().ofAngle(angle).find(blueprint);
670
+ cornerFinder().not(f => f.atPoint([0, 0])).find(blueprint);
671
+ cornerFinder().either([f => f.atPoint([0, 0]), f => f.atPoint([1, 1])]).find(blueprint);
672
+ cornerFinder().when(corner => customPredicate(corner)).find(blueprint);
673
+ ```
674
+
675
+ ## Curve Operations
676
+
677
+ ```typescript
678
+ import {
679
+ getCurveType, curveStartPoint, curveEndPoint,
680
+ curvePointAt, curveTangentAt, curveLength,
681
+ curveIsClosed, curveIsPeriodic, curvePeriod,
682
+ getOrientation, flipOrientation, offsetWire2D,
683
+ interpolateCurve, approximateCurve
684
+ } from 'brepjs';
685
+
686
+ getCurveType(edge): CurveType // 'LINE' | 'CIRCLE' | 'ELLIPSE' | 'BEZIER_CURVE' | 'BSPLINE_CURVE' | ...
687
+ curveStartPoint(shape): Vec3
688
+ curveEndPoint(shape): Vec3
689
+ curvePointAt(shape, t?): Vec3 // Point at parameter t (0-1)
690
+ curveTangentAt(shape, t?): Vec3 // Tangent at parameter t
691
+ curveLength(shape): number
692
+ curveIsClosed(shape): boolean
693
+ curveIsPeriodic(shape): boolean
694
+ curvePeriod(shape): number
695
+ getOrientation(shape): 'forward' | 'backward'
696
+ flipOrientation(shape): Edge | Wire
697
+
698
+ // Create curves from points
699
+ interpolateCurve(points, {periodic?, tolerance?}?): Result<Edge>
700
+ approximateCurve(points, {tolerance?, degMin?, degMax?, smoothing?}?): Result<Edge>
701
+
702
+ // 2D wire offset
703
+ offsetWire2D(wire, offset, kind?): Result<Wire> // kind: 'arc' | 'intersection' | 'tangent'
704
+ ```
705
+
706
+ ## Face Operations
707
+
708
+ ```typescript
709
+ import {
710
+ getSurfaceType, faceGeomType, faceOrientation, flipFaceOrientation,
711
+ uvBounds, pointOnSurface, uvCoordinates, normalAt,
712
+ faceCenter, classifyPointOnFace, outerWire, innerWires,
713
+ projectPointOnFace
714
+ } from 'brepjs';
715
+
716
+ getSurfaceType(face): Result<SurfaceType> // 'PLANE' | 'CYLINDRE' | 'CONE' | 'SPHERE' | 'TORUS' | 'BSPLINE_SURFACE' | ...
717
+ faceGeomType(face): SurfaceType
718
+ faceOrientation(face): 'forward' | 'backward'
719
+ flipFaceOrientation(face): Face
720
+ uvBounds(face): { uMin, uMax, vMin, vMax }
721
+ pointOnSurface(face, u, v): Vec3
722
+ uvCoordinates(face, point): [number, number]
723
+ normalAt(face, point?): Vec3
724
+ faceCenter(face): Vec3
725
+ classifyPointOnFace(face, point, tolerance?): 'in' | 'on' | 'out'
726
+ outerWire(face): Wire
727
+ innerWires(face): Wire[]
728
+ projectPointOnFace(face, point): Result<{ uv, point, distance }>
729
+ ```
730
+
731
+ ## Measurements
732
+
733
+ ```typescript
734
+ import {
735
+ measureVolume, measureArea, measureLength,
736
+ measureDistance
737
+ } from 'brepjs';
738
+
739
+ measureVolume(solid); // number
740
+ measureArea(face); // number
741
+ measureLength(edge); // number
742
+ measureDistance(shape1, shape2); // number
743
+ ```
744
+
745
+ Functional measurement API:
746
+ ```typescript
747
+ import {
748
+ measureVolume, measureArea, measureLength, measureDistance,
749
+ measureVolumeProps, measureSurfaceProps, measureLinearProps,
750
+ createDistanceQuery, measureCurvatureAt, measureCurvatureAtMid
751
+ } from 'brepjs';
752
+
753
+ measureVolumeProps(shape): { volume, mass, centerOfMass }
754
+ measureSurfaceProps(shape): { area, mass, centerOfMass }
755
+ measureLinearProps(shape): { length, mass, centerOfMass }
756
+ measureDistance(shape1, shape2): number
757
+ createDistanceQuery(ref): { distanceTo(other): number, dispose(): void }
758
+
759
+ // Surface curvature
760
+ measureCurvatureAt(face, u, v): CurvatureResult
761
+ measureCurvatureAtMid(face): CurvatureResult
762
+ // CurvatureResult: { mean, gaussian, maxCurvature, minCurvature, maxDirection, minDirection }
763
+ ```
764
+
765
+ All measurement functions throw on null shape input with descriptive messages (e.g. `"measureVolumeProps: shape is a null shape"`). Use `isEmpty()` to check before measuring if the shape may be null.
766
+
767
+ ### Interference Detection
768
+
769
+ ```typescript
770
+ import { checkInterference, checkAllInterferences } from 'brepjs';
771
+
772
+ checkInterference(shape1, shape2, tolerance?): Result<InterferenceResult>
773
+ // InterferenceResult: { hasInterference, minDistance, pointOnShape1, pointOnShape2 }
774
+
775
+ checkAllInterferences(shapes, tolerance?): InterferencePair[]
776
+ // InterferencePair: { i, j, result: InterferenceResult }
777
+ ```
778
+
779
+ `checkInterference` returns a `Result` error with `kind: 'VALIDATION'` and `code: 'NULL_SHAPE_INPUT'` if either shape is null. `checkAllInterferences` propagates via `unwrap` (throws on null).
780
+
781
+ ## Shape Healing & Validation
782
+
783
+ ```typescript
784
+ import { isValid, healSolid, healFace, healWire, heal, autoHeal } from 'brepjs';
785
+
786
+ isValid(shape): boolean
787
+ healSolid(solid): Result<Solid>
788
+ healFace(face): Result<Face>
789
+ healWire(wire, face?): Result<Wire>
790
+ heal(shape): Result<T>
791
+
792
+ // Auto-healing pipeline with diagnostics
793
+ autoHeal(shape, options?): Result<{ shape, report: HealingReport }>
794
+ // Options: { fixWires?: boolean (default true), fixFaces?: boolean (default true),
795
+ // fixSolids?: boolean (default true), sewTolerance?: number,
796
+ // fixSelfIntersection?: boolean (default FALSE — unlike the others) }
797
+ // HealingReport: { isValid, alreadyValid, wiresHealed, facesHealed, solidHealed, steps, diagnostics }
798
+ // alreadyValid: true when shape was valid before healing — distinguishes "nothing to fix" from "fix succeeded"
799
+ ```
800
+
801
+ ## Import / Export
802
+
803
+ ### STEP Files
804
+
805
+ End-to-end import → modify → export:
806
+ ```typescript
807
+ import { importSTEP, exportSTEP, shape, unwrap } from 'brepjs';
808
+ import { readFileSync, writeFileSync } from 'fs';
809
+
810
+ // Import a STEP file
811
+ const stepBytes = readFileSync('input.step');
812
+ const stepBlob = new Blob([stepBytes]);
813
+ const imported = unwrap(await importSTEP(stepBlob)); // Result<AnyShape>
814
+
815
+ // Modify the imported shape
816
+ const modified = shape(imported).fillet(2).translate([0, 0, 10]).val;
817
+
818
+ // Export back to STEP
819
+ const outputBlob = unwrap(exportSTEP(modified)); // Result<Blob>
820
+ writeFileSync('output.step', Buffer.from(await outputBlob.arrayBuffer()));
821
+ ```
822
+
823
+ Export assembly with colors and names:
824
+ ```typescript
825
+ import { exportAssemblySTEP, unwrap } from 'brepjs';
826
+
827
+ const stepBlob = unwrap(exportAssemblySTEP([
828
+ { shape: body, color: '#3366cc', name: 'Body' },
829
+ { shape: lid, color: '#cc6633', name: 'Lid' },
830
+ ], { unit: 'millimeter' }));
831
+ ```
832
+
833
+ Functional API:
834
+ ```typescript
835
+ import { importSTEP, exportSTEP, exportAssemblySTEP } from 'brepjs';
836
+
837
+ importSTEP(blob): Promise<Result<AnyShape>>
838
+ exportSTEP(shape): Result<Blob>
839
+ exportAssemblySTEP(shapes, { unit?, modelUnit? }?): Result<Blob>
840
+ // ShapeConfig: { shape, color?, alpha?, name? }
841
+ // SupportedUnit: 'millimeter' | 'centimeter' | 'meter' | 'inch' | 'foot'
842
+ ```
843
+
844
+ ### STL Files
845
+
846
+ ```typescript
847
+ import { importSTL, exportSTL } from 'brepjs';
848
+
849
+ importSTL(blob): Promise<Result<AnyShape>>
850
+ exportSTL(shape, { tolerance?, angularTolerance?, binary? }?): Result<Blob>
851
+ ```
852
+
853
+ ### IGES Files
854
+
855
+ ```typescript
856
+ import { importIGES, exportIGES } from 'brepjs';
857
+
858
+ importIGES(blob): Promise<Result<AnyShape>>
859
+ exportIGES(shape): Result<Blob>
860
+ ```
861
+
862
+ ### glTF / GLB (with PBR materials)
863
+
864
+ ```typescript
865
+ import { exportGltf, exportGlb } from 'brepjs';
866
+
867
+ const json = exportGltf(mesh, options?); // glTF JSON string
868
+ const binary = exportGlb(mesh, options?); // GLB ArrayBuffer
869
+
870
+ // GltfExportOptions: { materials?: Map<faceId, GltfMaterial> }
871
+ // GltfMaterial: { name?, baseColor?: [r,g,b,a], metallic?: number, roughness?: number }
872
+ ```
873
+
874
+ ### DXF
875
+
876
+ ```typescript
877
+ import { importDXF, exportDXF, blueprintToDXF } from 'brepjs';
878
+
879
+ importDXF(blob, options?): Promise<Result<Wire[]>>
880
+ // DXFImportOptions: { layer?: string }
881
+
882
+ exportDXF(entities, options?): string
883
+ blueprintToDXF(drawing, options?): string
884
+ // DXFExportOptions: { layer?, curveSegments? }
885
+ // DXFEntity: { type: 'LINE', start, end, layer? } | { type: 'POLYLINE', points, closed?, layer? }
886
+ ```
887
+
888
+ ### 3MF
889
+
890
+ ```typescript
891
+ import { importThreeMF, exportThreeMF } from 'brepjs';
892
+
893
+ importThreeMF(blob): Promise<Result<AnyShape>>
894
+ exportThreeMF(mesh, options?): ArrayBuffer
895
+ // ThreeMFExportOptions: { name?, unit?: 'micron' | 'millimeter' | 'centimeter' | 'meter' | 'inch' | 'foot' }
896
+ ```
897
+
898
+ ### OBJ
899
+
900
+ ```typescript
901
+ import { importOBJ, exportOBJ } from 'brepjs';
902
+
903
+ importOBJ(blob): Promise<Result<AnyShape>>
904
+ exportOBJ(mesh): string
905
+ ```
906
+
907
+ ### SVG Import
908
+
909
+ ```typescript
910
+ import { importSVGPathD, importSVG } from 'brepjs';
911
+
912
+ importSVGPathD(pathD): Result<Blueprint> // Single SVG path d attribute
913
+ importSVG(svgString): Result<Blueprint[]> // Extract all <path> elements from SVG string
914
+ // SVGImportOptions type is exported: { flipY?: boolean } (Y-axis is flipped by default since SVG Y is down)
915
+ ```
916
+
917
+ ## Topology Helpers
918
+
919
+ Create shapes directly without the sketching API:
920
+
921
+ ```typescript
922
+ import {
923
+ line, circle, ellipse, helix,
924
+ threePointArc, ellipseArc, tangentArc,
925
+ bsplineApprox, bezier,
926
+ wire, face, filledFace, subFace,
927
+ addHoles, polygon,
928
+ cylinder, sphere, cone, torus, ellipsoid,
929
+ box, vertex, offsetFace, makeBaseBox,
930
+ compound, sewShells, solid
931
+ } from 'brepjs';
932
+
933
+ // 1D shapes (edges & wires)
934
+ line(from, to): Edge
935
+ circle(radius, { at?, normal? }?): Edge
936
+ ellipse(major, minor, { at?, normal?, xDir? }?): Result<Edge>
937
+ helix(pitch, height, radius, { at?, axis?, lefthand? }?): Wire
938
+ threePointArc(v1, v2, v3): Edge
939
+ ellipseArc(major, minor, startAngleDeg, endAngleDeg, { at?, normal?, xDir? }?): Result<Edge>
940
+ tangentArc(startPoint, startTangent, endPoint): Edge
941
+ bsplineApprox(points, config?): Result<Edge>
942
+ bezier(points): Result<Edge>
943
+ wire(edgesOrWires): Result<Wire>
944
+
945
+ wireLoop(edgesOrWires): Result<ClosedWire> // Assemble + verify closure
946
+
947
+ // 2D faces (require ClosedWire, return OrientedFace)
948
+ face(wire: ClosedWire, holes?: ClosedWire[]): Result<OrientedFace>
949
+ filledFace(wire: ClosedWire): Result<OrientedFace>
950
+ subFace(originFace, wire: ClosedWire): OrientedFace
951
+ addHoles(face, holes: ClosedWire[]): OrientedFace
952
+ polygon(points): Result<OrientedFace>
953
+
954
+ // 3D solids (return ValidSolid)
955
+ box(width, depth, height, { at?, centered? }?): ValidSolid
956
+ sphere(radius, { at? }?): ValidSolid
957
+ cylinder(radius, height, { at?, axis?, centered? }?): ValidSolid
958
+ cone(bottomRadius, topRadius, height, { at?, axis?, centered? }?): ValidSolid
959
+ torus(majorRadius, minorRadius, { at?, axis? }?): ValidSolid
960
+ ellipsoid(rx, ry, rz, { at? }?): ValidSolid
961
+ makeBaseBox(x, y, z): Shape3D
962
+ solid(facesOrShells): Result<ValidSolid>
963
+
964
+ // Vertex
965
+ vertex(point): Vertex
966
+
967
+ // Compound & shell
968
+ compound(shapes): Compound
969
+ sewShells(facesOrShells, ignoreType?): Result<Shell>
970
+ offsetFace(face, offset, tolerance?): Result<Shape3D>
971
+ ```
972
+
973
+ ## Constructive Geometry
974
+
975
+ ```typescript
976
+ import { hull, minkowski, polyhedron, surfaceFromGrid, surfaceFromImage, roof } from 'brepjs';
977
+
978
+ hull(shapes, options?): Result<Solid> // Convex hull of shapes
979
+ minkowski(shape, tool, options?): Result<Solid> // Minkowski sum
980
+ polyhedron(points, faces, options?): Result<Solid> // From vertices + face indices
981
+ surfaceFromGrid(heights, options?): Result<AnyShape> // Height-map surface
982
+ surfaceFromImage(blob, options?): Promise<Result<AnyShape>> // Image-based surface
983
+ roof(wire, { angle? }?): Result<Solid> // Roof from closed wire outline
984
+ ```
985
+
986
+ `SurfaceFromGridOptions`: `{ width?, depth?, scaleZ? }`
987
+ `SurfaceFromImageOptions`: extends grid options + `{ channel?: 'r' | 'g' | 'b' | 'luminance', downsample? }`
988
+
989
+ ### Advanced Sweeps
990
+
991
+ ```typescript
992
+ import { multiSectionSweep, guidedSweep } from 'brepjs';
993
+
994
+ multiSectionSweep(sections, spine, options?): Result<Solid | Shell>
995
+ // sections: { wire: Wire, location?: number }[]
996
+ // MultiSweepOptions: { solid?, ruled?, tolerance? }
997
+
998
+ guidedSweep(profile, spine, guides, options?): Result<Solid | Shell>
999
+ // GuidedSweepOptions: { transition?: 'transformed' | 'round' | 'right', solid?, tolerance? }
1000
+ ```
1001
+
1002
+ ## Shape Coloring & Tagging
1003
+
1004
+ ```typescript
1005
+ import { colorFaces, colorShape, getFaceColor, getShapeColor } from 'brepjs';
1006
+
1007
+ colorFaces(shape, faces, color): T // Color specific faces
1008
+ colorShape(shape, color): T // Color entire shape
1009
+ getFaceColor(shape, face): Color | undefined // Get face color
1010
+ getShapeColor(shape): Color | undefined // Get shape color
1011
+ // ColorInput: string ('#ff0000') | [r, g, b] | [r, g, b, a]
1012
+ ```
1013
+
1014
+ ```typescript
1015
+ import { tagFaces, findFacesByTag, getFaceTags, setTagMetadata, getTagMetadata } from 'brepjs';
1016
+
1017
+ tagFaces(shape, selector, tag): AnyShape // Tag faces by array or predicate
1018
+ findFacesByTag(shape, tag): Face[] // Find faces by tag name
1019
+ getFaceTags(shape): Map<string, Face[]> // Get all tags
1020
+ setTagMetadata(shape, tag, metadata): AnyShape // Attach metadata to a tag
1021
+ getTagMetadata(shape, tag): Record<string, unknown> | undefined
1022
+ ```
1023
+
1024
+ ## 2D Blueprints & Curves
1025
+
1026
+ The `Drawing` class supports 2D boolean operations and transformations:
1027
+
1028
+ ```typescript
1029
+ const plate = drawRectangle(100, 50);
1030
+ const hole = drawCircle(10).translate(20, 0); // Drawing.translate is still available
1031
+ const withHole = plate.cut(hole); // Drawing.cut is still available
1032
+ const filleted = withHole.fillet(3); // Drawing.fillet is still available
1033
+ const svg = filleted.toSVG();
1034
+ ```
1035
+
1036
+ Functional Blueprint API:
1037
+ ```typescript
1038
+ import {
1039
+ createBlueprint, getBounds2D, getOrientation2D,
1040
+ translate2D, rotate2D, scale2D, mirror2D,
1041
+ stretch2D, toSVGPathD, isInside2D,
1042
+ sketchOnPlane2D, sketchOnFace2D
1043
+ } from 'brepjs';
1044
+
1045
+ createBlueprint(curves): Blueprint
1046
+ getBounds2D(bp): BoundingBox2d
1047
+ getOrientation2D(bp): 'clockwise' | 'counterClockwise'
1048
+ translate2D(bp, dx, dy): Blueprint
1049
+ rotate2D(bp, angle, center?): Blueprint
1050
+ scale2D(bp, factor, center?): Blueprint
1051
+ mirror2D(bp, dir, origin?, mode?): Blueprint
1052
+ stretch2D(bp, ratio, direction, origin?): Blueprint
1053
+ toSVGPathD(bp): string
1054
+ isInside2D(bp, point): boolean
1055
+ sketchOnPlane2D(bp, plane?, origin?): Sketch
1056
+ sketchOnFace2D(bp, face, scaleMode?): Sketch
1057
+ ```
1058
+
1059
+ Blueprint construction helpers:
1060
+ ```typescript
1061
+ import { polysidesBlueprint, roundedRectangleBlueprint, organiseBlueprints } from 'brepjs';
1062
+
1063
+ polysidesBlueprint(radius, sidesCount, sagitta?): Blueprint // Regular polygon as a Blueprint (lower-level than drawPolysides)
1064
+ roundedRectangleBlueprint(width, height, r?): Blueprint // Rounded rect as Blueprint. r: number | {rx?, ry?}
1065
+ organiseBlueprints(blueprints: Blueprint[]): Blueprints // Group flat blueprints into compound blueprints with hole detection
1066
+ ```
1067
+
1068
+ Low-level Blueprint boolean operations (single blueprint → single blueprint):
1069
+ ```typescript
1070
+ import { fuseBlueprints, cutBlueprints, intersectBlueprints } from 'brepjs';
1071
+
1072
+ fuseBlueprints(first: Blueprint, second: Blueprint): null | Blueprint | Blueprints
1073
+ cutBlueprints(first: Blueprint, second: Blueprint): null | Blueprint | Blueprints
1074
+ intersectBlueprints(first: Blueprint, second: Blueprint): null | Blueprint | Blueprints
1075
+ ```
1076
+
1077
+ 2D Boolean (functional):
1078
+ ```typescript
1079
+ import { fuse2D, cut2D, intersect2D } from 'brepjs';
1080
+
1081
+ fuse2D(first, second): Shape2D
1082
+ cut2D(first, second): Shape2D
1083
+ intersect2D(first, second): Shape2D
1084
+ // Shape2D = Blueprint | Blueprints | CompoundBlueprint | null
1085
+ ```
1086
+
1087
+ 2D Curve functions:
1088
+ ```typescript
1089
+ import {
1090
+ reverseCurve, curve2dBoundingBox, curve2dFirstPoint, curve2dLastPoint,
1091
+ curve2dSplitAt, curve2dParameter, curve2dTangentAt, curve2dIsOnCurve, curve2dDistanceFrom
1092
+ } from 'brepjs';
1093
+ ```
1094
+
1095
+ ## Projection & Camera
1096
+
1097
+ Project 3D shapes to 2D for technical drawings:
1098
+
1099
+ ```typescript
1100
+ import { drawProjection, createCamera, cameraLookAt, unwrap } from 'brepjs';
1101
+
1102
+ // Quick projection from a named plane
1103
+ const { visible, hidden } = drawProjection(shape, 'front');
1104
+ const svg = visible.toSVG();
1105
+
1106
+ // Custom camera
1107
+ const camera = unwrap(createCamera([100, 100, 100], [0, 0, -1]));
1108
+ const lookingAt = unwrap(cameraLookAt(camera, [0, 0, 0]));
1109
+ const projected = drawProjection(shape, lookingAt);
1110
+ ```
1111
+
1112
+ Camera API:
1113
+ ```typescript
1114
+ import { createCamera, cameraLookAt, cameraFromPlane, projectEdges } from 'brepjs';
1115
+
1116
+ createCamera(position?, direction?, xAxis?): Result<Camera>
1117
+ cameraLookAt(camera, target): Result<Camera>
1118
+ cameraFromPlane(planeName): Result<Camera>
1119
+ projectEdges(shape, camera, withHiddenLines?): { visible: Edge[], hidden: Edge[] }
1120
+ ```
1121
+
1122
+ Additional projection helpers:
1123
+ ```typescript
1124
+ import { isProjectionPlane, makeProjectedEdges } from 'brepjs';
1125
+
1126
+ isProjectionPlane(plane): plane is ProjectionPlane // Type guard for ProjectionPlane strings
1127
+ makeProjectedEdges(shape, camera, withHiddenLines?): { visible: Edge[], hidden: Edge[] } // HLR projection
1128
+ ```
1129
+
1130
+ ## Text
1131
+
1132
+ Render text as 2D outlines (requires font loading):
1133
+
1134
+ ```typescript
1135
+ import { loadFont, getFont, drawText, sketchText, textBlueprints } from 'brepjs';
1136
+
1137
+ await loadFont('/fonts/Roboto-Regular.ttf', 'Roboto');
1138
+
1139
+ // 2D text drawing
1140
+ const text2d = drawText('Hello', { fontSize: 20, fontFamily: 'Roboto' });
1141
+
1142
+ // 3D text (sketch on plane, ready to extrude)
1143
+ const text3d = sketchText('Hello', { fontSize: 20, fontFamily: 'Roboto' }, { plane: 'XY' });
1144
+
1145
+ // Get raw blueprints
1146
+ const bps = textBlueprints('Hello', { fontSize: 20, fontFamily: 'Roboto' });
1147
+ ```
1148
+
1149
+ ## Assembly Tree
1150
+
1151
+ Build hierarchical assemblies with transforms:
1152
+
1153
+ ```typescript
1154
+ import {
1155
+ createAssemblyNode, addChild, removeChild, updateNode,
1156
+ findNode, walkAssembly, countNodes, collectShapes
1157
+ } from 'brepjs';
1158
+
1159
+ const root = createAssemblyNode('Root');
1160
+ const part = createAssemblyNode('Part', {
1161
+ shape: myShape,
1162
+ translate: [10, 0, 0],
1163
+ rotate: { angle: 45, axis: [0, 0, 1] },
1164
+ metadata: { material: 'steel' }
1165
+ });
1166
+
1167
+ const assembly = addChild(root, part);
1168
+ const updated = updateNode(assembly, { translate: [20, 0, 0] });
1169
+ const found = findNode(assembly, 'Part');
1170
+ walkAssembly(assembly, (node, depth) => console.log(node.name, depth));
1171
+ const count = countNodes(assembly);
1172
+ const shapes = collectShapes(assembly);
1173
+ const pruned = removeChild(assembly, 'Part');
1174
+ ```
1175
+
1176
+ ### Assembly Mates (Constraints)
1177
+
1178
+ ```typescript
1179
+ import { addMate, solveAssembly } from 'brepjs';
1180
+
1181
+ // Add a constraint between parts
1182
+ const constrained = addMate(assembly, {
1183
+ type: 'coincident',
1184
+ entityA: { node: 'Lid', face: topFace },
1185
+ entityB: { node: 'Body', face: bottomFace },
1186
+ });
1187
+
1188
+ // Solve constraints to compute transforms
1189
+ const solved = unwrap(solveAssembly(constrained));
1190
+ // solved: { transforms: Map<string, { position, rotation }>, dof, converged }
1191
+ ```
1192
+
1193
+ Mate types: `'coincident'`, `'concentric'`, `'distance'`, `'angle'`, `'fixed'`
1194
+
1195
+ ## Parametric History
1196
+
1197
+ Track modeling operations for undo/replay:
1198
+
1199
+ ```typescript
1200
+ import {
1201
+ createHistory, addStep, undoLast, findStep,
1202
+ getHistoryShape, stepCount, stepsFrom,
1203
+ registerShape, createRegistry, registerOperation,
1204
+ replayHistory, replayFrom, modifyStep
1205
+ } from 'brepjs';
1206
+
1207
+ // Create history and register operations
1208
+ let history = createHistory();
1209
+ let registry = createRegistry();
1210
+ registry = registerOperation(registry, 'extrude', (inputs, params) => {
1211
+ return sketchRectangle(params.w, params.h).extrude(params.depth);
1212
+ });
1213
+
1214
+ // Add steps
1215
+ history = registerShape(history, 'base', baseShape);
1216
+ history = addStep(history, {
1217
+ id: 'step1', type: 'extrude',
1218
+ parameters: { w: 10, h: 10, depth: 20 },
1219
+ inputIds: ['base'], outputId: 'extruded'
1220
+ }, resultShape);
1221
+
1222
+ // Undo, replay, modify
1223
+ history = undoLast(history);
1224
+ const replayed = replayHistory(history, registry);
1225
+ const modified = modifyStep(history, 'step1', { depth: 30 }, registry);
1226
+ ```
1227
+
1228
+ ## Meshing
1229
+
1230
+ Convert shapes to triangle meshes for rendering:
1231
+
1232
+ ```typescript
1233
+ import { mesh, meshEdges } from 'brepjs';
1234
+
1235
+ const m = mesh(shape, { tolerance: 0.5, angularTolerance: 20, includeUVs: true });
1236
+ // m.vertices: Float32Array (flat xyz)
1237
+ // m.triangles: Uint32Array (triangle indices)
1238
+ // m.normals: Float32Array (flat normals)
1239
+ // m.uvs: Float32Array (UV coordinates when includeUVs: true)
1240
+ // m.faceGroups: {start, count, faceId}[]
1241
+
1242
+ const edgeMesh = meshEdges(shape, { tolerance: 0.5 });
1243
+ // edgeMesh.lines: number[]
1244
+ // edgeMesh.edgeGroups: {start, count, edgeId}[]
1245
+
1246
+ // Full mesh options (with defaults):
1247
+ // mesh(shape, {
1248
+ // tolerance: 1e-3, // linear deflection
1249
+ // angularTolerance: 0.1, // angular deflection (radians)
1250
+ // skipNormals: false, // omit normals from output
1251
+ // includeUVs: false, // include UV coordinates per-vertex
1252
+ // cache: true, // cache results (WeakMap by shape)
1253
+ // signal?: AbortSignal, // abort between face iterations
1254
+ // })
1255
+ ```
1256
+
1257
+ Mesh caching:
1258
+ ```typescript
1259
+ import { clearMeshCache, createMeshCache } from 'brepjs';
1260
+
1261
+ clearMeshCache(); // Clear global cache
1262
+ const cache = createMeshCache(); // Create isolated cache
1263
+ ```
1264
+
1265
+ ### Three.js Integration
1266
+
1267
+ ```typescript
1268
+ import { toBufferGeometryData, toLineGeometryData, toGroupedBufferGeometryData } from 'brepjs';
1269
+
1270
+ const bufferData = toBufferGeometryData(mesh);
1271
+ // { position: Float32Array, normal: Float32Array, index: Uint32Array }
1272
+
1273
+ const lineData = toLineGeometryData(edgeMesh);
1274
+ // { position: Float32Array }
1275
+
1276
+ const grouped = toGroupedBufferGeometryData(mesh);
1277
+ // extends bufferData with: groups: [{start, count, materialIndex, faceId}]
1278
+ ```
1279
+
1280
+ ## Memory Management
1281
+
1282
+ OCCT objects are allocated in WASM memory and are **not** garbage-collected by the JavaScript engine. You must explicitly clean them up. brepjs provides four cleanup patterns:
1283
+
1284
+ ### Pattern 1: `using` keyword (recommended, TS 5.9+)
1285
+
1286
+ The `using` declaration automatically disposes shapes when they go out of scope via TC39 `Symbol.dispose`:
1287
+
1288
+ ```typescript
1289
+ import { box, cylinder, cut, unwrap } from 'brepjs';
1290
+
1291
+ {
1292
+ using b = box(10, 10, 10);
1293
+ using hole = cylinder(3, 15);
1294
+ const result = unwrap(cut(b, hole));
1295
+ // b and hole are automatically freed at block end
1296
+ // result survives because it was not declared with `using`
1297
+ }
1298
+
1299
+ // Works in loops too
1300
+ for (let i = 0; i < 100; i++) {
1301
+ using temp = box(1, 1, 1);
1302
+ processBox(temp);
1303
+ // temp freed each iteration — no memory leak
1304
+ }
1305
+ ```
1306
+
1307
+ Requires TypeScript 5.9+ with `"lib": ["ES2022", "ESNext.Disposable"]` and Node.js 20+ or modern browsers.
1308
+
1309
+ ### Pattern 2: `DisposalScope` (deterministic, multiple temporaries)
1310
+
1311
+ ```typescript
1312
+ import { DisposalScope, box, cylinder, cut, unwrap } from 'brepjs';
1313
+
1314
+ function buildPart() {
1315
+ using scope = new DisposalScope();
1316
+ const b = scope.register(box(10, 10, 10)); // register for cleanup
1317
+ const hole = scope.register(cylinder(3, 15)); // register for cleanup
1318
+ return unwrap(cut(b, hole)); // result escapes; b and hole freed when scope exits
1319
+ }
1320
+ ```
1321
+
1322
+ ### Pattern 3: `withScope()` (scoped, returns result)
1323
+
1324
+ ```typescript
1325
+ import { withScope, box, cylinder, cut, unwrap } from 'brepjs';
1326
+
1327
+ const result = withScope((scope) => {
1328
+ const b = scope.register(box(10, 10, 10));
1329
+ const hole = scope.register(cylinder(3, 15));
1330
+ return unwrap(cut(b, hole)); // returned value survives the scope
1331
+ });
1332
+ ```
1333
+
1334
+ `withScopeResult(fn)` and `withScopeResultAsync(fn)` are variants that accept functions returning `Result<T>` — useful inside operations that already use Result-based error handling:
1335
+ ```typescript
1336
+ import { withScopeResult } from 'brepjs';
1337
+
1338
+ const result: Result<Solid> = withScopeResult((scope) => {
1339
+ const temp = scope.register(cylinder(5, 20));
1340
+ return cut(myBox, temp); // returns Result directly
1341
+ });
1342
+ ```
1343
+
1344
+ ### Low-level handles
1345
+
1346
+ ```typescript
1347
+ import { createHandle, createKernelHandle } from 'brepjs';
1348
+
1349
+ const handle = createHandle(ocShape); // ShapeHandle: { wrapped, disposed, [Symbol.dispose] }
1350
+ const kernelHandle = createKernelHandle(ocObj); // KernelHandle<T>: { value, disposed, [Symbol.dispose] }
1351
+ ```
1352
+
1353
+ `FinalizationRegistry` provides a safety net for missed cleanup, but relying on it is not recommended because GC timing is unpredictable. Always use one of the explicit patterns above.
1354
+
1355
+ ## Error Handling
1356
+
1357
+ Many operations return `Result<T, BrepError>`:
1358
+
1359
+ ```typescript
1360
+ import { ok, err, OK, isOk, isErr, unwrap, unwrapOr, unwrapOrElse, match, map, andThen, collect, tryCatch, pipeline } from 'brepjs';
1361
+
1362
+ // Construction
1363
+ ok(value): Ok<T>
1364
+ err(error): Err<E>
1365
+ OK // Pre-built Ok<undefined> for void success
1366
+
1367
+ // Type guards
1368
+ isOk(result): result is Ok<T>
1369
+ isErr(result): result is Err<E>
1370
+
1371
+ // Extraction
1372
+ unwrap(result): T // throws on Err
1373
+ unwrapOr(result, defaultValue): T
1374
+ unwrapOrElse(result, fn): T
1375
+ unwrapErr(result): E // throws on Ok
1376
+
1377
+ // Combinators
1378
+ map(result, fn): Result<U, E>
1379
+ mapErr(result, fn): Result<T, F>
1380
+ andThen(result, fn): Result<U, E> // flatMap alias
1381
+ collect(results): Result<T[], E> // All-or-nothing
1382
+
1383
+ // Pattern matching
1384
+ match(result, { ok: fn, err: fn }): U
1385
+
1386
+ // Try-catch boundary
1387
+ tryCatch(fn, mapError): Result<T, E>
1388
+ tryCatchAsync(fn, mapError): Promise<Result<T, E>>
1389
+
1390
+ // Pipeline
1391
+ pipeline(input).then(fn).then(fn).result // Chain Result operations
1392
+ ```
1393
+
1394
+ BrepError structure:
1395
+ ```typescript
1396
+ interface BrepError {
1397
+ kind: BrepErrorKind; // 'KERNEL_OPERATION' | 'VALIDATION' | 'TYPE_CAST' | 'SKETCHER_STATE' | 'MODULE_INIT' | 'COMPUTATION' | 'IO' | 'QUERY'
1398
+ code: string; // e.g. 'FUSE_FAILED', 'STEP_IMPORT_FAILED'
1399
+ message: string;
1400
+ cause?: unknown;
1401
+ metadata?: Record<string, unknown>;
1402
+ }
1403
+ ```
1404
+
1405
+ Error constructors: `kernelError(code, msg, cause?, meta?)`, `validationError(...)`, `typeCastError(...)`, `ioError(...)`, `computationError(...)`, `queryError(...)`, `sketcherStateError(...)`, `moduleInitError(...)`
1406
+
1407
+ ## Worker Protocol
1408
+
1409
+ Off-main-thread CAD operations:
1410
+
1411
+ ```typescript
1412
+ import {
1413
+ createWorkerClient, createOperationRegistry, registerHandler, createWorkerHandler,
1414
+ createTaskQueue, enqueueTask, dequeueTask, pendingCount, isQueueEmpty, rejectAll,
1415
+ isInitRequest, isOperationRequest, isDisposeRequest, isSuccessResponse, isErrorResponse
1416
+ } from 'brepjs';
1417
+
1418
+ // Client side
1419
+ const client = createWorkerClient({ worker: myWorker, wasmUrl: '/wasm/oc.wasm' });
1420
+ await client.init();
1421
+ const result = await client.execute('fuse', [shape1Brep, shape2Brep], {});
1422
+ client.dispose();
1423
+
1424
+ // Worker side
1425
+ let registry = createOperationRegistry();
1426
+ registry = registerHandler(registry, 'fuse', (shapesBrep, params) => {
1427
+ // shapesBrep: ReadonlyArray<string> — BREP-serialized input shapes
1428
+ // params: Readonly<Record<string, unknown>> — operation parameters
1429
+ // Must return { resultBrep?: string, resultData?: unknown }
1430
+ return { resultBrep: outputBrep };
1431
+ });
1432
+ createWorkerHandler(registry, async (wasmUrl) => { /* init WASM */ });
1433
+ ```
1434
+
1435
+ ## Vec3 Math Utilities
1436
+
1437
+ ```typescript
1438
+ import {
1439
+ vecAdd, vecSub, vecScale, vecNegate,
1440
+ vecDot, vecCross, vecLength, vecLengthSq, vecDistance,
1441
+ vecNormalize, vecEquals, vecIsZero,
1442
+ vecAngle, vecProjectToPlane, vecRotate, vecRepr
1443
+ } from 'brepjs';
1444
+
1445
+ vecAdd([1,0,0], [0,1,0]): Vec3 // [1, 1, 0]
1446
+ vecSub(a, b): Vec3
1447
+ vecScale(v, scalar): Vec3
1448
+ vecNegate(v): Vec3
1449
+ vecDot(a, b): number
1450
+ vecCross(a, b): Vec3
1451
+ vecLength(v): number
1452
+ vecLengthSq(v): number
1453
+ vecDistance(a, b): number
1454
+ vecNormalize(v): Vec3
1455
+ vecEquals(a, b, tolerance?): boolean
1456
+ vecIsZero(v, tolerance?): boolean
1457
+ vecAngle(a, b): number // radians
1458
+ vecProjectToPlane(v, origin, normal): Vec3
1459
+ vecRotate(v, axis, angleRad): Vec3
1460
+ vecRepr(v): string // e.g. "(1.00, 2.00, 3.00)"
1461
+ ```
1462
+
1463
+ ## Plane Operations
1464
+
1465
+ ```typescript
1466
+ import { createPlane, createNamedPlane, resolvePlane, translatePlane, pivotPlane, makePlane } from 'brepjs';
1467
+
1468
+ createPlane(origin, xDirection?, normal?): Plane
1469
+ createNamedPlane(name, origin?): Result<Plane>
1470
+ resolvePlane(input, origin?): Plane // PlaneName | Plane → Plane
1471
+ translatePlane(plane, offset): Plane
1472
+ pivotPlane(plane, angleDeg, axis?): Plane
1473
+ makePlane(plane?, origin?): Plane // From PlaneName + origin
1474
+ ```
1475
+
1476
+ ## Branded Shape Types (Functional API)
1477
+
1478
+ ```typescript
1479
+ import {
1480
+ castShape, getShapeKind,
1481
+ createVertex, createEdge, createWire, createFace, createShell, createSolid, createCompound,
1482
+ isVertex, isEdge, isWire, isFace, isShell, isSolid, isCompound, isShape3D, isShape1D,
1483
+ is3D, is2D,
1484
+ closedWire, orientedFace, manifoldShell, validSolid,
1485
+ isClosedWire, isOrientedFace, isManifoldShell, isValidSolid,
1486
+ } from 'brepjs';
1487
+
1488
+ castShape(ocShape): AnyShape // Auto-detect and wrap
1489
+ getShapeKind(shape): ShapeKind // 'vertex' | 'edge' | 'wire' | 'face' | 'shell' | 'solid' | 'compsolid' | 'compound'
1490
+ isVertex(s): s is Vertex // Type guards
1491
+ // ... all type guards follow the same pattern
1492
+ is3D(s): s is AnyShape<'3D'> // Dimension guards
1493
+ is2D(s): s is AnyShape<'2D'>
1494
+ closedWire(w): Result<ClosedWire, string> // Validity smart constructors
1495
+ orientedFace(f): Result<OrientedFace, string>
1496
+ manifoldShell(s): Result<ManifoldShell, string>
1497
+ validSolid(s): Result<ValidSolid, string>
1498
+ isClosedWire(w): w is ClosedWire // Validity type guards
1499
+ isOrientedFace(f): f is OrientedFace
1500
+ isManifoldShell(s): s is ManifoldShell
1501
+ isValidSolid(s): s is ValidSolid
1502
+ ```
1503
+
1504
+ ## Kernel Boundary Conversions
1505
+
1506
+ Low-level helpers for interop with raw kernel objects:
1507
+
1508
+ ```typescript
1509
+ import { toKernelVec, fromKernelVec, fromKernelPnt, fromKernelDir, withKernelVec, withKernelPnt, withKernelDir } from 'brepjs';
1510
+
1511
+ toKernelVec(v): gp_Vec // Caller must delete()
1512
+ fromKernelVec(ocVec): Vec3 // Extract tuple from gp_Vec
1513
+ fromKernelPnt(ocPnt): Vec3 // Extract tuple from gp_Pnt
1514
+ fromKernelDir(ocDir): Vec3 // Extract tuple from gp_Dir
1515
+
1516
+ // Scoped (auto-cleanup)
1517
+ withKernelVec(v, fn): T // fn receives gp_Vec, deleted after
1518
+ withKernelPnt(v, fn): T
1519
+ withKernelDir(v, fn): T
1520
+ ```
1521
+
1522
+ ## Constants
1523
+
1524
+ - `DEG2RAD` — Multiply degrees to get radians
1525
+ - `RAD2DEG` — Multiply radians to get degrees
1526
+ - `HASH_CODE_MAX` — Maximum hash code value (2147483647)
1527
+
1528
+ ## Types Reference
1529
+
1530
+ Key types used across the API:
1531
+ - `Vec3`: `readonly [number, number, number]`
1532
+ - `Vec2`: `readonly [number, number]`
1533
+ - `PointInput`: `Vec3 | Vec2`
1534
+ - `Point2D`: `[number, number]`
1535
+ - `Direction`: `Vec3 | 'X' | 'Y' | 'Z'`
1536
+ - `Plane`: `{ origin: Vec3, xDir: Vec3, yDir: Vec3, zDir: Vec3 }`
1537
+ - `PlaneName`: `'XY' | 'XZ' | 'YZ' | 'ZX' | 'YX' | 'ZY' | 'front' | 'back' | 'left' | 'right' | 'top' | 'bottom'`
1538
+ - `PlaneInput`: `Plane | PlaneName`
1539
+ - `ShapeKind`: `'vertex' | 'edge' | 'wire' | 'face' | 'shell' | 'solid' | 'compsolid' | 'compound'`
1540
+ - `AnyShape`: Union of Vertex, Edge, Wire, Face, Shell, Solid, CompSolid, Compound
1541
+ - `Shape3D`: Shell | Solid | CompSolid | Compound
1542
+ - `Shape1D`: Edge | Wire
1543
+ - `CurveType`: `'LINE' | 'CIRCLE' | 'ELLIPSE' | 'HYPERBOLA' | 'PARABOLA' | 'BEZIER_CURVE' | 'BSPLINE_CURVE' | 'OFFSET_CURVE' | 'OTHER_CURVE'`
1544
+ - `SurfaceType`: `'PLANE' | 'CYLINDRE' | 'CONE' | 'SPHERE' | 'TORUS' | 'BEZIER_SURFACE' | 'BSPLINE_SURFACE' | 'REVOLUTION_SURFACE' | 'EXTRUSION_SURFACE' | 'OFFSET_SURFACE' | 'OTHER_SURFACE'`
1545
+ - `Result<T, E>`: `Ok<T> | Err<E>` (default `E = BrepError`)
1546
+ - `BrepError`: `{ kind: BrepErrorKind, code: string, message: string, cause?, metadata? }`
1547
+ - `ShapeMesh`: `{ triangles: Uint32Array, vertices: Float32Array, normals: Float32Array, uvs: Float32Array, faceGroups }`
1548
+ - `EdgeMesh`: `{ lines: number[], edgeGroups: {start, count, edgeId}[] }`
1549
+ - `MeshOptions`: `{ tolerance?, angularTolerance?, signal? }` (mesh() also accepts `{ skipNormals?, includeUVs?, cache? }`)
1550
+ - `BooleanOptions`: `{ optimisation?, simplify?, strategy?, signal? }`
1551
+ - `Camera`: `{ position: Vec3, direction: Vec3, xAxis: Vec3, yAxis: Vec3 }`
1552
+ - `ProjectionPlane`: `'XY' | 'XZ' | 'YZ' | 'YX' | 'ZX' | 'ZY' | 'front' | 'back' | 'top' | 'bottom' | 'left' | 'right'`
1553
+ - `AssemblyNode`: `{ name, shape?, translate?, rotate?: { angle, axis? }, metadata?, children }`
1554
+ - `ModelHistory`: `{ steps: ReadonlyArray<OperationStep>, shapes: ReadonlyMap<string, AnyShape> }`
1555
+ - `OperationStep`: `{ id, type, parameters, inputIds, outputId, timestamp, metadata? }`
1556
+ - `OperationFn`: `(inputs: AnyShape[], params: Record<string, unknown>) => AnyShape`
1557
+ - `HistoryOperationRegistry`: `{ operations: ReadonlyMap<string, OperationFn> }`
1558
+ - `HealingReport`: `{ isValid, alreadyValid, wiresHealed, facesHealed, solidHealed, steps, diagnostics }`
1559
+ - `InterferenceResult`: `{ hasInterference, minDistance, pointOnShape1, pointOnShape2 }`
1560
+ - `CurvatureResult`: `{ mean, gaussian, maxCurvature, minCurvature, maxDirection, minDirection }`
1561
+ - `ShapeHandle`: `{ wrapped, disposed, [Symbol.dispose]() }`
1562
+ - `Bounds3D`: `{ xMin, xMax, yMin, yMax, zMin, zMax }`
1563
+ - `ShapeDescription`: `{ kind, faceCount, edgeCount, wireCount, vertexCount, valid, bounds }`
1564
+
1565
+ ## Advanced Examples
1566
+
1567
+ ### Flanged pipe fitting with loft, sweep, fillet, shell, and boolean
1568
+
1569
+ ```typescript
1570
+ import {
1571
+ initFromOC, DisposalScope, unwrap,
1572
+ sketchCircle, sketchRectangle, sketchRoundedRectangle,
1573
+ Sketcher, draw, drawCircle, drawRectangle,
1574
+ helix, cylinder, sphere,
1575
+ exportSTEP, mesh, exportGlb,
1576
+ faceFinder, edgeFinder, getFaces,
1577
+ fuse, cut, shell, fillet,
1578
+ measureVolume, checkInterference, rotate,
1579
+ } from 'brepjs';
1580
+
1581
+ // 1. Flanged pipe: main tube + two flanges, hollowed out
1582
+ const pipeFitting = (() => {
1583
+ using _scope = new DisposalScope();
1584
+ // Main tube body
1585
+ const tube = cylinder(15, 100);
1586
+
1587
+ // Flanges at top and bottom
1588
+ const bottomFlange = cylinder(30, 5);
1589
+ const topFlange = cylinder(30, 5, { at: [0, 0, 95] });
1590
+
1591
+ // Fuse tube + flanges
1592
+ const step1 = unwrap(fuse(tube, bottomFlange));
1593
+ const body = unwrap(fuse(step1, topFlange));
1594
+
1595
+ // Hollow out: remove top face, shell to 2mm wall thickness
1596
+ // parallelTo('XY') finds faces with Z-normal; atDistance selects the one at Z=100
1597
+ const shellFaces = faceFinder().parallelTo('XY').atDistance(100, [0, 0, 0]).findAll(body);
1598
+ const hollowed = unwrap(shell(body, shellFaces, 2));
1599
+
1600
+ // Fillet the tube-to-flange transitions
1601
+ const filletEdges = edgeFinder().ofCurveType('CIRCLE').ofLength(2 * Math.PI * 15).findAll(hollowed);
1602
+ const filleted = unwrap(fillet(hollowed, filletEdges, 3));
1603
+
1604
+ // Bolt holes in each flange
1605
+ let result = filleted;
1606
+ for (let i = 0; i < 6; i++) {
1607
+ const angle = (360 / 6) * i;
1608
+ const hole = rotate(cylinder(3, 10, { at: [22, 0, -2] }), angle, { axis: [0, 0, 1] });
1609
+ result = unwrap(cut(result, hole));
1610
+ }
1611
+ // Same holes at top
1612
+ for (let i = 0; i < 6; i++) {
1613
+ const angle = (360 / 6) * i;
1614
+ const hole = rotate(cylinder(3, 10, { at: [22, 0, 90] }), angle, { axis: [0, 0, 1] });
1615
+ result = unwrap(cut(result, hole));
1616
+ }
1617
+ return result;
1618
+ })();
1619
+
1620
+ console.log('Volume:', measureVolume(pipeFitting));
1621
+ ```
1622
+
1623
+ ### Enclosure with drafted walls, snap-fit features, and embossed text
1624
+
1625
+ ```typescript
1626
+ import {
1627
+ DisposalScope, unwrap,
1628
+ sketchRoundedRectangle, sketchCircle, sketchRectangle,
1629
+ draw, drawText, loadFont, sketchText,
1630
+ cylinder, compoundSketchExtrude,
1631
+ fuse, cut, shell, fillet,
1632
+ faceFinder, edgeFinder, translate,
1633
+ } from 'brepjs';
1634
+
1635
+ await loadFont('/fonts/Roboto-Regular.ttf', 'Roboto');
1636
+
1637
+ const enclosure = (() => {
1638
+ using _scope = new DisposalScope();
1639
+ // Base box with rounded corners — sketch.extrude() returns Shape3D directly (not Result)
1640
+ const b = sketchRoundedRectangle(80, 50, 5).extrude(30);
1641
+
1642
+ // Shell: remove top face
1643
+ // parallelTo accepts StandardPlane strings ('XY', 'XZ', 'YZ') or Plane objects
1644
+ const shellFaces = faceFinder().parallelTo('XY').findAll(b);
1645
+ const shelled = unwrap(shell(b, shellFaces, 2));
1646
+
1647
+ // Fillet all vertical edges
1648
+ const vertEdges = edgeFinder().inDirection([0, 0, 1]).findAll(shelled);
1649
+ const filleted = unwrap(fillet(shelled, vertEdges, 1));
1650
+
1651
+ // Mounting bosses: 4 cylinders at corners inside the box
1652
+ const bossPositions: [number, number][] = [[30, 18], [-30, 18], [30, -18], [-30, -18]];
1653
+ let result = filleted;
1654
+ for (const [x, y] of bossPositions) {
1655
+ const bossShape = cylinder(3, 25, { at: [x, y, 2] });
1656
+ const hole = cylinder(1.2, 25, { at: [x, y, 2] });
1657
+ result = unwrap(cut(unwrap(fuse(result, bossShape)), hole));
1658
+ }
1659
+
1660
+ // Ventilation slots on side face
1661
+ for (let i = -2; i <= 2; i++) {
1662
+ const slot = translate(
1663
+ sketchRoundedRectangle(1.5, 10, 0.5, { plane: 'XZ' }).extrude(5),
1664
+ [0, -27, 15 + i * 3]
1665
+ );
1666
+ result = unwrap(cut(result, slot));
1667
+ }
1668
+
1669
+ // Embossed text on top — text produces CompoundSketch, use compoundSketchExtrude
1670
+ const textSketch = sketchText('brepjs', { fontSize: 8, fontFamily: 'Roboto' }, { plane: 'XY', origin: 30 });
1671
+ const textSolid = compoundSketchExtrude(textSketch, 1);
1672
+ result = unwrap(fuse(result, textSolid));
1673
+
1674
+ return result;
1675
+ })();
1676
+ ```
1677
+
1678
+ ### Parametric spring with sweep and interference check
1679
+
1680
+ ```typescript
1681
+ import {
1682
+ DisposalScope, unwrap,
1683
+ sketchCircle, sketchHelix,
1684
+ Sketcher, helix, cylinder,
1685
+ fuse, checkInterference,
1686
+ measureLength, describe,
1687
+ translate, rotate,
1688
+ } from 'brepjs';
1689
+
1690
+ const spring = (() => {
1691
+ using _scope = new DisposalScope();
1692
+ // Helix spine: pitch=8, height=60, radius=20
1693
+ const helixSketch = sketchHelix(8, 60, 20);
1694
+
1695
+ // Sweep a circular cross-section along the helix
1696
+ const coil = helixSketch.sweepSketch((plane, origin) =>
1697
+ new Sketcher(plane).movePointerTo([-1.5, 0]).sagittaArc(3, 0, 1.5).sagittaArc(-3, 0, 1.5).close()
1698
+ );
1699
+
1700
+ // Flat ends: cylinders at top and bottom
1701
+ const bottomEnd = cylinder(20, 2, { at: [0, 0, -2] });
1702
+ const topEnd = cylinder(20, 2, { at: [0, 0, 60] });
1703
+
1704
+ return unwrap(fuse(unwrap(fuse(coil, bottomEnd)), topEnd));
1705
+ })();
1706
+
1707
+ // Check if spring fits inside a housing cylinder
1708
+ const housing = cylinder(25, 70, { at: [0, 0, -5] });
1709
+ const interference = unwrap(checkInterference(spring, housing));
1710
+ console.log('Fits:', !interference.hasInterference, 'Gap:', interference.minDistance);
1711
+
1712
+ // Chain transforms
1713
+ const movedSpring = rotate(translate(spring, [100, 0, 0]), 90, { axis: [0, 1, 0] });
1714
+ ```
1715
+
1716
+ ### Multi-format export with materials
1717
+
1718
+ ```typescript
1719
+ import {
1720
+ mesh as meshFn, exportGlb, exportOBJ, exportThreeMF, exportDXF,
1721
+ drawProjection, exportSTEP, exportAssemblySTEP,
1722
+ toBufferGeometryData, toGroupedBufferGeometryData,
1723
+ } from 'brepjs';
1724
+
1725
+ // Mesh the shape for rendering
1726
+ const meshData = meshFn(part, { tolerance: 0.1, angularTolerance: 15, includeUVs: true });
1727
+
1728
+ // glTF with per-face PBR materials
1729
+ const materials = new Map();
1730
+ for (const group of meshData.faceGroups) {
1731
+ materials.set(group.faceId, {
1732
+ name: `face_${group.faceId}`,
1733
+ baseColor: [0.7, 0.7, 0.8, 1.0],
1734
+ metallic: 0.8,
1735
+ roughness: 0.3,
1736
+ });
1737
+ }
1738
+ const glb = exportGlb(meshData, { materials });
1739
+
1740
+ // 3MF for 3D printing
1741
+ const threemf = exportThreeMF(meshData, { name: 'MyPart', unit: 'millimeter' });
1742
+
1743
+ // OBJ for compatibility
1744
+ const obj = exportOBJ(meshData);
1745
+
1746
+ // STEP with assembly structure and colors
1747
+ const step = exportAssemblySTEP(
1748
+ [
1749
+ { shape: body, color: '#336699', name: 'Body' },
1750
+ { shape: lid, color: '#996633', name: 'Lid', alpha: 0.8 },
1751
+ ],
1752
+ { unit: 'millimeter' }
1753
+ );
1754
+
1755
+ // 2D projection → DXF for laser cutting
1756
+ const { visible } = drawProjection(part, 'top');
1757
+ const dxf = exportDXF([
1758
+ { type: 'POLYLINE', points: [[0,0], [100,0], [100,50], [0,50]], closed: true, layer: 'outline' },
1759
+ ], { layer: '0', curveSegments: 64 });
1760
+
1761
+ // Three.js integration
1762
+ const bufferData = toGroupedBufferGeometryData(meshData);
1763
+ // Use bufferData.position, .normal, .index, .groups with THREE.BufferGeometry
1764
+ ```
1765
+
1766
+ ### Functional API: immutable pipeline with healing
1767
+
1768
+ ```typescript
1769
+ import {
1770
+ unwrap,
1771
+ extrude, face, wire, line, circle,
1772
+ fuse, cut, fillet, shell,
1773
+ getEdges, getFaces, edgeFinder, faceFinder,
1774
+ autoHeal, isValid, describe,
1775
+ translate, rotate, linearPattern,
1776
+ mesh, exportGlb,
1777
+ } from 'brepjs';
1778
+
1779
+ // Build with functional API (no classes, all immutable operations)
1780
+ const base = extrude(
1781
+ unwrap(face(unwrap(wire([
1782
+ line([0,0,0], [40,0,0]),
1783
+ line([40,0,0], [40,30,0]),
1784
+ line([40,30,0], [0,30,0]),
1785
+ line([0,30,0], [0,0,0]),
1786
+ ])))),
1787
+ [0, 0, 20]
1788
+ );
1789
+
1790
+ // Find top face and cut mounting holes
1791
+ const topFaces = faceFinder().inDirection('Z').findAll(base);
1792
+ const holeFace = unwrap(face(unwrap(wire([circle(3, { at: [10, 15, 20] })]))));
1793
+ const hole1 = extrude(holeFace, [0, 0, -25]);
1794
+ const withHoles = unwrap(cut(base, hole1));
1795
+
1796
+ // Fillet just the top edges
1797
+ const topEdges = edgeFinder().atDistance(20, [20, 15, 0]).findAll(withHoles);
1798
+ const filleted = unwrap(fillet(withHoles, topEdges, 2));
1799
+
1800
+ // Shell it out
1801
+ const shellFaces = faceFinder().inDirection([0, 0, 1]).findAll(filleted);
1802
+ const shelled = unwrap(shell(filleted, shellFaces, 1.5));
1803
+
1804
+ // Validate and heal
1805
+ if (!isValid(shelled)) {
1806
+ const { shape: healed, report } = unwrap(autoHeal(shelled));
1807
+ console.log('Healed:', report.steps);
1808
+ }
1809
+
1810
+ // Describe the result
1811
+ const desc = describe(shelled);
1812
+ console.log(`${desc.kind}: ${desc.faceCount} faces, ${desc.edgeCount} edges, valid: ${desc.valid}`);
1813
+ ```
1814
+
1815
+ ---
1816
+
1817
+ # Internal Development Reference
1818
+
1819
+ For contributors and AI agents working on brepjs internals (not for library consumers).
1820
+
1821
+ ## Architecture Rules
1822
+
1823
+ - **Layer boundaries**: imports flow downward only (Layer 0 ← Layer 1 ← Layer 2 ← Layer 3). Enforced by `npm run check:boundaries`.
1824
+ - **Kernel access**: Layer 2+ code calls `getKernel().method(shape.wrapped)` — never access `.wrapped` methods directly. ESLint enforces this.
1825
+ - **Functional API**: All new code goes in `*Fns.ts` files. Never add methods to legacy class wrappers.
1826
+ - **Result types**: Layer 2+ functions return `Result<T, BrepError>` — never throw.
1827
+ - **ESM imports**: All `.ts` imports must use `.js` extensions.
1828
+ - **`withKernel(id, fn)` is sync-only** — async callbacks silently use the wrong kernel after the first `await`.
1829
+
1830
+ ## Verification (fastest → slowest)
1831
+
1832
+ | Command | What | Time |
1833
+ |---------|------|------|
1834
+ | `npm run typecheck` | Type errors | ~5s |
1835
+ | `npm run lint` | Style + banned patterns | ~3s |
1836
+ | `npm run check:boundaries` | Layer imports | ~2s |
1837
+ | `npm run format:check` | Formatting | ~2s |
1838
+ | `npx vitest run tests/<file>.test.ts` | Single test | ~5s |
1839
+ | `npm run test:affected` | Changed file tests | ~10s |
1840
+ | `npm run test:coverage` | Full suite | ~30s |
1841
+ | `npm run validate` | All above except single test and full suite | ~22s |
1842
+
1843
+ ## Functional API Pattern
1844
+
1845
+ Template for all `*Fns.ts` files. Follow exactly when adding new operations.
1846
+
1847
+ ```typescript
1848
+ // src/topology/booleanFns.ts — fuse() is the canonical example
1849
+
1850
+ /**
1851
+ * Fuse two 3D shapes together (boolean union). Returns a new shape.
1852
+ *
1853
+ * @param a - The first operand.
1854
+ * @param b - The second operand.
1855
+ * @param options - Boolean operation options.
1856
+ * @returns Ok with the fused shape, or Err if the result is not 3D.
1857
+ *
1858
+ * @example
1859
+ * ```ts
1860
+ * const result = fuse(box, cylinder);
1861
+ * if (isOk(result)) console.log(describe(result.value));
1862
+ * ```
1863
+ */
1864
+ export function fuse(
1865
+ a: Shape3D,
1866
+ b: Shape3D,
1867
+ { optimisation = 'none', simplify = false, signal }: BooleanOptions = {}
1868
+ ): Result<Shape3D> {
1869
+ if (signal?.aborted) throw signal.reason;
1870
+ // 1. Validate inputs
1871
+ const checkA = validateShape3D(a, 'fuse: first operand');
1872
+ if (isErr(checkA)) return checkA;
1873
+ const checkB = validateShape3D(b, 'fuse: second operand');
1874
+ if (isErr(checkB)) return checkB;
1875
+ // 2. Call kernel (never call .wrapped methods directly)
1876
+ const inputFaceHashes = collectInputFaceHashes([a, b]);
1877
+ const { shape: resultShape, evolution } = getKernel().fuseWithHistory(
1878
+ a.wrapped, b.wrapped, inputFaceHashes, HASH_CODE_MAX, { optimisation, simplify }
1879
+ );
1880
+ // 3. Cast result and check type
1881
+ const fuseResult = castToShape3D(resultShape, 'FUSE_NOT_3D', 'Fuse did not produce a 3D shape');
1882
+ // 4. Propagate metadata
1883
+ if (fuseResult.ok) {
1884
+ propagateOriginsFromEvolution(evolution, [a, b], fuseResult.value);
1885
+ propagateFaceTagsFromEvolution(evolution, [a, b], fuseResult.value);
1886
+ propagateColorsFromEvolution(evolution, [a, b], fuseResult.value);
1887
+ }
1888
+ return fuseResult;
1889
+ }
1890
+ ```
1891
+
1892
+ ## Test Pattern
1893
+
1894
+ ```typescript
1895
+ import { describe, expect, it, beforeAll } from 'vitest';
1896
+ import { initOC } from './setup.js';
1897
+ import { box, fuse, isOk, unwrap, measureVolume, isSolid } from '../src/index.js';
1898
+
1899
+ beforeAll(async () => {
1900
+ await initOC();
1901
+ }, 30000);
1902
+
1903
+ describe('fuse', () => {
1904
+ it('fuses two boxes', () => {
1905
+ const a = box(10, 10, 10);
1906
+ const b = box(10, 10, 10);
1907
+ const result = fuse(a, b);
1908
+ expect(isOk(result)).toBe(true);
1909
+ const shape = unwrap(result);
1910
+ expect(isSolid(shape)).toBe(true);
1911
+ expect(measureVolume(shape)).toBeCloseTo(1000, 0); // geometry: use toBeCloseTo
1912
+ });
1913
+ });
1914
+ ```
1915
+
1916
+ ## Branded Shape Types (src/core/shapeTypes.ts)
1917
+
1918
+ ```typescript
1919
+ type Dimension = '2D' | '3D';
1920
+
1921
+ // Base types — phantom D parameter for compile-time 2D/3D safety
1922
+ type Vertex<D extends Dimension = '3D'> = ShapeHandle & { readonly [__brand]: 'vertex'; readonly [__dim]: D };
1923
+ type Edge<D extends Dimension = '3D'> = ShapeHandle & { readonly [__brand]: 'edge'; readonly [__dim]: D };
1924
+ type Wire<D extends Dimension = '3D'> = ShapeHandle & { readonly [__brand]: 'wire'; readonly [__dim]: D };
1925
+ type Face<D extends Dimension = '3D'> = ShapeHandle & { readonly [__brand]: 'face'; readonly [__dim]: D };
1926
+ type Shell = ShapeHandle & { readonly [__brand]: 'shell' };
1927
+ type Solid = ShapeHandle & { readonly [__brand]: 'solid' };
1928
+ type CompSolid = ShapeHandle & { readonly [__brand]: 'compsolid' };
1929
+ type Compound<D extends Dimension = '3D'> = ShapeHandle & { readonly [__brand]: 'compound'; readonly [__dim]: D };
1930
+
1931
+ type AnyShape<D extends Dimension = '3D'> = Vertex<D> | Edge<D> | Wire<D> | Face<D> | Shell | Solid | CompSolid | Compound<D>;
1932
+ type Shape1D<D extends Dimension = '3D'> = Edge<D> | Wire<D>;
1933
+ type Shape3D = Shell | Solid | CompSolid | Compound<'3D'>;
1934
+
1935
+ // Validity brands — encode topological invariants at compile time
1936
+ type ClosedWire<D extends Dimension = '3D'> = Wire<D> & { readonly [__closed]: true };
1937
+ type OrientedFace<D extends Dimension = '3D'> = Face<D> & { readonly [__oriented]: true };
1938
+ type ManifoldShell = Shell & { readonly [__manifold]: true };
1939
+ type ValidSolid = Solid & { readonly [__valid]: true };
1940
+ (validity smart constructors return Result<T, string> — see closedWire/orientedFace/manifoldShell/validSolid above; check with isOk, extract with unwrap)
1941
+
1942
+ // Factories — always use these, never raw casts
1943
+ createVertex(ocShape): Vertex
1944
+ createEdge(ocShape): Edge
1945
+ castShape(ocShape): AnyShape // Auto-detects type
1946
+
1947
+ // Type guards
1948
+ isVertex(s): s is Vertex
1949
+ isEdge(s): s is Edge
1950
+ isSolid(s): s is Solid
1951
+ isShape3D(s): s is Shape3D
1952
+ is3D(s): s is AnyShape<'3D'>
1953
+ is2D(s): s is AnyShape<'2D'>
1954
+
1955
+ // Validity smart constructors
1956
+ closedWire(w): Result<ClosedWire, string>
1957
+ orientedFace(f): Result<OrientedFace, string>
1958
+ manifoldShell(s): Result<ManifoldShell, string>
1959
+ validSolid(s): Result<ValidSolid, string>
1960
+
1961
+ // Validity type guards
1962
+ isClosedWire(w): w is ClosedWire
1963
+ isOrientedFace(f): f is OrientedFace
1964
+ isManifoldShell(s): s is ManifoldShell
1965
+ isValidSolid(s): s is ValidSolid
1966
+ ```
1967
+
1968
+ ## All Error Codes (src/core/errors.ts)
1969
+
1970
+ ```typescript
1971
+ const BrepErrorCode = {
1972
+ // Kernel operation failures
1973
+ BSPLINE_FAILED, FACE_BUILD_FAILED, SWEEP_FAILED, LOFT_FAILED,
1974
+ FUSE_FAILED, CUT_FAILED, HEAL_NO_EFFECT,
1975
+
1976
+ // Validation
1977
+ ELLIPSE_RADII, FUSE_ALL_EMPTY, FILLET_NO_EDGES, CHAMFER_NO_EDGES,
1978
+ CHAMFER_ANGLE_NO_EDGES, CHAMFER_ANGLE_BAD_DISTANCE, CHAMFER_ANGLE_BAD_ANGLE,
1979
+ BEZIER_MIN_POINTS, POLYGON_MIN_POINTS, ZERO_LENGTH_EXTRUSION, ZERO_TWIST_ANGLE,
1980
+ LOFT_EMPTY, UNSUPPORTED_PROFILE, UNKNOWN_PLANE, NULL_SHAPE_INPUT,
1981
+ INVALID_FILLET_RADIUS, INVALID_CHAMFER_DISTANCE, INVALID_THICKNESS, ZERO_OFFSET,
1982
+ NO_EDGES, NO_FACES,
1983
+
1984
+ // Type cast
1985
+ FUSE_NOT_3D, CUT_NOT_3D, INTERSECT_NOT_3D, FUSE_ALL_NOT_3D, CUT_ALL_NOT_3D,
1986
+ LOFT_NOT_3D, SWEEP_NOT_3D, REVOLUTION_NOT_3D, FILLET_NOT_3D, CHAMFER_NOT_3D,
1987
+ CHAMFER_ANGLE_NOT_3D, CHAMFER_ANGLE_FAILED, SHELL_NOT_3D, OFFSET_NOT_3D,
1988
+ NULL_SHAPE, NO_WRAPPER, WELD_NOT_SHELL, SOLID_BUILD_FAILED, OFFSET_NOT_WIRE,
1989
+ UNKNOWN_SURFACE_TYPE, UNKNOWN_CURVE_TYPE, SWEEP_START_NOT_WIRE, SWEEP_END_NOT_WIRE,
1990
+
1991
+ // I/O
1992
+ STEP_EXPORT_FAILED, STEP_FILE_READ_ERROR, STL_EXPORT_FAILED, STL_FILE_READ_ERROR,
1993
+ STEP_IMPORT_FAILED, STL_IMPORT_FAILED, IGES_EXPORT_FAILED, IGES_IMPORT_FAILED,
1994
+ DXF_IMPORT_FAILED, OBJ_IMPORT_FAILED, THREEMF_IMPORT_FAILED,
1995
+
1996
+ // Computation
1997
+ PARAMETER_NOT_FOUND, INTERSECTION_FAILED, SELF_INTERSECTION_FAILED,
1998
+
1999
+ // Compound
2000
+ COMPOUND_NO_FACES, COMPOUND_FACE_NOT_FOUND,
2001
+
2002
+ // Query
2003
+ FINDER_NOT_UNIQUE,
2004
+
2005
+ // Hull
2006
+ HULL_EMPTY_INPUT, HULL_FAILED, HULL_DEGENERATE, HULL_NOT_3D,
2007
+
2008
+ // Minkowski
2009
+ MINKOWSKI_FAILED, MINKOWSKI_NULL_TOOL, MINKOWSKI_NOT_3D,
2010
+
2011
+ // Polyhedron
2012
+ POLYHEDRON_INSUFFICIENT_POINTS, POLYHEDRON_INSUFFICIENT_FACES,
2013
+ POLYHEDRON_INVALID_INDEX, POLYHEDRON_FAILED,
2014
+
2015
+ // Domain-specific
2016
+ ROOF_FAILED,
2017
+ MULTI_SWEEP_INSUFFICIENT_SECTIONS, MULTI_SWEEP_FAILED,
2018
+ GUIDED_SWEEP_FAILED,
2019
+ SURFACE_GRID_TOO_SMALL, SURFACE_GRID_JAGGED, SURFACE_FAILED,
2020
+ ASSEMBLY_MATE_INVALID, ASSEMBLY_SOLVE_FAILED, ASSEMBLY_NOT_CONVERGED,
2021
+ } as const;
2022
+ ```
2023
+
2024
+ ## KernelAdapter Interface Summary (src/kernel/types.ts)
2025
+
2026
+ The KernelAdapter has ~164 methods. Key categories:
2027
+
2028
+ **Boolean ops**: `fuse`, `cut`, `intersect`, `section`, `fuseAll`, `cutAll`
2029
+ **Primitives**: `makeBox`, `makeCylinder`, `makeSphere`, `makeCone`, `makeTorus`, `makeEllipsoid`
2030
+ **Edges/Wires**: `makeLineEdge`, `makeCircleEdge`, `makeArcEdge`, `makeBezierEdge`, `makeHelixWire`, `makeWire`
2031
+ **Extrusion/Sweep**: `extrude`, `revolve`, `loft`, `sweep`, `sweepPipeShell`, `loftAdvanced`
2032
+ **Modification**: `fillet`, `chamfer`, `chamferDistAngle`, `shell`, `thicken`, `offset`
2033
+ **Transforms**: `translate`, `rotate`, `mirror`, `scale`, `generalTransform`
2034
+ **History variants**: `fuseWithHistory`, `cutWithHistory`, `filletWithHistory`, etc. — return `{ shape, evolution }`
2035
+ **Measurement**: `volume`, `area`, `length`, `centerOfMass`, `boundingBox`
2036
+ **Topology**: `iterShapes`, `shapeType`, `isSame`, `hashCode`, `downcast`
2037
+ **Geometry queries**: `surfaceType`, `surfaceNormal`, `curveTangent`, `curveType`, `vertexPosition`
2038
+ **I/O**: `exportSTEP`, `importSTEP`, `exportSTL`, `importSTL`, `exportIGES`, `importIGES`
2039
+ **Repair**: `isValid`, `sew`, `healSolid`, `fixShape`, `fixSelfIntersection`
2040
+ **Meshing**: `mesh`, `meshEdges`
2041
+
2042
+ All methods accept `KernelShape` (opaque handle) — callers pass `shape.wrapped`.
2043
+
2044
+ ## Gotchas
2045
+
2046
+ - OCCT Emscripten returns enum objects with `.value` — extract: `typeof val === 'number' ? val : Number(val?.value ?? val)`
2047
+ - `autoHeal` short-circuits valid shapes: `report.alreadyValid=true`, no sew/heal diagnostics run
2048
+ - Assembly solver uses original face coordinates — distance constraints don't compose across multiple mates
2049
+ - `Uint32Array` from WASM: always convert to regular `Array` before passing to kernel methods
2050
+ - `DisposalScope` disposes in LIFO order — register dependencies after their dependees
2051
+ - `noUncheckedIndexedAccess` enabled — array indexing returns `T | undefined`, add bounds checks
2052
+ - `exactOptionalPropertyTypes` enabled — use `prop?: T | undefined` for optional fields