forgecad 0.6.3 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -11
- package/dist/assets/{AdminPage-CeqCUUgu.js → AdminPage-DAu1C1ST.js} +250 -151
- package/dist/assets/{BlogPage-P_AJP0v9.js → BlogPage-CJEXL_zJ.js} +94 -70
- package/dist/assets/{DocsPage-CKRV2iq2.js → DocsPage-Gc_BCdqC.js} +269 -143
- package/dist/assets/EditorApp-D9bJvtf7.js +11338 -0
- package/dist/assets/{EditorApp-CnC2k4cW.css → EditorApp-DG1-oUSV.css} +459 -87
- package/dist/assets/{EmbedViewer-DBlzmQ5i.js → EmbedViewer-CEO8XbV8.js} +2 -4
- package/dist/assets/LandingPage-CdCuEOdC.js +451 -0
- package/dist/assets/PricingPage-BSrxu6d7.js +232 -0
- package/dist/assets/{SettingsPage-BqCh9JcC.js → SettingsPage-FUCSIRq6.js} +129 -5
- package/dist/assets/{evalWorker-Ql-aKwLA.js → evalWorker-KoR0SNKq.js} +6770 -2914
- package/dist/assets/{index-2hfs_ub0.css → index-CyVd1D4D.css} +227 -53
- package/dist/assets/{Viewport-CoB46f5R.js → index-wTEK39at.js} +31385 -6439
- package/dist/assets/{javascript-DCxGoE5Y.js → javascript-DAl8Gmyo.js} +1 -1
- package/dist/assets/{manifold-CqNMHHKO.js → manifold-B1sGWdYk.js} +4 -3
- package/dist/assets/{manifold-Cce9wRFz.js → manifold-D7o0N50J.js} +1 -1
- package/dist/assets/{manifold-D6BeHIOo.js → manifold-G5sBaXzi.js} +1 -1
- package/dist/assets/{reportWorker-sFEFonXf.js → reportWorker-DYcRHhv9.js} +6798 -3341
- package/dist/assets/{vendor-react-Dt7-aaJH.js → vendor-react-CG3i_wp0.js} +65 -8
- package/dist/docs-raw/generated/assembly.md +691 -112
- package/dist/docs-raw/generated/concepts.md +1225 -1400
- package/dist/docs-raw/generated/core.md +464 -1412
- package/dist/docs-raw/generated/curves.md +593 -117
- package/dist/docs-raw/generated/lib.md +38 -748
- package/dist/docs-raw/generated/output.md +139 -245
- package/dist/docs-raw/generated/sheet-metal.md +473 -21
- package/dist/docs-raw/generated/sketch.md +553 -349
- package/dist/docs-raw/generated/viewport.md +345 -303
- package/dist/docs-raw/generated/wood.md +104 -0
- package/dist/index.html +2 -2
- package/dist/sitemap.xml +6 -6
- package/dist-cli/chunk-PZ5AY32C.js +10 -0
- package/dist-cli/chunk-PZ5AY32C.js.map +1 -0
- package/dist-cli/forgecad.js +9435 -5407
- package/dist-cli/forgecad.js.map +1 -0
- package/dist-cli/solver-FV7TJZGI.js +365 -0
- package/dist-cli/solver-FV7TJZGI.js.map +1 -0
- package/dist-skill/CONTEXT.md +3186 -7145
- package/dist-skill/SKILL-dev.md +21 -63
- package/dist-skill/SKILL.md +12 -56
- package/dist-skill/docs/API/core/concepts.md +16 -98
- package/dist-skill/docs/CLI/export.md +91 -0
- package/dist-skill/docs/CLI/projects.md +107 -0
- package/dist-skill/docs/CLI/studio_publishing.md +52 -0
- package/dist-skill/docs/CLI/validation.md +66 -0
- package/dist-skill/docs/generated/assembly.md +691 -112
- package/dist-skill/docs/generated/core.md +464 -1412
- package/dist-skill/docs/generated/curves.md +593 -117
- package/dist-skill/docs/generated/lib.md +38 -748
- package/dist-skill/docs/generated/output.md +139 -245
- package/dist-skill/docs/generated/sheet-metal.md +473 -21
- package/dist-skill/docs/generated/sketch.md +553 -349
- package/dist-skill/docs/generated/viewport.md +345 -303
- package/dist-skill/docs/generated/wood.md +104 -0
- package/dist-skill/docs/guides/coordinate-system.md +11 -17
- package/dist-skill/docs/guides/geometry-conventions.md +13 -70
- package/dist-skill/docs/guides/modeling-recipes.md +22 -195
- package/dist-skill/docs/guides/positioning.md +88 -147
- package/dist-skill/docs-dev/API/core/concepts.md +51 -0
- package/dist-skill/docs-dev/API/core/sdf-advanced.md +92 -0
- package/dist-skill/docs-dev/API/core/sdf-primitives.md +58 -0
- package/dist-skill/docs-dev/API/core/sdf-workflow.md +42 -0
- package/dist-skill/docs-dev/CLI/export.md +91 -0
- package/dist-skill/docs-dev/CLI/projects.md +107 -0
- package/dist-skill/docs-dev/CLI/studio_publishing.md +52 -0
- package/dist-skill/docs-dev/CLI/validation.md +66 -0
- package/dist-skill/{docs → docs-dev}/blueprint-first.md +5 -0
- package/dist-skill/{docs → docs-dev}/coding-best-practices.md +6 -8
- package/dist-skill/{docs → docs-dev}/coding.md +1 -3
- package/dist-skill/docs-dev/generated/assembly.md +771 -0
- package/dist-skill/docs-dev/generated/core.md +775 -0
- package/dist-skill/docs-dev/generated/curves.md +688 -0
- package/dist-skill/docs-dev/generated/lib.md +50 -0
- package/dist-skill/docs-dev/generated/output.md +234 -0
- package/dist-skill/docs-dev/generated/sheet-metal.md +506 -0
- package/dist-skill/docs-dev/generated/sketch.md +801 -0
- package/dist-skill/docs-dev/generated/viewport.md +486 -0
- package/dist-skill/docs-dev/generated/wood.md +104 -0
- package/dist-skill/docs-dev/guides/coordinate-system.md +46 -0
- package/dist-skill/docs-dev/guides/geometry-conventions.md +52 -0
- package/dist-skill/docs-dev/guides/modeling-recipes.md +77 -0
- package/dist-skill/docs-dev/guides/positioning.md +151 -0
- package/dist-skill/{docs → docs-dev}/guides/skill-maintenance.md +21 -10
- package/dist-skill/{docs → docs-dev}/internals/compiler.md +5 -6
- package/dist-skill/{docs → docs-dev}/internals/constraint-solver-quality.md +0 -1
- package/dist-skill/{docs → docs-dev}/internals/constraint-solver.md +0 -1
- package/dist-skill/{docs → docs-dev}/internals/sketch-2d-pipeline.md +2 -3
- package/examples/api/attachTo-basics.forge.js +5 -5
- package/examples/api/boolean-operations.forge.js +3 -3
- package/examples/api/bounding-box-visualizer.forge.js +2 -2
- package/examples/api/clone-duplicate.forge.js +1 -1
- package/examples/api/colors-union-vs-array.forge.js +6 -6
- package/examples/api/connector-assembly.forge.js +4 -4
- package/examples/api/connector-basics.forge.js +2 -2
- package/examples/api/extrude-options.forge.js +4 -10
- package/examples/api/feature-created-faces.forge.js +6 -10
- package/examples/api/fillet-showcase.forge.js +1 -1
- package/examples/api/folded-service-panel-cover.forge.js +2 -2
- package/examples/api/group-test.forge.js +1 -1
- package/examples/api/group-vs-union.forge.js +1 -1
- package/examples/api/highlight-debug.forge.js +4 -0
- package/examples/api/js-module-pillars.js +1 -1
- package/examples/api/js-module-scene.js +2 -2
- package/examples/api/mesh-import-slats.forge.js +1 -1
- package/examples/api/pointAlong-orientation.forge.js +1 -1
- package/examples/api/profile-2020-b-slot6.forge.js +0 -1
- package/examples/api/route-perimeter-flange.forge.js +1 -1
- package/examples/api/sdf-rover-demo.forge.js +10 -10
- package/examples/api/sketch-on-face-demo.forge.js +2 -2
- package/examples/api/sketch-regions.forge.js +4 -4
- package/examples/api/transition-curves.forge.js +1 -1
- package/examples/api/variable-sweep-pure-sdf-test.forge.js +162 -0
- package/examples/api/variable-sweep-test.forge.js +2 -2
- package/examples/api/wood-joinery.forge.js +60 -0
- package/examples/compiler-corpus/enclosure-shell-cuts.forge.js +3 -3
- package/examples/compiler-corpus/fastener-plate-variants.forge.js +2 -2
- package/examples/experiments/drone-arm.forge.js +53 -0
- package/examples/furniture/adjustable-table.forge.js +2 -2
- package/examples/furniture/bathroom.forge.js +11 -11
- package/examples/furniture/chair.forge.js +1 -1
- package/examples/generative/crystal-growth.forge.js +2 -2
- package/examples/generative/frost-spires.forge.js +3 -3
- package/examples/generative/golden-spiral-tower.forge.js +3 -3
- package/examples/mechanical/3d-printer.forge.js +28 -28
- package/examples/mechanical/5-finger-robot-hand.forge.js +15 -15
- package/examples/mechanical/airplane-propeller.forge.js +2 -2
- package/examples/mechanical/fillet-enclosure.forge.js +1 -1
- package/examples/mechanical/headphone-hanger-v2.forge.js +2 -2
- package/examples/mechanical/robot_hand.forge.js +15 -15
- package/examples/mechanical/robot_hand_2.forge.js +9 -9
- package/examples/products/bottle.forge.js +1 -1
- package/examples/products/chess-set.forge.js +19 -19
- package/examples/products/classical-piano.forge.js +11 -11
- package/examples/products/clock.forge.js +12 -12
- package/examples/products/iphone.forge.js +8 -8
- package/examples/products/laptop.forge.js +15 -15
- package/examples/products/liquid-soap-dispenser.forge.js +18 -18
- package/examples/products/origami-fish.forge.js +8 -6
- package/examples/products/spiderman-cake.forge.js +4 -4
- package/examples/toolbox/bolted-joint.forge.js +2 -2
- package/package.json +7 -4
- package/dist/assets/EditorApp-B-vQvgam.js +0 -9888
- package/dist/assets/LandingPage-C5n9hDXI.js +0 -322
- package/dist/assets/PublishedModelPage-Dt7PCVBj.js +0 -146
- package/dist/assets/__vite-browser-external-CURh0WXD.js +0 -8
- package/dist/assets/deserializeRunResult-BLAFoiE0.js +0 -19365
- package/dist/assets/index-1CYp3zUp.js +0 -1455
- package/dist/docs-raw/CLI.md +0 -865
- package/dist-skill/docs/API/API.md +0 -1666
- package/dist-skill/docs/API/README.md +0 -37
- package/dist-skill/docs/API/assembly/assembly.md +0 -617
- package/dist-skill/docs/API/core/edge-queries.md +0 -130
- package/dist-skill/docs/API/core/parameters.md +0 -122
- package/dist-skill/docs/API/core/reserved-terms.md +0 -137
- package/dist-skill/docs/API/core/sdf.md +0 -326
- package/dist-skill/docs/API/core/skill-cli.md +0 -194
- package/dist-skill/docs/API/core/skill-guide.md +0 -205
- package/dist-skill/docs/API/core/specs.md +0 -186
- package/dist-skill/docs/API/core/topology.md +0 -372
- package/dist-skill/docs/API/entities.md +0 -268
- package/dist-skill/docs/API/output/bom.md +0 -58
- package/dist-skill/docs/API/output/brep-export.md +0 -87
- package/dist-skill/docs/API/output/dimensions.md +0 -67
- package/dist-skill/docs/API/output/export.md +0 -110
- package/dist-skill/docs/API/output/gcode.md +0 -195
- package/dist-skill/docs/API/runtime/viewport.md +0 -420
- package/dist-skill/docs/API/sheet-metal/sheet-metal.md +0 -185
- package/dist-skill/docs/API/sketch/anchor.md +0 -37
- package/dist-skill/docs/API/sketch/booleans.md +0 -91
- package/dist-skill/docs/API/sketch/core.md +0 -73
- package/dist-skill/docs/API/sketch/extrude.md +0 -62
- package/dist-skill/docs/API/sketch/on-face.md +0 -104
- package/dist-skill/docs/API/sketch/operations.md +0 -78
- package/dist-skill/docs/API/sketch/path.md +0 -75
- package/dist-skill/docs/API/sketch/primitives.md +0 -146
- package/dist-skill/docs/API/sketch/regions.md +0 -80
- package/dist-skill/docs/API/sketch/text.md +0 -108
- package/dist-skill/docs/API/sketch/transforms.md +0 -65
- package/dist-skill/docs/API/toolbox/fasteners.md +0 -129
- package/dist-skill/docs/CLI.md +0 -865
- package/dist-skill/docs/INDEX.md +0 -94
- package/dist-skill/docs/RELEASING.md +0 -55
- package/dist-skill/docs/cli-monetization.md +0 -111
- package/dist-skill/docs/deployment.md +0 -281
- package/dist-skill/docs/generated/concepts.md +0 -2112
- package/dist-skill/docs/internals/shape-from-slices.md +0 -152
- package/dist-skill/docs/platform/admin.md +0 -45
- package/dist-skill/docs/platform/architecture.md +0 -79
- package/dist-skill/docs/platform/auth.md +0 -110
- package/dist-skill/docs/platform/email.md +0 -67
- package/dist-skill/docs/platform/projects.md +0 -111
- package/dist-skill/docs/platform/sharing.md +0 -90
- package/dist-skill/docs/runbook.md +0 -345
|
@@ -6,10 +6,10 @@ Every public API function belongs to one of 16 fundamental concepts. This docume
|
|
|
6
6
|
|
|
7
7
|
## Concepts
|
|
8
8
|
|
|
9
|
-
- **[C1: Primitive Construction](#c1-primitive-construction)** — Create geometry from parameters — no input geometry required. *(
|
|
10
|
-
- **[C2: Boolean Combination](#c2-boolean-combination)** — Combine same-dimension geometry using CSG set operations. *(
|
|
9
|
+
- **[C1: Primitive Construction](#c1-primitive-construction)** — Create geometry from parameters — no input geometry required. *(20 functions)*
|
|
10
|
+
- **[C2: Boolean Combination](#c2-boolean-combination)** — Combine same-dimension geometry using CSG set operations. *(6 functions)*
|
|
11
11
|
- **[C3: Rigid Transform](#c3-rigid-transform)** — Reposition or reorient geometry without changing its shape. *(3 functions)*
|
|
12
|
-
- **[C4: Dimensional Promotion](#c4-dimensional-promotion)** — Convert a 2D profile into a 3D solid (extrude, revolve, loft, sweep). *(
|
|
12
|
+
- **[C4: Dimensional Promotion](#c4-dimensional-promotion)** — Convert a 2D profile into a 3D solid (extrude, revolve, loft, sweep). *(10 functions)*
|
|
13
13
|
- **[C5: Topology Query](#c5-topology-query)** — Select or inspect named faces and edges on a shape. *(3 functions)*
|
|
14
14
|
- **[C6: Edge Feature](#c6-edge-feature)** — Modify edges of a solid — fillets, chamfers, draft, offset. *(7 functions)*
|
|
15
15
|
- **[C7: Pattern Replication](#c7-pattern-replication)** — Duplicate geometry in regular arrangements (linear, circular, mirror). *(6 functions)*
|
|
@@ -19,7 +19,7 @@ Every public API function belongs to one of 16 fundamental concepts. This docume
|
|
|
19
19
|
- **[C11: Parameterization & UI](#c11-parameterization-ui)** — Declare user-facing controls that drive model geometry. *(6 functions)*
|
|
20
20
|
- **[C12: Dimensional Demotion](#c12-dimensional-demotion)** — Extract 2D geometry from a 3D solid (section, projection). *(3 functions)*
|
|
21
21
|
- **[C13: Export & Output](#c13-export-output)** — Convert geometry to external formats (STL, 3MF, SVG, DXF, G-code, PDF). *(5 functions)*
|
|
22
|
-
- **[C14: Visual & Debugging](#c14-visual-debugging)** — Control viewport appearance and debugging aids. *(
|
|
22
|
+
- **[C14: Visual & Debugging](#c14-visual-debugging)** — Control viewport appearance and debugging aids. *(6 functions)*
|
|
23
23
|
- **[C15: Import & Composition](#c15-import-composition)** — Bring external geometry or other ForgeCAD modules into the current script. *(1 functions)*
|
|
24
24
|
- **[C16: Part Library](#c16-part-library)** — Pre-built parametric parts accessible via `lib.*`. *(0 functions)*
|
|
25
25
|
|
|
@@ -29,223 +29,287 @@ Every public API function belongs to one of 16 fundamental concepts. This docume
|
|
|
29
29
|
|
|
30
30
|
Create geometry from parameters — no input geometry required.
|
|
31
31
|
|
|
32
|
-
#### `
|
|
32
|
+
#### `circle2d()` — Create a 2D circle centered at the origin.
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
arcBridgeBetweenRects(rectA: RectAreaArg, rectB: RectAreaArg, segments?: number): Shape
|
|
36
|
-
```
|
|
34
|
+
**Details**
|
|
37
35
|
|
|
38
|
-
|
|
36
|
+
Omit `segments` for a smooth (auto-tessellated) circle. Pass an integer to get a regular polygon approximation — e.g. `6` for a hexagon, `8` for an octagon.
|
|
39
37
|
|
|
40
|
-
|
|
38
|
+
**Example**
|
|
41
39
|
|
|
42
40
|
```ts
|
|
43
|
-
circle2d(
|
|
41
|
+
circle2d(25).extrude(10); // smooth cylinder
|
|
42
|
+
circle2d(25, 6).extrude(10); // hexagonal prism
|
|
44
43
|
```
|
|
45
44
|
|
|
46
|
-
|
|
45
|
+
`circle2d(radius: number, segments?: number): Sketch`
|
|
47
46
|
|
|
48
|
-
#### `ellipse()`
|
|
47
|
+
#### `ellipse()` — Create a 2D ellipse centered at the origin.
|
|
48
|
+
|
|
49
|
+
**Example**
|
|
49
50
|
|
|
50
51
|
```ts
|
|
51
|
-
ellipse(
|
|
52
|
+
ellipse(30, 15).extrude(5);
|
|
53
|
+
ellipse(30, 15, 32).extrude(5); // lower-resolution approximation
|
|
52
54
|
```
|
|
53
55
|
|
|
54
|
-
|
|
56
|
+
`ellipse(rx: number, ry: number, segments?: number): Sketch`
|
|
55
57
|
|
|
56
|
-
#### `loadFont()`
|
|
58
|
+
#### `loadFont()` — Pre-load and cache a font for use with `text2d()`.
|
|
57
59
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
**Details**
|
|
61
|
+
|
|
62
|
+
Fonts are cached by their source string (or `cacheKey` for `ArrayBuffer` sources), so repeated calls with the same path are free. Pre-loading is useful when you call `text2d()` many times with the same font — it avoids repeated disk reads.
|
|
61
63
|
|
|
62
|
-
|
|
64
|
+
Built-in font names that work everywhere (browser + CLI): - `'sans-serif'` or `'inter'` — bundled Inter Regular
|
|
63
65
|
|
|
64
|
-
|
|
66
|
+
**Example**
|
|
65
67
|
|
|
66
68
|
```ts
|
|
67
|
-
|
|
69
|
+
const font = loadFont('/path/to/Arial Bold.ttf');
|
|
70
|
+
text2d('Title', { size: 12, font }).extrude(1.5);
|
|
71
|
+
text2d('Subtitle', { size: 8, font }).extrude(1);
|
|
68
72
|
```
|
|
69
73
|
|
|
70
|
-
|
|
74
|
+
`loadFont(source: string | ArrayBuffer, cacheKey?: string): opentype$1.Font`
|
|
71
75
|
|
|
72
|
-
#### `
|
|
76
|
+
#### `ngon()` — Create a regular polygon inscribed in a circle of the given radius.
|
|
73
77
|
|
|
74
|
-
|
|
75
|
-
path(): PathBuilder
|
|
76
|
-
```
|
|
78
|
+
**Details**
|
|
77
79
|
|
|
78
|
-
|
|
80
|
+
`radius` is the center-to-vertex (circumradius) distance. Use `sides` of `3` for a triangle, `6` for a hexagon, etc. The first vertex is at the top (−90° from +X).
|
|
79
81
|
|
|
80
|
-
|
|
82
|
+
**Example**
|
|
81
83
|
|
|
82
84
|
```ts
|
|
83
|
-
|
|
85
|
+
ngon(6, 20).extrude(10); // hexagonal prism, circumradius 20
|
|
84
86
|
```
|
|
85
87
|
|
|
86
|
-
|
|
88
|
+
`ngon(sides: number, radius: number): Sketch`
|
|
87
89
|
|
|
88
|
-
#### `
|
|
90
|
+
#### `path()` — Create a new `PathBuilder` for tracing a 2D outline point by point.
|
|
89
91
|
|
|
90
|
-
|
|
91
|
-
polygon(points: ([ number, number ] | Point2D)[]): Sketch
|
|
92
|
-
```
|
|
92
|
+
**Details**
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
`PathBuilder` is a fluent API for constructing 2D profiles using a mix of line segments, arcs, bezier curves, and splines. Always start with `.moveTo(x, y)` to set the starting point. Call `.close()` to get a filled `Sketch`, or `.stroke(width)` to thicken an open polyline into a solid profile.
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
Edge labels can be assigned with `.label('name')` after any segment — they propagate through extrusion, revolve, loft, and sweep into named faces on the resulting `Shape`.
|
|
97
|
+
|
|
98
|
+
**Example**
|
|
97
99
|
|
|
98
100
|
```ts
|
|
99
|
-
|
|
101
|
+
// Closed triangle
|
|
102
|
+
const triangle = path().moveTo(0, 0).lineH(50).lineV(30).close();
|
|
103
|
+
|
|
104
|
+
// L-shaped bracket as a stroke
|
|
105
|
+
const bracket = path().moveTo(0, 0).lineH(50).lineV(-70).lineAngled(20, 235).stroke(4);
|
|
106
|
+
|
|
107
|
+
// Labeled edges for downstream face references
|
|
108
|
+
const slot = path()
|
|
109
|
+
.moveTo(0, 0)
|
|
110
|
+
.lineTo(30, 0).label('bottom')
|
|
111
|
+
.lineTo(30, 10)
|
|
112
|
+
.lineTo(0, 10).label('top')
|
|
113
|
+
.close();
|
|
100
114
|
```
|
|
101
115
|
|
|
102
|
-
|
|
116
|
+
`path(): PathBuilder`
|
|
103
117
|
|
|
104
|
-
|
|
118
|
+
#### `polygon()` — Create a 2D polygon from an array of `[x, y]` points or `Point2D` objects.
|
|
105
119
|
|
|
106
|
-
|
|
107
|
-
interface PolygonVerticesOptions {
|
|
108
|
-
/** Angle of the first vertex in degrees (default: 90 = top). */
|
|
109
|
-
startDeg?: number;
|
|
110
|
-
/** Center X coordinate (default: 0). */
|
|
111
|
-
centerX?: number;
|
|
112
|
-
/** Center Y coordinate (default: 0). */
|
|
113
|
-
centerY?: number;
|
|
114
|
-
}
|
|
115
|
-
```
|
|
120
|
+
**Details**
|
|
116
121
|
|
|
117
|
-
|
|
122
|
+
Winding order is normalized automatically — clockwise (CW) input is silently reversed to CCW before being passed to the geometry kernel.
|
|
118
123
|
|
|
119
|
-
|
|
124
|
+
**Example**
|
|
120
125
|
|
|
121
126
|
```ts
|
|
122
|
-
|
|
123
|
-
x: number;
|
|
124
|
-
y: number;
|
|
125
|
-
}
|
|
127
|
+
polygon([[0, 0], [50, 0], [25, 40]]).extrude(5); // triangle
|
|
126
128
|
```
|
|
127
129
|
|
|
128
|
-
|
|
130
|
+
`polygon(points: ([ number, number ] | Point2D)[]): Sketch`
|
|
129
131
|
|
|
130
|
-
#### `
|
|
132
|
+
#### `polygonVertices()` — Compute the vertex positions of a regular polygon.
|
|
131
133
|
|
|
132
|
-
|
|
133
|
-
rect(width: number, height: number, center?: boolean): Sketch
|
|
134
|
-
```
|
|
134
|
+
Default orientation places the first vertex at the top (90 degrees), matching the convention used by `ngon()`.
|
|
135
135
|
|
|
136
|
-
|
|
136
|
+
Eliminates manual Math.sqrt(3) for triangles, pentagon vertex math, etc:
|
|
137
137
|
|
|
138
|
-
|
|
138
|
+
```js
|
|
139
|
+
// Before — manual equilateral triangle
|
|
140
|
+
const v1 = [center.x - r/2, center.y + r * Math.sqrt(3)/2];
|
|
141
|
+
const v2 = [center.x - r/2, center.y - r * Math.sqrt(3)/2];
|
|
142
|
+
const v3 = [center.x + r, center.y];
|
|
139
143
|
|
|
140
|
-
|
|
141
|
-
|
|
144
|
+
// After — declarative
|
|
145
|
+
const [v1, v2, v3] = polygonVertices(3, r);
|
|
142
146
|
```
|
|
143
147
|
|
|
144
|
-
|
|
148
|
+
`polygonVertices(sides: number, radius: number, options?: PolygonVerticesOptions): LayoutPoint[]`
|
|
145
149
|
|
|
146
|
-
|
|
150
|
+
**`PolygonVerticesOptions`**
|
|
151
|
+
- `startDeg?: number` — Angle of the first vertex in degrees (default: 90 = top).
|
|
152
|
+
- `centerX?: number` — Center X coordinate (default: 0).
|
|
153
|
+
- `centerY?: number` — Center Y coordinate (default: 0).
|
|
147
154
|
|
|
148
|
-
|
|
149
|
-
routePerimeter(steps: PerimeterStep[]): Sketch
|
|
150
|
-
```
|
|
155
|
+
`LayoutPoint`: `{ x: number, y: number }`
|
|
151
156
|
|
|
152
|
-
|
|
157
|
+
#### `rect()` — Create a 2D rectangle centered at the origin.
|
|
153
158
|
|
|
154
|
-
|
|
159
|
+
**Example**
|
|
155
160
|
|
|
156
161
|
```ts
|
|
157
|
-
|
|
162
|
+
rect(40, 20).extrude(5);
|
|
158
163
|
```
|
|
159
164
|
|
|
160
|
-
|
|
165
|
+
`rect(width: number, height: number): Sketch`
|
|
161
166
|
|
|
162
|
-
#### `slot()
|
|
167
|
+
#### `arcSlot()` — Create an arc-shaped slot (banana / annular sector) centered at the origin.
|
|
163
168
|
|
|
164
|
-
|
|
165
|
-
slot(length: number, width: number): Sketch
|
|
166
|
-
```
|
|
169
|
+
**Details**
|
|
167
170
|
|
|
168
|
-
|
|
171
|
+
The slot is symmetric about the +X axis. The two ends are closed with semicircular caps. `pitchRadius` is the distance from the origin to the centerline of the slot, and `thickness` is the radial width of the slot.
|
|
169
172
|
|
|
170
|
-
|
|
173
|
+
**Example**
|
|
171
174
|
|
|
172
175
|
```ts
|
|
173
|
-
|
|
176
|
+
arcSlot(135, 74, 40).extrude(5); // pitch R135, 74° sweep, 40mm wide
|
|
174
177
|
```
|
|
175
178
|
|
|
176
|
-
|
|
179
|
+
`arcSlot(pitchRadius: number, sweepDeg: number, thickness: number): Sketch`
|
|
177
180
|
|
|
178
|
-
|
|
181
|
+
#### `roundedRect()` — Create a 2D rectangle with rounded corners, centered at the origin.
|
|
179
182
|
|
|
180
|
-
|
|
181
|
-
interface Spline2DOptions {
|
|
182
|
-
/** Closed loop (default true). */
|
|
183
|
-
closed?: boolean;
|
|
184
|
-
/** Catmull-Rom tension in [0, 1]. 0 = very round, 1 = linear-ish. Default 0.5. */
|
|
185
|
-
tension?: number;
|
|
186
|
-
/** Samples per segment (minimum 3). Default 16. */
|
|
187
|
-
samplesPerSegment?: number;
|
|
188
|
-
/** For open splines, provide stroke width to return a solid Sketch. If omitted for open splines, an error is thrown. */
|
|
189
|
-
strokeWidth?: number;
|
|
190
|
-
/** Stroke join for open splines. Default 'Round'. */
|
|
191
|
-
join?: "Round" | "Square";
|
|
192
|
-
}
|
|
193
|
-
```
|
|
183
|
+
**Details**
|
|
194
184
|
|
|
195
|
-
|
|
185
|
+
The corner radius is automatically clamped to `min(width/2, height/2)` so it can never exceed the shape dimensions.
|
|
196
186
|
|
|
197
|
-
|
|
187
|
+
**Example**
|
|
198
188
|
|
|
199
189
|
```ts
|
|
200
|
-
|
|
190
|
+
roundedRect(60, 30, 5).extrude(3);
|
|
201
191
|
```
|
|
202
192
|
|
|
203
|
-
|
|
193
|
+
`roundedRect(width: number, height: number, radius: number): Sketch`
|
|
194
|
+
|
|
195
|
+
#### `slot()` — Create a slot (oblong / stadium shape) — a rectangle with semicircular ends, centered at the origin.
|
|
204
196
|
|
|
205
|
-
|
|
197
|
+
**Example**
|
|
206
198
|
|
|
207
199
|
```ts
|
|
208
|
-
|
|
200
|
+
slot(40, 10).extrude(3); // 40mm long, 10mm wide slot
|
|
209
201
|
```
|
|
210
202
|
|
|
211
|
-
|
|
203
|
+
`slot(length: number, width: number): Sketch`
|
|
204
|
+
|
|
205
|
+
#### `spline2d()` — Build a smooth Catmull-Rom spline sketch from 2D control points.
|
|
212
206
|
|
|
213
|
-
|
|
207
|
+
A closed spline (default) returns a filled profile. An open spline requires a strokeWidth option to produce a solid sketch. Use tension (0..1, default 0.5) to control curve tightness.
|
|
208
|
+
|
|
209
|
+
`spline2d(points: Vec2[], options?: Spline2DOptions): Sketch`
|
|
210
|
+
|
|
211
|
+
**`Spline2DOptions`**
|
|
212
|
+
|
|
213
|
+
| Option | Type | Description |
|
|
214
|
+
|--------|------|-------------|
|
|
215
|
+
| `closed?` | `boolean` | Closed loop (default true). |
|
|
216
|
+
| `tension?` | `number` | Catmull-Rom tension in [0, 1]. 0 = very round, 1 = linear-ish. Default 0.5. |
|
|
217
|
+
| `samplesPerSegment?` | `number` | Samples per segment (minimum 3). Default 16. |
|
|
218
|
+
| `strokeWidth?` | `number` | For open splines, provide stroke width to return a solid Sketch. If omitted for open splines, an error is thrown. |
|
|
219
|
+
| `join?` | `"Round" | "Square"` | Stroke join for open splines. Default 'Round'. |
|
|
220
|
+
|
|
221
|
+
#### `star()` — Create a star shape with alternating outer and inner radii.
|
|
222
|
+
|
|
223
|
+
**Example**
|
|
214
224
|
|
|
215
225
|
```ts
|
|
216
|
-
|
|
226
|
+
star(5, 30, 12).extrude(4); // five-pointed star
|
|
217
227
|
```
|
|
218
228
|
|
|
219
|
-
|
|
229
|
+
`star(points: number, outerR: number, innerR: number): Sketch`
|
|
230
|
+
|
|
231
|
+
#### `stroke()` — Create a stroked polyline sketch from an array of 2D points.
|
|
232
|
+
|
|
233
|
+
`stroke(points: [ number, number ][], width: number, join?: "Round" | "Square"): Sketch`
|
|
234
|
+
|
|
235
|
+
#### `text2d()` — Build a filled 2D Sketch from a text string.
|
|
220
236
|
|
|
221
|
-
|
|
237
|
+
**Details**
|
|
238
|
+
|
|
239
|
+
The Sketch origin is at the left end of the text baseline by default. Use `align` and `baseline` options to adjust placement. Text is rendered using the bundled Inter font by default, or any TTF/OTF/WOFF font you provide.
|
|
240
|
+
|
|
241
|
+
Alignment reference table:
|
|
242
|
+
|
|
243
|
+
| `align` | `baseline` | Origin | |------------|--------------|-------------------------------------| | `'left'` | `'baseline'` | Bottom-left of first char (default) | | `'center'` | `'center'` | Dead center of text block | | `'right'` | `'top'` | Top-right corner |
|
|
244
|
+
|
|
245
|
+
**Example**
|
|
222
246
|
|
|
223
247
|
```ts
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
248
|
+
// Extruded nameplate
|
|
249
|
+
text2d('FORGE CAD', { size: 8 }).extrude(1.2);
|
|
250
|
+
|
|
251
|
+
// Centered label on the XY plane
|
|
252
|
+
text2d('V 2.0', { size: 6, align: 'center', baseline: 'center' });
|
|
253
|
+
|
|
254
|
+
// Engraved text cut into the top face of a box
|
|
255
|
+
const label = text2d('REV A', { size: 5, align: 'center', baseline: 'center' });
|
|
256
|
+
plate.subtract(label.onFace(plate, 'top', { protrude: -0.5 }).extrude(1));
|
|
257
|
+
|
|
258
|
+
// Custom TTF font
|
|
259
|
+
text2d('Hello', { size: 10, font: '/path/to/Arial.ttf' }).extrude(1);
|
|
260
|
+
|
|
261
|
+
// Pre-loaded font for reuse
|
|
262
|
+
const font = loadFont('/path/to/Arial Bold.ttf');
|
|
263
|
+
text2d('Title', { size: 12, font }).extrude(1.5);
|
|
238
264
|
```
|
|
239
265
|
|
|
240
|
-
|
|
266
|
+
`text2d(content: string, options?: TextOptions): Sketch`
|
|
267
|
+
|
|
268
|
+
**`TextOptions`**
|
|
269
|
+
|
|
270
|
+
| Option | Type | Description |
|
|
271
|
+
|--------|------|-------------|
|
|
272
|
+
| `size?` | `number` | Cap height of the text in model units. All other dimensions (stroke weight, spacing) scale proportionally. |
|
|
273
|
+
| `letterSpacing?` | `number` | Extra space between characters in model units. Negative values tighten the tracking. |
|
|
274
|
+
| `align?` | `"left" | "center" | "right"` | Horizontal alignment relative to x = 0. - `'left'` — left edge at x = 0 (default) - `'center'` — centred on x = 0 - `'right'` — right edge at x = 0 |
|
|
275
|
+
| `baseline?` | `"baseline" | "center" | "top"` | Vertical alignment relative to y = 0. - `'baseline'` — y = 0 is the text baseline (bottom of capital letters) - `'center'` — y = 0 is the vertical midpoint of the cap height - `'top'` — y = 0 is the top of capital letters |
|
|
276
|
+
| `font?` | `string | opentype$1.Font` | Font to use for text rendering. - `'sans-serif'` or `'inter'` — bundled Inter font (works everywhere, including browser) - **file path** — path to a TTF, OTF, or WOFF font file (CLI/Node only) - **Font object** — a previously loaded opentype.js Font (from `loadFont()`) - **omitted** — uses the bundled Inter font (same as `'sans-serif'`) text2d('Hello World', { size: 10 }) // default Inter text2d('Custom Font', { size: 10, font: '/path/to/font.ttf' }) |
|
|
277
|
+
| `flattenTolerance?` | `number` | Bezier flattening tolerance in model units. Smaller = more polygon segments = smoother curves. |
|
|
278
|
+
|
|
279
|
+
#### `textWidth()` — Measure the rendered advance width of a string without creating any geometry.
|
|
280
|
+
|
|
281
|
+
**Details**
|
|
282
|
+
|
|
283
|
+
Uses the same font metrics as `text2d()`. Useful for computing layout dimensions before building the actual sketch — e.g. sizing a plate to fit a label.
|
|
241
284
|
|
|
242
|
-
|
|
285
|
+
**Example**
|
|
243
286
|
|
|
244
287
|
```ts
|
|
245
|
-
textWidth(
|
|
288
|
+
const w = textWidth('SERIAL: 001', { size: 6 });
|
|
289
|
+
const plate = box(w + 10, 12, 2);
|
|
246
290
|
```
|
|
247
291
|
|
|
248
|
-
|
|
292
|
+
`textWidth(content: string, options?: Pick<TextOptions, "size" | "letterSpacing" | "font">): number`
|
|
293
|
+
|
|
294
|
+
#### `box()` — Create a rectangular box. Centered on XY, base at Z=0.
|
|
295
|
+
|
|
296
|
+
For named faces, build from a labeled sketch: `rect(x, y).labelEdges('s', 'e', 'n', 'w').extrude(z, { labels: { start: 'bottom', end: 'top' } })`.
|
|
297
|
+
|
|
298
|
+
`box$1(x: number, y: number, z: number): Shape`
|
|
299
|
+
|
|
300
|
+
#### `cylinder()` — Create a cylinder or cone with named faces and edges. Centered on XY, base at Z=0.
|
|
301
|
+
|
|
302
|
+
When radiusTop differs from radius, creates a tapered cone. Use the segments parameter to create regular prisms (e.g. 6 for a hexagonal prism). Returns a Shape with faces: top, bottom, side; and edges: top-rim, bottom-rim.
|
|
303
|
+
|
|
304
|
+
`cylinder$1(height: number, radius: number, radiusTop?: number, segments?: number): Shape`
|
|
305
|
+
|
|
306
|
+
#### `sphere()` — Create a sphere centered at the origin. Use segments for lower-poly approximations.
|
|
307
|
+
|
|
308
|
+
`sphere$1(radius: number, segments?: number): Shape`
|
|
309
|
+
|
|
310
|
+
#### `torus()` — Create a torus (donut shape) lying in the XY plane. Centered on all axes (origin is the ring center).
|
|
311
|
+
|
|
312
|
+
`torus$1(majorRadius: number, minorRadius: number, segments?: number): Shape`
|
|
249
313
|
|
|
250
314
|
---
|
|
251
315
|
|
|
@@ -253,399 +317,305 @@ Returns the rendered width of a string in model units (same options as text2d).
|
|
|
253
317
|
|
|
254
318
|
Combine same-dimension geometry using CSG set operations.
|
|
255
319
|
|
|
256
|
-
#### `difference2d()`
|
|
320
|
+
#### `difference2d()` — Subtract one or more 2D sketches from a base sketch.
|
|
257
321
|
|
|
258
|
-
|
|
259
|
-
difference2d(...inputs: SketchOperandInput[]): Sketch
|
|
260
|
-
```
|
|
322
|
+
**Details**
|
|
261
323
|
|
|
262
|
-
|
|
324
|
+
The first sketch is the base; all subsequent sketches are subtracted from it. Accepts individual sketches or arrays: `difference2d(base, c1, c2)` or `difference2d([base, c1, c2])`. Uses Manifold's batch operation — faster than chaining `.subtract()` one by one.
|
|
263
325
|
|
|
264
|
-
|
|
326
|
+
**Example**
|
|
265
327
|
|
|
266
328
|
```ts
|
|
267
|
-
|
|
329
|
+
const donut = difference2d(circle2d(50), circle2d(30));
|
|
268
330
|
```
|
|
269
331
|
|
|
270
|
-
|
|
332
|
+
`difference2d(...inputs: SketchOperandInput[]): Sketch`
|
|
333
|
+
|
|
334
|
+
#### `intersection2d()` — Keep only the area where all input sketches overlap (intersection boolean).
|
|
335
|
+
|
|
336
|
+
**Details**
|
|
337
|
+
|
|
338
|
+
Accepts individual sketches or arrays: `intersection2d(a, b)` or `intersection2d([a, b, c])`. Uses Manifold's batch operation — faster than chaining `.intersect()` one by one.
|
|
271
339
|
|
|
272
|
-
|
|
340
|
+
**Example**
|
|
273
341
|
|
|
274
342
|
```ts
|
|
275
|
-
|
|
343
|
+
const lens = intersection2d(circle2d(30).translate(-10, 0), circle2d(30).translate(10, 0));
|
|
276
344
|
```
|
|
277
345
|
|
|
278
|
-
|
|
346
|
+
`intersection2d(...inputs: SketchOperandInput[]): Sketch`
|
|
279
347
|
|
|
280
|
-
|
|
348
|
+
#### `union2d()` — Combine 2D sketches into a single profile using an additive boolean union.
|
|
281
349
|
|
|
282
|
-
|
|
350
|
+
**Details**
|
|
283
351
|
|
|
284
|
-
|
|
352
|
+
Accepts individual sketches or arrays: `union2d(a, b, c)` or `union2d([a, b, c])`. Uses Manifold's batch operation — faster than chaining `.add()` one by one when combining many sketches.
|
|
285
353
|
|
|
286
|
-
|
|
354
|
+
**Example**
|
|
287
355
|
|
|
288
356
|
```ts
|
|
289
|
-
|
|
357
|
+
const cross = union2d(rect(60, 10), rect(10, 60));
|
|
290
358
|
```
|
|
291
359
|
|
|
292
|
-
|
|
360
|
+
`union2d(...inputs: SketchOperandInput[]): Sketch`
|
|
293
361
|
|
|
294
|
-
#### `
|
|
362
|
+
#### `union()` — Combine shapes into a single solid (additive boolean).
|
|
295
363
|
|
|
296
|
-
|
|
297
|
-
radians(rad: number): number
|
|
298
|
-
```
|
|
364
|
+
Accepts individual shapes, or an array of shapes. The first operand's color is preserved in the result.
|
|
299
365
|
|
|
300
|
-
|
|
366
|
+
`union(...inputs: ShapeOperandInput[]): Shape`
|
|
301
367
|
|
|
302
|
-
#### `
|
|
368
|
+
#### `difference()` — Subtract shapes from a base shape (subtractive boolean).
|
|
303
369
|
|
|
304
|
-
|
|
305
|
-
composeChain(...steps: TransformInput[]): Transform
|
|
306
|
-
```
|
|
370
|
+
The first shape is the base; all subsequent shapes are subtracted from it. Accepts individual shapes, or an array of shapes.
|
|
307
371
|
|
|
308
|
-
|
|
372
|
+
`difference(...inputs: ShapeOperandInput[]): Shape`
|
|
309
373
|
|
|
310
|
-
|
|
374
|
+
#### `intersection()` — Keep only the overlapping volume of the input shapes (intersection boolean).
|
|
311
375
|
|
|
312
|
-
|
|
376
|
+
Requires at least two shapes. Accepts individual shapes, or an array.
|
|
313
377
|
|
|
314
|
-
|
|
378
|
+
`intersection(...inputs: ShapeOperandInput[]): Shape`
|
|
315
379
|
|
|
316
|
-
|
|
380
|
+
---
|
|
317
381
|
|
|
318
|
-
|
|
319
|
-
connectEdges(edgeA: EdgeSegment, edgeB: EdgeSegment, options?: ConnectEdgesOptions): Shape
|
|
320
|
-
```
|
|
382
|
+
## C3: Rigid Transform
|
|
321
383
|
|
|
322
|
-
|
|
384
|
+
Reposition or reorient geometry without changing its shape.
|
|
323
385
|
|
|
324
|
-
|
|
325
|
-
interface EdgeSegment {
|
|
326
|
-
/** Stable index within the extraction (deterministic for a given mesh). */
|
|
327
|
-
index: number;
|
|
328
|
-
start: Vec3;
|
|
329
|
-
end: Vec3;
|
|
330
|
-
midpoint: Vec3;
|
|
331
|
-
/** Normalized direction from start → end. */
|
|
332
|
-
direction: Vec3;
|
|
333
|
-
length: number;
|
|
334
|
-
/** Dihedral angle in degrees (0 = coplanar, 180 = knife edge). */
|
|
335
|
-
dihedralAngle: number;
|
|
336
|
-
/** true = outside corner (convex), false = inside corner (concave). */
|
|
337
|
-
convex: boolean;
|
|
338
|
-
/** Normal of first adjacent face. */
|
|
339
|
-
normalA: Vec3;
|
|
340
|
-
/** Normal of second adjacent face (same as normalA for boundary edges). */
|
|
341
|
-
normalB: Vec3;
|
|
342
|
-
/** true if this is a boundary (unmatched) edge — unusual for closed solids. */
|
|
343
|
-
boundary: boolean;
|
|
344
|
-
}
|
|
345
|
-
```
|
|
386
|
+
#### `degrees()` — Identity function that returns degrees unchanged.
|
|
346
387
|
|
|
347
|
-
|
|
388
|
+
Use for clarity when the unit of an angle value would otherwise be ambiguous — e.g. `param("Angle", degrees(45))`.
|
|
348
389
|
|
|
349
|
-
|
|
390
|
+
`degrees(deg: number): number`
|
|
350
391
|
|
|
351
|
-
|
|
352
|
-
interface TransitionCurveOptions {
|
|
353
|
-
/** Weight for the start edge. Controls tangent magnitude at the start. - 1.0 (default): balanced transition - > 1.0: curve follows start edge longer before turning - < 1.0: curve turns sooner at the start */
|
|
354
|
-
weightA?: number;
|
|
355
|
-
/** Weight for the end edge. Controls tangent magnitude at the end. - 1.0 (default): balanced transition - > 1.0: curve follows end edge longer before turning - < 1.0: curve turns sooner at the end */
|
|
356
|
-
weightB?: number;
|
|
357
|
-
/** Number of sample points for the output polyline. Default 64. Higher values give smoother curves at the cost of more geometry. */
|
|
358
|
-
samples?: number;
|
|
359
|
-
}
|
|
360
|
-
```
|
|
392
|
+
#### `radians()` — Convert radians to degrees.
|
|
361
393
|
|
|
362
|
-
|
|
394
|
+
ForgeCAD's public API uses degrees throughout. Use this when you have a radian value (e.g. from `Math.atan2`) that you want to express in degrees.
|
|
363
395
|
|
|
364
|
-
|
|
396
|
+
`radians(rad: number): number`
|
|
365
397
|
|
|
366
|
-
|
|
367
|
-
interface TransitionSurfaceOptions extends TransitionCurveOptions {
|
|
368
|
-
/** Cross-section profile to sweep along the transition curve. If omitted, a circular profile with `radius` is used. */
|
|
369
|
-
profile?: Sketch;
|
|
370
|
-
/** Radius of circular cross-section (used when `profile` is omitted). Default: 5% of chord length. */
|
|
371
|
-
radius?: number;
|
|
372
|
-
width: number;
|
|
373
|
-
height: number;
|
|
374
|
-
/** Preferred up vector for the sweep frame. Default: auto-detected. */
|
|
375
|
-
up?: Vec3$7;
|
|
376
|
-
/** Edge length for level-set meshing. Smaller = finer. */
|
|
377
|
-
edgeLength?: number;
|
|
378
|
-
/** Extra bounds padding for level-set meshing. */
|
|
379
|
-
boundsPadding?: number;
|
|
380
|
-
}
|
|
381
|
-
```
|
|
398
|
+
#### `composeChain()` — Compose transforms in chain order. Equivalent to Transform.identity().mul(a).mul(b).mul(c)...
|
|
382
399
|
|
|
383
|
-
|
|
400
|
+
`composeChain(...steps: TransformInput[]): Transform`
|
|
384
401
|
|
|
385
|
-
|
|
402
|
+
---
|
|
386
403
|
|
|
387
|
-
|
|
388
|
-
interface ConnectEdgesOptions extends TransitionSurfaceOptions {
|
|
389
|
-
/** Which end of edge A to connect. Default: 'start'. */
|
|
390
|
-
endA?: EdgeEnd;
|
|
391
|
-
/** Which end of edge B to connect. Default: 'start'. */
|
|
392
|
-
endB?: EdgeEnd;
|
|
393
|
-
/** Tangent mode for edge A. Default: 'along'. */
|
|
394
|
-
tangentModeA?: TangentMode;
|
|
395
|
-
/** Tangent mode for edge B. Default: 'along'. */
|
|
396
|
-
tangentModeB?: TangentMode;
|
|
397
|
-
/** Explicit tangent for edge A. */
|
|
398
|
-
tangentA?: Vec3$7;
|
|
399
|
-
/** Explicit tangent for edge B. */
|
|
400
|
-
tangentB?: Vec3$7;
|
|
401
|
-
/** Flip tangent A. */
|
|
402
|
-
flipA?: boolean;
|
|
403
|
-
/** Flip tangent B. */
|
|
404
|
-
flipB?: boolean;
|
|
405
|
-
}
|
|
406
|
-
```
|
|
404
|
+
## C4: Dimensional Promotion
|
|
407
405
|
|
|
408
|
-
|
|
406
|
+
Convert a 2D profile into a 3D solid (extrude, revolve, loft, sweep).
|
|
409
407
|
|
|
410
|
-
#### `
|
|
408
|
+
#### `connectEdges()` — Create a transition surface or solid bridge between two edge segments.
|
|
411
409
|
|
|
412
|
-
|
|
413
|
-
hermiteTransition(a: EdgeEndpoint, b: EdgeEndpoint): HermiteCurve3D
|
|
414
|
-
```
|
|
410
|
+
Tangents can be inferred from neighboring geometry or supplied explicitly through `options`. This is useful for loft-like blends where you want a direct connection between two edge spans.
|
|
415
411
|
|
|
416
|
-
|
|
412
|
+
`connectEdges(edgeA: EdgeSegment, edgeB: EdgeSegment, options?: ConnectEdgesOptions): Shape`
|
|
417
413
|
|
|
418
|
-
|
|
414
|
+
**`EdgeSegment`**
|
|
419
415
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
}
|
|
431
|
-
```
|
|
416
|
+
| Option | Type | Description |
|
|
417
|
+
|--------|------|-------------|
|
|
418
|
+
| `index` | `number` | Stable index within the extraction (deterministic for a given mesh). |
|
|
419
|
+
| `direction` | `Vec3` | Normalized direction from start → end. |
|
|
420
|
+
| `dihedralAngle` | `number` | Dihedral angle in degrees (0 = coplanar, 180 = knife edge). |
|
|
421
|
+
| `convex` | `boolean` | true = outside corner (convex), false = inside corner (concave). |
|
|
422
|
+
| `normalA` | `Vec3` | Normal of first adjacent face. |
|
|
423
|
+
| `normalB` | `Vec3` | Normal of second adjacent face (same as normalA for boundary edges). |
|
|
424
|
+
| `boundary` | `boolean` | true if this is a boundary (unmatched) edge — unusual for closed solids. |
|
|
425
|
+
| `start`, `end`, `midpoint`, `length` | | — |
|
|
432
426
|
|
|
433
|
-
|
|
427
|
+
**`TransitionCurveOptions`**
|
|
428
|
+
- `weightA?: number` — Weight for the start edge. Controls tangent magnitude at the start. - 1.0 (default): balanced transition - > 1.0: curve follows start edge longer before turning - < 1.0: curve turns sooner at the start
|
|
429
|
+
- `weightB?: number` — Weight for the end edge. Controls tangent magnitude at the end. - 1.0 (default): balanced transition - > 1.0: curve follows end edge longer before turning - < 1.0: curve turns sooner at the end
|
|
430
|
+
- `samples?: number` — Number of sample points for the output polyline. Default 64. Higher values give smoother curves at the cost of more geometry.
|
|
434
431
|
|
|
435
|
-
|
|
432
|
+
**`TransitionSurfaceOptions`** extends TransitionCurveOptions
|
|
436
433
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
434
|
+
| Option | Type | Description |
|
|
435
|
+
|--------|------|-------------|
|
|
436
|
+
| `profile?` | `Sketch` | Cross-section profile to sweep along the transition curve. If omitted, a circular profile with `radius` is used. |
|
|
437
|
+
| `radius?` | `number` | Radius of circular cross-section (used when `profile` is omitted). Default: 5% of chord length. |
|
|
438
|
+
| `up?` | `Vec3$6` | Preferred up vector for the sweep frame. Default: auto-detected. |
|
|
439
|
+
| `edgeLength?` | `number` | Edge length for level-set meshing. Smaller = finer. |
|
|
440
|
+
| `boundsPadding?` | `number` | Extra bounds padding for level-set meshing. |
|
|
441
|
+
| `width`, `height` | | — |
|
|
440
442
|
|
|
441
|
-
|
|
443
|
+
**`ConnectEdgesOptions`** extends TransitionSurfaceOptions
|
|
442
444
|
|
|
443
|
-
|
|
445
|
+
| Option | Type | Description |
|
|
446
|
+
|--------|------|-------------|
|
|
447
|
+
| `endA?` | `EdgeEnd` | Which end of edge A to connect. Default: 'start'. |
|
|
448
|
+
| `endB?` | `EdgeEnd` | Which end of edge B to connect. Default: 'start'. |
|
|
449
|
+
| `tangentModeA?` | `TangentMode` | Tangent mode for edge A. Default: 'along'. |
|
|
450
|
+
| `tangentModeB?` | `TangentMode` | Tangent mode for edge B. Default: 'along'. |
|
|
451
|
+
| `tangentA?` | `Vec3$6` | Explicit tangent for edge A. |
|
|
452
|
+
| `tangentB?` | `Vec3$6` | Explicit tangent for edge B. |
|
|
453
|
+
| `flipA?` | `boolean` | Flip tangent A. |
|
|
454
|
+
| `flipB?` | `boolean` | Flip tangent B. |
|
|
444
455
|
|
|
445
|
-
|
|
446
|
-
interface QuinticHermiteCurveEndpoint {
|
|
447
|
-
/** Position */
|
|
448
|
-
point: Vec3$5;
|
|
449
|
-
/** Tangent direction (will be normalized internally) */
|
|
450
|
-
tangent: Vec3$5;
|
|
451
|
-
/** Second derivative / curvature vector. Default [0, 0, 0]. */
|
|
452
|
-
curvature?: Vec3$5;
|
|
453
|
-
/** Weight: scales tangent magnitude relative to chord length. Default 1.0. */
|
|
454
|
-
weight?: number;
|
|
455
|
-
}
|
|
456
|
-
```
|
|
456
|
+
#### `hermiteTransitionG2()` — Create a quintic Hermite transition curve between two edge endpoints (G2 continuity).
|
|
457
457
|
|
|
458
|
-
|
|
458
|
+
The curve starts at `a.point` tangent to `a.tangent` with curvature `a.curvature`, and ends at `b.point` tangent to `b.tangent` with curvature `b.curvature`, with smooth G2-continuous interpolation matching position, tangent, and curvature.
|
|
459
459
|
|
|
460
|
-
|
|
460
|
+
`hermiteTransitionG2(a: QuinticHermiteCurveEndpoint, b: QuinticHermiteCurveEndpoint): QuinticHermiteCurve3D`
|
|
461
461
|
|
|
462
|
-
|
|
463
|
-
loft(profiles: Sketch[], heights: number[], options?: LoftOptions): Shape
|
|
464
|
-
```
|
|
462
|
+
**`QuinticHermiteCurveEndpoint`**
|
|
465
463
|
|
|
466
|
-
|
|
464
|
+
| Option | Type | Description |
|
|
465
|
+
|--------|------|-------------|
|
|
466
|
+
| `point` | `Vec3$4` | Position |
|
|
467
|
+
| `tangent` | `Vec3$4` | Tangent direction (will be normalized internally) |
|
|
468
|
+
| `curvature?` | `Vec3$4` | Second derivative / curvature vector. Default [0, 0, 0]. |
|
|
469
|
+
| `weight?` | `number` | Weight: scales tangent magnitude relative to chord length. Default 1.0. |
|
|
467
470
|
|
|
468
|
-
|
|
471
|
+
#### `loft()` — Loft between multiple sketches along Z stations.
|
|
469
472
|
|
|
470
|
-
|
|
471
|
-
interface LoftOptions {
|
|
472
|
-
/** Marching-grid edge length for level-set meshing. Smaller = finer. */
|
|
473
|
-
edgeLength?: number;
|
|
474
|
-
/** Optional extra bounds padding. */
|
|
475
|
-
boundsPadding?: number;
|
|
476
|
-
}
|
|
477
|
-
```
|
|
473
|
+
Profiles can differ in topology and vertex count: interpolation is done on signed-distance fields and meshed with level-set extraction. Heights must be strictly increasing. Compatible loft stacks can export through the OCCT exact route.
|
|
478
474
|
|
|
479
|
-
|
|
475
|
+
Performance note: loft is significantly heavier than primitive/extrude/revolve. If the part is axis-symmetric (bottles, vases, knobs), prefer revolve().
|
|
480
476
|
|
|
481
|
-
|
|
477
|
+
`loft(profiles: Sketch[], heights: number[], options?: LoftOptions): Shape`
|
|
482
478
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
479
|
+
**`LoftOptions`**
|
|
480
|
+
- `edgeLength?: number` — Marching-grid edge length for level-set meshing. Smaller = finer.
|
|
481
|
+
- `boundsPadding?: number` — Optional extra bounds padding.
|
|
486
482
|
|
|
487
|
-
Loft between multiple profiles positioned along an arbitrary 3D spine curve.
|
|
483
|
+
#### `loftAlongSpine()` — Loft between multiple profiles positioned along an arbitrary 3D spine curve.
|
|
488
484
|
|
|
489
|
-
|
|
485
|
+
Unlike loft() which only supports Z heights, loftAlongSpine() places each profile at a position along a 3D spine, oriented perpendicular to the spine tangent. This enables lofting along curved paths — e.g., a wing root-to-tip transition that follows a swept-back leading edge.
|
|
490
486
|
|
|
491
|
-
|
|
492
|
-
interface LoftAlongSpineOptions {
|
|
493
|
-
/** Number of samples when spine is a Curve3D. Default 48. */
|
|
494
|
-
samples?: number;
|
|
495
|
-
/** Marching-grid edge length for level-set meshing. Smaller = finer. */
|
|
496
|
-
edgeLength?: number;
|
|
497
|
-
/** Optional extra bounds padding. */
|
|
498
|
-
boundsPadding?: number;
|
|
499
|
-
/** Preferred "up" vector for local profile frame. Auto fallback is used near parallel segments. */
|
|
500
|
-
up?: Vec3$4;
|
|
501
|
-
}
|
|
502
|
-
```
|
|
487
|
+
The tValues array specifies where each profile sits along the spine (0 = start, 1 = end). Must have the same length as profiles and be in [0, 1].
|
|
503
488
|
|
|
504
|
-
|
|
489
|
+
Internally uses variableSweep infrastructure with SDF interpolation.
|
|
505
490
|
|
|
506
|
-
|
|
491
|
+
Performance note: uses level-set meshing, heavier than simple loft().
|
|
507
492
|
|
|
508
|
-
|
|
509
|
-
spline3d(points: Vec3$4[], options?: Spline3DOptions): Curve3D
|
|
510
|
-
```
|
|
493
|
+
`loftAlongSpine(profiles: Sketch[], spine: Curve3D | Vec3$3[], tValues: number[], options?: LoftAlongSpineOptions): Shape`
|
|
511
494
|
|
|
512
|
-
|
|
495
|
+
**`LoftAlongSpineOptions`**
|
|
513
496
|
|
|
514
|
-
|
|
497
|
+
| Option | Type | Description |
|
|
498
|
+
|--------|------|-------------|
|
|
499
|
+
| `samples?` | `number` | Number of samples when spine is a Curve3D. Default 48. |
|
|
500
|
+
| `edgeLength?` | `number` | Marching-grid edge length for level-set meshing. Smaller = finer. |
|
|
501
|
+
| `boundsPadding?` | `number` | Optional extra bounds padding. |
|
|
502
|
+
| `up?` | `Vec3$3` | Preferred "up" vector for local profile frame. Auto fallback is used near parallel segments. |
|
|
515
503
|
|
|
516
|
-
|
|
517
|
-
interface Spline3DOptions {
|
|
518
|
-
/** Closed loop (default false). */
|
|
519
|
-
closed?: boolean;
|
|
520
|
-
/** Catmull-Rom tension in [0, 1]. 0 = very round, 1 = linear-ish. Default 0.5. */
|
|
521
|
-
tension?: number;
|
|
522
|
-
}
|
|
523
|
-
```
|
|
504
|
+
#### `spline3d()` — Create a reusable 3D spline curve object (Catmull-Rom).
|
|
524
505
|
|
|
525
|
-
|
|
506
|
+
The returned Curve3D provides sample(), pointAt(t), tangentAt(t), and length() for downstream use in sweep() or manual path operations.
|
|
526
507
|
|
|
527
|
-
|
|
508
|
+
`spline3d(points: Vec3$3[], options?: Spline3DOptions): Curve3D`
|
|
528
509
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
510
|
+
**`Spline3DOptions`**
|
|
511
|
+
- `closed?: boolean` — Closed loop (default false).
|
|
512
|
+
- `tension?: number` — Catmull-Rom tension in [0, 1]. 0 = very round, 1 = linear-ish. Default 0.5.
|
|
532
513
|
|
|
533
|
-
Create a smooth surface patch from 4 boundary curves (Coons patch).
|
|
514
|
+
#### `surfacePatch()` — Create a smooth surface patch from 4 boundary curves (Coons patch).
|
|
534
515
|
|
|
535
|
-
|
|
516
|
+
The four curves form the boundary of a quadrilateral patch: - bottom: u=0..1 at v=0 (from corner00 to corner10) - top: u=0..1 at v=1 (from corner01 to corner11) - left: v=0..1 at u=0 (from corner00 to corner01) - right: v=0..1 at u=1 (from corner10 to corner11)
|
|
536
517
|
|
|
537
|
-
|
|
538
|
-
interface SurfacePatchOptions {
|
|
539
|
-
/** Number of samples along each direction. Default 24. */
|
|
540
|
-
resolution?: number;
|
|
541
|
-
/** Thickness of the generated solid. Default 0.5. */
|
|
542
|
-
thickness?: number;
|
|
543
|
-
}
|
|
544
|
-
```
|
|
518
|
+
The interior is filled using bilinear Coons patch interpolation: P(u,v) = Lc(u,v) + Ld(u,v) - B(u,v)
|
|
545
519
|
|
|
546
|
-
|
|
520
|
+
The result is a thin solid created by offsetting the surface mesh along its normals by the specified thickness.
|
|
547
521
|
|
|
548
|
-
|
|
522
|
+
Note: curves should meet at corners. Small gaps are tolerated.
|
|
549
523
|
|
|
550
|
-
|
|
551
|
-
sweep(profile: Sketch, path: Curve3D | Vec3$4[], options?: SweepOptions): Shape
|
|
552
|
-
```
|
|
524
|
+
`surfacePatch(curves: { ... }, options?: SurfacePatchOptions): Shape`
|
|
553
525
|
|
|
554
|
-
|
|
526
|
+
**`SurfacePatchOptions`**
|
|
527
|
+
- `resolution?: number` — Number of samples along each direction. Default 24.
|
|
528
|
+
- `thickness?: number` — Thickness of the generated solid. Default 0.5.
|
|
555
529
|
|
|
556
|
-
|
|
530
|
+
#### `sweep()` — Sweep a 2D profile along a 3D path to create a solid.
|
|
557
531
|
|
|
558
|
-
|
|
559
|
-
interface SweepOptions {
|
|
560
|
-
/** Number of samples when path is a Curve3D. Default 48. */
|
|
561
|
-
samples?: number;
|
|
562
|
-
/** Marching-grid edge length for level-set meshing. Smaller = finer. */
|
|
563
|
-
edgeLength?: number;
|
|
564
|
-
/** Optional extra bounds padding. */
|
|
565
|
-
boundsPadding?: number;
|
|
566
|
-
/** Preferred "up" vector for local profile frame. Auto fallback is used near parallel segments. */
|
|
567
|
-
up?: Vec3$4;
|
|
568
|
-
}
|
|
569
|
-
```
|
|
532
|
+
Path can be a Curve3D from spline3d() or an array of [x,y,z] points (polyline). The profile is interpreted in the local frame normal plane. Compatible sweeps can export through the OCCT exact route using the canonical path representation.
|
|
570
533
|
|
|
571
|
-
|
|
534
|
+
Performance note: sweep uses level-set meshing internally. Prefer direct primitives/extrude/revolve when they can express the same shape.
|
|
572
535
|
|
|
573
|
-
|
|
536
|
+
`sweep(profile: Sketch, path: Curve3D | Vec3$3[], options?: SweepOptions): Shape`
|
|
574
537
|
|
|
575
|
-
|
|
576
|
-
variableSweep(spine: Curve3D | Vec3$4[], sections: VariableSweepSection[], options?: VariableSweepOptions): Shape
|
|
577
|
-
```
|
|
538
|
+
**`SweepOptions`**
|
|
578
539
|
|
|
579
|
-
|
|
540
|
+
| Option | Type | Description |
|
|
541
|
+
|--------|------|-------------|
|
|
542
|
+
| `samples?` | `number` | Number of samples when path is a Curve3D. Default 48. |
|
|
543
|
+
| `edgeLength?` | `number` | Marching-grid edge length for level-set meshing. Smaller = finer. |
|
|
544
|
+
| `boundsPadding?` | `number` | Optional extra bounds padding. |
|
|
545
|
+
| `up?` | `Vec3$3` | Preferred "up" vector for local profile frame. Auto fallback is used near parallel segments. |
|
|
580
546
|
|
|
581
|
-
|
|
547
|
+
#### `variableSweep()` — Sweep a variable cross-section along a 3D spine curve.
|
|
582
548
|
|
|
583
|
-
|
|
584
|
-
interface VariableSweepSection {
|
|
585
|
-
/** Parameter along the spine (0 = start, 1 = end). */
|
|
586
|
-
t: number;
|
|
587
|
-
/** Cross-section profile at this station. */
|
|
588
|
-
profile: Sketch;
|
|
589
|
-
}
|
|
590
|
-
```
|
|
549
|
+
Unlike sweep(), which uses a single constant profile, variableSweep() interpolates between multiple profiles at different stations along the spine. This enables organic shapes like tapering tubes, bone-like structures, and sculptural forms.
|
|
591
550
|
|
|
592
|
-
|
|
551
|
+
Each section specifies a t parameter (0 = start, 1 = end of spine) and a 2D profile sketch. The SDF-based level-set mesher smoothly blends between profiles at intermediate positions.
|
|
593
552
|
|
|
594
|
-
|
|
553
|
+
Performance note: like sweep(), this uses level-set meshing internally.
|
|
595
554
|
|
|
596
|
-
|
|
597
|
-
interface VariableSweepOptions {
|
|
598
|
-
/** Number of samples when spine is a Curve3D. Default 48. */
|
|
599
|
-
samples?: number;
|
|
600
|
-
/** Marching-grid edge length for level-set meshing. Smaller = finer. */
|
|
601
|
-
edgeLength?: number;
|
|
602
|
-
/** Optional extra bounds padding. */
|
|
603
|
-
boundsPadding?: number;
|
|
604
|
-
/** Preferred "up" vector for local profile frame. Auto fallback is used near parallel segments. */
|
|
605
|
-
up?: Vec3$4;
|
|
606
|
-
}
|
|
607
|
-
```
|
|
555
|
+
`variableSweep(spine: Curve3D | Vec3$3[], sections: VariableSweepSection[], options?: VariableSweepOptions): Shape`
|
|
608
556
|
|
|
609
|
-
|
|
557
|
+
**`VariableSweepSection`**
|
|
558
|
+
- `t: number` — Parameter along the spine (0 = start, 1 = end).
|
|
559
|
+
- `profile: Sketch` — Cross-section profile at this station.
|
|
610
560
|
|
|
611
|
-
|
|
561
|
+
**`VariableSweepOptions`**
|
|
612
562
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
563
|
+
| Option | Type | Description |
|
|
564
|
+
|--------|------|-------------|
|
|
565
|
+
| `samples?` | `number` | Number of samples when spine is a Curve3D. Default 48. |
|
|
566
|
+
| `edgeLength?` | `number` | Marching-grid edge length for level-set meshing. Smaller = finer. |
|
|
567
|
+
| `boundsPadding?` | `number` | Optional extra bounds padding. |
|
|
568
|
+
| `up?` | `Vec3$3` | Preferred "up" vector for local profile frame. Auto fallback is used near parallel segments. |
|
|
616
569
|
|
|
617
|
-
|
|
570
|
+
#### `transitionCurve()` — Create a smooth transition curve between two edges.
|
|
618
571
|
|
|
619
|
-
|
|
572
|
+
Returns a `HermiteCurve3D` that starts at `edgeA.point` tangent to `edgeA.tangent` and ends at `edgeB.point` tangent to `edgeB.tangent`.
|
|
620
573
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
tangent:
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
574
|
+
The curve maintains G1 continuity (matching tangent direction) at both endpoints. Weight parameters control the shape of the transition.
|
|
575
|
+
|
|
576
|
+
```js
|
|
577
|
+
// Connect two edges with a balanced transition
|
|
578
|
+
const curve = transitionCurve(
|
|
579
|
+
{ point: [0, 0, 0], tangent: [1, 0, 0] },
|
|
580
|
+
{ point: [10, 5, 0], tangent: [1, 0, 0] },
|
|
581
|
+
);
|
|
582
|
+
|
|
583
|
+
// Weighted: curve hugs edge A longer
|
|
584
|
+
const weighted = transitionCurve(
|
|
585
|
+
{ point: [0, 0, 0], tangent: [1, 0, 0] },
|
|
586
|
+
{ point: [10, 5, 0], tangent: [1, 0, 0] },
|
|
587
|
+
{ weightA: 2.0, weightB: 0.5 },
|
|
588
|
+
);
|
|
630
589
|
```
|
|
631
590
|
|
|
632
|
-
|
|
591
|
+
`transitionCurve(edgeA: TransitionEdge, edgeB: TransitionEdge, options?: TransitionCurveOptions): HermiteCurve3D`
|
|
633
592
|
|
|
634
|
-
|
|
593
|
+
**`TransitionEdge`**
|
|
594
|
+
- `point: Vec3$6` — Connection point on the edge. Can be any point along the edge where the transition should connect.
|
|
595
|
+
- `tangent: Vec3$6` — Tangent direction at the connection point. This is the direction the curve should initially follow when leaving this edge. For a straight edge, this is typically the edge direction pointing "outward" (away from the body of the edge, toward the other edge).
|
|
596
|
+
- `normal?: Vec3$6` — Surface normal at the connection point (optional). Used as a hint for the sweep frame's up vector.
|
|
635
597
|
|
|
636
|
-
|
|
637
|
-
transitionCurveFromPoints(startPoint: Vec3$7, startTangent: Vec3$7, endPoint: Vec3$7, endTangent: Vec3$7, options?: TransitionCurveOptions): HermiteCurve3D
|
|
638
|
-
```
|
|
598
|
+
#### `transitionSurface()` — Create a solid transition surface between two edges by sweeping a profile along a Hermite transition curve.
|
|
639
599
|
|
|
640
|
-
|
|
600
|
+
This produces a watertight solid that smoothly connects the two edges. Works with both Manifold and OCCT backends.
|
|
641
601
|
|
|
642
|
-
|
|
602
|
+
```js
|
|
603
|
+
// Circular tube connecting two edges
|
|
604
|
+
const tube = transitionSurface(
|
|
605
|
+
{ point: [0, 0, 0], tangent: [1, 0, 0] },
|
|
606
|
+
{ point: [10, 5, 3], tangent: [0, 1, 0] },
|
|
607
|
+
{ radius: 0.5 },
|
|
608
|
+
);
|
|
643
609
|
|
|
644
|
-
|
|
645
|
-
|
|
610
|
+
// Custom profile with weights
|
|
611
|
+
const custom = transitionSurface(
|
|
612
|
+
{ point: [0, 0, 0], tangent: [1, 0, 0] },
|
|
613
|
+
{ point: [10, 5, 3], tangent: [0, 1, 0] },
|
|
614
|
+
{ profile: mySketch, weightA: 1.5, weightB: 0.8 },
|
|
615
|
+
);
|
|
646
616
|
```
|
|
647
617
|
|
|
648
|
-
|
|
618
|
+
`transitionSurface(edgeA: TransitionEdge, edgeB: TransitionEdge, options?: TransitionSurfaceOptions): Shape`
|
|
649
619
|
|
|
650
620
|
---
|
|
651
621
|
|
|
@@ -653,106 +623,94 @@ Create a solid transition surface between two edges by sweeping a profile along
|
|
|
653
623
|
|
|
654
624
|
Select or inspect named faces and edges on a shape.
|
|
655
625
|
|
|
656
|
-
#### `coalesceEdges()`
|
|
626
|
+
#### `coalesceEdges()` — Merge collinear edge segments into longer logical edges.
|
|
657
627
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
628
|
+
**Details**
|
|
629
|
+
|
|
630
|
+
Tessellation often splits one geometric edge into multiple short segments. `coalesceEdges` groups adjacent collinear segments and merges each group into a single `EdgeSegment` spanning the full extent. This is usually needed before passing edges to `fillet()` or `chamfer()` on non-primitive shapes.
|
|
661
631
|
|
|
662
|
-
|
|
632
|
+
The `tolerance` controls the maximum perpendicular distance from collinearity before two segments are considered non-collinear. Default: `0.01`.
|
|
663
633
|
|
|
664
|
-
|
|
634
|
+
**Example**
|
|
665
635
|
|
|
666
636
|
```ts
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
start: Vec3;
|
|
671
|
-
end: Vec3;
|
|
672
|
-
midpoint: Vec3;
|
|
673
|
-
/** Normalized direction from start → end. */
|
|
674
|
-
direction: Vec3;
|
|
675
|
-
length: number;
|
|
676
|
-
/** Dihedral angle in degrees (0 = coplanar, 180 = knife edge). */
|
|
677
|
-
dihedralAngle: number;
|
|
678
|
-
/** true = outside corner (convex), false = inside corner (concave). */
|
|
679
|
-
convex: boolean;
|
|
680
|
-
/** Normal of first adjacent face. */
|
|
681
|
-
normalA: Vec3;
|
|
682
|
-
/** Normal of second adjacent face (same as normalA for boundary edges). */
|
|
683
|
-
normalB: Vec3;
|
|
684
|
-
/** true if this is a boundary (unmatched) edge — unusual for closed solids. */
|
|
685
|
-
boundary: boolean;
|
|
637
|
+
const topEdges = selectEdges(part, { atZ: 20 });
|
|
638
|
+
for (const edge of coalesceEdges(topEdges)) {
|
|
639
|
+
result = fillet(result, 2, edge);
|
|
686
640
|
}
|
|
687
641
|
```
|
|
688
642
|
|
|
689
|
-
|
|
643
|
+
`coalesceEdges(segments: EdgeSegment[], tolerance?: number): EdgeSegment[]`
|
|
690
644
|
|
|
691
|
-
|
|
645
|
+
**`EdgeSegment`**
|
|
692
646
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
647
|
+
| Option | Type | Description |
|
|
648
|
+
|--------|------|-------------|
|
|
649
|
+
| `index` | `number` | Stable index within the extraction (deterministic for a given mesh). |
|
|
650
|
+
| `direction` | `Vec3` | Normalized direction from start → end. |
|
|
651
|
+
| `dihedralAngle` | `number` | Dihedral angle in degrees (0 = coplanar, 180 = knife edge). |
|
|
652
|
+
| `convex` | `boolean` | true = outside corner (convex), false = inside corner (concave). |
|
|
653
|
+
| `normalA` | `Vec3` | Normal of first adjacent face. |
|
|
654
|
+
| `normalB` | `Vec3` | Normal of second adjacent face (same as normalA for boundary edges). |
|
|
655
|
+
| `boundary` | `boolean` | true if this is a boundary (unmatched) edge — unusual for closed solids. |
|
|
656
|
+
| `start`, `end`, `midpoint`, `length` | | — |
|
|
696
657
|
|
|
697
|
-
Select the single best-matching edge from a shape.
|
|
658
|
+
#### `selectEdge()` — Select the single best-matching edge from a shape.
|
|
698
659
|
|
|
699
|
-
|
|
660
|
+
**Details**
|
|
661
|
+
|
|
662
|
+
When `near` is specified, returns the edge whose midpoint is closest to that point. Otherwise returns the first matching edge in mesh order. Throws if no edges match the query — useful as a guard when you expect exactly one result.
|
|
663
|
+
|
|
664
|
+
**Example**
|
|
700
665
|
|
|
701
666
|
```ts
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
/** Filter: edge direction approximately parallel to this vector. */
|
|
706
|
-
parallel?: Vec3;
|
|
707
|
-
/** Filter: edge direction approximately perpendicular to this vector. */
|
|
708
|
-
perpendicular?: Vec3;
|
|
709
|
-
/** Filter: only convex (outside corner) edges. */
|
|
710
|
-
convex?: boolean;
|
|
711
|
-
/** Filter: only concave (inside corner) edges. */
|
|
712
|
-
concave?: boolean;
|
|
713
|
-
/** Filter: minimum dihedral angle in degrees. */
|
|
714
|
-
minAngle?: number;
|
|
715
|
-
/** Filter: maximum dihedral angle in degrees. */
|
|
716
|
-
maxAngle?: number;
|
|
717
|
-
/** Filter: minimum edge length. */
|
|
718
|
-
minLength?: number;
|
|
719
|
-
/** Filter: maximum edge length. */
|
|
720
|
-
maxLength?: number;
|
|
721
|
-
/** Filter: edge midpoint must be within this bounding region. */
|
|
722
|
-
within?: BoundingRegion;
|
|
723
|
-
/** Shorthand: edge midpoint Z ≈ this value (within tolerance). */
|
|
724
|
-
atZ?: number;
|
|
725
|
-
/** Tolerance for approximate matches (default: 1.0). */
|
|
726
|
-
tolerance?: number;
|
|
727
|
-
/** Angular tolerance in degrees for parallel/perpendicular (default: 10). */
|
|
728
|
-
angleTolerance?: number;
|
|
729
|
-
}
|
|
667
|
+
// Chamfer one specific edge near a known point
|
|
668
|
+
const bottomEdge = selectEdge(part, { near: [25, 0, 0], atZ: 0 });
|
|
669
|
+
result = chamfer(result, 1.5, bottomEdge);
|
|
730
670
|
```
|
|
731
671
|
|
|
732
|
-
|
|
672
|
+
`selectEdge(shape: Shape, query?: EdgeQuery): EdgeSegment`
|
|
733
673
|
|
|
734
|
-
|
|
674
|
+
**`EdgeQuery`**
|
|
735
675
|
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
676
|
+
| Option | Type | Description |
|
|
677
|
+
|--------|------|-------------|
|
|
678
|
+
| `near?` | `Vec3` | Sort by proximity to this point (closest first). When used with `selectEdge`, picks the closest match. |
|
|
679
|
+
| `parallel?` | `Vec3` | Filter: edge direction approximately parallel to this vector. |
|
|
680
|
+
| `perpendicular?` | `Vec3` | Filter: edge direction approximately perpendicular to this vector. |
|
|
681
|
+
| `convex?` | `boolean` | Filter: only convex (outside corner) edges. |
|
|
682
|
+
| `concave?` | `boolean` | Filter: only concave (inside corner) edges. |
|
|
683
|
+
| `minAngle?` | `number` | Filter: minimum dihedral angle in degrees. |
|
|
684
|
+
| `maxAngle?` | `number` | Filter: maximum dihedral angle in degrees. |
|
|
685
|
+
| `minLength?` | `number` | Filter: minimum edge length. |
|
|
686
|
+
| `maxLength?` | `number` | Filter: maximum edge length. |
|
|
687
|
+
| `within?` | `BoundingRegion` | Filter: edge midpoint must be within this bounding region. |
|
|
688
|
+
| `atZ?` | `number` | Shorthand: edge midpoint Z ≈ this value (within `tolerance`). Equivalent to `within: { zMin: atZ - tol, zMax: atZ + tol }`. |
|
|
689
|
+
| `tolerance?` | `number` | Position tolerance for approximate matches (default: `1.0`). Used by `atZ` and `near`. |
|
|
690
|
+
| `angleTolerance?` | `number` | Angular tolerance in degrees for `parallel`/`perpendicular` filters (default: `10`). |
|
|
691
|
+
|
|
692
|
+
`BoundingRegion`: `{ xMin?: number, xMax?: number, yMin?: number, yMax?: number, zMin?: number, zMax?: number }`
|
|
746
693
|
|
|
747
|
-
|
|
694
|
+
#### `selectEdges()` — Select all edges from a shape that match the given query.
|
|
748
695
|
|
|
749
|
-
|
|
696
|
+
**Details**
|
|
697
|
+
|
|
698
|
+
Extracts sharp edges from the mesh (dihedral angle > 1°), applies all filters in the query, and returns the matching `EdgeSegment[]`. When `near` is specified the results are sorted closest-first.
|
|
699
|
+
|
|
700
|
+
Works on any shape — primitives, booleans, shells, and imported meshes. Use this when tracked topology is unavailable (e.g. after a difference or on imported geometry). For simpler cases, pass an `EdgeQuery` directly to `fillet()` or `chamfer()` instead of calling `selectEdges` separately.
|
|
701
|
+
|
|
702
|
+
**Example**
|
|
750
703
|
|
|
751
704
|
```ts
|
|
752
|
-
|
|
705
|
+
// Fillet all top edges of a box
|
|
706
|
+
const topEdges = selectEdges(part, { atZ: 20, perpendicular: [0, 0, 1] });
|
|
707
|
+
let result = part;
|
|
708
|
+
for (const edge of coalesceEdges(topEdges)) {
|
|
709
|
+
result = fillet(result, 2, edge);
|
|
710
|
+
}
|
|
753
711
|
```
|
|
754
712
|
|
|
755
|
-
|
|
713
|
+
`selectEdges(shape: Shape, query?: EdgeQuery): EdgeSegment[]`
|
|
756
714
|
|
|
757
715
|
---
|
|
758
716
|
|
|
@@ -760,100 +718,155 @@ Select all edges from a shape that match the given query. Extracts sharp edges f
|
|
|
760
718
|
|
|
761
719
|
Modify edges of a solid — fillets, chamfers, draft, offset.
|
|
762
720
|
|
|
763
|
-
#### `
|
|
721
|
+
#### `chamfer()` — Apply chamfers (beveled edges) to one or more edges of a shape.
|
|
764
722
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
723
|
+
**Details**
|
|
724
|
+
|
|
725
|
+
Produces a 45° bevel at the specified `size` (distance from edge). Works on both straight and curved edges. Supports OCCT and Manifold backends.
|
|
768
726
|
|
|
769
|
-
|
|
727
|
+
The `edges` parameter accepts the same options as `fillet()`: inline `EdgeQuery`, pre-selected `EdgeSegment`/`EdgeSegment[]`, or `undefined` (all sharp edges).
|
|
770
728
|
|
|
771
|
-
|
|
729
|
+
**Example**
|
|
772
730
|
|
|
773
731
|
```ts
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
midpoint: Vec3;
|
|
780
|
-
/** Normalized direction from start → end. */
|
|
781
|
-
direction: Vec3;
|
|
782
|
-
length: number;
|
|
783
|
-
/** Dihedral angle in degrees (0 = coplanar, 180 = knife edge). */
|
|
784
|
-
dihedralAngle: number;
|
|
785
|
-
/** true = outside corner (convex), false = inside corner (concave). */
|
|
786
|
-
convex: boolean;
|
|
787
|
-
/** Normal of first adjacent face. */
|
|
788
|
-
normalA: Vec3;
|
|
789
|
-
/** Normal of second adjacent face (same as normalA for boundary edges). */
|
|
790
|
-
normalB: Vec3;
|
|
791
|
-
/** true if this is a boundary (unmatched) edge — unusual for closed solids. */
|
|
792
|
-
boundary: boolean;
|
|
793
|
-
}
|
|
732
|
+
// Chamfer all edges
|
|
733
|
+
chamfer(myShape, 1)
|
|
734
|
+
|
|
735
|
+
// Chamfer only vertical edges
|
|
736
|
+
chamfer(myShape, 2, { parallel: [0, 0, 1] })
|
|
794
737
|
```
|
|
795
738
|
|
|
796
|
-
|
|
739
|
+
`chamfer(shape: Shape, size: number, edges?: EdgeSelector): Shape`
|
|
797
740
|
|
|
798
|
-
#### `
|
|
741
|
+
#### `draft()` — Apply a draft angle (taper) to vertical faces for mold extraction.
|
|
799
742
|
|
|
800
|
-
|
|
801
|
-
chamferEdgeSegment(shape: Shape, segment: EdgeSegment, size: number): Shape
|
|
802
|
-
```
|
|
743
|
+
**Details**
|
|
803
744
|
|
|
804
|
-
|
|
745
|
+
Adds a taper angle to the vertical faces of a solid so that it can be extracted from a mold. The neutral plane is the Z position where the draft angle is zero — faces above and below are tapered symmetrically. Typical values for injection molding are 1–5°.
|
|
805
746
|
|
|
806
|
-
|
|
747
|
+
Requires the OCCT backend. Throws on Manifold.
|
|
748
|
+
|
|
749
|
+
**Example**
|
|
807
750
|
|
|
808
751
|
```ts
|
|
809
|
-
|
|
752
|
+
// Add 3° draft to a box for injection molding
|
|
753
|
+
draft(myBox, 3)
|
|
754
|
+
|
|
755
|
+
// Draft with custom pull direction and neutral plane
|
|
756
|
+
draft(myShape, 2, [0, 0, 1], 10)
|
|
810
757
|
```
|
|
811
758
|
|
|
812
|
-
|
|
759
|
+
`draft(shape: Shape, angleDeg: number, pullDirection?: [ number, number, number ], neutralPlaneOffset?: number): Shape`
|
|
760
|
+
|
|
761
|
+
#### `fillet()` — Apply fillets (rounded edges) to one or more edges of a shape.
|
|
813
762
|
|
|
814
|
-
|
|
763
|
+
**Details**
|
|
764
|
+
|
|
765
|
+
Works on both straight and curved edges. Supports OCCT and Manifold backends. When using OCCT, all edges are filleted in a single kernel operation for best quality. When using Manifold, edges are filleted sequentially.
|
|
766
|
+
|
|
767
|
+
The `edges` parameter is flexible: - Omit to fillet **all** sharp edges - Pass an `EdgeQuery` for an inline filter (most common) - Pass an `EdgeSegment` or `EdgeSegment[]` from `selectEdges()` for pre-selected edges
|
|
768
|
+
|
|
769
|
+
Throws if no edges match the selection, or if `radius` is not a positive finite number.
|
|
770
|
+
|
|
771
|
+
**Example**
|
|
815
772
|
|
|
816
773
|
```ts
|
|
817
|
-
|
|
774
|
+
// Fillet all edges
|
|
775
|
+
fillet(myShape, 2)
|
|
776
|
+
|
|
777
|
+
// Fillet only top convex edges
|
|
778
|
+
fillet(myShape, 1.5, { atZ: 20, convex: true })
|
|
779
|
+
|
|
780
|
+
// Fillet vertical edges selected beforehand
|
|
781
|
+
const edges = selectEdges(myShape, { parallel: [0, 0, 1] })
|
|
782
|
+
fillet(myShape, 3, edges)
|
|
818
783
|
```
|
|
819
784
|
|
|
820
|
-
|
|
785
|
+
`fillet(shape: Shape, radius: number, edges?: EdgeSelector, segments?: number): Shape`
|
|
786
|
+
|
|
787
|
+
#### `offsetSolid()` — Uniformly offset all surfaces of a solid inward or outward.
|
|
788
|
+
|
|
789
|
+
**Details**
|
|
821
790
|
|
|
822
|
-
|
|
791
|
+
Unlike `shell()`, which hollows a solid by removing one face, `offsetSolid()` produces a new solid whose every surface is shifted by `thickness`. Positive values grow the shape outward; negative values shrink it inward.
|
|
792
|
+
|
|
793
|
+
Requires the OCCT backend. Throws on Manifold.
|
|
794
|
+
|
|
795
|
+
**Example**
|
|
823
796
|
|
|
824
797
|
```ts
|
|
825
|
-
|
|
798
|
+
// Grow a box outward by 1mm on all sides
|
|
799
|
+
offsetSolid(myBox, 1)
|
|
800
|
+
|
|
801
|
+
// Shrink a shape inward by 0.5mm
|
|
802
|
+
offsetSolid(myShape, -0.5)
|
|
826
803
|
```
|
|
827
804
|
|
|
828
|
-
|
|
805
|
+
`offsetSolid(shape: Shape, thickness: number): Shape`
|
|
806
|
+
|
|
807
|
+
#### `chamfer2d()` — Bevel a named vertical edge of a shape with a 45° chamfer.
|
|
829
808
|
|
|
830
|
-
|
|
809
|
+
**Details**
|
|
810
|
+
|
|
811
|
+
Compiler-owned chamfer for tracked vertical edges. Requires a compile-plan-covered target. Supported subset and quadrant semantics are the same as `fillet2d()` — see that function for details.
|
|
812
|
+
|
|
813
|
+
**Example**
|
|
831
814
|
|
|
832
815
|
```ts
|
|
833
|
-
|
|
816
|
+
const b = rectangle(0, 0, 50, 50).extrude(20);
|
|
817
|
+
const chamfered = chamfer2d(b.toShape(), b.edge('vert-br'), 3, [-1, -1]);
|
|
834
818
|
```
|
|
835
819
|
|
|
836
|
-
|
|
820
|
+
`chamfer2d(shape: Shape, edge: EdgeRef, size: number, quadrant?: [ number, number ]): Shape`
|
|
821
|
+
|
|
822
|
+
**`EdgeRef`**
|
|
823
|
+
- `query?: EdgeQueryRef` — Compiler-owned edge query when available.
|
|
824
|
+
- Also: `name: EdgeName`
|
|
837
825
|
|
|
838
|
-
#### `
|
|
826
|
+
#### `fillet2d()` — Round a named vertical edge of a shape with a circular fillet.
|
|
827
|
+
|
|
828
|
+
**Details**
|
|
829
|
+
|
|
830
|
+
Compiler-owned fillet for tracked vertical edges. Requires a compile-plan-covered target (shapes from `box()`, `rectangle().extrude()`, or rigid transforms of those).
|
|
831
|
+
|
|
832
|
+
**Supported edges:** - Tracked vertical edges from `box()` or `rectangle().extrude()` - Rigid transforms between tracked source and target - Untouched sibling tracked vertical edges after earlier `fillet2d`/`chamfer2d`
|
|
833
|
+
|
|
834
|
+
**Not supported:** edges after shell, hole, cut, trim, difference, intersection, generic sketch extrudes, or tapered extrudes. Use `fillet()` with an `EdgeQuery` for those cases.
|
|
835
|
+
|
|
836
|
+
Canonical quadrants: `vert-bl → [1,-1]`, `vert-br → [-1,-1]`, `vert-tr → [-1,1]`, `vert-tl → [1,1]`
|
|
837
|
+
|
|
838
|
+
**Example**
|
|
839
839
|
|
|
840
840
|
```ts
|
|
841
|
-
|
|
841
|
+
const b = rectangle(0, 0, 50, 50).extrude(20);
|
|
842
|
+
const filleted = fillet2d(b.toShape(), b.edge('vert-br'), 5, [-1, -1]);
|
|
842
843
|
```
|
|
843
844
|
|
|
844
|
-
|
|
845
|
+
`fillet2d(shape: Shape, edge: EdgeRef, radius: number, quadrant?: [ number, number ], segments?: number): Shape`
|
|
846
|
+
|
|
847
|
+
#### `filletCorners()` — Create a polygon from points with specific corners rounded to arc fillets.
|
|
845
848
|
|
|
846
|
-
|
|
849
|
+
**Details**
|
|
850
|
+
|
|
851
|
+
Each corner spec identifies a vertex by its index in the `points` array and the desired fillet `radius`. Both convex and concave corners are supported.
|
|
852
|
+
|
|
853
|
+
Constraints: - Collinear corners cannot be filleted (throws an error) - Two neighboring fillets whose tangent lengths overlap the same edge will throw - Radius must be positive and small enough to fit within the adjacent edge lengths
|
|
854
|
+
|
|
855
|
+
Use `offset(-r).offset(+r)` instead if you want to round **all** convex corners uniformly. Use `filletCorners` when you need selective or mixed sharp/rounded profiles.
|
|
856
|
+
|
|
857
|
+
**Example**
|
|
847
858
|
|
|
848
859
|
```ts
|
|
849
|
-
|
|
850
|
-
index:
|
|
851
|
-
radius:
|
|
852
|
-
|
|
853
|
-
|
|
860
|
+
const roof = filletCorners(roofPoints, [
|
|
861
|
+
{ index: 3, radius: 19 },
|
|
862
|
+
{ index: 4, radius: 19 },
|
|
863
|
+
{ index: 5, radius: 19 },
|
|
864
|
+
]);
|
|
854
865
|
```
|
|
855
866
|
|
|
856
|
-
|
|
867
|
+
`filletCorners(points: PointInput[], corners: FilletCornerSpec[]): Sketch`
|
|
868
|
+
|
|
869
|
+
`FilletCornerSpec`: `{ index: number, radius: number, segments?: number }`
|
|
857
870
|
|
|
858
871
|
---
|
|
859
872
|
|
|
@@ -861,92 +874,93 @@ interface FilletCornerSpec {
|
|
|
861
874
|
|
|
862
875
|
Duplicate geometry in regular arrangements (linear, circular, mirror).
|
|
863
876
|
|
|
864
|
-
#### `circularLayout()`
|
|
877
|
+
#### `circularLayout()` — Compute evenly-spaced positions around a circle.
|
|
865
878
|
|
|
866
|
-
|
|
867
|
-
circularLayout(count: number, radius: number, options?: CircularLayoutOptions): LayoutPoint[]
|
|
868
|
-
```
|
|
869
|
-
|
|
870
|
-
Compute evenly-spaced positions around a circle. Eliminates the most common trig pattern in CAD scripts: ```js // Before — manual trig for (let i = 0; i < 12; i++) { const angle = i * 30 * Math.PI / 180; markers.push(marker.translate(r * Math.cos(angle), r * Math.sin(angle), 0)); } // After — declarative for (const {x, y} of circularLayout(12, r)) { markers.push(marker.translate(x, y, 0)); } ```
|
|
879
|
+
Eliminates the most common trig pattern in CAD scripts:
|
|
871
880
|
|
|
872
|
-
|
|
881
|
+
```js
|
|
882
|
+
// Before — manual trig
|
|
883
|
+
for (let i = 0; i < 12; i++) {
|
|
884
|
+
const angle = i * 30 * Math.PI / 180;
|
|
885
|
+
markers.push(marker.translate(r * Math.cos(angle), r * Math.sin(angle), 0));
|
|
886
|
+
}
|
|
873
887
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
startDeg?: number;
|
|
878
|
-
/** Center X coordinate (default: 0). */
|
|
879
|
-
centerX?: number;
|
|
880
|
-
/** Center Y coordinate (default: 0). */
|
|
881
|
-
centerY?: number;
|
|
888
|
+
// After — declarative
|
|
889
|
+
for (const {x, y} of circularLayout(12, r)) {
|
|
890
|
+
markers.push(marker.translate(x, y, 0));
|
|
882
891
|
}
|
|
883
892
|
```
|
|
884
893
|
|
|
885
|
-
|
|
894
|
+
`circularLayout(count: number, radius: number, options?: CircularLayoutOptions): LayoutPoint[]`
|
|
886
895
|
|
|
887
|
-
|
|
896
|
+
**`CircularLayoutOptions`**
|
|
897
|
+
- `startDeg?: number` — Angle of the first element in degrees (default: 0 = +X axis).
|
|
898
|
+
- `centerX?: number` — Center X coordinate (default: 0).
|
|
899
|
+
- `centerY?: number` — Center Y coordinate (default: 0).
|
|
888
900
|
|
|
889
|
-
|
|
890
|
-
interface LayoutPoint {
|
|
891
|
-
x: number;
|
|
892
|
-
y: number;
|
|
893
|
-
}
|
|
894
|
-
```
|
|
901
|
+
`LayoutPoint`: `{ x: number, y: number }`
|
|
895
902
|
|
|
896
|
-
|
|
903
|
+
#### `circularPattern()` — Repeat a shape in a circular pattern around an axis and union the copies.
|
|
897
904
|
|
|
898
|
-
|
|
905
|
+
**Details**
|
|
899
906
|
|
|
900
|
-
|
|
901
|
-
circularPattern(shape: Shape, count: number, centerXOrOpts?: number | CircularPatternOptions, centerY?: number): Shape
|
|
902
|
-
```
|
|
907
|
+
Distributes `count` copies evenly around the rotation axis (360° / count per step). All copies are unioned into a single `Shape`. Distinct compiler ownership is assigned to each copy — post-merge face identity via owner-scoped canonical queries still works for pattern descendants.
|
|
903
908
|
|
|
904
|
-
|
|
909
|
+
Two calling conventions: - **Simple** (Z axis): `circularPattern(shape, 6)` or `circularPattern(shape, 6, centerX, centerY)` - **Advanced** (arbitrary axis): `circularPattern(shape, 6, { axis, origin })`
|
|
905
910
|
|
|
906
|
-
|
|
911
|
+
**Example**
|
|
907
912
|
|
|
908
913
|
```ts
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
}
|
|
914
|
+
// 8 holes evenly spaced around origin
|
|
915
|
+
circularPattern(cylinder(12, 4).translate(30, 0, -1), 8)
|
|
916
|
+
|
|
917
|
+
// Circular pattern around X axis
|
|
918
|
+
circularPattern(myFeature, 4, { axis: [1, 0, 0], origin: [0, 0, 50] })
|
|
915
919
|
```
|
|
916
920
|
|
|
917
|
-
|
|
921
|
+
`circularPattern(shape: Shape, count: number, centerXOrOpts?: number | CircularPatternOptions, centerY?: number): Shape`
|
|
918
922
|
|
|
919
|
-
|
|
923
|
+
**`CircularPatternOptions`**
|
|
924
|
+
- `centerX?: number` — Center X of the rotation (default: 0). Used when axis is Z (legacy mode).
|
|
925
|
+
- `centerY?: number` — Center Y of the rotation (default: 0). Used when axis is Z (legacy mode).
|
|
920
926
|
|
|
921
|
-
|
|
922
|
-
circularPattern2d(sketch: Sketch, count: number, centerXOrOpts?: number | { centerX?: number; centerY?: number; startDeg?: number; }, centerY?: number): Sketch
|
|
923
|
-
```
|
|
927
|
+
#### `circularPattern2d()` — Repeat a 2D sketch in a circular pattern around a center point and union the copies.
|
|
924
928
|
|
|
925
|
-
|
|
929
|
+
`circularPattern2d(sketch: Sketch, count: number, centerXOrOpts?: number | { centerX?: number; centerY?: number; startDeg?: number; }, centerY?: number): Sketch`
|
|
926
930
|
|
|
927
|
-
#### `linearPattern()`
|
|
931
|
+
#### `linearPattern()` — Repeat a shape in a linear pattern along a direction vector and union the copies.
|
|
928
932
|
|
|
929
|
-
|
|
930
|
-
linearPattern(shape: Shape, count: number, dx: number, dy: number, dz?: number): Shape
|
|
931
|
-
```
|
|
933
|
+
**Details**
|
|
932
934
|
|
|
933
|
-
|
|
935
|
+
Creates `count` copies of `shape`, each offset by `(dx*i, dy*i, dz*i)` from the original. All copies are unioned into a single `Shape`. Distinct compiler ownership is assigned to each copy so face identity via owner-scoped canonical queries still works post-merge.
|
|
934
936
|
|
|
935
|
-
|
|
937
|
+
**Example**
|
|
936
938
|
|
|
937
939
|
```ts
|
|
938
|
-
|
|
940
|
+
// 5 cylinders, 20mm apart along X
|
|
941
|
+
linearPattern(cylinder(10, 3), 5, 20, 0)
|
|
939
942
|
```
|
|
940
943
|
|
|
941
|
-
|
|
944
|
+
`linearPattern(shape: Shape, count: number, dx: number, dy: number, dz?: number): Shape`
|
|
945
|
+
|
|
946
|
+
#### `linearPattern2d()` — Repeat a 2D sketch in a linear pattern and union the copies.
|
|
947
|
+
|
|
948
|
+
`linearPattern2d(sketch: Sketch, count: number, dx: number, dy?: number): Sketch`
|
|
949
|
+
|
|
950
|
+
#### `mirrorCopy()` — Mirror a shape across a plane and union the mirror with the original.
|
|
942
951
|
|
|
943
|
-
|
|
952
|
+
**Details**
|
|
953
|
+
|
|
954
|
+
The mirror plane passes through the origin and is defined by its normal vector. The mirrored copy is unioned with the original to produce a single symmetric Shape.
|
|
955
|
+
|
|
956
|
+
**Example**
|
|
944
957
|
|
|
945
958
|
```ts
|
|
946
|
-
|
|
959
|
+
// Mirror across the YZ plane (X=0)
|
|
960
|
+
mirrorCopy(box(50, 30, 10), [1, 0, 0])
|
|
947
961
|
```
|
|
948
962
|
|
|
949
|
-
|
|
963
|
+
`mirrorCopy(shape: Shape, normal: [ number, number, number ]): Shape`
|
|
950
964
|
|
|
951
965
|
---
|
|
952
966
|
|
|
@@ -954,177 +968,190 @@ Mirror a shape across a plane defined by its normal and union the mirror with th
|
|
|
954
968
|
|
|
955
969
|
Define geometry by relationships and let a solver find positions.
|
|
956
970
|
|
|
957
|
-
#### `addPolygon()`
|
|
971
|
+
#### `addPolygon()` — Add a general polygon concept to the builder.
|
|
958
972
|
|
|
959
|
-
|
|
960
|
-
addPolygon(sk: ConstrainedSketchBuilder, options: PolygonOptions): ConstrainedPolygon
|
|
961
|
-
```
|
|
973
|
+
Creates n vertices and n sides (CCW: `sides[i]` from `vertices[i]` → `vertices[(i+1) % n]`). Applies a `ccw` constraint to enforce winding. All dimensional constraints (lengths, angles, position) are left to the caller.
|
|
962
974
|
|
|
963
|
-
|
|
975
|
+
Use `sk.addPolygon()` as the shorthand builder method.
|
|
964
976
|
|
|
965
|
-
|
|
977
|
+
**Example**
|
|
966
978
|
|
|
967
979
|
```ts
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
}
|
|
980
|
+
const sk = constrainedSketch();
|
|
981
|
+
const tri = sk.addPolygon({ points: [[0,0],[100,0],[50,80]] });
|
|
982
|
+
sk.fix(tri.vertex(0), 0, 0);
|
|
983
|
+
sk.length(tri.side(0), 100);
|
|
984
|
+
return sk.solve().extrude(5);
|
|
974
985
|
```
|
|
975
986
|
|
|
976
|
-
|
|
987
|
+
`addPolygon(sk: ConstrainedSketchBuilder, options: PolygonOptions): ConstrainedPolygon`
|
|
977
988
|
|
|
978
|
-
|
|
989
|
+
**`PolygonOptions`**
|
|
990
|
+
- `addLoop?: boolean` — Whether to register a closed loop for sketch generation. Default: true.
|
|
991
|
+
- `blockRotation?: boolean` — Prevent 180° rotation (ensures first edge maintains its initial direction). Default: false.
|
|
979
992
|
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
}
|
|
989
|
-
```
|
|
993
|
+
**`ConstrainedPolygon`**
|
|
994
|
+
- `vertices: PointId[]` — CCW-ordered PointIds.
|
|
995
|
+
- `sides: LineId[]` — CCW-ordered LineIds. `sides[i]` runs from `vertices[i]` → `vertices[(i+1) % n]`.
|
|
996
|
+
- `shape: ShapeId` — ShapeId for `shapeWidth`, `shapeHeight`, `shapeArea`, `shapeCentroidX/Y`.
|
|
997
|
+
|
|
998
|
+
#### `addRect()` — Add an axis-aligned rectangle concept to the builder.
|
|
999
|
+
|
|
1000
|
+
Creates 4 vertices (CCW: bl→br→tr→tl), 4 sides, 4 structural constraints (`horizontal`/`vertical` on each side), CCW winding, a center point, a loop, and a shape. Returns a `ConstrainedRect` handle with 4 DOF (x, y, width, height).
|
|
990
1001
|
|
|
991
|
-
|
|
1002
|
+
Use `sk.rect()` as the shorthand builder method.
|
|
992
1003
|
|
|
993
|
-
|
|
1004
|
+
**Example**
|
|
994
1005
|
|
|
995
1006
|
```ts
|
|
996
|
-
|
|
1007
|
+
const sk = constrainedSketch();
|
|
1008
|
+
const r = sk.rect({ x: 0, y: 0, width: 100, height: 50 });
|
|
1009
|
+
sk.fix(r.bottomLeft, 0, 0);
|
|
1010
|
+
sk.length(r.bottom, 120); // override initial width
|
|
1011
|
+
return sk.solve().extrude(10);
|
|
997
1012
|
```
|
|
998
1013
|
|
|
999
|
-
|
|
1014
|
+
`addRect(sk: ConstrainedSketchBuilder, options?: RectOptions): ConstrainedRect`
|
|
1000
1015
|
|
|
1001
|
-
|
|
1016
|
+
**`RectOptions`**
|
|
1002
1017
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
width?: number;
|
|
1011
|
-
/** Height (along y). Default: 10. */
|
|
1012
|
-
height?: number;
|
|
1013
|
-
/** Prevent 180° rotation (ensures bottom edge points rightward). Default: false. */
|
|
1014
|
-
blockRotation?: boolean;
|
|
1015
|
-
}
|
|
1016
|
-
```
|
|
1018
|
+
| Option | Type | Description |
|
|
1019
|
+
|--------|------|-------------|
|
|
1020
|
+
| `x?` | `number` | Bottom-left x coordinate. Default: 0. |
|
|
1021
|
+
| `y?` | `number` | Bottom-left y coordinate. Default: 0. |
|
|
1022
|
+
| `width?` | `number` | Width (along x). Default: 10. |
|
|
1023
|
+
| `height?` | `number` | Height (along y). Default: 10. |
|
|
1024
|
+
| `blockRotation?` | `boolean` | Prevent 180° rotation (ensures bottom edge points rightward). Default: false. |
|
|
1017
1025
|
|
|
1018
|
-
|
|
1026
|
+
**`ConstrainedRect`**
|
|
1019
1027
|
|
|
1020
|
-
|
|
1028
|
+
| Option | Type | Description |
|
|
1029
|
+
|--------|------|-------------|
|
|
1030
|
+
| `bottom` | `LineId` | bottom-left → bottom-right |
|
|
1031
|
+
| `right` | `LineId` | bottom-right → top-right |
|
|
1032
|
+
| `top` | `LineId` | top-right → top-left |
|
|
1033
|
+
| `left` | `LineId` | top-left → bottom-left |
|
|
1034
|
+
| `center` | `PointId` | Center point constrained to the geometric center via `midpoint` on the diagonal. Can be used in further constraints: `sk.fix(rect.center, 0, 0)`, `sk.coincident(rect.center, other)`. |
|
|
1035
|
+
| `shape` | `ShapeId` | ShapeId for `shapeWidth`, `shapeHeight`, `shapeArea`, `shapeCentroidX/Y`. |
|
|
1036
|
+
| `bottomLeft`, `bottomRight`, `topRight`, `topLeft` | | — |
|
|
1021
1037
|
|
|
1022
|
-
|
|
1023
|
-
interface ConstrainedRect {
|
|
1024
|
-
bottomLeft: PointId;
|
|
1025
|
-
bottomRight: PointId;
|
|
1026
|
-
topRight: PointId;
|
|
1027
|
-
topLeft: PointId;
|
|
1028
|
-
/** bottom-left → bottom-right */
|
|
1029
|
-
bottom: LineId;
|
|
1030
|
-
/** bottom-right → top-right */
|
|
1031
|
-
right: LineId;
|
|
1032
|
-
/** top-right → top-left */
|
|
1033
|
-
top: LineId;
|
|
1034
|
-
/** top-left → bottom-left */
|
|
1035
|
-
left: LineId;
|
|
1036
|
-
/** Center point constrained to the geometric center via `midpoint` on the diagonal. Can be used in further constraints: `sk.fix(rect.center, 0, 0)`, `sk.coincident(rect.center, other)`. */
|
|
1037
|
-
center: PointId;
|
|
1038
|
-
/** ShapeId for `shapeWidth`, `shapeHeight`, `shapeArea`, `shapeCentroidX/Y`. */
|
|
1039
|
-
shape: ShapeId;
|
|
1040
|
-
}
|
|
1041
|
-
```
|
|
1038
|
+
#### `addRegularPolygon()` — Add a regular n-gon concept to the builder.
|
|
1042
1039
|
|
|
1043
|
-
|
|
1040
|
+
Vertices are placed at `(cx + r·cos(startAngle + i·2π/n), cy + r·sin(...))`. Equal-radius and equal-side constraints enforce regularity (4 DOF: center x/y, radius, rotation). The center point is tracked by the solver and exposed via the returned handle.
|
|
1044
1041
|
|
|
1045
|
-
|
|
1042
|
+
Use `sk.regularPolygon()` as the shorthand builder method.
|
|
1043
|
+
|
|
1044
|
+
**Example**
|
|
1046
1045
|
|
|
1047
1046
|
```ts
|
|
1048
|
-
|
|
1047
|
+
const sk = constrainedSketch();
|
|
1048
|
+
const hex = sk.regularPolygon({ sides: 6, radius: 25 });
|
|
1049
|
+
sk.fix(hex.center, 0, 0);
|
|
1050
|
+
sk.length(hex.side(0), 30); // all sides change (equal constraint)
|
|
1051
|
+
return sk.solve().extrude(5);
|
|
1049
1052
|
```
|
|
1050
1053
|
|
|
1051
|
-
|
|
1054
|
+
`addRegularPolygon(sk: ConstrainedSketchBuilder, options: RegularPolygonOptions): ConstrainedRegularPolygon`
|
|
1052
1055
|
|
|
1053
|
-
|
|
1056
|
+
**`RegularPolygonOptions`**
|
|
1054
1057
|
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
/** Center y coordinate. Default: 0. */
|
|
1064
|
-
cy?: number;
|
|
1065
|
-
/** Angle (in degrees) of vertex[0] measured from the +X axis (CCW positive). Default: 0 (rightmost vertex). */
|
|
1066
|
-
startAngle?: number;
|
|
1067
|
-
/** Prevent 180° rotation (ensures first edge maintains its initial direction). Default: false. */
|
|
1068
|
-
blockRotation?: boolean;
|
|
1069
|
-
}
|
|
1070
|
-
```
|
|
1058
|
+
| Option | Type | Description |
|
|
1059
|
+
|--------|------|-------------|
|
|
1060
|
+
| `sides` | `number` | Number of sides (minimum 3). |
|
|
1061
|
+
| `radius?` | `number` | Circumradius — distance from center to vertex. Default: 10. |
|
|
1062
|
+
| `cx?` | `number` | Center x coordinate. Default: 0. |
|
|
1063
|
+
| `cy?` | `number` | Center y coordinate. Default: 0. |
|
|
1064
|
+
| `startAngle?` | `number` | Angle (in degrees) of vertex[0] measured from the +X axis (CCW positive). Default: 0 (rightmost vertex). |
|
|
1065
|
+
| `blockRotation?` | `boolean` | Prevent 180° rotation (ensures first edge maintains its initial direction). Default: false. |
|
|
1071
1066
|
|
|
1072
|
-
</details>
|
|
1073
1067
|
|
|
1068
|
+
**`ConstrainedRegularPolygon`** extends ConstrainedPolygon
|
|
1069
|
+
- `center: PointId` — Center point. Use `sk.fix(poly.center, x, y)` to pin location, or `sk.coincident(poly.center, other)` to align with other geometry.
|
|
1074
1070
|
|
|
1075
|
-
|
|
1071
|
+
#### `circle()` — Create an analytic 2D circle for measurement, construction, and extrusion.
|
|
1072
|
+
|
|
1073
|
+
**Example**
|
|
1076
1074
|
|
|
1077
1075
|
```ts
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1076
|
+
const c = circle(0, 0, 25);
|
|
1077
|
+
c.diameter; c.circumference; c.area;
|
|
1078
|
+
c.pointAtAngle(90); // Point2D at top (90° CCW from +X)
|
|
1079
|
+
|
|
1080
|
+
// Extrude to cylinder with named faces
|
|
1081
|
+
const cyl = c.extrude(30);
|
|
1082
|
+
cyl.face('top'); // FaceRef (planar)
|
|
1083
|
+
cyl.face('side'); // FaceRef (curved)
|
|
1084
|
+
|
|
1085
|
+
Circle2D.fromDiameter(point(0, 0), 50);
|
|
1082
1086
|
```
|
|
1083
1087
|
|
|
1084
|
-
|
|
1088
|
+
`circle(cx: number, cy: number, radius: number): Circle2D`
|
|
1085
1089
|
|
|
1086
|
-
#### `
|
|
1090
|
+
#### `constrainedSketch()` — Create a parametric 2D sketch driven by geometric constraints and a nonlinear solver.
|
|
1087
1091
|
|
|
1088
|
-
|
|
1089
|
-
circle(cx: number, cy: number, radius: number): Circle2D
|
|
1090
|
-
```
|
|
1092
|
+
**Workflow**
|
|
1091
1093
|
|
|
1092
|
-
Create
|
|
1094
|
+
1. Create a builder with `constrainedSketch()`. 2. Add geometry — points, lines, circles, arcs — using the builder methods. 3. Add constraints (`horizontal`, `length`, `fix`, etc.) to drive the geometry. 4. Call `.solve()` to run the solver and get a `ConstraintSketch` (which extends `Sketch`).
|
|
1093
1095
|
|
|
1094
|
-
|
|
1096
|
+
**Example**
|
|
1095
1097
|
|
|
1096
1098
|
```ts
|
|
1097
|
-
constrainedSketch(
|
|
1099
|
+
const sk = constrainedSketch();
|
|
1100
|
+
const p1 = sk.point(0, 0);
|
|
1101
|
+
const p2 = sk.point(50, 0);
|
|
1102
|
+
const l1 = sk.line(p1, p2);
|
|
1103
|
+
sk.fix(p1, 0, 0);
|
|
1104
|
+
sk.horizontal(l1);
|
|
1105
|
+
sk.length(l1, 50);
|
|
1106
|
+
return sk.solve().extrude(10);
|
|
1098
1107
|
```
|
|
1099
1108
|
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
<details><summary><code>ConstrainedSketchOptions</code></summary>
|
|
1109
|
+
**Solver status**
|
|
1103
1110
|
|
|
1104
1111
|
```ts
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1112
|
+
const result = sk.solve();
|
|
1113
|
+
result.constraintMeta.status; // 'fully' | 'under' | 'over' | 'over-redundant'
|
|
1114
|
+
result.constraintMeta.dof; // 0 = fully constrained
|
|
1115
|
+
result.constraintMeta.maxError; // residual — should be < 1e-6
|
|
1116
|
+
result.inspect(); // human-readable summary
|
|
1117
|
+
result.withUpdatedConstraint('cst-5', 120); // update a dimension without rebuilding
|
|
1109
1118
|
```
|
|
1110
1119
|
|
|
1111
|
-
|
|
1120
|
+
`constrainedSketch(options?: ConstrainedSketchOptions): ConstrainedSketchBuilder`
|
|
1121
|
+
|
|
1122
|
+
**`ConstrainedSketchOptions`**
|
|
1123
|
+
- `strict?: boolean` — When true, adding a constraint that cannot be satisfied throws instead of silently discarding it.
|
|
1112
1124
|
|
|
1113
|
-
#### `line()`
|
|
1125
|
+
#### `line()` — Create an analytic 2D line segment between two points.
|
|
1126
|
+
|
|
1127
|
+
**Example**
|
|
1114
1128
|
|
|
1115
1129
|
```ts
|
|
1116
|
-
line(
|
|
1130
|
+
const l = line(0, 0, 50, 0);
|
|
1131
|
+
l.length; l.midpoint; l.angle; l.direction;
|
|
1132
|
+
l.parallel(10); // parallel line offset 10 (positive = left)
|
|
1133
|
+
l.intersect(l2); // Point2D — treats lines as infinite
|
|
1134
|
+
l.intersectSegment(l2); // Point2D or null — segments only
|
|
1135
|
+
|
|
1136
|
+
Line2D.fromPointAndAngle(point(0, 0), 45, 100);
|
|
1137
|
+
Line2D.fromPointAndDirection(point(0, 0), [1, 1], 50);
|
|
1117
1138
|
```
|
|
1118
1139
|
|
|
1119
|
-
|
|
1140
|
+
`line(x1: number, y1: number, x2: number, y2: number): Line2D`
|
|
1141
|
+
|
|
1142
|
+
#### `point()` — Create an analytic 2D point for measurement and construction geometry.
|
|
1120
1143
|
|
|
1121
|
-
|
|
1144
|
+
**Example**
|
|
1122
1145
|
|
|
1123
1146
|
```ts
|
|
1124
|
-
point(
|
|
1147
|
+
const p = point(10, 20);
|
|
1148
|
+
p.distanceTo(point(30, 40)); // Euclidean distance
|
|
1149
|
+
p.midpointTo(point(30, 40)); // midpoint
|
|
1150
|
+
p.translate(5, 5); // new shifted point
|
|
1151
|
+
p.toTuple(); // [10, 20]
|
|
1125
1152
|
```
|
|
1126
1153
|
|
|
1127
|
-
|
|
1154
|
+
`point(x: number, y: number): Point2D`
|
|
1128
1155
|
|
|
1129
1156
|
---
|
|
1130
1157
|
|
|
@@ -1140,167 +1167,155 @@ Position geometry relative to other geometry using semantic anchors.
|
|
|
1140
1167
|
|
|
1141
1168
|
Compose parts with joints for kinematic simulation.
|
|
1142
1169
|
|
|
1143
|
-
#### `assembly()`
|
|
1170
|
+
#### `assembly()` — Create an assembly container with named parts and joints for kinematic mechanisms.
|
|
1144
1171
|
|
|
1145
|
-
|
|
1146
|
-
assembly(name?: string): Assembly
|
|
1147
|
-
```
|
|
1172
|
+
**Details**
|
|
1148
1173
|
|
|
1149
|
-
|
|
1174
|
+
An assembly models a mechanism as a directed graph of parts connected by joints. Parts are the nodes; joints are directed edges from parent to child. The graph must be a forest (no cycles). Root parts (those with no incoming joint) are anchored to world space.
|
|
1150
1175
|
|
|
1151
|
-
|
|
1176
|
+
Three joint types are supported: `'revolute'` (hinge), `'prismatic'` (slider), and `'fixed'` (rigid attachment). Use `addPart()` to add geometry, `addJoint()` (or the shorthands `addRevolute()`, `addPrismatic()`, `addFixed()`) to connect parts, and `solve()` to compute world-space positions at a given joint state.
|
|
1152
1177
|
|
|
1153
|
-
|
|
1154
|
-
bomToCsv(rows: BomRow[]): string
|
|
1155
|
-
```
|
|
1178
|
+
The higher-level `connect()` API uses declared **connectors** to compute joint frames automatically. The `match()` API uses typed connectors (with gender and type metadata) for automatic compatibility validation and joint creation.
|
|
1156
1179
|
|
|
1157
|
-
|
|
1180
|
+
For multi-file assemblies, a file that returns an `Assembly` is importable via `require()` and yields an `ImportedAssembly`. Use `mergeInto()` to flatten a sub-assembly into a parent assembly.
|
|
1158
1181
|
|
|
1159
|
-
|
|
1182
|
+
**Example**
|
|
1160
1183
|
|
|
1161
1184
|
```ts
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1185
|
+
const mech = assembly("Arm")
|
|
1186
|
+
.addPart("base", box(80, 80, 20, true), {
|
|
1187
|
+
metadata: { material: "PETG", process: "FDM", qty: 1 },
|
|
1188
|
+
})
|
|
1189
|
+
.addPart("link", box(140, 24, 24).translate(0, -12, -12))
|
|
1190
|
+
.addRevolute("shoulder", "base", "link", {
|
|
1191
|
+
axis: [0, 1, 0],
|
|
1192
|
+
min: -30, max: 120, default: 25,
|
|
1193
|
+
frame: Transform.identity().translate(0, 0, 20),
|
|
1194
|
+
});
|
|
1195
|
+
|
|
1196
|
+
return mech; // auto-solved at defaults, renders all parts
|
|
1171
1197
|
```
|
|
1172
1198
|
|
|
1173
|
-
|
|
1199
|
+
`assembly(name?: string): Assembly`
|
|
1174
1200
|
|
|
1175
|
-
|
|
1201
|
+
#### `bomToCsv()` — Convert an array of BOM rows into a CSV string.
|
|
1176
1202
|
|
|
1177
|
-
|
|
1178
|
-
interface PartMetadata {
|
|
1179
|
-
material?: string;
|
|
1180
|
-
process?: string;
|
|
1181
|
-
tolerance?: string;
|
|
1182
|
-
qty?: number;
|
|
1183
|
-
notes?: string;
|
|
1184
|
-
densityKgM3?: number;
|
|
1185
|
-
massKg?: number;
|
|
1186
|
-
}
|
|
1187
|
-
```
|
|
1203
|
+
**Details**
|
|
1188
1204
|
|
|
1189
|
-
|
|
1205
|
+
Produces a CSV with columns: `part`, `qty`, `material`, `process`, `tolerance`, `notes`. String values are quoted and internal double-quotes are escaped. Prefer calling `solvedAssembly.bomCsv()` directly — this function is exposed for custom BOM processing.
|
|
1190
1206
|
|
|
1191
|
-
|
|
1207
|
+
`bomToCsv(rows: BomRow[]): string`
|
|
1192
1208
|
|
|
1193
|
-
|
|
1194
|
-
joint(name: string, shape: Shape, pivot: [ number, number, number ], opts?: RevoluteJointOpts): Shape
|
|
1195
|
-
```
|
|
1209
|
+
**`BomRow`**: `part: string`, `qty: number`, `material?: string`, `process?: string`, `tolerance?: string`, `notes?: string`, `metadata?: PartMetadata`
|
|
1196
1210
|
|
|
1197
|
-
|
|
1211
|
+
**`PartMetadata`**: `material?: string`, `process?: string`, `tolerance?: string`, `qty?: number`, `notes?: string`, `densityKgM3?: number`, `massKg?: number`
|
|
1198
1212
|
|
|
1199
|
-
|
|
1213
|
+
#### `joint()` — Create a revolute joint that auto-generates a parameter slider and rotates the shape.
|
|
1200
1214
|
|
|
1201
|
-
|
|
1202
|
-
interface RevoluteJointOpts {
|
|
1203
|
-
min?: number;
|
|
1204
|
-
max?: number;
|
|
1205
|
-
default?: number;
|
|
1206
|
-
unit?: string;
|
|
1207
|
-
reverse?: boolean;
|
|
1208
|
-
}
|
|
1209
|
-
```
|
|
1215
|
+
**Details**
|
|
1210
1216
|
|
|
1211
|
-
|
|
1217
|
+
This is a convenience wrapper for single-shape, single-joint use cases. It calls `param()` to create a named angle slider, then applies `rotateAroundAxis()` to the shape. Use the full `Assembly` API for mechanisms with multiple parts and joints.
|
|
1212
1218
|
|
|
1213
|
-
|
|
1219
|
+
**Example**
|
|
1214
1220
|
|
|
1215
1221
|
```ts
|
|
1216
|
-
|
|
1222
|
+
const arm = joint("Shoulder", armShape, [0, 0, 20], {
|
|
1223
|
+
axis: [0, 1, 0],
|
|
1224
|
+
min: -30, max: 120, default: 25,
|
|
1225
|
+
});
|
|
1226
|
+
return arm;
|
|
1217
1227
|
```
|
|
1218
1228
|
|
|
1219
|
-
|
|
1229
|
+
`joint(name: string, shape: Shape, pivot: [ number, number, number ], opts?: RevoluteJointOpts): Shape`
|
|
1220
1230
|
|
|
1221
|
-
|
|
1231
|
+
`RevoluteJointOpts`: `{ min?: number, max?: number, default?: number, unit?: string, reverse?: boolean }`
|
|
1222
1232
|
|
|
1223
|
-
|
|
1224
|
-
interface JointsViewOptions {
|
|
1225
|
-
enabled?: boolean;
|
|
1226
|
-
joints?: JointViewInput[];
|
|
1227
|
-
couplings?: JointViewCouplingInput[];
|
|
1228
|
-
animations?: JointViewAnimationInput[];
|
|
1229
|
-
defaultAnimation?: string;
|
|
1230
|
-
}
|
|
1231
|
-
```
|
|
1233
|
+
#### `jointsView()` — Register viewport-only mechanism controls that animate returned objects without re-running the script.
|
|
1232
1234
|
|
|
1233
|
-
|
|
1235
|
+
**Details**
|
|
1234
1236
|
|
|
1235
|
-
|
|
1237
|
+
Defines joints (revolute or prismatic), optional gear/rack couplings, and named animations. The viewport resolves transforms through the joint chain at display time — the script geometry is computed only once at rest pose.
|
|
1236
1238
|
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
}
|
|
1239
|
+
**Critical:** Solve the assembly at **rest pose** (all animated joints = 0). The viewport applies `jointsView` transforms on top of the returned scene. If geometry is already solved at non-zero angles, animation will double-rotate everything.
|
|
1240
|
+
|
|
1241
|
+
```js
|
|
1242
|
+
// BAD — double rotation
|
|
1243
|
+
const solved = mech.solve({ shoulder: 45, elbow: 30 });
|
|
1244
|
+
jointsView({ joints: [{ name: 'shoulder', ... }] });
|
|
1245
|
+
return solved;
|
|
1246
|
+
|
|
1247
|
+
// GOOD — rest pose, jointsView controls all posing
|
|
1248
|
+
const solved = mech.solve({ shoulder: 0, elbow: 0 });
|
|
1249
|
+
jointsView({
|
|
1250
|
+
joints: [
|
|
1251
|
+
{ name: 'shoulder', child: 'Upper Arm', default: 45, ... },
|
|
1252
|
+
{ name: 'elbow', child: 'Forearm', parent: 'Upper Arm', default: 30, ... },
|
|
1253
|
+
],
|
|
1254
|
+
});
|
|
1255
|
+
return solved;
|
|
1250
1256
|
```
|
|
1251
1257
|
|
|
1252
|
-
|
|
1258
|
+
**Pivot coordinates** are world-space positions of each joint origin at rest pose. For `addRevolute('shoulder', 'Base', 'Link', { frame: Transform.identity().translate(0, 0, 20) })` where "Base" is at world origin, the pivot is `[0, 0, 20]`.
|
|
1253
1259
|
|
|
1254
|
-
|
|
1260
|
+
**Fixed attachments** that must follow a parent during animation need a zero-angle revolute joint in the chain:
|
|
1255
1261
|
|
|
1256
|
-
```
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
offset?: number;
|
|
1261
|
-
}
|
|
1262
|
+
```js
|
|
1263
|
+
{ name: 'EE_Follow', child: 'End Effector', parent: 'Last Link',
|
|
1264
|
+
type: 'revolute', axis: [0, 0, 1], pivot: [linkLength, 0, 0],
|
|
1265
|
+
min: 0, max: 0, default: 0 }
|
|
1262
1266
|
```
|
|
1263
1267
|
|
|
1264
|
-
|
|
1268
|
+
Animation values are interpolated linearly between keyframes. ForgeCAD does **not** auto-wrap revolute values across `-180/180`. Keep keyframe values continuous — a `-180 -> 171` jump spins the part the long way around. Use `-180 -> -189` instead. Author high-speed multi-turn joints as accumulating angles (`0, 360, 720, ...`) with `continuous: true`.
|
|
1265
1269
|
|
|
1266
|
-
|
|
1270
|
+
**Tick-based keyframes:** Omit `at` from all keyframes to auto-distribute by tick weight:
|
|
1267
1271
|
|
|
1268
|
-
```
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
}
|
|
1272
|
+
```js
|
|
1273
|
+
keyframes: [
|
|
1274
|
+
{ ticks: 3, values: { Shoulder: 20 } }, // slow segment (3x weight)
|
|
1275
|
+
{ ticks: 1, values: { Shoulder: -10 } }, // fast segment (1x weight)
|
|
1276
|
+
{ values: { Shoulder: 20 } }, // last keyframe; ticks ignored
|
|
1277
|
+
]
|
|
1278
|
+
// positions: 0, 0.75, 1.0
|
|
1273
1279
|
```
|
|
1274
1280
|
|
|
1275
|
-
|
|
1281
|
+
Mixing explicit `at` and omitted `at` in the same animation is not allowed.
|
|
1276
1282
|
|
|
1277
|
-
|
|
1283
|
+
**Example**
|
|
1278
1284
|
|
|
1279
|
-
```
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1285
|
+
```js
|
|
1286
|
+
jointsView({
|
|
1287
|
+
joints: [{
|
|
1288
|
+
name: 'Shoulder', child: 'Upper Arm', parent: 'Base',
|
|
1289
|
+
type: 'revolute', axis: [0, -1, 0], pivot: [0, 0, 46],
|
|
1290
|
+
min: -30, max: 110, default: 15,
|
|
1291
|
+
}],
|
|
1292
|
+
animations: [{
|
|
1293
|
+
name: 'Walk Cycle', duration: 1.6, loop: true,
|
|
1294
|
+
keyframes: [
|
|
1295
|
+
{ values: { Shoulder: 20 } },
|
|
1296
|
+
{ values: { Shoulder: -10 } },
|
|
1297
|
+
{ values: { Shoulder: 20 } },
|
|
1298
|
+
],
|
|
1299
|
+
}],
|
|
1300
|
+
});
|
|
1287
1301
|
```
|
|
1288
1302
|
|
|
1289
|
-
|
|
1303
|
+
`jointsView(options?: JointsViewOptions): void`
|
|
1290
1304
|
|
|
1291
|
-
|
|
1305
|
+
**`JointsViewOptions`**: `enabled?: boolean`, `joints?: JointViewInput[]`, `couplings?: JointViewCouplingInput[]`, `animations?: JointViewAnimationInput[]`, `defaultAnimation?: string`
|
|
1292
1306
|
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
}
|
|
1301
|
-
```
|
|
1307
|
+
**`JointViewInput`**: `name: string`, `child: string`, `parent?: string`, `type?: JointViewType`, `axis?: JointViewAxis`, `min?: number`, `max?: number`, `default?: number`, `unit?: string`, `hidden?: boolean`
|
|
1308
|
+
|
|
1309
|
+
`JointViewCouplingInput`: `{ joint: string, terms: JointViewCouplingTermInput[], offset?: number }`
|
|
1310
|
+
|
|
1311
|
+
`JointViewCouplingTermInput`: `{ joint: string, ratio?: number }`
|
|
1312
|
+
|
|
1313
|
+
`JointViewAnimationInput`: `{ name: string, duration?: number, loop?: boolean, continuous?: boolean, keyframes: JointViewAnimationKeyframeInput[] }`
|
|
1302
1314
|
|
|
1303
|
-
|
|
1315
|
+
**`JointViewAnimationKeyframeInput`**
|
|
1316
|
+
- `at?: number` — Timeline position [0, 1]. If omitted from ALL keyframes, positions are auto-computed from tick weights.
|
|
1317
|
+
- `ticks?: number` — Relative weight of the segment from this keyframe to the next (default 1). Only used in tick-based mode (when `at` is omitted). Last keyframe's ticks value is ignored.
|
|
1318
|
+
- Also: `values: Record<string, number>`
|
|
1304
1319
|
|
|
1305
1320
|
---
|
|
1306
1321
|
|
|
@@ -1308,784 +1323,590 @@ interface JointViewAnimationKeyframeInput {
|
|
|
1308
1323
|
|
|
1309
1324
|
Declare user-facing controls that drive model geometry.
|
|
1310
1325
|
|
|
1311
|
-
#### `boolParam()`
|
|
1326
|
+
#### `boolParam()` — Declare a boolean parameter that renders as a checkbox in the UI.
|
|
1312
1327
|
|
|
1313
|
-
|
|
1314
|
-
boolParam(name: string, defaultValue: boolean): boolean
|
|
1315
|
-
```
|
|
1328
|
+
**Details**
|
|
1316
1329
|
|
|
1317
|
-
|
|
1330
|
+
Internally stored as `0`/`1`. When overriding from CLI or `require()`, pass `1` for true and `0` for false. The `name` string is the override key.
|
|
1318
1331
|
|
|
1319
|
-
|
|
1332
|
+
**Example**
|
|
1320
1333
|
|
|
1321
1334
|
```ts
|
|
1322
|
-
|
|
1335
|
+
const showHoles = boolParam("Show Holes", true);
|
|
1336
|
+
if (showHoles) return difference(plate, cylinder(10, 5).translate(50, 30, 0));
|
|
1337
|
+
return plate;
|
|
1323
1338
|
```
|
|
1324
1339
|
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
#### `listParam()`
|
|
1340
|
+
Override via import:
|
|
1328
1341
|
|
|
1329
1342
|
```ts
|
|
1330
|
-
|
|
1343
|
+
const pan = require("./pan.forge.js", { "Show Lid": 0 });
|
|
1331
1344
|
```
|
|
1332
1345
|
|
|
1333
|
-
|
|
1346
|
+
`boolParam(name: string, defaultValue: boolean): boolean`
|
|
1334
1347
|
|
|
1335
|
-
|
|
1348
|
+
#### `choiceParam()` — Declare a choice parameter that renders as a dropdown in the UI.
|
|
1336
1349
|
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
max?: number;
|
|
1341
|
-
step?: number;
|
|
1342
|
-
unit?: string;
|
|
1343
|
-
integer?: boolean;
|
|
1344
|
-
boolean?: boolean;
|
|
1345
|
-
choices?: string[];
|
|
1346
|
-
}
|
|
1347
|
-
```
|
|
1350
|
+
**Details**
|
|
1351
|
+
|
|
1352
|
+
`defaultValue` must exactly match one entry in `choices`. Returns the selected string label. Prefer `choiceParam` over `param` when a slider would hide intent — named choices like `"wok"` are self-describing.
|
|
1348
1353
|
|
|
1349
|
-
|
|
1354
|
+
Overrides may be passed as the choice label string (preferred) or as a numeric index. The `name` string is the override key.
|
|
1350
1355
|
|
|
1351
|
-
|
|
1356
|
+
**Example**
|
|
1352
1357
|
|
|
1353
1358
|
```ts
|
|
1354
|
-
|
|
1359
|
+
const panStyle = choiceParam("Pan Style", "frying-pan", ["frying-pan", "saute-pan", "wok"]);
|
|
1360
|
+
if (panStyle === "wok") return buildWok();
|
|
1355
1361
|
```
|
|
1356
1362
|
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
#### `dim()`
|
|
1363
|
+
Override via import:
|
|
1360
1364
|
|
|
1361
1365
|
```ts
|
|
1362
|
-
|
|
1366
|
+
const pan = require("./pan.forge.js", { "Pan Style": "wok" });
|
|
1363
1367
|
```
|
|
1364
1368
|
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
<details><summary><code>DimOpts</code></summary>
|
|
1369
|
+
Override via CLI:
|
|
1368
1370
|
|
|
1369
|
-
```
|
|
1370
|
-
|
|
1371
|
-
offset?: number;
|
|
1372
|
-
label?: string;
|
|
1373
|
-
color?: string;
|
|
1374
|
-
component?: string | string[];
|
|
1375
|
-
currentComponent?: boolean;
|
|
1376
|
-
}
|
|
1371
|
+
```bash
|
|
1372
|
+
forgecad run model.forge.js --param "Pan Style=wok"
|
|
1377
1373
|
```
|
|
1378
1374
|
|
|
1379
|
-
|
|
1375
|
+
`choiceParam(name: string, defaultValue: string, choices: string[]): string`
|
|
1380
1376
|
|
|
1381
|
-
#### `
|
|
1377
|
+
#### `listParam()` — Declare a list parameter — an array of struct items with per-field UI controls.
|
|
1382
1378
|
|
|
1383
|
-
|
|
1384
|
-
dimLine(l: Line2D, opts?: DimOpts): void
|
|
1385
|
-
```
|
|
1379
|
+
**Details**
|
|
1386
1380
|
|
|
1387
|
-
|
|
1381
|
+
Each item in the list is a struct whose fields each render as their own control (slider, checkbox, or dropdown). The user can add/remove rows up to `minItems`/`maxItems` bounds.
|
|
1388
1382
|
|
|
1389
|
-
|
|
1383
|
+
Field types: - Boolean fields (`boolean: true` in field defs) return as `boolean` - Choice fields (`choices: [...]` in field defs) return as `string` - All other fields return as `number`
|
|
1390
1384
|
|
|
1391
|
-
|
|
1385
|
+
`listParam<T extends Record<string, number | boolean | string>>(name: string, defaultItems: T[], opts: { ... }): T[]`
|
|
1392
1386
|
|
|
1393
|
-
|
|
1387
|
+
`ListParamFieldDef`: `{ min?: number, max?: number, step?: number, unit?: string, integer?: boolean, boolean?: boolean, choices?: string[] }`
|
|
1394
1388
|
|
|
1395
|
-
#### `
|
|
1389
|
+
#### `param()` — Declare a numeric parameter that renders as a slider in the UI.
|
|
1396
1390
|
|
|
1397
|
-
|
|
1398
|
-
faceProfile(shape: Shape, face: FaceSelector): Sketch
|
|
1399
|
-
```
|
|
1391
|
+
**Details**
|
|
1400
1392
|
|
|
1401
|
-
|
|
1393
|
+
Each `param()` call registers a slider control. When the user moves the slider the entire script re-executes with the new value. Parameter values are also overridable from `require()` imports or the CLI `--param` flag — the `name` string is the key used in both cases.
|
|
1402
1394
|
|
|
1403
|
-
|
|
1404
|
-
intersectWithPlane(shape: Shape, plane: PlaneSpec): Sketch
|
|
1405
|
-
```
|
|
1395
|
+
Default range rules when options are omitted: - `min` defaults to `0` - `max` defaults to `defaultValue * 4` - `step` is auto-calculated: `1` for integer params, `0.1` for ranges ≤ 100, `1` for larger ranges
|
|
1406
1396
|
|
|
1407
|
-
|
|
1397
|
+
The `unit` option is cosmetic only — no conversion is performed. Use `integer: true` for counts, sides, quantities (rounds to whole numbers; step defaults to `1`).
|
|
1408
1398
|
|
|
1409
|
-
|
|
1399
|
+
**Example**
|
|
1410
1400
|
|
|
1411
1401
|
```ts
|
|
1412
|
-
|
|
1402
|
+
const width = param("Width", 50);
|
|
1403
|
+
const angle = param("Angle", 45, { min: 0, max: 180, unit: "°" });
|
|
1404
|
+
const sides = param("Sides", 6, { min: 3, max: 12, integer: true });
|
|
1413
1405
|
```
|
|
1414
1406
|
|
|
1415
|
-
|
|
1407
|
+
**Parameter overrides** — key must match `name` exactly:
|
|
1416
1408
|
|
|
1417
|
-
|
|
1409
|
+
```ts
|
|
1410
|
+
// Via require()
|
|
1411
|
+
const bracket = require("./bracket.forge.js", { Width: 80 });
|
|
1418
1412
|
|
|
1419
|
-
|
|
1413
|
+
// Via CLI
|
|
1414
|
+
// forgecad run model.forge.js --param "Wall Thickness=3"
|
|
1415
|
+
```
|
|
1420
1416
|
|
|
1421
|
-
|
|
1417
|
+
`param(name: string, defaultValue: number, opts?: { min?: number; max?: number; step?: number; unit?: string; integer?: boolean; reverse?: boolean; }): number`
|
|
1422
1418
|
|
|
1423
|
-
#### `
|
|
1419
|
+
#### `dim()` — Add a dimension annotation between two points.
|
|
1424
1420
|
|
|
1425
|
-
|
|
1426
|
-
bom(quantity: number, description: string, opts?: BomOpts): void
|
|
1427
|
-
```
|
|
1421
|
+
**Details**
|
|
1428
1422
|
|
|
1429
|
-
|
|
1423
|
+
Dimension annotations are purely visual callouts rendered in the viewport and report export. They do not affect geometry or constrain the model.
|
|
1430
1424
|
|
|
1431
|
-
|
|
1425
|
+
Point arguments accept 2D tuples `[x, y]`, 3D tuples `[x, y, z]`, or `Point2D` objects (Z is treated as 0 for 2D inputs).
|
|
1432
1426
|
|
|
1433
|
-
|
|
1434
|
-
interface BomOpts {
|
|
1435
|
-
/** Quantity unit label, e.g. "mm", "pieces", "kg". Default: "pieces" */
|
|
1436
|
-
unit?: string;
|
|
1437
|
-
/** Optional explicit grouping key used during report aggregation. */
|
|
1438
|
-
key?: string;
|
|
1439
|
-
}
|
|
1440
|
-
```
|
|
1427
|
+
**Ownership Rules (Report Pages)**
|
|
1441
1428
|
|
|
1442
|
-
|
|
1429
|
+
- `currentComponent: true` — deterministic ownership by the calling import instance. Use when authoring reusable imported parts. - `component: "Part Name"` — route dimension to another named returned object. - Multiple owners: dimension is shared and appears on the assembly overview page. - No ownership set: report export infers ownership via endpoint-in-bbox.
|
|
1443
1430
|
|
|
1444
|
-
|
|
1431
|
+
**Example**
|
|
1445
1432
|
|
|
1446
1433
|
```ts
|
|
1447
|
-
|
|
1434
|
+
dim([-w / 2, 0, 0], [w / 2, 0, 0], { label: "Width" });
|
|
1435
|
+
dim([0, 0, -h / 2], [0, 0, h / 2], { label: "Height", offset: 14 });
|
|
1436
|
+
dim([0, 0, 0], [100, 0, 0], { component: "Base", color: "#00AAFF" });
|
|
1448
1437
|
```
|
|
1449
1438
|
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
<details><summary><code>RobotExportOptions</code></summary>
|
|
1439
|
+
`component` (string or string[] — report ownership), `currentComponent` (boolean)
|
|
1453
1440
|
|
|
1454
|
-
|
|
1455
|
-
interface RobotExportOptions {
|
|
1456
|
-
assembly: Assembly;
|
|
1457
|
-
modelName?: string;
|
|
1458
|
-
state?: JointState;
|
|
1459
|
-
static?: boolean;
|
|
1460
|
-
selfCollide?: boolean;
|
|
1461
|
-
allowAutoDisable?: boolean;
|
|
1462
|
-
links?: Record<string, RobotLinkExportOptions>;
|
|
1463
|
-
joints?: Record<string, RobotJointExportOptions>;
|
|
1464
|
-
diffDrive?: RobotDiffDrivePluginOptions;
|
|
1465
|
-
jointStatePublisher?: RobotJointStatePublisherOptions;
|
|
1466
|
-
world?: RobotWorldOptions;
|
|
1467
|
-
}
|
|
1468
|
-
```
|
|
1441
|
+
`dim(from: PointArg$1, to: PointArg$1, opts?: DimOpts): void`
|
|
1469
1442
|
|
|
1470
|
-
|
|
1443
|
+
`DimOpts`: `{ offset?: number, label?: string, color?: string, component?: string | string[], currentComponent?: boolean }`
|
|
1471
1444
|
|
|
1472
|
-
|
|
1445
|
+
#### `dimLine()` — Add a dimension annotation along a `Line2D`.
|
|
1473
1446
|
|
|
1474
|
-
|
|
1475
|
-
interface RobotLinkExportOptions {
|
|
1476
|
-
massKg?: number;
|
|
1477
|
-
densityKgM3?: number;
|
|
1478
|
-
collision?: "visual" | "convex" | "box" | "none";
|
|
1479
|
-
}
|
|
1480
|
-
```
|
|
1447
|
+
**Details**
|
|
1481
1448
|
|
|
1482
|
-
|
|
1449
|
+
Convenience wrapper around { points from a constrained-sketch `Line2D` entity. All `opts` are forwarded unchanged.
|
|
1483
1450
|
|
|
1484
|
-
|
|
1451
|
+
**Example**
|
|
1485
1452
|
|
|
1486
1453
|
```ts
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
damping?: number;
|
|
1491
|
-
friction?: number;
|
|
1492
|
-
}
|
|
1454
|
+
const a = point(0, 0);
|
|
1455
|
+
const b = point(100, 0);
|
|
1456
|
+
dimLine(line(a, b), { label: "Span", offset: -8 });
|
|
1493
1457
|
```
|
|
1494
1458
|
|
|
1495
|
-
|
|
1459
|
+
`dimLine(l: Line2D, opts?: DimOpts): void`
|
|
1496
1460
|
|
|
1497
|
-
|
|
1461
|
+
---
|
|
1498
1462
|
|
|
1499
|
-
|
|
1500
|
-
interface RobotDiffDrivePluginOptions {
|
|
1501
|
-
leftJoints: string[];
|
|
1502
|
-
rightJoints: string[];
|
|
1503
|
-
wheelSeparationMm: number;
|
|
1504
|
-
wheelRadiusMm: number;
|
|
1505
|
-
topic?: string;
|
|
1506
|
-
odomTopic?: string;
|
|
1507
|
-
tfTopic?: string;
|
|
1508
|
-
frameId?: string;
|
|
1509
|
-
odomFrameId?: string;
|
|
1510
|
-
maxLinearVelocity?: number;
|
|
1511
|
-
maxAngularVelocity?: number;
|
|
1512
|
-
linearAcceleration?: number;
|
|
1513
|
-
angularAcceleration?: number;
|
|
1514
|
-
}
|
|
1515
|
-
```
|
|
1463
|
+
## C12: Dimensional Demotion
|
|
1516
1464
|
|
|
1517
|
-
|
|
1465
|
+
Extract 2D geometry from a 3D solid (section, projection).
|
|
1518
1466
|
|
|
1519
|
-
|
|
1467
|
+
#### `faceProfile()` — Extract the boundary profile of a named face as a 2D sketch.
|
|
1520
1468
|
|
|
1521
|
-
|
|
1522
|
-
interface RobotJointStatePublisherOptions {
|
|
1523
|
-
enabled?: boolean;
|
|
1524
|
-
joints?: string[];
|
|
1525
|
-
topic?: string;
|
|
1526
|
-
updateRate?: number;
|
|
1527
|
-
}
|
|
1528
|
-
```
|
|
1469
|
+
The result is returned in the face's local 2D coordinate system, making it convenient for offsets, pocket profiles, or follow-up sketch operations driven by an existing face.
|
|
1529
1470
|
|
|
1530
|
-
|
|
1471
|
+
`faceProfile(shape: Shape, face: FaceSelector): Sketch`
|
|
1531
1472
|
|
|
1532
|
-
|
|
1473
|
+
#### `intersectWithPlane()` — Cross-section: slice a 3D shape with a plane and return the intersection as a 2D Sketch.
|
|
1533
1474
|
|
|
1534
|
-
|
|
1535
|
-
interface RobotWorldOptions {
|
|
1536
|
-
name?: string;
|
|
1537
|
-
generateDemoWorld?: boolean;
|
|
1538
|
-
spawnPose?: RobotPose6;
|
|
1539
|
-
keyboardTeleop?: RobotWorldKeyboardTeleopOptions;
|
|
1540
|
-
}
|
|
1541
|
-
```
|
|
1475
|
+
`intersectWithPlane(shape: Shape, plane: PlaneSpec): Sketch`
|
|
1542
1476
|
|
|
1543
|
-
|
|
1477
|
+
#### `projectToPlane()` — Orthographically project a 3D shape onto a plane and return the silhouette as a 2D Sketch.
|
|
1544
1478
|
|
|
1545
|
-
|
|
1479
|
+
`projectToPlane(shape: Shape, plane: PlaneSpec): Sketch`
|
|
1546
1480
|
|
|
1547
|
-
|
|
1548
|
-
interface RobotWorldKeyboardTeleopOptions {
|
|
1549
|
-
enabled?: boolean;
|
|
1550
|
-
linearStep?: number;
|
|
1551
|
-
angularStep?: number;
|
|
1552
|
-
}
|
|
1553
|
-
```
|
|
1481
|
+
---
|
|
1554
1482
|
|
|
1555
|
-
|
|
1483
|
+
## C13: Export & Output
|
|
1556
1484
|
|
|
1557
|
-
|
|
1485
|
+
Convert geometry to external formats (STL, 3MF, SVG, DXF, G-code, PDF).
|
|
1558
1486
|
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
static: boolean;
|
|
1565
|
-
selfCollide: boolean;
|
|
1566
|
-
allowAutoDisable: boolean;
|
|
1567
|
-
links: Record<string, RobotLinkExportOptions>;
|
|
1568
|
-
joints: Record<string, RobotJointExportOptions>;
|
|
1569
|
-
diffDrive?: RobotDiffDrivePluginOptions;
|
|
1570
|
-
jointStatePublisher?: RobotJointStatePublisherOptions;
|
|
1571
|
-
world: RobotWorldOptions | null;
|
|
1572
|
-
}
|
|
1573
|
-
```
|
|
1487
|
+
#### `bom()` — Register a Bill of Materials entry for report export.
|
|
1488
|
+
|
|
1489
|
+
**Details**
|
|
1490
|
+
|
|
1491
|
+
BOM entries are accumulated during script execution and exported alongside the model in report views. Rows are grouped by normalized `description + unit`. Pass an explicit `key` to force multiple descriptions to collapse into a single line item.
|
|
1574
1492
|
|
|
1575
|
-
|
|
1493
|
+
- `quantity` must be a finite number `>= 0`. A quantity of `0` is silently ignored (useful for conditional scripting with `param()`-driven counts). - `unit` defaults to `"pieces"` when omitted or empty. - The assembly `solved.bom()` / `solved.bomCsv()` API is separate and covers per-part assembly metadata; this function is for free-form purchased-item annotation.
|
|
1576
1494
|
|
|
1577
|
-
|
|
1495
|
+
**Example**
|
|
1578
1496
|
|
|
1579
1497
|
```ts
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
}
|
|
1498
|
+
const tubeLen = param("Tube Length", 1200, { min: 300, max: 4000, unit: "mm" });
|
|
1499
|
+
const boltCount = param("Bolt Count", 16, { min: 0, max: 200, integer: true });
|
|
1500
|
+
|
|
1501
|
+
bom(tubeLen, "iron tube 30 x 20", { unit: "mm" });
|
|
1502
|
+
bom(boltCount, "M4 bolt, 16 mm length");
|
|
1503
|
+
bom(4, "rubber foot", { key: "foot-rubber" }); // explicit aggregation key
|
|
1586
1504
|
```
|
|
1587
1505
|
|
|
1588
|
-
|
|
1506
|
+
`bom(quantity: number, description: string, opts?: BomOpts): void`
|
|
1589
1507
|
|
|
1590
|
-
|
|
1508
|
+
**`BomOpts`**
|
|
1509
|
+
- `unit?: string` — Quantity unit label, e.g. "mm", "pieces", "kg". Default: "pieces"
|
|
1510
|
+
- `key?: string` — Optional explicit grouping key used during report aggregation.
|
|
1591
1511
|
|
|
1592
|
-
|
|
1593
|
-
interface AssemblyPartDef {
|
|
1594
|
-
name: string;
|
|
1595
|
-
part: AssemblyPart;
|
|
1596
|
-
base: Transform;
|
|
1597
|
-
metadata?: PartMetadata;
|
|
1598
|
-
}
|
|
1599
|
-
```
|
|
1512
|
+
#### `robotExport()` — Declare that this script should export the assembly as a SDF/URDF robot package.
|
|
1600
1513
|
|
|
1601
|
-
|
|
1514
|
+
**Details**
|
|
1602
1515
|
|
|
1603
|
-
|
|
1516
|
+
Call `robotExport()` alongside your assembly definition. The CLI commands `forgecad export sdf` and `forgecad export urdf` pick up the declaration and produce a robot package with: - Mesh-based inertia tensors (full 6-component, not bounding-box approximations) - Separate collision meshes (convex hull by default — ~50–80% smaller) - Joint mimic elements derived from `addJointCoupling` / `addGearCoupling`
|
|
1604
1517
|
|
|
1605
|
-
|
|
1606
|
-
interface PartMetadata {
|
|
1607
|
-
material?: string;
|
|
1608
|
-
process?: string;
|
|
1609
|
-
tolerance?: string;
|
|
1610
|
-
qty?: number;
|
|
1611
|
-
notes?: string;
|
|
1612
|
-
densityKgM3?: number;
|
|
1613
|
-
massKg?: number;
|
|
1614
|
-
}
|
|
1615
|
-
```
|
|
1518
|
+
**Collision mesh modes** (set per-link via `links["PartName"].collision`):
|
|
1616
1519
|
|
|
1617
|
-
|
|
1520
|
+
| Mode | Description | Default | |------|-------------|---------| | `'convex'` | Convex hull (separate `_collision.stl`) | Yes | | `'box'` | AABB primitive — fastest physics | | | `'visual'` | Same mesh as visual — exact but slow | | | `'none'` | No collision geometry | |
|
|
1618
1521
|
|
|
1619
|
-
|
|
1522
|
+
**Unit conventions:** - Revolute `velocity` is in degrees/second in Forge; exporters convert to rad/s. - Prismatic distances are in mm in Forge; exported in meters. - `massKg` is preferred; `densityKgM3` is used when mass is unknown. - Couplings with multiple terms: only the primary term (largest ratio) maps to `<mimic>` — SDF/URDF support single-leader mimic only. Dropped terms emit a warning.
|
|
1523
|
+
|
|
1524
|
+
**Example**
|
|
1620
1525
|
|
|
1621
1526
|
```ts
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
max?: number;
|
|
1631
|
-
defaultValue: number;
|
|
1632
|
-
unit?: string;
|
|
1633
|
-
effort?: number;
|
|
1634
|
-
velocity?: number;
|
|
1635
|
-
damping?: number;
|
|
1636
|
-
friction?: number;
|
|
1637
|
-
}
|
|
1638
|
-
```
|
|
1527
|
+
const rover = assembly("Scout")
|
|
1528
|
+
.addPart("Chassis", box(300, 220, 50, true))
|
|
1529
|
+
.addPart("Left Wheel", cylinder(30, 60, undefined, 48, true))
|
|
1530
|
+
.addRevolute("leftWheel", "Chassis", "Left Wheel", {
|
|
1531
|
+
axis: [0, 1, 0],
|
|
1532
|
+
frame: Transform.identity().translate(90, 140, 60),
|
|
1533
|
+
effort: 20, velocity: 1080,
|
|
1534
|
+
});
|
|
1639
1535
|
|
|
1640
|
-
|
|
1536
|
+
robotExport({
|
|
1537
|
+
assembly: rover,
|
|
1538
|
+
modelName: "Scout",
|
|
1539
|
+
links: {
|
|
1540
|
+
Chassis: { massKg: 10 },
|
|
1541
|
+
"Left Wheel": { massKg: 0.8 },
|
|
1542
|
+
},
|
|
1543
|
+
plugins: {
|
|
1544
|
+
diffDrive: {
|
|
1545
|
+
leftJoints: ["leftWheel"], rightJoints: ["rightWheel"],
|
|
1546
|
+
wheelSeparationMm: 280, wheelRadiusMm: 60,
|
|
1547
|
+
},
|
|
1548
|
+
},
|
|
1549
|
+
world: { generateDemoWorld: true },
|
|
1550
|
+
});
|
|
1551
|
+
```
|
|
1641
1552
|
|
|
1642
|
-
|
|
1553
|
+
**CLI usage**
|
|
1643
1554
|
|
|
1644
|
-
```
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
terms: JointCouplingTermRecord[];
|
|
1648
|
-
offset: number;
|
|
1649
|
-
}
|
|
1555
|
+
```bash
|
|
1556
|
+
forgecad export sdf model.forge.js # SDF package (Gazebo/Ignition)
|
|
1557
|
+
forgecad export urdf model.forge.js # URDF package (ROS/PyBullet/MuJoCo)
|
|
1650
1558
|
```
|
|
1651
1559
|
|
|
1652
|
-
|
|
1560
|
+
`robotExport(options: RobotExportOptions): CollectedRobotExport`
|
|
1653
1561
|
|
|
1654
|
-
<
|
|
1562
|
+
**`RobotExportOptions`**: `assembly: Assembly`, `modelName?: string`, `state?: JointState`, `static?: boolean`, `selfCollide?: boolean`, `allowAutoDisable?: boolean`, `links?: Record<string, RobotLinkExportOptions>`, `joints?: Record<string, RobotJointExportOptions>`, `diffDrive?: RobotDiffDrivePluginOptions`, `jointStatePublisher?: RobotJointStatePublisherOptions`, `world?: RobotWorldOptions`
|
|
1655
1563
|
|
|
1656
|
-
|
|
1657
|
-
interface JointCouplingTermRecord {
|
|
1658
|
-
joint: string;
|
|
1659
|
-
ratio: number;
|
|
1660
|
-
}
|
|
1661
|
-
```
|
|
1564
|
+
`RobotLinkExportOptions`: `{ massKg?: number, densityKgM3?: number, collision?: "visual" | "convex" | "box" | "none" }`
|
|
1662
1565
|
|
|
1663
|
-
|
|
1566
|
+
`RobotJointExportOptions`: `{ effort?: number, velocity?: number, damping?: number, friction?: number }`
|
|
1664
1567
|
|
|
1665
|
-
|
|
1568
|
+
**`RobotDiffDrivePluginOptions`**: `leftJoints: string[]`, `rightJoints: string[]`, `wheelSeparationMm: number`, `wheelRadiusMm: number`, `topic?: string`, `odomTopic?: string`, `tfTopic?: string`, `frameId?: string`, `odomFrameId?: string`, `maxLinearVelocity?: number`, `maxAngularVelocity?: number`, `linearAcceleration?: number`, `angularAcceleration?: number`
|
|
1666
1569
|
|
|
1667
|
-
|
|
1668
|
-
sheetMetal(options: SheetMetalOptions): SheetMetalPart
|
|
1669
|
-
```
|
|
1570
|
+
`RobotJointStatePublisherOptions`: `{ enabled?: boolean, joints?: string[], topic?: string, updateRate?: number }`
|
|
1670
1571
|
|
|
1671
|
-
|
|
1572
|
+
`RobotWorldOptions`: `{ name?: string, generateDemoWorld?: boolean, spawnPose?: RobotPose6, keyboardTeleop?: RobotWorldKeyboardTeleopOptions }`
|
|
1672
1573
|
|
|
1673
|
-
|
|
1574
|
+
`RobotWorldKeyboardTeleopOptions`: `{ enabled?: boolean, linearStep?: number, angularStep?: number }`
|
|
1674
1575
|
|
|
1675
|
-
|
|
1676
|
-
interface SheetMetalOptions {
|
|
1677
|
-
width: number;
|
|
1678
|
-
height: number;
|
|
1679
|
-
thickness: number;
|
|
1680
|
-
bendRadius: number;
|
|
1681
|
-
kFactor: number;
|
|
1682
|
-
kind?: "rect";
|
|
1683
|
-
size: number;
|
|
1684
|
-
}
|
|
1685
|
-
```
|
|
1576
|
+
**`CollectedRobotExport`**: `modelName: string`, `assembly: AssemblyDefinition`, `state: JointState`, `static: boolean`, `selfCollide: boolean`, `allowAutoDisable: boolean`, `links: Record<string, RobotLinkExportOptions>`, `joints: Record<string, RobotJointExportOptions>`, `diffDrive?: RobotDiffDrivePluginOptions`, `jointStatePublisher?: RobotJointStatePublisherOptions`, `world: RobotWorldOptions | null`
|
|
1686
1577
|
|
|
1687
|
-
|
|
1578
|
+
`AssemblyDefinition`: `{ name: string, parts: AssemblyPartDef[], joints: AssemblyJointDef[], jointCouplings: AssemblyJointCouplingDef[] }`
|
|
1688
1579
|
|
|
1689
|
-
|
|
1580
|
+
`AssemblyPartDef`: `{ name: string, part: AssemblyPart, base: Transform, metadata?: PartMetadata }`
|
|
1690
1581
|
|
|
1691
|
-
|
|
1692
|
-
sketchToDxf(sketch: Sketch, options?: SketchDxfOptions): string
|
|
1693
|
-
```
|
|
1582
|
+
**`PartMetadata`**: `material?: string`, `process?: string`, `tolerance?: string`, `qty?: number`, `notes?: string`, `densityKgM3?: number`, `massKg?: number`
|
|
1694
1583
|
|
|
1695
|
-
|
|
1584
|
+
**`AssemblyJointDef`**: `name: string`, `type: JointType`, `parent: string`, `child: string`, `frame: Transform`, `axis: Vec3`, `min?: number`, `max?: number`, `defaultValue: number`, `unit?: string`, `effort?: number`, `velocity?: number`, `damping?: number`, `friction?: number`
|
|
1696
1585
|
|
|
1697
|
-
|
|
1586
|
+
`AssemblyJointCouplingDef`: `{ joint: string, terms: JointCouplingTermRecord[], offset: number }`
|
|
1698
1587
|
|
|
1699
|
-
|
|
1700
|
-
interface SketchDxfOptions {
|
|
1701
|
-
/** DXF layer name. Default: "0" */
|
|
1702
|
-
layer?: string;
|
|
1703
|
-
/** DXF color index (1–255, AutoCAD ACI). Default: 7 (white/black) */
|
|
1704
|
-
colorIndex?: number;
|
|
1705
|
-
}
|
|
1706
|
-
```
|
|
1588
|
+
`JointCouplingTermRecord`: `{ joint: string, ratio: number }`
|
|
1707
1589
|
|
|
1708
|
-
|
|
1590
|
+
#### `sheetMetal()` — Create a parametric sheet metal part with flanges, bend allowances, and flat-pattern unfolding.
|
|
1709
1591
|
|
|
1710
|
-
|
|
1592
|
+
**Details**
|
|
1711
1593
|
|
|
1712
|
-
|
|
1713
|
-
sketchToSvg(sketch: Sketch, options?: SketchSvgOptions): string
|
|
1714
|
-
```
|
|
1594
|
+
`sheetMetal()` keeps one semantic model and derives both a folded 3D solid and an accurate flat pattern from it. The K-factor bend allowance is applied during unfolding. This is a strict v1 subset — it does not infer sheet metal from arbitrary solids.
|
|
1715
1595
|
|
|
1716
|
-
|
|
1596
|
+
**Recommended authoring order:** 1. Define the base panel + thickness + bend parameters. 2. Chain `.flange()` calls for each edge. Validate with `.folded()` and `.flatPattern()` before adding cutouts. 3. Add panel cutouts, then flange cutouts one region at a time. 4. Validate after each new cutout region.
|
|
1717
1597
|
|
|
1718
|
-
|
|
1598
|
+
**v1 limitations:** one base panel, up to four 90° edge flanges, constant thickness, explicit K-factor, rectangular corner reliefs, planar cutouts only. No hems, jogs, lofted bends, non-90° flanges, or bend-region cutouts.
|
|
1599
|
+
|
|
1600
|
+
**Example**
|
|
1719
1601
|
|
|
1720
1602
|
```ts
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
}
|
|
1603
|
+
const cover = sheetMetal({
|
|
1604
|
+
panel: { width: 180, height: 110 },
|
|
1605
|
+
thickness: 1.5,
|
|
1606
|
+
bendRadius: 2,
|
|
1607
|
+
bendAllowance: { kFactor: 0.42 },
|
|
1608
|
+
cornerRelief: { size: 4 },
|
|
1609
|
+
})
|
|
1610
|
+
.flange('top', { length: 18 })
|
|
1611
|
+
.flange('right', { length: 18 })
|
|
1612
|
+
.flange('bottom', { length: 18 })
|
|
1613
|
+
.flange('left', { length: 18 })
|
|
1614
|
+
.cutout('panel', rect(72, 36, true), { selfAnchor: 'center' })
|
|
1615
|
+
.cutout('flange-right', roundedRect(26, 10, 5, true), { selfAnchor: 'center' });
|
|
1616
|
+
|
|
1617
|
+
const folded = cover.folded();
|
|
1618
|
+
const flat = cover.flatPattern();
|
|
1733
1619
|
```
|
|
1734
1620
|
|
|
1735
|
-
|
|
1621
|
+
`sheetMetal(options: SheetMetalOptions): SheetMetalPart`
|
|
1736
1622
|
|
|
1737
|
-
|
|
1623
|
+
**`SheetMetalOptions`**
|
|
1738
1624
|
|
|
1739
|
-
|
|
1625
|
+
| Option | Type | Description |
|
|
1626
|
+
|--------|------|-------------|
|
|
1627
|
+
| `width` | `number` | Width of the panel along the X axis. |
|
|
1628
|
+
| `height` | `number` | Height of the panel along the Y axis. |
|
|
1629
|
+
| `thickness` | `number` | Sheet thickness in mm. Applied uniformly across the panel and all flanges. |
|
|
1630
|
+
| `bendRadius` | `number` | Inside bend radius in mm. Must be ≥ 0. Typically 0.5–2× the sheet thickness. |
|
|
1631
|
+
| `kFactor` | `number` | K-factor (neutral-axis offset, 0–1). |
|
|
1632
|
+
| `kind?` | `"rect"` | Relief shape — only `'rect'` is supported in v1. |
|
|
1633
|
+
| `size` | `number` | Side length of the square relief cut in mm. |
|
|
1740
1634
|
|
|
1741
|
-
|
|
1635
|
+
#### `sketchToDxf()` — Export a 2D sketch as a DXF string (R12/AC1009 — maximally compatible).
|
|
1742
1636
|
|
|
1743
|
-
|
|
1637
|
+
**Details**
|
|
1744
1638
|
|
|
1745
|
-
|
|
1746
|
-
explodeView(options?: ExplodeViewOptions): void
|
|
1747
|
-
```
|
|
1639
|
+
For regular sketches, each polygon loop becomes a closed `LWPOLYLINE`. For constrained sketches, exports raw `LINE`, `CIRCLE`, and `ARC` entities from the constraint edge geometry, which preserves internal/shared edges that `toPolygons()` would merge away.
|
|
1748
1640
|
|
|
1749
|
-
|
|
1641
|
+
The R12 format is chosen for maximum compatibility with CAM tools, laser-cutter software, and older CAD readers.
|
|
1750
1642
|
|
|
1751
|
-
|
|
1643
|
+
**Example**
|
|
1752
1644
|
|
|
1753
1645
|
```ts
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
enabled?: boolean;
|
|
1757
|
-
/** Scales the UI explode amount. Default: 1 */
|
|
1758
|
-
amountScale?: number;
|
|
1759
|
-
/** Per-depth stage multipliers (depth 1 = first level). If depth exceeds this array, the last value is reused. Default when omitted: reciprocal depth (1, 1/2, 1/3, ...) */
|
|
1760
|
-
stages?: number[];
|
|
1761
|
-
/** Global direction mode fallback. Default: 'radial' */
|
|
1762
|
-
mode?: ExplodeViewDirection;
|
|
1763
|
-
/** Global axis lock fallback. */
|
|
1764
|
-
axisLock?: ExplodeAxis;
|
|
1765
|
-
/** Per-object overrides by final object name. */
|
|
1766
|
-
byName?: Record<string, ExplodeViewDirective>;
|
|
1767
|
-
/** Per-tree-path overrides using slash-separated object tree segments. */
|
|
1768
|
-
byPath?: Record<string, ExplodeViewDirective>;
|
|
1769
|
-
}
|
|
1646
|
+
const s = rect(100, 60);
|
|
1647
|
+
const dxf = sketchToDxf(s, { layer: 'cut' });
|
|
1770
1648
|
```
|
|
1771
1649
|
|
|
1772
|
-
|
|
1650
|
+
`sketchToDxf(sketch: Sketch, options?: SketchDxfOptions): string`
|
|
1773
1651
|
|
|
1774
|
-
|
|
1652
|
+
**`SketchDxfOptions`**
|
|
1653
|
+
- `layer?: string` — DXF layer name. Default: "0"
|
|
1654
|
+
- `colorIndex?: number` — DXF color index (1–255, AutoCAD ACI). Default: 7 (white/black)
|
|
1775
1655
|
|
|
1776
|
-
|
|
1777
|
-
interface ExplodeDirective {
|
|
1778
|
-
/** Multiplier applied to `amount` for this node */
|
|
1779
|
-
stage?: number;
|
|
1780
|
-
/** Direction mode for this node */
|
|
1781
|
-
direction?: ExplodeDirection;
|
|
1782
|
-
/** Optional axis lock after direction is resolved */
|
|
1783
|
-
axisLock?: ExplodeAxis;
|
|
1784
|
-
}
|
|
1785
|
-
```
|
|
1656
|
+
#### `sketchToSvg()` — Export a 2D sketch as an SVG string.
|
|
1786
1657
|
|
|
1787
|
-
|
|
1658
|
+
**Details**
|
|
1788
1659
|
|
|
1789
|
-
|
|
1660
|
+
For regular sketches, exports filled polygon regions. For constrained sketches, exports raw edge geometry (LINE, ARC, CIRCLE) which preserves internal/shared edges that `toPolygons()` would merge away.
|
|
1661
|
+
|
|
1662
|
+
The SVG uses the sketch's native coordinate system (Y-up) with a CSS transform that flips Y so the output renders correctly in SVG's Y-down space. Coordinates are in sketch units (typically mm).
|
|
1663
|
+
|
|
1664
|
+
**Example**
|
|
1790
1665
|
|
|
1791
1666
|
```ts
|
|
1792
|
-
|
|
1793
|
-
}
|
|
1667
|
+
const s = rect(100, 60);
|
|
1668
|
+
const svg = sketchToSvg(s, { stroke: '#333', strokeWidth: 0.8 });
|
|
1794
1669
|
```
|
|
1795
1670
|
|
|
1796
|
-
|
|
1671
|
+
`sketchToSvg(sketch: Sketch, options?: SketchSvgOptions): string`
|
|
1797
1672
|
|
|
1798
|
-
|
|
1673
|
+
**`SketchSvgOptions`**
|
|
1799
1674
|
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1675
|
+
| Option | Type | Description |
|
|
1676
|
+
|--------|------|-------------|
|
|
1677
|
+
| `stroke?` | `string` | Stroke color. Default: "black" |
|
|
1678
|
+
| `strokeWidth?` | `number` | Stroke width in sketch units. Default: 0.5 |
|
|
1679
|
+
| `fill?` | `string` | Fill color. Default: "none" |
|
|
1680
|
+
| `padding?` | `number` | Padding around the sketch bounding box in sketch units. Default: 2 |
|
|
1681
|
+
| `pixelsPerUnit?` | `number` | If set, scale so 1 sketch-unit = this many px. Otherwise auto-fit. |
|
|
1803
1682
|
|
|
1804
|
-
|
|
1683
|
+
---
|
|
1805
1684
|
|
|
1806
|
-
|
|
1807
|
-
interface CutPlaneOptions {
|
|
1808
|
-
/** Optional offset along the plane normal (primarily for object-form overload). */
|
|
1809
|
-
offset?: number;
|
|
1810
|
-
/** Object names to keep uncut for this plane. */
|
|
1811
|
-
exclude?: CutPlaneExcludeInput;
|
|
1812
|
-
}
|
|
1813
|
-
```
|
|
1685
|
+
## C14: Visual & Debugging
|
|
1814
1686
|
|
|
1815
|
-
|
|
1687
|
+
Control viewport appearance and debugging aids.
|
|
1816
1688
|
|
|
1817
|
-
#### `
|
|
1689
|
+
#### `explodeView()` — Configure how the viewport explode slider offsets returned objects.
|
|
1818
1690
|
|
|
1819
|
-
|
|
1820
|
-
scene(options: SceneOptions): void
|
|
1821
|
-
```
|
|
1691
|
+
**Details**
|
|
1822
1692
|
|
|
1823
|
-
|
|
1693
|
+
Offsets are resolved from the returned object tree, not a flat list. In `radial` mode each node follows its parent branch direction, then fans locally from the immediate parent center — nested assemblies peel apart level by level. In fixed-axis or fixed-vector modes, the branch follows that axis/vector but nested descendants fan out perpendicular by default.
|
|
1824
1694
|
|
|
1825
|
-
|
|
1695
|
+
Multiple calls merge — later values override earlier ones on a per-key basis. `byName` and `byPath` maps are merged entry-by-entry.
|
|
1826
1696
|
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
}
|
|
1697
|
+
For programmatic explode applied before returning (without the slider), use `lib.explode()` instead.
|
|
1698
|
+
|
|
1699
|
+
**Example**
|
|
1700
|
+
|
|
1701
|
+
```js
|
|
1702
|
+
explodeView({
|
|
1703
|
+
amountScale: 1.2,
|
|
1704
|
+
stages: [0.35, 0.8],
|
|
1705
|
+
mode: 'radial',
|
|
1706
|
+
byPath: { 'Drive/Shaft': { direction: [1, 0, 0], stage: 1.6 } },
|
|
1707
|
+
});
|
|
1839
1708
|
```
|
|
1840
1709
|
|
|
1841
|
-
|
|
1710
|
+
`explodeView(options?: ExplodeViewOptions): void`
|
|
1842
1711
|
|
|
1843
|
-
|
|
1712
|
+
**`ExplodeViewOptions`**
|
|
1844
1713
|
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1714
|
+
| Option | Type | Description |
|
|
1715
|
+
|--------|------|-------------|
|
|
1716
|
+
| `enabled?` | `boolean` | Set false to disable viewport explode offsets for this script output. |
|
|
1717
|
+
| `amountScale?` | `number` | Scales the UI explode amount. Default: 1 |
|
|
1718
|
+
| `stages?` | `number[]` | Per-depth stage multipliers (depth 1 = first level). If depth exceeds this array, the last value is reused. Default when omitted: reciprocal depth (1, 1/2, 1/3, ...) |
|
|
1719
|
+
| `mode?` | `ExplodeViewDirection` | Global direction mode fallback. Default: 'radial' |
|
|
1720
|
+
| `axisLock?` | `ExplodeAxis` | Global axis lock fallback. |
|
|
1721
|
+
| `byName?` | `Record<string, ExplodeViewDirective>` | Per-object overrides by final object name. |
|
|
1722
|
+
| `byPath?` | `Record<string, ExplodeViewDirective>` | Per-tree-path overrides using slash-separated object tree segments. |
|
|
1851
1723
|
|
|
1852
|
-
|
|
1724
|
+
**`ExplodeDirective`**
|
|
1725
|
+
- `stage?: number` — Multiplier applied to `amount` for this node
|
|
1726
|
+
- `direction?: ExplodeDirection` — Direction mode for this node
|
|
1727
|
+
- `axisLock?: ExplodeAxis` — Optional axis lock after direction is resolved
|
|
1853
1728
|
|
|
1854
|
-
|
|
1729
|
+
#### `cutPlane()`
|
|
1855
1730
|
|
|
1856
|
-
|
|
1857
|
-
interface SceneCameraConfig {
|
|
1858
|
-
fov?: number;
|
|
1859
|
-
type?: "perspective" | "orthographic";
|
|
1860
|
-
}
|
|
1861
|
-
```
|
|
1731
|
+
`cutPlane(name: string, normal: [ number, number, number ], options?: CutPlaneOptions): void`
|
|
1862
1732
|
|
|
1863
|
-
|
|
1733
|
+
**`CutPlaneOptions`**
|
|
1734
|
+
- `offset?: number` — Optional offset along the plane normal (primarily for object-form overload).
|
|
1735
|
+
- `exclude?: CutPlaneExcludeInput` — Object names to keep uncut for this plane.
|
|
1864
1736
|
|
|
1865
|
-
|
|
1737
|
+
#### `scene()` — Configure the scene environment for the current script execution.
|
|
1866
1738
|
|
|
1867
|
-
|
|
1868
|
-
interface SceneLightConfig {
|
|
1869
|
-
type: SceneLightType;
|
|
1870
|
-
color?: string;
|
|
1871
|
-
intensity?: number;
|
|
1872
|
-
/** Ground color for hemisphere lights */
|
|
1873
|
-
groundColor?: string;
|
|
1874
|
-
/** Sky color alias for hemisphere lights (same as color) */
|
|
1875
|
-
skyColor?: string;
|
|
1876
|
-
/** Spot light cone angle in radians */
|
|
1877
|
-
angle?: number;
|
|
1878
|
-
/** Spot light penumbra (0–1) */
|
|
1879
|
-
penumbra?: number;
|
|
1880
|
-
/** Point/spot light decay */
|
|
1881
|
-
decay?: number;
|
|
1882
|
-
/** Point/spot light distance (0 = infinite) */
|
|
1883
|
-
distance?: number;
|
|
1884
|
-
/** Whether this light casts shadows */
|
|
1885
|
-
castShadow?: boolean;
|
|
1886
|
-
}
|
|
1887
|
-
```
|
|
1739
|
+
**Details**
|
|
1888
1740
|
|
|
1889
|
-
|
|
1741
|
+
Controls camera position, lighting rig, background color or gradient, atmospheric fog, environment maps, post-processing effects, and capture parameters for the `forgecad capture` command. Multiple calls merge — later values override earlier ones on a per-key basis, so you can split configuration across multiple `scene()` calls.
|
|
1890
1742
|
|
|
1891
|
-
|
|
1743
|
+
When `lights` is specified, **all** default lights are removed. You must include your own ambient light or the scene will be fully dark.
|
|
1892
1744
|
|
|
1893
|
-
|
|
1894
|
-
interface SceneEnvironmentConfig {
|
|
1895
|
-
/** Built-in preset name or 'none' to disable */
|
|
1896
|
-
preset?: "studio" | "sunset" | "dawn" | "warehouse" | "forest" | "apartment" | "lobby" | "city" | "park" | "night" | "none";
|
|
1897
|
-
/** Environment map intensity */
|
|
1898
|
-
intensity?: number;
|
|
1899
|
-
/** Use environment map as scene background */
|
|
1900
|
-
background?: boolean;
|
|
1901
|
-
}
|
|
1902
|
-
```
|
|
1745
|
+
Setting `camera.position` overrides auto-framing — the viewport will no longer auto-fit the geometry on script reload.
|
|
1903
1746
|
|
|
1904
|
-
|
|
1747
|
+
Post-processing effects (`bloom`, `vignette`, `grain`) work in the browser viewport only. The CLI applies camera, lights, background, fog, and `toneMappingExposure` but skips shader effects.
|
|
1905
1748
|
|
|
1906
|
-
|
|
1749
|
+
All numeric values accept `param()` expressions.
|
|
1907
1750
|
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
}
|
|
1751
|
+
**Example**
|
|
1752
|
+
|
|
1753
|
+
```js
|
|
1754
|
+
scene({
|
|
1755
|
+
background: { top: '#000814', bottom: '#001d3d' },
|
|
1756
|
+
camera: { position: [160, -120, 100], target: [0, 0, 50], fov: 52 },
|
|
1757
|
+
lights: [
|
|
1758
|
+
{ type: 'ambient', color: '#001233', intensity: 0.08 },
|
|
1759
|
+
{ type: 'point', position: [120, -80, 130], color: '#00f5d4', intensity: 4, distance: 400, decay: 1 },
|
|
1760
|
+
{ type: 'point', position: [-100, 60, 20], color: '#f72585', intensity: 3, distance: 350 },
|
|
1761
|
+
{ type: 'directional', position: [50, -30, 200], color: '#ffd60a', intensity: 1.2 },
|
|
1762
|
+
{ type: 'hemisphere', skyColor: '#003566', groundColor: '#000814', intensity: 0.2 },
|
|
1763
|
+
],
|
|
1764
|
+
fog: { color: '#000814', near: 100, far: 450 },
|
|
1765
|
+
postProcessing: {
|
|
1766
|
+
bloom: { intensity: param('bloom', 1.5, 0, 4), threshold: 0.5, radius: 0.7 },
|
|
1767
|
+
vignette: { darkness: 0.8, offset: 0.25 },
|
|
1768
|
+
grain: { intensity: 0.08 },
|
|
1769
|
+
toneMappingExposure: param('exposure', 1.5, 0.5, 4),
|
|
1770
|
+
},
|
|
1771
|
+
});
|
|
1918
1772
|
```
|
|
1919
1773
|
|
|
1920
|
-
|
|
1774
|
+
`scene(options: SceneOptions): void`
|
|
1921
1775
|
|
|
1922
|
-
|
|
1776
|
+
**`SceneOptions`**
|
|
1923
1777
|
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
grain?: SceneGrainConfig;
|
|
1929
|
-
toneMappingExposure?: number;
|
|
1930
|
-
}
|
|
1931
|
-
```
|
|
1778
|
+
| Option | Type | Description |
|
|
1779
|
+
|--------|------|-------------|
|
|
1780
|
+
| `capture?` | `SceneCaptureConfig` | Default capture parameters for `forgecad capture` — CLI flags override these. |
|
|
1781
|
+
| `background?`, `camera?`, `lights?`, `environment?`, `fog?`, `postProcessing?`, `ground?` | | — |
|
|
1932
1782
|
|
|
1933
|
-
|
|
1783
|
+
`SceneBackgroundGradient`: `{ top: string, bottom: string }`
|
|
1934
1784
|
|
|
1935
|
-
|
|
1785
|
+
`SceneCameraConfig`: `{ fov?: number, type?: "perspective" | "orthographic" }`
|
|
1936
1786
|
|
|
1937
|
-
|
|
1938
|
-
interface SceneBloomConfig {
|
|
1939
|
-
intensity?: number;
|
|
1940
|
-
threshold?: number;
|
|
1941
|
-
radius?: number;
|
|
1942
|
-
}
|
|
1943
|
-
```
|
|
1787
|
+
**`SceneLightConfig`**
|
|
1944
1788
|
|
|
1945
|
-
|
|
1789
|
+
| Option | Type | Description |
|
|
1790
|
+
|--------|------|-------------|
|
|
1791
|
+
| `groundColor?` | `string` | Ground color for hemisphere lights |
|
|
1792
|
+
| `skyColor?` | `string` | Sky color alias for hemisphere lights (same as color) |
|
|
1793
|
+
| `angle?` | `number` | Spot light cone angle in radians |
|
|
1794
|
+
| `penumbra?` | `number` | Spot light penumbra (0–1) |
|
|
1795
|
+
| `decay?` | `number` | Point/spot light decay |
|
|
1796
|
+
| `distance?` | `number` | Point/spot light distance (0 = infinite) |
|
|
1797
|
+
| `castShadow?` | `boolean` | Whether this light casts shadows |
|
|
1798
|
+
| `type`, `color?`, `intensity?` | | — |
|
|
1946
1799
|
|
|
1947
|
-
|
|
1800
|
+
**`SceneEnvironmentConfig`**
|
|
1801
|
+
- `preset?: "studio" | "sunset" | "dawn" | "warehouse" | "forest" | "apartment" | "lobby" | "city" | "park" | "night" | "none"` — Built-in preset name or 'none' to disable
|
|
1802
|
+
- `intensity?: number` — Environment map intensity
|
|
1803
|
+
- `background?: boolean` — Use environment map as scene background
|
|
1948
1804
|
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
```
|
|
1805
|
+
**`SceneFogConfig`**
|
|
1806
|
+
- `near?: number` — Linear fog near distance
|
|
1807
|
+
- `far?: number` — Linear fog far distance
|
|
1808
|
+
- `density?: number` — Exponential fog density (if set, uses FogExp2 instead of linear Fog)
|
|
1809
|
+
- Also: `color?: string`
|
|
1955
1810
|
|
|
1956
|
-
|
|
1811
|
+
`ScenePostProcessingConfig`: `{ bloom?: SceneBloomConfig, vignette?: SceneVignetteConfig, grain?: SceneGrainConfig, toneMappingExposure?: number }`
|
|
1957
1812
|
|
|
1958
|
-
|
|
1813
|
+
`SceneBloomConfig`: `{ intensity?: number, threshold?: number, radius?: number }`
|
|
1959
1814
|
|
|
1960
|
-
|
|
1961
|
-
interface SceneGrainConfig {
|
|
1962
|
-
intensity?: number;
|
|
1963
|
-
}
|
|
1964
|
-
```
|
|
1815
|
+
`SceneVignetteConfig`: `{ darkness?: number, offset?: number }`
|
|
1965
1816
|
|
|
1966
|
-
|
|
1817
|
+
`SceneGrainConfig`: `{ intensity?: number }`
|
|
1967
1818
|
|
|
1968
|
-
|
|
1819
|
+
**`SceneGroundConfig`**
|
|
1969
1820
|
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
/** Offset below the model's bounding box minimum Z. Default 0 (flush with model bottom). */
|
|
1977
|
-
offset?: number;
|
|
1978
|
-
/** Receive shadows on the ground */
|
|
1979
|
-
receiveShadow?: boolean;
|
|
1980
|
-
}
|
|
1981
|
-
```
|
|
1821
|
+
| Option | Type | Description |
|
|
1822
|
+
|--------|------|-------------|
|
|
1823
|
+
| `visible?` | `boolean` | Show a ground plane |
|
|
1824
|
+
| `color?` | `string` | Ground color |
|
|
1825
|
+
| `offset?` | `number` | Offset below the model's bounding box minimum Z. Default 0 (flush with model bottom). |
|
|
1826
|
+
| `receiveShadow?` | `boolean` | Receive shadows on the ground |
|
|
1982
1827
|
|
|
1983
|
-
|
|
1828
|
+
**`SceneCaptureConfig`**
|
|
1984
1829
|
|
|
1985
|
-
|
|
1830
|
+
| Option | Type | Description |
|
|
1831
|
+
|--------|------|-------------|
|
|
1832
|
+
| `framesPerTurn?` | `number` | Frames for one full orbit rotation (default: 72) |
|
|
1833
|
+
| `holdFrames?` | `number` | Frozen frames before motion starts (default: 6) |
|
|
1834
|
+
| `pitchDeg?` | `number` | Orbit pitch angle in degrees (default: auto from camera) |
|
|
1835
|
+
| `fps?` | `number` | Output frame rate (default: 24) |
|
|
1836
|
+
| `size?` | `number` | Output frame size in pixels (default: 960) |
|
|
1837
|
+
| `background?` | `string` | Canvas background color for capture (default: '#252526') |
|
|
1986
1838
|
|
|
1987
|
-
|
|
1988
|
-
interface SceneCaptureConfig {
|
|
1989
|
-
/** Frames for one full orbit rotation (default: 72) */
|
|
1990
|
-
framesPerTurn?: number;
|
|
1991
|
-
/** Frozen frames before motion starts (default: 6) */
|
|
1992
|
-
holdFrames?: number;
|
|
1993
|
-
/** Orbit pitch angle in degrees (default: auto from camera) */
|
|
1994
|
-
pitchDeg?: number;
|
|
1995
|
-
/** Output frame rate (default: 24) */
|
|
1996
|
-
fps?: number;
|
|
1997
|
-
/** Output frame size in pixels (default: 960) */
|
|
1998
|
-
size?: number;
|
|
1999
|
-
/** Canvas background color for capture (default: '#252526') */
|
|
2000
|
-
background?: string;
|
|
2001
|
-
}
|
|
2002
|
-
```
|
|
1839
|
+
#### `showLabels()` — Highlight all user-labeled faces on a shape for visual debugging.
|
|
2003
1840
|
|
|
2004
|
-
|
|
1841
|
+
Shows each user-authored label name in the viewport for visual debugging. Returns the shape unchanged for chaining: `return showLabels(myShape)`.
|
|
2005
1842
|
|
|
2006
|
-
|
|
1843
|
+
`showLabels(shape: Shape): Shape`
|
|
2007
1844
|
|
|
2008
|
-
|
|
2009
|
-
viewConfig(options?: ViewConfigOptions): void
|
|
2010
|
-
```
|
|
1845
|
+
#### `viewConfig()` — Configure viewport helper visuals for the current script execution.
|
|
2011
1846
|
|
|
2012
|
-
|
|
1847
|
+
**Details**
|
|
2013
1848
|
|
|
2014
|
-
|
|
1849
|
+
Controls renderer-only overlays that appear in the viewport but are not part of the geometry. Currently supports the joint overlay that renders axis arrows and arc indicators when `jointsView` is active. Multiple calls merge — later values override earlier ones per key.
|
|
2015
1850
|
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
1851
|
+
This does **not** trigger a geometry recompute; it only affects the visual helpers drawn on top of the 3D scene.
|
|
1852
|
+
|
|
1853
|
+
**Example**
|
|
1854
|
+
|
|
1855
|
+
```js
|
|
1856
|
+
viewConfig({
|
|
1857
|
+
jointOverlay: {
|
|
1858
|
+
axisColor: '#13dfff',
|
|
1859
|
+
arcColor: '#ff7a1a',
|
|
1860
|
+
axisLineRadiusScale: 0.03,
|
|
1861
|
+
arcLineRadiusScale: 0.022,
|
|
1862
|
+
},
|
|
1863
|
+
});
|
|
2020
1864
|
```
|
|
2021
1865
|
|
|
2022
|
-
|
|
1866
|
+
`viewConfig(options?: ViewConfigOptions): void`
|
|
2023
1867
|
|
|
2024
|
-
|
|
1868
|
+
`ViewConfigOptions`: `{ jointOverlay?: JointOverlayViewConfigOptions }`
|
|
2025
1869
|
|
|
2026
|
-
|
|
2027
|
-
interface JointOverlayViewConfigOptions {
|
|
2028
|
-
enabled?: boolean;
|
|
2029
|
-
axisColor?: string;
|
|
2030
|
-
axisCoreColor?: string;
|
|
2031
|
-
arcColor?: string;
|
|
2032
|
-
zeroColor?: string;
|
|
2033
|
-
arcVisualLimitDeg?: number;
|
|
2034
|
-
axisLengthScale?: number;
|
|
2035
|
-
axisLengthMin?: number;
|
|
2036
|
-
axisLineRadiusScale?: number;
|
|
2037
|
-
axisLineRadiusMin?: number;
|
|
2038
|
-
axisLineRadiusMax?: number;
|
|
2039
|
-
spokeLineRadiusScale?: number;
|
|
2040
|
-
spokeLineRadiusMin?: number;
|
|
2041
|
-
spokeLineRadiusMax?: number;
|
|
2042
|
-
arcLineRadiusScale?: number;
|
|
2043
|
-
arcLineRadiusMin?: number;
|
|
2044
|
-
arcLineRadiusMax?: number;
|
|
2045
|
-
axisDotRadiusScale?: number;
|
|
2046
|
-
axisDotRadiusMin?: number;
|
|
2047
|
-
axisArrowRadiusScale?: number;
|
|
2048
|
-
axisArrowRadiusMin?: number;
|
|
2049
|
-
axisArrowLengthScale?: number;
|
|
2050
|
-
axisArrowLengthMin?: number;
|
|
2051
|
-
axisArrowOffsetFactor?: number;
|
|
2052
|
-
arcRadiusScale?: number;
|
|
2053
|
-
arcRadiusMin?: number;
|
|
2054
|
-
arcDotRadiusScale?: number;
|
|
2055
|
-
arcDotRadiusMin?: number;
|
|
2056
|
-
arcArrowRadiusScale?: number;
|
|
2057
|
-
arcArrowRadiusMin?: number;
|
|
2058
|
-
arcArrowLengthScale?: number;
|
|
2059
|
-
arcArrowLengthMin?: number;
|
|
2060
|
-
arcArrowOffsetFactor?: number;
|
|
2061
|
-
arcStepDeg?: number;
|
|
2062
|
-
arcMinSteps?: number;
|
|
2063
|
-
arcTubeSegmentsMin?: number;
|
|
2064
|
-
arcTubeSegmentsFactor?: number;
|
|
2065
|
-
arcTubeRadialSegments?: number;
|
|
2066
|
-
}
|
|
2067
|
-
```
|
|
1870
|
+
**`JointOverlayViewConfigOptions`**: `enabled?: boolean`, `axisColor?: string`, `axisCoreColor?: string`, `arcColor?: string`, `zeroColor?: string`, `arcVisualLimitDeg?: number`, `axisLengthScale?: number`, `axisLengthMin?: number`, `axisLineRadiusScale?: number`, `axisLineRadiusMin?: number`, `axisLineRadiusMax?: number`, `spokeLineRadiusScale?: number`, `spokeLineRadiusMin?: number`, `spokeLineRadiusMax?: number`, `arcLineRadiusScale?: number`, `arcLineRadiusMin?: number`, `arcLineRadiusMax?: number`, `axisDotRadiusScale?: number`, `axisDotRadiusMin?: number`, `axisArrowRadiusScale?: number`, `axisArrowRadiusMin?: number`, `axisArrowLengthScale?: number`, `axisArrowLengthMin?: number`, `axisArrowOffsetFactor?: number`, `arcRadiusScale?: number`, `arcRadiusMin?: number`, `arcDotRadiusScale?: number`, `arcDotRadiusMin?: number`, `arcArrowRadiusScale?: number`, `arcArrowRadiusMin?: number`, `arcArrowLengthScale?: number`, `arcArrowLengthMin?: number`, `arcArrowOffsetFactor?: number`, `arcStepDeg?: number`, `arcMinSteps?: number`, `arcTubeSegmentsMin?: number`, `arcTubeSegmentsFactor?: number`, `arcTubeRadialSegments?: number`
|
|
2068
1871
|
|
|
2069
|
-
|
|
1872
|
+
#### `spec()` — Create a named, reusable bundle of verification checks.
|
|
2070
1873
|
|
|
2071
|
-
|
|
1874
|
+
**Details**
|
|
2072
1875
|
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
1876
|
+
A spec groups related `verify.*` calls under a collapsible header in the Checks panel. This makes large check suites scannable. Specs can be applied to multiple shapes and can check relationships between parts.
|
|
1877
|
+
|
|
1878
|
+
Specs can be defined in separate `.forge.js` files and imported via `require()` to share them across models.
|
|
2076
1879
|
|
|
2077
|
-
|
|
1880
|
+
`spec.check()` returns a `SpecResult` — you can inspect it programmatically or ignore the return value and let the Checks panel show results.
|
|
2078
1881
|
|
|
2079
|
-
|
|
1882
|
+
**Example**
|
|
2080
1883
|
|
|
2081
1884
|
```ts
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
1885
|
+
const printable = spec("Fits printer bed", (shape) => {
|
|
1886
|
+
verify.notEmpty("Has geometry", shape);
|
|
1887
|
+
const bb = shape.boundingBox();
|
|
1888
|
+
verify.lessThan("Width < 220mm", bb.max[0] - bb.min[0], 220);
|
|
1889
|
+
verify.lessThan("Depth < 220mm", bb.max[1] - bb.min[1], 220);
|
|
1890
|
+
verify.lessThan("Height < 250mm", bb.max[2] - bb.min[2], 250);
|
|
1891
|
+
});
|
|
1892
|
+
|
|
1893
|
+
// Reuse on multiple shapes
|
|
1894
|
+
printable.check(bracket);
|
|
1895
|
+
printable.check(standoff);
|
|
1896
|
+
|
|
1897
|
+
// Check relationships between parts
|
|
1898
|
+
const fitSpec = spec("Assembly fit", (partA, partB) => {
|
|
1899
|
+
verify.notColliding("No interference", partA, partB, 10);
|
|
1900
|
+
});
|
|
1901
|
+
fitSpec.check(bracket, standoff);
|
|
2086
1902
|
```
|
|
2087
1903
|
|
|
2088
|
-
|
|
1904
|
+
**Spec-first workflow:** Write specs before building geometry. Checks go from red to green as you build — effectively TDD for CAD.
|
|
1905
|
+
|
|
1906
|
+
`spec(name: string, checkFn: (...args: any[]) => void): Spec`
|
|
1907
|
+
|
|
1908
|
+
**`Spec`**
|
|
1909
|
+
- `name: string` — The display name of this spec
|
|
2089
1910
|
|
|
2090
1911
|
---
|
|
2091
1912
|
|
|
@@ -2093,13 +1914,17 @@ interface Spec {
|
|
|
2093
1914
|
|
|
2094
1915
|
Bring external geometry or other ForgeCAD modules into the current script.
|
|
2095
1916
|
|
|
2096
|
-
#### `group()`
|
|
1917
|
+
#### `group()` — Group multiple shapes/sketches for joint transforms without merging into a single mesh.
|
|
2097
1918
|
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
1919
|
+
Unlike union(), colors and individual identities are preserved. Children can be plain shapes, named descriptors ({ name, shape/sketch/group }), or nested groups. The returned ShapeGroup supports all Shape transforms (translate, rotate, etc.).
|
|
1920
|
+
|
|
1921
|
+
**Local coordinate pattern:** Build child parts at the origin (local coordinates), then group and translate once to place the whole assembly. This eliminates the error-prone pattern of manually adding parent offsets to every sub-part.
|
|
1922
|
+
|
|
1923
|
+
// BAD — every sub-part repeats the parent's global offset const unitX = 0, unitY = -18, unitZ = 70; const body = roundedBox(100, 20, 32, 4).translate(unitX, unitY, unitZ); const panel = box(98, 2, 18).translate(unitX, unitY - 12, unitZ + 4); const louver = box(88, 2, 6).translate(unitX, unitY - 14, unitZ - 11);
|
|
1924
|
+
|
|
1925
|
+
// GOOD — build at origin, group, translate once const body = roundedBox(100, 20, 32, 4); const panel = box(98, 2, 18).translate(0, -12, 4); const louver = box(88, 2, 6).translate(0, -14, -11); const indoorUnit = group( { name: 'Body', shape: body }, { name: 'Panel', shape: panel }, { name: 'Louver', shape: louver }, ).translate(0, -18, 70);
|
|
2101
1926
|
|
|
2102
|
-
|
|
1927
|
+
`group(...items: GroupInput[]): ShapeGroup`
|
|
2103
1928
|
|
|
2104
1929
|
---
|
|
2105
1930
|
|