@inweb/viewer-three 26.10.6 → 26.11.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 (143) hide show
  1. package/README.md +7 -4
  2. package/dist/{plugins → extensions}/components/AxesHelperComponent.js +23 -1
  3. package/dist/extensions/components/AxesHelperComponent.js.map +1 -0
  4. package/dist/extensions/components/AxesHelperComponent.min.js +24 -0
  5. package/dist/{plugins → extensions}/components/AxesHelperComponent.module.js +24 -2
  6. package/dist/extensions/components/AxesHelperComponent.module.js.map +1 -0
  7. package/dist/{plugins → extensions}/components/ExtentsHelperComponent.js +18 -0
  8. package/dist/extensions/components/ExtentsHelperComponent.js.map +1 -0
  9. package/dist/{plugins/components/AxesHelperComponent.min.js → extensions/components/ExtentsHelperComponent.min.js} +1 -1
  10. package/dist/{plugins → extensions}/components/ExtentsHelperComponent.module.js +19 -1
  11. package/dist/extensions/components/ExtentsHelperComponent.module.js.map +1 -0
  12. package/dist/extensions/components/GridHelperComponent.js.map +1 -0
  13. package/dist/extensions/components/GridHelperComponent.module.js.map +1 -0
  14. package/dist/extensions/components/LightHelperComponent.js.map +1 -0
  15. package/dist/extensions/components/LightHelperComponent.module.js.map +1 -0
  16. package/dist/extensions/components/RoomEnvironmentComponent.js.map +1 -0
  17. package/dist/extensions/components/RoomEnvironmentComponent.module.js.map +1 -0
  18. package/dist/extensions/components/StatsPanelComponent.js.map +1 -0
  19. package/dist/extensions/components/StatsPanelComponent.module.js.map +1 -0
  20. package/dist/{plugins → extensions}/loaders/GLTFCloudLoader.js +2 -3
  21. package/dist/extensions/loaders/GLTFCloudLoader.js.map +1 -0
  22. package/dist/{plugins → extensions}/loaders/GLTFCloudLoader.min.js +1 -1
  23. package/dist/{plugins → extensions}/loaders/GLTFCloudLoader.module.js +2 -3
  24. package/dist/extensions/loaders/GLTFCloudLoader.module.js.map +1 -0
  25. package/dist/extensions/loaders/GLTFFileLoader.js +2499 -0
  26. package/dist/extensions/loaders/GLTFFileLoader.js.map +1 -0
  27. package/dist/extensions/loaders/GLTFFileLoader.min.js +24 -0
  28. package/dist/extensions/loaders/GLTFFileLoader.module.js +74 -0
  29. package/dist/extensions/loaders/GLTFFileLoader.module.js.map +1 -0
  30. package/dist/{plugins → extensions}/loaders/IFCXLoader.js +5 -7
  31. package/dist/extensions/loaders/IFCXLoader.js.map +1 -0
  32. package/dist/{plugins → extensions}/loaders/IFCXLoader.min.js +1 -1
  33. package/dist/{plugins → extensions}/loaders/IFCXLoader.module.js +5 -7
  34. package/dist/extensions/loaders/IFCXLoader.module.js.map +1 -0
  35. package/dist/{plugins → extensions}/loaders/PotreeLoader.js +1 -2
  36. package/dist/extensions/loaders/PotreeLoader.js.map +1 -0
  37. package/dist/{plugins → extensions}/loaders/PotreeLoader.min.js +1 -1
  38. package/dist/{plugins → extensions}/loaders/PotreeLoader.module.js +1 -2
  39. package/dist/extensions/loaders/PotreeLoader.module.js.map +1 -0
  40. package/dist/viewer-three.js +1015 -2926
  41. package/dist/viewer-three.js.map +1 -1
  42. package/dist/viewer-three.min.js +3 -3
  43. package/dist/viewer-three.module.js +847 -356
  44. package/dist/viewer-three.module.js.map +1 -1
  45. package/{plugins → extensions}/components/AxesHelperComponent.ts +31 -2
  46. package/{plugins → extensions}/components/ExtentsHelperComponent.ts +25 -0
  47. package/{plugins → extensions}/loaders/GLTFCloudLoader.ts +2 -3
  48. package/{src/Viewer → extensions}/loaders/GLTFFileLoader.ts +21 -12
  49. package/{plugins → extensions}/loaders/IFCX/IFCXCloudLoader.ts +5 -5
  50. package/{plugins → extensions}/loaders/IFCX/IFCXFileLoader.ts +3 -4
  51. package/{plugins → extensions}/loaders/Potree/PotreeFileLoader.ts +3 -4
  52. package/lib/Viewer/Viewer.d.ts +27 -20
  53. package/lib/Viewer/commands/GetSelected2.d.ts +2 -0
  54. package/lib/Viewer/commands/SelectModel.d.ts +1 -1
  55. package/lib/Viewer/commands/SetSelected2.d.ts +2 -0
  56. package/lib/Viewer/components/SelectionComponent.d.ts +1 -3
  57. package/lib/Viewer/components/index.d.ts +6 -6
  58. package/lib/Viewer/draggers/MeasureLineDragger.d.ts +7 -1
  59. package/lib/Viewer/loaders/DynamicGltfLoader/DynamicModelImpl.d.ts +0 -1
  60. package/lib/Viewer/loaders/GLTFBinaryExtension.d.ts +5 -0
  61. package/lib/Viewer/loaders/GLTFCloudDynamicLoader.d.ts +2 -2
  62. package/lib/Viewer/loaders/{GLTFFileLoader.d.ts → GLTFFileDynamicLoader.d.ts} +7 -1
  63. package/lib/Viewer/loaders/GLTFLoadingManager.d.ts +4 -3
  64. package/lib/Viewer/loaders/RangesLoader.d.ts +15 -0
  65. package/lib/Viewer/loaders/index.d.ts +22 -14
  66. package/lib/Viewer/measurement/Snapper.d.ts +15 -0
  67. package/lib/Viewer/measurement/UnitConverter.d.ts +63 -0
  68. package/lib/Viewer/measurement/UnitFormatter.d.ts +4 -0
  69. package/lib/Viewer/models/IModelImpl.d.ts +10 -8
  70. package/lib/Viewer/models/ModelImpl.d.ts +7 -5
  71. package/package.json +11 -11
  72. package/src/Viewer/Viewer.ts +120 -88
  73. package/src/Viewer/commands/ClearSelected.ts +3 -1
  74. package/src/Viewer/commands/GetModels.ts +1 -1
  75. package/src/Viewer/commands/GetSelected.ts +2 -2
  76. package/src/Viewer/commands/GetSelected2.ts +34 -0
  77. package/src/Viewer/commands/HideSelected.ts +3 -1
  78. package/src/Viewer/commands/SelectModel.ts +5 -5
  79. package/src/Viewer/commands/SetSelected.ts +9 -10
  80. package/src/Viewer/commands/SetSelected2.ts +42 -0
  81. package/src/Viewer/commands/ZoomToObjects.ts +5 -6
  82. package/src/Viewer/commands/ZoomToSelected.ts +3 -1
  83. package/src/Viewer/commands/index.ts +4 -0
  84. package/src/Viewer/components/CameraComponent.ts +6 -1
  85. package/src/Viewer/components/ExtentsComponent.ts +4 -1
  86. package/src/Viewer/components/SelectionComponent.ts +7 -30
  87. package/src/Viewer/components/index.ts +6 -6
  88. package/src/Viewer/draggers/MeasureLineDragger.ts +84 -226
  89. package/src/Viewer/loaders/DynamicGltfLoader/DynamicGltfLoader.js +263 -34
  90. package/src/Viewer/loaders/DynamicGltfLoader/DynamicModelImpl.ts +20 -10
  91. package/src/Viewer/loaders/DynamicGltfLoader/GltfStructure.js +4 -1
  92. package/src/Viewer/loaders/GLTFBinaryExtension.ts +91 -0
  93. package/src/Viewer/loaders/GLTFCloudDynamicLoader.ts +13 -19
  94. package/src/Viewer/loaders/GLTFFileDynamicLoader.ts +145 -0
  95. package/src/Viewer/loaders/GLTFLoadingManager.ts +5 -4
  96. package/src/Viewer/loaders/RangesLoader.ts +95 -0
  97. package/src/Viewer/loaders/index.ts +24 -16
  98. package/src/Viewer/measurement/Snapper.ts +208 -0
  99. package/src/Viewer/measurement/UnitConverter.ts +47 -0
  100. package/src/Viewer/measurement/UnitFormatter.ts +95 -0
  101. package/src/Viewer/models/IModelImpl.ts +15 -8
  102. package/src/Viewer/models/ModelImpl.ts +48 -17
  103. package/src/index-umd.ts +1 -1
  104. package/dist/plugins/components/AxesHelperComponent.js.map +0 -1
  105. package/dist/plugins/components/AxesHelperComponent.module.js.map +0 -1
  106. package/dist/plugins/components/ExtentsHelperComponent.js.map +0 -1
  107. package/dist/plugins/components/ExtentsHelperComponent.min.js +0 -24
  108. package/dist/plugins/components/ExtentsHelperComponent.module.js.map +0 -1
  109. package/dist/plugins/components/GridHelperComponent.js.map +0 -1
  110. package/dist/plugins/components/GridHelperComponent.module.js.map +0 -1
  111. package/dist/plugins/components/LightHelperComponent.js.map +0 -1
  112. package/dist/plugins/components/LightHelperComponent.module.js.map +0 -1
  113. package/dist/plugins/components/RoomEnvironmentComponent.js.map +0 -1
  114. package/dist/plugins/components/RoomEnvironmentComponent.module.js.map +0 -1
  115. package/dist/plugins/components/StatsPanelComponent.js.map +0 -1
  116. package/dist/plugins/components/StatsPanelComponent.module.js.map +0 -1
  117. package/dist/plugins/loaders/GLTFCloudLoader.js.map +0 -1
  118. package/dist/plugins/loaders/GLTFCloudLoader.module.js.map +0 -1
  119. package/dist/plugins/loaders/IFCXLoader.js.map +0 -1
  120. package/dist/plugins/loaders/IFCXLoader.module.js.map +0 -1
  121. package/dist/plugins/loaders/PotreeLoader.js.map +0 -1
  122. package/dist/plugins/loaders/PotreeLoader.module.js.map +0 -1
  123. /package/dist/{plugins → extensions}/components/GridHelperComponent.js +0 -0
  124. /package/dist/{plugins → extensions}/components/GridHelperComponent.min.js +0 -0
  125. /package/dist/{plugins → extensions}/components/GridHelperComponent.module.js +0 -0
  126. /package/dist/{plugins → extensions}/components/LightHelperComponent.js +0 -0
  127. /package/dist/{plugins → extensions}/components/LightHelperComponent.min.js +0 -0
  128. /package/dist/{plugins → extensions}/components/LightHelperComponent.module.js +0 -0
  129. /package/dist/{plugins → extensions}/components/RoomEnvironmentComponent.js +0 -0
  130. /package/dist/{plugins → extensions}/components/RoomEnvironmentComponent.min.js +0 -0
  131. /package/dist/{plugins → extensions}/components/RoomEnvironmentComponent.module.js +0 -0
  132. /package/dist/{plugins → extensions}/components/StatsPanelComponent.js +0 -0
  133. /package/dist/{plugins → extensions}/components/StatsPanelComponent.min.js +0 -0
  134. /package/dist/{plugins → extensions}/components/StatsPanelComponent.module.js +0 -0
  135. /package/{plugins → extensions}/components/GridHelperComponent.ts +0 -0
  136. /package/{plugins → extensions}/components/LightHelperComponent.ts +0 -0
  137. /package/{plugins → extensions}/components/RoomEnvironmentComponent.ts +0 -0
  138. /package/{plugins → extensions}/components/StatsPanelComponent.ts +0 -0
  139. /package/{plugins → extensions}/loaders/IFCX/IFCXLoader.ts +0 -0
  140. /package/{plugins → extensions}/loaders/IFCX/index.ts +0 -0
  141. /package/{plugins → extensions}/loaders/IFCX/render.js +0 -0
  142. /package/{plugins → extensions}/loaders/Potree/PotreeModelImpl.ts +0 -0
  143. /package/{plugins → extensions}/loaders/Potree/index.ts +0 -0
@@ -23,12 +23,11 @@
23
23
 
24
24
  import { draggersRegistry, commandsRegistry, Options, componentsRegistry, Loader, loadersRegistry, CANVAS_EVENTS } from '@inweb/viewer-core';
25
25
  export * from '@inweb/viewer-core';
26
- import { Line, Vector3, BufferGeometry, Float32BufferAttribute, LineBasicMaterial, Mesh, MeshBasicMaterial, DoubleSide, EventDispatcher, MOUSE, TOUCH, Spherical, Quaternion, Vector2, Plane, Object3D, Line3, Matrix4, Vector4, MathUtils, Raycaster, EdgesGeometry, Controls, Clock, Sphere, Box3, Color, PerspectiveCamera, OrthographicCamera, AmbientLight, DirectionalLight, HemisphereLight, MeshPhongMaterial, WebGLRenderTarget, UnsignedByteType, RGBAFormat, CylinderGeometry, Sprite, CanvasTexture, SRGBColorSpace, SpriteMaterial, LoadingManager, LoaderUtils, TextureLoader, BufferAttribute, PointsMaterial, Points, TriangleStripDrawMode, TriangleFanDrawMode, LineSegments, LineLoop, Group, NormalBlending, UniformsUtils, ShaderMaterial, AdditiveBlending, HalfFloatType, Scene, WebGLRenderer, LinearSRGBColorSpace } from 'three';
26
+ import { Line, Vector3, BufferGeometry, Float32BufferAttribute, LineBasicMaterial, Mesh, MeshBasicMaterial, DoubleSide, EventDispatcher, MOUSE, TOUCH, Spherical, Quaternion, Vector2, Plane, Object3D, Line3, Raycaster, MathUtils, EdgesGeometry, Matrix4, Vector4, Controls, Clock, Sphere, Box3, Color, PerspectiveCamera, OrthographicCamera, AmbientLight, DirectionalLight, HemisphereLight, MeshPhongMaterial, WebGLRenderTarget, UnsignedByteType, RGBAFormat, CylinderGeometry, Sprite, CanvasTexture, SRGBColorSpace, SpriteMaterial, TextureLoader, BufferAttribute, PointsMaterial, Points, TriangleStripDrawMode, TriangleFanDrawMode, LineSegments, LineLoop, Group, NormalBlending, LoadingManager, LoaderUtils, FileLoader, UniformsUtils, ShaderMaterial, AdditiveBlending, HalfFloatType, Scene, WebGLRenderer, LinearSRGBColorSpace } from 'three';
27
27
  import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js';
