@inweb/viewer-three 26.10.5 → 26.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/README.md +7 -4
  2. package/dist/{plugins → extensions}/components/AxesHelperComponent.js +23 -1
  3. package/dist/extensions/components/AxesHelperComponent.js.map +1 -0
  4. package/dist/extensions/components/AxesHelperComponent.min.js +24 -0
  5. package/dist/{plugins → extensions}/components/AxesHelperComponent.module.js +24 -2
  6. package/dist/extensions/components/AxesHelperComponent.module.js.map +1 -0
  7. package/dist/{plugins → extensions}/components/ExtentsHelperComponent.js +18 -0
  8. package/dist/extensions/components/ExtentsHelperComponent.js.map +1 -0
  9. package/dist/{plugins/components/AxesHelperComponent.min.js → extensions/components/ExtentsHelperComponent.min.js} +1 -1
  10. package/dist/{plugins → extensions}/components/ExtentsHelperComponent.module.js +19 -1
  11. package/dist/extensions/components/ExtentsHelperComponent.module.js.map +1 -0
  12. package/dist/extensions/components/GridHelperComponent.js.map +1 -0
  13. package/dist/extensions/components/GridHelperComponent.module.js.map +1 -0
  14. package/dist/extensions/components/LightHelperComponent.js.map +1 -0
  15. package/dist/extensions/components/LightHelperComponent.module.js.map +1 -0
  16. package/dist/extensions/components/RoomEnvironmentComponent.js.map +1 -0
  17. package/dist/extensions/components/RoomEnvironmentComponent.module.js.map +1 -0
  18. package/dist/extensions/components/StatsPanelComponent.js.map +1 -0
  19. package/dist/extensions/components/StatsPanelComponent.module.js.map +1 -0
  20. package/dist/{plugins → extensions}/loaders/GLTFCloudLoader.js +2 -3
  21. package/dist/extensions/loaders/GLTFCloudLoader.js.map +1 -0
  22. package/dist/{plugins → extensions}/loaders/GLTFCloudLoader.min.js +1 -1
  23. package/dist/{plugins → extensions}/loaders/GLTFCloudLoader.module.js +2 -3
  24. package/dist/extensions/loaders/GLTFCloudLoader.module.js.map +1 -0
  25. package/dist/extensions/loaders/GLTFFileLoader.js +2499 -0
  26. package/dist/extensions/loaders/GLTFFileLoader.js.map +1 -0
  27. package/dist/extensions/loaders/GLTFFileLoader.min.js +24 -0
  28. package/dist/extensions/loaders/GLTFFileLoader.module.js +74 -0
  29. package/dist/extensions/loaders/GLTFFileLoader.module.js.map +1 -0
  30. package/dist/{plugins → extensions}/loaders/IFCXLoader.js +5 -7
  31. package/dist/extensions/loaders/IFCXLoader.js.map +1 -0
  32. package/dist/{plugins → extensions}/loaders/IFCXLoader.min.js +1 -1
  33. package/dist/{plugins → extensions}/loaders/IFCXLoader.module.js +5 -7
  34. package/dist/extensions/loaders/IFCXLoader.module.js.map +1 -0
  35. package/dist/{plugins → extensions}/loaders/PotreeLoader.js +1 -2
  36. package/dist/extensions/loaders/PotreeLoader.js.map +1 -0
  37. package/dist/{plugins → extensions}/loaders/PotreeLoader.min.js +1 -1
  38. package/dist/{plugins → extensions}/loaders/PotreeLoader.module.js +1 -2
  39. package/dist/extensions/loaders/PotreeLoader.module.js.map +1 -0
  40. package/dist/viewer-three.js +1023 -2928
  41. package/dist/viewer-three.js.map +1 -1
  42. package/dist/viewer-three.min.js +3 -3
  43. package/dist/viewer-three.module.js +856 -359
  44. package/dist/viewer-three.module.js.map +1 -1
  45. package/{plugins → extensions}/components/AxesHelperComponent.ts +31 -2
  46. package/{plugins → extensions}/components/ExtentsHelperComponent.ts +25 -0
  47. package/{plugins → extensions}/loaders/GLTFCloudLoader.ts +2 -3
  48. package/{src/Viewer → extensions}/loaders/GLTFFileLoader.ts +21 -12
  49. package/{plugins → extensions}/loaders/IFCX/IFCXCloudLoader.ts +5 -5
  50. package/{plugins → extensions}/loaders/IFCX/IFCXFileLoader.ts +3 -4
  51. package/{plugins → extensions}/loaders/Potree/PotreeFileLoader.ts +3 -4
  52. package/lib/Viewer/Viewer.d.ts +27 -20
  53. package/lib/Viewer/commands/GetSelected2.d.ts +2 -0
  54. package/lib/Viewer/commands/SelectModel.d.ts +1 -1
  55. package/lib/Viewer/commands/SetSelected2.d.ts +2 -0
  56. package/lib/Viewer/components/SelectionComponent.d.ts +1 -3
  57. package/lib/Viewer/components/index.d.ts +6 -6
  58. package/lib/Viewer/draggers/MeasureLineDragger.d.ts +7 -1
  59. package/lib/Viewer/draggers/OrbitDragger.d.ts +3 -0
  60. package/lib/Viewer/loaders/DynamicGltfLoader/DynamicModelImpl.d.ts +0 -1
  61. package/lib/Viewer/loaders/GLTFBinaryExtension.d.ts +5 -0
  62. package/lib/Viewer/loaders/GLTFCloudDynamicLoader.d.ts +2 -2
  63. package/lib/Viewer/loaders/{GLTFFileLoader.d.ts → GLTFFileDynamicLoader.d.ts} +7 -1
  64. package/lib/Viewer/loaders/GLTFLoadingManager.d.ts +4 -3
  65. package/lib/Viewer/loaders/RangesLoader.d.ts +15 -0
  66. package/lib/Viewer/loaders/index.d.ts +22 -14
  67. package/lib/Viewer/measurement/Snapper.d.ts +15 -0
  68. package/lib/Viewer/measurement/UnitConverter.d.ts +63 -0
  69. package/lib/Viewer/measurement/UnitFormatter.d.ts +4 -0
  70. package/lib/Viewer/models/IModelImpl.d.ts +10 -8
  71. package/lib/Viewer/models/ModelImpl.d.ts +7 -5
  72. package/package.json +11 -11
  73. package/src/Viewer/Viewer.ts +120 -88
  74. package/src/Viewer/commands/ClearSelected.ts +3 -1
  75. package/src/Viewer/commands/GetModels.ts +1 -1
  76. package/src/Viewer/commands/GetSelected.ts +2 -2
  77. package/src/Viewer/commands/GetSelected2.ts +34 -0
  78. package/src/Viewer/commands/HideSelected.ts +3 -1
  79. package/src/Viewer/commands/SelectModel.ts +5 -5
  80. package/src/Viewer/commands/SetSelected.ts +9 -10
  81. package/src/Viewer/commands/SetSelected2.ts +42 -0
  82. package/src/Viewer/commands/ZoomToObjects.ts +5 -6
  83. package/src/Viewer/commands/ZoomToSelected.ts +3 -1
  84. package/src/Viewer/commands/index.ts +4 -0
  85. package/src/Viewer/components/CameraComponent.ts +6 -1
  86. package/src/Viewer/components/ExtentsComponent.ts +4 -1
  87. package/src/Viewer/components/HighlighterComponent.ts +5 -3
  88. package/src/Viewer/components/SelectionComponent.ts +7 -30
  89. package/src/Viewer/components/index.ts +6 -6
  90. package/src/Viewer/draggers/MeasureLineDragger.ts +84 -226
  91. package/src/Viewer/draggers/OrbitDragger.ts +6 -0
  92. package/src/Viewer/loaders/DynamicGltfLoader/DynamicGltfLoader.js +263 -34
  93. package/src/Viewer/loaders/DynamicGltfLoader/DynamicModelImpl.ts +20 -10
  94. package/src/Viewer/loaders/DynamicGltfLoader/GltfStructure.js +4 -1
  95. package/src/Viewer/loaders/GLTFBinaryExtension.ts +91 -0
  96. package/src/Viewer/loaders/GLTFCloudDynamicLoader.ts +13 -19
  97. package/src/Viewer/loaders/GLTFFileDynamicLoader.ts +145 -0
  98. package/src/Viewer/loaders/GLTFLoadingManager.ts +5 -4
  99. package/src/Viewer/loaders/RangesLoader.ts +95 -0
  100. package/src/Viewer/loaders/index.ts +24 -16
  101. package/src/Viewer/measurement/Snapper.ts +208 -0
  102. package/src/Viewer/measurement/UnitConverter.ts +47 -0
  103. package/src/Viewer/measurement/UnitFormatter.ts +95 -0
  104. package/src/Viewer/models/IModelImpl.ts +15 -8
  105. package/src/Viewer/models/ModelImpl.ts +48 -17
  106. package/src/index-umd.ts +1 -1
  107. package/dist/plugins/components/AxesHelperComponent.js.map +0 -1
  108. package/dist/plugins/components/AxesHelperComponent.module.js.map +0 -1
  109. package/dist/plugins/components/ExtentsHelperComponent.js.map +0 -1
  110. package/dist/plugins/components/ExtentsHelperComponent.min.js +0 -24
  111. package/dist/plugins/components/ExtentsHelperComponent.module.js.map +0 -1
  112. package/dist/plugins/components/GridHelperComponent.js.map +0 -1
  113. package/dist/plugins/components/GridHelperComponent.module.js.map +0 -1
  114. package/dist/plugins/components/LightHelperComponent.js.map +0 -1
  115. package/dist/plugins/components/LightHelperComponent.module.js.map +0 -1
  116. package/dist/plugins/components/RoomEnvironmentComponent.js.map +0 -1
  117. package/dist/plugins/components/RoomEnvironmentComponent.module.js.map +0 -1
  118. package/dist/plugins/components/StatsPanelComponent.js.map +0 -1
  119. package/dist/plugins/components/StatsPanelComponent.module.js.map +0 -1
  120. package/dist/plugins/loaders/GLTFCloudLoader.js.map +0 -1
  121. package/dist/plugins/loaders/GLTFCloudLoader.module.js.map +0 -1
  122. package/dist/plugins/loaders/IFCXLoader.js.map +0 -1
  123. package/dist/plugins/loaders/IFCXLoader.module.js.map +0 -1
  124. package/dist/plugins/loaders/PotreeLoader.js.map +0 -1
  125. package/dist/plugins/loaders/PotreeLoader.module.js.map +0 -1
  126. /package/dist/{plugins → extensions}/components/GridHelperComponent.js +0 -0
  127. /package/dist/{plugins → extensions}/components/GridHelperComponent.min.js +0 -0
  128. /package/dist/{plugins → extensions}/components/GridHelperComponent.module.js +0 -0
  129. /package/dist/{plugins → extensions}/components/LightHelperComponent.js +0 -0
  130. /package/dist/{plugins → extensions}/components/LightHelperComponent.min.js +0 -0
  131. /package/dist/{plugins → extensions}/components/LightHelperComponent.module.js +0 -0
  132. /package/dist/{plugins → extensions}/components/RoomEnvironmentComponent.js +0 -0
  133. /package/dist/{plugins → extensions}/components/RoomEnvironmentComponent.min.js +0 -0
  134. /package/dist/{plugins → extensions}/components/RoomEnvironmentComponent.module.js +0 -0
  135. /package/dist/{plugins → extensions}/components/StatsPanelComponent.js +0 -0
  136. /package/dist/{plugins → extensions}/components/StatsPanelComponent.min.js +0 -0
  137. /package/dist/{plugins → extensions}/components/StatsPanelComponent.module.js +0 -0
  138. /package/{plugins → extensions}/components/GridHelperComponent.ts +0 -0
  139. /package/{plugins → extensions}/components/LightHelperComponent.ts +0 -0
  140. /package/{plugins → extensions}/components/RoomEnvironmentComponent.ts +0 -0
  141. /package/{plugins → extensions}/components/StatsPanelComponent.ts +0 -0
  142. /package/{plugins → extensions}/loaders/IFCX/IFCXLoader.ts +0 -0
  143. /package/{plugins → extensions}/loaders/IFCX/index.ts +0 -0
  144. /package/{plugins → extensions}/loaders/IFCX/render.js +0 -0
  145. /package/{plugins → extensions}/loaders/Potree/PotreeModelImpl.ts +0 -0
  146. /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, componentsRegistry, Loader, loadersRegistry, Options, CANVAS_EVENTS } from '@inweb/viewer-core';
