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,688 @@
1
+ ---
2
+ skill-group: curves
3
+ skill-order: 100
4
+ ---
5
+
6
+ # Curves & Surfacing
7
+
8
+ Smooth curves, lofted surfaces, swept solids, and splines.
9
+
10
+ ## Contents
11
+
12
+ - [Curves & Surfacing](#curves-surfacing) — `hermiteTransitionG2`, `spline2d`, `spline3d`, `loft`, `loftAlongSpine`, `sweep`, `variableSweep`, `surfacePatch`, `transitionCurve`, `transitionSurface`, `connectEdges`
13
+ - [Curve3D](#curve3d)
14
+ - [PathBuilder](#pathbuilder) — Line Segments, Arcs, Curves, Closing & Output
15
+ - [HermiteCurve3D](#hermitecurve3d)
16
+ - [QuinticHermiteCurve3D](#quintichermitecurve3d)
17
+
18
+ ## Functions
19
+
20
+ ### Curves & Surfacing
21
+
22
+ #### `hermiteTransitionG2()` — Create a quintic Hermite transition curve between two edge endpoints (G2 continuity).
23
+
24
+ The curve starts at `a.point` tangent to `a.tangent` with curvature `a.curvature`, and ends at `b.point` tangent to `b.tangent` with curvature `b.curvature`, with smooth G2-continuous interpolation matching position, tangent, and curvature.
25
+
26
+ ```ts
27
+ hermiteTransitionG2(a: QuinticHermiteCurveEndpoint, b: QuinticHermiteCurveEndpoint): QuinticHermiteCurve3D
28
+ ```
29
+
30
+ **`QuinticHermiteCurveEndpoint`**
31
+
32
+ | Option | Type | Description |
33
+ |--------|------|-------------|
34
+ | `point` | `Vec3` | Position |
35
+ | `tangent` | `Vec3` | Tangent direction (will be normalized internally) |
36
+ | `curvature?` | `Vec3` | Second derivative / curvature vector. Default [0, 0, 0]. |
37
+ | `weight?` | `number` | Weight: scales tangent magnitude relative to chord length. Default 1.0. |
38
+
39
+ #### `spline2d()` — Build a smooth Catmull-Rom spline sketch from 2D control points.
40
+
41
+ A closed spline (default) returns a filled profile. An open spline requires a strokeWidth option to produce a solid sketch. Use tension (0..1, default 0.5) to control curve tightness.
42
+
43
+ ```ts
44
+ spline2d(points: Vec2[], options?: Spline2DOptions): Sketch
45
+ ```
46
+
47
+ **`Spline2DOptions`**
48
+
49
+ | Option | Type | Description |
50
+ |--------|------|-------------|
51
+ | `closed?` | `boolean` | Closed loop (default true). |
52
+ | `tension?` | `number` | Catmull-Rom tension in [0, 1]. 0 = very round, 1 = linear-ish. Default 0.5. |
53
+ | `samplesPerSegment?` | `number` | Samples per segment (minimum 3). Default 16. |
54
+ | `strokeWidth?` | `number` | For open splines, provide stroke width to return a solid Sketch. If omitted for open splines, an error is thrown. |
55
+ | `join?` | `"Round" | "Square"` | Stroke join for open splines. Default 'Round'. |
56
+
57
+ #### `spline3d()` — Create a reusable 3D spline curve object (Catmull-Rom).
58
+
59
+ The returned Curve3D provides sample(), pointAt(t), tangentAt(t), and length() for downstream use in sweep() or manual path operations.
60
+
61
+ ```ts
62
+ spline3d(points: Vec3[], options?: Spline3DOptions): Curve3D
63
+ ```
64
+
65
+ **`Spline3DOptions`**
66
+ - `closed?: boolean` — Closed loop (default false).
67
+ - `tension?: number` — Catmull-Rom tension in [0, 1]. 0 = very round, 1 = linear-ish. Default 0.5.
68
+
69
+ #### `loft()` — Loft between multiple sketches along Z stations.
70
+
71
+ Profiles can differ in topology and vertex count: interpolation is done on signed-distance fields and meshed with level-set extraction. Heights must be strictly increasing. Compatible loft stacks can export through the OCCT exact route.
72
+
73
+ Performance note: loft is significantly heavier than primitive/extrude/revolve. If the part is axis-symmetric (bottles, vases, knobs), prefer revolve().
74
+
75
+ ```ts
76
+ loft(profiles: Sketch[], heights: number[], options?: LoftOptions): Shape
77
+ ```
78
+
79
+ **`LoftOptions`**
80
+ - `edgeLength?: number` — Marching-grid edge length for level-set meshing. Smaller = finer.
81
+ - `boundsPadding?: number` — Optional extra bounds padding.
82
+
83
+ #### `loftAlongSpine()` — Loft between multiple profiles positioned along an arbitrary 3D spine curve.
84
+
85
+ Unlike loft() which only supports Z heights, loftAlongSpine() places each profile at a position along a 3D spine, oriented perpendicular to the spine tangent. This enables lofting along curved paths — e.g., a wing root-to-tip transition that follows a swept-back leading edge.
86
+
87
+ The tValues array specifies where each profile sits along the spine (0 = start, 1 = end). Must have the same length as profiles and be in [0, 1].
88
+
89
+ Internally uses variableSweep infrastructure with SDF interpolation.
90
+
91
+ Performance note: uses level-set meshing, heavier than simple loft().
92
+
93
+ ```ts
94
+ loftAlongSpine(profiles: Sketch[], spine: Curve3D | Vec3[], tValues: number[], options?: LoftAlongSpineOptions): Shape
95
+ ```
96
+
97
+ **`LoftAlongSpineOptions`**
98
+
99
+ | Option | Type | Description |
100
+ |--------|------|-------------|
101
+ | `samples?` | `number` | Number of samples when spine is a Curve3D. Default 48. |
102
+ | `edgeLength?` | `number` | Marching-grid edge length for level-set meshing. Smaller = finer. |
103
+ | `boundsPadding?` | `number` | Optional extra bounds padding. |
104
+ | `up?` | `Vec3` | Preferred "up" vector for local profile frame. Auto fallback is used near parallel segments. |
105
+
106
+ #### `sweep()`
107
+
108
+ ```ts
109
+ sweep(profile: Sketch, path: SweepPathInput, options?: SweepOptions): Shape
110
+ ```
111
+
112
+ **`SweepOptions`**
113
+
114
+ | Option | Type | Description |
115
+ |--------|------|-------------|
116
+ | `samples?` | `number` | Number of samples when path is a Curve3D. Default 48. |
117
+ | `edgeLength?` | `number` | Marching-grid edge length for level-set meshing. Smaller = finer. |
118
+ | `boundsPadding?` | `number` | Optional extra bounds padding. |
119
+ | `up?` | `Vec3` | Preferred "up" vector for local profile frame. Auto fallback is used near parallel segments. |
120
+
121
+ #### `variableSweep()` — Sweep a variable cross-section along a 3D spine curve.
122
+
123
+ Unlike sweep(), which uses a single constant profile, variableSweep() interpolates between multiple profiles at different stations along the spine. This enables organic shapes like tapering tubes, bone-like structures, and sculptural forms.
124
+
125
+ Each section specifies a t parameter (0 = start, 1 = end of spine) and a 2D profile sketch. The SDF-based level-set mesher smoothly blends between profiles at intermediate positions.
126
+
127
+ Performance note: like sweep(), this uses level-set meshing internally.
128
+
129
+ ```ts
130
+ variableSweep(spine: SweepPathInput, sections: VariableSweepSection[], options?: VariableSweepOptions): Shape
131
+ ```
132
+
133
+ **`VariableSweepSection`**
134
+ - `t: number` — Parameter along the spine (0 = start, 1 = end).
135
+ - `profile: Sketch` — Cross-section profile at this station.
136
+
137
+ **`VariableSweepOptions`**
138
+
139
+ | Option | Type | Description |
140
+ |--------|------|-------------|
141
+ | `samples?` | `number` | Number of samples when spine is a Curve3D. Default 48. |
142
+ | `edgeLength?` | `number` | Marching-grid edge length for level-set meshing. Smaller = finer. |
143
+ | `boundsPadding?` | `number` | Optional extra bounds padding. |
144
+ | `up?` | `Vec3` | Preferred "up" vector for local profile frame. Auto fallback is used near parallel segments. |
145
+
146
+ #### `surfacePatch()` — Create a smooth surface patch from 4 boundary curves (Coons patch).
147
+
148
+ The four curves form the boundary of a quadrilateral patch:
149
+
150
+ - bottom: u=0..1 at v=0 (from corner00 to corner10)
151
+ - top: u=0..1 at v=1 (from corner01 to corner11)
152
+ - left: v=0..1 at u=0 (from corner00 to corner01)
153
+ - right: v=0..1 at u=1 (from corner10 to corner11)
154
+
155
+ The interior is filled using bilinear Coons patch interpolation: P(u,v) = Lc(u,v) + Ld(u,v) - B(u,v)
156
+
157
+ The result is a thin solid created by offsetting the surface mesh along its normals by the specified thickness.
158
+
159
+ Note: curves should meet at corners. Small gaps are tolerated.
160
+
161
+ ```ts
162
+ surfacePatch(curves: { ... }, options?: SurfacePatchOptions): Shape
163
+ ```
164
+
165
+ **`SurfacePatchOptions`**
166
+ - `resolution?: number` — Number of samples along each direction. Default 24.
167
+ - `thickness?: number` — Thickness of the generated solid. Default 0.5.
168
+
169
+ #### `transitionCurve()` — Create a smooth transition curve between two edges.
170
+
171
+ Returns a `HermiteCurve3D` that starts at `edgeA.point` tangent to `edgeA.tangent` and ends at `edgeB.point` tangent to `edgeB.tangent`.
172
+
173
+ The curve maintains G1 continuity (matching tangent direction) at both endpoints. Weight parameters control the shape of the transition.
174
+
175
+ ```js
176
+ ```js
177
+
178
+ // Connect two edges with a balanced transition const curve = transitionCurve( { point: [0, 0, 0], tangent: [1, 0, 0] }, { point: [10, 5, 0], tangent: [1, 0, 0] }, );
179
+
180
+ ```
181
+
182
+ // Weighted: curve hugs edge A longer
183
+ const weighted = transitionCurve(
184
+ { point: [0, 0, 0], tangent: [1, 0, 0] },
185
+ { point: [10, 5, 0], tangent: [1, 0, 0] },
186
+ { weightA: 2.0, weightB: 0.5 },
187
+ );
188
+ ```
189
+
190
+ ```ts
191
+ transitionCurve(edgeA: TransitionEdge, edgeB: TransitionEdge, options?: TransitionCurveOptions): HermiteCurve3D
192
+ ```
193
+
194
+ **`TransitionEdge`**
195
+ - `point: Vec3` — Connection point on the edge. Can be any point along the edge where the transition should connect.
196
+ - `tangent: Vec3` — Tangent direction at the connection point. This is the direction the curve should initially follow when leaving this edge. For a straight edge, this is typically the edge direction pointing "outward" (away from the body of the edge, toward the other edge).
197
+ - `normal?: Vec3` — Surface normal at the connection point (optional). Used as a hint for the sweep frame's up vector.
198
+
199
+ **`TransitionCurveOptions`**
200
+ - `weightA?: number` — Weight for the start edge. Controls tangent magnitude at the start. - 1.0 (default): balanced transition - > 1.0: curve follows start edge longer before turning - < 1.0: curve turns sooner at the start
201
+ - `weightB?: number` — Weight for the end edge. Controls tangent magnitude at the end. - 1.0 (default): balanced transition - > 1.0: curve follows end edge longer before turning - < 1.0: curve turns sooner at the end
202
+ - `samples?: number` — Number of sample points for the output polyline. Default 64. Higher values give smoother curves at the cost of more geometry.
203
+
204
+ #### `transitionSurface()` — Create a solid transition surface between two edges by sweeping a profile along a Hermite transition curve.
205
+
206
+ This produces a watertight solid that smoothly connects the two edges. Works with both Manifold and OCCT backends.
207
+
208
+ ```js
209
+ ```js
210
+
211
+ // Circular tube connecting two edges const tube = transitionSurface( { point: [0, 0, 0], tangent: [1, 0, 0] }, { point: [10, 5, 3], tangent: [0, 1, 0] }, { radius: 0.5 }, );
212
+
213
+ ```
214
+
215
+ // Custom profile with weights
216
+ const custom = transitionSurface(
217
+ { point: [0, 0, 0], tangent: [1, 0, 0] },
218
+ { point: [10, 5, 3], tangent: [0, 1, 0] },
219
+ { profile: mySketch, weightA: 1.5, weightB: 0.8 },
220
+ );
221
+ ```
222
+
223
+ ```ts
224
+ transitionSurface(edgeA: TransitionEdge, edgeB: TransitionEdge, options?: TransitionSurfaceOptions): Shape
225
+ ```
226
+
227
+
228
+ **`TransitionSurfaceOptions`** extends TransitionCurveOptions
229
+
230
+ | Option | Type | Description |
231
+ |--------|------|-------------|
232
+ | `profile?` | `Sketch` | Cross-section profile to sweep along the transition curve. If omitted, a circular profile with `radius` is used. |
233
+ | `radius?` | `number` | Radius of circular cross-section (used when `profile` is omitted). Default: 5% of chord length. |
234
+ | `up?` | `Vec3` | Preferred up vector for the sweep frame. Default: auto-detected. |
235
+ | `edgeLength?` | `number` | Edge length for level-set meshing. Smaller = finer. |
236
+ | `boundsPadding?` | `number` | Extra bounds padding for level-set meshing. |
237
+ | `width`, `height` | | — |
238
+
239
+ #### `connectEdges()` — Create a transition surface or solid bridge between two edge segments.
240
+
241
+ Tangents can be inferred from neighboring geometry or supplied explicitly through `options`. This is useful for loft-like blends where you want a direct connection between two edge spans.
242
+
243
+ ```ts
244
+ connectEdges(edgeA: EdgeSegment, edgeB: EdgeSegment, options?: ConnectEdgesOptions): Shape
245
+ ```
246
+
247
+ **`EdgeSegment`**
248
+
249
+ | Option | Type | Description |
250
+ |--------|------|-------------|
251
+ | `index` | `number` | Stable index within the extraction (deterministic for a given mesh). |
252
+ | `direction` | `Vec3` | Normalized direction from start → end. |
253
+ | `dihedralAngle` | `number` | Dihedral angle in degrees (0 = coplanar, 180 = knife edge). |
254
+ | `convex` | `boolean` | true = outside corner (convex), false = inside corner (concave). |
255
+ | `normalA` | `Vec3` | Normal of first adjacent face. |
256
+ | `normalB` | `Vec3` | Normal of second adjacent face (same as normalA for boundary edges). |
257
+ | `boundary` | `boolean` | true if this is a boundary (unmatched) edge — unusual for closed solids. |
258
+ | `start`, `end`, `midpoint`, `length` | | — |
259
+
260
+
261
+ **`ConnectEdgesOptions`** extends TransitionSurfaceOptions
262
+
263
+ | Option | Type | Description |
264
+ |--------|------|-------------|
265
+ | `endA?` | `EdgeEnd` | Which end of edge A to connect. Default: 'start'. |
266
+ | `endB?` | `EdgeEnd` | Which end of edge B to connect. Default: 'start'. |
267
+ | `tangentModeA?` | `TangentMode` | Tangent mode for edge A. Default: 'along'. |
268
+ | `tangentModeB?` | `TangentMode` | Tangent mode for edge B. Default: 'along'. |
269
+ | `tangentA?` | `Vec3` | Explicit tangent for edge A. |
270
+ | `tangentB?` | `Vec3` | Explicit tangent for edge B. |
271
+ | `flipA?` | `boolean` | Flip tangent A. |
272
+ | `flipB?` | `boolean` | Flip tangent B. |
273
+
274
+ ---
275
+
276
+ ## Classes
277
+
278
+ ### `Curve3D`
279
+
280
+ **Properties:**
281
+
282
+ | Property | Type | Description |
283
+ |----------|------|-------------|
284
+ | `points` | `Vec3[]` | — |
285
+ | `closed` | `boolean` | — |
286
+ | `tension` | `number` | — |
287
+
288
+ **Methods:**
289
+
290
+ #### `sampleBySegment()` — Sample the curve with a fixed number of points per segment.
291
+
292
+ ```ts
293
+ sampleBySegment(samplesPerSegment?: number): Vec3[]
294
+ ```
295
+
296
+ #### `sample()` — Sample the curve to an approximate total point count.
297
+
298
+ ```ts
299
+ sample(count?: number): Vec3[]
300
+ ```
301
+
302
+ #### `pointAt()` — Return the position on the curve at normalized parameter `t` in `[0, 1]`. O(1), no allocations.
303
+
304
+ ```ts
305
+ pointAt(t: number): Vec3
306
+ ```
307
+
308
+ #### `tangentAt()` — Return a unit tangent vector at normalized parameter `t` in `[0, 1]`. O(1), analytical derivative.
309
+
310
+ ```ts
311
+ tangentAt(t: number): Vec3
312
+ ```
313
+
314
+ #### `length()` — Approximate the curve length by polyline sampling.
315
+
316
+ ```ts
317
+ length(samples?: number): number
318
+ ```
319
+
320
+ ### `PathBuilder`
321
+
322
+ **Line Segments**
323
+
324
+ #### `moveTo()` — Move the cursor to an absolute position without drawing a segment.
325
+
326
+ When called after the initial [`path()`](/docs/sketch#path), this establishes the start of the outline. Calling `moveTo` again mid-path starts a new sub-path (hole in `close()`, separate segment for [`stroke()`](/docs/sketch#stroke)).
327
+
328
+ ```ts
329
+ moveTo(x: number, y: number): this
330
+ ```
331
+
332
+ #### `lineTo()` — Draw a straight line from the current cursor to an absolute position.
333
+
334
+ ```ts
335
+ lineTo(x: number, y: number): this
336
+ ```
337
+
338
+ #### `lineH()` — Draw a horizontal line segment by `dx` units from the current cursor.
339
+
340
+ Positive `dx` moves right; negative moves left.
341
+
342
+ ```ts
343
+ lineH(dx: number): this
344
+ ```
345
+
346
+ #### `lineV()` — Draw a vertical line segment by `dy` units from the current cursor.
347
+
348
+ Positive `dy` moves up; negative moves down.
349
+
350
+ ```ts
351
+ lineV(dy: number): this
352
+ ```
353
+
354
+ #### `lineAngled()` — Draw a line at the given angle and length from the current cursor.
355
+
356
+ Angle convention: `0°` points right (+X), `90°` points up (+Y).
357
+
358
+ ```ts
359
+ // L-bracket with angled return
360
+ path().moveTo(0, 0).lineH(50).lineV(-70).lineAngled(20, 235).stroke(4);
361
+ ```
362
+
363
+ ```ts
364
+ lineAngled(length: number, degrees: number): this
365
+ ```
366
+
367
+ **Arcs**
368
+
369
+ #### `arc()` — Draw an arc defined by center, radius, and angle range (no trig needed). If the path has no segments yet, automatically moves to the arc start. Positive sweep (startDeg < endDeg) = CCW, negative = CW.
370
+
371
+ ```js
372
+ // Arc centered at (10, 0), radius 50, from -30° to +30°
373
+ path().arc(10, 0, 50, -30, 30).stroke(8, 'Round')
374
+ ```
375
+
376
+ ```ts
377
+ arc(cx: number, cy: number, radius: number, startDeg: number, endDeg: number): this
378
+ ```
379
+
380
+ #### `arcTo()` — Draw a circular arc from the current position to (x, y) with the given radius. `clockwise=true` → arc curves to the right of the start→end direction. `clockwise=false` → arc curves to the left of the start→end direction.
381
+
382
+ ```ts
383
+ arcTo(x: number, y: number, radius: number, clockwise?: boolean): this
384
+ ```
385
+
386
+ #### `tangentArcTo()` — G1-continuous arc — radius derived from current tangent + endpoint. Throws if endpoint is collinear with current direction.
387
+
388
+ ```ts
389
+ tangentArcTo(x: number, y: number): this
390
+ ```
391
+
392
+ **Curves**
393
+
394
+ #### `bezierTo()` — Cubic bezier from current position to (x, y) via two control points.
395
+
396
+ ```ts
397
+ bezierTo(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number): this
398
+ ```
399
+
400
+ **Closing & Output**
401
+
402
+ #### `close()` — Close the path and return a filled [`Sketch`](/docs/sketch#sketch).
403
+
404
+ The winding of the polygon is automatically corrected to CCW (the expected orientation for ForgeCAD sketches). If the path contains multiple sub-paths (started with subsequent `moveTo` calls), the first sub-path is the outer contour and subsequent sub-paths become holes subtracted from it.
405
+
406
+ Edge labels (assigned with `.label('name')`) are transferred to the resulting sketch and propagate through `extrude()`, `revolve()`, `loft()`, and `sweep()` into named faces on the resulting [`Shape`](/docs/core#shape).
407
+
408
+ ```ts
409
+ const triangle = path().moveTo(0, 0).lineH(50).lineV(30).close();
410
+
411
+ // With a hole (second sub-path)
412
+ const frame = path()
413
+ .moveTo(0, 0).lineH(40).lineV(30).lineH(-40).close(); // outer
414
+ // (hole would be added with another moveTo and line sequence before close)
415
+ ```
416
+
417
+ ```ts
418
+ close(): Sketch
419
+ ```
420
+
421
+ #### `closeLabel()` — Label the closing segment and close the path. Shorthand for labeling the implicit line from the last point back to the start, then closing.
422
+
423
+ ```ts
424
+ closeLabel(name: string): Sketch
425
+ ```
426
+
427
+ #### [`stroke()`](/docs/sketch#stroke) — Thicken an open polyline (centerline) into a solid filled profile with uniform width.
428
+
429
+ Expands the path into a closed profile `width` units wide (half-width on each side of the centerline). Use `'Round'` for ribs, wire traces, and organic profiles — it adds semicircular endcaps and rounds joins. Use `'Square'` (default) for sharp miter joins without endcaps.
430
+
431
+ Not the same as rounding corners of a closed polygon — for mixed sharp-and-rounded outlines, build the polygon first and apply [`filletCorners()`](/docs/core#filletcorners).
432
+
433
+ ```ts
434
+ // Square-join L-bracket
435
+ const bracket = path().moveTo(0, 0).lineH(50).lineV(-70).lineAngled(20, 235).stroke(4);
436
+
437
+ // Round-join rib
438
+ const rib = path().moveTo(0, 0).lineH(60).stroke(6, 'Round');
439
+
440
+ // Equivalent standalone form
441
+ const wire = stroke([[0, 0], [50, 0], [50, -70]], 4);
442
+ ```
443
+
444
+ and semicircular endcaps.
445
+
446
+ ```ts
447
+ stroke(width: number, join?: "Round" | "Square"): Sketch
448
+ ```
449
+
450
+ #### `label()` — Label the most recently added segment. Labels are born here and grow into face names when the sketch is extruded, lofted, swept, or revolved.
451
+
452
+ Labels must be unique within a path. Each segment can have at most one label.
453
+
454
+ ```ts
455
+ label(name: string): this
456
+ ```
457
+
458
+ **Other**
459
+
460
+ #### `getX()` — Current cursor X position.
461
+
462
+ ```ts
463
+ getX(): number
464
+ ```
465
+
466
+ #### `getY()` — Current cursor Y position.
467
+
468
+ ```ts
469
+ getY(): number
470
+ ```
471
+
472
+ #### `lineBy()` — Draw a line by a relative `(dx, dy)` displacement from the current cursor.
473
+
474
+ ```ts
475
+ lineBy(dx: number, dy: number): this
476
+ ```
477
+
478
+ #### `arcBy()` — Draw an arc to a point offset from the current cursor.
479
+
480
+ ```ts
481
+ arcBy(dx: number, dy: number, radius: number, clockwise?: boolean): this
482
+ ```
483
+
484
+ #### `bezierBy()` — Draw a cubic Bezier using control points relative to the current cursor.
485
+
486
+ ```ts
487
+ bezierBy(dcp1x: number, dcp1y: number, dcp2x: number, dcp2y: number, dx: number, dy: number): this
488
+ ```
489
+
490
+ #### `arcAround()` — Arc around a known center point, sweeping by the given angle. Radius is derived from the distance between the current position and the center. Positive sweep = CCW (math convention), negative = CW.
491
+
492
+ ```js
493
+ // Arc 90° CCW around (50, 50)
494
+ path().moveTo(70, 50).arcAround(50, 50, 90)
495
+ // Arc 45° CW around the origin
496
+ path().moveTo(10, 0).arcAround(0, 0, -45)
497
+ ```
498
+
499
+ ```ts
500
+ arcAround(cx: number, cy: number, sweepDeg: number): this
501
+ ```
502
+
503
+ #### `arcAroundRelative()` — Arc around a center point given as an offset from the current position. `(dx, dy)` is the vector from the current point to the center. Positive sweep = CCW (math convention), negative = CW.
504
+
505
+ ```js
506
+ // Arc 90° CCW around a center 20 units to the right
507
+ path().moveTo(50, 50).arcAroundRelative(20, 0, 90)
508
+ // Equivalent to: path().moveTo(50, 50).arcAround(70, 50, 90)
509
+ ```
510
+
511
+ ```ts
512
+ arcAroundRelative(dx: number, dy: number, sweepDeg: number): this
513
+ ```
514
+
515
+ #### `smoothCapTo()` — Smooth three-arc end cap from the current position to (endX, endY). Inserts: small corner arc → large cap arc → small corner arc, all G1-continuous.
516
+
517
+ ```ts
518
+ smoothCapTo(endX: number, endY: number, cornerRadius: number, capRadius: number): this
519
+ ```
520
+
521
+ #### `tangentBezierTo()` — G1-continuous cubic bezier — first control point is auto-derived from the current tangent direction. `weight` controls how far the auto-placed control point extends along the tangent (default: 1/3 of the chord).
522
+
523
+ The second control point `(cp2x, cp2y)` must be provided — it controls the arrival curvature. For a fully automatic smooth curve, see `smoothThrough`.
524
+
525
+ ```ts
526
+ tangentBezierTo(cp2x: number, cp2y: number, x: number, y: number, weight?: number): this
527
+ ```
528
+
529
+ #### `smoothThrough()` — Catmull-Rom spline through a list of waypoints from the current position. The current position is included as the first point. The last waypoint becomes the new cursor position.
530
+
531
+ ```ts
532
+ smoothThrough(waypoints: [ number, number ][], tension?: number): this
533
+ ```
534
+
535
+ #### [`fillet()`](/docs/core#fillet) — Round the last corner (the junction between the previous two segments) with a tangent arc of the given radius.
536
+
537
+ Must be called after at least two line/arc segments that form a corner. The fillet trims back both segments and inserts a tangent arc.
538
+
539
+ ```js
540
+ path().moveTo(0,0).lineTo(10,0).lineTo(10,10).fillet(2).lineTo(0,10).close()
541
+ ```
542
+
543
+ ```ts
544
+ fillet(radius: number): this
545
+ ```
546
+
547
+ #### [`chamfer()`](/docs/core#chamfer) — Chamfer the last corner with a straight cut of the given distance.
548
+
549
+ ```js
550
+ path().moveTo(0,0).lineTo(10,0).lineTo(10,10).chamfer(2).lineTo(0,10).close()
551
+ ```
552
+
553
+ ```ts
554
+ chamfer(distance: number): this
555
+ ```
556
+
557
+ #### `mirror()` — Mirror all existing segments across an axis and append the mirrored copy in reverse order, creating a symmetric path. The axis passes through the current cursor position.
558
+
559
+ 'y' mirrors across the local Y-axis (flips X), or `[nx, ny]` for an arbitrary axis direction.
560
+
561
+ ```js
562
+ // Build right half, mirror to get full symmetric profile
563
+ path().moveTo(0,0).lineTo(10,0).lineTo(10,5).mirror('x').close()
564
+ ```
565
+
566
+ ```ts
567
+ mirror(axis: "x" | "y" | [ number, number ]): this
568
+ ```
569
+
570
+ #### `closeOffset()` — Close the path and return an offset version of the filled Sketch. Positive delta expands outward, negative shrinks inward.
571
+
572
+ ```ts
573
+ closeOffset(delta: number, join?: "Round" | "Square" | "Miter"): Sketch
574
+ ```
575
+
576
+ ### `HermiteCurve3D`
577
+
578
+ **Properties:**
579
+
580
+ | Property | Type | Description |
581
+ |----------|------|-------------|
582
+ | `p0` | `Vec3` | Start position |
583
+ | `p1` | `Vec3` | End position |
584
+ | `t0` | `Vec3` | Scaled tangent at start (direction * weight * chordLength) |
585
+ | `t1` | `Vec3` | Scaled tangent at end (direction * weight * chordLength) |
586
+ | `chordLength` | `number` | Chord length (straight-line distance between endpoints) |
587
+
588
+ **Methods:**
589
+
590
+ #### `pointAt()` — Evaluate position at parameter t ∈ [0, 1]
591
+
592
+ ```ts
593
+ pointAt(t: number): Vec3
594
+ ```
595
+
596
+ #### `tangentAt()` — Evaluate tangent (first derivative) at parameter t ∈ [0, 1]
597
+
598
+ ```ts
599
+ tangentAt(t: number): Vec3
600
+ ```
601
+
602
+ #### `curvatureAt()` — Evaluate curvature vector (second derivative) at parameter t ∈ [0, 1]
603
+
604
+ ```ts
605
+ curvatureAt(t: number): Vec3
606
+ ```
607
+
608
+ #### `sample()` — Sample the curve as a polyline of evenly-spaced parameter values.
609
+
610
+ ```ts
611
+ sample(count?: number): Vec3[]
612
+ ```
613
+
614
+ #### `length()` — Approximate arc length by sampling.
615
+
616
+ ```ts
617
+ length(samples?: number): number
618
+ ```
619
+
620
+ #### `sampleAdaptive()` — Sample with adaptive density — more points where curvature is higher. Returns at least `minCount` points, up to `maxCount`.
621
+
622
+ ```ts
623
+ sampleAdaptive(minCount?: number, maxCount?: number): Vec3[]
624
+ ```
625
+
626
+ #### `toPolyline()` — Convert to a format compatible with sweep() path input.
627
+
628
+ ```ts
629
+ toPolyline(samples?: number): Vec3[]
630
+ ```
631
+
632
+ ### `QuinticHermiteCurve3D`
633
+
634
+ **Properties:**
635
+
636
+ | Property | Type | Description |
637
+ |----------|------|-------------|
638
+ | `p0` | `Vec3` | Start position |
639
+ | `p1` | `Vec3` | End position |
640
+ | `t0` | `Vec3` | Scaled tangent at start (direction * weight * chordLength) |
641
+ | `t1` | `Vec3` | Scaled tangent at end (direction * weight * chordLength) |
642
+ | `c0` | `Vec3` | Scaled second derivative at start (curvature * weight² * chordLength²) |
643
+ | `c1` | `Vec3` | Scaled second derivative at end (curvature * weight² * chordLength²) |
644
+ | `chordLength` | `number` | Chord length (straight-line distance between endpoints) |
645
+
646
+ **Methods:**
647
+
648
+ #### `pointAt()` — Evaluate position at parameter t ∈ [0, 1]
649
+
650
+ ```ts
651
+ pointAt(t: number): Vec3
652
+ ```
653
+
654
+ #### `tangentAt()` — Evaluate tangent (first derivative, normalized) at parameter t ∈ [0, 1]
655
+
656
+ ```ts
657
+ tangentAt(t: number): Vec3
658
+ ```
659
+
660
+ #### `curvatureAt()` — Evaluate curvature vector (second derivative) at parameter t ∈ [0, 1]
661
+
662
+ ```ts
663
+ curvatureAt(t: number): Vec3
664
+ ```
665
+
666
+ #### `sample()` — Sample the curve as a polyline of evenly-spaced parameter values.
667
+
668
+ ```ts
669
+ sample(count?: number): Vec3[]
670
+ ```
671
+
672
+ #### `length()` — Approximate arc length by sampling.
673
+
674
+ ```ts
675
+ length(samples?: number): number
676
+ ```
677
+
678
+ #### `sampleAdaptive()` — Sample with adaptive density — more points where curvature is higher. Returns at least `minCount` points, up to `maxCount`.
679
+
680
+ ```ts
681
+ sampleAdaptive(minCount?: number, maxCount?: number): Vec3[]
682
+ ```
683
+
684
+ #### `toPolyline()` — Convert to a format compatible with sweep() path input.
685
+
686
+ ```ts
687
+ toPolyline(samples?: number): Vec3[]
688
+ ```