forgecad 0.9.14 → 0.9.16

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 (239) hide show
  1. package/LICENSE +6 -4
  2. package/README.md +8 -4
  3. package/dist/assets/{AdminPage-eWGs2K6H.js → AdminPage-CXvls4-J.js} +2 -2
  4. package/dist/assets/{BenchmarkPage-CTrLKfpo.js → BenchmarkPage-B27zk8xL.js} +4 -15
  5. package/dist/assets/{BlogPage-5nPesyds.js → BlogPage-CMAVvgQL.js} +2 -2
  6. package/dist/assets/{DocsPage-C4Y3nbYc.js → DocsPage-knf4I4h7.js} +9 -3
  7. package/dist/assets/EditorApp-BHMQlJ-D.js +14686 -0
  8. package/dist/assets/{EditorApp-BAnckbsk.css → EditorApp-BpjZgzk0.css} +846 -0
  9. package/dist/assets/{EmbedViewer-C8fB4n5U.js → EmbedViewer-D7ZGlFjx.js} +3 -3
  10. package/dist/assets/{LandingPageProofDriven-jSz0LaMM.js → LandingPageProofDriven-CnevhTE8.js} +36 -38
  11. package/dist/assets/LegalPage-BPTUmqeg.js +39 -0
  12. package/dist/assets/LegalPage-BRlScr9A.css +91 -0
  13. package/dist/assets/{PricingPage-B83B90zh.js → PricingPage-B0D4goG_.js} +19 -19
  14. package/dist/assets/{PricingPage-BMedqFef.css → PricingPage-BPF6HKyO.css} +25 -0
  15. package/dist/assets/{SettingsPage-DY889pcu.js → SettingsPage-CFF-UgjI.js} +2 -2
  16. package/dist/assets/app-CE3sYcV7.css +3890 -0
  17. package/dist/assets/{app-bEww1ic4.js → app-T0pDcSX4.js} +3382 -1069
  18. package/dist/assets/cli/{render-Cho2uKG_.js → render-C5pcIISc.js} +477 -29
  19. package/dist/assets/{constructionHistoryWorker-HYwzJY4m.js → constructionHistoryWorker-Ba2Hm58b.js} +928 -243
  20. package/dist/assets/{evalWorker-CjQwJSE-.js → evalWorker-vkx310U2.js} +8883 -6040
  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-BuTJDVX6.js} +1179 -273
  24. package/dist/assets/{javascript-70-4uGcz.js → javascript-1kQXfVaz.js} +1 -1
  25. package/dist/assets/{targets-D6PWsv6X.js → jointPose-B_Cgedn9.js} +71 -3
  26. package/dist/assets/landing-proof-driven-DiGqdtWa.js +18 -0
  27. package/dist/assets/{landing-proof-driven-oFYW6mjz.css → landing-proof-driven-ORyigZ6p.css} +13 -7
  28. package/dist/assets/legalContent-ZfFGMmi4.js +251 -0
  29. package/dist/assets/{manifold-rmfAcdwF.js → manifold-BWgsjmAM.js} +1 -1
  30. package/dist/assets/{manifold-uRzgk5O8.js → manifold-D6IFSkhH.js} +2 -2
  31. package/dist/assets/{manifold-CG9Fokx-.js → manifold-rZexZI0G.js} +1 -1
  32. package/dist/assets/{reportWorker-4cW_ZpoS.js → reportWorker-0AGij1Ru.js} +8659 -12771
  33. package/dist/assets/{scalar-sampling-budget-CfDiFvh7.js → scalar-sampling-budget-J5cuzxT1.js} +8050 -6203
  34. package/dist/assets/{scanProxyWorker-Bs2TDgLw.js → scanProxyWorker-Vl4Wxa1y.js} +50 -6
  35. package/dist/assets/{solver-DuJAO8S6.js → solver-BZ9LPTHs.js} +1 -1
  36. package/dist/assets/solver_bg-DAHZJ_rw.wasm +0 -0
  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 +5 -12
  42. package/dist/docs-raw/CLI.md +34 -10
  43. package/dist/docs-raw/component-model.md +27 -11
  44. package/dist/docs-raw/generated/assembly.md +374 -187
  45. package/dist/docs-raw/generated/concepts.md +245 -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 +9 -19
  49. package/dist/docs-raw/generated/output.md +29 -4
  50. package/dist/docs-raw/generated/runtime-names.md +49 -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 +40 -39
  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 +3 -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/docs-raw/welcome.md +2 -0
  78. package/dist/index.html +40 -12
  79. package/dist/llms.txt +8 -0
  80. package/dist/site.webmanifest +1 -1
  81. package/dist/sitemap.xml +49 -13
  82. package/dist-cli/{check-compiler-U5SOPN7X.js → check-compiler-SYQ2PWOB.js} +1 -2
  83. package/dist-cli/{check-query-propagation-XOKNSSYU.js → check-query-propagation-HIAGV62W.js} +1 -2
  84. package/dist-cli/{chunk-EXWGNL6K.js → chunk-SPZE3DUY.js} +20659 -17930
  85. package/dist-cli/forgecad.js +3568 -1250
  86. package/dist-cli/{forgecad_geometry-GYVNKPIE.js → forgecad_geometry-QOQIIP53.js} +42 -1
  87. package/dist-cli/forgecad_geometry_bg.wasm +0 -0
  88. package/dist-cli/{solver-46FFSK2U.js → solver-OK4HECRH.js} +0 -1
  89. package/dist-cli/solver_bg.wasm +0 -0
  90. package/dist-skill/CONTEXT.md +1192 -725
  91. package/dist-skill/SKILL.md +3 -2
  92. package/dist-skill/docs/API/core/concepts.md +64 -1
  93. package/dist-skill/docs/CLI.md +34 -10
  94. package/dist-skill/docs/generated/assembly.md +339 -213
  95. package/dist-skill/docs/generated/core.md +283 -6
  96. package/dist-skill/docs/generated/curves.md +272 -362
  97. package/dist-skill/docs/generated/lib.md +9 -19
  98. package/dist-skill/docs/generated/output.md +29 -4
  99. package/dist-skill/docs/generated/runtime-names.md +40 -0
  100. package/dist-skill/docs/generated/sdf.md +31 -0
  101. package/dist-skill/docs/generated/sheet-metal.md +9 -0
  102. package/dist-skill/docs/generated/sketch.md +44 -2
  103. package/dist-skill/docs/generated/viewport.md +2 -87
  104. package/dist-skill/docs/guides/coordinate-system.md +20 -16
  105. package/dist-skill/docs/guides/geometry-conventions.md +2 -2
  106. package/dist-skill/docs/guides/inspection-bundles.md +2 -1
  107. package/dist-skill/docs/guides/joint-design.md +24 -0
  108. package/dist-skill/docs/guides/positioning.md +13 -3
  109. package/dist-skill/library/forgecad-component-model/SKILL.md +10 -1
  110. package/dist-skill/library/forgecad-image-replicator/SKILL.md +6 -6
  111. package/dist-skill/library/forgecad-image-replicator/scripts/compare_images.py +166 -0
  112. package/dist-skill/library/forgecad-make-a-model/SKILL.md +39 -38
  113. package/dist-skill/library/forgecad-model-grader/SKILL.md +1 -1
  114. package/dist-skill/library/forgecad-prepare-prompt/SKILL.md +1 -1
  115. package/dist-skill/library/forgecad-project/SKILL.md +2 -0
  116. package/dist-skill/library/forgecad-render-inspect/SKILL.md +3 -1
  117. package/examples/api/assembly-kinematics-foundation.forge.js +65 -0
  118. package/examples/api/assembly-kinematics-four-bar.forge.js +115 -0
  119. package/examples/api/assembly-kinematics-limb.forge.js +116 -0
  120. package/examples/api/connector-frame-rig-chain.forge.js +102 -0
  121. package/examples/api/exact-sheet-shell-assembly.forge.js +0 -2
  122. package/examples/api/exact-surface-studio.forge.js +6 -8
  123. package/examples/api/helix-basics.forge.js +8 -8
  124. package/examples/api/lean-foundations/README.md +12 -0
  125. package/examples/api/lean-foundations/curve-blend-exact.forge.js +22 -0
  126. package/examples/api/lean-foundations/curve-fit-interpolation.forge.js +18 -0
  127. package/examples/api/lean-foundations/curve-helix-canonicalization.forge.js +27 -0
  128. package/examples/api/lean-foundations/curve-route-canonicalization.forge.js +16 -0
  129. package/examples/api/lean-foundations/curve-trim-reverse.forge.js +24 -0
  130. package/examples/api/lean-foundations/exact-curve-arc.forge.js +36 -0
  131. package/examples/api/mixed-edge-finishes-proof.forge.js +8 -11
  132. package/examples/api/route3d-elbow.forge.js +71 -0
  133. package/examples/api/transition-curves.forge.js +44 -15
  134. package/examples/api/variable-sweep-test.forge.js +3 -1
  135. package/examples/api/y-blend-corner-showcase.forge.js +0 -2
  136. package/examples/generative/coral-vase.forge.js +1 -1
  137. package/examples/nurbs-tube.forge.js +1 -1
  138. package/package.json +17 -13
  139. package/dist/assets/EditorApp-lXv53A1m.js +0 -13610
  140. package/dist/assets/app-CsHnaBWt.css +0 -1789
  141. package/dist/assets/forgecad_geometry_bg-C5_E9Oa9.wasm +0 -0
  142. package/dist/assets/solver_bg-CWvv4lnN.wasm +0 -0
  143. package/dist/docs-raw/API/README.md +0 -16
  144. package/dist/docs-raw/API/core/concepts.md +0 -118
  145. package/dist/docs-raw/INDEX.md +0 -138
  146. package/dist/docs-raw/RELEASING.md +0 -87
  147. package/dist/docs-raw/agent-native-api.md +0 -27
  148. package/dist/docs-raw/beta-deployment.md +0 -304
  149. package/dist/docs-raw/beta-operations.md +0 -325
  150. package/dist/docs-raw/blueprint-first.md +0 -145
  151. package/dist/docs-raw/cli-monetization.md +0 -112
  152. package/dist/docs-raw/coding-best-practices.md +0 -120
  153. package/dist/docs-raw/coding.md +0 -340
  154. package/dist/docs-raw/deployment.md +0 -374
  155. package/dist/docs-raw/guides/skill-maintenance.md +0 -161
  156. package/dist/docs-raw/guides/surface-members.md +0 -82
  157. package/dist/docs-raw/harbor-cli.md +0 -854
  158. package/dist/docs-raw/internals/backend-vocabulary.md +0 -35
  159. package/dist/docs-raw/internals/compiler.md +0 -307
  160. package/dist/docs-raw/internals/constraint-solver-quality.md +0 -161
  161. package/dist/docs-raw/internals/constraint-solver.md +0 -176
  162. package/dist/docs-raw/internals/shape-from-slices.md +0 -152
  163. package/dist/docs-raw/internals/sketch-2d-pipeline.md +0 -108
  164. package/dist/docs-raw/platform/admin.md +0 -45
  165. package/dist/docs-raw/platform/architecture.md +0 -82
  166. package/dist/docs-raw/platform/auth.md +0 -139
  167. package/dist/docs-raw/platform/email.md +0 -67
  168. package/dist/docs-raw/platform/google-oauth-setup.md +0 -88
  169. package/dist/docs-raw/platform/observability.md +0 -197
  170. package/dist/docs-raw/platform/projects.md +0 -111
  171. package/dist/docs-raw/platform/sharing.md +0 -90
  172. package/dist/docs-raw/product/README.md +0 -39
  173. package/dist/docs-raw/product/api-as-product-language.md +0 -13
  174. package/dist/docs-raw/product/business-model.md +0 -15
  175. package/dist/docs-raw/product/competitive-positioning.md +0 -17
  176. package/dist/docs-raw/product/creative-manufacturing.md +0 -15
  177. package/dist/docs-raw/product/founder-story.md +0 -11
  178. package/dist/docs-raw/product/manufacturing-workflows.md +0 -15
  179. package/dist/docs-raw/product/onboarding-first-experience.md +0 -256
  180. package/dist/docs-raw/product/product-loop.md +0 -17
  181. package/dist/docs-raw/product/strategic-decisions.md +0 -22
  182. package/dist/docs-raw/product/user-outreach-email-templates.md +0 -161
  183. package/dist/docs-raw/product/user-segments.md +0 -15
  184. package/dist/docs-raw/product/vision.md +0 -26
  185. package/dist/docs-raw/rl-environments.md +0 -350
  186. package/dist/docs-raw/runbook.md +0 -611
  187. package/dist-cli/check-compiler-U5SOPN7X.js.map +0 -1
  188. package/dist-cli/check-query-propagation-XOKNSSYU.js.map +0 -1
  189. package/dist-cli/chunk-EXWGNL6K.js.map +0 -1
  190. package/dist-cli/forgecad.js.map +0 -1
  191. package/dist-cli/forgecad_geometry-GYVNKPIE.js.map +0 -1
  192. package/dist-cli/solver-46FFSK2U.js.map +0 -1
  193. package/dist-skill/SKILL-dev.md +0 -145
  194. package/dist-skill/docs-dev/API/core/concepts.md +0 -118
  195. package/dist-skill/docs-dev/CLI.md +0 -677
  196. package/dist-skill/docs-dev/agent-native-api.md +0 -27
  197. package/dist-skill/docs-dev/blueprint-first.md +0 -145
  198. package/dist-skill/docs-dev/coding-best-practices.md +0 -120
  199. package/dist-skill/docs-dev/coding.md +0 -340
  200. package/dist-skill/docs-dev/component-model.md +0 -164
  201. package/dist-skill/docs-dev/generated/assembly.md +0 -794
  202. package/dist-skill/docs-dev/generated/core.md +0 -2117
  203. package/dist-skill/docs-dev/generated/curves.md +0 -2583
  204. package/dist-skill/docs-dev/generated/lib.md +0 -169
  205. package/dist-skill/docs-dev/generated/output.md +0 -247
  206. package/dist-skill/docs-dev/generated/sdf.md +0 -446
  207. package/dist-skill/docs-dev/generated/sheet-metal.md +0 -504
  208. package/dist-skill/docs-dev/generated/sketch.md +0 -1811
  209. package/dist-skill/docs-dev/generated/viewport.md +0 -585
  210. package/dist-skill/docs-dev/generated/wood.md +0 -108
  211. package/dist-skill/docs-dev/guides/coordinate-system.md +0 -46
  212. package/dist-skill/docs-dev/guides/geometry-conventions.md +0 -52
  213. package/dist-skill/docs-dev/guides/inspection-bundles.md +0 -485
  214. package/dist-skill/docs-dev/guides/joint-design.md +0 -78
  215. package/dist-skill/docs-dev/guides/modeling-recipes.md +0 -78
  216. package/dist-skill/docs-dev/guides/positioning.md +0 -161
  217. package/dist-skill/docs-dev/guides/skill-maintenance.md +0 -161
  218. package/dist-skill/docs-dev/internals/backend-vocabulary.md +0 -35
  219. package/dist-skill/docs-dev/internals/compiler.md +0 -307
  220. package/dist-skill/docs-dev/internals/constraint-solver-quality.md +0 -161
  221. package/dist-skill/docs-dev/internals/constraint-solver.md +0 -176
  222. package/dist-skill/docs-dev/internals/sketch-2d-pipeline.md +0 -108
  223. package/dist-skill/library/forgecad-image-replicator/scripts/compare_images.mjs +0 -289
  224. package/examples/api/bolted-service-cover.forge.js +0 -17
  225. package/examples/api/cable-gland-anchor.forge.js +0 -14
  226. package/examples/api/captured-cartridge-guide.forge.js +0 -14
  227. package/examples/api/captured-linear-slide.forge.js +0 -13
  228. package/examples/api/clevis-pin-joint.forge.js +0 -13
  229. package/examples/api/datum-enclosure.forge.js +0 -16
  230. package/examples/api/hose-barb-port.forge.js +0 -14
  231. package/examples/api/knuckled-hinge-assembly.forge.js +0 -15
  232. package/examples/api/living-hinge-cover.forge.js +0 -14
  233. package/examples/api/pcb-terminal-block.forge.js +0 -22
  234. package/examples/api/pinned-lever-pivot-stack.forge.js +0 -14
  235. package/examples/api/retained-shaft-knob-stack.forge.js +0 -15
  236. package/examples/api/routed-tube-clip.forge.js +0 -15
  237. package/examples/api/seated-bearing-stack.forge.js +0 -30
  238. package/examples/api/snap-latch-cover.forge.js +0 -14
  239. package/examples/api/thumb-screw-clamp.forge.js +0 -15
