fl-web-component 2.0.7 → 2.0.9

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 (30) hide show
  1. package/README.md +2 -0
  2. package/dist/fl-web-component.common.js +7223 -1277
  3. package/dist/fl-web-component.common.js.map +1 -1
  4. package/dist/fl-web-component.css +1 -1
  5. package/package.json +1 -1
  6. package/packages/components/com-flcanvas/index.vue +0 -2
  7. package/packages/components/com-graphics/component/context.js +123 -0
  8. package/packages/components/com-graphics/index.vue +1148 -69
  9. package/packages/utils/StreamLoader.js +73 -16
  10. package/src/utils/threejs/editor/command.js +36 -0
  11. package/src/utils/threejs/editor/commands/add-element-command.js +41 -0
  12. package/src/utils/threejs/editor/commands/add-group-command.js +10 -0
  13. package/src/utils/threejs/editor/commands/clone-element-command.js +100 -0
  14. package/src/utils/threejs/editor/commands/move-element-command.js +57 -0
  15. package/src/utils/threejs/editor/commands/multi-command.js +29 -0
  16. package/src/utils/threejs/editor/commands/remove-element-command.js +46 -0
  17. package/src/utils/threejs/editor/commands/reset-original-model-style-command.js +30 -0
  18. package/src/utils/threejs/editor/commands/set-geometry-params-command.js +41 -0
  19. package/src/utils/threejs/editor/commands/set-original-model-style-command.js +56 -0
  20. package/src/utils/threejs/editor/commands/set-position-command.js +54 -0
  21. package/src/utils/threejs/editor/commands/set-rotation-command.js +53 -0
  22. package/src/utils/threejs/editor/commands/set-scale-command.js +54 -0
  23. package/src/utils/threejs/editor/commands/set-value-command.js +107 -0
  24. package/src/utils/threejs/editor/constants.js +107 -0
  25. package/src/utils/threejs/editor/element-factory.js +163 -0
  26. package/src/utils/threejs/editor/event-bus.js +34 -0
  27. package/src/utils/threejs/editor/history.js +80 -0
  28. package/src/utils/threejs/editor/scene-command-service.js +1529 -0
  29. package/src/utils/threejs/editor/scene-event-bridge.js +32 -0
  30. package/src/utils/threejs/editor/scene-helpers.js +415 -0
@@ -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
  }
@@ -3411,24 +4408,56 @@ export default {
3411
4408
  }
3412
4409
  }
3413
4410
  },
4411
+ expandLocateBoxByObject(box3, object) {
4412
+ if (!box3 || !object) return;
4413
+ if (object.isGroup) {
4414
+ object.traverseVisible(child => {
4415
+ if (child.isMesh || child.isLine || child.isPoints) {
4416
+ box3.expandByObject(child);
4417
+ }
4418
+ });
4419
+ return;
4420
+ }
4421
+ box3.expandByObject(object);
4422
+ },
4423
+ locateObjectByBox(object, options = {}) {
4424
+ if (!object) return false;
4425
+ const box3 = new this.THREE.Box3();
4426
+ this.expandLocateBoxByObject(box3, object);
4427
+ if (box3.isEmpty()) return false;
4428
+ const center = box3.getCenter(new this.THREE.Vector3());
4429
+ const size = box3.getSize(new this.THREE.Vector3());
4430
+ this.locateByCenterBox(center, size, options);
4431
+ return true;
4432
+ },
3414
4433
  // 定位到模型
