forgecad 0.6.3 → 0.8.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 +3 -12
- package/dist/assets/{AdminPage-CeqCUUgu.js → AdminPage-D4bocK4E.js} +250 -151
- package/dist/assets/{BlogPage-P_AJP0v9.js → BlogPage-CJEXL_zJ.js} +94 -70
- package/dist/assets/{DocsPage-CKRV2iq2.js → DocsPage-D3A_g8V3.js} +329 -163
- package/dist/assets/{EditorApp-CnC2k4cW.css → EditorApp-BWYUSpUN.css} +590 -136
- package/dist/assets/EditorApp-Cihhqcsq.js +11692 -0
- package/dist/assets/{EmbedViewer-DBlzmQ5i.js → EmbedViewer-kWjKaC_t.js} +2 -4
- package/dist/assets/LandingPageProofDriven-Bg2IUc3l.css +856 -0
- package/dist/assets/LandingPageProofDriven-DXkKlyhI.js +601 -0
- package/dist/assets/PricingPage-BsU5vzEx.js +232 -0
- package/dist/assets/{SettingsPage-BqCh9JcC.js → SettingsPage-PqvpAKIs.js} +129 -5
- package/dist/assets/{evalWorker-Ql-aKwLA.js → evalWorker-C-hzNUMy.js} +8949 -3161
- package/dist/assets/{Viewport-CoB46f5R.js → index-Pz321YAt.js} +38382 -7501
- package/dist/assets/{index-2hfs_ub0.css → index-ay13WNfa.css} +726 -53
- package/dist/assets/{javascript-DCxGoE5Y.js → javascript-DAl8Gmyo.js} +1 -1
- package/dist/assets/{manifold-CqNMHHKO.js → manifold-BcbjWLIo.js} +4 -3
- package/dist/assets/{manifold-Cce9wRFz.js → manifold-DBckbFgx.js} +1 -1
- package/dist/assets/{manifold-D6BeHIOo.js → manifold-O2AAGXyj.js} +1 -1
- package/dist/assets/{reportWorker-sFEFonXf.js → reportWorker-Dxr-5A7w.js} +8760 -3559
- package/dist/assets/{vendor-react-Dt7-aaJH.js → vendor-react-CG3i_wp0.js} +65 -8
- package/dist/docs/index.html +2 -2
- package/dist/docs-raw/CLI.md +341 -718
- package/dist/docs-raw/generated/assembly.md +699 -112
- package/dist/docs-raw/generated/concepts.md +1834 -1346
- package/dist/docs-raw/generated/core.md +1012 -1059
- package/dist/docs-raw/generated/curves.md +759 -116
- package/dist/docs-raw/generated/lib.md +43 -748
- package/dist/docs-raw/generated/output.md +139 -245
- package/dist/docs-raw/generated/sdf.md +208 -0
- package/dist/docs-raw/generated/sheet-metal.md +473 -21
- package/dist/docs-raw/generated/sketch.md +1518 -362
- package/dist/docs-raw/generated/viewport.md +368 -299
- package/dist/docs-raw/generated/wood.md +104 -0
- package/dist/index.html +2 -2
- package/dist/landing/proof-ams-adapter.png +0 -0
- package/dist/landing/proof-bolt-and-nut.png +0 -0
- package/dist/landing/proof-fillet-enclosure.png +0 -0
- package/dist/landing/proof-glasses.png +0 -0
- package/dist/landing/proof-gyroid.png +0 -0
- package/dist/sitemap.xml +6 -6
- package/dist-cli/forgecad.js +12321 -5700
- package/dist-cli/forgecad.js.map +1 -0
- package/dist-cli/solver-46FFSK2U.js +363 -0
- package/dist-cli/solver-46FFSK2U.js.map +1 -0
- package/dist-skill/CONTEXT.md +4890 -6302
- package/dist-skill/SKILL-dev.md +22 -66
- package/dist-skill/SKILL.md +20 -59
- package/dist-skill/docs/API/core/concepts.md +37 -92
- package/dist-skill/docs/CLI.md +341 -718
- package/dist-skill/docs/generated/assembly.md +699 -112
- package/dist-skill/docs/generated/core.md +1012 -1059
- package/dist-skill/docs/generated/curves.md +759 -116
- package/dist-skill/docs/generated/lib.md +43 -748
- package/dist-skill/docs/generated/output.md +139 -245
- package/dist-skill/docs/generated/sdf.md +208 -0
- package/dist-skill/docs/generated/sheet-metal.md +473 -21
- package/dist-skill/docs/generated/sketch.md +1518 -362
- package/dist-skill/docs/generated/viewport.md +368 -299
- 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/joint-design.md +78 -0
- 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 +78 -0
- package/dist-skill/docs-dev/CLI.md +488 -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 +2 -4
- package/dist-skill/docs-dev/component-model.md +164 -0
- package/dist-skill/docs-dev/generated/assembly.md +779 -0
- package/dist-skill/docs-dev/generated/core.md +1676 -0
- package/dist-skill/docs-dev/generated/curves.md +855 -0
- package/dist-skill/docs-dev/generated/lib.md +55 -0
- package/dist-skill/docs-dev/generated/output.md +234 -0
- package/dist-skill/docs-dev/generated/sdf.md +208 -0
- package/dist-skill/docs-dev/generated/sheet-metal.md +506 -0
- package/dist-skill/docs-dev/generated/sketch.md +1753 -0
- package/dist-skill/docs-dev/generated/viewport.md +513 -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/joint-design.md +78 -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 +8 -8
- package/examples/api/bill-of-materials.forge.js +9 -9
- package/examples/api/bolt-pattern.forge.js +5 -5
- package/examples/api/boolean-operations.forge.js +5 -5
- package/examples/api/bounding-box-visualizer.forge.js +3 -3
- package/examples/api/clone-duplicate.forge.js +2 -2
- package/examples/api/colors-union-vs-array.forge.js +6 -6
- package/examples/api/connector-assembly.forge.js +8 -6
- package/examples/api/connector-basics.forge.js +7 -7
- package/examples/api/constrained-sketch-mechanical.forge.js +4 -4
- package/examples/api/elbow-test.forge.js +3 -3
- package/examples/api/extrude-options.forge.js +8 -14
- package/examples/api/feature-created-faces.forge.js +6 -10
- package/examples/api/fillet-showcase.forge.js +2 -2
- package/examples/api/folded-service-panel-cover.forge.js +2 -2
- package/examples/api/gears-tier1.forge.js +5 -5
- package/examples/api/group-test.forge.js +3 -3
- 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 +4 -4
- package/examples/api/patterns.forge.js +3 -3
- package/examples/api/pointAlong-orientation.forge.js +3 -3
- package/examples/api/profile-2020-b-slot6.forge.js +4 -5
- 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/sketch-rounding-strategies.forge.js +1 -1
- package/examples/api/smooth-curve-connections.forge.js +1 -1
- package/examples/api/transition-curves.forge.js +4 -4
- 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/constraints/01-fully-constrained-rect.forge.js +2 -2
- package/examples/constraints/02-underconstrained-triangle.forge.js +1 -1
- package/examples/constraints/03-redundant-constraints.forge.js +2 -2
- package/examples/constraints/05-parallel-with-linedistance.forge.js +2 -2
- package/examples/constraints/06-complex-spectrogram.forge.js +1 -1
- package/examples/constraints/07-perpendicular-chain.forge.js +4 -4
- package/examples/constraints/08-symmetric-bracket.forge.js +4 -4
- package/examples/constraints/09-stress-spiral.forge.js +1 -1
- package/examples/constraints/10-stress-honeycomb.forge.js +1 -1
- package/examples/constraints/11-surface-grid.forge.js +2 -2
- package/examples/constraints/12-surface-nested.forge.js +4 -4
- package/examples/constraints/13-surface-complex.forge.js +1 -1
- package/examples/exact-arc-housing.forge.js +12 -0
- package/examples/experiments/drone-arm.forge.js +53 -0
- package/examples/furniture/adjustable-table.forge.js +15 -15
- package/examples/furniture/bathroom.forge.js +26 -26
- package/examples/furniture/chair.forge.js +13 -13
- package/examples/furniture/picture-frame.forge.js +6 -6
- package/examples/furniture/shoe-rack-doors.forge.js +8 -8
- package/examples/furniture/shoe-rack.forge.js +7 -7
- package/examples/furniture/table-lamp.forge.js +8 -8
- package/examples/gcode/lissajous-vase.forge.js +4 -4
- package/examples/gcode/math-surface.forge.js +3 -3
- package/examples/gcode/parametric-vase.forge.js +4 -4
- package/examples/gcode/spiral-tower.forge.js +4 -4
- package/examples/generative/crystal-growth.forge.js +9 -9
- package/examples/generative/frost-spires.forge.js +9 -9
- package/examples/generative/golden-spiral-tower.forge.js +11 -11
- package/examples/generative/molten-forge.forge.js +6 -6
- package/examples/generative/neon-coral.forge.js +7 -7
- package/examples/mechanical/3d-printer.forge.js +37 -37
- package/examples/mechanical/5-finger-robot-hand.forge.js +19 -19
- package/examples/mechanical/airplane-propeller.forge.js +9 -9
- package/examples/mechanical/bolt-and-nut.forge.js +10 -10
- package/examples/mechanical/door-with-hinges.forge.js +7 -7
- package/examples/mechanical/fillet-enclosure.forge.js +15 -11
- package/examples/mechanical/headphone-hanger-v2.forge.js +11 -11
- package/examples/mechanical/robot_hand.forge.js +24 -24
- package/examples/mechanical/robot_hand_2.forge.js +26 -26
- package/examples/nurbs-surface.forge.js +8 -0
- package/examples/nurbs-tube.forge.js +7 -0
- package/examples/products/bottle.forge.js +8 -8
- package/examples/products/chess-set.forge.js +25 -25
- package/examples/products/classical-piano.forge.js +20 -20
- package/examples/products/clock.forge.js +33 -33
- package/examples/products/cup.forge.js +5 -5
- package/examples/products/iphone.forge.js +20 -20
- package/examples/products/laptop.forge.js +24 -24
- package/examples/products/laser-cut-box.forge.js +6 -6
- package/examples/products/laser-cut-tray.forge.js +6 -6
- package/examples/products/liquid-soap-dispenser.forge.js +23 -23
- package/examples/products/origami-fish.forge.js +14 -12
- package/examples/products/spiderman-cake.forge.js +6 -6
- package/examples/shelf/container.forge.js +5 -5
- package/examples/shelf/shelf-unit.forge.js +6 -6
- package/examples/toolbox/bolted-joint.forge.js +7 -7
- package/package.json +9 -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-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/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
|
@@ -1,27 +1,25 @@
|
|
|
1
1
|
# API by Concept
|
|
2
2
|
|
|
3
|
-
> **Auto-generated** from `@concept` tags in `src/forge/forge-public-api.ts`. Do not edit by hand — run `npm run gen:docs` to regenerate.
|
|
4
|
-
|
|
5
3
|
Every public API function belongs to one of 16 fundamental concepts. This document groups them by concept rather than by module, making it easy to see the full set of operations for each idea.
|
|
6
4
|
|
|
7
5
|
## Concepts
|
|
8
6
|
|
|
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. *(
|
|
7
|
+
- **[C1: Primitive Construction](#c1-primitive-construction)** — Create geometry from parameters — no input geometry required. *(50 functions)*
|
|
8
|
+
- **[C2: Boolean Combination](#c2-boolean-combination)** — Combine same-dimension geometry using CSG set operations. *(6 functions)*
|
|
11
9
|
- **[C3: Rigid Transform](#c3-rigid-transform)** — Reposition or reorient geometry without changing its shape. *(3 functions)*
|
|
12
10
|
- **[C4: Dimensional Promotion](#c4-dimensional-promotion)** — Convert a 2D profile into a 3D solid (extrude, revolve, loft, sweep). *(12 functions)*
|
|
13
11
|
- **[C5: Topology Query](#c5-topology-query)** — Select or inspect named faces and edges on a shape. *(3 functions)*
|
|
14
12
|
- **[C6: Edge Feature](#c6-edge-feature)** — Modify edges of a solid — fillets, chamfers, draft, offset. *(7 functions)*
|
|
15
13
|
- **[C7: Pattern Replication](#c7-pattern-replication)** — Duplicate geometry in regular arrangements (linear, circular, mirror). *(6 functions)*
|
|
16
|
-
- **[C8: Constraint Solving](#c8-constraint-solving)** — Define geometry by relationships and let a solver find positions. *(
|
|
17
|
-
- **[C9: Spatial Placement](#c9-spatial-placement)** — Position geometry relative to other geometry using semantic anchors. *(
|
|
14
|
+
- **[C8: Constraint Solving](#c8-constraint-solving)** — Define geometry by relationships and let a solver find positions. *(17 functions)*
|
|
15
|
+
- **[C9: Spatial Placement](#c9-spatial-placement)** — Position geometry relative to other geometry using semantic anchors. *(6 functions)*
|
|
18
16
|
- **[C10: Assembly & Kinematics](#c10-assembly-kinematics)** — Compose parts with joints for kinematic simulation. *(4 functions)*
|
|
19
|
-
- **[C11: Parameterization & UI](#c11-parameterization-ui)** — Declare user-facing controls that drive model geometry. *(
|
|
17
|
+
- **[C11: Parameterization & UI](#c11-parameterization-ui)** — Declare user-facing controls that drive model geometry. *(7 functions)*
|
|
20
18
|
- **[C12: Dimensional Demotion](#c12-dimensional-demotion)** — Extract 2D geometry from a 3D solid (section, projection). *(3 functions)*
|
|
21
19
|
- **[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. *(
|
|
20
|
+
- **[C14: Visual & Debugging](#c14-visual-debugging)** — Control viewport appearance and debugging aids. *(26 functions)*
|
|
23
21
|
- **[C15: Import & Composition](#c15-import-composition)** — Bring external geometry or other ForgeCAD modules into the current script. *(1 functions)*
|
|
24
|
-
- **[C16: Part Library](#c16-part-library)** — Pre-built parametric parts accessible via `lib.*`. *(
|
|
22
|
+
- **[C16: Part Library](#c16-part-library)** — Pre-built parametric parts accessible via `lib.*`. *(4 functions)*
|
|
25
23
|
|
|
26
24
|
---
|
|
27
25
|
|
|
@@ -29,1362 +27,1882 @@ Every public API function belongs to one of 16 fundamental concepts. This docume
|
|
|
29
27
|
|
|
30
28
|
Create geometry from parameters — no input geometry required.
|
|
31
29
|
|
|
32
|
-
#### `
|
|
30
|
+
#### `sdf.sphere()` — Create an SDF sphere centered at the origin.
|
|
33
31
|
|
|
34
32
|
```ts
|
|
35
|
-
|
|
33
|
+
sdf.sphere(radius: number): SdfShape
|
|
36
34
|
```
|
|
37
35
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
#### `circle2d()`
|
|
36
|
+
#### `sdf.box()` — Create an SDF box centered at the origin with given full dimensions (not half-extents).
|
|
41
37
|
|
|
42
38
|
```ts
|
|
43
|
-
|
|
39
|
+
sdf.box(x: number, y: number, z: number): SdfShape
|
|
44
40
|
```
|
|
45
41
|
|
|
46
|
-
Create
|
|
47
|
-
|
|
48
|
-
#### `ellipse()`
|
|
42
|
+
#### `sdf.cylinder()` — Create an SDF cylinder centered at the origin, axis along Z.
|
|
49
43
|
|
|
50
44
|
```ts
|
|
51
|
-
|
|
45
|
+
sdf.cylinder(height: number, radius: number): SdfShape
|
|
52
46
|
```
|
|
53
47
|
|
|
54
|
-
Create
|
|
55
|
-
|
|
56
|
-
#### `loadFont()`
|
|
48
|
+
#### `sdf.torus()` — Create an SDF torus centered at the origin, lying in the XY plane.
|
|
57
49
|
|
|
58
50
|
```ts
|
|
59
|
-
|
|
51
|
+
sdf.torus(majorRadius: number, minorRadius: number): SdfShape
|
|
60
52
|
```
|
|
61
53
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
#### `ngon()`
|
|
54
|
+
#### `sdf.capsule()` — Create an SDF capsule centered at the origin, axis along Z.
|
|
65
55
|
|
|
66
56
|
```ts
|
|
67
|
-
|
|
57
|
+
sdf.capsule(height: number, radius: number): SdfShape
|
|
68
58
|
```
|
|
69
59
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
#### `path()`
|
|
60
|
+
#### `sdf.cone()` — Create an SDF cone with base at z=0 and tip at z=height.
|
|
73
61
|
|
|
74
62
|
```ts
|
|
75
|
-
|
|
63
|
+
sdf.cone(height: number, radius: number): SdfShape
|
|
76
64
|
```
|
|
77
65
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
#### `polar()`
|
|
66
|
+
#### `sdf.smoothUnion()` — Smooth union — blends shapes together with a smooth transition radius.
|
|
81
67
|
|
|
82
68
|
```ts
|
|
83
|
-
|
|
69
|
+
sdf.smoothUnion(a: SdfShape, b: SdfShape, options: { radius: number; }): SdfShape
|
|
84
70
|
```
|
|
85
71
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
#### `polygon()`
|
|
72
|
+
#### `sdf.smoothDifference()` — Smooth difference — smoothly subtracts b from a.
|
|
89
73
|
|
|
90
74
|
```ts
|
|
91
|
-
|
|
75
|
+
sdf.smoothDifference(a: SdfShape, b: SdfShape, options: { radius: number; }): SdfShape
|
|
92
76
|
```
|
|
93
77
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
#### `polygonVertices()`
|
|
78
|
+
#### `sdf.smoothIntersection()` — Smooth intersection — smoothly intersects a and b.
|
|
97
79
|
|
|
98
80
|
```ts
|
|
99
|
-
|
|
81
|
+
sdf.smoothIntersection(a: SdfShape, b: SdfShape, options: { radius: number; }): SdfShape
|
|
100
82
|
```
|
|
101
83
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
<details><summary><code>PolygonVerticesOptions</code></summary>
|
|
84
|
+
#### `sdf.morph()` — Morph between two SDF shapes. t=0 → a, t=1 → b.
|
|
105
85
|
|
|
106
86
|
```ts
|
|
107
|
-
|
|
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
|
-
}
|
|
87
|
+
sdf.morph(a: SdfShape, b: SdfShape, t: number): SdfShape
|
|
115
88
|
```
|
|
116
89
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
<details><summary><code>LayoutPoint</code></summary>
|
|
90
|
+
#### `sdf.blend()` — Spatially blend between two SDF patterns. The blend function receives (x, y, z) and returns 0..1: 0 = fully pattern `a`, 1 = fully pattern `b`.
|
|
120
91
|
|
|
121
92
|
```ts
|
|
122
|
-
|
|
123
|
-
x: number;
|
|
124
|
-
y: number;
|
|
125
|
-
}
|
|
93
|
+
sdf.blend(a: SdfShape, b: SdfShape, fn: (x: number, y: number, z: number) => number, options?: BlendOptions): SdfShape
|
|
126
94
|
```
|
|
127
95
|
|
|
128
|
-
|
|
96
|
+
**`BlendOptions`**
|
|
97
|
+
- `constants?: Record<string, number>` — Optional named constants accessible in the blend function.
|
|
129
98
|
|
|
130
|
-
#### `
|
|
99
|
+
#### `sdf.gyroid()` — Gyroid TPMS lattice — the most common lattice for additive manufacturing.
|
|
131
100
|
|
|
132
101
|
```ts
|
|
133
|
-
|
|
102
|
+
sdf.gyroid(options: TpmsOptions): SdfShape
|
|
134
103
|
```
|
|
135
104
|
|
|
136
|
-
|
|
105
|
+
`TpmsOptions`: `{ cellSize: number, thickness: number }`
|
|
137
106
|
|
|
138
|
-
#### `
|
|
107
|
+
#### `sdf.schwarzP()` — Schwarz-P TPMS lattice — isotropic pore structure.
|
|
139
108
|
|
|
140
109
|
```ts
|
|
141
|
-
|
|
110
|
+
sdf.schwarzP(options: TpmsOptions): SdfShape
|
|
142
111
|
```
|
|
143
112
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
#### `routePerimeter()`
|
|
113
|
+
#### `sdf.diamond()` — Diamond TPMS lattice — stiffest TPMS structure.
|
|
147
114
|
|
|
148
115
|
```ts
|
|
149
|
-
|
|
116
|
+
sdf.diamond(options: TpmsOptions): SdfShape
|
|
150
117
|
```
|
|
151
118
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
#### `roundedRect()`
|
|
119
|
+
#### `sdf.lidinoid()` — Lidinoid TPMS lattice — visually distinct from gyroid, popular in research and art.
|
|
155
120
|
|
|
156
121
|
```ts
|
|
157
|
-
|
|
122
|
+
sdf.lidinoid(options: TpmsOptions): SdfShape
|
|
158
123
|
```
|
|
159
124
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
#### `slot()`
|
|
125
|
+
#### `sdf.noise()` — 3D Simplex noise field — produces organic, natural-looking displacements.
|
|
163
126
|
|
|
164
127
|
```ts
|
|
165
|
-
|
|
128
|
+
sdf.noise(options?: NoiseOptions): SdfShape
|
|
166
129
|
```
|
|
167
130
|
|
|
168
|
-
|
|
131
|
+
**`NoiseOptions`**
|
|
169
132
|
|
|
170
|
-
|
|
133
|
+
| Option | Type | Description |
|
|
134
|
+
|--------|------|-------------|
|
|
135
|
+
| `scale?` | `number` | Spatial frequency — smaller = larger features. Default: 0.1 |
|
|
136
|
+
| `amplitude?` | `number` | Peak displacement amplitude. Default: 1 |
|
|
137
|
+
| `octaves?` | `number` | fBm octaves (1 = plain simplex, higher = more detail). Default: 1 |
|
|
138
|
+
| `seed?` | `number` | Seed for deterministic variation. Default: 0 |
|
|
139
|
+
|
|
140
|
+
#### `sdf.voronoi()` — 3D Voronoi pattern — organic cellular structures like bone, coral, or soap bubbles.
|
|
171
141
|
|
|
172
142
|
```ts
|
|
173
|
-
|
|
143
|
+
sdf.voronoi(options?: VoronoiOptions): SdfShape
|
|
174
144
|
```
|
|
175
145
|
|
|
176
|
-
|
|
146
|
+
**`VoronoiOptions`**
|
|
177
147
|
|
|
178
|
-
|
|
148
|
+
| Option | Type | Description |
|
|
149
|
+
|--------|------|-------------|
|
|
150
|
+
| `cellSize?` | `number` | Size of each Voronoi cell in world units. Default: 10 |
|
|
151
|
+
| `wallThickness?` | `number` | Wall thickness between cells. Default: 1 |
|
|
152
|
+
| `seed?` | `number` | Seed for deterministic variation. Default: 0 |
|
|
153
|
+
| `suppressionThreshold?` | `number` | Projection weight for membrane suppression (0..1). Controls how much of the surface-normal distance component is removed from Voronoi cell distances. 0 = no projection (classic 3D voronoi with membranes). 1 = full tangent-plane projection (pure 2D pattern on surface). Default: 0.85. Only active when voronoi is intersected with another shape. |
|
|
154
|
+
|
|
155
|
+
#### `sdf.honeycomb()` — Honeycomb (hexagonal) lattice pattern. Intersect with your shape to apply.
|
|
179
156
|
|
|
180
157
|
```ts
|
|
181
|
-
|
|
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
|
-
}
|
|
158
|
+
sdf.honeycomb(options?: HoneycombOptions): SdfShape
|
|
193
159
|
```
|
|
194
160
|
|
|
195
|
-
|
|
161
|
+
**`HoneycombOptions`**
|
|
162
|
+
- `cellSize?: number` — Size of each hex cell. Default: 8
|
|
163
|
+
- `wallThickness?: number` — Wall thickness. Default: 1
|
|
196
164
|
|
|
197
|
-
#### `
|
|
165
|
+
#### `sdf.waves()` — Sinusoidal wave ridges — parallel ridges along an axis.
|
|
198
166
|
|
|
199
167
|
```ts
|
|
200
|
-
|
|
168
|
+
sdf.waves(options?: WavesOptions): SdfShape
|
|
201
169
|
```
|
|
202
170
|
|
|
203
|
-
|
|
171
|
+
**`WavesOptions`**
|
|
172
|
+
- `wavelength?: number` — Distance between wave peaks. Default: 10
|
|
173
|
+
- `amplitude?: number` — Height of waves. Default: 1
|
|
174
|
+
- `axis?: "x" | "y" | "z"` — Axis along which waves propagate: 'x', 'y', or 'z'. Default: 'x'
|
|
204
175
|
|
|
205
|
-
#### `
|
|
176
|
+
#### `sdf.knurl()` — Knurl pattern — crossed helical grooves for grips and handles.
|
|
206
177
|
|
|
207
178
|
```ts
|
|
208
|
-
|
|
179
|
+
sdf.knurl(options?: KnurlOptions): SdfShape
|
|
209
180
|
```
|
|
210
181
|
|
|
211
|
-
|
|
182
|
+
**`KnurlOptions`**
|
|
183
|
+
- `pitch?: number` — Distance between knurl ridges. Default: 3
|
|
184
|
+
- `depth?: number` — Depth of knurl grooves. Default: 0.5
|
|
185
|
+
- `angle?: number` — Helix angle in degrees. Default: 30
|
|
212
186
|
|
|
213
|
-
#### `
|
|
187
|
+
#### `sdf.perforated()` — Perforated plate pattern — regular array of cylindrical holes.
|
|
214
188
|
|
|
215
189
|
```ts
|
|
216
|
-
|
|
190
|
+
sdf.perforated(options?: PerforatedOptions): SdfShape
|
|
217
191
|
```
|
|
218
192
|
|
|
219
|
-
|
|
193
|
+
**`PerforatedOptions`**
|
|
194
|
+
- `radius?: number` — Hole radius. Default: 3
|
|
195
|
+
- `spacing?: number` — Center-to-center spacing. Default: 8
|
|
220
196
|
|
|
221
|
-
|
|
197
|
+
#### `sdf.scales()` — Fish/dragon scale pattern — overlapping circular scales in hex-packed rows.
|
|
222
198
|
|
|
223
199
|
```ts
|
|
224
|
-
|
|
225
|
-
/** Cap height of the text in model units. All other dimensions (stroke weight, spacing) scale proportionally. */
|
|
226
|
-
size?: number;
|
|
227
|
-
/** Extra space between characters in model units. Negative values tighten the tracking. */
|
|
228
|
-
letterSpacing?: number;
|
|
229
|
-
/** Horizontal alignment relative to x = 0. - `'left'` — left edge at x = 0 (default) - `'center'` — centred on x = 0 - `'right'` — right edge at x = 0 */
|
|
230
|
-
align?: "left" | "center" | "right";
|
|
231
|
-
/** 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 */
|
|
232
|
-
baseline?: "baseline" | "center" | "top";
|
|
233
|
-
/** 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' }) */
|
|
234
|
-
font?: string | opentype$1.Font;
|
|
235
|
-
/** Bezier flattening tolerance in model units. Smaller = more polygon segments = smoother curves. */
|
|
236
|
-
flattenTolerance?: number;
|
|
237
|
-
}
|
|
200
|
+
sdf.scales(options?: ScalesOptions): SdfShape
|
|
238
201
|
```
|
|
239
202
|
|
|
240
|
-
|
|
203
|
+
**`ScalesOptions`**
|
|
204
|
+
- `size?: number` — Scale diameter. Default: 5
|
|
205
|
+
- `depth?: number` — How much scales protrude. Default: 0.8
|
|
241
206
|
|
|
242
|
-
#### `
|
|
207
|
+
#### `sdf.brick()` — Brick/stone wall pattern — running bond with mortar grooves.
|
|
243
208
|
|
|
244
209
|
```ts
|
|
245
|
-
|
|
210
|
+
sdf.brick(options?: BrickOptions): SdfShape
|
|
246
211
|
```
|
|
247
212
|
|
|
248
|
-
|
|
213
|
+
**`BrickOptions`**
|
|
249
214
|
|
|
250
|
-
|
|
215
|
+
| Option | Type | Description |
|
|
216
|
+
|--------|------|-------------|
|
|
217
|
+
| `width?` | `number` | Brick width. Default: 10 |
|
|
218
|
+
| `height?` | `number` | Brick height. Default: 5 |
|
|
219
|
+
| `depth?` | `number` | Mortar groove depth. Default: 0.5 |
|
|
220
|
+
| `mortar?` | `number` | Mortar gap width. Default: 1 |
|
|
251
221
|
|
|
252
|
-
|
|
222
|
+
#### `sdf.weave()` — Grid lattice pattern — two families of infinite slabs crossing at 90°.
|
|
253
223
|
|
|
254
|
-
|
|
224
|
+
```ts
|
|
225
|
+
sdf.weave(options?: WeaveOptions): SdfShape
|
|
226
|
+
```
|
|
255
227
|
|
|
256
|
-
|
|
228
|
+
**`WeaveOptions`**
|
|
229
|
+
- `spacing?: number` — Thread center-to-center spacing (for intersection patterns). Default: 5
|
|
230
|
+
- `threadRadius?: number` — Thread half-width. Default: 1
|
|
231
|
+
|
|
232
|
+
#### `sdf.basketWeave()` — Basket weave surface pattern — threads with over-under crossings in UV space. Returns a SurfacePattern for use with `.surfaceDisplace()`.
|
|
257
233
|
|
|
258
234
|
```ts
|
|
259
|
-
|
|
235
|
+
sdf.basketWeave(options?: BasketWeaveOptions): SurfacePattern
|
|
260
236
|
```
|
|
261
237
|
|
|
262
|
-
|
|
238
|
+
**`BasketWeaveOptions`**
|
|
239
|
+
- `spacing?: number` — Spacing between threads in mm (both directions). Default: 3
|
|
240
|
+
- `threadWidth?: number` — Thread width in mm. Default: 1.5
|
|
241
|
+
- `depth?: number` — Thread protrusion depth in mm. Default: 0.8
|
|
263
242
|
|
|
264
|
-
#### `
|
|
243
|
+
#### `sdf.twist()` — Twist an SDF shape around the Z axis.
|
|
265
244
|
|
|
266
245
|
```ts
|
|
267
|
-
|
|
246
|
+
sdf.twist(shape: SdfShape, degreesPerUnit: number): SdfShape
|
|
268
247
|
```
|
|
269
248
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
#### `union2d()`
|
|
249
|
+
#### `sdf.bend()` — Bend an SDF shape around the Z axis.
|
|
273
250
|
|
|
274
251
|
```ts
|
|
275
|
-
|
|
252
|
+
sdf.bend(shape: SdfShape, radius: number): SdfShape
|
|
276
253
|
```
|
|
277
254
|
|
|
278
|
-
|
|
255
|
+
#### `sdf.repeat()` — Repeat an SDF shape in space.
|
|
279
256
|
|
|
280
|
-
|
|
257
|
+
```ts
|
|
258
|
+
sdf.repeat(shape: SdfShape, spacing: Vec3, count?: Vec3): SdfShape
|
|
259
|
+
```
|
|
281
260
|
|
|
282
|
-
|
|
261
|
+
#### `sdf.SurfacePattern()` — A 2D surface pattern — a heightmap function for use with `.surfaceDisplace()`.
|
|
283
262
|
|
|
284
|
-
|
|
263
|
+
```ts
|
|
264
|
+
sdf.SurfacePattern: typeof SurfacePattern
|
|
265
|
+
```
|
|
285
266
|
|
|
286
|
-
#### `
|
|
267
|
+
#### `sdf.fromFunction()` — Create an SDF shape from an arbitrary distance function. You must provide bounds since the function is opaque.
|
|
287
268
|
|
|
288
269
|
```ts
|
|
289
|
-
|
|
270
|
+
sdf.fromFunction(fn: (x: number, y: number, z: number) => number, bounds: { min: Vec3; max: Vec3; }, constants?: Record<string, number>): SdfShape
|
|
290
271
|
```
|
|
291
272
|
|
|
292
|
-
|
|
273
|
+
#### `circle2d()` — Create a 2D circle centered at the origin.
|
|
293
274
|
|
|
294
|
-
|
|
275
|
+
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.
|
|
295
276
|
|
|
296
277
|
```ts
|
|
297
|
-
|
|
278
|
+
circle2d(25).extrude(10); // smooth cylinder
|
|
279
|
+
circle2d(25, 6).extrude(10); // hexagonal prism
|
|
298
280
|
```
|
|
299
281
|
|
|
300
|
-
|
|
282
|
+
```ts
|
|
283
|
+
circle2d(radius: number, segments?: number): Sketch
|
|
284
|
+
```
|
|
301
285
|
|
|
302
|
-
#### `
|
|
286
|
+
#### `ellipse()` — Create a 2D ellipse centered at the origin.
|
|
303
287
|
|
|
304
288
|
```ts
|
|
305
|
-
|
|
289
|
+
ellipse(30, 15).extrude(5);
|
|
290
|
+
ellipse(30, 15, 32).extrude(5); // lower-resolution approximation
|
|
306
291
|
```
|
|
307
292
|
|
|
308
|
-
|
|
293
|
+
```ts
|
|
294
|
+
ellipse(rx: number, ry: number, segments?: number): Sketch
|
|
295
|
+
```
|
|
309
296
|
|
|
310
|
-
|
|
297
|
+
#### `loadFont()` — Pre-load and cache a font for use with `text2d()`.
|
|
311
298
|
|
|
312
|
-
|
|
299
|
+
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.
|
|
313
300
|
|
|
314
|
-
|
|
301
|
+
Built-in font names that work everywhere (browser + CLI):
|
|
315
302
|
|
|
316
|
-
|
|
303
|
+
- `'sans-serif'` or `'inter'` — bundled Inter Regular
|
|
317
304
|
|
|
318
305
|
```ts
|
|
319
|
-
|
|
306
|
+
const font = loadFont('/path/to/Arial Bold.ttf');
|
|
307
|
+
text2d('Title', { size: 12, font }).extrude(1.5);
|
|
308
|
+
text2d('Subtitle', { size: 8, font }).extrude(1);
|
|
320
309
|
```
|
|
321
310
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
```ts
|
|
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
|
-
}
|
|
311
|
+
```ts
|
|
312
|
+
loadFont(source: string | ArrayBuffer, cacheKey?: string): opentype.Font
|
|
345
313
|
```
|
|
346
314
|
|
|
347
|
-
|
|
315
|
+
#### `ngon()` — Create a regular polygon inscribed in a circle of the given radius.
|
|
348
316
|
|
|
349
|
-
|
|
317
|
+
`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).
|
|
350
318
|
|
|
351
319
|
```ts
|
|
352
|
-
|
|
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
|
-
}
|
|
320
|
+
ngon(6, 20).extrude(10); // hexagonal prism, circumradius 20
|
|
360
321
|
```
|
|
361
322
|
|
|
362
|
-
</details>
|
|
363
|
-
|
|
364
|
-
<details><summary><code>TransitionSurfaceOptions</code> extends TransitionCurveOptions</summary>
|
|
365
|
-
|
|
366
323
|
```ts
|
|
367
|
-
|
|
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
|
-
}
|
|
324
|
+
ngon(sides: number, radius: number): Sketch
|
|
381
325
|
```
|
|
382
326
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
<details><summary><code>ConnectEdgesOptions</code> extends TransitionSurfaceOptions</summary>
|
|
386
|
-
|
|
387
|
-
```ts
|
|
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
|
-
```
|
|
327
|
+
#### `path()` — Create a new `PathBuilder` for tracing a 2D outline point by point.
|
|
407
328
|
|
|
408
|
-
|
|
329
|
+
`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.
|
|
409
330
|
|
|
410
|
-
|
|
331
|
+
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`.
|
|
411
332
|
|
|
412
333
|
```ts
|
|
413
|
-
|
|
414
|
-
|
|
334
|
+
// Closed triangle
|
|
335
|
+
const triangle = path().moveTo(0, 0).lineH(50).lineV(30).close();
|
|
415
336
|
|
|
416
|
-
|
|
337
|
+
// L-shaped bracket as a stroke
|
|
338
|
+
const bracket = path().moveTo(0, 0).lineH(50).lineV(-70).lineAngled(20, 235).stroke(4);
|
|
417
339
|
|
|
418
|
-
|
|
340
|
+
// Labeled edges for downstream face references
|
|
341
|
+
const slot = path()
|
|
342
|
+
.moveTo(0, 0)
|
|
343
|
+
.lineTo(30, 0).label('bottom')
|
|
344
|
+
.lineTo(30, 10)
|
|
345
|
+
.lineTo(0, 10).label('top')
|
|
346
|
+
.close();
|
|
347
|
+
```
|
|
419
348
|
|
|
420
349
|
```ts
|
|
421
|
-
|
|
422
|
-
/** Connection point on the edge */
|
|
423
|
-
point: Vec3$5;
|
|
424
|
-
/** Tangent direction along the edge at the connection point */
|
|
425
|
-
tangent: Vec3$5;
|
|
426
|
-
/** Surface normal at the connection point (optional, for future G2 support) */
|
|
427
|
-
normal?: Vec3$5;
|
|
428
|
-
/** Weight controlling how far the curve follows this edge's tangent. Default 1.0. */
|
|
429
|
-
weight?: number;
|
|
430
|
-
}
|
|
350
|
+
path(): PathBuilder
|
|
431
351
|
```
|
|
432
352
|
|
|
433
|
-
|
|
353
|
+
#### `polygon()` — Create a 2D polygon from an array of `[x, y]` points or `Point2D` objects.
|
|
434
354
|
|
|
435
|
-
|
|
355
|
+
Winding order is normalized automatically — clockwise (CW) input is silently reversed to CCW before being passed to the geometry kernel.
|
|
436
356
|
|
|
437
357
|
```ts
|
|
438
|
-
|
|
358
|
+
polygon([[0, 0], [50, 0], [25, 40]]).extrude(5); // triangle
|
|
439
359
|
```
|
|
440
360
|
|
|
441
|
-
Create a quintic Hermite transition curve between two edge endpoints (G2 continuity). 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.
|
|
442
|
-
|
|
443
|
-
<details><summary><code>QuinticHermiteCurveEndpoint</code></summary>
|
|
444
|
-
|
|
445
361
|
```ts
|
|
446
|
-
|
|
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
|
-
}
|
|
362
|
+
polygon(points: ([ number, number ] | Point2D)[]): Sketch
|
|
456
363
|
```
|
|
457
364
|
|
|
458
|
-
|
|
365
|
+
#### `polygonVertices()` — Compute the vertex positions of a regular polygon.
|
|
459
366
|
|
|
460
|
-
|
|
367
|
+
Default orientation places the first vertex at the top (90 degrees), matching the convention used by `ngon()`.
|
|
461
368
|
|
|
462
|
-
|
|
463
|
-
loft(profiles: Sketch[], heights: number[], options?: LoftOptions): Shape
|
|
464
|
-
```
|
|
369
|
+
Eliminates manual Math.sqrt(3) for triangles, pentagon vertex math, etc:
|
|
465
370
|
|
|
466
|
-
|
|
371
|
+
```js
|
|
372
|
+
// Before — manual equilateral triangle
|
|
373
|
+
const v1 = [center.x - r/2, center.y + r * Math.sqrt(3)/2];
|
|
374
|
+
const v2 = [center.x - r/2, center.y - r * Math.sqrt(3)/2];
|
|
375
|
+
const v3 = [center.x + r, center.y];
|
|
467
376
|
|
|
468
|
-
|
|
377
|
+
// After — declarative
|
|
378
|
+
const [v1, v2, v3] = polygonVertices(3, r);
|
|
379
|
+
```
|
|
469
380
|
|
|
470
381
|
```ts
|
|
471
|
-
|
|
472
|
-
/** Marching-grid edge length for level-set meshing. Smaller = finer. */
|
|
473
|
-
edgeLength?: number;
|
|
474
|
-
/** Optional extra bounds padding. */
|
|
475
|
-
boundsPadding?: number;
|
|
476
|
-
}
|
|
382
|
+
polygonVertices(sides: number, radius: number, options?: PolygonVerticesOptions): LayoutPoint[]
|
|
477
383
|
```
|
|
478
384
|
|
|
479
|
-
|
|
385
|
+
**`PolygonVerticesOptions`**
|
|
386
|
+
- `startDeg?: number` — Angle of the first vertex in degrees (default: 90 = top).
|
|
387
|
+
- `centerX?: number` — Center X coordinate (default: 0).
|
|
388
|
+
- `centerY?: number` — Center Y coordinate (default: 0).
|
|
389
|
+
|
|
390
|
+
`LayoutPoint`: `{ x: number, y: number }`
|
|
480
391
|
|
|
481
|
-
#### `
|
|
392
|
+
#### `rect()` — Create a 2D rectangle centered at the origin.
|
|
482
393
|
|
|
483
394
|
```ts
|
|
484
|
-
|
|
395
|
+
rect(40, 20).extrude(5);
|
|
485
396
|
```
|
|
486
397
|
|
|
487
|
-
Loft between multiple profiles positioned along an arbitrary 3D spine curve. 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. 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]. Internally uses variableSweep infrastructure with SDF interpolation. Performance note: uses level-set meshing, heavier than simple loft().
|
|
488
|
-
|
|
489
|
-
<details><summary><code>LoftAlongSpineOptions</code></summary>
|
|
490
|
-
|
|
491
398
|
```ts
|
|
492
|
-
|
|
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
|
-
}
|
|
399
|
+
rect(width: number, height: number): Sketch
|
|
502
400
|
```
|
|
503
401
|
|
|
504
|
-
|
|
402
|
+
#### `arcSlot()` — Create an arc-shaped slot (banana / annular sector) centered at the origin.
|
|
505
403
|
|
|
506
|
-
|
|
404
|
+
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.
|
|
507
405
|
|
|
508
406
|
```ts
|
|
509
|
-
|
|
407
|
+
arcSlot(135, 74, 40).extrude(5); // pitch R135, 74° sweep, 40mm wide
|
|
510
408
|
```
|
|
511
409
|
|
|
512
|
-
Create a reusable 3D spline curve object (Catmull-Rom). The returned Curve3D provides sample(), pointAt(t), tangentAt(t), and length() for downstream use in sweep() or manual path operations.
|
|
513
|
-
|
|
514
|
-
<details><summary><code>Spline3DOptions</code></summary>
|
|
515
|
-
|
|
516
410
|
```ts
|
|
517
|
-
|
|
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
|
-
}
|
|
411
|
+
arcSlot(pitchRadius: number, sweepDeg: number, thickness: number): Sketch
|
|
523
412
|
```
|
|
524
413
|
|
|
525
|
-
|
|
414
|
+
#### `roundedRect()` — Create a 2D rectangle with rounded corners, centered at the origin.
|
|
526
415
|
|
|
527
|
-
|
|
416
|
+
The corner radius is automatically clamped to `min(width/2, height/2)` so it can never exceed the shape dimensions.
|
|
528
417
|
|
|
529
418
|
```ts
|
|
530
|
-
|
|
419
|
+
roundedRect(60, 30, 5).extrude(3);
|
|
531
420
|
```
|
|
532
421
|
|
|
533
|
-
Create a smooth surface patch from 4 boundary curves (Coons patch). 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) The interior is filled using bilinear Coons patch interpolation: P(u,v) = Lc(u,v) + Ld(u,v) - B(u,v) The result is a thin solid created by offsetting the surface mesh along its normals by the specified thickness. Note: curves should meet at corners. Small gaps are tolerated.
|
|
534
|
-
|
|
535
|
-
<details><summary><code>SurfacePatchOptions</code></summary>
|
|
536
|
-
|
|
537
422
|
```ts
|
|
538
|
-
|
|
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
|
-
}
|
|
423
|
+
roundedRect(width: number, height: number, radius: number): Sketch
|
|
544
424
|
```
|
|
545
425
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
#### `sweep()`
|
|
426
|
+
#### `slot()` — Create a slot (oblong / stadium shape) — a rectangle with semicircular ends, centered at the origin.
|
|
549
427
|
|
|
550
428
|
```ts
|
|
551
|
-
|
|
429
|
+
slot(40, 10).extrude(3); // 40mm long, 10mm wide slot
|
|
552
430
|
```
|
|
553
431
|
|
|
554
|
-
Sweep a 2D profile along a 3D path to create a solid. 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. Performance note: sweep uses level-set meshing internally. Prefer direct primitives/extrude/revolve when they can express the same shape.
|
|
555
|
-
|
|
556
|
-
<details><summary><code>SweepOptions</code></summary>
|
|
557
|
-
|
|
558
432
|
```ts
|
|
559
|
-
|
|
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
|
-
}
|
|
433
|
+
slot(length: number, width: number): Sketch
|
|
569
434
|
```
|
|
570
435
|
|
|
571
|
-
|
|
436
|
+
#### `spline2d()` — Build a smooth Catmull-Rom spline sketch from 2D control points.
|
|
572
437
|
|
|
573
|
-
|
|
438
|
+
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.
|
|
574
439
|
|
|
575
440
|
```ts
|
|
576
|
-
|
|
441
|
+
spline2d(points: Vec2[], options?: Spline2DOptions): Sketch
|
|
577
442
|
```
|
|
578
443
|
|
|
579
|
-
|
|
444
|
+
**`Spline2DOptions`**
|
|
580
445
|
|
|
581
|
-
|
|
446
|
+
| Option | Type | Description |
|
|
447
|
+
|--------|------|-------------|
|
|
448
|
+
| `closed?` | `boolean` | Closed loop (default true). |
|
|
449
|
+
| `tension?` | `number` | Catmull-Rom tension in [0, 1]. 0 = very round, 1 = linear-ish. Default 0.5. |
|
|
450
|
+
| `samplesPerSegment?` | `number` | Samples per segment (minimum 3). Default 16. |
|
|
451
|
+
| `strokeWidth?` | `number` | For open splines, provide stroke width to return a solid Sketch. If omitted for open splines, an error is thrown. |
|
|
452
|
+
| `join?` | `"Round" | "Square"` | Stroke join for open splines. Default 'Round'. |
|
|
453
|
+
|
|
454
|
+
#### `star()` — Create a star shape with alternating outer and inner radii.
|
|
582
455
|
|
|
583
456
|
```ts
|
|
584
|
-
|
|
585
|
-
/** Parameter along the spine (0 = start, 1 = end). */
|
|
586
|
-
t: number;
|
|
587
|
-
/** Cross-section profile at this station. */
|
|
588
|
-
profile: Sketch;
|
|
589
|
-
}
|
|
457
|
+
star(5, 30, 12).extrude(4); // five-pointed star
|
|
590
458
|
```
|
|
591
459
|
|
|
592
|
-
</details>
|
|
593
|
-
|
|
594
|
-
<details><summary><code>VariableSweepOptions</code></summary>
|
|
595
|
-
|
|
596
460
|
```ts
|
|
597
|
-
|
|
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
|
-
}
|
|
461
|
+
star(points: number, outerR: number, innerR: number): Sketch
|
|
607
462
|
```
|
|
608
463
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
#### `transitionCurve()`
|
|
464
|
+
#### `stroke()` — Create a stroked polyline sketch from an array of 2D points.
|
|
612
465
|
|
|
613
466
|
```ts
|
|
614
|
-
|
|
467
|
+
stroke(points: [ number, number ][], width: number, join?: "Round" | "Square"): Sketch
|
|
615
468
|
```
|
|
616
469
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
<details><summary><code>TransitionEdge</code></summary>
|
|
470
|
+
#### `text2d()` — Build a filled 2D Sketch from a text string.
|
|
620
471
|
|
|
621
|
-
|
|
622
|
-
interface TransitionEdge {
|
|
623
|
-
/** Connection point on the edge. Can be any point along the edge where the transition should connect. */
|
|
624
|
-
point: Vec3$7;
|
|
625
|
-
/** 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). */
|
|
626
|
-
tangent: Vec3$7;
|
|
627
|
-
/** Surface normal at the connection point (optional). Used as a hint for the sweep frame's up vector. */
|
|
628
|
-
normal?: Vec3$7;
|
|
629
|
-
}
|
|
630
|
-
```
|
|
472
|
+
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.
|
|
631
473
|
|
|
632
|
-
|
|
474
|
+
Alignment reference table:
|
|
633
475
|
|
|
634
|
-
|
|
476
|
+
| `align` | `baseline` | Origin | |------------|--------------|-------------------------------------| | `'left'` | `'baseline'` | Bottom-left of first char (default) | | `'center'` | `'center'` | Dead center of text block | | `'right'` | `'top'` | Top-right corner |
|
|
635
477
|
|
|
636
478
|
```ts
|
|
637
|
-
|
|
638
|
-
|
|
479
|
+
// Extruded nameplate
|
|
480
|
+
text2d('FORGE CAD', { size: 8 }).extrude(1.2);
|
|
639
481
|
|
|
640
|
-
|
|
482
|
+
// Centered label on the XY plane
|
|
483
|
+
text2d('V 2.0', { size: 6, align: 'center', baseline: 'center' });
|
|
641
484
|
|
|
642
|
-
|
|
485
|
+
// Engraved text cut into the top face of a box
|
|
486
|
+
const label = text2d('REV A', { size: 5, align: 'center', baseline: 'center' });
|
|
487
|
+
plate.subtract(label.onFace(plate, 'top', { protrude: -0.5 }).extrude(1));
|
|
643
488
|
|
|
644
|
-
|
|
645
|
-
|
|
489
|
+
// Custom TTF font
|
|
490
|
+
text2d('Hello', { size: 10, font: '/path/to/Arial.ttf' }).extrude(1);
|
|
491
|
+
|
|
492
|
+
// Pre-loaded font for reuse
|
|
493
|
+
const font = loadFont('/path/to/Arial Bold.ttf');
|
|
494
|
+
text2d('Title', { size: 12, font }).extrude(1.5);
|
|
646
495
|
```
|
|
647
496
|
|
|
648
|
-
|
|
497
|
+
```ts
|
|
498
|
+
text2d(content: string, options?: TextOptions): Sketch
|
|
499
|
+
```
|
|
649
500
|
|
|
650
|
-
|
|
501
|
+
**`TextOptions`**
|
|
651
502
|
|
|
652
|
-
|
|
503
|
+
| Option | Type | Description |
|
|
504
|
+
|--------|------|-------------|
|
|
505
|
+
| `size?` | `number` | Cap height of the text in model units. All other dimensions (stroke weight, spacing) scale proportionally. |
|
|
506
|
+
| `letterSpacing?` | `number` | Extra space between characters in model units. Negative values tighten the tracking. |
|
|
507
|
+
| `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 |
|
|
508
|
+
| `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 |
|
|
509
|
+
| `font?` | `string | opentype.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' }) |
|
|
510
|
+
| `flattenTolerance?` | `number` | Bezier flattening tolerance in model units. Smaller = more polygon segments = smoother curves. |
|
|
653
511
|
|
|
654
|
-
|
|
512
|
+
#### `textWidth()` — Measure the rendered advance width of a string without creating any geometry.
|
|
655
513
|
|
|
656
|
-
|
|
514
|
+
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.
|
|
657
515
|
|
|
658
516
|
```ts
|
|
659
|
-
|
|
517
|
+
const w = textWidth('SERIAL: 001', { size: 6 });
|
|
518
|
+
const plate = box(w + 10, 12, 2);
|
|
660
519
|
```
|
|
661
520
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
<details><summary><code>EdgeSegment</code></summary>
|
|
665
|
-
|
|
666
|
-
```ts
|
|
667
|
-
interface EdgeSegment {
|
|
668
|
-
/** Stable index within the extraction (deterministic for a given mesh). */
|
|
669
|
-
index: number;
|
|
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;
|
|
686
|
-
}
|
|
521
|
+
```ts
|
|
522
|
+
textWidth(content: string, options?: Pick<TextOptions, "size" | "letterSpacing" | "font">): number
|
|
687
523
|
```
|
|
688
524
|
|
|
689
|
-
|
|
525
|
+
#### `box()` — Create a rectangular box. Centered on XY, base at Z=0.
|
|
690
526
|
|
|
691
|
-
|
|
527
|
+
For named faces, build from a labeled sketch: `rect(x, y).labelEdges('s', 'e', 'n', 'w').extrude(z, { labels: { start: 'bottom', end: 'top' } })`.
|
|
692
528
|
|
|
693
529
|
```ts
|
|
694
|
-
|
|
695
|
-
```
|
|
696
|
-
|
|
697
|
-
Select the single best-matching edge from a shape. When `near` is specified, returns the closest matching edge. Otherwise returns the first matching edge (by mesh order). Throws if no edges match.
|
|
698
|
-
|
|
699
|
-
<details><summary><code>EdgeQuery</code></summary>
|
|
700
|
-
|
|
701
|
-
```ts
|
|
702
|
-
interface EdgeQuery {
|
|
703
|
-
/** Sort by proximity to this point (closest first). */
|
|
704
|
-
near?: Vec3;
|
|
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
|
-
}
|
|
530
|
+
box(x: number, y: number, z: number): Shape
|
|
730
531
|
```
|
|
731
532
|
|
|
732
|
-
|
|
533
|
+
#### `cylinder()` — Create a cylinder or cone with named faces and edges. Centered on XY, base at Z=0.
|
|
733
534
|
|
|
734
|
-
|
|
535
|
+
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.
|
|
735
536
|
|
|
736
537
|
```ts
|
|
737
|
-
|
|
738
|
-
xMin?: number;
|
|
739
|
-
xMax?: number;
|
|
740
|
-
yMin?: number;
|
|
741
|
-
yMax?: number;
|
|
742
|
-
zMin?: number;
|
|
743
|
-
zMax?: number;
|
|
744
|
-
}
|
|
538
|
+
cylinder(height: number, radius: number, radiusTop?: number, segments?: number): Shape
|
|
745
539
|
```
|
|
746
540
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
#### `selectEdges()`
|
|
541
|
+
#### `sphere()` — Create a sphere centered at the origin. Use segments for lower-poly approximations.
|
|
750
542
|
|
|
751
543
|
```ts
|
|
752
|
-
|
|
544
|
+
sphere(radius: number, segments?: number): Shape
|
|
753
545
|
```
|
|
754
546
|
|
|
755
|
-
|
|
547
|
+
#### `torus()` — Create a torus (donut shape) lying in the XY plane. Centered on all axes (origin is the ring center).
|
|
548
|
+
|
|
549
|
+
```ts
|
|
550
|
+
torus(majorRadius: number, minorRadius: number, segments?: number): Shape
|
|
551
|
+
```
|
|
756
552
|
|
|
757
553
|
---
|
|
758
554
|
|
|
759
|
-
##
|
|
555
|
+
## C2: Boolean Combination
|
|
760
556
|
|
|
761
|
-
|
|
557
|
+
Combine same-dimension geometry using CSG set operations.
|
|
558
|
+
|
|
559
|
+
#### `difference2d()` — Subtract one or more 2D sketches from a base sketch.
|
|
762
560
|
|
|
763
|
-
|
|
561
|
+
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.
|
|
764
562
|
|
|
765
563
|
```ts
|
|
766
|
-
|
|
564
|
+
const donut = difference2d(circle2d(50), circle2d(30));
|
|
767
565
|
```
|
|
768
566
|
|
|
769
|
-
Apply a fillet (rounded edge) to a mesh-selected edge. Works on any straight edge of any shape — not limited to tracked box edges. The edge must have been obtained from selectEdge() / selectEdges().
|
|
770
|
-
|
|
771
|
-
<details><summary><code>EdgeSegment</code></summary>
|
|
772
|
-
|
|
773
567
|
```ts
|
|
774
|
-
|
|
775
|
-
/** Stable index within the extraction (deterministic for a given mesh). */
|
|
776
|
-
index: number;
|
|
777
|
-
start: Vec3;
|
|
778
|
-
end: Vec3;
|
|
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
|
-
}
|
|
568
|
+
difference2d(...inputs: SketchOperandInput[]): Sketch
|
|
794
569
|
```
|
|
795
570
|
|
|
796
|
-
|
|
571
|
+
#### `intersection2d()` — Keep only the area where all input sketches overlap (intersection boolean).
|
|
797
572
|
|
|
798
|
-
|
|
573
|
+
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.
|
|
799
574
|
|
|
800
575
|
```ts
|
|
801
|
-
|
|
576
|
+
const lens = intersection2d(circle2d(30).translate(-10, 0), circle2d(30).translate(10, 0));
|
|
802
577
|
```
|
|
803
578
|
|
|
804
|
-
Apply a chamfer (beveled edge) to a mesh-selected edge. Works on any straight edge of any shape — not limited to tracked box edges.
|
|
805
|
-
|
|
806
|
-
#### `chamfer()`
|
|
807
|
-
|
|
808
579
|
```ts
|
|
809
|
-
|
|
580
|
+
intersection2d(...inputs: SketchOperandInput[]): Sketch
|
|
810
581
|
```
|
|
811
582
|
|
|
812
|
-
|
|
583
|
+
#### `union2d()` — Combine 2D sketches into a single profile using an additive boolean union.
|
|
813
584
|
|
|
814
|
-
|
|
585
|
+
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.
|
|
815
586
|
|
|
816
587
|
```ts
|
|
817
|
-
|
|
588
|
+
const cross = union2d(rect(60, 10), rect(10, 60));
|
|
818
589
|
```
|
|
819
590
|
|
|
820
|
-
Apply a draft angle (taper) to all faces of a solid for mold extraction. Draft angle is a manufacturing feature that adds taper to the vertical faces of a solid so that it can be extracted from a mold. The neutral plane is where the draft angle is zero — faces above and below are tapered symmetrically. Requires the OCCT backend. Throws on Manifold. // Add 3° draft to a box for injection molding draft(myBox, 3) // Draft with custom pull direction and neutral plane draft(myShape, 2, [0, 0, 1], 10)
|
|
821
|
-
|
|
822
|
-
#### `fillet()`
|
|
823
|
-
|
|
824
591
|
```ts
|
|
825
|
-
|
|
592
|
+
union2d(...inputs: SketchOperandInput[]): Sketch
|
|
826
593
|
```
|
|
827
594
|
|
|
828
|
-
|
|
595
|
+
#### `union()` — Combine shapes into a single solid (additive boolean).
|
|
829
596
|
|
|
830
|
-
|
|
597
|
+
Accepts individual shapes, or an array of shapes. The first operand's color is preserved in the result.
|
|
831
598
|
|
|
832
599
|
```ts
|
|
833
|
-
|
|
600
|
+
union(...inputs: ShapeOperandInput[]): Shape
|
|
834
601
|
```
|
|
835
602
|
|
|
836
|
-
|
|
603
|
+
#### `difference()` — Subtract shapes from a base shape (subtractive boolean).
|
|
837
604
|
|
|
838
|
-
|
|
605
|
+
The first shape is the base; all subsequent shapes are subtracted from it. Accepts individual shapes, or an array of shapes.
|
|
839
606
|
|
|
840
607
|
```ts
|
|
841
|
-
|
|
608
|
+
difference(...inputs: ShapeOperandInput[]): Shape
|
|
842
609
|
```
|
|
843
610
|
|
|
844
|
-
|
|
611
|
+
#### `intersection()` — Keep only the overlapping volume of the input shapes (intersection boolean).
|
|
845
612
|
|
|
846
|
-
|
|
613
|
+
Requires at least two shapes. Accepts individual shapes, or an array.
|
|
847
614
|
|
|
848
615
|
```ts
|
|
849
|
-
|
|
850
|
-
index: number;
|
|
851
|
-
radius: number;
|
|
852
|
-
segments?: number;
|
|
853
|
-
}
|
|
616
|
+
intersection(...inputs: ShapeOperandInput[]): Shape
|
|
854
617
|
```
|
|
855
618
|
|
|
856
|
-
</details>
|
|
857
|
-
|
|
858
619
|
---
|
|
859
620
|
|
|
860
|
-
##
|
|
621
|
+
## C3: Rigid Transform
|
|
861
622
|
|
|
862
|
-
|
|
623
|
+
Reposition or reorient geometry without changing its shape.
|
|
624
|
+
|
|
625
|
+
#### `degrees()` — Identity function that returns degrees unchanged.
|
|
863
626
|
|
|
864
|
-
|
|
627
|
+
Use for clarity when the unit of an angle value would otherwise be ambiguous — e.g. `param("Angle", degrees(45))`.
|
|
865
628
|
|
|
866
629
|
```ts
|
|
867
|
-
|
|
630
|
+
degrees(deg: number): number
|
|
868
631
|
```
|
|
869
632
|
|
|
870
|
-
|
|
633
|
+
#### `radians()` — Convert radians to degrees.
|
|
871
634
|
|
|
872
|
-
|
|
635
|
+
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.
|
|
873
636
|
|
|
874
637
|
```ts
|
|
875
|
-
|
|
876
|
-
/** Angle of the first element in degrees (default: 0 = +X axis). */
|
|
877
|
-
startDeg?: number;
|
|
878
|
-
/** Center X coordinate (default: 0). */
|
|
879
|
-
centerX?: number;
|
|
880
|
-
/** Center Y coordinate (default: 0). */
|
|
881
|
-
centerY?: number;
|
|
882
|
-
}
|
|
638
|
+
radians(rad: number): number
|
|
883
639
|
```
|
|
884
640
|
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
<details><summary><code>LayoutPoint</code></summary>
|
|
641
|
+
#### `composeChain()` — Compose transforms in chain order. Equivalent to Transform.identity().mul(a).mul(b).mul(c)...
|
|
888
642
|
|
|
889
643
|
```ts
|
|
890
|
-
|
|
891
|
-
x: number;
|
|
892
|
-
y: number;
|
|
893
|
-
}
|
|
644
|
+
composeChain(...steps: TransformInput[]): Transform
|
|
894
645
|
```
|
|
895
646
|
|
|
896
|
-
|
|
647
|
+
---
|
|
897
648
|
|
|
898
|
-
|
|
649
|
+
## C4: Dimensional Promotion
|
|
899
650
|
|
|
900
|
-
|
|
901
|
-
circularPattern(shape: Shape, count: number, centerXOrOpts?: number | CircularPatternOptions, centerY?: number): Shape
|
|
902
|
-
```
|
|
651
|
+
Convert a 2D profile into a 3D solid (extrude, revolve, loft, sweep).
|
|
903
652
|
|
|
904
|
-
|
|
653
|
+
#### `nurbs3d()` — Create a NURBS curve from control points.
|
|
905
654
|
|
|
906
|
-
|
|
655
|
+
With default options, creates a cubic non-rational B-spline with uniform clamped knots. Set `weights` for rational curves (exact circles, conics). Set `degree` for linear (1), quadratic (2), cubic (3), or higher-order curves.
|
|
907
656
|
|
|
908
|
-
```
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
/** Center Y of the rotation (default: 0). Used when axis is Z (legacy mode). */
|
|
913
|
-
centerY?: number;
|
|
914
|
-
}
|
|
657
|
+
```js
|
|
658
|
+
// Simple cubic B-spline through control points
|
|
659
|
+
const curve = nurbs3d([[0,0,0], [10,5,0], [20,-5,10], [30,0,5]]);
|
|
660
|
+
const tube = sweep(circle(2), curve);
|
|
915
661
|
```
|
|
916
662
|
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
663
|
+
```js
|
|
664
|
+
// Rational quadratic — exact circular arc
|
|
665
|
+
const arc = nurbs3d(
|
|
666
|
+
[[10,0,0], [10,10,0], [0,10,0]],
|
|
667
|
+
{ degree: 2, weights: [1, Math.SQRT1_2, 1] }
|
|
668
|
+
);
|
|
669
|
+
```
|
|
920
670
|
|
|
921
671
|
```ts
|
|
922
|
-
|
|
672
|
+
nurbs3d(points: Vec3[], options?: NurbsCurve3DOptions): NurbsCurve3D
|
|
923
673
|
```
|
|
924
674
|
|
|
925
|
-
|
|
675
|
+
**`NurbsCurve3DOptions`**
|
|
926
676
|
|
|
927
|
-
|
|
677
|
+
| Option | Type | Description |
|
|
678
|
+
|--------|------|-------------|
|
|
679
|
+
| `degree?` | `number` | Polynomial degree (default 3 = cubic). Must be ≥ 1. |
|
|
680
|
+
| `weights?` | `number[]` | Rational weights, one per control point (default: all 1.0 = non-rational). |
|
|
681
|
+
| `knots?` | `number[]` | Knot vector (default: uniform clamped). Must have length = controlPoints.length + degree + 1. |
|
|
682
|
+
| `closed?` | `boolean` | Whether the curve is closed/periodic (default false). |
|
|
928
683
|
|
|
929
|
-
|
|
930
|
-
linearPattern(shape: Shape, count: number, dx: number, dy: number, dz?: number): Shape
|
|
931
|
-
```
|
|
684
|
+
#### `nurbsSurface()` — Create a NURBS surface from a grid of control points.
|
|
932
685
|
|
|
933
|
-
|
|
686
|
+
The control grid is indexed as `controlGrid[u][v]` — each row is a curve in the V direction, and columns trace curves in the U direction.
|
|
934
687
|
|
|
935
|
-
|
|
688
|
+
With default options, creates a bicubic non-rational B-spline surface with uniform clamped knots.
|
|
936
689
|
|
|
937
|
-
```
|
|
938
|
-
|
|
690
|
+
```js
|
|
691
|
+
// Simple 4×4 control grid — a gently curved surface
|
|
692
|
+
const grid = [
|
|
693
|
+
[[0,0,0], [10,0,2], [20,0,2], [30,0,0]],
|
|
694
|
+
[[0,10,1], [10,10,5], [20,10,5], [30,10,1]],
|
|
695
|
+
[[0,20,1], [10,20,5], [20,20,5], [30,20,1]],
|
|
696
|
+
[[0,30,0], [10,30,2], [20,30,2], [30,30,0]],
|
|
697
|
+
];
|
|
698
|
+
const surface = nurbsSurface(grid, { thickness: 2 });
|
|
939
699
|
```
|
|
940
700
|
|
|
941
|
-
Repeat a sketch in a linear pattern
|
|
942
|
-
|
|
943
|
-
#### `mirrorCopy()`
|
|
944
|
-
|
|
945
701
|
```ts
|
|
946
|
-
|
|
702
|
+
nurbsSurface(controlGrid: Vec3[][], options?: NurbsSurfaceOptions): Shape
|
|
947
703
|
```
|
|
948
704
|
|
|
949
|
-
|
|
705
|
+
**`NurbsSurfaceOptions`**
|
|
950
706
|
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
707
|
+
| Option | Type | Description |
|
|
708
|
+
|--------|------|-------------|
|
|
709
|
+
| `degreeU?` | `number` | Degree in U direction (default 3). |
|
|
710
|
+
| `degreeV?` | `number` | Degree in V direction (default 3). |
|
|
711
|
+
| `weights?` | `number[][]` | Weights grid — same dimensions as controlGrid (default: all 1.0). |
|
|
712
|
+
| `knotsU?` | `number[]` | Knot vector in U direction (default: uniform clamped). |
|
|
713
|
+
| `knotsV?` | `number[]` | Knot vector in V direction (default: uniform clamped). |
|
|
714
|
+
| `thickness?` | `number` | Sheet thickness — if > 0, thickens the surface into a solid (default 0 = surface only). |
|
|
715
|
+
| `resolution?` | `number` | Tessellation resolution — points per direction (default 32). |
|
|
954
716
|
|
|
955
|
-
|
|
717
|
+
#### `connectEdges()` — Create a transition surface or solid bridge between two edge segments.
|
|
956
718
|
|
|
957
|
-
|
|
719
|
+
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.
|
|
958
720
|
|
|
959
721
|
```ts
|
|
960
|
-
|
|
722
|
+
connectEdges(edgeA: EdgeSegment, edgeB: EdgeSegment, options?: ConnectEdgesOptions): Shape
|
|
961
723
|
```
|
|
962
724
|
|
|
963
|
-
|
|
725
|
+
**`EdgeSegment`**
|
|
964
726
|
|
|
965
|
-
|
|
727
|
+
| Option | Type | Description |
|
|
728
|
+
|--------|------|-------------|
|
|
729
|
+
| `index` | `number` | Stable index within the extraction (deterministic for a given mesh). |
|
|
730
|
+
| `direction` | `Vec3` | Normalized direction from start → end. |
|
|
731
|
+
| `dihedralAngle` | `number` | Dihedral angle in degrees (0 = coplanar, 180 = knife edge). |
|
|
732
|
+
| `convex` | `boolean` | true = outside corner (convex), false = inside corner (concave). |
|
|
733
|
+
| `normalA` | `Vec3` | Normal of first adjacent face. |
|
|
734
|
+
| `normalB` | `Vec3` | Normal of second adjacent face (same as normalA for boundary edges). |
|
|
735
|
+
| `boundary` | `boolean` | true if this is a boundary (unmatched) edge — unusual for closed solids. |
|
|
736
|
+
| `start`, `end`, `midpoint`, `length` | | — |
|
|
966
737
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
/** Prevent 180° rotation (ensures first edge maintains its initial direction). Default: false. */
|
|
972
|
-
blockRotation?: boolean;
|
|
973
|
-
}
|
|
974
|
-
```
|
|
738
|
+
**`TransitionCurveOptions`**
|
|
739
|
+
- `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
|
|
740
|
+
- `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
|
|
741
|
+
- `samples?: number` — Number of sample points for the output polyline. Default 64. Higher values give smoother curves at the cost of more geometry.
|
|
975
742
|
|
|
976
|
-
|
|
743
|
+
**`TransitionSurfaceOptions`** extends TransitionCurveOptions
|
|
977
744
|
|
|
978
|
-
|
|
745
|
+
| Option | Type | Description |
|
|
746
|
+
|--------|------|-------------|
|
|
747
|
+
| `profile?` | `Sketch` | Cross-section profile to sweep along the transition curve. If omitted, a circular profile with `radius` is used. |
|
|
748
|
+
| `radius?` | `number` | Radius of circular cross-section (used when `profile` is omitted). Default: 5% of chord length. |
|
|
749
|
+
| `up?` | `Vec3` | Preferred up vector for the sweep frame. Default: auto-detected. |
|
|
750
|
+
| `edgeLength?` | `number` | Edge length for level-set meshing. Smaller = finer. |
|
|
751
|
+
| `boundsPadding?` | `number` | Extra bounds padding for level-set meshing. |
|
|
752
|
+
| `width`, `height` | | — |
|
|
979
753
|
|
|
980
|
-
|
|
981
|
-
interface ConstrainedPolygon {
|
|
982
|
-
/** CCW-ordered PointIds. */
|
|
983
|
-
vertices: PointId[];
|
|
984
|
-
/** CCW-ordered LineIds. `sides[i]` runs from `vertices[i]` → `vertices[(i+1) % n]`. */
|
|
985
|
-
sides: LineId[];
|
|
986
|
-
/** ShapeId for `shapeWidth`, `shapeHeight`, `shapeArea`, `shapeCentroidX/Y`. */
|
|
987
|
-
shape: ShapeId;
|
|
988
|
-
}
|
|
989
|
-
```
|
|
754
|
+
**`ConnectEdgesOptions`** extends TransitionSurfaceOptions
|
|
990
755
|
|
|
991
|
-
|
|
756
|
+
| Option | Type | Description |
|
|
757
|
+
|--------|------|-------------|
|
|
758
|
+
| `endA?` | `EdgeEnd` | Which end of edge A to connect. Default: 'start'. |
|
|
759
|
+
| `endB?` | `EdgeEnd` | Which end of edge B to connect. Default: 'start'. |
|
|
760
|
+
| `tangentModeA?` | `TangentMode` | Tangent mode for edge A. Default: 'along'. |
|
|
761
|
+
| `tangentModeB?` | `TangentMode` | Tangent mode for edge B. Default: 'along'. |
|
|
762
|
+
| `tangentA?` | `Vec3` | Explicit tangent for edge A. |
|
|
763
|
+
| `tangentB?` | `Vec3` | Explicit tangent for edge B. |
|
|
764
|
+
| `flipA?` | `boolean` | Flip tangent A. |
|
|
765
|
+
| `flipB?` | `boolean` | Flip tangent B. |
|
|
992
766
|
|
|
993
|
-
#### `
|
|
767
|
+
#### `hermiteTransitionG2()` — Create a quintic Hermite transition curve between two edge endpoints (G2 continuity).
|
|
768
|
+
|
|
769
|
+
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.
|
|
994
770
|
|
|
995
771
|
```ts
|
|
996
|
-
|
|
772
|
+
hermiteTransitionG2(a: QuinticHermiteCurveEndpoint, b: QuinticHermiteCurveEndpoint): QuinticHermiteCurve3D
|
|
997
773
|
```
|
|
998
774
|
|
|
999
|
-
|
|
775
|
+
**`QuinticHermiteCurveEndpoint`**
|
|
776
|
+
|
|
777
|
+
| Option | Type | Description |
|
|
778
|
+
|--------|------|-------------|
|
|
779
|
+
| `point` | `Vec3` | Position |
|
|
780
|
+
| `tangent` | `Vec3` | Tangent direction (will be normalized internally) |
|
|
781
|
+
| `curvature?` | `Vec3` | Second derivative / curvature vector. Default [0, 0, 0]. |
|
|
782
|
+
| `weight?` | `number` | Weight: scales tangent magnitude relative to chord length. Default 1.0. |
|
|
1000
783
|
|
|
1001
|
-
|
|
784
|
+
#### `loft()` — Loft between multiple sketches along Z stations.
|
|
785
|
+
|
|
786
|
+
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.
|
|
787
|
+
|
|
788
|
+
Performance note: loft is significantly heavier than primitive/extrude/revolve. If the part is axis-symmetric (bottles, vases, knobs), prefer revolve().
|
|
1002
789
|
|
|
1003
790
|
```ts
|
|
1004
|
-
|
|
1005
|
-
/** Bottom-left x coordinate. Default: 0. */
|
|
1006
|
-
x?: number;
|
|
1007
|
-
/** Bottom-left y coordinate. Default: 0. */
|
|
1008
|
-
y?: number;
|
|
1009
|
-
/** Width (along x). Default: 10. */
|
|
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
|
-
}
|
|
791
|
+
loft(profiles: Sketch[], heights: number[], options?: LoftOptions): Shape
|
|
1016
792
|
```
|
|
1017
793
|
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
```ts
|
|
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
|
-
```
|
|
794
|
+
**`LoftOptions`**
|
|
795
|
+
- `edgeLength?: number` — Marching-grid edge length for level-set meshing. Smaller = finer.
|
|
796
|
+
- `boundsPadding?: number` — Optional extra bounds padding.
|
|
1042
797
|
|
|
1043
|
-
|
|
798
|
+
#### `loftAlongSpine()` — Loft between multiple profiles positioned along an arbitrary 3D spine curve.
|
|
1044
799
|
|
|
1045
|
-
|
|
800
|
+
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.
|
|
1046
801
|
|
|
1047
|
-
|
|
1048
|
-
addRegularPolygon(sk: ConstrainedSketchBuilder, options: RegularPolygonOptions): ConstrainedRegularPolygon
|
|
1049
|
-
```
|
|
802
|
+
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].
|
|
1050
803
|
|
|
1051
|
-
|
|
804
|
+
Internally uses variableSweep infrastructure with SDF interpolation.
|
|
1052
805
|
|
|
1053
|
-
|
|
806
|
+
Performance note: uses level-set meshing, heavier than simple loft().
|
|
1054
807
|
|
|
1055
808
|
```ts
|
|
1056
|
-
|
|
1057
|
-
/** Number of sides (minimum 3). */
|
|
1058
|
-
sides: number;
|
|
1059
|
-
/** Circumradius — distance from center to vertex. Default: 10. */
|
|
1060
|
-
radius?: number;
|
|
1061
|
-
/** Center x coordinate. Default: 0. */
|
|
1062
|
-
cx?: number;
|
|
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
|
-
}
|
|
809
|
+
loftAlongSpine(profiles: Sketch[], spine: Curve3D | Vec3[], tValues: number[], options?: LoftAlongSpineOptions): Shape
|
|
1070
810
|
```
|
|
1071
811
|
|
|
1072
|
-
|
|
812
|
+
**`LoftAlongSpineOptions`**
|
|
1073
813
|
|
|
814
|
+
| Option | Type | Description |
|
|
815
|
+
|--------|------|-------------|
|
|
816
|
+
| `samples?` | `number` | Number of samples when spine is a Curve3D. Default 48. |
|
|
817
|
+
| `edgeLength?` | `number` | Marching-grid edge length for level-set meshing. Smaller = finer. |
|
|
818
|
+
| `boundsPadding?` | `number` | Optional extra bounds padding. |
|
|
819
|
+
| `up?` | `Vec3` | Preferred "up" vector for local profile frame. Auto fallback is used near parallel segments. |
|
|
1074
820
|
|
|
1075
|
-
|
|
821
|
+
#### `spline3d()` — Create a reusable 3D spline curve object (Catmull-Rom).
|
|
822
|
+
|
|
823
|
+
The returned Curve3D provides sample(), pointAt(t), tangentAt(t), and length() for downstream use in sweep() or manual path operations.
|
|
1076
824
|
|
|
1077
825
|
```ts
|
|
1078
|
-
|
|
1079
|
-
/** Center point. Use `sk.fix(poly.center, x, y)` to pin location, or `sk.coincident(poly.center, other)` to align with other geometry. */
|
|
1080
|
-
center: PointId;
|
|
1081
|
-
}
|
|
826
|
+
spline3d(points: Vec3[], options?: Spline3DOptions): Curve3D
|
|
1082
827
|
```
|
|
1083
828
|
|
|
1084
|
-
|
|
829
|
+
**`Spline3DOptions`**
|
|
830
|
+
- `closed?: boolean` — Closed loop (default false).
|
|
831
|
+
- `tension?: number` — Catmull-Rom tension in [0, 1]. 0 = very round, 1 = linear-ish. Default 0.5.
|
|
1085
832
|
|
|
1086
|
-
#### `
|
|
833
|
+
#### `surfacePatch()` — Create a smooth surface patch from 4 boundary curves (Coons patch).
|
|
1087
834
|
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
835
|
+
The four curves form the boundary of a quadrilateral patch:
|
|
836
|
+
|
|
837
|
+
- bottom: u=0..1 at v=0 (from corner00 to corner10)
|
|
838
|
+
- top: u=0..1 at v=1 (from corner01 to corner11)
|
|
839
|
+
- left: v=0..1 at u=0 (from corner00 to corner01)
|
|
840
|
+
- right: v=0..1 at u=1 (from corner10 to corner11)
|
|
1091
841
|
|
|
1092
|
-
|
|
842
|
+
The interior is filled using bilinear Coons patch interpolation: P(u,v) = Lc(u,v) + Ld(u,v) - B(u,v)
|
|
1093
843
|
|
|
1094
|
-
|
|
844
|
+
The result is a thin solid created by offsetting the surface mesh along its normals by the specified thickness.
|
|
845
|
+
|
|
846
|
+
Note: curves should meet at corners. Small gaps are tolerated.
|
|
1095
847
|
|
|
1096
848
|
```ts
|
|
1097
|
-
|
|
849
|
+
surfacePatch(curves: { ... }, options?: SurfacePatchOptions): Shape
|
|
1098
850
|
```
|
|
1099
851
|
|
|
1100
|
-
|
|
852
|
+
**`SurfacePatchOptions`**
|
|
853
|
+
- `resolution?: number` — Number of samples along each direction. Default 24.
|
|
854
|
+
- `thickness?: number` — Thickness of the generated solid. Default 0.5.
|
|
1101
855
|
|
|
1102
|
-
|
|
856
|
+
#### `sweep()`
|
|
1103
857
|
|
|
1104
858
|
```ts
|
|
1105
|
-
|
|
1106
|
-
/** When true, adding a constraint that cannot be satisfied throws instead of silently discarding it. */
|
|
1107
|
-
strict?: boolean;
|
|
1108
|
-
}
|
|
859
|
+
sweep(profile: Sketch, path: SweepPathInput, options?: SweepOptions): Shape
|
|
1109
860
|
```
|
|
1110
861
|
|
|
1111
|
-
|
|
862
|
+
**`SweepOptions`**
|
|
1112
863
|
|
|
1113
|
-
|
|
864
|
+
| Option | Type | Description |
|
|
865
|
+
|--------|------|-------------|
|
|
866
|
+
| `samples?` | `number` | Number of samples when path is a Curve3D. Default 48. |
|
|
867
|
+
| `edgeLength?` | `number` | Marching-grid edge length for level-set meshing. Smaller = finer. |
|
|
868
|
+
| `boundsPadding?` | `number` | Optional extra bounds padding. |
|
|
869
|
+
| `up?` | `Vec3` | Preferred "up" vector for local profile frame. Auto fallback is used near parallel segments. |
|
|
1114
870
|
|
|
1115
|
-
|
|
1116
|
-
line(x1: number, y1: number, x2: number, y2: number): Line2D
|
|
1117
|
-
```
|
|
871
|
+
#### `variableSweep()` — Sweep a variable cross-section along a 3D spine curve.
|
|
1118
872
|
|
|
1119
|
-
|
|
873
|
+
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.
|
|
1120
874
|
|
|
1121
|
-
|
|
875
|
+
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.
|
|
876
|
+
|
|
877
|
+
Performance note: like sweep(), this uses level-set meshing internally.
|
|
1122
878
|
|
|
1123
879
|
```ts
|
|
1124
|
-
|
|
880
|
+
variableSweep(spine: SweepPathInput, sections: VariableSweepSection[], options?: VariableSweepOptions): Shape
|
|
1125
881
|
```
|
|
1126
882
|
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
883
|
+
**`VariableSweepSection`**
|
|
884
|
+
- `t: number` — Parameter along the spine (0 = start, 1 = end).
|
|
885
|
+
- `profile: Sketch` — Cross-section profile at this station.
|
|
1130
886
|
|
|
1131
|
-
|
|
887
|
+
**`VariableSweepOptions`**
|
|
1132
888
|
|
|
1133
|
-
|
|
889
|
+
| Option | Type | Description |
|
|
890
|
+
|--------|------|-------------|
|
|
891
|
+
| `samples?` | `number` | Number of samples when spine is a Curve3D. Default 48. |
|
|
892
|
+
| `edgeLength?` | `number` | Marching-grid edge length for level-set meshing. Smaller = finer. |
|
|
893
|
+
| `boundsPadding?` | `number` | Optional extra bounds padding. |
|
|
894
|
+
| `up?` | `Vec3` | Preferred "up" vector for local profile frame. Auto fallback is used near parallel segments. |
|
|
1134
895
|
|
|
1135
|
-
|
|
896
|
+
#### `transitionCurve()` — Create a smooth transition curve between two edges.
|
|
1136
897
|
|
|
1137
|
-
|
|
898
|
+
Returns a `HermiteCurve3D` that starts at `edgeA.point` tangent to `edgeA.tangent` and ends at `edgeB.point` tangent to `edgeB.tangent`.
|
|
1138
899
|
|
|
1139
|
-
|
|
900
|
+
The curve maintains G1 continuity (matching tangent direction) at both endpoints. Weight parameters control the shape of the transition.
|
|
1140
901
|
|
|
1141
|
-
|
|
902
|
+
```js
|
|
903
|
+
```js
|
|
1142
904
|
|
|
1143
|
-
|
|
905
|
+
// Connect two edges with a balanced transition const curve = transitionCurve( { point: [0, 0, 0], tangent: [1, 0, 0] }, { point: [10, 5, 0], tangent: [1, 0, 0] }, );
|
|
1144
906
|
|
|
1145
|
-
```ts
|
|
1146
|
-
assembly(name?: string): Assembly
|
|
1147
907
|
```
|
|
1148
908
|
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
909
|
+
// Weighted: curve hugs edge A longer
|
|
910
|
+
const weighted = transitionCurve(
|
|
911
|
+
{ point: [0, 0, 0], tangent: [1, 0, 0] },
|
|
912
|
+
{ point: [10, 5, 0], tangent: [1, 0, 0] },
|
|
913
|
+
{ weightA: 2.0, weightB: 0.5 },
|
|
914
|
+
);
|
|
915
|
+
```
|
|
1152
916
|
|
|
1153
917
|
```ts
|
|
1154
|
-
|
|
918
|
+
transitionCurve(edgeA: TransitionEdge, edgeB: TransitionEdge, options?: TransitionCurveOptions): HermiteCurve3D
|
|
1155
919
|
```
|
|
1156
920
|
|
|
1157
|
-
|
|
921
|
+
**`TransitionEdge`**
|
|
922
|
+
- `point: Vec3` — Connection point on the edge. Can be any point along the edge where the transition should connect.
|
|
923
|
+
- `tangent: Vec3` — 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).
|
|
924
|
+
- `normal?: Vec3` — Surface normal at the connection point (optional). Used as a hint for the sweep frame's up vector.
|
|
1158
925
|
|
|
1159
|
-
|
|
926
|
+
#### `transitionSurface()` — Create a solid transition surface between two edges by sweeping a profile along a Hermite transition curve.
|
|
1160
927
|
|
|
1161
|
-
|
|
1162
|
-
interface BomRow {
|
|
1163
|
-
part: string;
|
|
1164
|
-
qty: number;
|
|
1165
|
-
material?: string;
|
|
1166
|
-
process?: string;
|
|
1167
|
-
tolerance?: string;
|
|
1168
|
-
notes?: string;
|
|
1169
|
-
metadata?: PartMetadata;
|
|
1170
|
-
}
|
|
1171
|
-
```
|
|
928
|
+
This produces a watertight solid that smoothly connects the two edges. Works with both Manifold and OCCT backends.
|
|
1172
929
|
|
|
1173
|
-
|
|
930
|
+
```js
|
|
931
|
+
```js
|
|
1174
932
|
|
|
1175
|
-
|
|
933
|
+
// Circular tube connecting two edges const tube = transitionSurface( { point: [0, 0, 0], tangent: [1, 0, 0] }, { point: [10, 5, 3], tangent: [0, 1, 0] }, { radius: 0.5 }, );
|
|
1176
934
|
|
|
1177
|
-
```ts
|
|
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
935
|
```
|
|
1188
936
|
|
|
1189
|
-
|
|
937
|
+
// Custom profile with weights
|
|
938
|
+
const custom = transitionSurface(
|
|
939
|
+
{ point: [0, 0, 0], tangent: [1, 0, 0] },
|
|
940
|
+
{ point: [10, 5, 3], tangent: [0, 1, 0] },
|
|
941
|
+
{ profile: mySketch, weightA: 1.5, weightB: 0.8 },
|
|
942
|
+
);
|
|
943
|
+
```
|
|
944
|
+
|
|
945
|
+
```ts
|
|
946
|
+
transitionSurface(edgeA: TransitionEdge, edgeB: TransitionEdge, options?: TransitionSurfaceOptions): Shape
|
|
947
|
+
```
|
|
948
|
+
|
|
949
|
+
---
|
|
950
|
+
|
|
951
|
+
## C5: Topology Query
|
|
952
|
+
|
|
953
|
+
Select or inspect named faces and edges on a shape.
|
|
954
|
+
|
|
955
|
+
#### `coalesceEdges()` — Merge collinear edge segments into longer logical edges.
|
|
956
|
+
|
|
957
|
+
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.
|
|
958
|
+
|
|
959
|
+
The `tolerance` controls the maximum perpendicular distance from collinearity before two segments are considered non-collinear. Default: `0.01`.
|
|
960
|
+
|
|
961
|
+
```ts
|
|
962
|
+
const topEdges = selectEdges(part, { atZ: 20 });
|
|
963
|
+
for (const edge of coalesceEdges(topEdges)) {
|
|
964
|
+
result = fillet(result, 2, edge);
|
|
965
|
+
}
|
|
966
|
+
```
|
|
967
|
+
|
|
968
|
+
```ts
|
|
969
|
+
coalesceEdges(segments: EdgeSegment[], tolerance?: number): EdgeSegment[]
|
|
970
|
+
```
|
|
971
|
+
|
|
972
|
+
**`EdgeSegment`**
|
|
973
|
+
|
|
974
|
+
| Option | Type | Description |
|
|
975
|
+
|--------|------|-------------|
|
|
976
|
+
| `index` | `number` | Stable index within the extraction (deterministic for a given mesh). |
|
|
977
|
+
| `direction` | `Vec3` | Normalized direction from start → end. |
|
|
978
|
+
| `dihedralAngle` | `number` | Dihedral angle in degrees (0 = coplanar, 180 = knife edge). |
|
|
979
|
+
| `convex` | `boolean` | true = outside corner (convex), false = inside corner (concave). |
|
|
980
|
+
| `normalA` | `Vec3` | Normal of first adjacent face. |
|
|
981
|
+
| `normalB` | `Vec3` | Normal of second adjacent face (same as normalA for boundary edges). |
|
|
982
|
+
| `boundary` | `boolean` | true if this is a boundary (unmatched) edge — unusual for closed solids. |
|
|
983
|
+
| `start`, `end`, `midpoint`, `length` | | — |
|
|
984
|
+
|
|
985
|
+
#### `selectEdge()` — Select the single best-matching edge from a shape.
|
|
986
|
+
|
|
987
|
+
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.
|
|
988
|
+
|
|
989
|
+
```ts
|
|
990
|
+
// Chamfer one specific edge near a known point
|
|
991
|
+
const bottomEdge = selectEdge(part, { near: [25, 0, 0], atZ: 0 });
|
|
992
|
+
result = chamfer(result, 1.5, bottomEdge);
|
|
993
|
+
```
|
|
994
|
+
|
|
995
|
+
```ts
|
|
996
|
+
selectEdge(shape: Shape, query?: EdgeQuery): EdgeSegment
|
|
997
|
+
```
|
|
998
|
+
|
|
999
|
+
**`EdgeQuery`**
|
|
1000
|
+
|
|
1001
|
+
| Option | Type | Description |
|
|
1002
|
+
|--------|------|-------------|
|
|
1003
|
+
| `near?` | `Vec3` | Sort by proximity to this point (closest first). When used with `selectEdge`, picks the closest match. |
|
|
1004
|
+
| `parallel?` | `Vec3` | Filter: edge direction approximately parallel to this vector. |
|
|
1005
|
+
| `perpendicular?` | `Vec3` | Filter: edge direction approximately perpendicular to this vector. |
|
|
1006
|
+
| `convex?` | `boolean` | Filter: only convex (outside corner) edges. |
|
|
1007
|
+
| `concave?` | `boolean` | Filter: only concave (inside corner) edges. |
|
|
1008
|
+
| `minAngle?` | `number` | Filter: minimum dihedral angle in degrees. |
|
|
1009
|
+
| `maxAngle?` | `number` | Filter: maximum dihedral angle in degrees. |
|
|
1010
|
+
| `minLength?` | `number` | Filter: minimum edge length. |
|
|
1011
|
+
| `maxLength?` | `number` | Filter: maximum edge length. |
|
|
1012
|
+
| `within?` | `BoundingRegion` | Filter: edge midpoint must be within this bounding region. |
|
|
1013
|
+
| `atZ?` | `number` | Shorthand: edge midpoint Z ≈ this value (within `tolerance`). Equivalent to `within: { zMin: atZ - tol, zMax: atZ + tol }`. |
|
|
1014
|
+
| `tolerance?` | `number` | Position tolerance for approximate matches (default: `1.0`). Used by `atZ` and `near`. |
|
|
1015
|
+
| `angleTolerance?` | `number` | Angular tolerance in degrees for `parallel`/`perpendicular` filters (default: `10`). |
|
|
1016
|
+
|
|
1017
|
+
`BoundingRegion`: `{ xMin?: number, xMax?: number, yMin?: number, yMax?: number, zMin?: number, zMax?: number }`
|
|
1018
|
+
|
|
1019
|
+
#### `selectEdges()` — Select all edges from a shape that match the given query.
|
|
1020
|
+
|
|
1021
|
+
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.
|
|
1022
|
+
|
|
1023
|
+
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.
|
|
1024
|
+
|
|
1025
|
+
```ts
|
|
1026
|
+
// Fillet all top edges of a box
|
|
1027
|
+
const topEdges = selectEdges(part, { atZ: 20, perpendicular: [0, 0, 1] });
|
|
1028
|
+
let result = part;
|
|
1029
|
+
for (const edge of coalesceEdges(topEdges)) {
|
|
1030
|
+
result = fillet(result, 2, edge);
|
|
1031
|
+
}
|
|
1032
|
+
```
|
|
1033
|
+
|
|
1034
|
+
```ts
|
|
1035
|
+
selectEdges(shape: Shape, query?: EdgeQuery): EdgeSegment[]
|
|
1036
|
+
```
|
|
1037
|
+
|
|
1038
|
+
---
|
|
1039
|
+
|
|
1040
|
+
## C6: Edge Feature
|
|
1041
|
+
|
|
1042
|
+
Modify edges of a solid — fillets, chamfers, draft, offset.
|
|
1043
|
+
|
|
1044
|
+
#### `chamfer()` — Apply chamfers (beveled edges) to one or more edges of a shape.
|
|
1045
|
+
|
|
1046
|
+
Produces a 45° bevel at the specified `size` (distance from edge). Works on both straight and curved edges. Supports OCCT and Manifold backends.
|
|
1047
|
+
|
|
1048
|
+
The `edges` parameter accepts the same options as `fillet()`: inline `EdgeQuery`, pre-selected `EdgeSegment`/`EdgeSegment[]`, or `undefined` (all sharp edges).
|
|
1049
|
+
|
|
1050
|
+
```ts
|
|
1051
|
+
// Chamfer all edges
|
|
1052
|
+
chamfer(myShape, 1)
|
|
1053
|
+
|
|
1054
|
+
// Chamfer only vertical edges
|
|
1055
|
+
chamfer(myShape, 2, { parallel: [0, 0, 1] })
|
|
1056
|
+
```
|
|
1057
|
+
|
|
1058
|
+
```ts
|
|
1059
|
+
chamfer(shape: Shape, size: number, edges?: EdgeSelector): Shape
|
|
1060
|
+
```
|
|
1061
|
+
|
|
1062
|
+
#### `draft()` — Apply a draft angle (taper) to vertical faces for mold extraction.
|
|
1063
|
+
|
|
1064
|
+
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°.
|
|
1065
|
+
|
|
1066
|
+
Requires the OCCT backend. Throws on Manifold.
|
|
1067
|
+
|
|
1068
|
+
```ts
|
|
1069
|
+
// Add 3° draft to a box for injection molding
|
|
1070
|
+
draft(myBox, 3)
|
|
1071
|
+
|
|
1072
|
+
// Draft with custom pull direction and neutral plane
|
|
1073
|
+
draft(myShape, 2, [0, 0, 1], 10)
|
|
1074
|
+
```
|
|
1075
|
+
|
|
1076
|
+
```ts
|
|
1077
|
+
draft(shape: Shape, angleDeg: number, pullDirection?: [ number, number, number ], neutralPlaneOffset?: number): Shape
|
|
1078
|
+
```
|
|
1079
|
+
|
|
1080
|
+
#### `fillet()` — Apply fillets (rounded edges) to one or more edges of a shape.
|
|
1081
|
+
|
|
1082
|
+
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.
|
|
1083
|
+
|
|
1084
|
+
The `edges` parameter is flexible:
|
|
1085
|
+
|
|
1086
|
+
- Omit to fillet **all** sharp edges
|
|
1087
|
+
- Pass an `EdgeQuery` for an inline filter (most common)
|
|
1088
|
+
- Pass an `EdgeSegment` or `EdgeSegment[]` from `selectEdges()` for pre-selected edges
|
|
1089
|
+
|
|
1090
|
+
Throws if no edges match the selection, or if `radius` is not a positive finite number.
|
|
1091
|
+
|
|
1092
|
+
```ts
|
|
1093
|
+
// Fillet all edges
|
|
1094
|
+
fillet(myShape, 2)
|
|
1095
|
+
|
|
1096
|
+
// Fillet only top convex edges
|
|
1097
|
+
fillet(myShape, 1.5, { atZ: 20, convex: true })
|
|
1098
|
+
|
|
1099
|
+
// Fillet vertical edges selected beforehand
|
|
1100
|
+
const edges = selectEdges(myShape, { parallel: [0, 0, 1] })
|
|
1101
|
+
fillet(myShape, 3, edges)
|
|
1102
|
+
```
|
|
1103
|
+
|
|
1104
|
+
```ts
|
|
1105
|
+
fillet(shape: Shape, radius: number, edges?: EdgeSelector, segments?: number): Shape
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
#### `offsetSolid()` — Uniformly offset all surfaces of a solid inward or outward.
|
|
1109
|
+
|
|
1110
|
+
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.
|
|
1111
|
+
|
|
1112
|
+
Requires the OCCT backend. Throws on Manifold.
|
|
1113
|
+
|
|
1114
|
+
```ts
|
|
1115
|
+
// Grow a box outward by 1mm on all sides
|
|
1116
|
+
offsetSolid(myBox, 1)
|
|
1117
|
+
|
|
1118
|
+
// Shrink a shape inward by 0.5mm
|
|
1119
|
+
offsetSolid(myShape, -0.5)
|
|
1120
|
+
```
|
|
1121
|
+
|
|
1122
|
+
```ts
|
|
1123
|
+
offsetSolid(shape: Shape, thickness: number): Shape
|
|
1124
|
+
```
|
|
1125
|
+
|
|
1126
|
+
#### `chamfer2d()` — Bevel a named vertical edge of a shape with a 45° chamfer.
|
|
1127
|
+
|
|
1128
|
+
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.
|
|
1129
|
+
|
|
1130
|
+
```ts
|
|
1131
|
+
const b = rectangle(0, 0, 50, 50).extrude(20);
|
|
1132
|
+
const chamfered = chamfer2d(b.toShape(), b.edge('vert-br'), 3, [-1, -1]);
|
|
1133
|
+
```
|
|
1134
|
+
|
|
1135
|
+
```ts
|
|
1136
|
+
chamfer2d(shape: Shape, edge: EdgeRef, size: number, quadrant?: [ number, number ]): Shape
|
|
1137
|
+
```
|
|
1138
|
+
|
|
1139
|
+
**`EdgeRef`**
|
|
1140
|
+
- `query?: EdgeQueryRef` — Compiler-owned edge query when available.
|
|
1141
|
+
- Also: `name: EdgeName`
|
|
1142
|
+
|
|
1143
|
+
#### `fillet2d()` — Round a named vertical edge of a shape with a circular fillet.
|
|
1144
|
+
|
|
1145
|
+
Compiler-owned fillet for tracked vertical edges. Requires a compile-plan-covered target (shapes from `box()`, `rectangle().extrude()`, or rigid transforms of those).
|
|
1146
|
+
|
|
1147
|
+
**Supported edges:**
|
|
1148
|
+
|
|
1149
|
+
- Tracked vertical edges from `box()` or `rectangle().extrude()`
|
|
1150
|
+
- Rigid transforms between tracked source and target
|
|
1151
|
+
- Untouched sibling tracked vertical edges after earlier `fillet2d`/`chamfer2d`
|
|
1152
|
+
|
|
1153
|
+
**Not supported:** edges after shell, hole, cut, trim, difference, intersection, generic sketch extrudes, or tapered extrudes. Use `fillet()` with an `EdgeQuery` for those cases.
|
|
1154
|
+
|
|
1155
|
+
Canonical quadrants: `vert-bl → [1,-1]`, `vert-br → [-1,-1]`, `vert-tr → [-1,1]`, `vert-tl → [1,1]`
|
|
1156
|
+
|
|
1157
|
+
```ts
|
|
1158
|
+
const b = rectangle(0, 0, 50, 50).extrude(20);
|
|
1159
|
+
const filleted = fillet2d(b.toShape(), b.edge('vert-br'), 5, [-1, -1]);
|
|
1160
|
+
```
|
|
1161
|
+
|
|
1162
|
+
```ts
|
|
1163
|
+
fillet2d(shape: Shape, edge: EdgeRef, radius: number, quadrant?: [ number, number ], segments?: number): Shape
|
|
1164
|
+
```
|
|
1165
|
+
|
|
1166
|
+
#### `filletCorners()` — Create a polygon from points with specific corners rounded to arc fillets.
|
|
1167
|
+
|
|
1168
|
+
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.
|
|
1169
|
+
|
|
1170
|
+
Constraints:
|
|
1171
|
+
|
|
1172
|
+
- Collinear corners cannot be filleted (throws an error)
|
|
1173
|
+
- Two neighboring fillets whose tangent lengths overlap the same edge will throw
|
|
1174
|
+
- Radius must be positive and small enough to fit within the adjacent edge lengths
|
|
1175
|
+
|
|
1176
|
+
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.
|
|
1177
|
+
|
|
1178
|
+
```ts
|
|
1179
|
+
const roof = filletCorners(roofPoints, [
|
|
1180
|
+
{ index: 3, radius: 19 },
|
|
1181
|
+
{ index: 4, radius: 19 },
|
|
1182
|
+
{ index: 5, radius: 19 },
|
|
1183
|
+
]);
|
|
1184
|
+
```
|
|
1185
|
+
|
|
1186
|
+
```ts
|
|
1187
|
+
filletCorners(points: PointInput[], corners: FilletCornerSpec[]): Sketch
|
|
1188
|
+
```
|
|
1189
|
+
|
|
1190
|
+
`FilletCornerSpec`: `{ index: number, radius: number, segments?: number }`
|
|
1191
|
+
|
|
1192
|
+
---
|
|
1193
|
+
|
|
1194
|
+
## C7: Pattern Replication
|
|
1195
|
+
|
|
1196
|
+
Duplicate geometry in regular arrangements (linear, circular, mirror).
|
|
1197
|
+
|
|
1198
|
+
#### `circularLayout()` — Compute evenly-spaced positions around a circle.
|
|
1199
|
+
|
|
1200
|
+
Eliminates the most common trig pattern in CAD scripts:
|
|
1201
|
+
|
|
1202
|
+
```js
|
|
1203
|
+
// Before — manual trig
|
|
1204
|
+
for (let i = 0; i < 12; i++) {
|
|
1205
|
+
const angle = i * 30 * Math.PI / 180;
|
|
1206
|
+
markers.push(marker.translate(r * Math.cos(angle), r * Math.sin(angle), 0));
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
// After — declarative
|
|
1210
|
+
for (const {x, y} of circularLayout(12, r)) {
|
|
1211
|
+
markers.push(marker.translate(x, y, 0));
|
|
1212
|
+
}
|
|
1213
|
+
```
|
|
1214
|
+
|
|
1215
|
+
```ts
|
|
1216
|
+
circularLayout(count: number, radius: number, options?: CircularLayoutOptions): LayoutPoint[]
|
|
1217
|
+
```
|
|
1218
|
+
|
|
1219
|
+
**`CircularLayoutOptions`**
|
|
1220
|
+
- `startDeg?: number` — Angle of the first element in degrees (default: 0 = +X axis).
|
|
1221
|
+
- `centerX?: number` — Center X coordinate (default: 0).
|
|
1222
|
+
- `centerY?: number` — Center Y coordinate (default: 0).
|
|
1223
|
+
|
|
1224
|
+
`LayoutPoint`: `{ x: number, y: number }`
|
|
1225
|
+
|
|
1226
|
+
#### `circularPattern()` — Repeat a shape in a circular pattern around an axis and union the copies.
|
|
1227
|
+
|
|
1228
|
+
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.
|
|
1229
|
+
|
|
1230
|
+
Two calling conventions:
|
|
1231
|
+
|
|
1232
|
+
- **Simple** (Z axis): `circularPattern(shape, 6)` or `circularPattern(shape, 6, centerX, centerY)`
|
|
1233
|
+
- **Advanced** (arbitrary axis): `circularPattern(shape, 6, { axis, origin })`
|
|
1234
|
+
|
|
1235
|
+
```ts
|
|
1236
|
+
// 8 holes evenly spaced around origin
|
|
1237
|
+
circularPattern(cylinder(12, 4).translate(30, 0, -1), 8)
|
|
1238
|
+
|
|
1239
|
+
// Circular pattern around X axis
|
|
1240
|
+
circularPattern(myFeature, 4, { axis: [1, 0, 0], origin: [0, 0, 50] })
|
|
1241
|
+
```
|
|
1242
|
+
|
|
1243
|
+
```ts
|
|
1244
|
+
circularPattern(shape: Shape, count: number, centerXOrOpts?: number | CircularPatternOptions, centerY?: number): Shape
|
|
1245
|
+
```
|
|
1246
|
+
|
|
1247
|
+
**`CircularPatternOptions`**
|
|
1248
|
+
- `centerX?: number` — Center X of the rotation (default: 0). Used when axis is Z (legacy mode).
|
|
1249
|
+
- `centerY?: number` — Center Y of the rotation (default: 0). Used when axis is Z (legacy mode).
|
|
1250
|
+
|
|
1251
|
+
#### `circularPattern2d()` — Repeat a 2D sketch in a circular pattern around a center point and union the copies.
|
|
1252
|
+
|
|
1253
|
+
```ts
|
|
1254
|
+
circularPattern2d(sketch: Sketch, count: number, centerXOrOpts?: number | { centerX?: number; centerY?: number; startDeg?: number; }, centerY?: number): Sketch
|
|
1255
|
+
```
|
|
1256
|
+
|
|
1257
|
+
#### `linearPattern()` — Repeat a shape in a linear pattern along a direction vector and union the copies.
|
|
1258
|
+
|
|
1259
|
+
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.
|
|
1260
|
+
|
|
1261
|
+
```ts
|
|
1262
|
+
// 5 cylinders, 20mm apart along X
|
|
1263
|
+
linearPattern(cylinder(10, 3), 5, 20, 0)
|
|
1264
|
+
```
|
|
1265
|
+
|
|
1266
|
+
```ts
|
|
1267
|
+
linearPattern(shape: Shape, count: number, dx: number, dy: number, dz?: number): Shape
|
|
1268
|
+
```
|
|
1269
|
+
|
|
1270
|
+
#### `linearPattern2d()` — Repeat a 2D sketch in a linear pattern and union the copies.
|
|
1271
|
+
|
|
1272
|
+
```ts
|
|
1273
|
+
linearPattern2d(sketch: Sketch, count: number, dx: number, dy?: number): Sketch
|
|
1274
|
+
```
|
|
1275
|
+
|
|
1276
|
+
#### `mirrorCopy()` — Mirror a shape across a plane and union the mirror with the original.
|
|
1277
|
+
|
|
1278
|
+
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.
|
|
1279
|
+
|
|
1280
|
+
```ts
|
|
1281
|
+
// Mirror across the YZ plane (X=0)
|
|
1282
|
+
mirrorCopy(box(50, 30, 10), [1, 0, 0])
|
|
1283
|
+
```
|
|
1284
|
+
|
|
1285
|
+
```ts
|
|
1286
|
+
mirrorCopy(shape: Shape, normal: [ number, number, number ]): Shape
|
|
1287
|
+
```
|
|
1288
|
+
|
|
1289
|
+
---
|
|
1290
|
+
|
|
1291
|
+
## C8: Constraint Solving
|
|
1292
|
+
|
|
1293
|
+
Define geometry by relationships and let a solver find positions.
|
|
1294
|
+
|
|
1295
|
+
#### `Constraint.makeParallel()` — Constrain two lines to be parallel.
|
|
1296
|
+
|
|
1297
|
+
```ts
|
|
1298
|
+
Constraint.makeParallel(builder: ConstrainedSketchBuilder, a: LineArg, b: LineArg): ConstrainedSketchBuilder
|
|
1299
|
+
```
|
|
1300
|
+
|
|
1301
|
+
#### `Constraint.enforceAngle()` — Constrain the signed angle from line `a` to line `b`.
|
|
1302
|
+
|
|
1303
|
+
```ts
|
|
1304
|
+
Constraint.enforceAngle(builder: ConstrainedSketchBuilder, a: LineArg, b: LineArg, angleDeg: number): ConstrainedSketchBuilder
|
|
1305
|
+
```
|
|
1306
|
+
|
|
1307
|
+
#### `Constraint.horizontal()` — Constrain a line to be horizontal.
|
|
1308
|
+
|
|
1309
|
+
```ts
|
|
1310
|
+
Constraint.horizontal(builder: ConstrainedSketchBuilder, line: LineArg): ConstrainedSketchBuilder
|
|
1311
|
+
```
|
|
1312
|
+
|
|
1313
|
+
#### `Constraint.vertical()` — Constrain a line to be vertical.
|
|
1314
|
+
|
|
1315
|
+
```ts
|
|
1316
|
+
Constraint.vertical(builder: ConstrainedSketchBuilder, line: LineArg): ConstrainedSketchBuilder
|
|
1317
|
+
```
|
|
1318
|
+
|
|
1319
|
+
#### `Constraint.equalLength()` — Constrain two lines to have equal length.
|
|
1320
|
+
|
|
1321
|
+
```ts
|
|
1322
|
+
Constraint.equalLength(builder: ConstrainedSketchBuilder, a: LineArg, b: LineArg): ConstrainedSketchBuilder
|
|
1323
|
+
```
|
|
1324
|
+
|
|
1325
|
+
#### `Constraint.distance()` — Constrain the distance between two points.
|
|
1326
|
+
|
|
1327
|
+
```ts
|
|
1328
|
+
Constraint.distance(builder: ConstrainedSketchBuilder, a: PointArg, b: PointArg, value: number): ConstrainedSketchBuilder
|
|
1329
|
+
```
|
|
1330
|
+
|
|
1331
|
+
#### `Constraint.fix()` — Fix a point at a specific coordinate.
|
|
1332
|
+
|
|
1333
|
+
```ts
|
|
1334
|
+
Constraint.fix(builder: ConstrainedSketchBuilder, pt: PointArg, x: number, y: number): ConstrainedSketchBuilder
|
|
1335
|
+
```
|
|
1336
|
+
|
|
1337
|
+
#### `Constraint.coincident()` — Constrain two points to occupy the same location.
|
|
1338
|
+
|
|
1339
|
+
```ts
|
|
1340
|
+
Constraint.coincident(builder: ConstrainedSketchBuilder, a: PointArg, b: PointArg): ConstrainedSketchBuilder
|
|
1341
|
+
```
|
|
1342
|
+
|
|
1343
|
+
#### `Constraint.perpendicular()` — Constrain two lines to be perpendicular.
|
|
1344
|
+
|
|
1345
|
+
```ts
|
|
1346
|
+
Constraint.perpendicular(builder: ConstrainedSketchBuilder, a: LineArg, b: LineArg): ConstrainedSketchBuilder
|
|
1347
|
+
```
|
|
1348
|
+
|
|
1349
|
+
#### `Constraint.length()` — Constrain the length of a line.
|
|
1350
|
+
|
|
1351
|
+
```ts
|
|
1352
|
+
Constraint.length(builder: ConstrainedSketchBuilder, line: LineArg, value: number): ConstrainedSketchBuilder
|
|
1353
|
+
```
|
|
1354
|
+
|
|
1355
|
+
#### `addPolygon()` — Add a general polygon concept to the builder.
|
|
1356
|
+
|
|
1357
|
+
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.
|
|
1358
|
+
|
|
1359
|
+
Use `sk.addPolygon()` as the shorthand builder method.
|
|
1360
|
+
|
|
1361
|
+
```ts
|
|
1362
|
+
const sk = constrainedSketch();
|
|
1363
|
+
const tri = sk.addPolygon({ points: [[0,0],[100,0],[50,80]] });
|
|
1364
|
+
sk.fix(tri.vertex(0), 0, 0);
|
|
1365
|
+
sk.length(tri.side(0), 100);
|
|
1366
|
+
return sk.solve().extrude(5);
|
|
1367
|
+
```
|
|
1368
|
+
|
|
1369
|
+
```ts
|
|
1370
|
+
addPolygon(sk: ConstrainedSketchBuilder, options: PolygonOptions): ConstrainedPolygon
|
|
1371
|
+
```
|
|
1372
|
+
|
|
1373
|
+
**`PolygonOptions`**
|
|
1374
|
+
- `addLoop?: boolean` — Whether to register a closed loop for sketch generation. Default: true.
|
|
1375
|
+
- `blockRotation?: boolean` — Prevent 180° rotation (ensures first edge maintains its initial direction). Default: false.
|
|
1376
|
+
|
|
1377
|
+
**`ConstrainedPolygon`**
|
|
1378
|
+
- `vertices: PointId[]` — CCW-ordered PointIds.
|
|
1379
|
+
- `sides: LineId[]` — CCW-ordered LineIds. `sides[i]` runs from `vertices[i]` → `vertices[(i+1) % n]`.
|
|
1380
|
+
- `shape: ShapeId` — ShapeId for `shapeWidth`, `shapeHeight`, `shapeArea`, `shapeCentroidX/Y`.
|
|
1381
|
+
|
|
1382
|
+
#### `addRect()` — Add an axis-aligned rectangle concept to the builder.
|
|
1383
|
+
|
|
1384
|
+
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).
|
|
1385
|
+
|
|
1386
|
+
Use `sk.rect()` as the shorthand builder method.
|
|
1387
|
+
|
|
1388
|
+
```ts
|
|
1389
|
+
const sk = constrainedSketch();
|
|
1390
|
+
const r = sk.rect({ x: 0, y: 0, width: 100, height: 50 });
|
|
1391
|
+
sk.fix(r.bottomLeft, 0, 0);
|
|
1392
|
+
sk.length(r.bottom, 120); // override initial width
|
|
1393
|
+
return sk.solve().extrude(10);
|
|
1394
|
+
```
|
|
1395
|
+
|
|
1396
|
+
```ts
|
|
1397
|
+
addRect(sk: ConstrainedSketchBuilder, options?: RectOptions): ConstrainedRect
|
|
1398
|
+
```
|
|
1399
|
+
|
|
1400
|
+
**`RectOptions`**
|
|
1401
|
+
|
|
1402
|
+
| Option | Type | Description |
|
|
1403
|
+
|--------|------|-------------|
|
|
1404
|
+
| `x?` | `number` | Bottom-left x coordinate. Default: 0. |
|
|
1405
|
+
| `y?` | `number` | Bottom-left y coordinate. Default: 0. |
|
|
1406
|
+
| `width?` | `number` | Width (along x). Default: 10. |
|
|
1407
|
+
| `height?` | `number` | Height (along y). Default: 10. |
|
|
1408
|
+
| `blockRotation?` | `boolean` | Prevent 180° rotation (ensures bottom edge points rightward). Default: false. |
|
|
1409
|
+
|
|
1410
|
+
**`ConstrainedRect`**
|
|
1411
|
+
|
|
1412
|
+
| Option | Type | Description |
|
|
1413
|
+
|--------|------|-------------|
|
|
1414
|
+
| `bottom` | `LineId` | bottom-left → bottom-right |
|
|
1415
|
+
| `right` | `LineId` | bottom-right → top-right |
|
|
1416
|
+
| `top` | `LineId` | top-right → top-left |
|
|
1417
|
+
| `left` | `LineId` | top-left → bottom-left |
|
|
1418
|
+
| `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)`. |
|
|
1419
|
+
| `shape` | `ShapeId` | ShapeId for `shapeWidth`, `shapeHeight`, `shapeArea`, `shapeCentroidX/Y`. |
|
|
1420
|
+
| `bottomLeft`, `bottomRight`, `topRight`, `topLeft` | | — |
|
|
1421
|
+
|
|
1422
|
+
#### `addRegularPolygon()` — Add a regular n-gon concept to the builder.
|
|
1423
|
+
|
|
1424
|
+
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.
|
|
1425
|
+
|
|
1426
|
+
Use `sk.regularPolygon()` as the shorthand builder method.
|
|
1427
|
+
|
|
1428
|
+
```ts
|
|
1429
|
+
const sk = constrainedSketch();
|
|
1430
|
+
const hex = sk.regularPolygon({ sides: 6, radius: 25 });
|
|
1431
|
+
sk.fix(hex.center, 0, 0);
|
|
1432
|
+
sk.length(hex.side(0), 30); // all sides change (equal constraint)
|
|
1433
|
+
return sk.solve().extrude(5);
|
|
1434
|
+
```
|
|
1435
|
+
|
|
1436
|
+
```ts
|
|
1437
|
+
addRegularPolygon(sk: ConstrainedSketchBuilder, options: RegularPolygonOptions): ConstrainedRegularPolygon
|
|
1438
|
+
```
|
|
1439
|
+
|
|
1440
|
+
**`RegularPolygonOptions`**
|
|
1441
|
+
|
|
1442
|
+
| Option | Type | Description |
|
|
1443
|
+
|--------|------|-------------|
|
|
1444
|
+
| `sides` | `number` | Number of sides (minimum 3). |
|
|
1445
|
+
| `radius?` | `number` | Circumradius — distance from center to vertex. Default: 10. |
|
|
1446
|
+
| `cx?` | `number` | Center x coordinate. Default: 0. |
|
|
1447
|
+
| `cy?` | `number` | Center y coordinate. Default: 0. |
|
|
1448
|
+
| `startAngle?` | `number` | Angle (in degrees) of vertex[0] measured from the +X axis (CCW positive). Default: 0 (rightmost vertex). |
|
|
1449
|
+
| `blockRotation?` | `boolean` | Prevent 180° rotation (ensures first edge maintains its initial direction). Default: false. |
|
|
1450
|
+
|
|
1451
|
+
|
|
1452
|
+
**`ConstrainedRegularPolygon`** extends ConstrainedPolygon
|
|
1453
|
+
- `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.
|
|
1454
|
+
|
|
1455
|
+
#### `circle()` — Create an analytic 2D circle for measurement, construction, and extrusion.
|
|
1456
|
+
|
|
1457
|
+
```ts
|
|
1458
|
+
const c = circle(0, 0, 25);
|
|
1459
|
+
c.diameter; c.circumference; c.area;
|
|
1460
|
+
c.pointAtAngle(90); // Point2D at top (90° CCW from +X)
|
|
1461
|
+
|
|
1462
|
+
// Extrude to cylinder with named faces
|
|
1463
|
+
const cyl = c.extrude(30);
|
|
1464
|
+
cyl.face('top'); // FaceRef (planar)
|
|
1465
|
+
cyl.face('side'); // FaceRef (curved)
|
|
1466
|
+
|
|
1467
|
+
Circle2D.fromDiameter(point(0, 0), 50);
|
|
1468
|
+
```
|
|
1469
|
+
|
|
1470
|
+
```ts
|
|
1471
|
+
circle(cx: number, cy: number, radius: number): Circle2D
|
|
1472
|
+
```
|
|
1473
|
+
|
|
1474
|
+
#### `constrainedSketch()` — Create a parametric 2D sketch driven by geometric constraints and a nonlinear solver.
|
|
1475
|
+
|
|
1476
|
+
**Workflow**
|
|
1477
|
+
|
|
1478
|
+
1. Create a builder with `constrainedSketch()`.
|
|
1479
|
+
2. Add geometry — points, lines, circles, arcs — using the builder methods.
|
|
1480
|
+
3. Add constraints (`horizontal`, `length`, `fix`, etc.) to drive the geometry.
|
|
1481
|
+
4. Call `.solve()` to run the solver and get a `ConstraintSketch` (which extends `Sketch`).
|
|
1482
|
+
|
|
1483
|
+
```ts
|
|
1484
|
+
const sk = constrainedSketch();
|
|
1485
|
+
const p1 = sk.point(0, 0);
|
|
1486
|
+
const p2 = sk.point(50, 0);
|
|
1487
|
+
const l1 = sk.line(p1, p2);
|
|
1488
|
+
sk.fix(p1, 0, 0);
|
|
1489
|
+
sk.horizontal(l1);
|
|
1490
|
+
sk.length(l1, 50);
|
|
1491
|
+
return sk.solve().extrude(10);
|
|
1492
|
+
```
|
|
1493
|
+
|
|
1494
|
+
**Solver status**
|
|
1495
|
+
|
|
1496
|
+
```ts
|
|
1497
|
+
const result = sk.solve();
|
|
1498
|
+
result.constraintMeta.status; // 'fully' | 'under' | 'over' | 'over-redundant'
|
|
1499
|
+
result.constraintMeta.dof; // 0 = fully constrained
|
|
1500
|
+
result.constraintMeta.maxError; // residual — should be < 1e-6
|
|
1501
|
+
result.inspect(); // human-readable summary
|
|
1502
|
+
result.withUpdatedConstraint('cst-5', 120); // update a dimension without rebuilding
|
|
1503
|
+
```
|
|
1504
|
+
|
|
1505
|
+
```ts
|
|
1506
|
+
constrainedSketch(options?: ConstrainedSketchOptions): ConstrainedSketchBuilder
|
|
1507
|
+
```
|
|
1508
|
+
|
|
1509
|
+
**`ConstrainedSketchOptions`**
|
|
1510
|
+
- `strict?: boolean` — When true, adding a constraint that cannot be satisfied throws instead of silently discarding it.
|
|
1511
|
+
|
|
1512
|
+
#### `line()` — Create an analytic 2D line segment between two points.
|
|
1513
|
+
|
|
1514
|
+
```ts
|
|
1515
|
+
const l = line(0, 0, 50, 0);
|
|
1516
|
+
l.length; l.midpoint; l.angle; l.direction;
|
|
1517
|
+
l.parallel(10); // parallel line offset 10 (positive = left)
|
|
1518
|
+
l.intersect(l2); // Point2D — treats lines as infinite
|
|
1519
|
+
l.intersectSegment(l2); // Point2D or null — segments only
|
|
1520
|
+
|
|
1521
|
+
Line2D.fromPointAndAngle(point(0, 0), 45, 100);
|
|
1522
|
+
Line2D.fromPointAndDirection(point(0, 0), [1, 1], 50);
|
|
1523
|
+
```
|
|
1524
|
+
|
|
1525
|
+
```ts
|
|
1526
|
+
line(x1: number, y1: number, x2: number, y2: number): Line2D
|
|
1527
|
+
```
|
|
1528
|
+
|
|
1529
|
+
#### `point()` — Create an analytic 2D point for measurement and construction geometry.
|
|
1530
|
+
|
|
1531
|
+
```ts
|
|
1532
|
+
const p = point(10, 20);
|
|
1533
|
+
p.distanceTo(point(30, 40)); // Euclidean distance
|
|
1534
|
+
p.midpointTo(point(30, 40)); // midpoint
|
|
1535
|
+
p.translate(5, 5); // new shifted point
|
|
1536
|
+
p.toTuple(); // [10, 20]
|
|
1537
|
+
```
|
|
1538
|
+
|
|
1539
|
+
```ts
|
|
1540
|
+
point(x: number, y: number): Point2D
|
|
1541
|
+
```
|
|
1542
|
+
|
|
1543
|
+
---
|
|
1544
|
+
|
|
1545
|
+
## C9: Spatial Placement
|
|
1546
|
+
|
|
1547
|
+
Position geometry relative to other geometry using semantic anchors.
|
|
1548
|
+
|
|
1549
|
+
|
|
1550
|
+
#### `Points.distance()` — Euclidean distance between two 3D points.
|
|
1551
|
+
|
|
1552
|
+
```ts
|
|
1553
|
+
Points.readonly distance: typeof distance
|
|
1554
|
+
```
|
|
1555
|
+
|
|
1556
|
+
#### `Points.midpoint()` — Center point between two 3D points.
|
|
1557
|
+
|
|
1558
|
+
```ts
|
|
1559
|
+
Points.readonly midpoint: typeof midpoint
|
|
1560
|
+
```
|
|
1561
|
+
|
|
1562
|
+
#### `Points.lerp()` — Linearly interpolate between two 3D points. t=0 returns a, t=1 returns b.
|
|
1563
|
+
|
|
1564
|
+
```ts
|
|
1565
|
+
Points.readonly lerp: typeof lerp
|
|
1566
|
+
```
|
|
1567
|
+
|
|
1568
|
+
#### `Points.direction()` — Unit direction vector from a to b. Throws if a and b are the same point.
|
|
1569
|
+
|
|
1570
|
+
```ts
|
|
1571
|
+
Points.readonly direction: typeof direction
|
|
1572
|
+
```
|
|
1573
|
+
|
|
1574
|
+
#### `Points.offset()` — Move a point along a direction vector by a given amount.
|
|
1575
|
+
|
|
1576
|
+
```ts
|
|
1577
|
+
Points.readonly offset: typeof offset
|
|
1578
|
+
```
|
|
1579
|
+
|
|
1580
|
+
#### `Points.polar()` — Compute a 2D point at distance and angle (degrees) from an optional origin.
|
|
1581
|
+
|
|
1582
|
+
```ts
|
|
1583
|
+
Points.readonly polar: typeof polar
|
|
1584
|
+
```
|
|
1585
|
+
|
|
1586
|
+
---
|
|
1587
|
+
|
|
1588
|
+
## C10: Assembly & Kinematics
|
|
1589
|
+
|
|
1590
|
+
Compose parts with joints for kinematic simulation.
|
|
1591
|
+
|
|
1592
|
+
#### `assembly()` — Create an assembly container with named parts and joints for kinematic mechanisms.
|
|
1593
|
+
|
|
1594
|
+
**Use this from iteration 1 for any model with moving parts.** Hinges, sliders, gears, articulated fingers, doors — all start with `assembly()`, not with manual rotation math. Don't build a static "extended pose" first and refactor to an assembly later: joint sliders, animations, sweeps, collision detection, and robot export all flow from the kinematic graph.
|
|
1595
|
+
|
|
1596
|
+
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.
|
|
1597
|
+
|
|
1598
|
+
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.
|
|
1599
|
+
|
|
1600
|
+
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.
|
|
1601
|
+
|
|
1602
|
+
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.
|
|
1603
|
+
|
|
1604
|
+
```ts
|
|
1605
|
+
const mech = assembly("Arm")
|
|
1606
|
+
.addPart("base", box(80, 80, 20, true), {
|
|
1607
|
+
metadata: { material: "PETG", process: "FDM", qty: 1 },
|
|
1608
|
+
})
|
|
1609
|
+
.addPart("link", box(140, 24, 24).translate(0, -12, -12))
|
|
1610
|
+
.addRevolute("shoulder", "base", "link", {
|
|
1611
|
+
axis: [0, 1, 0],
|
|
1612
|
+
min: -30, max: 120, default: 25,
|
|
1613
|
+
frame: Transform.identity().translate(0, 0, 20),
|
|
1614
|
+
});
|
|
1615
|
+
|
|
1616
|
+
return mech; // auto-solved at defaults, renders all parts
|
|
1617
|
+
```
|
|
1618
|
+
|
|
1619
|
+
```ts
|
|
1620
|
+
assembly(name?: string): Assembly
|
|
1621
|
+
```
|
|
1622
|
+
|
|
1623
|
+
#### `bomToCsv()` — Convert an array of BOM rows into a CSV string.
|
|
1624
|
+
|
|
1625
|
+
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.
|
|
1626
|
+
|
|
1627
|
+
```ts
|
|
1628
|
+
bomToCsv(rows: BomRow[]): string
|
|
1629
|
+
```
|
|
1630
|
+
|
|
1631
|
+
**`BomRow`**: `part: string`, `qty: number`, `material?: string`, `process?: string`, `tolerance?: string`, `notes?: string`, `metadata?: PartMetadata`
|
|
1632
|
+
|
|
1633
|
+
**`PartMetadata`**: `material?: string`, `process?: string`, `tolerance?: string`, `qty?: number`, `notes?: string`, `densityKgM3?: number`, `massKg?: number`
|
|
1634
|
+
|
|
1635
|
+
#### `joint()` — Create a revolute joint that auto-generates a parameter slider and rotates the shape.
|
|
1636
|
+
|
|
1637
|
+
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.
|
|
1190
1638
|
|
|
1191
|
-
|
|
1639
|
+
```ts
|
|
1640
|
+
const arm = joint("Shoulder", armShape, [0, 0, 20], {
|
|
1641
|
+
axis: [0, 1, 0],
|
|
1642
|
+
min: -30, max: 120, default: 25,
|
|
1643
|
+
});
|
|
1644
|
+
return arm;
|
|
1645
|
+
```
|
|
1192
1646
|
|
|
1193
1647
|
```ts
|
|
1194
1648
|
joint(name: string, shape: Shape, pivot: [ number, number, number ], opts?: RevoluteJointOpts): Shape
|
|
1195
1649
|
```
|
|
1196
1650
|
|
|
1197
|
-
|
|
1651
|
+
`RevoluteJointOpts`: `{ min?: number, max?: number, default?: number, unit?: string, reverse?: boolean }`
|
|
1198
1652
|
|
|
1199
|
-
|
|
1653
|
+
#### `jointsView()` — Register viewport-only mechanism controls that animate returned objects without re-running the script.
|
|
1200
1654
|
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
}
|
|
1655
|
+
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.
|
|
1656
|
+
|
|
1657
|
+
**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.
|
|
1658
|
+
|
|
1659
|
+
```js
|
|
1660
|
+
// BAD — double rotation
|
|
1661
|
+
const solved = mech.solve({ shoulder: 45, elbow: 30 });
|
|
1662
|
+
jointsView({ joints: [{ name: 'shoulder', ... }] });
|
|
1663
|
+
return solved;
|
|
1664
|
+
|
|
1665
|
+
// GOOD — rest pose, jointsView controls all posing
|
|
1666
|
+
const solved = mech.solve({ shoulder: 0, elbow: 0 });
|
|
1667
|
+
jointsView({
|
|
1668
|
+
joints: [
|
|
1669
|
+
{ name: 'shoulder', child: 'Upper Arm', default: 45, ... },
|
|
1670
|
+
{ name: 'elbow', child: 'Forearm', parent: 'Upper Arm', default: 30, ... },
|
|
1671
|
+
],
|
|
1672
|
+
});
|
|
1673
|
+
return solved;
|
|
1209
1674
|
```
|
|
1210
1675
|
|
|
1211
|
-
|
|
1676
|
+
**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]`.
|
|
1212
1677
|
|
|
1213
|
-
|
|
1678
|
+
**Fixed attachments** that must follow a parent during animation need a zero-angle revolute joint in the chain:
|
|
1214
1679
|
|
|
1215
|
-
```
|
|
1216
|
-
|
|
1680
|
+
```js
|
|
1681
|
+
{ name: 'EE_Follow', child: 'End Effector', parent: 'Last Link',
|
|
1682
|
+
type: 'revolute', axis: [0, 0, 1], pivot: [linkLength, 0, 0],
|
|
1683
|
+
min: 0, max: 0, default: 0 }
|
|
1217
1684
|
```
|
|
1218
1685
|
|
|
1219
|
-
|
|
1686
|
+
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`.
|
|
1220
1687
|
|
|
1221
|
-
|
|
1688
|
+
**Tick-based keyframes:** Omit `at` from all keyframes to auto-distribute by tick weight:
|
|
1222
1689
|
|
|
1223
|
-
```
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
}
|
|
1690
|
+
```js
|
|
1691
|
+
keyframes: [
|
|
1692
|
+
{ ticks: 3, values: { Shoulder: 20 } }, // slow segment (3x weight)
|
|
1693
|
+
{ ticks: 1, values: { Shoulder: -10 } }, // fast segment (1x weight)
|
|
1694
|
+
{ values: { Shoulder: 20 } }, // last keyframe; ticks ignored
|
|
1695
|
+
]
|
|
1696
|
+
// positions: 0, 0.75, 1.0
|
|
1231
1697
|
```
|
|
1232
1698
|
|
|
1233
|
-
|
|
1699
|
+
Mixing explicit `at` and omitted `at` in the same animation is not allowed.
|
|
1234
1700
|
|
|
1235
|
-
|
|
1701
|
+
```js
|
|
1702
|
+
jointsView({
|
|
1703
|
+
joints: [{
|
|
1704
|
+
name: 'Shoulder', child: 'Upper Arm', parent: 'Base',
|
|
1705
|
+
type: 'revolute', axis: [0, -1, 0], pivot: [0, 0, 46],
|
|
1706
|
+
min: -30, max: 110, default: 15,
|
|
1707
|
+
}],
|
|
1708
|
+
animations: [{
|
|
1709
|
+
name: 'Walk Cycle', duration: 1.6, loop: true,
|
|
1710
|
+
keyframes: [
|
|
1711
|
+
{ values: { Shoulder: 20 } },
|
|
1712
|
+
{ values: { Shoulder: -10 } },
|
|
1713
|
+
{ values: { Shoulder: 20 } },
|
|
1714
|
+
],
|
|
1715
|
+
}],
|
|
1716
|
+
});
|
|
1717
|
+
```
|
|
1236
1718
|
|
|
1237
1719
|
```ts
|
|
1238
|
-
|
|
1239
|
-
name: string;
|
|
1240
|
-
child: string;
|
|
1241
|
-
parent?: string;
|
|
1242
|
-
type?: JointViewType;
|
|
1243
|
-
axis?: JointViewAxis;
|
|
1244
|
-
min?: number;
|
|
1245
|
-
max?: number;
|
|
1246
|
-
default?: number;
|
|
1247
|
-
unit?: string;
|
|
1248
|
-
hidden?: boolean;
|
|
1249
|
-
}
|
|
1720
|
+
jointsView(options?: JointsViewOptions): void
|
|
1250
1721
|
```
|
|
1251
1722
|
|
|
1252
|
-
|
|
1723
|
+
**`JointsViewOptions`**: `enabled?: boolean`, `joints?: JointViewInput[]`, `couplings?: JointViewCouplingInput[]`, `animations?: JointViewAnimationInput[]`, `defaultAnimation?: string`
|
|
1724
|
+
|
|
1725
|
+
**`JointViewInput`**: `name: string`, `child: string`, `parent?: string`, `type?: JointViewType`, `axis?: JointViewAxis`, `min?: number`, `max?: number`, `default?: number`, `unit?: string`, `hidden?: boolean`
|
|
1726
|
+
|
|
1727
|
+
`JointViewCouplingInput`: `{ joint: string, terms: JointViewCouplingTermInput[], offset?: number }`
|
|
1253
1728
|
|
|
1254
|
-
|
|
1729
|
+
`JointViewCouplingTermInput`: `{ joint: string, ratio?: number }`
|
|
1730
|
+
|
|
1731
|
+
`JointViewAnimationInput`: `{ name: string, duration?: number, loop?: boolean, continuous?: boolean, keyframes: JointViewAnimationKeyframeInput[] }`
|
|
1732
|
+
|
|
1733
|
+
**`JointViewAnimationKeyframeInput`**
|
|
1734
|
+
- `at?: number` — Timeline position [0, 1]. If omitted from ALL keyframes, positions are auto-computed from tick weights.
|
|
1735
|
+
- `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.
|
|
1736
|
+
- Also: `values: Record<string, number>`
|
|
1737
|
+
|
|
1738
|
+
---
|
|
1739
|
+
|
|
1740
|
+
## C11: Parameterization & UI
|
|
1741
|
+
|
|
1742
|
+
Declare user-facing controls that drive model geometry.
|
|
1743
|
+
|
|
1744
|
+
#### `Param.number()` — Declare a numeric parameter that renders as a slider in the UI.
|
|
1745
|
+
|
|
1746
|
+
Each 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.
|
|
1747
|
+
|
|
1748
|
+
Default range rules when options are omitted:
|
|
1749
|
+
|
|
1750
|
+
- `min` defaults to `0`
|
|
1751
|
+
- `max` defaults to `defaultValue * 4`
|
|
1752
|
+
- `step` is auto-calculated: `1` for integer params, `0.1` for ranges ≤ 100, `1` for larger ranges
|
|
1753
|
+
|
|
1754
|
+
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`).
|
|
1255
1755
|
|
|
1256
1756
|
```ts
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
offset?: number;
|
|
1261
|
-
}
|
|
1757
|
+
const width = Param.number("Width", 50);
|
|
1758
|
+
const angle = Param.number("Angle", 45, { min: 0, max: 180, unit: "°" });
|
|
1759
|
+
const sides = Param.number("Sides", 6, { min: 3, max: 12, integer: true });
|
|
1262
1760
|
```
|
|
1263
1761
|
|
|
1264
|
-
|
|
1762
|
+
**Parameter overrides** — key must match `name` exactly:
|
|
1763
|
+
|
|
1764
|
+
```ts
|
|
1765
|
+
// Via require()
|
|
1766
|
+
const bracket = require("./bracket.forge.js", { Width: 80 });
|
|
1767
|
+
|
|
1768
|
+
// Via CLI
|
|
1769
|
+
// forgecad run model.forge.js --param "Wall Thickness=3"
|
|
1770
|
+
```
|
|
1265
1771
|
|
|
1266
|
-
|
|
1772
|
+
Also available as the shorthand alias `param()`.
|
|
1267
1773
|
|
|
1268
1774
|
```ts
|
|
1269
|
-
|
|
1270
|
-
joint: string;
|
|
1271
|
-
ratio?: number;
|
|
1272
|
-
}
|
|
1775
|
+
Param.number(name: string, defaultValue: number, opts?: { min?: number; max?: number; step?: number; unit?: string; integer?: boolean; reverse?: boolean; }): number
|
|
1273
1776
|
```
|
|
1274
1777
|
|
|
1275
|
-
|
|
1778
|
+
#### `Param.string()` — Declare a string parameter that renders as a text input in the UI.
|
|
1276
1779
|
|
|
1277
|
-
|
|
1780
|
+
String parameters let users type free-form text — labels, names, inscriptions, file paths, etc. The `name` string is the override key.
|
|
1278
1781
|
|
|
1279
1782
|
```ts
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
duration?: number;
|
|
1283
|
-
loop?: boolean;
|
|
1284
|
-
continuous?: boolean;
|
|
1285
|
-
keyframes: JointViewAnimationKeyframeInput[];
|
|
1286
|
-
}
|
|
1783
|
+
const label = Param.string("Label", "Hello World");
|
|
1784
|
+
const name = Param.string("Name", "Part-001", { maxLength: 20 });
|
|
1287
1785
|
```
|
|
1288
1786
|
|
|
1289
|
-
|
|
1787
|
+
Override via import:
|
|
1290
1788
|
|
|
1291
|
-
|
|
1789
|
+
```ts
|
|
1790
|
+
const tag = require("./tag.forge.js", { Label: "Custom Text" });
|
|
1791
|
+
```
|
|
1792
|
+
|
|
1793
|
+
Only available as `Param.string()` — no standalone alias.
|
|
1292
1794
|
|
|
1293
1795
|
```ts
|
|
1294
|
-
|
|
1295
|
-
/** Timeline position [0, 1]. If omitted from ALL keyframes, positions are auto-computed from tick weights. */
|
|
1296
|
-
at?: number;
|
|
1297
|
-
/** 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. */
|
|
1298
|
-
ticks?: number;
|
|
1299
|
-
values: Record<string, number>;
|
|
1300
|
-
}
|
|
1796
|
+
Param.string(name: string, defaultValue: string, opts?: { maxLength?: number; }): string
|
|
1301
1797
|
```
|
|
1302
1798
|
|
|
1303
|
-
|
|
1799
|
+
#### `Param.bool()` — Declare a boolean parameter that renders as a checkbox in the UI.
|
|
1304
1800
|
|
|
1305
|
-
|
|
1801
|
+
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.
|
|
1306
1802
|
|
|
1307
|
-
|
|
1803
|
+
```ts
|
|
1804
|
+
const showHoles = Param.bool("Show Holes", true);
|
|
1805
|
+
if (showHoles) return difference(plate, cylinder(10, 5).translate(50, 30, 0));
|
|
1806
|
+
return plate;
|
|
1807
|
+
```
|
|
1308
1808
|
|
|
1309
|
-
|
|
1809
|
+
Override via import:
|
|
1810
|
+
|
|
1811
|
+
```ts
|
|
1812
|
+
const pan = require("./pan.forge.js", { "Show Lid": 0 });
|
|
1813
|
+
```
|
|
1310
1814
|
|
|
1311
|
-
|
|
1815
|
+
Also available as the shorthand alias `boolParam()`.
|
|
1312
1816
|
|
|
1313
1817
|
```ts
|
|
1314
|
-
|
|
1818
|
+
Param.bool(name: string, defaultValue: boolean): boolean
|
|
1315
1819
|
```
|
|
1316
1820
|
|
|
1317
|
-
Declare a
|
|
1821
|
+
#### `Param.choice()` — Declare a choice parameter that renders as a dropdown in the UI.
|
|
1318
1822
|
|
|
1319
|
-
|
|
1823
|
+
`defaultValue` must exactly match one entry in `choices`. Returns the selected string label. Prefer `Param.choice` over `Param.number` when a slider would hide intent — named choices like `"wok"` are self-describing.
|
|
1824
|
+
|
|
1825
|
+
Overrides may be passed as the choice label string (preferred) or as a numeric index. The `name` string is the override key.
|
|
1320
1826
|
|
|
1321
1827
|
```ts
|
|
1322
|
-
|
|
1828
|
+
const panStyle = Param.choice("Pan Style", "frying-pan", ["frying-pan", "saute-pan", "wok"]);
|
|
1829
|
+
if (panStyle === "wok") return buildWok();
|
|
1323
1830
|
```
|
|
1324
1831
|
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
#### `listParam()`
|
|
1832
|
+
Override via import:
|
|
1328
1833
|
|
|
1329
1834
|
```ts
|
|
1330
|
-
|
|
1835
|
+
const pan = require("./pan.forge.js", { "Pan Style": "wok" });
|
|
1331
1836
|
```
|
|
1332
1837
|
|
|
1333
|
-
|
|
1838
|
+
Override via CLI:
|
|
1839
|
+
|
|
1840
|
+
```bash
|
|
1841
|
+
forgecad run model.forge.js --param "Pan Style=wok"
|
|
1842
|
+
```
|
|
1334
1843
|
|
|
1335
|
-
|
|
1844
|
+
Also available as the shorthand alias `choiceParam()`.
|
|
1336
1845
|
|
|
1337
1846
|
```ts
|
|
1338
|
-
|
|
1339
|
-
min?: number;
|
|
1340
|
-
max?: number;
|
|
1341
|
-
step?: number;
|
|
1342
|
-
unit?: string;
|
|
1343
|
-
integer?: boolean;
|
|
1344
|
-
boolean?: boolean;
|
|
1345
|
-
choices?: string[];
|
|
1346
|
-
}
|
|
1847
|
+
Param.choice(name: string, defaultValue: string, choices: string[]): string
|
|
1347
1848
|
```
|
|
1348
1849
|
|
|
1349
|
-
|
|
1850
|
+
#### `Param.list()` — Declare a list parameter — an array of struct items with per-field UI controls.
|
|
1851
|
+
|
|
1852
|
+
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.
|
|
1350
1853
|
|
|
1351
|
-
|
|
1854
|
+
Field types:
|
|
1855
|
+
|
|
1856
|
+
- Boolean fields (`boolean: true` in field defs) return as `boolean`
|
|
1857
|
+
- Choice fields (`choices: [...]` in field defs) return as `string`
|
|
1858
|
+
- All other fields return as `number`
|
|
1352
1859
|
|
|
1353
1860
|
```ts
|
|
1354
|
-
|
|
1861
|
+
Param.list<T extends Record<string, number | boolean | string>>(name: string, defaultItems: T[], opts: { ... }): T[]
|
|
1355
1862
|
```
|
|
1356
1863
|
|
|
1357
|
-
|
|
1864
|
+
`ListParamFieldDef`: `{ min?: number, max?: number, step?: number, unit?: string, integer?: boolean, boolean?: boolean, choices?: string[] }`
|
|
1865
|
+
|
|
1866
|
+
#### `dim()` — Add a dimension annotation between two points.
|
|
1358
1867
|
|
|
1359
|
-
|
|
1868
|
+
Dimension annotations are purely visual callouts rendered in the viewport and report export. They do not affect geometry or constrain the model.
|
|
1869
|
+
|
|
1870
|
+
Point arguments accept 2D tuples `[x, y]`, 3D tuples `[x, y, z]`, or `Point2D` objects (Z is treated as 0 for 2D inputs).
|
|
1871
|
+
|
|
1872
|
+
**Ownership Rules (Report Pages)**
|
|
1873
|
+
|
|
1874
|
+
- `currentComponent: true` — deterministic ownership by the calling import instance. Use when authoring reusable imported parts.
|
|
1875
|
+
- `component: "Part Name"` — route dimension to another named returned object.
|
|
1876
|
+
- Multiple owners: dimension is shared and appears on the assembly overview page.
|
|
1877
|
+
- No ownership set: report export infers ownership via endpoint-in-bbox.
|
|
1360
1878
|
|
|
1361
1879
|
```ts
|
|
1362
|
-
dim(
|
|
1880
|
+
dim([-w / 2, 0, 0], [w / 2, 0, 0], { label: "Width" });
|
|
1881
|
+
dim([0, 0, -h / 2], [0, 0, h / 2], { label: "Height", offset: 14 });
|
|
1882
|
+
dim([0, 0, 0], [100, 0, 0], { component: "Base", color: "#00AAFF" });
|
|
1363
1883
|
```
|
|
1364
1884
|
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
<details><summary><code>DimOpts</code></summary>
|
|
1885
|
+
`component` (string or string[] — report ownership), `currentComponent` (boolean)
|
|
1368
1886
|
|
|
1369
1887
|
```ts
|
|
1370
|
-
|
|
1371
|
-
offset?: number;
|
|
1372
|
-
label?: string;
|
|
1373
|
-
color?: string;
|
|
1374
|
-
component?: string | string[];
|
|
1375
|
-
currentComponent?: boolean;
|
|
1376
|
-
}
|
|
1888
|
+
dim(from: PointArg, to: PointArg, opts?: DimOpts): void
|
|
1377
1889
|
```
|
|
1378
1890
|
|
|
1379
|
-
|
|
1891
|
+
`DimOpts`: `{ offset?: number, label?: string, color?: string, component?: string | string[], currentComponent?: boolean }`
|
|
1892
|
+
|
|
1893
|
+
#### `dimLine()` — Add a dimension annotation along a `Line2D`.
|
|
1380
1894
|
|
|
1381
|
-
|
|
1895
|
+
Convenience wrapper around { points from a constrained-sketch `Line2D` entity. All `opts` are forwarded unchanged.
|
|
1382
1896
|
|
|
1383
1897
|
```ts
|
|
1384
|
-
|
|
1898
|
+
const a = point(0, 0);
|
|
1899
|
+
const b = point(100, 0);
|
|
1900
|
+
dimLine(line(a, b), { label: "Span", offset: -8 });
|
|
1385
1901
|
```
|
|
1386
1902
|
|
|
1387
|
-
|
|
1903
|
+
```ts
|
|
1904
|
+
dimLine(l: Line2D, opts?: DimOpts): void
|
|
1905
|
+
```
|
|
1388
1906
|
|
|
1389
1907
|
---
|
|
1390
1908
|
|
|
@@ -1392,700 +1910,618 @@ Add a dimension annotation along a Line2D.
|
|
|
1392
1910
|
|
|
1393
1911
|
Extract 2D geometry from a 3D solid (section, projection).
|
|
1394
1912
|
|
|
1395
|
-
#### `faceProfile()`
|
|
1913
|
+
#### `faceProfile()` — Extract the boundary profile of a named face as a 2D sketch.
|
|
1914
|
+
|
|
1915
|
+
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.
|
|
1396
1916
|
|
|
1397
1917
|
```ts
|
|
1398
1918
|
faceProfile(shape: Shape, face: FaceSelector): Sketch
|
|
1399
1919
|
```
|
|
1400
1920
|
|
|
1401
|
-
#### `intersectWithPlane()`
|
|
1921
|
+
#### `intersectWithPlane()` — Cross-section: slice a 3D shape with a plane and return the intersection as a 2D Sketch.
|
|
1402
1922
|
|
|
1403
1923
|
```ts
|
|
1404
1924
|
intersectWithPlane(shape: Shape, plane: PlaneSpec): Sketch
|
|
1405
1925
|
```
|
|
1406
1926
|
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
#### `projectToPlane()`
|
|
1927
|
+
#### `projectToPlane()` — Orthographically project a 3D shape onto a plane and return the silhouette as a 2D Sketch.
|
|
1410
1928
|
|
|
1411
1929
|
```ts
|
|
1412
1930
|
projectToPlane(shape: Shape, plane: PlaneSpec): Sketch
|
|
1413
1931
|
```
|
|
1414
1932
|
|
|
1415
|
-
Orthographically project a 3D shape onto a plane and return the silhouette as a 2D Sketch.
|
|
1416
|
-
|
|
1417
1933
|
---
|
|
1418
1934
|
|
|
1419
1935
|
## C13: Export & Output
|
|
1420
1936
|
|
|
1421
1937
|
Convert geometry to external formats (STL, 3MF, SVG, DXF, G-code, PDF).
|
|
1422
1938
|
|
|
1423
|
-
#### `bom()`
|
|
1939
|
+
#### `bom()` — Register a Bill of Materials entry for report export.
|
|
1940
|
+
|
|
1941
|
+
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.
|
|
1942
|
+
|
|
1943
|
+
- `quantity` must be a finite number `>= 0`. A quantity of `0` is silently ignored (useful for conditional scripting with `param()`-driven counts).
|
|
1944
|
+
- `unit` defaults to `"pieces"` when omitted or empty.
|
|
1945
|
+
- The assembly `solved.bom()` / `solved.bomCsv()` API is separate and covers per-part assembly metadata; this function is for free-form purchased-item annotation.
|
|
1946
|
+
|
|
1947
|
+
```ts
|
|
1948
|
+
const tubeLen = param("Tube Length", 1200, { min: 300, max: 4000, unit: "mm" });
|
|
1949
|
+
const boltCount = param("Bolt Count", 16, { min: 0, max: 200, integer: true });
|
|
1950
|
+
|
|
1951
|
+
bom(tubeLen, "iron tube 30 x 20", { unit: "mm" });
|
|
1952
|
+
bom(boltCount, "M4 bolt, 16 mm length");
|
|
1953
|
+
bom(4, "rubber foot", { key: "foot-rubber" }); // explicit aggregation key
|
|
1954
|
+
|
|
1955
|
+
// Structured metadata for richer reports:
|
|
1956
|
+
bom(tubeLen, "rectangular steel tube", {
|
|
1957
|
+
unit: "mm",
|
|
1958
|
+
material: "steel",
|
|
1959
|
+
section: [30, 20],
|
|
1960
|
+
wall: 3,
|
|
1961
|
+
});
|
|
1962
|
+
```
|
|
1424
1963
|
|
|
1425
1964
|
```ts
|
|
1426
1965
|
bom(quantity: number, description: string, opts?: BomOpts): void
|
|
1427
1966
|
```
|
|
1428
1967
|
|
|
1429
|
-
|
|
1968
|
+
**`BomOpts`**
|
|
1969
|
+
|
|
1970
|
+
| Option | Type | Description |
|
|
1971
|
+
|--------|------|-------------|
|
|
1972
|
+
| `unit?` | `string` | Quantity unit label, e.g. "mm", "pieces", "kg". Default: "pieces" |
|
|
1973
|
+
| `key?` | `string` | Optional explicit grouping key used during report aggregation. |
|
|
1974
|
+
| `material?` | `string` | Material name, e.g. "steel", "birch plywood", "nylon" |
|
|
1975
|
+
| `dimensions?` | `number[]` | Overall dimensions `[width, height]` or `[width, height, thickness]` in the entry's unit |
|
|
1976
|
+
| `section?` | `number[]` | Cross-section dimensions `[w, h]` for tubes and profiles |
|
|
1977
|
+
| `wall?` | `number` | Wall thickness for hollow sections (mm) |
|
|
1978
|
+
| `diameter?` | `number` | Diameter for round stock, bolts, dowels (mm) |
|
|
1979
|
+
| `length?` | `number` | Length for fasteners (mm) |
|
|
1980
|
+
| `process?` | `string` | Manufacturing process, e.g. "laser cut", "CNC", "welded" |
|
|
1981
|
+
| `notes?` | `string` | Free-form notes |
|
|
1982
|
+
| `grain?` | `string` | Wood grain direction, e.g. "long", "cross" |
|
|
1983
|
+
|
|
1984
|
+
#### `robotExport()` — Declare that this script should export the assembly as a SDF/URDF robot package.
|
|
1985
|
+
|
|
1986
|
+
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:
|
|
1987
|
+
|
|
1988
|
+
- Mesh-based inertia tensors (full 6-component, not bounding-box approximations)
|
|
1989
|
+
- Separate collision meshes (convex hull by default — ~50–80% smaller)
|
|
1990
|
+
- Joint mimic elements derived from `addJointCoupling` / `addGearCoupling`
|
|
1430
1991
|
|
|
1431
|
-
|
|
1992
|
+
**Collision mesh modes** (set per-link via `links["PartName"].collision`):
|
|
1993
|
+
|
|
1994
|
+
| 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 | |
|
|
1995
|
+
|
|
1996
|
+
**Unit conventions:**
|
|
1997
|
+
|
|
1998
|
+
- Revolute `velocity` is in degrees/second in Forge; exporters convert to rad/s.
|
|
1999
|
+
- Prismatic distances are in mm in Forge; exported in meters.
|
|
2000
|
+
- `massKg` is preferred; `densityKgM3` is used when mass is unknown.
|
|
2001
|
+
- 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.
|
|
1432
2002
|
|
|
1433
2003
|
```ts
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
2004
|
+
const rover = assembly("Scout")
|
|
2005
|
+
.addPart("Chassis", box(300, 220, 50, true))
|
|
2006
|
+
.addPart("Left Wheel", cylinder(30, 60, undefined, 48, true))
|
|
2007
|
+
.addRevolute("leftWheel", "Chassis", "Left Wheel", {
|
|
2008
|
+
axis: [0, 1, 0],
|
|
2009
|
+
frame: Transform.identity().translate(90, 140, 60),
|
|
2010
|
+
effort: 20, velocity: 1080,
|
|
2011
|
+
});
|
|
2012
|
+
|
|
2013
|
+
robotExport({
|
|
2014
|
+
assembly: rover,
|
|
2015
|
+
modelName: "Scout",
|
|
2016
|
+
links: {
|
|
2017
|
+
Chassis: { massKg: 10 },
|
|
2018
|
+
"Left Wheel": { massKg: 0.8 },
|
|
2019
|
+
},
|
|
2020
|
+
plugins: {
|
|
2021
|
+
diffDrive: {
|
|
2022
|
+
leftJoints: ["leftWheel"], rightJoints: ["rightWheel"],
|
|
2023
|
+
wheelSeparationMm: 280, wheelRadiusMm: 60,
|
|
2024
|
+
},
|
|
2025
|
+
},
|
|
2026
|
+
world: { generateDemoWorld: true },
|
|
2027
|
+
});
|
|
1440
2028
|
```
|
|
1441
2029
|
|
|
1442
|
-
|
|
2030
|
+
**CLI usage**
|
|
1443
2031
|
|
|
1444
|
-
|
|
2032
|
+
```bash
|
|
2033
|
+
forgecad export sdf model.forge.js # SDF package (Gazebo/Ignition)
|
|
2034
|
+
forgecad export urdf model.forge.js # URDF package (ROS/PyBullet/MuJoCo)
|
|
2035
|
+
```
|
|
1445
2036
|
|
|
1446
2037
|
```ts
|
|
1447
2038
|
robotExport(options: RobotExportOptions): CollectedRobotExport
|
|
1448
2039
|
```
|
|
1449
2040
|
|
|
1450
|
-
|
|
2041
|
+
**`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`
|
|
1451
2042
|
|
|
1452
|
-
|
|
2043
|
+
`RobotLinkExportOptions`: `{ massKg?: number, densityKgM3?: number, collision?: "visual" | "convex" | "box" | "none" }`
|
|
1453
2044
|
|
|
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
|
-
```
|
|
2045
|
+
`RobotJointExportOptions`: `{ effort?: number, velocity?: number, damping?: number, friction?: number }`
|
|
1469
2046
|
|
|
1470
|
-
|
|
2047
|
+
**`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`
|
|
1471
2048
|
|
|
1472
|
-
|
|
2049
|
+
`RobotJointStatePublisherOptions`: `{ enabled?: boolean, joints?: string[], topic?: string, updateRate?: number }`
|
|
1473
2050
|
|
|
1474
|
-
|
|
1475
|
-
interface RobotLinkExportOptions {
|
|
1476
|
-
massKg?: number;
|
|
1477
|
-
densityKgM3?: number;
|
|
1478
|
-
collision?: "visual" | "convex" | "box" | "none";
|
|
1479
|
-
}
|
|
1480
|
-
```
|
|
2051
|
+
`RobotWorldOptions`: `{ name?: string, generateDemoWorld?: boolean, spawnPose?: RobotPose6, keyboardTeleop?: RobotWorldKeyboardTeleopOptions }`
|
|
1481
2052
|
|
|
1482
|
-
|
|
2053
|
+
`RobotWorldKeyboardTeleopOptions`: `{ enabled?: boolean, linearStep?: number, angularStep?: number }`
|
|
1483
2054
|
|
|
1484
|
-
<
|
|
2055
|
+
**`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`
|
|
1485
2056
|
|
|
1486
|
-
|
|
1487
|
-
interface RobotJointExportOptions {
|
|
1488
|
-
effort?: number;
|
|
1489
|
-
velocity?: number;
|
|
1490
|
-
damping?: number;
|
|
1491
|
-
friction?: number;
|
|
1492
|
-
}
|
|
1493
|
-
```
|
|
2057
|
+
`AssemblyDefinition`: `{ name: string, parts: AssemblyPartDef[], joints: AssemblyJointDef[], jointCouplings: AssemblyJointCouplingDef[] }`
|
|
1494
2058
|
|
|
1495
|
-
|
|
2059
|
+
`AssemblyPartDef`: `{ name: string, part: AssemblyPart, base: Transform, metadata?: PartMetadata }`
|
|
1496
2060
|
|
|
1497
|
-
|
|
2061
|
+
**`PartMetadata`**: `material?: string`, `process?: string`, `tolerance?: string`, `qty?: number`, `notes?: string`, `densityKgM3?: number`, `massKg?: number`
|
|
1498
2062
|
|
|
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
|
-
```
|
|
2063
|
+
**`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`
|
|
1516
2064
|
|
|
1517
|
-
|
|
2065
|
+
`AssemblyJointCouplingDef`: `{ joint: string, terms: JointCouplingTermRecord[], offset: number }`
|
|
1518
2066
|
|
|
1519
|
-
|
|
2067
|
+
`JointCouplingTermRecord`: `{ joint: string, ratio: number }`
|
|
1520
2068
|
|
|
1521
|
-
|
|
1522
|
-
interface RobotJointStatePublisherOptions {
|
|
1523
|
-
enabled?: boolean;
|
|
1524
|
-
joints?: string[];
|
|
1525
|
-
topic?: string;
|
|
1526
|
-
updateRate?: number;
|
|
1527
|
-
}
|
|
1528
|
-
```
|
|
2069
|
+
#### `sheetMetal()` — Create a parametric sheet metal part with flanges, bend allowances, and flat-pattern unfolding.
|
|
1529
2070
|
|
|
1530
|
-
|
|
2071
|
+
`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.
|
|
1531
2072
|
|
|
1532
|
-
|
|
2073
|
+
**Recommended authoring order:**
|
|
1533
2074
|
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
spawnPose?: RobotPose6;
|
|
1539
|
-
keyboardTeleop?: RobotWorldKeyboardTeleopOptions;
|
|
1540
|
-
}
|
|
1541
|
-
```
|
|
2075
|
+
1. Define the base panel + thickness + bend parameters.
|
|
2076
|
+
2. Chain `.flange()` calls for each edge. Validate with `.folded()` and `.flatPattern()` before adding cutouts.
|
|
2077
|
+
3. Add panel cutouts, then flange cutouts one region at a time.
|
|
2078
|
+
4. Validate after each new cutout region.
|
|
1542
2079
|
|
|
1543
|
-
|
|
2080
|
+
**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.
|
|
1544
2081
|
|
|
1545
|
-
|
|
2082
|
+
```ts
|
|
2083
|
+
const cover = sheetMetal({
|
|
2084
|
+
panel: { width: 180, height: 110 },
|
|
2085
|
+
thickness: 1.5,
|
|
2086
|
+
bendRadius: 2,
|
|
2087
|
+
bendAllowance: { kFactor: 0.42 },
|
|
2088
|
+
cornerRelief: { size: 4 },
|
|
2089
|
+
})
|
|
2090
|
+
.flange('top', { length: 18 })
|
|
2091
|
+
.flange('right', { length: 18 })
|
|
2092
|
+
.flange('bottom', { length: 18 })
|
|
2093
|
+
.flange('left', { length: 18 })
|
|
2094
|
+
.cutout('panel', rect(72, 36, true), { selfAnchor: 'center' })
|
|
2095
|
+
.cutout('flange-right', roundedRect(26, 10, 5, true), { selfAnchor: 'center' });
|
|
2096
|
+
|
|
2097
|
+
const folded = cover.folded();
|
|
2098
|
+
const flat = cover.flatPattern();
|
|
2099
|
+
```
|
|
1546
2100
|
|
|
1547
2101
|
```ts
|
|
1548
|
-
|
|
1549
|
-
enabled?: boolean;
|
|
1550
|
-
linearStep?: number;
|
|
1551
|
-
angularStep?: number;
|
|
1552
|
-
}
|
|
2102
|
+
sheetMetal(options: SheetMetalOptions): SheetMetalPart
|
|
1553
2103
|
```
|
|
1554
2104
|
|
|
1555
|
-
|
|
2105
|
+
**`SheetMetalOptions`**
|
|
1556
2106
|
|
|
1557
|
-
|
|
2107
|
+
| Option | Type | Description |
|
|
2108
|
+
|--------|------|-------------|
|
|
2109
|
+
| `width` | `number` | Width of the panel along the X axis. |
|
|
2110
|
+
| `height` | `number` | Height of the panel along the Y axis. |
|
|
2111
|
+
| `thickness` | `number` | Sheet thickness in mm. Applied uniformly across the panel and all flanges. |
|
|
2112
|
+
| `bendRadius` | `number` | Inside bend radius in mm. Must be ≥ 0. Typically 0.5–2× the sheet thickness. |
|
|
2113
|
+
| `kFactor` | `number` | K-factor (neutral-axis offset, 0–1). |
|
|
2114
|
+
| `kind?` | `"rect"` | Relief shape — only `'rect'` is supported in v1. |
|
|
2115
|
+
| `size` | `number` | Side length of the square relief cut in mm. |
|
|
1558
2116
|
|
|
1559
|
-
|
|
1560
|
-
interface CollectedRobotExport {
|
|
1561
|
-
modelName: string;
|
|
1562
|
-
assembly: AssemblyDefinition;
|
|
1563
|
-
state: JointState;
|
|
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
|
-
```
|
|
2117
|
+
#### `sketchToDxf()` — Export a 2D sketch as a DXF string (R12/AC1009 — maximally compatible).
|
|
1574
2118
|
|
|
1575
|
-
|
|
2119
|
+
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.
|
|
1576
2120
|
|
|
1577
|
-
|
|
2121
|
+
The R12 format is chosen for maximum compatibility with CAM tools, laser-cutter software, and older CAD readers.
|
|
1578
2122
|
|
|
1579
2123
|
```ts
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
parts: AssemblyPartDef[];
|
|
1583
|
-
joints: AssemblyJointDef[];
|
|
1584
|
-
jointCouplings: AssemblyJointCouplingDef[];
|
|
1585
|
-
}
|
|
2124
|
+
const s = rect(100, 60);
|
|
2125
|
+
const dxf = sketchToDxf(s, { layer: 'cut' });
|
|
1586
2126
|
```
|
|
1587
2127
|
|
|
1588
|
-
</details>
|
|
1589
|
-
|
|
1590
|
-
<details><summary><code>AssemblyPartDef</code></summary>
|
|
1591
|
-
|
|
1592
2128
|
```ts
|
|
1593
|
-
|
|
1594
|
-
name: string;
|
|
1595
|
-
part: AssemblyPart;
|
|
1596
|
-
base: Transform;
|
|
1597
|
-
metadata?: PartMetadata;
|
|
1598
|
-
}
|
|
2129
|
+
sketchToDxf(sketch: Sketch, options?: SketchDxfOptions): string
|
|
1599
2130
|
```
|
|
1600
2131
|
|
|
1601
|
-
|
|
2132
|
+
**`SketchDxfOptions`**
|
|
2133
|
+
- `layer?: string` — DXF layer name. Default: "0"
|
|
2134
|
+
- `colorIndex?: number` — DXF color index (1–255, AutoCAD ACI). Default: 7 (white/black)
|
|
1602
2135
|
|
|
1603
|
-
|
|
2136
|
+
#### `sketchToSvg()` — Export a 2D sketch as an SVG string.
|
|
1604
2137
|
|
|
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
|
-
```
|
|
2138
|
+
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.
|
|
1616
2139
|
|
|
1617
|
-
|
|
2140
|
+
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).
|
|
1618
2141
|
|
|
1619
|
-
|
|
2142
|
+
```ts
|
|
2143
|
+
const s = rect(100, 60);
|
|
2144
|
+
const svg = sketchToSvg(s, { stroke: '#333', strokeWidth: 0.8 });
|
|
2145
|
+
```
|
|
1620
2146
|
|
|
1621
2147
|
```ts
|
|
1622
|
-
|
|
1623
|
-
name: string;
|
|
1624
|
-
type: JointType;
|
|
1625
|
-
parent: string;
|
|
1626
|
-
child: string;
|
|
1627
|
-
frame: Transform;
|
|
1628
|
-
axis: Vec3;
|
|
1629
|
-
min?: number;
|
|
1630
|
-
max?: number;
|
|
1631
|
-
defaultValue: number;
|
|
1632
|
-
unit?: string;
|
|
1633
|
-
effort?: number;
|
|
1634
|
-
velocity?: number;
|
|
1635
|
-
damping?: number;
|
|
1636
|
-
friction?: number;
|
|
1637
|
-
}
|
|
2148
|
+
sketchToSvg(sketch: Sketch, options?: SketchSvgOptions): string
|
|
1638
2149
|
```
|
|
1639
2150
|
|
|
1640
|
-
|
|
2151
|
+
**`SketchSvgOptions`**
|
|
1641
2152
|
|
|
1642
|
-
|
|
2153
|
+
| Option | Type | Description |
|
|
2154
|
+
|--------|------|-------------|
|
|
2155
|
+
| `stroke?` | `string` | Stroke color. Default: "black" |
|
|
2156
|
+
| `strokeWidth?` | `number` | Stroke width in sketch units. Default: 0.5 |
|
|
2157
|
+
| `fill?` | `string` | Fill color. Default: "none" |
|
|
2158
|
+
| `padding?` | `number` | Padding around the sketch bounding box in sketch units. Default: 2 |
|
|
2159
|
+
| `pixelsPerUnit?` | `number` | If set, scale so 1 sketch-unit = this many px. Otherwise auto-fit. |
|
|
1643
2160
|
|
|
1644
|
-
|
|
1645
|
-
interface AssemblyJointCouplingDef {
|
|
1646
|
-
joint: string;
|
|
1647
|
-
terms: JointCouplingTermRecord[];
|
|
1648
|
-
offset: number;
|
|
1649
|
-
}
|
|
1650
|
-
```
|
|
2161
|
+
---
|
|
1651
2162
|
|
|
1652
|
-
|
|
2163
|
+
## C14: Visual & Debugging
|
|
2164
|
+
|
|
2165
|
+
Control viewport appearance and debugging aids.
|
|
1653
2166
|
|
|
1654
|
-
|
|
2167
|
+
#### `verify.that()` — Custom predicate check.
|
|
1655
2168
|
|
|
1656
2169
|
```ts
|
|
1657
|
-
|
|
1658
|
-
joint: string;
|
|
1659
|
-
ratio: number;
|
|
1660
|
-
}
|
|
2170
|
+
verify.that(label: string, check: () => boolean, message?: string): void
|
|
1661
2171
|
```
|
|
1662
2172
|
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
#### `sheetMetal()`
|
|
2173
|
+
#### `verify.equal()` — Check that two numbers are approximately equal (within tolerance).
|
|
1666
2174
|
|
|
1667
2175
|
```ts
|
|
1668
|
-
|
|
2176
|
+
verify.equal(label: string, actual: number, expected: number, tolerance?: number, message?: string): void
|
|
1669
2177
|
```
|
|
1670
2178
|
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
<details><summary><code>SheetMetalOptions</code></summary>
|
|
2179
|
+
#### `verify.notEqual()` — Check that two numbers are NOT equal (differ by more than tolerance).
|
|
1674
2180
|
|
|
1675
2181
|
```ts
|
|
1676
|
-
|
|
1677
|
-
width: number;
|
|
1678
|
-
height: number;
|
|
1679
|
-
thickness: number;
|
|
1680
|
-
bendRadius: number;
|
|
1681
|
-
kFactor: number;
|
|
1682
|
-
kind?: "rect";
|
|
1683
|
-
size: number;
|
|
1684
|
-
}
|
|
2182
|
+
verify.notEqual(label: string, actual: number, unexpected: number, tolerance?: number, message?: string): void
|
|
1685
2183
|
```
|
|
1686
2184
|
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
#### `sketchToDxf()`
|
|
2185
|
+
#### `verify.greaterThan()` — Check that actual > min.
|
|
1690
2186
|
|
|
1691
2187
|
```ts
|
|
1692
|
-
|
|
2188
|
+
verify.greaterThan(label: string, actual: number, min: number, message?: string): void
|
|
1693
2189
|
```
|
|
1694
2190
|
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
<details><summary><code>SketchDxfOptions</code></summary>
|
|
2191
|
+
#### `verify.lessThan()` — Check that actual < max.
|
|
1698
2192
|
|
|
1699
2193
|
```ts
|
|
1700
|
-
|
|
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
|
-
}
|
|
2194
|
+
verify.lessThan(label: string, actual: number, max: number, message?: string): void
|
|
1706
2195
|
```
|
|
1707
2196
|
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
#### `sketchToSvg()`
|
|
2197
|
+
#### `verify.inRange()` — Check that min <= actual <= max.
|
|
1711
2198
|
|
|
1712
2199
|
```ts
|
|
1713
|
-
|
|
2200
|
+
verify.inRange(label: string, actual: number, min: number, max: number, message?: string): void
|
|
1714
2201
|
```
|
|
1715
2202
|
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
<details><summary><code>SketchSvgOptions</code></summary>
|
|
2203
|
+
#### `verify.centersCoincide()` — Check that the bounding-box centers of two shapes coincide within tolerance (mm).
|
|
1719
2204
|
|
|
1720
2205
|
```ts
|
|
1721
|
-
|
|
1722
|
-
/** Stroke color. Default: "black" */
|
|
1723
|
-
stroke?: string;
|
|
1724
|
-
/** Stroke width in sketch units. Default: 0.5 */
|
|
1725
|
-
strokeWidth?: number;
|
|
1726
|
-
/** Fill color. Default: "none" */
|
|
1727
|
-
fill?: string;
|
|
1728
|
-
/** Padding around the sketch bounding box in sketch units. Default: 2 */
|
|
1729
|
-
padding?: number;
|
|
1730
|
-
/** If set, scale so 1 sketch-unit = this many px. Otherwise auto-fit. */
|
|
1731
|
-
pixelsPerUnit?: number;
|
|
1732
|
-
}
|
|
2206
|
+
verify.centersCoincide(label: string, a: ShapeLike, b: ShapeLike, tolerance?: number): void
|
|
1733
2207
|
```
|
|
1734
2208
|
|
|
1735
|
-
|
|
2209
|
+
#### `verify.notColliding()` — Check that two shapes do not collide (minGap > 0).
|
|
1736
2210
|
|
|
1737
|
-
|
|
2211
|
+
```ts
|
|
2212
|
+
verify.notColliding(label: string, a: ShapeLike, b: ShapeLike, searchLength?: number): void
|
|
2213
|
+
```
|
|
1738
2214
|
|
|
1739
|
-
|
|
2215
|
+
#### `verify.minClearance()` — Check that a minimum clearance gap exists between two shapes.
|
|
1740
2216
|
|
|
1741
|
-
|
|
2217
|
+
```ts
|
|
2218
|
+
verify.minClearance(label: string, a: ShapeLike, b: ShapeLike, minGap: number, searchLength?: number): void
|
|
2219
|
+
```
|
|
1742
2220
|
|
|
1743
|
-
#### `
|
|
2221
|
+
#### `verify.parallel()` — Check that two face normals are parallel (within toleranceDeg degrees).
|
|
1744
2222
|
|
|
1745
2223
|
```ts
|
|
1746
|
-
|
|
2224
|
+
verify.parallel(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number): void
|
|
1747
2225
|
```
|
|
1748
2226
|
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
<details><summary><code>ExplodeViewOptions</code></summary>
|
|
2227
|
+
#### `verify.perpendicular()` — Check that two face normals are perpendicular (within toleranceDeg degrees).
|
|
1752
2228
|
|
|
1753
2229
|
```ts
|
|
1754
|
-
|
|
1755
|
-
/** Set false to disable viewport explode offsets for this script output. */
|
|
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
|
-
}
|
|
2230
|
+
verify.perpendicular(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number): void
|
|
1770
2231
|
```
|
|
1771
2232
|
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
<details><summary><code>ExplodeDirective</code></summary>
|
|
2233
|
+
#### `verify.coplanar()` — Check that a face is coplanar with (same plane as) another face, meaning they are parallel AND their centers lie on the same plane.
|
|
1775
2234
|
|
|
1776
2235
|
```ts
|
|
1777
|
-
|
|
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
|
-
}
|
|
2236
|
+
verify.coplanar(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number, toleranceMm?: number): void
|
|
1785
2237
|
```
|
|
1786
2238
|
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
<details><summary><code>ExplodeViewDirective</code> extends ExplodeDirective</summary>
|
|
2239
|
+
#### `verify.faceAt()` — Check that a face center lies at a specific position (within toleranceMm).
|
|
1790
2240
|
|
|
1791
2241
|
```ts
|
|
1792
|
-
|
|
1793
|
-
}
|
|
2242
|
+
verify.faceAt(label: string, face: FaceRefLike, expectedPos: [ number
|
|
1794
2243
|
```
|
|
1795
2244
|
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
#### `cutPlane()`
|
|
2245
|
+
#### `verify.sameDirection()` — Check that two face normals point in the same direction (not antiparallel). Stricter than parallel — both |angle| AND sign must match.
|
|
1799
2246
|
|
|
1800
2247
|
```ts
|
|
1801
|
-
|
|
2248
|
+
verify.sameDirection(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number): void
|
|
1802
2249
|
```
|
|
1803
2250
|
|
|
1804
|
-
|
|
2251
|
+
#### `verify.isEmpty()` — Check that a shape is empty.
|
|
1805
2252
|
|
|
1806
2253
|
```ts
|
|
1807
|
-
|
|
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
|
-
}
|
|
2254
|
+
verify.isEmpty(label: string, shape: ShapeLike, message?: string): void
|
|
1813
2255
|
```
|
|
1814
2256
|
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
#### `scene()`
|
|
2257
|
+
#### `verify.notEmpty()` — Check that a shape is NOT empty.
|
|
1818
2258
|
|
|
1819
2259
|
```ts
|
|
1820
|
-
|
|
2260
|
+
verify.notEmpty(label: string, shape: ShapeLike, message?: string): void
|
|
1821
2261
|
```
|
|
1822
2262
|
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
<details><summary><code>SceneOptions</code></summary>
|
|
2263
|
+
#### `verify.volumeApprox()` — Check that a shape's volume is approximately equal to expected (mm³).
|
|
1826
2264
|
|
|
1827
2265
|
```ts
|
|
1828
|
-
|
|
1829
|
-
background?: string | SceneBackgroundGradient;
|
|
1830
|
-
camera?: SceneCameraConfig;
|
|
1831
|
-
lights?: SceneLightConfig[];
|
|
1832
|
-
environment?: SceneEnvironmentConfig;
|
|
1833
|
-
fog?: SceneFogConfig;
|
|
1834
|
-
postProcessing?: ScenePostProcessingConfig;
|
|
1835
|
-
ground?: SceneGroundConfig;
|
|
1836
|
-
/** Default capture parameters for `forgecad capture` — CLI flags override these. */
|
|
1837
|
-
capture?: SceneCaptureConfig;
|
|
1838
|
-
}
|
|
2266
|
+
verify.volumeApprox(label: string, shape: ShapeLike, expected: number, tolerance?: number): void
|
|
1839
2267
|
```
|
|
1840
2268
|
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
<details><summary><code>SceneBackgroundGradient</code></summary>
|
|
2269
|
+
#### `verify.areaApprox()` — Check that a shape's surface area is approximately equal to expected (mm²).
|
|
1844
2270
|
|
|
1845
2271
|
```ts
|
|
1846
|
-
|
|
1847
|
-
top: string;
|
|
1848
|
-
bottom: string;
|
|
1849
|
-
}
|
|
2272
|
+
verify.areaApprox(label: string, shape: ShapeLike, expected: number, tolerance?: number): void
|
|
1850
2273
|
```
|
|
1851
2274
|
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
<details><summary><code>SceneCameraConfig</code></summary>
|
|
2275
|
+
#### `verify.boundingBoxSize()` — Check that a shape's bounding box has approximately the given size.
|
|
1855
2276
|
|
|
1856
2277
|
```ts
|
|
1857
|
-
|
|
1858
|
-
fov?: number;
|
|
1859
|
-
type?: "perspective" | "orthographic";
|
|
1860
|
-
}
|
|
2278
|
+
verify.boundingBoxSize(label: string, shape: ShapeLike, expectedSize: [ number
|
|
1861
2279
|
```
|
|
1862
2280
|
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
<details><summary><code>SceneLightConfig</code></summary>
|
|
1866
|
-
|
|
1867
|
-
```ts
|
|
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
|
-
```
|
|
2281
|
+
#### `explodeView()` — Configure how the viewport explode slider offsets returned objects.
|
|
1888
2282
|
|
|
1889
|
-
|
|
2283
|
+
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.
|
|
1890
2284
|
|
|
1891
|
-
|
|
2285
|
+
Multiple calls merge — later values override earlier ones on a per-key basis. `byName` and `byPath` maps are merged entry-by-entry.
|
|
2286
|
+
|
|
2287
|
+
For programmatic explode applied before returning (without the slider), use `lib.explode()` instead.
|
|
2288
|
+
|
|
2289
|
+
```js
|
|
2290
|
+
explodeView({
|
|
2291
|
+
amountScale: 1.2,
|
|
2292
|
+
stages: [0.35, 0.8],
|
|
2293
|
+
mode: 'radial',
|
|
2294
|
+
byPath: { 'Drive/Shaft': { direction: [1, 0, 0], stage: 1.6 } },
|
|
2295
|
+
});
|
|
2296
|
+
```
|
|
1892
2297
|
|
|
1893
2298
|
```ts
|
|
1894
|
-
|
|
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
|
-
}
|
|
2299
|
+
explodeView(options?: ExplodeViewOptions): void
|
|
1902
2300
|
```
|
|
1903
2301
|
|
|
1904
|
-
|
|
2302
|
+
**`ExplodeViewOptions`**
|
|
2303
|
+
|
|
2304
|
+
| Option | Type | Description |
|
|
2305
|
+
|--------|------|-------------|
|
|
2306
|
+
| `enabled?` | `boolean` | Set false to disable viewport explode offsets for this script output. |
|
|
2307
|
+
| `amountScale?` | `number` | Scales the UI explode amount. Default: 1 |
|
|
2308
|
+
| `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, ...) |
|
|
2309
|
+
| `mode?` | `ExplodeViewDirection` | Global direction mode fallback. Default: 'radial' |
|
|
2310
|
+
| `axisLock?` | `ExplodeAxis` | Global axis lock fallback. |
|
|
2311
|
+
| `byName?` | `Record<string, ExplodeViewDirective>` | Per-object overrides by final object name. |
|
|
2312
|
+
| `byPath?` | `Record<string, ExplodeViewDirective>` | Per-tree-path overrides using slash-separated object tree segments. |
|
|
1905
2313
|
|
|
1906
|
-
|
|
2314
|
+
**`ExplodeDirective`**
|
|
2315
|
+
- `stage?: number` — Multiplier applied to `amount` for this node
|
|
2316
|
+
- `direction?: ExplodeDirection` — Direction mode for this node
|
|
2317
|
+
- `axisLock?: ExplodeAxis` — Optional axis lock after direction is resolved
|
|
2318
|
+
|
|
2319
|
+
#### `cutPlane()`
|
|
1907
2320
|
|
|
1908
2321
|
```ts
|
|
1909
|
-
|
|
1910
|
-
color?: string;
|
|
1911
|
-
/** Linear fog near distance */
|
|
1912
|
-
near?: number;
|
|
1913
|
-
/** Linear fog far distance */
|
|
1914
|
-
far?: number;
|
|
1915
|
-
/** Exponential fog density (if set, uses FogExp2 instead of linear Fog) */
|
|
1916
|
-
density?: number;
|
|
1917
|
-
}
|
|
2322
|
+
cutPlane(name: string, normal: [ number, number, number ], options?: CutPlaneOptions): void
|
|
1918
2323
|
```
|
|
1919
2324
|
|
|
1920
|
-
|
|
2325
|
+
**`CutPlaneOptions`**
|
|
2326
|
+
- `offset?: number` — Optional offset along the plane normal (primarily for object-form overload).
|
|
2327
|
+
- `exclude?: CutPlaneExcludeInput` — Object names to keep uncut for this plane.
|
|
1921
2328
|
|
|
1922
|
-
|
|
2329
|
+
#### `mock()` — Register a mock (context) object for visualization and collision checking.
|
|
1923
2330
|
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
vignette?: SceneVignetteConfig;
|
|
1928
|
-
grain?: SceneGrainConfig;
|
|
1929
|
-
toneMappingExposure?: number;
|
|
1930
|
-
}
|
|
1931
|
-
```
|
|
2331
|
+
Mock objects appear in the viewport and spatial analysis when you run a file directly, but are excluded when the file is imported via `require()`. This lets you model the surrounding context — walls, bolts, mating parts — without polluting the module's exports.
|
|
2332
|
+
|
|
2333
|
+
The shape is returned unchanged, so you can reference it for alignment, dimensioning, and `verify` checks.
|
|
1932
2334
|
|
|
1933
|
-
|
|
2335
|
+
Mock objects participate in `forgecad run` collision detection and spatial analysis. Their names appear with a `(mock)` suffix in reports.
|
|
1934
2336
|
|
|
1935
|
-
|
|
2337
|
+
In the viewport, mock objects render at reduced opacity so they are visually distinct from real geometry.
|
|
1936
2338
|
|
|
1937
2339
|
```ts
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
radius?: number;
|
|
1942
|
-
}
|
|
1943
|
-
```
|
|
2340
|
+
// bracket.forge.js
|
|
2341
|
+
const wall = mock(box(100, 200, 10).translate(0, 0, -5), "wall");
|
|
2342
|
+
const bolt = mock(cylinder(3, 15).translate(10, 15, 0), "bolt");
|
|
1944
2343
|
|
|
1945
|
-
|
|
2344
|
+
const bracket = box(20, 30, 5);
|
|
2345
|
+
verify.notColliding("bracket vs wall", bracket, wall);
|
|
1946
2346
|
|
|
1947
|
-
|
|
2347
|
+
return bracket;
|
|
2348
|
+
// When imported: only bracket is exported
|
|
2349
|
+
// When run directly: bracket + wall + bolt all visible
|
|
2350
|
+
```
|
|
1948
2351
|
|
|
1949
2352
|
```ts
|
|
1950
|
-
|
|
1951
|
-
darkness?: number;
|
|
1952
|
-
offset?: number;
|
|
1953
|
-
}
|
|
2353
|
+
mock<T extends Shape>(shape: T, name?: string): T
|
|
1954
2354
|
```
|
|
1955
2355
|
|
|
1956
|
-
|
|
2356
|
+
#### `scene()` — Configure the scene environment for the current script execution.
|
|
1957
2357
|
|
|
1958
|
-
|
|
2358
|
+
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.
|
|
1959
2359
|
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
}
|
|
1964
|
-
```
|
|
2360
|
+
When `lights` is specified, **all** default lights are removed. You must include your own ambient light or the scene will be fully dark.
|
|
2361
|
+
|
|
2362
|
+
Setting `camera.position` overrides auto-framing — the viewport will no longer auto-fit the geometry on script reload.
|
|
1965
2363
|
|
|
1966
|
-
|
|
2364
|
+
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.
|
|
1967
2365
|
|
|
1968
|
-
|
|
2366
|
+
All numeric values accept `param()` expressions.
|
|
2367
|
+
|
|
2368
|
+
```js
|
|
2369
|
+
scene({
|
|
2370
|
+
background: { top: '#000814', bottom: '#001d3d' },
|
|
2371
|
+
camera: { position: [160, -120, 100], target: [0, 0, 50], fov: 52 },
|
|
2372
|
+
lights: [
|
|
2373
|
+
{ type: 'ambient', color: '#001233', intensity: 0.08 },
|
|
2374
|
+
{ type: 'point', position: [120, -80, 130], color: '#00f5d4', intensity: 4, distance: 400, decay: 1 },
|
|
2375
|
+
{ type: 'point', position: [-100, 60, 20], color: '#f72585', intensity: 3, distance: 350 },
|
|
2376
|
+
{ type: 'directional', position: [50, -30, 200], color: '#ffd60a', intensity: 1.2 },
|
|
2377
|
+
{ type: 'hemisphere', skyColor: '#003566', groundColor: '#000814', intensity: 0.2 },
|
|
2378
|
+
],
|
|
2379
|
+
fog: { color: '#000814', near: 100, far: 450 },
|
|
2380
|
+
postProcessing: {
|
|
2381
|
+
bloom: { intensity: param('bloom', 1.5, 0, 4), threshold: 0.5, radius: 0.7 },
|
|
2382
|
+
vignette: { darkness: 0.8, offset: 0.25 },
|
|
2383
|
+
grain: { intensity: 0.08 },
|
|
2384
|
+
toneMappingExposure: param('exposure', 1.5, 0.5, 4),
|
|
2385
|
+
},
|
|
2386
|
+
});
|
|
2387
|
+
```
|
|
1969
2388
|
|
|
1970
2389
|
```ts
|
|
1971
|
-
|
|
1972
|
-
/** Show a ground plane */
|
|
1973
|
-
visible?: boolean;
|
|
1974
|
-
/** Ground color */
|
|
1975
|
-
color?: string;
|
|
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
|
-
}
|
|
2390
|
+
scene(options: SceneOptions): void
|
|
1981
2391
|
```
|
|
1982
2392
|
|
|
1983
|
-
|
|
2393
|
+
**`SceneOptions`**
|
|
2394
|
+
|
|
2395
|
+
| Option | Type | Description |
|
|
2396
|
+
|--------|------|-------------|
|
|
2397
|
+
| `capture?` | `SceneCaptureConfig` | Default capture parameters for `forgecad capture` — CLI flags override these. |
|
|
2398
|
+
| `background?`, `camera?`, `lights?`, `environment?`, `fog?`, `postProcessing?`, `ground?` | | — |
|
|
2399
|
+
|
|
2400
|
+
`SceneBackgroundGradient`: `{ top: string, bottom: string }`
|
|
2401
|
+
|
|
2402
|
+
`SceneCameraConfig`: `{ fov?: number, type?: "perspective" | "orthographic" }`
|
|
2403
|
+
|
|
2404
|
+
**`SceneLightConfig`**
|
|
2405
|
+
|
|
2406
|
+
| Option | Type | Description |
|
|
2407
|
+
|--------|------|-------------|
|
|
2408
|
+
| `groundColor?` | `string` | Ground color for hemisphere lights |
|
|
2409
|
+
| `skyColor?` | `string` | Sky color alias for hemisphere lights (same as color) |
|
|
2410
|
+
| `angle?` | `number` | Spot light cone angle in radians |
|
|
2411
|
+
| `penumbra?` | `number` | Spot light penumbra (0–1) |
|
|
2412
|
+
| `decay?` | `number` | Point/spot light decay |
|
|
2413
|
+
| `distance?` | `number` | Point/spot light distance (0 = infinite) |
|
|
2414
|
+
| `castShadow?` | `boolean` | Whether this light casts shadows |
|
|
2415
|
+
| `type`, `color?`, `intensity?` | | — |
|
|
2416
|
+
|
|
2417
|
+
**`SceneEnvironmentConfig`**
|
|
2418
|
+
- `preset?: "studio" | "sunset" | "dawn" | "warehouse" | "forest" | "apartment" | "lobby" | "city" | "park" | "night" | "none"` — Built-in preset name or 'none' to disable
|
|
2419
|
+
- `intensity?: number` — Environment map intensity
|
|
2420
|
+
- `background?: boolean` — Use environment map as scene background
|
|
2421
|
+
|
|
2422
|
+
**`SceneFogConfig`**
|
|
2423
|
+
- `near?: number` — Linear fog near distance
|
|
2424
|
+
- `far?: number` — Linear fog far distance
|
|
2425
|
+
- `density?: number` — Exponential fog density (if set, uses FogExp2 instead of linear Fog)
|
|
2426
|
+
- Also: `color?: string`
|
|
2427
|
+
|
|
2428
|
+
`ScenePostProcessingConfig`: `{ bloom?: SceneBloomConfig, vignette?: SceneVignetteConfig, grain?: SceneGrainConfig, toneMappingExposure?: number }`
|
|
2429
|
+
|
|
2430
|
+
`SceneBloomConfig`: `{ intensity?: number, threshold?: number, radius?: number }`
|
|
2431
|
+
|
|
2432
|
+
`SceneVignetteConfig`: `{ darkness?: number, offset?: number }`
|
|
1984
2433
|
|
|
1985
|
-
|
|
2434
|
+
`SceneGrainConfig`: `{ intensity?: number }`
|
|
2435
|
+
|
|
2436
|
+
**`SceneGroundConfig`**
|
|
2437
|
+
|
|
2438
|
+
| Option | Type | Description |
|
|
2439
|
+
|--------|------|-------------|
|
|
2440
|
+
| `visible?` | `boolean` | Show a ground plane |
|
|
2441
|
+
| `color?` | `string` | Ground color |
|
|
2442
|
+
| `offset?` | `number` | Offset below the model's bounding box minimum Z. Default 0 (flush with model bottom). |
|
|
2443
|
+
| `receiveShadow?` | `boolean` | Receive shadows on the ground |
|
|
2444
|
+
|
|
2445
|
+
**`SceneCaptureConfig`**
|
|
2446
|
+
|
|
2447
|
+
| Option | Type | Description |
|
|
2448
|
+
|--------|------|-------------|
|
|
2449
|
+
| `framesPerTurn?` | `number` | Frames for one full orbit rotation (default: 72) |
|
|
2450
|
+
| `holdFrames?` | `number` | Frozen frames before motion starts (default: 6) |
|
|
2451
|
+
| `pitchDeg?` | `number` | Orbit pitch angle in degrees (default: auto from camera) |
|
|
2452
|
+
| `fps?` | `number` | Output frame rate (default: 24) |
|
|
2453
|
+
| `size?` | `number` | Output frame size in pixels (default: 960) |
|
|
2454
|
+
| `background?` | `string` | Canvas background color for capture (default: '#252526') |
|
|
2455
|
+
|
|
2456
|
+
#### `showLabels()` — Highlight all user-labeled faces on a shape for visual debugging.
|
|
2457
|
+
|
|
2458
|
+
Shows each user-authored label name in the viewport for visual debugging. Returns the shape unchanged for chaining: `return showLabels(myShape)`.
|
|
1986
2459
|
|
|
1987
2460
|
```ts
|
|
1988
|
-
|
|
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
|
-
}
|
|
2461
|
+
showLabels(shape: Shape): Shape
|
|
2002
2462
|
```
|
|
2003
2463
|
|
|
2004
|
-
|
|
2464
|
+
#### `viewConfig()` — Configure viewport helper visuals for the current script execution.
|
|
2465
|
+
|
|
2466
|
+
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.
|
|
2005
2467
|
|
|
2006
|
-
|
|
2468
|
+
This does **not** trigger a geometry recompute; it only affects the visual helpers drawn on top of the 3D scene.
|
|
2469
|
+
|
|
2470
|
+
```js
|
|
2471
|
+
viewConfig({
|
|
2472
|
+
jointOverlay: {
|
|
2473
|
+
axisColor: '#13dfff',
|
|
2474
|
+
arcColor: '#ff7a1a',
|
|
2475
|
+
axisLineRadiusScale: 0.03,
|
|
2476
|
+
arcLineRadiusScale: 0.022,
|
|
2477
|
+
},
|
|
2478
|
+
});
|
|
2479
|
+
```
|
|
2007
2480
|
|
|
2008
2481
|
```ts
|
|
2009
2482
|
viewConfig(options?: ViewConfigOptions): void
|
|
2010
2483
|
```
|
|
2011
2484
|
|
|
2012
|
-
|
|
2485
|
+
`ViewConfigOptions`: `{ jointOverlay?: JointOverlayViewConfigOptions }`
|
|
2013
2486
|
|
|
2014
|
-
|
|
2487
|
+
**`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`
|
|
2015
2488
|
|
|
2016
|
-
|
|
2017
|
-
interface ViewConfigOptions {
|
|
2018
|
-
jointOverlay?: JointOverlayViewConfigOptions;
|
|
2019
|
-
}
|
|
2020
|
-
```
|
|
2489
|
+
#### `spec()` — Create a named, reusable bundle of verification checks.
|
|
2021
2490
|
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
<details><summary><code>JointOverlayViewConfigOptions</code></summary>
|
|
2025
|
-
|
|
2026
|
-
```ts
|
|
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
|
-
```
|
|
2491
|
+
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.
|
|
2068
2492
|
|
|
2069
|
-
|
|
2493
|
+
Specs can be defined in separate `.forge.js` files and imported via `require()` to share them across models.
|
|
2070
2494
|
|
|
2071
|
-
|
|
2495
|
+
`spec.check()` returns a `SpecResult` — you can inspect it programmatically or ignore the return value and let the Checks panel show results.
|
|
2072
2496
|
|
|
2073
2497
|
```ts
|
|
2074
|
-
spec(
|
|
2075
|
-
|
|
2498
|
+
const printable = spec("Fits printer bed", (shape) => {
|
|
2499
|
+
verify.notEmpty("Has geometry", shape);
|
|
2500
|
+
const bb = shape.boundingBox();
|
|
2501
|
+
verify.lessThan("Width < 220mm", bb.max[0] - bb.min[0], 220);
|
|
2502
|
+
verify.lessThan("Depth < 220mm", bb.max[1] - bb.min[1], 220);
|
|
2503
|
+
verify.lessThan("Height < 250mm", bb.max[2] - bb.min[2], 250);
|
|
2504
|
+
});
|
|
2505
|
+
|
|
2506
|
+
// Reuse on multiple shapes
|
|
2507
|
+
printable.check(bracket);
|
|
2508
|
+
printable.check(standoff);
|
|
2076
2509
|
|
|
2077
|
-
|
|
2510
|
+
// Check relationships between parts
|
|
2511
|
+
const fitSpec = spec("Assembly fit", (partA, partB) => {
|
|
2512
|
+
verify.notColliding("No interference", partA, partB, 10);
|
|
2513
|
+
});
|
|
2514
|
+
fitSpec.check(bracket, standoff);
|
|
2515
|
+
```
|
|
2078
2516
|
|
|
2079
|
-
|
|
2517
|
+
**Spec-first workflow:** Write specs before building geometry. Checks go from red to green as you build — effectively TDD for CAD.
|
|
2080
2518
|
|
|
2081
2519
|
```ts
|
|
2082
|
-
|
|
2083
|
-
/** The display name of this spec */
|
|
2084
|
-
name: string;
|
|
2085
|
-
}
|
|
2520
|
+
spec(name: string, checkFn: (...args: any[]) => void): Spec
|
|
2086
2521
|
```
|
|
2087
2522
|
|
|
2088
|
-
|
|
2523
|
+
**`Spec`**
|
|
2524
|
+
- `name: string` — The display name of this spec
|
|
2089
2525
|
|
|
2090
2526
|
---
|
|
2091
2527
|
|
|
@@ -2093,20 +2529,72 @@ interface Spec {
|
|
|
2093
2529
|
|
|
2094
2530
|
Bring external geometry or other ForgeCAD modules into the current script.
|
|
2095
2531
|
|
|
2096
|
-
#### `group()`
|
|
2532
|
+
#### `group()` — Group multiple shapes/sketches for joint transforms without merging into a single mesh.
|
|
2533
|
+
|
|
2534
|
+
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.).
|
|
2535
|
+
|
|
2536
|
+
**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.
|
|
2537
|
+
|
|
2538
|
+
```js
|
|
2539
|
+
// BAD — every sub-part repeats the parent's global offset
|
|
2540
|
+
const unitX = 0, unitY = -18, unitZ = 70;
|
|
2541
|
+
const body = roundedBox(100, 20, 32, 4).translate(unitX, unitY, unitZ);
|
|
2542
|
+
const panel = box(98, 2, 18).translate(unitX, unitY - 12, unitZ + 4);
|
|
2543
|
+
const louver = box(88, 2, 6).translate(unitX, unitY - 14, unitZ - 11);
|
|
2544
|
+
```
|
|
2545
|
+
|
|
2546
|
+
// 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);
|
|
2097
2547
|
|
|
2098
2548
|
```ts
|
|
2099
2549
|
group(...items: GroupInput[]): ShapeGroup
|
|
2100
2550
|
```
|
|
2101
2551
|
|
|
2102
|
-
Group multiple shapes/sketches for joint transforms without merging into a single mesh. 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.).
|
|
2103
|
-
|
|
2104
2552
|
---
|
|
2105
2553
|
|
|
2106
2554
|
## C16: Part Library
|
|
2107
2555
|
|
|
2108
2556
|
Pre-built parametric parts accessible via `lib.*`.
|
|
2109
2557
|
|
|
2110
|
-
|
|
2558
|
+
#### `Wood.board()` — Create a wood board with metadata for manufacturing.
|
|
2559
|
+
|
|
2560
|
+
The board is a box(width, height, thickness) centered on XY, base at Z=0. Width along X, height along Y, thickness along Z (0 to thickness).
|
|
2561
|
+
|
|
2562
|
+
```ts
|
|
2563
|
+
Wood.readonly board: (width: number, height: number, thickness: number, opts?: WoodBoardOptions) => WoodBoard
|
|
2564
|
+
```
|
|
2565
|
+
|
|
2566
|
+
**`WoodBoardOptions`**
|
|
2567
|
+
|
|
2568
|
+
| Option | Type | Description |
|
|
2569
|
+
|--------|------|-------------|
|
|
2570
|
+
| `species?` | `string` | Wood species, e.g. "birch", "oak", "plywood". Default: "wood" |
|
|
2571
|
+
| `material?` | `string` | Material description for BOM, e.g. "birch plywood". Default: species value |
|
|
2572
|
+
| `grain?` | `string` | Grain direction: "long" (along width) or "cross" (along height). Default: "long" |
|
|
2573
|
+
| `color?` | `string` | Color hex string for visualization. Default: "#d2b48c" (tan/wood) |
|
|
2574
|
+
| `autoBom?` | `boolean` | If false, skip automatic BOM registration. Default: true |
|
|
2575
|
+
|
|
2576
|
+
#### `Wood.dado()` — Cut a dado (channel) across the face of a host board for a guest board to sit in.
|
|
2577
|
+
|
|
2578
|
+
Mutates `host.shape` by subtracting a rectangular channel.
|
|
2579
|
+
|
|
2580
|
+
```ts
|
|
2581
|
+
Wood.readonly dado: typeof dado
|
|
2582
|
+
```
|
|
2583
|
+
|
|
2584
|
+
#### `Wood.rabbet()` — Cut a rabbet (L-shaped step) along an edge of a board.
|
|
2585
|
+
|
|
2586
|
+
Mutates `board.shape` by subtracting a step from the specified edge.
|
|
2587
|
+
|
|
2588
|
+
```ts
|
|
2589
|
+
Wood.readonly rabbet: typeof rabbet
|
|
2590
|
+
```
|
|
2591
|
+
|
|
2592
|
+
#### `Wood.mortiseAndTenon()` — Cut a mortise in one board and shape a tenon on another.
|
|
2593
|
+
|
|
2594
|
+
Mutates both boards — subtracts the mortise pocket and removes shoulder material to form the tenon.
|
|
2595
|
+
|
|
2596
|
+
```ts
|
|
2597
|
+
Wood.readonly mortiseAndTenon: typeof mortiseAndTenon
|
|
2598
|
+
```
|
|
2111
2599
|
|
|
2112
2600
|
---
|