forgecad 0.6.3 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/README.md +2 -11
  2. package/dist/assets/{AdminPage-CeqCUUgu.js → AdminPage-DAu1C1ST.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-Gc_BCdqC.js} +269 -143
  5. package/dist/assets/EditorApp-D9bJvtf7.js +11338 -0
  6. package/dist/assets/{EditorApp-CnC2k4cW.css → EditorApp-DG1-oUSV.css} +459 -87
  7. package/dist/assets/{EmbedViewer-DBlzmQ5i.js → EmbedViewer-CEO8XbV8.js} +2 -4
  8. package/dist/assets/LandingPage-CdCuEOdC.js +451 -0
  9. package/dist/assets/PricingPage-BSrxu6d7.js +232 -0
  10. package/dist/assets/{SettingsPage-BqCh9JcC.js → SettingsPage-FUCSIRq6.js} +129 -5
  11. package/dist/assets/{evalWorker-Ql-aKwLA.js → evalWorker-KoR0SNKq.js} +6770 -2914
  12. package/dist/assets/{index-2hfs_ub0.css → index-CyVd1D4D.css} +227 -53
  13. package/dist/assets/{Viewport-CoB46f5R.js → index-wTEK39at.js} +31385 -6439
  14. package/dist/assets/{javascript-DCxGoE5Y.js → javascript-DAl8Gmyo.js} +1 -1
  15. package/dist/assets/{manifold-CqNMHHKO.js → manifold-B1sGWdYk.js} +4 -3
  16. package/dist/assets/{manifold-Cce9wRFz.js → manifold-D7o0N50J.js} +1 -1
  17. package/dist/assets/{manifold-D6BeHIOo.js → manifold-G5sBaXzi.js} +1 -1
  18. package/dist/assets/{reportWorker-sFEFonXf.js → reportWorker-DYcRHhv9.js} +6798 -3341
  19. package/dist/assets/{vendor-react-Dt7-aaJH.js → vendor-react-CG3i_wp0.js} +65 -8
  20. package/dist/docs-raw/generated/assembly.md +691 -112
  21. package/dist/docs-raw/generated/concepts.md +1225 -1400
  22. package/dist/docs-raw/generated/core.md +464 -1412
  23. package/dist/docs-raw/generated/curves.md +593 -117
  24. package/dist/docs-raw/generated/lib.md +38 -748
  25. package/dist/docs-raw/generated/output.md +139 -245
  26. package/dist/docs-raw/generated/sheet-metal.md +473 -21
  27. package/dist/docs-raw/generated/sketch.md +553 -349
  28. package/dist/docs-raw/generated/viewport.md +345 -303
  29. package/dist/docs-raw/generated/wood.md +104 -0
  30. package/dist/index.html +2 -2
  31. package/dist/sitemap.xml +6 -6
  32. package/dist-cli/chunk-PZ5AY32C.js +10 -0
  33. package/dist-cli/chunk-PZ5AY32C.js.map +1 -0
  34. package/dist-cli/forgecad.js +9435 -5407
  35. package/dist-cli/forgecad.js.map +1 -0
  36. package/dist-cli/solver-FV7TJZGI.js +365 -0
  37. package/dist-cli/solver-FV7TJZGI.js.map +1 -0
  38. package/dist-skill/CONTEXT.md +3186 -7145
  39. package/dist-skill/SKILL-dev.md +21 -63
  40. package/dist-skill/SKILL.md +12 -56
  41. package/dist-skill/docs/API/core/concepts.md +16 -98
  42. package/dist-skill/docs/CLI/export.md +91 -0
  43. package/dist-skill/docs/CLI/projects.md +107 -0
  44. package/dist-skill/docs/CLI/studio_publishing.md +52 -0
  45. package/dist-skill/docs/CLI/validation.md +66 -0
  46. package/dist-skill/docs/generated/assembly.md +691 -112
  47. package/dist-skill/docs/generated/core.md +464 -1412
  48. package/dist-skill/docs/generated/curves.md +593 -117
  49. package/dist-skill/docs/generated/lib.md +38 -748
  50. package/dist-skill/docs/generated/output.md +139 -245
  51. package/dist-skill/docs/generated/sheet-metal.md +473 -21
  52. package/dist-skill/docs/generated/sketch.md +553 -349
  53. package/dist-skill/docs/generated/viewport.md +345 -303
  54. package/dist-skill/docs/generated/wood.md +104 -0
  55. package/dist-skill/docs/guides/coordinate-system.md +11 -17
  56. package/dist-skill/docs/guides/geometry-conventions.md +13 -70
  57. package/dist-skill/docs/guides/modeling-recipes.md +22 -195
  58. package/dist-skill/docs/guides/positioning.md +88 -147
  59. package/dist-skill/docs-dev/API/core/concepts.md +51 -0
  60. package/dist-skill/docs-dev/API/core/sdf-advanced.md +92 -0
  61. package/dist-skill/docs-dev/API/core/sdf-primitives.md +58 -0
  62. package/dist-skill/docs-dev/API/core/sdf-workflow.md +42 -0
  63. package/dist-skill/docs-dev/CLI/export.md +91 -0
  64. package/dist-skill/docs-dev/CLI/projects.md +107 -0
  65. package/dist-skill/docs-dev/CLI/studio_publishing.md +52 -0
  66. package/dist-skill/docs-dev/CLI/validation.md +66 -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 +1 -3
  70. package/dist-skill/docs-dev/generated/assembly.md +771 -0
  71. package/dist-skill/docs-dev/generated/core.md +775 -0
  72. package/dist-skill/docs-dev/generated/curves.md +688 -0
  73. package/dist-skill/docs-dev/generated/lib.md +50 -0
  74. package/dist-skill/docs-dev/generated/output.md +234 -0
  75. package/dist-skill/docs-dev/generated/sheet-metal.md +506 -0
  76. package/dist-skill/docs-dev/generated/sketch.md +801 -0
  77. package/dist-skill/docs-dev/generated/viewport.md +486 -0
  78. package/dist-skill/docs-dev/generated/wood.md +104 -0
  79. package/dist-skill/docs-dev/guides/coordinate-system.md +46 -0
  80. package/dist-skill/docs-dev/guides/geometry-conventions.md +52 -0
  81. package/dist-skill/docs-dev/guides/modeling-recipes.md +77 -0
  82. package/dist-skill/docs-dev/guides/positioning.md +151 -0
  83. package/dist-skill/{docs → docs-dev}/guides/skill-maintenance.md +21 -10
  84. package/dist-skill/{docs → docs-dev}/internals/compiler.md +5 -6
  85. package/dist-skill/{docs → docs-dev}/internals/constraint-solver-quality.md +0 -1
  86. package/dist-skill/{docs → docs-dev}/internals/constraint-solver.md +0 -1
  87. package/dist-skill/{docs → docs-dev}/internals/sketch-2d-pipeline.md +2 -3
  88. package/examples/api/attachTo-basics.forge.js +5 -5
  89. package/examples/api/boolean-operations.forge.js +3 -3
  90. package/examples/api/bounding-box-visualizer.forge.js +2 -2
  91. package/examples/api/clone-duplicate.forge.js +1 -1
  92. package/examples/api/colors-union-vs-array.forge.js +6 -6
  93. package/examples/api/connector-assembly.forge.js +4 -4
  94. package/examples/api/connector-basics.forge.js +2 -2
  95. package/examples/api/extrude-options.forge.js +4 -10
  96. package/examples/api/feature-created-faces.forge.js +6 -10
  97. package/examples/api/fillet-showcase.forge.js +1 -1
  98. package/examples/api/folded-service-panel-cover.forge.js +2 -2
  99. package/examples/api/group-test.forge.js +1 -1
  100. package/examples/api/group-vs-union.forge.js +1 -1
  101. package/examples/api/highlight-debug.forge.js +4 -0
  102. package/examples/api/js-module-pillars.js +1 -1
  103. package/examples/api/js-module-scene.js +2 -2
  104. package/examples/api/mesh-import-slats.forge.js +1 -1
  105. package/examples/api/pointAlong-orientation.forge.js +1 -1
  106. package/examples/api/profile-2020-b-slot6.forge.js +0 -1
  107. package/examples/api/route-perimeter-flange.forge.js +1 -1
  108. package/examples/api/sdf-rover-demo.forge.js +10 -10
  109. package/examples/api/sketch-on-face-demo.forge.js +2 -2
  110. package/examples/api/sketch-regions.forge.js +4 -4
  111. package/examples/api/transition-curves.forge.js +1 -1
  112. package/examples/api/variable-sweep-pure-sdf-test.forge.js +162 -0
  113. package/examples/api/variable-sweep-test.forge.js +2 -2
  114. package/examples/api/wood-joinery.forge.js +60 -0
  115. package/examples/compiler-corpus/enclosure-shell-cuts.forge.js +3 -3
  116. package/examples/compiler-corpus/fastener-plate-variants.forge.js +2 -2
  117. package/examples/experiments/drone-arm.forge.js +53 -0
  118. package/examples/furniture/adjustable-table.forge.js +2 -2
  119. package/examples/furniture/bathroom.forge.js +11 -11
  120. package/examples/furniture/chair.forge.js +1 -1
  121. package/examples/generative/crystal-growth.forge.js +2 -2
  122. package/examples/generative/frost-spires.forge.js +3 -3
  123. package/examples/generative/golden-spiral-tower.forge.js +3 -3
  124. package/examples/mechanical/3d-printer.forge.js +28 -28
  125. package/examples/mechanical/5-finger-robot-hand.forge.js +15 -15
  126. package/examples/mechanical/airplane-propeller.forge.js +2 -2
  127. package/examples/mechanical/fillet-enclosure.forge.js +1 -1
  128. package/examples/mechanical/headphone-hanger-v2.forge.js +2 -2
  129. package/examples/mechanical/robot_hand.forge.js +15 -15
  130. package/examples/mechanical/robot_hand_2.forge.js +9 -9
  131. package/examples/products/bottle.forge.js +1 -1
  132. package/examples/products/chess-set.forge.js +19 -19
  133. package/examples/products/classical-piano.forge.js +11 -11
  134. package/examples/products/clock.forge.js +12 -12
  135. package/examples/products/iphone.forge.js +8 -8
  136. package/examples/products/laptop.forge.js +15 -15
  137. package/examples/products/liquid-soap-dispenser.forge.js +18 -18
  138. package/examples/products/origami-fish.forge.js +8 -6
  139. package/examples/products/spiderman-cake.forge.js +4 -4
  140. package/examples/toolbox/bolted-joint.forge.js +2 -2
  141. package/package.json +7 -4
  142. package/dist/assets/EditorApp-B-vQvgam.js +0 -9888
  143. package/dist/assets/LandingPage-C5n9hDXI.js +0 -322
  144. package/dist/assets/PublishedModelPage-Dt7PCVBj.js +0 -146
  145. package/dist/assets/__vite-browser-external-CURh0WXD.js +0 -8
  146. package/dist/assets/deserializeRunResult-BLAFoiE0.js +0 -19365
  147. package/dist/assets/index-1CYp3zUp.js +0 -1455
  148. package/dist/docs-raw/CLI.md +0 -865
  149. package/dist-skill/docs/API/API.md +0 -1666
  150. package/dist-skill/docs/API/README.md +0 -37
  151. package/dist-skill/docs/API/assembly/assembly.md +0 -617
  152. package/dist-skill/docs/API/core/edge-queries.md +0 -130
  153. package/dist-skill/docs/API/core/parameters.md +0 -122
  154. package/dist-skill/docs/API/core/reserved-terms.md +0 -137
  155. package/dist-skill/docs/API/core/sdf.md +0 -326
  156. package/dist-skill/docs/API/core/skill-cli.md +0 -194
  157. package/dist-skill/docs/API/core/skill-guide.md +0 -205
  158. package/dist-skill/docs/API/core/specs.md +0 -186
  159. package/dist-skill/docs/API/core/topology.md +0 -372
  160. package/dist-skill/docs/API/entities.md +0 -268
  161. package/dist-skill/docs/API/output/bom.md +0 -58
  162. package/dist-skill/docs/API/output/brep-export.md +0 -87
  163. package/dist-skill/docs/API/output/dimensions.md +0 -67
  164. package/dist-skill/docs/API/output/export.md +0 -110
  165. package/dist-skill/docs/API/output/gcode.md +0 -195
  166. package/dist-skill/docs/API/runtime/viewport.md +0 -420
  167. package/dist-skill/docs/API/sheet-metal/sheet-metal.md +0 -185
  168. package/dist-skill/docs/API/sketch/anchor.md +0 -37
  169. package/dist-skill/docs/API/sketch/booleans.md +0 -91
  170. package/dist-skill/docs/API/sketch/core.md +0 -73
  171. package/dist-skill/docs/API/sketch/extrude.md +0 -62
  172. package/dist-skill/docs/API/sketch/on-face.md +0 -104
  173. package/dist-skill/docs/API/sketch/operations.md +0 -78
  174. package/dist-skill/docs/API/sketch/path.md +0 -75
  175. package/dist-skill/docs/API/sketch/primitives.md +0 -146
  176. package/dist-skill/docs/API/sketch/regions.md +0 -80
  177. package/dist-skill/docs/API/sketch/text.md +0 -108
  178. package/dist-skill/docs/API/sketch/transforms.md +0 -65
  179. package/dist-skill/docs/API/toolbox/fasteners.md +0 -129
  180. package/dist-skill/docs/CLI.md +0 -865
  181. package/dist-skill/docs/INDEX.md +0 -94
  182. package/dist-skill/docs/RELEASING.md +0 -55
  183. package/dist-skill/docs/cli-monetization.md +0 -111
  184. package/dist-skill/docs/deployment.md +0 -281
  185. package/dist-skill/docs/generated/concepts.md +0 -2112
  186. package/dist-skill/docs/internals/shape-from-slices.md +0 -152
  187. package/dist-skill/docs/platform/admin.md +0 -45
  188. package/dist-skill/docs/platform/architecture.md +0 -79
  189. package/dist-skill/docs/platform/auth.md +0 -110
  190. package/dist-skill/docs/platform/email.md +0 -67
  191. package/dist-skill/docs/platform/projects.md +0 -111
  192. package/dist-skill/docs/platform/sharing.md +0 -90
  193. package/dist-skill/docs/runbook.md +0 -345