3415
- // name 模型名称 可以是数组
4434
+ // name 模型名称或运行态 uuid,可以是数组
3416
4435
  locateModel(name) {
3417
- if (!this.scene) return;
4436
+ if (!this.scene) return false;
3418
4437
  if (Array.isArray(name)) {
3419
4438
  const box3 = new this.THREE.Box3();
3420
4439
  name.forEach(n => {
4440
+ const objectByUuid = this.getObjectByUuid(n);
4441
+ if (objectByUuid) {
4442
+ this.expandLocateBoxByObject(box3, objectByUuid);
4443
+ return;
4444
+ }
3421
4445
  const arr = this.getObjectByName(n);
3422
4446
  arr.forEach(o => {
3423
- box3.expandByObject(o);
4447
+ this.expandLocateBoxByObject(box3, o);
3424
4448
  });
3425
4449
  });
3426
4450
  if (!box3.isEmpty()) {
3427
4451
  const center = box3.getCenter(new this.THREE.Vector3());
3428
4452
  const size = box3.getSize(new this.THREE.Vector3());
3429
4453
  this.locateByCenterBox(center, size, { viewAll: true });
4454
+ return true;
3430
4455
  }
3431
- return;
4456
+ return false;
4457
+ }
4458
+ const objectByUuid = this.getObjectByUuid(name);
4459
+ if (objectByUuid && this.locateObjectByBox(objectByUuid)) {
4460
+ return true;
3432
4461
  }
3433
4462
  let obj = this.getObjectByName(name)[0];
3434
4463
  if (obj) {
@@ -3440,7 +4469,9 @@ export default {
3440
4469
  let size = this.getSize(obj);
3441
4470
  this.locateByCenterBox(center, size);
3442
4471
  }
4472
+ return true;
3443
4473
  }
4474
+ return false;
3444
4475
  },
3445
4476
  // 根据自定义参数修改模型
3446
4477
  /*
@@ -3569,6 +4600,11 @@ export default {
3569
4600
  });
3570
4601
  return object;
3571
4602
  },
4603
+ // 通过 uuid 获取实体对象
4604
+ getObjectByUuid(uuid) {
4605
+ if (!this.scene || !uuid) return null;
4606
+ return this.scene.getObjectByProperty('uuid', uuid) || null;
4607
+ },
3572
4608
  // 通过id获取实体对象, 返回查找到的对象
3573
4609
  getObjectById(id) {
3574
4610
  if (!this.scene) return null;
@@ -3654,6 +4690,7 @@ export default {
3654
4690
  removeAll() {
3655
4691
  return new Promise(resolve => {
3656
4692
  if (this.scene) {
4693
+ this.removeAllCustomElements();
3657
4694
  this.removeTraverse();
3658
4695
  this.removeModelByDocumentId();
3659
4696
  resolve();
@@ -3693,6 +4730,16 @@ export default {
3693
4730
  // 销毁场景 释放内存
3694
4731
  destroyScene() {
3695
4732
  cancelAnimationFrame(this.animateId);
4733
+ this.disposeTransformControls();
4734
+
4735
+ if (this.sceneCommandEventDisposers.length > 0) {
4736
+ this.sceneCommandEventDisposers.forEach(dispose => dispose && dispose());
4737
+ this.sceneCommandEventDisposers = [];
4738
+ }
4739
+ if (this.sceneCommandService) {
4740
+ this.sceneCommandService.destroy();
4741
+ this.sceneCommandService = null;
4742
+ }
3696
4743
 
3697
4744
  if (this.noObserver && this.noObserver.outlineInstanceProxyMap) {
3698
4745
  this.noObserver.outlineInstanceProxyMap.forEach(proxy => {
@@ -3755,12 +4802,7 @@ export default {
3755
4802
  }
3756
4803
 
3757
4804
  // 移除鼠标点击/按下事件监听器
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
- }
4805
+ this.unbindScenePointerEvents();
3764
4806
 
3765
4807
  // 取消 pointer lock 并移除相关键盘事件
3766
4808
  if (this.pointControls) {
@@ -4002,13 +5044,46 @@ export default {
4002
5044
  });
4003
5045
  return clippingPlanesConstant;
4004
5046
  },
5047
+ isGlobalClippingExcludedNode(object) {
5048
+ if (!object) return true;
5049
+ const userData = object.userData || {};
5050
+ const nodeType = userData.nodeType || userData.rootType || '';
5051
+ if (nodeType === 'custom-root') return true;
5052
+ if (userData.transformControlHelper === true) return true;
5053
+ if (object.isCamera || object.isLight) return true;
5054
+ if (typeof object.type === 'string' && /Helper$/i.test(object.type)) return true;
5055
+ return object.type === 'CSS2DObject' || object.type === 'TransformControlsRoot';
5056
+ },
5057
+ getGlobalClippingRoots() {
5058
+ if (this.modelGroup && this.modelGroup.children && this.modelGroup.children.length) {
5059
+ return [this.modelGroup];
5060
+ }
5061
+ if (!this.scene || !this.scene.children || this.scene.children.length === 0) return [];
5062
+ return this.scene.children.filter(child => !this.isGlobalClippingExcludedNode(child));
5063
+ },
5064
+ getGlobalClippingBox() {
5065
+ if (!this.scene) return null;
5066
+ const roots = this.getGlobalClippingRoots();
5067
+ if (roots.length === 0) return null;
5068
+ const box3 = new this.THREE.Box3();
5069
+ roots.forEach(root => {
5070
+ box3.expandByObject(root);
5071
+ });
5072
+ return box3.isEmpty() ? null : box3;
5073
+ },
4005
5074
  // 设置全局整体剖切
4006
5075
  /*
4007
5076
  先开启模型全局剖切模式, 会返回剖切值的最大最小值
4008
5077
  剖切值变换时, 使用
4009
5078
  */
