@toolpath/viewer 1.1.0 → 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 +620 -191
- package/dist/index.js +1015 -13
- 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";
|
|
@@ -155,6 +175,18 @@ function gridGeometry(spec) {
|
|
|
155
175
|
geometry.setAttribute("position", new Float32BufferAttribute(positions, 3));
|
|
156
176
|
return geometry;
|
|
157
177
|
}
|
|
178
|
+
function gridFor(box, options = {}) {
|
|
179
|
+
if (box.isEmpty()) return null;
|
|
180
|
+
const spec = gridSpec(box);
|
|
181
|
+
const cell = options.step ?? spec.step;
|
|
182
|
+
return gridGeometry({
|
|
183
|
+
...spec,
|
|
184
|
+
step: cell,
|
|
185
|
+
// Snapped outwards to a whole number of cells, so the part sits inside the
|
|
186
|
+
// grid rather than ending part-way through a square.
|
|
187
|
+
extent: options.extent ?? Math.ceil(spec.extent / cell) * cell
|
|
188
|
+
});
|
|
189
|
+
}
|
|
158
190
|
|
|
159
191
|
// src/primitives.tsx
|
|
160
192
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -162,18 +194,9 @@ var FURNITURE = { [EXCLUDE_FROM_FRAME]: true };
|
|
|
162
194
|
var Grid = ({ step, extent, color, opacity = 0.35 }) => {
|
|
163
195
|
const theme = resolveTheme();
|
|
164
196
|
const box = useContentBox();
|
|
165
|
-
const geometry = useMemo(() => {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
return gridGeometry({
|
|
169
|
-
...spec,
|
|
170
|
-
step: cell,
|
|
171
|
-
// Snapped outwards to a whole number of cells, so the part sits inside
|
|
172
|
-
// the grid rather than ending part-way through a square.
|
|
173
|
-
extent: extent ?? Math.ceil(spec.extent / cell) * cell
|
|
174
|
-
});
|
|
175
|
-
}, [box, extent, step]);
|
|
176
|
-
useEffect(() => () => geometry.dispose(), [geometry]);
|
|
197
|
+
const geometry = useMemo(() => gridFor(box, { step, extent }), [box, extent, step]);
|
|
198
|
+
useEffect(() => () => geometry?.dispose(), [geometry]);
|
|
199
|
+
if (!geometry) return null;
|
|
177
200
|
return /* @__PURE__ */ jsx("lineSegments", { geometry, renderOrder: -1, raycast: () => null, userData: FURNITURE, children: /* @__PURE__ */ jsx("lineBasicMaterial", { color: color ?? theme.cubeEdge, transparent: true, opacity }) });
|
|
178
201
|
};
|
|
179
202
|
var Axes = ({ size = 25 }) => /* @__PURE__ */ jsx("axesHelper", { args: [size], userData: FURNITURE, raycast: () => null });
|
|
@@ -351,6 +374,936 @@ var Arrow = ({ direction, box, color, opacity, onPick, onTop = false }) => {
|
|
|
351
374
|
] });
|
|
352
375
|
};
|
|
353
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
|
+
|
|
354
1307
|
// src/model/normals.ts
|
|
355
1308
|
function regionNormals(geometry, regions) {
|
|
356
1309
|
const position = geometry.getAttribute("position");
|
|
@@ -385,14 +1338,23 @@ function regionNormals(geometry, regions) {
|
|
|
385
1338
|
return normals;
|
|
386
1339
|
}
|
|
387
1340
|
export {
|
|
1341
|
+
ANGLE_ARC_FRACTION,
|
|
1342
|
+
ANGLE_ARC_SEGMENTS,
|
|
1343
|
+
AXES_PLANE_OFFSET,
|
|
1344
|
+
AXES_PLANE_SCALE,
|
|
1345
|
+
AXIS_COLORS,
|
|
388
1346
|
Axes,
|
|
389
1347
|
CAD_CAMERA_UP,
|
|
390
1348
|
CANDIDATE_WEIGHT,
|
|
1349
|
+
CAP_HATCH,
|
|
391
1350
|
CHAMFER,
|
|
392
1351
|
CadCameraControls,
|
|
393
1352
|
DEFAULT_FIT_MARGIN,
|
|
1353
|
+
DEFAULT_SECTION_NORMAL,
|
|
394
1354
|
DEFAULT_THEME,
|
|
1355
|
+
DELTA_DASH_PIXELS,
|
|
395
1356
|
DIRECTION_COLORS,
|
|
1357
|
+
DISABLED_SECTION,
|
|
396
1358
|
DOUBLE_TAP_MS,
|
|
397
1359
|
DirectionArrows,
|
|
398
1360
|
EXCLUDE_FROM_FRAME,
|
|
@@ -405,7 +1367,12 @@ export {
|
|
|
405
1367
|
HIGHLIGHT_WEIGHT,
|
|
406
1368
|
HOVER_WEIGHT,
|
|
407
1369
|
MAX_FRAME_RATIO,
|
|
1370
|
+
MEASURE_LABEL_CLASS,
|
|
1371
|
+
MEASURE_LINE_PIXELS,
|
|
1372
|
+
MEASURE_MARKER_PIXELS,
|
|
1373
|
+
MEASURE_RENDER_ORDER,
|
|
408
1374
|
MIN_FRAME_RATIO,
|
|
1375
|
+
MeasureTool,
|
|
409
1376
|
NO_MODIFIERS,
|
|
410
1377
|
ORBIT_TARGET_COLOR,
|
|
411
1378
|
ORBIT_TARGET_FADE_MS,
|
|
@@ -415,12 +1382,18 @@ export {
|
|
|
415
1382
|
ORBIT_TARGET_RING_OPACITY,
|
|
416
1383
|
ORBIT_TARGET_RING_PIXELS,
|
|
417
1384
|
ORBIT_TARGET_RING_WIDTH,
|
|
1385
|
+
OUTLINE_SCALE,
|
|
418
1386
|
PERSPECTIVE_FOV,
|
|
419
1387
|
PICKED_SURFACE_LABEL,
|
|
1388
|
+
POINTS_PER_MEASUREMENT,
|
|
1389
|
+
PREVIEW_SCALE,
|
|
420
1390
|
PartMesh,
|
|
421
1391
|
PartReportFormatError,
|
|
422
1392
|
REGION_ATTRIBUTE,
|
|
423
1393
|
SECTION_RENDER_ORDER,
|
|
1394
|
+
SNAP_MARKER_PIXELS,
|
|
1395
|
+
SNAP_PIXELS,
|
|
1396
|
+
SectionTool,
|
|
424
1397
|
SectionView,
|
|
425
1398
|
TAP_SLOP,
|
|
426
1399
|
UnsupportedKernelVersionError,
|
|
@@ -429,10 +1402,15 @@ export {
|
|
|
429
1402
|
ViewCube,
|
|
430
1403
|
Viewer,
|
|
431
1404
|
adaptedUp,
|
|
1405
|
+
angleArc,
|
|
1406
|
+
angleArcRadius,
|
|
1407
|
+
angleAt,
|
|
1408
|
+
applyCapTheme,
|
|
432
1409
|
applyHighlightLayers,
|
|
433
1410
|
applyProjection,
|
|
434
1411
|
arrowPlacement,
|
|
435
1412
|
aspectRatio,
|
|
1413
|
+
axesPlanes,
|
|
436
1414
|
bestOwner,
|
|
437
1415
|
boundsFromBox,
|
|
438
1416
|
buildPick,
|
|
@@ -442,25 +1420,43 @@ export {
|
|
|
442
1420
|
cadViewDirections,
|
|
443
1421
|
cameraLimits,
|
|
444
1422
|
contentBounds,
|
|
1423
|
+
createCapMaterial,
|
|
1424
|
+
createMaskMaterial,
|
|
1425
|
+
createMaskTarget,
|
|
445
1426
|
createPart,
|
|
1427
|
+
createSectionStore,
|
|
1428
|
+
createStencilMaterials,
|
|
446
1429
|
cubeOutlineGeometry,
|
|
447
1430
|
cubeZones,
|
|
448
1431
|
currentViewDirection,
|
|
449
1432
|
cycleOwner,
|
|
450
1433
|
defaultBounds,
|
|
1434
|
+
deltaBetween,
|
|
1435
|
+
deltaLegs,
|
|
451
1436
|
directionColor,
|
|
452
1437
|
directionIndexOf,
|
|
453
1438
|
directionLabel,
|
|
1439
|
+
distanceBetween,
|
|
454
1440
|
dragPlane,
|
|
1441
|
+
excludedFromFrame,
|
|
455
1442
|
featureTypeRank,
|
|
456
1443
|
fitDistance,
|
|
457
1444
|
focusForPick,
|
|
1445
|
+
formatDegrees,
|
|
1446
|
+
formatMillimetres,
|
|
458
1447
|
gridGeometry,
|
|
459
1448
|
gridSpec,
|
|
460
1449
|
groupByDirection,
|
|
1450
|
+
hitUnderRay,
|
|
461
1451
|
labelGeometry,
|
|
462
1452
|
labelTexture,
|
|
1453
|
+
lockToAxis,
|
|
1454
|
+
measurementFromPoints,
|
|
1455
|
+
measurementLabel,
|
|
1456
|
+
measurementLabelAnchor,
|
|
1457
|
+
midpoint,
|
|
463
1458
|
movedFar,
|
|
1459
|
+
nextMeasurementId,
|
|
464
1460
|
normalizePartReport,
|
|
465
1461
|
orbitTargetOpacity,
|
|
466
1462
|
orthographicHalfHeight,
|
|
@@ -476,6 +1472,8 @@ export {
|
|
|
476
1472
|
retargetPose,
|
|
477
1473
|
sameDirection,
|
|
478
1474
|
screenLength,
|
|
1475
|
+
screenPoint,
|
|
1476
|
+
sectionAnchor,
|
|
479
1477
|
sectionBounds,
|
|
480
1478
|
sectionConstant,
|
|
481
1479
|
sectionDepth,
|
|
@@ -483,16 +1481,20 @@ export {
|
|
|
483
1481
|
sectionDepthRange,
|
|
484
1482
|
sectionFromPick,
|
|
485
1483
|
sectionOffset,
|
|
1484
|
+
sectionOptionsFromState,
|
|
486
1485
|
sectionPlane,
|
|
487
1486
|
smoothRegionNormals,
|
|
1487
|
+
snapAt,
|
|
488
1488
|
squaredUp,
|
|
489
1489
|
startPosition,
|
|
1490
|
+
surfaceUnderRay,
|
|
490
1491
|
targetBoundary,
|
|
491
1492
|
themesEqual,
|
|
492
1493
|
trackDoubleTaps,
|
|
493
1494
|
trackTaps,
|
|
494
1495
|
useContentBox,
|
|
495
1496
|
useRetarget,
|
|
1497
|
+
useSectionStore,
|
|
496
1498
|
useTapGuard,
|
|
497
1499
|
useViewerControls,
|
|
498
1500
|
viewDirection,
|