@@ -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,7 +7832,20 @@ 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. */
7848
+ // biome-ignore lint/suspicious/useAdjacentOverloadSignatures: Static Transform.scale() and chainable instance scale() intentionally share the CAD API name.
7770
7849
  scale(v) {
7771
7850
  return this.mul(Transform.scale(v));
7772
7851
  }
@@ -7776,11 +7855,11 @@ class Transform {
7776
7855
  }
7777
7856
  /** Transform a point using homogeneous coordinates. */
7778
7857
  point(p2) {
7779
- return transformPoint(this.m, p2, 1);
7858
+ return transformPoint(this.m, requireFiniteVec3$1(p2, "Transform.point() point"), 1);
7780
7859
  }
7781
7860
  /** Transform a direction vector without translation. */
7782
7861
  vector(v) {
7783
- return transformPoint(this.m, v, 0);
7862
+ return transformPoint(this.m, requireFiniteVec3$1(v, "Transform.vector() vector"), 0);
7784
7863
  }
7785
7864
  /** Return the transform as a raw 4x4 matrix array. */
7786
7865
  toArray() {
@@ -10010,6 +10089,42 @@ function cloneCutTaperCompilePlan(plan) {
10010
10089
  scale: [canonicalNumber(plan.scale[0]), canonicalNumber(plan.scale[1])]
10011
10090
  };
10012
10091
  }
10092
+ function cloneEdgeFeatureTarget(target) {
10093
+ return {
10094
+ midpoint: [target.midpoint[0], target.midpoint[1], target.midpoint[2]],
10095
+ start: [target.start[0], target.start[1], target.start[2]],
10096
+ end: [target.end[0], target.end[1], target.end[2]],
10097
+ convex: target.convex,
10098
+ ...target.nativeTopology ? { nativeTopology: { backend: target.nativeTopology.backend, edge: target.nativeTopology.edge } } : {}
10099
+ };
10100
+ }
10101
+ function cloneEdgeFeatureQuery(query) {
10102
+ if (!query) return void 0;
10103
+ return {
10104
+ ...query.near ? { near: [query.near[0], query.near[1], query.near[2]] } : {},
10105
+ ...query.parallel ? { parallel: [query.parallel[0], query.parallel[1], query.parallel[2]] } : {},
10106
+ ...query.perpendicular ? { perpendicular: [query.perpendicular[0], query.perpendicular[1], query.perpendicular[2]] } : {},
10107
+ ...query.convex !== void 0 ? { convex: query.convex } : {},
10108
+ ...query.concave !== void 0 ? { concave: query.concave } : {},
10109
+ ...query.minAngle !== void 0 ? { minAngle: canonicalNumber(query.minAngle) } : {},
10110
+ ...query.maxAngle !== void 0 ? { maxAngle: canonicalNumber(query.maxAngle) } : {},
10111
+ ...query.minLength !== void 0 ? { minLength: canonicalNumber(query.minLength) } : {},
10112
+ ...query.maxLength !== void 0 ? { maxLength: canonicalNumber(query.maxLength) } : {},
10113
+ ...query.within ? {
10114
+ within: {
10115
+ ...query.within.xMin !== void 0 ? { xMin: canonicalNumber(query.within.xMin) } : {},
10116
+ ...query.within.xMax !== void 0 ? { xMax: canonicalNumber(query.within.xMax) } : {},
10117
+ ...query.within.yMin !== void 0 ? { yMin: canonicalNumber(query.within.yMin) } : {},
10118
+ ...query.within.yMax !== void 0 ? { yMax: canonicalNumber(query.within.yMax) } : {},
10119
+ ...query.within.zMin !== void 0 ? { zMin: canonicalNumber(query.within.zMin) } : {},
10120
+ ...query.within.zMax !== void 0 ? { zMax: canonicalNumber(query.within.zMax) } : {}
10121
+ }
10122
+ } : {},
10123
+ ...query.atZ !== void 0 ? { atZ: canonicalNumber(query.atZ) } : {},
10124
+ ...query.tolerance !== void 0 ? { tolerance: canonicalNumber(query.tolerance) } : {},
10125
+ ...query.angleTolerance !== void 0 ? { angleTolerance: canonicalNumber(query.angleTolerance) } : {}
10126
+ };
10127
+ }
10013
10128
  function featureCutExtentForwardSide(extent) {
10014
10129
  return extent.kind === "two-sided" ? extent.forward : extent;
10015
10130
  }
@@ -10106,6 +10221,41 @@ function cloneSweepPathCompilePlan(path) {
10106
10221
  degree: path.degree,
10107
10222
  closed: path.closed
10108
10223
  };
10224
+ case "route3d":
10225
+ return {
10226
+ kind: "route3d",
10227
+ segments: path.segments.map(
10228
+ (segment) => segment.kind === "line" ? {
10229
+ kind: "line",
10230
+ from: canonicalVec3(segment.from),
10231
+ to: canonicalVec3(segment.to),
10232
+ length: canonicalNumber(segment.length)
10233
+ } : {
10234
+ kind: "arc",
10235
+ center: canonicalVec3(segment.center),
10236
+ radius: canonicalNumber(segment.radius),
10237
+ axis: canonicalVec3(segment.axis),
10238
+ start: canonicalVec3(segment.start),
10239
+ end: canonicalVec3(segment.end),
10240
+ sweepDeg: canonicalNumber(segment.sweepDeg),
10241
+ length: canonicalNumber(segment.length)
10242
+ }
10243
+ ),
10244
+ ports: Object.fromEntries(
10245
+ Object.entries(path.ports).map(([name, port]) => [
10246
+ name,
10247
+ {
10248
+ name: port.name,
10249
+ origin: canonicalVec3(port.origin),
10250
+ axis: canonicalVec3(port.axis),
10251
+ xAxis: canonicalVec3(port.xAxis),
10252
+ yAxis: canonicalVec3(port.yAxis),
10253
+ station: canonicalNumber(port.station)
10254
+ }
10255
+ ])
10256
+ ),
10257
+ length: canonicalNumber(path.length)
10258
+ };
10109
10259
  default:
10110
10260
  assertExhaustive(path);
10111
10261
  }
@@ -10200,6 +10350,7 @@ function cloneProfileEdge(edge) {
10200
10350
  }
10201
10351
  }
10202
10352
  function cloneShapeCompilePlan(plan) {
10353
+ var _a3, _b3, _c2;
10203
10354
  if (!plan) return null;
10204
10355
  let result;
10205
10356
  switch (plan.kind) {
@@ -10378,13 +10529,9 @@ function cloneShapeCompilePlan(plan) {
10378
10529
  radius: plan.radius,
10379
10530
  segments: plan.segments,
10380
10531
  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
- }))
10532
+ edgeTargets: (_a3 = plan.edgeTargets) == null ? void 0 : _a3.map(cloneEdgeFeatureTarget),
10533
+ edgeQuery: cloneEdgeFeatureQuery(plan.edgeQuery),
10534
+ edgeSelection: plan.edgeSelection
10388
10535
  };
10389
10536
  break;
10390
10537
  case "cornerYBlend":
@@ -10394,13 +10541,7 @@ function cloneShapeCompilePlan(plan) {
10394
10541
  radius: plan.radius,
10395
10542
  segments: plan.segments,
10396
10543
  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
- })),
10544
+ edgeTargets: ((_b3 = plan.edgeTargets) == null ? void 0 : _b3.map(cloneEdgeFeatureTarget)) ?? [],
10404
10545
  cornerPoint: [plan.cornerPoint[0], plan.cornerPoint[1], plan.cornerPoint[2]],
10405
10546
  cornerTolerance: canonicalNumber(plan.cornerTolerance),
10406
10547
  minBranchAngleDeg: canonicalNumber(plan.minBranchAngleDeg)
@@ -10411,13 +10552,9 @@ function cloneShapeCompilePlan(plan) {
10411
10552
  kind: "chamferEdges",
10412
10553
  base: cloneShapeCompilePlan(plan.base),
10413
10554
  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
- }))
10555
+ edgeTargets: (_c2 = plan.edgeTargets) == null ? void 0 : _c2.map(cloneEdgeFeatureTarget),
10556
+ edgeQuery: cloneEdgeFeatureQuery(plan.edgeQuery),
10557
+ edgeSelection: plan.edgeSelection
10421
10558
  };
10422
10559
  break;
10423
10560
  case "draft":
@@ -10437,7 +10574,13 @@ function cloneShapeCompilePlan(plan) {
10437
10574
  };
10438
10575
  break;
10439
10576
  case "importedMesh":
10440
- result = { kind: "importedMesh", filePath: plan.filePath, format: plan.format, fileData: plan.fileData };
10577
+ result = {
10578
+ kind: "importedMesh",
10579
+ filePath: plan.filePath,
10580
+ format: plan.format,
10581
+ ...plan.object ? { object: plan.object } : {},
10582
+ fileData: plan.fileData
10583
+ };
10441
10584
  break;
10442
10585
  case "sdf":
10443
10586
  result = {
@@ -10752,6 +10895,83 @@ function remapToKnotDomain(t, n, degree, knots) {
10752
10895
  const uMax = knots[n];
10753
10896
  return uMin + Math.max(0, Math.min(1, t)) * (uMax - uMin);
10754
10897
  }
10898
+ const EPSILON$1 = 1e-9;
10899
+ function add$2(a2, b) {
10900
+ return [a2[0] + b[0], a2[1] + b[1], a2[2] + b[2]];
10901
+ }
10902
+ function scale$2(v, factor) {
10903
+ return [v[0] * factor, v[1] * factor, v[2] * factor];
10904
+ }
10905
+ function sub$2(a2, b) {
10906
+ return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
10907
+ }
10908
+ function dot$3(a2, b) {
10909
+ return a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2];
10910
+ }
10911
+ function cross$4(a2, b) {
10912
+ 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]];
10913
+ }
10914
+ function rotateAroundAxis(v, axis, angleRad) {
10915
+ const c2 = Math.cos(angleRad);
10916
+ const s = Math.sin(angleRad);
10917
+ const term1 = scale$2(v, c2);
10918
+ const term2 = scale$2(cross$4(axis, v), s);
10919
+ const term3 = scale$2(axis, dot$3(axis, v) * (1 - c2));
10920
+ return add$2(add$2(term1, term2), term3);
10921
+ }
10922
+ function arcPointAt(segment, t) {
10923
+ const sweepRad = segment.sweepDeg * Math.PI / 180 * t;
10924
+ return add$2(segment.center, rotateAroundAxis(sub$2(segment.start, segment.center), segment.axis, sweepRad));
10925
+ }
10926
+ function pushUnique(points, point) {
10927
+ const prev = points[points.length - 1];
10928
+ if (!prev || Math.hypot(prev[0] - point[0], prev[1] - point[1], prev[2] - point[2]) > EPSILON$1) {
10929
+ points.push([point[0], point[1], point[2]]);
10930
+ }
10931
+ }
10932
+ function resolveOptions(options) {
10933
+ if (typeof options === "number") return { samples: Math.max(2, Math.floor(options)), maxAngleDeg: 6 };
10934
+ return {
10935
+ samples: Math.max(2, Math.floor((options == null ? void 0 : options.samples) ?? 48)),
10936
+ maxAngleDeg: Math.max(0.25, (options == null ? void 0 : options.maxAngleDeg) ?? 6)
10937
+ };
10938
+ }
10939
+ function routePointAt(plan, t) {
10940
+ if (plan.segments.length === 0) return [0, 0, 0];
10941
+ const target = Math.max(0, Math.min(1, t)) * plan.length;
10942
+ let station = 0;
10943
+ for (const segment of plan.segments) {
10944
+ const next = station + segment.length;
10945
+ if (target <= next || segment === plan.segments[plan.segments.length - 1]) {
10946
+ const localT = segment.length <= EPSILON$1 ? 1 : Math.max(0, Math.min(1, (target - station) / segment.length));
10947
+ if (segment.kind === "line") return add$2(segment.from, scale$2(sub$2(segment.to, segment.from), localT));
10948
+ return arcPointAt(segment, localT);
10949
+ }
10950
+ station = next;
10951
+ }
10952
+ const last = plan.segments[plan.segments.length - 1];
10953
+ return last.kind === "line" ? last.to : last.end;
10954
+ }
10955
+ function sampleRoute3DCompilePlan(plan, options) {
10956
+ const resolved = resolveOptions(options);
10957
+ const points = [];
10958
+ const totalIntervals = Math.max(1, resolved.samples - 1);
10959
+ for (const segment of plan.segments) {
10960
+ const proportional = plan.length > EPSILON$1 ? Math.ceil(segment.length / plan.length * totalIntervals) : 1;
10961
+ const arcIntervals = segment.kind === "arc" ? Math.ceil(Math.abs(segment.sweepDeg) / resolved.maxAngleDeg) : 1;
10962
+ const intervals = Math.max(1, proportional, arcIntervals);
10963
+ const start = segment.kind === "line" ? segment.from : segment.start;
10964
+ pushUnique(points, start);
10965
+ for (let i = 1; i <= intervals; i += 1) {
10966
+ if (segment.kind === "line") {
10967
+ pushUnique(points, add$2(segment.from, scale$2(sub$2(segment.to, segment.from), i / intervals)));
10968
+ } else {
10969
+ pushUnique(points, arcPointAt(segment, i / intervals));
10970
+ }
10971
+ }
10972
+ }
10973
+ return points;
10974
+ }
10755
10975
  function catmullRom3D$1(p0, p1, p2, p3, t, tension) {
10756
10976
  const tt = t * t;
10757
10977
  const ttt = tt * t;
@@ -10890,6 +11110,8 @@ function evalPathAt(path, t) {
10890
11110
  const u2 = remapToKnotDomain(Math.max(0, Math.min(1, t)), path.controlPoints.length, path.degree, path.knots);
10891
11111
  return deBoor3D(path.controlPoints, path.weights, path.knots, path.degree, u2);
10892
11112
  }
11113
+ case "route3d":
11114
+ return routePointAt(path, t);
10893
11115
  }
