@toolpath/viewer 2.2.1 → 2.3.1

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.
package/dist/index.js CHANGED
@@ -99,6 +99,7 @@ import {
99
99
  dragPlane,
100
100
  excludedFromFrame,
101
101
  featureTypeRank,
102
+ findSharpCorners,
102
103
  fitDistance,
103
104
  focusForPick,
104
105
  formatDegrees,
@@ -140,10 +141,12 @@ import {
140
141
  sectionOffset,
141
142
  sectionOptionsFromState,
142
143
  sectionPlane,
144
+ sharpCornerPositions,
143
145
  smoothRegionNormals,
144
146
  snapAt,
145
147
  squaredUp,
146
148
  startPosition,
149
+ surfaceCreases,
147
150
  surfaceUnderRay,
148
151
  targetBoundary,
149
152
  themesEqual,
@@ -160,7 +163,7 @@ import {
160
163
  viewUp,
161
164
  viewVector,
162
165
  visualSurfaces
163
- } from "./chunk-7S6SIYHL.js";
166
+ } from "./chunk-N54FHMTG.js";
164
167
 
165
168
  // src/render/feature-triangles.ts
166
169
  var EMPTY = { positions: new Float32Array(), normals: new Float32Array() };
@@ -170,13 +173,13 @@ function featureTriangles(model, geometry, tag) {
170
173
  const normal = geometry.getAttribute("normal");
171
174
  const index = geometry.getIndex();
172
175
  const vertex = (corner) => index ? index.getX(corner) : corner;
173
- const triangleCount = Math.floor((index ? index.count : position.count) / 3);
176
+ const triangleCount2 = Math.floor((index ? index.count : position.count) / 3);
174
177
  const positions = [];
175
178
  const normals = [];
176
179
  for (const region of new Set(model.regionIndex.regionsForFeature(tag))) {
177
180
  const range = model.regionIndex.rangeForRegion(region);
178
181
  if (!range) continue;
179
- for (let triangle = range.start; triangle < Math.min(range.end, triangleCount); triangle++) {
182
+ for (let triangle = range.start; triangle < Math.min(range.end, triangleCount2); triangle++) {
180
183
  const corners = [vertex(triangle * 3), vertex(triangle * 3 + 1), vertex(triangle * 3 + 2)];
181
184
  const at = corners.map((corner) => [
182
185
  position.getX(corner),
@@ -241,7 +244,7 @@ var HoverCard = ({ pick, children, className: className2, offset = 16 }) => {
241
244
  import { createContext, useContext } from "react";
242
245
 
243
246
  // src/toolbar-icons.tsx
244
- import { jsx as jsx2 } from "react/jsx-runtime";
247
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
245
248
  var SvgIcon = ({ viewBox = "0 0 24 24", fill = "none", children }) => /* @__PURE__ */ jsx2(
246
249
  "svg",
247
250
  {
@@ -267,6 +270,10 @@ var BananaIcon = () => /* @__PURE__ */ jsx2(SvgIcon, { viewBox: "0 0 576 512", f
267
270
  var ToolsIcon = () => /* @__PURE__ */ jsx2(SvgIcon, { children: /* @__PURE__ */ jsx2("path", { d: "M9 2h6v7H9zM9.5 9v9.5l2.5 3.5 2.5-3.5V9M9.5 12l5 2.5M9.5 15.5l5 2.5" }) });
268
271
  var DirectionsIcon = () => /* @__PURE__ */ jsx2(SvgIcon, { children: /* @__PURE__ */ jsx2("path", { d: "M7 3v18m-4-4 4 4 4-4M17 21V3m-4 4 4-4 4 4" }) });
269
272
  var DfmIcon = () => /* @__PURE__ */ jsx2(SvgIcon, { children: /* @__PURE__ */ jsx2("path", { d: "M8.3 3h7.4L21 8.3v7.4L15.7 21H8.3L3 15.7V8.3zM12 7.5v5.5m0 3.5v.01" }) });
273
+ var SharpCornersIcon = () => /* @__PURE__ */ jsxs(SvgIcon, { children: [
274
+ /* @__PURE__ */ jsx2("path", { d: "M4 3v17h17" }),
275
+ /* @__PURE__ */ jsx2("circle", { cx: "9", cy: "15", r: "4" })
276
+ ] });
270
277
  var HoverIcon = () => /* @__PURE__ */ jsx2(SvgIcon, { children: /* @__PURE__ */ jsx2("path", { d: "M4 4h16v12H9l-5 4zm4 5h8m-8 3h5" }) });
271
278
  var FocusIcon = () => /* @__PURE__ */ jsx2(SvgIcon, { children: /* @__PURE__ */ jsx2("path", { d: "M4 4h16v16H4zM8 8h8v8H8zM2 12h4m12 0h4M12 2v4m0 12v4" }) });
272
279
  var WireframeIcon = () => /* @__PURE__ */ jsx2(SvgIcon, { children: /* @__PURE__ */ jsx2("path", { d: "M4 7l8-4 8 4v10l-8 4-8-4zM4 7l8 4 8-4M12 11v10M4 17l8-4 8 4M12 3v10" }) });
@@ -283,6 +290,7 @@ var ICONS = {
283
290
  tools: ToolsIcon,
284
291
  directions: DirectionsIcon,
285
292
  dfm: DfmIcon,
293
+ sharpCorners: SharpCornersIcon,
286
294
  hover: HoverIcon,
287
295
  focus: FocusIcon,
288
296
  wireframe: WireframeIcon,
@@ -295,7 +303,7 @@ var ToolbarIcon = ({ name }) => {
295
303
  };
296
304
 
297
305
  // src/viewer-toolbar.tsx
298
- import { jsx as jsx3, jsxs } from "react/jsx-runtime";
306
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
299
307
  var ViewerToolbarContext = createContext(null);
300
308
  var ViewerToolbarProvider = ({
301
309
  controls,
@@ -313,7 +321,7 @@ var ToolbarButton = ({ action, icon, label, toggle = true }) => {
313
321
  throw new Error(`<ViewerToolbar.${buttonName(action)}> requires a '${action}' toolbar control`);
314
322
  }
315
323
  const pressed = control.pressed ?? false;
316
- return /* @__PURE__ */ jsxs(
324
+ return /* @__PURE__ */ jsxs2(
317
325
  "button",
318
326
  {
319
327
  className: "viewer-toolbar-button",
@@ -385,6 +393,14 @@ var DfmButton = () => /* @__PURE__ */ jsx3(
385
393
  label: (pressed) => pressed ? "Hide DFM flags" : "Show DFM flags"
386
394
  }
387
395
  );
396
+ var SharpCornersButton = () => /* @__PURE__ */ jsx3(
397
+ ToolbarButton,
398
+ {
399
+ action: "sharpCorners",
400
+ icon: "sharpCorners",
401
+ label: (pressed) => pressed ? "Hide sharp corners" : "Show sharp corners"
402
+ }
403
+ );
388
404
  var HoverButton = () => /* @__PURE__ */ jsx3(
389
405
  ToolbarButton,
390
406
  {
@@ -444,6 +460,7 @@ var DefaultControls = () => {
444
460
  const analysis = [
445
461
  controls.directions ? /* @__PURE__ */ jsx3(DirectionsButton, {}, "directions") : null,
446
462
  controls.dfm ? /* @__PURE__ */ jsx3(DfmButton, {}, "dfm") : null,
463
+ controls.sharpCorners ? /* @__PURE__ */ jsx3(SharpCornersButton, {}, "sharpCorners") : null,
447
464
  controls.hover ? /* @__PURE__ */ jsx3(HoverButton, {}, "hover") : null,
448
465
  controls.focus ? /* @__PURE__ */ jsx3(FocusButton, {}, "focus") : null,
449
466
  controls.wireframe ? /* @__PURE__ */ jsx3(WireframeButton, {}, "wireframe") : null,
@@ -455,7 +472,7 @@ var DefaultControls = () => {
455
472
  controls.reset ? /* @__PURE__ */ jsx3(ResetButton, {}, "reset") : null,
456
473
  controls.top ? /* @__PURE__ */ jsx3(TopButton, {}, "top") : null
457
474
  ].filter(Boolean);
458
- return /* @__PURE__ */ jsxs(Controls, { children: [
475
+ return /* @__PURE__ */ jsxs2(Controls, { children: [
459
476
  display,
460
477
  display.length && analysis.length ? /* @__PURE__ */ jsx3(Divider, {}) : null,
461
478
  analysis,
@@ -484,6 +501,7 @@ var ViewerToolbar = Object.assign(ViewerToolbarRoot, {
484
501
  ToolsButton,
485
502
  DirectionsButton,
486
503
  DfmButton,
504
+ SharpCornersButton,
487
505
  HoverButton,
488
506
  FocusButton,
489
507
  WireframeButton,
@@ -576,8 +594,9 @@ function createStockMeshBuffer() {
576
594
  import {
577
595
  Box3,
578
596
  BoxGeometry,
597
+ BufferGeometry,
579
598
  CylinderGeometry,
580
- EdgesGeometry,
599
+ Float32BufferAttribute,
581
600
  Group,
582
601
  LineBasicMaterial,
583
602
  LineSegments,
@@ -585,8 +604,134 @@ import {
585
604
  Mesh as Mesh2,
586
605
  MeshLambertMaterial,
587
606
  Quaternion,
588
- Vector3
607
+ Vector3 as Vector32
589
608
  } from "three";
609
+
610
+ // src/render/stock-edges.ts
611
+ import { MathUtils, Triangle, Vector3 } from "three";
612
+ var STOCK_EDGE_ANGLE = 15;
613
+ var SYNC_EDGE_TRIANGLES = 2e4;
614
+ var SLICE_MS = 4;
615
+ var CHECK_EVERY = 256;
616
+ var _v0 = new Vector3();
617
+ var _v1 = new Vector3();
618
+ var _normal = new Vector3();
619
+ var _triangle = new Triangle();
620
+ function* edgeWork(geometry, thresholdAngle) {
621
+ const precision = Math.pow(10, 4);
622
+ const thresholdDot = Math.cos(MathUtils.DEG2RAD * thresholdAngle);
623
+ const indexAttr = geometry.getIndex();
624
+ const positionAttr = geometry.getAttribute("position");
625
+ const indexCount = indexAttr ? indexAttr.count : positionAttr.count;
626
+ const indexArr = [0, 0, 0];
627
+ const corners = [_triangle.a, _triangle.b, _triangle.c];
628
+ const hashes = ["", "", ""];
629
+ const edgeData = /* @__PURE__ */ new Map();
630
+ const vertices = [];
631
+ const key = (v) => `${Math.round(v.x * precision)},${Math.round(v.y * precision)},${Math.round(v.z * precision)}`;
632
+ for (let i = 0, step2 = 0; i < indexCount; i += 3, step2 += 1) {
633
+ if (step2 % CHECK_EVERY === 0 && step2 > 0) yield;
634
+ if (indexAttr) {
635
+ indexArr[0] = indexAttr.getX(i);
636
+ indexArr[1] = indexAttr.getX(i + 1);
637
+ indexArr[2] = indexAttr.getX(i + 2);
638
+ } else {
639
+ indexArr[0] = i;
640
+ indexArr[1] = i + 1;
641
+ indexArr[2] = i + 2;
642
+ }
643
+ _triangle.a.fromBufferAttribute(positionAttr, indexArr[0]);
644
+ _triangle.b.fromBufferAttribute(positionAttr, indexArr[1]);
645
+ _triangle.c.fromBufferAttribute(positionAttr, indexArr[2]);
646
+ _triangle.getNormal(_normal);
647
+ hashes[0] = key(_triangle.a);
648
+ hashes[1] = key(_triangle.b);
649
+ hashes[2] = key(_triangle.c);
650
+ if (hashes[0] === hashes[1] || hashes[1] === hashes[2] || hashes[2] === hashes[0]) continue;
651
+ for (let j = 0; j < 3; j += 1) {
652
+ const jNext = (j + 1) % 3;
653
+ const v0 = corners[j];
654
+ const v1 = corners[jNext];
655
+ const hash = `${hashes[j]}_${hashes[jNext]}`;
656
+ const reverse = edgeData.get(`${hashes[jNext]}_${hashes[j]}`);
657
+ if (reverse) {
658
+ if (_normal.dot(reverse.normal) <= thresholdDot) {
659
+ vertices.push(v0.x, v0.y, v0.z, v1.x, v1.y, v1.z);
660
+ }
661
+ edgeData.set(`${hashes[jNext]}_${hashes[j]}`, null);
662
+ } else if (!edgeData.has(hash)) {
663
+ edgeData.set(hash, {
664
+ index0: indexArr[j],
665
+ index1: indexArr[jNext],
666
+ normal: _normal.clone()
667
+ });
668
+ }
669
+ }
670
+ }
671
+ let step = 0;
672
+ for (const edge of edgeData.values()) {
673
+ step += 1;
674
+ if (step % CHECK_EVERY === 0) yield;
675
+ if (!edge) continue;
676
+ _v0.fromBufferAttribute(positionAttr, edge.index0);
677
+ _v1.fromBufferAttribute(positionAttr, edge.index1);
678
+ vertices.push(_v0.x, _v0.y, _v0.z, _v1.x, _v1.y, _v1.z);
679
+ }
680
+ return new Float32Array(vertices);
681
+ }
682
+ function stockEdgePositions(geometry, thresholdAngle = STOCK_EDGE_ANGLE) {
683
+ const work = edgeWork(geometry, thresholdAngle);
684
+ for (; ; ) {
685
+ const next = work.next();
686
+ if (next.done) return next.value;
687
+ }
688
+ }
689
+ function triangleCount(geometry) {
690
+ const index = geometry.getIndex();
691
+ return Math.floor((index ? index.count : geometry.getAttribute("position")?.count ?? 0) / 3);
692
+ }
693
+ var nextTurn = (run) => {
694
+ if (typeof MessageChannel === "undefined") {
695
+ const timer = setTimeout(run, 0);
696
+ return () => clearTimeout(timer);
697
+ }
698
+ const channel = new MessageChannel();
699
+ let live = true;
700
+ channel.port1.onmessage = () => {
701
+ channel.port1.close();
702
+ if (live) run();
703
+ };
704
+ channel.port2.postMessage(null);
705
+ return () => {
706
+ live = false;
707
+ channel.port1.close();
708
+ };
709
+ };
710
+ function buildStockEdges(geometry, onDone, thresholdAngle = STOCK_EDGE_ANGLE) {
711
+ const work = edgeWork(geometry, thresholdAngle);
712
+ let cancelTurn = () => {
713
+ };
714
+ let cancelled = false;
715
+ const slice = () => {
716
+ const until = performance.now() + SLICE_MS;
717
+ for (; ; ) {
718
+ const next = work.next();
719
+ if (next.done) {
720
+ onDone(next.value);
721
+ return;
722
+ }
723
+ if (performance.now() >= until) break;
724
+ }
725
+ if (!cancelled) cancelTurn = nextTurn(slice);
726
+ };
727
+ cancelTurn = nextTurn(slice);
728
+ return () => {
729
+ cancelled = true;
730
+ cancelTurn();
731
+ };
732
+ }
733
+
734
+ // src/render/stock.ts
590
735
  function boxFigure(bounds) {
591
736
  return {
592
737
  lower: { x: bounds.min.x, y: bounds.min.y, z: bounds.min.z },
@@ -594,7 +739,7 @@ function boxFigure(bounds) {
594
739
  };
595
740
  }
596
741
  var CYLINDER_SEGMENTS = 64;
597
- var CYLINDER_AXIS = new Vector3(0, 1, 0);
742
+ var CYLINDER_AXIS = new Vector32(0, 1, 0);
598
743
  function fixedBoxStockBounds(geometry, dimensions, position = "model_centered", positionOffset = 0, offset = { x: 0, y: 0, z: 0 }) {
599
744
  if (![dimensions.x, dimensions.y, dimensions.z].every(Number.isFinite) || [dimensions.x, dimensions.y, dimensions.z].some((value) => value <= 0)) {
600
745
  throw new RangeError("Fixed stock dimensions must be finite and positive.");
@@ -606,12 +751,12 @@ function fixedBoxStockBounds(geometry, dimensions, position = "model_centered",
606
751
  throw new RangeError("Fixed stock offset must be finite.");
607
752
  }
608
753
  const part = partBounds(geometry);
609
- const center = part.getCenter(new Vector3());
754
+ const center = part.getCenter(new Vector32());
610
755
  const z = fixedStockLowerZ(part, dimensions.z, position, positionOffset);
611
756
  return new Box3(
612
- new Vector3(center.x - dimensions.x / 2, center.y - dimensions.y / 2, z),
613
- new Vector3(center.x + dimensions.x / 2, center.y + dimensions.y / 2, z + dimensions.z)
614
- ).translate(new Vector3(offset.x, offset.y, offset.z));
757
+ new Vector32(center.x - dimensions.x / 2, center.y - dimensions.y / 2, z),
758
+ new Vector32(center.x + dimensions.x / 2, center.y + dimensions.y / 2, z + dimensions.z)
759
+ ).translate(new Vector32(offset.x, offset.y, offset.z));
615
760
  }
616
761
  function fixedCylinderStock(geometry, size, position = "model_centered", positionOffset = 0) {
617
762
  if (![size.diameter, size.length].every((value) => Number.isFinite(value) && value > 0)) {
@@ -621,7 +766,7 @@ function fixedCylinderStock(geometry, size, position = "model_centered", positio
621
766
  throw new RangeError("Fixed stock position offset must be finite.");
622
767
  }
623
768
  const part = partBounds(geometry);
624
- const center = part.getCenter(new Vector3());
769
+ const center = part.getCenter(new Vector32());
625
770
  return {
626
771
  origin: {
627
772
  x: center.x,
@@ -647,13 +792,13 @@ function cylinderStockGeometry(input) {
647
792
  if (![diameter, length].every((value) => Number.isFinite(value) && value > 0)) {
648
793
  throw new RangeError("Cylinder stock diameter and length must be finite and positive.");
649
794
  }
650
- const axis = new Vector3(figure.axis.x, figure.axis.y, figure.axis.z);
795
+ const axis = new Vector32(figure.axis.x, figure.axis.y, figure.axis.z);
651
796
  const norm = axis.length();
652
797
  if (!Number.isFinite(norm) || norm === 0) {
653
798
  throw new RangeError("Cylinder stock axis must be finite and non-zero.");
654
799
  }
655
800
  axis.divideScalar(norm);
656
- const center = axis.clone().multiplyScalar(length / 2).add(new Vector3(origin.x, origin.y, origin.z));
801
+ const center = axis.clone().multiplyScalar(length / 2).add(new Vector32(origin.x, origin.y, origin.z));
657
802
  return new CylinderGeometry(diameter / 2, diameter / 2, length, CYLINDER_SEGMENTS).applyQuaternion(new Quaternion().setFromUnitVectors(CYLINDER_AXIS, axis)).translate(center.x, center.y, center.z);
658
803
  }
659
804
  function boxStockGeometry(input) {
@@ -681,12 +826,12 @@ function orientedBoxStockGeometry(figure) {
681
826
  )) {
682
827
  throw new RangeError("Box stock frame and corners must be finite.");
683
828
  }
684
- const size = new Vector3(upper.x - lower.x, upper.y - lower.y, upper.z - lower.z);
829
+ const size = new Vector32(upper.x - lower.x, upper.y - lower.y, upper.z - lower.z);
685
830
  if (size.toArray().some((value) => !(value > 0))) {
686
831
  throw new RangeError("Box stock upper corner must be above its lower corner on every axis.");
687
832
  }
688
- const z = new Vector3(axis.x, axis.y, axis.z).normalize();
689
- const x = new Vector3(refDirection.x, refDirection.y, refDirection.z);
833
+ const z = new Vector32(axis.x, axis.y, axis.z).normalize();
834
+ const x = new Vector32(refDirection.x, refDirection.y, refDirection.z);
690
835
  const reference = x.length();
691
836
  x.addScaledVector(z, -x.dot(z));
692
837
  if (!(z.lengthSq() > 0 && x.length() > 1e-9 * reference)) {
@@ -695,11 +840,11 @@ function orientedBoxStockGeometry(figure) {
695
840
  );
696
841
  }
697
842
  x.normalize();
698
- const y = new Vector3().crossVectors(z, x);
843
+ const y = new Vector32().crossVectors(z, x);
699
844
  return new BoxGeometry(size.x, size.y, size.z).translate((lower.x + upper.x) / 2, (lower.y + upper.y) / 2, (lower.z + upper.z) / 2).applyMatrix4(new Matrix4().makeBasis(x, y, z).setPosition(location.x, location.y, location.z));
700
845
  }
701
846
  function boxStockBounds(geometry, allowance = 0, offset = { x: 0, y: 0, z: 0 }) {
702
- const padding = typeof allowance === "number" ? new Vector3(allowance, allowance, allowance) : "wall" in allowance ? new Vector3(allowance.wall, allowance.wall, allowance.floor) : new Vector3(allowance.x, allowance.y, allowance.z);
847
+ const padding = typeof allowance === "number" ? new Vector32(allowance, allowance, allowance) : "wall" in allowance ? new Vector32(allowance.wall, allowance.wall, allowance.floor) : new Vector32(allowance.x, allowance.y, allowance.z);
703
848
  if (padding.toArray().some((value) => !Number.isFinite(value) || value < 0)) {
704
849
  throw new RangeError("Stock allowance must be finite and non-negative.");
705
850
  }
@@ -707,8 +852,8 @@ function boxStockBounds(geometry, allowance = 0, offset = { x: 0, y: 0, z: 0 })
707
852
  throw new RangeError("Stock offset must be finite.");
708
853
  }
709
854
  const box = partBounds(geometry);
710
- box.expandByVector(padding).translate(new Vector3(offset.x, offset.y, offset.z));
711
- const size = box.getSize(new Vector3());
855
+ box.expandByVector(padding).translate(new Vector32(offset.x, offset.y, offset.z));
856
+ const size = box.getSize(new Vector32());
712
857
  if (size.toArray().some((value) => !Number.isFinite(value) || value <= 0)) {
713
858
  throw new RangeError("Stock dimensions must be finite and positive.");
714
859
  }
@@ -719,7 +864,7 @@ function partBounds(geometry) {
719
864
  if (!position || position.count === 0)
720
865
  throw new RangeError("Stock needs non-empty part geometry.");
721
866
  const box = new Box3();
722
- const point = new Vector3();
867
+ const point = new Vector32();
723
868
  for (let index = 0; index < position.count; index += 1) {
724
869
  point.fromBufferAttribute(position, index);
725
870
  if (!Number.isFinite(point.x) || !Number.isFinite(point.y) || !Number.isFinite(point.z)) {
@@ -729,7 +874,7 @@ function partBounds(geometry) {
729
874
  }
730
875
  return box;
731
876
  }
732
- function createStock(geometry, legacyWorkpiece = false) {
877
+ function createStock(geometry, legacyWorkpiece = false, onEdges) {
733
878
  const material = new MeshLambertMaterial({ transparent: true, depthWrite: false });
734
879
  const edgeMaterial = new LineBasicMaterial({ transparent: true, depthWrite: false });
735
880
  const mesh = new Mesh2(geometry, material);
@@ -747,6 +892,18 @@ function createStock(geometry, legacyWorkpiece = false) {
747
892
  edgeMaterial.depthWrite = true;
748
893
  }
749
894
  let edges = null;
895
+ let building = null;
896
+ let shown = false;
897
+ const attach = (positions) => {
898
+ const outline = new BufferGeometry();
899
+ outline.setAttribute("position", new Float32BufferAttribute(positions, 3));
900
+ edges = new LineSegments(outline, edgeMaterial);
901
+ edges.renderOrder = legacyWorkpiece ? 1 : 6;
902
+ edges.raycast = () => {
903
+ };
904
+ edges.visible = shown;
905
+ object.add(edges);
906
+ };
750
907
  return {
751
908
  object,
752
909
  material,
@@ -754,19 +911,28 @@ function createStock(geometry, legacyWorkpiece = false) {
754
911
  /**
755
912
  * Shows or hides the outline, building it the first time it is shown: the
756
913
  * edge pass reads every triangle, and stock cut by a toolpath has a great
757
- * many of them.
914
+ * many of them. A small mesh's outline is built at once; a large one's in
915
+ * slices between frames, so the stock shows first and its outline joins it
916
+ * a moment later, the same lines `EdgesGeometry` would have drawn.
758
917
  */
759
918
  showEdges(show) {
760
- if (show && !edges) {
761
- edges = new LineSegments(new EdgesGeometry(geometry, 15), edgeMaterial);
762
- edges.renderOrder = legacyWorkpiece ? 1 : 6;
763
- edges.raycast = () => {
764
- };
765
- object.add(edges);
919
+ shown = show;
920
+ if (show && !edges && !building) {
921
+ if (triangleCount(geometry) <= SYNC_EDGE_TRIANGLES) {
922
+ attach(stockEdgePositions(geometry));
923
+ } else {
924
+ building = buildStockEdges(geometry, (positions) => {
925
+ building = null;
926
+ attach(positions);
927
+ onEdges?.();
928
+ });
929
+ }
766
930
  }
767
931
  if (edges) edges.visible = show;
768
932
  },
769
933
  dispose() {
934
+ building?.();
935
+ building = null;
770
936
  material.dispose();
771
937
  edgeMaterial.dispose();
772
938
  edges?.geometry.dispose();
@@ -790,8 +956,8 @@ var GeometryStock = ({
790
956
  }) => {
791
957
  const invalidate = useThree((state) => state.invalidate);
792
958
  const stock = useMemo(
793
- () => createStock(geometry, renderStyle === "legacy-workpiece"),
794
- [geometry, renderStyle]
959
+ () => createStock(geometry, renderStyle === "legacy-workpiece", invalidate),
960
+ [geometry, renderStyle, invalidate]
795
961
  );
796
962
  useEffect2(() => () => stock.dispose(), [stock]);
797
963
  useLayoutEffect(() => {
@@ -898,13 +1064,13 @@ import { useThree as useThree2 } from "@react-three/fiber";
898
1064
  import { useLayoutEffect as useLayoutEffect2, useMemo as useMemo2, useRef } from "react";
899
1065
 
900
1066
  // src/render/banana.ts
901
- import { Vector3 as Vector32 } from "three";
1067
+ import { Vector3 as Vector33 } from "three";
902
1068
  var BANANA_GAP = 0.1;
903
1069
  function bananaPosition(part, banana) {
904
- const size = part.getSize(new Vector32());
1070
+ const size = part.getSize(new Vector33());
905
1071
  const reach = Math.max(size.x, size.y, size.z);
906
- const centre = part.getCenter(new Vector32());
907
- return new Vector32(
1072
+ const centre = part.getCenter(new Vector33());
1073
+ return new Vector33(
908
1074
  part.max.x + reach * BANANA_GAP - banana.min.x,
909
1075
  centre.y - (banana.min.y + banana.max.y) / 2,
910
1076
  part.min.z - banana.min.z
@@ -974,13 +1140,13 @@ import { useThree as useThree3 } from "@react-three/fiber";
974
1140
  import { useEffect as useEffect3, useMemo as useMemo3, useRef as useRef2, useState as useState3 } from "react";
975
1141
 
976
1142
  // src/render/grid.ts
977
- import { BufferGeometry, Float32BufferAttribute, Vector3 as Vector33 } from "three";
1143
+ import { BufferGeometry as BufferGeometry2, Float32BufferAttribute as Float32BufferAttribute2, Vector3 as Vector34 } from "three";
978
1144
  var STEPS = [0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1e3];
979
1145
  var TARGET_CELLS = 10;
980
1146
  var OVERHANG = 1.6;
981
1147
  function gridSpec(box) {
982
- const size = box.getSize(new Vector33());
983
- const center = box.getCenter(new Vector33());
1148
+ const size = box.getSize(new Vector34());
1149
+ const center = box.getCenter(new Vector34());
984
1150
  const largest = Math.max(size.x, size.y, 1e-6);
985
1151
  const ideal = largest / TARGET_CELLS;
986
1152
  const step = [...STEPS].reverse().find((candidate) => candidate <= ideal) ?? STEPS[0];
@@ -1006,8 +1172,8 @@ function gridGeometry(spec) {
1006
1172
  z
1007
1173
  );
1008
1174
  }
1009
- const geometry = new BufferGeometry();
1010
- geometry.setAttribute("position", new Float32BufferAttribute(positions, 3));
1175
+ const geometry = new BufferGeometry2();
1176
+ geometry.setAttribute("position", new Float32BufferAttribute2(positions, 3));
1011
1177
  return geometry;
1012
1178
  }
1013
1179
  function gridFor(box, options = {}) {
@@ -1024,7 +1190,7 @@ function gridFor(box, options = {}) {
1024
1190
  }
1025
1191
 
1026
1192
  // src/primitives.tsx
1027
- import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
1193
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
1028
1194
  var FURNITURE2 = { [EXCLUDE_FROM_FRAME]: true };
1029
1195
  var Grid = ({ step, extent, color, opacity = 0.35 }) => {
1030
1196
  const theme = resolveTheme();
@@ -1086,7 +1252,7 @@ var CubePanels = ({
1086
1252
  setHovered(name);
1087
1253
  invalidate();
1088
1254
  };
1089
- return /* @__PURE__ */ jsxs2("group", { ref: groupRef, scale: 38, children: [
1255
+ return /* @__PURE__ */ jsxs3("group", { ref: groupRef, scale: 38, children: [
1090
1256
  panels.map(({ zone, geometry }) => /* @__PURE__ */ jsx6(
1091
1257
  "mesh",
1092
1258
  {
@@ -1122,17 +1288,17 @@ var CubePanels = ({
1122
1288
  // src/direction-arrows.tsx
1123
1289
  import { useThree as useThree4 } from "@react-three/fiber";
1124
1290
  import { useEffect as useEffect4, useMemo as useMemo4 } from "react";
1125
- import { Quaternion as Quaternion2, Vector3 as Vector35 } from "three";
1291
+ import { Quaternion as Quaternion2, Vector3 as Vector36 } from "three";
1126
1292
 
1127
1293
  // src/render/directions.ts
1128
- import { Vector3 as Vector34 } from "three";
1294
+ import { Vector3 as Vector35 } from "three";
1129
1295
  var LENGTH = 0.33;
1130
1296
  var GAP = 0.2;
1131
1297
  function arrowPlacement(direction, box) {
1132
- const center = box.getCenter(new Vector34());
1133
- const half = box.getSize(new Vector34()).multiplyScalar(0.5);
1298
+ const center = box.getCenter(new Vector35());
1299
+ const half = box.getSize(new Vector35()).multiplyScalar(0.5);
1134
1300
  const radius = half.length() || 1;
1135
- const axis = new Vector34(direction.x, direction.y, direction.z);
1301
+ const axis = new Vector35(direction.x, direction.y, direction.z);
1136
1302
  if (axis.lengthSq() === 0) axis.set(0, 0, 1);
1137
1303
  axis.normalize();
1138
1304
  let exit = Number.POSITIVE_INFINITY;
@@ -1148,7 +1314,7 @@ function arrowPlacement(direction, box) {
1148
1314
  }
1149
1315
 
1150
1316
  // src/direction-arrows.tsx
1151
- import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
1317
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
1152
1318
  var FURNITURE3 = { [EXCLUDE_FROM_FRAME]: true };
1153
1319
  var DirectionArrows = ({
1154
1320
  directions,
@@ -1175,7 +1341,7 @@ var DirectionArrows = ({
1175
1341
  // Excluded from framing: the arrows are deliberately placed outside the
1176
1342
  // part, so a Fit that measured them would frame the arrows and leave the
1177
1343
  // part small in the middle of them.
1178
- /* @__PURE__ */ jsxs3("group", { userData: FURNITURE3, children: [
1344
+ /* @__PURE__ */ jsxs4("group", { userData: FURNITURE3, children: [
1179
1345
  directions.map((direction, index) => {
1180
1346
  if (!drawn(index)) return null;
1181
1347
  return /* @__PURE__ */ jsx7(
@@ -1223,7 +1389,7 @@ var Arrow = ({ direction, box, geometry, color, opacity, onPick, onTop = false }
1223
1389
  const placement = arrowPlacement(direction, box);
1224
1390
  const aim = new Quaternion2().setFromUnitVectors(
1225
1391
  ARROW_AXIS,
1226
- new Vector35(direction.x, direction.y, direction.z).normalize()
1392
+ new Vector36(direction.x, direction.y, direction.z).normalize()
1227
1393
  );
1228
1394
  return { tip: placement.tip, length: placement.length, quaternion: aim };
1229
1395
  }, [box, direction]);
@@ -1241,16 +1407,16 @@ var Arrow = ({ direction, box, geometry, color, opacity, onPick, onTop = false }
1241
1407
  import { useFrame, useThree as useThree5 } from "@react-three/fiber";
1242
1408
  import { useEffect as useEffect5, useMemo as useMemo5, useRef as useRef3, useState as useState4, useSyncExternalStore } from "react";
1243
1409
  import {
1244
- BufferGeometry as BufferGeometry2,
1410
+ BufferGeometry as BufferGeometry3,
1245
1411
  DoubleSide,
1246
1412
  Quaternion as Quaternion3,
1247
1413
  Raycaster,
1248
1414
  Vector2,
1249
- Vector3 as Vector36
1415
+ Vector3 as Vector37
1250
1416
  } from "three";
1251
- import { Fragment, jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
1417
+ import { Fragment, jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1252
1418
  var FURNITURE4 = { [EXCLUDE_FROM_FRAME]: true };
1253
- var Z = new Vector36(0, 0, 1);
1419
+ var Z = new Vector37(0, 0, 1);
1254
1420
  var AXIS_ROTATIONS = {
1255
1421
  x: [0, Math.PI / 2, 0],
1256
1422
  y: [Math.PI / 2, 0, 0],
@@ -1280,11 +1446,11 @@ var SectionTool = ({ theme }) => {
1280
1446
  };
1281
1447
  var useSquare = () => {
1282
1448
  const square = useMemo5(
1283
- () => new BufferGeometry2().setFromPoints([
1284
- new Vector36(-1, 1, 0),
1285
- new Vector36(1, 1, 0),
1286
- new Vector36(1, -1, 0),
1287
- new Vector36(-1, -1, 0)
1449
+ () => new BufferGeometry3().setFromPoints([
1450
+ new Vector37(-1, 1, 0),
1451
+ new Vector37(1, 1, 0),
1452
+ new Vector37(1, -1, 0),
1453
+ new Vector37(-1, -1, 0)
1288
1454
  ]),
1289
1455
  []
1290
1456
  );
@@ -1302,8 +1468,8 @@ var Picker = ({ box, theme }) => {
1302
1468
  const previewRef = useRef3(null);
1303
1469
  const hover = useRef3(null);
1304
1470
  const [hoveredPlane, setHoveredPlane] = useState4(null);
1305
- const span = useMemo5(() => box.getSize(new Vector36()).length(), [box]);
1306
- const centre = useMemo5(() => box.getCenter(new Vector36()), [box]);
1471
+ const span = useMemo5(() => box.getSize(new Vector37()).length(), [box]);
1472
+ const centre = useMemo5(() => box.getCenter(new Vector37()), [box]);
1307
1473
  const previewSize = span * PREVIEW_SCALE;
1308
1474
  useEffect5(() => {
1309
1475
  store.setPicking(true);
@@ -1312,7 +1478,7 @@ var Picker = ({ box, theme }) => {
1312
1478
  useEffect5(() => {
1313
1479
  const raycaster = new Raycaster();
1314
1480
  const pointer = new Vector2();
1315
- const aim = new Vector36();
1481
+ const aim = new Vector37();
1316
1482
  const hide = () => {
1317
1483
  if (!hover.current) return;
1318
1484
  hover.current = null;
@@ -1383,9 +1549,9 @@ var Picker = ({ box, theme }) => {
1383
1549
  store.set(plane.options);
1384
1550
  }
1385
1551
  });
1386
- return /* @__PURE__ */ jsxs4(Fragment, { children: [
1387
- /* @__PURE__ */ jsxs4("group", { ref: previewRef, visible: false, children: [
1388
- /* @__PURE__ */ jsxs4("mesh", { renderOrder: SECTION_RENDER_ORDER.outline, raycast: () => null, children: [
1552
+ return /* @__PURE__ */ jsxs5(Fragment, { children: [
1553
+ /* @__PURE__ */ jsxs5("group", { ref: previewRef, visible: false, children: [
1554
+ /* @__PURE__ */ jsxs5("mesh", { renderOrder: SECTION_RENDER_ORDER.outline, raycast: () => null, children: [
1389
1555
  /* @__PURE__ */ jsx8("planeGeometry", { args: [previewSize, previewSize] }),
1390
1556
  /* @__PURE__ */ jsx8(
1391
1557
  "meshBasicMaterial",
@@ -1418,13 +1584,13 @@ var Picker = ({ box, theme }) => {
1418
1584
  }
1419
1585
  )
1420
1586
  ] }),
1421
- planes.map((plane) => /* @__PURE__ */ jsxs4(
1587
+ planes.map((plane) => /* @__PURE__ */ jsxs5(
1422
1588
  "group",
1423
1589
  {
1424
1590
  position: [plane.position.x, plane.position.y, plane.position.z],
1425
1591
  rotation: AXIS_ROTATIONS[plane.axis],
1426
1592
  children: [
1427
- /* @__PURE__ */ jsxs4("mesh", { renderOrder: SECTION_RENDER_ORDER.outline, ...planeHandlers(plane), children: [
1593
+ /* @__PURE__ */ jsxs5("mesh", { renderOrder: SECTION_RENDER_ORDER.outline, ...planeHandlers(plane), children: [
1428
1594
  /* @__PURE__ */ jsx8("planeGeometry", { args: [plane.size, plane.size] }),
1429
1595
  /* @__PURE__ */ jsx8(
1430
1596
  "meshBasicMaterial",
@@ -1456,7 +1622,7 @@ var Picker = ({ box, theme }) => {
1456
1622
  var CutSheet = ({ box, section, theme }) => {
1457
1623
  const square = useSquare();
1458
1624
  const cut = useMemo5(() => resolveSectionPlane(section, box), [box, section]);
1459
- const span = useMemo5(() => box.getSize(new Vector36()).length(), [box]);
1625
+ const span = useMemo5(() => box.getSize(new Vector37()).length(), [box]);
1460
1626
  const placement = useMemo5(() => {
1461
1627
  if (!cut) return null;
1462
1628
  const position = sectionAnchor(box, cut.plane).addScaledVector(
@@ -1467,8 +1633,8 @@ var CutSheet = ({ box, section, theme }) => {
1467
1633
  }, [box, cut, span]);
1468
1634
  if (!placement) return null;
1469
1635
  const size = span * OUTLINE_SCALE;
1470
- return /* @__PURE__ */ jsxs4("group", { position: placement.position, quaternion: placement.quaternion, children: [
1471
- /* @__PURE__ */ jsxs4("mesh", { renderOrder: SECTION_RENDER_ORDER.outline, raycast: () => null, children: [
1636
+ return /* @__PURE__ */ jsxs5("group", { position: placement.position, quaternion: placement.quaternion, children: [
1637
+ /* @__PURE__ */ jsxs5("mesh", { renderOrder: SECTION_RENDER_ORDER.outline, raycast: () => null, children: [
1472
1638
  /* @__PURE__ */ jsx8("planeGeometry", { args: [size, size] }),
1473
1639
  /* @__PURE__ */ jsx8(
1474
1640
  "meshBasicMaterial",
@@ -1519,18 +1685,18 @@ import {
1519
1685
  Raycaster as Raycaster2,
1520
1686
  SphereGeometry,
1521
1687
  Vector2 as Vector22,
1522
- Vector3 as Vector38
1688
+ Vector3 as Vector39
1523
1689
  } from "three";
1524
1690
 
1525
1691
  // src/render/resample.ts
1526
- import { Vector3 as Vector37 } from "three";
1692
+ import { Vector3 as Vector38 } from "three";
1527
1693
  var ON_PLANE = 1e-7;
1528
1694
  var STITCH = 1e-5;
1529
1695
  var COLLINEAR = 1e-6;
1530
1696
  function clipSegments(positions, plane) {
1531
1697
  const out = [];
1532
- const a = new Vector37();
1533
- const b = new Vector37();
1698
+ const a = new Vector38();
1699
+ const b = new Vector38();
1534
1700
  const count = Math.floor(positions.length / 6);
1535
1701
  for (let i = 0; i < count; i += 1) {
1536
1702
  a.set(positions[i * 6], positions[i * 6 + 1], positions[i * 6 + 2]);
@@ -1545,14 +1711,14 @@ function clipSegments(positions, plane) {
1545
1711
  return Float32Array.from(out);
1546
1712
  }
1547
1713
  function sectionContour(positions, regionOf, plane) {
1548
- const a = new Vector37();
1549
- const b = new Vector37();
1550
- const c = new Vector37();
1551
- const p = new Vector37();
1552
- const q = new Vector37();
1714
+ const a = new Vector38();
1715
+ const b = new Vector38();
1716
+ const c = new Vector38();
1717
+ const p = new Vector38();
1718
+ const q = new Vector38();
1553
1719
  const byRegion = /* @__PURE__ */ new Map();
1554
- const triangleCount = Math.floor(positions.length / 9);
1555
- for (let t = 0; t < triangleCount; t += 1) {
1720
+ const triangleCount2 = Math.floor(positions.length / 9);
1721
+ for (let t = 0; t < triangleCount2; t += 1) {
1556
1722
  a.set(positions[t * 9], positions[t * 9 + 1], positions[t * 9 + 2]);
1557
1723
  b.set(positions[t * 9 + 3], positions[t * 9 + 4], positions[t * 9 + 5]);
1558
1724
  c.set(positions[t * 9 + 6], positions[t * 9 + 7], positions[t * 9 + 8]);
@@ -1607,17 +1773,17 @@ function stitch(segments, out) {
1607
1773
  return into.set(segments[o], segments[o + 1], segments[o + 2]);
1608
1774
  };
1609
1775
  const direction = (i, into) => {
1610
- const s = new Vector37();
1611
- const e = new Vector37();
1776
+ const s = new Vector38();
1777
+ const e = new Vector38();
1612
1778
  point(i, 0, s);
1613
1779
  point(i, 1, e);
1614
1780
  return into.subVectors(e, s).normalize();
1615
1781
  };
1616
1782
  const consumed = new Uint8Array(count);
1617
- const d = new Vector37();
1618
- const n = new Vector37();
1783
+ const d = new Vector38();
1784
+ const n = new Vector38();
1619
1785
  const next = (i, end) => {
1620
- const at = point(i, end, new Vector37());
1786
+ const at = point(i, end, new Vector38());
1621
1787
  const list = ends.get(key(at.x, at.y, at.z)) ?? [];
1622
1788
  if (list.length !== 2) return null;
1623
1789
  const other = list.find((entry) => entry >> 1 !== i);
@@ -1646,8 +1812,8 @@ function stitch(segments, out) {
1646
1812
  };
1647
1813
  const back = walk(0);
1648
1814
  const forward = walk(1);
1649
- const s = point(back.index, back.end, new Vector37());
1650
- const e = point(forward.index, forward.end, new Vector37());
1815
+ const s = point(back.index, back.end, new Vector38());
1816
+ const e = point(forward.index, forward.end, new Vector38());
1651
1817
  out.push(s.x, s.y, s.z, e.x, e.y, e.z);
1652
1818
  }
1653
1819
  }
@@ -1655,8 +1821,8 @@ function pointInContour(point, plane, contour) {
1655
1821
  const [u, v] = planeAxes(plane);
1656
1822
  const px = point.dot(u);
1657
1823
  const py = point.dot(v);
1658
- const a = new Vector37();
1659
- const b = new Vector37();
1824
+ const a = new Vector38();
1825
+ const b = new Vector38();
1660
1826
  let inside = false;
1661
1827
  const count = Math.floor(contour.length / 6);
1662
1828
  for (let i = 0; i < count; i += 1) {
@@ -1673,15 +1839,15 @@ function pointInContour(point, plane, contour) {
1673
1839
  return inside;
1674
1840
  }
1675
1841
  function capHit(ray, plane, contour) {
1676
- const point = ray.intersectPlane(plane, new Vector37());
1842
+ const point = ray.intersectPlane(plane, new Vector38());
1677
1843
  if (!point) return null;
1678
1844
  return pointInContour(point, plane, contour) ? point : null;
1679
1845
  }
1680
1846
  function planeAxes(plane) {
1681
1847
  const n = plane.normal;
1682
- const seed = Math.abs(n.x) < 0.9 ? new Vector37(1, 0, 0) : new Vector37(0, 1, 0);
1683
- const u = new Vector37().crossVectors(n, seed).normalize();
1684
- const v = new Vector37().crossVectors(n, u).normalize();
1848
+ const seed = Math.abs(n.x) < 0.9 ? new Vector38(1, 0, 0) : new Vector38(0, 1, 0);
1849
+ const u = new Vector38().crossVectors(n, seed).normalize();
1850
+ const v = new Vector38().crossVectors(n, u).normalize();
1685
1851
  return [u, v];
1686
1852
  }
1687
1853
  function sampleSection(meshPositions, regionOf, edgePositions, plane) {
@@ -1694,7 +1860,7 @@ function sampleSection(meshPositions, regionOf, edgePositions, plane) {
1694
1860
  }
1695
1861
 
1696
1862
  // src/measure-tool.tsx
1697
- import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
1863
+ import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1698
1864
  var FURNITURE5 = { [EXCLUDE_FROM_FRAME]: true };
1699
1865
  var MEASURE_LABEL_CLASS = "toolpath-measure-label";
1700
1866
  var MeasureTool = ({
@@ -1755,7 +1921,7 @@ var MeasureTool = ({
1755
1921
  };
1756
1922
  if (box.isEmpty()) return null;
1757
1923
  const label = className(labelClassName);
1758
- return /* @__PURE__ */ jsxs5("group", { userData: FURNITURE5, children: [
1924
+ return /* @__PURE__ */ jsxs6("group", { userData: FURNITURE5, children: [
1759
1925
  picking ? null : /* @__PURE__ */ jsx9(
1760
1926
  Snapper,
1761
1927
  {
@@ -1791,7 +1957,7 @@ var MeasureTool = ({
1791
1957
  ] });
1792
1958
  };
1793
1959
  var NO_POINTER = { pointerEvents: "none" };
1794
- var NOWHERE = [new Vector38(), new Vector38()];
1960
+ var NOWHERE = [new Vector39(), new Vector39()];
1795
1961
  var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
1796
1962
  const camera = useThree6((state) => state.camera);
1797
1963
  const scene = useThree6((state) => state.scene);
@@ -1844,7 +2010,7 @@ var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
1844
2010
  const snapped = snapAt(target, target.edges, camera, viewport);
1845
2011
  const from = placed[placed.length - 1];
1846
2012
  const lock = shift && from ? lockToAxis(from, snapped.point) : null;
1847
- const found = lock ? { ...snapped, point: new Vector38(lock.point.x, lock.point.y, lock.point.z), edge: null } : snapped;
2013
+ const found = lock ? { ...snapped, point: new Vector39(lock.point.x, lock.point.y, lock.point.z), edge: null } : snapped;
1848
2014
  snap.current = found;
1849
2015
  const node = marker.current;
1850
2016
  if (node) {
@@ -1929,7 +2095,7 @@ var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
1929
2095
  invalidate();
1930
2096
  }
1931
2097
  }, [draft, invalidate]);
1932
- return /* @__PURE__ */ jsxs5(Fragment2, { children: [
2098
+ return /* @__PURE__ */ jsxs6(Fragment2, { children: [
1933
2099
  /* @__PURE__ */ jsx9("group", { ref: marker, visible: false, children: /* @__PURE__ */ jsx9("mesh", { geometry: dot, renderOrder: MEASURE_RENDER_ORDER, raycast: () => null, children: /* @__PURE__ */ jsx9("meshBasicMaterial", { color: theme.measureSnap, depthTest: false, depthWrite: false }) }) }),
1934
2100
  /* @__PURE__ */ jsx9(
1935
2101
  Line,
@@ -2124,7 +2290,7 @@ var MeasurementView = ({
2124
2290
  const anchor = measurementLabelAnchor(measurement);
2125
2291
  if (measurement.kind === "distance") {
2126
2292
  const [a2, b2] = measurement.points;
2127
- return /* @__PURE__ */ jsxs5(Fragment2, { children: [
2293
+ return /* @__PURE__ */ jsxs6(Fragment2, { children: [
2128
2294
  /* @__PURE__ */ jsx9(Segment, { points: [a2, b2], color: theme.measure }),
2129
2295
  /* @__PURE__ */ jsx9(ScreenDot, { point: a2, pixels: MEASURE_MARKER_PIXELS, color: theme.measure }),
2130
2296
  /* @__PURE__ */ jsx9(ScreenDot, { point: b2, pixels: MEASURE_MARKER_PIXELS, color: theme.measure }),
@@ -2134,7 +2300,7 @@ var MeasurementView = ({
2134
2300
  }
2135
2301
  const [a, vertex, b] = measurement.points;
2136
2302
  const arc = angleArc(vertex, a, b, angleArcRadius(vertex, a, b));
2137
- return /* @__PURE__ */ jsxs5(Fragment2, { children: [
2303
+ return /* @__PURE__ */ jsxs6(Fragment2, { children: [
2138
2304
  /* @__PURE__ */ jsx9(Segment, { points: [vertex, a], color: theme.measure }),
2139
2305
  /* @__PURE__ */ jsx9(Segment, { points: [vertex, b], color: theme.measure }),
2140
2306
  /* @__PURE__ */ jsx9(Polyline, { points: arc, color: theme.measure }),
@@ -2152,7 +2318,7 @@ var DeltaLegs = ({ a, b, id, format, label }) => {
2152
2318
  const lines = useRef4([]);
2153
2319
  const centre = useMemo6(() => {
2154
2320
  const at = midpoint(a, b);
2155
- return new Vector38(at.x, at.y, at.z);
2321
+ return new Vector39(at.x, at.y, at.z);
2156
2322
  }, [a, b]);
2157
2323
  useFrame2(() => {
2158
2324
  const dash = screenLength(camera, centre, size, DELTA_DASH_PIXELS);
@@ -2166,7 +2332,7 @@ var DeltaLegs = ({ a, b, id, format, label }) => {
2166
2332
  }
2167
2333
  if (changed) invalidate();
2168
2334
  });
2169
- return /* @__PURE__ */ jsxs5(Fragment2, { children: [
2335
+ return /* @__PURE__ */ jsxs6(Fragment2, { children: [
2170
2336
  legs.map((leg, index) => /* @__PURE__ */ jsx9(
2171
2337
  Line,
2172
2338
  {
@@ -2174,8 +2340,8 @@ var DeltaLegs = ({ a, b, id, format, label }) => {
2174
2340
  lines.current[index] = line;
2175
2341
  },
2176
2342
  points: [
2177
- new Vector38(leg.from.x, leg.from.y, leg.from.z),
2178
- new Vector38(leg.to.x, leg.to.y, leg.to.z)
2343
+ new Vector39(leg.from.x, leg.from.y, leg.from.z),
2344
+ new Vector39(leg.to.x, leg.to.y, leg.to.z)
2179
2345
  ],
2180
2346
  color: AXIS_COLORS[leg.axis],
2181
2347
  lineWidth: MEASURE_LINE_PIXELS.leg,
@@ -2211,7 +2377,7 @@ var ScreenDot = ({ point, pixels, color }) => {
2211
2377
  const invalidate = useThree6((state) => state.invalidate);
2212
2378
  const node = useRef4(null);
2213
2379
  const dot = useDot();
2214
- const position = useMemo6(() => new Vector38(point.x, point.y, point.z), [point]);
2380
+ const position = useMemo6(() => new Vector39(point.x, point.y, point.z), [point]);
2215
2381
  useFrame2(() => {
2216
2382
  const group = node.current;
2217
2383
  if (!group) return;
@@ -2226,7 +2392,7 @@ var ScreenDot = ({ point, pixels, color }) => {
2226
2392
  var Segment = ({ points, color }) => {
2227
2393
  const [a, b] = points;
2228
2394
  const ends = useMemo6(
2229
- () => [new Vector38(a.x, a.y, a.z), new Vector38(b.x, b.y, b.z)],
2395
+ () => [new Vector39(a.x, a.y, a.z), new Vector39(b.x, b.y, b.z)],
2230
2396
  // Keyed on the numbers: a draft's tuple is a fresh array on every render,
2231
2397
  // and a rebuilt array would rebuild the line's geometry with it.
2232
2398
  [a.x, a.y, a.z, b.x, b.y, b.z]
@@ -2268,7 +2434,7 @@ import { useMemo as useMemo7 } from "react";
2268
2434
  import { DoubleSide as DoubleSide2 } from "three";
2269
2435
 
2270
2436
  // src/render/tool-marks.ts
2271
- import { Quaternion as Quaternion4, Vector2 as Vector23, Vector3 as Vector39 } from "three";
2437
+ import { Quaternion as Quaternion4, Vector2 as Vector23, Vector3 as Vector310 } from "three";
2272
2438
  var TOOL_CORNER_STEPS = 12;
2273
2439
  var TOOL_ROUND = 64;
2274
2440
  function toolProfile(radius, corner, height) {
@@ -2288,9 +2454,9 @@ function toolProfile(radius, corner, height) {
2288
2454
  points.push(new Vector23(radius, Math.max(height, rounded)));
2289
2455
  return points;
2290
2456
  }
2291
- var UP = new Vector39(0, 1, 0);
2292
- var AXES = [new Vector39(1, 0, 0), new Vector39(0, 1, 0), new Vector39(0, 0, 1)];
2293
- var toVector = ({ x, y, z }) => new Vector39(x, y, z);
2457
+ var UP = new Vector310(0, 1, 0);
2458
+ var AXES = [new Vector310(1, 0, 0), new Vector310(0, 1, 0), new Vector310(0, 0, 1)];
2459
+ var toVector = ({ x, y, z }) => new Vector310(x, y, z);
2294
2460
  var squareTo = (w, along) => {
2295
2461
  const wanted = along ? toVector(along) : null;
2296
2462
  const flat = wanted?.clone().addScaledVector(w, -wanted.dot(w));
@@ -2310,7 +2476,7 @@ function toolMarkShape(mark) {
2310
2476
  if (!(mark.diameter > 0) || axis.lengthSq() === 0) return null;
2311
2477
  const w = axis.normalize();
2312
2478
  const u = squareTo(w, mark.across);
2313
- const v = new Vector39().crossVectors(w, u);
2479
+ const v = new Vector310().crossVectors(w, u);
2314
2480
  const radius = mark.diameter / 2;
2315
2481
  const corner = Math.min(Math.max(mark.cornerRadius ?? 0, 0), radius);
2316
2482
  const height = Math.max(mark.height, corner);
@@ -2340,7 +2506,7 @@ function toolMarkShape(mark) {
2340
2506
  }
2341
2507
 
2342
2508
  // src/tool-marks.tsx
2343
- import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
2509
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
2344
2510
  var BODY = { color: 16317180, opacity: 0.32 };
2345
2511
  var NO_POINTER2 = { pointerEvents: "none" };
2346
2512
  var ignoreRay = () => void 0;
@@ -2354,7 +2520,7 @@ var ToolMarks = ({ marks, visible = true, labelClassName, theme }) => {
2354
2520
  [marks]
2355
2521
  );
2356
2522
  const label = labelClassName ? `${MEASURE_LABEL_CLASS} ${labelClassName}` : MEASURE_LABEL_CLASS;
2357
- return /* @__PURE__ */ jsx10("group", { visible, userData: { [EXCLUDE_FROM_FRAME]: true }, children: shapes.map(({ mark, shape }, index) => /* @__PURE__ */ jsxs6("group", { children: [
2523
+ return /* @__PURE__ */ jsx10("group", { visible, userData: { [EXCLUDE_FROM_FRAME]: true }, children: shapes.map(({ mark, shape }, index) => /* @__PURE__ */ jsxs7("group", { children: [
2358
2524
  /* @__PURE__ */ jsx10(ToolBody, { shape }),
2359
2525
  [...shape.rims, shape.dimension, ...shape.ticks].map((points, line) => /* @__PURE__ */ jsx10(
2360
2526
  Line2,
@@ -2369,16 +2535,16 @@ var ToolMarks = ({ marks, visible = true, labelClassName, theme }) => {
2369
2535
  },
2370
2536
  line
2371
2537
  )),
2372
- mark.label && visible ? /* @__PURE__ */ jsx10(Html2, { position: shape.labelAt, center: true, zIndexRange: [0, 0], style: NO_POINTER2, children: /* @__PURE__ */ jsxs6("div", { className: label, "data-measure-label": "tool", children: [
2538
+ mark.label && visible ? /* @__PURE__ */ jsx10(Html2, { position: shape.labelAt, center: true, zIndexRange: [0, 0], style: NO_POINTER2, children: /* @__PURE__ */ jsxs7("div", { className: label, "data-measure-label": "tool", children: [
2373
2539
  /* @__PURE__ */ jsx10("span", { "data-measure-value": true, children: mark.label }),
2374
- mark.note ? /* @__PURE__ */ jsxs6("span", { "data-measure-note": true, children: [
2540
+ mark.note ? /* @__PURE__ */ jsxs7("span", { "data-measure-note": true, children: [
2375
2541
  " ",
2376
2542
  mark.note
2377
2543
  ] }) : null
2378
2544
  ] }) }) : null
2379
2545
  ] }, index)) });
2380
2546
  };
2381
- var ToolBody = ({ shape }) => /* @__PURE__ */ jsxs6(
2547
+ var ToolBody = ({ shape }) => /* @__PURE__ */ jsxs7(
2382
2548
  "mesh",
2383
2549
  {
2384
2550
  position: shape.base,
@@ -2554,6 +2720,7 @@ export {
2554
2720
  excludedFromFrame,
2555
2721
  featureTriangles,
2556
2722
  featureTypeRank,
2723
+ findSharpCorners,
2557
2724
  fitDistance,
2558
2725
  fixedBoxStockBounds,
2559
2726
  focusForPick,
@@ -2602,10 +2769,12 @@ export {
2602
2769
  sectionOffset,
2603
2770
  sectionOptionsFromState,
2604
2771
  sectionPlane,
2772
+ sharpCornerPositions,
2605
2773
  smoothRegionNormals,
2606
2774
  snapAt,
2607
2775
  squaredUp,
2608
2776
  startPosition,
2777
+ surfaceCreases,
2609
2778
  surfaceUnderRay,
2610
2779
  targetBoundary,
2611
2780
  themesEqual,