@toolpath/viewer 1.4.0 → 2.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.
@@ -11,7 +11,7 @@ import {
11
11
  useRef as useRef6,
12
12
  useSyncExternalStore
13
13
  } from "react";
14
- import { Vector3 as Vector310 } from "three";
14
+ import { Vector3 as Vector311 } from "three";
15
15
 
16
16
  // src/model/directions.ts
17
17
  var EPSILON = 1e-6;
@@ -193,14 +193,31 @@ function applyHighlightLayers(part, layers, theme) {
193
193
  }
194
194
  }
195
195
 
196
+ // src/render/focus.ts
197
+ var DEFAULT_FOCUS_OPACITY = 0.15;
198
+ function focusOpacity(options) {
199
+ return Math.min(Math.max(options?.opacity ?? DEFAULT_FOCUS_OPACITY, 0), 1);
200
+ }
201
+ function focusStateKey(options, selection) {
202
+ return `${options === void 0 ? "off" : `on:${options.opacity ?? ""}`}|${selection.join(" ")}`;
203
+ }
204
+ function focusedRegions(model, selection) {
205
+ const regions = /* @__PURE__ */ new Set();
206
+ for (const tag of selection) {
207
+ for (const region of model.regionIndex.regionsForFeature(tag)) regions.add(region);
208
+ }
209
+ return regions;
210
+ }
211
+
196
212
  // src/render/section.ts
197
- import { Plane, Vector3 as Vector32 } from "three";
213
+ import { Plane, Vector3 as Vector33 } from "three";
198
214
 
199
215
  // src/render/camera.ts
200
216
  import { OrthographicCamera, Vector3 } from "three";
201
217
  var PERSPECTIVE_FOV = 30;
202
218
  var DEFAULT_FIT_MARGIN = 1.2;
203
219
  var EXCLUDE_FROM_FRAME = "viewerExcludeFromFrame";
220
+ var STOCK_OBJECT = "viewerStock";
204
221
  function defaultBounds() {
205
222
  return { center: new Vector3(0, 0, 0), radius: 1 };
206
223
  }
@@ -221,16 +238,30 @@ function boundsFromBox(box) {
221
238
  return { center, radius: radius > 0 ? radius : 1 };
222
239
  }
223
240
  function contentBounds(root, into) {
241
+ return measureBounds(root, into, false);
242
+ }
243
+ function partBounds(root, into) {
244
+ return measureBounds(root, into, true);
245
+ }
246
+ function measureBounds(root, into, partOnly) {
224
247
  into.makeEmpty();
225
248
  root.updateWorldMatrix(true, true);
226
249
  root.traverse((object) => {
227
- if (excludedFromFrame(object, root)) return;
250
+ if (partOnly ? excludedFromPart(object, root) : excludedFromFrame(object, root)) return;
228
251
  if ("isMesh" in object || "isLine" in object || "isPoints" in object) {
229
252
  into.expandByObject(object);
230
253
  }
231
254
  });
232
255
  return boundsFromBox(into);
233
256
  }