28
28
  import { LineSegmentsGeometry } from 'three/examples/jsm/lines/LineSegmentsGeometry.js';
29
29
  import { Wireframe } from 'three/examples/jsm/lines/Wireframe.js';
30
30
  import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js';
31
- import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
32
31
  import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
33
32
  import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
34
33
  import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
@@ -914,116 +913,91 @@ class CuttingPlaneZAxisDragger extends CuttingPlaneDragger {
914
913
  }
915
914
  }
916
915
 
917
- const PRECISION = 0.01;
918
- const DESKTOP_SNAP_DISTANCE = 10;
919
- const MOBILE_SNAP_DISTANCE = 50;
920
- class MeasureLineDragger extends OrbitDragger {
921
- constructor(viewer) {
922
- super(viewer);
923
- this.onPointerDown = (event) => {
924
- if (event.button !== 0)
925
- return;
926
- this.line.startPoint = this.snapper.getSnapPoint(event);
927
- this.line.render();
928
- this.viewer.canvas.setPointerCapture(event.pointerId);
929
- this.orbit.enabled = !this.line.startPoint;
930
- };
931
- this.onPointerMove = (event) => {
932
- if (this.orbit.enabled && this.orbit.state !== -1)
933
- return;
934
- const snapPoint = this.snapper.getSnapPoint(event);
935
- if (snapPoint && this.line.endPoint && snapPoint.equals(this.line.endPoint))
936
- return;
937
- this.line.endPoint = snapPoint;
938
- this.line.render();
939
- if (this.line.startPoint)
940
- this.changed = true;
941
- };
942
- this.onPointerUp = (event) => {
943
- if (this.line.startPoint && this.line.endPoint && this.line.getDistance() >= PRECISION) {
944
- this.line = new MeasureLine(this.overlay);
945
- this.overlay.addLine(this.line);
946
- }
947
- else {
948
- this.line.startPoint = undefined;
949
- this.line.endPoint = undefined;
950
- this.line.render();
951
- }
952
- this.viewer.canvas.releasePointerCapture(event.pointerId);
953
- this.orbit.enabled = true;
954
- };
955
- this.onPointerCancel = (event) => {
956
- this.viewer.canvas.dispatchEvent(new PointerEvent("pointerup", event));
957
- };
958
- this.onPointerLeave = () => {
959
- this.line.endPoint = undefined;
960
- this.line.render();
961
- };
962
- this.renderOverlay = () => {
963
- this.overlay.render();
964
- };
965
- this.updateSnapper = () => {
966
- this.snapper.setFromViewer(this.viewer);
967
- };
968
- this.updateSnapperCamera = () => {
969
- this.snapper.camera = this.viewer.camera;
970
- this.overlay.camera = this.viewer.camera;
971
- };
972
- this.overlay = new MeasureOverlay(viewer.camera, viewer.canvas);
973
- this.overlay.attach();
974
- this.line = new MeasureLine(this.overlay);
975
- this.overlay.addLine(this.line);
976
- this.snapper = new MeasureSnapper(viewer.camera, viewer.canvas);
977
- this.updateSnapper();
978
- this.viewer.canvas.addEventListener("pointerdown", this.onPointerDown);
979
- this.viewer.canvas.addEventListener("pointermove", this.onPointerMove);
980
- this.viewer.canvas.addEventListener("pointerup", this.onPointerUp);
981
- this.viewer.canvas.addEventListener("pointercancel", this.onPointerCancel);
982
- this.viewer.canvas.addEventListener("pointerleave", this.onPointerLeave);
983
- this.viewer.addEventListener("render", this.renderOverlay);
984
- this.viewer.addEventListener("hide", this.updateSnapper);
985
- this.viewer.addEventListener("isolate", this.updateSnapper);
986
- this.viewer.addEventListener("show", this.updateSnapper);
987
- this.viewer.addEventListener("showall", this.updateSnapper);
988
- this.viewer.addEventListener("changecameramode", this.updateSnapperCamera);
989
- }
990
- dispose() {
991
- this.viewer.canvas.removeEventListener("pointerdown", this.onPointerDown);
992
- this.viewer.canvas.removeEventListener("pointermove", this.onPointerMove);
993
- this.viewer.canvas.removeEventListener("pointerup", this.onPointerUp);
994
- this.viewer.canvas.removeEventListener("pointercancel", this.onPointerCancel);
995
- this.viewer.canvas.removeEventListener("pointerleave", this.onPointerLeave);
996
- this.viewer.removeEventListener("render", this.renderOverlay);
997
- this.viewer.removeEventListener("hide", this.updateSnapper);
998
- this.viewer.removeEventListener("isolate", this.updateSnapper);
999
- this.viewer.removeEventListener("show", this.updateSnapper);
1000
- this.viewer.removeEventListener("showall", this.updateSnapper);
1001
- this.viewer.removeEventListener("changecameramode", this.updateSnapperCamera);
1002
- this.snapper.dispose();
1003
- this.overlay.detach();
1004
- this.overlay.dispose();
1005
- super.dispose();
916
+ const ModelUnits = {
917
+ Meters: { name: "Meters", type: "m", scale: 1.0 },
918
+ Centimeters: { name: "Centimeters", type: "cm", scale: 0.01 },
919
+ Millimeters: { name: "Millimeters", type: "mm", scale: 0.001 },
920
+ Feet: { name: "Feet", type: "ft", scale: 0.3048 },
921
+ Inches: { name: "Inches", type: "in", scale: 0.0254 },
922
+ Yards: { name: "Yards", type: "yd", scale: 0.9144 },
923
+ Kilometers: { name: "Kilometers", type: "km", scale: 1000.0 },
924
+ Miles: { name: "Miles", type: "mi", scale: 1609.344 },
925
+ Micrometers: { name: "Micrometers", type: "µm", scale: 0.000001 },
926
+ Mils: { name: "Mils", type: "mil", scale: 0.0000254 },
927
+ MicroInches: { name: "Micro-inches", type: "µin", scale: 0.0000000254 },
928
+ Default: { name: "File units", type: "unit", scale: 1.0 },
929
+ };
930
+ function convertUnits(fromUnits, toUnits, distance) {
931
+ const fromFactor = 1 / (ModelUnits[fromUnits] || ModelUnits.Default).scale;
932
+ const toFactor = (ModelUnits[toUnits] || ModelUnits.Default).scale || 1;
933
+ return distance * fromFactor * toFactor;
934
+ }
935
+
936
+ function getDisplayUnit(units) {
937
+ return (ModelUnits[units] || ModelUnits.Default).type;
938
+ }
939
+ function calculatePrecision(value) {
940
+ const distance = Math.abs(value);
941
+ if (distance >= 1000)
942
+ return 0;
943
+ if (distance >= 10)
944
+ return 1;
945
+ if (distance >= 0.1)
946
+ return 2;
947
+ if (distance >= 0.001)
948
+ return 3;
949
+ return distance > 0 ? Math.floor(-Math.log10(distance)) + 1 : 2;
950
+ }
951
+ function formatNumber(distance, digits, precision) {
952
+ let result = distance.toFixed(digits);
953
+ if (precision === "Auto")
954
+ result = result.replace(/\.0+$/, "").replace(/\.$/, "");
955
+ if (+result !== distance)
956
+ result = "~ " + result;
957
+ return result;
958
+ }
959
+ function formatDistance(distance, units, precision = 2) {
960
+ let digits;
961
+ if (precision === "Auto")
962
+ digits = calculatePrecision(distance);
963
+ else if (Number.isFinite(precision))
964
+ digits = precision;
965
+ else
966
+ digits = parseFloat(precision);
967
+ if (!Number.isFinite(digits))
968
+ digits = 2;
969
+ else if (digits < 0)
970
+ digits = 0;
971
+ else if (digits > 10)
972
+ digits = 10;
973
+ if (ModelUnits[units]) {
974
+ return formatNumber(distance, digits, precision) + " " + ModelUnits[units].type;
975
+ }
976
+ else if (units) {
977
+ return formatNumber(distance, digits, precision) + " " + units;
978
+ }
979
+ else {
980
+ return formatNumber(distance, digits, precision);
1006
981
  }
1007
982
  }
983
+
984
+ const DESKTOP_SNAP_DISTANCE = 10;
985
+ const MOBILE_SNAP_DISTANCE = 50;
1008
986
  const _vertex = new Vector3();
1009
987
  const _start = new Vector3();
1010
988
  const _end = new Vector3();
1011
989
  const _line = new Line3();
1012
990
  const _center = new Vector3();
1013
991
  const _projection = new Vector3();
1014
- class MeasureSnapper {
1015
- constructor(camera, canvas) {
992
+ class Snapper {
993
+ constructor(camera, renderer, canvas) {
1016
994
  this.camera = camera;
995
+ this.renderer = renderer;
1017
996
  this.canvas = canvas;
1018
- this.objects = [];
1019
- this.clippingPlanes = [];
1020
997
  this.raycaster = new Raycaster();
1021
998
  this.detectRadiusInPixels = this.isMobile() ? MOBILE_SNAP_DISTANCE : DESKTOP_SNAP_DISTANCE;
1022
999
  this.edgesCache = new WeakMap();
1023
1000
  }
1024
- dispose() {
1025
- this.objects = [];
1026
- }
1027
1001
  isMobile() {
1028
1002
  if (typeof navigator === "undefined")
1029
1003
  return false;
@@ -1032,7 +1006,7 @@ class MeasureSnapper {
1032
1006
  getMousePosition(event, target) {
1033
1007
  return target.set(event.clientX, event.clientY);
1034
1008
  }
1035
- getPointerIntersects(mouse) {
1009
+ getPointerIntersects(mouse, objects) {
1036
1010
  const rect = this.canvas.getBoundingClientRect();
1037
1011
  const x = ((mouse.x - rect.left) / rect.width) * 2 - 1;
1038
1012
  const y = (-(mouse.y - rect.top) / rect.height) * 2 + 1;
@@ -1046,8 +1020,8 @@ class MeasureSnapper {
1046
1020
  Points: { threshold: 0.01 },
1047
1021
  Sprite: {},
1048
1022
  };
1049
- let intersects = this.raycaster.intersectObjects(this.objects, false);
1050
- this.clippingPlanes.forEach((plane) => {
1023
+ let intersects = this.raycaster.intersectObjects(objects, false);
1024
+ (this.renderer.clippingPlanes || []).forEach((plane) => {
1051
1025
  intersects = intersects.filter((intersect) => plane.distanceToPoint(intersect.point) >= 0);
1052
1026
  });
1053
1027
  return intersects;
@@ -1069,9 +1043,8 @@ class MeasureSnapper {
1069
1043
  }
1070
1044
  return 0.1;
1071
1045
  }
1072
- getSnapPoint(event) {
1073
- const mouse = this.getMousePosition(event, new Vector2());
1074
- const intersections = this.getPointerIntersects(mouse);
1046
+ getSnapPoint(mouse, objects) {
1047
+ const intersections = this.getPointerIntersects(mouse, objects);
1075
1048
  if (intersections.length === 0)
1076
1049
  return undefined;
1077
1050
  const object = intersections[0].object;
@@ -1119,13 +1092,133 @@ class MeasureSnapper {
1119
1092
  return object.localToWorld(snapPoint);
1120
1093
  return intersectionPoint.clone();
1121
1094
  }
1122
- setFromViewer(viewer) {
1095
+ }
1096
+
1097
+ const _downPoint = new Vector2();
1098
+ class MeasureLineDragger extends OrbitDragger {
1099
+ constructor(viewer) {
1100
+ super(viewer);
1101
+ this.scale = 1.0;
1102
+ this.units = "";
1103
+ this.precision = 2;
1104
+ this.onPointerDown = (event) => {
1105
+ if (event.button !== 0)
1106
+ return;
1107
+ const mouse = this.snapper.getMousePosition(event, _downPoint);
1108
+ this.line.startPoint = this.snapper.getSnapPoint(mouse, this.objects);
1109
+ this.line.render();
1110
+ this.viewer.canvas.setPointerCapture(event.pointerId);
1111
+ this.orbit.enabled = !this.line.startPoint;
1112
+ };
1113
+ this.onPointerMove = (event) => {
1114
+ if (this.orbit.enabled && this.orbit.state !== -1)
1115
+ return;
1116
+ const mouse = this.snapper.getMousePosition(event, _downPoint);
1117
+ const snapPoint = this.snapper.getSnapPoint(mouse, this.objects);
1118
+ if (snapPoint && this.line.endPoint && snapPoint.equals(this.line.endPoint))
1119
+ return;
1120
+ this.line.endPoint = snapPoint;
1121
+ this.line.render();
1122
+ if (this.line.startPoint)
1123
+ this.changed = true;
1124
+ };
1125
+ this.onPointerUp = (event) => {
1126
+ if (this.line.startPoint && this.line.endPoint && this.line.getDistance() > 0) {
1127
+ this.line = new MeasureLine(this.overlay, this.scale, this.units, this.precision);
1128
+ this.overlay.addLine(this.line);
1129
+ }
1130
+ else {
1131
+ this.line.startPoint = undefined;
1132
+ this.line.endPoint = undefined;
1133
+ this.line.render();
1134
+ }
1135
+ this.viewer.canvas.releasePointerCapture(event.pointerId);
1136
+ this.orbit.enabled = true;
1137
+ };
1138
+ this.onPointerCancel = (event) => {
1139
+ this.viewer.canvas.dispatchEvent(new PointerEvent("pointerup", event));
1140
+ };
1141
+ this.onPointerLeave = () => {
1142
+ this.line.endPoint = undefined;
1143
+ this.line.render();
1144
+ };
1145
+ this.clearOverlay = () => {
1146
+ this.overlay.clear();
1147
+ this.line = new MeasureLine(this.overlay, this.scale, this.units, this.precision);
1148
+ this.overlay.addLine(this.line);
1149
+ };
1150
+ this.renderOverlay = () => {
1151
+ this.overlay.render();
1152
+ };
1153
+ this.updateObjects = () => {
1154
+ this.objects.length = 0;
1155
+ this.viewer.models.forEach((model) => {
1156
+ model.getVisibleObjects().forEach((object) => this.objects.push(object));
1157
+ });
1158
+ };
1159
+ this.updateSnapperCamera = () => {
1160
+ this.snapper.camera = this.viewer.camera;
1161
+ this.overlay.camera = this.viewer.camera;
1162
+ };
1163
+ this.updateUnits = () => {
1164
+ var _a, _b;
1165
+ const model = this.viewer.models[0];
1166
+ const units = (_a = this.viewer.options.rulerUnit) !== null && _a !== void 0 ? _a : "Default";
1167
+ const precision = (_b = this.viewer.options.rulerPrecision) !== null && _b !== void 0 ? _b : "Default";
1168
+ if (units === "Default") {
1169
+ this.scale = model.getUnitScale();
1170
+ this.units = model.getUnitString();
1171
+ }
1172
+ else {
1173
+ this.scale = convertUnits(model.getUnits(), units, 1);
1174
+ this.units = units;
1175
+ }
1176
+ if (precision === "Default") {
1177
+ this.precision = model.getPrecision();
1178
+ }
1179
+ else {
1180
+ this.precision = precision;
1181
+ }
1182
+ this.overlay.updateLineUnits(this.scale, this.units, this.precision);
1183
+ };
1184
+ this.overlay = new MeasureOverlay(viewer.camera, viewer.canvas);
1185
+ this.overlay.attach();
1186
+ this.line = new MeasureLine(this.overlay, this.scale, this.units, this.precision);
1187
+ this.overlay.addLine(this.line);
1188
+ this.snapper = new Snapper(viewer.camera, viewer.renderer, viewer.canvas);
1189
+ this.objects = [];
1190
+ this.updateObjects();
1191
+ this.updateUnits();
1192
+ this.viewer.canvas.addEventListener("pointerdown", this.onPointerDown);
1193
+ this.viewer.canvas.addEventListener("pointermove", this.onPointerMove);
1194
+ this.viewer.canvas.addEventListener("pointerup", this.onPointerUp);
1195
+ this.viewer.canvas.addEventListener("pointercancel", this.onPointerCancel);
1196
+ this.viewer.canvas.addEventListener("pointerleave", this.onPointerLeave);
1197
+ this.viewer.addEventListener("render", this.renderOverlay);
1198
+ this.viewer.addEventListener("hide", this.updateObjects);
1199
+ this.viewer.addEventListener("isolate", this.updateObjects);
1200
+ this.viewer.addEventListener("show", this.updateObjects);
1201
+ this.viewer.addEventListener("showall", this.updateObjects);
1202
+ this.viewer.addEventListener("changecameramode", this.updateSnapperCamera);
1203
+ this.viewer.addEventListener("optionschange", this.updateUnits);
1204
+ }
1205
+ dispose() {
1206
+ this.viewer.canvas.removeEventListener("pointerdown", this.onPointerDown);
1207
+ this.viewer.canvas.removeEventListener("pointermove", this.onPointerMove);
1208
+ this.viewer.canvas.removeEventListener("pointerup", this.onPointerUp);
1209
+ this.viewer.canvas.removeEventListener("pointercancel", this.onPointerCancel);
1210
+ this.viewer.canvas.removeEventListener("pointerleave", this.onPointerLeave);
1211
+ this.viewer.removeEventListener("render", this.renderOverlay);
1212
+ this.viewer.removeEventListener("hide", this.updateObjects);
1213
+ this.viewer.removeEventListener("isolate", this.updateObjects);
1214
+ this.viewer.removeEventListener("show", this.updateObjects);
1215
+ this.viewer.removeEventListener("showall", this.updateObjects);
1216
+ this.viewer.removeEventListener("changecameramode", this.updateSnapperCamera);
1217
+ this.viewer.removeEventListener("optionschange", this.updateUnits);
1123
1218
  this.objects.length = 0;
1124
- viewer.models.forEach((model) => {
1125
- model.getVisibleObjects().forEach((object) => this.objects.push(object));
1126
- });
1127
- this.camera = viewer.camera;
1128
- this.clippingPlanes = viewer.renderer.clippingPlanes || [];
1219
+ this.overlay.detach();
1220
+ this.overlay.dispose();
1221
+ super.dispose();
1129
1222
  }
1130
1223
  }
1131
1224
  class MeasureOverlay {
@@ -1145,6 +1238,9 @@ class MeasureOverlay {
1145
1238
  this.projector = new MeasureProjector(camera, canvas);
1146
1239
  this.resizeObserver = new ResizeObserver(this.resizeContainer);
1147
1240
  }
1241
+ dispose() {
1242
+ this.clear();
1243
+ }
1148
1244
  attach() {
1149
1245
  this.container = document.createElement("div");
1150
1246
  this.container.id = "measure-container";
@@ -1157,9 +1253,6 @@ class MeasureOverlay {
1157
1253
  this.canvas.parentElement.appendChild(this.container);
1158
1254
  this.resizeObserver.observe(this.canvas);
1159
1255
  }
1160
- dispose() {
1161
- this.clear();
1162
- }
1163
1256
  detach() {
1164
1257
  this.resizeObserver.disconnect();
1165
1258
  this.container.remove();
@@ -1167,7 +1260,7 @@ class MeasureOverlay {
1167
1260
  }
1168
1261
  clear() {
1169
1262
  this.lines.forEach((line) => line.dispose());
1170
- this.lines = [];
1263
+ this.lines.length = 0;
1171
1264
  }
1172
1265
  render() {
1173
1266
  this.projector.setFromCamera(this.camera);
@@ -1182,13 +1275,18 @@ class MeasureOverlay {
1182
1275
  removeLine(line) {
1183
1276
  this.lines = this.lines.filter((x) => x !== line);
1184
1277
  }
1278
+ updateLineUnits(scale, units, precision) {
1279
+ this.lines.forEach((line) => {
1280
+ line.scale = scale;
1281
+ line.units = units;
1282
+ line.precision = precision;
1283
+ });
1284
+ }
1185
1285
  }
1186
1286
  const _middlePoint = new Vector3();
1187
1287
  class MeasureLine {
1188
- constructor(overlay) {
1288
+ constructor(overlay, scale, units, precision) {
1189
1289
  this.id = MathUtils.generateUUID();
1190
- this.unit = "";
1191
- this.scale = 1.0;
1192
1290
  this.size = 10.0;
1193
1291
  this.lineWidth = 2;
1194
1292
  this.style = {
@@ -1199,6 +1297,9 @@ class MeasureLine {
1199
1297
  font: "1rem system-ui",
1200
1298
  };
1201
1299
  this.overlay = overlay;
1300
+ this.scale = scale;
1301
+ this.units = units;
1302
+ this.precision = precision;
1202
1303
  this.elementStartPoint = overlay.container.appendChild(document.createElement("div"));
1203
1304
  this.elementEndPoint = overlay.container.appendChild(document.createElement("div"));
1204
1305
  this.elementLine = overlay.container.appendChild(document.createElement("div"));
@@ -1253,10 +1354,10 @@ class MeasureLine {
1253
1354
  _middlePoint.lerpVectors(this.startPoint, this.endPoint, 0.5);
1254
1355
  const { point, visible } = projector.projectPoint(_middlePoint);
1255
1356
  const distance = this.getDistance();
1256
- this.elementLabel.style.display = visible && distance >= PRECISION ? "block" : "none";
1357
+ this.elementLabel.style.display = visible && distance > 0 ? "block" : "none";
1257
1358
  this.elementLabel.style.left = `${point.x}px`;
1258
1359
  this.elementLabel.style.top = `${point.y}px`;
1259
- this.elementLabel.innerHTML = `${distance.toFixed(2)} ${this.unit}`;
1360
+ this.elementLabel.innerHTML = formatDistance(distance, this.units, this.precision);
1260
1361
  }
1261
1362
  else {
1262
1363
  this.elementLabel.style.display = "none";
@@ -2060,7 +2161,8 @@ function clearSelected(viewer) {
2060
2161
  const selection = viewer.getComponent("SelectionComponent");
2061
2162
  selection.clearSelection();
2062
2163
  viewer.update();
2063
- viewer.emitEvent({ type: "select", data: undefined, handles: [] });
2164
+ viewer.emitEvent({ type: "select", handles: [] });
2165
+ viewer.emitEvent({ type: "select2", handles: [] });
2064
2166
  }
2065
2167
 
2066
2168
  function clearSlices(viewer) {
@@ -2149,22 +2251,31 @@ function getDefaultViewPositions() {
2149
2251
  }
2150
2252
 
2151
2253
  function getModels(viewer) {
2152
- return viewer.models.map((model) => model.handle);
2254
+ return viewer.models.map((model) => model.id);
2153
2255
  }
2154
2256
 
2155
2257
  function getSelected(viewer) {
2156
- const handles = [];
2157
- viewer.models.forEach((model) => handles.push(...model.getHandlesByObjects(viewer.selected)));
2258
+ const handles2 = viewer.executeCommand("getSelected2");
2259
+ const handles = handles2.map((handle) => handle.slice(handle.indexOf(":") + 1));
2158
2260
  return handles;
2159
2261
  }
2160
2262
 
2263
+ function getSelected2(viewer) {
2264
+ const handles2 = [];
2265
+ viewer.models.forEach((model) => {
2266
+ handles2.push(...model.getHandlesByObjects(viewer.selected));
2267
+ });
2268
+ return handles2;
2269
+ }
2270
+
2161
2271
  function hideSelected(viewer) {
2162
2272
  viewer.models.forEach((model) => model.hideObjects(viewer.selected));
2163
2273
  const selection = viewer.getComponent("SelectionComponent");
2164
2274
  selection.clearSelection();
2165
2275
  viewer.update();
2166
2276
  viewer.emitEvent({ type: "hide" });
2167
- viewer.emitEvent({ type: "select", data: undefined, handles: [] });
2277
+ viewer.emitEvent({ type: "select", handles: [] });
2278
+ viewer.emitEvent({ type: "select2", handles: [] });
2168
2279
  }
2169
2280
 
2170
2281
  function isolateSelected(viewer) {
@@ -2191,14 +2302,13 @@ function resetView(viewer) {
2191
2302
  viewer.emit({ type: "resetview" });
2192
2303
  }
2193
2304
 
2194
- function selectModel(viewer, handle) {
2305
+ function selectModel(viewer, id) {
2195
2306
  const selection = viewer.getComponent("SelectionComponent");
2196
2307
  selection.clearSelection();
2197
- viewer.models
2198
- .filter((model) => model.handle === handle)
2199
- .forEach((model) => selection.select(model.getObjects(), model));
2308
+ viewer.models.filter((model) => model.id === id).forEach((model) => selection.select(model.getObjects(), model));
2200
2309
  viewer.update();
2201
- viewer.emit({ type: "select", data: [] });
2310
+ viewer.emitEvent({ type: "select", handles: viewer.getSelected() });
2311
+ viewer.emitEvent({ type: "select2", handles: viewer.getSelected2() });
2202
2312
  }
2203
2313
 
2204
2314
  function setActiveDragger(viewer, dragger = "") {
@@ -2210,16 +2320,31 @@ function setMarkupColor(viewer, r = 255, g = 0, b = 0) {
2210
2320
  }
2211
2321
 
2212
2322
  function setSelected(viewer, handles = []) {
2213
- const selection = viewer.getComponent("SelectionComponent");
2214
- selection.clearSelection();
2323
+ const handles2 = [];
2324
+ handles.forEach((handle) => {
2325
+ if (handle.includes(":")) {
2326
+ handles2.push(handle);
2327
+ }
2328
+ else
2329
+ viewer.models.forEach((model) => {
2330
+ handles2.push(`${model.id}:${handle}`);
2331
+ });
2332
+ });
2333
+ viewer.executeCommand("setSelected2", handles2);
2334
+ }
2335
+
2336
+ function setSelected2(viewer, handles = []) {
2337
+ const selectionComponent = viewer.getComponent("SelectionComponent");
2338
+ selectionComponent.clearSelection();
2215
2339
  viewer.models.forEach((model) => {
2216
2340
  const objects = model.getObjectsByHandles(handles);
2217
2341
  model.showObjects(objects);
2218
- selection.select(objects, model);
2342
+ selectionComponent.select(objects, model);
2219
2343
  });
2220
2344
  viewer.update();
2221
2345
  viewer.emitEvent({ type: "show" });
2222
- viewer.emitEvent({ type: "select", data: undefined, handles });
2346
+ viewer.emitEvent({ type: "select", data: undefined, handles: viewer.getSelected() });
2347
+ viewer.emitEvent({ type: "select2", data: undefined, handles });
2223
2348
  }
2224
2349
 
2225
2350
  function showAll(viewer) {
@@ -2233,21 +2358,19 @@ function zoomToExtents(viewer) {
2233
2358
  }
2234
2359
 
2235
2360
  function zoomToObjects(viewer, handles = []) {
2236
- const handleSet = new Set(handles);
2237
- const objects = [];
2238
- viewer.scene.traverseVisible((child) => {
2239
- var _a;
2240
- if (handleSet.has((_a = child.userData) === null || _a === void 0 ? void 0 : _a.handle))
2241
- objects.push(child);
2361
+ const extents = new Box3();
2362
+ viewer.models.forEach((model) => {
2363
+ const objects = model.getObjectsByHandles(handles);
2364
+ objects.forEach((object) => extents.expandByObject(object));
2242
2365
  });
2243
- const extents = objects.reduce((result, object) => result.expandByObject(object), new Box3());
2244
2366
  if (extents.isEmpty())
2245
2367
  extents.copy(viewer.extents);
2246
2368
  zoomTo(viewer, extents);
2247
2369
  }
2248
2370
 
2249
2371
  function zoomToSelected(viewer) {
2250
- const extents = viewer.selected.reduce((result, object) => result.expandByObject(object), new Box3());
2372
+ const extents = new Box3();
2373
+ viewer.selected.forEach((object) => extents.expandByObject(object));
2251
2374
  if (extents.isEmpty())
2252
2375
  extents.copy(viewer.extents);
2253
2376
  zoomTo(viewer, extents);
@@ -2264,6 +2387,7 @@ commands.registerCommand("explode", explode);
2264
2387
  commands.registerCommand("getDefaultViewPositions", getDefaultViewPositions);
2265
2388
  commands.registerCommand("getModels", getModels);
2266
2389
  commands.registerCommand("getSelected", getSelected);
2390
+ commands.registerCommand("getSelected2", getSelected2);
2267
2391
  commands.registerCommand("hideSelected", hideSelected);
2268
2392
  commands.registerCommand("isolateSelected", isolateSelected);
2269
2393
  commands.registerCommand("regenerateAll", regenerateAll);
@@ -2273,6 +2397,7 @@ commands.registerCommand("setActiveDragger", setActiveDragger);
2273
2397
  commands.registerCommand("setDefaultViewPosition", setDefaultViewPosition);
2274
2398
  commands.registerCommand("setMarkupColor", setMarkupColor);
2275
2399
  commands.registerCommand("setSelected", setSelected);
2400
+ commands.registerCommand("setSelected2", setSelected2);
2276
2401
  commands.registerCommand("showAll", showAll);
2277
2402
  commands.registerCommand("zoomToExtents", zoomToExtents);
2278
2403
  commands.registerCommand("zoomToObjects", zoomToObjects);
@@ -2325,6 +2450,10 @@ class CameraComponent {
2325
2450
  this.switchCameraMode(this.viewer.options.cameraMode);
2326
2451
  };
2327
2452
  this.geometryEnd = () => {
2453
+ if (this.viewer.models.length > 1) {
2454
+ this.switchCamera(this.viewer.camera);
2455
+ return;
2456
+ }
2328
2457
  let camera;
2329
2458
  this.viewer.scene.traverse((object) => {
2330
2459
  if (object.isCamera)
@@ -2422,6 +2551,8 @@ class ExtentsComponent {
2422
2551
  const extents = new Box3();
2423
2552
  this.viewer.models.forEach((model) => model.getExtents(extents));
2424
2553
  this.viewer.extents.copy(extents);
2554
+ if (this.viewer.models.length > 1)
2555
+ return;
2425
2556
  this.viewer.extents.getCenter(this.viewer.target);
2426
2557
  };
2427
2558
  this.viewer = viewer;
@@ -2749,10 +2880,11 @@ class SelectionComponent {
2749
2880
  const upPosition = this.getMousePosition(event, new Vector2());
2750
2881
  if (upPosition.distanceTo(this.downPosition) !== 0)
2751
2882
  return;
2883
+ const snapper = new Snapper(this.viewer.camera, this.viewer.renderer, this.viewer.canvas);
2752
2884
  let intersections = [];
2753
2885
  this.viewer.models.forEach((model) => {
2754
2886
  const objects = model.getVisibleObjects();
2755
- const intersects = this.getPointerIntersects(upPosition, objects);
2887
+ const intersects = snapper.getPointerIntersects(upPosition, objects);
2756
2888
  if (intersects.length > 0)
2757
2889
  intersections.push({ ...intersects[0], model });
2758
2890
  });
@@ -2770,6 +2902,7 @@ class SelectionComponent {
2770
2902
  }
2771
2903
  this.viewer.update();
2772
2904
  this.viewer.emitEvent({ type: "select", data: undefined, handles: this.viewer.getSelected() });
2905
+ this.viewer.emitEvent({ type: "select2", data: undefined, handles: this.viewer.getSelected2() });
2773
2906
  };
2774
2907
  this.onDoubleClick = (event) => {
2775
2908
  if (event.button !== 0)
@@ -2780,7 +2913,6 @@ class SelectionComponent {
2780
2913
  this.highlighter = this.viewer.getComponent("HighlighterComponent");
2781
2914
  };
2782
2915
  this.viewer = viewer;
2783
- this.raycaster = new Raycaster();
2784
2916
  this.downPosition = new Vector2();
2785
2917
  this.viewer.addEventListener("pointerdown", this.onPointerDown);
2786
2918
  this.viewer.addEventListener("pointerup", this.onPointerUp);
@@ -2796,26 +2928,6 @@ class SelectionComponent {
2796
2928
  getMousePosition(event, target) {
2797
2929
  return target.set(event.clientX, event.clientY);
2798
2930
  }
2799
- getPointerIntersects(mouse, objects) {
2800
- const rect = this.viewer.canvas.getBoundingClientRect();
2801
- const x = ((mouse.x - rect.left) / rect.width) * 2 - 1;
2802
- const y = (-(mouse.y - rect.top) / rect.height) * 2 + 1;
2803
- const coords = new Vector2(x, y);
2804
- this.raycaster.setFromCamera(coords, this.viewer.camera);
2805
- this.raycaster.params = {
2806
- Mesh: {},
2807
- Line: { threshold: 0.05 },
2808
- Line2: { threshold: 0.05 },
2809
- LOD: {},
2810
- Points: { threshold: 0.01 },
2811
- Sprite: {},
2812
- };
2813
- let intersects = this.raycaster.intersectObjects(objects, false);
2814
- (this.viewer.renderer.clippingPlanes || []).forEach((plane) => {
2815
- intersects = intersects.filter((intersect) => plane.distanceToPoint(intersect.point) >= 0);
2816
- });
2817
- return intersects;
2818
- }
2819
2931
  select(objects, model) {
2820
2932
  if (!model) {
2821
2933
  this.viewer.models.forEach((model) => this.select(objects, model));
@@ -3014,48 +3126,8 @@ components.registerComponent("SelectionComponent", (viewer) => new SelectionComp
3014
3126
  components.registerComponent("WCSHelperComponent", (viewer) => new WCSHelperComponent(viewer));
3015
3127
  components.registerComponent("ResetComponent", (viewer) => new ResetComponent(viewer));
3016
3128
 
3017
- class GLTFLoadingManager extends LoadingManager {
3018
- constructor(file, params = {}) {
3019
- super();
3020
- this.path = "";
3021
- this.resourcePath = "";
3022
- this.fileURL = "";
3023
- this.dataURLs = new Map();
3024
- this.path = params.path || "";
3025
- const externalFiles = params.externalFiles || new Map();
3026
- if (typeof file === "string") {
3027
- this.fileURL = file;
3028
- this.resourcePath = LoaderUtils.extractUrlBase(file);
3029
- }
3030
- else {
3031
- externalFiles.forEach((value, key) => (this.fileURL = value === file ? key : this.fileURL));
3032
- externalFiles.set(this.fileURL, file);
3033
- }
3034
- externalFiles.forEach((value, key) => {
3035
- let dataURL;
3036
- if (typeof value === "string")
3037
- dataURL = value;
3038
- else
3039
- dataURL = URL.createObjectURL(new Blob([value]));
3040
- this.dataURLs.set(key, dataURL);
3041
- });
3042
- this.setURLModifier((url) => {
3043
- const key = decodeURI(url)
3044
- .replace(this.path, "")
3045
- .replace(this.resourcePath, "")
3046
- .replace(/^(\.?\/)/, "");
3047
- const dataURL = this.dataURLs.get(key);
3048
- return dataURL !== null && dataURL !== void 0 ? dataURL : url;
3049
- });
3050
- }
3051
- dispose() {
3052
- this.dataURLs.forEach(URL.revokeObjectURL);
3053
- }
3054
- }
3055
-
3056
3129
  class ModelImpl {
3057
3130
  constructor(scene) {
3058
- this.handle = "1";
3059
3131
  this.scene = scene;
3060
3132
  }
3061
3133
  dispose() {
@@ -3075,6 +3147,18 @@ class ModelImpl {
3075
3147
  this.scene.traverse(disposeObject);
3076
3148
  this.scene.clear();
3077
3149
  }
3150
+ getUnits() {
3151
+ return "Meters";
3152
+ }
3153
+ getUnitScale() {
3154
+ return convertUnits(this.getUnits(), "Meters", 1);
3155
+ }
3156
+ getUnitString() {
3157
+ return getDisplayUnit(this.getUnits());
3158
+ }
3159
+ getPrecision() {
3160
+ return 2;
3161
+ }
3078
3162
  getExtents(target) {
3079
3163
  this.scene.traverseVisible((object) => !object.children.length && target.expandByObject(object));
3080
3164
  return target;
@@ -3097,27 +3181,45 @@ class ModelImpl {
3097
3181
  }
3098
3182
  return false;
3099
3183
  }
3184
+ hasHandle(handle) {
3185
+ return !handle.includes(":") || handle.split(":", 1)[0] === this.id + "";
3186
+ }
3100
3187
  getOwnObjects(objects) {
3101
3188
  if (!Array.isArray(objects))
3102
3189
  objects = [objects];
3103
3190
  return objects.filter((object) => this.hasObject(object));
3104
3191
  }
3192
+ getOwnHandles(handles) {
3193
+ if (!Array.isArray(handles))
3194
+ handles = [handles];
3195
+ return handles.filter((handle) => this.hasHandle(handle));
3196
+ }
3105
3197
  getObjectsByHandles(handles) {
3106
- const handleSet = new Set(handles);
3198
+ const ownHandles = this.getOwnHandles(handles);
3199
+ if (ownHandles.length === 0)
3200
+ return [];
3201
+ const handleSet = new Set(ownHandles.map((handle) => handle.slice(handle.indexOf(":") + 1)));
3107
3202
  const objects = [];
3108
- this.scene.traverse((object) => handleSet.has(object.userData.handle) && objects.push(object));
3203
+ this.scene.traverse((object) => {
3204
+ const handle = object.userData.handle;
3205
+ if (handle && handleSet.has(handle))
3206
+ objects.push(object);
3207
+ });
3109
3208
  return objects;
3110
3209
  }
3111
3210
  getHandlesByObjects(objects) {
3112
- if (!Array.isArray(objects))
3113
- objects = [objects];
3114
- const handlesSet = new Set();
3115
- this.getOwnObjects(objects).forEach((object) => handlesSet.add(object.userData.handle));
3116
- return Array.from(handlesSet);
3211
+ const ownObjects = this.getOwnObjects(objects);
3212
+ if (ownObjects.length === 0)
3213
+ return [];
3214
+ const handleSet = new Set();
3215
+ ownObjects.forEach((object) => {
3216
+ const handle = object.userData.handle;
3217
+ if (handle)
3218
+ handleSet.add(`${this.id}:${handle}`);
3219
+ });
3220
+ return Array.from(handleSet);
3117
3221
  }
3118
3222
  hideObjects(objects) {
3119
- if (!Array.isArray(objects))
3120
- objects = [objects];
3121
3223
  this.getOwnObjects(objects).forEach((object) => (object.visible = false));
3122
3224
  return this;
3123
3225
  }
@@ -3133,8 +3235,6 @@ class ModelImpl {
3133
3235
  return this;
3134
3236
  }
3135
3237
  showObjects(objects) {
3136
- if (!Array.isArray(objects))
3137
- objects = [objects];
3138
3238
  this.getOwnObjects(objects).forEach((object) => {
3139
3239
  object.visible = true;
3140
3240
  object.traverseAncestors((parent) => (parent.visible = true));
@@ -3190,42 +3290,6 @@ class ModelImpl {
3190
3290
  }
3191
3291
  }
3192
3292
 
3193
- class GLTFFileLoader extends Loader {
3194
- constructor(viewer) {
3195
- super();
3196
- this.viewer = viewer;
3197
- }
3198
- isSupport(file, format) {
3199
- return ((typeof file === "string" || file instanceof globalThis.File || file instanceof ArrayBuffer) &&
3200
- /(gltf|glb)$/i.test(format));
3201
- }
3202
- async load(file, format, params) {
3203
- const manager = new GLTFLoadingManager(file, params);
3204
- const loader = new GLTFLoader(manager);
3205
- loader.setPath(manager.path);
3206
- loader.setCrossOrigin(params.crossOrigin || loader.crossOrigin);
3207
- loader.setWithCredentials(params.withCredentials || loader.withCredentials);
3208
- const progress = (event) => {
3209
- const { lengthComputable, loaded, total } = event;
3210
- const progress = lengthComputable ? loaded / total : 1;
3211
- this.viewer.emitEvent({ type: "geometryprogress", data: progress, file });
3212
- };
3213
- const gltf = await loader.loadAsync(manager.fileURL, progress);
3214
- if (!this.viewer.scene)
3215
- return this;
3216
- const modelImpl = new ModelImpl(gltf.scene);
3217
- modelImpl.loader = this;
3218
- modelImpl.viewer = this.viewer;
3219
- this.viewer.scene.add(gltf.scene);
3220
- this.viewer.models.push(modelImpl);
3221
- this.viewer.syncOptions();
3222
- this.viewer.syncOverlay();
3223
- this.viewer.update();
3224
- this.viewer.emitEvent({ type: "databasechunk", data: gltf.scene, file });
3225
- return this;
3226
- }
3227
- }
3228
-
3229
3293
  class DynamicModelImpl extends ModelImpl {
3230
3294
  getExtents(target) {
3231
3295
  return target.union(this.gltfLoader.getTotalGeometryExtent());
@@ -3244,31 +3308,40 @@ class DynamicModelImpl extends ModelImpl {
3244
3308
  return this.gltfLoader.originalObjects.has(object);
3245
3309
  }
3246
3310
  getObjectsByHandles(handles) {
3247
- const handlesSet = new Set(handles);
3311
+ const ownHandles = this.getOwnHandles(handles);
3312
+ if (ownHandles.length === 0)
3313
+ return [];
3314
+ const handlesSet = new Set(ownHandles);
3248
3315
  const objects = [];
3249
3316
  handlesSet.forEach((handle) => {
3250
- const handle2 = `${this.modelId}_${handle}`;
3251
- const handles = this.gltfLoader.handleToObjects.get(handle2) || [];
3252
- objects.push(...Array.from(handles));
3317
+ objects.push(...this.gltfLoader.getObjectsByHandle(handle));
3253
3318
  });
3254
3319
  return objects;
3255
3320
  }
3256
3321
  getHandlesByObjects(objects) {
3257
- const handles = super.getHandlesByObjects(objects);
3258
- return handles.map((x) => x.split("_").pop());
3322
+ const ownObjects = this.getOwnObjects(objects);
3323
+ if (ownObjects.length === 0)
3324
+ return [];
3325
+ const handleSet = new Set();
3326
+ ownObjects.forEach((object) => {
3327
+ const handle = object.userData.handle;
3328
+ if (handle)
3329
+ handleSet.add(handle);
3330
+ });
3331
+ return Array.from(handleSet);
3259
3332
  }
3260
3333
  hideObjects(objects) {
3261
- const handles = super.getHandlesByObjects(objects);
3334
+ const handles = this.getHandlesByObjects(objects);
3262
3335
  this.gltfLoader.hideObjects(handles);
3263
3336
  return this;
3264
3337
  }
3265
3338
  isolateObjects(objects) {
3266
- const handles = super.getHandlesByObjects(objects);
3339
+ const handles = this.getHandlesByObjects(objects);
3267
3340
  this.gltfLoader.isolateObjects(new Set(handles));
3268
3341
  return this;
3269
3342
  }
3270
3343
  showObjects(objects) {
3271
- const handles = super.getHandlesByObjects(objects);
3344
+ const handles = this.getHandlesByObjects(objects);
3272
3345
  this.gltfLoader.showObjects(handles);
3273
3346
  return this;
3274
3347
  }
@@ -3332,11 +3405,14 @@ class GltfStructure {
3332
3405
  this.materials = new Map();
3333
3406
  this.textureCache = new Map();
3334
3407
  this.materialCache = new Map();
3408
+ this.uri = "";
3409
+ this._nextObjectId = 0;
3335
3410
  }
3336
3411
  async initialize(loader) {
3337
3412
  this.json = await this.loadController.loadJson();
3338
3413
  this.baseUrl = await this.loadController.baseUrl();
3339
3414
  this.loader = loader;
3415
+ this.uri = this.json.buffers[0].uri || "";
3340
3416
  }
3341
3417
  clear() {
3342
3418
  this.json = null;
@@ -3430,7 +3506,7 @@ class GltfStructure {
3430
3506
  await this.loader.waitForChunkSlot();
3431
3507
  try {
3432
3508
  const length = range.end - range.start;
3433
- const buffer = await this.loadController.loadBinaryData([{ offset: range.start, length }]);
3509
+ const buffer = await this.loadController.loadBinaryData([{ offset: range.start, length }], this.uri);
3434
3510
  for (const req of range.requests) {
3435
3511
  const relOffset = req.offset - range.start;
3436
3512
  try {
@@ -3747,6 +3823,7 @@ class GltfStructure {
3747
3823
  }
3748
3824
  }
3749
3825
 
3826
+ const STRUCTURE_ID_SEPARATOR = ":";
3750
3827
  class DynamicGltfLoader {
3751
3828
  constructor(camera, scene, renderer) {
3752
3829
  this.camera = camera;
@@ -3759,6 +3836,7 @@ class DynamicGltfLoader {
3759
3836
  geometryerror: [],
3760
3837
  update: [],
3761
3838
  geometrymemory: [],
3839
+ optimizationprogress: [],
3762
3840
  };
3763
3841
  this.loadDistance = 100;
3764
3842
  this.unloadDistance = 150;
@@ -3800,7 +3878,6 @@ class DynamicGltfLoader {
3800
3878
  this.hiddenHandles = new Set();
3801
3879
  this.newOptimizedObjects = new Set();
3802
3880
  this.oldOptimizeObjects = new Set();
3803
- this.maxConcurrentChunks = 8;
3804
3881
  this.activeChunkLoads = 0;
3805
3882
  this.chunkQueue = [];
3806
3883
  this.objectIdToIndex = new Map();
@@ -3809,6 +3886,7 @@ class DynamicGltfLoader {
3809
3886
  this.maxConcurrentChunks = 6;
3810
3887
  this.mergedObjectMap = new Map();
3811
3888
  this.mergedGeometryVisibility = new Map();
3889
+ this._webglInfoCache = null;
3812
3890
  }
3813
3891
  setVisibleEdges(visible) {
3814
3892
  this.visibleEdges = visible;
@@ -3912,6 +3990,123 @@ class DynamicGltfLoader {
3912
3990
  this.updateMemoryIndicator();
3913
3991
  console.log(`Final memory usage: ${Math.round(currentMemoryUsage / (1024 * 1024))}MB`);
3914
3992
  }
3993
+ getStats() {
3994
+ let totalObjects = 0;
3995
+ let renderedObjects = 0;
3996
+ let totalTriangles = 0;
3997
+ let renderedTriangles = 0;
3998
+ let totalLines = 0;
3999
+ let renderedLines = 0;
4000
+ let totalEdges = 0;
4001
+ let renderedEdges = 0;
4002
+ this.scene.traverse((object) => {
4003
+ totalObjects++;
4004
+ const geometry = object.geometry;
4005
+ if (!geometry) return;
4006
+ let triCount = 0;
4007
+ if (geometry.index) {
4008
+ triCount = Math.floor(geometry.index.count / 3);
4009
+ } else if (geometry.attributes && geometry.attributes.position) {
4010
+ triCount = Math.floor(geometry.attributes.position.count / 3);
4011
+ }
4012
+ totalTriangles += triCount;
4013
+ let lineCount = 0;
4014
+ if (geometry.index) {
4015
+ lineCount = Math.floor(geometry.index.count / 2);
4016
+ } else if (geometry.attributes && geometry.attributes.position) {
4017
+ lineCount = Math.floor(geometry.attributes.position.count / 2);
4018
+ }
4019
+ if (object.type === "Line" || object.type === "LineSegments" || object.type === "LineLoop") {
4020
+ if (object.userData.isEdge) {
4021
+ totalEdges += lineCount;
4022
+ } else {
4023
+ totalLines += lineCount;
4024
+ }
4025
+ }
4026
+ if (object.visible !== false) {
4027
+ if (object.isMesh || object.isLine || object.isPoints) {
4028
+ renderedObjects++;
4029
+ if (object.isMesh) {
4030
+ renderedTriangles += triCount;
4031
+ } else if (object.type === "Line" || object.type === "LineSegments" || object.type === "LineLoop") {
4032
+ if (object.userData.isEdge) {
4033
+ renderedEdges += lineCount;
4034
+ } else {
4035
+ renderedLines += lineCount;
4036
+ }
4037
+ }
4038
+ }
4039
+ }
4040
+ });
4041
+ const geometryCount = this.geometryCache ? this.geometryCache.size : 0;
4042
+ const geometryMemoryBytes = Array.from(this.geometryCache?.values?.() || []).reduce((a, b) => a + b, 0);
4043
+ const uniqueMaterialIds = new Set();
4044
+ const uniqueTextureIds = new Set();
4045
+ if (Array.isArray(this.structures)) {
4046
+ for (const structure of this.structures) {
4047
+ console.log(structure.materialCache.values());
4048
+ try {
4049
+ for (const entry of structure.materialCache.values()) {
4050
+ if (entry?.mesh?.uuid) uniqueMaterialIds.add(entry.mesh.uuid);
4051
+ if (entry?.points?.uuid) uniqueMaterialIds.add(entry.points.uuid);
4052
+ if (entry?.lines?.uuid) uniqueMaterialIds.add(entry.lines.uuid);
4053
+ }
4054
+ } catch (exp) {
4055
+ console.error("Error adding material to uniqueMaterialIds", exp);
4056
+ }
4057
+ }
4058
+ }
4059
+ const materialCount = uniqueMaterialIds.size;
4060
+ const textureCount = uniqueTextureIds.size;
4061
+ const estimatedGpuMemoryBytes = geometryMemoryBytes;
4062
+ if (!this._webglInfoCache) {
4063
+ try {
4064
+ const gl = this.renderer.getContext();
4065
+ const dbgInfo = gl.getExtension("WEBGL_debug_renderer_info");
4066
+ if (dbgInfo) {
4067
+ const rendererStr = gl.getParameter(dbgInfo.UNMASKED_RENDERER_WEBGL);
4068
+ const vendorStr = gl.getParameter(dbgInfo.UNMASKED_VENDOR_WEBGL);
4069
+ this._webglInfoCache = { renderer: rendererStr, vendor: vendorStr };
4070
+ } else {
4071
+ this._webglInfoCache = { renderer: null, vendor: null };
4072
+ }
4073
+ } catch (e) {
4074
+ console.error("Error getting webgl info", e);
4075
+ this._webglInfoCache = { renderer: null, vendor: null };
4076
+ }
4077
+ }
4078
+ const size = new Vector2();
4079
+ if (this.renderer && this.renderer.getSize) {
4080
+ this.renderer.getSize(size);
4081
+ }
4082
+ return {
4083
+ scene: {
4084
+ beforeOptimization: {
4085
+ objects: totalObjects - renderedObjects,
4086
+ triangles: totalTriangles - renderedTriangles,
4087
+ lines: totalLines - renderedLines,
4088
+ edges: totalEdges - renderedEdges,
4089
+ },
4090
+ afterOptimization: {
4091
+ objects: renderedObjects,
4092
+ triangles: renderedTriangles,
4093
+ lines: renderedLines,
4094
+ edges: renderedEdges,
4095
+ },
4096
+ },
4097
+ memory: {
4098
+ geometries: { count: geometryCount, bytes: geometryMemoryBytes },
4099
+ textures: { count: textureCount },
4100
+ materials: { count: materialCount },
4101
+ totalEstimatedGpuBytes: estimatedGpuMemoryBytes,
4102
+ },
4103
+ system: {
4104
+ webglRenderer: this._webglInfoCache?.renderer || "",
4105
+ webglVendor: this._webglInfoCache?.vendor || "",
4106
+ viewport: { width: size.x || 0, height: size.y || 0 },
4107
+ },
4108
+ };
4109
+ }
3915
4110
  async loadNode(nodeId, onLoadFinishCb) {
3916
4111
  const node = this.nodes.get(nodeId);
3917
4112
  if (!node || node.loaded || node.loading) return;
@@ -4082,7 +4277,7 @@ class DynamicGltfLoader {
4082
4277
  if (node.handle) {
4083
4278
  mesh.userData.handle = node.handle;
4084
4279
  } else {
4085
- mesh.userData.handle = `${node.structure.id}_${mesh.userData.handle}`;
4280
+ mesh.userData.handle = this.getFullHandle(node.structure.id, mesh.userData.handle);
4086
4281
  }
4087
4282
  if (mesh.material.name === "edges") {
4088
4283
  mesh.userData.isEdge = true;
@@ -4223,12 +4418,15 @@ class DynamicGltfLoader {
4223
4418
  })),
4224
4419
  });
4225
4420
  }
4421
+ getFullHandle(structureId, originalHandle) {
4422
+ return `${structureId}${STRUCTURE_ID_SEPARATOR}${originalHandle}`;
4423
+ }
4226
4424
  async processNodeHierarchy(structure, nodeId, parentGroup) {
4227
4425
  const nodeDef = structure.json.nodes[nodeId];
4228
4426
  let nodeGroup = null;
4229
4427
  let handle = null;
4230
4428
  if (nodeDef.extras?.handle) {
4231
- handle = `${structure.id}_${nodeDef.extras.handle}`;
4429
+ handle = this.getFullHandle(structure.id, nodeDef.extras.handle);
4232
4430
  }
4233
4431
  if (nodeDef.camera !== undefined) {
4234
4432
  const camera = this.loadCamera(structure, nodeDef.camera, nodeDef);
@@ -4245,7 +4443,7 @@ class DynamicGltfLoader {
4245
4443
  if (nodeDef.extras) {
4246
4444
  nodeGroup.userData = { ...nodeDef.extras };
4247
4445
  if (nodeGroup.userData.handle) {
4248
- nodeGroup.userData.handle = `${structure.id}_${nodeGroup.userData.handle}`;
4446
+ nodeGroup.userData.handle = this.getFullHandle(structure.id, nodeGroup.userData.handle);
4249
4447
  }
4250
4448
  }
4251
4449
  if (nodeDef.matrix) {
@@ -4290,7 +4488,7 @@ class DynamicGltfLoader {
4290
4488
  this.edgeNodes.push(uniqueNodeId);
4291
4489
  }
4292
4490
  if (meshDef.extras && meshDef.extras.handle) {
4293
- handle = `${structure.id}_${meshDef.extras.handle}`;
4491
+ handle = this.getFullHandle(structure.id, meshDef.extras.handle);
4294
4492
  }
4295
4493
  this.nodes.set(uniqueNodeId, {
4296
4494
  position: nodeGroup ? nodeGroup.position.clone() : new Vector3().setFromMatrixPosition(nodeMatrix),
@@ -4303,7 +4501,7 @@ class DynamicGltfLoader {
4303
4501
  structure,
4304
4502
  extras: nodeDef.extras,
4305
4503
  geometryExtents,
4306
- handle,
4504
+ handle: handle || this.getFullHandle(structure.id, structure._nextObjectId++),
4307
4505
  });
4308
4506
  }
4309
4507
  if (nodeDef.children) {
@@ -4376,12 +4574,12 @@ class DynamicGltfLoader {
4376
4574
  }
4377
4575
  }
4378
4576
  async loadNodes() {
4379
- console.time("process nodes");
4577
+ console.time("Process nodes");
4380
4578
  await this.processNodes();
4381
- console.timeEnd("process nodes");
4382
- console.time("optimize scene");
4579
+ console.timeEnd("Process nodes");
4580
+ console.time("Optimize scene");
4383
4581
  await this.optimizeScene();
4384
- console.timeEnd("optimize scene");
4582
+ console.timeEnd("Optimize scene");
4385
4583
  }
4386
4584
  cleanupPartialLoad() {
4387
4585
  this.nodesToLoad.forEach((nodeId) => {
@@ -4702,7 +4900,7 @@ class DynamicGltfLoader {
4702
4900
  this.handleToObjects.set(fullHandle, new Set());
4703
4901
  }
4704
4902
  this.handleToObjects.get(fullHandle).add(object);
4705
- object.userData.structureId = object.userData.handle.split("_")[0];
4903
+ object.userData.structureId = object.userData.handle.split(STRUCTURE_ID_SEPARATOR)[0];
4706
4904
  }
4707
4905
  getObjectsByHandle(handle) {
4708
4906
  if (!handle) return [];
@@ -4768,10 +4966,28 @@ class DynamicGltfLoader {
4768
4966
  }
4769
4967
  this.originalObjects.add(object);
4770
4968
  }
4771
- optimizeScene() {
4969
+ yieldToUI() {
4970
+ return new Promise((resolve) => {
4971
+ requestAnimationFrame(() => {
4972
+ setTimeout(resolve, 0);
4973
+ });
4974
+ });
4975
+ }
4976
+ async optimizeScene() {
4977
+ console.log("Starting scene optimization...");
4978
+ this.dispatchEvent("optimizationprogress", {
4979
+ phase: "start",
4980
+ progress: 0,
4981
+ message: "Starting optimization...",
4982
+ });
4772
4983
  this.originalObjects.clear();
4773
4984
  this.originalObjectsToSelection.clear();
4774
4985
  const structureGroups = new Map();
4986
+ this.dispatchEvent("optimizationprogress", {
4987
+ phase: "collecting",
4988
+ progress: 5,
4989
+ message: "Collecting scene objects...",
4990
+ });
4775
4991
  this.scene.traverse((object) => {
4776
4992
  if (object.userData.structureId) {
4777
4993
  const structureId = object.userData.structureId;
@@ -4800,16 +5016,44 @@ class DynamicGltfLoader {
4800
5016
  }
4801
5017
  }
4802
5018
  });
5019
+ let processedGroups = 0;
5020
+ const totalGroups = structureGroups.size;
5021
+ this.dispatchEvent("optimizationprogress", {
5022
+ phase: "merging",
5023
+ progress: 10,
5024
+ message: `Merging ${totalGroups} structure groups...`,
5025
+ current: 0,
5026
+ total: totalGroups,
5027
+ });
4803
5028
  for (const group of structureGroups.values()) {
4804
5029
  group.mapMeshes.clear();
4805
5030
  group.mapLines.clear();
4806
5031
  group.mapLineSegments.clear();
4807
5032
  group.mapPoints.clear();
4808
- this.mergeMeshGroups(group.meshes, group.rootGroup);
4809
- this.mergeLineGroups(group.lines, group.rootGroup);
4810
- this.mergeLineSegmentGroups(group.lineSegments, group.rootGroup);
4811
- this.mergePointsGroups(group.points, group.rootGroup);
5033
+ await this.mergeMeshGroups(group.meshes, group.rootGroup);
5034
+ await this.yieldToUI();
5035
+ await this.mergeLineGroups(group.lines, group.rootGroup);
5036
+ await this.yieldToUI();
5037
+ await this.mergeLineSegmentGroups(group.lineSegments, group.rootGroup);
5038
+ await this.yieldToUI();
5039
+ await this.mergePointsGroups(group.points, group.rootGroup);
5040
+ processedGroups++;
5041
+ const progress = 10 + Math.round((processedGroups / totalGroups) * 80);
5042
+ this.dispatchEvent("optimizationprogress", {
5043
+ phase: "merging",
5044
+ progress,
5045
+ message: `Processing structure ${processedGroups}/${totalGroups}...`,
5046
+ current: processedGroups,
5047
+ total: totalGroups,
5048
+ });
5049
+ console.log(`Optimization progress: ${processedGroups}/${totalGroups} structure groups processed (${progress}%)`);
5050
+ await this.yieldToUI();
4812
5051
  }
5052
+ this.dispatchEvent("optimizationprogress", {
5053
+ phase: "finalizing",
5054
+ progress: 95,
5055
+ message: "Finalizing optimization...",
5056
+ });
4813
5057
  this.originalObjects.forEach((obj) => {
4814
5058
  obj.visible = false;
4815
5059
  if (!(obj instanceof Points) && !obj.userData.isEdge) {
@@ -4818,9 +5062,15 @@ class DynamicGltfLoader {
4818
5062
  });
4819
5063
  this.initializeObjectVisibility();
4820
5064
  console.log(`Optimization complete. Total objects: ${this.maxObjectId}`);
5065
+ this.dispatchEvent("optimizationprogress", {
5066
+ phase: "complete",
5067
+ progress: 100,
5068
+ message: `Optimization complete! ${this.maxObjectId} objects processed.`,
5069
+ });
4821
5070
  this.dispatchEvent("update");
4822
5071
  }
4823
- mergeMeshGroups(materialGroups, rootGroup) {
5072
+ async mergeMeshGroups(materialGroups, rootGroup) {
5073
+ let processedGroups = 0;
4824
5074
  for (const group of materialGroups) {
4825
5075
  if (!group.material) {
4826
5076
  console.warn("Skipping mesh group with null material");
@@ -4834,8 +5084,6 @@ class DynamicGltfLoader {
4834
5084
  let currentVertexOffset = 0;
4835
5085
  for (const mesh of group.objects) {
4836
5086
  const geometry = mesh.geometry.clone();
4837
- mesh.updateWorldMatrix(true, false);
4838
- geometry.applyMatrix4(mesh.matrixWorld);
4839
5087
  const handle = mesh.userData.handle;
4840
5088
  if (!this.objectIdToIndex.has(handle)) {
4841
5089
  this.objectIdToIndex.set(handle, this.maxObjectId++);
@@ -4894,6 +5142,10 @@ class DynamicGltfLoader {
4894
5142
  this.handleToOptimizedObjects.set(handle, mergedObjects);
4895
5143
  }
4896
5144
  });
5145
+ processedGroups++;
5146
+ if (processedGroups % 5 === 0) {
5147
+ await this.yieldToUI();
5148
+ }
4897
5149
  } catch (error) {
4898
5150
  console.error("Failed to merge meshes for material:", error);
4899
5151
  group.objects.forEach((mesh) => {
@@ -4902,7 +5154,8 @@ class DynamicGltfLoader {
4902
5154
  }
4903
5155
  }
4904
5156
  }
4905
- mergeLineGroups(materialGroups, rootGroup) {
5157
+ async mergeLineGroups(materialGroups, rootGroup) {
5158
+ let processedGroups = 0;
4906
5159
  for (const group of materialGroups) {
4907
5160
  if (group.objects.length === 0) continue;
4908
5161
  if (!group.material) {
@@ -4921,7 +5174,9 @@ class DynamicGltfLoader {
4921
5174
  let posOffset = 0;
4922
5175
  const indices = [];
4923
5176
  let vertexOffset = 0;
5177
+ let isEdge = false;
4924
5178
  group.objects.forEach((line) => {
5179
+ isEdge = line.userData.isEdge;
4925
5180
  const geometry = line.geometry;
4926
5181
  const positionAttr = geometry.attributes.position;
4927
5182
  const vertexCount = positionAttr.count;
@@ -4934,12 +5189,9 @@ class DynamicGltfLoader {
4934
5189
  vertexCount,
4935
5190
  });
4936
5191
  currentVertexOffset += vertexCount;
4937
- line.updateWorldMatrix(true, false);
4938
- const matrix = line.matrixWorld;
4939
5192
  const vector = new Vector3();
4940
5193
  for (let i = 0; i < vertexCount; i++) {
4941
5194
  vector.fromBufferAttribute(positionAttr, i);
4942
- vector.applyMatrix4(matrix);
4943
5195
  positions[posOffset++] = vector.x;
4944
5196
  positions[posOffset++] = vector.y;
4945
5197
  positions[posOffset++] = vector.z;
@@ -4972,6 +5224,7 @@ class DynamicGltfLoader {
4972
5224
  geometry.setAttribute("visibility", new BufferAttribute(visibilityArray, 1));
4973
5225
  const visibilityMaterial = this.createVisibilityMaterial(group.material);
4974
5226
  const mergedLine = new LineSegments(geometry, visibilityMaterial);
5227
+ mergedLine.userData.isEdge = isEdge;
4975
5228
  const mergedObjects = [mergedLine];
4976
5229
  if (this.useVAO) {
4977
5230
  this.createVAO(mergedLine);
@@ -4994,9 +5247,14 @@ class DynamicGltfLoader {
4994
5247
  this.handleToOptimizedObjects.set(handle, mergedObjects);
4995
5248
  }
4996
5249
  });
5250
+ processedGroups++;
5251
+ if (processedGroups % 5 === 0) {
5252
+ await this.yieldToUI();
5253
+ }
4997
5254
  }
4998
5255
  }
4999
- mergeLineSegmentGroups(materialGroups, rootGroup) {
5256
+ async mergeLineSegmentGroups(materialGroups, rootGroup) {
5257
+ let processedGroups = 0;
5000
5258
  for (const group of materialGroups) {
5001
5259
  if (!group.material) {
5002
5260
  console.warn("Skipping line segment group with null material");
@@ -5008,10 +5266,10 @@ class DynamicGltfLoader {
5008
5266
  const handles = new Set();
5009
5267
  const objectMapping = new Map();
5010
5268
  let currentVertexOffset = 0;
5269
+ let isEdge = false;
5011
5270
  for (const line of group.objects) {
5271
+ isEdge = line.userData.isEdge;
5012
5272
  const geometry = line.geometry.clone();
5013
- line.updateWorldMatrix(true, false);
5014
- geometry.applyMatrix4(line.matrixWorld);
5015
5273
  const handle = line.userData.handle;
5016
5274
  if (!this.objectIdToIndex.has(handle)) {
5017
5275
  this.objectIdToIndex.set(handle, this.maxObjectId++);
@@ -5044,6 +5302,7 @@ class DynamicGltfLoader {
5044
5302
  mergedGeometry.setAttribute("visibility", new BufferAttribute(visibilityArray, 1));
5045
5303
  const visibilityMaterial = this.createVisibilityMaterial(group.material);
5046
5304
  const mergedLine = new LineSegments(mergedGeometry, visibilityMaterial);
5305
+ mergedLine.userData.isEdge = isEdge;
5047
5306
  if (this.useVAO) {
5048
5307
  this.createVAO(mergedLine);
5049
5308
  }
@@ -5070,6 +5329,10 @@ class DynamicGltfLoader {
5070
5329
  this.handleToOptimizedObjects.set(handle, mergedObjects);
5071
5330
  }
5072
5331
  });
5332
+ processedGroups++;
5333
+ if (processedGroups % 5 === 0) {
5334
+ await this.yieldToUI();
5335
+ }
5073
5336
  } catch (error) {
5074
5337
  console.warn("Failed to merge line segments for material:", error);
5075
5338
  group.objects.forEach((line) => {
@@ -5078,7 +5341,8 @@ class DynamicGltfLoader {
5078
5341
  }
5079
5342
  }
5080
5343
  }
5081
- mergePointsGroups(materialGroups, rootGroup) {
5344
+ async mergePointsGroups(materialGroups, rootGroup) {
5345
+ let processedGroups = 0;
5082
5346
  for (const group of materialGroups) {
5083
5347
  if (!group.material) {
5084
5348
  console.warn("Skipping points group with null material");
@@ -5090,8 +5354,6 @@ class DynamicGltfLoader {
5090
5354
  const handles = new Set();
5091
5355
  for (const points of group.objects) {
5092
5356
  const geometry = points.geometry.clone();
5093
- points.updateWorldMatrix(true, false);
5094
- geometry.applyMatrix4(points.matrixWorld);
5095
5357
  geometries.push(geometry);
5096
5358
  optimizedObjects.push(points);
5097
5359
  handles.add(points.userData.handle);
@@ -5120,6 +5382,10 @@ class DynamicGltfLoader {
5120
5382
  this.handleToOptimizedObjects.set(handle, mergedObjects);
5121
5383
  }
5122
5384
  });
5385
+ processedGroups++;
5386
+ if (processedGroups % 5 === 0) {
5387
+ await this.yieldToUI();
5388
+ }
5123
5389
  } catch (error) {
5124
5390
  console.warn("Failed to merge points for material:", error);
5125
5391
  group.objects.forEach((points) => {
@@ -5138,7 +5404,6 @@ class DynamicGltfLoader {
5138
5404
  const hasNormals = lineSegmentsArray.some((segment) => segment.geometry.attributes.normal !== undefined);
5139
5405
  lineSegmentsArray.forEach((segment) => {
5140
5406
  const clonedGeometry = segment.geometry.clone();
5141
- segment.updateWorldMatrix(true, false);
5142
5407
  clonedGeometry.applyMatrix4(segment.matrixWorld);
5143
5408
  if (hasNormals && !clonedGeometry.attributes.normal) {
5144
5409
  clonedGeometry.computeVertexNormals();
@@ -5355,8 +5620,225 @@ class DynamicGltfLoader {
5355
5620
  }
5356
5621
  }
5357
5622
 
5358
- class GLTFCloudDynamicLoader {
5623
+ class GLTFLoadingManager extends LoadingManager {
5624
+ constructor(file, params = {}) {
5625
+ super();
5626
+ this.path = "";
5627
+ this.resourcePath = "";
5628
+ this.fileURL = "";
5629
+ this.dataURLs = new Map();
5630
+ this.path = params.path || "";
5631
+ const externalFiles = params.externalFiles || new Map();
5632
+ if (typeof file === "string") {
5633
+ this.fileURL = file;
5634
+ this.resourcePath = LoaderUtils.extractUrlBase(file);
5635
+ }
5636
+ else {
5637
+ externalFiles.forEach((value, key) => (this.fileURL = value === file ? key : this.fileURL));
5638
+ externalFiles.set(this.fileURL, file);
5639
+ }
5640
+ externalFiles.forEach((value, key) => {
5641
+ let dataURL;
5642
+ if (typeof value === "string")
5643
+ dataURL = value;
5644
+ else
5645
+ dataURL = URL.createObjectURL(new Blob([value]));
5646
+ this.dataURLs.set(key, dataURL);
5647
+ });
5648
+ this.setURLModifier((url) => {
5649
+ const key = decodeURI(url)
5650
+ .replace(this.path, "")
5651
+ .replace(this.resourcePath, "")
5652
+ .replace(/^(\.?\/)/, "");
5653
+ const dataURL = this.dataURLs.get(key);
5654
+ return dataURL !== null && dataURL !== void 0 ? dataURL : url;
5655
+ });
5656
+ }
5657
+ dispose() {
5658
+ this.dataURLs.forEach((dataURL) => URL.revokeObjectURL(dataURL));
5659
+ }
5660
+ }
5661
+
5662
+ const BINARY_EXTENSION_HEADER_MAGIC = "glTF";
5663
+ const BINARY_EXTENSION_HEADER_LENGTH = 12;
5664
+ const BINARY_EXTENSION_CHUNK_TYPES = { JSON: 0x4e4f534a, BIN: 0x004e4042 };
5665
+ class GLTFBinaryExtension {
5666
+ constructor(data) {
5667
+ const headerView = new DataView(data, 0, BINARY_EXTENSION_HEADER_LENGTH);
5668
+ const textDecoder = new TextDecoder();
5669
+ const magic = textDecoder.decode(new Uint8Array(data.slice(0, 4)));
5670
+ if (magic !== BINARY_EXTENSION_HEADER_MAGIC) {
5671
+ this.content = textDecoder.decode(data);
5672
+ return;
5673
+ }
5674
+ const header = {
5675
+ magic,
5676
+ version: headerView.getUint32(4, true),
5677
+ length: headerView.getUint32(8, true),
5678
+ };
5679
+ if (header.magic !== BINARY_EXTENSION_HEADER_MAGIC) {
5680
+ throw new Error("Unsupported glTF-Binary header.");
5681
+ }
5682
+ if (header.version < 2.0) {
5683
+ throw new Error("Legacy binary file detected.");
5684
+ }
5685
+ const chunkContentsLength = header.length - BINARY_EXTENSION_HEADER_LENGTH;
5686
+ const chunkView = new DataView(data, BINARY_EXTENSION_HEADER_LENGTH);
5687
+ let chunkIndex = 0;
5688
+ while (chunkIndex < chunkContentsLength) {
5689
+ const chunkLength = chunkView.getUint32(chunkIndex, true);
5690
+ chunkIndex += 4;
5691
+ const chunkType = chunkView.getUint32(chunkIndex, true);
5692
+ chunkIndex += 4;
5693
+ if (chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON) {
5694
+ const contentArray = new Uint8Array(data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength);
5695
+ this.content = textDecoder.decode(contentArray);
5696
+ }
5697
+ else if (chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN) {
5698
+ const byteOffset = BINARY_EXTENSION_HEADER_LENGTH + chunkIndex;
5699
+ this.body = data.slice(byteOffset, byteOffset + chunkLength);
5700
+ }
5701
+ chunkIndex += chunkLength;
5702
+ }
5703
+ if (typeof this.content === "undefined") {
5704
+ throw new Error("JSON content not found.");
5705
+ }
5706
+ }
5707
+ }
5708
+
5709
+ class RangesLoader {
5710
+ constructor() {
5711
+ this.requestHeader = {};
5712
+ this.withCredentials = false;
5713
+ this.abortSignal = undefined;
5714
+ }
5715
+ setRequestHeader(requestHeader) {
5716
+ this.requestHeader = requestHeader;
5717
+ }
5718
+ setWithCredentials(withCredentials) {
5719
+ this.withCredentials = withCredentials;
5720
+ }
5721
+ setAbortSignal(abortSignal) {
5722
+ this.abortSignal = abortSignal;
5723
+ }
5724
+ async load(url, ranges) {
5725
+ const init = {
5726
+ headers: {
5727
+ ...this.requestHeader,
5728
+ Range: "bytes=" + ranges.map((x) => `${x.offset}-${x.offset + x.length - 1}`).join(","),
5729
+ },
5730
+ credentials: this.withCredentials ? "include" : "same-origin",
5731
+ signal: this.abortSignal,
5732
+ };
5733
+ const response = await fetch(url, init);
5734
+ if (!response.ok) {
5735
+ throw new Error(`Failed to fetch "${url}", status ${response.status}`);
5736
+ }
5737
+ if (response.status !== 206) {
5738
+ const arrayBuffer = await response.arrayBuffer();
5739
+ return this.extractRanges(arrayBuffer, ranges);
5740
+ }
5741
+ return response.arrayBuffer();
5742
+ }
5743
+ extractRanges(arrayBuffer, ranges) {
5744
+ const totalLength = ranges.reduce((sum, range) => sum + range.length, 0);
5745
+ const result = new Uint8Array(totalLength);
5746
+ let offset = 0;
5747
+ for (const range of ranges) {
5748
+ const chunk = new Uint8Array(arrayBuffer, range.offset, range.length);
5749
+ result.set(chunk, offset);
5750
+ offset += range.length;
5751
+ }
5752
+ return result.buffer;
5753
+ }
5754
+ }
5755
+
5756
+ class GLTFFileDynamicLoader extends Loader {
5757
+ constructor(viewer) {
5758
+ super();
5759
+ this.viewer = viewer;
5760
+ }
5761
+ dispose() {
5762
+ if (this.gltfLoader)
5763
+ this.gltfLoader.clear();
5764
+ if (this.manager)
5765
+ this.manager.dispose();
5766
+ }
5767
+ isSupport(file, format) {
5768
+ return ((typeof file === "string" || file instanceof globalThis.File || file instanceof ArrayBuffer) &&
5769
+ /(gltf|glb)$/i.test(format));
5770
+ }
5771
+ async load(file, format, params) {
5772
+ this.manager = new GLTFLoadingManager(file, params);
5773
+ const scene = new Group();
5774
+ this.gltfLoader = new DynamicGltfLoader(this.viewer.camera, scene, this.viewer.renderer);
5775
+ this.gltfLoader.memoryLimit = this.viewer.options.memoryLimit;
5776
+ this.gltfLoader.visibleEdges = this.viewer.options.edgeModel;
5777
+ const modelImpl = new DynamicModelImpl(scene);
5778
+ modelImpl.id = params.modelId || this.extractFileName(file);
5779
+ modelImpl.gltfLoader = this.gltfLoader;
5780
+ this.gltfLoader.addEventListener("databasechunk", () => {
5781
+ this.viewer.scene.add(scene);
5782
+ this.viewer.models.push(modelImpl);
5783
+ this.viewer.syncOptions();
5784
+ this.viewer.syncOverlay();
5785
+ this.viewer.update();
5786
+ this.viewer.emitEvent({ type: "databasechunk", data: scene, file });
5787
+ });
5788
+ this.gltfLoader.addEventListener("geometryerror", (data) => {
5789
+ this.viewer.emitEvent({ type: "geometryerror", data, file });
5790
+ });
5791
+ this.gltfLoader.addEventListener("update", (data) => {
5792
+ this.viewer.update();
5793
+ });
5794
+ const loadController = {
5795
+ loadJson: async () => {
5796
+ const loader = new FileLoader(this.manager);
5797
+ loader.setPath(this.manager.path);
5798
+ loader.setRequestHeader(params.requestHeader || {});
5799
+ loader.setWithCredentials(params.withCredentials || loader.withCredentials);
5800
+ loader.setResponseType("arraybuffer");
5801
+ const progress = (event) => {
5802
+ const { lengthComputable, loaded, total } = event;
5803
+ const progress = lengthComputable ? loaded / total : 1;
5804
+ this.viewer.emitEvent({ type: "geometryprogress", data: progress, file });
5805
+ };
5806
+ const data = await loader.loadAsync(this.manager.fileURL, progress);
5807
+ const extension = new GLTFBinaryExtension(data);
5808
+ this.gltf = JSON.parse(extension.content);
5809
+ this.bin = extension.body;
5810
+ return this.gltf;
5811
+ },
5812
+ loadBinaryData: (ranges, uri = "") => {
5813
+ const loader = new RangesLoader();
5814
+ loader.setRequestHeader(params.requestHeader || {});
5815
+ loader.setWithCredentials(params.withCredentials || false);
5816
+ loader.setAbortSignal(this.gltfLoader.abortController.signal);
5817
+ if (this.bin)
5818
+ return loader.extractRanges(this.bin, ranges);
5819
+ const path = this.manager.path || this.manager.resourcePath;
5820
+ const url = LoaderUtils.resolveURL(uri, path);
5821
+ return loader.load(this.manager.resolveURL(url), ranges);
5822
+ },
5823
+ baseUrl: () => {
5824
+ const path = this.manager.path || this.manager.resourcePath;
5825
+ return Promise.resolve(path);
5826
+ },
5827
+ };
5828
+ const structure = new GltfStructure(modelImpl.id, loadController);
5829
+ await this.gltfLoader.loadStructure(structure);
5830
+ await this.gltfLoader.loadNodes();
5831
+ return this;
5832
+ }
5833
+ cancel() {
5834
+ if (this.gltfLoader)
5835
+ this.gltfLoader.abortLoading();
5836
+ }
5837
+ }
5838
+
5839
+ class GLTFCloudDynamicLoader extends Loader {
5359
5840
  constructor(viewer) {
5841
+ super();
5360
5842
  this.requestId = 0;
5361
5843
  this.viewer = viewer;
5362
5844
  }
@@ -5371,17 +5853,15 @@ class GLTFCloudDynamicLoader {
5371
5853
  typeof file.downloadResourceRange === "function" &&
5372
5854
  /.gltf$/i.test(file.database));
5373
5855
  }
5374
- async load(model, format, params) {
5856
+ async load(model, format, params = {}) {
5375
5857
  const scene = new Group();
5376
5858
  this.gltfLoader = new DynamicGltfLoader(this.viewer.camera, scene, this.viewer.renderer);
5377
5859
  this.gltfLoader.memoryLimit = this.viewer.options.memoryLimit;
5378
5860
  this.gltfLoader.setVisibleEdges(this.viewer.options.edgeModel);
5861
+ const modelImpl = new DynamicModelImpl(scene);
5862
+ modelImpl.id = model.file.id;
5863
+ modelImpl.gltfLoader = this.gltfLoader;
5379
5864
  this.gltfLoader.addEventListener("databasechunk", (data) => {
5380
- const modelImpl = new DynamicModelImpl(scene);
5381
- modelImpl.loader = this;
5382
- modelImpl.viewer = this.viewer;
5383
- modelImpl.gltfLoader = this.gltfLoader;
5384
- modelImpl.modelId = model.id;
5385
5865
  this.viewer.scene.add(scene);
5386
5866
  this.viewer.models.push(modelImpl);
5387
5867
  this.viewer.syncOptions();
@@ -5389,10 +5869,6 @@ class GLTFCloudDynamicLoader {
5389
5869
  this.viewer.update();
5390
5870
  this.viewer.emitEvent({ type: "databasechunk", data: scene, file: model.file, model });
5391
5871
  });
5392
- this.gltfLoader.addEventListener("geometryprogress", (data) => {
5393
- const progress = data.loaded / data.total;
5394
- this.viewer.emitEvent({ type: "geometryprogress", data: progress, file: model.file, model });
5395
- });
5396
5872
  this.gltfLoader.addEventListener("geometryerror", (data) => {
5397
5873
  this.viewer.emitEvent({ type: "geometryerror", data, file: model.file, model });
5398
5874
  });
@@ -5402,7 +5878,7 @@ class GLTFCloudDynamicLoader {
5402
5878
  const loadController = {
5403
5879
  loadJson: async () => {
5404
5880
  const progress = (progress) => {
5405
- this.viewer.emitEvent({ type: "geometryprogress", data: progress, file: model });
5881
+ this.viewer.emitEvent({ type: "geometryprogress", data: progress, file: model.file, model });
5406
5882
  };
5407
5883
  const arrayBuffer = await model.downloadResource(model.database, progress, this.gltfLoader.getAbortController().signal);
5408
5884
  const text = new TextDecoder().decode(arrayBuffer);
@@ -5419,7 +5895,7 @@ class GLTFCloudDynamicLoader {
5419
5895
  },
5420
5896
  baseUrl: () => Promise.resolve(`${model.httpClient.serverUrl}${model.path}/`),
5421
5897
  };
5422
- const structure = new GltfStructure(model.id, loadController);
5898
+ const structure = new GltfStructure(modelImpl.id, loadController);
5423
5899
  await this.gltfLoader.loadStructure(structure);
5424
5900
  await this.gltfLoader.loadNodes();
5425
5901
  return this;
@@ -5431,7 +5907,7 @@ class GLTFCloudDynamicLoader {
5431
5907
  }
5432
5908
 
5433
5909
  const loaders = loadersRegistry("threejs");
5434
- loaders.registerLoader("gltf-file", (viewer) => new GLTFFileLoader(viewer));
5910
+ loaders.registerLoader("gltf-file", (viewer) => new GLTFFileDynamicLoader(viewer));
5435
5911
  loaders.registerLoader("gltf-cloud", (viewer) => new GLTFCloudDynamicLoader(viewer));
5436
5912
 
5437
5913
  class SSAARenderPass extends Pass {
@@ -5638,24 +6114,25 @@ class Helpers extends Scene {
5638
6114
  class Viewer extends EventEmitter2 {
5639
6115
  constructor(client) {
5640
6116
  super();
5641
- this._options = new Options(this);
5642
6117
  this.client = client;
5643
- this.canvasEvents = CANVAS_EVENTS;
5644
- this.canvaseventlistener = (event) => this.emit(event);
6118
+ this.options = new Options(this);
5645
6119
  this.loaders = [];
5646
6120
  this.models = [];
6121
+ this.canvasEvents = CANVAS_EVENTS.slice();
6122
+ this.canvaseventlistener = (event) => this.emit(event);
5647
6123
  this.selected = [];
5648
6124
  this.extents = new Box3();
5649
- this.target = new Vector3();
6125
+ this.target = new Vector3(0, 0, 0);
5650
6126
  this._activeDragger = null;
5651
6127
  this._components = [];
6128
+ this._renderNeeded = false;
5652
6129
  this._renderTime = 0;
5653
6130
  this.render = this.render.bind(this);
5654
6131
  this.update = this.update.bind(this);
5655
6132
  this._markup = new Markup();
5656
6133
  }
5657
- get options() {
5658
- return this._options;
6134
+ get markup() {
6135
+ return this._markup;
5659
6136
  }
5660
6137
  get draggers() {
5661
6138
  return [...draggers.getDraggers().keys()];
@@ -5663,14 +6140,10 @@ class Viewer extends EventEmitter2 {
5663
6140
  get components() {
5664
6141
  return [...components.getComponents().keys()];
5665
6142
  }
5666
- get markup() {
5667
- return this._markup;
5668
- }
5669
6143
  initialize(canvas, onProgress) {
5670
6144
  this.addEventListener("optionschange", (event) => this.syncOptions(event.data));
5671
6145
  this.scene = new Scene();
5672
6146
  this.helpers = new Helpers();
5673
- this.target = new Vector3(0, 0, 0);
5674
6147
  const pixelRatio = window.devicePixelRatio;
5675
6148
  const rect = canvas.parentElement.getBoundingClientRect();
5676
6149
  const width = rect.width || 1;
@@ -5821,21 +6294,25 @@ class Viewer extends EventEmitter2 {
5821
6294
  async open(file, params = {}) {
5822
6295
  if (!this.renderer)
5823
6296
  return this;
5824
- if (params.mode !== "a" && params.mode !== "append") {
6297
+ const mode = params.mode || "file";
6298
+ if (mode !== "assembly" && mode !== "a" && mode !== "append") {
5825
6299
  this.cancel();
5826
6300
  this.clear();
5827
6301
  }
5828
- this.emitEvent({ type: "open", file });
6302
+ this.emitEvent({ type: "open", mode, file });
5829
6303
  let model = file;
5830
6304
  if (model && typeof model.getModels === "function") {
5831
6305
  const models = await model.getModels();
5832
6306
  model = models.find((model) => model.default) || models[0] || file;
5833
6307
  }
6308
+ if (model && typeof model.database === "string") {
6309
+ file = model.file;
6310
+ }
5834
6311
  if (!model)
5835
6312
  throw new Error(`Format not supported`);
5836
6313
  let format = params.format;
5837
- if (!format && typeof model.type === "string")
5838
- format = model.type.split(".").pop();
6314
+ if (!format && typeof file["type"] === "string")
6315
+ format = file["type"].split(".").pop();
5839
6316
  if (!format && typeof file === "string")
5840
6317
  format = file.split(".").pop();
5841
6318
  if (!format && file instanceof globalThis.File)
@@ -5862,7 +6339,7 @@ class Viewer extends EventEmitter2 {
5862
6339
  }
5863
6340
  loadGltfFile(file, externalFiles, params = {}) {
5864
6341
  console.warn("Viewer.loadGltfFile() has been deprecated since 26.4 and will be removed in a future release, use Viewer.open() instead.");
5865
- return this.open(file, { ...params, format: "gltf", externalFiles, mode: "append" });
6342
+ return this.open(file, { ...params, format: "gltf", externalFiles, mode: "assembly" });
5866
6343
  }
5867
6344
  cancel() {
5868
6345
  this.loaders.forEach((loader) => loader.cancel());
@@ -5882,12 +6359,17 @@ class Viewer extends EventEmitter2 {
5882
6359
  this.models = [];
5883
6360
  this.scene.clear();
5884
6361
  this.helpers.clear();
6362
+ this.extents.makeEmpty();
6363
+ this.target.set(0, 0, 0);
5885
6364
  this.syncOptions();
5886
6365
  this.syncOverlay();
5887
6366
  this.update(true);
5888
6367
  this.emitEvent({ type: "clear" });
5889
6368
  return this;
5890
6369
  }
6370
+ is3D() {
6371
+ return true;
6372
+ }
5891
6373
  syncOptions(options = this.options) {
5892
6374
  if (!this.renderer)
5893
6375
  return;
@@ -5919,9 +6401,15 @@ class Viewer extends EventEmitter2 {
5919
6401
  getSelected() {
5920
6402
  return this.executeCommand("getSelected");
5921
6403
  }
6404
+ getSelected2() {
6405
+ return this.executeCommand("getSelected2");
6406
+ }
5922
6407
  setSelected(handles) {
5923
6408
  this.executeCommand("setSelected", handles);
5924
6409
  }
6410
+ setSelected2(handles) {
6411
+ this.executeCommand("setSelected2", handles);
6412
+ }
5925
6413
  clearSelected() {
5926
6414
  this.executeCommand("clearSelected");
5927
6415
  }
@@ -5981,37 +6469,8 @@ class Viewer extends EventEmitter2 {
5981
6469
  getComponent(name) {
5982
6470
  return this._components.find((component) => component.name === name);
5983
6471
  }
5984
- is3D() {
5985
- return true;
5986
- }
5987
- screenToWorld(position) {
5988
- if (!this.renderer)
5989
- return { x: position.x, y: position.y, z: 0 };
5990
- const rect = this.canvas.getBoundingClientRect();
5991
- const x = position.x / (rect.width / 2) - 1;
5992
- const y = -position.y / (rect.height / 2) + 1;
5993
- const point = new Vector3(x, y, -1);
5994
- point.unproject(this.camera);
5995
- return { x: point.x, y: point.y, z: point.z };
5996
- }
5997
- worldToScreen(position) {
5998
- if (!this.renderer)
5999
- return { x: position.x, y: position.y };
6000
- const point = new Vector3(position.x, position.y, position.z);
6001
- point.project(this.camera);
6002
- const rect = this.canvas.getBoundingClientRect();
6003
- const x = (point.x + 1) * (rect.width / 2);
6004
- const y = (-point.y + 1) * (rect.height / 2);
6005
- return { x, y };
6006
- }
6007
- getScale() {
6008
- return { x: 1, y: 1, z: 1 };
6009
- }
6010
- executeCommand(id, ...args) {
6011
- return commands.executeCommand(id, this, ...args);
6012
- }
6013
6472
  drawViewpoint(viewpoint) {
6014
- var _a, _b, _c;
6473
+ var _a, _b, _c, _d;
6015
6474
  if (!this.renderer)
6016
6475
  return;
6017
6476
  const getVector3FromPoint3d = ({ x, y, z }) => new Vector3(x, y, z);
@@ -6063,11 +6522,13 @@ class Viewer extends EventEmitter2 {
6063
6522
  }
6064
6523
  };
6065
6524
  const setClippingPlanes = (clipping_planes) => {
6066
- clipping_planes === null || clipping_planes === void 0 ? void 0 : clipping_planes.forEach((clipping_plane) => {
6067
- const plane = new Plane();
6068
- plane.setFromNormalAndCoplanarPoint(getVector3FromPoint3d(clipping_plane.direction), getVector3FromPoint3d(clipping_plane.location));
6069
- this.renderer.clippingPlanes.push(plane);
6070
- });
6525
+ if (clipping_planes) {
6526
+ clipping_planes.forEach((clipping_plane) => {
6527
+ const plane = new Plane();
6528
+ plane.setFromNormalAndCoplanarPoint(getVector3FromPoint3d(clipping_plane.direction), getVector3FromPoint3d(clipping_plane.location));
6529
+ this.renderer.clippingPlanes.push(plane);
6530
+ });
6531
+ }
6071
6532
  };
6072
6533
  const setSelection = (selection) => {
6073
6534
  if (selection)
@@ -6083,9 +6544,9 @@ class Viewer extends EventEmitter2 {
6083
6544
  setOrthogonalCamera(viewpoint.orthogonal_camera);
6084
6545
  setPerspectiveCamera(viewpoint.perspective_camera);
6085
6546
  setClippingPlanes(viewpoint.clipping_planes);
6086
- setSelection(viewpoint.selection);
6547
+ setSelection(((_b = viewpoint.custom_fields) === null || _b === void 0 ? void 0 : _b.selection2) || viewpoint.selection);
6087
6548
  this._markup.setViewpoint(viewpoint);
6088
- this.target = getVector3FromPoint3d((_c = (_b = viewpoint.custom_fields) === null || _b === void 0 ? void 0 : _b.camera_target) !== null && _c !== void 0 ? _c : this.target);
6549
+ this.target.copy(getVector3FromPoint3d((_d = (_c = viewpoint.custom_fields) === null || _c === void 0 ? void 0 : _c.camera_target) !== null && _d !== void 0 ? _d : this.target));
6089
6550
  this.setActiveDragger(draggerName);
6090
6551
  this.emitEvent({ type: "drawviewpoint", data: viewpoint });
6091
6552
  this.update();
@@ -6132,6 +6593,9 @@ class Viewer extends EventEmitter2 {
6132
6593
  const getSelection = () => {
6133
6594
  return this.getSelected().map((handle) => ({ handle }));
6134
6595
  };
6596
+ const getSelection2 = () => {
6597
+ return this.getSelected2().map((handle) => ({ handle }));
6598
+ };
6135
6599
  const viewpoint = { custom_fields: {} };
6136
6600
  viewpoint.orthogonal_camera = getOrthogonalCamera();
6137
6601
  viewpoint.perspective_camera = getPerspectiveCamera();
@@ -6140,9 +6604,36 @@ class Viewer extends EventEmitter2 {
6140
6604
  viewpoint.description = new Date().toDateString();
6141
6605
  this._markup.getViewpoint(viewpoint);
6142
6606
  viewpoint.custom_fields.camera_target = getPoint3dFromVector3(this.target);
6607
+ viewpoint.custom_fields.selection2 = getSelection2();
6143
6608
  this.emitEvent({ type: "createviewpoint", data: viewpoint });
6144
6609
  return viewpoint;
6145
6610
  }
6611
+ screenToWorld(position) {
6612
+ if (!this.renderer)
6613
+ return { x: position.x, y: position.y, z: 0 };
6614
+ const rect = this.canvas.getBoundingClientRect();
6615
+ const x = position.x / (rect.width / 2) - 1;
6616
+ const y = -position.y / (rect.height / 2) + 1;
6617
+ const point = new Vector3(x, y, -1);
6618
+ point.unproject(this.camera);
6619
+ return { x: point.x, y: point.y, z: point.z };
6620
+ }
6621
+ worldToScreen(position) {
6622
+ if (!this.renderer)
6623
+ return { x: position.x, y: position.y };
6624
+ const point = new Vector3(position.x, position.y, position.z);
6625
+ point.project(this.camera);
6626
+ const rect = this.canvas.getBoundingClientRect();
6627
+ const x = (point.x + 1) * (rect.width / 2);
6628
+ const y = (-point.y + 1) * (rect.height / 2);
6629
+ return { x, y };
6630
+ }
6631
+ getScale() {
6632
+ return { x: 1, y: 1, z: 1 };
6633
+ }
6634
+ executeCommand(id, ...args) {
6635
+ return commands.executeCommand(id, this, ...args);
6636
+ }
6146
6637
  }
6147
6638
 
6148
6639
  export { GLTFLoadingManager, ModelImpl, Viewer, commands, components, draggers, loaders };