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
@@ -5,109 +5,271 @@ skill-order: 100
5
5
 
6
6
  # Curves & Surfacing
7
7
 
8
- > **Auto-generated** from `src/forge/forge-public-api.ts`. Do not edit by hand — run `npm run gen:docs` to regenerate.
9
-
10
8
  Smooth curves, lofted surfaces, swept solids, and splines.
11
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
+
12
18
  ## Functions
13
19
 
14
20
  ### Curves & Surfacing
15
21
 
16
- Create smooth curves, lofted surfaces, and swept solids.
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.
17
40
 
18
- #### `spline2d()`
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.
19
42
 
20
43
  ```ts
21
44
  spline2d(points: Vec2[], options?: Spline2DOptions): Sketch
22
45
  ```
23
46
 
24
- Build a smooth Catmull-Rom spline sketch from 2D control points. 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.
47
+ **`Spline2DOptions`**
25
48
 
26
- <details><summary><code>Spline2DOptions</code></summary>
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.
27
60
 
28
61
  ```ts
29
- interface Spline2DOptions {
30
- /** Closed loop (default true). */
31
- closed?: boolean;
32
- /** Catmull-Rom tension in [0, 1]. 0 = very round, 1 = linear-ish. Default 0.5. */
33
- tension?: number;
34
- /** Samples per segment (minimum 3). Default 16. */
35
- samplesPerSegment?: number;
36
- /** For open splines, provide stroke width to return a solid Sketch. If omitted for open splines, an error is thrown. */
37
- strokeWidth?: number;
38
- /** Stroke join for open splines. Default 'Round'. */
39
- join?: "Round" | "Square";
40
- }
62
+ spline3d(points: Vec3[], options?: Spline3DOptions): Curve3D
41
63
  ```
42
64
 
43
- </details>
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.
44
70
 
45
- #### `spline3d()`
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().
46
74
 
47
75
  ```ts
48
- spline3d(points: Vec3$4[], options?: Spline3DOptions): Curve3D
76
+ loft(profiles: Sketch[], heights: number[], options?: LoftOptions): Shape
49
77
  ```
50
78
 
51
- Create a reusable 3D spline curve object (Catmull-Rom). The returned Curve3D provides sample(), pointAt(t), tangentAt(t), and length() for downstream use in sweep() or manual path operations.
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.
52
90
 
53
- <details><summary><code>Spline3DOptions</code></summary>
91
+ Performance note: uses level-set meshing, heavier than simple loft().
54
92
 
55
93
  ```ts
56
- interface Spline3DOptions {
57
- /** Closed loop (default false). */
58
- closed?: boolean;
59
- /** Catmull-Rom tension in [0, 1]. 0 = very round, 1 = linear-ish. Default 0.5. */
60
- tension?: number;
61
- }
94
+ loftAlongSpine(profiles: Sketch[], spine: Curve3D | Vec3[], tValues: number[], options?: LoftAlongSpineOptions): Shape
62
95
  ```
63
96
 
64
- </details>
97
+ **`LoftAlongSpineOptions`**
65
98
 
66
- #### `loft()`
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()`
67
107
 
68
108
  ```ts
69
- loft(profiles: Sketch[], heights: number[], options?: LoftOptions): Shape
109
+ sweep(profile: Sketch, path: SweepPathInput, options?: SweepOptions): Shape
70
110
  ```
71
111
 
72
- Loft between multiple sketches along Z stations. 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. Performance note: loft is significantly heavier than primitive/extrude/revolve. If the part is axis-symmetric (bottles, vases, knobs), prefer revolve().
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.
73
122
 
74
- <details><summary><code>LoftOptions</code></summary>
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.
75
128
 
76
129
  ```ts
77
- interface LoftOptions {
78
- /** Marching-grid edge length for level-set meshing. Smaller = finer. */
79
- edgeLength?: number;
80
- /** Optional extra bounds padding. */
81
- boundsPadding?: number;
82
- }
130
+ variableSweep(spine: SweepPathInput, sections: VariableSweepSection[], options?: VariableSweepOptions): Shape
83
131
  ```
84
132
 
85
- </details>
133
+ **`VariableSweepSection`**
134
+ - `t: number` — Parameter along the spine (0 = start, 1 = end).
135
+ - `profile: Sketch` — Cross-section profile at this station.
86
136
 
87
- #### `sweep()`
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.
88
160
 
89
161
  ```ts
