forgecad 0.9.14 → 0.9.15

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 (219) hide show
  1. package/LICENSE +6 -4
  2. package/README.md +8 -4
  3. package/dist/assets/{AdminPage-eWGs2K6H.js → AdminPage-CDyGUinA.js} +2 -2
  4. package/dist/assets/{BenchmarkPage-CTrLKfpo.js → BenchmarkPage-DfPMY_-d.js} +4 -15
  5. package/dist/assets/{BlogPage-5nPesyds.js → BlogPage-kF0fkdJT.js} +2 -2
  6. package/dist/assets/{DocsPage-C4Y3nbYc.js → DocsPage-B954L3YN.js} +9 -3
  7. package/dist/assets/EditorApp-Beb-IZ0y.js +14014 -0
  8. package/dist/assets/{EditorApp-BAnckbsk.css → EditorApp-CuDLxKqL.css} +698 -0
  9. package/dist/assets/{EmbedViewer-C8fB4n5U.js → EmbedViewer-C77B-TrF.js} +3 -3
  10. package/dist/assets/{LandingPageProofDriven-jSz0LaMM.js → LandingPageProofDriven-Cr6fXMDj.js} +35 -37
  11. package/dist/assets/LegalPage-BRlScr9A.css +91 -0
  12. package/dist/assets/LegalPage-Dzklqmmg.js +39 -0
  13. package/dist/assets/{PricingPage-BMedqFef.css → PricingPage-BPF6HKyO.css} +25 -0
  14. package/dist/assets/{PricingPage-B83B90zh.js → PricingPage-zWXkvlwl.js} +19 -19
  15. package/dist/assets/{SettingsPage-DY889pcu.js → SettingsPage-Bz0of4KQ.js} +2 -2
  16. package/dist/assets/app-CE3sYcV7.css +3890 -0
  17. package/dist/assets/{app-bEww1ic4.js → app-D3kDkggg.js} +2293 -946
  18. package/dist/assets/cli/{render-Cho2uKG_.js → render-DSY3mMQa.js} +337 -7
  19. package/dist/assets/{constructionHistoryWorker-HYwzJY4m.js → constructionHistoryWorker-gpDo-uH2.js} +927 -243
  20. package/dist/assets/{evalWorker-CjQwJSE-.js → evalWorker-CU0Ke6DP.js} +7800 -4164
  21. package/dist/assets/{forgecad_geometry-CH2nvuLA.js → forgecad_geometry-Dgceylq9.js} +43 -1
  22. package/dist/assets/forgecad_geometry_bg-dD4RNQF1.wasm +0 -0
  23. package/dist/assets/{inspectWorker-DeRnMVv1.js → inspectWorker-COyp8XXA.js} +927 -243
  24. package/dist/assets/{javascript-70-4uGcz.js → javascript-1kQXfVaz.js} +1 -1
  25. package/dist/assets/landing-proof-driven-DiGqdtWa.js +18 -0
  26. package/dist/assets/{landing-proof-driven-oFYW6mjz.css → landing-proof-driven-ORyigZ6p.css} +13 -7
  27. package/dist/assets/legalContent-ZfFGMmi4.js +251 -0
  28. package/dist/assets/{manifold-CG9Fokx-.js → manifold-BRI5prcH.js} +1 -1
  29. package/dist/assets/{manifold-uRzgk5O8.js → manifold-C-3h2M7p.js} +2 -2
  30. package/dist/assets/{manifold-rmfAcdwF.js → manifold-DNkrUWpA.js} +1 -1
  31. package/dist/assets/{reportWorker-4cW_ZpoS.js → reportWorker-CdBz5bNg.js} +7538 -10857
  32. package/dist/assets/{scalar-sampling-budget-CfDiFvh7.js → scalar-sampling-budget-wJF98aY9.js} +6935 -4331
  33. package/dist/assets/{scanProxyWorker-Bs2TDgLw.js → scanProxyWorker-B-9VbLIs.js} +32 -1
  34. package/dist/assets/{solver-DuJAO8S6.js → solver-BZ9LPTHs.js} +1 -1
  35. package/dist/assets/solver_bg-DAHZJ_rw.wasm +0 -0
  36. package/dist/assets/{targets-D6PWsv6X.js → targets-B9sGB5nB.js} +1 -1
  37. package/dist/assets/{vendor-react-Da3A2QmU.js → vendor-react-6j1Kke-Y.js} +6 -5
  38. package/dist/cli/render.html +1 -1
  39. package/dist/docs/index.html +2 -2
  40. package/dist/docs-raw/AI/ai-native-cad.md +50 -0
  41. package/dist/docs-raw/AI/usage.md +3 -12
  42. package/dist/docs-raw/CLI.md +30 -10
  43. package/dist/docs-raw/component-model.md +27 -11
  44. package/dist/docs-raw/generated/assembly.md +301 -212
  45. package/dist/docs-raw/generated/concepts.md +235 -237
  46. package/dist/docs-raw/generated/core.md +283 -6
  47. package/dist/docs-raw/generated/curves.md +274 -361
  48. package/dist/docs-raw/generated/lib.md +7 -1
  49. package/dist/docs-raw/generated/output.md +19 -4
  50. package/dist/docs-raw/generated/runtime-names.md +41 -0
  51. package/dist/docs-raw/generated/sdf.md +31 -0
  52. package/dist/docs-raw/generated/sheet-metal.md +9 -0
  53. package/dist/docs-raw/generated/sketch.md +44 -1
  54. package/dist/docs-raw/generated/viewport.md +11 -3
  55. package/dist/docs-raw/guides/coordinate-system.md +20 -16
  56. package/dist/docs-raw/guides/geometry-conventions.md +2 -2
  57. package/dist/docs-raw/guides/inspection-bundles.md +2 -1
  58. package/dist/docs-raw/guides/joint-design.md +24 -0
  59. package/dist/docs-raw/guides/positioning.md +13 -3
  60. package/dist/docs-raw/legal/privacy.md +63 -0
  61. package/dist/docs-raw/legal/software-license.md +55 -0
  62. package/dist/docs-raw/legal/terms.md +87 -0
  63. package/dist/docs-raw/skills/forgecad-3d-reconstruction.md +1 -1
  64. package/dist/docs-raw/skills/forgecad-blockout-model.md +1 -1
  65. package/dist/docs-raw/skills/forgecad-component-model.md +11 -2
  66. package/dist/docs-raw/skills/forgecad-high-level-spec.md +1 -1
  67. package/dist/docs-raw/skills/forgecad-image-replicator.md +8 -8
  68. package/dist/docs-raw/skills/forgecad-lld.md +1 -1
  69. package/dist/docs-raw/skills/forgecad-make-a-model.md +1 -1
  70. package/dist/docs-raw/skills/forgecad-model-grader.md +2 -2
  71. package/dist/docs-raw/skills/forgecad-prepare-prompt.md +2 -2
  72. package/dist/docs-raw/skills/forgecad-project.md +1 -1
  73. package/dist/docs-raw/skills/forgecad-reconstruction-benchmark.md +1 -1
  74. package/dist/docs-raw/skills/forgecad-render-inspect.md +4 -2
  75. package/dist/docs-raw/skills/forgecad-visual-spec.md +1 -1
  76. package/dist/docs-raw/skills/forgecad.md +4 -3
  77. package/dist/index.html +40 -12
  78. package/dist/llms.txt +8 -0
  79. package/dist/site.webmanifest +1 -1
  80. package/dist/sitemap.xml +49 -13
  81. package/dist-cli/{check-compiler-U5SOPN7X.js → check-compiler-SDX5QIXI.js} +1 -2
  82. package/dist-cli/{check-query-propagation-XOKNSSYU.js → check-query-propagation-EAYEFT77.js} +1 -2
  83. package/dist-cli/{chunk-EXWGNL6K.js → chunk-N4O47JLF.js} +12540 -9046
  84. package/dist-cli/forgecad.js +1786 -679
  85. package/dist-cli/{forgecad_geometry-GYVNKPIE.js → forgecad_geometry-QOQIIP53.js} +42 -1
  86. package/dist-cli/forgecad_geometry_bg.wasm +0 -0
  87. package/dist-cli/{solver-46FFSK2U.js → solver-OK4HECRH.js} +0 -1
  88. package/dist-cli/solver_bg.wasm +0 -0
  89. package/dist-skill/CONTEXT.md +1117 -721
  90. package/dist-skill/SKILL.md +3 -2
  91. package/dist-skill/docs/API/core/concepts.md +64 -1
  92. package/dist-skill/docs/CLI.md +30 -10
  93. package/dist-skill/docs/generated/assembly.md +277 -229
  94. package/dist-skill/docs/generated/core.md +283 -6
  95. package/dist-skill/docs/generated/curves.md +272 -362
  96. package/dist-skill/docs/generated/lib.md +7 -1
  97. package/dist-skill/docs/generated/output.md +19 -4
  98. package/dist-skill/docs/generated/runtime-names.md +41 -0
  99. package/dist-skill/docs/generated/sdf.md +31 -0
  100. package/dist-skill/docs/generated/sheet-metal.md +9 -0
  101. package/dist-skill/docs/generated/sketch.md +44 -2
  102. package/dist-skill/docs/generated/viewport.md +2 -87
  103. package/dist-skill/docs/guides/coordinate-system.md +20 -16
  104. package/dist-skill/docs/guides/geometry-conventions.md +2 -2
  105. package/dist-skill/docs/guides/inspection-bundles.md +2 -1
  106. package/dist-skill/docs/guides/joint-design.md +24 -0
  107. package/dist-skill/docs/guides/positioning.md +13 -3
  108. package/dist-skill/library/forgecad-component-model/SKILL.md +10 -1
  109. package/dist-skill/library/forgecad-image-replicator/SKILL.md +6 -6
  110. package/dist-skill/library/forgecad-image-replicator/scripts/compare_images.py +166 -0
  111. package/dist-skill/library/forgecad-model-grader/SKILL.md +1 -1
  112. package/dist-skill/library/forgecad-prepare-prompt/SKILL.md +1 -1
  113. package/dist-skill/library/forgecad-render-inspect/SKILL.md +3 -1
  114. package/examples/api/assembly-kinematics-foundation.forge.js +65 -0
  115. package/examples/api/assembly-kinematics-four-bar.forge.js +115 -0
  116. package/examples/api/assembly-kinematics-limb.forge.js +116 -0
  117. package/examples/api/connector-frame-rig-chain.forge.js +102 -0
  118. package/examples/api/exact-sheet-shell-assembly.forge.js +0 -2
  119. package/examples/api/exact-surface-studio.forge.js +6 -8
  120. package/examples/api/helix-basics.forge.js +6 -6
  121. package/examples/api/lean-foundations/README.md +12 -0
  122. package/examples/api/lean-foundations/curve-blend-exact.forge.js +22 -0
  123. package/examples/api/lean-foundations/curve-fit-interpolation.forge.js +18 -0
  124. package/examples/api/lean-foundations/curve-helix-canonicalization.forge.js +27 -0
  125. package/examples/api/lean-foundations/curve-route-canonicalization.forge.js +16 -0
  126. package/examples/api/lean-foundations/curve-trim-reverse.forge.js +24 -0
  127. package/examples/api/lean-foundations/exact-curve-arc.forge.js +36 -0
  128. package/examples/api/mixed-edge-finishes-proof.forge.js +8 -11
  129. package/examples/api/route3d-elbow.forge.js +68 -0
  130. package/examples/api/transition-curves.forge.js +44 -15
  131. package/examples/api/y-blend-corner-showcase.forge.js +0 -2
  132. package/examples/generative/coral-vase.forge.js +1 -1
  133. package/examples/nurbs-tube.forge.js +1 -1
  134. package/package.json +14 -13
  135. package/dist/assets/EditorApp-lXv53A1m.js +0 -13610
  136. package/dist/assets/app-CsHnaBWt.css +0 -1789
  137. package/dist/assets/forgecad_geometry_bg-C5_E9Oa9.wasm +0 -0
  138. package/dist/assets/solver_bg-CWvv4lnN.wasm +0 -0
  139. package/dist/docs-raw/API/README.md +0 -16
  140. package/dist/docs-raw/API/core/concepts.md +0 -118
  141. package/dist/docs-raw/INDEX.md +0 -138
  142. package/dist/docs-raw/RELEASING.md +0 -87
  143. package/dist/docs-raw/agent-native-api.md +0 -27
  144. package/dist/docs-raw/beta-deployment.md +0 -304
  145. package/dist/docs-raw/beta-operations.md +0 -325
  146. package/dist/docs-raw/blueprint-first.md +0 -145
  147. package/dist/docs-raw/cli-monetization.md +0 -112
  148. package/dist/docs-raw/coding-best-practices.md +0 -120
  149. package/dist/docs-raw/coding.md +0 -340
  150. package/dist/docs-raw/deployment.md +0 -374
  151. package/dist/docs-raw/guides/skill-maintenance.md +0 -161
  152. package/dist/docs-raw/guides/surface-members.md +0 -82
  153. package/dist/docs-raw/harbor-cli.md +0 -854
  154. package/dist/docs-raw/internals/backend-vocabulary.md +0 -35
  155. package/dist/docs-raw/internals/compiler.md +0 -307
  156. package/dist/docs-raw/internals/constraint-solver-quality.md +0 -161
  157. package/dist/docs-raw/internals/constraint-solver.md +0 -176
  158. package/dist/docs-raw/internals/shape-from-slices.md +0 -152
  159. package/dist/docs-raw/internals/sketch-2d-pipeline.md +0 -108
  160. package/dist/docs-raw/platform/admin.md +0 -45
  161. package/dist/docs-raw/platform/architecture.md +0 -82
  162. package/dist/docs-raw/platform/auth.md +0 -139
  163. package/dist/docs-raw/platform/email.md +0 -67
  164. package/dist/docs-raw/platform/google-oauth-setup.md +0 -88
  165. package/dist/docs-raw/platform/observability.md +0 -197
  166. package/dist/docs-raw/platform/projects.md +0 -111
  167. package/dist/docs-raw/platform/sharing.md +0 -90
  168. package/dist/docs-raw/product/README.md +0 -39
  169. package/dist/docs-raw/product/api-as-product-language.md +0 -13
  170. package/dist/docs-raw/product/business-model.md +0 -15
  171. package/dist/docs-raw/product/competitive-positioning.md +0 -17
  172. package/dist/docs-raw/product/creative-manufacturing.md +0 -15
  173. package/dist/docs-raw/product/founder-story.md +0 -11
  174. package/dist/docs-raw/product/manufacturing-workflows.md +0 -15
  175. package/dist/docs-raw/product/onboarding-first-experience.md +0 -256
  176. package/dist/docs-raw/product/product-loop.md +0 -17
  177. package/dist/docs-raw/product/strategic-decisions.md +0 -22
  178. package/dist/docs-raw/product/user-outreach-email-templates.md +0 -161
  179. package/dist/docs-raw/product/user-segments.md +0 -15
  180. package/dist/docs-raw/product/vision.md +0 -26
  181. package/dist/docs-raw/rl-environments.md +0 -350
  182. package/dist/docs-raw/runbook.md +0 -611
  183. package/dist-cli/check-compiler-U5SOPN7X.js.map +0 -1
  184. package/dist-cli/check-query-propagation-XOKNSSYU.js.map +0 -1
  185. package/dist-cli/chunk-EXWGNL6K.js.map +0 -1
  186. package/dist-cli/forgecad.js.map +0 -1
  187. package/dist-cli/forgecad_geometry-GYVNKPIE.js.map +0 -1
  188. package/dist-cli/solver-46FFSK2U.js.map +0 -1
  189. package/dist-skill/SKILL-dev.md +0 -145
  190. package/dist-skill/docs-dev/API/core/concepts.md +0 -118
  191. package/dist-skill/docs-dev/CLI.md +0 -677
  192. package/dist-skill/docs-dev/agent-native-api.md +0 -27
  193. package/dist-skill/docs-dev/blueprint-first.md +0 -145
  194. package/dist-skill/docs-dev/coding-best-practices.md +0 -120
  195. package/dist-skill/docs-dev/coding.md +0 -340
  196. package/dist-skill/docs-dev/component-model.md +0 -164
  197. package/dist-skill/docs-dev/generated/assembly.md +0 -794
  198. package/dist-skill/docs-dev/generated/core.md +0 -2117
  199. package/dist-skill/docs-dev/generated/curves.md +0 -2583
  200. package/dist-skill/docs-dev/generated/lib.md +0 -169
  201. package/dist-skill/docs-dev/generated/output.md +0 -247
  202. package/dist-skill/docs-dev/generated/sdf.md +0 -446
  203. package/dist-skill/docs-dev/generated/sheet-metal.md +0 -504
  204. package/dist-skill/docs-dev/generated/sketch.md +0 -1811
  205. package/dist-skill/docs-dev/generated/viewport.md +0 -585
  206. package/dist-skill/docs-dev/generated/wood.md +0 -108
  207. package/dist-skill/docs-dev/guides/coordinate-system.md +0 -46
  208. package/dist-skill/docs-dev/guides/geometry-conventions.md +0 -52
  209. package/dist-skill/docs-dev/guides/inspection-bundles.md +0 -485
  210. package/dist-skill/docs-dev/guides/joint-design.md +0 -78
  211. package/dist-skill/docs-dev/guides/modeling-recipes.md +0 -78
  212. package/dist-skill/docs-dev/guides/positioning.md +0 -161
  213. package/dist-skill/docs-dev/guides/skill-maintenance.md +0 -161
  214. package/dist-skill/docs-dev/internals/backend-vocabulary.md +0 -35
  215. package/dist-skill/docs-dev/internals/compiler.md +0 -307
  216. package/dist-skill/docs-dev/internals/constraint-solver-quality.md +0 -161
  217. package/dist-skill/docs-dev/internals/constraint-solver.md +0 -176
  218. package/dist-skill/docs-dev/internals/sketch-2d-pipeline.md +0 -108
  219. package/dist-skill/library/forgecad-image-replicator/scripts/compare_images.mjs +0 -289