4010
5079
  setGlobalClipping(flag = true) {
4011
- const box3 = new this.THREE.Box3().setFromObject(this.scene.children[0]);
5080
+ const box3 = this.getGlobalClippingBox();
5081
+ if (!box3) {
5082
+ return {
5083
+ min: null,
5084
+ max: null,
5085
+ };
5086
+ }
4012
5087
  let max = box3.max;
4013
5088
  let min = box3.min;
4014
5089
  const clippingPlanes = [
@@ -4280,11 +5355,11 @@ export default {
4280
5355
  } catch (e) {}
4281
5356
  // 锁定
4282
5357
  this.pointControls.addEventListener('lock', () => {
5358
+ this.detachTransformControls();
4283
5359
  this.cameraControls.enabled = false;
4284
5360
  window.addEventListener('keydown', this.onKeyDown, false);
4285
5361
  window.addEventListener('keyup', this.onKeyUp, false);
4286
- this.renderer.domElement.removeEventListener('mouseup', this.mouseClick, false);
4287
- this.renderer.domElement.removeEventListener('mousedown', this.mouseDown, false);
5362
+ this.unbindScenePointerEvents();
4288
5363
  if (typeof this._cameraChangeObserver === 'function') {
4289
5364
  this._cameraChangeObserver('firstPersonLock');
4290
5365
  }
@@ -4306,8 +5381,8 @@ export default {
4306
5381
  setTimeout(() => {
4307
5382
  window.removeEventListener('keydown', this.onKeyDown);
4308
5383
  window.removeEventListener('keyup', this.onKeyUp);
4309
- this.renderer.domElement.addEventListener('mouseup', this.mouseClick, false);
4310
- this.renderer.domElement.addEventListener('mousedown', this.mouseDown, false);
5384
+ this.bindScenePointerEvents();
5385
+ this.ensureTransformSelectionValid();
4311
5386
  // this.timeRender()
4312
5387
  }, 0);
4313
5388
  if (typeof this._cameraChangeObserver === 'function') {
@@ -5002,7 +6077,7 @@ export default {
5002
6077
  : this.batchLoadingState;
5003
6078
  // 如果已经在加载中,先停止之前的加载
5004
6079
  if (loadingState.isLoading) {
5005
- this.stopBatchLoading();
6080
+ this.stopBatchLoading('restart');
5006
6081
  }
5007
6082
 
5008
6083
  // 重置instance-parser的处理状态
@@ -5337,7 +6412,7 @@ export default {
5337
6412
  /**
5338
6413
  * 停止批量加载
5339
6414
  */
5340
- stopBatchLoading() {
6415
+ stopBatchLoading(reason = 'stop') {
5341
6416
  const loadingState = this.noObserver
5342
6417
  ? this.noObserver.batchLoadingState
5343
6418
  : this.batchLoadingState;
@@ -5345,6 +6420,9 @@ export default {
5345
6420
  cancelAnimationFrame(loadingState.animationFrameId);
5346
6421
  loadingState.animationFrameId = null;
5347
6422
  }
6423
+ if (loadingState.isLoading && typeof loadingState.options?.onCancel === 'function') {
6424
+ loadingState.options.onCancel({ reason });
6425
+ }
5348
6426
  loadingState.isLoading = false;
5349
6427
  this.batchLoadingState.isLoading = false;
5350
6428
  },
@@ -5592,6 +6670,7 @@ export default {
5592
6670
  .fl-model-containor {
5593
6671
  width: 100%;
5594
6672
  height: 100%;
6673
+ position: relative;
5595
6674
  cursor: pointer;
5596
6675
  }
5597
6676
  ::v-deep .tips-label {