@toolpath/viewer 0.2.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/dist/index.js ADDED
@@ -0,0 +1,676 @@
1
+ import {
2
+ CAD_CAMERA_UP,
3
+ CANDIDATE_WEIGHT,
4
+ CONE_AXIS,
5
+ CONTINUES_WITHIN,
6
+ CadCameraControls,
7
+ DEFAULT_FIT_MARGIN,
8
+ DEFAULT_THEME,
9
+ DIRECTION_COLORS,
10
+ EXCLUDE_FROM_FRAME,
11
+ EnginePart,
12
+ ExtendedCameraControls,
13
+ FEATURE_TYPE_RANKS,
14
+ HANDLE_PIXELS,
15
+ HEAD,
16
+ HEAD_RADIUS,
17
+ HIGHLIGHT_COLORS,
18
+ HIGHLIGHT_WEIGHT,
19
+ HOVER_WEIGHT,
20
+ NO_MODIFIERS,
21
+ PERSPECTIVE_FOV,
22
+ PICKED_SURFACE_LABEL,
23
+ PartMesh,
24
+ PartReportFormatError,
25
+ REGION_ATTRIBUTE,
26
+ SECTION_RENDER_ORDER,
27
+ SHAFT_RADIUS,
28
+ SectionView,
29
+ TAP_SLOP,
30
+ UnsupportedKernelVersionError,
31
+ Viewer,
32
+ applyHighlightLayers,
33
+ applyProjection,
34
+ arrowPlacement,
35
+ aspectRatio,
36
+ bestOwner,
37
+ boundsFromBox,
38
+ buildPick,
39
+ buildRegionAttribute,
40
+ buildRegionIndex,
41
+ buildRegionTexels,
42
+ cadViewDirections,
43
+ contentBounds,
44
+ createPart,
45
+ currentViewDirection,
46
+ cycleOwner,
47
+ defaultBounds,
48
+ directionColor,
49
+ directionIndexOf,
50
+ directionLabel,
51
+ dragPlane,
52
+ featureTypeRank,
53
+ fitDistance,
54
+ focusForPick,
55
+ groupByDirection,
56
+ movedFar,
57
+ normalizePartReport,
58
+ orthographicHalfHeight,
59
+ perspectiveFitDistance,
60
+ pickedStartDepth,
61
+ rankOwners,
62
+ regionEdgesGeometry,
63
+ resolveSectionPlane,
64
+ resolveTheme,
65
+ sameDirection,
66
+ screenLength,
67
+ sectionBounds,
68
+ sectionConstant,
69
+ sectionDepth,
70
+ sectionDepthConstant,
71
+ sectionDepthRange,
72
+ sectionFromPick,
73
+ sectionOffset,
74
+ sectionPlane,
75
+ smoothRegionNormals,
76
+ startPosition,
77
+ themesEqual,
78
+ trackTaps,
79
+ useContentBox,
80
+ useTapGuard,
81
+ useViewerControls,
82
+ viewDirection,
83
+ visualSurfaces
84
+ } from "./chunk-7NQBV7EQ.js";
85
+
86
+ // src/primitives.tsx
87
+ import { GizmoHelper } from "@react-three/drei";
88
+ import { useThree } from "@react-three/fiber";
89
+ import { useEffect, useMemo, useRef, useState } from "react";
90
+
91
+ // src/render/grid.ts
92
+ import { BufferGeometry, Float32BufferAttribute, Vector3 } from "three";
93
+ var STEPS = [0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1e3];
94
+ var TARGET_CELLS = 10;
95
+ var OVERHANG = 1.6;
96
+ function gridSpec(box) {
97
+ const size = box.getSize(new Vector3());
98
+ const center = box.getCenter(new Vector3());
99
+ const largest = Math.max(size.x, size.y, 1e-6);
100
+ const ideal = largest / TARGET_CELLS;
101
+ const step = [...STEPS].reverse().find((candidate) => candidate <= ideal) ?? STEPS[0];
102
+ const extent = Math.ceil(largest * OVERHANG / 2 / step) * step;
103
+ return { step, extent, z: box.min.z, center };
104
+ }
105
+ function gridGeometry(spec) {
106
+ const { step, extent, z, center } = spec;
107
+ const positions = [];
108
+ for (let offset = -extent; offset <= extent + 1e-9; offset += step) {
109
+ positions.push(
110
+ center.x + offset,
111
+ center.y - extent,
112
+ z,
113
+ center.x + offset,
114
+ center.y + extent,
115
+ z,
116
+ center.x - extent,
117
+ center.y + offset,
118
+ z,
119
+ center.x + extent,
120
+ center.y + offset,
121
+ z
122
+ );
123
+ }
124
+ const geometry = new BufferGeometry();
125
+ geometry.setAttribute("position", new Float32BufferAttribute(positions, 3));
126
+ return geometry;
127
+ }
128
+
129
+ // src/render/view-cube.ts
130
+ import {
131
+ BufferGeometry as BufferGeometry2,
132
+ CanvasTexture,
133
+ Float32BufferAttribute as Float32BufferAttribute2,
134
+ SRGBColorSpace,
135
+ Vector3 as Vector32
136
+ } from "three";
137
+ var VIEW_NAMES = [
138
+ // The six faces.
139
+ "top",
140
+ "bottom",
141
+ "front",
142
+ "back",
143
+ "left",
144
+ "right",
145
+ // The twelve edges.
146
+ "top-front",
147
+ "top-back",
148
+ "top-left",
149
+ "top-right",
150
+ "bottom-front",
151
+ "bottom-back",
152
+ "bottom-left",
153
+ "bottom-right",
154
+ "front-left",
155
+ "front-right",
156
+ "back-left",
157
+ "back-right",
158
+ // The eight corners.
159
+ "top-front-left",
160
+ "top-front-right",
161
+ "top-back-left",
162
+ "top-back-right",
163
+ "bottom-front-left",
164
+ "bottom-front-right",
165
+ "bottom-back-left",
166
+ "bottom-back-right"
167
+ ];
168
+ var VIEW_SIGNS = {
169
+ top: [0, 0, 1],
170
+ bottom: [0, 0, -1],
171
+ front: [0, -1, 0],
172
+ back: [0, 1, 0],
173
+ left: [-1, 0, 0],
174
+ right: [1, 0, 0],
175
+ "top-front": [0, -1, 1],
176
+ "top-back": [0, 1, 1],
177
+ "top-left": [-1, 0, 1],
178
+ "top-right": [1, 0, 1],
179
+ "bottom-front": [0, -1, -1],
180
+ "bottom-back": [0, 1, -1],
181
+ "bottom-left": [-1, 0, -1],
182
+ "bottom-right": [1, 0, -1],
183
+ "front-left": [-1, -1, 0],
184
+ "front-right": [1, -1, 0],
185
+ "back-left": [-1, 1, 0],
186
+ "back-right": [1, 1, 0],
187
+ "top-front-left": [-1, -1, 1],
188
+ "top-front-right": [1, -1, 1],
189
+ "top-back-left": [-1, 1, 1],
190
+ "top-back-right": [1, 1, 1],
191
+ "bottom-front-left": [-1, -1, -1],
192
+ "bottom-front-right": [1, -1, -1],
193
+ "bottom-back-left": [-1, 1, -1],
194
+ "bottom-back-right": [1, 1, -1]
195
+ };
196
+ function viewKind(name) {
197
+ const off = VIEW_SIGNS[name].filter((sign) => sign !== 0).length;
198
+ if (off === 1) return "face";
199
+ if (off === 2) return "edge";
200
+ return "corner";
201
+ }
202
+ var DIRECTIONS = new Map(
203
+ VIEW_NAMES.map((name) => {
204
+ const [x, y, z] = VIEW_SIGNS[name];
205
+ const length = Math.hypot(x, y, z);
206
+ return [name, { x: x / length, y: y / length, z: z / length }];
207
+ })
208
+ );
209
+ function viewVector(name) {
210
+ return DIRECTIONS.get(name) ?? { x: 0, y: 0, z: 1 };
211
+ }
212
+ function viewUp(direction) {
213
+ if (direction.x === 0 && direction.y === 0) {
214
+ return { x: 0, y: direction.z > 0 ? 1 : -1, z: 0 };
215
+ }
216
+ return { x: 0, y: 0, z: 1 };
217
+ }
218
+ var CHAMFER = 0.5834;
219
+ function vec(v) {
220
+ return { x: v.x, y: v.y, z: v.z };
221
+ }
222
+ function cubeZones(chamfer = CHAMFER) {
223
+ const AXES = [0, 1, 2];
224
+ return VIEW_NAMES.map((name) => {
225
+ const kind = viewKind(name);
226
+ const direction = viewVector(name);
227
+ const [sx, sy, sz] = VIEW_SIGNS[name];
228
+ const sign = (axis) => VIEW_SIGNS[name][axis] ?? 0;
229
+ const normal = new Vector32(direction.x, direction.y, direction.z);
230
+ let polygon;
231
+ if (kind === "face") {
232
+ const up = viewUp(direction);
233
+ const upVector = new Vector32(up.x, up.y, up.z);
234
+ const right = normal.clone().negate().cross(upVector).normalize();
235
+ const square = [
236
+ [-1, -1],
237
+ [1, -1],
238
+ [1, 1],
239
+ [-1, 1]
240
+ ];
241
+ polygon = square.map(
242
+ ([u, v]) => normal.clone().addScaledVector(right, u * chamfer).addScaledVector(upVector, v * chamfer)
243
+ );
244
+ } else if (kind === "edge") {
245
+ const a = AXES.find((axis) => sign(axis) !== 0) ?? 0;
246
+ const b = [...AXES].reverse().find((axis) => sign(axis) !== 0) ?? 0;
247
+ const c = AXES.find((axis) => sign(axis) === 0) ?? 0;
248
+ const rectangle = [
249
+ [1, chamfer, -chamfer],
250
+ [1, chamfer, chamfer],
251
+ [chamfer, 1, chamfer],
252
+ [chamfer, 1, -chamfer]
253
+ ];
254
+ polygon = rectangle.map(
255
+ ([onA, onB, onC]) => new Vector32().setComponent(a, sign(a) * onA).setComponent(b, sign(b) * onB).setComponent(c, onC)
256
+ );
257
+ } else {
258
+ const triangle = [
259
+ [1, chamfer, chamfer],
260
+ [chamfer, 1, chamfer],
261
+ [chamfer, chamfer, 1]
262
+ ];
263
+ polygon = triangle.map(([onX, onY, onZ]) => new Vector32(sx * onX, sy * onY, sz * onZ));
264
+ }
265
+ const [first, second, third] = polygon;
266
+ if (first && second && third) {
267
+ const facing = new Vector32().subVectors(second, first).cross(new Vector32().subVectors(third, first));
268
+ if (facing.dot(normal) < 0) polygon.reverse();
269
+ }
270
+ return { name, kind, direction, polygon: polygon.map(vec) };
271
+ });
272
+ }
273
+ function panelGeometry(zone) {
274
+ const positions = [];
275
+ const normals = [];
276
+ const [first] = zone.polygon;
277
+ const geometry = new BufferGeometry2();
278
+ if (!first) return geometry;
279
+ for (let index = 1; index + 1 < zone.polygon.length; index += 1) {
280
+ const b = zone.polygon[index];
281
+ const c = zone.polygon[index + 1];
282
+ if (!b || !c) continue;
283
+ positions.push(first.x, first.y, first.z, b.x, b.y, b.z, c.x, c.y, c.z);
284
+ for (let vertex = 0; vertex < 3; vertex += 1) {
285
+ normals.push(zone.direction.x, zone.direction.y, zone.direction.z);
286
+ }
287
+ }
288
+ geometry.setAttribute("position", new Float32BufferAttribute2(positions, 3));
289
+ geometry.setAttribute("normal", new Float32BufferAttribute2(normals, 3));
290
+ return geometry;
291
+ }
292
+ function cubeOutlineGeometry(zones) {
293
+ const positions = [];
294
+ for (const zone of zones) {
295
+ for (let index = 0; index < zone.polygon.length; index += 1) {
296
+ const from = zone.polygon[index];
297
+ const to = zone.polygon[(index + 1) % zone.polygon.length];
298
+ if (!from || !to) continue;
299
+ positions.push(from.x, from.y, from.z, to.x, to.y, to.z);
300
+ }
301
+ }
302
+ const geometry = new BufferGeometry2();
303
+ geometry.setAttribute("position", new Float32BufferAttribute2(positions, 3));
304
+ return geometry;
305
+ }
306
+ function labelGeometry(zone) {
307
+ const positions = [];
308
+ const uvs = [];
309
+ const corners = [0, 1, 2, 0, 2, 3];
310
+ const corner = [
311
+ [0, 0],
312
+ [1, 0],
313
+ [1, 1],
314
+ [0, 1]
315
+ ];
316
+ for (const index of corners) {
317
+ const point = zone.polygon[index];
318
+ const uv = corner[index];
319
+ if (!point || !uv) continue;
320
+ positions.push(
321
+ point.x + zone.direction.x * 4e-3,
322
+ point.y + zone.direction.y * 4e-3,
323
+ point.z + zone.direction.z * 4e-3
324
+ );
325
+ uvs.push(uv[0] ?? 0, uv[1] ?? 0);
326
+ }
327
+ const geometry = new BufferGeometry2();
328
+ geometry.setAttribute("position", new Float32BufferAttribute2(positions, 3));
329
+ geometry.setAttribute("uv", new Float32BufferAttribute2(uvs, 2));
330
+ return geometry;
331
+ }
332
+ function labelTexture(label, color) {
333
+ const size = 128;
334
+ const canvas = document.createElement("canvas");
335
+ canvas.width = size;
336
+ canvas.height = size;
337
+ const context = canvas.getContext("2d");
338
+ if (context) {
339
+ const text = label.toUpperCase();
340
+ const hex = `#${color.toString(16).padStart(6, "0")}`;
341
+ context.font = `600 ${size / 4}px system-ui, sans-serif`;
342
+ const width = context.measureText("BOTTOM").width || 1;
343
+ const scale = Math.min(1, size * 0.78 / width);
344
+ context.font = `600 ${size / 4 * scale}px system-ui, sans-serif`;
345
+ context.textAlign = "center";
346
+ context.textBaseline = "middle";
347
+ context.fillStyle = hex;
348
+ context.fillText(text, size / 2, size / 2);
349
+ }
350
+ const texture = new CanvasTexture(canvas);
351
+ texture.colorSpace = SRGBColorSpace;
352
+ texture.needsUpdate = true;
353
+ return texture;
354
+ }
355
+
356
+ // src/primitives.tsx
357
+ import { jsx, jsxs } from "react/jsx-runtime";
358
+ var FURNITURE = { [EXCLUDE_FROM_FRAME]: true };
359
+ var Grid = ({ step, extent, color, opacity = 0.35 }) => {
360
+ const theme = resolveTheme();
361
+ const box = useContentBox();
362
+ const geometry = useMemo(() => {
363
+ const spec = gridSpec(box);
364
+ const cell = step ?? spec.step;
365
+ return gridGeometry({
366
+ ...spec,
367
+ step: cell,
368
+ // Snapped outwards to a whole number of cells, so the part sits inside
369
+ // the grid rather than ending part-way through a square.
370
+ extent: extent ?? Math.ceil(spec.extent / cell) * cell
371
+ });
372
+ }, [box, extent, step]);
373
+ useEffect(() => () => geometry.dispose(), [geometry]);
374
+ return /* @__PURE__ */ jsx("lineSegments", { geometry, renderOrder: -1, raycast: () => null, userData: FURNITURE, children: /* @__PURE__ */ jsx("lineBasicMaterial", { color: color ?? theme.cubeEdge, transparent: true, opacity }) });
375
+ };
376
+ var Axes = ({ size = 25 }) => /* @__PURE__ */ jsx("axesHelper", { args: [size], userData: FURNITURE, raycast: () => null });
377
+ var ViewCube = ({
378
+ alignment = "top-right",
379
+ margin = [80, 80],
380
+ theme,
381
+ onViewChange
382
+ }) => {
383
+ const resolved = useMemo(() => resolveTheme(theme), [theme]);
384
+ const zones = useMemo(() => cubeZones(), []);
385
+ return /* @__PURE__ */ jsx(GizmoHelper, { alignment, margin, children: /* @__PURE__ */ jsx(CubePanels, { zones, theme: resolved, onViewChange }) });
386
+ };
387
+ var CubePanels = ({
388
+ zones,
389
+ theme,
390
+ onViewChange
391
+ }) => {
392
+ const controls = useViewerControls();
393
+ const invalidate = useThree((state) => state.invalidate);
394
+ const groupRef = useRef(null);
395
+ const [hovered, setHovered] = useState(null);
396
+ const panels = useMemo(
397
+ () => zones.map((zone) => ({ zone, geometry: panelGeometry(zone) })),
398
+ [zones]
399
+ );
400
+ const outline = useMemo(() => cubeOutlineGeometry(zones), [zones]);
401
+ const labels = useMemo(
402
+ () => zones.filter((zone) => zone.kind === "face").map((zone) => ({
403
+ zone,
404
+ geometry: labelGeometry(zone),
405
+ texture: labelTexture(zone.name, theme.cubeLabel)
406
+ })),
407
+ [theme.cubeLabel, zones]
408
+ );
409
+ useEffect(
410
+ () => () => {
411
+ for (const panel of panels) panel.geometry.dispose();
412
+ for (const label of labels) {
413
+ label.geometry.dispose();
414
+ label.texture.dispose();
415
+ }
416
+ outline.dispose();
417
+ },
418
+ [labels, outline, panels]
419
+ );
420
+ const choose = (zone) => {
421
+ controls.setViewDirection(zone.direction);
422
+ onViewChange?.(zone.name);
423
+ };
424
+ const hover = (name) => {
425
+ setHovered(name);
426
+ invalidate();
427
+ };
428
+ return /* @__PURE__ */ jsxs("group", { ref: groupRef, scale: 38, children: [
429
+ panels.map(({ zone, geometry }) => /* @__PURE__ */ jsx(
430
+ "mesh",
431
+ {
432
+ geometry,
433
+ onPointerOver: (event) => {
434
+ event.stopPropagation();
435
+ hover(zone.name);
436
+ },
437
+ onPointerOut: () => hover(null),
438
+ onClick: (event) => {
439
+ event.stopPropagation();
440
+ choose(zone);
441
+ },
442
+ children: /* @__PURE__ */ jsx(
443
+ "meshLambertMaterial",
444
+ {
445
+ color: hovered === zone.name ? theme.hover : theme.cube,
446
+ polygonOffset: true,
447
+ polygonOffsetFactor: 1,
448
+ polygonOffsetUnits: 1
449
+ }
450
+ )
451
+ },
452
+ zone.name
453
+ )),
454
+ labels.map(({ zone, geometry, texture }) => /* @__PURE__ */ jsx("mesh", { geometry, raycast: () => null, children: /* @__PURE__ */ jsx("meshBasicMaterial", { map: texture, transparent: true }) }, `${zone.name}-label`)),
455
+ /* @__PURE__ */ jsx("lineSegments", { geometry: outline, raycast: () => null, children: /* @__PURE__ */ jsx("lineBasicMaterial", { color: theme.cubeEdge }) }),
456
+ /* @__PURE__ */ jsx("ambientLight", { intensity: 1.4 }),
457
+ /* @__PURE__ */ jsx("directionalLight", { position: [viewVector("top-front-right").x, 1, 2], intensity: 1.1 })
458
+ ] });
459
+ };
460
+
461
+ // src/direction-arrows.tsx
462
+ import { useThree as useThree2 } from "@react-three/fiber";
463
+ import { useMemo as useMemo2 } from "react";
464
+ import { Quaternion, Vector3 as Vector33 } from "three";
465
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
466
+ var FURNITURE2 = { [EXCLUDE_FROM_FRAME]: true };
467
+ var DirectionArrows = ({
468
+ directions,
469
+ activeDirection = null,
470
+ shownDirection,
471
+ previewDirection = null,
472
+ namedDirections = [],
473
+ onPickDirection,
474
+ theme,
475
+ visible = true
476
+ }) => {
477
+ const box = useContentBox();
478
+ const resolved = useMemo2(() => resolveTheme(theme), [theme]);
479
+ const shown = shownDirection === void 0 ? activeDirection : shownDirection;
480
+ if (!visible) return null;
481
+ return (
482
+ // Excluded from framing: the arrows are deliberately placed outside the
483
+ // part, so a Fit that measured them would frame the arrows and leave the
484
+ // part small in the middle of them.
485
+ /* @__PURE__ */ jsxs2("group", { userData: FURNITURE2, children: [
486
+ directions.map((direction, index) => {
487
+ if (shown !== null && shown !== index) return null;
488
+ return /* @__PURE__ */ jsx2(
489
+ Arrow,
490
+ {
491
+ direction,
492
+ box,
493
+ color: directionColor(index),
494
+ opacity: 0.9,
495
+ onPick: onPickDirection ? () => onPickDirection(index) : void 0
496
+ },
497
+ `candidate-${index}`
498
+ );
499
+ }),
500
+ namedDirections.map((named) => /* @__PURE__ */ jsx2(
501
+ Arrow,
502
+ {
503
+ direction: named.direction,
504
+ box,
505
+ color: named.color,
506
+ opacity: 0.9,
507
+ onPick: onPickDirection ? () => onPickDirection(named.index) : void 0
508
+ },
509
+ `named-${named.index}`
510
+ )),
511
+ previewDirection ? /* @__PURE__ */ jsx2(Arrow, { direction: previewDirection, box, color: resolved.hover, opacity: 0.95, onTop: true }) : null
512
+ ] })
513
+ );
514
+ };
515
+ var Arrow = ({ direction, box, color, opacity, onPick, onTop = false }) => {
516
+ const invalidate = useThree2((state) => state.invalidate);
517
+ const { tip, length, quaternion } = useMemo2(() => {
518
+ const placement = arrowPlacement(direction, box);
519
+ const aim = new Quaternion().setFromUnitVectors(
520
+ CONE_AXIS,
521
+ new Vector33(direction.x, direction.y, direction.z).normalize()
522
+ );
523
+ return { tip: placement.tip, length: placement.length, quaternion: aim };
524
+ }, [box, direction]);
525
+ const head = length * HEAD;
526
+ const shaft = length * (1 - HEAD);
527
+ const press = onPick ? {
528
+ onClick: (event) => {
529
+ event.stopPropagation();
530
+ onPick();
531
+ invalidate();
532
+ }
533
+ } : {};
534
+ return /* @__PURE__ */ jsxs2("group", { position: tip, quaternion, renderOrder: onTop ? 10 : 3, children: [
535
+ /* @__PURE__ */ jsxs2("mesh", { position: [0, head * 0.5, 0], rotation: [Math.PI, 0, 0], ...press, children: [
536
+ /* @__PURE__ */ jsx2("coneGeometry", { args: [length * HEAD_RADIUS, head, 20] }),
537
+ /* @__PURE__ */ jsx2("meshBasicMaterial", { color, transparent: true, opacity, depthTest: !onTop })
538
+ ] }),
539
+ /* @__PURE__ */ jsxs2("mesh", { position: [0, head + shaft * 0.5, 0], ...press, children: [
540
+ /* @__PURE__ */ jsx2("cylinderGeometry", { args: [length * SHAFT_RADIUS, length * SHAFT_RADIUS, shaft, 12] }),
541
+ /* @__PURE__ */ jsx2("meshBasicMaterial", { color, transparent: true, opacity, depthTest: !onTop })
542
+ ] })
543
+ ] });
544
+ };
545
+
546
+ // src/model/normals.ts
547
+ function regionNormals(geometry, regions) {
548
+ const position = geometry.getAttribute("position");
549
+ const index = geometry.getIndex();
550
+ const normals = /* @__PURE__ */ new Map();
551
+ if (!position) return normals;
552
+ const vertexAt = (at) => {
553
+ const vertex = index ? index.getX(at) : at;
554
+ return [position.getX(vertex), position.getY(vertex), position.getZ(vertex)];
555
+ };
556
+ for (const region of regions) {
557
+ let x = 0;
558
+ let y = 0;
559
+ let z = 0;
560
+ for (let triangle = region.triangles.start; triangle < region.triangles.end; triangle++) {
561
+ const [ax, ay, az] = vertexAt(triangle * 3);
562
+ const [bx, by, bz] = vertexAt(triangle * 3 + 1);
563
+ const [cx, cy, cz] = vertexAt(triangle * 3 + 2);
564
+ const ux = bx - ax;
565
+ const uy = by - ay;
566
+ const uz = bz - az;
567
+ const vx = cx - ax;
568
+ const vy = cy - ay;
569
+ const vz = cz - az;
570
+ x += uy * vz - uz * vy;
571
+ y += uz * vx - ux * vz;
572
+ z += ux * vy - uy * vx;
573
+ }
574
+ const length = Math.sqrt(x * x + y * y + z * z);
575
+ if (length > 1e-9) normals.set(region.idx, { x: x / length, y: y / length, z: z / length });
576
+ }
577
+ return normals;
578
+ }
579
+ export {
580
+ Axes,
581
+ CAD_CAMERA_UP,
582
+ CANDIDATE_WEIGHT,
583
+ CHAMFER,
584
+ CONTINUES_WITHIN,
585
+ CadCameraControls,
586
+ DEFAULT_FIT_MARGIN,
587
+ DEFAULT_THEME,
588
+ DIRECTION_COLORS,
589
+ DirectionArrows,
590
+ EXCLUDE_FROM_FRAME,
591
+ EnginePart,
592
+ ExtendedCameraControls,
593
+ FEATURE_TYPE_RANKS,
594
+ Grid,
595
+ HANDLE_PIXELS,
596
+ HIGHLIGHT_COLORS,
597
+ HIGHLIGHT_WEIGHT,
598
+ HOVER_WEIGHT,
599
+ NO_MODIFIERS,
600
+ PERSPECTIVE_FOV,
601
+ PICKED_SURFACE_LABEL,
602
+ PartMesh,
603
+ PartReportFormatError,
604
+ REGION_ATTRIBUTE,
605
+ SECTION_RENDER_ORDER,
606
+ SectionView,
607
+ TAP_SLOP,
608
+ UnsupportedKernelVersionError,
609
+ VIEW_NAMES,
610
+ VIEW_SIGNS,
611
+ ViewCube,
612
+ Viewer,
613
+ applyHighlightLayers,
614
+ applyProjection,
615
+ arrowPlacement,
616
+ aspectRatio,
617
+ bestOwner,
618
+ boundsFromBox,
619
+ buildPick,
620
+ buildRegionAttribute,
621
+ buildRegionIndex,
622
+ buildRegionTexels,
623
+ cadViewDirections,
624
+ contentBounds,
625
+ createPart,
626
+ cubeOutlineGeometry,
627
+ cubeZones,
628
+ currentViewDirection,
629
+ cycleOwner,
630
+ defaultBounds,
631
+ directionColor,
632
+ directionIndexOf,
633
+ directionLabel,
634
+ dragPlane,
635
+ featureTypeRank,
636
+ fitDistance,
637
+ focusForPick,
638
+ gridGeometry,
639
+ gridSpec,
640
+ groupByDirection,
641
+ labelGeometry,
642
+ labelTexture,
643
+ movedFar,
644
+ normalizePartReport,
645
+ orthographicHalfHeight,
646
+ panelGeometry,
647
+ perspectiveFitDistance,
648
+ pickedStartDepth,
649
+ rankOwners,
650
+ regionEdgesGeometry,
651
+ regionNormals,
652
+ resolveSectionPlane,
653
+ resolveTheme,
654
+ sameDirection,
655
+ screenLength,
656
+ sectionBounds,
657
+ sectionConstant,
658
+ sectionDepth,
659
+ sectionDepthConstant,
660
+ sectionDepthRange,
661
+ sectionFromPick,
662
+ sectionOffset,
663
+ sectionPlane,
664
+ smoothRegionNormals,
665
+ startPosition,
666
+ themesEqual,
667
+ trackTaps,
668
+ useContentBox,
669
+ useTapGuard,
670
+ useViewerControls,
671
+ viewDirection,
672
+ viewKind,
673
+ viewUp,
674
+ viewVector,
675
+ visualSurfaces
676
+ };