forgecad 0.1.1 → 0.1.3
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/dist/assets/{evalWorker-DuS4V-H5.js → evalWorker-1m873KWd.js} +107 -107
- package/dist/assets/{index-d60dJDhX.js → index-Dvz3nSDc.js} +342 -326
- package/dist/assets/{manifold-DEOPQqeC.js → manifold-C38sUiKu.js} +1 -1
- package/dist/assets/{manifold-cL7A7HeM.js → manifold-Dk2u-lhj.js} +1 -1
- package/dist/assets/{manifold-DbvY5u9x.js → manifold-rOWQW9fU.js} +1 -1
- package/dist/assets/{reportWorker-GZVKtf9S.js → reportWorker-Cj587shw.js} +129 -129
- package/dist/index.html +1 -1
- package/dist-cli/forgecad.js +204 -19
- package/dist-skill/SKILL.md +31 -4561
- package/dist-skill/docs/API/README.md +24 -0
- package/dist-skill/docs/API/guides/modeling-recipes.md +246 -0
- package/dist-skill/docs/API/internals/compiler.md +300 -0
- package/dist-skill/docs/API/internals/manifold.md +7 -0
- package/dist-skill/docs/API/model-building/README.md +31 -0
- package/dist-skill/docs/API/model-building/assembly.md +205 -0
- package/dist-skill/docs/API/model-building/coordinate-system.md +43 -0
- package/dist-skill/docs/API/model-building/entities.md +282 -0
- package/dist-skill/docs/API/model-building/geometry-conventions.md +104 -0
- package/dist-skill/docs/API/model-building/positioning.md +170 -0
- package/dist-skill/docs/API/model-building/reference.md +1936 -0
- package/dist-skill/docs/API/model-building/sheet-metal.md +180 -0
- package/dist-skill/docs/API/model-building/sketch-anchor.md +32 -0
- package/dist-skill/docs/API/model-building/sketch-booleans.md +101 -0
- package/dist-skill/docs/API/model-building/sketch-core.md +68 -0
- package/dist-skill/docs/API/model-building/sketch-extrude.md +57 -0
- package/dist-skill/docs/API/model-building/sketch-on-face.md +98 -0
- package/dist-skill/docs/API/model-building/sketch-operations.md +92 -0
- package/dist-skill/docs/API/model-building/sketch-path.md +70 -0
- package/dist-skill/docs/API/model-building/sketch-primitives.md +104 -0
- package/dist-skill/docs/API/model-building/sketch-transforms.md +60 -0
- package/dist-skill/docs/API/output/bom.md +53 -0
- package/dist-skill/docs/API/output/brep-export.md +82 -0
- package/dist-skill/docs/API/output/dimensions.md +62 -0
- package/dist-skill/docs/API/runtime/viewport.md +229 -0
- package/dist-skill/docs/CLI.md +672 -0
- package/dist-skill/docs/CODING.md +345 -0
- package/dist-skill/docs/PROGRAM-LEAD.md +180 -0
- package/dist-skill/docs/VISION.md +77 -0
- package/examples/api/import-group-assembly.forge.js +34 -0
- package/examples/api/import-group-source.forge.js +35 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1936 @@
|
|
|
1
|
+
# ForgeCAD Model-Building Reference
|
|
2
|
+
|
|
3
|
+
This file covers the script authoring surface for geometry, sketches, topology, assemblies, imports, and reusable parts.
|
|
4
|
+
|
|
5
|
+
It intentionally excludes:
|
|
6
|
+
- viewport-only APIs in [../runtime/viewport.md](../runtime/viewport.md)
|
|
7
|
+
- report/export APIs in [../output/bom.md](../output/bom.md), [../output/dimensions.md](../output/dimensions.md), and [../output/brep-export.md](../output/brep-export.md)
|
|
8
|
+
- implementation notes in [../internals/manifold.md](../internals/manifold.md)
|
|
9
|
+
|
|
10
|
+
## Core Concepts
|
|
11
|
+
|
|
12
|
+
ForgeCAD scripts are JavaScript code that returns geometry. The forge API is globally available — no imports needed.
|
|
13
|
+
|
|
14
|
+
### Basic Structure
|
|
15
|
+
```javascript
|
|
16
|
+
// 1. Declare parameters (creates UI sliders)
|
|
17
|
+
const width = param("Width", 50, { min: 20, max: 100, unit: "mm" });
|
|
18
|
+
|
|
19
|
+
// 2. Create geometry
|
|
20
|
+
const shape = box(width, 30, 10);
|
|
21
|
+
|
|
22
|
+
// 3. Return the final shape
|
|
23
|
+
return shape;
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Execution Model
|
|
27
|
+
- Scripts execute on every parameter change (400ms debounce)
|
|
28
|
+
- All operations are **immutable** — they return new shapes, never modify in place
|
|
29
|
+
- Must return one of:
|
|
30
|
+
- A `Shape` (3D solid)
|
|
31
|
+
- A `Sketch` (2D profile — rendered flat on XY plane)
|
|
32
|
+
- A `TrackedShape` (3D solid with named faces/edges — auto-unwrapped)
|
|
33
|
+
- A `ShapeGroup` (multiple shapes/sketches grouped for joint transforms)
|
|
34
|
+
- An `Array` of shapes/sketches/groups (multi-object scene)
|
|
35
|
+
- An `Array` of `{ name, shape?, sketch?, color? }` objects (named multi-object scene)
|
|
36
|
+
|
|
37
|
+
### ⚠️ Important: Unions Remove Colors
|
|
38
|
+
|
|
39
|
+
When you use `union()` to combine shapes, the result becomes a single solid mesh with only one color. Individual colors assigned to the original shapes are lost:
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
// ❌ BAD: Colors are lost after union!
|
|
43
|
+
const red = box(30, 30, 30).color('#ff0000');
|
|
44
|
+
const blue = box(20, 20, 20).translate(30, 0, 0).color('#0066ff');
|
|
45
|
+
return union(red, blue); // Result is all one color (red)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Solution**: Return objects as a composite response instead:
|
|
49
|
+
|
|
50
|
+
```javascript
|
|
51
|
+
// ✅ GOOD: Each object keeps its color
|
|
52
|
+
const red = box(30, 30, 30).color('#ff0000');
|
|
53
|
+
const blue = box(20, 20, 20).translate(30, 0, 0).color('#0066ff');
|
|
54
|
+
|
|
55
|
+
// Return as named objects to preserve individual colors and materials
|
|
56
|
+
return [
|
|
57
|
+
{ "label": red }, // Each gets its own color, toggle, and controls
|
|
58
|
+
{ "label": blue }
|
|
59
|
+
];
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Each object in the returned array stays independently colorable and selectable, which is the main alternative to booleaning everything into one solid.
|
|
63
|
+
|
|
64
|
+
### Coordinate System
|
|
65
|
+
ForgeCAD uses **Z-up** right-handed coordinates:
|
|
66
|
+
- **X** = left/right
|
|
67
|
+
- **Y** = forward/back
|
|
68
|
+
- **Z** = up/down
|
|
69
|
+
|
|
70
|
+
See [coordinate-system.md](coordinate-system.md) for view mapping details.
|
|
71
|
+
|
|
72
|
+
## Parameters
|
|
73
|
+
|
|
74
|
+
### `param(name, default, options?)`
|
|
75
|
+
Declares a parameter and creates a UI slider.
|
|
76
|
+
|
|
77
|
+
**Parameters:**
|
|
78
|
+
- `name` (string) - Display name in UI
|
|
79
|
+
- `default` (number) - Initial value
|
|
80
|
+
- `options` (object, optional):
|
|
81
|
+
- `min` (number) - Minimum value (default: 0)
|
|
82
|
+
- `max` (number) - Maximum value (default: default * 4)
|
|
83
|
+
- `step` (number) - Slider increment (auto-calculated if not provided)
|
|
84
|
+
- `unit` (string) - Display unit like "mm", "°", "%"
|
|
85
|
+
- `integer` (boolean) - If true, value is always rounded to whole number. Step defaults to 1. Use for counts, quantities, sides, etc.
|
|
86
|
+
|
|
87
|
+
**Returns:** Current parameter value (number)
|
|
88
|
+
|
|
89
|
+
**Examples:**
|
|
90
|
+
```javascript
|
|
91
|
+
const width = param("Width", 50);
|
|
92
|
+
const angle = param("Angle", 45, { min: 0, max: 180, unit: "°" });
|
|
93
|
+
const thick = param("Thickness", 2, { min: 0.5, max: 10, step: 0.5, unit: "mm" });
|
|
94
|
+
const count = param("Count", 5, { min: 1, max: 20, integer: true });
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Colors
|
|
98
|
+
|
|
99
|
+
Both Shape and Sketch support colors via `.color()`:
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
const red = box(50, 50, 50).color('#ff0000');
|
|
103
|
+
const blue = circle2d(25).color('#0066ff');
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Colors are preserved through transforms and boolean operations (the first operand's color wins).
|
|
107
|
+
|
|
108
|
+
When returning multiple objects, colors can also be set per-object:
|
|
109
|
+
|
|
110
|
+
```javascript
|
|
111
|
+
return [
|
|
112
|
+
{ name: "Base", shape: box(100, 100, 5), color: "#888888" },
|
|
113
|
+
{ name: "Column", shape: cylinder(50, 10).translate(50, 50, 5), color: "#4488cc" },
|
|
114
|
+
];
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## 3D Primitives
|
|
118
|
+
|
|
119
|
+
### `box(x, y, z, center?)`
|
|
120
|
+
Creates a rectangular box with named faces and edges.
|
|
121
|
+
|
|
122
|
+
**Parameters:**
|
|
123
|
+
- `x, y, z` (number) - Dimensions
|
|
124
|
+
- `center` (boolean, optional) - If true, centers at origin. Default: false (corner at origin)
|
|
125
|
+
|
|
126
|
+
**Returns:** `TrackedShape` (with faces: top, bottom, side-left, side-right, side-top, side-bottom; edges: vert-bl, vert-br, vert-tr, vert-tl, etc.)
|
|
127
|
+
|
|
128
|
+
```javascript
|
|
129
|
+
const cube = box(50, 50, 50, true); // Centered cube
|
|
130
|
+
const plate = box(100, 80, 5); // Corner at origin
|
|
131
|
+
plate.face('top'); // FaceRef { normal, center, planar, uAxis, vAxis }
|
|
132
|
+
plate.edge('vert-bl'); // EdgeRef { start, end }
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### `cylinder(height, radius, radiusTop?, segments?, center?)`
|
|
136
|
+
Creates a cylinder or cone with named faces and edges.
|
|
137
|
+
|
|
138
|
+
**Parameters:**
|
|
139
|
+
- `height` (number) - Height along Z axis
|
|
140
|
+
- `radius` (number) - Bottom radius
|
|
141
|
+
- `radiusTop` (number, optional) - Top radius. If different from radius, creates a cone. Default: same as radius
|
|
142
|
+
- `segments` (number, optional) - Number of sides. Default: auto (smooth circle)
|
|
143
|
+
- `center` (boolean, optional) - If true, centers along Z. Default: false
|
|
144
|
+
|
|
145
|
+
**Returns:** `TrackedShape` (with faces: top, bottom, side; edges: top-rim, bottom-rim)
|
|
146
|
+
|
|
147
|
+
```javascript
|
|
148
|
+
const cyl = cylinder(50, 10); // Cylinder
|
|
149
|
+
const cone = cylinder(50, 20, 5); // Cone (tapered)
|
|
150
|
+
const hex = cylinder(10, 15, 15, 6); // Hexagonal prism
|
|
151
|
+
cyl.face('top'); // FaceRef (planar)
|
|
152
|
+
cyl.face('side'); // FaceRef (curved, planar === false)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### `sphere(radius, segments?)`
|
|
156
|
+
Creates a sphere.
|
|
157
|
+
|
|
158
|
+
**Parameters:**
|
|
159
|
+
- `radius` (number) - Sphere radius
|
|
160
|
+
- `segments` (number, optional) - Tessellation detail. Default: auto (smooth)
|
|
161
|
+
|
|
162
|
+
**Returns:** `Shape`
|
|
163
|
+
|
|
164
|
+
```javascript
|
|
165
|
+
const ball = sphere(25);
|
|
166
|
+
const lowPoly = sphere(25, 8); // Octahedron-like
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## 3D Transforms
|
|
170
|
+
|
|
171
|
+
All transforms are **chainable** and **immutable** (return new shapes).
|
|
172
|
+
The core 3D transform set uses the same names/signatures on `Shape`, `TrackedShape`, and `ShapeGroup`.
|
|
173
|
+
For mixed groups that include `Sketch` children, `transform` / `rotateAround` / `pointAlong` are 3D-only.
|
|
174
|
+
|
|
175
|
+
### `.clone()` / `.duplicate()`
|
|
176
|
+
Create an explicit copy handle of a shape (same geometry/color) so you can branch variants clearly.
|
|
177
|
+
|
|
178
|
+
```javascript
|
|
179
|
+
const bracket = box(60, 20, 8);
|
|
180
|
+
const left = bracket.clone().translate(-40, 0, 0);
|
|
181
|
+
const right = bracket.duplicate().translate(40, 0, 0);
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### `.translate(x, y, z)`
|
|
185
|
+
Moves the shape relative to its current position.
|
|
186
|
+
|
|
187
|
+
```javascript
|
|
188
|
+
const moved = box(10, 10, 10).translate(50, 0, 0);
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### `.moveTo(x, y, z)`
|
|
192
|
+
Positions the shape so its bounding box min corner is at the given global coordinate.
|
|
193
|
+
|
|
194
|
+
```javascript
|
|
195
|
+
// Place a box at exactly (100, 50, 0) in world space
|
|
196
|
+
const placed = box(30, 30, 10).moveTo(100, 50, 0);
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### `.moveToLocal(target, x, y, z)`
|
|
200
|
+
Positions the shape relative to another shape's local coordinate system (bounding box min corner).
|
|
201
|
+
|
|
202
|
+
**Parameters:**
|
|
203
|
+
- `target` (Shape | TrackedShape) — The reference shape
|
|
204
|
+
- `x, y, z` (number) — Offset from target's bounding box min corner
|
|
205
|
+
|
|
206
|
+
```javascript
|
|
207
|
+
const base = box(100, 100, 10);
|
|
208
|
+
const part = box(20, 20, 30);
|
|
209
|
+
|
|
210
|
+
// Place part at (10, 10, 10) relative to base's origin corner
|
|
211
|
+
const placed = part.moveToLocal(base, 10, 10, 10);
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### `.rotate(x, y, z)`
|
|
215
|
+
Rotates using Euler angles in **degrees**.
|
|
216
|
+
|
|
217
|
+
**Parameters:**
|
|
218
|
+
- `x, y, z` (number) - Rotation in degrees around each axis
|
|
219
|
+
|
|
220
|
+
```javascript
|
|
221
|
+
const rotated = box(50, 20, 10).rotate(0, 0, 45); // 45° around Z
|
|
222
|
+
const tilted = cylinder(50, 10).rotate(90, 0, 0); // Lay on side
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### `.scale(v)`
|
|
226
|
+
Scales the shape.
|
|
227
|
+
|
|
228
|
+
**Parameters:**
|
|
229
|
+
- `v` (number | [number, number, number]) - Uniform scale or per-axis scale
|
|
230
|
+
|
|
231
|
+
```javascript
|
|
232
|
+
const bigger = sphere(10).scale(2); // 2x larger
|
|
233
|
+
const stretched = box(10, 10, 10).scale([2, 1, 0.5]); // Non-uniform
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### `.mirror(normal)`
|
|
237
|
+
Mirrors across a plane defined by its normal vector.
|
|
238
|
+
|
|
239
|
+
**Parameters:**
|
|
240
|
+
- `normal` ([number, number, number]) - Plane normal (doesn't need to be unit length)
|
|
241
|
+
|
|
242
|
+
```javascript
|
|
243
|
+
const mirrored = shape.mirror([1, 0, 0]); // Mirror across YZ plane
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### `.transform(m)`
|
|
247
|
+
Applies a custom 4x4 transform matrix or `Transform` object.
|
|
248
|
+
|
|
249
|
+
**Parameters:**
|
|
250
|
+
- `m` (`number[] | Transform`) - 4x4 column-major matrix (`number[16]`) or a `Transform`
|
|
251
|
+
|
|
252
|
+
```javascript
|
|
253
|
+
const T = Transform.identity()
|
|
254
|
+
.translate(0, 0, 1.5)
|
|
255
|
+
.rotateAxis([1, 0, 0], 35, [0, hingeY, 0]);
|
|
256
|
+
|
|
257
|
+
const moved = lid.transform(T);
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### `.rotateAround(axis, angleDeg, pivot?)`
|
|
261
|
+
Rotates around an arbitrary axis through a pivot point.
|
|
262
|
+
|
|
263
|
+
**Parameters:**
|
|
264
|
+
- `axis` ([number, number, number]) - Rotation axis direction
|
|
265
|
+
- `angleDeg` (number) - Rotation angle in degrees
|
|
266
|
+
- `pivot` ([number, number, number], optional) - Pivot point. Default: origin
|
|
267
|
+
|
|
268
|
+
```javascript
|
|
269
|
+
// Rotate a door 45° around Z axis at the hinge position
|
|
270
|
+
const opened = door.rotateAround([0, 0, 1], 45, [hingeX, hingeY, 0]);
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### `.rotateAroundTo(axis, pivot, movingPoint, targetPoint, opts?)`
|
|
274
|
+
Solves the rotation around an axis that makes `movingPoint` reach the target line/plane implied by `targetPoint`.
|
|
275
|
+
|
|
276
|
+
**Parameters:**
|
|
277
|
+
- `axis` ([number, number, number]) - Rotation axis direction
|
|
278
|
+
- `pivot` ([number, number, number]) - Point on the rotation axis
|
|
279
|
+
- `movingPoint` (`[number, number, number] | string`) - World-space point or this shape's anchor/reference
|
|
280
|
+
- `targetPoint` (`[number, number, number] | string`) - World-space point or this shape's anchor/reference
|
|
281
|
+
- `opts` (object, optional)
|
|
282
|
+
- `mode` (`'plane' | 'line'`) - Default: `'plane'`
|
|
283
|
+
|
|
284
|
+
Modes:
|
|
285
|
+
- `'plane'` — rotate until `movingPoint` lies in the plane defined by `axis` + `targetPoint`
|
|
286
|
+
- `'line'` — rotate until `movingPoint` lies on the infinite line from `pivot` through `targetPoint`; throws if the geometry makes that impossible
|
|
287
|
+
|
|
288
|
+
```javascript
|
|
289
|
+
const arm = box(80, 8, 8, true)
|
|
290
|
+
.translate(40, 0, 0)
|
|
291
|
+
.withReferences({ points: { tip: [80, 0, 0] } });
|
|
292
|
+
|
|
293
|
+
const aimed = arm.rotateAroundTo(
|
|
294
|
+
[0, 0, 1],
|
|
295
|
+
[0, 0, 0],
|
|
296
|
+
"tip",
|
|
297
|
+
[30, 30, 20],
|
|
298
|
+
);
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### `.pointAlong(direction)`
|
|
302
|
+
Reorients a shape so its primary axis (Z) points along the given direction. Useful for laying cylinders and extrusions along X or Y without thinking about Euler angles.
|
|
303
|
+
|
|
304
|
+
**Parameters:**
|
|
305
|
+
- `direction` ([number, number, number]) - Target direction vector
|
|
306
|
+
|
|
307
|
+
```javascript
|
|
308
|
+
// Lay a cylinder along the X axis
|
|
309
|
+
const axle = cylinder(100, 5).pointAlong([1, 0, 0]);
|
|
310
|
+
|
|
311
|
+
// Symmetric hinges pointing outward from center
|
|
312
|
+
const hingeL = cylinder(40, 5).pointAlong([-1, 0, 0]).translate(-50, 0, 0);
|
|
313
|
+
const hingeR = cylinder(40, 5).pointAlong([1, 0, 0]).translate(50, 0, 0);
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### `Transform` primitives (for kinematic chains)
|
|
317
|
+
Use `Transform` when manual pivot math becomes hard to maintain.
|
|
318
|
+
|
|
319
|
+
```javascript
|
|
320
|
+
const T = Transform.identity()
|
|
321
|
+
.translate(0, 0, 120)
|
|
322
|
+
.rotateAxis([0, 0, 1], 35);
|
|
323
|
+
|
|
324
|
+
const p = T.point([10, 0, 0]); // transform a point
|
|
325
|
+
const v = T.vector([1, 0, 0]); // transform a direction (no translation)
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Core methods:
|
|
329
|
+
- `Transform.identity()`
|
|
330
|
+
- `Transform.translation(x, y, z)`
|
|
331
|
+
- `Transform.rotationAxis(axis, angleDeg, pivot?)`
|
|
332
|
+
- `Transform.rotateAroundTo(axis, pivot, movingPoint, targetPoint, opts?)`
|
|
333
|
+
- `Transform.scale(v)`
|
|
334
|
+
- `T.mul(other)` (chain-composition order)
|
|
335
|
+
- `composeChain(a, b, c, ...)` explicit left-to-right chain composition
|
|
336
|
+
- `T.inverse()`
|
|
337
|
+
- `shape.transform(T)` / `trackedShape.transform(T)` / `group.transform(T)`
|
|
338
|
+
|
|
339
|
+
## Joints
|
|
340
|
+
|
|
341
|
+
### `joint(name, shape, pivot, opts?)`
|
|
342
|
+
Create a revolute (hinge) joint. Auto-creates a param slider and rotates the shape.
|
|
343
|
+
|
|
344
|
+
**Parameters:**
|
|
345
|
+
- `name` (string) - Display name for the angle parameter
|
|
346
|
+
- `shape` (Shape) - The shape to rotate
|
|
347
|
+
- `pivot` ([number, number, number]) - The pivot point
|
|
348
|
+
- `opts` (object, optional):
|
|
349
|
+
- `axis` ([number, number, number]) - Rotation axis. Default: [0, 0, 1] (Z axis)
|
|
350
|
+
- `min` (number) - Minimum angle. Default: 0
|
|
351
|
+
- `max` (number) - Maximum angle. Default: 360
|
|
352
|
+
- `default` (number) - Initial angle. Default: 0
|
|
353
|
+
- `unit` (string) - Display unit. Default: "°"
|
|
354
|
+
|
|
355
|
+
**Returns:** `Shape` (rotated by the current slider value)
|
|
356
|
+
|
|
357
|
+
```javascript
|
|
358
|
+
// One line: creates a "Lid Angle" slider and rotates the lid around the hinge
|
|
359
|
+
const openLid = joint("Lid Angle", lid, [0, boxDepth, boxHeight], {
|
|
360
|
+
axis: [1, 0, 0],
|
|
361
|
+
max: 120,
|
|
362
|
+
default: 45,
|
|
363
|
+
});
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
## Assembly Graph (Mechanisms)
|
|
367
|
+
|
|
368
|
+
See also: `assembly.md`.
|
|
369
|
+
|
|
370
|
+
### `assembly(name?)`
|
|
371
|
+
Creates an assembly container with named parts + joints.
|
|
372
|
+
|
|
373
|
+
```javascript
|
|
374
|
+
const mech = assembly("Two-Link Arm")
|
|
375
|
+
.addPart("base", box(80, 80, 20, true))
|
|
376
|
+
.addPart("link1", box(120, 24, 24).translate(0, -12, -12))
|
|
377
|
+
.addPart("link2", box(100, 20, 20).translate(0, -10, -10))
|
|
378
|
+
.addJoint("shoulder", "revolute", "base", "link1", {
|
|
379
|
+
axis: [0, 1, 0],
|
|
380
|
+
min: -30, max: 120, default: 20,
|
|
381
|
+
frame: Transform.identity().translate(0, 0, 20),
|
|
382
|
+
})
|
|
383
|
+
.addJoint("elbow", "revolute", "link1", "link2", {
|
|
384
|
+
axis: [0, 1, 0],
|
|
385
|
+
min: -20, max: 140, default: 40,
|
|
386
|
+
frame: Transform.identity().translate(120, 0, 0),
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
const solved = mech.solve();
|
|
390
|
+
return solved.toScene();
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Linked joint example:
|
|
394
|
+
|
|
395
|
+
```javascript
|
|
396
|
+
mech
|
|
397
|
+
.addJointCoupling("Top Gear", {
|
|
398
|
+
terms: [
|
|
399
|
+
{ joint: "Steering", ratio: 1 },
|
|
400
|
+
{ joint: "Wheel Drive", ratio: 20 / 14 },
|
|
401
|
+
],
|
|
402
|
+
})
|
|
403
|
+
.addJointCoupling("Motor 1", {
|
|
404
|
+
terms: [{ joint: "Top Gear", ratio: -2 }],
|
|
405
|
+
});
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Gear helper example:
|
|
409
|
+
|
|
410
|
+
```javascript
|
|
411
|
+
const pair = lib.gearPair({
|
|
412
|
+
pinion: { module: 1.25, teeth: 14, faceWidth: 8 },
|
|
413
|
+
gear: { module: 1.25, teeth: 42, faceWidth: 8 },
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
mech.addGearCoupling("Driven", "Pinion", { pair }); // uses pair.jointRatio
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
Bevel/face helper example:
|
|
420
|
+
|
|
421
|
+
```javascript
|
|
422
|
+
const bevel = lib.bevelGearPair({
|
|
423
|
+
pinion: { module: 1.5, teeth: 18, faceWidth: 10 },
|
|
424
|
+
gear: { module: 1.5, teeth: 36, faceWidth: 9 },
|
|
425
|
+
shaftAngleDeg: 90,
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
const face = lib.faceGearPair({
|
|
429
|
+
face: { module: 1.5, teeth: 44, faceWidth: 7, toothHeight: 1.2, side: 'top' },
|
|
430
|
+
vertical: { module: 1.5, teeth: 16, faceWidth: 8 },
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
mech.addGearCoupling("Bevel Driven", "Bevel Driver", { pair: bevel });
|
|
434
|
+
mech.addGearCoupling("Face Driven", "Face Driver", { pair: face });
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
Key methods:
|
|
438
|
+
- `addPart(name, shape, { transform?, metadata? })`
|
|
439
|
+
- `addFrame(name, { transform? })` for virtual mechanism frames
|
|
440
|
+
- `addJoint(name, type, parent, child, opts)` where `type` is `'fixed' | 'revolute' | 'prismatic'`
|
|
441
|
+
- `addRevolute(...)`, `addPrismatic(...)`, `addFixed(...)` shorthand helpers
|
|
442
|
+
- `addJointCoupling(jointName, { terms, offset? })` for linked joints:
|
|
443
|
+
- `jointName`: driven joint
|
|
444
|
+
- `terms`: `[{ joint, ratio? }, ...]` where each source contributes `ratio * sourceValue` (default ratio `1`)
|
|
445
|
+
- `offset`: additive bias after term sum (default `0`)
|
|
446
|
+
- `addGearCoupling(drivenJoint, driverJoint, opts)` for revolute gear meshes:
|
|
447
|
+
- ratio source (exactly one): `ratio`, `pair` (`pair.jointRatio`), or `driverTeeth + drivenTeeth`
|
|
448
|
+
- `mesh`: `'external' | 'internal' | 'bevel' | 'face'` (teeth mode only, default `'external'`; `bevel`/`face` follow external sign)
|
|
449
|
+
- `offset`: additive bias after gear ratio
|
|
450
|
+
- `solve(state?)` with per-joint value overrides
|
|
451
|
+
- `sweepJoint(jointName, from, to, steps, baseState?, collisionOptions?)`
|
|
452
|
+
|
|
453
|
+
Solved assembly helpers:
|
|
454
|
+
- `solved.toScene()` for rendering
|
|
455
|
+
- `solved.collisionReport()` for interference checks
|
|
456
|
+
- `solved.minClearance(partA, partB, searchLength?)`
|
|
457
|
+
- `solved.bom()` / `solved.bomCsv()`
|
|
458
|
+
- `bomToCsv(rows)` (standalone helper)
|
|
459
|
+
|
|
460
|
+
### `robotExport(options)`
|
|
461
|
+
Declares that the current script should also export an `assembly(...)` as a robot package for the SDF CLI.
|
|
462
|
+
|
|
463
|
+
Key fields:
|
|
464
|
+
- `assembly`: required source assembly graph
|
|
465
|
+
- `modelName`: simulator-facing model name
|
|
466
|
+
- `links.<part>.massKg` or `densityKgM3`: inertial hints
|
|
467
|
+
- `joints.<joint>.effort|velocity|damping|friction`: simulator tuning
|
|
468
|
+
- `plugins.diffDrive`: diff-drive plugin wiring for Gazebo
|
|
469
|
+
- `world.generateDemoWorld`: emit a simple obstacle-course world alongside the model
|
|
470
|
+
|
|
471
|
+
Example:
|
|
472
|
+
|
|
473
|
+
```javascript
|
|
474
|
+
robotExport({
|
|
475
|
+
assembly: mech,
|
|
476
|
+
modelName: "Forge Scout",
|
|
477
|
+
links: {
|
|
478
|
+
Base: { massKg: 12 },
|
|
479
|
+
},
|
|
480
|
+
plugins: {
|
|
481
|
+
diffDrive: {
|
|
482
|
+
leftJoints: ["leftFront", "leftRear"],
|
|
483
|
+
rightJoints: ["rightFront", "rightRear"],
|
|
484
|
+
wheelSeparationMm: 320,
|
|
485
|
+
wheelRadiusMm: 72,
|
|
486
|
+
},
|
|
487
|
+
},
|
|
488
|
+
});
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
## 3D Boolean Operations
|
|
492
|
+
|
|
493
|
+
### `union(...shapes)`
|
|
494
|
+
Combines shapes (additive).
|
|
495
|
+
|
|
496
|
+
```javascript
|
|
497
|
+
const combined = union(
|
|
498
|
+
box(50, 50, 10),
|
|
499
|
+
cylinder(20, 15).translate(25, 25, 10)
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
const parts = [
|
|
503
|
+
box(50, 50, 10),
|
|
504
|
+
cylinder(20, 15).translate(25, 25, 10),
|
|
505
|
+
];
|
|
506
|
+
const combinedFromArray = union(parts);
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### `difference(...shapes)`
|
|
510
|
+
Subtracts shapes[1..n] from shapes[0].
|
|
511
|
+
|
|
512
|
+
```javascript
|
|
513
|
+
const plate = box(100, 100, 5);
|
|
514
|
+
const hole1 = cylinder(6, 10).translate(25, 50, 0);
|
|
515
|
+
const hole2 = cylinder(6, 10).translate(75, 50, 0);
|
|
516
|
+
const result = difference(plate, hole1, hole2);
|
|
517
|
+
const resultFromArray = difference([plate, hole1, hole2]);
|
|
518
|
+
|
|
519
|
+
// Or using method syntax:
|
|
520
|
+
const result = plate.subtract(hole1, hole2);
|
|
521
|
+
const sameResult = plate.subtract([hole1, hole2]);
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
### `intersection(...shapes)`
|
|
525
|
+
Keeps only overlapping volume.
|
|
526
|
+
|
|
527
|
+
```javascript
|
|
528
|
+
const overlap = intersection(
|
|
529
|
+
sphere(30),
|
|
530
|
+
box(40, 40, 40, true)
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
const overlapFromArray = intersection([
|
|
534
|
+
sphere(30),
|
|
535
|
+
box(40, 40, 40, true),
|
|
536
|
+
]);
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
### Method Syntax
|
|
540
|
+
Shapes also have boolean methods:
|
|
541
|
+
|
|
542
|
+
```javascript
|
|
543
|
+
shape.add(other1, other2)
|
|
544
|
+
shape.add([other1, other2])
|
|
545
|
+
shape.subtract(other1, other2)
|
|
546
|
+
shape.subtract([other1, other2])
|
|
547
|
+
shape.intersect(other1, other2)
|
|
548
|
+
shape.intersect([other1, other2])
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
## Group
|
|
552
|
+
|
|
553
|
+
### `group(...items)`
|
|
554
|
+
Groups multiple shapes/sketches for joint transforms without merging them into a single mesh. Unlike `union`, colors and individual identities are preserved.
|
|
555
|
+
|
|
556
|
+
**Parameters:**
|
|
557
|
+
- `...items` (Shape | Sketch | TrackedShape | ShapeGroup | `{ name, shape? | sketch? | group? }`) - Items to group (nested groups allowed)
|
|
558
|
+
|
|
559
|
+
**Returns:** `ShapeGroup`
|
|
560
|
+
|
|
561
|
+
```javascript
|
|
562
|
+
const base = box(100, 100, 5).color('#888888');
|
|
563
|
+
const column = cylinder(40, 5).translate(50, 50, 5).color('#4488cc');
|
|
564
|
+
|
|
565
|
+
// Group them — they stay separate but transform together
|
|
566
|
+
const assembly = group(base, column).translate(200, 0, 0);
|
|
567
|
+
return assembly;
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
Named child descriptors are useful when the group will be flattened later, especially inside assemblies:
|
|
571
|
+
|
|
572
|
+
```javascript
|
|
573
|
+
const shell = box(80, 60, 24).color('#6e7b88');
|
|
574
|
+
const lid = box(80, 60, 4).translate(0, 0, 24).color('#c9d2db');
|
|
575
|
+
|
|
576
|
+
const housing = group(
|
|
577
|
+
{ name: 'Shell', shape: shell },
|
|
578
|
+
{ name: 'Lid', shape: lid },
|
|
579
|
+
);
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
When that group is returned directly, each named child keeps its own viewport object. When the group is used as an assembly part, Forge uses those child names to produce labels such as `Base Assembly.Lid` instead of `Base Assembly.2`.
|
|
583
|
+
|
|
584
|
+
### ShapeGroup Methods
|
|
585
|
+
|
|
586
|
+
**Transforms** — all chainable, return a new ShapeGroup. Placement references (see below) are transformed along with the children.
|
|
587
|
+
|
|
588
|
+
```javascript
|
|
589
|
+
group.translate(x, y, z)
|
|
590
|
+
group.moveTo(x, y, z)
|
|
591
|
+
group.moveToLocal(target, x, y, z)
|
|
592
|
+
group.attachTo(target, targetAnchor, selfAnchor?, offset?)
|
|
593
|
+
group.rotate(x, y, z)
|
|
594
|
+
group.rotateAround(axis, angleDeg, pivot?)
|
|
595
|
+
group.rotateAroundTo(axis, pivot, movingPoint, targetPoint, opts?)
|
|
596
|
+
group.pointAlong(direction)
|
|
597
|
+
group.transform(m)
|
|
598
|
+
group.scale(v)
|
|
599
|
+
group.mirror(normal)
|
|
600
|
+
group.color(hex) // applies to all children
|
|
601
|
+
group.clone()
|
|
602
|
+
group.duplicate() // alias
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
**Placement references** — same API as `Shape`. References survive all transforms and `importGroup()` round-trips.
|
|
606
|
+
|
|
607
|
+
```javascript
|
|
608
|
+
group.withReferences({ points?, edges?, surfaces?, objects? })
|
|
609
|
+
group.referenceNames(kind?)
|
|
610
|
+
group.referencePoint(ref)
|
|
611
|
+
group.placeReference(ref, target, offset?)
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
**Child access** — get a named child as a live `Shape`/`TrackedShape`/`ShapeGroup`.
|
|
615
|
+
|
|
616
|
+
```javascript
|
|
617
|
+
group.child(name) // throws if name not found
|
|
618
|
+
group.children // raw array
|
|
619
|
+
group.childNames // name array (parallel to children)
|
|
620
|
+
group.childName(index) // name at index
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
`group.rotateAround(...)` is convenience sugar for `group.transform(Transform.rotationAxis(...))`.
|
|
624
|
+
`group.rotateAroundTo(...)` is convenience sugar for `group.transform(Transform.rotateAroundTo(...))`.
|
|
625
|
+
`group.pointAlong(...)` is convenience sugar for a group-wide axis rotation from Z to `direction`.
|
|
626
|
+
|
|
627
|
+
```javascript
|
|
628
|
+
const hingeY = 40;
|
|
629
|
+
const lid = group(shell, logo);
|
|
630
|
+
|
|
631
|
+
const openedA = lid.rotateAround([1, 0, 0], 35, [0, hingeY, 0]); // sugar
|
|
632
|
+
const openedB = lid.transform(Transform.rotationAxis([1, 0, 0], 35, [0, hingeY, 0])); // equivalent
|
|
633
|
+
|
|
634
|
+
const laidDown = lid.pointAlong([1, 0, 0]); // same intent as Shape/TrackedShape.pointAlong
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
### ShapeGroup and placement references
|
|
638
|
+
|
|
639
|
+
`group()` fully supports `.withReferences()`, `.placeReference()`, and `.referencePoint()` — the same API available on `Shape` and `TrackedShape`. References survive every transform (`translate`, `rotate`, `scale`, `mirror`, `transform`, `rotateAround`, etc.).
|
|
640
|
+
|
|
641
|
+
```javascript
|
|
642
|
+
const bracketGroup = group(
|
|
643
|
+
{ name: 'Bracket Left', shape: leftBracket },
|
|
644
|
+
{ name: 'Bracket Right', shape: rightBracket },
|
|
645
|
+
{ name: 'Dowel', shape: dowel },
|
|
646
|
+
).withReferences({
|
|
647
|
+
points: {
|
|
648
|
+
mountCenter: [0, 0, 0],
|
|
649
|
+
topCenter: [34, 30, 40],
|
|
650
|
+
},
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
// Place the group by a named reference point
|
|
654
|
+
const placed = bracketGroup.placeReference('mountCenter', [100, 0, 0]);
|
|
655
|
+
|
|
656
|
+
// Reference survives the translation:
|
|
657
|
+
const pt = placed.referencePoint('mountCenter'); // → [100, 0, 0]
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
When a ShapeGroup is returned from a script, each child becomes a separate viewport object with its own visibility/color controls. Named children keep those names in the viewport/object tree.
|
|
661
|
+
|
|
662
|
+
## 3D Anchor Positioning
|
|
663
|
+
|
|
664
|
+
### `.attachTo(target, targetAnchor, selfAnchor?, offset?)`
|
|
665
|
+
Position a shape relative to another using named 3D anchor points based on bounding boxes.
|
|
666
|
+
|
|
667
|
+
Available on both `Shape` and `TrackedShape`.
|
|
668
|
+
|
|
669
|
+
**Parameters:**
|
|
670
|
+
- `target` (Shape | TrackedShape) — The shape to attach to
|
|
671
|
+
- `targetAnchor` (Anchor3D) — Point on target
|
|
672
|
+
- `selfAnchor` (Anchor3D, optional) — Point on this shape to align. Default: 'center'
|
|
673
|
+
- `offset` ([number, number, number], optional) — Additional offset after alignment
|
|
674
|
+
|
|
675
|
+
**Anchor3D values:**
|
|
676
|
+
- `'center'` — bounding box center
|
|
677
|
+
- Face centers (1 axis pinned): `'front'` (−Y), `'back'` (+Y), `'left'` (−X), `'right'` (+X), `'top'` (+Z), `'bottom'` (−Z)
|
|
678
|
+
- Edge midpoints (2 axes pinned): `'front-left'`, `'front-right'`, `'back-left'`, `'back-right'`, `'top-front'`, `'top-back'`, `'top-left'`, `'top-right'`, `'bottom-front'`, `'bottom-back'`, `'bottom-left'`, `'bottom-right'`
|
|
679
|
+
- True corners (3 axes pinned): `'top-front-left'`, `'top-front-right'`, `'top-back-left'`, `'top-back-right'`, `'bottom-front-left'`, `'bottom-front-right'`, `'bottom-back-left'`, `'bottom-back-right'`
|
|
680
|
+
|
|
681
|
+
Anchor word order is flexible for built-ins: `'front-left'` and `'left-front'` are treated the same.
|
|
682
|
+
|
|
683
|
+
**Returns:** Same type as caller (Shape or TrackedShape)
|
|
684
|
+
|
|
685
|
+
```javascript
|
|
686
|
+
const base = box(100, 100, 10);
|
|
687
|
+
const column = cylinder(50, 8);
|
|
688
|
+
|
|
689
|
+
// Place column on top of base, centered
|
|
690
|
+
const placed = column.attachTo(base, 'top', 'bottom');
|
|
691
|
+
|
|
692
|
+
// Stack boxes: place b on top of a, aligned at back-left corner
|
|
693
|
+
const a = box(50, 50, 20);
|
|
694
|
+
const b = box(30, 30, 10);
|
|
695
|
+
const stacked = b.attachTo(a, 'top-back-left', 'bottom-back-left');
|
|
696
|
+
|
|
697
|
+
// Place with offset: center on top, then shift 10mm right
|
|
698
|
+
const shifted = column.attachTo(base, 'top', 'bottom', [10, 0, 0]);
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
### `.onFace(parent, face, opts?)`
|
|
702
|
+
Place a shape on a specific face of a parent shape. Think of it like sticking a label on a box surface.
|
|
703
|
+
|
|
704
|
+
**Parameters:**
|
|
705
|
+
- `parent` (Shape | TrackedShape) — The parent shape
|
|
706
|
+
- `face` ('front' | 'back' | 'left' | 'right' | 'top' | 'bottom') — Which face to place on
|
|
707
|
+
- `opts` (object, optional):
|
|
708
|
+
- `u` (number) — Horizontal offset within the face (from center). Default: 0
|
|
709
|
+
- `v` (number) — Vertical offset within the face (from center). Default: 0
|
|
710
|
+
- `protrude` (number) — How far the child sticks out from the face. Default: 0
|
|
711
|
+
|
|
712
|
+
**Face coordinate mapping (u, v):**
|
|
713
|
+
- front/back: u = left/right (X), v = up/down (Z)
|
|
714
|
+
- left/right: u = forward/back (Y), v = up/down (Z)
|
|
715
|
+
- top/bottom: u = left/right (X), v = forward/back (Y)
|
|
716
|
+
|
|
717
|
+
**Returns:** Same type as caller
|
|
718
|
+
|
|
719
|
+
```javascript
|
|
720
|
+
const body = box(100, 40, 60, true);
|
|
721
|
+
|
|
722
|
+
// Vent on front face, centered, 15mm below center, protruding 2mm
|
|
723
|
+
const vent = box(80, 2, 12, true).color('#333')
|
|
724
|
+
.onFace(body, 'front', { v: -15, protrude: 2 });
|
|
725
|
+
|
|
726
|
+
// Display near top-right of front face
|
|
727
|
+
const display = box(35, 1.5, 8, true).color('#00ddee')
|
|
728
|
+
.onFace(body, 'front', { u: 20, v: 15, protrude: 1 });
|
|
729
|
+
|
|
730
|
+
// Fan on top, protruding 5mm
|
|
731
|
+
const fan = cylinder(10, 40).color('#333')
|
|
732
|
+
.onFace(body, 'top', { protrude: 5 });
|
|
733
|
+
|
|
734
|
+
// Side vent on left face
|
|
735
|
+
const sideVent = box(2, 30, 40, true).color('#666')
|
|
736
|
+
.onFace(body, 'left', { protrude: 1 });
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
**When to use `onFace()` vs `attachTo()`:**
|
|
740
|
+
- `onFace()` — placing surface details (vents, displays, buttons, labels) on a parent body
|
|
741
|
+
- `attachTo()` — stacking independent parts (column on base, unit on wall)
|
|
742
|
+
|
|
743
|
+
## Advanced 3D Operations
|
|
744
|
+
|
|
745
|
+
### `hull3d(...args)`
|
|
746
|
+
Convex hull of multiple shapes and/or points.
|
|
747
|
+
|
|
748
|
+
```javascript
|
|
749
|
+
const hull = hull3d(
|
|
750
|
+
sphere(10),
|
|
751
|
+
sphere(10).translate(50, 0, 0),
|
|
752
|
+
[25, 0, 30], // bare point
|
|
753
|
+
);
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
### `levelSet(sdf, bounds, edgeLength, level?)`
|
|
757
|
+
Create a shape from a signed distance function (SDF). Positive = inside.
|
|
758
|
+
|
|
759
|
+
```javascript
|
|
760
|
+
const gyroid = levelSet(
|
|
761
|
+
([x, y, z]) => Math.sin(x) * Math.cos(y) + Math.sin(y) * Math.cos(z) + Math.sin(z) * Math.cos(x),
|
|
762
|
+
{ min: [-10, -10, -10], max: [10, 10, 10] },
|
|
763
|
+
0.5, // edge length (resolution)
|
|
764
|
+
);
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
### Smoothing
|
|
768
|
+
|
|
769
|
+
```javascript
|
|
770
|
+
// Mark edges for smoothing, then subdivide
|
|
771
|
+
const smooth = box(50, 50, 50, true)
|
|
772
|
+
.smoothOut(60) // edges sharper than 60° get smoothed
|
|
773
|
+
.refine(4); // subdivide 4 times
|
|
774
|
+
|
|
775
|
+
// Or refine by edge length / tolerance
|
|
776
|
+
shape.refineToLength(2); // max edge length 2mm
|
|
777
|
+
shape.refineToTolerance(0.1); // max deviation 0.1mm from smooth surface
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
### Cutting
|
|
781
|
+
|
|
782
|
+
```javascript
|
|
783
|
+
// Split by another shape → [inside, outside]
|
|
784
|
+
const [inside, outside] = shape.split(cutter);
|
|
785
|
+
|
|
786
|
+
// Split by infinite plane → [positive side, opposite side]
|
|
787
|
+
const [above, below] = shape.splitByPlane([0, 0, 1], 10); // Z=10 plane
|
|
788
|
+
|
|
789
|
+
// Trim: keep the positive side of the plane
|
|
790
|
+
const trimmed = shape.trimByPlane([0, 0, 1], 10);
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
#### `shape.shell(thickness, options?)`
|
|
794
|
+
Hollow out a supported solid by keeping the outside wall and subtracting an inward cavity.
|
|
795
|
+
|
|
796
|
+
Current compiler-backed `shell()` support is intentionally narrow:
|
|
797
|
+
|
|
798
|
+
- compile-covered `box()`, `cylinder()`, and straight `Sketch.extrude()` solids
|
|
799
|
+
- optional `openFaces: ['top' | 'bottom']`
|
|
800
|
+
- rigid transforms before shelling are preserved through both lowerers
|
|
801
|
+
- defended named-face queries on the resulting outer shell faces plus created inner faces where Forge can model them exactly
|
|
802
|
+
|
|
803
|
+
Not supported yet:
|
|
804
|
+
|
|
805
|
+
- tapered extrudes (`scaleTop`)
|
|
806
|
+
- scale transforms before shelling
|
|
807
|
+
- already-shelled results
|
|
808
|
+
- general boolean, `revolve()`, `loft()`, `sweep()`, `sphere()`, hull, and plane-trim bases
|
|
809
|
+
|
|
810
|
+
Forge keeps `shell` as semantic compiler intent, then lowers it into backend-supported boolean/extrude/cylinder plans for both Manifold and CadQuery/OCCT.
|
|
811
|
+
|
|
812
|
+
Supported named-face subset on the shell result:
|
|
813
|
+
|
|
814
|
+
- preserved outer faces stay queryable on defended bases
|
|
815
|
+
- created inner faces use names like `inner-top`, `inner-bottom`, `inner-side`, or `inner-side-right`
|
|
816
|
+
- downstream `onFace(result, 'inner-side-right', ...)` placement is defended on the planar members of that subset
|
|
817
|
+
- generic straight extrudes still shell exactly, but named created-face support is currently limited to the defended profile families Forge can model directly (`rect`, `roundedRect`, `circle`)
|
|
818
|
+
|
|
819
|
+
```javascript
|
|
820
|
+
const cup = roundedRect(80, 50, 6, true)
|
|
821
|
+
.extrude(30)
|
|
822
|
+
.translate(4, -3, 2)
|
|
823
|
+
.shell(2.5, { openFaces: ['top'] });
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
#### `shape.hole(face, options)`
|
|
827
|
+
Compiler-owned circular hole workflow anchored to a face/workplane query.
|
|
828
|
+
|
|
829
|
+
- `depth` omitted = through-hole
|
|
830
|
+
- `depth` provided = blind hole
|
|
831
|
+
- `upToFace` = stop on a queried planar face parallel to the hole direction
|
|
832
|
+
- `extent: { forward, reverse? }` = two-sided extent with forward/reverse termination options
|
|
833
|
+
- `u` / `v` place the hole in face-local coordinates
|
|
834
|
+
- `counterbore: { diameter, depth }` adds a wider cylindrical recess at the entry
|
|
835
|
+
- `countersink: { diameter, angleDeg? }` adds a conical entry (default `90°`)
|
|
836
|
+
- `thread: { designation?, pitch?, class?, handedness?, depth?, modeled? }` carries thread intent metadata
|
|
837
|
+
- `depth` and `upToFace` are mutually exclusive with `extent`
|
|
838
|
+
- `counterbore` and `countersink` are mutually exclusive
|
|
839
|
+
|
|
840
|
+
```javascript
|
|
841
|
+
const block = roundedRect(90, 60, 8, true).extrude(24);
|
|
842
|
+
const exitFace = block.face('bottom');
|
|
843
|
+
|
|
844
|
+
const body = block
|
|
845
|
+
.hole('front', { diameter: 8, u: 0, v: 2 }) // through
|
|
846
|
+
.hole('top', { diameter: 6, u: -18, v: 10, depth: 10 }) // blind
|
|
847
|
+
.hole('top', {
|
|
848
|
+
diameter: 5,
|
|
849
|
+
u: 18,
|
|
850
|
+
v: 10,
|
|
851
|
+
upToFace: exitFace,
|
|
852
|
+
counterbore: { diameter: 9, depth: 4 },
|
|
853
|
+
});
|
|
854
|
+
```
|
|
855
|
+
|
|
856
|
+
Supported subset:
|
|
857
|
+
|
|
858
|
+
- compile-covered target bodies
|
|
859
|
+
- canonical faces, tracked planar faces, and `FaceRef` targets
|
|
860
|
+
- through, blind, and planar `upToFace` circular holes
|
|
861
|
+
- two-sided extents with forward/reverse termination (blind or `upToFace` per side)
|
|
862
|
+
- counterbore and countersink entry variants on one-sided hole extents
|
|
863
|
+
- thread metadata for deferred thread intent (modeled threads not yet supported)
|
|
864
|
+
- exact lowering through both Manifold and CadQuery/OCCT
|
|
865
|
+
- created hole faces in the defended subset:
|
|
866
|
+
- `wall` on all supported hole variants
|
|
867
|
+
- `floor` on blind holes
|
|
868
|
+
- `cap` on reverse blind two-sided holes
|
|
869
|
+
- `counterbore-wall` and `counterbore-floor` on counterbored holes
|
|
870
|
+
- `countersink-wall` on countersunk holes
|
|
871
|
+
- preserved non-host faces stay queryable where Forge can defend them
|
|
872
|
+
- rewritten host/exit faces now stay queryable as defended descendant regions when Forge can keep one stable source surface/frame
|
|
873
|
+
- reusing the same `upToFace` stop plane through later rewrites is supported when you keep a `FaceRef` from the earlier face (`const exitFace = block.face('bottom')`)
|
|
874
|
+
|
|
875
|
+
Not supported yet:
|
|
876
|
+
|
|
877
|
+
- non-planar or non-parallel `upToFace` termination faces
|
|
878
|
+
- two-sided extents combined with counterbore or countersink heads
|
|
879
|
+
- modeled helical threads (pass thread metadata with `modeled: false` or omitted for deferred intent)
|
|
880
|
+
- combined counterbore+countersink heads
|
|
881
|
+
- drafted/tapered main holes
|
|
882
|
+
- segmented polygonal hole cutters
|
|
883
|
+
- runtime-only target bodies without compiler intent
|
|
884
|
+
|
|
885
|
+
#### `shape.cutout(sketch, options?)`
|
|
886
|
+
Compiler-owned cut-extrude workflow using a sketch already placed with `Sketch.onFace(...)`.
|
|
887
|
+
|
|
888
|
+
- `depth` omitted = through-cut
|
|
889
|
+
- `depth` provided = blind cut
|
|
890
|
+
- `upToFace` = stop on a queried planar face parallel to the cut direction
|
|
891
|
+
- `extent: { forward, reverse? }` = two-sided extent with forward/reverse termination options
|
|
892
|
+
- `taperScale` = draft angle for tapered cuts (uniform or `[x, y]` pair)
|
|
893
|
+
- the sketch must carry semantic workplane placement from `onFace(...)`
|
|
894
|
+
|
|
895
|
+
```javascript
|
|
896
|
+
const block = roundedRect(90, 60, 8, true).extrude(24);
|
|
897
|
+
const exitFace = block.face('bottom');
|
|
898
|
+
const pocket = roundedRect(18, 10, 2, true)
|
|
899
|
+
.onFace(block, 'top', { u: 14, v: -8, selfAnchor: 'center' });
|
|
900
|
+
|
|
901
|
+
const body = block.cutout(pocket, { upToFace: exitFace });
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
Supported subset:
|
|
905
|
+
|
|
906
|
+
- compile-covered sketch profiles that CadQuery/OCCT already supports
|
|
907
|
+
- sketches placed on queried faces via `onFace(...)`
|
|
908
|
+
- through, blind, and planar `upToFace` cut extents
|
|
909
|
+
- two-sided extents with forward/reverse termination
|
|
910
|
+
- tapered cuts via `taperScale` on circle, rect, and roundedRect profiles
|
|
911
|
+
- exact/runtime parity through the shared compiler node family
|
|
912
|
+
- created cut faces in the defended subset:
|
|
913
|
+
- `floor` on blind cuts
|
|
914
|
+
- `wall` on circular cuts
|
|
915
|
+
- `wall-bottom`, `wall-right`, `wall-top`, `wall-left` on rectangular and rounded-rectangle cuts
|
|
916
|
+
- `cap` on reverse blind two-sided cuts
|
|
917
|
+
- preserved non-host faces stay queryable where Forge can defend them
|
|
918
|
+
- rewritten host/exit faces now stay queryable as defended descendant regions when Forge can keep one stable source surface/frame
|
|
919
|
+
- reusing the same `upToFace` stop plane through later rewrites is supported when you keep a `FaceRef` from the earlier face
|
|
920
|
+
|
|
921
|
+
Not supported yet:
|
|
922
|
+
|
|
923
|
+
- free-floating sketches without face/workplane provenance
|
|
924
|
+
- non-planar or non-parallel `upToFace` termination faces
|
|
925
|
+
- two-sided extents combined with `taperScale`
|
|
926
|
+
- named created-wall support for arbitrary boolean/offset/projected cut profiles
|
|
927
|
+
|
|
928
|
+
#### `sheetMetal(options)`
|
|
929
|
+
Compiler-owned sheet-metal v1 builder.
|
|
930
|
+
|
|
931
|
+
`sheetMetal(...)` returns a `SheetMetalPart`, not a solid directly.
|
|
932
|
+
|
|
933
|
+
Core flow:
|
|
934
|
+
|
|
935
|
+
- define the base panel, thickness, bend radius, and explicit `kFactor`
|
|
936
|
+
- add `90°` edge flanges with `.flange(...)`
|
|
937
|
+
- add planar panel/flange cutouts with `.cutout(...)`
|
|
938
|
+
- materialize either `.folded()` or `.flatPattern()`
|
|
939
|
+
|
|
940
|
+
```javascript
|
|
941
|
+
const cover = sheetMetal({
|
|
942
|
+
panel: { width: 180, height: 110 },
|
|
943
|
+
thickness: 1.5,
|
|
944
|
+
bendRadius: 2,
|
|
945
|
+
bendAllowance: { kFactor: 0.42 },
|
|
946
|
+
cornerRelief: { size: 4 },
|
|
947
|
+
})
|
|
948
|
+
.flange('top', { length: 18 })
|
|
949
|
+
.flange('right', { length: 18 })
|
|
950
|
+
.flange('bottom', { length: 18 })
|
|
951
|
+
.flange('left', { length: 18 })
|
|
952
|
+
.cutout('panel', rect(72, 36, true), { selfAnchor: 'center' })
|
|
953
|
+
.cutout('flange-right', roundedRect(26, 10, 5, true), { selfAnchor: 'center' });
|
|
954
|
+
|
|
955
|
+
const folded = cover.folded();
|
|
956
|
+
const flat = cover.flatPattern();
|
|
957
|
+
```
|
|
958
|
+
|
|
959
|
+
Supported v1 subset:
|
|
960
|
+
|
|
961
|
+
- one base panel
|
|
962
|
+
- up to four `90°` edge flanges
|
|
963
|
+
- constant thickness
|
|
964
|
+
- explicit bend radius plus explicit K-factor bend metadata
|
|
965
|
+
- rectangular corner reliefs
|
|
966
|
+
- planar cutouts on the panel and existing flange regions
|
|
967
|
+
- defended semantic regions such as `panel`, `flange-right`, and `bend-right`
|
|
968
|
+
|
|
969
|
+
Not supported yet:
|
|
970
|
+
|
|
971
|
+
- arbitrary solid conversion into sheet metal
|
|
972
|
+
- hems, jogs, lofted bends, or broader miter-corner logic
|
|
973
|
+
- nonuniform thickness
|
|
974
|
+
- cutouts directly on bend regions
|
|
975
|
+
|
|
976
|
+
Use [sheet-metal.md](sheet-metal.md) for the full sheet-metal contract and the maintained `folded-service-panel-cover` proof model.
|
|
977
|
+
|
|
978
|
+
### Warping
|
|
979
|
+
|
|
980
|
+
```javascript
|
|
981
|
+
// Deform vertices with arbitrary function
|
|
982
|
+
const warped = box(50, 50, 50, true).warp(([x, y, z]) => {
|
|
983
|
+
// Twist around Z axis
|
|
984
|
+
const angle = z * 0.05;
|
|
985
|
+
const cos = Math.cos(angle), sin = Math.sin(angle);
|
|
986
|
+
const nx = x * cos - y * sin;
|
|
987
|
+
const ny = x * sin + y * cos;
|
|
988
|
+
return [nx, ny, z];
|
|
989
|
+
});
|
|
990
|
+
```
|
|
991
|
+
|
|
992
|
+
### Plane Operations
|
|
993
|
+
|
|
994
|
+
```javascript
|
|
995
|
+
// Cross-section: intersect shape with a plane → Sketch
|
|
996
|
+
const section = intersectWithPlane(shape, { plane: 'XY', offset: 10 });
|
|
997
|
+
|
|
998
|
+
// Project: flatten shape onto a plane → Sketch
|
|
999
|
+
const shadow = projectToPlane(shape, { origin: [0, 0, 0], normal: [0, 0, 1] });
|
|
1000
|
+
```
|
|
1001
|
+
|
|
1002
|
+
`projectToPlane()` always works as a runtime modeling utility.
|
|
1003
|
+
|
|
1004
|
+
Compiler-owned replay is narrower today:
|
|
1005
|
+
- supported for downstream exact export when the projected source reduces to one defended planar projection basis:
|
|
1006
|
+
- plain `extrude()` / `box()` / `cylinder()` sources in their native XY basis
|
|
1007
|
+
- `Sketch.onFace()`-placed straight extrusions on a matching parallel target plane
|
|
1008
|
+
- compatible `shell()` / through-`hole()` / through-`cutout()` descendants aligned to that same basis
|
|
1009
|
+
- boolean `union()` combinations of compatible projected operands, including mirrored/patterned descendants
|
|
1010
|
+
- aligned blind holes/cuts keep the same replayed silhouette, while aligned through holes/cuts subtract their projected profile from it
|
|
1011
|
+
- the target plane still has to stay parallel to that defended basis without introducing in-plane shear
|
|
1012
|
+
- boolean `difference()` / `intersection()` sources, trim/fillet/chamfer silhouette changes, and non-parallel host workplanes still return a usable runtime sketch but fall back to explicit compiler diagnostics instead of pretending the projection is exact-safe
|
|
1013
|
+
|
|
1014
|
+
## 2D Sketches
|
|
1015
|
+
|
|
1016
|
+
This file intentionally avoids repeating full 2D API signatures that already live in dedicated docs.
|
|
1017
|
+
|
|
1018
|
+
Use these canonical files:
|
|
1019
|
+
|
|
1020
|
+
- [sketch-core.md](sketch-core.md) - `Sketch` basics, queries, anchors, color, clone/duplicate
|
|
1021
|
+
- [sketch-primitives.md](sketch-primitives.md) - `rect`, `circle2d`, `roundedRect`, `polygon`, `ngon`, `ellipse`, `slot`, `star`
|
|
1022
|
+
- [sketch-path.md](sketch-path.md) - `path()` builder and `stroke(...)`
|
|
1023
|
+
- [sketch-transforms.md](sketch-transforms.md) - translate/rotate/scale/mirror for sketches
|
|
1024
|
+
- [sketch-booleans.md](sketch-booleans.md) - `union2d`/`difference2d`/`intersection2d`/`hull2d` and method forms
|
|
1025
|
+
- [sketch-operations.md](sketch-operations.md) - `offset`, `filletCorners`, `simplify`, `warp`, hull
|
|
1026
|
+
- [sketch-on-face.md](sketch-on-face.md) - sketch placement on planar faces
|
|
1027
|
+
- [sketch-extrude.md](sketch-extrude.md) - `extrude` and `revolve`
|
|
1028
|
+
- [sketch-anchor.md](sketch-anchor.md) - 2D anchor-based positioning
|
|
1029
|
+
- [entities.md](entities.md) - constrained sketches, named entities, topology-aware utilities
|
|
1030
|
+
|
|
1031
|
+
Integration rule: start with the smallest relevant doc set and add more only when the task expands.
|
|
1032
|
+
|
|
1033
|
+
### Curves & Surfacing
|
|
1034
|
+
|
|
1035
|
+
#### `spline2d(points, options?)`
|
|
1036
|
+
Build a smooth Catmull-Rom spline sketch from 2D control points.
|
|
1037
|
+
|
|
1038
|
+
**Options:**
|
|
1039
|
+
- `closed` (boolean) - Default: `true`
|
|
1040
|
+
- `tension` (number, 0..1) - Default: `0.5`
|
|
1041
|
+
- `samplesPerSegment` (number) - Default: `16`
|
|
1042
|
+
- `strokeWidth` (number) - Required when `closed: false` (creates a stroked solid)
|
|
1043
|
+
- `join` (`'Round' | 'Square'`) - Stroke corner style for open splines. Default: `'Round'`
|
|
1044
|
+
|
|
1045
|
+
```javascript
|
|
1046
|
+
const closed = spline2d([[20,0],[12,10],[0,12],[-12,10],[-20,0],[0,-14]]);
|
|
1047
|
+
const openRail = spline2d([[0,0],[30,20],[70,10]], { closed: false, strokeWidth: 3 });
|
|
1048
|
+
```
|
|
1049
|
+
|
|
1050
|
+
#### `spline3d(points, options?)` / `Curve3D`
|
|
1051
|
+
Create a reusable 3D spline curve object.
|
|
1052
|
+
|
|
1053
|
+
`Curve3D` methods:
|
|
1054
|
+
- `.sample(count?)`
|
|
1055
|
+
- `.sampleBySegment(samplesPerSegment?)`
|
|
1056
|
+
- `.pointAt(t)` where `t` is `[0..1]`
|
|
1057
|
+
- `.tangentAt(t)`
|
|
1058
|
+
- `.length(samples?)`
|
|
1059
|
+
|
|
1060
|
+
```javascript
|
|
1061
|
+
const rail = spline3d(
|
|
1062
|
+
[[0,0,0], [20,10,30], [40,0,60]],
|
|
1063
|
+
{ tension: 0.45 }
|
|
1064
|
+
);
|
|
1065
|
+
```
|
|
1066
|
+
|
|
1067
|
+
#### `loft(profiles, heights, options?)`
|
|
1068
|
+
Loft between multiple sketches along Z stations.
|
|
1069
|
+
|
|
1070
|
+
This implementation interpolates signed-distance fields and meshes via level-set extraction, so profiles can differ in vertex count/topology. Forge now records that loft intent in the compiler graph as well, and compatible loft stacks can export through the CadQuery/OCCT exact route even though viewport/runtime geometry remains sampled.
|
|
1071
|
+
|
|
1072
|
+
Performance note: `loft()` is significantly heavier than primitive/extrude/revolve paths. Use loft only when profile interpolation is required. If your part is axis-symmetric (bottles, vases, knobs, lathe-style parts), prefer `revolve()` for much faster generation.
|
|
1073
|
+
|
|
1074
|
+
**Parameters:**
|
|
1075
|
+
- `profiles` (`Sketch[]`) - At least 2
|
|
1076
|
+
- `heights` (`number[]`) - Same length as `profiles`, strictly increasing
|
|
1077
|
+
- `options`:
|
|
1078
|
+
- `edgeLength` (number) - Mesh resolution
|
|
1079
|
+
- `boundsPadding` (number) - Extra level-set bounds padding
|
|
1080
|
+
|
|
1081
|
+
```javascript
|
|
1082
|
+
const body = loft(
|
|
1083
|
+
[circle2d(20), roundedRect(30, 24, 6, true), circle2d(10)],
|
|
1084
|
+
[0, 40, 70],
|
|
1085
|
+
{ edgeLength: 1.0 }
|
|
1086
|
+
);
|
|
1087
|
+
```
|
|
1088
|
+
|
|
1089
|
+
#### `sweep(profile, path, options?)`
|
|
1090
|
+
Sweep a 2D profile along a 3D path (`Curve3D` or point polyline).
|
|
1091
|
+
|
|
1092
|
+
Performance note: `sweep()` also uses level-set meshing internally. Prefer direct primitives/extrude/revolve when they can express the same shape. Forge records the sampled sweep path in the compiler graph, so compatible sweeps can export through the CadQuery/OCCT exact route using that canonical path representation.
|
|
1093
|
+
|
|
1094
|
+
**Parameters:**
|
|
1095
|
+
- `profile` (`Sketch`) - Local cross-section in XY plane
|
|
1096
|
+
- `path` (`Curve3D | [x,y,z][]`)
|
|
1097
|
+
- `options`:
|
|
1098
|
+
- `samples` (number) - Sampling count for `Curve3D` paths (default `48`)
|
|
1099
|
+
- `edgeLength` (number) - Mesh resolution
|
|
1100
|
+
- `boundsPadding` (number) - Extra level-set bounds padding
|
|
1101
|
+
- `up` (`[x,y,z]`) - Preferred frame-up vector
|
|
1102
|
+
|
|
1103
|
+
```javascript
|
|
1104
|
+
const tubePath = spline3d([[0,0,0], [20,0,20], [40,10,30]]);
|
|
1105
|
+
const tube = sweep(circle2d(3), tubePath, { samples: 36, edgeLength: 0.7 });
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
## Entities, Constraints, and Patterns
|
|
1109
|
+
|
|
1110
|
+
`reference.md` delegates detailed entity/constraint coverage to [entities.md](entities.md) to avoid duplication.
|
|
1111
|
+
|
|
1112
|
+
Use [entities.md](entities.md) for:
|
|
1113
|
+
|
|
1114
|
+
- `Point2D`, `Line2D`, `Circle2D`, `Rectangle2D`
|
|
1115
|
+
- `TrackedShape` topology access (`face`, `edge`, `faceNames`, `edgeNames`, `toShape`)
|
|
1116
|
+
- `constrainedSketch()` and `Constraint.*`
|
|
1117
|
+
- `linearPattern`, `circularPattern`, `mirrorCopy`
|
|
1118
|
+
- `filletEdge`, `chamferEdge`, `arcBridgeBetweenRects`
|
|
1119
|
+
- Utility helpers like `degrees(...)` and `radians(...)`
|
|
1120
|
+
|
|
1121
|
+
## Multi-File Projects
|
|
1122
|
+
|
|
1123
|
+
ForgeCAD supports multi-file projects. Files are either **sketches** (`.sketch.js`, return a `Sketch`), **parts** (`.forge.js`, return a `Shape`, `TrackedShape`, or `ShapeGroup`), or **SVG assets** (`.svg`, parsed into a `Sketch`).
|
|
1124
|
+
|
|
1125
|
+
### File Types
|
|
1126
|
+
- `*.sketch.js` — 2D sketch file; when used with `importSketch()`, must return a `Sketch`
|
|
1127
|
+
- `*.forge.js` — 3D Forge file:
|
|
1128
|
+
- when used with `importPart()`, must return a `Shape` or `TrackedShape`
|
|
1129
|
+
- when used with `importGroup()`, must return a `ShapeGroup` (via `group(...)`)
|
|
1130
|
+
- `*.svg` — vector artwork file, imported as sketch geometry
|
|
1131
|
+
|
|
1132
|
+
### Import Path Resolution
|
|
1133
|
+
- `./file.forge.js`, `./file.sketch.js`, `./asset.svg` (and `../...`) resolve relative to the file that calls imports
|
|
1134
|
+
- Bare paths like `api/bracket.forge.js` resolve from the opened project root
|
|
1135
|
+
- Leading `/` is treated as project-root relative
|
|
1136
|
+
|
|
1137
|
+
### `importSketch(fileName, paramOverridesOrSvgOptions?)`
|
|
1138
|
+
Imports a sketch and returns `Sketch`.
|
|
1139
|
+
|
|
1140
|
+
- For `*.sketch.js`: executes the file (must return `Sketch`)
|
|
1141
|
+
- For `*.svg`: parses vector geometry into a `Sketch`
|
|
1142
|
+
|
|
1143
|
+
**Parameters:**
|
|
1144
|
+
- `fileName` (string) — Import path (e.g. `"./profile.sketch.js"` or `"api/profile.sketch.js"`)
|
|
1145
|
+
- `paramOverridesOrSvgOptions` (optional object)
|
|
1146
|
+
- For `*.sketch.js`: import-time param overrides by param name
|
|
1147
|
+
- For `*.svg`: SVG import options (see `importSvgSketch`)
|
|
1148
|
+
|
|
1149
|
+
**Returns:** `Sketch`
|
|
1150
|
+
|
|
1151
|
+
```javascript
|
|
1152
|
+
// In a .forge.js file:
|
|
1153
|
+
const profile = importSketch("bracket-profile.sketch.js", {
|
|
1154
|
+
"Width": 42,
|
|
1155
|
+
"Height": 18,
|
|
1156
|
+
});
|
|
1157
|
+
return profile.extrude(50);
|
|
1158
|
+
```
|
|
1159
|
+
|
|
1160
|
+
```javascript
|
|
1161
|
+
// Import SVG and keep only the largest connected region
|
|
1162
|
+
const logo = importSketch("assets/logo.svg", {
|
|
1163
|
+
include: "auto",
|
|
1164
|
+
regionSelection: "largest",
|
|
1165
|
+
flattenTolerance: 0.25,
|
|
1166
|
+
});
|
|
1167
|
+
return logo.extrude(2);
|
|
1168
|
+
```
|
|
1169
|
+
|
|
1170
|
+
### `importSvgSketch(fileName, options?)`
|
|
1171
|
+
Parses an SVG file and returns a `Sketch`.
|
|
1172
|
+
|
|
1173
|
+
**Parameters:**
|
|
1174
|
+
- `fileName` (string) — Import path to an `.svg`
|
|
1175
|
+
- `options` (optional object):
|
|
1176
|
+
- `include`: `'auto' | 'fill' | 'stroke' | 'fill-and-stroke'` (default: `'auto'`)
|
|
1177
|
+
- `regionSelection`: `'all' | 'largest'` (default: `'all'`)
|
|
1178
|
+
- `maxRegions`: number (largest-first cap)
|
|
1179
|
+
- `minRegionArea`: number
|
|
1180
|
+
- `minRegionAreaRatio`: number (fraction of largest region area)
|
|
1181
|
+
- `flattenTolerance`: number (curve discretization tolerance)
|
|
1182
|
+
- `arcSegments`: number (minimum arc segment count)
|
|
1183
|
+
- `scale`: number (uniform scale factor)
|
|
1184
|
+
- `maxWidth`: number (uniformly downscale to keep final sketch width within this limit)
|
|
1185
|
+
- `maxHeight`: number (uniformly downscale to keep final sketch height within this limit)
|
|
1186
|
+
- `centerOnOrigin`: boolean (default: `false`, recenters final sketch bounds center to `(0, 0)`)
|
|
1187
|
+
- `simplify`: number (final simplify tolerance, default: `0`)
|
|
1188
|
+
- `invertY`: boolean (default: `true`, converts SVG Y-down to CAD Y-up)
|
|
1189
|
+
|
|
1190
|
+
**Returns:** `Sketch`
|
|
1191
|
+
|
|
1192
|
+
```javascript
|
|
1193
|
+
const badge = importSvgSketch("assets/badge.svg", {
|
|
1194
|
+
include: "fill-and-stroke",
|
|
1195
|
+
minRegionAreaRatio: 0.001,
|
|
1196
|
+
maxRegions: 8,
|
|
1197
|
+
maxWidth: 120,
|
|
1198
|
+
maxHeight: 80,
|
|
1199
|
+
centerOnOrigin: true,
|
|
1200
|
+
});
|
|
1201
|
+
return badge;
|
|
1202
|
+
```
|
|
1203
|
+
|
|
1204
|
+
### `importPart(fileName, paramOverrides?)`
|
|
1205
|
+
Executes another file and returns its result as a `Shape`. The target file may return either `Shape` or `TrackedShape` (tracked results are auto-unwrapped to `Shape`).
|
|
1206
|
+
|
|
1207
|
+
**Parameters:**
|
|
1208
|
+
- `fileName` (string) — Import path (e.g. `"./bracket.forge.js"` or `"api/bracket.forge.js"`)
|
|
1209
|
+
- `paramOverrides` (optional object) — Import-time parameter overrides by param name
|
|
1210
|
+
|
|
1211
|
+
**Returns:** `Shape` (chainable)
|
|
1212
|
+
|
|
1213
|
+
```javascript
|
|
1214
|
+
// Assembly: import parts and position them
|
|
1215
|
+
const bracket = importPart("bracket.forge.js", { "Thickness": 4 });
|
|
1216
|
+
const bracket2 = importPart("bracket.forge.js", { "Thickness": 8 })
|
|
1217
|
+
.translate(100, 0, 0)
|
|
1218
|
+
.rotate(0, 0, 180);
|
|
1219
|
+
|
|
1220
|
+
return union(bracket, bracket2);
|
|
1221
|
+
```
|
|
1222
|
+
|
|
1223
|
+
Imported parts can also carry named placement references:
|
|
1224
|
+
|
|
1225
|
+
```javascript
|
|
1226
|
+
// widget.forge.js
|
|
1227
|
+
return union(base, post).withReferences({
|
|
1228
|
+
points: {
|
|
1229
|
+
mount: [0, -16, -4],
|
|
1230
|
+
},
|
|
1231
|
+
objects: {
|
|
1232
|
+
post,
|
|
1233
|
+
},
|
|
1234
|
+
});
|
|
1235
|
+
```
|
|
1236
|
+
|
|
1237
|
+
```javascript
|
|
1238
|
+
// assembly.forge.js
|
|
1239
|
+
const widget = importPart("widget.forge.js")
|
|
1240
|
+
.placeReference("mount", [120, 40, 0]);
|
|
1241
|
+
|
|
1242
|
+
const cap = box(18, 18, 8, true)
|
|
1243
|
+
.attachTo(widget, "objects.post.top", "bottom");
|
|
1244
|
+
|
|
1245
|
+
return [widget, cap];
|
|
1246
|
+
```
|
|
1247
|
+
|
|
1248
|
+
### `importGroup(fileName, paramOverrides?)`
|
|
1249
|
+
Executes another file and returns its result as a `ShapeGroup`. The target file **must** return a `ShapeGroup` (i.e. use `group(...)` as the final `return`). Use this when you want to import a multipart component and either transform it as a unit or access individual named children separately.
|
|
1250
|
+
|
|
1251
|
+
**Parameters:**
|
|
1252
|
+
- `fileName` (string) — Import path (e.g. `"./bracket-assembly.forge.js"`)
|
|
1253
|
+
- `paramOverrides` (optional object) — Import-time parameter overrides by param name
|
|
1254
|
+
|
|
1255
|
+
**Returns:** `ShapeGroup` (chainable)
|
|
1256
|
+
|
|
1257
|
+
```javascript
|
|
1258
|
+
// bracket-assembly.forge.js ← the source file
|
|
1259
|
+
const thickness = param("Thickness", 4);
|
|
1260
|
+
const leftBracket = box(thickness, 60, 40).color('#5b7c8d');
|
|
1261
|
+
const rightBracket = box(thickness, 60, 40).translate(64, 0, 0).color('#5b7c8d');
|
|
1262
|
+
const dowel = cylinder(60, 3).rotate(90,0,0).translate(34, 60, 20).color('#d38b4d');
|
|
1263
|
+
|
|
1264
|
+
return group(
|
|
1265
|
+
{ name: "Bracket Left", shape: leftBracket },
|
|
1266
|
+
{ name: "Bracket Right", shape: rightBracket },
|
|
1267
|
+
{ name: "Dowel", shape: dowel },
|
|
1268
|
+
).withReferences({
|
|
1269
|
+
points: { mountCenter: [0, 30, 20] },
|
|
1270
|
+
});
|
|
1271
|
+
```
|
|
1272
|
+
|
|
1273
|
+
```javascript
|
|
1274
|
+
// scene.forge.js ← the consumer
|
|
1275
|
+
// Import and use as a whole unit
|
|
1276
|
+
const bracketA = importGroup("bracket-assembly.forge.js")
|
|
1277
|
+
.placeReference("mountCenter", [0, 0, 0]);
|
|
1278
|
+
|
|
1279
|
+
// Import with param overrides, then work on children individually
|
|
1280
|
+
const bracketB = importGroup("bracket-assembly.forge.js", { "Thickness": 6 });
|
|
1281
|
+
const leftOnly = bracketB.child("Bracket Left").color('#ff4444');
|
|
1282
|
+
const rightOnly = bracketB.child("Bracket Right").translate(200, 0, 0);
|
|
1283
|
+
|
|
1284
|
+
return [bracketA, leftOnly, rightOnly];
|
|
1285
|
+
```
|
|
1286
|
+
|
|
1287
|
+
**When to use `importGroup` vs `importPart`:**
|
|
1288
|
+
|
|
1289
|
+
| | `importPart` | `importGroup` |
|
|
1290
|
+
|---|---|---|
|
|
1291
|
+
| Source returns | `Shape` or `TrackedShape` | `ShapeGroup` via `group(...)` |
|
|
1292
|
+
| Result type | `Shape` — chainable, supports all boolean ops | `ShapeGroup` — children stay separate |
|
|
1293
|
+
| Access children | Not possible | `group.child("Name")` |
|
|
1294
|
+
| Placement refs | `.withReferences()` on the Shape | `.withReferences()` on the group |
|
|
1295
|
+
|
|
1296
|
+
### Import Rules
|
|
1297
|
+
- Circular imports are detected and throw an error
|
|
1298
|
+
- Imported files can be instantiated multiple times (each call is a fresh execution)
|
|
1299
|
+
- `paramOverrides` only affects that import call (other imports are independent)
|
|
1300
|
+
- Params supplied through `paramOverrides` are treated as fixed arguments for that import call
|
|
1301
|
+
- Relative imports (`./` / `../`) are resolved from the current file path
|
|
1302
|
+
- `importPart()` accepts `Shape` or `TrackedShape` results and always returns a chainable `Shape`
|
|
1303
|
+
- `importGroup()` accepts only `ShapeGroup` results; use `group(...)` as the return value in the source file
|
|
1304
|
+
- Source files can attach placement references with `.withReferences({ points, edges, surfaces, objects })` — works on both `Shape` and `ShapeGroup`
|
|
1305
|
+
- Imported tracked solids keep their named faces/edges as `surfaces.<faceName>` and `edges.<edgeName>` references
|
|
1306
|
+
- SVG import supports deterministic region filtering (`regionSelection`, `maxRegions`, area thresholds)
|
|
1307
|
+
- The returned `Shape`, `Sketch`, or `ShapeGroup` is fully chainable — use `.translate()`, `.rotate()`, etc.
|
|
1308
|
+
|
|
1309
|
+
### Plain JS Module Imports
|
|
1310
|
+
Alongside `importPart()` / `importSketch()`, regular JS `import` / `require(...)` is supported for utility modules.
|
|
1311
|
+
|
|
1312
|
+
- If a module uses `export` / `module.exports`, that export value is used.
|
|
1313
|
+
- If a module has no explicit exports and uses a top-level `return`, that return value becomes the module value (including arrays).
|
|
1314
|
+
- Do not mix explicit exports with top-level `return` in the same module; this throws an error.
|
|
1315
|
+
|
|
1316
|
+
```javascript
|
|
1317
|
+
// scene-items.js
|
|
1318
|
+
import { box, cylinder } from "forgecad";
|
|
1319
|
+
|
|
1320
|
+
return [
|
|
1321
|
+
{ name: "Plate", shape: box(20, 12, 2, true) },
|
|
1322
|
+
{ name: "Pin", shape: cylinder(14, 3, undefined, undefined, true).translate(0, 0, 8) },
|
|
1323
|
+
];
|
|
1324
|
+
```
|
|
1325
|
+
|
|
1326
|
+
```javascript
|
|
1327
|
+
// main.forge.js
|
|
1328
|
+
import items from "./scene-items.js";
|
|
1329
|
+
|
|
1330
|
+
return items.map((entry, index) => ({
|
|
1331
|
+
name: entry.name,
|
|
1332
|
+
shape: entry.shape.translate(index === 0 ? -20 : 20, 0, 0),
|
|
1333
|
+
}));
|
|
1334
|
+
```
|
|
1335
|
+
|
|
1336
|
+
### Placement References
|
|
1337
|
+
|
|
1338
|
+
### `.withReferences({ points?, edges?, surfaces?, objects? })`
|
|
1339
|
+
Attach named placement references to a `Shape`, `TrackedShape`, or `ShapeGroup`. References survive all normal transforms and import round-trips (`importPart()`, `importGroup()`).
|
|
1340
|
+
|
|
1341
|
+
**Reference kinds:**
|
|
1342
|
+
- `points`: exact 3D coordinates
|
|
1343
|
+
- `edges`: `{ start, end }` segments; default reference point is the midpoint
|
|
1344
|
+
- `surfaces`: `{ center, normal }`; default reference point is the center
|
|
1345
|
+
- `objects`: bounding boxes derived from another shape/group or explicit `{ min, max }`
|
|
1346
|
+
|
|
1347
|
+
```javascript
|
|
1348
|
+
const part = union(base, post).withReferences({
|
|
1349
|
+
points: {
|
|
1350
|
+
mount: [0, -16, -4],
|
|
1351
|
+
},
|
|
1352
|
+
edges: {
|
|
1353
|
+
postAxis: { start: [12, 0, 4], end: [12, 0, 30] },
|
|
1354
|
+
},
|
|
1355
|
+
surfaces: {
|
|
1356
|
+
mountingFace: { center: [0, -16, 0], normal: [0, -1, 0] },
|
|
1357
|
+
},
|
|
1358
|
+
objects: {
|
|
1359
|
+
base,
|
|
1360
|
+
post,
|
|
1361
|
+
},
|
|
1362
|
+
});
|
|
1363
|
+
```
|
|
1364
|
+
|
|
1365
|
+
### `.referenceNames(kind?)`
|
|
1366
|
+
Lists named placement references on a shape.
|
|
1367
|
+
|
|
1368
|
+
```javascript
|
|
1369
|
+
part.referenceNames(); // ['edges.postAxis', 'objects.base', 'objects.post', 'points.mount', ...]
|
|
1370
|
+
part.referenceNames('points'); // ['mount']
|
|
1371
|
+
```
|
|
1372
|
+
|
|
1373
|
+
### `.referencePoint(ref)`
|
|
1374
|
+
Resolve a placement reference to a world-space point.
|
|
1375
|
+
|
|
1376
|
+
Supported forms:
|
|
1377
|
+
- `mount` or `points.mount`
|
|
1378
|
+
- `edges.postAxis`
|
|
1379
|
+
- `edges.postAxis.start`
|
|
1380
|
+
- `surfaces.mountingFace`
|
|
1381
|
+
- `objects.post.top`
|
|
1382
|
+
|
|
1383
|
+
```javascript
|
|
1384
|
+
const p = part.referencePoint("objects.post.top");
|
|
1385
|
+
```
|
|
1386
|
+
|
|
1387
|
+
### `.placeReference(ref, [x, y, z], offset?)`
|
|
1388
|
+
Translate a shape so the given placement reference lands on a target coordinate.
|
|
1389
|
+
|
|
1390
|
+
```javascript
|
|
1391
|
+
const placed = importPart("widget.forge.js")
|
|
1392
|
+
.placeReference("mount", [120, 40, 0]);
|
|
1393
|
+
```
|
|
1394
|
+
|
|
1395
|
+
### `attachTo()` with named references
|
|
1396
|
+
|
|
1397
|
+
`attachTo()` still accepts the built-in 3D anchors, but it can now also consume named placement references:
|
|
1398
|
+
|
|
1399
|
+
```javascript
|
|
1400
|
+
const cap = box(18, 18, 8, true)
|
|
1401
|
+
.attachTo(widget, "objects.post.top", "bottom");
|
|
1402
|
+
```
|
|
1403
|
+
|
|
1404
|
+
### Typical Project Structure
|
|
1405
|
+
```
|
|
1406
|
+
my-project/
|
|
1407
|
+
├── base-profile.sketch.js ← 2D cross-section
|
|
1408
|
+
├── bracket.forge.js ← extrudes the sketch, adds holes
|
|
1409
|
+
└── assembly.forge.js ← imports multiple parts, positions them
|
|
1410
|
+
```
|
|
1411
|
+
|
|
1412
|
+
## Part Library
|
|
1413
|
+
|
|
1414
|
+
Pre-built parametric parts available via `lib.xxx()`. No imports needed.
|
|
1415
|
+
|
|
1416
|
+
### `lib.boltHole(diameter, depth)`
|
|
1417
|
+
Through-hole cylinder (centered).
|
|
1418
|
+
|
|
1419
|
+
### `lib.fastenerHole(opts)`
|
|
1420
|
+
Standardized metric hole helper with fits and optional counterbore/countersink.
|
|
1421
|
+
|
|
1422
|
+
```javascript
|
|
1423
|
+
const m4 = lib.fastenerHole({
|
|
1424
|
+
size: "M4",
|
|
1425
|
+
fit: "normal", // close | normal | loose | tap
|
|
1426
|
+
depth: 12,
|
|
1427
|
+
counterbore: { depth: 3.5 }, // diameter auto from size unless provided
|
|
1428
|
+
});
|
|
1429
|
+
```
|
|
1430
|
+
|
|
1431
|
+
### `lib.counterbore(holeDia, boreDia, boreDepth, totalDepth)`
|
|
1432
|
+
Through-hole with wider recess at top.
|
|
1433
|
+
|
|
1434
|
+
### `lib.tube(outerX, outerY, outerZ, wall)`
|
|
1435
|
+
Rectangular hollow tube.
|
|
1436
|
+
|
|
1437
|
+
### `lib.pipe(height, outerRadius, wall, segments?)`
|
|
1438
|
+
Hollow cylinder.
|
|
1439
|
+
|
|
1440
|
+
### `lib.hexNut(acrossFlats, height, holeDia)`
|
|
1441
|
+
Hex nut via intersection of 3 rotated slabs, with center bore.
|
|
1442
|
+
|
|
1443
|
+
### `lib.roundedBox(x, y, z, radius)`
|
|
1444
|
+
Approximate rounded box via union of axis-aligned slabs.
|
|
1445
|
+
|
|
1446
|
+
### `lib.bracket(width, height, depth, thick, holeDia?)`
|
|
1447
|
+
L-shaped mounting bracket with optional holes.
|
|
1448
|
+
|
|
1449
|
+
### `lib.holePattern(rows, cols, spacingX, spacingY, holeDia, depth)`
|
|
1450
|
+
Grid of cylindrical holes.
|
|
1451
|
+
|
|
1452
|
+
### `lib.spurGear(options)`
|
|
1453
|
+
Involute external spur gear with optional bore.
|
|
1454
|
+
|
|
1455
|
+
**Options:**
|
|
1456
|
+
- `module` (number) - Metric module (pitch diameter / tooth count)
|
|
1457
|
+
- `teeth` (integer) - Tooth count (>= 6)
|
|
1458
|
+
- `faceWidth` (number) - Extrusion width along Z
|
|
1459
|
+
- `pressureAngleDeg` (number, optional) - Default: `20`
|
|
1460
|
+
- `backlash` (number, optional) - Tangential backlash at pitch circle. Default: `0`
|
|
1461
|
+
- `clearance` (number, optional) - Root clearance. Default: `0.25 * module`
|
|
1462
|
+
- `addendum` (number, optional) - Tooth addendum. Default: `module`
|
|
1463
|
+
- `dedendum` (number, optional) - Tooth dedendum. Default: `addendum + clearance`
|
|
1464
|
+
- `boreDiameter` (number, optional) - Center bore diameter
|
|
1465
|
+
- `center` (boolean, optional) - Center extrusion around Z=0. Default: `true`
|
|
1466
|
+
- `segmentsPerTooth` (number, optional) - Involute sampling quality. Default: `10`
|
|
1467
|
+
|
|
1468
|
+
```javascript
|
|
1469
|
+
const pinion = lib.spurGear({
|
|
1470
|
+
module: 1.25,
|
|
1471
|
+
teeth: 14,
|
|
1472
|
+
faceWidth: 8,
|
|
1473
|
+
boreDiameter: 5,
|
|
1474
|
+
});
|
|
1475
|
+
```
|
|
1476
|
+
|
|
1477
|
+
### `lib.faceGear(options)`
|
|
1478
|
+
Face gear (crown style) where teeth are on one face (`top` or `bottom`) instead of the outer rim.
|
|
1479
|
+
|
|
1480
|
+
Uses the same involute tooth sizing inputs as `lib.spurGear(...)`, then projects the tooth band axially from one side.
|
|
1481
|
+
|
|
1482
|
+
**Options:**
|
|
1483
|
+
- all `lib.spurGear(...)` options, plus:
|
|
1484
|
+
- `side` (`'top' | 'bottom'`, optional) - Which face gets the teeth. Default: `'top'`
|
|
1485
|
+
- `toothHeight` (number, optional) - Tooth projection height from the selected face. Default: `module`
|
|
1486
|
+
|
|
1487
|
+
```javascript
|
|
1488
|
+
const face = lib.faceGear({
|
|
1489
|
+
module: 1.25,
|
|
1490
|
+
teeth: 36,
|
|
1491
|
+
faceWidth: 8,
|
|
1492
|
+
toothHeight: 1.2,
|
|
1493
|
+
side: 'top',
|
|
1494
|
+
boreDiameter: 8,
|
|
1495
|
+
});
|
|
1496
|
+
```
|
|
1497
|
+
|
|
1498
|
+
`lib.sideGear(...)` is kept as a compatibility alias.
|
|
1499
|
+
|
|
1500
|
+
### `lib.ringGear(options)`
|
|
1501
|
+
Internal ring gear with involute-derived tooth spaces.
|
|
1502
|
+
|
|
1503
|
+
**Options:**
|
|
1504
|
+
- `module` (number)
|
|
1505
|
+
- `teeth` (integer, >= 12)
|
|
1506
|
+
- `faceWidth` (number)
|
|
1507
|
+
- `pressureAngleDeg` (number, optional) - Default: `20`
|
|
1508
|
+
- `backlash` (number, optional) - Default: `0`
|
|
1509
|
+
- `clearance` (number, optional) - Default: `0.25 * module`
|
|
1510
|
+
- `addendum` (number, optional) - Default: `module`
|
|
1511
|
+
- `dedendum` (number, optional) - Default: `addendum + clearance`
|
|
1512
|
+
- `rimWidth` (number, optional) - Radial ring thickness outside tooth roots
|
|
1513
|
+
- `outerDiameter` (number, optional) - Overrides `rimWidth` if provided
|
|
1514
|
+
- `center` (boolean, optional) - Default: `true`
|
|
1515
|
+
- `segmentsPerTooth` (number, optional) - Default: `10`
|
|
1516
|
+
|
|
1517
|
+
```javascript
|
|
1518
|
+
const ring = lib.ringGear({
|
|
1519
|
+
module: 1.25,
|
|
1520
|
+
teeth: 58,
|
|
1521
|
+
faceWidth: 10,
|
|
1522
|
+
rimWidth: 4,
|
|
1523
|
+
});
|
|
1524
|
+
```
|
|
1525
|
+
|
|
1526
|
+
### `lib.rackGear(options)`
|
|
1527
|
+
Linear rack gear with pressure-angle flanks.
|
|
1528
|
+
|
|
1529
|
+
**Options:**
|
|
1530
|
+
- `module` (number)
|
|
1531
|
+
- `teeth` (integer, >= 2)
|
|
1532
|
+
- `faceWidth` (number)
|
|
1533
|
+
- `pressureAngleDeg` (number, optional) - Default: `20`
|
|
1534
|
+
- `backlash` (number, optional) - Default: `0`
|
|
1535
|
+
- `clearance` (number, optional) - Default: `0.25 * module`
|
|
1536
|
+
- `addendum` (number, optional) - Default: `module`
|
|
1537
|
+
- `dedendum` (number, optional) - Default: `addendum + clearance`
|
|
1538
|
+
- `baseHeight` (number, optional) - Rack body thickness behind root line
|
|
1539
|
+
- `center` (boolean, optional) - Default: `true`
|
|
1540
|
+
|
|
1541
|
+
```javascript
|
|
1542
|
+
const rack = lib.rackGear({
|
|
1543
|
+
module: 1.25,
|
|
1544
|
+
teeth: 24,
|
|
1545
|
+
faceWidth: 8,
|
|
1546
|
+
baseHeight: 3.5,
|
|
1547
|
+
});
|
|
1548
|
+
```
|
|
1549
|
+
|
|
1550
|
+
### `lib.bevelGear(options)`
|
|
1551
|
+
Conical bevel gear generated from a tapered involute extrusion.
|
|
1552
|
+
|
|
1553
|
+
**Options:**
|
|
1554
|
+
- `module` (number)
|
|
1555
|
+
- `teeth` (integer, >= 6)
|
|
1556
|
+
- `faceWidth` (number)
|
|
1557
|
+
- `pressureAngleDeg` (number, optional) - Default: `20`
|
|
1558
|
+
- `backlash` (number, optional) - Default: `0`
|
|
1559
|
+
- `clearance` (number, optional) - Default: `0.25 * module`
|
|
1560
|
+
- `addendum` (number, optional) - Default: `module`
|
|
1561
|
+
- `dedendum` (number, optional) - Default: `addendum + clearance`
|
|
1562
|
+
- `boreDiameter` (number, optional)
|
|
1563
|
+
- pitch cone setup (choose one):
|
|
1564
|
+
- `pitchAngleDeg` (number, optional), or
|
|
1565
|
+
- `mateTeeth` (+ optional `shaftAngleDeg`, default `90`) for auto pitch-angle derivation
|
|
1566
|
+
- `center` (boolean, optional) - Default: `true`
|
|
1567
|
+
- `segmentsPerTooth` (number, optional) - Default: `10`
|
|
1568
|
+
|
|
1569
|
+
```javascript
|
|
1570
|
+
const bevelPinion = lib.bevelGear({
|
|
1571
|
+
module: 1.5,
|
|
1572
|
+
teeth: 18,
|
|
1573
|
+
faceWidth: 10,
|
|
1574
|
+
mateTeeth: 36,
|
|
1575
|
+
shaftAngleDeg: 90,
|
|
1576
|
+
boreDiameter: 5,
|
|
1577
|
+
});
|
|
1578
|
+
```
|
|
1579
|
+
|
|
1580
|
+
### `lib.gearPair(options)`
|
|
1581
|
+
Build or validate a spur-gear pair and return ratio/backlash/mesh diagnostics.
|
|
1582
|
+
|
|
1583
|
+
Accepts either:
|
|
1584
|
+
- spur gear shapes produced by `lib.spurGear(...)`, or
|
|
1585
|
+
- analytical specs (`{ module, teeth, ... }`) for each member
|
|
1586
|
+
|
|
1587
|
+
**Options:**
|
|
1588
|
+
- `pinion` (`Shape | GearPairSpec`) - input gear
|
|
1589
|
+
- `gear` (`Shape | GearPairSpec`) - mating output gear
|
|
1590
|
+
- `backlash` (number, optional) - target backlash used for auto center distance
|
|
1591
|
+
- `centerDistance` (number, optional) - override center distance directly
|
|
1592
|
+
- `place` (boolean, optional) - auto-place `gear` at +X center distance. Default: `true`
|
|
1593
|
+
- `phaseDeg` (number, optional) - additional Z rotation applied to placed gear before translation
|
|
1594
|
+
|
|
1595
|
+
**Returns:** `GearPairResult` with:
|
|
1596
|
+
- `pinion`, `gear` (shapes)
|
|
1597
|
+
- `jointRatio`, `speedReduction`
|
|
1598
|
+
- `centerDistance`, `centerDistanceNominal`, `backlash`
|
|
1599
|
+
- `pressureAngleDeg`, `workingPressureAngleDeg`, `contactRatio`
|
|
1600
|
+
- `diagnostics[]` and `status` (`ok | warn | error`)
|
|
1601
|
+
|
|
1602
|
+
```javascript
|
|
1603
|
+
const pair = lib.gearPair({
|
|
1604
|
+
pinion: { module: 1.25, teeth: 14, faceWidth: 8, boreDiameter: 5 },
|
|
1605
|
+
gear: { module: 1.25, teeth: 42, faceWidth: 8, boreDiameter: 8 },
|
|
1606
|
+
backlash: 0.05,
|
|
1607
|
+
});
|
|
1608
|
+
|
|
1609
|
+
if (pair.status !== 'ok') {
|
|
1610
|
+
console.warn(pair.diagnostics);
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
return [pair.pinion, pair.gear];
|
|
1614
|
+
```
|
|
1615
|
+
|
|
1616
|
+
### `lib.bevelGearPair(options)`
|
|
1617
|
+
Build or validate a bevel-gear pair and return ratio diagnostics plus recommended joint placement vectors.
|
|
1618
|
+
|
|
1619
|
+
Accepts either:
|
|
1620
|
+
- bevel gear shapes produced by `lib.bevelGear(...)`, or
|
|
1621
|
+
- analytical specs (`{ module, teeth, ... }`) for each member
|
|
1622
|
+
|
|
1623
|
+
**Options:**
|
|
1624
|
+
- `pinion` (`Shape | GearPairSpec`)
|
|
1625
|
+
- `gear` (`Shape | GearPairSpec`)
|
|
1626
|
+
- `shaftAngleDeg` (number, optional) - Default: `90`
|
|
1627
|
+
- `backlash` (number, optional)
|
|
1628
|
+
- `place` (boolean, optional) - Apply recommended transforms to returned shapes. Default: `true`
|
|
1629
|
+
- `phaseDeg` (number, optional) - Extra phase on the placed driven bevel gear
|
|
1630
|
+
|
|
1631
|
+
**Returns:** `BevelGearPairResult` with:
|
|
1632
|
+
- `pinion`, `gear` (shapes)
|
|
1633
|
+
- `jointRatio`, `speedReduction`
|
|
1634
|
+
- `shaftAngleDeg`, `pinionPitchAngleDeg`, `gearPitchAngleDeg`, `coneDistance`
|
|
1635
|
+
- `pinionAxis`, `gearAxis`, `pinionCenter`, `gearCenter` (joint setup helpers)
|
|
1636
|
+
- `diagnostics[]` and `status` (`ok | warn | error`)
|
|
1637
|
+
|
|
1638
|
+
```javascript
|
|
1639
|
+
const bevelPair = lib.bevelGearPair({
|
|
1640
|
+
pinion: { module: 1.5, teeth: 18, faceWidth: 10 },
|
|
1641
|
+
gear: { module: 1.5, teeth: 36, faceWidth: 9 },
|
|
1642
|
+
shaftAngleDeg: 90,
|
|
1643
|
+
});
|
|
1644
|
+
```
|
|
1645
|
+
|
|
1646
|
+
### `lib.faceGearPair(options)`
|
|
1647
|
+
Build or validate a perpendicular pair between a face gear and a vertical spur gear.
|
|
1648
|
+
|
|
1649
|
+
Accepts either:
|
|
1650
|
+
- face gear shapes produced by `lib.faceGear(...)` or face-gear specs (`{ module, teeth, ... }`)
|
|
1651
|
+
- vertical spur shapes produced by `lib.spurGear(...)` or spur specs (`{ module, teeth, ... }`)
|
|
1652
|
+
|
|
1653
|
+
**Options:**
|
|
1654
|
+
- `face` (`Shape | FaceGearSpec`) - face/crown gear member
|
|
1655
|
+
- `vertical` (`Shape | GearPairSpec`) - mating perpendicular spur gear
|
|
1656
|
+
- `backlash` (number, optional) - target radial backlash for auto center distance
|
|
1657
|
+
- `centerDistance` (number, optional) - override center distance directly
|
|
1658
|
+
- `meshPlaneZ` (number, optional) - override the Z plane where the vertical gear is placed
|
|
1659
|
+
- `place` (boolean, optional) - auto-place `vertical`. Default: `true`
|
|
1660
|
+
- `phaseDeg` (number, optional) - phase rotation applied before perpendicular placement
|
|
1661
|
+
|
|
1662
|
+
**Returns:** `FaceGearPairResult` with:
|
|
1663
|
+
- `face`, `vertical` (shapes)
|
|
1664
|
+
- `jointRatio`, `speedReduction`
|
|
1665
|
+
- `centerDistance`, `centerDistanceNominal`, `backlash`
|
|
1666
|
+
- `meshPlaneZ`, `radialOverlap`
|
|
1667
|
+
- `diagnostics[]` and `status` (`ok | warn | error`)
|
|
1668
|
+
|
|
1669
|
+
```javascript
|
|
1670
|
+
const pair = lib.faceGearPair({
|
|
1671
|
+
face: { module: 1.25, teeth: 36, faceWidth: 8, toothHeight: 1.2, side: 'top' },
|
|
1672
|
+
vertical: { module: 1.25, teeth: 12, faceWidth: 8 },
|
|
1673
|
+
backlash: 0.05,
|
|
1674
|
+
});
|
|
1675
|
+
|
|
1676
|
+
if (pair.status !== 'ok') {
|
|
1677
|
+
console.warn(pair.diagnostics);
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
return [pair.face, pair.vertical];
|
|
1681
|
+
```
|
|
1682
|
+
|
|
1683
|
+
`lib.sideGearPair(...)` is kept as a compatibility alias.
|
|
1684
|
+
|
|
1685
|
+
### `lib.tSlotProfile(options?)`
|
|
1686
|
+
Build a 2D T-slot cross-section sketch.
|
|
1687
|
+
|
|
1688
|
+
This is a generic, tunable T-slot generator.
|
|
1689
|
+
|
|
1690
|
+
**Options:**
|
|
1691
|
+
- `size` (number) - Outer profile size. Default: `20`
|
|
1692
|
+
- `slotWidth` (number) - Slot mouth width. Default: `6`
|
|
1693
|
+
- `slotInnerWidth` (number) - Wider interior slot cavity width. Default: `10.4`
|
|
1694
|
+
- `slotDepth` (number) - Slot depth from outer face. Default: `6`
|
|
1695
|
+
- `slotNeckDepth` (number) - Narrow mouth depth before widening. Default: `1.6`
|
|
1696
|
+
- `wall` (number) - Outer shell thickness. Default: `1.4`
|
|
1697
|
+
- `web` (number) - Central cross-web thickness. Default: `2.1`
|
|
1698
|
+
- `centerBossDia` (number) - Center boss diameter. Default: `8.2`
|
|
1699
|
+
- `centerBoreDia` (number) - Center bore diameter. Default: `4.2`
|
|
1700
|
+
- `outerCornerRadius` (number) - Outer corner radius. Default: `1`
|
|
1701
|
+
- `segments` (number) - Circle smoothness for 2D bores/bosses. Default: `36`
|
|
1702
|
+
|
|
1703
|
+
**Returns:** `Sketch`
|
|
1704
|
+
|
|
1705
|
+
```javascript
|
|
1706
|
+
const profile = lib.tSlotProfile();
|
|
1707
|
+
return profile; // 2D drawing-ready cross-section
|
|
1708
|
+
```
|
|
1709
|
+
|
|
1710
|
+
### `lib.tSlotExtrusion(length, options?)`
|
|
1711
|
+
Build a 3D extrusion from `lib.tSlotProfile(...)`.
|
|
1712
|
+
|
|
1713
|
+
**Parameters:**
|
|
1714
|
+
- `length` (number) - Extrusion length along Z
|
|
1715
|
+
- `options` - Same options as `lib.tSlotProfile(...)` plus:
|
|
1716
|
+
- `center` (boolean) - Center the length around Z=0. Default: `false`
|
|
1717
|
+
|
|
1718
|
+
**Returns:** `Shape`
|
|
1719
|
+
|
|
1720
|
+
```javascript
|
|
1721
|
+
const rail = lib.tSlotExtrusion(300, { center: true });
|
|
1722
|
+
```
|
|
1723
|
+
|
|
1724
|
+
### `lib.profile2020BSlot6Profile(options?)`
|
|
1725
|
+
Profile-accurate 2D helper for a 20x20 B-type slot 6 section.
|
|
1726
|
+
|
|
1727
|
+
Defaults target common B-type 20x20 conventions:
|
|
1728
|
+
- slot width `6.0`
|
|
1729
|
+
- slot depth `5.5`
|
|
1730
|
+
- center bore `5.5`
|
|
1731
|
+
- center boss `8.4`
|
|
1732
|
+
- diagonal web width `4.4`
|
|
1733
|
+
- no edge pocket holes (only central bore is cut)
|
|
1734
|
+
|
|
1735
|
+
**Options:**
|
|
1736
|
+
- `slotWidth` (number) - Default: `6.0`
|
|
1737
|
+
- `slotInnerWidth` (number) - Default: `8.2`
|
|
1738
|
+
- `slotDepth` (number) - Default: `5.5`
|
|
1739
|
+
- `slotNeckDepth` (number) - Default: `1.8`
|
|
1740
|
+
- `centerBoreDia` (number) - Default: `5.5` (set `0` to disable)
|
|
1741
|
+
- `centerBossDia` (number) - Default: `8.4`
|
|
1742
|
+
- `diagonalWebWidth` (number) - Default: `4.4`
|
|
1743
|
+
- `outerCornerRadius` (number) - Default: `1.0`
|
|
1744
|
+
- `segments` (number) - Default: `40`
|
|
1745
|
+
|
|
1746
|
+
```javascript
|
|
1747
|
+
const profile2d = lib.profile2020BSlot6Profile();
|
|
1748
|
+
```
|
|
1749
|
+
|
|
1750
|
+
### `lib.profile2020BSlot6(length, options?)`
|
|
1751
|
+
3D extrusion helper built from `lib.profile2020BSlot6Profile(...)`.
|
|
1752
|
+
|
|
1753
|
+
Use `options` to override supplier-specific tolerances.
|
|
1754
|
+
- Supports all profile options above
|
|
1755
|
+
- Plus `center` (boolean) to center length about Z=0
|
|
1756
|
+
|
|
1757
|
+
```javascript
|
|
1758
|
+
const profile = lib.profile2020BSlot6(500, { center: true });
|
|
1759
|
+
```
|
|
1760
|
+
|
|
1761
|
+
### Exploded-view helpers
|
|
1762
|
+
For scene-layout helpers such as `lib.explode(...)` and viewport explode overrides, see [../runtime/viewport.md](../runtime/viewport.md).
|
|
1763
|
+
|
|
1764
|
+
### `lib.pipeRoute(points, radius, options?)`
|
|
1765
|
+
Route a pipe through 3D waypoints with smooth torus bends at corners.
|
|
1766
|
+
|
|
1767
|
+
**Parameters:**
|
|
1768
|
+
- `points` ([number, number, number][]) - Array of 3D waypoints
|
|
1769
|
+
- `radius` (number) - Pipe outer radius
|
|
1770
|
+
- `options` (object, optional):
|
|
1771
|
+
- `bendRadius` (number) - Radius of bends at corners. Default: `radius * 4`
|
|
1772
|
+
- `wall` (number) - Wall thickness for hollow pipe. If omitted, pipe is solid
|
|
1773
|
+
- `segments` (number) - Circumferential segments. Default: 32
|
|
1774
|
+
|
|
1775
|
+
**Returns:** `Shape`
|
|
1776
|
+
|
|
1777
|
+
```javascript
|
|
1778
|
+
// Solid copper pipe with 90° bends
|
|
1779
|
+
const refrigPipe = lib.pipeRoute(
|
|
1780
|
+
[[0, 0, 0], [100, 0, 0], [100, 80, 0], [100, 80, 60]],
|
|
1781
|
+
4,
|
|
1782
|
+
{ bendRadius: 20 }
|
|
1783
|
+
).color('#B87333');
|
|
1784
|
+
|
|
1785
|
+
// Hollow drain pipe
|
|
1786
|
+
const drainPipe = lib.pipeRoute(
|
|
1787
|
+
[[0, 0, 20], [60, 0, 20], [60, 80, 20]],
|
|
1788
|
+
3,
|
|
1789
|
+
{ bendRadius: 15, wall: 1 }
|
|
1790
|
+
).color('#CCCCCC');
|
|
1791
|
+
```
|
|
1792
|
+
|
|
1793
|
+
### `lib.elbow(pipeRadius, bendRadius, angle?, options?)`
|
|
1794
|
+
Curved pipe section (torus arc) for connecting two pipe directions. Creates a bend at the origin.
|
|
1795
|
+
|
|
1796
|
+
**Parameters:**
|
|
1797
|
+
- `pipeRadius` (number) - Pipe outer radius
|
|
1798
|
+
- `bendRadius` (number) - Centerline bend radius
|
|
1799
|
+
- `angle` (number, optional) - Bend angle in degrees. Default: 90
|
|
1800
|
+
|
|
1801
|
+
**Options:**
|
|
1802
|
+
- `wall` (number) - Wall thickness for hollow pipe
|
|
1803
|
+
- `segments` (number) - Circumferential segments. Default: 32
|
|
1804
|
+
- `from` ([number, number, number]) - Incoming direction vector
|
|
1805
|
+
- `to` ([number, number, number]) - Outgoing direction vector (overrides angle)
|
|
1806
|
+
|
|
1807
|
+
**Alternative call:** `lib.elbow(pipeRadius, bendRadius, { from, to, wall, segments })`
|
|
1808
|
+
|
|
1809
|
+
```javascript
|
|
1810
|
+
// Simple 90° elbow
|
|
1811
|
+
const bend = lib.elbow(5, 20, 90);
|
|
1812
|
+
|
|
1813
|
+
// 45° hollow elbow
|
|
1814
|
+
const bend45 = lib.elbow(5, 20, 45, { wall: 1.5 });
|
|
1815
|
+
|
|
1816
|
+
// Direction-based: connect Z-up pipe to X-right pipe
|
|
1817
|
+
const bend = lib.elbow(5, 20, { from: [0, 0, 1], to: [1, 0, 0] });
|
|
1818
|
+
```
|
|
1819
|
+
|
|
1820
|
+
### `lib.thread(diameter, pitch, length, options?)`
|
|
1821
|
+
External thread (helical ridge) via twisted extrusion. Returns a threaded cylinder along +Z.
|
|
1822
|
+
|
|
1823
|
+
**Options:**
|
|
1824
|
+
- `depth` (number) - Thread depth. Default: `pitch * 0.35`
|
|
1825
|
+
- `segments` (number) - Circumferential segments. Default: 36
|
|
1826
|
+
|
|
1827
|
+
```javascript
|
|
1828
|
+
const m8thread = lib.thread(8, 1.25, 30);
|
|
1829
|
+
const smooth = lib.thread(8, 1.0, 30, { segments: 48 });
|
|
1830
|
+
```
|
|
1831
|
+
|
|
1832
|
+
### `lib.bolt(diameter, length, options?)`
|
|
1833
|
+
Hex bolt with real helical threads. Head at z=0, shaft extends along −Z.
|
|
1834
|
+
|
|
1835
|
+
**Options:**
|
|
1836
|
+
- `pitch` (number) - Thread pitch. Default: `diameter * 0.15`
|
|
1837
|
+
- `headHeight` (number) - Default: `diameter * 0.65`
|
|
1838
|
+
- `headAcrossFlats` (number) - Default: `diameter * 1.6`
|
|
1839
|
+
- `threadLength` (number) - Threaded portion. Default: full length
|
|
1840
|
+
- `segments` (number) - Circumferential segments. Default: 36
|
|
1841
|
+
|
|
1842
|
+
```javascript
|
|
1843
|
+
const m8bolt = lib.bolt(8, 30);
|
|
1844
|
+
const custom = lib.bolt(10, 40, { pitch: 1.5, headHeight: 7 });
|
|
1845
|
+
```
|
|
1846
|
+
|
|
1847
|
+
### `lib.nut(diameter, options?)`
|
|
1848
|
+
Hex nut with bore, centered at origin.
|
|
1849
|
+
|
|
1850
|
+
**Options:**
|
|
1851
|
+
- `pitch` (number) - Default: `diameter * 0.15`
|
|
1852
|
+
- `height` (number) - Default: `diameter * 0.8`
|
|
1853
|
+
- `acrossFlats` (number) - Default: `diameter * 1.6`
|
|
1854
|
+
- `segments` (number) - Circumferential segments. Default: 36
|
|
1855
|
+
|
|
1856
|
+
```javascript
|
|
1857
|
+
const m8nut = lib.nut(8);
|
|
1858
|
+
const m8nut2 = lib.nut(8, { height: 6.5, acrossFlats: 13 });
|
|
1859
|
+
```
|
|
1860
|
+
|
|
1861
|
+
## Query Methods
|
|
1862
|
+
|
|
1863
|
+
### 3D Shape Queries
|
|
1864
|
+
```javascript
|
|
1865
|
+
shape.volume() // Volume in mm³
|
|
1866
|
+
shape.surfaceArea() // Surface area in mm²
|
|
1867
|
+
shape.boundingBox() // { min: [x,y,z], max: [x,y,z] }
|
|
1868
|
+
shape.isEmpty() // true if no geometry
|
|
1869
|
+
shape.numTri() // Triangle count
|
|
1870
|
+
shape.minGap(other, 50) // Minimum distance to another shape (within search radius)
|
|
1871
|
+
shape.geometryInfo() // { backend, representation, fidelity, topology, sources }
|
|
1872
|
+
```
|
|
1873
|
+
|
|
1874
|
+
`geometryInfo()` is the current contract boundary for future hybrid kernels. Today most results are `manifold` + `mesh-solid`; `loft()` / `sweep()` report `sampled`, and tracked extrusions report `topology: 'synthetic'`. A future OCCT/BREP backend can change these values without forcing a language rewrite.
|
|
1875
|
+
|
|
1876
|
+
For the maintained exact STEP/BREP support matrix, see [../output/brep-export.md](../output/brep-export.md).
|
|
1877
|
+
|
|
1878
|
+
### 2D Sketch Queries
|
|
1879
|
+
```javascript
|
|
1880
|
+
sketch.area() // Area in mm²
|
|
1881
|
+
sketch.bounds() // { min: [x,y], max: [x,y] }
|
|
1882
|
+
sketch.isEmpty() // true if no area
|
|
1883
|
+
sketch.numVert() // Vertex count
|
|
1884
|
+
```
|
|
1885
|
+
|
|
1886
|
+
## Returning Multiple Objects
|
|
1887
|
+
|
|
1888
|
+
Scripts can return arrays to display multiple objects in the viewport:
|
|
1889
|
+
|
|
1890
|
+
```javascript
|
|
1891
|
+
// Simple array — auto-named "Object 1", "Object 2", etc.
|
|
1892
|
+
return [
|
|
1893
|
+
box(50, 50, 10),
|
|
1894
|
+
cylinder(20, 8).translate(25, 25, 10),
|
|
1895
|
+
];
|
|
1896
|
+
|
|
1897
|
+
// Named objects with colors
|
|
1898
|
+
return [
|
|
1899
|
+
{ name: "Base Plate", shape: box(100, 100, 5), color: "#888888" },
|
|
1900
|
+
{ name: "Column", shape: cylinder(50, 10).translate(50, 50, 5), color: "#4488cc" },
|
|
1901
|
+
{ name: "Profile", sketch: circle2d(20), color: "#ff6600" },
|
|
1902
|
+
];
|
|
1903
|
+
```
|
|
1904
|
+
|
|
1905
|
+
Each object gets its own visibility toggle, opacity slider, and color picker in the View Panel.
|
|
1906
|
+
|
|
1907
|
+
### Assembly Groups
|
|
1908
|
+
|
|
1909
|
+
For complex assemblies, use nested groups to organize related parts:
|
|
1910
|
+
|
|
1911
|
+
```javascript
|
|
1912
|
+
return [
|
|
1913
|
+
{ name: "Bed Assembly", group: [
|
|
1914
|
+
{ name: "Bed Plate", shape: bedPlate },
|
|
1915
|
+
{ name: "Glass Bed", shape: glass },
|
|
1916
|
+
{ name: "Heater", shape: heater },
|
|
1917
|
+
]},
|
|
1918
|
+
{ name: "Gantry", group: [
|
|
1919
|
+
{ name: "Left Rail", shape: leftRail },
|
|
1920
|
+
{ name: "Right Rail", shape: rightRail },
|
|
1921
|
+
{ name: "Cross Bar", shape: crossBar },
|
|
1922
|
+
]},
|
|
1923
|
+
];
|
|
1924
|
+
```
|
|
1925
|
+
|
|
1926
|
+
**Benefits:**
|
|
1927
|
+
- **Spatial analysis** skips intra-group collision checks (intentional overlaps)
|
|
1928
|
+
- **Group-level summary** reports relationships between assemblies
|
|
1929
|
+
- **Object listing** shows group tags: `Bed Plate [Bed Assembly]`
|
|
1930
|
+
- **Parameter validation** (`param-check` CLI) ignores collisions within groups
|
|
1931
|
+
|
|
1932
|
+
## Guides and Examples
|
|
1933
|
+
|
|
1934
|
+
See [../guides/modeling-recipes.md](../guides/modeling-recipes.md) for patterns, best practices, debugging, and sample snippets.
|
|
1935
|
+
|
|
1936
|
+
For runnable end-to-end models, read `examples/api/`.
|