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
@@ -118,7 +118,7 @@ function denormalize(value, array) {
118
118
  throw new Error("Invalid component type.");
119
119
  }
120
120
  }
121
- function normalize$3(value, array) {
121
+ function normalize$4(value, array) {
122
122
  switch (array.constructor) {
123
123
  case Float32Array:
124
124
  return value;
@@ -6531,7 +6531,7 @@ class BufferAttribute {
6531
6531
  * @return {BufferAttribute} A reference to this instance.
6532
6532
  */
6533
6533
  setComponent(index2, component, value) {
6534
- if (this.normalized) value = normalize$3(value, this.array);
6534
+ if (this.normalized) value = normalize$4(value, this.array);
6535
6535
  this.array[index2 * this.itemSize + component] = value;
6536
6536
  return this;
6537
6537
  }
@@ -6554,7 +6554,7 @@ class BufferAttribute {
6554
6554
  * @return {BufferAttribute} A reference to this instance.
6555
6555
  */
6556
6556
  setX(index2, x2) {
6557
- if (this.normalized) x2 = normalize$3(x2, this.array);
6557
+ if (this.normalized) x2 = normalize$4(x2, this.array);
6558
6558
  this.array[index2 * this.itemSize] = x2;
6559
6559
  return this;
6560
6560
  }
@@ -6577,7 +6577,7 @@ class BufferAttribute {
6577
6577
  * @return {BufferAttribute} A reference to this instance.
6578
6578
  */
6579
6579
  setY(index2, y2) {
6580
- if (this.normalized) y2 = normalize$3(y2, this.array);
6580
+ if (this.normalized) y2 = normalize$4(y2, this.array);
6581
6581
  this.array[index2 * this.itemSize + 1] = y2;
6582
6582
  return this;
6583
6583
  }
@@ -6600,7 +6600,7 @@ class BufferAttribute {
6600
6600
  * @return {BufferAttribute} A reference to this instance.
6601
6601
  */
6602
6602
  setZ(index2, z2) {
6603
- if (this.normalized) z2 = normalize$3(z2, this.array);
6603
+ if (this.normalized) z2 = normalize$4(z2, this.array);
6604
6604
  this.array[index2 * this.itemSize + 2] = z2;
6605
6605
  return this;
6606
6606
  }
@@ -6623,7 +6623,7 @@ class BufferAttribute {
6623
6623
  * @return {BufferAttribute} A reference to this instance.
6624
6624
  */
6625
6625
  setW(index2, w2) {
6626
- if (this.normalized) w2 = normalize$3(w2, this.array);
6626
+ if (this.normalized) w2 = normalize$4(w2, this.array);
6627
6627
  this.array[index2 * this.itemSize + 3] = w2;
6628
6628
  return this;
6629
6629
  }
@@ -6638,8 +6638,8 @@ class BufferAttribute {
6638
6638
  setXY(index2, x2, y2) {
6639
6639
  index2 *= this.itemSize;
6640
6640
  if (this.normalized) {
6641
- x2 = normalize$3(x2, this.array);
6642
- y2 = normalize$3(y2, this.array);
6641
+ x2 = normalize$4(x2, this.array);
6642
+ y2 = normalize$4(y2, this.array);
6643
6643
  }
6644
6644
  this.array[index2 + 0] = x2;
6645
6645
  this.array[index2 + 1] = y2;
@@ -6657,9 +6657,9 @@ class BufferAttribute {
6657
6657
  setXYZ(index2, x2, y2, z2) {
6658
6658
  index2 *= this.itemSize;
6659
6659
  if (this.normalized) {
6660
- x2 = normalize$3(x2, this.array);
6661
- y2 = normalize$3(y2, this.array);
6662
- z2 = normalize$3(z2, this.array);
6660
+ x2 = normalize$4(x2, this.array);
6661
+ y2 = normalize$4(y2, this.array);
6662
+ z2 = normalize$4(z2, this.array);
6663
6663
  }
6664
6664
  this.array[index2 + 0] = x2;
6665
6665
  this.array[index2 + 1] = y2;
@@ -6679,10 +6679,10 @@ class BufferAttribute {
6679
6679
  setXYZW(index2, x2, y2, z2, w2) {
6680
6680
  index2 *= this.itemSize;
6681
6681
  if (this.normalized) {
6682
- x2 = normalize$3(x2, this.array);
6683
- y2 = normalize$3(y2, this.array);
6684
- z2 = normalize$3(z2, this.array);
6685
- w2 = normalize$3(w2, this.array);
6682
+ x2 = normalize$4(x2, this.array);
6683
+ y2 = normalize$4(y2, this.array);
6684
+ z2 = normalize$4(z2, this.array);
6685
+ w2 = normalize$4(w2, this.array);
6686
6686
  }
6687
6687
  this.array[index2 + 0] = x2;
6688
6688
  this.array[index2 + 1] = y2;
@@ -7554,6 +7554,55 @@ function disposeShapeBackend(backend) {
7554
7554
  const dispose = backend.dispose;
7555
7555
  dispose == null ? void 0 : dispose.call(backend);
7556
7556
  }
7557
+ function formatValidationValue(value) {
7558
+ if (typeof value === "number") return Number.isNaN(value) ? "NaN" : String(value);
7559
+ if (value === void 0) return "undefined";
7560
+ if (typeof value === "string") return JSON.stringify(value);
7561
+ try {
7562
+ return JSON.stringify(value) ?? String(value);
7563
+ } catch {
7564
+ return String(value);
7565
+ }
7566
+ }
7567
+ function requireFiniteNumber(value, label) {
7568
+ if (typeof value !== "number" || !Number.isFinite(value)) {
7569
+ throw new Error(`${label} must be a finite number, got ${formatValidationValue(value)}`);
7570
+ }
7571
+ return value;
7572
+ }
7573
+ function requirePositiveFiniteNumber(value, label) {
7574
+ const n = requireFiniteNumber(value, label);
7575
+ if (n <= 0) throw new Error(`${label} must be a positive finite number, got ${formatValidationValue(value)}`);
7576
+ return n;
7577
+ }
7578
+ function requireFiniteVec3$1(value, label) {
7579
+ if (!Array.isArray(value) || value.length !== 3) {
7580
+ throw new Error(`${label} must be [x, y, z] with finite numbers, got ${formatValidationValue(value)}`);
7581
+ }
7582
+ return [
7583
+ requireFiniteNumber(value[0], `${label}[0]`),
7584
+ requireFiniteNumber(value[1], `${label}[1]`),
7585
+ requireFiniteNumber(value[2], `${label}[2]`)
7586
+ ];
7587
+ }
7588
+ function requireNonZeroFiniteVec3(value, label) {
7589
+ const v = requireFiniteVec3$1(value, label);
7590
+ if (v[0] === 0 && v[1] === 0 && v[2] === 0) throw new Error(`${label} must not be [0, 0, 0]`);
7591
+ return v;
7592
+ }
7593
+ function requireFiniteMat4(value, label) {
7594
+ if (!Array.isArray(value) || value.length !== 16) {
7595
+ throw new Error(`${label} must be a 4x4 matrix array with 16 finite numbers, got ${formatValidationValue(value)}`);
7596
+ }
7597
+ return value.map((entry, index2) => requireFiniteNumber(entry, `${label}[${index2}]`));
7598
+ }
7599
+ function requireNonZeroFiniteScale3(value, label) {
7600
+ const scale2 = typeof value === "number" ? [requireFiniteNumber(value, label), requireFiniteNumber(value, label), requireFiniteNumber(value, label)] : requireFiniteVec3$1(value, label);
7601
+ if (Math.abs(scale2[0]) < 1e-12 || Math.abs(scale2[1]) < 1e-12 || Math.abs(scale2[2]) < 1e-12) {
7602
+ throw new Error(`${label} must have finite non-zero components, got ${formatValidationValue(value)}`);
7603
+ }
7604
+ return scale2;
7605
+ }
7557
7606
  const EPS$4 = 1e-10;
7558
7607
  function subVec3(a2, b) {
7559
7608
  return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
@@ -7635,9 +7684,6 @@ function solveRotateAroundAngle(axis, pivot, movingPoint, targetPoint, options =
7635
7684
  function identityMatrix() {
7636
7685
  return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
7637
7686
  }
7638
- function toMat4(input) {
7639
- return input instanceof Transform ? input.toArray() : input;
7640
- }
7641
7687
  function multiplyMat4(a2, b) {
7642
7688
  const out = new Array(16).fill(0);
7643
7689
  for (let col = 0; col < 4; col++) {
@@ -7714,23 +7760,40 @@ class Transform {
7714
7760
  }
7715
7761
  /** Wrap an existing `Transform` or raw 4x4 matrix as a `Transform`. */
7716
7762
  static from(input) {
7717
- return input instanceof Transform ? input : new Transform(input);
7763
+ return input instanceof Transform ? input : new Transform(requireFiniteMat4(input, "Transform.from() matrix"));
7718
7764
  }
7719
7765
  /** Create a translation transform. */
7720
7766
  static translation(x2, y2, z2) {
7721
- return new Transform([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x2, y2, z2, 1]);
7767
+ return new Transform([
7768
+ 1,
7769
+ 0,
7770
+ 0,
7771
+ 0,
7772
+ 0,
7773
+ 1,
7774
+ 0,
7775
+ 0,
7776
+ 0,
7777
+ 0,
7778
+ 1,
7779
+ 0,
7780
+ requireFiniteNumber(x2, "Transform.translation() x"),
7781
+ requireFiniteNumber(y2, "Transform.translation() y"),
7782
+ requireFiniteNumber(z2, "Transform.translation() z"),
7783
+ 1
7784
+ ]);
7722
7785
  }
7723
7786
  /** Create a uniform or per-axis scale transform. */
7724
7787
  static scale(v) {
7725
- const sx = typeof v === "number" ? v : v[0];
7726
- const sy = typeof v === "number" ? v : v[1];
7727
- const sz = typeof v === "number" ? v : v[2];
7788
+ const [sx, sy, sz] = requireNonZeroFiniteScale3(v, "Transform.scale() scale");
7728
7789
  return new Transform([sx, 0, 0, 0, 0, sy, 0, 0, 0, 0, sz, 0, 0, 0, 0, 1]);
7729
7790
  }
7730
7791
  /** Create a rotation around an arbitrary axis, optionally about a pivot. */
7731
7792
  static rotationAxis(axis, angleDeg, pivot = [0, 0, 0]) {
7732
- const [ux, uy, uz] = normalizeVec3$3(axis);
7733
- const rad = angleDeg * Math.PI / 180;
7793
+ const [ux, uy, uz] = normalizeVec3$3(requireNonZeroFiniteVec3(axis, "Transform.rotationAxis() axis"));
7794
+ const degrees = requireFiniteNumber(angleDeg, "Transform.rotationAxis() angleDeg");
7795
+ const [px, py, pz] = requireFiniteVec3$1(pivot, "Transform.rotationAxis() pivot");
7796
+ const rad = degrees * Math.PI / 180;
7734
7797
  const cos2 = Math.cos(rad);
7735
7798
  const sin2 = Math.sin(rad);
7736
7799
  const m00 = cos2 + ux * ux * (1 - cos2);
@@ -7742,7 +7805,6 @@ class Transform {
7742
7805
  const m20 = uz * ux * (1 - cos2) - uy * sin2;
7743
7806
  const m21 = uz * uy * (1 - cos2) + ux * sin2;
7744
7807
  const m22 = cos2 + uz * uz * (1 - cos2);
7745
- const [px, py, pz] = pivot;
7746
7808
  const tx = px - (m00 * px + m01 * py + m02 * pz);
7747
7809
  const ty = py - (m10 * px + m11 * py + m12 * pz);
7748
7810
  const tz = pz - (m20 * px + m21 * py + m22 * pz);
@@ -7750,12 +7812,16 @@ class Transform {
7750
7812
  }
7751
7813
  /** Solve the rotation needed to move one point onto a target line or plane. */
7752
7814
  static rotateAroundTo(axis, pivot, movingPoint, targetPoint, options = {}) {
7753
- const angleDeg = solveRotateAroundAngle(axis, pivot, movingPoint, targetPoint, options);
7754
- return Transform.rotationAxis(axis, angleDeg, pivot);
7815
+ const rotateAxis = requireNonZeroFiniteVec3(axis, "Transform.rotateAroundTo() axis");
7816
+ const rotatePivot = requireFiniteVec3$1(pivot, "Transform.rotateAroundTo() pivot");
7817
+ const moving = requireFiniteVec3$1(movingPoint, "Transform.rotateAroundTo() movingPoint");
7818
+ const target = requireFiniteVec3$1(targetPoint, "Transform.rotateAroundTo() targetPoint");
7819
+ const angleDeg = solveRotateAroundAngle(rotateAxis, rotatePivot, moving, target, options);
7820
+ return Transform.rotationAxis(rotateAxis, angleDeg, rotatePivot);
7755
7821
  }
7756
7822
  /** Compose transforms in chain order: `a.mul(b)` applies `a`, then `b`. */
7757
7823
  mul(other) {
7758
- const rhs = toMat4(other);
7824
+ const rhs = other instanceof Transform ? other.toArray() : requireFiniteMat4(other, "Transform.mul() matrix");
7759
7825
  return new Transform(multiplyMat4(rhs, this.m));
7760
7826
  }
7761
7827
  /** Translate after the current transform. */
@@ -7766,6 +7832,18 @@ class Transform {
7766
7832
  rotateAxis(axis, angleDeg, pivot = [0, 0, 0]) {
7767
7833
  return this.mul(Transform.rotationAxis(axis, angleDeg, pivot));
7768
7834
  }
7835
+ /** Rotate about the X axis after the current transform (parity with `Shape.rotateX`). */
7836
+ rotateX(angleDeg, pivot = [0, 0, 0]) {
7837
+ return this.rotateAxis([1, 0, 0], angleDeg, pivot);
7838
+ }
7839
+ /** Rotate about the Y axis after the current transform (parity with `Shape.rotateY`). */
7840
+ rotateY(angleDeg, pivot = [0, 0, 0]) {
7841
+ return this.rotateAxis([0, 1, 0], angleDeg, pivot);
7842
+ }
7843
+ /** Rotate about the Z axis after the current transform (parity with `Shape.rotateZ`). */
7844
+ rotateZ(angleDeg, pivot = [0, 0, 0]) {
7845
+ return this.rotateAxis([0, 0, 1], angleDeg, pivot);
7846
+ }
7769
7847
  /** Scale after the current transform. */
7770
7848
  scale(v) {
7771
7849
  return this.mul(Transform.scale(v));
@@ -7776,11 +7854,11 @@ class Transform {
7776
7854
  }
7777
7855
  /** Transform a point using homogeneous coordinates. */
7778
7856
  point(p2) {
7779
- return transformPoint(this.m, p2, 1);
7857
+ return transformPoint(this.m, requireFiniteVec3$1(p2, "Transform.point() point"), 1);
7780
7858
  }
7781
7859
  /** Transform a direction vector without translation. */
7782
7860
  vector(v) {
7783
- return transformPoint(this.m, v, 0);
7861
+ return transformPoint(this.m, requireFiniteVec3$1(v, "Transform.vector() vector"), 0);
7784
7862
  }
7785
7863
  /** Return the transform as a raw 4x4 matrix array. */
7786
7864
  toArray() {
@@ -10010,6 +10088,42 @@ function cloneCutTaperCompilePlan(plan) {
10010
10088
  scale: [canonicalNumber(plan.scale[0]), canonicalNumber(plan.scale[1])]
10011
10089
  };
10012
10090
  }
10091
+ function cloneEdgeFeatureTarget(target) {
10092
+ return {
10093
+ midpoint: [target.midpoint[0], target.midpoint[1], target.midpoint[2]],
10094
+ start: [target.start[0], target.start[1], target.start[2]],
10095
+ end: [target.end[0], target.end[1], target.end[2]],
10096
+ convex: target.convex,
10097
+ ...target.nativeTopology ? { nativeTopology: { backend: target.nativeTopology.backend, edge: target.nativeTopology.edge } } : {}
10098
+ };
10099
+ }
10100
+ function cloneEdgeFeatureQuery(query) {
10101
+ if (!query) return void 0;
10102
+ return {
10103
+ ...query.near ? { near: [query.near[0], query.near[1], query.near[2]] } : {},
10104
+ ...query.parallel ? { parallel: [query.parallel[0], query.parallel[1], query.parallel[2]] } : {},
10105
+ ...query.perpendicular ? { perpendicular: [query.perpendicular[0], query.perpendicular[1], query.perpendicular[2]] } : {},
10106
+ ...query.convex !== void 0 ? { convex: query.convex } : {},
10107
+ ...query.concave !== void 0 ? { concave: query.concave } : {},
10108
+ ...query.minAngle !== void 0 ? { minAngle: canonicalNumber(query.minAngle) } : {},
10109
+ ...query.maxAngle !== void 0 ? { maxAngle: canonicalNumber(query.maxAngle) } : {},
10110
+ ...query.minLength !== void 0 ? { minLength: canonicalNumber(query.minLength) } : {},
10111
+ ...query.maxLength !== void 0 ? { maxLength: canonicalNumber(query.maxLength) } : {},
10112
+ ...query.within ? {
10113
+ within: {
10114
+ ...query.within.xMin !== void 0 ? { xMin: canonicalNumber(query.within.xMin) } : {},
10115
+ ...query.within.xMax !== void 0 ? { xMax: canonicalNumber(query.within.xMax) } : {},
10116
+ ...query.within.yMin !== void 0 ? { yMin: canonicalNumber(query.within.yMin) } : {},
10117
+ ...query.within.yMax !== void 0 ? { yMax: canonicalNumber(query.within.yMax) } : {},
10118
+ ...query.within.zMin !== void 0 ? { zMin: canonicalNumber(query.within.zMin) } : {},
10119
+ ...query.within.zMax !== void 0 ? { zMax: canonicalNumber(query.within.zMax) } : {}
10120
+ }
10121
+ } : {},
10122
+ ...query.atZ !== void 0 ? { atZ: canonicalNumber(query.atZ) } : {},
10123
+ ...query.tolerance !== void 0 ? { tolerance: canonicalNumber(query.tolerance) } : {},
10124
+ ...query.angleTolerance !== void 0 ? { angleTolerance: canonicalNumber(query.angleTolerance) } : {}
10125
+ };
10126
+ }
10013
10127
  function featureCutExtentForwardSide(extent) {
10014
10128
  return extent.kind === "two-sided" ? extent.forward : extent;
10015
10129
  }
@@ -10106,6 +10220,41 @@ function cloneSweepPathCompilePlan(path) {
10106
10220
  degree: path.degree,
10107
10221
  closed: path.closed
10108
10222
  };
10223
+ case "route3d":
10224
+ return {
10225
+ kind: "route3d",
10226
+ segments: path.segments.map(
10227
+ (segment) => segment.kind === "line" ? {
10228
+ kind: "line",
10229
+ from: canonicalVec3(segment.from),
10230
+ to: canonicalVec3(segment.to),
10231
+ length: canonicalNumber(segment.length)
10232
+ } : {
10233
+ kind: "arc",
10234
+ center: canonicalVec3(segment.center),
10235
+ radius: canonicalNumber(segment.radius),
10236
+ axis: canonicalVec3(segment.axis),
10237
+ start: canonicalVec3(segment.start),
10238
+ end: canonicalVec3(segment.end),
10239
+ sweepDeg: canonicalNumber(segment.sweepDeg),
10240
+ length: canonicalNumber(segment.length)
10241
+ }
10242
+ ),
10243
+ ports: Object.fromEntries(
10244
+ Object.entries(path.ports).map(([name, port]) => [
10245
+ name,
10246
+ {
10247
+ name: port.name,
10248
+ origin: canonicalVec3(port.origin),
10249
+ axis: canonicalVec3(port.axis),
10250
+ xAxis: canonicalVec3(port.xAxis),
10251
+ yAxis: canonicalVec3(port.yAxis),
10252
+ station: canonicalNumber(port.station)
10253
+ }
10254
+ ])
10255
+ ),
10256
+ length: canonicalNumber(path.length)
10257
+ };
10109
10258
  default:
10110
10259
  assertExhaustive(path);
10111
10260
  }
@@ -10200,6 +10349,7 @@ function cloneProfileEdge(edge) {
10200
10349
  }
10201
10350
  }
10202
10351
  function cloneShapeCompilePlan(plan) {
10352
+ var _a3, _b3, _c2;
10203
10353
  if (!plan) return null;
10204
10354
  let result;
10205
10355
  switch (plan.kind) {
@@ -10378,13 +10528,9 @@ function cloneShapeCompilePlan(plan) {
10378
10528
  radius: plan.radius,
10379
10529
  segments: plan.segments,
10380
10530
  continuity: plan.continuity,
10381
- edgeTargets: plan.edgeTargets.map((t) => ({
10382
- midpoint: [t.midpoint[0], t.midpoint[1], t.midpoint[2]],
10383
- start: [t.start[0], t.start[1], t.start[2]],
10384
- end: [t.end[0], t.end[1], t.end[2]],
10385
- convex: t.convex,
10386
- ...t.nativeTopology ? { nativeTopology: { backend: t.nativeTopology.backend, edge: t.nativeTopology.edge } } : {}
10387
- }))
10531
+ edgeTargets: (_a3 = plan.edgeTargets) == null ? void 0 : _a3.map(cloneEdgeFeatureTarget),
10532
+ edgeQuery: cloneEdgeFeatureQuery(plan.edgeQuery),
10533
+ edgeSelection: plan.edgeSelection
10388
10534
  };
10389
10535
  break;
10390
10536
  case "cornerYBlend":
@@ -10394,13 +10540,7 @@ function cloneShapeCompilePlan(plan) {
10394
10540
  radius: plan.radius,
10395
10541
  segments: plan.segments,
10396
10542
  continuity: plan.continuity,
10397
- edgeTargets: plan.edgeTargets.map((t) => ({
10398
- midpoint: [t.midpoint[0], t.midpoint[1], t.midpoint[2]],
10399
- start: [t.start[0], t.start[1], t.start[2]],
10400
- end: [t.end[0], t.end[1], t.end[2]],
10401
- convex: t.convex,
10402
- ...t.nativeTopology ? { nativeTopology: { backend: t.nativeTopology.backend, edge: t.nativeTopology.edge } } : {}
10403
- })),
10543
+ edgeTargets: ((_b3 = plan.edgeTargets) == null ? void 0 : _b3.map(cloneEdgeFeatureTarget)) ?? [],
10404
10544
  cornerPoint: [plan.cornerPoint[0], plan.cornerPoint[1], plan.cornerPoint[2]],
10405
10545
  cornerTolerance: canonicalNumber(plan.cornerTolerance),
10406
10546
  minBranchAngleDeg: canonicalNumber(plan.minBranchAngleDeg)
@@ -10411,13 +10551,9 @@ function cloneShapeCompilePlan(plan) {
10411
10551
  kind: "chamferEdges",
10412
10552
  base: cloneShapeCompilePlan(plan.base),
10413
10553
  size: plan.size,
10414
- edgeTargets: plan.edgeTargets.map((t) => ({
10415
- midpoint: [t.midpoint[0], t.midpoint[1], t.midpoint[2]],
10416
- start: [t.start[0], t.start[1], t.start[2]],
10417
- end: [t.end[0], t.end[1], t.end[2]],
10418
- convex: t.convex,
10419
- ...t.nativeTopology ? { nativeTopology: { backend: t.nativeTopology.backend, edge: t.nativeTopology.edge } } : {}
10420
- }))
10554
+ edgeTargets: (_c2 = plan.edgeTargets) == null ? void 0 : _c2.map(cloneEdgeFeatureTarget),
10555
+ edgeQuery: cloneEdgeFeatureQuery(plan.edgeQuery),
10556
+ edgeSelection: plan.edgeSelection
10421
10557
  };
10422
10558
  break;
10423
10559
  case "draft":
@@ -10437,7 +10573,13 @@ function cloneShapeCompilePlan(plan) {
10437
10573
  };
10438
10574
  break;
10439
10575
  case "importedMesh":
10440
- result = { kind: "importedMesh", filePath: plan.filePath, format: plan.format, fileData: plan.fileData };
10576
+ result = {
10577
+ kind: "importedMesh",
10578
+ filePath: plan.filePath,
10579
+ format: plan.format,
10580
+ ...plan.object ? { object: plan.object } : {},
10581
+ fileData: plan.fileData
10582
+ };
10441
10583
  break;
10442
10584
  case "sdf":
10443
10585
  result = {
@@ -10752,6 +10894,83 @@ function remapToKnotDomain(t, n, degree, knots) {
10752
10894
  const uMax = knots[n];
10753
10895
  return uMin + Math.max(0, Math.min(1, t)) * (uMax - uMin);
10754
10896
  }
10897
+ const EPSILON$1 = 1e-9;
10898
+ function add$2(a2, b) {
10899
+ return [a2[0] + b[0], a2[1] + b[1], a2[2] + b[2]];
10900
+ }
10901
+ function scale$2(v, factor) {
10902
+ return [v[0] * factor, v[1] * factor, v[2] * factor];
10903
+ }
10904
+ function sub$2(a2, b) {
10905
+ return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
10906
+ }
10907
+ function dot$3(a2, b) {
10908
+ return a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2];
10909
+ }
10910
+ function cross$4(a2, b) {
10911
+ 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]];
10912
+ }
10913
+ function rotateAroundAxis(v, axis, angleRad) {
10914
+ const c2 = Math.cos(angleRad);
10915
+ const s = Math.sin(angleRad);
10916
+ const term1 = scale$2(v, c2);
10917
+ const term2 = scale$2(cross$4(axis, v), s);
10918
+ const term3 = scale$2(axis, dot$3(axis, v) * (1 - c2));
10919
+ return add$2(add$2(term1, term2), term3);
10920
+ }
10921
+ function arcPointAt(segment, t) {
10922
+ const sweepRad = segment.sweepDeg * Math.PI / 180 * t;
10923
+ return add$2(segment.center, rotateAroundAxis(sub$2(segment.start, segment.center), segment.axis, sweepRad));
10924
+ }
10925
+ function pushUnique(points, point) {
10926
+ const prev = points[points.length - 1];
10927
+ if (!prev || Math.hypot(prev[0] - point[0], prev[1] - point[1], prev[2] - point[2]) > EPSILON$1) {
10928
+ points.push([point[0], point[1], point[2]]);
10929
+ }
10930
+ }
10931
+ function resolveOptions(options) {
10932
+ if (typeof options === "number") return { samples: Math.max(2, Math.floor(options)), maxAngleDeg: 6 };
10933
+ return {
10934
+ samples: Math.max(2, Math.floor((options == null ? void 0 : options.samples) ?? 48)),
10935
+ maxAngleDeg: Math.max(0.25, (options == null ? void 0 : options.maxAngleDeg) ?? 6)
10936
+ };
10937
+ }
10938
+ function routePointAt(plan, t) {
10939
+ if (plan.segments.length === 0) return [0, 0, 0];
10940
+ const target = Math.max(0, Math.min(1, t)) * plan.length;
10941
+ let station = 0;
10942
+ for (const segment of plan.segments) {
10943
+ const next = station + segment.length;
10944
+ if (target <= next || segment === plan.segments[plan.segments.length - 1]) {
10945
+ const localT = segment.length <= EPSILON$1 ? 1 : Math.max(0, Math.min(1, (target - station) / segment.length));
10946
+ if (segment.kind === "line") return add$2(segment.from, scale$2(sub$2(segment.to, segment.from), localT));
10947
+ return arcPointAt(segment, localT);
10948
+ }
10949
+ station = next;
10950
+ }
10951
+ const last = plan.segments[plan.segments.length - 1];
10952
+ return last.kind === "line" ? last.to : last.end;
10953
+ }
10954
+ function sampleRoute3DCompilePlan(plan, options) {
10955
+ const resolved = resolveOptions(options);
10956
+ const points = [];
10957
+ const totalIntervals = Math.max(1, resolved.samples - 1);
10958
+ for (const segment of plan.segments) {
10959
+ const proportional = plan.length > EPSILON$1 ? Math.ceil(segment.length / plan.length * totalIntervals) : 1;
10960
+ const arcIntervals = segment.kind === "arc" ? Math.ceil(Math.abs(segment.sweepDeg) / resolved.maxAngleDeg) : 1;
10961
+ const intervals = Math.max(1, proportional, arcIntervals);
10962
+ const start = segment.kind === "line" ? segment.from : segment.start;
10963
+ pushUnique(points, start);
10964
+ for (let i = 1; i <= intervals; i += 1) {
10965
+ if (segment.kind === "line") {
10966
+ pushUnique(points, add$2(segment.from, scale$2(sub$2(segment.to, segment.from), i / intervals)));
10967
+ } else {
10968
+ pushUnique(points, arcPointAt(segment, i / intervals));
10969
+ }
10970
+ }
10971
+ }
10972
+ return points;
10973
+ }
10755
10974
  function catmullRom3D$1(p0, p1, p2, p3, t, tension) {
10756
10975
  const tt = t * t;
10757
10976
  const ttt = tt * t;
@@ -10890,6 +11109,8 @@ function evalPathAt(path, t) {
10890
11109
  const u2 = remapToKnotDomain(Math.max(0, Math.min(1, t)), path.controlPoints.length, path.degree, path.knots);
10891
11110
  return deBoor3D(path.controlPoints, path.weights, path.knots, path.degree, u2);
10892
11111
  }
11112
+ case "route3d":
11113
+ return routePointAt(path, t);
10893
11114
  }
10894
11115
  }
10895
11116
  function estimateCurvatureAt(path, t) {
@@ -10936,10 +11157,13 @@ function sweepPathToPolyline(path, samples = 48) {
10936
11157
  }
10937
11158
  return result;
10938
11159
  }
11160
+ case "route3d":
11161
+ return sampleRoute3DCompilePlan(path, samples);
10939
11162
  }
10940
11163
  }
10941
11164
  function sweepPathToPolylineAdaptive(path, baseSamples = 48) {
10942
11165
  if (path.kind === "polyline") return path.points;
11166
+ if (path.kind === "route3d") return sampleRoute3DCompilePlan(path, baseSamples);
10943
11167
  const probeCount = 64;
10944
11168
  const curvatures = [];
10945
11169
  for (let i = 0; i <= probeCount; i++) {
@@ -10970,7 +11194,7 @@ const EPS$2 = 1e-8;
10970
11194
  function midpoint$1(start, end) {
10971
11195
  return [(start[0] + end[0]) * 0.5, (start[1] + end[1]) * 0.5, (start[2] + end[2]) * 0.5];
10972
11196
  }
10973
- function normalize$2(v) {
11197
+ function normalize$3(v) {
10974
11198
  const len = Math.hypot(v[0], v[1], v[2]);
10975
11199
  if (len <= EPS$2) throw new Error("Edge feature selection requires a non-zero direction vector");
10976
11200
  return [v[0] / len, v[1] / len, v[2] / len];
@@ -11389,8 +11613,8 @@ function extrudeEdgeSelection(plan, edgeName) {
11389
11613
  }
11390
11614
  const points = plan.profile.points;
11391
11615
  const [bl, br, _tr, tl] = points;
11392
- const u2 = normalize$2([br[0] - bl[0], br[1] - bl[1], 0]);
11393
- const v = normalize$2([tl[0] - bl[0], tl[1] - bl[1], 0]);
11616
+ const u2 = normalize$3([br[0] - bl[0], br[1] - bl[1], 0]);
11617
+ const v = normalize$3([tl[0] - bl[0], tl[1] - bl[1], 0]);
11394
11618
  const vertex = points[index2];
11395
11619
  const quadrant = index2 === 0 ? [1, -1] : index2 === 1 ? [-1, -1] : index2 === 2 ? [-1, 1] : [1, 1];
11396
11620
  return {
@@ -11411,9 +11635,9 @@ function extrudeEdgeSelection(plan, edgeName) {
11411
11635
  function applySelectionTransform(selection, transform) {
11412
11636
  const start = transform.point(selection.start);
11413
11637
  const end = transform.point(selection.end);
11414
- const basisX = normalize$2(transform.vector(selection.basisX));
11415
- const basisY = normalize$2(transform.vector(selection.basisY));
11416
- const axis = normalize$2(subtract(end, start));
11638
+ const basisX = normalize$3(transform.vector(selection.basisX));
11639
+ const basisY = normalize$3(transform.vector(selection.basisY));
11640
+ const axis = normalize$3(subtract(end, start));
11417
11641
  return {
11418
11642
  kind: "line-segment",
11419
11643
  edgeName: selection.edgeName,
@@ -12688,6 +12912,20 @@ function cleanName(value) {
12688
12912
  const trimmed = value == null ? void 0 : value.replace(/\s+/g, " ").trim();
12689
12913
  return trimmed && trimmed.length > 0 ? trimmed : null;
12690
12914
  }
12915
+ function uniqueNames(baseNames) {
12916
+ const totals = /* @__PURE__ */ new Map();
12917
+ const seen = /* @__PURE__ */ new Map();
12918
+ for (const baseName of baseNames) totals.set(baseName, (totals.get(baseName) ?? 0) + 1);
12919
+ return baseNames.map((baseName) => {
12920
+ if ((totals.get(baseName) ?? 0) <= 1) return baseName;
12921
+ const ordinal = (seen.get(baseName) ?? 0) + 1;
12922
+ seen.set(baseName, ordinal);
12923
+ return `${baseName} #${String(ordinal).padStart(3, "0")}`;
12924
+ });
12925
+ }
12926
+ function normalizeSelector(value) {
12927
+ return value.trim().replace(/\s+/g, " ").toLowerCase();
12928
+ }
12691
12929
  function extractModelXml(data) {
12692
12930
  const zip = unzipSync(new Uint8Array(data));
12693
12931
  const path = Object.keys(zip).find((entry) => entry.toLowerCase() === "3d/3dmodel.model");
@@ -12759,14 +12997,105 @@ function collectObjectGeometry(model, objectId, transforms = [], visiting = /* @
12759
12997
  }
12760
12998
  return { vertices, triangles, bbox };
12761
12999
  }
13000
+ function buildBuildEntries(model) {
13001
+ const buildBaseNames = model.buildItems.map(
13002
+ (item) => {
13003
+ var _a3;
13004
+ return cleanName((_a3 = model.objectsById.get(item.objectId)) == null ? void 0 : _a3.sourceName) ?? `object-${item.objectId}`;
13005
+ }
13006
+ );
13007
+ const buildNames = uniqueNames(buildBaseNames);
13008
+ return model.buildItems.map((item, index2) => {
13009
+ const object = model.objectsById.get(item.objectId);
13010
+ return {
13011
+ stableRef: `3mf:build:${String(index2 + 1).padStart(3, "0")}:object:${item.objectId}`,
13012
+ objectId: item.objectId,
13013
+ autoName: buildNames[index2],
13014
+ sourceName: object == null ? void 0 : object.sourceName,
13015
+ type: object == null ? void 0 : object.type,
13016
+ transform: item.transform
13017
+ };
13018
+ });
13019
+ }
13020
+ function buildObjectEntries(model) {
13021
+ const objectNames = uniqueNames(model.objects.map((object) => cleanName(object.sourceName) ?? `object-${object.id}`));
13022
+ return model.objects.map(
13023
+ (object, index2) => ({
13024
+ stableRef: `3mf:object:${object.id}`,
13025
+ objectId: object.id,
13026
+ autoName: objectNames[index2],
13027
+ sourceName: object.sourceName,
13028
+ type: object.type
13029
+ })
13030
+ );
13031
+ }
13032
+ function buildDefaultSourceEntries(model) {
13033
+ return model.buildItems.length > 0 ? buildBuildEntries(model) : buildObjectEntries(model);
13034
+ }
13035
+ function buildSelectableEntries(model) {
13036
+ return model.buildItems.length > 0 ? [...buildBuildEntries(model), ...buildObjectEntries(model)] : buildObjectEntries(model);
13037
+ }
13038
+ function entryMatchesSelector(entry, selector) {
13039
+ const needle = normalizeSelector(selector);
13040
+ return [
13041
+ entry.stableRef,
13042
+ entry.autoName,
13043
+ entry.sourceName ?? "",
13044
+ entry.objectId,
13045
+ `object-${entry.objectId}`,
13046
+ `3mf:object:${entry.objectId}`
13047
+ ].some((candidate) => normalizeSelector(candidate) === needle);
13048
+ }
13049
+ function availableEntryList(entries) {
13050
+ return entries.map((entry) => `${entry.autoName} [${entry.stableRef}]`).join(", ") || "none";
13051
+ }
13052
+ function selectSourceEntry(entries, selector) {
13053
+ const matches = entries.filter((entry) => entryMatchesSelector(entry, selector));
13054
+ if (matches.length === 1) return matches[0];
13055
+ if (matches.length > 1) {
13056
+ throw new Error(
13057
+ `3MF object selector "${selector}" is ambiguous. Use a stable ref: ${matches.map((entry) => entry.stableRef).join(", ")}`
13058
+ );
13059
+ }
13060
+ throw new Error(`3MF object selector "${selector}" matched no build item or resource object. Available: ${availableEntryList(entries)}`);
13061
+ }
13062
+ function selectorLooksLikeStableRef(selector) {
13063
+ return /^3mf:(build|object):/i.test(selector.trim());
13064
+ }
13065
+ function meshFromGeometry(geometry) {
13066
+ const vertices = [];
13067
+ const triangles = [];
13068
+ for (const vertex of geometry.vertices) vertices.push(vertex[0], vertex[1], vertex[2]);
13069
+ for (const triangle of geometry.triangles) triangles.push(triangle[0], triangle[1], triangle[2]);
13070
+ return {
13071
+ vertProperties: new Float32Array(vertices),
13072
+ triVerts: new Uint32Array(triangles),
13073
+ numProp: 3,
13074
+ mergeFromVert: new Uint32Array(0),
13075
+ mergeToVert: new Uint32Array(0)
13076
+ };
13077
+ }
13078
+ function partFromEntry(model, entry) {
13079
+ const geometry = collectObjectGeometry(model, entry.objectId, [entry.transform]);
13080
+ return {
13081
+ stableRef: entry.stableRef,
13082
+ objectId: entry.objectId,
13083
+ autoName: entry.autoName,
13084
+ sourceName: entry.sourceName,
13085
+ type: entry.type,
13086
+ mesh: meshFromGeometry(geometry),
13087
+ bbox: geometry.bbox,
13088
+ vertexCount: geometry.vertices.length,
13089
+ triangleCount: geometry.triangles.length
13090
+ };
13091
+ }
12762
13092
  function parse3mfMesh(data) {
12763
13093
  const model = parseModel(data);
12764
- const sourceIds = model.buildItems.length > 0 ? model.buildItems.map((item) => item.objectId) : model.objects.map((object) => object.id);
12765
- const transforms = model.buildItems.length > 0 ? model.buildItems.map((item) => item.transform) : sourceIds.map(() => void 0);
13094
+ const entries = buildDefaultSourceEntries(model);
12766
13095
  const vertices = [];
12767
13096
  const triangles = [];
12768
- sourceIds.forEach((objectId, index2) => {
12769
- const geometry = collectObjectGeometry(model, objectId, [transforms[index2]]);
13097
+ entries.forEach((entry) => {
13098
+ const geometry = collectObjectGeometry(model, entry.objectId, [entry.transform]);
12770
13099
  const offset = vertices.length / 3;
12771
13100
  for (const vertex of geometry.vertices) vertices.push(vertex[0], vertex[1], vertex[2]);
12772
13101
  for (const triangle of geometry.triangles) triangles.push(triangle[0] + offset, triangle[1] + offset, triangle[2] + offset);
@@ -12779,6 +13108,11 @@ function parse3mfMesh(data) {
12779
13108
  mergeToVert: new Uint32Array(0)
12780
13109
  };
12781
13110
  }
13111
+ function parse3mfMeshPart(data, selector) {
13112
+ const model = parseModel(data);
13113
+ const entries = selectorLooksLikeStableRef(selector) ? buildSelectableEntries(model) : buildDefaultSourceEntries(model);
13114
+ return partFromEntry(model, selectSourceEntry(entries, selector));
13115
+ }
12782
13116
  function isStlBinary(data) {
12783
13117
  if (data.byteLength < 84) return false;
12784
13118
  const view = new DataView(data);
@@ -12923,17 +13257,17 @@ function parseObj(data) {
12923
13257
  mergeToVert: new Uint32Array(0)
12924
13258
  };
12925
13259
  }
12926
- function parse3mf(data) {
12927
- return parse3mfMesh(data);
13260
+ function parse3mf(data, options = {}) {
13261
+ return options.object ? parse3mfMeshPart(data, options.object).mesh : parse3mfMesh(data);
12928
13262
  }
12929
- function parseMeshFile(data, format) {
13263
+ function parseMeshFile(data, format, options = {}) {
12930
13264
  switch (format) {
12931
13265
  case "stl":
12932
13266
  return parseStl(data);
12933
13267
  case "obj":
12934
13268
  return parseObj(data);
12935
13269
  case "3mf":
12936
- return parse3mf(data);
13270
+ return parse3mf(data, options);
12937
13271
  }
12938
13272
  }
12939
13273
  function cleanZero(value) {
@@ -12960,6 +13294,84 @@ function planeFrameToWorldToPlaneMatrix(frame) {
12960
13294
  1
12961
13295
  ];
12962
13296
  }
13297
+ const DEFAULT_TOLERANCE = 1;
13298
+ const DEFAULT_ANGLE_TOLERANCE = 10;
13299
+ function distSq$1(a2, b) {
13300
+ const dx = a2[0] - b[0];
13301
+ const dy = a2[1] - b[1];
13302
+ const dz = a2[2] - b[2];
13303
+ return dx * dx + dy * dy + dz * dz;
13304
+ }
13305
+ function vecLength$2(v) {
13306
+ return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
13307
+ }
13308
+ function normalize$2(v) {
13309
+ const len = vecLength$2(v);
13310
+ if (len < 1e-12) return [0, 0, 0];
13311
+ return [v[0] / len, v[1] / len, v[2] / len];
13312
+ }
13313
+ function absDot$1(a2, b) {
13314
+ return Math.abs(a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2]);
13315
+ }
13316
+ function applyEdgeQueryFilters(edges, query) {
13317
+ const tol = query.tolerance ?? DEFAULT_TOLERANCE;
13318
+ const angleTol = query.angleTolerance ?? DEFAULT_ANGLE_TOLERANCE;
13319
+ const cosAngleTol = Math.cos(angleTol * Math.PI / 180);
13320
+ let result = edges;
13321
+ if (query.convex === true) {
13322
+ result = result.filter((e) => e.convex);
13323
+ }
13324
+ if (query.concave === true) {
13325
+ result = result.filter((e) => !e.convex);
13326
+ }
13327
+ if (query.minAngle != null) {
13328
+ const min2 = query.minAngle;
13329
+ result = result.filter((e) => e.dihedralAngle >= min2);
13330
+ }
13331
+ if (query.maxAngle != null) {
13332
+ const max2 = query.maxAngle;
13333
+ result = result.filter((e) => e.dihedralAngle <= max2);
13334
+ }
13335
+ if (query.minLength != null) {
13336
+ const min2 = query.minLength;
13337
+ result = result.filter((e) => e.length >= min2);
13338
+ }
13339
+ if (query.maxLength != null) {
13340
+ const max2 = query.maxLength;
13341
+ result = result.filter((e) => e.length <= max2);
13342
+ }
13343
+ if (query.parallel) {
13344
+ const dir = normalize$2(query.parallel);
13345
+ result = result.filter((e) => absDot$1(e.direction, dir) >= cosAngleTol);
13346
+ }
13347
+ if (query.perpendicular) {
13348
+ const dir = normalize$2(query.perpendicular);
13349
+ const sinAngleTol = Math.sin(angleTol * Math.PI / 180);
13350
+ result = result.filter((e) => absDot$1(e.direction, dir) <= sinAngleTol);
13351
+ }
13352
+ if (query.atZ != null) {
13353
+ const z2 = query.atZ;
13354
+ result = result.filter((e) => Math.abs(e.midpoint[2] - z2) <= tol);
13355
+ }
13356
+ if (query.within) {
13357
+ const b = query.within;
13358
+ result = result.filter((e) => {
13359
+ const [mx, my, mz] = e.midpoint;
13360
+ if (b.xMin != null && mx < b.xMin) return false;
13361
+ if (b.xMax != null && mx > b.xMax) return false;
13362
+ if (b.yMin != null && my < b.yMin) return false;
13363
+ if (b.yMax != null && my > b.yMax) return false;
13364
+ if (b.zMin != null && mz < b.zMin) return false;
13365
+ if (b.zMax != null && mz > b.zMax) return false;
13366
+ return true;
13367
+ });
13368
+ }
13369
+ if (query.near) {
13370
+ const pt = query.near;
13371
+ result = result.slice().sort((a2, b) => distSq$1(a2.midpoint, pt) - distSq$1(b.midpoint, pt));
13372
+ }
13373
+ return result;
13374
+ }
12963
13375
  const SHELL_OPEN_FACE_CANONICAL = {
12964
13376
  front: "side-bottom",
12965
13377
  back: "side-top",
@@ -15259,6 +15671,8 @@ function getUnsupportedSdfProgramReason(node) {
15259
15671
  return "noise depends on table-based simplex evaluation";
15260
15672
  case "sdf:voronoi":
15261
15673
  return "voronoi depends on table-based Worley evaluation";
15674
+ case "sdf:circularArray":
15675
+ return "circularArray folds polar coordinates and is not implemented in SdfProgram yet";
15262
15676
  case "sdf:custom":
15263
15677
  return "custom uses a dynamic JavaScript function body";
15264
15678
  case "sdf:polylineSweep":
@@ -16065,6 +16479,8 @@ function sampleSweepPath(path, edgeLengthHint) {
16065
16479
  path,
16066
16480
  edgeLengthHint != null ? Math.max(16, Math.round(path.controlPoints.length * 12 / Math.max(0.1, edgeLengthHint))) : 48
16067
16481
  );
16482
+ case "route3d":
16483
+ return sweepPathToPolyline(path, resolveCurveSampleCount(edgeLengthHint, path.length));
16068
16484
  }
16069
16485
  }
16070
16486
  function clamp(v, lo, hi) {
@@ -17348,6 +17764,7 @@ function rotateVector(v, axis, c2, s) {
17348
17764
  ];
17349
17765
  }
17350
17766
  const OFFSET_SOLID_EPS$1 = 1e-9;
17767
+ const OCCT_BACKEND_REQUIRED_HINT = "Select the OCCT backend in the editor or run the CLI with --backend occt.";
17351
17768
  function disposeWasmObject(value) {
17352
17769
  if (value != null && typeof value.delete === "function") value.delete();
17353
17770
  }
@@ -17832,7 +18249,7 @@ function profilePolygonLoopTopologyMatches(left, right) {
17832
18249
  }
17833
18250
  function lowerOffsetLoftCompilePlan(plan, thickness, wasm) {
17834
18251
  if (plan.profiles.length !== plan.heights.length || plan.profiles.length < 2) {
17835
- throw new Error("Offset solid requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
18252
+ throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
17836
18253
  }
17837
18254
  const heights = [...plan.heights];
17838
18255
  heights[0] -= thickness;
@@ -17843,7 +18260,7 @@ function lowerOffsetLoftCompilePlan(plan, thickness, wasm) {
17843
18260
  const offsetPolygons = plan.profiles.map((profile) => offsetProfilePolygonsForManifold(profile, thickness, wasm));
17844
18261
  const stitched = loftStitched(offsetPolygons, heights, wasm);
17845
18262
  if (!stitched) {
17846
- throw new Error("Offset solid requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
18263
+ throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
17847
18264
  }
17848
18265
  return stitched;
17849
18266
  }
@@ -17883,16 +18300,16 @@ function lowerOffsetSweepCompilePlan(plan, thickness, wasm) {
17883
18300
  const basePolygons = profilePolygonsForManifold(plan.profile, wasm);
17884
18301
  const offsetPolygons = offsetProfilePolygonsForManifold(plan.profile, thickness, wasm);
17885
18302
  if (!profilePolygonLoopTopologyMatches(basePolygons, offsetPolygons)) {
17886
- throw new Error("Offset solid requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
18303
+ throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
17887
18304
  }
17888
18305
  const pathPoints = sweepPathToPolylineAdaptive(plan.path, plan.pathSamples ?? 48);
17889
18306
  const extendedPath = extendedStraightSweepPathForManifold(pathPoints, thickness);
17890
18307
  if (!extendedPath) {
17891
- throw new Error("Offset solid requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
18308
+ throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
17892
18309
  }
17893
18310
  const stitched = sweepStitched(offsetPolygons, extendedPath, [plan.up[0], plan.up[1], plan.up[2]], wasm);
17894
18311
  if (!stitched) {
17895
- throw new Error("Offset solid requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
18312
+ throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
17896
18313
  }
17897
18314
  return stitched;
17898
18315
  }
@@ -17901,7 +18318,7 @@ function lowerTransformedOffsetSolidCompilePlan(plan, thickness, wasm) {
17901
18318
  for (const step of plan.steps) {
17902
18319
  const stepScale = offsetSolidTransformDistanceScaleForManifold(step);
17903
18320
  if (stepScale == null) {
17904
- throw new Error("Offset solid requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
18321
+ throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
17905
18322
  }
17906
18323
  distanceScale *= stepScale;
17907
18324
  }
@@ -17942,7 +18359,7 @@ function lowerOffsetSolidCompilePlan(plan, wasm) {
17942
18359
  }
17943
18360
  const base = verticalPrismOffsetBaseForManifold(basePlan);
17944
18361
  if (!base) {
17945
- throw new Error("Offset solid requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
18362
+ throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
17946
18363
  }
17947
18364
  const zMin = base.zMin - plan.thickness;
17948
18365
  const zMax = base.zMax + plan.thickness;
@@ -18467,6 +18884,18 @@ function matchEdgeSegmentByMidpoint(segments, target) {
18467
18884
  }
18468
18885
  return best;
18469
18886
  }
18887
+ function selectEdgeSegmentsForFeature(segments, plan) {
18888
+ if (plan.edgeQuery) {
18889
+ const selected = applyEdgeQueryFilters(segments, plan.edgeQuery);
18890
+ return plan.edgeSelection === "first" ? selected.slice(0, 1) : selected;
18891
+ }
18892
+ const matched = [];
18893
+ for (const target of plan.edgeTargets ?? []) {
18894
+ const seg = matchEdgeSegmentByMidpoint(segments, target);
18895
+ if (seg && seg.length >= 1e-6) matched.push(seg);
18896
+ }
18897
+ return matched;
18898
+ }
18470
18899
  function lowerFilletEdgesCompilePlan(plan, wasm) {
18471
18900
  let manifold = lowerShapeCompilePlanToManifold(plan.base, wasm);
18472
18901
  const mesh = manifold.getMesh();
@@ -18476,11 +18905,8 @@ function lowerFilletEdgesCompilePlan(plan, wasm) {
18476
18905
  triVerts: mesh.triVerts,
18477
18906
  vertProperties: mesh.vertProperties
18478
18907
  });
18479
- const matched = [];
18480
- for (const target of plan.edgeTargets) {
18481
- const seg = matchEdgeSegmentByMidpoint(segments, target);
18482
- if (seg && seg.length >= 1e-6) matched.push(seg);
18483
- }
18908
+ const matched = selectEdgeSegmentsForFeature(segments, plan);
18909
+ if (plan.edgeQuery && matched.length === 0) throw new Error("filletEdges(): no edges match the deferred edge query.");
18484
18910
  matched.sort((a2, b) => b.length - a2.length);
18485
18911
  for (const seg of matched) {
18486
18912
  try {
@@ -18503,11 +18929,8 @@ function lowerChamferEdgesCompilePlan(plan, wasm) {
18503
18929
  triVerts: mesh.triVerts,
18504
18930
  vertProperties: mesh.vertProperties
18505
18931
  });
18506
- const matched = [];
18507
- for (const target of plan.edgeTargets) {
18508
- const seg = matchEdgeSegmentByMidpoint(segments, target);
18509
- if (seg && seg.length >= 1e-6) matched.push(seg);
18510
- }
18932
+ const matched = selectEdgeSegmentsForFeature(segments, plan);
18933
+ if (plan.edgeQuery && matched.length === 0) throw new Error("chamferEdges(): no edges match the deferred edge query.");
18511
18934
  matched.sort((a2, b) => b.length - a2.length);
18512
18935
  for (const seg of matched) {
18513
18936
  try {
@@ -18609,20 +19032,20 @@ function lowerShapeCompilePlanToManifold(plan, wasm) {
18609
19032
  case "filletEdges":
18610
19033
  return lowerFilletEdgesCompilePlan(plan, wasm);
18611
19034
  case "cornerYBlend":
18612
- throw new Error("Blend.CornerY() requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
19035
+ throw new Error(`Blend.CornerY() requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
18613
19036
  case "chamferEdges":
18614
19037
  return lowerChamferEdgesCompilePlan(plan, wasm);
18615
19038
  case "draft": {
18616
19039
  const lowered = lowerDraftShapeCompilePlanToLoftPlan(plan);
18617
19040
  if (lowered) return lowerShapeCompilePlanToManifold(lowered, wasm);
18618
- throw new Error("Draft angle requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
19041
+ throw new Error(`Draft angle requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
18619
19042
  }
18620
19043
  case "offsetSolid":
18621
19044
  return lowerOffsetSolidCompilePlan(plan, wasm);
18622
19045
  case "trimByPlane":
18623
19046
  return lowerShapeTrimByPlaneCompilePlan(plan, wasm);
18624
19047
  case "importedMesh":
18625
- return lowerImportedMeshToManifold(plan.fileData, plan.format, plan.filePath, wasm);
19048
+ return lowerImportedMeshToManifold(plan.fileData, plan.format, plan.filePath, wasm, plan.object);
18626
19049
  case "sdf": {
18627
19050
  const evaluator = compileSdfMaterializationEvaluator3(plan.tree);
18628
19051
  return lowerSdfToManifold(
@@ -18646,9 +19069,9 @@ function lowerShapeCompilePlanToManifold(plan, wasm) {
18646
19069
  case "surfaceExtend":
18647
19070
  case "surfaceThicken":
18648
19071
  case "surfaceSolid":
18649
- throw new Error("Exact surfacing operations require the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
19072
+ throw new Error(`Exact surfacing operations require the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
18650
19073
  case "importedStep":
18651
- throw new Error(`importStep("${plan.filePath}") requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.`);
19074
+ throw new Error(`importStep("${plan.filePath}") requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
18652
19075
  default:
18653
19076
  assertExhaustive(plan);
18654
19077
  }
@@ -18789,11 +19212,11 @@ function projectVerticesToSurfaceWithNormals(vertProperties, sdfFn, out6) {
18789
19212
  function lowerNurbsSurfaceToManifold(plan, wasm) {
18790
19213
  if (!plan.allowApproximation && plan.thickness === 0) {
18791
19214
  throw new Error(
18792
- "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."
19215
+ `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.`
18793
19216
  );
18794
19217
  }
18795
19218
  if (plan.thickness === 0) {
18796
- throw new Error("The Manifold backend cannot represent open sheet surfaces. Add setActiveBackend('occt') at the top of your script.");
19219
+ throw new Error(`The Manifold backend cannot represent open sheet surfaces. ${OCCT_BACKEND_REQUIRED_HINT}`);
18797
19220
  }
18798
19221
  const surface = new NurbsSurface(plan.controlGrid, {
18799
19222
  degreeU: plan.degreeU,
@@ -18943,8 +19366,8 @@ function lowerTrimmedNurbsSurfaceToManifold(surface, plan, wasm) {
18943
19366
  disposeWasmObject(mesh);
18944
19367
  }
18945
19368
  }
18946
- function lowerImportedMeshToManifold(fileData, format, filePath, wasm) {
18947
- const parsed = parseMeshFile(fileData, format);
19369
+ function lowerImportedMeshToManifold(fileData, format, filePath, wasm, object) {
19370
+ const parsed = parseMeshFile(fileData, format, { object });
18948
19371
  if (parsed.triVerts.length === 0) {
18949
19372
  throw new Error(`importMesh("${filePath}"): file contains no triangles`);
18950
19373
  }
@@ -18966,7 +19389,11 @@ function lowerImportedMeshToManifold(fileData, format, filePath, wasm) {
18966
19389
  }
18967
19390
  }
18968
19391
  function lowerShapeCompilePlanToShapeBackend(plan, wasm) {
18969
- return lowerShapeBackendWithCache("manifold", plan, (basePlan) => wrapManifoldShapeBackend(lowerShapeCompilePlanToManifold(basePlan, wasm)));
19392
+ return lowerShapeBackendWithCache(
19393
+ "manifold",
19394
+ plan,
19395
+ (basePlan) => wrapManifoldShapeBackend(lowerShapeCompilePlanToManifold(basePlan, wasm))
19396
+ );
18970
19397
  }
18971
19398
  const PROFILE_BACKEND_MARKER = Symbol.for("forgecad.profileBackend");
18972
19399
  function occtProfileOffsetJoinType(oc, join) {
@@ -18981,6 +19408,7 @@ function occtProfileOffsetJoinType(oc, join) {
18981
19408
  }
18982
19409
  const BSPLINE_WIRE_THRESHOLD = 20;
18983
19410
  const OFFSET_SOLID_EPS = 1e-8;
19411
+ const MANIFOLD_BACKEND_REQUIRED_HINT = "Select the Manifold backend in the editor or run the CLI with --backend manifold.";
18984
19412
  function transformProfileRadiusPoint(point, step) {
18985
19413
  switch (step.kind) {
18986
19414
  case "translate":
@@ -19686,6 +20114,31 @@ function findOCCTEdgeByMidpoint(oc, shape, midpoint2) {
19686
20114
  }
19687
20115
  return bestEdge;
19688
20116
  }
20117
+ function edgeSegmentToTarget(segment) {
20118
+ return {
20119
+ midpoint: [segment.midpoint[0], segment.midpoint[1], segment.midpoint[2]],
20120
+ start: [segment.start[0], segment.start[1], segment.start[2]],
20121
+ end: [segment.end[0], segment.end[1], segment.end[2]],
20122
+ convex: segment.convex,
20123
+ ...segment.nativeTopology ? { nativeTopology: segment.nativeTopology } : {}
20124
+ };
20125
+ }
20126
+ function selectOCCTEdgeFeatureTargets(base, plan) {
20127
+ if (!plan.edgeQuery) return plan.edgeTargets ?? [];
20128
+ const mesh = wrapOCCTShapeBackend(base).getMesh();
20129
+ const selected = applyEdgeQueryFilters(
20130
+ extractEdgeSegments({
20131
+ numProp: mesh.numProp,
20132
+ numTri: mesh.numTri,
20133
+ triVerts: mesh.triVerts,
20134
+ vertProperties: mesh.vertProperties,
20135
+ mergeFromVert: mesh.mergeFromVert,
20136
+ mergeToVert: mesh.mergeToVert
20137
+ }),
20138
+ plan.edgeQuery
20139
+ );
20140
+ return (plan.edgeSelection === "first" ? selected.slice(0, 1) : selected).map(edgeSegmentToTarget);
20141
+ }
19689
20142
  function occtPointDistance(point, target) {
19690
20143
  return Math.hypot(point.X() - target[0], point.Y() - target[1], point.Z() - target[2]);
19691
20144
  }
@@ -19705,7 +20158,7 @@ function lowerFilletEdgesPlan$1(oc, plan) {
19705
20158
  mkFillet.SetContinuity(mapSurfaceContinuityToOcct(oc, plan.continuity), 2 * Math.PI / 180);
19706
20159
  }
19707
20160
  let addedCount = 0;
19708
- for (const target of plan.edgeTargets) {
20161
+ for (const target of selectOCCTEdgeFeatureTargets(base, plan)) {
19709
20162
  const matchedEdge = findOCCTEdgeByMidpoint(oc, base, target.midpoint);
19710
20163
  if (matchedEdge) {
19711
20164
  mkFillet.Add_2(plan.radius, matchedEdge);
@@ -19752,7 +20205,7 @@ function lowerChamferEdgesPlan$1(oc, plan) {
19752
20205
  const base = lowerShapeCompilePlanToOCCT(plan.base, oc);
19753
20206
  const mkChamfer = new oc.BRepFilletAPI_MakeChamfer(base);
19754
20207
  let addedCount = 0;
19755
- for (const target of plan.edgeTargets) {
20208
+ for (const target of selectOCCTEdgeFeatureTargets(base, plan)) {
19756
20209
  const matchedEdge = findOCCTEdgeByMidpoint(oc, base, target.midpoint);
19757
20210
  if (matchedEdge) {
19758
20211
  mkChamfer.Add_2(plan.size, matchedEdge);
@@ -20039,7 +20492,7 @@ function _lowerShapeCompilePlanToOCCTInner(plan, oc) {
20039
20492
  `importMesh("${plan.filePath}") is not supported with the OCCT backend. Switch to the Manifold backend or use the default backend.`
20040
20493
  );
20041
20494
  case "sdf":
20042
- throw new Error("SDF shapes require the Manifold backend. Add setActiveBackend('manifold') at the top of your script.");
20495
+ throw new Error(`SDF shapes require the Manifold backend. ${MANIFOLD_BACKEND_REQUIRED_HINT}`);
20043
20496
  case "fromSlices":
20044
20497
  return lowerFromSlicesPlan$1(oc, plan);
20045
20498
  case "nurbsSurface":
@@ -20211,6 +20664,7 @@ function buildBSplineCurveHandleFromSweepPathPlan(oc, path, context, allowApprox
20211
20664
  case "catmull-rom":
20212
20665
  case "hermite":
20213
20666
  case "quintic-hermite":
20667
+ case "route3d":
20214
20668
  if (allowApproximation) {
20215
20669
  return buildApproximateBSplineCurveHandle(oc, sweepPathToPolyline(path, 96), context);
20216
20670
  }
@@ -20591,27 +21045,166 @@ function lowerLoftPlan$1(oc, plan) {
20591
21045
  }
20592
21046
  return ts.Shape();
20593
21047
  }
20594
- function buildZToNormalTransform(oc, normal) {
21048
+ function fromSlicesPlaneFrameForOCCT(normalInput) {
21049
+ const normal = normalizeVec3$1(normalInput);
21050
+ if (!normal) throw new Error("Shape.fromSlices group normal must be non-zero");
20595
21051
  const [nx, ny, nz] = normal;
21052
+ if (Math.abs(nz) > 1 - 1e-8) {
21053
+ return { u: [1, 0, 0], v: [0, nz > 0 ? 1 : -1, 0], normal };
21054
+ }
21055
+ if (Math.abs(ny) > 1 - 1e-8) {
21056
+ return { u: [1, 0, 0], v: [0, 0, ny > 0 ? 1 : -1], normal };
21057
+ }
21058
+ if (Math.abs(nx) > 1 - 1e-8) {
21059
+ return { u: [0, 1, 0], v: [0, 0, nx > 0 ? 1 : -1], normal };
21060
+ }
21061
+ const reference = Math.abs(nx) < 0.9 ? [1, 0, 0] : [0, 1, 0];
21062
+ const u2 = normalizeVec3$1(crossVec3$1(reference, normal));
21063
+ if (!u2) throw new Error("Shape.fromSlices profile u axis is invalid");
21064
+ return { u: u2, v: crossVec3$1(normal, u2), normal };
21065
+ }
21066
+ function buildFromSlicesLocalToWorldTransform(oc, normal) {
21067
+ const frame = fromSlicesPlaneFrameForOCCT(normal);
20596
21068
  const trsf = new oc.gp_Trsf_1();
20597
- if (Math.abs(nx) < 1e-10 && Math.abs(ny) < 1e-10) {
20598
- if (nz < 0) {
20599
- 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);
20600
- }
20601
- return trsf;
20602
- }
20603
- const ax = -ny;
20604
- const ay = nx;
20605
- const az = 0;
20606
- const len = Math.sqrt(ax * ax + ay * ay + az * az);
20607
- const angle = Math.acos(Math.max(-1, Math.min(1, nz)));
20608
- 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);
21069
+ trsf.SetValues(
21070
+ frame.u[0],
21071
+ frame.v[0],
21072
+ frame.normal[0],
21073
+ 0,
21074
+ frame.u[1],
21075
+ frame.v[1],
21076
+ frame.normal[1],
21077
+ 0,
21078
+ frame.u[2],
21079
+ frame.v[2],
21080
+ frame.normal[2],
21081
+ 0
21082
+ );
20609
21083
  return trsf;
20610
21084
  }
21085
+ function axisAlignedDirectionForOCCT(vector) {
21086
+ let bestAxis = 0;
21087
+ let best = Math.abs(vector[0]);
21088
+ for (const axis of [1, 2]) {
21089
+ const value = Math.abs(vector[axis]);
21090
+ if (value > best) {
21091
+ best = value;
21092
+ bestAxis = axis;
21093
+ }
21094
+ }
21095
+ if (best < 1 - 1e-8) return null;
21096
+ return { axis: bestAxis, sign: vector[bestAxis] < 0 ? -1 : 1 };
21097
+ }
21098
+ function centeredEllipseProfileRadiiForOCCT(plan) {
21099
+ if (plan.kind !== "circle") return null;
21100
+ let rx = Math.abs(plan.radius);
21101
+ let ry = Math.abs(plan.radius);
21102
+ for (const transform of plan.transforms) {
21103
+ if (transform.kind !== "scale") return null;
21104
+ rx *= Math.abs(transform.x);
21105
+ ry *= Math.abs(transform.y);
21106
+ }
21107
+ return rx > 1e-10 && ry > 1e-10 ? [rx, ry] : null;
21108
+ }
21109
+ function setEllipsoidRadiusForOCCT(radii, axis, radius) {
21110
+ const existing = radii[axis];
21111
+ if (existing == null) {
21112
+ radii[axis] = radius;
21113
+ return true;
21114
+ }
21115
+ const tolerance = Math.max(1e-7, Math.max(Math.abs(existing), Math.abs(radius)) * 1e-7);
21116
+ return Math.abs(existing - radius) <= tolerance;
21117
+ }
21118
+ function buildRationalQuadraticEdgeForOCCT(oc, points, weights) {
21119
+ const poles = new oc.TColgp_Array1OfPnt_2(1, 3);
21120
+ const weightArr = new oc.TColStd_Array1OfReal_2(1, 3);
21121
+ for (let i = 0; i < 3; i++) {
21122
+ const [x2, y2, z2] = points[i];
21123
+ poles.SetValue(i + 1, new oc.gp_Pnt_3(x2, y2, z2));
21124
+ weightArr.SetValue(i + 1, weights[i]);
21125
+ }
21126
+ const knots = new oc.TColStd_Array1OfReal_2(1, 2);
21127
+ knots.SetValue(1, 0);
21128
+ knots.SetValue(2, 1);
21129
+ const mults = new oc.TColStd_Array1OfInteger_2(1, 2);
21130
+ mults.SetValue(1, 3);
21131
+ mults.SetValue(2, 3);
21132
+ const curve = new oc.Geom_BSplineCurve_2(poles, weightArr, knots, mults, 2, false, true);
21133
+ const handle = new oc.Handle_Geom_BSplineCurve_2(curve);
21134
+ return new oc.BRepBuilderAPI_MakeEdge_24(new oc.Handle_Geom_Curve_2(handle.get())).Edge();
21135
+ }
21136
+ function lowerAxisymmetricEllipsoidForOCCT(oc, radius, heightRadius) {
21137
+ const w2 = Math.SQRT1_2;
21138
+ const mkWire = new oc.BRepBuilderAPI_MakeWire_1();
21139
+ mkWire.Add_1(
21140
+ buildRationalQuadraticEdgeForOCCT(
21141
+ oc,
21142
+ [
21143
+ [0, 0, heightRadius],
21144
+ [radius, 0, heightRadius],
21145
+ [radius, 0, 0]
21146
+ ],
21147
+ [1, w2, 1]
21148
+ )
21149
+ );
21150
+ mkWire.Add_1(
21151
+ buildRationalQuadraticEdgeForOCCT(
21152
+ oc,
21153
+ [
21154
+ [radius, 0, 0],
21155
+ [radius, 0, -heightRadius],
21156
+ [0, 0, -heightRadius]
21157
+ ],
21158
+ [1, w2, 1]
21159
+ )
21160
+ );
21161
+ 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());
21162
+ const face = buildFaceFromWire(oc, mkWire.Wire());
21163
+ const axis = new oc.gp_Ax1_2(new oc.gp_Pnt_3(0, 0, 0), new oc.gp_Dir_4(0, 0, 1));
21164
+ const revol = new oc.BRepPrimAPI_MakeRevol_1(face, axis, Math.PI * 2, true);
21165
+ revol.Build(new oc.Message_ProgressRange_1());
21166
+ if (!revol.IsDone()) {
21167
+ throw new Error("Shape.fromSlices exact ellipsoid revolve failed");
21168
+ }
21169
+ return revol.Shape();
21170
+ }
21171
+ function lowerOrthogonalEllipseFromSlicesPlan(oc, plan) {
21172
+ if (plan.groups.length !== 2) return null;
21173
+ const center = [0, 0, 0];
21174
+ const radii = [null, null, null];
21175
+ for (const group of plan.groups) {
21176
+ if (group.slices.length !== 1) return null;
21177
+ const slice = group.slices[0];
21178
+ const profileRadii = centeredEllipseProfileRadiiForOCCT(slice.profile);
21179
+ if (!profileRadii) return null;
21180
+ const frame = fromSlicesPlaneFrameForOCCT(group.normal);
21181
+ const normalAxis = axisAlignedDirectionForOCCT(frame.normal);
21182
+ const uAxis = axisAlignedDirectionForOCCT(frame.u);
21183
+ const vAxis = axisAlignedDirectionForOCCT(frame.v);
21184
+ if (!normalAxis || !uAxis || !vAxis) return null;
21185
+ center[normalAxis.axis] = normalAxis.sign * slice.offset;
21186
+ if (!setEllipsoidRadiusForOCCT(radii, uAxis.axis, profileRadii[0])) return null;
21187
+ if (!setEllipsoidRadiusForOCCT(radii, vAxis.axis, profileRadii[1])) return null;
21188
+ }
21189
+ if (radii.some((radius) => radius == null)) return null;
21190
+ const rx = radii[0];
21191
+ const ry = radii[1];
21192
+ const rz = radii[2];
21193
+ const axisymmetricTolerance = Math.max(1e-7, Math.max(Math.abs(rx), Math.abs(ry)) * 1e-7);
21194
+ if (Math.abs(rx - ry) > axisymmetricTolerance) return null;
21195
+ let shape = lowerAxisymmetricEllipsoidForOCCT(oc, (rx + ry) / 2, rz);
21196
+ if (Math.hypot(center[0], center[1], center[2]) > 1e-10) {
21197
+ const trsf = new oc.gp_Trsf_1();
21198
+ trsf.SetTranslation_1(new oc.gp_Vec_4(center[0], center[1], center[2]));
21199
+ const moved = new oc.BRepBuilderAPI_Transform_2(shape, trsf, true);
21200
+ shape = moved.Shape();
21201
+ }
21202
+ return shape;
21203
+ }
20611
21204
  function lowerFromSlicesGroup(oc, group, singleSliceHalfExtent) {
20612
21205
  const { normal, slices } = group;
20613
21206
  const sorted = [...slices].sort((a2, b) => a2.offset - b.offset);
20614
- const rotTrsf = buildZToNormalTransform(oc, normal);
21207
+ const localToWorldTrsf = buildFromSlicesLocalToWorldTransform(oc, normal);
20615
21208
  if (sorted.length === 1) {
20616
21209
  const s = sorted[0];
20617
21210
  const rawFace = lowerProfileToFace(oc, s.profile);
@@ -20625,7 +21218,7 @@ function lowerFromSlicesGroup(oc, group, singleSliceHalfExtent) {
20625
21218
  centerTrsf.SetTranslation_1(new oc.gp_Vec_4(0, 0, s.offset - extrudeHalf));
20626
21219
  const centered = new oc.BRepBuilderAPI_Transform_2(shape, centerTrsf, true);
20627
21220
  shape = centered.Shape();
20628
- const rotated = new oc.BRepBuilderAPI_Transform_2(shape, rotTrsf, true);
21221
+ const rotated = new oc.BRepBuilderAPI_Transform_2(shape, localToWorldTrsf, true);
20629
21222
  return rotated.Shape();
20630
21223
  }
20631
21224
  const ts = new oc.BRepOffsetAPI_ThruSections(true, false, 1e-6);
@@ -20636,10 +21229,8 @@ function lowerFromSlicesGroup(oc, group, singleSliceHalfExtent) {
20636
21229
  wire = toBSplineWireIfNeeded(oc, wire);
20637
21230
  const placeTrsf = new oc.gp_Trsf_1();
20638
21231
  placeTrsf.SetTranslation_1(new oc.gp_Vec_4(0, 0, s.offset));
20639
- const compound = new oc.gp_Trsf_1();
20640
- compound.Multiply(rotTrsf);
20641
- compound.Multiply(placeTrsf);
20642
- const transformed = new oc.BRepBuilderAPI_Transform_2(wire, compound, true);
21232
+ const placed = new oc.BRepBuilderAPI_Transform_2(wire, placeTrsf, true);
21233
+ const transformed = new oc.BRepBuilderAPI_Transform_2(placed.Shape(), localToWorldTrsf, true);
20643
21234
  ts.AddWire(oc.TopoDS.Wire_1(transformed.Shape()));
20644
21235
  }
20645
21236
  ts.CheckCompatibility(true);
@@ -20651,6 +21242,8 @@ function lowerFromSlicesGroup(oc, group, singleSliceHalfExtent) {
20651
21242
  }
20652
21243
  function lowerFromSlicesPlan$1(oc, plan) {
20653
21244
  if (plan.groups.length === 0) throw new Error("Shape.fromSlices requires at least one slice");
21245
+ const exactEllipsoid = lowerOrthogonalEllipseFromSlicesPlan(oc, plan);
21246
+ if (exactEllipsoid) return exactEllipsoid;
20654
21247
  const singleSliceHalfExtent = fromSlicesSingleSliceHalfExtentForOCCT(plan);
20655
21248
  const groupSolids = plan.groups.map((g2) => lowerFromSlicesGroup(oc, g2, singleSliceHalfExtent));
20656
21249
  if (groupSolids.length === 1) return groupSolids[0];
@@ -20677,14 +21270,40 @@ function buildPolylineSpineWire(oc, points) {
20677
21270
  }
20678
21271
  return mkWire.Wire();
20679
21272
  }
21273
+ function buildRoute3DSpineWire(oc, path) {
21274
+ if (path.segments.length === 0) throw new Error("Route3D sweep path needs at least one segment");
21275
+ const mkWire = new oc.BRepBuilderAPI_MakeWire_1();
21276
+ for (const segment of path.segments) {
21277
+ if (segment.kind === "line") {
21278
+ const [x1, y1, z1] = segment.from;
21279
+ const [x2, y2, z2] = segment.to;
21280
+ if (x1 === x2 && y1 === y2 && z1 === z2) continue;
21281
+ 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());
21282
+ continue;
21283
+ }
21284
+ const radial = normalizeVec3$1(subtractVec3(segment.start, segment.center));
21285
+ if (!radial) throw new Error("Route3D sweep arc has a collapsed start radius");
21286
+ const axis = new oc.gp_Ax2_2(
21287
+ new oc.gp_Pnt_3(segment.center[0], segment.center[1], segment.center[2]),
21288
+ new oc.gp_Dir_4(segment.axis[0], segment.axis[1], segment.axis[2]),
21289
+ new oc.gp_Dir_4(radial[0], radial[1], radial[2])
21290
+ );
21291
+ const circle = new oc.gp_Circ_2(axis, segment.radius);
21292
+ mkWire.Add_1(new oc.BRepBuilderAPI_MakeEdge_9(circle, 0, segment.sweepDeg * Math.PI / 180).Edge());
21293
+ }
21294
+ return mkWire.Wire();
21295
+ }
20680
21296
  function buildSpineWireFromPlan(oc, path, pathSamples) {
20681
21297
  switch (path.kind) {
20682
21298
  case "polyline":
20683
21299
  return buildPolylineSpineWire(oc, path.points);
21300
+ case "route3d":
21301
+ return buildRoute3DSpineWire(oc, path);
21302
+ case "nurbs":
21303
+ return new oc.BRepBuilderAPI_MakeWire_2(buildCurveEdgeFromSweepPathPlan(oc, path, "OCCT sweep path")).Wire();
20684
21304
  case "catmull-rom":
20685
21305
  case "hermite":
20686
21306
  case "quintic-hermite":
20687
- case "nurbs":
20688
21307
  return buildPolylineSpineWire(oc, sweepPathToPolyline(path, pathSamples ?? 48));
20689
21308
  }
20690
21309
  }
@@ -28541,7 +29160,7 @@ async function initTruckGeometryWasm() {
28541
29160
  if (_initPromise) return _initPromise;
28542
29161
  _initPromise = (async () => {
28543
29162
  try {
28544
- const geometryModule = await import("./forgecad_geometry-CH2nvuLA.js");
29163
+ const geometryModule = await import("./forgecad_geometry-Dgceylq9.js");
28545
29164
  const isNode = isNodeRuntime();
28546
29165
  if (isNode) {
28547
29166
  const { readFileSync, existsSync } = await import("./__vite-browser-external-Dhvy_jtL.js");
@@ -31348,7 +31967,7 @@ function nurbsSurfaceForPlan(plan) {
31348
31967
  return null;
31349
31968
  }
31350
31969
  function lowerImportedMeshPlan(plan) {
31351
- const parsed = parseMeshFile(plan.fileData, plan.format);
31970
+ const parsed = parseMeshFile(plan.fileData, plan.format, { object: plan.object });
31352
31971
  if (parsed.numProp !== 3) {
31353
31972
  throw new Error(`importMesh("${plan.filePath}"): expected xyz vertex data`);
31354
31973
  }
@@ -31651,12 +32270,18 @@ function applyNativeTruckFilletTargets(initialShape, targets, radius, segments,
31651
32270
  }
31652
32271
  }
31653
32272
  function lowerFilletEdgesPlan(plan) {
32273
+ if (plan.edgeQuery) {
32274
+ throw new Error("filletEdges(): deferred edge queries are not supported by the Truck lowerer yet.");
32275
+ }
31654
32276
  const shape = lowerShapeCompilePlanToTruckBackend(plan.base);
31655
- return applyNativeTruckFilletTargets(shape, plan.edgeTargets, plan.radius, plan.segments, "filletEdges()");
32277
+ return applyNativeTruckFilletTargets(shape, plan.edgeTargets ?? [], plan.radius, plan.segments, "filletEdges()");
31656
32278
  }
31657
32279
  function lowerChamferEdgesPlan(plan) {
32280
+ if (plan.edgeQuery) {
32281
+ throw new Error("chamferEdges(): deferred edge queries are not supported by the Truck lowerer yet.");
32282
+ }
31658
32283
  const shape = lowerShapeCompilePlanToTruckBackend(plan.base);
31659
- return applyNativeTruckChamferTargets(shape, plan.edgeTargets, plan.size, "chamferEdges()");
32284
+ return applyNativeTruckChamferTargets(shape, plan.edgeTargets ?? [], plan.size, "chamferEdges()");
31660
32285
  }
31661
32286
  function resolvedEdgeFeatureSelectionToTarget(selection) {
31662
32287
  return {
@@ -37673,8 +38298,8 @@ function resolveShapeFace(plan, name) {
37673
38298
  const DEPRECATED_SIDE_NAMES = {
37674
38299
  "side-left": "left",
37675
38300
  "side-right": "right",
37676
- "side-top": "front",
37677
- "side-bottom": "back"
38301
+ "side-top": "back",
38302
+ "side-bottom": "front"
37678
38303
  };
37679
38304
  function explainMissingShapeFace(plan, name) {
37680
38305
  const table = resolveShapeFaceTable(plan);
@@ -38599,11 +39224,14 @@ class ShapeGroup {
38599
39224
  }
38600
39225
  /** Move the entire group by (x, y, z). All children move together as a unit. */
38601
39226
  translate(x2, y2, z2) {
38602
- const matrix = Transform.translation(x2, y2, z2).toArray();
39227
+ const dx = requireFiniteNumber(x2, "ShapeGroup.translate() x");
39228
+ const dy = requireFiniteNumber(y2, "ShapeGroup.translate() y");
39229
+ const dz = requireFiniteNumber(z2, "ShapeGroup.translate() z");
39230
+ const matrix = Transform.translation(dx, dy, dz).toArray();
38603
39231
  return this.mapChildrenTransform((c2) => {
38604
- if (c2 instanceof ShapeGroup) return c2.translate(x2, y2, z2);
38605
- if (c2 instanceof Shape) return c2.translate(x2, y2, z2);
38606
- return c2.translate(x2, y2);
39232
+ if (c2 instanceof ShapeGroup) return c2.translate(dx, dy, dz);
39233
+ if (c2 instanceof Shape) return c2.translate(dx, dy, dz);
39234
+ return c2.translate(dx, dy);
38607
39235
  }, matrix);
38608
39236
  }
38609
39237
  /** Compute combined bounding box of all 3D children */
@@ -38639,17 +39267,23 @@ class ShapeGroup {
38639
39267
  return { min: bb.min, max: bb.max };
38640
39268
  }
38641
39269
  resolveRotatePoint(point) {
38642
- if (Array.isArray(point)) return [point[0], point[1], point[2]];
39270
+ if (Array.isArray(point)) return requireFiniteVec3$1(point, "ShapeGroup.rotateAroundTo() point");
38643
39271
  const bb = this._bbox();
38644
39272
  return resolveAnchor3D(bb.min, bb.max, point);
38645
39273
  }
38646
39274
  /** Move the group so its bounding-box min corner lands at the given coordinate. */
38647
39275
  moveTo(x2, y2, z2) {
39276
+ const targetX = requireFiniteNumber(x2, "ShapeGroup.moveTo() x");
39277
+ const targetY = requireFiniteNumber(y2, "ShapeGroup.moveTo() y");
39278
+ const targetZ = requireFiniteNumber(z2, "ShapeGroup.moveTo() z");
38648
39279
  const bb = this._bbox();
38649
- return this.translate(x2 - bb.min[0], y2 - bb.min[1], z2 - bb.min[2]);
39280
+ return this.translate(targetX - bb.min[0], targetY - bb.min[1], targetZ - bb.min[2]);
38650
39281
  }
38651
39282
  /** Move the group relative to another part's bounding-box min corner. */
38652
39283
  moveToLocal(target, x2, y2, z2) {
39284
+ const localX = requireFiniteNumber(x2, "ShapeGroup.moveToLocal() x");
39285
+ const localY = requireFiniteNumber(y2, "ShapeGroup.moveToLocal() y");
39286
+ const localZ = requireFiniteNumber(z2, "ShapeGroup.moveToLocal() z");
38653
39287
  let tbb;
38654
39288
  if (target instanceof ShapeGroup) {
38655
39289
  tbb = target._bbox();
@@ -38657,7 +39291,7 @@ class ShapeGroup {
38657
39291
  const bb = target.boundingBox();
38658
39292
  tbb = { min: bb.min };
38659
39293
  }
38660
- return this.moveTo(tbb.min[0] + x2, tbb.min[1] + y2, tbb.min[2] + z2);
39294
+ return this.moveTo(tbb.min[0] + localX, tbb.min[1] + localY, tbb.min[2] + localZ);
38661
39295
  }
38662
39296
  /**
38663
39297
  * Attach this group to a face or anchor on another part.
@@ -38686,9 +39320,10 @@ class ShapeGroup {
38686
39320
  const sp = resolveAnchor3D(sbb.min, sbb.max, selfAnchor);
38687
39321
  let dx = tp[0] - sp[0], dy = tp[1] - sp[1], dz = tp[2] - sp[2];
38688
39322
  if (offset) {
38689
- dx += offset[0];
38690
- dy += offset[1];
38691
- dz += offset[2];
39323
+ const offsetPoint = requireFiniteVec3$1(offset, "ShapeGroup.attachTo() offset");
39324
+ dx += offsetPoint[0];
39325
+ dy += offsetPoint[1];
39326
+ dz += offsetPoint[2];
38692
39327
  }
38693
39328
  return this.translate(dx, dy, dz);
38694
39329
  }
@@ -38697,7 +39332,9 @@ class ShapeGroup {
38697
39332
  * See Shape.onFace() for full documentation.
38698
39333
  */
38699
39334
  onFace(parent, face, opts = {}) {
38700
- const u2 = opts.u ?? 0, v = opts.v ?? 0, p2 = opts.protrude ?? 0;
39335
+ const u2 = requireFiniteNumber(opts.u ?? 0, "ShapeGroup.onFace() u");
39336
+ const v = requireFiniteNumber(opts.v ?? 0, "ShapeGroup.onFace() v");
39337
+ const p2 = requireFiniteNumber(opts.protrude ?? 0, "ShapeGroup.onFace() protrude");
38701
39338
  const opp = { front: "back", back: "front", left: "right", right: "left", top: "bottom", bottom: "top" };
38702
39339
  const uvMap = {
38703
39340
  front: (u22, v2, p22) => [u22, -p22, v2],
@@ -38736,20 +39373,31 @@ class ShapeGroup {
38736
39373
  }
38737
39374
  /** Rotate around an arbitrary axis, optionally through a pivot point. */
38738
39375
  rotateAroundAxis(axis, angleDeg, pivot = [0, 0, 0]) {
38739
- return this.transform(Transform.rotationAxis(axis, angleDeg, pivot));
39376
+ const rotateAxis = requireNonZeroFiniteVec3(axis, "ShapeGroup.rotateAroundAxis() axis");
39377
+ const degrees = requireFiniteNumber(angleDeg, "ShapeGroup.rotateAroundAxis() angleDeg");
39378
+ const rotatePivot = requireFiniteVec3$1(pivot, "ShapeGroup.rotateAroundAxis() pivot");
39379
+ return this.transform(Transform.rotationAxis(rotateAxis, degrees, rotatePivot));
38740
39380
  }
38741
39381
  /**
38742
39382
  * Rotate around an axis until a moving point reaches the target line/plane defined by the axis and target point.
38743
39383
  * ShapeGroup string points use built-in anchors only.
38744
39384
  */
38745
39385
  rotateAroundTo(axis, pivot, movingPoint, targetPoint, options = {}) {
39386
+ const rotateAxis = requireNonZeroFiniteVec3(axis, "ShapeGroup.rotateAroundTo() axis");
39387
+ const rotatePivot = requireFiniteVec3$1(pivot, "ShapeGroup.rotateAroundTo() pivot");
38746
39388
  return this.transform(
38747
- Transform.rotateAroundTo(axis, pivot, this.resolveRotatePoint(movingPoint), this.resolveRotatePoint(targetPoint), options)
39389
+ Transform.rotateAroundTo(
39390
+ rotateAxis,
39391
+ rotatePivot,
39392
+ this.resolveRotatePoint(movingPoint),
39393
+ this.resolveRotatePoint(targetPoint),
39394
+ options
39395
+ )
38748
39396
  );
38749
39397
  }
38750
39398
  /** Reorient the group so its local Z axis points along `direction`. */
38751
39399
  pointAlong(direction) {
38752
- const [dx, dy, dz] = direction;
39400
+ const [dx, dy, dz] = requireNonZeroFiniteVec3(direction, "ShapeGroup.pointAlong() direction");
38753
39401
  const len = Math.sqrt(dx * dx + dy * dy + dz * dz) || 1;
38754
39402
  const nx = dx / len, ny = dy / len, nz = dz / len;
38755
39403
  const cx = -ny, cy = nx, cz = 0;
@@ -38764,11 +39412,11 @@ class ShapeGroup {
38764
39412
  }
38765
39413
  /** Apply a 4x4 transform matrix or `Transform` to all 3D children. */
38766
39414
  transform(m2) {
38767
- const matrix = m2 instanceof Transform ? m2.toArray() : m2;
39415
+ const matrix = m2 instanceof Transform ? m2.toArray() : requireFiniteMat4(m2, "ShapeGroup.transform() matrix");
38768
39416
  const next = new ShapeGroup(
38769
39417
  this.children.map((c2) => {
38770
- if (c2 instanceof ShapeGroup) return c2.transform(m2);
38771
- if (c2 instanceof Shape) return c2.transform(m2);
39418
+ if (c2 instanceof ShapeGroup) return c2.transform(matrix);
39419
+ if (c2 instanceof Shape) return c2.transform(matrix);
38772
39420
  throw new Error(
38773
39421
  "ShapeGroup.transform only supports 3D children (Shape/ShapeGroup). For Sketch children, use 2D transforms (translate/rotate/scale/mirror)."
38774
39422
  );
@@ -38781,25 +39429,28 @@ class ShapeGroup {
38781
39429
  }
38782
39430
  /** Scale uniformly or per-axis from the group's bounding-box center. */
38783
39431
  scale(v) {
38784
- return this.scaleAround(this._bboxCenter(), v);
39432
+ const scale2 = requireNonZeroFiniteScale3(v, "ShapeGroup.scale() scale");
39433
+ return this.scaleAround(this._bboxCenter(), scale2);
38785
39434
  }
38786
39435
  /** Scale uniformly or per-axis from an explicit pivot point. */
38787
39436
  scaleAround(pivot, v) {
38788
- const matrix = Transform.scale(v).toArray();
38789
- if (pivot[0] === 0 && pivot[1] === 0 && pivot[2] === 0) {
39437
+ const scale2 = requireNonZeroFiniteScale3(v, "ShapeGroup.scaleAround() scale");
39438
+ const scalePivot = requireFiniteVec3$1(pivot, "ShapeGroup.scaleAround() pivot");
39439
+ const matrix = Transform.scale(scale2).toArray();
39440
+ if (scalePivot[0] === 0 && scalePivot[1] === 0 && scalePivot[2] === 0) {
38790
39441
  return this.mapChildrenTransform((c2) => {
38791
- if (c2 instanceof ShapeGroup) return c2.scaleAround([0, 0, 0], v);
38792
- if (c2 instanceof Shape) return c2.scaleAround([0, 0, 0], v);
38793
- return c2.scale(typeof v === "number" ? v : [v[0], v[1]]);
39442
+ if (c2 instanceof ShapeGroup) return c2.scaleAround([0, 0, 0], scale2);
39443
+ if (c2 instanceof Shape) return c2.scaleAround([0, 0, 0], scale2);
39444
+ return c2.scale([scale2[0], scale2[1]]);
38794
39445
  }, matrix);
38795
39446
  }
38796
- const moved = this.translate(-pivot[0], -pivot[1], -pivot[2]);
39447
+ const moved = this.translate(-scalePivot[0], -scalePivot[1], -scalePivot[2]);
38797
39448
  const scaled = moved.mapChildrenTransform((c2) => {
38798
- if (c2 instanceof ShapeGroup) return c2.scaleAround([0, 0, 0], v);
38799
- if (c2 instanceof Shape) return c2.scaleAround([0, 0, 0], v);
38800
- return c2.scale(typeof v === "number" ? v : [v[0], v[1]]);
39449
+ if (c2 instanceof ShapeGroup) return c2.scaleAround([0, 0, 0], scale2);
39450
+ if (c2 instanceof Shape) return c2.scaleAround([0, 0, 0], scale2);
39451
+ return c2.scale([scale2[0], scale2[1]]);
38801
39452
  }, matrix);
38802
- return scaled.translate(pivot[0], pivot[1], pivot[2]);
39453
+ return scaled.translate(scalePivot[0], scalePivot[1], scalePivot[2]);
38803
39454
  }
38804
39455
  /** Mirror across a plane through the group's bounding-box center. */
38805
39456
  mirror(normal) {
@@ -38807,21 +39458,23 @@ class ShapeGroup {
38807
39458
  }
38808
39459
  /** Mirror across a plane through an explicit point. */
38809
39460
  mirrorThrough(point, normal) {
38810
- const matrix = mirrorPlaneMatrix(normal);
38811
- if (point[0] === 0 && point[1] === 0 && point[2] === 0) {
39461
+ const mirrorPoint = requireFiniteVec3$1(point, "ShapeGroup.mirrorThrough() point");
39462
+ const mirrorNormal = requireNonZeroFiniteVec3(normal, "ShapeGroup.mirrorThrough() normal");
39463
+ const matrix = mirrorPlaneMatrix(mirrorNormal);
39464
+ if (mirrorPoint[0] === 0 && mirrorPoint[1] === 0 && mirrorPoint[2] === 0) {
38812
39465
  return this.mapChildrenTransform((c2) => {
38813
- if (c2 instanceof ShapeGroup) return c2.mirrorThrough([0, 0, 0], normal);
38814
- if (c2 instanceof Shape) return c2.mirrorThrough([0, 0, 0], normal);
38815
- return c2.mirror([normal[0], normal[1]]);
39466
+ if (c2 instanceof ShapeGroup) return c2.mirrorThrough([0, 0, 0], mirrorNormal);
39467
+ if (c2 instanceof Shape) return c2.mirrorThrough([0, 0, 0], mirrorNormal);
39468
+ return c2.mirror([mirrorNormal[0], mirrorNormal[1]]);
38816
39469
  }, matrix);
38817
39470
  }
38818
- const moved = this.translate(-point[0], -point[1], -point[2]);
39471
+ const moved = this.translate(-mirrorPoint[0], -mirrorPoint[1], -mirrorPoint[2]);
38819
39472
  const mirrored = moved.mapChildrenTransform((c2) => {
38820
- if (c2 instanceof ShapeGroup) return c2.mirrorThrough([0, 0, 0], normal);
38821
- if (c2 instanceof Shape) return c2.mirrorThrough([0, 0, 0], normal);
38822
- return c2.mirror([normal[0], normal[1]]);
39473
+ if (c2 instanceof ShapeGroup) return c2.mirrorThrough([0, 0, 0], mirrorNormal);
39474
+ if (c2 instanceof Shape) return c2.mirrorThrough([0, 0, 0], mirrorNormal);
39475
+ return c2.mirror([mirrorNormal[0], mirrorNormal[1]]);
38823
39476
  }, matrix);
38824
- return mirrored.translate(point[0], point[1], point[2]);
39477
+ return mirrored.translate(mirrorPoint[0], mirrorPoint[1], mirrorPoint[2]);
38825
39478
  }
38826
39479
  /** Return a copy of the group with the given display color applied to each child. */
38827
39480
  color(hex) {
@@ -38893,14 +39546,16 @@ class ShapeGroup {
38893
39546
  * ```
38894
39547
  */
38895
39548
  placeReference(ref, target, offset) {
39549
+ const targetPoint = requireFiniteVec3$1(target, "ShapeGroup.placeReference() target");
39550
+ const offsetPoint = offset === void 0 ? void 0 : requireFiniteVec3$1(offset, "ShapeGroup.placeReference() offset");
38896
39551
  const sourcePoint = this.referencePoint(ref);
38897
- let dx = target[0] - sourcePoint[0];
38898
- let dy = target[1] - sourcePoint[1];
38899
- let dz = target[2] - sourcePoint[2];
38900
- if (offset) {
38901
- dx += offset[0];
38902
- dy += offset[1];
38903
- dz += offset[2];
39552
+ let dx = targetPoint[0] - sourcePoint[0];
39553
+ let dy = targetPoint[1] - sourcePoint[1];
39554
+ let dz = targetPoint[2] - sourcePoint[2];
39555
+ if (offsetPoint) {
39556
+ dx += offsetPoint[0];
39557
+ dy += offsetPoint[1];
39558
+ dz += offsetPoint[2];
38904
39559
  }
38905
39560
  return this.translate(dx, dy, dz);
38906
39561
  }
@@ -39832,14 +40487,21 @@ function attachTopologyRewritePropagation(plan, propagation) {
39832
40487
  }
39833
40488
  return cloneNodeWithPropagation(plan, propagation);
39834
40489
  }
40490
+ const DEFERRED_EDGE_SELECTION_MARKER = Symbol.for("forgecad.deferredEdgeSelection");
40491
+ function isDeferredEdgeSelection(value) {
40492
+ return Boolean(value && typeof value === "object" && value[DEFERRED_EDGE_SELECTION_MARKER] === true);
40493
+ }
39835
40494
  function distSq(a2, b) {
39836
- const dx = a2[0] - b[0], dy = a2[1] - b[1], dz = a2[2] - b[2];
40495
+ const dx = a2[0] - b[0];
40496
+ const dy = a2[1] - b[1];
40497
+ const dz = a2[2] - b[2];
39837
40498
  return dx * dx + dy * dy + dz * dz;
39838
40499
  }
39839
40500
  function absDot(a2, b) {
39840
40501
  return Math.abs(a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2]);
39841
40502
  }
39842
40503
  function coalesceEdges(segments, tolerance = 0.01) {
40504
+ if (segments.some(isDeferredEdgeSelection)) return segments;
39843
40505
  if (segments.length <= 1) return segments;
39844
40506
  const used = new Uint8Array(segments.length);
39845
40507
  const result = [];
@@ -40562,16 +41224,6 @@ function mirrorMatrix(normal) {
40562
41224
  const m22 = 1 - 2 * nz * nz;
40563
41225
  return [m00, m10, m20, 0, m01, m11, m21, 0, m02, m12, m22, 0, 0, 0, 0, 1];
40564
41226
  }
40565
- function normalizeShapeScale(v) {
40566
- const scale2 = typeof v === "number" ? [v, v, v] : v;
40567
- if (!Number.isFinite(scale2[0]) || !Number.isFinite(scale2[1]) || !Number.isFinite(scale2[2])) {
40568
- return null;
40569
- }
40570
- if (Math.abs(scale2[0]) < 1e-12 || Math.abs(scale2[1]) < 1e-12 || Math.abs(scale2[2]) < 1e-12) {
40571
- return null;
40572
- }
40573
- return [scale2[0], scale2[1], scale2[2]];
40574
- }
40575
41227
  function dotVec3$1(a2, b) {
40576
41228
  return a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2];
40577
41229
  }
@@ -41660,7 +42312,7 @@ function withBaseDimensionsAndMergedSourceSpans(base, sources, out, preserveOutp
41660
42312
  return result;
41661
42313
  }
41662
42314
  function resolveRotationPoint(shape, point) {
41663
- if (Array.isArray(point)) return [point[0], point[1], point[2]];
42315
+ if (Array.isArray(point)) return requireFiniteVec3$1(point, "rotateAroundTo(): point");
41664
42316
  return shape.referencePoint(point);
41665
42317
  }
41666
42318
  function setShapePlacementReferences(shape, refs, options = {}) {
@@ -42388,7 +43040,9 @@ class Shape {
42388
43040
  edgesOf(faceLabel, options) {
42389
43041
  const faceRefs = this._resolveFaceLabels(faceLabel);
42390
43042
  const result = uniqueEdgeSegments(
42391
- faceRefs.flatMap((faceRef) => edgesOfFace(this, faceRef, options, (name) => this._resolveFaceLabel(name)))
43043
+ faceRefs.flatMap((faceRef) => {
43044
+ return edgesOfFace(this, faceRef, options, (name) => this._resolveFaceLabel(name));
43045
+ })
42392
43046
  );
42393
43047
  if (result.length === 0) {
42394
43048
  throw new Error(
@@ -42436,7 +43090,11 @@ class Shape {
42436
43090
  const refsA = this._resolveFaceLabels(faceA);
42437
43091
  const bNames = Array.isArray(faceB) ? faceB : [faceB];
42438
43092
  const refBs = bNames.flatMap((name) => this._resolveFaceLabels(name));
42439
- const result = uniqueEdgeSegments(refsA.flatMap((refA) => edgesBetweenFaces(this, refA, refBs)));
43093
+ const result = uniqueEdgeSegments(
43094
+ refsA.flatMap((refA) => {
43095
+ return edgesBetweenFaces(this, refA, refBs);
43096
+ })
43097
+ );
42440
43098
  if (result.length === 0) {
42441
43099
  const bStr = bNames.length === 1 ? `'${bNames[0]}'` : `[${bNames.map((n) => `'${n}'`).join(", ")}]`;
42442
43100
  throw new Error(
@@ -42500,14 +43158,16 @@ class Shape {
42500
43158
  * ```
42501
43159
  */
42502
43160
  placeReference(ref, target, offset) {
43161
+ const targetPoint = requireFiniteVec3$1(target, "Shape.placeReference() target");
43162
+ const offsetPoint = offset === void 0 ? void 0 : requireFiniteVec3$1(offset, "Shape.placeReference() offset");
42503
43163
  const sourcePoint = this.referencePoint(ref);
42504
- let dx = target[0] - sourcePoint[0];
42505
- let dy = target[1] - sourcePoint[1];
42506
- let dz = target[2] - sourcePoint[2];
42507
- if (offset) {
42508
- dx += offset[0];
42509
- dy += offset[1];
42510
- dz += offset[2];
43164
+ let dx = targetPoint[0] - sourcePoint[0];
43165
+ let dy = targetPoint[1] - sourcePoint[1];
43166
+ let dz = targetPoint[2] - sourcePoint[2];
43167
+ if (offsetPoint) {
43168
+ dx += offsetPoint[0];
43169
+ dy += offsetPoint[1];
43170
+ dz += offsetPoint[2];
42511
43171
  }
42512
43172
  return this.translate(dx, dy, dz);
42513
43173
  }
@@ -42519,27 +43179,39 @@ class Shape {
42519
43179
  * Example: `shape.translatePolar(50, 30)` moves 50mm at 30 degrees from +X.
42520
43180
  */
42521
43181
  translatePolar(radius, angleDeg, z2 = 0) {
42522
- const rad = angleDeg * (Math.PI / 180);
42523
- return this.translate(radius * Math.cos(rad), radius * Math.sin(rad), z2);
43182
+ const r = requireFiniteNumber(radius, "Shape.translatePolar() radius");
43183
+ const angle = requireFiniteNumber(angleDeg, "Shape.translatePolar() angleDeg");
43184
+ const zOffset = requireFiniteNumber(z2, "Shape.translatePolar() z");
43185
+ const rad = angle * (Math.PI / 180);
43186
+ return this.translate(r * Math.cos(rad), r * Math.sin(rad), zOffset);
42524
43187
  }
42525
43188
  /** Move the shape relative to its current position. All transforms are immutable and return new shapes. */
42526
43189
  translate(x2, y2, z2) {
42527
- const nextPlan = appendShapeCompileTransform(getShapeCompilePlanInternal(this), { kind: "translate", x: x2, y: y2, z: z2 });
43190
+ const dx = requireFiniteNumber(x2, "Shape.translate() x");
43191
+ const dy = requireFiniteNumber(y2, "Shape.translate() y");
43192
+ const dz = requireFiniteNumber(z2, "Shape.translate() z");
43193
+ const nextPlan = appendShapeCompileTransform(getShapeCompilePlanInternal(this), { kind: "translate", x: dx, y: dy, z: dz });
42528
43194
  return setShapeCompilePlanInternal(
42529
- withTransformedDimensions(this, buildShapeFromCompilePlan(nextPlan, this.colorHex), Transform.translation(x2, y2, z2).toArray()),
43195
+ withTransformedDimensions(this, buildShapeFromCompilePlan(nextPlan, this.colorHex), Transform.translation(dx, dy, dz).toArray()),
42530
43196
  nextPlan
42531
43197
  );
42532
43198
  }
42533
43199
  /** Position the shape so its bounding box min corner is at the given global coordinate. */
42534
43200
  moveTo(x2, y2, z2) {
43201
+ const targetX = requireFiniteNumber(x2, "Shape.moveTo() x");
43202
+ const targetY = requireFiniteNumber(y2, "Shape.moveTo() y");
43203
+ const targetZ = requireFiniteNumber(z2, "Shape.moveTo() z");
42535
43204
  const bb = this.boundingBox();
42536
- return this.translate(x2 - bb.min[0], y2 - bb.min[1], z2 - bb.min[2]);
43205
+ return this.translate(targetX - bb.min[0], targetY - bb.min[1], targetZ - bb.min[2]);
42537
43206
  }
42538
43207
  /** Position the shape relative to another shape's local coordinate system (bounding box min corner). */
42539
43208
  moveToLocal(target, x2, y2, z2) {
43209
+ const localX = requireFiniteNumber(x2, "Shape.moveToLocal() x");
43210
+ const localY = requireFiniteNumber(y2, "Shape.moveToLocal() y");
43211
+ const localZ = requireFiniteNumber(z2, "Shape.moveToLocal() z");
42540
43212
  const s = "toShape" in target ? target.toShape() : target;
42541
43213
  const tbb = s.boundingBox();
42542
- return this.moveTo(tbb.min[0] + x2, tbb.min[1] + y2, tbb.min[2] + z2);
43214
+ return this.moveTo(tbb.min[0] + localX, tbb.min[1] + localY, tbb.min[2] + localZ);
42543
43215
  }
42544
43216
  /** Rotate around an arbitrary axis through the origin. */
42545
43217
  rotate(axis, angleDeg, options) {
@@ -42568,7 +43240,7 @@ class Shape {
42568
43240
  }
42569
43241
  /** Apply a 4x4 affine transform matrix (column-major) or a Transform object. */
42570
43242
  transform(m2) {
42571
- const mat = m2 instanceof Transform ? m2.toArray() : m2;
43243
+ const mat = m2 instanceof Transform ? m2.toArray() : requireFiniteMat4(m2, "Shape.transform() matrix");
42572
43244
  const steps = rigidTransformStepsFromMatrix(mat);
42573
43245
  if (steps == null) {
42574
43246
  throw new Error(
@@ -42580,23 +43252,20 @@ class Shape {
42580
43252
  }
42581
43253
  /** Scale the shape uniformly or per-axis from the shape's bounding box center. Accepts a single number or [x, y, z] array. */
42582
43254
  scale(v) {
43255
+ const scale2 = requireNonZeroFiniteScale3(v, "Shape.scale() scale");
42583
43256
  const bb = this.boundingBox();
42584
43257
  const center = [
42585
43258
  (bb.min[0] + bb.max[0]) / 2,
42586
43259
  (bb.min[1] + bb.max[1]) / 2,
42587
43260
  (bb.min[2] + bb.max[2]) / 2
42588
43261
  ];
42589
- return this.scaleAround(center, v);
43262
+ return this.scaleAround(center, scale2);
42590
43263
  }
42591
43264
  /** Scale the shape uniformly or per-axis from an explicit pivot point. */
42592
43265
  scaleAround(pivot, v) {
42593
- const scale2 = normalizeShapeScale(v);
42594
- if (!scale2) {
42595
- throw new Error(
42596
- `Shape.scaleAround() received a degenerate scale value (${JSON.stringify(v)}). All scale components must be finite and non-zero.`
42597
- );
42598
- }
42599
- if (pivot[0] === 0 && pivot[1] === 0 && pivot[2] === 0) {
43266
+ const scale2 = requireNonZeroFiniteScale3(v, "Shape.scaleAround() scale");
43267
+ const scalePivot = requireFiniteVec3$1(pivot, "Shape.scaleAround() pivot");
43268
+ if (scalePivot[0] === 0 && scalePivot[1] === 0 && scalePivot[2] === 0) {
42600
43269
  const nextPlan2 = appendShapeCompileTransform(getShapeCompilePlanInternal(this), {
42601
43270
  kind: "scale",
42602
43271
  x: scale2[0],
@@ -42604,11 +43273,11 @@ class Shape {
42604
43273
  z: scale2[2]
42605
43274
  });
42606
43275
  return setShapeCompilePlanInternal(
42607
- withTransformedDimensions(this, buildShapeFromCompilePlan(nextPlan2, this.colorHex), Transform.scale(v).toArray()),
43276
+ withTransformedDimensions(this, buildShapeFromCompilePlan(nextPlan2, this.colorHex), Transform.scale(scale2).toArray()),
42608
43277
  nextPlan2
42609
43278
  );
42610
43279
  }
42611
- const translated = this.translate(-pivot[0], -pivot[1], -pivot[2]);
43280
+ const translated = this.translate(-scalePivot[0], -scalePivot[1], -scalePivot[2]);
42612
43281
  const nextPlan = appendShapeCompileTransform(getShapeCompilePlanInternal(translated), {
42613
43282
  kind: "scale",
42614
43283
  x: scale2[0],
@@ -42616,10 +43285,10 @@ class Shape {
42616
43285
  z: scale2[2]
42617
43286
  });
42618
43287
  const scaled = setShapeCompilePlanInternal(
42619
- withTransformedDimensions(translated, buildShapeFromCompilePlan(nextPlan, translated.colorHex), Transform.scale(v).toArray()),
43288
+ withTransformedDimensions(translated, buildShapeFromCompilePlan(nextPlan, translated.colorHex), Transform.scale(scale2).toArray()),
42620
43289
  nextPlan
42621
43290
  );
42622
- return scaled.translate(pivot[0], pivot[1], pivot[2]);
43291
+ return scaled.translate(scalePivot[0], scalePivot[1], scalePivot[2]);
42623
43292
  }
42624
43293
  /** Mirror across a plane through the shape's bounding box center, defined by its normal vector. */
42625
43294
  mirror(normal) {
@@ -42633,32 +43302,34 @@ class Shape {
42633
43302
  }
42634
43303
  /** Mirror across a plane through an explicit point, defined by its normal vector. */
42635
43304
  mirrorThrough(point, normal) {
42636
- if (point[0] === 0 && point[1] === 0 && point[2] === 0) {
43305
+ const mirrorPoint = requireFiniteVec3$1(point, "Shape.mirrorThrough() point");
43306
+ const mirrorNormal = requireNonZeroFiniteVec3(normal, "Shape.mirrorThrough() normal");
43307
+ if (mirrorPoint[0] === 0 && mirrorPoint[1] === 0 && mirrorPoint[2] === 0) {
42637
43308
  const transformedPlan2 = appendShapeCompileTransform(getShapeCompilePlanInternal(this), {
42638
43309
  kind: "mirror",
42639
- normalX: normal[0],
42640
- normalY: normal[1],
42641
- normalZ: normal[2]
43310
+ normalX: mirrorNormal[0],
43311
+ normalY: mirrorNormal[1],
43312
+ normalZ: mirrorNormal[2]
42642
43313
  });
42643
43314
  const nextPlan2 = wrapRepeatedShapeCompilePlan(transformedPlan2, "mirror");
42644
43315
  return setShapeCompilePlanInternal(
42645
- withTransformedDimensions(this, buildShapeFromCompilePlan(nextPlan2, this.colorHex), mirrorMatrix(normal)),
43316
+ withTransformedDimensions(this, buildShapeFromCompilePlan(nextPlan2, this.colorHex), mirrorMatrix(mirrorNormal)),
42646
43317
  nextPlan2
42647
43318
  );
42648
43319
  }
42649
- const translated = this.translate(-point[0], -point[1], -point[2]);
43320
+ const translated = this.translate(-mirrorPoint[0], -mirrorPoint[1], -mirrorPoint[2]);
42650
43321
  const transformedPlan = appendShapeCompileTransform(getShapeCompilePlanInternal(translated), {
42651
43322
  kind: "mirror",
42652
- normalX: normal[0],
42653
- normalY: normal[1],
42654
- normalZ: normal[2]
43323
+ normalX: mirrorNormal[0],
43324
+ normalY: mirrorNormal[1],
43325
+ normalZ: mirrorNormal[2]
42655
43326
  });
42656
43327
  const nextPlan = wrapRepeatedShapeCompilePlan(transformedPlan, "mirror");
42657
43328
  const mirrored = setShapeCompilePlanInternal(
42658
- withTransformedDimensions(translated, buildShapeFromCompilePlan(nextPlan, translated.colorHex), mirrorMatrix(normal)),
43329
+ withTransformedDimensions(translated, buildShapeFromCompilePlan(nextPlan, translated.colorHex), mirrorMatrix(mirrorNormal)),
42659
43330
  nextPlan
42660
43331
  );
42661
- return mirrored.translate(point[0], point[1], point[2]);
43332
+ return mirrored.translate(mirrorPoint[0], mirrorPoint[1], mirrorPoint[2]);
42662
43333
  }
42663
43334
  /**
42664
43335
  * Reorient a shape so its primary axis (Z) points along the given direction.
@@ -42668,7 +43339,7 @@ class Shape {
42668
43339
  * Example: cylinder(40, 5).pointAlong([1, 0, 0]) — lays cylinder along X, starting at origin
42669
43340
  */
42670
43341
  pointAlong(direction) {
42671
- const [dx, dy, dz] = direction;
43342
+ const [dx, dy, dz] = requireNonZeroFiniteVec3(direction, "Shape.pointAlong() direction");
42672
43343
  const len = Math.sqrt(dx * dx + dy * dy + dz * dz) || 1;
42673
43344
  const nx = dx / len, ny = dy / len, nz = dz / len;
42674
43345
  const cx = -ny, cy = nx, cz = 0;
@@ -42687,18 +43358,21 @@ class Shape {
42687
43358
  * @internal Prefer rotate(), rotateX(), rotateY(), rotateZ() for public use.
42688
43359
  */
42689
43360
  rotateAroundAxis(axis, angleDeg, pivot = [0, 0, 0]) {
42690
- const len = Math.sqrt(axis[0] ** 2 + axis[1] ** 2 + axis[2] ** 2) || 1;
42691
- const normalizedAxis = [axis[0] / len, axis[1] / len, axis[2] / len];
42692
- const matrix = rotationAroundAxisMatrix(normalizedAxis, angleDeg, pivot);
43361
+ const rotateAxis = requireNonZeroFiniteVec3(axis, "Shape.rotateAroundAxis() axis");
43362
+ const degrees = requireFiniteNumber(angleDeg, "Shape.rotateAroundAxis() angleDeg");
43363
+ const rotatePivot = requireFiniteVec3$1(pivot, "Shape.rotateAroundAxis() pivot");
43364
+ const len = Math.sqrt(rotateAxis[0] ** 2 + rotateAxis[1] ** 2 + rotateAxis[2] ** 2) || 1;
43365
+ const normalizedAxis = [rotateAxis[0] / len, rotateAxis[1] / len, rotateAxis[2] / len];
43366
+ const matrix = rotationAroundAxisMatrix(normalizedAxis, degrees, rotatePivot);
42693
43367
  const nextPlan = appendShapeCompileTransform(getShapeCompilePlanInternal(this), {
42694
43368
  kind: "rotateAround",
42695
43369
  axisX: normalizedAxis[0],
42696
43370
  axisY: normalizedAxis[1],
42697
43371
  axisZ: normalizedAxis[2],
42698
- degrees: angleDeg,
42699
- pivotX: pivot[0],
42700
- pivotY: pivot[1],
42701
- pivotZ: pivot[2]
43372
+ degrees,
43373
+ pivotX: rotatePivot[0],
43374
+ pivotY: rotatePivot[1],
43375
+ pivotZ: rotatePivot[2]
42702
43376
  });
42703
43377
  return setShapeCompilePlanInternal(
42704
43378
  withTransformedDimensions(this, buildShapeFromCompilePlan(nextPlan, this.colorHex), matrix),
@@ -42710,10 +43384,12 @@ class Shape {
42710
43384
  * `movingPoint` / `targetPoint` may be raw world points or this shape's anchors/references.
42711
43385
  */
42712
43386
  rotateAroundTo(axis, pivot, movingPoint, targetPoint, options = {}) {
43387
+ const rotateAxis = requireNonZeroFiniteVec3(axis, "Shape.rotateAroundTo() axis");
43388
+ const rotatePivot = requireFiniteVec3$1(pivot, "Shape.rotateAroundTo() pivot");
42713
43389
  const moving = resolveRotationPoint(this, movingPoint);
42714
43390
  const target = resolveRotationPoint(this, targetPoint);
42715
- const angleDeg = solveRotateAroundAngle(axis, pivot, moving, target, options);
42716
- return this.rotateAroundAxis(axis, angleDeg, pivot);
43391
+ const angleDeg = solveRotateAroundAngle(rotateAxis, rotatePivot, moving, target, options);
43392
+ return this.rotateAroundAxis(rotateAxis, angleDeg, rotatePivot);
42717
43393
  }
42718
43394
  // --- Booleans ---
42719
43395
  /** Unwrap any object with toShape() without circular import. */
@@ -42877,17 +43553,19 @@ class Shape {
42877
43553
  }
42878
43554
  /** Split by infinite plane. Returns [positive-side, negative-side]. */
42879
43555
  splitByPlane(normal, originOffset = 0) {
43556
+ const planeNormal = requireNonZeroFiniteVec3(normal, "Shape.splitByPlane() normal");
43557
+ const planeOffset = requireFiniteNumber(originOffset, "Shape.splitByPlane() originOffset");
42880
43558
  const info = deriveGeometryInfo(getShapeGeometryInfoInternal(this), "boolean", {
42881
43559
  topology: _activeBackend === "truck" ? "kernel" : "none"
42882
43560
  });
42883
43561
  const basePlan = getShapeCompilePlanInternal(this);
42884
43562
  const firstPlan = createOwnedTopologyRewritePlan(
42885
- buildTrimByPlaneShapeCompilePlan(basePlan, normal, originOffset),
43563
+ buildTrimByPlaneShapeCompilePlan(basePlan, planeNormal, planeOffset),
42886
43564
  "splitByPlane:positive",
42887
43565
  (owner) => buildTrimByPlaneTopologyRewritePropagation(owner, basePlan)
42888
43566
  );
42889
43567
  const secondPlan = createOwnedTopologyRewritePlan(
42890
- buildTrimByPlaneShapeCompilePlan(basePlan, [-normal[0], -normal[1], -normal[2]], -originOffset),
43568
+ buildTrimByPlaneShapeCompilePlan(basePlan, [-planeNormal[0], -planeNormal[1], -planeNormal[2]], -planeOffset),
42891
43569
  "splitByPlane:opposite",
42892
43570
  (owner) => buildTrimByPlaneTopologyRewritePropagation(owner, basePlan)
42893
43571
  );
@@ -42905,9 +43583,11 @@ class Shape {
42905
43583
  }
42906
43584
  /** Keep the positive side of the plane and discard the opposite side. */
42907
43585
  trimByPlane(normal, originOffset = 0) {
43586
+ const planeNormal = requireNonZeroFiniteVec3(normal, "Shape.trimByPlane() normal");
43587
+ const planeOffset = requireFiniteNumber(originOffset, "Shape.trimByPlane() originOffset");
42908
43588
  const basePlan = getShapeCompilePlanInternal(this);
42909
43589
  const nextPlan = createOwnedTopologyRewritePlan(
42910
- buildTrimByPlaneShapeCompilePlan(basePlan, normal, originOffset),
43590
+ buildTrimByPlaneShapeCompilePlan(basePlan, planeNormal, planeOffset),
42911
43591
  "trimByPlane",
42912
43592
  (owner) => buildTrimByPlaneTopologyRewritePropagation(owner, basePlan)
42913
43593
  );
@@ -42927,8 +43607,9 @@ class Shape {
42927
43607
  * `openFaces` names any subset of the base shape's labeled faces to leave open (no wall).
42928
43608
  */
42929
43609
  shell(thickness, opts = {}) {
43610
+ const wallThickness = requirePositiveFiniteNumber(thickness, "Shape.shell() thickness");
42930
43611
  const basePlan = getShapeCompilePlanInternal(this);
42931
- const shellPlan = buildShellShapeCompilePlan(basePlan, thickness, opts.openFaces);
43612
+ const shellPlan = buildShellShapeCompilePlan(basePlan, wallThickness, opts.openFaces);
42932
43613
  if (!shellPlan) {
42933
43614
  throw new Error(
42934
43615
  "Shape.shell() supports box(), cylinder(), straight extrude(), loft(), sweep(), and variableSweep() solids with optional face openings and rigid transforms."
@@ -42949,7 +43630,8 @@ class Shape {
42949
43630
  }
42950
43631
  /** Offset-thicken an exact open surface or shell into a solid. */
42951
43632
  thicken(thickness) {
42952
- if (!Number.isFinite(thickness) || thickness === 0) {
43633
+ const wallThickness = requireFiniteNumber(thickness, "Shape.thicken() thickness");
43634
+ if (wallThickness === 0) {
42953
43635
  throw new Error("Shape.thicken() requires a non-zero finite thickness.");
42954
43636
  }
42955
43637
  const info = getShapeGeometryInfoInternal(this);
@@ -42957,7 +43639,7 @@ class Shape {
42957
43639
  throw new Error("Shape.thicken() is only available on surface-representation shapes.");
42958
43640
  }
42959
43641
  const basePlan = getShapeCompilePlanInternal(this);
42960
- const nextPlan = buildSurfaceThickenShapeCompilePlan(basePlan, thickness);
43642
+ const nextPlan = buildSurfaceThickenShapeCompilePlan(basePlan, wallThickness);
42961
43643
  const representation = info.backend === "occt" || info.backend === "truck" ? "brep-solid" : "mesh-solid";
42962
43644
  return setShapeCompilePlanInternal(
42963
43645
  setShapeGeometryInfoInternal(
@@ -42998,11 +43680,12 @@ class Shape {
42998
43680
  }
42999
43681
  /** Slice the runtime solid by a plane normal to local Z at the given offset. */
43000
43682
  slice(offset = 0) {
43683
+ const planeOffset = requireFiniteNumber(offset, "Shape.slice() offset");
43001
43684
  if (getShapeGeometryInfoInternal(this).backend === "truck") {
43002
- const slicedProfile = lowerExactSlicedShapeCompilePlanToTruckProfileBackend(getShapeCompilePlanInternal(this), offset);
43685
+ const slicedProfile = lowerExactSlicedShapeCompilePlanToTruckProfileBackend(getShapeCompilePlanInternal(this), planeOffset);
43003
43686
  if (slicedProfile) return slicedProfile;
43004
43687
  }
43005
- return getShapeRuntimeBackendInternal(this).slice(offset);
43688
+ return getShapeRuntimeBackendInternal(this).slice(planeOffset);
43006
43689
  }
43007
43690
  /** Orthographically project the runtime solid onto the local XY plane. */
43008
43691
  project() {
@@ -43025,9 +43708,10 @@ class Shape {
43025
43708
  const sp = this.referencePoint(selfAnchor);
43026
43709
  let dx = tp[0] - sp[0], dy = tp[1] - sp[1], dz = tp[2] - sp[2];
43027
43710
  if (offset) {
43028
- dx += offset[0];
43029
- dy += offset[1];
43030
- dz += offset[2];
43711
+ const offsetPoint = requireFiniteVec3$1(offset, "Shape.attachTo() offset");
43712
+ dx += offsetPoint[0];
43713
+ dy += offsetPoint[1];
43714
+ dz += offsetPoint[2];
43031
43715
  }
43032
43716
  return this.translate(dx, dy, dz);
43033
43717
  }
@@ -43043,9 +43727,9 @@ class Shape {
43043
43727
  * - `protrude` = how far the child sticks out (positive = outward from face)
43044
43728
  */
43045
43729
  onFace(parent, face, opts = {}) {
43046
- const u2 = opts.u ?? 0;
43047
- const v = opts.v ?? 0;
43048
- const p2 = opts.protrude ?? 0;
43730
+ const u2 = requireFiniteNumber(opts.u ?? 0, "Shape.onFace() u");
43731
+ const v = requireFiniteNumber(opts.v ?? 0, "Shape.onFace() v");
43732
+ const p2 = requireFiniteNumber(opts.protrude ?? 0, "Shape.onFace() protrude");
43049
43733
  const opposite = {
43050
43734
  front: "back",
43051
43735
  back: "front",