24
+ import { draggersRegistry, commandsRegistry, Options, componentsRegistry, Loader, loadersRegistry, CANVAS_EVENTS } from '@inweb/viewer-core';
25
25
  export * from '@inweb/viewer-core';
26
- import { Line, Vector3, BufferGeometry, Float32BufferAttribute, LineBasicMaterial, Mesh, MeshBasicMaterial, DoubleSide, EventDispatcher, MOUSE, TOUCH, Spherical, Quaternion, Vector2, Plane, Object3D, Line3, Matrix4, Vector4, MathUtils, Raycaster, EdgesGeometry, Controls, Clock, Sphere, Box3, Color, PerspectiveCamera, OrthographicCamera, AmbientLight, DirectionalLight, HemisphereLight, MeshPhongMaterial, WebGLRenderTarget, UnsignedByteType, RGBAFormat, CylinderGeometry, Sprite, CanvasTexture, SRGBColorSpace, SpriteMaterial, LoadingManager, LoaderUtils, TextureLoader, BufferAttribute, PointsMaterial, Points, TriangleStripDrawMode, TriangleFanDrawMode, LineSegments, LineLoop, Group, NormalBlending, UniformsUtils, ShaderMaterial, AdditiveBlending, HalfFloatType, Scene, WebGLRenderer, LinearSRGBColorSpace } from 'three';
26
+ import { Line, Vector3, BufferGeometry, Float32BufferAttribute, LineBasicMaterial, Mesh, MeshBasicMaterial, DoubleSide, EventDispatcher, MOUSE, TOUCH, Spherical, Quaternion, Vector2, Plane, Object3D, Line3, Raycaster, MathUtils, EdgesGeometry, Matrix4, Vector4, Controls, Clock, Sphere, Box3, Color, PerspectiveCamera, OrthographicCamera, AmbientLight, DirectionalLight, HemisphereLight, MeshPhongMaterial, WebGLRenderTarget, UnsignedByteType, RGBAFormat, CylinderGeometry, Sprite, CanvasTexture, SRGBColorSpace, SpriteMaterial, TextureLoader, BufferAttribute, PointsMaterial, Points, TriangleStripDrawMode, TriangleFanDrawMode, LineSegments, LineLoop, Group, NormalBlending, LoadingManager, LoaderUtils, FileLoader, UniformsUtils, ShaderMaterial, AdditiveBlending, HalfFloatType, Scene, WebGLRenderer, LinearSRGBColorSpace } from 'three';
27
27
  import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js';
28
28
  import { LineSegmentsGeometry } from 'three/examples/jsm/lines/LineSegmentsGeometry.js';
29
29
  import { Wireframe } from 'three/examples/jsm/lines/Wireframe.js';
30
30
  import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js';
31
- import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
32
31
  import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
33
32
  import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
34
33
  import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
@@ -752,6 +751,9 @@ class OrbitDragger {
752
751
  this.orbit.object = this.viewer.camera;
753
752
  this.orbit.update();
754
753
  };
754
+ this.optionsChange = ({ data: options }) => {
755
+ this.orbit.zoomSpeed = Math.abs(this.orbit.zoomSpeed) * (options.reverseZoomWheel ? -1 : 1);
756
+ };
755
757
  this.controlsStart = () => {
756
758
  this.changed = false;
757
759
  };
@@ -807,6 +809,7 @@ class OrbitDragger {
807
809
  this.viewer.addEventListener("zoom", this.updateControls);
808
810
  this.viewer.addEventListener("drawviewpoint", this.updateControls);
809
811
  this.viewer.addEventListener("changecameramode", this.updateControlsCamera);
812
+ this.viewer.addEventListener("optionschange", this.optionsChange);
810
813
  this.viewer.addEventListener("contextmenu", this.stopContextMenu);
811
814
  this.updateControls();
812
815
  }
@@ -817,6 +820,7 @@ class OrbitDragger {
817
820
  this.viewer.removeEventListener("zoom", this.updateControls);
818
821
  this.viewer.removeEventListener("drawviewpoint", this.updateControls);
819
822
  this.viewer.removeEventListener("changecameramode", this.updateControlsCamera);
823
+ this.viewer.removeEventListener("optionschange", this.optionsChange);
820
824
  this.viewer.removeEventListener("contextmenu", this.stopContextMenu);
821
825
  this.orbit.removeEventListener("start", this.controlsStart);
822
826
  this.orbit.removeEventListener("change", this.controlsChange);
@@ -909,116 +913,91 @@ class CuttingPlaneZAxisDragger extends CuttingPlaneDragger {
909
913
  }
910
914
  }
911
915
 
912
- const PRECISION = 0.01;
913
- const DESKTOP_SNAP_DISTANCE = 10;
914
- const MOBILE_SNAP_DISTANCE = 50;
915
- class MeasureLineDragger extends OrbitDragger {
916
- constructor(viewer) {
917
- super(viewer);
918
- this.onPointerDown = (event) => {
919
- if (event.button !== 0)
920
- return;
921
- this.line.startPoint = this.snapper.getSnapPoint(event);
922
- this.line.render();
923
- this.viewer.canvas.setPointerCapture(event.pointerId);
924
- this.orbit.enabled = !this.line.startPoint;
925
- };
926
- this.onPointerMove = (event) => {
927
- if (this.orbit.enabled && this.orbit.state !== -1)
928
- return;
929
- const snapPoint = this.snapper.getSnapPoint(event);
930
- if (snapPoint && this.line.endPoint && snapPoint.equals(this.line.endPoint))
931
- return;
932
- this.line.endPoint = snapPoint;
933
- this.line.render();
934
- if (this.line.startPoint)
935
- this.changed = true;
936
- };
937
- this.onPointerUp = (event) => {
938
- if (this.line.startPoint && this.line.endPoint && this.line.getDistance() >= PRECISION) {
939
- this.line = new MeasureLine(this.overlay);
940
- this.overlay.addLine(this.line);
941
- }
942
- else {
943
- this.line.startPoint = undefined;
944
- this.line.endPoint = undefined;
945
- this.line.render();
946
- }
947
- this.viewer.canvas.releasePointerCapture(event.pointerId);
948
- this.orbit.enabled = true;
949
- };
950
- this.onPointerCancel = (event) => {
951
- this.viewer.canvas.dispatchEvent(new PointerEvent("pointerup", event));
952
- };
953
- this.onPointerLeave = () => {
954
- this.line.endPoint = undefined;
955
- this.line.render();
956
- };
957
- this.renderOverlay = () => {
958
- this.overlay.render();
959
- };
960
- this.updateSnapper = () => {
961
- this.snapper.setFromViewer(this.viewer);
962
- };
963
- this.updateSnapperCamera = () => {
964
- this.snapper.camera = this.viewer.camera;
965
- this.overlay.camera = this.viewer.camera;
966
- };
967
- this.overlay = new MeasureOverlay(viewer.camera, viewer.canvas);
968
- this.overlay.attach();
969
- this.line = new MeasureLine(this.overlay);
970
- this.overlay.addLine(this.line);
971
- this.snapper = new MeasureSnapper(viewer.camera, viewer.canvas);
972
- this.updateSnapper();
973
- this.viewer.canvas.addEventListener("pointerdown", this.onPointerDown);
974
- this.viewer.canvas.addEventListener("pointermove", this.onPointerMove);
975
- this.viewer.canvas.addEventListener("pointerup", this.onPointerUp);
976
- this.viewer.canvas.addEventListener("pointercancel", this.onPointerCancel);
977
- this.viewer.canvas.addEventListener("pointerleave", this.onPointerLeave);
978
- this.viewer.addEventListener("render", this.renderOverlay);
979
- this.viewer.addEventListener("hide", this.updateSnapper);
980
- this.viewer.addEventListener("isolate", this.updateSnapper);
981
- this.viewer.addEventListener("show", this.updateSnapper);
982
- this.viewer.addEventListener("showall", this.updateSnapper);
983
- this.viewer.addEventListener("changecameramode", this.updateSnapperCamera);
984
- }
985
- dispose() {
986
- this.viewer.canvas.removeEventListener("pointerdown", this.onPointerDown);
987
- this.viewer.canvas.removeEventListener("pointermove", this.onPointerMove);
988
- this.viewer.canvas.removeEventListener("pointerup", this.onPointerUp);
989
- this.viewer.canvas.removeEventListener("pointercancel", this.onPointerCancel);
990
- this.viewer.canvas.removeEventListener("pointerleave", this.onPointerLeave);
991
- this.viewer.removeEventListener("render", this.renderOverlay);
992
- this.viewer.removeEventListener("hide", this.updateSnapper);
993
- this.viewer.removeEventListener("isolate", this.updateSnapper);
994
- this.viewer.removeEventListener("show", this.updateSnapper);
995
- this.viewer.removeEventListener("showall", this.updateSnapper);
996
- this.viewer.removeEventListener("changecameramode", this.updateSnapperCamera);
997
- this.snapper.dispose();
998
- this.overlay.detach();
999
- this.overlay.dispose();
1000
- 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);
1001
981
  }
1002
982
  }
983
+
984
+ const DESKTOP_SNAP_DISTANCE = 10;
985
+ const MOBILE_SNAP_DISTANCE = 50;
1003
986
  const _vertex = new Vector3();
1004
987
  const _start = new Vector3();
1005
988
  const _end = new Vector3();
1006
989
  const _line = new Line3();
1007
990
  const _center = new Vector3();
1008
991
  const _projection = new Vector3();
