three-cad-viewer 4.1.2 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/Readme.md +12 -5
  2. package/dist/camera/camera.d.ts +14 -2
  3. package/dist/core/studio-manager.d.ts +91 -0
  4. package/dist/core/types.d.ts +260 -9
  5. package/dist/core/viewer-state.d.ts +28 -2
  6. package/dist/core/viewer.d.ts +200 -6
  7. package/dist/index.d.ts +7 -2
  8. package/dist/rendering/environment.d.ts +239 -0
  9. package/dist/rendering/light-detection.d.ts +44 -0
  10. package/dist/rendering/material-factory.d.ts +77 -2
  11. package/dist/rendering/material-presets.d.ts +32 -0
  12. package/dist/rendering/room-environment.d.ts +13 -0
  13. package/dist/rendering/studio-composer.d.ts +130 -0
  14. package/dist/rendering/studio-floor.d.ts +53 -0
  15. package/dist/rendering/texture-cache.d.ts +142 -0
  16. package/dist/rendering/triplanar.d.ts +37 -0
  17. package/dist/scene/animation.d.ts +1 -1
  18. package/dist/scene/clipping.d.ts +31 -0
  19. package/dist/scene/nestedgroup.d.ts +64 -27
  20. package/dist/scene/objectgroup.d.ts +47 -0
  21. package/dist/three-cad-viewer.css +339 -29
  22. package/dist/three-cad-viewer.esm.js +27567 -11874
  23. package/dist/three-cad-viewer.esm.js.map +1 -1
  24. package/dist/three-cad-viewer.esm.min.js +10 -4
  25. package/dist/three-cad-viewer.js +27486 -11787
  26. package/dist/three-cad-viewer.min.js +10 -4
  27. package/dist/ui/display.d.ts +147 -0
  28. package/dist/utils/decode-instances.d.ts +60 -0
  29. package/dist/utils/utils.d.ts +10 -0
  30. package/package.json +4 -2
  31. package/src/_version.ts +1 -1
  32. package/src/camera/camera.ts +27 -10
  33. package/src/core/studio-manager.ts +682 -0
  34. package/src/core/types.ts +328 -9
  35. package/src/core/viewer-state.ts +84 -4
  36. package/src/core/viewer.ts +453 -22
  37. package/src/index.ts +25 -1
  38. package/src/rendering/environment.ts +840 -0
  39. package/src/rendering/light-detection.ts +327 -0
  40. package/src/rendering/material-factory.ts +456 -2
  41. package/src/rendering/material-presets.ts +303 -0
  42. package/src/rendering/raycast.ts +2 -2
  43. package/src/rendering/room-environment.ts +192 -0
  44. package/src/rendering/studio-composer.ts +577 -0
  45. package/src/rendering/studio-floor.ts +108 -0
  46. package/src/rendering/texture-cache.ts +1020 -0
  47. package/src/rendering/triplanar.ts +329 -0
  48. package/src/scene/animation.ts +3 -2
  49. package/src/scene/clipping.ts +59 -0
  50. package/src/scene/nestedgroup.ts +399 -0
  51. package/src/scene/objectgroup.ts +186 -11
  52. package/src/scene/orientation.ts +12 -0
  53. package/src/scene/render-shape.ts +55 -21
  54. package/src/types/n8ao.d.ts +28 -0
  55. package/src/ui/display.ts +1032 -27
  56. package/src/ui/index.html +181 -44
  57. package/src/utils/decode-instances.ts +233 -0
  58. package/src/utils/utils.ts +33 -20
package/src/core/types.ts CHANGED
@@ -28,7 +28,7 @@ export type UpDirection = "Z" | "Y" | "legacy";
28
28
  export type AnimationMode = "none" | "animation" | "explode";
29
29
 
30
30
  /** Active sidebar tab */
31
- export type ActiveTab = "tree" | "clip" | "material" | "zebra";
31
+ export type ActiveTab = "tree" | "clip" | "material" | "zebra" | "studio";
32
32
 