@@ -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.
@@ -17,19 +17,18 @@ ForgeCAD uses a **Z-up** right-handed coordinate system.
17
17
 
18
18
  ## Standard Views
19
19
 
20
- | View | Camera position direction | Sees plane | Camera up |
21
- |--------|--------------------------|------------|-----------|
22
- | Front | −Y (camera at −Y) | XZ | Z |
23
- | Back | +Y (camera at +Y) | XZ | Z |
24
- | Right | +X (camera at +X) | YZ | Z |
25
- | Left | −X (camera at −X) | YZ | Z |
26
- | Top | +Z (camera at +Z) | XY | +Y |
27
- | Bottom | −Z (camera at −Z) | XY | −Y |
28
- | Iso | +X −Y +Z (diagonal) | — | Z |
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 |
29
28
 
30
29
  ## GizmoViewcube Face Mapping
31
30
 
32
- Three.js BoxGeometry material indices (cube face order):
31
+ Three.js BoxGeometry material indices vs ForgeCAD labels (Z-up remapping):
33
32
 
34
33
  | Index | Three.js direction | ForgeCAD label |
35
34
  |-------|--------------------|----------------|
@@ -40,13 +39,8 @@ Three.js BoxGeometry material indices (cube face order):
40
39
  | 4 | +Z | Top |
