forgecad 0.7.0 → 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.
Files changed (158) hide show
  1. package/README.md +1 -1
  2. package/dist/assets/{AdminPage-DAu1C1ST.js → AdminPage-D4bocK4E.js} +1 -1
  3. package/dist/assets/{DocsPage-Gc_BCdqC.js → DocsPage-D3A_g8V3.js} +85 -45
  4. package/dist/assets/{EditorApp-DG1-oUSV.css → EditorApp-BWYUSpUN.css} +133 -51
  5. package/dist/assets/EditorApp-Cihhqcsq.js +11692 -0
  6. package/dist/assets/{EmbedViewer-CEO8XbV8.js → EmbedViewer-kWjKaC_t.js} +1 -1
  7. package/dist/assets/LandingPageProofDriven-Bg2IUc3l.css +856 -0
  8. package/dist/assets/LandingPageProofDriven-DXkKlyhI.js +601 -0
  9. package/dist/assets/{PricingPage-BSrxu6d7.js → PricingPage-BsU5vzEx.js} +1 -1
  10. package/dist/assets/{SettingsPage-FUCSIRq6.js → SettingsPage-PqvpAKIs.js} +1 -1
  11. package/dist/assets/{evalWorker-KoR0SNKq.js → evalWorker-C-hzNUMy.js} +2218 -286
  12. package/dist/assets/{index-wTEK39at.js → index-Pz321YAt.js} +7416 -1481
  13. package/dist/assets/{index-CyVd1D4D.css → index-ay13WNfa.css} +501 -2
  14. package/dist/assets/{manifold-B1sGWdYk.js → manifold-BcbjWLIo.js} +3 -3
  15. package/dist/assets/{manifold-D7o0N50J.js → manifold-DBckbFgx.js} +1 -1
  16. package/dist/assets/{manifold-G5sBaXzi.js → manifold-O2AAGXyj.js} +1 -1
  17. package/dist/assets/{reportWorker-DYcRHhv9.js → reportWorker-Dxr-5A7w.js} +2003 -259
  18. package/dist/docs/index.html +2 -2
  19. package/dist/docs-raw/CLI.md +488 -0
  20. package/dist/docs-raw/generated/assembly.md +19 -11
  21. package/dist/docs-raw/generated/concepts.md +1023 -360
  22. package/dist/docs-raw/generated/core.md +1165 -264
  23. package/dist/docs-raw/generated/curves.md +168 -1
  24. package/dist/docs-raw/generated/lib.md +10 -5
  25. package/dist/docs-raw/generated/output.md +1 -1
  26. package/dist/docs-raw/generated/sdf.md +208 -0
  27. package/dist/docs-raw/generated/sketch.md +1281 -329
  28. package/dist/docs-raw/generated/viewport.md +29 -2
  29. package/dist/index.html +2 -2
  30. package/dist/landing/proof-ams-adapter.png +0 -0
  31. package/dist/landing/proof-bolt-and-nut.png +0 -0
  32. package/dist/landing/proof-fillet-enclosure.png +0 -0
  33. package/dist/landing/proof-glasses.png +0 -0
  34. package/dist/landing/proof-gyroid.png +0 -0
  35. package/dist/sitemap.xml +6 -6
  36. package/dist-cli/forgecad.js +3148 -555
  37. package/dist-cli/forgecad.js.map +1 -1
  38. package/dist-cli/{solver-FV7TJZGI.js → solver-46FFSK2U.js} +1 -3
  39. package/dist-cli/{solver-FV7TJZGI.js.map → solver-46FFSK2U.js.map} +1 -1
  40. package/dist-skill/CONTEXT.md +3700 -1153
  41. package/dist-skill/SKILL-dev.md +15 -17
  42. package/dist-skill/SKILL.md +14 -9
  43. package/dist-skill/docs/API/core/concepts.md +28 -1
  44. package/dist-skill/docs/CLI.md +488 -0
  45. package/dist-skill/docs/generated/assembly.md +19 -11
  46. package/dist-skill/docs/generated/core.md +1165 -264
  47. package/dist-skill/docs/generated/curves.md +168 -1
  48. package/dist-skill/docs/generated/lib.md +10 -5
  49. package/dist-skill/docs/generated/output.md +1 -1
  50. package/dist-skill/docs/generated/sdf.md +208 -0
  51. package/dist-skill/docs/generated/sketch.md +1281 -329
  52. package/dist-skill/docs/generated/viewport.md +29 -2
  53. package/dist-skill/docs/guides/joint-design.md +78 -0
  54. package/dist-skill/docs-dev/API/core/concepts.md +28 -1
  55. package/dist-skill/docs-dev/CLI.md +488 -0
  56. package/dist-skill/docs-dev/coding.md +1 -1
  57. package/dist-skill/docs-dev/component-model.md +164 -0
  58. package/dist-skill/docs-dev/generated/assembly.md +19 -11
  59. package/dist-skill/docs-dev/generated/core.md +1165 -264
  60. package/dist-skill/docs-dev/generated/curves.md +168 -1
  61. package/dist-skill/docs-dev/generated/lib.md +10 -5
  62. package/dist-skill/docs-dev/generated/output.md +1 -1
  63. package/dist-skill/docs-dev/generated/sdf.md +208 -0
  64. package/dist-skill/docs-dev/generated/sketch.md +1281 -329
  65. package/dist-skill/docs-dev/generated/viewport.md +29 -2
  66. package/dist-skill/docs-dev/guides/joint-design.md +78 -0
  67. package/examples/api/attachTo-basics.forge.js +3 -3
  68. package/examples/api/bill-of-materials.forge.js +9 -9
  69. package/examples/api/bolt-pattern.forge.js +5 -5
  70. package/examples/api/boolean-operations.forge.js +2 -2
  71. package/examples/api/bounding-box-visualizer.forge.js +1 -1
  72. package/examples/api/clone-duplicate.forge.js +1 -1
  73. package/examples/api/connector-assembly.forge.js +4 -2
  74. package/examples/api/connector-basics.forge.js +5 -5
  75. package/examples/api/constrained-sketch-mechanical.forge.js +4 -4
  76. package/examples/api/elbow-test.forge.js +3 -3
  77. package/examples/api/extrude-options.forge.js +4 -4
  78. package/examples/api/fillet-showcase.forge.js +1 -1
  79. package/examples/api/gears-tier1.forge.js +5 -5
  80. package/examples/api/group-test.forge.js +2 -2
  81. package/examples/api/mesh-import-slats.forge.js +3 -3
  82. package/examples/api/patterns.forge.js +3 -3
  83. package/examples/api/pointAlong-orientation.forge.js +2 -2
  84. package/examples/api/profile-2020-b-slot6.forge.js +4 -4
  85. package/examples/api/sketch-rounding-strategies.forge.js +1 -1
  86. package/examples/api/smooth-curve-connections.forge.js +1 -1
  87. package/examples/api/transition-curves.forge.js +3 -3
  88. package/examples/constraints/01-fully-constrained-rect.forge.js +2 -2
  89. package/examples/constraints/02-underconstrained-triangle.forge.js +1 -1
  90. package/examples/constraints/03-redundant-constraints.forge.js +2 -2
  91. package/examples/constraints/05-parallel-with-linedistance.forge.js +2 -2
  92. package/examples/constraints/06-complex-spectrogram.forge.js +1 -1
  93. package/examples/constraints/07-perpendicular-chain.forge.js +4 -4
  94. package/examples/constraints/08-symmetric-bracket.forge.js +4 -4
  95. package/examples/constraints/09-stress-spiral.forge.js +1 -1
  96. package/examples/constraints/10-stress-honeycomb.forge.js +1 -1
  97. package/examples/constraints/11-surface-grid.forge.js +2 -2
  98. package/examples/constraints/12-surface-nested.forge.js +4 -4
  99. package/examples/constraints/13-surface-complex.forge.js +1 -1
  100. package/examples/exact-arc-housing.forge.js +12 -0
  101. package/examples/furniture/adjustable-table.forge.js +13 -13
  102. package/examples/furniture/bathroom.forge.js +15 -15
  103. package/examples/furniture/chair.forge.js +12 -12
  104. package/examples/furniture/picture-frame.forge.js +6 -6
  105. package/examples/furniture/shoe-rack-doors.forge.js +8 -8
  106. package/examples/furniture/shoe-rack.forge.js +7 -7
  107. package/examples/furniture/table-lamp.forge.js +8 -8
  108. package/examples/gcode/lissajous-vase.forge.js +4 -4
  109. package/examples/gcode/math-surface.forge.js +3 -3
  110. package/examples/gcode/parametric-vase.forge.js +4 -4
  111. package/examples/gcode/spiral-tower.forge.js +4 -4
  112. package/examples/generative/crystal-growth.forge.js +7 -7
  113. package/examples/generative/frost-spires.forge.js +6 -6
  114. package/examples/generative/golden-spiral-tower.forge.js +8 -8
  115. package/examples/generative/molten-forge.forge.js +6 -6
  116. package/examples/generative/neon-coral.forge.js +7 -7
  117. package/examples/mechanical/3d-printer.forge.js +9 -9
  118. package/examples/mechanical/5-finger-robot-hand.forge.js +4 -4
  119. package/examples/mechanical/airplane-propeller.forge.js +7 -7
  120. package/examples/mechanical/bolt-and-nut.forge.js +10 -10
  121. package/examples/mechanical/door-with-hinges.forge.js +7 -7
  122. package/examples/mechanical/fillet-enclosure.forge.js +14 -10
  123. package/examples/mechanical/headphone-hanger-v2.forge.js +9 -9
  124. package/examples/mechanical/robot_hand.forge.js +10 -10
  125. package/examples/mechanical/robot_hand_2.forge.js +17 -17
  126. package/examples/nurbs-surface.forge.js +8 -0
  127. package/examples/nurbs-tube.forge.js +7 -0
  128. package/examples/products/bottle.forge.js +7 -7
  129. package/examples/products/chess-set.forge.js +6 -6
  130. package/examples/products/classical-piano.forge.js +9 -9
  131. package/examples/products/clock.forge.js +21 -21
  132. package/examples/products/cup.forge.js +5 -5
  133. package/examples/products/iphone.forge.js +12 -12
  134. package/examples/products/laptop.forge.js +9 -9
  135. package/examples/products/laser-cut-box.forge.js +6 -6
  136. package/examples/products/laser-cut-tray.forge.js +6 -6
  137. package/examples/products/liquid-soap-dispenser.forge.js +5 -5
  138. package/examples/products/origami-fish.forge.js +6 -6
  139. package/examples/products/spiderman-cake.forge.js +2 -2
  140. package/examples/shelf/container.forge.js +5 -5
  141. package/examples/shelf/shelf-unit.forge.js +6 -6
  142. package/examples/toolbox/bolted-joint.forge.js +5 -5
  143. package/package.json +3 -1
  144. package/dist/assets/EditorApp-D9bJvtf7.js +0 -11338
  145. package/dist/assets/LandingPage-CdCuEOdC.js +0 -451
  146. package/dist-cli/chunk-PZ5AY32C.js +0 -10
  147. package/dist-cli/chunk-PZ5AY32C.js.map +0 -1
  148. package/dist-skill/docs/CLI/export.md +0 -91
  149. package/dist-skill/docs/CLI/projects.md +0 -107
  150. package/dist-skill/docs/CLI/studio_publishing.md +0 -52
  151. package/dist-skill/docs/CLI/validation.md +0 -66
  152. package/dist-skill/docs-dev/API/core/sdf-advanced.md +0 -92
  153. package/dist-skill/docs-dev/API/core/sdf-primitives.md +0 -58
  154. package/dist-skill/docs-dev/API/core/sdf-workflow.md +0 -42
  155. package/dist-skill/docs-dev/CLI/export.md +0 -91
  156. package/dist-skill/docs-dev/CLI/projects.md +0 -107
  157. package/dist-skill/docs-dev/CLI/studio_publishing.md +0 -52
  158. package/dist-skill/docs-dev/CLI/validation.md +0 -66
@@ -5,33 +5,30 @@ skill-order: 100
5
5
 
6
6
  # Core API
7
7
 
8
- > **Auto-generated** from `src/forge/forge-public-api.ts`. Do not edit by hand — run `npm run gen:docs` to regenerate.
9
-
10
8
  3D primitives, boolean operations, transforms, patterns, imports, and parameters.
11
9
 
