@toolpath/viewer 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/{chunk-HWAEUJ4H.js → chunk-HH3W5T42.js} +17 -7
- package/dist/engine/index.js +1 -1
- package/dist/index.d.ts +87 -5
- package/dist/index.js +326 -25
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -594,6 +594,16 @@ convert, so pass `format` to show anything else:
|
|
|
594
594
|
<MeasureTool format={(mm) => `${(mm / MM_PER_INCH).toFixed(3)} in`} />
|
|
595
595
|
```
|
|
596
596
|
|
|
597
|
+
**Measuring a cut part.** Mount the measure tool with a section cut in place, or beside
|
|
598
|
+
`<SectionTool>`, and it measures what is on screen: the half a cut has removed is not there to snap
|
|
599
|
+
to, the part's edges stop at the plane, the outline where the plane passes through solid material
|
|
600
|
+
is an edge with corners of its own, and the capped face is a surface a point can land on. The
|
|
601
|
+
sample follows the cut — drag the handle or set a depth and the next pointer move sees the new cut.
|
|
602
|
+
It reads the plane off the part's own material, so a controlled `section` prop works the same way.
|
|
603
|
+
Beside a `<SectionTool>` that has no cut yet, the measure tool waits: it offers no snap and places
|
|
604
|
+
no point until a cut is chosen or the section tool is unmounted, so the click that picks the cut is
|
|
605
|
+
not also the first point of a measurement. Measurements already made stay up meanwhile.
|
|
606
|
+
|
|
597
607
|
Hold Shift to hold the next point to the X, Y or Z line through the last one — whichever axis the
|
|
598
608
|
pointer is furthest along — so a length along an edge is measured square rather than slightly across
|
|
599
609
|
it. The line wears that axis's colour while it is held, and the finished distance has no legs to
|
|
@@ -1328,11 +1328,12 @@ function retargetPose(cameraPosition, currentTarget, hitPoint) {
|
|
|
1328
1328
|
// src/render/section-store.ts
|
|
1329
1329
|
function createSectionStore() {
|
|
1330
1330
|
let current = null;
|
|
1331
|
-
let engaged = 0;
|
|
1332
1331
|
const listeners = /* @__PURE__ */ new Set();
|
|
1333
1332
|
const notify = () => {
|
|
1334
1333
|
for (const listener of listeners) listener();
|
|
1335
1334
|
};
|
|
1335
|
+
const engaged = counted(notify);
|
|
1336
|
+
const picking = counted(notify);
|
|
1336
1337
|
return {
|
|
1337
1338
|
get: () => current,
|
|
1338
1339
|
set: (next) => {
|
|
@@ -1340,12 +1341,10 @@ function createSectionStore() {
|
|
|
1340
1341
|
current = next;
|
|
1341
1342
|
notify();
|
|
1342
1343
|
},
|
|
1343
|
-
isEngaged:
|
|
1344
|
-
setEngaged:
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
if (engaged > 0 !== was) notify();
|
|
1348
|
-
},
|
|
1344
|
+
isEngaged: engaged.is,
|
|
1345
|
+
setEngaged: engaged.set,
|
|
1346
|
+
isPicking: picking.is,
|
|
1347
|
+
setPicking: picking.set,
|
|
1349
1348
|
subscribe: (listener) => {
|
|
1350
1349
|
listeners.add(listener);
|
|
1351
1350
|
return () => {
|
|
@@ -1354,6 +1353,17 @@ function createSectionStore() {
|
|
|
1354
1353
|
}
|
|
1355
1354
|
};
|
|
1356
1355
|
}
|
|
1356
|
+
function counted(notify) {
|
|
1357
|
+
let held = 0;
|
|
1358
|
+
return {
|
|
1359
|
+
is: () => held > 0,
|
|
1360
|
+
set: (next) => {
|
|
1361
|
+
const was = held > 0;
|
|
1362
|
+
held = Math.max(0, held + (next ? 1 : -1));
|
|
1363
|
+
if (held > 0 !== was) notify();
|
|
1364
|
+
}
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1357
1367
|
|
|
1358
1368
|
// src/target-marker.tsx
|
|
1359
1369
|
import { useFrame as useFrame2, useThree as useThree3 } from "@react-three/fiber";
|
package/dist/engine/index.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { P as PartModel, V as Vec3, d as ViewerTheme, S as SectionOptions, e as SectionState, f as PartModelRegion, T as TriangleRange, F as FeatureTag, R as RegionIndex, g as PartModelFeature, h as FeatureType } from './normalize-DHOSNiuO.js';
|
|
2
2
|
export { A as AXES_PLANE_OFFSET, i as AXES_PLANE_SCALE, j as AxesPlane, B as BuildPickInput, C as CANDIDATE_WEIGHT, k as CAP_HATCH, D as DEFAULT_SECTION_NORMAL, l as DEFAULT_THEME, m as DIRECTION_COLORS, o as DISABLED_SECTION, E as EnginePart, p as FeatureHighlight, H as HANDLE_PIXELS, q as HIGHLIGHT_COLORS, r as HIGHLIGHT_WEIGHT, t as HOVER_WEIGHT, u as HighlightLayers, K as KnownFeatureType, v as KnownShapeKind, N as NO_MODIFIERS, O as OUTLINE_SCALE, w as PICKED_SURFACE_LABEL, x as PREVIEW_SCALE, y as PartMesh, z as PartMeshProps, a as PartMeshRefs, G as PartObject, I as PartPick, J as PickModifiers, L as REGION_ATTRIBUTE, Q as RegionHighlight, U as RegionPaint, W as SECTION_RENDER_ORDER, X as SectionAnchor, Y as SectionBounds, Z as SectionPlacement, _ as ShapeKind, $ as SurfaceHit, a0 as applyHighlightLayers, a1 as axesPlanes, a2 as buildPick, a3 as buildRegionAttribute, a4 as buildRegionTexels, a5 as createPart, a6 as directionColor, a7 as dragPlane, a8 as focusForPick, a9 as hitUnderRay, n as normalizePartReport, aa as pickedStartDepth, ab as resolveTheme, ac as sectionAnchor, ad as sectionBounds, ae as sectionConstant, af as sectionDepth, ag as sectionDepthConstant, ah as sectionDepthRange, ai as sectionFromPick, aj as sectionOffset, ak as sectionOptionsFromState, al as sectionPlane, s as smoothRegionNormals, am as surfaceUnderRay, an as themesEqual, ao as viewDirection } from './normalize-DHOSNiuO.js';
|
|
3
|
-
import { BufferGeometry, CanvasTexture, Box3, Plane, Vector3, OrthographicCamera, PerspectiveCamera, Object3D, Matrix4, Vector2, ShaderMaterial, Texture, Color, MeshBasicMaterial, WebGLRenderTarget } from 'three';
|
|
3
|
+
import { BufferGeometry, CanvasTexture, Box3, Plane, Vector3, OrthographicCamera, PerspectiveCamera, Object3D, Matrix4, Vector2, Ray, ShaderMaterial, Texture, Color, MeshBasicMaterial, WebGLRenderTarget } from 'three';
|
|
4
4
|
import * as react from 'react';
|
|
5
5
|
import { RefObject, ReactNode, CSSProperties } from 'react';
|
|
6
6
|
import CameraControls from 'camera-controls';
|
|
@@ -245,7 +245,9 @@ interface SectionToolProps {
|
|
|
245
245
|
* the way to set or clear it from outside the canvas. While this is mounted
|
|
246
246
|
* the part reports no hovers or picks: a click on a face cuts through it
|
|
247
247
|
* without also selecting it, and a cut part is looked at rather than picked
|
|
248
|
-
* at. Unmounting it hands the pointer back.
|
|
248
|
+
* at. Unmounting it hands the pointer back. While it is offering a cut — up,
|
|
249
|
+
* with none placed yet — a `<MeasureTool>` beside it waits too, so the click
|
|
250
|
+
* that chooses the cut does not also place a measurement point.
|
|
249
251
|
*/
|
|
250
252
|
declare const SectionTool: ({ theme }: SectionToolProps) => react.JSX.Element | null;
|
|
251
253
|
|
|
@@ -687,7 +689,12 @@ interface MeasureToolProps {
|
|
|
687
689
|
*
|
|
688
690
|
* Nothing needs wiring. While it is mounted the part reports no hovers or
|
|
689
691
|
* picks — a click that places a point is not a click that selects a face — and
|
|
690
|
-
* unmounting it hands the pointer back.
|
|
692
|
+
* unmounting it hands the pointer back. Beside a `<SectionTool>` that has no
|
|
693
|
+
* cut yet it waits: no snap is offered and no click places a point until the
|
|
694
|
+
* cut is chosen or the section tool is unmounted, and the click that chooses
|
|
695
|
+
* the cut — the one it wakes on — places nothing either: a click counts only
|
|
696
|
+
* when it was pressed as well as released while the tool was taking points.
|
|
697
|
+
* Measurements already made stay up meanwhile. It snaps to the edges the part draws,
|
|
691
698
|
* which are the boundaries between analytic surfaces rather than the mesh's
|
|
692
699
|
* triangles, so a corner is a corner of the part and not of a facet.
|
|
693
700
|
*
|
|
@@ -723,6 +730,12 @@ declare const MeasureTool: ({ mode, measurements, onChange, showDeltas, format,
|
|
|
723
730
|
* on a face is not a request to select the face — which is why the flag is a
|
|
724
731
|
* count rather than a boolean: two tools up at once let go one at a time.
|
|
725
732
|
*
|
|
733
|
+
* And whether a tool is **picking** — `<SectionTool>` is up with no cut in
|
|
734
|
+
* place, so the next click on the part chooses where the cut goes. That click
|
|
735
|
+
* is the section tool's alone: `<MeasureTool>` beside it offers no snap and
|
|
736
|
+
* places no point until the cut is chosen or the section tool is unmounted.
|
|
737
|
+
* Counted for the same reason as `engaged`.
|
|
738
|
+
*
|
|
726
739
|
* A subscription rather than React state, so a drag re-renders the two
|
|
727
740
|
* subscribers and not everything under the canvas.
|
|
728
741
|
*/
|
|
@@ -733,11 +746,80 @@ interface SectionStore {
|
|
|
733
746
|
isEngaged(): boolean;
|
|
734
747
|
/** Counted: a tool engages on mount and disengages on unmount, and the part waits for the last. */
|
|
735
748
|
setEngaged(engaged: boolean): void;
|
|
736
|
-
/**
|
|
749
|
+
/** Whether a section tool is waiting for a click to say where the cut goes. */
|
|
750
|
+
isPicking(): boolean;
|
|
751
|
+
/** Counted, as `setEngaged` is: raised while a section tool offers a cut, dropped once one is placed. */
|
|
752
|
+
setPicking(picking: boolean): void;
|
|
753
|
+
/** Notified on every change to the cut, the engaged flag, or the picking flag. */
|
|
737
754
|
subscribe(listener: () => void): () => void;
|
|
738
755
|
}
|
|
739
756
|
declare function createSectionStore(): SectionStore;
|
|
740
757
|
|
|
758
|
+
/**
|
|
759
|
+
* A cut part, re-sampled for measuring.
|
|
760
|
+
*
|
|
761
|
+
* A clipping plane hides half of a part without changing a triangle, so the
|
|
762
|
+
* mesh and its edges still describe the whole thing — and a tool that snaps
|
|
763
|
+
* to them would offer corners nobody can see, and nothing at all on the cut.
|
|
764
|
+
* This is what the measure tool works against instead: the part's edges
|
|
765
|
+
* trimmed to the half that is left, the outline where the plane passes
|
|
766
|
+
* through solid material, and a test for whether a point on the plane is on
|
|
767
|
+
* the capped face. It is rebuilt whenever the plane moves; see
|
|
768
|
+
* `measure-tool.tsx`.
|
|
769
|
+
*
|
|
770
|
+
* Everything here is in one space — the mesh's own — and the plane is
|
|
771
|
+
* expected in it too. Six floats per segment throughout, the layout
|
|
772
|
+
* `regionEdgesGeometry` uses.
|
|
773
|
+
*/
|
|
774
|
+
interface SectionSample {
|
|
775
|
+
/** The edges of the part on the kept side, plus the cut's outline. */
|
|
776
|
+
readonly edges: Float32Array;
|
|
777
|
+
/** The cut's outline alone: where the plane crosses solid material. */
|
|
778
|
+
readonly contour: Float32Array;
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Trims segments to the half-space a plane keeps.
|
|
782
|
+
*
|
|
783
|
+
* A segment wholly on the removed side goes; one that crosses is cut at the
|
|
784
|
+
* plane and its kept part stays. `Plane` keeps the side its normal points
|
|
785
|
+
* into — where the signed distance is positive — as the part's own clipping
|
|
786
|
+
* does.
|
|
787
|
+
*/
|
|
788
|
+
declare function clipSegments(positions: ArrayLike<number>, plane: Plane): Float32Array;
|
|
789
|
+
/**
|
|
790
|
+
* Where a plane passes through a mesh: one segment per triangle it crosses,
|
|
791
|
+
* then straight runs inside one region stitched into one segment each.
|
|
792
|
+
*
|
|
793
|
+
* The stitching is what makes the outline snappable. A flat face is two
|
|
794
|
+
* triangles or two hundred, and the plane crosses each of them separately,
|
|
795
|
+
* so before stitching every triangle boundary on the face was a corner the
|
|
796
|
+
* pointer would snap to. After it, a corner is where the outline turns —
|
|
797
|
+
* where the plane leaves one surface for the next — and on a curved surface
|
|
798
|
+
* the facets stay, as they do in the part's own edges.
|
|
799
|
+
*
|
|
800
|
+
* `regionOf` says which surface each triangle belongs to, indexed by
|
|
801
|
+
* triangle; without it every triangle is one surface, which stitches a
|
|
802
|
+
* straight run across a fold only if the fold is perfectly flat, which it is
|
|
803
|
+
* not.
|
|
804
|
+
*/
|
|
805
|
+
declare function sectionContour(positions: ArrayLike<number>, regionOf: ArrayLike<number> | null, plane: Plane): Float32Array;
|
|
806
|
+
/**
|
|
807
|
+
* Whether a point on the plane lies inside the cut's outline — on the capped
|
|
808
|
+
* face rather than in the air beside it.
|
|
809
|
+
*
|
|
810
|
+
* Even-odd against every outline segment, in the plane's own two axes, so a
|
|
811
|
+
* part cut through a bore is inside the outer loop and outside the hole's.
|
|
812
|
+
*/
|
|
813
|
+
declare function pointInContour(point: Vector3, plane: Plane, contour: ArrayLike<number>): boolean;
|
|
814
|
+
/**
|
|
815
|
+
* Where a ray meets the capped face, or `null` when it misses the cap — a
|
|
816
|
+
* ray through the open half that lands beside the part, or one that reaches
|
|
817
|
+
* the plane from behind.
|
|
818
|
+
*/
|
|
819
|
+
declare function capHit(ray: Ray, plane: Plane, contour: ArrayLike<number>): Vector3 | null;
|
|
820
|
+
/** The edges and outline of a part cut by `plane`, everything in the mesh's own space. */
|
|
821
|
+
declare function sampleSection(meshPositions: ArrayLike<number>, regionOf: ArrayLike<number> | null, edgePositions: ArrayLike<number> | null, plane: Plane): SectionSample;
|
|
822
|
+
|
|
741
823
|
type CapUniforms = {
|
|
742
824
|
uMask: {
|
|
743
825
|
value: Texture | null;
|
|
@@ -1510,4 +1592,4 @@ declare const ORBIT_TARGET_FADE_MS = 1000;
|
|
|
1510
1592
|
*/
|
|
1511
1593
|
declare function orbitTargetOpacity(sinceHold: number, fade?: number): number;
|
|
1512
1594
|
|
|
1513
|
-
export { ANGLE_ARC_FRACTION, ANGLE_ARC_SEGMENTS, AXIS_COLORS, type AngleMeasurement, type ArrowPlacement, Axes, type AxesProps, type Axis, type AxisLock, CAD_CAMERA_UP, CHAMFER, CadCameraControls, type CadCameraControlsProps, type CameraLimits, type CapMaterial, type CapUniforms, type ControlScheme, type CubeZone, DEFAULT_FIT_MARGIN, DELTA_DASH_PIXELS, DOUBLE_TAP_MS, type DeltaLeg, DirectionArrows, type DirectionArrowsProps, type DirectionGroup, type DistanceMeasurement, type DoubleTapPoint, type DoubleTapTracker, EXCLUDE_FROM_FRAME, ExtendedCameraControls, type ExtendedCameraControlsOptions, FEATURE_TYPE_RANKS, FeatureTag, FeatureType, Grid, type GridProps, type GridSpec, MAX_FRAME_RATIO, MEASURE_LABEL_CLASS, MEASURE_LINE_PIXELS, MEASURE_MARKER_PIXELS, MEASURE_RENDER_ORDER, MIN_FRAME_RATIO, type MeasureLabelKind, type MeasureMode, MeasureTool, type MeasureToolProps, type Measurement, type NamedDirection, ORBIT_TARGET_COLOR, ORBIT_TARGET_FADE_MS, ORBIT_TARGET_FLASH_MS, ORBIT_TARGET_PIXELS, ORBIT_TARGET_RING_COLOR, ORBIT_TARGET_RING_OPACITY, ORBIT_TARGET_RING_PIXELS, ORBIT_TARGET_RING_WIDTH, PERSPECTIVE_FOV, POINTS_PER_MEASUREMENT, PartModel, PartModelFeature, PartModelRegion, PartReportFormatError, type Projection, type RankingContext, RegionIndex, type Retarget, type RetargetPose, SNAP_MARKER_PIXELS, SNAP_PIXELS, type SceneBounds, SectionOptions, SectionState, type SectionStore, SectionTool, type SectionToolProps, SectionView, type Snap, type SnapEdges, type SnapHit, type SnapKind, type SurfaceOf, TAP_SLOP, type TapPoint, type TapTracker, TriangleRange, UnsupportedKernelVersionError, VIEW_NAMES, VIEW_SIGNS, Vec3, ViewCube, type ViewCubeProps, type ViewKind, type ViewName, Viewer, type ViewerCamera, type ViewerControls, type ViewerHandle, type ViewerProps, ViewerTheme, type ViewerView, type ViewportSize, adaptedUp, angleArc, angleArcRadius, angleAt, applyCapTheme, applyProjection, arrowPlacement, aspectRatio, bestOwner, boundsFromBox, buildRegionIndex, cadViewDirections, cameraLimits, contentBounds, createCapMaterial, createMaskMaterial, createMaskTarget, createSectionStore, createStencilMaterials, cubeOutlineGeometry, cubeZones, currentViewDirection, cycleOwner, defaultBounds, deltaBetween, deltaLegs, directionIndexOf, directionLabel, distanceBetween, excludedFromFrame, featureTypeRank, fitDistance, formatDegrees, formatMillimetres, gridGeometry, gridSpec, groupByDirection, labelGeometry, labelTexture, lockToAxis, measurementFromPoints, measurementLabel, measurementLabelAnchor, midpoint, movedFar, nextMeasurementId, orbitTargetOpacity, orthographicHalfHeight, panelGeometry, perspectiveFitDistance, rankOwners, regionAdjacency, regionEdgesGeometry, regionNormals, resolveSectionPlane, retargetPose, sameDirection, screenLength, screenPoint, snapAt, squaredUp, startPosition, targetBoundary, trackDoubleTaps, trackTaps, useContentBox, useRetarget, useSectionStore, useTapGuard, useViewerControls, viewKind, viewUp, viewVector, visualSurfaces };
|
|
1595
|
+
export { ANGLE_ARC_FRACTION, ANGLE_ARC_SEGMENTS, AXIS_COLORS, type AngleMeasurement, type ArrowPlacement, Axes, type AxesProps, type Axis, type AxisLock, CAD_CAMERA_UP, CHAMFER, CadCameraControls, type CadCameraControlsProps, type CameraLimits, type CapMaterial, type CapUniforms, type ControlScheme, type CubeZone, DEFAULT_FIT_MARGIN, DELTA_DASH_PIXELS, DOUBLE_TAP_MS, type DeltaLeg, DirectionArrows, type DirectionArrowsProps, type DirectionGroup, type DistanceMeasurement, type DoubleTapPoint, type DoubleTapTracker, EXCLUDE_FROM_FRAME, ExtendedCameraControls, type ExtendedCameraControlsOptions, FEATURE_TYPE_RANKS, FeatureTag, FeatureType, Grid, type GridProps, type GridSpec, MAX_FRAME_RATIO, MEASURE_LABEL_CLASS, MEASURE_LINE_PIXELS, MEASURE_MARKER_PIXELS, MEASURE_RENDER_ORDER, MIN_FRAME_RATIO, type MeasureLabelKind, type MeasureMode, MeasureTool, type MeasureToolProps, type Measurement, type NamedDirection, ORBIT_TARGET_COLOR, ORBIT_TARGET_FADE_MS, ORBIT_TARGET_FLASH_MS, ORBIT_TARGET_PIXELS, ORBIT_TARGET_RING_COLOR, ORBIT_TARGET_RING_OPACITY, ORBIT_TARGET_RING_PIXELS, ORBIT_TARGET_RING_WIDTH, PERSPECTIVE_FOV, POINTS_PER_MEASUREMENT, PartModel, PartModelFeature, PartModelRegion, PartReportFormatError, type Projection, type RankingContext, RegionIndex, type Retarget, type RetargetPose, SNAP_MARKER_PIXELS, SNAP_PIXELS, type SceneBounds, SectionOptions, type SectionSample, SectionState, type SectionStore, SectionTool, type SectionToolProps, SectionView, type Snap, type SnapEdges, type SnapHit, type SnapKind, type SurfaceOf, TAP_SLOP, type TapPoint, type TapTracker, TriangleRange, UnsupportedKernelVersionError, VIEW_NAMES, VIEW_SIGNS, Vec3, ViewCube, type ViewCubeProps, type ViewKind, type ViewName, Viewer, type ViewerCamera, type ViewerControls, type ViewerHandle, type ViewerProps, ViewerTheme, type ViewerView, type ViewportSize, adaptedUp, angleArc, angleArcRadius, angleAt, applyCapTheme, applyProjection, arrowPlacement, aspectRatio, bestOwner, boundsFromBox, buildRegionIndex, cadViewDirections, cameraLimits, capHit, clipSegments, contentBounds, createCapMaterial, createMaskMaterial, createMaskTarget, createSectionStore, createStencilMaterials, cubeOutlineGeometry, cubeZones, currentViewDirection, cycleOwner, defaultBounds, deltaBetween, deltaLegs, directionIndexOf, directionLabel, distanceBetween, excludedFromFrame, featureTypeRank, fitDistance, formatDegrees, formatMillimetres, gridGeometry, gridSpec, groupByDirection, labelGeometry, labelTexture, lockToAxis, measurementFromPoints, measurementLabel, measurementLabelAnchor, midpoint, movedFar, nextMeasurementId, orbitTargetOpacity, orthographicHalfHeight, panelGeometry, perspectiveFitDistance, pointInContour, rankOwners, regionAdjacency, regionEdgesGeometry, regionNormals, resolveSectionPlane, retargetPose, sameDirection, sampleSection, screenLength, screenPoint, sectionContour, snapAt, squaredUp, startPosition, targetBoundary, trackDoubleTaps, trackTaps, useContentBox, useRetarget, useSectionStore, useTapGuard, useViewerControls, viewKind, viewUp, viewVector, visualSurfaces };
|
package/dist/index.js
CHANGED
|
@@ -131,7 +131,7 @@ import {
|
|
|
131
131
|
viewUp,
|
|
132
132
|
viewVector,
|
|
133
133
|
visualSurfaces
|
|
134
|
-
} from "./chunk-
|
|
134
|
+
} from "./chunk-HH3W5T42.js";
|
|
135
135
|
|
|
136
136
|
// src/primitives.tsx
|
|
137
137
|
import { GizmoHelper } from "@react-three/drei";
|
|
@@ -644,6 +644,10 @@ var Picker = ({ box, theme }) => {
|
|
|
644
644
|
const span = useMemo3(() => box.getSize(new Vector34()).length(), [box]);
|
|
645
645
|
const centre = useMemo3(() => box.getCenter(new Vector34()), [box]);
|
|
646
646
|
const previewSize = span * PREVIEW_SCALE;
|
|
647
|
+
useEffect2(() => {
|
|
648
|
+
store.setPicking(true);
|
|
649
|
+
return () => store.setPicking(false);
|
|
650
|
+
}, [store]);
|
|
647
651
|
useEffect2(() => {
|
|
648
652
|
const raycaster = new Raycaster();
|
|
649
653
|
const pointer = new Vector22();
|
|
@@ -840,13 +844,194 @@ var CutSheet = ({ box, section, theme }) => {
|
|
|
840
844
|
// src/measure-tool.tsx
|
|
841
845
|
import { Html, Line } from "@react-three/drei";
|
|
842
846
|
import { useFrame as useFrame2, useThree as useThree4 } from "@react-three/fiber";
|
|
843
|
-
import { useEffect as useEffect3, useLayoutEffect, useMemo as useMemo4, useRef as useRef3, useState as useState3 } from "react";
|
|
844
847
|
import {
|
|
848
|
+
useEffect as useEffect3,
|
|
849
|
+
useLayoutEffect,
|
|
850
|
+
useMemo as useMemo4,
|
|
851
|
+
useRef as useRef3,
|
|
852
|
+
useState as useState3,
|
|
853
|
+
useSyncExternalStore as useSyncExternalStore2
|
|
854
|
+
} from "react";
|
|
855
|
+
import {
|
|
856
|
+
Matrix4,
|
|
845
857
|
Raycaster as Raycaster2,
|
|
846
858
|
SphereGeometry,
|
|
847
859
|
Vector2 as Vector23,
|
|
848
|
-
Vector3 as
|
|
860
|
+
Vector3 as Vector36
|
|
849
861
|
} from "three";
|
|
862
|
+
|
|
863
|
+
// src/render/resample.ts
|
|
864
|
+
import { Vector3 as Vector35 } from "three";
|
|
865
|
+
var ON_PLANE = 1e-7;
|
|
866
|
+
var STITCH = 1e-5;
|
|
867
|
+
var COLLINEAR = 1e-6;
|
|
868
|
+
function clipSegments(positions, plane) {
|
|
869
|
+
const out = [];
|
|
870
|
+
const a = new Vector35();
|
|
871
|
+
const b = new Vector35();
|
|
872
|
+
const count = Math.floor(positions.length / 6);
|
|
873
|
+
for (let i = 0; i < count; i += 1) {
|
|
874
|
+
a.set(positions[i * 6], positions[i * 6 + 1], positions[i * 6 + 2]);
|
|
875
|
+
b.set(positions[i * 6 + 3], positions[i * 6 + 4], positions[i * 6 + 5]);
|
|
876
|
+
const da = plane.distanceToPoint(a);
|
|
877
|
+
const db = plane.distanceToPoint(b);
|
|
878
|
+
if (da < -ON_PLANE && db < -ON_PLANE) continue;
|
|
879
|
+
if (da < -ON_PLANE) a.lerp(b, da / (da - db));
|
|
880
|
+
else if (db < -ON_PLANE) b.lerp(a, db / (db - da));
|
|
881
|
+
out.push(a.x, a.y, a.z, b.x, b.y, b.z);
|
|
882
|
+
}
|
|
883
|
+
return Float32Array.from(out);
|
|
884
|
+
}
|
|
885
|
+
function sectionContour(positions, regionOf, plane) {
|
|
886
|
+
const a = new Vector35();
|
|
887
|
+
const b = new Vector35();
|
|
888
|
+
const c = new Vector35();
|
|
889
|
+
const p = new Vector35();
|
|
890
|
+
const q = new Vector35();
|
|
891
|
+
const byRegion = /* @__PURE__ */ new Map();
|
|
892
|
+
const triangleCount = Math.floor(positions.length / 9);
|
|
893
|
+
for (let t = 0; t < triangleCount; t += 1) {
|
|
894
|
+
a.set(positions[t * 9], positions[t * 9 + 1], positions[t * 9 + 2]);
|
|
895
|
+
b.set(positions[t * 9 + 3], positions[t * 9 + 4], positions[t * 9 + 5]);
|
|
896
|
+
c.set(positions[t * 9 + 6], positions[t * 9 + 7], positions[t * 9 + 8]);
|
|
897
|
+
const crossings = triangleCrossings(a, b, c, plane, p, q);
|
|
898
|
+
if (!crossings) continue;
|
|
899
|
+
const region = regionOf ? regionOf[t] ?? 0 : 0;
|
|
900
|
+
const list = byRegion.get(region);
|
|
901
|
+
const entry = [p.x, p.y, p.z, q.x, q.y, q.z];
|
|
902
|
+
if (list) list.push(...entry);
|
|
903
|
+
else byRegion.set(region, entry);
|
|
904
|
+
}
|
|
905
|
+
const out = [];
|
|
906
|
+
for (const segments of byRegion.values()) stitch(segments, out);
|
|
907
|
+
return Float32Array.from(out);
|
|
908
|
+
}
|
|
909
|
+
function triangleCrossings(a, b, c, plane, p, q) {
|
|
910
|
+
const da = plane.distanceToPoint(a);
|
|
911
|
+
const db = plane.distanceToPoint(b);
|
|
912
|
+
const dc = plane.distanceToPoint(c);
|
|
913
|
+
const sa = da < -ON_PLANE;
|
|
914
|
+
const sb = db < -ON_PLANE;
|
|
915
|
+
const sc = dc < -ON_PLANE;
|
|
916
|
+
if (sa === sb && sb === sc) return false;
|
|
917
|
+
const found = [];
|
|
918
|
+
const edge = (u, v, du, dv, su, sv) => {
|
|
919
|
+
if (su === sv) return;
|
|
920
|
+
found.push(u.clone().lerp(v, du / (du - dv)));
|
|
921
|
+
};
|
|
922
|
+
edge(a, b, da, db, sa, sb);
|
|
923
|
+
edge(b, c, db, dc, sb, sc);
|
|
924
|
+
edge(c, a, dc, da, sc, sa);
|
|
925
|
+
if (found.length < 2) return false;
|
|
926
|
+
p.copy(found[0]);
|
|
927
|
+
q.copy(found[1]);
|
|
928
|
+
return p.distanceToSquared(q) > ON_PLANE * ON_PLANE;
|
|
929
|
+
}
|
|
930
|
+
function stitch(segments, out) {
|
|
931
|
+
const count = segments.length / 6;
|
|
932
|
+
const key = (x, y, z) => `${Math.round(x / STITCH)},${Math.round(y / STITCH)},${Math.round(z / STITCH)}`;
|
|
933
|
+
const ends = /* @__PURE__ */ new Map();
|
|
934
|
+
for (let i = 0; i < count; i += 1) {
|
|
935
|
+
for (const end of [0, 1]) {
|
|
936
|
+
const o = i * 6 + end * 3;
|
|
937
|
+
const k = key(segments[o], segments[o + 1], segments[o + 2]);
|
|
938
|
+
const list = ends.get(k);
|
|
939
|
+
if (list) list.push(i * 2 + end);
|
|
940
|
+
else ends.set(k, [i * 2 + end]);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
const point = (i, end, into) => {
|
|
944
|
+
const o = i * 6 + end * 3;
|
|
945
|
+
return into.set(segments[o], segments[o + 1], segments[o + 2]);
|
|
946
|
+
};
|
|
947
|
+
const direction = (i, into) => {
|
|
948
|
+
const s = new Vector35();
|
|
949
|
+
const e = new Vector35();
|
|
950
|
+
point(i, 0, s);
|
|
951
|
+
point(i, 1, e);
|
|
952
|
+
return into.subVectors(e, s).normalize();
|
|
953
|
+
};
|
|
954
|
+
const consumed = new Uint8Array(count);
|
|
955
|
+
const d = new Vector35();
|
|
956
|
+
const n = new Vector35();
|
|
957
|
+
const next = (i, end) => {
|
|
958
|
+
const at = point(i, end, new Vector35());
|
|
959
|
+
const list = ends.get(key(at.x, at.y, at.z)) ?? [];
|
|
960
|
+
if (list.length !== 2) return null;
|
|
961
|
+
const other = list.find((entry) => entry >> 1 !== i);
|
|
962
|
+
if (other === void 0) return null;
|
|
963
|
+
const j = other >> 1;
|
|
964
|
+
if (consumed[j]) return null;
|
|
965
|
+
direction(i, d);
|
|
966
|
+
direction(j, n);
|
|
967
|
+
if (Math.abs(Math.abs(d.dot(n)) - 1) > COLLINEAR) return null;
|
|
968
|
+
return { index: j, end: other & 1 };
|
|
969
|
+
};
|
|
970
|
+
for (let i = 0; i < count; i += 1) {
|
|
971
|
+
if (consumed[i]) continue;
|
|
972
|
+
consumed[i] = 1;
|
|
973
|
+
const walk = (end) => {
|
|
974
|
+
let at = { index: i, end };
|
|
975
|
+
let steps = 0;
|
|
976
|
+
for (; ; ) {
|
|
977
|
+
const step = next(at.index, at.end);
|
|
978
|
+
if (!step || steps > count) break;
|
|
979
|
+
consumed[step.index] = 1;
|
|
980
|
+
at = { index: step.index, end: 1 - step.end };
|
|
981
|
+
steps += 1;
|
|
982
|
+
}
|
|
983
|
+
return at;
|
|
984
|
+
};
|
|
985
|
+
const back = walk(0);
|
|
986
|
+
const forward = walk(1);
|
|
987
|
+
const s = point(back.index, back.end, new Vector35());
|
|
988
|
+
const e = point(forward.index, forward.end, new Vector35());
|
|
989
|
+
out.push(s.x, s.y, s.z, e.x, e.y, e.z);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
function pointInContour(point, plane, contour) {
|
|
993
|
+
const [u, v] = planeAxes(plane);
|
|
994
|
+
const px = point.dot(u);
|
|
995
|
+
const py = point.dot(v);
|
|
996
|
+
const a = new Vector35();
|
|
997
|
+
const b = new Vector35();
|
|
998
|
+
let inside = false;
|
|
999
|
+
const count = Math.floor(contour.length / 6);
|
|
1000
|
+
for (let i = 0; i < count; i += 1) {
|
|
1001
|
+
a.set(contour[i * 6], contour[i * 6 + 1], contour[i * 6 + 2]);
|
|
1002
|
+
b.set(contour[i * 6 + 3], contour[i * 6 + 4], contour[i * 6 + 5]);
|
|
1003
|
+
const ax = a.dot(u);
|
|
1004
|
+
const ay = a.dot(v);
|
|
1005
|
+
const bx = b.dot(u);
|
|
1006
|
+
const by = b.dot(v);
|
|
1007
|
+
if (ay > py === by > py) continue;
|
|
1008
|
+
const x = ax + (py - ay) / (by - ay) * (bx - ax);
|
|
1009
|
+
if (px < x) inside = !inside;
|
|
1010
|
+
}
|
|
1011
|
+
return inside;
|
|
1012
|
+
}
|
|
1013
|
+
function capHit(ray, plane, contour) {
|
|
1014
|
+
const point = ray.intersectPlane(plane, new Vector35());
|
|
1015
|
+
if (!point) return null;
|
|
1016
|
+
return pointInContour(point, plane, contour) ? point : null;
|
|
1017
|
+
}
|
|
1018
|
+
function planeAxes(plane) {
|
|
1019
|
+
const n = plane.normal;
|
|
1020
|
+
const seed = Math.abs(n.x) < 0.9 ? new Vector35(1, 0, 0) : new Vector35(0, 1, 0);
|
|
1021
|
+
const u = new Vector35().crossVectors(n, seed).normalize();
|
|
1022
|
+
const v = new Vector35().crossVectors(n, u).normalize();
|
|
1023
|
+
return [u, v];
|
|
1024
|
+
}
|
|
1025
|
+
function sampleSection(meshPositions, regionOf, edgePositions, plane) {
|
|
1026
|
+
const contour = sectionContour(meshPositions, regionOf, plane);
|
|
1027
|
+
const kept = edgePositions ? clipSegments(edgePositions, plane) : new Float32Array();
|
|
1028
|
+
const edges = new Float32Array(kept.length + contour.length);
|
|
1029
|
+
edges.set(kept, 0);
|
|
1030
|
+
edges.set(contour, kept.length);
|
|
1031
|
+
return { edges, contour };
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// src/measure-tool.tsx
|
|
850
1035
|
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
851
1036
|
var FURNITURE4 = { [EXCLUDE_FROM_FRAME]: true };
|
|
852
1037
|
var MEASURE_LABEL_CLASS = "toolpath-measure-label";
|
|
@@ -862,6 +1047,7 @@ var MeasureTool = ({
|
|
|
862
1047
|
const store = useSectionStore();
|
|
863
1048
|
const box = useContentBox();
|
|
864
1049
|
const resolved = useMemo4(() => resolveTheme(theme), [theme]);
|
|
1050
|
+
const picking = useSyncExternalStore2(store.subscribe, store.isPicking, store.isPicking);
|
|
865
1051
|
const [held, setHeld] = useState3([]);
|
|
866
1052
|
const controlled = measurements !== void 0;
|
|
867
1053
|
const list = controlled ? measurements : held;
|
|
@@ -908,7 +1094,7 @@ var MeasureTool = ({
|
|
|
908
1094
|
if (box.isEmpty()) return null;
|
|
909
1095
|
const label = className(labelClassName);
|
|
910
1096
|
return /* @__PURE__ */ jsxs4("group", { userData: FURNITURE4, children: [
|
|
911
|
-
/* @__PURE__ */ jsx4(
|
|
1097
|
+
picking ? null : /* @__PURE__ */ jsx4(
|
|
912
1098
|
Snapper,
|
|
913
1099
|
{
|
|
914
1100
|
mode,
|
|
@@ -943,7 +1129,7 @@ var MeasureTool = ({
|
|
|
943
1129
|
] });
|
|
944
1130
|
};
|
|
945
1131
|
var NO_POINTER = { pointerEvents: "none" };
|
|
946
|
-
var NOWHERE = [new
|
|
1132
|
+
var NOWHERE = [new Vector36(), new Vector36()];
|
|
947
1133
|
var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
|
|
948
1134
|
const camera = useThree4((state) => state.camera);
|
|
949
1135
|
const scene = useThree4((state) => state.scene);
|
|
@@ -967,6 +1153,7 @@ var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
|
|
|
967
1153
|
useEffect3(() => {
|
|
968
1154
|
const raycaster = new Raycaster2();
|
|
969
1155
|
const pointer = new Vector23();
|
|
1156
|
+
const samples = new SampleCache();
|
|
970
1157
|
const hide = () => {
|
|
971
1158
|
if (!snap.current) return;
|
|
972
1159
|
snap.current = null;
|
|
@@ -986,22 +1173,16 @@ var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
|
|
|
986
1173
|
-((clientY - rect.top) / rect.height) * 2 + 1
|
|
987
1174
|
);
|
|
988
1175
|
raycaster.setFromCamera(pointer, camera);
|
|
989
|
-
const
|
|
990
|
-
if (!
|
|
1176
|
+
const target = targetUnderRay(raycaster, scene, samples);
|
|
1177
|
+
if (!target) {
|
|
991
1178
|
hide();
|
|
992
1179
|
return;
|
|
993
1180
|
}
|
|
994
|
-
const normal = hit.face.normal.clone().transformDirection(hit.object.matrixWorld);
|
|
995
1181
|
const { size: viewport, draft: placed, mode: kind, format: write } = current.current;
|
|
996
|
-
const snapped = snapAt(
|
|
997
|
-
{ point: hit.point, normal },
|
|
998
|
-
edgesBeside(hit.object),
|
|
999
|
-
camera,
|
|
1000
|
-
viewport
|
|
1001
|
-
);
|
|
1182
|
+
const snapped = snapAt(target, target.edges, camera, viewport);
|
|
1002
1183
|
const from = placed[placed.length - 1];
|
|
1003
1184
|
const lock = shift && from ? lockToAxis(from, snapped.point) : null;
|
|
1004
|
-
const found = lock ? { ...snapped, point: new
|
|
1185
|
+
const found = lock ? { ...snapped, point: new Vector36(lock.point.x, lock.point.y, lock.point.z), edge: null } : snapped;
|
|
1005
1186
|
snap.current = found;
|
|
1006
1187
|
const node = marker.current;
|
|
1007
1188
|
if (node) {
|
|
@@ -1043,8 +1224,14 @@ var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
|
|
|
1043
1224
|
if (event.key !== "Shift" || !last || !snap.current) return;
|
|
1044
1225
|
update(last.x, last.y, event.type === "keydown");
|
|
1045
1226
|
};
|
|
1227
|
+
let pressed = false;
|
|
1228
|
+
const down = () => {
|
|
1229
|
+
pressed = true;
|
|
1230
|
+
};
|
|
1046
1231
|
const up = (event) => {
|
|
1047
|
-
|
|
1232
|
+
const seen = pressed;
|
|
1233
|
+
pressed = false;
|
|
1234
|
+
if (!seen || event.button !== 0 || !isTap(event)) return;
|
|
1048
1235
|
update(event.clientX, event.clientY, event.shiftKey);
|
|
1049
1236
|
const found = snap.current;
|
|
1050
1237
|
if (!found) return;
|
|
@@ -1056,12 +1243,14 @@ var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
|
|
|
1056
1243
|
};
|
|
1057
1244
|
domElement.addEventListener("pointermove", move);
|
|
1058
1245
|
domElement.addEventListener("pointerleave", leave);
|
|
1246
|
+
domElement.addEventListener("pointerdown", down);
|
|
1059
1247
|
domElement.addEventListener("pointerup", up);
|
|
1060
1248
|
window.addEventListener("keydown", key);
|
|
1061
1249
|
window.addEventListener("keyup", key);
|
|
1062
1250
|
return () => {
|
|
1063
1251
|
domElement.removeEventListener("pointermove", move);
|
|
1064
1252
|
domElement.removeEventListener("pointerleave", leave);
|
|
1253
|
+
domElement.removeEventListener("pointerdown", down);
|
|
1065
1254
|
domElement.removeEventListener("pointerup", up);
|
|
1066
1255
|
window.removeEventListener("keydown", key);
|
|
1067
1256
|
window.removeEventListener("keyup", key);
|
|
@@ -1144,17 +1333,124 @@ function liveLabel(mode, placed, at, format) {
|
|
|
1144
1333
|
}
|
|
1145
1334
|
return null;
|
|
1146
1335
|
}
|
|
1336
|
+
function targetUnderRay(raycaster, scene, samples) {
|
|
1337
|
+
let best = null;
|
|
1338
|
+
const hit = hitUnderRay(raycaster, scene);
|
|
1339
|
+
if (hit?.face) {
|
|
1340
|
+
best = {
|
|
1341
|
+
point: hit.point,
|
|
1342
|
+
normal: hit.face.normal.clone().transformDirection(hit.object.matrixWorld),
|
|
1343
|
+
distance: hit.distance,
|
|
1344
|
+
edges: samples.edgesFor(hit.object)
|
|
1345
|
+
};
|
|
1346
|
+
}
|
|
1347
|
+
const local = new Matrix4();
|
|
1348
|
+
for (const mesh of cutMeshes(scene)) {
|
|
1349
|
+
const planes = clippingPlanesOf(mesh);
|
|
1350
|
+
const sample = samples.for(mesh, planes);
|
|
1351
|
+
local.copy(mesh.matrixWorld).invert();
|
|
1352
|
+
const ray = raycaster.ray.clone().applyMatrix4(local);
|
|
1353
|
+
for (const [index, plane] of planes.entries()) {
|
|
1354
|
+
const contour = sample.contours[index];
|
|
1355
|
+
if (!contour) continue;
|
|
1356
|
+
const point = capHit(ray, plane.clone().applyMatrix4(local), contour);
|
|
1357
|
+
if (!point) continue;
|
|
1358
|
+
const world = point.applyMatrix4(mesh.matrixWorld);
|
|
1359
|
+
if (planes.some((other, i) => i !== index && other.distanceToPoint(world) < -1e-7)) continue;
|
|
1360
|
+
const distance = world.distanceTo(raycaster.ray.origin);
|
|
1361
|
+
if (best && distance >= best.distance) continue;
|
|
1362
|
+
best = {
|
|
1363
|
+
point: world,
|
|
1364
|
+
// The cap faces the half that was taken away.
|
|
1365
|
+
normal: plane.normal.clone().negate(),
|
|
1366
|
+
distance,
|
|
1367
|
+
edges: { positions: sample.edges, matrixWorld: mesh.matrixWorld }
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
return best;
|
|
1372
|
+
}
|
|
1373
|
+
function cutMeshes(scene) {
|
|
1374
|
+
const found = [];
|
|
1375
|
+
scene.traverse((object) => {
|
|
1376
|
+
if (!("isMesh" in object) || !object.visible || excludedFromFrame(object, scene)) return;
|
|
1377
|
+
if (clippingPlanesOf(object).length) found.push(object);
|
|
1378
|
+
});
|
|
1379
|
+
return found;
|
|
1380
|
+
}
|
|
1381
|
+
function clippingPlanesOf(mesh) {
|
|
1382
|
+
const material = Array.isArray(mesh.material) ? mesh.material[0] : mesh.material;
|
|
1383
|
+
return material?.clippingPlanes ?? [];
|
|
1384
|
+
}
|
|
1147
1385
|
function edgesBeside(object) {
|
|
1148
1386
|
const siblings = object.parent?.children ?? [];
|
|
1149
1387
|
for (const sibling of siblings) {
|
|
1150
1388
|
if (!("isLineSegments" in sibling)) continue;
|
|
1151
|
-
const
|
|
1152
|
-
|
|
1153
|
-
if (!positions) continue;
|
|
1154
|
-
return { positions: positions.array, matrixWorld: sibling.matrixWorld };
|
|
1389
|
+
const positions = sibling.geometry.getAttribute("position");
|
|
1390
|
+
if (positions) return positions.array;
|
|
1155
1391
|
}
|
|
1156
1392
|
return null;
|
|
1157
1393
|
}
|
|
1394
|
+
var SampleCache = class {
|
|
1395
|
+
held = /* @__PURE__ */ new Map();
|
|
1396
|
+
regions = /* @__PURE__ */ new Map();
|
|
1397
|
+
/** The edges to snap to on `mesh`: its own, or the trimmed set with the cut's outline. */
|
|
1398
|
+
edgesFor(mesh) {
|
|
1399
|
+
const planes = clippingPlanesOf(mesh);
|
|
1400
|
+
if (planes.length === 0) {
|
|
1401
|
+
const positions = edgesBeside(mesh);
|
|
1402
|
+
return positions ? { positions, matrixWorld: mesh.matrixWorld } : null;
|
|
1403
|
+
}
|
|
1404
|
+
return { positions: this.for(mesh, planes).edges, matrixWorld: mesh.matrixWorld };
|
|
1405
|
+
}
|
|
1406
|
+
for(mesh, planes) {
|
|
1407
|
+
const key = planes.map((plane) => `${plane.normal.x},${plane.normal.y},${plane.normal.z},${plane.constant}`).join("|");
|
|
1408
|
+
const cached = this.held.get(mesh);
|
|
1409
|
+
if (cached && cached.key === key) return cached;
|
|
1410
|
+
const local = new Matrix4().copy(mesh.matrixWorld).invert();
|
|
1411
|
+
const localPlanes = planes.map((plane) => plane.clone().applyMatrix4(local));
|
|
1412
|
+
const positions = mesh.geometry.getAttribute("position")?.array ?? [];
|
|
1413
|
+
const regionOf = this.regionsOf(mesh.geometry);
|
|
1414
|
+
const contours = localPlanes.map(
|
|
1415
|
+
(plane, index) => localPlanes.reduce(
|
|
1416
|
+
(outline, other, i) => i === index ? outline : clipSegments(outline, other),
|
|
1417
|
+
sectionContour(positions, regionOf, plane)
|
|
1418
|
+
)
|
|
1419
|
+
);
|
|
1420
|
+
const own = edgesBeside(mesh) ?? [];
|
|
1421
|
+
const trimmed = localPlanes.reduce(
|
|
1422
|
+
(kept, plane) => clipSegments(kept, plane),
|
|
1423
|
+
own
|
|
1424
|
+
);
|
|
1425
|
+
const total = contours.reduce((sum, contour) => sum + contour.length, trimmed.length);
|
|
1426
|
+
const edges = new Float32Array(total);
|
|
1427
|
+
edges.set(trimmed, 0);
|
|
1428
|
+
let offset = trimmed.length;
|
|
1429
|
+
for (const contour of contours) {
|
|
1430
|
+
edges.set(contour, offset);
|
|
1431
|
+
offset += contour.length;
|
|
1432
|
+
}
|
|
1433
|
+
const sample = { key, contours, edges };
|
|
1434
|
+
this.held.set(mesh, sample);
|
|
1435
|
+
return sample;
|
|
1436
|
+
}
|
|
1437
|
+
/**
|
|
1438
|
+
* Which surface each triangle belongs to, read off the region attribute
|
|
1439
|
+
* `createPart` gives the mesh. A mesh without one is sampled as a single
|
|
1440
|
+
* surface, which still stitches a flat face's outline into one run.
|
|
1441
|
+
*/
|
|
1442
|
+
regionsOf(geometry) {
|
|
1443
|
+
if (this.regions.has(geometry)) return this.regions.get(geometry) ?? null;
|
|
1444
|
+
const attribute = geometry.getAttribute(REGION_ATTRIBUTE);
|
|
1445
|
+
let regions = null;
|
|
1446
|
+
if (attribute && !geometry.index) {
|
|
1447
|
+
regions = new Int32Array(Math.floor(attribute.count / 3));
|
|
1448
|
+
for (let t = 0; t < regions.length; t += 1) regions[t] = attribute.getX(t * 3);
|
|
1449
|
+
}
|
|
1450
|
+
this.regions.set(geometry, regions);
|
|
1451
|
+
return regions;
|
|
1452
|
+
}
|
|
1453
|
+
};
|
|
1158
1454
|
var MeasurementView = ({
|
|
1159
1455
|
measurement,
|
|
1160
1456
|
theme,
|
|
@@ -1194,7 +1490,7 @@ var DeltaLegs = ({ a, b, id, format, label }) => {
|
|
|
1194
1490
|
const lines = useRef3([]);
|
|
1195
1491
|
const centre = useMemo4(() => {
|
|
1196
1492
|
const at = midpoint(a, b);
|
|
1197
|
-
return new
|
|
1493
|
+
return new Vector36(at.x, at.y, at.z);
|
|
1198
1494
|
}, [a, b]);
|
|
1199
1495
|
useFrame2(() => {
|
|
1200
1496
|
const dash = screenLength(camera, centre, size, DELTA_DASH_PIXELS);
|
|
@@ -1216,8 +1512,8 @@ var DeltaLegs = ({ a, b, id, format, label }) => {
|
|
|
1216
1512
|
lines.current[index] = line;
|
|
1217
1513
|
},
|
|
1218
1514
|
points: [
|
|
1219
|
-
new
|
|
1220
|
-
new
|
|
1515
|
+
new Vector36(leg.from.x, leg.from.y, leg.from.z),
|
|
1516
|
+
new Vector36(leg.to.x, leg.to.y, leg.to.z)
|
|
1221
1517
|
],
|
|
1222
1518
|
color: AXIS_COLORS[leg.axis],
|
|
1223
1519
|
lineWidth: MEASURE_LINE_PIXELS.leg,
|
|
@@ -1253,7 +1549,7 @@ var ScreenDot = ({ point, pixels, color }) => {
|
|
|
1253
1549
|
const invalidate = useThree4((state) => state.invalidate);
|
|
1254
1550
|
const node = useRef3(null);
|
|
1255
1551
|
const dot = useDot();
|
|
1256
|
-
const position = useMemo4(() => new
|
|
1552
|
+
const position = useMemo4(() => new Vector36(point.x, point.y, point.z), [point]);
|
|
1257
1553
|
useFrame2(() => {
|
|
1258
1554
|
const group = node.current;
|
|
1259
1555
|
if (!group) return;
|
|
@@ -1268,7 +1564,7 @@ var ScreenDot = ({ point, pixels, color }) => {
|
|
|
1268
1564
|
var Segment = ({ points, color }) => {
|
|
1269
1565
|
const [a, b] = points;
|
|
1270
1566
|
const ends = useMemo4(
|
|
1271
|
-
() => [new
|
|
1567
|
+
() => [new Vector36(a.x, a.y, a.z), new Vector36(b.x, b.y, b.z)],
|
|
1272
1568
|
// Keyed on the numbers: a draft's tuple is a fresh array on every render,
|
|
1273
1569
|
// and a rebuilt array would rebuild the line's geometry with it.
|
|
1274
1570
|
[a.x, a.y, a.z, b.x, b.y, b.z]
|
|
@@ -1419,6 +1715,8 @@ export {
|
|
|
1419
1715
|
buildRegionTexels,
|
|
1420
1716
|
cadViewDirections,
|
|
1421
1717
|
cameraLimits,
|
|
1718
|
+
capHit,
|
|
1719
|
+
clipSegments,
|
|
1422
1720
|
contentBounds,
|
|
1423
1721
|
createCapMaterial,
|
|
1424
1722
|
createMaskMaterial,
|
|
@@ -1463,6 +1761,7 @@ export {
|
|
|
1463
1761
|
panelGeometry,
|
|
1464
1762
|
perspectiveFitDistance,
|
|
1465
1763
|
pickedStartDepth,
|
|
1764
|
+
pointInContour,
|
|
1466
1765
|
rankOwners,
|
|
1467
1766
|
regionAdjacency,
|
|
1468
1767
|
regionEdgesGeometry,
|
|
@@ -1471,11 +1770,13 @@ export {
|
|
|
1471
1770
|
resolveTheme,
|
|
1472
1771
|
retargetPose,
|
|
1473
1772
|
sameDirection,
|
|
1773
|
+
sampleSection,
|
|
1474
1774
|
screenLength,
|
|
1475
1775
|
screenPoint,
|
|
1476
1776
|
sectionAnchor,
|
|
1477
1777
|
sectionBounds,
|
|
1478
1778
|
sectionConstant,
|
|
1779
|
+
sectionContour,
|
|
1479
1780
|
sectionDepth,
|
|
1480
1781
|
sectionDepthConstant,
|
|
1481
1782
|
sectionDepthRange,
|