@@ -235,7 +235,7 @@ function denormalize(value, array) {
235
235
  throw new Error("Invalid component type.");
236
236
  }
237
237
  }
238
- function normalize$3(value, array) {
238
+ function normalize$4(value, array) {
239
239
  switch (array.constructor) {
240
240
  case Float32Array:
241
241
  return value;
@@ -487,7 +487,7 @@ const MathUtils = {
487
487
  * @param {TypedArray} array - The typed array that defines the data type of the value.
488
488
  * @return {number} The normalize value.
489
489
  */
490
- normalize: normalize$3,
490
+ normalize: normalize$4,
491
491
  /**
492
492
  * Denormalizes the given value according to the given typed array.
493
493
  *
@@ -9728,7 +9728,7 @@ class BufferAttribute {
9728
9728
  * @return {BufferAttribute} A reference to this instance.
9729
9729
  */
9730
9730
  setComponent(index2, component, value) {
9731
- if (this.normalized) value = normalize$3(value, this.array);
9731
+ if (this.normalized) value = normalize$4(value, this.array);
9732
9732
  this.array[index2 * this.itemSize + component] = value;
9733
9733
  return this;
9734
9734
  }
@@ -9751,7 +9751,7 @@ class BufferAttribute {
9751
9751
  * @return {BufferAttribute} A reference to this instance.
9752
9752
  */
9753
9753
  setX(index2, x2) {
9754
- if (this.normalized) x2 = normalize$3(x2, this.array);
9754
+ if (this.normalized) x2 = normalize$4(x2, this.array);
9755
9755
  this.array[index2 * this.itemSize] = x2;
9756
9756
  return this;
9757
9757
  }
@@ -9774,7 +9774,7 @@ class BufferAttribute {
9774
9774
  * @return {BufferAttribute} A reference to this instance.
9775
9775
  */
9776
9776
  setY(index2, y2) {
9777
- if (this.normalized) y2 = normalize$3(y2, this.array);
9777
+ if (this.normalized) y2 = normalize$4(y2, this.array);
9778
9778
  this.array[index2 * this.itemSize + 1] = y2;
9779
9779
  return this;
9780
9780
  }
@@ -9797,7 +9797,7 @@ class BufferAttribute {
9797
9797
  * @return {BufferAttribute} A reference to this instance.
9798
9798
  */
9799
9799
  setZ(index2, z2) {
9800
- if (this.normalized) z2 = normalize$3(z2, this.array);
9800
+ if (this.normalized) z2 = normalize$4(z2, this.array);
9801
9801
  this.array[index2 * this.itemSize + 2] = z2;
9802
9802
  return this;
9803
9803
  }
@@ -9820,7 +9820,7 @@ class BufferAttribute {
9820
9820
  * @return {BufferAttribute} A reference to this instance.
9821
9821
  */
9822
9822
  setW(index2, w2) {
9823
- if (this.normalized) w2 = normalize$3(w2, this.array);
9823
+ if (this.normalized) w2 = normalize$4(w2, this.array);
9824
9824
  this.array[index2 * this.itemSize + 3] = w2;
9825
9825
  return this;
9826
9826
  }
@@ -9835,8 +9835,8 @@ class BufferAttribute {
9835
9835
  setXY(index2, x2, y2) {
9836
9836
  index2 *= this.itemSize;
9837
9837
  if (this.normalized) {
9838
- x2 = normalize$3(x2, this.array);
9839
- y2 = normalize$3(y2, this.array);
9838
+ x2 = normalize$4(x2, this.array);
9839
+ y2 = normalize$4(y2, this.array);
9840
9840
  }
9841
9841
  this.array[index2 + 0] = x2;
9842
9842
  this.array[index2 + 1] = y2;
@@ -9854,9 +9854,9 @@ class BufferAttribute {
9854
9854
  setXYZ(index2, x2, y2, z2) {
9855
9855
  index2 *= this.itemSize;
9856
9856
  if (this.normalized) {
9857
- x2 = normalize$3(x2, this.array);
9858
- y2 = normalize$3(y2, this.array);
9859
- z2 = normalize$3(z2, this.array);
9857
+ x2 = normalize$4(x2, this.array);
9858
+ y2 = normalize$4(y2, this.array);
9859
+ z2 = normalize$4(z2, this.array);
9860
9860
  }
9861
9861
  this.array[index2 + 0] = x2;
9862
9862
  this.array[index2 + 1] = y2;
@@ -9876,10 +9876,10 @@ class BufferAttribute {
9876
9876
  setXYZW(index2, x2, y2, z2, w2) {
9877
9877
  index2 *= this.itemSize;
9878
9878
  if (this.normalized) {
9879
- x2 = normalize$3(x2, this.array);
9880
- y2 = normalize$3(y2, this.array);
9881
- z2 = normalize$3(z2, this.array);
9882
- w2 = normalize$3(w2, this.array);
9879
+ x2 = normalize$4(x2, this.array);
9880
+ y2 = normalize$4(y2, this.array);
9881
+ z2 = normalize$4(z2, this.array);
9882
+ w2 = normalize$4(w2, this.array);
9883
9883
  }
9884
9884
  this.array[index2 + 0] = x2;
9885
9885
  this.array[index2 + 1] = y2;
@@ -12298,6 +12298,55 @@ function cloneShapeWorkplanePlacement(placement) {
12298
12298
  placement: cloneSketchPlacementModel(placement.placement)
12299
12299
  };
12300
12300
  }
12301
+ function formatValidationValue(value) {
12302
+ if (typeof value === "number") return Number.isNaN(value) ? "NaN" : String(value);
12303
+ if (value === void 0) return "undefined";
12304
+ if (typeof value === "string") return JSON.stringify(value);
12305
+ try {
12306
+ return JSON.stringify(value) ?? String(value);
12307
+ } catch {
12308
+ return String(value);
12309
+ }
12310
+ }
12311
+ function requireFiniteNumber(value, label) {
12312
+ if (typeof value !== "number" || !Number.isFinite(value)) {
12313
+ throw new Error(`${label} must be a finite number, got ${formatValidationValue(value)}`);
12314
+ }
12315
+ return value;
12316
+ }
12317
+ function requirePositiveFiniteNumber(value, label) {
12318
+ const n = requireFiniteNumber(value, label);
12319
+ if (n <= 0) throw new Error(`${label} must be a positive finite number, got ${formatValidationValue(value)}`);
12320
+ return n;
12321
+ }
12322
+ function requireFiniteVec3$1(value, label) {
12323
+ if (!Array.isArray(value) || value.length !== 3) {
12324
+ throw new Error(`${label} must be [x, y, z] with finite numbers, got ${formatValidationValue(value)}`);
12325
+ }
12326
+ return [
12327
+ requireFiniteNumber(value[0], `${label}[0]`),
12328
+ requireFiniteNumber(value[1], `${label}[1]`),
12329
+ requireFiniteNumber(value[2], `${label}[2]`)
12330
+ ];
12331
+ }
12332
+ function requireNonZeroFiniteVec3(value, label) {
12333
+ const v = requireFiniteVec3$1(value, label);
12334
+ if (v[0] === 0 && v[1] === 0 && v[2] === 0) throw new Error(`${label} must not be [0, 0, 0]`);
12335
+ return v;
12336
+ }
12337
+ function requireFiniteMat4(value, label) {
12338
+ if (!Array.isArray(value) || value.length !== 16) {
12339
+ throw new Error(`${label} must be a 4x4 matrix array with 16 finite numbers, got ${formatValidationValue(value)}`);
12340
+ }
12341
+ return value.map((entry, index2) => requireFiniteNumber(entry, `${label}[${index2}]`));
12342
+ }
12343
+ function requireNonZeroFiniteScale3(value, label) {
12344
+ const scale2 = typeof value === "number" ? [requireFiniteNumber(value, label), requireFiniteNumber(value, label), requireFiniteNumber(value, label)] : requireFiniteVec3$1(value, label);
12345
+ if (Math.abs(scale2[0]) < 1e-12 || Math.abs(scale2[1]) < 1e-12 || Math.abs(scale2[2]) < 1e-12) {
12346
+ throw new Error(`${label} must have finite non-zero components, got ${formatValidationValue(value)}`);
12347
+ }
12348
+ return scale2;
12349
+ }
12301
12350
  const EPS$3 = 1e-10;
12302
12351
  function subVec3(a2, b) {
12303
12352
  return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
@@ -12379,9 +12428,6 @@ function solveRotateAroundAngle(axis, pivot, movingPoint, targetPoint, options =
12379
12428
  function identityMatrix() {
12380
12429
  return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
12381
12430
  }
12382
- function toMat4(input) {
12383
- return input instanceof Transform ? input.toArray() : input;
12384
- }
12385
12431
  function multiplyMat4(a2, b) {
12386
12432
  const out = new Array(16).fill(0);
12387
12433
  for (let col = 0; col < 4; col++) {
@@ -12458,23 +12504,40 @@ class Transform {
12458
12504
  }
12459
12505
  /** Wrap an existing `Transform` or raw 4x4 matrix as a `Transform`. */
12460
12506
  static from(input) {
12461
- return input instanceof Transform ? input : new Transform(input);
12507
+ return input instanceof Transform ? input : new Transform(requireFiniteMat4(input, "Transform.from() matrix"));
12462
12508
  }
12463
12509
  /** Create a translation transform. */
12464
12510
  static translation(x2, y2, z2) {
12465
- return new Transform([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x2, y2, z2, 1]);
12511
+ return new Transform([
12512
+ 1,
12513
+ 0,
12514
+ 0,
12515
+ 0,
12516
+ 0,
12517
+ 1,
12518
+ 0,
12519
+ 0,
12520
+ 0,
12521
+ 0,
12522
+ 1,
12523
+ 0,
12524
+ requireFiniteNumber(x2, "Transform.translation() x"),
12525
+ requireFiniteNumber(y2, "Transform.translation() y"),
12526
+ requireFiniteNumber(z2, "Transform.translation() z"),
12527
+ 1
12528
+ ]);
12466
12529
  }
12467
12530
  /** Create a uniform or per-axis scale transform. */
12468
12531
  static scale(v) {
12469
- const sx = typeof v === "number" ? v : v[0];
12470
- const sy = typeof v === "number" ? v : v[1];
12471
- const sz = typeof v === "number" ? v : v[2];
12532
+ const [sx, sy, sz] = requireNonZeroFiniteScale3(v, "Transform.scale() scale");
12472
12533
  return new Transform([sx, 0, 0, 0, 0, sy, 0, 0, 0, 0, sz, 0, 0, 0, 0, 1]);
12473
12534
  }
12474
12535
  /** Create a rotation around an arbitrary axis, optionally about a pivot. */
12475
12536
  static rotationAxis(axis, angleDeg, pivot = [0, 0, 0]) {
12476
- const [ux, uy, uz] = normalizeVec3$3(axis);
12477
- const rad = angleDeg * Math.PI / 180;
12537
+ const [ux, uy, uz] = normalizeVec3$3(requireNonZeroFiniteVec3(axis, "Transform.rotationAxis() axis"));
12538
+ const degrees = requireFiniteNumber(angleDeg, "Transform.rotationAxis() angleDeg");
12539
+ const [px, py, pz] = requireFiniteVec3$1(pivot, "Transform.rotationAxis() pivot");
12540
+ const rad = degrees * Math.PI / 180;
12478
12541
  const cos2 = Math.cos(rad);
12479
12542
  const sin2 = Math.sin(rad);
12480
12543
  const m00 = cos2 + ux * ux * (1 - cos2);
@@ -12486,7 +12549,6 @@ class Transform {
12486
12549
  const m20 = uz * ux * (1 - cos2) - uy * sin2;
12487
12550
  const m21 = uz * uy * (1 - cos2) + ux * sin2;
12488
12551
  const m22 = cos2 + uz * uz * (1 - cos2);
12489
- const [px, py, pz] = pivot;
12490
12552
  const tx = px - (m00 * px + m01 * py + m02 * pz);
12491
12553
  const ty = py - (m10 * px + m11 * py + m12 * pz);
12492
12554
  const tz = pz - (m20 * px + m21 * py + m22 * pz);
@@ -12494,12 +12556,16 @@ class Transform {
12494
12556
  }
12495
12557
  /** Solve the rotation needed to move one point onto a target line or plane. */
12496
12558
  static rotateAroundTo(axis, pivot, movingPoint, targetPoint, options = {}) {
12497
- const angleDeg = solveRotateAroundAngle(axis, pivot, movingPoint, targetPoint, options);
12498
- return Transform.rotationAxis(axis, angleDeg, pivot);
12559
+ const rotateAxis = requireNonZeroFiniteVec3(axis, "Transform.rotateAroundTo() axis");
12560
+ const rotatePivot = requireFiniteVec3$1(pivot, "Transform.rotateAroundTo() pivot");
12561
+ const moving = requireFiniteVec3$1(movingPoint, "Transform.rotateAroundTo() movingPoint");
12562
+ const target = requireFiniteVec3$1(targetPoint, "Transform.rotateAroundTo() targetPoint");
12563
+ const angleDeg = solveRotateAroundAngle(rotateAxis, rotatePivot, moving, target, options);
12564
+ return Transform.rotationAxis(rotateAxis, angleDeg, rotatePivot);
12499
12565
  }
12500
12566
  /** Compose transforms in chain order: `a.mul(b)` applies `a`, then `b`. */
12501
12567
  mul(other) {
12502
- const rhs = toMat4(other);
12568
+ const rhs = other instanceof Transform ? other.toArray() : requireFiniteMat4(other, "Transform.mul() matrix");
12503
12569
  return new Transform(multiplyMat4(rhs, this.m));
12504
12570
  }
12505
12571
  /** Translate after the current transform. */
@@ -12510,6 +12576,18 @@ class Transform {
12510
12576
  rotateAxis(axis, angleDeg, pivot = [0, 0, 0]) {
12511
12577
  return this.mul(Transform.rotationAxis(axis, angleDeg, pivot));
12512
12578
  }
12579
+ /** Rotate about the X axis after the current transform (parity with `Shape.rotateX`). */
12580
+ rotateX(angleDeg, pivot = [0, 0, 0]) {
12581
+ return this.rotateAxis([1, 0, 0], angleDeg, pivot);
12582
+ }
12583
+ /** Rotate about the Y axis after the current transform (parity with `Shape.rotateY`). */
12584
+ rotateY(angleDeg, pivot = [0, 0, 0]) {
12585
+ return this.rotateAxis([0, 1, 0], angleDeg, pivot);
12586
+ }
12587
+ /** Rotate about the Z axis after the current transform (parity with `Shape.rotateZ`). */
12588
+ rotateZ(angleDeg, pivot = [0, 0, 0]) {
12589
+ return this.rotateAxis([0, 0, 1], angleDeg, pivot);
12590
+ }
12513
12591
  /** Scale after the current transform. */
12514
12592
  scale(v) {
12515
12593
  return this.mul(Transform.scale(v));
@@ -12520,11 +12598,11 @@ class Transform {
12520
12598
  }
12521
12599
  /** Transform a point using homogeneous coordinates. */
12522
12600
  point(p2) {
12523
- return transformPoint$1(this.m, p2, 1);
12601
+ return transformPoint$1(this.m, requireFiniteVec3$1(p2, "Transform.point() point"), 1);
12524
12602
  }
12525
12603
  /** Transform a direction vector without translation. */
12526
12604
  vector(v) {
12527
- return transformPoint$1(this.m, v, 0);
12605
+ return transformPoint$1(this.m, requireFiniteVec3$1(v, "Transform.vector() vector"), 0);
12528
12606
  }
12529
12607
  /** Return the transform as a raw 4x4 matrix array. */
12530
12608
  toArray() {
@@ -12913,6 +12991,42 @@ function cloneCutTaperCompilePlan(plan) {
12913
12991
  scale: [canonicalNumber(plan.scale[0]), canonicalNumber(plan.scale[1])]
12914
12992
  };
12915
12993
  }
12994
+ function cloneEdgeFeatureTarget(target) {
12995
+ return {
12996
+ midpoint: [target.midpoint[0], target.midpoint[1], target.midpoint[2]],
12997
+ start: [target.start[0], target.start[1], target.start[2]],
12998
+ end: [target.end[0], target.end[1], target.end[2]],
12999
+ convex: target.convex,
13000
+ ...target.nativeTopology ? { nativeTopology: { backend: target.nativeTopology.backend, edge: target.nativeTopology.edge } } : {}
13001
+ };
13002
+ }
13003
+ function cloneEdgeFeatureQuery(query) {
13004
+ if (!query) return void 0;
13005
+ return {
13006
+ ...query.near ? { near: [query.near[0], query.near[1], query.near[2]] } : {},
13007
+ ...query.parallel ? { parallel: [query.parallel[0], query.parallel[1], query.parallel[2]] } : {},
13008
+ ...query.perpendicular ? { perpendicular: [query.perpendicular[0], query.perpendicular[1], query.perpendicular[2]] } : {},
13009
+ ...query.convex !== void 0 ? { convex: query.convex } : {},
13010
+ ...query.concave !== void 0 ? { concave: query.concave } : {},
13011
+ ...query.minAngle !== void 0 ? { minAngle: canonicalNumber(query.minAngle) } : {},
13012
+ ...query.maxAngle !== void 0 ? { maxAngle: canonicalNumber(query.maxAngle) } : {},
13013
+ ...query.minLength !== void 0 ? { minLength: canonicalNumber(query.minLength) } : {},
13014
+ ...query.maxLength !== void 0 ? { maxLength: canonicalNumber(query.maxLength) } : {},
13015
+ ...query.within ? {
13016
+ within: {
13017
+ ...query.within.xMin !== void 0 ? { xMin: canonicalNumber(query.within.xMin) } : {},
13018
+ ...query.within.xMax !== void 0 ? { xMax: canonicalNumber(query.within.xMax) } : {},
13019
+ ...query.within.yMin !== void 0 ? { yMin: canonicalNumber(query.within.yMin) } : {},
13020
+ ...query.within.yMax !== void 0 ? { yMax: canonicalNumber(query.within.yMax) } : {},
13021
+ ...query.within.zMin !== void 0 ? { zMin: canonicalNumber(query.within.zMin) } : {},
13022
+ ...query.within.zMax !== void 0 ? { zMax: canonicalNumber(query.within.zMax) } : {}
13023
+ }
13024
+ } : {},
13025
+ ...query.atZ !== void 0 ? { atZ: canonicalNumber(query.atZ) } : {},
13026
+ ...query.tolerance !== void 0 ? { tolerance: canonicalNumber(query.tolerance) } : {},
13027
+ ...query.angleTolerance !== void 0 ? { angleTolerance: canonicalNumber(query.angleTolerance) } : {}
13028
+ };
13029
+ }
12916
13030
  function featureCutExtentForwardSide(extent) {
12917
13031
  return extent.kind === "two-sided" ? extent.forward : extent;
12918
13032
  }
@@ -13009,6 +13123,41 @@ function cloneSweepPathCompilePlan(path) {
13009
13123
  degree: path.degree,
13010
13124
  closed: path.closed
13011
13125
  };
13126
+ case "route3d":
13127
+ return {
13128
+ kind: "route3d",
13129
+ segments: path.segments.map(
13130
+ (segment) => segment.kind === "line" ? {
13131
+ kind: "line",
13132
+ from: canonicalVec3(segment.from),
13133
+ to: canonicalVec3(segment.to),
13134
+ length: canonicalNumber(segment.length)
13135
+ } : {
13136
+ kind: "arc",
13137
+ center: canonicalVec3(segment.center),
13138
+ radius: canonicalNumber(segment.radius),
13139
+ axis: canonicalVec3(segment.axis),
13140
+ start: canonicalVec3(segment.start),
13141
+ end: canonicalVec3(segment.end),
13142
+ sweepDeg: canonicalNumber(segment.sweepDeg),
13143
+ length: canonicalNumber(segment.length)
13144
+ }
13145
+ ),
13146
+ ports: Object.fromEntries(
13147
+ Object.entries(path.ports).map(([name, port]) => [
13148
+ name,
13149
+ {
13150
+ name: port.name,
13151
+ origin: canonicalVec3(port.origin),
13152
+ axis: canonicalVec3(port.axis),
13153
+ xAxis: canonicalVec3(port.xAxis),
13154
+ yAxis: canonicalVec3(port.yAxis),
13155
+ station: canonicalNumber(port.station)
13156
+ }
13157
+ ])
13158
+ ),
13159
+ length: canonicalNumber(path.length)
13160
+ };
13012
13161
  default:
13013
13162
  assertExhaustive(path);
13014
13163
  }
@@ -13103,6 +13252,7 @@ function cloneProfileEdge(edge) {
13103
13252
  }
13104
13253
  }
13105
13254
  function cloneShapeCompilePlan(plan) {
13255
+ var _a3, _b3, _c2;
13106
13256
  if (!plan) return null;
13107
13257
  let result;
13108
13258
  switch (plan.kind) {
@@ -13281,13 +13431,9 @@ function cloneShapeCompilePlan(plan) {
13281
13431
  radius: plan.radius,
13282
13432
  segments: plan.segments,
13283
13433
  continuity: plan.continuity,
13284
- edgeTargets: plan.edgeTargets.map((t) => ({
13285
- midpoint: [t.midpoint[0], t.midpoint[1], t.midpoint[2]],
13286
- start: [t.start[0], t.start[1], t.start[2]],
13287
- end: [t.end[0], t.end[1], t.end[2]],
13288
- convex: t.convex,
13289
- ...t.nativeTopology ? { nativeTopology: { backend: t.nativeTopology.backend, edge: t.nativeTopology.edge } } : {}
13290
- }))
13434
+ edgeTargets: (_a3 = plan.edgeTargets) == null ? void 0 : _a3.map(cloneEdgeFeatureTarget),
13435
+ edgeQuery: cloneEdgeFeatureQuery(plan.edgeQuery),
13436
+ edgeSelection: plan.edgeSelection
13291
13437
  };
13292
13438
  break;
13293
13439
  case "cornerYBlend":
@@ -13297,13 +13443,7 @@ function cloneShapeCompilePlan(plan) {
13297
13443
  radius: plan.radius,
13298
13444
  segments: plan.segments,
13299
13445
  continuity: plan.continuity,
13300
- edgeTargets: plan.edgeTargets.map((t) => ({
13301
- midpoint: [t.midpoint[0], t.midpoint[1], t.midpoint[2]],
13302
- start: [t.start[0], t.start[1], t.start[2]],
13303
- end: [t.end[0], t.end[1], t.end[2]],
13304
- convex: t.convex,
13305
- ...t.nativeTopology ? { nativeTopology: { backend: t.nativeTopology.backend, edge: t.nativeTopology.edge } } : {}
13306
- })),
13446
+ edgeTargets: ((_b3 = plan.edgeTargets) == null ? void 0 : _b3.map(cloneEdgeFeatureTarget)) ?? [],
13307
13447
  cornerPoint: [plan.cornerPoint[0], plan.cornerPoint[1], plan.cornerPoint[2]],
13308
13448
  cornerTolerance: canonicalNumber(plan.cornerTolerance),
13309
13449
  minBranchAngleDeg: canonicalNumber(plan.minBranchAngleDeg)
@@ -13314,13 +13454,9 @@ function cloneShapeCompilePlan(plan) {
13314
13454
  kind: "chamferEdges",
13315
13455
  base: cloneShapeCompilePlan(plan.base),
13316
13456
  size: plan.size,
13317
- edgeTargets: plan.edgeTargets.map((t) => ({
13318
- midpoint: [t.midpoint[0], t.midpoint[1], t.midpoint[2]],
13319
- start: [t.start[0], t.start[1], t.start[2]],
13320
- end: [t.end[0], t.end[1], t.end[2]],
13321
- convex: t.convex,
13322
- ...t.nativeTopology ? { nativeTopology: { backend: t.nativeTopology.backend, edge: t.nativeTopology.edge } } : {}
13323
- }))
13457
+ edgeTargets: (_c2 = plan.edgeTargets) == null ? void 0 : _c2.map(cloneEdgeFeatureTarget),
13458
+ edgeQuery: cloneEdgeFeatureQuery(plan.edgeQuery),
13459
+ edgeSelection: plan.edgeSelection
13324
13460
  };
13325
13461
  break;
13326
13462
  case "draft":
@@ -13340,7 +13476,13 @@ function cloneShapeCompilePlan(plan) {
13340
13476
  };
13341
13477
  break;
13342
13478
  case "importedMesh":
13343
- result = { kind: "importedMesh", filePath: plan.filePath, format: plan.format, fileData: plan.fileData };
13479
+ result = {
13480
+ kind: "importedMesh",
13481
+ filePath: plan.filePath,
13482
+ format: plan.format,
13483
+ ...plan.object ? { object: plan.object } : {},
13484
+ fileData: plan.fileData
13485
+ };
13344
13486
  break;
13345
13487
  case "sdf":
13346
13488
  result = {
@@ -13655,6 +13797,83 @@ function remapToKnotDomain(t, n, degree, knots) {
13655
13797
  const uMax = knots[n];
13656
13798
  return uMin + Math.max(0, Math.min(1, t)) * (uMax - uMin);
13657
13799
  }
13800
+ const EPSILON$2 = 1e-9;
13801
+ function add$2(a2, b) {
13802
+ return [a2[0] + b[0], a2[1] + b[1], a2[2] + b[2]];
13803
+ }
13804
+ function scale$2(v, factor) {
13805
+ return [v[0] * factor, v[1] * factor, v[2] * factor];
13806
+ }
13807
+ function sub$2(a2, b) {
13808
+ return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
13809
+ }
13810
+ function dot$3(a2, b) {
13811
+ return a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2];
13812
+ }
13813
+ function cross$4(a2, b) {
13814
+ return [a2[1] * b[2] - a2[2] * b[1], a2[2] * b[0] - a2[0] * b[2], a2[0] * b[1] - a2[1] * b[0]];
13815
+ }
13816
+ function rotateAroundAxis(v, axis, angleRad) {
13817
+ const c2 = Math.cos(angleRad);
13818
+ const s = Math.sin(angleRad);
13819
+ const term1 = scale$2(v, c2);
13820
+ const term2 = scale$2(cross$4(axis, v), s);
13821
+ const term3 = scale$2(axis, dot$3(axis, v) * (1 - c2));
13822
+ return add$2(add$2(term1, term2), term3);
13823
+ }
13824
+ function arcPointAt(segment, t) {
13825
+ const sweepRad = segment.sweepDeg * Math.PI / 180 * t;
13826
+ return add$2(segment.center, rotateAroundAxis(sub$2(segment.start, segment.center), segment.axis, sweepRad));
13827
+ }
13828
+ function pushUnique(points, point) {
13829
+ const prev = points[points.length - 1];
13830
+ if (!prev || Math.hypot(prev[0] - point[0], prev[1] - point[1], prev[2] - point[2]) > EPSILON$2) {
13831
+ points.push([point[0], point[1], point[2]]);
13832
+ }
13833
+ }
13834
+ function resolveOptions(options) {
13835
+ if (typeof options === "number") return { samples: Math.max(2, Math.floor(options)), maxAngleDeg: 6 };
13836
+ return {
13837
+ samples: Math.max(2, Math.floor((options == null ? void 0 : options.samples) ?? 48)),
13838
+ maxAngleDeg: Math.max(0.25, (options == null ? void 0 : options.maxAngleDeg) ?? 6)
13839
+ };
13840
+ }
13841
+ function routePointAt(plan, t) {
13842
+ if (plan.segments.length === 0) return [0, 0, 0];
13843
+ const target = Math.max(0, Math.min(1, t)) * plan.length;
13844
+ let station = 0;
13845
+ for (const segment of plan.segments) {
13846
+ const next = station + segment.length;
13847
+ if (target <= next || segment === plan.segments[plan.segments.length - 1]) {
13848
+ const localT = segment.length <= EPSILON$2 ? 1 : Math.max(0, Math.min(1, (target - station) / segment.length));
13849
+ if (segment.kind === "line") return add$2(segment.from, scale$2(sub$2(segment.to, segment.from), localT));
13850
+ return arcPointAt(segment, localT);
13851
+ }
13852
+ station = next;
13853
+ }
13854
+ const last = plan.segments[plan.segments.length - 1];
13855
+ return last.kind === "line" ? last.to : last.end;
13856
+ }
13857
+ function sampleRoute3DCompilePlan(plan, options) {
13858
+ const resolved = resolveOptions(options);
13859
+ const points = [];
13860
+ const totalIntervals = Math.max(1, resolved.samples - 1);
13861
+ for (const segment of plan.segments) {
13862
+ const proportional = plan.length > EPSILON$2 ? Math.ceil(segment.length / plan.length * totalIntervals) : 1;
13863
+ const arcIntervals = segment.kind === "arc" ? Math.ceil(Math.abs(segment.sweepDeg) / resolved.maxAngleDeg) : 1;
13864
+ const intervals = Math.max(1, proportional, arcIntervals);
13865
+ const start = segment.kind === "line" ? segment.from : segment.start;
13866
+ pushUnique(points, start);
13867
+ for (let i = 1; i <= intervals; i += 1) {
13868
+ if (segment.kind === "line") {
13869
+ pushUnique(points, add$2(segment.from, scale$2(sub$2(segment.to, segment.from), i / intervals)));
13870
+ } else {
13871
+ pushUnique(points, arcPointAt(segment, i / intervals));
13872
+ }
13873
+ }
13874
+ }
13875
+ return points;
13876
+ }
13658
13877
  function catmullRom3D$1(p0, p1, p2, p3, t, tension) {
13659
13878
  const tt = t * t;
13660
13879
  const ttt = tt * t;
@@ -13793,6 +14012,8 @@ function evalPathAt(path, t) {
13793
14012
  const u2 = remapToKnotDomain(Math.max(0, Math.min(1, t)), path.controlPoints.length, path.degree, path.knots);
13794
14013
  return deBoor3D(path.controlPoints, path.weights, path.knots, path.degree, u2);
13795
14014
  }
14015
+ case "route3d":
14016
+ return routePointAt(path, t);
13796
14017
  }
13797
14018
  }
13798
14019
  function estimateCurvatureAt(path, t) {
@@ -13839,10 +14060,13 @@ function sweepPathToPolyline(path, samples = 48) {
13839
14060
  }
13840
14061
  return result;
13841
14062
  }
14063
+ case "route3d":
14064
+ return sampleRoute3DCompilePlan(path, samples);
13842
14065
  }
13843
14066
  }
13844
14067
  function sweepPathToPolylineAdaptive(path, baseSamples = 48) {
13845
14068
  if (path.kind === "polyline") return path.points;
14069
+ if (path.kind === "route3d") return sampleRoute3DCompilePlan(path, baseSamples);
13846
14070
  const probeCount = 64;
13847
14071
  const curvatures = [];
13848
14072
  for (let i = 0; i <= probeCount; i++) {
@@ -13873,7 +14097,7 @@ const EPS$2 = 1e-8;
13873
14097
  function midpoint$1(start, end) {
13874
14098
  return [(start[0] + end[0]) * 0.5, (start[1] + end[1]) * 0.5, (start[2] + end[2]) * 0.5];
13875
14099
  }
13876
- function normalize$2(v) {
14100
+ function normalize$3(v) {
13877
14101
  const len = Math.hypot(v[0], v[1], v[2]);
13878
14102
  if (len <= EPS$2) throw new Error("Edge feature selection requires a non-zero direction vector");
13879
14103
  return [v[0] / len, v[1] / len, v[2] / len];
@@ -14292,8 +14516,8 @@ function extrudeEdgeSelection(plan, edgeName) {
14292
14516
  }
14293
14517
  const points = plan.profile.points;
14294
14518
  const [bl, br, _tr, tl] = points;
14295
- const u2 = normalize$2([br[0] - bl[0], br[1] - bl[1], 0]);
14296
- const v = normalize$2([tl[0] - bl[0], tl[1] - bl[1], 0]);
14519
+ const u2 = normalize$3([br[0] - bl[0], br[1] - bl[1], 0]);
14520
+ const v = normalize$3([tl[0] - bl[0], tl[1] - bl[1], 0]);
14297
14521
  const vertex = points[index2];
14298
14522
  const quadrant = index2 === 0 ? [1, -1] : index2 === 1 ? [-1, -1] : index2 === 2 ? [-1, 1] : [1, 1];
14299
14523
  return {
@@ -14314,9 +14538,9 @@ function extrudeEdgeSelection(plan, edgeName) {
14314
14538
  function applySelectionTransform(selection, transform) {
14315
14539
  const start = transform.point(selection.start);
14316
14540
  const end = transform.point(selection.end);
14317
- const basisX = normalize$2(transform.vector(selection.basisX));
14318
- const basisY = normalize$2(transform.vector(selection.basisY));
14319
- const axis = normalize$2(subtract(end, start));
14541
+ const basisX = normalize$3(transform.vector(selection.basisX));
14542
+ const basisY = normalize$3(transform.vector(selection.basisY));
14543
+ const axis = normalize$3(subtract(end, start));
14320
14544
  return {
14321
14545
  kind: "line-segment",
14322
14546
  edgeName: selection.edgeName,
@@ -15591,6 +15815,20 @@ function cleanName(value) {
15591
15815
  const trimmed = value == null ? void 0 : value.replace(/\s+/g, " ").trim();
15592
15816
  return trimmed && trimmed.length > 0 ? trimmed : null;
15593
15817
  }
15818
+ function uniqueNames(baseNames) {
15819
+ const totals = /* @__PURE__ */ new Map();
15820
+ const seen = /* @__PURE__ */ new Map();
15821
+ for (const baseName of baseNames) totals.set(baseName, (totals.get(baseName) ?? 0) + 1);
15822
+ return baseNames.map((baseName) => {
15823
+ if ((totals.get(baseName) ?? 0) <= 1) return baseName;
15824
+ const ordinal = (seen.get(baseName) ?? 0) + 1;
15825
+ seen.set(baseName, ordinal);
15826
+ return `${baseName} #${String(ordinal).padStart(3, "0")}`;
15827
+ });
15828
+ }
15829
+ function normalizeSelector(value) {
15830
+ return value.trim().replace(/\s+/g, " ").toLowerCase();
15831
+ }
15594
15832
  function extractModelXml(data) {
15595
15833
  const zip = unzipSync(new Uint8Array(data));
15596
15834
  const path = Object.keys(zip).find((entry) => entry.toLowerCase() === "3d/3dmodel.model");
@@ -15662,14 +15900,105 @@ function collectObjectGeometry(model, objectId, transforms = [], visiting = /* @
15662
15900
  }
15663
15901
  return { vertices, triangles, bbox };
15664
15902
  }
15903
+ function buildBuildEntries(model) {
15904
+ const buildBaseNames = model.buildItems.map(
15905
+ (item) => {
15906
+ var _a3;
15907
+ return cleanName((_a3 = model.objectsById.get(item.objectId)) == null ? void 0 : _a3.sourceName) ?? `object-${item.objectId}`;
15908
+ }
15909
+ );
15910
+ const buildNames = uniqueNames(buildBaseNames);
15911
+ return model.buildItems.map((item, index2) => {
15912
+ const object = model.objectsById.get(item.objectId);
15913
+ return {
15914
+ stableRef: `3mf:build:${String(index2 + 1).padStart(3, "0")}:object:${item.objectId}`,
15915
+ objectId: item.objectId,
15916
+ autoName: buildNames[index2],
15917
+ sourceName: object == null ? void 0 : object.sourceName,
15918
+ type: object == null ? void 0 : object.type,
15919
+ transform: item.transform
15920
+ };
15921
+ });
15922
+ }
15923
+ function buildObjectEntries(model) {
15924
+ const objectNames = uniqueNames(model.objects.map((object) => cleanName(object.sourceName) ?? `object-${object.id}`));
15925
+ return model.objects.map(
15926
+ (object, index2) => ({
15927
+ stableRef: `3mf:object:${object.id}`,
15928
+ objectId: object.id,
15929
+ autoName: objectNames[index2],
15930
+ sourceName: object.sourceName,
15931
+ type: object.type
15932
+ })
15933
+ );
15934
+ }
15935
+ function buildDefaultSourceEntries(model) {
15936
+ return model.buildItems.length > 0 ? buildBuildEntries(model) : buildObjectEntries(model);
15937
+ }
15938
+ function buildSelectableEntries(model) {
15939
+ return model.buildItems.length > 0 ? [...buildBuildEntries(model), ...buildObjectEntries(model)] : buildObjectEntries(model);
15940
+ }
15941
+ function entryMatchesSelector(entry, selector) {
15942
+ const needle = normalizeSelector(selector);
15943
+ return [
15944
+ entry.stableRef,
15945
+ entry.autoName,
15946
+ entry.sourceName ?? "",
15947
+ entry.objectId,
15948
+ `object-${entry.objectId}`,
15949
+ `3mf:object:${entry.objectId}`
15950
+ ].some((candidate) => normalizeSelector(candidate) === needle);
15951
+ }
15952
+ function availableEntryList(entries) {
15953
+ return entries.map((entry) => `${entry.autoName} [${entry.stableRef}]`).join(", ") || "none";
15954
+ }
15955
+ function selectSourceEntry(entries, selector) {
15956
+ const matches = entries.filter((entry) => entryMatchesSelector(entry, selector));
15957
+ if (matches.length === 1) return matches[0];
15958
+ if (matches.length > 1) {
15959
+ throw new Error(
15960
+ `3MF object selector "${selector}" is ambiguous. Use a stable ref: ${matches.map((entry) => entry.stableRef).join(", ")}`
15961
+ );
15962
+ }
15963
+ throw new Error(`3MF object selector "${selector}" matched no build item or resource object. Available: ${availableEntryList(entries)}`);
15964
+ }
15965
+ function selectorLooksLikeStableRef(selector) {
15966
+ return /^3mf:(build|object):/i.test(selector.trim());
15967
+ }
15968
+ function meshFromGeometry(geometry) {
15969
+ const vertices = [];
15970
+ const triangles = [];
15971
+ for (const vertex of geometry.vertices) vertices.push(vertex[0], vertex[1], vertex[2]);
15972
+ for (const triangle of geometry.triangles) triangles.push(triangle[0], triangle[1], triangle[2]);
15973
+ return {
15974
+ vertProperties: new Float32Array(vertices),
15975
+ triVerts: new Uint32Array(triangles),
15976
+ numProp: 3,
15977
+ mergeFromVert: new Uint32Array(0),
15978
+ mergeToVert: new Uint32Array(0)
15979
+ };
15980
+ }
15981
+ function partFromEntry(model, entry) {
15982
+ const geometry = collectObjectGeometry(model, entry.objectId, [entry.transform]);
15983
+ return {
15984
+ stableRef: entry.stableRef,
15985
+ objectId: entry.objectId,
15986
+ autoName: entry.autoName,
15987
+ sourceName: entry.sourceName,
15988
+ type: entry.type,
15989
+ mesh: meshFromGeometry(geometry),
15990
+ bbox: geometry.bbox,
15991
+ vertexCount: geometry.vertices.length,
15992
+ triangleCount: geometry.triangles.length
15993
+ };
15994
+ }
15665
15995
  function parse3mfMesh(data) {
15666
15996
  const model = parseModel(data);
15667
- const sourceIds = model.buildItems.length > 0 ? model.buildItems.map((item) => item.objectId) : model.objects.map((object) => object.id);
15668
- const transforms = model.buildItems.length > 0 ? model.buildItems.map((item) => item.transform) : sourceIds.map(() => void 0);
15997
+ const entries = buildDefaultSourceEntries(model);
15669
15998
  const vertices = [];
15670
15999
  const triangles = [];
15671
- sourceIds.forEach((objectId, index2) => {
15672
- const geometry = collectObjectGeometry(model, objectId, [transforms[index2]]);
16000
+ entries.forEach((entry) => {
16001
+ const geometry = collectObjectGeometry(model, entry.objectId, [entry.transform]);
15673
16002
  const offset = vertices.length / 3;
15674
16003
  for (const vertex of geometry.vertices) vertices.push(vertex[0], vertex[1], vertex[2]);
15675
16004
  for (const triangle of geometry.triangles) triangles.push(triangle[0] + offset, triangle[1] + offset, triangle[2] + offset);
@@ -15682,6 +16011,11 @@ function parse3mfMesh(data) {
15682
16011
  mergeToVert: new Uint32Array(0)
15683
16012
  };
15684
16013
  }
16014
+ function parse3mfMeshPart(data, selector) {
16015
+ const model = parseModel(data);
16016
+ const entries = selectorLooksLikeStableRef(selector) ? buildSelectableEntries(model) : buildDefaultSourceEntries(model);
16017
+ return partFromEntry(model, selectSourceEntry(entries, selector));
16018
+ }
15685
16019
  function isStlBinary(data) {
15686
16020
  if (data.byteLength < 84) return false;
15687
16021
  const view = new DataView(data);
@@ -15826,17 +16160,17 @@ function parseObj(data) {
15826
16160
  mergeToVert: new Uint32Array(0)
15827
16161
  };
15828
16162
  }
15829
- function parse3mf(data) {
15830
- return parse3mfMesh(data);
16163
+ function parse3mf(data, options = {}) {
16164
+ return options.object ? parse3mfMeshPart(data, options.object).mesh : parse3mfMesh(data);
15831
16165
  }
15832
- function parseMeshFile(data, format) {
16166
+ function parseMeshFile(data, format, options = {}) {
15833
16167
  switch (format) {
15834
16168
  case "stl":
15835
16169
  return parseStl(data);
15836
16170
  case "obj":
15837
16171
  return parseObj(data);
15838
16172
  case "3mf":
15839
- return parse3mf(data);
16173
+ return parse3mf(data, options);
15840
16174
  }
15841
16175
  }
15842
16176
  function cleanZero(value) {
@@ -15863,6 +16197,84 @@ function planeFrameToWorldToPlaneMatrix(frame) {
15863
16197
  1
15864
16198
  ];
15865
16199
  }
16200
+ const DEFAULT_TOLERANCE = 1;
16201
+ const DEFAULT_ANGLE_TOLERANCE = 10;
16202
+ function distSq$2(a2, b) {
16203
+ const dx = a2[0] - b[0];
16204
+ const dy = a2[1] - b[1];
16205
+ const dz = a2[2] - b[2];
16206
+ return dx * dx + dy * dy + dz * dz;
16207
+ }
16208
+ function vecLength$2(v) {
16209
+ return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
16210
+ }
16211
+ function normalize$2(v) {
16212
+ const len = vecLength$2(v);
16213
+ if (len < 1e-12) return [0, 0, 0];
16214
+ return [v[0] / len, v[1] / len, v[2] / len];
16215
+ }
16216
+ function absDot$1(a2, b) {
16217
+ return Math.abs(a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2]);
16218
+ }
16219
+ function applyEdgeQueryFilters(edges, query) {
16220
+ const tol = query.tolerance ?? DEFAULT_TOLERANCE;
16221
+ const angleTol = query.angleTolerance ?? DEFAULT_ANGLE_TOLERANCE;
16222
+ const cosAngleTol = Math.cos(angleTol * Math.PI / 180);
16223
+ let result = edges;
16224
+ if (query.convex === true) {
16225
+ result = result.filter((e) => e.convex);
16226
+ }
16227
+ if (query.concave === true) {
16228
+ result = result.filter((e) => !e.convex);
16229
+ }
16230
+ if (query.minAngle != null) {
16231
+ const min2 = query.minAngle;
16232
+ result = result.filter((e) => e.dihedralAngle >= min2);
16233
+ }
16234
+ if (query.maxAngle != null) {
16235
+ const max2 = query.maxAngle;
16236
+ result = result.filter((e) => e.dihedralAngle <= max2);
16237
+ }
16238
+ if (query.minLength != null) {
16239
+ const min2 = query.minLength;
16240
+ result = result.filter((e) => e.length >= min2);
16241
+ }
16242
+ if (query.maxLength != null) {
16243
+ const max2 = query.maxLength;
16244
+ result = result.filter((e) => e.length <= max2);
16245
+ }
16246
+ if (query.parallel) {
16247
+ const dir = normalize$2(query.parallel);
16248
+ result = result.filter((e) => absDot$1(e.direction, dir) >= cosAngleTol);
16249
+ }
16250
+ if (query.perpendicular) {
16251
+ const dir = normalize$2(query.perpendicular);
16252
+ const sinAngleTol = Math.sin(angleTol * Math.PI / 180);
16253
+ result = result.filter((e) => absDot$1(e.direction, dir) <= sinAngleTol);
16254
+ }
16255
+ if (query.atZ != null) {
16256
+ const z2 = query.atZ;
16257
+ result = result.filter((e) => Math.abs(e.midpoint[2] - z2) <= tol);
16258
+ }
16259
+ if (query.within) {
16260
+ const b = query.within;
16261
+ result = result.filter((e) => {
16262
+ const [mx, my, mz] = e.midpoint;
16263
+ if (b.xMin != null && mx < b.xMin) return false;
16264
+ if (b.xMax != null && mx > b.xMax) return false;
16265
+ if (b.yMin != null && my < b.yMin) return false;
16266
+ if (b.yMax != null && my > b.yMax) return false;
16267
+ if (b.zMin != null && mz < b.zMin) return false;
16268
+ if (b.zMax != null && mz > b.zMax) return false;
16269
+ return true;
16270
+ });
16271
+ }
16272
+ if (query.near) {
16273
+ const pt = query.near;
16274
+ result = result.slice().sort((a2, b) => distSq$2(a2.midpoint, pt) - distSq$2(b.midpoint, pt));
16275
+ }
16276
+ return result;
16277
+ }
15866
16278
  const SHELL_OPEN_FACE_CANONICAL = {
15867
16279
  front: "side-bottom",
15868
16280
  back: "side-top",
@@ -18170,6 +18582,8 @@ function getUnsupportedSdfProgramReason(node) {
18170
18582
  return "noise depends on table-based simplex evaluation";
18171
18583
  case "sdf:voronoi":
18172
18584
  return "voronoi depends on table-based Worley evaluation";
18585
+ case "sdf:circularArray":
18586
+ return "circularArray folds polar coordinates and is not implemented in SdfProgram yet";
18173
18587
  case "sdf:custom":
18174
18588
  return "custom uses a dynamic JavaScript function body";
18175
18589
  case "sdf:polylineSweep":
@@ -18850,6 +19264,7 @@ function requireOCCTShape(backend, apiName = "requireOCCTShape()") {
18850
19264
  }
18851
19265
  const BSPLINE_WIRE_THRESHOLD = 20;
18852
19266
  const OFFSET_SOLID_EPS$1 = 1e-8;
19267
+ const MANIFOLD_BACKEND_REQUIRED_HINT = "Select the Manifold backend in the editor or run the CLI with --backend manifold.";
18853
19268
  function transformProfileRadiusPoint$1(point, step) {
18854
19269
  switch (step.kind) {
18855
19270
  case "translate":
@@ -19555,6 +19970,31 @@ function findOCCTEdgeByMidpoint(oc, shape, midpoint2) {
19555
19970
  }
19556
19971
  return bestEdge;
19557
19972
  }
19973
+ function edgeSegmentToTarget(segment) {
19974
+ return {
19975
+ midpoint: [segment.midpoint[0], segment.midpoint[1], segment.midpoint[2]],
19976
+ start: [segment.start[0], segment.start[1], segment.start[2]],
19977
+ end: [segment.end[0], segment.end[1], segment.end[2]],
19978
+ convex: segment.convex,
19979
+ ...segment.nativeTopology ? { nativeTopology: segment.nativeTopology } : {}
19980
+ };
19981
+ }
19982
+ function selectOCCTEdgeFeatureTargets(base, plan) {
19983
+ if (!plan.edgeQuery) return plan.edgeTargets ?? [];
19984
+ const mesh = wrapOCCTShapeBackend(base).getMesh();
19985
+ const selected = applyEdgeQueryFilters(
19986
+ extractEdgeSegments({
19987
+ numProp: mesh.numProp,
19988
+ numTri: mesh.numTri,
19989
+ triVerts: mesh.triVerts,
19990
+ vertProperties: mesh.vertProperties,
19991
+ mergeFromVert: mesh.mergeFromVert,
19992
+ mergeToVert: mesh.mergeToVert
19993
+ }),
19994
+ plan.edgeQuery
19995
+ );
19996
+ return (plan.edgeSelection === "first" ? selected.slice(0, 1) : selected).map(edgeSegmentToTarget);
19997
+ }
19558
19998
  function occtPointDistance(point, target) {
19559
19999
  return Math.hypot(point.X() - target[0], point.Y() - target[1], point.Z() - target[2]);
19560
20000
  }
@@ -19574,7 +20014,7 @@ function lowerFilletEdgesPlan$1(oc, plan) {
19574
20014
  mkFillet.SetContinuity(mapSurfaceContinuityToOcct(oc, plan.continuity), 2 * Math.PI / 180);
19575
20015
  }
19576
20016
  let addedCount = 0;
19577
- for (const target of plan.edgeTargets) {
20017
+ for (const target of selectOCCTEdgeFeatureTargets(base, plan)) {
19578
20018
  const matchedEdge = findOCCTEdgeByMidpoint(oc, base, target.midpoint);
19579
20019
  if (matchedEdge) {
19580
20020
  mkFillet.Add_2(plan.radius, matchedEdge);
@@ -19621,7 +20061,7 @@ function lowerChamferEdgesPlan$1(oc, plan) {
19621
20061
  const base = lowerShapeCompilePlanToOCCT(plan.base, oc);
19622
20062
  const mkChamfer = new oc.BRepFilletAPI_MakeChamfer(base);
19623
20063
  let addedCount = 0;
19624
- for (const target of plan.edgeTargets) {
20064
+ for (const target of selectOCCTEdgeFeatureTargets(base, plan)) {
19625
20065
  const matchedEdge = findOCCTEdgeByMidpoint(oc, base, target.midpoint);
19626
20066
  if (matchedEdge) {
19627
20067
  mkChamfer.Add_2(plan.size, matchedEdge);
@@ -19908,7 +20348,7 @@ function _lowerShapeCompilePlanToOCCTInner(plan, oc) {
19908
20348
  `importMesh("${plan.filePath}") is not supported with the OCCT backend. Switch to the Manifold backend or use the default backend.`
19909
20349
  );
19910
20350
  case "sdf":
19911
- throw new Error("SDF shapes require the Manifold backend. Add setActiveBackend('manifold') at the top of your script.");
20351
+ throw new Error(`SDF shapes require the Manifold backend. ${MANIFOLD_BACKEND_REQUIRED_HINT}`);
19912
20352
  case "fromSlices":
19913
20353
  return lowerFromSlicesPlan$1(oc, plan);
19914
20354
  case "nurbsSurface":
@@ -20080,6 +20520,7 @@ function buildBSplineCurveHandleFromSweepPathPlan(oc, path, context, allowApprox
20080
20520
  case "catmull-rom":
20081
20521
  case "hermite":
20082
20522
  case "quintic-hermite":
20523
+ case "route3d":
20083
20524
  if (allowApproximation) {
20084
20525
  return buildApproximateBSplineCurveHandle(oc, sweepPathToPolyline(path, 96), context);
20085
20526
  }
@@ -20460,27 +20901,166 @@ function lowerLoftPlan$1(oc, plan) {
20460
20901
  }
20461
20902
  return ts.Shape();
20462
20903
  }
20463
- function buildZToNormalTransform(oc, normal) {
20904
+ function fromSlicesPlaneFrameForOCCT(normalInput) {
20905
+ const normal = normalizeVec3$2(normalInput);
20906
+ if (!normal) throw new Error("Shape.fromSlices group normal must be non-zero");
20464
20907
  const [nx, ny, nz] = normal;
20908
+ if (Math.abs(nz) > 1 - 1e-8) {
20909
+ return { u: [1, 0, 0], v: [0, nz > 0 ? 1 : -1, 0], normal };
20910
+ }
20911
+ if (Math.abs(ny) > 1 - 1e-8) {
20912
+ return { u: [1, 0, 0], v: [0, 0, ny > 0 ? 1 : -1], normal };
20913
+ }
20914
+ if (Math.abs(nx) > 1 - 1e-8) {
20915
+ return { u: [0, 1, 0], v: [0, 0, nx > 0 ? 1 : -1], normal };
20916
+ }
20917
+ const reference = Math.abs(nx) < 0.9 ? [1, 0, 0] : [0, 1, 0];
20918
+ const u2 = normalizeVec3$2(crossVec3$1(reference, normal));
20919
+ if (!u2) throw new Error("Shape.fromSlices profile u axis is invalid");
20920
+ return { u: u2, v: crossVec3$1(normal, u2), normal };
20921
+ }
20922
+ function buildFromSlicesLocalToWorldTransform(oc, normal) {
20923
+ const frame = fromSlicesPlaneFrameForOCCT(normal);
20465
20924
  const trsf = new oc.gp_Trsf_1();
20466
- if (Math.abs(nx) < 1e-10 && Math.abs(ny) < 1e-10) {
20467
- if (nz < 0) {
20468
- trsf.SetRotation_1(new oc.gp_Ax1_2(new oc.gp_Pnt_3(0, 0, 0), new oc.gp_Dir_4(1, 0, 0)), Math.PI);
20469
- }
20470
- return trsf;
20471
- }
20472
- const ax = -ny;
20473
- const ay = nx;
20474
- const az = 0;
20475
- const len = Math.sqrt(ax * ax + ay * ay + az * az);
20476
- const angle = Math.acos(Math.max(-1, Math.min(1, nz)));
20477
- trsf.SetRotation_1(new oc.gp_Ax1_2(new oc.gp_Pnt_3(0, 0, 0), new oc.gp_Dir_4(ax / len, ay / len, az / len)), angle);
20925
+ trsf.SetValues(
20926
+ frame.u[0],
20927
+ frame.v[0],
20928
+ frame.normal[0],
20929
+ 0,
20930
+ frame.u[1],
20931
+ frame.v[1],
20932
+ frame.normal[1],
20933
+ 0,
20934
+ frame.u[2],
20935
+ frame.v[2],
20936
+ frame.normal[2],
20937
+ 0
20938
+ );
20478
20939
  return trsf;
20479
20940
  }
20941
+ function axisAlignedDirectionForOCCT(vector) {
20942
+ let bestAxis = 0;
20943
+ let best = Math.abs(vector[0]);
20944
+ for (const axis of [1, 2]) {
20945
+ const value = Math.abs(vector[axis]);
20946
+ if (value > best) {
20947
+ best = value;
20948
+ bestAxis = axis;
20949
+ }
20950
+ }
20951
+ if (best < 1 - 1e-8) return null;
20952
+ return { axis: bestAxis, sign: vector[bestAxis] < 0 ? -1 : 1 };
20953
+ }
20954
+ function centeredEllipseProfileRadiiForOCCT(plan) {
20955
+ if (plan.kind !== "circle") return null;
20956
+ let rx = Math.abs(plan.radius);
20957
+ let ry = Math.abs(plan.radius);
20958
+ for (const transform of plan.transforms) {
20959
+ if (transform.kind !== "scale") return null;
20960
+ rx *= Math.abs(transform.x);
20961
+ ry *= Math.abs(transform.y);
20962
+ }
20963
+ return rx > 1e-10 && ry > 1e-10 ? [rx, ry] : null;
20964
+ }
20965
+ function setEllipsoidRadiusForOCCT(radii, axis, radius) {
20966
+ const existing = radii[axis];
20967
+ if (existing == null) {
20968
+ radii[axis] = radius;
20969
+ return true;
20970
+ }
20971
+ const tolerance = Math.max(1e-7, Math.max(Math.abs(existing), Math.abs(radius)) * 1e-7);
20972
+ return Math.abs(existing - radius) <= tolerance;
20973
+ }
20974
+ function buildRationalQuadraticEdgeForOCCT(oc, points, weights) {
20975
+ const poles = new oc.TColgp_Array1OfPnt_2(1, 3);
20976
+ const weightArr = new oc.TColStd_Array1OfReal_2(1, 3);
20977
+ for (let i = 0; i < 3; i++) {
20978
+ const [x2, y2, z2] = points[i];
20979
+ poles.SetValue(i + 1, new oc.gp_Pnt_3(x2, y2, z2));
20980
+ weightArr.SetValue(i + 1, weights[i]);
20981
+ }
20982
+ const knots = new oc.TColStd_Array1OfReal_2(1, 2);
20983
+ knots.SetValue(1, 0);
20984
+ knots.SetValue(2, 1);
20985
+ const mults = new oc.TColStd_Array1OfInteger_2(1, 2);
20986
+ mults.SetValue(1, 3);
20987
+ mults.SetValue(2, 3);
20988
+ const curve = new oc.Geom_BSplineCurve_2(poles, weightArr, knots, mults, 2, false, true);
20989
+ const handle = new oc.Handle_Geom_BSplineCurve_2(curve);
20990
+ return new oc.BRepBuilderAPI_MakeEdge_24(new oc.Handle_Geom_Curve_2(handle.get())).Edge();
20991
+ }
20992
+ function lowerAxisymmetricEllipsoidForOCCT(oc, radius, heightRadius) {
20993
+ const w2 = Math.SQRT1_2;
20994
+ const mkWire = new oc.BRepBuilderAPI_MakeWire_1();
20995
+ mkWire.Add_1(
20996
+ buildRationalQuadraticEdgeForOCCT(
20997
+ oc,
20998
+ [
20999
+ [0, 0, heightRadius],
21000
+ [radius, 0, heightRadius],
21001
+ [radius, 0, 0]
21002
+ ],
21003
+ [1, w2, 1]
21004
+ )
21005
+ );
21006
+ mkWire.Add_1(
21007
+ buildRationalQuadraticEdgeForOCCT(
21008
+ oc,
21009
+ [
21010
+ [radius, 0, 0],
21011
+ [radius, 0, -heightRadius],
21012
+ [0, 0, -heightRadius]
21013
+ ],
21014
+ [1, w2, 1]
21015
+ )
21016
+ );
21017
+ mkWire.Add_1(new oc.BRepBuilderAPI_MakeEdge_3(new oc.gp_Pnt_3(0, 0, -heightRadius), new oc.gp_Pnt_3(0, 0, heightRadius)).Edge());
21018
+ const face = buildFaceFromWire(oc, mkWire.Wire());
21019
+ const axis = new oc.gp_Ax1_2(new oc.gp_Pnt_3(0, 0, 0), new oc.gp_Dir_4(0, 0, 1));
21020
+ const revol = new oc.BRepPrimAPI_MakeRevol_1(face, axis, Math.PI * 2, true);
21021
+ revol.Build(new oc.Message_ProgressRange_1());
21022
+ if (!revol.IsDone()) {
21023
+ throw new Error("Shape.fromSlices exact ellipsoid revolve failed");
21024
+ }
21025
+ return revol.Shape();
21026
+ }
21027
+ function lowerOrthogonalEllipseFromSlicesPlan(oc, plan) {
21028
+ if (plan.groups.length !== 2) return null;
21029
+ const center = [0, 0, 0];
21030
+ const radii = [null, null, null];
21031
+ for (const group of plan.groups) {
21032
+ if (group.slices.length !== 1) return null;
21033
+ const slice = group.slices[0];
21034
+ const profileRadii = centeredEllipseProfileRadiiForOCCT(slice.profile);
21035
+ if (!profileRadii) return null;
21036
+ const frame = fromSlicesPlaneFrameForOCCT(group.normal);
21037
+ const normalAxis = axisAlignedDirectionForOCCT(frame.normal);
21038
+ const uAxis = axisAlignedDirectionForOCCT(frame.u);
21039
+ const vAxis = axisAlignedDirectionForOCCT(frame.v);
21040
+ if (!normalAxis || !uAxis || !vAxis) return null;
21041
+ center[normalAxis.axis] = normalAxis.sign * slice.offset;
21042
+ if (!setEllipsoidRadiusForOCCT(radii, uAxis.axis, profileRadii[0])) return null;
21043
+ if (!setEllipsoidRadiusForOCCT(radii, vAxis.axis, profileRadii[1])) return null;
21044
+ }
21045
+ if (radii.some((radius) => radius == null)) return null;
21046
+ const rx = radii[0];
21047
+ const ry = radii[1];
21048
+ const rz = radii[2];
21049
+ const axisymmetricTolerance = Math.max(1e-7, Math.max(Math.abs(rx), Math.abs(ry)) * 1e-7);
21050
+ if (Math.abs(rx - ry) > axisymmetricTolerance) return null;
21051
+ let shape = lowerAxisymmetricEllipsoidForOCCT(oc, (rx + ry) / 2, rz);
21052
+ if (Math.hypot(center[0], center[1], center[2]) > 1e-10) {
21053
+ const trsf = new oc.gp_Trsf_1();
21054
+ trsf.SetTranslation_1(new oc.gp_Vec_4(center[0], center[1], center[2]));
21055
+ const moved = new oc.BRepBuilderAPI_Transform_2(shape, trsf, true);
21056
+ shape = moved.Shape();
21057
+ }
21058
+ return shape;
21059
+ }
20480
21060
  function lowerFromSlicesGroup(oc, group, singleSliceHalfExtent) {
20481
21061
  const { normal, slices } = group;
20482
21062
  const sorted = [...slices].sort((a2, b) => a2.offset - b.offset);
20483
- const rotTrsf = buildZToNormalTransform(oc, normal);
21063
+ const localToWorldTrsf = buildFromSlicesLocalToWorldTransform(oc, normal);
20484
21064
  if (sorted.length === 1) {
20485
21065
  const s = sorted[0];
20486
21066
  const rawFace = lowerProfileToFace(oc, s.profile);
@@ -20494,7 +21074,7 @@ function lowerFromSlicesGroup(oc, group, singleSliceHalfExtent) {
20494
21074
  centerTrsf.SetTranslation_1(new oc.gp_Vec_4(0, 0, s.offset - extrudeHalf));
20495
21075
  const centered = new oc.BRepBuilderAPI_Transform_2(shape, centerTrsf, true);
20496
21076
  shape = centered.Shape();
20497
- const rotated = new oc.BRepBuilderAPI_Transform_2(shape, rotTrsf, true);
21077
+ const rotated = new oc.BRepBuilderAPI_Transform_2(shape, localToWorldTrsf, true);
20498
21078
  return rotated.Shape();
20499
21079
  }
20500
21080
  const ts = new oc.BRepOffsetAPI_ThruSections(true, false, 1e-6);
@@ -20505,10 +21085,8 @@ function lowerFromSlicesGroup(oc, group, singleSliceHalfExtent) {
20505
21085
  wire = toBSplineWireIfNeeded(oc, wire);
20506
21086
  const placeTrsf = new oc.gp_Trsf_1();
20507
21087
  placeTrsf.SetTranslation_1(new oc.gp_Vec_4(0, 0, s.offset));
20508
- const compound = new oc.gp_Trsf_1();
20509
- compound.Multiply(rotTrsf);
20510
- compound.Multiply(placeTrsf);
20511
- const transformed = new oc.BRepBuilderAPI_Transform_2(wire, compound, true);
21088
+ const placed = new oc.BRepBuilderAPI_Transform_2(wire, placeTrsf, true);
21089
+ const transformed = new oc.BRepBuilderAPI_Transform_2(placed.Shape(), localToWorldTrsf, true);
20512
21090
  ts.AddWire(oc.TopoDS.Wire_1(transformed.Shape()));
20513
21091
  }
20514
21092
  ts.CheckCompatibility(true);
@@ -20520,6 +21098,8 @@ function lowerFromSlicesGroup(oc, group, singleSliceHalfExtent) {
20520
21098
  }
20521
21099
  function lowerFromSlicesPlan$1(oc, plan) {
20522
21100
  if (plan.groups.length === 0) throw new Error("Shape.fromSlices requires at least one slice");
21101
+ const exactEllipsoid = lowerOrthogonalEllipseFromSlicesPlan(oc, plan);
21102
+ if (exactEllipsoid) return exactEllipsoid;
20523
21103
  const singleSliceHalfExtent = fromSlicesSingleSliceHalfExtentForOCCT(plan);
20524
21104
  const groupSolids = plan.groups.map((g2) => lowerFromSlicesGroup(oc, g2, singleSliceHalfExtent));
20525
21105
  if (groupSolids.length === 1) return groupSolids[0];
@@ -20546,14 +21126,40 @@ function buildPolylineSpineWire(oc, points) {
20546
21126
  }
20547
21127
  return mkWire.Wire();
20548
21128
  }
21129
+ function buildRoute3DSpineWire(oc, path) {
21130
+ if (path.segments.length === 0) throw new Error("Route3D sweep path needs at least one segment");
21131
+ const mkWire = new oc.BRepBuilderAPI_MakeWire_1();
21132
+ for (const segment of path.segments) {
21133
+ if (segment.kind === "line") {
21134
+ const [x1, y1, z1] = segment.from;
21135
+ const [x2, y2, z2] = segment.to;
21136
+ if (x1 === x2 && y1 === y2 && z1 === z2) continue;
21137
+ mkWire.Add_1(new oc.BRepBuilderAPI_MakeEdge_3(new oc.gp_Pnt_3(x1, y1, z1), new oc.gp_Pnt_3(x2, y2, z2)).Edge());
21138
+ continue;
21139
+ }
21140
+ const radial = normalizeVec3$2(subtractVec3(segment.start, segment.center));
21141
+ if (!radial) throw new Error("Route3D sweep arc has a collapsed start radius");
21142
+ const axis = new oc.gp_Ax2_2(
21143
+ new oc.gp_Pnt_3(segment.center[0], segment.center[1], segment.center[2]),
21144
+ new oc.gp_Dir_4(segment.axis[0], segment.axis[1], segment.axis[2]),
21145
+ new oc.gp_Dir_4(radial[0], radial[1], radial[2])
21146
+ );
21147
+ const circle = new oc.gp_Circ_2(axis, segment.radius);
21148
+ mkWire.Add_1(new oc.BRepBuilderAPI_MakeEdge_9(circle, 0, segment.sweepDeg * Math.PI / 180).Edge());
21149
+ }
21150
+ return mkWire.Wire();
21151
+ }
20549
21152
  function buildSpineWireFromPlan(oc, path, pathSamples) {
20550
21153
  switch (path.kind) {
20551
21154
  case "polyline":
20552
21155
  return buildPolylineSpineWire(oc, path.points);
21156
+ case "route3d":
21157
+ return buildRoute3DSpineWire(oc, path);
21158
+ case "nurbs":
21159
+ return new oc.BRepBuilderAPI_MakeWire_2(buildCurveEdgeFromSweepPathPlan(oc, path, "OCCT sweep path")).Wire();
20553
21160
  case "catmull-rom":
20554
21161
  case "hermite":
20555
21162
  case "quintic-hermite":
20556
- case "nurbs":
20557
21163
  return buildPolylineSpineWire(oc, sweepPathToPolyline(path, pathSamples ?? 48));
20558
21164
  }
20559
21165
  }
@@ -31521,7 +32127,7 @@ function nurbsSurfaceForPlan(plan) {
31521
32127
  return null;
31522
32128
  }
31523
32129
  function lowerImportedMeshPlan(plan) {
31524
- const parsed = parseMeshFile(plan.fileData, plan.format);
32130
+ const parsed = parseMeshFile(plan.fileData, plan.format, { object: plan.object });
31525
32131
  if (parsed.numProp !== 3) {
31526
32132
  throw new Error(`importMesh("${plan.filePath}"): expected xyz vertex data`);
31527
32133
  }
@@ -31824,12 +32430,18 @@ function applyNativeTruckFilletTargets(initialShape, targets, radius, segments,
31824
32430
  }
31825
32431
  }
31826
32432
  function lowerFilletEdgesPlan(plan) {
32433
+ if (plan.edgeQuery) {
32434
+ throw new Error("filletEdges(): deferred edge queries are not supported by the Truck lowerer yet.");
32435
+ }
31827
32436
  const shape = lowerShapeCompilePlanToTruckBackend(plan.base);
31828
- return applyNativeTruckFilletTargets(shape, plan.edgeTargets, plan.radius, plan.segments, "filletEdges()");
32437
+ return applyNativeTruckFilletTargets(shape, plan.edgeTargets ?? [], plan.radius, plan.segments, "filletEdges()");
31829
32438
  }
31830
32439
  function lowerChamferEdgesPlan(plan) {
32440
+ if (plan.edgeQuery) {
32441
+ throw new Error("chamferEdges(): deferred edge queries are not supported by the Truck lowerer yet.");
32442
+ }
31831
32443
  const shape = lowerShapeCompilePlanToTruckBackend(plan.base);
31832
- return applyNativeTruckChamferTargets(shape, plan.edgeTargets, plan.size, "chamferEdges()");
32444
+ return applyNativeTruckChamferTargets(shape, plan.edgeTargets ?? [], plan.size, "chamferEdges()");
31833
32445
  }
31834
32446
  function resolvedEdgeFeatureSelectionToTarget(selection) {
31835
32447
  return {
@@ -37846,8 +38458,8 @@ function resolveShapeFace(plan, name) {
37846
38458
  const DEPRECATED_SIDE_NAMES = {
37847
38459
  "side-left": "left",
37848
38460
  "side-right": "right",
37849
- "side-top": "front",
37850
- "side-bottom": "back"
38461
+ "side-top": "back",
38462
+ "side-bottom": "front"
37851
38463
  };
37852
38464
  function explainMissingShapeFace(plan, name) {
37853
38465
  const table = resolveShapeFaceTable(plan);
@@ -38926,11 +39538,14 @@ class ShapeGroup {
38926
39538
  }
38927
39539
  /** Move the entire group by (x, y, z). All children move together as a unit. */
38928
39540
  translate(x2, y2, z2) {
38929
- const matrix = Transform.translation(x2, y2, z2).toArray();
39541
+ const dx = requireFiniteNumber(x2, "ShapeGroup.translate() x");
39542
+ const dy = requireFiniteNumber(y2, "ShapeGroup.translate() y");
39543
+ const dz = requireFiniteNumber(z2, "ShapeGroup.translate() z");
39544
+ const matrix = Transform.translation(dx, dy, dz).toArray();
38930
39545
  return this.mapChildrenTransform((c2) => {
38931
- if (c2 instanceof ShapeGroup) return c2.translate(x2, y2, z2);
38932
- if (c2 instanceof Shape) return c2.translate(x2, y2, z2);
38933
- return c2.translate(x2, y2);
39546
+ if (c2 instanceof ShapeGroup) return c2.translate(dx, dy, dz);
39547
+ if (c2 instanceof Shape) return c2.translate(dx, dy, dz);
39548
+ return c2.translate(dx, dy);
38934
39549
  }, matrix);
38935
39550
  }
38936
39551
  /** Compute combined bounding box of all 3D children */
@@ -38966,17 +39581,23 @@ class ShapeGroup {
38966
39581
  return { min: bb.min, max: bb.max };
38967
39582
  }
38968
39583
  resolveRotatePoint(point) {
38969
- if (Array.isArray(point)) return [point[0], point[1], point[2]];
39584
+ if (Array.isArray(point)) return requireFiniteVec3$1(point, "ShapeGroup.rotateAroundTo() point");
38970
39585
  const bb = this._bbox();
38971
39586
  return resolveAnchor3D(bb.min, bb.max, point);
38972
39587
  }
38973
39588
  /** Move the group so its bounding-box min corner lands at the given coordinate. */
38974
39589
  moveTo(x2, y2, z2) {
39590
+ const targetX = requireFiniteNumber(x2, "ShapeGroup.moveTo() x");
39591
+ const targetY = requireFiniteNumber(y2, "ShapeGroup.moveTo() y");
39592
+ const targetZ = requireFiniteNumber(z2, "ShapeGroup.moveTo() z");
38975
39593
  const bb = this._bbox();
38976
- return this.translate(x2 - bb.min[0], y2 - bb.min[1], z2 - bb.min[2]);
39594
+ return this.translate(targetX - bb.min[0], targetY - bb.min[1], targetZ - bb.min[2]);
38977
39595
  }
38978
39596
  /** Move the group relative to another part's bounding-box min corner. */
38979
39597
  moveToLocal(target, x2, y2, z2) {
39598
+ const localX = requireFiniteNumber(x2, "ShapeGroup.moveToLocal() x");
39599
+ const localY = requireFiniteNumber(y2, "ShapeGroup.moveToLocal() y");
39600
+ const localZ = requireFiniteNumber(z2, "ShapeGroup.moveToLocal() z");
38980
39601
  let tbb;
38981
39602
  if (target instanceof ShapeGroup) {
38982
39603
  tbb = target._bbox();
@@ -38984,7 +39605,7 @@ class ShapeGroup {
38984
39605
  const bb = target.boundingBox();
38985
39606
  tbb = { min: bb.min };
38986
39607
  }
38987
- return this.moveTo(tbb.min[0] + x2, tbb.min[1] + y2, tbb.min[2] + z2);
39608
+ return this.moveTo(tbb.min[0] + localX, tbb.min[1] + localY, tbb.min[2] + localZ);
38988
39609
  }
38989
39610
  /**
38990
39611
  * Attach this group to a face or anchor on another part.
@@ -39013,9 +39634,10 @@ class ShapeGroup {
39013
39634
  const sp = resolveAnchor3D(sbb.min, sbb.max, selfAnchor);
39014
39635
  let dx = tp[0] - sp[0], dy = tp[1] - sp[1], dz = tp[2] - sp[2];
39015
39636
  if (offset) {
39016
- dx += offset[0];
39017
- dy += offset[1];
39018
- dz += offset[2];
39637
+ const offsetPoint = requireFiniteVec3$1(offset, "ShapeGroup.attachTo() offset");
39638
+ dx += offsetPoint[0];
39639
+ dy += offsetPoint[1];
39640
+ dz += offsetPoint[2];
39019
39641
  }
39020
39642
  return this.translate(dx, dy, dz);
39021
39643
  }
@@ -39024,7 +39646,9 @@ class ShapeGroup {
39024
39646
  * See Shape.onFace() for full documentation.
39025
39647
  */
39026
39648
  onFace(parent, face, opts = {}) {
39027
- const u2 = opts.u ?? 0, v = opts.v ?? 0, p2 = opts.protrude ?? 0;
39649
+ const u2 = requireFiniteNumber(opts.u ?? 0, "ShapeGroup.onFace() u");
39650
+ const v = requireFiniteNumber(opts.v ?? 0, "ShapeGroup.onFace() v");
39651
+ const p2 = requireFiniteNumber(opts.protrude ?? 0, "ShapeGroup.onFace() protrude");
39028
39652
  const opp = { front: "back", back: "front", left: "right", right: "left", top: "bottom", bottom: "top" };
39029
39653
  const uvMap = {
39030
39654
  front: (u22, v2, p22) => [u22, -p22, v2],
@@ -39063,20 +39687,31 @@ class ShapeGroup {
39063
39687
  }
39064
39688
  /** Rotate around an arbitrary axis, optionally through a pivot point. */
39065
39689
  rotateAroundAxis(axis, angleDeg, pivot = [0, 0, 0]) {
39066
- return this.transform(Transform.rotationAxis(axis, angleDeg, pivot));
39690
+ const rotateAxis = requireNonZeroFiniteVec3(axis, "ShapeGroup.rotateAroundAxis() axis");
39691
+ const degrees = requireFiniteNumber(angleDeg, "ShapeGroup.rotateAroundAxis() angleDeg");
39692
+ const rotatePivot = requireFiniteVec3$1(pivot, "ShapeGroup.rotateAroundAxis() pivot");
39693
+ return this.transform(Transform.rotationAxis(rotateAxis, degrees, rotatePivot));
39067
39694
  }
39068
39695
  /**
39069
39696
  * Rotate around an axis until a moving point reaches the target line/plane defined by the axis and target point.
39070
39697
  * ShapeGroup string points use built-in anchors only.
39071
39698
  */
39072
39699
  rotateAroundTo(axis, pivot, movingPoint, targetPoint, options = {}) {
39700
+ const rotateAxis = requireNonZeroFiniteVec3(axis, "ShapeGroup.rotateAroundTo() axis");
39701
+ const rotatePivot = requireFiniteVec3$1(pivot, "ShapeGroup.rotateAroundTo() pivot");
39073
39702
  return this.transform(
39074
- Transform.rotateAroundTo(axis, pivot, this.resolveRotatePoint(movingPoint), this.resolveRotatePoint(targetPoint), options)
39703
+ Transform.rotateAroundTo(
39704
+ rotateAxis,
39705
+ rotatePivot,
39706
+ this.resolveRotatePoint(movingPoint),
39707
+ this.resolveRotatePoint(targetPoint),
39708
+ options
39709
+ )
39075
39710
  );
39076
39711
  }
39077
39712
  /** Reorient the group so its local Z axis points along `direction`. */
39078
39713
  pointAlong(direction) {
39079
- const [dx, dy, dz] = direction;
39714
+ const [dx, dy, dz] = requireNonZeroFiniteVec3(direction, "ShapeGroup.pointAlong() direction");
39080
39715
  const len = Math.sqrt(dx * dx + dy * dy + dz * dz) || 1;
39081
39716
  const nx = dx / len, ny = dy / len, nz = dz / len;
39082
39717
  const cx = -ny, cy = nx, cz = 0;
@@ -39091,11 +39726,11 @@ class ShapeGroup {
39091
39726
  }
39092
39727
  /** Apply a 4x4 transform matrix or `Transform` to all 3D children. */
39093
39728
  transform(m2) {
39094
- const matrix = m2 instanceof Transform ? m2.toArray() : m2;
39729
+ const matrix = m2 instanceof Transform ? m2.toArray() : requireFiniteMat4(m2, "ShapeGroup.transform() matrix");
39095
39730
  const next = new ShapeGroup(
39096
39731
  this.children.map((c2) => {
39097
- if (c2 instanceof ShapeGroup) return c2.transform(m2);
39098
- if (c2 instanceof Shape) return c2.transform(m2);
39732
+ if (c2 instanceof ShapeGroup) return c2.transform(matrix);
39733
+ if (c2 instanceof Shape) return c2.transform(matrix);
39099
39734
  throw new Error(
39100
39735
  "ShapeGroup.transform only supports 3D children (Shape/ShapeGroup). For Sketch children, use 2D transforms (translate/rotate/scale/mirror)."
39101
39736
  );
@@ -39108,25 +39743,28 @@ class ShapeGroup {
39108
39743
  }
39109
39744
  /** Scale uniformly or per-axis from the group's bounding-box center. */
39110
39745
  scale(v) {
39111
- return this.scaleAround(this._bboxCenter(), v);
39746
+ const scale2 = requireNonZeroFiniteScale3(v, "ShapeGroup.scale() scale");
39747
+ return this.scaleAround(this._bboxCenter(), scale2);
39112
39748
  }
39113
39749
  /** Scale uniformly or per-axis from an explicit pivot point. */
39114
39750
  scaleAround(pivot, v) {
39115
- const matrix = Transform.scale(v).toArray();
39116
- if (pivot[0] === 0 && pivot[1] === 0 && pivot[2] === 0) {
39751
+ const scale2 = requireNonZeroFiniteScale3(v, "ShapeGroup.scaleAround() scale");
39752
+ const scalePivot = requireFiniteVec3$1(pivot, "ShapeGroup.scaleAround() pivot");
39753
+ const matrix = Transform.scale(scale2).toArray();
39754
+ if (scalePivot[0] === 0 && scalePivot[1] === 0 && scalePivot[2] === 0) {
39117
39755
  return this.mapChildrenTransform((c2) => {
39118
- if (c2 instanceof ShapeGroup) return c2.scaleAround([0, 0, 0], v);
39119
- if (c2 instanceof Shape) return c2.scaleAround([0, 0, 0], v);
39120
- return c2.scale(typeof v === "number" ? v : [v[0], v[1]]);
39756
+ if (c2 instanceof ShapeGroup) return c2.scaleAround([0, 0, 0], scale2);
39757
+ if (c2 instanceof Shape) return c2.scaleAround([0, 0, 0], scale2);
39758
+ return c2.scale([scale2[0], scale2[1]]);
39121
39759
  }, matrix);
39122
39760
  }
39123
- const moved = this.translate(-pivot[0], -pivot[1], -pivot[2]);
39761
+ const moved = this.translate(-scalePivot[0], -scalePivot[1], -scalePivot[2]);
39124
39762
  const scaled = moved.mapChildrenTransform((c2) => {
39125
- if (c2 instanceof ShapeGroup) return c2.scaleAround([0, 0, 0], v);
39126
- if (c2 instanceof Shape) return c2.scaleAround([0, 0, 0], v);
39127
- return c2.scale(typeof v === "number" ? v : [v[0], v[1]]);
39763
+ if (c2 instanceof ShapeGroup) return c2.scaleAround([0, 0, 0], scale2);
39764
+ if (c2 instanceof Shape) return c2.scaleAround([0, 0, 0], scale2);
39765
+ return c2.scale([scale2[0], scale2[1]]);
39128
39766
  }, matrix);
39129
- return scaled.translate(pivot[0], pivot[1], pivot[2]);
39767
+ return scaled.translate(scalePivot[0], scalePivot[1], scalePivot[2]);
39130
39768
  }
39131
39769
  /** Mirror across a plane through the group's bounding-box center. */
39132
39770
  mirror(normal) {
@@ -39134,21 +39772,23 @@ class ShapeGroup {
39134
39772
  }
39135
39773
  /** Mirror across a plane through an explicit point. */
39136
39774
  mirrorThrough(point, normal) {
39137
- const matrix = mirrorPlaneMatrix(normal);
39138
- if (point[0] === 0 && point[1] === 0 && point[2] === 0) {
39775
+ const mirrorPoint = requireFiniteVec3$1(point, "ShapeGroup.mirrorThrough() point");
39776
+ const mirrorNormal = requireNonZeroFiniteVec3(normal, "ShapeGroup.mirrorThrough() normal");
39777
+ const matrix = mirrorPlaneMatrix(mirrorNormal);
39778
+ if (mirrorPoint[0] === 0 && mirrorPoint[1] === 0 && mirrorPoint[2] === 0) {
39139
39779
  return this.mapChildrenTransform((c2) => {
39140
- if (c2 instanceof ShapeGroup) return c2.mirrorThrough([0, 0, 0], normal);
39141
- if (c2 instanceof Shape) return c2.mirrorThrough([0, 0, 0], normal);
39142
- return c2.mirror([normal[0], normal[1]]);
39780
+ if (c2 instanceof ShapeGroup) return c2.mirrorThrough([0, 0, 0], mirrorNormal);
39781
+ if (c2 instanceof Shape) return c2.mirrorThrough([0, 0, 0], mirrorNormal);
39782
+ return c2.mirror([mirrorNormal[0], mirrorNormal[1]]);
39143
39783
  }, matrix);
39144
39784
  }
39145
- const moved = this.translate(-point[0], -point[1], -point[2]);
39785
+ const moved = this.translate(-mirrorPoint[0], -mirrorPoint[1], -mirrorPoint[2]);
39146
39786
  const mirrored = moved.mapChildrenTransform((c2) => {
39147
- if (c2 instanceof ShapeGroup) return c2.mirrorThrough([0, 0, 0], normal);
39148
- if (c2 instanceof Shape) return c2.mirrorThrough([0, 0, 0], normal);
39149
- return c2.mirror([normal[0], normal[1]]);
39787
+ if (c2 instanceof ShapeGroup) return c2.mirrorThrough([0, 0, 0], mirrorNormal);
39788
+ if (c2 instanceof Shape) return c2.mirrorThrough([0, 0, 0], mirrorNormal);
39789
+ return c2.mirror([mirrorNormal[0], mirrorNormal[1]]);
39150
39790
  }, matrix);
39151
- return mirrored.translate(point[0], point[1], point[2]);
39791
+ return mirrored.translate(mirrorPoint[0], mirrorPoint[1], mirrorPoint[2]);
39152
39792
  }
39153
39793
  /** Return a copy of the group with the given display color applied to each child. */
39154
39794
  color(hex) {
@@ -39220,14 +39860,16 @@ class ShapeGroup {
39220
39860
  * ```
39221
39861
  */
39222
39862
  placeReference(ref, target, offset) {
39863
+ const targetPoint = requireFiniteVec3$1(target, "ShapeGroup.placeReference() target");
39864
+ const offsetPoint = offset === void 0 ? void 0 : requireFiniteVec3$1(offset, "ShapeGroup.placeReference() offset");
39223
39865
  const sourcePoint = this.referencePoint(ref);
39224
- let dx = target[0] - sourcePoint[0];
39225
- let dy = target[1] - sourcePoint[1];
39226
- let dz = target[2] - sourcePoint[2];
39227
- if (offset) {
39228
- dx += offset[0];
39229
- dy += offset[1];
39230
- dz += offset[2];
39866
+ let dx = targetPoint[0] - sourcePoint[0];
39867
+ let dy = targetPoint[1] - sourcePoint[1];
39868
+ let dz = targetPoint[2] - sourcePoint[2];
39869
+ if (offsetPoint) {
39870
+ dx += offsetPoint[0];
39871
+ dy += offsetPoint[1];
39872
+ dz += offsetPoint[2];
39231
39873
  }
39232
39874
  return this.translate(dx, dy, dz);
39233
39875
  }
@@ -40159,14 +40801,21 @@ function attachTopologyRewritePropagation(plan, propagation) {
40159
40801
  }
40160
40802
  return cloneNodeWithPropagation(plan, propagation);
40161
40803
  }
40804
+ const DEFERRED_EDGE_SELECTION_MARKER = Symbol.for("forgecad.deferredEdgeSelection");
40805
+ function isDeferredEdgeSelection(value) {
40806
+ return Boolean(value && typeof value === "object" && value[DEFERRED_EDGE_SELECTION_MARKER] === true);
40807
+ }
40162
40808
  function distSq$1(a2, b) {
40163
- const dx = a2[0] - b[0], dy = a2[1] - b[1], dz = a2[2] - b[2];
40809
+ const dx = a2[0] - b[0];
40810
+ const dy = a2[1] - b[1];
40811
+ const dz = a2[2] - b[2];
40164
40812
  return dx * dx + dy * dy + dz * dz;
40165
40813
  }
40166
40814
  function absDot(a2, b) {
40167
40815
  return Math.abs(a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2]);
40168
40816
  }
40169
40817
  function coalesceEdges(segments, tolerance = 0.01) {
40818
+ if (segments.some(isDeferredEdgeSelection)) return segments;
40170
40819
  if (segments.length <= 1) return segments;
40171
40820
  const used = new Uint8Array(segments.length);
40172
40821
  const result = [];
@@ -40889,16 +41538,6 @@ function mirrorMatrix(normal) {
40889
41538
  const m22 = 1 - 2 * nz * nz;
40890
41539
  return [m00, m10, m20, 0, m01, m11, m21, 0, m02, m12, m22, 0, 0, 0, 0, 1];
40891
41540
  }
40892
- function normalizeShapeScale(v) {
40893
- const scale2 = typeof v === "number" ? [v, v, v] : v;
40894
- if (!Number.isFinite(scale2[0]) || !Number.isFinite(scale2[1]) || !Number.isFinite(scale2[2])) {
40895
- return null;
40896
- }
40897
- if (Math.abs(scale2[0]) < 1e-12 || Math.abs(scale2[1]) < 1e-12 || Math.abs(scale2[2]) < 1e-12) {
40898
- return null;
40899
- }
40900
- return [scale2[0], scale2[1], scale2[2]];
40901
- }
40902
41541
  function dotVec3$1(a2, b) {
40903
41542
  return a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2];
40904
41543
  }
@@ -42051,7 +42690,7 @@ function withBaseDimensionsAndMergedSourceSpans(base, sources, out, preserveOutp
42051
42690
  return result;
42052
42691
  }
42053
42692
  function resolveRotationPoint(shape, point) {
42054
- if (Array.isArray(point)) return [point[0], point[1], point[2]];
42693
+ if (Array.isArray(point)) return requireFiniteVec3$1(point, "rotateAroundTo(): point");
42055
42694
  return shape.referencePoint(point);
42056
42695
  }
42057
42696
  function setShapePlacementReferences(shape, refs, options = {}) {
@@ -42779,7 +43418,9 @@ class Shape {
42779
43418
  edgesOf(faceLabel, options) {
42780
43419
  const faceRefs = this._resolveFaceLabels(faceLabel);
42781
43420
  const result = uniqueEdgeSegments(
42782
- faceRefs.flatMap((faceRef) => edgesOfFace(this, faceRef, options, (name) => this._resolveFaceLabel(name)))
43421
+ faceRefs.flatMap((faceRef) => {
43422
+ return edgesOfFace(this, faceRef, options, (name) => this._resolveFaceLabel(name));
43423
+ })
42783
43424
  );
42784
43425
  if (result.length === 0) {
42785
43426
  throw new Error(
@@ -42827,7 +43468,11 @@ class Shape {
42827
43468
  const refsA = this._resolveFaceLabels(faceA);
42828
43469
  const bNames = Array.isArray(faceB) ? faceB : [faceB];
42829
43470
  const refBs = bNames.flatMap((name) => this._resolveFaceLabels(name));
42830
- const result = uniqueEdgeSegments(refsA.flatMap((refA) => edgesBetweenFaces(this, refA, refBs)));
43471
+ const result = uniqueEdgeSegments(
43472
+ refsA.flatMap((refA) => {
43473
+ return edgesBetweenFaces(this, refA, refBs);
43474
+ })
43475
+ );
42831
43476
  if (result.length === 0) {
42832
43477
  const bStr = bNames.length === 1 ? `'${bNames[0]}'` : `[${bNames.map((n) => `'${n}'`).join(", ")}]`;
42833
43478
  throw new Error(
@@ -42891,14 +43536,16 @@ class Shape {
42891
43536
  * ```
42892
43537
  */
42893
43538
  placeReference(ref, target, offset) {
43539
+ const targetPoint = requireFiniteVec3$1(target, "Shape.placeReference() target");
43540
+ const offsetPoint = offset === void 0 ? void 0 : requireFiniteVec3$1(offset, "Shape.placeReference() offset");
42894
43541
  const sourcePoint = this.referencePoint(ref);
42895
- let dx = target[0] - sourcePoint[0];
42896
- let dy = target[1] - sourcePoint[1];
42897
- let dz = target[2] - sourcePoint[2];
42898
- if (offset) {
42899
- dx += offset[0];
42900
- dy += offset[1];
42901
- dz += offset[2];
43542
+ let dx = targetPoint[0] - sourcePoint[0];
43543
+ let dy = targetPoint[1] - sourcePoint[1];
43544
+ let dz = targetPoint[2] - sourcePoint[2];
43545
+ if (offsetPoint) {
43546
+ dx += offsetPoint[0];
43547
+ dy += offsetPoint[1];
43548
+ dz += offsetPoint[2];
42902
43549
  }
42903
43550
  return this.translate(dx, dy, dz);
42904
43551
  }
@@ -42910,27 +43557,39 @@ class Shape {
42910
43557
  * Example: `shape.translatePolar(50, 30)` moves 50mm at 30 degrees from +X.
42911
43558
  */
42912
43559
  translatePolar(radius, angleDeg, z2 = 0) {
42913
- const rad = angleDeg * (Math.PI / 180);
42914
- return this.translate(radius * Math.cos(rad), radius * Math.sin(rad), z2);
43560
+ const r = requireFiniteNumber(radius, "Shape.translatePolar() radius");
43561
+ const angle = requireFiniteNumber(angleDeg, "Shape.translatePolar() angleDeg");
43562
+ const zOffset = requireFiniteNumber(z2, "Shape.translatePolar() z");
43563
+ const rad = angle * (Math.PI / 180);
43564
+ return this.translate(r * Math.cos(rad), r * Math.sin(rad), zOffset);
42915
43565
  }
42916
43566
  /** Move the shape relative to its current position. All transforms are immutable and return new shapes. */
42917
43567
  translate(x2, y2, z2) {
42918
- const nextPlan = appendShapeCompileTransform(getShapeCompilePlanInternal(this), { kind: "translate", x: x2, y: y2, z: z2 });
43568
+ const dx = requireFiniteNumber(x2, "Shape.translate() x");
43569
+ const dy = requireFiniteNumber(y2, "Shape.translate() y");
43570
+ const dz = requireFiniteNumber(z2, "Shape.translate() z");
43571
+ const nextPlan = appendShapeCompileTransform(getShapeCompilePlanInternal(this), { kind: "translate", x: dx, y: dy, z: dz });
42919
43572
  return setShapeCompilePlanInternal(
42920
- withTransformedDimensions(this, buildShapeFromCompilePlan(nextPlan, this.colorHex), Transform.translation(x2, y2, z2).toArray()),
43573
+ withTransformedDimensions(this, buildShapeFromCompilePlan(nextPlan, this.colorHex), Transform.translation(dx, dy, dz).toArray()),
42921
43574
  nextPlan
42922
43575
  );
42923
43576
  }
42924
43577
  /** Position the shape so its bounding box min corner is at the given global coordinate. */
42925
43578
  moveTo(x2, y2, z2) {
43579
+ const targetX = requireFiniteNumber(x2, "Shape.moveTo() x");
43580
+ const targetY = requireFiniteNumber(y2, "Shape.moveTo() y");
43581
+ const targetZ = requireFiniteNumber(z2, "Shape.moveTo() z");
42926
43582
  const bb = this.boundingBox();
42927
- return this.translate(x2 - bb.min[0], y2 - bb.min[1], z2 - bb.min[2]);
43583
+ return this.translate(targetX - bb.min[0], targetY - bb.min[1], targetZ - bb.min[2]);
42928
43584
  }
42929
43585
  /** Position the shape relative to another shape's local coordinate system (bounding box min corner). */
42930
43586
  moveToLocal(target, x2, y2, z2) {
43587
+ const localX = requireFiniteNumber(x2, "Shape.moveToLocal() x");
43588
+ const localY = requireFiniteNumber(y2, "Shape.moveToLocal() y");
43589
+ const localZ = requireFiniteNumber(z2, "Shape.moveToLocal() z");
42931
43590
  const s = "toShape" in target ? target.toShape() : target;
42932
43591
  const tbb = s.boundingBox();
42933
- return this.moveTo(tbb.min[0] + x2, tbb.min[1] + y2, tbb.min[2] + z2);
43592
+ return this.moveTo(tbb.min[0] + localX, tbb.min[1] + localY, tbb.min[2] + localZ);
42934
43593
  }
42935
43594
  /** Rotate around an arbitrary axis through the origin. */
42936
43595
  rotate(axis, angleDeg, options) {
@@ -42959,7 +43618,7 @@ class Shape {
42959
43618
  }
42960
43619
  /** Apply a 4x4 affine transform matrix (column-major) or a Transform object. */
42961
43620
  transform(m2) {
42962
- const mat = m2 instanceof Transform ? m2.toArray() : m2;
43621
+ const mat = m2 instanceof Transform ? m2.toArray() : requireFiniteMat4(m2, "Shape.transform() matrix");
42963
43622
  const steps = rigidTransformStepsFromMatrix(mat);
42964
43623
  if (steps == null) {
42965
43624
  throw new Error(
@@ -42971,23 +43630,20 @@ class Shape {
42971
43630
  }
42972
43631
  /** Scale the shape uniformly or per-axis from the shape's bounding box center. Accepts a single number or [x, y, z] array. */
42973
43632
  scale(v) {
43633
+ const scale2 = requireNonZeroFiniteScale3(v, "Shape.scale() scale");
42974
43634
  const bb = this.boundingBox();
42975
43635
  const center = [
42976
43636
  (bb.min[0] + bb.max[0]) / 2,
42977
43637
  (bb.min[1] + bb.max[1]) / 2,
42978
43638
  (bb.min[2] + bb.max[2]) / 2
42979
43639
  ];
42980
- return this.scaleAround(center, v);
43640
+ return this.scaleAround(center, scale2);
42981
43641
  }
42982
43642
  /** Scale the shape uniformly or per-axis from an explicit pivot point. */
42983
43643
  scaleAround(pivot, v) {
42984
- const scale2 = normalizeShapeScale(v);
42985
- if (!scale2) {
42986
- throw new Error(
42987
- `Shape.scaleAround() received a degenerate scale value (${JSON.stringify(v)}). All scale components must be finite and non-zero.`
42988
- );
42989
- }
42990
- if (pivot[0] === 0 && pivot[1] === 0 && pivot[2] === 0) {
43644
+ const scale2 = requireNonZeroFiniteScale3(v, "Shape.scaleAround() scale");
43645
+ const scalePivot = requireFiniteVec3$1(pivot, "Shape.scaleAround() pivot");
43646
+ if (scalePivot[0] === 0 && scalePivot[1] === 0 && scalePivot[2] === 0) {
42991
43647
  const nextPlan2 = appendShapeCompileTransform(getShapeCompilePlanInternal(this), {
42992
43648
  kind: "scale",
42993
43649
  x: scale2[0],
@@ -42995,11 +43651,11 @@ class Shape {
42995
43651
  z: scale2[2]
42996
43652
  });
42997
43653
  return setShapeCompilePlanInternal(
42998
- withTransformedDimensions(this, buildShapeFromCompilePlan(nextPlan2, this.colorHex), Transform.scale(v).toArray()),
43654
+ withTransformedDimensions(this, buildShapeFromCompilePlan(nextPlan2, this.colorHex), Transform.scale(scale2).toArray()),
42999
43655
  nextPlan2
43000
43656
  );
43001
43657
  }
43002
- const translated = this.translate(-pivot[0], -pivot[1], -pivot[2]);
43658
+ const translated = this.translate(-scalePivot[0], -scalePivot[1], -scalePivot[2]);
43003
43659
  const nextPlan = appendShapeCompileTransform(getShapeCompilePlanInternal(translated), {
43004
43660
  kind: "scale",
43005
43661
  x: scale2[0],
@@ -43007,10 +43663,10 @@ class Shape {
43007
43663
  z: scale2[2]
43008
43664
  });
43009
43665
  const scaled = setShapeCompilePlanInternal(
43010
- withTransformedDimensions(translated, buildShapeFromCompilePlan(nextPlan, translated.colorHex), Transform.scale(v).toArray()),
43666
+ withTransformedDimensions(translated, buildShapeFromCompilePlan(nextPlan, translated.colorHex), Transform.scale(scale2).toArray()),
43011
43667
  nextPlan
43012
43668
  );
43013
- return scaled.translate(pivot[0], pivot[1], pivot[2]);
43669
+ return scaled.translate(scalePivot[0], scalePivot[1], scalePivot[2]);
43014
43670
  }
43015
43671
  /** Mirror across a plane through the shape's bounding box center, defined by its normal vector. */
43016
43672
  mirror(normal) {
@@ -43024,32 +43680,34 @@ class Shape {
43024
43680
  }
43025
43681
  /** Mirror across a plane through an explicit point, defined by its normal vector. */
43026
43682
  mirrorThrough(point, normal) {
43027
- if (point[0] === 0 && point[1] === 0 && point[2] === 0) {
43683
+ const mirrorPoint = requireFiniteVec3$1(point, "Shape.mirrorThrough() point");
43684
+ const mirrorNormal = requireNonZeroFiniteVec3(normal, "Shape.mirrorThrough() normal");
43685
+ if (mirrorPoint[0] === 0 && mirrorPoint[1] === 0 && mirrorPoint[2] === 0) {
43028
43686
  const transformedPlan2 = appendShapeCompileTransform(getShapeCompilePlanInternal(this), {
43029
43687
  kind: "mirror",
43030
- normalX: normal[0],
43031
- normalY: normal[1],
43032
- normalZ: normal[2]
43688
+ normalX: mirrorNormal[0],
43689
+ normalY: mirrorNormal[1],
43690
+ normalZ: mirrorNormal[2]
43033
43691
  });
43034
43692
  const nextPlan2 = wrapRepeatedShapeCompilePlan(transformedPlan2, "mirror");
43035
43693
  return setShapeCompilePlanInternal(
43036
- withTransformedDimensions(this, buildShapeFromCompilePlan(nextPlan2, this.colorHex), mirrorMatrix(normal)),
43694
+ withTransformedDimensions(this, buildShapeFromCompilePlan(nextPlan2, this.colorHex), mirrorMatrix(mirrorNormal)),
43037
43695
  nextPlan2
43038
43696
  );
43039
43697
  }
43040
- const translated = this.translate(-point[0], -point[1], -point[2]);
43698
+ const translated = this.translate(-mirrorPoint[0], -mirrorPoint[1], -mirrorPoint[2]);
43041
43699
  const transformedPlan = appendShapeCompileTransform(getShapeCompilePlanInternal(translated), {
43042
43700
  kind: "mirror",
43043
- normalX: normal[0],
43044
- normalY: normal[1],
43045
- normalZ: normal[2]
43701
+ normalX: mirrorNormal[0],
43702
+ normalY: mirrorNormal[1],
43703
+ normalZ: mirrorNormal[2]
43046
43704
  });
43047
43705
  const nextPlan = wrapRepeatedShapeCompilePlan(transformedPlan, "mirror");
43048
43706
  const mirrored = setShapeCompilePlanInternal(
43049
- withTransformedDimensions(translated, buildShapeFromCompilePlan(nextPlan, translated.colorHex), mirrorMatrix(normal)),
43707
+ withTransformedDimensions(translated, buildShapeFromCompilePlan(nextPlan, translated.colorHex), mirrorMatrix(mirrorNormal)),
43050
43708
  nextPlan
43051
43709
  );
43052
- return mirrored.translate(point[0], point[1], point[2]);
43710
+ return mirrored.translate(mirrorPoint[0], mirrorPoint[1], mirrorPoint[2]);
43053
43711
  }
43054
43712
  /**
43055
43713
  * Reorient a shape so its primary axis (Z) points along the given direction.
@@ -43059,7 +43717,7 @@ class Shape {
43059
43717
  * Example: cylinder(40, 5).pointAlong([1, 0, 0]) — lays cylinder along X, starting at origin
43060
43718
  */
43061
43719
  pointAlong(direction) {
43062
- const [dx, dy, dz] = direction;
43720
+ const [dx, dy, dz] = requireNonZeroFiniteVec3(direction, "Shape.pointAlong() direction");
43063
43721
  const len = Math.sqrt(dx * dx + dy * dy + dz * dz) || 1;
43064
43722
  const nx = dx / len, ny = dy / len, nz = dz / len;
43065
43723
  const cx = -ny, cy = nx, cz = 0;
@@ -43078,18 +43736,21 @@ class Shape {
43078
43736
  * @internal Prefer rotate(), rotateX(), rotateY(), rotateZ() for public use.
43079
43737
  */
43080
43738
  rotateAroundAxis(axis, angleDeg, pivot = [0, 0, 0]) {
43081
- const len = Math.sqrt(axis[0] ** 2 + axis[1] ** 2 + axis[2] ** 2) || 1;
43082
- const normalizedAxis = [axis[0] / len, axis[1] / len, axis[2] / len];
43083
- const matrix = rotationAroundAxisMatrix(normalizedAxis, angleDeg, pivot);
43739
+ const rotateAxis = requireNonZeroFiniteVec3(axis, "Shape.rotateAroundAxis() axis");
43740
+ const degrees = requireFiniteNumber(angleDeg, "Shape.rotateAroundAxis() angleDeg");
43741
+ const rotatePivot = requireFiniteVec3$1(pivot, "Shape.rotateAroundAxis() pivot");
43742
+ const len = Math.sqrt(rotateAxis[0] ** 2 + rotateAxis[1] ** 2 + rotateAxis[2] ** 2) || 1;
43743
+ const normalizedAxis = [rotateAxis[0] / len, rotateAxis[1] / len, rotateAxis[2] / len];
43744
+ const matrix = rotationAroundAxisMatrix(normalizedAxis, degrees, rotatePivot);
43084
43745
  const nextPlan = appendShapeCompileTransform(getShapeCompilePlanInternal(this), {
43085
43746
  kind: "rotateAround",
43086
43747
  axisX: normalizedAxis[0],
43087
43748
  axisY: normalizedAxis[1],
43088
43749
  axisZ: normalizedAxis[2],
43089
- degrees: angleDeg,
43090
- pivotX: pivot[0],
43091
- pivotY: pivot[1],
43092
- pivotZ: pivot[2]
43750
+ degrees,
43751
+ pivotX: rotatePivot[0],
43752
+ pivotY: rotatePivot[1],
43753
+ pivotZ: rotatePivot[2]
43093
43754
  });
43094
43755
  return setShapeCompilePlanInternal(
43095
43756
  withTransformedDimensions(this, buildShapeFromCompilePlan(nextPlan, this.colorHex), matrix),
@@ -43101,10 +43762,12 @@ class Shape {
43101
43762
  * `movingPoint` / `targetPoint` may be raw world points or this shape's anchors/references.
43102
43763
  */
43103
43764
  rotateAroundTo(axis, pivot, movingPoint, targetPoint, options = {}) {
43765
+ const rotateAxis = requireNonZeroFiniteVec3(axis, "Shape.rotateAroundTo() axis");
43766
+ const rotatePivot = requireFiniteVec3$1(pivot, "Shape.rotateAroundTo() pivot");
43104
43767
  const moving = resolveRotationPoint(this, movingPoint);
43105
43768
  const target = resolveRotationPoint(this, targetPoint);
43106
- const angleDeg = solveRotateAroundAngle(axis, pivot, moving, target, options);
43107
- return this.rotateAroundAxis(axis, angleDeg, pivot);
43769
+ const angleDeg = solveRotateAroundAngle(rotateAxis, rotatePivot, moving, target, options);
43770
+ return this.rotateAroundAxis(rotateAxis, angleDeg, rotatePivot);
43108
43771
  }
43109
43772
  // --- Booleans ---
43110
43773
  /** Unwrap any object with toShape() without circular import. */
@@ -43268,17 +43931,19 @@ class Shape {
43268
43931
  }
43269
43932
  /** Split by infinite plane. Returns [positive-side, negative-side]. */
43270
43933
  splitByPlane(normal, originOffset = 0) {
43934
+ const planeNormal = requireNonZeroFiniteVec3(normal, "Shape.splitByPlane() normal");
43935
+ const planeOffset = requireFiniteNumber(originOffset, "Shape.splitByPlane() originOffset");
43271
43936
  const info = deriveGeometryInfo(getShapeGeometryInfoInternal(this), "boolean", {
43272
43937
  topology: _activeBackend === "truck" ? "kernel" : "none"
43273
43938
  });
43274
43939
  const basePlan = getShapeCompilePlanInternal(this);
43275
43940
  const firstPlan = createOwnedTopologyRewritePlan(
43276
- buildTrimByPlaneShapeCompilePlan(basePlan, normal, originOffset),
43941
+ buildTrimByPlaneShapeCompilePlan(basePlan, planeNormal, planeOffset),
43277
43942
  "splitByPlane:positive",
43278
43943
  (owner) => buildTrimByPlaneTopologyRewritePropagation(owner, basePlan)
43279
43944
  );
43280
43945
  const secondPlan = createOwnedTopologyRewritePlan(
43281
- buildTrimByPlaneShapeCompilePlan(basePlan, [-normal[0], -normal[1], -normal[2]], -originOffset),
43946
+ buildTrimByPlaneShapeCompilePlan(basePlan, [-planeNormal[0], -planeNormal[1], -planeNormal[2]], -planeOffset),
43282
43947
  "splitByPlane:opposite",
43283
43948
  (owner) => buildTrimByPlaneTopologyRewritePropagation(owner, basePlan)
43284
43949
  );
@@ -43296,9 +43961,11 @@ class Shape {
43296
43961
  }
43297
43962
  /** Keep the positive side of the plane and discard the opposite side. */
43298
43963
  trimByPlane(normal, originOffset = 0) {
43964
+ const planeNormal = requireNonZeroFiniteVec3(normal, "Shape.trimByPlane() normal");
43965
+ const planeOffset = requireFiniteNumber(originOffset, "Shape.trimByPlane() originOffset");
43299
43966
  const basePlan = getShapeCompilePlanInternal(this);
43300
43967
  const nextPlan = createOwnedTopologyRewritePlan(
43301
- buildTrimByPlaneShapeCompilePlan(basePlan, normal, originOffset),
43968
+ buildTrimByPlaneShapeCompilePlan(basePlan, planeNormal, planeOffset),
43302
43969
  "trimByPlane",
43303
43970
  (owner) => buildTrimByPlaneTopologyRewritePropagation(owner, basePlan)
43304
43971
  );
@@ -43318,8 +43985,9 @@ class Shape {
43318
43985
  * `openFaces` names any subset of the base shape's labeled faces to leave open (no wall).
43319
43986
  */
43320
43987
  shell(thickness, opts = {}) {
43988
+ const wallThickness = requirePositiveFiniteNumber(thickness, "Shape.shell() thickness");
43321
43989
  const basePlan = getShapeCompilePlanInternal(this);
43322
- const shellPlan = buildShellShapeCompilePlan(basePlan, thickness, opts.openFaces);
43990
+ const shellPlan = buildShellShapeCompilePlan(basePlan, wallThickness, opts.openFaces);
43323
43991
  if (!shellPlan) {
43324
43992
  throw new Error(
43325
43993
  "Shape.shell() supports box(), cylinder(), straight extrude(), loft(), sweep(), and variableSweep() solids with optional face openings and rigid transforms."
@@ -43340,7 +44008,8 @@ class Shape {
43340
44008
  }
43341
44009
  /** Offset-thicken an exact open surface or shell into a solid. */
43342
44010
  thicken(thickness) {
43343
- if (!Number.isFinite(thickness) || thickness === 0) {
44011
+ const wallThickness = requireFiniteNumber(thickness, "Shape.thicken() thickness");
44012
+ if (wallThickness === 0) {
43344
44013
  throw new Error("Shape.thicken() requires a non-zero finite thickness.");
43345
44014
  }
43346
44015
  const info = getShapeGeometryInfoInternal(this);
@@ -43348,7 +44017,7 @@ class Shape {
43348
44017
  throw new Error("Shape.thicken() is only available on surface-representation shapes.");
43349
44018
  }
43350
44019
  const basePlan = getShapeCompilePlanInternal(this);
43351
- const nextPlan = buildSurfaceThickenShapeCompilePlan(basePlan, thickness);
44020
+ const nextPlan = buildSurfaceThickenShapeCompilePlan(basePlan, wallThickness);
43352
44021
  const representation = info.backend === "occt" || info.backend === "truck" ? "brep-solid" : "mesh-solid";
43353
44022
  return setShapeCompilePlanInternal(
43354
44023
  setShapeGeometryInfoInternal(
@@ -43389,11 +44058,12 @@ class Shape {
43389
44058
  }
43390
44059
  /** Slice the runtime solid by a plane normal to local Z at the given offset. */
43391
44060
  slice(offset = 0) {
44061
+ const planeOffset = requireFiniteNumber(offset, "Shape.slice() offset");
43392
44062
  if (getShapeGeometryInfoInternal(this).backend === "truck") {
43393
- const slicedProfile = lowerExactSlicedShapeCompilePlanToTruckProfileBackend(getShapeCompilePlanInternal(this), offset);
44063
+ const slicedProfile = lowerExactSlicedShapeCompilePlanToTruckProfileBackend(getShapeCompilePlanInternal(this), planeOffset);
43394
44064
  if (slicedProfile) return slicedProfile;
43395
44065
  }
43396
- return getShapeRuntimeBackendInternal(this).slice(offset);
44066
+ return getShapeRuntimeBackendInternal(this).slice(planeOffset);
43397
44067
  }
43398
44068
  /** Orthographically project the runtime solid onto the local XY plane. */
43399
44069
  project() {
@@ -43416,9 +44086,10 @@ class Shape {
43416
44086
  const sp = this.referencePoint(selfAnchor);
43417
44087
  let dx = tp[0] - sp[0], dy = tp[1] - sp[1], dz = tp[2] - sp[2];
43418
44088
  if (offset) {
43419
- dx += offset[0];
43420
- dy += offset[1];
43421
- dz += offset[2];
44089
+ const offsetPoint = requireFiniteVec3$1(offset, "Shape.attachTo() offset");
44090
+ dx += offsetPoint[0];
44091
+ dy += offsetPoint[1];
44092
+ dz += offsetPoint[2];
43422
44093
  }
43423
44094
  return this.translate(dx, dy, dz);
43424
44095
  }
@@ -43434,9 +44105,9 @@ class Shape {
43434
44105
  * - `protrude` = how far the child sticks out (positive = outward from face)
43435
44106
  */
43436
44107
  onFace(parent, face, opts = {}) {
43437
- const u2 = opts.u ?? 0;
43438
- const v = opts.v ?? 0;
43439
- const p2 = opts.protrude ?? 0;
44108
+ const u2 = requireFiniteNumber(opts.u ?? 0, "Shape.onFace() u");
44109
+ const v = requireFiniteNumber(opts.v ?? 0, "Shape.onFace() v");
44110
+ const p2 = requireFiniteNumber(opts.protrude ?? 0, "Shape.onFace() protrude");
43440
44111
  const opposite = {
43441
44112
  front: "back",
43442
44113
  back: "front",
@@ -44015,6 +44686,8 @@ function sampleSweepPath(path, edgeLengthHint) {
44015
44686
  path,
44016
44687
  edgeLengthHint != null ? Math.max(16, Math.round(path.controlPoints.length * 12 / Math.max(0.1, edgeLengthHint))) : 48
44017
44688
  );
44689
+ case "route3d":
44690
+ return sweepPathToPolyline(path, resolveCurveSampleCount(edgeLengthHint, path.length));
44018
44691
  }
44019
44692
  }
44020
44693
  function clamp(v, lo, hi) {
@@ -45144,6 +45817,7 @@ function rotateVector(v, axis, c2, s) {
45144
45817
  ];
45145
45818
  }
45146
45819
  const OFFSET_SOLID_EPS = 1e-9;
45820
+ const OCCT_BACKEND_REQUIRED_HINT = "Select the OCCT backend in the editor or run the CLI with --backend occt.";
45147
45821
  function disposeWasmObject(value) {
45148
45822
  if (value != null && typeof value.delete === "function") value.delete();
45149
45823
  }
@@ -45628,7 +46302,7 @@ function profilePolygonLoopTopologyMatches(left, right) {
45628
46302
  }
45629
46303
  function lowerOffsetLoftCompilePlan(plan, thickness, wasm) {
45630
46304
  if (plan.profiles.length !== plan.heights.length || plan.profiles.length < 2) {
45631
- throw new Error("Offset solid requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
46305
+ throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
45632
46306
  }
45633
46307
  const heights = [...plan.heights];
45634
46308
  heights[0] -= thickness;
@@ -45639,7 +46313,7 @@ function lowerOffsetLoftCompilePlan(plan, thickness, wasm) {
45639
46313
  const offsetPolygons = plan.profiles.map((profile) => offsetProfilePolygonsForManifold(profile, thickness, wasm));
45640
46314
  const stitched = loftStitched(offsetPolygons, heights, wasm);
45641
46315
  if (!stitched) {
45642
- throw new Error("Offset solid requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
46316
+ throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
45643
46317
  }
45644
46318
  return stitched;
45645
46319
  }
@@ -45679,16 +46353,16 @@ function lowerOffsetSweepCompilePlan(plan, thickness, wasm) {
45679
46353
  const basePolygons = profilePolygonsForManifold(plan.profile, wasm);
45680
46354
  const offsetPolygons = offsetProfilePolygonsForManifold(plan.profile, thickness, wasm);
45681
46355
  if (!profilePolygonLoopTopologyMatches(basePolygons, offsetPolygons)) {
45682
- throw new Error("Offset solid requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
46356
+ throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
45683
46357
  }
45684
46358
  const pathPoints = sweepPathToPolylineAdaptive(plan.path, plan.pathSamples ?? 48);
45685
46359
  const extendedPath = extendedStraightSweepPathForManifold(pathPoints, thickness);
45686
46360
  if (!extendedPath) {
45687
- throw new Error("Offset solid requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
46361
+ throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
45688
46362
  }
45689
46363
  const stitched = sweepStitched(offsetPolygons, extendedPath, [plan.up[0], plan.up[1], plan.up[2]], wasm);
45690
46364
  if (!stitched) {
45691
- throw new Error("Offset solid requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
46365
+ throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
45692
46366
  }
45693
46367
  return stitched;
45694
46368
  }
@@ -45697,7 +46371,7 @@ function lowerTransformedOffsetSolidCompilePlan(plan, thickness, wasm) {
45697
46371
  for (const step of plan.steps) {
45698
46372
  const stepScale = offsetSolidTransformDistanceScaleForManifold(step);
45699
46373
  if (stepScale == null) {
45700
- throw new Error("Offset solid requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
46374
+ throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
45701
46375
  }
45702
46376
  distanceScale *= stepScale;
45703
46377
  }
@@ -45738,7 +46412,7 @@ function lowerOffsetSolidCompilePlan(plan, wasm) {
45738
46412
  }
45739
46413
  const base = verticalPrismOffsetBaseForManifold(basePlan);
45740
46414
  if (!base) {
45741
- throw new Error("Offset solid requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
46415
+ throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
45742
46416
  }
45743
46417
  const zMin = base.zMin - plan.thickness;
45744
46418
  const zMax = base.zMax + plan.thickness;
@@ -46263,6 +46937,18 @@ function matchEdgeSegmentByMidpoint(segments, target) {
46263
46937
  }
46264
46938
  return best;
46265
46939
  }
46940
+ function selectEdgeSegmentsForFeature(segments, plan) {
46941
+ if (plan.edgeQuery) {
46942
+ const selected = applyEdgeQueryFilters(segments, plan.edgeQuery);
46943
+ return plan.edgeSelection === "first" ? selected.slice(0, 1) : selected;
46944
+ }
46945
+ const matched = [];
46946
+ for (const target of plan.edgeTargets ?? []) {
46947
+ const seg = matchEdgeSegmentByMidpoint(segments, target);
46948
+ if (seg && seg.length >= 1e-6) matched.push(seg);
46949
+ }
46950
+ return matched;
46951
+ }
46266
46952
  function lowerFilletEdgesCompilePlan(plan, wasm) {
46267
46953
  let manifold = lowerShapeCompilePlanToManifold(plan.base, wasm);
46268
46954
  const mesh = manifold.getMesh();
@@ -46272,11 +46958,8 @@ function lowerFilletEdgesCompilePlan(plan, wasm) {
46272
46958
  triVerts: mesh.triVerts,
46273
46959
  vertProperties: mesh.vertProperties
46274
46960
  });
46275
- const matched = [];
46276
- for (const target of plan.edgeTargets) {
46277
- const seg = matchEdgeSegmentByMidpoint(segments, target);
46278
- if (seg && seg.length >= 1e-6) matched.push(seg);
46279
- }
46961
+ const matched = selectEdgeSegmentsForFeature(segments, plan);
46962
+ if (plan.edgeQuery && matched.length === 0) throw new Error("filletEdges(): no edges match the deferred edge query.");
46280
46963
  matched.sort((a2, b) => b.length - a2.length);
46281
46964
  for (const seg of matched) {
46282
46965
  try {
@@ -46299,11 +46982,8 @@ function lowerChamferEdgesCompilePlan(plan, wasm) {
46299
46982
  triVerts: mesh.triVerts,
46300
46983
  vertProperties: mesh.vertProperties
46301
46984
  });
46302
- const matched = [];
46303
- for (const target of plan.edgeTargets) {
46304
- const seg = matchEdgeSegmentByMidpoint(segments, target);
46305
- if (seg && seg.length >= 1e-6) matched.push(seg);
46306
- }
46985
+ const matched = selectEdgeSegmentsForFeature(segments, plan);
46986
+ if (plan.edgeQuery && matched.length === 0) throw new Error("chamferEdges(): no edges match the deferred edge query.");
46307
46987
  matched.sort((a2, b) => b.length - a2.length);
46308
46988
  for (const seg of matched) {
46309
46989
  try {
@@ -46405,20 +47085,20 @@ function lowerShapeCompilePlanToManifold(plan, wasm) {
46405
47085
  case "filletEdges":
46406
47086
  return lowerFilletEdgesCompilePlan(plan, wasm);
46407
47087
  case "cornerYBlend":
46408
- throw new Error("Blend.CornerY() requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
47088
+ throw new Error(`Blend.CornerY() requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
46409
47089
  case "chamferEdges":
46410
47090
  return lowerChamferEdgesCompilePlan(plan, wasm);
46411
47091
  case "draft": {
46412
47092
  const lowered = lowerDraftShapeCompilePlanToLoftPlan(plan);
46413
47093
  if (lowered) return lowerShapeCompilePlanToManifold(lowered, wasm);
46414
- throw new Error("Draft angle requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
47094
+ throw new Error(`Draft angle requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
46415
47095
  }
46416
47096
  case "offsetSolid":
46417
47097
  return lowerOffsetSolidCompilePlan(plan, wasm);
46418
47098
  case "trimByPlane":
46419
47099
  return lowerShapeTrimByPlaneCompilePlan(plan, wasm);
46420
47100
  case "importedMesh":
46421
- return lowerImportedMeshToManifold(plan.fileData, plan.format, plan.filePath, wasm);
47101
+ return lowerImportedMeshToManifold(plan.fileData, plan.format, plan.filePath, wasm, plan.object);
46422
47102
  case "sdf": {
46423
47103
  const evaluator = compileSdfMaterializationEvaluator3(plan.tree);
46424
47104
  return lowerSdfToManifold(
@@ -46442,9 +47122,9 @@ function lowerShapeCompilePlanToManifold(plan, wasm) {
46442
47122
  case "surfaceExtend":
46443
47123
  case "surfaceThicken":
46444
47124
  case "surfaceSolid":
46445
- throw new Error("Exact surfacing operations require the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
47125
+ throw new Error(`Exact surfacing operations require the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
46446
47126
  case "importedStep":
46447
- throw new Error(`importStep("${plan.filePath}") requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.`);
47127
+ throw new Error(`importStep("${plan.filePath}") requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
46448
47128
  default:
46449
47129
  assertExhaustive(plan);
46450
47130
  }
@@ -46585,11 +47265,11 @@ function projectVerticesToSurfaceWithNormals(vertProperties, sdfFn, out6) {
46585
47265
  function lowerNurbsSurfaceToManifold(plan, wasm) {
46586
47266
  if (!plan.allowApproximation && plan.thickness === 0) {
46587
47267
  throw new Error(
46588
- "nurbsSurface() now defaults to an exact open sheet. The Manifold backend cannot represent exact open surfaces. Add setActiveBackend('occt') or pass { approximate: true } with a non-zero thickness if you explicitly want a sampled fallback."
47268
+ `nurbsSurface() now defaults to an exact open sheet. The Manifold backend cannot represent exact open surfaces. ${OCCT_BACKEND_REQUIRED_HINT} Pass { approximate: true } with a non-zero thickness if you explicitly want a sampled fallback.`
46589
47269
  );
46590
47270
  }
46591
47271
  if (plan.thickness === 0) {
46592
- throw new Error("The Manifold backend cannot represent open sheet surfaces. Add setActiveBackend('occt') at the top of your script.");
47272
+ throw new Error(`The Manifold backend cannot represent open sheet surfaces. ${OCCT_BACKEND_REQUIRED_HINT}`);
46593
47273
  }
46594
47274
  const surface = new NurbsSurface(plan.controlGrid, {
46595
47275
  degreeU: plan.degreeU,
@@ -46739,8 +47419,8 @@ function lowerTrimmedNurbsSurfaceToManifold(surface, plan, wasm) {
46739
47419
  disposeWasmObject(mesh);
46740
47420
  }
46741
47421
  }
46742
- function lowerImportedMeshToManifold(fileData, format, filePath, wasm) {
46743
- const parsed = parseMeshFile(fileData, format);
47422
+ function lowerImportedMeshToManifold(fileData, format, filePath, wasm, object) {
47423
+ const parsed = parseMeshFile(fileData, format, { object });
46744
47424
  if (parsed.triVerts.length === 0) {
46745
47425
  throw new Error(`importMesh("${filePath}"): file contains no triangles`);
46746
47426
  }
@@ -46762,7 +47442,11 @@ function lowerImportedMeshToManifold(fileData, format, filePath, wasm) {
46762
47442
  }
46763
47443
  }
46764
47444
  function lowerShapeCompilePlanToShapeBackend(plan, wasm) {
46765
- return lowerShapeBackendWithCache("manifold", plan, (basePlan) => wrapManifoldShapeBackend(lowerShapeCompilePlanToManifold(basePlan, wasm)));
47445
+ return lowerShapeBackendWithCache(
47446
+ "manifold",
47447
+ plan,
47448
+ (basePlan) => wrapManifoldShapeBackend(lowerShapeCompilePlanToManifold(basePlan, wasm))
47449
+ );
46766
47450
  }
46767
47451
  const DEFAULT_LEAF_SIZE = 8;
46768
47452
  function cloneVec3$3(value) {