@toolpath/viewer 1.5.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.
- package/README.md +87 -10
- package/dist/index.d.ts +101 -25
- package/dist/index.js +363 -109
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -76,7 +76,7 @@ What each wrapper is for:
|
|
|
76
76
|
├─ <EnginePart> validates the report, loads the mesh, then renders <PartMesh>
|
|
77
77
|
│ └─ <PartMesh> draws the part and handles hover, click, colours, section cuts
|
|
78
78
|
├─ <DirectionArrows> arrows for the directions the part can be machined from
|
|
79
|
-
├─ <Stock>
|
|
79
|
+
├─ <Stock> translucent geometry, GLB, box or cylinder stock, included in Fit
|
|
80
80
|
├─ <SectionTool> optional: click a face or a plane to cut the part open
|
|
81
81
|
├─ <MeasureTool> optional: click two points for a distance, three for an angle
|
|
82
82
|
├─ <Grid> <Axes> reference geometry, sized to the part
|
|
@@ -146,9 +146,8 @@ context rather than a fact of the part surface.
|
|
|
146
146
|
|
|
147
147
|
### Stock and display controls
|
|
148
148
|
|
|
149
|
-
Use `<
|
|
150
|
-
|
|
151
|
-
must use the same millimetre, Z-up frame. Both components include stock in Fit and Reset while
|
|
149
|
+
Use `<Stock>` for a box, cylinder, prepared geometry or GLB bytes. Stock and part coordinates
|
|
150
|
+
must use the same millimetre, Z-up frame. Stock is included in Fit and Reset while
|
|
152
151
|
keeping section tools, measurements, the grid, and direction arrows sized to the finished part.
|
|
153
152
|
Stock does not intercept clicks or get clipped by the part's section plane.
|
|
154
153
|
|
|
@@ -156,7 +155,7 @@ Stock does not intercept clicks or get clipped by the part's section plane.
|
|
|
156
155
|
import { useMemo, useRef, useState } from 'react'
|
|
157
156
|
import {
|
|
158
157
|
Axes,
|
|
159
|
-
|
|
158
|
+
Stock,
|
|
160
159
|
DirectionArrows,
|
|
161
160
|
PartMesh,
|
|
162
161
|
Viewer,
|
|
@@ -187,7 +186,7 @@ export function StockPreview({ model, geometry }: { model: PartModel; geometry:
|
|
|
187
186
|
regionHighlights={colors}
|
|
188
187
|
activeDirection={directions ? direction : null}
|
|
189
188
|
/>
|
|
190
|
-
{stock && <
|
|
189
|
+
{stock && <Stock box={{ partGeometry: geometry, allowance: { wall: 3, floor: 3 } }} />}
|
|
191
190
|
{axes && <Axes />}
|
|
192
191
|
<DirectionArrows
|
|
193
192
|
directions={model.candidateDirections}
|
|
@@ -214,25 +213,103 @@ export function StockPreview({ model, geometry }: { model: PartModel; geometry:
|
|
|
214
213
|
}
|
|
215
214
|
```
|
|
216
215
|
|
|
217
|
-
|
|
216
|
+
For a box preview, pass `box={{ partGeometry, allowance, offset }}`. Allowance is padding
|
|
217
|
+
**per side**, in millimetres. The preferred form is
|
|
218
218
|
`{ wall, floor }`: wall stock expands X/Y and floor stock expands Z, matching the wall/floor
|
|
219
219
|
roughing-stock distinction used by machining settings. A number or `{ x, y, z }` remains accepted
|
|
220
220
|
for uniform or axis-specific padding. It defaults to zero. `offset` translates the stock from the
|
|
221
221
|
part's bounding-box centre. `boxStockBounds(geometry, allowance, offset)` returns the same `Box3`
|
|
222
222
|
for displaying dimensions.
|
|
223
223
|
|
|
224
|
-
For fixed-box
|
|
224
|
+
For fixed-box previews, pass `box={{ partGeometry, dimensions, position, positionOffset, offset }}`
|
|
225
|
+
instead, where `dimensions` is `{ x, y, z }`. Dimensions and allowance are alternative sizing
|
|
226
|
+
modes, not simultaneous options. `position` accepts
|
|
225
227
|
`model_centered`, `offset_from_top`, or `offset_from_bottom`, and `positionOffset` is the distance
|
|
226
228
|
from the selected top or bottom bound. All fixed-box values use millimetres and the part's Z-up
|
|
227
229
|
coordinate frame.
|
|
228
230
|
Negative/non-finite allowances, non-finite coordinates, and empty or non-positive stock dimensions
|
|
229
231
|
throw `RangeError`. The 3 mm allowance above is an example, not an automatic stock recommendation.
|
|
230
232
|
|
|
231
|
-
|
|
232
|
-
`
|
|
233
|
+
`<Stock cylinder={{ origin, axis, diameter, length }} />` draws round stock along any axis:
|
|
234
|
+
`origin` is the centre of its base and `axis` points from the base toward its far end, in part
|
|
235
|
+
coordinates. For a preview, pass
|
|
236
|
+
`cylinder={{ partGeometry, diameter, length, position, positionOffset }}` instead. It stands along
|
|
237
|
+
the part's Z, is centred on the part's bounding box, and is placed along Z by the same `position`
|
|
238
|
+
rule as fixed-box stock. The kernel centres fixed round stock on the part's
|
|
239
|
+
smallest enclosing circle about the first setup's cutting direction, so render the resolved stock
|
|
240
|
+
once a job is planned.
|
|
241
|
+
|
|
242
|
+
`<Stock box={{ frame, lower, upper }} />` draws a block whose corners are given in a frame
|
|
243
|
+
of `location`, `axis` and `refDirection`: a point `(x, y, z)` sits at
|
|
244
|
+
`location + x·refDirection + y·(axis × refDirection) + z·axis`, with `axis` normalised and
|
|
245
|
+
`refDirection` projected square to it and normalised. That is the shape of an Engine plan's
|
|
246
|
+
`stock.resolved` box, which is squared to the setup rather than to the part. Omit `frame` when
|
|
247
|
+
`lower` and `upper` are already in part coordinates. Resolved placement and `partGeometry`
|
|
248
|
+
preview options are mutually exclusive for both shapes.
|
|
249
|
+
|
|
250
|
+
Box and cylinder sources rebuild their mesh only when their figures/options change (or the
|
|
251
|
+
preview's `partGeometry` reference changes). Inline option objects do not trigger repeated part
|
|
252
|
+
scans on ordinary renders. Figures that cannot make a
|
|
253
|
+
solid — a zero axis, a non-positive size, a frame whose axis and reference direction are parallel —
|
|
254
|
+
throw `RangeError`.
|
|
255
|
+
|
|
256
|
+
`Stock` accepts exactly one of `geometry`, `glb`, `box` or `cylinder`:
|
|
257
|
+
|
|
258
|
+
```tsx
|
|
259
|
+
<Stock geometry={geometry} showEdges={false} />
|
|
260
|
+
// Or:
|
|
261
|
+
<Stock glb={glbBytes} showEdges={false} />
|
|
262
|
+
<Stock box={resolvedBox} />
|
|
263
|
+
<Stock cylinder={resolvedCylinder} />
|
|
264
|
+
<Stock box={{ partGeometry, dimensions, position, positionOffset }} />
|
|
265
|
+
<Stock cylinder={{ partGeometry, diameter, length, position, positionOffset }} />
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
All four inputs are mutually exclusive in `StockProps`. With `geometry`, the caller owns its
|
|
269
|
+
lifetime and disposal. With `box` or `cylinder`, `Stock` owns the generated geometry. With `glb`,
|
|
270
|
+
`Stock` decodes the bytes internally and disposes decoded
|
|
271
|
+
geometry when the bytes change or the component unmounts. Keep the same `ArrayBuffer` reference
|
|
272
|
+
while displaying the same artifact to avoid decoding it again on ordinary renders.
|
|
273
|
+
It renders nothing while decoding and throws decoding failures to the nearest error boundary.
|
|
274
|
+
Changing the source immediately hides the previous stock; late results from an earlier decode
|
|
275
|
+
are discarded and cleaned up.
|
|
276
|
+
|
|
277
|
+
The component does not fetch, cache or store artifacts. The caller supplies the bytes.
|
|
278
|
+
GLB node transforms are applied without centering, rescaling or changing axes:
|
|
279
|
+
Engine stock artifacts are already in part coordinates, millimetres and Z-up. Indexed geometry
|
|
280
|
+
stays indexed; missing normals are computed without requiring a part report. The parser cleans up
|
|
281
|
+
its temporary scene after preparing the returned geometry.
|
|
282
|
+
|
|
283
|
+
Engine stock files are plain GLB served with HTTP gzip, which the browser handles automatically;
|
|
284
|
+
no meshopt decoder is needed. Authentication, durable artifact copies, action selection and
|
|
285
|
+
missing-artifact fallback and artifact caching belong to the application. The example's
|
|
286
|
+
**Stock source → GLB fixture** option exercises synthetic blanks encoded by the services stock
|
|
287
|
+
writer. **Shape preview** exposes the separate box/cylinder shape controls.
|
|
288
|
+
|
|
289
|
+
`Stock` accepts `color`, `emissive` (default black), `flatShading` (default `false`),
|
|
290
|
+
`opacity` (default `0.2`), `edgeColor`, `edgeOpacity`, and `showEdges`. Emissive shading
|
|
291
|
+
and flat shading change only the material, not the source's vertices, indices or normals.
|
|
292
|
+
`renderStyle="legacy-workpiece"` reproduces legacy Workpiece rendering: white with dark emissive
|
|
293
|
+
shading, per-face shading, 20% opacity, two shared-geometry surface passes and black 50%-opacity
|
|
294
|
+
edges drawn before surfaces (rather than over them). The default `overlay` style is unchanged.
|
|
295
|
+
|
|
296
|
+
For action-to-action playback, `retainPrevious` keeps the current decoded GLB visible until
|
|
297
|
+
the next GLB is ready, then swaps meshes without a blank frame. Key the stock's parent by
|
|
298
|
+
part/calculation identity (not action) to prevent retaining geometry across unrelated runs.
|
|
299
|
+
The default still hides the previous source during decoding. Geometry is disposed after a
|
|
300
|
+
replacement commits; cancelled/late results are discarded.
|
|
301
|
+
The outline is built the first time it is shown, so a large stock mesh with `showEdges={false}`
|
|
302
|
+
costs no edge pass. All source types accept the same appearance props. Caller-provided geometry is never disposed; the
|
|
233
303
|
components dispose their own materials and outlines. Conditionally mount stock to toggle it.
|
|
234
304
|
Toggling stock preserves the camera; Fit/Reset then frames the visible stock together with the part.
|
|
235
305
|
|
|
306
|
+
**Migration from 1.x:** `BoxStock` and `BoxStockProps` have been removed. Use
|
|
307
|
+
`<Stock box={{ partGeometry, allowance, offset }} />` for allowance-based previews, or
|
|
308
|
+
`<Stock box={{ partGeometry, dimensions, position, positionOffset, offset }} />` for fixed
|
|
309
|
+
dimensions. No preview-builder helper is required. `Stock geometry={geometry}` remains supported.
|
|
310
|
+
`StockProps` is now a union type alias, so `interface X extends StockProps` becomes
|
|
311
|
+
`type X = StockProps & { … }`; `StockAppearanceProps` names the shared appearance props alone.
|
|
312
|
+
|
|
236
313
|
`directionHighlights(model, activeDirection?)` returns region colors in the same palette as
|
|
237
314
|
`DirectionArrows`. For a face with several owners, the most specific feature wins, followed by
|
|
238
315
|
candidate-direction order and feature tag. Unmatched directions are left unpainted. Hover and
|
package/dist/index.d.ts
CHANGED
|
@@ -106,8 +106,82 @@ type StockAllowance = number | Vec3 | {
|
|
|
106
106
|
wall: number;
|
|
107
107
|
floor: number;
|
|
108
108
|
};
|
|
109
|
-
/** How
|
|
109
|
+
/** How explicit fixed stock is positioned relative to the part's Z bounds. */
|
|
110
110
|
type StockPosition = 'model_centered' | 'offset_from_top' | 'offset_from_bottom';
|
|
111
|
+
/**
|
|
112
|
+
* Round stock in part coordinates, in millimetres: the centre of its base, the
|
|
113
|
+
* direction from the base toward its far end, and its size. The Engine's
|
|
114
|
+
* resolved `StockCylinder` carries the same four figures.
|
|
115
|
+
*/
|
|
116
|
+
interface CylinderStockFigure {
|
|
117
|
+
origin: Vec3;
|
|
118
|
+
axis: Vec3;
|
|
119
|
+
diameter: number;
|
|
120
|
+
length: number;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* A block squared to a frame of its own rather than to the part, in
|
|
124
|
+
* millimetres. `lower` and `upper` are corners in `frame`, whose point
|
|
125
|
+
* `(x, y, z)` sits at `location + x·refDirection + y·(axis × refDirection) + z·axis`
|
|
126
|
+
* in part coordinates, with `axis` normalised and `refDirection` projected
|
|
127
|
+
* square to it and normalised. This is the shape of the Engine's plan
|
|
128
|
+
* `StockBox`, which is squared to the setup.
|
|
129
|
+
*/
|
|
130
|
+
interface BoxStockFigure {
|
|
131
|
+
/** Omit for a box whose corners are already in part coordinates. */
|
|
132
|
+
frame?: {
|
|
133
|
+
location: Vec3;
|
|
134
|
+
axis: Vec3;
|
|
135
|
+
refDirection: Vec3;
|
|
136
|
+
};
|
|
137
|
+
lower: Vec3;
|
|
138
|
+
upper: Vec3;
|
|
139
|
+
}
|
|
140
|
+
/** Box preview around the part: explicit dimensions or per-side allowance. */
|
|
141
|
+
type BoxStockPreview = {
|
|
142
|
+
partGeometry: BufferGeometry;
|
|
143
|
+
offset?: Vec3;
|
|
144
|
+
} & ({
|
|
145
|
+
dimensions: Vec3;
|
|
146
|
+
position?: StockPosition;
|
|
147
|
+
positionOffset?: number;
|
|
148
|
+
allowance?: never;
|
|
149
|
+
} | {
|
|
150
|
+
allowance?: StockAllowance;
|
|
151
|
+
dimensions?: never;
|
|
152
|
+
position?: never;
|
|
153
|
+
positionOffset?: never;
|
|
154
|
+
});
|
|
155
|
+
/** Resolved corners or part-relative preview options, never both. */
|
|
156
|
+
type BoxStockInput = (BoxStockFigure & {
|
|
157
|
+
partGeometry?: never;
|
|
158
|
+
offset?: never;
|
|
159
|
+
allowance?: never;
|
|
160
|
+
dimensions?: never;
|
|
161
|
+
position?: never;
|
|
162
|
+
positionOffset?: never;
|
|
163
|
+
}) | (BoxStockPreview & {
|
|
164
|
+
lower?: never;
|
|
165
|
+
upper?: never;
|
|
166
|
+
frame?: never;
|
|
167
|
+
});
|
|
168
|
+
/** Upright cylinder preview centered on the part's bounding box. */
|
|
169
|
+
interface CylinderStockPreview {
|
|
170
|
+
partGeometry: BufferGeometry;
|
|
171
|
+
diameter: number;
|
|
172
|
+
length: number;
|
|
173
|
+
position?: StockPosition;
|
|
174
|
+
positionOffset?: number;
|
|
175
|
+
}
|
|
176
|
+
/** Resolved cylinder placement or part-relative preview options, never both. */
|
|
177
|
+
type CylinderStockInput = (CylinderStockFigure & {
|
|
178
|
+
partGeometry?: never;
|
|
179
|
+
position?: never;
|
|
180
|
+
positionOffset?: never;
|
|
181
|
+
}) | (CylinderStockPreview & {
|
|
182
|
+
origin?: never;
|
|
183
|
+
axis?: never;
|
|
184
|
+
});
|
|
111
185
|
/**
|
|
112
186
|
* Bounds for an explicit fixed box. X/Y are centered on the part's bounding
|
|
113
187
|
* box; Z is centered, offset from the top, or offset from the bottom. `offset`
|
|
@@ -117,36 +191,38 @@ declare function fixedBoxStockBounds(geometry: BufferGeometry, dimensions: Vec3,
|
|
|
117
191
|
/** Per-side allowance and centre offset in millimetres, in the part's coordinates. */
|
|
118
192
|
declare function boxStockBounds(geometry: BufferGeometry, allowance?: StockAllowance, offset?: Vec3): Box3;
|
|
119
193
|
|
|
120
|
-
interface
|
|
121
|
-
/**
|
|
122
|
-
|
|
194
|
+
interface StockAppearanceProps {
|
|
195
|
+
/** Keep the preceding decoded GLB visible until its replacement is ready. Key by part/run to reset. */
|
|
196
|
+
retainPrevious?: boolean;
|
|
197
|
+
/** Reproduce the legacy Workpiece's materials, duplicate surface pass and edge ordering. */
|
|
198
|
+
renderStyle?: 'overlay' | 'legacy-workpiece';
|
|
123
199
|
color?: number;
|
|
200
|
+
/** Unlit surface contribution, useful for readable workpiece shading beside the CAD model. */
|
|
201
|
+
emissive?: number;
|
|
202
|
+
/** Shade each triangle independently without changing the source's vertices or normals. */
|
|
203
|
+
flatShading?: boolean;
|
|
124
204
|
opacity?: number;
|
|
125
205
|
edgeColor?: number;
|
|
126
206
|
edgeOpacity?: number;
|
|
127
207
|
showEdges?: boolean;
|
|
128
208
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
positionOffset?: number;
|
|
139
|
-
/**
|
|
140
|
-
* Stock left around the part, in millimetres. In the `{ wall, floor }` form,
|
|
141
|
-
* wall applies to X/Y and floor applies to Z. Number and `{ x, y, z }` forms
|
|
142
|
-
* are retained for compatibility. Defaults to zero.
|
|
143
|
-
*/
|
|
144
|
-
allowance?: StockAllowance;
|
|
145
|
-
/** Translation from the part's bounding-box centre, in millimetres. */
|
|
146
|
-
offset?: Vec3;
|
|
209
|
+
interface StockSources {
|
|
210
|
+
/** Caller-owned geometry; Stock never disposes it. */
|
|
211
|
+
geometry: BufferGeometry;
|
|
212
|
+
/** Caller-provided GLB bytes; Stock owns and cleans up the decoded geometry. */
|
|
213
|
+
glb: ArrayBuffer;
|
|
214
|
+
/** Resolved box corners/frame, or part-relative box preview options. */
|
|
215
|
+
box: BoxStockInput;
|
|
216
|
+
/** Resolved cylinder placement, or part-relative cylinder preview options. */
|
|
217
|
+
cylinder: CylinderStockInput;
|
|
147
218
|
}
|
|
148
|
-
/**
|
|
149
|
-
|
|
219
|
+
/** Exactly one source, in the same millimetre, Z-up coordinates as the part. */
|
|
220
|
+
type StockSource = {
|
|
221
|
+
[K in keyof StockSources]: Pick<StockSources, K> & Partial<Record<Exclude<keyof StockSources, K>, never>>;
|
|
222
|
+
}[keyof StockSources];
|
|
223
|
+
type StockProps = StockAppearanceProps & StockSource;
|
|
224
|
+
/** Translucent stock, included in Fit but ignored by picking, sections and measurements. */
|
|
225
|
+
declare const Stock: ({ geometry, glb, box, cylinder, ...appearance }: StockProps) => react.JSX.Element;
|
|
150
226
|
|
|
151
227
|
/** The six-inch (154 mm) reference model bundled with `@toolpath/viewer`. */
|
|
152
228
|
declare const BANANA_MODEL_URL: string;
|
|
@@ -1778,4 +1854,4 @@ declare const ORBIT_TARGET_FADE_MS = 1000;
|
|
|
1778
1854
|
*/
|
|
1779
1855
|
declare function orbitTargetOpacity(sinceHold: number, fade?: number): number;
|
|
1780
1856
|
|
|
1781
|
-
export { ANGLE_ARC_FRACTION, ANGLE_ARC_SEGMENTS, AXIS_COLORS, type AngleMeasurement, type ArrowPlacement, Axes, type AxesProps, type Axis, type AxisLock, BANANA_GAP, BANANA_MODEL_URL, Banana, type BananaProps,
|
|
1857
|
+
export { ANGLE_ARC_FRACTION, ANGLE_ARC_SEGMENTS, AXIS_COLORS, type AngleMeasurement, type ArrowPlacement, Axes, type AxesProps, type Axis, type AxisLock, BANANA_GAP, BANANA_MODEL_URL, Banana, type BananaProps, type BoxStockFigure, type BoxStockInput, type BoxStockPreview, CAD_CAMERA_UP, CHAMFER, CONTROL_SCHEME_OPTIONS, CadCameraControls, type CadCameraControlsProps, type CameraLimits, type CapMaterial, type CapUniforms, type ControlScheme, type ControlSchemeOption, type CubeZone, type CylinderStockFigure, type CylinderStockInput, type CylinderStockPreview, 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, HoverCard, type HoverCardProps, 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, PartPick, PartReportFormatError, type Projection, type RankingContext, RegionHighlight, 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, Stock, type StockAllowance, type StockAppearanceProps, type StockPosition, type StockProps, type StockSource, 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, ViewerToolbar, type ViewerToolbarControl, type ViewerToolbarControls, type ViewerToolbarControlsProps, type ViewerToolbarProps, ViewerToolbarProvider, type ViewerView, type ViewportSize, adaptedUp, angleArc, angleArcRadius, angleAt, applyCapTheme, applyProjection, arrowPlacement, aspectRatio, bananaFrameBounds, bananaPosition, bestOwner, boundsFromBox, boxStockBounds, buildRegionIndex, cadViewDirections, cameraLimits, capHit, clipSegments, contentBounds, createCapMaterial, createMaskMaterial, createMaskTarget, createSectionStore, createStencilMaterials, cubeOutlineGeometry, cubeZones, currentViewDirection, cycleOwner, defaultBounds, deltaBetween, deltaLegs, directionHighlights, directionIndexOf, directionLabel, distanceBetween, excludedFromFrame, featureTypeRank, fitDistance, fixedBoxStockBounds, 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, useViewerToolbar, viewKind, viewUp, viewVector, visualSurfaces };
|
package/dist/index.js
CHANGED
|
@@ -430,20 +430,105 @@ var ViewerToolbar = Object.assign(ViewerToolbarRoot, {
|
|
|
430
430
|
|
|
431
431
|
// src/stock.tsx
|
|
432
432
|
import { useThree } from "@react-three/fiber";
|
|
433
|
-
import { useEffect as useEffect2, useLayoutEffect, useMemo } from "react";
|
|
434
|
-
|
|
433
|
+
import { useEffect as useEffect2, useLayoutEffect, useMemo, useState as useState2 } from "react";
|
|
434
|
+
|
|
435
|
+
// src/render/stock-mesh.ts
|
|
436
|
+
import { Mesh } from "three";
|
|
437
|
+
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
438
|
+
async function parseStockGlb(data) {
|
|
439
|
+
const { scene } = await new GLTFLoader().parseAsync(data, "");
|
|
440
|
+
try {
|
|
441
|
+
return stockMeshGeometries(scene);
|
|
442
|
+
} finally {
|
|
443
|
+
scene.traverse((object) => {
|
|
444
|
+
if (!(object instanceof Mesh)) return;
|
|
445
|
+
object.geometry.dispose();
|
|
446
|
+
for (const material of Array.isArray(object.material) ? object.material : [object.material]) {
|
|
447
|
+
material.dispose();
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
function stockMeshGeometries(scene) {
|
|
453
|
+
const geometries = [];
|
|
454
|
+
scene.updateWorldMatrix(true, true);
|
|
455
|
+
scene.traverse((object) => {
|
|
456
|
+
if (!(object instanceof Mesh)) return;
|
|
457
|
+
const geometry = object.geometry.clone().applyMatrix4(object.matrixWorld);
|
|
458
|
+
if (!geometry.hasAttribute("normal")) geometry.computeVertexNormals();
|
|
459
|
+
geometries.push(geometry);
|
|
460
|
+
});
|
|
461
|
+
if (geometries.length === 0) throw new Error("The stock GLB contains no mesh.");
|
|
462
|
+
return geometries;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// src/render/stock-mesh-buffer.ts
|
|
466
|
+
function createStockMeshBuffer() {
|
|
467
|
+
const owned = /* @__PURE__ */ new Set();
|
|
468
|
+
let disposed = false;
|
|
469
|
+
return {
|
|
470
|
+
request(decode, ready, failed) {
|
|
471
|
+
disposed = false;
|
|
472
|
+
let active = true;
|
|
473
|
+
void decode().then(
|
|
474
|
+
(geometries) => {
|
|
475
|
+
if (!active || disposed) {
|
|
476
|
+
geometries.forEach((geometry) => geometry.dispose());
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
geometries.forEach((geometry) => owned.add(geometry));
|
|
480
|
+
ready(geometries);
|
|
481
|
+
},
|
|
482
|
+
(cause) => {
|
|
483
|
+
if (active && !disposed) {
|
|
484
|
+
failed(cause);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
);
|
|
488
|
+
return () => {
|
|
489
|
+
active = false;
|
|
490
|
+
};
|
|
491
|
+
},
|
|
492
|
+
commit(geometries) {
|
|
493
|
+
const visible = new Set(geometries);
|
|
494
|
+
for (const geometry of owned) {
|
|
495
|
+
if (!visible.has(geometry)) {
|
|
496
|
+
owned.delete(geometry);
|
|
497
|
+
geometry.dispose();
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
},
|
|
501
|
+
dispose() {
|
|
502
|
+
disposed = true;
|
|
503
|
+
owned.forEach((geometry) => geometry.dispose());
|
|
504
|
+
owned.clear();
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
}
|
|
435
508
|
|
|
436
509
|
// src/render/stock.ts
|
|
437
510
|
import {
|
|
438
511
|
Box3,
|
|
512
|
+
BoxGeometry,
|
|
513
|
+
CylinderGeometry,
|
|
439
514
|
EdgesGeometry,
|
|
440
515
|
Group,
|
|
441
516
|
LineBasicMaterial,
|
|
442
517
|
LineSegments,
|
|
443
|
-
|
|
518
|
+
Matrix4,
|
|
519
|
+
Mesh as Mesh2,
|
|
444
520
|
MeshLambertMaterial,
|
|
521
|
+
Quaternion,
|
|
445
522
|
Vector3
|
|
446
523
|
} from "three";
|
|
524
|
+
function boxFigure(bounds) {
|
|
525
|
+
return {
|
|
526
|
+
lower: { x: bounds.min.x, y: bounds.min.y, z: bounds.min.z },
|
|
527
|
+
upper: { x: bounds.max.x, y: bounds.max.y, z: bounds.max.z }
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
var CYLINDER_SEGMENTS = 64;
|
|
531
|
+
var CYLINDER_AXIS = new Vector3(0, 1, 0);
|
|
447
532
|
function fixedBoxStockBounds(geometry, dimensions, position = "model_centered", positionOffset = 0, offset = { x: 0, y: 0, z: 0 }) {
|
|
448
533
|
if (![dimensions.x, dimensions.y, dimensions.z].every(Number.isFinite) || [dimensions.x, dimensions.y, dimensions.z].some((value) => value <= 0)) {
|
|
449
534
|
throw new RangeError("Fixed stock dimensions must be finite and positive.");
|
|
@@ -456,13 +541,97 @@ function fixedBoxStockBounds(geometry, dimensions, position = "model_centered",
|
|
|
456
541
|
}
|
|
457
542
|
const part = partBounds(geometry);
|
|
458
543
|
const center = part.getCenter(new Vector3());
|
|
459
|
-
const z =
|
|
460
|
-
const half = new Vector3(dimensions.x / 2, dimensions.y / 2, dimensions.z / 2);
|
|
544
|
+
const z = fixedStockLowerZ(part, dimensions.z, position, positionOffset);
|
|
461
545
|
return new Box3(
|
|
462
|
-
new Vector3(center.x, center.y, z)
|
|
463
|
-
new Vector3(center.x, center.y, z
|
|
546
|
+
new Vector3(center.x - dimensions.x / 2, center.y - dimensions.y / 2, z),
|
|
547
|
+
new Vector3(center.x + dimensions.x / 2, center.y + dimensions.y / 2, z + dimensions.z)
|
|
464
548
|
).translate(new Vector3(offset.x, offset.y, offset.z));
|
|
465
549
|
}
|
|
550
|
+
function fixedCylinderStock(geometry, size, position = "model_centered", positionOffset = 0) {
|
|
551
|
+
if (![size.diameter, size.length].every((value) => Number.isFinite(value) && value > 0)) {
|
|
552
|
+
throw new RangeError("Fixed stock diameter and length must be finite and positive.");
|
|
553
|
+
}
|
|
554
|
+
if (!Number.isFinite(positionOffset)) {
|
|
555
|
+
throw new RangeError("Fixed stock position offset must be finite.");
|
|
556
|
+
}
|
|
557
|
+
const part = partBounds(geometry);
|
|
558
|
+
const center = part.getCenter(new Vector3());
|
|
559
|
+
return {
|
|
560
|
+
origin: {
|
|
561
|
+
x: center.x,
|
|
562
|
+
y: center.y,
|
|
563
|
+
z: fixedStockLowerZ(part, size.length, position, positionOffset)
|
|
564
|
+
},
|
|
565
|
+
axis: { x: 0, y: 0, z: 1 },
|
|
566
|
+
diameter: size.diameter,
|
|
567
|
+
length: size.length
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
function fixedStockLowerZ(part, length, position, positionOffset) {
|
|
571
|
+
if (position === "offset_from_top") return part.max.z + positionOffset - length;
|
|
572
|
+
if (position === "offset_from_bottom") return part.min.z - positionOffset;
|
|
573
|
+
return (part.min.z + part.max.z - length) / 2;
|
|
574
|
+
}
|
|
575
|
+
function cylinderStockGeometry(input) {
|
|
576
|
+
const figure = input.partGeometry ? fixedCylinderStock(input.partGeometry, input, input.position, input.positionOffset) : input;
|
|
577
|
+
const { origin, diameter, length } = figure;
|
|
578
|
+
if (![origin.x, origin.y, origin.z].every(Number.isFinite)) {
|
|
579
|
+
throw new RangeError("Cylinder stock origin must be finite.");
|
|
580
|
+
}
|
|
581
|
+
if (![diameter, length].every((value) => Number.isFinite(value) && value > 0)) {
|
|
582
|
+
throw new RangeError("Cylinder stock diameter and length must be finite and positive.");
|
|
583
|
+
}
|
|
584
|
+
const axis = new Vector3(figure.axis.x, figure.axis.y, figure.axis.z);
|
|
585
|
+
const norm = axis.length();
|
|
586
|
+
if (!Number.isFinite(norm) || norm === 0) {
|
|
587
|
+
throw new RangeError("Cylinder stock axis must be finite and non-zero.");
|
|
588
|
+
}
|
|
589
|
+
axis.divideScalar(norm);
|
|
590
|
+
const center = axis.clone().multiplyScalar(length / 2).add(new Vector3(origin.x, origin.y, origin.z));
|
|
591
|
+
return new CylinderGeometry(diameter / 2, diameter / 2, length, CYLINDER_SEGMENTS).applyQuaternion(new Quaternion().setFromUnitVectors(CYLINDER_AXIS, axis)).translate(center.x, center.y, center.z);
|
|
592
|
+
}
|
|
593
|
+
function boxStockGeometry(input) {
|
|
594
|
+
const figure = input.partGeometry ? boxFigure(
|
|
595
|
+
input.dimensions ? fixedBoxStockBounds(
|
|
596
|
+
input.partGeometry,
|
|
597
|
+
input.dimensions,
|
|
598
|
+
input.position,
|
|
599
|
+
input.positionOffset,
|
|
600
|
+
input.offset
|
|
601
|
+
) : boxStockBounds(input.partGeometry, input.allowance, input.offset)
|
|
602
|
+
) : input;
|
|
603
|
+
return orientedBoxStockGeometry(figure);
|
|
604
|
+
}
|
|
605
|
+
function orientedBoxStockGeometry(figure) {
|
|
606
|
+
const { lower, upper } = figure;
|
|
607
|
+
const frame = figure.frame ?? {
|
|
608
|
+
location: { x: 0, y: 0, z: 0 },
|
|
609
|
+
axis: { x: 0, y: 0, z: 1 },
|
|
610
|
+
refDirection: { x: 1, y: 0, z: 0 }
|
|
611
|
+
};
|
|
612
|
+
const { location, axis, refDirection } = frame;
|
|
613
|
+
if (![location, axis, refDirection, lower, upper].every(
|
|
614
|
+
(vector) => [vector.x, vector.y, vector.z].every(Number.isFinite)
|
|
615
|
+
)) {
|
|
616
|
+
throw new RangeError("Box stock frame and corners must be finite.");
|
|
617
|
+
}
|
|
618
|
+
const size = new Vector3(upper.x - lower.x, upper.y - lower.y, upper.z - lower.z);
|
|
619
|
+
if (size.toArray().some((value) => !(value > 0))) {
|
|
620
|
+
throw new RangeError("Box stock upper corner must be above its lower corner on every axis.");
|
|
621
|
+
}
|
|
622
|
+
const z = new Vector3(axis.x, axis.y, axis.z).normalize();
|
|
623
|
+
const x = new Vector3(refDirection.x, refDirection.y, refDirection.z);
|
|
624
|
+
const reference = x.length();
|
|
625
|
+
x.addScaledVector(z, -x.dot(z));
|
|
626
|
+
if (!(z.lengthSq() > 0 && x.length() > 1e-9 * reference)) {
|
|
627
|
+
throw new RangeError(
|
|
628
|
+
"Box stock frame axis and reference direction must be non-zero and not parallel."
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
x.normalize();
|
|
632
|
+
const y = new Vector3().crossVectors(z, x);
|
|
633
|
+
return new BoxGeometry(size.x, size.y, size.z).translate((lower.x + upper.x) / 2, (lower.y + upper.y) / 2, (lower.z + upper.z) / 2).applyMatrix4(new Matrix4().makeBasis(x, y, z).setPosition(location.x, location.y, location.z));
|
|
634
|
+
}
|
|
466
635
|
function boxStockBounds(geometry, allowance = 0, offset = { x: 0, y: 0, z: 0 }) {
|
|
467
636
|
const padding = typeof allowance === "number" ? new Vector3(allowance, allowance, allowance) : "wall" in allowance ? new Vector3(allowance.wall, allowance.wall, allowance.floor) : new Vector3(allowance.x, allowance.y, allowance.z);
|
|
468
637
|
if (padding.toArray().some((value) => !Number.isFinite(value) || value < 0)) {
|
|
@@ -494,81 +663,167 @@ function partBounds(geometry) {
|
|
|
494
663
|
}
|
|
495
664
|
return box;
|
|
496
665
|
}
|
|
497
|
-
function createStock(geometry) {
|
|
666
|
+
function createStock(geometry, legacyWorkpiece = false) {
|
|
498
667
|
const material = new MeshLambertMaterial({ transparent: true, depthWrite: false });
|
|
499
668
|
const edgeMaterial = new LineBasicMaterial({ transparent: true, depthWrite: false });
|
|
500
|
-
const mesh = new
|
|
501
|
-
const edgeGeometry = new EdgesGeometry(geometry, 15);
|
|
502
|
-
const edges = new LineSegments(edgeGeometry, edgeMaterial);
|
|
669
|
+
const mesh = new Mesh2(geometry, material);
|
|
503
670
|
const object = new Group();
|
|
504
671
|
object.userData[STOCK_OBJECT] = true;
|
|
505
|
-
mesh.renderOrder = 5;
|
|
506
|
-
edges.renderOrder = 6;
|
|
672
|
+
mesh.renderOrder = legacyWorkpiece ? 2 : 5;
|
|
507
673
|
mesh.raycast = () => {
|
|
508
674
|
};
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
675
|
+
object.add(mesh);
|
|
676
|
+
if (legacyWorkpiece) {
|
|
677
|
+
const shadow = mesh.clone();
|
|
678
|
+
shadow.raycast = () => {
|
|
679
|
+
};
|
|
680
|
+
object.add(shadow);
|
|
681
|
+
edgeMaterial.depthWrite = true;
|
|
682
|
+
}
|
|
683
|
+
let edges = null;
|
|
512
684
|
return {
|
|
513
685
|
object,
|
|
514
686
|
material,
|
|
515
687
|
edgeMaterial,
|
|
516
|
-
|
|
688
|
+
/**
|
|
689
|
+
* Shows or hides the outline, building it the first time it is shown: the
|
|
690
|
+
* edge pass reads every triangle, and stock cut by a toolpath has a great
|
|
691
|
+
* many of them.
|
|
692
|
+
*/
|
|
693
|
+
showEdges(show) {
|
|
694
|
+
if (show && !edges) {
|
|
695
|
+
edges = new LineSegments(new EdgesGeometry(geometry, 15), edgeMaterial);
|
|
696
|
+
edges.renderOrder = legacyWorkpiece ? 1 : 6;
|
|
697
|
+
edges.raycast = () => {
|
|
698
|
+
};
|
|
699
|
+
object.add(edges);
|
|
700
|
+
}
|
|
701
|
+
if (edges) edges.visible = show;
|
|
702
|
+
},
|
|
517
703
|
dispose() {
|
|
518
704
|
material.dispose();
|
|
519
705
|
edgeMaterial.dispose();
|
|
520
|
-
|
|
706
|
+
edges?.geometry.dispose();
|
|
521
707
|
}
|
|
522
708
|
};
|
|
523
709
|
}
|
|
524
710
|
|
|
525
711
|
// src/stock.tsx
|
|
526
712
|
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
527
|
-
var Stock = ({
|
|
713
|
+
var Stock = ({ geometry, glb, box, cylinder, ...appearance }) => geometry ? /* @__PURE__ */ jsx4(GeometryStock, { geometry, ...appearance }) : glb ? /* @__PURE__ */ jsx4(GlbStock, { glb, ...appearance }) : /* @__PURE__ */ jsx4(ShapeStock, { box, cylinder, ...appearance });
|
|
714
|
+
var GeometryStock = ({
|
|
528
715
|
geometry,
|
|
529
|
-
|
|
716
|
+
renderStyle = "overlay",
|
|
717
|
+
color = renderStyle === "legacy-workpiece" ? 16777215 : 12176354,
|
|
718
|
+
emissive = renderStyle === "legacy-workpiece" ? 3948625 : 0,
|
|
719
|
+
flatShading = renderStyle === "legacy-workpiece",
|
|
530
720
|
opacity = 0.2,
|
|
531
|
-
edgeColor = 11058648,
|
|
532
|
-
edgeOpacity = 0.75,
|
|
721
|
+
edgeColor = renderStyle === "legacy-workpiece" ? 0 : 11058648,
|
|
722
|
+
edgeOpacity = renderStyle === "legacy-workpiece" ? 0.5 : 0.75,
|
|
533
723
|
showEdges = true
|
|
534
724
|
}) => {
|
|
535
725
|
const invalidate = useThree((state) => state.invalidate);
|
|
536
|
-
const stock = useMemo(
|
|
726
|
+
const stock = useMemo(
|
|
727
|
+
() => createStock(geometry, renderStyle === "legacy-workpiece"),
|
|
728
|
+
[geometry, renderStyle]
|
|
729
|
+
);
|
|
537
730
|
useEffect2(() => () => stock.dispose(), [stock]);
|
|
538
731
|
useLayoutEffect(() => {
|
|
539
732
|
stock.material.color.setHex(color);
|
|
733
|
+
stock.material.emissive.setHex(emissive);
|
|
734
|
+
if (stock.material.flatShading !== flatShading) {
|
|
735
|
+
stock.material.flatShading = flatShading;
|
|
736
|
+
stock.material.needsUpdate = true;
|
|
737
|
+
}
|
|
540
738
|
stock.material.opacity = opacity;
|
|
739
|
+
stock.material.depthWrite = renderStyle === "legacy-workpiece" && opacity === 1;
|
|
740
|
+
stock.material.polygonOffset = renderStyle === "legacy-workpiece" && opacity === 1;
|
|
741
|
+
stock.material.polygonOffsetFactor = 1;
|
|
742
|
+
stock.material.polygonOffsetUnits = 1;
|
|
541
743
|
stock.edgeMaterial.color.setHex(edgeColor);
|
|
542
744
|
stock.edgeMaterial.opacity = edgeOpacity;
|
|
543
|
-
stock.
|
|
745
|
+
stock.showEdges(showEdges);
|
|
544
746
|
invalidate();
|
|
545
|
-
}, [
|
|
747
|
+
}, [
|
|
748
|
+
color,
|
|
749
|
+
emissive,
|
|
750
|
+
flatShading,
|
|
751
|
+
edgeColor,
|
|
752
|
+
edgeOpacity,
|
|
753
|
+
invalidate,
|
|
754
|
+
opacity,
|
|
755
|
+
renderStyle,
|
|
756
|
+
showEdges,
|
|
757
|
+
stock
|
|
758
|
+
]);
|
|
546
759
|
return /* @__PURE__ */ jsx4("primitive", { object: stock.object, dispose: null });
|
|
547
760
|
};
|
|
548
|
-
var
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
761
|
+
var GlbStock = ({
|
|
762
|
+
glb,
|
|
763
|
+
retainPrevious = false,
|
|
764
|
+
...appearance
|
|
765
|
+
}) => {
|
|
766
|
+
const buffer = useMemo(() => createStockMeshBuffer(), []);
|
|
767
|
+
const [decoded, setDecoded] = useState2(null);
|
|
768
|
+
useEffect2(() => () => buffer.dispose(), [buffer]);
|
|
769
|
+
useLayoutEffect(() => {
|
|
770
|
+
buffer.commit(decoded?.geometries ?? []);
|
|
771
|
+
}, [buffer, decoded]);
|
|
772
|
+
useEffect2(
|
|
773
|
+
() => buffer.request(
|
|
774
|
+
() => parseStockGlb(glb),
|
|
775
|
+
(geometries) => {
|
|
776
|
+
setDecoded({ source: glb, geometries });
|
|
777
|
+
},
|
|
778
|
+
(cause) => {
|
|
779
|
+
setDecoded({
|
|
780
|
+
source: glb,
|
|
781
|
+
error: cause instanceof Error ? cause : new Error(String(cause))
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
),
|
|
785
|
+
[buffer, glb]
|
|
786
|
+
);
|
|
787
|
+
if (!decoded || !retainPrevious && decoded.source !== glb) return null;
|
|
788
|
+
if (decoded.error) throw decoded.error;
|
|
789
|
+
return decoded.geometries?.map((geometry) => /* @__PURE__ */ jsx4(GeometryStock, { geometry, ...appearance }, geometry.uuid));
|
|
790
|
+
};
|
|
791
|
+
var keepNonFinite = (_key, value) => typeof value === "number" && !Number.isFinite(value) ? String(value) : value;
|
|
792
|
+
var restoreNonFinite = (_key, value) => value === "NaN" || value === "Infinity" || value === "-Infinity" ? Number(value) : value;
|
|
793
|
+
var ShapeStock = ({
|
|
794
|
+
box,
|
|
795
|
+
cylinder,
|
|
796
|
+
...appearance
|
|
556
797
|
}) => {
|
|
557
|
-
const
|
|
558
|
-
const
|
|
559
|
-
const
|
|
798
|
+
const isBox = box !== void 0;
|
|
799
|
+
const partGeometry = box?.partGeometry ?? cylinder?.partGeometry;
|
|
800
|
+
const figure = JSON.stringify(
|
|
801
|
+
box ? box.partGeometry ? {
|
|
802
|
+
dimensions: box.dimensions,
|
|
803
|
+
allowance: box.allowance,
|
|
804
|
+
offset: box.offset,
|
|
805
|
+
position: box.position,
|
|
806
|
+
positionOffset: box.positionOffset
|
|
807
|
+
} : { frame: box.frame, lower: box.lower, upper: box.upper } : cylinder.partGeometry ? {
|
|
808
|
+
diameter: cylinder.diameter,
|
|
809
|
+
length: cylinder.length,
|
|
810
|
+
position: cylinder.position,
|
|
811
|
+
positionOffset: cylinder.positionOffset
|
|
812
|
+
} : {
|
|
813
|
+
origin: cylinder.origin,
|
|
814
|
+
axis: cylinder.axis,
|
|
815
|
+
diameter: cylinder.diameter,
|
|
816
|
+
length: cylinder.length
|
|
817
|
+
},
|
|
818
|
+
keepNonFinite
|
|
819
|
+
);
|
|
560
820
|
const geometry = useMemo(() => {
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
}) : boxStockBounds(partGeometry, allowance, { x: ox, y: oy, z: oz });
|
|
566
|
-
const size = box.getSize(new Vector32());
|
|
567
|
-
const center = box.getCenter(new Vector32());
|
|
568
|
-
return new BoxGeometry(size.x, size.y, size.z).translate(center.x, center.y, center.z);
|
|
569
|
-
}, [allowance, dimensions, ox, oy, oz, partGeometry, position, positionOffset]);
|
|
821
|
+
const options = JSON.parse(figure, restoreNonFinite);
|
|
822
|
+
const input = partGeometry ? { ...options, partGeometry } : options;
|
|
823
|
+
return isBox ? boxStockGeometry(input) : cylinderStockGeometry(input);
|
|
824
|
+
}, [figure, isBox, partGeometry]);
|
|
570
825
|
useEffect2(() => () => geometry.dispose(), [geometry]);
|
|
571
|
-
return /* @__PURE__ */ jsx4(
|
|
826
|
+
return /* @__PURE__ */ jsx4(GeometryStock, { geometry, ...appearance });
|
|
572
827
|
};
|
|
573
828
|
|
|
574
829
|
// src/banana.tsx
|
|
@@ -577,13 +832,13 @@ import { useThree as useThree2 } from "@react-three/fiber";
|
|
|
577
832
|
import { useLayoutEffect as useLayoutEffect2, useMemo as useMemo2, useRef } from "react";
|
|
578
833
|
|
|
579
834
|
// src/render/banana.ts
|
|
580
|
-
import { Vector3 as
|
|
835
|
+
import { Vector3 as Vector32 } from "three";
|
|
581
836
|
var BANANA_GAP = 0.1;
|
|
582
837
|
function bananaPosition(part, banana) {
|
|
583
|
-
const size = part.getSize(new
|
|
838
|
+
const size = part.getSize(new Vector32());
|
|
584
839
|
const reach = Math.max(size.x, size.y, size.z);
|
|
585
|
-
const centre = part.getCenter(new
|
|
586
|
-
return new
|
|
840
|
+
const centre = part.getCenter(new Vector32());
|
|
841
|
+
return new Vector32(
|
|
587
842
|
part.max.x + reach * BANANA_GAP - banana.min.x,
|
|
588
843
|
centre.y - (banana.min.y + banana.max.y) / 2,
|
|
589
844
|
part.min.z - banana.min.z
|
|
@@ -650,16 +905,16 @@ function directionHighlights(model, activeDirection = null) {
|
|
|
650
905
|
// src/primitives.tsx
|
|
651
906
|
import { GizmoHelper } from "@react-three/drei";
|
|
652
907
|
import { useThree as useThree3 } from "@react-three/fiber";
|
|
653
|
-
import { useEffect as useEffect3, useMemo as useMemo3, useRef as useRef2, useState as
|
|
908
|
+
import { useEffect as useEffect3, useMemo as useMemo3, useRef as useRef2, useState as useState3 } from "react";
|
|
654
909
|
|
|
655
910
|
// src/render/grid.ts
|
|
656
|
-
import { BufferGeometry, Float32BufferAttribute, Vector3 as
|
|
911
|
+
import { BufferGeometry, Float32BufferAttribute, Vector3 as Vector33 } from "three";
|
|
657
912
|
var STEPS = [0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1e3];
|
|
658
913
|
var TARGET_CELLS = 10;
|
|
659
914
|
var OVERHANG = 1.6;
|
|
660
915
|
function gridSpec(box) {
|
|
661
|
-
const size = box.getSize(new
|
|
662
|
-
const center = box.getCenter(new
|
|
916
|
+
const size = box.getSize(new Vector33());
|
|
917
|
+
const center = box.getCenter(new Vector33());
|
|
663
918
|
const largest = Math.max(size.x, size.y, 1e-6);
|
|
664
919
|
const ideal = largest / TARGET_CELLS;
|
|
665
920
|
const step = [...STEPS].reverse().find((candidate) => candidate <= ideal) ?? STEPS[0];
|
|
@@ -732,7 +987,7 @@ var CubePanels = ({
|
|
|
732
987
|
const controls = useViewerControls();
|
|
733
988
|
const invalidate = useThree3((state) => state.invalidate);
|
|
734
989
|
const groupRef = useRef2(null);
|
|
735
|
-
const [hovered, setHovered] =
|
|
990
|
+
const [hovered, setHovered] = useState3(null);
|
|
736
991
|
const panels = useMemo3(
|
|
737
992
|
() => zones.map((zone) => ({ zone, geometry: panelGeometry(zone) })),
|
|
738
993
|
[zones]
|
|
@@ -801,7 +1056,7 @@ var CubePanels = ({
|
|
|
801
1056
|
// src/direction-arrows.tsx
|
|
802
1057
|
import { useThree as useThree4 } from "@react-three/fiber";
|
|
803
1058
|
import { useMemo as useMemo4 } from "react";
|
|
804
|
-
import { Quaternion, Vector3 as
|
|
1059
|
+
import { Quaternion as Quaternion2, Vector3 as Vector34 } from "three";
|
|
805
1060
|
import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
806
1061
|
var FURNITURE3 = { [EXCLUDE_FROM_FRAME]: true };
|
|
807
1062
|
var DirectionArrows = ({
|
|
@@ -861,9 +1116,9 @@ var Arrow = ({ direction, box, color, opacity, onPick, onTop = false }) => {
|
|
|
861
1116
|
const invalidate = useThree4((state) => state.invalidate);
|
|
862
1117
|
const { tip, length, quaternion } = useMemo4(() => {
|
|
863
1118
|
const placement = arrowPlacement(direction, box);
|
|
864
|
-
const aim = new
|
|
1119
|
+
const aim = new Quaternion2().setFromUnitVectors(
|
|
865
1120
|
CONE_AXIS,
|
|
866
|
-
new
|
|
1121
|
+
new Vector34(direction.x, direction.y, direction.z).normalize()
|
|
867
1122
|
);
|
|
868
1123
|
return { tip: placement.tip, length: placement.length, quaternion: aim };
|
|
869
1124
|
}, [box, direction]);
|
|
@@ -890,18 +1145,18 @@ var Arrow = ({ direction, box, color, opacity, onPick, onTop = false }) => {
|
|
|
890
1145
|
|
|
891
1146
|
// src/section-tool.tsx
|
|
892
1147
|
import { useFrame, useThree as useThree5 } from "@react-three/fiber";
|
|
893
|
-
import { useEffect as useEffect4, useMemo as useMemo5, useRef as useRef3, useState as
|
|
1148
|
+
import { useEffect as useEffect4, useMemo as useMemo5, useRef as useRef3, useState as useState4, useSyncExternalStore } from "react";
|
|
894
1149
|
import {
|
|
895
1150
|
BufferGeometry as BufferGeometry2,
|
|
896
1151
|
DoubleSide,
|
|
897
|
-
Quaternion as
|
|
1152
|
+
Quaternion as Quaternion3,
|
|
898
1153
|
Raycaster,
|
|
899
1154
|
Vector2,
|
|
900
|
-
Vector3 as
|
|
1155
|
+
Vector3 as Vector35
|
|
901
1156
|
} from "three";
|
|
902
1157
|
import { Fragment, jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
903
1158
|
var FURNITURE4 = { [EXCLUDE_FROM_FRAME]: true };
|
|
904
|
-
var Z = new
|
|
1159
|
+
var Z = new Vector35(0, 0, 1);
|
|
905
1160
|
var AXIS_ROTATIONS = {
|
|
906
1161
|
x: [0, Math.PI / 2, 0],
|
|
907
1162
|
y: [Math.PI / 2, 0, 0],
|
|
@@ -932,10 +1187,10 @@ var SectionTool = ({ theme }) => {
|
|
|
932
1187
|
var useSquare = () => {
|
|
933
1188
|
const square = useMemo5(
|
|
934
1189
|
() => new BufferGeometry2().setFromPoints([
|
|
935
|
-
new
|
|
936
|
-
new
|
|
937
|
-
new
|
|
938
|
-
new
|
|
1190
|
+
new Vector35(-1, 1, 0),
|
|
1191
|
+
new Vector35(1, 1, 0),
|
|
1192
|
+
new Vector35(1, -1, 0),
|
|
1193
|
+
new Vector35(-1, -1, 0)
|
|
939
1194
|
]),
|
|
940
1195
|
[]
|
|
941
1196
|
);
|
|
@@ -952,9 +1207,9 @@ var Picker = ({ box, theme }) => {
|
|
|
952
1207
|
const square = useSquare();
|
|
953
1208
|
const previewRef = useRef3(null);
|
|
954
1209
|
const hover = useRef3(null);
|
|
955
|
-
const [hoveredPlane, setHoveredPlane] =
|
|
956
|
-
const span = useMemo5(() => box.getSize(new
|
|
957
|
-
const centre = useMemo5(() => box.getCenter(new
|
|
1210
|
+
const [hoveredPlane, setHoveredPlane] = useState4(null);
|
|
1211
|
+
const span = useMemo5(() => box.getSize(new Vector35()).length(), [box]);
|
|
1212
|
+
const centre = useMemo5(() => box.getCenter(new Vector35()), [box]);
|
|
958
1213
|
const previewSize = span * PREVIEW_SCALE;
|
|
959
1214
|
useEffect4(() => {
|
|
960
1215
|
store.setPicking(true);
|
|
@@ -963,7 +1218,7 @@ var Picker = ({ box, theme }) => {
|
|
|
963
1218
|
useEffect4(() => {
|
|
964
1219
|
const raycaster = new Raycaster();
|
|
965
1220
|
const pointer = new Vector2();
|
|
966
|
-
const aim = new
|
|
1221
|
+
const aim = new Vector35();
|
|
967
1222
|
const hide = () => {
|
|
968
1223
|
if (!hover.current) return;
|
|
969
1224
|
hover.current = null;
|
|
@@ -1008,7 +1263,7 @@ var Picker = ({ box, theme }) => {
|
|
|
1008
1263
|
domElement.removeEventListener("pointerup", up);
|
|
1009
1264
|
};
|
|
1010
1265
|
}, [box, camera, domElement, invalidate, isTap, scene, span, store]);
|
|
1011
|
-
const [planes, setPlanes] =
|
|
1266
|
+
const [planes, setPlanes] = useState4(() => axesPlanes(box, camera.position));
|
|
1012
1267
|
const key = planes.map((plane) => plane.sign).join();
|
|
1013
1268
|
useFrame(() => {
|
|
1014
1269
|
const next = [camera.position.x, camera.position.y, camera.position.z].map((eye, axis) => eye - [centre.x, centre.y, centre.z][axis] < 0 ? -1 : 1).join();
|
|
@@ -1107,14 +1362,14 @@ var Picker = ({ box, theme }) => {
|
|
|
1107
1362
|
var CutSheet = ({ box, section, theme }) => {
|
|
1108
1363
|
const square = useSquare();
|
|
1109
1364
|
const cut = useMemo5(() => resolveSectionPlane(section, box), [box, section]);
|
|
1110
|
-
const span = useMemo5(() => box.getSize(new
|
|
1365
|
+
const span = useMemo5(() => box.getSize(new Vector35()).length(), [box]);
|
|
1111
1366
|
const placement = useMemo5(() => {
|
|
1112
1367
|
if (!cut) return null;
|
|
1113
1368
|
const position = sectionAnchor(box, cut.plane).addScaledVector(
|
|
1114
1369
|
cut.plane.normal,
|
|
1115
1370
|
-span * SHEET_SETBACK
|
|
1116
1371
|
);
|
|
1117
|
-
return { position, quaternion: new
|
|
1372
|
+
return { position, quaternion: new Quaternion3().setFromUnitVectors(Z, cut.plane.normal) };
|
|
1118
1373
|
}, [box, cut, span]);
|
|
1119
1374
|
if (!placement) return null;
|
|
1120
1375
|
const size = span * OUTLINE_SCALE;
|
|
@@ -1162,26 +1417,26 @@ import {
|
|
|
1162
1417
|
useLayoutEffect as useLayoutEffect3,
|
|
1163
1418
|
useMemo as useMemo6,
|
|
1164
1419
|
useRef as useRef4,
|
|
1165
|
-
useState as
|
|
1420
|
+
useState as useState5,
|
|
1166
1421
|
useSyncExternalStore as useSyncExternalStore2
|
|
1167
1422
|
} from "react";
|
|
1168
1423
|
import {
|
|
1169
|
-
Matrix4,
|
|
1424
|
+
Matrix4 as Matrix42,
|
|
1170
1425
|
Raycaster as Raycaster2,
|
|
1171
1426
|
SphereGeometry,
|
|
1172
1427
|
Vector2 as Vector22,
|
|
1173
|
-
Vector3 as
|
|
1428
|
+
Vector3 as Vector37
|
|
1174
1429
|
} from "three";
|
|
1175
1430
|
|
|
1176
1431
|
// src/render/resample.ts
|
|
1177
|
-
import { Vector3 as
|
|
1432
|
+
import { Vector3 as Vector36 } from "three";
|
|
1178
1433
|
var ON_PLANE = 1e-7;
|
|
1179
1434
|
var STITCH = 1e-5;
|
|
1180
1435
|
var COLLINEAR = 1e-6;
|
|
1181
1436
|
function clipSegments(positions, plane) {
|
|
1182
1437
|
const out = [];
|
|
1183
|
-
const a = new
|
|
1184
|
-
const b = new
|
|
1438
|
+
const a = new Vector36();
|
|
1439
|
+
const b = new Vector36();
|
|
1185
1440
|
const count = Math.floor(positions.length / 6);
|
|
1186
1441
|
for (let i = 0; i < count; i += 1) {
|
|
1187
1442
|
a.set(positions[i * 6], positions[i * 6 + 1], positions[i * 6 + 2]);
|
|
@@ -1196,11 +1451,11 @@ function clipSegments(positions, plane) {
|
|
|
1196
1451
|
return Float32Array.from(out);
|
|
1197
1452
|
}
|
|
1198
1453
|
function sectionContour(positions, regionOf, plane) {
|
|
1199
|
-
const a = new
|
|
1200
|
-
const b = new
|
|
1201
|
-
const c = new
|
|
1202
|
-
const p = new
|
|
1203
|
-
const q = new
|
|
1454
|
+
const a = new Vector36();
|
|
1455
|
+
const b = new Vector36();
|
|
1456
|
+
const c = new Vector36();
|
|
1457
|
+
const p = new Vector36();
|
|
1458
|
+
const q = new Vector36();
|
|
1204
1459
|
const byRegion = /* @__PURE__ */ new Map();
|
|
1205
1460
|
const triangleCount = Math.floor(positions.length / 9);
|
|
1206
1461
|
for (let t = 0; t < triangleCount; t += 1) {
|
|
@@ -1258,17 +1513,17 @@ function stitch(segments, out) {
|
|
|
1258
1513
|
return into.set(segments[o], segments[o + 1], segments[o + 2]);
|
|
1259
1514
|
};
|
|
1260
1515
|
const direction = (i, into) => {
|
|
1261
|
-
const s = new
|
|
1262
|
-
const e = new
|
|
1516
|
+
const s = new Vector36();
|
|
1517
|
+
const e = new Vector36();
|
|
1263
1518
|
point(i, 0, s);
|
|
1264
1519
|
point(i, 1, e);
|
|
1265
1520
|
return into.subVectors(e, s).normalize();
|
|
1266
1521
|
};
|
|
1267
1522
|
const consumed = new Uint8Array(count);
|
|
1268
|
-
const d = new
|
|
1269
|
-
const n = new
|
|
1523
|
+
const d = new Vector36();
|
|
1524
|
+
const n = new Vector36();
|
|
1270
1525
|
const next = (i, end) => {
|
|
1271
|
-
const at = point(i, end, new
|
|
1526
|
+
const at = point(i, end, new Vector36());
|
|
1272
1527
|
const list = ends.get(key(at.x, at.y, at.z)) ?? [];
|
|
1273
1528
|
if (list.length !== 2) return null;
|
|
1274
1529
|
const other = list.find((entry) => entry >> 1 !== i);
|
|
@@ -1297,8 +1552,8 @@ function stitch(segments, out) {
|
|
|
1297
1552
|
};
|
|
1298
1553
|
const back = walk(0);
|
|
1299
1554
|
const forward = walk(1);
|
|
1300
|
-
const s = point(back.index, back.end, new
|
|
1301
|
-
const e = point(forward.index, forward.end, new
|
|
1555
|
+
const s = point(back.index, back.end, new Vector36());
|
|
1556
|
+
const e = point(forward.index, forward.end, new Vector36());
|
|
1302
1557
|
out.push(s.x, s.y, s.z, e.x, e.y, e.z);
|
|
1303
1558
|
}
|
|
1304
1559
|
}
|
|
@@ -1306,8 +1561,8 @@ function pointInContour(point, plane, contour) {
|
|
|
1306
1561
|
const [u, v] = planeAxes(plane);
|
|
1307
1562
|
const px = point.dot(u);
|
|
1308
1563
|
const py = point.dot(v);
|
|
1309
|
-
const a = new
|
|
1310
|
-
const b = new
|
|
1564
|
+
const a = new Vector36();
|
|
1565
|
+
const b = new Vector36();
|
|
1311
1566
|
let inside = false;
|
|
1312
1567
|
const count = Math.floor(contour.length / 6);
|
|
1313
1568
|
for (let i = 0; i < count; i += 1) {
|
|
@@ -1324,15 +1579,15 @@ function pointInContour(point, plane, contour) {
|
|
|
1324
1579
|
return inside;
|
|
1325
1580
|
}
|
|
1326
1581
|
function capHit(ray, plane, contour) {
|
|
1327
|
-
const point = ray.intersectPlane(plane, new
|
|
1582
|
+
const point = ray.intersectPlane(plane, new Vector36());
|
|
1328
1583
|
if (!point) return null;
|
|
1329
1584
|
return pointInContour(point, plane, contour) ? point : null;
|
|
1330
1585
|
}
|
|
1331
1586
|
function planeAxes(plane) {
|
|
1332
1587
|
const n = plane.normal;
|
|
1333
|
-
const seed = Math.abs(n.x) < 0.9 ? new
|
|
1334
|
-
const u = new
|
|
1335
|
-
const v = new
|
|
1588
|
+
const seed = Math.abs(n.x) < 0.9 ? new Vector36(1, 0, 0) : new Vector36(0, 1, 0);
|
|
1589
|
+
const u = new Vector36().crossVectors(n, seed).normalize();
|
|
1590
|
+
const v = new Vector36().crossVectors(n, u).normalize();
|
|
1336
1591
|
return [u, v];
|
|
1337
1592
|
}
|
|
1338
1593
|
function sampleSection(meshPositions, regionOf, edgePositions, plane) {
|
|
@@ -1361,10 +1616,10 @@ var MeasureTool = ({
|
|
|
1361
1616
|
const box = useContentBox();
|
|
1362
1617
|
const resolved = useMemo6(() => resolveTheme(theme), [theme]);
|
|
1363
1618
|
const picking = useSyncExternalStore2(store.subscribe, store.isPicking, store.isPicking);
|
|
1364
|
-
const [held, setHeld] =
|
|
1619
|
+
const [held, setHeld] = useState5([]);
|
|
1365
1620
|
const controlled = measurements !== void 0;
|
|
1366
1621
|
const list = controlled ? measurements : held;
|
|
1367
|
-
const [draft, setDraft] =
|
|
1622
|
+
const [draft, setDraft] = useState5([]);
|
|
1368
1623
|
useEffect5(() => {
|
|
1369
1624
|
store.setEngaged(true);
|
|
1370
1625
|
return () => store.setEngaged(false);
|
|
@@ -1442,7 +1697,7 @@ var MeasureTool = ({
|
|
|
1442
1697
|
] });
|
|
1443
1698
|
};
|
|
1444
1699
|
var NO_POINTER = { pointerEvents: "none" };
|
|
1445
|
-
var NOWHERE = [new
|
|
1700
|
+
var NOWHERE = [new Vector37(), new Vector37()];
|
|
1446
1701
|
var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
|
|
1447
1702
|
const camera = useThree6((state) => state.camera);
|
|
1448
1703
|
const scene = useThree6((state) => state.scene);
|
|
@@ -1495,7 +1750,7 @@ var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
|
|
|
1495
1750
|
const snapped = snapAt(target, target.edges, camera, viewport);
|
|
1496
1751
|
const from = placed[placed.length - 1];
|
|
1497
1752
|
const lock = shift && from ? lockToAxis(from, snapped.point) : null;
|
|
1498
|
-
const found = lock ? { ...snapped, point: new
|
|
1753
|
+
const found = lock ? { ...snapped, point: new Vector37(lock.point.x, lock.point.y, lock.point.z), edge: null } : snapped;
|
|
1499
1754
|
snap.current = found;
|
|
1500
1755
|
const node = marker.current;
|
|
1501
1756
|
if (node) {
|
|
@@ -1657,7 +1912,7 @@ function targetUnderRay(raycaster, scene, samples) {
|
|
|
1657
1912
|
edges: samples.edgesFor(hit.object)
|
|
1658
1913
|
};
|
|
1659
1914
|
}
|
|
1660
|
-
const local = new
|
|
1915
|
+
const local = new Matrix42();
|
|
1661
1916
|
for (const mesh of cutMeshes(scene)) {
|
|
1662
1917
|
const planes = clippingPlanesOf(mesh);
|
|
1663
1918
|
const sample = samples.for(mesh, planes);
|
|
@@ -1720,7 +1975,7 @@ var SampleCache = class {
|
|
|
1720
1975
|
const key = planes.map((plane) => `${plane.normal.x},${plane.normal.y},${plane.normal.z},${plane.constant}`).join("|");
|
|
1721
1976
|
const cached = this.held.get(mesh);
|
|
1722
1977
|
if (cached && cached.key === key) return cached;
|
|
1723
|
-
const local = new
|
|
1978
|
+
const local = new Matrix42().copy(mesh.matrixWorld).invert();
|
|
1724
1979
|
const localPlanes = planes.map((plane) => plane.clone().applyMatrix4(local));
|
|
1725
1980
|
const positions = mesh.geometry.getAttribute("position")?.array ?? [];
|
|
1726
1981
|
const regionOf = this.regionsOf(mesh.geometry);
|
|
@@ -1803,7 +2058,7 @@ var DeltaLegs = ({ a, b, id, format, label }) => {
|
|
|
1803
2058
|
const lines = useRef4([]);
|
|
1804
2059
|
const centre = useMemo6(() => {
|
|
1805
2060
|
const at = midpoint(a, b);
|
|
1806
|
-
return new
|
|
2061
|
+
return new Vector37(at.x, at.y, at.z);
|
|
1807
2062
|
}, [a, b]);
|
|
1808
2063
|
useFrame2(() => {
|
|
1809
2064
|
const dash = screenLength(camera, centre, size, DELTA_DASH_PIXELS);
|
|
@@ -1825,8 +2080,8 @@ var DeltaLegs = ({ a, b, id, format, label }) => {
|
|
|
1825
2080
|
lines.current[index] = line;
|
|
1826
2081
|
},
|
|
1827
2082
|
points: [
|
|
1828
|
-
new
|
|
1829
|
-
new
|
|
2083
|
+
new Vector37(leg.from.x, leg.from.y, leg.from.z),
|
|
2084
|
+
new Vector37(leg.to.x, leg.to.y, leg.to.z)
|
|
1830
2085
|
],
|
|
1831
2086
|
color: AXIS_COLORS[leg.axis],
|
|
1832
2087
|
lineWidth: MEASURE_LINE_PIXELS.leg,
|
|
@@ -1862,7 +2117,7 @@ var ScreenDot = ({ point, pixels, color }) => {
|
|
|
1862
2117
|
const invalidate = useThree6((state) => state.invalidate);
|
|
1863
2118
|
const node = useRef4(null);
|
|
1864
2119
|
const dot = useDot();
|
|
1865
|
-
const position = useMemo6(() => new
|
|
2120
|
+
const position = useMemo6(() => new Vector37(point.x, point.y, point.z), [point]);
|
|
1866
2121
|
useFrame2(() => {
|
|
1867
2122
|
const group = node.current;
|
|
1868
2123
|
if (!group) return;
|
|
@@ -1877,7 +2132,7 @@ var ScreenDot = ({ point, pixels, color }) => {
|
|
|
1877
2132
|
var Segment = ({ points, color }) => {
|
|
1878
2133
|
const [a, b] = points;
|
|
1879
2134
|
const ends = useMemo6(
|
|
1880
|
-
() => [new
|
|
2135
|
+
() => [new Vector37(a.x, a.y, a.z), new Vector37(b.x, b.y, b.z)],
|
|
1881
2136
|
// Keyed on the numbers: a draft's tuple is a fresh array on every render,
|
|
1882
2137
|
// and a rebuilt array would rebuild the line's geometry with it.
|
|
1883
2138
|
[a.x, a.y, a.z, b.x, b.y, b.z]
|
|
@@ -1956,7 +2211,6 @@ export {
|
|
|
1956
2211
|
BANANA_GAP,
|
|
1957
2212
|
BANANA_MODEL_URL,
|
|
1958
2213
|
Banana,
|
|
1959
|
-
BoxStock,
|
|
1960
2214
|
CAD_CAMERA_UP,
|
|
1961
2215
|
CANDIDATE_WEIGHT,
|
|
1962
2216
|
CAP_HATCH,
|