@inweb/viewer-three 26.10.6 → 26.12.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 (153) 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/InfoPanelComponent.js +170 -0
  15. package/dist/extensions/components/InfoPanelComponent.js.map +1 -0
  16. package/dist/extensions/components/InfoPanelComponent.min.js +24 -0
  17. package/dist/extensions/components/InfoPanelComponent.module.js +164 -0
  18. package/dist/extensions/components/InfoPanelComponent.module.js.map +1 -0
  19. package/dist/extensions/components/LightHelperComponent.js.map +1 -0
  20. package/dist/extensions/components/LightHelperComponent.module.js.map +1 -0
  21. package/dist/extensions/components/RoomEnvironmentComponent.js.map +1 -0
  22. package/dist/extensions/components/RoomEnvironmentComponent.module.js.map +1 -0
  23. package/dist/{plugins → extensions}/components/StatsPanelComponent.js +9 -3
  24. package/dist/extensions/components/StatsPanelComponent.js.map +1 -0
  25. package/dist/extensions/components/StatsPanelComponent.min.js +24 -0
  26. package/dist/{plugins → extensions}/components/StatsPanelComponent.module.js +9 -3
  27. package/dist/extensions/components/StatsPanelComponent.module.js.map +1 -0
  28. package/dist/{plugins → extensions}/loaders/GLTFCloudLoader.js +2 -3
  29. package/dist/extensions/loaders/GLTFCloudLoader.js.map +1 -0
  30. package/dist/{plugins → extensions}/loaders/GLTFCloudLoader.min.js +1 -1
  31. package/dist/{plugins → extensions}/loaders/GLTFCloudLoader.module.js +2 -3
  32. package/dist/extensions/loaders/GLTFCloudLoader.module.js.map +1 -0
  33. package/dist/extensions/loaders/GLTFFileLoader.js +2499 -0
  34. package/dist/extensions/loaders/GLTFFileLoader.js.map +1 -0
  35. package/dist/extensions/loaders/GLTFFileLoader.min.js +24 -0
  36. package/dist/extensions/loaders/GLTFFileLoader.module.js +74 -0
  37. package/dist/extensions/loaders/GLTFFileLoader.module.js.map +1 -0
  38. package/dist/{plugins → extensions}/loaders/IFCXLoader.js +5 -7
  39. package/dist/extensions/loaders/IFCXLoader.js.map +1 -0
  40. package/dist/{plugins → extensions}/loaders/IFCXLoader.min.js +1 -1
  41. package/dist/{plugins → extensions}/loaders/IFCXLoader.module.js +5 -7
  42. package/dist/extensions/loaders/IFCXLoader.module.js.map +1 -0
  43. package/dist/{plugins → extensions}/loaders/PotreeLoader.js +56 -6
  44. package/dist/extensions/loaders/PotreeLoader.js.map +1 -0
  45. package/dist/extensions/loaders/PotreeLoader.min.js +24 -0
  46. package/dist/{plugins → extensions}/loaders/PotreeLoader.module.js +53 -2
  47. package/dist/extensions/loaders/PotreeLoader.module.js.map +1 -0
  48. package/dist/viewer-three.js +1416 -2930
  49. package/dist/viewer-three.js.map +1 -1
  50. package/dist/viewer-three.min.js +8 -3
  51. package/dist/viewer-three.module.js +1205 -363
  52. package/dist/viewer-three.module.js.map +1 -1
  53. package/{plugins → extensions}/components/AxesHelperComponent.ts +31 -2
  54. package/{plugins → extensions}/components/ExtentsHelperComponent.ts +25 -0
  55. package/extensions/components/InfoPanelComponent.ts +197 -0
  56. package/{plugins → extensions}/components/StatsPanelComponent.ts +10 -3
  57. package/{plugins → extensions}/loaders/GLTFCloudLoader.ts +2 -3
  58. package/{src/Viewer → extensions}/loaders/GLTFFileLoader.ts +21 -12
  59. package/{plugins → extensions}/loaders/IFCX/IFCXCloudLoader.ts +5 -5
  60. package/{plugins → extensions}/loaders/IFCX/IFCXFileLoader.ts +3 -4
  61. package/{plugins → extensions}/loaders/Potree/PotreeFileLoader.ts +3 -4
  62. package/extensions/loaders/Potree/PotreeModelImpl.ts +108 -0
  63. package/lib/Viewer/Viewer.d.ts +28 -20
  64. package/lib/Viewer/commands/GetSelected2.d.ts +2 -0
  65. package/lib/Viewer/commands/SelectModel.d.ts +1 -1
  66. package/lib/Viewer/commands/SetSelected2.d.ts +2 -0
  67. package/lib/Viewer/components/InfoComponent.d.ts +22 -0
  68. package/lib/Viewer/components/SelectionComponent.d.ts +1 -3
  69. package/lib/Viewer/components/index.d.ts +6 -6
  70. package/lib/Viewer/draggers/MeasureLineDragger.d.ts +7 -1
  71. package/lib/Viewer/loaders/DynamicGltfLoader/DynamicModelImpl.d.ts +2 -1
  72. package/lib/Viewer/loaders/GLTFBinaryExtension.d.ts +5 -0
  73. package/lib/Viewer/loaders/GLTFCloudDynamicLoader.d.ts +2 -2
  74. package/lib/Viewer/loaders/{GLTFFileLoader.d.ts → GLTFFileDynamicLoader.d.ts} +7 -1
  75. package/lib/Viewer/loaders/GLTFLoadingManager.d.ts +4 -3
  76. package/lib/Viewer/loaders/RangesLoader.d.ts +15 -0
  77. package/lib/Viewer/loaders/index.d.ts +22 -14
  78. package/lib/Viewer/measurement/Snapper.d.ts +15 -0
  79. package/lib/Viewer/measurement/UnitConverter.d.ts +63 -0
  80. package/lib/Viewer/measurement/UnitFormatter.d.ts +4 -0
  81. package/lib/Viewer/models/IModelImpl.d.ts +11 -8
  82. package/lib/Viewer/models/ModelImpl.d.ts +9 -5
  83. package/package.json +11 -11
  84. package/src/Viewer/Viewer.ts +127 -88
  85. package/src/Viewer/commands/ClearSelected.ts +3 -1
  86. package/src/Viewer/commands/GetModels.ts +1 -1
  87. package/src/Viewer/commands/GetSelected.ts +2 -2
  88. package/{plugins/loaders/Potree/PotreeModelImpl.ts → src/Viewer/commands/GetSelected2.ts} +7 -9
  89. package/src/Viewer/commands/HideSelected.ts +3 -1
  90. package/src/Viewer/commands/SelectModel.ts +5 -5
  91. package/src/Viewer/commands/SetSelected.ts +9 -10
  92. package/src/Viewer/commands/SetSelected2.ts +42 -0
  93. package/src/Viewer/commands/ZoomToObjects.ts +5 -6
  94. package/src/Viewer/commands/ZoomToSelected.ts +3 -1
  95. package/src/Viewer/commands/index.ts +4 -0
  96. package/src/Viewer/components/CameraComponent.ts +6 -1
  97. package/src/Viewer/components/ExtentsComponent.ts +4 -1
  98. package/src/Viewer/components/InfoComponent.ts +187 -0
  99. package/src/Viewer/components/SelectionComponent.ts +7 -30
  100. package/src/Viewer/components/index.ts +8 -6
  101. package/src/Viewer/draggers/MeasureLineDragger.ts +84 -226
  102. package/src/Viewer/loaders/DynamicGltfLoader/DynamicGltfLoader.js +276 -39
  103. package/src/Viewer/loaders/DynamicGltfLoader/DynamicModelImpl.ts +45 -10
  104. package/src/Viewer/loaders/DynamicGltfLoader/GltfStructure.js +71 -2
  105. package/src/Viewer/loaders/GLTFBinaryExtension.ts +91 -0
  106. package/src/Viewer/loaders/GLTFCloudDynamicLoader.ts +13 -19
  107. package/src/Viewer/loaders/GLTFFileDynamicLoader.ts +145 -0
  108. package/src/Viewer/loaders/GLTFLoadingManager.ts +5 -4
  109. package/src/Viewer/loaders/RangesLoader.ts +105 -0
  110. package/src/Viewer/loaders/index.ts +24 -16
  111. package/src/Viewer/measurement/Snapper.ts +208 -0
  112. package/src/Viewer/measurement/UnitConverter.ts +47 -0
  113. package/src/Viewer/measurement/UnitFormatter.ts +95 -0
  114. package/src/Viewer/models/IModelImpl.ts +17 -8
  115. package/src/Viewer/models/ModelImpl.ts +205 -16
  116. package/src/index-umd.ts +1 -1
  117. package/dist/plugins/components/AxesHelperComponent.js.map +0 -1
  118. package/dist/plugins/components/AxesHelperComponent.module.js.map +0 -1
  119. package/dist/plugins/components/ExtentsHelperComponent.js.map +0 -1
  120. package/dist/plugins/components/ExtentsHelperComponent.min.js +0 -24
  121. package/dist/plugins/components/ExtentsHelperComponent.module.js.map +0 -1
  122. package/dist/plugins/components/GridHelperComponent.js.map +0 -1
  123. package/dist/plugins/components/GridHelperComponent.module.js.map +0 -1
  124. package/dist/plugins/components/LightHelperComponent.js.map +0 -1
  125. package/dist/plugins/components/LightHelperComponent.module.js.map +0 -1
  126. package/dist/plugins/components/RoomEnvironmentComponent.js.map +0 -1
  127. package/dist/plugins/components/RoomEnvironmentComponent.module.js.map +0 -1
  128. package/dist/plugins/components/StatsPanelComponent.js.map +0 -1
  129. package/dist/plugins/components/StatsPanelComponent.min.js +0 -24
  130. package/dist/plugins/components/StatsPanelComponent.module.js.map +0 -1
  131. package/dist/plugins/loaders/GLTFCloudLoader.js.map +0 -1
  132. package/dist/plugins/loaders/GLTFCloudLoader.module.js.map +0 -1
  133. package/dist/plugins/loaders/IFCXLoader.js.map +0 -1
  134. package/dist/plugins/loaders/IFCXLoader.module.js.map +0 -1
  135. package/dist/plugins/loaders/PotreeLoader.js.map +0 -1
  136. package/dist/plugins/loaders/PotreeLoader.min.js +0 -24
  137. package/dist/plugins/loaders/PotreeLoader.module.js.map +0 -1
  138. /package/dist/{plugins → extensions}/components/GridHelperComponent.js +0 -0
  139. /package/dist/{plugins → extensions}/components/GridHelperComponent.min.js +0 -0
  140. /package/dist/{plugins → extensions}/components/GridHelperComponent.module.js +0 -0
  141. /package/dist/{plugins → extensions}/components/LightHelperComponent.js +0 -0
  142. /package/dist/{plugins → extensions}/components/LightHelperComponent.min.js +0 -0
  143. /package/dist/{plugins → extensions}/components/LightHelperComponent.module.js +0 -0
  144. /package/dist/{plugins → extensions}/components/RoomEnvironmentComponent.js +0 -0
  145. /package/dist/{plugins → extensions}/components/RoomEnvironmentComponent.min.js +0 -0
  146. /package/dist/{plugins → extensions}/components/RoomEnvironmentComponent.module.js +0 -0
  147. /package/{plugins → extensions}/components/GridHelperComponent.ts +0 -0
  148. /package/{plugins → extensions}/components/LightHelperComponent.ts +0 -0
  149. /package/{plugins → extensions}/components/RoomEnvironmentComponent.ts +0 -0
  150. /package/{plugins → extensions}/loaders/IFCX/IFCXLoader.ts +0 -0
  151. /package/{plugins → extensions}/loaders/IFCX/index.ts +0 -0
  152. /package/{plugins → extensions}/loaders/IFCX/render.js +0 -0
  153. /package/{plugins → extensions}/loaders/Potree/index.ts +0 -0
