@toolpath/viewer 1.1.1 → 1.2.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 +866 -94
- package/dist/{chunk-4BU4QZCL.js → chunk-HWAEUJ4H.js} +636 -277
- package/dist/engine/index.d.ts +2 -2
- package/dist/engine/index.js +1 -1
- package/dist/index.d.ts +615 -198
- package/dist/index.js +1000 -1
- package/dist/{normalize-OULo7e0F.d.ts → normalize-DHOSNiuO.d.ts} +221 -139
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
|
+
AXES_PLANE_OFFSET,
|
|
3
|
+
AXES_PLANE_SCALE,
|
|
2
4
|
CAD_CAMERA_UP,
|
|
3
5
|
CANDIDATE_WEIGHT,
|
|
6
|
+
CAP_HATCH,
|
|
4
7
|
CHAMFER,
|
|
5
8
|
CONE_AXIS,
|
|
6
9
|
CadCameraControls,
|
|
7
10
|
DEFAULT_FIT_MARGIN,
|
|
11
|
+
DEFAULT_SECTION_NORMAL,
|
|
8
12
|
DEFAULT_THEME,
|
|
9
13
|
DIRECTION_COLORS,
|
|
14
|
+
DISABLED_SECTION,
|
|
10
15
|
DOUBLE_TAP_MS,
|
|
11
16
|
EXCLUDE_FROM_FRAME,
|
|
12
17
|
EnginePart,
|
|
@@ -29,8 +34,10 @@ import {
|
|
|
29
34
|
ORBIT_TARGET_RING_OPACITY,
|
|
30
35
|
ORBIT_TARGET_RING_PIXELS,
|
|
31
36
|
ORBIT_TARGET_RING_WIDTH,
|
|
37
|
+
OUTLINE_SCALE,
|
|
32
38
|
PERSPECTIVE_FOV,
|
|
33
39
|
PICKED_SURFACE_LABEL,
|
|
40
|
+
PREVIEW_SCALE,
|
|
34
41
|
PartMesh,
|
|
35
42
|
PartReportFormatError,
|
|
36
43
|
REGION_ATTRIBUTE,
|
|
@@ -43,10 +50,12 @@ import {
|
|
|
43
50
|
VIEW_SIGNS,
|
|
44
51
|
Viewer,
|
|
45
52
|
adaptedUp,
|
|
53
|
+
applyCapTheme,
|
|
46
54
|
applyHighlightLayers,
|
|
47
55
|
applyProjection,
|
|
48
56
|
arrowPlacement,
|
|
49
57
|
aspectRatio,
|
|
58
|
+
axesPlanes,
|
|
50
59
|
bestOwner,
|
|
51
60
|
boundsFromBox,
|
|
52
61
|
buildPick,
|
|
@@ -56,7 +65,12 @@ import {
|
|
|
56
65
|
cadViewDirections,
|
|
57
66
|
cameraLimits,
|
|
58
67
|
contentBounds,
|
|
68
|
+
createCapMaterial,
|
|
69
|
+
createMaskMaterial,
|
|
70
|
+
createMaskTarget,
|
|
59
71
|
createPart,
|
|
72
|
+
createSectionStore,
|
|
73
|
+
createStencilMaterials,
|
|
60
74
|
cubeOutlineGeometry,
|
|
61
75
|
cubeZones,
|
|
62
76
|
currentViewDirection,
|
|
@@ -66,10 +80,12 @@ import {
|
|
|
66
80
|
directionIndexOf,
|
|
67
81
|
directionLabel,
|
|
68
82
|
dragPlane,
|
|
83
|
+
excludedFromFrame,
|
|
69
84
|
featureTypeRank,
|
|
70
85
|
fitDistance,
|
|
71
86
|
focusForPick,
|
|
72
87
|
groupByDirection,
|
|
88
|
+
hitUnderRay,
|
|
73
89
|
labelGeometry,
|
|
74
90
|
labelTexture,
|
|
75
91
|
movedFar,
|
|
@@ -87,6 +103,7 @@ import {
|
|
|
87
103
|
retargetPose,
|
|
88
104
|
sameDirection,
|
|
89
105
|
screenLength,
|
|
106
|
+
sectionAnchor,
|
|
90
107
|
sectionBounds,
|
|
91
108
|
sectionConstant,
|
|
92
109
|
sectionDepth,
|
|
@@ -94,16 +111,19 @@ import {
|
|
|
94
111
|
sectionDepthRange,
|
|
95
112
|
sectionFromPick,
|
|
96
113
|
sectionOffset,
|
|
114
|
+
sectionOptionsFromState,
|
|
97
115
|
sectionPlane,
|
|
98
116
|
smoothRegionNormals,
|
|
99
117
|
squaredUp,
|
|
100
118
|
startPosition,
|
|
119
|
+
surfaceUnderRay,
|
|
101
120
|
targetBoundary,
|
|
102
121
|
themesEqual,
|
|
103
122
|
trackDoubleTaps,
|
|
104
123
|
trackTaps,
|
|
105
124
|
useContentBox,
|
|
106
125
|
useRetarget,
|
|
126
|
+
useSectionStore,
|
|
107
127
|
useTapGuard,
|
|
108
128
|
useViewerControls,
|
|
109
129
|
viewDirection,
|
|
@@ -111,7 +131,7 @@ import {
|
|
|
111
131
|
viewUp,
|
|
112
132
|
viewVector,
|
|
113
133
|
visualSurfaces
|
|
114
|
-
} from "./chunk-
|
|
134
|
+
} from "./chunk-HWAEUJ4H.js";
|
|
115
135
|
|
|
116
136
|
// src/primitives.tsx
|
|
117
137
|
import { GizmoHelper } from "@react-three/drei";
|
|
@@ -354,6 +374,936 @@ var Arrow = ({ direction, box, color, opacity, onPick, onTop = false }) => {
|
|
|
354
374
|
] });
|
|
355
375
|
};
|
|
356
376
|
|
|
377
|
+
// src/section-tool.tsx
|
|
378
|
+
import { useFrame, useThree as useThree3 } from "@react-three/fiber";
|
|
379
|
+
import { useEffect as useEffect2, useMemo as useMemo3, useRef as useRef2, useState as useState2, useSyncExternalStore } from "react";
|
|
380
|
+
import {
|
|
381
|
+
BufferGeometry as BufferGeometry2,
|
|
382
|
+
DoubleSide,
|
|
383
|
+
Quaternion as Quaternion2,
|
|
384
|
+
Raycaster,
|
|
385
|
+
Vector2 as Vector22,
|
|
386
|
+
Vector3 as Vector34
|
|
387
|
+
} from "three";
|
|
388
|
+
|
|
389
|
+
// src/render/measure.ts
|
|
390
|
+
import { Vector2, Vector3 as Vector33 } from "three";
|
|
391
|
+
var MEASURE_RENDER_ORDER = 7;
|
|
392
|
+
var AXIS_COLORS = { x: 16739179, y: 7331978, z: 7314431 };
|
|
393
|
+
var SNAP_PIXELS = {
|
|
394
|
+
vertex: 14,
|
|
395
|
+
midpoint: 12,
|
|
396
|
+
edge: 9
|
|
397
|
+
};
|
|
398
|
+
var SNAP_MARKER_PIXELS = {
|
|
399
|
+
vertex: 6,
|
|
400
|
+
midpoint: 5,
|
|
401
|
+
edge: 4,
|
|
402
|
+
face: 3.5
|
|
403
|
+
};
|
|
404
|
+
var MEASURE_MARKER_PIXELS = 3.5;
|
|
405
|
+
var MEASURE_LINE_PIXELS = {
|
|
406
|
+
line: 2,
|
|
407
|
+
leg: 1.5,
|
|
408
|
+
edge: 3
|
|
409
|
+
};
|
|
410
|
+
var DELTA_DASH_PIXELS = 6;
|
|
411
|
+
var ANGLE_ARC_FRACTION = 0.3;
|
|
412
|
+
var ANGLE_ARC_SEGMENTS = 24;
|
|
413
|
+
function snapAt(hit, edges, camera, viewport) {
|
|
414
|
+
const face = {
|
|
415
|
+
kind: "face",
|
|
416
|
+
point: hit.point.clone(),
|
|
417
|
+
normal: hit.normal.clone(),
|
|
418
|
+
edge: null
|
|
419
|
+
};
|
|
420
|
+
if (!edges) return face;
|
|
421
|
+
const positions = edges.positions;
|
|
422
|
+
const segments = Math.floor(positions.length / 6);
|
|
423
|
+
if (segments === 0) return face;
|
|
424
|
+
const inverse = edges.matrixWorld.clone().invert();
|
|
425
|
+
const p = hit.point.clone().applyMatrix4(inverse);
|
|
426
|
+
const n = hit.normal.clone().transformDirection(inverse);
|
|
427
|
+
const reach = screenLength(camera, hit.point, viewport, SNAP_PIXELS.vertex) * 1.5;
|
|
428
|
+
const reachSq = reach * reach;
|
|
429
|
+
const pointer = screenPoint(camera, hit.point, viewport);
|
|
430
|
+
const best = {
|
|
431
|
+
vertex: { distance: Number.POSITIVE_INFINITY, index: -1, t: 0 },
|
|
432
|
+
midpoint: { distance: Number.POSITIVE_INFINITY, index: -1, t: 0 },
|
|
433
|
+
edge: { distance: Number.POSITIVE_INFINITY, index: -1, t: 0 }
|
|
434
|
+
};
|
|
435
|
+
const a = new Vector33();
|
|
436
|
+
const b = new Vector33();
|
|
437
|
+
const ab = new Vector33();
|
|
438
|
+
const ap = new Vector33();
|
|
439
|
+
const q = new Vector33();
|
|
440
|
+
const pixel = new Vector2();
|
|
441
|
+
const consider = (kind, point, index, t) => {
|
|
442
|
+
const distance = screenPoint(
|
|
443
|
+
camera,
|
|
444
|
+
q.copy(point).applyMatrix4(edges.matrixWorld),
|
|
445
|
+
viewport,
|
|
446
|
+
pixel
|
|
447
|
+
).distanceTo(pointer);
|
|
448
|
+
if (distance <= SNAP_PIXELS[kind] && distance < best[kind].distance) {
|
|
449
|
+
best[kind] = { distance, index, t };
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
for (let index = 0; index < segments; index += 1) {
|
|
453
|
+
const offset = index * 6;
|
|
454
|
+
a.set(positions[offset], positions[offset + 1], positions[offset + 2]);
|
|
455
|
+
b.set(positions[offset + 3], positions[offset + 4], positions[offset + 5]);
|
|
456
|
+
if (Math.abs(ap.subVectors(a, p).dot(n)) > reach) continue;
|
|
457
|
+
if (Math.abs(ap.subVectors(b, p).dot(n)) > reach) continue;
|
|
458
|
+
ab.subVectors(b, a);
|
|
459
|
+
ap.subVectors(p, a);
|
|
460
|
+
const lengthSq = ab.lengthSq();
|
|
461
|
+
const t = lengthSq === 0 ? 0 : Math.min(1, Math.max(0, ap.dot(ab) / lengthSq));
|
|
462
|
+
q.copy(a).addScaledVector(ab, t);
|
|
463
|
+
if (q.distanceToSquared(p) > reachSq) continue;
|
|
464
|
+
consider("vertex", a, index, 0);
|
|
465
|
+
consider("vertex", b, index, 1);
|
|
466
|
+
consider("midpoint", q.copy(a).addScaledVector(ab, 0.5), index, 0.5);
|
|
467
|
+
consider("edge", q.copy(a).addScaledVector(ab, t), index, t);
|
|
468
|
+
}
|
|
469
|
+
for (const kind of ["vertex", "midpoint", "edge"]) {
|
|
470
|
+
const found = best[kind];
|
|
471
|
+
if (found.index < 0) continue;
|
|
472
|
+
const offset = found.index * 6;
|
|
473
|
+
const start = new Vector33(positions[offset], positions[offset + 1], positions[offset + 2]);
|
|
474
|
+
const end = new Vector33(positions[offset + 3], positions[offset + 4], positions[offset + 5]);
|
|
475
|
+
const point = start.clone().lerp(end, found.t);
|
|
476
|
+
start.applyMatrix4(edges.matrixWorld);
|
|
477
|
+
end.applyMatrix4(edges.matrixWorld);
|
|
478
|
+
point.applyMatrix4(edges.matrixWorld);
|
|
479
|
+
return {
|
|
480
|
+
kind,
|
|
481
|
+
point,
|
|
482
|
+
normal: hit.normal.clone(),
|
|
483
|
+
edge: kind === "vertex" ? null : [start, end]
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
return face;
|
|
487
|
+
}
|
|
488
|
+
function screenPoint(camera, point, viewport, into = new Vector2()) {
|
|
489
|
+
const ndc = point.clone().project(camera);
|
|
490
|
+
return into.set((ndc.x + 1) / 2 * viewport.width, (1 - ndc.y) / 2 * viewport.height);
|
|
491
|
+
}
|
|
492
|
+
var POINTS_PER_MEASUREMENT = {
|
|
493
|
+
distance: 2,
|
|
494
|
+
angle: 3
|
|
495
|
+
};
|
|
496
|
+
function measurementFromPoints(kind, points, id) {
|
|
497
|
+
if (points.length < POINTS_PER_MEASUREMENT[kind]) return null;
|
|
498
|
+
const [a, b, c] = points;
|
|
499
|
+
if (kind === "distance") return { id, kind, points: [copy(a), copy(b)] };
|
|
500
|
+
return { id, kind, points: [copy(a), copy(b), copy(c)] };
|
|
501
|
+
}
|
|
502
|
+
function nextMeasurementId(held) {
|
|
503
|
+
return held.reduce((top, entry) => Math.max(top, entry.id), 0) + 1;
|
|
504
|
+
}
|
|
505
|
+
function distanceBetween(a, b) {
|
|
506
|
+
return Math.hypot(b.x - a.x, b.y - a.y, b.z - a.z);
|
|
507
|
+
}
|
|
508
|
+
function deltaBetween(a, b) {
|
|
509
|
+
return { x: b.x - a.x, y: b.y - a.y, z: b.z - a.z };
|
|
510
|
+
}
|
|
511
|
+
function midpoint(a, b) {
|
|
512
|
+
return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2, z: (a.z + b.z) / 2 };
|
|
513
|
+
}
|
|
514
|
+
function angleAt(vertex, a, b) {
|
|
515
|
+
const u = new Vector33(a.x - vertex.x, a.y - vertex.y, a.z - vertex.z);
|
|
516
|
+
const w = new Vector33(b.x - vertex.x, b.y - vertex.y, b.z - vertex.z);
|
|
517
|
+
if (u.lengthSq() === 0 || w.lengthSq() === 0) return 0;
|
|
518
|
+
return u.angleTo(w) * 180 / Math.PI;
|
|
519
|
+
}
|
|
520
|
+
function lockToAxis(from, to) {
|
|
521
|
+
const delta = deltaBetween(from, to);
|
|
522
|
+
let axis = "x";
|
|
523
|
+
for (const candidate of ["y", "z"]) {
|
|
524
|
+
if (Math.abs(delta[candidate]) > Math.abs(delta[axis])) axis = candidate;
|
|
525
|
+
}
|
|
526
|
+
return { axis, point: { ...from, [axis]: to[axis] } };
|
|
527
|
+
}
|
|
528
|
+
function deltaLegs(a, b) {
|
|
529
|
+
const delta = deltaBetween(a, b);
|
|
530
|
+
const threshold = Math.max(1e-9, distanceBetween(a, b) * 1e-6);
|
|
531
|
+
const legs = [];
|
|
532
|
+
let cursor = copy(a);
|
|
533
|
+
for (const axis of ["x", "y", "z"]) {
|
|
534
|
+
if (Math.abs(delta[axis]) <= threshold) continue;
|
|
535
|
+
const to = { ...cursor, [axis]: b[axis] };
|
|
536
|
+
legs.push({ axis, from: cursor, to, length: Math.abs(delta[axis]) });
|
|
537
|
+
cursor = to;
|
|
538
|
+
}
|
|
539
|
+
return legs.length > 1 ? legs : [];
|
|
540
|
+
}
|
|
541
|
+
function angleArc(vertex, a, b, radius, segments = ANGLE_ARC_SEGMENTS) {
|
|
542
|
+
const v = new Vector33(vertex.x, vertex.y, vertex.z);
|
|
543
|
+
const u = new Vector33(a.x, a.y, a.z).sub(v);
|
|
544
|
+
const w = new Vector33(b.x, b.y, b.z).sub(v);
|
|
545
|
+
if (u.lengthSq() === 0 || w.lengthSq() === 0) return [];
|
|
546
|
+
u.normalize();
|
|
547
|
+
w.normalize();
|
|
548
|
+
const theta = u.angleTo(w);
|
|
549
|
+
const sine = Math.sin(theta);
|
|
550
|
+
if (sine < 1e-6) {
|
|
551
|
+
return [v.clone().addScaledVector(u, radius), v.clone().addScaledVector(w, radius)];
|
|
552
|
+
}
|
|
553
|
+
const points = [];
|
|
554
|
+
for (let i = 0; i <= segments; i += 1) {
|
|
555
|
+
const t = i / segments;
|
|
556
|
+
const direction = u.clone().multiplyScalar(Math.sin((1 - t) * theta) / sine).addScaledVector(w, Math.sin(t * theta) / sine);
|
|
557
|
+
points.push(v.clone().addScaledVector(direction, radius));
|
|
558
|
+
}
|
|
559
|
+
return points;
|
|
560
|
+
}
|
|
561
|
+
function angleArcRadius(vertex, a, b) {
|
|
562
|
+
return Math.min(distanceBetween(vertex, a), distanceBetween(vertex, b)) * ANGLE_ARC_FRACTION;
|
|
563
|
+
}
|
|
564
|
+
function formatMillimetres(mm, digits = 2) {
|
|
565
|
+
return `${mm.toFixed(digits)} mm`;
|
|
566
|
+
}
|
|
567
|
+
function formatDegrees(degrees, digits = 1) {
|
|
568
|
+
return `${degrees.toFixed(digits)}\xB0`;
|
|
569
|
+
}
|
|
570
|
+
function measurementLabel(measurement, format = formatMillimetres) {
|
|
571
|
+
if (measurement.kind === "distance") {
|
|
572
|
+
const [a2, b2] = measurement.points;
|
|
573
|
+
return format(distanceBetween(a2, b2));
|
|
574
|
+
}
|
|
575
|
+
const [a, vertex, b] = measurement.points;
|
|
576
|
+
return formatDegrees(angleAt(vertex, a, b));
|
|
577
|
+
}
|
|
578
|
+
function measurementLabelAnchor(measurement) {
|
|
579
|
+
if (measurement.kind === "distance") return midpoint(measurement.points[0], measurement.points[1]);
|
|
580
|
+
const [a, vertex, b] = measurement.points;
|
|
581
|
+
const arc = angleArc(vertex, a, b, angleArcRadius(vertex, a, b) * 1.6, 2);
|
|
582
|
+
const middle = arc[1] ?? arc[0];
|
|
583
|
+
return middle ? { x: middle.x, y: middle.y, z: middle.z } : copy(vertex);
|
|
584
|
+
}
|
|
585
|
+
function copy(v) {
|
|
586
|
+
return { x: v.x, y: v.y, z: v.z };
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// src/section-tool.tsx
|
|
590
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
591
|
+
var FURNITURE3 = { [EXCLUDE_FROM_FRAME]: true };
|
|
592
|
+
var Z = new Vector34(0, 0, 1);
|
|
593
|
+
var AXIS_ROTATIONS = {
|
|
594
|
+
x: [0, Math.PI / 2, 0],
|
|
595
|
+
y: [Math.PI / 2, 0, 0],
|
|
596
|
+
z: [0, 0, 0]
|
|
597
|
+
};
|
|
598
|
+
var PREVIEW_LIFT = 2e-3;
|
|
599
|
+
var SHEET_SETBACK = 1e-3;
|
|
600
|
+
var SectionTool = ({ theme }) => {
|
|
601
|
+
const store = useSectionStore();
|
|
602
|
+
const section = useSyncExternalStore(store.subscribe, store.get, store.get);
|
|
603
|
+
const box = useContentBox();
|
|
604
|
+
const resolved = useMemo3(() => resolveTheme(theme), [theme]);
|
|
605
|
+
useEffect2(() => {
|
|
606
|
+
store.setEngaged(true);
|
|
607
|
+
return () => store.setEngaged(false);
|
|
608
|
+
}, [store]);
|
|
609
|
+
useEffect2(() => {
|
|
610
|
+
if (!section) return void 0;
|
|
611
|
+
const clear = (event) => {
|
|
612
|
+
if (event.key === "Escape") store.set(null);
|
|
613
|
+
};
|
|
614
|
+
window.addEventListener("keydown", clear);
|
|
615
|
+
return () => window.removeEventListener("keydown", clear);
|
|
616
|
+
}, [section, store]);
|
|
617
|
+
if (box.isEmpty()) return null;
|
|
618
|
+
return /* @__PURE__ */ jsx3("group", { userData: FURNITURE3, children: section ? /* @__PURE__ */ jsx3(CutSheet, { box, section, theme: resolved }) : /* @__PURE__ */ jsx3(Picker, { box, theme: resolved }) });
|
|
619
|
+
};
|
|
620
|
+
var useSquare = () => {
|
|
621
|
+
const square = useMemo3(
|
|
622
|
+
() => new BufferGeometry2().setFromPoints([
|
|
623
|
+
new Vector34(-1, 1, 0),
|
|
624
|
+
new Vector34(1, 1, 0),
|
|
625
|
+
new Vector34(1, -1, 0),
|
|
626
|
+
new Vector34(-1, -1, 0)
|
|
627
|
+
]),
|
|
628
|
+
[]
|
|
629
|
+
);
|
|
630
|
+
useEffect2(() => () => square.dispose(), [square]);
|
|
631
|
+
return square;
|
|
632
|
+
};
|
|
633
|
+
var Picker = ({ box, theme }) => {
|
|
634
|
+
const store = useSectionStore();
|
|
635
|
+
const camera = useThree3((state) => state.camera);
|
|
636
|
+
const scene = useThree3((state) => state.scene);
|
|
637
|
+
const domElement = useThree3((state) => state.gl.domElement);
|
|
638
|
+
const invalidate = useThree3((state) => state.invalidate);
|
|
639
|
+
const isTap = useTapGuard();
|
|
640
|
+
const square = useSquare();
|
|
641
|
+
const previewRef = useRef2(null);
|
|
642
|
+
const hover = useRef2(null);
|
|
643
|
+
const [hoveredPlane, setHoveredPlane] = useState2(null);
|
|
644
|
+
const span = useMemo3(() => box.getSize(new Vector34()).length(), [box]);
|
|
645
|
+
const centre = useMemo3(() => box.getCenter(new Vector34()), [box]);
|
|
646
|
+
const previewSize = span * PREVIEW_SCALE;
|
|
647
|
+
useEffect2(() => {
|
|
648
|
+
const raycaster = new Raycaster();
|
|
649
|
+
const pointer = new Vector22();
|
|
650
|
+
const aim = new Vector34();
|
|
651
|
+
const hide = () => {
|
|
652
|
+
if (!hover.current) return;
|
|
653
|
+
hover.current = null;
|
|
654
|
+
if (previewRef.current) previewRef.current.visible = false;
|
|
655
|
+
invalidate();
|
|
656
|
+
};
|
|
657
|
+
const move = (event) => {
|
|
658
|
+
const rect = domElement.getBoundingClientRect();
|
|
659
|
+
pointer.set(
|
|
660
|
+
(event.clientX - rect.left) / rect.width * 2 - 1,
|
|
661
|
+
-((event.clientY - rect.top) / rect.height) * 2 + 1
|
|
662
|
+
);
|
|
663
|
+
raycaster.setFromCamera(pointer, camera);
|
|
664
|
+
const hit = surfaceUnderRay(raycaster, scene);
|
|
665
|
+
const preview = previewRef.current;
|
|
666
|
+
if (!hit || !preview) {
|
|
667
|
+
hide();
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
hover.current = hit;
|
|
671
|
+
preview.position.copy(hit.point).addScaledVector(hit.normal, span * PREVIEW_LIFT);
|
|
672
|
+
preview.lookAt(aim.copy(hit.point).add(hit.normal));
|
|
673
|
+
preview.visible = true;
|
|
674
|
+
invalidate();
|
|
675
|
+
};
|
|
676
|
+
const up = (event) => {
|
|
677
|
+
const hit = hover.current;
|
|
678
|
+
if (event.button !== 0 || !hit || !isTap(event)) return;
|
|
679
|
+
store.set({
|
|
680
|
+
enabled: true,
|
|
681
|
+
plane: sectionFromPick({ point: hit.point, normal: hit.normal }),
|
|
682
|
+
depth: pickedStartDepth(box)
|
|
683
|
+
});
|
|
684
|
+
};
|
|
685
|
+
domElement.addEventListener("pointermove", move);
|
|
686
|
+
domElement.addEventListener("pointerleave", hide);
|
|
687
|
+
domElement.addEventListener("pointerup", up);
|
|
688
|
+
return () => {
|
|
689
|
+
domElement.removeEventListener("pointermove", move);
|
|
690
|
+
domElement.removeEventListener("pointerleave", hide);
|
|
691
|
+
domElement.removeEventListener("pointerup", up);
|
|
692
|
+
};
|
|
693
|
+
}, [box, camera, domElement, invalidate, isTap, scene, span, store]);
|
|
694
|
+
const [planes, setPlanes] = useState2(() => axesPlanes(box, camera.position));
|
|
695
|
+
const key = planes.map((plane) => plane.sign).join();
|
|
696
|
+
useFrame(() => {
|
|
697
|
+
const next = [camera.position.x, camera.position.y, camera.position.z].map((eye, axis) => eye - [centre.x, centre.y, centre.z][axis] < 0 ? -1 : 1).join();
|
|
698
|
+
if (next !== key) setPlanes(axesPlanes(box, camera.position));
|
|
699
|
+
});
|
|
700
|
+
const planeHandlers = (plane) => ({
|
|
701
|
+
// A plane is behind the part, and the part does not stop propagation: a
|
|
702
|
+
// click on a face reaches the plane behind it too. The preview says which
|
|
703
|
+
// one the pointer is really on.
|
|
704
|
+
onPointerOver: (event) => {
|
|
705
|
+
if (hover.current) return;
|
|
706
|
+
event.stopPropagation();
|
|
707
|
+
setHoveredPlane(plane.axis);
|
|
708
|
+
invalidate();
|
|
709
|
+
},
|
|
710
|
+
onPointerOut: () => {
|
|
711
|
+
setHoveredPlane((held) => held === plane.axis ? null : held);
|
|
712
|
+
invalidate();
|
|
713
|
+
},
|
|
714
|
+
onClick: (event) => {
|
|
715
|
+
if (hover.current || !isTap(event.nativeEvent)) return;
|
|
716
|
+
event.stopPropagation();
|
|
717
|
+
store.set(plane.options);
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
return /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
721
|
+
/* @__PURE__ */ jsxs3("group", { ref: previewRef, visible: false, children: [
|
|
722
|
+
/* @__PURE__ */ jsxs3("mesh", { renderOrder: SECTION_RENDER_ORDER.outline, raycast: () => null, children: [
|
|
723
|
+
/* @__PURE__ */ jsx3("planeGeometry", { args: [previewSize, previewSize] }),
|
|
724
|
+
/* @__PURE__ */ jsx3(
|
|
725
|
+
"meshBasicMaterial",
|
|
726
|
+
{
|
|
727
|
+
color: theme.sectionOutline,
|
|
728
|
+
transparent: true,
|
|
729
|
+
opacity: 0.2,
|
|
730
|
+
side: DoubleSide,
|
|
731
|
+
depthWrite: false
|
|
732
|
+
}
|
|
733
|
+
)
|
|
734
|
+
] }),
|
|
735
|
+
/* @__PURE__ */ jsx3(
|
|
736
|
+
"lineLoop",
|
|
737
|
+
{
|
|
738
|
+
geometry: square,
|
|
739
|
+
scale: previewSize / 2,
|
|
740
|
+
renderOrder: SECTION_RENDER_ORDER.outline,
|
|
741
|
+
raycast: () => null,
|
|
742
|
+
children: /* @__PURE__ */ jsx3(
|
|
743
|
+
"lineBasicMaterial",
|
|
744
|
+
{
|
|
745
|
+
color: theme.sectionOutline,
|
|
746
|
+
transparent: true,
|
|
747
|
+
opacity: 0.5,
|
|
748
|
+
depthTest: false,
|
|
749
|
+
depthWrite: false
|
|
750
|
+
}
|
|
751
|
+
)
|
|
752
|
+
}
|
|
753
|
+
)
|
|
754
|
+
] }),
|
|
755
|
+
planes.map((plane) => /* @__PURE__ */ jsxs3(
|
|
756
|
+
"group",
|
|
757
|
+
{
|
|
758
|
+
position: [plane.position.x, plane.position.y, plane.position.z],
|
|
759
|
+
rotation: AXIS_ROTATIONS[plane.axis],
|
|
760
|
+
children: [
|
|
761
|
+
/* @__PURE__ */ jsxs3("mesh", { renderOrder: SECTION_RENDER_ORDER.outline, ...planeHandlers(plane), children: [
|
|
762
|
+
/* @__PURE__ */ jsx3("planeGeometry", { args: [plane.size, plane.size] }),
|
|
763
|
+
/* @__PURE__ */ jsx3(
|
|
764
|
+
"meshBasicMaterial",
|
|
765
|
+
{
|
|
766
|
+
color: AXIS_COLORS[plane.axis],
|
|
767
|
+
transparent: true,
|
|
768
|
+
opacity: hoveredPlane === plane.axis ? 0.45 : 0.3,
|
|
769
|
+
side: DoubleSide,
|
|
770
|
+
depthWrite: false
|
|
771
|
+
}
|
|
772
|
+
)
|
|
773
|
+
] }),
|
|
774
|
+
/* @__PURE__ */ jsx3(
|
|
775
|
+
"lineLoop",
|
|
776
|
+
{
|
|
777
|
+
geometry: square,
|
|
778
|
+
scale: plane.size / 2,
|
|
779
|
+
renderOrder: SECTION_RENDER_ORDER.outline,
|
|
780
|
+
raycast: () => null,
|
|
781
|
+
children: /* @__PURE__ */ jsx3("lineBasicMaterial", { color: AXIS_COLORS[plane.axis], transparent: true, opacity: 0.7 })
|
|
782
|
+
}
|
|
783
|
+
)
|
|
784
|
+
]
|
|
785
|
+
},
|
|
786
|
+
plane.axis
|
|
787
|
+
))
|
|
788
|
+
] });
|
|
789
|
+
};
|
|
790
|
+
var CutSheet = ({ box, section, theme }) => {
|
|
791
|
+
const square = useSquare();
|
|
792
|
+
const cut = useMemo3(() => resolveSectionPlane(section, box), [box, section]);
|
|
793
|
+
const span = useMemo3(() => box.getSize(new Vector34()).length(), [box]);
|
|
794
|
+
const placement = useMemo3(() => {
|
|
795
|
+
if (!cut) return null;
|
|
796
|
+
const position = sectionAnchor(box, cut.plane).addScaledVector(
|
|
797
|
+
cut.plane.normal,
|
|
798
|
+
-span * SHEET_SETBACK
|
|
799
|
+
);
|
|
800
|
+
return { position, quaternion: new Quaternion2().setFromUnitVectors(Z, cut.plane.normal) };
|
|
801
|
+
}, [box, cut, span]);
|
|
802
|
+
if (!placement) return null;
|
|
803
|
+
const size = span * OUTLINE_SCALE;
|
|
804
|
+
return /* @__PURE__ */ jsxs3("group", { position: placement.position, quaternion: placement.quaternion, children: [
|
|
805
|
+
/* @__PURE__ */ jsxs3("mesh", { renderOrder: SECTION_RENDER_ORDER.outline, raycast: () => null, children: [
|
|
806
|
+
/* @__PURE__ */ jsx3("planeGeometry", { args: [size, size] }),
|
|
807
|
+
/* @__PURE__ */ jsx3(
|
|
808
|
+
"meshBasicMaterial",
|
|
809
|
+
{
|
|
810
|
+
color: theme.sectionOutline,
|
|
811
|
+
transparent: true,
|
|
812
|
+
opacity: 0.1,
|
|
813
|
+
side: DoubleSide,
|
|
814
|
+
depthWrite: false
|
|
815
|
+
}
|
|
816
|
+
)
|
|
817
|
+
] }),
|
|
818
|
+
/* @__PURE__ */ jsx3(
|
|
819
|
+
"lineLoop",
|
|
820
|
+
{
|
|
821
|
+
geometry: square,
|
|
822
|
+
scale: size / 2,
|
|
823
|
+
renderOrder: SECTION_RENDER_ORDER.outline,
|
|
824
|
+
raycast: () => null,
|
|
825
|
+
children: /* @__PURE__ */ jsx3(
|
|
826
|
+
"lineBasicMaterial",
|
|
827
|
+
{
|
|
828
|
+
color: theme.sectionOutline,
|
|
829
|
+
transparent: true,
|
|
830
|
+
opacity: 0.5,
|
|
831
|
+
depthTest: false,
|
|
832
|
+
depthWrite: false
|
|
833
|
+
}
|
|
834
|
+
)
|
|
835
|
+
}
|
|
836
|
+
)
|
|
837
|
+
] });
|
|
838
|
+
};
|
|
839
|
+
|
|
840
|
+
// src/measure-tool.tsx
|
|
841
|
+
import { Html, Line } from "@react-three/drei";
|
|
842
|
+
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";
|
|
844
|
+
import {
|
|
845
|
+
Raycaster as Raycaster2,
|
|
846
|
+
SphereGeometry,
|
|
847
|
+
Vector2 as Vector23,
|
|
848
|
+
Vector3 as Vector35
|
|
849
|
+
} from "three";
|
|
850
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
851
|
+
var FURNITURE4 = { [EXCLUDE_FROM_FRAME]: true };
|
|
852
|
+
var MEASURE_LABEL_CLASS = "toolpath-measure-label";
|
|
853
|
+
var MeasureTool = ({
|
|
854
|
+
mode = "distance",
|
|
855
|
+
measurements,
|
|
856
|
+
onChange,
|
|
857
|
+
showDeltas = true,
|
|
858
|
+
format = formatMillimetres,
|
|
859
|
+
labelClassName,
|
|
860
|
+
theme
|
|
861
|
+
}) => {
|
|
862
|
+
const store = useSectionStore();
|
|
863
|
+
const box = useContentBox();
|
|
864
|
+
const resolved = useMemo4(() => resolveTheme(theme), [theme]);
|
|
865
|
+
const [held, setHeld] = useState3([]);
|
|
866
|
+
const controlled = measurements !== void 0;
|
|
867
|
+
const list = controlled ? measurements : held;
|
|
868
|
+
const [draft, setDraft] = useState3([]);
|
|
869
|
+
useEffect3(() => {
|
|
870
|
+
store.setEngaged(true);
|
|
871
|
+
return () => store.setEngaged(false);
|
|
872
|
+
}, [store]);
|
|
873
|
+
useEffect3(() => {
|
|
874
|
+
setDraft([]);
|
|
875
|
+
}, [mode]);
|
|
876
|
+
const commit = (next) => {
|
|
877
|
+
if (!controlled) setHeld(next);
|
|
878
|
+
onChange?.(next);
|
|
879
|
+
};
|
|
880
|
+
const latest = useRef3({ list, commit });
|
|
881
|
+
latest.current = { list, commit };
|
|
882
|
+
useEffect3(() => {
|
|
883
|
+
const onKey = (event) => {
|
|
884
|
+
if (event.key === "Escape") {
|
|
885
|
+
setDraft([]);
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
if (event.key !== "Delete" && event.key !== "Backspace") return;
|
|
889
|
+
if (isEditing(event.target)) return;
|
|
890
|
+
const { list: current, commit: apply } = latest.current;
|
|
891
|
+
if (current.length === 0) return;
|
|
892
|
+
event.preventDefault();
|
|
893
|
+
apply(current.slice(0, -1));
|
|
894
|
+
};
|
|
895
|
+
window.addEventListener("keydown", onKey);
|
|
896
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
897
|
+
}, []);
|
|
898
|
+
const place = (point) => {
|
|
899
|
+
const points = [...draft, point];
|
|
900
|
+
const complete = measurementFromPoints(mode, points, nextMeasurementId(list));
|
|
901
|
+
if (complete) {
|
|
902
|
+
commit([...list, complete]);
|
|
903
|
+
setDraft([]);
|
|
904
|
+
} else {
|
|
905
|
+
setDraft(points);
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
if (box.isEmpty()) return null;
|
|
909
|
+
const label = className(labelClassName);
|
|
910
|
+
return /* @__PURE__ */ jsxs4("group", { userData: FURNITURE4, children: [
|
|
911
|
+
/* @__PURE__ */ jsx4(
|
|
912
|
+
Snapper,
|
|
913
|
+
{
|
|
914
|
+
mode,
|
|
915
|
+
draft,
|
|
916
|
+
onPlace: place,
|
|
917
|
+
theme: resolved,
|
|
918
|
+
format,
|
|
919
|
+
label
|
|
920
|
+
}
|
|
921
|
+
),
|
|
922
|
+
draft.map((point, index) => /* @__PURE__ */ jsx4(
|
|
923
|
+
ScreenDot,
|
|
924
|
+
{
|
|
925
|
+
point,
|
|
926
|
+
pixels: MEASURE_MARKER_PIXELS,
|
|
927
|
+
color: resolved.measure
|
|
928
|
+
},
|
|
929
|
+
index
|
|
930
|
+
)),
|
|
931
|
+
draft.length === 2 ? /* @__PURE__ */ jsx4(Segment, { points: [draft[0], draft[1]], color: resolved.measure }) : null,
|
|
932
|
+
list.map((measurement) => /* @__PURE__ */ jsx4(
|
|
933
|
+
MeasurementView,
|
|
934
|
+
{
|
|
935
|
+
measurement,
|
|
936
|
+
theme: resolved,
|
|
937
|
+
format,
|
|
938
|
+
showDeltas,
|
|
939
|
+
label
|
|
940
|
+
},
|
|
941
|
+
measurement.id
|
|
942
|
+
))
|
|
943
|
+
] });
|
|
944
|
+
};
|
|
945
|
+
var NO_POINTER = { pointerEvents: "none" };
|
|
946
|
+
var NOWHERE = [new Vector35(), new Vector35()];
|
|
947
|
+
var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
|
|
948
|
+
const camera = useThree4((state) => state.camera);
|
|
949
|
+
const scene = useThree4((state) => state.scene);
|
|
950
|
+
const size = useThree4((state) => state.size);
|
|
951
|
+
const domElement = useThree4((state) => state.gl.domElement);
|
|
952
|
+
const invalidate = useThree4((state) => state.invalidate);
|
|
953
|
+
const isTap = useTapGuard();
|
|
954
|
+
const marker = useRef3(null);
|
|
955
|
+
const edgeLine = useRef3(null);
|
|
956
|
+
const band = useRef3(null);
|
|
957
|
+
const bandLabel = useRef3(null);
|
|
958
|
+
const bandText = useRef3(null);
|
|
959
|
+
const snap = useRef3(null);
|
|
960
|
+
const dot = useDot();
|
|
961
|
+
useLayoutEffect(() => {
|
|
962
|
+
if (edgeLine.current) edgeLine.current.visible = false;
|
|
963
|
+
if (band.current) band.current.visible = false;
|
|
964
|
+
}, []);
|
|
965
|
+
const current = useRef3({ mode, draft, onPlace, format, size });
|
|
966
|
+
current.current = { mode, draft, onPlace, format, size };
|
|
967
|
+
useEffect3(() => {
|
|
968
|
+
const raycaster = new Raycaster2();
|
|
969
|
+
const pointer = new Vector23();
|
|
970
|
+
const hide = () => {
|
|
971
|
+
if (!snap.current) return;
|
|
972
|
+
snap.current = null;
|
|
973
|
+
if (marker.current) marker.current.visible = false;
|
|
974
|
+
if (edgeLine.current) edgeLine.current.visible = false;
|
|
975
|
+
if (band.current) band.current.visible = false;
|
|
976
|
+
if (bandLabel.current) bandLabel.current.visible = false;
|
|
977
|
+
if (bandText.current) bandText.current.style.display = "none";
|
|
978
|
+
invalidate();
|
|
979
|
+
};
|
|
980
|
+
let last = null;
|
|
981
|
+
const update = (clientX, clientY, shift) => {
|
|
982
|
+
last = { x: clientX, y: clientY };
|
|
983
|
+
const rect = domElement.getBoundingClientRect();
|
|
984
|
+
pointer.set(
|
|
985
|
+
(clientX - rect.left) / rect.width * 2 - 1,
|
|
986
|
+
-((clientY - rect.top) / rect.height) * 2 + 1
|
|
987
|
+
);
|
|
988
|
+
raycaster.setFromCamera(pointer, camera);
|
|
989
|
+
const hit = hitUnderRay(raycaster, scene);
|
|
990
|
+
if (!hit?.face) {
|
|
991
|
+
hide();
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
const normal = hit.face.normal.clone().transformDirection(hit.object.matrixWorld);
|
|
995
|
+
const { size: viewport, draft: placed, mode: kind, format: write } = current.current;
|
|
996
|
+
const snapped = snapAt(
|
|
997
|
+
{ point: hit.point, normal },
|
|
998
|
+
edgesBeside(hit.object),
|
|
999
|
+
camera,
|
|
1000
|
+
viewport
|
|
1001
|
+
);
|
|
1002
|
+
const from = placed[placed.length - 1];
|
|
1003
|
+
const lock = shift && from ? lockToAxis(from, snapped.point) : null;
|
|
1004
|
+
const found = lock ? { ...snapped, point: new Vector35(lock.point.x, lock.point.y, lock.point.z), edge: null } : snapped;
|
|
1005
|
+
snap.current = found;
|
|
1006
|
+
const node = marker.current;
|
|
1007
|
+
if (node) {
|
|
1008
|
+
node.position.copy(found.point);
|
|
1009
|
+
node.visible = true;
|
|
1010
|
+
node.scale.setScalar(
|
|
1011
|
+
screenLength(camera, found.point, viewport, SNAP_MARKER_PIXELS[found.kind])
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
if (edgeLine.current) {
|
|
1015
|
+
edgeLine.current.visible = found.edge !== null;
|
|
1016
|
+
if (found.edge) moveLine(edgeLine.current, found.edge[0], found.edge[1]);
|
|
1017
|
+
}
|
|
1018
|
+
const text = liveLabel(kind, placed, found.point, write);
|
|
1019
|
+
if (band.current) {
|
|
1020
|
+
band.current.visible = from !== void 0;
|
|
1021
|
+
if (from) moveLine(band.current, from, found.point);
|
|
1022
|
+
setLineColor(band.current, lock ? AXIS_COLORS[lock.axis] : theme.measure);
|
|
1023
|
+
}
|
|
1024
|
+
if (bandLabel.current && from) {
|
|
1025
|
+
const at = midpoint(from, found.point);
|
|
1026
|
+
bandLabel.current.position.set(at.x, at.y, at.z);
|
|
1027
|
+
bandLabel.current.visible = text !== null;
|
|
1028
|
+
}
|
|
1029
|
+
if (bandText.current) {
|
|
1030
|
+
bandText.current.style.display = text === null ? "none" : "";
|
|
1031
|
+
bandText.current.textContent = text ?? "";
|
|
1032
|
+
}
|
|
1033
|
+
invalidate();
|
|
1034
|
+
};
|
|
1035
|
+
const move = (event) => {
|
|
1036
|
+
if (event.buttons !== 0) {
|
|
1037
|
+
hide();
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
update(event.clientX, event.clientY, event.shiftKey);
|
|
1041
|
+
};
|
|
1042
|
+
const key = (event) => {
|
|
1043
|
+
if (event.key !== "Shift" || !last || !snap.current) return;
|
|
1044
|
+
update(last.x, last.y, event.type === "keydown");
|
|
1045
|
+
};
|
|
1046
|
+
const up = (event) => {
|
|
1047
|
+
if (event.button !== 0 || !isTap(event)) return;
|
|
1048
|
+
update(event.clientX, event.clientY, event.shiftKey);
|
|
1049
|
+
const found = snap.current;
|
|
1050
|
+
if (!found) return;
|
|
1051
|
+
current.current.onPlace({ x: found.point.x, y: found.point.y, z: found.point.z });
|
|
1052
|
+
};
|
|
1053
|
+
const leave = () => {
|
|
1054
|
+
last = null;
|
|
1055
|
+
hide();
|
|
1056
|
+
};
|
|
1057
|
+
domElement.addEventListener("pointermove", move);
|
|
1058
|
+
domElement.addEventListener("pointerleave", leave);
|
|
1059
|
+
domElement.addEventListener("pointerup", up);
|
|
1060
|
+
window.addEventListener("keydown", key);
|
|
1061
|
+
window.addEventListener("keyup", key);
|
|
1062
|
+
return () => {
|
|
1063
|
+
domElement.removeEventListener("pointermove", move);
|
|
1064
|
+
domElement.removeEventListener("pointerleave", leave);
|
|
1065
|
+
domElement.removeEventListener("pointerup", up);
|
|
1066
|
+
window.removeEventListener("keydown", key);
|
|
1067
|
+
window.removeEventListener("keyup", key);
|
|
1068
|
+
};
|
|
1069
|
+
}, [camera, domElement, invalidate, isTap, scene, theme.measure]);
|
|
1070
|
+
useEffect3(() => {
|
|
1071
|
+
if (draft.length === 0 && band.current) {
|
|
1072
|
+
band.current.visible = false;
|
|
1073
|
+
if (bandLabel.current) bandLabel.current.visible = false;
|
|
1074
|
+
if (bandText.current) {
|
|
1075
|
+
bandText.current.style.display = "none";
|
|
1076
|
+
bandText.current.textContent = "";
|
|
1077
|
+
}
|
|
1078
|
+
invalidate();
|
|
1079
|
+
}
|
|
1080
|
+
}, [draft, invalidate]);
|
|
1081
|
+
return /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
1082
|
+
/* @__PURE__ */ jsx4("group", { ref: marker, visible: false, children: /* @__PURE__ */ jsx4("mesh", { geometry: dot, renderOrder: MEASURE_RENDER_ORDER, raycast: () => null, children: /* @__PURE__ */ jsx4("meshBasicMaterial", { color: theme.measureSnap, depthTest: false, depthWrite: false }) }) }),
|
|
1083
|
+
/* @__PURE__ */ jsx4(
|
|
1084
|
+
Line,
|
|
1085
|
+
{
|
|
1086
|
+
ref: edgeLine,
|
|
1087
|
+
points: NOWHERE,
|
|
1088
|
+
color: theme.measureSnap,
|
|
1089
|
+
lineWidth: MEASURE_LINE_PIXELS.edge,
|
|
1090
|
+
depthTest: false,
|
|
1091
|
+
depthWrite: false,
|
|
1092
|
+
frustumCulled: false,
|
|
1093
|
+
renderOrder: MEASURE_RENDER_ORDER,
|
|
1094
|
+
raycast: () => null
|
|
1095
|
+
}
|
|
1096
|
+
),
|
|
1097
|
+
/* @__PURE__ */ jsx4(
|
|
1098
|
+
Line,
|
|
1099
|
+
{
|
|
1100
|
+
ref: band,
|
|
1101
|
+
points: NOWHERE,
|
|
1102
|
+
color: theme.measure,
|
|
1103
|
+
lineWidth: MEASURE_LINE_PIXELS.line,
|
|
1104
|
+
depthTest: false,
|
|
1105
|
+
depthWrite: false,
|
|
1106
|
+
frustumCulled: false,
|
|
1107
|
+
renderOrder: MEASURE_RENDER_ORDER,
|
|
1108
|
+
raycast: () => null
|
|
1109
|
+
}
|
|
1110
|
+
),
|
|
1111
|
+
/* @__PURE__ */ jsx4("group", { ref: bandLabel, visible: false, children: /* @__PURE__ */ jsx4(Html, { center: true, zIndexRange: [0, 0], style: NO_POINTER, children: /* @__PURE__ */ jsx4(
|
|
1112
|
+
"div",
|
|
1113
|
+
{
|
|
1114
|
+
ref: bandText,
|
|
1115
|
+
className: label,
|
|
1116
|
+
"data-measure-label": "live",
|
|
1117
|
+
style: { display: "none" }
|
|
1118
|
+
}
|
|
1119
|
+
) }) })
|
|
1120
|
+
] });
|
|
1121
|
+
};
|
|
1122
|
+
function moveLine(line, from, to) {
|
|
1123
|
+
const start = line.geometry.getAttribute("instanceStart");
|
|
1124
|
+
const array = start.data.array;
|
|
1125
|
+
array[0] = from.x;
|
|
1126
|
+
array[1] = from.y;
|
|
1127
|
+
array[2] = from.z;
|
|
1128
|
+
array[3] = to.x;
|
|
1129
|
+
array[4] = to.y;
|
|
1130
|
+
array[5] = to.z;
|
|
1131
|
+
start.data.needsUpdate = true;
|
|
1132
|
+
line.computeLineDistances();
|
|
1133
|
+
}
|
|
1134
|
+
function setLineColor(line, color) {
|
|
1135
|
+
const material = line.material;
|
|
1136
|
+
if (material.color.getHex() !== color) material.color.setHex(color);
|
|
1137
|
+
}
|
|
1138
|
+
function liveLabel(mode, placed, at, format) {
|
|
1139
|
+
if (mode === "distance" && placed.length === 1) {
|
|
1140
|
+
return format(distanceBetween(placed[0], at));
|
|
1141
|
+
}
|
|
1142
|
+
if (mode === "angle" && placed.length === 2) {
|
|
1143
|
+
return formatDegrees(angleAt(placed[1], placed[0], at));
|
|
1144
|
+
}
|
|
1145
|
+
return null;
|
|
1146
|
+
}
|
|
1147
|
+
function edgesBeside(object) {
|
|
1148
|
+
const siblings = object.parent?.children ?? [];
|
|
1149
|
+
for (const sibling of siblings) {
|
|
1150
|
+
if (!("isLineSegments" in sibling)) continue;
|
|
1151
|
+
const geometry = sibling.geometry;
|
|
1152
|
+
const positions = geometry.getAttribute("position");
|
|
1153
|
+
if (!positions) continue;
|
|
1154
|
+
return { positions: positions.array, matrixWorld: sibling.matrixWorld };
|
|
1155
|
+
}
|
|
1156
|
+
return null;
|
|
1157
|
+
}
|
|
1158
|
+
var MeasurementView = ({
|
|
1159
|
+
measurement,
|
|
1160
|
+
theme,
|
|
1161
|
+
format,
|
|
1162
|
+
showDeltas,
|
|
1163
|
+
label
|
|
1164
|
+
}) => {
|
|
1165
|
+
const text = measurementLabel(measurement, format);
|
|
1166
|
+
const anchor = measurementLabelAnchor(measurement);
|
|
1167
|
+
if (measurement.kind === "distance") {
|
|
1168
|
+
const [a2, b2] = measurement.points;
|
|
1169
|
+
return /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
1170
|
+
/* @__PURE__ */ jsx4(Segment, { points: [a2, b2], color: theme.measure }),
|
|
1171
|
+
/* @__PURE__ */ jsx4(ScreenDot, { point: a2, pixels: MEASURE_MARKER_PIXELS, color: theme.measure }),
|
|
1172
|
+
/* @__PURE__ */ jsx4(ScreenDot, { point: b2, pixels: MEASURE_MARKER_PIXELS, color: theme.measure }),
|
|
1173
|
+
showDeltas ? /* @__PURE__ */ jsx4(DeltaLegs, { a: a2, b: b2, id: measurement.id, format, label }) : null,
|
|
1174
|
+
/* @__PURE__ */ jsx4(Label, { at: anchor, text, kind: "distance", id: measurement.id, className: label })
|
|
1175
|
+
] });
|
|
1176
|
+
}
|
|
1177
|
+
const [a, vertex, b] = measurement.points;
|
|
1178
|
+
const arc = angleArc(vertex, a, b, angleArcRadius(vertex, a, b));
|
|
1179
|
+
return /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
1180
|
+
/* @__PURE__ */ jsx4(Segment, { points: [vertex, a], color: theme.measure }),
|
|
1181
|
+
/* @__PURE__ */ jsx4(Segment, { points: [vertex, b], color: theme.measure }),
|
|
1182
|
+
/* @__PURE__ */ jsx4(Polyline, { points: arc, color: theme.measure }),
|
|
1183
|
+
/* @__PURE__ */ jsx4(ScreenDot, { point: a, pixels: MEASURE_MARKER_PIXELS, color: theme.measure }),
|
|
1184
|
+
/* @__PURE__ */ jsx4(ScreenDot, { point: vertex, pixels: MEASURE_MARKER_PIXELS, color: theme.measure }),
|
|
1185
|
+
/* @__PURE__ */ jsx4(ScreenDot, { point: b, pixels: MEASURE_MARKER_PIXELS, color: theme.measure }),
|
|
1186
|
+
/* @__PURE__ */ jsx4(Label, { at: anchor, text, kind: "angle", id: measurement.id, className: label })
|
|
1187
|
+
] });
|
|
1188
|
+
};
|
|
1189
|
+
var DeltaLegs = ({ a, b, id, format, label }) => {
|
|
1190
|
+
const camera = useThree4((state) => state.camera);
|
|
1191
|
+
const size = useThree4((state) => state.size);
|
|
1192
|
+
const invalidate = useThree4((state) => state.invalidate);
|
|
1193
|
+
const legs = useMemo4(() => deltaLegs(a, b), [a, b]);
|
|
1194
|
+
const lines = useRef3([]);
|
|
1195
|
+
const centre = useMemo4(() => {
|
|
1196
|
+
const at = midpoint(a, b);
|
|
1197
|
+
return new Vector35(at.x, at.y, at.z);
|
|
1198
|
+
}, [a, b]);
|
|
1199
|
+
useFrame2(() => {
|
|
1200
|
+
const dash = screenLength(camera, centre, size, DELTA_DASH_PIXELS);
|
|
1201
|
+
let changed = false;
|
|
1202
|
+
for (const line of lines.current) {
|
|
1203
|
+
const material = line?.material;
|
|
1204
|
+
if (!material || Math.abs(material.dashSize - dash) <= dash * 1e-3) continue;
|
|
1205
|
+
material.dashSize = dash;
|
|
1206
|
+
material.gapSize = dash;
|
|
1207
|
+
changed = true;
|
|
1208
|
+
}
|
|
1209
|
+
if (changed) invalidate();
|
|
1210
|
+
});
|
|
1211
|
+
return /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
1212
|
+
legs.map((leg, index) => /* @__PURE__ */ jsx4(
|
|
1213
|
+
Line,
|
|
1214
|
+
{
|
|
1215
|
+
ref: (line) => {
|
|
1216
|
+
lines.current[index] = line;
|
|
1217
|
+
},
|
|
1218
|
+
points: [
|
|
1219
|
+
new Vector35(leg.from.x, leg.from.y, leg.from.z),
|
|
1220
|
+
new Vector35(leg.to.x, leg.to.y, leg.to.z)
|
|
1221
|
+
],
|
|
1222
|
+
color: AXIS_COLORS[leg.axis],
|
|
1223
|
+
lineWidth: MEASURE_LINE_PIXELS.leg,
|
|
1224
|
+
dashed: true,
|
|
1225
|
+
dashSize: 1,
|
|
1226
|
+
gapSize: 1,
|
|
1227
|
+
depthTest: false,
|
|
1228
|
+
depthWrite: false,
|
|
1229
|
+
frustumCulled: false,
|
|
1230
|
+
renderOrder: MEASURE_RENDER_ORDER,
|
|
1231
|
+
raycast: () => null
|
|
1232
|
+
},
|
|
1233
|
+
leg.axis
|
|
1234
|
+
)),
|
|
1235
|
+
legs.map((leg) => /* @__PURE__ */ jsx4(
|
|
1236
|
+
Label,
|
|
1237
|
+
{
|
|
1238
|
+
at: midpoint(leg.from, leg.to),
|
|
1239
|
+
text: `${leg.axis.toUpperCase()} ${format(leg.length)}`,
|
|
1240
|
+
kind: "delta",
|
|
1241
|
+
axis: leg.axis,
|
|
1242
|
+
id,
|
|
1243
|
+
className: label
|
|
1244
|
+
},
|
|
1245
|
+
leg.axis
|
|
1246
|
+
))
|
|
1247
|
+
] });
|
|
1248
|
+
};
|
|
1249
|
+
var Label = ({ at, text, kind, id, axis, className: name }) => /* @__PURE__ */ jsx4("group", { position: [at.x, at.y, at.z], children: /* @__PURE__ */ jsx4(Html, { center: true, zIndexRange: [0, 0], style: NO_POINTER, children: /* @__PURE__ */ jsx4("div", { className: name, "data-measure-label": kind, "data-measurement-id": id, "data-axis": axis, children: text }) }) });
|
|
1250
|
+
var ScreenDot = ({ point, pixels, color }) => {
|
|
1251
|
+
const camera = useThree4((state) => state.camera);
|
|
1252
|
+
const size = useThree4((state) => state.size);
|
|
1253
|
+
const invalidate = useThree4((state) => state.invalidate);
|
|
1254
|
+
const node = useRef3(null);
|
|
1255
|
+
const dot = useDot();
|
|
1256
|
+
const position = useMemo4(() => new Vector35(point.x, point.y, point.z), [point]);
|
|
1257
|
+
useFrame2(() => {
|
|
1258
|
+
const group = node.current;
|
|
1259
|
+
if (!group) return;
|
|
1260
|
+
const length = screenLength(camera, position, size, pixels);
|
|
1261
|
+
if (Math.abs(group.scale.x - length) > length * 1e-3) {
|
|
1262
|
+
group.scale.setScalar(length);
|
|
1263
|
+
invalidate();
|
|
1264
|
+
}
|
|
1265
|
+
});
|
|
1266
|
+
return /* @__PURE__ */ jsx4("group", { ref: node, position, children: /* @__PURE__ */ jsx4("mesh", { geometry: dot, renderOrder: MEASURE_RENDER_ORDER, raycast: () => null, children: /* @__PURE__ */ jsx4("meshBasicMaterial", { color, depthTest: false, depthWrite: false }) }) });
|
|
1267
|
+
};
|
|
1268
|
+
var Segment = ({ points, color }) => {
|
|
1269
|
+
const [a, b] = points;
|
|
1270
|
+
const ends = useMemo4(
|
|
1271
|
+
() => [new Vector35(a.x, a.y, a.z), new Vector35(b.x, b.y, b.z)],
|
|
1272
|
+
// Keyed on the numbers: a draft's tuple is a fresh array on every render,
|
|
1273
|
+
// and a rebuilt array would rebuild the line's geometry with it.
|
|
1274
|
+
[a.x, a.y, a.z, b.x, b.y, b.z]
|
|
1275
|
+
);
|
|
1276
|
+
return /* @__PURE__ */ jsx4(Polyline, { points: ends, color });
|
|
1277
|
+
};
|
|
1278
|
+
var Polyline = ({ points, color }) => {
|
|
1279
|
+
if (points.length < 2) return null;
|
|
1280
|
+
return /* @__PURE__ */ jsx4(
|
|
1281
|
+
Line,
|
|
1282
|
+
{
|
|
1283
|
+
points,
|
|
1284
|
+
color,
|
|
1285
|
+
lineWidth: MEASURE_LINE_PIXELS.line,
|
|
1286
|
+
depthTest: false,
|
|
1287
|
+
depthWrite: false,
|
|
1288
|
+
frustumCulled: false,
|
|
1289
|
+
renderOrder: MEASURE_RENDER_ORDER,
|
|
1290
|
+
raycast: () => null
|
|
1291
|
+
}
|
|
1292
|
+
);
|
|
1293
|
+
};
|
|
1294
|
+
function useDot() {
|
|
1295
|
+
const geometry = useMemo4(() => new SphereGeometry(1, 16, 12), []);
|
|
1296
|
+
useEffect3(() => () => geometry.dispose(), [geometry]);
|
|
1297
|
+
return geometry;
|
|
1298
|
+
}
|
|
1299
|
+
function isEditing(target) {
|
|
1300
|
+
if (!(target instanceof HTMLElement)) return false;
|
|
1301
|
+
return target.isContentEditable || target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement;
|
|
1302
|
+
}
|
|
1303
|
+
function className(extra) {
|
|
1304
|
+
return extra ? `${MEASURE_LABEL_CLASS} ${extra}` : MEASURE_LABEL_CLASS;
|
|
1305
|
+
}
|
|
1306
|
+
|
|
357
1307
|
// src/model/normals.ts
|
|
358
1308
|
function regionNormals(geometry, regions) {
|
|
359
1309
|
const position = geometry.getAttribute("position");
|
|
@@ -388,14 +1338,23 @@ function regionNormals(geometry, regions) {
|
|
|
388
1338
|
return normals;
|
|
389
1339
|
}
|
|
390
1340
|
export {
|
|
1341
|
+
ANGLE_ARC_FRACTION,
|
|
1342
|
+
ANGLE_ARC_SEGMENTS,
|
|
1343
|
+
AXES_PLANE_OFFSET,
|
|
1344
|
+
AXES_PLANE_SCALE,
|
|
1345
|
+
AXIS_COLORS,
|
|
391
1346
|
Axes,
|
|
392
1347
|
CAD_CAMERA_UP,
|
|
393
1348
|
CANDIDATE_WEIGHT,
|
|
1349
|
+
CAP_HATCH,
|
|
394
1350
|
CHAMFER,
|
|
395
1351
|
CadCameraControls,
|
|
396
1352
|
DEFAULT_FIT_MARGIN,
|
|
1353
|
+
DEFAULT_SECTION_NORMAL,
|
|
397
1354
|
DEFAULT_THEME,
|
|
1355
|
+
DELTA_DASH_PIXELS,
|
|
398
1356
|
DIRECTION_COLORS,
|
|
1357
|
+
DISABLED_SECTION,
|
|
399
1358
|
DOUBLE_TAP_MS,
|
|
400
1359
|
DirectionArrows,
|
|
401
1360
|
EXCLUDE_FROM_FRAME,
|
|
@@ -408,7 +1367,12 @@ export {
|
|
|
408
1367
|
HIGHLIGHT_WEIGHT,
|
|
409
1368
|
HOVER_WEIGHT,
|
|
410
1369
|
MAX_FRAME_RATIO,
|
|
1370
|
+
MEASURE_LABEL_CLASS,
|
|
1371
|
+
MEASURE_LINE_PIXELS,
|
|
1372
|
+
MEASURE_MARKER_PIXELS,
|
|
1373
|
+
MEASURE_RENDER_ORDER,
|
|
411
1374
|
MIN_FRAME_RATIO,
|
|
1375
|
+
MeasureTool,
|
|
412
1376
|
NO_MODIFIERS,
|
|
413
1377
|
ORBIT_TARGET_COLOR,
|
|
414
1378
|
ORBIT_TARGET_FADE_MS,
|
|
@@ -418,12 +1382,18 @@ export {
|
|
|
418
1382
|
ORBIT_TARGET_RING_OPACITY,
|
|
419
1383
|
ORBIT_TARGET_RING_PIXELS,
|
|
420
1384
|
ORBIT_TARGET_RING_WIDTH,
|
|
1385
|
+
OUTLINE_SCALE,
|
|
421
1386
|
PERSPECTIVE_FOV,
|
|
422
1387
|
PICKED_SURFACE_LABEL,
|
|
1388
|
+
POINTS_PER_MEASUREMENT,
|
|
1389
|
+
PREVIEW_SCALE,
|
|
423
1390
|
PartMesh,
|
|
424
1391
|
PartReportFormatError,
|
|
425
1392
|
REGION_ATTRIBUTE,
|
|
426
1393
|
SECTION_RENDER_ORDER,
|
|
1394
|
+
SNAP_MARKER_PIXELS,
|
|
1395
|
+
SNAP_PIXELS,
|
|
1396
|
+
SectionTool,
|
|
427
1397
|
SectionView,
|
|
428
1398
|
TAP_SLOP,
|
|
429
1399
|
UnsupportedKernelVersionError,
|
|
@@ -432,10 +1402,15 @@ export {
|
|
|
432
1402
|
ViewCube,
|
|
433
1403
|
Viewer,
|
|
434
1404
|
adaptedUp,
|
|
1405
|
+
angleArc,
|
|
1406
|
+
angleArcRadius,
|
|
1407
|
+
angleAt,
|
|
1408
|
+
applyCapTheme,
|
|
435
1409
|
applyHighlightLayers,
|
|
436
1410
|
applyProjection,
|
|
437
1411
|
arrowPlacement,
|
|
438
1412
|
aspectRatio,
|
|
1413
|
+
axesPlanes,
|
|
439
1414
|
bestOwner,
|
|
440
1415
|
boundsFromBox,
|
|
441
1416
|
buildPick,
|
|
@@ -445,25 +1420,43 @@ export {
|
|
|
445
1420
|
cadViewDirections,
|
|
446
1421
|
cameraLimits,
|
|
447
1422
|
contentBounds,
|
|
1423
|
+
createCapMaterial,
|
|
1424
|
+
createMaskMaterial,
|
|
1425
|
+
createMaskTarget,
|
|
448
1426
|
createPart,
|
|
1427
|
+
createSectionStore,
|
|
1428
|
+
createStencilMaterials,
|
|
449
1429
|
cubeOutlineGeometry,
|
|
450
1430
|
cubeZones,
|
|
451
1431
|
currentViewDirection,
|
|
452
1432
|
cycleOwner,
|
|
453
1433
|
defaultBounds,
|
|
1434
|
+
deltaBetween,
|
|
1435
|
+
deltaLegs,
|
|
454
1436
|
directionColor,
|
|
455
1437
|
directionIndexOf,
|
|
456
1438
|
directionLabel,
|
|
1439
|
+
distanceBetween,
|
|
457
1440
|
dragPlane,
|
|
1441
|
+
excludedFromFrame,
|
|
458
1442
|
featureTypeRank,
|
|
459
1443
|
fitDistance,
|
|
460
1444
|
focusForPick,
|
|
1445
|
+
formatDegrees,
|
|
1446
|
+
formatMillimetres,
|
|
461
1447
|
gridGeometry,
|
|
462
1448
|
gridSpec,
|
|
463
1449
|
groupByDirection,
|
|
1450
|
+
hitUnderRay,
|
|
464
1451
|
labelGeometry,
|
|
465
1452
|
labelTexture,
|
|
1453
|
+
lockToAxis,
|
|
1454
|
+
measurementFromPoints,
|
|
1455
|
+
measurementLabel,
|
|
1456
|
+
measurementLabelAnchor,
|
|
1457
|
+
midpoint,
|
|
466
1458
|
movedFar,
|
|
1459
|
+
nextMeasurementId,
|
|
467
1460
|
normalizePartReport,
|
|
468
1461
|
orbitTargetOpacity,
|
|
469
1462
|
orthographicHalfHeight,
|
|
@@ -479,6 +1472,8 @@ export {
|
|
|
479
1472
|
retargetPose,
|
|
480
1473
|
sameDirection,
|
|
481
1474
|
screenLength,
|
|
1475
|
+
screenPoint,
|
|
1476
|
+
sectionAnchor,
|
|
482
1477
|
sectionBounds,
|
|
483
1478
|
sectionConstant,
|
|
484
1479
|
sectionDepth,
|
|
@@ -486,16 +1481,20 @@ export {
|
|
|
486
1481
|
sectionDepthRange,
|
|
487
1482
|
sectionFromPick,
|
|
488
1483
|
sectionOffset,
|
|
1484
|
+
sectionOptionsFromState,
|
|
489
1485
|
sectionPlane,
|
|
490
1486
|
smoothRegionNormals,
|
|
1487
|
+
snapAt,
|
|
491
1488
|
squaredUp,
|
|
492
1489
|
startPosition,
|
|
1490
|
+
surfaceUnderRay,
|
|
493
1491
|
targetBoundary,
|
|
494
1492
|
themesEqual,
|
|
495
1493
|
trackDoubleTaps,
|
|
496
1494
|
trackTaps,
|
|
497
1495
|
useContentBox,
|
|
498
1496
|
useRetarget,
|
|
1497
|
+
useSectionStore,
|
|
499
1498
|
useTapGuard,
|
|
500
1499
|
useViewerControls,
|
|
501
1500
|
viewDirection,
|