41
40
  | 5 | −Z | Bottom |
42
41
 
43
- Default drei labels are `['Right', 'Left', 'Top', 'Bottom', 'Front', 'Back']` (Y-up).
44
- For Z-up we pass `faces={['Right', 'Left', 'Front', 'Back', 'Top', 'Bottom']}`.
45
-
46
- ## Kernel Axis Convention
47
-
48
- Manifold (the geometry kernel) is Y-up internally. ForgeCAD is Z-up externally. If a kernel-facing operation behaves as if axes are swapped, check whether a Manifold call is still assuming Y-up semantics.
42
+ Default drei labels are Y-up; ForgeCAD passes `faces={['Right','Left','Front','Back','Top','Bottom']}`.
49
43
 
50
44
  ## Grid
51
45
 
52
- The ground plane is XY (Z = 0). The grid lies on this plane.
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.
@@ -5,105 +5,48 @@ skill-order: 2
5
5
 
6
6
  # Geometry Conventions
7
7
 
8
- ForgeCAD wraps Manifold (a mesh kernel) and Three.js (a Y-up renderer). These libraries have their own conventions that conflict with each other and with CAD norms. This doc captures every convention mismatch and how ForgeCAD resolves it.
9
-
10
- **Core principle: the user script should never need to know about kernel or renderer internals.** If the user writes something geometrically reasonable, it should work. All convention translation happens inside ForgeCAD's layer.
8
+ ForgeCAD wraps Manifold (mesh kernel) and Three.js (Y-up renderer). This doc captures convention mismatches and how ForgeCAD resolves them.
11
9
 
