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.
- package/CHANGELOG.md +20 -0
- package/README.md +26 -0
- package/dist/brepjs-verify.cjs +1 -1
- package/dist/brepjs-verify.js +1 -1
- package/dist/cli/main.cjs +1 -1
- package/dist/cli/main.js +1 -1
- package/dist/{diff-CZ4mLtrf.cjs → diff-BNmCp_8I.cjs} +90 -5
- package/dist/{diff-D7ZBNRJG.js → diff-D5U3Ie2F.js} +90 -5
- package/dist/snapshot/registry.cjs +1 -1
- package/dist/snapshot/registry.js +1 -1
- package/dist/snapshot/static.cjs +1 -1
- package/dist/snapshot/static.d.ts +1 -1
- package/dist/snapshot/static.js +1 -1
- package/dist/verify/brepjsRuntime.d.ts +1 -0
- package/dist/verify/expected.d.ts +15 -0
- package/package.json +8 -3
- package/reference/llms-full.txt +2052 -0
- package/reference/llms.txt +1810 -0
- package/viewer/dist/assets/{brepjs-CDZqKweN.js → brepjs-CI5VXw8W.js} +19 -19
- package/viewer/dist/assets/{index-B8QUQDqM.js → index-CiN0lKoi.js} +1 -1
- package/viewer/dist/assets/{kernelWorker-C6s5i9JH.js → kernelWorker-BtcMpY8t.js} +1 -1
- package/viewer/dist/index.html +1 -1
|
@@ -0,0 +1,1810 @@
|
|
|
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
|
+
wireLoop(edgesOrWires): Result<ClosedWire> // Assemble + verify closure
|
|
945
|
+
|
|
946
|
+
// 2D faces (require ClosedWire, return OrientedFace)
|
|
947
|
+
face(wire: ClosedWire, holes?: ClosedWire[]): Result<OrientedFace>
|
|
948
|
+
filledFace(wire: ClosedWire): Result<OrientedFace>
|
|
949
|
+
subFace(originFace, wire: ClosedWire): OrientedFace
|
|
950
|
+
addHoles(face, holes: ClosedWire[]): OrientedFace
|
|
951
|
+
polygon(points): Result<OrientedFace>
|
|
952
|
+
|
|
953
|
+
// 3D solids (return ValidSolid)
|
|
954
|
+
box(width, depth, height, { at?, centered? }?): ValidSolid
|
|
955
|
+
sphere(radius, { at? }?): ValidSolid
|
|
956
|
+
cylinder(radius, height, { at?, axis?, centered? }?): ValidSolid
|
|
957
|
+
cone(bottomRadius, topRadius, height, { at?, axis?, centered? }?): ValidSolid
|
|
958
|
+
torus(majorRadius, minorRadius, { at?, axis? }?): ValidSolid
|
|
959
|
+
ellipsoid(rx, ry, rz, { at? }?): ValidSolid
|
|
960
|
+
makeBaseBox(x, y, z): Shape3D
|
|
961
|
+
solid(facesOrShells): Result<ValidSolid>
|
|
962
|
+
|
|
963
|
+
// Vertex
|
|
964
|
+
vertex(point): Vertex
|
|
965
|
+
|
|
966
|
+
// Compound & shell
|
|
967
|
+
compound(shapes): Compound
|
|
968
|
+
sewShells(facesOrShells, ignoreType?): Result<Shell>
|
|
969
|
+
offsetFace(face, offset, tolerance?): Result<Shape3D>
|
|
970
|
+
```
|
|
971
|
+
|
|
972
|
+
## Constructive Geometry
|
|
973
|
+
|
|
974
|
+
```typescript
|
|
975
|
+
import { hull, minkowski, polyhedron, surfaceFromGrid, surfaceFromImage, roof } from 'brepjs';
|
|
976
|
+
|
|
977
|
+
hull(shapes, options?): Result<Solid> // Convex hull of shapes
|
|
978
|
+
minkowski(shape, tool, options?): Result<Solid> // Minkowski sum
|
|
979
|
+
polyhedron(points, faces, options?): Result<Solid> // From vertices + face indices
|
|
980
|
+
surfaceFromGrid(heights, options?): Result<AnyShape> // Height-map surface
|
|
981
|
+
surfaceFromImage(blob, options?): Promise<Result<AnyShape>> // Image-based surface
|
|
982
|
+
roof(wire, { angle? }?): Result<Solid> // Roof from closed wire outline
|
|
983
|
+
```
|
|
984
|
+
|
|
985
|
+
`SurfaceFromGridOptions`: `{ width?, depth?, scaleZ? }`
|
|
986
|
+
`SurfaceFromImageOptions`: extends grid options + `{ channel?: 'r' | 'g' | 'b' | 'luminance', downsample? }`
|
|
987
|
+
|
|
988
|
+
### Advanced Sweeps
|
|
989
|
+
|
|
990
|
+
```typescript
|
|
991
|
+
import { multiSectionSweep, guidedSweep } from 'brepjs';
|
|
992
|
+
|
|
993
|
+
multiSectionSweep(sections, spine, options?): Result<Solid | Shell>
|
|
994
|
+
// sections: { wire: Wire, location?: number }[]
|
|
995
|
+
// MultiSweepOptions: { solid?, ruled?, tolerance? }
|
|
996
|
+
|
|
997
|
+
guidedSweep(profile, spine, guides, options?): Result<Solid | Shell>
|
|
998
|
+
// GuidedSweepOptions: { transition?: 'transformed' | 'round' | 'right', solid?, tolerance? }
|
|
999
|
+
```
|
|
1000
|
+
|
|
1001
|
+
## Shape Coloring & Tagging
|
|
1002
|
+
|
|
1003
|
+
```typescript
|
|
1004
|
+
import { colorFaces, colorShape, getFaceColor, getShapeColor } from 'brepjs';
|
|
1005
|
+
|
|
1006
|
+
colorFaces(shape, faces, color): T // Color specific faces
|
|
1007
|
+
colorShape(shape, color): T // Color entire shape
|
|
1008
|
+
getFaceColor(shape, face): Color | undefined // Get face color
|
|
1009
|
+
getShapeColor(shape): Color | undefined // Get shape color
|
|
1010
|
+
// ColorInput: string ('#ff0000') | [r, g, b] | [r, g, b, a]
|
|
1011
|
+
```
|
|
1012
|
+
|
|
1013
|
+
```typescript
|
|
1014
|
+
import { tagFaces, findFacesByTag, getFaceTags, setTagMetadata, getTagMetadata } from 'brepjs';
|
|
1015
|
+
|
|
1016
|
+
tagFaces(shape, selector, tag): AnyShape // Tag faces by array or predicate
|
|
1017
|
+
findFacesByTag(shape, tag): Face[] // Find faces by tag name
|
|
1018
|
+
getFaceTags(shape): Map<string, Face[]> // Get all tags
|
|
1019
|
+
setTagMetadata(shape, tag, metadata): AnyShape // Attach metadata to a tag
|
|
1020
|
+
getTagMetadata(shape, tag): Record<string, unknown> | undefined
|
|
1021
|
+
```
|
|
1022
|
+
|
|
1023
|
+
## 2D Blueprints & Curves
|
|
1024
|
+
|
|
1025
|
+
The `Drawing` class supports 2D boolean operations and transformations:
|
|
1026
|
+
|
|
1027
|
+
```typescript
|
|
1028
|
+
const plate = drawRectangle(100, 50);
|
|
1029
|
+
const hole = drawCircle(10).translate(20, 0); // Drawing.translate is still available
|
|
1030
|
+
const withHole = plate.cut(hole); // Drawing.cut is still available
|
|
1031
|
+
const filleted = withHole.fillet(3); // Drawing.fillet is still available
|
|
1032
|
+
const svg = filleted.toSVG();
|
|
1033
|
+
```
|
|
1034
|
+
|
|
1035
|
+
Functional Blueprint API:
|
|
1036
|
+
```typescript
|
|
1037
|
+
import {
|
|
1038
|
+
createBlueprint, getBounds2D, getOrientation2D,
|
|
1039
|
+
translate2D, rotate2D, scale2D, mirror2D,
|
|
1040
|
+
stretch2D, toSVGPathD, isInside2D,
|
|
1041
|
+
sketchOnPlane2D, sketchOnFace2D
|
|
1042
|
+
} from 'brepjs';
|
|
1043
|
+
|
|
1044
|
+
createBlueprint(curves): Blueprint
|
|
1045
|
+
getBounds2D(bp): BoundingBox2d
|
|
1046
|
+
getOrientation2D(bp): 'clockwise' | 'counterClockwise'
|
|
1047
|
+
translate2D(bp, dx, dy): Blueprint
|
|
1048
|
+
rotate2D(bp, angle, center?): Blueprint
|
|
1049
|
+
scale2D(bp, factor, center?): Blueprint
|
|
1050
|
+
mirror2D(bp, dir, origin?, mode?): Blueprint
|
|
1051
|
+
stretch2D(bp, ratio, direction, origin?): Blueprint
|
|
1052
|
+
toSVGPathD(bp): string
|
|
1053
|
+
isInside2D(bp, point): boolean
|
|
1054
|
+
sketchOnPlane2D(bp, plane?, origin?): Sketch
|
|
1055
|
+
sketchOnFace2D(bp, face, scaleMode?): Sketch
|
|
1056
|
+
```
|
|
1057
|
+
|
|
1058
|
+
Blueprint construction helpers:
|
|
1059
|
+
```typescript
|
|
1060
|
+
import { polysidesBlueprint, roundedRectangleBlueprint, organiseBlueprints } from 'brepjs';
|
|
1061
|
+
|
|
1062
|
+
polysidesBlueprint(radius, sidesCount, sagitta?): Blueprint // Regular polygon as a Blueprint (lower-level than drawPolysides)
|
|
1063
|
+
roundedRectangleBlueprint(width, height, r?): Blueprint // Rounded rect as Blueprint. r: number | {rx?, ry?}
|
|
1064
|
+
organiseBlueprints(blueprints: Blueprint[]): Blueprints // Group flat blueprints into compound blueprints with hole detection
|
|
1065
|
+
```
|
|
1066
|
+
|
|
1067
|
+
Low-level Blueprint boolean operations (single blueprint → single blueprint):
|
|
1068
|
+
```typescript
|
|
1069
|
+
import { fuseBlueprints, cutBlueprints, intersectBlueprints } from 'brepjs';
|
|
1070
|
+
|
|
1071
|
+
fuseBlueprints(first: Blueprint, second: Blueprint): null | Blueprint | Blueprints
|
|
1072
|
+
cutBlueprints(first: Blueprint, second: Blueprint): null | Blueprint | Blueprints
|
|
1073
|
+
intersectBlueprints(first: Blueprint, second: Blueprint): null | Blueprint | Blueprints
|
|
1074
|
+
```
|
|
1075
|
+
|
|
1076
|
+
2D Boolean (functional):
|
|
1077
|
+
```typescript
|
|
1078
|
+
import { fuse2D, cut2D, intersect2D } from 'brepjs';
|
|
1079
|
+
|
|
1080
|
+
fuse2D(first, second): Shape2D
|
|
1081
|
+
cut2D(first, second): Shape2D
|
|
1082
|
+
intersect2D(first, second): Shape2D
|
|
1083
|
+
// Shape2D = Blueprint | Blueprints | CompoundBlueprint | null
|
|
1084
|
+
```
|
|
1085
|
+
|
|
1086
|
+
2D Curve functions:
|
|
1087
|
+
```typescript
|
|
1088
|
+
import {
|
|
1089
|
+
reverseCurve, curve2dBoundingBox, curve2dFirstPoint, curve2dLastPoint,
|
|
1090
|
+
curve2dSplitAt, curve2dParameter, curve2dTangentAt, curve2dIsOnCurve, curve2dDistanceFrom
|
|
1091
|
+
} from 'brepjs';
|
|
1092
|
+
```
|
|
1093
|
+
|
|
1094
|
+
## Projection & Camera
|
|
1095
|
+
|
|
1096
|
+
Project 3D shapes to 2D for technical drawings:
|
|
1097
|
+
|
|
1098
|
+
```typescript
|
|
1099
|
+
import { drawProjection, createCamera, cameraLookAt, unwrap } from 'brepjs';
|
|
1100
|
+
|
|
1101
|
+
// Quick projection from a named plane
|
|
1102
|
+
const { visible, hidden } = drawProjection(shape, 'front');
|
|
1103
|
+
const svg = visible.toSVG();
|
|
1104
|
+
|
|
1105
|
+
// Custom camera
|
|
1106
|
+
const camera = unwrap(createCamera([100, 100, 100], [0, 0, -1]));
|
|
1107
|
+
const lookingAt = unwrap(cameraLookAt(camera, [0, 0, 0]));
|
|
1108
|
+
const projected = drawProjection(shape, lookingAt);
|
|
1109
|
+
```
|
|
1110
|
+
|
|
1111
|
+
Camera API:
|
|
1112
|
+
```typescript
|
|
1113
|
+
import { createCamera, cameraLookAt, cameraFromPlane, projectEdges } from 'brepjs';
|
|
1114
|
+
|
|
1115
|
+
createCamera(position?, direction?, xAxis?): Result<Camera>
|
|
1116
|
+
cameraLookAt(camera, target): Result<Camera>
|
|
1117
|
+
cameraFromPlane(planeName): Result<Camera>
|
|
1118
|
+
projectEdges(shape, camera, withHiddenLines?): { visible: Edge[], hidden: Edge[] }
|
|
1119
|
+
```
|
|
1120
|
+
|
|
1121
|
+
Additional projection helpers:
|
|
1122
|
+
```typescript
|
|
1123
|
+
import { isProjectionPlane, makeProjectedEdges } from 'brepjs';
|
|
1124
|
+
|
|
1125
|
+
isProjectionPlane(plane): plane is ProjectionPlane // Type guard for ProjectionPlane strings
|
|
1126
|
+
makeProjectedEdges(shape, camera, withHiddenLines?): { visible: Edge[], hidden: Edge[] } // HLR projection
|
|
1127
|
+
```
|
|
1128
|
+
|
|
1129
|
+
## Text
|
|
1130
|
+
|
|
1131
|
+
Render text as 2D outlines (requires font loading):
|
|
1132
|
+
|
|
1133
|
+
```typescript
|
|
1134
|
+
import { loadFont, getFont, drawText, sketchText, textBlueprints } from 'brepjs';
|
|
1135
|
+
|
|
1136
|
+
await loadFont('/fonts/Roboto-Regular.ttf', 'Roboto');
|
|
1137
|
+
|
|
1138
|
+
// 2D text drawing
|
|
1139
|
+
const text2d = drawText('Hello', { fontSize: 20, fontFamily: 'Roboto' });
|
|
1140
|
+
|
|
1141
|
+
// 3D text (sketch on plane, ready to extrude)
|
|
1142
|
+
const text3d = sketchText('Hello', { fontSize: 20, fontFamily: 'Roboto' }, { plane: 'XY' });
|
|
1143
|
+
|
|
1144
|
+
// Get raw blueprints
|
|
1145
|
+
const bps = textBlueprints('Hello', { fontSize: 20, fontFamily: 'Roboto' });
|
|
1146
|
+
```
|
|
1147
|
+
|
|
1148
|
+
## Assembly Tree
|
|
1149
|
+
|
|
1150
|
+
Build hierarchical assemblies with transforms:
|
|
1151
|
+
|
|
1152
|
+
```typescript
|
|
1153
|
+
import {
|
|
1154
|
+
createAssemblyNode, addChild, removeChild, updateNode,
|
|
1155
|
+
findNode, walkAssembly, countNodes, collectShapes
|
|
1156
|
+
} from 'brepjs';
|
|
1157
|
+
|
|
1158
|
+
const root = createAssemblyNode('Root');
|
|
1159
|
+
const part = createAssemblyNode('Part', {
|
|
1160
|
+
shape: myShape,
|
|
1161
|
+
translate: [10, 0, 0],
|
|
1162
|
+
rotate: { angle: 45, axis: [0, 0, 1] },
|
|
1163
|
+
metadata: { material: 'steel' }
|
|
1164
|
+
});
|
|
1165
|
+
|
|
1166
|
+
const assembly = addChild(root, part);
|
|
1167
|
+
const updated = updateNode(assembly, { translate: [20, 0, 0] });
|
|
1168
|
+
const found = findNode(assembly, 'Part');
|
|
1169
|
+
walkAssembly(assembly, (node, depth) => console.log(node.name, depth));
|
|
1170
|
+
const count = countNodes(assembly);
|
|
1171
|
+
const shapes = collectShapes(assembly);
|
|
1172
|
+
const pruned = removeChild(assembly, 'Part');
|
|
1173
|
+
```
|
|
1174
|
+
|
|
1175
|
+
### Assembly Mates (Constraints)
|
|
1176
|
+
|
|
1177
|
+
```typescript
|
|
1178
|
+
import { addMate, solveAssembly } from 'brepjs';
|
|
1179
|
+
|
|
1180
|
+
// Add a constraint between parts
|
|
1181
|
+
const constrained = addMate(assembly, {
|
|
1182
|
+
type: 'coincident',
|
|
1183
|
+
entityA: { node: 'Lid', face: topFace },
|
|
1184
|
+
entityB: { node: 'Body', face: bottomFace },
|
|
1185
|
+
});
|
|
1186
|
+
|
|
1187
|
+
// Solve constraints to compute transforms
|
|
1188
|
+
const solved = unwrap(solveAssembly(constrained));
|
|
1189
|
+
// solved: { transforms: Map<string, { position, rotation }>, dof, converged }
|
|
1190
|
+
```
|
|
1191
|
+
|
|
1192
|
+
Mate types: `'coincident'`, `'concentric'`, `'distance'`, `'angle'`, `'fixed'`
|
|
1193
|
+
|
|
1194
|
+
## Parametric History
|
|
1195
|
+
|
|
1196
|
+
Track modeling operations for undo/replay:
|
|
1197
|
+
|
|
1198
|
+
```typescript
|
|
1199
|
+
import {
|
|
1200
|
+
createHistory, addStep, undoLast, findStep,
|
|
1201
|
+
getHistoryShape, stepCount, stepsFrom,
|
|
1202
|
+
registerShape, createRegistry, registerOperation,
|
|
1203
|
+
replayHistory, replayFrom, modifyStep
|
|
1204
|
+
} from 'brepjs';
|
|
1205
|
+
|
|
1206
|
+
// Create history and register operations
|
|
1207
|
+
let history = createHistory();
|
|
1208
|
+
let registry = createRegistry();
|
|
1209
|
+
registry = registerOperation(registry, 'extrude', (inputs, params) => {
|
|
1210
|
+
return sketchRectangle(params.w, params.h).extrude(params.depth);
|
|
1211
|
+
});
|
|
1212
|
+
|
|
1213
|
+
// Add steps
|
|
1214
|
+
history = registerShape(history, 'base', baseShape);
|
|
1215
|
+
history = addStep(history, {
|
|
1216
|
+
id: 'step1', type: 'extrude',
|
|
1217
|
+
parameters: { w: 10, h: 10, depth: 20 },
|
|
1218
|
+
inputIds: ['base'], outputId: 'extruded'
|
|
1219
|
+
}, resultShape);
|
|
1220
|
+
|
|
1221
|
+
// Undo, replay, modify
|
|
1222
|
+
history = undoLast(history);
|
|
1223
|
+
const replayed = replayHistory(history, registry);
|
|
1224
|
+
const modified = modifyStep(history, 'step1', { depth: 30 }, registry);
|
|
1225
|
+
```
|
|
1226
|
+
|
|
1227
|
+
## Meshing
|
|
1228
|
+
|
|
1229
|
+
Convert shapes to triangle meshes for rendering:
|
|
1230
|
+
|
|
1231
|
+
```typescript
|
|
1232
|
+
import { mesh, meshEdges } from 'brepjs';
|
|
1233
|
+
|
|
1234
|
+
const m = mesh(shape, { tolerance: 0.5, angularTolerance: 20, includeUVs: true });
|
|
1235
|
+
// m.vertices: Float32Array (flat xyz)
|
|
1236
|
+
// m.triangles: Uint32Array (triangle indices)
|
|
1237
|
+
// m.normals: Float32Array (flat normals)
|
|
1238
|
+
// m.uvs: Float32Array (UV coordinates when includeUVs: true)
|
|
1239
|
+
// m.faceGroups: {start, count, faceId}[]
|
|
1240
|
+
|
|
1241
|
+
const edgeMesh = meshEdges(shape, { tolerance: 0.5 });
|
|
1242
|
+
// edgeMesh.lines: number[]
|
|
1243
|
+
// edgeMesh.edgeGroups: {start, count, edgeId}[]
|
|
1244
|
+
|
|
1245
|
+
// Full mesh options (with defaults):
|
|
1246
|
+
// mesh(shape, {
|
|
1247
|
+
// tolerance: 1e-3, // linear deflection
|
|
1248
|
+
// angularTolerance: 0.1, // angular deflection (radians)
|
|
1249
|
+
// skipNormals: false, // omit normals from output
|
|
1250
|
+
// includeUVs: false, // include UV coordinates per-vertex
|
|
1251
|
+
// cache: true, // cache results (WeakMap by shape)
|
|
1252
|
+
// signal?: AbortSignal, // abort between face iterations
|
|
1253
|
+
// })
|
|
1254
|
+
```
|
|
1255
|
+
|
|
1256
|
+
Mesh caching:
|
|
1257
|
+
```typescript
|
|
1258
|
+
import { clearMeshCache, createMeshCache } from 'brepjs';
|
|
1259
|
+
|
|
1260
|
+
clearMeshCache(); // Clear global cache
|
|
1261
|
+
const cache = createMeshCache(); // Create isolated cache
|
|
1262
|
+
```
|
|
1263
|
+
|
|
1264
|
+
### Three.js Integration
|
|
1265
|
+
|
|
1266
|
+
```typescript
|
|
1267
|
+
import { toBufferGeometryData, toLineGeometryData, toGroupedBufferGeometryData } from 'brepjs';
|
|
1268
|
+
|
|
1269
|
+
const bufferData = toBufferGeometryData(mesh);
|
|
1270
|
+
// { position: Float32Array, normal: Float32Array, index: Uint32Array }
|
|
1271
|
+
|
|
1272
|
+
const lineData = toLineGeometryData(edgeMesh);
|
|
1273
|
+
// { position: Float32Array }
|
|
1274
|
+
|
|
1275
|
+
const grouped = toGroupedBufferGeometryData(mesh);
|
|
1276
|
+
// extends bufferData with: groups: [{start, count, materialIndex, faceId}]
|
|
1277
|
+
```
|
|
1278
|
+
|
|
1279
|
+
## Memory Management
|
|
1280
|
+
|
|
1281
|
+
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:
|
|
1282
|
+
|
|
1283
|
+
### Pattern 1: `using` keyword (recommended, TS 5.9+)
|
|
1284
|
+
|
|
1285
|
+
The `using` declaration automatically disposes shapes when they go out of scope via TC39 `Symbol.dispose`:
|
|
1286
|
+
|
|
1287
|
+
```typescript
|
|
1288
|
+
import { box, cylinder, cut, unwrap } from 'brepjs';
|
|
1289
|
+
|
|
1290
|
+
{
|
|
1291
|
+
using b = box(10, 10, 10);
|
|
1292
|
+
using hole = cylinder(3, 15);
|
|
1293
|
+
const result = unwrap(cut(b, hole));
|
|
1294
|
+
// b and hole are automatically freed at block end
|
|
1295
|
+
// result survives because it was not declared with `using`
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
// Works in loops too
|
|
1299
|
+
for (let i = 0; i < 100; i++) {
|
|
1300
|
+
using temp = box(1, 1, 1);
|
|
1301
|
+
processBox(temp);
|
|
1302
|
+
// temp freed each iteration — no memory leak
|
|
1303
|
+
}
|
|
1304
|
+
```
|
|
1305
|
+
|
|
1306
|
+
Requires TypeScript 5.9+ with `"lib": ["ES2022", "ESNext.Disposable"]` and Node.js 20+ or modern browsers.
|
|
1307
|
+
|
|
1308
|
+
### Pattern 2: `DisposalScope` (deterministic, multiple temporaries)
|
|
1309
|
+
|
|
1310
|
+
```typescript
|
|
1311
|
+
import { DisposalScope, box, cylinder, cut, unwrap } from 'brepjs';
|
|
1312
|
+
|
|
1313
|
+
function buildPart() {
|
|
1314
|
+
using scope = new DisposalScope();
|
|
1315
|
+
const b = scope.register(box(10, 10, 10)); // register for cleanup
|
|
1316
|
+
const hole = scope.register(cylinder(3, 15)); // register for cleanup
|
|
1317
|
+
return unwrap(cut(b, hole)); // result escapes; b and hole freed when scope exits
|
|
1318
|
+
}
|
|
1319
|
+
```
|
|
1320
|
+
|
|
1321
|
+
### Pattern 3: `withScope()` (scoped, returns result)
|
|
1322
|
+
|
|
1323
|
+
```typescript
|
|
1324
|
+
import { withScope, box, cylinder, cut, unwrap } from 'brepjs';
|
|
1325
|
+
|
|
1326
|
+
const result = withScope((scope) => {
|
|
1327
|
+
const b = scope.register(box(10, 10, 10));
|
|
1328
|
+
const hole = scope.register(cylinder(3, 15));
|
|
1329
|
+
return unwrap(cut(b, hole)); // returned value survives the scope
|
|
1330
|
+
});
|
|
1331
|
+
```
|
|
1332
|
+
|
|
1333
|
+
`withScopeResult(fn)` and `withScopeResultAsync(fn)` are variants that accept functions returning `Result<T>` — useful inside operations that already use Result-based error handling:
|
|
1334
|
+
```typescript
|
|
1335
|
+
import { withScopeResult } from 'brepjs';
|
|
1336
|
+
|
|
1337
|
+
const result: Result<Solid> = withScopeResult((scope) => {
|
|
1338
|
+
const temp = scope.register(cylinder(5, 20));
|
|
1339
|
+
return cut(myBox, temp); // returns Result directly
|
|
1340
|
+
});
|
|
1341
|
+
```
|
|
1342
|
+
|
|
1343
|
+
### Low-level handles
|
|
1344
|
+
|
|
1345
|
+
```typescript
|
|
1346
|
+
import { createHandle, createKernelHandle } from 'brepjs';
|
|
1347
|
+
|
|
1348
|
+
const handle = createHandle(ocShape); // ShapeHandle: { wrapped, disposed, [Symbol.dispose] }
|
|
1349
|
+
const kernelHandle = createKernelHandle(ocObj); // KernelHandle<T>: { value, disposed, [Symbol.dispose] }
|
|
1350
|
+
```
|
|
1351
|
+
|
|
1352
|
+
`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.
|
|
1353
|
+
|
|
1354
|
+
## Error Handling
|
|
1355
|
+
|
|
1356
|
+
Many operations return `Result<T, BrepError>`:
|
|
1357
|
+
|
|
1358
|
+
```typescript
|
|
1359
|
+
import { ok, err, OK, isOk, isErr, unwrap, unwrapOr, unwrapOrElse, match, map, andThen, collect, tryCatch, pipeline } from 'brepjs';
|
|
1360
|
+
|
|
1361
|
+
// Construction
|
|
1362
|
+
ok(value): Ok<T>
|
|
1363
|
+
err(error): Err<E>
|
|
1364
|
+
OK // Pre-built Ok<undefined> for void success
|
|
1365
|
+
|
|
1366
|
+
// Type guards
|
|
1367
|
+
isOk(result): result is Ok<T>
|
|
1368
|
+
isErr(result): result is Err<E>
|
|
1369
|
+
|
|
1370
|
+
// Extraction
|
|
1371
|
+
unwrap(result): T // throws on Err
|
|
1372
|
+
unwrapOr(result, defaultValue): T
|
|
1373
|
+
unwrapOrElse(result, fn): T
|
|
1374
|
+
unwrapErr(result): E // throws on Ok
|
|
1375
|
+
|
|
1376
|
+
// Combinators
|
|
1377
|
+
map(result, fn): Result<U, E>
|
|
1378
|
+
mapErr(result, fn): Result<T, F>
|
|
1379
|
+
andThen(result, fn): Result<U, E> // flatMap alias
|
|
1380
|
+
collect(results): Result<T[], E> // All-or-nothing
|
|
1381
|
+
|
|
1382
|
+
// Pattern matching
|
|
1383
|
+
match(result, { ok: fn, err: fn }): U
|
|
1384
|
+
|
|
1385
|
+
// Try-catch boundary
|
|
1386
|
+
tryCatch(fn, mapError): Result<T, E>
|
|
1387
|
+
tryCatchAsync(fn, mapError): Promise<Result<T, E>>
|
|
1388
|
+
|
|
1389
|
+
// Pipeline
|
|
1390
|
+
pipeline(input).then(fn).then(fn).result // Chain Result operations
|
|
1391
|
+
```
|
|
1392
|
+
|
|
1393
|
+
BrepError structure:
|
|
1394
|
+
```typescript
|
|
1395
|
+
interface BrepError {
|
|
1396
|
+
kind: BrepErrorKind; // 'KERNEL_OPERATION' | 'VALIDATION' | 'TYPE_CAST' | 'SKETCHER_STATE' | 'MODULE_INIT' | 'COMPUTATION' | 'IO' | 'QUERY'
|
|
1397
|
+
code: string; // e.g. 'FUSE_FAILED', 'STEP_IMPORT_FAILED'
|
|
1398
|
+
message: string;
|
|
1399
|
+
cause?: unknown;
|
|
1400
|
+
metadata?: Record<string, unknown>;
|
|
1401
|
+
}
|
|
1402
|
+
```
|
|
1403
|
+
|
|
1404
|
+
Error constructors: `kernelError(code, msg, cause?, meta?)`, `validationError(...)`, `typeCastError(...)`, `ioError(...)`, `computationError(...)`, `queryError(...)`, `sketcherStateError(...)`, `moduleInitError(...)`
|
|
1405
|
+
|
|
1406
|
+
## Worker Protocol
|
|
1407
|
+
|
|
1408
|
+
Off-main-thread CAD operations:
|
|
1409
|
+
|
|
1410
|
+
```typescript
|
|
1411
|
+
import {
|
|
1412
|
+
createWorkerClient, createOperationRegistry, registerHandler, createWorkerHandler,
|
|
1413
|
+
createTaskQueue, enqueueTask, dequeueTask, pendingCount, isQueueEmpty, rejectAll,
|
|
1414
|
+
isInitRequest, isOperationRequest, isDisposeRequest, isSuccessResponse, isErrorResponse
|
|
1415
|
+
} from 'brepjs';
|
|
1416
|
+
|
|
1417
|
+
// Client side
|
|
1418
|
+
const client = createWorkerClient({ worker: myWorker, wasmUrl: '/wasm/oc.wasm' });
|
|
1419
|
+
await client.init();
|
|
1420
|
+
const result = await client.execute('fuse', [shape1Brep, shape2Brep], {});
|
|
1421
|
+
client.dispose();
|
|
1422
|
+
|
|
1423
|
+
// Worker side
|
|
1424
|
+
let registry = createOperationRegistry();
|
|
1425
|
+
registry = registerHandler(registry, 'fuse', (shapesBrep, params) => {
|
|
1426
|
+
// shapesBrep: ReadonlyArray<string> — BREP-serialized input shapes
|
|
1427
|
+
// params: Readonly<Record<string, unknown>> — operation parameters
|
|
1428
|
+
// Must return { resultBrep?: string, resultData?: unknown }
|
|
1429
|
+
return { resultBrep: outputBrep };
|
|
1430
|
+
});
|
|
1431
|
+
createWorkerHandler(registry, async (wasmUrl) => { /* init WASM */ });
|
|
1432
|
+
```
|
|
1433
|
+
|
|
1434
|
+
## Vec3 Math Utilities
|
|
1435
|
+
|
|
1436
|
+
```typescript
|
|
1437
|
+
import {
|
|
1438
|
+
vecAdd, vecSub, vecScale, vecNegate,
|
|
1439
|
+
vecDot, vecCross, vecLength, vecLengthSq, vecDistance,
|
|
1440
|
+
vecNormalize, vecEquals, vecIsZero,
|
|
1441
|
+
vecAngle, vecProjectToPlane, vecRotate, vecRepr
|
|
1442
|
+
} from 'brepjs';
|
|
1443
|
+
|
|
1444
|
+
vecAdd([1,0,0], [0,1,0]): Vec3 // [1, 1, 0]
|
|
1445
|
+
vecSub(a, b): Vec3
|
|
1446
|
+
vecScale(v, scalar): Vec3
|
|
1447
|
+
vecNegate(v): Vec3
|
|
1448
|
+
vecDot(a, b): number
|
|
1449
|
+
vecCross(a, b): Vec3
|
|
1450
|
+
vecLength(v): number
|
|
1451
|
+
vecLengthSq(v): number
|
|
1452
|
+
vecDistance(a, b): number
|
|
1453
|
+
vecNormalize(v): Vec3
|
|
1454
|
+
vecEquals(a, b, tolerance?): boolean
|
|
1455
|
+
vecIsZero(v, tolerance?): boolean
|
|
1456
|
+
vecAngle(a, b): number // radians
|
|
1457
|
+
vecProjectToPlane(v, origin, normal): Vec3
|
|
1458
|
+
vecRotate(v, axis, angleRad): Vec3
|
|
1459
|
+
vecRepr(v): string // e.g. "(1.00, 2.00, 3.00)"
|
|
1460
|
+
```
|
|
1461
|
+
|
|
1462
|
+
## Plane Operations
|
|
1463
|
+
|
|
1464
|
+
```typescript
|
|
1465
|
+
import { createPlane, createNamedPlane, resolvePlane, translatePlane, pivotPlane, makePlane } from 'brepjs';
|
|
1466
|
+
|
|
1467
|
+
createPlane(origin, xDirection?, normal?): Plane
|
|
1468
|
+
createNamedPlane(name, origin?): Result<Plane>
|
|
1469
|
+
resolvePlane(input, origin?): Plane // PlaneName | Plane → Plane
|
|
1470
|
+
translatePlane(plane, offset): Plane
|
|
1471
|
+
pivotPlane(plane, angleDeg, axis?): Plane
|
|
1472
|
+
makePlane(plane?, origin?): Plane // From PlaneName + origin
|
|
1473
|
+
```
|
|
1474
|
+
|
|
1475
|
+
## Branded Shape Types (Functional API)
|
|
1476
|
+
|
|
1477
|
+
```typescript
|
|
1478
|
+
import {
|
|
1479
|
+
castShape, getShapeKind,
|
|
1480
|
+
createVertex, createEdge, createWire, createFace, createShell, createSolid, createCompound,
|
|
1481
|
+
isVertex, isEdge, isWire, isFace, isShell, isSolid, isCompound, isShape3D, isShape1D,
|
|
1482
|
+
is3D, is2D,
|
|
1483
|
+
// Validity constructors and guards
|
|
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' | ... | 'compound'
|
|
1490
|
+
isVertex(s): s is Vertex // Type guards for shape kinds
|
|
1491
|
+
is3D(s): s is AnyShape<'3D'> // Dimension guards
|
|
1492
|
+
is2D(s): s is AnyShape<'2D'>
|
|
1493
|
+
closedWire(w): Result<ClosedWire, string> // Validity smart constructors
|
|
1494
|
+
orientedFace(f): Result<OrientedFace, string>
|
|
1495
|
+
isClosedWire(w): w is ClosedWire // Validity type guards
|
|
1496
|
+
isOrientedFace(f): f is OrientedFace
|
|
1497
|
+
isManifoldShell(s): s is ManifoldShell
|
|
1498
|
+
isValidSolid(s): s is ValidSolid
|
|
1499
|
+
```
|
|
1500
|
+
|
|
1501
|
+
## Kernel Boundary Conversions
|
|
1502
|
+
|
|
1503
|
+
Low-level helpers for interop with raw kernel objects:
|
|
1504
|
+
|
|
1505
|
+
```typescript
|
|
1506
|
+
import { toKernelVec, fromKernelVec, fromKernelPnt, fromKernelDir, withKernelVec, withKernelPnt, withKernelDir } from 'brepjs';
|
|
1507
|
+
|
|
1508
|
+
toKernelVec(v): gp_Vec // Caller must delete()
|
|
1509
|
+
fromKernelVec(ocVec): Vec3 // Extract tuple from gp_Vec
|
|
1510
|
+
fromKernelPnt(ocPnt): Vec3 // Extract tuple from gp_Pnt
|
|
1511
|
+
fromKernelDir(ocDir): Vec3 // Extract tuple from gp_Dir
|
|
1512
|
+
|
|
1513
|
+
// Scoped (auto-cleanup)
|
|
1514
|
+
withKernelVec(v, fn): T // fn receives gp_Vec, deleted after
|
|
1515
|
+
withKernelPnt(v, fn): T
|
|
1516
|
+
withKernelDir(v, fn): T
|
|
1517
|
+
```
|
|
1518
|
+
|
|
1519
|
+
## Constants
|
|
1520
|
+
|
|
1521
|
+
- `DEG2RAD` — Multiply degrees to get radians
|
|
1522
|
+
- `RAD2DEG` — Multiply radians to get degrees
|
|
1523
|
+
- `HASH_CODE_MAX` — Maximum hash code value (2147483647)
|
|
1524
|
+
|
|
1525
|
+
## Types Reference
|
|
1526
|
+
|
|
1527
|
+
Key types used across the API:
|
|
1528
|
+
- `Vec3`: `readonly [number, number, number]`
|
|
1529
|
+
- `Vec2`: `readonly [number, number]`
|
|
1530
|
+
- `PointInput`: `Vec3 | Vec2`
|
|
1531
|
+
- `Point2D`: `[number, number]`
|
|
1532
|
+
- `Direction`: `Vec3 | 'X' | 'Y' | 'Z'`
|
|
1533
|
+
- `Plane`: `{ origin: Vec3, xDir: Vec3, yDir: Vec3, zDir: Vec3 }`
|
|
1534
|
+
- `PlaneName`: `'XY' | 'XZ' | 'YZ' | 'ZX' | 'YX' | 'ZY' | 'front' | 'back' | 'left' | 'right' | 'top' | 'bottom'`
|
|
1535
|
+
- `PlaneInput`: `Plane | PlaneName`
|
|
1536
|
+
- `ShapeKind`: `'vertex' | 'edge' | 'wire' | 'face' | 'shell' | 'solid' | 'compsolid' | 'compound'`
|
|
1537
|
+
- `AnyShape`: Union of Vertex, Edge, Wire, Face, Shell, Solid, CompSolid, Compound
|
|
1538
|
+
- `Shape3D`: Shell | Solid | CompSolid | Compound
|
|
1539
|
+
- `Shape1D`: Edge | Wire
|
|
1540
|
+
- `CurveType`: `'LINE' | 'CIRCLE' | 'ELLIPSE' | 'HYPERBOLA' | 'PARABOLA' | 'BEZIER_CURVE' | 'BSPLINE_CURVE' | 'OFFSET_CURVE' | 'OTHER_CURVE'`
|
|
1541
|
+
- `SurfaceType`: `'PLANE' | 'CYLINDRE' | 'CONE' | 'SPHERE' | 'TORUS' | 'BEZIER_SURFACE' | 'BSPLINE_SURFACE' | 'REVOLUTION_SURFACE' | 'EXTRUSION_SURFACE' | 'OFFSET_SURFACE' | 'OTHER_SURFACE'`
|
|
1542
|
+
- `Result<T, E>`: `Ok<T> | Err<E>` (default `E = BrepError`)
|
|
1543
|
+
- `BrepError`: `{ kind: BrepErrorKind, code: string, message: string, cause?, metadata? }`
|
|
1544
|
+
- `ShapeMesh`: `{ triangles: Uint32Array, vertices: Float32Array, normals: Float32Array, uvs: Float32Array, faceGroups }`
|
|
1545
|
+
- `EdgeMesh`: `{ lines: number[], edgeGroups: {start, count, edgeId}[] }`
|
|
1546
|
+
- `MeshOptions`: `{ tolerance?, angularTolerance?, signal? }` (mesh() also accepts `{ skipNormals?, includeUVs?, cache? }`)
|
|
1547
|
+
- `BooleanOptions`: `{ optimisation?, simplify?, strategy?, signal? }`
|
|
1548
|
+
- `Camera`: `{ position: Vec3, direction: Vec3, xAxis: Vec3, yAxis: Vec3 }`
|
|
1549
|
+
- `ProjectionPlane`: `'XY' | 'XZ' | 'YZ' | 'YX' | 'ZX' | 'ZY' | 'front' | 'back' | 'top' | 'bottom' | 'left' | 'right'`
|
|
1550
|
+
- `AssemblyNode`: `{ name, shape?, translate?, rotate?: { angle, axis? }, metadata?, children }`
|
|
1551
|
+
- `ModelHistory`: `{ steps: ReadonlyArray<OperationStep>, shapes: ReadonlyMap<string, AnyShape> }`
|
|
1552
|
+
- `OperationStep`: `{ id, type, parameters, inputIds, outputId, timestamp, metadata? }`
|
|
1553
|
+
- `OperationFn`: `(inputs: AnyShape[], params: Record<string, unknown>) => AnyShape`
|
|
1554
|
+
- `HistoryOperationRegistry`: `{ operations: ReadonlyMap<string, OperationFn> }`
|
|
1555
|
+
- `HealingReport`: `{ isValid, alreadyValid, wiresHealed, facesHealed, solidHealed, steps, diagnostics }`
|
|
1556
|
+
- `InterferenceResult`: `{ hasInterference, minDistance, pointOnShape1, pointOnShape2 }`
|
|
1557
|
+
- `CurvatureResult`: `{ mean, gaussian, maxCurvature, minCurvature, maxDirection, minDirection }`
|
|
1558
|
+
- `ShapeHandle`: `{ wrapped, disposed, [Symbol.dispose]() }`
|
|
1559
|
+
- `Bounds3D`: `{ xMin, xMax, yMin, yMax, zMin, zMax }`
|
|
1560
|
+
- `ShapeDescription`: `{ kind, faceCount, edgeCount, wireCount, vertexCount, valid, bounds }`
|
|
1561
|
+
|
|
1562
|
+
## Advanced Examples
|
|
1563
|
+
|
|
1564
|
+
### Flanged pipe fitting with loft, sweep, fillet, shell, and boolean
|
|
1565
|
+
|
|
1566
|
+
```typescript
|
|
1567
|
+
import {
|
|
1568
|
+
initFromOC, DisposalScope, unwrap,
|
|
1569
|
+
sketchCircle, sketchRectangle, sketchRoundedRectangle,
|
|
1570
|
+
Sketcher, draw, drawCircle, drawRectangle,
|
|
1571
|
+
helix, cylinder, sphere,
|
|
1572
|
+
exportSTEP, mesh, exportGlb,
|
|
1573
|
+
faceFinder, edgeFinder, getFaces,
|
|
1574
|
+
fuse, cut, shell, fillet,
|
|
1575
|
+
measureVolume, checkInterference, rotate,
|
|
1576
|
+
} from 'brepjs';
|
|
1577
|
+
|
|
1578
|
+
// 1. Flanged pipe: main tube + two flanges, hollowed out
|
|
1579
|
+
const pipeFitting = (() => {
|
|
1580
|
+
using _scope = new DisposalScope();
|
|
1581
|
+
// Main tube body
|
|
1582
|
+
const tube = cylinder(15, 100);
|
|
1583
|
+
|
|
1584
|
+
// Flanges at top and bottom
|
|
1585
|
+
const bottomFlange = cylinder(30, 5);
|
|
1586
|
+
const topFlange = cylinder(30, 5, { at: [0, 0, 95] });
|
|
1587
|
+
|
|
1588
|
+
// Fuse tube + flanges
|
|
1589
|
+
const step1 = unwrap(fuse(tube, bottomFlange));
|
|
1590
|
+
const body = unwrap(fuse(step1, topFlange));
|
|
1591
|
+
|
|
1592
|
+
// Hollow out: remove top face, shell to 2mm wall thickness
|
|
1593
|
+
// parallelTo('XY') finds faces with Z-normal; atDistance selects the one at Z=100
|
|
1594
|
+
const shellFaces = faceFinder().parallelTo('XY').atDistance(100, [0, 0, 0]).findAll(body);
|
|
1595
|
+
const hollowed = unwrap(shell(body, shellFaces, 2));
|
|
1596
|
+
|
|
1597
|
+
// Fillet the tube-to-flange transitions
|
|
1598
|
+
const filletEdges = edgeFinder().ofCurveType('CIRCLE').ofLength(2 * Math.PI * 15).findAll(hollowed);
|
|
1599
|
+
const filleted = unwrap(fillet(hollowed, filletEdges, 3));
|
|
1600
|
+
|
|
1601
|
+
// Bolt holes in each flange
|
|
1602
|
+
let result = filleted;
|
|
1603
|
+
for (let i = 0; i < 6; i++) {
|
|
1604
|
+
const angle = (360 / 6) * i;
|
|
1605
|
+
const hole = rotate(cylinder(3, 10, { at: [22, 0, -2] }), angle, { axis: [0, 0, 1] });
|
|
1606
|
+
result = unwrap(cut(result, hole));
|
|
1607
|
+
}
|
|
1608
|
+
// Same holes at top
|
|
1609
|
+
for (let i = 0; i < 6; i++) {
|
|
1610
|
+
const angle = (360 / 6) * i;
|
|
1611
|
+
const hole = rotate(cylinder(3, 10, { at: [22, 0, 90] }), angle, { axis: [0, 0, 1] });
|
|
1612
|
+
result = unwrap(cut(result, hole));
|
|
1613
|
+
}
|
|
1614
|
+
return result;
|
|
1615
|
+
})();
|
|
1616
|
+
|
|
1617
|
+
console.log('Volume:', measureVolume(pipeFitting));
|
|
1618
|
+
```
|
|
1619
|
+
|
|
1620
|
+
### Enclosure with drafted walls, snap-fit features, and embossed text
|
|
1621
|
+
|
|
1622
|
+
```typescript
|
|
1623
|
+
import {
|
|
1624
|
+
DisposalScope, unwrap,
|
|
1625
|
+
sketchRoundedRectangle, sketchCircle, sketchRectangle,
|
|
1626
|
+
draw, drawText, loadFont, sketchText,
|
|
1627
|
+
cylinder, compoundSketchExtrude,
|
|
1628
|
+
fuse, cut, shell, fillet,
|
|
1629
|
+
faceFinder, edgeFinder, translate,
|
|
1630
|
+
} from 'brepjs';
|
|
1631
|
+
|
|
1632
|
+
await loadFont('/fonts/Roboto-Regular.ttf', 'Roboto');
|
|
1633
|
+
|
|
1634
|
+
const enclosure = (() => {
|
|
1635
|
+
using _scope = new DisposalScope();
|
|
1636
|
+
// Base box with rounded corners — sketch.extrude() returns Shape3D directly (not Result)
|
|
1637
|
+
const b = sketchRoundedRectangle(80, 50, 5).extrude(30);
|
|
1638
|
+
|
|
1639
|
+
// Shell: remove top face
|
|
1640
|
+
// parallelTo accepts StandardPlane strings ('XY', 'XZ', 'YZ') or Plane objects
|
|
1641
|
+
const shellFaces = faceFinder().parallelTo('XY').findAll(b);
|
|
1642
|
+
const shelled = unwrap(shell(b, shellFaces, 2));
|
|
1643
|
+
|
|
1644
|
+
// Fillet all vertical edges
|
|
1645
|
+
const vertEdges = edgeFinder().inDirection([0, 0, 1]).findAll(shelled);
|
|
1646
|
+
const filleted = unwrap(fillet(shelled, vertEdges, 1));
|
|
1647
|
+
|
|
1648
|
+
// Mounting bosses: 4 cylinders at corners inside the box
|
|
1649
|
+
const bossPositions: [number, number][] = [[30, 18], [-30, 18], [30, -18], [-30, -18]];
|
|
1650
|
+
let result = filleted;
|
|
1651
|
+
for (const [x, y] of bossPositions) {
|
|
1652
|
+
const bossShape = cylinder(3, 25, { at: [x, y, 2] });
|
|
1653
|
+
const hole = cylinder(1.2, 25, { at: [x, y, 2] });
|
|
1654
|
+
result = unwrap(cut(unwrap(fuse(result, bossShape)), hole));
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
// Ventilation slots on side face
|
|
1658
|
+
for (let i = -2; i <= 2; i++) {
|
|
1659
|
+
const slot = translate(
|
|
1660
|
+
sketchRoundedRectangle(1.5, 10, 0.5, { plane: 'XZ' }).extrude(5),
|
|
1661
|
+
[0, -27, 15 + i * 3]
|
|
1662
|
+
);
|
|
1663
|
+
result = unwrap(cut(result, slot));
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
// Embossed text on top — text produces CompoundSketch, use compoundSketchExtrude
|
|
1667
|
+
const textSketch = sketchText('brepjs', { fontSize: 8, fontFamily: 'Roboto' }, { plane: 'XY', origin: 30 });
|
|
1668
|
+
const textSolid = compoundSketchExtrude(textSketch, 1);
|
|
1669
|
+
result = unwrap(fuse(result, textSolid));
|
|
1670
|
+
|
|
1671
|
+
return result;
|
|
1672
|
+
})();
|
|
1673
|
+
```
|
|
1674
|
+
|
|
1675
|
+
### Parametric spring with sweep and interference check
|
|
1676
|
+
|
|
1677
|
+
```typescript
|
|
1678
|
+
import {
|
|
1679
|
+
DisposalScope, unwrap,
|
|
1680
|
+
sketchCircle, sketchHelix,
|
|
1681
|
+
Sketcher, helix, cylinder,
|
|
1682
|
+
fuse, checkInterference,
|
|
1683
|
+
measureLength, describe,
|
|
1684
|
+
translate, rotate,
|
|
1685
|
+
} from 'brepjs';
|
|
1686
|
+
|
|
1687
|
+
const spring = (() => {
|
|
1688
|
+
using _scope = new DisposalScope();
|
|
1689
|
+
// Helix spine: pitch=8, height=60, radius=20
|
|
1690
|
+
const helixSketch = sketchHelix(8, 60, 20);
|
|
1691
|
+
|
|
1692
|
+
// Sweep a circular cross-section along the helix
|
|
1693
|
+
const coil = helixSketch.sweepSketch((plane, origin) =>
|
|
1694
|
+
new Sketcher(plane).movePointerTo([-1.5, 0]).sagittaArc(3, 0, 1.5).sagittaArc(-3, 0, 1.5).close()
|
|
1695
|
+
);
|
|
1696
|
+
|
|
1697
|
+
// Flat ends: cylinders at top and bottom
|
|
1698
|
+
const bottomEnd = cylinder(20, 2, { at: [0, 0, -2] });
|
|
1699
|
+
const topEnd = cylinder(20, 2, { at: [0, 0, 60] });
|
|
1700
|
+
|
|
1701
|
+
return unwrap(fuse(unwrap(fuse(coil, bottomEnd)), topEnd));
|
|
1702
|
+
})();
|
|
1703
|
+
|
|
1704
|
+
// Check if spring fits inside a housing cylinder
|
|
1705
|
+
const housing = cylinder(25, 70, { at: [0, 0, -5] });
|
|
1706
|
+
const interference = unwrap(checkInterference(spring, housing));
|
|
1707
|
+
console.log('Fits:', !interference.hasInterference, 'Gap:', interference.minDistance);
|
|
1708
|
+
|
|
1709
|
+
// Chain transforms
|
|
1710
|
+
const movedSpring = rotate(translate(spring, [100, 0, 0]), 90, { axis: [0, 1, 0] });
|
|
1711
|
+
```
|
|
1712
|
+
|
|
1713
|
+
### Multi-format export with materials
|
|
1714
|
+
|
|
1715
|
+
```typescript
|
|
1716
|
+
import {
|
|
1717
|
+
mesh as meshFn, exportGlb, exportOBJ, exportThreeMF, exportDXF,
|
|
1718
|
+
drawProjection, exportSTEP, exportAssemblySTEP,
|
|
1719
|
+
toBufferGeometryData, toGroupedBufferGeometryData,
|
|
1720
|
+
} from 'brepjs';
|
|
1721
|
+
|
|
1722
|
+
// Mesh the shape for rendering
|
|
1723
|
+
const meshData = meshFn(part, { tolerance: 0.1, angularTolerance: 15, includeUVs: true });
|
|
1724
|
+
|
|
1725
|
+
// glTF with per-face PBR materials
|
|
1726
|
+
const materials = new Map();
|
|
1727
|
+
for (const group of meshData.faceGroups) {
|
|
1728
|
+
materials.set(group.faceId, {
|
|
1729
|
+
name: `face_${group.faceId}`,
|
|
1730
|
+
baseColor: [0.7, 0.7, 0.8, 1.0],
|
|
1731
|
+
metallic: 0.8,
|
|
1732
|
+
roughness: 0.3,
|
|
1733
|
+
});
|
|
1734
|
+
}
|
|
1735
|
+
const glb = exportGlb(meshData, { materials });
|
|
1736
|
+
|
|
1737
|
+
// 3MF for 3D printing
|
|
1738
|
+
const threemf = exportThreeMF(meshData, { name: 'MyPart', unit: 'millimeter' });
|
|
1739
|
+
|
|
1740
|
+
// OBJ for compatibility
|
|
1741
|
+
const obj = exportOBJ(meshData);
|
|
1742
|
+
|
|
1743
|
+
// STEP with assembly structure and colors
|
|
1744
|
+
const step = exportAssemblySTEP(
|
|
1745
|
+
[
|
|
1746
|
+
{ shape: body, color: '#336699', name: 'Body' },
|
|
1747
|
+
{ shape: lid, color: '#996633', name: 'Lid', alpha: 0.8 },
|
|
1748
|
+
],
|
|
1749
|
+
{ unit: 'millimeter' }
|
|
1750
|
+
);
|
|
1751
|
+
|
|
1752
|
+
// 2D projection → DXF for laser cutting
|
|
1753
|
+
const { visible } = drawProjection(part, 'top');
|
|
1754
|
+
const dxf = exportDXF([
|
|
1755
|
+
{ type: 'POLYLINE', points: [[0,0], [100,0], [100,50], [0,50]], closed: true, layer: 'outline' },
|
|
1756
|
+
], { layer: '0', curveSegments: 64 });
|
|
1757
|
+
|
|
1758
|
+
// Three.js integration
|
|
1759
|
+
const bufferData = toGroupedBufferGeometryData(meshData);
|
|
1760
|
+
// Use bufferData.position, .normal, .index, .groups with THREE.BufferGeometry
|
|
1761
|
+
```
|
|
1762
|
+
|
|
1763
|
+
### Functional API: immutable pipeline with healing
|
|
1764
|
+
|
|
1765
|
+
```typescript
|
|
1766
|
+
import {
|
|
1767
|
+
unwrap,
|
|
1768
|
+
extrude, face, wire, line, circle,
|
|
1769
|
+
fuse, cut, fillet, shell,
|
|
1770
|
+
getEdges, getFaces, edgeFinder, faceFinder,
|
|
1771
|
+
autoHeal, isValid, describe,
|
|
1772
|
+
translate, rotate, linearPattern,
|
|
1773
|
+
mesh, exportGlb,
|
|
1774
|
+
} from 'brepjs';
|
|
1775
|
+
|
|
1776
|
+
// Build with functional API (no classes, all immutable operations)
|
|
1777
|
+
const base = extrude(
|
|
1778
|
+
unwrap(face(unwrap(wire([
|
|
1779
|
+
line([0,0,0], [40,0,0]),
|
|
1780
|
+
line([40,0,0], [40,30,0]),
|
|
1781
|
+
line([40,30,0], [0,30,0]),
|
|
1782
|
+
line([0,30,0], [0,0,0]),
|
|
1783
|
+
])))),
|
|
1784
|
+
[0, 0, 20]
|
|
1785
|
+
);
|
|
1786
|
+
|
|
1787
|
+
// Find top face and cut mounting holes
|
|
1788
|
+
const topFaces = faceFinder().inDirection('Z').findAll(base);
|
|
1789
|
+
const holeFace = unwrap(face(unwrap(wire([circle(3, { at: [10, 15, 20] })]))));
|
|
1790
|
+
const hole1 = extrude(holeFace, [0, 0, -25]);
|
|
1791
|
+
const withHoles = unwrap(cut(base, hole1));
|
|
1792
|
+
|
|
1793
|
+
// Fillet just the top edges
|
|
1794
|
+
const topEdges = edgeFinder().atDistance(20, [20, 15, 0]).findAll(withHoles);
|
|
1795
|
+
const filleted = unwrap(fillet(withHoles, topEdges, 2));
|
|
1796
|
+
|
|
1797
|
+
// Shell it out
|
|
1798
|
+
const shellFaces = faceFinder().inDirection([0, 0, 1]).findAll(filleted);
|
|
1799
|
+
const shelled = unwrap(shell(filleted, shellFaces, 1.5));
|
|
1800
|
+
|
|
1801
|
+
// Validate and heal
|
|
1802
|
+
if (!isValid(shelled)) {
|
|
1803
|
+
const { shape: healed, report } = unwrap(autoHeal(shelled));
|
|
1804
|
+
console.log('Healed:', report.steps);
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
// Describe the result
|
|
1808
|
+
const desc = describe(shelled);
|
|
1809
|
+
console.log(`${desc.kind}: ${desc.faceCount} faces, ${desc.edgeCount} edges, valid: ${desc.valid}`);
|
|
1810
|
+
```
|