33
33
  /** Zebra color scheme */
34
34
  export type ZebraColorScheme = "blackwhite" | "colorful" | "grayscale";
@@ -36,6 +36,23 @@ export type ZebraColorScheme = "blackwhite" | "colorful" | "grayscale";
36
36
  /** Zebra mapping mode */
37
37
  export type ZebraMappingMode = "reflection" | "normal";
38
38
 
39
+ /**
40
+ * Studio environment preset name.
41
+ * - "studio": Built-in procedural RoomEnvironment (zero network)
42
+ * - "none": No environment map
43
+ * - Any other string: Poly Haven HDR preset slug or custom HDR URL
44
+ */
45
+ export type StudioEnvironment = string;
46
+
47
+ /** Studio tone mapping algorithm */
48
+ export type StudioToneMapping = "neutral" | "ACES" | "none";
49
+
50
+ /** Studio background mode */
51
+ export type StudioBackground = "grey" | "darkgrey" | "white" | "gradient" | "gradient-dark" | "environment" | "transparent";
52
+
53
+ /** Studio texture mapping mode */
54
+ export type StudioTextureMapping = "triplanar" | "parametric";
55
+
39
56
  /** Shape type */
40
57
  export type ShapeType = "shapes" | "edges" | "vertices";
41
58
 
@@ -70,6 +87,9 @@ export type ColorValue = number | string;
70
87
  /** RGB color as tuple [r, g, b] with values 0-1 */
71
88
  export type RGBColor = [number, number, number];
72
89
 
90
+ /** RGBA color as tuple [r, g, b, a] with values 0-1 */
91
+ export type RGBAColor = [number, number, number, number];
92
+
73
93
  /** Axis colors per theme - array of RGB colors for X, Y, Z axes */
74
94
  export type AxisColors = Record<Theme, RGBColor[]>;
75
95
 
@@ -184,7 +204,7 @@ export type ActionShortcutName =
184
204
  | "reset" | "resize" | "iso" | "front" | "rear" | "top" | "bottom" | "left" | "right"
185
205
  | "explode" | "distance" | "properties" | "select" | "help"
186
206
  | "play" | "stop"
187
- | "tree" | "clip" | "material" | "zebra";
207
+ | "tree" | "clip" | "material" | "zebra" | "studio";
188
208
 
189
209
  /** Action keymap: action name → key character */
190
210
  export type ActionKeymap = Partial<Record<ActionShortcutName, string>>;
@@ -224,21 +244,27 @@ export interface DisplayOptions {
224
244
  zscaleTool?: boolean;
225
245
  /** Show zebra tool (default: true) */
226
246
  zebraTool?: boolean;
247
+ /** Show studio tool (default: true) */
248
+ studioTool?: boolean;
227
249
  /** Enable measurement debug mode (default: false) */
228
250
  measurementDebug?: boolean;
251
+ /** External canvas element to use for the WebGL renderer, enabling shared WebGL context scenarios (default: undefined — renderer creates its own canvas) */
252
+ canvas?: HTMLCanvasElement;
253
+ /** External WebGL context to use for the renderer. When provided together with `canvas`, the renderer will use this context instead of creating a new one. Useful for sharing a context with other renderers like PixiJS. (default: undefined) */
254
+ gl?: WebGLRenderingContext | WebGL2RenderingContext;
229
255
  }
230
256
 
231
257
  /** Render options */
