@toolpath/viewer 0.3.1 → 1.0.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 +12 -2
- package/dist/{chunk-RICG6UVH.js → chunk-4BU4QZCL.js} +921 -202
- package/dist/engine/index.d.ts +2 -2
- package/dist/engine/index.js +1 -1
- package/dist/index.d.ts +491 -12
- package/dist/index.js +60 -231
- package/dist/{normalize-Bzzwwxt4.d.ts → normalize-DsoIyM8B.d.ts} +37 -95
- package/package.json +7 -3
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// src/engine/engine-part.tsx
|
|
2
|
-
import { useEffect as
|
|
2
|
+
import { useEffect as useEffect6, useMemo as useMemo7 } from "react";
|
|
3
3
|
|
|
4
4
|
// src/part-mesh.tsx
|
|
5
|
-
import { useThree as
|
|
6
|
-
import { useCallback as useCallback3, useEffect as
|
|
7
|
-
import { Vector3 as
|
|
5
|
+
import { useThree as useThree7 } from "@react-three/fiber";
|
|
6
|
+
import { useCallback as useCallback3, useEffect as useEffect5, useLayoutEffect, useMemo as useMemo6, useRef as useRef6 } from "react";
|
|
7
|
+
import { Vector3 as Vector310 } from "three";
|
|
8
8
|
|
|
9
9
|
// src/model/directions.ts
|
|
10
10
|
var EPSILON = 1e-6;
|
|
@@ -54,34 +54,48 @@ var HIGHLIGHT_COLORS = {
|
|
|
54
54
|
geometryIssue: 16711680
|
|
55
55
|
};
|
|
56
56
|
var DIRECTION_COLORS = [
|
|
57
|
+
/*
|
|
58
|
+
* Nine ways up, told apart at a glance.
|
|
59
|
+
*
|
|
60
|
+
* The palette is an **identity**, not a ranking — the same colour on the
|
|
61
|
+
* face, on the arrow and on the row, which is the whole point of it. Two
|
|
62
|
+
* entries that look alike make it a worse identity than eight that do not,
|
|
63
|
+
* and this list had three problems at once:
|
|
64
|
+
*
|
|
65
|
+
* - teal-500, cyan-500 and emerald-500 were neighbours in hue *and*
|
|
66
|
+
* lightness, so three ways up were one blue-green smear;
|
|
67
|
+
* - blue-500 and indigo-500 were the closest pair of all;
|
|
68
|
+
* - indigo-500 sat 0.06 from the violet an offer is painted in — the very
|
|
69
|
+
* clash that sent a purple out of this list once already, still here under
|
|
70
|
+
* a different name.
|
|
71
|
+
*
|
|
72
|
+
* The warm ramp belongs to difficulty, red to sharp corners, orange to the
|
|
73
|
+
* selection over this cycle, violet to an offer. There is no room to move
|
|
74
|
+
* anything to a fresh hue, so the spread is along **lightness** as much as
|
|
75
|
+
* hue: a pale cyan and a mid teal, a light green and a deep navy.
|
|
76
|
+
*
|
|
77
|
+
* `selection-colors.test.ts` measures every pair in Oklab and holds them all
|
|
78
|
+
* above 0.1 — including against the four colours the part can wear over this
|
|
79
|
+
* wash. The binding pair now is blue against an offer's violet, at 0.12.
|
|
80
|
+
*/
|
|
57
81
|
3900150,
|
|
58
82
|
// blue
|
|
59
|
-
|
|
83
|
+
889992,
|
|
60
84
|
// teal
|
|
61
85
|
14239471,
|
|
62
86
|
// fuchsia
|
|
63
|
-
|
|
64
|
-
// cyan
|
|
65
|
-
/*
|
|
66
|
-
* Olive, where a purple used to be.
|
|
67
|
-
*
|
|
68
|
-
* The purple sat one step from the violet a proposed feature is painted in,
|
|
69
|
-
* so an offer laid over work already assigned to this direction was a clash
|
|
70
|
-
* between "suggested" and "decided" — the two states the part is read for.
|
|
71
|
-
* Olive is the only hue with room: the warm ramp belongs to the difficulty
|
|
72
|
-
* bands, red to sharp corners, orange to faces being picked, and green to
|
|
73
|
-
* whatever is being looked at.
|
|
74
|
-
*/
|
|
87
|
+
6809849,
|
|
88
|
+
// pale cyan
|
|
75
89
|
6660877,
|
|
76
90
|
// olive
|
|
77
91
|
15485081,
|
|
78
92
|
// pink
|
|
79
|
-
|
|
80
|
-
// slate
|
|
81
|
-
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
//
|
|
93
|
+
9741240,
|
|
94
|
+
// light slate
|
|
95
|
+
4906624,
|
|
96
|
+
// light green
|
|
97
|
+
1981066
|
|
98
|
+
// deep navy
|
|
85
99
|
];
|
|
86
100
|
var DEFAULT_THEME = {
|
|
87
101
|
background: null,
|
|
@@ -164,7 +178,7 @@ function applyHighlightLayers(part, layers, theme) {
|
|
|
164
178
|
}
|
|
165
179
|
|
|
166
180
|
// src/render/section.ts
|
|
167
|
-
import {
|
|
181
|
+
import { Plane, Vector3 } from "three";
|
|
168
182
|
var SECTION_RENDER_ORDER = {
|
|
169
183
|
stencil: 1,
|
|
170
184
|
cap: 2,
|
|
@@ -233,12 +247,6 @@ function sectionPlane(box, normal, offset, into = new Plane()) {
|
|
|
233
247
|
const axis = unit(normal);
|
|
234
248
|
return into.set(axis, sectionConstant(sectionBounds(box, normal), offset));
|
|
235
249
|
}
|
|
236
|
-
function screenLength(camera, point, viewport, pixels) {
|
|
237
|
-
const height = viewport.height > 0 ? viewport.height : 1;
|
|
238
|
-
const zoom = camera.zoom || 1;
|
|
239
|
-
const visible = camera instanceof OrthographicCamera ? (camera.top - camera.bottom) / zoom : 2 * Math.tan(camera.fov / 2 * Math.PI / 180) * Math.max(camera.position.distanceTo(point), EPSILON2) / zoom;
|
|
240
|
-
return visible / height * pixels;
|
|
241
|
-
}
|
|
242
250
|
function dragPlane(axis, view, point, into = new Plane()) {
|
|
243
251
|
const along = axis.lengthSq() === 0 ? ARROW_AXIS.clone() : axis.clone().normalize();
|
|
244
252
|
const side = new Vector3().crossVectors(view, along);
|
|
@@ -252,7 +260,13 @@ function perpendicular(axis) {
|
|
|
252
260
|
|
|
253
261
|
// src/tap.tsx
|
|
254
262
|
import { useThree } from "@react-three/fiber";
|
|
255
|
-
import {
|
|
263
|
+
import {
|
|
264
|
+
createContext,
|
|
265
|
+
useContext,
|
|
266
|
+
useEffect,
|
|
267
|
+
useMemo,
|
|
268
|
+
useRef
|
|
269
|
+
} from "react";
|
|
256
270
|
|
|
257
271
|
// src/render/tap.ts
|
|
258
272
|
var TAP_SLOP = 4;
|
|
@@ -272,20 +286,43 @@ function trackTaps(element) {
|
|
|
272
286
|
dispose: () => element.removeEventListener("pointerdown", down, { capture: true })
|
|
273
287
|
};
|
|
274
288
|
}
|
|
289
|
+
var DOUBLE_TAP_MS = 400;
|
|
290
|
+
function trackDoubleTaps(within = DOUBLE_TAP_MS, slop = TAP_SLOP) {
|
|
291
|
+
let last = null;
|
|
292
|
+
return {
|
|
293
|
+
isDouble: (event) => {
|
|
294
|
+
const paired = last !== null && event.timeStamp - last.timeStamp <= within && !movedFar(last, event, slop);
|
|
295
|
+
last = paired ? null : event;
|
|
296
|
+
return paired;
|
|
297
|
+
},
|
|
298
|
+
reset: () => {
|
|
299
|
+
last = null;
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
}
|
|
275
303
|
|
|
276
304
|
// src/tap.tsx
|
|
305
|
+
import { jsx } from "react/jsx-runtime";
|
|
306
|
+
var ViewerTapContext = createContext(null);
|
|
307
|
+
var ViewerTapProvider = ({ value, children }) => /* @__PURE__ */ jsx(ViewerTapContext.Provider, { value, children });
|
|
277
308
|
function useTapGuard() {
|
|
309
|
+
const shared = useContext(ViewerTapContext);
|
|
278
310
|
const domElement = useThree((state) => state.gl.domElement);
|
|
279
311
|
const tracker = useRef(null);
|
|
280
312
|
useEffect(() => {
|
|
313
|
+
if (shared) return;
|
|
281
314
|
const tracked = trackTaps(domElement);
|
|
282
315
|
tracker.current = tracked;
|
|
283
316
|
return () => {
|
|
284
317
|
tracked.dispose();
|
|
285
318
|
if (tracker.current === tracked) tracker.current = null;
|
|
286
319
|
};
|
|
287
|
-
}, [domElement]);
|
|
288
|
-
|
|
320
|
+
}, [domElement, shared]);
|
|
321
|
+
const own = useMemo(
|
|
322
|
+
() => (event) => tracker.current?.isTap(event) ?? true,
|
|
323
|
+
[]
|
|
324
|
+
);
|
|
325
|
+
return shared ?? own;
|
|
289
326
|
}
|
|
290
327
|
|
|
291
328
|
// src/render/part.ts
|
|
@@ -391,10 +428,11 @@ function buildRegionAttribute(model, texels, vertexCount) {
|
|
|
391
428
|
function createPart(model, geometry, theme) {
|
|
392
429
|
const position = geometry.getAttribute("position");
|
|
393
430
|
const texels = buildRegionTexels(model);
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
431
|
+
const regionAttribute = new Float32BufferAttribute2(
|
|
432
|
+
buildRegionAttribute(model, texels, position.count),
|
|
433
|
+
1
|
|
397
434
|
);
|
|
435
|
+
geometry.setAttribute(REGION_ATTRIBUTE, regionAttribute);
|
|
398
436
|
const width = model.regions.length + 1;
|
|
399
437
|
const state = new Uint8Array(width * 4);
|
|
400
438
|
const stateTexture = new DataTexture(state, width, 1, RGBAFormat, UnsignedByteType);
|
|
@@ -430,7 +468,8 @@ function createPart(model, geometry, theme) {
|
|
|
430
468
|
"#include <color_fragment>",
|
|
431
469
|
`#include <color_fragment>
|
|
432
470
|
regionState = texelFetch(uRegionState, ivec2(int(vRegion + 0.5), 0), 0);
|
|
433
|
-
diffuseColor.rgb = mix(diffuseColor.rgb, regionState.rgb, regionState.a)
|
|
471
|
+
diffuseColor.rgb = mix(diffuseColor.rgb, regionState.rgb, regionState.a);
|
|
472
|
+
`
|
|
434
473
|
).replace(
|
|
435
474
|
"#include <emissivemap_fragment>",
|
|
436
475
|
`#include <emissivemap_fragment>
|
|
@@ -520,7 +559,9 @@ function createPart(model, geometry, theme) {
|
|
|
520
559
|
},
|
|
521
560
|
dispose() {
|
|
522
561
|
object.clear();
|
|
523
|
-
geometry.
|
|
562
|
+
if (geometry.getAttribute(REGION_ATTRIBUTE) === regionAttribute) {
|
|
563
|
+
geometry.deleteAttribute(REGION_ATTRIBUTE);
|
|
564
|
+
}
|
|
524
565
|
material.dispose();
|
|
525
566
|
edgeGeometry.dispose();
|
|
526
567
|
edgeMaterial.dispose();
|
|
@@ -529,6 +570,48 @@ function createPart(model, geometry, theme) {
|
|
|
529
570
|
};
|
|
530
571
|
}
|
|
531
572
|
|
|
573
|
+
// src/render/adjacency.ts
|
|
574
|
+
var GRID = 1e-4;
|
|
575
|
+
var keyFor = (x, y, z) => `${Math.round(x / GRID)},${Math.round(y / GRID)},${Math.round(z / GRID)}`;
|
|
576
|
+
function regionAdjacency(model, geometry) {
|
|
577
|
+
const position = geometry.getAttribute("position");
|
|
578
|
+
const adjacency = /* @__PURE__ */ new Map();
|
|
579
|
+
if (!position) return adjacency;
|
|
580
|
+
const owner = new Int32Array(Math.floor(position.count / 3)).fill(-1);
|
|
581
|
+
for (const region of model.regions) {
|
|
582
|
+
for (let at = region.triangles.start; at < region.triangles.end; at++) {
|
|
583
|
+
if (at >= 0 && at < owner.length) owner[at] = region.idx;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
const edges = /* @__PURE__ */ new Map();
|
|
587
|
+
const join = (a, b) => {
|
|
588
|
+
if (a === b) return;
|
|
589
|
+
const mine = adjacency.get(a) ?? /* @__PURE__ */ new Set();
|
|
590
|
+
mine.add(b);
|
|
591
|
+
adjacency.set(a, mine);
|
|
592
|
+
const theirs = adjacency.get(b) ?? /* @__PURE__ */ new Set();
|
|
593
|
+
theirs.add(a);
|
|
594
|
+
adjacency.set(b, theirs);
|
|
595
|
+
};
|
|
596
|
+
for (let triangle = 0; triangle < owner.length; triangle++) {
|
|
597
|
+
const region = owner[triangle];
|
|
598
|
+
if (region === -1) continue;
|
|
599
|
+
const corners = [0, 1, 2].map((corner) => {
|
|
600
|
+
const at = triangle * 3 + corner;
|
|
601
|
+
return keyFor(position.getX(at), position.getY(at), position.getZ(at));
|
|
602
|
+
});
|
|
603
|
+
for (let corner = 0; corner < 3; corner++) {
|
|
604
|
+
const from = corners[corner];
|
|
605
|
+
const to = corners[(corner + 1) % 3];
|
|
606
|
+
const edge = from < to ? `${from}|${to}` : `${to}|${from}`;
|
|
607
|
+
const seen = edges.get(edge);
|
|
608
|
+
if (seen === void 0) edges.set(edge, region);
|
|
609
|
+
else join(seen, region);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
return adjacency;
|
|
613
|
+
}
|
|
614
|
+
|
|
532
615
|
// src/render/selection.ts
|
|
533
616
|
var FEATURE_TYPE_RANKS = [
|
|
534
617
|
["blind_hole", "through_hole", "filleted_blind_hole"],
|
|
@@ -616,7 +699,8 @@ function buildPick(input) {
|
|
|
616
699
|
triangleIndex: input.triangleIndex,
|
|
617
700
|
point: input.point,
|
|
618
701
|
normal: input.normal,
|
|
619
|
-
modifiers: input.modifiers ?? NO_MODIFIERS
|
|
702
|
+
modifiers: input.modifiers ?? NO_MODIFIERS,
|
|
703
|
+
doubled: input.doubled ?? false
|
|
620
704
|
};
|
|
621
705
|
}
|
|
622
706
|
function focusForPick(pick, previousRegion, previousFocus) {
|
|
@@ -627,25 +711,25 @@ function focusForPick(pick, previousRegion, previousFocus) {
|
|
|
627
711
|
}
|
|
628
712
|
|
|
629
713
|
// src/viewer.tsx
|
|
630
|
-
import { Canvas, useFrame as
|
|
714
|
+
import { Canvas, useFrame as useFrame3, useThree as useThree4 } from "@react-three/fiber";
|
|
631
715
|
import {
|
|
632
|
-
createContext,
|
|
716
|
+
createContext as createContext2,
|
|
633
717
|
forwardRef,
|
|
634
718
|
useCallback,
|
|
635
|
-
useContext,
|
|
636
|
-
useEffect as
|
|
719
|
+
useContext as useContext2,
|
|
720
|
+
useEffect as useEffect4,
|
|
637
721
|
useImperativeHandle,
|
|
638
|
-
useMemo as
|
|
639
|
-
useRef as
|
|
722
|
+
useMemo as useMemo4,
|
|
723
|
+
useRef as useRef3
|
|
640
724
|
} from "react";
|
|
641
|
-
import { Box3 as Box33, Vector3 as
|
|
725
|
+
import { Box3 as Box33, Vector3 as Vector37 } from "three";
|
|
642
726
|
|
|
643
727
|
// src/camera.tsx
|
|
644
728
|
import { useFrame, useThree as useThree2 } from "@react-three/fiber";
|
|
645
729
|
import { useEffect as useEffect2, useMemo as useMemo2 } from "react";
|
|
646
730
|
|
|
647
731
|
// src/render/camera.ts
|
|
648
|
-
import { OrthographicCamera
|
|
732
|
+
import { OrthographicCamera, Vector3 as Vector33 } from "three";
|
|
649
733
|
var PERSPECTIVE_FOV = 30;
|
|
650
734
|
var DEFAULT_FIT_MARGIN = 1.2;
|
|
651
735
|
var EXCLUDE_FROM_FRAME = "viewerExcludeFromFrame";
|
|
@@ -655,6 +739,13 @@ function defaultBounds() {
|
|
|
655
739
|
function aspectRatio(size) {
|
|
656
740
|
return size.width > 0 && size.height > 0 ? size.width / size.height : 1;
|
|
657
741
|
}
|
|
742
|
+
var SCREEN_LENGTH_EPSILON = 1e-9;
|
|
743
|
+
function screenLength(camera, point, viewport, pixels) {
|
|
744
|
+
const height = viewport.height > 0 ? viewport.height : 1;
|
|
745
|
+
const zoom = camera.zoom || 1;
|
|
746
|
+
const visible = camera instanceof OrthographicCamera ? (camera.top - camera.bottom) / zoom : 2 * Math.tan(camera.fov / 2 * Math.PI / 180) * Math.max(camera.position.distanceTo(point), SCREEN_LENGTH_EPSILON) / zoom;
|
|
747
|
+
return visible / height * pixels;
|
|
748
|
+
}
|
|
658
749
|
function boundsFromBox(box) {
|
|
659
750
|
if (box.isEmpty()) return defaultBounds();
|
|
660
751
|
const center = box.getCenter(new Vector33());
|
|
@@ -703,10 +794,53 @@ function startPosition(projection, size, bounds, margin = DEFAULT_FIT_MARGIN) {
|
|
|
703
794
|
const [x, y, z] = START_DIRECTION[projection];
|
|
704
795
|
return new Vector33(x, y, z).normalize().multiplyScalar(fitDistance(projection, size, bounds, margin)).add(bounds.center);
|
|
705
796
|
}
|
|
797
|
+
var MIN_FRAME_RATIO = 0.25;
|
|
798
|
+
var MAX_FRAME_RATIO = 10;
|
|
799
|
+
var TARGET_BOUNDARY_RATIO = 4;
|
|
800
|
+
function cameraLimits(projection, size, bounds, margin = DEFAULT_FIT_MARGIN, framed) {
|
|
801
|
+
const fit = fitDistance(projection, size, bounds, margin);
|
|
802
|
+
const framedRadius = framed && framed.radius > 0 ? framed.radius : bounds.radius;
|
|
803
|
+
if (projection === "orthographic") {
|
|
804
|
+
const reach = bounds.radius > 0 ? bounds.radius / framedRadius : 1;
|
|
805
|
+
return {
|
|
806
|
+
minZoom: Math.min(MIN_FRAME_RATIO, MIN_FRAME_RATIO * reach),
|
|
807
|
+
maxZoom: Math.max(MAX_FRAME_RATIO, MAX_FRAME_RATIO * reach),
|
|
808
|
+
// Distance does not change apparent size under an orthographic camera, so
|
|
809
|
+
// these are not the zoom clamps in another form — they only keep the
|
|
810
|
+
// camera clear of the geometry it is looking at, and bounded.
|
|
811
|
+
minDistance: bounds.radius * margin,
|
|
812
|
+
maxDistance: fit / MIN_FRAME_RATIO
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
const framedFit = fitDistance(
|
|
816
|
+
projection,
|
|
817
|
+
size,
|
|
818
|
+
framedRadius === bounds.radius ? bounds : { ...bounds, radius: framedRadius },
|
|
819
|
+
margin
|
|
820
|
+
);
|
|
821
|
+
return {
|
|
822
|
+
// The wheel is `ACTION.DOLLY` under a perspective camera, so nothing here
|
|
823
|
+
// moves `camera.zoom`. They are set to the same pair anyway: a consumer
|
|
824
|
+
// that does zoom should not find the two cameras disagreeing about how far
|
|
825
|
+
// in is too far.
|
|
826
|
+
minZoom: MIN_FRAME_RATIO,
|
|
827
|
+
maxZoom: MAX_FRAME_RATIO,
|
|
828
|
+
minDistance: Math.min(fit, framedFit) / MAX_FRAME_RATIO,
|
|
829
|
+
maxDistance: Math.max(fit, framedFit) / MIN_FRAME_RATIO
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
var scratchBoundarySize = new Vector33();
|
|
833
|
+
function targetBoundary(bounds, into, margin = DEFAULT_FIT_MARGIN) {
|
|
834
|
+
const reach = bounds.radius * margin * TARGET_BOUNDARY_RATIO;
|
|
835
|
+
return into.setFromCenterAndSize(
|
|
836
|
+
bounds.center,
|
|
837
|
+
scratchBoundarySize.set(reach * 2, reach * 2, reach * 2)
|
|
838
|
+
);
|
|
839
|
+
}
|
|
706
840
|
function applyProjection(camera, size, bounds, margin = DEFAULT_FIT_MARGIN) {
|
|
707
841
|
const aspect = aspectRatio(size);
|
|
708
842
|
const radius = bounds.radius * margin;
|
|
709
|
-
if (camera instanceof
|
|
843
|
+
if (camera instanceof OrthographicCamera) {
|
|
710
844
|
const halfHeight = orthographicHalfHeight(aspect, radius);
|
|
711
845
|
const halfWidth = halfHeight * aspect;
|
|
712
846
|
camera.left = -halfWidth;
|
|
@@ -726,6 +860,11 @@ function applyProjection(camera, size, bounds, margin = DEFAULT_FIT_MARGIN) {
|
|
|
726
860
|
camera.updateProjectionMatrix();
|
|
727
861
|
}
|
|
728
862
|
var CAD_CAMERA_UP = new Vector33(0, 0, 1);
|
|
863
|
+
var scratchSide = new Vector33();
|
|
864
|
+
function adaptedUp(view, up, into) {
|
|
865
|
+
const side = scratchSide.crossVectors(view, up).normalize();
|
|
866
|
+
return into.crossVectors(side, view).normalize();
|
|
867
|
+
}
|
|
729
868
|
var cadViewDirections = {
|
|
730
869
|
front: new Vector33(0, -1, 0),
|
|
731
870
|
back: new Vector33(0, 1, 0),
|
|
@@ -746,7 +885,7 @@ import CameraControls from "camera-controls";
|
|
|
746
885
|
import {
|
|
747
886
|
Box3 as Box32,
|
|
748
887
|
Matrix4,
|
|
749
|
-
OrthographicCamera as
|
|
888
|
+
OrthographicCamera as OrthographicCamera2,
|
|
750
889
|
Quaternion,
|
|
751
890
|
Raycaster,
|
|
752
891
|
Sphere,
|
|
@@ -773,6 +912,8 @@ var FUSION_PINCH_ZOOM_SCALE = 0.04;
|
|
|
773
912
|
var FUSION_ROTATE_SCALE = 2.2;
|
|
774
913
|
var FUSION_TRUCK_SCALE = 0.33;
|
|
775
914
|
var DEFAULT_SMOOTH_TIME = 1e-3;
|
|
915
|
+
var DOLLY_SPEED = 1.15;
|
|
916
|
+
var REST_THRESHOLD = 5e-3;
|
|
776
917
|
var ExtendedCameraControls = class extends CameraControls {
|
|
777
918
|
#domElement;
|
|
778
919
|
#freeOrbit;
|
|
@@ -787,13 +928,34 @@ var ExtendedCameraControls = class extends CameraControls {
|
|
|
787
928
|
#scratchA = new Vector34();
|
|
788
929
|
#scratchB = new Vector34();
|
|
789
930
|
#scratchC = new Vector34();
|
|
790
|
-
#scratchD = new Vector34();
|
|
791
931
|
constructor(camera, domElement, options = {}) {
|
|
792
932
|
super(camera, domElement);
|
|
793
933
|
this.#domElement = domElement;
|
|
794
934
|
this.#freeOrbit = options.freeOrbit ?? true;
|
|
795
935
|
this.azimuthRotateSpeed = 0;
|
|
796
936
|
this.polarRotateSpeed = 0;
|
|
937
|
+
this.dollySpeed = DOLLY_SPEED;
|
|
938
|
+
this.restThreshold = REST_THRESHOLD;
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Bounds how far the wheel may travel, and where the orbit target may go.
|
|
942
|
+
*
|
|
943
|
+
* Re-applied whenever the scene is re-measured rather than set once: the
|
|
944
|
+
* limits are derived from the part's bounds, and a viewer that loads a second
|
|
945
|
+
* part would otherwise keep the first part's idea of far.
|
|
946
|
+
*
|
|
947
|
+
* The boundary confines the *target*. Zoom-to-cursor moves it, and goes on
|
|
948
|
+
* moving it after the zoom clamp bites — which is a runaway no zoom clamp can
|
|
949
|
+
* catch. `boundaryEnclosesCamera` stays off: under an orthographic camera the
|
|
950
|
+
* camera sits well outside the framing it is looking at, so enclosing it would
|
|
951
|
+
* drag the target back in on every frame.
|
|
952
|
+
*/
|
|
953
|
+
applyLimits(limits, boundary) {
|
|
954
|
+
this.minZoom = limits.minZoom;
|
|
955
|
+
this.maxZoom = limits.maxZoom;
|
|
956
|
+
this.minDistance = limits.minDistance;
|
|
957
|
+
this.maxDistance = limits.maxDistance;
|
|
958
|
+
this.setBoundary(boundary);
|
|
797
959
|
}
|
|
798
960
|
get freeOrbit() {
|
|
799
961
|
return this.#freeOrbit;
|
|
@@ -863,7 +1025,7 @@ var ExtendedCameraControls = class extends CameraControls {
|
|
|
863
1025
|
this.mouseButtons.left = CameraControls.ACTION.ROTATE;
|
|
864
1026
|
this.mouseButtons.right = CameraControls.ACTION.TRUCK;
|
|
865
1027
|
this.mouseButtons.middle = CameraControls.ACTION.TRUCK;
|
|
866
|
-
this.mouseButtons.wheel = this.camera instanceof
|
|
1028
|
+
this.mouseButtons.wheel = this.camera instanceof OrthographicCamera2 ? CameraControls.ACTION.ZOOM : CameraControls.ACTION.DOLLY;
|
|
867
1029
|
}
|
|
868
1030
|
setFreeOrbit(freeOrbit) {
|
|
869
1031
|
if (this.#freeOrbit === freeOrbit) {
|
|
@@ -921,9 +1083,7 @@ var ExtendedCameraControls = class extends CameraControls {
|
|
|
921
1083
|
#adaptUpVector = () => {
|
|
922
1084
|
const target = this.getTarget(this.#scratchA);
|
|
923
1085
|
const view = this.#scratchB.subVectors(target, this.camera.position).normalize();
|
|
924
|
-
|
|
925
|
-
const up = this.#scratchD.crossVectors(side, view).normalize();
|
|
926
|
-
this.camera.up.copy(up);
|
|
1086
|
+
this.camera.up.copy(adaptedUp(view, this.camera.up, this.#scratchC));
|
|
927
1087
|
const position = this.getPosition(this.#scratchA);
|
|
928
1088
|
this.updateCameraUp();
|
|
929
1089
|
void this.setPosition(position.x, position.y, position.z, false);
|
|
@@ -990,7 +1150,8 @@ var ExtendedCameraControls = class extends CameraControls {
|
|
|
990
1150
|
var CadCameraControls = ({
|
|
991
1151
|
controlsRef,
|
|
992
1152
|
scheme = "toolpath",
|
|
993
|
-
freeOrbit = true
|
|
1153
|
+
freeOrbit = true,
|
|
1154
|
+
zoomTo = "cursor"
|
|
994
1155
|
}) => {
|
|
995
1156
|
const camera = useThree2((state) => state.camera);
|
|
996
1157
|
const domElement = useThree2((state) => state.gl.domElement);
|
|
@@ -1029,46 +1190,470 @@ var CadCameraControls = ({
|
|
|
1029
1190
|
useEffect2(() => {
|
|
1030
1191
|
controls.setFreeOrbit(freeOrbit);
|
|
1031
1192
|
}, [controls, freeOrbit]);
|
|
1193
|
+
useEffect2(() => {
|
|
1194
|
+
controls.dollyToCursor = zoomTo === "cursor";
|
|
1195
|
+
}, [controls, zoomTo]);
|
|
1032
1196
|
useFrame((_, delta) => {
|
|
1033
1197
|
controls.update(delta);
|
|
1034
1198
|
});
|
|
1035
1199
|
return null;
|
|
1036
1200
|
};
|
|
1037
1201
|
|
|
1202
|
+
// src/render/retarget.ts
|
|
1203
|
+
function retargetPose(cameraPosition, currentTarget, hitPoint) {
|
|
1204
|
+
const dx = hitPoint.x - currentTarget.x;
|
|
1205
|
+
const dy = hitPoint.y - currentTarget.y;
|
|
1206
|
+
const dz = hitPoint.z - currentTarget.z;
|
|
1207
|
+
return {
|
|
1208
|
+
position: {
|
|
1209
|
+
x: cameraPosition.x + dx,
|
|
1210
|
+
y: cameraPosition.y + dy,
|
|
1211
|
+
z: cameraPosition.z + dz
|
|
1212
|
+
},
|
|
1213
|
+
target: { x: hitPoint.x, y: hitPoint.y, z: hitPoint.z }
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
// src/target-marker.tsx
|
|
1218
|
+
import { useFrame as useFrame2, useThree as useThree3 } from "@react-three/fiber";
|
|
1219
|
+
import { useEffect as useEffect3, useMemo as useMemo3, useRef as useRef2 } from "react";
|
|
1220
|
+
import {
|
|
1221
|
+
DoubleSide,
|
|
1222
|
+
RingGeometry,
|
|
1223
|
+
SphereGeometry,
|
|
1224
|
+
Vector3 as Vector35
|
|
1225
|
+
} from "three";
|
|
1226
|
+
|
|
1227
|
+
// src/render/target.ts
|
|
1228
|
+
var ORBIT_TARGET_PIXELS = 3;
|
|
1229
|
+
var ORBIT_TARGET_RING_PIXELS = 14;
|
|
1230
|
+
var ORBIT_TARGET_RING_WIDTH = 0.16;
|
|
1231
|
+
var ORBIT_TARGET_COLOR = 3948625;
|
|
1232
|
+
var ORBIT_TARGET_RING_COLOR = 7057587;
|
|
1233
|
+
var ORBIT_TARGET_RING_OPACITY = 0.9;
|
|
1234
|
+
var ORBIT_TARGET_FLASH_MS = 100;
|
|
1235
|
+
var ORBIT_TARGET_FADE_MS = 1e3;
|
|
1236
|
+
function orbitTargetOpacity(sinceHold, fade = ORBIT_TARGET_FADE_MS) {
|
|
1237
|
+
if (sinceHold <= 0) return 1;
|
|
1238
|
+
if (fade <= 0 || sinceHold >= fade) return 0;
|
|
1239
|
+
return 1 - sinceHold / fade;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
// src/target-marker.tsx
|
|
1243
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
1244
|
+
var FURNITURE = { [EXCLUDE_FROM_FRAME]: true };
|
|
1245
|
+
var TargetMarker = ({ controlsRef }) => {
|
|
1246
|
+
const camera = useThree3((state) => state.camera);
|
|
1247
|
+
const size = useThree3((state) => state.size);
|
|
1248
|
+
const invalidate = useThree3((state) => state.invalidate);
|
|
1249
|
+
const registered = useThree3((state) => state.controls);
|
|
1250
|
+
const group = useRef2(null);
|
|
1251
|
+
const ring = useRef2(null);
|
|
1252
|
+
const dotMaterial = useRef2(null);
|
|
1253
|
+
const ringMaterial = useRef2(null);
|
|
1254
|
+
const dotGeometry = useMemo3(() => new SphereGeometry(1, 16, 12), []);
|
|
1255
|
+
const ringGeometry = useMemo3(() => new RingGeometry(1 - ORBIT_TARGET_RING_WIDTH, 1, 48), []);
|
|
1256
|
+
useEffect3(
|
|
1257
|
+
() => () => {
|
|
1258
|
+
dotGeometry.dispose();
|
|
1259
|
+
ringGeometry.dispose();
|
|
1260
|
+
},
|
|
1261
|
+
[dotGeometry, ringGeometry]
|
|
1262
|
+
);
|
|
1263
|
+
const dragging = useRef2(false);
|
|
1264
|
+
const heldUntil = useRef2(0);
|
|
1265
|
+
const painted = useRef2(-1);
|
|
1266
|
+
const placed = useRef2(false);
|
|
1267
|
+
const lastTarget = useMemo3(() => new Vector35(), []);
|
|
1268
|
+
const scratch = useMemo3(() => new Vector35(), []);
|
|
1269
|
+
useEffect3(() => {
|
|
1270
|
+
const controls = controlsRef.current;
|
|
1271
|
+
if (!controls) return void 0;
|
|
1272
|
+
const start = () => {
|
|
1273
|
+
dragging.current = true;
|
|
1274
|
+
invalidate();
|
|
1275
|
+
};
|
|
1276
|
+
const end = () => {
|
|
1277
|
+
dragging.current = false;
|
|
1278
|
+
heldUntil.current = performance.now();
|
|
1279
|
+
invalidate();
|
|
1280
|
+
};
|
|
1281
|
+
controls.addEventListener("controlstart", start);
|
|
1282
|
+
controls.addEventListener("controlend", end);
|
|
1283
|
+
return () => {
|
|
1284
|
+
controls.removeEventListener("controlstart", start);
|
|
1285
|
+
controls.removeEventListener("controlend", end);
|
|
1286
|
+
};
|
|
1287
|
+
}, [controlsRef, invalidate, registered]);
|
|
1288
|
+
useFrame2(() => {
|
|
1289
|
+
const controls = controlsRef.current;
|
|
1290
|
+
const node = group.current;
|
|
1291
|
+
if (!controls || !node) return;
|
|
1292
|
+
const now = performance.now();
|
|
1293
|
+
const target = controls.getTarget(scratch);
|
|
1294
|
+
if (!placed.current) {
|
|
1295
|
+
placed.current = true;
|
|
1296
|
+
lastTarget.copy(target);
|
|
1297
|
+
node.position.copy(target);
|
|
1298
|
+
} else if (!target.equals(lastTarget)) {
|
|
1299
|
+
lastTarget.copy(target);
|
|
1300
|
+
node.position.copy(target);
|
|
1301
|
+
heldUntil.current = Math.max(heldUntil.current, now + ORBIT_TARGET_FLASH_MS);
|
|
1302
|
+
}
|
|
1303
|
+
const opacity = dragging.current ? 1 : orbitTargetOpacity(now - heldUntil.current);
|
|
1304
|
+
if (opacity !== painted.current) {
|
|
1305
|
+
painted.current = opacity;
|
|
1306
|
+
node.visible = opacity > 0;
|
|
1307
|
+
if (dotMaterial.current) dotMaterial.current.opacity = opacity;
|
|
1308
|
+
if (ringMaterial.current) ringMaterial.current.opacity = opacity * ORBIT_TARGET_RING_OPACITY;
|
|
1309
|
+
invalidate();
|
|
1310
|
+
}
|
|
1311
|
+
if (node.visible) {
|
|
1312
|
+
node.scale.setScalar(screenLength(camera, target, size, 1));
|
|
1313
|
+
ring.current?.quaternion.copy(camera.quaternion);
|
|
1314
|
+
}
|
|
1315
|
+
});
|
|
1316
|
+
return /* @__PURE__ */ jsxs("group", { ref: group, visible: false, userData: FURNITURE, children: [
|
|
1317
|
+
/* @__PURE__ */ jsx2(
|
|
1318
|
+
"mesh",
|
|
1319
|
+
{
|
|
1320
|
+
ref: ring,
|
|
1321
|
+
geometry: ringGeometry,
|
|
1322
|
+
scale: ORBIT_TARGET_RING_PIXELS,
|
|
1323
|
+
renderOrder: 998,
|
|
1324
|
+
raycast: () => null,
|
|
1325
|
+
children: /* @__PURE__ */ jsx2(
|
|
1326
|
+
"meshBasicMaterial",
|
|
1327
|
+
{
|
|
1328
|
+
ref: ringMaterial,
|
|
1329
|
+
color: ORBIT_TARGET_RING_COLOR,
|
|
1330
|
+
transparent: true,
|
|
1331
|
+
opacity: 0,
|
|
1332
|
+
depthTest: false,
|
|
1333
|
+
depthWrite: false,
|
|
1334
|
+
side: DoubleSide
|
|
1335
|
+
}
|
|
1336
|
+
)
|
|
1337
|
+
}
|
|
1338
|
+
),
|
|
1339
|
+
/* @__PURE__ */ jsx2(
|
|
1340
|
+
"mesh",
|
|
1341
|
+
{
|
|
1342
|
+
geometry: dotGeometry,
|
|
1343
|
+
scale: ORBIT_TARGET_PIXELS,
|
|
1344
|
+
renderOrder: 999,
|
|
1345
|
+
raycast: () => null,
|
|
1346
|
+
children: /* @__PURE__ */ jsx2(
|
|
1347
|
+
"meshBasicMaterial",
|
|
1348
|
+
{
|
|
1349
|
+
ref: dotMaterial,
|
|
1350
|
+
color: ORBIT_TARGET_COLOR,
|
|
1351
|
+
transparent: true,
|
|
1352
|
+
opacity: 0,
|
|
1353
|
+
depthTest: false,
|
|
1354
|
+
depthWrite: false
|
|
1355
|
+
}
|
|
1356
|
+
)
|
|
1357
|
+
}
|
|
1358
|
+
)
|
|
1359
|
+
] });
|
|
1360
|
+
};
|
|
1361
|
+
|
|
1362
|
+
// src/render/view-cube.ts
|
|
1363
|
+
import {
|
|
1364
|
+
BufferGeometry,
|
|
1365
|
+
CanvasTexture,
|
|
1366
|
+
Float32BufferAttribute as Float32BufferAttribute3,
|
|
1367
|
+
SRGBColorSpace,
|
|
1368
|
+
Vector3 as Vector36
|
|
1369
|
+
} from "three";
|
|
1370
|
+
var VIEW_NAMES = [
|
|
1371
|
+
// The six faces.
|
|
1372
|
+
"top",
|
|
1373
|
+
"bottom",
|
|
1374
|
+
"front",
|
|
1375
|
+
"back",
|
|
1376
|
+
"left",
|
|
1377
|
+
"right",
|
|
1378
|
+
// The twelve edges.
|
|
1379
|
+
"top-front",
|
|
1380
|
+
"top-back",
|
|
1381
|
+
"top-left",
|
|
1382
|
+
"top-right",
|
|
1383
|
+
"bottom-front",
|
|
1384
|
+
"bottom-back",
|
|
1385
|
+
"bottom-left",
|
|
1386
|
+
"bottom-right",
|
|
1387
|
+
"front-left",
|
|
1388
|
+
"front-right",
|
|
1389
|
+
"back-left",
|
|
1390
|
+
"back-right",
|
|
1391
|
+
// The eight corners.
|
|
1392
|
+
"top-front-left",
|
|
1393
|
+
"top-front-right",
|
|
1394
|
+
"top-back-left",
|
|
1395
|
+
"top-back-right",
|
|
1396
|
+
"bottom-front-left",
|
|
1397
|
+
"bottom-front-right",
|
|
1398
|
+
"bottom-back-left",
|
|
1399
|
+
"bottom-back-right"
|
|
1400
|
+
];
|
|
1401
|
+
var VIEW_SIGNS = {
|
|
1402
|
+
top: [0, 0, 1],
|
|
1403
|
+
bottom: [0, 0, -1],
|
|
1404
|
+
front: [0, -1, 0],
|
|
1405
|
+
back: [0, 1, 0],
|
|
1406
|
+
left: [-1, 0, 0],
|
|
1407
|
+
right: [1, 0, 0],
|
|
1408
|
+
"top-front": [0, -1, 1],
|
|
1409
|
+
"top-back": [0, 1, 1],
|
|
1410
|
+
"top-left": [-1, 0, 1],
|
|
1411
|
+
"top-right": [1, 0, 1],
|
|
1412
|
+
"bottom-front": [0, -1, -1],
|
|
1413
|
+
"bottom-back": [0, 1, -1],
|
|
1414
|
+
"bottom-left": [-1, 0, -1],
|
|
1415
|
+
"bottom-right": [1, 0, -1],
|
|
1416
|
+
"front-left": [-1, -1, 0],
|
|
1417
|
+
"front-right": [1, -1, 0],
|
|
1418
|
+
"back-left": [-1, 1, 0],
|
|
1419
|
+
"back-right": [1, 1, 0],
|
|
1420
|
+
"top-front-left": [-1, -1, 1],
|
|
1421
|
+
"top-front-right": [1, -1, 1],
|
|
1422
|
+
"top-back-left": [-1, 1, 1],
|
|
1423
|
+
"top-back-right": [1, 1, 1],
|
|
1424
|
+
"bottom-front-left": [-1, -1, -1],
|
|
1425
|
+
"bottom-front-right": [1, -1, -1],
|
|
1426
|
+
"bottom-back-left": [-1, 1, -1],
|
|
1427
|
+
"bottom-back-right": [1, 1, -1]
|
|
1428
|
+
};
|
|
1429
|
+
function viewKind(name) {
|
|
1430
|
+
const off = VIEW_SIGNS[name].filter((sign) => sign !== 0).length;
|
|
1431
|
+
if (off === 1) return "face";
|
|
1432
|
+
if (off === 2) return "edge";
|
|
1433
|
+
return "corner";
|
|
1434
|
+
}
|
|
1435
|
+
var DIRECTIONS = new Map(
|
|
1436
|
+
VIEW_NAMES.map((name) => {
|
|
1437
|
+
const [x, y, z] = VIEW_SIGNS[name];
|
|
1438
|
+
const length = Math.hypot(x, y, z);
|
|
1439
|
+
return [name, { x: x / length, y: y / length, z: z / length }];
|
|
1440
|
+
})
|
|
1441
|
+
);
|
|
1442
|
+
function viewVector(name) {
|
|
1443
|
+
return DIRECTIONS.get(name) ?? { x: 0, y: 0, z: 1 };
|
|
1444
|
+
}
|
|
1445
|
+
function viewUp(direction) {
|
|
1446
|
+
if (direction.x === 0 && direction.y === 0) {
|
|
1447
|
+
return { x: 0, y: direction.z > 0 ? 1 : -1, z: 0 };
|
|
1448
|
+
}
|
|
1449
|
+
return { x: 0, y: 0, z: 1 };
|
|
1450
|
+
}
|
|
1451
|
+
function squaredUp(direction, currentUp) {
|
|
1452
|
+
const forward = new Vector36(direction.x, direction.y, direction.z).normalize();
|
|
1453
|
+
const canonical = viewUp(direction);
|
|
1454
|
+
const zero = new Vector36(canonical.x, canonical.y, canonical.z);
|
|
1455
|
+
zero.addScaledVector(forward, -forward.dot(zero)).normalize();
|
|
1456
|
+
const quarter = new Vector36().crossVectors(forward, zero);
|
|
1457
|
+
const current = new Vector36(currentUp.x, currentUp.y, currentUp.z);
|
|
1458
|
+
const rolls = [zero, quarter, zero.clone().negate(), quarter.clone().negate()];
|
|
1459
|
+
const nearest = rolls.reduce(
|
|
1460
|
+
(best, roll) => roll.dot(current) > best.dot(current) ? roll : best
|
|
1461
|
+
);
|
|
1462
|
+
return vec(nearest);
|
|
1463
|
+
}
|
|
1464
|
+
var CHAMFER = 0.5834;
|
|
1465
|
+
function vec(v) {
|
|
1466
|
+
return { x: v.x, y: v.y, z: v.z };
|
|
1467
|
+
}
|
|
1468
|
+
function cubeZones(chamfer = CHAMFER) {
|
|
1469
|
+
const AXES = [0, 1, 2];
|
|
1470
|
+
return VIEW_NAMES.map((name) => {
|
|
1471
|
+
const kind = viewKind(name);
|
|
1472
|
+
const direction = viewVector(name);
|
|
1473
|
+
const [sx, sy, sz] = VIEW_SIGNS[name];
|
|
1474
|
+
const sign = (axis) => VIEW_SIGNS[name][axis] ?? 0;
|
|
1475
|
+
const normal = new Vector36(direction.x, direction.y, direction.z);
|
|
1476
|
+
let polygon;
|
|
1477
|
+
if (kind === "face") {
|
|
1478
|
+
const up = viewUp(direction);
|
|
1479
|
+
const upVector = new Vector36(up.x, up.y, up.z);
|
|
1480
|
+
const right = normal.clone().negate().cross(upVector).normalize();
|
|
1481
|
+
const square = [
|
|
1482
|
+
[-1, -1],
|
|
1483
|
+
[1, -1],
|
|
1484
|
+
[1, 1],
|
|
1485
|
+
[-1, 1]
|
|
1486
|
+
];
|
|
1487
|
+
polygon = square.map(
|
|
1488
|
+
([u, v]) => normal.clone().addScaledVector(right, u * chamfer).addScaledVector(upVector, v * chamfer)
|
|
1489
|
+
);
|
|
1490
|
+
} else if (kind === "edge") {
|
|
1491
|
+
const a = AXES.find((axis) => sign(axis) !== 0) ?? 0;
|
|
1492
|
+
const b = [...AXES].reverse().find((axis) => sign(axis) !== 0) ?? 0;
|
|
1493
|
+
const c = AXES.find((axis) => sign(axis) === 0) ?? 0;
|
|
1494
|
+
const rectangle = [
|
|
1495
|
+
[1, chamfer, -chamfer],
|
|
1496
|
+
[1, chamfer, chamfer],
|
|
1497
|
+
[chamfer, 1, chamfer],
|
|
1498
|
+
[chamfer, 1, -chamfer]
|
|
1499
|
+
];
|
|
1500
|
+
polygon = rectangle.map(
|
|
1501
|
+
([onA, onB, onC]) => new Vector36().setComponent(a, sign(a) * onA).setComponent(b, sign(b) * onB).setComponent(c, onC)
|
|
1502
|
+
);
|
|
1503
|
+
} else {
|
|
1504
|
+
const triangle = [
|
|
1505
|
+
[1, chamfer, chamfer],
|
|
1506
|
+
[chamfer, 1, chamfer],
|
|
1507
|
+
[chamfer, chamfer, 1]
|
|
1508
|
+
];
|
|
1509
|
+
polygon = triangle.map(([onX, onY, onZ]) => new Vector36(sx * onX, sy * onY, sz * onZ));
|
|
1510
|
+
}
|
|
1511
|
+
const [first, second, third] = polygon;
|
|
1512
|
+
if (first && second && third) {
|
|
1513
|
+
const facing = new Vector36().subVectors(second, first).cross(new Vector36().subVectors(third, first));
|
|
1514
|
+
if (facing.dot(normal) < 0) polygon.reverse();
|
|
1515
|
+
}
|
|
1516
|
+
return { name, kind, direction, polygon: polygon.map(vec) };
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
function panelGeometry(zone) {
|
|
1520
|
+
const positions = [];
|
|
1521
|
+
const normals = [];
|
|
1522
|
+
const [first] = zone.polygon;
|
|
1523
|
+
const geometry = new BufferGeometry();
|
|
1524
|
+
if (!first) return geometry;
|
|
1525
|
+
for (let index = 1; index + 1 < zone.polygon.length; index += 1) {
|
|
1526
|
+
const b = zone.polygon[index];
|
|
1527
|
+
const c = zone.polygon[index + 1];
|
|
1528
|
+
if (!b || !c) continue;
|
|
1529
|
+
positions.push(first.x, first.y, first.z, b.x, b.y, b.z, c.x, c.y, c.z);
|
|
1530
|
+
for (let vertex = 0; vertex < 3; vertex += 1) {
|
|
1531
|
+
normals.push(zone.direction.x, zone.direction.y, zone.direction.z);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
geometry.setAttribute("position", new Float32BufferAttribute3(positions, 3));
|
|
1535
|
+
geometry.setAttribute("normal", new Float32BufferAttribute3(normals, 3));
|
|
1536
|
+
return geometry;
|
|
1537
|
+
}
|
|
1538
|
+
function cubeOutlineGeometry(zones) {
|
|
1539
|
+
const positions = [];
|
|
1540
|
+
for (const zone of zones) {
|
|
1541
|
+
for (let index = 0; index < zone.polygon.length; index += 1) {
|
|
1542
|
+
const from = zone.polygon[index];
|
|
1543
|
+
const to = zone.polygon[(index + 1) % zone.polygon.length];
|
|
1544
|
+
if (!from || !to) continue;
|
|
1545
|
+
positions.push(from.x, from.y, from.z, to.x, to.y, to.z);
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
const geometry = new BufferGeometry();
|
|
1549
|
+
geometry.setAttribute("position", new Float32BufferAttribute3(positions, 3));
|
|
1550
|
+
return geometry;
|
|
1551
|
+
}
|
|
1552
|
+
function labelGeometry(zone) {
|
|
1553
|
+
const positions = [];
|
|
1554
|
+
const uvs = [];
|
|
1555
|
+
const corners = [0, 1, 2, 0, 2, 3];
|
|
1556
|
+
const corner = [
|
|
1557
|
+
[0, 0],
|
|
1558
|
+
[1, 0],
|
|
1559
|
+
[1, 1],
|
|
1560
|
+
[0, 1]
|
|
1561
|
+
];
|
|
1562
|
+
for (const index of corners) {
|
|
1563
|
+
const point = zone.polygon[index];
|
|
1564
|
+
const uv = corner[index];
|
|
1565
|
+
if (!point || !uv) continue;
|
|
1566
|
+
positions.push(
|
|
1567
|
+
point.x + zone.direction.x * 4e-3,
|
|
1568
|
+
point.y + zone.direction.y * 4e-3,
|
|
1569
|
+
point.z + zone.direction.z * 4e-3
|
|
1570
|
+
);
|
|
1571
|
+
uvs.push(uv[0] ?? 0, uv[1] ?? 0);
|
|
1572
|
+
}
|
|
1573
|
+
const geometry = new BufferGeometry();
|
|
1574
|
+
geometry.setAttribute("position", new Float32BufferAttribute3(positions, 3));
|
|
1575
|
+
geometry.setAttribute("uv", new Float32BufferAttribute3(uvs, 2));
|
|
1576
|
+
return geometry;
|
|
1577
|
+
}
|
|
1578
|
+
function labelTexture(label, color) {
|
|
1579
|
+
const size = 128;
|
|
1580
|
+
const canvas = document.createElement("canvas");
|
|
1581
|
+
canvas.width = size;
|
|
1582
|
+
canvas.height = size;
|
|
1583
|
+
const context = canvas.getContext("2d");
|
|
1584
|
+
if (context) {
|
|
1585
|
+
const text = label.toUpperCase();
|
|
1586
|
+
const hex = `#${color.toString(16).padStart(6, "0")}`;
|
|
1587
|
+
context.font = `600 ${size / 4}px system-ui, sans-serif`;
|
|
1588
|
+
const width = context.measureText("BOTTOM").width || 1;
|
|
1589
|
+
const scale = Math.min(1, size * 0.78 / width);
|
|
1590
|
+
context.font = `600 ${size / 4 * scale}px system-ui, sans-serif`;
|
|
1591
|
+
context.textAlign = "center";
|
|
1592
|
+
context.textBaseline = "middle";
|
|
1593
|
+
context.fillStyle = hex;
|
|
1594
|
+
context.fillText(text, size / 2, size / 2);
|
|
1595
|
+
}
|
|
1596
|
+
const texture = new CanvasTexture(canvas);
|
|
1597
|
+
texture.colorSpace = SRGBColorSpace;
|
|
1598
|
+
texture.needsUpdate = true;
|
|
1599
|
+
return texture;
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1038
1602
|
// src/viewer.tsx
|
|
1039
|
-
import {
|
|
1040
|
-
var ViewerControlsContext =
|
|
1603
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1604
|
+
var ViewerControlsContext = createContext2(null);
|
|
1041
1605
|
var useViewerControls = () => {
|
|
1042
|
-
const controls =
|
|
1606
|
+
const controls = useContext2(ViewerControlsContext);
|
|
1043
1607
|
if (!controls) throw new Error("useViewerControls must be used inside <Viewer>");
|
|
1044
1608
|
return controls;
|
|
1045
1609
|
};
|
|
1610
|
+
var ViewerRetargetContext = createContext2(null);
|
|
1611
|
+
var useRetarget = () => useContext2(ViewerRetargetContext);
|
|
1046
1612
|
var ViewerScene = ({
|
|
1047
1613
|
children,
|
|
1048
1614
|
setControls,
|
|
1049
1615
|
projection,
|
|
1050
1616
|
scheme,
|
|
1051
1617
|
freeOrbit,
|
|
1618
|
+
zoomTo,
|
|
1619
|
+
recentreOnDoubleClick,
|
|
1620
|
+
retargetOnDoubleClick,
|
|
1621
|
+
showOrbitTarget,
|
|
1052
1622
|
theme
|
|
1053
1623
|
}) => {
|
|
1054
|
-
const camera =
|
|
1055
|
-
const size =
|
|
1056
|
-
const invalidate =
|
|
1057
|
-
const
|
|
1058
|
-
const
|
|
1059
|
-
const
|
|
1060
|
-
const
|
|
1061
|
-
const
|
|
1062
|
-
const
|
|
1063
|
-
const
|
|
1064
|
-
const
|
|
1624
|
+
const camera = useThree4((state) => state.camera);
|
|
1625
|
+
const size = useThree4((state) => state.size);
|
|
1626
|
+
const invalidate = useThree4((state) => state.invalidate);
|
|
1627
|
+
const domElement = useThree4((state) => state.gl.domElement);
|
|
1628
|
+
const isTap = useTapGuard();
|
|
1629
|
+
const controlsRef = useRef3(null);
|
|
1630
|
+
const contentRef = useRef3(null);
|
|
1631
|
+
const lightsRef = useRef3(null);
|
|
1632
|
+
const initialFrameComplete = useRef3(false);
|
|
1633
|
+
const boundsRef = useRef3(defaultBounds());
|
|
1634
|
+
const framedRef = useRef3(null);
|
|
1635
|
+
const scratchBox = useMemo4(() => new Box33(), []);
|
|
1636
|
+
const scratchBoundary = useMemo4(() => new Box33(), []);
|
|
1637
|
+
const scratchDirection = useMemo4(() => new Vector37(), []);
|
|
1638
|
+
const scratchView = useMemo4(() => new Vector37(), []);
|
|
1639
|
+
const scratchTarget = useMemo4(() => new Vector37(), []);
|
|
1640
|
+
const scratchPosition = useMemo4(() => new Vector37(), []);
|
|
1641
|
+
const applyLimits = useCallback(
|
|
1642
|
+
(bounds, framed) => {
|
|
1643
|
+
controlsRef.current?.applyLimits(
|
|
1644
|
+
cameraLimits(projection, size, bounds, DEFAULT_FIT_MARGIN, framed),
|
|
1645
|
+
targetBoundary(bounds, scratchBoundary, DEFAULT_FIT_MARGIN)
|
|
1646
|
+
);
|
|
1647
|
+
},
|
|
1648
|
+
[projection, scratchBoundary, size]
|
|
1649
|
+
);
|
|
1065
1650
|
const measure = useCallback(() => {
|
|
1066
1651
|
const content = contentRef.current;
|
|
1067
1652
|
boundsRef.current = content ? contentBounds(content, scratchBox) : defaultBounds();
|
|
1068
1653
|
return boundsRef.current;
|
|
1069
1654
|
}, [scratchBox]);
|
|
1070
1655
|
const frame = useCallback(
|
|
1071
|
-
(direction, transition = false) => {
|
|
1656
|
+
(direction, transition = false, up) => {
|
|
1072
1657
|
const controls = controlsRef.current;
|
|
1073
1658
|
if (!controls) return false;
|
|
1074
1659
|
const bounds = measure();
|
|
@@ -1076,6 +1661,10 @@ var ViewerScene = ({
|
|
|
1076
1661
|
const distance = fitDistance(projection, size, bounds, DEFAULT_FIT_MARGIN);
|
|
1077
1662
|
const position = scratchDirection.copy(direction).normalize().multiplyScalar(distance);
|
|
1078
1663
|
position.add(bounds.center);
|
|
1664
|
+
if (up) {
|
|
1665
|
+
camera.up.set(up.x, up.y, up.z);
|
|
1666
|
+
controls.updateCameraUp();
|
|
1667
|
+
}
|
|
1079
1668
|
void controls.setLookAt(
|
|
1080
1669
|
position.x,
|
|
1081
1670
|
position.y,
|
|
@@ -1086,17 +1675,19 @@ var ViewerScene = ({
|
|
|
1086
1675
|
transition
|
|
1087
1676
|
);
|
|
1088
1677
|
if (projection === "orthographic") void controls.zoomTo(1, transition);
|
|
1678
|
+
framedRef.current = null;
|
|
1679
|
+
applyLimits(bounds);
|
|
1089
1680
|
invalidate();
|
|
1090
1681
|
return true;
|
|
1091
1682
|
},
|
|
1092
|
-
[camera, invalidate, measure, projection, scratchDirection, size]
|
|
1683
|
+
[applyLimits, camera, invalidate, measure, projection, scratchDirection, size]
|
|
1093
1684
|
);
|
|
1094
1685
|
const frameBounds = useCallback(
|
|
1095
1686
|
(bounds) => {
|
|
1096
1687
|
const controls = controlsRef.current;
|
|
1097
1688
|
if (!controls) return false;
|
|
1098
|
-
const target = controls.getTarget(new
|
|
1099
|
-
const direction = currentViewDirection(camera, target, new
|
|
1689
|
+
const target = controls.getTarget(new Vector37());
|
|
1690
|
+
const direction = currentViewDirection(camera, target, new Vector37());
|
|
1100
1691
|
const distance = fitDistance(projection, size, bounds, DEFAULT_FIT_MARGIN);
|
|
1101
1692
|
const position = direction.multiplyScalar(distance).add(bounds.center);
|
|
1102
1693
|
void controls.setLookAt(
|
|
@@ -1108,26 +1699,65 @@ var ViewerScene = ({
|
|
|
1108
1699
|
bounds.center.z,
|
|
1109
1700
|
true
|
|
1110
1701
|
);
|
|
1702
|
+
framedRef.current = bounds;
|
|
1703
|
+
applyLimits(boundsRef.current, bounds);
|
|
1111
1704
|
if (projection === "orthographic") {
|
|
1112
1705
|
void controls.zoomTo(boundsRef.current.radius / bounds.radius, true);
|
|
1113
1706
|
}
|
|
1114
1707
|
invalidate();
|
|
1115
1708
|
return true;
|
|
1116
1709
|
},
|
|
1117
|
-
[camera, invalidate, projection, size]
|
|
1710
|
+
[applyLimits, camera, invalidate, projection, size]
|
|
1118
1711
|
);
|
|
1119
1712
|
const fitContent = useCallback(() => {
|
|
1120
1713
|
const controls = controlsRef.current;
|
|
1121
1714
|
if (!controls) return false;
|
|
1122
|
-
const target = controls.getTarget(new
|
|
1123
|
-
return frame(currentViewDirection(camera, target, new
|
|
1715
|
+
const target = controls.getTarget(new Vector37());
|
|
1716
|
+
return frame(currentViewDirection(camera, target, new Vector37()), true);
|
|
1124
1717
|
}, [camera, frame]);
|
|
1125
1718
|
const resetContent = useCallback(() => {
|
|
1126
1719
|
const bounds = measure();
|
|
1127
1720
|
const start = startPosition(projection, size, bounds).sub(bounds.center);
|
|
1128
|
-
return frame(start, true);
|
|
1721
|
+
return frame(start, true, CAD_CAMERA_UP);
|
|
1129
1722
|
}, [frame, measure, projection, size]);
|
|
1130
|
-
|
|
1723
|
+
const retarget = useCallback(
|
|
1724
|
+
(point) => {
|
|
1725
|
+
const controls = controlsRef.current;
|
|
1726
|
+
if (!controls) return;
|
|
1727
|
+
const pose = retargetPose(
|
|
1728
|
+
controls.getPosition(scratchPosition),
|
|
1729
|
+
controls.getTarget(scratchTarget),
|
|
1730
|
+
point
|
|
1731
|
+
);
|
|
1732
|
+
void controls.setLookAt(
|
|
1733
|
+
pose.position.x,
|
|
1734
|
+
pose.position.y,
|
|
1735
|
+
pose.position.z,
|
|
1736
|
+
pose.target.x,
|
|
1737
|
+
pose.target.y,
|
|
1738
|
+
pose.target.z,
|
|
1739
|
+
true
|
|
1740
|
+
);
|
|
1741
|
+
invalidate();
|
|
1742
|
+
},
|
|
1743
|
+
[invalidate, scratchPosition, scratchTarget]
|
|
1744
|
+
);
|
|
1745
|
+
const retargetOn = useMemo4(
|
|
1746
|
+
() => retargetOnDoubleClick ? retarget : null,
|
|
1747
|
+
[retarget, retargetOnDoubleClick]
|
|
1748
|
+
);
|
|
1749
|
+
useEffect4(() => {
|
|
1750
|
+
if (!recentreOnDoubleClick) return void 0;
|
|
1751
|
+
const doubles = trackDoubleTaps();
|
|
1752
|
+
const recentre = (event) => {
|
|
1753
|
+
if (event.button !== 1) return;
|
|
1754
|
+
if (!isTap(event)) return;
|
|
1755
|
+
if (doubles.isDouble(event)) fitContent();
|
|
1756
|
+
};
|
|
1757
|
+
domElement.addEventListener("auxclick", recentre);
|
|
1758
|
+
return () => domElement.removeEventListener("auxclick", recentre);
|
|
1759
|
+
}, [domElement, fitContent, isTap, recentreOnDoubleClick]);
|
|
1760
|
+
useEffect4(() => {
|
|
1131
1761
|
setControls({
|
|
1132
1762
|
fit: () => {
|
|
1133
1763
|
fitContent();
|
|
@@ -1136,62 +1766,80 @@ var ViewerScene = ({
|
|
|
1136
1766
|
resetContent();
|
|
1137
1767
|
},
|
|
1138
1768
|
setView: (view) => {
|
|
1139
|
-
frame(cadViewDirections[view], true);
|
|
1769
|
+
frame(cadViewDirections[view], true, squaredUp(cadViewDirections[view], camera.up));
|
|
1140
1770
|
},
|
|
1141
1771
|
setViewDirection: (direction) => {
|
|
1142
|
-
frame(
|
|
1772
|
+
frame(
|
|
1773
|
+
scratchView.set(direction.x, direction.y, direction.z),
|
|
1774
|
+
true,
|
|
1775
|
+
squaredUp(direction, camera.up)
|
|
1776
|
+
);
|
|
1143
1777
|
},
|
|
1144
1778
|
frameBox: (box) => {
|
|
1145
1779
|
frameBounds(boundsFromBox(box));
|
|
1146
1780
|
}
|
|
1147
1781
|
});
|
|
1148
|
-
}, [fitContent, frame, frameBounds, resetContent, scratchView, setControls]);
|
|
1149
|
-
|
|
1782
|
+
}, [camera, fitContent, frame, frameBounds, resetContent, scratchView, setControls]);
|
|
1783
|
+
useEffect4(() => {
|
|
1150
1784
|
if (initialFrameComplete.current) resetContent();
|
|
1151
1785
|
}, [projection]);
|
|
1152
|
-
|
|
1786
|
+
useEffect4(() => {
|
|
1153
1787
|
applyProjection(camera, size, boundsRef.current, DEFAULT_FIT_MARGIN);
|
|
1788
|
+
if (initialFrameComplete.current) applyLimits(boundsRef.current, framedRef.current ?? void 0);
|
|
1154
1789
|
invalidate();
|
|
1155
|
-
}, [camera, invalidate, size]);
|
|
1156
|
-
|
|
1790
|
+
}, [applyLimits, camera, invalidate, size]);
|
|
1791
|
+
useFrame3(() => {
|
|
1157
1792
|
const lights = lightsRef.current;
|
|
1158
1793
|
if (lights && !lights.quaternion.equals(camera.quaternion)) {
|
|
1159
1794
|
lights.quaternion.copy(camera.quaternion);
|
|
1160
1795
|
invalidate();
|
|
1161
1796
|
}
|
|
1162
1797
|
});
|
|
1163
|
-
|
|
1798
|
+
useFrame3(() => {
|
|
1164
1799
|
if (!initialFrameComplete.current && contentRef.current?.children.length) {
|
|
1165
1800
|
initialFrameComplete.current = resetContent();
|
|
1166
1801
|
}
|
|
1167
1802
|
});
|
|
1168
|
-
return /* @__PURE__ */
|
|
1169
|
-
/* @__PURE__ */
|
|
1170
|
-
/* @__PURE__ */
|
|
1171
|
-
/* @__PURE__ */
|
|
1803
|
+
return /* @__PURE__ */ jsx3(ViewerTapProvider, { value: isTap, children: /* @__PURE__ */ jsxs2(ViewerRetargetContext.Provider, { value: retargetOn, children: [
|
|
1804
|
+
/* @__PURE__ */ jsxs2("group", { ref: lightsRef, children: [
|
|
1805
|
+
/* @__PURE__ */ jsx3("ambientLight", { color: theme.ambient, intensity: theme.ambientIntensity }),
|
|
1806
|
+
/* @__PURE__ */ jsx3(
|
|
1172
1807
|
"hemisphereLight",
|
|
1173
1808
|
{
|
|
1174
1809
|
args: [theme.hemisphereSky, theme.hemisphereGround, theme.hemisphereIntensity]
|
|
1175
1810
|
}
|
|
1176
1811
|
)
|
|
1177
1812
|
] }),
|
|
1178
|
-
/* @__PURE__ */
|
|
1179
|
-
/* @__PURE__ */
|
|
1180
|
-
|
|
1813
|
+
/* @__PURE__ */ jsx3("group", { ref: contentRef, children }),
|
|
1814
|
+
/* @__PURE__ */ jsx3(
|
|
1815
|
+
CadCameraControls,
|
|
1816
|
+
{
|
|
1817
|
+
controlsRef,
|
|
1818
|
+
scheme,
|
|
1819
|
+
freeOrbit,
|
|
1820
|
+
zoomTo
|
|
1821
|
+
}
|
|
1822
|
+
),
|
|
1823
|
+
showOrbitTarget ? /* @__PURE__ */ jsx3(TargetMarker, { controlsRef }) : null
|
|
1824
|
+
] }) });
|
|
1181
1825
|
};
|
|
1182
1826
|
var Viewer = forwardRef(function Viewer2({
|
|
1183
1827
|
children,
|
|
1184
1828
|
className,
|
|
1185
1829
|
style,
|
|
1186
|
-
projection = "
|
|
1830
|
+
projection = "orthographic",
|
|
1187
1831
|
controls = "toolpath",
|
|
1188
1832
|
freeOrbit = true,
|
|
1833
|
+
zoomTo = "cursor",
|
|
1834
|
+
recentreOnDoubleClick = true,
|
|
1835
|
+
retargetOnDoubleClick = true,
|
|
1836
|
+
showOrbitTarget = false,
|
|
1189
1837
|
theme,
|
|
1190
1838
|
onPointerMissed
|
|
1191
1839
|
}, ref) {
|
|
1192
|
-
const actionsRef =
|
|
1193
|
-
const resolved =
|
|
1194
|
-
const proxy =
|
|
1840
|
+
const actionsRef = useRef3(null);
|
|
1841
|
+
const resolved = useMemo4(() => resolveTheme(theme), [theme]);
|
|
1842
|
+
const proxy = useMemo4(
|
|
1195
1843
|
() => ({
|
|
1196
1844
|
fit: () => actionsRef.current?.fit(),
|
|
1197
1845
|
reset: () => actionsRef.current?.reset(),
|
|
@@ -1205,47 +1853,62 @@ var Viewer = forwardRef(function Viewer2({
|
|
|
1205
1853
|
const setControls = useCallback((next) => {
|
|
1206
1854
|
actionsRef.current = next;
|
|
1207
1855
|
}, []);
|
|
1208
|
-
const tracker =
|
|
1856
|
+
const tracker = useRef3(null);
|
|
1209
1857
|
const hold = useCallback((element) => {
|
|
1210
1858
|
tracker.current?.dispose();
|
|
1211
1859
|
tracker.current = element ? trackTaps(element) : null;
|
|
1212
1860
|
}, []);
|
|
1213
|
-
return /* @__PURE__ */
|
|
1214
|
-
|
|
1861
|
+
return /* @__PURE__ */ jsx3(ViewerControlsContext.Provider, { value: proxy, children: /* @__PURE__ */ jsx3(
|
|
1862
|
+
"div",
|
|
1215
1863
|
{
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
gl: { antialias: true, alpha: true, stencil: true, localClippingEnabled: true },
|
|
1221
|
-
onPointerMissed: (event) => {
|
|
1222
|
-
if (event.button !== 0) return;
|
|
1223
|
-
if (tracker.current?.isTap(event) ?? true) onPointerMissed?.();
|
|
1864
|
+
className,
|
|
1865
|
+
ref: hold,
|
|
1866
|
+
onMouseDown: (event) => {
|
|
1867
|
+
if (event.target instanceof HTMLCanvasElement) event.preventDefault();
|
|
1224
1868
|
},
|
|
1225
|
-
|
|
1226
|
-
|
|
1869
|
+
style: { height: "100%", width: "100%", ...style },
|
|
1870
|
+
children: /* @__PURE__ */ jsx3(
|
|
1871
|
+
Canvas,
|
|
1227
1872
|
{
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1873
|
+
orthographic: projection === "orthographic",
|
|
1874
|
+
camera: { fov: PERSPECTIVE_FOV, up: [0, 0, 1], position: [1, -1, 1] },
|
|
1875
|
+
dpr: [1, 2],
|
|
1876
|
+
frameloop: "demand",
|
|
1877
|
+
gl: { antialias: true, alpha: true, stencil: true, localClippingEnabled: true },
|
|
1878
|
+
onPointerMissed: (event) => {
|
|
1879
|
+
if (event.button !== 0) return;
|
|
1880
|
+
if (tracker.current?.isTap(event) ?? true) onPointerMissed?.();
|
|
1881
|
+
},
|
|
1882
|
+
children: /* @__PURE__ */ jsx3(
|
|
1883
|
+
ViewerScene,
|
|
1884
|
+
{
|
|
1885
|
+
setControls,
|
|
1886
|
+
projection,
|
|
1887
|
+
scheme: controls,
|
|
1888
|
+
freeOrbit,
|
|
1889
|
+
zoomTo,
|
|
1890
|
+
recentreOnDoubleClick,
|
|
1891
|
+
retargetOnDoubleClick,
|
|
1892
|
+
showOrbitTarget,
|
|
1893
|
+
theme: resolved,
|
|
1894
|
+
children
|
|
1895
|
+
}
|
|
1896
|
+
)
|
|
1897
|
+
},
|
|
1898
|
+
projection
|
|
1235
1899
|
)
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
|
-
) }) });
|
|
1900
|
+
}
|
|
1901
|
+
) });
|
|
1239
1902
|
});
|
|
1240
1903
|
|
|
1241
1904
|
// src/section-view.tsx
|
|
1242
|
-
import { useFrame as
|
|
1243
|
-
import { useCallback as useCallback2, useMemo as
|
|
1905
|
+
import { useFrame as useFrame4, useThree as useThree5 } from "@react-three/fiber";
|
|
1906
|
+
import { useCallback as useCallback2, useMemo as useMemo5, useRef as useRef4, useState } from "react";
|
|
1244
1907
|
import {
|
|
1245
1908
|
AlwaysStencilFunc,
|
|
1246
1909
|
BackSide,
|
|
1247
1910
|
DecrementWrapStencilOp,
|
|
1248
|
-
DoubleSide,
|
|
1911
|
+
DoubleSide as DoubleSide2,
|
|
1249
1912
|
FrontSide,
|
|
1250
1913
|
IncrementWrapStencilOp,
|
|
1251
1914
|
NotEqualStencilFunc,
|
|
@@ -1254,22 +1917,22 @@ import {
|
|
|
1254
1917
|
Raycaster as Raycaster2,
|
|
1255
1918
|
ReplaceStencilOp,
|
|
1256
1919
|
Vector2 as Vector22,
|
|
1257
|
-
Vector3 as
|
|
1920
|
+
Vector3 as Vector39
|
|
1258
1921
|
} from "three";
|
|
1259
1922
|
|
|
1260
1923
|
// src/render/directions.ts
|
|
1261
|
-
import { Vector3 as
|
|
1262
|
-
var CONE_AXIS = new
|
|
1924
|
+
import { Vector3 as Vector38 } from "three";
|
|
1925
|
+
var CONE_AXIS = new Vector38(0, 1, 0);
|
|
1263
1926
|
var LENGTH = 0.45;
|
|
1264
1927
|
var HEAD = 0.45;
|
|
1265
1928
|
var HEAD_RADIUS = 0.3;
|
|
1266
1929
|
var SHAFT_RADIUS = 0.09;
|
|
1267
1930
|
var GAP = 0.2;
|
|
1268
1931
|
function arrowPlacement(direction, box) {
|
|
1269
|
-
const center = box.getCenter(new
|
|
1270
|
-
const half = box.getSize(new
|
|
1932
|
+
const center = box.getCenter(new Vector38());
|
|
1933
|
+
const half = box.getSize(new Vector38()).multiplyScalar(0.5);
|
|
1271
1934
|
const radius = half.length() || 1;
|
|
1272
|
-
const axis = new
|
|
1935
|
+
const axis = new Vector38(direction.x, direction.y, direction.z);
|
|
1273
1936
|
if (axis.lengthSq() === 0) axis.set(0, 0, 1);
|
|
1274
1937
|
axis.normalize();
|
|
1275
1938
|
let exit = Number.POSITIVE_INFINITY;
|
|
@@ -1285,13 +1948,13 @@ function arrowPlacement(direction, box) {
|
|
|
1285
1948
|
}
|
|
1286
1949
|
|
|
1287
1950
|
// src/section-view.tsx
|
|
1288
|
-
import { jsx as
|
|
1951
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1289
1952
|
var DEFAULT_NORMAL = { x: 0, y: 0, z: 1 };
|
|
1290
|
-
var
|
|
1953
|
+
var FURNITURE2 = { [EXCLUDE_FROM_FRAME]: true };
|
|
1291
1954
|
function resolveSectionPlane(options, box) {
|
|
1292
1955
|
if (!options?.enabled || box.isEmpty()) return null;
|
|
1293
1956
|
const normal = options.plane?.normal ?? options.normal ?? DEFAULT_NORMAL;
|
|
1294
|
-
const axis = new
|
|
1957
|
+
const axis = new Vector39(normal.x, normal.y, normal.z);
|
|
1295
1958
|
if (axis.lengthSq() === 0) axis.set(0, 0, 1);
|
|
1296
1959
|
axis.normalize();
|
|
1297
1960
|
const bounds = sectionBounds(box, normal);
|
|
@@ -1319,31 +1982,31 @@ var SectionView = ({
|
|
|
1319
1982
|
showHandle,
|
|
1320
1983
|
onDrag
|
|
1321
1984
|
}) => {
|
|
1322
|
-
const camera =
|
|
1323
|
-
const size =
|
|
1324
|
-
const invalidate =
|
|
1325
|
-
const controls =
|
|
1326
|
-
const domElement =
|
|
1327
|
-
const capRef =
|
|
1328
|
-
const handleRef =
|
|
1985
|
+
const camera = useThree5((state) => state.camera);
|
|
1986
|
+
const size = useThree5((state) => state.size);
|
|
1987
|
+
const invalidate = useThree5((state) => state.invalidate);
|
|
1988
|
+
const controls = useThree5((state) => state.controls);
|
|
1989
|
+
const domElement = useThree5((state) => state.gl.domElement);
|
|
1990
|
+
const capRef = useRef4(null);
|
|
1991
|
+
const handleRef = useRef4(null);
|
|
1329
1992
|
const [hovered, setHovered] = useState(false);
|
|
1330
|
-
const dragging =
|
|
1331
|
-
const centre =
|
|
1332
|
-
const span =
|
|
1333
|
-
const clip =
|
|
1334
|
-
const capPosition =
|
|
1993
|
+
const dragging = useRef4(null);
|
|
1994
|
+
const centre = useMemo5(() => box.getCenter(new Vector39()), [box]);
|
|
1995
|
+
const span = useMemo5(() => box.getSize(new Vector39()).length(), [box]);
|
|
1996
|
+
const clip = useMemo5(() => [plane], [plane]);
|
|
1997
|
+
const capPosition = useMemo5(() => {
|
|
1335
1998
|
const point = centre.clone();
|
|
1336
1999
|
return point.addScaledVector(plane.normal, -(plane.constant + plane.normal.dot(centre)));
|
|
1337
2000
|
}, [centre, plane]);
|
|
1338
|
-
const capQuaternion =
|
|
1339
|
-
() => new Quaternion2().setFromUnitVectors(new
|
|
2001
|
+
const capQuaternion = useMemo5(
|
|
2002
|
+
() => new Quaternion2().setFromUnitVectors(new Vector39(0, 0, 1), plane.normal),
|
|
1340
2003
|
[plane]
|
|
1341
2004
|
);
|
|
1342
|
-
const handleQuaternion =
|
|
2005
|
+
const handleQuaternion = useMemo5(
|
|
1343
2006
|
() => new Quaternion2().setFromUnitVectors(CONE_AXIS, plane.normal.clone().negate()),
|
|
1344
2007
|
[plane]
|
|
1345
2008
|
);
|
|
1346
|
-
|
|
2009
|
+
useFrame4(() => {
|
|
1347
2010
|
const handle = handleRef.current;
|
|
1348
2011
|
if (!handle) return;
|
|
1349
2012
|
const length = screenLength(camera, capPosition, size, HANDLE_PIXELS);
|
|
@@ -1364,7 +2027,7 @@ var SectionView = ({
|
|
|
1364
2027
|
event.stopPropagation();
|
|
1365
2028
|
const view = camera.position.clone().sub(capPosition).normalize();
|
|
1366
2029
|
const surface = dragPlane(plane.normal.clone(), view, capPosition);
|
|
1367
|
-
const hit = event.ray.intersectPlane(surface, new
|
|
2030
|
+
const hit = event.ray.intersectPlane(surface, new Vector39());
|
|
1368
2031
|
const start = {
|
|
1369
2032
|
plane: surface,
|
|
1370
2033
|
from: hit ? hit.dot(plane.normal) : 0,
|
|
@@ -1381,7 +2044,7 @@ var SectionView = ({
|
|
|
1381
2044
|
-((native.clientY - rect.top) / rect.height) * 2 + 1
|
|
1382
2045
|
);
|
|
1383
2046
|
raycaster.setFromCamera(pointer, camera);
|
|
1384
|
-
const point = raycaster.ray.intersectPlane(start.plane, new
|
|
2047
|
+
const point = raycaster.ray.intersectPlane(start.plane, new Vector39());
|
|
1385
2048
|
if (!point) return;
|
|
1386
2049
|
onDrag(start.constant - (point.dot(plane.normal) - start.from));
|
|
1387
2050
|
invalidate();
|
|
@@ -1413,8 +2076,8 @@ var SectionView = ({
|
|
|
1413
2076
|
// Excluded from framing, all of it. The cap is a quad half again as wide as
|
|
1414
2077
|
// the part's diagonal, so a Fit that measured it would frame the cut rather
|
|
1415
2078
|
// than the part and leave the part a speck in the middle of it.
|
|
1416
|
-
/* @__PURE__ */
|
|
1417
|
-
/* @__PURE__ */
|
|
2079
|
+
/* @__PURE__ */ jsxs3("group", { userData: FURNITURE2, children: [
|
|
2080
|
+
/* @__PURE__ */ jsx4("mesh", { geometry, renderOrder: SECTION_RENDER_ORDER.stencil, raycast: () => null, children: /* @__PURE__ */ jsx4(
|
|
1418
2081
|
"meshBasicMaterial",
|
|
1419
2082
|
{
|
|
1420
2083
|
side: BackSide,
|
|
@@ -1429,7 +2092,7 @@ var SectionView = ({
|
|
|
1429
2092
|
clippingPlanes: clip
|
|
1430
2093
|
}
|
|
1431
2094
|
) }),
|
|
1432
|
-
/* @__PURE__ */
|
|
2095
|
+
/* @__PURE__ */ jsx4("mesh", { geometry, renderOrder: SECTION_RENDER_ORDER.stencil, raycast: () => null, children: /* @__PURE__ */ jsx4(
|
|
1433
2096
|
"meshBasicMaterial",
|
|
1434
2097
|
{
|
|
1435
2098
|
side: FrontSide,
|
|
@@ -1444,13 +2107,13 @@ var SectionView = ({
|
|
|
1444
2107
|
clippingPlanes: clip
|
|
1445
2108
|
}
|
|
1446
2109
|
) }),
|
|
1447
|
-
/* @__PURE__ */
|
|
1448
|
-
/* @__PURE__ */
|
|
1449
|
-
/* @__PURE__ */
|
|
2110
|
+
/* @__PURE__ */ jsx4("group", { ref: capRef, position: capPosition, quaternion: capQuaternion, children: /* @__PURE__ */ jsxs3("mesh", { renderOrder: SECTION_RENDER_ORDER.cap, raycast: () => null, children: [
|
|
2111
|
+
/* @__PURE__ */ jsx4("planeGeometry", { args: [span * 1.5, span * 1.5] }),
|
|
2112
|
+
/* @__PURE__ */ jsx4(
|
|
1450
2113
|
"meshBasicMaterial",
|
|
1451
2114
|
{
|
|
1452
2115
|
color: theme.sectionCap,
|
|
1453
|
-
side:
|
|
2116
|
+
side: DoubleSide2,
|
|
1454
2117
|
stencilWrite: true,
|
|
1455
2118
|
stencilRef: 0,
|
|
1456
2119
|
stencilFunc: NotEqualStencilFunc,
|
|
@@ -1460,7 +2123,7 @@ var SectionView = ({
|
|
|
1460
2123
|
}
|
|
1461
2124
|
)
|
|
1462
2125
|
] }) }),
|
|
1463
|
-
showHandle && onDrag ? /* @__PURE__ */
|
|
2126
|
+
showHandle && onDrag ? /* @__PURE__ */ jsxs3(
|
|
1464
2127
|
"group",
|
|
1465
2128
|
{
|
|
1466
2129
|
ref: handleRef,
|
|
@@ -1468,9 +2131,9 @@ var SectionView = ({
|
|
|
1468
2131
|
quaternion: handleQuaternion,
|
|
1469
2132
|
renderOrder: SECTION_RENDER_ORDER.handle,
|
|
1470
2133
|
children: [
|
|
1471
|
-
/* @__PURE__ */
|
|
1472
|
-
/* @__PURE__ */
|
|
1473
|
-
/* @__PURE__ */
|
|
2134
|
+
/* @__PURE__ */ jsxs3("mesh", { position: [0, 0.28, 0], ...grab, children: [
|
|
2135
|
+
/* @__PURE__ */ jsx4("coneGeometry", { args: [0.16, 0.34, 20] }),
|
|
2136
|
+
/* @__PURE__ */ jsx4(
|
|
1474
2137
|
"meshBasicMaterial",
|
|
1475
2138
|
{
|
|
1476
2139
|
color: hovered ? theme.hover : theme.sectionHandle,
|
|
@@ -1478,9 +2141,9 @@ var SectionView = ({
|
|
|
1478
2141
|
}
|
|
1479
2142
|
)
|
|
1480
2143
|
] }),
|
|
1481
|
-
/* @__PURE__ */
|
|
1482
|
-
/* @__PURE__ */
|
|
1483
|
-
/* @__PURE__ */
|
|
2144
|
+
/* @__PURE__ */ jsxs3("mesh", { position: [0, 0.08, 0], ...grab, children: [
|
|
2145
|
+
/* @__PURE__ */ jsx4("cylinderGeometry", { args: [0.045, 0.045, 0.4, 12] }),
|
|
2146
|
+
/* @__PURE__ */ jsx4(
|
|
1484
2147
|
"meshBasicMaterial",
|
|
1485
2148
|
{
|
|
1486
2149
|
color: hovered ? theme.hover : theme.sectionHandle,
|
|
@@ -1496,14 +2159,14 @@ var SectionView = ({
|
|
|
1496
2159
|
};
|
|
1497
2160
|
|
|
1498
2161
|
// src/content-box.ts
|
|
1499
|
-
import { useFrame as
|
|
1500
|
-
import { useRef as
|
|
2162
|
+
import { useFrame as useFrame5, useThree as useThree6 } from "@react-three/fiber";
|
|
2163
|
+
import { useRef as useRef5, useState as useState2 } from "react";
|
|
1501
2164
|
import { Box3 as Box34 } from "three";
|
|
1502
2165
|
function useContentBox() {
|
|
1503
|
-
const scene =
|
|
2166
|
+
const scene = useThree6((state) => state.scene);
|
|
1504
2167
|
const [box, setBox] = useState2(() => new Box34());
|
|
1505
|
-
const measured =
|
|
1506
|
-
|
|
2168
|
+
const measured = useRef5(false);
|
|
2169
|
+
useFrame5(() => {
|
|
1507
2170
|
if (measured.current) return;
|
|
1508
2171
|
const next = new Box34();
|
|
1509
2172
|
contentBounds(scene, next);
|
|
@@ -1515,7 +2178,7 @@ function useContentBox() {
|
|
|
1515
2178
|
}
|
|
1516
2179
|
|
|
1517
2180
|
// src/part-mesh.tsx
|
|
1518
|
-
import { Fragment
|
|
2181
|
+
import { Fragment, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1519
2182
|
var PartMesh = ({
|
|
1520
2183
|
model,
|
|
1521
2184
|
geometry,
|
|
@@ -1528,22 +2191,23 @@ var PartMesh = ({
|
|
|
1528
2191
|
activeDirection = null,
|
|
1529
2192
|
section,
|
|
1530
2193
|
onSectionChange,
|
|
2194
|
+
onAdjacency,
|
|
1531
2195
|
focusFeature = null,
|
|
1532
2196
|
onHover,
|
|
1533
2197
|
onPick,
|
|
1534
2198
|
theme,
|
|
1535
2199
|
showEdges = true
|
|
1536
2200
|
}) => {
|
|
1537
|
-
const { camera, controls, invalidate } =
|
|
2201
|
+
const { camera, controls, invalidate } = useThree7();
|
|
1538
2202
|
const viewerControls = useViewerControls();
|
|
1539
2203
|
const resolved = useStableTheme(theme);
|
|
1540
|
-
const currentTheme =
|
|
2204
|
+
const currentTheme = useRef6(resolved);
|
|
1541
2205
|
currentTheme.current = resolved;
|
|
1542
|
-
const part =
|
|
1543
|
-
const hoverRegion =
|
|
2206
|
+
const part = useMemo6(() => createPart(model, geometry, currentTheme.current), [geometry, model]);
|
|
2207
|
+
const hoverRegion = useRef6(null);
|
|
1544
2208
|
const box = useContentBox();
|
|
1545
|
-
const cut =
|
|
1546
|
-
const layers =
|
|
2209
|
+
const cut = useMemo6(() => resolveSectionPlane(section, box), [box, section]);
|
|
2210
|
+
const layers = useRef6({
|
|
1547
2211
|
selection,
|
|
1548
2212
|
candidates,
|
|
1549
2213
|
highlights,
|
|
@@ -1568,9 +2232,13 @@ var PartMesh = ({
|
|
|
1568
2232
|
);
|
|
1569
2233
|
invalidate();
|
|
1570
2234
|
}, [invalidate, part]);
|
|
1571
|
-
|
|
1572
|
-
const
|
|
1573
|
-
|
|
2235
|
+
useEffect5(() => () => part.dispose(), [part]);
|
|
2236
|
+
const adjacency = useMemo6(() => regionAdjacency(model, geometry), [model, geometry]);
|
|
2237
|
+
useEffect5(() => {
|
|
2238
|
+
onAdjacency?.(adjacency);
|
|
2239
|
+
}, [adjacency, onAdjacency]);
|
|
2240
|
+
const framed = useRef6(null);
|
|
2241
|
+
useEffect5(() => {
|
|
1574
2242
|
if (focusFeature === null || focusFeature === framed.current) return;
|
|
1575
2243
|
framed.current = focusFeature;
|
|
1576
2244
|
const box2 = part.boxForFeature(focusFeature);
|
|
@@ -1588,7 +2256,7 @@ var PartMesh = ({
|
|
|
1588
2256
|
part.setClippingPlanes(cut ? [cut.plane] : null);
|
|
1589
2257
|
invalidate();
|
|
1590
2258
|
}, [cut, invalidate, part]);
|
|
1591
|
-
const reportedSection =
|
|
2259
|
+
const reportedSection = useRef6("");
|
|
1592
2260
|
useLayoutEffect(() => {
|
|
1593
2261
|
const state = cut?.state ?? null;
|
|
1594
2262
|
const key = state ? `${state.constant}|${state.normal.x},${state.normal.y},${state.normal.z}` : "";
|
|
@@ -1608,7 +2276,9 @@ var PartMesh = ({
|
|
|
1608
2276
|
repaint();
|
|
1609
2277
|
}, [layerKey, repaint]);
|
|
1610
2278
|
const isTap = useTapGuard();
|
|
1611
|
-
const
|
|
2279
|
+
const retarget = useRetarget();
|
|
2280
|
+
const doubles = useMemo6(() => trackDoubleTaps(), []);
|
|
2281
|
+
const pickFor = (event, doubled = false) => {
|
|
1612
2282
|
const triangleIndex = event.faceIndex;
|
|
1613
2283
|
if (triangleIndex == null) return null;
|
|
1614
2284
|
const region = model.regionIndex.regionForTriangle(triangleIndex);
|
|
@@ -1623,6 +2293,7 @@ var PartMesh = ({
|
|
|
1623
2293
|
point: [event.point.x, event.point.y, event.point.z],
|
|
1624
2294
|
normal: [normal.x, normal.y, normal.z],
|
|
1625
2295
|
activeDirection,
|
|
2296
|
+
doubled,
|
|
1626
2297
|
viewDirection: viewDirection(camera, target),
|
|
1627
2298
|
modifiers: {
|
|
1628
2299
|
alt: source.altKey,
|
|
@@ -1653,21 +2324,38 @@ var PartMesh = ({
|
|
|
1653
2324
|
},
|
|
1654
2325
|
[box, cut, onSectionChange, section]
|
|
1655
2326
|
);
|
|
1656
|
-
return /* @__PURE__ */
|
|
1657
|
-
/* @__PURE__ */
|
|
2327
|
+
return /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
2328
|
+
/* @__PURE__ */ jsx5(
|
|
1658
2329
|
"primitive",
|
|
1659
2330
|
{
|
|
1660
2331
|
object: part.object,
|
|
1661
2332
|
onPointerMove: (event) => emitHover(pickFor(event)),
|
|
1662
|
-
onPointerOut: () =>
|
|
2333
|
+
onPointerOut: () => {
|
|
2334
|
+
emitHover(null);
|
|
2335
|
+
doubles.reset();
|
|
2336
|
+
},
|
|
1663
2337
|
onClick: (event) => {
|
|
2338
|
+
if (!isTap(event.nativeEvent)) {
|
|
2339
|
+
doubles.reset();
|
|
2340
|
+
return;
|
|
2341
|
+
}
|
|
2342
|
+
const doubled = doubles.isDouble(event.nativeEvent);
|
|
2343
|
+
const pick = pickFor(event, doubled);
|
|
2344
|
+
if (doubled && retarget) retarget(event.point);
|
|
2345
|
+
if (pick) onPick?.(pick);
|
|
2346
|
+
},
|
|
2347
|
+
onPointerUp: (event) => {
|
|
2348
|
+
if (event.nativeEvent.button !== 2) return;
|
|
1664
2349
|
if (!isTap(event.nativeEvent)) return;
|
|
1665
2350
|
const pick = pickFor(event);
|
|
1666
2351
|
if (pick) onPick?.(pick);
|
|
2352
|
+
},
|
|
2353
|
+
onContextMenu: (event) => {
|
|
2354
|
+
if (pickFor(event)) event.nativeEvent.preventDefault();
|
|
1667
2355
|
}
|
|
1668
2356
|
}
|
|
1669
2357
|
),
|
|
1670
|
-
cut ? /* @__PURE__ */
|
|
2358
|
+
cut ? /* @__PURE__ */ jsx5(
|
|
1671
2359
|
SectionView,
|
|
1672
2360
|
{
|
|
1673
2361
|
geometry,
|
|
@@ -1680,9 +2368,9 @@ var PartMesh = ({
|
|
|
1680
2368
|
) : null
|
|
1681
2369
|
] });
|
|
1682
2370
|
};
|
|
1683
|
-
var ORIGIN = new
|
|
1684
|
-
var UP = new
|
|
1685
|
-
var TARGET = new
|
|
2371
|
+
var ORIGIN = new Vector310();
|
|
2372
|
+
var UP = new Vector310(0, 0, 1);
|
|
2373
|
+
var TARGET = new Vector310();
|
|
1686
2374
|
function readTarget(controls) {
|
|
1687
2375
|
if (controls && typeof controls.getTarget === "function") {
|
|
1688
2376
|
return controls.getTarget(TARGET);
|
|
@@ -1691,14 +2379,14 @@ function readTarget(controls) {
|
|
|
1691
2379
|
return target ?? ORIGIN;
|
|
1692
2380
|
}
|
|
1693
2381
|
function useStableTheme(theme) {
|
|
1694
|
-
const held =
|
|
2382
|
+
const held = useRef6(null);
|
|
1695
2383
|
const next = resolveTheme(theme);
|
|
1696
2384
|
if (held.current === null || !themesEqual(held.current, next)) held.current = next;
|
|
1697
2385
|
return held.current;
|
|
1698
2386
|
}
|
|
1699
2387
|
|
|
1700
2388
|
// src/engine/geometry.ts
|
|
1701
|
-
import { Matrix4 as Matrix42, Mesh as
|
|
2389
|
+
import { Matrix4 as Matrix42, Mesh as Mesh3 } from "three";
|
|
1702
2390
|
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
1703
2391
|
import { STLLoader } from "three/examples/jsm/loaders/STLLoader.js";
|
|
1704
2392
|
var PartMeshError = class extends Error {
|
|
@@ -1773,7 +2461,7 @@ function parseStl(data, mesh) {
|
|
|
1773
2461
|
function singleMesh(scene) {
|
|
1774
2462
|
const meshes = [];
|
|
1775
2463
|
scene.traverse((object) => {
|
|
1776
|
-
if (object instanceof
|
|
2464
|
+
if (object instanceof Mesh3) meshes.push(object);
|
|
1777
2465
|
});
|
|
1778
2466
|
const [mesh] = meshes;
|
|
1779
2467
|
if (mesh === void 0 || meshes.length !== 1) {
|
|
@@ -1809,7 +2497,7 @@ function assertCounts(geometry, expected) {
|
|
|
1809
2497
|
}
|
|
1810
2498
|
function disposeMaterials(scene) {
|
|
1811
2499
|
scene.traverse((object) => {
|
|
1812
|
-
if (!(object instanceof
|
|
2500
|
+
if (!(object instanceof Mesh3)) return;
|
|
1813
2501
|
for (const material of Array.isArray(object.material) ? object.material : [object.material]) {
|
|
1814
2502
|
material.dispose();
|
|
1815
2503
|
}
|
|
@@ -1817,7 +2505,7 @@ function disposeMaterials(scene) {
|
|
|
1817
2505
|
}
|
|
1818
2506
|
|
|
1819
2507
|
// src/engine/normals.ts
|
|
1820
|
-
import { Float32BufferAttribute as
|
|
2508
|
+
import { Float32BufferAttribute as Float32BufferAttribute4 } from "three";
|
|
1821
2509
|
function smoothRegionNormals(geometry, regions) {
|
|
1822
2510
|
const position = geometry.getAttribute("position");
|
|
1823
2511
|
if (!position || geometry.index) return;
|
|
@@ -1883,7 +2571,7 @@ function smoothRegionNormals(geometry, regions) {
|
|
|
1883
2571
|
normals[vertex * 3 + 1] = y / length;
|
|
1884
2572
|
normals[vertex * 3 + 2] = z / length;
|
|
1885
2573
|
}
|
|
1886
|
-
geometry.setAttribute("normal", new
|
|
2574
|
+
geometry.setAttribute("normal", new Float32BufferAttribute4(normals, 3));
|
|
1887
2575
|
}
|
|
1888
2576
|
|
|
1889
2577
|
// src/engine/geometry-cache.ts
|
|
@@ -2250,12 +2938,12 @@ function readDirections(value, issues) {
|
|
|
2250
2938
|
}
|
|
2251
2939
|
const directions = [];
|
|
2252
2940
|
for (const [i, raw] of value.entries()) {
|
|
2253
|
-
const
|
|
2254
|
-
if (
|
|
2941
|
+
const vec2 = readVec3(raw);
|
|
2942
|
+
if (vec2 === null) {
|
|
2255
2943
|
issues.push(`candidateDirections[${i}] is not a vector`);
|
|
2256
2944
|
continue;
|
|
2257
2945
|
}
|
|
2258
|
-
directions.push(
|
|
2946
|
+
directions.push(vec2);
|
|
2259
2947
|
}
|
|
2260
2948
|
return directions;
|
|
2261
2949
|
}
|
|
@@ -2306,15 +2994,15 @@ function compareVersions(a, b) {
|
|
|
2306
2994
|
}
|
|
2307
2995
|
|
|
2308
2996
|
// src/engine/engine-part.tsx
|
|
2309
|
-
import { jsx as
|
|
2997
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
2310
2998
|
var EnginePart = ({ report, ...props }) => {
|
|
2311
|
-
const model =
|
|
2999
|
+
const model = useMemo7(() => normalizePartReport(report), [report]);
|
|
2312
3000
|
const resource = useGeometryResource(model);
|
|
2313
|
-
|
|
3001
|
+
useEffect6(() => {
|
|
2314
3002
|
engineGeometryCache.retain(resource);
|
|
2315
3003
|
return () => engineGeometryCache.release(resource);
|
|
2316
3004
|
}, [resource]);
|
|
2317
|
-
return /* @__PURE__ */
|
|
3005
|
+
return /* @__PURE__ */ jsx6(PartMesh, { model, geometry: resource.geometry, ...props });
|
|
2318
3006
|
};
|
|
2319
3007
|
function useGeometryResource(part) {
|
|
2320
3008
|
const resource = engineGeometryCache.get(part);
|
|
@@ -2350,11 +3038,12 @@ export {
|
|
|
2350
3038
|
sectionFromPick,
|
|
2351
3039
|
pickedStartDepth,
|
|
2352
3040
|
sectionPlane,
|
|
2353
|
-
screenLength,
|
|
2354
3041
|
dragPlane,
|
|
2355
3042
|
TAP_SLOP,
|
|
2356
3043
|
movedFar,
|
|
2357
3044
|
trackTaps,
|
|
3045
|
+
DOUBLE_TAP_MS,
|
|
3046
|
+
trackDoubleTaps,
|
|
2358
3047
|
useTapGuard,
|
|
2359
3048
|
visualSurfaces,
|
|
2360
3049
|
regionEdgesGeometry,
|
|
@@ -2362,6 +3051,7 @@ export {
|
|
|
2362
3051
|
buildRegionTexels,
|
|
2363
3052
|
buildRegionAttribute,
|
|
2364
3053
|
createPart,
|
|
3054
|
+
regionAdjacency,
|
|
2365
3055
|
FEATURE_TYPE_RANKS,
|
|
2366
3056
|
featureTypeRank,
|
|
2367
3057
|
rankOwners,
|
|
@@ -2376,19 +3066,48 @@ export {
|
|
|
2376
3066
|
EXCLUDE_FROM_FRAME,
|
|
2377
3067
|
defaultBounds,
|
|
2378
3068
|
aspectRatio,
|
|
3069
|
+
screenLength,
|
|
2379
3070
|
boundsFromBox,
|
|
2380
3071
|
contentBounds,
|
|
2381
3072
|
perspectiveFitDistance,
|
|
2382
3073
|
orthographicHalfHeight,
|
|
2383
3074
|
fitDistance,
|
|
2384
3075
|
startPosition,
|
|
3076
|
+
MIN_FRAME_RATIO,
|
|
3077
|
+
MAX_FRAME_RATIO,
|
|
3078
|
+
cameraLimits,
|
|
3079
|
+
targetBoundary,
|
|
2385
3080
|
applyProjection,
|
|
2386
3081
|
CAD_CAMERA_UP,
|
|
3082
|
+
adaptedUp,
|
|
2387
3083
|
cadViewDirections,
|
|
2388
3084
|
currentViewDirection,
|
|
2389
3085
|
ExtendedCameraControls,
|
|
2390
3086
|
CadCameraControls,
|
|
3087
|
+
retargetPose,
|
|
3088
|
+
ORBIT_TARGET_PIXELS,
|
|
3089
|
+
ORBIT_TARGET_RING_PIXELS,
|
|
3090
|
+
ORBIT_TARGET_RING_WIDTH,
|
|
3091
|
+
ORBIT_TARGET_COLOR,
|
|
3092
|
+
ORBIT_TARGET_RING_COLOR,
|
|
3093
|
+
ORBIT_TARGET_RING_OPACITY,
|
|
3094
|
+
ORBIT_TARGET_FLASH_MS,
|
|
3095
|
+
ORBIT_TARGET_FADE_MS,
|
|
3096
|
+
orbitTargetOpacity,
|
|
3097
|
+
VIEW_NAMES,
|
|
3098
|
+
VIEW_SIGNS,
|
|
3099
|
+
viewKind,
|
|
3100
|
+
viewVector,
|
|
3101
|
+
viewUp,
|
|
3102
|
+
squaredUp,
|
|
3103
|
+
CHAMFER,
|
|
3104
|
+
cubeZones,
|
|
3105
|
+
panelGeometry,
|
|
3106
|
+
cubeOutlineGeometry,
|
|
3107
|
+
labelGeometry,
|
|
3108
|
+
labelTexture,
|
|
2391
3109
|
useViewerControls,
|
|
3110
|
+
useRetarget,
|
|
2392
3111
|
Viewer,
|
|
2393
3112
|
CONE_AXIS,
|
|
2394
3113
|
HEAD,
|