three-cad-viewer 4.3.4 → 4.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/scene/clipping.d.ts +6 -0
- package/dist/three-cad-viewer.esm.js +20 -5
- package/dist/three-cad-viewer.esm.js.map +1 -1
- package/dist/three-cad-viewer.esm.min.js +1 -1
- package/dist/three-cad-viewer.js +20 -5
- package/dist/three-cad-viewer.min.js +1 -1
- package/package.json +2 -3
- package/src/_version.ts +0 -1
- package/src/camera/camera.ts +0 -445
- package/src/camera/controls/CADOrbitControls.ts +0 -241
- package/src/camera/controls/CADTrackballControls.ts +0 -598
- package/src/camera/controls.ts +0 -380
- package/src/core/patches.ts +0 -16
- package/src/core/studio-manager.ts +0 -652
- package/src/core/types.ts +0 -892
- package/src/core/viewer-state.ts +0 -784
- package/src/core/viewer.ts +0 -4821
- package/src/index.ts +0 -151
- package/src/rendering/environment.ts +0 -840
- package/src/rendering/light-detection.ts +0 -327
- package/src/rendering/material-factory.ts +0 -735
- package/src/rendering/material-presets.ts +0 -289
- package/src/rendering/raycast.ts +0 -291
- package/src/rendering/room-environment.ts +0 -192
- package/src/rendering/studio-composer.ts +0 -577
- package/src/rendering/studio-floor.ts +0 -108
- package/src/rendering/texture-cache.ts +0 -324
- package/src/rendering/tree-model.ts +0 -542
- package/src/rendering/triplanar.ts +0 -329
- package/src/scene/animation.ts +0 -343
- package/src/scene/axes.ts +0 -108
- package/src/scene/bbox.ts +0 -223
- package/src/scene/clipping.ts +0 -640
- package/src/scene/grid.ts +0 -864
- package/src/scene/nestedgroup.ts +0 -1444
- package/src/scene/objectgroup.ts +0 -866
- package/src/scene/orientation.ts +0 -259
- package/src/scene/render-shape.ts +0 -634
- package/src/tools/cad_tools/measure.ts +0 -811
- package/src/tools/cad_tools/select.ts +0 -100
- package/src/tools/cad_tools/tools.ts +0 -231
- package/src/tools/cad_tools/ui.ts +0 -454
- package/src/tools/cad_tools/zebra.ts +0 -369
- package/src/types/html.d.ts +0 -5
- package/src/types/n8ao.d.ts +0 -28
- package/src/types/three-augmentation.d.ts +0 -60
- package/src/ui/display.ts +0 -3295
- package/src/ui/index.html +0 -505
- package/src/ui/info.ts +0 -177
- package/src/ui/slider.ts +0 -206
- package/src/ui/toolbar.ts +0 -347
- package/src/ui/treeview.ts +0 -945
- package/src/utils/decode-instances.ts +0 -233
- package/src/utils/font.ts +0 -60
- package/src/utils/gpu-tracker.ts +0 -265
- package/src/utils/logger.ts +0 -92
- package/src/utils/sizeof.ts +0 -116
- package/src/utils/timer.ts +0 -69
- package/src/utils/utils.ts +0 -446
package/src/core/viewer-state.ts
DELETED
|
@@ -1,784 +0,0 @@
|
|
|
1
|
-
import * as THREE from "three";
|
|
2
|
-
import {
|
|
3
|
-
CollapseState,
|
|
4
|
-
type Theme,
|
|
5
|
-
type ThemeInput,
|
|
6
|
-
type ControlType,
|
|
7
|
-
type UpDirection,
|
|
8
|
-
type AnimationMode,
|
|
9
|
-
type ActiveTab,
|
|
10
|
-
type ZebraColorScheme,
|
|
11
|
-
type ZebraMappingMode,
|
|
12
|
-
type StudioBackground,
|
|
13
|
-
type StudioToneMapping,
|
|
14
|
-
type StudioTextureMapping,
|
|
15
|
-
type Keymap,
|
|
16
|
-
type StateChange,
|
|
17
|
-
type StateSubscriber,
|
|
18
|
-
type GlobalStateSubscriber,
|
|
19
|
-
type SubscribeOptions,
|
|
20
|
-
type RenderOptions,
|
|
21
|
-
type ViewerOptions,
|
|
22
|
-
type StudioOptions,
|
|
23
|
-
} from "./types";
|
|
24
|
-
import { logger } from "../utils/logger.js";
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Display configuration defaults
|
|
28
|
-
*/
|
|
29
|
-
interface DisplayDefaults {
|
|
30
|
-
theme: Theme;
|
|
31
|
-
cadWidth: number;
|
|
32
|
-
treeWidth: number;
|
|
33
|
-
treeHeight: number;
|
|
34
|
-
height: number;
|
|
35
|
-
pinning: boolean;
|
|
36
|
-
glass: boolean;
|
|
37
|
-
tools: boolean;
|
|
38
|
-
keymap: Keymap;
|
|
39
|
-
newTreeBehavior: boolean;
|
|
40
|
-
measureTools: boolean;
|
|
41
|
-
selectTool: boolean;
|
|
42
|
-
explodeTool: boolean;
|
|
43
|
-
zscaleTool: boolean;
|
|
44
|
-
zebraTool: boolean;
|
|
45
|
-
studioTool: boolean;
|
|
46
|
-
measurementDebug: boolean;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Render configuration defaults
|
|
51
|
-
*/
|
|
52
|
-
interface RenderDefaults {
|
|
53
|
-
ambientIntensity: number;
|
|
54
|
-
directIntensity: number;
|
|
55
|
-
metalness: number;
|
|
56
|
-
roughness: number;
|
|
57
|
-
defaultOpacity: number;
|
|
58
|
-
edgeColor: number;
|
|
59
|
-
normalLen: number;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Viewer/view configuration defaults
|
|
64
|
-
*/
|
|
65
|
-
interface ViewerDefaults {
|
|
66
|
-
axes: boolean;
|
|
67
|
-
axes0: boolean;
|
|
68
|
-
grid: [boolean, boolean, boolean];
|
|
69
|
-
ortho: boolean;
|
|
70
|
-
transparent: boolean;
|
|
71
|
-
blackEdges: boolean;
|
|
72
|
-
collapse: CollapseState;
|
|
73
|
-
clipIntersection: boolean;
|
|
74
|
-
clipPlaneHelpers: boolean;
|
|
75
|
-
clipObjectColors: boolean;
|
|
76
|
-
clipNormal0: THREE.Vector3;
|
|
77
|
-
clipNormal1: THREE.Vector3;
|
|
78
|
-
clipNormal2: THREE.Vector3;
|
|
79
|
-
clipSlider0: number;
|
|
80
|
-
clipSlider1: number;
|
|
81
|
-
clipSlider2: number;
|
|
82
|
-
control: ControlType;
|
|
83
|
-
holroyd: boolean;
|
|
84
|
-
up: UpDirection;
|
|
85
|
-
ticks: number;
|
|
86
|
-
gridFontSize: number;
|
|
87
|
-
centerGrid: boolean;
|
|
88
|
-
position: THREE.Vector3 | null;
|
|
89
|
-
quaternion: THREE.Quaternion | null;
|
|
90
|
-
target: THREE.Vector3 | null;
|
|
91
|
-
zoom: number;
|
|
92
|
-
panSpeed: number;
|
|
93
|
-
rotateSpeed: number;
|
|
94
|
-
zoomSpeed: number;
|
|
95
|
-
timeit: boolean;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Zebra tool defaults
|
|
100
|
-
*/
|
|
101
|
-
interface ZebraDefaults {
|
|
102
|
-
zebraCount: number;
|
|
103
|
-
zebraOpacity: number;
|
|
104
|
-
zebraDirection: number;
|
|
105
|
-
zebraColorScheme: ZebraColorScheme;
|
|
106
|
-
zebraMappingMode: ZebraMappingMode;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Studio mode defaults
|
|
111
|
-
*/
|
|
112
|
-
interface StudioModeDefaults {
|
|
113
|
-
studioEnvironment: string;
|
|
114
|
-
studioEnvIntensity: number;
|
|
115
|
-
studioBackground: StudioBackground;
|
|
116
|
-
studioToneMapping: StudioToneMapping;
|
|
117
|
-
studioExposure: number;
|
|
118
|
-
studio4kEnvMaps: boolean;
|
|
119
|
-
studioTextureMapping: StudioTextureMapping;
|
|
120
|
-
studioEnvRotation: number;
|
|
121
|
-
studioShadowIntensity: number;
|
|
122
|
-
studioShadowSoftness: number;
|
|
123
|
-
studioAOIntensity: number;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Runtime state defaults
|
|
128
|
-
*/
|
|
129
|
-
interface RuntimeDefaults {
|
|
130
|
-
activeTool: string | null;
|
|
131
|
-
animationMode: AnimationMode;
|
|
132
|
-
animationSliderValue: number;
|
|
133
|
-
zscaleActive: boolean;
|
|
134
|
-
highlightedButton: string | null;
|
|
135
|
-
activeTab: ActiveTab;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Complete state shape
|
|
140
|
-
*/
|
|
141
|
-
type StateShape = DisplayDefaults & RenderDefaults & ViewerDefaults & ZebraDefaults & StudioModeDefaults & RuntimeDefaults;
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Keys of the state shape
|
|
145
|
-
*/
|
|
146
|
-
type StateKey = keyof StateShape;
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Options that can be passed to ViewerState constructor.
|
|
150
|
-
* Accepts StateShape properties plus an index signature for runtime validation.
|
|
151
|
-
*/
|
|
152
|
-
type ViewerStateOptions = Partial<StateShape> & { theme?: ThemeInput } & { [key: string]: unknown };
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* External notification payload - single key/change pair
|
|
156
|
-
*/
|
|
157
|
-
type ExternalNotification = { key: string; change: StateChange<unknown> };
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* All valid state keys for runtime validation
|
|
161
|
-
*/
|
|
162
|
-
const STATE_KEYS: ReadonlySet<string> = new Set<StateKey>([
|
|
163
|
-
// Display
|
|
164
|
-
"theme", "cadWidth", "treeWidth", "treeHeight", "height", "pinning", "glass", "tools",
|
|
165
|
-
"keymap", "newTreeBehavior", "measureTools", "selectTool", "explodeTool", "zscaleTool",
|
|
166
|
-
"zebraTool", "studioTool", "measurementDebug",
|
|
167
|
-
// Render
|
|
168
|
-
"ambientIntensity", "directIntensity", "metalness", "roughness", "defaultOpacity",
|
|
169
|
-
"edgeColor", "normalLen",
|
|
170
|
-
// Viewer
|
|
171
|
-
"axes", "axes0", "grid", "ortho", "transparent", "blackEdges", "collapse",
|
|
172
|
-
"clipIntersection", "clipPlaneHelpers", "clipObjectColors", "clipNormal0", "clipNormal1",
|
|
173
|
-
"clipNormal2", "clipSlider0", "clipSlider1", "clipSlider2", "control", "holroyd", "up",
|
|
174
|
-
"ticks", "gridFontSize", "centerGrid", "position", "quaternion", "target", "zoom",
|
|
175
|
-
"panSpeed", "rotateSpeed", "zoomSpeed", "timeit",
|
|
176
|
-
// Zebra
|
|
177
|
-
"zebraCount", "zebraOpacity", "zebraDirection", "zebraColorScheme", "zebraMappingMode",
|
|
178
|
-
// Studio
|
|
179
|
-
"studioEnvironment", "studioEnvIntensity", "studioBackground",
|
|
180
|
-
"studioToneMapping", "studioExposure", "studio4kEnvMaps", "studioTextureMapping",
|
|
181
|
-
"studioEnvRotation", "studioShadowIntensity", "studioShadowSoftness", "studioAOIntensity",
|
|
182
|
-
// Runtime
|
|
183
|
-
"activeTool", "animationMode", "animationSliderValue", "zscaleActive", "highlightedButton",
|
|
184
|
-
"activeTab",
|
|
185
|
-
]);
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Type guard to check if a string is a valid state key
|
|
189
|
-
*/
|
|
190
|
-
function isStateKey(key: string): key is StateKey {
|
|
191
|
-
return STATE_KEYS.has(key);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Mapping from state keys to external notification keys.
|
|
196
|
-
* Only keys that should trigger external notifications are included.
|
|
197
|
-
* State keys not in this map won't trigger external notifications.
|
|
198
|
-
*/
|
|
199
|
-
const STATE_TO_NOTIFICATION_KEY: Partial<Record<StateKey, string>> = {
|
|
200
|
-
// View settings
|
|
201
|
-
axes: "axes",
|
|
202
|
-
axes0: "axes0",
|
|
203
|
-
grid: "grid",
|
|
204
|
-
ortho: "ortho",
|
|
205
|
-
transparent: "transparent",
|
|
206
|
-
blackEdges: "black_edges",
|
|
207
|
-
tools: "tools",
|
|
208
|
-
glass: "glass",
|
|
209
|
-
centerGrid: "center_grid",
|
|
210
|
-
collapse: "collapse",
|
|
211
|
-
activeTab: "tab",
|
|
212
|
-
// Render settings
|
|
213
|
-
ambientIntensity: "ambient_intensity",
|
|
214
|
-
directIntensity: "direct_intensity",
|
|
215
|
-
metalness: "metalness",
|
|
216
|
-
roughness: "roughness",
|
|
217
|
-
edgeColor: "default_edgecolor",
|
|
218
|
-
defaultOpacity: "default_opacity",
|
|
219
|
-
// Control settings
|
|
220
|
-
zoomSpeed: "zoom_speed",
|
|
221
|
-
panSpeed: "pan_speed",
|
|
222
|
-
rotateSpeed: "rotate_speed",
|
|
223
|
-
holroyd: "holroyd",
|
|
224
|
-
// Clipping settings
|
|
225
|
-
clipIntersection: "clip_intersection",
|
|
226
|
-
clipObjectColors: "clip_object_colors",
|
|
227
|
-
clipPlaneHelpers: "clip_planes",
|
|
228
|
-
clipSlider0: "clip_slider_0",
|
|
229
|
-
clipSlider1: "clip_slider_1",
|
|
230
|
-
clipSlider2: "clip_slider_2",
|
|
231
|
-
clipNormal0: "clip_normal_0",
|
|
232
|
-
clipNormal1: "clip_normal_1",
|
|
233
|
-
clipNormal2: "clip_normal_2",
|
|
234
|
-
// Zebra settings
|
|
235
|
-
zebraCount: "zebra_count",
|
|
236
|
-
zebraOpacity: "zebra_opacity",
|
|
237
|
-
zebraDirection: "zebra_direction",
|
|
238
|
-
zebraColorScheme: "zebra_color_scheme",
|
|
239
|
-
zebraMappingMode: "zebra_mapping_mode",
|
|
240
|
-
// Studio settings
|
|
241
|
-
studioEnvironment: "studio_environment",
|
|
242
|
-
studioEnvIntensity: "studio_env_intensity",
|
|
243
|
-
studioBackground: "studio_background",
|
|
244
|
-
studioToneMapping: "studio_tone_mapping",
|
|
245
|
-
studioExposure: "studio_exposure",
|
|
246
|
-
studio4kEnvMaps: "studio_4k_env_maps",
|
|
247
|
-
studioTextureMapping: "studio_texture_mapping",
|
|
248
|
-
studioEnvRotation: "studio_env_rotation",
|
|
249
|
-
studioShadowIntensity: "studio_shadow_intensity",
|
|
250
|
-
studioShadowSoftness: "studio_shadow_softness",
|
|
251
|
-
studioAOIntensity: "studio_ao_intensity",
|
|
252
|
-
// Animation/Explode slider (shared state, mutually exclusive modes)
|
|
253
|
-
animationSliderValue: "relative_time",
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Transform functions for notification values.
|
|
258
|
-
* Converts internal state values to external notification format.
|
|
259
|
-
*/
|
|
260
|
-
const STATE_NOTIFICATION_TRANSFORM: Partial<Record<StateKey, (v: unknown) => unknown>> = {
|
|
261
|
-
// Slider stores 0-1000, but notifications should be 0-1
|
|
262
|
-
animationSliderValue: (v) => (v as number) / 1000,
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Compare two values for equality (handles arrays)
|
|
267
|
-
*/
|
|
268
|
-
function valuesEqual(a: unknown, b: unknown): boolean {
|
|
269
|
-
if (a === b) return true;
|
|
270
|
-
if (Array.isArray(a) && Array.isArray(b)) {
|
|
271
|
-
return a.length === b.length && a.every((v, i) => v === b[i]);
|
|
272
|
-
}
|
|
273
|
-
return false;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Resolve theme input to actual theme value
|
|
278
|
-
*/
|
|
279
|
-
function resolveTheme(inputTheme: ThemeInput | undefined): Theme | undefined {
|
|
280
|
-
if (inputTheme === "dark") return "dark";
|
|
281
|
-
if (inputTheme === "light") return "light";
|
|
282
|
-
if (
|
|
283
|
-
inputTheme === "browser" &&
|
|
284
|
-
typeof window !== "undefined" &&
|
|
285
|
-
window.matchMedia("(prefers-color-scheme: dark)").matches
|
|
286
|
-
) {
|
|
287
|
-
return "dark";
|
|
288
|
-
}
|
|
289
|
-
return undefined;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Centralized state management for the viewer.
|
|
294
|
-
*
|
|
295
|
-
* ViewerState is the single source of truth for all viewer configuration:
|
|
296
|
-
* - Display settings (theme, dimensions, tools enabled)
|
|
297
|
-
* - Render settings (lighting, materials)
|
|
298
|
-
* - View settings (camera, clipping, grid)
|
|
299
|
-
* - Runtime state (active tool, animation)
|
|
300
|
-
*
|
|
301
|
-
* ## Observable Pattern
|
|
302
|
-
* State changes can be observed via `subscribe()`:
|
|
303
|
-
* ```typescript
|
|
304
|
-
* const unsubscribe = state.subscribe("axes", (change) => {
|
|
305
|
-
* console.log(`axes changed from ${change.old} to ${change.new}`);
|
|
306
|
-
* });
|
|
307
|
-
* ```
|
|
308
|
-
*
|
|
309
|
-
* ## Key Methods
|
|
310
|
-
* - `get(key)`: Get current value
|
|
311
|
-
* - `set(key, value)`: Set value (triggers subscribers)
|
|
312
|
-
* - `subscribe(key, callback)`: Subscribe to changes
|
|
313
|
-
* - `subscribeAll(callback)`: Subscribe to all changes
|
|
314
|
-
*
|
|
315
|
-
* ## Default Values
|
|
316
|
-
* - `DISPLAY_DEFAULTS`: Theme, dimensions, tools
|
|
317
|
-
* - `RENDER_DEFAULTS`: Lighting, materials
|
|
318
|
-
* - `VIEWER_DEFAULTS`: Camera, clipping, grid
|
|
319
|
-
*
|
|
320
|
-
* @internal - This is an internal class used by Viewer
|
|
321
|
-
*/
|
|
322
|
-
class ViewerState {
|
|
323
|
-
/**
|
|
324
|
-
* Default values for display configuration
|
|
325
|
-
*/
|
|
326
|
-
static DISPLAY_DEFAULTS: DisplayDefaults = {
|
|
327
|
-
theme: "light",
|
|
328
|
-
cadWidth: 800,
|
|
329
|
-
treeWidth: 260,
|
|
330
|
-
treeHeight: 400,
|
|
331
|
-
height: 600,
|
|
332
|
-
pinning: false,
|
|
333
|
-
glass: false,
|
|
334
|
-
tools: true,
|
|
335
|
-
keymap: {
|
|
336
|
-
shift: "shiftKey", ctrl: "ctrlKey", meta: "metaKey", alt: "altKey",
|
|
337
|
-
axes: "a", axes0: "A", grid: "g", gridxy: "G", perspective: "p", transparent: "t", blackedges: "b",
|
|
338
|
-
reset: "R", resize: "r",
|
|
339
|
-
iso: "5", front: "1", rear: "3", top: "8", bottom: "2", left: "4", right: "6",
|
|
340
|
-
explode: "x", zscale: "L", distance: "D", properties: "P", select: "S", help: "h", play: " ", stop: "Escape",
|
|
341
|
-
tree: "T", clip: "C", material: "M", zebra: "Z", studio: "s",
|
|
342
|
-
},
|
|
343
|
-
newTreeBehavior: true,
|
|
344
|
-
measureTools: true,
|
|
345
|
-
selectTool: true,
|
|
346
|
-
explodeTool: true,
|
|
347
|
-
zscaleTool: false,
|
|
348
|
-
zebraTool: true,
|
|
349
|
-
studioTool: true,
|
|
350
|
-
measurementDebug: false,
|
|
351
|
-
};
|
|
352
|
-
|
|
353
|
-
/**
|
|
354
|
-
* Default values for render configuration
|
|
355
|
-
*/
|
|
356
|
-
static RENDER_DEFAULTS: RenderDefaults = {
|
|
357
|
-
ambientIntensity: 1,
|
|
358
|
-
directIntensity: 1.1,
|
|
359
|
-
metalness: 0.3,
|
|
360
|
-
roughness: 0.65,
|
|
361
|
-
defaultOpacity: 0.5,
|
|
362
|
-
edgeColor: 0x707070,
|
|
363
|
-
normalLen: 0,
|
|
364
|
-
};
|
|
365
|
-
|
|
366
|
-
/**
|
|
367
|
-
* Default values for viewer/view configuration
|
|
368
|
-
*/
|
|
369
|
-
static VIEWER_DEFAULTS: ViewerDefaults = {
|
|
370
|
-
axes: false,
|
|
371
|
-
axes0: false,
|
|
372
|
-
grid: [false, false, false],
|
|
373
|
-
ortho: true,
|
|
374
|
-
transparent: false,
|
|
375
|
-
blackEdges: false,
|
|
376
|
-
collapse: CollapseState.COLLAPSED,
|
|
377
|
-
clipIntersection: false,
|
|
378
|
-
clipPlaneHelpers: false,
|
|
379
|
-
clipObjectColors: false,
|
|
380
|
-
clipNormal0: new THREE.Vector3(-1, 0, 0),
|
|
381
|
-
clipNormal1: new THREE.Vector3(0, -1, 0),
|
|
382
|
-
clipNormal2: new THREE.Vector3(0, 0, -1),
|
|
383
|
-
clipSlider0: -1,
|
|
384
|
-
clipSlider1: -1,
|
|
385
|
-
clipSlider2: -1,
|
|
386
|
-
control: "orbit",
|
|
387
|
-
holroyd: true,
|
|
388
|
-
up: "Z",
|
|
389
|
-
ticks: 10,
|
|
390
|
-
gridFontSize: 10,
|
|
391
|
-
centerGrid: false,
|
|
392
|
-
position: null,
|
|
393
|
-
quaternion: null,
|
|
394
|
-
target: null,
|
|
395
|
-
zoom: 1,
|
|
396
|
-
panSpeed: 1.0,
|
|
397
|
-
rotateSpeed: 1.0,
|
|
398
|
-
zoomSpeed: 1.0,
|
|
399
|
-
timeit: false,
|
|
400
|
-
};
|
|
401
|
-
|
|
402
|
-
/**
|
|
403
|
-
* Zebra tool settings
|
|
404
|
-
*/
|
|
405
|
-
static ZEBRA_DEFAULTS: ZebraDefaults = {
|
|
406
|
-
zebraCount: 9,
|
|
407
|
-
zebraOpacity: 1.0,
|
|
408
|
-
zebraDirection: 0,
|
|
409
|
-
zebraColorScheme: "blackwhite",
|
|
410
|
-
zebraMappingMode: "reflection",
|
|
411
|
-
};
|
|
412
|
-
|
|
413
|
-
/**
|
|
414
|
-
* Studio mode settings
|
|
415
|
-
*/
|
|
416
|
-
static STUDIO_MODE_DEFAULTS: StudioModeDefaults = {
|
|
417
|
-
studioEnvironment: "studio",
|
|
418
|
-
studioEnvIntensity: 1.0,
|
|
419
|
-
studioBackground: "environment",
|
|
420
|
-
studioToneMapping: "neutral",
|
|
421
|
-
studioExposure: 1.0,
|
|
422
|
-
studio4kEnvMaps: false,
|
|
423
|
-
studioTextureMapping: "triplanar",
|
|
424
|
-
studioEnvRotation: 0,
|
|
425
|
-
studioShadowIntensity: 0.5,
|
|
426
|
-
studioShadowSoftness: 0.2,
|
|
427
|
-
studioAOIntensity: 0.5,
|
|
428
|
-
};
|
|
429
|
-
|
|
430
|
-
/**
|
|
431
|
-
* Runtime state (not from options, changes during execution)
|
|
432
|
-
*/
|
|
433
|
-
static RUNTIME_DEFAULTS: RuntimeDefaults = {
|
|
434
|
-
activeTool: null,
|
|
435
|
-
animationMode: "none",
|
|
436
|
-
animationSliderValue: 0,
|
|
437
|
-
zscaleActive: false,
|
|
438
|
-
highlightedButton: null,
|
|
439
|
-
activeTab: "tree",
|
|
440
|
-
};
|
|
441
|
-
|
|
442
|
-
private _state: StateShape;
|
|
443
|
-
private _listeners: Map<StateKey, StateSubscriber<unknown>[]>;
|
|
444
|
-
private _globalListeners: GlobalStateSubscriber[];
|
|
445
|
-
private _externalNotifyCallback: ((
|
|
446
|
-
input: ExternalNotification | ExternalNotification[]
|
|
447
|
-
) => void) | null = null;
|
|
448
|
-
|
|
449
|
-
/**
|
|
450
|
-
* Create a ViewerState instance
|
|
451
|
-
*/
|
|
452
|
-
constructor(options: ViewerStateOptions = {}) {
|
|
453
|
-
// Start with all defaults
|
|
454
|
-
this._state = {
|
|
455
|
-
...ViewerState.DISPLAY_DEFAULTS,
|
|
456
|
-
...ViewerState.RENDER_DEFAULTS,
|
|
457
|
-
...ViewerState.VIEWER_DEFAULTS,
|
|
458
|
-
...ViewerState.ZEBRA_DEFAULTS,
|
|
459
|
-
...ViewerState.STUDIO_MODE_DEFAULTS,
|
|
460
|
-
...ViewerState.RUNTIME_DEFAULTS,
|
|
461
|
-
};
|
|
462
|
-
|
|
463
|
-
// Handle special theme logic (browser theme detection)
|
|
464
|
-
const resolvedTheme = resolveTheme(options.theme);
|
|
465
|
-
if (resolvedTheme) {
|
|
466
|
-
this._state.theme = resolvedTheme;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
// Apply user options (with validation)
|
|
470
|
-
this._applyOptions(options);
|
|
471
|
-
|
|
472
|
-
this._listeners = new Map();
|
|
473
|
-
this._globalListeners = [];
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
/**
|
|
477
|
-
* Apply options to state, validating keys
|
|
478
|
-
*/
|
|
479
|
-
private _applyOptions(options: ViewerStateOptions): void {
|
|
480
|
-
for (const key of Object.keys(options)) {
|
|
481
|
-
if (key === "theme") continue; // Already handled
|
|
482
|
-
if (!isStateKey(key)) {
|
|
483
|
-
logger.warn(`Unknown option "${key}" - ignored`);
|
|
484
|
-
continue;
|
|
485
|
-
}
|
|
486
|
-
const value = options[key];
|
|
487
|
-
if (value !== undefined && value !== null) {
|
|
488
|
-
// Type-safe assignment using Object.assign for the single property
|
|
489
|
-
Object.assign(this._state, { [key]: value });
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
/**
|
|
495
|
-
* Get a state value
|
|
496
|
-
*/
|
|
497
|
-
get<K extends StateKey>(key: K): StateShape[K] {
|
|
498
|
-
return this._state[key];
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
/**
|
|
502
|
-
* Set a state value and notify listeners
|
|
503
|
-
*/
|
|
504
|
-
set<K extends StateKey>(key: K, value: StateShape[K], notify: boolean = true): void {
|
|
505
|
-
const oldValue = this._state[key];
|
|
506
|
-
|
|
507
|
-
// Skip if value hasn't changed
|
|
508
|
-
if (valuesEqual(oldValue, value)) return;
|
|
509
|
-
|
|
510
|
-
this._state[key] = value;
|
|
511
|
-
|
|
512
|
-
if (notify) {
|
|
513
|
-
this._notify(key, { old: oldValue, new: value });
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
/**
|
|
518
|
-
* Update multiple state values at once
|
|
519
|
-
*/
|
|
520
|
-
private _update(updates: Partial<StateShape>, notify: boolean = true): void {
|
|
521
|
-
const changes: Array<{ key: StateKey; change: StateChange<unknown> }> = [];
|
|
522
|
-
|
|
523
|
-
for (const key of Object.keys(updates)) {
|
|
524
|
-
if (!isStateKey(key)) continue;
|
|
525
|
-
const value = updates[key];
|
|
526
|
-
// Skip undefined/null, except for keys where null is a valid value (reset to default)
|
|
527
|
-
const KEYS_WITH_VALID_NULL = ["position", "quaternion", "target"];
|
|
528
|
-
if (value === undefined || (value === null && !KEYS_WITH_VALID_NULL.includes(key))) continue;
|
|
529
|
-
|
|
530
|
-
const oldValue = this._state[key];
|
|
531
|
-
if (!valuesEqual(oldValue, value)) {
|
|
532
|
-
Object.assign(this._state, { [key]: value });
|
|
533
|
-
changes.push({ key, change: { old: oldValue, new: value } });
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
if (notify) {
|
|
538
|
-
for (const { key, change } of changes) {
|
|
539
|
-
this._notify(key, change);
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
/**
|
|
545
|
-
* Update render state from RenderOptions.
|
|
546
|
-
* RenderOptions types are directly compatible with StateShape.
|
|
547
|
-
*/
|
|
548
|
-
updateRenderState(options: RenderOptions, notify: boolean = true): void {
|
|
549
|
-
this._update(options, notify);
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
/**
|
|
553
|
-
* Update viewer state from ViewerOptions.
|
|
554
|
-
* Converts Vector3Tuple/QuaternionTuple to THREE objects.
|
|
555
|
-
*/
|
|
556
|
-
updateViewerState(options: ViewerOptions, notify: boolean = true): void {
|
|
557
|
-
// Extract properties that need conversion to THREE objects
|
|
558
|
-
const { clipNormal0, clipNormal1, clipNormal2, position, quaternion, target, ...rest } = options;
|
|
559
|
-
|
|
560
|
-
const converted: Partial<StateShape> = { ...rest };
|
|
561
|
-
|
|
562
|
-
// Convert tuple values to THREE objects
|
|
563
|
-
if (clipNormal0 !== undefined) {
|
|
564
|
-
converted.clipNormal0 = new THREE.Vector3(...clipNormal0);
|
|
565
|
-
}
|
|
566
|
-
if (clipNormal1 !== undefined) {
|
|
567
|
-
converted.clipNormal1 = new THREE.Vector3(...clipNormal1);
|
|
568
|
-
}
|
|
569
|
-
if (clipNormal2 !== undefined) {
|
|
570
|
-
converted.clipNormal2 = new THREE.Vector3(...clipNormal2);
|
|
571
|
-
}
|
|
572
|
-
if (position !== undefined) {
|
|
573
|
-
converted.position = position ? new THREE.Vector3(...position) : null;
|
|
574
|
-
}
|
|
575
|
-
if (quaternion !== undefined) {
|
|
576
|
-
converted.quaternion = quaternion ? new THREE.Quaternion(...quaternion) : null;
|
|
577
|
-
}
|
|
578
|
-
if (target !== undefined) {
|
|
579
|
-
converted.target = target ? new THREE.Vector3(...target) : null;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
this._update(converted, notify);
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
/**
|
|
586
|
-
* Update studio state from StudioOptions (shapes.studioOptions).
|
|
587
|
-
* Maps short field names to prefixed state keys.
|
|
588
|
-
*/
|
|
589
|
-
updateStudioState(options: StudioOptions): void {
|
|
590
|
-
const map: Partial<StateShape> = {};
|
|
591
|
-
if (options.environment !== undefined) map.studioEnvironment = options.environment;
|
|
592
|
-
if (options.envIntensity !== undefined) map.studioEnvIntensity = options.envIntensity;
|
|
593
|
-
if (options.background !== undefined) map.studioBackground = options.background;
|
|
594
|
-
if (options.toneMapping !== undefined) map.studioToneMapping = options.toneMapping;
|
|
595
|
-
if (options.toneMappingExposure !== undefined) map.studioExposure = options.toneMappingExposure;
|
|
596
|
-
if (options.use4kEnvMaps !== undefined) map.studio4kEnvMaps = options.use4kEnvMaps;
|
|
597
|
-
if (options.textureMapping !== undefined) map.studioTextureMapping = options.textureMapping;
|
|
598
|
-
if (options.envRotation !== undefined) map.studioEnvRotation = options.envRotation;
|
|
599
|
-
if (options.shadowIntensity !== undefined) map.studioShadowIntensity = options.shadowIntensity;
|
|
600
|
-
if (options.shadowSoftness !== undefined) map.studioShadowSoftness = options.shadowSoftness;
|
|
601
|
-
if (options.aoIntensity !== undefined) map.studioAOIntensity = options.aoIntensity;
|
|
602
|
-
this._update(map, false);
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
/**
|
|
606
|
-
* Get all state as a plain object (for serialization)
|
|
607
|
-
*/
|
|
608
|
-
getAll(): StateShape {
|
|
609
|
-
return { ...this._state };
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
/**
|
|
613
|
-
* Subscribe to changes for a specific state key
|
|
614
|
-
*/
|
|
615
|
-
subscribe<K extends StateKey>(
|
|
616
|
-
key: K,
|
|
617
|
-
listener: StateSubscriber<StateShape[K]>,
|
|
618
|
-
options: SubscribeOptions = {}
|
|
619
|
-
): () => void {
|
|
620
|
-
if (!this._listeners.has(key)) {
|
|
621
|
-
this._listeners.set(key, []);
|
|
622
|
-
}
|
|
623
|
-
this._listeners.get(key)!.push(listener as StateSubscriber<unknown>);
|
|
624
|
-
|
|
625
|
-
// Immediately invoke with current value if requested
|
|
626
|
-
if (options.immediate) {
|
|
627
|
-
const currentValue = this._state[key];
|
|
628
|
-
listener({ old: undefined, new: currentValue });
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
// Return unsubscribe function
|
|
632
|
-
return () => {
|
|
633
|
-
const listeners = this._listeners.get(key);
|
|
634
|
-
if (listeners) {
|
|
635
|
-
const index = listeners.indexOf(listener as StateSubscriber<unknown>);
|
|
636
|
-
if (index > -1) {
|
|
637
|
-
listeners.splice(index, 1);
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
};
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
/**
|
|
644
|
-
* Subscribe to all state changes
|
|
645
|
-
*/
|
|
646
|
-
subscribeAll(listener: GlobalStateSubscriber): () => void {
|
|
647
|
-
this._globalListeners.push(listener);
|
|
648
|
-
|
|
649
|
-
return () => {
|
|
650
|
-
const index = this._globalListeners.indexOf(listener);
|
|
651
|
-
if (index > -1) {
|
|
652
|
-
this._globalListeners.splice(index, 1);
|
|
653
|
-
}
|
|
654
|
-
};
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
/**
|
|
658
|
-
* Set a callback for external notifications (e.g., to notify external clients).
|
|
659
|
-
* The callback receives the notification key (snake_case) and the change object.
|
|
660
|
-
*/
|
|
661
|
-
setExternalNotifyCallback(
|
|
662
|
-
callback: ((input: ExternalNotification | ExternalNotification[]) => void) | null
|
|
663
|
-
): void {
|
|
664
|
-
this._externalNotifyCallback = callback;
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
/**
|
|
668
|
-
* Get all notifiable state values in external format.
|
|
669
|
-
* Returns a dictionary with snake_case keys and StateChange values.
|
|
670
|
-
* Used for initial config sync to external clients.
|
|
671
|
-
*/
|
|
672
|
-
getAllNotifiable(): Record<string, StateChange<unknown>> {
|
|
673
|
-
const result: Record<string, StateChange<unknown>> = {};
|
|
674
|
-
for (const [stateKey, notifyKey] of Object.entries(STATE_TO_NOTIFICATION_KEY)) {
|
|
675
|
-
const value = this._state[stateKey as StateKey];
|
|
676
|
-
const transform = STATE_NOTIFICATION_TRANSFORM[stateKey as StateKey];
|
|
677
|
-
const transformedValue = transform
|
|
678
|
-
? transform(value)
|
|
679
|
-
: value instanceof THREE.Vector3
|
|
680
|
-
? value.toArray()
|
|
681
|
-
: value;
|
|
682
|
-
result[notifyKey] = { old: null, new: transformedValue };
|
|
683
|
-
}
|
|
684
|
-
return result;
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
private _notify(key: StateKey, change: StateChange<unknown>): void {
|
|
688
|
-
// Notify key-specific listeners (internal UI updates)
|
|
689
|
-
const listeners = this._listeners.get(key);
|
|
690
|
-
if (listeners) {
|
|
691
|
-
for (const listener of listeners) {
|
|
692
|
-
listener(change);
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
// Notify global listeners (internal)
|
|
697
|
-
for (const listener of this._globalListeners) {
|
|
698
|
-
listener(key, change);
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
// Notify external callback if registered and key has a notification mapping
|
|
702
|
-
if (this._externalNotifyCallback) {
|
|
703
|
-
const notificationKey = STATE_TO_NOTIFICATION_KEY[key];
|
|
704
|
-
if (notificationKey) {
|
|
705
|
-
// Apply transform if defined (e.g., slider 0-1000 → relative 0-1)
|
|
706
|
-
const transform = STATE_NOTIFICATION_TRANSFORM[key];
|
|
707
|
-
const notifyChange = transform
|
|
708
|
-
? { old: change.old != null ? transform(change.old) : null, new: transform(change.new) }
|
|
709
|
-
: change;
|
|
710
|
-
this._externalNotifyCallback({ key: notificationKey, change: notifyChange });
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
/**
|
|
716
|
-
* Reset state to default values
|
|
717
|
-
*/
|
|
718
|
-
reset(options: ViewerStateOptions = {}): void {
|
|
719
|
-
const oldState = { ...this._state };
|
|
720
|
-
|
|
721
|
-
// Reset to all defaults
|
|
722
|
-
this._state = {
|
|
723
|
-
...ViewerState.DISPLAY_DEFAULTS,
|
|
724
|
-
...ViewerState.RENDER_DEFAULTS,
|
|
725
|
-
...ViewerState.VIEWER_DEFAULTS,
|
|
726
|
-
...ViewerState.ZEBRA_DEFAULTS,
|
|
727
|
-
...ViewerState.STUDIO_MODE_DEFAULTS,
|
|
728
|
-
...ViewerState.RUNTIME_DEFAULTS,
|
|
729
|
-
};
|
|
730
|
-
|
|
731
|
-
// Handle special theme logic
|
|
732
|
-
const resolvedTheme = resolveTheme(options.theme);
|
|
733
|
-
if (resolvedTheme) {
|
|
734
|
-
this._state.theme = resolvedTheme;
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
// Apply options
|
|
738
|
-
this._applyOptions(options);
|
|
739
|
-
|
|
740
|
-
// Notify all changes
|
|
741
|
-
for (const key of Object.keys(this._state)) {
|
|
742
|
-
if (!isStateKey(key)) continue;
|
|
743
|
-
if (!valuesEqual(oldState[key], this._state[key])) {
|
|
744
|
-
this._notify(key, { old: oldState[key], new: this._state[key] });
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
/**
|
|
750
|
-
* Get all default values (useful for documentation/debugging)
|
|
751
|
-
*/
|
|
752
|
-
static getDefaults(): StateShape {
|
|
753
|
-
return {
|
|
754
|
-
...ViewerState.DISPLAY_DEFAULTS,
|
|
755
|
-
...ViewerState.RENDER_DEFAULTS,
|
|
756
|
-
...ViewerState.VIEWER_DEFAULTS,
|
|
757
|
-
...ViewerState.ZEBRA_DEFAULTS,
|
|
758
|
-
...ViewerState.STUDIO_MODE_DEFAULTS,
|
|
759
|
-
...ViewerState.RUNTIME_DEFAULTS,
|
|
760
|
-
};
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
/**
|
|
764
|
-
* Dump all state values to console, organized by category
|
|
765
|
-
*/
|
|
766
|
-
dump(): void {
|
|
767
|
-
const logCategory = (name: string, defaults: Partial<StateShape>): void => {
|
|
768
|
-
logger.info(`${name}:`);
|
|
769
|
-
for (const key of Object.keys(defaults)) {
|
|
770
|
-
logger.info(`- ${key}`, this._state[key as StateKey]);
|
|
771
|
-
}
|
|
772
|
-
};
|
|
773
|
-
|
|
774
|
-
logCategory("Display", ViewerState.DISPLAY_DEFAULTS);
|
|
775
|
-
logCategory("Render", ViewerState.RENDER_DEFAULTS);
|
|
776
|
-
logCategory("View", ViewerState.VIEWER_DEFAULTS);
|
|
777
|
-
logCategory("Zebra", ViewerState.ZEBRA_DEFAULTS);
|
|
778
|
-
logCategory("Studio", ViewerState.STUDIO_MODE_DEFAULTS);
|
|
779
|
-
logCategory("Runtime", ViewerState.RUNTIME_DEFAULTS);
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
export { ViewerState };
|
|
784
|
-
export type { StateShape, StateKey, ViewerStateOptions };
|