90
- sweep(profile: Sketch, path: Curve3D | Vec3$4[], options?: SweepOptions): Shape
162
+ surfacePatch(curves: { ... }, options?: SurfacePatchOptions): Shape
91
163
  ```
92
164
 
93
- Sweep a 2D profile along a 3D path to create a solid. Path can be a Curve3D from spline3d() or an array of [x,y,z] points (polyline). The profile is interpreted in the local frame normal plane. Compatible sweeps can export through the OCCT exact route using the canonical path representation. Performance note: sweep uses level-set meshing internally. Prefer direct primitives/extrude/revolve when they can express the same shape.
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.
94
174
 
95
- <details><summary><code>SweepOptions</code></summary>
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
+ ```
96
222
 
97
223
  ```ts
98
- interface SweepOptions {
99
- /** Number of samples when path is a Curve3D. Default 48. */
100
- samples?: number;
101
- /** Marching-grid edge length for level-set meshing. Smaller = finer. */
102
- edgeLength?: number;
103
- /** Optional extra bounds padding. */
104
- boundsPadding?: number;
105
- /** Preferred "up" vector for local profile frame. Auto fallback is used near parallel segments. */
106
- up?: Vec3$4;
107
- }
224
+ transitionSurface(edgeA: TransitionEdge, edgeB: TransitionEdge, options?: TransitionSurfaceOptions): Shape
108
225
  ```
109
226
 
110
- </details>
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. |
111
273
 
112
274
  ---
113
275
 
@@ -119,94 +281,408 @@ interface SweepOptions {
119
281
 
120
282
  | Property | Type | Description |
121
283
  |----------|------|-------------|
122
- | `points` | `Vec3$4[]` | — |
284
+ | `points` | `Vec3[]` | — |
123
285
  | `closed` | `boolean` | — |
124
286
  | `tension` | `number` | — |
125
287
 
126
288
  **Methods:**
127
289
 
128
- - `sampleBySegment()` — sampleBySegment(samplesPerSegment?: number): Vec3$4[]
129
- - `sample()` — sample(count?: number): Vec3$4[]
130
- - `pointAt()` — pointAt(t: number): Vec3$4
131
- - `tangentAt()` — tangentAt(t: number): Vec3$4
132
- - `length()` — length(samples?: number): number
290
+ #### `sampleBySegment()` — Sample the curve with a fixed number of points per segment.
133
291
 
134
- ### `HermiteCurve3D`
292
+ ```ts
293
+ sampleBySegment(samplesPerSegment?: number): Vec3[]
294
+ ```
295
+
296
+ #### `sample()` — Sample the curve to an approximate total point count.
135
297
 
136
- A cubic Hermite curve in 3D space. Interpolates between two endpoints matching position and tangent (G1 continuity). Weight parameters control tangent magnitude, affecting the "reach" of the curve along each edge's direction before turning.
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`
137
577
 
138
578
  **Properties:**
139
579
 
140
580
  | Property | Type | Description |
141
581
  |----------|------|-------------|
142
- | `p0` | `Vec3$5` | Start position |
143
- | `p1` | `Vec3$5` | End position |
144
- | `t0` | `Vec3$5` | Scaled tangent at start (direction * weight * chordLength) |
145
- | `t1` | `Vec3$5` | Scaled tangent at end (direction * weight * chordLength) |
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) |
146
586
  | `chordLength` | `number` | Chord length (straight-line distance between endpoints) |
147
587
 
148
588
  **Methods:**
149
589
 
150
- - `pointAt()` — Evaluate position at parameter t ∈ [0, 1]
151
- - `tangentAt()` — Evaluate tangent (first derivative) at parameter t ∈ [0, 1]
152
- - `curvatureAt()` — Evaluate curvature vector (second derivative) at parameter t ∈ [0, 1]
153
- - `sample()` — Sample the curve as a polyline of evenly-spaced parameter values.
154
- - `length()` — Approximate arc length by sampling.
155
- - `sampleAdaptive()` — Sample with adaptive density — more points where curvature is higher. Returns at least `minCount` points, up to `maxCount`.
156
- - `toPolyline()` — Convert to a format compatible with sweep() path input.
590
+ #### `pointAt()` — Evaluate position at parameter t ∈ [0, 1]
157
591
 
158
- ### `QuinticHermiteCurve3D`
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
+ ```
159
631
 
160
- A quintic Hermite curve in 3D space. Interpolates between two endpoints matching position, tangent, and second derivative (G2 / curvature continuity). Uses degree-5 Hermite basis functions. Weight parameters scale tangent magnitudes relative to chord length. Curvature vectors are scaled by weight² * chordLength² for consistent behavior.
632
+ ### `QuinticHermiteCurve3D`
161
633
 
162
634
  **Properties:**
163
635
 
164
636
  | Property | Type | Description |
165
637
  |----------|------|-------------|
166
- | `p0` | `Vec3$5` | Start position |
167
- | `p1` | `Vec3$5` | End position |
168
- | `t0` | `Vec3$5` | Scaled tangent at start (direction * weight * chordLength) |
169
- | `t1` | `Vec3$5` | Scaled tangent at end (direction * weight * chordLength) |
170
- | `c0` | `Vec3$5` | Scaled second derivative at start (curvature * weight² * chordLength²) |
171
- | `c1` | `Vec3$5` | Scaled second derivative at end (curvature * weight² * chordLength²) |
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²) |
172
644
  | `chordLength` | `number` | Chord length (straight-line distance between endpoints) |