10894
11116
  }
10895
11117
  function estimateCurvatureAt(path, t) {
@@ -10936,10 +11158,13 @@ function sweepPathToPolyline(path, samples = 48) {
10936
11158
  }
10937
11159
  return result;
10938
11160
  }
11161
+ case "route3d":
11162
+ return sampleRoute3DCompilePlan(path, samples);
10939
11163
  }
10940
11164
  }
10941
11165
  function sweepPathToPolylineAdaptive(path, baseSamples = 48) {
10942
11166
  if (path.kind === "polyline") return path.points;
11167
+ if (path.kind === "route3d") return sampleRoute3DCompilePlan(path, baseSamples);
10943
11168
  const probeCount = 64;
10944
11169
  const curvatures = [];
10945
11170
  for (let i = 0; i <= probeCount; i++) {
@@ -10970,7 +11195,7 @@ const EPS$2 = 1e-8;
10970
11195
  function midpoint$1(start, end) {
10971
11196
  return [(start[0] + end[0]) * 0.5, (start[1] + end[1]) * 0.5, (start[2] + end[2]) * 0.5];
10972
11197
  }
10973
- function normalize$2(v) {
11198
+ function normalize$3(v) {
10974
11199
  const len = Math.hypot(v[0], v[1], v[2]);
10975
11200
  if (len <= EPS$2) throw new Error("Edge feature selection requires a non-zero direction vector");
10976
11201
  return [v[0] / len, v[1] / len, v[2] / len];
@@ -11389,8 +11614,8 @@ function extrudeEdgeSelection(plan, edgeName) {
11389
11614
  }
11390
11615
  const points = plan.profile.points;
11391
11616
  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]);
11617
+ const u2 = normalize$3([br[0] - bl[0], br[1] - bl[1], 0]);
11618
+ const v = normalize$3([tl[0] - bl[0], tl[1] - bl[1], 0]);
11394
11619
  const vertex = points[index2];
11395
11620
  const quadrant = index2 === 0 ? [1, -1] : index2 === 1 ? [-1, -1] : index2 === 2 ? [-1, 1] : [1, 1];
11396
11621
  return {
@@ -11411,9 +11636,9 @@ function extrudeEdgeSelection(plan, edgeName) {
11411
11636
  function applySelectionTransform(selection, transform) {
11412
11637
  const start = transform.point(selection.start);
11413
11638
  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));
11639
+ const basisX = normalize$3(transform.vector(selection.basisX));
11640
+ const basisY = normalize$3(transform.vector(selection.basisY));
11641
+ const axis = normalize$3(subtract(end, start));
11417
11642
  return {
11418
11643
  kind: "line-segment",
11419
11644
  edgeName: selection.edgeName,
@@ -12688,6 +12913,20 @@ function cleanName(value) {
12688
12913
  const trimmed = value == null ? void 0 : value.replace(/\s+/g, " ").trim();
12689
12914
  return trimmed && trimmed.length > 0 ? trimmed : null;
12690
12915
  }
12916
+ function uniqueNames(baseNames) {
12917
+ const totals = /* @__PURE__ */ new Map();
12918
+ const seen = /* @__PURE__ */ new Map();
12919
+ for (const baseName of baseNames) totals.set(baseName, (totals.get(baseName) ?? 0) + 1);
12920
+ return baseNames.map((baseName) => {
12921
+ if ((totals.get(baseName) ?? 0) <= 1) return baseName;
12922
+ const ordinal = (seen.get(baseName) ?? 0) + 1;
12923
+ seen.set(baseName, ordinal);
12924
+ return `${baseName} #${String(ordinal).padStart(3, "0")}`;
12925
+ });
12926
+ }
12927
+ function normalizeSelector(value) {
12928
+ return value.trim().replace(/\s+/g, " ").toLowerCase();
12929
+ }
12691
12930
  function extractModelXml(data) {
12692
12931
  const zip = unzipSync(new Uint8Array(data));
12693
12932
  const path = Object.keys(zip).find((entry) => entry.toLowerCase() === "3d/3dmodel.model");
@@ -12759,14 +12998,105 @@ function collectObjectGeometry(model, objectId, transforms = [], visiting = /* @
12759
12998
  }
12760
12999
  return { vertices, triangles, bbox };
12761
13000
  }
13001
+ function buildBuildEntries(model) {
13002
+ const buildBaseNames = model.buildItems.map(
13003
+ (item) => {
13004
+ var _a3;
13005
+ return cleanName((_a3 = model.objectsById.get(item.objectId)) == null ? void 0 : _a3.sourceName) ?? `object-${item.objectId}`;
13006
+ }
13007
+ );
13008
+ const buildNames = uniqueNames(buildBaseNames);
13009
+ return model.buildItems.map((item, index2) => {
13010
+ const object = model.objectsById.get(item.objectId);
13011
+ return {
13012
+ stableRef: `3mf:build:${String(index2 + 1).padStart(3, "0")}:object:${item.objectId}`,
13013
+ objectId: item.objectId,
13014
+ autoName: buildNames[index2],
13015
+ sourceName: object == null ? void 0 : object.sourceName,
13016
+ type: object == null ? void 0 : object.type,
13017
+ transform: item.transform
13018
+ };
13019
+ });
13020
+ }
13021
+ function buildObjectEntries(model) {
13022
+ const objectNames = uniqueNames(model.objects.map((object) => cleanName(object.sourceName) ?? `object-${object.id}`));
13023
+ return model.objects.map(
13024
+ (object, index2) => ({
13025
+ stableRef: `3mf:object:${object.id}`,
13026
+ objectId: object.id,
13027
+ autoName: objectNames[index2],
13028
+ sourceName: object.sourceName,
13029
+ type: object.type
13030
+ })
13031
+ );
13032
+ }
13033
+ function buildDefaultSourceEntries(model) {
13034
+ return model.buildItems.length > 0 ? buildBuildEntries(model) : buildObjectEntries(model);
13035
+ }
13036
+ function buildSelectableEntries(model) {
13037
+ return model.buildItems.length > 0 ? [...buildBuildEntries(model), ...buildObjectEntries(model)] : buildObjectEntries(model);
13038
+ }
13039
+ function entryMatchesSelector(entry, selector) {
13040
+ const needle = normalizeSelector(selector);
13041
+ return [
13042
+ entry.stableRef,
13043
+ entry.autoName,
13044
+ entry.sourceName ?? "",
13045
+ entry.objectId,
13046
+ `object-${entry.objectId}`,
13047
+ `3mf:object:${entry.objectId}`
13048
+ ].some((candidate) => normalizeSelector(candidate) === needle);
13049
+ }
13050
+ function availableEntryList(entries) {
13051
+ return entries.map((entry) => `${entry.autoName} [${entry.stableRef}]`).join(", ") || "none";
13052
+ }
13053
+ function selectSourceEntry(entries, selector) {
13054
+ const matches = entries.filter((entry) => entryMatchesSelector(entry, selector));
13055
+ if (matches.length === 1) return matches[0];
13056
+ if (matches.length > 1) {
13057
+ throw new Error(
13058
+ `3MF object selector "${selector}" is ambiguous. Use a stable ref: ${matches.map((entry) => entry.stableRef).join(", ")}`
13059
+ );
13060
+ }
13061
+ throw new Error(`3MF object selector "${selector}" matched no build item or resource object. Available: ${availableEntryList(entries)}`);
13062
+ }
13063
+ function selectorLooksLikeStableRef(selector) {
13064
+ return /^3mf:(build|object):/i.test(selector.trim());
13065
+ }
13066
+ function meshFromGeometry(geometry) {
13067
+ const vertices = [];
13068
+ const triangles = [];
13069
+ for (const vertex of geometry.vertices) vertices.push(vertex[0], vertex[1], vertex[2]);
13070
+ for (const triangle of geometry.triangles) triangles.push(triangle[0], triangle[1], triangle[2]);
13071
+ return {
13072
+ vertProperties: new Float32Array(vertices),
13073
+ triVerts: new Uint32Array(triangles),
13074
+ numProp: 3,
13075
+ mergeFromVert: new Uint32Array(0),
13076
+ mergeToVert: new Uint32Array(0)
13077
+ };
13078
+ }
13079
+ function partFromEntry(model, entry) {
13080
+ const geometry = collectObjectGeometry(model, entry.objectId, [entry.transform]);
13081
+ return {
13082
+ stableRef: entry.stableRef,
13083
+ objectId: entry.objectId,
13084
+ autoName: entry.autoName,
13085
+ sourceName: entry.sourceName,
13086
+ type: entry.type,
13087
+ mesh: meshFromGeometry(geometry),
13088
+ bbox: geometry.bbox,
13089
+ vertexCount: geometry.vertices.length,
13090
+ triangleCount: geometry.triangles.length
13091
+ };
13092
+ }
12762
13093
  function parse3mfMesh(data) {
12763
13094
  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);
13095
+ const entries = buildDefaultSourceEntries(model);
12766
13096
  const vertices = [];
12767
13097
  const triangles = [];
12768
- sourceIds.forEach((objectId, index2) => {
12769
- const geometry = collectObjectGeometry(model, objectId, [transforms[index2]]);
13098
+ entries.forEach((entry) => {
13099
+ const geometry = collectObjectGeometry(model, entry.objectId, [entry.transform]);
12770
13100
  const offset = vertices.length / 3;
12771
13101
  for (const vertex of geometry.vertices) vertices.push(vertex[0], vertex[1], vertex[2]);
12772
13102
  for (const triangle of geometry.triangles) triangles.push(triangle[0] + offset, triangle[1] + offset, triangle[2] + offset);
@@ -12779,6 +13109,11 @@ function parse3mfMesh(data) {
12779
13109
  mergeToVert: new Uint32Array(0)
12780
13110
  };
12781
13111
  }
13112
+ function parse3mfMeshPart(data, selector) {
13113
+ const model = parseModel(data);
13114
+ const entries = selectorLooksLikeStableRef(selector) ? buildSelectableEntries(model) : buildDefaultSourceEntries(model);
13115
+ return partFromEntry(model, selectSourceEntry(entries, selector));
13116
+ }
12782
13117
  function isStlBinary(data) {
12783
13118
  if (data.byteLength < 84) return false;
12784
13119
  const view = new DataView(data);
@@ -12923,17 +13258,17 @@ function parseObj(data) {
12923
13258
  mergeToVert: new Uint32Array(0)
12924
13259
  };
12925
13260
  }
12926
- function parse3mf(data) {
12927
- return parse3mfMesh(data);
13261
+ function parse3mf(data, options = {}) {
13262
+ return options.object ? parse3mfMeshPart(data, options.object).mesh : parse3mfMesh(data);
12928
13263
  }
12929
- function parseMeshFile(data, format) {
13264
+ function parseMeshFile(data, format, options = {}) {
12930
13265
  switch (format) {
12931
13266
  case "stl":
12932
13267
  return parseStl(data);
12933
13268
  case "obj":
12934
13269
  return parseObj(data);
12935
13270
  case "3mf":
12936
- return parse3mf(data);
13271
+ return parse3mf(data, options);
12937
13272
  }
12938
13273
  }
12939
13274
  function cleanZero(value) {
@@ -12960,6 +13295,84 @@ function planeFrameToWorldToPlaneMatrix(frame) {
12960
13295
  1
12961
13296
  ];
12962
13297
  }
13298
+ const DEFAULT_TOLERANCE = 1;
13299
+ const DEFAULT_ANGLE_TOLERANCE = 10;
13300
+ function distSq$1(a2, b) {
13301
+ const dx = a2[0] - b[0];
13302
+ const dy = a2[1] - b[1];
13303
+ const dz = a2[2] - b[2];
13304
+ return dx * dx + dy * dy + dz * dz;
13305
+ }
13306
+ function vecLength$2(v) {
13307
+ return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
13308
+ }
13309
+ function normalize$2(v) {
13310
+ const len = vecLength$2(v);
13311
+ if (len < 1e-12) return [0, 0, 0];
13312
+ return [v[0] / len, v[1] / len, v[2] / len];
13313
+ }
13314
+ function absDot$1(a2, b) {
13315
+ return Math.abs(a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2]);
13316
+ }
13317
+ function applyEdgeQueryFilters(edges, query) {
13318
+ const tol = query.tolerance ?? DEFAULT_TOLERANCE;
13319
+ const angleTol = query.angleTolerance ?? DEFAULT_ANGLE_TOLERANCE;
13320
+ const cosAngleTol = Math.cos(angleTol * Math.PI / 180);
13321
+ let result = edges;
13322
+ if (query.convex === true) {
13323
+ result = result.filter((e) => e.convex);
13324
+ }
13325
+ if (query.concave === true) {
13326
+ result = result.filter((e) => !e.convex);
13327
+ }
13328
+ if (query.minAngle != null) {
13329
+ const min2 = query.minAngle;
13330
+ result = result.filter((e) => e.dihedralAngle >= min2);
13331
+ }
13332
+ if (query.maxAngle != null) {
13333
+ const max2 = query.maxAngle;
13334
+ result = result.filter((e) => e.dihedralAngle <= max2);
13335
+ }
13336
+ if (query.minLength != null) {
13337
+ const min2 = query.minLength;
13338
+ result = result.filter((e) => e.length >= min2);
13339
+ }
13340
+ if (query.maxLength != null) {
13341
+ const max2 = query.maxLength;
13342
+ result = result.filter((e) => e.length <= max2);
13343
+ }
13344
+ if (query.parallel) {
13345
+ const dir = normalize$2(query.parallel);
13346
+ result = result.filter((e) => absDot$1(e.direction, dir) >= cosAngleTol);
13347
+ }
13348
+ if (query.perpendicular) {
13349
+ const dir = normalize$2(query.perpendicular);
13350
+ const sinAngleTol = Math.sin(angleTol * Math.PI / 180);
13351
+ result = result.filter((e) => absDot$1(e.direction, dir) <= sinAngleTol);
13352
+ }
13353
+ if (query.atZ != null) {
13354
+ const z2 = query.atZ;
13355
+ result = result.filter((e) => Math.abs(e.midpoint[2] - z2) <= tol);
13356
+ }
13357
+ if (query.within) {
13358
+ const b = query.within;
13359
+ result = result.filter((e) => {
13360
+ const [mx, my, mz] = e.midpoint;
13361
+ if (b.xMin != null && mx < b.xMin) return false;
13362
+ if (b.xMax != null && mx > b.xMax) return false;
13363
+ if (b.yMin != null && my < b.yMin) return false;
13364
+ if (b.yMax != null && my > b.yMax) return false;
13365
+ if (b.zMin != null && mz < b.zMin) return false;
13366
+ if (b.zMax != null && mz > b.zMax) return false;
13367
+ return true;
13368
+ });
13369
+ }
13370
+ if (query.near) {
13371
+ const pt = query.near;
13372
+ result = result.slice().sort((a2, b) => distSq$1(a2.midpoint, pt) - distSq$1(b.midpoint, pt));
13373
+ }
13374
+ return result;
13375
+ }
12963
13376
  const SHELL_OPEN_FACE_CANONICAL = {
12964
13377
  front: "side-bottom",
12965
13378
  back: "side-top",
@@ -15259,6 +15672,8 @@ function getUnsupportedSdfProgramReason(node) {
15259
15672
  return "noise depends on table-based simplex evaluation";
15260
15673
  case "sdf:voronoi":
15261
15674
  return "voronoi depends on table-based Worley evaluation";
15675
+ case "sdf:circularArray":
15676
+ return "circularArray folds polar coordinates and is not implemented in SdfProgram yet";
15262
15677
  case "sdf:custom":
15263
15678
  return "custom uses a dynamic JavaScript function body";
15264
15679
  case "sdf:polylineSweep":
@@ -16065,6 +16480,8 @@ function sampleSweepPath(path, edgeLengthHint) {
16065
16480
  path,
16066
16481
  edgeLengthHint != null ? Math.max(16, Math.round(path.controlPoints.length * 12 / Math.max(0.1, edgeLengthHint))) : 48
16067
16482
  );
16483
+ case "route3d":
16484
+ return sweepPathToPolyline(path, resolveCurveSampleCount(edgeLengthHint, path.length));
16068
16485
  }
16069
16486
  }
16070
16487
  function clamp(v, lo, hi) {
@@ -17348,6 +17765,7 @@ function rotateVector(v, axis, c2, s) {
17348
17765
  ];
17349
17766
  }
17350
17767
  const OFFSET_SOLID_EPS$1 = 1e-9;
17768
+ const OCCT_BACKEND_REQUIRED_HINT = "Select the OCCT backend in the editor or run the CLI with --backend occt.";
17351
17769
  function disposeWasmObject(value) {
17352
17770
  if (value != null && typeof value.delete === "function") value.delete();
17353
17771
  }
@@ -17832,7 +18250,7 @@ function profilePolygonLoopTopologyMatches(left, right) {
17832
18250
  }
17833
18251
  function lowerOffsetLoftCompilePlan(plan, thickness, wasm) {
17834
18252
  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.");
18253
+ throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
17836
18254
  }
17837
18255
  const heights = [...plan.heights];
17838
18256
  heights[0] -= thickness;
@@ -17843,7 +18261,7 @@ function lowerOffsetLoftCompilePlan(plan, thickness, wasm) {
17843
18261
  const offsetPolygons = plan.profiles.map((profile) => offsetProfilePolygonsForManifold(profile, thickness, wasm));
17844
18262
  const stitched = loftStitched(offsetPolygons, heights, wasm);
17845
18263
  if (!stitched) {
17846
- throw new Error("Offset solid requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
18264
+ throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
17847
18265
  }
17848
18266
  return stitched;
17849
18267
  }
@@ -17883,16 +18301,16 @@ function lowerOffsetSweepCompilePlan(plan, thickness, wasm) {
17883
18301
  const basePolygons = profilePolygonsForManifold(plan.profile, wasm);
17884
18302
  const offsetPolygons = offsetProfilePolygonsForManifold(plan.profile, thickness, wasm);
17885
18303
  if (!profilePolygonLoopTopologyMatches(basePolygons, offsetPolygons)) {
17886
- throw new Error("Offset solid requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
18304
+ throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
17887
18305
  }
17888
18306
  const pathPoints = sweepPathToPolylineAdaptive(plan.path, plan.pathSamples ?? 48);
17889
18307
  const extendedPath = extendedStraightSweepPathForManifold(pathPoints, thickness);
17890
18308
  if (!extendedPath) {
17891
- throw new Error("Offset solid requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
18309
+ throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
17892
18310
  }
17893
18311
  const stitched = sweepStitched(offsetPolygons, extendedPath, [plan.up[0], plan.up[1], plan.up[2]], wasm);
17894
18312
  if (!stitched) {
17895
- throw new Error("Offset solid requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
18313
+ throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
17896
18314
  }
17897
18315
  return stitched;
17898
18316
  }
@@ -17901,7 +18319,7 @@ function lowerTransformedOffsetSolidCompilePlan(plan, thickness, wasm) {
17901
18319
  for (const step of plan.steps) {
17902
18320
  const stepScale = offsetSolidTransformDistanceScaleForManifold(step);
17903
18321
  if (stepScale == null) {
17904
- throw new Error("Offset solid requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
18322
+ throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
17905
18323
  }
17906
18324
  distanceScale *= stepScale;
17907
18325
  }
@@ -17942,7 +18360,7 @@ function lowerOffsetSolidCompilePlan(plan, wasm) {
17942
18360
  }
17943
18361
  const base = verticalPrismOffsetBaseForManifold(basePlan);
17944
18362
  if (!base) {
17945
- throw new Error("Offset solid requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
18363
+ throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
17946
18364
  }
17947
18365
  const zMin = base.zMin - plan.thickness;
17948
18366
  const zMax = base.zMax + plan.thickness;
@@ -18467,6 +18885,18 @@ function matchEdgeSegmentByMidpoint(segments, target) {
18467
18885
  }
18468
18886
  return best;
18469
18887
  }
18888
+ function selectEdgeSegmentsForFeature(segments, plan) {
18889
+ if (plan.edgeQuery) {
18890
+ const selected = applyEdgeQueryFilters(segments, plan.edgeQuery);
18891
+ return plan.edgeSelection === "first" ? selected.slice(0, 1) : selected;
18892
+ }
18893
+ const matched = [];
18894
+ for (const target of plan.edgeTargets ?? []) {
18895
+ const seg = matchEdgeSegmentByMidpoint(segments, target);
18896
+ if (seg && seg.length >= 1e-6) matched.push(seg);
18897
+ }
18898
+ return matched;
18899
+ }
18470
18900
  function lowerFilletEdgesCompilePlan(plan, wasm) {
18471
18901
  let manifold = lowerShapeCompilePlanToManifold(plan.base, wasm);
18472
18902
  const mesh = manifold.getMesh();
@@ -18476,11 +18906,8 @@ function lowerFilletEdgesCompilePlan(plan, wasm) {
18476
18906
  triVerts: mesh.triVerts,
18477
18907
  vertProperties: mesh.vertProperties
18478
18908
  });
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
- }
18909
+ const matched = selectEdgeSegmentsForFeature(segments, plan);
18910
+ if (plan.edgeQuery && matched.length === 0) throw new Error("filletEdges(): no edges match the deferred edge query.");
18484
18911
  matched.sort((a2, b) => b.length - a2.length);
18485
18912
  for (const seg of matched) {
18486
18913
  try {
@@ -18503,11 +18930,8 @@ function lowerChamferEdgesCompilePlan(plan, wasm) {
18503
18930
  triVerts: mesh.triVerts,
18504
18931
  vertProperties: mesh.vertProperties
18505
18932
  });
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
- }
18933
+ const matched = selectEdgeSegmentsForFeature(segments, plan);
18934
+ if (plan.edgeQuery && matched.length === 0) throw new Error("chamferEdges(): no edges match the deferred edge query.");
18511
18935
  matched.sort((a2, b) => b.length - a2.length);
18512
18936
  for (const seg of matched) {
18513
18937
  try {
@@ -18609,20 +19033,20 @@ function lowerShapeCompilePlanToManifold(plan, wasm) {
18609
19033
  case "filletEdges":
18610
19034
  return lowerFilletEdgesCompilePlan(plan, wasm);
18611
19035
  case "cornerYBlend":
18612
- throw new Error("Blend.CornerY() requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
19036
+ throw new Error(`Blend.CornerY() requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
18613
19037
  case "chamferEdges":
18614
19038
  return lowerChamferEdgesCompilePlan(plan, wasm);
18615
19039
  case "draft": {
18616
19040
  const lowered = lowerDraftShapeCompilePlanToLoftPlan(plan);
18617
19041
  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.");
19042
+ throw new Error(`Draft angle requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
18619
19043
  }
18620
19044
  case "offsetSolid":
18621
19045
  return lowerOffsetSolidCompilePlan(plan, wasm);
18622
19046
  case "trimByPlane":
18623
19047
  return lowerShapeTrimByPlaneCompilePlan(plan, wasm);
18624
19048
  case "importedMesh":
18625
- return lowerImportedMeshToManifold(plan.fileData, plan.format, plan.filePath, wasm);
19049
+ return lowerImportedMeshToManifold(plan.fileData, plan.format, plan.filePath, wasm, plan.object);
18626
19050
  case "sdf": {
18627
19051
  const evaluator = compileSdfMaterializationEvaluator3(plan.tree);
18628
19052
  return lowerSdfToManifold(
@@ -18646,9 +19070,9 @@ function lowerShapeCompilePlanToManifold(plan, wasm) {
18646
19070
  case "surfaceExtend":
18647
19071
  case "surfaceThicken":
18648
19072
  case "surfaceSolid":
18649
- throw new Error("Exact surfacing operations require the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
19073
+ throw new Error(`Exact surfacing operations require the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
18650
19074
  case "importedStep":
18651
- throw new Error(`importStep("${plan.filePath}") requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.`);
19075
+ throw new Error(`importStep("${plan.filePath}") requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
18652
19076
  default:
18653
19077
  assertExhaustive(plan);
18654
19078
  }
@@ -18789,11 +19213,11 @@ function projectVerticesToSurfaceWithNormals(vertProperties, sdfFn, out6) {
18789
19213
  function lowerNurbsSurfaceToManifold(plan, wasm) {
18790
19214
  if (!plan.allowApproximation && plan.thickness === 0) {
18791
19215
  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."
19216
+ `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
19217
  );
18794
19218
  }
18795
19219
  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.");
19220
+ throw new Error(`The Manifold backend cannot represent open sheet surfaces. ${OCCT_BACKEND_REQUIRED_HINT}`);
18797
19221
  }
18798
19222
  const surface = new NurbsSurface(plan.controlGrid, {
18799
19223
  degreeU: plan.degreeU,
@@ -18943,8 +19367,8 @@ function lowerTrimmedNurbsSurfaceToManifold(surface, plan, wasm) {
18943
19367
  disposeWasmObject(mesh);
18944
19368
  }
18945
19369
  }
18946
- function lowerImportedMeshToManifold(fileData, format, filePath, wasm) {
18947
- const parsed = parseMeshFile(fileData, format);
19370
+ function lowerImportedMeshToManifold(fileData, format, filePath, wasm, object) {
19371
+ const parsed = parseMeshFile(fileData, format, { object });
18948
19372
  if (parsed.triVerts.length === 0) {
18949
19373
  throw new Error(`importMesh("${filePath}"): file contains no triangles`);
18950
19374
  }
@@ -18966,7 +19390,11 @@ function lowerImportedMeshToManifold(fileData, format, filePath, wasm) {
18966
19390
  }
18967
19391
  }
18968
19392
  function lowerShapeCompilePlanToShapeBackend(plan, wasm) {
18969
- return lowerShapeBackendWithCache("manifold", plan, (basePlan) => wrapManifoldShapeBackend(lowerShapeCompilePlanToManifold(basePlan, wasm)));
19393
+ return lowerShapeBackendWithCache(
19394
+ "manifold",
19395
+ plan,
19396
+ (basePlan) => wrapManifoldShapeBackend(lowerShapeCompilePlanToManifold(basePlan, wasm))
19397
+ );
18970
19398
  }
18971
19399
  const PROFILE_BACKEND_MARKER = Symbol.for("forgecad.profileBackend");
18972
19400
  function occtProfileOffsetJoinType(oc, join) {
@@ -18981,6 +19409,7 @@ function occtProfileOffsetJoinType(oc, join) {
18981
19409
  }
18982
19410
  const BSPLINE_WIRE_THRESHOLD = 20;
18983
19411
  const OFFSET_SOLID_EPS = 1e-8;
19412
+ const MANIFOLD_BACKEND_REQUIRED_HINT = "Select the Manifold backend in the editor or run the CLI with --backend manifold.";
18984
19413
  function transformProfileRadiusPoint(point, step) {
18985
19414
  switch (step.kind) {
18986
19415
  case "translate":
@@ -19686,6 +20115,31 @@ function findOCCTEdgeByMidpoint(oc, shape, midpoint2) {
19686
20115
  }
19687
20116
  return bestEdge;
19688
20117
  }
20118
+ function edgeSegmentToTarget(segment) {
20119
+ return {
20120
+ midpoint: [segment.midpoint[0], segment.midpoint[1], segment.midpoint[2]],
20121
+ start: [segment.start[0], segment.start[1], segment.start[2]],
20122
+ end: [segment.end[0], segment.end[1], segment.end[2]],
20123
+ convex: segment.convex,
20124
+ ...segment.nativeTopology ? { nativeTopology: segment.nativeTopology } : {}
20125
+ };
20126
+ }
20127
+ function selectOCCTEdgeFeatureTargets(base, plan) {
20128
+ if (!plan.edgeQuery) return plan.edgeTargets ?? [];
20129
+ const mesh = wrapOCCTShapeBackend(base).getMesh();
20130
+ const selected = applyEdgeQueryFilters(
20131
+ extractEdgeSegments({
20132
+ numProp: mesh.numProp,
20133
+ numTri: mesh.numTri,
20134
+ triVerts: mesh.triVerts,
20135
+ vertProperties: mesh.vertProperties,
20136
+ mergeFromVert: mesh.mergeFromVert,
20137
+ mergeToVert: mesh.mergeToVert
20138
+ }),
20139
+ plan.edgeQuery
20140
+ );
20141
+ return (plan.edgeSelection === "first" ? selected.slice(0, 1) : selected).map(edgeSegmentToTarget);
20142
+ }
19689
20143
  function occtPointDistance(point, target) {
19690
20144
  return Math.hypot(point.X() - target[0], point.Y() - target[1], point.Z() - target[2]);
19691
20145
  }
@@ -19705,7 +20159,7 @@ function lowerFilletEdgesPlan$1(oc, plan) {
19705
20159
  mkFillet.SetContinuity(mapSurfaceContinuityToOcct(oc, plan.continuity), 2 * Math.PI / 180);
19706
20160
  }
19707
20161
  let addedCount = 0;
19708
- for (const target of plan.edgeTargets) {
20162
+ for (const target of selectOCCTEdgeFeatureTargets(base, plan)) {
19709
20163
  const matchedEdge = findOCCTEdgeByMidpoint(oc, base, target.midpoint);
19710
20164
  if (matchedEdge) {
19711
20165
  mkFillet.Add_2(plan.radius, matchedEdge);
@@ -19752,7 +20206,7 @@ function lowerChamferEdgesPlan$1(oc, plan) {
19752
20206
  const base = lowerShapeCompilePlanToOCCT(plan.base, oc);
19753
20207
  const mkChamfer = new oc.BRepFilletAPI_MakeChamfer(base);
19754
20208
  let addedCount = 0;
19755
- for (const target of plan.edgeTargets) {
20209
+ for (const target of selectOCCTEdgeFeatureTargets(base, plan)) {
19756
20210
  const matchedEdge = findOCCTEdgeByMidpoint(oc, base, target.midpoint);
19757
20211
  if (matchedEdge) {
19758
20212
  mkChamfer.Add_2(plan.size, matchedEdge);
@@ -20039,7 +20493,7 @@ function _lowerShapeCompilePlanToOCCTInner(plan, oc) {
20039
20493
  `importMesh("${plan.filePath}") is not supported with the OCCT backend. Switch to the Manifold backend or use the default backend.`
20040
20494
  );
20041
20495
  case "sdf":
20042
- throw new Error("SDF shapes require the Manifold backend. Add setActiveBackend('manifold') at the top of your script.");
20496
+ throw new Error(`SDF shapes require the Manifold backend. ${MANIFOLD_BACKEND_REQUIRED_HINT}`);
20043
20497
  case "fromSlices":
20044
20498
  return lowerFromSlicesPlan$1(oc, plan);
20045
20499
  case "nurbsSurface":
@@ -20211,6 +20665,7 @@ function buildBSplineCurveHandleFromSweepPathPlan(oc, path, context, allowApprox
20211
20665
  case "catmull-rom":
20212
20666
  case "hermite":
20213
20667
  case "quintic-hermite":
20668
+ case "route3d":
20214
20669
  if (allowApproximation) {
20215
20670
  return buildApproximateBSplineCurveHandle(oc, sweepPathToPolyline(path, 96), context);
20216
20671
  }
@@ -20591,27 +21046,166 @@ function lowerLoftPlan$1(oc, plan) {
20591
21046
  }
20592
21047
  return ts.Shape();
20593
21048
  }
20594
- function buildZToNormalTransform(oc, normal) {
21049
+ function fromSlicesPlaneFrameForOCCT(normalInput) {
21050
+ const normal = normalizeVec3$1(normalInput);
21051
+ if (!normal) throw new Error("Shape.fromSlices group normal must be non-zero");
20595
21052
  const [nx, ny, nz] = normal;
21053
+ if (Math.abs(nz) > 1 - 1e-8) {
21054
+ return { u: [1, 0, 0], v: [0, nz > 0 ? 1 : -1, 0], normal };
21055
+ }
21056
+ if (Math.abs(ny) > 1 - 1e-8) {
21057
+ return { u: [1, 0, 0], v: [0, 0, ny > 0 ? 1 : -1], normal };
21058
+ }
21059
+ if (Math.abs(nx) > 1 - 1e-8) {
21060
+ return { u: [0, 1, 0], v: [0, 0, nx > 0 ? 1 : -1], normal };
21061
+ }
21062
+ const reference = Math.abs(nx) < 0.9 ? [1, 0, 0] : [0, 1, 0];
21063
+ const u2 = normalizeVec3$1(crossVec3$1(reference, normal));
21064
+ if (!u2) throw new Error("Shape.fromSlices profile u axis is invalid");
21065
+ return { u: u2, v: crossVec3$1(normal, u2), normal };
21066
+ }
21067
+ function buildFromSlicesLocalToWorldTransform(oc, normal) {
21068
+ const frame = fromSlicesPlaneFrameForOCCT(normal);
20596
21069
  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);
21070
+ trsf.SetValues(
21071
+ frame.u[0],
21072
+ frame.v[0],
21073
+ frame.normal[0],
21074
+ 0,
21075
+ frame.u[1],
21076
+ frame.v[1],
21077
+ frame.normal[1],
21078
+ 0,
21079
+ frame.u[2],
21080
+ frame.v[2],
21081
+ frame.normal[2],
21082
+ 0
21083
+ );
20609
21084
  return trsf;
20610
21085
  }
21086
+ function axisAlignedDirectionForOCCT(vector) {
21087
+ let bestAxis = 0;
21088
+ let best = Math.abs(vector[0]);
21089
+ for (const axis of [1, 2]) {
21090
+ const value = Math.abs(vector[axis]);
21091
+ if (value > best) {
21092
+ best = value;
21093
+ bestAxis = axis;
21094
+ }
21095
+ }
21096
+ if (best < 1 - 1e-8) return null;
21097
+ return { axis: bestAxis, sign: vector[bestAxis] < 0 ? -1 : 1 };
21098
+ }
21099
+ function centeredEllipseProfileRadiiForOCCT(plan) {
21100
+ if (plan.kind !== "circle") return null;
21101
+ let rx = Math.abs(plan.radius);
21102
+ let ry = Math.abs(plan.radius);
21103
+ for (const transform of plan.transforms) {
21104
+ if (transform.kind !== "scale") return null;
21105
+ rx *= Math.abs(transform.x);
21106
+ ry *= Math.abs(transform.y);
21107
+ }
21108
+ return rx > 1e-10 && ry > 1e-10 ? [rx, ry] : null;
21109
+ }
21110
+ function setEllipsoidRadiusForOCCT(radii, axis, radius) {
21111
+ const existing = radii[axis];
21112
+ if (existing == null) {
21113
+ radii[axis] = radius;
21114
+ return true;
21115
+ }
21116
+ const tolerance = Math.max(1e-7, Math.max(Math.abs(existing), Math.abs(radius)) * 1e-7);
21117
+ return Math.abs(existing - radius) <= tolerance;
21118
+ }
21119
+ function buildRationalQuadraticEdgeForOCCT(oc, points, weights) {
21120
+ const poles = new oc.TColgp_Array1OfPnt_2(1, 3);
21121
+ const weightArr = new oc.TColStd_Array1OfReal_2(1, 3);
21122
+ for (let i = 0; i < 3; i++) {
21123
+ const [x2, y2, z2] = points[i];
21124
+ poles.SetValue(i + 1, new oc.gp_Pnt_3(x2, y2, z2));
21125
+ weightArr.SetValue(i + 1, weights[i]);
21126
+ }
21127
+ const knots = new oc.TColStd_Array1OfReal_2(1, 2);
21128
+ knots.SetValue(1, 0);
21129
+ knots.SetValue(2, 1);
21130
+ const mults = new oc.TColStd_Array1OfInteger_2(1, 2);
21131
+ mults.SetValue(1, 3);
21132
+ mults.SetValue(2, 3);
21133
+ const curve = new oc.Geom_BSplineCurve_2(poles, weightArr, knots, mults, 2, false, true);
21134
+ const handle = new oc.Handle_Geom_BSplineCurve_2(curve);
21135
+ return new oc.BRepBuilderAPI_MakeEdge_24(new oc.Handle_Geom_Curve_2(handle.get())).Edge();
21136
+ }
21137
+ function lowerAxisymmetricEllipsoidForOCCT(oc, radius, heightRadius) {
21138
+ const w2 = Math.SQRT1_2;
21139
+ const mkWire = new oc.BRepBuilderAPI_MakeWire_1();
21140
+ mkWire.Add_1(
21141
+ buildRationalQuadraticEdgeForOCCT(
21142
+ oc,
21143
+ [
21144
+ [0, 0, heightRadius],
21145
+ [radius, 0, heightRadius],
21146
+ [radius, 0, 0]
21147
+ ],
21148
+ [1, w2, 1]
21149
+ )
21150
+ );
21151
+ mkWire.Add_1(
21152
+ buildRationalQuadraticEdgeForOCCT(
21153
+ oc,
21154
+ [
21155
+ [radius, 0, 0],
21156
+ [radius, 0, -heightRadius],
21157
+ [0, 0, -heightRadius]
21158
+ ],
21159
+ [1, w2, 1]
21160
+ )
21161
+ );
21162
+ 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());
21163
+ const face = buildFaceFromWire(oc, mkWire.Wire());
21164
+ const axis = new oc.gp_Ax1_2(new oc.gp_Pnt_3(0, 0, 0), new oc.gp_Dir_4(0, 0, 1));
21165
+ const revol = new oc.BRepPrimAPI_MakeRevol_1(face, axis, Math.PI * 2, true);
21166
+ revol.Build(new oc.Message_ProgressRange_1());
21167
+ if (!revol.IsDone()) {
21168
+ throw new Error("Shape.fromSlices exact ellipsoid revolve failed");
21169
+ }
21170
+ return revol.Shape();
21171
+ }
21172
+ function lowerOrthogonalEllipseFromSlicesPlan(oc, plan) {
21173
+ if (plan.groups.length !== 2) return null;
21174
+ const center = [0, 0, 0];
21175
+ const radii = [null, null, null];
21176
+ for (const group of plan.groups) {
21177
+ if (group.slices.length !== 1) return null;
21178
+ const slice = group.slices[0];
21179
+ const profileRadii = centeredEllipseProfileRadiiForOCCT(slice.profile);
21180
+ if (!profileRadii) return null;
21181
+ const frame = fromSlicesPlaneFrameForOCCT(group.normal);
21182
+ const normalAxis = axisAlignedDirectionForOCCT(frame.normal);
21183
+ const uAxis = axisAlignedDirectionForOCCT(frame.u);
21184
+ const vAxis = axisAlignedDirectionForOCCT(frame.v);
21185
+ if (!normalAxis || !uAxis || !vAxis) return null;
21186
+ center[normalAxis.axis] = normalAxis.sign * slice.offset;
21187
+ if (!setEllipsoidRadiusForOCCT(radii, uAxis.axis, profileRadii[0])) return null;
21188
+ if (!setEllipsoidRadiusForOCCT(radii, vAxis.axis, profileRadii[1])) return null;
21189
+ }
21190
+ if (radii.some((radius) => radius == null)) return null;
21191
+ const rx = radii[0];
21192
+ const ry = radii[1];
21193
+ const rz = radii[2];
21194
+ const axisymmetricTolerance = Math.max(1e-7, Math.max(Math.abs(rx), Math.abs(ry)) * 1e-7);
21195
+ if (Math.abs(rx - ry) > axisymmetricTolerance) return null;
21196
+ let shape = lowerAxisymmetricEllipsoidForOCCT(oc, (rx + ry) / 2, rz);
21197
+ if (Math.hypot(center[0], center[1], center[2]) > 1e-10) {
21198
+ const trsf = new oc.gp_Trsf_1();
21199
+ trsf.SetTranslation_1(new oc.gp_Vec_4(center[0], center[1], center[2]));
21200
+ const moved = new oc.BRepBuilderAPI_Transform_2(shape, trsf, true);
21201
+ shape = moved.Shape();
21202
+ }
21203
+ return shape;
21204
+ }
20611
21205
  function lowerFromSlicesGroup(oc, group, singleSliceHalfExtent) {
20612
21206
  const { normal, slices } = group;
20613
21207
  const sorted = [...slices].sort((a2, b) => a2.offset - b.offset);
20614
- const rotTrsf = buildZToNormalTransform(oc, normal);
21208
+ const localToWorldTrsf = buildFromSlicesLocalToWorldTransform(oc, normal);
20615
21209
  if (sorted.length === 1) {
20616
21210
  const s = sorted[0];
20617
21211
  const rawFace = lowerProfileToFace(oc, s.profile);
@@ -20625,7 +21219,7 @@ function lowerFromSlicesGroup(oc, group, singleSliceHalfExtent) {
20625
21219
  centerTrsf.SetTranslation_1(new oc.gp_Vec_4(0, 0, s.offset - extrudeHalf));
20626
21220
  const centered = new oc.BRepBuilderAPI_Transform_2(shape, centerTrsf, true);
20627
21221
  shape = centered.Shape();
20628
- const rotated = new oc.BRepBuilderAPI_Transform_2(shape, rotTrsf, true);
21222
+ const rotated = new oc.BRepBuilderAPI_Transform_2(shape, localToWorldTrsf, true);
20629
21223
  return rotated.Shape();
20630
21224
  }
20631
21225
  const ts = new oc.BRepOffsetAPI_ThruSections(true, false, 1e-6);
@@ -20636,10 +21230,8 @@ function lowerFromSlicesGroup(oc, group, singleSliceHalfExtent) {
20636
21230
  wire = toBSplineWireIfNeeded(oc, wire);
20637
21231
  const placeTrsf = new oc.gp_Trsf_1();
20638
21232
  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);
21233
+ const placed = new oc.BRepBuilderAPI_Transform_2(wire, placeTrsf, true);
21234
+ const transformed = new oc.BRepBuilderAPI_Transform_2(placed.Shape(), localToWorldTrsf, true);
20643
21235
  ts.AddWire(oc.TopoDS.Wire_1(transformed.Shape()));
20644
21236
  }
20645
21237
  ts.CheckCompatibility(true);
@@ -20651,6 +21243,8 @@ function lowerFromSlicesGroup(oc, group, singleSliceHalfExtent) {
20651
21243
  }
20652
21244
  function lowerFromSlicesPlan$1(oc, plan) {
20653
21245
  if (plan.groups.length === 0) throw new Error("Shape.fromSlices requires at least one slice");
21246
+ const exactEllipsoid = lowerOrthogonalEllipseFromSlicesPlan(oc, plan);
21247
+ if (exactEllipsoid) return exactEllipsoid;
20654
21248
  const singleSliceHalfExtent = fromSlicesSingleSliceHalfExtentForOCCT(plan);
20655
21249
  const groupSolids = plan.groups.map((g2) => lowerFromSlicesGroup(oc, g2, singleSliceHalfExtent));
20656
21250
  if (groupSolids.length === 1) return groupSolids[0];
@@ -20677,14 +21271,40 @@ function buildPolylineSpineWire(oc, points) {
20677
21271
  }
20678
21272
  return mkWire.Wire();
20679
21273
  }
21274
+ function buildRoute3DSpineWire(oc, path) {
21275
+ if (path.segments.length === 0) throw new Error("Route3D sweep path needs at least one segment");
21276
+ const mkWire = new oc.BRepBuilderAPI_MakeWire_1();
21277
+ for (const segment of path.segments) {
21278
+ if (segment.kind === "line") {
21279
+ const [x1, y1, z1] = segment.from;
21280
+ const [x2, y2, z2] = segment.to;
21281
+ if (x1 === x2 && y1 === y2 && z1 === z2) continue;
21282
+ 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());
21283
+ continue;
21284
+ }
21285
+ const radial = normalizeVec3$1(subtractVec3(segment.start, segment.center));
21286
+ if (!radial) throw new Error("Route3D sweep arc has a collapsed start radius");
21287
+ const axis = new oc.gp_Ax2_2(
21288
+ new oc.gp_Pnt_3(segment.center[0], segment.center[1], segment.center[2]),
21289
+ new oc.gp_Dir_4(segment.axis[0], segment.axis[1], segment.axis[2]),
21290
+ new oc.gp_Dir_4(radial[0], radial[1], radial[2])
21291
+ );
21292
+ const circle = new oc.gp_Circ_2(axis, segment.radius);
21293
+ mkWire.Add_1(new oc.BRepBuilderAPI_MakeEdge_9(circle, 0, segment.sweepDeg * Math.PI / 180).Edge());
21294
+ }
21295
+ return mkWire.Wire();
21296
+ }
20680
21297
  function buildSpineWireFromPlan(oc, path, pathSamples) {
20681
21298
  switch (path.kind) {
20682
21299
  case "polyline":
20683
21300
  return buildPolylineSpineWire(oc, path.points);
21301
+ case "route3d":
21302
+ return buildRoute3DSpineWire(oc, path);
21303
+ case "nurbs":
21304
+ return new oc.BRepBuilderAPI_MakeWire_2(buildCurveEdgeFromSweepPathPlan(oc, path, "OCCT sweep path")).Wire();
20684
21305
  case "catmull-rom":
20685
21306
  case "hermite":
20686
21307
  case "quintic-hermite":
20687
- case "nurbs":
20688
21308
  return buildPolylineSpineWire(oc, sweepPathToPolyline(path, pathSamples ?? 48));
20689
21309
  }
20690
21310
  }
@@ -28541,7 +29161,7 @@ async function initTruckGeometryWasm() {
28541
29161
  if (_initPromise) return _initPromise;
28542
29162
  _initPromise = (async () => {
28543
29163
  try {
28544
- const geometryModule = await import("./forgecad_geometry-CH2nvuLA.js");
29164
+ const geometryModule = await import("./forgecad_geometry-Dgceylq9.js");
28545
29165
  const isNode = isNodeRuntime();
28546
29166
  if (isNode) {
28547
29167
  const { readFileSync, existsSync } = await import("./__vite-browser-external-Dhvy_jtL.js");
@@ -31348,7 +31968,7 @@ function nurbsSurfaceForPlan(plan) {
31348
31968
  return null;
31349
31969
  }
31350
31970
  function lowerImportedMeshPlan(plan) {
31351
- const parsed = parseMeshFile(plan.fileData, plan.format);
31971
+ const parsed = parseMeshFile(plan.fileData, plan.format, { object: plan.object });
31352
31972
  if (parsed.numProp !== 3) {
31353
31973
  throw new Error(`importMesh("${plan.filePath}"): expected xyz vertex data`);
31354
31974
  }
@@ -31651,12 +32271,18 @@ function applyNativeTruckFilletTargets(initialShape, targets, radius, segments,
31651
32271
  }
31652
32272
  }
31653
32273
  function lowerFilletEdgesPlan(plan) {
32274
+ if (plan.edgeQuery) {
32275
+ throw new Error("filletEdges(): deferred edge queries are not supported by the Truck lowerer yet.");
32276
+ }
31654
32277
  const shape = lowerShapeCompilePlanToTruckBackend(plan.base);
31655
- return applyNativeTruckFilletTargets(shape, plan.edgeTargets, plan.radius, plan.segments, "filletEdges()");
32278
+ return applyNativeTruckFilletTargets(shape, plan.edgeTargets ?? [], plan.radius, plan.segments, "filletEdges()");
31656
32279
  }
31657
32280
  function lowerChamferEdgesPlan(plan) {
32281
+ if (plan.edgeQuery) {
32282
+ throw new Error("chamferEdges(): deferred edge queries are not supported by the Truck lowerer yet.");
32283
+ }
31658
32284
  const shape = lowerShapeCompilePlanToTruckBackend(plan.base);
31659
- return applyNativeTruckChamferTargets(shape, plan.edgeTargets, plan.size, "chamferEdges()");
32285
+ return applyNativeTruckChamferTargets(shape, plan.edgeTargets ?? [], plan.size, "chamferEdges()");
31660
32286
  }
31661
32287
  function resolvedEdgeFeatureSelectionToTarget(selection) {
31662
32288
  return {
@@ -37673,8 +38299,8 @@ function resolveShapeFace(plan, name) {
37673
38299
  const DEPRECATED_SIDE_NAMES = {
37674
38300
  "side-left": "left",
37675
38301
  "side-right": "right",
37676
- "side-top": "front",
37677
- "side-bottom": "back"
38302
+ "side-top": "back",
38303
+ "side-bottom": "front"
37678
38304
  };
37679
38305
  function explainMissingShapeFace(plan, name) {
37680
38306
  const table = resolveShapeFaceTable(plan);
@@ -38599,11 +39225,14 @@ class ShapeGroup {
38599
39225
  }
38600
39226
  /** Move the entire group by (x, y, z). All children move together as a unit. */
38601
39227
  translate(x2, y2, z2) {
38602
- const matrix = Transform.translation(x2, y2, z2).toArray();
39228
+ const dx = requireFiniteNumber(x2, "ShapeGroup.translate() x");
39229
+ const dy = requireFiniteNumber(y2, "ShapeGroup.translate() y");
39230
+ const dz = requireFiniteNumber(z2, "ShapeGroup.translate() z");
39231
+ const matrix = Transform.translation(dx, dy, dz).toArray();
38603
39232
  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);
39233
+ if (c2 instanceof ShapeGroup) return c2.translate(dx, dy, dz);
39234
+ if (c2 instanceof Shape) return c2.translate(dx, dy, dz);
39235
+ return c2.translate(dx, dy);
38607
39236
  }, matrix);
38608
39237
  }
38609
39238
  /** Compute combined bounding box of all 3D children */
@@ -38639,17 +39268,23 @@ class ShapeGroup {
38639
39268
  return { min: bb.min, max: bb.max };
38640
39269
  }
38641
39270
  resolveRotatePoint(point) {
38642
- if (Array.isArray(point)) return [point[0], point[1], point[2]];
39271
+ if (Array.isArray(point)) return requireFiniteVec3$1(point, "ShapeGroup.rotateAroundTo() point");
38643
39272
  const bb = this._bbox();
38644
39273
  return resolveAnchor3D(bb.min, bb.max, point);
38645
39274
  }
38646
39275
  /** Move the group so its bounding-box min corner lands at the given coordinate. */
38647
39276
  moveTo(x2, y2, z2) {
39277
+ const targetX = requireFiniteNumber(x2, "ShapeGroup.moveTo() x");
39278
+ const targetY = requireFiniteNumber(y2, "ShapeGroup.moveTo() y");
39279
+ const targetZ = requireFiniteNumber(z2, "ShapeGroup.moveTo() z");
38648
39280
  const bb = this._bbox();
38649
- return this.translate(x2 - bb.min[0], y2 - bb.min[1], z2 - bb.min[2]);
39281
+ return this.translate(targetX - bb.min[0], targetY - bb.min[1], targetZ - bb.min[2]);
38650
39282
  }
38651
39283
  /** Move the group relative to another part's bounding-box min corner. */
38652
39284
  moveToLocal(target, x2, y2, z2) {
39285
+ const localX = requireFiniteNumber(x2, "ShapeGroup.moveToLocal() x");
39286
+ const localY = requireFiniteNumber(y2, "ShapeGroup.moveToLocal() y");
39287
+ const localZ = requireFiniteNumber(z2, "ShapeGroup.moveToLocal() z");
38653
39288
  let tbb;
38654
39289
  if (target instanceof ShapeGroup) {
38655
39290
  tbb = target._bbox();
@@ -38657,7 +39292,7 @@ class ShapeGroup {
38657
39292
  const bb = target.boundingBox();
38658
39293
  tbb = { min: bb.min };
38659
39294
  }
38660
- return this.moveTo(tbb.min[0] + x2, tbb.min[1] + y2, tbb.min[2] + z2);
39295
+ return this.moveTo(tbb.min[0] + localX, tbb.min[1] + localY, tbb.min[2] + localZ);
38661
39296
  }
38662
39297
  /**
38663
39298
  * Attach this group to a face or anchor on another part.
@@ -38686,9 +39321,10 @@ class ShapeGroup {
38686
39321
  const sp = resolveAnchor3D(sbb.min, sbb.max, selfAnchor);
38687
39322
  let dx = tp[0] - sp[0], dy = tp[1] - sp[1], dz = tp[2] - sp[2];
38688
39323
  if (offset) {
38689
- dx += offset[0];
38690
- dy += offset[1];
38691
- dz += offset[2];
39324
+ const offsetPoint = requireFiniteVec3$1(offset, "ShapeGroup.attachTo() offset");
39325
+ dx += offsetPoint[0];
39326
+ dy += offsetPoint[1];
39327
+ dz += offsetPoint[2];
38692
39328
  }
38693
39329
  return this.translate(dx, dy, dz);
38694
39330
  }
@@ -38697,7 +39333,9 @@ class ShapeGroup {
38697
39333
  * See Shape.onFace() for full documentation.
38698
39334
  */
38699
39335
  onFace(parent, face, opts = {}) {
38700
- const u2 = opts.u ?? 0, v = opts.v ?? 0, p2 = opts.protrude ?? 0;
39336
+ const u2 = requireFiniteNumber(opts.u ?? 0, "ShapeGroup.onFace() u");
39337
+ const v = requireFiniteNumber(opts.v ?? 0, "ShapeGroup.onFace() v");
39338
+ const p2 = requireFiniteNumber(opts.protrude ?? 0, "ShapeGroup.onFace() protrude");
38701
39339
  const opp = { front: "back", back: "front", left: "right", right: "left", top: "bottom", bottom: "top" };
38702
39340
  const uvMap = {
38703
39341
  front: (u22, v2, p22) => [u22, -p22, v2],
@@ -38736,20 +39374,31 @@ class ShapeGroup {
38736
39374
  }
38737
39375
  /** Rotate around an arbitrary axis, optionally through a pivot point. */
38738
39376
  rotateAroundAxis(axis, angleDeg, pivot = [0, 0, 0]) {
38739
- return this.transform(Transform.rotationAxis(axis, angleDeg, pivot));
39377
+ const rotateAxis = requireNonZeroFiniteVec3(axis, "ShapeGroup.rotateAroundAxis() axis");
39378
+ const degrees = requireFiniteNumber(angleDeg, "ShapeGroup.rotateAroundAxis() angleDeg");
39379
+ const rotatePivot = requireFiniteVec3$1(pivot, "ShapeGroup.rotateAroundAxis() pivot");
39380
+ return this.transform(Transform.rotationAxis(rotateAxis, degrees, rotatePivot));
38740
39381
  }
38741
39382
  /**
38742
39383
  * Rotate around an axis until a moving point reaches the target line/plane defined by the axis and target point.
38743
39384
  * ShapeGroup string points use built-in anchors only.
38744
39385
  */
38745
39386
  rotateAroundTo(axis, pivot, movingPoint, targetPoint, options = {}) {
39387
+ const rotateAxis = requireNonZeroFiniteVec3(axis, "ShapeGroup.rotateAroundTo() axis");
39388
+ const rotatePivot = requireFiniteVec3$1(pivot, "ShapeGroup.rotateAroundTo() pivot");
38746
39389
  return this.transform(
38747
- Transform.rotateAroundTo(axis, pivot, this.resolveRotatePoint(movingPoint), this.resolveRotatePoint(targetPoint), options)
39390
+ Transform.rotateAroundTo(
39391
+ rotateAxis,
39392
+ rotatePivot,
39393
+ this.resolveRotatePoint(movingPoint),
39394
+ this.resolveRotatePoint(targetPoint),
39395
+ options
39396
+ )
38748
39397
  );
38749
39398
  }
38750
39399
  /** Reorient the group so its local Z axis points along `direction`. */
38751
39400
  pointAlong(direction) {
38752
- const [dx, dy, dz] = direction;
39401
+ const [dx, dy, dz] = requireNonZeroFiniteVec3(direction, "ShapeGroup.pointAlong() direction");
38753
39402
  const len = Math.sqrt(dx * dx + dy * dy + dz * dz) || 1;
38754
39403
  const nx = dx / len, ny = dy / len, nz = dz / len;
38755
39404
  const cx = -ny, cy = nx, cz = 0;
@@ -38764,11 +39413,11 @@ class ShapeGroup {
38764
39413
  }
38765
39414
  /** Apply a 4x4 transform matrix or `Transform` to all 3D children. */
38766
39415
  transform(m2) {
38767
- const matrix = m2 instanceof Transform ? m2.toArray() : m2;
39416
+ const matrix = m2 instanceof Transform ? m2.toArray() : requireFiniteMat4(m2, "ShapeGroup.transform() matrix");
38768
39417
  const next = new ShapeGroup(
38769
39418
  this.children.map((c2) => {
38770
- if (c2 instanceof ShapeGroup) return c2.transform(m2);
38771
- if (c2 instanceof Shape) return c2.transform(m2);
39419
+ if (c2 instanceof ShapeGroup) return c2.transform(matrix);
39420
+ if (c2 instanceof Shape) return c2.transform(matrix);
38772
39421
  throw new Error(
38773
39422
  "ShapeGroup.transform only supports 3D children (Shape/ShapeGroup). For Sketch children, use 2D transforms (translate/rotate/scale/mirror)."
38774
39423
  );
@@ -38781,25 +39430,28 @@ class ShapeGroup {
38781
39430
  }
38782
39431
  /** Scale uniformly or per-axis from the group's bounding-box center. */
38783
39432
  scale(v) {
38784
- return this.scaleAround(this._bboxCenter(), v);
39433
+ const scale2 = requireNonZeroFiniteScale3(v, "ShapeGroup.scale() scale");
39434
+ return this.scaleAround(this._bboxCenter(), scale2);
38785
39435
  }
38786
39436
  /** Scale uniformly or per-axis from an explicit pivot point. */
38787
39437
  scaleAround(pivot, v) {
38788
- const matrix = Transform.scale(v).toArray();
38789
- if (pivot[0] === 0 && pivot[1] === 0 && pivot[2] === 0) {
39438
+ const scale2 = requireNonZeroFiniteScale3(v, "ShapeGroup.scaleAround() scale");
39439
+ const scalePivot = requireFiniteVec3$1(pivot, "ShapeGroup.scaleAround() pivot");
39440
+ const matrix = Transform.scale(scale2).toArray();
39441
+ if (scalePivot[0] === 0 && scalePivot[1] === 0 && scalePivot[2] === 0) {
38790
39442
  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]]);
39443
+ if (c2 instanceof ShapeGroup) return c2.scaleAround([0, 0, 0], scale2);
39444
+ if (c2 instanceof Shape) return c2.scaleAround([0, 0, 0], scale2);
39445
+ return c2.scale([scale2[0], scale2[1]]);
38794
39446
  }, matrix);
38795
39447
  }
38796
- const moved = this.translate(-pivot[0], -pivot[1], -pivot[2]);
39448
+ const moved = this.translate(-scalePivot[0], -scalePivot[1], -scalePivot[2]);
38797
39449
  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]]);
39450
+ if (c2 instanceof ShapeGroup) return c2.scaleAround([0, 0, 0], scale2);
39451
+ if (c2 instanceof Shape) return c2.scaleAround([0, 0, 0], scale2);
39452
+ return c2.scale([scale2[0], scale2[1]]);
38801
39453
  }, matrix);
38802
- return scaled.translate(pivot[0], pivot[1], pivot[2]);
39454
+ return scaled.translate(scalePivot[0], scalePivot[1], scalePivot[2]);
38803
39455
  }
38804
39456
  /** Mirror across a plane through the group's bounding-box center. */
38805
39457
  mirror(normal) {
@@ -38807,21 +39459,23 @@ class ShapeGroup {
38807
39459
  }
38808
39460
  /** Mirror across a plane through an explicit point. */
38809
39461
  mirrorThrough(point, normal) {
38810
- const matrix = mirrorPlaneMatrix(normal);
38811
- if (point[0] === 0 && point[1] === 0 && point[2] === 0) {
39462
+ const mirrorPoint = requireFiniteVec3$1(point, "ShapeGroup.mirrorThrough() point");
39463
+ const mirrorNormal = requireNonZeroFiniteVec3(normal, "ShapeGroup.mirrorThrough() normal");
39464
+ const matrix = mirrorPlaneMatrix(mirrorNormal);
39465
+ if (mirrorPoint[0] === 0 && mirrorPoint[1] === 0 && mirrorPoint[2] === 0) {
38812
39466
  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]]);
39467
+ if (c2 instanceof ShapeGroup) return c2.mirrorThrough([0, 0, 0], mirrorNormal);
39468
+ if (c2 instanceof Shape) return c2.mirrorThrough([0, 0, 0], mirrorNormal);
39469
+ return c2.mirror([mirrorNormal[0], mirrorNormal[1]]);
38816
39470
  }, matrix);
38817
39471
  }
