fl-web-component 2.0.8 → 2.0.10

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 (35) hide show
  1. package/README.md +4 -0
  2. package/dist/fl-web-component.common.2.js +126 -48
  3. package/dist/fl-web-component.common.2.js.map +1 -1
  4. package/dist/fl-web-component.common.js +7366 -1277
  5. package/dist/fl-web-component.common.js.map +1 -1
  6. package/dist/fl-web-component.css +1 -1
  7. package/package.json +1 -1
  8. package/packages/components/com-graphics/component/context.js +123 -0
  9. package/packages/components/com-graphics/index.vue +1243 -85
  10. package/packages/utils/StreamLoader.js +73 -16
  11. package/src/utils/threejs/editor/command.js +36 -0
  12. package/src/utils/threejs/editor/commands/add-element-command.js +41 -0
  13. package/src/utils/threejs/editor/commands/add-group-command.js +10 -0
  14. package/src/utils/threejs/editor/commands/clone-element-command.js +100 -0
  15. package/src/utils/threejs/editor/commands/move-element-command.js +57 -0
  16. package/src/utils/threejs/editor/commands/multi-command.js +29 -0
  17. package/src/utils/threejs/editor/commands/remove-element-command.js +46 -0
  18. package/src/utils/threejs/editor/commands/reset-original-model-style-command.js +30 -0
  19. package/src/utils/threejs/editor/commands/set-geometry-params-command.js +41 -0
  20. package/src/utils/threejs/editor/commands/set-original-model-style-command.js +56 -0
  21. package/src/utils/threejs/editor/commands/set-position-command.js +54 -0
  22. package/src/utils/threejs/editor/commands/set-rotation-command.js +53 -0
  23. package/src/utils/threejs/editor/commands/set-scale-command.js +54 -0
  24. package/src/utils/threejs/editor/commands/set-value-command.js +107 -0
  25. package/src/utils/threejs/editor/constants.js +107 -0
  26. package/src/utils/threejs/editor/element-factory.js +163 -0
  27. package/src/utils/threejs/editor/event-bus.js +34 -0
  28. package/src/utils/threejs/editor/history.js +80 -0
  29. package/src/utils/threejs/editor/scene-command-service.js +1529 -0
  30. package/src/utils/threejs/editor/scene-event-bridge.js +32 -0
  31. package/src/utils/threejs/editor/scene-helpers.js +415 -0
  32. package/src/utils/threejs/measure-angle.js +22 -2
  33. package/src/utils/threejs/measure-area.js +23 -3
  34. package/src/utils/threejs/measure-distance.js +20 -1
  35. package/src/utils/threejs/measure-height.js +17 -1
@@ -128,6 +128,7 @@ import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment
128
128
  import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer';
129
129
  import { CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer';
130
130
  import { PointerLockControls } from 'three/examples/jsm/controls/PointerLockControls.js';
131
+ import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js';
131
132
  import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
132
133
  import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js';
133
134
  import MeasureDistance from '@/utils/threejs/measure-distance.js';
@@ -155,6 +156,10 @@ import { OBB } from 'three/examples/jsm/math/OBB.js';
155
156
  import boxJson from './box.json';
156
157
  import { StreamLoader } from '../../utils/StreamLoader.js';
157
158
  import StreamLoaderParserWorker from '../../utils/StreamLoaderParser.worker.js';
159
+ import SceneCommandService from '@/utils/threejs/editor/scene-command-service.js';
160
+ import { EDITOR_EVENT, SCENE_NODE_TYPE, TRANSFORM_MODE } from '@/utils/threejs/editor/constants.js';
161
+ import { isCustomRoot, isTransformAttachableObject } from '@/utils/threejs/editor/scene-helpers.js';
162
+ import { onContextHandle } from './component/context';
158
163
 
159
164
  const isDebug = process.env.NODE_ENV !== 'production' || process.env.VUE_APP_IS_WATCH === true;
160
165
  // const isDebug = false;
@@ -168,6 +173,10 @@ export default {
168
173
  return {};
169
174
  },
170
175
  },
176
+ transformEditDisabled: {
177
+ type: Boolean,
178
+ default: false,
179
+ },
171
180
  containId: {
172
181
  type: String,
173
182
  default: 'fl-model',
@@ -200,8 +209,16 @@ export default {
200
209
  totalCount: 0,
201
210
  isPaused: false,
202
211
  },
212
+ isolateMode: false,
203
213
  };
204
214
  },
