@toolpath/viewer 1.3.0 → 1.4.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/README.md CHANGED
@@ -600,6 +600,9 @@ to, the part's edges stop at the plane, the outline where the plane passes throu
600
600
  is an edge with corners of its own, and the capped face is a surface a point can land on. The
601
601
  sample follows the cut — drag the handle or set a depth and the next pointer move sees the new cut.
602
602
  It reads the plane off the part's own material, so a controlled `section` prop works the same way.
603
+ Beside a `<SectionTool>` that has no cut yet, the measure tool waits: it offers no snap and places
604
+ no point until a cut is chosen or the section tool is unmounted, so the click that picks the cut is
605
+ not also the first point of a measurement. Measurements already made stay up meanwhile.
603
606
 
604
607
  Hold Shift to hold the next point to the X, Y or Z line through the last one — whichever axis the
605
608
  pointer is furthest along — so a length along an edge is measured square rather than slightly across
@@ -1328,11 +1328,12 @@ function retargetPose(cameraPosition, currentTarget, hitPoint) {
1328
1328
  // src/render/section-store.ts
1329
1329
  function createSectionStore() {
1330
1330
  let current = null;
1331
- let engaged = 0;
1332
1331
  const listeners = /* @__PURE__ */ new Set();
1333
1332
  const notify = () => {
1334
1333
  for (const listener of listeners) listener();
1335
1334
  };
1335
+ const engaged = counted(notify);
1336
+ const picking = counted(notify);
1336
1337
  return {
1337
1338
  get: () => current,
1338
1339
  set: (next) => {
@@ -1340,12 +1341,10 @@ function createSectionStore() {
1340
1341
  current = next;
1341
1342
  notify();
1342
1343
  },
1343
- isEngaged: () => engaged > 0,
1344
- setEngaged: (next) => {
1345
- const was = engaged > 0;
1346
- engaged = Math.max(0, engaged + (next ? 1 : -1));
1347
- if (engaged > 0 !== was) notify();
1348
- },
1344
+ isEngaged: engaged.is,
1345
+ setEngaged: engaged.set,
1346
+ isPicking: picking.is,
1347
+ setPicking: picking.set,
1349
1348
  subscribe: (listener) => {
1350
1349
  listeners.add(listener);
1351
1350
  return () => {
@@ -1354,6 +1353,17 @@ function createSectionStore() {
1354
1353
  }
1355
1354
  };
1356
1355
  }
1356
+ function counted(notify) {
1357
+ let held = 0;
1358
+ return {
1359
+ is: () => held > 0,
1360
+ set: (next) => {
1361
+ const was = held > 0;
1362
+ held = Math.max(0, held + (next ? 1 : -1));
1363
+ if (held > 0 !== was) notify();
1364
+ }
1365
+ };
1366
+ }
1357
1367
 
1358
1368
  // src/target-marker.tsx
1359
1369
  import { useFrame as useFrame2, useThree as useThree3 } from "@react-three/fiber";
@@ -12,7 +12,7 @@ import {
12
12
  parsePartGeometry,
13
13
  partMeshAssets,
14
14
  smoothRegionNormals
15
- } from "../chunk-HWAEUJ4H.js";
15
+ } from "../chunk-HH3W5T42.js";
16
16
  export {
17
17
  EnginePart,
18
18
  MIN_KERNEL_VERSION,
package/dist/index.d.ts CHANGED
@@ -245,7 +245,9 @@ interface SectionToolProps {
245
245
  * the way to set or clear it from outside the canvas. While this is mounted
246
246
  * the part reports no hovers or picks: a click on a face cuts through it
247
247
  * without also selecting it, and a cut part is looked at rather than picked
248
- * at. Unmounting it hands the pointer back.
248
+ * at. Unmounting it hands the pointer back. While it is offering a cut — up,
249
+ * with none placed yet — a `<MeasureTool>` beside it waits too, so the click
250
+ * that chooses the cut does not also place a measurement point.
249
251
  */
250
252
  declare const SectionTool: ({ theme }: SectionToolProps) => react.JSX.Element | null;
251
253
 
@@ -687,7 +689,12 @@ interface MeasureToolProps {
687
689
  *
688
690
  * Nothing needs wiring. While it is mounted the part reports no hovers or
689
691
  * picks — a click that places a point is not a click that selects a face — and
690
- * unmounting it hands the pointer back. It snaps to the edges the part draws,
692
+ * unmounting it hands the pointer back. Beside a `<SectionTool>` that has no
693
+ * cut yet it waits: no snap is offered and no click places a point until the
694
+ * cut is chosen or the section tool is unmounted, and the click that chooses
695
+ * the cut — the one it wakes on — places nothing either: a click counts only
696
+ * when it was pressed as well as released while the tool was taking points.
697
+ * Measurements already made stay up meanwhile. It snaps to the edges the part draws,
691
698
  * which are the boundaries between analytic surfaces rather than the mesh's
692
699
  * triangles, so a corner is a corner of the part and not of a facet.
693
700
  *
@@ -723,6 +730,12 @@ declare const MeasureTool: ({ mode, measurements, onChange, showDeltas, format,
723
730
  * on a face is not a request to select the face — which is why the flag is a
724
731
  * count rather than a boolean: two tools up at once let go one at a time.
725
732
  *
733
+ * And whether a tool is **picking** — `<SectionTool>` is up with no cut in
734
+ * place, so the next click on the part chooses where the cut goes. That click
735
+ * is the section tool's alone: `<MeasureTool>` beside it offers no snap and
736
+ * places no point until the cut is chosen or the section tool is unmounted.
737
+ * Counted for the same reason as `engaged`.
738
+ *
726
739
  * A subscription rather than React state, so a drag re-renders the two
727
740
  * subscribers and not everything under the canvas.
728
741
  */
@@ -733,7 +746,11 @@ interface SectionStore {
733
746
  isEngaged(): boolean;
734
747
  /** Counted: a tool engages on mount and disengages on unmount, and the part waits for the last. */
735
748
  setEngaged(engaged: boolean): void;
736
- /** Notified on every change to either the cut or the engaged flag. */
749
+ /** Whether a section tool is waiting for a click to say where the cut goes. */
750
+ isPicking(): boolean;
751
+ /** Counted, as `setEngaged` is: raised while a section tool offers a cut, dropped once one is placed. */
752
+ setPicking(picking: boolean): void;
753
+ /** Notified on every change to the cut, the engaged flag, or the picking flag. */
737
754
  subscribe(listener: () => void): () => void;
738
755
  }
739
756
  declare function createSectionStore(): SectionStore;
package/dist/index.js CHANGED
@@ -131,7 +131,7 @@ import {
131
131
  viewUp,
132
132
  viewVector,
133
133
  visualSurfaces
134
- } from "./chunk-HWAEUJ4H.js";
134
+ } from "./chunk-HH3W5T42.js";
135
135
 
136
136
  // src/primitives.tsx
137
137
  import { GizmoHelper } from "@react-three/drei";
@@ -644,6 +644,10 @@ var Picker = ({ box, theme }) => {
644
644
  const span = useMemo3(() => box.getSize(new Vector34()).length(), [box]);
645
645
  const centre = useMemo3(() => box.getCenter(new Vector34()), [box]);
646
646
  const previewSize = span * PREVIEW_SCALE;
647
+ useEffect2(() => {
648
+ store.setPicking(true);
649
+ return () => store.setPicking(false);
650
+ }, [store]);
647
651
  useEffect2(() => {
648
652
  const raycaster = new Raycaster();
649
653
  const pointer = new Vector22();
@@ -840,7 +844,14 @@ var CutSheet = ({ box, section, theme }) => {
840
844
  // src/measure-tool.tsx
841
845
  import { Html, Line } from "@react-three/drei";
842
846
  import { useFrame as useFrame2, useThree as useThree4 } from "@react-three/fiber";
843
- import { useEffect as useEffect3, useLayoutEffect, useMemo as useMemo4, useRef as useRef3, useState as useState3 } from "react";
847
+ import {
848
+ useEffect as useEffect3,
849
+ useLayoutEffect,
850
+ useMemo as useMemo4,
851
+ useRef as useRef3,
852
+ useState as useState3,
853
+ useSyncExternalStore as useSyncExternalStore2
854
+ } from "react";
844
855
  import {
845
856
  Matrix4,
846
857
  Raycaster as Raycaster2,
@@ -1036,6 +1047,7 @@ var MeasureTool = ({
1036
1047
  const store = useSectionStore();
1037
1048
  const box = useContentBox();
1038
1049
  const resolved = useMemo4(() => resolveTheme(theme), [theme]);
1050
+ const picking = useSyncExternalStore2(store.subscribe, store.isPicking, store.isPicking);
1039
1051
  const [held, setHeld] = useState3([]);
1040
1052
  const controlled = measurements !== void 0;
1041
1053
  const list = controlled ? measurements : held;
@@ -1082,7 +1094,7 @@ var MeasureTool = ({
1082
1094
  if (box.isEmpty()) return null;
1083
1095
  const label = className(labelClassName);
1084
1096
  return /* @__PURE__ */ jsxs4("group", { userData: FURNITURE4, children: [
1085
- /* @__PURE__ */ jsx4(
1097
+ picking ? null : /* @__PURE__ */ jsx4(
1086
1098
  Snapper,
1087
1099
  {
1088
1100
  mode,
@@ -1212,8 +1224,14 @@ var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
1212
1224
  if (event.key !== "Shift" || !last || !snap.current) return;
1213
1225
  update(last.x, last.y, event.type === "keydown");
1214
1226
  };
1227
+ let pressed = false;
1228
+ const down = () => {
1229
+ pressed = true;
1230
+ };
1215
1231
  const up = (event) => {
1216
- if (event.button !== 0 || !isTap(event)) return;
1232
+ const seen = pressed;
1233
+ pressed = false;
1234
+ if (!seen || event.button !== 0 || !isTap(event)) return;
1217
1235
  update(event.clientX, event.clientY, event.shiftKey);
1218
1236
  const found = snap.current;
1219
1237
  if (!found) return;
@@ -1225,12 +1243,14 @@ var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
1225
1243
  };
1226
1244
  domElement.addEventListener("pointermove", move);
1227
1245
  domElement.addEventListener("pointerleave", leave);
1246
+ domElement.addEventListener("pointerdown", down);
1228
1247
  domElement.addEventListener("pointerup", up);
1229
1248
  window.addEventListener("keydown", key);
1230
1249
  window.addEventListener("keyup", key);
1231
1250
  return () => {
1232
1251
  domElement.removeEventListener("pointermove", move);
1233
1252
  domElement.removeEventListener("pointerleave", leave);
1253
+ domElement.removeEventListener("pointerdown", down);
1234
1254
  domElement.removeEventListener("pointerup", up);
1235
1255
  window.removeEventListener("keydown", key);
1236
1256
  window.removeEventListener("keyup", key);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toolpath/viewer",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "React Three Fiber viewer for Toolpath Engine part reports",
5
5
  "license": "MIT",
6
6
  "engines": {