@toolpath/viewer 1.5.0 → 2.1.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 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> <BoxStock> translucent stock, included when fitting the camera
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 `<BoxStock>` for an axis-aligned blank around a part, or `<Stock geometry={stockGeometry} />`
150
- for an actual stock mesh, including cylindrical or irregular blanks. Stock and part coordinates
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
- BoxStock,
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 && <BoxStock partGeometry={geometry} allowance={{ wall: 3, floor: 3 }} />}
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
- `BoxStock.allowance` is padding **per side**, in millimetres. The preferred form is
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 stock, pass `dimensions={{ x, y, z }}` instead. `position` accepts
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
- `Stock` accepts `color`, `opacity` (default `0.2`), `edgeColor`, `edgeOpacity`, and `showEdges`.
232
- `BoxStock` accepts the same appearance props. Caller-provided geometry is never disposed; the
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
@@ -280,8 +357,8 @@ individual buttons to use only the controls and order your application needs.
280
357
  </ViewerToolbarProvider>
281
358
  ```
282
359
 
283
- `ViewerToolbar.Controls`, `Divider`, and the thirteen `*Button` components are available on
284
- `ViewerToolbar`: `StockButton`, `AxesButton`, `GridButton`, `BananaButton`, `DirectionsButton`,
360
+ `ViewerToolbar.Controls`, `Divider`, and the fourteen `*Button` components are available on
361
+ `ViewerToolbar`: `StockButton`, `AxesButton`, `GridButton`, `BananaButton`, `ToolsButton`, `DirectionsButton`,
285
362
  `HoverButton`, `FocusButton`, `WireframeButton`, `SectionButton`, `MeasureButton`, `FitButton`,
286
363
  `ResetButton`, and `TopButton`. A rendered button requires its matching provider control. For an
287
364
  application-specific toolbar component, `useViewerToolbar()` reads the same controls from inside
@@ -311,19 +388,19 @@ override any of these selectors in your application stylesheet.
311
388
  The following `data-*` attributes identify generated elements without depending on labels, which
312
389
  can change with state or localization:
313
390
 
314
- | Attribute | Element and values |
315
- | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
316
- | `data-viewer-root="true"` | `<Viewer>` wrapper |
317
- | `data-viewer-canvas-container="true"` | R3F canvas event container |
318
- | `data-viewer-canvas-frame="true"` | R3F canvas-sizing frame |
319
- | `data-viewer-canvas="true"` | The `<canvas>` itself |
320
- | `data-viewer-hover-card="true"` | `<HoverCard>` shell |
321
- | `data-viewer-toolbar="true"` | Toolbar stack |
322
- | `data-viewer-toolbar-controls="true"` | Controls group |
323
- | `data-viewer-toolbar-action` | Standard button: `stock`, `axes`, `grid`, `banana`, `directions`, `hover`, `focus`, `wireframe`, `section`, `measure`, `fit`, `reset`, or `top` |
324
- | `data-viewer-toolbar-icon="true"` | Standard button SVG |
325
- | `data-viewer-toolbar-tooltip="true"` | Standard button tooltip |
326
- | `data-viewer-toolbar-divider="true"` | Toolbar separator |
391
+ | Attribute | Element and values |
392
+ | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
393
+ | `data-viewer-root="true"` | `<Viewer>` wrapper |
394
+ | `data-viewer-canvas-container="true"` | R3F canvas event container |
395
+ | `data-viewer-canvas-frame="true"` | R3F canvas-sizing frame |
396
+ | `data-viewer-canvas="true"` | The `<canvas>` itself |
397
+ | `data-viewer-hover-card="true"` | `<HoverCard>` shell |
398
+ | `data-viewer-toolbar="true"` | Toolbar stack |
399
+ | `data-viewer-toolbar-controls="true"` | Controls group |
400
+ | `data-viewer-toolbar-action` | Standard button: `stock`, `axes`, `grid`, `banana`, `tools`, `directions`, `hover`, `focus`, `wireframe`, `section`, `measure`, `fit`, `reset`, or `top` |
401
+ | `data-viewer-toolbar-icon="true"` | Standard button SVG |
402
+ | `data-viewer-toolbar-tooltip="true"` | Standard button tooltip |
403
+ | `data-viewer-toolbar-divider="true"` | Toolbar separator |
327
404
 
328
405
  Toggle buttons also carry standard `aria-pressed="true"` or `"false"`; use that to style their
329
406
  current state. `Fit`, `Reset`, and `Top view` are actions rather than toggles, so they omit the
@@ -424,6 +501,7 @@ Draws the part and handles clicks. You only use it directly when you
424
501
  | `section` | `SectionOptions` | Cut the part open. Omit it to follow the viewer's own cut. See [Section view](#section-view). |
425
502
  | `onSectionChange` | `(state: SectionState) => void` | Called when the cut moves or goes away. With `section`, passing it also shows the drag handle. |
426
503
  | `onAdjacency` | `(map) => void` | Called once per mesh with which faces touch which. |
504
+ | `children` | `ReactNode` | Objects drawn on the part. They read its model and mesh with `usePartContext()`. |
427
505
 
428
506
  Hovering over the part is handled for you. You only need `onHover` if you want to show the hovered
429
507
  feature elsewhere in your UI.
@@ -529,6 +607,56 @@ what a label looks like is your stylesheet's, through the `toolpath-measure-labe
529
607
  data attributes on each one — see [Measuring](#measuring) for a starting point. While the tool is
530
608
  mounted the part reports no hovers or picks, as with `<SectionTool>`.
531
609
 
610
+ ### `<ToolMarks>`
611
+
612
+ Cutting tools drawn on the part, each to scale where it stands: a see-through end mill — flat,
613
+ bull nose or ball — or any tool given by its profile, rimmed at its bottom and top, with a dimension
614
+ across its top and a label on it. What a tool stands for is yours: the widest tool a pocket admits, the tool an operation uses.
615
+ Nothing here reads a datasheet.
616
+
617
+ ```tsx
618
+ <EnginePart report={report}>
619
+ <ToolMarks
620
+ marks={[
621
+ {
622
+ base: { x: 12, y: 4, z: -8 },
623
+ axis: { x: 0, y: 0, z: 1 },
624
+ diameter: 6,
625
+ height: 8,
626
+ cornerRadius: 0.5,
627
+ label: '⌀ 6.000 mm',
628
+ note: 'bull nose R 0.5',
629
+ },
630
+ ]}
631
+ />
632
+ </EnginePart>
633
+ ```
634
+
635
+ | Prop | What it does |
636
+ | ---------------- | ---------------------------------------------------------------------------------- |
637
+ | `marks` | The tools, in the coordinates the part is drawn in. See `ToolMark` below. |
638
+ | `visible` | `false` hides them without unmounting. On by default. |
639
+ | `labelClassName` | Added to every label, beside `toolpath-measure-label`. |
640
+ | `theme` | `measure` colours the rims and the dimension, as it does the measure tool's lines. |
641
+
642
+ A `ToolMark` is `base` (the centre of the tool's bottom), `axis` (up the tool), `diameter`,
643
+ `height`, and optionally `cornerRadius` (0 flat, half the diameter a ball), `across` (which way the
644
+ dimension runs; any direction square to the axis otherwise), `label` and `note`. Any other tool —
645
+ a drill, a chamfer mill, a tool in its holder — takes `profile`: its side as `{ r, z }` points from
646
+ the tip up, such as `@toolpath/tool-drawing`'s outline, turned about the axis in place of the end
647
+ mill.
648
+
649
+ Show and hide them with `visible`, from the toolbar's `ToolsButton` and its `tools` control.
650
+
651
+ It draws as `<MeasureTool>` draws: the lines show through the part, and each label is an unstyled
652
+ `<div class="toolpath-measure-label" data-measure-label="tool">` with its text in a
653
+ `data-measure-value` span and its note in a `data-measure-note` one. The marks take no pointer
654
+ events and are left out of framing.
655
+
656
+ Inside `<PartMesh>` or `<EnginePart>`, `usePartContext()` gives the part's `model` and `geometry`,
657
+ and `featureTriangles(model, geometry, tag)` a feature's triangles, for working out where a tool
658
+ stands.
659
+
532
660
  ### `<Grid>` and `<Axes>`
533
661
 
534
662
  Reference geometry. The camera ignores both when it frames the part.
@@ -1108,6 +1236,7 @@ is a complete app built this way, with no API key needed.
1108
1236
  | `useViewerToolbar()` | Inside `<ViewerToolbarProvider>` | Get the actions and state supplied to the provider. |
1109
1237
  | `useSectionStore()` | Inside `<Viewer>` | Read, set, or subscribe to the viewer's own cut. |
1110
1238
  | `useContentBox()` | Inside `<Viewer>` | Get the part's bounding box (a `THREE.Box3`, empty until loaded). |
1239
+ | `usePartContext()` | Inside `<PartMesh>` | Get the part's `model` and the `geometry` it is drawn from, for an overlay. |
1111
1240
  | `useTapGuard()` | Inside `<Viewer>` | Check whether a pointer event was a click and not a drag. |
1112
1241
 
1113
1242
  ### Helpers
@@ -1121,6 +1250,7 @@ is a complete app built this way, with no API key needed.
1121
1250
  | `focusForPick(pick, lastRegion, lastFocus)` | Steps through a face's matches on repeated clicks. |
1122
1251
  | `rankOwners` / `bestOwner` | The ranking `onPick` uses, if you want to run it yourself. |
1123
1252
  | `groupByDirection(model)` | Features grouped by machining direction. |
1253
+ | `featureTriangles(model, geometry, tag)` | A feature's triangles and outward normals, as plain arrays. |
1124
1254
  | `directionLabel(v)` / `directionColor(i)` | A direction's label and colour. |
1125
1255
  | `sectionFromPick({ point, normal })` | Turns a clicked surface into a section plane. |
1126
1256
  | `measurementLabel(measurement, format?)` | Writes a measurement the way `<MeasureTool>` labels it. |
@@ -13,6 +13,15 @@ import {
13
13
  } from "react";
14
14
  import { Vector3 as Vector311 } from "three";
15
15
 
16
+ // src/part-context.tsx
17
+ import { createContext, useContext } from "react";
18
+ var PartContext = createContext(null);
19
+ var usePartContext = () => {
20
+ const part = useContext(PartContext);
21
+ if (!part) throw new Error("usePartContext must be used inside <PartMesh> or <EnginePart>");
22
+ return part;
23
+ };
24
+
16
25
  // src/model/directions.ts
17
26
  var EPSILON = 1e-6;
18
27
  function sameDirection(a, b) {
@@ -781,8 +790,8 @@ function perpendicular(axis) {
781
790
  // src/tap.tsx
782
791
  import { useThree } from "@react-three/fiber";
783
792
  import {
784
- createContext,
785
- useContext,
793
+ createContext as createContext2,
794
+ useContext as useContext2,
786
795
  useEffect,
787
796
  useMemo,
788
797
  useRef
@@ -823,10 +832,10 @@ function trackDoubleTaps(within = DOUBLE_TAP_MS, slop = TAP_SLOP) {
823
832
 
824
833
  // src/tap.tsx
825
834
  import { jsx } from "react/jsx-runtime";
826
- var ViewerTapContext = createContext(null);
835
+ var ViewerTapContext = createContext2(null);
827
836
  var ViewerTapProvider = ({ value, children }) => /* @__PURE__ */ jsx(ViewerTapContext.Provider, { value, children });
828
837
  function useTapGuard() {
829
- const shared = useContext(ViewerTapContext);
838
+ const shared = useContext2(ViewerTapContext);
830
839
  const domElement = useThree((state) => state.gl.domElement);
831
840
  const tracker = useRef(null);
832
841
  useEffect(() => {
@@ -1319,10 +1328,10 @@ function focusForPick(pick, previousRegion, previousFocus) {
1319
1328
  // src/viewer.tsx
1320
1329
  import { Canvas, useFrame as useFrame3, useThree as useThree4 } from "@react-three/fiber";
1321
1330
  import {
1322
- createContext as createContext2,
1331
+ createContext as createContext3,
1323
1332
  forwardRef,
1324
1333
  useCallback,
1325
- useContext as useContext2,
1334
+ useContext as useContext3,
1326
1335
  useEffect as useEffect4,
1327
1336
  useImperativeHandle,
1328
1337
  useMemo as useMemo4,
@@ -2193,20 +2202,20 @@ function labelTexture(label, color) {
2193
2202
 
2194
2203
  // src/viewer.tsx
2195
2204
  import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
2196
- var ViewerControlsContext = createContext2(null);
2205
+ var ViewerControlsContext = createContext3(null);
2197
2206
  var useViewerControls = () => {
2198
- const controls = useContext2(ViewerControlsContext);
2207
+ const controls = useContext3(ViewerControlsContext);
2199
2208
  if (!controls) throw new Error("useViewerControls must be used inside <Viewer>");
2200
2209
  return controls;
2201
2210
  };
2202
- var SectionStoreContext = createContext2(null);
2211
+ var SectionStoreContext = createContext3(null);
2203
2212
  var useSectionStore = () => {
2204
- const store = useContext2(SectionStoreContext);
2213
+ const store = useContext3(SectionStoreContext);
2205
2214
  if (!store) throw new Error("useSectionStore must be used inside <Viewer>");
2206
2215
  return store;
2207
2216
  };
2208
- var ViewerRetargetContext = createContext2(null);
2209
- var useRetarget = () => useContext2(ViewerRetargetContext);
2217
+ var ViewerRetargetContext = createContext3(null);
2218
+ var useRetarget = () => useContext3(ViewerRetargetContext);
2210
2219
  var ViewerScene = ({
2211
2220
  children,
2212
2221
  setControls,
@@ -3029,9 +3038,11 @@ var PartMesh = ({
3029
3038
  onPick,
3030
3039
  theme,
3031
3040
  showEdges = true,
3032
- display = "solid"
3041
+ display = "solid",
3042
+ children
3033
3043
  }) => {
3034
3044
  const { camera, controls, invalidate } = useThree7();
3045
+ const context = useMemo6(() => ({ model, geometry }), [model, geometry]);
3035
3046
  const viewerControls = useViewerControls();
3036
3047
  const resolved = useStableTheme(theme);
3037
3048
  const currentTheme = useRef6(resolved);
@@ -3243,7 +3254,8 @@ var PartMesh = ({
3243
3254
  showHandle: !controlled || onSectionChange !== void 0,
3244
3255
  onDrag: !controlled || onSectionChange ? dragSection : void 0
3245
3256
  }
3246
- ) : null
3257
+ ) : null,
3258
+ children ? /* @__PURE__ */ jsx5(PartContext.Provider, { value: context, children }) : null
3247
3259
  ] });
3248
3260
  };
3249
3261
  var ORIGIN = new Vector311();
@@ -3890,6 +3902,7 @@ function useGeometryResource(part) {
3890
3902
  }
3891
3903
 
3892
3904
  export {
3905
+ usePartContext,
3893
3906
  sameDirection,
3894
3907
  directionIndexOf,
3895
3908
  groupByDirection,
@@ -1,5 +1,5 @@
1
- import { P as PartModel, a as PartMeshRefs } from '../normalize-CvnV9Ggh.js';
2
- export { E as EnginePart, b as EnginePartProps, M as MIN_KERNEL_VERSION, c as assertSupportedKernelVersion, n as normalizePartReport, s as smoothRegionNormals } from '../normalize-CvnV9Ggh.js';
1
+ import { P as PartModel, a as PartMeshRefs } from '../normalize--FiXUhO4.js';
2
+ export { E as EnginePart, b as EnginePartProps, M as MIN_KERNEL_VERSION, c as assertSupportedKernelVersion, n as normalizePartReport, s as smoothRegionNormals } from '../normalize--FiXUhO4.js';
3
3
  import { BufferGeometry } from 'three';
4
4
  import 'react';
5
5
 
@@ -12,7 +12,7 @@ import {
12
12
  parsePartGeometry,
13
13
  partMeshAssets,
14
14
  smoothRegionNormals
15
- } from "../chunk-4TBLMR4Z.js";
15
+ } from "../chunk-WTTOUG4P.js";
16
16
  export {
17
17
  EnginePart,
18
18
  MIN_KERNEL_VERSION,