12
10
  ## Winding Order
13
11
 
14
- **What it is:** The order of vertices in a 2D polygon determines its "direction" — counter-clockwise (CCW) = positive area, clockwise (CW) = negative/zero area in Manifold's `CrossSection`.
15
-
16
- **The problem:** Manifold silently produces empty geometry for CW polygons. A user writing `polygon([[0,0], [50,0], [50,30]])` vs `polygon([[0,0], [50,30], [50,0]])` gets either a triangle or nothing, with no error.
17
-
18
- **ForgeCAD's fix:** All entry points that accept raw points auto-fix winding:
19
- - `polygon(points)` — computes signed area, reverses if CW
12
+ CCW = positive area, CW = empty in Manifold's `CrossSection`. ForgeCAD auto-fixes at all entry points:
13
+ - `polygon(points)` — computes signed area (shoelace), reverses if CW
20
14
  - `path().close()` — same fix
21
15
 
22
- **Signed area test** (shoelace formula):
23
- ```
24
- signedArea = Σ (x₂ - x₁)(y₂ + y₁)
25
- ```
26
- If `signedArea > 0` → CW → reverse to make CCW.
27
-
28
- **Implementation:** `src/forge/sketch/primitives.ts` (polygon), `src/forge/sketch/path.ts` (close).
29
-
30
- **Rule for new code:** Any function that takes user-provided point arrays and creates a `CrossSection` MUST auto-fix winding. Never pass raw user points to Manifold without this check.
16
+ **Rule for new code:** Any function accepting user point arrays that creates a `CrossSection` MUST auto-fix winding.
31
17
 
