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,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 |
@@ -0,0 +1,78 @@
1
+ ---
2
+ skill-group: recipes
3
+ skill-order: 5
4
+ ---
5
+
6
+ # Joint Design Recipes
7
+
8
+ How to build mechanical joints — clevis-tongue hinges, ball-and-socket, dovetails — that actually rotate without binding and stop where they should.
9
+
10
+ ## The Cavity Rule
11
+
12
+ Every mechanical joint has a **cavity** in one part and a **tenon** in the other. The cavity must be a real empty volume — not a gap implied by the absence of two separate solids.
13
+
14
+ If two adjacent parts in an assembly show a collision volume larger than the expected clearance volume in `forgecad run`, one part is missing its cavity. Both parts have solid material at the same joint position. This will look fine at rest pose but will block rotation and produce confusing joint behavior.
15
+
16
+ ```ts
17
+ // BAD — body has a stadium cap at both ends; the "slot" between two clevis tines
18
+ // is just empty space next to a solid body cap. The next phalanx's tongue knuckle
19
+ // has nowhere to go (it intersects the previous body's cap).
20
+ const body = stadiumBar(L); // cap at X=0 AND X=L
21
+ const tine1 = box(...).translate(L, Y_OFF, 0);
22
+ const tine2 = box(...).translate(L, -Y_OFF, 0);
23
+ let phalanx = union(body, tine1, tine2);
24
+
25
+ // GOOD — body ends FLAT before the joint. Tines extend forward to the pivot.
26
+ // The X = L-KNUCK_R..L+KNUCK_R volume between the tines is genuinely empty.
27
+ const body = box(L - KNUCK_R, TONG_T, H, true).translate((L - KNUCK_R) / 2, 0, 0);
28
+ const tongueKnuckle = knuckleDisc(0, 0, TONG_T); // proximal cap only
29
+ let phalanx = union(tongueKnuckle, body, tine1, tine2, ...tineCaps);
30
+ ```
31
+
32
+ After applying the cavity rule, `forgecad run` collision volume between adjacent parts in a clevis-tongue chain should drop to **zero** (or a few mm³ of clearance overlap). If it doesn't, there's still solid material where there should be a cavity.
33
+
34
+ ## Connecting Cantilevers
35
+
36
+ A clevis tine arm at Y=±Y_OFF is geometrically separate from a body at Y=±TONG_T/2. With Y_OFF > TONG_T/2 + clearance, there is a **physical gap** between them. The tines float — they would snap off as soon as load is applied.
37
+
38
+ Always add a **yoke**: a short slab spanning the full clevis width, sitting between the body's flat distal end and the tines' attachment point. The yoke fills the Y gap so material is continuous from the body through to each tine.
39
+
40
+ ```ts
41
+ const yokeLen = 3; // a few mm of structural overlap
42
+ const yokeStart = L - KNUCK_R - yokeLen;
43
+ const totalY = (Y_OFF + TINE_T / 2) * 2; // full clevis width
44
+ const yoke = box(yokeLen, totalY, H, true)
45
+ .translate(yokeStart + yokeLen / 2, 0, 0);
46
+ phalanx = union(phalanx, yoke);
47
+ ```
48
+
49
+ ## Hard Stops vs Slider Limits
50
+
51
+ `addRevolute({ min: 0, max: 90 })` sets **slider limits** — the viewport won't let the user drag past them, but the geometry permits any rotation. There is no physical stop.
52
+
53
+ For a **geometric** hard stop (parts can't backbend past extension, or can't curl past full closure), add a small protrusion on one part that interferes with the other at the limit angle:
54
+
55
+ - **Extension stop at 0°** (typical for fingers, knees, elbows): add a small "lip" on the dorsal side of the proximal end of the child phalanx, sized so it just touches the parent's distal dorsal corner at 0°. Negative rotation (backbending) is then blocked by part-on-part contact.
56
+ - **Flexion stop at θmax**: add a similar lip on the palmar side, or rely on the body-to-body collision when bodies meet.
57
+
58
+ Verify with `forgecad run` at the limit poses — the contact pair should show ~0 mm³ collision (just touching), and rotation past the limit should report a non-zero collision volume.
59
+
60
+ ## Knuckle Sizing
61
+
62
+ For a clevis-tongue joint with body height H, the tongue knuckle radius and clevis tine knuckle radius must satisfy:
63
+
64
+ ```
65
+ KNUCK_R >= H / 2
66
+ ```
67
+
68
+ If the knuckle radius is smaller than the body's half-height, the body's corners protrude beyond the knuckle envelope. When the joint rotates, those corners sweep through space outside the cylindrical envelope and collide with the adjacent part.
69
+
70
+ Setting `KNUCK_R = H / 2` exactly makes the body cross-section a stadium that perfectly fits the knuckle envelope.
71
+
72
+ ## Verification Workflow
73
+
74
+ 1. Build the joint at rest pose. Run `forgecad run`. Check collision volumes.
75
+ 2. If adjacent parts in the joint show > clearance-volume of overlap → missing cavity (apply the cavity rule).
76
+ 3. Render with `--focus PartName` to inspect each part in isolation. The clevis end should clearly show a gap between the tines (the cavity).
77
+ 4. Render at curl angles (set joint debug params) at 30°, 60°, 90°. No new collisions should appear from rotation.
78
+ 5. Render at -10° (backbend test). Either no rotation possible (geometric stop in place) or rotation occurs and you need to add a stop.
@@ -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/`.