173
645
 
174
646
  **Methods:**
175
647
 
176
- - `pointAt()` — Evaluate position at parameter t ∈ [0, 1]
177
- - `tangentAt()` — Evaluate tangent (first derivative, normalized) at parameter t ∈ [0, 1]
178
- - `curvatureAt()` — Evaluate curvature vector (second derivative) at parameter t ∈ [0, 1]
179
- - `sample()` — Sample the curve as a polyline of evenly-spaced parameter values.
180
- - `length()` — Approximate arc length by sampling.
181
- - `sampleAdaptive()` — Sample with adaptive density — more points where curvature is higher. Returns at least `minCount` points, up to `maxCount`.
182
- - `toPolyline()` — Convert to a format compatible with sweep() path input.
648
+ #### `pointAt()` — Evaluate position at parameter t ∈ [0, 1]
183
649
 
184
- ### `PathBuilder`
650
+ ```ts
651
+ pointAt(t: number): Vec3
652
+ ```
185
653
 
186
- **Methods:**
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]
187
661
 
188
- - `getX()` — Current cursor X position.
189
- - `getY()` — Current cursor Y position.
190
- - `moveTo()` — moveTo(x: number, y: number): this
191
- - `lineTo()` — lineTo(x: number, y: number): this
192
- - `lineH()` — lineH(dx: number): this
193
- - `lineV()` — lineV(dy: number): this
194
- - `lineAngled()` — lineAngled(length: number, degrees: number): this
195
- - `lineBy()` — lineBy(dx: number, dy: number): this
196
- - `arcBy()` — arcBy(dx: number, dy: number, radius: number, clockwise?: boolean): this
197
- - `bezierBy()` — bezierBy(dcp1x: number, dcp1y: number, dcp2x: number, dcp2y: number, dx: number,
198
- - `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.
199
- - `tangentArcTo()` — G1-continuous arc — radius derived from current tangent + endpoint. Throws if endpoint is collinear with current direction.
200
- - `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. ```js // Arc centered at (10, 0), radius 50, from -30° to +30° path().arc(10, 0, 50, -30, 30).stroke(8, 'Round') ```
201
- - `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. ```js // Arc 90° CCW around (50, 50) path().moveTo(70, 50).arcAround(50, 50, 90) // Arc 45° CW around the origin path().moveTo(10, 0).arcAround(0, 0, -45) ```
202
- - `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. ```js // Arc 90° CCW around a center 20 units to the right path().moveTo(50, 50).arcAroundRelative(20, 0, 90) // Equivalent to: path().moveTo(50, 50).arcAround(70, 50, 90) ```
203
- - `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.
204
- - `bezierTo()` — Cubic bezier from current position to (x, y) via two control points.
205
- - `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). The second control point `(cp2x, cp2y)` must be provided — it controls the arrival curvature. For a fully automatic smooth curve, see `smoothThrough`.
206
- - `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.
207
- - `fillet()` Round the last corner (the junction between the previous two segments) with a tangent arc of the given radius. 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. ```js path().moveTo(0,0).lineTo(10,0).lineTo(10,10).fillet(2).lineTo(0,10).close() ```
208
- - `chamfer()` — Chamfer the last corner with a straight cut of the given distance. ```js path().moveTo(0,0).lineTo(10,0).lineTo(10,10).chamfer(2).lineTo(0,10).close() ```
209
- - `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. 'y' mirrors across the local Y-axis (flips X), or `[nx, ny]` for an arbitrary axis direction. ```js // Build right half, mirror to get full symmetric profile path().moveTo(0,0).lineTo(10,0).lineTo(10,5).mirror('x').close() ```
210
- - `close()` — Close the path and return a filled Sketch. If the path contains multiple sub-paths (multiple moveTo calls), the first sub-path is the outer contour and subsequent sub-paths are holes (subtracted from the outer contour).
211
- - `closeOffset()` — Close the path and return an offset version of the filled Sketch. Positive delta expands outward, negative shrinks inward.
212
- - `stroke()` — stroke(width: number, join?: "Round" | "Square"): Sketch
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
+ ```