forgecad 0.10.3 → 0.10.4

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 (99) hide show
  1. package/dist/assets/{AdminPage-CK7ObBz3.js → AdminPage-B3L3W1Uo.js} +1 -1
  2. package/dist/assets/{BenchmarkPage-Ds7Z2doN.js → BenchmarkPage-DXKVXMrJ.js} +2 -2
  3. package/dist/assets/{BlogPage-DlPbpt6A.js → BlogPage-B7BWxOCg.js} +1 -1
  4. package/dist/assets/{DocsPage-vZb3b3Y0.js → DocsPage-BPGGwht1.js} +28 -43
  5. package/dist/assets/{EditorApp-HLoKfe15.js → EditorApp-BWUGCdD5.js} +49 -16
  6. package/dist/assets/{EmbedViewer--KnqBKrJ.js → EmbedViewer-DygByZS2.js} +2 -2
  7. package/dist/assets/{LandingPageProofDriven-C_LssmnA.js → LandingPageProofDriven-BoVE7JGY.js} +54 -36
  8. package/dist/assets/{LegalPage-DGsyo4n1.js → LegalPage-Din8wv8d.js} +2 -2
  9. package/dist/assets/{PricingPage-BOE27B-R.js → PricingPage-C2PMzmDc.js} +2 -2
  10. package/dist/assets/{SettingsPage-f47cnk39.js → SettingsPage-BlJDCRe8.js} +1 -1
  11. package/dist/assets/{app-D6ccu2Xx.js → app-BsRYSfxY.js} +238 -3714
  12. package/dist/assets/{backendInit-DbTkQN9J.js → backendInit-6C0DLgH0.js} +5972 -1566
  13. package/dist/assets/cli/{render-BsngirjC.js → render-XXol_ET7.js} +724 -112
  14. package/dist/assets/{constructionHistoryWorker-PCwXrTDB.js → constructionHistoryWorker-cTHWRJEi.js} +528 -252
  15. package/dist/assets/{evalWorker-CS63PfZu.js → evalWorker-BssDYW9u.js} +1453 -902
  16. package/dist/assets/{inspectWorker-Y4cOzNyA.js → inspectWorker-ymhBV4Ll.js} +2635 -1024
  17. package/dist/assets/{jointPose-AMvCywzS.js → jointPose-B0blBj9A.js} +1 -1
  18. package/dist/assets/{landing-proof-driven-ORyigZ6p.css → landing-proof-driven-Cpf-MIbI.css} +73 -13
  19. package/dist/assets/{manifold-Crd_F2qx.js → manifold-B_7QXpGB.js} +1 -1
  20. package/dist/assets/{manifold-k2kRcc85.js → manifold-CNShmpEJ.js} +1 -1
  21. package/dist/assets/{manifold-CBry38ly.js → manifold-CYlIm-M6.js} +2 -2
  22. package/dist/assets/{reportWorker-CWvn0CEv.js → reportWorker-Cb5eyM7D.js} +1407 -892
  23. package/dist/cli/render.html +1 -1
  24. package/dist/docs/index.html +2 -2
  25. package/dist/docs-raw/AI/usage.md +17 -15
  26. package/dist/docs-raw/component-model.md +2 -2
  27. package/dist/docs-raw/generated/concepts.md +5 -1
  28. package/dist/docs-raw/generated/core.md +26 -0
  29. package/dist/docs-raw/generated/runtime-names.md +1 -1
  30. package/dist/docs-raw/guides/inspection-bundles.md +1 -1
  31. package/dist/docs-raw/simulation-workflow.md +1 -1
  32. package/dist/docs-raw/skills/{forgecad-make-a-model.md → forgecad-build-model.md} +18 -8
  33. package/dist/docs-raw/skills/{forgecad-spec-by-walking-through-it.md → forgecad-design-spec.md} +6 -6
  34. package/dist/docs-raw/skills/{forgecad-model-grader.md → forgecad-grade-model.md} +8 -6
  35. package/{dist-skill/website/skills/forgecad-visual-spec.md → dist/docs-raw/skills/forgecad-image-prompt.md} +7 -7
  36. package/dist/docs-raw/skills/{forgecad-render-inspect.md → forgecad-inspect-model.md} +6 -6
  37. package/{dist-skill/website/skills/forgecad-project.md → dist/docs-raw/skills/forgecad-project-sync.md} +5 -5
  38. package/dist/docs-raw/skills/{forgecad-3d-reconstruction.md → forgecad-reconstruct-cad-file.md} +7 -7
  39. package/dist/docs-raw/skills/{forgecad-image-replicator.md → forgecad-reconstruct-from-images.md} +12 -12
  40. package/dist/docs-raw/skills/{forgecad-mujoco-verify.md → forgecad-verify-mujoco.md} +6 -6
  41. package/dist/docs-raw/skills/index.md +9 -12
  42. package/dist/index.html +9 -9
  43. package/dist/llms.txt +7 -7
  44. package/dist/sitemap.xml +16 -16
  45. package/dist-cli/{check-compiler-HPF2T2FS.js → check-compiler-4RPB6SB5.js} +1 -1
  46. package/dist-cli/{check-query-propagation-HYSLTXAB.js → check-query-propagation-KN3DFQTX.js} +1 -1
  47. package/dist-cli/{chunk-WLUKAW3H.js → chunk-UHBRMYA6.js} +28802 -28152
  48. package/dist-cli/forgecad.js +660 -9
  49. package/dist-skill/CONTEXT.md +27 -1
  50. package/dist-skill/docs/generated/core.md +26 -0
  51. package/dist-skill/docs/generated/runtime-names.md +1 -1
  52. package/dist-skill/docs/guides/inspection-bundles.md +1 -1
  53. package/dist-skill/library/README.md +9 -12
  54. package/dist-skill/library/{forgecad-make-a-model → forgecad-build-model}/SKILL.md +16 -6
  55. package/dist-skill/library/{forgecad-spec-by-walking-through-it → forgecad-design-spec}/SKILL.md +4 -4
  56. package/dist-skill/library/{forgecad-spec-by-walking-through-it → forgecad-design-spec}/references/master-prompt.md +1 -1
  57. package/dist-skill/library/{forgecad-model-grader → forgecad-grade-model}/SKILL.md +6 -4
  58. package/dist-skill/library/forgecad-grade-model/agents/openai.yaml +4 -0
  59. package/dist-skill/library/{forgecad-visual-spec → forgecad-image-prompt}/SKILL.md +5 -5
  60. package/dist-skill/library/forgecad-image-prompt/agents/openai.yaml +4 -0
  61. package/dist-skill/library/{forgecad-render-inspect → forgecad-inspect-model}/SKILL.md +4 -4
  62. package/dist-skill/library/{forgecad-project → forgecad-project-sync}/SKILL.md +3 -3
  63. package/dist-skill/library/{forgecad-3d-reconstruction → forgecad-reconstruct-cad-file}/SKILL.md +5 -5
  64. package/dist-skill/library/forgecad-reconstruct-cad-file/agents/openai.yaml +4 -0
  65. package/dist-skill/library/{forgecad-image-replicator → forgecad-reconstruct-from-images}/SKILL.md +10 -10
  66. package/dist-skill/library/forgecad-reconstruct-from-images/agents/openai.yaml +4 -0
  67. package/dist-skill/library/{forgecad-mujoco-verify → forgecad-verify-mujoco}/SKILL.md +4 -4
  68. package/dist-skill/website/skills/{forgecad-make-a-model.md → forgecad-build-model.md} +18 -8
  69. package/dist-skill/website/skills/{forgecad-spec-by-walking-through-it.md → forgecad-design-spec.md} +6 -6
  70. package/dist-skill/website/skills/{forgecad-model-grader.md → forgecad-grade-model.md} +8 -6
  71. package/{dist/docs-raw/skills/forgecad-visual-spec.md → dist-skill/website/skills/forgecad-image-prompt.md} +7 -7
  72. package/dist-skill/website/skills/{forgecad-render-inspect.md → forgecad-inspect-model.md} +6 -6
  73. package/{dist/docs-raw/skills/forgecad-project.md → dist-skill/website/skills/forgecad-project-sync.md} +5 -5
  74. package/dist-skill/website/skills/{forgecad-3d-reconstruction.md → forgecad-reconstruct-cad-file.md} +7 -7
  75. package/dist-skill/website/skills/{forgecad-image-replicator.md → forgecad-reconstruct-from-images.md} +12 -12
  76. package/dist-skill/website/skills/{forgecad-mujoco-verify.md → forgecad-verify-mujoco.md} +6 -6
  77. package/dist-skill/website/skills/index.md +9 -12
  78. package/examples/api/texture-projection.forge.js +75 -0
  79. package/examples/assets/uv-grid.png +0 -0
  80. package/package.json +1 -1
  81. package/dist/docs-raw/skills/forgecad-blockout-model.md +0 -49
  82. package/dist/docs-raw/skills/forgecad-component-model.md +0 -53
  83. package/dist/docs-raw/skills/forgecad-reconstruction-benchmark.md +0 -60
  84. package/dist-skill/library/forgecad-3d-reconstruction/agents/openai.yaml +0 -4
  85. package/dist-skill/library/forgecad-blockout-model/SKILL.md +0 -42
  86. package/dist-skill/library/forgecad-component-model/SKILL.md +0 -46
  87. package/dist-skill/library/forgecad-image-replicator/agents/openai.yaml +0 -4
  88. package/dist-skill/library/forgecad-model-grader/agents/openai.yaml +0 -4
  89. package/dist-skill/library/forgecad-reconstruction-benchmark/SKILL.md +0 -48
  90. package/dist-skill/library/forgecad-reconstruction-benchmark/agents/openai.yaml +0 -4
  91. package/dist-skill/library/forgecad-visual-spec/agents/openai.yaml +0 -4
  92. package/dist-skill/website/skills/forgecad-blockout-model.md +0 -49
  93. package/dist-skill/website/skills/forgecad-component-model.md +0 -53
  94. package/dist-skill/website/skills/forgecad-reconstruction-benchmark.md +0 -60
  95. /package/dist/assets/{landing-proof-driven-DiGqdtWa.js → landing-proof-driven-BxZZh5r5.js} +0 -0
  96. /package/dist-skill/library/{forgecad-spec-by-walking-through-it → forgecad-design-spec}/references/default-profiles.md +0 -0
  97. /package/dist-skill/library/{forgecad-render-inspect → forgecad-inspect-model}/summarize_manifest.py +0 -0
  98. /package/dist-skill/library/{forgecad-image-replicator → forgecad-reconstruct-from-images}/scripts/compare_images.py +0 -0
  99. /package/dist-skill/library/{forgecad-mujoco-verify → forgecad-verify-mujoco}/scripts/mujoco_verify.py +0 -0