38818
- const moved = this.translate(-point[0], -point[1], -point[2]);
39472
+ const moved = this.translate(-mirrorPoint[0], -mirrorPoint[1], -mirrorPoint[2]);
38819
39473
  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]]);
39474
+ if (c2 instanceof ShapeGroup) return c2.mirrorThrough([0, 0, 0], mirrorNormal);
39475
+ if (c2 instanceof Shape) return c2.mirrorThrough([0, 0, 0], mirrorNormal);
39476
+ return c2.mirror([mirrorNormal[0], mirrorNormal[1]]);
38823
39477
  }, matrix);
38824
- return mirrored.translate(point[0], point[1], point[2]);
39478
+ return mirrored.translate(mirrorPoint[0], mirrorPoint[1], mirrorPoint[2]);
38825
39479
  }
38826
39480
  /** Return a copy of the group with the given display color applied to each child. */
38827
39481
  color(hex) {
@@ -38893,14 +39547,16 @@ class ShapeGroup {
38893
39547
  * ```
38894
39548
  */
38895
39549
  placeReference(ref, target, offset) {
39550
+ const targetPoint = requireFiniteVec3$1(target, "ShapeGroup.placeReference() target");
39551
+ const offsetPoint = offset === void 0 ? void 0 : requireFiniteVec3$1(offset, "ShapeGroup.placeReference() offset");
38896
39552
  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];
39553
+ let dx = targetPoint[0] - sourcePoint[0];
39554
+ let dy = targetPoint[1] - sourcePoint[1];
39555
+ let dz = targetPoint[2] - sourcePoint[2];
39556
+ if (offsetPoint) {
39557
+ dx += offsetPoint[0];
39558
+ dy += offsetPoint[1];
39559
+ dz += offsetPoint[2];
38904
39560
  }
38905
39561
  return this.translate(dx, dy, dz);
38906
39562
  }
@@ -39832,14 +40488,21 @@ function attachTopologyRewritePropagation(plan, propagation) {
39832
40488
  }
39833
40489
  return cloneNodeWithPropagation(plan, propagation);
39834
40490
  }
40491
+ const DEFERRED_EDGE_SELECTION_MARKER = Symbol.for("forgecad.deferredEdgeSelection");
40492
+ function isDeferredEdgeSelection(value) {
40493
+ return Boolean(value && typeof value === "object" && value[DEFERRED_EDGE_SELECTION_MARKER] === true);
40494
+ }
39835
40495
  function distSq(a2, b) {
39836
- const dx = a2[0] - b[0], dy = a2[1] - b[1], dz = a2[2] - b[2];
40496
+ const dx = a2[0] - b[0];
40497
+ const dy = a2[1] - b[1];
40498
+ const dz = a2[2] - b[2];
39837
40499
  return dx * dx + dy * dy + dz * dz;
39838
40500
  }
39839
40501
  function absDot(a2, b) {
39840
40502
  return Math.abs(a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2]);
39841
40503
  }
39842
40504
  function coalesceEdges(segments, tolerance = 0.01) {
40505
+ if (segments.some(isDeferredEdgeSelection)) return segments;
39843
40506
  if (segments.length <= 1) return segments;
39844
40507
  const used = new Uint8Array(segments.length);
39845
40508
  const result = [];
@@ -40562,16 +41225,6 @@ function mirrorMatrix(normal) {
40562
41225
  const m22 = 1 - 2 * nz * nz;
40563
41226
  return [m00, m10, m20, 0, m01, m11, m21, 0, m02, m12, m22, 0, 0, 0, 0, 1];
40564
41227
  }
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
41228
  function dotVec3$1(a2, b) {
40576
41229
  return a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2];
40577
41230
  }
@@ -41660,7 +42313,7 @@ function withBaseDimensionsAndMergedSourceSpans(base, sources, out, preserveOutp
41660
42313
  return result;
41661
42314
  }
41662
42315
  function resolveRotationPoint(shape, point) {
41663
- if (Array.isArray(point)) return [point[0], point[1], point[2]];
42316
+ if (Array.isArray(point)) return requireFiniteVec3$1(point, "rotateAroundTo(): point");
41664
42317
  return shape.referencePoint(point);
41665
42318
  }
41666
42319
  function setShapePlacementReferences(shape, refs, options = {}) {
@@ -42388,7 +43041,9 @@ class Shape {
42388
43041
  edgesOf(faceLabel, options) {
42389
43042
  const faceRefs = this._resolveFaceLabels(faceLabel);
42390
43043
  const result = uniqueEdgeSegments(
42391
- faceRefs.flatMap((faceRef) => edgesOfFace(this, faceRef, options, (name) => this._resolveFaceLabel(name)))
43044
+ faceRefs.flatMap((faceRef) => {
43045
+ return edgesOfFace(this, faceRef, options, (name) => this._resolveFaceLabel(name));
43046
+ })
42392
43047
  );
42393
43048
  if (result.length === 0) {
42394
43049
  throw new Error(
@@ -42436,7 +43091,11 @@ class Shape {
42436
43091
  const refsA = this._resolveFaceLabels(faceA);
42437
43092
  const bNames = Array.isArray(faceB) ? faceB : [faceB];
42438
43093
  const refBs = bNames.flatMap((name) => this._resolveFaceLabels(name));
42439
- const result = uniqueEdgeSegments(refsA.flatMap((refA) => edgesBetweenFaces(this, refA, refBs)));
43094
+ const result = uniqueEdgeSegments(
43095
+ refsA.flatMap((refA) => {
43096
+ return edgesBetweenFaces(this, refA, refBs);
43097
+ })
43098
+ );
42440
43099
  if (result.length === 0) {
42441
43100
  const bStr = bNames.length === 1 ? `'${bNames[0]}'` : `[${bNames.map((n) => `'${n}'`).join(", ")}]`;
42442
43101
  throw new Error(
@@ -42500,14 +43159,16 @@ class Shape {
42500
43159
  * ```
42501
43160
  */
42502
43161
  placeReference(ref, target, offset) {
43162
+ const targetPoint = requireFiniteVec3$1(target, "Shape.placeReference() target");
43163
+ const offsetPoint = offset === void 0 ? void 0 : requireFiniteVec3$1(offset, "Shape.placeReference() offset");
42503
43164
  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];
43165
+ let dx = targetPoint[0] - sourcePoint[0];
43166
+ let dy = targetPoint[1] - sourcePoint[1];
43167
+ let dz = targetPoint[2] - sourcePoint[2];
43168
+ if (offsetPoint) {
43169
+ dx += offsetPoint[0];
43170
+ dy += offsetPoint[1];
43171
+ dz += offsetPoint[2];
42511
43172
  }
42512
43173
  return this.translate(dx, dy, dz);
42513
43174
  }
@@ -42519,27 +43180,39 @@ class Shape {
42519
43180
  * Example: `shape.translatePolar(50, 30)` moves 50mm at 30 degrees from +X.
42520
43181
  */
42521
43182
  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);
43183
+ const r = requireFiniteNumber(radius, "Shape.translatePolar() radius");
43184
+ const angle = requireFiniteNumber(angleDeg, "Shape.translatePolar() angleDeg");
43185
+ const zOffset = requireFiniteNumber(z2, "Shape.translatePolar() z");
43186
+ const rad = angle * (Math.PI / 180);
43187
+ return this.translate(r * Math.cos(rad), r * Math.sin(rad), zOffset);
42524
43188
  }
