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
@@ -235,7 +235,7 @@ function denormalize(value, array) {
235
235
  throw new Error("Invalid component type.");
236
236
  }
237
237
  }
238
- function normalize$3(value, array) {
238
+ function normalize$4(value, array) {
239
239
  switch (array.constructor) {
240
240
  case Float32Array:
241
241
  return value;
@@ -487,7 +487,7 @@ const MathUtils = {
487
487
  * @param {TypedArray} array - The typed array that defines the data type of the value.
488
488
  * @return {number} The normalize value.
489
489
  */
490
- normalize: normalize$3,
490
+ normalize: normalize$4,
491
491
  /**
492
492
  * Denormalizes the given value according to the given typed array.
493
493
  *
@@ -9728,7 +9728,7 @@ class BufferAttribute {
9728
9728
  * @return {BufferAttribute} A reference to this instance.
9729
9729
  */
9730
9730
  setComponent(index2, component, value) {
9731
- if (this.normalized) value = normalize$3(value, this.array);
9731
+ if (this.normalized) value = normalize$4(value, this.array);
9732
9732
  this.array[index2 * this.itemSize + component] = value;
9733
9733
  return this;
9734
9734
  }
@@ -9751,7 +9751,7 @@ class BufferAttribute {
9751
9751
  * @return {BufferAttribute} A reference to this instance.
9752
9752
  */
9753
9753
  setX(index2, x2) {
9754
- if (this.normalized) x2 = normalize$3(x2, this.array);
9754
+ if (this.normalized) x2 = normalize$4(x2, this.array);
9755
9755
  this.array[index2 * this.itemSize] = x2;
9756
9756
  return this;
9757
9757
  }
@@ -9774,7 +9774,7 @@ class BufferAttribute {
9774
9774
  * @return {BufferAttribute} A reference to this instance.
9775
9775
  */
9776
9776
  setY(index2, y2) {
9777
- if (this.normalized) y2 = normalize$3(y2, this.array);
9777
+ if (this.normalized) y2 = normalize$4(y2, this.array);
9778
9778
  this.array[index2 * this.itemSize + 1] = y2;
9779
9779
  return this;
9780
9780
  }
@@ -9797,7 +9797,7 @@ class BufferAttribute {
9797
9797
  * @return {BufferAttribute} A reference to this instance.
9798
9798
  */
9799
9799
  setZ(index2, z2) {
9800
- if (this.normalized) z2 = normalize$3(z2, this.array);
9800
+ if (this.normalized) z2 = normalize$4(z2, this.array);
9801
9801
  this.array[index2 * this.itemSize + 2] = z2;
9802
9802
  return this;
9803
9803
  }
@@ -9820,7 +9820,7 @@ class BufferAttribute {
9820
9820
  * @return {BufferAttribute} A reference to this instance.
9821
9821
  */
9822
9822
  setW(index2, w2) {
9823
- if (this.normalized) w2 = normalize$3(w2, this.array);
9823
+ if (this.normalized) w2 = normalize$4(w2, this.array);
9824
9824
  this.array[index2 * this.itemSize + 3] = w2;
9825
9825
  return this;
9826
9826
  }
@@ -9835,8 +9835,8 @@ class BufferAttribute {
9835
9835
  setXY(index2, x2, y2) {
9836
9836
  index2 *= this.itemSize;
9837
9837
  if (this.normalized) {
9838
- x2 = normalize$3(x2, this.array);
9839
- y2 = normalize$3(y2, this.array);
9838
+ x2 = normalize$4(x2, this.array);
9839
+ y2 = normalize$4(y2, this.array);
9840
9840
  }
9841
9841
  this.array[index2 + 0] = x2;
9842
9842
  this.array[index2 + 1] = y2;
@@ -9854,9 +9854,9 @@ class BufferAttribute {
9854
9854
  setXYZ(index2, x2, y2, z2) {
9855
9855
  index2 *= this.itemSize;
9856
9856
  if (this.normalized) {
9857
- x2 = normalize$3(x2, this.array);
9858
- y2 = normalize$3(y2, this.array);
9859
- z2 = normalize$3(z2, this.array);
9857
+ x2 = normalize$4(x2, this.array);
9858
+ y2 = normalize$4(y2, this.array);
9859
+ z2 = normalize$4(z2, this.array);
9860
9860
  }
9861
9861
  this.array[index2 + 0] = x2;
9862
9862
  this.array[index2 + 1] = y2;
@@ -9876,10 +9876,10 @@ class BufferAttribute {
9876
9876
  setXYZW(index2, x2, y2, z2, w2) {
9877
9877
  index2 *= this.itemSize;
9878
9878
  if (this.normalized) {
9879
- x2 = normalize$3(x2, this.array);
9880
- y2 = normalize$3(y2, this.array);
9881
- z2 = normalize$3(z2, this.array);
9882
- w2 = normalize$3(w2, this.array);
9879
+ x2 = normalize$4(x2, this.array);
9880
+ y2 = normalize$4(y2, this.array);
9881
+ z2 = normalize$4(z2, this.array);
9882
+ w2 = normalize$4(w2, this.array);
9883
9883
  }
9884
9884
  this.array[index2 + 0] = x2;
9885
9885
  this.array[index2 + 1] = y2;
@@ -12298,6 +12298,55 @@ function cloneShapeWorkplanePlacement(placement) {
12298
12298
  placement: cloneSketchPlacementModel(placement.placement)
12299
12299
  };
12300
12300
  }
12301
+ function formatValidationValue(value) {
12302
+ if (typeof value === "number") return Number.isNaN(value) ? "NaN" : String(value);
12303
+ if (value === void 0) return "undefined";
12304
+ if (typeof value === "string") return JSON.stringify(value);
12305
+ try {
12306
+ return JSON.stringify(value) ?? String(value);
12307
+ } catch {
12308
+ return String(value);
12309
+ }
12310
+ }
12311
+ function requireFiniteNumber(value, label) {
12312
+ if (typeof value !== "number" || !Number.isFinite(value)) {
12313
+ throw new Error(`${label} must be a finite number, got ${formatValidationValue(value)}`);
12314
+ }
12315
+ return value;
12316
+ }
12317
+ function requirePositiveFiniteNumber(value, label) {
12318
+ const n = requireFiniteNumber(value, label);
12319
+ if (n <= 0) throw new Error(`${label} must be a positive finite number, got ${formatValidationValue(value)}`);
12320
+ return n;
12321
+ }
12322
+ function requireFiniteVec3$1(value, label) {
12323
+ if (!Array.isArray(value) || value.length !== 3) {
12324
+ throw new Error(`${label} must be [x, y, z] with finite numbers, got ${formatValidationValue(value)}`);
12325
+ }
12326
+ return [
12327
+ requireFiniteNumber(value[0], `${label}[0]`),
12328
+ requireFiniteNumber(value[1], `${label}[1]`),
12329
+ requireFiniteNumber(value[2], `${label}[2]`)
12330
+ ];
12331
+ }
12332
+ function requireNonZeroFiniteVec3(value, label) {
12333
+ const v = requireFiniteVec3$1(value, label);
12334
+ if (v[0] === 0 && v[1] === 0 && v[2] === 0) throw new Error(`${label} must not be [0, 0, 0]`);
12335
+ return v;
12336
+ }
12337
+ function requireFiniteMat4(value, label) {
12338
+ if (!Array.isArray(value) || value.length !== 16) {
12339
+ throw new Error(`${label} must be a 4x4 matrix array with 16 finite numbers, got ${formatValidationValue(value)}`);
12340
+ }
12341
+ return value.map((entry, index2) => requireFiniteNumber(entry, `${label}[${index2}]`));
12342
+ }
12343
+ function requireNonZeroFiniteScale3(value, label) {
12344
+ const scale2 = typeof value === "number" ? [requireFiniteNumber(value, label), requireFiniteNumber(value, label), requireFiniteNumber(value, label)] : requireFiniteVec3$1(value, label);
12345
+ if (Math.abs(scale2[0]) < 1e-12 || Math.abs(scale2[1]) < 1e-12 || Math.abs(scale2[2]) < 1e-12) {
12346
+ throw new Error(`${label} must have finite non-zero components, got ${formatValidationValue(value)}`);
12347
+ }
12348
+ return scale2;
12349
+ }
12301
12350
  const EPS$3 = 1e-10;
12302
12351
  function subVec3(a2, b) {
12303
12352
  return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
@@ -12379,9 +12428,6 @@ function solveRotateAroundAngle(axis, pivot, movingPoint, targetPoint, options =
12379
12428
  function identityMatrix() {
12380
12429
  return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
12381
12430
  }
12382
- function toMat4(input) {
12383
- return input instanceof Transform ? input.toArray() : input;
12384
- }
12385
12431
  function multiplyMat4(a2, b) {
12386
12432
  const out = new Array(16).fill(0);
12387
12433
  for (let col = 0; col < 4; col++) {
@@ -12458,23 +12504,40 @@ class Transform {
12458
12504
  }
12459
12505
  /** Wrap an existing `Transform` or raw 4x4 matrix as a `Transform`. */
12460
12506
  static from(input) {
12461
- return input instanceof Transform ? input : new Transform(input);
12507
+ return input instanceof Transform ? input : new Transform(requireFiniteMat4(input, "Transform.from() matrix"));
12462
12508
  }
12463
12509
  /** Create a translation transform. */
12464
12510
  static translation(x2, y2, z2) {
12465
- return new Transform([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x2, y2, z2, 1]);
12511
+ return new Transform([
12512
+ 1,
12513
+ 0,
12514
+ 0,
12515
+ 0,
12516
+ 0,
12517
+ 1,
12518
+ 0,
12519
+ 0,
12520
+ 0,
12521
+ 0,
12522
+ 1,
12523
+ 0,
12524
+ requireFiniteNumber(x2, "Transform.translation() x"),
12525
+ requireFiniteNumber(y2, "Transform.translation() y"),
12526
+ requireFiniteNumber(z2, "Transform.translation() z"),
12527
+ 1
12528
+ ]);
12466
12529
  }
12467
12530
  /** Create a uniform or per-axis scale transform. */
12468
12531
  static scale(v) {
12469
- const sx = typeof v === "number" ? v : v[0];
12470
- const sy = typeof v === "number" ? v : v[1];
12471
- const sz = typeof v === "number" ? v : v[2];
12532
+ const [sx, sy, sz] = requireNonZeroFiniteScale3(v, "Transform.scale() scale");
12472
12533
  return new Transform([sx, 0, 0, 0, 0, sy, 0, 0, 0, 0, sz, 0, 0, 0, 0, 1]);
12473
12534
  }
12474
12535
  /** Create a rotation around an arbitrary axis, optionally about a pivot. */
12475
12536
  static rotationAxis(axis, angleDeg, pivot = [0, 0, 0]) {
12476
- const [ux, uy, uz] = normalizeVec3$3(axis);
12477
- const rad = angleDeg * Math.PI / 180;
12537
+ const [ux, uy, uz] = normalizeVec3$3(requireNonZeroFiniteVec3(axis, "Transform.rotationAxis() axis"));
12538
+ const degrees = requireFiniteNumber(angleDeg, "Transform.rotationAxis() angleDeg");
12539
+ const [px, py, pz] = requireFiniteVec3$1(pivot, "Transform.rotationAxis() pivot");
12540
+ const rad = degrees * Math.PI / 180;
12478
12541
  const cos2 = Math.cos(rad);
12479
12542
  const sin2 = Math.sin(rad);
12480
12543
  const m00 = cos2 + ux * ux * (1 - cos2);
@@ -12486,7 +12549,6 @@ class Transform {
12486
12549
  const m20 = uz * ux * (1 - cos2) - uy * sin2;
12487
12550
  const m21 = uz * uy * (1 - cos2) + ux * sin2;
12488
12551
  const m22 = cos2 + uz * uz * (1 - cos2);
12489
- const [px, py, pz] = pivot;
12490
12552
  const tx = px - (m00 * px + m01 * py + m02 * pz);
12491
12553
  const ty = py - (m10 * px + m11 * py + m12 * pz);
12492
12554
  const tz = pz - (m20 * px + m21 * py + m22 * pz);
@@ -12494,12 +12556,16 @@ class Transform {
12494
12556
  }
12495
12557
  /** Solve the rotation needed to move one point onto a target line or plane. */
12496
12558
  static rotateAroundTo(axis, pivot, movingPoint, targetPoint, options = {}) {
12497
- const angleDeg = solveRotateAroundAngle(axis, pivot, movingPoint, targetPoint, options);
12498
- return Transform.rotationAxis(axis, angleDeg, pivot);
12559
+ const rotateAxis = requireNonZeroFiniteVec3(axis, "Transform.rotateAroundTo() axis");
12560
+ const rotatePivot = requireFiniteVec3$1(pivot, "Transform.rotateAroundTo() pivot");
12561
+ const moving = requireFiniteVec3$1(movingPoint, "Transform.rotateAroundTo() movingPoint");
12562
+ const target = requireFiniteVec3$1(targetPoint, "Transform.rotateAroundTo() targetPoint");
12563
+ const angleDeg = solveRotateAroundAngle(rotateAxis, rotatePivot, moving, target, options);
12564
+ return Transform.rotationAxis(rotateAxis, angleDeg, rotatePivot);
12499
12565
  }
12500
12566
  /** Compose transforms in chain order: `a.mul(b)` applies `a`, then `b`. */
12501
12567
  mul(other) {
12502
- const rhs = toMat4(other);
12568
+ const rhs = other instanceof Transform ? other.toArray() : requireFiniteMat4(other, "Transform.mul() matrix");
12503
12569
  return new Transform(multiplyMat4(rhs, this.m));
12504
12570
  }
12505
12571
  /** Translate after the current transform. */
@@ -12510,7 +12576,20 @@ class Transform {
12510
12576
  rotateAxis(axis, angleDeg, pivot = [0, 0, 0]) {
12511
12577
  return this.mul(Transform.rotationAxis(axis, angleDeg, pivot));
12512
12578
  }
12579
+ /** Rotate about the X axis after the current transform (parity with `Shape.rotateX`). */
12580
+ rotateX(angleDeg, pivot = [0, 0, 0]) {
12581
+ return this.rotateAxis([1, 0, 0], angleDeg, pivot);
12582
+ }
12583
+ /** Rotate about the Y axis after the current transform (parity with `Shape.rotateY`). */
12584
+ rotateY(angleDeg, pivot = [0, 0, 0]) {
12585
+ return this.rotateAxis([0, 1, 0], angleDeg, pivot);
12586
+ }
12587
+ /** Rotate about the Z axis after the current transform (parity with `Shape.rotateZ`). */
12588
+ rotateZ(angleDeg, pivot = [0, 0, 0]) {
12589
+ return this.rotateAxis([0, 0, 1], angleDeg, pivot);
12590
+ }
12513
12591
  /** Scale after the current transform. */
12592
+ // biome-ignore lint/suspicious/useAdjacentOverloadSignatures: Static Transform.scale() and chainable instance scale() intentionally share the CAD API name.
12514
12593
  scale(v) {
12515
12594
  return this.mul(Transform.scale(v));
12516
12595
  }
@@ -12520,11 +12599,11 @@ class Transform {
12520
12599
  }
12521
12600
  /** Transform a point using homogeneous coordinates. */
12522
12601
  point(p2) {
12523
- return transformPoint$1(this.m, p2, 1);
12602
+ return transformPoint$1(this.m, requireFiniteVec3$1(p2, "Transform.point() point"), 1);
12524
12603
  }
12525
12604
  /** Transform a direction vector without translation. */
12526
12605
  vector(v) {
12527
- return transformPoint$1(this.m, v, 0);
12606
+ return transformPoint$1(this.m, requireFiniteVec3$1(v, "Transform.vector() vector"), 0);
12528
12607
  }
12529
12608
  /** Return the transform as a raw 4x4 matrix array. */
12530
12609
  toArray() {
@@ -12913,6 +12992,42 @@ function cloneCutTaperCompilePlan(plan) {
12913
12992
  scale: [canonicalNumber(plan.scale[0]), canonicalNumber(plan.scale[1])]
12914
12993
  };
12915
12994
  }
12995
+ function cloneEdgeFeatureTarget(target) {
12996
+ return {
12997
+ midpoint: [target.midpoint[0], target.midpoint[1], target.midpoint[2]],
12998
+ start: [target.start[0], target.start[1], target.start[2]],
12999
+ end: [target.end[0], target.end[1], target.end[2]],
13000
+ convex: target.convex,
13001
+ ...target.nativeTopology ? { nativeTopology: { backend: target.nativeTopology.backend, edge: target.nativeTopology.edge } } : {}
13002
+ };
13003
+ }
13004
+ function cloneEdgeFeatureQuery(query) {
13005
+ if (!query) return void 0;
13006
+ return {
13007
+ ...query.near ? { near: [query.near[0], query.near[1], query.near[2]] } : {},
13008
+ ...query.parallel ? { parallel: [query.parallel[0], query.parallel[1], query.parallel[2]] } : {},
13009
+ ...query.perpendicular ? { perpendicular: [query.perpendicular[0], query.perpendicular[1], query.perpendicular[2]] } : {},
13010
+ ...query.convex !== void 0 ? { convex: query.convex } : {},
13011
+ ...query.concave !== void 0 ? { concave: query.concave } : {},
13012
+ ...query.minAngle !== void 0 ? { minAngle: canonicalNumber(query.minAngle) } : {},
13013
+ ...query.maxAngle !== void 0 ? { maxAngle: canonicalNumber(query.maxAngle) } : {},
13014
+ ...query.minLength !== void 0 ? { minLength: canonicalNumber(query.minLength) } : {},
13015
+ ...query.maxLength !== void 0 ? { maxLength: canonicalNumber(query.maxLength) } : {},
13016
+ ...query.within ? {
13017
+ within: {
13018
+ ...query.within.xMin !== void 0 ? { xMin: canonicalNumber(query.within.xMin) } : {},
13019
+ ...query.within.xMax !== void 0 ? { xMax: canonicalNumber(query.within.xMax) } : {},
13020
+ ...query.within.yMin !== void 0 ? { yMin: canonicalNumber(query.within.yMin) } : {},
13021
+ ...query.within.yMax !== void 0 ? { yMax: canonicalNumber(query.within.yMax) } : {},
13022
+ ...query.within.zMin !== void 0 ? { zMin: canonicalNumber(query.within.zMin) } : {},
13023
+ ...query.within.zMax !== void 0 ? { zMax: canonicalNumber(query.within.zMax) } : {}
13024
+ }
13025
+ } : {},
13026
+ ...query.atZ !== void 0 ? { atZ: canonicalNumber(query.atZ) } : {},
13027
+ ...query.tolerance !== void 0 ? { tolerance: canonicalNumber(query.tolerance) } : {},
13028
+ ...query.angleTolerance !== void 0 ? { angleTolerance: canonicalNumber(query.angleTolerance) } : {}
13029
+ };
13030
+ }
12916
13031
  function featureCutExtentForwardSide(extent) {
12917
13032
  return extent.kind === "two-sided" ? extent.forward : extent;
12918
13033
  }
@@ -13009,6 +13124,41 @@ function cloneSweepPathCompilePlan(path) {
13009
13124
  degree: path.degree,
13010
13125
  closed: path.closed
13011
13126
  };
13127
+ case "route3d":
13128
+ return {
13129
+ kind: "route3d",
13130
+ segments: path.segments.map(
13131
+ (segment) => segment.kind === "line" ? {
13132
+ kind: "line",
13133
+ from: canonicalVec3(segment.from),
13134
+ to: canonicalVec3(segment.to),
13135
+ length: canonicalNumber(segment.length)
13136
+ } : {
13137
+ kind: "arc",
13138
+ center: canonicalVec3(segment.center),
13139
+ radius: canonicalNumber(segment.radius),
13140
+ axis: canonicalVec3(segment.axis),
13141
+ start: canonicalVec3(segment.start),
13142
+ end: canonicalVec3(segment.end),
13143
+ sweepDeg: canonicalNumber(segment.sweepDeg),
13144
+ length: canonicalNumber(segment.length)
13145
+ }
13146
+ ),
13147
+ ports: Object.fromEntries(
13148
+ Object.entries(path.ports).map(([name, port]) => [
13149
+ name,
13150
+ {
13151
+ name: port.name,
13152
+ origin: canonicalVec3(port.origin),
13153
+ axis: canonicalVec3(port.axis),
13154
+ xAxis: canonicalVec3(port.xAxis),
13155
+ yAxis: canonicalVec3(port.yAxis),
13156
+ station: canonicalNumber(port.station)
13157
+ }
13158
+ ])
13159
+ ),
13160
+ length: canonicalNumber(path.length)
13161
+ };
13012
13162
  default:
13013
13163
  assertExhaustive(path);
13014
13164
  }
@@ -13103,6 +13253,7 @@ function cloneProfileEdge(edge) {
13103
13253
  }
13104
13254
  }
13105
13255
  function cloneShapeCompilePlan(plan) {
13256
+ var _a3, _b3, _c2;
13106
13257
  if (!plan) return null;
13107
13258
  let result;
13108
13259
  switch (plan.kind) {
@@ -13281,13 +13432,9 @@ function cloneShapeCompilePlan(plan) {
13281
13432
  radius: plan.radius,
13282
13433
  segments: plan.segments,
13283
13434
  continuity: plan.continuity,
13284
- edgeTargets: plan.edgeTargets.map((t) => ({
13285
- midpoint: [t.midpoint[0], t.midpoint[1], t.midpoint[2]],
13286
- start: [t.start[0], t.start[1], t.start[2]],
13287
- end: [t.end[0], t.end[1], t.end[2]],
13288
- convex: t.convex,
13289
- ...t.nativeTopology ? { nativeTopology: { backend: t.nativeTopology.backend, edge: t.nativeTopology.edge } } : {}
13290
- }))
13435
+ edgeTargets: (_a3 = plan.edgeTargets) == null ? void 0 : _a3.map(cloneEdgeFeatureTarget),
13436
+ edgeQuery: cloneEdgeFeatureQuery(plan.edgeQuery),
13437
+ edgeSelection: plan.edgeSelection
13291
13438
  };
13292
13439
  break;
13293
13440
  case "cornerYBlend":
@@ -13297,13 +13444,7 @@ function cloneShapeCompilePlan(plan) {
13297
13444
  radius: plan.radius,
13298
13445
  segments: plan.segments,
13299
13446
  continuity: plan.continuity,
13300
- edgeTargets: plan.edgeTargets.map((t) => ({
13301
- midpoint: [t.midpoint[0], t.midpoint[1], t.midpoint[2]],
13302
- start: [t.start[0], t.start[1], t.start[2]],
13303
- end: [t.end[0], t.end[1], t.end[2]],
13304
- convex: t.convex,
13305
- ...t.nativeTopology ? { nativeTopology: { backend: t.nativeTopology.backend, edge: t.nativeTopology.edge } } : {}
13306
- })),
13447
+ edgeTargets: ((_b3 = plan.edgeTargets) == null ? void 0 : _b3.map(cloneEdgeFeatureTarget)) ?? [],
13307
13448
  cornerPoint: [plan.cornerPoint[0], plan.cornerPoint[1], plan.cornerPoint[2]],
13308
13449
  cornerTolerance: canonicalNumber(plan.cornerTolerance),
13309
13450
  minBranchAngleDeg: canonicalNumber(plan.minBranchAngleDeg)
@@ -13314,13 +13455,9 @@ function cloneShapeCompilePlan(plan) {
13314
13455
  kind: "chamferEdges",
13315
13456
  base: cloneShapeCompilePlan(plan.base),
13316
13457
  size: plan.size,
13317
- edgeTargets: plan.edgeTargets.map((t) => ({
13318
- midpoint: [t.midpoint[0], t.midpoint[1], t.midpoint[2]],
13319
- start: [t.start[0], t.start[1], t.start[2]],
13320
- end: [t.end[0], t.end[1], t.end[2]],
13321
- convex: t.convex,
13322
- ...t.nativeTopology ? { nativeTopology: { backend: t.nativeTopology.backend, edge: t.nativeTopology.edge } } : {}
13323
- }))
13458
+ edgeTargets: (_c2 = plan.edgeTargets) == null ? void 0 : _c2.map(cloneEdgeFeatureTarget),
13459
+ edgeQuery: cloneEdgeFeatureQuery(plan.edgeQuery),
13460
+ edgeSelection: plan.edgeSelection
13324
13461
  };
13325
13462
  break;
13326
13463
  case "draft":
@@ -13340,7 +13477,13 @@ function cloneShapeCompilePlan(plan) {
13340
13477
  };
13341
13478
  break;
13342
13479
  case "importedMesh":
13343
- result = { kind: "importedMesh", filePath: plan.filePath, format: plan.format, fileData: plan.fileData };
13480
+ result = {
13481
+ kind: "importedMesh",
13482
+ filePath: plan.filePath,
13483
+ format: plan.format,
13484
+ ...plan.object ? { object: plan.object } : {},
13485
+ fileData: plan.fileData
13486
+ };
13344
13487
  break;
13345
13488
  case "sdf":
13346
13489
  result = {
@@ -13655,6 +13798,83 @@ function remapToKnotDomain(t, n, degree, knots) {
13655
13798
  const uMax = knots[n];
13656
13799
  return uMin + Math.max(0, Math.min(1, t)) * (uMax - uMin);
13657
13800
  }
13801
+ const EPSILON$2 = 1e-9;
13802
+ function add$2(a2, b) {
13803
+ return [a2[0] + b[0], a2[1] + b[1], a2[2] + b[2]];
13804
+ }
13805
+ function scale$2(v, factor) {
13806
+ return [v[0] * factor, v[1] * factor, v[2] * factor];
13807
+ }
13808
+ function sub$2(a2, b) {
13809
+ return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
13810
+ }
13811
+ function dot$3(a2, b) {
13812
+ return a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2];
13813
+ }
13814
+ function cross$4(a2, b) {
13815
+ 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]];
13816
+ }
13817
+ function rotateAroundAxis(v, axis, angleRad) {
13818
+ const c2 = Math.cos(angleRad);
13819
+ const s = Math.sin(angleRad);
13820
+ const term1 = scale$2(v, c2);
13821
+ const term2 = scale$2(cross$4(axis, v), s);
13822
+ const term3 = scale$2(axis, dot$3(axis, v) * (1 - c2));
13823
+ return add$2(add$2(term1, term2), term3);
13824
+ }
13825
+ function arcPointAt(segment, t) {
13826
+ const sweepRad = segment.sweepDeg * Math.PI / 180 * t;
13827
+ return add$2(segment.center, rotateAroundAxis(sub$2(segment.start, segment.center), segment.axis, sweepRad));
13828
+ }
13829
+ function pushUnique(points, point) {
13830
+ const prev = points[points.length - 1];
13831
+ if (!prev || Math.hypot(prev[0] - point[0], prev[1] - point[1], prev[2] - point[2]) > EPSILON$2) {
13832
+ points.push([point[0], point[1], point[2]]);
13833
+ }
13834
+ }
13835
+ function resolveOptions(options) {
13836
+ if (typeof options === "number") return { samples: Math.max(2, Math.floor(options)), maxAngleDeg: 6 };
13837
+ return {
13838
+ samples: Math.max(2, Math.floor((options == null ? void 0 : options.samples) ?? 48)),
13839
+ maxAngleDeg: Math.max(0.25, (options == null ? void 0 : options.maxAngleDeg) ?? 6)
13840
+ };
13841
+ }
13842
+ function routePointAt(plan, t) {
13843
+ if (plan.segments.length === 0) return [0, 0, 0];
13844
+ const target = Math.max(0, Math.min(1, t)) * plan.length;
13845
+ let station = 0;
13846
+ for (const segment of plan.segments) {
13847
+ const next = station + segment.length;
13848
+ if (target <= next || segment === plan.segments[plan.segments.length - 1]) {
13849
+ const localT = segment.length <= EPSILON$2 ? 1 : Math.max(0, Math.min(1, (target - station) / segment.length));
13850
+ if (segment.kind === "line") return add$2(segment.from, scale$2(sub$2(segment.to, segment.from), localT));
13851
+ return arcPointAt(segment, localT);
13852
+ }
13853
+ station = next;
13854
+ }
13855
+ const last = plan.segments[plan.segments.length - 1];
13856
+ return last.kind === "line" ? last.to : last.end;
13857
+ }
13858
+ function sampleRoute3DCompilePlan(plan, options) {
13859
+ const resolved = resolveOptions(options);
13860
+ const points = [];
13861
+ const totalIntervals = Math.max(1, resolved.samples - 1);
13862
+ for (const segment of plan.segments) {
13863
+ const proportional = plan.length > EPSILON$2 ? Math.ceil(segment.length / plan.length * totalIntervals) : 1;
13864
+ const arcIntervals = segment.kind === "arc" ? Math.ceil(Math.abs(segment.sweepDeg) / resolved.maxAngleDeg) : 1;
13865
+ const intervals = Math.max(1, proportional, arcIntervals);
13866
+ const start = segment.kind === "line" ? segment.from : segment.start;
13867
+ pushUnique(points, start);
13868
+ for (let i = 1; i <= intervals; i += 1) {
13869
+ if (segment.kind === "line") {
13870
+ pushUnique(points, add$2(segment.from, scale$2(sub$2(segment.to, segment.from), i / intervals)));
13871
+ } else {
13872
+ pushUnique(points, arcPointAt(segment, i / intervals));
13873
+ }
13874
+ }
13875
+ }
13876
+ return points;
13877
+ }
13658
13878
  function catmullRom3D$1(p0, p1, p2, p3, t, tension) {
13659
13879
  const tt = t * t;
13660
13880
  const ttt = tt * t;
@@ -13793,6 +14013,8 @@ function evalPathAt(path, t) {
13793
14013
  const u2 = remapToKnotDomain(Math.max(0, Math.min(1, t)), path.controlPoints.length, path.degree, path.knots);
13794
14014
  return deBoor3D(path.controlPoints, path.weights, path.knots, path.degree, u2);
13795
14015
  }
14016
+ case "route3d":
14017
+ return routePointAt(path, t);
13796
14018
  }
13797
14019
  }
13798
14020
  function estimateCurvatureAt(path, t) {
@@ -13839,10 +14061,13 @@ function sweepPathToPolyline(path, samples = 48) {
13839
14061
  }
13840
14062
  return result;
13841
14063
  }
14064
+ case "route3d":
14065
+ return sampleRoute3DCompilePlan(path, samples);
13842
14066
  }
13843
14067
  }
13844
14068
  function sweepPathToPolylineAdaptive(path, baseSamples = 48) {
13845
14069
  if (path.kind === "polyline") return path.points;
14070
+ if (path.kind === "route3d") return sampleRoute3DCompilePlan(path, baseSamples);
13846
14071
  const probeCount = 64;
13847
14072
  const curvatures = [];
13848
14073
  for (let i = 0; i <= probeCount; i++) {
@@ -13873,7 +14098,7 @@ const EPS$2 = 1e-8;
13873
14098
  function midpoint$1(start, end) {
13874
14099
  return [(start[0] + end[0]) * 0.5, (start[1] + end[1]) * 0.5, (start[2] + end[2]) * 0.5];
13875
14100
  }
13876
- function normalize$2(v) {
14101
+ function normalize$3(v) {
13877
14102
  const len = Math.hypot(v[0], v[1], v[2]);
13878
14103
  if (len <= EPS$2) throw new Error("Edge feature selection requires a non-zero direction vector");
13879
14104
  return [v[0] / len, v[1] / len, v[2] / len];
@@ -14292,8 +14517,8 @@ function extrudeEdgeSelection(plan, edgeName) {
14292
14517
  }
14293
14518
  const points = plan.profile.points;
14294
14519
  const [bl, br, _tr, tl] = points;
14295
- const u2 = normalize$2([br[0] - bl[0], br[1] - bl[1], 0]);
14296
- const v = normalize$2([tl[0] - bl[0], tl[1] - bl[1], 0]);
14520
+ const u2 = normalize$3([br[0] - bl[0], br[1] - bl[1], 0]);
14521
+ const v = normalize$3([tl[0] - bl[0], tl[1] - bl[1], 0]);
14297
14522
  const vertex = points[index2];
14298
14523
  const quadrant = index2 === 0 ? [1, -1] : index2 === 1 ? [-1, -1] : index2 === 2 ? [-1, 1] : [1, 1];
14299
14524
  return {
@@ -14314,9 +14539,9 @@ function extrudeEdgeSelection(plan, edgeName) {
14314
14539
  function applySelectionTransform(selection, transform) {
14315
14540
  const start = transform.point(selection.start);
14316
14541
  const end = transform.point(selection.end);
14317
- const basisX = normalize$2(transform.vector(selection.basisX));
14318
- const basisY = normalize$2(transform.vector(selection.basisY));
14319
- const axis = normalize$2(subtract(end, start));
14542
+ const basisX = normalize$3(transform.vector(selection.basisX));
14543
+ const basisY = normalize$3(transform.vector(selection.basisY));
14544
+ const axis = normalize$3(subtract(end, start));
14320
14545
  return {
14321
14546
  kind: "line-segment",
14322
14547
  edgeName: selection.edgeName,
@@ -15591,6 +15816,20 @@ function cleanName(value) {
15591
15816
  const trimmed = value == null ? void 0 : value.replace(/\s+/g, " ").trim();
15592
15817
  return trimmed && trimmed.length > 0 ? trimmed : null;
15593
15818
  }
15819
+ function uniqueNames(baseNames) {
15820
+ const totals = /* @__PURE__ */ new Map();
15821
+ const seen = /* @__PURE__ */ new Map();
15822
+ for (const baseName of baseNames) totals.set(baseName, (totals.get(baseName) ?? 0) + 1);
15823
+ return baseNames.map((baseName) => {
15824
+ if ((totals.get(baseName) ?? 0) <= 1) return baseName;
15825
+ const ordinal = (seen.get(baseName) ?? 0) + 1;
15826
+ seen.set(baseName, ordinal);
15827
+ return `${baseName} #${String(ordinal).padStart(3, "0")}`;
15828
+ });
15829
+ }
15830
+ function normalizeSelector(value) {
15831
+ return value.trim().replace(/\s+/g, " ").toLowerCase();
15832
+ }
15594
15833
  function extractModelXml(data) {
15595
15834
  const zip = unzipSync(new Uint8Array(data));
15596
15835
  const path = Object.keys(zip).find((entry) => entry.toLowerCase() === "3d/3dmodel.model");
@@ -15662,14 +15901,105 @@ function collectObjectGeometry(model, objectId, transforms = [], visiting = /* @
15662
15901
  }
15663
15902
  return { vertices, triangles, bbox };
15664
15903
  }
15904
+ function buildBuildEntries(model) {
15905
+ const buildBaseNames = model.buildItems.map(
15906
+ (item) => {
15907
+ var _a3;
15908
+ return cleanName((_a3 = model.objectsById.get(item.objectId)) == null ? void 0 : _a3.sourceName) ?? `object-${item.objectId}`;
15909
+ }
15910
+ );
15911
+ const buildNames = uniqueNames(buildBaseNames);
15912
+ return model.buildItems.map((item, index2) => {
15913
+ const object = model.objectsById.get(item.objectId);
15914
+ return {
15915
+ stableRef: `3mf:build:${String(index2 + 1).padStart(3, "0")}:object:${item.objectId}`,
15916
+ objectId: item.objectId,
15917
+ autoName: buildNames[index2],
15918
+ sourceName: object == null ? void 0 : object.sourceName,
15919
+ type: object == null ? void 0 : object.type,
15920
+ transform: item.transform
15921
+ };
15922
+ });
15923
+ }
15924
+ function buildObjectEntries(model) {
15925
+ const objectNames = uniqueNames(model.objects.map((object) => cleanName(object.sourceName) ?? `object-${object.id}`));
15926
+ return model.objects.map(
15927
+ (object, index2) => ({
15928
+ stableRef: `3mf:object:${object.id}`,
15929
+ objectId: object.id,
15930
+ autoName: objectNames[index2],
15931
+ sourceName: object.sourceName,
15932
+ type: object.type
15933
+ })
15934
+ );
15935
+ }
15936
+ function buildDefaultSourceEntries(model) {
15937
+ return model.buildItems.length > 0 ? buildBuildEntries(model) : buildObjectEntries(model);
15938
+ }
15939
+ function buildSelectableEntries(model) {
15940
+ return model.buildItems.length > 0 ? [...buildBuildEntries(model), ...buildObjectEntries(model)] : buildObjectEntries(model);
15941
+ }
15942
+ function entryMatchesSelector(entry, selector) {
15943
+ const needle = normalizeSelector(selector);
15944
+ return [
15945
+ entry.stableRef,
15946
+ entry.autoName,
15947
+ entry.sourceName ?? "",
15948
+ entry.objectId,
15949
+ `object-${entry.objectId}`,
15950
+ `3mf:object:${entry.objectId}`
15951
+ ].some((candidate) => normalizeSelector(candidate) === needle);
15952
+ }
15953
+ function availableEntryList(entries) {
15954
+ return entries.map((entry) => `${entry.autoName} [${entry.stableRef}]`).join(", ") || "none";
15955
+ }
15956
+ function selectSourceEntry(entries, selector) {
15957
+ const matches = entries.filter((entry) => entryMatchesSelector(entry, selector));
15958
+ if (matches.length === 1) return matches[0];
15959
+ if (matches.length > 1) {
15960
+ throw new Error(
15961
+ `3MF object selector "${selector}" is ambiguous. Use a stable ref: ${matches.map((entry) => entry.stableRef).join(", ")}`
15962
+ );
15963
+ }
15964
+ throw new Error(`3MF object selector "${selector}" matched no build item or resource object. Available: ${availableEntryList(entries)}`);
15965
+ }
15966
+ function selectorLooksLikeStableRef(selector) {
15967
+ return /^3mf:(build|object):/i.test(selector.trim());
15968
+ }
15969
+ function meshFromGeometry(geometry) {
15970
+ const vertices = [];
15971
+ const triangles = [];
15972
+ for (const vertex of geometry.vertices) vertices.push(vertex[0], vertex[1], vertex[2]);
15973
+ for (const triangle of geometry.triangles) triangles.push(triangle[0], triangle[1], triangle[2]);
15974
+ return {
15975
+ vertProperties: new Float32Array(vertices),
15976
+ triVerts: new Uint32Array(triangles),
15977
+ numProp: 3,
15978
+ mergeFromVert: new Uint32Array(0),
15979
+ mergeToVert: new Uint32Array(0)
15980
+ };
15981
+ }
15982
+ function partFromEntry(model, entry) {
15983
+ const geometry = collectObjectGeometry(model, entry.objectId, [entry.transform]);
15984
+ return {
15985
+ stableRef: entry.stableRef,
15986
+ objectId: entry.objectId,
15987
+ autoName: entry.autoName,
15988
+ sourceName: entry.sourceName,
15989
+ type: entry.type,
15990
+ mesh: meshFromGeometry(geometry),
15991
+ bbox: geometry.bbox,
15992
+ vertexCount: geometry.vertices.length,
15993
+ triangleCount: geometry.triangles.length
15994
+ };
15995
+ }
15665
15996
  function parse3mfMesh(data) {
15666
15997
  const model = parseModel(data);
15667
- const sourceIds = model.buildItems.length > 0 ? model.buildItems.map((item) => item.objectId) : model.objects.map((object) => object.id);
15668
- const transforms = model.buildItems.length > 0 ? model.buildItems.map((item) => item.transform) : sourceIds.map(() => void 0);
15998
+ const entries = buildDefaultSourceEntries(model);
15669
15999
  const vertices = [];
15670
16000
  const triangles = [];
15671
- sourceIds.forEach((objectId, index2) => {
15672
- const geometry = collectObjectGeometry(model, objectId, [transforms[index2]]);
16001
+ entries.forEach((entry) => {
16002
+ const geometry = collectObjectGeometry(model, entry.objectId, [entry.transform]);
15673
16003
  const offset = vertices.length / 3;
15674
16004
  for (const vertex of geometry.vertices) vertices.push(vertex[0], vertex[1], vertex[2]);
15675
16005
  for (const triangle of geometry.triangles) triangles.push(triangle[0] + offset, triangle[1] + offset, triangle[2] + offset);
@@ -15682,6 +16012,11 @@ function parse3mfMesh(data) {
15682
16012
  mergeToVert: new Uint32Array(0)
15683
16013
  };
15684
16014
  }
16015
+ function parse3mfMeshPart(data, selector) {
16016
+ const model = parseModel(data);
16017
+ const entries = selectorLooksLikeStableRef(selector) ? buildSelectableEntries(model) : buildDefaultSourceEntries(model);
16018
+ return partFromEntry(model, selectSourceEntry(entries, selector));
16019
+ }
15685
16020
  function isStlBinary(data) {
15686
16021
  if (data.byteLength < 84) return false;
15687
16022
  const view = new DataView(data);
@@ -15826,17 +16161,17 @@ function parseObj(data) {
15826
16161
  mergeToVert: new Uint32Array(0)
15827
16162
  };
15828
16163
  }
15829
- function parse3mf(data) {
15830
- return parse3mfMesh(data);
16164
+ function parse3mf(data, options = {}) {
16165
+ return options.object ? parse3mfMeshPart(data, options.object).mesh : parse3mfMesh(data);
15831
16166
  }
15832
- function parseMeshFile(data, format) {
16167
+ function parseMeshFile(data, format, options = {}) {
15833
16168
  switch (format) {
15834
16169
  case "stl":
15835
16170
  return parseStl(data);
15836
16171
  case "obj":
15837
16172
  return parseObj(data);
15838
16173
  case "3mf":
15839
- return parse3mf(data);
16174
+ return parse3mf(data, options);
15840
16175
  }
15841
16176
  }
15842
16177
  function cleanZero(value) {
@@ -15863,6 +16198,84 @@ function planeFrameToWorldToPlaneMatrix(frame) {
15863
16198
  1
15864
16199
  ];
15865
16200
  }
16201
+ const DEFAULT_TOLERANCE = 1;
16202
+ const DEFAULT_ANGLE_TOLERANCE = 10;
16203
+ function distSq$2(a2, b) {
16204
+ const dx = a2[0] - b[0];
16205
+ const dy = a2[1] - b[1];
16206
+ const dz = a2[2] - b[2];
16207
+ return dx * dx + dy * dy + dz * dz;
16208
+ }
16209
+ function vecLength$2(v) {
16210
+ return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
16211
+ }
16212
+ function normalize$2(v) {
16213
+ const len = vecLength$2(v);
16214
+ if (len < 1e-12) return [0, 0, 0];
16215
+ return [v[0] / len, v[1] / len, v[2] / len];
16216
+ }
16217
+ function absDot$1(a2, b) {
16218
+ return Math.abs(a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2]);
16219
+ }
16220
+ function applyEdgeQueryFilters(edges, query) {
16221
+ const tol = query.tolerance ?? DEFAULT_TOLERANCE;
16222
+ const angleTol = query.angleTolerance ?? DEFAULT_ANGLE_TOLERANCE;
16223
+ const cosAngleTol = Math.cos(angleTol * Math.PI / 180);
16224
+ let result = edges;
16225
+ if (query.convex === true) {
16226
+ result = result.filter((e) => e.convex);
16227
+ }
16228
+ if (query.concave === true) {
16229
+ result = result.filter((e) => !e.convex);
16230
+ }
16231
+ if (query.minAngle != null) {
16232
+ const min2 = query.minAngle;
16233
+ result = result.filter((e) => e.dihedralAngle >= min2);
16234
+ }
16235
+ if (query.maxAngle != null) {
16236
+ const max2 = query.maxAngle;
16237
+ result = result.filter((e) => e.dihedralAngle <= max2);
16238
+ }
16239
+ if (query.minLength != null) {
16240
+ const min2 = query.minLength;
16241
+ result = result.filter((e) => e.length >= min2);
16242
+ }
16243
+ if (query.maxLength != null) {
16244
+ const max2 = query.maxLength;
16245
+ result = result.filter((e) => e.length <= max2);
16246
+ }
16247
+ if (query.parallel) {
16248
+ const dir = normalize$2(query.parallel);
16249
+ result = result.filter((e) => absDot$1(e.direction, dir) >= cosAngleTol);
16250
+ }
16251
+ if (query.perpendicular) {
16252
+ const dir = normalize$2(query.perpendicular);
16253
+ const sinAngleTol = Math.sin(angleTol * Math.PI / 180);
16254
+ result = result.filter((e) => absDot$1(e.direction, dir) <= sinAngleTol);
16255
+ }
16256
+ if (query.atZ != null) {
16257
+ const z2 = query.atZ;
16258
+ result = result.filter((e) => Math.abs(e.midpoint[2] - z2) <= tol);
16259
+ }
16260
+ if (query.within) {
16261
+ const b = query.within;
16262
+ result = result.filter((e) => {
16263
+ const [mx, my, mz] = e.midpoint;
16264
+ if (b.xMin != null && mx < b.xMin) return false;
16265
+ if (b.xMax != null && mx > b.xMax) return false;
16266
+ if (b.yMin != null && my < b.yMin) return false;
16267
+ if (b.yMax != null && my > b.yMax) return false;
16268
+ if (b.zMin != null && mz < b.zMin) return false;
16269
+ if (b.zMax != null && mz > b.zMax) return false;
16270
+ return true;
16271
+ });
16272
+ }
16273
+ if (query.near) {
16274
+ const pt = query.near;
16275
+ result = result.slice().sort((a2, b) => distSq$2(a2.midpoint, pt) - distSq$2(b.midpoint, pt));
16276
+ }
16277
+ return result;
16278
+ }
15866
16279
  const SHELL_OPEN_FACE_CANONICAL = {
15867
16280
  front: "side-bottom",
15868
16281
  back: "side-top",
@@ -18170,6 +18583,8 @@ function getUnsupportedSdfProgramReason(node) {
18170
18583
  return "noise depends on table-based simplex evaluation";
18171
18584
  case "sdf:voronoi":
18172
18585
  return "voronoi depends on table-based Worley evaluation";
18586
+ case "sdf:circularArray":
18587
+ return "circularArray folds polar coordinates and is not implemented in SdfProgram yet";
18173
18588
  case "sdf:custom":
18174
18589
  return "custom uses a dynamic JavaScript function body";
18175
18590
  case "sdf:polylineSweep":
@@ -18850,6 +19265,7 @@ function requireOCCTShape(backend, apiName = "requireOCCTShape()") {
18850
19265
  }
18851
19266
  const BSPLINE_WIRE_THRESHOLD = 20;
18852
19267
  const OFFSET_SOLID_EPS$1 = 1e-8;
19268
+ const MANIFOLD_BACKEND_REQUIRED_HINT = "Select the Manifold backend in the editor or run the CLI with --backend manifold.";
18853
19269
  function transformProfileRadiusPoint$1(point, step) {
18854
19270
  switch (step.kind) {
18855
19271
  case "translate":
@@ -19555,6 +19971,31 @@ function findOCCTEdgeByMidpoint(oc, shape, midpoint2) {
19555
19971
  }
19556
19972
  return bestEdge;
19557
19973
  }
19974
+ function edgeSegmentToTarget(segment) {
19975
+ return {
19976
+ midpoint: [segment.midpoint[0], segment.midpoint[1], segment.midpoint[2]],
19977
+ start: [segment.start[0], segment.start[1], segment.start[2]],
19978
+ end: [segment.end[0], segment.end[1], segment.end[2]],
19979
+ convex: segment.convex,
19980
+ ...segment.nativeTopology ? { nativeTopology: segment.nativeTopology } : {}
19981
+ };
19982
+ }
19983
+ function selectOCCTEdgeFeatureTargets(base, plan) {
19984
+ if (!plan.edgeQuery) return plan.edgeTargets ?? [];
19985
+ const mesh = wrapOCCTShapeBackend(base).getMesh();
19986
+ const selected = applyEdgeQueryFilters(
19987
+ extractEdgeSegments({
19988
+ numProp: mesh.numProp,
19989
+ numTri: mesh.numTri,
19990
+ triVerts: mesh.triVerts,
19991
+ vertProperties: mesh.vertProperties,
19992
+ mergeFromVert: mesh.mergeFromVert,
19993
+ mergeToVert: mesh.mergeToVert
19994
+ }),
19995
+ plan.edgeQuery
19996
+ );
19997
+ return (plan.edgeSelection === "first" ? selected.slice(0, 1) : selected).map(edgeSegmentToTarget);
19998
+ }
19558
19999
  function occtPointDistance(point, target) {
19559
20000
  return Math.hypot(point.X() - target[0], point.Y() - target[1], point.Z() - target[2]);
19560
20001
  }
@@ -19574,7 +20015,7 @@ function lowerFilletEdgesPlan$1(oc, plan) {
19574
20015
  mkFillet.SetContinuity(mapSurfaceContinuityToOcct(oc, plan.continuity), 2 * Math.PI / 180);
19575
20016
  }
19576
20017
  let addedCount = 0;
19577
- for (const target of plan.edgeTargets) {
20018
+ for (const target of selectOCCTEdgeFeatureTargets(base, plan)) {
19578
20019
  const matchedEdge = findOCCTEdgeByMidpoint(oc, base, target.midpoint);
19579
20020
  if (matchedEdge) {
19580
20021
  mkFillet.Add_2(plan.radius, matchedEdge);
@@ -19621,7 +20062,7 @@ function lowerChamferEdgesPlan$1(oc, plan) {
19621
20062
  const base = lowerShapeCompilePlanToOCCT(plan.base, oc);
19622
20063
  const mkChamfer = new oc.BRepFilletAPI_MakeChamfer(base);
19623
20064
  let addedCount = 0;
19624
- for (const target of plan.edgeTargets) {
20065
+ for (const target of selectOCCTEdgeFeatureTargets(base, plan)) {
19625
20066
  const matchedEdge = findOCCTEdgeByMidpoint(oc, base, target.midpoint);
19626
20067
  if (matchedEdge) {
19627
20068
  mkChamfer.Add_2(plan.size, matchedEdge);
@@ -19908,7 +20349,7 @@ function _lowerShapeCompilePlanToOCCTInner(plan, oc) {
19908
20349
  `importMesh("${plan.filePath}") is not supported with the OCCT backend. Switch to the Manifold backend or use the default backend.`
19909
20350
  );
19910
20351
  case "sdf":
19911
- throw new Error("SDF shapes require the Manifold backend. Add setActiveBackend('manifold') at the top of your script.");
20352
+ throw new Error(`SDF shapes require the Manifold backend. ${MANIFOLD_BACKEND_REQUIRED_HINT}`);
19912
20353
  case "fromSlices":
19913
20354
  return lowerFromSlicesPlan$1(oc, plan);
19914
20355
  case "nurbsSurface":
@@ -20080,6 +20521,7 @@ function buildBSplineCurveHandleFromSweepPathPlan(oc, path, context, allowApprox
20080
20521
  case "catmull-rom":
20081
20522
  case "hermite":
20082
20523
  case "quintic-hermite":
20524
+ case "route3d":
20083
20525
  if (allowApproximation) {
20084
20526
  return buildApproximateBSplineCurveHandle(oc, sweepPathToPolyline(path, 96), context);
20085
20527
  }
@@ -20460,27 +20902,166 @@ function lowerLoftPlan$1(oc, plan) {
20460
20902
  }
20461
20903
  return ts.Shape();
20462
20904
  }
20463
- function buildZToNormalTransform(oc, normal) {
20905
+ function fromSlicesPlaneFrameForOCCT(normalInput) {
20906
+ const normal = normalizeVec3$2(normalInput);
20907
+ if (!normal) throw new Error("Shape.fromSlices group normal must be non-zero");
20464
20908
  const [nx, ny, nz] = normal;
20909
+ if (Math.abs(nz) > 1 - 1e-8) {
20910
+ return { u: [1, 0, 0], v: [0, nz > 0 ? 1 : -1, 0], normal };
20911
+ }
20912
+ if (Math.abs(ny) > 1 - 1e-8) {
20913
+ return { u: [1, 0, 0], v: [0, 0, ny > 0 ? 1 : -1], normal };
20914
+ }
20915
+ if (Math.abs(nx) > 1 - 1e-8) {
20916
+ return { u: [0, 1, 0], v: [0, 0, nx > 0 ? 1 : -1], normal };
20917
+ }
20918
+ const reference = Math.abs(nx) < 0.9 ? [1, 0, 0] : [0, 1, 0];
20919
+ const u2 = normalizeVec3$2(crossVec3$1(reference, normal));
20920
+ if (!u2) throw new Error("Shape.fromSlices profile u axis is invalid");
20921
+ return { u: u2, v: crossVec3$1(normal, u2), normal };
20922
+ }
20923
+ function buildFromSlicesLocalToWorldTransform(oc, normal) {
20924
+ const frame = fromSlicesPlaneFrameForOCCT(normal);
20465
20925
  const trsf = new oc.gp_Trsf_1();
20466
- if (Math.abs(nx) < 1e-10 && Math.abs(ny) < 1e-10) {
20467
- if (nz < 0) {
20468
- trsf.SetRotation_1(new oc.gp_Ax1_2(new oc.gp_Pnt_3(0, 0, 0), new oc.gp_Dir_4(1, 0, 0)), Math.PI);
20469
- }
20470
- return trsf;
20471
- }
20472
- const ax = -ny;
20473
- const ay = nx;
20474
- const az = 0;
20475
- const len = Math.sqrt(ax * ax + ay * ay + az * az);
20476
- const angle = Math.acos(Math.max(-1, Math.min(1, nz)));
20477
- trsf.SetRotation_1(new oc.gp_Ax1_2(new oc.gp_Pnt_3(0, 0, 0), new oc.gp_Dir_4(ax / len, ay / len, az / len)), angle);
20926
+ trsf.SetValues(
20927
+ frame.u[0],
20928
+ frame.v[0],
20929
+ frame.normal[0],
20930
+ 0,
20931
+ frame.u[1],
20932
+ frame.v[1],
20933
+ frame.normal[1],
20934
+ 0,
20935
+ frame.u[2],
20936
+ frame.v[2],
20937
+ frame.normal[2],
20938
+ 0
20939
+ );
20478
20940
  return trsf;
20479
20941
  }
20942
+ function axisAlignedDirectionForOCCT(vector) {
20943
+ let bestAxis = 0;
20944
+ let best = Math.abs(vector[0]);
20945
+ for (const axis of [1, 2]) {
20946
+ const value = Math.abs(vector[axis]);
20947
+ if (value > best) {
20948
+ best = value;
20949
+ bestAxis = axis;
20950
+ }
20951
+ }
20952
+ if (best < 1 - 1e-8) return null;
20953
+ return { axis: bestAxis, sign: vector[bestAxis] < 0 ? -1 : 1 };
20954
+ }
20955
+ function centeredEllipseProfileRadiiForOCCT(plan) {
20956
+ if (plan.kind !== "circle") return null;
20957
+ let rx = Math.abs(plan.radius);
20958
+ let ry = Math.abs(plan.radius);
20959
+ for (const transform of plan.transforms) {
20960
+ if (transform.kind !== "scale") return null;
20961
+ rx *= Math.abs(transform.x);
20962
+ ry *= Math.abs(transform.y);
20963
+ }
20964
+ return rx > 1e-10 && ry > 1e-10 ? [rx, ry] : null;
20965
+ }
20966
+ function setEllipsoidRadiusForOCCT(radii, axis, radius) {
20967
+ const existing = radii[axis];
20968
+ if (existing == null) {
20969
+ radii[axis] = radius;
20970
+ return true;
20971
+ }
20972
+ const tolerance = Math.max(1e-7, Math.max(Math.abs(existing), Math.abs(radius)) * 1e-7);
20973
+ return Math.abs(existing - radius) <= tolerance;
20974
+ }
20975
+ function buildRationalQuadraticEdgeForOCCT(oc, points, weights) {
20976
+ const poles = new oc.TColgp_Array1OfPnt_2(1, 3);
20977
+ const weightArr = new oc.TColStd_Array1OfReal_2(1, 3);
20978
+ for (let i = 0; i < 3; i++) {
20979
+ const [x2, y2, z2] = points[i];
20980
+ poles.SetValue(i + 1, new oc.gp_Pnt_3(x2, y2, z2));
20981
+ weightArr.SetValue(i + 1, weights[i]);
20982
+ }
20983
+ const knots = new oc.TColStd_Array1OfReal_2(1, 2);
20984
+ knots.SetValue(1, 0);
20985
+ knots.SetValue(2, 1);
20986
+ const mults = new oc.TColStd_Array1OfInteger_2(1, 2);
20987
+ mults.SetValue(1, 3);
20988
+ mults.SetValue(2, 3);
20989
+ const curve = new oc.Geom_BSplineCurve_2(poles, weightArr, knots, mults, 2, false, true);
20990
+ const handle = new oc.Handle_Geom_BSplineCurve_2(curve);
20991
+ return new oc.BRepBuilderAPI_MakeEdge_24(new oc.Handle_Geom_Curve_2(handle.get())).Edge();
20992
+ }
20993
+ function lowerAxisymmetricEllipsoidForOCCT(oc, radius, heightRadius) {
20994
+ const w2 = Math.SQRT1_2;
20995
+ const mkWire = new oc.BRepBuilderAPI_MakeWire_1();
20996
+ mkWire.Add_1(
20997
+ buildRationalQuadraticEdgeForOCCT(
20998
+ oc,
20999
+ [
21000
+ [0, 0, heightRadius],
21001
+ [radius, 0, heightRadius],
21002
+ [radius, 0, 0]
21003
+ ],
21004
+ [1, w2, 1]
21005
+ )
21006
+ );
21007
+ mkWire.Add_1(
21008
+ buildRationalQuadraticEdgeForOCCT(
21009
+ oc,
21010
+ [
21011
+ [radius, 0, 0],
21012
+ [radius, 0, -heightRadius],
21013
+ [0, 0, -heightRadius]
21014
+ ],
21015
+ [1, w2, 1]
21016
+ )
21017
+ );
21018
+ 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());
21019
+ const face = buildFaceFromWire(oc, mkWire.Wire());
21020
+ const axis = new oc.gp_Ax1_2(new oc.gp_Pnt_3(0, 0, 0), new oc.gp_Dir_4(0, 0, 1));
21021
+ const revol = new oc.BRepPrimAPI_MakeRevol_1(face, axis, Math.PI * 2, true);
21022
+ revol.Build(new oc.Message_ProgressRange_1());
21023
+ if (!revol.IsDone()) {
21024
+ throw new Error("Shape.fromSlices exact ellipsoid revolve failed");
21025
+ }
21026
+ return revol.Shape();
21027
+ }
21028
+ function lowerOrthogonalEllipseFromSlicesPlan(oc, plan) {
21029
+ if (plan.groups.length !== 2) return null;
21030
+ const center = [0, 0, 0];
21031
+ const radii = [null, null, null];
21032
+ for (const group of plan.groups) {
21033
+ if (group.slices.length !== 1) return null;
21034
+ const slice = group.slices[0];
21035
+ const profileRadii = centeredEllipseProfileRadiiForOCCT(slice.profile);
21036
+ if (!profileRadii) return null;
21037
+ const frame = fromSlicesPlaneFrameForOCCT(group.normal);
21038
+ const normalAxis = axisAlignedDirectionForOCCT(frame.normal);
21039
+ const uAxis = axisAlignedDirectionForOCCT(frame.u);
21040
+ const vAxis = axisAlignedDirectionForOCCT(frame.v);
21041
+ if (!normalAxis || !uAxis || !vAxis) return null;
21042
+ center[normalAxis.axis] = normalAxis.sign * slice.offset;
21043
+ if (!setEllipsoidRadiusForOCCT(radii, uAxis.axis, profileRadii[0])) return null;
21044
+ if (!setEllipsoidRadiusForOCCT(radii, vAxis.axis, profileRadii[1])) return null;
21045
+ }
21046
+ if (radii.some((radius) => radius == null)) return null;
21047
+ const rx = radii[0];
21048
+ const ry = radii[1];
21049
+ const rz = radii[2];
21050
+ const axisymmetricTolerance = Math.max(1e-7, Math.max(Math.abs(rx), Math.abs(ry)) * 1e-7);
21051
+ if (Math.abs(rx - ry) > axisymmetricTolerance) return null;
21052
+ let shape = lowerAxisymmetricEllipsoidForOCCT(oc, (rx + ry) / 2, rz);
21053
+ if (Math.hypot(center[0], center[1], center[2]) > 1e-10) {
21054
+ const trsf = new oc.gp_Trsf_1();
21055
+ trsf.SetTranslation_1(new oc.gp_Vec_4(center[0], center[1], center[2]));
21056
+ const moved = new oc.BRepBuilderAPI_Transform_2(shape, trsf, true);
21057
+ shape = moved.Shape();
21058
+ }
21059
+ return shape;
21060
+ }
20480
21061
  function lowerFromSlicesGroup(oc, group, singleSliceHalfExtent) {
20481
21062
  const { normal, slices } = group;
20482
21063
  const sorted = [...slices].sort((a2, b) => a2.offset - b.offset);
20483
- const rotTrsf = buildZToNormalTransform(oc, normal);
21064
+ const localToWorldTrsf = buildFromSlicesLocalToWorldTransform(oc, normal);
20484
21065
  if (sorted.length === 1) {
20485
21066
  const s = sorted[0];
20486
21067
  const rawFace = lowerProfileToFace(oc, s.profile);
@@ -20494,7 +21075,7 @@ function lowerFromSlicesGroup(oc, group, singleSliceHalfExtent) {
20494
21075
  centerTrsf.SetTranslation_1(new oc.gp_Vec_4(0, 0, s.offset - extrudeHalf));
20495
21076
  const centered = new oc.BRepBuilderAPI_Transform_2(shape, centerTrsf, true);
20496
21077
  shape = centered.Shape();
20497
- const rotated = new oc.BRepBuilderAPI_Transform_2(shape, rotTrsf, true);
21078
+ const rotated = new oc.BRepBuilderAPI_Transform_2(shape, localToWorldTrsf, true);
20498
21079
  return rotated.Shape();
20499
21080
  }
20500
21081
  const ts = new oc.BRepOffsetAPI_ThruSections(true, false, 1e-6);
@@ -20505,10 +21086,8 @@ function lowerFromSlicesGroup(oc, group, singleSliceHalfExtent) {
20505
21086
  wire = toBSplineWireIfNeeded(oc, wire);
20506
21087
  const placeTrsf = new oc.gp_Trsf_1();
20507
21088
  placeTrsf.SetTranslation_1(new oc.gp_Vec_4(0, 0, s.offset));
20508
- const compound = new oc.gp_Trsf_1();
20509
- compound.Multiply(rotTrsf);
20510
- compound.Multiply(placeTrsf);
20511
- const transformed = new oc.BRepBuilderAPI_Transform_2(wire, compound, true);
21089
+ const placed = new oc.BRepBuilderAPI_Transform_2(wire, placeTrsf, true);
21090
+ const transformed = new oc.BRepBuilderAPI_Transform_2(placed.Shape(), localToWorldTrsf, true);
20512
21091
  ts.AddWire(oc.TopoDS.Wire_1(transformed.Shape()));
20513
21092
  }
20514
21093
  ts.CheckCompatibility(true);
@@ -20520,6 +21099,8 @@ function lowerFromSlicesGroup(oc, group, singleSliceHalfExtent) {
20520
21099
  }
20521
21100
  function lowerFromSlicesPlan$1(oc, plan) {
20522
21101
  if (plan.groups.length === 0) throw new Error("Shape.fromSlices requires at least one slice");
21102
+ const exactEllipsoid = lowerOrthogonalEllipseFromSlicesPlan(oc, plan);
21103
+ if (exactEllipsoid) return exactEllipsoid;
20523
21104
  const singleSliceHalfExtent = fromSlicesSingleSliceHalfExtentForOCCT(plan);
20524
21105
  const groupSolids = plan.groups.map((g2) => lowerFromSlicesGroup(oc, g2, singleSliceHalfExtent));
20525
21106
  if (groupSolids.length === 1) return groupSolids[0];
@@ -20546,14 +21127,40 @@ function buildPolylineSpineWire(oc, points) {
20546
21127
  }
20547
21128
  return mkWire.Wire();
20548
21129
  }
21130
+ function buildRoute3DSpineWire(oc, path) {
21131
+ if (path.segments.length === 0) throw new Error("Route3D sweep path needs at least one segment");
21132
+ const mkWire = new oc.BRepBuilderAPI_MakeWire_1();
21133
+ for (const segment of path.segments) {
21134
+ if (segment.kind === "line") {
21135
+ const [x1, y1, z1] = segment.from;
21136
+ const [x2, y2, z2] = segment.to;
21137
+ if (x1 === x2 && y1 === y2 && z1 === z2) continue;
21138
+ 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());
21139
+ continue;
21140
+ }
21141
+ const radial = normalizeVec3$2(subtractVec3(segment.start, segment.center));
21142
+ if (!radial) throw new Error("Route3D sweep arc has a collapsed start radius");
21143
+ const axis = new oc.gp_Ax2_2(
21144
+ new oc.gp_Pnt_3(segment.center[0], segment.center[1], segment.center[2]),
21145
+ new oc.gp_Dir_4(segment.axis[0], segment.axis[1], segment.axis[2]),
21146
+ new oc.gp_Dir_4(radial[0], radial[1], radial[2])
21147
+ );
21148
+ const circle = new oc.gp_Circ_2(axis, segment.radius);
21149
+ mkWire.Add_1(new oc.BRepBuilderAPI_MakeEdge_9(circle, 0, segment.sweepDeg * Math.PI / 180).Edge());
21150
+ }
21151
+ return mkWire.Wire();
21152
+ }
20549
21153
  function buildSpineWireFromPlan(oc, path, pathSamples) {
20550
21154
  switch (path.kind) {
20551
21155
  case "polyline":
20552
21156
  return buildPolylineSpineWire(oc, path.points);
21157
+ case "route3d":
21158
+ return buildRoute3DSpineWire(oc, path);
21159
+ case "nurbs":
21160
+ return new oc.BRepBuilderAPI_MakeWire_2(buildCurveEdgeFromSweepPathPlan(oc, path, "OCCT sweep path")).Wire();
20553
21161
  case "catmull-rom":
20554
21162
  case "hermite":
20555
21163
  case "quintic-hermite":
20556
- case "nurbs":
20557
21164
  return buildPolylineSpineWire(oc, sweepPathToPolyline(path, pathSamples ?? 48));
20558
21165
  }
20559
21166
  }
@@ -29418,18 +30025,18 @@ function faceAxes(face) {
29418
30025
  }
29419
30026
  return {};
29420
30027
  }
29421
- function edgeKey$1(start, end) {
30028
+ function edgeKey$2(start, end) {
29422
30029
  const encode = (p2) => p2.map((value) => value.toFixed(9)).join(",");
29423
30030
  const a2 = encode(start);
29424
30031
  const b = encode(end);
29425
30032
  return a2 < b ? `${a2}|${b}` : `${b}|${a2}`;
29426
30033
  }
29427
30034
  function faceEdgeIndex(face, start, end) {
29428
- const target = edgeKey$1(start, end);
30035
+ const target = edgeKey$2(start, end);
29429
30036
  for (let i = 0; i < face.vertices.length; i++) {
29430
30037
  const faceStart = face.vertices[i];
29431
30038
  const faceEnd = face.vertices[(i + 1) % face.vertices.length];
29432
- if (faceStart && faceEnd && edgeKey$1(faceStart, faceEnd) === target) return i;
30039
+ if (faceStart && faceEnd && edgeKey$2(faceStart, faceEnd) === target) return i;
29433
30040
  }
29434
30041
  return null;
29435
30042
  }
@@ -29630,7 +30237,7 @@ function topologyPayloadToTopology(payload) {
29630
30237
  const start = explicitVertices[explicitEdge.vertices[0]];
29631
30238
  const end = explicitVertices[explicitEdge.vertices[1]];
29632
30239
  if (!isVec3(start) || !isVec3(end)) continue;
29633
- const key = edgeKey$1(start, end);
30240
+ const key = edgeKey$2(start, end);
29634
30241
  if (seenEdges.has(key)) continue;
29635
30242
  seenEdges.set(key, edges.size);
29636
30243
  const display = explicitEdgeDisplayName(payload, explicitEdge, explicitEdgeIndex, start, end);
@@ -29650,7 +30257,7 @@ function topologyPayloadToTopology(payload) {
29650
30257
  const start = face.vertices[i];
29651
30258
  const end = face.vertices[(i + 1) % face.vertices.length];
29652
30259
  if (!start || !end) continue;
29653
- const key = edgeKey$1(start, end);
30260
+ const key = edgeKey$2(start, end);
29654
30261
  if (seenEdges.has(key)) continue;
29655
30262
  seenEdges.set(key, edges.size);
29656
30263
  edges.set(`${face.id}:edge-${i}`, {
@@ -31521,7 +32128,7 @@ function nurbsSurfaceForPlan(plan) {
31521
32128
  return null;
31522
32129
  }
31523
32130
  function lowerImportedMeshPlan(plan) {
31524
- const parsed = parseMeshFile(plan.fileData, plan.format);
32131
+ const parsed = parseMeshFile(plan.fileData, plan.format, { object: plan.object });
31525
32132
  if (parsed.numProp !== 3) {
31526
32133
  throw new Error(`importMesh("${plan.filePath}"): expected xyz vertex data`);
31527
32134
  }
@@ -31824,12 +32431,18 @@ function applyNativeTruckFilletTargets(initialShape, targets, radius, segments,
31824
32431
  }
31825
32432
  }
31826
32433
  function lowerFilletEdgesPlan(plan) {
32434
+ if (plan.edgeQuery) {
32435
+ throw new Error("filletEdges(): deferred edge queries are not supported by the Truck lowerer yet.");
32436
+ }
31827
32437
  const shape = lowerShapeCompilePlanToTruckBackend(plan.base);
31828
- return applyNativeTruckFilletTargets(shape, plan.edgeTargets, plan.radius, plan.segments, "filletEdges()");
32438
+ return applyNativeTruckFilletTargets(shape, plan.edgeTargets ?? [], plan.radius, plan.segments, "filletEdges()");
31829
32439
  }
31830
32440
  function lowerChamferEdgesPlan(plan) {
32441
+ if (plan.edgeQuery) {
32442
+ throw new Error("chamferEdges(): deferred edge queries are not supported by the Truck lowerer yet.");
32443
+ }
31831
32444
  const shape = lowerShapeCompilePlanToTruckBackend(plan.base);
31832
- return applyNativeTruckChamferTargets(shape, plan.edgeTargets, plan.size, "chamferEdges()");
32445
+ return applyNativeTruckChamferTargets(shape, plan.edgeTargets ?? [], plan.size, "chamferEdges()");
31833
32446
  }
31834
32447
  function resolvedEdgeFeatureSelectionToTarget(selection) {
31835
32448
  return {
@@ -37846,8 +38459,8 @@ function resolveShapeFace(plan, name) {
37846
38459
  const DEPRECATED_SIDE_NAMES = {
37847
38460
  "side-left": "left",
37848
38461
  "side-right": "right",
37849
- "side-top": "front",
37850
- "side-bottom": "back"
38462
+ "side-top": "back",
38463
+ "side-bottom": "front"
37851
38464
  };
37852
38465
  function explainMissingShapeFace(plan, name) {
37853
38466
  const table = resolveShapeFaceTable(plan);
@@ -38926,11 +39539,14 @@ class ShapeGroup {
38926
39539
  }
38927
39540
  /** Move the entire group by (x, y, z). All children move together as a unit. */
38928
39541
  translate(x2, y2, z2) {
38929
- const matrix = Transform.translation(x2, y2, z2).toArray();
39542
+ const dx = requireFiniteNumber(x2, "ShapeGroup.translate() x");
39543
+ const dy = requireFiniteNumber(y2, "ShapeGroup.translate() y");
39544
+ const dz = requireFiniteNumber(z2, "ShapeGroup.translate() z");
39545
+ const matrix = Transform.translation(dx, dy, dz).toArray();
38930
39546
  return this.mapChildrenTransform((c2) => {
38931
- if (c2 instanceof ShapeGroup) return c2.translate(x2, y2, z2);
38932
- if (c2 instanceof Shape) return c2.translate(x2, y2, z2);
38933
- return c2.translate(x2, y2);
39547
+ if (c2 instanceof ShapeGroup) return c2.translate(dx, dy, dz);
39548
+ if (c2 instanceof Shape) return c2.translate(dx, dy, dz);
39549
+ return c2.translate(dx, dy);
38934
39550
  }, matrix);
38935
39551
  }
38936
39552
  /** Compute combined bounding box of all 3D children */
@@ -38966,17 +39582,23 @@ class ShapeGroup {
38966
39582
  return { min: bb.min, max: bb.max };
38967
39583
  }
38968
39584
  resolveRotatePoint(point) {
38969
- if (Array.isArray(point)) return [point[0], point[1], point[2]];
39585
+ if (Array.isArray(point)) return requireFiniteVec3$1(point, "ShapeGroup.rotateAroundTo() point");
38970
39586
  const bb = this._bbox();
38971
39587
  return resolveAnchor3D(bb.min, bb.max, point);
38972
39588
  }
38973
39589
  /** Move the group so its bounding-box min corner lands at the given coordinate. */
38974
39590
  moveTo(x2, y2, z2) {
39591
+ const targetX = requireFiniteNumber(x2, "ShapeGroup.moveTo() x");
39592
+ const targetY = requireFiniteNumber(y2, "ShapeGroup.moveTo() y");
39593
+ const targetZ = requireFiniteNumber(z2, "ShapeGroup.moveTo() z");
38975
39594
  const bb = this._bbox();
38976
- return this.translate(x2 - bb.min[0], y2 - bb.min[1], z2 - bb.min[2]);
39595
+ return this.translate(targetX - bb.min[0], targetY - bb.min[1], targetZ - bb.min[2]);
38977
39596
  }
38978
39597
  /** Move the group relative to another part's bounding-box min corner. */
38979
39598
  moveToLocal(target, x2, y2, z2) {
39599
+ const localX = requireFiniteNumber(x2, "ShapeGroup.moveToLocal() x");
39600
+ const localY = requireFiniteNumber(y2, "ShapeGroup.moveToLocal() y");
39601
+ const localZ = requireFiniteNumber(z2, "ShapeGroup.moveToLocal() z");
38980
39602
  let tbb;
38981
39603
  if (target instanceof ShapeGroup) {
38982
39604
  tbb = target._bbox();
@@ -38984,7 +39606,7 @@ class ShapeGroup {
38984
39606
  const bb = target.boundingBox();
38985
39607
  tbb = { min: bb.min };
38986
39608
  }
38987
- return this.moveTo(tbb.min[0] + x2, tbb.min[1] + y2, tbb.min[2] + z2);
39609
+ return this.moveTo(tbb.min[0] + localX, tbb.min[1] + localY, tbb.min[2] + localZ);
38988
39610
  }
38989
39611
  /**
38990
39612
  * Attach this group to a face or anchor on another part.
@@ -39013,9 +39635,10 @@ class ShapeGroup {
39013
39635
  const sp = resolveAnchor3D(sbb.min, sbb.max, selfAnchor);
39014
39636
  let dx = tp[0] - sp[0], dy = tp[1] - sp[1], dz = tp[2] - sp[2];
39015
39637
  if (offset) {
39016
- dx += offset[0];
39017
- dy += offset[1];
39018
- dz += offset[2];
39638
+ const offsetPoint = requireFiniteVec3$1(offset, "ShapeGroup.attachTo() offset");
39639
+ dx += offsetPoint[0];
39640
+ dy += offsetPoint[1];
39641
+ dz += offsetPoint[2];
39019
39642
  }
39020
39643
  return this.translate(dx, dy, dz);
39021
39644
  }
@@ -39024,7 +39647,9 @@ class ShapeGroup {
39024
39647
  * See Shape.onFace() for full documentation.
39025
39648
  */
39026
39649
  onFace(parent, face, opts = {}) {
39027
- const u2 = opts.u ?? 0, v = opts.v ?? 0, p2 = opts.protrude ?? 0;
39650
+ const u2 = requireFiniteNumber(opts.u ?? 0, "ShapeGroup.onFace() u");
39651
+ const v = requireFiniteNumber(opts.v ?? 0, "ShapeGroup.onFace() v");
39652
+ const p2 = requireFiniteNumber(opts.protrude ?? 0, "ShapeGroup.onFace() protrude");
39028
39653
  const opp = { front: "back", back: "front", left: "right", right: "left", top: "bottom", bottom: "top" };
39029
39654
  const uvMap = {
39030
39655
  front: (u22, v2, p22) => [u22, -p22, v2],
@@ -39063,20 +39688,31 @@ class ShapeGroup {
39063
39688
  }
39064
39689
  /** Rotate around an arbitrary axis, optionally through a pivot point. */
39065
39690
  rotateAroundAxis(axis, angleDeg, pivot = [0, 0, 0]) {
39066
- return this.transform(Transform.rotationAxis(axis, angleDeg, pivot));
39691
+ const rotateAxis = requireNonZeroFiniteVec3(axis, "ShapeGroup.rotateAroundAxis() axis");
39692
+ const degrees = requireFiniteNumber(angleDeg, "ShapeGroup.rotateAroundAxis() angleDeg");
39693
+ const rotatePivot = requireFiniteVec3$1(pivot, "ShapeGroup.rotateAroundAxis() pivot");
39694
+ return this.transform(Transform.rotationAxis(rotateAxis, degrees, rotatePivot));
39067
39695
  }
39068
39696
  /**
39069
39697
  * Rotate around an axis until a moving point reaches the target line/plane defined by the axis and target point.
39070
39698
  * ShapeGroup string points use built-in anchors only.
39071
39699
  */
39072
39700
  rotateAroundTo(axis, pivot, movingPoint, targetPoint, options = {}) {
39701
+ const rotateAxis = requireNonZeroFiniteVec3(axis, "ShapeGroup.rotateAroundTo() axis");
39702
+ const rotatePivot = requireFiniteVec3$1(pivot, "ShapeGroup.rotateAroundTo() pivot");
39073
39703
  return this.transform(
39074
- Transform.rotateAroundTo(axis, pivot, this.resolveRotatePoint(movingPoint), this.resolveRotatePoint(targetPoint), options)
39704
+ Transform.rotateAroundTo(
39705
+ rotateAxis,
39706
+ rotatePivot,
39707
+ this.resolveRotatePoint(movingPoint),
39708
+ this.resolveRotatePoint(targetPoint),
39709
+ options
39710
+ )
39075
39711
  );
39076
39712
  }
39077
39713
  /** Reorient the group so its local Z axis points along `direction`. */
39078
39714
  pointAlong(direction) {
39079
- const [dx, dy, dz] = direction;
39715
+ const [dx, dy, dz] = requireNonZeroFiniteVec3(direction, "ShapeGroup.pointAlong() direction");
39080
39716
  const len = Math.sqrt(dx * dx + dy * dy + dz * dz) || 1;
39081
39717
  const nx = dx / len, ny = dy / len, nz = dz / len;
39082
39718
  const cx = -ny, cy = nx, cz = 0;
@@ -39091,11 +39727,11 @@ class ShapeGroup {
39091
39727
  }
39092
39728
  /** Apply a 4x4 transform matrix or `Transform` to all 3D children. */
39093
39729
  transform(m2) {
39094
- const matrix = m2 instanceof Transform ? m2.toArray() : m2;
39730
+ const matrix = m2 instanceof Transform ? m2.toArray() : requireFiniteMat4(m2, "ShapeGroup.transform() matrix");
39095
39731
  const next = new ShapeGroup(
39096
39732
  this.children.map((c2) => {
39097
- if (c2 instanceof ShapeGroup) return c2.transform(m2);
39098
- if (c2 instanceof Shape) return c2.transform(m2);
39733
+ if (c2 instanceof ShapeGroup) return c2.transform(matrix);
39734
+ if (c2 instanceof Shape) return c2.transform(matrix);
39099
39735
  throw new Error(
39100
39736
  "ShapeGroup.transform only supports 3D children (Shape/ShapeGroup). For Sketch children, use 2D transforms (translate/rotate/scale/mirror)."
39101
39737
  );
@@ -39108,25 +39744,28 @@ class ShapeGroup {
39108
39744
  }
39109
39745
  /** Scale uniformly or per-axis from the group's bounding-box center. */
39110
39746
  scale(v) {
39111
- return this.scaleAround(this._bboxCenter(), v);
39747
+ const scale2 = requireNonZeroFiniteScale3(v, "ShapeGroup.scale() scale");
39748
+ return this.scaleAround(this._bboxCenter(), scale2);
39112
39749
  }
39113
39750
  /** Scale uniformly or per-axis from an explicit pivot point. */
39114
39751
  scaleAround(pivot, v) {
39115
- const matrix = Transform.scale(v).toArray();
39116
- if (pivot[0] === 0 && pivot[1] === 0 && pivot[2] === 0) {
39752
+ const scale2 = requireNonZeroFiniteScale3(v, "ShapeGroup.scaleAround() scale");
39753
+ const scalePivot = requireFiniteVec3$1(pivot, "ShapeGroup.scaleAround() pivot");
39754
+ const matrix = Transform.scale(scale2).toArray();
39755
+ if (scalePivot[0] === 0 && scalePivot[1] === 0 && scalePivot[2] === 0) {
39117
39756
  return this.mapChildrenTransform((c2) => {
39118
- if (c2 instanceof ShapeGroup) return c2.scaleAround([0, 0, 0], v);
39119
- if (c2 instanceof Shape) return c2.scaleAround([0, 0, 0], v);
39120
- return c2.scale(typeof v === "number" ? v : [v[0], v[1]]);
39757
+ if (c2 instanceof ShapeGroup) return c2.scaleAround([0, 0, 0], scale2);
39758
+ if (c2 instanceof Shape) return c2.scaleAround([0, 0, 0], scale2);
39759
+ return c2.scale([scale2[0], scale2[1]]);
39121
39760
  }, matrix);
39122
39761
  }
39123
- const moved = this.translate(-pivot[0], -pivot[1], -pivot[2]);
39762
+ const moved = this.translate(-scalePivot[0], -scalePivot[1], -scalePivot[2]);
39124
39763
  const scaled = moved.mapChildrenTransform((c2) => {
39125
- if (c2 instanceof ShapeGroup) return c2.scaleAround([0, 0, 0], v);
39126
- if (c2 instanceof Shape) return c2.scaleAround([0, 0, 0], v);
39127
- return c2.scale(typeof v === "number" ? v : [v[0], v[1]]);
39764
+ if (c2 instanceof ShapeGroup) return c2.scaleAround([0, 0, 0], scale2);
39765
+ if (c2 instanceof Shape) return c2.scaleAround([0, 0, 0], scale2);
39766
+ return c2.scale([scale2[0], scale2[1]]);
39128
39767
  }, matrix);
39129
- return scaled.translate(pivot[0], pivot[1], pivot[2]);
39768
+ return scaled.translate(scalePivot[0], scalePivot[1], scalePivot[2]);
39130
39769
  }
39131
39770
  /** Mirror across a plane through the group's bounding-box center. */
39132
39771
  mirror(normal) {
@@ -39134,21 +39773,23 @@ class ShapeGroup {
39134
39773
  }
39135
39774
  /** Mirror across a plane through an explicit point. */
39136
39775
  mirrorThrough(point, normal) {
39137
- const matrix = mirrorPlaneMatrix(normal);
39138
- if (point[0] === 0 && point[1] === 0 && point[2] === 0) {
39776
+ const mirrorPoint = requireFiniteVec3$1(point, "ShapeGroup.mirrorThrough() point");
39777
+ const mirrorNormal = requireNonZeroFiniteVec3(normal, "ShapeGroup.mirrorThrough() normal");
39778
+ const matrix = mirrorPlaneMatrix(mirrorNormal);
39779
+ if (mirrorPoint[0] === 0 && mirrorPoint[1] === 0 && mirrorPoint[2] === 0) {
39139
39780
  return this.mapChildrenTransform((c2) => {
39140
- if (c2 instanceof ShapeGroup) return c2.mirrorThrough([0, 0, 0], normal);
39141
- if (c2 instanceof Shape) return c2.mirrorThrough([0, 0, 0], normal);
39142
- return c2.mirror([normal[0], normal[1]]);
39781
+ if (c2 instanceof ShapeGroup) return c2.mirrorThrough([0, 0, 0], mirrorNormal);
39782
+ if (c2 instanceof Shape) return c2.mirrorThrough([0, 0, 0], mirrorNormal);
39783
+ return c2.mirror([mirrorNormal[0], mirrorNormal[1]]);
39143
39784
  }, matrix);
39144
39785
  }
39145
- const moved = this.translate(-point[0], -point[1], -point[2]);
39786
+ const moved = this.translate(-mirrorPoint[0], -mirrorPoint[1], -mirrorPoint[2]);
39146
39787
  const mirrored = moved.mapChildrenTransform((c2) => {
39147
- if (c2 instanceof ShapeGroup) return c2.mirrorThrough([0, 0, 0], normal);
39148
- if (c2 instanceof Shape) return c2.mirrorThrough([0, 0, 0], normal);
39149
- return c2.mirror([normal[0], normal[1]]);
39788
+ if (c2 instanceof ShapeGroup) return c2.mirrorThrough([0, 0, 0], mirrorNormal);
39789
+ if (c2 instanceof Shape) return c2.mirrorThrough([0, 0, 0], mirrorNormal);
39790
+ return c2.mirror([mirrorNormal[0], mirrorNormal[1]]);
39150
39791
  }, matrix);
39151
- return mirrored.translate(point[0], point[1], point[2]);
39792
+ return mirrored.translate(mirrorPoint[0], mirrorPoint[1], mirrorPoint[2]);
39152
39793
  }
39153
39794
  /** Return a copy of the group with the given display color applied to each child. */
39154
39795
  color(hex) {
@@ -39220,14 +39861,16 @@ class ShapeGroup {
39220
39861
  * ```
39221
39862
  */
39222
39863
  placeReference(ref, target, offset) {
39864
+ const targetPoint = requireFiniteVec3$1(target, "ShapeGroup.placeReference() target");
39865
+ const offsetPoint = offset === void 0 ? void 0 : requireFiniteVec3$1(offset, "ShapeGroup.placeReference() offset");
39223
39866
  const sourcePoint = this.referencePoint(ref);
39224
- let dx = target[0] - sourcePoint[0];
39225
- let dy = target[1] - sourcePoint[1];
39226
- let dz = target[2] - sourcePoint[2];
39227
- if (offset) {
39228
- dx += offset[0];
39229
- dy += offset[1];
39230
- dz += offset[2];
39867
+ let dx = targetPoint[0] - sourcePoint[0];
39868
+ let dy = targetPoint[1] - sourcePoint[1];
39869
+ let dz = targetPoint[2] - sourcePoint[2];
39870
+ if (offsetPoint) {
39871
+ dx += offsetPoint[0];
39872
+ dy += offsetPoint[1];
39873
+ dz += offsetPoint[2];
39231
39874
  }
39232
39875
  return this.translate(dx, dy, dz);
39233
39876
  }
@@ -40159,14 +40802,21 @@ function attachTopologyRewritePropagation(plan, propagation) {
40159
40802
  }
40160
40803
  return cloneNodeWithPropagation(plan, propagation);
40161
40804
  }
40805
+ const DEFERRED_EDGE_SELECTION_MARKER = Symbol.for("forgecad.deferredEdgeSelection");
40806
+ function isDeferredEdgeSelection(value) {
40807
+ return Boolean(value && typeof value === "object" && value[DEFERRED_EDGE_SELECTION_MARKER] === true);
40808
+ }
40162
40809
  function distSq$1(a2, b) {
40163
- const dx = a2[0] - b[0], dy = a2[1] - b[1], dz = a2[2] - b[2];
40810
+ const dx = a2[0] - b[0];
40811
+ const dy = a2[1] - b[1];
40812
+ const dz = a2[2] - b[2];
40164
40813
  return dx * dx + dy * dy + dz * dz;
40165
40814
  }
40166
40815
  function absDot(a2, b) {
40167
40816
  return Math.abs(a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2]);
40168
40817
  }
40169
40818
  function coalesceEdges(segments, tolerance = 0.01) {
40819
+ if (segments.some(isDeferredEdgeSelection)) return segments;
40170
40820
  if (segments.length <= 1) return segments;
40171
40821
  const used = new Uint8Array(segments.length);
40172
40822
  const result = [];
@@ -40889,16 +41539,6 @@ function mirrorMatrix(normal) {
40889
41539
  const m22 = 1 - 2 * nz * nz;
40890
41540
  return [m00, m10, m20, 0, m01, m11, m21, 0, m02, m12, m22, 0, 0, 0, 0, 1];
40891
41541
  }
40892
- function normalizeShapeScale(v) {
40893
- const scale2 = typeof v === "number" ? [v, v, v] : v;
40894
- if (!Number.isFinite(scale2[0]) || !Number.isFinite(scale2[1]) || !Number.isFinite(scale2[2])) {
40895
- return null;
40896
- }
40897
- if (Math.abs(scale2[0]) < 1e-12 || Math.abs(scale2[1]) < 1e-12 || Math.abs(scale2[2]) < 1e-12) {
40898
- return null;
40899
- }
40900
- return [scale2[0], scale2[1], scale2[2]];
40901
- }
40902
41542
  function dotVec3$1(a2, b) {
40903
41543
  return a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2];
40904
41544
  }
@@ -42051,7 +42691,7 @@ function withBaseDimensionsAndMergedSourceSpans(base, sources, out, preserveOutp
42051
42691
  return result;
42052
42692
  }
42053
42693
  function resolveRotationPoint(shape, point) {
42054
- if (Array.isArray(point)) return [point[0], point[1], point[2]];
42694
+ if (Array.isArray(point)) return requireFiniteVec3$1(point, "rotateAroundTo(): point");
42055
42695
  return shape.referencePoint(point);
42056
42696
  }
42057
42697
  function setShapePlacementReferences(shape, refs, options = {}) {
@@ -42779,7 +43419,9 @@ class Shape {
42779
43419
  edgesOf(faceLabel, options) {
42780
43420
  const faceRefs = this._resolveFaceLabels(faceLabel);
42781
43421
  const result = uniqueEdgeSegments(
42782
- faceRefs.flatMap((faceRef) => edgesOfFace(this, faceRef, options, (name) => this._resolveFaceLabel(name)))
43422
+ faceRefs.flatMap((faceRef) => {
43423
+ return edgesOfFace(this, faceRef, options, (name) => this._resolveFaceLabel(name));
43424
+ })
42783
43425
  );
42784
43426
  if (result.length === 0) {
42785
43427
  throw new Error(
@@ -42827,7 +43469,11 @@ class Shape {
42827
43469
  const refsA = this._resolveFaceLabels(faceA);
42828
43470
  const bNames = Array.isArray(faceB) ? faceB : [faceB];
42829
43471
  const refBs = bNames.flatMap((name) => this._resolveFaceLabels(name));
42830
- const result = uniqueEdgeSegments(refsA.flatMap((refA) => edgesBetweenFaces(this, refA, refBs)));
43472
+ const result = uniqueEdgeSegments(
43473
+ refsA.flatMap((refA) => {
43474
+ return edgesBetweenFaces(this, refA, refBs);
43475
+ })
43476
+ );
42831
43477
  if (result.length === 0) {
42832
43478
  const bStr = bNames.length === 1 ? `'${bNames[0]}'` : `[${bNames.map((n) => `'${n}'`).join(", ")}]`;
42833
43479
  throw new Error(
@@ -42891,14 +43537,16 @@ class Shape {
42891
43537
  * ```
42892
43538
  */
42893
43539
  placeReference(ref, target, offset) {
43540
+ const targetPoint = requireFiniteVec3$1(target, "Shape.placeReference() target");
43541
+ const offsetPoint = offset === void 0 ? void 0 : requireFiniteVec3$1(offset, "Shape.placeReference() offset");
42894
43542
  const sourcePoint = this.referencePoint(ref);
42895
- let dx = target[0] - sourcePoint[0];
42896
- let dy = target[1] - sourcePoint[1];
42897
- let dz = target[2] - sourcePoint[2];
42898
- if (offset) {
42899
- dx += offset[0];
42900
- dy += offset[1];
42901
- dz += offset[2];
43543
+ let dx = targetPoint[0] - sourcePoint[0];
43544
+ let dy = targetPoint[1] - sourcePoint[1];
43545
+ let dz = targetPoint[2] - sourcePoint[2];
43546
+ if (offsetPoint) {
43547
+ dx += offsetPoint[0];
43548
+ dy += offsetPoint[1];
43549
+ dz += offsetPoint[2];
42902
43550
  }
42903
43551
  return this.translate(dx, dy, dz);
42904
43552
  }
@@ -42910,27 +43558,39 @@ class Shape {
42910
43558
  * Example: `shape.translatePolar(50, 30)` moves 50mm at 30 degrees from +X.
42911
43559
  */
42912
43560
  translatePolar(radius, angleDeg, z2 = 0) {
42913
- const rad = angleDeg * (Math.PI / 180);
42914
- return this.translate(radius * Math.cos(rad), radius * Math.sin(rad), z2);
43561
+ const r = requireFiniteNumber(radius, "Shape.translatePolar() radius");
43562
+ const angle = requireFiniteNumber(angleDeg, "Shape.translatePolar() angleDeg");
43563
+ const zOffset = requireFiniteNumber(z2, "Shape.translatePolar() z");
43564
+ const rad = angle * (Math.PI / 180);
43565
+ return this.translate(r * Math.cos(rad), r * Math.sin(rad), zOffset);
42915
43566
  }
42916
43567
  /** Move the shape relative to its current position. All transforms are immutable and return new shapes. */
42917
43568
  translate(x2, y2, z2) {
42918
- const nextPlan = appendShapeCompileTransform(getShapeCompilePlanInternal(this), { kind: "translate", x: x2, y: y2, z: z2 });
43569
+ const dx = requireFiniteNumber(x2, "Shape.translate() x");
43570
+ const dy = requireFiniteNumber(y2, "Shape.translate() y");
43571
+ const dz = requireFiniteNumber(z2, "Shape.translate() z");
43572
+ const nextPlan = appendShapeCompileTransform(getShapeCompilePlanInternal(this), { kind: "translate", x: dx, y: dy, z: dz });
42919
43573
  return setShapeCompilePlanInternal(
42920
- withTransformedDimensions(this, buildShapeFromCompilePlan(nextPlan, this.colorHex), Transform.translation(x2, y2, z2).toArray()),
43574
+ withTransformedDimensions(this, buildShapeFromCompilePlan(nextPlan, this.colorHex), Transform.translation(dx, dy, dz).toArray()),
42921
43575
  nextPlan
42922
43576
  );
42923
43577
  }
42924
43578
  /** Position the shape so its bounding box min corner is at the given global coordinate. */
42925
43579
  moveTo(x2, y2, z2) {
43580
+ const targetX = requireFiniteNumber(x2, "Shape.moveTo() x");
43581
+ const targetY = requireFiniteNumber(y2, "Shape.moveTo() y");
43582
+ const targetZ = requireFiniteNumber(z2, "Shape.moveTo() z");
42926
43583
  const bb = this.boundingBox();
42927
- return this.translate(x2 - bb.min[0], y2 - bb.min[1], z2 - bb.min[2]);
43584
+ return this.translate(targetX - bb.min[0], targetY - bb.min[1], targetZ - bb.min[2]);
42928
43585
  }
42929
43586
  /** Position the shape relative to another shape's local coordinate system (bounding box min corner). */
42930
43587
  moveToLocal(target, x2, y2, z2) {
43588
+ const localX = requireFiniteNumber(x2, "Shape.moveToLocal() x");
43589
+ const localY = requireFiniteNumber(y2, "Shape.moveToLocal() y");
43590
+ const localZ = requireFiniteNumber(z2, "Shape.moveToLocal() z");
42931
43591
  const s = "toShape" in target ? target.toShape() : target;
42932
43592
  const tbb = s.boundingBox();
42933
- return this.moveTo(tbb.min[0] + x2, tbb.min[1] + y2, tbb.min[2] + z2);
43593
+ return this.moveTo(tbb.min[0] + localX, tbb.min[1] + localY, tbb.min[2] + localZ);
42934
43594
  }
42935
43595
  /** Rotate around an arbitrary axis through the origin. */
42936
43596
  rotate(axis, angleDeg, options) {
@@ -42959,7 +43619,7 @@ class Shape {
42959
43619
  }
42960
43620
  /** Apply a 4x4 affine transform matrix (column-major) or a Transform object. */
42961
43621
  transform(m2) {
42962
- const mat = m2 instanceof Transform ? m2.toArray() : m2;
43622
+ const mat = m2 instanceof Transform ? m2.toArray() : requireFiniteMat4(m2, "Shape.transform() matrix");
42963
43623
  const steps = rigidTransformStepsFromMatrix(mat);
42964
43624
  if (steps == null) {
42965
43625
  throw new Error(
@@ -42971,23 +43631,20 @@ class Shape {
42971
43631
  }
42972
43632
  /** Scale the shape uniformly or per-axis from the shape's bounding box center. Accepts a single number or [x, y, z] array. */
42973
43633
  scale(v) {
43634
+ const scale2 = requireNonZeroFiniteScale3(v, "Shape.scale() scale");
42974
43635
  const bb = this.boundingBox();
42975
43636
  const center = [
42976
43637
  (bb.min[0] + bb.max[0]) / 2,
42977
43638
  (bb.min[1] + bb.max[1]) / 2,
42978
43639
  (bb.min[2] + bb.max[2]) / 2
42979
43640
  ];
42980
- return this.scaleAround(center, v);
43641
+ return this.scaleAround(center, scale2);
42981
43642
  }
42982
43643
  /** Scale the shape uniformly or per-axis from an explicit pivot point. */
42983
43644
  scaleAround(pivot, v) {
42984
- const scale2 = normalizeShapeScale(v);
42985
- if (!scale2) {
42986
- throw new Error(
42987
- `Shape.scaleAround() received a degenerate scale value (${JSON.stringify(v)}). All scale components must be finite and non-zero.`
42988
- );
42989
- }
42990
- if (pivot[0] === 0 && pivot[1] === 0 && pivot[2] === 0) {
43645
+ const scale2 = requireNonZeroFiniteScale3(v, "Shape.scaleAround() scale");
43646
+ const scalePivot = requireFiniteVec3$1(pivot, "Shape.scaleAround() pivot");
43647
+ if (scalePivot[0] === 0 && scalePivot[1] === 0 && scalePivot[2] === 0) {
42991
43648
  const nextPlan2 = appendShapeCompileTransform(getShapeCompilePlanInternal(this), {
42992
43649
  kind: "scale",
42993
43650
  x: scale2[0],
@@ -42995,11 +43652,11 @@ class Shape {
42995
43652
  z: scale2[2]
42996
43653
  });
42997
43654
  return setShapeCompilePlanInternal(
42998
- withTransformedDimensions(this, buildShapeFromCompilePlan(nextPlan2, this.colorHex), Transform.scale(v).toArray()),
43655
+ withTransformedDimensions(this, buildShapeFromCompilePlan(nextPlan2, this.colorHex), Transform.scale(scale2).toArray()),
42999
43656
  nextPlan2
43000
43657
  );
43001
43658
  }
43002
- const translated = this.translate(-pivot[0], -pivot[1], -pivot[2]);
43659
+ const translated = this.translate(-scalePivot[0], -scalePivot[1], -scalePivot[2]);
43003
43660
  const nextPlan = appendShapeCompileTransform(getShapeCompilePlanInternal(translated), {
43004
43661
  kind: "scale",
43005
43662
  x: scale2[0],
@@ -43007,10 +43664,10 @@ class Shape {
43007
43664
  z: scale2[2]
43008
43665
  });
43009
43666
  const scaled = setShapeCompilePlanInternal(
43010
- withTransformedDimensions(translated, buildShapeFromCompilePlan(nextPlan, translated.colorHex), Transform.scale(v).toArray()),
43667
+ withTransformedDimensions(translated, buildShapeFromCompilePlan(nextPlan, translated.colorHex), Transform.scale(scale2).toArray()),
43011
43668
  nextPlan
43012
43669
  );
43013
- return scaled.translate(pivot[0], pivot[1], pivot[2]);
43670
+ return scaled.translate(scalePivot[0], scalePivot[1], scalePivot[2]);
43014
43671
  }
43015
43672
  /** Mirror across a plane through the shape's bounding box center, defined by its normal vector. */
43016
43673
  mirror(normal) {
@@ -43024,32 +43681,34 @@ class Shape {
43024
43681
  }
43025
43682
  /** Mirror across a plane through an explicit point, defined by its normal vector. */
43026
43683
  mirrorThrough(point, normal) {
43027
- if (point[0] === 0 && point[1] === 0 && point[2] === 0) {
43684
+ const mirrorPoint = requireFiniteVec3$1(point, "Shape.mirrorThrough() point");
43685
+ const mirrorNormal = requireNonZeroFiniteVec3(normal, "Shape.mirrorThrough() normal");
43686
+ if (mirrorPoint[0] === 0 && mirrorPoint[1] === 0 && mirrorPoint[2] === 0) {
43028
43687
  const transformedPlan2 = appendShapeCompileTransform(getShapeCompilePlanInternal(this), {
43029
43688
  kind: "mirror",
43030
- normalX: normal[0],
43031
- normalY: normal[1],
43032
- normalZ: normal[2]
43689
+ normalX: mirrorNormal[0],
43690
+ normalY: mirrorNormal[1],
43691
+ normalZ: mirrorNormal[2]
43033
43692
  });
43034
43693
  const nextPlan2 = wrapRepeatedShapeCompilePlan(transformedPlan2, "mirror");
43035
43694
  return setShapeCompilePlanInternal(
43036
- withTransformedDimensions(this, buildShapeFromCompilePlan(nextPlan2, this.colorHex), mirrorMatrix(normal)),
43695
+ withTransformedDimensions(this, buildShapeFromCompilePlan(nextPlan2, this.colorHex), mirrorMatrix(mirrorNormal)),
43037
43696
  nextPlan2
43038
43697
  );
43039
43698
  }
43040
- const translated = this.translate(-point[0], -point[1], -point[2]);
43699
+ const translated = this.translate(-mirrorPoint[0], -mirrorPoint[1], -mirrorPoint[2]);
43041
43700
  const transformedPlan = appendShapeCompileTransform(getShapeCompilePlanInternal(translated), {
43042
43701
  kind: "mirror",
43043
- normalX: normal[0],
43044
- normalY: normal[1],
43045
- normalZ: normal[2]
43702
+ normalX: mirrorNormal[0],
43703
+ normalY: mirrorNormal[1],
43704
+ normalZ: mirrorNormal[2]
43046
43705
  });
43047
43706
  const nextPlan = wrapRepeatedShapeCompilePlan(transformedPlan, "mirror");
43048
43707
  const mirrored = setShapeCompilePlanInternal(
43049
- withTransformedDimensions(translated, buildShapeFromCompilePlan(nextPlan, translated.colorHex), mirrorMatrix(normal)),
43708
+ withTransformedDimensions(translated, buildShapeFromCompilePlan(nextPlan, translated.colorHex), mirrorMatrix(mirrorNormal)),
43050
43709
  nextPlan
43051
43710
  );
43052
- return mirrored.translate(point[0], point[1], point[2]);
43711
+ return mirrored.translate(mirrorPoint[0], mirrorPoint[1], mirrorPoint[2]);
43053
43712
  }
43054
43713
  /**
43055
43714
  * Reorient a shape so its primary axis (Z) points along the given direction.
@@ -43059,7 +43718,7 @@ class Shape {
43059
43718
  * Example: cylinder(40, 5).pointAlong([1, 0, 0]) — lays cylinder along X, starting at origin
43060
43719
  */
43061
43720
  pointAlong(direction) {
43062
- const [dx, dy, dz] = direction;
43721
+ const [dx, dy, dz] = requireNonZeroFiniteVec3(direction, "Shape.pointAlong() direction");
43063
43722
  const len = Math.sqrt(dx * dx + dy * dy + dz * dz) || 1;
43064
43723
  const nx = dx / len, ny = dy / len, nz = dz / len;
43065
43724
  const cx = -ny, cy = nx, cz = 0;
@@ -43078,18 +43737,21 @@ class Shape {
43078
43737
  * @internal Prefer rotate(), rotateX(), rotateY(), rotateZ() for public use.
43079
43738
  */
43080
43739
  rotateAroundAxis(axis, angleDeg, pivot = [0, 0, 0]) {
43081
- const len = Math.sqrt(axis[0] ** 2 + axis[1] ** 2 + axis[2] ** 2) || 1;
43082
- const normalizedAxis = [axis[0] / len, axis[1] / len, axis[2] / len];
43083
- const matrix = rotationAroundAxisMatrix(normalizedAxis, angleDeg, pivot);
43740
+ const rotateAxis = requireNonZeroFiniteVec3(axis, "Shape.rotateAroundAxis() axis");
43741
+ const degrees = requireFiniteNumber(angleDeg, "Shape.rotateAroundAxis() angleDeg");
43742
+ const rotatePivot = requireFiniteVec3$1(pivot, "Shape.rotateAroundAxis() pivot");
43743
+ const len = Math.sqrt(rotateAxis[0] ** 2 + rotateAxis[1] ** 2 + rotateAxis[2] ** 2) || 1;
43744
+ const normalizedAxis = [rotateAxis[0] / len, rotateAxis[1] / len, rotateAxis[2] / len];
43745
+ const matrix = rotationAroundAxisMatrix(normalizedAxis, degrees, rotatePivot);
43084
43746
  const nextPlan = appendShapeCompileTransform(getShapeCompilePlanInternal(this), {
43085
43747
  kind: "rotateAround",
43086
43748
  axisX: normalizedAxis[0],
43087
43749
  axisY: normalizedAxis[1],
43088
43750
  axisZ: normalizedAxis[2],
43089
- degrees: angleDeg,
43090
- pivotX: pivot[0],
43091
- pivotY: pivot[1],
43092
- pivotZ: pivot[2]
43751
+ degrees,
43752
+ pivotX: rotatePivot[0],
43753
+ pivotY: rotatePivot[1],
43754
+ pivotZ: rotatePivot[2]
43093
43755
  });
43094
43756
  return setShapeCompilePlanInternal(
43095
43757
  withTransformedDimensions(this, buildShapeFromCompilePlan(nextPlan, this.colorHex), matrix),
@@ -43101,10 +43763,12 @@ class Shape {
43101
43763
  * `movingPoint` / `targetPoint` may be raw world points or this shape's anchors/references.
43102
43764
  */
43103
43765
  rotateAroundTo(axis, pivot, movingPoint, targetPoint, options = {}) {
43766
+ const rotateAxis = requireNonZeroFiniteVec3(axis, "Shape.rotateAroundTo() axis");
43767
+ const rotatePivot = requireFiniteVec3$1(pivot, "Shape.rotateAroundTo() pivot");
43104
43768
  const moving = resolveRotationPoint(this, movingPoint);
43105
43769
  const target = resolveRotationPoint(this, targetPoint);
43106
- const angleDeg = solveRotateAroundAngle(axis, pivot, moving, target, options);
43107
- return this.rotateAroundAxis(axis, angleDeg, pivot);
43770
+ const angleDeg = solveRotateAroundAngle(rotateAxis, rotatePivot, moving, target, options);
43771
+ return this.rotateAroundAxis(rotateAxis, angleDeg, rotatePivot);
43108
43772
  }
43109
43773
  // --- Booleans ---
43110
43774
  /** Unwrap any object with toShape() without circular import. */
@@ -43268,17 +43932,19 @@ class Shape {
43268
43932
  }
43269
43933
  /** Split by infinite plane. Returns [positive-side, negative-side]. */
43270
43934
  splitByPlane(normal, originOffset = 0) {
43935
+ const planeNormal = requireNonZeroFiniteVec3(normal, "Shape.splitByPlane() normal");
43936
+ const planeOffset = requireFiniteNumber(originOffset, "Shape.splitByPlane() originOffset");
43271
43937
  const info = deriveGeometryInfo(getShapeGeometryInfoInternal(this), "boolean", {
43272
43938
  topology: _activeBackend === "truck" ? "kernel" : "none"
43273
43939
  });
43274
43940
  const basePlan = getShapeCompilePlanInternal(this);
43275
43941
  const firstPlan = createOwnedTopologyRewritePlan(
43276
- buildTrimByPlaneShapeCompilePlan(basePlan, normal, originOffset),
43942
+ buildTrimByPlaneShapeCompilePlan(basePlan, planeNormal, planeOffset),
43277
43943
  "splitByPlane:positive",
43278
43944
  (owner) => buildTrimByPlaneTopologyRewritePropagation(owner, basePlan)
43279
43945
  );
43280
43946
  const secondPlan = createOwnedTopologyRewritePlan(
43281
- buildTrimByPlaneShapeCompilePlan(basePlan, [-normal[0], -normal[1], -normal[2]], -originOffset),
43947
+ buildTrimByPlaneShapeCompilePlan(basePlan, [-planeNormal[0], -planeNormal[1], -planeNormal[2]], -planeOffset),
43282
43948
  "splitByPlane:opposite",
43283
43949
  (owner) => buildTrimByPlaneTopologyRewritePropagation(owner, basePlan)
43284
43950
  );
@@ -43296,9 +43962,11 @@ class Shape {
43296
43962
  }
43297
43963
  /** Keep the positive side of the plane and discard the opposite side. */
43298
43964
  trimByPlane(normal, originOffset = 0) {
43965
+ const planeNormal = requireNonZeroFiniteVec3(normal, "Shape.trimByPlane() normal");
43966
+ const planeOffset = requireFiniteNumber(originOffset, "Shape.trimByPlane() originOffset");
43299
43967
  const basePlan = getShapeCompilePlanInternal(this);
43300
43968
  const nextPlan = createOwnedTopologyRewritePlan(
43301
- buildTrimByPlaneShapeCompilePlan(basePlan, normal, originOffset),
43969
+ buildTrimByPlaneShapeCompilePlan(basePlan, planeNormal, planeOffset),
43302
43970
  "trimByPlane",
43303
43971
  (owner) => buildTrimByPlaneTopologyRewritePropagation(owner, basePlan)
43304
43972
  );
@@ -43318,8 +43986,9 @@ class Shape {
43318
43986
  * `openFaces` names any subset of the base shape's labeled faces to leave open (no wall).
43319
43987
  */
43320
43988
  shell(thickness, opts = {}) {
43989
+ const wallThickness = requirePositiveFiniteNumber(thickness, "Shape.shell() thickness");
43321
43990
  const basePlan = getShapeCompilePlanInternal(this);
43322
- const shellPlan = buildShellShapeCompilePlan(basePlan, thickness, opts.openFaces);
43991
+ const shellPlan = buildShellShapeCompilePlan(basePlan, wallThickness, opts.openFaces);
43323
43992
  if (!shellPlan) {
43324
43993
  throw new Error(
43325
43994
  "Shape.shell() supports box(), cylinder(), straight extrude(), loft(), sweep(), and variableSweep() solids with optional face openings and rigid transforms."
@@ -43340,7 +44009,8 @@ class Shape {
43340
44009
  }
43341
44010
  /** Offset-thicken an exact open surface or shell into a solid. */
43342
44011
  thicken(thickness) {
43343
- if (!Number.isFinite(thickness) || thickness === 0) {
44012
+ const wallThickness = requireFiniteNumber(thickness, "Shape.thicken() thickness");
44013
+ if (wallThickness === 0) {
43344
44014
  throw new Error("Shape.thicken() requires a non-zero finite thickness.");
43345
44015
  }
43346
44016
  const info = getShapeGeometryInfoInternal(this);
@@ -43348,7 +44018,7 @@ class Shape {
43348
44018
  throw new Error("Shape.thicken() is only available on surface-representation shapes.");
43349
44019
  }
43350
44020
  const basePlan = getShapeCompilePlanInternal(this);
43351
- const nextPlan = buildSurfaceThickenShapeCompilePlan(basePlan, thickness);
44021
+ const nextPlan = buildSurfaceThickenShapeCompilePlan(basePlan, wallThickness);
43352
44022
  const representation = info.backend === "occt" || info.backend === "truck" ? "brep-solid" : "mesh-solid";
43353
44023
  return setShapeCompilePlanInternal(
43354
44024
  setShapeGeometryInfoInternal(
@@ -43389,11 +44059,12 @@ class Shape {
43389
44059
  }
43390
44060
  /** Slice the runtime solid by a plane normal to local Z at the given offset. */
43391
44061
  slice(offset = 0) {
44062
+ const planeOffset = requireFiniteNumber(offset, "Shape.slice() offset");
43392
44063
  if (getShapeGeometryInfoInternal(this).backend === "truck") {
43393
- const slicedProfile = lowerExactSlicedShapeCompilePlanToTruckProfileBackend(getShapeCompilePlanInternal(this), offset);
44064
+ const slicedProfile = lowerExactSlicedShapeCompilePlanToTruckProfileBackend(getShapeCompilePlanInternal(this), planeOffset);
43394
44065
  if (slicedProfile) return slicedProfile;
43395
44066
  }
43396
- return getShapeRuntimeBackendInternal(this).slice(offset);
44067
+ return getShapeRuntimeBackendInternal(this).slice(planeOffset);
43397
44068
  }
43398
44069
  /** Orthographically project the runtime solid onto the local XY plane. */
43399
44070
  project() {
@@ -43416,9 +44087,10 @@ class Shape {
43416
44087
  const sp = this.referencePoint(selfAnchor);
43417
44088
  let dx = tp[0] - sp[0], dy = tp[1] - sp[1], dz = tp[2] - sp[2];
43418
44089
  if (offset) {
43419
- dx += offset[0];
43420
- dy += offset[1];
43421
- dz += offset[2];
44090
+ const offsetPoint = requireFiniteVec3$1(offset, "Shape.attachTo() offset");
44091
+ dx += offsetPoint[0];
44092
+ dy += offsetPoint[1];
44093
+ dz += offsetPoint[2];
43422
44094
  }
43423
44095
  return this.translate(dx, dy, dz);
43424
44096
  }
@@ -43434,9 +44106,9 @@ class Shape {
43434
44106
  * - `protrude` = how far the child sticks out (positive = outward from face)
43435
44107
  */
43436
44108
  onFace(parent, face, opts = {}) {
43437
- const u2 = opts.u ?? 0;
43438
- const v = opts.v ?? 0;
43439
- const p2 = opts.protrude ?? 0;
44109
+ const u2 = requireFiniteNumber(opts.u ?? 0, "Shape.onFace() u");
44110
+ const v = requireFiniteNumber(opts.v ?? 0, "Shape.onFace() v");
44111
+ const p2 = requireFiniteNumber(opts.protrude ?? 0, "Shape.onFace() protrude");
43440
44112
  const opposite = {
43441
44113
  front: "back",
43442
44114
  back: "front",
@@ -44015,6 +44687,8 @@ function sampleSweepPath(path, edgeLengthHint) {
44015
44687
  path,
44016
44688
  edgeLengthHint != null ? Math.max(16, Math.round(path.controlPoints.length * 12 / Math.max(0.1, edgeLengthHint))) : 48
44017
44689
  );
44690
+ case "route3d":
44691
+ return sweepPathToPolyline(path, resolveCurveSampleCount(edgeLengthHint, path.length));
44018
44692
  }
44019
44693
  }
44020
44694
  function clamp(v, lo, hi) {
@@ -45144,6 +45818,7 @@ function rotateVector(v, axis, c2, s) {
45144
45818
  ];
45145
45819
  }
45146
45820
  const OFFSET_SOLID_EPS = 1e-9;
45821
+ const OCCT_BACKEND_REQUIRED_HINT = "Select the OCCT backend in the editor or run the CLI with --backend occt.";
45147
45822
  function disposeWasmObject(value) {
45148
45823
  if (value != null && typeof value.delete === "function") value.delete();
45149
45824
  }
@@ -45628,7 +46303,7 @@ function profilePolygonLoopTopologyMatches(left, right) {
45628
46303
  }
45629
46304
  function lowerOffsetLoftCompilePlan(plan, thickness, wasm) {
45630
46305
  if (plan.profiles.length !== plan.heights.length || plan.profiles.length < 2) {
45631
- throw new Error("Offset solid requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
46306
+ throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
45632
46307
  }
45633
46308
  const heights = [...plan.heights];
45634
46309
  heights[0] -= thickness;
@@ -45639,7 +46314,7 @@ function lowerOffsetLoftCompilePlan(plan, thickness, wasm) {
45639
46314
  const offsetPolygons = plan.profiles.map((profile) => offsetProfilePolygonsForManifold(profile, thickness, wasm));
45640
46315
  const stitched = loftStitched(offsetPolygons, heights, wasm);
45641
46316
  if (!stitched) {
45642
- throw new Error("Offset solid requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
46317
+ throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
45643
46318
  }
45644
46319
  return stitched;
45645
46320
  }
@@ -45679,16 +46354,16 @@ function lowerOffsetSweepCompilePlan(plan, thickness, wasm) {
45679
46354
  const basePolygons = profilePolygonsForManifold(plan.profile, wasm);
45680
46355
  const offsetPolygons = offsetProfilePolygonsForManifold(plan.profile, thickness, wasm);
45681
46356
  if (!profilePolygonLoopTopologyMatches(basePolygons, offsetPolygons)) {
45682
- throw new Error("Offset solid requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
46357
+ throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
45683
46358
  }
45684
46359
  const pathPoints = sweepPathToPolylineAdaptive(plan.path, plan.pathSamples ?? 48);
45685
46360
  const extendedPath = extendedStraightSweepPathForManifold(pathPoints, thickness);
45686
46361
  if (!extendedPath) {
45687
- throw new Error("Offset solid requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
46362
+ throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
45688
46363
  }
45689
46364
  const stitched = sweepStitched(offsetPolygons, extendedPath, [plan.up[0], plan.up[1], plan.up[2]], wasm);
45690
46365
  if (!stitched) {
45691
- throw new Error("Offset solid requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
46366
+ throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
45692
46367
  }
45693
46368
  return stitched;
45694
46369
  }
@@ -45697,7 +46372,7 @@ function lowerTransformedOffsetSolidCompilePlan(plan, thickness, wasm) {
45697
46372
  for (const step of plan.steps) {
45698
46373
  const stepScale = offsetSolidTransformDistanceScaleForManifold(step);
45699
46374
  if (stepScale == null) {
45700
- throw new Error("Offset solid requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
46375
+ throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
45701
46376
  }
45702
46377
  distanceScale *= stepScale;
45703
46378
  }
@@ -45738,7 +46413,7 @@ function lowerOffsetSolidCompilePlan(plan, wasm) {
45738
46413
  }
45739
46414
  const base = verticalPrismOffsetBaseForManifold(basePlan);
45740
46415
  if (!base) {
45741
- throw new Error("Offset solid requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
46416
+ throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
45742
46417
  }
45743
46418
  const zMin = base.zMin - plan.thickness;
45744
46419
  const zMax = base.zMax + plan.thickness;
@@ -46263,6 +46938,18 @@ function matchEdgeSegmentByMidpoint(segments, target) {
46263
46938
  }
46264
46939
  return best;
46265
46940
  }
46941
+ function selectEdgeSegmentsForFeature(segments, plan) {
46942
+ if (plan.edgeQuery) {
46943
+ const selected = applyEdgeQueryFilters(segments, plan.edgeQuery);
46944
+ return plan.edgeSelection === "first" ? selected.slice(0, 1) : selected;
46945
+ }
46946
+ const matched = [];
46947
+ for (const target of plan.edgeTargets ?? []) {
46948
+ const seg = matchEdgeSegmentByMidpoint(segments, target);
46949
+ if (seg && seg.length >= 1e-6) matched.push(seg);
46950
+ }
46951
+ return matched;
46952
+ }
46266
46953
  function lowerFilletEdgesCompilePlan(plan, wasm) {
46267
46954
  let manifold = lowerShapeCompilePlanToManifold(plan.base, wasm);
46268
46955
  const mesh = manifold.getMesh();
@@ -46272,11 +46959,8 @@ function lowerFilletEdgesCompilePlan(plan, wasm) {
46272
46959
  triVerts: mesh.triVerts,
46273
46960
  vertProperties: mesh.vertProperties
46274
46961
  });
46275
- const matched = [];
46276
- for (const target of plan.edgeTargets) {
46277
- const seg = matchEdgeSegmentByMidpoint(segments, target);
46278
- if (seg && seg.length >= 1e-6) matched.push(seg);
46279
- }
46962
+ const matched = selectEdgeSegmentsForFeature(segments, plan);
46963
+ if (plan.edgeQuery && matched.length === 0) throw new Error("filletEdges(): no edges match the deferred edge query.");
46280
46964
  matched.sort((a2, b) => b.length - a2.length);
46281
46965
  for (const seg of matched) {
46282
46966
  try {
@@ -46299,11 +46983,8 @@ function lowerChamferEdgesCompilePlan(plan, wasm) {
46299
46983
  triVerts: mesh.triVerts,
46300
46984
  vertProperties: mesh.vertProperties
46301
46985
  });
46302
- const matched = [];
46303
- for (const target of plan.edgeTargets) {
46304
- const seg = matchEdgeSegmentByMidpoint(segments, target);
46305
- if (seg && seg.length >= 1e-6) matched.push(seg);
46306
- }
46986
+ const matched = selectEdgeSegmentsForFeature(segments, plan);
46987
+ if (plan.edgeQuery && matched.length === 0) throw new Error("chamferEdges(): no edges match the deferred edge query.");
46307
46988
  matched.sort((a2, b) => b.length - a2.length);
46308
46989
  for (const seg of matched) {
46309
46990
  try {
@@ -46405,20 +47086,20 @@ function lowerShapeCompilePlanToManifold(plan, wasm) {
46405
47086
  case "filletEdges":
46406
47087
  return lowerFilletEdgesCompilePlan(plan, wasm);
46407
47088
  case "cornerYBlend":
46408
- throw new Error("Blend.CornerY() requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
47089
+ throw new Error(`Blend.CornerY() requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
46409
47090
  case "chamferEdges":
46410
47091
  return lowerChamferEdgesCompilePlan(plan, wasm);
46411
47092
  case "draft": {
46412
47093
  const lowered = lowerDraftShapeCompilePlanToLoftPlan(plan);
46413
47094
  if (lowered) return lowerShapeCompilePlanToManifold(lowered, wasm);
46414
- throw new Error("Draft angle requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
47095
+ throw new Error(`Draft angle requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
46415
47096
  }
46416
47097
  case "offsetSolid":
46417
47098
  return lowerOffsetSolidCompilePlan(plan, wasm);
46418
47099
  case "trimByPlane":
46419
47100
  return lowerShapeTrimByPlaneCompilePlan(plan, wasm);
46420
47101
  case "importedMesh":
46421
- return lowerImportedMeshToManifold(plan.fileData, plan.format, plan.filePath, wasm);
47102
+ return lowerImportedMeshToManifold(plan.fileData, plan.format, plan.filePath, wasm, plan.object);
46422
47103
  case "sdf": {
46423
47104
  const evaluator = compileSdfMaterializationEvaluator3(plan.tree);
46424
47105
  return lowerSdfToManifold(
@@ -46442,9 +47123,9 @@ function lowerShapeCompilePlanToManifold(plan, wasm) {
46442
47123
  case "surfaceExtend":
46443
47124
  case "surfaceThicken":
46444
47125
  case "surfaceSolid":
46445
- throw new Error("Exact surfacing operations require the OCCT backend. Add setActiveBackend('occt') at the top of your script.");
47126
+ throw new Error(`Exact surfacing operations require the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
46446
47127
  case "importedStep":
46447
- throw new Error(`importStep("${plan.filePath}") requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.`);
47128
+ throw new Error(`importStep("${plan.filePath}") requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
46448
47129
  default:
46449
47130
  assertExhaustive(plan);
46450
47131
  }
@@ -46585,11 +47266,11 @@ function projectVerticesToSurfaceWithNormals(vertProperties, sdfFn, out6) {
46585
47266
  function lowerNurbsSurfaceToManifold(plan, wasm) {
46586
47267
  if (!plan.allowApproximation && plan.thickness === 0) {
46587
47268
  throw new Error(
46588
- "nurbsSurface() now defaults to an exact open sheet. The Manifold backend cannot represent exact open surfaces. Add setActiveBackend('occt') or pass { approximate: true } with a non-zero thickness if you explicitly want a sampled fallback."
47269
+ `nurbsSurface() now defaults to an exact open sheet. The Manifold backend cannot represent exact open surfaces. ${OCCT_BACKEND_REQUIRED_HINT} Pass { approximate: true } with a non-zero thickness if you explicitly want a sampled fallback.`
46589
47270
  );
46590
47271
  }
46591
47272
  if (plan.thickness === 0) {
46592
- throw new Error("The Manifold backend cannot represent open sheet surfaces. Add setActiveBackend('occt') at the top of your script.");
47273
+ throw new Error(`The Manifold backend cannot represent open sheet surfaces. ${OCCT_BACKEND_REQUIRED_HINT}`);
46593
47274
  }
46594
47275
  const surface = new NurbsSurface(plan.controlGrid, {
46595
47276
  degreeU: plan.degreeU,
@@ -46739,8 +47420,8 @@ function lowerTrimmedNurbsSurfaceToManifold(surface, plan, wasm) {
46739
47420
  disposeWasmObject(mesh);
46740
47421
  }
46741
47422
  }
46742
- function lowerImportedMeshToManifold(fileData, format, filePath, wasm) {
46743
- const parsed = parseMeshFile(fileData, format);
47423
+ function lowerImportedMeshToManifold(fileData, format, filePath, wasm, object) {
47424
+ const parsed = parseMeshFile(fileData, format, { object });
46744
47425
  if (parsed.triVerts.length === 0) {
46745
47426
  throw new Error(`importMesh("${filePath}"): file contains no triangles`);
46746
47427
  }
@@ -46762,7 +47443,11 @@ function lowerImportedMeshToManifold(fileData, format, filePath, wasm) {
46762
47443
  }
46763
47444
  }
46764
47445
  function lowerShapeCompilePlanToShapeBackend(plan, wasm) {
46765
- return lowerShapeBackendWithCache("manifold", plan, (basePlan) => wrapManifoldShapeBackend(lowerShapeCompilePlanToManifold(basePlan, wasm)));
47446
+ return lowerShapeBackendWithCache(
47447
+ "manifold",
47448
+ plan,
47449
+ (basePlan) => wrapManifoldShapeBackend(lowerShapeCompilePlanToManifold(basePlan, wasm))
47450
+ );
46766
47451
  }
46767
47452
  const DEFAULT_LEAF_SIZE = 8;
46768
47453
  function cloneVec3$3(value) {
@@ -48104,6 +48789,92 @@ function percentile(sorted, q) {
48104
48789
  const index2 = MathUtils.clamp(Math.floor(sorted.length * q), 0, sorted.length - 1);
48105
48790
  return Number(sorted[index2].toFixed(2));
48106
48791
  }
48792
+ class DisjointSet {
48793
+ constructor(size) {
48794
+ __publicField(this, "parent");
48795
+ __publicField(this, "rank");
48796
+ this.parent = Array.from({ length: size }, (_2, index2) => index2);
48797
+ this.rank = Array.from({ length: size }, () => 0);
48798
+ }
48799
+ find(index2) {
48800
+ const parent = this.parent[index2];
48801
+ if (parent === index2) return index2;
48802
+ const root = this.find(parent);
48803
+ this.parent[index2] = root;
48804
+ return root;
48805
+ }
48806
+ union(a2, b) {
48807
+ const rootA = this.find(a2);
48808
+ const rootB = this.find(b);
48809
+ if (rootA === rootB) return;
48810
+ if (this.rank[rootA] < this.rank[rootB]) {
48811
+ this.parent[rootA] = rootB;
48812
+ } else if (this.rank[rootA] > this.rank[rootB]) {
48813
+ this.parent[rootB] = rootA;
48814
+ } else {
48815
+ this.parent[rootB] = rootA;
48816
+ this.rank[rootA] += 1;
48817
+ }
48818
+ }
48819
+ }
48820
+ function connectedCoplanarSurfacePatches(triangles) {
48821
+ const snap = surfacePatchSnap(triangles);
48822
+ const planeKeys = triangles.map((triangle) => planeKey(triangle, snap));
48823
+ const edgeOwners = /* @__PURE__ */ new Map();
48824
+ const sets = new DisjointSet(triangles.length);
48825
+ triangles.forEach((triangle, index2) => {
48826
+ for (const key of triangleEdgeKeys(triangle, snap)) {
48827
+ const owners = edgeOwners.get(key);
48828
+ if (owners) {
48829
+ for (const owner of owners) {
48830
+ if (planeKeys[owner] === planeKeys[index2]) sets.union(owner, index2);
48831
+ }
48832
+ owners.push(index2);
48833
+ } else {
48834
+ edgeOwners.set(key, [index2]);
48835
+ }
48836
+ }
48837
+ });
48838
+ const patchByRoot = /* @__PURE__ */ new Map();
48839
+ triangles.forEach((triangle, index2) => {
48840
+ const root = sets.find(index2);
48841
+ const patch = patchByRoot.get(root) ?? { triangleIndexes: [], area: 0 };
48842
+ patch.triangleIndexes.push(index2);
48843
+ patch.area += triangle.area;
48844
+ patchByRoot.set(root, patch);
48845
+ });
48846
+ return [...patchByRoot.values()];
48847
+ }
48848
+ function surfacePatchSnap(triangles) {
48849
+ const bounds = new Box3();
48850
+ for (const triangle of triangles) {
48851
+ bounds.expandByPoint(triangle.a);
48852
+ bounds.expandByPoint(triangle.b);
48853
+ bounds.expandByPoint(triangle.c);
48854
+ }
48855
+ const size = bounds.getSize(new Vector3());
48856
+ return Math.max(1e-6, size.length() * 1e-8);
48857
+ }
48858
+ function planeKey(triangle, snap) {
48859
+ const normalSnap = 1e-6;
48860
+ const distance = triangle.normal.dot(triangle.a);
48861
+ return [
48862
+ Math.round(triangle.normal.x / normalSnap),
48863
+ Math.round(triangle.normal.y / normalSnap),
48864
+ Math.round(triangle.normal.z / normalSnap),
48865
+ Math.round(distance / snap)
48866
+ ].join(",");
48867
+ }
48868
+ function triangleEdgeKeys(triangle, snap) {
48869
+ const vertices = [vertexKey$2(triangle.a, snap), vertexKey$2(triangle.b, snap), vertexKey$2(triangle.c, snap)];
48870
+ return [edgeKey$1(vertices[0], vertices[1]), edgeKey$1(vertices[1], vertices[2]), edgeKey$1(vertices[2], vertices[0])];
48871
+ }
48872
+ function vertexKey$2(point, snap) {
48873
+ return `${Math.round(point.x / snap)},${Math.round(point.y / snap)},${Math.round(point.z / snap)}`;
48874
+ }
48875
+ function edgeKey$1(a2, b) {
48876
+ return a2 < b ? `${a2}|${b}` : `${b}|${a2}`;
48877
+ }
48107
48878
  const MIN_TRIANGLE_AREA = 1e-12;
48108
48879
  const R2_ALPHA = 0.7548776662466927;
48109
48880
  const R2_BETA = 0.5698402909980532;
@@ -48156,7 +48927,7 @@ function allocateAreaSampleCounts(triangles, maxSamples) {
48156
48927
  return counts;
48157
48928
  }
48158
48929
  function sampleSurfaceTriangles(triangles, maxSamples) {
48159
- const counts = allocateAreaSampleCounts(triangles, maxSamples);
48930
+ const counts = allocateSurfacePatchSampleCounts(triangles, maxSamples);
48160
48931
  const samples = [];
48161
48932
  const position = new Vector3();
48162
48933
  let sampleIndex = 0;
@@ -48181,6 +48952,35 @@ function sampleSurfaceTriangles(triangles, maxSamples) {
48181
48952
  });
48182
48953
  return samples;
48183
48954
  }
48955
+ function allocateSurfacePatchSampleCounts(triangles, maxSamples) {
48956
+ const counts = new Array(triangles.length).fill(0);
48957
+ if (triangles.length === 0) return counts;
48958
+ const patches = connectedCoplanarSurfacePatches(triangles);
48959
+ const patchCounts = allocateAreaSampleCounts(
48960
+ patches.map((patch, index2) => {
48961
+ var _a3, _b3, _c2, _d2;
48962
+ return {
48963
+ index: index2,
48964
+ a: ((_a3 = triangles[patch.triangleIndexes[0]]) == null ? void 0 : _a3.a) ?? new Vector3(),
48965
+ b: ((_b3 = triangles[patch.triangleIndexes[0]]) == null ? void 0 : _b3.b) ?? new Vector3(),
48966
+ c: ((_c2 = triangles[patch.triangleIndexes[0]]) == null ? void 0 : _c2.c) ?? new Vector3(),
48967
+ normal: ((_d2 = triangles[patch.triangleIndexes[0]]) == null ? void 0 : _d2.normal) ?? new Vector3(0, 0, 1),
48968
+ area: patch.area
48969
+ };
48970
+ }),
48971
+ maxSamples
48972
+ );
48973
+ patches.forEach((patch, patchIndex) => {
48974
+ const patchBudget = patchCounts[patchIndex] ?? 0;
48975
+ if (patchBudget <= 0) return;
48976
+ const patchTriangles = patch.triangleIndexes.map((index2) => triangles[index2]);
48977
+ const localCounts = allocateAreaSampleCounts(patchTriangles, patchBudget);
48978
+ patch.triangleIndexes.forEach((triangleIndex, localIndex) => {
48979
+ counts[triangleIndex] += localCounts[localIndex] ?? 0;
48980
+ });
48981
+ });
48982
+ return counts;
48983
+ }
48184
48984
  function totalSurfaceArea(triangles) {
48185
48985
  return triangles.reduce((sum2, triangle) => sum2 + triangle.area, 0);
48186
48986
  }
@@ -48361,16 +49161,23 @@ const DEFAULT_THICKNESS_INSPECTION_OPTIONS = {
48361
49161
  minThickness: 1.2,
48362
49162
  warnThickness: 2,
48363
49163
  maxThickness: 6,
49164
+ colorMinThickness: 0,
49165
+ colorMaxThickness: 6,
48364
49166
  maxSamplesPerObject: 5e3,
48365
49167
  contactTolerance: DEFAULT_PHYSICAL_CONNECTIVITY_OPTIONS.contactTolerance
48366
49168
  };
48367
49169
  const THICKNESS_COLORS = {
48368
49170
  critical: [255, 28, 28],
48369
- warning: [255, 150, 0],
48370
49171
  ok: [60, 220, 90],
48371
49172
  thick: [70, 145, 255],
48372
49173
  unknown: [90, 90, 90]
48373
49174
  };
49175
+ const THICKNESS_GRADIENT_COLORS = [
49176
+ THICKNESS_COLORS.critical,
49177
+ [255, 222, 0],
49178
+ THICKNESS_COLORS.ok,
49179
+ THICKNESS_COLORS.thick
49180
+ ];
48374
49181
  function finitePositive(value, fallback, label) {
48375
49182
  if (value === void 0) return fallback;
48376
49183
  if (!Number.isFinite(value) || value <= 0) {
@@ -48389,6 +49196,16 @@ function resolveThicknessInspectionOptions(raw = {}) {
48389
49196
  const minThickness = finitePositive(raw.minThickness, DEFAULT_THICKNESS_INSPECTION_OPTIONS.minThickness, "minThickness");
48390
49197
  const warnThickness = finitePositive(raw.warnThickness, DEFAULT_THICKNESS_INSPECTION_OPTIONS.warnThickness, "warnThickness");
48391
49198
  const maxThickness = finitePositive(raw.maxThickness, DEFAULT_THICKNESS_INSPECTION_OPTIONS.maxThickness, "maxThickness");
49199
+ const colorMinThickness = finiteNonNegative(
49200
+ raw.colorMinThickness,
49201
+ DEFAULT_THICKNESS_INSPECTION_OPTIONS.colorMinThickness,
49202
+ "colorMinThickness"
49203
+ );
49204
+ const colorMaxThickness = finitePositive(
49205
+ raw.colorMaxThickness,
49206
+ DEFAULT_THICKNESS_INSPECTION_OPTIONS.colorMaxThickness,
49207
+ "colorMaxThickness"
49208
+ );
48392
49209
  const maxSamplesPerObject = finitePositive(
48393
49210
  raw.maxSamplesPerObject,
48394
49211
  DEFAULT_THICKNESS_INSPECTION_OPTIONS.maxSamplesPerObject,
@@ -48405,10 +49222,15 @@ function resolveThicknessInspectionOptions(raw = {}) {
48405
49222
  if (warnThickness > maxThickness) {
48406
49223
  throw new Error("warnThickness must be less than or equal to maxThickness.");
48407
49224
  }
49225
+ if (colorMinThickness >= colorMaxThickness) {
49226
+ throw new Error("colorMinThickness must be less than colorMaxThickness.");
49227
+ }
48408
49228
  return {
48409
49229
  minThickness,
48410
49230
  warnThickness,
48411
49231
  maxThickness,
49232
+ colorMinThickness,
49233
+ colorMaxThickness,
48412
49234
  maxSamplesPerObject: Math.max(1, Math.floor(maxSamplesPerObject)),
48413
49235
  contactTolerance
48414
49236
  };
@@ -48419,6 +49241,16 @@ function lerp$1(a2, b, t) {
48419
49241
  function lerpColor(a2, b, t) {
48420
49242
  return [Math.round(lerp$1(a2[0], b[0], t)), Math.round(lerp$1(a2[1], b[1], t)), Math.round(lerp$1(a2[2], b[2], t))];
48421
49243
  }
49244
+ function gradientColor(stops, t) {
49245
+ if (stops.length === 0) return THICKNESS_COLORS.unknown;
49246
+ if (stops.length === 1) return stops[0] ?? THICKNESS_COLORS.unknown;
49247
+ const clamped = Math.max(0, Math.min(1, t));
49248
+ const scaled = clamped * (stops.length - 1);
49249
+ const leftIndex = Math.min(stops.length - 2, Math.floor(scaled));
49250
+ const left = stops[leftIndex] ?? THICKNESS_COLORS.unknown;
49251
+ const right = stops[leftIndex + 1] ?? left;
49252
+ return lerpColor(left, right, scaled - leftIndex);
49253
+ }
48422
49254
  function thicknessClass(thickness, options) {
48423
49255
  if (thickness == null || !Number.isFinite(thickness) || thickness <= 0) return "unknown";
48424
49256
  if (thickness <= options.minThickness) return "critical";
@@ -48427,18 +49259,9 @@ function thicknessClass(thickness, options) {
48427
49259
  return "thick";
48428
49260
  }
48429
49261
  function thicknessColor(thickness, options) {
48430
- const cls = thicknessClass(thickness, options);
48431
- if (cls === "unknown") return THICKNESS_COLORS.unknown;
48432
- if (cls === "critical") return THICKNESS_COLORS.critical;
48433
- if (cls === "warning") {
48434
- const span = Math.max(1e-9, options.warnThickness - options.minThickness);
48435
- return lerpColor(THICKNESS_COLORS.critical, THICKNESS_COLORS.warning, ((thickness ?? 0) - options.minThickness) / span);
48436
- }
48437
- if (cls === "ok") {
48438
- const span = Math.max(1e-9, options.maxThickness - options.warnThickness);
48439
- return lerpColor(THICKNESS_COLORS.ok, THICKNESS_COLORS.thick, ((thickness ?? 0) - options.warnThickness) / span);
48440
- }
48441
- return THICKNESS_COLORS.thick;
49262
+ if (thickness == null || !Number.isFinite(thickness) || thickness <= 0) return THICKNESS_COLORS.unknown;
49263
+ const span = Math.max(1e-9, options.colorMaxThickness - options.colorMinThickness);
49264
+ return gradientColor(THICKNESS_GRADIENT_COLORS, (thickness - options.colorMinThickness) / span);
48442
49265
  }
48443
49266
  function cloneGeometryForFaceColors(geometry) {
48444
49267
  return geometry.index ? geometry.toNonIndexed() : geometry.clone();
@@ -49302,7 +50125,7 @@ for (let radius = 0; radius <= MAX_SEARCH_RADIUS; radius += 1) {
49302
50125
  function gridKey(x2, y2, z2) {
49303
50126
  return `${x2},${y2},${z2}`;
49304
50127
  }
49305
- function buildGeometryBounds(geometry) {
50128
+ function buildInspectHeatmapFieldBounds(geometry) {
49306
50129
  const position = geometry.getAttribute("position");
49307
50130
  if (!position || position.count === 0) return null;
49308
50131
  const bounds = new Box3().setFromBufferAttribute(position);
@@ -49310,7 +50133,11 @@ function buildGeometryBounds(geometry) {
49310
50133
  const size = bounds.getSize(new Vector3());
49311
50134
  const pad = Math.max(size.x, size.y, size.z, 1) * 1e-5;
49312
50135
  bounds.expandByScalar(pad);
49313
- return bounds;
50136
+ const boundsSize = bounds.getSize(new Vector3());
50137
+ return {
50138
+ boundsMin: [bounds.min.x, bounds.min.y, bounds.min.z],
50139
+ boundsSize: [boundsSize.x, boundsSize.y, boundsSize.z]
50140
+ };
49314
50141
  }
49315
50142
  function buildSampleGrid(pointCloud) {
49316
50143
  const sampleCount = Math.floor(pointCloud.positions.length / 3);
@@ -49394,11 +50221,15 @@ function blendedColorAt(point, pointCloud, sampleGrid) {
49394
50221
  return [r / weightSum, g2 / weightSum, b / weightSum];
49395
50222
  }
49396
50223
  function buildInspectHeatmapFieldData(geometry, pointCloud) {
49397
- const bounds = buildGeometryBounds(geometry);
50224
+ const bounds = buildInspectHeatmapFieldBounds(geometry);
50225
+ return bounds ? buildInspectHeatmapFieldDataFromBounds(bounds, pointCloud) : null;
50226
+ }
50227
+ function buildInspectHeatmapFieldDataFromBounds(bounds, pointCloud) {
49398
50228
  const sampleGrid = buildSampleGrid(pointCloud);
49399
50229
  if (!bounds || !sampleGrid) return null;
49400
50230
  const gridSize = fieldGridSizeForSampleCount(Math.floor(pointCloud.positions.length / 3));
49401
- const boundsSize = bounds.getSize(new Vector3());
50231
+ const boundsMin = new Vector3(...bounds.boundsMin);
50232
+ const boundsSize = new Vector3(...bounds.boundsSize);
49402
50233
  const data = new Uint8Array(gridSize * gridSize * gridSize * 4);
49403
50234
  const point = new Vector3();
49404
50235
  let dataOffset = 0;
@@ -49406,9 +50237,9 @@ function buildInspectHeatmapFieldData(geometry, pointCloud) {
49406
50237
  for (let z2 = 0; z2 < gridSize; z2 += 1) {
49407
50238
  for (let x2 = 0; x2 < gridSize; x2 += 1) {
49408
50239
  point.set(
49409
- bounds.min.x + boundsSize.x * x2 / (gridSize - 1),
49410
- bounds.min.y + boundsSize.y * y2 / (gridSize - 1),
49411
- bounds.min.z + boundsSize.z * z2 / (gridSize - 1)
50240
+ boundsMin.x + boundsSize.x * x2 / (gridSize - 1),
50241
+ boundsMin.y + boundsSize.y * y2 / (gridSize - 1),
50242
+ boundsMin.z + boundsSize.z * z2 / (gridSize - 1)
49412
50243
  );
49413
50244
  const [r, g2, b] = blendedColorAt(point, pointCloud, sampleGrid);
49414
50245
  data[dataOffset] = Math.max(0, Math.min(255, Math.round(r)));
@@ -49421,13 +50252,14 @@ function buildInspectHeatmapFieldData(geometry, pointCloud) {
49421
50252
  }
49422
50253
  return {
49423
50254
  data,
49424
- boundsMin: [bounds.min.x, bounds.min.y, bounds.min.z],
50255
+ boundsMin: bounds.boundsMin,
49425
50256
  boundsSize: [boundsSize.x, boundsSize.y, boundsSize.z],
49426
50257
  gridSize
49427
50258
  };
49428
50259
  }
49429
50260
  const workerScope = self;
49430
50261
  let manifoldReadyPromise = null;
50262
+ let cachedThicknessColorizeAnalysis = null;
49431
50263
  function ensureManifoldReady() {
49432
50264
  if (!manifoldReadyPromise) manifoldReadyPromise = initKernelManifoldOnly();
49433
50265
  return manifoldReadyPromise;
@@ -49473,6 +50305,7 @@ function geometryFromPositions(positions) {
49473
50305
  function pointBuffers(samples) {
49474
50306
  const positions = new Float32Array(samples.length * 3);
49475
50307
  const colors = new Float32Array(samples.length * 3);
50308
+ const values = new Float32Array(samples.length);
49476
50309
  samples.forEach((sample, index2) => {
49477
50310
  const base = index2 * 3;
49478
50311
  positions[base] = sample.position[0] + sample.normal[0] * 0.025;
@@ -49481,8 +50314,9 @@ function pointBuffers(samples) {
49481
50314
  colors[base] = sample.color[0] / 255;
49482
50315
  colors[base + 1] = sample.color[1] / 255;
49483
50316
  colors[base + 2] = sample.color[2] / 255;
50317
+ values[index2] = sample.value ?? Number.NaN;
49484
50318
  });
49485
- return { positions, colors };
50319
+ return { positions, colors, values };
49486
50320
  }
49487
50321
  function rgbFloatsForHex(hex) {
49488
50322
  const color = new Color(hex);
@@ -49507,6 +50341,7 @@ function analyzeScalarChannel(request) {
49507
50341
  const pointObjects = [];
49508
50342
  const heatmapFieldObjects = [];
49509
50343
  const warnings = [];
50344
+ const thicknessCacheObjects = [];
49510
50345
  for (const object of request.objects) {
49511
50346
  if (!object.positions || object.positions.length < 9) continue;
49512
50347
  const geometry = geometryFromPositions(object.positions);
@@ -49514,13 +50349,22 @@ function analyzeScalarChannel(request) {
49514
50349
  const analysis = request.channel === "thickness" ? analyzeThicknessGeometry(geometry, request.thickness) : analyzeRoughnessGeometry(geometry, request.roughness);
49515
50350
  analysis.warnings.forEach((warning) => warnings.push(`${object.name}: ${warning}`));
49516
50351
  const buffers = pointBuffers(analysis.pointSamples);
49517
- const field = buildInspectHeatmapFieldData(analysis.geometry, buffers);
50352
+ const heatmapBounds = buildInspectHeatmapFieldBounds(analysis.geometry);
50353
+ const field = heatmapBounds ? buildInspectHeatmapFieldDataFromBounds(heatmapBounds, buffers) : buildInspectHeatmapFieldData(analysis.geometry, buffers);
49518
50354
  if (field) {
49519
50355
  heatmapFieldObjects.push({
49520
50356
  objectId: object.id,
49521
50357
  ...field
49522
50358
  });
49523
50359
  }
50360
+ if (request.channel === "thickness" && buffers.values) {
50361
+ thicknessCacheObjects.push({
50362
+ objectId: object.id,
50363
+ positions: new Float32Array(buffers.positions),
50364
+ values: new Float32Array(buffers.values),
50365
+ heatmapBounds
50366
+ });
50367
+ }
49524
50368
  pointObjects.push({
49525
50369
  objectId: object.id,
49526
50370
  sampleCount: analysis.pointSamples.length,
@@ -49531,7 +50375,9 @@ function analyzeScalarChannel(request) {
49531
50375
  geometry.dispose();
49532
50376
  }
49533
50377
  }
50378
+ cachedThicknessColorizeAnalysis = request.channel === "thickness" ? { analysisId: request.reqId, objects: thicknessCacheObjects } : null;
49534
50379
  return {
50380
+ analysisId: request.reqId,
49535
50381
  channel: request.channel,
49536
50382
  objectColors: {},
49537
50383
  pointObjects,
@@ -49541,6 +50387,47 @@ function analyzeScalarChannel(request) {
49541
50387
  warnings
49542
50388
  };
49543
50389
  }
50390
+ function thicknessColorsForValues(values, colorMinThickness, colorMaxThickness) {
50391
+ const options = resolveThicknessInspectionOptions({ colorMinThickness, colorMaxThickness });
50392
+ const colors = new Float32Array(values.length * 3);
50393
+ for (let index2 = 0; index2 < values.length; index2 += 1) {
50394
+ const color = thicknessColor(values[index2], options);
50395
+ const offset = index2 * 3;
50396
+ colors[offset] = color[0] / 255;
50397
+ colors[offset + 1] = color[1] / 255;
50398
+ colors[offset + 2] = color[2] / 255;
50399
+ }
50400
+ return colors;
50401
+ }
50402
+ function colorizeThicknessAnalysis(request) {
50403
+ const cached = cachedThicknessColorizeAnalysis;
50404
+ if (!cached || cached.analysisId !== request.analysisId) {
50405
+ throw new Error("Thickness colorize cache is no longer available.");
50406
+ }
50407
+ const pointObjects = [];
50408
+ const heatmapFieldObjects = [];
50409
+ for (const object of cached.objects) {
50410
+ const colors = thicknessColorsForValues(object.values, request.colorMinThickness, request.colorMaxThickness);
50411
+ pointObjects.push({ objectId: object.objectId, colors });
50412
+ if (object.heatmapBounds) {
50413
+ const field = buildInspectHeatmapFieldDataFromBounds(object.heatmapBounds, {
50414
+ positions: object.positions,
50415
+ colors
50416
+ });
50417
+ if (field) {
50418
+ heatmapFieldObjects.push({
50419
+ objectId: object.objectId,
50420
+ ...field
50421
+ });
50422
+ }
50423
+ }
50424
+ }
50425
+ return {
50426
+ analysisId: cached.analysisId,
50427
+ pointObjects,
50428
+ heatmapFieldObjects
50429
+ };
50430
+ }
49544
50431
  function analyzeConnectivityChannel(request) {
49545
50432
  const bodyInput = buildMeshBodyConnectivityInput(
49546
50433
  request.objects.map((object) => ({
@@ -49707,7 +50594,11 @@ function analyzeCollisionChannel(request) {
49707
50594
  }
49708
50595
  function transferFor(result) {
49709
50596
  return [
49710
- ...result.pointObjects.flatMap((object) => [object.positions.buffer, object.colors.buffer]),
50597
+ ...result.pointObjects.flatMap((object) => [
50598
+ object.positions.buffer,
50599
+ object.colors.buffer,
50600
+ ...object.values ? [object.values.buffer] : []
50601
+ ]),
49711
50602
  ...result.meshColorObjects.map((object) => object.colors.buffer),
49712
50603
  ...result.heatmapFieldObjects.map((object) => object.data.buffer),
49713
50604
  ...result.collisionGeometryObjects.flatMap((object) => [object.positions.buffer, object.normals.buffer, object.edgePositions.buffer])
@@ -49715,6 +50606,21 @@ function transferFor(result) {
49715
50606
  }
49716
50607
  async function handleRequest(request) {
49717
50608
  try {
50609
+ if (request.type === "colorize-thickness") {
50610
+ const result2 = colorizeThicknessAnalysis(request.payload);
50611
+ const response2 = {
50612
+ type: "inspect-colorize-success",
50613
+ payload: {
50614
+ reqId: request.payload.reqId,
50615
+ result: result2
50616
+ }
50617
+ };
50618
+ workerScope.postMessage(response2, [
50619
+ ...result2.pointObjects.map((object) => object.colors.buffer),
50620
+ ...result2.heatmapFieldObjects.map((object) => object.data.buffer)
50621
+ ]);
50622
+ return;
50623
+ }
49718
50624
  if (request.payload.channel === "collisions") await ensureManifoldReady();
49719
50625
  const result = request.payload.channel === "thickness" || request.payload.channel === "roughness" ? analyzeScalarChannel(request.payload) : request.payload.channel === "collisions" ? analyzeCollisionChannel(request.payload) : analyzeConnectivityChannel(request.payload);
49720
50626
  const response = {