@@ -21,14 +21,13 @@
21
21
  // acknowledge and accept the above terms.
22
22
  ///////////////////////////////////////////////////////////////////////////////
23
23
 
24
- import { draggersRegistry, commandsRegistry, Options, componentsRegistry, Loader, loadersRegistry, CANVAS_EVENTS } from '@inweb/viewer-core';
24
+ import { draggersRegistry, commandsRegistry, Options, componentsRegistry, Info, 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, REVISION, 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;
@@ -2502,6 +2633,140 @@ class LightComponent {
2502
2633
  }
2503
2634
  }
2504
2635
 
2636
+ class InfoComponent {
2637
+ constructor(viewer) {
2638
+ this.initialize = () => {
2639
+ try {
2640
+ const gl = this.viewer.renderer.getContext();
2641
+ const dbgInfo = gl.getExtension("WEBGL_debug_renderer_info");
2642
+ if (dbgInfo) {
2643
+ this.viewer.info.system.webglRenderer = gl.getParameter(dbgInfo.UNMASKED_RENDERER_WEBGL);
2644
+ this.viewer.info.system.webglVendor = gl.getParameter(dbgInfo.UNMASKED_VENDOR_WEBGL);
2645
+ }
2646
+ }
2647
+ catch (error) {
2648
+ console.error("Error reading WebGL info.", error);
2649
+ }
2650
+ console.log("THREE.WebGLRenderer:", REVISION);
2651
+ console.log("WebGL Renderer:", this.viewer.info.system.webglRenderer);
2652
+ console.log("WebGL Vendor:", this.viewer.info.system.webglVendor);
2653
+ this.resize();
2654
+ this.optionsChange({ data: this.viewer.options });
2655
+ };
2656
+ this.clear = () => {
2657
+ this.viewer.info.performance.timeToFirstRender = 0;
2658
+ this.viewer.info.performance.loadTime = 0;
2659
+ this.viewer.info.scene.objects = 0;
2660
+ this.viewer.info.scene.triangles = 0;
2661
+ this.viewer.info.scene.points = 0;
2662
+ this.viewer.info.scene.lines = 0;
2663
+ this.viewer.info.scene.edges = 0;
2664
+ this.viewer.info.optimizedScene.objects = 0;
2665
+ this.viewer.info.optimizedScene.triangles = 0;
2666
+ this.viewer.info.optimizedScene.points = 0;
2667
+ this.viewer.info.optimizedScene.lines = 0;
2668
+ this.viewer.info.optimizedScene.edges = 0;
2669
+ this.viewer.info.memory.geometries = 0;
2670
+ this.viewer.info.memory.geometryBytes = 0;
2671
+ this.viewer.info.memory.textures = 0;
2672
+ this.viewer.info.memory.textureBytes = 0;
2673
+ this.viewer.info.memory.materials = 0;
2674
+ this.viewer.info.memory.totalEstimatedGpuBytes = 0;
2675
+ this.viewer.info.memory.usedJSHeapSize = 0;
2676
+ };
2677
+ this.optionsChange = ({ data: options }) => {
2678
+ if (options.antialiasing === false)
2679
+ this.viewer.info.render.antialiasing = "";
2680
+ else if (options.antialiasing === true)
2681
+ this.viewer.info.render.antialiasing = "mxaa";
2682
+ else
2683
+ this.viewer.info.render.antialiasing = options.antialiasing;
2684
+ };
2685
+ this.geometryStart = () => {
2686
+ this.startTime = performance.now();
2687
+ };
2688
+ this.databaseChunk = () => {
2689
+ this.viewer.info.performance.timeToFirstRender += performance.now() - this.startTime;
2690
+ console.log("Time to first render:", this.viewer.info.performance.timeToFirstRender, "ms");
2691
+ };
2692
+ this.geometryEnd = () => {
2693
+ const model = this.viewer.models[this.viewer.models.length - 1];
2694
+ const info = model.getInfo();
2695
+ this.viewer.info.scene.objects += info.scene.objects;
2696
+ this.viewer.info.scene.triangles += info.scene.triangles;
2697
+ this.viewer.info.scene.points += info.scene.points;
2698
+ this.viewer.info.scene.lines += info.scene.lines;
2699
+ this.viewer.info.scene.edges += info.scene.edges;
2700
+ this.viewer.info.optimizedScene.objects += info.optimizedScene.objects;
2701
+ this.viewer.info.optimizedScene.triangles += info.optimizedScene.triangles;
2702
+ this.viewer.info.optimizedScene.points += info.optimizedScene.points;
2703
+ this.viewer.info.optimizedScene.lines += info.optimizedScene.lines;
2704
+ this.viewer.info.optimizedScene.edges += info.optimizedScene.edges;
2705
+ this.viewer.info.memory.geometries += info.memory.geometries;
2706
+ this.viewer.info.memory.geometryBytes += info.memory.geometryBytes;
2707
+ this.viewer.info.memory.textures += info.memory.textures;
2708
+ this.viewer.info.memory.textureBytes += info.memory.textureBytes;
2709
+ this.viewer.info.memory.materials += info.memory.materials;
2710
+ this.viewer.info.memory.totalEstimatedGpuBytes += info.memory.totalEstimatedGpuBytes;
2711
+ const memory = performance["memory"];
2712
+ if (memory)
2713
+ this.viewer.info.memory.usedJSHeapSize = memory.usedJSHeapSize;
2714
+ this.viewer.info.performance.loadTime += performance.now() - this.startTime;
2715
+ console.log("Number of objects:", info.scene.objects);
2716
+ console.log("Number of objects after optimization:", info.optimizedScene.objects);
2717
+ console.log("Total geometry size:", info.memory.totalEstimatedGpuBytes / (1024 * 1024), "MB");
2718
+ console.log("File load time:", this.viewer.info.performance.loadTime, "ms");
2719
+ };
2720
+ this.resize = () => {
2721
+ const rendererSize = this.viewer.renderer.getSize(new Vector2());
2722
+ this.viewer.info.render.viewport.width = rendererSize.x;
2723
+ this.viewer.info.render.viewport.height = rendererSize.y;
2724
+ };
2725
+ this.render = () => {
2726
+ this.viewer.info.render.drawCalls = this.viewer.renderer.info.render.calls;
2727
+ this.viewer.info.render.triangles = this.viewer.renderer.info.render.triangles;
2728
+ this.viewer.info.render.points = this.viewer.renderer.info.render.points;
2729
+ this.viewer.info.render.lines = this.viewer.renderer.info.render.lines;
2730
+ };
2731
+ this.animate = () => {
2732
+ const time = performance.now();
2733
+ this.viewer.info.performance.frameTime = Math.round(time - this.beginTime);
2734
+ this.beginTime = time;
2735
+ this.frames++;
2736
+ if (time - this.prevTime >= 1000) {
2737
+ this.viewer.info.performance.fps = Math.round((this.frames * 1000) / (time - this.prevTime));
2738
+ this.prevTime = time;
2739
+ this.frames = 0;
2740
+ }
2741
+ };
2742
+ this.viewer = viewer;
2743
+ this.startTime = 0;
2744
+ this.beginTime = performance.now();
2745
+ this.prevTime = performance.now();
2746
+ this.frames = 0;
2747
+ this.viewer.addEventListener("initialize", this.initialize);
2748
+ this.viewer.addEventListener("clear", this.clear);
2749
+ this.viewer.addEventListener("optionschange", this.optionsChange);
2750
+ this.viewer.addEventListener("geometrystart", this.geometryStart);
2751
+ this.viewer.addEventListener("databasechunk", this.databaseChunk);
2752
+ this.viewer.addEventListener("geometryend", this.geometryEnd);
2753
+ this.viewer.addEventListener("resize", this.resize);
2754
+ this.viewer.addEventListener("render", this.render);
2755
+ this.viewer.addEventListener("animate", this.animate);
2756
+ }
2757
+ dispose() {
2758
+ this.viewer.removeEventListener("initialize", this.initialize);
2759
+ this.viewer.removeEventListener("clear", this.clear);
2760
+ this.viewer.removeEventListener("optionschange", this.optionsChange);
2761
+ this.viewer.removeEventListener("geometrystart", this.geometryStart);
2762
+ this.viewer.removeEventListener("databasechunk", this.databaseChunk);
2763
+ this.viewer.removeEventListener("geometryend", this.geometryEnd);
2764
+ this.viewer.removeEventListener("resize", this.resize);
2765
+ this.viewer.removeEventListener("render", this.render);
2766
+ this.viewer.addEventListener("animate", this.animate);
2767
+ }
2768
+ }
2769
+
2505
2770
  class RenderLoopComponent {
2506
2771
  constructor(viewer) {
2507
2772
  this.animate = (time = 0) => {
@@ -2749,10 +3014,11 @@ class SelectionComponent {
2749
3014
  const upPosition = this.getMousePosition(event, new Vector2());
2750
3015
  if (upPosition.distanceTo(this.downPosition) !== 0)
2751
3016
  return;
3017
+ const snapper = new Snapper(this.viewer.camera, this.viewer.renderer, this.viewer.canvas);
2752
3018
  let intersections = [];
2753
3019
  this.viewer.models.forEach((model) => {
2754
3020
  const objects = model.getVisibleObjects();
2755
- const intersects = this.getPointerIntersects(upPosition, objects);
3021
+ const intersects = snapper.getPointerIntersects(upPosition, objects);
2756
3022
  if (intersects.length > 0)
2757
3023
  intersections.push({ ...intersects[0], model });
2758
3024
  });
@@ -2770,6 +3036,7 @@ class SelectionComponent {
2770
3036
  }
2771
3037
  this.viewer.update();
2772
3038
  this.viewer.emitEvent({ type: "select", data: undefined, handles: this.viewer.getSelected() });
3039
+ this.viewer.emitEvent({ type: "select2", data: undefined, handles: this.viewer.getSelected2() });
2773
3040
  };
2774
3041
  this.onDoubleClick = (event) => {
2775
3042
  if (event.button !== 0)
@@ -2780,7 +3047,6 @@ class SelectionComponent {
2780
3047
  this.highlighter = this.viewer.getComponent("HighlighterComponent");
2781
3048
  };
2782
3049
  this.viewer = viewer;
2783
- this.raycaster = new Raycaster();
2784
3050
  this.downPosition = new Vector2();
2785
3051
  this.viewer.addEventListener("pointerdown", this.onPointerDown);
2786
3052
  this.viewer.addEventListener("pointerup", this.onPointerUp);
@@ -2796,26 +3062,6 @@ class SelectionComponent {
2796
3062
  getMousePosition(event, target) {
2797
3063
  return target.set(event.clientX, event.clientY);
2798
3064
  }
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
3065
  select(objects, model) {
2820
3066
  if (!model) {
2821
3067
  this.viewer.models.forEach((model) => this.select(objects, model));
@@ -3007,6 +3253,7 @@ components.registerComponent("ExtentsComponent", (viewer) => new ExtentsComponen
3007
3253
  components.registerComponent("CameraComponent", (viewer) => new CameraComponent(viewer));
3008
3254
  components.registerComponent("BackgroundComponent", (viewer) => new BackgroundComponent(viewer));
3009
3255
  components.registerComponent("LightComponent", (viewer) => new LightComponent(viewer));
3256
+ components.registerComponent("InfoComponent", (viewer) => new InfoComponent(viewer));
3010
3257
  components.registerComponent("ResizeCanvasComponent", (viewer) => new ResizeCanvasComponent(viewer));
3011
3258
  components.registerComponent("RenderLoopComponent", (viewer) => new RenderLoopComponent(viewer));
3012
3259
  components.registerComponent("HighlighterComponent", (viewer) => new HighlighterComponent(viewer));
@@ -3014,48 +3261,8 @@ components.registerComponent("SelectionComponent", (viewer) => new SelectionComp
3014
3261
  components.registerComponent("WCSHelperComponent", (viewer) => new WCSHelperComponent(viewer));
3015
3262
  components.registerComponent("ResetComponent", (viewer) => new ResetComponent(viewer));
3016
3263
 
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
3264
  class ModelImpl {
3057
3265
  constructor(scene) {
3058
- this.handle = "1";
3059
3266
  this.scene = scene;
3060
3267
  }
3061
3268
  dispose() {
@@ -3075,6 +3282,144 @@ class ModelImpl {
3075
3282
  this.scene.traverse(disposeObject);
3076
3283
  this.scene.clear();
3077
3284
  }
3285
+ getUnits() {
3286
+ return "Meters";
3287
+ }
3288
+ getUnitScale() {
3289
+ return convertUnits(this.getUnits(), "Meters", 1);
3290
+ }
3291
+ getUnitString() {
3292
+ return getDisplayUnit(this.getUnits());
3293
+ }
3294
+ getPrecision() {
3295
+ return 2;
3296
+ }
3297
+ getInfo() {
3298
+ const geometries = new Set();
3299
+ const materials = new Set();
3300
+ const textures = new Set();
3301
+ let totalObjects = 0;
3302
+ let totalTriangles = 0;
3303
+ let totalPoints = 0;
3304
+ let totalLines = 0;
3305
+ let totalEdges = 0;
3306
+ let geometryBytes = 0;
3307
+ let textureBytes = 0;
3308
+ this.scene.traverse((object) => {
3309
+ totalObjects++;
3310
+ if (object.geometry) {
3311
+ const geometry = object.geometry;
3312
+ if (!geometries.has(geometry)) {
3313
+ geometries.add(geometry);
3314
+ if (geometry.attributes) {
3315
+ for (const name in geometry.attributes) {
3316
+ const attribute = geometry.attributes[name];
3317
+ if (attribute && attribute.array) {
3318
+ geometryBytes += attribute.array.byteLength;
3319
+ }
3320
+ }
3321
+ }
3322
+ if (geometry.index && geometry.index.array) {
3323
+ geometryBytes += geometry.index.array.byteLength;
3324
+ }
3325
+ }
3326
+ if (geometry.index) {
3327
+ const indexCount = geometry.index.count;
3328
+ if (object.isLine || object.isLineSegments) {
3329
+ totalLines += indexCount / 2;
3330
+ }
3331
+ else if (object.isPoints) {
3332
+ totalPoints += indexCount;
3333
+ }
3334
+ else {
3335
+ totalTriangles += indexCount / 3;
3336
+ }
3337
+ }
3338
+ else if (geometry.attributes && geometry.attributes.position) {
3339
+ const positionCount = geometry.attributes.position.count;
3340
+ if (object.isLine || object.isLineSegments) {
3341
+ totalLines += positionCount / 2;
3342
+ }
3343
+ else if (object.isPoints) {
3344
+ totalPoints += positionCount;
3345
+ }
3346
+ else {
3347
+ totalTriangles += positionCount / 3;
3348
+ }
3349
+ }
3350
+ if (object.isLineSegments && geometry.attributes.position) {
3351
+ totalEdges += geometry.attributes.position.count / 2;
3352
+ }
3353
+ }
3354
+ if (object.material) {
3355
+ const materialsArray = Array.isArray(object.material) ? object.material : [object.material];
3356
+ materialsArray.forEach((material) => {
3357
+ materials.add(material);
3358
+ if (material.map && !textures.has(material.map)) {
3359
+ textures.add(material.map);
3360
+ textureBytes += estimateTextureSize(material.map);
3361
+ }
3362
+ const textureProps = [
3363
+ "alphaMap",
3364
+ "aoMap",
3365
+ "bumpMap",
3366
+ "displacementMap",
3367
+ "emissiveMap",
3368
+ "envMap",
3369
+ "lightMap",
3370
+ "metalnessMap",
3371
+ "normalMap",
3372
+ "roughnessMap",
3373
+ "specularMap",
3374
+ "clearcoatMap",
3375
+ "clearcoatNormalMap",
3376
+ "clearcoatRoughnessMap",
3377
+ "iridescenceMap",
3378
+ "sheenColorMap",
3379
+ "sheenRoughnessMap",
3380
+ "thicknessMap",
3381
+ "transmissionMap",
3382
+ "anisotropyMap",
3383
+ "gradientMap",
3384
+ ];
3385
+ textureProps.forEach((prop) => {
3386
+ const texture = material[prop];
3387
+ if (texture && !textures.has(texture)) {
3388
+ textures.add(texture);
3389
+ textureBytes += estimateTextureSize(texture);
3390
+ }
3391
+ });
3392
+ });
3393
+ }
3394
+ });
3395
+ function estimateTextureSize(texture) {
3396
+ if (!texture.image)
3397
+ return 0;
3398
+ const width = texture.image.width || 0;
3399
+ const height = texture.image.height || 0;
3400
+ const bytesPerPixel = 4;
3401
+ const mipmapMultiplier = texture.generateMipmaps ? 1.33 : 1;
3402
+ return width * height * bytesPerPixel * mipmapMultiplier;
3403
+ }
3404
+ const info = new Info();
3405
+ info.scene.objects = totalObjects;
3406
+ info.scene.triangles = Math.floor(totalTriangles);
3407
+ info.scene.points = Math.floor(totalPoints);
3408
+ info.scene.lines = Math.floor(totalLines);
3409
+ info.scene.edges = Math.floor(totalEdges);
3410
+ info.memory.geometries = geometries.size;
3411
+ info.memory.geometryBytes = geometryBytes;
3412
+ info.memory.textures = textures.size;
3413
+ info.memory.textureBytes = Math.floor(textureBytes);
3414
+ info.memory.materials = materials.size;
3415
+ info.memory.totalEstimatedGpuBytes = geometryBytes + Math.floor(textureBytes);
3416
+ info.optimizedScene.objects = info.scene.objects;
3417
+ info.optimizedScene.triangles = info.scene.triangles;
3418
+ info.optimizedScene.points = info.scene.points;
3419
+ info.optimizedScene.lines = info.scene.lines;
3420
+ info.optimizedScene.edges = info.scene.edges;
3421
+ return info;
3422
+ }
3078
3423
  getExtents(target) {
3079
3424
  this.scene.traverseVisible((object) => !object.children.length && target.expandByObject(object));
3080
3425
  return target;
@@ -3097,27 +3442,45 @@ class ModelImpl {
3097
3442
  }
3098
3443
  return false;
3099
3444
  }
3445
+ hasHandle(handle) {
3446
+ return !handle.includes(":") || handle.split(":", 1)[0] === this.id + "";
3447
+ }
3100
3448
  getOwnObjects(objects) {
3101
3449
  if (!Array.isArray(objects))
3102
3450
  objects = [objects];
3103
3451
  return objects.filter((object) => this.hasObject(object));
3104
3452
  }
3453
+ getOwnHandles(handles) {
3454
+ if (!Array.isArray(handles))
3455
+ handles = [handles];
3456
+ return handles.filter((handle) => this.hasHandle(handle));
3457
+ }
3105
3458
  getObjectsByHandles(handles) {
3106
- const handleSet = new Set(handles);
3459
+ const ownHandles = this.getOwnHandles(handles);
3460
+ if (ownHandles.length === 0)
3461
+ return [];
3462
+ const handleSet = new Set(ownHandles.map((handle) => handle.slice(handle.indexOf(":") + 1)));
3107
3463
  const objects = [];
3108
- this.scene.traverse((object) => handleSet.has(object.userData.handle) && objects.push(object));
3464
+ this.scene.traverse((object) => {
3465
+ const handle = object.userData.handle;
3466
+ if (handle && handleSet.has(handle))
3467
+ objects.push(object);
3468
+ });
3109
3469
  return objects;
3110
3470
  }
3111
3471
  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);
3472
+ const ownObjects = this.getOwnObjects(objects);
3473
+ if (ownObjects.length === 0)
3474
+ return [];
3475
+ const handleSet = new Set();
3476
+ ownObjects.forEach((object) => {
3477
+ const handle = object.userData.handle;
3478
+ if (handle)
3479
+ handleSet.add(`${this.id}:${handle}`);
3480
+ });
3481
+ return Array.from(handleSet);
3117
3482
  }
3118
3483
  hideObjects(objects) {
3119
- if (!Array.isArray(objects))
3120
- objects = [objects];
3121
3484
  this.getOwnObjects(objects).forEach((object) => (object.visible = false));
3122
3485
  return this;
3123
3486
  }
@@ -3133,8 +3496,6 @@ class ModelImpl {
3133
3496
  return this;
3134
3497
  }
3135
3498
  showObjects(objects) {
3136
- if (!Array.isArray(objects))
3137
- objects = [objects];
3138
3499
  this.getOwnObjects(objects).forEach((object) => {
3139
3500
  object.visible = true;
3140
3501
  object.traverseAncestors((parent) => (parent.visible = true));
@@ -3184,49 +3545,31 @@ class ModelImpl {
3184
3545
  }
3185
3546
  object.children.forEach((x) => explodeObject(x, depth + 1));
3186
3547
  }
3187
- explodeObject(this.scene, 0);
3188
- this.scene.updateMatrixWorld();
3189
- return this;
3190
- }
3191
- }
3192
-
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 });
3548
+ explodeObject(this.scene, 0);
3549
+ this.scene.updateMatrixWorld();
3225
3550
  return this;
3226
3551
  }
3227
3552
  }
3228
3553
 
3229
3554
  class DynamicModelImpl extends ModelImpl {
3555
+ getInfo() {
3556
+ const stats = this.gltfLoader.getStats();
3557
+ const info = new Info();
3558
+ info.scene.objects = stats.scene.beforeOptimization.objects;
3559
+ info.scene.triangles = stats.scene.beforeOptimization.triangles;
3560
+ info.scene.lines = stats.scene.beforeOptimization.lines;
3561
+ info.scene.edges = stats.scene.beforeOptimization.edges;
3562
+ info.optimizedScene.objects = stats.scene.afterOptimization.objects;
3563
+ info.optimizedScene.triangles = stats.scene.afterOptimization.triangles;
3564
+ info.optimizedScene.lines = stats.scene.afterOptimization.lines;
3565
+ info.optimizedScene.edges = stats.scene.afterOptimization.edges;
3566
+ info.memory.geometries = stats.memory.geometries.count;
3567
+ info.memory.geometryBytes = stats.memory.geometries.bytes;
3568
+ info.memory.textures = stats.memory.textures.count;
3569
+ info.memory.materials = stats.memory.materials.count;
3570
+ info.memory.totalEstimatedGpuBytes = stats.memory.totalEstimatedGpuBytes;
3571
+ return info;
3572
+ }
3230
3573
  getExtents(target) {
3231
3574
  return target.union(this.gltfLoader.getTotalGeometryExtent());
3232
3575
  }
@@ -3244,31 +3587,40 @@ class DynamicModelImpl extends ModelImpl {
3244
3587
  return this.gltfLoader.originalObjects.has(object);
3245
3588
  }
3246
3589
  getObjectsByHandles(handles) {
3247
- const handlesSet = new Set(handles);
3590
+ const ownHandles = this.getOwnHandles(handles);
3591
+ if (ownHandles.length === 0)
3592
+ return [];
3593
+ const handlesSet = new Set(ownHandles);
3248
3594
  const objects = [];
3249
3595
  handlesSet.forEach((handle) => {
3250
- const handle2 = `${this.modelId}_${handle}`;
3251
- const handles = this.gltfLoader.handleToObjects.get(handle2) || [];
3252
- objects.push(...Array.from(handles));
3596
+ objects.push(...this.gltfLoader.getObjectsByHandle(handle));
3253
3597
  });
3254
3598
  return objects;
3255
3599
  }
3256
3600
  getHandlesByObjects(objects) {
3257
- const handles = super.getHandlesByObjects(objects);
3258
- return handles.map((x) => x.split("_").pop());
3601
+ const ownObjects = this.getOwnObjects(objects);
3602
+ if (ownObjects.length === 0)
3603
+ return [];
3604
+ const handleSet = new Set();
3605
+ ownObjects.forEach((object) => {
3606
+ const handle = object.userData.handle;
3607
+ if (handle)
3608
+ handleSet.add(handle);
3609
+ });
3610
+ return Array.from(handleSet);
3259
3611
  }
3260
3612
  hideObjects(objects) {
3261
- const handles = super.getHandlesByObjects(objects);
3613
+ const handles = this.getHandlesByObjects(objects);
3262
3614
  this.gltfLoader.hideObjects(handles);
3263
3615
  return this;
3264
3616
  }
3265
3617
  isolateObjects(objects) {
3266
- const handles = super.getHandlesByObjects(objects);
3618
+ const handles = this.getHandlesByObjects(objects);
3267
3619
  this.gltfLoader.isolateObjects(new Set(handles));
3268
3620
  return this;
3269
3621
  }
3270
3622
  showObjects(objects) {
3271
- const handles = super.getHandlesByObjects(objects);
3623
+ const handles = this.getHandlesByObjects(objects);
3272
3624
  this.gltfLoader.showObjects(handles);
3273
3625
  return this;
3274
3626
  }
@@ -3332,11 +3684,16 @@ class GltfStructure {
3332
3684
  this.materials = new Map();
3333
3685
  this.textureCache = new Map();
3334
3686
  this.materialCache = new Map();
3687
+ this.uri = "";
3688
+ this._nextObjectId = 0;
3689
+ this.loadingAborted = false;
3690
+ this.criticalError = null;
3335
3691
  }
3336
3692
  async initialize(loader) {
3337
3693
  this.json = await this.loadController.loadJson();
3338
3694
  this.baseUrl = await this.loadController.baseUrl();
3339
3695
  this.loader = loader;
3696
+ this.uri = this.json.buffers[0].uri || "";
3340
3697
  }
3341
3698
  clear() {
3342
3699
  this.json = null;
@@ -3352,12 +3709,18 @@ class GltfStructure {
3352
3709
  this.materials.clear();
3353
3710
  this.activeChunkLoads = 0;
3354
3711
  this.chunkQueue = [];
3712
+ this.loadingAborted = false;
3713
+ this.criticalError = null;
3355
3714
  }
3356
3715
  getJson() {
3357
3716
  return this.json;
3358
3717
  }
3359
3718
  scheduleRequest(request) {
3360
3719
  return new Promise((resolve, reject) => {
3720
+ if (this.loadingAborted) {
3721
+ reject(this.criticalError || new Error("Structure loading has been aborted due to critical error"));
3722
+ return;
3723
+ }
3361
3724
  this.pendingRequests.push({
3362
3725
  ...request,
3363
3726
  _resolve: resolve,
@@ -3365,6 +3728,41 @@ class GltfStructure {
3365
3728
  });
3366
3729
  });
3367
3730
  }
3731
+ isCriticalHttpError(error) {
3732
+ if (!error) return false;
3733
+ const status = error.status || error.statusCode || error.code;
3734
+ if (typeof status === "number") {
3735
+ return status >= 400 && status < 600;
3736
+ }
3737
+ if (error.message) {
3738
+ const match = error.message.match(/HTTP\s+(\d{3})/i);
3739
+ if (match) {
3740
+ const code = parseInt(match[1], 10);
3741
+ return code >= 400 && code < 600;
3742
+ }
3743
+ }
3744
+ return false;
3745
+ }
3746
+ abortLoading(error) {
3747
+ if (this.loadingAborted) {
3748
+ return;
3749
+ }
3750
+ this.loadingAborted = true;
3751
+ this.criticalError = error;
3752
+ const requests = [...this.pendingRequests];
3753
+ this.pendingRequests = [];
3754
+ for (const req of requests) {
3755
+ if (req._reject) {
3756
+ req._reject(error);
3757
+ }
3758
+ }
3759
+ console.error(
3760
+ `❌ Critical error for structure "${this.id}". All further loading aborted.`,
3761
+ `\n Error: ${error.message || error}`,
3762
+ `\n Rejected ${requests.length} pending chunk requests.`
3763
+ );
3764
+ throw error;
3765
+ }
3368
3766
  async flushBufferRequests() {
3369
3767
  if (!this.pendingRequests || this.pendingRequests.length === 0) return;
3370
3768
  const requests = [...this.pendingRequests];
@@ -3427,10 +3825,16 @@ class GltfStructure {
3427
3825
  }
3428
3826
  }
3429
3827
  const promises = finalRanges.map(async (range, index) => {
3828
+ if (this.loadingAborted) {
3829
+ for (const req of range.requests) {
3830
+ req._reject(this.criticalError || new Error("Structure loading aborted"));
3831
+ }
3832
+ return;
3833
+ }
3430
3834
  await this.loader.waitForChunkSlot();
3431
3835
  try {
3432
3836
  const length = range.end - range.start;
3433
- const buffer = await this.loadController.loadBinaryData([{ offset: range.start, length }]);
3837
+ const buffer = await this.loadController.loadBinaryData([{ offset: range.start, length }], this.uri);
3434
3838
  for (const req of range.requests) {
3435
3839
  const relOffset = req.offset - range.start;
3436
3840
  try {
@@ -3443,7 +3847,11 @@ class GltfStructure {
3443
3847
  for (const req of range.requests) {
3444
3848
  req._reject(error);
3445
3849
  }
3446
- console.warn(`Failed to load chunk ${index + 1}/${finalRanges.length} (${range.start}-${range.end}):`, error);
3850
+ if (this.isCriticalHttpError(error)) {
3851
+ this.abortLoading(error);
3852
+ } else {
3853
+ console.warn(`Failed to load chunk ${index + 1}/${finalRanges.length} (${range.start}-${range.end}):`, error);
3854
+ }
3447
3855
  } finally {
3448
3856
  this.loader.releaseChunkSlot();
3449
3857
  }
@@ -3747,6 +4155,7 @@ class GltfStructure {
3747
4155
  }
3748
4156
  }
3749
4157
 
4158
+ const STRUCTURE_ID_SEPARATOR = ":";
3750
4159
  class DynamicGltfLoader {
3751
4160
  constructor(camera, scene, renderer) {
3752
4161
  this.camera = camera;
@@ -3759,6 +4168,7 @@ class DynamicGltfLoader {
3759
4168
  geometryerror: [],
3760
4169
  update: [],
3761
4170
  geometrymemory: [],
4171
+ optimizationprogress: [],
3762
4172
  };
3763
4173
  this.loadDistance = 100;
3764
4174
  this.unloadDistance = 150;
@@ -3800,7 +4210,6 @@ class DynamicGltfLoader {
3800
4210
  this.hiddenHandles = new Set();
3801
4211
  this.newOptimizedObjects = new Set();
3802
4212
  this.oldOptimizeObjects = new Set();
3803
- this.maxConcurrentChunks = 8;
3804
4213
  this.activeChunkLoads = 0;
3805
4214
  this.chunkQueue = [];
3806
4215
  this.objectIdToIndex = new Map();
@@ -3809,6 +4218,7 @@ class DynamicGltfLoader {
3809
4218
  this.maxConcurrentChunks = 6;
3810
4219
  this.mergedObjectMap = new Map();
3811
4220
  this.mergedGeometryVisibility = new Map();
4221
+ this._webglInfoCache = null;
3812
4222
  }
3813
4223
  setVisibleEdges(visible) {
3814
4224
  this.visibleEdges = visible;
@@ -3912,6 +4322,123 @@ class DynamicGltfLoader {
3912
4322
  this.updateMemoryIndicator();
3913
4323
  console.log(`Final memory usage: ${Math.round(currentMemoryUsage / (1024 * 1024))}MB`);
3914
4324
  }
4325
+ getStats() {
4326
+ let totalObjects = 0;
4327
+ let renderedObjects = 0;
4328
+ let totalTriangles = 0;
4329
+ let renderedTriangles = 0;
4330
+ let totalLines = 0;
4331
+ let renderedLines = 0;
4332
+ let totalEdges = 0;
4333
+ let renderedEdges = 0;
4334
+ this.scene.traverse((object) => {
4335
+ totalObjects++;
4336
+ const geometry = object.geometry;
4337
+ if (!geometry) return;
4338
+ let triCount = 0;
4339
+ if (geometry.index) {
4340
+ triCount = Math.floor(geometry.index.count / 3);
4341
+ } else if (geometry.attributes && geometry.attributes.position) {
4342
+ triCount = Math.floor(geometry.attributes.position.count / 3);
4343
+ }
4344
+ totalTriangles += triCount;
4345
+ let lineCount = 0;
4346
+ if (geometry.index) {
4347
+ lineCount = Math.floor(geometry.index.count / 2);
4348
+ } else if (geometry.attributes && geometry.attributes.position) {
4349
+ lineCount = Math.floor(geometry.attributes.position.count / 2);
4350
+ }
4351
+ if (object.type === "Line" || object.type === "LineSegments" || object.type === "LineLoop") {
4352
+ if (object.userData.isEdge) {
4353
+ totalEdges += lineCount;
4354
+ } else {
4355
+ totalLines += lineCount;
4356
+ }
4357
+ }
4358
+ if (object.visible !== false) {
4359
+ if (object.isMesh || object.isLine || object.isPoints) {
4360
+ renderedObjects++;
4361
+ if (object.isMesh) {
4362
+ renderedTriangles += triCount;
4363
+ } else if (object.type === "Line" || object.type === "LineSegments" || object.type === "LineLoop") {
4364
+ if (object.userData.isEdge) {
4365
+ renderedEdges += lineCount;
4366
+ } else {
4367
+ renderedLines += lineCount;
4368
+ }
4369
+ }
4370
+ }
4371
+ }
4372
+ });
4373
+ const geometryCount = this.geometryCache ? this.geometryCache.size : 0;
4374
+ const geometryMemoryBytes = Array.from(this.geometryCache?.values?.() || []).reduce((a, b) => a + b, 0);
4375
+ const uniqueMaterialIds = new Set();
4376
+ const uniqueTextureIds = new Set();
4377
+ if (Array.isArray(this.structures)) {
4378
+ for (const structure of this.structures) {
4379
+ console.log(structure.materialCache.values());
4380
+ try {
4381
+ for (const entry of structure.materialCache.values()) {
4382
+ if (entry?.mesh?.uuid) uniqueMaterialIds.add(entry.mesh.uuid);
4383
+ if (entry?.points?.uuid) uniqueMaterialIds.add(entry.points.uuid);
4384
+ if (entry?.lines?.uuid) uniqueMaterialIds.add(entry.lines.uuid);
4385
+ }
4386
+ } catch (exp) {
4387
+ console.error("Error adding material to uniqueMaterialIds", exp);
4388
+ }
4389
+ }
4390
+ }
4391
+ const materialCount = uniqueMaterialIds.size;
4392
+ const textureCount = uniqueTextureIds.size;
4393
+ const estimatedGpuMemoryBytes = geometryMemoryBytes;
4394
+ if (!this._webglInfoCache) {
4395
+ try {
4396
+ const gl = this.renderer.getContext();
4397
+ const dbgInfo = gl.getExtension("WEBGL_debug_renderer_info");
4398
+ if (dbgInfo) {
4399
+ const rendererStr = gl.getParameter(dbgInfo.UNMASKED_RENDERER_WEBGL);
4400
+ const vendorStr = gl.getParameter(dbgInfo.UNMASKED_VENDOR_WEBGL);
4401
+ this._webglInfoCache = { renderer: rendererStr, vendor: vendorStr };
4402
+ } else {
4403
+ this._webglInfoCache = { renderer: null, vendor: null };
4404
+ }
4405
+ } catch (e) {
4406
+ console.error("Error getting webgl info", e);
4407
+ this._webglInfoCache = { renderer: null, vendor: null };
4408
+ }
4409
+ }
4410
+ const size = new Vector2();
4411
+ if (this.renderer && this.renderer.getSize) {
4412
+ this.renderer.getSize(size);
4413
+ }
4414
+ return {
4415
+ scene: {
4416
+ beforeOptimization: {
4417
+ objects: totalObjects - renderedObjects,
4418
+ triangles: totalTriangles - renderedTriangles,
4419
+ lines: totalLines - renderedLines,
4420
+ edges: totalEdges - renderedEdges,
4421
+ },
4422
+ afterOptimization: {
4423
+ objects: renderedObjects,
4424
+ triangles: renderedTriangles,
4425
+ lines: renderedLines,
4426
+ edges: renderedEdges,
4427
+ },
4428
+ },
4429
+ memory: {
4430
+ geometries: { count: geometryCount, bytes: geometryMemoryBytes },
4431
+ textures: { count: textureCount },
4432
+ materials: { count: materialCount },
4433
+ totalEstimatedGpuBytes: estimatedGpuMemoryBytes,
4434
+ },
4435
+ system: {
4436
+ webglRenderer: this._webglInfoCache?.renderer || "",
4437
+ webglVendor: this._webglInfoCache?.vendor || "",
4438
+ viewport: { width: size.x || 0, height: size.y || 0 },
4439
+ },
4440
+ };
4441
+ }
3915
4442
  async loadNode(nodeId, onLoadFinishCb) {
3916
4443
  const node = this.nodes.get(nodeId);
3917
4444
  if (!node || node.loaded || node.loading) return;
@@ -4082,7 +4609,7 @@ class DynamicGltfLoader {
4082
4609
  if (node.handle) {
4083
4610
  mesh.userData.handle = node.handle;
4084
4611
  } else {
4085
- mesh.userData.handle = `${node.structure.id}_${mesh.userData.handle}`;
4612
+ mesh.userData.handle = this.getFullHandle(node.structure.id, mesh.userData.handle);
4086
4613
  }
4087
4614
  if (mesh.material.name === "edges") {
4088
4615
  mesh.userData.isEdge = true;
@@ -4115,10 +4642,14 @@ class DynamicGltfLoader {
4115
4642
  onLoadFinishCb();
4116
4643
  }
4117
4644
  } catch (error) {
4118
- if (error.name !== "AbortError") {
4119
- console.error(`Error loading node ${nodeId}:`, error);
4120
- }
4121
4645
  node.loading = false;
4646
+ if (error.name === "AbortError") {
4647
+ return;
4648
+ }
4649
+ if (node.structure && node.structure.loadingAborted) {
4650
+ return;
4651
+ }
4652
+ console.error(`Error loading node ${nodeId}:`, error);
4122
4653
  }
4123
4654
  }
4124
4655
  unloadNode(nodeId) {
@@ -4223,12 +4754,15 @@ class DynamicGltfLoader {
4223
4754
  })),
4224
4755
  });
4225
4756
  }
4757
+ getFullHandle(structureId, originalHandle) {
4758
+ return `${structureId}${STRUCTURE_ID_SEPARATOR}${originalHandle}`;
4759
+ }
4226
4760
  async processNodeHierarchy(structure, nodeId, parentGroup) {
4227
4761
  const nodeDef = structure.json.nodes[nodeId];
4228
4762
  let nodeGroup = null;
4229
4763
  let handle = null;
4230
4764
  if (nodeDef.extras?.handle) {
4231
- handle = `${structure.id}_${nodeDef.extras.handle}`;
4765
+ handle = this.getFullHandle(structure.id, nodeDef.extras.handle);
4232
4766
  }
4233
4767
  if (nodeDef.camera !== undefined) {
4234
4768
  const camera = this.loadCamera(structure, nodeDef.camera, nodeDef);
@@ -4245,7 +4779,7 @@ class DynamicGltfLoader {
4245
4779
  if (nodeDef.extras) {
4246
4780
  nodeGroup.userData = { ...nodeDef.extras };
4247
4781
  if (nodeGroup.userData.handle) {
4248
- nodeGroup.userData.handle = `${structure.id}_${nodeGroup.userData.handle}`;
4782
+ nodeGroup.userData.handle = this.getFullHandle(structure.id, nodeGroup.userData.handle);
4249
4783
  }
4250
4784
  }
4251
4785
  if (nodeDef.matrix) {
@@ -4290,7 +4824,7 @@ class DynamicGltfLoader {
4290
4824
  this.edgeNodes.push(uniqueNodeId);
4291
4825
  }
4292
4826
  if (meshDef.extras && meshDef.extras.handle) {
4293
- handle = `${structure.id}_${meshDef.extras.handle}`;
4827
+ handle = this.getFullHandle(structure.id, meshDef.extras.handle);
4294
4828
  }
4295
4829
  this.nodes.set(uniqueNodeId, {
4296
4830
  position: nodeGroup ? nodeGroup.position.clone() : new Vector3().setFromMatrixPosition(nodeMatrix),
@@ -4303,7 +4837,7 @@ class DynamicGltfLoader {
4303
4837
  structure,
4304
4838
  extras: nodeDef.extras,
4305
4839
  geometryExtents,
4306
- handle,
4840
+ handle: handle || this.getFullHandle(structure.id, structure._nextObjectId++),
4307
4841
  });
4308
4842
  }
4309
4843
  if (nodeDef.children) {
@@ -4376,12 +4910,12 @@ class DynamicGltfLoader {
4376
4910
  }
4377
4911
  }
4378
4912
  async loadNodes() {
4379
- console.time("process nodes");
4913
+ console.time("Process nodes");
4380
4914
  await this.processNodes();
4381
- console.timeEnd("process nodes");
4382
- console.time("optimize scene");
4915
+ console.timeEnd("Process nodes");
4916
+ console.time("Optimize scene");
4383
4917
  await this.optimizeScene();
4384
- console.timeEnd("optimize scene");
4918
+ console.timeEnd("Optimize scene");
4385
4919
  }
4386
4920
  cleanupPartialLoad() {
4387
4921
  this.nodesToLoad.forEach((nodeId) => {
@@ -4702,7 +5236,7 @@ class DynamicGltfLoader {
4702
5236
  this.handleToObjects.set(fullHandle, new Set());
4703
5237
  }
4704
5238
  this.handleToObjects.get(fullHandle).add(object);
4705
- object.userData.structureId = object.userData.handle.split("_")[0];
5239
+ object.userData.structureId = object.userData.handle.split(STRUCTURE_ID_SEPARATOR)[0];
4706
5240
  }
4707
5241
  getObjectsByHandle(handle) {
4708
5242
  if (!handle) return [];
@@ -4768,10 +5302,28 @@ class DynamicGltfLoader {
4768
5302
  }
4769
5303
  this.originalObjects.add(object);
4770
5304
  }
4771
- optimizeScene() {
5305
+ yieldToUI() {
5306
+ return new Promise((resolve) => {
5307
+ requestAnimationFrame(() => {
5308
+ setTimeout(resolve, 0);
5309
+ });
5310
+ });
5311
+ }
5312
+ async optimizeScene() {
5313
+ console.log("Starting scene optimization...");
5314
+ this.dispatchEvent("optimizationprogress", {
5315
+ phase: "start",
5316
+ progress: 0,
5317
+ message: "Starting optimization...",
5318
+ });
4772
5319
  this.originalObjects.clear();
4773
5320
  this.originalObjectsToSelection.clear();
4774
5321
  const structureGroups = new Map();
5322
+ this.dispatchEvent("optimizationprogress", {
5323
+ phase: "collecting",
5324
+ progress: 5,
5325
+ message: "Collecting scene objects...",
5326
+ });
4775
5327
  this.scene.traverse((object) => {
4776
5328
  if (object.userData.structureId) {
4777
5329
  const structureId = object.userData.structureId;
@@ -4800,16 +5352,44 @@ class DynamicGltfLoader {
4800
5352
  }
4801
5353
  }
4802
5354
  });
5355
+ let processedGroups = 0;
5356
+ const totalGroups = structureGroups.size;
5357
+ this.dispatchEvent("optimizationprogress", {
5358
+ phase: "merging",
5359
+ progress: 10,
5360
+ message: `Merging ${totalGroups} structure groups...`,
5361
+ current: 0,
5362
+ total: totalGroups,
5363
+ });
4803
5364
  for (const group of structureGroups.values()) {
4804
5365
  group.mapMeshes.clear();
4805
5366
  group.mapLines.clear();
4806
5367
  group.mapLineSegments.clear();
4807
5368
  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);
5369
+ await this.mergeMeshGroups(group.meshes, group.rootGroup);
5370
+ await this.yieldToUI();
5371
+ await this.mergeLineGroups(group.lines, group.rootGroup);
5372
+ await this.yieldToUI();
5373
+ await this.mergeLineSegmentGroups(group.lineSegments, group.rootGroup);
5374
+ await this.yieldToUI();
5375
+ await this.mergePointsGroups(group.points, group.rootGroup);
5376
+ processedGroups++;
5377
+ const progress = 10 + Math.round((processedGroups / totalGroups) * 80);
5378
+ this.dispatchEvent("optimizationprogress", {
5379
+ phase: "merging",
5380
+ progress,
5381
+ message: `Processing structure ${processedGroups}/${totalGroups}...`,
5382
+ current: processedGroups,
5383
+ total: totalGroups,
5384
+ });
5385
+ console.log(`Optimization progress: ${processedGroups}/${totalGroups} structure groups processed (${progress}%)`);
5386
+ await this.yieldToUI();
4812
5387
  }
5388
+ this.dispatchEvent("optimizationprogress", {
5389
+ phase: "finalizing",
5390
+ progress: 95,
5391
+ message: "Finalizing optimization...",
5392
+ });
4813
5393
  this.originalObjects.forEach((obj) => {
4814
5394
  obj.visible = false;
4815
5395
  if (!(obj instanceof Points) && !obj.userData.isEdge) {
@@ -4818,9 +5398,15 @@ class DynamicGltfLoader {
4818
5398
  });
4819
5399
  this.initializeObjectVisibility();
4820
5400
  console.log(`Optimization complete. Total objects: ${this.maxObjectId}`);
5401
+ this.dispatchEvent("optimizationprogress", {
5402
+ phase: "complete",
5403
+ progress: 100,
5404
+ message: `Optimization complete! ${this.maxObjectId} objects processed.`,
5405
+ });
4821
5406
  this.dispatchEvent("update");
4822
5407
  }
4823
- mergeMeshGroups(materialGroups, rootGroup) {
5408
+ async mergeMeshGroups(materialGroups, rootGroup) {
5409
+ let processedGroups = 0;
4824
5410
  for (const group of materialGroups) {
4825
5411
  if (!group.material) {
4826
5412
  console.warn("Skipping mesh group with null material");
@@ -4834,8 +5420,6 @@ class DynamicGltfLoader {
4834
5420
  let currentVertexOffset = 0;
4835
5421
  for (const mesh of group.objects) {
4836
5422
  const geometry = mesh.geometry.clone();
4837
- mesh.updateWorldMatrix(true, false);
4838
- geometry.applyMatrix4(mesh.matrixWorld);
4839
5423
  const handle = mesh.userData.handle;
4840
5424
  if (!this.objectIdToIndex.has(handle)) {
4841
5425
  this.objectIdToIndex.set(handle, this.maxObjectId++);
@@ -4871,6 +5455,7 @@ class DynamicGltfLoader {
4871
5455
  }
4872
5456
  const visibilityMaterial = this.createVisibilityMaterial(group.material);
4873
5457
  const mergedMesh = new Mesh(mergedGeometry, visibilityMaterial);
5458
+ mergedMesh.userData.isOptimized = true;
4874
5459
  rootGroup.add(mergedMesh);
4875
5460
  this.mergedMesh.add(mergedMesh);
4876
5461
  this.optimizedOriginalMap.set(mergedMesh, optimizedObjects);
@@ -4894,6 +5479,10 @@ class DynamicGltfLoader {
4894
5479
  this.handleToOptimizedObjects.set(handle, mergedObjects);
4895
5480
  }
4896
5481
  });
5482
+ processedGroups++;
5483
+ if (processedGroups % 5 === 0) {
5484
+ await this.yieldToUI();
5485
+ }
4897
5486
  } catch (error) {
4898
5487
  console.error("Failed to merge meshes for material:", error);
4899
5488
  group.objects.forEach((mesh) => {
@@ -4902,7 +5491,8 @@ class DynamicGltfLoader {
4902
5491
  }
4903
5492
  }
4904
5493
  }
4905
- mergeLineGroups(materialGroups, rootGroup) {
5494
+ async mergeLineGroups(materialGroups, rootGroup) {
5495
+ let processedGroups = 0;
4906
5496
  for (const group of materialGroups) {
4907
5497
  if (group.objects.length === 0) continue;
4908
5498
  if (!group.material) {
@@ -4921,7 +5511,9 @@ class DynamicGltfLoader {
4921
5511
  let posOffset = 0;
4922
5512
  const indices = [];
4923
5513
  let vertexOffset = 0;
5514
+ let isEdge = false;
4924
5515
  group.objects.forEach((line) => {
5516
+ isEdge = line.userData.isEdge;
4925
5517
  const geometry = line.geometry;
4926
5518
  const positionAttr = geometry.attributes.position;
4927
5519
  const vertexCount = positionAttr.count;
@@ -4934,12 +5526,9 @@ class DynamicGltfLoader {
4934
5526
  vertexCount,
4935
5527
  });
4936
5528
  currentVertexOffset += vertexCount;
4937
- line.updateWorldMatrix(true, false);
4938
- const matrix = line.matrixWorld;
4939
5529
  const vector = new Vector3();
4940
5530
  for (let i = 0; i < vertexCount; i++) {
4941
5531
  vector.fromBufferAttribute(positionAttr, i);
4942
- vector.applyMatrix4(matrix);
4943
5532
  positions[posOffset++] = vector.x;
4944
5533
  positions[posOffset++] = vector.y;
4945
5534
  positions[posOffset++] = vector.z;
@@ -4972,6 +5561,8 @@ class DynamicGltfLoader {
4972
5561
  geometry.setAttribute("visibility", new BufferAttribute(visibilityArray, 1));
4973
5562
  const visibilityMaterial = this.createVisibilityMaterial(group.material);
4974
5563
  const mergedLine = new LineSegments(geometry, visibilityMaterial);
5564
+ mergedLine.userData.isEdge = isEdge;
5565
+ mergedLine.userData.isOptimized = true;
4975
5566
  const mergedObjects = [mergedLine];
4976
5567
  if (this.useVAO) {
4977
5568
  this.createVAO(mergedLine);
@@ -4994,9 +5585,14 @@ class DynamicGltfLoader {
4994
5585
  this.handleToOptimizedObjects.set(handle, mergedObjects);
4995
5586
  }
4996
5587
  });
5588
+ processedGroups++;
5589
+ if (processedGroups % 5 === 0) {
5590
+ await this.yieldToUI();
5591
+ }
4997
5592
  }
4998
5593
  }
4999
- mergeLineSegmentGroups(materialGroups, rootGroup) {
5594
+ async mergeLineSegmentGroups(materialGroups, rootGroup) {
5595
+ let processedGroups = 0;
5000
5596
  for (const group of materialGroups) {
5001
5597
  if (!group.material) {
5002
5598
  console.warn("Skipping line segment group with null material");
@@ -5008,10 +5604,10 @@ class DynamicGltfLoader {
5008
5604
  const handles = new Set();
5009
5605
  const objectMapping = new Map();
5010
5606
  let currentVertexOffset = 0;
5607
+ let isEdge = false;
5011
5608
  for (const line of group.objects) {
5609
+ isEdge = line.userData.isEdge;
5012
5610
  const geometry = line.geometry.clone();
5013
- line.updateWorldMatrix(true, false);
5014
- geometry.applyMatrix4(line.matrixWorld);
5015
5611
  const handle = line.userData.handle;
5016
5612
  if (!this.objectIdToIndex.has(handle)) {
5017
5613
  this.objectIdToIndex.set(handle, this.maxObjectId++);
@@ -5044,6 +5640,8 @@ class DynamicGltfLoader {
5044
5640
  mergedGeometry.setAttribute("visibility", new BufferAttribute(visibilityArray, 1));
5045
5641
  const visibilityMaterial = this.createVisibilityMaterial(group.material);
5046
5642
  const mergedLine = new LineSegments(mergedGeometry, visibilityMaterial);
5643
+ mergedLine.userData.isEdge = isEdge;
5644
+ mergedLine.userData.isOptimized = true;
5047
5645
  if (this.useVAO) {
5048
5646
  this.createVAO(mergedLine);
5049
5647
  }
@@ -5070,6 +5668,10 @@ class DynamicGltfLoader {
5070
5668
  this.handleToOptimizedObjects.set(handle, mergedObjects);
5071
5669
  }
5072
5670
  });
5671
+ processedGroups++;
5672
+ if (processedGroups % 5 === 0) {
5673
+ await this.yieldToUI();
5674
+ }
5073
5675
  } catch (error) {
5074
5676
  console.warn("Failed to merge line segments for material:", error);
5075
5677
  group.objects.forEach((line) => {
@@ -5078,7 +5680,8 @@ class DynamicGltfLoader {
5078
5680
  }
5079
5681
  }
5080
5682
  }
5081
- mergePointsGroups(materialGroups, rootGroup) {
5683
+ async mergePointsGroups(materialGroups, rootGroup) {
5684
+ let processedGroups = 0;
5082
5685
  for (const group of materialGroups) {
5083
5686
  if (!group.material) {
5084
5687
  console.warn("Skipping points group with null material");
@@ -5090,8 +5693,6 @@ class DynamicGltfLoader {
5090
5693
  const handles = new Set();
5091
5694
  for (const points of group.objects) {
5092
5695
  const geometry = points.geometry.clone();
5093
- points.updateWorldMatrix(true, false);
5094
- geometry.applyMatrix4(points.matrixWorld);
5095
5696
  geometries.push(geometry);
5096
5697
  optimizedObjects.push(points);
5097
5698
  handles.add(points.userData.handle);
@@ -5100,6 +5701,7 @@ class DynamicGltfLoader {
5100
5701
  if (geometries.length > 0) {
5101
5702
  const mergedGeometry = mergeGeometries(geometries, false);
5102
5703
  const mergedPoints = new Points(mergedGeometry, group.material);
5704
+ mergedPoints.userData.isOptimized = true;
5103
5705
  if (this.useVAO) {
5104
5706
  this.createVAO(mergedPoints);
5105
5707
  }
@@ -5120,6 +5722,10 @@ class DynamicGltfLoader {
5120
5722
  this.handleToOptimizedObjects.set(handle, mergedObjects);
5121
5723
  }
5122
5724
  });
5725
+ processedGroups++;
5726
+ if (processedGroups % 5 === 0) {
5727
+ await this.yieldToUI();
5728
+ }
5123
5729
  } catch (error) {
5124
5730
  console.warn("Failed to merge points for material:", error);
5125
5731
  group.objects.forEach((points) => {
@@ -5138,7 +5744,6 @@ class DynamicGltfLoader {
5138
5744
  const hasNormals = lineSegmentsArray.some((segment) => segment.geometry.attributes.normal !== undefined);
5139
5745
  lineSegmentsArray.forEach((segment) => {
5140
5746
  const clonedGeometry = segment.geometry.clone();
5141
- segment.updateWorldMatrix(true, false);
5142
5747
  clonedGeometry.applyMatrix4(segment.matrixWorld);
5143
5748
  if (hasNormals && !clonedGeometry.attributes.normal) {
5144
5749
  clonedGeometry.computeVertexNormals();
@@ -5172,6 +5777,7 @@ class DynamicGltfLoader {
5172
5777
  }
5173
5778
  const mergedLine = new LineSegments(finalGeometry, material);
5174
5779
  mergedLine.userData.structureId = structureId;
5780
+ mergedLine.userData.isOptimized = true;
5175
5781
  rootGroup.add(mergedLine);
5176
5782
  this.mergedLineSegments.add(mergedLine);
5177
5783
  lineSegmentsArray.forEach((obj) => {
@@ -5355,8 +5961,232 @@ class DynamicGltfLoader {
5355
5961
  }
5356
5962
  }
5357
5963
 
5358
- class GLTFCloudDynamicLoader {
5964
+ class GLTFLoadingManager extends LoadingManager {
5965
+ constructor(file, params = {}) {
5966
+ super();
5967
+ this.path = "";
5968
+ this.resourcePath = "";
5969
+ this.fileURL = "";
5970
+ this.dataURLs = new Map();
5971
+ this.path = params.path || "";
5972
+ const externalFiles = params.externalFiles || new Map();
5973
+ if (typeof file === "string") {
5974
+ this.fileURL = file;
5975
+ this.resourcePath = LoaderUtils.extractUrlBase(file);
5976
+ }
5977
+ else {
5978
+ externalFiles.forEach((value, key) => (this.fileURL = value === file ? key : this.fileURL));
5979
+ externalFiles.set(this.fileURL, file);
5980
+ }
5981
+ externalFiles.forEach((value, key) => {
5982
+ let dataURL;
5983
+ if (typeof value === "string")
5984
+ dataURL = value;
5985
+ else
5986
+ dataURL = URL.createObjectURL(new Blob([value]));
5987
+ this.dataURLs.set(key, dataURL);
5988
+ });
5989
+ this.setURLModifier((url) => {
5990
+ const key = decodeURI(url)
5991
+ .replace(this.path, "")
5992
+ .replace(this.resourcePath, "")
5993
+ .replace(/^(\.?\/)/, "");
5994
+ const dataURL = this.dataURLs.get(key);
5995
+ return dataURL !== null && dataURL !== void 0 ? dataURL : url;
5996
+ });
5997
+ }
5998
+ dispose() {
5999
+ this.dataURLs.forEach((dataURL) => URL.revokeObjectURL(dataURL));
6000
+ }
6001
+ }
6002
+
6003
+ const BINARY_EXTENSION_HEADER_MAGIC = "glTF";
6004
+ const BINARY_EXTENSION_HEADER_LENGTH = 12;
6005
+ const BINARY_EXTENSION_CHUNK_TYPES = { JSON: 0x4e4f534a, BIN: 0x004e4042 };
6006
+ class GLTFBinaryExtension {
6007
+ constructor(data) {
6008
+ const headerView = new DataView(data, 0, BINARY_EXTENSION_HEADER_LENGTH);
6009
+ const textDecoder = new TextDecoder();
6010
+ const magic = textDecoder.decode(new Uint8Array(data.slice(0, 4)));
6011
+ if (magic !== BINARY_EXTENSION_HEADER_MAGIC) {
6012
+ this.content = textDecoder.decode(data);
6013
+ return;
6014
+ }
6015
+ const header = {
6016
+ magic,
6017
+ version: headerView.getUint32(4, true),
6018
+ length: headerView.getUint32(8, true),
6019
+ };
6020
+ if (header.magic !== BINARY_EXTENSION_HEADER_MAGIC) {
6021
+ throw new Error("Unsupported glTF-Binary header.");
6022
+ }
6023
+ if (header.version < 2.0) {
6024
+ throw new Error("Legacy binary file detected.");
6025
+ }
6026
+ const chunkContentsLength = header.length - BINARY_EXTENSION_HEADER_LENGTH;
6027
+ const chunkView = new DataView(data, BINARY_EXTENSION_HEADER_LENGTH);
6028
+ let chunkIndex = 0;
6029
+ while (chunkIndex < chunkContentsLength) {
6030
+ const chunkLength = chunkView.getUint32(chunkIndex, true);
6031
+ chunkIndex += 4;
6032
+ const chunkType = chunkView.getUint32(chunkIndex, true);
6033
+ chunkIndex += 4;
6034
+ if (chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON) {
6035
+ const contentArray = new Uint8Array(data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength);
6036
+ this.content = textDecoder.decode(contentArray);
6037
+ }
6038
+ else if (chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN) {
6039
+ const byteOffset = BINARY_EXTENSION_HEADER_LENGTH + chunkIndex;
6040
+ this.body = data.slice(byteOffset, byteOffset + chunkLength);
6041
+ }
6042
+ chunkIndex += chunkLength;
6043
+ }
6044
+ if (typeof this.content === "undefined") {
6045
+ throw new Error("JSON content not found.");
6046
+ }
6047
+ }
6048
+ }
6049
+
6050
+ class FetchError extends Error {
6051
+ constructor(status, message) {
6052
+ super(message);
6053
+ this.name = "FetchError";
6054
+ this.status = status;
6055
+ }
6056
+ }
6057
+ class RangesLoader {
6058
+ constructor() {
6059
+ this.requestHeader = {};
6060
+ this.withCredentials = false;
6061
+ this.abortSignal = undefined;
6062
+ }
6063
+ setRequestHeader(requestHeader) {
6064
+ this.requestHeader = requestHeader;
6065
+ }
6066
+ setWithCredentials(withCredentials) {
6067
+ this.withCredentials = withCredentials;
6068
+ }
6069
+ setAbortSignal(abortSignal) {
6070
+ this.abortSignal = abortSignal;
6071
+ }
6072
+ async load(url, ranges) {
6073
+ const init = {
6074
+ headers: {
6075
+ ...this.requestHeader,
6076
+ Range: "bytes=" + ranges.map((x) => `${x.offset}-${x.offset + x.length - 1}`).join(","),
6077
+ },
6078
+ credentials: this.withCredentials ? "include" : "same-origin",
6079
+ signal: this.abortSignal,
6080
+ };
6081
+ const response = await fetch(url, init);
6082
+ if (!response.ok) {
6083
+ throw new FetchError(response.status, `Failed to fetch "${url}", status ${response.status}`);
6084
+ }
6085
+ if (response.status !== 206) {
6086
+ const arrayBuffer = await response.arrayBuffer();
6087
+ return this.extractRanges(arrayBuffer, ranges);
6088
+ }
6089
+ return response.arrayBuffer();
6090
+ }
6091
+ extractRanges(arrayBuffer, ranges) {
6092
+ const totalLength = ranges.reduce((sum, range) => sum + range.length, 0);
6093
+ const result = new Uint8Array(totalLength);
6094
+ let offset = 0;
6095
+ for (const range of ranges) {
6096
+ const chunk = new Uint8Array(arrayBuffer, range.offset, range.length);
6097
+ result.set(chunk, offset);
6098
+ offset += range.length;
6099
+ }
6100
+ return result.buffer;
6101
+ }
6102
+ }
6103
+
6104
+ class GLTFFileDynamicLoader extends Loader {
6105
+ constructor(viewer) {
6106
+ super();
6107
+ this.viewer = viewer;
6108
+ }
6109
+ dispose() {
6110
+ if (this.gltfLoader)
6111
+ this.gltfLoader.clear();
6112
+ if (this.manager)
6113
+ this.manager.dispose();
6114
+ }
6115
+ isSupport(file, format) {
6116
+ return ((typeof file === "string" || file instanceof globalThis.File || file instanceof ArrayBuffer) &&
6117
+ /(gltf|glb)$/i.test(format));
6118
+ }
6119
+ async load(file, format, params) {
6120
+ this.manager = new GLTFLoadingManager(file, params);
6121
+ const scene = new Group();
6122
+ this.gltfLoader = new DynamicGltfLoader(this.viewer.camera, scene, this.viewer.renderer);
6123
+ this.gltfLoader.memoryLimit = this.viewer.options.memoryLimit;
6124
+ this.gltfLoader.visibleEdges = this.viewer.options.edgeModel;
6125
+ const modelImpl = new DynamicModelImpl(scene);
6126
+ modelImpl.id = params.modelId || this.extractFileName(file);
6127
+ modelImpl.gltfLoader = this.gltfLoader;
6128
+ this.gltfLoader.addEventListener("databasechunk", () => {
6129
+ this.viewer.scene.add(scene);
6130
+ this.viewer.models.push(modelImpl);
6131
+ this.viewer.syncOptions();
6132
+ this.viewer.syncOverlay();
6133
+ this.viewer.update();
6134
+ this.viewer.emitEvent({ type: "databasechunk", data: scene, file });
6135
+ });
6136
+ this.gltfLoader.addEventListener("geometryerror", (data) => {
6137
+ this.viewer.emitEvent({ type: "geometryerror", data, file });
6138
+ });
6139
+ this.gltfLoader.addEventListener("update", (data) => {
6140
+ this.viewer.update();
6141
+ });
6142
+ const loadController = {
6143
+ loadJson: async () => {
6144
+ const loader = new FileLoader(this.manager);
6145
+ loader.setPath(this.manager.path);
6146
+ loader.setRequestHeader(params.requestHeader || {});
6147
+ loader.setWithCredentials(params.withCredentials || loader.withCredentials);
6148
+ loader.setResponseType("arraybuffer");
6149
+ const progress = (event) => {
6150
+ const { lengthComputable, loaded, total } = event;
6151
+ const progress = lengthComputable ? loaded / total : 1;
6152
+ this.viewer.emitEvent({ type: "geometryprogress", data: progress, file });
6153
+ };
6154
+ const data = await loader.loadAsync(this.manager.fileURL, progress);
6155
+ const extension = new GLTFBinaryExtension(data);
6156
+ this.gltf = JSON.parse(extension.content);
6157
+ this.bin = extension.body;
6158
+ return this.gltf;
6159
+ },
6160
+ loadBinaryData: (ranges, uri = "") => {
6161
+ const loader = new RangesLoader();
6162
+ loader.setRequestHeader(params.requestHeader || {});
6163
+ loader.setWithCredentials(params.withCredentials || false);
6164
+ loader.setAbortSignal(this.gltfLoader.abortController.signal);
6165
+ if (this.bin)
6166
+ return loader.extractRanges(this.bin, ranges);
6167
+ const path = this.manager.path || this.manager.resourcePath;
6168
+ const url = LoaderUtils.resolveURL(uri, path);
6169
+ return loader.load(this.manager.resolveURL(url), ranges);
6170
+ },
6171
+ baseUrl: () => {
6172
+ const path = this.manager.path || this.manager.resourcePath;
6173
+ return Promise.resolve(path);
6174
+ },
6175
+ };
6176
+ const structure = new GltfStructure(modelImpl.id, loadController);
6177
+ await this.gltfLoader.loadStructure(structure);
6178
+ await this.gltfLoader.loadNodes();
6179
+ return this;
6180
+ }
6181
+ cancel() {
6182
+ if (this.gltfLoader)
6183
+ this.gltfLoader.abortLoading();
6184
+ }
6185
+ }
6186
+
6187
+ class GLTFCloudDynamicLoader extends Loader {
5359
6188
  constructor(viewer) {
6189
+ super();
5360
6190
  this.requestId = 0;
5361
6191
  this.viewer = viewer;
5362
6192
  }
@@ -5371,17 +6201,15 @@ class GLTFCloudDynamicLoader {
5371
6201
  typeof file.downloadResourceRange === "function" &&
5372
6202
  /.gltf$/i.test(file.database));
5373
6203
  }
5374
- async load(model, format, params) {
6204
+ async load(model, format, params = {}) {
5375
6205
  const scene = new Group();
5376
6206
  this.gltfLoader = new DynamicGltfLoader(this.viewer.camera, scene, this.viewer.renderer);
5377
6207
  this.gltfLoader.memoryLimit = this.viewer.options.memoryLimit;
5378
6208
  this.gltfLoader.setVisibleEdges(this.viewer.options.edgeModel);
6209
+ const modelImpl = new DynamicModelImpl(scene);
6210
+ modelImpl.id = model.file.id;
6211
+ modelImpl.gltfLoader = this.gltfLoader;
5379
6212
  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
6213
  this.viewer.scene.add(scene);
5386
6214
  this.viewer.models.push(modelImpl);
5387
6215
  this.viewer.syncOptions();
@@ -5389,10 +6217,6 @@ class GLTFCloudDynamicLoader {
5389
6217
  this.viewer.update();
5390
6218
  this.viewer.emitEvent({ type: "databasechunk", data: scene, file: model.file, model });
5391
6219
  });
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
6220
  this.gltfLoader.addEventListener("geometryerror", (data) => {
5397
6221
  this.viewer.emitEvent({ type: "geometryerror", data, file: model.file, model });
5398
6222
  });
@@ -5402,7 +6226,7 @@ class GLTFCloudDynamicLoader {
5402
6226
  const loadController = {
5403
6227
  loadJson: async () => {
5404
6228
  const progress = (progress) => {
5405
- this.viewer.emitEvent({ type: "geometryprogress", data: progress, file: model });
6229
+ this.viewer.emitEvent({ type: "geometryprogress", data: progress, file: model.file, model });
5406
6230
  };
5407
6231
  const arrayBuffer = await model.downloadResource(model.database, progress, this.gltfLoader.getAbortController().signal);
5408
6232
  const text = new TextDecoder().decode(arrayBuffer);
@@ -5419,7 +6243,7 @@ class GLTFCloudDynamicLoader {
5419
6243
  },
5420
6244
  baseUrl: () => Promise.resolve(`${model.httpClient.serverUrl}${model.path}/`),
5421
6245
  };
5422
- const structure = new GltfStructure(model.id, loadController);
6246
+ const structure = new GltfStructure(modelImpl.id, loadController);
5423
6247
  await this.gltfLoader.loadStructure(structure);
5424
6248
  await this.gltfLoader.loadNodes();
5425
6249
  return this;
@@ -5431,7 +6255,7 @@ class GLTFCloudDynamicLoader {
5431
6255
  }
5432
6256
 
5433
6257
  const loaders = loadersRegistry("threejs");
5434
- loaders.registerLoader("gltf-file", (viewer) => new GLTFFileLoader(viewer));
6258
+ loaders.registerLoader("gltf-file", (viewer) => new GLTFFileDynamicLoader(viewer));
5435
6259
  loaders.registerLoader("gltf-cloud", (viewer) => new GLTFCloudDynamicLoader(viewer));
5436
6260
 
5437
6261
  class SSAARenderPass extends Pass {
@@ -5638,24 +6462,26 @@ class Helpers extends Scene {
5638
6462
  class Viewer extends EventEmitter2 {
5639
6463
  constructor(client) {
5640
6464
  super();
5641
- this._options = new Options(this);
5642
6465
  this.client = client;
5643
- this.canvasEvents = CANVAS_EVENTS;
5644
- this.canvaseventlistener = (event) => this.emit(event);
6466
+ this.options = new Options(this);
5645
6467
  this.loaders = [];
5646
6468
  this.models = [];
6469
+ this.info = new Info();
6470
+ this.canvasEvents = CANVAS_EVENTS.slice();
6471
+ this.canvaseventlistener = (event) => this.emit(event);
5647
6472
  this.selected = [];
5648
6473
  this.extents = new Box3();
5649
- this.target = new Vector3();
6474
+ this.target = new Vector3(0, 0, 0);
5650
6475
  this._activeDragger = null;
5651
6476
  this._components = [];
6477
+ this._renderNeeded = false;
5652
6478
  this._renderTime = 0;
5653
6479
  this.render = this.render.bind(this);
5654
6480
  this.update = this.update.bind(this);
5655
6481
  this._markup = new Markup();
5656
6482
  }
5657
- get options() {
5658
- return this._options;
6483
+ get markup() {
6484
+ return this._markup;
5659
6485
  }
5660
6486
  get draggers() {
5661
6487
  return [...draggers.getDraggers().keys()];
@@ -5663,14 +6489,10 @@ class Viewer extends EventEmitter2 {
5663
6489
  get components() {
5664
6490
  return [...components.getComponents().keys()];
5665
6491
  }
5666
- get markup() {
5667
- return this._markup;
5668
- }
5669
6492
  initialize(canvas, onProgress) {
5670
6493
  this.addEventListener("optionschange", (event) => this.syncOptions(event.data));
5671
6494
  this.scene = new Scene();
5672
6495
  this.helpers = new Helpers();
5673
- this.target = new Vector3(0, 0, 0);
5674
6496
  const pixelRatio = window.devicePixelRatio;
5675
6497
  const rect = canvas.parentElement.getBoundingClientRect();
5676
6498
  const width = rect.width || 1;
@@ -5805,6 +6627,8 @@ class Viewer extends EventEmitter2 {
5805
6627
  const deltaTime = (time - this._renderTime) / 1000;
5806
6628
  this._renderTime = time;
5807
6629
  this._renderNeeded = false;
6630
+ this.renderer.info.autoReset = false;
6631
+ this.renderer.info.reset();
5808
6632
  if (this.options.antialiasing === true || this.options.antialiasing === "msaa") {
5809
6633
  this.renderer.render(this.scene, this.camera);
5810
6634
  this.renderer.render(this.helpers, this.camera);
@@ -5821,21 +6645,25 @@ class Viewer extends EventEmitter2 {
5821
6645
  async open(file, params = {}) {
5822
6646
  if (!this.renderer)
5823
6647
  return this;
5824
- if (params.mode !== "a" && params.mode !== "append") {
6648
+ const mode = params.mode || "file";
6649
+ if (mode !== "assembly" && mode !== "a" && mode !== "append") {
5825
6650
  this.cancel();
5826
6651
  this.clear();
5827
6652
  }
5828
- this.emitEvent({ type: "open", file });
6653
+ this.emitEvent({ type: "open", mode, file });
5829
6654
  let model = file;
5830
6655
  if (model && typeof model.getModels === "function") {
5831
6656
  const models = await model.getModels();
5832
6657
  model = models.find((model) => model.default) || models[0] || file;
5833
6658
  }
6659
+ if (model && typeof model.database === "string") {
6660
+ file = model.file;
6661
+ }
5834
6662
  if (!model)
5835
6663
  throw new Error(`Format not supported`);
5836
6664
  let format = params.format;
5837
- if (!format && typeof model.type === "string")
5838
- format = model.type.split(".").pop();
6665
+ if (!format && typeof file["type"] === "string")
6666
+ format = file["type"].split(".").pop();
5839
6667
  if (!format && typeof file === "string")
5840
6668
  format = file.split(".").pop();
5841
6669
  if (!format && file instanceof globalThis.File)
@@ -5862,7 +6690,7 @@ class Viewer extends EventEmitter2 {
5862
6690
  }
5863
6691
  loadGltfFile(file, externalFiles, params = {}) {
5864
6692
  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" });
6693
+ return this.open(file, { ...params, format: "gltf", externalFiles, mode: "assembly" });
5866
6694
  }
5867
6695
  cancel() {
5868
6696
  this.loaders.forEach((loader) => loader.cancel());
@@ -5882,12 +6710,17 @@ class Viewer extends EventEmitter2 {
5882
6710
  this.models = [];
5883
6711
  this.scene.clear();
5884
6712
  this.helpers.clear();
6713
+ this.extents.makeEmpty();
6714
+ this.target.set(0, 0, 0);
5885
6715
  this.syncOptions();
5886
6716
  this.syncOverlay();
5887
6717
  this.update(true);
5888
6718
  this.emitEvent({ type: "clear" });
5889
6719
  return this;
5890
6720
  }
6721
+ is3D() {
6722
+ return true;
6723
+ }
5891
6724
  syncOptions(options = this.options) {
5892
6725
  if (!this.renderer)
5893
6726
  return;
@@ -5919,9 +6752,15 @@ class Viewer extends EventEmitter2 {
5919
6752
  getSelected() {
5920
6753
  return this.executeCommand("getSelected");
5921
6754
  }
6755
+ getSelected2() {
6756
+ return this.executeCommand("getSelected2");
6757
+ }
5922
6758
  setSelected(handles) {
5923
6759
  this.executeCommand("setSelected", handles);
5924
6760
  }
6761
+ setSelected2(handles) {
6762
+ this.executeCommand("setSelected2", handles);
6763
+ }
5925
6764
  clearSelected() {
5926
6765
  this.executeCommand("clearSelected");
5927
6766
  }
@@ -5981,37 +6820,8 @@ class Viewer extends EventEmitter2 {
5981
6820
  getComponent(name) {
5982
6821
  return this._components.find((component) => component.name === name);
5983
6822
  }
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
6823
  drawViewpoint(viewpoint) {
6014
- var _a, _b, _c;
6824
+ var _a, _b, _c, _d;
6015
6825
  if (!this.renderer)
6016
6826
  return;
6017
6827
  const getVector3FromPoint3d = ({ x, y, z }) => new Vector3(x, y, z);
@@ -6063,11 +6873,13 @@ class Viewer extends EventEmitter2 {
6063
6873
  }
6064
6874
  };
6065
6875
  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
- });
6876
+ if (clipping_planes) {
6877
+ clipping_planes.forEach((clipping_plane) => {
6878
+ const plane = new Plane();
6879
+ plane.setFromNormalAndCoplanarPoint(getVector3FromPoint3d(clipping_plane.direction), getVector3FromPoint3d(clipping_plane.location));
6880
+ this.renderer.clippingPlanes.push(plane);
6881
+ });
6882
+ }
6071
6883
  };
6072
6884
  const setSelection = (selection) => {
6073
6885
  if (selection)
@@ -6083,9 +6895,9 @@ class Viewer extends EventEmitter2 {
6083
6895
  setOrthogonalCamera(viewpoint.orthogonal_camera);
6084
6896
  setPerspectiveCamera(viewpoint.perspective_camera);
6085
6897
  setClippingPlanes(viewpoint.clipping_planes);
6086
- setSelection(viewpoint.selection);
6898
+ setSelection(((_b = viewpoint.custom_fields) === null || _b === void 0 ? void 0 : _b.selection2) || viewpoint.selection);
6087
6899
  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);
6900
+ 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
6901
  this.setActiveDragger(draggerName);
6090
6902
  this.emitEvent({ type: "drawviewpoint", data: viewpoint });
6091
6903
  this.update();
@@ -6132,6 +6944,9 @@ class Viewer extends EventEmitter2 {
6132
6944
  const getSelection = () => {
6133
6945
  return this.getSelected().map((handle) => ({ handle }));
6134
6946
  };
6947
+ const getSelection2 = () => {
6948
+ return this.getSelected2().map((handle) => ({ handle }));
6949
+ };
6135
6950
  const viewpoint = { custom_fields: {} };
6136
6951
  viewpoint.orthogonal_camera = getOrthogonalCamera();
6137
6952
  viewpoint.perspective_camera = getPerspectiveCamera();
@@ -6140,9 +6955,36 @@ class Viewer extends EventEmitter2 {
6140
6955
  viewpoint.description = new Date().toDateString();
6141
6956
  this._markup.getViewpoint(viewpoint);
6142
6957
  viewpoint.custom_fields.camera_target = getPoint3dFromVector3(this.target);
6958
+ viewpoint.custom_fields.selection2 = getSelection2();
6143
6959
  this.emitEvent({ type: "createviewpoint", data: viewpoint });
6144
6960
  return viewpoint;
6145
6961
  }
6962
+ screenToWorld(position) {
6963
+ if (!this.renderer)
6964
+ return { x: position.x, y: position.y, z: 0 };
6965
+ const rect = this.canvas.getBoundingClientRect();
6966
+ const x = position.x / (rect.width / 2) - 1;
6967
+ const y = -position.y / (rect.height / 2) + 1;
6968
+ const point = new Vector3(x, y, -1);
6969
+ point.unproject(this.camera);
6970
+ return { x: point.x, y: point.y, z: point.z };
6971
+ }
6972
+ worldToScreen(position) {
6973
+ if (!this.renderer)
6974
+ return { x: position.x, y: position.y };
6975
+ const point = new Vector3(position.x, position.y, position.z);
6976
+ point.project(this.camera);
6977
+ const rect = this.canvas.getBoundingClientRect();
6978
+ const x = (point.x + 1) * (rect.width / 2);
6979
+ const y = (-point.y + 1) * (rect.height / 2);
6980
+ return { x, y };
6981
+ }
6982
+ getScale() {
6983
+ return { x: 1, y: 1, z: 1 };
6984
+ }
6985
+ executeCommand(id, ...args) {
6986
+ return commands.executeCommand(id, this, ...args);
6987
+ }
6146
6988
  }
6147
6989
 
6148
6990
  export { GLTFLoadingManager, ModelImpl, Viewer, commands, components, draggers, loaders };