@@ -722,12 +722,12 @@ class Vector2 {
722
722
  * @param {number} angle - The angle to rotate, in radians.
723
723
  * @return {Vector2} A reference to this vector.
724
724
  */
725
- rotateAround(center, angle) {
725
+ rotateAround(center2, angle) {
726
726
  const c2 = Math.cos(angle), s = Math.sin(angle);
727
- const x2 = this.x - center.x;
728
- const y2 = this.y - center.y;
729
- this.x = x2 * c2 - y2 * s + center.x;
730
- this.y = x2 * s + y2 * c2 + center.y;
727
+ const x2 = this.x - center2.x;
728
+ const y2 = this.y - center2.y;
729
+ this.x = x2 * c2 - y2 * s + center2.x;
730
+ this.y = x2 * s + y2 * c2 + center2.y;
731
731
  return this;
732
732
  }
733
733
  /**
@@ -3256,10 +3256,10 @@ class Box3 {
3256
3256
  * @param {Vector3} size - The x, y and z dimensions of the box.
3257
3257
  * @return {Box3} A reference to this bounding box.
3258
3258
  */
3259
- setFromCenterAndSize(center, size) {
3259
+ setFromCenterAndSize(center2, size) {
3260
3260
  const halfSize = _vector$b.copy(size).multiplyScalar(0.5);
3261
- this.min.copy(center).sub(halfSize);
3262
- this.max.copy(center).add(halfSize);
3261
+ this.min.copy(center2).sub(halfSize);
3262
+ this.max.copy(center2).add(halfSize);
3263
3263
  return this;
3264
3264
  }
3265
3265
  /**
@@ -3709,9 +3709,9 @@ class Sphere {
3709
3709
  * @param {Vector3} [center=(0,0,0)] - The center of the sphere
3710
3710
  * @param {number} [radius=-1] - The radius of the sphere.
3711
3711
  */
3712
- constructor(center = new Vector3(), radius = -1) {
3712
+ constructor(center2 = new Vector3(), radius = -1) {
3713
3713
  this.isSphere = true;
3714
- this.center = center;
3714
+ this.center = center2;
3715
3715
  this.radius = radius;
3716
3716
  }
3717
3717
  /**
@@ -3721,8 +3721,8 @@ class Sphere {
3721
3721
  * @param {number} radius - The radius.
3722
3722
  * @return {Sphere} A reference to this sphere.
3723
3723
  */
3724
- set(center, radius) {
3725
- this.center.copy(center);
3724
+ set(center2, radius) {
3725
+ this.center.copy(center2);
3726
3726
  this.radius = radius;
3727
3727
  return this;
3728
3728
  }
@@ -3737,15 +3737,15 @@ class Sphere {
3737
3737
  * @return {Sphere} A reference to this sphere.
3738
3738
  */
3739
3739
  setFromPoints(points, optionalCenter) {
3740
- const center = this.center;
3740
+ const center2 = this.center;
3741
3741
  if (optionalCenter !== void 0) {
3742
- center.copy(optionalCenter);
3742
+ center2.copy(optionalCenter);
3743
3743
  } else {
3744
- _box$3.setFromPoints(points).getCenter(center);
3744
+ _box$3.setFromPoints(points).getCenter(center2);
3745
3745
  }
3746
3746
  let maxRadiusSq = 0;
3747
3747
  for (let i = 0, il = points.length; i < il; i++) {
3748
- maxRadiusSq = Math.max(maxRadiusSq, center.distanceToSquared(points[i]));
3748
+ maxRadiusSq = Math.max(maxRadiusSq, center2.distanceToSquared(points[i]));
3749
3749
  }
3750
3750
  this.radius = Math.sqrt(maxRadiusSq);
3751
3751
  return this;
@@ -7133,7 +7133,7 @@ class BufferGeometry extends EventDispatcher {
7133
7133
  return;
7134
7134
  }
7135
7135
  if (position) {
7136
- const center = this.boundingSphere.center;
7136
+ const center2 = this.boundingSphere.center;
7137
7137
  _box$2.setFromBufferAttribute(position);
7138
7138
  if (morphAttributesPosition) {
7139
7139
  for (let i = 0, il = morphAttributesPosition.length; i < il; i++) {
@@ -7150,11 +7150,11 @@ class BufferGeometry extends EventDispatcher {
7150
7150
  }
7151
7151
  }
7152
7152
  }
7153
- _box$2.getCenter(center);
7153
+ _box$2.getCenter(center2);
7154
7154
  let maxRadiusSq = 0;
7155
7155
  for (let i = 0, il = position.count; i < il; i++) {
7156
7156
  _vector$8.fromBufferAttribute(position, i);
7157
- maxRadiusSq = Math.max(maxRadiusSq, center.distanceToSquared(_vector$8));
7157
+ maxRadiusSq = Math.max(maxRadiusSq, center2.distanceToSquared(_vector$8));
7158
7158
  }
7159
7159
  if (morphAttributesPosition) {
7160
7160
  for (let i = 0, il = morphAttributesPosition.length; i < il; i++) {
@@ -7166,7 +7166,7 @@ class BufferGeometry extends EventDispatcher {
7166
7166
  _offset.fromBufferAttribute(position, j);
7167
7167
  _vector$8.add(_offset);
7168
7168
  }
7169
- maxRadiusSq = Math.max(maxRadiusSq, center.distanceToSquared(_vector$8));
7169
+ maxRadiusSq = Math.max(maxRadiusSq, center2.distanceToSquared(_vector$8));
7170
7170
  }
7171
7171
  }
7172
7172
  }
@@ -8043,6 +8043,12 @@ if (typeof window !== "undefined") {
8043
8043
  }
8044
8044
  }
8045
8045
  const SHAPE_BACKEND_MARKER = Symbol.for("forgecad.shapeBackend");
8046
+ const POSITION_OFFSET = 0;
8047
+ const NORMAL_OFFSET = 3;
8048
+ const UV_OFFSET = 6;
8049
+ const NUM_PROP_POSITION_ONLY = 3;
8050
+ const NUM_PROP_WITH_NORMAL = 6;
8051
+ const NUM_PROP_WITH_UV = 8;
8046
8052
  function isShapeBackend(value) {
8047
8053
  return Boolean(value && typeof value === "object" && value[SHAPE_BACKEND_MARKER] === true);
8048
8054
  }
@@ -9187,11 +9193,11 @@ function applyFilletSelectionToManifold(base, selection, radius, segments, wasm)
9187
9193
  const height = edgeLength(selection);
9188
9194
  if (!(height > 1e-6)) return base;
9189
9195
  const pad = Math.max(MIN_EDGE_PAD, radius);
9190
- const span = height + pad * 2;
9196
+ const span2 = height + pad * 2;
9191
9197
  const frame = edgeFrameMatrix(selection, -pad);
9192
9198
  const cs = buildFilletCrossSection(selection, radius, segments, wasm) ?? legacyFilletCrossSection(selection, radius, segments, wasm);
9193
- const corner = cs.wedge.extrude(span, 0, 0, void 0, false).transform(frame);
9194
- const cyl = cs.cylinder.extrude(span, 0, 0, void 0, false).transform(frame);
9199
+ const corner = cs.wedge.extrude(span2, 0, 0, void 0, false).transform(frame);
9200
+ const cyl = cs.cylinder.extrude(span2, 0, 0, void 0, false).transform(frame);
9195
9201
  const crescent = wasm.Manifold.difference([corner, cyl]);
9196
9202
  return wasm.Manifold.difference([base, crescent]);
9197
9203
  }
@@ -9199,11 +9205,11 @@ function applyConcaveFilletSelectionToManifold(base, selection, radius, segments
9199
9205
  const height = edgeLength(selection);
9200
9206
  if (!(height > 1e-6)) return base;
9201
9207
  const pad = Math.max(MIN_EDGE_PAD, radius);
9202
- const span = height + pad * 2;
9208
+ const span2 = height + pad * 2;
9203
9209
  const frame = edgeFrameMatrix(selection, -pad);
9204
9210
  const cs = buildFilletCrossSection(selection, radius, segments, wasm) ?? legacyFilletCrossSection(selection, radius, segments, wasm);
9205
- const corner = cs.wedge.extrude(span, 0, 0, void 0, false).transform(frame);
9206
- const cyl = cs.cylinder.extrude(span, 0, 0, void 0, false).transform(frame);
9211
+ const corner = cs.wedge.extrude(span2, 0, 0, void 0, false).transform(frame);
9212
+ const cyl = cs.cylinder.extrude(span2, 0, 0, void 0, false).transform(frame);
9207
9213
  const crescent = wasm.Manifold.difference([corner, cyl]);
9208
9214
  return wasm.Manifold.union([base, crescent]);
9209
9215
  }
@@ -9211,20 +9217,20 @@ function applyChamferSelectionToManifold(base, selection, size, wasm) {
9211
9217
  const height = edgeLength(selection);
9212
9218
  if (!(height > 1e-6)) return base;
9213
9219
  const pad = Math.max(MIN_EDGE_PAD, size);
9214
- const span = height + pad * 2;
9220
+ const span2 = height + pad * 2;
9215
9221
  const frame = edgeFrameMatrix(selection, -pad);
9216
9222
  const triangle = buildChamferCrossSection(selection, size, wasm) ?? legacyChamferCrossSection(selection, size, wasm);
9217
- const chamfer = triangle.extrude(span, 0, 0, void 0, false).transform(frame);
9223
+ const chamfer = triangle.extrude(span2, 0, 0, void 0, false).transform(frame);
9218
9224
  return wasm.Manifold.difference([base, chamfer]);
9219
9225
  }
9220
9226
  function applyConcaveChamferSelectionToManifold(base, selection, size, wasm) {
9221
9227
  const height = edgeLength(selection);
9222
9228
  if (!(height > 1e-6)) return base;
9223
9229
  const pad = Math.max(MIN_EDGE_PAD, size);
9224
- const span = height + pad * 2;
9230
+ const span2 = height + pad * 2;
9225
9231
  const frame = edgeFrameMatrix(selection, -pad);
9226
9232
  const triangle = buildChamferCrossSection(selection, size, wasm) ?? legacyChamferCrossSection(selection, size, wasm);
9227
- const chamfer = triangle.extrude(span, 0, 0, void 0, false).transform(frame);
9233
+ const chamfer = triangle.extrude(span2, 0, 0, void 0, false).transform(frame);
9228
9234
  return wasm.Manifold.union([base, chamfer]);
9229
9235
  }
9230
9236
  function cloneEdgeFinishQuadrant(quadrant) {
@@ -9378,6 +9384,7 @@ function cloneTopologyRewritePropagation(propagation) {
9378
9384
  }
9379
9385
  function faceQueryRefsEqual(a2, b) {
9380
9386
  if (a2 == null || b == null) return a2 == null && b == null;
9387
+ if (a2 === b) return true;
9381
9388
  if (a2.kind !== b.kind) return false;
9382
9389
  switch (a2.kind) {
9383
9390
  case "canonical-face":
@@ -9394,6 +9401,7 @@ function faceQueryRefsEqual(a2, b) {
9394
9401
  }
9395
9402
  function edgeQueryRefsEqual(a2, b) {
9396
9403
  if (a2 == null || b == null) return a2 == null && b == null;
9404
+ if (a2 === b) return true;
9397
9405
  if (a2.kind !== b.kind) return false;
9398
9406
  switch (a2.kind) {
9399
9407
  case "tracked-edge":
@@ -9760,8 +9768,8 @@ function deriveSheetMetalModel(model) {
9760
9768
  const trimStart = flanges.has(adjacent.start) ? model.cornerRelief.size : 0;
9761
9769
  const trimEnd = flanges.has(adjacent.end) ? model.cornerRelief.size : 0;
9762
9770
  const fullLength = edge === "top" || edge === "bottom" ? model.panel.width : model.panel.height;
9763
- const span = fullLength - trimStart - trimEnd;
9764
- if (!(span > EPS$5)) {
9771
+ const span2 = fullLength - trimStart - trimEnd;
9772
+ if (!(span2 > EPS$5)) {
9765
9773
  throw new Error(
9766
9774
  `${edgeDisplayName(edge)} loses all usable span after applying the defended rectangular corner relief size ${model.cornerRelief.size}.`
9767
9775
  );
@@ -9773,7 +9781,7 @@ function deriveSheetMetalModel(model) {
9773
9781
  bendAllowance,
9774
9782
  trimStart,
9775
9783
  trimEnd,
9776
- span,
9784
+ span: span2,
9777
9785
  centerAlongEdge: (trimStart - trimEnd) / 2
9778
9786
  });
9779
9787
  }
@@ -9952,10 +9960,10 @@ function lowerSheetMetalBasePlan(model, output) {
9952
9960
  const pieces = lowerSheetMetalBasePiecePlans(model, output).map((piece) => piece.plan);
9953
9961
  return pieces.length === 1 ? pieces[0] : buildBooleanShapeCompilePlan("union", pieces);
9954
9962
  }
9955
- function descriptor(name, center, normal2, planar, uAxis, vAxis, semantic = "face", memberNames = [name], coplanar = planar) {
9963
+ function descriptor(name, center2, normal2, planar, uAxis, vAxis, semantic = "face", memberNames = [name], coplanar = planar) {
9956
9964
  return {
9957
9965
  name,
9958
- center: cloneVec3$3(center),
9966
+ center: cloneVec3$3(center2),
9959
9967
  normal: cloneVec3$3(normal2),
9960
9968
  planar,
9961
9969
  uAxis: cloneFaceAxis(uAxis),
@@ -11408,14 +11416,14 @@ function findSpan(n, degree, u2, knots) {
11408
11416
  }
11409
11417
  return mid;
11410
11418
  }
11411
- function basisFuns(span, u2, degree, knots) {
11419
+ function basisFuns(span2, u2, degree, knots) {
11412
11420
  const N = new Array(degree + 1);
11413
11421
  const left = new Array(degree + 1);
11414
11422
  const right = new Array(degree + 1);
11415
11423
  N[0] = 1;
11416
11424
  for (let j = 1; j <= degree; j++) {
11417
- left[j] = u2 - knots[span + 1 - j];
11418
- right[j] = knots[span + j] - u2;
11425
+ left[j] = u2 - knots[span2 + 1 - j];
11426
+ right[j] = knots[span2 + j] - u2;
11419
11427
  let saved = 0;
11420
11428
  for (let r = 0; r < j; r++) {
11421
11429
  const denom = right[r + 1] + left[j - r];
@@ -11427,15 +11435,15 @@ function basisFuns(span, u2, degree, knots) {
11427
11435
  }
11428
11436
  return N;
11429
11437
  }
11430
- function basisFunsDeriv(span, u2, degree, knots, nDeriv) {
11438
+ function basisFunsDeriv(span2, u2, degree, knots, nDeriv) {
11431
11439
  const ndu = Array.from({ length: degree + 1 }, () => new Array(degree + 1).fill(0));
11432
11440
  const a2 = Array.from({ length: 2 }, () => new Array(degree + 1).fill(0));
11433
11441
  const left = new Array(degree + 1);
11434
11442
  const right = new Array(degree + 1);
11435
11443
  ndu[0][0] = 1;
11436
11444
  for (let j = 1; j <= degree; j++) {
11437
- left[j] = u2 - knots[span + 1 - j];
11438
- right[j] = knots[span + j] - u2;
11445
+ left[j] = u2 - knots[span2 + 1 - j];
11446
+ right[j] = knots[span2 + j] - u2;
11439
11447
  let saved = 0;
11440
11448
  for (let r2 = 0; r2 < j; r2++) {
11441
11449
  ndu[j][r2] = right[r2 + 1] + left[j - r2];
@@ -11488,11 +11496,11 @@ function basisFunsDeriv(span, u2, degree, knots, nDeriv) {
11488
11496
  }
11489
11497
  function deBoor3D(controlPoints, weights, knots, degree, u2) {
11490
11498
  const n = controlPoints.length;
11491
- const span = findSpan(n, degree, u2, knots);
11492
- const N = basisFuns(span, u2, degree, knots);
11499
+ const span2 = findSpan(n, degree, u2, knots);
11500
+ const N = basisFuns(span2, u2, degree, knots);
11493
11501
  let wx = 0, wy = 0, wz = 0, wSum = 0;
11494
11502
  for (let j = 0; j <= degree; j++) {
11495
- const idx = span - degree + j;
11503
+ const idx = span2 - degree + j;
11496
11504
  const w2 = weights[idx] * N[j];
11497
11505
  wx += w2 * controlPoints[idx][0];
11498
11506
  wy += w2 * controlPoints[idx][1];
@@ -11504,11 +11512,11 @@ function deBoor3D(controlPoints, weights, knots, degree, u2) {
11504
11512
  }
11505
11513
  function deBoor2D(controlPoints, weights, knots, degree, u2) {
11506
11514
  const n = controlPoints.length;
11507
- const span = findSpan(n, degree, u2, knots);
11508
- const N = basisFuns(span, u2, degree, knots);
11515
+ const span2 = findSpan(n, degree, u2, knots);
11516
+ const N = basisFuns(span2, u2, degree, knots);
11509
11517
  let wx = 0, wy = 0, wSum = 0;
11510
11518
  for (let j = 0; j <= degree; j++) {
11511
- const idx = span - degree + j;
11519
+ const idx = span2 - degree + j;
11512
11520
  const w2 = weights[idx] * N[j];
11513
11521
  wx += w2 * controlPoints[idx][0];
11514
11522
  wy += w2 * controlPoints[idx][1];
@@ -15472,6 +15480,23 @@ function analyzeNodeUV(node, toLocal) {
15472
15480
  function clampUnit(v) {
15473
15481
  return v < -1 ? -1 : v > 1 ? 1 : v;
15474
15482
  }
15483
+ function sphereUVLocal(lx, ly, lz, radius) {
15484
+ const u2 = atan2(ly, lx) * radius;
15485
+ const len = sqrt$3(lx * lx + ly * ly + lz * lz);
15486
+ const v = acos(clampUnit(lz / (len || 1))) * radius;
15487
+ return [u2, v];
15488
+ }
15489
+ function cylinderUVLocal(lx, ly, lz, radius) {
15490
+ const u2 = atan2(ly, lx) * radius;
15491
+ const v = lz;
15492
+ return [u2, v];
15493
+ }
15494
+ function torusUVLocal(lx, ly, lz, majorRadius, minorRadius) {
15495
+ const u2 = atan2(ly, lx) * majorRadius;
15496
+ const xyDist = sqrt$3(lx * lx + ly * ly) - majorRadius;
15497
+ const v = atan2(lz, xyDist) * minorRadius;
15498
+ return [u2, v];
15499
+ }
15475
15500
  function compileUVFunction(analysis) {
15476
15501
  if (analysis.mode === "triplanar") return null;
15477
15502
  const toLocal = analysis.toLocal;
@@ -15480,19 +15505,14 @@ function compileUVFunction(analysis) {
15480
15505
  const R = analysis.radius;
15481
15506
  return (p2) => {
15482
15507
  const lp = toLocal(p2);
15483
- const u2 = atan2(lp[1], lp[0]) * R;
15484
- const len = sqrt$3(lp[0] * lp[0] + lp[1] * lp[1] + lp[2] * lp[2]);
15485
- const v = acos(clampUnit(lp[2] / (len || 1))) * R;
15486
- return [u2, v];
15508
+ return sphereUVLocal(lp[0], lp[1], lp[2], R);
15487
15509
  };
15488
15510
  }
15489
15511
  case "cylinder": {
15490
15512
  const r = analysis.radius;
15491
15513
  return (p2) => {
15492
15514
  const lp = toLocal(p2);
15493
- const u2 = atan2(lp[1], lp[0]) * r;
15494
- const v = lp[2];
15495
- return [u2, v];
15515
+ return cylinderUVLocal(lp[0], lp[1], lp[2], r);
15496
15516
  };
15497
15517
  }
15498
15518
  case "torus": {
@@ -15500,10 +15520,7 @@ function compileUVFunction(analysis) {
15500
15520
  const r = analysis.radius;
15501
15521
  return (p2) => {
15502
15522
  const lp = toLocal(p2);
15503
- const u2 = atan2(lp[1], lp[0]) * R;
15504
- const xyDist = sqrt$3(lp[0] * lp[0] + lp[1] * lp[1]) - R;
15505
- const v = atan2(lp[2], xyDist) * r;
15506
- return [u2, v];
15523
+ return torusUVLocal(lp[0], lp[1], lp[2], R, r);
15507
15524
  };
15508
15525
  }
15509
15526
  }
@@ -15814,8 +15831,8 @@ function sdProfileRevolve$1(profileFn, degrees, x2, y2, z2) {
15814
15831
  const profileDistance = profileFn(radial, z2);
15815
15832
  if (abs(degrees) >= 360) return profileDistance;
15816
15833
  const sweep = abs(degrees) * DEG$1;
15817
- const center = degrees * DEG$1 * 0.5;
15818
- let angle = Math.atan2(y2, x2) - center;
15834
+ const center2 = degrees * DEG$1 * 0.5;
15835
+ let angle = Math.atan2(y2, x2) - center2;
15819
15836
  while (angle <= -PI$1) angle += PI$1 * 2;
15820
15837
  while (angle > PI$1) angle -= PI$1 * 2;
15821
15838
  const half = sweep * 0.5;
@@ -15833,9 +15850,9 @@ function smax$1(a2, b, k2) {
15833
15850
  function repeatCoord$1(v, spacing, count) {
15834
15851
  if (spacing <= 0) return v;
15835
15852
  if (count > 0) {
15836
- const center = (count - 1) * 0.5;
15837
- const index2 = clamp$3(Math.round(v / spacing + center), 0, count - 1);
15838
- return v - (index2 - center) * spacing;
15853
+ const center2 = (count - 1) * 0.5;
15854
+ const index2 = clamp$3(Math.round(v / spacing + center2), 0, count - 1);
15855
+ return v - (index2 - center2) * spacing;
15839
15856
  }
15840
15857
  return v - spacing * Math.round(v / spacing);
15841
15858
  }
@@ -16761,8 +16778,8 @@ function sdProfileRevolve(b, node, x2, y2, z2) {
16761
16778
  const profileDistance = profilePolygonsSdf(b, node.polygons, radial, z2);
16762
16779
  if (Math.abs(node.degrees) >= 360) return profileDistance;
16763
16780
  const sweep = Math.abs(node.degrees) * DEG;
16764
- const center = node.degrees * DEG * 0.5;
16765
- const angle = b.sub(b.atan2(y2, x2), b.constant(center));
16781
+ const center2 = node.degrees * DEG * 0.5;
16782
+ const angle = b.sub(b.atan2(y2, x2), b.constant(center2));
16766
16783
  const wrapped = b.sub(angle, b.mul(b.constant(TAU), b.round(b.div(angle, b.constant(TAU)))));
16767
16784
  const absAngle = b.abs(wrapped);
16768
16785
  const half = sweep * 0.5;
@@ -16829,9 +16846,9 @@ function select01(b, selector, ifOne, ifZero) {
16829
16846
  function repeatCoord(b, v, spacing, count) {
16830
16847
  if (spacing <= 0) return v;
16831
16848
  if (count > 0) {
16832
- const center = (count - 1) * 0.5;
16833
- const index2 = clampSlot(b, b.round(b.add(b.div(v, b.constant(spacing)), b.constant(center))), 0, count - 1);
16834
- return b.sub(v, b.mul(b.sub(index2, b.constant(center)), b.constant(spacing)));
16849
+ const center2 = (count - 1) * 0.5;
16850
+ const index2 = clampSlot(b, b.round(b.add(b.div(v, b.constant(spacing)), b.constant(center2))), 0, count - 1);
16851
+ return b.sub(v, b.mul(b.sub(index2, b.constant(center2)), b.constant(spacing)));
16835
16852
  }
16836
16853
  return b.sub(v, b.mul(b.constant(spacing), b.round(b.div(v, b.constant(spacing)))));
16837
16854
  }
@@ -17887,49 +17904,55 @@ function signedArea2D(loop) {
17887
17904
  }
17888
17905
  return area2 * 0.5;
17889
17906
  }
17890
- function pointInLoop$2(point2, loop) {
17907
+ function buildSdfLoopEdges(pts) {
17908
+ const n = pts.length;
17909
+ const ax = new Float64Array(n);
17910
+ const ay = new Float64Array(n);
17911
+ const ex = new Float64Array(n);
17912
+ const ey = new Float64Array(n);
17913
+ const invLen2 = new Float64Array(n);
17914
+ for (let i = 0; i < n; i += 1) {
17915
+ const a2 = pts[i];
17916
+ const b = pts[(i + 1) % n];
17917
+ const vx = b[0] - a2[0];
17918
+ const vy = b[1] - a2[1];
17919
+ ax[i] = a2[0];
17920
+ ay[i] = a2[1];
17921
+ ex[i] = vx;
17922
+ ey[i] = vy;
17923
+ const len2 = vx * vx + vy * vy;
17924
+ invLen2[i] = len2 < 1e-12 ? 0 : 1 / len2;
17925
+ }
17926
+ return { n, area: signedArea2D(pts), ax, ay, ex, ey, invLen2 };
17927
+ }
17928
+ function loopSignedDistanceScalar(px, py, e) {
17929
+ let minDist2 = Infinity;
17891
17930
  let inside = false;
17892
- const [px, py] = point2;
17893
- for (let index2 = 0, prev = loop.length - 1; index2 < loop.length; prev = index2, index2 += 1) {
17894
- const [xi, yi] = loop[index2];
17895
- const [xj, yj] = loop[prev];
17896
- const intersects2 = yi > py !== yj > py && px < (xj - xi) * (py - yi) / (yj - yi + 1e-20) + xi;
17897
- if (intersects2) inside = !inside;
17898
- }
17899
- return inside;
17900
- }
17901
- function pointSegDist2D(point2, a2, b) {
17902
- const abx = b[0] - a2[0];
17903
- const aby = b[1] - a2[1];
17904
- const apx = point2[0] - a2[0];
17905
- const apy = point2[1] - a2[1];
17906
- const den = abx * abx + aby * aby;
17907
- const t = den < 1e-12 ? 0 : clamp$1((apx * abx + apy * aby) / den, 0, 1);
17908
- const qx = a2[0] + abx * t;
17909
- const qy = a2[1] + aby * t;
17910
- const dx = point2[0] - qx;
17911
- const dy = point2[1] - qy;
17912
- return Math.sqrt(dx * dx + dy * dy);
17913
- }
17914
- function loopSignedDistance(point2, loop) {
17915
- let minDist = Infinity;
17916
- for (let index2 = 0; index2 < loop.length; index2 += 1) {
17917
- const a2 = loop[index2];
17918
- const b = loop[(index2 + 1) % loop.length];
17919
- minDist = Math.min(minDist, pointSegDist2D(point2, a2, b));
17920
- }
17921
- return pointInLoop$2(point2, loop) ? minDist : -minDist;
17931
+ for (let i = 0; i < e.n; i += 1) {
17932
+ const ax = e.ax[i];
17933
+ const ay = e.ay[i];
17934
+ const vx = e.ex[i];
17935
+ const vy = e.ey[i];
17936
+ const t = clamp$1(((px - ax) * vx + (py - ay) * vy) * e.invLen2[i], 0, 1);
17937
+ const ddx = px - (ax + vx * t);
17938
+ const ddy = py - (ay + vy * t);
17939
+ const d22 = ddx * ddx + ddy * ddy;
17940
+ if (d22 < minDist2) minDist2 = d22;
17941
+ const by = ay + vy;
17942
+ if (ay > py !== by > py && px < vx * (py - ay) / (vy + 1e-20) + ax) inside = !inside;
17943
+ }
17944
+ const d2 = Math.sqrt(minDist2);
17945
+ return inside ? d2 : -d2;
17922
17946
  }
17923
17947
  function compilePolygonsSdf(polygons) {
17924
- const loops = polygons.filter((loop) => Array.isArray(loop) && loop.length >= 3).map((loop) => ({ pts: loop.map(([x2, y2]) => [x2, y2]), area: signedArea2D(loop) }));
17948
+ const loops = polygons.filter((loop) => Array.isArray(loop) && loop.length >= 3).map((loop) => buildSdfLoopEdges(loop.map(([x2, y2]) => [x2, y2])));
17925
17949
  if (loops.length === 0) {
17926
17950
  return () => -1;
17927
17951
  }
17928
17952
  return (x2, y2) => {
17929
- const point2 = [x2, y2];
17930
17953
  let field2 = -Infinity;
17931
17954
  for (const loop of loops) {
17932
- const loopField = loopSignedDistance(point2, loop.pts);
17955
+ const loopField = loopSignedDistanceScalar(x2, y2, loop);
17933
17956
  field2 = loop.area >= 0 ? Math.max(field2, loopField) : Math.min(field2, -loopField);
17934
17957
  }
17935
17958
  return field2;
@@ -18087,24 +18110,40 @@ function interpolateSweepFrame(segment, alpha, origin) {
18087
18110
  function findNearestSweepPoint(point2, segments) {
18088
18111
  let bestIndex = 0;
18089
18112
  let bestAlpha = 0;
18090
- let bestPoint = segments[0].a;
18113
+ let bestAlong = 0;
18091
18114
  let bestDist2 = Infinity;
18115
+ const px = point2[0];
18116
+ const py = point2[1];
18117
+ const pz = point2[2];
18092
18118
  for (let index2 = 0; index2 < segments.length; index2 += 1) {
18093
18119
  const segment = segments[index2];
18094
- const offset = vec3Sub(point2, segment.a);
18095
- const along = clamp$1(vec3Dot(offset, segment.t), 0, segment.len);
18096
- const alpha = segment.len > 1e-9 ? along / segment.len : 0;
18097
- const pointOnPath = vec3Add(segment.a, vec3Scale(segment.t, along));
18098
- const delta = vec3Sub(point2, pointOnPath);
18099
- const dist2 = vec3Dot(delta, delta);
18120
+ const ax = segment.a[0];
18121
+ const ay = segment.a[1];
18122
+ const az = segment.a[2];
18123
+ const tx = segment.t[0];
18124
+ const ty = segment.t[1];
18125
+ const tz = segment.t[2];
18126
+ const along = clamp$1((px - ax) * tx + (py - ay) * ty + (pz - az) * tz, 0, segment.len);
18127
+ const qx = ax + tx * along;
18128
+ const qy = ay + ty * along;
18129
+ const qz = az + tz * along;
18130
+ const dx = px - qx;
18131
+ const dy = py - qy;
18132
+ const dz = pz - qz;
18133
+ const dist2 = dx * dx + dy * dy + dz * dz;
18100
18134
  if (dist2 < bestDist2) {
18101
18135
  bestIndex = index2;
18102
- bestAlpha = alpha;
18103
- bestPoint = pointOnPath;
18136
+ bestAlong = along;
18137
+ bestAlpha = segment.len > 1e-9 ? along / segment.len : 0;
18104
18138
  bestDist2 = dist2;
18105
18139
  }
18106
18140
  }
18107
18141
  const bestSegment = segments[bestIndex];
18142
+ const bestPoint = [
18143
+ bestSegment.a[0] + bestSegment.t[0] * bestAlong,
18144
+ bestSegment.a[1] + bestSegment.t[1] * bestAlong,
18145
+ bestSegment.a[2] + bestSegment.t[2] * bestAlong
18146
+ ];
18108
18147
  return {
18109
18148
  segmentIndex: bestIndex,
18110
18149
  segment: bestSegment,
@@ -19093,8 +19132,8 @@ function stitchSingleLoopLoft(loops, heights, wasm, options) {
19093
19132
  return null;
19094
19133
  }
19095
19134
  }
19096
- function surfaceNormal(chord, span) {
19097
- const n = cross3$6(chord, span);
19135
+ function surfaceNormal(chord, span2) {
19136
+ const n = cross3$6(chord, span2);
19098
19137
  const len = Math.hypot(n[0], n[1], n[2]);
19099
19138
  if (len < 1e-12) {
19100
19139
  const radial = Math.hypot(chord[0], chord[1]);
@@ -19755,6 +19794,16 @@ function fromSlicesSingleSliceHalfExtentForManifold(plan) {
19755
19794
  return Math.max(1, radius + maxOffsetMagnitude + plan.boundsPadding + plan.edgeLength * 3);
19756
19795
  }
19757
19796
  const BOOLEAN_OPERAND_NORMAL_SHARP_ANGLE_DEG = 60;
19797
+ const BOOLEAN_OPERAND_UV_EXTRA_PROP_MIN = 4;
19798
+ function rejectParametricUvBooleanOperands(shapes) {
19799
+ for (const shape of shapes) {
19800
+ if (shape.numProp() >= BOOLEAN_OPERAND_UV_EXTRA_PROP_MIN) {
19801
+ throw new Error(
19802
+ "Cannot boolean a shape that carries parametric UV (mesh numProp 8, vertProperties[6..7]). Parametric UV is baked into the mesh and is destroyed by cuts — Manifold interpolates the uv channels from the other operand onto every new cut/seam vertex. Apply booleans BEFORE texturing the result, or use a projected texture (Shape.wrapTexture with Wrap.*), which is computed from final position and survives booleans."
19803
+ );
19804
+ }
19805
+ }
19806
+ }
19758
19807
  function promoteBooleanOperandNormals(shapes) {
19759
19808
  let maxExtra = 0;
19760
19809
  for (const shape of shapes) maxExtra = Math.max(maxExtra, shape.numProp());
@@ -19776,6 +19825,12 @@ function lowerShapeBooleanCompilePlan(plan, wasm) {
19776
19825
  if (shapes.length === 1) {
19777
19826
  return shapes[0];
19778
19827
  }
19828
+ try {
19829
+ rejectParametricUvBooleanOperands(shapes);
19830
+ } catch (error) {
19831
+ disposeWasmObjects(shapes);
19832
+ throw error;
19833
+ }
19779
19834
  const { operands, created } = promoteBooleanOperandNormals(shapes);
19780
19835
  try {
19781
19836
  switch (plan.op) {
@@ -20533,9 +20588,9 @@ function lowerVerticalVariableSweepStitchedForManifold(plan, sectionPolygons, wa
20533
20588
  return null;
20534
20589
  }
20535
20590
  const frameY = cross3$5(tangent, frameX);
20536
- const span = end[2] - start[2];
20591
+ const span2 = end[2] - start[2];
20537
20592
  const sections = sectionPolygons.map((section) => ({
20538
- height: start[2] + span * section.t,
20593
+ height: start[2] + span2 * section.t,
20539
20594
  polygons: section.polygons.map(
20540
20595
  (loop) => loop.map(([u2, v]) => [start[0] + u2 * frameX[0] + v * frameY[0], start[1] + u2 * frameX[1] + v * frameY[1]])
20541
20596
  )
@@ -23082,7 +23137,7 @@ function lowerAxisymmetricEllipsoidForOCCT(oc, radius, heightRadius) {
23082
23137
  }
23083
23138
  function lowerOrthogonalEllipseFromSlicesPlan(oc, plan) {
23084
23139
  if (plan.groups.length !== 2) return null;
23085
- const center = [0, 0, 0];
23140
+ const center2 = [0, 0, 0];
23086
23141
  const radii = [null, null, null];
23087
23142
  for (const group of plan.groups) {
23088
23143
  if (group.slices.length !== 1) return null;
@@ -23094,7 +23149,7 @@ function lowerOrthogonalEllipseFromSlicesPlan(oc, plan) {
23094
23149
  const uAxis = axisAlignedDirectionForOCCT(frame.u);
23095
23150
  const vAxis = axisAlignedDirectionForOCCT(frame.v);
23096
23151
  if (!normalAxis || !uAxis || !vAxis) return null;
23097
- center[normalAxis.axis] = normalAxis.sign * slice.offset;
23152
+ center2[normalAxis.axis] = normalAxis.sign * slice.offset;
23098
23153
  if (!setEllipsoidRadiusForOCCT(radii, uAxis.axis, profileRadii[0])) return null;
23099
23154
  if (!setEllipsoidRadiusForOCCT(radii, vAxis.axis, profileRadii[1])) return null;
23100
23155
  }
@@ -23105,9 +23160,9 @@ function lowerOrthogonalEllipseFromSlicesPlan(oc, plan) {
23105
23160
  const axisymmetricTolerance = Math.max(1e-7, Math.max(Math.abs(rx), Math.abs(ry)) * 1e-7);
23106
23161
  if (Math.abs(rx - ry) > axisymmetricTolerance) return null;
23107
23162
  let shape = lowerAxisymmetricEllipsoidForOCCT(oc, (rx + ry) / 2, rz);
23108
- if (Math.hypot(center[0], center[1], center[2]) > 1e-10) {
23163
+ if (Math.hypot(center2[0], center2[1], center2[2]) > 1e-10) {
23109
23164
  const trsf = new oc.gp_Trsf_1();
23110
- trsf.SetTranslation_1(new oc.gp_Vec_4(center[0], center[1], center[2]));
23165
+ trsf.SetTranslation_1(new oc.gp_Vec_4(center2[0], center2[1], center2[2]));
23111
23166
  const moved = new oc.BRepBuilderAPI_Transform_2(shape, trsf, true);
23112
23167
  shape = moved.Shape();
23113
23168
  }
@@ -24029,8 +24084,8 @@ function resamplePolyline$1(points, count) {
24029
24084
  for (let index2 = 0; index2 < count; index2 += 1) {
24030
24085
  const target = index2 / (count - 1) * total;
24031
24086
  while (segment < cumulative.length - 2 && cumulative[segment + 1] < target) segment += 1;
24032
- const span = cumulative[segment + 1] - cumulative[segment];
24033
- const t = span > 1e-12 ? (target - cumulative[segment]) / span : 0;
24087
+ const span2 = cumulative[segment + 1] - cumulative[segment];
24088
+ const t = span2 > 1e-12 ? (target - cumulative[segment]) / span2 : 0;
24034
24089
  const a2 = points[segment];
24035
24090
  const b = points[segment + 1];
24036
24091
  out.push([a2[0] + (b[0] - a2[0]) * t, a2[1] + (b[1] - a2[1]) * t, a2[2] + (b[2] - a2[2]) * t]);
@@ -24161,19 +24216,23 @@ function surfaceGridForFillPlan$1(plan) {
24161
24216
  function surfaceGridForAnalyticPlan$1(plan) {
24162
24217
  const count = Math.max(2, plan.resolution + 1);
24163
24218
  const grid = [];
24219
+ const uvGrid = [];
24164
24220
  const { uMin, uMax, vMin, vMax } = plan.surface;
24165
24221
  for (let uIndex = 0; uIndex < count; uIndex += 1) {
24166
24222
  const uNorm = uIndex / (count - 1);
24167
24223
  const u2 = uMin + (uMax - uMin) * uNorm;
24168
24224
  const row = [];
24225
+ const uvRow = [];
24169
24226
  for (let vIndex = 0; vIndex < count; vIndex += 1) {
24170
24227
  const vNorm = vIndex / (count - 1);
24171
24228
  const v = vMin + (vMax - vMin) * vNorm;
24172
24229
  row.push(analyticSurfacePoint$1(plan.surface, u2, v));
24230
+ uvRow.push([uNorm, vNorm]);
24173
24231
  }
24174
24232
  grid.push(row);
24233
+ uvGrid.push(uvRow);
24175
24234
  }
24176
- return { grid, trim: sampleTrim(plan.trim) };
24235
+ return { grid, trim: sampleTrim(plan.trim), uvGrid };
24177
24236
  }
24178
24237
  function computeGridNormals(positions, indices) {
24179
24238
  const normals = Array.from({ length: positions.length }, () => [0, 0, 0]);
@@ -24199,6 +24258,7 @@ function meshFromSurfaceGrid(grid, trim = null, context = "SDF surface sheet", u
24199
24258
  const cols = ((_a3 = grid[0]) == null ? void 0 : _a3.length) ?? 0;
24200
24259
  if (rows < 2 || cols < 2) throw new Error(`${context} requires at least a 2x2 sampled grid.`);
24201
24260
  const positions = grid.flat();
24261
+ const hasParametricUv = uvGrid !== void 0;
24202
24262
  const uvs = [];
24203
24263
  if (uvGrid) {
24204
24264
  if (uvGrid.length !== rows || uvGrid.some((row) => row.length !== cols)) {
@@ -24210,6 +24270,16 @@ function meshFromSurfaceGrid(grid, trim = null, context = "SDF surface sheet", u
24210
24270
  for (let col = 0; col < cols; col += 1) uvs.push([row / (rows - 1), col / (cols - 1)]);
24211
24271
  }
24212
24272
  }
24273
+ const normalizeUv = (() => {
24274
+ if (!uvGrid) return (uv) => uv;
24275
+ const { uDomain, vDomain } = uvDomainFromGrid(uvGrid);
24276
+ const uSpan = uDomain[1] - uDomain[0];
24277
+ const vSpan = vDomain[1] - vDomain[0];
24278
+ return (uv) => [
24279
+ uSpan > 0 ? (uv[0] - uDomain[0]) / uSpan : 0,
24280
+ vSpan > 0 ? (uv[1] - vDomain[0]) / vSpan : 0
24281
+ ];
24282
+ })();
24213
24283
  const sourceIndices = [];
24214
24284
  const includeTriangle = (a2, b, c2) => {
24215
24285
  const centroid2 = [(uvs[a2][0] + uvs[b][0] + uvs[c2][0]) / 3, (uvs[a2][1] + uvs[b][1] + uvs[c2][1]) / 3];
@@ -24228,29 +24298,37 @@ function meshFromSurfaceGrid(grid, trim = null, context = "SDF surface sheet", u
24228
24298
  if (sourceIndices.length === 0) throw new Error(`${context} trim loops removed the whole sampled surface.`);
24229
24299
  const used = /* @__PURE__ */ new Map();
24230
24300
  const compactPositions = [];
24301
+ const compactUvs = [];
24231
24302
  const compactIndices = sourceIndices.map((sourceIndex) => {
24232
24303
  const existing = used.get(sourceIndex);
24233
24304
  if (existing !== void 0) return existing;
24234
24305
  const next = compactPositions.length;
24235
24306
  used.set(sourceIndex, next);
24236
24307
  compactPositions.push(positions[sourceIndex]);
24308
+ if (hasParametricUv) compactUvs.push(normalizeUv(uvs[sourceIndex]));
24237
24309
  return next;
24238
24310
  });
24239
24311
  const compactNormals = computeGridNormals(compactPositions, compactIndices);
24240
- const vertProperties = new Float32Array(compactPositions.length * 6);
24312
+ const numProp = hasParametricUv ? NUM_PROP_WITH_UV : NUM_PROP_WITH_NORMAL;
24313
+ const vertProperties = new Float32Array(compactPositions.length * numProp);
24241
24314
  for (let index2 = 0; index2 < compactPositions.length; index2 += 1) {
24242
24315
  const [x2, y2, z2] = compactPositions[index2];
24243
24316
  const [nx, ny, nz] = compactNormals[index2];
24244
- const offset = index2 * 6;
24245
- vertProperties[offset] = x2;
24246
- vertProperties[offset + 1] = y2;
24247
- vertProperties[offset + 2] = z2;
24248
- vertProperties[offset + 3] = nx;
24249
- vertProperties[offset + 4] = ny;
24250
- vertProperties[offset + 5] = nz;
24317
+ const offset = index2 * numProp;
24318
+ vertProperties[offset + POSITION_OFFSET] = x2;
24319
+ vertProperties[offset + POSITION_OFFSET + 1] = y2;
24320
+ vertProperties[offset + POSITION_OFFSET + 2] = z2;
24321
+ vertProperties[offset + NORMAL_OFFSET] = nx;
24322
+ vertProperties[offset + NORMAL_OFFSET + 1] = ny;
24323
+ vertProperties[offset + NORMAL_OFFSET + 2] = nz;
24324
+ if (hasParametricUv) {
24325
+ const [u2, v] = compactUvs[index2];
24326
+ vertProperties[offset + UV_OFFSET] = u2;
24327
+ vertProperties[offset + UV_OFFSET + 1] = v;
24328
+ }
24251
24329
  }
24252
24330
  return {
24253
- numProp: 6,
24331
+ numProp,
24254
24332
  numVert: compactPositions.length,
24255
24333
  numTri: compactIndices.length / 3,
24256
24334
  vertProperties,
@@ -24335,16 +24413,20 @@ function surfaceGridForNurbsPlan(plan) {
24335
24413
  });
24336
24414
  const resolution = Math.max(2, Math.round(plan.resolution));
24337
24415
  const grid = [];
24416
+ const uvGrid = [];
24338
24417
  for (let i = 0; i <= resolution; i += 1) {
24339
24418
  const u2 = i / resolution;
24340
24419
  const row = [];
24420
+ const uvRow = [];
24341
24421
  for (let j = 0; j <= resolution; j += 1) {
24342
24422
  const v = j / resolution;
24343
24423
  row.push(surface.pointAt(u2, v));
24424
+ uvRow.push([u2, v]);
24344
24425
  }
24345
24426
  grid.push(row);
24427
+ uvGrid.push(uvRow);
24346
24428
  }
24347
- return { grid, rowAxis: "u", trim: sampleTrim(plan.trim), uDomain: [0, 1], vDomain: [0, 1] };
24429
+ return { grid, rowAxis: "u", trim: sampleTrim(plan.trim), uDomain: [0, 1], vDomain: [0, 1], uvGrid };
24348
24430
  }
24349
24431
  function surfaceGridForSheetPlan(plan) {
24350
24432
  if (plan.kind === "queryOwner") return surfaceGridForSheetPlan(plan.base);
@@ -24359,8 +24441,8 @@ function surfaceGridForSheetPlan(plan) {
24359
24441
  }
24360
24442
  if (plan.kind === "nurbsSurface") return surfaceGridForNurbsPlan(plan);
24361
24443
  if (plan.kind === "analyticSurface") {
24362
- const { grid, trim } = surfaceGridForAnalyticPlan$1(plan);
24363
- return { grid, rowAxis: "u", trim, uDomain: [0, 1], vDomain: [0, 1] };
24444
+ const { grid, trim, uvGrid } = surfaceGridForAnalyticPlan$1(plan);
24445
+ return { grid, rowAxis: "u", trim, uDomain: [0, 1], vDomain: [0, 1], uvGrid };
24364
24446
  }
24365
24447
  if (plan.kind === "surfaceRuled") return { grid: surfaceGridForRuledPlan$1(plan), rowAxis: "v" };
24366
24448
  if (plan.kind === "surfaceFill") return { grid: surfaceGridForFillPlan$1(plan), rowAxis: "v" };
@@ -24634,8 +24716,8 @@ function meshFromOpenSurfaceSheetPlan(plan) {
24634
24716
  }
24635
24717
  if (plan.kind === "nurbsSurface") return meshFromOpenNurbsSurface(plan);
24636
24718
  if (plan.kind === "analyticSurface") {
24637
- const { grid, trim } = surfaceGridForAnalyticPlan$1(plan);
24638
- return meshFromSurfaceGrid(grid, trim, "SDF analytic surface");
24719
+ const { grid, trim, uvGrid } = surfaceGridForAnalyticPlan$1(plan);
24720
+ return meshFromSurfaceGrid(grid, trim, "SDF analytic surface", uvGrid);
24639
24721
  }
24640
24722
  if (plan.kind === "surfaceRuled") return meshFromSurfaceGrid(surfaceGridForRuledPlan$1(plan), null, "SDF ruled surface");
24641
24723
  if (plan.kind === "surfaceFill") return meshFromSurfaceFillPlan(plan);
@@ -32117,8 +32199,8 @@ function revolveProfilePolygonsToSdfField(polygons, degrees = 360) {
32117
32199
  const profileDistance = profile.eval(radial, z2);
32118
32200
  if (Math.abs(degrees) >= 360) return profileDistance;
32119
32201
  const sweep = Math.abs(degrees) * (Math.PI / 180);
32120
- const center = degrees * Math.PI / 360;
32121
- let angle = Math.atan2(y2, x2) - center;
32202
+ const center2 = degrees * Math.PI / 360;
32203
+ let angle = Math.atan2(y2, x2) - center2;
32122
32204
  while (angle <= -Math.PI) angle += Math.PI * 2;
32123
32205
  while (angle > Math.PI) angle -= Math.PI * 2;
32124
32206
  const half = sweep / 2;
@@ -33424,15 +33506,15 @@ function deBoorPoint(degree, knots, controlPoints, parameter) {
33424
33506
  const domainEnd = knots[controlPoints.length];
33425
33507
  const t = Math.max(domainStart, Math.min(domainEnd, parameter));
33426
33508
  if (t >= domainEnd - 1e-12) return controlPoints[controlPoints.length - 1];
33427
- let span = degree;
33509
+ let span2 = degree;
33428
33510
  const lastSpan = controlPoints.length - 1;
33429
- while (span < lastSpan && t >= knots[span + 1]) span += 1;
33511
+ while (span2 < lastSpan && t >= knots[span2 + 1]) span2 += 1;
33430
33512
  const points = [];
33431
- for (let index2 = 0; index2 <= degree; index2 += 1) points.push([...controlPoints[span - degree + index2]]);
33513
+ for (let index2 = 0; index2 <= degree; index2 += 1) points.push([...controlPoints[span2 - degree + index2]]);
33432
33514
  for (let order = 1; order <= degree; order += 1) {
33433
33515
  for (let index2 = degree; index2 >= order; index2 -= 1) {
33434
- const knotLeft = knots[span - degree + index2];
33435
- const knotRight = knots[span + index2 + 1 - order];
33516
+ const knotLeft = knots[span2 - degree + index2];
33517
+ const knotRight = knots[span2 + index2 + 1 - order];
33436
33518
  const alpha = Math.abs(knotRight - knotLeft) <= 1e-12 ? 0 : (t - knotLeft) / (knotRight - knotLeft);
33437
33519
  points[index2] = [
33438
33520
  points[index2 - 1][0] * (1 - alpha) + points[index2][0] * alpha,
@@ -33448,16 +33530,16 @@ function deBoorHomogeneousPoint(degree, knots, controlPoints, parameter) {
33448
33530
  const domainEnd = knots[controlPoints.length];
33449
33531
  const t = Math.max(domainStart, Math.min(domainEnd, parameter));
33450
33532
  if (t >= domainEnd - 1e-12) return controlPoints[controlPoints.length - 1];
33451
- let span = degree;
33533
+ let span2 = degree;
33452
33534
  const lastSpan = controlPoints.length - 1;
33453
- while (span < lastSpan && t >= knots[span + 1]) span += 1;
33535
+ while (span2 < lastSpan && t >= knots[span2 + 1]) span2 += 1;
33454
33536
  const points = [];
33455
33537
  for (let index2 = 0; index2 <= degree; index2 += 1)
33456
- points.push([...controlPoints[span - degree + index2]]);
33538
+ points.push([...controlPoints[span2 - degree + index2]]);
33457
33539
  for (let order = 1; order <= degree; order += 1) {
33458
33540
  for (let index2 = degree; index2 >= order; index2 -= 1) {
33459
- const knotLeft = knots[span - degree + index2];
33460
- const knotRight = knots[span + index2 + 1 - order];
33541
+ const knotLeft = knots[span2 - degree + index2];
33542
+ const knotRight = knots[span2 + index2 + 1 - order];
33461
33543
  const alpha = Math.abs(knotRight - knotLeft) <= 1e-12 ? 0 : (t - knotLeft) / (knotRight - knotLeft);
33462
33544
  points[index2] = [
33463
33545
  points[index2 - 1][0] * (1 - alpha) + points[index2][0] * alpha,
@@ -33519,9 +33601,9 @@ function closestBSplineParameter(curve, point2) {
33519
33601
  function wrappedBSplinePoint(curve, parameter) {
33520
33602
  const [domainStart, domainEnd] = bsplineDomain(curve);
33521
33603
  if (!curve.closed) return bsplinePoint(curve, parameter);
33522
- const span = domainEnd - domainStart;
33523
- let wrapped = (parameter - domainStart) % span + domainStart;
33524
- if (wrapped < domainStart) wrapped += span;
33604
+ const span2 = domainEnd - domainStart;
33605
+ let wrapped = (parameter - domainStart) % span2 + domainStart;
33606
+ if (wrapped < domainStart) wrapped += span2;
33525
33607
  return bsplinePoint(curve, wrapped);
33526
33608
  }
33527
33609
  function bsplineSurfacePoint(surface, u2, v) {
@@ -33837,8 +33919,8 @@ function resampleClosedLoop(loop, count) {
33837
33919
  for (let index2 = 0; index2 < count; index2 += 1) {
33838
33920
  const target = index2 / count * total;
33839
33921
  while (segment < points.length - 1 && cumulative[segment + 1] < target) segment += 1;
33840
- const span = cumulative[segment + 1] - cumulative[segment];
33841
- const t = span <= 1e-12 ? 0 : (target - cumulative[segment]) / span;
33922
+ const span2 = cumulative[segment + 1] - cumulative[segment];
33923
+ const t = span2 <= 1e-12 ? 0 : (target - cumulative[segment]) / span2;
33842
33924
  const a2 = points[segment];
33843
33925
  const b = points[(segment + 1) % points.length];
33844
33926
  out.push([a2[0] + (b[0] - a2[0]) * t, a2[1] + (b[1] - a2[1]) * t, a2[2] + (b[2] - a2[2]) * t]);
@@ -33866,14 +33948,14 @@ function orientRadialTriangles(triangles, frame, sameSense) {
33866
33948
  const flip = sameSense ? !radialOutward : radialOutward;
33867
33949
  return flip ? triangles.map(reverseTriangle) : triangles;
33868
33950
  }
33869
- function orientCenteredTriangles(triangles, center, sameSense) {
33951
+ function orientCenteredTriangles(triangles, center2, sameSense) {
33870
33952
  return triangles.map((triangle) => {
33871
33953
  const sample = [
33872
33954
  (triangle[0][0] + triangle[1][0] + triangle[2][0]) / 3,
33873
33955
  (triangle[0][1] + triangle[1][1] + triangle[2][1]) / 3,
33874
33956
  (triangle[0][2] + triangle[1][2] + triangle[2][2]) / 3
33875
33957
  ];
33876
- const outward = [sample[0] - center[0], sample[1] - center[1], sample[2] - center[2]];
33958
+ const outward = [sample[0] - center2[0], sample[1] - center2[1], sample[2] - center2[2]];
33877
33959
  const normal2 = cross3$4(
33878
33960
  [triangle[1][0] - triangle[0][0], triangle[1][1] - triangle[0][1], triangle[1][2] - triangle[0][2]],
33879
33961
  [triangle[2][0] - triangle[0][0], triangle[2][1] - triangle[0][1], triangle[2][2] - triangle[0][2]]
@@ -34112,8 +34194,8 @@ function orientRevolvedProfileTriangles(triangles, frame, axis, profileCenter, p
34112
34194
  (triangle[0][2] + triangle[1][2] + triangle[2][2]) / 3
34113
34195
  ];
34114
34196
  const angle = revolutionAngle(frame, sample, filePath, "SURFACE_OF_REVOLUTION");
34115
- const center = rotateAroundAxis(profileCenter, axis, normalizeAngleDelta(angle - profileAngle));
34116
- const outward = sub3$1(sample, center);
34197
+ const center2 = rotateAroundAxis(profileCenter, axis, normalizeAngleDelta(angle - profileAngle));
34198
+ const outward = sub3$1(sample, center2);
34117
34199
  if (Math.hypot(outward[0], outward[1], outward[2]) <= 1e-12)
34118
34200
  failStepImport(filePath, "SURFACE_OF_REVOLUTION sample hit degenerate profile centerline.");
34119
34201
  const normal2 = cross3$4(sub3$1(triangle[1], triangle[0]), sub3$1(triangle[2], triangle[0]));
@@ -34518,18 +34600,18 @@ function triangulateTrimmedBSplineSurface(surface, loops, sameSense, filePath, c
34518
34600
  const { outer: uvLoop, holes } = trimmedBSplineSurfaceUvLoops(surface, loops, filePath, context);
34519
34601
  if (holes.length > 0 || !isConvexUvLoop(uvLoop))
34520
34602
  return triangulateBSplineSurfaceUvPolygon(surface, uvLoop, holes, sameSense, filePath, context);
34521
- const center = uvCentroid(uvLoop);
34603
+ const center2 = uvCentroid(uvLoop);
34522
34604
  const ringCount = Math.max(3, Math.ceil(Math.max(surface.controlPoints.length, surface.controlPoints[0].length) * 1.5));
34523
34605
  const rings = [];
34524
34606
  for (let ringIndex = 1; ringIndex <= ringCount; ringIndex += 1) {
34525
34607
  const t = ringIndex / ringCount;
34526
- rings.push(uvLoop.map((point2) => [center[0] + (point2[0] - center[0]) * t, center[1] + (point2[1] - center[1]) * t]));
34608
+ rings.push(uvLoop.map((point2) => [center2[0] + (point2[0] - center2[0]) * t, center2[1] + (point2[1] - center2[1]) * t]));
34527
34609
  }
34528
34610
  const mapPoint = (point2) => bsplineSurfacePoint(surface, point2[0], point2[1]);
34529
34611
  const triangles = [];
34530
34612
  const firstRing = rings[0];
34531
34613
  for (let index2 = 0; index2 < firstRing.length; index2 += 1) {
34532
- triangles.push([mapPoint(center), mapPoint(firstRing[index2]), mapPoint(firstRing[(index2 + 1) % firstRing.length])]);
34614
+ triangles.push([mapPoint(center2), mapPoint(firstRing[index2]), mapPoint(firstRing[(index2 + 1) % firstRing.length])]);
34533
34615
  }
34534
34616
  for (let ringIndex = 1; ringIndex < rings.length; ringIndex += 1) {
34535
34617
  const inner = rings[ringIndex - 1];
@@ -34546,9 +34628,9 @@ function rotateArray(items, start) {
34546
34628
  return [...items.slice(start), ...items.slice(0, start)];
34547
34629
  }
34548
34630
  function periodicParameterDistance(a2, b, start, end) {
34549
- const span = end - start;
34631
+ const span2 = end - start;
34550
34632
  const raw = Math.abs(a2 - b);
34551
- return Math.min(raw, Math.max(0, span - raw));
34633
+ return Math.min(raw, Math.max(0, span2 - raw));
34552
34634
  }
34553
34635
  function closedUBSplineBoundaryRing(surface, loop, count, filePath, context) {
34554
34636
  const points = resampleClosedLoop(loop, count);
@@ -34813,11 +34895,11 @@ function sphericalLoopInfo(frame, radius, loop, filePath, context) {
34813
34895
  if (Math.abs(distance - radius) > tolerance)
34814
34896
  failStepImport(filePath, `${context} bounded SPHERICAL_SURFACE boundary point is not on the sphere.`);
34815
34897
  }
34816
- const center = centroid(points);
34898
+ const center2 = centroid(points);
34817
34899
  const normal2 = normalize3$2(newellNormal(points), `${context} bounded SPHERICAL_SURFACE boundary normal`);
34818
- const maxPlaneError = points.reduce((max2, point2) => Math.max(max2, Math.abs(dot3$3(sub3$1(point2, center), normal2))), 0);
34900
+ const maxPlaneError = points.reduce((max2, point2) => Math.max(max2, Math.abs(dot3$3(sub3$1(point2, center2), normal2))), 0);
34819
34901
  if (maxPlaneError > tolerance) failStepImport(filePath, `${context} bounded SPHERICAL_SURFACE boundary loop is not planar.`);
34820
- return { points, center, normal: normal2 };
34902
+ return { points, center: center2, normal: normal2 };
34821
34903
  }
34822
34904
  function triangulateBoundedSphericalBand(frame, radius, loops, sameSense, filePath, context) {
34823
34905
  if (loops.length !== 2) failStepImport(filePath, `${context} bounded SPHERICAL_SURFACE band supports exactly two loops right now.`);
@@ -34838,15 +34920,15 @@ function triangulateBoundedSphericalBand(frame, radius, loops, sameSense, filePa
34838
34920
  const xProjection = dot3$3(rawX, axis);
34839
34921
  const xAxis = normalize3$2([rawX[0] - axis[0] * xProjection, rawX[1] - axis[1] * xProjection, rawX[2] - axis[2] * xProjection], context);
34840
34922
  const yAxis = cross3$4(axis, xAxis);
34841
- const loopAngle = (point2, center) => {
34842
- const local = sub3$1(point2, center);
34923
+ const loopAngle = (point2, center2) => {
34924
+ const local = sub3$1(point2, center2);
34843
34925
  return Math.atan2(dot3$3(local, yAxis), dot3$3(local, xAxis));
34844
34926
  };
34845
- const rotateLoop = (loop, center, targetAngle) => {
34927
+ const rotateLoop = (loop, center2, targetAngle) => {
34846
34928
  let bestIndex = 0;
34847
34929
  let bestDistance = Number.POSITIVE_INFINITY;
34848
34930
  for (let index2 = 0; index2 < loop.length; index2 += 1) {
34849
- const distance = angleDistance(loopAngle(loop[index2], center), targetAngle);
34931
+ const distance = angleDistance(loopAngle(loop[index2], center2), targetAngle);
34850
34932
  if (distance < bestDistance) {
34851
34933
  bestDistance = distance;
34852
34934
  bestIndex = index2;
@@ -34890,14 +34972,14 @@ function triangulateBoundedSphericalBand(frame, radius, loops, sameSense, filePa
34890
34972
  return orientCenteredTriangles(triangles, frame.origin, sameSense);
34891
34973
  }
34892
34974
  function orientBridgeTriangles(triangles, aCenter, bCenter, sameSense) {
34893
- const center = [(aCenter[0] + bCenter[0]) / 2, (aCenter[1] + bCenter[1]) / 2, (aCenter[2] + bCenter[2]) / 2];
34975
+ const center2 = [(aCenter[0] + bCenter[0]) / 2, (aCenter[1] + bCenter[1]) / 2, (aCenter[2] + bCenter[2]) / 2];
34894
34976
  const sample = triangles[0];
34895
34977
  const mid = [
34896
34978
  (sample[0][0] + sample[1][0] + sample[2][0]) / 3,
34897
34979
  (sample[0][1] + sample[1][1] + sample[2][1]) / 3,
34898
34980
  (sample[0][2] + sample[1][2] + sample[2][2]) / 3
34899
34981
  ];
34900
- const outward = [mid[0] - center[0], mid[1] - center[1], mid[2] - center[2]];
34982
+ const outward = [mid[0] - center2[0], mid[1] - center2[1], mid[2] - center2[2]];
34901
34983
  const normal2 = cross3$4(
34902
34984
  [sample[1][0] - sample[0][0], sample[1][1] - sample[0][1], sample[1][2] - sample[0][2]],
34903
34985
  [sample[2][0] - sample[0][0], sample[2][1] - sample[0][1], sample[2][2] - sample[0][2]]
@@ -36405,18 +36487,18 @@ function circleFootprintFromProfile(plan) {
36405
36487
  if (plan.kind !== "circle") return null;
36406
36488
  const radius = Math.abs(plan.radius);
36407
36489
  if (!finitePositive(radius)) return null;
36408
- const center = transformProfilePointThrough([0, 0], plan.transforms);
36490
+ const center2 = transformProfilePointThrough([0, 0], plan.transforms);
36409
36491
  const xPoint = transformProfilePointThrough([1, 0], plan.transforms);
36410
36492
  const yPoint = transformProfilePointThrough([0, 1], plan.transforms);
36411
- const xAxis = [xPoint[0] - center[0], xPoint[1] - center[1]];
36412
- const yAxis = [yPoint[0] - center[0], yPoint[1] - center[1]];
36493
+ const xAxis = [xPoint[0] - center2[0], xPoint[1] - center2[1]];
36494
+ const yAxis = [yPoint[0] - center2[0], yPoint[1] - center2[1]];
36413
36495
  const xScale = Math.hypot(xAxis[0], xAxis[1]);
36414
36496
  const yScale = Math.hypot(yAxis[0], yAxis[1]);
36415
36497
  const dot2 = xAxis[0] * yAxis[0] + xAxis[1] * yAxis[1];
36416
36498
  if (!finitePositive(xScale) || !finitePositive(yScale)) return null;
36417
36499
  if (Math.abs(xScale - yScale) > EPS || Math.abs(dot2) > EPS * xScale * yScale) return null;
36418
36500
  return {
36419
- center,
36501
+ center: center2,
36420
36502
  radius: radius * xScale,
36421
36503
  segments: plan.segments
36422
36504
  };
@@ -36975,19 +37057,19 @@ function explicitGeometrySurface(geometry, face) {
36975
37057
  }
36976
37058
  }
36977
37059
  if ("AnalyticSphere" in geometry) {
36978
- const center = geometry.AnalyticSphere.center;
37060
+ const center2 = geometry.AnalyticSphere.center;
36979
37061
  const radius = geometry.AnalyticSphere.radius;
36980
- if (isVec3(center) && isFiniteNumber(radius) && radius > 0) {
36981
- return { kind: "sphere", center, radius };
37062
+ if (isVec3(center2) && isFiniteNumber(radius) && radius > 0) {
37063
+ return { kind: "sphere", center: center2, radius };
36982
37064
  }
36983
37065
  }
36984
37066
  if ("AnalyticTorus" in geometry) {
36985
- const center = geometry.AnalyticTorus.center;
37067
+ const center2 = geometry.AnalyticTorus.center;
36986
37068
  const axis = geometry.AnalyticTorus.axis;
36987
37069
  const majorRadius = geometry.AnalyticTorus.major_radius;
36988
37070
  const minorRadius = geometry.AnalyticTorus.minor_radius;
36989
- if (isVec3(center) && isVec3(axis) && isFiniteNumber(majorRadius) && isFiniteNumber(minorRadius) && majorRadius > 0 && minorRadius > 0) {
36990
- return { kind: "torus", center, axis: normalizeVec3(axis), majorRadius, minorRadius };
37071
+ if (isVec3(center2) && isVec3(axis) && isFiniteNumber(majorRadius) && isFiniteNumber(minorRadius) && majorRadius > 0 && minorRadius > 0) {
37072
+ return { kind: "torus", center: center2, axis: normalizeVec3(axis), majorRadius, minorRadius };
36991
37073
  }
36992
37074
  }
36993
37075
  if ("NurbsPatch" in geometry) {
@@ -37081,13 +37163,13 @@ function explicitGeometrySurface(geometry, face) {
37081
37163
  function explicitEdgeCurve(geometry, faceName) {
37082
37164
  var _a3, _b3, _c2, _d2, _e2;
37083
37165
  if (!geometry) return void 0;
37084
- const center = (_a3 = geometry.CircularArc) == null ? void 0 : _a3.center;
37166
+ const center2 = (_a3 = geometry.CircularArc) == null ? void 0 : _a3.center;
37085
37167
  const axis = (_b3 = geometry.CircularArc) == null ? void 0 : _b3.axis;
37086
37168
  const radius = (_c2 = geometry.CircularArc) == null ? void 0 : _c2.radius;
37087
- if (center !== void 0 && axis !== void 0 && radius !== void 0 && isVec3(center) && isVec3(axis) && isFiniteNumber(radius) && radius > 0) {
37169
+ if (center2 !== void 0 && axis !== void 0 && radius !== void 0 && isVec3(center2) && isVec3(axis) && isFiniteNumber(radius) && radius > 0) {
37088
37170
  return {
37089
37171
  kind: "circle",
37090
- center,
37172
+ center: center2,
37091
37173
  axis: normalizeVec3(axis),
37092
37174
  radius,
37093
37175
  faceName
@@ -40034,8 +40116,8 @@ function resamplePolyline(points, count) {
40034
40116
  for (let idx = 0; idx < count; idx++) {
40035
40117
  const target = idx / (count - 1) * total;
40036
40118
  while (segment < cumulative.length - 2 && cumulative[segment + 1] < target) segment++;
40037
- const span = cumulative[segment + 1] - cumulative[segment];
40038
- const t = span > 1e-12 ? (target - cumulative[segment]) / span : 0;
40119
+ const span2 = cumulative[segment + 1] - cumulative[segment];
40120
+ const t = span2 > 1e-12 ? (target - cumulative[segment]) / span2 : 0;
40039
40121
  const a2 = points[segment];
40040
40122
  const b = points[segment + 1];
40041
40123
  out.push([a2[0] + (b[0] - a2[0]) * t, a2[1] + (b[1] - a2[1]) * t, a2[2] + (b[2] - a2[2]) * t]);
@@ -40793,12 +40875,12 @@ function transformedSphereProfile(basePlan, steps, sliceOffset) {
40793
40875
  transform = transform.mul(transformForShapeTransform(step));
40794
40876
  scale2 *= stepScale;
40795
40877
  }
40796
- const center = transform.point([0, 0, 0]);
40878
+ const center2 = transform.point([0, 0, 0]);
40797
40879
  const radius = Math.abs(source.radius * scale2);
40798
- const sectionRadius = sliceOffset == null ? radius : Math.sqrt(Math.max(0, radius * radius - (sliceOffset - center[2]) * (sliceOffset - center[2])));
40880
+ const sectionRadius = sliceOffset == null ? radius : Math.sqrt(Math.max(0, radius * radius - (sliceOffset - center2[2]) * (sliceOffset - center2[2])));
40799
40881
  if (sectionRadius <= EXACT_PROFILE_EPS) return emptyProfilePlan();
40800
40882
  const profile = circleProfilePlan(sectionRadius, source.segments);
40801
- if (!isNearlyZero(center[0]) || !isNearlyZero(center[1])) profile.transforms.push({ kind: "translate", x: center[0], y: center[1] });
40883
+ if (!isNearlyZero(center2[0]) || !isNearlyZero(center2[1])) profile.transforms.push({ kind: "translate", x: center2[0], y: center2[1] });
40802
40884
  return profile;
40803
40885
  }
40804
40886
  function profileTransformsForShapeTransform(step) {
@@ -40901,20 +40983,20 @@ function sliceOffsetBeforeShapeTransforms(steps, offset) {
40901
40983
  }
40902
40984
  return sourceOffset;
40903
40985
  }
40904
- function transformZSpan(span, step) {
40986
+ function transformZSpan(span2, step) {
40905
40987
  switch (step.kind) {
40906
40988
  case "translate":
40907
- return [span[0] + step.z, span[1] + step.z];
40989
+ return [span2[0] + step.z, span2[1] + step.z];
40908
40990
  case "scale":
40909
40991
  if (!Number.isFinite(step.z) || isNearlyZero(step.z)) return null;
40910
- return [Math.min(span[0] * step.z, span[1] * step.z), Math.max(span[0] * step.z, span[1] * step.z)];
40992
+ return [Math.min(span2[0] * step.z, span2[1] * step.z), Math.max(span2[0] * step.z, span2[1] * step.z)];
40911
40993
  case "rotateAround": {
40912
40994
  const axisLength = Math.hypot(step.axisX, step.axisY, step.axisZ);
40913
40995
  if (axisLength <= EXACT_PROFILE_EPS) return null;
40914
40996
  const axisX = step.axisX / axisLength;
40915
40997
  const axisY = step.axisY / axisLength;
40916
40998
  const axisZ = step.axisZ / axisLength;
40917
- return !isNearlyZero(axisX) || !isNearlyZero(axisY) || Math.abs(Math.abs(axisZ) - 1) > EXACT_PROFILE_EPS ? null : span;
40999
+ return !isNearlyZero(axisX) || !isNearlyZero(axisY) || Math.abs(Math.abs(axisZ) - 1) > EXACT_PROFILE_EPS ? null : span2;
40918
41000
  }
40919
41001
  case "mirror": {
40920
41002
  const normalLength = Math.hypot(step.normalX, step.normalY, step.normalZ);
@@ -40922,14 +41004,14 @@ function transformZSpan(span, step) {
40922
41004
  const normalX = step.normalX / normalLength;
40923
41005
  const normalY = step.normalY / normalLength;
40924
41006
  const normalZ = step.normalZ / normalLength;
40925
- if (isNearlyZero(normalX) && isNearlyZero(normalY)) return [-span[1], -span[0]];
40926
- return isNearlyZero(normalZ) ? span : null;
41007
+ if (isNearlyZero(normalX) && isNearlyZero(normalY)) return [-span2[1], -span2[0]];
41008
+ return isNearlyZero(normalZ) ? span2 : null;
40927
41009
  }
40928
41010
  case "workplanePlacement": {
40929
41011
  const replay = workplanePlacementReplay(step.matrix);
40930
41012
  if (!replay) return null;
40931
- const z0 = replay.zScale * span[0] + replay.zTranslate;
40932
- const z1 = replay.zScale * span[1] + replay.zTranslate;
41013
+ const z0 = replay.zScale * span2[0] + replay.zTranslate;
41014
+ const z1 = replay.zScale * span2[1] + replay.zTranslate;
40933
41015
  return [Math.min(z0, z1), Math.max(z0, z1)];
40934
41016
  }
40935
41017
  default:
@@ -40983,14 +41065,14 @@ function transformProfilePointThroughRadial(point2, transforms) {
40983
41065
  return out;
40984
41066
  }
40985
41067
  function profileRadialTransformBasis(transforms) {
40986
- const center = transformProfilePointThroughRadial([0, 0], transforms);
41068
+ const center2 = transformProfilePointThroughRadial([0, 0], transforms);
40987
41069
  const xBasis = transformProfilePointThroughRadial([1, 0], transforms);
40988
41070
  const yBasis = transformProfilePointThroughRadial([0, 1], transforms);
40989
- if (!center || !xBasis || !yBasis) return null;
41071
+ if (!center2 || !xBasis || !yBasis) return null;
40990
41072
  return {
40991
- center,
40992
- xAxis: [xBasis[0] - center[0], xBasis[1] - center[1]],
40993
- yAxis: [yBasis[0] - center[0], yBasis[1] - center[1]]
41073
+ center: center2,
41074
+ xAxis: [xBasis[0] - center2[0], xBasis[1] - center2[1]],
41075
+ yAxis: [yBasis[0] - center2[0], yBasis[1] - center2[1]]
40994
41076
  };
40995
41077
  }
40996
41078
  function circleRadialProjectionInterval(plan) {
@@ -41138,13 +41220,13 @@ function radialRoundedRectBoundaryMaxDistanceFromPoint(footprint, point2) {
41138
41220
  [-1, -1]
41139
41221
  ];
41140
41222
  for (const [sx, sy] of cornerSigns) {
41141
- const center = [sx * coreHalfWidth, sy * coreHalfHeight];
41142
- includeLocal([center[0] + sx * footprint.radius, center[1]]);
41143
- includeLocal([center[0], center[1] + sy * footprint.radius]);
41144
- const away = [center[0] - localPoint[0], center[1] - localPoint[1]];
41223
+ const center2 = [sx * coreHalfWidth, sy * coreHalfHeight];
41224
+ includeLocal([center2[0] + sx * footprint.radius, center2[1]]);
41225
+ includeLocal([center2[0], center2[1] + sy * footprint.radius]);
41226
+ const away = [center2[0] - localPoint[0], center2[1] - localPoint[1]];
41145
41227
  const awayLength = Math.hypot(away[0], away[1]);
41146
41228
  if (awayLength > EXACT_PROFILE_EPS && away[0] * sx >= -EXACT_PROFILE_EPS && away[1] * sy >= -EXACT_PROFILE_EPS) {
41147
- includeLocal([center[0] + away[0] / awayLength * footprint.radius, center[1] + away[1] / awayLength * footprint.radius]);
41229
+ includeLocal([center2[0] + away[0] / awayLength * footprint.radius, center2[1] + away[1] / awayLength * footprint.radius]);
41148
41230
  }
41149
41231
  }
41150
41232
  return maxDistance;
@@ -41242,10 +41324,10 @@ function lineIntervalsForRect(base, tangent, halfWidth, halfHeight) {
41242
41324
  if (!applyAxis(base[0], tangent[0], halfWidth) || !applyAxis(base[1], tangent[1], halfHeight)) return [];
41243
41325
  return [[min2, max2]];
41244
41326
  }
41245
- function lineIntervalsForCircle(base, tangent, center, radius) {
41327
+ function lineIntervalsForCircle(base, tangent, center2, radius) {
41246
41328
  if (radius < -EXACT_PROFILE_EPS) return null;
41247
- const dx = base[0] - center[0];
41248
- const dy = base[1] - center[1];
41329
+ const dx = base[0] - center2[0];
41330
+ const dy = base[1] - center2[1];
41249
41331
  const projection = dx * tangent[0] + dy * tangent[1];
41250
41332
  const distanceSq = dx * dx + dy * dy - projection * projection;
41251
41333
  const radiusSq = radius * radius;
@@ -41288,7 +41370,7 @@ function roundedRectRadialSliceIntervals(plan, y2) {
41288
41370
  [-coreHalfWidth, coreHalfHeight],
41289
41371
  [-coreHalfWidth, -coreHalfHeight]
41290
41372
  ];
41291
- for (const center of centers) include(lineIntervalsForCircle(base, tangent, center, radius));
41373
+ for (const center2 of centers) include(lineIntervalsForCircle(base, tangent, center2, radius));
41292
41374
  }
41293
41375
  const xFunctional = [basis.xAxis[0], basis.yAxis[0]];
41294
41376
  const xAtBase = basis.center[0] + xFunctional[0] * base[0] + xFunctional[1] * base[1];
@@ -42596,9 +42678,9 @@ function expandSimpleFullRevolutionProfileFootprint(plan, distance) {
42596
42678
  }
42597
42679
  }
42598
42680
  function profilePlanForSimpleFullRevolutionFootprint(footprint, segments) {
42599
- const translate = (profile, center) => {
42600
- if (!isNearlyZero(center[0]) || !isNearlyZero(center[1])) {
42601
- profile.transforms.push({ kind: "translate", x: center[0], y: center[1] });
42681
+ const translate = (profile, center2) => {
42682
+ if (!isNearlyZero(center2[0]) || !isNearlyZero(center2[1])) {
42683
+ profile.transforms.push({ kind: "translate", x: center2[0], y: center2[1] });
42602
42684
  }
42603
42685
  return profile;
42604
42686
  };
@@ -42616,7 +42698,7 @@ function profilePlanForSimpleFullRevolutionFootprint(footprint, segments) {
42616
42698
  footprint.footprint.center
42617
42699
  ) : null;
42618
42700
  case "roundedRect": {
42619
- const { halfWidth, halfHeight, radius, xAxis, yAxis, center } = footprint.footprint;
42701
+ const { halfWidth, halfHeight, radius, xAxis, yAxis, center: center2 } = footprint.footprint;
42620
42702
  if (halfWidth <= EXACT_PROFILE_EPS || halfHeight <= EXACT_PROFILE_EPS || radius < -EXACT_PROFILE_EPS) return null;
42621
42703
  const profile = roundedRectProfilePlan({
42622
42704
  width: halfWidth * 2,
@@ -42631,7 +42713,7 @@ function profilePlanForSimpleFullRevolutionFootprint(footprint, segments) {
42631
42713
  m11: yAxis[1]
42632
42714
  })
42633
42715
  );
42634
- return translate(profile, center);
42716
+ return translate(profile, center2);
42635
42717
  }
42636
42718
  default:
42637
42719
  return assertExhaustive(footprint);
@@ -43218,16 +43300,16 @@ function exactVerticalProjectionProfilePlan(plan) {
43218
43300
  const base = exactVerticalProjectionProfilePlan(plan.base);
43219
43301
  if (!base) return null;
43220
43302
  let profile = base.profile;
43221
- let span = [base.zMin, base.zMax];
43303
+ let span2 = [base.zMin, base.zMax];
43222
43304
  for (const step of plan.steps) {
43223
43305
  const projectedTransforms = profileTransformsForShapeTransform(step);
43224
43306
  if (!projectedTransforms) return null;
43225
- const nextSpan = transformZSpan(span, step);
43307
+ const nextSpan = transformZSpan(span2, step);
43226
43308
  if (!nextSpan) return null;
43227
43309
  profile = appendProfileTransforms(profile, projectedTransforms);
43228
- span = nextSpan;
43310
+ span2 = nextSpan;
43229
43311
  }
43230
- return { profile, zMin: span[0], zMax: span[1] };
43312
+ return { profile, zMin: span2[0], zMax: span2[1] };
43231
43313
  }
43232
43314
  default:
43233
43315
  return null;
@@ -43247,7 +43329,7 @@ function projectVerticalDifferenceProfilePlan(subject, clips) {
43247
43329
  const clipped = clips.map((clip) => ({ clip, span: clippedZSpan(subject, clip) })).filter((entry) => entry.span != null);
43248
43330
  if (clipped.length === 0) return cloneProfileCompilePlan(subject.profile);
43249
43331
  const zBreaks = [subject.zMin, subject.zMax];
43250
- for (const { span } of clipped) zBreaks.push(span[0], span[1]);
43332
+ for (const { span: span2 } of clipped) zBreaks.push(span2[0], span2[1]);
43251
43333
  zBreaks.sort((a2, b) => a2 - b);
43252
43334
  const uniqueBreaks = [];
43253
43335
  for (const z2 of zBreaks) {
@@ -43260,7 +43342,7 @@ function projectVerticalDifferenceProfilePlan(subject, clips) {
43260
43342
  const zMax = uniqueBreaks[idx + 1];
43261
43343
  if (zMax - zMin <= EXACT_PROFILE_EPS) continue;
43262
43344
  const zMid = (zMin + zMax) / 2;
43263
- const activeClips = clipped.filter(({ span }) => offsetInRange(zMid, span[0], span[1]));
43345
+ const activeClips = clipped.filter(({ span: span2 }) => offsetInRange(zMid, span2[0], span2[1]));
43264
43346
  if (activeClips.length === 0) return cloneProfileCompilePlan(subject.profile);
43265
43347
  slabProfiles.push({
43266
43348
  kind: "boolean",
@@ -44233,7 +44315,7 @@ function clusterMeshFaces(shape) {
44233
44315
  return clusters;
44234
44316
  }
44235
44317
  function clusterToFaceRef(cluster, name = "") {
44236
- const center = [
44318
+ const center2 = [
44237
44319
  cluster.centroidSum[0] / cluster.count,
44238
44320
  cluster.centroidSum[1] / cluster.count,
44239
44321
  cluster.centroidSum[2] / cluster.count
@@ -44242,7 +44324,7 @@ function clusterToFaceRef(cluster, name = "") {
44242
44324
  return {
44243
44325
  name,
44244
44326
  normal: cluster.normal,
44245
- center,
44327
+ center: center2,
44246
44328
  planar: true,
44247
44329
  uAxis: u2,
44248
44330
  vAxis: v
@@ -44414,7 +44496,7 @@ function representativeFaceForMembers(name, faces, query, semantic) {
44414
44496
  const cloned = faces.map((face) => cloneFaceRefValue(face));
44415
44497
  const first = cloned[0];
44416
44498
  const coplanar = facesAreCoplanar(cloned);
44417
- const center = cloned.reduce(
44499
+ const center2 = cloned.reduce(
44418
44500
  (acc, face) => [acc[0] + face.center[0], acc[1] + face.center[1], acc[2] + face.center[2]],
44419
44501
  [0, 0, 0]
44420
44502
  );
@@ -44422,7 +44504,7 @@ function representativeFaceForMembers(name, faces, query, semantic) {
44422
44504
  const representative = {
44423
44505
  ...first,
44424
44506
  name,
44425
- center: [center[0] / count, center[1] / count, center[2] / count],
44507
+ center: [center2[0] / count, center2[1] / count, center2[2] / count],
44426
44508
  query: cloneFaceQueryRef(query),
44427
44509
  planar: coplanar ? first.planar : false,
44428
44510
  uAxis: coplanar ? first.uAxis : void 0,
@@ -45542,10 +45624,10 @@ function resolveShapeFaceTableInternal(plan, owner) {
45542
45624
  if (!planeCapQuery) return table;
45543
45625
  const baseTable = resolveShapeFaceTable(plan.base);
45544
45626
  const centers = Array.from(baseTable.faces.values()).map((face) => face.center);
45545
- const averageCenter = centers.length > 0 ? centers.reduce((acc, center2) => [acc[0] + center2[0], acc[1] + center2[1], acc[2] + center2[2]], [0, 0, 0]).map((value) => value / centers.length) : [0, 0, plan.originOffset];
45627
+ const averageCenter = centers.length > 0 ? centers.reduce((acc, center22) => [acc[0] + center22[0], acc[1] + center22[1], acc[2] + center22[2]], [0, 0, 0]).map((value) => value / centers.length) : [0, 0, plan.originOffset];
45546
45628
  const normal2 = normalizeAxis([plan.normalX, plan.normalY, plan.normalZ]);
45547
45629
  const planeDistance = averageCenter[0] * normal2[0] + averageCenter[1] * normal2[1] + averageCenter[2] * normal2[2] - plan.originOffset;
45548
- const center = [
45630
+ const center2 = [
45549
45631
  averageCenter[0] - normal2[0] * planeDistance,
45550
45632
  averageCenter[1] - normal2[1] * planeDistance,
45551
45633
  averageCenter[2] - normal2[2] * planeDistance
@@ -45554,7 +45636,7 @@ function resolveShapeFaceTableInternal(plan, owner) {
45554
45636
  registerFace(table, {
45555
45637
  name: "plane-cap",
45556
45638
  normal: normal2,
45557
- center,
45639
+ center: center2,
45558
45640
  planar: true,
45559
45641
  uAxis: basis.u,
45560
45642
  vAxis: basis.v,
@@ -48454,6 +48536,99 @@ function computeSeatOverTranslation(targetFaceVertices, targetFaceNormal, selfMe
48454
48536
  sampleCount: targetFaceVertices.length
48455
48537
  };
48456
48538
  }
48539
+ const UV_ANCHORS = ["top", "bottom", "front", "back", "left", "right"];
48540
+ new Set(UV_ANCHORS);
48541
+ const FACE_FRAME = {
48542
+ front: { u: 0, v: 2 },
48543
+ // -y face: u→x, v→z
48544
+ back: { u: 0, v: 2 },
48545
+ // +y face
48546
+ left: { u: 1, v: 2 },
48547
+ // -x face: u→y, v→z
48548
+ right: { u: 1, v: 2 },
48549
+ // +x face
48550
+ top: { u: 0, v: 1 },
48551
+ // +z face: u→x, v→y
48552
+ bottom: { u: 0, v: 1 }
48553
+ // -z face
48554
+ };
48555
+ const AXIS_INDEX = { x: 0, y: 1, z: 2 };
48556
+ function span(bbox, axis) {
48557
+ return bbox.max[axis] - bbox.min[axis];
48558
+ }
48559
+ function center(bbox) {
48560
+ return [
48561
+ (bbox.min[0] + bbox.max[0]) / 2,
48562
+ (bbox.min[1] + bbox.max[1]) / 2,
48563
+ (bbox.min[2] + bbox.max[2]) / 2
48564
+ ];
48565
+ }
48566
+ function autoFitProjection(spec, bbox) {
48567
+ switch (spec.kind) {
48568
+ case "flat": {
48569
+ const frame = FACE_FRAME[spec.onto];
48570
+ const out = { ...spec };
48571
+ if (out.width === void 0) {
48572
+ const w2 = span(bbox, frame.u);
48573
+ if (w2 > 0) {
48574
+ out.width = w2;
48575
+ if (out.offsetU === void 0) out.offsetU = bbox.min[frame.u];
48576
+ }
48577
+ }
48578
+ if (out.height === void 0) {
48579
+ const h = span(bbox, frame.v);
48580
+ if (h > 0) {
48581
+ out.height = h;
48582
+ if (out.offsetV === void 0) out.offsetV = bbox.min[frame.v];
48583
+ }
48584
+ }
48585
+ return out;
48586
+ }
48587
+ case "box": {
48588
+ const out = { ...spec };
48589
+ if (out.size === void 0) {
48590
+ const sx = span(bbox, 0);
48591
+ const sy = span(bbox, 1);
48592
+ const sz = span(bbox, 2);
48593
+ if (sx > 0 && sy > 0 && sz > 0) out.size = [sx, sy, sz];
48594
+ }
48595
+ if (out.origin === void 0) out.origin = center(bbox);
48596
+ return out;
48597
+ }
48598
+ case "cylinder": {
48599
+ const axis = AXIS_INDEX[spec.axis];
48600
+ const out = { ...spec };
48601
+ if (out.height === void 0) {
48602
+ const h = span(bbox, axis);
48603
+ if (h > 0) {
48604
+ out.height = h;
48605
+ if (out.offsetV === void 0) out.offsetV = bbox.min[axis];
48606
+ }
48607
+ }
48608
+ if (out.origin === void 0) out.origin = center(bbox);
48609
+ return out;
48610
+ }
48611
+ case "sphere": {
48612
+ const out = { ...spec };
48613
+ if (out.origin === void 0) out.origin = center(bbox);
48614
+ if (out.radius === void 0) {
48615
+ const c2 = center(bbox);
48616
+ const radius = Math.max(
48617
+ bbox.max[0] - c2[0],
48618
+ bbox.max[1] - c2[1],
48619
+ bbox.max[2] - c2[2]
48620
+ );
48621
+ if (radius > 0) out.radius = radius;
48622
+ }
48623
+ return out;
48624
+ }
48625
+ default:
48626
+ throw new Error(`autoFitProjection: unknown projection kind "${spec.kind}"`);
48627
+ }
48628
+ }
48629
+ function isImageHandle(value) {
48630
+ return typeof value === "object" && value !== null && value.__forgeImage === true;
48631
+ }
48457
48632
  const _shapeDimensions = /* @__PURE__ */ new WeakMap();
48458
48633
  let _shapeDimensionCounter = 0;
48459
48634
  function nextShapeDimensionId() {
@@ -48826,8 +49001,8 @@ function mergeShapeSourceSpans(sources, target) {
48826
49001
  records = /* @__PURE__ */ new Map();
48827
49002
  _shapeSourceSpans.set(target, records);
48828
49003
  }
48829
- for (const [key2, span] of sourceRecords) {
48830
- if (!records.has(key2)) records.set(key2, span);
49004
+ for (const [key2, span2] of sourceRecords) {
49005
+ if (!records.has(key2)) records.set(key2, span2);
48831
49006
  }
48832
49007
  }
48833
49008
  }
@@ -49233,7 +49408,7 @@ function annotateTruckPrimitiveAnalyticFaces(plan, topology) {
49233
49408
  const radiusTop = (source.radiusTop ?? source.radius) * distanceScale;
49234
49409
  const origin = transform.point(signedHeight >= 0 ? [0, 0, 0] : [0, 0, signedHeight]);
49235
49410
  const sidePointRadius = (Math.abs(radiusBottom) + Math.abs(radiusTop)) / 2;
49236
- const center = transform.point([sidePointRadius / distanceScale, 0, signedHeight / 2]);
49411
+ const center2 = transform.point([sidePointRadius / distanceScale, 0, signedHeight / 2]);
49237
49412
  const normal2 = transformedUnitAxis(transform, [1, 0, 0]) ?? [1, 0, 0];
49238
49413
  const surface = sameScalar(radiusBottom, radiusTop) ? { kind: "cylinder", origin, axis, radius: radiusBottom, height } : { kind: "cone", origin, axis, radiusBottom, radiusTop, height };
49239
49414
  for (const [name, face] of faces) {
@@ -49244,7 +49419,7 @@ function annotateTruckPrimitiveAnalyticFaces(plan, topology) {
49244
49419
  upsertAnalyticFace(faces, {
49245
49420
  name: "side",
49246
49421
  normal: normal2,
49247
- center,
49422
+ center: center2,
49248
49423
  planar: false,
49249
49424
  surface
49250
49425
  });
@@ -49254,8 +49429,8 @@ function annotateTruckPrimitiveAnalyticFaces(plan, topology) {
49254
49429
  if (distanceScale === null) return;
49255
49430
  const normal2 = transformedUnitAxis(transform, [1, 0, 0]) ?? [1, 0, 0];
49256
49431
  const radius = source.radius * distanceScale;
49257
- const center = transform.point([0, 0, 0]);
49258
- const surface = { kind: "sphere", center, radius };
49432
+ const center2 = transform.point([0, 0, 0]);
49433
+ const surface = { kind: "sphere", center: center2, radius };
49259
49434
  for (const [name, face] of faces) {
49260
49435
  if (name === "surface" || /^north-\d+$/.test(name) || /^band-\d+-\d+$/.test(name) || /^south-\d+$/.test(name)) {
49261
49436
  faces.set(name, { ...face, surface: cloneFaceSurface(surface) ?? surface });
@@ -49275,10 +49450,10 @@ function annotateTruckPrimitiveAnalyticFaces(plan, topology) {
49275
49450
  const axis = transformedUnitAxis(transform, [0, 0, 1]);
49276
49451
  const normal2 = transformedUnitAxis(transform, [1, 0, 0]) ?? [1, 0, 0];
49277
49452
  if (!axis) return;
49278
- const center = transform.point([0, 0, 0]);
49453
+ const center2 = transform.point([0, 0, 0]);
49279
49454
  const surface = {
49280
49455
  kind: "torus",
49281
- center,
49456
+ center: center2,
49282
49457
  axis,
49283
49458
  majorRadius: source.majorRadius * distanceScale,
49284
49459
  minorRadius: source.minorRadius * distanceScale
@@ -49453,7 +49628,7 @@ function addCompileAliasesToTopology(plan, topology) {
49453
49628
  ["top-rim", [radiusTop, 0, source.height], [0, radiusTop, source.height], [0, 0, source.height], radiusTop, [0, 0, 1]],
49454
49629
  ["bottom-rim", [source.radius, 0, 0], [0, source.radius, 0], [0, 0, 0], source.radius, [0, 0, -1]]
49455
49630
  ];
49456
- for (const [name, start, end, center, radius, axis] of semanticEdges2) {
49631
+ for (const [name, start, end, center2, radius, axis] of semanticEdges2) {
49457
49632
  if (topology.edges.has(name)) continue;
49458
49633
  const faceName = name === "top-rim" ? "top" : "bottom";
49459
49634
  topology.edges.set(name, {
@@ -49468,7 +49643,7 @@ function addCompileAliasesToTopology(plan, topology) {
49468
49643
  },
49469
49644
  curve: {
49470
49645
  kind: "circle",
49471
- center,
49646
+ center: center2,
49472
49647
  axis,
49473
49648
  radius,
49474
49649
  faceName
@@ -49662,6 +49837,7 @@ function withBaseDimensions(base, out) {
49662
49837
  if (baseTopo) _shapeTopology.set(out, cloneTopology(baseTopo));
49663
49838
  const baseLabels = cloneFaceLabelMap(_shapeFaceLabels.get(base));
49664
49839
  if (baseLabels) _shapeFaceLabels.set(out, baseLabels);
49840
+ if (base.materialProps) out.materialProps = { ...base.materialProps };
49665
49841
  copyShapeReferenceMetadata(base, out);
49666
49842
  copyShapeSourceSpans(base, out);
49667
49843
  return setShapeCompilePlanInternal(out, getShapeCompilePlanInternal(base));
@@ -49750,6 +49926,30 @@ function createOwnedTopologyRewritePlan(plan, operation2, buildPropagation) {
49750
49926
  const owner = createShapeQueryOwner(operation2);
49751
49927
  return wrapShapeCompilePlanWithQueryOwner(attachTopologyRewritePropagation(plan, buildPropagation(owner)), owner);
49752
49928
  }
49929
+ const KNOWN_PROJECTION_KINDS = /* @__PURE__ */ new Set(["flat", "cylinder", "sphere", "box"]);
49930
+ function validateProjectionFinite(spec) {
49931
+ switch (spec.kind) {
49932
+ case "flat":
49933
+ if (spec.width !== void 0) requireFiniteNumber(spec.width, "wrapTexture() flat width");
49934
+ if (spec.height !== void 0) requireFiniteNumber(spec.height, "wrapTexture() flat height");
49935
+ if (spec.offsetU !== void 0) requireFiniteNumber(spec.offsetU, "wrapTexture() flat offsetU");
49936
+ if (spec.offsetV !== void 0) requireFiniteNumber(spec.offsetV, "wrapTexture() flat offsetV");
49937
+ break;
49938
+ case "box":
49939
+ if (spec.size !== void 0) requireFiniteVec3$1(spec.size, "wrapTexture() box size");
49940
+ if (spec.origin !== void 0) requireFiniteVec3$1(spec.origin, "wrapTexture() box origin");
49941
+ break;
49942
+ case "cylinder":
49943
+ if (spec.height !== void 0) requireFiniteNumber(spec.height, "wrapTexture() cylinder height");
49944
+ if (spec.offsetV !== void 0) requireFiniteNumber(spec.offsetV, "wrapTexture() cylinder offsetV");
49945
+ if (spec.origin !== void 0) requireFiniteVec3$1(spec.origin, "wrapTexture() cylinder origin");
49946
+ break;
49947
+ case "sphere":
49948
+ if (spec.origin !== void 0) requireFiniteVec3$1(spec.origin, "wrapTexture() sphere origin");
49949
+ if (spec.radius !== void 0) requireFiniteNumber(spec.radius, "wrapTexture() sphere radius");
49950
+ break;
49951
+ }
49952
+ }
49753
49953
  function parseReferencePath(path) {
49754
49954
  const trimmed = path.trim();
49755
49955
  if (!trimmed) throw new Error("Shape.ref() requires a non-empty path.");
@@ -50127,6 +50327,61 @@ class Shape {
50127
50327
  out.materialProps = { ...this.materialProps ?? {}, ...props };
50128
50328
  return out;
50129
50329
  }
50330
+ /**
50331
+ * Wrap an imported bitmap image around this shape using a projection.
50332
+ *
50333
+ * **Details**
50334
+ *
50335
+ * The `image` comes from `Import.image('path.png')`; the `projection` is one of the `Wrap.*`
50336
+ * helpers — `Wrap.flat({ onto: 'top' })` lays it flat on a face, `Wrap.aroundCylinder({ axis: 'z' })`
50337
+ * wraps it like a can label, `Wrap.onSphere()` maps it like a globe, and `Wrap.box()` cube-maps
50338
+ * it onto the six sides.
50339
+ *
50340
+ * By default the image **auto-fits** the shape — one copy across the relevant extent, so no
50341
+ * `width`/`height`/`size` is needed (pass them only to override). The (u,v) is derived from each
50342
+ * vertex's final world position, so the image stays glued to the surface through transforms and
50343
+ * boolean cuts with no UV layout to maintain — apply `wrapTexture` *after* positioning the shape.
50344
+ * Returns a new Shape; the original is unchanged.
50345
+ *
50346
+ * **Example**
50347
+ *
50348
+ * ```js
50349
+ * const logo = Import.image('./logo.png');
50350
+ * box(80, 80, 10).wrapTexture(logo, Wrap.flat({ onto: 'top' })); // auto-fits the face
50351
+ *
50352
+ * const label = Import.image('./label.jpg');
50353
+ * cylinder(60, 20).wrapTexture(label, Wrap.aroundCylinder({ axis: 'z' })); // wraps the side
50354
+ * ```
50355
+ *
50356
+ * @param image - An imported bitmap from `Import.image(...)`
50357
+ * @param projection - A projection spec from `Wrap.flat` / `Wrap.aroundCylinder` / `Wrap.onSphere` / `Wrap.box`
50358
+ * @returns A new Shape with the projected texture recorded in its material properties
50359
+ * @category Materials
50360
+ */
50361
+ wrapTexture(image, projection) {
50362
+ if (!isImageHandle(image)) {
50363
+ throw new Error("wrapTexture() expects an image from Import.image(...) as its first argument");
50364
+ }
50365
+ if (!projection || typeof projection !== "object" || !KNOWN_PROJECTION_KINDS.has(projection.kind)) {
50366
+ throw new Error(
50367
+ `wrapTexture() expects a projection from Wrap.flat / Wrap.aroundCylinder / Wrap.onSphere / Wrap.box, got ${JSON.stringify(projection == null ? void 0 : projection.kind)}`
50368
+ );
50369
+ }
50370
+ const bbox = this.boundingBox();
50371
+ const filled = autoFitProjection(projection, bbox);
50372
+ validateProjectionFinite(filled);
50373
+ const out = this.clone();
50374
+ out.materialProps = {
50375
+ ...this.materialProps ?? {},
50376
+ texture: {
50377
+ image: image.dataUri,
50378
+ projection: filled,
50379
+ imageWidth: image.width,
50380
+ imageHeight: image.height
50381
+ }
50382
+ };
50383
+ return out;
50384
+ }
50130
50385
  /** Return a new Shape wrapper for explicit duplication in scripts. */
50131
50386
  clone() {
50132
50387
  const out = withCopiedDimensions(this, new Shape(getShapeRuntimeBackendInternal(this).clone(), this.colorHex));
@@ -50654,12 +50909,12 @@ class Shape {
50654
50909
  scale(v) {
50655
50910
  const scale2 = requireNonZeroFiniteScale3(v, "Shape.scale() scale");
50656
50911
  const bb = this.boundingBox();
50657
- const center = [
50912
+ const center2 = [
50658
50913
  (bb.min[0] + bb.max[0]) / 2,
50659
50914
  (bb.min[1] + bb.max[1]) / 2,
50660
50915
  (bb.min[2] + bb.max[2]) / 2
50661
50916
  ];
50662
- return this.scaleAround(center, scale2);
50917
+ return this.scaleAround(center2, scale2);
50663
50918
  }
50664
50919
  /** Scale the shape uniformly or per-axis from an explicit pivot point. */
50665
50920
  scaleAround(pivot, v) {
@@ -50693,12 +50948,12 @@ class Shape {
50693
50948
  /** Mirror across a plane through the shape's bounding box center, defined by its normal vector. */
50694
50949
  mirror(normal2) {
50695
50950
  const bb = this.boundingBox();
50696
- const center = [
50951
+ const center2 = [
50697
50952
  (bb.min[0] + bb.max[0]) / 2,
50698
50953
  (bb.min[1] + bb.max[1]) / 2,
50699
50954
  (bb.min[2] + bb.max[2]) / 2
50700
50955
  ];
50701
- return this.mirrorThrough(center, normal2);
50956
+ return this.mirrorThrough(center2, normal2);
50702
50957
  }
50703
50958
  /** Mirror across a plane through an explicit point, defined by its normal vector. */
50704
50959
  mirrorThrough(point2, normal2) {
@@ -51458,7 +51713,8 @@ class FrozenShapeBackend {
51458
51713
  edgePositions: this._data.geometryEdgePositions,
51459
51714
  triangleFaceIds: this._data.geometryTriangleFaceIds,
51460
51715
  faceIdNames: this._data.geometryFaceIdNames,
51461
- hasSmoothNormals: this._data.hasSmoothNormals ?? false
51716
+ hasSmoothNormals: this._data.hasSmoothNormals ?? false,
51717
+ uvs: this._data.geometryUvs
51462
51718
  };
51463
51719
  }
51464
51720
  getMeshReconstructionData() {
@@ -51629,9 +51885,17 @@ const EDGE_THRESHOLD_DOT = Math.cos(Math.PI / 180);
51629
51885
  const SMOOTH_THRESHOLD_DOT = Math.cos(30 * Math.PI / 180);
51630
51886
  function computeGeometryArrays(mesh, options = {}) {
51631
51887
  const { numProp, numTri: triCount, triVerts, vertProperties, vertNormals, cornerNormals } = mesh;
51888
+ if (numProp !== NUM_PROP_POSITION_ONLY && numProp !== NUM_PROP_WITH_NORMAL && numProp !== NUM_PROP_WITH_UV) {
51889
+ throw new Error(
51890
+ `computeGeometryArrays: illegal vertProperties numProp ${numProp}. Only ${NUM_PROP_POSITION_ONLY} (position), ${NUM_PROP_WITH_NORMAL} (position+normal), and ${NUM_PROP_WITH_UV} (position+normal+uv) are valid; ${numProp} (e.g. a truncated uv channel) is not.`
51891
+ );
51892
+ }
51893
+ const hasStoredNormals = numProp === NUM_PROP_WITH_NORMAL || numProp === NUM_PROP_WITH_UV;
51894
+ const hasStoredUvs = numProp === NUM_PROP_WITH_UV;
51632
51895
  const useCornerNormals = !!cornerNormals && cornerNormals.length === triCount * 9;
51633
51896
  const positions = new Float32Array(triCount * 9);
51634
51897
  const normals = new Float32Array(triCount * 9);
51898
+ const uvs = hasStoredUvs ? new Float32Array(triCount * 6) : void 0;
51635
51899
  const faceNx = new Float32Array(triCount);
51636
51900
  const faceNy = new Float32Array(triCount);
51637
51901
  const faceNz = new Float32Array(triCount);
@@ -51661,6 +51925,15 @@ function computeGeometryArrays(mesh, options = {}) {
51661
51925
  positions[o + 6] = cx;
51662
51926
  positions[o + 7] = cy;
51663
51927
  positions[o + 8] = cz;
51928
+ if (uvs) {
51929
+ const u2 = t * 6;
51930
+ uvs[u2] = vertProperties[i0 * numProp + UV_OFFSET];
51931
+ uvs[u2 + 1] = vertProperties[i0 * numProp + UV_OFFSET + 1];
51932
+ uvs[u2 + 2] = vertProperties[i1 * numProp + UV_OFFSET];
51933
+ uvs[u2 + 3] = vertProperties[i1 * numProp + UV_OFFSET + 1];
51934
+ uvs[u2 + 4] = vertProperties[i2 * numProp + UV_OFFSET];
51935
+ uvs[u2 + 5] = vertProperties[i2 * numProp + UV_OFFSET + 1];
51936
+ }
51664
51937
  if (useCornerNormals) {
51665
51938
  for (let k2 = 0; k2 < 9; k2++) normals[o + k2] = cornerNormals[o + k2];
51666
51939
  } else if (vertNormals) {
@@ -51673,13 +51946,13 @@ function computeGeometryArrays(mesh, options = {}) {
51673
51946
  normals[o + 6] = vertNormals[i2 * 3];
51674
51947
  normals[o + 7] = vertNormals[i2 * 3 + 1];
51675
51948
  normals[o + 8] = vertNormals[i2 * 3 + 2];
51676
- } else if (numProp >= 6) {
51949
+ } else if (hasStoredNormals) {
51677
51950
  const corners = [i0, i1, i2];
51678
51951
  for (let v = 0; v < 3; v++) {
51679
51952
  const base = corners[v] * numProp;
51680
- const nx = vertProperties[base + 3];
51681
- const ny = vertProperties[base + 4];
51682
- const nz = vertProperties[base + 5];
51953
+ const nx = vertProperties[base + NORMAL_OFFSET];
51954
+ const ny = vertProperties[base + NORMAL_OFFSET + 1];
51955
+ const nz = vertProperties[base + NORMAL_OFFSET + 2];
51683
51956
  const oc = o + v * 3;
51684
51957
  if (nx * nx + ny * ny + nz * nz > 1e-12) {
51685
51958
  normals[oc] = nx;
@@ -51706,7 +51979,7 @@ function computeGeometryArrays(mesh, options = {}) {
51706
51979
  faceNy[t] = fny;
51707
51980
  faceNz[t] = fnz;
51708
51981
  }
51709
- if (!useCornerNormals && !vertNormals && numProp < 6 && triCount > 0) {
51982
+ if (!useCornerNormals && !vertNormals && !hasStoredNormals && triCount > 0) {
51710
51983
  computeAutoSmoothNormals(
51711
51984
  triVerts,
51712
51985
  vertProperties,
@@ -51725,7 +51998,8 @@ function computeGeometryArrays(mesh, options = {}) {
51725
51998
  positions,
51726
51999
  normals,
51727
52000
  edgePositions,
51728
- hasSmoothNormals: triCount > 0
52001
+ hasSmoothNormals: triCount > 0,
52002
+ uvs
51729
52003
  };
51730
52004
  }
51731
52005
  function computeAutoSmoothNormals(triVerts, vertProperties, numProp, triCount, faceNx, faceNy, faceNz, normals, mergeFromVert, mergeToVert) {
@@ -51860,10 +52134,11 @@ function attachKernelFaceMetadata(geometry, triangleFaceIds, faceIdNames) {
51860
52134
  }
51861
52135
  function shapeToGeometry(shape) {
51862
52136
  if (shape instanceof FrozenShape) {
51863
- const { positions, normals, edgePositions, triangleFaceIds, faceIdNames, hasSmoothNormals } = shape.getPrecomputedGeometry();
52137
+ const { positions, normals, edgePositions, triangleFaceIds, faceIdNames, hasSmoothNormals, uvs } = shape.getPrecomputedGeometry();
51864
52138
  const solid = new BufferGeometry();
51865
52139
  solid.setAttribute("position", new BufferAttribute(positions, 3));
51866
52140
  solid.setAttribute("normal", new BufferAttribute(normals, 3));
52141
+ if (uvs) solid.setAttribute("uv", new BufferAttribute(uvs, 2));
51867
52142
  attachKernelFaceMetadata(solid, triangleFaceIds, faceIdNames);
51868
52143
  const edges = new BufferGeometry();
51869
52144
  edges.setAttribute("position", new BufferAttribute(edgePositions, 3));
@@ -51886,7 +52161,7 @@ function shapeToGeometryFallback(shape) {
51886
52161
  } catch {
51887
52162
  mesh = shape.getMesh();
51888
52163
  }
51889
- const { positions, normals, edgePositions, hasSmoothNormals } = computeGeometryArrays({
52164
+ const { positions, normals, edgePositions, hasSmoothNormals, uvs } = computeGeometryArrays({
51890
52165
  numProp: mesh.numProp,
51891
52166
  numTri: mesh.numTri,
51892
52167
  triVerts: mesh.triVerts,
@@ -51899,6 +52174,7 @@ function shapeToGeometryFallback(shape) {
51899
52174
  const solid = new BufferGeometry();
51900
52175
  solid.setAttribute("position", new BufferAttribute(positions, 3));
51901
52176
  solid.setAttribute("normal", new BufferAttribute(normals, 3));
52177
+ if (uvs) solid.setAttribute("uv", new BufferAttribute(uvs, 2));
51902
52178
  attachKernelFaceMetadata(solid, mesh.faceID, mesh.faceIdNames);
51903
52179
  const edges = new BufferGeometry();
51904
52180
  edges.setAttribute("position", new BufferAttribute(edgePositions, 3));