215
+ watch: {
216
+ transformEditDisabled(val) {
217
+ if (val) {
218
+ this.detachTransformControls();
219
+ }
220
+ },
221
+ },
205
222
  beforeCreate() {
206
223
  this.spaceUp = true;
207
224
  let arr = [
@@ -345,6 +362,23 @@ export default {
345
362
  occlusionWorkerRequestMap: new Map(),
346
363
  occlusionWorkerRequestId: 0,
347
364
  };
365
+ this.sceneCommandService = null;
366
+ this.sceneCommandEventDisposers = [];
367
+ this.transformEditor = {
368
+ transformControls: null,
369
+ transformHelper: null,
370
+ transformMode: TRANSFORM_MODE.TRANSLATE,
371
+ transforming: false,
372
+ transformTargetUuid: '',
373
+ transformStartSnapshot: null,
374
+ suppressSelectionOnce: false,
375
+ suppressSelectionTimer: null,
376
+ cameraControlsEnabled: true,
377
+ pointerCameraGuard: false,
378
+ pointerCameraEnabled: true,
379
+ keydownHandler: null,
380
+ handlers: {},
381
+ };
348
382
  this.modelGroups = [];
349
383
  this.modelActions = [];
350
384
  this.lastMiddleClickTime = 0;
@@ -369,8 +403,10 @@ export default {
369
403
  this.instructions = document.getElementById(this.containId); // 'fl-model'
370
404
  this.initRender();
371
405
  this.initScene();
406
+ this.initSceneCommandService();
372
407
  this.initCamera();
373
408
  this.initControl();
409
+ this.initTransformControls();
374
410
  this.initPostProcessing();
375
411
  // 初始化统一的相机事件监听
376
412
  this.initCameraChangeObserver();
@@ -379,14 +415,7 @@ export default {
379
415
  this.exportParmas();
380
416
 
381
417
  // 判断是设备是手机还是电脑
382
- let isMobileDevice = this.isMobileDevice();
383
- if (isMobileDevice) {
384
- this.renderer.domElement.addEventListener('pointerup', this.mouseClick, false);
385
- this.renderer.domElement.addEventListener('pointerdown', this.mouseDown, false);
386
- } else {
387
- this.renderer.domElement.addEventListener('mouseup', this.mouseClick, false);
388
- this.renderer.domElement.addEventListener('mousedown', this.mouseDown, false);
389
- }
418
+ this.bindScenePointerEvents();
390
419
  this.animate();
391
420
  },
392
421
  beforeDestroy() {
@@ -394,6 +423,764 @@ export default {
394
423
  this.destroyScene();
395
424
  },
396
425
  methods: {
426
+ bindScenePointerEvents() {
427
+ if (!this.renderer || !this.renderer.domElement) return;
428
+ this.renderer.domElement.addEventListener('pointerup', this.mouseClick, true);
429
+ this.renderer.domElement.addEventListener('pointerdown', this.mouseDown, true);
430
+ },
431
+ unbindScenePointerEvents() {
432
+ if (!this.renderer || !this.renderer.domElement) return;
433
+ this.renderer.domElement.removeEventListener('pointerup', this.mouseClick, true);
434
+ this.renderer.domElement.removeEventListener('pointerdown', this.mouseDown, true);
435
+ this.renderer.domElement.removeEventListener('mouseup', this.mouseClick, false);
436
+ this.renderer.domElement.removeEventListener('mousedown', this.mouseDown, false);
437
+ this.renderer.domElement.removeEventListener('pointerup', this.mouseClick, false);
438
+ this.renderer.domElement.removeEventListener('pointerdown', this.mouseDown, false);
439
+ },
440
+ setPointerCameraGuard(active) {
441
+ const editor = this.transformEditor;
442
+ if (!editor || !this.cameraControls) return;
443
+ if (active) {
444
+ if (editor.pointerCameraGuard) return;
445
+ editor.pointerCameraEnabled = this.cameraControls.enabled;
446
+ editor.pointerCameraGuard = true;
447
+ this.cameraControls.enabled = false;
448
+ return;
449
+ }
450
+ if (!editor.pointerCameraGuard) {
451
+ this.restoreTransformCameraControls();
452
+ return;
453
+ }
454
+ if (!(this.firstPerSign && this.pointControls && this.pointControls.isLocked)) {
455
+ this.cameraControls.enabled = editor.pointerCameraEnabled || editor.cameraControlsEnabled;
456
+ }
457
+ editor.pointerCameraGuard = false;
458
+ },
459
+ restoreTransformCameraControls() {
460
+ const editor = this.transformEditor;
461
+ if (!editor || !this.cameraControls) return;
462
+ const isFirstPersonLocked = !!(
463
+ this.firstPerSign &&
464
+ this.pointControls &&
465
+ this.pointControls.isLocked
466
+ );
467
+ editor.pointerCameraGuard = false;
468
+ editor.transforming = false;
469
+ if (!isFirstPersonLocked) {
470
+ this.cameraControls.enabled = true;
471
+ editor.cameraControlsEnabled = true;
472
+ editor.pointerCameraEnabled = true;
473
+ }
474
+ },
475
+ scheduleSuppressSelectionOnce() {
476
+ const editor = this.transformEditor;
477
+ if (!editor) return;
478
+ editor.suppressSelectionOnce = true;
479
+ if (editor.suppressSelectionTimer) {
480
+ clearTimeout(editor.suppressSelectionTimer);
481
+ }
482
+ editor.suppressSelectionTimer = setTimeout(() => {
483
+ editor.suppressSelectionOnce = false;
484
+ editor.suppressSelectionTimer = null;
485
+ }, 0);
486
+ },
487
+ initSceneCommandService() {
488
+ if (!this.scene || this.sceneCommandService) return;
489
+ this.sceneCommandService = new SceneCommandService({
490
+ THREE: this.THREE,
491
+ getScene: () => this.scene,
492
+ requestRender: () => {
493
+ if (typeof this.notifyCameraChange === 'function') {
494
+ this.notifyCameraChange('customEdit');
495
+ }
496
+ },
497
+ });
498
+ Object.values(EDITOR_EVENT).forEach(eventName => {
499
+ const dispose = this.sceneCommandService.subscribe(eventName, payload => {
500
+ this.handleSceneCommandEvent(eventName, payload);
501
+ this.$emit(eventName, payload);
502
+ });
503
+ this.sceneCommandEventDisposers.push(dispose);
504
+ });
505
+ },
506
+ handleSceneCommandEvent(eventName, payload) {
507
+ switch (eventName) {
508
+ case EDITOR_EVENT.OBJECT_SELECTED:
509
+ this.syncTransformSelection(payload && payload.uuid ? payload.uuid : '');
510
+ break;
511
+ case EDITOR_EVENT.TRANSFORM_MODE_CHANGED:
512
+ this.applyTransformMode(
513
+ payload && payload.mode ? payload.mode : TRANSFORM_MODE.TRANSLATE
514
+ );
515
+ break;
516
+ case EDITOR_EVENT.OBJECT_ADDED:
517
+ case EDITOR_EVENT.OBJECT_REMOVED:
518
+ case EDITOR_EVENT.OBJECT_CHANGED:
519
+ case EDITOR_EVENT.HISTORY_CHANGED:
520
+ case EDITOR_EVENT.SCENE_GRAPH_CHANGED:
521
+ this.ensureTransformSelectionValid();
522
+ break;
523
+ }
524
+ },
525
+ initTransformControls() {
526
+ if (!this.scene || !this.camera || !this.renderer || this.transformEditor.transformControls)
527
+ return;
528
+ const controls = new TransformControls(this.camera, this.renderer.domElement);
529
+ const helper = controls.getHelper();
530
+ controls.setMode(this.getTransformMode());
531
+ helper.visible = false;
532
+ helper.traverse(item => {
533
+ if (!item.userData) {
534
+ item.userData = {};
535
+ }
536
+ item.userData.transformControlHelper = true;
537
+ });
538
+ this.transformEditor.handlers = {
539
+ change: () => {
540
+ if (typeof this.notifyCameraChange === 'function') {
541
+ this.notifyCameraChange('customTransformControl');
542
+ }
543
+ },
544
+ objectChange: () => {
545
+ this.handleTransformObjectChange();
546
+ },
547
+ mouseUp: () => {
548
+ this.scheduleSuppressSelectionOnce();
549
+ this.restoreTransformCameraControls();
550
+ },
551
+ draggingChanged: event => {
552
+ this.handleTransformDraggingChanged(event);
553
+ },
554
+ };
555
+ controls.addEventListener('change', this.transformEditor.handlers.change);
556
+ controls.addEventListener('objectChange', this.transformEditor.handlers.objectChange);
557
+ controls.addEventListener('mouseUp', this.transformEditor.handlers.mouseUp);
558
+ controls.addEventListener('dragging-changed', this.transformEditor.handlers.draggingChanged);
559
+ this.scene.add(helper);
560
+ this.transformEditor.transformControls = controls;
561
+ this.transformEditor.transformHelper = helper;
562
+ this.transformEditor.keydownHandler = event => {
563
+ const isEscape = event && (event.key === 'Escape' || event.keyCode === 27);
564
+ if (!isEscape) return;
565
+ if (this.sceneCommandService && this.sceneCommandService.selectedUuid) {
566
+ this.clearCustomSelection();
567
+ }
568
+ };
569
+ window.addEventListener('keydown', this.transformEditor.keydownHandler, false);
570
+ },
571
+ disposeTransformControls() {
572
+ const editor = this.transformEditor;
573
+ if (!editor) return;
574
+ if (editor.keydownHandler) {
575
+ window.removeEventListener('keydown', editor.keydownHandler, false);
576
+ editor.keydownHandler = null;
577
+ }
578
+ const controls = editor.transformControls;
579
+ const helper = editor.transformHelper;
580
+ if (editor.suppressSelectionTimer) {
581
+ clearTimeout(editor.suppressSelectionTimer);
582
+ editor.suppressSelectionTimer = null;
583
+ }
584
+ if (controls) {
585
+ if (editor.handlers.change) {
586
+ controls.removeEventListener('change', editor.handlers.change);
587
+ }
588
+ if (editor.handlers.objectChange) {
589
+ controls.removeEventListener('objectChange', editor.handlers.objectChange);
590
+ }
591
+ if (editor.handlers.mouseUp) {
592
+ controls.removeEventListener('mouseUp', editor.handlers.mouseUp);
593
+ }
594
+ if (editor.handlers.draggingChanged) {
595
+ controls.removeEventListener('dragging-changed', editor.handlers.draggingChanged);
596
+ }
597
+ controls.detach();
598
+ }
599
+ if (helper && this.scene) {
600
+ this.scene.remove(helper);
601
+ }
602
+ editor.handlers = {};
603
+ editor.transformControls = null;
604
+ editor.transformHelper = null;
605
+ editor.transforming = false;
606
+ editor.transformTargetUuid = '';
607
+ editor.transformStartSnapshot = null;
608
+ editor.suppressSelectionOnce = false;
609
+ },
610
+ isTransformEditBlocked() {
611
+ return !!(this.firstPerSign && this.pointControls && this.pointControls.isLocked);
612
+ },
613
+ isTransformControlDisabled() {
614
+ return !!this.transformEditDisabled || this.isTransformEditBlocked();
615
+ },
616
+ applyTransformMode(mode) {
617
+ const nextMode =
618
+ Object.values(TRANSFORM_MODE).indexOf(mode) !== -1 ? mode : TRANSFORM_MODE.TRANSLATE;
619
+ this.transformEditor.transformMode = nextMode;
620
+ if (this.transformEditor.transformControls) {
621
+ this.transformEditor.transformControls.setMode(nextMode);
622
+ }
623
+ return nextMode;
624
+ },
625
+ getSelectableSceneObject(target) {
626
+ if (!this.sceneCommandService || !target) return null;
627
+ return this.sceneCommandService.findSelectableObject(target);
628
+ },
629
+ getSelectableCustomObject(target) {
630
+ const object = this.getSelectableSceneObject(target);
631
+ if (!object || !this.sceneCommandService) return null;
632
+ const nodeType = this.sceneCommandService.getNodeType(object.uuid);
633
+ return nodeType === 'custom-element' ||
634
+ nodeType === 'custom-group' ||
635
+ nodeType === 'custom-root'
636
+ ? object
637
+ : null;
638
+ },
639
+ selectSceneObject(uuid, options = {}) {
640
+ if (!this.sceneCommandService) return null;
641
+ if (this.isTransformEditBlocked()) return null;
642
+ return this.sceneCommandService.selectObject(uuid, options);
643
+ },
644
+ selectElement(uuid, options = {}) {
645
+ if (!this.sceneCommandService) return null;
646
+ if (this.isTransformEditBlocked()) return null;
647
+ return this.sceneCommandService.selectElement(uuid, options);
648
+ },
649
+ clearCustomSelection(options = {}) {
650
+ if (!this.sceneCommandService) {
651
+ this.detachTransformControls();
652
+ return;
653
+ }
654
+ this.sceneCommandService.clearSelection(options);
655
+ },
656
+ getDefaultPrimitivePlacement() {
657
+ const defaultPosition = { x: 0, y: 0, z: 0 };
658
+ const defaultSize = 100;
659
+ if (!this.cameraControls) {
660
+ return {
661
+ position: defaultPosition,
662
+ size: defaultSize,
663
+ };
664
+ }
665
+ const target = new this.THREE.Vector3();
666
+ if (typeof this.cameraControls.getTarget === 'function') {
667
+ this.cameraControls.getTarget(target);
668
+ } else if (this.cameraControls._target) {
669
+ target.copy(this.cameraControls._target);
670
+ }
671
+ const distance =
672
+ this.camera && this.camera.position && typeof this.camera.position.distanceTo === 'function'
673
+ ? this.camera.position.distanceTo(target)
674
+ : 0;
675
+ const size = Math.min(Math.max(distance * 0.008, 20), 100) || defaultSize;
676
+ return {
677
+ position: {
678
+ x: target.x,
679
+ y: target.y,
680
+ z: target.z,
681
+ },
682
+ size,
683
+ };
684
+ },
685
+ buildDefaultPrimitiveTransform(transform, position) {
686
+ const nextTransform = transform && typeof transform === 'object' ? { ...transform } : {};
687
+ if (!Array.isArray(nextTransform.position)) {
688
+ nextTransform.position = [position.x, position.y, position.z];
689
+ }
690
+ if (!Array.isArray(nextTransform.rotation)) {
691
+ nextTransform.rotation = [0, 0, 0];
692
+ }
693
+ if (!Array.isArray(nextTransform.scale)) {
694
+ nextTransform.scale = [1, 1, 1];
695
+ }
696
+ return nextTransform;
697
+ },
698
+ buildDefaultPrimitiveGeometryParams(type, size) {
699
+ const primitiveSize = Number.isFinite(size) ? size : 100;
700
+ switch (type) {
701
+ case 'box':
702
+ return {
703
+ width: primitiveSize,
704
+ height: primitiveSize,
705
+ depth: primitiveSize,
706
+ };
707
+ case 'sphere':
708
+ return {
709
+ radius: primitiveSize / 2,
710
+ phiStart: 0,
711
+ phiLength: 360,
712
+ thetaStart: 0,
713
+ thetaLength: 180,
714
+ };
715
+ case 'cylinder':
716
+ return {
717
+ radiusTop: primitiveSize / 2,
718
+ radiusBottom: primitiveSize / 2,
719
+ height: primitiveSize,
720
+ };
721
+ case 'capsule':
722
+ return {
723
+ radius: primitiveSize / 3,
724
+ length: primitiveSize,
725
+ };
726
+ case 'tetrahedron':
727
+ case 'octahedron':
728
+ return {
729
+ radius: primitiveSize / 2,
730
+ };
731
+ case 'ring':
732
+ return {
733
+ radius: primitiveSize / 2,
734
+ tube: primitiveSize / 8,
735
+ arc: 360,
736
+ };
737
+ default:
738
+ return {};
739
+ }
740
+ },
741
+ normalizeAddElementOptions(options = {}) {
742
+ const placement = this.getDefaultPrimitivePlacement();
743
+ const nextOptions = { ...options };
744
+ nextOptions.transform = this.buildDefaultPrimitiveTransform(
745
+ nextOptions.transform,
746
+ placement.position
747
+ );
748
+ if (!nextOptions.geometryParams) {
749
+ nextOptions.geometryParams = this.buildDefaultPrimitiveGeometryParams(
750
+ nextOptions.type,
751
+ placement.size
752
+ );
753
+ }
754
+ return nextOptions;
755
+ },
756
+ normalizeAddGroupOptions(options = {}) {
757
+ const placement = this.getDefaultPrimitivePlacement();
758
+ return {
759
+ ...options,
760
+ transform: this.buildDefaultPrimitiveTransform(options.transform, placement.position),
761
+ };
762
+ },
763
+ addElement(options) {
764
+ if (!this.sceneCommandService) return null;
765
+ return this.sceneCommandService.addElement(this.normalizeAddElementOptions(options));
766
+ },
767
+ addGroup(options = {}) {
768
+ if (!this.sceneCommandService) return null;
769
+ return this.sceneCommandService.addGroup(this.normalizeAddGroupOptions(options));
770
+ },
771
+ removeElement(uuid) {
772
+ if (!this.sceneCommandService) return;
773
+ this.sceneCommandService.removeElement(uuid);
774
+ },
775
+ cloneElement(uuid, options = {}) {
776
+ if (!this.sceneCommandService) return null;
777
+ return this.sceneCommandService.cloneElement(uuid, options);
778
+ },
779
+ moveElement(uuid, targetParentUuid, referenceUuid, insertMode = 'append') {
780
+ if (!this.sceneCommandService) return null;
781
+ return this.sceneCommandService.moveElement(
782
+ uuid,
783
+ targetParentUuid,
784
+ referenceUuid,
785
+ insertMode
786
+ );
787
+ },
788
+ updateElement(options) {
789
+ if (!this.sceneCommandService) return null;
790
+ return this.sceneCommandService.updateElement(options);
791
+ },
792
+ updateOriginalModelStyle(options) {
793
+ if (!this.sceneCommandService) return null;
794
+ return this.sceneCommandService.updateOriginalModelStyle(options);
795
+ },
796
+ resetOriginalModelStyle(uuid) {
797
+ if (!this.sceneCommandService) return null;
798
+ return this.sceneCommandService.resetOriginalModelStyle(uuid);
799
+ },
800
+ setElementPosition(uuid, position, oldPosition) {
801
+ if (!this.sceneCommandService) return null;
802
+ return this.sceneCommandService.setElementPosition(uuid, position, oldPosition);
803
+ },
804
+ setElementRotation(uuid, rotation, oldRotation) {
805
+ if (!this.sceneCommandService) return null;
806
+ return this.sceneCommandService.setElementRotation(uuid, rotation, oldRotation);
807
+ },
808
+ setElementScale(uuid, scale, oldScale) {
809
+ if (!this.sceneCommandService) return null;
810
+ return this.sceneCommandService.setElementScale(uuid, scale, oldScale);
811
+ },
812
+ renameElement(uuid, name) {
813
+ if (!this.sceneCommandService) return null;
814
+ return this.sceneCommandService.renameElement(uuid, name);
815
+ },
816
+ setElementVisible(uuid, visible) {
817
+ if (!this.sceneCommandService) return null;
818
+ return this.sceneCommandService.setElementVisible(uuid, visible);
819
+ },
820
+ setTransformMode(mode) {
821
+ if (!this.sceneCommandService) return null;
822
+ return this.sceneCommandService.setTransformMode(mode, {
823
+ forceEmit: true,
824
+ });
825
+ },
826
+ getTransformMode() {
827
+ if (!this.sceneCommandService) {
828
+ return this.transformEditor.transformMode;
829
+ }
830
+ return this.sceneCommandService.getTransformMode();
831
+ },
832
+ getCustomObjectSnapshot(uuid) {
833
+ if (!this.sceneCommandService) return null;
834
+ return this.sceneCommandService.getObjectSnapshot(uuid);
835
+ },
836
+ getModelReviseLocateName(uuid) {
837
+ if (!uuid || !this.sceneCommandService) return '';
838
+ const snapshot = this.sceneCommandService.getObjectSnapshot(uuid);
839
+ if (snapshot && snapshot.name) {
840
+ return snapshot.name;
841
+ }
842
+ const originalRecord = this.sceneCommandService.getOriginalModelStyle(uuid);
843
+ return originalRecord && originalRecord.name ? originalRecord.name : '';
844
+ },
845
+ setOriginalModelNodeName(options = {}) {
846
+ if (!this.sceneCommandService || !this.sceneCommandService.setOriginalModelNodeName) {
847
+ return '';
848
+ }
849
+ return this.sceneCommandService.setOriginalModelNodeName(options);
850
+ },
851
+ getOriginalModelSelectablePriority(object) {
852
+ if (!object) return -1;
853
+ let priority = 0;
854
+ if (object.isInstancedMesh === true) {
855
+ priority += 8;
856
+ }
857
+ if (object.type && object.type !== 'Group') {
858
+ priority += 4;
859
+ }
860
+ if (object.material) {
861
+ priority += 2;
862
+ }
863
+ return priority;
864
+ },
865
+ findOriginalModelSelectableObject(uuid, name) {
866
+ if (
867
+ this.sceneCommandService &&
868
+ typeof this.sceneCommandService.resolveOriginalModelObject === 'function'
869
+ ) {
870
+ const originalObject = this.sceneCommandService.resolveOriginalModelObject({
871
+ uuid,
872
+ name,
873
+ });
874
+ if (originalObject && originalObject.uuid) {
875
+ return originalObject;
876
+ }
877
+ }
878
+ const objectByUuid = this.sceneCommandService
879
+ ? this.sceneCommandService.getObjectByUuid(uuid)
880
+ : null;
881
+ if (objectByUuid) {
882
+ return objectByUuid;
883
+ }
884
+ if (!name) {
885
+ return null;
886
+ }
887
+ const objectList = this.getObjectByName(name, '');
888
+ if (!Array.isArray(objectList) || objectList.length === 0) {
889
+ return null;
890
+ }
891
+ return (
892
+ objectList
893
+ .filter(item => item && item.uuid)
894
+ .sort(
895
+ (left, right) =>
896
+ this.getOriginalModelSelectablePriority(right) -
897
+ this.getOriginalModelSelectablePriority(left)
898
+ )[0] || null
899
+ );
900
+ },
901
+ async locateModelByUuid(uuid) {
902
+ if (!uuid || !this.sceneCommandService) return false;
903
+ const originalRecord = this.sceneCommandService.getOriginalModelStyle(uuid);
904
+ const nodeType =
905
+ this.sceneCommandService.getNodeType(uuid) ||
906
+ (originalRecord ? SCENE_NODE_TYPE.ORIGINAL_MODEL : '');
907
+ if (uuid === SCENE_NODE_TYPE.ORIGINAL_MODEL || nodeType === SCENE_NODE_TYPE.CUSTOM_ROOT) {
908
+ this.selectElement();
909
+ return false;
910
+ }
911
+ if (
912
+ nodeType === SCENE_NODE_TYPE.CUSTOM_GROUP ||
913
+ nodeType === SCENE_NODE_TYPE.CUSTOM_ELEMENT
914
+ ) {
915
+ this.selectElement(uuid);
916
+ return this.locateModel(uuid);
917
+ }
918
+ const name =
919
+ originalRecord && originalRecord.name
920
+ ? originalRecord.name
921
+ : this.getModelReviseLocateName(uuid);
922
+ if (nodeType !== SCENE_NODE_TYPE.ORIGINAL_MODEL && !name) {
923
+ return false;
924
+ }
925
+ if (!name) return false;
926
+ try {
927
+ const targetObject = this.findOriginalModelSelectableObject(uuid, name);
928
+ if (targetObject && targetObject.uuid) {
929
+ this.selectSceneObject(targetObject.uuid);
930
+ } else {
931
+ this.selectElement();
932
+ }
933
+ await this.loadModelByIds({
934
+ params: {
935
+ ids: [name],
936
+ },
937
+ onComplete: () => {
938
+ this.locateModel([name]);
939
+ const loadedObject = this.findOriginalModelSelectableObject(uuid, name);
940
+ if (loadedObject && loadedObject.uuid) {
941
+ this.selectSceneObject(loadedObject.uuid);
942
+ }
943
+ },
944
+ });
945
+ return true;
946
+ } catch (error) {
947
+ return false;
948
+ }
949
+ },
950
+ getSelectedSceneObjectSnapshot() {
951
+ if (!this.sceneCommandService) return null;
952
+ return this.sceneCommandService.getSelectedObjectSnapshot();
953
+ },
954
+ getSelectedCustomObjectSnapshot() {
955
+ if (!this.sceneCommandService) return null;
956
+ if (this.sceneCommandService.getSelectedNodeType() === 'original-model') {
957
+ return null;
958
+ }
959
+ return this.sceneCommandService.getSelectedObjectSnapshot();
960
+ },
961
+ getSelectedOriginalModelStyleSnapshot() {
962
+ if (!this.sceneCommandService) return null;
963
+ return this.sceneCommandService.getSelectedOriginalModelStyleSnapshot();
964
+ },
965
+ getOriginalModelStyleChanges() {
966
+ if (!this.sceneCommandService) return [];
967
+ return this.sceneCommandService.getOriginalModelStyleChanges();
968
+ },
969
+ undo() {
970
+ if (!this.sceneCommandService) return null;
971
+ return this.sceneCommandService.undo();
972
+ },
973
+ redo() {
974
+ if (!this.sceneCommandService) return null;
975
+ return this.sceneCommandService.redo();
976
+ },
977
+ subscribe(eventName, handler) {
978
+ if (!this.sceneCommandService) {
979
+ return function () {};
980
+ }
981
+ return this.sceneCommandService.subscribe(eventName, handler);
982
+ },
983
+ getCustomTree() {
984
+ if (!this.sceneCommandService) return null;
985
+ return this.sceneCommandService.getCustomTree();
986
+ },
987
+ getCustomSaveTree() {
988
+ if (!this.sceneCommandService) return null;
989
+ return this.sceneCommandService.getCustomSaveTree();
990
+ },
991
+ getSaveSnapshot() {
992
+ if (!this.sceneCommandService) return null;
993
+ return this.sceneCommandService.getSaveSnapshot();
994
+ },
995
+ applySaveSnapshot(snapshot) {
996
+ if (!this.sceneCommandService) return null;
997
+ return this.sceneCommandService.applySaveSnapshot(snapshot);
998
+ },
999
+ serializeCustomElements() {
1000
+ if (!this.sceneCommandService) return null;
1001
+ return this.sceneCommandService.serializeCustomElements();
1002
+ },
1003
+ deserializeCustomElements(json) {
1004
+ if (!this.sceneCommandService) return null;
1005
+ return this.sceneCommandService.deserializeCustomElements(json);
1006
+ },
1007
+ removeAllCustomElements() {
1008
+ if (!this.sceneCommandService) return;
1009
+ this.sceneCommandService.removeAllCustomElements();
1010
+ },
1011
+ resolveInsertTarget(selectedUuid) {
1012
+ if (!this.sceneCommandService) return null;
1013
+ return this.sceneCommandService.resolveInsertTarget(selectedUuid);
1014
+ },
1015
+ detachTransformControls() {
1016
+ const editor = this.transformEditor;
1017
+ if (!editor) return;
1018
+ const controls = editor.transformControls;
1019
+ const helper = editor.transformHelper;
1020
+ if (controls) {
1021
+ controls.detach();
1022
+ }
1023
+ if (helper) {
1024
+ helper.visible = false;
1025
+ }
1026
+ editor.transforming = false;
1027
+ editor.transformTargetUuid = '';
1028
+ editor.transformStartSnapshot = null;
1029
+ this.restoreTransformCameraControls();
1030
+ if (typeof this.notifyCameraChange === 'function') {
1031
+ this.notifyCameraChange('customTransformDetach');
1032
+ }
1033
+ },
1034
+ attachTransformControls(object) {
1035
+ const editor = this.transformEditor;
1036
+ if (!editor || !editor.transformControls) return null;
1037
+ if (this.isTransformControlDisabled()) {
1038
+ this.detachTransformControls();
1039
+ return null;
1040
+ }
1041
+ if (!object || isCustomRoot(object) || !isTransformAttachableObject(object)) {
1042
+ this.detachTransformControls();
1043
+ return null;
1044
+ }
1045
+ object.updateMatrixWorld(true);
1046
+ editor.transformControls.attach(object);
1047
+ if (editor.transformHelper) {
1048
+ editor.transformHelper.visible = true;
1049
+ }
1050
+ editor.transformTargetUuid = object.uuid;
1051
+ this.applyTransformMode(this.getTransformMode());
1052
+ if (typeof this.notifyCameraChange === 'function') {
1053
+ this.notifyCameraChange('customTransformAttach');
1054
+ }
1055
+ return object;
1056
+ },
1057
+ syncTransformSelection(uuid) {
1058
+ if (!this.sceneCommandService) return null;
1059
+ if (!uuid) {
1060
+ this.detachTransformControls();
1061
+ return null;
1062
+ }
1063
+ const object = this.sceneCommandService.getObjectByUuid(uuid);
1064
+ return this.attachTransformControls(object);
1065
+ },
1066
+ ensureTransformSelectionValid() {
1067
+ if (!this.sceneCommandService) return null;
1068
+ const selectedUuid = this.sceneCommandService.selectedUuid;
1069
+ if (!selectedUuid) {
1070
+ this.detachTransformControls();
1071
+ return null;
1072
+ }
1073
+ const object = this.sceneCommandService.getObjectByUuid(selectedUuid);
1074
+ if (!object || !isTransformAttachableObject(object)) {
1075
+ this.detachTransformControls();
1076
+ return null;
1077
+ }
1078
+ return this.attachTransformControls(object);
1079
+ },
1080
+ buildTransformPayload(objectData, options = {}) {
1081
+ if (!objectData) return null;
1082
+ return {
1083
+ ...objectData,
1084
+ isTransforming: !!options.isTransforming,
1085
+ mode: this.getTransformMode(),
1086
+ };
1087
+ },
1088
+ emitTransformEvent(eventName, objectData, options = {}) {
1089
+ const payload = this.buildTransformPayload(objectData, options);
1090
+ if (!payload || !this.sceneCommandService) return;
1091
+ this.sceneCommandService.emitEvent(eventName, payload);
1092
+ },
1093
+ isTransformValueChanged(oldValue = [], newValue = []) {
1094
+ if (
1095
+ !Array.isArray(oldValue) ||
1096
+ !Array.isArray(newValue) ||
1097
+ oldValue.length !== newValue.length
1098
+ ) {
1099
+ return true;
1100
+ }
1101
+ for (let index = 0; index < oldValue.length; index += 1) {
1102
+ const currentDiff = Math.abs(
1103
+ (Number(oldValue[index]) || 0) - (Number(newValue[index]) || 0)
1104
+ );
1105
+ if (currentDiff > 0.000001) {
1106
+ return true;
1107
+ }
1108
+ }
1109
+ return false;
1110
+ },
1111
+ commitTransformChanges(object) {
1112
+ if (!object || !this.sceneCommandService) return null;
1113
+ const startSnapshot = this.transformEditor.transformStartSnapshot;
1114
+ const endSnapshot = this.getCustomObjectSnapshot(object.uuid);
1115
+ if (!startSnapshot || !endSnapshot) {
1116
+ return endSnapshot;
1117
+ }
1118
+ const mode = this.getTransformMode();
1119
+ if (mode === TRANSFORM_MODE.TRANSLATE) {
1120
+ if (this.isTransformValueChanged(startSnapshot.position, endSnapshot.position)) {
1121
+ this.setElementPosition(object.uuid, endSnapshot.position, startSnapshot.position);
1122
+ }
1123
+ } else if (mode === TRANSFORM_MODE.ROTATE) {
1124
+ if (this.isTransformValueChanged(startSnapshot.rotation, endSnapshot.rotation)) {
1125
+ this.setElementRotation(object.uuid, endSnapshot.rotation, startSnapshot.rotation);
1126
+ }
1127
+ } else if (mode === TRANSFORM_MODE.SCALE) {
1128
+ if (this.isTransformValueChanged(startSnapshot.scale, endSnapshot.scale)) {
1129
+ this.setElementScale(object.uuid, endSnapshot.scale, startSnapshot.scale);
1130
+ }
1131
+ }
1132
+ return this.getCustomObjectSnapshot(object.uuid);
1133
+ },
1134
+ handleTransformObjectChange() {
1135
+ const editor = this.transformEditor;
1136
+ const controls = editor && editor.transformControls;
1137
+ if (!editor || !controls || !controls.object || !editor.transforming) return;
1138
+ controls.object.updateMatrixWorld(true);
1139
+ this.emitTransformEvent(
1140
+ EDITOR_EVENT.OBJECT_TRANSFORM_CHANGING,
1141
+ this.getCustomObjectSnapshot(controls.object.uuid),
1142
+ {
1143
+ isTransforming: true,
1144
+ }
1145
+ );
1146
+ if (typeof this.notifyCameraChange === 'function') {
1147
+ this.notifyCameraChange('customTransform');
1148
+ }
1149
+ },
1150
+ handleTransformDraggingChanged(event) {
1151
+ const editor = this.transformEditor;
1152
+ if (!editor) return;
1153
+ const isDraggingNow = !!(event && event.value);
1154
+ editor.transforming = isDraggingNow;
1155
+ if (this.cameraControls) {
1156
+ if (isDraggingNow) {
1157
+ editor.cameraControlsEnabled = editor.pointerCameraGuard
1158
+ ? editor.pointerCameraEnabled
1159
+ : this.cameraControls.enabled;
1160
+ this.cameraControls.enabled = false;
1161
+ } else {
1162
+ this.restoreTransformCameraControls();
1163
+ }
1164
+ }
1165
+ const controls = editor.transformControls;
1166
+ const object = controls && controls.object ? controls.object : null;
1167
+ if (isDraggingNow) {
1168
+ editor.transformStartSnapshot = object ? this.getCustomObjectSnapshot(object.uuid) : null;
1169
+ this.emitTransformEvent(
1170
+ EDITOR_EVENT.OBJECT_TRANSFORM_CHANGING,
1171
+ editor.transformStartSnapshot,
1172
+ {
1173
+ isTransforming: true,
1174
+ }
1175
+ );
1176
+ return;
1177
+ }
1178
+ const resultSnapshot = this.commitTransformChanges(object);
1179
+ editor.transformStartSnapshot = null;
1180
+ this.emitTransformEvent(EDITOR_EVENT.OBJECT_TRANSFORM_CHANGED, resultSnapshot, {
1181
+ isTransforming: false,
1182
+ });
1183
+ },
397
1184
  getOutlineInstanceProxyKey(instancedMesh, instanceIndex) {
398
1185
  return `${instancedMesh.uuid}:${instanceIndex}`;
399
1186
  },
@@ -490,20 +1277,6 @@ export default {
490
1277
  if (proxy.material) proxy.material.dispose && proxy.material.dispose();
491
1278
  return proxy;
492
1279
  },
493
- // 判断是设备是手机还是电脑
494
- isMobileDevice() {
495
- const userAgent = navigator.userAgent || navigator.vendor || window.opera;
496
- if (/windows phone/i.test(userAgent)) {
497
- return true;
498
- }
499
- if (/android/i.test(userAgent)) {
500
- return true;
501
- }
502
- if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) {
503
- return true;
504
- }
505
- return false;
506
- },
507
1280
  // 节流工具方法
508
1281
  throttle(func, limit) {
509
1282
  let lastFunc;
@@ -2306,6 +3079,7 @@ export default {
2306
3079
  projectId,
2307
3080
  debug: isDebug,
2308
3081
  renderModelData: this.renderModelData.bind(this),
3082
+ onRangeStreamComplete: this.handleRangeStreamComplete.bind(this),
2309
3083
  ensureNotInteracting: async abortSignal => {
2310
3084
  if (
2311
3085
  this.userInteracting ||
@@ -2415,6 +3189,19 @@ export default {
2415
3189
  this.setSceneBox(null, documentId, false);
2416
3190
  this.setBoxIndex(null, documentId, false);
2417
3191
  },
3192
+ locateSceneBoxByDocumentId(documentId, options = {}) {
3193
+ if (!documentId || !this.noObserver || !this.noObserver.sceneBoxes) {
3194
+ return false;
3195
+ }
3196
+ const box = this.noObserver.sceneBoxes.get(String(documentId));
3197
+ if (!box || !box.isBox3 || box.isEmpty()) {
3198
+ return false;
3199
+ }
3200
+ const center = box.getCenter(new this.THREE.Vector3());
3201
+ const size = box.getSize(new this.THREE.Vector3());
3202
+ this.locateByCenterBox(center, size, options);
3203
+ return true;
3204
+ },
2418
3205
  /**
2419
3206
  * 同步获取当前WebGL画面的截图数据,解决preserveDrawingBuffer为false时html2canvas截黑屏的问题
2420
3207
  */
@@ -2434,7 +3221,15 @@ export default {
2434
3221
  /**
2435
3222
  * 内部渲染流式数据方法
2436
3223
  */
2437
- renderModelData(meshes, primitives, list, range, onComplete, immediateUpdate) {
3224
+ renderModelData(
3225
+ meshes,
3226
+ primitives,
3227
+ list,
3228
+ range,
3229
+ onComplete,
3230
+ immediateUpdate,
3231
+ renderOptions = {}
3232
+ ) {
2438
3233
  // 构造 drawModel 需要的数据格式
2439
3234
  const modelRegistry = this.noObserver.streamLoader.modelRegistry;
2440
3235
  const modelRecords = Array.from(modelRegistry.values());
@@ -2481,10 +3276,56 @@ export default {
2481
3276
  version: list ? list.version : '',
2482
3277
  };
2483
3278
 
2484
- options.onComplete = onComplete;
3279
+ options.suppressLoadComplete = !!renderOptions.suppressLoadComplete;
2485
3280
  options.immediateUpdate = immediateUpdate;
2486
3281
 
2487
- this.drawModel(regionModelData, '', meshNameConfig, options);
3282
+ return new Promise(resolve => {
3283
+ let finished = false;
3284
+ const finish = result => {
3285
+ if (finished) return;
3286
+ finished = true;
3287
+ resolve(result || {});
3288
+ };
3289
+
3290
+ options.onComplete = complete => {
3291
+ if (
3292
+ this.sceneCommandService &&
3293
+ typeof this.sceneCommandService.retryPendingOriginalModelSaveChanges === 'function'
3294
+ ) {
3295
+ this.sceneCommandService.retryPendingOriginalModelSaveChanges();
3296
+ }
3297
+ onComplete?.(complete);
3298
+ finish(complete);
3299
+ };
3300
+ options.onCancel = cancelInfo => {
3301
+ finish({
3302
+ canceled: true,
3303
+ ...(cancelInfo || {}),
3304
+ });
3305
+ };
3306
+
3307
+ this.drawModel(regionModelData, '', meshNameConfig, options);
3308
+ });
3309
+ },
3310
+
3311
+ waitNextRenderFrame() {
3312
+ return new Promise(resolve => {
3313
+ if (typeof requestAnimationFrame === 'function') {
3314
+ requestAnimationFrame(() => {
3315
+ resolve();
3316
+ });
3317
+ return;
3318
+ }
3319
+ setTimeout(resolve, 0);
3320
+ });
3321
+ },
3322
+
3323
+ async handleRangeStreamComplete(payload = {}) {
3324
+ await this.waitNextRenderFrame();
3325
+ this.$emit('loadComplete', {
3326
+ source: 'inRangeDis2',
3327
+ ...payload,
3328
+ });
2488
3329
  },
2489
3330
 
2490
3331
  getRangeStream(options) {
@@ -2570,6 +3411,9 @@ export default {
2570
3411
  this.renderer.debug.checkShaderErrors = false;
2571
3412
  this.renderer.info.autoReset = false;
2572
3413
  this.renderer.setPixelRatio(window.devicePixelRatio);
3414
+ if (this.instructions && window.getComputedStyle(this.instructions).position === 'static') {
3415
+ this.instructions.style.position = 'relative';
3416
+ }
2573
3417
  const rect = this.instructions.getBoundingClientRect();
2574
3418
  this.renderer.setSize(rect.width, rect.height);
2575
3419
  this.renderer.domElement.id = 'three-model-' + this.containId;
@@ -2825,7 +3669,7 @@ export default {
2825
3669
  const rect = this.instructions.getBoundingClientRect();
2826
3670
  this.labelRenderer.setSize(rect.width, rect.height);
2827
3671
  this.labelRenderer.domElement.style.position = 'absolute';
2828
-
3672
+ this.labelRenderer.domElement.style.left = '0';
2829
3673
  this.labelRenderer.domElement.style.top = '0';
2830
3674
  this.labelRenderer.domElement.style.pointerEvents = 'none';
2831
3675
  this.instructions.appendChild(this.labelRenderer.domElement);
@@ -2853,7 +3697,9 @@ export default {
2853
3697
  options?.onComplete?.(complete);
2854
3698
  console.log('加载完成');
2855
3699
  // 触发原有的完成事件
2856
- this.$emit('loadComplete');
3700
+ if (!options?.suppressLoadComplete) {
3701
+ this.$emit('loadComplete');
3702
+ }
2857
3703
  }
2858
3704
  );
2859
3705
  },
@@ -2885,6 +3731,14 @@ export default {
2885
3731
  this.skipNextRenderFrame = true;
2886
3732
 
2887
3733
  const intersects = this.getRaycasterObjects(event);
3734
+ const shouldBlockCamera = intersects.some(item =>
3735
+ this.isActiveTransformControlIntersection(item)
3736
+ );
3737
+ if (shouldBlockCamera) {
3738
+ this.setPointerCameraGuard(true);
3739
+ } else {
3740
+ this.setPointerCameraGuard(false);
3741
+ }
2888
3742
  this.firstTime = new Date().getTime();
2889
3743
  let params = {
2890
3744
  event,
@@ -2915,6 +3769,87 @@ export default {
2915
3769
  ? this.raycaster.intersectObjects(this.scene.children, true)
2916
3770
  : [];
2917
3771
  },
3772
+ isTransformControlIntersection(intersection) {
3773
+ let current = intersection && intersection.object ? intersection.object : null;
3774
+ while (current) {
3775
+ if (current.userData && current.userData.transformControlHelper === true) {
3776
+ return true;
3777
+ }
3778
+ current = current.parent;
3779
+ }
3780
+ return false;
3781
+ },
3782
+ isActiveTransformControlIntersection(intersection) {
3783
+ const editor = this.transformEditor;
3784
+ const controls = editor && editor.transformControls;
3785
+ const helper = editor && editor.transformHelper;
3786
+ if (!controls || !controls.object || !helper || !helper.visible) {
3787
+ return false;
3788
+ }
3789
+ return this.isTransformControlIntersection(intersection);
3790
+ },
3791
+ getPrimaryIntersection(intersects = []) {
3792
+ if (!Array.isArray(intersects) || intersects.length === 0) {
3793
+ return null;
3794
+ }
3795
+ const validIntersects = intersects.filter(
3796
+ item => item && item.object && !this.isTransformControlIntersection(item)
3797
+ );
3798
+ if (validIntersects.length === 0) {
3799
+ return null;
3800
+ }
3801
+ const selectableIntersect = validIntersects.find(item =>
3802
+ this.getSelectableSceneObject(item.object)
3803
+ );
3804
+ return selectableIntersect || validIntersects[0] || null;
3805
+ },
3806
+ buildIntersectionParams(intersection, event, cameraData) {
3807
+ if (!intersection) {
3808
+ return {
3809
+ objects: [],
3810
+ mousePosition: { x: event.clientX, y: event.clientY },
3811
+ camera: cameraData,
3812
+ v3Position: {
3813
+ x: -1,
3814
+ y: -1,
3815
+ z: -1,
3816
+ },
3817
+ };
3818
+ }
3819
+ const selectableObject = this.getSelectableSceneObject(intersection.object);
3820
+ const params = {
3821
+ objects: [intersection.object],
3822
+ mousePosition: { x: event.clientX, y: event.clientY },
3823
+ camera: cameraData,
3824
+ v3Position: {
3825
+ x: intersection.point.x,
3826
+ y: intersection.point.y,
3827
+ z: intersection.point.z,
3828
+ },
3829
+ instanceId: this.getInstanceId(intersection.object, intersection.instanceId),
3830
+ };
3831
+ if (selectableObject) {
3832
+ const nodeType = this.sceneCommandService
3833
+ ? this.sceneCommandService.getNodeType(selectableObject.uuid)
3834
+ : '';
3835
+ params.selectedObject = selectableObject;
3836
+ params.selectedObjectUuid = selectableObject.uuid;
3837
+ params.selectedNodeType = nodeType;
3838
+ if (
3839
+ nodeType === 'custom-element' ||
3840
+ nodeType === 'custom-group' ||
3841
+ nodeType === 'custom-root'
3842
+ ) {
3843
+ params.customObject = selectableObject;
3844
+ params.customObjectUuid = selectableObject.uuid;
3845
+ }
3846
+ if (nodeType === 'original-model') {
3847
+ params.originalModel = selectableObject;
3848
+ params.originalModelUuid = selectableObject.uuid;
3849
+ }
3850
+ }
3851
+ return params;
3852
+ },
2918
3853
  getInstanceId(instancedMesh, instanceIndex, options) {
2919
3854
  if (!instancedMesh || instanceIndex === undefined || instanceIndex === null) {
2920
3855
  return null;
@@ -2936,10 +3871,14 @@ export default {
2936
3871
  }, 50); // 短暂延迟确保交互完成
2937
3872
 
2938
3873
  // 在测量模式下,不进行事件暴露
3874
+ if (!this.transformEditor.transforming) {
3875
+ this.setPointerCameraGuard(false);
3876
+ }
2939
3877
  if (!this.measureFlag) {
2940
3878
  this.lastTime = new Date().getTime();
2941
3879
  if (this.lastTime - this.firstTime < 300) {
2942
3880
  const intersects = this.getRaycasterObjects(event);
3881
+ const primaryIntersection = this.getPrimaryIntersection(intersects);
2943
3882
  let params = {};
2944
3883
  let cameraData = {
2945
3884
  position: {
@@ -2953,43 +3892,101 @@ export default {
2953
3892
  roll: this.cameraControls._target.z,
2954
3893
  },
2955
3894
  };
2956
- if (intersects.length > 0) {
3895
+ if (primaryIntersection) {
2957
3896
  this.clearBypassCullingModelIds();
2958
- const instanceId = this.getInstanceId(intersects[0].object, intersects[0].instanceId);
2959
- params = {
2960
- objects: [intersects[0].object],
2961
- mousePosition: { x: event.clientX, y: event.clientY },
2962
- camera: cameraData,
2963
- v3Position: {
2964
- x: intersects[0].point.x,
2965
- y: intersects[0].point.y,
2966
- z: intersects[0].point.z,
2967
- },
2968
- // inHighPriorityRegion: inHighPriority,
2969
- instanceId,
2970
- };
3897
+ params = this.buildIntersectionParams(primaryIntersection, event, cameraData);
2971
3898
  } else {
2972
- params = {
2973
- objects: [],
2974
- mousePosition: { x: event.clientX, y: event.clientY },
2975
- camera: cameraData,
2976
- v3Position: {
2977
- x: -1,
2978
- y: -1,
2979
- z: -1,
2980
- },
2981
- // inHighPriorityRegion: false,
2982
- };
3899
+ params = this.buildIntersectionParams(null, event, cameraData);
2983
3900
  }
2984
3901
  if (event.button === 0) {
3902
+ if (this.transformEditor && this.transformEditor.suppressSelectionOnce) {
3903
+ this.transformEditor.suppressSelectionOnce = false;
3904
+ this.setPointerCameraGuard(false);
3905
+ return;
3906
+ }
3907
+ if (params.selectedObjectUuid) {
3908
+ this.selectSceneObject(params.selectedObjectUuid);
3909
+ } else if (this.sceneCommandService) {
3910
+ this.clearCustomSelection();
3911
+ }
3912
+ if (!this.transformEditor.transforming) {
3913
+ this.setPointerCameraGuard(false);
3914
+ }
2985
3915
  this.$emit('leftClick', params);
2986
3916
  } else if (event.button === 1) {
3917
+ this.setPointerCameraGuard(false);
2987
3918
  if (this.lastTime - this.lastMiddleClickTime < 2000) {
2988
3919
  this.$emit('middleDblClick', params);
2989
3920
  this.lastMiddleClickTime = 0;
2990
3921
  } else {
2991
3922
  this.lastMiddleClickTime = this.lastTime;
2992
3923
  }
3924
+ } else if (event.button === 2) {
3925
+ if ((params.objects && params.objects.length > 0) || this.isolateMode) {
3926
+ this.setPointerCameraGuard(false);
3927
+ this.$emit('rightClick', params);
3928
+ onContextHandle(
3929
+ event,
3930
+ 'fl-model',
3931
+ '隐藏',
3932
+ this.isolateMode ? '取消隔离' : '隔离',
3933
+ () => {
3934
+ this.updateProperty([
3935
+ {
3936
+ name: params.instanceId,
3937
+ attr: {
3938
+ visible: false,
3939
+ },
3940
+ },
3941
+ ]);
3942
+ },
3943
+ () => {
3944
+ this.setAllModelVisible(this.isolateMode);
3945
+ this.updateProperty([
3946
+ {
3947
+ name: params.instanceId,
3948
+ attr: {
3949
+ visible: true,
3950
+ },
3951
+ },
3952
+ ]);
3953
+ this.isolateMode = !this.isolateMode;
3954
+ }
3955
+ );
3956
+ }
3957
+ } else if (event.button === 2) {
3958
+ if ((params.objects && params.objects.length > 0) || this.isolateMode) {
3959
+ this.setPointerCameraGuard(false);
3960
+ this.$emit('rightClick', params);
3961
+ onContextHandle(
3962
+ event,
3963
+ 'fl-model',
3964
+ '隐藏',
3965
+ this.isolateMode ? '取消隔离' : '隔离',
3966
+ () => {
3967
+ this.updateProperty([
3968
+ {
3969
+ name: params.instanceId,
3970
+ attr: {
3971
+ visible: false,
3972
+ },
3973
+ },
3974
+ ]);
3975
+ },
3976
+ () => {
3977
+ this.setAllModelVisible(this.isolateMode);
3978
+ this.updateProperty([
3979
+ {
3980
+ name: params.instanceId,
3981
+ attr: {
3982
+ visible: true,
3983
+ },
3984
+ },
3985
+ ]);
3986
+ this.isolateMode = !this.isolateMode;
3987
+ }
3988
+ );
3989
+ }
2993
3990
  }
2994
3991
  }
2995
3992
  }
@@ -3129,7 +4126,9 @@ export default {
3129
4126
  case 'color':
3130
4127
  targetObj.forEach(children => {
3131
4128
  if (children.isMesh) {
3132
- const { instanceIndex } = children.userData.instancesMap.get(instanceId);
4129
+ const instanceInfo = children.userData?.instancesMap?.get(instanceId);
4130
+ if (!instanceInfo) return;
4131
+ const { instanceIndex } = instanceInfo;
3133
4132
  children.setColorAt(instanceIndex, new this.THREE.Color(ele.attr[key]));
3134
4133
  children.instanceColor.needsUpdate = true;
3135
4134
  if (!this.isCurrentVisible(instanceId, children)) {
@@ -3167,6 +4166,7 @@ export default {
3167
4166
  }
3168
4167
  targetObj.forEach(children => {
3169
4168
  const instanceInfo = children.userData.instancesMap.get(instanceId);
4169
+ if (!instanceInfo) return;
3170
4170
  const { instanceIndex, copyMatrix } = instanceInfo;
3171
4171
  const nextVisible = requestedVisible && this.isSourceVisible(instanceId, children);
3172
4172
  instanceInfo.visible = nextVisible;
@@ -3193,7 +4193,9 @@ export default {
3193
4193
  targetObj.forEach(children => {
3194
4194
  if (children.isMesh) {
3195
4195
  const opacity = children.geometry.attributes.opacity.array;
3196
- const { instanceIndex } = children.userData.instancesMap.get(instanceId);
4196
+ const instanceInfo = children.userData?.instancesMap?.get(instanceId);
4197
+ if (!instanceInfo) return;
4198
+ const { instanceIndex } = instanceInfo;
3197
4199
  opacity[instanceIndex] = ele.attr[key];
3198
4200
  children.geometry.attributes.opacity.needsUpdate = true;
3199
4201
  }
@@ -3263,7 +4265,9 @@ export default {
3263
4265
  let targetObj = this.getObjectByName(instanceId);
3264
4266
  targetObj.forEach(children => {
3265
4267
  if (children.isMesh) {
3266
- const { instanceIndex } = children.userData.instancesMap.get(instanceId);
4268
+ const instanceInfo = children.userData?.instancesMap?.get(instanceId);
4269
+ if (!instanceInfo) return;
4270
+ const { instanceIndex } = instanceInfo;
3267
4271
  if (!this.isSourceVisible(instanceId, children)) {
3268
4272
  this.removeOutlineObject(children, instanceIndex);
3269
4273
  return;
@@ -3287,7 +4291,9 @@ export default {
3287
4291
  let targetObj = this.getObjectByName(instanceId);
3288
4292
  targetObj.forEach(children => {
3289
4293
  if (children.isMesh) {
3290
- const { instanceIndex } = children.userData.instancesMap.get(instanceId);
4294
+ const instanceInfo = children.userData?.instancesMap?.get(instanceId);
4295
+ if (!instanceInfo) return;
4296
+ const { instanceIndex } = instanceInfo;
3291
4297
  this.removeOutlineObject(children, instanceIndex);
3292
4298
  }
3293
4299
  });
@@ -3340,7 +4346,9 @@ export default {
3340
4346
  case 'nColor':
3341
4347
  targetObj.forEach(children => {
3342
4348
  if (children.isMesh) {
3343
- const { instanceIndex } = children.userData.instancesMap.get(instanceId);
4349
+ const instanceInfo = children.userData?.instancesMap?.get(instanceId);
4350
+ if (!instanceInfo) return;
4351
+ const { instanceIndex } = instanceInfo;
3344
4352
  children.setColorAt(instanceIndex, children.material.userData['oColor']);
3345
4353
  children.instanceColor.needsUpdate = true;
3346
4354
  children.material.userData[key] = children.material.userData['oColor'];
@@ -3363,7 +4371,9 @@ export default {
3363
4371
  case 'color':
3364
4372
  obj.forEach(children => {
3365
4373
  if (children.isMesh) {
3366
- const { instanceIndex } = children.userData.instancesMap.get(instanceId);
4374
+ const instanceInfo = children.userData?.instancesMap?.get(instanceId);
4375
+ if (!instanceInfo) return;
4376
+ const { instanceIndex } = instanceInfo;
3367
4377
  children.setColorAt(instanceIndex, children.material.userData.nColor);
3368
4378
  children.instanceColor.needsUpdate = true;
3369
4379
  if (this.outlinePass) {
@@ -3380,6 +4390,7 @@ export default {
3380
4390
  case 'visible': {
3381
4391
  obj.forEach(children => {
3382
4392
  const instanceInfo = children.userData.instancesMap.get(instanceId);
4393
+ if (!instanceInfo) return;
3383
4394
  const { instanceIndex, copyMatrix } = instanceInfo;
3384
4395
  if (this.isSourceVisible(instanceId, children)) {
3385
4396
  instanceInfo.visible = true;
@@ -3400,7 +4411,9 @@ export default {
3400
4411
  case 'opacity':
3401
4412
  obj.forEach(children => {
3402
4413
  if (children.isMesh) {
3403
- const { instanceIndex } = children.userData.instancesMap.get(instanceId);
4414
+ const instanceInfo = children.userData?.instancesMap?.get(instanceId);
4415
+ if (!instanceInfo) return;
4416
+ const { instanceIndex } = instanceInfo;
3404
4417
  const opacity = children.geometry.attributes.opacity.array;
3405
4418
  opacity[instanceIndex] = children.material.userData.nOpacity;
3406
4419
  children.geometry.attributes.opacity.needsUpdate = true;
@@ -3411,24 +4424,56 @@ export default {
3411
4424
  }
3412
4425
  }
3413
4426
  },
4427
+ expandLocateBoxByObject(box3, object) {
4428
+ if (!box3 || !object) return;
4429
+ if (object.isGroup) {
4430
+ object.traverseVisible(child => {
4431
+ if (child.isMesh || child.isLine || child.isPoints) {
4432
+ box3.expandByObject(child);
4433
+ }
4434
+ });
4435
+ return;
4436
+ }
4437
+ box3.expandByObject(object);
4438
+ },
4439
+ locateObjectByBox(object, options = {}) {
4440
+ if (!object) return false;
4441
+ const box3 = new this.THREE.Box3();
4442
+ this.expandLocateBoxByObject(box3, object);
4443
+ if (box3.isEmpty()) return false;
4444
+ const center = box3.getCenter(new this.THREE.Vector3());
4445
+ const size = box3.getSize(new this.THREE.Vector3());
4446
+ this.locateByCenterBox(center, size, options);
4447
+ return true;
4448
+ },
3414
4449
  // 定位到模型
3415
- // name 模型名称 可以是数组
4450
+ // name 模型名称或运行态 uuid,可以是数组
3416
4451
  locateModel(name) {
3417
- if (!this.scene) return;
4452
+ if (!this.scene) return false;
3418
4453
  if (Array.isArray(name)) {
3419
4454
  const box3 = new this.THREE.Box3();
3420
4455
  name.forEach(n => {
4456
+ const objectByUuid = this.getObjectByUuid(n);
4457
+ if (objectByUuid) {
4458
+ this.expandLocateBoxByObject(box3, objectByUuid);
4459
+ return;
4460
+ }
3421
4461
  const arr = this.getObjectByName(n);
3422
4462
  arr.forEach(o => {
3423
- box3.expandByObject(o);
4463
+ this.expandLocateBoxByObject(box3, o);
3424
4464
  });
3425
4465
  });
3426
4466
  if (!box3.isEmpty()) {
3427
4467
  const center = box3.getCenter(new this.THREE.Vector3());
3428
4468
  const size = box3.getSize(new this.THREE.Vector3());
3429
4469
  this.locateByCenterBox(center, size, { viewAll: true });
4470
+ return true;
3430
4471
  }
3431
- return;
4472
+ return false;
4473
+ }
4474
+ const objectByUuid = this.getObjectByUuid(name);
4475
+ if (objectByUuid && this.locateObjectByBox(objectByUuid)) {
4476
+ return true;
3432
4477
  }
3433
4478
  let obj = this.getObjectByName(name)[0];
3434
4479
  if (obj) {
@@ -3440,7 +4485,9 @@ export default {
3440
4485
  let size = this.getSize(obj);
3441
4486
  this.locateByCenterBox(center, size);
3442
4487
  }
4488
+ return true;
3443
4489
  }
4490
+ return false;
3444
4491
  },
3445
4492
  // 根据自定义参数修改模型
3446
4493
  /*
@@ -3559,16 +4606,41 @@ export default {
3559
4606
  */
3560
4607
  getObjectByName(name, passType = 'group') {
3561
4608
  if (!this.scene) return [];
4609
+ if (Array.isArray(name)) {
4610
+ const result = [];
4611
+ const exist = new Set();
4612
+ name.forEach(item => {
4613
+ this.getObjectByName(item, passType).forEach(obj => {
4614
+ if (!obj || exist.has(obj.uuid)) return;
4615
+ exist.add(obj.uuid);
4616
+ result.push(obj);
4617
+ });
4618
+ });
4619
+ return result;
4620
+ }
3562
4621
  let object = [];
4622
+ const added = new Set();
3563
4623
  const instancedMeshProps = instanceToInstancedMeshMap.get(name);
3564
4624
  this.scene.traverse(item => {
3565
4625
  const tempName = instancedMeshProps ? instancedMeshProps.drawObjectId : name;
3566
- if (item.name == tempName && item.type.toLowerCase() != passType.toLowerCase()) {
4626
+ const itemType = item.type ? item.type.toLowerCase() : '';
4627
+ const isPassType = itemType == passType.toLowerCase();
4628
+ const hasTargetInstance =
4629
+ item.userData &&
4630
+ item.userData.instancesMap instanceof Map &&
4631
+ item.userData.instancesMap.has(name);
4632
+ if (!isPassType && (item.name == tempName || hasTargetInstance) && !added.has(item.uuid)) {
4633
+ added.add(item.uuid);
3567
4634
  object.push(item);
3568
4635
  }
3569
4636
  });
3570
4637
  return object;
3571
4638
  },
4639
+ // 通过 uuid 获取实体对象
4640
+ getObjectByUuid(uuid) {
4641
+ if (!this.scene || !uuid) return null;
4642
+ return this.scene.getObjectByProperty('uuid', uuid) || null;
4643
+ },
3572
4644
  // 通过id获取实体对象, 返回查找到的对象
3573
4645
  getObjectById(id) {
3574
4646
  if (!this.scene) return null;
@@ -3654,6 +4726,7 @@ export default {
3654
4726
  removeAll() {
3655
4727
  return new Promise(resolve => {
3656
4728
  if (this.scene) {
4729
+ this.removeAllCustomElements();
3657
4730
  this.removeTraverse();
3658
4731
  this.removeModelByDocumentId();
3659
4732
  resolve();
@@ -3693,6 +4766,16 @@ export default {
3693
4766
  // 销毁场景 释放内存
3694
4767
  destroyScene() {
3695
4768
  cancelAnimationFrame(this.animateId);
4769
+ this.disposeTransformControls();
4770
+
4771
+ if (this.sceneCommandEventDisposers.length > 0) {
4772
+ this.sceneCommandEventDisposers.forEach(dispose => dispose && dispose());
4773
+ this.sceneCommandEventDisposers = [];
4774
+ }
4775
+ if (this.sceneCommandService) {
4776
+ this.sceneCommandService.destroy();
4777
+ this.sceneCommandService = null;
4778
+ }
3696
4779
 
3697
4780
  if (this.noObserver && this.noObserver.outlineInstanceProxyMap) {
3698
4781
  this.noObserver.outlineInstanceProxyMap.forEach(proxy => {
@@ -3755,12 +4838,7 @@ export default {
3755
4838
  }
3756
4839
 
3757
4840
  // 移除鼠标点击/按下事件监听器
3758
- if (this.renderer && this.renderer.domElement) {
3759
- this.renderer.domElement.removeEventListener('mouseup', this.mouseClick, false);
3760
- this.renderer.domElement.removeEventListener('mousedown', this.mouseDown, false);
3761
- this.renderer.domElement.removeEventListener('pointerup', this.mouseClick, false);
3762
- this.renderer.domElement.removeEventListener('pointerdown', this.mouseDown, false);
3763
- }
4841
+ this.unbindScenePointerEvents();
3764
4842
 
3765
4843
  // 取消 pointer lock 并移除相关键盘事件
3766
4844
  if (this.pointControls) {
@@ -4002,13 +5080,46 @@ export default {
4002
5080
  });
4003
5081
  return clippingPlanesConstant;
4004
5082
  },
5083
+ isGlobalClippingExcludedNode(object) {
5084
+ if (!object) return true;
5085
+ const userData = object.userData || {};
5086
+ const nodeType = userData.nodeType || userData.rootType || '';
5087
+ if (nodeType === 'custom-root') return true;
5088
+ if (userData.transformControlHelper === true) return true;
5089
+ if (object.isCamera || object.isLight) return true;
5090
+ if (typeof object.type === 'string' && /Helper$/i.test(object.type)) return true;
5091
+ return object.type === 'CSS2DObject' || object.type === 'TransformControlsRoot';
5092
+ },
5093
+ getGlobalClippingRoots() {
5094
+ if (this.modelGroup && this.modelGroup.children && this.modelGroup.children.length) {
5095
+ return [this.modelGroup];
5096
+ }
5097
+ if (!this.scene || !this.scene.children || this.scene.children.length === 0) return [];
5098
+ return this.scene.children.filter(child => !this.isGlobalClippingExcludedNode(child));
5099
+ },
5100
+ getGlobalClippingBox() {
5101
+ if (!this.scene) return null;
5102
+ const roots = this.getGlobalClippingRoots();
5103
+ if (roots.length === 0) return null;
5104
+ const box3 = new this.THREE.Box3();
5105
+ roots.forEach(root => {
5106
+ box3.expandByObject(root);
5107
+ });
5108
+ return box3.isEmpty() ? null : box3;
5109
+ },
4005
5110
  // 设置全局整体剖切
4006
5111
  /*
4007
5112
  先开启模型全局剖切模式, 会返回剖切值的最大最小值
4008
5113
  剖切值变换时, 使用
4009
5114
  */
4010
5115
  setGlobalClipping(flag = true) {
4011
- const box3 = new this.THREE.Box3().setFromObject(this.scene.children[0]);
5116
+ const box3 = this.getGlobalClippingBox();
5117
+ if (!box3) {
5118
+ return {
5119
+ min: null,
5120
+ max: null,
5121
+ };
5122
+ }
4012
5123
  let max = box3.max;
4013
5124
  let min = box3.min;
4014
5125
  const clippingPlanes = [
@@ -4240,10 +5351,38 @@ export default {
4240
5351
  objClipp2 && (objClipp2[4].constant = -d);
4241
5352
  });
4242
5353
  },
5354
+ getFirstPersonMoveSpeed(customMoveSpeed) {
5355
+ const defaultMoveSpeed = 800;
5356
+ const minMoveSpeed = 300;
5357
+ const maxMoveSpeed = 2500;
5358
+ const optionMoveSpeed =
5359
+ Number.isFinite(customMoveSpeed) && customMoveSpeed > 0
5360
+ ? customMoveSpeed
5361
+ : defaultMoveSpeed;
5362
+ let nextMoveSpeed = defaultMoveSpeed;
5363
+
5364
+ if (
5365
+ this.sceneBoundingBox &&
5366
+ this.sceneBoundingBox.isBox3 &&
5367
+ !this.sceneBoundingBox.isEmpty()
5368
+ ) {
5369
+ const size = this.sceneBoundingBox.getSize(new this.THREE.Vector3());
5370
+ const horizontalSpan = Math.max(size.x, size.z);
5371
+ if (Number.isFinite(horizontalSpan) && horizontalSpan > 0) {
5372
+ // 第一视角主要在水平面移动,优先使用场景水平范围估算移动速度
5373
+ nextMoveSpeed = horizontalSpan * 0.8;
5374
+ }
5375
+ }
5376
+
5377
+ return Math.min(
5378
+ Math.max(nextMoveSpeed, minMoveSpeed),
5379
+ Math.min(maxMoveSpeed, optionMoveSpeed)
5380
+ );
5381
+ },
4243
5382
  // 开启第一视角
4244
5383
  startFirstPer(options) {
4245
5384
  let { moveSpeed = 200, jumpSpeed = 200 } = options || {};
4246
- this.removeSpeed = moveSpeed;
5385
+ this.removeSpeed = this.getFirstPersonMoveSpeed(moveSpeed);
4247
5386
  this.upSpeed = jumpSpeed;
4248
5387
 
4249
5388
  this.clock = new this.THREE.Clock();
@@ -4280,11 +5419,11 @@ export default {
4280
5419
  } catch (e) {}
4281
5420
  // 锁定
4282
5421
  this.pointControls.addEventListener('lock', () => {
5422
+ this.detachTransformControls();
4283
5423
  this.cameraControls.enabled = false;
4284
5424
  window.addEventListener('keydown', this.onKeyDown, false);
4285
5425
  window.addEventListener('keyup', this.onKeyUp, false);
4286
- this.renderer.domElement.removeEventListener('mouseup', this.mouseClick, false);
4287
- this.renderer.domElement.removeEventListener('mousedown', this.mouseDown, false);
5426
+ this.unbindScenePointerEvents();
4288
5427
  if (typeof this._cameraChangeObserver === 'function') {
4289
5428
  this._cameraChangeObserver('firstPersonLock');
4290
5429
  }
@@ -4293,6 +5432,7 @@ export default {
4293
5432
  this.pointControls.addEventListener('unlock', () => {
4294
5433
  this.firstPerSign = false;
4295
5434
  this.cameraControls.enabled = true;
5435
+ console.log(this.cameraControls);
4296
5436
  // 返回初始视角
4297
5437
  this.home();
4298
5438
  try {
@@ -4306,8 +5446,8 @@ export default {
4306
5446
  setTimeout(() => {
4307
5447
  window.removeEventListener('keydown', this.onKeyDown);
4308
5448
  window.removeEventListener('keyup', this.onKeyUp);
4309
- this.renderer.domElement.addEventListener('mouseup', this.mouseClick, false);
4310
- this.renderer.domElement.addEventListener('mousedown', this.mouseDown, false);
5449
+ this.bindScenePointerEvents();
5450
+ this.ensureTransformSelectionValid();
4311
5451
  // this.timeRender()
4312
5452
  }, 0);
4313
5453
  if (typeof this._cameraChangeObserver === 'function') {
@@ -4353,8 +5493,8 @@ export default {
4353
5493
  this.canJump = true;
4354
5494
  }
4355
5495
  // 根据速度值移动控制器
4356
- this.pointControls.this.moveRight(-this.velocity.x * delta);
4357
- this.pointControls.this.moveForward(-this.velocity.z * delta);
5496
+ this.pointControls.moveRight(-this.velocity.x * delta);
5497
+ this.pointControls.moveForward(-this.velocity.z * delta);
4358
5498
  control.position.y += this.velocity.y * delta;
4359
5499
  // 保证控制器的y轴在平面上
4360
5500
  if (control.position.y < 3 - 0 / 10) {
@@ -4405,9 +5545,9 @@ export default {
4405
5545
  break;
4406
5546
  // 跳跃
4407
5547
  case 32:
4408
- if (this.canJump && spaceUp) this.velocity.y += this.upSpeed;
5548
+ if (this.canJump && this.spaceUp) this.velocity.y += this.upSpeed;
4409
5549
  this.canJump = false;
4410
- spaceUp = false;
5550
+ this.spaceUp = false;
4411
5551
  break;
4412
5552
  }
4413
5553
  },
@@ -4437,7 +5577,7 @@ export default {
4437
5577
  break;
4438
5578
  // 跳跃
4439
5579
  case 32:
4440
- spaceUp = true;
5580
+ this.spaceUp = true;
4441
5581
  break;
4442
5582
  }
4443
5583
  },
@@ -4470,9 +5610,9 @@ export default {
4470
5610
  /*
4471
5611
  参数: type: '', distance、area、angle、height, 暂时只提供距离、面积、角度、高度这四种方式
4472
5612
  */
4473
- openMeasure(type) {
5613
+ openMeasure(type, isClear = false) {
4474
5614
  if (this.threeMeasure) {
4475
- this.threeMeasure.close(false);
5615
+ this.threeMeasure.close(isClear);
4476
5616
  this.threeMeasure = null;
4477
5617
  }
4478
5618
  this.measureFlag = true;
@@ -4533,6 +5673,20 @@ export default {
4533
5673
  // 移除键盘事件监听器
4534
5674
  document.removeEventListener('keydown', this.handleMeasureKeyDown, false);
4535
5675
  },
5676
+ // 增加一个清除所有测量结果的方法 使用统一名字的
5677
+ clearMeasureByName() {
5678
+ let list = this.getObjectByName('measureObj')
5679
+ list.forEach(item => {
5680
+ if (item.geometry) {
5681
+ item.geometry.dispose();
5682
+ item.material.dispose();
5683
+ }
5684
+ if (this.scene) this.scene.remove(item);
5685
+ })
5686
+ if (this.threeMeasure) {
5687
+ this.threeMeasure.clear();
5688
+ }
5689
+ },
4536
5690
  handleMeasureKeyDown(event) {
4537
5691
  // 检查是否按下了ESC键
4538
5692
  const keyParam = {
@@ -5002,7 +6156,7 @@ export default {
5002
6156
  : this.batchLoadingState;
5003
6157
  // 如果已经在加载中,先停止之前的加载
5004
6158
  if (loadingState.isLoading) {
5005
- this.stopBatchLoading();
6159
+ this.stopBatchLoading('restart');
5006
6160
  }
5007
6161
 
5008
6162
  // 重置instance-parser的处理状态
@@ -5337,7 +6491,7 @@ export default {
5337
6491
  /**
5338
6492
  * 停止批量加载
5339
6493
  */
5340
- stopBatchLoading() {
6494
+ stopBatchLoading(reason = 'stop') {
5341
6495
  const loadingState = this.noObserver
5342
6496
  ? this.noObserver.batchLoadingState
5343
6497
  : this.batchLoadingState;
@@ -5345,6 +6499,9 @@ export default {
5345
6499
  cancelAnimationFrame(loadingState.animationFrameId);
5346
6500
  loadingState.animationFrameId = null;
5347
6501
  }
6502
+ if (loadingState.isLoading && typeof loadingState.options?.onCancel === 'function') {
6503
+ loadingState.options.onCancel({ reason });
6504
+ }
5348
6505
  loadingState.isLoading = false;
5349
6506
  this.batchLoadingState.isLoading = false;
5350
6507
  },
@@ -5592,6 +6749,7 @@ export default {
5592
6749
  .fl-model-containor {
5593
6750
  width: 100%;
5594
6751
  height: 100%;
6752
+ position: relative;
5595
6753
  cursor: pointer;
5596
6754
  }
5597
6755
  ::v-deep .tips-label {