32
18
  ## Coordinate System (Z-up vs Y-up)
33
19
 
34
- **The problem:** Three.js uses Y-up. CAD convention (and ForgeCAD) uses Z-up.
35
-
36
- **ForgeCAD's fix:** We set `camera.up = (0, 0, 1)` everywhere. Geometry coordinates are native Z-up — no matrix swizzling. The camera orientation handles the visual mapping.
37
-
38
- **Where this matters:**
39
- - `camera.up.set(0, 0, 1)` in `sceneBuilder.ts` and `render.ts`
40
- - GizmoViewcube face labels remapped (see coordinate-system.md)
41
- - Grid plane is XY (Z=0)
42
- - Extrusion goes along +Z
43
- - Revolution axis is Y (sketch plane), result maps to Z-up space
44
-
45
- **Rule for new code:** Never swap Y/Z in geometry. Always fix it at the camera/renderer level.
20
+ Three.js is Y-up; ForgeCAD is Z-up. Fix applied at camera level (`camera.up = (0,0,1)`) geometry coordinates are native Z-up. Never swap Y/Z in geometry.
46
21
 
47
22
  ## Revolution Axis
48
23
 
49
- **What it is:** `CrossSection.revolve()` in Manifold revolves around the Y axis. The sketch profile must be in the X-Y plane with X = radius (distance from axis) and Y = height.
50
-
51
- **The mapping:**
52
- - Profile X coordinate → radial distance from center
53
- - Profile Y coordinate → height (becomes Z after revolution)
54
- - Profile must be on the positive X side (X > 0) for valid geometry
55
-
56
- **Rule for new code:** Document which axis any new sweep/revolution operation uses. If it differs from user expectation, add a transform wrapper.
24
+ `CrossSection.revolve()` revolves around Y. Profile X = radial distance, Profile Y = height (becomes Z after revolution). Profile must be at X > 0.
57
25
 
58
26
  ## Boolean Winding (3D)
59
27
 
60
- **What it is:** Manifold requires consistent face normals (outward-pointing) for boolean operations. Manifold handles this internally for its own primitives, but imported meshes or degenerate operations can produce inside-out faces.
61
-
62
- **ForgeCAD's fix:** We only create meshes through Manifold's own constructors (`extrude`, `revolve`, `cylinder`, `sphere`, etc.), which guarantee correct normals. No raw mesh import path exists yet.
63
-
64
- **Rule for new code:** If adding mesh import (STL, OBJ), run `Manifold.asOriginal()` or validate manifoldness before allowing booleans.
28
+ Manifold requires consistent outward face normals. ForgeCAD only creates meshes through Manifold's own constructors, which guarantee correct normals.
65
29
 
66
30
  ## Transform Order
67
31
 
68
- **What it is:** Transforms are applied in call order (left to right in the chain). Default `rotate()`, `scale()`, and `mirror()` operate around the shape's own bounding-box center, so `shape.translate(10,0,0).rotate(0,0,45)` moves first and then spins in place. To orbit around world origin, use `rotateAround([0,0,0], ...)`.
32
+ Transforms apply left-to-right. `rotate()`, `scale()`, `mirror()` operate around bounding-box center. Use `rotateAround([0,0,0], ...)` to orbit around world origin.
69
33
 
70
- **Convention:** This matches the standard "post-multiply" convention. No surprises here, but worth noting because some systems (OpenSCAD) apply transforms in reverse order.
71
-
72
- For explicit transform objects:
73
- - `A.mul(B)` means **apply A, then B**.
74
- - `composeChain(A, B, C)` means **A -> B -> C**.
75
-
76
- **Rule for new code:** Keep this chain order everywhere. Document any operation that deviates.
34
+ For explicit transform objects: `A.mul(B)` = apply A then B; `composeChain(A, B, C)` = A→B→C.
77
35
 
78
36
  ## Assembly Frame Composition
79
37
 
80
- This is where regressions are most likely if convention is unclear.
81
-
82
- For a point in child geometry-local coordinates:
83
- - local -> `childBase` -> `jointMotion(value)` -> `jointFrame` -> `parentWorld`
84
-
85
- In Forge chain notation:
86
38
  ```ts
87
39
  childWorld = composeChain(childBase, jointMotion, jointFrame, parentWorld)
88
40
  ```
