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.
- package/Readme.md +12 -5
- package/dist/camera/camera.d.ts +14 -2
- package/dist/core/studio-manager.d.ts +91 -0
- package/dist/core/types.d.ts +260 -9
- package/dist/core/viewer-state.d.ts +28 -2
- package/dist/core/viewer.d.ts +200 -6
- package/dist/index.d.ts +7 -2
- package/dist/rendering/environment.d.ts +239 -0
- package/dist/rendering/light-detection.d.ts +44 -0
- package/dist/rendering/material-factory.d.ts +77 -2
- package/dist/rendering/material-presets.d.ts +32 -0
- package/dist/rendering/room-environment.d.ts +13 -0
- package/dist/rendering/studio-composer.d.ts +130 -0
- package/dist/rendering/studio-floor.d.ts +53 -0
- package/dist/rendering/texture-cache.d.ts +142 -0
- package/dist/rendering/triplanar.d.ts +37 -0
- package/dist/scene/animation.d.ts +1 -1
- package/dist/scene/clipping.d.ts +31 -0
- package/dist/scene/nestedgroup.d.ts +64 -27
- package/dist/scene/objectgroup.d.ts +47 -0
- package/dist/three-cad-viewer.css +339 -29
- package/dist/three-cad-viewer.esm.js +27567 -11874
- package/dist/three-cad-viewer.esm.js.map +1 -1
- package/dist/three-cad-viewer.esm.min.js +10 -4
- package/dist/three-cad-viewer.js +27486 -11787
- package/dist/three-cad-viewer.min.js +10 -4
- package/dist/ui/display.d.ts +147 -0
- package/dist/utils/decode-instances.d.ts +60 -0
- package/dist/utils/utils.d.ts +10 -0
- package/package.json +4 -2
- package/src/_version.ts +1 -1
- package/src/camera/camera.ts +27 -10
- package/src/core/studio-manager.ts +682 -0
- package/src/core/types.ts +328 -9
- package/src/core/viewer-state.ts +84 -4
- package/src/core/viewer.ts +453 -22
- package/src/index.ts +25 -1
- package/src/rendering/environment.ts +840 -0
- package/src/rendering/light-detection.ts +327 -0
- package/src/rendering/material-factory.ts +456 -2
- package/src/rendering/material-presets.ts +303 -0
- package/src/rendering/raycast.ts +2 -2
- package/src/rendering/room-environment.ts +192 -0
- package/src/rendering/studio-composer.ts +577 -0
- package/src/rendering/studio-floor.ts +108 -0
- package/src/rendering/texture-cache.ts +1020 -0
- package/src/rendering/triplanar.ts +329 -0
- package/src/scene/animation.ts +3 -2
- package/src/scene/clipping.ts +59 -0
- package/src/scene/nestedgroup.ts +399 -0
- package/src/scene/objectgroup.ts +186 -11
- package/src/scene/orientation.ts +12 -0
- package/src/scene/render-shape.ts +55 -21
- package/src/types/n8ao.d.ts +28 -0
- package/src/ui/display.ts +1032 -27
- package/src/ui/index.html +181 -44
- package/src/utils/decode-instances.ts +233 -0
- package/src/utils/utils.ts +33 -20
package/src/core/viewer.ts
CHANGED
|
@@ -40,14 +40,19 @@ import { BoundingBox, BoxHelper } from "../scene/bbox.js";
|
|
|
40
40
|
import { Tools, type ToolResponse } from "../tools/cad_tools/tools.js";
|
|
41
41
|
import { version } from "../_version.js";
|
|
42
42
|
import { PickedObject, Raycaster, TopoFilter } from "../rendering/raycast.js";
|
|
43
|
+
import { StudioManager } from "./studio-manager.js";
|
|
43
44
|
import { ViewerState } from "./viewer-state.js";
|
|
44
45
|
import { logger } from "../utils/logger.js";
|
|
46
|
+
import { isInstancedFormat, decodeInstancedFormat, decodeInlineBuffers } from "../utils/decode-instances.js";
|
|
45
47
|
import type { Display } from "../ui/display.js";
|
|
46
48
|
import type { Vector3Tuple, QuaternionTuple } from "three";
|
|
47
49
|
import {
|
|
48
50
|
CollapseState,
|
|
49
51
|
type ZebraColorScheme,
|
|
50
52
|
type ZebraMappingMode,
|
|
53
|
+
type StudioToneMapping,
|
|
54
|
+
type StudioTextureMapping,
|
|
55
|
+
type StudioBackground,
|
|
51
56
|
type NotificationCallback,
|
|
52
57
|
type RenderOptions,
|
|
53
58
|
type ViewerOptions,
|
|
@@ -205,6 +210,8 @@ interface DisplayOptionsInternal {
|
|
|
205
210
|
zebraTool?: boolean;
|
|
206
211
|
glass?: boolean;
|
|
207
212
|
tools?: boolean;
|
|
213
|
+
canvas?: HTMLCanvasElement;
|
|
214
|
+
gl?: WebGLRenderingContext | WebGL2RenderingContext;
|
|
208
215
|
keymap?: KeymapConfig;
|
|
209
216
|
[key: string]: unknown;
|
|
210
217
|
}
|
|
@@ -289,6 +296,8 @@ class Viewer {
|
|
|
289
296
|
// Always available (set in constructor)
|
|
290
297
|
display!: Display;
|
|
291
298
|
renderer!: THREE.WebGLRenderer;
|
|
299
|
+
private _externalGl: boolean;
|
|
300
|
+
onAfterRender: (() => void) | null;
|
|
292
301
|
mouse!: THREE.Vector2;
|
|
293
302
|
cadTools!: Tools;
|
|
294
303
|
animation!: Animation;
|
|
@@ -352,6 +361,11 @@ class Viewer {
|
|
|
352
361
|
// Raycaster
|
|
353
362
|
raycaster: Raycaster | null;
|
|
354
363
|
|
|
364
|
+
// Studio mode orchestration (owns composer, floor, shadow lights, env manager)
|
|
365
|
+
private _studioManager!: StudioManager;
|
|
366
|
+
|
|
367
|
+
/** Environment manager — proxied from StudioManager for display.ts access. */
|
|
368
|
+
get envManager() { return this._studioManager.envManager; }
|
|
355
369
|
// Z-scale
|
|
356
370
|
zScale!: number;
|
|
357
371
|
|
|
@@ -402,6 +416,7 @@ class Viewer {
|
|
|
402
416
|
this.notifyCallback = notifyCallback;
|
|
403
417
|
this.pinAsPngCallback = pinAsPngCallback;
|
|
404
418
|
this.updateMarker = updateMarker;
|
|
419
|
+
this.onAfterRender = null;
|
|
405
420
|
|
|
406
421
|
this.hasAnimationLoop = false;
|
|
407
422
|
|
|
@@ -445,17 +460,28 @@ class Viewer {
|
|
|
445
460
|
|
|
446
461
|
this.mouse = new THREE.Vector2();
|
|
447
462
|
|
|
448
|
-
// setup renderer
|
|
449
|
-
|
|
463
|
+
// setup renderer — support externally provided canvas and/or WebGL context
|
|
464
|
+
const rendererParams: THREE.WebGLRendererParameters = {
|
|
450
465
|
alpha: true,
|
|
451
466
|
antialias: true,
|
|
452
467
|
stencil: true,
|
|
453
|
-
}
|
|
468
|
+
};
|
|
469
|
+
if (options.canvas) {
|
|
470
|
+
rendererParams.canvas = options.canvas;
|
|
471
|
+
}
|
|
472
|
+
if (options.gl) {
|
|
473
|
+
rendererParams.context = options.gl;
|
|
474
|
+
}
|
|
475
|
+
this._externalGl = !!(options.canvas || options.gl);
|
|
476
|
+
this.renderer = new THREE.WebGLRenderer(rendererParams);
|
|
477
|
+
this.renderer.outputColorSpace = THREE.SRGBColorSpace;
|
|
454
478
|
this.renderer.setPixelRatio(window.devicePixelRatio);
|
|
455
479
|
this.renderer.setSize(this.state.get("cadWidth"), this.state.get("height"));
|
|
456
480
|
this.renderer.setClearColor(0xffffff, 0);
|
|
457
481
|
this.renderer.autoClear = false;
|
|
458
482
|
|
|
483
|
+
// Create studio manager (env, floor, composer created lazily inside)
|
|
484
|
+
|
|
459
485
|
this.lastNotification = {};
|
|
460
486
|
this.lastBbox = null;
|
|
461
487
|
|
|
@@ -493,9 +519,31 @@ class Viewer {
|
|
|
493
519
|
|
|
494
520
|
this.display.setupUI(this, this.renderer.domElement);
|
|
495
521
|
|
|
522
|
+
// Create studio manager (owns env, floor, composer, shadows, subscriptions)
|
|
523
|
+
this._studioManager = new StudioManager({
|
|
524
|
+
renderer: this.renderer,
|
|
525
|
+
state: this.state,
|
|
526
|
+
isRendered: () => this._rendered !== null,
|
|
527
|
+
getScene: () => this.rendered.scene,
|
|
528
|
+
getCamera: () => this.rendered.camera,
|
|
529
|
+
getAmbientLight: () => this.rendered.ambientLight,
|
|
530
|
+
getDirectLight: () => this.rendered.directLight,
|
|
531
|
+
getNestedGroup: () => this.rendered.nestedGroup,
|
|
532
|
+
getClipping: () => this.rendered.clipping,
|
|
533
|
+
getBbox: () => this.bbox,
|
|
534
|
+
getLastBboxId: () => this.lastBbox?.id ?? null,
|
|
535
|
+
setAxes: (flag, notify) => this.setAxes(flag, notify),
|
|
536
|
+
setGrids: (grids, notify) => this.setGrids(grids, notify),
|
|
537
|
+
setOrtho: (flag, notify) => this.setOrtho(flag, notify),
|
|
538
|
+
update: (updateMarker, notify) => this.update(updateMarker, notify),
|
|
539
|
+
dispatchEvent: (event) => this.display.container.dispatchEvent(event),
|
|
540
|
+
onSelectionChanged: (id) => this.display.onSelectionChanged(id),
|
|
541
|
+
});
|
|
542
|
+
|
|
496
543
|
console.debug("three-cad-viewer: WebGL Renderer created");
|
|
497
544
|
}
|
|
498
545
|
|
|
546
|
+
|
|
499
547
|
/**
|
|
500
548
|
* Return three-cad-viewer version as semver string.
|
|
501
549
|
* @returns semver version
|
|
@@ -805,7 +853,14 @@ class Viewer {
|
|
|
805
853
|
update = (updateMarker: boolean, notify: boolean = true): void => {
|
|
806
854
|
if (!this.ready) return;
|
|
807
855
|
|
|
808
|
-
this.
|
|
856
|
+
if (this._externalGl) {
|
|
857
|
+
this.renderer.resetState();
|
|
858
|
+
}
|
|
859
|
+
// When the composer is active, its RenderPass handles clearing;
|
|
860
|
+
// skip manual clear to avoid double-clear artifacts.
|
|
861
|
+
if (!this._studioManager.hasComposer) {
|
|
862
|
+
this.renderer.clear();
|
|
863
|
+
}
|
|
809
864
|
|
|
810
865
|
if (
|
|
811
866
|
this.raycaster &&
|
|
@@ -823,7 +878,19 @@ class Viewer {
|
|
|
823
878
|
this.state.get("cadWidth"),
|
|
824
879
|
this.state.get("height"),
|
|
825
880
|
);
|
|
826
|
-
|
|
881
|
+
|
|
882
|
+
// Env background: render HDRI to 2D render target (fixed-FOV bgCamera)
|
|
883
|
+
if (this._studioManager.isEnvBackgroundActive) {
|
|
884
|
+
this._studioManager.updateEnvBackground(this.renderer, this.rendered.camera.getCamera());
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// Render: use composer pipeline when available (AO + tone mapping + SMAA),
|
|
888
|
+
// otherwise fall back to direct renderer.render().
|
|
889
|
+
if (this._studioManager.hasComposer) {
|
|
890
|
+
this._studioManager.render();
|
|
891
|
+
} else {
|
|
892
|
+
this.renderer.render(this.rendered.scene, this.rendered.camera.getCamera());
|
|
893
|
+
}
|
|
827
894
|
this.cadTools.update();
|
|
828
895
|
|
|
829
896
|
this.rendered.directLight.position.copy(this.rendered.camera.getCamera().position);
|
|
@@ -860,6 +927,16 @@ class Viewer {
|
|
|
860
927
|
},
|
|
861
928
|
notify,
|
|
862
929
|
);
|
|
930
|
+
|
|
931
|
+
// In shared/external WebGL mode, clean up renderer state before external
|
|
932
|
+
// renderers/hooks (overlays, etc.) draw on the same context.
|
|
933
|
+
if (this._externalGl) {
|
|
934
|
+
this.renderer.resetState();
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
if (this.onAfterRender) {
|
|
938
|
+
this.onAfterRender();
|
|
939
|
+
}
|
|
863
940
|
};
|
|
864
941
|
|
|
865
942
|
/**
|
|
@@ -917,11 +994,13 @@ class Viewer {
|
|
|
917
994
|
dispose(): void {
|
|
918
995
|
this.clear();
|
|
919
996
|
|
|
997
|
+
// dispose studio resources (composer, floor, env, shadows — must be before renderer)
|
|
998
|
+
this._studioManager.dispose();
|
|
920
999
|
// dispose renderer
|
|
921
1000
|
this.renderer.renderLists.dispose();
|
|
922
1001
|
this.renderer.dispose();
|
|
923
|
-
//
|
|
924
|
-
if (typeof this.renderer.forceContextLoss === "function") {
|
|
1002
|
+
// Skip context loss for externally provided WebGL contexts
|
|
1003
|
+
if (!this._externalGl && typeof this.renderer.forceContextLoss === "function") {
|
|
925
1004
|
this.renderer.forceContextLoss();
|
|
926
1005
|
}
|
|
927
1006
|
console.debug("three-cad-viewer: WebGL context disposed");
|
|
@@ -984,7 +1063,11 @@ class Viewer {
|
|
|
984
1063
|
this.state.set("zscaleActive", false);
|
|
985
1064
|
}
|
|
986
1065
|
|
|
987
|
-
// Reset to tree tab for next render
|
|
1066
|
+
// Reset to tree tab for next render.
|
|
1067
|
+
// IMPORTANT: This fires the activeTab subscription synchronously,
|
|
1068
|
+
// which calls switchToTab("tree", oldTab). If oldTab was "studio",
|
|
1069
|
+
// leaveStudioMode() runs here, while _rendered and scene are still valid.
|
|
1070
|
+
// Do NOT move this after deepDispose(scene).
|
|
988
1071
|
this.state.set("activeTab", "tree");
|
|
989
1072
|
|
|
990
1073
|
// clear render canvas
|
|
@@ -1002,6 +1085,8 @@ class Viewer {
|
|
|
1002
1085
|
|
|
1003
1086
|
// dispose all rendered state objects
|
|
1004
1087
|
deepDispose(this._rendered.scene);
|
|
1088
|
+
|
|
1089
|
+
// Studio lights were children of the scene and have been disposed by
|
|
1005
1090
|
deepDispose(this._rendered.gridHelper);
|
|
1006
1091
|
deepDispose(this._rendered.clipping);
|
|
1007
1092
|
deepDispose(this._rendered.camera);
|
|
@@ -1133,6 +1218,9 @@ class Viewer {
|
|
|
1133
1218
|
if (group.front) {
|
|
1134
1219
|
return "#" + group.front.material.color.getHexString();
|
|
1135
1220
|
}
|
|
1221
|
+
if (group.originalColor) {
|
|
1222
|
+
return "#" + group.originalColor.getHexString();
|
|
1223
|
+
}
|
|
1136
1224
|
}
|
|
1137
1225
|
return null;
|
|
1138
1226
|
};
|
|
@@ -1289,7 +1377,7 @@ class Viewer {
|
|
|
1289
1377
|
|
|
1290
1378
|
/**
|
|
1291
1379
|
* Set the active sidebar tab.
|
|
1292
|
-
* @param tabName - Tab name: "tree", "clip", "material", or "
|
|
1380
|
+
* @param tabName - Tab name: "tree", "clip", "material", "zebra", or "studio"
|
|
1293
1381
|
* @param notify - whether to send notification or not.
|
|
1294
1382
|
*/
|
|
1295
1383
|
setActiveTab(tabName: ActiveTab, notify: boolean = true): void {
|
|
@@ -1341,10 +1429,26 @@ class Viewer {
|
|
|
1341
1429
|
renderOptions: RenderOptions,
|
|
1342
1430
|
viewerOptions: ViewerOptions,
|
|
1343
1431
|
): void {
|
|
1432
|
+
// Decode instanced/compressed format if detected
|
|
1433
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1434
|
+
if (isInstancedFormat(shapes as any)) {
|
|
1435
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1436
|
+
shapes = decodeInstancedFormat(shapes as any);
|
|
1437
|
+
}
|
|
1438
|
+
// Decode any remaining inline base64 buffers (e.g., edge/vertex-only objects)
|
|
1439
|
+
decodeInlineBuffers(shapes);
|
|
1344
1440
|
this.shapes = shapes;
|
|
1345
1441
|
this.renderOptions = renderOptions;
|
|
1346
1442
|
this.setViewerDefaults(viewerOptions);
|
|
1347
1443
|
|
|
1444
|
+
// Backward compat: studioOptions on shapes root is deprecated
|
|
1445
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1446
|
+
if ((shapes as any).studioOptions) {
|
|
1447
|
+
logger.warn("shapes.studioOptions is deprecated — pass studio settings in viewerOptions instead");
|
|
1448
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1449
|
+
this.state.updateStudioState((shapes as any).studioOptions);
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1348
1452
|
this.animation.cleanBackup();
|
|
1349
1453
|
|
|
1350
1454
|
const timer = new Timer("viewer", this.state.get("timeit"));
|
|
@@ -1435,8 +1539,12 @@ class Viewer {
|
|
|
1435
1539
|
cadWidth: this.state.get("cadWidth"),
|
|
1436
1540
|
height: this.state.get("height"),
|
|
1437
1541
|
maxAnisotropy: this.renderer.capabilities.getMaxAnisotropy(),
|
|
1438
|
-
|
|
1439
|
-
|
|
1542
|
+
...(this.state.get("tools")
|
|
1543
|
+
? {
|
|
1544
|
+
tickValueElement: this.display.tickValueElement,
|
|
1545
|
+
tickInfoElement: this.display.tickInfoElement,
|
|
1546
|
+
}
|
|
1547
|
+
: {}),
|
|
1440
1548
|
getCamera: () => this._rendered?.camera.getCamera() ?? null,
|
|
1441
1549
|
getAxes0: () => this.state?.get("axes0") ?? false,
|
|
1442
1550
|
});
|
|
@@ -1485,6 +1593,9 @@ class Viewer {
|
|
|
1485
1593
|
|
|
1486
1594
|
scene.add(clipping);
|
|
1487
1595
|
|
|
1596
|
+
// Add studio floor group to scene (hidden by default, shown in enterStudioMode)
|
|
1597
|
+
scene.add(this._studioManager.floor.group);
|
|
1598
|
+
|
|
1488
1599
|
// Theme is already resolved ("light" or "dark") by ViewerState constructor
|
|
1489
1600
|
const theme = this.state.get("theme");
|
|
1490
1601
|
|
|
@@ -1589,6 +1700,11 @@ class Viewer {
|
|
|
1589
1700
|
|
|
1590
1701
|
this.ready = true;
|
|
1591
1702
|
|
|
1703
|
+
if (!this.state.get("tools")) {
|
|
1704
|
+
this.display.showToolsPanel(false);
|
|
1705
|
+
this.rendered.orientationMarker.setVisible(false);
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1592
1708
|
// Apply clip settings AFTER ready=true (clip setters check this.ready)
|
|
1593
1709
|
// Set normals first (if provided), passing slider values to avoid reset to gridSize/2
|
|
1594
1710
|
this.setClipNormal(0, viewerOptions.clipNormal0 ?? null, clipSlider0, true);
|
|
@@ -1748,6 +1864,10 @@ class Viewer {
|
|
|
1748
1864
|
this.rendered.camera.switchCamera(flag);
|
|
1749
1865
|
this.rendered.controls.setCamera(this.rendered.camera.getCamera());
|
|
1750
1866
|
|
|
1867
|
+
// Update composer camera after the actual swap (not in the ortho
|
|
1868
|
+
// subscriber, which fires before the camera switches)
|
|
1869
|
+
this._studioManager.setCamera(this.rendered.camera.getCamera());
|
|
1870
|
+
|
|
1751
1871
|
this.rendered.gridHelper.scaleLabels();
|
|
1752
1872
|
this.rendered.gridHelper.update(this.rendered.camera.getZoom(), true);
|
|
1753
1873
|
|
|
@@ -1994,7 +2114,7 @@ class Viewer {
|
|
|
1994
2114
|
name: string,
|
|
1995
2115
|
meta: boolean,
|
|
1996
2116
|
shift: boolean,
|
|
1997
|
-
|
|
2117
|
+
alt: boolean,
|
|
1998
2118
|
point: THREE.Vector3 | null,
|
|
1999
2119
|
nodeType: string | null = "leaf",
|
|
2000
2120
|
tree: boolean = false,
|
|
@@ -2036,7 +2156,8 @@ class Viewer {
|
|
|
2036
2156
|
this.removeLastBbox();
|
|
2037
2157
|
if (tree) {
|
|
2038
2158
|
this.rendered.treeview.hideAll();
|
|
2039
|
-
this.
|
|
2159
|
+
const showEdges = this._studioManager.isActive ? 0 : 1;
|
|
2160
|
+
this.setState(id, [1, showEdges], nodeType ?? "leaf");
|
|
2040
2161
|
} else {
|
|
2041
2162
|
const center = boundingBox.center();
|
|
2042
2163
|
this.setCameraTarget(point ?? new THREE.Vector3(...center));
|
|
@@ -2045,18 +2166,27 @@ class Viewer {
|
|
|
2045
2166
|
} else if (shift) {
|
|
2046
2167
|
this.removeLastBbox();
|
|
2047
2168
|
this.rendered.treeview.hideAll();
|
|
2048
|
-
this.
|
|
2169
|
+
const showEdges = this._studioManager.isActive ? 0 : 1;
|
|
2170
|
+
this.setState(id, [1, showEdges], nodeType ?? "leaf");
|
|
2049
2171
|
const center = boundingBox.center();
|
|
2050
2172
|
this.setCameraTarget(new THREE.Vector3(...center));
|
|
2051
2173
|
this.display.showCenterInfo(center);
|
|
2052
2174
|
} else if (meta) {
|
|
2053
2175
|
this.setState(id, [0, 0], nodeType ?? "leaf");
|
|
2176
|
+
} else if (alt) {
|
|
2177
|
+
// same as else branch to make typscript happy
|
|
2178
|
+
this.display.showBoundingBoxInfo(path, name, boundingBox);
|
|
2179
|
+
this.setBoundingBox(id);
|
|
2180
|
+
this.rendered.treeview.openPath(id);
|
|
2054
2181
|
} else {
|
|
2055
2182
|
this.display.showBoundingBoxInfo(path, name, boundingBox);
|
|
2056
2183
|
this.setBoundingBox(id);
|
|
2057
2184
|
this.rendered.treeview.openPath(id);
|
|
2058
2185
|
}
|
|
2059
2186
|
}
|
|
2187
|
+
if (this._studioManager.isActive) {
|
|
2188
|
+
this.display.onSelectionChanged(this.lastBbox?.id ?? null);
|
|
2189
|
+
}
|
|
2060
2190
|
this.update(true);
|
|
2061
2191
|
};
|
|
2062
2192
|
|
|
@@ -2088,7 +2218,7 @@ class Viewer {
|
|
|
2088
2218
|
this.state.get("height"),
|
|
2089
2219
|
this.bb_max / 30,
|
|
2090
2220
|
this.rendered.scene.children[0],
|
|
2091
|
-
() => {},
|
|
2221
|
+
() => { },
|
|
2092
2222
|
);
|
|
2093
2223
|
raycaster.init();
|
|
2094
2224
|
raycaster.onPointerMove(e);
|
|
@@ -2121,9 +2251,9 @@ class Viewer {
|
|
|
2121
2251
|
boundingBox:
|
|
2122
2252
|
shapesFormat === "GDS"
|
|
2123
2253
|
? new THREE.Box3(
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2254
|
+
point.clone().subScalar(10),
|
|
2255
|
+
point.clone().addScalar(10),
|
|
2256
|
+
)
|
|
2127
2257
|
: nearestMesh.geometry.boundingBox,
|
|
2128
2258
|
boundingSphere:
|
|
2129
2259
|
shapesFormat === "GDS"
|
|
@@ -2672,6 +2802,18 @@ class Viewer {
|
|
|
2672
2802
|
this.update(this.updateMarker);
|
|
2673
2803
|
};
|
|
2674
2804
|
|
|
2805
|
+
/**
|
|
2806
|
+
* Resets zebra tool settings to defaults: count=9, opacity=1, direction=0,
|
|
2807
|
+
* colorScheme=blackwhite, mappingMode=reflection.
|
|
2808
|
+
*/
|
|
2809
|
+
resetZebra = (): void => {
|
|
2810
|
+
this.setZebraCount(9);
|
|
2811
|
+
this.setZebraOpacity(1.0);
|
|
2812
|
+
this.setZebraDirection(0);
|
|
2813
|
+
this.setZebraColorScheme("blackwhite");
|
|
2814
|
+
this.setZebraMappingMode("reflection");
|
|
2815
|
+
};
|
|
2816
|
+
|
|
2675
2817
|
/**
|
|
2676
2818
|
* Gets the current stripe count value.
|
|
2677
2819
|
* @returns The stripe count (2-50).
|
|
@@ -2712,6 +2854,253 @@ class Viewer {
|
|
|
2712
2854
|
return this.state.get("zebraMappingMode");
|
|
2713
2855
|
};
|
|
2714
2856
|
|
|
2857
|
+
// ---------------------------------------------------------------------------
|
|
2858
|
+
// Getters & Setters: Studio Mode
|
|
2859
|
+
// ---------------------------------------------------------------------------
|
|
2860
|
+
|
|
2861
|
+
/**
|
|
2862
|
+
* Sets the studio environment preset.
|
|
2863
|
+
* @param value - The environment name ("studio", "neutral", "outdoor", "none", or custom HDR URL).
|
|
2864
|
+
* @param notify - Whether to notify about the changes.
|
|
2865
|
+
* @public
|
|
2866
|
+
*/
|
|
2867
|
+
setStudioEnvironment = (value: string, notify: boolean = true): void => {
|
|
2868
|
+
this.state.set("studioEnvironment", value, notify);
|
|
2869
|
+
};
|
|
2870
|
+
|
|
2871
|
+
/**
|
|
2872
|
+
* Sets the studio environment intensity.
|
|
2873
|
+
* @param value - The environment intensity (0-3).
|
|
2874
|
+
* @param notify - Whether to notify about the changes.
|
|
2875
|
+
* @public
|
|
2876
|
+
*/
|
|
2877
|
+
setStudioEnvIntensity = (value: number, notify: boolean = true): void => {
|
|
2878
|
+
value = Math.max(0, Math.min(3, value));
|
|
2879
|
+
this.state.set("studioEnvIntensity", value, notify);
|
|
2880
|
+
};
|
|
2881
|
+
|
|
2882
|
+
/**
|
|
2883
|
+
* Sets the background mode for Studio mode.
|
|
2884
|
+
* @param value - The background mode ("grey", "white", "gradient", "environment", or "transparent").
|
|
2885
|
+
* @param notify - Whether to notify about the changes.
|
|
2886
|
+
* @public
|
|
2887
|
+
*/
|
|
2888
|
+
setStudioBackground = (value: StudioBackground, notify: boolean = true): void => {
|
|
2889
|
+
this.state.set("studioBackground", value, notify);
|
|
2890
|
+
};
|
|
2891
|
+
|
|
2892
|
+
/**
|
|
2893
|
+
* Sets the tone mapping mode for Studio mode.
|
|
2894
|
+
* @param value - The tone mapping mode ("neutral", "ACES", or "none").
|
|
2895
|
+
* @param notify - Whether to notify about the changes.
|
|
2896
|
+
* @public
|
|
2897
|
+
*/
|
|
2898
|
+
setStudioToneMapping = (value: StudioToneMapping, notify: boolean = true): void => {
|
|
2899
|
+
this.state.set("studioToneMapping", value, notify);
|
|
2900
|
+
};
|
|
2901
|
+
|
|
2902
|
+
/**
|
|
2903
|
+
* Sets the exposure value for Studio mode.
|
|
2904
|
+
* @param value - The exposure value (0-2).
|
|
2905
|
+
* @param notify - Whether to notify about the changes.
|
|
2906
|
+
* @public
|
|
2907
|
+
*/
|
|
2908
|
+
setStudioExposure = (value: number, notify: boolean = true): void => {
|
|
2909
|
+
value = Math.max(0, Math.min(2, value));
|
|
2910
|
+
this.state.set("studioExposure", value, notify);
|
|
2911
|
+
};
|
|
2912
|
+
|
|
2913
|
+
/**
|
|
2914
|
+
* Sets whether 4K environment maps are used (default: 2K).
|
|
2915
|
+
* @param value - True for 4K, false for 2K.
|
|
2916
|
+
* @param notify - Whether to notify about the changes.
|
|
2917
|
+
* @public
|
|
2918
|
+
*/
|
|
2919
|
+
setStudio4kEnvMaps = (value: boolean, notify: boolean = true): void => {
|
|
2920
|
+
this.state.set("studio4kEnvMaps", value, notify);
|
|
2921
|
+
};
|
|
2922
|
+
|
|
2923
|
+
/**
|
|
2924
|
+
* Gets whether 4K environment maps are enabled.
|
|
2925
|
+
* @returns True for 4K, false for 2K.
|
|
2926
|
+
* @public
|
|
2927
|
+
*/
|
|
2928
|
+
getStudio4kEnvMaps = (): boolean => {
|
|
2929
|
+
return this.state.get("studio4kEnvMaps");
|
|
2930
|
+
};
|
|
2931
|
+
|
|
2932
|
+
/**
|
|
2933
|
+
* Sets the environment rotation for Studio mode.
|
|
2934
|
+
* @param value - The rotation in degrees (0-360).
|
|
2935
|
+
* @param notify - Whether to notify about the changes.
|
|
2936
|
+
* @public
|
|
2937
|
+
*/
|
|
2938
|
+
setStudioEnvRotation = (value: number, notify: boolean = true): void => {
|
|
2939
|
+
this.state.set("studioEnvRotation", value, notify);
|
|
2940
|
+
};
|
|
2941
|
+
|
|
2942
|
+
/**
|
|
2943
|
+
* Gets the current environment rotation for Studio mode.
|
|
2944
|
+
* @returns The rotation in degrees (0-360).
|
|
2945
|
+
* @public
|
|
2946
|
+
*/
|
|
2947
|
+
getStudioEnvRotation = (): number => {
|
|
2948
|
+
return this.state.get("studioEnvRotation");
|
|
2949
|
+
};
|
|
2950
|
+
|
|
2951
|
+
/**
|
|
2952
|
+
* Sets the texture mapping mode for Studio mode.
|
|
2953
|
+
* @param value - The texture mapping mode ("triplanar" or "parametric").
|
|
2954
|
+
* @param notify - Whether to notify about the changes.
|
|
2955
|
+
* @public
|
|
2956
|
+
*/
|
|
2957
|
+
setStudioTextureMapping = (value: StudioTextureMapping, notify: boolean = true): void => {
|
|
2958
|
+
this.state.set("studioTextureMapping", value, notify);
|
|
2959
|
+
};
|
|
2960
|
+
|
|
2961
|
+
/**
|
|
2962
|
+
* Gets the current texture mapping mode for Studio mode.
|
|
2963
|
+
* @returns The texture mapping mode ("triplanar" or "parametric").
|
|
2964
|
+
* @public
|
|
2965
|
+
*/
|
|
2966
|
+
getStudioTextureMapping = (): StudioTextureMapping => {
|
|
2967
|
+
return this.state.get("studioTextureMapping");
|
|
2968
|
+
};
|
|
2969
|
+
|
|
2970
|
+
/**
|
|
2971
|
+
* Gets the current studio environment preset.
|
|
2972
|
+
* @returns The environment name ("studio", "neutral", "outdoor", "none", or custom HDR URL).
|
|
2973
|
+
* @public
|
|
2974
|
+
*/
|
|
2975
|
+
getStudioEnvironment = (): string => {
|
|
2976
|
+
return this.state.get("studioEnvironment");
|
|
2977
|
+
};
|
|
2978
|
+
|
|
2979
|
+
/**
|
|
2980
|
+
* Gets the current studio environment intensity.
|
|
2981
|
+
* @returns The environment intensity (0-3).
|
|
2982
|
+
* @public
|
|
2983
|
+
*/
|
|
2984
|
+
getStudioEnvIntensity = (): number => {
|
|
2985
|
+
return this.state.get("studioEnvIntensity");
|
|
2986
|
+
};
|
|
2987
|
+
|
|
2988
|
+
/**
|
|
2989
|
+
* Gets the current background mode for Studio mode.
|
|
2990
|
+
* @returns The background mode ("grey", "white", "gradient", "environment", or "transparent").
|
|
2991
|
+
* @public
|
|
2992
|
+
*/
|
|
2993
|
+
getStudioBackground = (): StudioBackground => {
|
|
2994
|
+
return this.state.get("studioBackground");
|
|
2995
|
+
};
|
|
2996
|
+
|
|
2997
|
+
/**
|
|
2998
|
+
* Gets the current tone mapping mode for Studio mode.
|
|
2999
|
+
* @returns The tone mapping mode ("neutral", "ACES", or "none").
|
|
3000
|
+
* @public
|
|
3001
|
+
*/
|
|
3002
|
+
getStudioToneMapping = (): StudioToneMapping => {
|
|
3003
|
+
return this.state.get("studioToneMapping");
|
|
3004
|
+
};
|
|
3005
|
+
|
|
3006
|
+
/**
|
|
3007
|
+
* Gets the current exposure value for Studio mode.
|
|
3008
|
+
* @returns The exposure value (0-3).
|
|
3009
|
+
* @public
|
|
3010
|
+
*/
|
|
3011
|
+
getStudioExposure = (): number => {
|
|
3012
|
+
return this.state.get("studioExposure");
|
|
3013
|
+
};
|
|
3014
|
+
|
|
3015
|
+
/**
|
|
3016
|
+
* Sets the shadow intensity in Studio mode.
|
|
3017
|
+
* A value of 0 disables shadows; values > 0 enable them at that darkness.
|
|
3018
|
+
* @param value - The shadow intensity (0-1).
|
|
3019
|
+
* @param notify - Whether to notify about the changes.
|
|
3020
|
+
* @public
|
|
3021
|
+
*/
|
|
3022
|
+
setStudioShadowIntensity = (value: number, notify: boolean = true): void => {
|
|
3023
|
+
value = Math.max(0, Math.min(1, value));
|
|
3024
|
+
this.state.set("studioShadowIntensity", value, notify);
|
|
3025
|
+
};
|
|
3026
|
+
|
|
3027
|
+
/**
|
|
3028
|
+
* Gets the current shadow intensity in Studio mode.
|
|
3029
|
+
* @returns The shadow intensity (0-1). 0 means shadows are off.
|
|
3030
|
+
* @public
|
|
3031
|
+
*/
|
|
3032
|
+
getStudioShadowIntensity = (): number => {
|
|
3033
|
+
return this.state.get("studioShadowIntensity");
|
|
3034
|
+
};
|
|
3035
|
+
|
|
3036
|
+
/**
|
|
3037
|
+
* Sets the shadow softness in Studio mode.
|
|
3038
|
+
* Controls PCSS penumbra width (virtual light source size).
|
|
3039
|
+
* @param value - The shadow softness (0-1).
|
|
3040
|
+
* @param notify - Whether to notify about the changes.
|
|
3041
|
+
* @public
|
|
3042
|
+
*/
|
|
3043
|
+
setStudioShadowSoftness = (value: number, notify: boolean = true): void => {
|
|
3044
|
+
value = Math.max(0, Math.min(1, value));
|
|
3045
|
+
this.state.set("studioShadowSoftness", value, notify);
|
|
3046
|
+
};
|
|
3047
|
+
|
|
3048
|
+
/**
|
|
3049
|
+
* Gets the current shadow softness in Studio mode.
|
|
3050
|
+
* @returns The shadow softness (0-1).
|
|
3051
|
+
* @public
|
|
3052
|
+
*/
|
|
3053
|
+
getStudioShadowSoftness = (): number => {
|
|
3054
|
+
return this.state.get("studioShadowSoftness");
|
|
3055
|
+
};
|
|
3056
|
+
|
|
3057
|
+
/**
|
|
3058
|
+
* Sets the ambient occlusion intensity in Studio mode.
|
|
3059
|
+
* A value of 0 disables AO; values > 0 enable it at that intensity.
|
|
3060
|
+
* @param value - The AO intensity (0-3.0).
|
|
3061
|
+
* @param notify - Whether to notify about the changes.
|
|
3062
|
+
* @public
|
|
3063
|
+
*/
|
|
3064
|
+
setStudioAOIntensity = (value: number, notify: boolean = true): void => {
|
|
3065
|
+
this.state.set("studioAOIntensity", value, notify);
|
|
3066
|
+
};
|
|
3067
|
+
|
|
3068
|
+
/**
|
|
3069
|
+
* Gets the current ambient occlusion intensity in Studio mode.
|
|
3070
|
+
* @returns The AO intensity value (0.5-3.0).
|
|
3071
|
+
* @public
|
|
3072
|
+
*/
|
|
3073
|
+
getStudioAOIntensity = (): number => {
|
|
3074
|
+
return this.state.get("studioAOIntensity");
|
|
3075
|
+
};
|
|
3076
|
+
|
|
3077
|
+
/**
|
|
3078
|
+
* Returns whether Studio mode is currently active.
|
|
3079
|
+
* @returns True if Studio mode is active and the viewer has rendered content.
|
|
3080
|
+
* @public
|
|
3081
|
+
*/
|
|
3082
|
+
get isStudioActive(): boolean {
|
|
3083
|
+
return this._studioManager.isActive;
|
|
3084
|
+
}
|
|
3085
|
+
|
|
3086
|
+
/**
|
|
3087
|
+
* Get the ObjectGroup and path for the currently selected object in Studio mode.
|
|
3088
|
+
* Returns null if nothing is selected, Studio mode is inactive, or the
|
|
3089
|
+
* selection is a CompoundGroup (assembly node) rather than a leaf object.
|
|
3090
|
+
*/
|
|
3091
|
+
getSelectedObjectGroup(): { object: ObjectGroup; path: string } | null {
|
|
3092
|
+
return this._studioManager.getSelectedObjectGroup();
|
|
3093
|
+
}
|
|
3094
|
+
|
|
3095
|
+
/** Enter Studio mode. Called by display.ts switchToTab(). @internal */
|
|
3096
|
+
enterStudioMode = () => this._studioManager.enterStudioMode();
|
|
3097
|
+
|
|
3098
|
+
/** Leave Studio mode. Called by display.ts switchToTab(). @internal */
|
|
3099
|
+
leaveStudioMode = () => this._studioManager.leaveStudioMode();
|
|
3100
|
+
|
|
3101
|
+
/** Reset Studio settings to defaults. @public */
|
|
3102
|
+
resetStudio = () => this._studioManager.resetStudio();
|
|
3103
|
+
|
|
2715
3104
|
// ---------------------------------------------------------------------------
|
|
2716
3105
|
// Camera State Getters & Setters
|
|
2717
3106
|
// ---------------------------------------------------------------------------
|
|
@@ -3927,6 +4316,21 @@ class Viewer {
|
|
|
3927
4316
|
}
|
|
3928
4317
|
};
|
|
3929
4318
|
|
|
4319
|
+
/**
|
|
4320
|
+
* Resets clip planes to default normals and slider positions.
|
|
4321
|
+
* Normals reset to -X, -Y, -Z; sliders to gridSize/2; checkboxes unchecked.
|
|
4322
|
+
*/
|
|
4323
|
+
resetClip = (): void => {
|
|
4324
|
+
if (!this.ready) return;
|
|
4325
|
+
const mid = this.gridSize / 2;
|
|
4326
|
+
this.setClipNormal(0, [-1, 0, 0], mid, true);
|
|
4327
|
+
this.setClipNormal(1, [0, -1, 0], mid, true);
|
|
4328
|
+
this.setClipNormal(2, [0, 0, -1], mid, true);
|
|
4329
|
+
this.setClipIntersection(false, true);
|
|
4330
|
+
this.setClipObjectColorCaps(false, true);
|
|
4331
|
+
this.setClipPlaneHelpers(false, true);
|
|
4332
|
+
};
|
|
4333
|
+
|
|
3930
4334
|
// ---------------------------------------------------------------------------
|
|
3931
4335
|
// Image Export
|
|
3932
4336
|
// ---------------------------------------------------------------------------
|
|
@@ -3982,7 +4386,14 @@ class Viewer {
|
|
|
3982
4386
|
this.state.get("cadWidth"),
|
|
3983
4387
|
this.state.get("height"),
|
|
3984
4388
|
);
|
|
3985
|
-
|
|
4389
|
+
if (this._studioManager.isEnvBackgroundActive) {
|
|
4390
|
+
this._studioManager.updateEnvBackground(this.renderer, this.rendered.camera.getCamera());
|
|
4391
|
+
}
|
|
4392
|
+
if (this._studioManager.hasComposer) {
|
|
4393
|
+
this._studioManager.render();
|
|
4394
|
+
} else {
|
|
4395
|
+
this.renderer.render(this.rendered.scene, this.rendered.camera.getCamera());
|
|
4396
|
+
}
|
|
3986
4397
|
},
|
|
3987
4398
|
onComplete: () => {
|
|
3988
4399
|
// Restore animation loop to original state
|
|
@@ -4226,6 +4637,9 @@ class Viewer {
|
|
|
4226
4637
|
this.rendered.camera.changeDimensions(this.bb_radius, cadWidth, height);
|
|
4227
4638
|
this.controls.handleResize();
|
|
4228
4639
|
|
|
4640
|
+
// Resize the post-processing composer (render targets must match viewport)
|
|
4641
|
+
this._studioManager.setSize(cadWidth, height);
|
|
4642
|
+
|
|
4229
4643
|
// update the this
|
|
4230
4644
|
this.update(true);
|
|
4231
4645
|
|
|
@@ -4307,14 +4721,31 @@ class Viewer {
|
|
|
4307
4721
|
};
|
|
4308
4722
|
|
|
4309
4723
|
/**
|
|
4310
|
-
*
|
|
4311
|
-
* @param flag -
|
|
4724
|
+
* Collapse or expand the info panel in glass mode.
|
|
4725
|
+
* @param flag - true to show, false to collapse
|
|
4312
4726
|
* @public
|
|
4313
4727
|
*/
|
|
4314
|
-
|
|
4728
|
+
showInfoPanel = (flag: boolean): void => {
|
|
4315
4729
|
this.display.showInfo(flag);
|
|
4316
4730
|
};
|
|
4317
4731
|
|
|
4732
|
+
/**
|
|
4733
|
+
* @deprecated Use showInfoPanel() instead.
|
|
4734
|
+
*/
|
|
4735
|
+
showInfo = (flag: boolean): void => {
|
|
4736
|
+
console.warn("showInfo() is deprecated, use showInfoPanel() instead.");
|
|
4737
|
+
this.showInfoPanel(flag);
|
|
4738
|
+
};
|
|
4739
|
+
|
|
4740
|
+
/**
|
|
4741
|
+
* Collapse or expand the tools panel (tabs + content) in glass mode.
|
|
4742
|
+
* @param flag - true to show, false to collapse
|
|
4743
|
+
* @public
|
|
4744
|
+
*/
|
|
4745
|
+
showToolsPanel = (flag: boolean): void => {
|
|
4746
|
+
this.display.showToolsPanel(flag);
|
|
4747
|
+
};
|
|
4748
|
+
|
|
4318
4749
|
/**
|
|
4319
4750
|
* Show/hide the pinning button.
|
|
4320
4751
|
* @param flag - whether to show the pinning button
|