@file-viewer/renderer-3d 2.2.3 → 2.2.5
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/model.js +86 -61
- package/dist/modelViewport.d.ts +23 -0
- package/dist/modelViewport.js +39 -0
- package/package.json +3 -3
package/dist/model.js
CHANGED
|
@@ -3,6 +3,7 @@ import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
|
3
3
|
import { createFileViewerViewStateChange, createFileViewerViewStateChangeEmitter, createFileViewerZoomChangeEmitter, createFileViewerTranslator, registerFileViewerZoomProvider, registerFileViewerViewStateProvider, resolveFileViewerColorScheme, resolveFileViewerLocale, resolveFileViewerModelAssetUrls, resolveFileViewerRuntimeAssetBaseUrl, unregisterFileViewerZoomProvider, unregisterFileViewerViewStateProvider, } from '@file-viewer/core';
|
|
4
4
|
import { formatGeometryKernelNotice, importOcctGeometryFile, inspectGeometryKernelFile, isGeometryKernelFormat, } from '@file-viewer/geometry-engine';
|
|
5
5
|
import { buildOcctThreeObject } from './occtModel.js';
|
|
6
|
+
import { syncModelViewport } from './modelViewport.js';
|
|
6
7
|
const modelStyle = `
|
|
7
8
|
.model-viewer{display:flex;height:100%;min-height:100%;flex-direction:column;background:#f8fafc;color:#162333}
|
|
8
9
|
.model-viewer *{box-sizing:border-box}
|
|
@@ -199,6 +200,8 @@ export default async function renderModel(buffer, target, type = 'glb', context)
|
|
|
199
200
|
let stopSystemThemeListener = null;
|
|
200
201
|
let activeDarkMode = null;
|
|
201
202
|
let resizeObserver = null;
|
|
203
|
+
let controlsWithEvents = null;
|
|
204
|
+
let disposed = false;
|
|
202
205
|
let animationFrame = 0;
|
|
203
206
|
let activeVersion = 0;
|
|
204
207
|
let modelMeshCount = 0;
|
|
@@ -397,12 +400,13 @@ export default async function renderModel(buffer, target, type = 'glb', context)
|
|
|
397
400
|
if (!renderer || !camera) {
|
|
398
401
|
return;
|
|
399
402
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
403
|
+
syncModelViewport({
|
|
404
|
+
renderer,
|
|
405
|
+
camera,
|
|
406
|
+
stage,
|
|
407
|
+
canvas,
|
|
408
|
+
devicePixelRatio: target.ownerDocument.defaultView?.devicePixelRatio || 1,
|
|
409
|
+
});
|
|
406
410
|
};
|
|
407
411
|
const findThemeHost = () => {
|
|
408
412
|
let current = root;
|
|
@@ -495,7 +499,6 @@ export default async function renderModel(buffer, target, type = 'glb', context)
|
|
|
495
499
|
canvas,
|
|
496
500
|
powerPreference: 'high-performance',
|
|
497
501
|
});
|
|
498
|
-
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
|
|
499
502
|
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
|
500
503
|
}
|
|
501
504
|
if (!scene) {
|
|
@@ -525,6 +528,10 @@ export default async function renderModel(buffer, target, type = 'glb', context)
|
|
|
525
528
|
applyModelTheme(initializeTheme);
|
|
526
529
|
updateHelperVisibility();
|
|
527
530
|
resize();
|
|
531
|
+
if (!controls) {
|
|
532
|
+
throw new Error('Failed to initialize 3D controls.');
|
|
533
|
+
}
|
|
534
|
+
return controls;
|
|
528
535
|
};
|
|
529
536
|
const clearModel = () => {
|
|
530
537
|
if (modelRoot && scene) {
|
|
@@ -919,12 +926,18 @@ export default async function renderModel(buffer, target, type = 'glb', context)
|
|
|
919
926
|
errorMessage = normalizeError(reason) || t('model.error.parseFailed', { type: normalizedType.toUpperCase() });
|
|
920
927
|
updateUi();
|
|
921
928
|
zoomEmitter.emit();
|
|
929
|
+
throw reason instanceof Error ? reason : new Error(errorMessage);
|
|
922
930
|
}
|
|
923
931
|
};
|
|
924
932
|
const renderFrame = (timestamp) => {
|
|
925
933
|
if (!renderer || !scene || !camera || !controls) {
|
|
926
934
|
return;
|
|
927
935
|
}
|
|
936
|
+
// A framework transition or a repeated File assignment can reset canvas
|
|
937
|
+
// attributes without changing its CSS box. ResizeObserver will not see
|
|
938
|
+
// that backing-buffer downgrade, so validate the cheap size contract on
|
|
939
|
+
// each frame and resize only when the values diverge.
|
|
940
|
+
resize();
|
|
928
941
|
timer.update(timestamp);
|
|
929
942
|
const delta = timer.getDelta();
|
|
930
943
|
controls.autoRotate = autoRotate;
|
|
@@ -972,63 +985,75 @@ export default async function renderModel(buffer, target, type = 'glb', context)
|
|
|
972
985
|
updateHelperVisibility();
|
|
973
986
|
emitViewStateChange('view-option-change', 'user');
|
|
974
987
|
});
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
988
|
+
const cleanup = () => {
|
|
989
|
+
if (disposed)
|
|
990
|
+
return;
|
|
991
|
+
disposed = true;
|
|
992
|
+
activeVersion += 1;
|
|
993
|
+
window.cancelAnimationFrame(animationFrame);
|
|
994
|
+
resizeObserver?.disconnect();
|
|
995
|
+
resizeObserver = null;
|
|
996
|
+
themeObserver?.disconnect();
|
|
997
|
+
themeObserver = null;
|
|
998
|
+
stopSystemThemeListener?.();
|
|
999
|
+
stopSystemThemeListener = null;
|
|
1000
|
+
clearModel();
|
|
1001
|
+
controlsWithEvents?.removeEventListener('start', onControlsStart);
|
|
1002
|
+
controlsWithEvents?.removeEventListener('end', onControlsEnd);
|
|
1003
|
+
controlsWithEvents = null;
|
|
1004
|
+
unregisterFileViewerZoomProvider(root);
|
|
1005
|
+
unregisterFileViewerViewStateProvider(root);
|
|
1006
|
+
controls?.dispose();
|
|
1007
|
+
controls = null;
|
|
1008
|
+
if (scene) {
|
|
1009
|
+
disposeObject(scene);
|
|
1010
|
+
}
|
|
1011
|
+
renderer?.dispose();
|
|
1012
|
+
renderer?.renderLists.dispose();
|
|
1013
|
+
renderer = null;
|
|
1014
|
+
timer.dispose();
|
|
1015
|
+
scene = null;
|
|
1016
|
+
camera = null;
|
|
1017
|
+
gridHelper = null;
|
|
1018
|
+
axesHelper = null;
|
|
1019
|
+
hemisphereLight = null;
|
|
1020
|
+
target.replaceChildren();
|
|
1021
|
+
};
|
|
1022
|
+
try {
|
|
1023
|
+
updateUi();
|
|
1024
|
+
controlsWithEvents = ensureScene();
|
|
1025
|
+
observeModelTheme();
|
|
1026
|
+
controlsWithEvents.addEventListener('start', onControlsStart);
|
|
1027
|
+
controlsWithEvents.addEventListener('end', onControlsEnd);
|
|
1028
|
+
registerFileViewerZoomProvider(root, {
|
|
1029
|
+
zoomIn: () => setModelZoom(getModelScale() * MODEL_ZOOM_STEP, 'user', 'zoom-in'),
|
|
1030
|
+
zoomOut: () => setModelZoom(getModelScale() / MODEL_ZOOM_STEP, 'user', 'zoom-out'),
|
|
1031
|
+
resetZoom: () => setModelZoom(1, 'user', 'zoom-reset'),
|
|
1032
|
+
setZoom: scale => setModelZoom(scale, 'api', 'zoom-change'),
|
|
1033
|
+
fit: applyModelFit,
|
|
1034
|
+
getState: getModelZoomState,
|
|
1035
|
+
subscribe: zoomEmitter.subscribe,
|
|
1036
|
+
});
|
|
1037
|
+
registerFileViewerViewStateProvider(root, {
|
|
1038
|
+
getState: getModelViewState,
|
|
1039
|
+
applyState: applyModelViewState,
|
|
1040
|
+
fit: applyModelFit,
|
|
1041
|
+
subscribe: viewStateEmitter.subscribe,
|
|
1042
|
+
});
|
|
1043
|
+
resizeObserver = new ResizeObserver(resize);
|
|
1044
|
+
resizeObserver.observe(stage);
|
|
1045
|
+
timer.connect(target.ownerDocument);
|
|
1046
|
+
renderFrame();
|
|
1047
|
+
await loadModel();
|
|
1048
|
+
}
|
|
1049
|
+
catch (error) {
|
|
1050
|
+
cleanup();
|
|
1051
|
+
throw error;
|
|
1052
|
+
}
|
|
1001
1053
|
return {
|
|
1002
1054
|
$el: root,
|
|
1003
1055
|
unmount() {
|
|
1004
|
-
|
|
1005
|
-
window.cancelAnimationFrame(animationFrame);
|
|
1006
|
-
resizeObserver?.disconnect();
|
|
1007
|
-
resizeObserver = null;
|
|
1008
|
-
themeObserver?.disconnect();
|
|
1009
|
-
themeObserver = null;
|
|
1010
|
-
stopSystemThemeListener?.();
|
|
1011
|
-
stopSystemThemeListener = null;
|
|
1012
|
-
clearModel();
|
|
1013
|
-
controlsWithEvents?.removeEventListener('start', onControlsStart);
|
|
1014
|
-
controlsWithEvents?.removeEventListener('end', onControlsEnd);
|
|
1015
|
-
unregisterFileViewerZoomProvider(root);
|
|
1016
|
-
unregisterFileViewerViewStateProvider(root);
|
|
1017
|
-
controls?.dispose();
|
|
1018
|
-
controls = null;
|
|
1019
|
-
if (scene) {
|
|
1020
|
-
disposeObject(scene);
|
|
1021
|
-
}
|
|
1022
|
-
renderer?.dispose();
|
|
1023
|
-
renderer?.renderLists.dispose();
|
|
1024
|
-
renderer = null;
|
|
1025
|
-
timer.dispose();
|
|
1026
|
-
scene = null;
|
|
1027
|
-
camera = null;
|
|
1028
|
-
gridHelper = null;
|
|
1029
|
-
axesHelper = null;
|
|
1030
|
-
hemisphereLight = null;
|
|
1031
|
-
target.replaceChildren();
|
|
1056
|
+
cleanup();
|
|
1032
1057
|
},
|
|
1033
1058
|
};
|
|
1034
1059
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface ModelViewportRenderer {
|
|
2
|
+
getPixelRatio(): number;
|
|
3
|
+
setPixelRatio(value: number): void;
|
|
4
|
+
setSize(width: number, height: number, updateStyle: boolean): void;
|
|
5
|
+
}
|
|
6
|
+
export interface ModelViewportCamera {
|
|
7
|
+
aspect: number;
|
|
8
|
+
updateProjectionMatrix(): void;
|
|
9
|
+
}
|
|
10
|
+
export interface SyncModelViewportInput {
|
|
11
|
+
renderer: ModelViewportRenderer;
|
|
12
|
+
camera: ModelViewportCamera;
|
|
13
|
+
stage: HTMLElement;
|
|
14
|
+
canvas: HTMLCanvasElement;
|
|
15
|
+
devicePixelRatio?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface ModelViewportState {
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
pixelRatio: number;
|
|
21
|
+
resized: boolean;
|
|
22
|
+
}
|
|
23
|
+
export declare const syncModelViewport: ({ renderer, camera, stage, canvas, devicePixelRatio }: SyncModelViewportInput) => ModelViewportState;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const resolveCssSize = (stage, canvas) => {
|
|
2
|
+
return {
|
|
3
|
+
width: Math.max(1, Math.floor(stage.clientWidth ||
|
|
4
|
+
canvas.clientWidth ||
|
|
5
|
+
stage.getBoundingClientRect().width ||
|
|
6
|
+
canvas.getBoundingClientRect().width)),
|
|
7
|
+
height: Math.max(1, Math.floor(stage.clientHeight ||
|
|
8
|
+
canvas.clientHeight ||
|
|
9
|
+
stage.getBoundingClientRect().height ||
|
|
10
|
+
canvas.getBoundingClientRect().height))
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
export const syncModelViewport = ({ renderer, camera, stage, canvas, devicePixelRatio = 1 }) => {
|
|
14
|
+
const { width, height } = resolveCssSize(stage, canvas);
|
|
15
|
+
const pixelRatio = Math.min(2, Math.max(1, Number.isFinite(devicePixelRatio) ? devicePixelRatio : 1));
|
|
16
|
+
const pixelRatioChanged = Math.abs(renderer.getPixelRatio() - pixelRatio) > 0.001;
|
|
17
|
+
if (pixelRatioChanged) {
|
|
18
|
+
renderer.setPixelRatio(pixelRatio);
|
|
19
|
+
}
|
|
20
|
+
const expectedWidth = Math.max(1, Math.floor(width * pixelRatio));
|
|
21
|
+
const expectedHeight = Math.max(1, Math.floor(height * pixelRatio));
|
|
22
|
+
const resized = (pixelRatioChanged ||
|
|
23
|
+
canvas.width !== expectedWidth ||
|
|
24
|
+
canvas.height !== expectedHeight);
|
|
25
|
+
if (resized) {
|
|
26
|
+
renderer.setSize(width, height, false);
|
|
27
|
+
}
|
|
28
|
+
const aspect = width / height;
|
|
29
|
+
if (Math.abs(camera.aspect - aspect) > 0.0001) {
|
|
30
|
+
camera.aspect = aspect;
|
|
31
|
+
camera.updateProjectionMatrix();
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
width,
|
|
35
|
+
height,
|
|
36
|
+
pixelRatio,
|
|
37
|
+
resized
|
|
38
|
+
};
|
|
39
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@file-viewer/renderer-3d",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.5",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Standalone 3D model renderer plugin for File Viewer powered by Three.js loaders and OrbitControls.",
|
|
@@ -57,8 +57,8 @@
|
|
|
57
57
|
"LICENSE"
|
|
58
58
|
],
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@file-viewer/core": "2.2.
|
|
61
|
-
"@file-viewer/geometry-engine": "2.2.
|
|
60
|
+
"@file-viewer/core": "2.2.5",
|
|
61
|
+
"@file-viewer/geometry-engine": "2.2.5",
|
|
62
62
|
"three": "^0.184.0"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|