89
41
 
90
- Equivalent matrix-style equation (for reference):
91
- ```txt
92
- T_world_child = T_parent_world * T_joint_frame * T_joint_motion * T_child_base
93
- ```
94
-
95
- **Rule for new code:** In kinematics/assembly code, prefer `composeChain(...)` over manual `.mul(...).mul(...)` sequences to avoid order mistakes.
42
+ Prefer `composeChain(...)` over manual `.mul(...).mul(...)` in kinematics code to avoid order mistakes.
96
43
 
97
- ## Summary of Shield Points
98
-
99
- These are the places where ForgeCAD translates between "what the user means" and "what the kernel needs":
44
+ ## Summary
100
45
 
101
46
  | Convention | User sees | Kernel needs | Where we fix it |
102
47
  |---|---|---|---|
103
48
  | Winding | Any point order | CCW | `polygon()`, `path().close()` |
104
49
  | Up axis | Z-up | Y-up (Three.js) | `camera.up`, gizmo labels |
105
- | Revolution | "revolve this profile" | Profile in X-Y, X>0 | Documented, not auto-fixed |
50
+ | Revolution | "revolve this profile" | Profile in X-Y, X>0 | Documented only |
106
51
  | Face normals | Doesn't think about it | Outward-pointing | Manifold constructors |
107
- | Transform order | Left-to-right chain | Post-multiply | Native match, no fix needed |
108
-
109
- When adding new geometry operations, check this table. If the operation introduces a new convention mismatch between user intent and kernel requirement, either auto-fix it (preferred) or document it clearly in the API docs.
52
+ | Transform order | Left-to-right chain | Post-multiply | Native match |
@@ -5,246 +5,73 @@ skill-order: 1
5
5
 
6
6
  # Modeling Recipes
7
7
 
8
- This file collects patterns, best practices, debugging tips, and example snippets that are useful once you already know the model-building API.
9
-
10
8
  ## Iteration Bias
11
9
 
12
- - For unfamiliar or high-risk geometry work, start in a notebook and keep setup, experiments, and validation in separate cells until the structure is obvious.
13
- - Default to a buildable first pass instead of a long proposal when the user clearly wants geometry changed.
14
- - Replace a broken or incoherent model wholesale when that is faster and cleaner than incremental patching.
15
- - Keep printed hardware structurally honest: use it for guides, spacers, retainers, and moderate-load mechanisms; use wood or metal for primary strength.
16
- - Validate early with `forgecad run <file>` and refine from the actual runtime result.
17
- - Prefer a few clean part files over one giant script once a design has repeated hardware or a small mechanism.
18
-
19
- Notebook helpers worth using during iteration:
20
-
21
- - `show(...)` pins the current intermediate geometry in the viewport
22
- - `forgecad notebook view <file> preview` prints the preview cell with stored outputs in the terminal
23
- - `forgecad run <file>.forge-notebook.json` validates the preview cell and runs the usual spatial analysis
10
+ - Default to a buildable first pass instead of a long proposal.
11
+ - Replace a broken model wholesale when that is faster than incremental patching.
12
+ - Validate early with `forgecad run <file>`.
24
13
 
25
14
  ## Common Patterns
26
15
 
27
- ### Parametric Box with Holes
28
- ```javascript
29
- const w = param("Width", 80, { min: 40, max: 150, unit: "mm" });
30
- const h = param("Height", 60, { min: 30, max: 100, unit: "mm" });
31
- const t = param("Thickness", 5, { min: 2, max: 10, unit: "mm" });
32
- const holeD = param("Hole Diameter", 8, { min: 4, max: 20, unit: "mm" });
33
-
34
- const base = box(w, h, t);
35
- const hole = cylinder(t + 2, holeD / 2).translate(w / 2, h / 2, -1);
36
-
37
- return base.subtract(hole);
38
- ```
39
-
40
- ### Hollow Shell (Wall Thickness)
16
+ ### Hollow Shell
41
17
  ```javascript
42
- const outer = param("Outer Size", 50, { min: 20, max: 100, unit: "mm" });
43
- const wall = param("Wall", 3, { min: 1, max: 10, unit: "mm" });
44
-
45
18
  const outerBox = box(outer, outer, outer, true);
46
19
  const innerBox = box(outer - 2 * wall, outer - 2 * wall, outer - 2 * wall, true);
47
-
48
20
  return outerBox.subtract(innerBox);
49
21
  ```
50
22
 
