forgecad 0.6.3 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/README.md +3 -12
  2. package/dist/assets/{AdminPage-CeqCUUgu.js → AdminPage-D4bocK4E.js} +250 -151
  3. package/dist/assets/{BlogPage-P_AJP0v9.js → BlogPage-CJEXL_zJ.js} +94 -70
  4. package/dist/assets/{DocsPage-CKRV2iq2.js → DocsPage-D3A_g8V3.js} +329 -163
  5. package/dist/assets/{EditorApp-CnC2k4cW.css → EditorApp-BWYUSpUN.css} +590 -136
  6. package/dist/assets/EditorApp-Cihhqcsq.js +11692 -0
  7. package/dist/assets/{EmbedViewer-DBlzmQ5i.js → EmbedViewer-kWjKaC_t.js} +2 -4
  8. package/dist/assets/LandingPageProofDriven-Bg2IUc3l.css +856 -0
  9. package/dist/assets/LandingPageProofDriven-DXkKlyhI.js +601 -0
  10. package/dist/assets/PricingPage-BsU5vzEx.js +232 -0
  11. package/dist/assets/{SettingsPage-BqCh9JcC.js → SettingsPage-PqvpAKIs.js} +129 -5
  12. package/dist/assets/{evalWorker-Ql-aKwLA.js → evalWorker-C-hzNUMy.js} +8949 -3161
  13. package/dist/assets/{Viewport-CoB46f5R.js → index-Pz321YAt.js} +38382 -7501
  14. package/dist/assets/{index-2hfs_ub0.css → index-ay13WNfa.css} +726 -53
  15. package/dist/assets/{javascript-DCxGoE5Y.js → javascript-DAl8Gmyo.js} +1 -1
  16. package/dist/assets/{manifold-CqNMHHKO.js → manifold-BcbjWLIo.js} +4 -3
  17. package/dist/assets/{manifold-Cce9wRFz.js → manifold-DBckbFgx.js} +1 -1
  18. package/dist/assets/{manifold-D6BeHIOo.js → manifold-O2AAGXyj.js} +1 -1
  19. package/dist/assets/{reportWorker-sFEFonXf.js → reportWorker-Dxr-5A7w.js} +8760 -3559
  20. package/dist/assets/{vendor-react-Dt7-aaJH.js → vendor-react-CG3i_wp0.js} +65 -8
  21. package/dist/docs/index.html +2 -2
  22. package/dist/docs-raw/CLI.md +341 -718
  23. package/dist/docs-raw/generated/assembly.md +699 -112
  24. package/dist/docs-raw/generated/concepts.md +1834 -1346
  25. package/dist/docs-raw/generated/core.md +1012 -1059
  26. package/dist/docs-raw/generated/curves.md +759 -116
  27. package/dist/docs-raw/generated/lib.md +43 -748
  28. package/dist/docs-raw/generated/output.md +139 -245
  29. package/dist/docs-raw/generated/sdf.md +208 -0
  30. package/dist/docs-raw/generated/sheet-metal.md +473 -21
  31. package/dist/docs-raw/generated/sketch.md +1518 -362
  32. package/dist/docs-raw/generated/viewport.md +368 -299
  33. package/dist/docs-raw/generated/wood.md +104 -0
  34. package/dist/index.html +2 -2
  35. package/dist/landing/proof-ams-adapter.png +0 -0
  36. package/dist/landing/proof-bolt-and-nut.png +0 -0
  37. package/dist/landing/proof-fillet-enclosure.png +0 -0
  38. package/dist/landing/proof-glasses.png +0 -0
  39. package/dist/landing/proof-gyroid.png +0 -0
  40. package/dist/sitemap.xml +6 -6
  41. package/dist-cli/forgecad.js +12321 -5700
  42. package/dist-cli/forgecad.js.map +1 -0
  43. package/dist-cli/solver-46FFSK2U.js +363 -0
  44. package/dist-cli/solver-46FFSK2U.js.map +1 -0
  45. package/dist-skill/CONTEXT.md +4890 -6302
  46. package/dist-skill/SKILL-dev.md +22 -66
  47. package/dist-skill/SKILL.md +20 -59
  48. package/dist-skill/docs/API/core/concepts.md +37 -92
  49. package/dist-skill/docs/CLI.md +341 -718
  50. package/dist-skill/docs/generated/assembly.md +699 -112
  51. package/dist-skill/docs/generated/core.md +1012 -1059
  52. package/dist-skill/docs/generated/curves.md +759 -116
  53. package/dist-skill/docs/generated/lib.md +43 -748
  54. package/dist-skill/docs/generated/output.md +139 -245
  55. package/dist-skill/docs/generated/sdf.md +208 -0
  56. package/dist-skill/docs/generated/sheet-metal.md +473 -21
  57. package/dist-skill/docs/generated/sketch.md +1518 -362
  58. package/dist-skill/docs/generated/viewport.md +368 -299
  59. package/dist-skill/docs/generated/wood.md +104 -0
  60. package/dist-skill/docs/guides/coordinate-system.md +11 -17
  61. package/dist-skill/docs/guides/geometry-conventions.md +13 -70
  62. package/dist-skill/docs/guides/joint-design.md +78 -0
  63. package/dist-skill/docs/guides/modeling-recipes.md +22 -195
  64. package/dist-skill/docs/guides/positioning.md +88 -147
  65. package/dist-skill/docs-dev/API/core/concepts.md +78 -0
  66. package/dist-skill/docs-dev/CLI.md +488 -0
  67. package/dist-skill/{docs → docs-dev}/blueprint-first.md +5 -0
  68. package/dist-skill/{docs → docs-dev}/coding-best-practices.md +6 -8
  69. package/dist-skill/{docs → docs-dev}/coding.md +2 -4
  70. package/dist-skill/docs-dev/component-model.md +164 -0
  71. package/dist-skill/docs-dev/generated/assembly.md +779 -0
  72. package/dist-skill/docs-dev/generated/core.md +1676 -0
  73. package/dist-skill/docs-dev/generated/curves.md +855 -0
  74. package/dist-skill/docs-dev/generated/lib.md +55 -0
  75. package/dist-skill/docs-dev/generated/output.md +234 -0
  76. package/dist-skill/docs-dev/generated/sdf.md +208 -0
  77. package/dist-skill/docs-dev/generated/sheet-metal.md +506 -0
  78. package/dist-skill/docs-dev/generated/sketch.md +1753 -0
  79. package/dist-skill/docs-dev/generated/viewport.md +513 -0
  80. package/dist-skill/docs-dev/generated/wood.md +104 -0
  81. package/dist-skill/docs-dev/guides/coordinate-system.md +46 -0
  82. package/dist-skill/docs-dev/guides/geometry-conventions.md +52 -0
  83. package/dist-skill/docs-dev/guides/joint-design.md +78 -0
  84. package/dist-skill/docs-dev/guides/modeling-recipes.md +77 -0
  85. package/dist-skill/docs-dev/guides/positioning.md +151 -0
  86. package/dist-skill/{docs → docs-dev}/guides/skill-maintenance.md +21 -10
  87. package/dist-skill/{docs → docs-dev}/internals/compiler.md +5 -6
  88. package/dist-skill/{docs → docs-dev}/internals/constraint-solver-quality.md +0 -1
  89. package/dist-skill/{docs → docs-dev}/internals/constraint-solver.md +0 -1
  90. package/dist-skill/{docs → docs-dev}/internals/sketch-2d-pipeline.md +2 -3
  91. package/examples/api/attachTo-basics.forge.js +8 -8
  92. package/examples/api/bill-of-materials.forge.js +9 -9
  93. package/examples/api/bolt-pattern.forge.js +5 -5
  94. package/examples/api/boolean-operations.forge.js +5 -5
  95. package/examples/api/bounding-box-visualizer.forge.js +3 -3
  96. package/examples/api/clone-duplicate.forge.js +2 -2
  97. package/examples/api/colors-union-vs-array.forge.js +6 -6
  98. package/examples/api/connector-assembly.forge.js +8 -6
  99. package/examples/api/connector-basics.forge.js +7 -7
  100. package/examples/api/constrained-sketch-mechanical.forge.js +4 -4
  101. package/examples/api/elbow-test.forge.js +3 -3
  102. package/examples/api/extrude-options.forge.js +8 -14
  103. package/examples/api/feature-created-faces.forge.js +6 -10
  104. package/examples/api/fillet-showcase.forge.js +2 -2
  105. package/examples/api/folded-service-panel-cover.forge.js +2 -2
  106. package/examples/api/gears-tier1.forge.js +5 -5
  107. package/examples/api/group-test.forge.js +3 -3
  108. package/examples/api/group-vs-union.forge.js +1 -1
  109. package/examples/api/highlight-debug.forge.js +4 -0
  110. package/examples/api/js-module-pillars.js +1 -1
  111. package/examples/api/js-module-scene.js +2 -2
  112. package/examples/api/mesh-import-slats.forge.js +4 -4
  113. package/examples/api/patterns.forge.js +3 -3
  114. package/examples/api/pointAlong-orientation.forge.js +3 -3
  115. package/examples/api/profile-2020-b-slot6.forge.js +4 -5
  116. package/examples/api/route-perimeter-flange.forge.js +1 -1
  117. package/examples/api/sdf-rover-demo.forge.js +10 -10
  118. package/examples/api/sketch-on-face-demo.forge.js +2 -2
  119. package/examples/api/sketch-regions.forge.js +4 -4
  120. package/examples/api/sketch-rounding-strategies.forge.js +1 -1
  121. package/examples/api/smooth-curve-connections.forge.js +1 -1
  122. package/examples/api/transition-curves.forge.js +4 -4
  123. package/examples/api/variable-sweep-pure-sdf-test.forge.js +162 -0
  124. package/examples/api/variable-sweep-test.forge.js +2 -2
  125. package/examples/api/wood-joinery.forge.js +60 -0
  126. package/examples/compiler-corpus/enclosure-shell-cuts.forge.js +3 -3
  127. package/examples/compiler-corpus/fastener-plate-variants.forge.js +2 -2
  128. package/examples/constraints/01-fully-constrained-rect.forge.js +2 -2
  129. package/examples/constraints/02-underconstrained-triangle.forge.js +1 -1
  130. package/examples/constraints/03-redundant-constraints.forge.js +2 -2
  131. package/examples/constraints/05-parallel-with-linedistance.forge.js +2 -2
  132. package/examples/constraints/06-complex-spectrogram.forge.js +1 -1
  133. package/examples/constraints/07-perpendicular-chain.forge.js +4 -4
  134. package/examples/constraints/08-symmetric-bracket.forge.js +4 -4
  135. package/examples/constraints/09-stress-spiral.forge.js +1 -1
  136. package/examples/constraints/10-stress-honeycomb.forge.js +1 -1
  137. package/examples/constraints/11-surface-grid.forge.js +2 -2
  138. package/examples/constraints/12-surface-nested.forge.js +4 -4
  139. package/examples/constraints/13-surface-complex.forge.js +1 -1
  140. package/examples/exact-arc-housing.forge.js +12 -0
  141. package/examples/experiments/drone-arm.forge.js +53 -0
  142. package/examples/furniture/adjustable-table.forge.js +15 -15
  143. package/examples/furniture/bathroom.forge.js +26 -26
  144. package/examples/furniture/chair.forge.js +13 -13
  145. package/examples/furniture/picture-frame.forge.js +6 -6
  146. package/examples/furniture/shoe-rack-doors.forge.js +8 -8
  147. package/examples/furniture/shoe-rack.forge.js +7 -7
  148. package/examples/furniture/table-lamp.forge.js +8 -8
  149. package/examples/gcode/lissajous-vase.forge.js +4 -4
  150. package/examples/gcode/math-surface.forge.js +3 -3
  151. package/examples/gcode/parametric-vase.forge.js +4 -4
  152. package/examples/gcode/spiral-tower.forge.js +4 -4
  153. package/examples/generative/crystal-growth.forge.js +9 -9
  154. package/examples/generative/frost-spires.forge.js +9 -9
  155. package/examples/generative/golden-spiral-tower.forge.js +11 -11
  156. package/examples/generative/molten-forge.forge.js +6 -6
  157. package/examples/generative/neon-coral.forge.js +7 -7
  158. package/examples/mechanical/3d-printer.forge.js +37 -37
  159. package/examples/mechanical/5-finger-robot-hand.forge.js +19 -19
  160. package/examples/mechanical/airplane-propeller.forge.js +9 -9
  161. package/examples/mechanical/bolt-and-nut.forge.js +10 -10
  162. package/examples/mechanical/door-with-hinges.forge.js +7 -7
  163. package/examples/mechanical/fillet-enclosure.forge.js +15 -11
  164. package/examples/mechanical/headphone-hanger-v2.forge.js +11 -11
  165. package/examples/mechanical/robot_hand.forge.js +24 -24
  166. package/examples/mechanical/robot_hand_2.forge.js +26 -26
  167. package/examples/nurbs-surface.forge.js +8 -0
  168. package/examples/nurbs-tube.forge.js +7 -0
  169. package/examples/products/bottle.forge.js +8 -8
  170. package/examples/products/chess-set.forge.js +25 -25
  171. package/examples/products/classical-piano.forge.js +20 -20
  172. package/examples/products/clock.forge.js +33 -33
  173. package/examples/products/cup.forge.js +5 -5
  174. package/examples/products/iphone.forge.js +20 -20
  175. package/examples/products/laptop.forge.js +24 -24
  176. package/examples/products/laser-cut-box.forge.js +6 -6
  177. package/examples/products/laser-cut-tray.forge.js +6 -6
  178. package/examples/products/liquid-soap-dispenser.forge.js +23 -23
  179. package/examples/products/origami-fish.forge.js +14 -12
  180. package/examples/products/spiderman-cake.forge.js +6 -6
  181. package/examples/shelf/container.forge.js +5 -5
  182. package/examples/shelf/shelf-unit.forge.js +6 -6
  183. package/examples/toolbox/bolted-joint.forge.js +7 -7
  184. package/package.json +9 -4
  185. package/dist/assets/EditorApp-B-vQvgam.js +0 -9888
  186. package/dist/assets/LandingPage-C5n9hDXI.js +0 -322
  187. package/dist/assets/PublishedModelPage-Dt7PCVBj.js +0 -146
  188. package/dist/assets/__vite-browser-external-CURh0WXD.js +0 -8
  189. package/dist/assets/deserializeRunResult-BLAFoiE0.js +0 -19365
  190. package/dist/assets/index-1CYp3zUp.js +0 -1455
  191. package/dist-skill/docs/API/API.md +0 -1666
  192. package/dist-skill/docs/API/README.md +0 -37
  193. package/dist-skill/docs/API/assembly/assembly.md +0 -617
  194. package/dist-skill/docs/API/core/edge-queries.md +0 -130
  195. package/dist-skill/docs/API/core/parameters.md +0 -122
  196. package/dist-skill/docs/API/core/reserved-terms.md +0 -137
  197. package/dist-skill/docs/API/core/sdf.md +0 -326
  198. package/dist-skill/docs/API/core/skill-cli.md +0 -194
  199. package/dist-skill/docs/API/core/skill-guide.md +0 -205
  200. package/dist-skill/docs/API/core/specs.md +0 -186
  201. package/dist-skill/docs/API/core/topology.md +0 -372
  202. package/dist-skill/docs/API/entities.md +0 -268
  203. package/dist-skill/docs/API/output/bom.md +0 -58
  204. package/dist-skill/docs/API/output/brep-export.md +0 -87
  205. package/dist-skill/docs/API/output/dimensions.md +0 -67
  206. package/dist-skill/docs/API/output/export.md +0 -110
  207. package/dist-skill/docs/API/output/gcode.md +0 -195
  208. package/dist-skill/docs/API/runtime/viewport.md +0 -420
  209. package/dist-skill/docs/API/sheet-metal/sheet-metal.md +0 -185
  210. package/dist-skill/docs/API/sketch/anchor.md +0 -37
  211. package/dist-skill/docs/API/sketch/booleans.md +0 -91
  212. package/dist-skill/docs/API/sketch/core.md +0 -73
  213. package/dist-skill/docs/API/sketch/extrude.md +0 -62
  214. package/dist-skill/docs/API/sketch/on-face.md +0 -104
  215. package/dist-skill/docs/API/sketch/operations.md +0 -78
  216. package/dist-skill/docs/API/sketch/path.md +0 -75
  217. package/dist-skill/docs/API/sketch/primitives.md +0 -146
  218. package/dist-skill/docs/API/sketch/regions.md +0 -80
  219. package/dist-skill/docs/API/sketch/text.md +0 -108
  220. package/dist-skill/docs/API/sketch/transforms.md +0 -65
  221. package/dist-skill/docs/API/toolbox/fasteners.md +0 -129
  222. package/dist-skill/docs/INDEX.md +0 -94
  223. package/dist-skill/docs/RELEASING.md +0 -55
  224. package/dist-skill/docs/cli-monetization.md +0 -111
  225. package/dist-skill/docs/deployment.md +0 -281
  226. package/dist-skill/docs/generated/concepts.md +0 -2112
  227. package/dist-skill/docs/internals/shape-from-slices.md +0 -152
  228. package/dist-skill/docs/platform/admin.md +0 -45
  229. package/dist-skill/docs/platform/architecture.md +0 -79
  230. package/dist-skill/docs/platform/auth.md +0 -110
  231. package/dist-skill/docs/platform/email.md +0 -67
  232. package/dist-skill/docs/platform/projects.md +0 -111
  233. package/dist-skill/docs/platform/sharing.md +0 -90
  234. package/dist-skill/docs/runbook.md +0 -345