12
- ## Functions
13
-
14
- ### 3D Primitives
15
-
16
- #### `box()`Create a rectangular box. Centered on XY, base at Z=0.
17
-
18
- For named faces, build from a labeled sketch: `rect(x, y).labelEdges('s', 'e', 'n', 'w').extrude(z, { labels: { start: 'bottom', end: 'top' } })`.
19
-
20
- `box$1(x: number, y: number, z: number): Shape`
21
-
22
- #### `cylinder()`Create a cylinder or cone with named faces and edges. Centered on XY, base at Z=0.
23
-
24
- 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.
10
+ ## Contents
11
+
12
+ - [Boolean Operations](#boolean-operations) — `union`, `difference`, `intersection`
13
+ - [Edge Features](#edge-features) — `fillet`, `chamfer`, `draft`, `offsetSolid`
14
+ - [Patterns & Layout](#patterns-layout) — `selectEdges`, `selectEdge`, `coalesceEdges`, `filletCorners`, `circularLayout`, `polygonVertices`, `linearPattern`, `circularPattern`, `linearPattern2d`, `circularPattern2d`, `mirrorCopy`
15
+ - [Imports & Composition](#imports-composition) — `require`, `importSvgSketch`, `importMesh`, `importStep`
16
+ - [Parameters](#parameters) `Param.number`, `Param.string`, `Param.bool`, `Param.choice`, `Param.list`
17
+ - [Grouping & Local Coordinates](#grouping-local-coordinates) — `group`
18
+ - [Section & Projection](#section-projection) `intersectWithPlane`, `faceProfile`, `projectToPlane`
19
+ - [Transforms](#transforms) — `composeChain`
20
+ - [Verification](#verification) — `spec`
21
+ - [Shape](#shape) — Appearance, Face Topology, Edge Topology, Transforms, Booleans & Cutting, Features, Placement, Connectors, References, Measurement
22
+ - [Transform](#transform)
23
+ - [ShapeGroup](#shapegroup) — Children, Transforms, Placement, Connectors, References
24
+ - [SurfacePattern](#surfacepattern)
25
+ - [ANCHOR3D_NAMES](#anchor3d-names)
26
+ - [verify](#verify)
27
+ - [Constraint](#constraint)
28
+ - [Points](#points)
29
+ - [connector](#connector)
25
30
 
26
- `cylinder$1(height: number, radius: number, radiusTop?: number, segments?: number): Shape`
27
-
28
- #### `sphere()` — Create a sphere centered at the origin. Use segments for lower-poly approximations.
29
-
30
- `sphere$1(radius: number, segments?: number): Shape`
31
-
32
- #### `torus()` — Create a torus (donut shape) lying in the XY plane. Centered on all axes (origin is the ring center).
33
-
34
- `torus$1(majorRadius: number, minorRadius: number, segments?: number): Shape`
31
+ ## Functions
35
32
 
36
33
  ### Boolean Operations
37
34
 
@@ -39,33 +36,39 @@ When radiusTop differs from radius, creates a tapered cone. Use the segments par
39
36
 
40
37
  Accepts individual shapes, or an array of shapes. The first operand's color is preserved in the result.
41
38
 
42
- `union(...inputs: ShapeOperandInput[]): Shape`
39
+ ```ts
40
+ union(...inputs: ShapeOperandInput[]): Shape
41
+ ```
43
42
 
44
43
  #### `difference()` — Subtract shapes from a base shape (subtractive boolean).
45
44
 
46
45
  The first shape is the base; all subsequent shapes are subtracted from it. Accepts individual shapes, or an array of shapes.
47
46
 
48
- `difference(...inputs: ShapeOperandInput[]): Shape`
47
+ ```ts
48
+ difference(...inputs: ShapeOperandInput[]): Shape
49
+ ```
49
50
 
50
51
  #### `intersection()` — Keep only the overlapping volume of the input shapes (intersection boolean).
51
52
 
52
53
  Requires at least two shapes. Accepts individual shapes, or an array.
53
54
 
54
- `intersection(...inputs: ShapeOperandInput[]): Shape`
55
+ ```ts
56
+ intersection(...inputs: ShapeOperandInput[]): Shape
57
+ ```
55
58
 
56
59
  ### Edge Features
57
60
 
58
61
  #### `fillet()` — Apply fillets (rounded edges) to one or more edges of a shape.
59
62
 
60
- **Details**
61
-
62
63
  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.
63
64
 
64
- The `edges` parameter is flexible: - Omit to fillet **all** sharp edges - Pass an `EdgeQuery` for an inline filter (most common) - Pass an `EdgeSegment` or `EdgeSegment[]` from `selectEdges()` for pre-selected edges
65
+ The `edges` parameter is flexible:
65
66
 
66
- Throws if no edges match the selection, or if `radius` is not a positive finite number.
67
+ - Omit to fillet **all** sharp edges
68
+ - Pass an `EdgeQuery` for an inline filter (most common)
69
+ - Pass an `EdgeSegment` or `EdgeSegment[]` from `selectEdges()` for pre-selected edges
67
70
 
68
- **Example**
71
+ Throws if no edges match the selection, or if `radius` is not a positive finite number.
69
72
 
70
73
  ```ts
71
74
  // Fillet all edges
@@ -79,18 +82,16 @@ const edges = selectEdges(myShape, { parallel: [0, 0, 1] })
79
82
  fillet(myShape, 3, edges)
80
83
  ```
81
84
 
82
- `fillet(shape: Shape, radius: number, edges?: EdgeSelector, segments?: number): Shape`
85
+ ```ts
86
+ fillet(shape: Shape, radius: number, edges?: EdgeSelector, segments?: number): Shape
87
+ ```
83
88
 
84
89
  #### `chamfer()` — Apply chamfers (beveled edges) to one or more edges of a shape.
85
90
 
86
- **Details**
87
-
88
91
  Produces a 45° bevel at the specified `size` (distance from edge). Works on both straight and curved edges. Supports OCCT and Manifold backends.
89
92
 
90
93
  The `edges` parameter accepts the same options as `fillet()`: inline `EdgeQuery`, pre-selected `EdgeSegment`/`EdgeSegment[]`, or `undefined` (all sharp edges).
91
94
 
92
- **Example**
93
-
94
95
  ```ts
95
96
  // Chamfer all edges
96
97
  chamfer(myShape, 1)
@@ -99,18 +100,16 @@ chamfer(myShape, 1)
99
100
  chamfer(myShape, 2, { parallel: [0, 0, 1] })
100
101
  ```
101
102
 
102
- `chamfer(shape: Shape, size: number, edges?: EdgeSelector): Shape`
103
+ ```ts
104
+ chamfer(shape: Shape, size: number, edges?: EdgeSelector): Shape
105
+ ```
103
106
 
104
107
  #### `draft()` — Apply a draft angle (taper) to vertical faces for mold extraction.
105
108
 
106
- **Details**
107
-
108
109
  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°.
109
110
 
110
111
  Requires the OCCT backend. Throws on Manifold.
111
112
 
112
- **Example**
113
-
114
113
  ```ts
115
114
  // Add 3° draft to a box for injection molding
116
115
  draft(myBox, 3)
@@ -119,18 +118,16 @@ draft(myBox, 3)
119
118
  draft(myShape, 2, [0, 0, 1], 10)
120
119
  ```
121
120
 
122
- `draft(shape: Shape, angleDeg: number, pullDirection?: [ number, number, number ], neutralPlaneOffset?: number): Shape`
121
+ ```ts
122
+ draft(shape: Shape, angleDeg: number, pullDirection?: [ number, number, number ], neutralPlaneOffset?: number): Shape
123
+ ```
123
124
 
124
125
  #### `offsetSolid()` — Uniformly offset all surfaces of a solid inward or outward.
125
126
 
126
- **Details**
127
-
128
127
  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.
129
128
 
130
129
  Requires the OCCT backend. Throws on Manifold.
131
130
 
132
- **Example**
133
-
134
131
  ```ts
135
132
  // Grow a box outward by 1mm on all sides
136
133
  offsetSolid(myBox, 1)
@@ -139,20 +136,18 @@ offsetSolid(myBox, 1)
139
136
  offsetSolid(myShape, -0.5)
140
137
  ```
141
138
 
142
- `offsetSolid(shape: Shape, thickness: number): Shape`
139
+ ```ts
140
+ offsetSolid(shape: Shape, thickness: number): Shape
141
+ ```
143
142
 
144
143
  ### Patterns & Layout
145
144
 
146
145
  #### `selectEdges()` — Select all edges from a shape that match the given query.
147
146
 
148
- **Details**
149
-
150
147
  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.
151
148
 
152
149
  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.
153
150
 
154
- **Example**
155
-
156
151
  ```ts
157
152
  // Fillet all top edges of a box
158
153
  const topEdges = selectEdges(part, { atZ: 20, perpendicular: [0, 0, 1] });
@@ -162,7 +157,9 @@ for (const edge of coalesceEdges(topEdges)) {
162
157
  }
163
158
  ```
164
159
 
165
- `selectEdges(shape: Shape, query?: EdgeQuery): EdgeSegment[]`
160
+ ```ts
161
+ selectEdges(shape: Shape, query?: EdgeQuery): EdgeSegment[]
162
+ ```
166
163
 
167
164
  **`EdgeQuery`**
168
165
 
@@ -199,30 +196,24 @@ for (const edge of coalesceEdges(topEdges)) {
199
196
 
200
197
  #### `selectEdge()` — Select the single best-matching edge from a shape.
201
198
 
202
- **Details**
203
-
204
199
  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.
205
200
 
206
- **Example**
207
-
208
201
  ```ts
209
202
  // Chamfer one specific edge near a known point
210
203
  const bottomEdge = selectEdge(part, { near: [25, 0, 0], atZ: 0 });
211
204
  result = chamfer(result, 1.5, bottomEdge);
212
205
  ```
213
206
 
214
- `selectEdge(shape: Shape, query?: EdgeQuery): EdgeSegment`
207
+ ```ts
208
+ selectEdge(shape: Shape, query?: EdgeQuery): EdgeSegment
209
+ ```
215
210
 
216
211
  #### `coalesceEdges()` — Merge collinear edge segments into longer logical edges.
217
212
 
218
- **Details**
219
-
220
213
  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.
221
214
 
222
215
  The `tolerance` controls the maximum perpendicular distance from collinearity before two segments are considered non-collinear. Default: `0.01`.
223
216
 
224
- **Example**
225
-
226
217
  ```ts
227
218
  const topEdges = selectEdges(part, { atZ: 20 });
228
219
  for (const edge of coalesceEdges(topEdges)) {
@@ -230,19 +221,21 @@ for (const edge of coalesceEdges(topEdges)) {
230
221
  }
231
222
  ```
232
223
 
233
- `coalesceEdges(segments: EdgeSegment[], tolerance?: number): EdgeSegment[]`
224
+ ```ts
225
+ coalesceEdges(segments: EdgeSegment[], tolerance?: number): EdgeSegment[]
226
+ ```
234
227
 
235
228
  #### `filletCorners()` — Create a polygon from points with specific corners rounded to arc fillets.
236
229
 
237
- **Details**
238
-
239
230
  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.
240
231
 
241
- Constraints: - Collinear corners cannot be filleted (throws an error) - Two neighboring fillets whose tangent lengths overlap the same edge will throw - Radius must be positive and small enough to fit within the adjacent edge lengths
232
+ Constraints:
242
233
 
243
- 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.
234
+ - Collinear corners cannot be filleted (throws an error)
235
+ - Two neighboring fillets whose tangent lengths overlap the same edge will throw
236
+ - Radius must be positive and small enough to fit within the adjacent edge lengths
244
237
 
245
- **Example**
238
+ 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.
246
239
 
247
240
  ```ts
248
241
  const roof = filletCorners(roofPoints, [
@@ -252,7 +245,9 @@ const roof = filletCorners(roofPoints, [
252
245
  ]);
253
246
  ```
254
247
 
255
- `filletCorners(points: PointInput[], corners: FilletCornerSpec[]): Sketch`
248
+ ```ts
249
+ filletCorners(points: PointInput[], corners: FilletCornerSpec[]): Sketch
250
+ ```
256
251
 
257
252
  `FilletCornerSpec`: `{ index: number, radius: number, segments?: number }`
258
253
 
@@ -273,7 +268,9 @@ for (const {x, y} of circularLayout(12, r)) {
273
268
  }
274
269
  ```
275
270
 
276
- `circularLayout(count: number, radius: number, options?: CircularLayoutOptions): LayoutPoint[]`
271
+ ```ts
272
+ circularLayout(count: number, radius: number, options?: CircularLayoutOptions): LayoutPoint[]
273
+ ```
277
274
 
278
275
  **`CircularLayoutOptions`**
279
276
  - `startDeg?: number` — Angle of the first element in degrees (default: 0 = +X axis).
@@ -284,7 +281,7 @@ for (const {x, y} of circularLayout(12, r)) {
284
281
 
285
282
  #### `polygonVertices()` — Compute the vertex positions of a regular polygon.
286
283
 
287
- Default orientation places the first vertex at the top (90 degrees), matching the convention used by `ngon()`.
284
+ Default orientation places the first vertex at the top (90 degrees), matching the convention used by [`ngon()`](/docs/sketch#ngon).
288
285
 
289
286
  Eliminates manual Math.sqrt(3) for triangles, pentagon vertex math, etc:
290
287
 
@@ -298,7 +295,9 @@ const v3 = [center.x + r, center.y];
298
295
  const [v1, v2, v3] = polygonVertices(3, r);
299
296
  ```
300
297
 
301
- `polygonVertices(sides: number, radius: number, options?: PolygonVerticesOptions): LayoutPoint[]`
298
+ ```ts
299
+ polygonVertices(sides: number, radius: number, options?: PolygonVerticesOptions): LayoutPoint[]
300
+ ```
302
301
 
303
302
  **`PolygonVerticesOptions`**
304
303
  - `startDeg?: number` — Angle of the first vertex in degrees (default: 90 = top).
@@ -307,28 +306,25 @@ const [v1, v2, v3] = polygonVertices(3, r);
307
306
 
308
307
  #### `linearPattern()` — Repeat a shape in a linear pattern along a direction vector and union the copies.
309
308
 
310
- **Details**
311
-
312
309
  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.
313
310
 
314
- **Example**
315
-
316
311
  ```ts
317
312
  // 5 cylinders, 20mm apart along X
318
313
  linearPattern(cylinder(10, 3), 5, 20, 0)
319
314
  ```
320
315
 
321
- `linearPattern(shape: Shape, count: number, dx: number, dy: number, dz?: number): Shape`
316
+ ```ts
317
+ linearPattern(shape: Shape, count: number, dx: number, dy: number, dz?: number): Shape
318
+ ```
322
319
 
323
320
  #### `circularPattern()` — Repeat a shape in a circular pattern around an axis and union the copies.
324
321
 
325
- **Details**
326
-
327
322
  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.
328
323
 
329
- Two calling conventions: - **Simple** (Z axis): `circularPattern(shape, 6)` or `circularPattern(shape, 6, centerX, centerY)` - **Advanced** (arbitrary axis): `circularPattern(shape, 6, { axis, origin })`
324
+ Two calling conventions:
330
325
 
331
- **Example**
326
+ - **Simple** (Z axis): `circularPattern(shape, 6)` or `circularPattern(shape, 6, centerX, centerY)`
327
+ - **Advanced** (arbitrary axis): `circularPattern(shape, 6, { axis, origin })`
332
328
 
333
329
  ```ts
334
330
  // 8 holes evenly spaced around origin
@@ -338,7 +334,9 @@ circularPattern(cylinder(12, 4).translate(30, 0, -1), 8)
338
334
  circularPattern(myFeature, 4, { axis: [1, 0, 0], origin: [0, 0, 50] })
339
335
  ```
340
336
 
341
- `circularPattern(shape: Shape, count: number, centerXOrOpts?: number | CircularPatternOptions, centerY?: number): Shape`
337
+ ```ts
338
+ circularPattern(shape: Shape, count: number, centerXOrOpts?: number | CircularPatternOptions, centerY?: number): Shape
339
+ ```
342
340
 
343
341
  **`CircularPatternOptions`**
344
342
  - `centerX?: number` — Center X of the rotation (default: 0). Used when axis is Z (legacy mode).
@@ -346,36 +344,71 @@ circularPattern(myFeature, 4, { axis: [1, 0, 0], origin: [0, 0, 50] })
346
344
 
347
345
  #### `linearPattern2d()` — Repeat a 2D sketch in a linear pattern and union the copies.
348
346
 
349
- `linearPattern2d(sketch: Sketch, count: number, dx: number, dy?: number): Sketch`
347
+ ```ts
348
+ linearPattern2d(sketch: Sketch, count: number, dx: number, dy?: number): Sketch
349
+ ```
350
350
 
351
351
  #### `circularPattern2d()` — Repeat a 2D sketch in a circular pattern around a center point and union the copies.
352
352
 
353
- `circularPattern2d(sketch: Sketch, count: number, centerXOrOpts?: number | { centerX?: number; centerY?: number; startDeg?: number; }, centerY?: number): Sketch`
353
+ ```ts
354
+ circularPattern2d(sketch: Sketch, count: number, centerXOrOpts?: number | { centerX?: number; centerY?: number; startDeg?: number; }, centerY?: number): Sketch
355
+ ```
354
356
 
355
357
  #### `mirrorCopy()` — Mirror a shape across a plane and union the mirror with the original.
356
358
 
357
- **Details**
358
-
359
359
  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.
360
360
 
361
- **Example**
362
-
363
361
  ```ts
364
362
  // Mirror across the YZ plane (X=0)
365
363
  mirrorCopy(box(50, 30, 10), [1, 0, 0])
366
364
  ```
367
365
 
368
- `mirrorCopy(shape: Shape, normal: [ number, number, number ]): Shape`
366
+ ```ts
367
+ mirrorCopy(shape: Shape, normal: [ number, number, number ]): Shape
368
+ ```
369
369
 
370
370
  ### Imports & Composition
371
371
 
372
372
  #### `require()` — Import a module with optional ForgeCAD parameter overrides. Returns the module's exports.
373
373
 
374
- `require$1(path: string, paramOverrides?: Record<string, number>): any`
374
+ When importing a `.forge.js` file, the return value is what the script returns. If the script returns a metadata object (e.g. `{ shape: myShape, bolts: {...} }`), the caller receives the full object — renderable values and metadata together.
375
+
376
+ **Parameter scoping:** Parameters declared in required files are automatically namespaced with a `"filename#N / "` prefix (e.g. `"bracket.forge.js#1 / Width"`). This prevents collisions when multiple files declare same-named params. Each file's params appear as separate sliders.
377
+
378
+ **Parameter overrides:** When passing overrides, use the bare param name (not the scoped name). Overrides are type-checked — unrecognized keys throw an error with typo suggestions.
379
+
380
+ **Multi-file assembly pattern** — pass cross-cutting design values from the assembly to parts:
381
+
382
+ ```js
383
+ // assembly.forge.js — owns cross-cutting params, passes to parts
384
+ const wall = param("Wall", 3);
385
+ const baseH = param("Base Height", 20);
386
+
387
+ const mount = require('./motor-mount.forge.js', { Wall: wall });
388
+ const base = require('./base-body.forge.js', { Wall: wall, Height: baseH });
389
+ ```
390
+
391
+ **Metadata pattern** — parts publish interface data alongside geometry:
392
+
393
+ ```js
394
+ // motor-mount.forge.js
395
+ return { shape: mount, bolts: { dia: 5.3, pos: holePositions } };
396
+
397
+ // base-body.forge.js
398
+ const mount = require('./motor-mount.forge.js');
399
+ mount.bolts.pos // access the metadata
400
+ mount.shape // access the geometry
401
+ ```
402
+
403
+ ```ts
404
+ require(path: string, paramOverrides?: Record<string, number | string>): any
405
+ ```
375
406
 
376
407
  #### `importSvgSketch()` — Parse an SVG file and return it as a Sketch with options for region filtering, scaling, and simplification.
377
408
 
378
- `importSvgSketch(fileName: string, options?: SvgImportOptions): Sketch`
409
+ ```ts
410
+ importSvgSketch(fileName: string, options?: SvgImportOptions): Sketch
411
+ ```
379
412
 
380
413
  **`SvgImportOptions`**
381
414
 
@@ -397,26 +430,34 @@ mirrorCopy(box(50, 30, 10), [1, 0, 0])
397
430
 
398
431
  #### `importMesh()` — Import an external mesh file (STL, OBJ, 3MF) as a Shape.
399
432
 
400
- `importMesh(fileName: string, options?: { scale?: number; center?: boolean; }): Shape`
433
+ ```ts
434
+ importMesh(fileName: string, options?: { scale?: number; center?: boolean; }): Shape
435
+ ```
436
+
437
+ #### `importStep()` — Import a STEP file (.step, .stp) as an exact OCCT-backed Shape. Preserves NURBS curves, B-spline surfaces, and exact topology. Requires `setActiveBackend('occt')`.
438
+
439
+ ```ts
440
+ importStep(fileName: string): Shape
441
+ ```
401
442
 
402
443
  ### Parameters
403
444
 
404
- #### `param()` — Declare a numeric parameter that renders as a slider in the UI.
445
+ #### `Param.number()` — Declare a numeric parameter that renders as a slider in the UI.
405
446
 
406
- **Details**
447
+ 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.
407
448
 
408
- Each `param()` call registers a slider control. When the user moves the slider the entire script re-executes with the new value. Parameter values are also overridable from `require()` imports or the CLI `--param` flag — the `name` string is the key used in both cases.
449
+ Default range rules when options are omitted:
409
450
 
410
- Default range rules when options are omitted: - `min` defaults to `0` - `max` defaults to `defaultValue * 4` - `step` is auto-calculated: `1` for integer params, `0.1` for ranges ≤ 100, `1` for larger ranges
451
+ - `min` defaults to `0`
452
+ - `max` defaults to `defaultValue * 4`
453
+ - `step` is auto-calculated: `1` for integer params, `0.1` for ranges ≤ 100, `1` for larger ranges
411
454
 
412
455
  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`).
413
456
 
414
- **Example**
415
-
416
457
  ```ts
417
- const width = param("Width", 50);
418
- const angle = param("Angle", 45, { min: 0, max: 180, unit: "°" });
419
- const sides = param("Sides", 6, { min: 3, max: 12, integer: true });
458
+ const width = Param.number("Width", 50);
459
+ const angle = Param.number("Angle", 45, { min: 0, max: 180, unit: "°" });
460
+ const sides = Param.number("Sides", 6, { min: 3, max: 12, integer: true });
420
461
  ```
421
462
 
422
463
  **Parameter overrides** — key must match `name` exactly:
@@ -429,18 +470,39 @@ const bracket = require("./bracket.forge.js", { Width: 80 });
429
470
  // forgecad run model.forge.js --param "Wall Thickness=3"
430
471
  ```
431
472
 
432
- `param(name: string, defaultValue: number, opts?: { min?: number; max?: number; step?: number; unit?: string; integer?: boolean; reverse?: boolean; }): number`
473
+ Also available as the shorthand alias `param()`.
433
474
 
434
- #### `boolParam()` — Declare a boolean parameter that renders as a checkbox in the UI.
475
+ ```ts
476
+ Param.number(name: string, defaultValue: number, opts?: { min?: number; max?: number; step?: number; unit?: string; integer?: boolean; reverse?: boolean; }): number
477
+ ```
435
478
 
436
- **Details**
479
+ #### `Param.string()` — Declare a string parameter that renders as a text input in the UI.
437
480
 
438
- 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.
481
+ String parameters let users type free-form text labels, names, inscriptions, file paths, etc. The `name` string is the override key.
482
+
483
+ ```ts
484
+ const label = Param.string("Label", "Hello World");
485
+ const name = Param.string("Name", "Part-001", { maxLength: 20 });
486
+ ```
487
+
488
+ Override via import:
489
+
490
+ ```ts
491
+ const tag = require("./tag.forge.js", { Label: "Custom Text" });
492
+ ```
493
+
494
+ Only available as `Param.string()` — no standalone alias.
495
+
496
+ ```ts
497
+ Param.string(name: string, defaultValue: string, opts?: { maxLength?: number; }): string
498
+ ```
499
+
500
+ #### `Param.bool()` — Declare a boolean parameter that renders as a checkbox in the UI.
439
501
 
440
- **Example**
502
+ 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.
441
503
 
442
504
  ```ts
443
- const showHoles = boolParam("Show Holes", true);
505
+ const showHoles = Param.bool("Show Holes", true);
444
506
  if (showHoles) return difference(plate, cylinder(10, 5).translate(50, 30, 0));
445
507
  return plate;
446
508
  ```
@@ -451,20 +513,20 @@ Override via import:
451
513
  const pan = require("./pan.forge.js", { "Show Lid": 0 });
452
514
  ```
453
515
 
454
- `boolParam(name: string, defaultValue: boolean): boolean`
516
+ Also available as the shorthand alias `boolParam()`.
455
517
 
456
- #### `choiceParam()` — Declare a choice parameter that renders as a dropdown in the UI.
518
+ ```ts
519
+ Param.bool(name: string, defaultValue: boolean): boolean
520
+ ```
457
521
 
458
- **Details**
522
+ #### `Param.choice()` — Declare a choice parameter that renders as a dropdown in the UI.
459
523
 
460
- `defaultValue` must exactly match one entry in `choices`. Returns the selected string label. Prefer `choiceParam` over `param` when a slider would hide intent — named choices like `"wok"` are self-describing.
524
+ `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.
461
525
 
462
526
  Overrides may be passed as the choice label string (preferred) or as a numeric index. The `name` string is the override key.
463
527
 
464
- **Example**
465
-
466
528
  ```ts
467
- const panStyle = choiceParam("Pan Style", "frying-pan", ["frying-pan", "saute-pan", "wok"]);
529
+ const panStyle = Param.choice("Pan Style", "frying-pan", ["frying-pan", "saute-pan", "wok"]);
468
530
  if (panStyle === "wok") return buildWok();
469
531
  ```
470
532
 
@@ -480,17 +542,25 @@ Override via CLI:
480
542
  forgecad run model.forge.js --param "Pan Style=wok"
481
543
  ```
482
544
 
483
- `choiceParam(name: string, defaultValue: string, choices: string[]): string`
545
+ Also available as the shorthand alias `choiceParam()`.
484
546
 
485
- #### `listParam()` — Declare a list parameter — an array of struct items with per-field UI controls.
547
+ ```ts
548
+ Param.choice(name: string, defaultValue: string, choices: string[]): string
549
+ ```
486
550
 
487
- **Details**
551
+ #### `Param.list()` — Declare a list parameter — an array of struct items with per-field UI controls.
488
552
 
489
553
  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.
490
554
 
491
- Field types: - Boolean fields (`boolean: true` in field defs) return as `boolean` - Choice fields (`choices: [...]` in field defs) return as `string` - All other fields return as `number`
555
+ Field types:
492
556
 
493
- `listParam<T extends Record<string, number | boolean | string>>(name: string, defaultItems: T[], opts: { ... }): T[]`
557
+ - Boolean fields (`boolean: true` in field defs) return as `boolean`
558
+ - Choice fields (`choices: [...]` in field defs) return as `string`
559
+ - All other fields return as `number`
560
+
561
+ ```ts
562
+ Param.list<T extends Record<string, number | boolean | string>>(name: string, defaultItems: T[], opts: { ... }): T[]
563
+ ```
494
564
 
495
565
  `ListParamFieldDef`: `{ min?: number, max?: number, step?: number, unit?: string, integer?: boolean, boolean?: boolean, choices?: string[] }`
496
566
 
@@ -502,48 +572,60 @@ Unlike union(), colors and individual identities are preserved. Children can be
502
572
 
503
573
  **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.
504
574
 
505
- // BAD — every sub-part repeats the parent's global offset const unitX = 0, unitY = -18, unitZ = 70; const body = roundedBox(100, 20, 32, 4).translate(unitX, unitY, unitZ); const panel = box(98, 2, 18).translate(unitX, unitY - 12, unitZ + 4); const louver = box(88, 2, 6).translate(unitX, unitY - 14, unitZ - 11);
575
+ ```js
576
+ // BAD — every sub-part repeats the parent's global offset
577
+ const unitX = 0, unitY = -18, unitZ = 70;
578
+ const body = roundedBox(100, 20, 32, 4).translate(unitX, unitY, unitZ);
579
+ const panel = box(98, 2, 18).translate(unitX, unitY - 12, unitZ + 4);
580
+ const louver = box(88, 2, 6).translate(unitX, unitY - 14, unitZ - 11);
581
+ ```
506
582
 
507
583
  // 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);
508
584
 
509
- `group(...items: GroupInput[]): ShapeGroup`
585
+ ```ts
586
+ group(...items: GroupInput[]): ShapeGroup
587
+ ```
510
588
 
511
589
  ### Section & Projection
512
590
 
513
591
  #### `intersectWithPlane()` — Cross-section: slice a 3D shape with a plane and return the intersection as a 2D Sketch.
514
592
 
515
- `intersectWithPlane(shape: Shape, plane: PlaneSpec): Sketch`
593
+ ```ts
594
+ intersectWithPlane(shape: Shape, plane: PlaneSpec): Sketch
595
+ ```
516
596
 
517
597
  #### `faceProfile()` — Extract the boundary profile of a named face as a 2D sketch.
518
598
 
519
599
  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.
520
600
 
521
- `faceProfile(shape: Shape, face: FaceSelector): Sketch`
601
+ ```ts
602
+ faceProfile(shape: Shape, face: FaceSelector): Sketch
603
+ ```
522
604
 
523
605
  #### `projectToPlane()` — Orthographically project a 3D shape onto a plane and return the silhouette as a 2D Sketch.
524
606
 
525
- `projectToPlane(shape: Shape, plane: PlaneSpec): Sketch`
607
+ ```ts
608
+ projectToPlane(shape: Shape, plane: PlaneSpec): Sketch
609
+ ```
526
610
 
527
611
  ### Transforms
528
612
 
529
613
  #### `composeChain()` — Compose transforms in chain order. Equivalent to Transform.identity().mul(a).mul(b).mul(c)...
530
614
 
531
- `composeChain(...steps: TransformInput[]): Transform`
615
+ ```ts
616
+ composeChain(...steps: TransformInput[]): Transform
617
+ ```
532
618
 
533
619
  ### Verification
534
620
 
535
621
  #### `spec()` — Create a named, reusable bundle of verification checks.
536
622
 
537
- **Details**
538
-
539
623
  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.
540
624
 
541
625
  Specs can be defined in separate `.forge.js` files and imported via `require()` to share them across models.
542
626
 
543
627
  `spec.check()` returns a `SpecResult` — you can inspect it programmatically or ignore the return value and let the Checks panel show results.
544
628
 
545
- **Example**
546
-
547
629
  ```ts
548
630
  const printable = spec("Fits printer bed", (shape) => {
549
631
  verify.notEmpty("Has geometry", shape);
@@ -566,7 +648,9 @@ fitSpec.check(bracket, standoff);
566
648
 
567
649
  **Spec-first workflow:** Write specs before building geometry. Checks go from red to green as you build — effectively TDD for CAD.
568
650
 
569
- `spec(name: string, checkFn: (...args: any[]) => void): Spec`
651
+ ```ts
652
+ spec(name: string, checkFn: (...args: any[]) => void): Spec
653
+ ```
570
654
 
571
655
  **`Spec`**
572
656
  - `name: string` — The display name of this spec
@@ -587,138 +671,955 @@ Supports transforms (translate, rotate, scale, mirror, transform, rotateAround,
587
671
  |----------|------|-------------|
588
672
  | `materialProps` | `ShapeMaterialProps | undefined` | — |
589
673
 
590
- **Methods:**
591
-
592
- - `color(value: string | undefined): Shape` — Set the color of this shape (hex string, e.g. "#ff0000"). Returns a new Shape with the color applied.
593
- - `material(props: ShapeMaterialProps): Shape` — Set PBR material properties for this shape's visual appearance. **Details** Returns a new Shape with the specified material properties merged on top of any previously set properties. All properties are optional — omitted keys retain their current value. Material properties survive transforms and boolean operations. Use `.color()` to set the base diffuse color; `.material()` controls how that color behaves under light (metalness, roughness, clearcoat) and can add emissive glow independent of lighting. Emissive glow pairs naturally with the `postProcessing.bloom` effect in `scene()`. **Example** ```js box(50, 50, 50).material({ metalness: 0.9, roughness: 0.1 }); // polished metal sphere(30).material({ emissive: '#ff6b35', emissiveIntensity: 2 }); // glowing cylinder(40, 20).material({ opacity: 0.4, clearcoat: 1.0, clearcoatRoughness: 0.02 }); // ice // Chainable with other shape methods box(100, 100, 10).color('#gold').material({ metalness: 0.95, roughness: 0.05 }).translate(0, 0, 50); ```
594
- - `clone(): Shape` — Return a new Shape wrapper for explicit duplication in scripts.
595
- - `geometryInfo(): GeometryInfo` — Inspect which backend/representation produced this solid.
596
- - `withReferences(refs: PlacementReferenceInput): Shape` — Attach named placement references that survive normal transforms and imports.
597
- - `referenceNames(kind?: PlacementReferenceKind): string[]` — List named placement references carried by this shape.
598
- - `withPorts(ports: Record<string, PortInput>): Shape` — Deprecated alias for `withConnectors()`.
599
- - `portNames(): string[]` — Deprecated alias for `connectorNames()`.
600
- - `referencePoint(ref: PlacementAnchorLike): [ number, number, number ]` — Resolve a named placement reference or built-in anchor to a 3D point.
601
- - `face(selector: FaceSelector): FaceRef` — Resolve a face by user-authored label or compiler-owned name. Returns a `FaceRef` that can be passed to `.onFace()`, `projectToPlane()`, or used directly in placement. **Details** `.face(name)` is a pure label lookup — it finds faces by user-authored labels, not by geometric queries. Labels are born in sketches via `.label()` / `.labelEdges()` and grow into face names through extrude, loft, revolve, and sweep. They are stable references that travel with the geometry. Labels must be unique within a shape. Use `.prefixLabels()` before combining shapes with `union()` / `difference()` to avoid collisions. Collision detection throws a clear error with a fix suggestion. For compile-covered shapes (extrude, loft, etc.) the lookup resolves via the shape's compile plan. As a fallback, planar-faced mesh shapes (e.g. results of boolean ops) are resolved via coplanar triangle clustering. **Example** ```ts // Edge labels become side face names after extrude const profile = path() .moveTo(0, 0) .lineTo(100, 0).label('floor') .lineTo(100, 50).label('wall') .lineTo(0, 50).label('ceiling') .closeLabel('left-wall'); const room = profile.extrude(30, { labels: { start: 'base', end: 'top' } }); room.face('floor'); // side face from the labeled edge room.face('base'); // base cap (user-specified) // .labelEdges() shorthand for sequential edge labeling const plate = rect(100, 50).labelEdges('south', 'east', 'north', 'west'); const solid = plate.extrude(20, { labels: { start: 'bottom', end: 'top' } }); solid.face('south'); // side face // Prefix before combining to avoid collisions const left = wing.prefixLabels('l/'); const right = wing.mirror([1, 0, 0]).prefixLabels('r/'); const full = union(left, right); full.face('l/upper'); // left wing upper surface ```
602
- - `faces(query?: FaceQuery): FaceRef[]` — Return all faces matching a query, or all mesh-detected faces when no query is given.
603
- - `faceNames(): string[]` — List defended semantic face names currently available on this shape, including user labels from `labelFaces()`.
604
- - `prefixLabels(prefix: string): Shape` — Prefix all user-authored face labels (both sketch-edge labels and labelFaces labels). Returns a new shape with modified labels.
605
- - `renameLabel(from: string, to: string): Shape` — Rename a single face label. Returns a new shape.
606
- - `dropLabels(...names: string[]): Shape` — Remove specific face labels. Returns a new shape.
607
- - `dropAllLabels(): Shape` — Remove all face labels. Returns a new shape.
608
- - `edge(name: string): EdgeRef` — Get a named topology edge. Only available on shapes with tracked topology (from box/cylinder/extrude).
609
- - `edgeNames(): string[]` — List named topology edge names. Returns empty array if shape has no tracked topology.
610
- - `labelFaces(mapping: Record<string, string>): Shape` — Assign user-chosen labels to faces identified by their canonical position keys. **Details** Primitives (`box`, `cylinder`) and extrusions have internal canonical face positions (`top`, `bottom`, `side`, `side-left`, etc.) but these are **not** labels — they are just position selectors. Use `labelFaces()` to give faces meaningful, project-specific names that survive through transforms, booleans, fillets, and chamfers. The mapping keys are canonical position selectors; the values are your labels. Labels are the recommended way to identify faces for topological edge queries (`edgesOf`, `edgesBetween`). **Example** ```js // Full workflow: label → query edges → fillet let plate = box(100, 60, 5).labelFaces({ top: 'work-surface', bottom: 'mount-face' }) plate = fillet(plate, 2, plate.edgesOf('work-surface')) // Cylinder: fillet the rim where cap meets barrel let tube = cylinder(30, 10).labelFaces({ top: 'cap', side: 'barrel' }) tube = fillet(tube, 1, tube.edgesBetween('cap', 'barrel')) // Prefix before combining shapes to avoid label collisions const left = plate.prefixLabels('l/') const right = plate.mirror([1, 0, 0]).prefixLabels('r/') const full = union(left, right) full.edgesOf('l/work-surface') // still works ```
611
- - `edgesOf(faceLabel: string, options?: EdgesOfOptions): EdgeSegment[]` — Return all boundary edges of a named face. **Details** Finds edges where one adjacent mesh face belongs to the target face and the other belongs to a different face. The result is coalesced (tessellation fragments merged) and can be passed directly to `fillet()` or `chamfer()`. This is a topological query — no coordinates, no tolerances, no minimum-length hacks. It works because an edge is the boundary between two faces. **Example** ```js // Fillet all top edges of a mounting plate let plate = box(120, 80, 6).labelFaces({ top: 'work-surface' }) plate = fillet(plate, 3, plate.edgesOf('work-surface')) // Shelled enclosure — fillet the outer lip let body = box(80, 50, 35).labelFaces({ top: 'opening' }) body = body.shell(2, { openFaces: ['top'] }) body = fillet(body, 1.5, body.edgesOf('opening')) // Filter: only concave edges (after a boolean subtraction) body.edgesOf('top', { concave: true }) ```
612
- - `edgesBetween(faceA: string, faceB: string | string[]): EdgeSegment[]` — Return edges shared between two named faces. **Details** An edge is "between" faces A and B when one of its adjacent mesh triangles belongs to A and the other belongs to B. This is the most precise topological edge selection — "fillet the edges where the top meets the wall." The second argument can be a single face name or an array (edges between A and any of B1, B2, ...). **Example** ```js // Fillet the edge where lid meets one wall let body = box(100, 60, 30).labelFaces({ top: 'lid', 'side-left': 'wall' }) body = fillet(body, 2, body.edgesBetween('lid', 'wall')) // Fillet a cylinder rim — where the flat cap meets the curved barrel let tube = cylinder(30, 10).labelFaces({ top: 'cap', side: 'barrel' }) tube = fillet(tube, 1, tube.edgesBetween('cap', 'barrel')) // Multiple target faces at once body.edgesBetween('lid', ['left-wall', 'right-wall', 'front-wall', 'back-wall']) ```
613
- - `faceHistory(name: string): FaceTransformationHistory` — Get the transformation history for a specific face.
614
- - `placeReference(ref: PlacementAnchorLike, target: [ number, number, number ], offset?: [ number, number, number ]): Shape` — Translate the shape so the given anchor or reference lands on the target coordinate. Accepts any built-in anchor name (`'bottom'`, `'center'`, `'top-front-left'`, etc.) or a custom placement reference attached via `withReferences()`. ```javascript // Ground a shape — put its bottom face center at Z = 0 shape.placeReference('bottom', [0, 0, 0]) // Center at the world origin shape.placeReference('center', [0, 0, 0]) // Align left edge to X = 10 shape.placeReference('left', [10, 0, 0]) ```
615
- - `translatePolar(radius: number, angleDeg: number, z?: number): Shape` — Translate using polar coordinates (radius + angle in degrees). Eliminates manual `r * Math.cos(angle * PI/180)` calculations. Example: `shape.translatePolar(50, 30)` moves 50mm at 30 degrees from +X.
616
- - `translate(x: number, y: number, z: number): Shape` — Move the shape relative to its current position. All transforms are immutable and return new shapes.
617
- - `moveTo(x: number, y: number, z: number): Shape` — Position the shape so its bounding box min corner is at the given global coordinate.
618
- - `moveToLocal(target: Shape | { toShape(): Shape; }, x: number, y: number, z: number): Shape` — Position the shape relative to another shape's local coordinate system (bounding box min corner).
619
- - `rotate(axis: [ number, number, number ], angleDeg: number, options?: { pivot?: [ number, number, number ]; }): Shape` — Rotate around an arbitrary axis through the origin.
620
- - `rotateX(angleDeg: number, options?: { pivot?: [ number, number, number ]; }): Shape` — Rotate around the X axis by the given angle in degrees.
621
- - `rotateY(angleDeg: number, options?: { pivot?: [ number, number, number ]; }): Shape` — Rotate around the Y axis by the given angle in degrees.
622
- - `rotateZ(angleDeg: number, options?: { pivot?: [ number, number, number ]; }): Shape` — Rotate around the Z axis by the given angle in degrees.
623
- - `transform(m: Mat4 | Transform): Shape` — Apply a 4x4 affine transform matrix (column-major) or a Transform object.
624
- - `scale(v: number | [ number, number, number ]): Shape` — Scale the shape uniformly or per-axis from the shape's bounding box center. Accepts a single number or [x, y, z] array.
625
- - `scaleAround(pivot: [ number, number, number ], v: number | [ number, number, number ]): Shape` — Scale the shape uniformly or per-axis from an explicit pivot point.
626
- - `mirror(normal: [ number, number, number ]): Shape` — Mirror across a plane through the shape's bounding box center, defined by its normal vector.
627
- - `mirrorThrough(point: [ number, number, number ], normal: [ number, number, number ]): Shape` — Mirror across a plane through an explicit point, defined by its normal vector.
628
- - `pointAlong(direction: [ number, number, number ]): Shape` — Reorient a shape so its primary axis (Z) points along the given direction. Useful for laying cylinders/extrusions along X or Y without thinking about Euler angles. The shape's origin stays at [0,0,0] — translate after pointAlong to position it. Example: cylinder(40, 5).pointAlong([1, 0, 0]) — lays cylinder along X, starting at origin
629
- - `rotateAroundTo(axis: [ number, number, number ], pivot: [ number, number, number ], movingPoint: RotationPointLike, targetPoint: RotationPointLike, options?: RotateAroundToOptions): Shape` — Rotate around an axis until a moving point reaches the target line/plane defined by the axis and target point. `movingPoint` / `targetPoint` may be raw world points or this shape's anchors/references.
630
- - `add(...others: ShapeOperandInput[]): Shape` — Union this shape with others (additive boolean). Method form of union().
631
- - `subtract(...others: ShapeOperandInput[]): Shape` — Subtract other shapes from this one. Method form of difference().
632
- - `intersect(...others: ShapeOperandInput[]): Shape` — Keep only the overlap with other shapes. Method form of intersection().
633
- - `split(cutter: Shape | { toShape(): Shape; }): [ Shape, Shape ]` — Split into [inside, outside] by another shape.
634
- - `splitByPlane(normal: [ number, number, number ], originOffset?: number): [ Shape, Shape ]` — Split by infinite plane. Returns [positive-side, negative-side].
635
- - `trimByPlane(normal: [ number, number, number ], originOffset?: number): Shape` — Keep the positive side of the plane and discard the opposite side.
636
- - `shell(thickness: number, opts?: { openFaces?: string[]; }): Shape` — Hollow out compile-covered boxes, cylinders, and straight extrudes. `openFaces` names any subset of the base shape's labeled faces to leave open (no wall).
637
- - `boundingBox(): ShapeRuntimeBounds` — Get the axis-aligned bounding box as { min: [x,y,z], max: [x,y,z] }.
638
- - `volume(): number` — Volume in mm cubed.
639
- - `surfaceArea(): number` — Surface area in mm squared.
640
- - `isEmpty(): boolean` — True if the shape contains no geometry.
641
- - `numBodies(): number` — Number of disconnected solid bodies in this shape.
642
- - `numTri(): number` — Triangle count of the mesh representation.
643
- - `getMesh(): ShapeRuntimeMesh` — Extract triangle mesh for Three.js rendering
644
- - `slice(offset?: number): any` — Slice the runtime solid by a plane normal to local Z at the given offset.
645
- - `project(): any` — Orthographically project the runtime solid onto the local XY plane.
646
- - `attachTo(target: ShapeAnchorTarget, targetAnchor: PlacementAnchorLike, selfAnchor?: PlacementAnchorLike, offset?: [ number, number, number ]): Shape` — Position this shape relative to another using named 3D anchor points. Anchors are bounding-box-relative: 'center', face centers ('top', 'front', ...), edge midpoints ('top-front', 'back-left', ...), and corners ('top-front-left', ...). Anchor word order is flexible: 'front-left' and 'left-front' are equivalent. Named placement references (from withReferences) can also be used as anchors.
647
- - `onFace(parent: ShapeAnchorTarget, face: "front" | "back" | "left" | "right" | "top" | "bottom", opts?: { u?: number; v?: number; protrude?: number; }): Shape` — Place this shape on a face of a parent shape. Think of it like sticking a label on a box surface: - `face` picks which surface ('front', 'back', 'top', etc.) - `u, v` position within that face's 2D plane (from center) - front/back: u = left/right (X), v = up/down (Z) - left/right: u = forward/back (Y), v = up/down (Z) - top/bottom: u = left/right (X), v = forward/back (Y) - `protrude` = how far the child sticks out (positive = outward from face)
648
- - `seatInto(target: Shape, surface: string, options?: SeatIntoOptions): Shape` — Slide this shape along an axis until a labeled face is embedded in the target body. Position the shape roughly first (translate/rotate), then call seatInto to auto-adjust the penetration depth. No manual coordinate math needed. ```js // Wing root embeds into fuselage — adapts to any fuselage shape wing.translate(0, wingY, 0).seatInto(fuselage, 'root'); // Sensor pod sits flush on fuselage surface pod.translate(0, station, radius + 20).seatInto(fuselage, 'base', { depth: 'flush' }); // Antenna with 3mm gasket standoff mast.translate(0, station, radius + 50).seatInto(fuselage, 'mount', { depth: 'flush', gap: 3 }); ```
649
- - `seatOver(target: Shape, targetSurface: string, options?: SeatIntoOptions): Shape` — Slide this shape until a target's labeled face is fully covered (inside this shape). The inverse of `seatInto`: instead of embedding *your* face into the target, you move until the *target's* face is embedded inside you. ```js // Nacelle moves up until pylon's bottom face is inside the nacelle nacelle.translate(rough).seatOver(pylon, 'bottom'); // Cap slides down over a post until post's top face is covered cap.translate(rough).seatOver(post, 'top'); ```
650
- - `withConnectors(connectors: Record<string, ConnectorInput>): Shape` — Attach named connectors — attachment points that survive transforms and imports. Connectors can be bare (position + orientation) or typed (with connectorType/gender for compatibility matching).
651
- - `connectorNames(): string[]` — List all connector names on this shape.
652
- - `connectorsByType(type: string): Array<{ name: string; port: PortDef; }>` — Get all connectors of a given type.
653
- - `connectorDistance(nameA: string, nameB: string): number` — Distance between two connector origins on this shape.
654
- - `connectorMeasurements(name: string): Record<string, number | string>` — Get measurements metadata from a connector.
655
- - `matchTo(targetOrPairs: Shape | MatchTarget | Array<[ Shape | MatchTarget, string, string ]>, selfConnOrDict?: string | Record<string, string>, targetConnOrOptions?: string | MatchToOptions, maybeOptions?: MatchToOptions): Shape` — Position this shape by matching connectors to a target. Overloads: - Single pair: `matchTo(target, selfConn, targetConn, options?)` - Dictionary (same target): `matchTo(target, { selfConn: targetConn, ... }, options?)` - Multi-target: `matchTo([ [target1, selfConn1, targetConn1], ... ], options?)`
656
- - `pocket(face: FaceSelector, depth: number, opts?: PocketOptions): Shape` — Cut a pocket (cavity) into this solid through the named face. box(100, 100, 20).pocket('top', 8) box(100, 100, 20).pocket('top', 8, { inset: 5 }) box(100, 100, 20).pocket('top', 8, { scale: 0.8 })
657
- - `boss(face: FaceSelector, height: number, opts?: BossOptions): Shape` — Add a boss (protrusion) from the named face. box(100, 100, 20).boss('top', 5) box(100, 100, 20).boss('top', 10, { scale: 0.6 })
658
- - `hole(faceOrRef: SketchFaceTarget | FaceRef, opts: ShapeHoleOptions): Shape` — Drill a hole into this solid at a face. box(50, 50, 20).hole('top', { diameter: 8, depth: 10 }) box(50, 50, 20).hole('top', { diameter: 6, counterbore: { diameter: 12, depth: 3 } })
659
- - `cutout(sketch: Sketch, opts?: ShapeCutoutOptions): Shape` — Cut a profile-shaped pocket through a face using a placed sketch. The sketch must be placed on a face with `Sketch.onFace(...)`. The cut follows the sketch's 2D profile. const profile = circle2d(10).onFace(body, 'top'); body.cutout(profile, { depth: 5 })
674
+ **Appearance**
660
675
 
661
- ### `Transform`
676
+ #### `color()` — Set the color of this shape (hex string, e.g. "#ff0000"). Returns a new Shape with the color applied.
662
677
 
663
- - `static identity(): Transform` — Return the identity transform.
664
- - `static from(input: TransformInput): Transform` — Wrap an existing `Transform` or raw 4x4 matrix as a `Transform`.
665
- - `static translation(x: number, y: number, z: number): Transform` — Create a translation transform.
666
- - `static scale(v: number | Vec3): Transform` — Create a uniform or per-axis scale transform.
667
- - `static rotationAxis(axis: Vec3, angleDeg: number, pivot?: Vec3): Transform` — Create a rotation around an arbitrary axis, optionally about a pivot.
668
- - `static rotateAroundTo(axis: Vec3, pivot: Vec3, movingPoint: Vec3, targetPoint: Vec3, options?: RotateAroundToOptions): Transform` — Solve the rotation needed to move one point onto a target line or plane.
669
- - `mul(other: TransformInput): Transform` — Compose transforms in chain order: `a.mul(b)` applies `a`, then `b`.
670
- - `translate(x: number, y: number, z: number): Transform` — Translate after the current transform.
671
- - `rotateAxis(axis: Vec3, angleDeg: number, pivot?: Vec3): Transform` — Rotate after the current transform.
672
- - `inverse(): Transform` — Return the inverse transform.
673
- - `point(p: Vec3): Vec3` — Transform a point using homogeneous coordinates.
674
- - `vector(v: Vec3): Vec3` — Transform a direction vector without translation.
675
- - `toArray(): Mat4` — Return the transform as a raw 4x4 matrix array.
678
+ ```ts
679
+ color(value: string | undefined): Shape
680
+ ```
676
681
 
677
- ### `ShapeGroup`
682
+ #### `material()` — Set PBR material properties for this shape's visual appearance.
678
683
 
679
- **Properties:**
684
+ Returns a new Shape with the specified material properties merged on top of any previously set properties. All properties are optional — omitted keys retain their current value. Material properties survive transforms and boolean operations.
680
685
 
681
- | Property | Type | Description |
682
- |----------|------|-------------|
683
- | `children` | `GroupChild[]` | — |
684
- | `childNames` | `Array<string | undefined>` | |
686
+ Use `.color()` to set the base diffuse color; `.material()` controls how that color behaves under light (metalness, roughness, clearcoat) and can add emissive glow independent of lighting. Emissive glow pairs naturally with the `postProcessing.bloom` effect in [`scene()`](/docs/viewport#scene).
687
+
688
+ ```js
689
+ box(50, 50, 50).material({ metalness: 0.9, roughness: 0.1 }); // polished metal
690
+ sphere(30).material({ emissive: '#ff6b35', emissiveIntensity: 2 }); // glowing
691
+ cylinder(40, 20).material({ opacity: 0.4, clearcoat: 1.0, clearcoatRoughness: 0.02 }); // ice
692
+
693
+ // Chainable with other shape methods
694
+ box(100, 100, 10).color('#gold').material({ metalness: 0.95, roughness: 0.05 }).translate(0, 0, 50);
695
+ ```
696
+
697
+ ```ts
698
+ material(props: ShapeMaterialProps): Shape
699
+ ```
700
+
701
+ **Face Topology**
702
+
703
+ #### `face()` — Resolve a face by user-authored label or compiler-owned name. Returns a `FaceRef` that can be passed to `.onFace()`, `projectToPlane()`, or used directly in placement.
704
+
705
+ `.face(name)` is a pure label lookup — it finds faces by user-authored labels, not by geometric queries. Labels are born in sketches via `.label()` / `.labelEdges()` and grow into face names through extrude, loft, revolve, and sweep. They are stable references that travel with the geometry.
706
+
707
+ Labels must be unique within a shape. Use `.prefixLabels()` before combining shapes with `union()` / `difference()` to avoid collisions. Collision detection throws a clear error with a fix suggestion.
708
+
709
+ For compile-covered shapes (extrude, loft, etc.) the lookup resolves via the shape's compile plan. As a fallback, planar-faced mesh shapes (e.g. results of boolean ops) are resolved via coplanar triangle clustering.
710
+
711
+ ```ts
712
+ // Edge labels become side face names after extrude
713
+ const profile = path()
714
+ .moveTo(0, 0)
715
+ .lineTo(100, 0).label('floor')
716
+ .lineTo(100, 50).label('wall')
717
+ .lineTo(0, 50).label('ceiling')
718
+ .closeLabel('left-wall');
719
+ const room = profile.extrude(30, { labels: { start: 'base', end: 'top' } });
720
+ room.face('floor'); // side face from the labeled edge
721
+ room.face('base'); // base cap (user-specified)
722
+
723
+ // .labelEdges() shorthand for sequential edge labeling
724
+ const plate = rect(100, 50).labelEdges('south', 'east', 'north', 'west');
725
+ const solid = plate.extrude(20, { labels: { start: 'bottom', end: 'top' } });
726
+ solid.face('south'); // side face
727
+
728
+ // Prefix before combining to avoid collisions
729
+ const left = wing.prefixLabels('l/');
730
+ const right = wing.mirror([1, 0, 0]).prefixLabels('r/');
731
+ const full = union(left, right);
732
+ full.face('l/upper'); // left wing upper surface
733
+ ```
734
+
735
+ ```ts
736
+ face(selector: FaceSelector): FaceRef
737
+ ```
738
+
739
+ #### `faces()` — Return all faces matching a query, or all mesh-detected faces when no query is given.
740
+
741
+ ```ts
742
+ faces(query?: FaceQuery): FaceRef[]
743
+ ```
744
+
745
+ #### `faceNames()` — List defended semantic face names currently available on this shape, including user labels from `labelFaces()`.
746
+
747
+ ```ts
748
+ faceNames(): string[]
749
+ ```
750
+
751
+ #### `labelFaces()` — Assign user-chosen labels to faces identified by their canonical position keys.
752
+
753
+ Primitives (`box`, `cylinder`) and extrusions have internal canonical face positions (`top`, `bottom`, `side`, `side-left`, etc.) but these are **not** labels — they are just position selectors. Use `labelFaces()` to give faces meaningful, project-specific names that survive through transforms, booleans, fillets, and chamfers.
754
+
755
+ The mapping keys are canonical position selectors; the values are your labels. Labels are the recommended way to identify faces for topological edge queries (`edgesOf`, `edgesBetween`).
756
+
757
+ ```js
758
+ // Full workflow: label → query edges → fillet
759
+ let plate = box(100, 60, 5).labelFaces({ top: 'work-surface', bottom: 'mount-face' })
760
+ plate = fillet(plate, 2, plate.edgesOf('work-surface'))
761
+
762
+ // Cylinder: fillet the rim where cap meets barrel
763
+ let tube = cylinder(30, 10).labelFaces({ top: 'cap', side: 'barrel' })
764
+ tube = fillet(tube, 1, tube.edgesBetween('cap', 'barrel'))
765
+
766
+ // Prefix before combining shapes to avoid label collisions
767
+ const left = plate.prefixLabels('l/')
768
+ const right = plate.mirror([1, 0, 0]).prefixLabels('r/')
769
+ const full = union(left, right)
770
+ full.edgesOf('l/work-surface') // still works
771
+ ```
772
+
773
+ ```ts
774
+ labelFaces(mapping: Record<string, string>): Shape
775
+ ```
776
+
777
+ #### `prefixLabels()` — Prefix all user-authored face labels (both sketch-edge labels and labelFaces labels). Returns a new shape with modified labels.
778
+
779
+ ```ts
780
+ prefixLabels(prefix: string): Shape
781
+ ```
782
+
783
+ #### `renameLabel()` — Rename a single face label. Returns a new shape.
784
+
785
+ ```ts
786
+ renameLabel(from: string, to: string): Shape
787
+ ```
788
+
789
+ #### `dropLabels()` — Remove specific face labels. Returns a new shape.
790
+
791
+ ```ts
792
+ dropLabels(...names: string[]): Shape
793
+ ```
794
+
795
+ #### `dropAllLabels()` — Remove all face labels. Returns a new shape.
796
+
797
+ ```ts
798
+ dropAllLabels(): Shape
799
+ ```
800
+
801
+ #### `faceHistory()` — Get the transformation history for a specific face.
802
+
803
+ ```ts
804
+ faceHistory(name: string): FaceTransformationHistory
805
+ ```
806
+
807
+ **Edge Topology**
808
+
809
+ #### `edge()` — Get a named topology edge. Only available on shapes with tracked topology (from box/cylinder/extrude).
810
+
811
+ ```ts
812
+ edge(name: string): EdgeRef
813
+ ```
814
+
815
+ #### `edgeNames()` — List named topology edge names. Returns empty array if shape has no tracked topology.
816
+
817
+ ```ts
818
+ edgeNames(): string[]
819
+ ```
820
+
821
+ #### `edgesOf()` — Return all boundary edges of a named face.
822
+
823
+ Finds edges where one adjacent mesh face belongs to the target face and the other belongs to a different face. The result is coalesced (tessellation fragments merged) and can be passed directly to `fillet()` or `chamfer()`.
824
+
825
+ This is a topological query — no coordinates, no tolerances, no minimum-length hacks. It works because an edge is the boundary between two faces.
826
+
827
+ ```js
828
+ // Fillet all top edges of a mounting plate
829
+ let plate = box(120, 80, 6).labelFaces({ top: 'work-surface' })
830
+ plate = fillet(plate, 3, plate.edgesOf('work-surface'))
831
+
832
+ // Shelled enclosure — fillet the outer lip
833
+ let body = box(80, 50, 35).labelFaces({ top: 'opening' })
834
+ body = body.shell(2, { openFaces: ['top'] })
835
+ body = fillet(body, 1.5, body.edgesOf('opening'))
836
+
837
+ // Filter: only concave edges (after a boolean subtraction)
838
+ body.edgesOf('top', { concave: true })
839
+ ```
840
+
841
+ ```ts
842
+ edgesOf(faceLabel: string, options?: EdgesOfOptions): EdgeSegment[]
843
+ ```
844
+
845
+ #### `edgesBetween()` — Return edges shared between two named faces.
846
+
847
+ An edge is "between" faces A and B when one of its adjacent mesh triangles belongs to A and the other belongs to B. This is the most precise topological edge selection — "fillet the edges where the top meets the wall."
848
+
849
+ The second argument can be a single face name or an array (edges between A and any of B1, B2, ...).
685
850
 
686
- **Methods:**
687
-
688
- - `childName(index: number): string | undefined` Return the optional name of the child at `index`.
689
- - `child(name: string): GroupChild` — Return the named child by name. Throws if not found. Useful when importing a multipart group and working on components individually.
690
- - `clone(): ShapeGroup` — Return a deep-cloned ShapeGroup tree (refs copied).
691
- - `translate(x: number, y: number, z: number): ShapeGroup` Move the entire group by (x, y, z). All children move together as a unit.
692
- - `boundingBox(): { min: [ number, number, number ]; max: [ number, number, number ]; }` — Return the combined 3D bounding box of all children.
693
- - `moveTo(x: number, y: number, z: number): ShapeGroup` — Move the group so its bounding-box min corner lands at the given coordinate.
694
- - `moveToLocal(target: Shape | ShapeGroup, x: number, y: number, z: number): ShapeGroup` — Move the group relative to another part's bounding-box min corner.
695
- - `attachTo(target: Shape | ShapeGroup, targetAnchor: Anchor3D | string, selfAnchor?: Anchor3D, offset?: [ number, number, number ]): ShapeGroup` — Attach this group to a face or anchor on another part. `targetAnchor` can be a built-in anchor name or a custom reference name on the target. `selfAnchor` selects the anchor on this group to align.
696
- - `onFace(parent: Shape | ShapeGroup, face: "front" | "back" | "left" | "right" | "top" | "bottom", opts?: { u?: number; v?: number; protrude?: number; }): ShapeGroup` — Place this group on a face of a parent shape. See Shape.onFace() for full documentation.
697
- - `rotate(axis: [ number, number, number ], angleDeg: number, options?: { pivot?: [ number, number, number ]; }): ShapeGroup` — Rotate the group around an arbitrary axis through the origin.
698
- - `rotateX(angleDeg: number, options?: { pivot?: [ number, number, number ]; }): ShapeGroup` — Rotate the group around the X axis.
699
- - `rotateY(angleDeg: number, options?: { pivot?: [ number, number, number ]; }): ShapeGroup` — Rotate the group around the Y axis.
700
- - `rotateZ(angleDeg: number, options?: { pivot?: [ number, number, number ]; }): ShapeGroup` — Rotate the group around the Z axis.
701
- - `rotateAroundAxis(axis: [ number, number, number ], angleDeg: number, pivot?: [ number, number, number ]): ShapeGroup` — Rotate around an arbitrary axis, optionally through a pivot point.
702
- - `rotateAroundTo(axis: [ number, number, number ], pivot: [ number, number, number ], movingPoint: Anchor3D | [ number, number, number ], targetPoint: Anchor3D | [ number, number, number ], options?: RotateAroundToOptions): ShapeGroup` — Rotate around an axis until a moving point reaches the target line/plane defined by the axis and target point. ShapeGroup string points use built-in anchors only.
703
- - `pointAlong(direction: [ number, number, number ]): ShapeGroup` — Reorient the group so its local Z axis points along `direction`.
704
- - `transform(m: Mat4 | Transform): ShapeGroup` — Apply a 4x4 transform matrix or `Transform` to all 3D children.
705
- - `scale(v: number | [ number, number, number ]): ShapeGroup` Scale uniformly or per-axis from the group's bounding-box center.
706
- - `scaleAround(pivot: [ number, number, number ], v: number | [ number, number, number ]): ShapeGroup` — Scale uniformly or per-axis from an explicit pivot point.
707
- - `mirror(normal: [ number, number, number ]): ShapeGroup` — Mirror across a plane through the group's bounding-box center.
708
- - `mirrorThrough(point: [ number, number, number ], normal: [ number, number, number ]): ShapeGroup` — Mirror across a plane through an explicit point.
709
- - `color(hex: string): ShapeGroup` — Return a copy of the group with the given display color applied to each child.
710
- - `withReferences(refs: PlacementReferenceInput): ShapeGroup` — Attach named placement references to this group. References survive normal transforms (translate/rotate/scale/mirror/transform). ```javascript const bracket = group( { name: 'Left', shape: leftShape }, { name: 'Right', shape: rightShape }, ).withReferences({ points: { mountCenter: [0, 0, 0] }, }); ```
711
- - `referenceNames(kind?: PlacementReferenceKind): string[]` — List named placement references carried by this group.
712
- - `withPorts(ports: Record<string, PortInput>): ShapeGroup` — Backward-compatible alias for `withConnectors()`.
713
- - `portNames(): string[]` Backward-compatible alias for `connectorNames()`.
714
- - `referencePoint(ref: PlacementAnchorLike): [ number, number, number ]` — Resolve a named placement reference or built-in Anchor3D to a 3D point. Named refs take priority over built-in anchors.
715
- - `placeReference(ref: PlacementAnchorLike, target: [ number, number, number ], offset?: [ number, number, number ]): ShapeGroup` — Translate the group so the given anchor or reference lands on the target coordinate. Accepts any built-in anchor name (`'bottom'`, `'center'`, `'top-front-left'`, etc.) or a custom placement reference attached via `withReferences()`. ```javascript // Ground a group — put its bottom at Z = 0 assembly.placeReference('bottom', [0, 0, 0]) // Use a custom reference from a multi-file part const placed = require('./bracket-assembly.forge.js').group .placeReference('mountCenter', [0, 0, 50]); ```
716
- - `withConnectors(connectors: Record<string, ConnectorInput>): ShapeGroup` Attach named connectors — attachment points that survive transforms. Connectors can be bare (position + orientation) or typed (with connectorType/gender for compatibility matching).
717
- - `connectorNames(): string[]` — List all connector names, including "ChildName.connectorName" from named children.
718
- - `connectorsByType(type: string): Array<{ name: string; port: PortDef; }>` — Get all connectors of a given type, including from named children.
719
- - `connectorDistance(nameA: string, nameB: string): number` Distance between two connector origins on this group (supports dotted child paths).
720
- - `connectorMeasurements(name: string): Record<string, number | string>` — Get measurements metadata from a connector (supports dotted child paths).
721
- - `matchTo(targetOrPairs: Shape | ShapeGroup | Array<[ Shape | ShapeGroup, string, string ]>, selfConnOrDict?: string | Record<string, string>, targetConnOrOptions?: string | MatchToOptions, maybeOptions?: MatchToOptions): ShapeGroup` — Position this group by matching connectors to a target. Connector names support dotted paths into named children: "ChildName.connectorName". Overloads: - Single pair: `matchTo(target, selfConn, targetConn, options?)` - Dictionary (same target): `matchTo(target, { selfConn: targetConn, ... }, options?)` - Multi-target: `matchTo([ [target1, selfConn1, targetConn1], ... ], options?)`
851
+ ```js
852
+ // Fillet the edge where lid meets one wall
853
+ let body = box(100, 60, 30).labelFaces({ top: 'lid', 'side-left': 'wall' })
854
+ body = fillet(body, 2, body.edgesBetween('lid', 'wall'))
855
+
856
+ // Fillet a cylinder rimwhere the flat cap meets the curved barrel
857
+ let tube = cylinder(30, 10).labelFaces({ top: 'cap', side: 'barrel' })
858
+ tube = fillet(tube, 1, tube.edgesBetween('cap', 'barrel'))
859
+
860
+ // Multiple target faces at once
861
+ body.edgesBetween('lid', ['left-wall', 'right-wall', 'front-wall', 'back-wall'])
862
+ ```
863
+
864
+ ```ts
865
+ edgesBetween(faceA: string, faceB: string | string[]): EdgeSegment[]
866
+ ```
867
+
868
+ **Transforms**
869
+
870
+ #### `translate()` Move the shape relative to its current position. All transforms are immutable and return new shapes.
871
+
872
+ ```ts
873
+ translate(x: number, y: number, z: number): Shape
874
+ ```
875
+
876
+ #### `translatePolar()` — Translate using polar coordinates (radius + angle in degrees). Eliminates manual `r * Math.cos(angle * PI/180)` calculations.
877
+
878
+ Example: `shape.translatePolar(50, 30)` moves 50mm at 30 degrees from +X.
879
+
880
+ ```ts
881
+ translatePolar(radius: number, angleDeg: number, z?: number): Shape
882
+ ```
883
+
884
+ #### `moveTo()` Position the shape so its bounding box min corner is at the given global coordinate.
885
+
886
+ ```ts
887
+ moveTo(x: number, y: number, z: number): Shape
888
+ ```
889
+
890
+ #### `moveToLocal()` — Position the shape relative to another shape's local coordinate system (bounding box min corner).
891
+
892
+ ```ts
893
+ moveToLocal(target: Shape | { toShape(): Shape; }, x: number, y: number, z: number): Shape
894
+ ```
895
+
896
+ #### `rotate()` — Rotate around an arbitrary axis through the origin.
897
+
898
+ ```ts
899
+ rotate(axis: [ number, number, number ], angleDeg: number, options?: { pivot?: [ number, number, number ]; }): Shape
900
+ ```
901
+
902
+ #### `rotateX()` — Rotate around the X axis by the given angle in degrees.
903
+
904
+ ```ts
905
+ rotateX(angleDeg: number, options?: { pivot?: [ number, number, number ]; }): Shape
906
+ ```
907
+
908
+ #### `rotateY()` — Rotate around the Y axis by the given angle in degrees.
909
+
910
+ ```ts
911
+ rotateY(angleDeg: number, options?: { pivot?: [ number, number, number ]; }): Shape
912
+ ```
913
+
914
+ #### `rotateZ()` — Rotate around the Z axis by the given angle in degrees.
915
+
916
+ ```ts
917
+ rotateZ(angleDeg: number, options?: { pivot?: [ number, number, number ]; }): Shape
918
+ ```
919
+
920
+ #### `rotateAroundTo()` — Rotate around an axis until a moving point reaches the target line/plane defined by the axis and target point. `movingPoint` / `targetPoint` may be raw world points or this shape's anchors/references.
921
+
922
+ ```ts
923
+ rotateAroundTo(axis: [ number, number, number ], pivot: [ number, number, number ], movingPoint: RotationPointLike, targetPoint: RotationPointLike, options?: RotateAroundToOptions): Shape
924
+ ```
925
+
926
+ #### `transform()` — Apply a 4x4 affine transform matrix (column-major) or a Transform object.
927
+
928
+ ```ts
929
+ transform(m: Mat4 | Transform): Shape
930
+ ```
931
+
932
+ #### `scale()` — Scale the shape uniformly or per-axis from the shape's bounding box center. Accepts a single number or [x, y, z] array.
933
+
934
+ ```ts
935
+ scale(v: number | [ number, number, number ]): Shape
936
+ ```
937
+
938
+ #### `scaleAround()` — Scale the shape uniformly or per-axis from an explicit pivot point.
939
+
940
+ ```ts
941
+ scaleAround(pivot: [ number, number, number ], v: number | [ number, number, number ]): Shape
942
+ ```
943
+
944
+ #### `mirror()` — Mirror across a plane through the shape's bounding box center, defined by its normal vector.
945
+
946
+ ```ts
947
+ mirror(normal: [ number, number, number ]): Shape
948
+ ```
949
+
950
+ #### `mirrorThrough()` — Mirror across a plane through an explicit point, defined by its normal vector.
951
+
952
+ ```ts
953
+ mirrorThrough(point: [ number, number, number ], normal: [ number, number, number ]): Shape
954
+ ```
955
+
956
+ #### `pointAlong()` — Reorient a shape so its primary axis (Z) points along the given direction. Useful for laying cylinders/extrusions along X or Y without thinking about Euler angles. The shape's origin stays at [0,0,0] — translate after pointAlong to position it.
957
+
958
+ Example: cylinder(40, 5).pointAlong([1, 0, 0]) — lays cylinder along X, starting at origin
959
+
960
+ ```ts
961
+ pointAlong(direction: [ number, number, number ]): Shape
962
+ ```
963
+
964
+ **Booleans & Cutting**
965
+
966
+ #### `add()` — Union this shape with others (additive boolean). Method form of union().
967
+
968
+ ```ts
969
+ add(...others: ShapeOperandInput[]): Shape
970
+ ```
971
+
972
+ #### `subtract()` — Subtract other shapes from this one. Method form of difference().
973
+
974
+ ```ts
975
+ subtract(...others: ShapeOperandInput[]): Shape
976
+ ```
977
+
978
+ #### `intersect()` — Keep only the overlap with other shapes. Method form of intersection().
979
+
980
+ ```ts
981
+ intersect(...others: ShapeOperandInput[]): Shape
982
+ ```
983
+
984
+ #### `split()` — Split into [inside, outside] by another shape.
985
+
986
+ ```ts
987
+ split(cutter: Shape | { toShape(): Shape; }): [ Shape, Shape ]
988
+ ```
989
+
990
+ #### `splitByPlane()` — Split by infinite plane. Returns [positive-side, negative-side].
991
+
992
+ ```ts
993
+ splitByPlane(normal: [ number, number, number ], originOffset?: number): [ Shape, Shape ]
994
+ ```
995
+
996
+ #### `trimByPlane()` — Keep the positive side of the plane and discard the opposite side.
997
+
998
+ ```ts
999
+ trimByPlane(normal: [ number, number, number ], originOffset?: number): Shape
1000
+ ```
1001
+
1002
+ **Features**
1003
+
1004
+ #### `shell()` — Hollow out compile-covered boxes, cylinders, and straight extrudes. `openFaces` names any subset of the base shape's labeled faces to leave open (no wall).
1005
+
1006
+ ```ts
1007
+ shell(thickness: number, opts?: { openFaces?: string[]; }): Shape
1008
+ ```
1009
+
1010
+ #### `pocket()` — Cut a pocket (cavity) into this solid through the named face.
1011
+
1012
+ ```js
1013
+ box(100, 100, 20).pocket('top', 8)
1014
+ box(100, 100, 20).pocket('top', 8, { inset: 5 })
1015
+ box(100, 100, 20).pocket('top', 8, { scale: 0.8 })
1016
+ ```
1017
+
1018
+ ```ts
1019
+ pocket(face: FaceSelector, depth: number, opts?: PocketOptions): Shape
1020
+ ```
1021
+
1022
+ #### `boss()` — Add a boss (protrusion) from the named face.
1023
+
1024
+ ```js
1025
+ box(100, 100, 20).boss('top', 5)
1026
+ box(100, 100, 20).boss('top', 10, { scale: 0.6 })
1027
+ ```
1028
+
1029
+ ```ts
1030
+ boss(face: FaceSelector, height: number, opts?: BossOptions): Shape
1031
+ ```
1032
+
1033
+ #### `hole()` — Drill a hole into this solid at a face.
1034
+
1035
+ ```js
1036
+ box(50, 50, 20).hole('top', { diameter: 8, depth: 10 })
1037
+ box(50, 50, 20).hole('top', { diameter: 6, counterbore: { diameter: 12, depth: 3 } })
1038
+ ```
1039
+
1040
+ ```ts
1041
+ hole(faceOrRef: SketchFaceTarget | FaceRef, opts: ShapeHoleOptions): Shape
1042
+ ```
1043
+
1044
+ #### `cutout()` — Cut a profile-shaped pocket through a face using a placed sketch.
1045
+
1046
+ The sketch must be placed on a face with `Sketch.onFace(...)`. The cut follows the sketch's 2D profile.
1047
+
1048
+ ```js
1049
+ const profile = circle2d(10).onFace(body, 'top');
1050
+ body.cutout(profile, { depth: 5 })
1051
+ ```
1052
+
1053
+ ```ts
1054
+ cutout(sketch: Sketch, opts?: ShapeCutoutOptions): Shape
1055
+ ```
1056
+
1057
+ **Placement**
1058
+
1059
+ #### `placeReference()` — Translate the shape so the given anchor or reference lands on the target coordinate.
1060
+
1061
+ Accepts any built-in anchor name (`'bottom'`, `'center'`, `'top-front-left'`, etc.) or a custom placement reference attached via `withReferences()`.
1062
+
1063
+ ```javascript
1064
+ // Ground a shape — put its bottom face center at Z = 0
1065
+ shape.placeReference('bottom', [0, 0, 0])
1066
+
1067
+ // Center at the world origin
1068
+ shape.placeReference('center', [0, 0, 0])
1069
+
1070
+ // Align left edge to X = 10
1071
+ shape.placeReference('left', [10, 0, 0])
1072
+ ```
1073
+
1074
+ ```ts
1075
+ placeReference(ref: PlacementAnchorLike, target: [ number, number, number ], offset?: [ number, number, number ]): Shape
1076
+ ```
1077
+
1078
+ #### `attachTo()` — Position this shape relative to another using named 3D anchor points.
1079
+
1080
+ Anchors are bounding-box-relative: 'center', face centers ('top', 'front', ...), edge midpoints ('top-front', 'back-left', ...), and corners ('top-front-left', ...). Anchor word order is flexible: 'front-left' and 'left-front' are equivalent. Named placement references (from withReferences) can also be used as anchors.
1081
+
1082
+ ```ts
1083
+ attachTo(target: ShapeAnchorTarget, targetAnchor: PlacementAnchorLike, selfAnchor?: PlacementAnchorLike, offset?: [ number, number, number ]): Shape
1084
+ ```
1085
+
1086
+ #### `onFace()` — Place this shape on a face of a parent shape.
1087
+
1088
+ Think of it like sticking a label on a box surface:
1089
+
1090
+ - `face` picks which surface ('front', 'back', 'top', etc.)
1091
+ - `u, v` position within that face's 2D plane (from center)
1092
+ - front/back: u = left/right (X), v = up/down (Z)
1093
+ - left/right: u = forward/back (Y), v = up/down (Z)
1094
+ - top/bottom: u = left/right (X), v = forward/back (Y)
1095
+ - `protrude` = how far the child sticks out (positive = outward from face)
1096
+
1097
+ ```ts
1098
+ onFace(parent: ShapeAnchorTarget, face: "front" | "back" | "left" | "right" | "top" | "bottom", opts?: { u?: number; v?: number; protrude?: number; }): Shape
1099
+ ```
1100
+
1101
+ #### `seatInto()` — Slide this shape along an axis until a labeled face is embedded in the target body.
1102
+
1103
+ Position the shape roughly first (translate/rotate), then call seatInto to auto-adjust the penetration depth. No manual coordinate math needed.
1104
+
1105
+ ```js
1106
+ // Wing root embeds into fuselage — adapts to any fuselage shape
1107
+ wing.translate(0, wingY, 0).seatInto(fuselage, 'root');
1108
+
1109
+ // Sensor pod sits flush on fuselage surface
1110
+ pod.translate(0, station, radius + 20).seatInto(fuselage, 'base', { depth: 'flush' });
1111
+
1112
+ // Antenna with 3mm gasket standoff
1113
+ mast.translate(0, station, radius + 50).seatInto(fuselage, 'mount', { depth: 'flush', gap: 3 });
1114
+ ```
1115
+
1116
+ ```ts
1117
+ seatInto(target: Shape, surface: string, options?: SeatIntoOptions): Shape
1118
+ ```
1119
+
1120
+ #### `seatOver()` — Slide this shape until a target's labeled face is fully covered (inside this shape).
1121
+
1122
+ The inverse of `seatInto`: instead of embedding *your* face into the target, you move until the *target's* face is embedded inside you.
1123
+
1124
+ ```js
1125
+ // Nacelle moves up until pylon's bottom face is inside the nacelle
1126
+ nacelle.translate(rough).seatOver(pylon, 'bottom');
1127
+
1128
+ // Cap slides down over a post until post's top face is covered
1129
+ cap.translate(rough).seatOver(post, 'top');
1130
+ ```
1131
+
1132
+ ```ts
1133
+ seatOver(target: Shape, targetSurface: string, options?: SeatIntoOptions): Shape
1134
+ ```
1135
+
1136
+ **Connectors**
1137
+
1138
+ #### `withConnectors()` — Attach named connectors — attachment points that survive transforms and imports. Connectors can be bare (position + orientation) or typed (with connectorType/gender for compatibility matching).
1139
+
1140
+ ```ts
1141
+ withConnectors(connectors: Record<string, ConnectorInput>): Shape
1142
+ ```
1143
+
1144
+ #### `connectorNames()` — List all connector names on this shape.
1145
+
1146
+ ```ts
1147
+ connectorNames(): string[]
1148
+ ```
1149
+
1150
+ #### `connectorsByType()` — Get all connectors of a given type.
1151
+
1152
+ ```ts
1153
+ connectorsByType(type: string): Array<{ name: string; port: PortDef; }>
1154
+ ```
1155
+
1156
+ #### `connectorDistance()` — Distance between two connector origins on this shape.
1157
+
1158
+ ```ts
1159
+ connectorDistance(nameA: string, nameB: string): number
1160
+ ```
1161
+
1162
+ #### `connectorMeasurements()` — Get measurements metadata from a connector.
1163
+
1164
+ ```ts
1165
+ connectorMeasurements(name: string): Record<string, number | string>
1166
+ ```
1167
+
1168
+ #### `matchTo()` — Position this shape by matching connectors to a target.
1169
+
1170
+ Overloads:
1171
+
1172
+ - Single pair: `matchTo(target, selfConn, targetConn, options?)`
1173
+ - Dictionary (same target): `matchTo(target, { selfConn: targetConn, ... }, options?)`
1174
+ - Multi-target: `matchTo([ [target1, selfConn1, targetConn1], ... ], options?)`
1175
+
1176
+ ```ts
1177
+ matchTo(targetOrPairs: Shape | MatchTarget | Array<[ Shape | MatchTarget, string, string ]>, selfConnOrDict?: string | Record<string, string>, targetConnOrOptions?: string | MatchToOptions, maybeOptions?: MatchToOptions): Shape
1178
+ ```
1179
+
1180
+ **References**
1181
+
1182
+ #### `withReferences()` — Attach named placement references that survive normal transforms and imports.
1183
+
1184
+ ```ts
1185
+ withReferences(refs: PlacementReferenceInput): Shape
1186
+ ```
1187
+
1188
+ #### `referenceNames()` — List named placement references carried by this shape.
1189
+
1190
+ ```ts
1191
+ referenceNames(kind?: PlacementReferenceKind): string[]
1192
+ ```
1193
+
1194
+ #### `referencePoint()` — Resolve a named placement reference or built-in anchor to a 3D point.
1195
+
1196
+ ```ts
1197
+ referencePoint(ref: PlacementAnchorLike): [ number, number, number ]
1198
+ ```
1199
+
1200
+ #### `withPorts()` — Deprecated alias for `withConnectors()`.
1201
+
1202
+ ```ts
1203
+ withPorts(ports: Record<string, PortInput>): Shape
1204
+ ```
1205
+
1206
+ #### `portNames()` — Deprecated alias for `connectorNames()`.
1207
+
1208
+ ```ts
1209
+ portNames(): string[]
1210
+ ```
1211
+
1212
+ **Measurement**
1213
+
1214
+ #### `boundingBox()` — Get the axis-aligned bounding box as { min: [x,y,z], max: [x,y,z] }.
1215
+
1216
+ ```ts
1217
+ boundingBox(): ShapeRuntimeBounds
1218
+ ```
1219
+
1220
+ #### `volume()` — Volume in mm cubed.
1221
+
1222
+ ```ts
1223
+ volume(): number
1224
+ ```
1225
+
1226
+ #### `surfaceArea()` — Surface area in mm squared.
1227
+
1228
+ ```ts
1229
+ surfaceArea(): number
1230
+ ```
1231
+
1232
+ #### `isEmpty()` — True if the shape contains no geometry.
1233
+
1234
+ ```ts
1235
+ isEmpty(): boolean
1236
+ ```
1237
+
1238
+ #### `numBodies()` — Number of disconnected solid bodies in this shape.
1239
+
1240
+ ```ts
1241
+ numBodies(): number
1242
+ ```
1243
+
1244
+ #### `numTri()` — Triangle count of the mesh representation.
1245
+
1246
+ ```ts
1247
+ numTri(): number
1248
+ ```
1249
+
1250
+ **Other**
1251
+
1252
+ #### `clone()` — Return a new Shape wrapper for explicit duplication in scripts.
1253
+
1254
+ ```ts
1255
+ clone(): Shape
1256
+ ```
1257
+
1258
+ #### `geometryInfo()` — Inspect which backend/representation produced this solid.
1259
+
1260
+ ```ts
1261
+ geometryInfo(): GeometryInfo
1262
+ ```
1263
+
1264
+ #### `getMesh()` — Extract triangle mesh for Three.js rendering
1265
+
1266
+ ```ts
1267
+ getMesh(): ShapeRuntimeMesh
1268
+ ```
1269
+
1270
+ #### `slice()` — Slice the runtime solid by a plane normal to local Z at the given offset.
1271
+
1272
+ ```ts
1273
+ slice(offset?: number): any
1274
+ ```
1275
+
1276
+ #### `project()` — Orthographically project the runtime solid onto the local XY plane.
1277
+
1278
+ ```ts
1279
+ project(): any
1280
+ ```
1281
+
1282
+ ### `Transform`
1283
+
1284
+ #### `identity()` — Return the identity transform.
1285
+
1286
+ ```ts
1287
+ static identity(): Transform
1288
+ ```
1289
+
1290
+ #### `from()` — Wrap an existing `Transform` or raw 4x4 matrix as a `Transform`.
1291
+
1292
+ ```ts
1293
+ static from(input: TransformInput): Transform
1294
+ ```
1295
+
1296
+ #### `translation()` — Create a translation transform.
1297
+
1298
+ ```ts
1299
+ static translation(x: number, y: number, z: number): Transform
1300
+ ```
1301
+
1302
+ #### `scale()` — Create a uniform or per-axis scale transform.
1303
+
1304
+ ```ts
1305
+ static scale(v: number | Vec3): Transform
1306
+ ```
1307
+
1308
+ #### `rotationAxis()` — Create a rotation around an arbitrary axis, optionally about a pivot.
1309
+
1310
+ ```ts
1311
+ static rotationAxis(axis: Vec3, angleDeg: number, pivot?: Vec3): Transform
1312
+ ```
1313
+
1314
+ #### `rotateAroundTo()` — Solve the rotation needed to move one point onto a target line or plane.
1315
+
1316
+ ```ts
1317
+ static rotateAroundTo(axis: Vec3, pivot: Vec3, movingPoint: Vec3, targetPoint: Vec3, options?: RotateAroundToOptions): Transform
1318
+ ```
1319
+
1320
+ #### `mul()` — Compose transforms in chain order: `a.mul(b)` applies `a`, then `b`.
1321
+
1322
+ ```ts
1323
+ mul(other: TransformInput): Transform
1324
+ ```
1325
+
1326
+ #### `translate()` — Translate after the current transform.
1327
+
1328
+ ```ts
1329
+ translate(x: number, y: number, z: number): Transform
1330
+ ```
1331
+
1332
+ #### `rotateAxis()` — Rotate after the current transform.
1333
+
1334
+ ```ts
1335
+ rotateAxis(axis: Vec3, angleDeg: number, pivot?: Vec3): Transform
1336
+ ```
1337
+
1338
+ #### `inverse()` — Return the inverse transform.
1339
+
1340
+ ```ts
1341
+ inverse(): Transform
1342
+ ```
1343
+
1344
+ #### [`point()`](/docs/sketch#point) — Transform a point using homogeneous coordinates.
1345
+
1346
+ ```ts
1347
+ point(p: Vec3): Vec3
1348
+ ```
1349
+
1350
+ #### `vector()` — Transform a direction vector without translation.
1351
+
1352
+ ```ts
1353
+ vector(v: Vec3): Vec3
1354
+ ```
1355
+
1356
+ #### `toArray()` — Return the transform as a raw 4x4 matrix array.
1357
+
1358
+ ```ts
1359
+ toArray(): Mat4
1360
+ ```
1361
+
1362
+ ### `ShapeGroup`
1363
+
1364
+ **Properties:**
1365
+
1366
+ | Property | Type | Description |
1367
+ |----------|------|-------------|
1368
+ | `children` | `GroupChild[]` | — |
1369
+ | `childNames` | `Array<string | undefined>` | — |
1370
+
1371
+ **Children**
1372
+
1373
+ #### `child()` — Return the named child by name. Throws if not found. Useful when importing a multipart group and working on components individually.
1374
+
1375
+ ```ts
1376
+ child(name: string): GroupChild
1377
+ ```
1378
+
1379
+ #### `childName()` — Return the optional name of the child at `index`.
1380
+
1381
+ ```ts
1382
+ childName(index: number): string | undefined
1383
+ ```
1384
+
1385
+ **Transforms**
1386
+
1387
+ #### `translate()` — Move the entire group by (x, y, z). All children move together as a unit.
1388
+
1389
+ ```ts
1390
+ translate(x: number, y: number, z: number): ShapeGroup
1391
+ ```
1392
+
1393
+ #### `moveTo()` — Move the group so its bounding-box min corner lands at the given coordinate.
1394
+
1395
+ ```ts
1396
+ moveTo(x: number, y: number, z: number): ShapeGroup
1397
+ ```
1398
+
1399
+ #### `moveToLocal()` — Move the group relative to another part's bounding-box min corner.
1400
+
1401
+ ```ts
1402
+ moveToLocal(target: Shape | ShapeGroup, x: number, y: number, z: number): ShapeGroup
1403
+ ```
1404
+
1405
+ #### `rotate()` — Rotate the group around an arbitrary axis through the origin.
1406
+
1407
+ ```ts
1408
+ rotate(axis: [ number, number, number ], angleDeg: number, options?: { pivot?: [ number, number, number ]; }): ShapeGroup
1409
+ ```
1410
+
1411
+ #### `rotateX()` — Rotate the group around the X axis.
1412
+
1413
+ ```ts
1414
+ rotateX(angleDeg: number, options?: { pivot?: [ number, number, number ]; }): ShapeGroup
1415
+ ```
1416
+
1417
+ #### `rotateY()` — Rotate the group around the Y axis.
1418
+
1419
+ ```ts
1420
+ rotateY(angleDeg: number, options?: { pivot?: [ number, number, number ]; }): ShapeGroup
1421
+ ```
1422
+
1423
+ #### `rotateZ()` — Rotate the group around the Z axis.
1424
+
1425
+ ```ts
1426
+ rotateZ(angleDeg: number, options?: { pivot?: [ number, number, number ]; }): ShapeGroup
1427
+ ```
1428
+
1429
+ #### `rotateAroundAxis()` — Rotate around an arbitrary axis, optionally through a pivot point.
1430
+
1431
+ ```ts
1432
+ rotateAroundAxis(axis: [ number, number, number ], angleDeg: number, pivot?: [ number, number, number ]): ShapeGroup
1433
+ ```
1434
+
1435
+ #### `rotateAroundTo()` — Rotate around an axis until a moving point reaches the target line/plane defined by the axis and target point. ShapeGroup string points use built-in anchors only.
1436
+
1437
+ ```ts
1438
+ rotateAroundTo(axis: [ number, number, number ], pivot: [ number, number, number ], movingPoint: Anchor3D | [ number, number, number ], targetPoint: Anchor3D | [ number, number, number ], options?: RotateAroundToOptions): ShapeGroup
1439
+ ```
1440
+
1441
+ #### `pointAlong()` — Reorient the group so its local Z axis points along `direction`.
1442
+
1443
+ ```ts
1444
+ pointAlong(direction: [ number, number, number ]): ShapeGroup
1445
+ ```
1446
+
1447
+ #### `transform()` — Apply a 4x4 transform matrix or `Transform` to all 3D children.
1448
+
1449
+ ```ts
1450
+ transform(m: Mat4 | Transform): ShapeGroup
1451
+ ```
1452
+
1453
+ #### `scale()` — Scale uniformly or per-axis from the group's bounding-box center.
1454
+
1455
+ ```ts
1456
+ scale(v: number | [ number, number, number ]): ShapeGroup
1457
+ ```
1458
+
1459
+ #### `scaleAround()` — Scale uniformly or per-axis from an explicit pivot point.
1460
+
1461
+ ```ts
1462
+ scaleAround(pivot: [ number, number, number ], v: number | [ number, number, number ]): ShapeGroup
1463
+ ```
1464
+
1465
+ #### `mirror()` — Mirror across a plane through the group's bounding-box center.
1466
+
1467
+ ```ts
1468
+ mirror(normal: [ number, number, number ]): ShapeGroup
1469
+ ```
1470
+
1471
+ #### `mirrorThrough()` — Mirror across a plane through an explicit point.
1472
+
1473
+ ```ts
1474
+ mirrorThrough(point: [ number, number, number ], normal: [ number, number, number ]): ShapeGroup
1475
+ ```
1476
+
1477
+ **Placement**
1478
+
1479
+ #### `placeReference()` — Translate the group so the given anchor or reference lands on the target coordinate.
1480
+
1481
+ Accepts any built-in anchor name (`'bottom'`, `'center'`, `'top-front-left'`, etc.) or a custom placement reference attached via `withReferences()`.
1482
+
1483
+ ```javascript
1484
+ // Ground a group — put its bottom at Z = 0
1485
+ assembly.placeReference('bottom', [0, 0, 0])
1486
+
1487
+ // Use a custom reference from a multi-file part
1488
+ const placed = require('./bracket-assembly.forge.js').group
1489
+ .placeReference('mountCenter', [0, 0, 50]);
1490
+ ```
1491
+
1492
+ ```ts
1493
+ placeReference(ref: PlacementAnchorLike, target: [ number, number, number ], offset?: [ number, number, number ]): ShapeGroup
1494
+ ```
1495
+
1496
+ #### `attachTo()` — Attach this group to a face or anchor on another part.
1497
+
1498
+ `targetAnchor` can be a built-in anchor name or a custom reference name on the target. `selfAnchor` selects the anchor on this group to align.
1499
+
1500
+ ```ts
1501
+ attachTo(target: Shape | ShapeGroup, targetAnchor: Anchor3D | string, selfAnchor?: Anchor3D, offset?: [ number, number, number ]): ShapeGroup
1502
+ ```
1503
+
1504
+ #### `onFace()` — Place this group on a face of a parent shape. See Shape.onFace() for full documentation.
1505
+
1506
+ ```ts
1507
+ onFace(parent: Shape | ShapeGroup, face: "front" | "back" | "left" | "right" | "top" | "bottom", opts?: { u?: number; v?: number; protrude?: number; }): ShapeGroup
1508
+ ```
1509
+
1510
+ **Connectors**
1511
+
1512
+ #### `withConnectors()` — Attach named connectors — attachment points that survive transforms. Connectors can be bare (position + orientation) or typed (with connectorType/gender for compatibility matching).
1513
+
1514
+ ```ts
1515
+ withConnectors(connectors: Record<string, ConnectorInput>): ShapeGroup
1516
+ ```
1517
+
1518
+ #### `connectorNames()` — List all connector names, including "ChildName.connectorName" from named children.
1519
+
1520
+ ```ts
1521
+ connectorNames(): string[]
1522
+ ```
1523
+
1524
+ #### `connectorsByType()` — Get all connectors of a given type, including from named children.
1525
+
1526
+ ```ts
1527
+ connectorsByType(type: string): Array<{ name: string; port: PortDef; }>
1528
+ ```
1529
+
1530
+ #### `connectorDistance()` — Distance between two connector origins on this group (supports dotted child paths).
1531
+
1532
+ ```ts
1533
+ connectorDistance(nameA: string, nameB: string): number
1534
+ ```
1535
+
1536
+ #### `connectorMeasurements()` — Get measurements metadata from a connector (supports dotted child paths).
1537
+
1538
+ ```ts
1539
+ connectorMeasurements(name: string): Record<string, number | string>
1540
+ ```
1541
+
1542
+ #### `matchTo()` — Position this group by matching connectors to a target. Connector names support dotted paths into named children: "ChildName.connectorName".
1543
+
1544
+ Overloads:
1545
+
1546
+ - Single pair: `matchTo(target, selfConn, targetConn, options?)`
1547
+ - Dictionary (same target): `matchTo(target, { selfConn: targetConn, ... }, options?)`
1548
+ - Multi-target: `matchTo([ [target1, selfConn1, targetConn1], ... ], options?)`
1549
+
1550
+ ```ts
1551
+ matchTo(targetOrPairs: Shape | ShapeGroup | Array<[ Shape | ShapeGroup, string, string ]>, selfConnOrDict?: string | Record<string, string>, targetConnOrOptions?: string | MatchToOptions, maybeOptions?: MatchToOptions): ShapeGroup
1552
+ ```
1553
+
1554
+ **References**
1555
+
1556
+ #### `withReferences()` — Attach named placement references to this group. References survive normal transforms (translate/rotate/scale/mirror/transform).
1557
+
1558
+ ```javascript
1559
+ const bracket = group(
1560
+ { name: 'Left', shape: leftShape },
1561
+ { name: 'Right', shape: rightShape },
1562
+ ).withReferences({
1563
+ points: { mountCenter: [0, 0, 0] },
1564
+ });
1565
+ ```
1566
+
1567
+ ```ts
1568
+ withReferences(refs: PlacementReferenceInput): ShapeGroup
1569
+ ```
1570
+
1571
+ #### `referenceNames()` — List named placement references carried by this group.
1572
+
1573
+ ```ts
1574
+ referenceNames(kind?: PlacementReferenceKind): string[]
1575
+ ```
1576
+
1577
+ #### `referencePoint()` — Resolve a named placement reference or built-in Anchor3D to a 3D point. Named refs take priority over built-in anchors.
1578
+
1579
+ ```ts
1580
+ referencePoint(ref: PlacementAnchorLike): [ number, number, number ]
1581
+ ```
1582
+
1583
+ #### `withPorts()` — Backward-compatible alias for `withConnectors()`.
1584
+
1585
+ ```ts
1586
+ withPorts(ports: Record<string, PortInput>): ShapeGroup
1587
+ ```
1588
+
1589
+ #### `portNames()` — Backward-compatible alias for `connectorNames()`.
1590
+
1591
+ ```ts
1592
+ portNames(): string[]
1593
+ ```
1594
+
1595
+ **Other**
1596
+
1597
+ #### `clone()` — Return a deep-cloned ShapeGroup tree (refs copied).
1598
+
1599
+ ```ts
1600
+ clone(): ShapeGroup
1601
+ ```
1602
+
1603
+ #### `boundingBox()` — Return the combined 3D bounding box of all children.
1604
+
1605
+ ```ts
1606
+ boundingBox(): { min: [ number, number, number ]; max: [ number, number, number ]; }
1607
+ ```
1608
+
1609
+ #### `color()` — Return a copy of the group with the given display color applied to each child.
1610
+
1611
+ ```ts
1612
+ color(hex: string): ShapeGroup
1613
+ ```
1614
+
1615
+ ### `SurfacePattern`
1616
+
1617
+ **Properties:**
1618
+
1619
+ | Property | Type | Description |
1620
+ |----------|------|-------------|
1621
+ | `body` | `string` | Function body: receives (u, v) in surface mm, returns height displacement. |
1622
+ | `constants` | `Record<string, number>` | Named constants injected into the function. |
722
1623
 
723
1624
  ---
724
1625
 
@@ -734,19 +1635,19 @@ Supports transforms (translate, rotate, scale, mirror, transform, rotateAround,
734
1635
  - `greaterThan(label: string, actual: number, min: number, message?: string): void` — Check that actual > min.
735
1636
  - `lessThan(label: string, actual: number, max: number, message?: string): void` — Check that actual < max.
736
1637
  - `inRange(label: string, actual: number, min: number, max: number, message?: string): void` — Check that min <= actual <= max.
737
- - `centersCoincide(label: string, a: ShapeLike$1, b: ShapeLike$1, tolerance?: number): void` — Check that the bounding-box centers of two shapes coincide within tolerance (mm).
738
- - `notColliding(label: string, a: ShapeLike$1, b: ShapeLike$1, searchLength?: number): void` — Check that two shapes do not collide (minGap > 0).
739
- - `minClearance(label: string, a: ShapeLike$1, b: ShapeLike$1, minGap: number, searchLength?: number): void` — Check that a minimum clearance gap exists between two shapes.
1638
+ - `centersCoincide(label: string, a: ShapeLike, b: ShapeLike, tolerance?: number): void` — Check that the bounding-box centers of two shapes coincide within tolerance (mm).
1639
+ - `notColliding(label: string, a: ShapeLike, b: ShapeLike, searchLength?: number): void` — Check that two shapes do not collide (minGap > 0).
1640
+ - `minClearance(label: string, a: ShapeLike, b: ShapeLike, minGap: number, searchLength?: number): void` — Check that a minimum clearance gap exists between two shapes.
740
1641
  - `parallel(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number): void` — Check that two face normals are parallel (within toleranceDeg degrees).
741
1642
  - `perpendicular(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number): void` — Check that two face normals are perpendicular (within toleranceDeg degrees).
742
1643
  - `coplanar(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number, toleranceMm?: number): void` — Check that a face is coplanar with (same plane as) another face, meaning they are parallel AND their centers lie on the same plane.
743
1644
  - `faceAt(label: string, face: FaceRefLike, expectedPos: [ number` — Check that a face center lies at a specific position (within toleranceMm).
744
1645
  - `sameDirection(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number): void` — Check that two face normals point in the same direction (not antiparallel). Stricter than parallel — both |angle| AND sign must match.
745
- - `isEmpty(label: string, shape: ShapeLike$1, message?: string): void` — Check that a shape is empty.
746
- - `notEmpty(label: string, shape: ShapeLike$1, message?: string): void` — Check that a shape is NOT empty.
747
- - `volumeApprox(label: string, shape: ShapeLike$1, expected: number, tolerance?: number): void` — Check that a shape's volume is approximately equal to expected (mm³).
748
- - `areaApprox(label: string, shape: ShapeLike$1, expected: number, tolerance?: number): void` — Check that a shape's surface area is approximately equal to expected (mm²).
749
- - `boundingBoxSize(label: string, shape: ShapeLike$1, expectedSize: [ number` — Check that a shape's bounding box has approximately the given size.
1646
+ - `isEmpty(label: string, shape: ShapeLike, message?: string): void` — Check that a shape is empty.
1647
+ - `notEmpty(label: string, shape: ShapeLike, message?: string): void` — Check that a shape is NOT empty.
1648
+ - `volumeApprox(label: string, shape: ShapeLike, expected: number, tolerance?: number): void` — Check that a shape's volume is approximately equal to expected (mm³).
1649
+ - `areaApprox(label: string, shape: ShapeLike, expected: number, tolerance?: number): void` — Check that a shape's surface area is approximately equal to expected (mm²).
1650
+ - `boundingBoxSize(label: string, shape: ShapeLike, expectedSize: [ number` — Check that a shape's bounding box has approximately the given size.
750
1651
 
751
1652
  ### `Constraint`
752
1653