beckhoff-xts-viewer-3d 5.2.3 → 6.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/LICENSE +21 -21
- package/README.md +241 -315
- package/dist/index.cjs +7 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2171 -1311
- package/dist/index.d.ts +2171 -1311
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/docs/screenshots/01-packaging-line.webp +0 -0
- package/docs/screenshots/02-standard-at.webp +0 -0
- package/docs/screenshots/03-eco-at2200.webp +0 -0
- package/docs/screenshots/04-nct-tools.webp +0 -0
- package/docs/screenshots/05-hygienic-ath.webp +0 -0
- package/docs/screenshots/06-hepco-gfx.webp +0 -0
- package/docs/screenshots/07-materials-closeup.webp +0 -0
- package/docs/screenshots/08-multi-track.webp +0 -0
- package/docs/screenshots/09-stations-areas.webp +0 -0
- package/docs/screenshots/10-dimensions.webp +0 -0
- package/docs/screenshots/11-stator-heatmap.webp +0 -0
- package/docs/screenshots/12-collision.webp +0 -0
- package/docs/screenshots/13-drive-status.webp +0 -0
- package/docs/screenshots/14-feed-segments.webp +0 -0
- package/docs/screenshots/15-measurements.webp +0 -0
- package/docs/screenshots/16-selection.webp +0 -0
- package/docs/screenshots/17-plan-view.webp +0 -0
- package/docs/screenshots/18-shadows.webp +0 -0
- package/docs/screenshots/19-perf-stress.webp +0 -0
- package/docs/screenshots/README.md +123 -123
- package/package.json +203 -194
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { Vector3, ColorRepresentation, Matrix4, Shape, Curve, CurvePath, WebGLRenderer, Object3D } from 'three';
|
|
1
2
|
import * as React from 'react';
|
|
2
3
|
import React__default from 'react';
|
|
3
|
-
import { Vector3, ColorRepresentation, Matrix4, Shape, Curve, CurvePath, WebGLRenderer, Object3D } from 'three';
|
|
4
4
|
import { GLTF } from 'three-stdlib';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -12,7 +12,7 @@ import { GLTF } from 'three-stdlib';
|
|
|
12
12
|
* Run `npm run sync-version` (or any script that depends on it) to refresh
|
|
13
13
|
* after bumping the package version.
|
|
14
14
|
*/
|
|
15
|
-
declare const VERSION: "
|
|
15
|
+
declare const VERSION: "6.0.0";
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Default CDN URL for the GLB asset bundle (`beckhoff-xts-viewer-3d-assets`).
|
|
@@ -32,7 +32,18 @@ declare const VERSION: "5.2.3";
|
|
|
32
32
|
* build time so every install streams from a known-compatible GLB
|
|
33
33
|
* release.
|
|
34
34
|
*/
|
|
35
|
-
declare const JSDELIVR_ASSETS_BASE_URL: "https://cdn.jsdelivr.net/npm/beckhoff-xts-viewer-3d-assets@
|
|
35
|
+
declare const JSDELIVR_ASSETS_BASE_URL: "https://cdn.jsdelivr.net/npm/beckhoff-xts-viewer-3d-assets@3.0.0/models";
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Scalar tuples shared by every other type module.
|
|
39
|
+
*
|
|
40
|
+
* The leaf of the type graph: `Vec3` is a position or an Euler triple in
|
|
41
|
+
* millimetres / degrees, `Vec2` a viewport or margin pair in pixels. Kept
|
|
42
|
+
* apart from the layout types so `display`, `annotations` and `config` can
|
|
43
|
+
* all reach them without importing each other.
|
|
44
|
+
*/
|
|
45
|
+
type Vec3 = [number, number, number];
|
|
46
|
+
type Vec2 = [number, number];
|
|
36
47
|
|
|
37
48
|
/**
|
|
38
49
|
* Catalogue taxonomy — the string unions naming every module, mover, tool and
|
|
@@ -44,7 +55,7 @@ declare const JSDELIVR_ASSETS_BASE_URL: "https://cdn.jsdelivr.net/npm/beckhoff-x
|
|
|
44
55
|
* public type surface. Nothing here imports anything.
|
|
45
56
|
*/
|
|
46
57
|
/** Beckhoff XTS module types supported by the 3D viewer. */
|
|
47
|
-
type ModuleType3D = 'AT2000_0250' | 'AT2001_0250' | 'AT2002_0250' | 'AT2002_0249' | '
|
|
58
|
+
type ModuleType3D = 'AT2000_0250' | 'AT2001_0250' | 'AT2002_0250' | 'AT2002_0249' | 'AT2000_0233' | 'AT2000_0249' | 'AT2100_0250' | 'AT2102_0250' | 'AT2020_0250' | 'AT2021_0250' | 'AT2025_0250' | 'AT2026_0250' | 'AT2040_0250' | 'AT2041_0250' | 'AT2050_0500' | 'AT2050_0501' | 'AT2050_0500_180' | 'AT2200_0500' | 'AT2202_0500' | 'ATH2000_0250' | 'ATH2001_0250' | 'ATH2002_0250' | 'ATH2020_0250' | 'ATH2040_0250' | 'ATH2041_0250' | 'ATH2042_0250' | 'ATH2050_0500' | 'ATH2050_0501' | 'ATH2050_0500_180';
|
|
48
59
|
/**
|
|
49
60
|
* Rail system beneath the module. Only affects the visual mesh
|
|
50
61
|
* (Beckhoff aluminium rail vs. Hepco GFX), not the path math.
|
|
@@ -65,7 +76,20 @@ type RailSystem = 'Beckhoff' | 'HepcoGfx';
|
|
|
65
76
|
* The `_S25` carrier ships without a magnet plate; the `_C25/_C34/_M34/_M40`
|
|
66
77
|
* variants include the Beckhoff magnet plate set (`7P`/`10P` = 7/10-pole).
|
|
67
78
|
*/
|
|
68
|
-
type MoverType3D = '
|
|
79
|
+
type MoverType3D = 'AT9014_0055' | 'AT9014_0070' | 'ATH9011_0075' | 'Hepco_GFX2_1TC_S25' | 'Hepco_GFX2_1TC_C25' | 'Hepco_GFX2_1TC_C34' | 'Hepco_GFX2_1TC_M34' | 'Hepco_GFX2_FCC_C25' | 'Hepco_GFX2_FCC_M34' | 'Hepco_GFX2_FCC_M34_7P' | 'Hepco_GFX2_FCC_M34_10P' | 'Hepco_GFX2_FCC_M40_10P' | 'Custom';
|
|
80
|
+
/**
|
|
81
|
+
* Beckhoff magnet plate sets — the part carrying the mover's magnets and,
|
|
82
|
+
* on the AT9001 family, the encoder flag ("Geberfahne").
|
|
83
|
+
*
|
|
84
|
+
* The digit after the dash is the pole count in base 16 (`0450` → 4,
|
|
85
|
+
* `0550` → 5, `0775` → 7, `0AA0` → 10), verified by counting magnets in each
|
|
86
|
+
* converted mesh rather than read off the part number. `ATH9001` is the
|
|
87
|
+
* hygienic family, whose rows are encapsulated in sealed stainless.
|
|
88
|
+
*
|
|
89
|
+
* Every set is symmetric about its own travel axis, so its GLB origin already
|
|
90
|
+
* sits on the magnet-plate centre that the mover frame is defined around.
|
|
91
|
+
*/
|
|
92
|
+
type MagnetPlateType3D = 'AT9001_0450' | 'AT9001_0550' | 'AT9001_0775' | 'AT9001_0AA0' | 'ATH9001_0550' | 'ATH9001_0550_0001' | 'ATH9001_0AA0';
|
|
69
93
|
/** Predefined mover-tool / carrier-plate types. */
|
|
70
94
|
type MoverToolType3D = 'AT8200_1000_0100' | 'AT8200_2000_0100' | 'Custom';
|
|
71
95
|
/**
|
|
@@ -73,28 +97,23 @@ type MoverToolType3D = 'AT8200_1000_0100' | 'AT8200_2000_0100' | 'Custom';
|
|
|
73
97
|
* when `RailSystem === 'Beckhoff'` to visualise the outer guide
|
|
74
98
|
* profile that the mover wheels engage.
|
|
75
99
|
*
|
|
76
|
-
* Curve sign drives the selection
|
|
100
|
+
* Curve sign drives the selection, and on curves so does the mover — see
|
|
101
|
+
* `resolveGuidingRailType`:
|
|
77
102
|
* - Straights: AT9000_0249 / AT9000_0250 / AT9000_0500
|
|
78
103
|
* - +22.5° curve: AT9020_1250
|
|
79
104
|
* - −22.5° curve: AT9025_1466
|
|
80
105
|
* - 45° curve: AT9040_0750
|
|
81
106
|
* - 180° clothoid (entire AT2050 500 mm path): AT9050_0500
|
|
82
107
|
*
|
|
108
|
+
* Beckhoff builds the curve rails per mover: the unsuffixed types above are
|
|
109
|
+
* the `-0055` order variants, made for the AT9014-0055, and the `_0170` ones
|
|
110
|
+
* are the `-0170` variants for the wider AT9014-0070. The straights carry no
|
|
111
|
+
* such variant — one AT9000 profile serves every mover.
|
|
112
|
+
*
|
|
83
113
|
* Hygienic ATH modules ship with their guide profile baked into the module
|
|
84
114
|
* GLB and therefore do NOT receive a separate guiding-rail mesh.
|
|
85
115
|
*/
|
|
86
|
-
type RailType3D = 'AT9000_0249' | 'AT9000_0250' | 'AT9000_0500' | 'AT9020_1250' | 'AT9025_1466' | 'AT9040_0750' | 'AT9050_0500';
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Scalar tuples shared by every other type module.
|
|
90
|
-
*
|
|
91
|
-
* The leaf of the type graph: `Vec3` is a position or an Euler triple in
|
|
92
|
-
* millimetres / degrees, `Vec2` a viewport or margin pair in pixels. Kept
|
|
93
|
-
* apart from the layout types so `display`, `annotations` and `config` can
|
|
94
|
-
* all reach them without importing each other.
|
|
95
|
-
*/
|
|
96
|
-
type Vec3 = [number, number, number];
|
|
97
|
-
type Vec2 = [number, number];
|
|
116
|
+
type RailType3D = 'AT9000_0249' | 'AT9000_0250' | 'AT9000_0500' | 'AT9020_1250' | 'AT9020_1250_0170' | 'AT9025_1466' | 'AT9025_1466_0170' | 'AT9040_0750' | 'AT9050_0500' | 'AT9050_0500_0170';
|
|
98
117
|
|
|
99
118
|
/**
|
|
100
119
|
* Display options — how a layout is drawn, as opposed to what it is.
|
|
@@ -128,10 +147,10 @@ interface DisplayOptions {
|
|
|
128
147
|
* `'viewer'` (no transform — current behaviour).
|
|
129
148
|
*/
|
|
130
149
|
coordinateSystem?: CoordinateSystem;
|
|
131
|
-
|
|
150
|
+
showStations?: boolean;
|
|
132
151
|
/** Render configured areas. Default: true (when `areas[]` is set). */
|
|
133
|
-
|
|
134
|
-
|
|
152
|
+
showAreas?: boolean;
|
|
153
|
+
showDimensions?: boolean;
|
|
135
154
|
showDriveErrors?: boolean;
|
|
136
155
|
showDriveWarnings?: boolean;
|
|
137
156
|
/**
|
|
@@ -181,10 +200,6 @@ interface DisplayOptions {
|
|
|
181
200
|
* boundary. Off by default.
|
|
182
201
|
*/
|
|
183
202
|
showModuleCornerMarkers?: boolean;
|
|
184
|
-
/** Diameter (mm) of the corner-marker spheres. Default: 10. */
|
|
185
|
-
moduleCornerMarkerSizeMm?: number;
|
|
186
|
-
/** Color of the corner markers. Default: '#FF2030' (red). */
|
|
187
|
-
moduleCornerMarkerColor?: string;
|
|
188
203
|
/**
|
|
189
204
|
* Render a red dot + vertical centre-line at each mover's anchor point —
|
|
190
205
|
* i.e. the track path point the mover is commanded to (its expected
|
|
@@ -196,10 +211,6 @@ interface DisplayOptions {
|
|
|
196
211
|
* default.
|
|
197
212
|
*/
|
|
198
213
|
showMoverAnchorMarker?: boolean;
|
|
199
|
-
/** Diameter (mm) of the anchor-marker dot. Default: 10. */
|
|
200
|
-
moverAnchorMarkerSizeMm?: number;
|
|
201
|
-
/** Color of the anchor marker. Default: '#FF2030' (red). */
|
|
202
|
-
moverAnchorMarkerColor?: string;
|
|
203
214
|
/**
|
|
204
215
|
* Print each module's type designation onto its type-plate panel.
|
|
205
216
|
*
|
|
@@ -212,97 +223,114 @@ interface DisplayOptions {
|
|
|
212
223
|
* Default: `true`.
|
|
213
224
|
*/
|
|
214
225
|
moduleTypePlates?: boolean;
|
|
226
|
+
/** Size and colour of the module corner markers. */
|
|
227
|
+
moduleCornerMarkerOptions?: CalibrationMarkerOptions;
|
|
228
|
+
/** Size and colour of the mover anchor marker. */
|
|
229
|
+
moverAnchorMarkerOptions?: CalibrationMarkerOptions;
|
|
230
|
+
/** Lighting, environment map and tone mapping. */
|
|
231
|
+
lighting?: LightingOptions;
|
|
232
|
+
/** Screen-space effects (SSAO, bloom). */
|
|
233
|
+
postProcessing?: PostProcessingOptions;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Size and colour of a calibration marker.
|
|
237
|
+
*
|
|
238
|
+
* The corner markers and the anchor marker each used to carry a loose
|
|
239
|
+
* `...SizeMm` / `...Color` pair on `DisplayOptions`, which is four flat
|
|
240
|
+
* fields describing two objects.
|
|
241
|
+
*/
|
|
242
|
+
interface CalibrationMarkerOptions {
|
|
243
|
+
/** Diameter in mm. Default: 10. */
|
|
244
|
+
sizeMm?: number;
|
|
245
|
+
/** CSS colour. Default: `'#FF2030'` (red). */
|
|
246
|
+
color?: string;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Lighting and the renderer's colour pipeline.
|
|
250
|
+
*
|
|
251
|
+
* Grouped rather than flat because these travel together: the whole block is
|
|
252
|
+
* handed to `<SceneLighting>` and the renderer, and none of it describes a
|
|
253
|
+
* layout. `DisplayOptions` already models per-overlay styling this way
|
|
254
|
+
* (`stationOptions`, `areaOptions`, `dimensionOptions`), so eleven loose
|
|
255
|
+
* scalars sitting beside them was the odd shape out.
|
|
256
|
+
*/
|
|
257
|
+
interface LightingOptions {
|
|
215
258
|
/**
|
|
216
259
|
* Render shadows: enables the WebGL shadow map, makes one directional
|
|
217
|
-
* light cast shadows, and renders a transparent shadow-catcher plane
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
260
|
+
* light cast shadows, and renders a transparent shadow-catcher plane so
|
|
261
|
+
* shadows from modules / movers / tools become visible. Default: false
|
|
262
|
+
* (no shadow-map cost when disabled). All loaded GLB meshes have their
|
|
263
|
+
* `castShadow` + `receiveShadow` flags set unconditionally — turning
|
|
264
|
+
* shadows on at the canvas level is the only thing that actually triggers
|
|
265
|
+
* the per-frame shadow pass.
|
|
223
266
|
*/
|
|
224
267
|
shadows?: boolean;
|
|
225
268
|
/**
|
|
226
|
-
* Image-based lighting via a procedural indoor environment map
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
269
|
+
* Image-based lighting via a procedural indoor environment map (three's
|
|
270
|
+
* `RoomEnvironment` processed through `PMREMGenerator` — no HTTP fetch, no
|
|
271
|
+
* asset shipped). Drives PBR reflections on metallic / roughness materials
|
|
272
|
+
* so aluminium rails and brushed mover plates read as actual metal instead
|
|
273
|
+
* of flat shaded surfaces.
|
|
231
274
|
*
|
|
232
|
-
* Cost: one-time PMREM build on mount (
|
|
275
|
+
* Cost: one-time PMREM build on mount (~1.5 MB GPU), zero per-frame
|
|
233
276
|
* overhead beyond the regular PBR shader's environment lookup.
|
|
234
277
|
*
|
|
235
|
-
* Default: `true` — looks noticeably better at no measurable cost.
|
|
236
|
-
*
|
|
278
|
+
* Default: `true` — looks noticeably better at no measurable cost. Set
|
|
279
|
+
* `false` to fall back to direct-lighting only.
|
|
237
280
|
*/
|
|
238
|
-
|
|
281
|
+
environment?: boolean;
|
|
239
282
|
/**
|
|
240
|
-
* Strength of the environment-map contribution. Lower values keep
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
* Default: `0.4` — reflections visible without overwhelming the
|
|
245
|
-
* directional + ambient lights. Range typically `0.0` … `1.5`.
|
|
283
|
+
* Strength of the environment-map contribution. Lower values keep the look
|
|
284
|
+
* closer to the direct-lighting-only baseline; higher values emphasise
|
|
285
|
+
* reflections. Default: `0.4`. Range typically `0.0` ... `1.5`.
|
|
246
286
|
*/
|
|
247
287
|
environmentIntensity?: number;
|
|
248
288
|
/**
|
|
249
|
-
* Renderer tone-mapping operator. ACES filmic compresses bright
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
* Default: `'aces'`.
|
|
289
|
+
* Renderer tone-mapping operator. ACES filmic compresses bright highlights
|
|
290
|
+
* into a perceptually pleasant rolloff and is the closest match to film /
|
|
291
|
+
* professional CAD viewers; `'linear'` reproduces the pre-tone-mapping
|
|
292
|
+
* behaviour for callers who relied on it. Default: `'aces'`.
|
|
255
293
|
*/
|
|
256
294
|
toneMapping?: 'aces' | 'linear' | 'reinhard' | 'cineon' | 'agx' | 'none';
|
|
257
295
|
/**
|
|
258
296
|
* Tone-mapping exposure multiplier (analogous to a camera's exposure
|
|
259
|
-
* stop). 1.0 = neutral.
|
|
260
|
-
* darkens it.
|
|
261
|
-
*
|
|
297
|
+
* stop). 1.0 = neutral. Above 1 brightens, below 1 darkens.
|
|
262
298
|
* Default: `1.0`.
|
|
263
299
|
*/
|
|
264
300
|
toneMappingExposure?: number;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Screen-space post-processing. Each effect is an extra half-res pass per
|
|
304
|
+
* frame, so both are opt-in.
|
|
305
|
+
*
|
|
306
|
+
* The whole block is forwarded to `<ScenePostProcessing>` unchanged — as
|
|
307
|
+
* seven flat fields it had to be re-threaded one by one, and every default
|
|
308
|
+
* restated at the far end.
|
|
309
|
+
*/
|
|
310
|
+
interface PostProcessingOptions {
|
|
265
311
|
/**
|
|
266
|
-
* Screen-space ambient occlusion (GTAO). Adds contact shadows in
|
|
267
|
-
*
|
|
268
|
-
* depth. Cost: one half-res pass per frame.
|
|
269
|
-
*
|
|
312
|
+
* Screen-space ambient occlusion (GTAO). Adds contact shadows in crevices
|
|
313
|
+
* and between adjacent surfaces for significantly more visual depth.
|
|
270
314
|
* Default: `false`.
|
|
271
315
|
*/
|
|
272
316
|
ssao?: boolean;
|
|
273
|
-
/**
|
|
274
|
-
* SSAO blend intensity. Higher values darken crevices more.
|
|
275
|
-
* Range: 0.0–4.0. Default: `1.0`.
|
|
276
|
-
*/
|
|
317
|
+
/** SSAO blend intensity; higher darkens crevices more. 0.0-4.0. Default `1.0`. */
|
|
277
318
|
ssaoIntensity?: number;
|
|
278
|
-
/**
|
|
279
|
-
* SSAO sampling radius in world units (mm). Larger values spread the
|
|
280
|
-
* occlusion over a wider area. Default: `20`.
|
|
281
|
-
*/
|
|
319
|
+
/** SSAO sampling radius in world units (mm). Default `20`. */
|
|
282
320
|
ssaoRadius?: number;
|
|
283
321
|
/**
|
|
284
322
|
* Bloom (glow) for bright metallic surfaces. Adds a soft halo around
|
|
285
|
-
* polished rails and highlights
|
|
286
|
-
* reflective appearance. Cost: one half-res pass per frame.
|
|
287
|
-
*
|
|
288
|
-
* Default: `false`.
|
|
323
|
+
* polished rails and highlights. Default: `false`.
|
|
289
324
|
*/
|
|
290
325
|
bloom?: boolean;
|
|
291
|
-
/**
|
|
292
|
-
* Bloom intensity (strength). Higher values produce brighter glow.
|
|
293
|
-
* Range: 0.0–3.0. Default: `0.4`.
|
|
294
|
-
*/
|
|
326
|
+
/** Bloom strength; higher produces brighter glow. 0.0-3.0. Default `0.4`. */
|
|
295
327
|
bloomIntensity?: number;
|
|
296
328
|
/**
|
|
297
|
-
* Bloom luminance threshold
|
|
298
|
-
*
|
|
299
|
-
* Range: 0.0–1.0. Default: `0.85`.
|
|
329
|
+
* Bloom luminance threshold - only pixels brighter than this contribute.
|
|
330
|
+
* Lower values bloom more of the scene. 0.0-1.0. Default `0.85`.
|
|
300
331
|
*/
|
|
301
332
|
bloomThreshold?: number;
|
|
302
|
-
/**
|
|
303
|
-
* Bloom blur radius. Controls how far the glow spreads.
|
|
304
|
-
* Range: 0.0–1.0. Default: `0.4`.
|
|
305
|
-
*/
|
|
333
|
+
/** Bloom blur radius; how far the glow spreads. 0.0-1.0. Default `0.4`. */
|
|
306
334
|
bloomRadius?: number;
|
|
307
335
|
}
|
|
308
336
|
/**
|
|
@@ -330,8 +358,13 @@ interface MoverIdLabelOptions {
|
|
|
330
358
|
*/
|
|
331
359
|
offsetMm?: Vec3;
|
|
332
360
|
/**
|
|
333
|
-
* URL of a TTF / OTF / WOFF font
|
|
334
|
-
*
|
|
361
|
+
* URL of a TTF / OTF / WOFF font for the label.
|
|
362
|
+
*
|
|
363
|
+
* Default: the viewer's built-in Roboto Condensed, compiled into the
|
|
364
|
+
* bundle and served from a blob URL — no request, and nothing that can
|
|
365
|
+
* fail. A custom URL is fetched once and shared by every label; until
|
|
366
|
+
* it answers, and permanently if it never does, labels render in the
|
|
367
|
+
* built-in font and the failure is reported once via `console.warn`.
|
|
335
368
|
*/
|
|
336
369
|
fontUrl?: string;
|
|
337
370
|
/** Outline thickness in mm. 0 disables the outline. Default: 0. */
|
|
@@ -457,7 +490,6 @@ interface DimensionOptions {
|
|
|
457
490
|
*/
|
|
458
491
|
lateralDisplacementMm?: number;
|
|
459
492
|
textOptions?: TextOptions;
|
|
460
|
-
textPattern?: number;
|
|
461
493
|
/**
|
|
462
494
|
* Show numeric mm-value labels at every dimension tick (module boundaries
|
|
463
495
|
* and — when enabled — intermediate steps). Labels are billboards that
|
|
@@ -675,6 +707,27 @@ interface ProcessingUnitConfig {
|
|
|
675
707
|
*/
|
|
676
708
|
objectId: number;
|
|
677
709
|
moverType: MoverType3D;
|
|
710
|
+
/**
|
|
711
|
+
* Magnet plate set attached to the carriage.
|
|
712
|
+
*
|
|
713
|
+
* Only meaningful for a mover whose GLB ships bare — the whole Hepco
|
|
714
|
+
* `GFX2-1TC` family. Every other mover models its plate already (see
|
|
715
|
+
* `MOVER_INTEGRATED_PLATE`) and ignores this, so setting it there cannot
|
|
716
|
+
* double-plate the mover. Defaults to `AT9001_0550`, the 5-pole set.
|
|
717
|
+
*
|
|
718
|
+
* This is how pole count is chosen. A 7-pole M34, which used to be its own
|
|
719
|
+
* `Hepco_GFX2_1TC_M34_7P` mover type, is the same carriage with a different
|
|
720
|
+
* plate on it:
|
|
721
|
+
*
|
|
722
|
+
* ```ts
|
|
723
|
+
* { moverType: 'Hepco_GFX2_1TC_M34', magnetPlateType: 'AT9001_0775' }
|
|
724
|
+
* ```
|
|
725
|
+
*
|
|
726
|
+
* Deliberately per processing unit rather than per mover: `moverType` is
|
|
727
|
+
* too, and a plate that varied between movers would force every one of them
|
|
728
|
+
* to claim its own scene node, disabling the instanced render path.
|
|
729
|
+
*/
|
|
730
|
+
magnetPlateType?: MagnetPlateType3D;
|
|
678
731
|
customMoverLayout?: CustomMoverLayout;
|
|
679
732
|
/** Default: 'Beckhoff'. */
|
|
680
733
|
railSystem?: RailSystem;
|
|
@@ -845,6 +898,20 @@ interface ModuleStatusEntry {
|
|
|
845
898
|
warning?: boolean;
|
|
846
899
|
error?: boolean;
|
|
847
900
|
}
|
|
901
|
+
/**
|
|
902
|
+
* Runtime state for a single mover tool — pushed through
|
|
903
|
+
* `XtsViewer3DRef.setMoverToolStates(update)`, keyed by `MoverConfig.id`
|
|
904
|
+
* and then by `MoverToolConfig.id`. A stored entry overrides the
|
|
905
|
+
* config-time `MoverToolConfig.visible`; `null` drops back to it.
|
|
906
|
+
*
|
|
907
|
+
* Visibility only: it is the state a host changes while a scene runs — a
|
|
908
|
+
* simulated line loading a product onto a mover at one station and taking
|
|
909
|
+
* it off at another. Placement, rotation and opacity describe what a tool
|
|
910
|
+
* IS and stay in the configuration.
|
|
911
|
+
*/
|
|
912
|
+
interface MoverToolStateEntry {
|
|
913
|
+
visible?: boolean;
|
|
914
|
+
}
|
|
848
915
|
interface MoverToolConfig {
|
|
849
916
|
toolType: MoverToolType3D;
|
|
850
917
|
/** Required when toolType === 'Custom'. */
|
|
@@ -902,1212 +969,1506 @@ interface CustomAssetConfig {
|
|
|
902
969
|
}
|
|
903
970
|
|
|
904
971
|
/**
|
|
905
|
-
*
|
|
972
|
+
* The transform gizmo — the translate / rotate / scale handles the viewer
|
|
973
|
+
* draws on whatever is selected.
|
|
906
974
|
*
|
|
907
|
-
*
|
|
908
|
-
*
|
|
909
|
-
*
|
|
910
|
-
*
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
interface ModuleRef {
|
|
914
|
-
processingUnitObjectId: number;
|
|
915
|
-
partObjectId: number;
|
|
916
|
-
moduleIndex: number;
|
|
917
|
-
}
|
|
918
|
-
interface MoverRef {
|
|
919
|
-
processingUnitObjectId: number;
|
|
920
|
-
moverIndex: number;
|
|
921
|
-
}
|
|
922
|
-
interface SelectionState {
|
|
923
|
-
modules: ModuleRef[];
|
|
924
|
-
movers: MoverRef[];
|
|
925
|
-
}
|
|
926
|
-
type SelectionMode = 'Off' | 'Single' | 'Multi';
|
|
927
|
-
/**
|
|
928
|
-
* Declarative module highlight. All modules whose `moduleType` appears in
|
|
929
|
-
* `moduleTypes` are tinted with the given `color` — same emissive effect as
|
|
930
|
-
* selection, but independent of the selection state. Multiple highlights can
|
|
931
|
-
* overlap; the first matching entry wins.
|
|
975
|
+
* Issue #107 asks for "OrbitControls … für Rotation, Translation und
|
|
976
|
+
* Scaling" on a selected object or a whole track. Those are two different
|
|
977
|
+
* things: `OrbitControls` is the camera, and what moves an *object* is a
|
|
978
|
+
* transform gizmo, so that is what this is called. The camera controls are
|
|
979
|
+
* unchanged and keep living behind `lock` / `keyboardNavigation`.
|
|
932
980
|
*
|
|
933
|
-
*
|
|
934
|
-
* array (enabled) or `undefined` / `[]` (disabled).
|
|
935
|
-
*/
|
|
936
|
-
interface ModuleHighlight {
|
|
937
|
-
/** Module types to highlight. */
|
|
938
|
-
moduleTypes: ModuleType3D[];
|
|
939
|
-
/** Highlight color (CSS color string, e.g. `'#FF6600'`). */
|
|
940
|
-
color: string;
|
|
941
|
-
}
|
|
942
|
-
/**
|
|
943
|
-
* Addresses one feed segment (Einspeisestrang). Either form works — pick
|
|
944
|
-
* whichever the host has at hand:
|
|
981
|
+
* What can be posed is what the configuration gives a free pose to:
|
|
945
982
|
*
|
|
946
|
-
*
|
|
947
|
-
*
|
|
948
|
-
*
|
|
949
|
-
*
|
|
950
|
-
*
|
|
983
|
+
* - a **track** (`ProcessingUnitConfig.trackTransform`) — position,
|
|
984
|
+
* rotation and uniform scale;
|
|
985
|
+
* - a **static custom asset** (`CustomAssetConfig.binding` with
|
|
986
|
+
* `type: 'static'`) — the same three.
|
|
987
|
+
*
|
|
988
|
+
* A module or a mover is NOT a target and never grows handles: modules bolt
|
|
989
|
+
* together end-to-end along the chain and movers ride it, so neither has a
|
|
990
|
+
* pose of its own, and posing "the track it belongs to" instead would move
|
|
991
|
+
* something other than what the user picked. Select the track — that is
|
|
992
|
+
* what `selectionScope: 'track'` is for. A mover-bound custom asset rides
|
|
993
|
+
* its mover for the same reason and is not a target either.
|
|
951
994
|
*/
|
|
952
|
-
|
|
953
|
-
|
|
995
|
+
|
|
996
|
+
/** Which handle set the gizmo shows. */
|
|
997
|
+
type TransformGizmoMode = 'translate' | 'rotate' | 'scale';
|
|
998
|
+
/** What the handles move. */
|
|
999
|
+
type TransformGizmoTarget = {
|
|
1000
|
+
kind: 'track';
|
|
1001
|
+
processingUnitObjectId: number;
|
|
954
1002
|
} | {
|
|
955
|
-
|
|
956
|
-
|
|
1003
|
+
kind: 'customAsset';
|
|
1004
|
+
assetId: string;
|
|
957
1005
|
};
|
|
958
1006
|
/**
|
|
959
|
-
*
|
|
960
|
-
*
|
|
961
|
-
* selection, but independent of the selection state.
|
|
962
|
-
*
|
|
963
|
-
* Multiple entries are allowed (that is how several segments get several
|
|
964
|
-
* colors at once); on overlap the first matching entry wins, mirroring
|
|
965
|
-
* `ModuleHighlight`.
|
|
966
|
-
*
|
|
967
|
-
* Toggle on/off by setting `feedSegmentHighlights` on `<XtsViewer3D>` to an
|
|
968
|
-
* array (enabled) or `undefined` / `[]` (disabled).
|
|
1007
|
+
* A free pose, in the frame the configuration states it in — the root
|
|
1008
|
+
* `XtsConfig.orientation` frame for both target kinds.
|
|
969
1009
|
*/
|
|
970
|
-
interface
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
1010
|
+
interface TransformGizmoPose {
|
|
1011
|
+
positionMm: Vec3;
|
|
1012
|
+
/** Intrinsic XYZ Euler angles in degrees. */
|
|
1013
|
+
rotationDegEuler: Vec3;
|
|
1014
|
+
/** Uniform scale factor. */
|
|
1015
|
+
scaleUniform: number;
|
|
975
1016
|
}
|
|
976
|
-
/**
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
* ahead of the part's first infeed module belong to the LAST segment, which
|
|
984
|
-
* then carries `wrapsSeam: true`. On an open chain those leading modules
|
|
985
|
-
* belong to no segment at all and cannot be tinted.
|
|
986
|
-
*
|
|
987
|
-
* Returned by `XtsViewer3DRef.getFeedSegments()`.
|
|
988
|
-
*/
|
|
989
|
-
interface FeedSegment {
|
|
990
|
-
processingUnitObjectId: number;
|
|
991
|
-
partObjectId: number;
|
|
992
|
-
/** 0-based, in chain order, counted from the part's first infeed module. */
|
|
993
|
-
segmentIndex: number;
|
|
1017
|
+
/** One drag, reported to the host. */
|
|
1018
|
+
interface TransformGizmoChange {
|
|
1019
|
+
target: TransformGizmoTarget;
|
|
1020
|
+
/** Which kind of handle produced it — all three are on screen at once. */
|
|
1021
|
+
mode: TransformGizmoMode;
|
|
1022
|
+
/** The pose after the drag, in the host's own coordinate convention. */
|
|
1023
|
+
pose: TransformGizmoPose;
|
|
994
1024
|
/**
|
|
995
|
-
* `
|
|
996
|
-
*
|
|
997
|
-
* `{ partObjectId, segmentIndex }`.
|
|
1025
|
+
* `pose` in the shape `trackTransformOverrides` / `trackTransform` take.
|
|
1026
|
+
* Empty for a `'customAsset'` target.
|
|
998
1027
|
*/
|
|
999
|
-
|
|
1000
|
-
/** Module type of the leading infeed module. */
|
|
1001
|
-
infeedModuleType: ModuleType3D;
|
|
1002
|
-
/** Chain indices of every module in this segment, in chain order. */
|
|
1003
|
-
moduleIndices: number[];
|
|
1004
|
-
/** Σ of the segment's module lengths, in mm. */
|
|
1005
|
-
lengthMm: number;
|
|
1006
|
-
/** True when the segment wraps the closed-loop seam. */
|
|
1007
|
-
wrapsSeam: boolean;
|
|
1008
|
-
}
|
|
1009
|
-
interface CameraState {
|
|
1010
|
-
positionMm: Vec3;
|
|
1011
|
-
targetMm: Vec3;
|
|
1012
|
-
/** Always 'Z' in the current spec. */
|
|
1013
|
-
upAxis: 'Z' | 'Y';
|
|
1014
|
-
zoom?: number;
|
|
1028
|
+
trackTransform?: TrackTransform;
|
|
1015
1029
|
}
|
|
1016
1030
|
/**
|
|
1017
|
-
*
|
|
1018
|
-
*
|
|
1019
|
-
*
|
|
1020
|
-
*
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
*
|
|
1025
|
-
*
|
|
1026
|
-
*
|
|
1027
|
-
*
|
|
1028
|
-
*
|
|
1029
|
-
*
|
|
1030
|
-
*
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1031
|
+
* Transform-gizmo configuration. Off unless `enabled` is set, so a viewer
|
|
1032
|
+
* that only displays a layout never grows draggable handles.
|
|
1033
|
+
*
|
|
1034
|
+
* The gizmo is drawn in the same palette as the corner navigation gizmo —
|
|
1035
|
+
* X red, Y green, Z blue — and sits at the target's origin.
|
|
1036
|
+
*
|
|
1037
|
+
* Applying a drag: the viewer shows it immediately, and reports it through
|
|
1038
|
+
* `onTransformChange`. A host that persists the change (into its config, or
|
|
1039
|
+
* into `trackTransformOverrides`) takes over from the viewer's own copy the
|
|
1040
|
+
* moment its value arrives; a host that ignores the callback keeps the
|
|
1041
|
+
* viewer's. Either way the object never jumps.
|
|
1042
|
+
*
|
|
1043
|
+
* @example
|
|
1044
|
+
* ```tsx
|
|
1045
|
+
* <XtsViewer3D
|
|
1046
|
+
* selectionMode="Single"
|
|
1047
|
+
* selectionScope="track"
|
|
1048
|
+
* transformGizmo={{
|
|
1049
|
+
* enabled: true,
|
|
1050
|
+
* translateSnapMm: 10,
|
|
1051
|
+
* onTransformChange: ({ target, trackTransform }) => {
|
|
1052
|
+
* if (target.kind === 'track' && trackTransform) {
|
|
1053
|
+
* setOverrides((o) => ({
|
|
1054
|
+
* ...o,
|
|
1055
|
+
* [target.processingUnitObjectId]: trackTransform,
|
|
1056
|
+
* }));
|
|
1057
|
+
* }
|
|
1058
|
+
* },
|
|
1059
|
+
* }}
|
|
1060
|
+
* />
|
|
1061
|
+
* ```
|
|
1062
|
+
*/
|
|
1063
|
+
interface TransformGizmoOptions {
|
|
1064
|
+
/** Draw the handles at all. Default `false`. */
|
|
1065
|
+
enabled?: boolean;
|
|
1050
1066
|
/**
|
|
1051
|
-
*
|
|
1052
|
-
*
|
|
1067
|
+
* Which handle sets to draw. Default: all three at once, which is the
|
|
1068
|
+
* point of the thing — move, turn and scale without first telling a
|
|
1069
|
+
* toolbar which of them you meant.
|
|
1070
|
+
*
|
|
1071
|
+
* They are told apart by shape and position rather than by a mode: the
|
|
1072
|
+
* translation arrow runs out along the positive half of each axis, the
|
|
1073
|
+
* scale cube along the negative half (drag away from the object to grow
|
|
1074
|
+
* it), and the rotation ring circles both. Narrow the list to leave a
|
|
1075
|
+
* kind of edit out entirely — `['translate', 'rotate']` for a layout
|
|
1076
|
+
* whose tracks must keep their scale.
|
|
1053
1077
|
*/
|
|
1054
|
-
|
|
1055
|
-
/**
|
|
1056
|
-
|
|
1078
|
+
handles?: TransformGizmoMode[];
|
|
1079
|
+
/**
|
|
1080
|
+
* What the handles move. Default `'selection'` — follow the selection: a
|
|
1081
|
+
* selected custom asset, or a selected whole track (which is what a click
|
|
1082
|
+
* produces under `selectionScope: 'track'`). A selected module or mover
|
|
1083
|
+
* shows no handles at all.
|
|
1084
|
+
*
|
|
1085
|
+
* Pass a `TransformGizmoTarget` to drive the gizmo from outside instead;
|
|
1086
|
+
* the selection is then ignored — that is the path for a host that picks
|
|
1087
|
+
* the track from its own list rather than from the canvas. `'none'` keeps
|
|
1088
|
+
* the feature configured but shows nothing.
|
|
1089
|
+
*/
|
|
1090
|
+
target?: 'selection' | 'none' | TransformGizmoTarget;
|
|
1091
|
+
/** On-screen length of one axis, in CSS px. Default `90`. */
|
|
1092
|
+
sizePx?: number;
|
|
1093
|
+
/**
|
|
1094
|
+
* Axis colours in x / y / z order. Defaults to the navigation gizmo's
|
|
1095
|
+
* palette, which is what makes the two read as one family.
|
|
1096
|
+
*/
|
|
1097
|
+
axisColors?: readonly [string, string, string];
|
|
1098
|
+
/** Round translation to this step, in mm. Default: no snapping. */
|
|
1099
|
+
translateSnapMm?: number;
|
|
1100
|
+
/** Round rotation to this step, in degrees. Default: no snapping. */
|
|
1101
|
+
rotateSnapDeg?: number;
|
|
1102
|
+
/** Round the scale factor to this step. Default: no snapping. */
|
|
1103
|
+
scaleSnap?: number;
|
|
1104
|
+
/** Smallest scale a drag can reach. Default `0.05`. */
|
|
1105
|
+
minScale?: number;
|
|
1106
|
+
/** A drag started. */
|
|
1107
|
+
onTransformStart?: (target: TransformGizmoTarget) => void;
|
|
1108
|
+
/** Fires continuously while dragging. */
|
|
1109
|
+
onTransformChange?: (change: TransformGizmoChange) => void;
|
|
1110
|
+
/** Fires once, on pointer release. */
|
|
1111
|
+
onTransformEnd?: (change: TransformGizmoChange) => void;
|
|
1057
1112
|
}
|
|
1058
1113
|
|
|
1059
1114
|
/**
|
|
1060
|
-
*
|
|
1115
|
+
* Module catalog (3D-canonical).
|
|
1061
1116
|
*
|
|
1062
|
-
*
|
|
1063
|
-
*
|
|
1064
|
-
*
|
|
1065
|
-
*
|
|
1066
|
-
* the `onError` callback as plain `XtsViewerError` records instead.
|
|
1067
|
-
*/
|
|
1068
|
-
type XtsViewerErrorCode = 'asset-load-failed' | 'unknown-module-type' | 'unknown-mover-type' | 'unknown-tool-type' | 'invalid-config' | 'unmatched-clothoid-half' | 'webgl-context-lost';
|
|
1069
|
-
interface XtsViewerError {
|
|
1070
|
-
code: XtsViewerErrorCode;
|
|
1071
|
-
message: string;
|
|
1072
|
-
details?: unknown;
|
|
1073
|
-
}
|
|
1074
|
-
declare class XtsViewerErrorException extends Error {
|
|
1075
|
-
readonly code: XtsViewerErrorCode;
|
|
1076
|
-
readonly details?: unknown;
|
|
1077
|
-
constructor(code: XtsViewerErrorCode, message: string, details?: unknown);
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
/**
|
|
1081
|
-
* Asset manifest — per-type overrides for the GLB URLs the viewer loads.
|
|
1117
|
+
* Every module declares its path topology (Straight / Curve / Free), its
|
|
1118
|
+
* arc length, and — for curves — the signed end-of-module yaw delta in
|
|
1119
|
+
* degrees. Centerline radius for analytic curves is derived from
|
|
1120
|
+
* `R = L / |θ_rad|`.
|
|
1082
1121
|
*
|
|
1083
|
-
*
|
|
1084
|
-
*
|
|
1085
|
-
*
|
|
1122
|
+
* Hygienic ATH variants share path math with their AT pendants — only the
|
|
1123
|
+
* GLB mesh differs.
|
|
1124
|
+
*
|
|
1125
|
+
* This file is hand-curated. The geometry module imports from here directly
|
|
1126
|
+
* (no JSON fetch) so the library has zero runtime data deps.
|
|
1086
1127
|
*/
|
|
1087
1128
|
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1129
|
+
type PathType = 'Straight' | 'Curve' | 'Free';
|
|
1130
|
+
interface ModuleCatalogEntry {
|
|
1131
|
+
moduleType: ModuleType3D;
|
|
1132
|
+
/** Arc length along the centerline, in mm. */
|
|
1133
|
+
moduleLengthMm: number;
|
|
1134
|
+
pathType: PathType;
|
|
1135
|
+
/**
|
|
1136
|
+
* Encoder-side end-of-module yaw, in degrees, in the module-local 3D frame.
|
|
1137
|
+
* Sign convention: positive endAngle ⇒ Y-flip math curves toward
|
|
1138
|
+
* −Y (= right of path). 0 for Straight. 180 for AT2050_*_180 free path.
|
|
1139
|
+
*/
|
|
1140
|
+
endAngleDeg: number;
|
|
1141
|
+
/**
|
|
1142
|
+
* Centerline radius for analytic curves, in mm. `null` for Straight + Free.
|
|
1143
|
+
* Computed as R = L / |θ_rad|; equals 636.6198 for ±22.5° and 318.3099 for
|
|
1144
|
+
* ±45° curves at L=250.
|
|
1145
|
+
*/
|
|
1146
|
+
centerlineRadiusMm: number | null;
|
|
1147
|
+
/**
|
|
1148
|
+
* Free-path point cloud reference. Only set for `pathType==='Free'`.
|
|
1149
|
+
* The string identifies the cloud by name; the actual data lives in
|
|
1150
|
+
* geometry/PointClouds.ts.
|
|
1151
|
+
*/
|
|
1152
|
+
freePathId?: string;
|
|
1153
|
+
/** True if STP is not delivered yet. */
|
|
1154
|
+
prepared?: boolean;
|
|
1155
|
+
/** True for Einspeisemodule (power feed-in variants). */
|
|
1156
|
+
isInfeed?: boolean;
|
|
1094
1157
|
}
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1158
|
+
declare const MODULE_CATALOG: Readonly<Record<ModuleType3D, ModuleCatalogEntry>>;
|
|
1159
|
+
/**
|
|
1160
|
+
* Beckhoff XTS infeed module types (Einspeisemodule). Convenience constant
|
|
1161
|
+
* for use with `moduleHighlights` on `<XtsViewer3D>`.
|
|
1162
|
+
*
|
|
1163
|
+
* Derived from the catalog (`isInfeed` flag) so it never drifts out of sync —
|
|
1164
|
+
* mark a new module with `isInfeed: true` in `MODULE_CATALOG` and it is
|
|
1165
|
+
* automatically included here.
|
|
1166
|
+
*/
|
|
1167
|
+
declare const INFEED_MODULE_TYPES: readonly ModuleType3D[];
|
|
1168
|
+
/** Lookup with type-narrowing. Returns undefined for unknown types. */
|
|
1169
|
+
declare function getModuleEntry(moduleType: ModuleType3D): ModuleCatalogEntry | undefined;
|
|
1170
|
+
/** Type-guard for safe lookups from untyped (e.g. JSON) sources. */
|
|
1171
|
+
declare function isKnownModuleType(value: string): value is ModuleType3D;
|
|
1172
|
+
|
|
1173
|
+
/**
|
|
1174
|
+
* ChainBuilder — builds the per-module `startWorldMatrix` for a Part.
|
|
1175
|
+
*
|
|
1176
|
+
* Chain composition (simplified — modules butt up edge-to-edge in this build):
|
|
1177
|
+
* chain_world = Identity
|
|
1178
|
+
* for each module_i:
|
|
1179
|
+
* module_i.startWorldMatrix = chain_world
|
|
1180
|
+
* module_i.startPositionInPartMm = cumulative
|
|
1181
|
+
* module_i.trackLengthMm = module_i.moduleLengthMm
|
|
1182
|
+
* cumulative += trackLengthMm
|
|
1183
|
+
* end_local = T( ΔX, -ΔY_canvas, 0 ) ⊗ R_z( -θ )
|
|
1184
|
+
* chain_world = chain_world ⊗ end_local
|
|
1185
|
+
*
|
|
1186
|
+
* `chain_world` is a planar pose: 2D translation (X,Y) + Z-yaw. We carry it as
|
|
1187
|
+
* a tiny struct rather than a full 4×4 to keep the pure-math layer free of
|
|
1188
|
+
* Three.js. A Three.js helper in PathBuilder.ts converts to Matrix4 on demand.
|
|
1189
|
+
*/
|
|
1190
|
+
|
|
1191
|
+
/** Planar pose (X,Y,yaw) in a 3D-RH-Z-up frame. Z is always 0 here. */
|
|
1192
|
+
interface PlanarPose {
|
|
1193
|
+
positionMm: Vec3;
|
|
1194
|
+
yawDeg: number;
|
|
1098
1195
|
}
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1196
|
+
declare const IDENTITY_POSE: PlanarPose;
|
|
1197
|
+
interface BuiltModule {
|
|
1198
|
+
moduleType: ModuleType3D;
|
|
1199
|
+
catalog: ModuleCatalogEntry;
|
|
1200
|
+
/** Pose of the module-local origin (= path entry). */
|
|
1201
|
+
startWorldPose: PlanarPose;
|
|
1202
|
+
/** Distance along the part where this module starts (mm). */
|
|
1203
|
+
startPositionInPartMm: number;
|
|
1204
|
+
/** moduleLength (mm). Used for findModuleContaining. */
|
|
1205
|
+
trackLengthMm: number;
|
|
1206
|
+
/** Forwarded from `ModuleEntry.globalNumber` — index into the runtime
|
|
1207
|
+
* `ModuleStatusStore` so renderers can subscribe per module. */
|
|
1208
|
+
globalNumber?: number;
|
|
1209
|
+
/** Forwarded from `ModuleEntry.status` so the renderer can light up
|
|
1210
|
+
* the per-module drive-status overlay without an extra lookup. */
|
|
1211
|
+
status?: {
|
|
1212
|
+
warning?: boolean;
|
|
1213
|
+
error?: boolean;
|
|
1104
1214
|
};
|
|
1105
|
-
sidecarUrl?: string;
|
|
1106
|
-
}
|
|
1107
|
-
interface MoverAssetEntry {
|
|
1108
|
-
glbUrl: string;
|
|
1109
|
-
sidecarUrl?: string;
|
|
1110
1215
|
}
|
|
1111
|
-
interface
|
|
1112
|
-
|
|
1113
|
-
|
|
1216
|
+
interface BuiltChain {
|
|
1217
|
+
modules: BuiltModule[];
|
|
1218
|
+
/** Σ moduleLength_i. */
|
|
1219
|
+
trackLengthMm: number;
|
|
1220
|
+
/** End-of-chain pose; useful for closure-checks. */
|
|
1221
|
+
endPose: PlanarPose;
|
|
1114
1222
|
}
|
|
1115
|
-
|
|
1116
1223
|
/**
|
|
1117
|
-
*
|
|
1224
|
+
* Build a chain from a part's module list.
|
|
1118
1225
|
*
|
|
1119
|
-
*
|
|
1120
|
-
*
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
*
|
|
1226
|
+
* Throws XtsViewerErrorException with code `unknown-module-type` on lookup
|
|
1227
|
+
* miss.
|
|
1228
|
+
*/
|
|
1229
|
+
declare function buildChain(modules: ReadonlyArray<ModuleEntry>): BuiltChain;
|
|
1230
|
+
/**
|
|
1231
|
+
* Locate the module containing `partPositionMm` along the chain.
|
|
1232
|
+
* First module with partPosition < startInPart + trackLength; at boundary
|
|
1233
|
+
* `partPosition == trackLength` the LAST module is returned with
|
|
1234
|
+
* `localPos = trackLength`.
|
|
1125
1235
|
*/
|
|
1236
|
+
declare function findModuleAt(chain: BuiltChain, partPositionMm: number): {
|
|
1237
|
+
module: BuiltModule;
|
|
1238
|
+
localPosMm: number;
|
|
1239
|
+
} | null;
|
|
1240
|
+
/**
|
|
1241
|
+
* Mover world pose at `partPositionMm`.
|
|
1242
|
+
*
|
|
1243
|
+
* The module's startWorldPose is composed with the local path sample at
|
|
1244
|
+
* `localPosMm` along that module's path.
|
|
1245
|
+
*/
|
|
1246
|
+
declare function moverWorldAt(chain: BuiltChain, partPositionMm: number): PlanarPose | null;
|
|
1247
|
+
/** Convenience: chain from full ModuleEntry[] returning trackLength alone. */
|
|
1248
|
+
declare function trackLengthOf(modules: ReadonlyArray<ModuleEntry>): number;
|
|
1126
1249
|
|
|
1127
1250
|
/**
|
|
1128
|
-
*
|
|
1129
|
-
*
|
|
1251
|
+
* normalizeXtsConfig — pre-render pass.
|
|
1252
|
+
*
|
|
1253
|
+
* Fail-fast validation + AT2050 half-clothoid merging:
|
|
1254
|
+
* AT2050_0500 + AT2050_0501 (gap=0) → AT2050_0500_180
|
|
1255
|
+
* ATH2050_0500 + ATH2050_0501 (gap=0) → ATH2050_0500_180
|
|
1256
|
+
*
|
|
1257
|
+
* Errors thrown via XtsViewerErrorException:
|
|
1258
|
+
* - 'unknown-module-type'
|
|
1259
|
+
* - 'unknown-mover-type'
|
|
1260
|
+
* - 'unmatched-clothoid-half' (0500 without 0501 or vice versa)
|
|
1261
|
+
*
|
|
1262
|
+
* Warnings (collected, non-fatal) returned alongside the normalized config:
|
|
1263
|
+
* - mover.partOid not in part-list
|
|
1264
|
+
* - duplicate mover.id within an XPU
|
|
1265
|
+
* - mover.partPositionMm out of range (clamped)
|
|
1130
1266
|
*/
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
type MeasuredEntityRef = {
|
|
1134
|
-
kind: 'module';
|
|
1135
|
-
ref: ModuleRef;
|
|
1136
|
-
} | {
|
|
1137
|
-
kind: 'mover';
|
|
1138
|
-
ref: MoverRef;
|
|
1139
|
-
} | {
|
|
1140
|
-
kind: 'station';
|
|
1141
|
-
stationId: number;
|
|
1142
|
-
};
|
|
1143
|
-
/** A position on a part's track path, in the XPU's user position frame. */
|
|
1144
|
-
interface TrackAnchor {
|
|
1145
|
-
processingUnitObjectId: number;
|
|
1146
|
-
/** `PartConfig.objectId`. */
|
|
1147
|
-
partObjectId: number;
|
|
1148
|
-
/** Position along the part, user frame (same convention as movers). */
|
|
1149
|
-
partPositionMm: number;
|
|
1150
|
-
}
|
|
1151
|
-
/** One measurement vertex. */
|
|
1152
|
-
interface MeasurementPoint {
|
|
1153
|
-
/** World position (mm). */
|
|
1154
|
-
positionMm: Vec3;
|
|
1155
|
-
/** How the point was obtained. Default `'free'` when omitted. */
|
|
1156
|
-
snap?: SnapKind;
|
|
1157
|
-
/** Entity the point sits on, when known. */
|
|
1158
|
-
entity?: MeasuredEntityRef;
|
|
1159
|
-
/**
|
|
1160
|
-
* Track position, set whenever the point resolved onto a part's path.
|
|
1161
|
-
* Required on both endpoints of a `'trackDistance'` measurement.
|
|
1162
|
-
*/
|
|
1163
|
-
track?: TrackAnchor;
|
|
1164
|
-
}
|
|
1165
|
-
/** Snapping configuration. */
|
|
1166
|
-
interface SnapOptions {
|
|
1167
|
-
/** Master switch. Default `true`. */
|
|
1168
|
-
enabled?: boolean;
|
|
1169
|
-
/**
|
|
1170
|
-
* Snap kinds to consider, highest priority first. Default:
|
|
1171
|
-
* `['vertex', 'edge-midpoint', 'mover-center', 'module-boundary',
|
|
1172
|
-
* 'station-stop', 'track-path', 'face-center']`.
|
|
1173
|
-
*/
|
|
1174
|
-
kinds?: SnapKind[];
|
|
1175
|
-
/** Screen-space radius (px) a candidate must fall within. Default `14`. */
|
|
1176
|
-
radiusPx?: number;
|
|
1267
|
+
|
|
1268
|
+
interface NormalizedXtsConfig extends XtsConfig {
|
|
1177
1269
|
/**
|
|
1178
|
-
*
|
|
1179
|
-
*
|
|
1270
|
+
* Same shape as the input, with all parts' modules pre-merged + validated.
|
|
1271
|
+
* Mover positions are clamped, but the original values are preserved in
|
|
1272
|
+
* `__warnings` for diagnostics.
|
|
1180
1273
|
*/
|
|
1181
|
-
|
|
1182
|
-
}
|
|
1183
|
-
type MeasurementId = string;
|
|
1184
|
-
type MeasurementKind = 'distance' | 'angle' | 'path' | 'trackDistance';
|
|
1185
|
-
/** Axis constraint for a distance measurement. */
|
|
1186
|
-
type MeasurementAxis = 'free' | 'x' | 'y' | 'z';
|
|
1187
|
-
interface MeasurementBase {
|
|
1188
|
-
id: MeasurementId;
|
|
1189
|
-
/** Optional caption rendered above the value. */
|
|
1190
|
-
label?: string;
|
|
1191
|
-
/** Overrides `MeasurementStyle.color` for this one measurement. */
|
|
1192
|
-
color?: string;
|
|
1193
|
-
/** Default `true`. */
|
|
1194
|
-
visible?: boolean;
|
|
1195
|
-
/** Free-form payload the host can round-trip (never read by the viewer). */
|
|
1196
|
-
meta?: Record<string, unknown>;
|
|
1197
|
-
}
|
|
1198
|
-
/** Straight-line distance between two points. */
|
|
1199
|
-
interface DistanceMeasurement extends MeasurementBase {
|
|
1200
|
-
kind: 'distance';
|
|
1201
|
-
points: [MeasurementPoint, MeasurementPoint];
|
|
1202
|
-
/** Constrain the reported value to one axis. Default `'free'`. */
|
|
1203
|
-
axis?: MeasurementAxis;
|
|
1204
|
-
/** Render dashed ΔX / ΔY / ΔZ guides. Default `false`. */
|
|
1205
|
-
showComponents?: boolean;
|
|
1206
|
-
}
|
|
1207
|
-
/** Angle at `points[1]` between the legs to `points[0]` and `points[2]`. */
|
|
1208
|
-
interface AngleMeasurement extends MeasurementBase {
|
|
1209
|
-
kind: 'angle';
|
|
1210
|
-
/** `[legA, vertex, legB]` — the middle point is the vertex. */
|
|
1211
|
-
points: [MeasurementPoint, MeasurementPoint, MeasurementPoint];
|
|
1274
|
+
readonly __warnings: NormalizationWarning[];
|
|
1212
1275
|
}
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
/** Close the polyline back to the first point. Default `false`. */
|
|
1218
|
-
closed?: boolean;
|
|
1219
|
-
/** Label every segment, not just the total. Default `true`. */
|
|
1220
|
-
showSegmentLengths?: boolean;
|
|
1276
|
+
interface NormalizationWarning {
|
|
1277
|
+
code: 'mover-out-of-range' | 'mover-orphan' | 'mover-duplicate-id' | 'mover-missing-id' | 'mover-invalid-id' | 'empty-part' | 'prepared-module' | 'duplicate-xpu-object-id' | 'missing-xpu-object-id' | 'invalid-xpu-object-id' | 'duplicate-part-object-id' | 'missing-part-object-id' | 'invalid-part-object-id' | 'duplicate-part-global-number' | 'missing-part-global-number' | 'invalid-part-global-number' | 'duplicate-module-global-number' | 'missing-module-global-number' | 'invalid-module-global-number';
|
|
1278
|
+
message: string;
|
|
1279
|
+
details?: unknown;
|
|
1221
1280
|
}
|
|
1222
1281
|
/**
|
|
1223
|
-
*
|
|
1224
|
-
*
|
|
1282
|
+
* Normalizer handle.
|
|
1283
|
+
*
|
|
1284
|
+
* This used to carry a single-slot structural cache keyed on a
|
|
1285
|
+
* `JSON.stringify` of the config with the frame-paced fields stripped out.
|
|
1286
|
+
* It was removed: the only production caller is `useNormalizedConfig`, whose
|
|
1287
|
+
* `useMemo` already collapses repeat calls on an unchanged config, so the
|
|
1288
|
+
* cache could fire only when the config identity changed but its structure
|
|
1289
|
+
* did not — and in exactly that case the "fast path" cost a full stringify
|
|
1290
|
+
* of the config plus a full rebuild of every XPU, which is strictly more
|
|
1291
|
+
* work than the single O(n) pass it was avoiding.
|
|
1292
|
+
*
|
|
1293
|
+
* It also cost correctness twice over. The key stripped `status` by name at
|
|
1294
|
+
* every depth, so it removed `ModuleEntry.status` along with the mover field
|
|
1295
|
+
* it was aimed at, while the clone path rebuilt only movers — freezing
|
|
1296
|
+
* declarative module warnings after the first normalize. And it forced a
|
|
1297
|
+
* second copy of the mover clamp that had already drifted from `clampMover`
|
|
1298
|
+
* (different `details` key, different `original` value), so hosts parsing
|
|
1299
|
+
* warning details saw two shapes for one warning code.
|
|
1300
|
+
*
|
|
1301
|
+
* The class stays because it is part of the published API. It is now a thin
|
|
1302
|
+
* handle over the pure function.
|
|
1225
1303
|
*/
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1304
|
+
declare class XtsConfigNormalizer {
|
|
1305
|
+
normalize(config: XtsConfig): NormalizedXtsConfig;
|
|
1306
|
+
/** No-op. Kept so existing callers keep compiling; there is no cache. */
|
|
1307
|
+
clearCache(): void;
|
|
1229
1308
|
}
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1309
|
+
declare function normalizeXtsConfig(config: XtsConfig): NormalizedXtsConfig;
|
|
1310
|
+
|
|
1311
|
+
/**
|
|
1312
|
+
* Mover collision detection.
|
|
1313
|
+
*
|
|
1314
|
+
* Sub-millimetre accurate for the dominant XTS case: two movers travelling
|
|
1315
|
+
* along the same chain. Each mover occupies a 1D footprint of
|
|
1316
|
+
* `pathLengthMm` centred on `partPositionMm`, so the collision predicate
|
|
1317
|
+
* collapses to one subtraction in path-space:
|
|
1318
|
+
*
|
|
1319
|
+
* centerDistance = arc-length |pos_a − pos_b|
|
|
1320
|
+
* requiredGap = (pathLength_a + pathLength_b) / 2
|
|
1321
|
+
* penetration = requiredGap − centerDistance
|
|
1322
|
+
* collision iff penetration > 0 (≥ 0 for "touching")
|
|
1323
|
+
*
|
|
1324
|
+
* For closed-loop chains (oval tracks, U-turn pairs) the arc-length is
|
|
1325
|
+
* taken as the *minimum* of the forward and the wrap-around path so two
|
|
1326
|
+
* movers near the seam (e.g. 50 mm and trackLength − 50 mm) are treated
|
|
1327
|
+
* as physically adjacent. Open chains are detected by comparing the
|
|
1328
|
+
* chain's start- and end-pose; for those the wrap-around distance is
|
|
1329
|
+
* simply not considered.
|
|
1330
|
+
*
|
|
1331
|
+
* Floating-point precision: positions are kept in mm in float64. A typical
|
|
1332
|
+
* XTS layout has trackLength ≤ 20 m → < 2×10⁴ mm, well inside the
|
|
1333
|
+
* 2⁵³-mantissa range, so subtractive cancellation costs at most ~10⁻¹²
|
|
1334
|
+
* mm — orders of magnitude below sub-millimetre.
|
|
1335
|
+
*
|
|
1336
|
+
* Multi-XPU (different chains): not handled here. Cross-track collisions
|
|
1337
|
+
* are dominated by track-transform layout and would need an OBB / mesh
|
|
1338
|
+
* test in 3D space; that's a different concern from "two movers on the
|
|
1339
|
+
* same line about to crash".
|
|
1340
|
+
*/
|
|
1341
|
+
|
|
1342
|
+
/**
|
|
1343
|
+
* One detected pair. Result list is symmetric (a/b is reported once per
|
|
1344
|
+
* pair, with `a` carrying the lower index pair-canonical).
|
|
1345
|
+
*/
|
|
1346
|
+
interface MoverCollision {
|
|
1347
|
+
a: MoverRef;
|
|
1348
|
+
b: MoverRef;
|
|
1254
1349
|
/**
|
|
1255
|
-
*
|
|
1256
|
-
*
|
|
1257
|
-
* the straight-line fallback.
|
|
1350
|
+
* Stable IDs of the two movers (mirrors `MoverConfig.id`). Useful when
|
|
1351
|
+
* the consumer keys by id rather than by `moverIndex`.
|
|
1258
1352
|
*/
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
/** Where an annotation hangs. Mover anchors follow the live mover position. */
|
|
1262
|
-
type AnnotationAnchor = {
|
|
1263
|
-
kind: 'world';
|
|
1264
|
-
positionMm: Vec3;
|
|
1265
|
-
} | {
|
|
1266
|
-
kind: 'mover';
|
|
1267
|
-
ref: MoverRef;
|
|
1268
|
-
} | {
|
|
1269
|
-
kind: 'module';
|
|
1270
|
-
ref: ModuleRef;
|
|
1271
|
-
} | ({
|
|
1272
|
-
kind: 'trackPosition';
|
|
1273
|
-
} & TrackAnchor);
|
|
1274
|
-
interface Annotation {
|
|
1275
|
-
id: string;
|
|
1276
|
-
text: string;
|
|
1277
|
-
anchor: AnnotationAnchor;
|
|
1353
|
+
idA: number;
|
|
1354
|
+
idB: number;
|
|
1278
1355
|
/**
|
|
1279
|
-
*
|
|
1280
|
-
*
|
|
1356
|
+
* Penetration along the 1D path:
|
|
1357
|
+
* • `> 0` — movers overlap by this many mm.
|
|
1358
|
+
* • `= 0` — touching exactly.
|
|
1359
|
+
* • `< 0` — closest-approach gap (only present in result when
|
|
1360
|
+
* `warningGapMm` is set; the magnitude is the free-space
|
|
1361
|
+
* distance between the closer footprint edges).
|
|
1281
1362
|
*/
|
|
1282
|
-
|
|
1283
|
-
color?: string;
|
|
1284
|
-
/** Text height (mm). Default `MeasurementStyle.labelSizeMm`. */
|
|
1285
|
-
sizeMm?: number;
|
|
1286
|
-
/** Marker drawn at the anchor. Default `'Sphere'`. */
|
|
1287
|
-
markerShape?: MarkerShape;
|
|
1288
|
-
markerSizeMm?: number;
|
|
1289
|
-
/** Default `true`. */
|
|
1290
|
-
visible?: boolean;
|
|
1291
|
-
meta?: Record<string, unknown>;
|
|
1292
|
-
}
|
|
1293
|
-
type AnnotationInput = Omit<Annotation, 'id'> & {
|
|
1294
|
-
id?: string;
|
|
1295
|
-
};
|
|
1296
|
-
type MeasurementTool = 'none' | 'distance' | 'angle' | 'path' | 'trackDistance' | 'annotation';
|
|
1297
|
-
/** In-progress pick. `null` while no tool is armed. */
|
|
1298
|
-
interface MeasurementDraft {
|
|
1299
|
-
tool: Exclude<MeasurementTool, 'none'>;
|
|
1300
|
-
/** Points committed so far. */
|
|
1301
|
-
points: MeasurementPoint[];
|
|
1302
|
-
/** Point currently under the cursor, or `null` when off the scene. */
|
|
1303
|
-
hover: MeasurementPoint | null;
|
|
1304
|
-
}
|
|
1305
|
-
interface MeasurementStyle {
|
|
1306
|
-
/** Line + label colour of committed measurements. Default `'#00E5FF'`. */
|
|
1307
|
-
color?: string;
|
|
1308
|
-
/** Colour of the in-progress draft. Default `'#FFC400'`. */
|
|
1309
|
-
draftColor?: string;
|
|
1310
|
-
/** Annotation colour. Default `'#FFFFFF'`. */
|
|
1311
|
-
annotationColor?: string;
|
|
1312
|
-
/** Line width in pixels. Default `2`. */
|
|
1313
|
-
lineWidthPx?: number;
|
|
1314
|
-
/** Text height (mm). Default `40`. */
|
|
1315
|
-
labelSizeMm?: number;
|
|
1316
|
-
/** Endpoint marker diameter (mm). Default `18`. */
|
|
1317
|
-
pointSizeMm?: number;
|
|
1318
|
-
/** Decimal places on values. Default `1`. */
|
|
1319
|
-
decimals?: number;
|
|
1320
|
-
/** Unit suffix appended to lengths. Default `' mm'`. */
|
|
1321
|
-
unitSuffix?: string;
|
|
1322
|
-
/** Font URL passed to troika (see `DEFAULT_LABEL_FONT_URL`). */
|
|
1323
|
-
fontFamily?: string;
|
|
1363
|
+
penetrationMm: number;
|
|
1324
1364
|
/**
|
|
1325
|
-
*
|
|
1326
|
-
*
|
|
1327
|
-
* readable regardless of what is in front of them.
|
|
1365
|
+
* Both `partPositionMm` values at the time of the test. Lets the
|
|
1366
|
+
* consumer recompute or visualise without re-querying.
|
|
1328
1367
|
*/
|
|
1329
|
-
|
|
1368
|
+
positionAMm: number;
|
|
1369
|
+
positionBMm: number;
|
|
1370
|
+
/** Mover lengths (catalog `pathLengthMm`). */
|
|
1371
|
+
pathLengthAMm: number;
|
|
1372
|
+
pathLengthBMm: number;
|
|
1373
|
+
/** True when measurement wrapped around the chain end (closed-loop seam). */
|
|
1374
|
+
viaWraparound: boolean;
|
|
1330
1375
|
}
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1376
|
+
interface CheckMoverCollisionsOptions {
|
|
1377
|
+
/**
|
|
1378
|
+
* When `> 0`, near-misses with closest-approach gap up to this many mm
|
|
1379
|
+
* are also returned (with `penetrationMm < 0`). Default 0 — only actual
|
|
1380
|
+
* collisions / contacts.
|
|
1381
|
+
*/
|
|
1382
|
+
warningGapMm?: number;
|
|
1383
|
+
/**
|
|
1384
|
+
* Force-treat the chain as closed (consider wrap-around distance) or
|
|
1385
|
+
* open (don't). When omitted, auto-detected by comparing
|
|
1386
|
+
* `chain.endPose` to `startPose ≈ identity` within ~0.1 mm tolerance.
|
|
1387
|
+
*/
|
|
1388
|
+
forceClosedLoop?: boolean;
|
|
1336
1389
|
}
|
|
1337
1390
|
/**
|
|
1338
|
-
*
|
|
1339
|
-
*
|
|
1340
|
-
*
|
|
1341
|
-
*
|
|
1342
|
-
* persist what the change callbacks report.
|
|
1391
|
+
* Effective mover state passed into the predicate. Decoupled from
|
|
1392
|
+
* MoverConfig so consumers can plug in live positions from the
|
|
1393
|
+
* MoverPositionStore (60-Hz tick) without round-tripping through React
|
|
1394
|
+
* state.
|
|
1343
1395
|
*/
|
|
1344
|
-
interface
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
annotations?: AnnotationInput[];
|
|
1351
|
-
snap?: SnapOptions;
|
|
1352
|
-
style?: MeasurementStyle;
|
|
1353
|
-
/**
|
|
1354
|
-
* Z of the fallback pick plane (mm) used when the ray misses all geometry,
|
|
1355
|
-
* so free space can be measured. Default `0`; `null` disables the fallback.
|
|
1356
|
-
*/
|
|
1357
|
-
groundPlaneZMm?: number | null;
|
|
1358
|
-
/** Fired after every change to the measurement list. */
|
|
1359
|
-
onMeasurementsChange?: (measurements: Measurement[], results: MeasurementResult[]) => void;
|
|
1360
|
-
/** Fired once when a tool completes a measurement. */
|
|
1361
|
-
onMeasurementCreate?: (measurement: Measurement, result: MeasurementResult) => void;
|
|
1362
|
-
onAnnotationsChange?: (annotations: Annotation[]) => void;
|
|
1363
|
-
onAnnotationCreate?: (annotation: Annotation) => void;
|
|
1364
|
-
/** Fired on every draft change (point added, hover moved, cancelled). */
|
|
1365
|
-
onDraftChange?: (draft: MeasurementDraft | null) => void;
|
|
1396
|
+
interface MoverProbe {
|
|
1397
|
+
ref: MoverRef;
|
|
1398
|
+
id: number;
|
|
1399
|
+
partOid: number;
|
|
1400
|
+
moverType: MoverType3D;
|
|
1401
|
+
partPositionMm: number;
|
|
1366
1402
|
/**
|
|
1367
|
-
*
|
|
1368
|
-
* `
|
|
1403
|
+
* Override the catalog `pathLengthMm`. Use for `Custom` movers where
|
|
1404
|
+
* the layout's `pathLengthMm` is the source of truth.
|
|
1369
1405
|
*/
|
|
1370
|
-
|
|
1406
|
+
pathLengthMm?: number;
|
|
1371
1407
|
}
|
|
1372
|
-
|
|
1373
1408
|
/**
|
|
1374
|
-
*
|
|
1409
|
+
* Pair-wise 1D collision test for movers on the same chain.
|
|
1375
1410
|
*
|
|
1376
|
-
*
|
|
1377
|
-
*
|
|
1378
|
-
*
|
|
1379
|
-
*
|
|
1380
|
-
*
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
* documents written before the measurement feature stay byte-identical.
|
|
1400
|
-
*/
|
|
1401
|
-
measurements?: MeasurementDocument;
|
|
1402
|
-
}
|
|
1403
|
-
interface XtsRuntimeState {
|
|
1404
|
-
movers: Array<{
|
|
1405
|
-
processingUnitObjectId: number;
|
|
1406
|
-
moverIndex: number;
|
|
1407
|
-
id: number;
|
|
1408
|
-
partOid: number;
|
|
1409
|
-
partPositionMm: number;
|
|
1410
|
-
velocity?: {
|
|
1411
|
-
mmPerSec: number;
|
|
1412
|
-
};
|
|
1413
|
-
status?: {
|
|
1414
|
-
warning?: boolean;
|
|
1415
|
-
error?: boolean;
|
|
1416
|
-
};
|
|
1417
|
-
}>;
|
|
1418
|
-
selection?: SelectionState;
|
|
1419
|
-
camera?: CameraState;
|
|
1420
|
-
display?: Partial<DisplayOptions>;
|
|
1421
|
-
}
|
|
1411
|
+
* `chain` is the BuiltChain corresponding to the partOid all probed
|
|
1412
|
+
* movers travel on. `probes` may contain movers from *any* part — this
|
|
1413
|
+
* function only considers pairs where `partOid` matches the chain.
|
|
1414
|
+
*
|
|
1415
|
+
* Returns one entry per colliding (or near-miss, when `warningGapMm > 0`)
|
|
1416
|
+
* pair, sorted by `penetrationMm` descending so the deepest collisions
|
|
1417
|
+
* appear first.
|
|
1418
|
+
*/
|
|
1419
|
+
declare function checkSamePathCollisions(chain: BuiltChain, partOid: number, probes: ReadonlyArray<MoverProbe>, opts?: CheckMoverCollisionsOptions): MoverCollision[];
|
|
1420
|
+
/**
|
|
1421
|
+
* Collision check across a whole config: build a probe per mover, group by
|
|
1422
|
+
* part, and run `checkSamePathCollisions` on each group with two or more.
|
|
1423
|
+
*
|
|
1424
|
+
* `livePositionOf` supplies the current position of a mover when one is
|
|
1425
|
+
* being driven imperatively — the `MoverPositionStore` at 60 Hz — falling
|
|
1426
|
+
* back to the config value. Passing it as a function rather than the store
|
|
1427
|
+
* itself is what keeps this a pure geometry function: it was previously
|
|
1428
|
+
* trapped inside the `useImperativeHandle` closure of `useXtsViewerHandle`,
|
|
1429
|
+
* where it could only be exercised through a mounted canvas.
|
|
1430
|
+
*
|
|
1431
|
+
* Results are sorted so the deepest collision in the scene comes first.
|
|
1432
|
+
*/
|
|
1433
|
+
declare function checkAllMoverCollisions(config: NormalizedXtsConfig, livePositionOf: (moverId: number) => number | undefined, opts?: CheckMoverCollisionsOptions): MoverCollision[];
|
|
1422
1434
|
|
|
1423
1435
|
/**
|
|
1424
|
-
*
|
|
1436
|
+
* Module collision / overlap detection.
|
|
1425
1437
|
*
|
|
1426
|
-
*
|
|
1427
|
-
*
|
|
1428
|
-
*
|
|
1438
|
+
* The mover collision (moverCollision.ts) is a 1D arc-length test for two
|
|
1439
|
+
* movers on the *same* chain; its header explicitly declines cross-XPU /
|
|
1440
|
+
* cross-track cases, which "would need an OBB / mesh test in 3D space".
|
|
1441
|
+
* This file is exactly that test, for the physical track *modules*.
|
|
1429
1442
|
*
|
|
1430
|
-
*
|
|
1431
|
-
*
|
|
1432
|
-
*
|
|
1443
|
+
* Each module is modelled as a chain of oriented bounding boxes (OBBs) that
|
|
1444
|
+
* follow its real centerline: the path is sampled (sampleModulePath) and each
|
|
1445
|
+
* segment becomes a small box (±halfWidth in Y, 0..height in Z) oriented along
|
|
1446
|
+
* the local tangent, then transformed into world space by
|
|
1447
|
+
*
|
|
1448
|
+
* world = orientation ⊗ trackTransform ⊗ partTransformation ⊗ chainPose
|
|
1449
|
+
*
|
|
1450
|
+
* Sampling the path (rather than a single straight box per module) is what
|
|
1451
|
+
* keeps curves and U-turns faithful — a single straight box would poke
|
|
1452
|
+
* outside the arc and produce both false positives and false negatives on a
|
|
1453
|
+
* closed oval. Straights collapse to one box; curves / free paths use several.
|
|
1454
|
+
*
|
|
1455
|
+
* Pairs are tested with the Separating Axis Theorem (15 axes) over their
|
|
1456
|
+
* segment boxes, yielding a penetration depth (minimum translation distance)
|
|
1457
|
+
* as `overlapMm`:
|
|
1458
|
+
*
|
|
1459
|
+
* overlapMm > 0 — boxes overlap by this many mm (deepest sub-box pair).
|
|
1460
|
+
* overlapMm = 0 — touching exactly.
|
|
1461
|
+
* overlapMm < 0 — closest-approach gap (only reported when
|
|
1462
|
+
* `warningGapMm` is set).
|
|
1463
|
+
*
|
|
1464
|
+
* Adjacent modules in the same part butt up edge-to-edge (ChainBuilder), so
|
|
1465
|
+
* consecutive `(i, i+1)` pairs — and the `(N-1, 0)` seam of a closed loop —
|
|
1466
|
+
* are excluded; everything else (cross-part, cross-XPU, same-part
|
|
1467
|
+
* non-adjacent) is tested.
|
|
1468
|
+
*
|
|
1469
|
+
* The box height comes from the module's own GLB sidecar
|
|
1470
|
+
* (`moduleHeightSpan`, moduleCrossSection.ts): a motor module measures about
|
|
1471
|
+
* 40 mm and straddles the path plane. It used to be one coarse box per type,
|
|
1472
|
+
* `0 … 100` mm and entirely above the plane, which reported an overlap between
|
|
1473
|
+
* any two tracks stacked closer than 100 mm — the arrangement a doublesided
|
|
1474
|
+
* guide rail is built as. The half-width stays the coarse `halfWidthMm` 50,
|
|
1475
|
+
* because a curved module's sidecar box spans its whole arc in X and Y and is
|
|
1476
|
+
* a cross-section only in Z; moduleCrossSection.ts sets that out.
|
|
1477
|
+
*
|
|
1478
|
+
* The `warningGapMm` near-miss is an axis-projected SAT separation, not a true
|
|
1479
|
+
* Euclidean (GJK) distance — slightly conservative for corner-to-corner cases.
|
|
1433
1480
|
*/
|
|
1434
1481
|
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1482
|
+
/** Default box half-width (Y) — matches `approximateModuleBounds`. */
|
|
1483
|
+
declare const DEFAULT_MODULE_HALF_WIDTH_MM = 50;
|
|
1484
|
+
/** Default box height (Z) — matches `approximateModuleBounds`. */
|
|
1485
|
+
declare const DEFAULT_MODULE_HEIGHT_MM = 100;
|
|
1486
|
+
/**
|
|
1487
|
+
* One detected pair. Symmetric: `a`/`b` are canonicalised so the lower
|
|
1488
|
+
* `(processingUnitObjectId, partObjectId, moduleIndex)` tuple is always `a`.
|
|
1489
|
+
*/
|
|
1490
|
+
interface ModuleCollision {
|
|
1491
|
+
a: ModuleRef;
|
|
1492
|
+
b: ModuleRef;
|
|
1493
|
+
moduleTypeA: ModuleType3D;
|
|
1494
|
+
moduleTypeB: ModuleType3D;
|
|
1495
|
+
/** Forwarded `ModuleEntry.globalNumber` (status-store key), if present. */
|
|
1496
|
+
globalNumberA?: number;
|
|
1497
|
+
globalNumberB?: number;
|
|
1498
|
+
/**
|
|
1499
|
+
* Penetration depth along the minimum-translation axis of the deepest
|
|
1500
|
+
* sub-box pair:
|
|
1501
|
+
* • `> 0` — overlap (mm).
|
|
1502
|
+
* • `= 0` — touching.
|
|
1503
|
+
* • `< 0` — closest-approach gap (only when `warningGapMm` is set).
|
|
1504
|
+
*/
|
|
1505
|
+
overlapMm: number;
|
|
1506
|
+
/** Centre-to-centre distance of the deepest sub-box pair (mm). */
|
|
1507
|
+
centerDistanceMm: number;
|
|
1508
|
+
/** Unit world-space axis of least penetration (the MTV direction). */
|
|
1509
|
+
axis: Vec3;
|
|
1438
1510
|
}
|
|
1439
|
-
interface
|
|
1511
|
+
interface CheckModuleCollisionsOptions {
|
|
1512
|
+
/**
|
|
1513
|
+
* When `> 0`, near-misses with an axis-projected gap up to this many mm
|
|
1514
|
+
* are also returned (with `overlapMm < 0`). Default 0 — only actual
|
|
1515
|
+
* overlaps / contacts.
|
|
1516
|
+
*/
|
|
1517
|
+
warningGapMm?: number;
|
|
1518
|
+
/**
|
|
1519
|
+
* Also test non-adjacent module pairs within the SAME part. Default true.
|
|
1520
|
+
* Consecutive `(i, i+1)` pairs and the closed-loop `(N-1, 0)` seam are
|
|
1521
|
+
* ALWAYS excluded regardless of this flag.
|
|
1522
|
+
*/
|
|
1523
|
+
includeSamePart?: boolean;
|
|
1524
|
+
/** Box half-width in Y (mm). Default 50. */
|
|
1525
|
+
halfWidthMm?: number;
|
|
1526
|
+
/**
|
|
1527
|
+
* Force one box height in Z (mm), spanning `0 … heightMm` above the path
|
|
1528
|
+
* plane for every module type.
|
|
1529
|
+
*
|
|
1530
|
+
* Left unset — the default — each module is given the vertical extent its
|
|
1531
|
+
* GLB sidecar reports (`moduleHeightSpan`), which both is the real height
|
|
1532
|
+
* and sits on the real side of the path plane. Setting this restores the one
|
|
1533
|
+
* coarse box the check used before that, for a caller who wants a
|
|
1534
|
+
* deliberately conservative envelope.
|
|
1535
|
+
*/
|
|
1536
|
+
heightMm?: number;
|
|
1537
|
+
}
|
|
1538
|
+
/** Oriented bounding box in world space (unit axes + scaled half-extents). */
|
|
1539
|
+
interface Obb {
|
|
1540
|
+
center: Vector3;
|
|
1541
|
+
axes: [Vector3, Vector3, Vector3];
|
|
1542
|
+
halfExtents: [number, number, number];
|
|
1543
|
+
/** Bounding-sphere radius (broad-phase reject). */
|
|
1544
|
+
radius: number;
|
|
1545
|
+
}
|
|
1546
|
+
/** Pre-built per-module probe consumed by `checkModuleCollisions`. */
|
|
1547
|
+
interface ModuleProbe {
|
|
1548
|
+
ref: ModuleRef;
|
|
1440
1549
|
moduleType: ModuleType3D;
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
min: Vec3;
|
|
1452
|
-
max: Vec3;
|
|
1453
|
-
};
|
|
1550
|
+
globalNumber?: number;
|
|
1551
|
+
/** Adjacency grouping key (part.globalNumber — system-wide unique). */
|
|
1552
|
+
partKey: number;
|
|
1553
|
+
/** Index of this module within its part's chain. */
|
|
1554
|
+
moduleIndex: number;
|
|
1555
|
+
/** Segment boxes following the module centerline (≥ 1). */
|
|
1556
|
+
obbs: Obb[];
|
|
1557
|
+
/** Module-level bounding sphere enclosing all segment boxes. */
|
|
1558
|
+
sphereCenter: Vector3;
|
|
1559
|
+
sphereRadius: number;
|
|
1454
1560
|
}
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1561
|
+
/** A part fed to `buildModuleProbes`. */
|
|
1562
|
+
interface ModuleCollisionPartInput {
|
|
1563
|
+
/** ModuleRef.partObjectId (`PartConfig.objectId`). */
|
|
1564
|
+
partObjectId: number;
|
|
1565
|
+
/** Adjacency key (`PartConfig.globalNumber`). */
|
|
1566
|
+
partKey: number;
|
|
1567
|
+
partTransformation?: PartTransformation | null;
|
|
1568
|
+
chain: BuiltChain;
|
|
1461
1569
|
}
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
min: Vec3;
|
|
1469
|
-
max: Vec3;
|
|
1470
|
-
};
|
|
1471
|
-
label: string;
|
|
1570
|
+
/** An XPU fed to `buildModuleProbes`. */
|
|
1571
|
+
interface ModuleCollisionXpuInput {
|
|
1572
|
+
xpuObjectId: number;
|
|
1573
|
+
/** Effective (override-resolved) per-XPU track transform. */
|
|
1574
|
+
trackTransform?: TrackTransform | null;
|
|
1575
|
+
parts: ReadonlyArray<ModuleCollisionPartInput>;
|
|
1472
1576
|
}
|
|
1473
1577
|
/**
|
|
1474
|
-
*
|
|
1475
|
-
*
|
|
1476
|
-
*
|
|
1578
|
+
* Turn a list of XPUs (with effective track transforms) + parts (with built
|
|
1579
|
+
* chains) into flat module probes plus a per-part adjacency map. Shared by
|
|
1580
|
+
* the imperative ref and the reactive monitor so the world-matrix
|
|
1581
|
+
* composition lives in one place.
|
|
1582
|
+
*
|
|
1583
|
+
* Hidden XPUs / parts (`visible === false`) are skipped to match the scene.
|
|
1477
1584
|
*/
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
};
|
|
1486
|
-
}
|
|
1487
|
-
declare const DEFAULT_ORIGIN_CORRECTION: OriginCorrection;
|
|
1585
|
+
declare function buildModuleProbes(orientation: Orientation | undefined, xpus: ReadonlyArray<ModuleCollisionXpuInput>, opts?: CheckModuleCollisionsOptions): {
|
|
1586
|
+
probes: ModuleProbe[];
|
|
1587
|
+
adjacency: Map<number, {
|
|
1588
|
+
count: number;
|
|
1589
|
+
closed: boolean;
|
|
1590
|
+
}>;
|
|
1591
|
+
};
|
|
1488
1592
|
/**
|
|
1489
|
-
*
|
|
1490
|
-
*
|
|
1491
|
-
*
|
|
1593
|
+
* Pairwise OBB overlap test over all module probes. Same-part consecutive
|
|
1594
|
+
* and closed-loop-seam pairs are excluded; everything else is tested.
|
|
1595
|
+
* Returns one entry per overlapping (or near-miss, when `warningGapMm > 0`)
|
|
1596
|
+
* pair, sorted deepest-overlap-first.
|
|
1492
1597
|
*/
|
|
1493
|
-
declare
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
clear(): void;
|
|
1498
|
-
/**
|
|
1499
|
-
* Fetch and cache the sidecar at `url`. The loader validates the one field
|
|
1500
|
-
* every sidecar shape shares (`originCorrection`) and hands the rest to the
|
|
1501
|
-
* caller as `T` — the kind is a property of the URL, not of the loader.
|
|
1502
|
-
*/
|
|
1503
|
-
fetch<T>(url: string): Promise<T | undefined>;
|
|
1504
|
-
}
|
|
1505
|
-
/**
|
|
1506
|
-
* Resolve the effective origin correction for a module sidecar — falls back
|
|
1507
|
-
* to identity when the sidecar is absent.
|
|
1508
|
-
*/
|
|
1509
|
-
declare function resolveOriginCorrection(sidecar: {
|
|
1510
|
-
originCorrection?: OriginCorrection;
|
|
1511
|
-
} | undefined): OriginCorrection;
|
|
1512
|
-
/** Standard sidecar filename from a base id. */
|
|
1513
|
-
declare function moduleSidecarFilename(moduleType: ModuleType3D): string;
|
|
1514
|
-
declare function moverSidecarFilename(moverType: MoverType3D): string;
|
|
1515
|
-
declare function toolSidecarFilename(toolType: MoverToolType3D): string;
|
|
1516
|
-
declare function railSidecarFilename(railType: RailType3D): string;
|
|
1517
|
-
/** Anything that names a GLB per rail system — a sidecar or a manifest entry. */
|
|
1518
|
-
interface GlbByRailSystem {
|
|
1519
|
-
glbByRailSystem: {
|
|
1520
|
-
Beckhoff: string | null;
|
|
1521
|
-
HepcoGfx?: string | null;
|
|
1522
|
-
};
|
|
1523
|
-
}
|
|
1598
|
+
declare function checkModuleCollisions(probes: ReadonlyArray<ModuleProbe>, adjacency: ReadonlyMap<number, {
|
|
1599
|
+
count: number;
|
|
1600
|
+
closed: boolean;
|
|
1601
|
+
}>, opts?: CheckModuleCollisionsOptions): ModuleCollision[];
|
|
1524
1602
|
/**
|
|
1525
|
-
*
|
|
1526
|
-
*
|
|
1603
|
+
* Resolve a normalized config into the per-XPU inputs `buildModuleProbes`
|
|
1604
|
+
* takes, applying the same override precedence the scene uses: an imperative
|
|
1605
|
+
* `trackTransformOverrides` entry beats the config value.
|
|
1527
1606
|
*
|
|
1528
|
-
*
|
|
1529
|
-
*
|
|
1530
|
-
* of
|
|
1531
|
-
*
|
|
1607
|
+
* This adapter was written out twice, character for character — once in
|
|
1608
|
+
* `<ModuleCollisionMonitor>` and once inside the `checkModuleCollisions`
|
|
1609
|
+
* closure of `useXtsViewerHandle` — with the only difference being where the
|
|
1610
|
+
* orientation came from.
|
|
1611
|
+
*
|
|
1612
|
+
* A part whose chain cannot be built is skipped, as before, but no longer in
|
|
1613
|
+
* silence: `normalizeXtsConfig` has already made that impossible, so if it
|
|
1614
|
+
* ever happens it means a part has silently dropped out of collision
|
|
1615
|
+
* detection, which is a safety feature. Reported rather than thrown, because
|
|
1616
|
+
* both callers run inside React and a throw would take the scene down.
|
|
1532
1617
|
*/
|
|
1533
|
-
declare function
|
|
1618
|
+
declare function toModuleCollisionInputs(config: NormalizedXtsConfig, trackTransformOverrides?: Record<number, TrackTransform>): ModuleCollisionXpuInput[];
|
|
1619
|
+
/** `toModuleCollisionInputs` → `buildModuleProbes` → `checkModuleCollisions`. */
|
|
1620
|
+
declare function checkAllModuleCollisions(config: NormalizedXtsConfig, orientation: Orientation | undefined, trackTransformOverrides?: Record<number, TrackTransform>, opts?: CheckModuleCollisionsOptions): ModuleCollision[];
|
|
1534
1621
|
|
|
1535
1622
|
/**
|
|
1536
|
-
*
|
|
1537
|
-
* internal `<XtsScene>` / `<ProcessingUnitRoot>` props.
|
|
1623
|
+
* Interaction surface — selection, highlighting and the camera.
|
|
1538
1624
|
*
|
|
1539
|
-
*
|
|
1540
|
-
*
|
|
1541
|
-
*
|
|
1542
|
-
*
|
|
1625
|
+
* The types a host exchanges with the viewer at runtime rather than at
|
|
1626
|
+
* configuration time: what is addressed (`ModuleRef`, `MoverRef`), what is
|
|
1627
|
+
* picked out (`SelectionState`, `ModuleHighlight`, `FeedSegmentHighlight`),
|
|
1628
|
+
* and where the camera looks (`CameraState`, `FocusTarget`).
|
|
1543
1629
|
*/
|
|
1544
1630
|
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
/** Default `'bottom-right'`. */
|
|
1550
|
-
alignment?: ViewCubeAlignment;
|
|
1551
|
-
/** Distance from the aligned corner, in CSS px. Default `[80, 80]`. */
|
|
1552
|
-
marginPx?: [number, number];
|
|
1631
|
+
interface ModuleRef {
|
|
1632
|
+
processingUnitObjectId: number;
|
|
1633
|
+
partObjectId: number;
|
|
1634
|
+
moduleIndex: number;
|
|
1553
1635
|
}
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
pan?: boolean;
|
|
1558
|
-
zoom?: boolean;
|
|
1559
|
-
selection?: boolean;
|
|
1636
|
+
interface MoverRef {
|
|
1637
|
+
processingUnitObjectId: number;
|
|
1638
|
+
moverIndex: number;
|
|
1560
1639
|
}
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
* (camera move, mover position update, selection). Saves >90% GPU time
|
|
1580
|
-
* when the scene is idle. Default: true.
|
|
1581
|
-
*/
|
|
1582
|
-
demandRendering?: boolean;
|
|
1583
|
-
/**
|
|
1584
|
-
* Cap the device pixel ratio. Values above 2 rarely improve perceptible
|
|
1585
|
-
* quality but double fill-rate cost on Retina/4K displays. Default: 2.
|
|
1586
|
-
*/
|
|
1587
|
-
maxDpr?: number;
|
|
1588
|
-
/**
|
|
1589
|
-
* Disable automatic per-frame shadow map updates. Shadows only re-render
|
|
1590
|
-
* on camera change. Default: true (when shadows enabled).
|
|
1591
|
-
*/
|
|
1592
|
-
shadowOnDemand?: boolean;
|
|
1640
|
+
/**
|
|
1641
|
+
* Addresses a whole track — one processing unit with everything anchored to
|
|
1642
|
+
* it: its modules, movers, tools and overlays.
|
|
1643
|
+
*/
|
|
1644
|
+
interface TrackRef {
|
|
1645
|
+
processingUnitObjectId: number;
|
|
1646
|
+
}
|
|
1647
|
+
/**
|
|
1648
|
+
* Addresses one entry of `XtsConfig.customAssets` by its `id`. Only assets
|
|
1649
|
+
* with a `'static'` binding are pickable — a mover-bound asset rides its
|
|
1650
|
+
* mover and is selected by selecting that mover.
|
|
1651
|
+
*/
|
|
1652
|
+
interface CustomAssetRef {
|
|
1653
|
+
assetId: string;
|
|
1654
|
+
}
|
|
1655
|
+
interface SelectionState {
|
|
1656
|
+
modules: ModuleRef[];
|
|
1657
|
+
movers: MoverRef[];
|
|
1593
1658
|
/**
|
|
1594
|
-
*
|
|
1595
|
-
*
|
|
1659
|
+
* Selected whole tracks — what a click produces under
|
|
1660
|
+
* `selectionScope: 'track'`. A selected track counts as selecting
|
|
1661
|
+
* everything on it: `isModuleSelected` and `isMoverSelected` both answer
|
|
1662
|
+
* `true` for its contents, so the whole track tints as one.
|
|
1596
1663
|
*/
|
|
1597
|
-
|
|
1664
|
+
tracks?: TrackRef[];
|
|
1598
1665
|
/**
|
|
1599
|
-
*
|
|
1600
|
-
*
|
|
1601
|
-
* three.js revision. Point this at a self-hosted
|
|
1602
|
-
* `basis_transcoder.{js,wasm}` directory for offline / air-gapped use.
|
|
1666
|
+
* Selected static custom assets. Optional so a host that only ever
|
|
1667
|
+
* selects modules and movers keeps building the object it always did.
|
|
1603
1668
|
*/
|
|
1604
|
-
|
|
1669
|
+
customAssets?: CustomAssetRef[];
|
|
1605
1670
|
}
|
|
1606
|
-
/**
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
interface SidecarOverrides {
|
|
1612
|
-
modules?: Partial<Record<ModuleType3D, OriginCorrection>>;
|
|
1613
|
-
movers?: Partial<Record<MoverType3D, OriginCorrection>>;
|
|
1614
|
-
tools?: Partial<Record<MoverToolType3D, OriginCorrection>>;
|
|
1615
|
-
rails?: Partial<Record<RailType3D, OriginCorrection>>;
|
|
1671
|
+
/** Per-entity selection highlight colours. */
|
|
1672
|
+
interface SelectionColors {
|
|
1673
|
+
module?: string;
|
|
1674
|
+
mover?: string;
|
|
1675
|
+
customAsset?: string;
|
|
1616
1676
|
}
|
|
1617
|
-
|
|
1677
|
+
type SelectionMode = 'Off' | 'Single' | 'Multi';
|
|
1618
1678
|
/**
|
|
1619
|
-
*
|
|
1679
|
+
* What a click into the track picks out.
|
|
1620
1680
|
*
|
|
1621
|
-
*
|
|
1622
|
-
*
|
|
1623
|
-
*
|
|
1624
|
-
*
|
|
1681
|
+
* - `'module'` (default) — the module or mover clicked, as before.
|
|
1682
|
+
* - `'track'` — the whole processing unit it belongs to, whichever of its
|
|
1683
|
+
* parts was hit. Individual modules and movers cannot be selected in
|
|
1684
|
+
* this scope at all.
|
|
1625
1685
|
*
|
|
1626
|
-
*
|
|
1627
|
-
*
|
|
1686
|
+
* A static custom asset is always selected in its own right: it stands free
|
|
1687
|
+
* in the scene rather than on a track.
|
|
1628
1688
|
*
|
|
1629
|
-
* This
|
|
1630
|
-
*
|
|
1689
|
+
* This is what the transform gizmo follows. A module has no pose of its own
|
|
1690
|
+
* — modules bolt together end-to-end along the chain — so a module
|
|
1691
|
+
* selection never grows handles; a track selection does.
|
|
1631
1692
|
*/
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
/**
|
|
1646
|
-
|
|
1647
|
-
* Computed as R = L / |θ_rad|; equals 636.6198 for ±22.5° and 318.3099 for
|
|
1648
|
-
* ±45° curves at L=250.
|
|
1649
|
-
*/
|
|
1650
|
-
centerlineRadiusMm: number | null;
|
|
1651
|
-
/**
|
|
1652
|
-
* Free-path point cloud reference. Only set for `pathType==='Free'`.
|
|
1653
|
-
* The string identifies the cloud by name; the actual data lives in
|
|
1654
|
-
* geometry/PointClouds.ts.
|
|
1655
|
-
*/
|
|
1656
|
-
freePathId?: string;
|
|
1657
|
-
/** True if STP is not delivered yet. */
|
|
1658
|
-
prepared?: boolean;
|
|
1659
|
-
/** True for Einspeisemodule (power feed-in variants). */
|
|
1660
|
-
isInfeed?: boolean;
|
|
1693
|
+
type SelectionScope = 'module' | 'track';
|
|
1694
|
+
/**
|
|
1695
|
+
* Declarative module highlight. All modules whose `moduleType` appears in
|
|
1696
|
+
* `moduleTypes` are tinted with the given `color` — same emissive effect as
|
|
1697
|
+
* selection, but independent of the selection state. Multiple highlights can
|
|
1698
|
+
* overlap; the first matching entry wins.
|
|
1699
|
+
*
|
|
1700
|
+
* Toggle on/off by setting `moduleHighlights` on `<XtsViewer3D>` to an
|
|
1701
|
+
* array (enabled) or `undefined` / `[]` (disabled).
|
|
1702
|
+
*/
|
|
1703
|
+
interface ModuleHighlight {
|
|
1704
|
+
/** Module types to highlight. */
|
|
1705
|
+
moduleTypes: ModuleType3D[];
|
|
1706
|
+
/** Highlight color (CSS color string, e.g. `'#FF6600'`). */
|
|
1707
|
+
color: string;
|
|
1661
1708
|
}
|
|
1662
|
-
declare const MODULE_CATALOG: Readonly<Record<ModuleType3D, ModuleCatalogEntry>>;
|
|
1663
1709
|
/**
|
|
1664
|
-
*
|
|
1665
|
-
*
|
|
1710
|
+
* Addresses one feed segment (Einspeisestrang). Either form works — pick
|
|
1711
|
+
* whichever the host has at hand:
|
|
1666
1712
|
*
|
|
1667
|
-
*
|
|
1668
|
-
*
|
|
1669
|
-
*
|
|
1713
|
+
* - `{ infeedModuleGlobalNumber }` — the `ModuleEntry.globalNumber` of the
|
|
1714
|
+
* infeed module that leads the segment. Stable across layout edits as long
|
|
1715
|
+
* as the global numbering is stable.
|
|
1716
|
+
* - `{ partObjectId, segmentIndex }` — ordinal address: `segmentIndex` counts
|
|
1717
|
+
* 0, 1, 2 … in chain order, starting at the part's first infeed module.
|
|
1670
1718
|
*/
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1719
|
+
type FeedSegmentSelector = {
|
|
1720
|
+
infeedModuleGlobalNumber: number;
|
|
1721
|
+
} | {
|
|
1722
|
+
partObjectId: number;
|
|
1723
|
+
segmentIndex: number;
|
|
1724
|
+
};
|
|
1677
1725
|
/**
|
|
1678
|
-
*
|
|
1726
|
+
* Declarative feed-segment highlight. Every module belonging to one of the
|
|
1727
|
+
* selected segments is tinted with `color` — same emissive effect as
|
|
1728
|
+
* selection, but independent of the selection state.
|
|
1679
1729
|
*
|
|
1680
|
-
*
|
|
1681
|
-
*
|
|
1682
|
-
*
|
|
1683
|
-
* module_i.startWorldMatrix = chain_world
|
|
1684
|
-
* module_i.startPositionInPartMm = cumulative
|
|
1685
|
-
* module_i.trackLengthMm = module_i.moduleLengthMm
|
|
1686
|
-
* cumulative += trackLengthMm
|
|
1687
|
-
* end_local = T( ΔX, -ΔY_canvas, 0 ) ⊗ R_z( -θ )
|
|
1688
|
-
* chain_world = chain_world ⊗ end_local
|
|
1730
|
+
* Multiple entries are allowed (that is how several segments get several
|
|
1731
|
+
* colors at once); on overlap the first matching entry wins, mirroring
|
|
1732
|
+
* `ModuleHighlight`.
|
|
1689
1733
|
*
|
|
1690
|
-
*
|
|
1691
|
-
*
|
|
1692
|
-
* Three.js. A Three.js helper in PathBuilder.ts converts to Matrix4 on demand.
|
|
1734
|
+
* Toggle on/off by setting `feedSegmentHighlights` on `<XtsViewer3D>` to an
|
|
1735
|
+
* array (enabled) or `undefined` / `[]` (disabled).
|
|
1693
1736
|
*/
|
|
1694
|
-
|
|
1695
|
-
/**
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1737
|
+
interface FeedSegmentHighlight {
|
|
1738
|
+
/** Segments to tint. */
|
|
1739
|
+
segments: FeedSegmentSelector[];
|
|
1740
|
+
/** Highlight color (CSS color string, e.g. `'#00AAFF'`). */
|
|
1741
|
+
color: string;
|
|
1699
1742
|
}
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1743
|
+
/**
|
|
1744
|
+
* One feed segment (Einspeisestrang) as detected in the configuration: the
|
|
1745
|
+
* contiguous run of modules starting at an infeed module (Einspeisemodul,
|
|
1746
|
+
* `ModuleCatalogEntry.isInfeed`) and ending right before the next infeed
|
|
1747
|
+
* module of the same part.
|
|
1748
|
+
*
|
|
1749
|
+
* On a closed chain (oval, U-turn pair — see `isChainClosed`) the modules
|
|
1750
|
+
* ahead of the part's first infeed module belong to the LAST segment, which
|
|
1751
|
+
* then carries `wrapsSeam: true`. On an open chain those leading modules
|
|
1752
|
+
* belong to no segment at all and cannot be tinted.
|
|
1753
|
+
*
|
|
1754
|
+
* Returned by `XtsViewer3DRef.getFeedSegments()`.
|
|
1755
|
+
*/
|
|
1756
|
+
interface FeedSegment {
|
|
1757
|
+
processingUnitObjectId: number;
|
|
1758
|
+
partObjectId: number;
|
|
1759
|
+
/** 0-based, in chain order, counted from the part's first infeed module. */
|
|
1760
|
+
segmentIndex: number;
|
|
1761
|
+
/**
|
|
1762
|
+
* `ModuleEntry.globalNumber` of the leading infeed module. `undefined` when
|
|
1763
|
+
* the config does not set it — such a segment can only be addressed via
|
|
1764
|
+
* `{ partObjectId, segmentIndex }`.
|
|
1765
|
+
*/
|
|
1766
|
+
infeedModuleGlobalNumber?: number;
|
|
1767
|
+
/** Module type of the leading infeed module. */
|
|
1768
|
+
infeedModuleType: ModuleType3D;
|
|
1769
|
+
/** Chain indices of every module in this segment, in chain order. */
|
|
1770
|
+
moduleIndices: number[];
|
|
1771
|
+
/** Σ of the segment's module lengths, in mm. */
|
|
1772
|
+
lengthMm: number;
|
|
1773
|
+
/** True when the segment wraps the closed-loop seam. */
|
|
1774
|
+
wrapsSeam: boolean;
|
|
1775
|
+
}
|
|
1776
|
+
interface CameraState {
|
|
1777
|
+
positionMm: Vec3;
|
|
1778
|
+
targetMm: Vec3;
|
|
1779
|
+
/** Always 'Z' in the current spec. */
|
|
1780
|
+
upAxis: 'Z' | 'Y';
|
|
1781
|
+
zoom?: number;
|
|
1782
|
+
}
|
|
1783
|
+
/**
|
|
1784
|
+
* Live camera projection.
|
|
1785
|
+
* - `'perspective'` (default) — the regular 3D view.
|
|
1786
|
+
* - `'orthographic'` — gives the flat 2D plan view
|
|
1787
|
+
* that matches `exportScreenshot({ mode: 'top-down' })`.
|
|
1788
|
+
*/
|
|
1789
|
+
type CameraProjection = 'perspective' | 'orthographic';
|
|
1790
|
+
/**
|
|
1791
|
+
* Target for the imperative `viewerRef.current.focusOn(target, opts)` camera
|
|
1792
|
+
* animation. The viewer computes the target's world-space bounding box and
|
|
1793
|
+
* flies the camera so the whole object fits the frame:
|
|
1794
|
+
* - In `'perspective'` projection the current view angle is preserved
|
|
1795
|
+
* (the camera only dollies / re-centres).
|
|
1796
|
+
* - In `'orthographic'` top-down the frustum is re-framed and the camera
|
|
1797
|
+
* pans straight over the object.
|
|
1798
|
+
*/
|
|
1799
|
+
type FocusTarget = {
|
|
1800
|
+
kind: 'scene';
|
|
1801
|
+
} | {
|
|
1802
|
+
kind: 'station';
|
|
1803
|
+
stationId: number;
|
|
1804
|
+
} | {
|
|
1805
|
+
kind: 'area';
|
|
1806
|
+
areaId: number;
|
|
1807
|
+
} | {
|
|
1808
|
+
kind: 'mover';
|
|
1809
|
+
ref: MoverRef;
|
|
1810
|
+
} | {
|
|
1811
|
+
kind: 'module';
|
|
1812
|
+
ref: ModuleRef;
|
|
1813
|
+
};
|
|
1814
|
+
interface FocusOptions {
|
|
1815
|
+
/** Animation duration in ms. Default 700. `0` jumps immediately. */
|
|
1816
|
+
durationMs?: number;
|
|
1817
|
+
/**
|
|
1818
|
+
* Padding around the target's bounding box (1 = tight fit). Default 1.2 in
|
|
1819
|
+
* perspective, 1.1 in orthographic top-down.
|
|
1820
|
+
*/
|
|
1821
|
+
paddingFactor?: number;
|
|
1822
|
+
/** Easing curve. Default `'easeInOutCubic'`. */
|
|
1823
|
+
easing?: 'linear' | 'easeInOutCubic';
|
|
1824
|
+
}
|
|
1825
|
+
/**
|
|
1826
|
+
* Collision monitoring.
|
|
1827
|
+
*
|
|
1828
|
+
* Movers and modules are watched by different machinery — movers move at
|
|
1829
|
+
* 60 Hz through the position store and are checked in `useFrame`, modules
|
|
1830
|
+
* only move when the configuration or a `trackTransform` changes and are
|
|
1831
|
+
* checked in an effect — but from the outside they configure the same way,
|
|
1832
|
+
* which is why they now sit under one prop.
|
|
1833
|
+
*/
|
|
1834
|
+
interface CollisionOptions {
|
|
1835
|
+
/** Continuous mover collision monitoring. */
|
|
1836
|
+
movers?: MoverCollisionOptions;
|
|
1837
|
+
/**
|
|
1838
|
+
* Static MODULE overlap monitoring. Detects track modules that physically
|
|
1839
|
+
* overlap in 3D space — different XPUs / parts placed via `trackTransform`
|
|
1840
|
+
* / `orientation` / `partTransformation`, plus non-adjacent modules of the
|
|
1841
|
+
* same part. Adjacent (seam) modules and the closed-loop seam are
|
|
1842
|
+
* excluded. Recomputed on config / track-transform change (NOT per-frame,
|
|
1843
|
+
* since modules don't move at 60 Hz) — `onCollisionsChange` fires only
|
|
1844
|
+
* when the overlap set actually changes.
|
|
1845
|
+
*/
|
|
1846
|
+
modules?: ModuleCollisionOptions;
|
|
1847
|
+
}
|
|
1848
|
+
interface MoverCollisionOptions {
|
|
1849
|
+
enabled?: boolean;
|
|
1850
|
+
/** Report a pair as colliding while their gap is below this. Default 0. */
|
|
1851
|
+
warningGapMm?: number;
|
|
1852
|
+
/** Throttle the check (e.g. 50 for 20 Hz). Default 0 = every frame. */
|
|
1853
|
+
intervalMs?: number;
|
|
1854
|
+
/** Treat the part as a closed loop regardless of chain geometry. */
|
|
1855
|
+
forceClosedLoop?: boolean;
|
|
1856
|
+
onCollisionsChange?: (collisions: MoverCollision[]) => void;
|
|
1857
|
+
}
|
|
1858
|
+
interface ModuleCollisionOptions {
|
|
1859
|
+
enabled?: boolean;
|
|
1860
|
+
/** Report a pair as overlapping while their gap is below this. Default 0. */
|
|
1861
|
+
warningGapMm?: number;
|
|
1862
|
+
onCollisionsChange?: (collisions: ModuleCollision[]) => void;
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
/**
|
|
1866
|
+
* Viewer errors.
|
|
1867
|
+
*
|
|
1868
|
+
* `XtsViewerErrorException` is the one runtime value in the public type
|
|
1869
|
+
* surface — everything else the viewer exports as `types` is erased at
|
|
1870
|
+
* compile time. It is thrown by the normalization pass for configurations
|
|
1871
|
+
* that cannot be rendered at all; recoverable problems are reported through
|
|
1872
|
+
* the `onError` callback as plain `XtsViewerError` records instead.
|
|
1873
|
+
*/
|
|
1874
|
+
type XtsViewerErrorCode = 'asset-load-failed' | 'unknown-module-type' | 'unknown-mover-type' | 'unknown-tool-type' | 'invalid-config' | 'unmatched-clothoid-half' | 'webgl-context-lost';
|
|
1875
|
+
interface XtsViewerError {
|
|
1876
|
+
code: XtsViewerErrorCode;
|
|
1877
|
+
message: string;
|
|
1878
|
+
details?: unknown;
|
|
1879
|
+
}
|
|
1880
|
+
declare class XtsViewerErrorException extends Error {
|
|
1881
|
+
readonly code: XtsViewerErrorCode;
|
|
1882
|
+
readonly details?: unknown;
|
|
1883
|
+
constructor(code: XtsViewerErrorCode, message: string, details?: unknown);
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
/**
|
|
1887
|
+
* Asset manifest — per-type overrides for the GLB URLs the viewer loads.
|
|
1888
|
+
*
|
|
1889
|
+
* A host registers custom or relocated assets here instead of replacing the
|
|
1890
|
+
* whole `assetsBaseUrl`. Entries fall back to the default CDN mapping for any
|
|
1891
|
+
* type they do not name.
|
|
1892
|
+
*/
|
|
1893
|
+
|
|
1894
|
+
interface AssetManifest {
|
|
1895
|
+
/** Override per moduleType. Falls back to default mapping otherwise. */
|
|
1896
|
+
modules?: Partial<Record<ModuleType3D, ModuleAssetEntry>>;
|
|
1897
|
+
movers?: Partial<Record<MoverType3D, MoverAssetEntry>>;
|
|
1898
|
+
tools?: Partial<Record<MoverToolType3D, MoverToolAssetEntry>>;
|
|
1899
|
+
magnetPlates?: Partial<Record<MagnetPlateType3D, MagnetPlateAssetEntry>>;
|
|
1900
|
+
guidingRails?: Partial<Record<RailType3D, RailAssetEntry>>;
|
|
1901
|
+
}
|
|
1902
|
+
interface RailAssetEntry {
|
|
1903
|
+
glbUrl: string;
|
|
1904
|
+
sidecarUrl?: string;
|
|
1905
|
+
}
|
|
1906
|
+
interface ModuleAssetEntry {
|
|
1907
|
+
/** Per-RailSystem GLB URL (relative to assetsBaseUrl). */
|
|
1908
|
+
glbByRailSystem: {
|
|
1909
|
+
Beckhoff: string | null;
|
|
1910
|
+
HepcoGfx?: string | null;
|
|
1718
1911
|
};
|
|
1912
|
+
sidecarUrl?: string;
|
|
1719
1913
|
}
|
|
1720
|
-
interface
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1914
|
+
interface MoverAssetEntry {
|
|
1915
|
+
glbUrl: string;
|
|
1916
|
+
sidecarUrl?: string;
|
|
1917
|
+
}
|
|
1918
|
+
interface MoverToolAssetEntry {
|
|
1919
|
+
glbUrl: string;
|
|
1920
|
+
sidecarUrl?: string;
|
|
1921
|
+
}
|
|
1922
|
+
interface MagnetPlateAssetEntry {
|
|
1923
|
+
glbUrl: string;
|
|
1924
|
+
sidecarUrl?: string;
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
/**
|
|
1928
|
+
* Public types for the measurement + annotation feature.
|
|
1929
|
+
*
|
|
1930
|
+
* Everything here is expressed in the viewer's WORLD frame — millimetres,
|
|
1931
|
+
* Z-up, identical to `XtsViewer3DRef.getMoverWorldTransform()` and
|
|
1932
|
+
* `CameraState.positionMm`. That is what makes one implementation serve both
|
|
1933
|
+
* the perspective 3D view and the orthographic 2D plan view
|
|
1934
|
+
* (`projection='orthographic'`): only the camera differs, the geometry does
|
|
1935
|
+
* not.
|
|
1936
|
+
*/
|
|
1937
|
+
|
|
1938
|
+
/**
|
|
1939
|
+
* How a measurement point was derived. `'free'` is the raw ray hit (surface
|
|
1940
|
+
* or ground plane); everything else is a snap the picker resolved to.
|
|
1941
|
+
*/
|
|
1942
|
+
type SnapKind = 'free' | 'vertex' | 'edge-midpoint' | 'face-center' | 'grid' | 'mover-center' | 'module-boundary' | 'module-center' | 'station-stop' | 'track-path';
|
|
1943
|
+
/** Scene entity a picked point belongs to, when the picker could resolve one. */
|
|
1944
|
+
type MeasuredEntityRef = {
|
|
1945
|
+
kind: 'module';
|
|
1946
|
+
ref: ModuleRef;
|
|
1947
|
+
} | {
|
|
1948
|
+
kind: 'mover';
|
|
1949
|
+
ref: MoverRef;
|
|
1950
|
+
} | {
|
|
1951
|
+
kind: 'station';
|
|
1952
|
+
stationId: number;
|
|
1953
|
+
};
|
|
1954
|
+
/** A position on a part's track path, in the XPU's user position frame. */
|
|
1955
|
+
interface TrackAnchor {
|
|
1956
|
+
processingUnitObjectId: number;
|
|
1957
|
+
/** `PartConfig.objectId`. */
|
|
1958
|
+
partObjectId: number;
|
|
1959
|
+
/** Position along the part, user frame (same convention as movers). */
|
|
1960
|
+
partPositionMm: number;
|
|
1961
|
+
}
|
|
1962
|
+
/** One measurement vertex. */
|
|
1963
|
+
interface MeasurementPoint {
|
|
1964
|
+
/** World position (mm). */
|
|
1965
|
+
positionMm: Vec3;
|
|
1966
|
+
/** How the point was obtained. Default `'free'` when omitted. */
|
|
1967
|
+
snap?: SnapKind;
|
|
1968
|
+
/** Entity the point sits on, when known. */
|
|
1969
|
+
entity?: MeasuredEntityRef;
|
|
1970
|
+
/**
|
|
1971
|
+
* Track position, set whenever the point resolved onto a part's path.
|
|
1972
|
+
* Required on both endpoints of a `'trackDistance'` measurement.
|
|
1973
|
+
*/
|
|
1974
|
+
track?: TrackAnchor;
|
|
1975
|
+
}
|
|
1976
|
+
/** Snapping configuration. */
|
|
1977
|
+
interface SnapOptions {
|
|
1978
|
+
/** Master switch. Default `true`. */
|
|
1979
|
+
enabled?: boolean;
|
|
1980
|
+
/**
|
|
1981
|
+
* Snap kinds to consider, highest priority first. Default:
|
|
1982
|
+
* `['vertex', 'edge-midpoint', 'mover-center', 'module-boundary',
|
|
1983
|
+
* 'station-stop', 'track-path', 'face-center']`.
|
|
1984
|
+
*/
|
|
1985
|
+
kinds?: SnapKind[];
|
|
1986
|
+
/** Screen-space radius (px) a candidate must fall within. Default `14`. */
|
|
1987
|
+
radiusPx?: number;
|
|
1988
|
+
/**
|
|
1989
|
+
* Grid step (mm) for the `'grid'` snap, applied to the free hit point when
|
|
1990
|
+
* no other candidate matched and `'grid'` is in `kinds`. Default `0` (off).
|
|
1991
|
+
*/
|
|
1992
|
+
gridMm?: number;
|
|
1993
|
+
}
|
|
1994
|
+
type MeasurementId = string;
|
|
1995
|
+
type MeasurementKind = 'distance' | 'angle' | 'path' | 'trackDistance';
|
|
1996
|
+
/** Axis constraint for a distance measurement. */
|
|
1997
|
+
type MeasurementAxis = 'free' | 'x' | 'y' | 'z';
|
|
1998
|
+
interface MeasurementBase {
|
|
1999
|
+
id: MeasurementId;
|
|
2000
|
+
/** Optional caption rendered above the value. */
|
|
2001
|
+
label?: string;
|
|
2002
|
+
/** Overrides `MeasurementStyle.color` for this one measurement. */
|
|
2003
|
+
color?: string;
|
|
2004
|
+
/** Default `true`. */
|
|
2005
|
+
visible?: boolean;
|
|
2006
|
+
/** Free-form payload the host can round-trip (never read by the viewer). */
|
|
2007
|
+
meta?: Record<string, unknown>;
|
|
2008
|
+
}
|
|
2009
|
+
/** Straight-line distance between two points. */
|
|
2010
|
+
interface DistanceMeasurement extends MeasurementBase {
|
|
2011
|
+
kind: 'distance';
|
|
2012
|
+
points: [MeasurementPoint, MeasurementPoint];
|
|
2013
|
+
/** Constrain the reported value to one axis. Default `'free'`. */
|
|
2014
|
+
axis?: MeasurementAxis;
|
|
2015
|
+
/** Render dashed ΔX / ΔY / ΔZ guides. Default `false`. */
|
|
2016
|
+
showComponents?: boolean;
|
|
2017
|
+
}
|
|
2018
|
+
/** Angle at `points[1]` between the legs to `points[0]` and `points[2]`. */
|
|
2019
|
+
interface AngleMeasurement extends MeasurementBase {
|
|
2020
|
+
kind: 'angle';
|
|
2021
|
+
/** `[legA, vertex, legB]` — the middle point is the vertex. */
|
|
2022
|
+
points: [MeasurementPoint, MeasurementPoint, MeasurementPoint];
|
|
2023
|
+
}
|
|
2024
|
+
/** Polyline through N points; reports per-segment and total length. */
|
|
2025
|
+
interface PathMeasurement extends MeasurementBase {
|
|
2026
|
+
kind: 'path';
|
|
2027
|
+
points: MeasurementPoint[];
|
|
2028
|
+
/** Close the polyline back to the first point. Default `false`. */
|
|
2029
|
+
closed?: boolean;
|
|
2030
|
+
/** Label every segment, not just the total. Default `true`. */
|
|
2031
|
+
showSegmentLengths?: boolean;
|
|
2032
|
+
}
|
|
2033
|
+
/**
|
|
2034
|
+
* Distance ALONG the XTS track between two points on the same part — the
|
|
2035
|
+
* distance a mover actually travels, not the straight line.
|
|
2036
|
+
*/
|
|
2037
|
+
interface TrackDistanceMeasurement extends MeasurementBase {
|
|
2038
|
+
kind: 'trackDistance';
|
|
2039
|
+
points: [MeasurementPoint, MeasurementPoint];
|
|
2040
|
+
}
|
|
2041
|
+
type Measurement = DistanceMeasurement | AngleMeasurement | PathMeasurement | TrackDistanceMeasurement;
|
|
2042
|
+
/** Same as `Measurement`, but the store generates the `id` when omitted. */
|
|
2043
|
+
type MeasurementInput = (Omit<DistanceMeasurement, 'id'> & {
|
|
2044
|
+
id?: MeasurementId;
|
|
2045
|
+
}) | (Omit<AngleMeasurement, 'id'> & {
|
|
2046
|
+
id?: MeasurementId;
|
|
2047
|
+
}) | (Omit<PathMeasurement, 'id'> & {
|
|
2048
|
+
id?: MeasurementId;
|
|
2049
|
+
}) | (Omit<TrackDistanceMeasurement, 'id'> & {
|
|
2050
|
+
id?: MeasurementId;
|
|
2051
|
+
});
|
|
2052
|
+
/** Computed value of a measurement. */
|
|
2053
|
+
interface MeasurementResult {
|
|
2054
|
+
id: MeasurementId;
|
|
2055
|
+
kind: MeasurementKind;
|
|
2056
|
+
/** Millimetres for lengths, degrees for angles. */
|
|
2057
|
+
value: number;
|
|
2058
|
+
unit: 'mm' | 'deg';
|
|
2059
|
+
/** Formatted value as rendered in the scene (e.g. `'1 234.5 mm'`). */
|
|
2060
|
+
text: string;
|
|
2061
|
+
/** Signed component deltas — `'distance'` only. */
|
|
2062
|
+
deltaMm?: Vec3;
|
|
2063
|
+
/** Per-segment lengths — `'path'` only. */
|
|
2064
|
+
segmentLengthsMm?: number[];
|
|
2065
|
+
/**
|
|
2066
|
+
* `true` when the measurement could not be evaluated (e.g. a
|
|
2067
|
+
* `trackDistance` whose endpoints sit on different parts). `value` is then
|
|
2068
|
+
* the straight-line fallback.
|
|
2069
|
+
*/
|
|
2070
|
+
incomplete?: boolean;
|
|
2071
|
+
}
|
|
2072
|
+
/** Where an annotation hangs. Mover anchors follow the live mover position. */
|
|
2073
|
+
type AnnotationAnchor = {
|
|
2074
|
+
kind: 'world';
|
|
2075
|
+
positionMm: Vec3;
|
|
2076
|
+
} | {
|
|
2077
|
+
kind: 'mover';
|
|
2078
|
+
ref: MoverRef;
|
|
2079
|
+
} | {
|
|
2080
|
+
kind: 'module';
|
|
2081
|
+
ref: ModuleRef;
|
|
2082
|
+
} | ({
|
|
2083
|
+
kind: 'trackPosition';
|
|
2084
|
+
} & TrackAnchor);
|
|
2085
|
+
interface Annotation {
|
|
2086
|
+
id: string;
|
|
2087
|
+
text: string;
|
|
2088
|
+
anchor: AnnotationAnchor;
|
|
2089
|
+
/**
|
|
2090
|
+
* Leader-line offset from the anchor to the label (mm, world frame).
|
|
2091
|
+
* Default `[0, 0, 250]`.
|
|
2092
|
+
*/
|
|
2093
|
+
offsetMm?: Vec3;
|
|
2094
|
+
color?: string;
|
|
2095
|
+
/** Text height (mm). Default `MeasurementStyle.labelSizeMm`. */
|
|
2096
|
+
sizeMm?: number;
|
|
2097
|
+
/** Marker drawn at the anchor. Default `'Sphere'`. */
|
|
2098
|
+
markerShape?: MarkerShape;
|
|
2099
|
+
markerSizeMm?: number;
|
|
2100
|
+
/** Default `true`. */
|
|
2101
|
+
visible?: boolean;
|
|
2102
|
+
meta?: Record<string, unknown>;
|
|
2103
|
+
}
|
|
2104
|
+
type AnnotationInput = Omit<Annotation, 'id'> & {
|
|
2105
|
+
id?: string;
|
|
2106
|
+
};
|
|
2107
|
+
type MeasurementTool = 'none' | 'distance' | 'angle' | 'path' | 'trackDistance' | 'annotation';
|
|
2108
|
+
/** In-progress pick. `null` while no tool is armed. */
|
|
2109
|
+
interface MeasurementDraft {
|
|
2110
|
+
tool: Exclude<MeasurementTool, 'none'>;
|
|
2111
|
+
/** Points committed so far. */
|
|
2112
|
+
points: MeasurementPoint[];
|
|
2113
|
+
/** Point currently under the cursor, or `null` when off the scene. */
|
|
2114
|
+
hover: MeasurementPoint | null;
|
|
2115
|
+
}
|
|
2116
|
+
interface MeasurementStyle {
|
|
2117
|
+
/** Line + label colour of committed measurements. Default `'#00E5FF'`. */
|
|
2118
|
+
color?: string;
|
|
2119
|
+
/** Colour of the in-progress draft. Default `'#FFC400'`. */
|
|
2120
|
+
draftColor?: string;
|
|
2121
|
+
/** Annotation colour. Default `'#FFFFFF'`. */
|
|
2122
|
+
annotationColor?: string;
|
|
2123
|
+
/** Line width in pixels. Default `2`. */
|
|
2124
|
+
lineWidthPx?: number;
|
|
2125
|
+
/** Text height (mm). Default `40`. */
|
|
2126
|
+
labelSizeMm?: number;
|
|
2127
|
+
/** Endpoint marker diameter (mm). Default `18`. */
|
|
2128
|
+
pointSizeMm?: number;
|
|
2129
|
+
/** Decimal places on values. Default `1`. */
|
|
2130
|
+
decimals?: number;
|
|
2131
|
+
/** Unit suffix appended to lengths. Default `' mm'`. */
|
|
2132
|
+
unitSuffix?: string;
|
|
2133
|
+
/** Font URL passed to troika (see `DEFAULT_LABEL_FONT_URL`). */
|
|
2134
|
+
fontFamily?: string;
|
|
2135
|
+
/**
|
|
2136
|
+
* Draw measurement lines and markers through solid geometry
|
|
2137
|
+
* (`depthTest: false`). Default `true` — CAD viewers keep dimensions
|
|
2138
|
+
* readable regardless of what is in front of them.
|
|
2139
|
+
*/
|
|
2140
|
+
alwaysOnTop?: boolean;
|
|
2141
|
+
}
|
|
2142
|
+
/** Serializable snapshot of everything the measurement feature holds. */
|
|
2143
|
+
interface MeasurementDocument {
|
|
2144
|
+
schemaVersion: 1;
|
|
2145
|
+
measurements: Measurement[];
|
|
2146
|
+
annotations: Annotation[];
|
|
1726
2147
|
}
|
|
1727
2148
|
/**
|
|
1728
|
-
*
|
|
1729
|
-
*
|
|
1730
|
-
*
|
|
1731
|
-
*
|
|
1732
|
-
|
|
1733
|
-
declare function buildChain(modules: ReadonlyArray<ModuleEntry>): BuiltChain;
|
|
1734
|
-
/**
|
|
1735
|
-
* Locate the module containing `partPositionMm` along the chain.
|
|
1736
|
-
* First module with partPosition < startInPart + trackLength; at boundary
|
|
1737
|
-
* `partPosition == trackLength` the LAST module is returned with
|
|
1738
|
-
* `localPos = trackLength`.
|
|
2149
|
+
* `measurement` prop of `<XtsViewer3D>`. Data can be driven either
|
|
2150
|
+
* declaratively (`measurements` / `annotations` — controlled arrays) or
|
|
2151
|
+
* imperatively via `viewerRef.current.measurements`. When the controlled
|
|
2152
|
+
* arrays are supplied they win on every render, so a host that uses them must
|
|
2153
|
+
* persist what the change callbacks report.
|
|
1739
2154
|
*/
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
2155
|
+
interface MeasurementOptions {
|
|
2156
|
+
/** Armed tool. Default `'none'` — no picking, no cursor change. */
|
|
2157
|
+
tool?: MeasurementTool;
|
|
2158
|
+
/** Controlled measurement list. Omit to let the viewer keep its own. */
|
|
2159
|
+
measurements?: MeasurementInput[];
|
|
2160
|
+
/** Controlled annotation list. Omit to let the viewer keep its own. */
|
|
2161
|
+
annotations?: AnnotationInput[];
|
|
2162
|
+
snap?: SnapOptions;
|
|
2163
|
+
style?: MeasurementStyle;
|
|
2164
|
+
/**
|
|
2165
|
+
* Z of the fallback pick plane (mm) used when the ray misses all geometry,
|
|
2166
|
+
* so free space can be measured. Default `0`; `null` disables the fallback.
|
|
2167
|
+
*/
|
|
2168
|
+
groundPlaneZMm?: number | null;
|
|
2169
|
+
/** Fired after every change to the measurement list. */
|
|
2170
|
+
onMeasurementsChange?: (measurements: Measurement[], results: MeasurementResult[]) => void;
|
|
2171
|
+
/** Fired once when a tool completes a measurement. */
|
|
2172
|
+
onMeasurementCreate?: (measurement: Measurement, result: MeasurementResult) => void;
|
|
2173
|
+
onAnnotationsChange?: (annotations: Annotation[]) => void;
|
|
2174
|
+
onAnnotationCreate?: (annotation: Annotation) => void;
|
|
2175
|
+
/** Fired on every draft change (point added, hover moved, cancelled). */
|
|
2176
|
+
onDraftChange?: (draft: MeasurementDraft | null) => void;
|
|
2177
|
+
/**
|
|
2178
|
+
* Text for an annotation the `'annotation'` tool just placed. Return
|
|
2179
|
+
* `null` to abort. Default: `'Annotation'`.
|
|
2180
|
+
*/
|
|
2181
|
+
annotationTextFactory?: (point: MeasurementPoint) => string | null;
|
|
2182
|
+
}
|
|
1744
2183
|
/**
|
|
1745
|
-
*
|
|
2184
|
+
* A partial measurement, kind by kind.
|
|
1746
2185
|
*
|
|
1747
|
-
*
|
|
1748
|
-
*
|
|
2186
|
+
* `Omit` does not distribute over a union: `Omit<Measurement, 'id' | 'kind'>`
|
|
2187
|
+
* keeps only the properties every measurement kind has, which dropped `axis`
|
|
2188
|
+
* and `showComponents` — fields `MeasurementOverlay` actually renders. Mapping
|
|
2189
|
+
* over the union first keeps them.
|
|
1749
2190
|
*/
|
|
1750
|
-
|
|
1751
|
-
/** Convenience: chain from full ModuleEntry[] returning trackLength alone. */
|
|
1752
|
-
declare function trackLengthOf(modules: ReadonlyArray<ModuleEntry>): number;
|
|
2191
|
+
type MeasurementPatch = Partial<Measurement extends infer M ? M extends Measurement ? Omit<M, 'id' | 'kind'> : never : never>;
|
|
1753
2192
|
|
|
1754
2193
|
/**
|
|
1755
|
-
*
|
|
1756
|
-
*
|
|
1757
|
-
* Fail-fast validation + AT2050 half-clothoid merging:
|
|
1758
|
-
* AT2050_0500 + AT2050_0501 (gap=0) → AT2050_0500_180
|
|
1759
|
-
* ATH2050_0500 + ATH2050_0501 (gap=0) → ATH2050_0500_180
|
|
1760
|
-
*
|
|
1761
|
-
* Errors thrown via XtsViewerErrorException:
|
|
1762
|
-
* - 'unknown-module-type'
|
|
1763
|
-
* - 'unknown-mover-type'
|
|
1764
|
-
* - 'unmatched-clothoid-half' (0500 without 0501 or vice versa)
|
|
2194
|
+
* `XtsModelDocument` — the canonical serialized form of a viewer scene.
|
|
1765
2195
|
*
|
|
1766
|
-
*
|
|
1767
|
-
*
|
|
1768
|
-
*
|
|
1769
|
-
*
|
|
2196
|
+
* What `viewerRef.current.exportModel()` produces and what a host persists:
|
|
2197
|
+
* the configuration, optionally the runtime mover state, and optionally the
|
|
2198
|
+
* measurements. The measurement half keeps its own types next to its
|
|
2199
|
+
* implementation (`measurement/types.ts`); only the serialized document
|
|
2200
|
+
* surfaces here.
|
|
1770
2201
|
*/
|
|
1771
2202
|
|
|
1772
|
-
interface
|
|
2203
|
+
interface XtsModelDocument {
|
|
2204
|
+
schemaVersion: 1;
|
|
2205
|
+
meta: {
|
|
2206
|
+
generatedAt: string;
|
|
2207
|
+
generator: string;
|
|
2208
|
+
sourceFormat?: string;
|
|
2209
|
+
sourceFileHash?: string;
|
|
2210
|
+
};
|
|
2211
|
+
config: XtsConfig;
|
|
2212
|
+
runtime?: XtsRuntimeState;
|
|
2213
|
+
railSystem?: RailSystem | {
|
|
2214
|
+
byProcessingUnit: Record<number, RailSystem>;
|
|
2215
|
+
};
|
|
1773
2216
|
/**
|
|
1774
|
-
*
|
|
1775
|
-
*
|
|
1776
|
-
*
|
|
2217
|
+
* Measurements + annotations placed on the layout, as returned by
|
|
2218
|
+
* `XtsViewer3DRef.measurements.toJSON()`. Omitted when there are none, so
|
|
2219
|
+
* documents written before the measurement feature stay byte-identical.
|
|
1777
2220
|
*/
|
|
1778
|
-
|
|
1779
|
-
}
|
|
1780
|
-
interface NormalizationWarning {
|
|
1781
|
-
code: 'mover-out-of-range' | 'mover-orphan' | 'mover-duplicate-id' | 'mover-missing-id' | 'mover-invalid-id' | 'empty-part' | 'prepared-module' | 'duplicate-xpu-object-id' | 'missing-xpu-object-id' | 'invalid-xpu-object-id' | 'duplicate-part-object-id' | 'missing-part-object-id' | 'invalid-part-object-id' | 'duplicate-part-global-number' | 'missing-part-global-number' | 'invalid-part-global-number' | 'duplicate-module-global-number' | 'missing-module-global-number' | 'invalid-module-global-number';
|
|
1782
|
-
message: string;
|
|
1783
|
-
details?: unknown;
|
|
2221
|
+
measurements?: MeasurementDocument;
|
|
1784
2222
|
}
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
* warning details saw two shapes for one warning code.
|
|
1804
|
-
*
|
|
1805
|
-
* The class stays because it is part of the published API. It is now a thin
|
|
1806
|
-
* handle over the pure function.
|
|
1807
|
-
*/
|
|
1808
|
-
declare class XtsConfigNormalizer {
|
|
1809
|
-
normalize(config: XtsConfig): NormalizedXtsConfig;
|
|
1810
|
-
/** No-op. Kept so existing callers keep compiling; there is no cache. */
|
|
1811
|
-
clearCache(): void;
|
|
2223
|
+
interface XtsRuntimeState {
|
|
2224
|
+
movers: Array<{
|
|
2225
|
+
processingUnitObjectId: number;
|
|
2226
|
+
moverIndex: number;
|
|
2227
|
+
id: number;
|
|
2228
|
+
partOid: number;
|
|
2229
|
+
partPositionMm: number;
|
|
2230
|
+
velocity?: {
|
|
2231
|
+
mmPerSec: number;
|
|
2232
|
+
};
|
|
2233
|
+
status?: {
|
|
2234
|
+
warning?: boolean;
|
|
2235
|
+
error?: boolean;
|
|
2236
|
+
};
|
|
2237
|
+
}>;
|
|
2238
|
+
selection?: SelectionState;
|
|
2239
|
+
camera?: CameraState;
|
|
2240
|
+
display?: Partial<DisplayOptions>;
|
|
1812
2241
|
}
|
|
1813
|
-
declare function normalizeXtsConfig(config: XtsConfig): NormalizedXtsConfig;
|
|
1814
2242
|
|
|
1815
2243
|
/**
|
|
1816
|
-
*
|
|
1817
|
-
*
|
|
1818
|
-
* Sub-millimetre accurate for the dominant XTS case: two movers travelling
|
|
1819
|
-
* along the same chain. Each mover occupies a 1D footprint of
|
|
1820
|
-
* `pathLengthMm` centred on `partPositionMm`, so the collision predicate
|
|
1821
|
-
* collapses to one subtraction in path-space:
|
|
1822
|
-
*
|
|
1823
|
-
* centerDistance = arc-length |pos_a − pos_b|
|
|
1824
|
-
* requiredGap = (pathLength_a + pathLength_b) / 2
|
|
1825
|
-
* penetration = requiredGap − centerDistance
|
|
1826
|
-
* collision iff penetration > 0 (≥ 0 for "touching")
|
|
1827
|
-
*
|
|
1828
|
-
* For closed-loop chains (oval tracks, U-turn pairs) the arc-length is
|
|
1829
|
-
* taken as the *minimum* of the forward and the wrap-around path so two
|
|
1830
|
-
* movers near the seam (e.g. 50 mm and trackLength − 50 mm) are treated
|
|
1831
|
-
* as physically adjacent. Open chains are detected by comparing the
|
|
1832
|
-
* chain's start- and end-pose; for those the wrap-around distance is
|
|
1833
|
-
* simply not considered.
|
|
2244
|
+
* SidecarLoader — fetches per-asset JSON sidecars.
|
|
1834
2245
|
*
|
|
1835
|
-
*
|
|
1836
|
-
*
|
|
1837
|
-
*
|
|
1838
|
-
* mm — orders of magnitude below sub-millimetre.
|
|
2246
|
+
* Sidecars carry origin-correction (translateMm + rotationDegEuler) plus
|
|
2247
|
+
* path metadata. They live next to the GLB at <baseUrl>/<id>.meta.json (or
|
|
2248
|
+
* <id>.tool.meta.json for mover tools).
|
|
1839
2249
|
*
|
|
1840
|
-
*
|
|
1841
|
-
*
|
|
1842
|
-
*
|
|
1843
|
-
* same line about to crash".
|
|
2250
|
+
* On 404 the loader falls back to default origin-correction `(0,0,0)` /
|
|
2251
|
+
* `(0,0,0)`. The renderer can still place the GLB; the calibration tool is
|
|
2252
|
+
* the authoritative source for sidecar values.
|
|
1844
2253
|
*/
|
|
1845
2254
|
|
|
2255
|
+
interface OriginCorrection {
|
|
2256
|
+
translateMm: Vec3;
|
|
2257
|
+
rotationDegEuler: Vec3;
|
|
2258
|
+
}
|
|
2259
|
+
interface ModuleSidecar {
|
|
2260
|
+
moduleType: ModuleType3D;
|
|
2261
|
+
glbByRailSystem: {
|
|
2262
|
+
Beckhoff: string | null;
|
|
2263
|
+
HepcoGfx?: string | null;
|
|
2264
|
+
};
|
|
2265
|
+
originCorrection: OriginCorrection;
|
|
2266
|
+
pathType: 'Straight' | 'Curve' | 'Free';
|
|
2267
|
+
moduleLengthMm: number;
|
|
2268
|
+
endAngleDeg: number;
|
|
2269
|
+
freePathFile?: string;
|
|
2270
|
+
approximateBoundsMm?: {
|
|
2271
|
+
min: Vec3;
|
|
2272
|
+
max: Vec3;
|
|
2273
|
+
};
|
|
2274
|
+
}
|
|
2275
|
+
interface MoverSidecar {
|
|
2276
|
+
moverType: MoverType3D;
|
|
2277
|
+
glb: string;
|
|
2278
|
+
originCorrection: OriginCorrection;
|
|
2279
|
+
magnetPlateCenterMm: Vec3;
|
|
2280
|
+
pathLengthMm: number;
|
|
2281
|
+
}
|
|
1846
2282
|
/**
|
|
1847
|
-
*
|
|
1848
|
-
*
|
|
2283
|
+
* Calibration sidecar for a standalone magnet plate set. The plate is drawn
|
|
2284
|
+
* in the mover-local frame (+X travel, +Z up) with this correction applied,
|
|
2285
|
+
* so `originCorrection` is what seats it on the carriage.
|
|
1849
2286
|
*/
|
|
1850
|
-
interface
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
penetrationMm: number;
|
|
1868
|
-
/**
|
|
1869
|
-
* Both `partPositionMm` values at the time of the test. Lets the
|
|
1870
|
-
* consumer recompute or visualise without re-querying.
|
|
1871
|
-
*/
|
|
1872
|
-
positionAMm: number;
|
|
1873
|
-
positionBMm: number;
|
|
1874
|
-
/** Mover lengths (catalog `pathLengthMm`). */
|
|
1875
|
-
pathLengthAMm: number;
|
|
1876
|
-
pathLengthBMm: number;
|
|
1877
|
-
/** True when measurement wrapped around the chain end (closed-loop seam). */
|
|
1878
|
-
viaWraparound: boolean;
|
|
2287
|
+
interface MagnetPlateSidecar {
|
|
2288
|
+
plateType: MagnetPlateType3D;
|
|
2289
|
+
glb: string;
|
|
2290
|
+
originCorrection: OriginCorrection;
|
|
2291
|
+
poleCount: number;
|
|
2292
|
+
lengthMm: number;
|
|
2293
|
+
}
|
|
2294
|
+
interface MoverToolSidecar {
|
|
2295
|
+
toolType: MoverToolType3D;
|
|
2296
|
+
glbUrl: string;
|
|
2297
|
+
originCorrection: OriginCorrection;
|
|
2298
|
+
defaultOffsetMm: Vec3;
|
|
2299
|
+
approximateBoundsMm?: {
|
|
2300
|
+
min: Vec3;
|
|
2301
|
+
max: Vec3;
|
|
2302
|
+
};
|
|
2303
|
+
label: string;
|
|
1879
2304
|
}
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
2305
|
+
/**
|
|
2306
|
+
* Calibration sidecar for a guiding-rail GLB. The rail is rendered in the
|
|
2307
|
+
* same module-local frame as the module (i.e. under the module's
|
|
2308
|
+
* `startWorldPose`) with this correction applied to the GLB scene.
|
|
2309
|
+
*/
|
|
2310
|
+
interface RailSidecar {
|
|
2311
|
+
railType: RailType3D;
|
|
2312
|
+
glb: string;
|
|
2313
|
+
originCorrection: OriginCorrection;
|
|
2314
|
+
approximateBoundsMm?: {
|
|
2315
|
+
min: Vec3;
|
|
2316
|
+
max: Vec3;
|
|
2317
|
+
};
|
|
1893
2318
|
}
|
|
2319
|
+
declare const DEFAULT_ORIGIN_CORRECTION: OriginCorrection;
|
|
1894
2320
|
/**
|
|
1895
|
-
*
|
|
1896
|
-
*
|
|
1897
|
-
*
|
|
1898
|
-
* state.
|
|
2321
|
+
* Generic sidecar fetch with promise caching + graceful fallback. Returns
|
|
2322
|
+
* `undefined` on 404 (caller substitutes a default), and surfaces network or
|
|
2323
|
+
* parse errors as `Error` (caller is expected to forward to onError).
|
|
1899
2324
|
*/
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
partPositionMm: number;
|
|
2325
|
+
declare class SidecarLoader {
|
|
2326
|
+
private readonly timeoutMs;
|
|
2327
|
+
private readonly cache;
|
|
2328
|
+
constructor(timeoutMs?: number);
|
|
2329
|
+
clear(): void;
|
|
1906
2330
|
/**
|
|
1907
|
-
*
|
|
1908
|
-
*
|
|
2331
|
+
* Fetch and cache the sidecar at `url`. The loader validates the one field
|
|
2332
|
+
* every sidecar shape shares (`originCorrection`) and hands the rest to the
|
|
2333
|
+
* caller as `T` — the kind is a property of the URL, not of the loader.
|
|
1909
2334
|
*/
|
|
1910
|
-
|
|
2335
|
+
fetch<T>(url: string): Promise<T | undefined>;
|
|
1911
2336
|
}
|
|
1912
2337
|
/**
|
|
1913
|
-
*
|
|
1914
|
-
*
|
|
1915
|
-
* `chain` is the BuiltChain corresponding to the partOid all probed
|
|
1916
|
-
* movers travel on. `probes` may contain movers from *any* part — this
|
|
1917
|
-
* function only considers pairs where `partOid` matches the chain.
|
|
1918
|
-
*
|
|
1919
|
-
* Returns one entry per colliding (or near-miss, when `warningGapMm > 0`)
|
|
1920
|
-
* pair, sorted by `penetrationMm` descending so the deepest collisions
|
|
1921
|
-
* appear first.
|
|
2338
|
+
* Resolve the effective origin correction for a module sidecar — falls back
|
|
2339
|
+
* to identity when the sidecar is absent.
|
|
1922
2340
|
*/
|
|
1923
|
-
declare function
|
|
2341
|
+
declare function resolveOriginCorrection(sidecar: {
|
|
2342
|
+
originCorrection?: OriginCorrection;
|
|
2343
|
+
} | undefined): OriginCorrection;
|
|
2344
|
+
/** Standard sidecar filename from a base id. */
|
|
2345
|
+
declare function moduleSidecarFilename(moduleType: ModuleType3D): string;
|
|
2346
|
+
declare function moverSidecarFilename(moverType: MoverType3D): string;
|
|
2347
|
+
declare function toolSidecarFilename(toolType: MoverToolType3D): string;
|
|
2348
|
+
declare function railSidecarFilename(railType: RailType3D): string;
|
|
2349
|
+
/** Anything that names a GLB per rail system — a sidecar or a manifest entry. */
|
|
2350
|
+
interface GlbByRailSystem {
|
|
2351
|
+
glbByRailSystem: {
|
|
2352
|
+
Beckhoff: string | null;
|
|
2353
|
+
HepcoGfx?: string | null;
|
|
2354
|
+
};
|
|
2355
|
+
}
|
|
1924
2356
|
/**
|
|
1925
|
-
*
|
|
1926
|
-
*
|
|
1927
|
-
*
|
|
1928
|
-
* `livePositionOf` supplies the current position of a mover when one is
|
|
1929
|
-
* being driven imperatively — the `MoverPositionStore` at 60 Hz — falling
|
|
1930
|
-
* back to the config value. Passing it as a function rather than the store
|
|
1931
|
-
* itself is what keeps this a pure geometry function: it was previously
|
|
1932
|
-
* trapped inside the `useImperativeHandle` closure of `useXtsViewerHandle`,
|
|
1933
|
-
* where it could only be exercised through a mounted canvas.
|
|
2357
|
+
* Pure helper: pick the GLB filename with rail-system fallback. Returns null
|
|
2358
|
+
* when the entry marks the module 'prepared but not delivered'.
|
|
1934
2359
|
*
|
|
1935
|
-
*
|
|
2360
|
+
* Takes the structural shape rather than `ModuleSidecar` so
|
|
2361
|
+
* `resolveModuleGlbUrl`, which used to carry a line-for-line identical copy
|
|
2362
|
+
* of this body over manifest entries, can delegate here. Both are public
|
|
2363
|
+
* API, so both stay — but there is one implementation of the fallback rule.
|
|
1936
2364
|
*/
|
|
1937
|
-
declare function
|
|
2365
|
+
declare function pickGlbForRail(entry: GlbByRailSystem | undefined, rail: RailSystem): string | null;
|
|
1938
2366
|
|
|
1939
2367
|
/**
|
|
1940
|
-
*
|
|
1941
|
-
*
|
|
1942
|
-
* The mover collision (moverCollision.ts) is a 1D arc-length test for two
|
|
1943
|
-
* movers on the *same* chain; its header explicitly declines cross-XPU /
|
|
1944
|
-
* cross-track cases, which "would need an OBB / mesh test in 3D space".
|
|
1945
|
-
* This file is exactly that test, for the physical track *modules*.
|
|
1946
|
-
*
|
|
1947
|
-
* Each module is modelled as a chain of oriented bounding boxes (OBBs) that
|
|
1948
|
-
* follow its real centerline: the path is sampled (sampleModulePath) and each
|
|
1949
|
-
* segment becomes a small box (±halfWidth in Y, 0..height in Z) oriented along
|
|
1950
|
-
* the local tangent, then transformed into world space by
|
|
1951
|
-
*
|
|
1952
|
-
* world = orientation ⊗ trackTransform ⊗ partTransformation ⊗ chainPose
|
|
1953
|
-
*
|
|
1954
|
-
* Sampling the path (rather than a single straight box per module) is what
|
|
1955
|
-
* keeps curves and U-turns faithful — a single straight box would poke
|
|
1956
|
-
* outside the arc and produce both false positives and false negatives on a
|
|
1957
|
-
* closed oval. Straights collapse to one box; curves / free paths use several.
|
|
1958
|
-
*
|
|
1959
|
-
* Pairs are tested with the Separating Axis Theorem (15 axes) over their
|
|
1960
|
-
* segment boxes, yielding a penetration depth (minimum translation distance)
|
|
1961
|
-
* as `overlapMm`:
|
|
1962
|
-
*
|
|
1963
|
-
* overlapMm > 0 — boxes overlap by this many mm (deepest sub-box pair).
|
|
1964
|
-
* overlapMm = 0 — touching exactly.
|
|
1965
|
-
* overlapMm < 0 — closest-approach gap (only reported when
|
|
1966
|
-
* `warningGapMm` is set).
|
|
1967
|
-
*
|
|
1968
|
-
* Adjacent modules in the same part butt up edge-to-edge (ChainBuilder), so
|
|
1969
|
-
* consecutive `(i, i+1)` pairs — and the `(N-1, 0)` seam of a closed loop —
|
|
1970
|
-
* are excluded; everything else (cross-part, cross-XPU, same-part
|
|
1971
|
-
* non-adjacent) is tested.
|
|
2368
|
+
* Option shapes shared between the public `<XtsViewer3D>` props and the
|
|
2369
|
+
* internal `<XtsScene>` / `<ProcessingUnitRoot>` props.
|
|
1972
2370
|
*
|
|
1973
|
-
*
|
|
1974
|
-
*
|
|
1975
|
-
*
|
|
1976
|
-
*
|
|
1977
|
-
* not a true Euclidean (GJK) distance — slightly conservative for
|
|
1978
|
-
* corner-to-corner cases.
|
|
2371
|
+
* Each of these used to be declared twice — once on each side of that
|
|
2372
|
+
* boundary, character for character — so the two could drift silently, and
|
|
2373
|
+
* `SidecarOverrides` was even exported under two public names. Declared once
|
|
2374
|
+
* here and imported by both.
|
|
1979
2375
|
*/
|
|
1980
2376
|
|
|
1981
|
-
/**
|
|
1982
|
-
|
|
1983
|
-
/** Default box height (Z) — matches `approximateModuleBounds`. */
|
|
1984
|
-
declare const DEFAULT_MODULE_HEIGHT_MM = 100;
|
|
2377
|
+
/** Where the orientation gizmo sits in the canvas. */
|
|
2378
|
+
type ViewCubeAlignment = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'top-center' | 'bottom-center' | 'center-left' | 'center-right' | 'center-center';
|
|
1985
2379
|
/**
|
|
1986
|
-
*
|
|
1987
|
-
*
|
|
2380
|
+
* The corner navigation gizmo: three labelled X / Y / Z axis handles that
|
|
2381
|
+
* track the camera and snap it onto an axis when clicked.
|
|
2382
|
+
*
|
|
2383
|
+
* Named `ViewCube*` for backwards compatibility — earlier versions drew a
|
|
2384
|
+
* navigation cube in the same spot.
|
|
1988
2385
|
*/
|
|
1989
|
-
interface
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
2386
|
+
interface ViewCubeOptions {
|
|
2387
|
+
enabled?: boolean;
|
|
2388
|
+
/** Default `'bottom-right'`. */
|
|
2389
|
+
alignment?: ViewCubeAlignment;
|
|
2390
|
+
/** Distance from the aligned corner, in CSS px. Default `[80, 80]`. */
|
|
2391
|
+
marginPx?: [number, number];
|
|
2392
|
+
}
|
|
2393
|
+
/**
|
|
2394
|
+
* Keyboard navigation — moving the camera *through* the scene rather than
|
|
2395
|
+
* orbiting it around a fixed point.
|
|
2396
|
+
*
|
|
2397
|
+
* The arrow keys (and the WASD block) walk the camera and the orbit pivot
|
|
2398
|
+
* together along the ground plane, PageUp / PageDown climb, Shift goes
|
|
2399
|
+
* faster and Alt finer. Keys are read from the canvas once it has focus,
|
|
2400
|
+
* which it takes on pointer-down, so an embedded viewer never swallows the
|
|
2401
|
+
* host page's arrow keys.
|
|
2402
|
+
*
|
|
2403
|
+
* Off automatically while `lock.pan` is set — moving through the scene is
|
|
2404
|
+
* panning by another input device.
|
|
2405
|
+
*/
|
|
2406
|
+
interface KeyboardNavigationOptions {
|
|
2407
|
+
/** Default `true`. */
|
|
2408
|
+
enabled?: boolean;
|
|
1997
2409
|
/**
|
|
1998
|
-
*
|
|
1999
|
-
*
|
|
2000
|
-
* • `> 0` — overlap (mm).
|
|
2001
|
-
* • `= 0` — touching.
|
|
2002
|
-
* • `< 0` — closest-approach gap (only when `warningGapMm` is set).
|
|
2410
|
+
* Multiplier on the default travel rate, which is ~0.6 × the current
|
|
2411
|
+
* orbit distance per second. Default `1`.
|
|
2003
2412
|
*/
|
|
2004
|
-
|
|
2005
|
-
/** Centre-to-centre distance of the deepest sub-box pair (mm). */
|
|
2006
|
-
centerDistanceMm: number;
|
|
2007
|
-
/** Unit world-space axis of least penetration (the MTV direction). */
|
|
2008
|
-
axis: Vec3;
|
|
2413
|
+
speed?: number;
|
|
2009
2414
|
}
|
|
2010
|
-
|
|
2415
|
+
/** Interaction locks. Each `true` disables that interaction. */
|
|
2416
|
+
interface InteractionLocks {
|
|
2417
|
+
rotate?: boolean;
|
|
2418
|
+
pan?: boolean;
|
|
2419
|
+
zoom?: boolean;
|
|
2420
|
+
selection?: boolean;
|
|
2421
|
+
}
|
|
2422
|
+
interface PerformanceOptions {
|
|
2011
2423
|
/**
|
|
2012
|
-
*
|
|
2013
|
-
*
|
|
2014
|
-
*
|
|
2424
|
+
* Draw a processing unit's movers as one `InstancedMesh` instead of one
|
|
2425
|
+
* scene node each, cutting mover draw calls from N to 1. Default: false.
|
|
2426
|
+
*
|
|
2427
|
+
* Applies from four movers up, and only to movers with nothing bound to
|
|
2428
|
+
* them: a mover carrying tools, a mover-bound custom asset, or a visible
|
|
2429
|
+
* drive status still gets its own node. Turning on the anchor marker or
|
|
2430
|
+
* mover-id labels gives every mover a child object, so the batch is
|
|
2431
|
+
* skipped for that unit entirely. Selection and clicks work either way.
|
|
2015
2432
|
*/
|
|
2016
|
-
|
|
2433
|
+
instancing?: boolean;
|
|
2434
|
+
/** Stop the frameloop while the tab is hidden. Default: true. */
|
|
2435
|
+
autoPauseOnHidden?: boolean;
|
|
2017
2436
|
/**
|
|
2018
|
-
*
|
|
2019
|
-
*
|
|
2020
|
-
*
|
|
2437
|
+
* Enable demand rendering: frames only render when something changes
|
|
2438
|
+
* (camera move, mover position update, selection). Saves >90% GPU time
|
|
2439
|
+
* when the scene is idle. Default: true.
|
|
2021
2440
|
*/
|
|
2022
|
-
|
|
2023
|
-
/**
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
/**
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
/**
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
globalNumber?: number;
|
|
2041
|
-
/** Adjacency grouping key (part.globalNumber — system-wide unique). */
|
|
2042
|
-
partKey: number;
|
|
2043
|
-
/** Index of this module within its part's chain. */
|
|
2044
|
-
moduleIndex: number;
|
|
2045
|
-
/** Segment boxes following the module centerline (≥ 1). */
|
|
2046
|
-
obbs: Obb[];
|
|
2047
|
-
/** Module-level bounding sphere enclosing all segment boxes. */
|
|
2048
|
-
sphereCenter: Vector3;
|
|
2049
|
-
sphereRadius: number;
|
|
2050
|
-
}
|
|
2051
|
-
/** A part fed to `buildModuleProbes`. */
|
|
2052
|
-
interface ModuleCollisionPartInput {
|
|
2053
|
-
/** ModuleRef.partObjectId (`PartConfig.objectId`). */
|
|
2054
|
-
partObjectId: number;
|
|
2055
|
-
/** Adjacency key (`PartConfig.globalNumber`). */
|
|
2056
|
-
partKey: number;
|
|
2057
|
-
partTransformation?: PartTransformation | null;
|
|
2058
|
-
chain: BuiltChain;
|
|
2059
|
-
}
|
|
2060
|
-
/** An XPU fed to `buildModuleProbes`. */
|
|
2061
|
-
interface ModuleCollisionXpuInput {
|
|
2062
|
-
xpuObjectId: number;
|
|
2063
|
-
/** Effective (override-resolved) per-XPU track transform. */
|
|
2064
|
-
trackTransform?: TrackTransform | null;
|
|
2065
|
-
parts: ReadonlyArray<ModuleCollisionPartInput>;
|
|
2441
|
+
demandRendering?: boolean;
|
|
2442
|
+
/**
|
|
2443
|
+
* Cap the device pixel ratio. Values above 2 rarely improve perceptible
|
|
2444
|
+
* quality but double fill-rate cost on Retina/4K displays. Default: 2.
|
|
2445
|
+
*/
|
|
2446
|
+
maxDpr?: number;
|
|
2447
|
+
/**
|
|
2448
|
+
* Disable automatic per-frame shadow map updates. Shadows only re-render
|
|
2449
|
+
* on camera change. Default: true (when shadows enabled).
|
|
2450
|
+
*/
|
|
2451
|
+
shadowOnDemand?: boolean;
|
|
2452
|
+
/**
|
|
2453
|
+
* Override the KTX2 (Basis) transcoder path used to decode compressed
|
|
2454
|
+
* textures in the GLB assets. Defaults to a CDN copy pinned to the bundled
|
|
2455
|
+
* three.js revision. Point this at a self-hosted
|
|
2456
|
+
* `basis_transcoder.{js,wasm}` directory for offline / air-gapped use.
|
|
2457
|
+
*/
|
|
2458
|
+
ktx2TranscoderUrl?: string;
|
|
2066
2459
|
}
|
|
2067
2460
|
/**
|
|
2068
|
-
*
|
|
2069
|
-
*
|
|
2070
|
-
*
|
|
2071
|
-
* composition lives in one place.
|
|
2072
|
-
*
|
|
2073
|
-
* Hidden XPUs / parts (`visible === false`) are skipped to match the scene.
|
|
2074
|
-
*/
|
|
2075
|
-
declare function buildModuleProbes(orientation: Orientation | undefined, xpus: ReadonlyArray<ModuleCollisionXpuInput>, opts?: CheckModuleCollisionsOptions): {
|
|
2076
|
-
probes: ModuleProbe[];
|
|
2077
|
-
adjacency: Map<number, {
|
|
2078
|
-
count: number;
|
|
2079
|
-
closed: boolean;
|
|
2080
|
-
}>;
|
|
2081
|
-
};
|
|
2082
|
-
/**
|
|
2083
|
-
* Pairwise OBB overlap test over all module probes. Same-part consecutive
|
|
2084
|
-
* and closed-loop-seam pairs are excluded; everything else is tested.
|
|
2085
|
-
* Returns one entry per overlapping (or near-miss, when `warningGapMm > 0`)
|
|
2086
|
-
* pair, sorted deepest-overlap-first.
|
|
2087
|
-
*/
|
|
2088
|
-
declare function checkModuleCollisions(probes: ReadonlyArray<ModuleProbe>, adjacency: ReadonlyMap<number, {
|
|
2089
|
-
count: number;
|
|
2090
|
-
closed: boolean;
|
|
2091
|
-
}>, opts?: CheckModuleCollisionsOptions): ModuleCollision[];
|
|
2092
|
-
/**
|
|
2093
|
-
* Resolve a normalized config into the per-XPU inputs `buildModuleProbes`
|
|
2094
|
-
* takes, applying the same override precedence the scene uses: an imperative
|
|
2095
|
-
* `trackTransformOverrides` entry beats the config value.
|
|
2096
|
-
*
|
|
2097
|
-
* This adapter was written out twice, character for character — once in
|
|
2098
|
-
* `<ModuleCollisionMonitor>` and once inside the `checkModuleCollisions`
|
|
2099
|
-
* closure of `useXtsViewerHandle` — with the only difference being where the
|
|
2100
|
-
* orientation came from.
|
|
2101
|
-
*
|
|
2102
|
-
* A part whose chain cannot be built is skipped, as before, but no longer in
|
|
2103
|
-
* silence: `normalizeXtsConfig` has already made that impossible, so if it
|
|
2104
|
-
* ever happens it means a part has silently dropped out of collision
|
|
2105
|
-
* detection, which is a safety feature. Reported rather than thrown, because
|
|
2106
|
-
* both callers run inside React and a throw would take the scene down.
|
|
2461
|
+
* Live override of the per-asset origin-correction sidecars, keyed by asset
|
|
2462
|
+
* type. Useful for calibration tooling that adjusts a model's origin without
|
|
2463
|
+
* re-publishing its `.meta.json`.
|
|
2107
2464
|
*/
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2465
|
+
interface SidecarOverrides {
|
|
2466
|
+
modules?: Partial<Record<ModuleType3D, OriginCorrection>>;
|
|
2467
|
+
movers?: Partial<Record<MoverType3D, OriginCorrection>>;
|
|
2468
|
+
tools?: Partial<Record<MoverToolType3D, OriginCorrection>>;
|
|
2469
|
+
magnetPlates?: Partial<Record<MagnetPlateType3D, OriginCorrection>>;
|
|
2470
|
+
rails?: Partial<Record<RailType3D, OriginCorrection>>;
|
|
2471
|
+
}
|
|
2111
2472
|
|
|
2112
2473
|
/**
|
|
2113
2474
|
* Shared, mostly-pure plumbing behind `exportScreenshot` and
|
|
@@ -2525,7 +2886,7 @@ declare function trackPolylineWorld(entry: TrackIndexEntry, fromChainMm: number,
|
|
|
2525
2886
|
* `getLivePositionMm` supplies the imperative store value; without it the
|
|
2526
2887
|
* config-time position is used.
|
|
2527
2888
|
*/
|
|
2528
|
-
declare function moverAnchorWorld(config: NormalizedXtsConfig, index: TrackIndex, ref: MoverRef, getLivePositionMm?: (moverId: number) => number | undefined): Vec3 | null;
|
|
2889
|
+
declare function moverAnchorWorld(config: NormalizedXtsConfig, index: TrackIndex, ref: MoverRef, getLivePositionMm?: (moverId: number) => number | undefined, getLivePartObjectId?: (moverId: number) => number | undefined): Vec3 | null;
|
|
2529
2890
|
/** World centre of a module, for `AnnotationAnchor` of kind `'module'`. */
|
|
2530
2891
|
declare function moduleAnchorWorld(index: TrackIndex, processingUnitObjectId: number, partObjectId: number, moduleIndex: number): Vec3 | null;
|
|
2531
2892
|
/** World position of a `TrackAnchor` (user-frame mm on a part). */
|
|
@@ -2561,7 +2922,7 @@ interface MeasurementApi {
|
|
|
2561
2922
|
/** Close the current pick — the polyline tool's "done". */
|
|
2562
2923
|
finish(): Measurement | null;
|
|
2563
2924
|
add(measurement: MeasurementInput): Measurement;
|
|
2564
|
-
update(id: MeasurementId, patch:
|
|
2925
|
+
update(id: MeasurementId, patch: MeasurementPatch): Measurement | null;
|
|
2565
2926
|
remove(id: MeasurementId): boolean;
|
|
2566
2927
|
clear(): void;
|
|
2567
2928
|
/** Replace the whole list (e.g. restoring a persisted session). */
|
|
@@ -2619,15 +2980,6 @@ interface XtsViewer3DRef {
|
|
|
2619
2980
|
focusOn(target: FocusTarget, opts?: FocusOptions): void;
|
|
2620
2981
|
setCamera(cam: CameraState): void;
|
|
2621
2982
|
getCamera(): CameraState | null;
|
|
2622
|
-
/**
|
|
2623
|
-
* @deprecated Not implemented — this is a no-op that warns in development.
|
|
2624
|
-
*
|
|
2625
|
-
* `orientation` is a static prop in this build; there is no state behind
|
|
2626
|
-
* it to update. Set it through `<XtsViewer3D orientation={…}>` instead.
|
|
2627
|
-
* The method is kept only so the published interface does not change
|
|
2628
|
-
* shape mid-major, and will be removed in the next major.
|
|
2629
|
-
*/
|
|
2630
|
-
setOrientation(orientation: XtsConfig['orientation']): void;
|
|
2631
2983
|
getMoverWorldTransform(ref: MoverRef): {
|
|
2632
2984
|
positionMm: [number, number, number];
|
|
2633
2985
|
rotationDegEuler: [number, number, number];
|
|
@@ -2691,16 +3043,6 @@ interface XtsViewer3DRef {
|
|
|
2691
3043
|
* frameloop tick.
|
|
2692
3044
|
*/
|
|
2693
3045
|
renderFrameNow(): void;
|
|
2694
|
-
/**
|
|
2695
|
-
* @deprecated Not implemented — this is a no-op that warns in development.
|
|
2696
|
-
*
|
|
2697
|
-
* GLB caching lives in `useGLTF`'s global cache, which r3f exposes no
|
|
2698
|
-
* per-instance handle to clear. Use `useGLTF.clear()` from
|
|
2699
|
-
* `@react-three/drei` directly if you need to drop cached assets. Kept
|
|
2700
|
-
* only so the published interface does not change shape mid-major, and
|
|
2701
|
-
* will be removed in the next major.
|
|
2702
|
-
*/
|
|
2703
|
-
reloadAssets(): Promise<void>;
|
|
2704
3046
|
setMoverPosition(moverId: number, partPositionMm: number): void;
|
|
2705
3047
|
/**
|
|
2706
3048
|
* Push mover positions in bulk. Two accepted forms:
|
|
@@ -2724,6 +3066,26 @@ interface XtsViewer3DRef {
|
|
|
2724
3066
|
setModuleStatuses(statuses: ReadonlyArray<ModuleStatusEntry | null | undefined>): void;
|
|
2725
3067
|
/** Clear all live module statuses (revert to config-time `ModuleEntry.status`). */
|
|
2726
3068
|
clearModuleStatuses(): void;
|
|
3069
|
+
/**
|
|
3070
|
+
* Push live mover-tool state, keyed by `MoverConfig.id` and then by
|
|
3071
|
+
* `MoverToolConfig.id` — `update[mover.id][tool.id] = { visible }`.
|
|
3072
|
+
*
|
|
3073
|
+
* For state that changes while the scene runs: a simulated line loads a
|
|
3074
|
+
* product onto a mover at one station and takes it off at another, so the
|
|
3075
|
+
* tool drawing that product appears and disappears several times a minute.
|
|
3076
|
+
* Saying it through `config` would replace the whole configuration on every
|
|
3077
|
+
* change; this is one call and one `invalidate()`, however many movers it
|
|
3078
|
+
* carries.
|
|
3079
|
+
*
|
|
3080
|
+
* A live entry OVERRIDES the config-time `MoverToolConfig.visible`. `null`
|
|
3081
|
+
* for a tool drops it back to the configuration. Only movers named in
|
|
3082
|
+
* `update` are touched, and only the movers that actually changed
|
|
3083
|
+
* re-render. A tool with no `id` cannot be addressed and always follows its
|
|
3084
|
+
* configuration.
|
|
3085
|
+
*/
|
|
3086
|
+
setMoverToolStates(update: Readonly<Record<number, Readonly<Record<string, MoverToolStateEntry | null>>>>): void;
|
|
3087
|
+
/** Clear all live mover-tool state (revert to config-time `MoverToolConfig.visible`). */
|
|
3088
|
+
clearMoverToolStates(): void;
|
|
2727
3089
|
checkMoverCollisions(opts?: CheckMoverCollisionsOptions): MoverCollision[];
|
|
2728
3090
|
/**
|
|
2729
3091
|
* On-demand check for physically overlapping track MODULES across the
|
|
@@ -2757,8 +3119,8 @@ interface XtsViewer3DRef {
|
|
|
2757
3119
|
}
|
|
2758
3120
|
|
|
2759
3121
|
/**
|
|
2760
|
-
* HepcoGfxRailProfile — cross-
|
|
2761
|
-
* the procedural extrusion in <HepcoGfxRail>.
|
|
3122
|
+
* HepcoGfxRailProfile — cross-sections of the Hepco GFX 1-Trak (TL1) guideway
|
|
3123
|
+
* used by the procedural extrusion in <HepcoGfxRail>.
|
|
2762
3124
|
*
|
|
2763
3125
|
* The profile is expressed in the Beckhoff path-frame convention:
|
|
2764
3126
|
* - path tangent = +X (extrusion direction)
|
|
@@ -2770,76 +3132,159 @@ interface XtsViewer3DRef {
|
|
|
2770
3132
|
* plane the initial Frenet frame computed by three.js is:
|
|
2771
3133
|
* normal = (0, 0, -1) -> shape.x maps to world -Z
|
|
2772
3134
|
* binormal = lateral in XY -> shape.y maps to world ±Y
|
|
2773
|
-
* To keep this module's public dimensions in natural "Z-up" coordinates,
|
|
2774
|
-
*
|
|
2775
|
-
*
|
|
3135
|
+
* To keep this module's public dimensions in natural "Z-up" coordinates, the
|
|
3136
|
+
* outlines below are built as (y, z) point lists and only converted to the
|
|
3137
|
+
* shape frame by `toShape()`. All the geometry — including the arcs — is
|
|
3138
|
+
* therefore expressed and testable in natural terms; the negation of Z is an
|
|
3139
|
+
* implementation detail of one function.
|
|
3140
|
+
*
|
|
3141
|
+
* The guideway cross-section is THREE disconnected regions, one mesh each:
|
|
3142
|
+
*
|
|
3143
|
+
* 1. Baseplate — a flat plate, 30 mm thick. The real part is 270 mm wide and
|
|
3144
|
+
* carries both tracks of the oval; the viewer extrudes one 135 mm half per
|
|
3145
|
+
* Part, so two Parts (or the two straights of one closed Part) together
|
|
3146
|
+
* span the full width. Its inner edge carries half of the 30 mm central
|
|
3147
|
+
* slot; the other half comes from the opposite track.
|
|
3148
|
+
*
|
|
3149
|
+
* 2. Rail — the hardened steel V-rail the mover's rollers engage. Apex to
|
|
3150
|
+
* apex 44 mm ("NM44"), 15.6 mm tall, with 35° V flanks, a shallow groove
|
|
3151
|
+
* in its top face, a waisted neck and two feet standing on the plate.
|
|
3152
|
+
*
|
|
3153
|
+
* 3. Bracket — the GFX-UMB upper mounting bracket that carries the XTS motor
|
|
3154
|
+
* modules above the plate. One per Part, on the inner side of the rail.
|
|
2776
3155
|
*
|
|
2777
|
-
*
|
|
3156
|
+
* ## Where the numbers come from
|
|
2778
3157
|
*
|
|
2779
|
-
*
|
|
2780
|
-
*
|
|
2781
|
-
*
|
|
3158
|
+
* Every dimension below is measured off the original CAD, not estimated:
|
|
3159
|
+
* `stepfiles/hepco-gfx-rail/BA-GFX-1TL-3M-B.stp`, which is an assembly of
|
|
3160
|
+
* named parts — `GFX-1TLBP-3S-B` (baseplate), `AT-XTS_GFX-NM44-L500` (straight
|
|
3161
|
+
* rail), `GFX-SEG-NM44-CSA_` (curved rail), `GFX-UMB-1RH`/`-2LH`/`-EB`
|
|
3162
|
+
* (brackets and end block). The same section was cross-checked against
|
|
3163
|
+
* HepcoMotion's own `GFX-1TL-8M-B` download, which agrees to 0.1 mm.
|
|
2782
3164
|
*
|
|
2783
|
-
*
|
|
2784
|
-
* of the baseplate at its +Y edge. Has horizontally pointed apexes on
|
|
2785
|
-
* both lateral sides; the mover's V-rollers engage these apexes.
|
|
3165
|
+
* ## The one constraint that must not move
|
|
2786
3166
|
*
|
|
2787
|
-
*
|
|
2788
|
-
*
|
|
2789
|
-
*
|
|
3167
|
+
* All nine Hepco carriage sidecars (`public/models/Hepco-GFX2-*.meta.json`)
|
|
3168
|
+
* are calibrated to seat their V-roller grooves on the two apexes at
|
|
3169
|
+
* chain-frame (Y, Z) = (9, -43) and (-35, -43), with only 0.7-1.6 mm of
|
|
3170
|
+
* clearance left. The apexes are the datum: every other dimension here is
|
|
3171
|
+
* derived relative to them, so the carriages stay seated.
|
|
2790
3172
|
*
|
|
2791
|
-
*
|
|
2792
|
-
*
|
|
2793
|
-
*
|
|
3173
|
+
* apex centre Y = stackYOffsetMm = -13
|
|
3174
|
+
* apex Z = railTopZMm - spearBaseHalfHeightMm
|
|
3175
|
+
* + stackZOffsetMm = -43
|
|
3176
|
+
* plate top Z = railTopZMm - railHeightMm + stackZOffsetMm = -55.5
|
|
3177
|
+
*
|
|
3178
|
+
* `HepcoGfxRailProfile.test.ts` pins this contract directly.
|
|
2794
3179
|
*/
|
|
2795
3180
|
|
|
3181
|
+
/** A point in the natural (lateral Y, vertical Z) profile frame, in mm. */
|
|
3182
|
+
type ProfilePoint = readonly [y: number, z: number];
|
|
2796
3183
|
interface HepcoGfxRailProfileDims {
|
|
2797
|
-
/** Baseplate Y-extent per track (mm)
|
|
2798
|
-
* cross-section width. */
|
|
3184
|
+
/** Baseplate Y-extent per track (mm) — half of the real 270 mm plate. */
|
|
2799
3185
|
readonly baseplateWidthMm: number;
|
|
2800
3186
|
/** Baseplate Z-extent (mm). */
|
|
2801
3187
|
readonly baseplateThicknessMm: number;
|
|
2802
|
-
/**
|
|
2803
|
-
*
|
|
3188
|
+
/**
|
|
3189
|
+
* Z of the baseplate's top face in the path frame (before stackZOffsetMm).
|
|
3190
|
+
* Also the Z the rail and the bracket stand on.
|
|
3191
|
+
*/
|
|
2804
3192
|
readonly baseplateTopZMm: number;
|
|
2805
|
-
/**
|
|
3193
|
+
/**
|
|
3194
|
+
* Lateral distance from the rail centreline to the plate's OUTER edge (mm),
|
|
3195
|
+
* on the +Y side. The real rail overhangs the plate edge, so this is smaller
|
|
3196
|
+
* than half the rail width. The inner edge follows from `baseplateWidthMm`.
|
|
3197
|
+
*/
|
|
3198
|
+
readonly baseplateOuterExtentMm: number;
|
|
3199
|
+
/**
|
|
3200
|
+
* Depth of the central-slot notch cut into the plate's inner edge (mm) —
|
|
3201
|
+
* half of the real 30 mm slot, the other half belonging to the opposite
|
|
3202
|
+
* track's plate.
|
|
3203
|
+
*/
|
|
3204
|
+
readonly baseplateSlotDepthMm: number;
|
|
3205
|
+
/** Length of one central slot along the path (mm). */
|
|
3206
|
+
readonly baseplateSlotLengthMm: number;
|
|
3207
|
+
/** Spacing of the central slots along the path (mm). */
|
|
3208
|
+
readonly baseplateSlotPitchMm: number;
|
|
3209
|
+
/** Rail Y-extent, apex to apex (mm). */
|
|
2806
3210
|
readonly railWidthMm: number;
|
|
2807
|
-
/** Rail Z-extent,
|
|
3211
|
+
/** Rail Z-extent, top face down to the feet (mm). */
|
|
2808
3212
|
readonly railHeightMm: number;
|
|
2809
|
-
/** Z
|
|
2810
|
-
* stackZOffsetMm is applied). */
|
|
3213
|
+
/** Z of the rail's top face in the path frame (before stackZOffsetMm). */
|
|
2811
3214
|
readonly railTopZMm: number;
|
|
2812
|
-
/**
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
*
|
|
3215
|
+
/**
|
|
3216
|
+
* Drop from the rail's top face to the apex centre (mm). Together with
|
|
3217
|
+
* `railTopZMm` and `stackZOffsetMm` this fixes the apex height, which the
|
|
3218
|
+
* carriage sidecars depend on — see the module header.
|
|
3219
|
+
*/
|
|
2816
3220
|
readonly spearBaseHalfHeightMm: number;
|
|
2817
|
-
/**
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
readonly
|
|
2821
|
-
/**
|
|
3221
|
+
/** Height of the small vertical flat at the very tip of each apex (mm). */
|
|
3222
|
+
readonly apexFlatHeightMm: number;
|
|
3223
|
+
/** V flank angle measured from horizontal (deg). Both flanks are equal. */
|
|
3224
|
+
readonly vFlankAngleDeg: number;
|
|
3225
|
+
/** Height of the rail's underside wings above the plate (mm). */
|
|
3226
|
+
readonly railWingHeightMm: number;
|
|
3227
|
+
/** Half-width of the underside wing flat, inner end (mm). */
|
|
3228
|
+
readonly railWingInnerHalfWidthMm: number;
|
|
3229
|
+
/** Width of the recessed groove in the rail's top face (mm). */
|
|
3230
|
+
readonly topGrooveWidthMm: number;
|
|
3231
|
+
/** Depth of that groove below the top face (mm). */
|
|
3232
|
+
readonly topGrooveDepthMm: number;
|
|
3233
|
+
/** Radius of the two tangent arcs forming each groove edge (mm). */
|
|
3234
|
+
readonly topGrooveFilletMm: number;
|
|
3235
|
+
/** Convex round below each wing: centre (half-width, height) and radius. */
|
|
3236
|
+
readonly neckRoundCentreMm: ProfilePoint;
|
|
3237
|
+
readonly neckRoundRadiusMm: number;
|
|
3238
|
+
/** Concave waist scallop: centre (half-width, height) and radius. */
|
|
3239
|
+
readonly neckWaistCentreMm: ProfilePoint;
|
|
3240
|
+
readonly neckWaistRadiusMm: number;
|
|
3241
|
+
/** Where the convex round hands over to the concave waist (half-width, height). */
|
|
3242
|
+
readonly neckJunctionMm: ProfilePoint;
|
|
3243
|
+
/** Half-width of the rail feet at their outer wall (mm). */
|
|
3244
|
+
readonly footOuterHalfWidthMm: number;
|
|
3245
|
+
/** Height at which the foot's outer wall meets the waist scallop (mm). */
|
|
3246
|
+
readonly footTopHeightMm: number;
|
|
3247
|
+
/** 45° chamfer on every foot corner at the plate face (mm). */
|
|
3248
|
+
readonly footChamferMm: number;
|
|
3249
|
+
/** Half-width of the channel between the two feet (mm). */
|
|
3250
|
+
readonly channelHalfWidthMm: number;
|
|
3251
|
+
/** Height of that channel (mm) — its floor is the plate's top face. */
|
|
3252
|
+
readonly channelHeightMm: number;
|
|
3253
|
+
/** Bracket footprint width (mm). */
|
|
3254
|
+
readonly bracketWidthMm: number;
|
|
3255
|
+
/** Bracket total height above the plate's top face (mm). */
|
|
3256
|
+
readonly bracketHeightMm: number;
|
|
3257
|
+
/** Height of the bracket's full-width base block (mm). */
|
|
3258
|
+
readonly bracketBaseHeightMm: number;
|
|
3259
|
+
/** Width of the upper wall standing on the base block's outer edge (mm). */
|
|
3260
|
+
readonly bracketWallWidthMm: number;
|
|
3261
|
+
/**
|
|
3262
|
+
* Lateral distance from the rail centreline to the bracket's OUTER face,
|
|
3263
|
+
* measured toward -Y (mm). The bracket sits between the rail and the oval's
|
|
3264
|
+
* centreline, and the XTS module bolts to the outer face of its wall.
|
|
3265
|
+
*/
|
|
3266
|
+
readonly bracketOuterOffsetMm: number;
|
|
3267
|
+
/** Calibration shift in Y applied to every region together (mm). */
|
|
2822
3268
|
readonly stackYOffsetMm: number;
|
|
2823
|
-
/** Calibration shift in Z applied to
|
|
3269
|
+
/** Calibration shift in Z applied to every region together (mm). */
|
|
2824
3270
|
readonly stackZOffsetMm: number;
|
|
3271
|
+
/**
|
|
3272
|
+
* @deprecated Described the straight-chamfer approximation of the old
|
|
3273
|
+
* scaffolding profile. No longer read by any shape factory; kept so that
|
|
3274
|
+
* callers passing it through `hepcoGfxProfile` keep type-checking.
|
|
3275
|
+
*/
|
|
3276
|
+
readonly spearProtrusionMm: number;
|
|
3277
|
+
/**
|
|
3278
|
+
* @deprecated See `spearProtrusionMm`. The real undercut is the waisted
|
|
3279
|
+
* neck described by `neckWaist*` / `neckRound*`.
|
|
3280
|
+
*/
|
|
3281
|
+
readonly undercutInsetMm: number;
|
|
2825
3282
|
}
|
|
2826
3283
|
/**
|
|
2827
|
-
* Default dimensions (mm)
|
|
2828
|
-
* rendered geometry is correct out of the box; the playground / a
|
|
2829
|
-
* `*.meta.json` sidecar (`Hepco-GFX.rail.meta.json`) can override them.
|
|
2830
|
-
*
|
|
2831
|
-
* Cross-checked against the exact Hepco TL1 (1-Trak loop) guideway STP
|
|
2832
|
-
* `stepfiles/hepco-gfx-rail/BA-GFX-1TL-3M-B.stp`: a perpendicular section of
|
|
2833
|
-
* a straight run shows the full beam is ~270 mm wide in the lateral axis
|
|
2834
|
-
* (matches `baseplateWidthMm` 134.45 × 2 tracks = 268.9 mm) and the steel
|
|
2835
|
-
* guide-rail block the V-rollers engage is ~37 mm wide × ~18 mm tall (close to
|
|
2836
|
-
* `railWidthMm` 44 / `railHeightMm` 15 here). The tall central structure in
|
|
2837
|
-
* the STP is the motor stator track, which the viewer renders as the module
|
|
2838
|
-
* GLB — not part of this guide-rail profile.
|
|
3284
|
+
* Default dimensions (mm), measured from the CAD named in the module header.
|
|
2839
3285
|
*
|
|
2840
|
-
*
|
|
2841
|
-
*
|
|
2842
|
-
* no dedicated STP and reuses this TL1-derived profile.
|
|
3286
|
+
* Kept in sync by hand with `public/models/Hepco-GFX.rail.meta.json`, which the
|
|
3287
|
+
* playground loads so the calibration panel can override them at runtime.
|
|
2843
3288
|
*/
|
|
2844
3289
|
declare const HEPCO_GFX_PROFILE: HepcoGfxRailProfileDims;
|
|
2845
3290
|
/**
|
|
@@ -2849,22 +3294,64 @@ declare const HEPCO_GFX_PROFILE: HepcoGfxRailProfileDims;
|
|
|
2849
3294
|
*/
|
|
2850
3295
|
declare function resolveHepcoGfxProfile(override?: Partial<HepcoGfxRailProfileDims>): HepcoGfxRailProfileDims;
|
|
2851
3296
|
/**
|
|
2852
|
-
* Build the
|
|
2853
|
-
*
|
|
2854
|
-
*
|
|
2855
|
-
*
|
|
3297
|
+
* Build the rail's closed outline in natural (Y, Z) coordinates, before the
|
|
3298
|
+
* stack offsets are applied. Exported so tests and calibration tooling can
|
|
3299
|
+
* reason about the real cross-section without going through the shape frame.
|
|
3300
|
+
*
|
|
3301
|
+
* Traversal starts at the top face's -Y end and runs to +Y, down the +Y flank,
|
|
3302
|
+
* along the underside (through the channel between the two feet) and back up
|
|
3303
|
+
* the -Y flank — the same winding the previous scaffolding outline used.
|
|
3304
|
+
*/
|
|
3305
|
+
declare function buildHepcoGfxRailOutline(dims?: HepcoGfxRailProfileDims): ProfilePoint[];
|
|
3306
|
+
/**
|
|
3307
|
+
* Build the baseplate outline in natural (Y, Z) coordinates.
|
|
3308
|
+
*
|
|
3309
|
+
* `slotDepthMm` trims the plate's inner edge back by that much — used to
|
|
3310
|
+
* extrude the plate's continuous web, with `buildHepcoGfxBaseplateBridgeOutline`
|
|
3311
|
+
* filling the trimmed strip back in between the central slots. Pass 0 (the
|
|
3312
|
+
* default) for the plain full-width section.
|
|
3313
|
+
*/
|
|
3314
|
+
declare function buildHepcoGfxBaseplateOutline(dims?: HepcoGfxRailProfileDims, slotDepthMm?: number): ProfilePoint[];
|
|
3315
|
+
/**
|
|
3316
|
+
* The strip the central slot removes from the plate's inner edge. Extruded in
|
|
3317
|
+
* short runs between the slots so the slots come out as real openings rather
|
|
3318
|
+
* than painted-on darkness.
|
|
3319
|
+
*/
|
|
3320
|
+
declare function buildHepcoGfxBaseplateBridgeOutline(dims?: HepcoGfxRailProfileDims): ProfilePoint[];
|
|
3321
|
+
/**
|
|
3322
|
+
* Build the GFX-UMB bracket outline: a full-width base block with the upper
|
|
3323
|
+
* wall standing on its outer edge, so the section is an L opening toward the
|
|
3324
|
+
* oval's centreline.
|
|
2856
3325
|
*/
|
|
2857
|
-
declare function
|
|
3326
|
+
declare function buildHepcoGfxBracketOutline(dims?: HepcoGfxRailProfileDims): ProfilePoint[];
|
|
3327
|
+
/** The bracket's upper wall alone — the part the windows interrupt. */
|
|
3328
|
+
declare function buildHepcoGfxBracketWallOutline(dims?: HepcoGfxRailProfileDims): ProfilePoint[];
|
|
2858
3329
|
/**
|
|
2859
|
-
*
|
|
2860
|
-
*
|
|
2861
|
-
* spear apexes (at Y=±railWidth/2, Z=midZ).
|
|
3330
|
+
* The GFX-UMB-EB end block that caps an open end of the bracket run: the
|
|
3331
|
+
* bracket's full width, but only over the upper wall's height band.
|
|
2862
3332
|
*
|
|
2863
|
-
* The
|
|
2864
|
-
*
|
|
2865
|
-
*
|
|
3333
|
+
* The real part is 84.7 mm wide and ties the Part's bracket to its opposite
|
|
3334
|
+
* number across the oval's centreline. The viewer draws one bracket per Part,
|
|
3335
|
+
* so it gets the half that belongs to this one — full bracket width, which is
|
|
3336
|
+
* what closes the visible end of the wall.
|
|
2866
3337
|
*/
|
|
3338
|
+
declare function buildHepcoGfxBracketEndBlockOutline(dims?: HepcoGfxRailProfileDims): ProfilePoint[];
|
|
3339
|
+
/** The bracket's base block alone — continuous under the windowed wall. */
|
|
3340
|
+
declare function buildHepcoGfxBracketBaseOutline(dims?: HepcoGfxRailProfileDims): ProfilePoint[];
|
|
3341
|
+
/** Baseplate cross-section (mm). See `buildHepcoGfxBaseplateOutline`. */
|
|
3342
|
+
declare function createHepcoGfxBaseplateShape(dims?: HepcoGfxRailProfileDims, slotDepthMm?: number): Shape;
|
|
3343
|
+
/** Baseplate inner-edge strip between two central slots (mm). */
|
|
3344
|
+
declare function createHepcoGfxBaseplateBridgeShape(dims?: HepcoGfxRailProfileDims): Shape;
|
|
3345
|
+
/** Rail cross-section (mm). See `buildHepcoGfxRailOutline`. */
|
|
2867
3346
|
declare function createHepcoGfxRailShape(dims?: HepcoGfxRailProfileDims): Shape;
|
|
3347
|
+
/** Full GFX-UMB bracket cross-section (mm). */
|
|
3348
|
+
declare function createHepcoGfxBracketShape(dims?: HepcoGfxRailProfileDims): Shape;
|
|
3349
|
+
/** GFX-UMB base block cross-section (mm). */
|
|
3350
|
+
declare function createHepcoGfxBracketBaseShape(dims?: HepcoGfxRailProfileDims): Shape;
|
|
3351
|
+
/** GFX-UMB upper wall cross-section (mm). */
|
|
3352
|
+
declare function createHepcoGfxBracketWallShape(dims?: HepcoGfxRailProfileDims): Shape;
|
|
3353
|
+
/** GFX-UMB-EB end block cross-section (mm). */
|
|
3354
|
+
declare function createHepcoGfxBracketEndBlockShape(dims?: HepcoGfxRailProfileDims): Shape;
|
|
2868
3355
|
|
|
2869
3356
|
/**
|
|
2870
3357
|
* <XtsViewer3D> — Top-level React component.
|
|
@@ -2913,30 +3400,38 @@ interface XtsViewer3DProps {
|
|
|
2913
3400
|
/**
|
|
2914
3401
|
* Live camera projection. Default `'perspective'`.
|
|
2915
3402
|
*
|
|
2916
|
-
* `'orthographic'`
|
|
2917
|
-
* 2D plan view — straight down +Z, up = +Y — pixel-consistent with
|
|
3403
|
+
* `'orthographic'` renders the flat 2D plan view — straight down +Z, up = +Y — pixel-consistent with
|
|
2918
3404
|
* `exportScreenshot({ mode: 'top-down' })`. Switching at runtime does NOT
|
|
2919
3405
|
* recreate the WebGL context. In orthographic top-down, rotation is
|
|
2920
3406
|
* auto-disabled (pan + zoom stay on) unless you set `lock.rotate` explicitly.
|
|
2921
3407
|
*/
|
|
2922
3408
|
projection?: CameraProjection;
|
|
2923
|
-
/**
|
|
2924
|
-
* When `projection === 'orthographic'`, frame the scene straight from above.
|
|
2925
|
-
* Default `true`. (Only `true` is meaningful today; reserved for future
|
|
2926
|
-
* orthographic side/front views.)
|
|
2927
|
-
*/
|
|
2928
|
-
topDown?: boolean;
|
|
2929
3409
|
/** Lock interactions. */
|
|
2930
3410
|
lock?: InteractionLocks;
|
|
3411
|
+
/**
|
|
3412
|
+
* Move the camera through the scene with the keyboard instead of only
|
|
3413
|
+
* orbiting it: arrow keys / WASD walk along the ground plane, PageUp /
|
|
3414
|
+
* PageDown climb, Shift is faster and Alt finer. On by default; the canvas
|
|
3415
|
+
* takes focus on pointer-down, so the keys arm themselves on the first
|
|
3416
|
+
* click into the viewer.
|
|
3417
|
+
*/
|
|
3418
|
+
keyboardNavigation?: KeyboardNavigationOptions;
|
|
2931
3419
|
/** Selection-Mode. Default: 'Off'. */
|
|
2932
3420
|
selectionMode?: SelectionMode;
|
|
3421
|
+
/**
|
|
3422
|
+
* What a click picks out. Default `'module'` — the module or mover under
|
|
3423
|
+
* the pointer, as before.
|
|
3424
|
+
*
|
|
3425
|
+
* `'track'` selects the whole processing unit instead, whichever part of
|
|
3426
|
+
* it was hit: that is the granularity the transform gizmo works at, since
|
|
3427
|
+
* a single module has no pose of its own. Static custom assets are always
|
|
3428
|
+
* selected in their own right, in either scope.
|
|
3429
|
+
*/
|
|
3430
|
+
selectionScope?: SelectionScope;
|
|
2933
3431
|
/** Controlled selection. */
|
|
2934
3432
|
selection?: SelectionState;
|
|
2935
3433
|
/** Selection-Highlight-Farben. */
|
|
2936
|
-
selectionColors?:
|
|
2937
|
-
module?: string;
|
|
2938
|
-
mover?: string;
|
|
2939
|
-
};
|
|
3434
|
+
selectionColors?: SelectionColors;
|
|
2940
3435
|
/**
|
|
2941
3436
|
* Declarative module highlights — tint all modules matching the given
|
|
2942
3437
|
* types with the specified color. Works independently of selection:
|
|
@@ -3007,6 +3502,8 @@ interface XtsViewer3DProps {
|
|
|
3007
3502
|
onCameraChange?: (c: CameraState) => void;
|
|
3008
3503
|
onMoverClick?: (m: MoverRef) => void;
|
|
3009
3504
|
onModuleClick?: (m: ModuleRef) => void;
|
|
3505
|
+
/** A click on a static custom asset (`binding.type === 'static'`). */
|
|
3506
|
+
onCustomAssetClick?: (a: CustomAssetRef) => void;
|
|
3010
3507
|
onMoverHover?: (m: MoverRef | null) => void;
|
|
3011
3508
|
onModuleHover?: (m: ModuleRef | null) => void;
|
|
3012
3509
|
onError?: (err: XtsViewerError) => void;
|
|
@@ -3030,30 +3527,35 @@ interface XtsViewer3DProps {
|
|
|
3030
3527
|
hepcoGfxProfile?: Partial<HepcoGfxRailProfileDims>;
|
|
3031
3528
|
/** CAD-style ViewCube overlay. */
|
|
3032
3529
|
viewCube?: ViewCubeOptions;
|
|
3530
|
+
/**
|
|
3531
|
+
* Translate / rotate / scale handles on the selection — a whole track, or
|
|
3532
|
+
* a static custom asset picked in its own right (issue #107). A single
|
|
3533
|
+
* module or mover has no pose of its own and never gets handles, which is
|
|
3534
|
+
* what `selectionScope: 'track'` is for. Off by default; see
|
|
3535
|
+
* `TransformGizmoOptions` for what a drag reports and how it reaches the
|
|
3536
|
+
* configuration.
|
|
3537
|
+
*
|
|
3538
|
+
* @example
|
|
3539
|
+
* ```tsx
|
|
3540
|
+
* <XtsViewer3D
|
|
3541
|
+
* selectionMode="Single"
|
|
3542
|
+
* selectionScope="track"
|
|
3543
|
+
* transformGizmo={{ enabled: true, mode: 'translate' }}
|
|
3544
|
+
* />
|
|
3545
|
+
* ```
|
|
3546
|
+
*/
|
|
3547
|
+
transformGizmo?: TransformGizmoOptions;
|
|
3033
3548
|
/** Stator heatmap data — coloured tube along each part's centerline. */
|
|
3034
3549
|
statorHeatmap?: StatorHeatmap;
|
|
3035
|
-
/** Continuous mover collision monitoring. */
|
|
3036
|
-
collisionDetection?: {
|
|
3037
|
-
enabled?: boolean;
|
|
3038
|
-
warningGapMm?: number;
|
|
3039
|
-
intervalMs?: number;
|
|
3040
|
-
forceClosedLoop?: boolean;
|
|
3041
|
-
onCollisionsChange?: (collisions: MoverCollision[]) => void;
|
|
3042
|
-
};
|
|
3043
3550
|
/**
|
|
3044
|
-
*
|
|
3045
|
-
*
|
|
3046
|
-
*
|
|
3047
|
-
*
|
|
3048
|
-
*
|
|
3049
|
-
*
|
|
3050
|
-
* when the overlap set actually changes.
|
|
3551
|
+
* Collision monitoring, for movers and for modules.
|
|
3552
|
+
*
|
|
3553
|
+
* One prop with two halves rather than two near-identical props: both
|
|
3554
|
+
* carry `enabled`, `warningGapMm` and `onCollisionsChange`, and a caller
|
|
3555
|
+
* reading the API had no way to tell from the names that
|
|
3556
|
+
* `collisionDetection` meant movers specifically.
|
|
3051
3557
|
*/
|
|
3052
|
-
|
|
3053
|
-
enabled?: boolean;
|
|
3054
|
-
warningGapMm?: number;
|
|
3055
|
-
onCollisionsChange?: (collisions: ModuleCollision[]) => void;
|
|
3056
|
-
};
|
|
3558
|
+
collision?: CollisionOptions;
|
|
3057
3559
|
/**
|
|
3058
3560
|
* CAD-style measurement tools + annotations, in the 3D view and the 2D
|
|
3059
3561
|
* plan view alike.
|
|
@@ -3111,9 +3613,72 @@ interface MoverCatalogEntry {
|
|
|
3111
3613
|
magnetPlateCenterMm: Vec3;
|
|
3112
3614
|
}
|
|
3113
3615
|
declare const MOVER_CATALOG: Readonly<Record<MoverType3D, MoverCatalogEntry>>;
|
|
3616
|
+
/**
|
|
3617
|
+
* The magnet plate set already modelled into each mover's own GLB, or `null`
|
|
3618
|
+
* for a carriage that ships bare and needs one attached.
|
|
3619
|
+
*
|
|
3620
|
+
* Nearly every mover asset is a combined carriage+plate assembly: the Beckhoff
|
|
3621
|
+
* movers say so in the filename (`AT9014-0055-0550` carries an AT9001-0550),
|
|
3622
|
+
* and each Hepco carriage records the plate it was ordered with in its
|
|
3623
|
+
* sidecar's `_calibration.hepcoPartNumber` (`…_AT9001-0775` and friends).
|
|
3624
|
+
* Attaching a second plate to one of those would draw a plate through a plate,
|
|
3625
|
+
* so `<XtsMoverPlate>` renders only where this table says `null`.
|
|
3626
|
+
*
|
|
3627
|
+
* `Custom` is `null` because its geometry comes from `customMoverLayout`,
|
|
3628
|
+
* which may or may not include a plate — the host decides.
|
|
3629
|
+
*
|
|
3630
|
+
* Exhaustive over `MoverType3D` on purpose (`Record`, not `Partial<Record>`):
|
|
3631
|
+
* a new mover type has to state which it is, so it cannot silently end up
|
|
3632
|
+
* double-plated or plate-less.
|
|
3633
|
+
*/
|
|
3634
|
+
declare const MOVER_INTEGRATED_PLATE: Readonly<Record<MoverType3D, MagnetPlateType3D | null>>;
|
|
3635
|
+
/**
|
|
3636
|
+
* Plate to attach when a bare carriage has not been given one explicitly.
|
|
3637
|
+
* The 5-pole AT9001-0550 is what the equivalent `_C25` carriage ships with,
|
|
3638
|
+
* and it models the encoder flag the bare S25 is otherwise missing.
|
|
3639
|
+
*/
|
|
3640
|
+
declare const DEFAULT_MAGNET_PLATE_TYPE: MagnetPlateType3D;
|
|
3641
|
+
/**
|
|
3642
|
+
* The plate that should render on top of `moverType`, or `null` when the
|
|
3643
|
+
* mover already has one of its own (or is `Custom`).
|
|
3644
|
+
*/
|
|
3645
|
+
declare function resolveAttachedPlateType(moverType: MoverType3D, configured?: MagnetPlateType3D): MagnetPlateType3D | null;
|
|
3114
3646
|
declare function getMoverEntry(moverType: MoverType3D): MoverCatalogEntry | undefined;
|
|
3115
3647
|
declare function isKnownMoverType(value: string): value is MoverType3D;
|
|
3116
3648
|
|
|
3649
|
+
/**
|
|
3650
|
+
* Magnet plate set catalog (3D-canonical).
|
|
3651
|
+
*
|
|
3652
|
+
* A plate set is the part that carries the mover's magnets. Most mover GLBs
|
|
3653
|
+
* ship with one already modelled in (see `MOVER_INTEGRATED_PLATE`); these
|
|
3654
|
+
* entries describe the sets that exist as assets of their own, so a bare
|
|
3655
|
+
* carriage can be given any pole count without a new combined CAD assembly.
|
|
3656
|
+
*
|
|
3657
|
+
* `lengthMm` is the plate's extent along the travel axis, measured from the
|
|
3658
|
+
* converted GLB rather than inferred from the part number. Pole count was
|
|
3659
|
+
* likewise counted in the mesh: each magnet is 7.7 mm wide and sits in a row
|
|
3660
|
+
* just proud of its backing plate, so the magnets separate cleanly along the
|
|
3661
|
+
* travel axis. That count is what establishes the part-number rule (the digit
|
|
3662
|
+
* after the dash is the pole count in base 16) — it is not assumed from it.
|
|
3663
|
+
*/
|
|
3664
|
+
|
|
3665
|
+
interface MagnetPlateCatalogEntry {
|
|
3666
|
+
plateType: MagnetPlateType3D;
|
|
3667
|
+
/** Magnets in one row, i.e. the pole count the part number encodes. */
|
|
3668
|
+
poleCount: number;
|
|
3669
|
+
/** Plate extent along the travel axis, mm (GLB-measured). */
|
|
3670
|
+
lengthMm: number;
|
|
3671
|
+
/**
|
|
3672
|
+
* Whether the set models the encoder flag ("Geberfahne"). The AT9001 sets
|
|
3673
|
+
* all carry it as a thin sheet reaching back along −Z; the hygienic ATH9001
|
|
3674
|
+
* sets encapsulate their rows in sealed stainless and model none.
|
|
3675
|
+
*/
|
|
3676
|
+
hasEncoderFlag: boolean;
|
|
3677
|
+
}
|
|
3678
|
+
declare const MAGNET_PLATE_CATALOG: Readonly<Record<MagnetPlateType3D, MagnetPlateCatalogEntry>>;
|
|
3679
|
+
declare function getMagnetPlateEntry(plateType: MagnetPlateType3D): MagnetPlateCatalogEntry | undefined;
|
|
3680
|
+
declare function isKnownMagnetPlateType(value: unknown): value is MagnetPlateType3D;
|
|
3681
|
+
|
|
3117
3682
|
/**
|
|
3118
3683
|
* Module-local path math.
|
|
3119
3684
|
*
|
|
@@ -3155,33 +3720,21 @@ interface EndDelta {
|
|
|
3155
3720
|
declare function moduleEndDelta(module: ModuleCatalogEntry): EndDelta;
|
|
3156
3721
|
|
|
3157
3722
|
/**
|
|
3158
|
-
*
|
|
3723
|
+
* XtsModuleCurve3 — Three.js Curve3 wrapper around a module's centreline.
|
|
3159
3724
|
*
|
|
3160
|
-
*
|
|
3161
|
-
*
|
|
3162
|
-
*
|
|
3163
|
-
*
|
|
3164
|
-
*
|
|
3165
|
-
* MUST return the actual arc length so subcurves are weighted correctly.
|
|
3166
|
-
* Our subcurves are arc-length parametrised (t = s / L), so this just-works.
|
|
3167
|
-
*/
|
|
3168
|
-
|
|
3169
|
-
declare class XtsArcCurve3 extends Curve<Vector3> {
|
|
3170
|
-
readonly module: ModuleCatalogEntry;
|
|
3171
|
-
constructor(module: ModuleCatalogEntry);
|
|
3172
|
-
getPoint(t: number, optionalTarget?: Vector3): Vector3;
|
|
3173
|
-
getTangent(t: number, optionalTarget?: Vector3): Vector3;
|
|
3174
|
-
getLength(): number;
|
|
3175
|
-
}
|
|
3176
|
-
|
|
3177
|
-
/**
|
|
3178
|
-
* XtsPointCloudCurve3 — Three.js Curve3 wrapper around the Free-path sample.
|
|
3725
|
+
* One class for every `pathType`, because there was never more than one
|
|
3726
|
+
* implementation: `sampleModulePath` already dispatches Straight / Curve /
|
|
3727
|
+
* Free itself, so the two classes this replaces (`XtsArcCurve3` and
|
|
3728
|
+
* `XtsPointCloudCurve3`) were byte-for-byte identical apart from the
|
|
3729
|
+
* `pathType` string each rejected in its constructor.
|
|
3179
3730
|
*
|
|
3180
|
-
*
|
|
3181
|
-
*
|
|
3731
|
+
* Note: CurvePath delegates to subcurves length-proportionally, so
|
|
3732
|
+
* `getLength()` MUST return the actual arc length for them to be weighted
|
|
3733
|
+
* correctly. Our subcurves are arc-length parametrised (t = s / L), so this
|
|
3734
|
+
* just works.
|
|
3182
3735
|
*/
|
|
3183
3736
|
|
|
3184
|
-
declare class
|
|
3737
|
+
declare class XtsModuleCurve3 extends Curve<Vector3> {
|
|
3185
3738
|
readonly module: ModuleCatalogEntry;
|
|
3186
3739
|
constructor(module: ModuleCatalogEntry);
|
|
3187
3740
|
getPoint(t: number, optionalTarget?: Vector3): Vector3;
|
|
@@ -3192,11 +3745,10 @@ declare class XtsPointCloudCurve3 extends Curve<Vector3> {
|
|
|
3192
3745
|
/**
|
|
3193
3746
|
* PathBuilder — assemble a `THREE.CurvePath<Vector3>` for a Part.
|
|
3194
3747
|
*
|
|
3195
|
-
* One CurvePath per part, chaining all module curves end-to-end.
|
|
3196
|
-
*
|
|
3197
|
-
*
|
|
3198
|
-
*
|
|
3199
|
-
* - Free → XtsPointCloudCurve3
|
|
3748
|
+
* One CurvePath per part, chaining all module curves end-to-end. Every
|
|
3749
|
+
* module — Straight, Curve or Free alike — becomes an `XtsModuleCurve3`,
|
|
3750
|
+
* which defers to `sampleModulePath`; that function already dispatches on
|
|
3751
|
+
* `pathType`, so doing it a second time here bought nothing.
|
|
3200
3752
|
*
|
|
3201
3753
|
* Each subcurve here is expressed in **module-local coordinates** (origin at
|
|
3202
3754
|
* module entry). The world transform of the module = chain.startWorldPose
|
|
@@ -3224,6 +3776,34 @@ declare function buildPartPath(modules: ReadonlyArray<ModuleEntry>): PartPath;
|
|
|
3224
3776
|
*/
|
|
3225
3777
|
declare function poseToMatrix4(pose: PlanarPose, out?: Matrix4): Matrix4;
|
|
3226
3778
|
|
|
3779
|
+
/**
|
|
3780
|
+
* Pure helpers for composing per-XPU `trackTransform` and root
|
|
3781
|
+
* `XtsConfig.orientation` into Three.js Matrix4 instances. Lives in geometry/
|
|
3782
|
+
* so the component layer (XtsViewer3D, XtsScene) and the test suite share a
|
|
3783
|
+
* single source of truth — no duplicated math.
|
|
3784
|
+
*
|
|
3785
|
+
* The field defaults come from `resolvePose`, which `<PoseGroup>` also uses
|
|
3786
|
+
* to build the corresponding three.js `<group>`. That is what keeps the
|
|
3787
|
+
* matrix these functions return describing the same pose the renderer draws;
|
|
3788
|
+
* `pose.test.ts` asserts it directly.
|
|
3789
|
+
*
|
|
3790
|
+
* Composition order:
|
|
3791
|
+
*
|
|
3792
|
+
* world = orientation ⊗ trackTransform ⊗ partTransformation ⊗ chain
|
|
3793
|
+
*
|
|
3794
|
+
* `partTransformation` is applied inside the per-XPU group by XtsScene, so
|
|
3795
|
+
* the helpers here only cover the outermost two layers.
|
|
3796
|
+
*/
|
|
3797
|
+
|
|
3798
|
+
/**
|
|
3799
|
+
* Default Z lift for a track that does not specify `positionMm`. The XTS
|
|
3800
|
+
* track sits on a base plate / table top in real installations, so 0 mm
|
|
3801
|
+
* (= floor) is rarely what the user wants. 200 mm matches the standard
|
|
3802
|
+
* Beckhoff table height used throughout the demo configs and renders
|
|
3803
|
+
* correctly under the default shadow-casting ground plane.
|
|
3804
|
+
*/
|
|
3805
|
+
declare const DEFAULT_TRACK_Z_MM = 200;
|
|
3806
|
+
|
|
3227
3807
|
/**
|
|
3228
3808
|
* Point-cloud registry for Free-path modules.
|
|
3229
3809
|
*
|
|
@@ -3328,6 +3908,67 @@ declare function interpolateHeatmapValue(samples: ReadonlyArray<HeatmapSample>,
|
|
|
3328
3908
|
*/
|
|
3329
3909
|
declare function normaliseToHeatmapRange(value: number, min: number, max: number): number;
|
|
3330
3910
|
|
|
3911
|
+
/**
|
|
3912
|
+
* The vertical extent of a track module, in the path frame.
|
|
3913
|
+
*
|
|
3914
|
+
* `moduleCollision.ts` needs to know how tall a module is and where it sits
|
|
3915
|
+
* relative to the mover path. It used to assume one coarse box for every type —
|
|
3916
|
+
* `0 … DEFAULT_MODULE_HEIGHT_MM`, entirely *above* the path plane — which is
|
|
3917
|
+
* neither the right height nor the right side: a motor module measures about
|
|
3918
|
+
* 40 mm and straddles the plane, roughly half above and half below. Two tracks
|
|
3919
|
+
* stacked closer than 100 mm therefore reported an overlap that does not exist,
|
|
3920
|
+
* and the deepest ones reported it on every module pair.
|
|
3921
|
+
*
|
|
3922
|
+
* ## Why only Z, and not the width too
|
|
3923
|
+
*
|
|
3924
|
+
* The GLB sidecar carries `approximateBoundsMm`, the axis-aligned box of the
|
|
3925
|
+
* CAD body *before* `originCorrection` is applied. Composing the two gives the
|
|
3926
|
+
* body in the path frame — but only its **Z** range is a cross-section.
|
|
3927
|
+
*
|
|
3928
|
+
* Module paths are planar in XY and curves bend about Z, so a curved module's
|
|
3929
|
+
* GLB spans its whole arc in X and Y: the 180° AT2050_0500 measures 307 mm
|
|
3930
|
+
* across in Y, which is the sweep of the U-turn and not how wide the beam is.
|
|
3931
|
+
* Its Z range, on the other hand, is the same 40.1 mm as the straight it is
|
|
3932
|
+
* built from, because the sweep never leaves the plane.
|
|
3933
|
+
*
|
|
3934
|
+
* So the height is read from the asset and the half-width stays the coarse
|
|
3935
|
+
* constant. Taking the width from the same box would inflate every curve
|
|
3936
|
+
* segment to the size of its arc and report a collision for every oval.
|
|
3937
|
+
*
|
|
3938
|
+
* Overrides are not consulted: `SidecarOverrides` can replace a module's
|
|
3939
|
+
* `originCorrection` but carries no bounds, so there is nothing to override.
|
|
3940
|
+
*/
|
|
3941
|
+
|
|
3942
|
+
/** Where a module's body sits along Z, relative to the mover path plane. */
|
|
3943
|
+
interface ModuleHeightSpan {
|
|
3944
|
+
/** Lowest point of the body [mm]; negative means below the path plane. */
|
|
3945
|
+
minZMm: number;
|
|
3946
|
+
/** Highest point of the body [mm]. */
|
|
3947
|
+
maxZMm: number;
|
|
3948
|
+
}
|
|
3949
|
+
/**
|
|
3950
|
+
* The span a module without usable bounds falls back to: the historic coarse
|
|
3951
|
+
* box, so a type the asset package does not describe behaves as it always did.
|
|
3952
|
+
*/
|
|
3953
|
+
declare function coarseHeightSpan(heightMm: number): ModuleHeightSpan;
|
|
3954
|
+
/**
|
|
3955
|
+
* Compose `approximateBoundsMm` through `originCorrection` and keep the Z range.
|
|
3956
|
+
*
|
|
3957
|
+
* All eight corners are transformed rather than only the two extremes: every
|
|
3958
|
+
* shipped correction happens to be a multiple of 90°, which would keep the box
|
|
3959
|
+
* axis-aligned, but nothing in the sidecar format promises that, and under a
|
|
3960
|
+
* 45° correction two corners would describe a box far too small.
|
|
3961
|
+
*/
|
|
3962
|
+
declare function heightSpanOf(sidecar: ModuleSidecar): ModuleHeightSpan | null;
|
|
3963
|
+
/**
|
|
3964
|
+
* How tall `moduleType` is and where it sits, or the coarse fallback.
|
|
3965
|
+
*
|
|
3966
|
+
* Resolved per module type rather than once per run: an Eco straight, a
|
|
3967
|
+
* hygienic module with its tower and an AT2002 with its infeed box are three
|
|
3968
|
+
* different heights, and a stack has to be judged against the one it holds.
|
|
3969
|
+
*/
|
|
3970
|
+
declare function moduleHeightSpan(moduleType: ModuleType3D, fallbackHeightMm: number): ModuleHeightSpan;
|
|
3971
|
+
|
|
3331
3972
|
/**
|
|
3332
3973
|
* Shared closed-loop detection for built chains.
|
|
3333
3974
|
*
|
|
@@ -3345,6 +3986,34 @@ declare function normaliseToHeatmapRange(value: number, min: number, max: number
|
|
|
3345
3986
|
*/
|
|
3346
3987
|
declare function isChainClosed(chain: BuiltChain): boolean;
|
|
3347
3988
|
|
|
3989
|
+
/**
|
|
3990
|
+
* ARGB integers ↔ CSS hex.
|
|
3991
|
+
*
|
|
3992
|
+
* `StationConfig.stationColor` and `AreaConfig.color` arrive as 32-bit ARGB
|
|
3993
|
+
* ints, which is how the 2D canvas tooling upstream of this viewer stores
|
|
3994
|
+
* colour. three.js wants `#RRGGBB`.
|
|
3995
|
+
*
|
|
3996
|
+
* These lived inside `XtsStations.tsx` and were imported across to
|
|
3997
|
+
* `XtsAreas` and `XtsStopPositionMovers` — a bit-twiddling function reached
|
|
3998
|
+
* through a component. They were also not exported from the package, so the
|
|
3999
|
+
* playground wrote its own copy of the same shifts to render the colour
|
|
4000
|
+
* pickers.
|
|
4001
|
+
*/
|
|
4002
|
+
/**
|
|
4003
|
+
* `0xAARRGGBB` → `'#RRGGBB'`.
|
|
4004
|
+
*
|
|
4005
|
+
* Alpha is dropped: three.js handles opacity through
|
|
4006
|
+
* `Material.transparent` / `.opacity`, separately from base colour.
|
|
4007
|
+
*/
|
|
4008
|
+
declare function argbIntToHex(argb: number): string;
|
|
4009
|
+
/**
|
|
4010
|
+
* `'#RRGGBB'` → `0xFFRRGGBB`, the inverse of {@link argbIntToHex}.
|
|
4011
|
+
*
|
|
4012
|
+
* Alpha is set to fully opaque, since the hex form carries none. A string
|
|
4013
|
+
* that is not a six-digit hex colour yields opaque black rather than `NaN`.
|
|
4014
|
+
*/
|
|
4015
|
+
declare function hexToArgbInt(hex: string): number;
|
|
4016
|
+
|
|
3348
4017
|
/**
|
|
3349
4018
|
* Feed segments (Einspeisestränge).
|
|
3350
4019
|
*
|
|
@@ -3435,8 +4104,126 @@ declare function collectFeedSegmentsForXpu(xpu: ProcessingUnitConfig, chainsByPa
|
|
|
3435
4104
|
* (rendering, imperative API, bounding-box) sees the corrected values.
|
|
3436
4105
|
*/
|
|
3437
4106
|
|
|
4107
|
+
/**
|
|
4108
|
+
* The Y / Z-Euler flip on a single track transform.
|
|
4109
|
+
*
|
|
4110
|
+
* The flip is its own inverse, so this is also how a value the viewer
|
|
4111
|
+
* computed in its own frame — a pose dragged with the transform gizmo — is
|
|
4112
|
+
* handed back in the convention the host states its transforms in.
|
|
4113
|
+
*/
|
|
4114
|
+
declare function applyBeckhoffXtsConventionToTrackTransform(tt: TrackTransform): TrackTransform;
|
|
4115
|
+
/**
|
|
4116
|
+
* The same Y / Z-Euler flip, applied to the imperative
|
|
4117
|
+
* `trackTransformOverrides` map.
|
|
4118
|
+
*
|
|
4119
|
+
* `effectiveTrackTransform` returns an override *instead of* the config
|
|
4120
|
+
* value, so an override that skipped this pass would be interpreted in the
|
|
4121
|
+
* opposite Y convention from the config entry it replaces — the track would
|
|
4122
|
+
* jump to the mirrored side the moment a caller touched it. Both inputs have
|
|
4123
|
+
* to cross the convention boundary together.
|
|
4124
|
+
*/
|
|
4125
|
+
declare function applyBeckhoffXtsConventionToOverrides(overrides: Record<number, TrackTransform> | undefined): Record<number, TrackTransform> | undefined;
|
|
3438
4126
|
declare function applyBeckhoffXtsConvention(config: NormalizedXtsConfig): NormalizedXtsConfig;
|
|
3439
4127
|
|
|
4128
|
+
/**
|
|
4129
|
+
* HepcoGfxLayout — where the Hepco GFX guideway's repeating features sit along
|
|
4130
|
+
* a Part's chained path.
|
|
4131
|
+
*
|
|
4132
|
+
* Everything here is pure arc-length arithmetic: spans are `[startMm, endMm]`
|
|
4133
|
+
* measured from the start of the chain, and holes are `{ atMm, lateralMm }`.
|
|
4134
|
+
* <HepcoGfxRail> turns those into curve parameters and transforms. Keeping the
|
|
4135
|
+
* layout out of the component is what makes the pattern testable without a
|
|
4136
|
+
* renderer, the same split `moverInstancing.ts` uses for the mover batch.
|
|
4137
|
+
*
|
|
4138
|
+
* ## Where the patterns come from
|
|
4139
|
+
*
|
|
4140
|
+
* Measured off `stepfiles/hepco-gfx-rail/BA-GFX-1TL-3M-B.stp`. In that assembly
|
|
4141
|
+
* the baseplate's module grid runs at 250 mm, and every feature keys off it:
|
|
4142
|
+
*
|
|
4143
|
+
* - Central plate slots: 104.78 mm long, centred ON the module boundaries.
|
|
4144
|
+
* - Plate M8 bores: Ø8.5, 62.5 mm pitch (= 250/4) from a module boundary,
|
|
4145
|
+
* in rows at 30 and 80 mm either side of the plate's centreline.
|
|
4146
|
+
* - Bracket lightening bores: Ø18, 125 mm pitch, on the 30 mm row so they sit
|
|
4147
|
+
* directly over every second M8 bore.
|
|
4148
|
+
* - Bracket wall windows: one 40.23 mm window and two 5.96 mm slots per
|
|
4149
|
+
* 250 mm, at the offsets in `WALL_WINDOWS_MM`.
|
|
4150
|
+
*
|
|
4151
|
+
* The brackets themselves come as `GFX-UMB-1RH` (240.30 mm) and `-2LH`
|
|
4152
|
+
* (490.30 mm) parts — i.e. 250·n − 9.70. Consecutive parts butt exactly on a
|
|
4153
|
+
* module boundary; the 9.70 mm shortfall is taken once at each END of a run,
|
|
4154
|
+
* not as a gap at every joint, and a `GFX-UMB-EB` end block closes each end.
|
|
4155
|
+
*
|
|
4156
|
+
* ## Where a bracket can go
|
|
4157
|
+
*
|
|
4158
|
+
* Not everywhere. The bracket's inner face sits 114 mm inboard of the path, so
|
|
4159
|
+
* on a tight enough turn that face is swept on a radius near zero and the beam
|
|
4160
|
+
* is crushed into the centre of the arc, inside the very module it carries. See
|
|
4161
|
+
* `carriesBracket` for the criterion and the numbers behind it — the catalogue
|
|
4162
|
+
* splits cleanly, with the 22.5 and 45 degree curves nowhere near the limit and
|
|
4163
|
+
* only the AT2050 clothoids past it.
|
|
4164
|
+
*
|
|
4165
|
+
* The 250 mm grid is measured from the start of the chain rather than from each
|
|
4166
|
+
* module, so it stays continuous across the 233 mm and 249 mm short modules —
|
|
4167
|
+
* which is also what the real extruded bracket does, being stock cut to length.
|
|
4168
|
+
*/
|
|
4169
|
+
|
|
4170
|
+
/** A run along the path, in mm from the start of the chain. */
|
|
4171
|
+
interface Span {
|
|
4172
|
+
readonly startMm: number;
|
|
4173
|
+
readonly endMm: number;
|
|
4174
|
+
}
|
|
4175
|
+
/** One bore, as a point on the path plus a lateral offset in the path frame. */
|
|
4176
|
+
interface BorePlacement {
|
|
4177
|
+
readonly atMm: number;
|
|
4178
|
+
/** Lateral offset from the rail centreline, before `stackYOffsetMm` (mm). */
|
|
4179
|
+
readonly lateralMm: number;
|
|
4180
|
+
}
|
|
4181
|
+
/**
|
|
4182
|
+
* The strips of plate that survive between the central slots — the inner-edge
|
|
4183
|
+
* band the slots would otherwise remove, put back between them.
|
|
4184
|
+
*
|
|
4185
|
+
* The slots are centred on the feature grid, so the first and last are cut off
|
|
4186
|
+
* by the ends of the chain; `invertSpans` clips them rather than dropping them,
|
|
4187
|
+
* which is what a plate cut to length actually looks like.
|
|
4188
|
+
*/
|
|
4189
|
+
declare function hepcoGfxBaseplateBridgeSpans(chain: BuiltChain, dims: HepcoGfxRailProfileDims): Span[];
|
|
4190
|
+
/** A stretch of bracket, and whether each of its ends is a real end. */
|
|
4191
|
+
interface BracketRun extends Span {
|
|
4192
|
+
/** The run begins at an end of the bracket, not at a wrap-around seam. */
|
|
4193
|
+
readonly capStart: boolean;
|
|
4194
|
+
readonly capEnd: boolean;
|
|
4195
|
+
}
|
|
4196
|
+
/**
|
|
4197
|
+
* The stretches the bracket occupies: one per contiguous run of modules that
|
|
4198
|
+
* can carry one, each inset by `HEPCO_BRACKET_END_INSET_MM` at its capped ends.
|
|
4199
|
+
*
|
|
4200
|
+
* A closed chain that begins and ends mid-straight has one run split across the
|
|
4201
|
+
* seam. Those two halves are still extruded separately — the spline runs from 0
|
|
4202
|
+
* to L and cannot cross the seam — but neither is inset or capped there, so the
|
|
4203
|
+
* bracket reads as continuous through it.
|
|
4204
|
+
*/
|
|
4205
|
+
declare function hepcoGfxBracketRuns(chain: BuiltChain, closed: boolean, dims: HepcoGfxRailProfileDims): BracketRun[];
|
|
4206
|
+
/**
|
|
4207
|
+
* The solid stretches of the bracket's upper wall — the complement of the
|
|
4208
|
+
* windows, clipped to the bracket run.
|
|
4209
|
+
*/
|
|
4210
|
+
declare function hepcoGfxBracketWallSpans(run: Span): Span[];
|
|
4211
|
+
/**
|
|
4212
|
+
* The GFX-UMB-EB end blocks: one at each capped end of each run.
|
|
4213
|
+
*
|
|
4214
|
+
* They occupy the first and last 15 mm OF a run, not a further 15 mm beyond it:
|
|
4215
|
+
* in the CAD the run starts at X = -378.04 and the end block occupies
|
|
4216
|
+
* -378.04 to -363.04. The block spans the bracket's full 40 mm width where the
|
|
4217
|
+
* wall above it is only 15 mm, so what it does is close off the open end of the
|
|
4218
|
+
* wall — which is why it has to sit inside the run to be seen. A seam end on a
|
|
4219
|
+
* closed chain is not an end and gets none.
|
|
4220
|
+
*/
|
|
4221
|
+
declare function hepcoGfxBracketEndBlockSpans(runs: readonly BracketRun[]): Span[];
|
|
4222
|
+
/** Every M8 clearance bore in the plate that falls on open plate. */
|
|
4223
|
+
declare function hepcoGfxPlateBores(chain: BuiltChain, dims: HepcoGfxRailProfileDims): BorePlacement[];
|
|
4224
|
+
/** Every lightening bore through the bracket's base blocks. */
|
|
4225
|
+
declare function hepcoGfxBracketBores(runs: readonly BracketRun[], dims: HepcoGfxRailProfileDims): BorePlacement[];
|
|
4226
|
+
|
|
3440
4227
|
/**
|
|
3441
4228
|
* Path-sample helpers — convert a chain + a [startMm, endMm] range into a
|
|
3442
4229
|
* world-frame polyline of Vector3s. Used by Stations, Dimensions and
|
|
@@ -3482,9 +4269,15 @@ declare function sampleChainPoint(chain: BuiltChain, positionMm: number, liftZ?:
|
|
|
3482
4269
|
* - Multi → ctrl/meta-click adds, shift-click range-selects (modules
|
|
3483
4270
|
* within the same part only)
|
|
3484
4271
|
*
|
|
3485
|
-
* Range-select is module-only. For movers,
|
|
3486
|
-
* plain replacing click — there is no natural
|
|
3487
|
-
*
|
|
4272
|
+
* Range-select is module-only. For movers, tracks and static custom assets,
|
|
4273
|
+
* shift-click behaves as a plain replacing click — there is no natural
|
|
4274
|
+
* "range" along any of those lists. Use ctrl/meta for additive selection.
|
|
4275
|
+
*
|
|
4276
|
+
* Selecting a whole track (`applyTrackClick`, what `selectionScope: 'track'`
|
|
4277
|
+
* dispatches to) selects everything on it: `isModuleSelected` and
|
|
4278
|
+
* `isMoverSelected` answer `true` for its contents without those entries
|
|
4279
|
+
* being listed one by one, so the track tints as one object and a host
|
|
4280
|
+
* asking "is this module selected" gets the answer it means.
|
|
3488
4281
|
*/
|
|
3489
4282
|
|
|
3490
4283
|
type ClickModifiers = {
|
|
@@ -3493,11 +4286,24 @@ type ClickModifiers = {
|
|
|
3493
4286
|
meta?: boolean;
|
|
3494
4287
|
};
|
|
3495
4288
|
declare const EMPTY_SELECTION: SelectionState;
|
|
4289
|
+
declare function isTrackSelected(selection: SelectionState, ref: TrackRef): boolean;
|
|
3496
4290
|
declare function isModuleSelected(selection: SelectionState, ref: ModuleRef): boolean;
|
|
3497
4291
|
declare function isMoverSelected(selection: SelectionState, ref: MoverRef): boolean;
|
|
4292
|
+
declare function isCustomAssetSelected(selection: SelectionState, ref: CustomAssetRef): boolean;
|
|
3498
4293
|
declare function deselectAll(): SelectionState;
|
|
3499
4294
|
declare function applyModuleClick(selection: SelectionState, ref: ModuleRef, mode: SelectionMode, modifiers?: ClickModifiers): SelectionState;
|
|
3500
4295
|
declare function applyMoverClick(selection: SelectionState, ref: MoverRef, mode: SelectionMode, modifiers?: ClickModifiers): SelectionState;
|
|
4296
|
+
/**
|
|
4297
|
+
* A click anywhere on a track, under `selectionScope: 'track'`. Whichever
|
|
4298
|
+
* module or mover was hit, what ends up selected is the processing unit —
|
|
4299
|
+
* the thing that actually has a pose to edit.
|
|
4300
|
+
*/
|
|
4301
|
+
declare function applyTrackClick(selection: SelectionState, ref: TrackRef, mode: SelectionMode, modifiers?: ClickModifiers): SelectionState;
|
|
4302
|
+
/**
|
|
4303
|
+
* A click on a static custom asset. Same modes as the others, minus
|
|
4304
|
+
* range-select: custom assets have no order to range over.
|
|
4305
|
+
*/
|
|
4306
|
+
declare function applyCustomAssetClick(selection: SelectionState, ref: CustomAssetRef, mode: SelectionMode, modifiers?: ClickModifiers): SelectionState;
|
|
3501
4307
|
declare function applyEmptyClick(selection: SelectionState, mode: SelectionMode): SelectionState;
|
|
3502
4308
|
|
|
3503
4309
|
/**
|
|
@@ -3530,24 +4336,70 @@ interface Props {
|
|
|
3530
4336
|
declare const ModuleCornerMarkers: React__default.FC<Props>;
|
|
3531
4337
|
|
|
3532
4338
|
/**
|
|
3533
|
-
*
|
|
3534
|
-
* (
|
|
3535
|
-
*
|
|
3536
|
-
* placed at a small +Z offset above the dimension tick so the text and
|
|
3537
|
-
* the marker don't z-fight.
|
|
4339
|
+
* Default axis palette — the red / green / blue triple used by the common
|
|
4340
|
+
* viewport gizmos (three-viewport-gizmo, Blender, Fusion). Bright enough to
|
|
4341
|
+
* stay readable on both the light and the dark canvas backgrounds.
|
|
3538
4342
|
*/
|
|
4343
|
+
declare const GIZMO_AXIS_COLORS: readonly [string, string, string];
|
|
4344
|
+
/** Default handle letters, positive axes first. */
|
|
4345
|
+
declare const GIZMO_AXIS_LABELS: readonly [string, string, string];
|
|
3539
4346
|
|
|
3540
4347
|
/**
|
|
3541
|
-
*
|
|
3542
|
-
*
|
|
3543
|
-
*
|
|
3544
|
-
*
|
|
4348
|
+
* labelFont — resolution of the font URL every 3D text label is drawn
|
|
4349
|
+
* with, and the reason none of them can stall the scene any more.
|
|
4350
|
+
*
|
|
4351
|
+
* troika-three-text loads its font over XHR and, when that request
|
|
4352
|
+
* fails, only logs `Failure loading font <url>`: the internal callback
|
|
4353
|
+
* queue for that URL is never drained. drei's `<Text>` wraps exactly
|
|
4354
|
+
* that callback in `suspend(...)`, so a dangling load becomes a promise
|
|
4355
|
+
* that never settles. With one `<Suspense fallback={null}>` around the
|
|
4356
|
+
* scene, a single unreachable font blanked the whole viewer — geometry,
|
|
4357
|
+
* grid, ViewCube and all — with nothing in the console beyond troika's
|
|
4358
|
+
* own line. Every sample that carried a station or an area label went
|
|
4359
|
+
* dark that way.
|
|
4360
|
+
*
|
|
4361
|
+
* Two things prevent that here:
|
|
4362
|
+
*
|
|
4363
|
+
* 1. The default font is compiled in (`labelFontData.ts`) and handed to
|
|
4364
|
+
* troika as a `blob:` URL, so the common path never touches the
|
|
4365
|
+
* network and cannot fail.
|
|
4366
|
+
* 2. A caller-supplied font URL is HEAD/GET-probed before it is handed
|
|
4367
|
+
* over. Until it answers, labels render in the built-in font; if it
|
|
4368
|
+
* never answers, they keep rendering in the built-in font and the
|
|
4369
|
+
* failure is reported once, loudly. troika is never given a URL that
|
|
4370
|
+
* has not already been fetched successfully.
|
|
4371
|
+
*
|
|
4372
|
+
* `<DimensionLabel>` / `<MoverIdLabel>` additionally wrap their `<Text>`
|
|
4373
|
+
* in a local Suspense boundary, so even a font that parses badly costs
|
|
4374
|
+
* one label rather than the scene.
|
|
4375
|
+
*/
|
|
4376
|
+
/**
|
|
4377
|
+
* Default 3D-text font: Roboto Condensed (Latin, regular weight).
|
|
4378
|
+
*
|
|
4379
|
+
* The value is the jsDelivr `@fontsource` mirror it was originally
|
|
4380
|
+
* fetched from, kept stable because it is part of the public API. It is
|
|
4381
|
+
* no longer fetched: the viewer ships the same WOFF compiled in, and
|
|
4382
|
+
* treats this URL as "use the built-in copy", so passing it explicitly
|
|
4383
|
+
* is equivalent to passing nothing and costs no request.
|
|
3545
4384
|
*
|
|
3546
|
-
* `font` on troika-three-text expects an OTF / TTF / WOFF URL — NOT a
|
|
3547
|
-
* font-family name.
|
|
3548
|
-
*
|
|
4385
|
+
* `font` on troika-three-text expects an OTF / TTF / WOFF URL — NOT a
|
|
4386
|
+
* CSS font-family name. Any other URL is used as given, once a probe
|
|
4387
|
+
* has confirmed it actually loads.
|
|
3549
4388
|
*/
|
|
3550
4389
|
declare const DEFAULT_LABEL_FONT_URL: "https://cdn.jsdelivr.net/npm/@fontsource/roboto-condensed@5.2.7/files/roboto-condensed-latin-400-normal.woff";
|
|
4390
|
+
/**
|
|
4391
|
+
* URL of the compiled-in Roboto Condensed WOFF, as a `blob:` object URL
|
|
4392
|
+
* created on first use and cached for the lifetime of the document.
|
|
4393
|
+
*
|
|
4394
|
+
* A blob URL rather than a `data:` URL on purpose: troika loads fonts
|
|
4395
|
+
* through `XMLHttpRequest` and discards any response whose `status` is
|
|
4396
|
+
* 0, which is what several engines report for non-HTTP schemes. Blob
|
|
4397
|
+
* URLs answer with a regular 200.
|
|
4398
|
+
*
|
|
4399
|
+
* Falls back to a `data:` URL where `URL.createObjectURL` is missing
|
|
4400
|
+
* (jsdom, some SSR shims) so the value is always a usable string.
|
|
4401
|
+
*/
|
|
4402
|
+
declare function bundledLabelFontUrl(): string;
|
|
3551
4403
|
|
|
3552
4404
|
/**
|
|
3553
4405
|
* <MoverIdLabel> — Camera-facing 3D text label that shows a mover's
|
|
@@ -3558,7 +4410,7 @@ declare const DEFAULT_LABEL_FONT_URL: "https://cdn.jsdelivr.net/npm/@fontsource/
|
|
|
3558
4410
|
* the hood) so the label stays readable as the camera orbits. All
|
|
3559
4411
|
* styling is driven by `MoverIdLabelOptions`; the component supplies
|
|
3560
4412
|
* the documented defaults (15 mm black text, 55 mm above the mover's
|
|
3561
|
-
* magnet-plate origin, Roboto Condensed
|
|
4413
|
+
* magnet-plate origin, the viewer's built-in Roboto Condensed).
|
|
3562
4414
|
*/
|
|
3563
4415
|
|
|
3564
4416
|
interface MoverIdLabelProps {
|
|
@@ -3579,9 +4431,10 @@ declare const MoverIdLabel: React__default.FC<MoverIdLabelProps>;
|
|
|
3579
4431
|
* so the viewer renders correctly without a matching CDN mirror.
|
|
3580
4432
|
*
|
|
3581
4433
|
* Module count: 27
|
|
3582
|
-
* Mover count:
|
|
4434
|
+
* Mover count: 12
|
|
3583
4435
|
* Tool count: 2
|
|
3584
|
-
*
|
|
4436
|
+
* Plate count: 7
|
|
4437
|
+
* Rail count: 11
|
|
3585
4438
|
*/
|
|
3586
4439
|
|
|
3587
4440
|
/**
|
|
@@ -3635,8 +4488,8 @@ declare function useSidecarSource(): SidecarSourceConfig;
|
|
|
3635
4488
|
* only set `Beckhoff`; the loader silently falls back to that variant when
|
|
3636
4489
|
* `HepcoGfx` is requested but unavailable.
|
|
3637
4490
|
*
|
|
3638
|
-
* `null`
|
|
3639
|
-
*
|
|
4491
|
+
* A `null` `Beckhoff` entry marks a module type that is catalogued but has
|
|
4492
|
+
* no GLB — the loader renders a placeholder AABB mesh instead.
|
|
3640
4493
|
*/
|
|
3641
4494
|
|
|
3642
4495
|
/** Filename mapping per ModuleType3D (relative to assetsBaseUrl). */
|
|
@@ -3662,7 +4515,7 @@ declare const DEFAULT_RAIL_MANIFEST: Readonly<Record<RailType3D, {
|
|
|
3662
4515
|
* - Straights pick the matching-length AT9000 rail (0249 / 0250 / 0500).
|
|
3663
4516
|
* - +22.5° curves (AT2020 / AT2021) use AT9020.
|
|
3664
4517
|
* - −22.5° curves (AT2025 / AT2026) use AT9025.
|
|
3665
|
-
* - 45° curves (AT2040 / AT2041
|
|
4518
|
+
* - 45° curves (AT2040 / AT2041) use AT9040.
|
|
3666
4519
|
* - The 180° clothoid pair (AT2050 0500/0501 + merged 0500_180) uses
|
|
3667
4520
|
* AT9050, whose GLB already covers the full 500 mm path.
|
|
3668
4521
|
* - Hygienic ATH modules already include the rail in their module GLB
|
|
@@ -3672,12 +4525,15 @@ declare const MODULE_GUIDING_RAIL_MAP: Readonly<Partial<Record<ModuleType3D, Rai
|
|
|
3672
4525
|
/** Resolve a module's GLB URL with manifest override + rail-system fallback. */
|
|
3673
4526
|
declare function resolveModuleGlbUrl(moduleType: ModuleType3D, rail: RailSystem, override?: AssetManifest['modules']): string | null;
|
|
3674
4527
|
declare function resolveMoverGlbUrl(moverType: MoverType3D, override?: AssetManifest['movers']): string;
|
|
4528
|
+
declare function resolvePlateGlbUrl(plateType: MagnetPlateType3D, override?: AssetManifest['magnetPlates']): string;
|
|
3675
4529
|
declare function resolveToolGlbUrl(toolType: MoverToolType3D, override?: AssetManifest['tools']): string;
|
|
3676
4530
|
/**
|
|
3677
|
-
*
|
|
3678
|
-
*
|
|
4531
|
+
* Which guiding rail a module gets, given the mover running on it.
|
|
4532
|
+
*
|
|
4533
|
+
* `moverType` is optional so existing callers keep the -0055 default; pass it
|
|
4534
|
+
* to get the variant Beckhoff actually pairs with that mover.
|
|
3679
4535
|
*/
|
|
3680
|
-
declare function resolveGuidingRailType(moduleType: ModuleType3D): RailType3D | null;
|
|
4536
|
+
declare function resolveGuidingRailType(moduleType: ModuleType3D, moverType?: MoverType3D): RailType3D | null;
|
|
3681
4537
|
/**
|
|
3682
4538
|
* Resolve the GLB filename for a guiding-rail type, honoring user-supplied
|
|
3683
4539
|
* manifest overrides. Returns empty string when the rail is unknown.
|
|
@@ -3774,9 +4630,9 @@ declare function getSharedAssetLoader(): AssetLoader;
|
|
|
3774
4630
|
declare function composeAssetUrl(baseUrl: string, filename: string): string;
|
|
3775
4631
|
|
|
3776
4632
|
/**
|
|
3777
|
-
* The
|
|
4633
|
+
* The five sidecar kinds as data.
|
|
3778
4634
|
*
|
|
3779
|
-
* Modules, movers, tools and rails all resolve
|
|
4635
|
+
* Modules, movers, tools, magnet plates and rails all resolve the same
|
|
3780
4636
|
* way — compiled-in table, else `<assetsBaseUrl>/<filename>`, else identity.
|
|
3781
4637
|
* Only two things differ per kind: which built-in table to read and how the
|
|
3782
4638
|
* filename is spelled. Keeping those two as a table (rather than four copies
|
|
@@ -3803,6 +4659,10 @@ interface SidecarKinds {
|
|
|
3803
4659
|
id: MoverToolType3D;
|
|
3804
4660
|
sidecar: MoverToolSidecar;
|
|
3805
4661
|
};
|
|
4662
|
+
plate: {
|
|
4663
|
+
id: MagnetPlateType3D;
|
|
4664
|
+
sidecar: MagnetPlateSidecar;
|
|
4665
|
+
};
|
|
3806
4666
|
rail: {
|
|
3807
4667
|
id: RailType3D;
|
|
3808
4668
|
sidecar: RailSidecar;
|
|
@@ -3922,4 +4782,4 @@ declare function snapToGrid(positionMm: Vec3, gridMm: number): Vec3;
|
|
|
3922
4782
|
/** Convenience: `resolveSceneContext` + `toEntityRef`. */
|
|
3923
4783
|
declare function resolveEntityFromObject(object: Object3D | null, instanceId?: number): MeasuredEntityRef | null;
|
|
3924
4784
|
|
|
3925
|
-
export { type AngleMeasurement, type Annotation, type AnnotationAnchor, type AnnotationInput, type AreaConfig, type AreaOptions, AssetLoader, type AssetManifest, BUILTIN_MODULE_SIDECARS, BUILTIN_MOVER_SIDECARS, BUILTIN_RAIL_SIDECARS, BUILTIN_TOOL_SIDECARS, type BuiltChain, type BuiltModule, type CameraProjection, type CameraState, type CaptureDimensions, type CaptureMode, type CaptureRequest, type CheckModuleCollisionsOptions, type CheckMoverCollisionsOptions, type ClickModifiers, type CoordinateSystem, type CustomAssetBinding, type CustomAssetConfig, type CustomMoverLayout, DEFAULT_LABEL_FONT_URL, DEFAULT_MEASUREMENT_STYLE, DEFAULT_MODULE_HALF_WIDTH_MM, DEFAULT_MODULE_HEIGHT_MM, DEFAULT_MODULE_MANIFEST, DEFAULT_MOVER_MANIFEST, DEFAULT_ORIGIN_CORRECTION, DEFAULT_RAIL_MANIFEST, DEFAULT_SNAP_KINDS, DEFAULT_SNAP_RADIUS_PX, DEFAULT_TOOL_MANIFEST, type DimensionOptions, type DisplayOptions, type DistanceMeasurement, EMPTY_SELECTION, type EndDelta, type FeedSegment, type FeedSegmentHighlight, type FeedSegmentSelector, type FeedSegmentSpan, type FocusOptions, type FocusTarget, type FrameCaptureOptions, type FrameCaptureSession, HEPCO_GFX_PROFILE, type HeatmapSample, type HepcoGfxRailProfileDims, IDENTITY_POSE, INFEED_MODULE_TYPES, type InfoBarConfig, type InfoBarMarkerConfig, type InfoBarOptions, type InteractionLocks, JSDELIVR_ASSETS_BASE_URL, MODULE_CATALOG, MODULE_GUIDING_RAIL_MAP, MOVER_CATALOG, type MarkerOptions, type MarkerShape, type MeasuredEntityRef, type Measurement, type MeasurementApi, type MeasurementAxis, type MeasurementDocument, type MeasurementDraft, type MeasurementFormatOptions, type MeasurementId, type MeasurementInput, type MeasurementKind, type MeasurementOptions, type MeasurementPoint, type MeasurementResult, type MeasurementSnapshot, type MeasurementStyle, type MeasurementTool, type ModuleAssetEntry, type ModuleCatalogEntry, type ModuleCollision, type ModuleCollisionPartInput, type ModuleCollisionXpuInput, ModuleCornerMarkers, type ModuleEntry, type ModuleHighlight, type ModuleProbe, type ModuleRef, type ModuleSidecar, type ModuleStatusEntry, type ModuleType3D, type MoverAssetEntry, type MoverCatalogEntry, type MoverCollision, type MoverConfig, MoverIdLabel, type MoverIdLabelOptions, type MoverPositionEntry, type MoverProbe, type MoverRef, type MoverSidecar, type MoverToolAssetEntry, type MoverToolConfig, type MoverToolSidecar, type MoverToolType3D, type MoverType3D, type NormalizationWarning, type NormalizedXtsConfig, type Orientation, type OriginCorrection, type PartConfig, type PartPath, type PartTransformation, type PathMeasurement, type PathSample, type PathType, type PerformanceOptions, type PlanarPose, type PointCloud, type PointProjector, type PositionFrame, type ProcessingUnitConfig, type RailAssetEntry, type RailSidecar, type RailSystem, type RailType3D, type ResolvedMeasurementStyle, SIDECAR_KINDS, type ScreenshotFormat, type ScreenshotMode, type ScreenshotOptions, type ScreenshotResult, type SelectionMode, type SelectionState, type SidecarIdOf, type SidecarKind, type SidecarKindSpec, type SidecarKinds, SidecarLoader, type SidecarOf, type SidecarOverrides, type SidecarSourceConfig, SidecarSourceContext, type SnapCandidate, type SnapKind, type SnapOptions, type SplitFeedSegmentsOptions, type StationConfig, type StationOptions, type StatorHeatmap, type StopPositionMoverOptions, type TextOptions, type TrackAnchor, type TrackDistanceMeasurement, type TrackIndex, type TrackIndexEntry, type TrackMetrics, type TrackMetricsResolver, type TrackProjection, type TrackTransform, VERSION, type Vec2, type Vec3, type ViewCubeAlignment, type ViewCubeOptions, XtsArcCurve3, type XtsConfig, XtsConfigNormalizer, type XtsModelDocument, XtsPointCloudCurve3, type XtsRuntimeState, XtsViewer3D, type XtsViewer3DProps, type XtsViewer3DRef, type XtsViewerError, type XtsViewerErrorCode, XtsViewerErrorException, angleDeg, applyBeckhoffXtsConvention, applyEmptyClick, applyModuleClick, applyMoverClick, arcPoints, axisDistanceMm, buildChain, buildModuleProbes, buildPartPath, buildTrackIndex, chainToUserPositionMm, checkAllModuleCollisions, checkAllMoverCollisions, checkModuleCollisions, checkSamePathCollisions, chooseSnapCandidate, collectFeedSegments, collectFeedSegmentsForXpu, collectTriangleSnapCandidates, composeAssetUrl, computeMeasurementResult, createHepcoGfxBaseplateShape, createHepcoGfxRailShape, deselectAll, distanceMm, findModuleAt, formatAngleDeg, formatLengthMm, getModuleEntry, getMoverEntry, getPointCloud, getSharedAssetLoader, interpolateHeatmapValue, isChainClosed, isKnownModuleType, isKnownMoverType, isModuleSelected, isMoverSelected, labelAnchorFor, moduleAnchorWorld, moduleEndDelta, moduleSidecarFilename, moverAnchorWorld, moverSidecarFilename, moverWorldAt, nearestTrackPoint, normaliseToHeatmapRange, normalizeXtsConfig, pathLengthsMm, pickGlbForRail, poseToMatrix4, railSidecarFilename, registerPointCloud, resolveEntityFromObject, resolveGuidingRailGlbUrl, resolveGuidingRailType, resolveHepcoGfxProfile, resolveMeasurementStyle, resolveModuleGlbUrl, resolveMoverGlbUrl, resolveOriginCorrection, resolveStopPositionMm, resolveToolGlbUrl, sampleChainPoint, sampleChainRange, sampleModulePath, snapToGrid, sortHeatmapSamples, splitIntoFeedSegments, toModuleCollisionInputs, toolSidecarFilename, trackAnchorWorld, trackDistanceMm, trackLengthOf, trackPolylineWorld, unregisterPointCloud, useSidecarSource, userToChainPositionMm, worldPointOnPart };
|
|
4785
|
+
export { type AngleMeasurement, type Annotation, type AnnotationAnchor, type AnnotationInput, type AreaConfig, type AreaOptions, AssetLoader, type AssetManifest, BUILTIN_MODULE_SIDECARS, BUILTIN_MOVER_SIDECARS, BUILTIN_RAIL_SIDECARS, BUILTIN_TOOL_SIDECARS, type BorePlacement, type BracketRun, type BuiltChain, type BuiltModule, type CalibrationMarkerOptions, type CameraProjection, type CameraState, type CaptureDimensions, type CaptureMode, type CaptureRequest, type CheckModuleCollisionsOptions, type CheckMoverCollisionsOptions, type ClickModifiers, type CollisionOptions, type CoordinateSystem, type CustomAssetBinding, type CustomAssetConfig, type CustomAssetRef, type CustomMoverLayout, DEFAULT_LABEL_FONT_URL, DEFAULT_MAGNET_PLATE_TYPE, DEFAULT_MEASUREMENT_STYLE, DEFAULT_MODULE_HALF_WIDTH_MM, DEFAULT_MODULE_HEIGHT_MM, DEFAULT_MODULE_MANIFEST, DEFAULT_MOVER_MANIFEST, DEFAULT_ORIGIN_CORRECTION, DEFAULT_RAIL_MANIFEST, DEFAULT_SNAP_KINDS, DEFAULT_SNAP_RADIUS_PX, DEFAULT_TOOL_MANIFEST, DEFAULT_TRACK_Z_MM, type DimensionOptions, type DisplayOptions, type DistanceMeasurement, EMPTY_SELECTION, type EndDelta, type FeedSegment, type FeedSegmentHighlight, type FeedSegmentSelector, type FeedSegmentSpan, type FocusOptions, type FocusTarget, type FrameCaptureOptions, type FrameCaptureSession, GIZMO_AXIS_COLORS, GIZMO_AXIS_LABELS, HEPCO_GFX_PROFILE, type HeatmapSample, type HepcoGfxRailProfileDims, IDENTITY_POSE, INFEED_MODULE_TYPES, type InfoBarConfig, type InfoBarMarkerConfig, type InfoBarOptions, type InteractionLocks, JSDELIVR_ASSETS_BASE_URL, type KeyboardNavigationOptions, type LightingOptions, MAGNET_PLATE_CATALOG, MODULE_CATALOG, MODULE_GUIDING_RAIL_MAP, MOVER_CATALOG, MOVER_INTEGRATED_PLATE, type MagnetPlateAssetEntry, type MagnetPlateCatalogEntry, type MagnetPlateType3D, type MarkerOptions, type MarkerShape, type MeasuredEntityRef, type Measurement, type MeasurementApi, type MeasurementAxis, type MeasurementDocument, type MeasurementDraft, type MeasurementFormatOptions, type MeasurementId, type MeasurementInput, type MeasurementKind, type MeasurementOptions, type MeasurementPatch, type MeasurementPoint, type MeasurementResult, type MeasurementSnapshot, type MeasurementStyle, type MeasurementTool, type ModuleAssetEntry, type ModuleCatalogEntry, type ModuleCollision, type ModuleCollisionOptions, type ModuleCollisionPartInput, type ModuleCollisionXpuInput, ModuleCornerMarkers, type ModuleEntry, type ModuleHeightSpan, type ModuleHighlight, type ModuleProbe, type ModuleRef, type ModuleSidecar, type ModuleStatusEntry, type ModuleType3D, type MoverAssetEntry, type MoverCatalogEntry, type MoverCollision, type MoverCollisionOptions, type MoverConfig, MoverIdLabel, type MoverIdLabelOptions, type MoverPositionEntry, type MoverProbe, type MoverRef, type MoverSidecar, type MoverToolAssetEntry, type MoverToolConfig, type MoverToolSidecar, type MoverToolStateEntry, type MoverToolType3D, type MoverType3D, type NormalizationWarning, type NormalizedXtsConfig, type Orientation, type OriginCorrection, type PartConfig, type PartPath, type PartTransformation, type PathMeasurement, type PathSample, type PathType, type PerformanceOptions, type PlanarPose, type PointCloud, type PointProjector, type PositionFrame, type PostProcessingOptions, type ProcessingUnitConfig, type ProfilePoint, type RailAssetEntry, type RailSidecar, type RailSystem, type RailType3D, type ResolvedMeasurementStyle, SIDECAR_KINDS, type ScreenshotFormat, type ScreenshotMode, type ScreenshotOptions, type ScreenshotResult, type SelectionColors, type SelectionMode, type SelectionScope, type SelectionState, type SidecarIdOf, type SidecarKind, type SidecarKindSpec, type SidecarKinds, SidecarLoader, type SidecarOf, type SidecarOverrides, type SidecarSourceConfig, SidecarSourceContext, type SnapCandidate, type SnapKind, type SnapOptions, type Span, type SplitFeedSegmentsOptions, type StationConfig, type StationOptions, type StatorHeatmap, type StopPositionMoverOptions, type TextOptions, type TrackAnchor, type TrackDistanceMeasurement, type TrackIndex, type TrackIndexEntry, type TrackMetrics, type TrackMetricsResolver, type TrackProjection, type TrackRef, type TrackTransform, type TransformGizmoChange, type TransformGizmoMode, type TransformGizmoOptions, type TransformGizmoPose, type TransformGizmoTarget, VERSION, type Vec2, type Vec3, type ViewCubeAlignment, type ViewCubeOptions, XtsModuleCurve3 as XtsArcCurve3, type XtsConfig, XtsConfigNormalizer, type XtsModelDocument, XtsModuleCurve3, XtsModuleCurve3 as XtsPointCloudCurve3, type XtsRuntimeState, XtsViewer3D, type XtsViewer3DProps, type XtsViewer3DRef, type XtsViewerError, type XtsViewerErrorCode, XtsViewerErrorException, angleDeg, applyBeckhoffXtsConvention, applyBeckhoffXtsConventionToOverrides, applyBeckhoffXtsConventionToTrackTransform, applyCustomAssetClick, applyEmptyClick, applyModuleClick, applyMoverClick, applyTrackClick, arcPoints, argbIntToHex, axisDistanceMm, buildChain, buildHepcoGfxBaseplateBridgeOutline, buildHepcoGfxBaseplateOutline, buildHepcoGfxBracketBaseOutline, buildHepcoGfxBracketEndBlockOutline, buildHepcoGfxBracketOutline, buildHepcoGfxBracketWallOutline, buildHepcoGfxRailOutline, buildModuleProbes, buildPartPath, buildTrackIndex, bundledLabelFontUrl, chainToUserPositionMm, checkAllModuleCollisions, checkAllMoverCollisions, checkModuleCollisions, checkSamePathCollisions, chooseSnapCandidate, coarseHeightSpan, collectFeedSegments, collectFeedSegmentsForXpu, collectTriangleSnapCandidates, composeAssetUrl, computeMeasurementResult, createHepcoGfxBaseplateBridgeShape, createHepcoGfxBaseplateShape, createHepcoGfxBracketBaseShape, createHepcoGfxBracketEndBlockShape, createHepcoGfxBracketShape, createHepcoGfxBracketWallShape, createHepcoGfxRailShape, deselectAll, distanceMm, findModuleAt, formatAngleDeg, formatLengthMm, getMagnetPlateEntry, getModuleEntry, getMoverEntry, getPointCloud, getSharedAssetLoader, heightSpanOf, hepcoGfxBaseplateBridgeSpans, hepcoGfxBracketBores, hepcoGfxBracketEndBlockSpans, hepcoGfxBracketRuns, hepcoGfxBracketWallSpans, hepcoGfxPlateBores, hexToArgbInt, interpolateHeatmapValue, isChainClosed, isCustomAssetSelected, isKnownMagnetPlateType, isKnownModuleType, isKnownMoverType, isModuleSelected, isMoverSelected, isTrackSelected, labelAnchorFor, moduleAnchorWorld, moduleEndDelta, moduleHeightSpan, moduleSidecarFilename, moverAnchorWorld, moverSidecarFilename, moverWorldAt, nearestTrackPoint, normaliseToHeatmapRange, normalizeXtsConfig, pathLengthsMm, pickGlbForRail, poseToMatrix4, railSidecarFilename, registerPointCloud, resolveAttachedPlateType, resolveEntityFromObject, resolveGuidingRailGlbUrl, resolveGuidingRailType, resolveHepcoGfxProfile, resolveMeasurementStyle, resolveModuleGlbUrl, resolveMoverGlbUrl, resolveOriginCorrection, resolvePlateGlbUrl, resolveStopPositionMm, resolveToolGlbUrl, sampleChainPoint, sampleChainRange, sampleModulePath, snapToGrid, sortHeatmapSamples, splitIntoFeedSegments, toModuleCollisionInputs, toolSidecarFilename, trackAnchorWorld, trackDistanceMm, trackLengthOf, trackPolylineWorld, unregisterPointCloud, useSidecarSource, userToChainPositionMm, worldPointOnPart };
|