@@ -0,0 +1,513 @@
1
+ ---
2
+ skill-group: viewport
3
+ skill-order: 100
4
+ ---
5
+
6
+ # Viewport & Runtime
7
+
8
+ Cut planes, exploded views, joint animations, and scene configuration.
9
+
10
+ ## Contents
11
+
12
+ - [Viewport & Runtime](#viewport-runtime) — `jointsView`, `explodeView`, `cutPlane`, `cutPlane`, `mock`, `scene`, `viewConfig`, `showLabels`, `highlight`, `highlight`, `highlight`, `highlight`, `highlight`, `highlight`, `highlight`, `highlight`
13
+ - [RouteBuilder](#routebuilder)
14
+ - [route](#route)
15
+
16
+ ## Functions
17
+
18
+ ### Viewport & Runtime
19
+
20
+ #### `jointsView()` — Register viewport-only mechanism controls that animate returned objects without re-running the script.
21
+
22
+ Defines joints (revolute or prismatic), optional gear/rack couplings, and named animations. The viewport resolves transforms through the joint chain at display time — the script geometry is computed only once at rest pose.
23
+
24
+ **Critical:** Solve the assembly at **rest pose** (all animated joints = 0). The viewport applies `jointsView` transforms on top of the returned scene. If geometry is already solved at non-zero angles, animation will double-rotate everything.
25
+
26
+ ```js
27
+ // BAD — double rotation
28
+ const solved = mech.solve({ shoulder: 45, elbow: 30 });
29
+ jointsView({ joints: [{ name: 'shoulder', ... }] });
30
+ return solved;
31
+
32
+ // GOOD — rest pose, jointsView controls all posing
33
+ const solved = mech.solve({ shoulder: 0, elbow: 0 });
34
+ jointsView({
35
+ joints: [
36
+ { name: 'shoulder', child: 'Upper Arm', default: 45, ... },
37
+ { name: 'elbow', child: 'Forearm', parent: 'Upper Arm', default: 30, ... },
38
+ ],
39
+ });
40
+ return solved;
41
+ ```
42
+
43
+ **Pivot coordinates** are world-space positions of each joint origin at rest pose. For `addRevolute('shoulder', 'Base', 'Link', { frame: Transform.identity().translate(0, 0, 20) })` where "Base" is at world origin, the pivot is `[0, 0, 20]`.
44
+
45
+ **Fixed attachments** that must follow a parent during animation need a zero-angle revolute joint in the chain:
46
+
47
+ ```js
48
+ { name: 'EE_Follow', child: 'End Effector', parent: 'Last Link',
49
+ type: 'revolute', axis: [0, 0, 1], pivot: [linkLength, 0, 0],
50
+ min: 0, max: 0, default: 0 }
51
+ ```
52
+
53
+ Animation values are interpolated linearly between keyframes. ForgeCAD does **not** auto-wrap revolute values across `-180/180`. Keep keyframe values continuous — a `-180 -> 171` jump spins the part the long way around. Use `-180 -> -189` instead. Author high-speed multi-turn joints as accumulating angles (`0, 360, 720, ...`) with `continuous: true`.
54
+
55
+ **Tick-based keyframes:** Omit `at` from all keyframes to auto-distribute by tick weight:
56
+
57
+ ```js
58
+ keyframes: [
59
+ { ticks: 3, values: { Shoulder: 20 } }, // slow segment (3x weight)
60
+ { ticks: 1, values: { Shoulder: -10 } }, // fast segment (1x weight)
61
+ { values: { Shoulder: 20 } }, // last keyframe; ticks ignored
62
+ ]
63
+ // positions: 0, 0.75, 1.0
64
+ ```
65
+
66
+ Mixing explicit `at` and omitted `at` in the same animation is not allowed.
67
+
68
+ ```js
69
+ jointsView({
70
+ joints: [{
71
+ name: 'Shoulder', child: 'Upper Arm', parent: 'Base',
72
+ type: 'revolute', axis: [0, -1, 0], pivot: [0, 0, 46],
73
+ min: -30, max: 110, default: 15,
74
+ }],
75
+ animations: [{
76
+ name: 'Walk Cycle', duration: 1.6, loop: true,
77
+ keyframes: [
78
+ { values: { Shoulder: 20 } },
79
+ { values: { Shoulder: -10 } },
80
+ { values: { Shoulder: 20 } },
81
+ ],
82
+ }],
83
+ });
84
+ ```
85
+
86
+ ```ts
87
+ jointsView(options?: JointsViewOptions): void
88
+ ```
89
+
90
+ **`JointsViewOptions`**: `enabled?: boolean`, `joints?: JointViewInput[]`, `couplings?: JointViewCouplingInput[]`, `animations?: JointViewAnimationInput[]`, `defaultAnimation?: string`
91
+
92
+ **`JointViewInput`**: `name: string`, `child: string`, `parent?: string`, `type?: JointViewType`, `axis?: JointViewAxis`, `min?: number`, `max?: number`, `default?: number`, `unit?: string`, `hidden?: boolean`
93
+
94
+ `JointViewCouplingInput`: `{ joint: string, terms: JointViewCouplingTermInput[], offset?: number }`
95
+
96
+ `JointViewCouplingTermInput`: `{ joint: string, ratio?: number }`
97
+
98
+ `JointViewAnimationInput`: `{ name: string, duration?: number, loop?: boolean, continuous?: boolean, keyframes: JointViewAnimationKeyframeInput[] }`
99
+
100
+ **`JointViewAnimationKeyframeInput`**
101
+ - `at?: number` — Timeline position [0, 1]. If omitted from ALL keyframes, positions are auto-computed from tick weights.
102
+ - `ticks?: number` — Relative weight of the segment from this keyframe to the next (default 1). Only used in tick-based mode (when `at` is omitted). Last keyframe's ticks value is ignored.
103
+ - Also: `values: Record<string, number>`
104
+
105
+ #### `explodeView()` — Configure how the viewport explode slider offsets returned objects.
106
+
107
+ Offsets are resolved from the returned object tree, not a flat list. In `radial` mode each node follows its parent branch direction, then fans locally from the immediate parent center — nested assemblies peel apart level by level. In fixed-axis or fixed-vector modes, the branch follows that axis/vector but nested descendants fan out perpendicular by default.
108
+
109
+ Multiple calls merge — later values override earlier ones on a per-key basis. `byName` and `byPath` maps are merged entry-by-entry.
110
+
111
+ For programmatic explode applied before returning (without the slider), use `lib.explode()` instead.
112
+
113
+ ```js
114
+ explodeView({
115
+ amountScale: 1.2,
116
+ stages: [0.35, 0.8],
117
+ mode: 'radial',
118
+ byPath: { 'Drive/Shaft': { direction: [1, 0, 0], stage: 1.6 } },
119
+ });
120
+ ```
121
+
122
+ ```ts
123
+ explodeView(options?: ExplodeViewOptions): void
124
+ ```
125
+
126
+ **`ExplodeViewOptions`**
127
+
128
+ | Option | Type | Description |
129
+ |--------|------|-------------|
130
+ | `enabled?` | `boolean` | Set false to disable viewport explode offsets for this script output. |
131
+ | `amountScale?` | `number` | Scales the UI explode amount. Default: 1 |
132
+ | `stages?` | `number[]` | Per-depth stage multipliers (depth 1 = first level). If depth exceeds this array, the last value is reused. Default when omitted: reciprocal depth (1, 1/2, 1/3, ...) |
133
+ | `mode?` | `ExplodeViewDirection` | Global direction mode fallback. Default: 'radial' |
134
+ | `axisLock?` | `ExplodeAxis` | Global axis lock fallback. |
135
+ | `byName?` | `Record<string, ExplodeViewDirective>` | Per-object overrides by final object name. |
136
+ | `byPath?` | `Record<string, ExplodeViewDirective>` | Per-tree-path overrides using slash-separated object tree segments. |
137
+
138
+ **`ExplodeDirective`**
139
+ - `stage?: number` — Multiplier applied to `amount` for this node
140
+ - `direction?: ExplodeDirection` — Direction mode for this node
141
+ - `axisLock?: ExplodeAxis` — Optional axis lock after direction is resolved
142
+
143
+ #### `cutPlane()` — Define a named section plane for inspecting internal geometry.
144
+
145
+ Registers a cut plane that appears as a toggle in the viewport View Panel. When enabled, geometry on the positive side of the plane (the side the normal points toward) is clipped away, revealing the internal cross-section. The newly exposed section faces render with a hatched overlay; pre-existing coplanar boundary faces are left unhatched.
146
+
147
+ Planes are registered once per script run. The viewport toggle state (on/off) persists across parameter changes without re-running the script. The `exclude` option only works correctly when the excluded object names are stable across parameter changes.
148
+
149
+ Accepts two overloads: `cutPlane(name, normal, offset?, options?)` or `cutPlane(name, normal, options?)` where options may include `offset`.
150
+
151
+ ```js
152
+ const cutZ = param('Cut Height', 10, { min: -50, max: 50, unit: 'mm' });
153
+ cutPlane('Inspection', [0, 0, 1], cutZ, { exclude: ['Probe', 'Fasteners'] });
154
+ ```
155
+
156
+ ```ts
157
+ cutPlane(name: string, normal: [ number, number, number ], offset?: number, options?: CutPlaneOptions): void
158
+ ```
159
+
160
+ **`CutPlaneOptions`**
161
+ - `offset?: number` — Optional offset along the plane normal (primarily for object-form overload).
162
+ - `exclude?: CutPlaneExcludeInput` — Object names to keep uncut for this plane.
163
+
164
+ #### `cutPlane()`
165
+
166
+ ```ts
167
+ cutPlane(name: string, normal: [ number, number, number ], options?: CutPlaneOptions): void
168
+ ```
169
+
170
+ #### `mock()` — Register a mock (context) object for visualization and collision checking.
171
+
172
+ Mock objects appear in the viewport and spatial analysis when you run a file directly, but are excluded when the file is imported via [`require()`](/docs/core#require). This lets you model the surrounding context — walls, bolts, mating parts — without polluting the module's exports.
173
+
174
+ The shape is returned unchanged, so you can reference it for alignment, dimensioning, and `verify` checks.
175
+
176
+ Mock objects participate in `forgecad run` collision detection and spatial analysis. Their names appear with a `(mock)` suffix in reports.
177
+
178
+ In the viewport, mock objects render at reduced opacity so they are visually distinct from real geometry.
179
+
180
+ ```ts
181
+ // bracket.forge.js
182
+ const wall = mock(box(100, 200, 10).translate(0, 0, -5), "wall");
183
+ const bolt = mock(cylinder(3, 15).translate(10, 15, 0), "bolt");
184
+
185
+ const bracket = box(20, 30, 5);
186
+ verify.notColliding("bracket vs wall", bracket, wall);
187
+
188
+ return bracket;
189
+ // When imported: only bracket is exported
190
+ // When run directly: bracket + wall + bolt all visible
191
+ ```
192
+
193
+ ```ts
194
+ mock<T extends Shape>(shape: T, name?: string): T
195
+ ```
196
+
197
+ #### `scene()` — Configure the scene environment for the current script execution.
198
+
199
+ Controls camera position, lighting rig, background color or gradient, atmospheric fog, environment maps, post-processing effects, and capture parameters for the `forgecad capture` command. Multiple calls merge — later values override earlier ones on a per-key basis, so you can split configuration across multiple `scene()` calls.
200
+
201
+ When `lights` is specified, **all** default lights are removed. You must include your own ambient light or the scene will be fully dark.
202
+
203
+ Setting `camera.position` overrides auto-framing — the viewport will no longer auto-fit the geometry on script reload.
204
+
205
+ Post-processing effects (`bloom`, `vignette`, `grain`) work in the browser viewport only. The CLI applies camera, lights, background, fog, and `toneMappingExposure` but skips shader effects.
206
+
207
+ All numeric values accept `param()` expressions.
208
+
209
+ ```js
210
+ scene({
211
+ background: { top: '#000814', bottom: '#001d3d' },
212
+ camera: { position: [160, -120, 100], target: [0, 0, 50], fov: 52 },
213
+ lights: [
214
+ { type: 'ambient', color: '#001233', intensity: 0.08 },
215
+ { type: 'point', position: [120, -80, 130], color: '#00f5d4', intensity: 4, distance: 400, decay: 1 },
216
+ { type: 'point', position: [-100, 60, 20], color: '#f72585', intensity: 3, distance: 350 },
217
+ { type: 'directional', position: [50, -30, 200], color: '#ffd60a', intensity: 1.2 },
218
+ { type: 'hemisphere', skyColor: '#003566', groundColor: '#000814', intensity: 0.2 },
219
+ ],
220
+ fog: { color: '#000814', near: 100, far: 450 },
221
+ postProcessing: {
222
+ bloom: { intensity: param('bloom', 1.5, 0, 4), threshold: 0.5, radius: 0.7 },
223
+ vignette: { darkness: 0.8, offset: 0.25 },
224
+ grain: { intensity: 0.08 },
225
+ toneMappingExposure: param('exposure', 1.5, 0.5, 4),
226
+ },
227
+ });
228
+ ```
229
+
230
+ ```ts
231
+ scene(options: SceneOptions): void
232
+ ```
233
+
234
+ **`SceneOptions`**
235
+
236
+ | Option | Type | Description |
237
+ |--------|------|-------------|
238
+ | `capture?` | `SceneCaptureConfig` | Default capture parameters for `forgecad capture` — CLI flags override these. |
239
+ | `background?`, `camera?`, `lights?`, `environment?`, `fog?`, `postProcessing?`, `ground?` | | — |
240
+
241
+ `SceneBackgroundGradient`: `{ top: string, bottom: string }`
242
+
243
+ `SceneCameraConfig`: `{ fov?: number, type?: "perspective" | "orthographic" }`
244
+
245
+ **`SceneLightConfig`**
246
+
247
+ | Option | Type | Description |
248
+ |--------|------|-------------|
249
+ | `groundColor?` | `string` | Ground color for hemisphere lights |
250
+ | `skyColor?` | `string` | Sky color alias for hemisphere lights (same as color) |
251
+ | `angle?` | `number` | Spot light cone angle in radians |
252
+ | `penumbra?` | `number` | Spot light penumbra (0–1) |
253
+ | `decay?` | `number` | Point/spot light decay |
254
+ | `distance?` | `number` | Point/spot light distance (0 = infinite) |
255
+ | `castShadow?` | `boolean` | Whether this light casts shadows |
256
+ | `type`, `color?`, `intensity?` | | — |
257
+
258
+ **`SceneEnvironmentConfig`**
259
+ - `preset?: "studio" | "sunset" | "dawn" | "warehouse" | "forest" | "apartment" | "lobby" | "city" | "park" | "night" | "none"` — Built-in preset name or 'none' to disable
260
+ - `intensity?: number` — Environment map intensity
261
+ - `background?: boolean` — Use environment map as scene background
262
+
263
+ **`SceneFogConfig`**
264
+ - `near?: number` — Linear fog near distance
265
+ - `far?: number` — Linear fog far distance
266
+ - `density?: number` — Exponential fog density (if set, uses FogExp2 instead of linear Fog)
267
+ - Also: `color?: string`
268
+
269
+ `ScenePostProcessingConfig`: `{ bloom?: SceneBloomConfig, vignette?: SceneVignetteConfig, grain?: SceneGrainConfig, toneMappingExposure?: number }`
270
+
271
+ `SceneBloomConfig`: `{ intensity?: number, threshold?: number, radius?: number }`
272
+
273
+ `SceneVignetteConfig`: `{ darkness?: number, offset?: number }`
274
+
275
+ `SceneGrainConfig`: `{ intensity?: number }`
276
+
277
+ **`SceneGroundConfig`**
278
+
279
+ | Option | Type | Description |
280
+ |--------|------|-------------|
281
+ | `visible?` | `boolean` | Show a ground plane |
282
+ | `color?` | `string` | Ground color |
283
+ | `offset?` | `number` | Offset below the model's bounding box minimum Z. Default 0 (flush with model bottom). |
284
+ | `receiveShadow?` | `boolean` | Receive shadows on the ground |
285
+
286
+ **`SceneCaptureConfig`**
287
+
288
+ | Option | Type | Description |
289
+ |--------|------|-------------|
290
+ | `framesPerTurn?` | `number` | Frames for one full orbit rotation (default: 72) |
291
+ | `holdFrames?` | `number` | Frozen frames before motion starts (default: 6) |
292
+ | `pitchDeg?` | `number` | Orbit pitch angle in degrees (default: auto from camera) |
293
+ | `fps?` | `number` | Output frame rate (default: 24) |
294
+ | `size?` | `number` | Output frame size in pixels (default: 960) |
295
+ | `background?` | `string` | Canvas background color for capture (default: '#252526') |
296
+
297
+ #### `viewConfig()` — Configure viewport helper visuals for the current script execution.
298
+
299
+ Controls renderer-only overlays that appear in the viewport but are not part of the geometry. Currently supports the joint overlay that renders axis arrows and arc indicators when `jointsView` is active. Multiple calls merge — later values override earlier ones per key.
300
+
301
+ This does **not** trigger a geometry recompute; it only affects the visual helpers drawn on top of the 3D scene.
302
+
303
+ ```js
304
+ viewConfig({
305
+ jointOverlay: {
306
+ axisColor: '#13dfff',
307
+ arcColor: '#ff7a1a',
308
+ axisLineRadiusScale: 0.03,
309
+ arcLineRadiusScale: 0.022,
310
+ },
311
+ });
312
+ ```
313
+
314
+ ```ts
315
+ viewConfig(options?: ViewConfigOptions): void
316
+ ```
317
+
318
+ `ViewConfigOptions`: `{ jointOverlay?: JointOverlayViewConfigOptions }`
319
+
320
+ **`JointOverlayViewConfigOptions`**: `enabled?: boolean`, `axisColor?: string`, `axisCoreColor?: string`, `arcColor?: string`, `zeroColor?: string`, `arcVisualLimitDeg?: number`, `axisLengthScale?: number`, `axisLengthMin?: number`, `axisLineRadiusScale?: number`, `axisLineRadiusMin?: number`, `axisLineRadiusMax?: number`, `spokeLineRadiusScale?: number`, `spokeLineRadiusMin?: number`, `spokeLineRadiusMax?: number`, `arcLineRadiusScale?: number`, `arcLineRadiusMin?: number`, `arcLineRadiusMax?: number`, `axisDotRadiusScale?: number`, `axisDotRadiusMin?: number`, `axisArrowRadiusScale?: number`, `axisArrowRadiusMin?: number`, `axisArrowLengthScale?: number`, `axisArrowLengthMin?: number`, `axisArrowOffsetFactor?: number`, `arcRadiusScale?: number`, `arcRadiusMin?: number`, `arcDotRadiusScale?: number`, `arcDotRadiusMin?: number`, `arcArrowRadiusScale?: number`, `arcArrowRadiusMin?: number`, `arcArrowLengthScale?: number`, `arcArrowLengthMin?: number`, `arcArrowOffsetFactor?: number`, `arcStepDeg?: number`, `arcMinSteps?: number`, `arcTubeSegmentsMin?: number`, `arcTubeSegmentsFactor?: number`, `arcTubeRadialSegments?: number`
321
+
322
+ #### `showLabels()` — Highlight all user-labeled faces on a shape for visual debugging.
323
+
324
+ Shows each user-authored label name in the viewport for visual debugging. Returns the shape unchanged for chaining: `return showLabels(myShape)`.
325
+
326
+ ```ts
327
+ showLabels(shape: Shape): Shape
328
+ ```
329
+
330
+ #### `highlight()` — Highlight any geometry for visual debugging in the viewport.
331
+
332
+ Supported inputs:
333
+
334
+ - `string` — sketch entity ID (e.g. `'L0'`, `'P0'`, `'C0'`)
335
+ - `[x, y, z]` — 3D point
336
+ - `[[x1,y1,z1], [x2,y2,z2]]` — edge (line segment)
337
+ - `{ normal: [x,y,z], offset: number }` — plane by normal + distance from origin
338
+ - `{ normal: [x,y,z], point: [x,y,z] }` — plane by normal + point on plane
339
+ - [`Shape`](/docs/core#shape) — highlight entire 3D shape
340
+ - `FaceRef` (from `shape.face('top')`) — highlight as plane at face center
341
+ - `EdgeRef` (from `shape.edge('left')`) — highlight as edge segment
342
+
343
+ ```ts
344
+ highlight(entityId: string, opts?: HighlightOptions): void
345
+ ```
346
+
347
+ **`HighlightOptions`**
348
+ - `size?: number` — Size hint for points (radius in mm) or planes (disc radius in mm).
349
+ - Also: `color?: string, label?: string, pulse?: boolean`
350
+
351
+ #### `highlight()`
352
+
353
+ ```ts
354
+ highlight(point: [ number, number, number ], opts?: HighlightOptions): void
355
+ ```
356
+
357
+ #### `highlight()`
358
+
359
+ ```ts
360
+ highlight(edge: [ [ number, number, number ], [ number, number, number ] ], opts?: HighlightOptions): void
361
+ ```
362
+
363
+ #### `highlight()`
364
+
365
+ ```ts
366
+ highlight(plane: { normal: [ number, number, number ]; offset: number; }, opts?: HighlightOptions): void
367
+ ```
368
+
369
+ #### `highlight()`
370
+
371
+ ```ts
372
+ highlight(plane: { normal: [ number, number, number ]; point: [ number, number, number ]; }, opts?: HighlightOptions): void
373
+ ```
374
+
375
+ #### `highlight()`
376
+
377
+ ```ts
378
+ highlight(shape: Shape, opts?: HighlightOptions): void
379
+ ```
380
+
381
+ #### `highlight()`
382
+
383
+ ```ts
384
+ highlight(face: FaceRef, opts?: HighlightOptions): void
385
+ ```
386
+
387
+ **`FaceRef`**
388
+ - `query?: FaceQueryRef` — Compiler-owned face query when available.
389
+ - `planar?: boolean` — True when the face can host a 2D sketch placement frame
390
+ - `descendant?: FaceDescendantMetadata` — Shared descendant-resolution metadata when this face is a semantic region/set.
391
+ - Also: `name: FaceName`
392
+
393
+ **`FaceDescendantMetadata`**: `kind: "single" | "face-set"`, `semantic: FaceDescendantSemantic`, `memberCount: number`, `memberNames: string[]`, `coplanar: boolean`
394
+
395
+ #### `highlight()`
396
+
397
+ ```ts
398
+ highlight(edge: EdgeRef, opts?: HighlightOptions): void
399
+ ```
400
+
401
+ **`EdgeRef`**
402
+ - `query?: EdgeQueryRef` — Compiler-owned edge query when available.
403
+ - Also: `name: EdgeName`
404
+
405
+ ---
406
+
407
+ ## Classes
408
+
409
+ ### `RouteBuilder`
410
+
411
+ #### `up()` — Vertical line going +Y. Length is optional (solver determines it from constraints).
412
+
413
+ ```ts
414
+ up(length?: number): LineId
415
+ ```
416
+
417
+ #### `down()` — Vertical line going -Y. Length is optional.
418
+
419
+ ```ts
420
+ down(length?: number): LineId
421
+ ```
422
+
423
+ #### `right()` — Horizontal line going +X. Length is optional.
424
+
425
+ ```ts
426
+ right(length?: number): LineId
427
+ ```
428
+
429
+ #### `left()` — Horizontal line going -X. Length is optional.
430
+
431
+ ```ts
432
+ left(length?: number): LineId
433
+ ```
434
+
435
+ #### `lineAt()` — Line at an arbitrary angle (degrees from +X). Length is optional.
436
+
437
+ ```ts
438
+ lineAt(angleDeg: number, length?: number): LineId
439
+ ```
440
+
441
+ #### [`line()`](/docs/sketch#line) — Line with solver-determined direction. Length is optional. Direction comes from tangency to previous arc or from constraints.
442
+
443
+ ```ts
444
+ line(length?: number): LineId
445
+ ```
446
+
447
+ #### `toward()` — Line toward a specific point. Length defaults to the distance to that point.
448
+
449
+ ```ts
450
+ toward(x: number, y: number): LineId
451
+ ```
452
+
453
+ #### `arcLeft()` — Tangent arc turning left relative to travel direction.
454
+
455
+ or `{ minSweep: degrees }` to seed the geometry without constraining. `minSweep` guides the solver to the correct branch for arcs that sweep more than the default 90° seed.
456
+
457
+ ```ts
458
+ arcLeft(radius?: number, sweepDegOrOpts?: number | { minSweep: number; }): ArcId
459
+ ```
460
+
461
+ #### `arcRight()` — Tangent arc turning right relative to travel direction.
462
+
463
+ or `{ minSweep: degrees }` to seed without constraining.
464
+
465
+ ```ts
466
+ arcRight(radius?: number, sweepDegOrOpts?: number | { minSweep: number; }): ArcId
467
+ ```
468
+
469
+ #### `close()` — Close the route with a straight line back to the start point.
470
+
471
+ ```ts
472
+ close(): void
473
+ ```
474
+
475
+ #### `done()` — Close the route back to its start point and register as a profile loop.
476
+
477
+ No extra line segment is added. A coincident constraint connects the last point to the start, and tangency is added for G1 smoothness when arcs are at the junction. The session's incremental solver processes these constraints, keeping seed positions accurate for the final solve.
478
+
479
+ ```ts
480
+ done(): void
481
+ ```
482
+
483
+ #### `start()` — PointId of the route's start point.
484
+
485
+ ```ts
486
+ get start(): PointId
487
+ ```
488
+
489
+ #### `end()` — PointId of the current cursor (route's end).
490
+
491
+ ```ts
492
+ get end(): PointId
493
+ ```
494
+
495
+ #### `startOf()` — Get the start point of a segment.
496
+
497
+ ```ts
498
+ startOf(segId: LineId | ArcId): PointId
499
+ ```
500
+
501
+ #### `endOf()` — Get the end point of a segment.
502
+
503
+ ```ts
504
+ endOf(segId: LineId | ArcId): PointId
505
+ ```
506
+
507
+ ---
508
+
509
+ ## Constants
510
+
511
+ ### `route`
512
+
513
+ Route step factories. Access via `route.line()`, `route.fillet()`, etc.
@@ -0,0 +1,104 @@
1
+ ---
2
+ skill-group: toolbox
3
+ skill-order: 100
4
+ ---
5
+
6
+ # Woodworking
7
+
8
+ Wood boards with grain/species metadata, and joinery operations: dado, rabbet, mortise & tenon. Access via `Wood.*`.
9
+
10
+ ## Contents
11
+
12
+ - [WoodBoard](#woodboard)
13
+ - [Wood](#wood)
14
+
15
+ ---
16
+
17
+ ## Classes
18
+
19
+ ### `WoodBoard`
20
+
21
+ A board of wood with metadata for manufacturing: grain direction, species, and dimensions. The underlying geometry is a simple box.
22
+
23
+ Shape is mutable — joint operations (Wood.dado, Wood.rabbet, Wood.mortiseAndTenon) subtract material in-place. Transform methods return new WoodBoard instances preserving all metadata.
24
+
25
+ **Properties:**
26
+
27
+ | Property | Type | Description |
28
+ |----------|------|-------------|
29
+ | `shape` | `Shape` | The underlying 3D shape — mutable, joints modify it in-place. |
30
+ | `width` | `number` | Board width (mm) — the longer flat dimension |
31
+ | `height` | `number` | Board height (mm) — the shorter flat dimension |
32
+ | `thickness` | `number` | Board thickness (mm) |
33
+ | `grain` | `string` | Grain direction: "long" or "cross" |
34
+ | `species` | `string` | Wood species, e.g. "birch", "oak" |
35
+ | `material` | `string` | Material label for BOM |
36
+
37
+ **Methods:**
38
+
39
+ #### `cut()` — Subtract a cutter from this board, modifying it in-place. Used by joint functions (dado, rabbet, mortiseAndTenon).
40
+
41
+ ```ts
42
+ cut(cutter: Shape): this
43
+ ```
44
+
45
+ #### `translate()` — Translate the board in 3D space.
46
+
47
+ ```ts
48
+ translate(x: number, y: number, z: number): WoodBoard
49
+ ```
50
+
51
+ #### `rotate()` — Rotate the board around an axis by a given angle in degrees.
52
+
53
+ ```ts
54
+ rotate(axis: [ number, number, number ], angleDeg: number, options?: { pivot?: [ number, number, number ]; }): WoodBoard
55
+ ```
56
+
57
+ #### `rotateX()` — Rotate the board around the X axis by a given angle in degrees.
58
+
59
+ ```ts
60
+ rotateX(angleDeg: number): WoodBoard
61
+ ```
62
+
63
+ #### `rotateY()` — Rotate the board around the Y axis by a given angle in degrees.
64
+
65
+ ```ts
66
+ rotateY(angleDeg: number): WoodBoard
67
+ ```
68
+
69
+ #### `rotateZ()` — Rotate the board around the Z axis by a given angle in degrees.
70
+
71
+ ```ts
72
+ rotateZ(angleDeg: number): WoodBoard
73
+ ```
74
+
75
+ #### `mirror()` — Mirror the board across a plane defined by its normal.
76
+
77
+ ```ts
78
+ mirror(normal: [ number, number, number ]): WoodBoard
79
+ ```
80
+
81
+ #### `color()` — Set the board's display color.
82
+
83
+ ```ts
84
+ color(value: string): WoodBoard
85
+ ```
86
+
87
+ #### `clone()` — Clone the board (creates an independent copy of the underlying shape).
88
+
89
+ ```ts
90
+ clone(): WoodBoard
91
+ ```
92
+
93
+ ---
94
+
95
+ ## Constants
96
+
97
+ ### `Wood`
98
+
99
+ Woodworking namespace — create boards and cut joints. **Boards:** `Wood.board()` creates a WoodBoard with grain, species, and BOM metadata. **Joints:** `Wood.dado()`, `Wood.rabbet()`, `Wood.mortiseAndTenon()` mutate boards in-place by subtracting material, following the same pattern as LaserKit's [`fingerJoint()`](/docs/sheet-metal#fingerjoint).
100
+
101
+ - `readonly board: (width: number, height: number, thickness: number, opts?: WoodBoardOptions) => WoodBoard` — Create a wood board with metadata for manufacturing. The board is a box(width, height, thickness) centered on XY, base at Z=0. Width along X, height along Y, thickness along Z (0 to thickness).
102
+ - `dado(host: WoodBoard, guest: WoodBoard, opts: DadoOptions): void` — Cut a dado (channel) across the face of a host board for a guest board to sit in. Mutates `host.shape` by subtracting a rectangular channel.
103
+ - `rabbet(board: WoodBoard, opts: RabbetOptions): void` — Cut a rabbet (L-shaped step) along an edge of a board. Mutates `board.shape` by subtracting a step from the specified edge.
104
+ - `mortiseAndTenon(mortiseBoard: WoodBoard, tenonBoard: WoodBoard, opts?: MortiseAndTenonOptions): void` — Cut a mortise in one board and shape a tenon on another. Mutates both boards — subtracts the mortise pocket and removes shoulder material to form the tenon.
@@ -0,0 +1,46 @@
1
+ ---
2
+ skill-group: geometry
3
+ skill-order: 1
4
+ ---
5
+
6
+ # Coordinate System Convention
7
+
8
+ ForgeCAD uses a **Z-up** right-handed coordinate system.
9
+
10
+ ## Axes
11
+
12
+ | Axis | Direction | Positive |
13
+ |------|-----------------|----------|
14
+ | X | Left / Right | Right |
15
+ | Y | Forward / Back | Forward |
16
+ | Z | Up / Down | Up |
17
+
18
+ ## Standard Views
19
+
20
+ | View | Camera position direction | Sees plane |
21
+ |--------|--------------------------|------------|
22
+ | Front | −Y | XZ |
23
+ | Back | +Y | XZ |
24
+ | Right | +X | YZ |
25
+ | Left | −X | YZ |
26
+ | Top | +Z | XY |
27
+ | Bottom | −Z | XY |
28
+
29
+ ## GizmoViewcube Face Mapping
30
+
31
+ Three.js BoxGeometry material indices vs ForgeCAD labels (Z-up remapping):
32
+
33
+ | Index | Three.js direction | ForgeCAD label |
34
+ |-------|--------------------|----------------|
35
+ | 0 | +X | Right |
36
+ | 1 | −X | Left |
37
+ | 2 | +Y | Front |
38
+ | 3 | −Y | Back |
39
+ | 4 | +Z | Top |
40
+ | 5 | −Z | Bottom |
41
+
42
+ Default drei labels are Y-up; ForgeCAD passes `faces={['Right','Left','Front','Back','Top','Bottom']}`.
43
+
44
+ ## Grid
45
+
46
+ The ground plane is XY (Z = 0). Extrusion goes along +Z. Manifold is Y-up internally — if a kernel-facing operation behaves as if axes are swapped, check for Manifold Y-up semantics leaking through.