@toolpath/viewer 0.2.0 → 0.3.1

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
@@ -22,7 +22,7 @@ import { EnginePart } from '@toolpath/viewer/engine'
22
22
  </Suspense>
23
23
  ```
24
24
 
25
- `EnginePart` takes a `PartReportResponse` exactly as `@toolpath/api` returns it and validates it:
25
+ `EnginePart` takes a `PartResponse` exactly as `@toolpath/api` returns it and validates it:
26
26
  a malformed report throws `PartReportFormatError` carrying every problem it found, and one from a
27
27
  kernel older than `0.3.0` — before `regions[]` and `featureTag` existed — throws
28
28
  `UnsupportedKernelVersionError`. It fetches `meshGlbUrl`, falls back to `meshStlUrl`, and refuses a
@@ -47,107 +47,6 @@ function trim(value) {
47
47
  return Number.parseFloat(value.toFixed(3)).toString();
48
48
  }
49
49
 
50
- // src/model/surfaces.ts
51
- var CONTINUES_WITHIN = Math.cos(1 * Math.PI / 180);
52
- var MERGEABLE = "Plane";
53
- var worked = /* @__PURE__ */ new WeakMap();
54
- function visualSurfaces(geometry, regions) {
55
- const already = worked.get(geometry);
56
- if (already && already.regions === regions) return already.of;
57
- const found = computeSurfaces(geometry, regions);
58
- worked.set(geometry, { regions, of: found });
59
- return found;
60
- }
61
- function computeSurfaces(geometry, regions) {
62
- const position = geometry.getAttribute("position");
63
- const surfaces = /* @__PURE__ */ new Map();
64
- for (const region of regions) surfaces.set(region.idx, region.idx);
65
- if (!position || geometry.index) return surfaces;
66
- const triangleCount = Math.floor(position.count / 3);
67
- const regionOf = new Int32Array(triangleCount).fill(-1);
68
- const kindOf = /* @__PURE__ */ new Map();
69
- for (const region of regions) {
70
- kindOf.set(region.idx, region.shapeKind);
71
- const end = Math.min(region.triangles.end, triangleCount);
72
- for (let triangle = region.triangles.start; triangle < end; triangle += 1) {
73
- regionOf[triangle] = region.idx;
74
- }
75
- }
76
- const normals = facetNormals(geometry, triangleCount);
77
- const parent = new Map(surfaces);
78
- const find = (idx) => {
79
- let root = idx;
80
- while (parent.get(root) !== root) root = parent.get(root) ?? root;
81
- let walk = idx;
82
- while (parent.get(walk) !== root) {
83
- const next = parent.get(walk) ?? root;
84
- parent.set(walk, root);
85
- walk = next;
86
- }
87
- return root;
88
- };
89
- const union = (a, b) => {
90
- const [rootA, rootB] = [find(a), find(b)];
91
- if (rootA !== rootB) parent.set(rootB, rootA);
92
- };
93
- const seen = /* @__PURE__ */ new Map();
94
- for (let triangle = 0; triangle < triangleCount; triangle += 1) {
95
- for (let corner = 0; corner < 3; corner += 1) {
96
- const a = triangle * 3 + corner;
97
- const b = triangle * 3 + (corner + 1) % 3;
98
- const id = edgeKey(position, a, b);
99
- const met = seen.get(id);
100
- if (met === void 0) {
101
- seen.set(id, triangle);
102
- continue;
103
- }
104
- const here = regionOf[triangle] ?? -1;
105
- const there = regionOf[met] ?? -1;
106
- if (here === -1 || there === -1 || here === there) continue;
107
- if (kindOf.get(here) !== kindOf.get(there)) continue;
108
- const facing = normals[triangle * 3] * normals[met * 3] + normals[triangle * 3 + 1] * normals[met * 3 + 1] + normals[triangle * 3 + 2] * normals[met * 3 + 2];
109
- if (kindOf.get(here) !== MERGEABLE) continue;
110
- if (facing >= CONTINUES_WITHIN) union(here, there);
111
- }
112
- }
113
- for (const region of regions) surfaces.set(region.idx, find(region.idx));
114
- return surfaces;
115
- }
116
- function facetNormals(geometry, triangleCount) {
117
- const position = geometry.getAttribute("position");
118
- const normals = new Float32Array(triangleCount * 3);
119
- for (let triangle = 0; triangle < triangleCount; triangle += 1) {
120
- const at = triangle * 3;
121
- const ax = position.getX(at);
122
- const ay = position.getY(at);
123
- const az = position.getZ(at);
124
- const bx = position.getX(at + 1) - ax;
125
- const by = position.getY(at + 1) - ay;
126
- const bz = position.getZ(at + 1) - az;
127
- const cx = position.getX(at + 2) - ax;
128
- const cy = position.getY(at + 2) - ay;
129
- const cz = position.getZ(at + 2) - az;
130
- const nx = by * cz - bz * cy;
131
- const ny = bz * cx - bx * cz;
132
- const nz = bx * cy - by * cx;
133
- const length = Math.hypot(nx, ny, nz) || 1;
134
- normals[at] = nx / length;
135
- normals[at + 1] = ny / length;
136
- normals[at + 2] = nz / length;
137
- }
138
- return normals;
139
- }
140
- function edgeKey(position, a, b) {
141
- const ax = position.getX(a);
142
- const ay = position.getY(a);
143
- const az = position.getZ(a);
144
- const bx = position.getX(b);
145
- const by = position.getY(b);
146
- const bz = position.getZ(b);
147
- const first = ax < bx || ax === bx && (ay < by || ay === by && az <= bz);
148
- return first ? `${ax},${ay},${az}|${bx},${by},${bz}` : `${bx},${by},${bz}|${ax},${ay},${az}`;
149
- }
150
-
151
50
  // src/render/theme.ts
152
51
  var HIGHLIGHT_COLORS = {
153
52
  default: 16753434,
@@ -262,31 +161,6 @@ function applyHighlightLayers(part, layers, theme) {
262
161
  if (layers.hoverRegion != null && !selectedRegions(part, layers).has(layers.hoverRegion)) {
263
162
  part.paintRegion(layers.hoverRegion, theme.hover, HOVER_WEIGHT);
264
163
  }
265
- spreadAcrossSurfaces(part);
266
- }
267
- function spreadAcrossSurfaces(part) {
268
- const surfaces = visualSurfaces(part.mesh.geometry, part.model.regions);
269
- const claims = /* @__PURE__ */ new Map();
270
- for (const region of part.model.regions) {
271
- const paint = part.regionPaint(region.idx);
272
- if (!paint || paint.weight === 0) continue;
273
- const surface = surfaces.get(region.idx) ?? region.idx;
274
- const claim = claims.get(surface);
275
- if (!claim) {
276
- claims.set(surface, { color: paint.color, weight: paint.weight });
277
- continue;
278
- }
279
- if (claim === "contested") continue;
280
- if (claim.color !== paint.color || claim.weight !== paint.weight) {
281
- claims.set(surface, "contested");
282
- }
283
- }
284
- for (const region of part.model.regions) {
285
- const paint = part.regionPaint(region.idx);
286
- if (paint && paint.weight > 0) continue;
287
- const claim = claims.get(surfaces.get(region.idx) ?? region.idx);
288
- if (claim && claim !== "contested") part.paintRegion(region.idx, claim.color, claim.weight);
289
- }
290
164
  }
291
165
 
292
166
  // src/render/section.ts
@@ -432,13 +306,20 @@ import {
432
306
 
433
307
  // src/render/edges.ts
434
308
  import { BufferGeometry as Buffer, Float32BufferAttribute } from "three";
309
+
310
+ // src/model/surfaces.ts
311
+ function visualSurfaces(regions) {
312
+ return new Map(regions.map((region) => [region.idx, region.splitOrigin]));
313
+ }
314
+
315
+ // src/render/edges.ts
435
316
  function regionEdgesGeometry(geometry, model) {
436
317
  const position = geometry.getAttribute("position");
437
318
  const edges = new Buffer();
438
319
  edges.setAttribute("position", new Float32BufferAttribute([], 3));
439
320
  if (!position || geometry.index) return edges;
440
321
  const triangleCount = Math.floor(position.count / 3);
441
- const surfaces = visualSurfaces(geometry, model.regions);
322
+ const surfaces = visualSurfaces(model.regions);
442
323
  const regionOf = new Int32Array(triangleCount).fill(-1);
443
324
  for (const region of model.regions) {
444
325
  const end = Math.min(region.triangles.end, triangleCount);
@@ -1942,7 +1823,7 @@ function smoothRegionNormals(geometry, regions) {
1942
1823
  if (!position || geometry.index) return;
1943
1824
  const vertexCount = position.count;
1944
1825
  const triangleCount = Math.floor(vertexCount / 3);
1945
- const surfaces = visualSurfaces(geometry, regions);
1826
+ const surfaces = visualSurfaces(regions);
1946
1827
  const regionOf = new Int32Array(triangleCount).fill(-1);
1947
1828
  for (const region of regions) {
1948
1829
  const end = Math.min(region.triangles.end, triangleCount);
@@ -2304,15 +2185,16 @@ function readRegions(value, issues) {
2304
2185
  continue;
2305
2186
  }
2306
2187
  const idx = raw["idx"];
2188
+ const splitOrigin = raw["splitOrigin"];
2307
2189
  const start = raw["triangleStart"];
2308
2190
  const end = raw["triangleEnd"];
2309
2191
  const area = raw["area"];
2310
2192
  const shapeKind = raw["shapeKind"];
2311
- if (!isNonNegativeInteger(idx) || !isNonNegativeInteger(start) || !isNonNegativeInteger(end) || !isFiniteNumber(area) || !isString(shapeKind)) {
2193
+ if (!isNonNegativeInteger(idx) || !isNonNegativeInteger(splitOrigin) || !isNonNegativeInteger(start) || !isNonNegativeInteger(end) || !isFiniteNumber(area) || !isString(shapeKind)) {
2312
2194
  issues.push(`regions[${i}] does not match the Region schema`);
2313
2195
  continue;
2314
2196
  }
2315
- regions.push({ idx, shapeKind, area, triangles: { start, end } });
2197
+ regions.push({ idx, splitOrigin, shapeKind, area, triangles: { start, end } });
2316
2198
  }
2317
2199
  return regions;
2318
2200
  }
@@ -2446,8 +2328,6 @@ export {
2446
2328
  directionIndexOf,
2447
2329
  groupByDirection,
2448
2330
  directionLabel,
2449
- CONTINUES_WITHIN,
2450
- visualSurfaces,
2451
2331
  HIGHLIGHT_COLORS,
2452
2332
  DIRECTION_COLORS,
2453
2333
  DEFAULT_THEME,
@@ -2476,6 +2356,7 @@ export {
2476
2356
  movedFar,
2477
2357
  trackTaps,
2478
2358
  useTapGuard,
2359
+ visualSurfaces,
2479
2360
  regionEdgesGeometry,
2480
2361
  REGION_ATTRIBUTE,
2481
2362
  buildRegionTexels,
@@ -1,5 +1,5 @@
1
- import { P as PartModel, a as PartMeshRefs } from '../normalize-B0HBvzGu.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-B0HBvzGu.js';
1
+ import { P as PartModel, a as PartMeshRefs } from '../normalize-Bzzwwxt4.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-Bzzwwxt4.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-7NQBV7EQ.js";
15
+ } from "../chunk-RICG6UVH.js";
16
16
  export {
17
17
  EnginePart,
18
18
  MIN_KERNEL_VERSION,
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { V as Vec3, d as ViewerTheme, P as PartModel, e as PartModelRegion, f as ViewerCamera, g as ViewerView, h as Projection, T as TriangleRange, F as FeatureTag, R as RegionIndex, i as PartModelFeature, j as FeatureType } from './normalize-B0HBvzGu.js';
2
- export { B as BuildPickInput, C as CAD_CAMERA_UP, k as CANDIDATE_WEIGHT, D as DEFAULT_FIT_MARGIN, l as DEFAULT_THEME, m as DIRECTION_COLORS, o as EXCLUDE_FROM_FRAME, 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, N as NO_MODIFIERS, v as PERSPECTIVE_FOV, w as PICKED_SURFACE_LABEL, x as PartMesh, y as PartMeshProps, a as PartMeshRefs, z as PartObject, A as PartPick, G as PickModifiers, I as REGION_ATTRIBUTE, J as RegionHighlight, L as RegionPaint, S as SECTION_RENDER_ORDER, O as SceneBounds, Q as SectionAnchor, U as SectionBounds, W as SectionOptions, X as SectionPlacement, Y as SectionState, Z as SectionView, _ as ViewportSize, $ as applyHighlightLayers, a0 as applyProjection, a1 as aspectRatio, a2 as boundsFromBox, a3 as buildPick, a4 as buildRegionAttribute, a5 as buildRegionTexels, a6 as cadViewDirections, a7 as contentBounds, a8 as createPart, a9 as currentViewDirection, aa as defaultBounds, ab as directionColor, ac as dragPlane, ad as fitDistance, ae as focusForPick, n as normalizePartReport, af as orthographicHalfHeight, ag as perspectiveFitDistance, ah as pickedStartDepth, ai as resolveSectionPlane, aj as resolveTheme, ak as screenLength, al as sectionBounds, am as sectionConstant, an as sectionDepth, ao as sectionDepthConstant, ap as sectionDepthRange, aq as sectionFromPick, ar as sectionOffset, as as sectionPlane, s as smoothRegionNormals, at as startPosition, au as themesEqual, av as viewDirection } from './normalize-B0HBvzGu.js';
1
+ import { V as Vec3, d as ViewerTheme, P as PartModel, e as PartModelRegion, f as ViewerCamera, g as ViewerView, h as Projection, T as TriangleRange, F as FeatureTag, R as RegionIndex, i as PartModelFeature, j as FeatureType } from './normalize-Bzzwwxt4.js';
2
+ export { B as BuildPickInput, C as CAD_CAMERA_UP, k as CANDIDATE_WEIGHT, D as DEFAULT_FIT_MARGIN, l as DEFAULT_THEME, m as DIRECTION_COLORS, o as EXCLUDE_FROM_FRAME, 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, N as NO_MODIFIERS, v as PERSPECTIVE_FOV, w as PICKED_SURFACE_LABEL, x as PartMesh, y as PartMeshProps, a as PartMeshRefs, z as PartObject, A as PartPick, G as PickModifiers, I as REGION_ATTRIBUTE, J as RegionHighlight, L as RegionPaint, S as SECTION_RENDER_ORDER, O as SceneBounds, Q as SectionAnchor, U as SectionBounds, W as SectionOptions, X as SectionPlacement, Y as SectionState, Z as SectionView, _ as ViewportSize, $ as applyHighlightLayers, a0 as applyProjection, a1 as aspectRatio, a2 as boundsFromBox, a3 as buildPick, a4 as buildRegionAttribute, a5 as buildRegionTexels, a6 as cadViewDirections, a7 as contentBounds, a8 as createPart, a9 as currentViewDirection, aa as defaultBounds, ab as directionColor, ac as dragPlane, ad as fitDistance, ae as focusForPick, n as normalizePartReport, af as orthographicHalfHeight, ag as perspectiveFitDistance, ah as pickedStartDepth, ai as resolveSectionPlane, aj as resolveTheme, ak as screenLength, al as sectionBounds, am as sectionConstant, an as sectionDepth, ao as sectionDepthConstant, ap as sectionDepthRange, aq as sectionFromPick, ar as sectionOffset, as as sectionPlane, s as smoothRegionNormals, at as startPosition, au as themesEqual, av as viewDirection } from './normalize-Bzzwwxt4.js';
3
3
  import * as react from 'react';
4
4
  import { RefObject, ReactNode, CSSProperties } from 'react';
5
5
  import { BufferGeometry, CanvasTexture, Vector3, Box3 } from 'three';
@@ -283,59 +283,27 @@ declare function gridGeometry(spec: GridSpec): BufferGeometry;
283
283
  * With one qualification. The Engine splits a surface where that makes a better
284
284
  * machining plan, and those splits are boundaries between regions without being
285
285
  * edges of the part: a floor cut in two to be reached from two directions is
286
- * still one flat floor. So the walk is over *visual surfaces* — regions grouped
287
- * where they continue each other — and a split leaves no line. See
288
- * `visualSurfaces`; nothing about picking or features goes through it.
286
+ * still one face to look at. So the walk is over *visual surfaces* — regions
287
+ * grouped by the kernel's exact `splitOrigin` lineage — and a split leaves no
288
+ * line. Nothing about picking or features goes through that grouping.
289
289
  *
290
290
  * The mesh must be non-indexed, which `parsePartGeometry` guarantees.
291
291
  */
292
292
  declare function regionEdgesGeometry(geometry: BufferGeometry, model: Pick<PartModel, 'regions' | 'regionIndex'>): BufferGeometry;
293
293
 
294
+ /** Region index → the visual surface it belongs to. */
295
+ type SurfaceOf = ReadonlyMap<number, number>;
294
296
  /**
295
- * Which regions are one surface to look at.
296
- *
297
- * The Engine splits a surface where that makes a better machining plan — a
298
- * floor cut in two so each half can be reached from a different direction, say.
299
- * Those splits are real and features depend on them, but they are not edges of
300
- * the part: a plane divided in two is still flat, and drawing the join or
301
- * shading across it makes a clean model look faceted and creased where nothing
302
- * creases.
303
- *
304
- * So this groups regions that continue each other, and the grouping is used for
305
- * the two things that are about how the part *looks* — its edges and its
306
- * shading. Nothing about picking, highlighting or features goes through here:
307
- * a split is still two regions to click on and still two regions a feature can
308
- * own, which is the whole reason the Engine made it.
309
- *
310
- * Two regions continue each other when they meet along an edge, are both
311
- * **planes**, and are flat to within a degree of one another — which for a
312
- * plane means they are the same plane.
313
- *
314
- * Planes only, and that is a limit of what the report says rather than caution.
315
- * A region carries an `idx`, a `shapeKind`, an area and a triangle range: there
316
- * is nothing in it that says which analytic surface a region was cut from. On a
317
- * flat face that does not matter, because two coplanar planes meeting along an
318
- * edge *are* one plane and no part has an edge there. On a curved one it
319
- * matters entirely: a fillet running tangentially into a shaft and a fillet
320
- * split down the middle look identical from the facets alone, and guessing
321
- * between them either rubs out a line the part has or leaves one it does not.
322
- * A line the part has is the worse of the two to lose, so curved boundaries are
323
- * all drawn.
324
- *
325
- * The exact version of this wants the Engine to say which surface a region came
326
- * from. Until it does, this is the half that can be proved.
297
+ * The visual surface of each post-split region.
298
+ *
299
+ * The kernel divides B-rep faces to make machining relationships unambiguous.
300
+ * Those divisions remain meaningful to feature extraction and picking, but
301
+ * they are not physical edges. `splitOrigin` is the kernel's exact lineage
302
+ * relation: equal values mean the regions were one face before that analysis
303
+ * split. It is therefore the single grouping used for rendering, rather than
304
+ * attempting to infer continuations from mesh normals or surface kinds.
327
305
  */
328
- /**
329
- * How far two planes may disagree across a shared edge and still be one plane.
330
- *
331
- * A degree, which is a rounding error rather than a judgement: a split is
332
- * exactly coplanar, and anything a part actually turns through is a chamfer at
333
- * fifteen degrees or more.
334
- */
335
- declare const CONTINUES_WITHIN: number;
336
- /** Region index → the surface it belongs to, by region `idx`. */
337
- type SurfaceOf = ReadonlyMap<number, number>;
338
- declare function visualSurfaces(geometry: BufferGeometry, regions: readonly PartModelRegion[]): SurfaceOf;
306
+ declare function visualSurfaces(regions: readonly PartModelRegion[]): SurfaceOf;
339
307
 
340
308
  /**
341
309
  * Mouse and trackpad presets.
@@ -634,4 +602,4 @@ declare function bestOwner(model: PartModel, owners: readonly FeatureTag[], cont
634
602
  */
635
603
  declare function cycleOwner(owners: readonly FeatureTag[], current: FeatureTag | null): FeatureTag | null;
636
604
 
637
- export { type ArrowPlacement, Axes, type AxesProps, CHAMFER, CONTINUES_WITHIN, CadCameraControls, type CadCameraControlsProps, type ControlScheme, type CubeZone, DirectionArrows, type DirectionArrowsProps, type DirectionGroup, ExtendedCameraControls, type ExtendedCameraControlsOptions, FEATURE_TYPE_RANKS, FeatureTag, FeatureType, Grid, type GridProps, type GridSpec, type NamedDirection, PartModel, PartModelFeature, PartModelRegion, PartReportFormatError, Projection, type RankingContext, RegionIndex, type SurfaceOf, TAP_SLOP, type TapPoint, type TapTracker, TriangleRange, UnsupportedKernelVersionError, VIEW_NAMES, VIEW_SIGNS, Vec3, ViewCube, type ViewCubeProps, type ViewKind, type ViewName, Viewer, ViewerCamera, type ViewerControls, type ViewerHandle, type ViewerProps, ViewerTheme, ViewerView, arrowPlacement, bestOwner, buildRegionIndex, cubeOutlineGeometry, cubeZones, cycleOwner, directionIndexOf, directionLabel, featureTypeRank, gridGeometry, gridSpec, groupByDirection, labelGeometry, labelTexture, movedFar, panelGeometry, rankOwners, regionEdgesGeometry, regionNormals, sameDirection, trackTaps, useContentBox, useTapGuard, useViewerControls, viewKind, viewUp, viewVector, visualSurfaces };
605
+ export { type ArrowPlacement, Axes, type AxesProps, CHAMFER, CadCameraControls, type CadCameraControlsProps, type ControlScheme, type CubeZone, DirectionArrows, type DirectionArrowsProps, type DirectionGroup, ExtendedCameraControls, type ExtendedCameraControlsOptions, FEATURE_TYPE_RANKS, FeatureTag, FeatureType, Grid, type GridProps, type GridSpec, type NamedDirection, PartModel, PartModelFeature, PartModelRegion, PartReportFormatError, Projection, type RankingContext, RegionIndex, type SurfaceOf, TAP_SLOP, type TapPoint, type TapTracker, TriangleRange, UnsupportedKernelVersionError, VIEW_NAMES, VIEW_SIGNS, Vec3, ViewCube, type ViewCubeProps, type ViewKind, type ViewName, Viewer, ViewerCamera, type ViewerControls, type ViewerHandle, type ViewerProps, ViewerTheme, ViewerView, arrowPlacement, bestOwner, buildRegionIndex, cubeOutlineGeometry, cubeZones, cycleOwner, directionIndexOf, directionLabel, featureTypeRank, gridGeometry, gridSpec, groupByDirection, labelGeometry, labelTexture, movedFar, panelGeometry, rankOwners, regionEdgesGeometry, regionNormals, sameDirection, trackTaps, useContentBox, useTapGuard, useViewerControls, viewKind, viewUp, viewVector, visualSurfaces };
package/dist/index.js CHANGED
@@ -2,7 +2,6 @@ import {
2
2
  CAD_CAMERA_UP,
3
3
  CANDIDATE_WEIGHT,
4
4
  CONE_AXIS,
5
- CONTINUES_WITHIN,
6
5
  CadCameraControls,
7
6
  DEFAULT_FIT_MARGIN,
8
7
  DEFAULT_THEME,
@@ -81,7 +80,7 @@ import {
81
80
  useViewerControls,
82
81
  viewDirection,
83
82
  visualSurfaces
84
- } from "./chunk-7NQBV7EQ.js";
83
+ } from "./chunk-RICG6UVH.js";
85
84
 
86
85
  // src/primitives.tsx
87
86
  import { GizmoHelper } from "@react-three/drei";
@@ -581,7 +580,6 @@ export {
581
580
  CAD_CAMERA_UP,
582
581
  CANDIDATE_WEIGHT,
583
582
  CHAMFER,
584
- CONTINUES_WITHIN,
585
583
  CadCameraControls,
586
584
  DEFAULT_FIT_MARGIN,
587
585
  DEFAULT_THEME,
@@ -5,7 +5,7 @@ import { Group, Mesh, LineSegments, Box3, Plane, BufferGeometry, Camera, Vector3
5
5
  * The normalized part — the only thing the renderer consumes.
6
6
  *
7
7
  * `engine/` produces this; nothing under `model/` or `render/` sees a
8
- * `PartReportResponse`. The renderer therefore survives an API change, and the
8
+ * `PartResponse`. The renderer therefore survives an API change, and the
9
9
  * viewer can be driven from a local file with no API at all, which is also how
10
10
  * it gets tested.
11
11
  *
@@ -84,6 +84,16 @@ interface RegionIndex {
84
84
  rangeForRegion(region: number): TriangleRange | null;
85
85
  }
86
86
  interface PartModelRegion {
87
+ /**
88
+ * Opaque, report-local identity of the B-rep face this region came from before
89
+ * analysis split it. Equal values are one visual surface; it is not an index
90
+ * or a kernel surface identifier.
91
+ */
92
+ readonly splitOrigin: number;
93
+ /**
94
+ * Post-split region identity. Feature ownership, picking, and triangle
95
+ * ranges deliberately continue to use this rather than `splitOrigin`.
96
+ */
87
97
  readonly idx: number;
88
98
  readonly shapeKind: ShapeKind;
89
99
  /**
@@ -246,7 +256,7 @@ interface PartObject {
246
256
  paintRegion(region: number, color: number, weight: number): void;
247
257
  /** What a region is painted with now. `null` for a region it does not have. */
248
258
  regionPaint(region: number): RegionPaint | null;
249
- /** Paints every region a feature owns. */
259
+ /** Paints every region the feature explicitly owns. */
250
260
  paintFeature(tag: FeatureTag, color: number, weight: number): void;
251
261
  clearPaint(): void;
252
262
  /** A feature's bounds in part space, for framing. `null` if it has none. */
@@ -265,8 +275,10 @@ interface RegionPaint {
265
275
  /** 0 for untouched, 1 for fully painted. */
266
276
  readonly weight: number;
267
277
  }
268
- /** The part of a `PartModel` the buffer builders need. */
269
- type RegionTable = Pick<PartModel, 'regions'>;
278
+ /** The part of a region table the buffer builders need. */
279
+ type RegionTable = {
280
+ readonly regions: readonly Pick<PartModel['regions'][number], 'idx' | 'triangles'>[];
281
+ };
270
282
  /**
271
283
  * Maps a region's `idx` to its column in the state texture.
272
284
  *
@@ -821,10 +833,9 @@ declare const EnginePart: ({ report, ...props }: EnginePartProps) => react.JSX.E
821
833
  * triangle its own normal, which is honest but leaves a bore looking like a
822
834
  * fifty-sided nut, because that is exactly what its triangles are.
823
835
  *
824
- * Neither is necessary here, because the report says which triangles belong to
825
- * one analytic surface. Averaging *within* a region and never *across* one
826
- * gives a bore that shades like a bore and an edge that stays an edge: the
827
- * distinction a mesh cannot express is one the region table can.
836
+ * The report says which post-split regions came from one original B-rep face.
837
+ * Averaging within that exact visual surface and never across one gives a bore
838
+ * that shades like a bore and an edge that stays an edge.
828
839
  *
829
840
  * Two vertices are the same point if their coordinates match exactly. That is
830
841
  * safe rather than optimistic — `toNonIndexed` copies each shared vertex from
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toolpath/viewer",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "React Three Fiber viewer for Toolpath Engine part reports",
5
5
  "license": "MIT",
6
6
  "repository": {