1009
- class MeasureSnapper {
1010
- constructor(camera, canvas) {
992
+ class Snapper {
993
+ constructor(camera, renderer, canvas) {
1011
994
  this.camera = camera;
995
+ this.renderer = renderer;
1012
996
  this.canvas = canvas;
1013
- this.objects = [];
1014
- this.clippingPlanes = [];
1015
997
  this.raycaster = new Raycaster();
1016
998
  this.detectRadiusInPixels = this.isMobile() ? MOBILE_SNAP_DISTANCE : DESKTOP_SNAP_DISTANCE;
1017
999
  this.edgesCache = new WeakMap();
1018
1000
  }
1019
- dispose() {
1020
- this.objects = [];
1021
- }
1022
1001
  isMobile() {
1023
1002
  if (typeof navigator === "undefined")
1024
1003
  return false;
@@ -1027,7 +1006,7 @@ class MeasureSnapper {
1027
1006
  getMousePosition(event, target) {
1028
1007
  return target.set(event.clientX, event.clientY);
1029
1008
  }
1030
- getPointerIntersects(mouse) {
1009
+ getPointerIntersects(mouse, objects) {
1031
1010
  const rect = this.canvas.getBoundingClientRect();
1032
1011
  const x = ((mouse.x - rect.left) / rect.width) * 2 - 1;
1033
1012
  const y = (-(mouse.y - rect.top) / rect.height) * 2 + 1;
@@ -1041,8 +1020,8 @@ class MeasureSnapper {
1041
1020
  Points: { threshold: 0.01 },
1042
1021
  Sprite: {},
1043
1022
  };
1044
- let intersects = this.raycaster.intersectObjects(this.objects, false);
1045
- this.clippingPlanes.forEach((plane) => {
1023
+ let intersects = this.raycaster.intersectObjects(objects, false);
1024
+ (this.renderer.clippingPlanes || []).forEach((plane) => {
1046
1025
  intersects = intersects.filter((intersect) => plane.distanceToPoint(intersect.point) >= 0);
1047
1026
  });
1048
1027
  return intersects;
@@ -1064,9 +1043,8 @@ class MeasureSnapper {
1064
1043
  }
1065
1044
  return 0.1;
1066
1045
  }
1067
- getSnapPoint(event) {
1068
- const mouse = this.getMousePosition(event, new Vector2());
1069
- const intersections = this.getPointerIntersects(mouse);
1046
+ getSnapPoint(mouse, objects) {
1047
+ const intersections = this.getPointerIntersects(mouse, objects);
1070
1048
  if (intersections.length === 0)
1071
1049
  return undefined;
1072
1050
  const object = intersections[0].object;
@@ -1114,13 +1092,133 @@ class MeasureSnapper {
1114
1092
  return object.localToWorld(snapPoint);
1115
1093
  return intersectionPoint.clone();
1116
1094
  }
1117
- 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);
1118
1218
  this.objects.length = 0;
1119
- viewer.models.forEach((model) => {
1120
- model.getVisibleObjects().forEach((object) => this.objects.push(object));
1121
- });
1122
- this.camera = viewer.camera;
1123
- this.clippingPlanes = viewer.renderer.clippingPlanes || [];
1219
+ this.overlay.detach();
1220
+ this.overlay.dispose();
1221
+ super.dispose();
1124
1222
  }
1125
1223
  }
1126
1224
  class MeasureOverlay {
@@ -1140,6 +1238,9 @@ class MeasureOverlay {
1140
1238
  this.projector = new MeasureProjector(camera, canvas);
1141
1239
  this.resizeObserver = new ResizeObserver(this.resizeContainer);
1142
1240
  }
1241
+ dispose() {
1242
+ this.clear();
1243
+ }
1143
1244
  attach() {
1144
1245
  this.container = document.createElement("div");
1145
1246
  this.container.id = "measure-container";
@@ -1152,9 +1253,6 @@ class MeasureOverlay {
1152
1253
  this.canvas.parentElement.appendChild(this.container);
1153
1254
  this.resizeObserver.observe(this.canvas);
1154
1255
  }
1155
- dispose() {
1156
- this.clear();
1157
- }
1158
1256
  detach() {
1159
1257
  this.resizeObserver.disconnect();
1160
1258
  this.container.remove();
@@ -1162,7 +1260,7 @@ class MeasureOverlay {
1162
1260
  }
1163
1261
  clear() {
1164
1262
  this.lines.forEach((line) => line.dispose());
1165
- this.lines = [];
1263
+ this.lines.length = 0;
1166
1264
  }
1167
1265
  render() {
1168
1266
  this.projector.setFromCamera(this.camera);
@@ -1177,13 +1275,18 @@ class MeasureOverlay {
1177
1275
  removeLine(line) {
1178
1276
  this.lines = this.lines.filter((x) => x !== line);
1179
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
+ }
1180
1285
  }
1181
1286
  const _middlePoint = new Vector3();
1182
1287
  class MeasureLine {
1183
- constructor(overlay) {
1288
+ constructor(overlay, scale, units, precision) {
1184
1289
  this.id = MathUtils.generateUUID();
1185
- this.unit = "";
1186
- this.scale = 1.0;
1187
1290
  this.size = 10.0;
1188
1291
  this.lineWidth = 2;
1189
1292
  this.style = {
@@ -1194,6 +1297,9 @@ class MeasureLine {
1194
1297
  font: "1rem system-ui",
1195
1298
  };
1196
1299
  this.overlay = overlay;
1300
+ this.scale = scale;
1301
+ this.units = units;
1302
+ this.precision = precision;
1197
1303
  this.elementStartPoint = overlay.container.appendChild(document.createElement("div"));
1198
1304
  this.elementEndPoint = overlay.container.appendChild(document.createElement("div"));
1199
1305
  this.elementLine = overlay.container.appendChild(document.createElement("div"));
@@ -1248,10 +1354,10 @@ class MeasureLine {
1248
1354
  _middlePoint.lerpVectors(this.startPoint, this.endPoint, 0.5);
1249
1355
  const { point, visible } = projector.projectPoint(_middlePoint);
1250
1356
  const distance = this.getDistance();
1251
- this.elementLabel.style.display = visible && distance >= PRECISION ? "block" : "none";
1357
+ this.elementLabel.style.display = visible && distance > 0 ? "block" : "none";
1252
1358
  this.elementLabel.style.left = `${point.x}px`;
1253
1359
  this.elementLabel.style.top = `${point.y}px`;
1254
- this.elementLabel.innerHTML = `${distance.toFixed(2)} ${this.unit}`;
1360
+ this.elementLabel.innerHTML = formatDistance(distance, this.units, this.precision);
1255
1361
  }
1256
1362
  else {
1257
1363
  this.elementLabel.style.display = "none";
@@ -2055,7 +2161,8 @@ function clearSelected(viewer) {
2055
2161
  const selection = viewer.getComponent("SelectionComponent");
2056
2162
  selection.clearSelection();
2057
2163
  viewer.update();
2058
- viewer.emitEvent({ type: "select", data: undefined, handles: [] });
2164
+ viewer.emitEvent({ type: "select", handles: [] });
2165
+ viewer.emitEvent({ type: "select2", handles: [] });
2059
2166
  }
2060
2167
 
2061
2168
  function clearSlices(viewer) {
@@ -2144,22 +2251,31 @@ function getDefaultViewPositions() {
2144
2251
  }
2145
2252
 
2146
2253
  function getModels(viewer) {
2147
- return viewer.models.map((model) => model.handle);
2254
+ return viewer.models.map((model) => model.id);
2148
2255
  }
2149
2256
 
2150
2257
  function getSelected(viewer) {
2151
- const handles = [];
2152
- 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));
2153
2260
  return handles;
2154
2261
  }
2155
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
+
2156
2271
  function hideSelected(viewer) {
2157
2272
  viewer.models.forEach((model) => model.hideObjects(viewer.selected));
2158
2273
  const selection = viewer.getComponent("SelectionComponent");
2159
2274
  selection.clearSelection();
2160
2275
  viewer.update();
2161
2276
  viewer.emitEvent({ type: "hide" });
2162
- viewer.emitEvent({ type: "select", data: undefined, handles: [] });
2277
+ viewer.emitEvent({ type: "select", handles: [] });
2278
+ viewer.emitEvent({ type: "select2", handles: [] });
2163
2279
  }
2164
2280
 
2165
2281
  function isolateSelected(viewer) {
@@ -2186,14 +2302,13 @@ function resetView(viewer) {
2186
2302
  viewer.emit({ type: "resetview" });
2187
2303
  }
2188
2304
 
2189
- function selectModel(viewer, handle) {
2305
+ function selectModel(viewer, id) {
2190
2306
  const selection = viewer.getComponent("SelectionComponent");
2191
2307
  selection.clearSelection();
2192
- viewer.models
2193
- .filter((model) => model.handle === handle)
2194
- .forEach((model) => selection.select(model.getObjects(), model));
2308
+ viewer.models.filter((model) => model.id === id).forEach((model) => selection.select(model.getObjects(), model));
2195
2309
  viewer.update();
2196
- viewer.emit({ type: "select", data: [] });
2310
+ viewer.emitEvent({ type: "select", handles: viewer.getSelected() });
2311
+ viewer.emitEvent({ type: "select2", handles: viewer.getSelected2() });
2197
2312
  }
2198
2313
 
2199
2314
  function setActiveDragger(viewer, dragger = "") {
@@ -2205,16 +2320,31 @@ function setMarkupColor(viewer, r = 255, g = 0, b = 0) {
2205
2320
  }
2206
2321
 
2207
2322
  function setSelected(viewer, handles = []) {
2208
- const selection = viewer.getComponent("SelectionComponent");
2209
- 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();
2210
2339
  viewer.models.forEach((model) => {
2211
2340
  const objects = model.getObjectsByHandles(handles);
2212
2341
  model.showObjects(objects);
2213
- selection.select(objects, model);
2342
+ selectionComponent.select(objects, model);
2214
2343
  });
2215
2344
  viewer.update();
2216
2345
  viewer.emitEvent({ type: "show" });
2217
- 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 });
2218
2348
  }
2219
2349
 
2220
2350
  function showAll(viewer) {
@@ -2228,21 +2358,19 @@ function zoomToExtents(viewer) {
2228
2358
  }
2229
2359
 
2230
2360
  function zoomToObjects(viewer, handles = []) {
2231
- const handleSet = new Set(handles);
2232
- const objects = [];
2233
- viewer.scene.traverseVisible((child) => {
2234
- var _a;
2235
- if (handleSet.has((_a = child.userData) === null || _a === void 0 ? void 0 : _a.handle))
2236
- 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));
2237
2365
  });
2238
- const extents = objects.reduce((result, object) => result.expandByObject(object), new Box3());
2239
2366
  if (extents.isEmpty())
2240
2367
  extents.copy(viewer.extents);
2241
2368
  zoomTo(viewer, extents);
2242
2369
  }
2243
2370
 
2244
2371
  function zoomToSelected(viewer) {
2245
- 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));
2246
2374
  if (extents.isEmpty())
2247
2375
  extents.copy(viewer.extents);
2248
2376
  zoomTo(viewer, extents);
@@ -2259,6 +2387,7 @@ commands.registerCommand("explode", explode);
2259
2387
  commands.registerCommand("getDefaultViewPositions", getDefaultViewPositions);
2260
2388
  commands.registerCommand("getModels", getModels);
2261
2389
  commands.registerCommand("getSelected", getSelected);
2390
+ commands.registerCommand("getSelected2", getSelected2);
2262
2391
  commands.registerCommand("hideSelected", hideSelected);
2263
2392
  commands.registerCommand("isolateSelected", isolateSelected);
2264
2393
  commands.registerCommand("regenerateAll", regenerateAll);
@@ -2268,6 +2397,7 @@ commands.registerCommand("setActiveDragger", setActiveDragger);
2268
2397
  commands.registerCommand("setDefaultViewPosition", setDefaultViewPosition);
2269
2398
  commands.registerCommand("setMarkupColor", setMarkupColor);
2270
2399
  commands.registerCommand("setSelected", setSelected);
2400
+ commands.registerCommand("setSelected2", setSelected2);
2271
2401
  commands.registerCommand("showAll", showAll);
2272
2402
  commands.registerCommand("zoomToExtents", zoomToExtents);
2273
2403
  commands.registerCommand("zoomToObjects", zoomToObjects);
@@ -2320,6 +2450,10 @@ class CameraComponent {
2320
2450
  this.switchCameraMode(this.viewer.options.cameraMode);
2321
2451
  };
2322
2452
  this.geometryEnd = () => {
2453
+ if (this.viewer.models.length > 1) {
2454
+ this.switchCamera(this.viewer.camera);
2455
+ return;
2456
+ }
2323
2457
  let camera;
2324
2458
  this.viewer.scene.traverse((object) => {
2325
2459
  if (object.isCamera)
@@ -2417,6 +2551,8 @@ class ExtentsComponent {
2417
2551
  const extents = new Box3();
2418
2552
  this.viewer.models.forEach((model) => model.getExtents(extents));
2419
2553
  this.viewer.extents.copy(extents);
2554
+ if (this.viewer.models.length > 1)
2555
+ return;
2420
2556
  this.viewer.extents.getCenter(this.viewer.target);
2421
2557
  };
2422
2558
  this.viewer = viewer;
@@ -2703,8 +2839,9 @@ class HighlighterComponent {
2703
2839
  });
2704
2840
  }
2705
2841
  syncHighlightColors() {
2706
- const { facesColor, facesTransparancy, facesOverlap } = this.viewer.options;
2707
- const { edgesColor, edgesVisibility, edgesOverlap } = this.viewer.options;
2842
+ const options = this.viewer.options.enableCustomHighlight ? this.viewer.options : Options.defaults();
2843
+ const { facesColor, facesTransparancy, facesOverlap } = options;
2844
+ const { edgesColor, edgesVisibility, edgesOverlap } = options;
2708
2845
  this.facesMaterial.color.setRGB(facesColor.r / 255, facesColor.g / 255, facesColor.b / 255);
2709
2846
  this.facesMaterial.opacity = (255 - facesTransparancy) / 255;
2710
2847
  this.facesMaterial.depthTest = !facesOverlap;
@@ -2743,10 +2880,11 @@ class SelectionComponent {
2743
2880
  const upPosition = this.getMousePosition(event, new Vector2());
2744
2881
  if (upPosition.distanceTo(this.downPosition) !== 0)
2745
2882
  return;
2883
+ const snapper = new Snapper(this.viewer.camera, this.viewer.renderer, this.viewer.canvas);
2746
2884
  let intersections = [];
2747
2885
  this.viewer.models.forEach((model) => {
2748
2886
  const objects = model.getVisibleObjects();
2749
- const intersects = this.getPointerIntersects(upPosition, objects);
2887
+ const intersects = snapper.getPointerIntersects(upPosition, objects);
2750
2888
  if (intersects.length > 0)
2751
2889
  intersections.push({ ...intersects[0], model });
2752
2890
  });
@@ -2764,6 +2902,7 @@ class SelectionComponent {
2764
2902
  }
2765
2903
  this.viewer.update();
2766
2904
  this.viewer.emitEvent({ type: "select", data: undefined, handles: this.viewer.getSelected() });
2905
+ this.viewer.emitEvent({ type: "select2", data: undefined, handles: this.viewer.getSelected2() });
2767
2906
  };
2768
2907
  this.onDoubleClick = (event) => {
2769
2908
  if (event.button !== 0)
@@ -2774,7 +2913,6 @@ class SelectionComponent {
2774
2913
  this.highlighter = this.viewer.getComponent("HighlighterComponent");
2775
2914
  };
2776
2915
  this.viewer = viewer;
2777
- this.raycaster = new Raycaster();
2778
2916
  this.downPosition = new Vector2();
2779
2917
  this.viewer.addEventListener("pointerdown", this.onPointerDown);
2780
2918
  this.viewer.addEventListener("pointerup", this.onPointerUp);
@@ -2790,26 +2928,6 @@ class SelectionComponent {
2790
2928
  getMousePosition(event, target) {
2791
2929
  return target.set(event.clientX, event.clientY);
2792
2930
  }
2793
- getPointerIntersects(mouse, objects) {
2794
- const rect = this.viewer.canvas.getBoundingClientRect();
2795
- const x = ((mouse.x - rect.left) / rect.width) * 2 - 1;
2796
- const y = (-(mouse.y - rect.top) / rect.height) * 2 + 1;
2797
- const coords = new Vector2(x, y);
2798
- this.raycaster.setFromCamera(coords, this.viewer.camera);
2799
- this.raycaster.params = {
2800
- Mesh: {},
2801
- Line: { threshold: 0.05 },
2802
- Line2: { threshold: 0.05 },
2803
- LOD: {},
2804
- Points: { threshold: 0.01 },
2805
- Sprite: {},
2806
- };
2807
- let intersects = this.raycaster.intersectObjects(objects, false);
2808
- (this.viewer.renderer.clippingPlanes || []).forEach((plane) => {
2809
- intersects = intersects.filter((intersect) => plane.distanceToPoint(intersect.point) >= 0);
2810
- });
2811
- return intersects;
2812
- }
2813
2931
  select(objects, model) {
2814
2932
  if (!model) {
2815
2933
  this.viewer.models.forEach((model) => this.select(objects, model));
@@ -3008,48 +3126,8 @@ components.registerComponent("SelectionComponent", (viewer) => new SelectionComp
3008
3126
  components.registerComponent("WCSHelperComponent", (viewer) => new WCSHelperComponent(viewer));
3009
3127
  components.registerComponent("ResetComponent", (viewer) => new ResetComponent(viewer));
3010
3128
 
3011
- class GLTFLoadingManager extends LoadingManager {
3012
- constructor(file, params = {}) {
3013
- super();
3014
- this.path = "";
3015
- this.resourcePath = "";
3016
- this.fileURL = "";
3017
- this.dataURLs = new Map();
3018
- this.path = params.path || "";
3019
- const externalFiles = params.externalFiles || new Map();
3020
- if (typeof file === "string") {
3021
- this.fileURL = file;
3022
- this.resourcePath = LoaderUtils.extractUrlBase(file);
3023
- }
3024
- else {
3025
- externalFiles.forEach((value, key) => (this.fileURL = value === file ? key : this.fileURL));
3026
- externalFiles.set(this.fileURL, file);
3027
- }
3028
- externalFiles.forEach((value, key) => {
3029
- let dataURL;
3030
- if (typeof value === "string")
3031
- dataURL = value;
3032
- else
3033
- dataURL = URL.createObjectURL(new Blob([value]));
3034
- this.dataURLs.set(key, dataURL);
3035
- });
3036
- this.setURLModifier((url) => {
3037
- const key = decodeURI(url)
3038
- .replace(this.path, "")
3039
- .replace(this.resourcePath, "")
3040
- .replace(/^(\.?\/)/, "");
3041
- const dataURL = this.dataURLs.get(key);
3042
- return dataURL !== null && dataURL !== void 0 ? dataURL : url;
3043
- });
3044
- }
3045
- dispose() {
3046
- this.dataURLs.forEach(URL.revokeObjectURL);
3047
- }
3048
- }
3049
-
3050
3129
  class ModelImpl {
3051
3130
  constructor(scene) {
3052
- this.handle = "1";
3053
3131
  this.scene = scene;
3054
3132
  }
3055
3133
  dispose() {
@@ -3069,6 +3147,18 @@ class ModelImpl {
3069
3147
  this.scene.traverse(disposeObject);
3070
3148
  this.scene.clear();
3071
3149
  }
3150
+ getUnits() {
3151
+ return "Meters";
3152
+ }
3153
+ getUnitScale() {
3154
+ return convertUnits(this.getUnits(), "Meters", 1);
3155
+ }
3156
+ getUnitString() {
3157
+ return getDisplayUnit(this.getUnits());
3158
+ }
3159
+ getPrecision() {
3160
+ return 2;
3161
+ }
3072
3162
  getExtents(target) {
3073
3163
  this.scene.traverseVisible((object) => !object.children.length && target.expandByObject(object));
3074
3164
  return target;
@@ -3091,27 +3181,45 @@ class ModelImpl {
3091
3181
  }
3092
3182
  return false;
3093
3183
  }
3184
+ hasHandle(handle) {
3185
+ return !handle.includes(":") || handle.split(":", 1)[0] === this.id + "";
3186
+ }
3094
3187
  getOwnObjects(objects) {
3095
3188
  if (!Array.isArray(objects))
3096
3189
  objects = [objects];
3097
3190
  return objects.filter((object) => this.hasObject(object));
3098
3191
  }
3192
+ getOwnHandles(handles) {
3193
+ if (!Array.isArray(handles))
3194
+ handles = [handles];
3195
+ return handles.filter((handle) => this.hasHandle(handle));
3196
+ }
3099
3197
  getObjectsByHandles(handles) {
3100
- const handleSet = new Set(handles);
3198
+ const ownHandles = this.getOwnHandles(handles);
3199
+ if (ownHandles.length === 0)
3200
+ return [];
3201
+ const handleSet = new Set(ownHandles.map((handle) => handle.slice(handle.indexOf(":") + 1)));
3101
3202
  const objects = [];
3102
- this.scene.traverse((object) => handleSet.has(object.userData.handle) && objects.push(object));
3203
+ this.scene.traverse((object) => {
3204
+ const handle = object.userData.handle;
3205
+ if (handle && handleSet.has(handle))
3206
+ objects.push(object);
3207
+ });
3103
3208
  return objects;
3104
3209
  }
3105
3210
  getHandlesByObjects(objects) {
3106
- if (!Array.isArray(objects))
3107
- objects = [objects];
3108
- const handlesSet = new Set();
3109
- this.getOwnObjects(objects).forEach((object) => handlesSet.add(object.userData.handle));
3110
- return Array.from(handlesSet);
3211
+ const ownObjects = this.getOwnObjects(objects);
3212
+ if (ownObjects.length === 0)
3213
+ return [];
3214
+ const handleSet = new Set();
3215
+ ownObjects.forEach((object) => {
3216
+ const handle = object.userData.handle;
3217
+ if (handle)
3218
+ handleSet.add(`${this.id}:${handle}`);
3219
+ });
3220
+ return Array.from(handleSet);
3111
3221
  }
3112
3222
  hideObjects(objects) {
3113
- if (!Array.isArray(objects))
3114
- objects = [objects];
3115
3223
  this.getOwnObjects(objects).forEach((object) => (object.visible = false));
3116
3224
  return this;
3117
3225
  }
@@ -3127,8 +3235,6 @@ class ModelImpl {
3127
3235
  return this;
3128
3236
  }
3129
3237
  showObjects(objects) {
3130
- if (!Array.isArray(objects))
3131
- objects = [objects];
3132
3238
  this.getOwnObjects(objects).forEach((object) => {
3133
3239
  object.visible = true;
3134
3240
  object.traverseAncestors((parent) => (parent.visible = true));
@@ -3184,42 +3290,6 @@ class ModelImpl {
3184
3290
  }
3185
3291
  }
3186
3292
 
3187
- class GLTFFileLoader extends Loader {
3188
- constructor(viewer) {
3189
- super();
3190
- this.viewer = viewer;
3191
- }
3192
- isSupport(file, format) {
3193
- return ((typeof file === "string" || file instanceof globalThis.File || file instanceof ArrayBuffer) &&
3194
- /(gltf|glb)$/i.test(format));
3195
- }
3196
- async load(file, format, params) {
3197
- const manager = new GLTFLoadingManager(file, params);
3198
- const loader = new GLTFLoader(manager);
3199
- loader.setPath(manager.path);
3200
- loader.setCrossOrigin(params.crossOrigin || loader.crossOrigin);
3201
- loader.setWithCredentials(params.withCredentials || loader.withCredentials);
3202
- const progress = (event) => {
3203
- const { lengthComputable, loaded, total } = event;
3204
- const progress = lengthComputable ? loaded / total : 1;
3205
- this.viewer.emitEvent({ type: "geometryprogress", data: progress, file });
3206
- };
3207
- const gltf = await loader.loadAsync(manager.fileURL, progress);
3208
- if (!this.viewer.scene)
3209
- return this;
3210
- const modelImpl = new ModelImpl(gltf.scene);
3211
- modelImpl.loader = this;
3212
- modelImpl.viewer = this.viewer;
3213
- this.viewer.scene.add(gltf.scene);
3214
- this.viewer.models.push(modelImpl);
3215
- this.viewer.syncOptions();
3216
- this.viewer.syncOverlay();
3217
- this.viewer.update();
3218
- this.viewer.emitEvent({ type: "databasechunk", data: gltf.scene, file });
3219
- return this;
3220
- }
3221
- }
3222
-
3223
3293
  class DynamicModelImpl extends ModelImpl {
3224
3294
  getExtents(target) {
3225
3295
  return target.union(this.gltfLoader.getTotalGeometryExtent());
@@ -3238,31 +3308,40 @@ class DynamicModelImpl extends ModelImpl {
3238
3308
  return this.gltfLoader.originalObjects.has(object);
3239
3309
  }
3240
3310
  getObjectsByHandles(handles) {
3241
- const handlesSet = new Set(handles);
3311
+ const ownHandles = this.getOwnHandles(handles);
3312
+ if (ownHandles.length === 0)
3313
+ return [];
3314
+ const handlesSet = new Set(ownHandles);
3242
3315
  const objects = [];
3243
3316
  handlesSet.forEach((handle) => {
3244
- const handle2 = `${this.modelId}_${handle}`;
3245
- const handles = this.gltfLoader.handleToObjects.get(handle2) || [];
3246
- objects.push(...Array.from(handles));
3317
+ objects.push(...this.gltfLoader.getObjectsByHandle(handle));
3247
3318
  });
3248
3319
  return objects;
3249
3320
  }
3250
3321
  getHandlesByObjects(objects) {
3251
- const handles = super.getHandlesByObjects(objects);
3252
- return handles.map((x) => x.split("_").pop());
3322
+ const ownObjects = this.getOwnObjects(objects);
3323
+ if (ownObjects.length === 0)
3324
+ return [];
3325
+ const handleSet = new Set();
3326
+ ownObjects.forEach((object) => {
3327
+ const handle = object.userData.handle;
3328
+ if (handle)
3329
+ handleSet.add(handle);
3330
+ });
3331
+ return Array.from(handleSet);
3253
3332
  }
3254
3333
  hideObjects(objects) {
3255
- const handles = super.getHandlesByObjects(objects);
3334
+ const handles = this.getHandlesByObjects(objects);
3256
3335
  this.gltfLoader.hideObjects(handles);
3257
3336
  return this;
3258
3337
  }
3259
3338
  isolateObjects(objects) {
3260
- const handles = super.getHandlesByObjects(objects);
3339
+ const handles = this.getHandlesByObjects(objects);
3261
3340
  this.gltfLoader.isolateObjects(new Set(handles));
3262
3341
  return this;
3263
3342
  }
3264
3343
  showObjects(objects) {
3265
- const handles = super.getHandlesByObjects(objects);
3344
+ const handles = this.getHandlesByObjects(objects);
3266
3345
  this.gltfLoader.showObjects(handles);
3267
3346
  return this;
3268
3347
  }
@@ -3326,11 +3405,14 @@ class GltfStructure {
3326
3405
  this.materials = new Map();
3327
3406
  this.textureCache = new Map();
3328
3407
  this.materialCache = new Map();
3408
+ this.uri = "";
3409
+ this._nextObjectId = 0;
3329
3410
  }
3330
3411
  async initialize(loader) {
3331
3412
  this.json = await this.loadController.loadJson();
3332
3413
  this.baseUrl = await this.loadController.baseUrl();
3333
3414
  this.loader = loader;
3415
+ this.uri = this.json.buffers[0].uri || "";
3334
3416
  }
3335
3417
  clear() {
3336
3418
  this.json = null;
@@ -3424,7 +3506,7 @@ class GltfStructure {
3424
3506
  await this.loader.waitForChunkSlot();
3425
3507
  try {
3426
3508
  const length = range.end - range.start;
3427
- const buffer = await this.loadController.loadBinaryData([{ offset: range.start, length }]);
3509
+ const buffer = await this.loadController.loadBinaryData([{ offset: range.start, length }], this.uri);
3428
3510
  for (const req of range.requests) {
3429
3511
  const relOffset = req.offset - range.start;
3430
3512
  try {
@@ -3741,6 +3823,7 @@ class GltfStructure {
3741
3823
  }
3742
3824
  }
3743
3825
 
3826
+ const STRUCTURE_ID_SEPARATOR = ":";
3744
3827
  class DynamicGltfLoader {
3745
3828
  constructor(camera, scene, renderer) {
3746
3829
  this.camera = camera;
@@ -3753,6 +3836,7 @@ class DynamicGltfLoader {
3753
3836
  geometryerror: [],
3754
3837
  update: [],
3755
3838
  geometrymemory: [],
3839
+ optimizationprogress: [],
3756
3840
  };
3757
3841
  this.loadDistance = 100;
3758
3842
  this.unloadDistance = 150;
@@ -3794,7 +3878,6 @@ class DynamicGltfLoader {
3794
3878
  this.hiddenHandles = new Set();
3795
3879
  this.newOptimizedObjects = new Set();
3796
3880
  this.oldOptimizeObjects = new Set();
3797
- this.maxConcurrentChunks = 8;
3798
3881
  this.activeChunkLoads = 0;
3799
3882
  this.chunkQueue = [];
3800
3883
  this.objectIdToIndex = new Map();
@@ -3803,6 +3886,7 @@ class DynamicGltfLoader {
3803
3886
  this.maxConcurrentChunks = 6;
3804
3887
  this.mergedObjectMap = new Map();
3805
3888
  this.mergedGeometryVisibility = new Map();
3889
+ this._webglInfoCache = null;
3806
3890
  }
3807
3891
  setVisibleEdges(visible) {
3808
3892
  this.visibleEdges = visible;
@@ -3906,6 +3990,123 @@ class DynamicGltfLoader {
3906
3990
  this.updateMemoryIndicator();
3907
3991
  console.log(`Final memory usage: ${Math.round(currentMemoryUsage / (1024 * 1024))}MB`);
3908
3992
  }
3993
+ getStats() {
3994
+ let totalObjects = 0;
3995
+ let renderedObjects = 0;
3996
+ let totalTriangles = 0;
3997
+ let renderedTriangles = 0;
3998
+ let totalLines = 0;
3999
+ let renderedLines = 0;
4000
+ let totalEdges = 0;
4001
+ let renderedEdges = 0;
4002
+ this.scene.traverse((object) => {
4003
+ totalObjects++;
4004
+ const geometry = object.geometry;
4005
+ if (!geometry) return;
4006
+ let triCount = 0;
4007
+ if (geometry.index) {
4008
+ triCount = Math.floor(geometry.index.count / 3);
4009
+ } else if (geometry.attributes && geometry.attributes.position) {
4010
+ triCount = Math.floor(geometry.attributes.position.count / 3);
4011
+ }
4012
+ totalTriangles += triCount;
4013
+ let lineCount = 0;
4014
+ if (geometry.index) {
4015
+ lineCount = Math.floor(geometry.index.count / 2);
4016
+ } else if (geometry.attributes && geometry.attributes.position) {
4017
+ lineCount = Math.floor(geometry.attributes.position.count / 2);
4018
+ }
4019
+ if (object.type === "Line" || object.type === "LineSegments" || object.type === "LineLoop") {
4020
+ if (object.userData.isEdge) {
4021
+ totalEdges += lineCount;
4022
+ } else {
4023
+ totalLines += lineCount;
4024
+ }
4025
+ }
4026
+ if (object.visible !== false) {
4027
+ if (object.isMesh || object.isLine || object.isPoints) {
4028
+ renderedObjects++;
4029
+ if (object.isMesh) {
4030
+ renderedTriangles += triCount;
4031
+ } else if (object.type === "Line" || object.type === "LineSegments" || object.type === "LineLoop") {
4032
+ if (object.userData.isEdge) {
4033
+ renderedEdges += lineCount;
4034
+ } else {
4035
+ renderedLines += lineCount;
4036
+ }
4037
+ }
4038
+ }
4039
+ }
4040
+ });
4041
+ const geometryCount = this.geometryCache ? this.geometryCache.size : 0;
4042
+ const geometryMemoryBytes = Array.from(this.geometryCache?.values?.() || []).reduce((a, b) => a + b, 0);
4043
+ const uniqueMaterialIds = new Set();
4044
+ const uniqueTextureIds = new Set();
4045
+ if (Array.isArray(this.structures)) {
4046
+ for (const structure of this.structures) {
4047
+ console.log(structure.materialCache.values());
4048
+ try {
4049
+ for (const entry of structure.materialCache.values()) {
4050
+ if (entry?.mesh?.uuid) uniqueMaterialIds.add(entry.mesh.uuid);
4051
+ if (entry?.points?.uuid) uniqueMaterialIds.add(entry.points.uuid);
4052
+ if (entry?.lines?.uuid) uniqueMaterialIds.add(entry.lines.uuid);
4053
+ }
4054
+ } catch (exp) {
4055
+ console.error("Error adding material to uniqueMaterialIds", exp);
4056
+ }
4057
+ }
4058
+ }
4059
+ const materialCount = uniqueMaterialIds.size;
4060
+ const textureCount = uniqueTextureIds.size;
4061
+ const estimatedGpuMemoryBytes = geometryMemoryBytes;
4062
+ if (!this._webglInfoCache) {
4063
+ try {
4064
+ const gl = this.renderer.getContext();
4065
+ const dbgInfo = gl.getExtension("WEBGL_debug_renderer_info");
4066
+ if (dbgInfo) {
4067
+ const rendererStr = gl.getParameter(dbgInfo.UNMASKED_RENDERER_WEBGL);
4068
+ const vendorStr = gl.getParameter(dbgInfo.UNMASKED_VENDOR_WEBGL);
4069
+ this._webglInfoCache = { renderer: rendererStr, vendor: vendorStr };
4070
+ } else {
4071
+ this._webglInfoCache = { renderer: null, vendor: null };
4072
+ }
4073
+ } catch (e) {
4074
+ console.error("Error getting webgl info", e);
4075
+ this._webglInfoCache = { renderer: null, vendor: null };
4076
+ }
4077
+ }
4078
+ const size = new Vector2();
4079
+ if (this.renderer && this.renderer.getSize) {
4080
+ this.renderer.getSize(size);
4081
+ }
4082
+ return {
4083
+ scene: {
4084
+ beforeOptimization: {
4085
+ objects: totalObjects - renderedObjects,
4086
+ triangles: totalTriangles - renderedTriangles,
4087
+ lines: totalLines - renderedLines,
4088
+ edges: totalEdges - renderedEdges,
4089
+ },
4090
+ afterOptimization: {
4091
+ objects: renderedObjects,
4092
+ triangles: renderedTriangles,
4093
+ lines: renderedLines,
4094
+ edges: renderedEdges,
4095
+ },
4096
+ },
4097
+ memory: {
4098
+ geometries: { count: geometryCount, bytes: geometryMemoryBytes },
4099
+ textures: { count: textureCount },
4100
+ materials: { count: materialCount },
4101
+ totalEstimatedGpuBytes: estimatedGpuMemoryBytes,
4102
+ },
4103
+ system: {
4104
+ webglRenderer: this._webglInfoCache?.renderer || "",
4105
+ webglVendor: this._webglInfoCache?.vendor || "",
4106
+ viewport: { width: size.x || 0, height: size.y || 0 },
4107
+ },
4108
+ };
4109
+ }
3909
4110
  async loadNode(nodeId, onLoadFinishCb) {
3910
4111
  const node = this.nodes.get(nodeId);
3911
4112
  if (!node || node.loaded || node.loading) return;
@@ -4076,7 +4277,7 @@ class DynamicGltfLoader {
4076
4277
  if (node.handle) {
4077
4278
  mesh.userData.handle = node.handle;
4078
4279
  } else {
4079
- mesh.userData.handle = `${node.structure.id}_${mesh.userData.handle}`;
4280
+ mesh.userData.handle = this.getFullHandle(node.structure.id, mesh.userData.handle);
4080
4281
  }
4081
4282
  if (mesh.material.name === "edges") {
4082
4283
  mesh.userData.isEdge = true;
@@ -4217,12 +4418,15 @@ class DynamicGltfLoader {
4217
4418
  })),
4218
4419
  });
4219
4420
  }
4421
+ getFullHandle(structureId, originalHandle) {
4422
+ return `${structureId}${STRUCTURE_ID_SEPARATOR}${originalHandle}`;
4423
+ }
4220
4424
  async processNodeHierarchy(structure, nodeId, parentGroup) {
4221
4425
  const nodeDef = structure.json.nodes[nodeId];
4222
4426
  let nodeGroup = null;
4223
4427
  let handle = null;
4224
4428
  if (nodeDef.extras?.handle) {
4225
- handle = `${structure.id}_${nodeDef.extras.handle}`;
4429
+ handle = this.getFullHandle(structure.id, nodeDef.extras.handle);
4226
4430
  }
4227
4431
  if (nodeDef.camera !== undefined) {
4228
4432
  const camera = this.loadCamera(structure, nodeDef.camera, nodeDef);
@@ -4239,7 +4443,7 @@ class DynamicGltfLoader {
4239
4443
  if (nodeDef.extras) {
4240
4444
  nodeGroup.userData = { ...nodeDef.extras };
4241
4445
  if (nodeGroup.userData.handle) {
4242
- nodeGroup.userData.handle = `${structure.id}_${nodeGroup.userData.handle}`;
4446
+ nodeGroup.userData.handle = this.getFullHandle(structure.id, nodeGroup.userData.handle);
4243
4447
  }
4244
4448
  }
4245
4449
  if (nodeDef.matrix) {
@@ -4284,7 +4488,7 @@ class DynamicGltfLoader {
4284
4488
  this.edgeNodes.push(uniqueNodeId);
4285
4489
  }
4286
4490
  if (meshDef.extras && meshDef.extras.handle) {
4287
- handle = `${structure.id}_${meshDef.extras.handle}`;
4491
+ handle = this.getFullHandle(structure.id, meshDef.extras.handle);
4288
4492
  }
4289
4493
  this.nodes.set(uniqueNodeId, {
4290
4494
  position: nodeGroup ? nodeGroup.position.clone() : new Vector3().setFromMatrixPosition(nodeMatrix),
@@ -4297,7 +4501,7 @@ class DynamicGltfLoader {
4297
4501
  structure,
4298
4502
  extras: nodeDef.extras,
4299
4503
  geometryExtents,
4300
- handle,
4504
+ handle: handle || this.getFullHandle(structure.id, structure._nextObjectId++),
4301
4505
  });
4302
4506
  }
4303
4507
  if (nodeDef.children) {
@@ -4370,12 +4574,12 @@ class DynamicGltfLoader {
4370
4574
  }
4371
4575
  }
4372
4576
  async loadNodes() {
4373
- console.time("process nodes");
4577
+ console.time("Process nodes");
4374
4578
  await this.processNodes();
4375
- console.timeEnd("process nodes");
4376
- console.time("optimize scene");
4579
+ console.timeEnd("Process nodes");
4580
+ console.time("Optimize scene");
4377
4581
  await this.optimizeScene();
4378
- console.timeEnd("optimize scene");
4582
+ console.timeEnd("Optimize scene");
4379
4583
  }
4380
4584
  cleanupPartialLoad() {
4381
4585
  this.nodesToLoad.forEach((nodeId) => {
@@ -4696,7 +4900,7 @@ class DynamicGltfLoader {
4696
4900
  this.handleToObjects.set(fullHandle, new Set());
4697
4901
  }
4698
4902
  this.handleToObjects.get(fullHandle).add(object);
4699
- object.userData.structureId = object.userData.handle.split("_")[0];
4903
+ object.userData.structureId = object.userData.handle.split(STRUCTURE_ID_SEPARATOR)[0];
4700
4904
  }
4701
4905
  getObjectsByHandle(handle) {
4702
4906
  if (!handle) return [];
@@ -4762,10 +4966,28 @@ class DynamicGltfLoader {
4762
4966
  }
4763
4967
  this.originalObjects.add(object);
4764
4968
  }
4765
- optimizeScene() {
4969
+ yieldToUI() {
4970
+ return new Promise((resolve) => {
4971
+ requestAnimationFrame(() => {
4972
+ setTimeout(resolve, 0);
4973
+ });
4974
+ });
4975
+ }
4976
+ async optimizeScene() {
4977
+ console.log("Starting scene optimization...");
4978
+ this.dispatchEvent("optimizationprogress", {
4979
+ phase: "start",
4980
+ progress: 0,
4981
+ message: "Starting optimization...",
4982
+ });
4766
4983
  this.originalObjects.clear();
4767
4984
  this.originalObjectsToSelection.clear();
4768
4985
  const structureGroups = new Map();
4986
+ this.dispatchEvent("optimizationprogress", {
4987
+ phase: "collecting",
4988
+ progress: 5,
4989
+ message: "Collecting scene objects...",
4990
+ });
4769
4991
  this.scene.traverse((object) => {
4770
4992
  if (object.userData.structureId) {
4771
4993
  const structureId = object.userData.structureId;
@@ -4794,16 +5016,44 @@ class DynamicGltfLoader {
4794
5016
  }
4795
5017
  }
4796
5018
  });
5019
+ let processedGroups = 0;
5020
+ const totalGroups = structureGroups.size;
5021
+ this.dispatchEvent("optimizationprogress", {
5022
+ phase: "merging",
5023
+ progress: 10,
5024
+ message: `Merging ${totalGroups} structure groups...`,
5025
+ current: 0,
5026
+ total: totalGroups,
5027
+ });
4797
5028
  for (const group of structureGroups.values()) {
4798
5029
  group.mapMeshes.clear();
4799
5030
  group.mapLines.clear();
4800
5031
  group.mapLineSegments.clear();
4801
5032
  group.mapPoints.clear();
4802
- this.mergeMeshGroups(group.meshes, group.rootGroup);
4803
- this.mergeLineGroups(group.lines, group.rootGroup);
4804
- this.mergeLineSegmentGroups(group.lineSegments, group.rootGroup);
4805
- this.mergePointsGroups(group.points, group.rootGroup);
5033
+ await this.mergeMeshGroups(group.meshes, group.rootGroup);
5034
+ await this.yieldToUI();
5035
+ await this.mergeLineGroups(group.lines, group.rootGroup);
5036
+ await this.yieldToUI();
5037
+ await this.mergeLineSegmentGroups(group.lineSegments, group.rootGroup);
5038
+ await this.yieldToUI();
5039
+ await this.mergePointsGroups(group.points, group.rootGroup);
5040
+ processedGroups++;
5041
+ const progress = 10 + Math.round((processedGroups / totalGroups) * 80);
5042
+ this.dispatchEvent("optimizationprogress", {
5043
+ phase: "merging",
5044
+ progress,
5045
+ message: `Processing structure ${processedGroups}/${totalGroups}...`,
5046
+ current: processedGroups,
5047
+ total: totalGroups,
5048
+ });
5049
+ console.log(`Optimization progress: ${processedGroups}/${totalGroups} structure groups processed (${progress}%)`);
5050
+ await this.yieldToUI();
4806
5051
  }
5052
+ this.dispatchEvent("optimizationprogress", {
5053
+ phase: "finalizing",
5054
+ progress: 95,
5055
+ message: "Finalizing optimization...",
5056
+ });
4807
5057
  this.originalObjects.forEach((obj) => {
4808
5058
  obj.visible = false;
4809
5059
  if (!(obj instanceof Points) && !obj.userData.isEdge) {
@@ -4812,9 +5062,15 @@ class DynamicGltfLoader {
4812
5062
  });
4813
5063
  this.initializeObjectVisibility();
4814
5064
  console.log(`Optimization complete. Total objects: ${this.maxObjectId}`);
5065
+ this.dispatchEvent("optimizationprogress", {
5066
+ phase: "complete",
5067
+ progress: 100,
5068
+ message: `Optimization complete! ${this.maxObjectId} objects processed.`,
5069
+ });
4815
5070
  this.dispatchEvent("update");
4816
5071
  }
4817
- mergeMeshGroups(materialGroups, rootGroup) {
5072
+ async mergeMeshGroups(materialGroups, rootGroup) {
5073
+ let processedGroups = 0;
4818
5074
  for (const group of materialGroups) {
4819
5075
  if (!group.material) {
4820
5076
  console.warn("Skipping mesh group with null material");
@@ -4828,8 +5084,6 @@ class DynamicGltfLoader {
4828
5084
  let currentVertexOffset = 0;
4829
5085
  for (const mesh of group.objects) {
4830
5086
  const geometry = mesh.geometry.clone();
4831
- mesh.updateWorldMatrix(true, false);
4832
- geometry.applyMatrix4(mesh.matrixWorld);
4833
5087
  const handle = mesh.userData.handle;
4834
5088
  if (!this.objectIdToIndex.has(handle)) {
4835
5089
  this.objectIdToIndex.set(handle, this.maxObjectId++);
@@ -4888,6 +5142,10 @@ class DynamicGltfLoader {
4888
5142
  this.handleToOptimizedObjects.set(handle, mergedObjects);
4889
5143
  }
4890
5144
  });
5145
+ processedGroups++;
5146
+ if (processedGroups % 5 === 0) {
5147
+ await this.yieldToUI();
5148
+ }
4891
5149
  } catch (error) {
4892
5150
  console.error("Failed to merge meshes for material:", error);
4893
5151
  group.objects.forEach((mesh) => {
@@ -4896,7 +5154,8 @@ class DynamicGltfLoader {
4896
5154
  }
4897
5155
  }
4898
5156
  }
4899
- mergeLineGroups(materialGroups, rootGroup) {
5157
+ async mergeLineGroups(materialGroups, rootGroup) {
5158
+ let processedGroups = 0;
4900
5159
  for (const group of materialGroups) {
4901
5160
  if (group.objects.length === 0) continue;
4902
5161
  if (!group.material) {
@@ -4915,7 +5174,9 @@ class DynamicGltfLoader {
4915
5174
  let posOffset = 0;
4916
5175
  const indices = [];
4917
5176
  let vertexOffset = 0;
5177
+ let isEdge = false;
4918
5178
  group.objects.forEach((line) => {
5179
+ isEdge = line.userData.isEdge;
4919
5180
  const geometry = line.geometry;
4920
5181
  const positionAttr = geometry.attributes.position;
4921
5182
  const vertexCount = positionAttr.count;
@@ -4928,12 +5189,9 @@ class DynamicGltfLoader {
4928
5189
  vertexCount,
4929
5190
  });
4930
5191
  currentVertexOffset += vertexCount;
4931
- line.updateWorldMatrix(true, false);
4932
- const matrix = line.matrixWorld;
4933
5192
  const vector = new Vector3();
4934
5193
  for (let i = 0; i < vertexCount; i++) {
4935
5194
  vector.fromBufferAttribute(positionAttr, i);
4936
- vector.applyMatrix4(matrix);
4937
5195
  positions[posOffset++] = vector.x;
4938
5196
  positions[posOffset++] = vector.y;
4939
5197
  positions[posOffset++] = vector.z;
@@ -4966,6 +5224,7 @@ class DynamicGltfLoader {
4966
5224
  geometry.setAttribute("visibility", new BufferAttribute(visibilityArray, 1));
4967
5225
  const visibilityMaterial = this.createVisibilityMaterial(group.material);
4968
5226
  const mergedLine = new LineSegments(geometry, visibilityMaterial);
5227
+ mergedLine.userData.isEdge = isEdge;
4969
5228
  const mergedObjects = [mergedLine];
4970
5229
  if (this.useVAO) {
4971
5230
  this.createVAO(mergedLine);
@@ -4988,9 +5247,14 @@ class DynamicGltfLoader {
4988
5247
  this.handleToOptimizedObjects.set(handle, mergedObjects);
4989
5248
  }
4990
5249
  });
5250
+ processedGroups++;
5251
+ if (processedGroups % 5 === 0) {
5252
+ await this.yieldToUI();
5253
+ }
4991
5254
  }
4992
5255
  }
4993
- mergeLineSegmentGroups(materialGroups, rootGroup) {
5256
+ async mergeLineSegmentGroups(materialGroups, rootGroup) {
5257
+ let processedGroups = 0;
4994
5258
  for (const group of materialGroups) {
4995
5259
  if (!group.material) {
4996
5260
  console.warn("Skipping line segment group with null material");
@@ -5002,10 +5266,10 @@ class DynamicGltfLoader {
5002
5266
  const handles = new Set();
5003
5267
  const objectMapping = new Map();
5004
5268
  let currentVertexOffset = 0;
5269
+ let isEdge = false;
5005
5270
  for (const line of group.objects) {
5271
+ isEdge = line.userData.isEdge;
5006
5272
  const geometry = line.geometry.clone();
5007
- line.updateWorldMatrix(true, false);
5008
- geometry.applyMatrix4(line.matrixWorld);
5009
5273
  const handle = line.userData.handle;
5010
5274
  if (!this.objectIdToIndex.has(handle)) {
5011
5275
  this.objectIdToIndex.set(handle, this.maxObjectId++);
@@ -5038,6 +5302,7 @@ class DynamicGltfLoader {
5038
5302
  mergedGeometry.setAttribute("visibility", new BufferAttribute(visibilityArray, 1));
5039
5303
  const visibilityMaterial = this.createVisibilityMaterial(group.material);
5040
5304
  const mergedLine = new LineSegments(mergedGeometry, visibilityMaterial);
5305
+ mergedLine.userData.isEdge = isEdge;
5041
5306
  if (this.useVAO) {
5042
5307
  this.createVAO(mergedLine);
5043
5308
  }
@@ -5064,6 +5329,10 @@ class DynamicGltfLoader {
5064
5329
  this.handleToOptimizedObjects.set(handle, mergedObjects);
5065
5330
  }
5066
5331
  });
5332
+ processedGroups++;
5333
+ if (processedGroups % 5 === 0) {
5334
+ await this.yieldToUI();
5335
+ }
5067
5336
  } catch (error) {
5068
5337
  console.warn("Failed to merge line segments for material:", error);
5069
5338
  group.objects.forEach((line) => {
@@ -5072,7 +5341,8 @@ class DynamicGltfLoader {
5072
5341
  }
5073
5342
  }
5074
5343
  }
5075
- mergePointsGroups(materialGroups, rootGroup) {
5344
+ async mergePointsGroups(materialGroups, rootGroup) {
5345
+ let processedGroups = 0;
5076
5346
  for (const group of materialGroups) {
5077
5347
  if (!group.material) {
5078
5348
  console.warn("Skipping points group with null material");
@@ -5084,8 +5354,6 @@ class DynamicGltfLoader {
5084
5354
  const handles = new Set();
5085
5355
  for (const points of group.objects) {
5086
5356
  const geometry = points.geometry.clone();
5087
- points.updateWorldMatrix(true, false);
5088
- geometry.applyMatrix4(points.matrixWorld);
5089
5357
  geometries.push(geometry);
5090
5358
  optimizedObjects.push(points);
5091
5359
  handles.add(points.userData.handle);
@@ -5114,6 +5382,10 @@ class DynamicGltfLoader {
5114
5382
  this.handleToOptimizedObjects.set(handle, mergedObjects);
5115
5383
  }
5116
5384
  });
5385
+ processedGroups++;
5386
+ if (processedGroups % 5 === 0) {
5387
+ await this.yieldToUI();
5388
+ }
5117
5389
  } catch (error) {
5118
5390
  console.warn("Failed to merge points for material:", error);
5119
5391
  group.objects.forEach((points) => {
@@ -5132,7 +5404,6 @@ class DynamicGltfLoader {
5132
5404
  const hasNormals = lineSegmentsArray.some((segment) => segment.geometry.attributes.normal !== undefined);
5133
5405
  lineSegmentsArray.forEach((segment) => {
5134
5406
  const clonedGeometry = segment.geometry.clone();
5135
- segment.updateWorldMatrix(true, false);
5136
5407
  clonedGeometry.applyMatrix4(segment.matrixWorld);
5137
5408
  if (hasNormals && !clonedGeometry.attributes.normal) {
5138
5409
  clonedGeometry.computeVertexNormals();
@@ -5349,8 +5620,225 @@ class DynamicGltfLoader {
5349
5620
  }
5350
5621
  }
5351
5622
 
5352
- class GLTFCloudDynamicLoader {
5623
+ class GLTFLoadingManager extends LoadingManager {
5624
+ constructor(file, params = {}) {
5625
+ super();
5626
+ this.path = "";
5627
+ this.resourcePath = "";
5628
+ this.fileURL = "";
5629
+ this.dataURLs = new Map();
5630
+ this.path = params.path || "";
5631
+ const externalFiles = params.externalFiles || new Map();
5632
+ if (typeof file === "string") {
5633
+ this.fileURL = file;
5634
+ this.resourcePath = LoaderUtils.extractUrlBase(file);
5635
+ }
5636
+ else {
5637
+ externalFiles.forEach((value, key) => (this.fileURL = value === file ? key : this.fileURL));
5638
+ externalFiles.set(this.fileURL, file);
5639
+ }
5640
+ externalFiles.forEach((value, key) => {
5641
+ let dataURL;
5642
+ if (typeof value === "string")
5643
+ dataURL = value;
5644
+ else
5645
+ dataURL = URL.createObjectURL(new Blob([value]));
5646
+ this.dataURLs.set(key, dataURL);
5647
+ });
5648
+ this.setURLModifier((url) => {
5649
+ const key = decodeURI(url)
5650
+ .replace(this.path, "")
5651
+ .replace(this.resourcePath, "")
5652
+ .replace(/^(\.?\/)/, "");
5653
+ const dataURL = this.dataURLs.get(key);
5654
+ return dataURL !== null && dataURL !== void 0 ? dataURL : url;
5655
+ });
5656
+ }
5657
+ dispose() {
5658
+ this.dataURLs.forEach((dataURL) => URL.revokeObjectURL(dataURL));
5659
+ }
5660
+ }
5661
+
5662
+ const BINARY_EXTENSION_HEADER_MAGIC = "glTF";
5663
+ const BINARY_EXTENSION_HEADER_LENGTH = 12;
5664
+ const BINARY_EXTENSION_CHUNK_TYPES = { JSON: 0x4e4f534a, BIN: 0x004e4042 };
5665
+ class GLTFBinaryExtension {
5666
+ constructor(data) {
5667
+ const headerView = new DataView(data, 0, BINARY_EXTENSION_HEADER_LENGTH);
5668
+ const textDecoder = new TextDecoder();
5669
+ const magic = textDecoder.decode(new Uint8Array(data.slice(0, 4)));
5670
+ if (magic !== BINARY_EXTENSION_HEADER_MAGIC) {
5671
+ this.content = textDecoder.decode(data);
5672
+ return;
5673
+ }
5674
+ const header = {
5675
+ magic,
5676
+ version: headerView.getUint32(4, true),
5677
+ length: headerView.getUint32(8, true),
5678
+ };
5679
+ if (header.magic !== BINARY_EXTENSION_HEADER_MAGIC) {
5680
+ throw new Error("Unsupported glTF-Binary header.");
5681
+ }
5682
+ if (header.version < 2.0) {
5683
+ throw new Error("Legacy binary file detected.");
5684
+ }
5685
+ const chunkContentsLength = header.length - BINARY_EXTENSION_HEADER_LENGTH;
5686
+ const chunkView = new DataView(data, BINARY_EXTENSION_HEADER_LENGTH);
5687
+ let chunkIndex = 0;
5688
+ while (chunkIndex < chunkContentsLength) {
5689
+ const chunkLength = chunkView.getUint32(chunkIndex, true);
5690
+ chunkIndex += 4;
5691
+ const chunkType = chunkView.getUint32(chunkIndex, true);
5692
+ chunkIndex += 4;
5693
+ if (chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON) {
5694
+ const contentArray = new Uint8Array(data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength);
5695
+ this.content = textDecoder.decode(contentArray);
5696
+ }
5697
+ else if (chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN) {
5698
+ const byteOffset = BINARY_EXTENSION_HEADER_LENGTH + chunkIndex;
5699
+ this.body = data.slice(byteOffset, byteOffset + chunkLength);
5700
+ }
5701
+ chunkIndex += chunkLength;
5702
+ }
5703
+ if (typeof this.content === "undefined") {
5704
+ throw new Error("JSON content not found.");
5705
+ }
5706
+ }
5707
+ }
5708
+
5709
+ class RangesLoader {
5710
+ constructor() {
5711
+ this.requestHeader = {};
5712
+ this.withCredentials = false;
5713
+ this.abortSignal = undefined;
5714
+ }
5715
+ setRequestHeader(requestHeader) {
5716
+ this.requestHeader = requestHeader;
5717
+ }
5718
+ setWithCredentials(withCredentials) {
5719
+ this.withCredentials = withCredentials;
5720
+ }
5721
+ setAbortSignal(abortSignal) {
5722
+ this.abortSignal = abortSignal;
5723
+ }
5724
+ async load(url, ranges) {
5725
+ const init = {
5726
+ headers: {
5727
+ ...this.requestHeader,
5728
+ Range: "bytes=" + ranges.map((x) => `${x.offset}-${x.offset + x.length - 1}`).join(","),
5729
+ },
5730
+ credentials: this.withCredentials ? "include" : "same-origin",
5731
+ signal: this.abortSignal,
5732
+ };
5733
+ const response = await fetch(url, init);
5734
+ if (!response.ok) {
5735
+ throw new Error(`Failed to fetch "${url}", status ${response.status}`);
5736
+ }
5737
+ if (response.status !== 206) {
5738
+ const arrayBuffer = await response.arrayBuffer();
5739
+ return this.extractRanges(arrayBuffer, ranges);
5740
+ }
5741
+ return response.arrayBuffer();
5742
+ }
5743
+ extractRanges(arrayBuffer, ranges) {
5744
+ const totalLength = ranges.reduce((sum, range) => sum + range.length, 0);
5745
+ const result = new Uint8Array(totalLength);
5746
+ let offset = 0;
5747
+ for (const range of ranges) {
5748
+ const chunk = new Uint8Array(arrayBuffer, range.offset, range.length);
5749
+ result.set(chunk, offset);
5750
+ offset += range.length;
5751
+ }
5752
+ return result.buffer;
5753
+ }
5754
+ }
5755
+
5756
+ class GLTFFileDynamicLoader extends Loader {
5757
+ constructor(viewer) {
5758
+ super();
5759
+ this.viewer = viewer;
5760
+ }
5761
+ dispose() {
5762
+ if (this.gltfLoader)
5763
+ this.gltfLoader.clear();
5764
+ if (this.manager)
5765
+ this.manager.dispose();
5766
+ }
5767
+ isSupport(file, format) {
5768
+ return ((typeof file === "string" || file instanceof globalThis.File || file instanceof ArrayBuffer) &&
5769
+ /(gltf|glb)$/i.test(format));
5770
+ }
5771
+ async load(file, format, params) {
5772
+ this.manager = new GLTFLoadingManager(file, params);
5773
+ const scene = new Group();
5774
+ this.gltfLoader = new DynamicGltfLoader(this.viewer.camera, scene, this.viewer.renderer);
5775
+ this.gltfLoader.memoryLimit = this.viewer.options.memoryLimit;
5776
+ this.gltfLoader.visibleEdges = this.viewer.options.edgeModel;
5777
+ const modelImpl = new DynamicModelImpl(scene);
5778
+ modelImpl.id = params.modelId || this.extractFileName(file);
5779
+ modelImpl.gltfLoader = this.gltfLoader;
5780
+ this.gltfLoader.addEventListener("databasechunk", () => {
5781
+ this.viewer.scene.add(scene);
5782
+ this.viewer.models.push(modelImpl);
5783
+ this.viewer.syncOptions();
5784
+ this.viewer.syncOverlay();
5785
+ this.viewer.update();
5786
+ this.viewer.emitEvent({ type: "databasechunk", data: scene, file });
5787
+ });
5788
+ this.gltfLoader.addEventListener("geometryerror", (data) => {
5789
+ this.viewer.emitEvent({ type: "geometryerror", data, file });
5790
+ });
5791
+ this.gltfLoader.addEventListener("update", (data) => {
5792
+ this.viewer.update();
5793
+ });
5794
+ const loadController = {
5795
+ loadJson: async () => {
5796
+ const loader = new FileLoader(this.manager);
5797
+ loader.setPath(this.manager.path);
5798
+ loader.setRequestHeader(params.requestHeader || {});
5799
+ loader.setWithCredentials(params.withCredentials || loader.withCredentials);
5800
+ loader.setResponseType("arraybuffer");
5801
+ const progress = (event) => {
5802
+ const { lengthComputable, loaded, total } = event;
5803
+ const progress = lengthComputable ? loaded / total : 1;
5804
+ this.viewer.emitEvent({ type: "geometryprogress", data: progress, file });
5805
+ };
5806
+ const data = await loader.loadAsync(this.manager.fileURL, progress);
5807
+ const extension = new GLTFBinaryExtension(data);
5808
+ this.gltf = JSON.parse(extension.content);
5809
+ this.bin = extension.body;
5810
+ return this.gltf;
5811
+ },
5812
+ loadBinaryData: (ranges, uri = "") => {
5813
+ const loader = new RangesLoader();
5814
+ loader.setRequestHeader(params.requestHeader || {});
5815
+ loader.setWithCredentials(params.withCredentials || false);
5816
+ loader.setAbortSignal(this.gltfLoader.abortController.signal);
5817
+ if (this.bin)
5818
+ return loader.extractRanges(this.bin, ranges);
5819
+ const path = this.manager.path || this.manager.resourcePath;
5820
+ const url = LoaderUtils.resolveURL(uri, path);
5821
+ return loader.load(this.manager.resolveURL(url), ranges);
5822
+ },
5823
+ baseUrl: () => {
5824
+ const path = this.manager.path || this.manager.resourcePath;
5825
+ return Promise.resolve(path);
5826
+ },
5827
+ };
5828
+ const structure = new GltfStructure(modelImpl.id, loadController);
5829
+ await this.gltfLoader.loadStructure(structure);
5830
+ await this.gltfLoader.loadNodes();
5831
+ return this;
5832
+ }
5833
+ cancel() {
5834
+ if (this.gltfLoader)
5835
+ this.gltfLoader.abortLoading();
5836
+ }
5837
+ }
5838
+
5839
+ class GLTFCloudDynamicLoader extends Loader {
5353
5840
  constructor(viewer) {
5841
+ super();
5354
5842
  this.requestId = 0;
5355
5843
  this.viewer = viewer;
5356
5844
  }
@@ -5365,17 +5853,15 @@ class GLTFCloudDynamicLoader {
5365
5853
  typeof file.downloadResourceRange === "function" &&
5366
5854
  /.gltf$/i.test(file.database));
5367
5855
  }
5368
- async load(model, format, params) {
5856
+ async load(model, format, params = {}) {
5369
5857
  const scene = new Group();
5370
5858
  this.gltfLoader = new DynamicGltfLoader(this.viewer.camera, scene, this.viewer.renderer);
5371
5859
  this.gltfLoader.memoryLimit = this.viewer.options.memoryLimit;
5372
5860
  this.gltfLoader.setVisibleEdges(this.viewer.options.edgeModel);
5861
+ const modelImpl = new DynamicModelImpl(scene);
5862
+ modelImpl.id = model.file.id;
5863
+ modelImpl.gltfLoader = this.gltfLoader;
5373
5864
  this.gltfLoader.addEventListener("databasechunk", (data) => {
5374
- const modelImpl = new DynamicModelImpl(scene);
5375
- modelImpl.loader = this;
5376
- modelImpl.viewer = this.viewer;
5377
- modelImpl.gltfLoader = this.gltfLoader;
5378
- modelImpl.modelId = model.id;
5379
5865
  this.viewer.scene.add(scene);
5380
5866
  this.viewer.models.push(modelImpl);
5381
5867
  this.viewer.syncOptions();
@@ -5383,10 +5869,6 @@ class GLTFCloudDynamicLoader {
5383
5869
  this.viewer.update();
5384
5870
  this.viewer.emitEvent({ type: "databasechunk", data: scene, file: model.file, model });
5385
5871
  });
5386
- this.gltfLoader.addEventListener("geometryprogress", (data) => {
5387
- const progress = data.loaded / data.total;
5388
- this.viewer.emitEvent({ type: "geometryprogress", data: progress, file: model.file, model });
5389
- });
5390
5872
  this.gltfLoader.addEventListener("geometryerror", (data) => {
5391
5873
  this.viewer.emitEvent({ type: "geometryerror", data, file: model.file, model });
5392
5874
  });
@@ -5396,7 +5878,7 @@ class GLTFCloudDynamicLoader {
5396
5878
  const loadController = {
5397
5879
  loadJson: async () => {
5398
5880
  const progress = (progress) => {
5399
- this.viewer.emitEvent({ type: "geometryprogress", data: progress, file: model });
5881
+ this.viewer.emitEvent({ type: "geometryprogress", data: progress, file: model.file, model });
5400
5882
  };
5401
5883
  const arrayBuffer = await model.downloadResource(model.database, progress, this.gltfLoader.getAbortController().signal);
5402
5884
  const text = new TextDecoder().decode(arrayBuffer);
@@ -5413,7 +5895,7 @@ class GLTFCloudDynamicLoader {
5413
5895
  },
5414
5896
  baseUrl: () => Promise.resolve(`${model.httpClient.serverUrl}${model.path}/`),
5415
5897
  };
5416
- const structure = new GltfStructure(model.id, loadController);
5898
+ const structure = new GltfStructure(modelImpl.id, loadController);
5417
5899
  await this.gltfLoader.loadStructure(structure);
5418
5900
  await this.gltfLoader.loadNodes();
5419
5901
  return this;
@@ -5425,7 +5907,7 @@ class GLTFCloudDynamicLoader {
5425
5907
  }
5426
5908
 
5427
5909
  const loaders = loadersRegistry("threejs");
5428
- loaders.registerLoader("gltf-file", (viewer) => new GLTFFileLoader(viewer));
5910
+ loaders.registerLoader("gltf-file", (viewer) => new GLTFFileDynamicLoader(viewer));
5429
5911
  loaders.registerLoader("gltf-cloud", (viewer) => new GLTFCloudDynamicLoader(viewer));
5430
5912
 
5431
5913
  class SSAARenderPass extends Pass {
@@ -5632,24 +6114,25 @@ class Helpers extends Scene {
5632
6114
  class Viewer extends EventEmitter2 {
5633
6115
  constructor(client) {
5634
6116
  super();
5635
- this._options = new Options(this);
5636
6117
  this.client = client;
5637
- this.canvasEvents = CANVAS_EVENTS;
5638
- this.canvaseventlistener = (event) => this.emit(event);
6118
+ this.options = new Options(this);
5639
6119
  this.loaders = [];
5640
6120
  this.models = [];
6121
+ this.canvasEvents = CANVAS_EVENTS.slice();
6122
+ this.canvaseventlistener = (event) => this.emit(event);
5641
6123
  this.selected = [];
5642
6124
  this.extents = new Box3();
5643
- this.target = new Vector3();
6125
+ this.target = new Vector3(0, 0, 0);
5644
6126
  this._activeDragger = null;
5645
6127
  this._components = [];
6128
+ this._renderNeeded = false;
5646
6129
  this._renderTime = 0;
5647
6130
  this.render = this.render.bind(this);
5648
6131
  this.update = this.update.bind(this);
5649
6132
  this._markup = new Markup();
5650
6133
  }
5651
- get options() {
5652
- return this._options;
6134
+ get markup() {
6135
+ return this._markup;
5653
6136
  }
5654
6137
  get draggers() {
5655
6138
  return [...draggers.getDraggers().keys()];
@@ -5657,14 +6140,10 @@ class Viewer extends EventEmitter2 {
5657
6140
  get components() {
5658
6141
  return [...components.getComponents().keys()];
5659
6142
  }
5660
- get markup() {
5661
- return this._markup;
5662
- }
5663
6143
  initialize(canvas, onProgress) {
5664
6144
  this.addEventListener("optionschange", (event) => this.syncOptions(event.data));
5665
6145
  this.scene = new Scene();
5666
6146
  this.helpers = new Helpers();
5667
- this.target = new Vector3(0, 0, 0);
5668
6147
  const pixelRatio = window.devicePixelRatio;
5669
6148
  const rect = canvas.parentElement.getBoundingClientRect();
5670
6149
  const width = rect.width || 1;
@@ -5815,21 +6294,25 @@ class Viewer extends EventEmitter2 {
5815
6294
  async open(file, params = {}) {
5816
6295
  if (!this.renderer)
5817
6296
  return this;
5818
- if (params.mode !== "a" && params.mode !== "append") {
6297
+ const mode = params.mode || "file";
6298
+ if (mode !== "assembly" && mode !== "a" && mode !== "append") {
5819
6299
  this.cancel();
5820
6300
  this.clear();
5821
6301
  }
5822
- this.emitEvent({ type: "open", file });
6302
+ this.emitEvent({ type: "open", mode, file });
5823
6303
  let model = file;
5824
6304
  if (model && typeof model.getModels === "function") {
5825
6305
  const models = await model.getModels();
5826
6306
  model = models.find((model) => model.default) || models[0] || file;
5827
6307
  }
6308
+ if (model && typeof model.database === "string") {
6309
+ file = model.file;
6310
+ }
5828
6311
  if (!model)
5829
6312
  throw new Error(`Format not supported`);
5830
6313
  let format = params.format;
5831
- if (!format && typeof model.type === "string")
5832
- format = model.type.split(".").pop();
6314
+ if (!format && typeof file["type"] === "string")
6315
+ format = file["type"].split(".").pop();
5833
6316
  if (!format && typeof file === "string")
5834
6317
  format = file.split(".").pop();
5835
6318
  if (!format && file instanceof globalThis.File)
@@ -5856,7 +6339,7 @@ class Viewer extends EventEmitter2 {
5856
6339
  }
5857
6340
  loadGltfFile(file, externalFiles, params = {}) {
5858
6341
  console.warn("Viewer.loadGltfFile() has been deprecated since 26.4 and will be removed in a future release, use Viewer.open() instead.");
5859
- return this.open(file, { ...params, format: "gltf", externalFiles, mode: "append" });
6342
+ return this.open(file, { ...params, format: "gltf", externalFiles, mode: "assembly" });
5860
6343
  }
5861
6344
  cancel() {
5862
6345
  this.loaders.forEach((loader) => loader.cancel());
@@ -5876,12 +6359,17 @@ class Viewer extends EventEmitter2 {
5876
6359
  this.models = [];
5877
6360
  this.scene.clear();
5878
6361
  this.helpers.clear();
6362
+ this.extents.makeEmpty();
6363
+ this.target.set(0, 0, 0);
5879
6364
  this.syncOptions();
5880
6365
  this.syncOverlay();
5881
6366
  this.update(true);
5882
6367
  this.emitEvent({ type: "clear" });
5883
6368
  return this;
5884
6369
  }
6370
+ is3D() {
6371
+ return true;
6372
+ }
5885
6373
  syncOptions(options = this.options) {
5886
6374
  if (!this.renderer)
5887
6375
  return;
@@ -5913,9 +6401,15 @@ class Viewer extends EventEmitter2 {
5913
6401
  getSelected() {
5914
6402
  return this.executeCommand("getSelected");
5915
6403
  }
6404
+ getSelected2() {
6405
+ return this.executeCommand("getSelected2");
6406
+ }
5916
6407
  setSelected(handles) {
5917
6408
  this.executeCommand("setSelected", handles);
5918
6409
  }
6410
+ setSelected2(handles) {
6411
+ this.executeCommand("setSelected2", handles);
6412
+ }
5919
6413
  clearSelected() {
5920
6414
  this.executeCommand("clearSelected");
5921
6415
  }
@@ -5975,37 +6469,8 @@ class Viewer extends EventEmitter2 {
5975
6469
  getComponent(name) {
5976
6470
  return this._components.find((component) => component.name === name);
5977
6471
  }
5978
- is3D() {
5979
- return true;
5980
- }
5981
- screenToWorld(position) {
5982
- if (!this.renderer)
5983
- return { x: position.x, y: position.y, z: 0 };
5984
- const rect = this.canvas.getBoundingClientRect();
5985
- const x = position.x / (rect.width / 2) - 1;
5986
- const y = -position.y / (rect.height / 2) + 1;
5987
- const point = new Vector3(x, y, -1);
5988
- point.unproject(this.camera);
5989
- return { x: point.x, y: point.y, z: point.z };
5990
- }
5991
- worldToScreen(position) {
5992
- if (!this.renderer)
5993
- return { x: position.x, y: position.y };
5994
- const point = new Vector3(position.x, position.y, position.z);
5995
- point.project(this.camera);
5996
- const rect = this.canvas.getBoundingClientRect();
5997
- const x = (point.x + 1) * (rect.width / 2);
5998
- const y = (-point.y + 1) * (rect.height / 2);
5999
- return { x, y };
6000
- }
6001
- getScale() {
6002
- return { x: 1, y: 1, z: 1 };
6003
- }
6004
- executeCommand(id, ...args) {
6005
- return commands.executeCommand(id, this, ...args);
6006
- }
6007
6472
  drawViewpoint(viewpoint) {
6008
- var _a, _b, _c;
6473
+ var _a, _b, _c, _d;
6009
6474
  if (!this.renderer)
6010
6475
  return;
6011
6476
  const getVector3FromPoint3d = ({ x, y, z }) => new Vector3(x, y, z);
@@ -6057,11 +6522,13 @@ class Viewer extends EventEmitter2 {
6057
6522
  }
6058
6523
  };
6059
6524
  const setClippingPlanes = (clipping_planes) => {
6060
- clipping_planes === null || clipping_planes === void 0 ? void 0 : clipping_planes.forEach((clipping_plane) => {
6061
- const plane = new Plane();
6062
- plane.setFromNormalAndCoplanarPoint(getVector3FromPoint3d(clipping_plane.direction), getVector3FromPoint3d(clipping_plane.location));
6063
- this.renderer.clippingPlanes.push(plane);
6064
- });
6525
+ if (clipping_planes) {
6526
+ clipping_planes.forEach((clipping_plane) => {
6527
+ const plane = new Plane();
6528
+ plane.setFromNormalAndCoplanarPoint(getVector3FromPoint3d(clipping_plane.direction), getVector3FromPoint3d(clipping_plane.location));
6529
+ this.renderer.clippingPlanes.push(plane);
6530
+ });
6531
+ }
6065
6532
  };
6066
6533
  const setSelection = (selection) => {
6067
6534
  if (selection)
@@ -6077,9 +6544,9 @@ class Viewer extends EventEmitter2 {
6077
6544
  setOrthogonalCamera(viewpoint.orthogonal_camera);
6078
6545
  setPerspectiveCamera(viewpoint.perspective_camera);
6079
6546
  setClippingPlanes(viewpoint.clipping_planes);
6080
- setSelection(viewpoint.selection);
6547
+ setSelection(((_b = viewpoint.custom_fields) === null || _b === void 0 ? void 0 : _b.selection2) || viewpoint.selection);
6081
6548
  this._markup.setViewpoint(viewpoint);
6082
- this.target = getVector3FromPoint3d((_c = (_b = viewpoint.custom_fields) === null || _b === void 0 ? void 0 : _b.camera_target) !== null && _c !== void 0 ? _c : this.target);
6549
+ this.target.copy(getVector3FromPoint3d((_d = (_c = viewpoint.custom_fields) === null || _c === void 0 ? void 0 : _c.camera_target) !== null && _d !== void 0 ? _d : this.target));
6083
6550
  this.setActiveDragger(draggerName);
6084
6551
  this.emitEvent({ type: "drawviewpoint", data: viewpoint });
6085
6552
  this.update();
@@ -6126,6 +6593,9 @@ class Viewer extends EventEmitter2 {
6126
6593
  const getSelection = () => {
6127
6594
  return this.getSelected().map((handle) => ({ handle }));
6128
6595
  };
6596
+ const getSelection2 = () => {
6597
+ return this.getSelected2().map((handle) => ({ handle }));
6598
+ };
6129
6599
  const viewpoint = { custom_fields: {} };
6130
6600
  viewpoint.orthogonal_camera = getOrthogonalCamera();
6131
6601
  viewpoint.perspective_camera = getPerspectiveCamera();
@@ -6134,9 +6604,36 @@ class Viewer extends EventEmitter2 {
6134
6604
  viewpoint.description = new Date().toDateString();
6135
6605
  this._markup.getViewpoint(viewpoint);
6136
6606
  viewpoint.custom_fields.camera_target = getPoint3dFromVector3(this.target);
6607
+ viewpoint.custom_fields.selection2 = getSelection2();
6137
6608
  this.emitEvent({ type: "createviewpoint", data: viewpoint });
6138
6609
  return viewpoint;
6139
6610
  }
6611
+ screenToWorld(position) {
6612
+ if (!this.renderer)
6613
+ return { x: position.x, y: position.y, z: 0 };
6614
+ const rect = this.canvas.getBoundingClientRect();
6615
+ const x = position.x / (rect.width / 2) - 1;
6616
+ const y = -position.y / (rect.height / 2) + 1;
6617
+ const point = new Vector3(x, y, -1);
6618
+ point.unproject(this.camera);
6619
+ return { x: point.x, y: point.y, z: point.z };
6620
+ }
6621
+ worldToScreen(position) {
6622
+ if (!this.renderer)
6623
+ return { x: position.x, y: position.y };
6624
+ const point = new Vector3(position.x, position.y, position.z);
6625
+ point.project(this.camera);
6626
+ const rect = this.canvas.getBoundingClientRect();
6627
+ const x = (point.x + 1) * (rect.width / 2);
6628
+ const y = (-point.y + 1) * (rect.height / 2);
6629
+ return { x, y };
6630
+ }
6631
+ getScale() {
6632
+ return { x: 1, y: 1, z: 1 };
6633
+ }
6634
+ executeCommand(id, ...args) {
6635
+ return commands.executeCommand(id, this, ...args);
6636
+ }
6140
6637
  }
6141
6638
 
6142
6639
  export { GLTFLoadingManager, ModelImpl, Viewer, commands, components, draggers, loaders };