42525
43189
  /** Move the shape relative to its current position. All transforms are immutable and return new shapes. */
42526
43190
  translate(x2, y2, z2) {
42527
- const nextPlan = appendShapeCompileTransform(getShapeCompilePlanInternal(this), { kind: "translate", x: x2, y: y2, z: z2 });
43191
+ const dx = requireFiniteNumber(x2, "Shape.translate() x");
43192
+ const dy = requireFiniteNumber(y2, "Shape.translate() y");
43193
+ const dz = requireFiniteNumber(z2, "Shape.translate() z");
43194
+ const nextPlan = appendShapeCompileTransform(getShapeCompilePlanInternal(this), { kind: "translate", x: dx, y: dy, z: dz });
42528
43195
  return setShapeCompilePlanInternal(
42529
- withTransformedDimensions(this, buildShapeFromCompilePlan(nextPlan, this.colorHex), Transform.translation(x2, y2, z2).toArray()),
43196
+ withTransformedDimensions(this, buildShapeFromCompilePlan(nextPlan, this.colorHex), Transform.translation(dx, dy, dz).toArray()),
42530
43197
  nextPlan
42531
43198
  );
42532
43199
  }
42533
43200
  /** Position the shape so its bounding box min corner is at the given global coordinate. */
42534
43201
  moveTo(x2, y2, z2) {
43202
+ const targetX = requireFiniteNumber(x2, "Shape.moveTo() x");
43203
+ const targetY = requireFiniteNumber(y2, "Shape.moveTo() y");
43204
+ const targetZ = requireFiniteNumber(z2, "Shape.moveTo() z");
42535
43205
  const bb = this.boundingBox();
42536
- return this.translate(x2 - bb.min[0], y2 - bb.min[1], z2 - bb.min[2]);
43206
+ return this.translate(targetX - bb.min[0], targetY - bb.min[1], targetZ - bb.min[2]);
42537
43207
  }