232
258
  export interface RenderOptions {
233
259
  /** Default edge color (default: 0x707070) */
234
260
  edgeColor?: number;
235
- /** Ambient light intensity (default: 0.5) */
261
+ /** Ambient light intensity (default: 1) */
236
262
  ambientIntensity?: number;
237
- /** Direct light intensity (default: 0.6) */
263
+ /** Direct light intensity (default: 1.1) */
238
264
  directIntensity?: number;
239
- /** Metalness (default: 0.7) */
265
+ /** Metalness (default: 0.3) */
240
266
  metalness?: number;
241
- /** Roughness (default: 0.7) */
267
+ /** Roughness (default: 0.65) */
242
268
  roughness?: number;
243
269
  /** Default opacity level for transparency (default: 0.5) */
244
270
  defaultOpacity?: number;
@@ -246,8 +272,8 @@ export interface RenderOptions {
246
272
  normalLen?: number;
247
273
  }
248
274
 
249
- /** Viewer options */
250
- export interface ViewerOptions {
275
+ /** Viewer options (includes studio mode configuration) */
276
+ export interface ViewerOptions extends StudioModeOptions {
251
277
  /** Use OrbitControls or TrackballControls (default: "orbit") */
252
278
  control?: ControlType;
253
279
  /** Show X-, Y-, Z-axes (default: false) */
@@ -324,8 +350,34 @@ export interface ZebraOptions {
324
350
  zebraMappingMode?: ZebraMappingMode;
325
351
  }
326
352
 
353
+ /** Studio mode options (environment, tone mapping, edges) */
354
+ export interface StudioModeOptions {
355
+ /** Environment preset or custom HDR URL (default: "studio") */
356
+ studioEnvironment?: string;
357
+ /** Environment map intensity, 0-1 (default: 0.5) */
358
+ studioEnvIntensity?: number;
359
+ /** Background mode (default: "environment") */
360
+ studioBackground?: StudioBackground;
361
+ /** Tone mapping algorithm (default: "neutral") */
362
+ studioToneMapping?: StudioToneMapping;
363
+ /** Tone mapping exposure, 0-2 (default: 1.0) */
364
+ studioExposure?: number;
365
+ /** Use 4K environment maps instead of 2K (default: false) */
366
+ studio4kEnvMaps?: boolean;
367
+ /** Texture mapping mode: triplanar projection or parametric UVs (default: "triplanar") */
368
+ studioTextureMapping?: StudioTextureMapping;
369
+ /** Environment map rotation in degrees, 0-360 (default: 0) */
370
+ studioEnvRotation?: number;
371
+ /** Shadow intensity, 0-1 (default: 0 = off) */
372
+ studioShadowIntensity?: number;
373
+ /** Shadow softness, 0-1 (default: 0.3) */
374
+ studioShadowSoftness?: number;
375
+ /** Ambient occlusion intensity, 0-3.0 (default: 0 = off) */
376
+ studioAOIntensity?: number;
377
+ }
378
+
327
379
  /** Combined options for initialization */
328
- export type CombinedOptions = DisplayOptions & RenderOptions & ViewerOptions & ZebraOptions;
380
+ export type CombinedOptions = DisplayOptions & RenderOptions & ViewerOptions & ZebraOptions & StudioModeOptions;
329
381
 
330
382
  // =============================================================================
331
383
  // Viewer State Shape
@@ -349,6 +401,7 @@ export interface ViewerStateShape {
349
401
  explodeTool: boolean;
350
402
  zscaleTool: boolean;
351
403
  zebraTool: boolean;
404
+ studioTool: boolean;
352
405
  measurementDebug: boolean;
353
406
 
354
407
  // Render
@@ -399,6 +452,19 @@ export interface ViewerStateShape {
399
452
  zebraColorScheme: ZebraColorScheme;
400
453
  zebraMappingMode: ZebraMappingMode;
401
454
 
455
+ // Studio
456
+ studioEnvironment: string;
457
+ studioEnvIntensity: number;
458
+ studioBackground: StudioBackground;
459
+ studioToneMapping: StudioToneMapping;
460
+ studioExposure: number;
461
+ studio4kEnvMaps: boolean;
462
+ studioTextureMapping: StudioTextureMapping;
463
+ studioEnvRotation: number;
464
+ studioShadowIntensity: number;
465
+ studioShadowSoftness: number;
466
+ studioAOIntensity: number;
467
+
402
468
  // Runtime
403
469
  activeTool: string | null;
404
470
  animationMode: AnimationMode;
@@ -411,6 +477,235 @@ export interface ViewerStateShape {
411
477
  /** Keys of ViewerStateShape */
412
478
  export type StateKey = keyof ViewerStateShape;
413
479
 
480
+ // =============================================================================
481
+ // Material Appearance (Studio Mode)
482
+ // =============================================================================
483
+
484
+ /**
485
+ * Material appearance definition for Studio mode.
486
+ *
487
+ * All fields are optional. Only provided fields override defaults.
488
+ * In Studio mode the viewer uses MeshPhysicalMaterial; properties left
489
+ * unset default to their "off" values (transmission=0, clearcoat=0, etc.)
490
+ * which the shader skips at near-zero cost.
491
+ *
492
+ * This is a data-format interface (describes JSON input), not a Three.js material.
493
+ * Texture string fields reference either a key in the root-level `textures` table,
494
+ * a data URI, or a URL resolved against the HTML page.
495
+ */
496
+ export interface MaterialAppearance {
497
+ /** Display name */
498
+ name?: string;
499
+ /** Reference to a built-in preset (e.g., "stainless-steel", "car-paint") */
500
+ builtin?: string;
501
+
502
+ // -- Color --
503
+
504
+ /** sRGB base color. Accepts RGBA tuple [r,g,b,a] (0-1) or CSS hex string "#rrggbb". */
505
+ color?: RGBAColor | string;
506
+ /** Texture reference for base color */
507
+ map?: string;
508
+
509
+ // -- Metallic-Roughness PBR --
510
+
511
+ /** Metalness factor, 0-1 (default: 0.0) */
512
+ metalness?: number;
513
+ /** Roughness factor, 0-1 (default: 0.5) */
514
+ roughness?: number;
515
+
516
+ // -- Textures (Standard) --
517
+
518
+ /** Normal map texture reference */
519
+ normalMap?: string;
520
+ /** Ambient occlusion texture reference */
521
+ aoMap?: string;
522
+ /** Metalness map texture reference */
523
+ metalnessMap?: string;
524
+ /** Roughness map texture reference */
525
+ roughnessMap?: string;
526
+
527
+ // -- Emissive --
528
+
529
+ /** Emissive color, linear RGB */
530
+ emissive?: RGBColor;
531
+ /** Emissive map texture reference */
532
+ emissiveMap?: string;
533
+ /** Emissive intensity multiplier (default: 1.0) */
534
+ emissiveIntensity?: number;
535
+
536
+ // -- Alpha --
537
+
538
+ /** Alpha blending mode */
539
+ alphaMode?: "OPAQUE" | "MASK" | "BLEND";
540
+ /** Alpha cutoff threshold for MASK mode (default: 0.5) */
541
+ alphaCutoff?: number;
542
+
543
+ // -- Transmission (glass, water) --
544
+
545
+ /** Transmission factor, 0-1 */
546
+ transmission?: number;
547
+ /** Transmission map texture reference */
548
+ transmissionMap?: string;
549
+
550
+ // -- Clearcoat (car paint, varnish) --
551
+
552
+ /** Clearcoat intensity, 0-1 */
553
+ clearcoat?: number;
554
+ /** Clearcoat roughness */
555
+ clearcoatRoughness?: number;
556
+ /** Clearcoat intensity texture reference */
557
+ clearcoatMap?: string;
558
+ /** Clearcoat roughness texture reference */
559
+ clearcoatRoughnessMap?: string;
560
+ /** Clearcoat normal map texture reference */
561
+ clearcoatNormalMap?: string;
562
+
563
+ // -- Volume (subsurface: jade, wax, skin) --
564
+
565
+ /** Thickness for volume effects */
566
+ thickness?: number;
567
+ /** Thickness map texture reference */
568
+ thicknessMap?: string;
569
+ /** Attenuation distance for volume absorption */
570
+ attenuationDistance?: number;
571
+ /** Attenuation color, linear RGB */
572
+ attenuationColor?: RGBColor;
573
+
574
+ // -- IOR --
575
+
576
+ /** Index of refraction (default: 1.5) */
577
+ ior?: number;
578
+
579
+ // -- Specular --
580
+
581
+ /** Specular intensity, 0-1 */
582
+ specularIntensity?: number;
583
+ /** Specular tint color, linear RGB */
584
+ specularColor?: RGBColor;
585
+ /** Specular intensity texture reference */
586
+ specularIntensityMap?: string;
587
+ /** Specular color texture reference */
588
+ specularColorMap?: string;
589
+
590
+ // -- Sheen (fabric, velvet) --
591
+
592
+ /** Sheen intensity, 0-1 (required to enable sheen layer) */
593
+ sheen?: number;
594
+ /** Sheen tint color, linear RGB */
595
+ sheenColor?: RGBColor;
596
+ /** Sheen roughness */
597
+ sheenRoughness?: number;
598
+ /** Sheen color texture reference */
599
+ sheenColorMap?: string;
600
+ /** Sheen roughness texture reference */
601
+ sheenRoughnessMap?: string;
602
+
603
+ // -- Anisotropy (brushed metal) --
604
+
605
+ /** Anisotropy strength, 0-1 */
606
+ anisotropy?: number;
607
+ /** Anisotropy rotation in radians */
608
+ anisotropyRotation?: number;
609
+ /** Anisotropy direction texture reference */
610
+ anisotropyMap?: string;
611
+
612
+ // -- Misc --
613
+
614
+ /** Use MeshBasicMaterial (unlit, no shading) */
615
+ unlit?: boolean;
616
+ /** Render both sides of faces (THREE.DoubleSide) */
617
+ doubleSided?: boolean;
618
+ }
619
+
620
+ // =============================================================================
621
+ // MaterialX Material (threejs-materials format)
622
+ // =============================================================================
623
+
624
+ /**
625
+ * Material definition from threejs-materials (Three.js MeshPhysicalMaterial-compatible).
626
+ *
627
+ * This format is produced by the threejs-materials Python library, which catalogs
628
+ * PBR materials from ambientCG, GPUOpen, PolyHaven, and PhysicallyBased.
629
+ * The `properties` dict uses simplified property names (e.g., "color", "roughness",
630
+ * "normal") where each entry has an optional `value` (scalar or color array in
631
+ * linear RGB) and/or `texture` (inline data URI).
632
+ *
633
+ * Detected by the presence of the `properties` key.
634
+ * Extra keys from threejs-materials (id, name, source, url, license) pass through
635
+ * harmlessly and are not part of this interface.
636
+ */
637
+ export interface MaterialXMaterial {
638
+ /** Material properties from threejs-materials. Each key maps to { value?, texture? } */
639
+ properties: Record<string, { value?: unknown; texture?: string }>;
640
+ /** Optional texture tiling [u, v], default [1, 1]. Applied to all textures. */
641
+ textureRepeat?: [number, number];
642
+ }
643
+
644
+ /**
645
+ * Type guard to check if a material entry is a threejs-materials format dict.
646
+ * Detected by the presence of the `properties` key.
647
+ */
648
+ export function isMaterialXMaterial(m: unknown): m is MaterialXMaterial {
649
+ return typeof m === "object" && m !== null && "properties" in m;
650
+ }
651
+
652
+ // =============================================================================
653
+ // Texture Entry (Studio Mode)
654
+ // =============================================================================
655
+
656
+ /**
657
+ * Entry in the root-level `textures` table.
658
+ *
659
+ * Each entry is either embedded (base64-encoded image data) or a URL reference
660
+ * loaded on demand. At least one of `data`+`format` or `url` must be provided.
661
+ * An empty TextureEntry is invalid and will be ignored at runtime.
662
+ * Multiple builtin preset texture fields can reference the same key for
663
+ * deduplication. threejs-materials carry their own textures as inline data URIs.
664
+ */
665
+ export interface TextureEntry {
666
+ /** Base64-encoded image data (for embedded textures) */
667
+ data?: string;
668
+ /** Image format, e.g., "png", "jpg", "webp" (required when data is provided) */
669
+ format?: string;
670
+ /** URL to load the texture from (for URL-referenced textures) */
671
+ url?: string;
672
+ }
673
+
674
+ // =============================================================================
675
+ // Studio Options
676
+ // =============================================================================
677
+
678
+ /**
679
+ * Root-level Studio mode configuration.
680
+ *
681
+ * Optional configuration for the rendering environment, only used when
682
+ * the Studio tab is active. Fields map directly to ViewerState keys on load.
683
+ */
684
+ export interface StudioOptions {
685
+ /** Environment preset slug, custom HDR URL, or "none" (default: "studio") */
686
+ environment?: StudioEnvironment;
687
+ /** Environment map intensity, 0-1 (default: 0.5) */
688
+ envIntensity?: number;
689
+ /** Background mode (default: "environment") */
690
+ background?: StudioBackground;
691
+ /** Tone mapping algorithm (default: "neutral") */
692
+ toneMapping?: StudioToneMapping;
693
+ /** Tone mapping exposure (default: 1.0) */
694
+ toneMappingExposure?: number;
695
+ /** Use 4K environment maps instead of 2K (default: false) */
696
+ use4kEnvMaps?: boolean;
697
+ /** Texture mapping mode (default: "triplanar") */
698
+ textureMapping?: StudioTextureMapping;
699
+ /** Environment map rotation in degrees (default: 0) */
700
+ envRotation?: number;
701
+ /** Shadow intensity, 0-1 (default: 0 = off) */
702
+ shadowIntensity?: number;
703
+ /** Shadow softness, 0-1 (default: 0.3) */
704
+ shadowSoftness?: number;
705
+ /** Ambient occlusion intensity, 0-3.0 (default: 0 = off) */
706
+ aoIntensity?: number;
707
+ }
708
+
414
709
  // =============================================================================
415
710
  // Shape & Texture Types
416
711
  // =============================================================================
@@ -451,6 +746,8 @@ export interface Shape {
451
746
  triangles_per_face?: number[] | Uint32Array;
452
747
  /** Number of segments per edge (when edges is flat) */
453
748
  segments_per_edge?: number[] | Uint32Array;
749
+ /** UV coordinates (2 floats per vertex, same indexing as vertices) */
750
+ uvs?: number[] | Float32Array;
454
751
  }
455
752
 
456
753
  /**
@@ -481,6 +778,16 @@ export interface ShapeNested {
481
778
  face_types: number[];
482
779
  }
483
780
 
781
+ /**
782
+ * Shape reference for the instanced/compressed format.
783
+ * Before decoding, a part's shape field may be `{ ref: N }` referencing
784
+ * the Nth entry in the instances array. After decoding, all ShapeRefs
785
+ * are replaced with full Shape objects.
786
+ */
787
+ export interface ShapeRef {
788
+ ref: number;
789
+ }
790
+
484
791
  /**
485
792
  * Check if shape uses binary format (has triangles_per_face).
486
793
  */
@@ -565,6 +872,18 @@ export interface Shapes {
565
872
  width?: number | undefined;
566
873
  /** Vertex size in pixels (added during decomposition for vertex shapes) */
567
874
  size?: number | undefined;
875
+ /** Material tag referencing materials table or built-in preset (leaf nodes) */
876
+ material?: string | undefined;
877
+ /** User-defined material library (root node).
878
+ * Values can be:
879
+ * - string: builtin preset reference (e.g., "builtin:car-paint")
880
+ * - MaterialXMaterial: threejs-materials format (detected by `properties` key)
881
+ * - MaterialAppearance: preset with overrides (e.g., { builtin: "acrylic-clear", color: "#55a0e3" })
882
+ */
883
+ materials?: Record<string, string | MaterialXMaterial | MaterialAppearance> | undefined;
884
+ /** Shared texture table for builtin preset materials (root node).
885
+ * threejs-materials carry their own textures inline. */
886
+ textures?: Record<string, TextureEntry> | undefined;
568
887
  }
569
888
 
570
889
  // =============================================================================
@@ -9,6 +9,9 @@ import {
9
9
  type ActiveTab,
10
10
  type ZebraColorScheme,
11
11
  type ZebraMappingMode,
12
+ type StudioBackground,
13
+ type StudioToneMapping,
14
+ type StudioTextureMapping,
12
15
  type Keymap,
13
16
  type StateChange,
14
17
  type StateSubscriber,
@@ -16,6 +19,7 @@ import {
16
19
  type SubscribeOptions,
17
20
  type RenderOptions,
18
21
  type ViewerOptions,
22
+ type StudioOptions,
19
23
  } from "./types";
20
24
  import { logger } from "../utils/logger.js";
21
25
 
@@ -38,6 +42,7 @@ interface DisplayDefaults {
38
42
  explodeTool: boolean;
39
43
  zscaleTool: boolean;
40
44
  zebraTool: boolean;
45
+ studioTool: boolean;
41
46
  measurementDebug: boolean;
42
47
  }
43
48
 
@@ -101,6 +106,23 @@ interface ZebraDefaults {
101
106
  zebraMappingMode: ZebraMappingMode;
102
107
  }
103
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
+
104
126
  /**
105
127
  * Runtime state defaults
106
128
  */
@@ -116,7 +138,7 @@ interface RuntimeDefaults {
116
138
  /**
117
139
  * Complete state shape
118
140
  */
119
- type StateShape = DisplayDefaults & RenderDefaults & ViewerDefaults & ZebraDefaults & RuntimeDefaults;
141
+ type StateShape = DisplayDefaults & RenderDefaults & ViewerDefaults & ZebraDefaults & StudioModeDefaults & RuntimeDefaults;
120
142
 
121
143
  /**
122
144
  * Keys of the state shape
@@ -141,7 +163,7 @@ const STATE_KEYS: ReadonlySet<string> = new Set<StateKey>([
141
163
  // Display
142
164
  "theme", "cadWidth", "treeWidth", "treeHeight", "height", "pinning", "glass", "tools",
143
165
  "keymap", "newTreeBehavior", "measureTools", "selectTool", "explodeTool", "zscaleTool",
144
- "zebraTool", "measurementDebug",
166
+ "zebraTool", "studioTool", "measurementDebug",
145
167
  // Render
146
168
  "ambientIntensity", "directIntensity", "metalness", "roughness", "defaultOpacity",
147
169
  "edgeColor", "normalLen",
@@ -153,6 +175,10 @@ const STATE_KEYS: ReadonlySet<string> = new Set<StateKey>([
153
175
  "panSpeed", "rotateSpeed", "zoomSpeed", "timeit",
154
176
  // Zebra
155
177
  "zebraCount", "zebraOpacity", "zebraDirection", "zebraColorScheme", "zebraMappingMode",
178
+ // Studio
179
+ "studioEnvironment", "studioEnvIntensity", "studioBackground",
180
+ "studioToneMapping", "studioExposure", "studio4kEnvMaps", "studioTextureMapping",
181
+ "studioEnvRotation", "studioShadowIntensity", "studioShadowSoftness", "studioAOIntensity",
156
182
  // Runtime
157
183
  "activeTool", "animationMode", "animationSliderValue", "zscaleActive", "highlightedButton",
158
184
  "activeTab",
@@ -211,6 +237,18 @@ const STATE_TO_NOTIFICATION_KEY: Partial<Record<StateKey, string>> = {
211
237
  zebraDirection: "zebra_direction",
212
238
  zebraColorScheme: "zebra_color_scheme",
213
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",
214
252
  // Animation/Explode slider (shared state, mutually exclusive modes)
215
253
  animationSliderValue: "relative_time",
216
254
  };
@@ -288,7 +326,7 @@ class ViewerState {
288
326
  static DISPLAY_DEFAULTS: DisplayDefaults = {
289
327
  theme: "light",
290
328
  cadWidth: 800,
291
- treeWidth: 250,
329
+ treeWidth: 260,
292
330
  treeHeight: 400,
293
331
  height: 600,
294
332
  pinning: false,
@@ -300,7 +338,7 @@ class ViewerState {
300
338
  reset: "R", resize: "r",
301
339
  iso: "5", front: "1", rear: "3", top: "8", bottom: "2", left: "4", right: "6",
302
340
  explode: "x", zscale: "L", distance: "D", properties: "P", select: "S", help: "h", play: " ", stop: "Escape",
303
- tree: "T", clip: "C", material: "M", zebra: "Z",
341
+ tree: "T", clip: "C", material: "M", zebra: "Z", studio: "s",
304
342
  },
305
343
  newTreeBehavior: true,
306
344
  measureTools: true,
@@ -308,6 +346,7 @@ class ViewerState {
308
346
  explodeTool: true,
309
347
  zscaleTool: false,
310
348
  zebraTool: true,
349
+ studioTool: true,
311
350
  measurementDebug: false,
312
351
  };
313
352
 
@@ -371,6 +410,23 @@ class ViewerState {
371
410
  zebraMappingMode: "reflection",
372
411
  };
373
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
+
374
430
  /**
375
431
  * Runtime state (not from options, changes during execution)
376
432
  */
@@ -400,6 +456,7 @@ class ViewerState {
400
456
  ...ViewerState.RENDER_DEFAULTS,
401
457
  ...ViewerState.VIEWER_DEFAULTS,
402
458
  ...ViewerState.ZEBRA_DEFAULTS,
459
+ ...ViewerState.STUDIO_MODE_DEFAULTS,
403
460
  ...ViewerState.RUNTIME_DEFAULTS,
404
461
  };
405
462
 
@@ -525,6 +582,26 @@ class ViewerState {
525
582
  this._update(converted, notify);
526
583
  }
527
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
+
528
605
  /**
529
606
  * Get all state as a plain object (for serialization)
530
607
  */
@@ -647,6 +724,7 @@ class ViewerState {
647
724
  ...ViewerState.RENDER_DEFAULTS,
648
725
  ...ViewerState.VIEWER_DEFAULTS,
649
726
  ...ViewerState.ZEBRA_DEFAULTS,
727
+ ...ViewerState.STUDIO_MODE_DEFAULTS,
650
728
  ...ViewerState.RUNTIME_DEFAULTS,
651
729
  };
652
730
 
@@ -677,6 +755,7 @@ class ViewerState {
677
755
  ...ViewerState.RENDER_DEFAULTS,
678
756
  ...ViewerState.VIEWER_DEFAULTS,
679
757
  ...ViewerState.ZEBRA_DEFAULTS,
758
+ ...ViewerState.STUDIO_MODE_DEFAULTS,
680
759
  ...ViewerState.RUNTIME_DEFAULTS,
681
760
  };
682
761
  }
@@ -696,6 +775,7 @@ class ViewerState {
696
775
  logCategory("Render", ViewerState.RENDER_DEFAULTS);
697
776
  logCategory("View", ViewerState.VIEWER_DEFAULTS);
698
777
  logCategory("Zebra", ViewerState.ZEBRA_DEFAULTS);
778
+ logCategory("Studio", ViewerState.STUDIO_MODE_DEFAULTS);
699
779
  logCategory("Runtime", ViewerState.RUNTIME_DEFAULTS);
700
780
  }
701
781
  }