257
+ function excludedFromPart(object, root) {
258
+ let current = object;
259
+ while (current && current !== root) {
260
+ if (current.userData[STOCK_OBJECT]) return true;
261
+ current = current.parent;
262
+ }
263
+ return excludedFromFrame(object, root);
264
+ }
234
265
  function excludedFromFrame(object, root) {
235
266
  let current = object;
236
267
  while (current && current !== root) {
@@ -351,6 +382,206 @@ function currentViewDirection(camera, target, into) {
351
382
  return into.normalize();
352
383
  }
353
384
 
385
+ // src/render/measure.ts
386
+ import { Vector2, Vector3 as Vector32 } from "three";
387
+ var MEASURE_RENDER_ORDER = 7;
388
+ var AXIS_COLORS = { x: 16739179, y: 7331978, z: 7314431 };
389
+ var SNAP_PIXELS = {
390
+ vertex: 14,
391
+ midpoint: 12,
392
+ edge: 9
393
+ };
394
+ var SNAP_MARKER_PIXELS = {
395
+ vertex: 6,
396
+ midpoint: 5,
397
+ edge: 4,
398
+ face: 3.5
399
+ };
400
+ var MEASURE_MARKER_PIXELS = 3.5;
401
+ var MEASURE_LINE_PIXELS = {
402
+ line: 2,
403
+ leg: 1.5,
404
+ edge: 3
405
+ };
406
+ var DELTA_DASH_PIXELS = 6;
407
+ var ANGLE_ARC_FRACTION = 0.3;
408
+ var ANGLE_ARC_SEGMENTS = 24;
409
+ function snapAt(hit, edges, camera, viewport) {
410
+ const face = {
411
+ kind: "face",
412
+ point: hit.point.clone(),
413
+ normal: hit.normal.clone(),
414
+ edge: null
415
+ };
416
+ if (!edges) return face;
417
+ const positions = edges.positions;
418
+ const segments = Math.floor(positions.length / 6);
419
+ if (segments === 0) return face;
420
+ const inverse = edges.matrixWorld.clone().invert();
421
+ const p = hit.point.clone().applyMatrix4(inverse);
422
+ const n = hit.normal.clone().transformDirection(inverse);
423
+ const reach = screenLength(camera, hit.point, viewport, SNAP_PIXELS.vertex) * 1.5;
424
+ const reachSq = reach * reach;
425
+ const pointer = screenPoint(camera, hit.point, viewport);
426
+ const best = {
427
+ vertex: { distance: Number.POSITIVE_INFINITY, index: -1, t: 0 },
428
+ midpoint: { distance: Number.POSITIVE_INFINITY, index: -1, t: 0 },
429
+ edge: { distance: Number.POSITIVE_INFINITY, index: -1, t: 0 }
430
+ };
431
+ const a = new Vector32();
432
+ const b = new Vector32();
433
+ const ab = new Vector32();
434
+ const ap = new Vector32();
435
+ const q = new Vector32();
436
+ const pixel = new Vector2();
437
+ const consider = (kind, point, index, t) => {
438
+ const distance = screenPoint(
439
+ camera,
440
+ q.copy(point).applyMatrix4(edges.matrixWorld),
441
+ viewport,
442
+ pixel
443
+ ).distanceTo(pointer);
444
+ if (distance <= SNAP_PIXELS[kind] && distance < best[kind].distance) {
445
+ best[kind] = { distance, index, t };
446
+ }
447
+ };
448
+ for (let index = 0; index < segments; index += 1) {
449
+ const offset = index * 6;
450
+ a.set(positions[offset], positions[offset + 1], positions[offset + 2]);
451
+ b.set(positions[offset + 3], positions[offset + 4], positions[offset + 5]);
452
+ if (Math.abs(ap.subVectors(a, p).dot(n)) > reach) continue;
453
+ if (Math.abs(ap.subVectors(b, p).dot(n)) > reach) continue;
454
+ ab.subVectors(b, a);
455
+ ap.subVectors(p, a);
456
+ const lengthSq = ab.lengthSq();
457
+ const t = lengthSq === 0 ? 0 : Math.min(1, Math.max(0, ap.dot(ab) / lengthSq));
458
+ q.copy(a).addScaledVector(ab, t);
459
+ if (q.distanceToSquared(p) > reachSq) continue;
460
+ consider("vertex", a, index, 0);
461
+ consider("vertex", b, index, 1);
462
+ consider("midpoint", q.copy(a).addScaledVector(ab, 0.5), index, 0.5);
463
+ consider("edge", q.copy(a).addScaledVector(ab, t), index, t);
464
+ }
465
+ for (const kind of ["vertex", "midpoint", "edge"]) {
466
+ const found = best[kind];
467
+ if (found.index < 0) continue;
468
+ const offset = found.index * 6;
469
+ const start = new Vector32(positions[offset], positions[offset + 1], positions[offset + 2]);
470
+ const end = new Vector32(positions[offset + 3], positions[offset + 4], positions[offset + 5]);
471
+ const point = start.clone().lerp(end, found.t);
472
+ start.applyMatrix4(edges.matrixWorld);
473
+ end.applyMatrix4(edges.matrixWorld);
474
+ point.applyMatrix4(edges.matrixWorld);
475
+ return {
476
+ kind,
477
+ point,
478
+ normal: hit.normal.clone(),
479
+ edge: kind === "vertex" ? null : [start, end]
480
+ };
481
+ }
482
+ return face;
483
+ }
484
+ function screenPoint(camera, point, viewport, into = new Vector2()) {
485
+ const ndc = point.clone().project(camera);
486
+ return into.set((ndc.x + 1) / 2 * viewport.width, (1 - ndc.y) / 2 * viewport.height);
487
+ }
488
+ var POINTS_PER_MEASUREMENT = {
489
+ distance: 2,
490
+ angle: 3
491
+ };
492
+ function measurementFromPoints(kind, points, id) {
493
+ if (points.length < POINTS_PER_MEASUREMENT[kind]) return null;
494
+ const [a, b, c] = points;
495
+ if (kind === "distance") return { id, kind, points: [copy(a), copy(b)] };
496
+ return { id, kind, points: [copy(a), copy(b), copy(c)] };
497
+ }
498
+ function nextMeasurementId(held) {
499
+ return held.reduce((top, entry) => Math.max(top, entry.id), 0) + 1;
500
+ }
501
+ function distanceBetween(a, b) {
502
+ return Math.hypot(b.x - a.x, b.y - a.y, b.z - a.z);
503
+ }
504
+ function deltaBetween(a, b) {
505
+ return { x: b.x - a.x, y: b.y - a.y, z: b.z - a.z };
506
+ }
507
+ function midpoint(a, b) {
508
+ return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2, z: (a.z + b.z) / 2 };
509
+ }
510
+ function angleAt(vertex, a, b) {
511
+ const u = new Vector32(a.x - vertex.x, a.y - vertex.y, a.z - vertex.z);
512
+ const w = new Vector32(b.x - vertex.x, b.y - vertex.y, b.z - vertex.z);
513
+ if (u.lengthSq() === 0 || w.lengthSq() === 0) return 0;
514
+ return u.angleTo(w) * 180 / Math.PI;
515
+ }
516
+ function lockToAxis(from, to) {
517
+ const delta = deltaBetween(from, to);
518
+ let axis = "x";
519
+ for (const candidate of ["y", "z"]) {
520
+ if (Math.abs(delta[candidate]) > Math.abs(delta[axis])) axis = candidate;
521
+ }
522
+ return { axis, point: { ...from, [axis]: to[axis] } };
523
+ }
524
+ function deltaLegs(a, b) {
525
+ const delta = deltaBetween(a, b);
526
+ const threshold = Math.max(1e-9, distanceBetween(a, b) * 1e-6);
527
+ const legs = [];
528
+ let cursor = copy(a);
529
+ for (const axis of ["x", "y", "z"]) {
530
+ if (Math.abs(delta[axis]) <= threshold) continue;
531
+ const to = { ...cursor, [axis]: b[axis] };
532
+ legs.push({ axis, from: cursor, to, length: Math.abs(delta[axis]) });
533
+ cursor = to;
534
+ }
535
+ return legs.length > 1 ? legs : [];
536
+ }
537
+ function angleArc(vertex, a, b, radius, segments = ANGLE_ARC_SEGMENTS) {
538
+ const v = new Vector32(vertex.x, vertex.y, vertex.z);
539
+ const u = new Vector32(a.x, a.y, a.z).sub(v);
540
+ const w = new Vector32(b.x, b.y, b.z).sub(v);
541
+ if (u.lengthSq() === 0 || w.lengthSq() === 0) return [];
542
+ u.normalize();
543
+ w.normalize();
544
+ const theta = u.angleTo(w);
545
+ const sine = Math.sin(theta);
546
+ if (sine < 1e-6) {
547
+ return [v.clone().addScaledVector(u, radius), v.clone().addScaledVector(w, radius)];
548
+ }
549
+ const points = [];
550
+ for (let i = 0; i <= segments; i += 1) {
551
+ const t = i / segments;
552
+ const direction = u.clone().multiplyScalar(Math.sin((1 - t) * theta) / sine).addScaledVector(w, Math.sin(t * theta) / sine);
553
+ points.push(v.clone().addScaledVector(direction, radius));
554
+ }
555
+ return points;
556
+ }
557
+ function angleArcRadius(vertex, a, b) {
558
+ return Math.min(distanceBetween(vertex, a), distanceBetween(vertex, b)) * ANGLE_ARC_FRACTION;
559
+ }
560
+ function formatMillimetres(mm, digits = 2) {
561
+ return `${mm.toFixed(digits)} mm`;
562
+ }
563
+ function formatDegrees(degrees, digits = 1) {
564
+ return `${degrees.toFixed(digits)}\xB0`;
565
+ }
566
+ function measurementLabel(measurement, format = formatMillimetres) {
567
+ if (measurement.kind === "distance") {
568
+ const [a2, b2] = measurement.points;
569
+ return format(distanceBetween(a2, b2));
570
+ }
571
+ const [a, vertex, b] = measurement.points;
572
+ return formatDegrees(angleAt(vertex, a, b));
573
+ }
574
+ function measurementLabelAnchor(measurement) {
575
+ if (measurement.kind === "distance") return midpoint(measurement.points[0], measurement.points[1]);
576
+ const [a, vertex, b] = measurement.points;
577
+ const arc = angleArc(vertex, a, b, angleArcRadius(vertex, a, b) * 1.6, 2);
578
+ const middle = arc[1] ?? arc[0];
579
+ return middle ? { x: middle.x, y: middle.y, z: middle.z } : copy(vertex);
580
+ }
581
+ function copy(v) {
582
+ return { x: v.x, y: v.y, z: v.z };
583
+ }
584
+
354
585
  // src/render/section.ts
355
586
  var SECTION_RENDER_ORDER = {
356
587
  stencil: 1,
@@ -361,6 +592,7 @@ var SECTION_RENDER_ORDER = {
361
592
  };
362
593
  var START_DEPTH = 5e-3;
363
594
  var HANDLE_PIXELS = 78;
595
+ var SECTION_GIZMO_FRAME_SCALE = 1.1;
364
596
  var CAP_HATCH = {
365
597
  pitch: 9,
366
598
  line: 1.25,
@@ -371,7 +603,7 @@ var AXES_PLANE_SCALE = 1.2;
371
603
  var PREVIEW_SCALE = 0.125;
372
604
  var OUTLINE_SCALE = 1.2;
373
605
  var EPSILON2 = 1e-9;
374
- var ARROW_AXIS = new Vector32(0, 1, 0);
606
+ var ARROW_AXIS = new Vector33(0, 1, 0);
375
607
  var DEFAULT_SECTION_NORMAL = { x: 0, y: 0, z: 1 };
376
608
  var DISABLED_SECTION = {
377
609
  enabled: false,
@@ -380,8 +612,25 @@ var DISABLED_SECTION = {
380
612
  constant: 0,
381
613
  plane: null,
382
614
  depth: null,
615
+ cutDistance: 0,
383
616
  depthRange: null
384
617
  };
618
+ function sectionMeasurement(state) {
619
+ return `${(state.depth ?? state.cutDistance ?? 0).toFixed(2)} mm`;
620
+ }
621
+ function sectionDirectionColor(normal) {
622
+ const x = Math.abs(normal.x);
623
+ const y = Math.abs(normal.y);
624
+ const z = Math.abs(normal.z);
625
+ const total = x + y + z || 1;
626
+ const channel = (shift) => Math.round(
627
+ ((AXIS_COLORS.x >> shift & 255) * x + (AXIS_COLORS.y >> shift & 255) * y + (AXIS_COLORS.z >> shift & 255) * z) / total
628
+ );
629
+ return channel(16) << 16 | channel(8) << 8 | channel(0);
630
+ }
631
+ function sectionHandleColor(themeColor, directionColor2) {
632
+ return themeColor ?? directionColor2;
633
+ }
385
634
  function sectionOptionsFromState(state) {
386
635
  if (state.plane) {
387
636
  return { enabled: state.enabled, plane: state.plane, depth: state.depth ?? 0 };
@@ -389,8 +638,8 @@ function sectionOptionsFromState(state) {
389
638
  return { enabled: state.enabled, normal: state.normal, offset: state.offset };
390
639
  }
391
640
  function axesPlanes(box, eye) {
392
- const centre = box.getCenter(new Vector32());
393
- const extent = box.getSize(new Vector32());
641
+ const centre = box.getCenter(new Vector33());
642
+ const extent = box.getSize(new Vector33());
394
643
  const largest = Math.max(extent.x, extent.y, extent.z);
395
644
  const size = largest * AXES_PLANE_SCALE;
396
645
  const offset = largest * AXES_PLANE_OFFSET;
@@ -425,7 +674,7 @@ function axesPlanes(box, eye) {
425
674
  function hitUnderRay(raycaster, root) {
426
675
  for (const hit of raycaster.intersectObjects(root.children, true)) {
427
676
  if (!("isMesh" in hit.object) || !hit.face) continue;
428
- if (!hit.object.visible || excludedFromFrame(hit.object, root)) continue;
677
+ if (!hit.object.visible || excludedFromPart(hit.object, root)) continue;
429
678
  if (clippedAway(hit)) continue;
430
679
  return hit;
431
680
  }
@@ -447,21 +696,21 @@ function surfaceUnderRay(raycaster, root) {
447
696
  const normal = hit.face.normal.clone().transformDirection(hit.object.matrixWorld);
448
697
  return { point: hit.point, normal };
449
698
  }
450
- function sectionAnchor(box, plane, into = new Vector32()) {
699
+ function sectionAnchor(box, plane, into = new Vector33()) {
451
700
  const centre = box.getCenter(into);
452
701
  return centre.addScaledVector(plane.normal, -(plane.constant + plane.normal.dot(centre)));
453
702
  }
454
703
  var PICKED_SURFACE_LABEL = "Part surface";
455
704
  function unit(v) {
456
- const vector = new Vector32(v.x, v.y, v.z);
705
+ const vector = new Vector33(v.x, v.y, v.z);
457
706
  return vector.lengthSq() === 0 ? vector.set(0, 0, 1) : vector.normalize();
458
707
  }
459
708
  function clamp01(value) {
460
709
  return Math.min(1, Math.max(0, value));
461
710
  }
462
- function sectionBounds(box, normal) {
711
+ function projectedBounds(box, normal) {
463
712
  const axis = unit(normal);
464
- const corner = new Vector32();
713
+ const corner = new Vector33();
465
714
  let low = Number.POSITIVE_INFINITY;
466
715
  let high = Number.NEGATIVE_INFINITY;
467
716
  for (let i = 0; i < 8; i += 1) {
@@ -474,9 +723,17 @@ function sectionBounds(box, normal) {
474
723
  low = Math.min(low, distance);
475
724
  high = Math.max(high, distance);
476
725
  }
726
+ return { low, high };
727
+ }
728
+ function sectionBounds(box, normal) {
729
+ const { low, high } = projectedBounds(box, normal);
477
730
  const margin = Math.max((high - low) * 5e-3, 1e-6);
478
731
  return { min: -(high + margin), max: -(low - margin) };
479
732
  }
733
+ function sectionCutDistance(box, normal, constant) {
734
+ const { low, high } = projectedBounds(box, normal);
735
+ return Math.max(0, Math.min(high - low, -constant - low));
736
+ }
480
737
  function sectionConstant(bounds, t) {
481
738
  return bounds.max + clamp01(t) * (bounds.min - bounds.max);
482
739
  }
@@ -485,7 +742,7 @@ function sectionOffset(bounds, constant) {
485
742
  return span === 0 ? 0 : clamp01((constant - bounds.max) / span);
486
743
  }
487
744
  function sectionDepth(normal, anchor, constant) {
488
- return -unit(normal).dot(new Vector32(anchor.x, anchor.y, anchor.z)) - constant;
745
+ return -unit(normal).dot(new Vector33(anchor.x, anchor.y, anchor.z)) - constant;
489
746
  }
490
747
  function sectionDepthConstant(normal, anchor, depth) {
491
748
  return sectionDepth(normal, anchor, depth);
@@ -504,7 +761,7 @@ function sectionFromPick(surface, label = PICKED_SURFACE_LABEL) {
504
761
  };
505
762
  }
506
763
  function pickedStartDepth(box) {
507
- return box.getSize(new Vector32()).length() * START_DEPTH;
764
+ return box.getSize(new Vector33()).length() * START_DEPTH;
508
765
  }
509
766
  function sectionPlane(box, normal, offset, into = new Plane()) {
510
767
  const axis = unit(normal);
@@ -512,12 +769,12 @@ function sectionPlane(box, normal, offset, into = new Plane()) {
512
769
  }
513
770
  function dragPlane(axis, view, point, into = new Plane()) {
514
771
  const along = axis.lengthSq() === 0 ? ARROW_AXIS.clone() : axis.clone().normalize();
515
- const side = new Vector32().crossVectors(view, along);
772
+ const side = new Vector33().crossVectors(view, along);
516
773
  if (side.lengthSq() < EPSILON2) side.copy(perpendicular(along));
517
774
  return into.setFromNormalAndCoplanarPoint(side.cross(along).normalize(), point);
518
775
  }
519
776
  function perpendicular(axis) {
520
- const candidate = Math.abs(axis.x) < 0.9 ? new Vector32(1, 0, 0) : new Vector32(0, 1, 0);
777
+ const candidate = Math.abs(axis.x) < 0.9 ? new Vector33(1, 0, 0) : new Vector33(0, 1, 0);
521
778
  return candidate.cross(axis).normalize();
522
779
  }
523
780
 
@@ -593,15 +850,17 @@ import {
593
850
  Box3,
594
851
  Color,
595
852
  DataTexture,
853
+ DoubleSide,
596
854
  Float32BufferAttribute as Float32BufferAttribute2,
597
855
  Group,
598
856
  LineBasicMaterial,
599
857
  LineSegments,
600
858
  Mesh,
601
859
  MeshLambertMaterial,
860
+ RedFormat,
602
861
  RGBAFormat,
603
862
  UnsignedByteType,
604
- Vector3 as Vector33
863
+ Vector3 as Vector34
605
864
  } from "three";
606
865
 
607
866
  // src/render/edges.ts
@@ -700,6 +959,13 @@ function createPart(model, geometry, theme) {
700
959
  const state = new Uint8Array(width * 4);
701
960
  const stateTexture = new DataTexture(state, width, 1, RGBAFormat, UnsignedByteType);
702
961
  stateTexture.needsUpdate = true;
962
+ const opacity = new Uint8Array(width).fill(255);
963
+ const opacityTexture = new DataTexture(opacity, width, 1, RedFormat, UnsignedByteType);
964
+ opacityTexture.needsUpdate = true;
965
+ const focusState = { value: false };
966
+ const wireframe = { value: false };
967
+ let focused = false;
968
+ let currentTheme = theme;
703
969
  const material = new MeshLambertMaterial({
704
970
  color: theme.part,
705
971
  emissive: theme.partEmissive,
@@ -709,40 +975,64 @@ function createPart(model, geometry, theme) {
709
975
  polygonOffsetFactor: 1,
710
976
  polygonOffsetUnits: 1
711
977
  });
712
- material.onBeforeCompile = (shader) => {
713
- shader.uniforms["uRegionState"] = { value: stateTexture };
714
- shader.vertexShader = shader.vertexShader.replace(
715
- "#include <common>",
716
- `#include <common>
978
+ const configureShader = (target, opaqueFocusPass) => {
979
+ target.onBeforeCompile = (shader) => {
980
+ shader.uniforms["uRegionState"] = { value: stateTexture };
981
+ shader.uniforms["uRegionOpacity"] = { value: opacityTexture };
982
+ shader.uniforms["uWireframe"] = wireframe;
983
+ shader.uniforms["uFocus"] = focusState;
984
+ shader.uniforms["uOpaqueFocusPass"] = { value: opaqueFocusPass };
985
+ shader.vertexShader = shader.vertexShader.replace(
986
+ "#include <common>",
987
+ `#include <common>
717
988
  attribute float ${REGION_ATTRIBUTE};
718
989
  varying float vRegion;`
719
- ).replace(
720
- "#include <begin_vertex>",
721
- `#include <begin_vertex>
990
+ ).replace(
991
+ "#include <begin_vertex>",
992
+ `#include <begin_vertex>
722
993
  vRegion = ${REGION_ATTRIBUTE};`
723
- );
724
- shader.fragmentShader = shader.fragmentShader.replace(
725
- "#include <common>",
726
- `#include <common>
994
+ );
995
+ shader.fragmentShader = shader.fragmentShader.replace(
996
+ "#include <common>",
997
+ `#include <common>
727
998
  uniform sampler2D uRegionState;
999
+ uniform sampler2D uRegionOpacity;
1000
+ uniform bool uWireframe;
1001
+ uniform bool uFocus;
1002
+ uniform bool uOpaqueFocusPass;
728
1003
  varying float vRegion;
729
1004
  vec4 regionState;`
730
- ).replace(
731
- "#include <color_fragment>",
732
- `#include <color_fragment>
1005
+ ).replace(
1006
+ "#include <color_fragment>",
1007
+ `#include <color_fragment>
733
1008
  regionState = texelFetch(uRegionState, ivec2(int(vRegion + 0.5), 0), 0);
734
1009
  diffuseColor.rgb = mix(diffuseColor.rgb, regionState.rgb, regionState.a);
1010
+ float regionOpacity = texelFetch(uRegionOpacity, ivec2(int(vRegion + 0.5), 0), 0).r;
1011
+ if (uFocus && uOpaqueFocusPass && regionOpacity < 0.999) discard;
1012
+ if (uFocus && !uOpaqueFocusPass && regionOpacity >= 0.999) discard;
1013
+ diffuseColor.a *= regionOpacity;
1014
+ if (uWireframe) diffuseColor.a *= regionState.a;
735
1015
  `
736
- ).replace(
737
- "#include <emissivemap_fragment>",
738
- `#include <emissivemap_fragment>
1016
+ ).replace(
1017
+ "#include <emissivemap_fragment>",
1018
+ `#include <emissivemap_fragment>
739
1019
  totalEmissiveRadiance = mix(
740
1020
  totalEmissiveRadiance,
741
1021
  regionState.rgb * ${EMISSIVE_MIX.toFixed(2)},
742
1022
  regionState.a
743
1023
  );`
744
- );
1024
+ );
1025
+ };
745
1026
  };
1027
+ const focusMaterial = new MeshLambertMaterial({
1028
+ color: theme.part,
1029
+ emissive: theme.partEmissive,
1030
+ // A selected face can be viewed through the part from the side its normal
1031
+ // faces away from.
1032
+ side: DoubleSide
1033
+ });
1034
+ configureShader(focusMaterial, true);
1035
+ configureShader(material, false);
746
1036
  const mesh = new Mesh(geometry, material);
747
1037
  mesh.renderOrder = 3;
748
1038
  const edgeGeometry = regionEdgesGeometry(geometry, model);
@@ -755,10 +1045,25 @@ function createPart(model, geometry, theme) {
755
1045
  edges.renderOrder = 4;
756
1046
  edges.raycast = () => {
757
1047
  };
1048
+ const focusMesh = new Mesh(geometry, focusMaterial);
1049
+ focusMesh.renderOrder = 3;
1050
+ focusMesh.visible = false;
1051
+ focusMesh.raycast = () => {
1052
+ };
1053
+ const syncTransparency = () => {
1054
+ const transparent = wireframe.value || focused;
1055
+ if (material.transparent !== transparent || material.depthWrite !== !transparent) {
1056
+ material.transparent = transparent;
1057
+ material.depthWrite = !transparent;
1058
+ material.needsUpdate = true;
1059
+ }
1060
+ focusState.value = focused;
1061
+ focusMesh.visible = focused;
1062
+ };
758
1063
  const object = new Group();
759
- object.add(mesh, edges);
1064
+ object.add(focusMesh, mesh, edges);
760
1065
  const scratchColor = new Color();
761
- const scratchVector = new Vector33();
1066
+ const scratchVector = new Vector34();
762
1067
  const paintRegion = (region, color, weight) => {
763
1068
  const column = texels.get(region);
764
1069
  if (column === void 0) return;
@@ -796,6 +1101,27 @@ function createPart(model, geometry, theme) {
796
1101
  state.fill(0);
797
1102
  stateTexture.needsUpdate = true;
798
1103
  },
1104
+ setFocus(selection, focus) {
1105
+ const regions = focus === void 0 ? null : focusedRegions(model, selection);
1106
+ const outside = focusOpacity(focus);
1107
+ let changed = false;
1108
+ let hasTransparency = false;
1109
+ for (const [region, column] of texels) {
1110
+ const value = regions === null || regions.size === 0 || regions.has(region) ? 255 : Math.round(outside * 255);
1111
+ if (opacity[column] !== value) {
1112
+ opacity[column] = value;
1113
+ changed = true;
1114
+ }
1115
+ hasTransparency ||= value < 255;
1116
+ }
1117
+ focused = hasTransparency;
1118
+ syncTransparency();
1119
+ if (changed) opacityTexture.needsUpdate = true;
1120
+ },
1121
+ regionOpacity(region) {
1122
+ const column = texels.get(region);
1123
+ return column === void 0 ? null : (opacity[column] ?? 0) / 255;
1124
+ },
799
1125
  boxForFeature(tag) {
800
1126
  const regions = model.regionIndex.regionsForFeature(tag);
801
1127
  if (regions.length === 0) return null;
@@ -812,13 +1138,27 @@ function createPart(model, geometry, theme) {
812
1138
  setClippingPlanes(planes) {
813
1139
  const value = planes === null ? null : [...planes];
814
1140
  material.clippingPlanes = value;
1141
+ focusMaterial.clippingPlanes = value;
815
1142
  edgeMaterial.clippingPlanes = value;
816
1143
  },
817
1144
  setTheme(next) {
1145
+ currentTheme = next;
818
1146
  material.color.setHex(next.part);
819
1147
  material.emissive.setHex(next.partEmissive);
820
- edgeMaterial.color.setHex(next.edge);
821
- edgeMaterial.opacity = next.edgeOpacity;
1148
+ focusMaterial.color.setHex(next.part);
1149
+ focusMaterial.emissive.setHex(next.partEmissive);
1150
+ edgeMaterial.color.setHex(wireframe.value ? next.part : next.edge);
1151
+ edgeMaterial.opacity = wireframe.value ? 1 : next.edgeOpacity;
1152
+ },
1153
+ setDisplay(display, showEdges = true) {
1154
+ const enabled = display === "wireframe";
1155
+ if (wireframe.value !== enabled) {
1156
+ wireframe.value = enabled;
1157
+ syncTransparency();
1158
+ }
1159
+ edges.visible = enabled || showEdges;
1160
+ edgeMaterial.color.setHex(enabled ? currentTheme.part : currentTheme.edge);
1161
+ edgeMaterial.opacity = enabled ? 1 : currentTheme.edgeOpacity;
822
1162
  },
823
1163
  dispose() {
824
1164
  object.clear();
@@ -826,9 +1166,11 @@ function createPart(model, geometry, theme) {
826
1166
  geometry.deleteAttribute(REGION_ATTRIBUTE);
827
1167
  }
828
1168
  material.dispose();
1169
+ focusMaterial.dispose();
829
1170
  edgeGeometry.dispose();
830
1171
  edgeMaterial.dispose();
831
1172
  stateTexture.dispose();
1173
+ opacityTexture.dispose();
832
1174
  }
833
1175
  };
834
1176
  }
@@ -962,6 +1304,7 @@ function buildPick(input) {
962
1304
  triangleIndex: input.triangleIndex,
963
1305
  point: input.point,
964
1306
  normal: input.normal,
1307
+ ...input.pointer === void 0 ? {} : { pointer: input.pointer },
965
1308
  modifiers: input.modifiers ?? NO_MODIFIERS,
966
1309
  doubled: input.doubled ?? false
967
1310
  };
@@ -985,7 +1328,7 @@ import {
985
1328
  useMemo as useMemo4,
986
1329
  useRef as useRef3
987
1330
  } from "react";
988
- import { Box3 as Box33, Vector3 as Vector37 } from "three";
1331
+ import { Box3 as Box33, Vector3 as Vector38 } from "three";
989
1332
 
990
1333
  // src/camera.tsx
991
1334
  import { useFrame, useThree as useThree2 } from "@react-three/fiber";
@@ -1001,10 +1344,96 @@ import {
1001
1344
  Raycaster,
1002
1345
  Sphere,
1003
1346
  Spherical,
1004
- Vector2,
1005
- Vector3 as Vector34,
1347
+ Vector2 as Vector22,
1348
+ Vector3 as Vector35,
1006
1349
  Vector4
1007
1350
  } from "three";
1351
+
1352
+ // src/render/control-schemes.ts
1353
+ var CONTROL_SCHEME_OPTIONS = [
1354
+ { value: "toolpath", label: "Toolpath" },
1355
+ { value: "fusion", label: "Fusion" },
1356
+ { value: "alias", label: "Alias" },
1357
+ { value: "inventor", label: "Inventor" },
1358
+ { value: "solidworks", label: "SolidWorks" },
1359
+ { value: "tinkercad", label: "Tinkercad" },
1360
+ { value: "powermill", label: "PowerMill" },
1361
+ { value: "onshape", label: "Onshape" }
1362
+ ];
1363
+ var DEFAULT_TOUCHES = {
1364
+ one: "rotate",
1365
+ two: "dolly-truck",
1366
+ three: "truck"
1367
+ };
1368
+ var wheelFor = (orthographic) => orthographic ? "zoom" : "dolly";
1369
+ var mapping = (environment, mouse, options, touches = DEFAULT_TOUCHES) => ({
1370
+ mouse: {
1371
+ left: "none",
1372
+ middle: "none",
1373
+ right: "none",
1374
+ wheel: wheelFor(environment.orthographic),
1375
+ ...mouse
1376
+ },
1377
+ touches,
1378
+ ...options
1379
+ });
1380
+ var resolveControlScheme = (scheme, environment) => {
1381
+ const { shift, ctrl } = environment.modifiers;
1382
+ switch (scheme) {
1383
+ case "fusion":
1384
+ case "inventor": {
1385
+ const rotating = shift;
1386
+ return mapping(
1387
+ environment,
1388
+ { middle: rotating ? "rotate" : "truck", wheel: "none" },
1389
+ { usesCadWheel: true, immediate: true },
1390
+ {
1391
+ one: "rotate",
1392
+ two: rotating ? "rotate" : "truck",
1393
+ three: "truck"
1394
+ }
1395
+ );
1396
+ }
1397
+ case "solidworks":
1398
+ return mapping(
1399
+ environment,
1400
+ { middle: shift ? "zoom" : ctrl ? "truck" : "rotate" },
1401
+ { usesCadWheel: false, immediate: false }
1402
+ );
1403
+ case "alias":
1404
+ return mapping(
1405
+ environment,
1406
+ { left: "rotate", middle: "truck" },
1407
+ { usesCadWheel: false, immediate: false }
1408
+ );
1409
+ case "tinkercad":
1410
+ return mapping(
1411
+ environment,
1412
+ { right: shift ? "truck" : "rotate" },
1413
+ { usesCadWheel: false, immediate: false }
1414
+ );
1415
+ case "powermill":
1416
+ return mapping(
1417
+ environment,
1418
+ { middle: shift ? "truck" : "rotate" },
1419
+ { usesCadWheel: false, immediate: false }
1420
+ );
1421
+ case "onshape":
1422
+ return mapping(
1423
+ environment,
1424
+ { middle: "truck", right: "rotate" },
1425
+ { usesCadWheel: false, immediate: false }
1426
+ );
1427
+ case "toolpath":
1428
+ return mapping(
1429
+ environment,
1430
+ { left: "rotate", right: "truck" },
1431
+ { usesCadWheel: false, immediate: false }
1432
+ );
1433
+ }
1434
+ };
1435
+
1436
+ // src/render/controls.ts
1008
1437
  CameraControls.install({
1009
1438
  THREE: {
1010
1439
  Box3: Box32,
@@ -1013,8 +1442,8 @@ CameraControls.install({
1013
1442
  Raycaster,
1014
1443
  Sphere,
1015
1444
  Spherical,
1016
- Vector2,
1017
- Vector3: Vector34,
1445
+ Vector2: Vector22,
1446
+ Vector3: Vector35,
1018
1447
  Vector4
1019
1448
  }
1020
1449
  });
@@ -1032,13 +1461,14 @@ var ExtendedCameraControls = class extends CameraControls {
1032
1461
  #attached = false;
1033
1462
  #autoUpEnabled = false;
1034
1463
  #shiftPressed = false;
1464
+ #ctrlPressed = false;
1035
1465
  #wheelHandler = null;
1036
1466
  // Scratch objects — `#onPointerMove` and `#adaptUpVector` run at pointer and
1037
1467
  // frame rate respectively, so neither may allocate.
1038
1468
  #spherical = new Spherical();
1039
- #scratchA = new Vector34();
1040
- #scratchB = new Vector34();
1041
- #scratchC = new Vector34();
1469
+ #scratchA = new Vector35();
1470
+ #scratchB = new Vector35();
1471
+ #scratchC = new Vector35();
1042
1472
  constructor(camera, domElement, options = {}) {
1043
1473
  super(camera, domElement);
1044
1474
  this.#domElement = domElement;
@@ -1101,7 +1531,7 @@ var ExtendedCameraControls = class extends CameraControls {
1101
1531
  view?.removeEventListener("keyup", this.#onModifierChange);
1102
1532
  view?.removeEventListener("blur", this.#onWindowBlur);
1103
1533
  this.#disableAutoUp();
1104
- this.#disableFusionWheel();
1534
+ this.#disableCadWheel();
1105
1535
  }
1106
1536
  dispose() {
1107
1537
  this.detach();
@@ -1114,29 +1544,38 @@ var ExtendedCameraControls = class extends CameraControls {
1114
1544
  */
1115
1545
  applyScheme(scheme) {
1116
1546
  this.#scheme = scheme;
1117
- this.mouseButtons.left = CameraControls.ACTION.NONE;
1118
- this.mouseButtons.middle = CameraControls.ACTION.NONE;
1119
- this.mouseButtons.right = CameraControls.ACTION.NONE;
1120
- this.mouseButtons.wheel = CameraControls.ACTION.NONE;
1121
- this.touches.one = CameraControls.ACTION.TOUCH_ROTATE;
1122
- this.touches.two = CameraControls.ACTION.TOUCH_DOLLY_TRUCK;
1123
- this.touches.three = CameraControls.ACTION.TOUCH_TRUCK;
1124
- this.smoothTime = DEFAULT_SMOOTH_TIME;
1125
- this.draggingSmoothTime = DEFAULT_SMOOTH_TIME;
1126
- this.#disableFusionWheel();
1127
- if (scheme === "fusion") {
1128
- const rotating = this.#shiftPressed;
1129
- this.mouseButtons.middle = rotating ? CameraControls.ACTION.ROTATE : CameraControls.ACTION.TRUCK;
1130
- this.touches.two = rotating ? CameraControls.ACTION.TOUCH_ROTATE : CameraControls.ACTION.TOUCH_TRUCK;
1131
- this.smoothTime = 0;
1132
- this.draggingSmoothTime = 0;
1133
- this.#enableFusionWheel();
1134
- return;
1547
+ const mapping2 = resolveControlScheme(scheme, {
1548
+ orthographic: this.camera instanceof OrthographicCamera2,
1549
+ modifiers: { shift: this.#shiftPressed, ctrl: this.#ctrlPressed }
1550
+ });
1551
+ this.#applyMapping(mapping2);
1552
+ }
1553
+ #applyMapping(mapping2) {
1554
+ const action = {
1555
+ none: CameraControls.ACTION.NONE,
1556
+ rotate: CameraControls.ACTION.ROTATE,
1557
+ truck: CameraControls.ACTION.TRUCK,
1558
+ zoom: CameraControls.ACTION.ZOOM,
1559
+ dolly: CameraControls.ACTION.DOLLY
1560
+ };
1561
+ const touchAction = {
1562
+ rotate: CameraControls.ACTION.TOUCH_ROTATE,
1563
+ truck: CameraControls.ACTION.TOUCH_TRUCK,
1564
+ "dolly-truck": CameraControls.ACTION.TOUCH_DOLLY_TRUCK
1565
+ };
1566
+ this.#disableCadWheel();
1567
+ this.mouseButtons.left = action[mapping2.mouse.left];
1568
+ this.mouseButtons.middle = action[mapping2.mouse.middle];
1569
+ this.mouseButtons.right = action[mapping2.mouse.right];
1570
+ this.mouseButtons.wheel = action[mapping2.mouse.wheel];
1571
+ this.touches.one = touchAction[mapping2.touches.one];
1572
+ this.touches.two = touchAction[mapping2.touches.two];
1573
+ this.touches.three = touchAction[mapping2.touches.three];
1574
+ this.smoothTime = mapping2.immediate ? 0 : DEFAULT_SMOOTH_TIME;
1575
+ this.draggingSmoothTime = mapping2.immediate ? 0 : DEFAULT_SMOOTH_TIME;
1576
+ if (mapping2.usesCadWheel) {
1577
+ this.#enableCadWheel();
1135
1578
  }
1136
- this.mouseButtons.left = CameraControls.ACTION.ROTATE;
1137
- this.mouseButtons.right = CameraControls.ACTION.TRUCK;
1138
- this.mouseButtons.middle = CameraControls.ACTION.TRUCK;
1139
- this.mouseButtons.wheel = this.camera instanceof OrthographicCamera2 ? CameraControls.ACTION.ZOOM : CameraControls.ACTION.DOLLY;
1140
1579
  }
1141
1580
  setFreeOrbit(freeOrbit) {
1142
1581
  if (this.#freeOrbit === freeOrbit) {
@@ -1174,13 +1613,16 @@ var ExtendedCameraControls = class extends CameraControls {
1174
1613
  this.#autoUpEnabled = false;
1175
1614
  this.removeEventListener("update", this.#adaptUpVector);
1176
1615
  }
1177
- #enableFusionWheel() {
1178
- this.#wheelHandler = (event) => this.#onFusionWheel(event);
1616
+ #enableCadWheel() {
1617
+ if (!this.#attached || this.#wheelHandler) {
1618
+ return;
1619
+ }
1620
+ this.#wheelHandler = (event) => this.#onCadWheel(event);
1179
1621
  this.#domElement.addEventListener("wheel", this.#wheelHandler, {
1180
1622
  passive: false
1181
1623
  });
1182
1624
  }
1183
- #disableFusionWheel() {
1625
+ #disableCadWheel() {
1184
1626
  if (!this.#wheelHandler) {
1185
1627
  return;
1186
1628
  }
@@ -1223,7 +1665,7 @@ var ExtendedCameraControls = class extends CameraControls {
1223
1665
  void this.rotateTo(azimuth, polar, false);
1224
1666
  this.update(0);
1225
1667
  };
1226
- #onFusionWheel = (event) => {
1668
+ #onCadWheel = (event) => {
1227
1669
  event.preventDefault();
1228
1670
  if (event.ctrlKey) {
1229
1671
  void this.zoom(-event.deltaY * FUSION_PINCH_ZOOM_SCALE, false);
@@ -1241,19 +1683,18 @@ var ExtendedCameraControls = class extends CameraControls {
1241
1683
  this.dispatchEvent({ type: "control" });
1242
1684
  };
1243
1685
  #onModifierChange = (event) => {
1244
- this.#setShiftPressed(event.shiftKey);
1686
+ this.#setModifiers(event.shiftKey, event.ctrlKey);
1245
1687
  };
1246
1688
  #onWindowBlur = () => {
1247
- this.#setShiftPressed(false);
1689
+ this.#setModifiers(false, false);
1248
1690
  };
1249
- #setShiftPressed(pressed) {
1250
- if (this.#shiftPressed === pressed) {
1691
+ #setModifiers(shift, ctrl) {
1692
+ if (this.#shiftPressed === shift && this.#ctrlPressed === ctrl) {
1251
1693
  return;
1252
1694
  }
1253
- this.#shiftPressed = pressed;
1254
- if (this.#scheme === "fusion") {
1255
- this.applyScheme(this.#scheme);
1256
- }
1695
+ this.#shiftPressed = shift;
1696
+ this.#ctrlPressed = ctrl;
1697
+ this.applyScheme(this.#scheme);
1257
1698
  }
1258
1699
  };
1259
1700
 
@@ -1369,10 +1810,10 @@ function counted(notify) {
1369
1810
  import { useFrame as useFrame2, useThree as useThree3 } from "@react-three/fiber";
1370
1811
  import { useEffect as useEffect3, useMemo as useMemo3, useRef as useRef2 } from "react";
1371
1812
  import {
1372
- DoubleSide,
1813
+ DoubleSide as DoubleSide2,
1373
1814
  RingGeometry,
1374
1815
  SphereGeometry,
1375
- Vector3 as Vector35
1816
+ Vector3 as Vector36
1376
1817
  } from "three";
1377
1818
 
1378
1819
  // src/render/target.ts
@@ -1415,8 +1856,8 @@ var TargetMarker = ({ controlsRef }) => {
1415
1856
  const heldUntil = useRef2(0);
1416
1857
  const painted = useRef2(-1);
1417
1858
  const placed = useRef2(false);
1418
- const lastTarget = useMemo3(() => new Vector35(), []);
1419
- const scratch = useMemo3(() => new Vector35(), []);
1859
+ const lastTarget = useMemo3(() => new Vector36(), []);
1860
+ const scratch = useMemo3(() => new Vector36(), []);
1420
1861
  useEffect3(() => {
1421
1862
  const controls = controlsRef.current;
1422
1863
  if (!controls) return void 0;
@@ -1482,7 +1923,7 @@ var TargetMarker = ({ controlsRef }) => {
1482
1923
  opacity: 0,
1483
1924
  depthTest: false,
1484
1925
  depthWrite: false,
1485
- side: DoubleSide
1926
+ side: DoubleSide2
1486
1927
  }
1487
1928
  )
1488
1929
  }
@@ -1516,7 +1957,7 @@ import {
1516
1957
  CanvasTexture,
1517
1958
  Float32BufferAttribute as Float32BufferAttribute3,
1518
1959
  SRGBColorSpace,
1519
- Vector3 as Vector36
1960
+ Vector3 as Vector37
1520
1961
  } from "three";
1521
1962
  var VIEW_NAMES = [
1522
1963
  // The six faces.
@@ -1600,12 +2041,12 @@ function viewUp(direction) {
1600
2041
  return { x: 0, y: 0, z: 1 };
1601
2042
  }
1602
2043
  function squaredUp(direction, currentUp) {
1603
- const forward = new Vector36(direction.x, direction.y, direction.z).normalize();
2044
+ const forward = new Vector37(direction.x, direction.y, direction.z).normalize();
1604
2045
  const canonical = viewUp(direction);
1605
- const zero = new Vector36(canonical.x, canonical.y, canonical.z);
2046
+ const zero = new Vector37(canonical.x, canonical.y, canonical.z);
1606
2047
  zero.addScaledVector(forward, -forward.dot(zero)).normalize();
1607
- const quarter = new Vector36().crossVectors(forward, zero);
1608
- const current = new Vector36(currentUp.x, currentUp.y, currentUp.z);
2048
+ const quarter = new Vector37().crossVectors(forward, zero);
2049
+ const current = new Vector37(currentUp.x, currentUp.y, currentUp.z);
1609
2050
  const rolls = [zero, quarter, zero.clone().negate(), quarter.clone().negate()];
1610
2051
  const nearest = rolls.reduce(
1611
2052
  (best, roll) => roll.dot(current) > best.dot(current) ? roll : best
@@ -1623,11 +2064,11 @@ function cubeZones(chamfer = CHAMFER) {
1623
2064
  const direction = viewVector(name);
1624
2065
  const [sx, sy, sz] = VIEW_SIGNS[name];
1625
2066
  const sign = (axis) => VIEW_SIGNS[name][axis] ?? 0;
1626
- const normal = new Vector36(direction.x, direction.y, direction.z);
2067
+ const normal = new Vector37(direction.x, direction.y, direction.z);
1627
2068
  let polygon;
1628
2069
  if (kind === "face") {
1629
2070
  const up = viewUp(direction);
1630
- const upVector = new Vector36(up.x, up.y, up.z);
2071
+ const upVector = new Vector37(up.x, up.y, up.z);
1631
2072
  const right = normal.clone().negate().cross(upVector).normalize();
1632
2073
  const square = [
1633
2074
  [-1, -1],
@@ -1649,7 +2090,7 @@ function cubeZones(chamfer = CHAMFER) {
1649
2090
  [chamfer, 1, -chamfer]
1650
2091
  ];
1651
2092
  polygon = rectangle.map(
1652
- ([onA, onB, onC]) => new Vector36().setComponent(a, sign(a) * onA).setComponent(b, sign(b) * onB).setComponent(c, onC)
2093
+ ([onA, onB, onC]) => new Vector37().setComponent(a, sign(a) * onA).setComponent(b, sign(b) * onB).setComponent(c, onC)
1653
2094
  );
1654
2095
  } else {
1655
2096
  const triangle = [
@@ -1657,11 +2098,11 @@ function cubeZones(chamfer = CHAMFER) {
1657
2098
  [chamfer, 1, chamfer],
1658
2099
  [chamfer, chamfer, 1]
1659
2100
  ];
1660
- polygon = triangle.map(([onX, onY, onZ]) => new Vector36(sx * onX, sy * onY, sz * onZ));
2101
+ polygon = triangle.map(([onX, onY, onZ]) => new Vector37(sx * onX, sy * onY, sz * onZ));
1661
2102
  }
1662
2103
  const [first, second, third] = polygon;
1663
2104
  if (first && second && third) {
1664
- const facing = new Vector36().subVectors(second, first).cross(new Vector36().subVectors(third, first));
2105
+ const facing = new Vector37().subVectors(second, first).cross(new Vector37().subVectors(third, first));
1665
2106
  if (facing.dot(normal) < 0) polygon.reverse();
1666
2107
  }
1667
2108
  return { name, kind, direction, polygon: polygon.map(vec) };
@@ -1791,10 +2232,10 @@ var ViewerScene = ({
1791
2232
  const framedRef = useRef3(null);
1792
2233
  const scratchBox = useMemo4(() => new Box33(), []);
1793
2234
  const scratchBoundary = useMemo4(() => new Box33(), []);
1794
- const scratchDirection = useMemo4(() => new Vector37(), []);
1795
- const scratchView = useMemo4(() => new Vector37(), []);
1796
- const scratchTarget = useMemo4(() => new Vector37(), []);
1797
- const scratchPosition = useMemo4(() => new Vector37(), []);
2235
+ const scratchDirection = useMemo4(() => new Vector38(), []);
2236
+ const scratchView = useMemo4(() => new Vector38(), []);
2237
+ const scratchTarget = useMemo4(() => new Vector38(), []);
2238
+ const scratchPosition = useMemo4(() => new Vector38(), []);
1798
2239
  const applyLimits = useCallback(
1799
2240
  (bounds, framed) => {
1800
2241
  controlsRef.current?.applyLimits(
@@ -1843,8 +2284,8 @@ var ViewerScene = ({
1843
2284
  (bounds) => {
1844
2285
  const controls = controlsRef.current;
1845
2286
  if (!controls) return false;
1846
- const target = controls.getTarget(new Vector37());
1847
- const direction = currentViewDirection(camera, target, new Vector37());
2287
+ const target = controls.getTarget(new Vector38());
2288
+ const direction = currentViewDirection(camera, target, new Vector38());
1848
2289
  const distance = fitDistance(projection, size, bounds, DEFAULT_FIT_MARGIN);
1849
2290
  const position = direction.multiplyScalar(distance).add(bounds.center);
1850
2291
  void controls.setLookAt(
@@ -1869,8 +2310,8 @@ var ViewerScene = ({
1869
2310
  const fitContent = useCallback(() => {
1870
2311
  const controls = controlsRef.current;
1871
2312
  if (!controls) return false;
1872
- const target = controls.getTarget(new Vector37());
1873
- return frame(currentViewDirection(camera, target, new Vector37()), true);
2313
+ const target = controls.getTarget(new Vector38());
2314
+ return frame(currentViewDirection(camera, target, new Vector38()), true);
1874
2315
  }, [camera, frame]);
1875
2316
  const resetContent = useCallback(() => {
1876
2317
  const bounds = measure();
@@ -2025,7 +2466,8 @@ var Viewer = forwardRef(function Viewer2({
2025
2466
  return /* @__PURE__ */ jsx3(ViewerControlsContext.Provider, { value: proxy, children: /* @__PURE__ */ jsx3(SectionStoreContext.Provider, { value: sectionStore, children: /* @__PURE__ */ jsx3(
2026
2467
  "div",
2027
2468
  {
2028
- className,
2469
+ className: ["viewer-root", className].filter(Boolean).join(" "),
2470
+ "data-viewer-root": "true",
2029
2471
  ref: hold,
2030
2472
  onMouseDown: (event) => {
2031
2473
  if (event.target instanceof HTMLCanvasElement) event.preventDefault();
@@ -2034,11 +2476,20 @@ var Viewer = forwardRef(function Viewer2({
2034
2476
  children: /* @__PURE__ */ jsx3(
2035
2477
  Canvas,
2036
2478
  {
2479
+ className: "viewer-canvas-container",
2480
+ "data-viewer-canvas-container": "true",
2037
2481
  orthographic: projection === "orthographic",
2038
2482
  camera: { fov: PERSPECTIVE_FOV, up: [0, 0, 1], position: [1, -1, 1] },
2039
2483
  dpr: [1, 2],
2040
2484
  frameloop: "demand",
2041
2485
  gl: { antialias: true, alpha: true, stencil: true, localClippingEnabled: true },
2486
+ onCreated: ({ gl }) => {
2487
+ const canvas = gl.domElement;
2488
+ canvas.classList.add("viewer-canvas");
2489
+ canvas.dataset.viewerCanvas = "true";
2490
+ canvas.parentElement?.classList.add("viewer-canvas-frame");
2491
+ canvas.parentElement?.setAttribute("data-viewer-canvas-frame", "true");
2492
+ },
2042
2493
  onPointerMissed: (event) => {
2043
2494
  if (event.button !== 0) return;
2044
2495
  if (tracker.current?.isTap(event) ?? true) onPointerMissed?.();
@@ -2070,29 +2521,31 @@ import { useFrame as useFrame4, useThree as useThree5 } from "@react-three/fiber
2070
2521
  import { useCallback as useCallback2, useEffect as useEffect5, useLayoutEffect, useMemo as useMemo5, useRef as useRef4, useState } from "react";
2071
2522
  import {
2072
2523
  Color as Color3,
2524
+ DoubleSide as DoubleSide4,
2525
+ EdgesGeometry,
2073
2526
  Mesh as Mesh3,
2074
2527
  Plane as Plane2,
2075
2528
  PlaneGeometry,
2076
2529
  Quaternion as Quaternion2,
2077
2530
  Raycaster as Raycaster2,
2078
2531
  Scene,
2079
- Vector2 as Vector23,
2080
- Vector3 as Vector39
2532
+ Vector2 as Vector24,
2533
+ Vector3 as Vector310
2081
2534
  } from "three";
2082
2535
 
2083
2536
  // src/render/directions.ts
2084
- import { Vector3 as Vector38 } from "three";
2085
- var CONE_AXIS = new Vector38(0, 1, 0);
2537
+ import { Vector3 as Vector39 } from "three";
2538
+ var CONE_AXIS = new Vector39(0, 1, 0);
2086
2539
  var LENGTH = 0.45;
2087
2540
  var HEAD = 0.45;
2088
2541
  var HEAD_RADIUS = 0.3;
2089
2542
  var SHAFT_RADIUS = 0.09;
2090
2543
  var GAP = 0.2;
2091
2544
  function arrowPlacement(direction, box) {
2092
- const center = box.getCenter(new Vector38());
2093
- const half = box.getSize(new Vector38()).multiplyScalar(0.5);
2545
+ const center = box.getCenter(new Vector39());
2546
+ const half = box.getSize(new Vector39()).multiplyScalar(0.5);
2094
2547
  const radius = half.length() || 1;
2095
- const axis = new Vector38(direction.x, direction.y, direction.z);
2548
+ const axis = new Vector39(direction.x, direction.y, direction.z);
2096
2549
  if (axis.lengthSq() === 0) axis.set(0, 0, 1);
2097
2550
  axis.normalize();
2098
2551
  let exit = Number.POSITIVE_INFINITY;
@@ -2113,7 +2566,7 @@ import {
2113
2566
  BackSide,
2114
2567
  Color as Color2,
2115
2568
  DecrementWrapStencilOp,
2116
- DoubleSide as DoubleSide2,
2569
+ DoubleSide as DoubleSide3,
2117
2570
  FrontSide,
2118
2571
  IncrementWrapStencilOp,
2119
2572
  LinearFilter,
@@ -2121,7 +2574,7 @@ import {
2121
2574
  NotEqualStencilFunc,
2122
2575
  ReplaceStencilOp,
2123
2576
  ShaderMaterial,
2124
- Vector2 as Vector22,
2577
+ Vector2 as Vector23,
2125
2578
  WebGLRenderTarget
2126
2579
  } from "three";
2127
2580
  var VERTEX = (
@@ -2180,7 +2633,7 @@ function createMaskTarget() {
2180
2633
  function createCapMaterial(mask) {
2181
2634
  const uniforms = {
2182
2635
  uMask: { value: mask },
2183
- uResolution: { value: new Vector22(1, 1) },
2636
+ uResolution: { value: new Vector23(1, 1) },
2184
2637
  uFill: { value: new Color2() },
2185
2638
  uHatch: { value: new Color2() },
2186
2639
  uOutline: { value: new Color2() },
@@ -2192,7 +2645,7 @@ function createCapMaterial(mask) {
2192
2645
  vertexShader: VERTEX,
2193
2646
  fragmentShader: FRAGMENT,
2194
2647
  uniforms,
2195
- side: DoubleSide2,
2648
+ side: DoubleSide3,
2196
2649
  stencilWrite: true,
2197
2650
  stencilRef: 0,
2198
2651
  stencilFunc: NotEqualStencilFunc,
@@ -2238,7 +2691,7 @@ function createStencilMaterials(clip) {
2238
2691
  function createMaskMaterial() {
2239
2692
  return new MeshBasicMaterial2({
2240
2693
  color: 16777215,
2241
- side: DoubleSide2,
2694
+ side: DoubleSide3,
2242
2695
  stencilWrite: true,
2243
2696
  stencilRef: 0,
2244
2697
  stencilFunc: NotEqualStencilFunc,
@@ -2249,12 +2702,12 @@ function createMaskMaterial() {
2249
2702
  }
2250
2703
 
2251
2704
  // src/section-view.tsx
2252
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
2705
+ import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
2253
2706
  var FURNITURE2 = { [EXCLUDE_FROM_FRAME]: true };
2254
2707
  function resolveSectionPlane(options, box) {
2255
2708
  if (!options?.enabled || box.isEmpty()) return null;
2256
2709
  const normal = options.plane?.normal ?? options.normal ?? DEFAULT_SECTION_NORMAL;
2257
- const axis = new Vector39(normal.x, normal.y, normal.z);
2710
+ const axis = new Vector310(normal.x, normal.y, normal.z);
2258
2711
  if (axis.lengthSq() === 0) axis.set(0, 0, 1);
2259
2712
  axis.normalize();
2260
2713
  const bounds = sectionBounds(box, normal);
@@ -2270,16 +2723,18 @@ function resolveSectionPlane(options, box) {
2270
2723
  constant,
2271
2724
  plane: options.plane ?? null,
2272
2725
  depth: anchor === null ? null : sectionDepth(normal, anchor, constant),
2726
+ cutDistance: sectionCutDistance(box, normal, constant),
2273
2727
  depthRange: anchor === null ? null : sectionDepthRange(bounds, normal, anchor)
2274
2728
  }
2275
2729
  };
2276
2730
  }
2277
- var Z = new Vector39(0, 0, 1);
2731
+ var Z = new Vector310(0, 0, 1);
2278
2732
  var SectionView = ({
2279
2733
  geometry,
2280
2734
  box,
2281
2735
  plane,
2282
2736
  theme,
2737
+ handleColor,
2283
2738
  showHandle,
2284
2739
  onDrag
2285
2740
  }) => {
@@ -2293,7 +2748,10 @@ var SectionView = ({
2293
2748
  const handleRef = useRef4(null);
2294
2749
  const [hovered, setHovered] = useState(false);
2295
2750
  const dragging = useRef4(null);
2296
- const span = useMemo5(() => box.getSize(new Vector39()).length(), [box]);
2751
+ const span = useMemo5(() => box.getSize(new Vector310()).length(), [box]);
2752
+ const frameSize = span * SECTION_GIZMO_FRAME_SCALE;
2753
+ const directionColor2 = useMemo5(() => sectionDirectionColor(plane.normal), [plane]);
2754
+ const resolvedHandleColor = sectionHandleColor(handleColor, directionColor2);
2297
2755
  const clip = useMemo5(() => [plane], [plane]);
2298
2756
  const capPosition = useMemo5(() => sectionAnchor(box, plane), [box, plane]);
2299
2757
  const capQuaternion = useMemo5(() => new Quaternion2().setFromUnitVectors(Z, plane.normal), [plane]);
@@ -2301,6 +2759,10 @@ var SectionView = ({
2301
2759
  () => new Quaternion2().setFromUnitVectors(CONE_AXIS, plane.normal.clone().negate()),
2302
2760
  [plane]
2303
2761
  );
2762
+ const frameSurface = useMemo5(() => new PlaneGeometry(frameSize, frameSize), [frameSize]);
2763
+ useEffect5(() => () => frameSurface.dispose(), [frameSurface]);
2764
+ const frameEdges = useMemo5(() => new EdgesGeometry(frameSurface), [frameSurface]);
2765
+ useEffect5(() => () => frameEdges.dispose(), [frameEdges]);
2304
2766
  const stencils = useMemo5(() => createStencilMaterials([]), []);
2305
2767
  useEffect5(() => () => stencils.forEach((material) => material.dispose()), [stencils]);
2306
2768
  useLayoutEffect(() => {
@@ -2330,7 +2792,7 @@ var SectionView = ({
2330
2792
  maskScene.maskMesh.quaternion.copy(capQuaternion);
2331
2793
  }, [capPosition, capQuaternion, maskScene]);
2332
2794
  useLayoutEffect(() => {
2333
- const buffer = gl.getDrawingBufferSize(new Vector23());
2795
+ const buffer = gl.getDrawingBufferSize(new Vector24());
2334
2796
  mask.setSize(buffer.x, buffer.y);
2335
2797
  cap.uniforms.uResolution.value.copy(buffer);
2336
2798
  applyCapTheme(cap, theme, dpr);
@@ -2368,7 +2830,7 @@ var SectionView = ({
2368
2830
  event.stopPropagation();
2369
2831
  const view = camera.position.clone().sub(capPosition).normalize();
2370
2832
  const surface = dragPlane(plane.normal.clone(), view, capPosition);
2371
- const hit = event.ray.intersectPlane(surface, new Vector39());
2833
+ const hit = event.ray.intersectPlane(surface, new Vector310());
2372
2834
  const start = {
2373
2835
  plane: surface,
2374
2836
  from: hit ? hit.dot(plane.normal) : 0,
@@ -2377,7 +2839,7 @@ var SectionView = ({
2377
2839
  dragging.current = start;
2378
2840
  setControlsEnabled(false);
2379
2841
  const raycaster = new Raycaster2();
2380
- const pointer = new Vector23();
2842
+ const pointer = new Vector24();
2381
2843
  const move = (native) => {
2382
2844
  const rect = domElement.getBoundingClientRect();
2383
2845
  pointer.set(
@@ -2385,7 +2847,7 @@ var SectionView = ({
2385
2847
  -((native.clientY - rect.top) / rect.height) * 2 + 1
2386
2848
  );
2387
2849
  raycaster.setFromCamera(pointer, camera);
2388
- const point = raycaster.ray.intersectPlane(start.plane, new Vector39());
2850
+ const point = raycaster.ray.intersectPlane(start.plane, new Vector310());
2389
2851
  if (!point) return;
2390
2852
  onDrag(start.constant - (point.dot(plane.normal) - start.from));
2391
2853
  invalidate();
@@ -2447,37 +2909,81 @@ var SectionView = ({
2447
2909
  raycast: () => null
2448
2910
  }
2449
2911
  ),
2450
- showHandle && onDrag ? /* @__PURE__ */ jsxs3(
2451
- "group",
2452
- {
2453
- ref: handleRef,
2454
- position: capPosition,
2455
- quaternion: handleQuaternion,
2456
- renderOrder: SECTION_RENDER_ORDER.handle,
2457
- children: [
2458
- /* @__PURE__ */ jsxs3("mesh", { position: [0, 0.28, 0], ...grab, children: [
2459
- /* @__PURE__ */ jsx4("coneGeometry", { args: [0.16, 0.34, 20] }),
2460
- /* @__PURE__ */ jsx4(
2461
- "meshBasicMaterial",
2462
- {
2463
- color: hovered ? theme.hover : theme.sectionHandle,
2464
- depthTest: false
2465
- }
2466
- )
2467
- ] }),
2468
- /* @__PURE__ */ jsxs3("mesh", { position: [0, 0.08, 0], ...grab, children: [
2469
- /* @__PURE__ */ jsx4("cylinderGeometry", { args: [0.045, 0.045, 0.4, 12] }),
2470
- /* @__PURE__ */ jsx4(
2912
+ showHandle && onDrag ? /* @__PURE__ */ jsxs3(Fragment, { children: [
2913
+ /* @__PURE__ */ jsxs3(
2914
+ "group",
2915
+ {
2916
+ position: capPosition,
2917
+ quaternion: capQuaternion,
2918
+ renderOrder: SECTION_RENDER_ORDER.outline,
2919
+ children: [
2920
+ /* @__PURE__ */ jsx4("mesh", { geometry: frameSurface, raycast: () => null, children: /* @__PURE__ */ jsx4(
2471
2921
  "meshBasicMaterial",
2472
2922
  {
2473
- color: hovered ? theme.hover : theme.sectionHandle,
2474
- depthTest: false
2923
+ color: directionColor2,
2924
+ transparent: true,
2925
+ opacity: 0.18,
2926
+ side: DoubleSide4,
2927
+ depthTest: false,
2928
+ depthWrite: false
2475
2929
  }
2476
- )
2477
- ] })
2478
- ]
2479
- }
2480
- ) : null
2930
+ ) }),
2931
+ /* @__PURE__ */ jsx4("lineSegments", { geometry: frameEdges, raycast: () => null, children: /* @__PURE__ */ jsx4("lineBasicMaterial", { color: directionColor2, depthTest: false, depthWrite: false }) })
2932
+ ]
2933
+ }
2934
+ ),
2935
+ /* @__PURE__ */ jsxs3(
2936
+ "group",
2937
+ {
2938
+ ref: handleRef,
2939
+ position: capPosition,
2940
+ quaternion: handleQuaternion,
2941
+ renderOrder: SECTION_RENDER_ORDER.handle,
2942
+ children: [
2943
+ /* @__PURE__ */ jsxs3("mesh", { ...grab, children: [
2944
+ /* @__PURE__ */ jsx4("sphereGeometry", { args: [0.1, 16, 12] }),
2945
+ /* @__PURE__ */ jsx4(
2946
+ "meshBasicMaterial",
2947
+ {
2948
+ color: hovered ? theme.hover : resolvedHandleColor,
2949
+ depthTest: false
2950
+ }
2951
+ )
2952
+ ] }),
2953
+ /* @__PURE__ */ jsxs3("mesh", { ...grab, children: [
2954
+ /* @__PURE__ */ jsx4("cylinderGeometry", { args: [0.035, 0.035, 0.9, 12] }),
2955
+ /* @__PURE__ */ jsx4(
2956
+ "meshBasicMaterial",
2957
+ {
2958
+ color: hovered ? theme.hover : resolvedHandleColor,
2959
+ depthTest: false
2960
+ }
2961
+ )
2962
+ ] }),
2963
+ /* @__PURE__ */ jsxs3("mesh", { position: [0, 0.61, 0], ...grab, children: [
2964
+ /* @__PURE__ */ jsx4("coneGeometry", { args: [0.15, 0.3, 20] }),
2965
+ /* @__PURE__ */ jsx4(
2966
+ "meshBasicMaterial",
2967
+ {
2968
+ color: hovered ? theme.hover : resolvedHandleColor,
2969
+ depthTest: false
2970
+ }
2971
+ )
2972
+ ] }),
2973
+ /* @__PURE__ */ jsxs3("mesh", { position: [0, -0.61, 0], rotation: [Math.PI, 0, 0], ...grab, children: [
2974
+ /* @__PURE__ */ jsx4("coneGeometry", { args: [0.15, 0.3, 20] }),
2975
+ /* @__PURE__ */ jsx4(
2976
+ "meshBasicMaterial",
2977
+ {
2978
+ color: hovered ? theme.hover : resolvedHandleColor,
2979
+ depthTest: false
2980
+ }
2981
+ )
2982
+ ] })
2983
+ ]
2984
+ }
2985
+ )
2986
+ ] }) : null
2481
2987
  ] })
2482
2988
  );
2483
2989
  };
@@ -2493,7 +2999,7 @@ function useContentBox() {
2493
2999
  useFrame5(() => {
2494
3000
  if (measured.current) return;
2495
3001
  const next = new Box34();
2496
- contentBounds(scene, next);
3002
+ partBounds(scene, next);
2497
3003
  if (next.isEmpty()) return;
2498
3004
  measured.current = true;
2499
3005
  setBox(next);
@@ -2502,16 +3008,18 @@ function useContentBox() {
2502
3008
  }
2503
3009
 
2504
3010
  // src/part-mesh.tsx
2505
- import { Fragment, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
3011
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
2506
3012
  var PartMesh = ({
2507
3013
  model,
2508
3014
  geometry,
2509
3015
  selection = [],
3016
+ focus,
2510
3017
  candidates = [],
2511
3018
  highlights = [],
2512
3019
  regionHighlights = [],
2513
3020
  pickedRegions = [],
2514
3021
  hoveredFeatureIds = [],
3022
+ hover = true,
2515
3023
  activeDirection = null,
2516
3024
  section,
2517
3025
  onSectionChange,
@@ -2520,7 +3028,8 @@ var PartMesh = ({
2520
3028
  onHover,
2521
3029
  onPick,
2522
3030
  theme,
2523
- showEdges = true
3031
+ showEdges = true,
3032
+ display = "solid"
2524
3033
  }) => {
2525
3034
  const { camera, controls, invalidate } = useThree7();
2526
3035
  const viewerControls = useViewerControls();
@@ -2529,6 +3038,8 @@ var PartMesh = ({
2529
3038
  currentTheme.current = resolved;
2530
3039
  const part = useMemo6(() => createPart(model, geometry, currentTheme.current), [geometry, model]);
2531
3040
  const hoverRegion = useRef6(null);
3041
+ const onHoverRef = useRef6(onHover);
3042
+ onHoverRef.current = onHover;
2532
3043
  const box = useContentBox();
2533
3044
  const store = useSectionStore();
2534
3045
  const shared = useSyncExternalStore(store.subscribe, store.get, store.get);
@@ -2578,10 +3089,19 @@ var PartMesh = ({
2578
3089
  part.setTheme(resolved);
2579
3090
  repaint();
2580
3091
  }, [part, repaint, resolved]);
3092
+ const focusRef = useRef6(focus);
3093
+ focusRef.current = focus;
3094
+ const selectionRef = useRef6(selection);
3095
+ selectionRef.current = selection;
3096
+ const focusKey = focusStateKey(focus, selection);
2581
3097
  useLayoutEffect2(() => {
2582
- part.edges.visible = showEdges;
3098
+ part.setFocus(selectionRef.current, focusRef.current);
2583
3099
  invalidate();
2584
- }, [invalidate, part, showEdges]);
3100
+ }, [focusKey, invalidate, part]);
3101
+ useLayoutEffect2(() => {
3102
+ part.setDisplay(display, showEdges);
3103
+ invalidate();
3104
+ }, [display, invalidate, part, showEdges]);
2585
3105
  useLayoutEffect2(() => {
2586
3106
  part.setClippingPlanes(cut ? [cut.plane] : null);
2587
3107
  invalidate();
@@ -2624,6 +3144,7 @@ var PartMesh = ({
2624
3144
  triangleIndex,
2625
3145
  point: [event.point.x, event.point.y, event.point.z],
2626
3146
  normal: [normal.x, normal.y, normal.z],
3147
+ pointer: { clientX: source.clientX, clientY: source.clientY },
2627
3148
  activeDirection,
2628
3149
  doubled,
2629
3150
  viewDirection: viewDirection(camera, target),
@@ -2643,8 +3164,12 @@ var PartMesh = ({
2643
3164
  repaint();
2644
3165
  onHover?.(next);
2645
3166
  };
2646
- const onHoverRef = useRef6(onHover);
2647
- onHoverRef.current = onHover;
3167
+ useLayoutEffect2(() => {
3168
+ if (hover || hoverRegion.current === null) return;
3169
+ hoverRegion.current = null;
3170
+ repaint();
3171
+ onHoverRef.current?.(null);
3172
+ }, [hover, repaint]);
2648
3173
  useLayoutEffect2(() => {
2649
3174
  if (!engaged || hoverRegion.current === null) return;
2650
3175
  hoverRegion.current = null;
@@ -2655,18 +3180,21 @@ var PartMesh = ({
2655
3180
  (constant) => {
2656
3181
  if (!cut) return;
2657
3182
  const anchor = options?.plane?.point;
3183
+ const bounds = sectionBounds(box, cut.state.normal);
3184
+ const offset = sectionOffset(bounds, constant);
2658
3185
  const state = {
2659
3186
  ...cut.state,
2660
3187
  constant,
2661
- offset: sectionOffset(sectionBounds(box, cut.state.normal), constant),
2662
- depth: anchor ? sectionDepth(cut.state.normal, anchor, constant) : null
3188
+ offset,
3189
+ depth: anchor ? sectionDepth(cut.state.normal, anchor, constant) : null,
3190
+ cutDistance: sectionCutDistance(box, cut.state.normal, constant)
2663
3191
  };
2664
3192
  if (!controlled) store.set(sectionOptionsFromState(state));
2665
3193
  onSectionChange?.(state);
2666
3194
  },
2667
3195
  [box, controlled, cut, onSectionChange, options, store]
2668
3196
  );
2669
- return /* @__PURE__ */ jsxs4(Fragment, { children: [
3197
+ return /* @__PURE__ */ jsxs4(Fragment2, { children: [
2670
3198
  /* @__PURE__ */ jsx5(
2671
3199
  "primitive",
2672
3200
  {
@@ -2675,7 +3203,7 @@ var PartMesh = ({
2675
3203
  pressedWhileEngaged.current = engaged;
2676
3204
  },
2677
3205
  onPointerMove: (event) => {
2678
- if (!engaged) emitHover(pickFor(event));
3206
+ if (!engaged && hover) emitHover(pickFor(event));
2679
3207
  },
2680
3208
  onPointerOut: () => {
2681
3209
  emitHover(null);
@@ -2711,15 +3239,16 @@ var PartMesh = ({
2711
3239
  box,
2712
3240
  plane: cut.plane,
2713
3241
  theme: resolved,
3242
+ handleColor: theme?.sectionHandle,
2714
3243
  showHandle: !controlled || onSectionChange !== void 0,
2715
3244
  onDrag: !controlled || onSectionChange ? dragSection : void 0
2716
3245
  }
2717
3246
  ) : null
2718
3247
  ] });
2719
3248
  };
2720
- var ORIGIN = new Vector310();
2721
- var UP = new Vector310(0, 0, 1);
2722
- var TARGET = new Vector310();
3249
+ var ORIGIN = new Vector311();
3250
+ var UP = new Vector311(0, 0, 1);
3251
+ var TARGET = new Vector311();
2723
3252
  function readTarget(controls) {
2724
3253
  if (controls && typeof controls.getTarget === "function") {
2725
3254
  return controls.getTarget(TARGET);
@@ -3378,6 +3907,7 @@ export {
3378
3907
  PERSPECTIVE_FOV,
3379
3908
  DEFAULT_FIT_MARGIN,
3380
3909
  EXCLUDE_FROM_FRAME,
3910
+ STOCK_OBJECT,
3381
3911
  defaultBounds,
3382
3912
  aspectRatio,
3383
3913
  screenLength,
@@ -3397,8 +3927,35 @@ export {
3397
3927
  adaptedUp,
3398
3928
  cadViewDirections,
3399
3929
  currentViewDirection,
3930
+ MEASURE_RENDER_ORDER,
3931
+ AXIS_COLORS,
3932
+ SNAP_PIXELS,
3933
+ SNAP_MARKER_PIXELS,
3934
+ MEASURE_MARKER_PIXELS,
3935
+ MEASURE_LINE_PIXELS,
3936
+ DELTA_DASH_PIXELS,
3937
+ ANGLE_ARC_FRACTION,
3938
+ ANGLE_ARC_SEGMENTS,
3939
+ snapAt,
3940
+ screenPoint,
3941
+ POINTS_PER_MEASUREMENT,
3942
+ measurementFromPoints,
3943
+ nextMeasurementId,
3944
+ distanceBetween,
3945
+ deltaBetween,
3946
+ midpoint,
3947
+ angleAt,
3948
+ lockToAxis,
3949
+ deltaLegs,
3950
+ angleArc,
3951
+ angleArcRadius,
3952
+ formatMillimetres,
3953
+ formatDegrees,
3954
+ measurementLabel,
3955
+ measurementLabelAnchor,
3400
3956
  SECTION_RENDER_ORDER,
3401
3957
  HANDLE_PIXELS,
3958
+ SECTION_GIZMO_FRAME_SCALE,
3402
3959
  CAP_HATCH,
3403
3960
  AXES_PLANE_OFFSET,
3404
3961
  AXES_PLANE_SCALE,
@@ -3406,6 +3963,7 @@ export {
3406
3963
  OUTLINE_SCALE,
3407
3964
  DEFAULT_SECTION_NORMAL,
3408
3965
  DISABLED_SECTION,
3966
+ sectionMeasurement,
3409
3967
  sectionOptionsFromState,
3410
3968
  axesPlanes,
3411
3969
  hitUnderRay,
@@ -3444,6 +4002,7 @@ export {
3444
4002
  viewDirection,
3445
4003
  buildPick,
3446
4004
  focusForPick,
4005
+ CONTROL_SCHEME_OPTIONS,
3447
4006
  ExtendedCameraControls,
3448
4007
  CadCameraControls,
3449
4008
  retargetPose,