42538
43208
  /** Position the shape relative to another shape's local coordinate system (bounding box min corner). */
42539
43209
  moveToLocal(target, x2, y2, z2) {
43210
+ const localX = requireFiniteNumber(x2, "Shape.moveToLocal() x");
43211
+ const localY = requireFiniteNumber(y2, "Shape.moveToLocal() y");
43212
+ const localZ = requireFiniteNumber(z2, "Shape.moveToLocal() z");
42540
43213
  const s = "toShape" in target ? target.toShape() : target;
42541
43214
  const tbb = s.boundingBox();
42542
- return this.moveTo(tbb.min[0] + x2, tbb.min[1] + y2, tbb.min[2] + z2);
43215
+ return this.moveTo(tbb.min[0] + localX, tbb.min[1] + localY, tbb.min[2] + localZ);
42543
43216
  }
42544
43217
  /** Rotate around an arbitrary axis through the origin. */
42545
43218
  rotate(axis, angleDeg, options) {
@@ -42568,7 +43241,7 @@ class Shape {
42568
43241
  }
42569
43242
  /** Apply a 4x4 affine transform matrix (column-major) or a Transform object. */
42570
43243
  transform(m2) {
42571
- const mat = m2 instanceof Transform ? m2.toArray() : m2;
43244
+ const mat = m2 instanceof Transform ? m2.toArray() : requireFiniteMat4(m2, "Shape.transform() matrix");
42572
43245
  const steps = rigidTransformStepsFromMatrix(mat);
42573
43246
  if (steps == null) {
42574
43247
  throw new Error(
@@ -42580,23 +43253,20 @@ class Shape {
42580
43253
  }
42581
43254
  /** Scale the shape uniformly or per-axis from the shape's bounding box center. Accepts a single number or [x, y, z] array. */
42582
43255
  scale(v) {
43256
+ const scale2 = requireNonZeroFiniteScale3(v, "Shape.scale() scale");
42583
43257
  const bb = this.boundingBox();
42584
43258
  const center = [
42585
43259
  (bb.min[0] + bb.max[0]) / 2,
42586
43260
  (bb.min[1] + bb.max[1]) / 2,
42587
43261
  (bb.min[2] + bb.max[2]) / 2
42588
43262
  ];
42589
- return this.scaleAround(center, v);
43263
+ return this.scaleAround(center, scale2);
42590
43264
  }
42591
43265
  /** Scale the shape uniformly or per-axis from an explicit pivot point. */
42592
43266
  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) {
43267
+ const scale2 = requireNonZeroFiniteScale3(v, "Shape.scaleAround() scale");
43268
+ const scalePivot = requireFiniteVec3$1(pivot, "Shape.scaleAround() pivot");
43269
+ if (scalePivot[0] === 0 && scalePivot[1] === 0 && scalePivot[2] === 0) {
42600
43270
  const nextPlan2 = appendShapeCompileTransform(getShapeCompilePlanInternal(this), {
42601
43271
  kind: "scale",
42602
43272
  x: scale2[0],
@@ -42604,11 +43274,11 @@ class Shape {
42604
43274
  z: scale2[2]
42605
43275
  });
42606
43276
  return setShapeCompilePlanInternal(
42607
- withTransformedDimensions(this, buildShapeFromCompilePlan(nextPlan2, this.colorHex), Transform.scale(v).toArray()),
43277
+ withTransformedDimensions(this, buildShapeFromCompilePlan(nextPlan2, this.colorHex), Transform.scale(scale2).toArray()),
42608
43278
  nextPlan2
42609
43279
  );
42610
43280
  }
42611
- const translated = this.translate(-pivot[0], -pivot[1], -pivot[2]);
43281
+ const translated = this.translate(-scalePivot[0], -scalePivot[1], -scalePivot[2]);
42612
43282
  const nextPlan = appendShapeCompileTransform(getShapeCompilePlanInternal(translated), {
42613
43283
  kind: "scale",
42614
43284
  x: scale2[0],
@@ -42616,10 +43286,10 @@ class Shape {
42616
43286
  z: scale2[2]
42617
43287
  });
42618
43288
  const scaled = setShapeCompilePlanInternal(
42619
- withTransformedDimensions(translated, buildShapeFromCompilePlan(nextPlan, translated.colorHex), Transform.scale(v).toArray()),
43289
+ withTransformedDimensions(translated, buildShapeFromCompilePlan(nextPlan, translated.colorHex), Transform.scale(scale2).toArray()),
42620
43290
  nextPlan
42621
43291
  );
42622
- return scaled.translate(pivot[0], pivot[1], pivot[2]);
43292
+ return scaled.translate(scalePivot[0], scalePivot[1], scalePivot[2]);
42623
43293
  }
42624
43294
  /** Mirror across a plane through the shape's bounding box center, defined by its normal vector. */
42625
43295
  mirror(normal) {
@@ -42633,32 +43303,34 @@ class Shape {
42633
43303
  }
42634
43304
  /** Mirror across a plane through an explicit point, defined by its normal vector. */
42635
43305
  mirrorThrough(point, normal) {
42636
- if (point[0] === 0 && point[1] === 0 && point[2] === 0) {
43306
+ const mirrorPoint = requireFiniteVec3$1(point, "Shape.mirrorThrough() point");
43307
+ const mirrorNormal = requireNonZeroFiniteVec3(normal, "Shape.mirrorThrough() normal");
43308
+ if (mirrorPoint[0] === 0 && mirrorPoint[1] === 0 && mirrorPoint[2] === 0) {
42637
43309
  const transformedPlan2 = appendShapeCompileTransform(getShapeCompilePlanInternal(this), {
42638
43310
  kind: "mirror",
42639
- normalX: normal[0],
42640
- normalY: normal[1],
42641
- normalZ: normal[2]
43311
+ normalX: mirrorNormal[0],
43312
+ normalY: mirrorNormal[1],
43313
+ normalZ: mirrorNormal[2]
42642
43314
  });
42643
43315
  const nextPlan2 = wrapRepeatedShapeCompilePlan(transformedPlan2, "mirror");
42644
43316
  return setShapeCompilePlanInternal(
42645
- withTransformedDimensions(this, buildShapeFromCompilePlan(nextPlan2, this.colorHex), mirrorMatrix(normal)),
43317
+ withTransformedDimensions(this, buildShapeFromCompilePlan(nextPlan2, this.colorHex), mirrorMatrix(mirrorNormal)),
42646
43318
  nextPlan2
42647
43319
  );
42648
43320
  }
42649
- const translated = this.translate(-point[0], -point[1], -point[2]);
43321
+ const translated = this.translate(-mirrorPoint[0], -mirrorPoint[1], -mirrorPoint[2]);
42650
43322
  const transformedPlan = appendShapeCompileTransform(getShapeCompilePlanInternal(translated), {
42651
43323
  kind: "mirror",
42652
- normalX: normal[0],
42653
- normalY: normal[1],
42654
- normalZ: normal[2]
43324
+ normalX: mirrorNormal[0],
43325
+ normalY: mirrorNormal[1],
43326
+ normalZ: mirrorNormal[2]
42655
43327
  });
42656
43328
  const nextPlan = wrapRepeatedShapeCompilePlan(transformedPlan, "mirror");
42657
43329
  const mirrored = setShapeCompilePlanInternal(
42658
- withTransformedDimensions(translated, buildShapeFromCompilePlan(nextPlan, translated.colorHex), mirrorMatrix(normal)),
43330
+ withTransformedDimensions(translated, buildShapeFromCompilePlan(nextPlan, translated.colorHex), mirrorMatrix(mirrorNormal)),
42659
43331
  nextPlan
42660
43332
  );
42661
- return mirrored.translate(point[0], point[1], point[2]);
43333
+ return mirrored.translate(mirrorPoint[0], mirrorPoint[1], mirrorPoint[2]);
42662
43334
  }
42663
43335
  /**
42664
43336
  * Reorient a shape so its primary axis (Z) points along the given direction.
@@ -42668,7 +43340,7 @@ class Shape {
42668
43340
  * Example: cylinder(40, 5).pointAlong([1, 0, 0]) — lays cylinder along X, starting at origin
42669
43341
  */
42670
43342
  pointAlong(direction) {
42671
- const [dx, dy, dz] = direction;
43343
+ const [dx, dy, dz] = requireNonZeroFiniteVec3(direction, "Shape.pointAlong() direction");
42672
43344
  const len = Math.sqrt(dx * dx + dy * dy + dz * dz) || 1;
42673
43345
  const nx = dx / len, ny = dy / len, nz = dz / len;
42674
43346
  const cx = -ny, cy = nx, cz = 0;
@@ -42687,18 +43359,21 @@ class Shape {
42687
43359
  * @internal Prefer rotate(), rotateX(), rotateY(), rotateZ() for public use.
42688
43360
  */
42689
43361
  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);
43362
+ const rotateAxis = requireNonZeroFiniteVec3(axis, "Shape.rotateAroundAxis() axis");
43363
+ const degrees = requireFiniteNumber(angleDeg, "Shape.rotateAroundAxis() angleDeg");
43364
+ const rotatePivot = requireFiniteVec3$1(pivot, "Shape.rotateAroundAxis() pivot");
43365
+ const len = Math.sqrt(rotateAxis[0] ** 2 + rotateAxis[1] ** 2 + rotateAxis[2] ** 2) || 1;
43366
+ const normalizedAxis = [rotateAxis[0] / len, rotateAxis[1] / len, rotateAxis[2] / len];
43367
+ const matrix = rotationAroundAxisMatrix(normalizedAxis, degrees, rotatePivot);
42693
43368
  const nextPlan = appendShapeCompileTransform(getShapeCompilePlanInternal(this), {
42694
43369
  kind: "rotateAround",
42695
43370
  axisX: normalizedAxis[0],
42696
43371
  axisY: normalizedAxis[1],
42697
43372
  axisZ: normalizedAxis[2],
42698
- degrees: angleDeg,
42699
- pivotX: pivot[0],
42700
- pivotY: pivot[1],
42701
- pivotZ: pivot[2]
43373
+ degrees,
43374
+ pivotX: rotatePivot[0],
43375
+ pivotY: rotatePivot[1],
43376
+ pivotZ: rotatePivot[2]
42702
43377
  });
42703
43378
  return setShapeCompilePlanInternal(
42704
43379
  withTransformedDimensions(this, buildShapeFromCompilePlan(nextPlan, this.colorHex), matrix),
@@ -42710,10 +43385,12 @@ class Shape {
42710
43385
  * `movingPoint` / `targetPoint` may be raw world points or this shape's anchors/references.
42711
43386
  */
42712
43387
  rotateAroundTo(axis, pivot, movingPoint, targetPoint, options = {}) {
43388
+ const rotateAxis = requireNonZeroFiniteVec3(axis, "Shape.rotateAroundTo() axis");
43389
+ const rotatePivot = requireFiniteVec3$1(pivot, "Shape.rotateAroundTo() pivot");
42713
43390
  const moving = resolveRotationPoint(this, movingPoint);
42714
43391
  const target = resolveRotationPoint(this, targetPoint);
42715
- const angleDeg = solveRotateAroundAngle(axis, pivot, moving, target, options);
42716
- return this.rotateAroundAxis(axis, angleDeg, pivot);
43392
+ const angleDeg = solveRotateAroundAngle(rotateAxis, rotatePivot, moving, target, options);
43393
+ return this.rotateAroundAxis(rotateAxis, angleDeg, rotatePivot);
42717
43394
  }
42718
43395
  // --- Booleans ---
42719
43396
  /** Unwrap any object with toShape() without circular import. */
@@ -42877,17 +43554,19 @@ class Shape {
42877
43554
  }
42878
43555
  /** Split by infinite plane. Returns [positive-side, negative-side]. */
42879
43556
  splitByPlane(normal, originOffset = 0) {
43557
+ const planeNormal = requireNonZeroFiniteVec3(normal, "Shape.splitByPlane() normal");
43558
+ const planeOffset = requireFiniteNumber(originOffset, "Shape.splitByPlane() originOffset");
42880
43559
  const info = deriveGeometryInfo(getShapeGeometryInfoInternal(this), "boolean", {
42881
43560
  topology: _activeBackend === "truck" ? "kernel" : "none"
42882
43561
  });
42883
43562
  const basePlan = getShapeCompilePlanInternal(this);
42884
43563
  const firstPlan = createOwnedTopologyRewritePlan(
42885
- buildTrimByPlaneShapeCompilePlan(basePlan, normal, originOffset),
43564
+ buildTrimByPlaneShapeCompilePlan(basePlan, planeNormal, planeOffset),
42886
43565
  "splitByPlane:positive",
42887
43566
  (owner) => buildTrimByPlaneTopologyRewritePropagation(owner, basePlan)
42888
43567
  );
42889
43568
  const secondPlan = createOwnedTopologyRewritePlan(
42890
- buildTrimByPlaneShapeCompilePlan(basePlan, [-normal[0], -normal[1], -normal[2]], -originOffset),
43569
+ buildTrimByPlaneShapeCompilePlan(basePlan, [-planeNormal[0], -planeNormal[1], -planeNormal[2]], -planeOffset),
42891
43570
  "splitByPlane:opposite",
42892
43571
  (owner) => buildTrimByPlaneTopologyRewritePropagation(owner, basePlan)
42893
43572
  );
@@ -42905,9 +43584,11 @@ class Shape {
42905
43584
  }
42906
43585
  /** Keep the positive side of the plane and discard the opposite side. */
42907
43586
  trimByPlane(normal, originOffset = 0) {
43587
+ const planeNormal = requireNonZeroFiniteVec3(normal, "Shape.trimByPlane() normal");
43588
+ const planeOffset = requireFiniteNumber(originOffset, "Shape.trimByPlane() originOffset");
42908
43589
  const basePlan = getShapeCompilePlanInternal(this);
42909
43590
  const nextPlan = createOwnedTopologyRewritePlan(
42910
- buildTrimByPlaneShapeCompilePlan(basePlan, normal, originOffset),
43591
+ buildTrimByPlaneShapeCompilePlan(basePlan, planeNormal, planeOffset),
42911
43592
  "trimByPlane",
42912
43593
  (owner) => buildTrimByPlaneTopologyRewritePropagation(owner, basePlan)
42913
43594
  );
@@ -42927,8 +43608,9 @@ class Shape {
42927
43608
  * `openFaces` names any subset of the base shape's labeled faces to leave open (no wall).
42928
43609
  */
42929
43610
  shell(thickness, opts = {}) {
43611
+ const wallThickness = requirePositiveFiniteNumber(thickness, "Shape.shell() thickness");
42930
43612
  const basePlan = getShapeCompilePlanInternal(this);
42931
- const shellPlan = buildShellShapeCompilePlan(basePlan, thickness, opts.openFaces);
43613
+ const shellPlan = buildShellShapeCompilePlan(basePlan, wallThickness, opts.openFaces);
42932
43614
  if (!shellPlan) {
42933
43615
  throw new Error(
42934
43616
  "Shape.shell() supports box(), cylinder(), straight extrude(), loft(), sweep(), and variableSweep() solids with optional face openings and rigid transforms."
@@ -42949,7 +43631,8 @@ class Shape {
42949
43631
  }
42950
43632
  /** Offset-thicken an exact open surface or shell into a solid. */
42951
43633
  thicken(thickness) {
42952
- if (!Number.isFinite(thickness) || thickness === 0) {
43634
+ const wallThickness = requireFiniteNumber(thickness, "Shape.thicken() thickness");
43635
+ if (wallThickness === 0) {
42953
43636
  throw new Error("Shape.thicken() requires a non-zero finite thickness.");
42954
43637
  }
42955
43638
  const info = getShapeGeometryInfoInternal(this);
@@ -42957,7 +43640,7 @@ class Shape {
42957
43640
  throw new Error("Shape.thicken() is only available on surface-representation shapes.");
42958
43641
  }
42959
43642
  const basePlan = getShapeCompilePlanInternal(this);
42960
- const nextPlan = buildSurfaceThickenShapeCompilePlan(basePlan, thickness);
43643
+ const nextPlan = buildSurfaceThickenShapeCompilePlan(basePlan, wallThickness);
42961
43644
  const representation = info.backend === "occt" || info.backend === "truck" ? "brep-solid" : "mesh-solid";
42962
43645
  return setShapeCompilePlanInternal(
42963
43646
  setShapeGeometryInfoInternal(
@@ -42998,11 +43681,12 @@ class Shape {
42998
43681
  }
42999
43682
  /** Slice the runtime solid by a plane normal to local Z at the given offset. */
43000
43683
  slice(offset = 0) {
43684
+ const planeOffset = requireFiniteNumber(offset, "Shape.slice() offset");
43001
43685
  if (getShapeGeometryInfoInternal(this).backend === "truck") {
43002
- const slicedProfile = lowerExactSlicedShapeCompilePlanToTruckProfileBackend(getShapeCompilePlanInternal(this), offset);
43686
+ const slicedProfile = lowerExactSlicedShapeCompilePlanToTruckProfileBackend(getShapeCompilePlanInternal(this), planeOffset);
43003
43687
  if (slicedProfile) return slicedProfile;
43004
43688
  }
43005
- return getShapeRuntimeBackendInternal(this).slice(offset);
43689
+ return getShapeRuntimeBackendInternal(this).slice(planeOffset);
43006
43690
  }
43007
43691
  /** Orthographically project the runtime solid onto the local XY plane. */
43008
43692
  project() {
@@ -43025,9 +43709,10 @@ class Shape {
43025
43709
  const sp = this.referencePoint(selfAnchor);
43026
43710
  let dx = tp[0] - sp[0], dy = tp[1] - sp[1], dz = tp[2] - sp[2];
43027
43711
  if (offset) {
43028
- dx += offset[0];
43029
- dy += offset[1];
43030
- dz += offset[2];
43712
+ const offsetPoint = requireFiniteVec3$1(offset, "Shape.attachTo() offset");
43713
+ dx += offsetPoint[0];
43714
+ dy += offsetPoint[1];
43715
+ dz += offsetPoint[2];
43031
43716
  }
43032
43717
  return this.translate(dx, dy, dz);
43033
43718
  }
@@ -43043,9 +43728,9 @@ class Shape {
43043
43728
  * - `protrude` = how far the child sticks out (positive = outward from face)
43044
43729
  */
43045
43730
  onFace(parent, face, opts = {}) {
43046
- const u2 = opts.u ?? 0;
43047
- const v = opts.v ?? 0;
43048
- const p2 = opts.protrude ?? 0;
43731
+ const u2 = requireFiniteNumber(opts.u ?? 0, "Shape.onFace() u");
43732
+ const v = requireFiniteNumber(opts.v ?? 0, "Shape.onFace() v");
43733
+ const p2 = requireFiniteNumber(opts.protrude ?? 0, "Shape.onFace() protrude");
43049
43734
  const opposite = {
43050
43735
  front: "back",
43051
43736
  back: "front",