51
- ### Array/Pattern
52
- ```javascript
53
- const count = param("Count", 5, { min: 2, max: 10 });
54
- const spacing = param("Spacing", 15, { min: 5, max: 30, unit: "mm" });
55
-
56
- let shapes = [];
57
- for (let i = 0; i < count; i++) {
58
- shapes.push(cylinder(10, 5).translate(i * spacing, 0, 0));
59
- }
60
-
61
- return union(...shapes);
62
- ```
63
-
64
- ### Sketch-Based Design
23
+ ### Sketch-Based Twist
65
24
  ```javascript
66
- const sides = param("Sides", 6, { min: 3, max: 12 });
67
- const radius = param("Radius", 25, { min: 10, max: 50, unit: "mm" });
68
- const height = param("Height", 60, { min: 20, max: 120, unit: "mm" });
69
- const wall = param("Wall", 3, { min: 1, max: 8, unit: "mm" });
70
-
71
25
  const outer = ngon(sides, radius);
72
26
  const inner = ngon(sides, radius - wall);
73
- const profile = outer.subtract(inner);
74
-
75
- return profile.extrude(height, { twist: 45, divisions: 32 });
27
+ return outer.subtract(inner).extrude(height, { twist: 45, divisions: 32 });
76
28
  ```
77
29
 
78
30
  ### Rounded Profiles
79
31
  ```javascript
32
+ // All convex corners — offset trick
80
33
  const base = rect(50, 30).offset(-3, 'Round').offset(3, 'Round');
81
- return base.extrude(10);
82
- ```
83
-
84
- Use that pattern when every convex corner should round. For mixed sharp-and-rounded outlines, fillet only the intended vertices instead:
85
-
86
- ```javascript
87
- const roofPoints = [
88
- [0, 0],
89
- [90, 0],
90
- [90, 44],
91
- [66, 74],
92
- [45, 86],
93
- [24, 74],
94
- [0, 44],
95
- ];
96
34
 
35
+ // Selected corners only
97
36
  const roof = filletCorners(roofPoints, [
98
37
  { index: 3, radius: 19 },
99
38
  { index: 4, radius: 19 },
100
39
  { index: 5, radius: 19 },
101
40
  ]);
102
-
103
- return roof.extrude(12);
104
- ```
105
-
106
- ### Chamfers and Fillets
107
- ```javascript
108
- const part = box(50, 50, 20);
109
- const chamfer = box(10, 60, 10)
110
- .rotate(0, 45, 0)
111
- .translate(50, -5, 15);
112
-
113
- return part.subtract(chamfer);
114
41
  ```
115
42
 
116
43
  ### Choosing the right sketch-rounding tool
117
44
 
118
- - `offset(-r).offset(+r)` for rounding every convex corner of a closed outline
119
- - `stroke(points, width, 'Round')` for centerline-based geometry such as ribs or traces
120
- - `filletCorners(points, ...)` for selective true-corner fillets on mixed profiles
45
+ - `offset(-r).offset(+r)` round every convex corner of a closed outline
46
+ - `stroke(points, width, 'Round')` centerline-based geometry (ribs, traces)
47
+ - `filletCorners(points, ...)` selective true-corner fillets on mixed profiles
121
48
 
122
49
  ## Best Practices
123
50
 
124
- ### Performance
125
- - Boolean operations are expensive; minimize them
126
- - Use parameters for values that might change
127
- - Avoid deep nesting of operations in loops
128
-
129
- ### Readability
130
- ```javascript
131
- const base = box(100, 100, 10);
132
- const hole = cylinder(12, 8);
133
- const result = base.subtract(hole.translate(50, 50, 0));
134
- return result;
135
- ```
136
-
137
- Prefer named intermediate values over deeply nested one-liners.
138
-
139
- ### Units
140
- - All dimensions are millimeters by default
141
- - Angles are degrees
142
- - Use the `unit` parameter option when it helps the reader
143
-
144
- ### Centering
145
- ```javascript
146
- const centered = box(50, 50, 50, true).translate(x, y, z);
147
- const corner = box(50, 50, 50).translate(x - 25, y - 25, z - 25);
148
- ```
149
-
150
- Centered primitives are usually easier to position.
51
+ - All dimensions in millimeters; angles in degrees.
52
+ - Primitives are centered on XY, base at Z=0. Use `placeReference('center', [0,0,0])` to center on all axes.
53
+ - Prefer named intermediate values over deeply nested one-liners.
54
+ - `union2d`, `difference2d`, `intersection2d` batch faster than chained `.add()` / `.subtract()`.
151
55
 
152
56
  ## Debugging
153
57
 
154
- ### Console Output
155
58
  ```javascript
156
- console.log("Width:", width);
157
59
  console.log("Volume:", shape.volume());
158
60
  ```
159
61
 
