@toolpath/viewer 2.2.0 → 2.3.0

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
@@ -1,14 +1,15 @@
1
1
  import {
2
2
  ANGLE_ARC_FRACTION,
3
3
  ANGLE_ARC_SEGMENTS,
4
+ ARROW_AXIS,
4
5
  AXES_PLANE_OFFSET,
5
6
  AXES_PLANE_SCALE,
6
7
  AXIS_COLORS,
8
+ ArrowMesh,
7
9
  CAD_CAMERA_UP,
8
10
  CANDIDATE_WEIGHT,
9
11
  CAP_HATCH,
10
12
  CHAMFER,
11
- CONE_AXIS,
12
13
  CONTROL_SCHEME_OPTIONS,
13
14
  CadCameraControls,
14
15
  DEFAULT_FIT_MARGIN,
@@ -23,8 +24,6 @@ import {
23
24
  ExtendedCameraControls,
24
25
  FEATURE_TYPE_RANKS,
25
26
  HANDLE_PIXELS,
26
- HEAD,
27
- HEAD_RADIUS,
28
27
  HIGHLIGHT_COLORS,
29
28
  HIGHLIGHT_WEIGHT,
30
29
  HOVER_WEIGHT,
@@ -52,7 +51,6 @@ import {
52
51
  REGION_ATTRIBUTE,
53
52
  SECTION_GIZMO_FRAME_SCALE,
54
53
  SECTION_RENDER_ORDER,
55
- SHAFT_RADIUS,
56
54
  SNAP_MARKER_PIXELS,
57
55
  SNAP_PIXELS,
58
56
  STOCK_OBJECT,
@@ -69,7 +67,7 @@ import {
69
67
  applyCapTheme,
70
68
  applyHighlightLayers,
71
69
  applyProjection,
72
- arrowPlacement,
70
+ arrowGeometry,
73
71
  aspectRatio,
74
72
  axesPlanes,
75
73
  bestOwner,
@@ -101,6 +99,7 @@ import {
101
99
  dragPlane,
102
100
  excludedFromFrame,
103
101
  featureTypeRank,
102
+ findSharpCorners,
104
103
  fitDistance,
105
104
  focusForPick,
106
105
  formatDegrees,
@@ -142,10 +141,12 @@ import {
142
141
  sectionOffset,
143
142
  sectionOptionsFromState,
144
143
  sectionPlane,
144
+ sharpCornerPositions,
145
145
  smoothRegionNormals,
146
146
  snapAt,
147
147
  squaredUp,
148
148
  startPosition,
149
+ surfaceCreases,
149
150
  surfaceUnderRay,
150
151
  targetBoundary,
151
152
  themesEqual,
@@ -162,7 +163,7 @@ import {
162
163
  viewUp,
163
164
  viewVector,
164
165
  visualSurfaces
165
- } from "./chunk-WTTOUG4P.js";
166
+ } from "./chunk-N54FHMTG.js";
166
167
 
167
168
  // src/render/feature-triangles.ts
168
169
  var EMPTY = { positions: new Float32Array(), normals: new Float32Array() };
@@ -243,7 +244,7 @@ var HoverCard = ({ pick, children, className: className2, offset = 16 }) => {
243
244
  import { createContext, useContext } from "react";
244
245
 
245
246
  // src/toolbar-icons.tsx
246
- import { jsx as jsx2 } from "react/jsx-runtime";
247
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
247
248
  var SvgIcon = ({ viewBox = "0 0 24 24", fill = "none", children }) => /* @__PURE__ */ jsx2(
248
249
  "svg",
249
250
  {
@@ -269,6 +270,10 @@ var BananaIcon = () => /* @__PURE__ */ jsx2(SvgIcon, { viewBox: "0 0 576 512", f
269
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" }) });
270
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" }) });
271
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
+ ] });
272
277
  var HoverIcon = () => /* @__PURE__ */ jsx2(SvgIcon, { children: /* @__PURE__ */ jsx2("path", { d: "M4 4h16v12H9l-5 4zm4 5h8m-8 3h5" }) });
273
278
  var FocusIcon = () => /* @__PURE__ */ jsx2(SvgIcon, { children: /* @__PURE__ */ jsx2("path", { d: "M4 4h16v16H4zM8 8h8v8H8zM2 12h4m12 0h4M12 2v4m0 12v4" }) });
274
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" }) });
@@ -285,6 +290,7 @@ var ICONS = {
285
290
  tools: ToolsIcon,
286
291
  directions: DirectionsIcon,
287
292
  dfm: DfmIcon,
293
+ sharpCorners: SharpCornersIcon,
288
294
  hover: HoverIcon,
289
295
  focus: FocusIcon,
290
296
  wireframe: WireframeIcon,
@@ -297,7 +303,7 @@ var ToolbarIcon = ({ name }) => {
297
303
  };
298
304
 
299
305
  // src/viewer-toolbar.tsx
300
- import { jsx as jsx3, jsxs } from "react/jsx-runtime";
306
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
301
307
  var ViewerToolbarContext = createContext(null);
302
308
  var ViewerToolbarProvider = ({
303
309
  controls,
@@ -315,7 +321,7 @@ var ToolbarButton = ({ action, icon, label, toggle = true }) => {
315
321
  throw new Error(`<ViewerToolbar.${buttonName(action)}> requires a '${action}' toolbar control`);
316
322
  }
317
323
  const pressed = control.pressed ?? false;
318
- return /* @__PURE__ */ jsxs(
324
+ return /* @__PURE__ */ jsxs2(
319
325
  "button",
320
326
  {
321
327
  className: "viewer-toolbar-button",
@@ -387,6 +393,14 @@ var DfmButton = () => /* @__PURE__ */ jsx3(
387
393
  label: (pressed) => pressed ? "Hide DFM flags" : "Show DFM flags"
388
394
  }
389
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
+ );
390
404
  var HoverButton = () => /* @__PURE__ */ jsx3(
391
405
  ToolbarButton,
392
406
  {
@@ -446,6 +460,7 @@ var DefaultControls = () => {
446
460
  const analysis = [
447
461
  controls.directions ? /* @__PURE__ */ jsx3(DirectionsButton, {}, "directions") : null,
448
462
  controls.dfm ? /* @__PURE__ */ jsx3(DfmButton, {}, "dfm") : null,
463
+ controls.sharpCorners ? /* @__PURE__ */ jsx3(SharpCornersButton, {}, "sharpCorners") : null,
449
464
  controls.hover ? /* @__PURE__ */ jsx3(HoverButton, {}, "hover") : null,
450
465
  controls.focus ? /* @__PURE__ */ jsx3(FocusButton, {}, "focus") : null,
451
466
  controls.wireframe ? /* @__PURE__ */ jsx3(WireframeButton, {}, "wireframe") : null,
@@ -457,7 +472,7 @@ var DefaultControls = () => {
457
472
  controls.reset ? /* @__PURE__ */ jsx3(ResetButton, {}, "reset") : null,
458
473
  controls.top ? /* @__PURE__ */ jsx3(TopButton, {}, "top") : null
459
474
  ].filter(Boolean);
460
- return /* @__PURE__ */ jsxs(Controls, { children: [
475
+ return /* @__PURE__ */ jsxs2(Controls, { children: [
461
476
  display,
462
477
  display.length && analysis.length ? /* @__PURE__ */ jsx3(Divider, {}) : null,
463
478
  analysis,
@@ -486,6 +501,7 @@ var ViewerToolbar = Object.assign(ViewerToolbarRoot, {
486
501
  ToolsButton,
487
502
  DirectionsButton,
488
503
  DfmButton,
504
+ SharpCornersButton,
489
505
  HoverButton,
490
506
  FocusButton,
491
507
  WireframeButton,
@@ -1026,7 +1042,7 @@ function gridFor(box, options = {}) {
1026
1042
  }
1027
1043
 
1028
1044
  // src/primitives.tsx
1029
- import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
1045
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
1030
1046
  var FURNITURE2 = { [EXCLUDE_FROM_FRAME]: true };
1031
1047
  var Grid = ({ step, extent, color, opacity = 0.35 }) => {
1032
1048
  const theme = resolveTheme();
@@ -1088,7 +1104,7 @@ var CubePanels = ({
1088
1104
  setHovered(name);
1089
1105
  invalidate();
1090
1106
  };
1091
- return /* @__PURE__ */ jsxs2("group", { ref: groupRef, scale: 38, children: [
1107
+ return /* @__PURE__ */ jsxs3("group", { ref: groupRef, scale: 38, children: [
1092
1108
  panels.map(({ zone, geometry }) => /* @__PURE__ */ jsx6(
1093
1109
  "mesh",
1094
1110
  {
@@ -1123,9 +1139,34 @@ var CubePanels = ({
1123
1139
 
1124
1140
  // src/direction-arrows.tsx
1125
1141
  import { useThree as useThree4 } from "@react-three/fiber";
1126
- import { useMemo as useMemo4 } from "react";
1127
- import { Quaternion as Quaternion2, Vector3 as Vector34 } from "three";
1128
- import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
1142
+ import { useEffect as useEffect4, useMemo as useMemo4 } from "react";
1143
+ import { Quaternion as Quaternion2, Vector3 as Vector35 } from "three";
1144
+
1145
+ // src/render/directions.ts
1146
+ import { Vector3 as Vector34 } from "three";
1147
+ var LENGTH = 0.33;
1148
+ var GAP = 0.2;
1149
+ function arrowPlacement(direction, box) {
1150
+ const center = box.getCenter(new Vector34());
1151
+ const half = box.getSize(new Vector34()).multiplyScalar(0.5);
1152
+ const radius = half.length() || 1;
1153
+ const axis = new Vector34(direction.x, direction.y, direction.z);
1154
+ if (axis.lengthSq() === 0) axis.set(0, 0, 1);
1155
+ axis.normalize();
1156
+ let exit = Number.POSITIVE_INFINITY;
1157
+ for (const [component, extent] of [
1158
+ [axis.x, half.x],
1159
+ [axis.y, half.y],
1160
+ [axis.z, half.z]
1161
+ ]) {
1162
+ if (Math.abs(component) > 1e-6) exit = Math.min(exit, extent / Math.abs(component));
1163
+ }
1164
+ if (!Number.isFinite(exit)) exit = radius;
1165
+ return { tip: center.addScaledVector(axis, exit + radius * GAP), length: radius * LENGTH };
1166
+ }
1167
+
1168
+ // src/direction-arrows.tsx
1169
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
1129
1170
  var FURNITURE3 = { [EXCLUDE_FROM_FRAME]: true };
1130
1171
  var DirectionArrows = ({
1131
1172
  directions,
@@ -1139,6 +1180,8 @@ var DirectionArrows = ({
1139
1180
  }) => {
1140
1181
  const box = useContentBox();
1141
1182
  const resolved = useMemo4(() => resolveTheme(theme), [theme]);
1183
+ const geometry = useMemo4(() => arrowGeometry(), []);
1184
+ useEffect4(() => () => geometry.dispose(), [geometry]);
1142
1185
  const shown = shownDirection === void 0 ? activeDirection : shownDirection;
1143
1186
  const drawn = (index) => {
1144
1187
  if (shown === null) return true;
@@ -1150,7 +1193,7 @@ var DirectionArrows = ({
1150
1193
  // Excluded from framing: the arrows are deliberately placed outside the
1151
1194
  // part, so a Fit that measured them would frame the arrows and leave the
1152
1195
  // part small in the middle of them.
1153
- /* @__PURE__ */ jsxs3("group", { userData: FURNITURE3, children: [
1196
+ /* @__PURE__ */ jsxs4("group", { userData: FURNITURE3, children: [
1154
1197
  directions.map((direction, index) => {
1155
1198
  if (!drawn(index)) return null;
1156
1199
  return /* @__PURE__ */ jsx7(
@@ -1158,6 +1201,7 @@ var DirectionArrows = ({
1158
1201
  {
1159
1202
  direction,
1160
1203
  box,
1204
+ geometry,
1161
1205
  color: directionColor(index),
1162
1206
  opacity: 0.9,
1163
1207
  onPick: onPickDirection ? () => onPickDirection(index) : void 0
@@ -1170,28 +1214,37 @@ var DirectionArrows = ({
1170
1214
  {
1171
1215
  direction: named.direction,
1172
1216
  box,
1217
+ geometry,
1173
1218
  color: named.color,
1174
1219
  opacity: 0.9,
1175
1220
  onPick: onPickDirection ? () => onPickDirection(named.index) : void 0
1176
1221
  },
1177
1222
  `named-${named.index}`
1178
1223
  )),
1179
- previewDirection ? /* @__PURE__ */ jsx7(Arrow, { direction: previewDirection, box, color: resolved.hover, opacity: 0.95, onTop: true }) : null
1224
+ previewDirection ? /* @__PURE__ */ jsx7(
1225
+ Arrow,
1226
+ {
1227
+ direction: previewDirection,
1228
+ box,
1229
+ geometry,
1230
+ color: resolved.hover,
1231
+ opacity: 0.95,
1232
+ onTop: true
1233
+ }
1234
+ ) : null
1180
1235
  ] })
1181
1236
  );
1182
1237
  };
1183
- var Arrow = ({ direction, box, color, opacity, onPick, onTop = false }) => {
1238
+ var Arrow = ({ direction, box, geometry, color, opacity, onPick, onTop = false }) => {
1184
1239
  const invalidate = useThree4((state) => state.invalidate);
1185
1240
  const { tip, length, quaternion } = useMemo4(() => {
1186
1241
  const placement = arrowPlacement(direction, box);
1187
1242
  const aim = new Quaternion2().setFromUnitVectors(
1188
- CONE_AXIS,
1189
- new Vector34(direction.x, direction.y, direction.z).normalize()
1243
+ ARROW_AXIS,
1244
+ new Vector35(direction.x, direction.y, direction.z).normalize()
1190
1245
  );
1191
1246
  return { tip: placement.tip, length: placement.length, quaternion: aim };
1192
1247
  }, [box, direction]);
1193
- const head = length * HEAD;
1194
- const shaft = length * (1 - HEAD);
1195
1248
  const press = onPick ? {
1196
1249
  onClick: (event) => {
1197
1250
  event.stopPropagation();
@@ -1199,32 +1252,23 @@ var Arrow = ({ direction, box, color, opacity, onPick, onTop = false }) => {
1199
1252
  invalidate();
1200
1253
  }
1201
1254
  } : {};
1202
- return /* @__PURE__ */ jsxs3("group", { position: tip, quaternion, renderOrder: onTop ? 10 : 3, children: [
1203
- /* @__PURE__ */ jsxs3("mesh", { position: [0, head * 0.5, 0], rotation: [Math.PI, 0, 0], ...press, children: [
1204
- /* @__PURE__ */ jsx7("coneGeometry", { args: [length * HEAD_RADIUS, head, 20] }),
1205
- /* @__PURE__ */ jsx7("meshBasicMaterial", { color, transparent: true, opacity, depthTest: !onTop })
1206
- ] }),
1207
- /* @__PURE__ */ jsxs3("mesh", { position: [0, head + shaft * 0.5, 0], ...press, children: [
1208
- /* @__PURE__ */ jsx7("cylinderGeometry", { args: [length * SHAFT_RADIUS, length * SHAFT_RADIUS, shaft, 12] }),
1209
- /* @__PURE__ */ jsx7("meshBasicMaterial", { color, transparent: true, opacity, depthTest: !onTop })
1210
- ] })
1211
- ] });
1255
+ return /* @__PURE__ */ jsx7("group", { position: tip, quaternion, scale: length, renderOrder: onTop ? 10 : 3, children: /* @__PURE__ */ jsx7(ArrowMesh, { geometry, color, opacity, onTop, events: press }) });
1212
1256
  };
1213
1257
 
1214
1258
  // src/section-tool.tsx
1215
1259
  import { useFrame, useThree as useThree5 } from "@react-three/fiber";
1216
- import { useEffect as useEffect4, useMemo as useMemo5, useRef as useRef3, useState as useState4, useSyncExternalStore } from "react";
1260
+ import { useEffect as useEffect5, useMemo as useMemo5, useRef as useRef3, useState as useState4, useSyncExternalStore } from "react";
1217
1261
  import {
1218
1262
  BufferGeometry as BufferGeometry2,
1219
1263
  DoubleSide,
1220
1264
  Quaternion as Quaternion3,
1221
1265
  Raycaster,
1222
1266
  Vector2,
1223
- Vector3 as Vector35
1267
+ Vector3 as Vector36
1224
1268
  } from "three";
1225
- import { Fragment, jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
1269
+ import { Fragment, jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1226
1270
  var FURNITURE4 = { [EXCLUDE_FROM_FRAME]: true };
1227
- var Z = new Vector35(0, 0, 1);
1271
+ var Z = new Vector36(0, 0, 1);
1228
1272
  var AXIS_ROTATIONS = {
1229
1273
  x: [0, Math.PI / 2, 0],
1230
1274
  y: [Math.PI / 2, 0, 0],
@@ -1237,11 +1281,11 @@ var SectionTool = ({ theme }) => {
1237
1281
  const section = useSyncExternalStore(store.subscribe, store.get, store.get);
1238
1282
  const box = useContentBox();
1239
1283
  const resolved = useMemo5(() => resolveTheme(theme), [theme]);
1240
- useEffect4(() => {
1284
+ useEffect5(() => {
1241
1285
  store.setEngaged(true);
1242
1286
  return () => store.setEngaged(false);
1243
1287
  }, [store]);
1244
- useEffect4(() => {
1288
+ useEffect5(() => {
1245
1289
  if (!section) return void 0;
1246
1290
  const clear = (event) => {
1247
1291
  if (event.key === "Escape") store.set(null);
@@ -1255,14 +1299,14 @@ var SectionTool = ({ theme }) => {
1255
1299
  var useSquare = () => {
1256
1300
  const square = useMemo5(
1257
1301
  () => new BufferGeometry2().setFromPoints([
1258
- new Vector35(-1, 1, 0),
1259
- new Vector35(1, 1, 0),
1260
- new Vector35(1, -1, 0),
1261
- new Vector35(-1, -1, 0)
1302
+ new Vector36(-1, 1, 0),
1303
+ new Vector36(1, 1, 0),
1304
+ new Vector36(1, -1, 0),
1305
+ new Vector36(-1, -1, 0)
1262
1306
  ]),
1263
1307
  []
1264
1308
  );
1265
- useEffect4(() => () => square.dispose(), [square]);
1309
+ useEffect5(() => () => square.dispose(), [square]);
1266
1310
  return square;
1267
1311
  };
1268
1312
  var Picker = ({ box, theme }) => {
@@ -1276,17 +1320,17 @@ var Picker = ({ box, theme }) => {
1276
1320
  const previewRef = useRef3(null);
1277
1321
  const hover = useRef3(null);
1278
1322
  const [hoveredPlane, setHoveredPlane] = useState4(null);
1279
- const span = useMemo5(() => box.getSize(new Vector35()).length(), [box]);
1280
- const centre = useMemo5(() => box.getCenter(new Vector35()), [box]);
1323
+ const span = useMemo5(() => box.getSize(new Vector36()).length(), [box]);
1324
+ const centre = useMemo5(() => box.getCenter(new Vector36()), [box]);
1281
1325
  const previewSize = span * PREVIEW_SCALE;
1282
- useEffect4(() => {
1326
+ useEffect5(() => {
1283
1327
  store.setPicking(true);
1284
1328
  return () => store.setPicking(false);
1285
1329
  }, [store]);
1286
- useEffect4(() => {
1330
+ useEffect5(() => {
1287
1331
  const raycaster = new Raycaster();
1288
1332
  const pointer = new Vector2();
1289
- const aim = new Vector35();
1333
+ const aim = new Vector36();
1290
1334
  const hide = () => {
1291
1335
  if (!hover.current) return;
1292
1336
  hover.current = null;
@@ -1357,9 +1401,9 @@ var Picker = ({ box, theme }) => {
1357
1401
  store.set(plane.options);
1358
1402
  }
1359
1403
  });
1360
- return /* @__PURE__ */ jsxs4(Fragment, { children: [
1361
- /* @__PURE__ */ jsxs4("group", { ref: previewRef, visible: false, children: [
1362
- /* @__PURE__ */ jsxs4("mesh", { renderOrder: SECTION_RENDER_ORDER.outline, raycast: () => null, children: [
1404
+ return /* @__PURE__ */ jsxs5(Fragment, { children: [
1405
+ /* @__PURE__ */ jsxs5("group", { ref: previewRef, visible: false, children: [
1406
+ /* @__PURE__ */ jsxs5("mesh", { renderOrder: SECTION_RENDER_ORDER.outline, raycast: () => null, children: [
1363
1407
  /* @__PURE__ */ jsx8("planeGeometry", { args: [previewSize, previewSize] }),
1364
1408
  /* @__PURE__ */ jsx8(
1365
1409
  "meshBasicMaterial",
@@ -1392,13 +1436,13 @@ var Picker = ({ box, theme }) => {
1392
1436
  }
1393
1437
  )
1394
1438
  ] }),
1395
- planes.map((plane) => /* @__PURE__ */ jsxs4(
1439
+ planes.map((plane) => /* @__PURE__ */ jsxs5(
1396
1440
  "group",
1397
1441
  {
1398
1442
  position: [plane.position.x, plane.position.y, plane.position.z],
1399
1443
  rotation: AXIS_ROTATIONS[plane.axis],
1400
1444
  children: [
1401
- /* @__PURE__ */ jsxs4("mesh", { renderOrder: SECTION_RENDER_ORDER.outline, ...planeHandlers(plane), children: [
1445
+ /* @__PURE__ */ jsxs5("mesh", { renderOrder: SECTION_RENDER_ORDER.outline, ...planeHandlers(plane), children: [
1402
1446
  /* @__PURE__ */ jsx8("planeGeometry", { args: [plane.size, plane.size] }),
1403
1447
  /* @__PURE__ */ jsx8(
1404
1448
  "meshBasicMaterial",
@@ -1430,7 +1474,7 @@ var Picker = ({ box, theme }) => {
1430
1474
  var CutSheet = ({ box, section, theme }) => {
1431
1475
  const square = useSquare();
1432
1476
  const cut = useMemo5(() => resolveSectionPlane(section, box), [box, section]);
1433
- const span = useMemo5(() => box.getSize(new Vector35()).length(), [box]);
1477
+ const span = useMemo5(() => box.getSize(new Vector36()).length(), [box]);
1434
1478
  const placement = useMemo5(() => {
1435
1479
  if (!cut) return null;
1436
1480
  const position = sectionAnchor(box, cut.plane).addScaledVector(
@@ -1441,8 +1485,8 @@ var CutSheet = ({ box, section, theme }) => {
1441
1485
  }, [box, cut, span]);
1442
1486
  if (!placement) return null;
1443
1487
  const size = span * OUTLINE_SCALE;
1444
- return /* @__PURE__ */ jsxs4("group", { position: placement.position, quaternion: placement.quaternion, children: [
1445
- /* @__PURE__ */ jsxs4("mesh", { renderOrder: SECTION_RENDER_ORDER.outline, raycast: () => null, children: [
1488
+ return /* @__PURE__ */ jsxs5("group", { position: placement.position, quaternion: placement.quaternion, children: [
1489
+ /* @__PURE__ */ jsxs5("mesh", { renderOrder: SECTION_RENDER_ORDER.outline, raycast: () => null, children: [
1446
1490
  /* @__PURE__ */ jsx8("planeGeometry", { args: [size, size] }),
1447
1491
  /* @__PURE__ */ jsx8(
1448
1492
  "meshBasicMaterial",
@@ -1481,7 +1525,7 @@ var CutSheet = ({ box, section, theme }) => {
1481
1525
  import { Html, Line } from "@react-three/drei";
1482
1526
  import { useFrame as useFrame2, useThree as useThree6 } from "@react-three/fiber";
1483
1527
  import {
1484
- useEffect as useEffect5,
1528
+ useEffect as useEffect6,
1485
1529
  useLayoutEffect as useLayoutEffect3,
1486
1530
  useMemo as useMemo6,
1487
1531
  useRef as useRef4,
@@ -1493,18 +1537,18 @@ import {
1493
1537
  Raycaster as Raycaster2,
1494
1538
  SphereGeometry,
1495
1539
  Vector2 as Vector22,
1496
- Vector3 as Vector37
1540
+ Vector3 as Vector38
1497
1541
  } from "three";
1498
1542
 
1499
1543
  // src/render/resample.ts
1500
- import { Vector3 as Vector36 } from "three";
1544
+ import { Vector3 as Vector37 } from "three";
1501
1545
  var ON_PLANE = 1e-7;
1502
1546
  var STITCH = 1e-5;
1503
1547
  var COLLINEAR = 1e-6;
1504
1548
  function clipSegments(positions, plane) {
1505
1549
  const out = [];
1506
- const a = new Vector36();
1507
- const b = new Vector36();
1550
+ const a = new Vector37();
1551
+ const b = new Vector37();
1508
1552
  const count = Math.floor(positions.length / 6);
1509
1553
  for (let i = 0; i < count; i += 1) {
1510
1554
  a.set(positions[i * 6], positions[i * 6 + 1], positions[i * 6 + 2]);
@@ -1519,11 +1563,11 @@ function clipSegments(positions, plane) {
1519
1563
  return Float32Array.from(out);
1520
1564
  }
1521
1565
  function sectionContour(positions, regionOf, plane) {
1522
- const a = new Vector36();
1523
- const b = new Vector36();
1524
- const c = new Vector36();
1525
- const p = new Vector36();
1526
- const q = new Vector36();
1566
+ const a = new Vector37();
1567
+ const b = new Vector37();
1568
+ const c = new Vector37();
1569
+ const p = new Vector37();
1570
+ const q = new Vector37();
1527
1571
  const byRegion = /* @__PURE__ */ new Map();
1528
1572
  const triangleCount = Math.floor(positions.length / 9);
1529
1573
  for (let t = 0; t < triangleCount; t += 1) {
@@ -1581,17 +1625,17 @@ function stitch(segments, out) {
1581
1625
  return into.set(segments[o], segments[o + 1], segments[o + 2]);
1582
1626
  };
1583
1627
  const direction = (i, into) => {
1584
- const s = new Vector36();
1585
- const e = new Vector36();
1628
+ const s = new Vector37();
1629
+ const e = new Vector37();
1586
1630
  point(i, 0, s);
1587
1631
  point(i, 1, e);
1588
1632
  return into.subVectors(e, s).normalize();
1589
1633
  };
1590
1634
  const consumed = new Uint8Array(count);
1591
- const d = new Vector36();
1592
- const n = new Vector36();
1635
+ const d = new Vector37();
1636
+ const n = new Vector37();
1593
1637
  const next = (i, end) => {
1594
- const at = point(i, end, new Vector36());
1638
+ const at = point(i, end, new Vector37());
1595
1639
  const list = ends.get(key(at.x, at.y, at.z)) ?? [];
1596
1640
  if (list.length !== 2) return null;
1597
1641
  const other = list.find((entry) => entry >> 1 !== i);
@@ -1620,8 +1664,8 @@ function stitch(segments, out) {
1620
1664
  };
1621
1665
  const back = walk(0);
1622
1666
  const forward = walk(1);
1623
- const s = point(back.index, back.end, new Vector36());
1624
- const e = point(forward.index, forward.end, new Vector36());
1667
+ const s = point(back.index, back.end, new Vector37());
1668
+ const e = point(forward.index, forward.end, new Vector37());
1625
1669
  out.push(s.x, s.y, s.z, e.x, e.y, e.z);
1626
1670
  }
1627
1671
  }
@@ -1629,8 +1673,8 @@ function pointInContour(point, plane, contour) {
1629
1673
  const [u, v] = planeAxes(plane);
1630
1674
  const px = point.dot(u);
1631
1675
  const py = point.dot(v);
1632
- const a = new Vector36();
1633
- const b = new Vector36();
1676
+ const a = new Vector37();
1677
+ const b = new Vector37();
1634
1678
  let inside = false;
1635
1679
  const count = Math.floor(contour.length / 6);
1636
1680
  for (let i = 0; i < count; i += 1) {
@@ -1647,15 +1691,15 @@ function pointInContour(point, plane, contour) {
1647
1691
  return inside;
1648
1692
  }
1649
1693
  function capHit(ray, plane, contour) {
1650
- const point = ray.intersectPlane(plane, new Vector36());
1694
+ const point = ray.intersectPlane(plane, new Vector37());
1651
1695
  if (!point) return null;
1652
1696
  return pointInContour(point, plane, contour) ? point : null;
1653
1697
  }
1654
1698
  function planeAxes(plane) {
1655
1699
  const n = plane.normal;
1656
- const seed = Math.abs(n.x) < 0.9 ? new Vector36(1, 0, 0) : new Vector36(0, 1, 0);
1657
- const u = new Vector36().crossVectors(n, seed).normalize();
1658
- const v = new Vector36().crossVectors(n, u).normalize();
1700
+ const seed = Math.abs(n.x) < 0.9 ? new Vector37(1, 0, 0) : new Vector37(0, 1, 0);
1701
+ const u = new Vector37().crossVectors(n, seed).normalize();
1702
+ const v = new Vector37().crossVectors(n, u).normalize();
1659
1703
  return [u, v];
1660
1704
  }
1661
1705
  function sampleSection(meshPositions, regionOf, edgePositions, plane) {
@@ -1668,7 +1712,7 @@ function sampleSection(meshPositions, regionOf, edgePositions, plane) {
1668
1712
  }
1669
1713
 
1670
1714
  // src/measure-tool.tsx
1671
- import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
1715
+ import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1672
1716
  var FURNITURE5 = { [EXCLUDE_FROM_FRAME]: true };
1673
1717
  var MEASURE_LABEL_CLASS = "toolpath-measure-label";
1674
1718
  var MeasureTool = ({
@@ -1688,11 +1732,11 @@ var MeasureTool = ({
1688
1732
  const controlled = measurements !== void 0;
1689
1733
  const list = controlled ? measurements : held;
1690
1734
  const [draft, setDraft] = useState5([]);
1691
- useEffect5(() => {
1735
+ useEffect6(() => {
1692
1736
  store.setEngaged(true);
1693
1737
  return () => store.setEngaged(false);
1694
1738
  }, [store]);
1695
- useEffect5(() => {
1739
+ useEffect6(() => {
1696
1740
  setDraft([]);
1697
1741
  }, [mode]);
1698
1742
  const commit = (next) => {
@@ -1701,7 +1745,7 @@ var MeasureTool = ({
1701
1745
  };
1702
1746
  const latest = useRef4({ list, commit });
1703
1747
  latest.current = { list, commit };
1704
- useEffect5(() => {
1748
+ useEffect6(() => {
1705
1749
  const onKey = (event) => {
1706
1750
  if (event.key === "Escape") {
1707
1751
  setDraft([]);
@@ -1729,7 +1773,7 @@ var MeasureTool = ({
1729
1773
  };
1730
1774
  if (box.isEmpty()) return null;
1731
1775
  const label = className(labelClassName);
1732
- return /* @__PURE__ */ jsxs5("group", { userData: FURNITURE5, children: [
1776
+ return /* @__PURE__ */ jsxs6("group", { userData: FURNITURE5, children: [
1733
1777
  picking ? null : /* @__PURE__ */ jsx9(
1734
1778
  Snapper,
1735
1779
  {
@@ -1765,7 +1809,7 @@ var MeasureTool = ({
1765
1809
  ] });
1766
1810
  };
1767
1811
  var NO_POINTER = { pointerEvents: "none" };
1768
- var NOWHERE = [new Vector37(), new Vector37()];
1812
+ var NOWHERE = [new Vector38(), new Vector38()];
1769
1813
  var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
1770
1814
  const camera = useThree6((state) => state.camera);
1771
1815
  const scene = useThree6((state) => state.scene);
@@ -1786,7 +1830,7 @@ var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
1786
1830
  }, []);
1787
1831
  const current = useRef4({ mode, draft, onPlace, format, size });
1788
1832
  current.current = { mode, draft, onPlace, format, size };
1789
- useEffect5(() => {
1833
+ useEffect6(() => {
1790
1834
  const raycaster = new Raycaster2();
1791
1835
  const pointer = new Vector22();
1792
1836
  const samples = new SampleCache();
@@ -1818,7 +1862,7 @@ var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
1818
1862
  const snapped = snapAt(target, target.edges, camera, viewport);
1819
1863
  const from = placed[placed.length - 1];
1820
1864
  const lock = shift && from ? lockToAxis(from, snapped.point) : null;
1821
- const found = lock ? { ...snapped, point: new Vector37(lock.point.x, lock.point.y, lock.point.z), edge: null } : snapped;
1865
+ const found = lock ? { ...snapped, point: new Vector38(lock.point.x, lock.point.y, lock.point.z), edge: null } : snapped;
1822
1866
  snap.current = found;
1823
1867
  const node = marker.current;
1824
1868
  if (node) {
@@ -1892,7 +1936,7 @@ var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
1892
1936
  window.removeEventListener("keyup", key);
1893
1937
  };
1894
1938
  }, [camera, domElement, invalidate, isTap, scene, theme.measure]);
1895
- useEffect5(() => {
1939
+ useEffect6(() => {
1896
1940
  if (draft.length === 0 && band.current) {
1897
1941
  band.current.visible = false;
1898
1942
  if (bandLabel.current) bandLabel.current.visible = false;
@@ -1903,7 +1947,7 @@ var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
1903
1947
  invalidate();
1904
1948
  }
1905
1949
  }, [draft, invalidate]);
1906
- return /* @__PURE__ */ jsxs5(Fragment2, { children: [
1950
+ return /* @__PURE__ */ jsxs6(Fragment2, { children: [
1907
1951
  /* @__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 }) }) }),
1908
1952
  /* @__PURE__ */ jsx9(
1909
1953
  Line,
@@ -2098,7 +2142,7 @@ var MeasurementView = ({
2098
2142
  const anchor = measurementLabelAnchor(measurement);
2099
2143
  if (measurement.kind === "distance") {
2100
2144
  const [a2, b2] = measurement.points;
2101
- return /* @__PURE__ */ jsxs5(Fragment2, { children: [
2145
+ return /* @__PURE__ */ jsxs6(Fragment2, { children: [
2102
2146
  /* @__PURE__ */ jsx9(Segment, { points: [a2, b2], color: theme.measure }),
2103
2147
  /* @__PURE__ */ jsx9(ScreenDot, { point: a2, pixels: MEASURE_MARKER_PIXELS, color: theme.measure }),
2104
2148
  /* @__PURE__ */ jsx9(ScreenDot, { point: b2, pixels: MEASURE_MARKER_PIXELS, color: theme.measure }),
@@ -2108,7 +2152,7 @@ var MeasurementView = ({
2108
2152
  }
2109
2153
  const [a, vertex, b] = measurement.points;
2110
2154
  const arc = angleArc(vertex, a, b, angleArcRadius(vertex, a, b));
2111
- return /* @__PURE__ */ jsxs5(Fragment2, { children: [
2155
+ return /* @__PURE__ */ jsxs6(Fragment2, { children: [
2112
2156
  /* @__PURE__ */ jsx9(Segment, { points: [vertex, a], color: theme.measure }),
2113
2157
  /* @__PURE__ */ jsx9(Segment, { points: [vertex, b], color: theme.measure }),
2114
2158
  /* @__PURE__ */ jsx9(Polyline, { points: arc, color: theme.measure }),
@@ -2126,7 +2170,7 @@ var DeltaLegs = ({ a, b, id, format, label }) => {
2126
2170
  const lines = useRef4([]);
2127
2171
  const centre = useMemo6(() => {
2128
2172
  const at = midpoint(a, b);
2129
- return new Vector37(at.x, at.y, at.z);
2173
+ return new Vector38(at.x, at.y, at.z);
2130
2174
  }, [a, b]);
2131
2175
  useFrame2(() => {
2132
2176
  const dash = screenLength(camera, centre, size, DELTA_DASH_PIXELS);
@@ -2140,7 +2184,7 @@ var DeltaLegs = ({ a, b, id, format, label }) => {
2140
2184
  }
2141
2185
  if (changed) invalidate();
2142
2186
  });
2143
- return /* @__PURE__ */ jsxs5(Fragment2, { children: [
2187
+ return /* @__PURE__ */ jsxs6(Fragment2, { children: [
2144
2188
  legs.map((leg, index) => /* @__PURE__ */ jsx9(
2145
2189
  Line,
2146
2190
  {
@@ -2148,8 +2192,8 @@ var DeltaLegs = ({ a, b, id, format, label }) => {
2148
2192
  lines.current[index] = line;
2149
2193
  },
2150
2194
  points: [
2151
- new Vector37(leg.from.x, leg.from.y, leg.from.z),
2152
- new Vector37(leg.to.x, leg.to.y, leg.to.z)
2195
+ new Vector38(leg.from.x, leg.from.y, leg.from.z),
2196
+ new Vector38(leg.to.x, leg.to.y, leg.to.z)
2153
2197
  ],
2154
2198
  color: AXIS_COLORS[leg.axis],
2155
2199
  lineWidth: MEASURE_LINE_PIXELS.leg,
@@ -2185,7 +2229,7 @@ var ScreenDot = ({ point, pixels, color }) => {
2185
2229
  const invalidate = useThree6((state) => state.invalidate);
2186
2230
  const node = useRef4(null);
2187
2231
  const dot = useDot();
2188
- const position = useMemo6(() => new Vector37(point.x, point.y, point.z), [point]);
2232
+ const position = useMemo6(() => new Vector38(point.x, point.y, point.z), [point]);
2189
2233
  useFrame2(() => {
2190
2234
  const group = node.current;
2191
2235
  if (!group) return;
@@ -2200,7 +2244,7 @@ var ScreenDot = ({ point, pixels, color }) => {
2200
2244
  var Segment = ({ points, color }) => {
2201
2245
  const [a, b] = points;
2202
2246
  const ends = useMemo6(
2203
- () => [new Vector37(a.x, a.y, a.z), new Vector37(b.x, b.y, b.z)],
2247
+ () => [new Vector38(a.x, a.y, a.z), new Vector38(b.x, b.y, b.z)],
2204
2248
  // Keyed on the numbers: a draft's tuple is a fresh array on every render,
2205
2249
  // and a rebuilt array would rebuild the line's geometry with it.
2206
2250
  [a.x, a.y, a.z, b.x, b.y, b.z]
@@ -2225,7 +2269,7 @@ var Polyline = ({ points, color }) => {
2225
2269
  };
2226
2270
  function useDot() {
2227
2271
  const geometry = useMemo6(() => new SphereGeometry(1, 16, 12), []);
2228
- useEffect5(() => () => geometry.dispose(), [geometry]);
2272
+ useEffect6(() => () => geometry.dispose(), [geometry]);
2229
2273
  return geometry;
2230
2274
  }
2231
2275
  function isEditing(target) {
@@ -2242,7 +2286,7 @@ import { useMemo as useMemo7 } from "react";
2242
2286
  import { DoubleSide as DoubleSide2 } from "three";
2243
2287
 
2244
2288
  // src/render/tool-marks.ts
2245
- import { Quaternion as Quaternion4, Vector2 as Vector23, Vector3 as Vector38 } from "three";
2289
+ import { Quaternion as Quaternion4, Vector2 as Vector23, Vector3 as Vector39 } from "three";
2246
2290
  var TOOL_CORNER_STEPS = 12;
2247
2291
  var TOOL_ROUND = 64;
2248
2292
  function toolProfile(radius, corner, height) {
@@ -2262,9 +2306,9 @@ function toolProfile(radius, corner, height) {
2262
2306
  points.push(new Vector23(radius, Math.max(height, rounded)));
2263
2307
  return points;
2264
2308
  }
2265
- var UP = new Vector38(0, 1, 0);
2266
- var AXES = [new Vector38(1, 0, 0), new Vector38(0, 1, 0), new Vector38(0, 0, 1)];
2267
- var toVector = ({ x, y, z }) => new Vector38(x, y, z);
2309
+ var UP = new Vector39(0, 1, 0);
2310
+ var AXES = [new Vector39(1, 0, 0), new Vector39(0, 1, 0), new Vector39(0, 0, 1)];
2311
+ var toVector = ({ x, y, z }) => new Vector39(x, y, z);
2268
2312
  var squareTo = (w, along) => {
2269
2313
  const wanted = along ? toVector(along) : null;
2270
2314
  const flat = wanted?.clone().addScaledVector(w, -wanted.dot(w));
@@ -2284,7 +2328,7 @@ function toolMarkShape(mark) {
2284
2328
  if (!(mark.diameter > 0) || axis.lengthSq() === 0) return null;
2285
2329
  const w = axis.normalize();
2286
2330
  const u = squareTo(w, mark.across);
2287
- const v = new Vector38().crossVectors(w, u);
2331
+ const v = new Vector39().crossVectors(w, u);
2288
2332
  const radius = mark.diameter / 2;
2289
2333
  const corner = Math.min(Math.max(mark.cornerRadius ?? 0, 0), radius);
2290
2334
  const height = Math.max(mark.height, corner);
@@ -2314,7 +2358,7 @@ function toolMarkShape(mark) {
2314
2358
  }
2315
2359
 
2316
2360
  // src/tool-marks.tsx
2317
- import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
2361
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
2318
2362
  var BODY = { color: 16317180, opacity: 0.32 };
2319
2363
  var NO_POINTER2 = { pointerEvents: "none" };
2320
2364
  var ignoreRay = () => void 0;
@@ -2328,7 +2372,7 @@ var ToolMarks = ({ marks, visible = true, labelClassName, theme }) => {
2328
2372
  [marks]
2329
2373
  );
2330
2374
  const label = labelClassName ? `${MEASURE_LABEL_CLASS} ${labelClassName}` : MEASURE_LABEL_CLASS;
2331
- return /* @__PURE__ */ jsx10("group", { visible, userData: { [EXCLUDE_FROM_FRAME]: true }, children: shapes.map(({ mark, shape }, index) => /* @__PURE__ */ jsxs6("group", { children: [
2375
+ return /* @__PURE__ */ jsx10("group", { visible, userData: { [EXCLUDE_FROM_FRAME]: true }, children: shapes.map(({ mark, shape }, index) => /* @__PURE__ */ jsxs7("group", { children: [
2332
2376
  /* @__PURE__ */ jsx10(ToolBody, { shape }),
2333
2377
  [...shape.rims, shape.dimension, ...shape.ticks].map((points, line) => /* @__PURE__ */ jsx10(
2334
2378
  Line2,
@@ -2343,16 +2387,16 @@ var ToolMarks = ({ marks, visible = true, labelClassName, theme }) => {
2343
2387
  },
2344
2388
  line
2345
2389
  )),
2346
- 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: [
2390
+ 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: [
2347
2391
  /* @__PURE__ */ jsx10("span", { "data-measure-value": true, children: mark.label }),
2348
- mark.note ? /* @__PURE__ */ jsxs6("span", { "data-measure-note": true, children: [
2392
+ mark.note ? /* @__PURE__ */ jsxs7("span", { "data-measure-note": true, children: [
2349
2393
  " ",
2350
2394
  mark.note
2351
2395
  ] }) : null
2352
2396
  ] }) }) : null
2353
2397
  ] }, index)) });
2354
2398
  };
2355
- var ToolBody = ({ shape }) => /* @__PURE__ */ jsxs6(
2399
+ var ToolBody = ({ shape }) => /* @__PURE__ */ jsxs7(
2356
2400
  "mesh",
2357
2401
  {
2358
2402
  position: shape.base,
@@ -2528,6 +2572,7 @@ export {
2528
2572
  excludedFromFrame,
2529
2573
  featureTriangles,
2530
2574
  featureTypeRank,
2575
+ findSharpCorners,
2531
2576
  fitDistance,
2532
2577
  fixedBoxStockBounds,
2533
2578
  focusForPick,
@@ -2576,10 +2621,12 @@ export {
2576
2621
  sectionOffset,
2577
2622
  sectionOptionsFromState,
2578
2623
  sectionPlane,
2624
+ sharpCornerPositions,
2579
2625
  smoothRegionNormals,
2580
2626
  snapAt,
2581
2627
  squaredUp,
2582
2628
  startPosition,
2629
+ surfaceCreases,
2583
2630
  surfaceUnderRay,
2584
2631
  targetBoundary,
2585
2632
  themesEqual,