160
- ### Incremental Building
161
- ```javascript
162
- const base = box(50, 50, 10);
163
- // return base;
164
-
165
- const withHole = base.subtract(cylinder(12, 5).translate(25, 25, 0));
166
- // return withHole;
167
-
168
- return withHole.add(cylinder(20, 3).translate(25, 25, 10));
169
- ```
170
-
171
- For sketch-heavy work, compare the raw profile and the rounded profile before extruding:
62
+ For sketch-heavy work, compare the raw profile and rounded profile side-by-side before extruding:
172
63
 
173
64
  ```javascript
174
- const raw = polygon(roofPoints);
175
- const rounded = filletCorners(roofPoints, [
176
- { index: 3, radius: 19 },
177
- { index: 4, radius: 19 },
178
- { index: 5, radius: 19 },
179
- ]);
180
-
181
65
  return [
182
- { name: "Raw", sketch: raw },
183
- { name: "Rounded", sketch: rounded.translate(120, 0) },
66
+ { name: "Raw", sketch: polygon(roofPoints) },
67
+ { name: "Rounded", sketch: filletCorners(roofPoints, [...]).translate(120, 0) },
184
68
  ];
185
69
  ```
186
70
 
187
- ## Error Handling
188
-
189
- Common errors:
190
- - `"Kernel not initialized"` - internal/runtime issue, reload the app
191
- - `"Cannot read property of undefined"` - usually a bad variable name or missing declaration
192
- - invalid geometry - commonly caused by zero dimensions or self-intersecting sketches
193
- - script execution error - inspect the JS error in console output
194
-
195
- ## Example Snippets
196
-
197
- ### Parametric Phone Stand
198
- ```javascript
199
- const width = param("Width", 80, { min: 40, max: 150, unit: "mm" });
200
- const depth = param("Depth", 60, { min: 30, max: 100, unit: "mm" });
201
- const thick = param("Thickness", 5, { min: 2, max: 15, unit: "mm" });
202
- const backH = param("Back Height", 40, { min: 20, max: 80, unit: "mm" });
203
- const cableD = param("Cable Hole", 8, { min: 4, max: 15, unit: "mm" });
204
-
205
- const base = box(width, depth, thick);
206
- const back = box(width, thick, backH).translate(0, depth - thick, thick);
207
- const lip = box(width, 10, 8).translate(0, 0, thick);
208
- const hole = cylinder(thick + 2, cableD / 2)
209
- .rotate(90, 0, 0)
210
- .translate(width / 2, depth / 2, -1);
211
-
212
- return union(base, back, lip).subtract(hole);
213
- ```
214
-
215
- ### Multi-Object Scene with Colors
216
- ```javascript
217
- const base = box(100, 100, 5).color('#888888');
218
- const col1 = cylinder(40, 5).translate(20, 20, 5).color('#cc4444');
219
- const col2 = cylinder(40, 5).translate(80, 20, 5).color('#4444cc');
220
- const col3 = cylinder(40, 5).translate(50, 80, 5).color('#44cc44');
221
- const top = box(100, 100, 3).translate(0, 0, 45).color('#888888');
222
-
223
- return [
224
- { name: "Base", shape: base },
225
- { name: "Column A", shape: col1 },
226
- { name: "Column B", shape: col2 },
227
- { name: "Column C", shape: col3 },
228
- { name: "Top", shape: top },
229
- ];
230
- ```
231
-
232
- ### Entity-Based Design with Topology
233
- ```javascript
234
- const baseRect = rectangle(0, 0, 80, 60);
235
- const base = baseRect.extrude(20);
236
-
237
- const result = filletEdge(base.toShape(), base.edge('vert-br'), 8, [-1, -1])
238
- .hole(base.face('top'), { diameter: 6, u: -16, v: 10, depth: 8 });
239
-
240
- const holes = circularPattern(
241
- cylinder(25, 4).translate(40, 30, -1),
242
- 4, 40, 30,
243
- );
244
-
245
- return result.subtract(holes);
246
- ```
71
+ ## Common Errors
247
72
 
248
- Use the original tracked body (`base`) when you need semantic faces after edge finishing, and keep using its untouched sibling vertical tracked edges if you apply another supported fillet/chamfer later. Those sibling edges can now also survive a later supported union when the compiler still records one preserved propagated edge lineage for them. The currently selected finished edge is still recorded as a merged descendant set, so Forge does not claim a new durable tracked edge for that rewritten corner yet.
73
+ - `"Kernel not initialized"` internal/runtime issue, reload the app
74
+ - zero dimensions or self-intersecting sketches → invalid geometry
75
+ - wrong variable name → `"Cannot read property of undefined"`
249
76
 
250
77
  For larger runnable examples, read `examples/api/`.