@sequent-org/ifc-viewer 1.2.4-ci.54.0 → 1.2.4-ci.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sequent-org/ifc-viewer",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "1.2.4-ci.
|
|
4
|
+
"version": "1.2.4-ci.56.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "IFC 3D model viewer component for web applications - fully self-contained with local IFCLoader",
|
|
7
7
|
"main": "src/index.js",
|
|
@@ -50,6 +50,21 @@ export class LabelPlacementController {
|
|
|
50
50
|
this._ghostPos = { x: 0, y: 0 };
|
|
51
51
|
this._raf = 0;
|
|
52
52
|
|
|
53
|
+
this._labelDrag = {
|
|
54
|
+
active: false,
|
|
55
|
+
moved: false,
|
|
56
|
+
pointerId: null,
|
|
57
|
+
id: null,
|
|
58
|
+
start: { x: 0, y: 0 },
|
|
59
|
+
last: { x: 0, y: 0 },
|
|
60
|
+
ghostPos: { x: 0, y: 0 },
|
|
61
|
+
clickMarker: null,
|
|
62
|
+
prevControlsEnabled: null,
|
|
63
|
+
threshold: 4,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
this._labelDragDropSelector = deps?.labelDragDropSelector || null;
|
|
67
|
+
|
|
53
68
|
this._controlsWasEnabled = null;
|
|
54
69
|
|
|
55
70
|
this._containerOffset = { left: 0, top: 0 };
|
|
@@ -59,6 +74,10 @@ export class LabelPlacementController {
|
|
|
59
74
|
open: false,
|
|
60
75
|
marker: null,
|
|
61
76
|
};
|
|
77
|
+
this._canvasMenu = {
|
|
78
|
+
open: false,
|
|
79
|
+
hit: null,
|
|
80
|
+
};
|
|
62
81
|
|
|
63
82
|
this._fly = {
|
|
64
83
|
raf: 0,
|
|
@@ -92,13 +111,31 @@ export class LabelPlacementController {
|
|
|
92
111
|
try { dom?.removeEventListener("pointerdown", this._onPointerDownCapture, { capture: true }); } catch (_) {
|
|
93
112
|
try { dom?.removeEventListener("pointerdown", this._onPointerDownCapture); } catch (_) {}
|
|
94
113
|
}
|
|
114
|
+
try { dom?.removeEventListener("pointerdown", this._onDbgDomPointerDown); } catch (_) {}
|
|
115
|
+
try { dom?.removeEventListener("pointermove", this._onDbgDomPointerMove); } catch (_) {}
|
|
116
|
+
try { dom?.removeEventListener("pointerup", this._onDbgDomPointerUp); } catch (_) {}
|
|
95
117
|
try { window.removeEventListener("keydown", this._onKeyDown); } catch (_) {}
|
|
96
118
|
try { window.removeEventListener("resize", this._onWindowResize); } catch (_) {}
|
|
97
119
|
try { window.removeEventListener("scroll", this._onWindowScroll, true); } catch (_) {}
|
|
98
120
|
try { window.removeEventListener("pointerdown", this._onWindowPointerDown, true); } catch (_) {}
|
|
121
|
+
try { document.removeEventListener("pointerdown", this._onDbgDocPointerDown); } catch (_) {}
|
|
122
|
+
try { document.removeEventListener("pointermove", this._onDbgDocPointerMove); } catch (_) {}
|
|
123
|
+
try { document.removeEventListener("pointerup", this._onDbgDocPointerUp); } catch (_) {}
|
|
124
|
+
try { document.removeEventListener("dragstart", this._onDbgDocDragStart); } catch (_) {}
|
|
125
|
+
try { document.removeEventListener("dragend", this._onDbgDocDragEnd); } catch (_) {}
|
|
126
|
+
try { document.removeEventListener("dragover", this._onDbgDocDragOver); } catch (_) {}
|
|
127
|
+
try { document.removeEventListener("drop", this._onDbgDocDrop); } catch (_) {}
|
|
128
|
+
try { document.removeEventListener("pointermove", this._onLabelDragPointerMove); } catch (_) {}
|
|
129
|
+
try { document.removeEventListener("pointerup", this._onLabelDragPointerUp); } catch (_) {}
|
|
130
|
+
try { document.removeEventListener("pointercancel", this._onLabelDragPointerCancel); } catch (_) {}
|
|
99
131
|
try { this._ui?.btn?.removeEventListener("click", this._onBtnClick); } catch (_) {}
|
|
100
132
|
try { this._ui?.menu?.removeEventListener("pointerdown", this._onMenuPointerDown); } catch (_) {}
|
|
101
133
|
try { this._ui?.menu?.removeEventListener("click", this._onMenuClick); } catch (_) {}
|
|
134
|
+
try { this._ui?.canvasMenu?.removeEventListener("pointerdown", this._onCanvasMenuPointerDown); } catch (_) {}
|
|
135
|
+
try { this._ui?.canvasMenu?.removeEventListener("click", this._onCanvasMenuClick); } catch (_) {}
|
|
136
|
+
try { dom?.removeEventListener("contextmenu", this._onCanvasContextMenu, { capture: true }); } catch (_) {
|
|
137
|
+
try { dom?.removeEventListener("contextmenu", this._onCanvasContextMenu); } catch (_) {}
|
|
138
|
+
}
|
|
102
139
|
|
|
103
140
|
if (this._raf) cancelAnimationFrame(this._raf);
|
|
104
141
|
this._raf = 0;
|
|
@@ -107,8 +144,10 @@ export class LabelPlacementController {
|
|
|
107
144
|
this._markers.length = 0;
|
|
108
145
|
|
|
109
146
|
try { this._ui?.ghost?.remove?.(); } catch (_) {}
|
|
147
|
+
try { this._ui?.dragGhost?.remove?.(); } catch (_) {}
|
|
110
148
|
try { this._ui?.btn?.remove?.(); } catch (_) {}
|
|
111
149
|
try { this._ui?.menu?.remove?.(); } catch (_) {}
|
|
150
|
+
try { this._ui?.canvasMenu?.remove?.(); } catch (_) {}
|
|
112
151
|
}
|
|
113
152
|
|
|
114
153
|
startPlacement() {
|
|
@@ -533,6 +572,20 @@ export class LabelPlacementController {
|
|
|
533
572
|
ghost.appendChild(dot);
|
|
534
573
|
ghost.appendChild(num);
|
|
535
574
|
|
|
575
|
+
const dragGhost = document.createElement("div");
|
|
576
|
+
dragGhost.className = "ifc-label-ghost ifc-label-ghost--drag";
|
|
577
|
+
dragGhost.setAttribute("aria-hidden", "true");
|
|
578
|
+
dragGhost.style.display = "none";
|
|
579
|
+
dragGhost.style.left = "0px";
|
|
580
|
+
dragGhost.style.top = "0px";
|
|
581
|
+
|
|
582
|
+
const dragDot = document.createElement("div");
|
|
583
|
+
dragDot.className = "ifc-label-dot";
|
|
584
|
+
const dragNum = document.createElement("div");
|
|
585
|
+
dragNum.className = "ifc-label-num";
|
|
586
|
+
dragGhost.appendChild(dragDot);
|
|
587
|
+
dragGhost.appendChild(dragNum);
|
|
588
|
+
|
|
536
589
|
const menu = document.createElement("div");
|
|
537
590
|
menu.className = "ifc-label-menu";
|
|
538
591
|
menu.style.display = "none";
|
|
@@ -560,14 +613,29 @@ export class LabelPlacementController {
|
|
|
560
613
|
menu.appendChild(menuMove);
|
|
561
614
|
menu.appendChild(menuDelete);
|
|
562
615
|
|
|
563
|
-
|
|
616
|
+
const canvasMenu = document.createElement("div");
|
|
617
|
+
canvasMenu.className = "ifc-label-menu";
|
|
618
|
+
canvasMenu.style.display = "none";
|
|
619
|
+
canvasMenu.setAttribute("role", "menu");
|
|
620
|
+
|
|
621
|
+
const menuAdd = document.createElement("button");
|
|
622
|
+
menuAdd.type = "button";
|
|
623
|
+
menuAdd.className = "ifc-label-menu-item";
|
|
624
|
+
menuAdd.textContent = "Добавить метку";
|
|
625
|
+
menuAdd.setAttribute("data-action", "add");
|
|
626
|
+
|
|
627
|
+
canvasMenu.appendChild(menuAdd);
|
|
628
|
+
|
|
629
|
+
return { btn, ghost, dot, num, dragGhost, dragNum, menu, canvasMenu };
|
|
564
630
|
}
|
|
565
631
|
|
|
566
632
|
#attachUi() {
|
|
567
633
|
// Важно: container должен быть position:relative (в index.html уже так).
|
|
568
634
|
this.container.appendChild(this._ui.btn);
|
|
569
635
|
this.container.appendChild(this._ui.ghost);
|
|
636
|
+
this.container.appendChild(this._ui.dragGhost);
|
|
570
637
|
this.container.appendChild(this._ui.menu);
|
|
638
|
+
this.container.appendChild(this._ui.canvasMenu);
|
|
571
639
|
}
|
|
572
640
|
|
|
573
641
|
#bindEvents() {
|
|
@@ -600,6 +668,27 @@ export class LabelPlacementController {
|
|
|
600
668
|
};
|
|
601
669
|
this._ui.menu.addEventListener("click", this._onMenuClick);
|
|
602
670
|
|
|
671
|
+
this._onCanvasMenuPointerDown = (e) => {
|
|
672
|
+
// Не даём клику меню попасть в canvas/OrbitControls
|
|
673
|
+
try { e.preventDefault(); } catch (_) {}
|
|
674
|
+
try { e.stopPropagation(); } catch (_) {}
|
|
675
|
+
try { e.stopImmediatePropagation?.(); } catch (_) {}
|
|
676
|
+
};
|
|
677
|
+
this._ui.canvasMenu.addEventListener("pointerdown", this._onCanvasMenuPointerDown, { passive: false });
|
|
678
|
+
|
|
679
|
+
this._onCanvasMenuClick = (e) => {
|
|
680
|
+
const target = e.target;
|
|
681
|
+
const action = target?.getAttribute?.("data-action");
|
|
682
|
+
if (action !== "add") return;
|
|
683
|
+
try { e.preventDefault(); } catch (_) {}
|
|
684
|
+
try { e.stopPropagation(); } catch (_) {}
|
|
685
|
+
|
|
686
|
+
const hit = this._canvasMenu?.hit || null;
|
|
687
|
+
if (hit) this.#createMarkerAtHit(hit);
|
|
688
|
+
this.#closeCanvasMenu();
|
|
689
|
+
};
|
|
690
|
+
this._ui.canvasMenu.addEventListener("click", this._onCanvasMenuClick);
|
|
691
|
+
|
|
603
692
|
this._onKeyDown = (e) => {
|
|
604
693
|
if (this._placing) {
|
|
605
694
|
if (e.key === "Escape") this.cancelPlacement();
|
|
@@ -634,10 +723,13 @@ export class LabelPlacementController {
|
|
|
634
723
|
window.addEventListener("keydown", this._onKeyDown);
|
|
635
724
|
|
|
636
725
|
this._onWindowPointerDown = (e) => {
|
|
637
|
-
if (!this._contextMenu.open) return;
|
|
726
|
+
if (!this._contextMenu.open && !this._canvasMenu.open) return;
|
|
638
727
|
const menu = this._ui?.menu;
|
|
728
|
+
const canvasMenu = this._ui?.canvasMenu;
|
|
639
729
|
if (menu && menu.contains(e.target)) return;
|
|
730
|
+
if (canvasMenu && canvasMenu.contains(e.target)) return;
|
|
640
731
|
this.#closeContextMenu();
|
|
732
|
+
this.#closeCanvasMenu();
|
|
641
733
|
};
|
|
642
734
|
window.addEventListener("pointerdown", this._onWindowPointerDown, true);
|
|
643
735
|
|
|
@@ -657,9 +749,64 @@ export class LabelPlacementController {
|
|
|
657
749
|
// scroll слушаем в capture, чтобы ловить скролл вложенных контейнеров
|
|
658
750
|
window.addEventListener("scroll", this._onWindowScroll, true);
|
|
659
751
|
|
|
752
|
+
const logDnDEvent = (scope, e) => {
|
|
753
|
+
const target = e?.target || null;
|
|
754
|
+
const closestMarker = (target && typeof target.closest === "function")
|
|
755
|
+
? target.closest(".ifc-label-marker")
|
|
756
|
+
: null;
|
|
757
|
+
if (!closestMarker) return;
|
|
758
|
+
const payload = {
|
|
759
|
+
type: e?.type,
|
|
760
|
+
target,
|
|
761
|
+
closestMarker,
|
|
762
|
+
button: e?.button,
|
|
763
|
+
buttons: e?.buttons,
|
|
764
|
+
clientX: e?.clientX,
|
|
765
|
+
clientY: e?.clientY,
|
|
766
|
+
defaultPrevented: !!e?.defaultPrevented,
|
|
767
|
+
};
|
|
768
|
+
try {
|
|
769
|
+
this.logger?.log?.("[LabelDnD]", scope, payload);
|
|
770
|
+
} catch (_) {
|
|
771
|
+
try { console.log("[LabelDnD]", scope, payload); } catch (_) {}
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
this._onDbgDocPointerDown = (e) => logDnDEvent("document", e);
|
|
776
|
+
this._onDbgDocPointerMove = (e) => logDnDEvent("document", e);
|
|
777
|
+
this._onDbgDocPointerUp = (e) => logDnDEvent("document", e);
|
|
778
|
+
this._onDbgDocDragStart = (e) => logDnDEvent("document", e);
|
|
779
|
+
this._onDbgDocDragEnd = (e) => logDnDEvent("document", e);
|
|
780
|
+
this._onDbgDocDragOver = (e) => logDnDEvent("document", e);
|
|
781
|
+
this._onDbgDocDrop = (e) => logDnDEvent("document", e);
|
|
782
|
+
|
|
783
|
+
document.addEventListener("pointerdown", this._onDbgDocPointerDown);
|
|
784
|
+
document.addEventListener("pointermove", this._onDbgDocPointerMove);
|
|
785
|
+
document.addEventListener("pointerup", this._onDbgDocPointerUp);
|
|
786
|
+
document.addEventListener("dragstart", this._onDbgDocDragStart);
|
|
787
|
+
document.addEventListener("dragend", this._onDbgDocDragEnd);
|
|
788
|
+
document.addEventListener("dragover", this._onDbgDocDragOver);
|
|
789
|
+
document.addEventListener("drop", this._onDbgDocDrop);
|
|
790
|
+
|
|
791
|
+
this._onLabelDragPointerMove = (e) => this.#updateLabelDrag(e);
|
|
792
|
+
this._onLabelDragPointerUp = (e) => this.#finishLabelDrag(e, "pointerup");
|
|
793
|
+
this._onLabelDragPointerCancel = (e) => this.#finishLabelDrag(e, "pointercancel");
|
|
794
|
+
|
|
795
|
+
document.addEventListener("pointermove", this._onLabelDragPointerMove, { passive: true });
|
|
796
|
+
document.addEventListener("pointerup", this._onLabelDragPointerUp, { passive: true });
|
|
797
|
+
document.addEventListener("pointercancel", this._onLabelDragPointerCancel, { passive: true });
|
|
798
|
+
|
|
660
799
|
const dom = this.viewer?.renderer?.domElement;
|
|
661
800
|
if (!dom) return;
|
|
662
801
|
|
|
802
|
+
this._onDbgDomPointerDown = (e) => logDnDEvent("ifc3dHost", e);
|
|
803
|
+
this._onDbgDomPointerMove = (e) => logDnDEvent("ifc3dHost", e);
|
|
804
|
+
this._onDbgDomPointerUp = (e) => logDnDEvent("ifc3dHost", e);
|
|
805
|
+
|
|
806
|
+
dom.addEventListener("pointerdown", this._onDbgDomPointerDown, { passive: true });
|
|
807
|
+
dom.addEventListener("pointermove", this._onDbgDomPointerMove, { passive: true });
|
|
808
|
+
dom.addEventListener("pointerup", this._onDbgDomPointerUp, { passive: true });
|
|
809
|
+
|
|
663
810
|
this._onPointerMove = (e) => {
|
|
664
811
|
if (!this._placing) return;
|
|
665
812
|
this.#updateGhostFromClient(e.clientX, e.clientY);
|
|
@@ -692,6 +839,25 @@ export class LabelPlacementController {
|
|
|
692
839
|
this.cancelPlacement(); // по ТЗ: “вторым кликом устанавливаем”
|
|
693
840
|
};
|
|
694
841
|
dom.addEventListener("pointerdown", this._onPointerDownCapture, { capture: true, passive: false });
|
|
842
|
+
|
|
843
|
+
this._onCanvasContextMenu = (e) => {
|
|
844
|
+
// Контекстное меню добавления метки по ПКМ на модели (если нет метки под курсором).
|
|
845
|
+
try { e.preventDefault(); } catch (_) {}
|
|
846
|
+
try { e.stopPropagation(); } catch (_) {}
|
|
847
|
+
try { e.stopImmediatePropagation?.(); } catch (_) {}
|
|
848
|
+
try { this.cancelPlacement(); } catch (_) {}
|
|
849
|
+
|
|
850
|
+
const el = document.elementFromPoint?.(e.clientX, e.clientY);
|
|
851
|
+
if (el && el.closest?.(".ifc-label-marker")) return;
|
|
852
|
+
if (el && el.closest?.(".ifc-label-menu")) return;
|
|
853
|
+
|
|
854
|
+
const hit = this.#pickModelPoint(e.clientX, e.clientY);
|
|
855
|
+
if (!hit) return;
|
|
856
|
+
|
|
857
|
+
this.#closeContextMenu();
|
|
858
|
+
this.#openCanvasMenu(hit, e.clientX, e.clientY);
|
|
859
|
+
};
|
|
860
|
+
dom.addEventListener("contextmenu", this._onCanvasContextMenu, { capture: true, passive: false });
|
|
695
861
|
}
|
|
696
862
|
|
|
697
863
|
#setGhostVisible(visible) {
|
|
@@ -737,6 +903,117 @@ export class LabelPlacementController {
|
|
|
737
903
|
this.#updateGhostFromClient(this._lastPointer.x, this._lastPointer.y);
|
|
738
904
|
}
|
|
739
905
|
|
|
906
|
+
#setDragGhostVisible(visible) {
|
|
907
|
+
if (!this._ui?.dragGhost) return;
|
|
908
|
+
this._ui.dragGhost.style.display = visible ? "block" : "none";
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
#applyDragGhostTransform() {
|
|
912
|
+
const g = this._ui?.dragGhost;
|
|
913
|
+
if (!g) return;
|
|
914
|
+
g.style.transform = `translate3d(${this._labelDrag.ghostPos.x}px, ${this._labelDrag.ghostPos.y}px, 0) translate(-50%, -50%)`;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
#updateDragGhostFromClient(clientX, clientY) {
|
|
918
|
+
this._labelDrag.last = { x: clientX, y: clientY };
|
|
919
|
+
if (!this._containerOffsetValid) this.#refreshContainerOffset();
|
|
920
|
+
const x = (clientX - this._containerOffset.left);
|
|
921
|
+
const y = (clientY - this._containerOffset.top);
|
|
922
|
+
this._labelDrag.ghostPos.x = x;
|
|
923
|
+
this._labelDrag.ghostPos.y = y;
|
|
924
|
+
this.#applyDragGhostTransform();
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
#beginLabelDrag(marker, e) {
|
|
928
|
+
if (!marker || !e || e.button !== 0) return;
|
|
929
|
+
this._labelDrag.active = true;
|
|
930
|
+
this._labelDrag.moved = false;
|
|
931
|
+
this._labelDrag.pointerId = e.pointerId ?? null;
|
|
932
|
+
this._labelDrag.id = marker.id;
|
|
933
|
+
this._labelDrag.start = { x: e.clientX, y: e.clientY };
|
|
934
|
+
this._labelDrag.last = { x: e.clientX, y: e.clientY };
|
|
935
|
+
this._labelDrag.clickMarker = marker;
|
|
936
|
+
|
|
937
|
+
try {
|
|
938
|
+
this._labelDrag.prevControlsEnabled = this.viewer?.controls?.enabled ?? null;
|
|
939
|
+
if (this.viewer?.controls) this.viewer.controls.enabled = false;
|
|
940
|
+
} catch (_) {}
|
|
941
|
+
|
|
942
|
+
if (this._ui?.dragNum) this._ui.dragNum.textContent = String(marker.id);
|
|
943
|
+
this.#setDragGhostVisible(false);
|
|
944
|
+
|
|
945
|
+
try { marker.el?.setPointerCapture?.(e.pointerId); } catch (_) {}
|
|
946
|
+
|
|
947
|
+
this.#dispatchLabelEvent("ifcviewer:label-drag-start", {
|
|
948
|
+
id: marker.id,
|
|
949
|
+
clientX: e.clientX,
|
|
950
|
+
clientY: e.clientY,
|
|
951
|
+
}, null);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
#updateLabelDrag(e) {
|
|
955
|
+
if (!this._labelDrag.active || !e) return;
|
|
956
|
+
if (this._labelDrag.pointerId != null && e.pointerId != null && e.pointerId !== this._labelDrag.pointerId) return;
|
|
957
|
+
|
|
958
|
+
const dx = (e.clientX - this._labelDrag.start.x);
|
|
959
|
+
const dy = (e.clientY - this._labelDrag.start.y);
|
|
960
|
+
const dist = Math.hypot(dx, dy);
|
|
961
|
+
if (!this._labelDrag.moved && dist >= this._labelDrag.threshold) {
|
|
962
|
+
this._labelDrag.moved = true;
|
|
963
|
+
this.#setDragGhostVisible(true);
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
if (this._labelDrag.moved) {
|
|
967
|
+
this.#updateDragGhostFromClient(e.clientX, e.clientY);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
#resolveDropTarget(clientX, clientY) {
|
|
972
|
+
const el = document.elementFromPoint?.(clientX, clientY) || null;
|
|
973
|
+
if (!el) return { element: null, card: null };
|
|
974
|
+
if (!this._labelDragDropSelector) return { element: el, card: null };
|
|
975
|
+
const card = el.closest?.(this._labelDragDropSelector) || null;
|
|
976
|
+
return { element: el, card };
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
#finishLabelDrag(e, reason = "pointerup") {
|
|
980
|
+
if (!this._labelDrag.active) return;
|
|
981
|
+
const marker = this._labelDrag.clickMarker;
|
|
982
|
+
const moved = !!this._labelDrag.moved;
|
|
983
|
+
const clientX = e?.clientX ?? this._labelDrag.last.x;
|
|
984
|
+
const clientY = e?.clientY ?? this._labelDrag.last.y;
|
|
985
|
+
|
|
986
|
+
this._labelDrag.active = false;
|
|
987
|
+
this._labelDrag.moved = false;
|
|
988
|
+
this._labelDrag.pointerId = null;
|
|
989
|
+
this._labelDrag.id = null;
|
|
990
|
+
this._labelDrag.clickMarker = null;
|
|
991
|
+
|
|
992
|
+
this.#setDragGhostVisible(false);
|
|
993
|
+
|
|
994
|
+
try {
|
|
995
|
+
if (this.viewer?.controls && this._labelDrag.prevControlsEnabled != null) {
|
|
996
|
+
this.viewer.controls.enabled = !!this._labelDrag.prevControlsEnabled;
|
|
997
|
+
}
|
|
998
|
+
} catch (_) {}
|
|
999
|
+
this._labelDrag.prevControlsEnabled = null;
|
|
1000
|
+
|
|
1001
|
+
if (!moved) {
|
|
1002
|
+
if (marker) this.#handleMarkerClick(marker);
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
const drop = this.#resolveDropTarget(clientX, clientY);
|
|
1007
|
+
this.#dispatchLabelEvent("ifcviewer:label-drop", {
|
|
1008
|
+
id: marker?.id ?? this._labelDrag.id,
|
|
1009
|
+
clientX,
|
|
1010
|
+
clientY,
|
|
1011
|
+
dropTarget: drop.card || null,
|
|
1012
|
+
elementFromPoint: drop.element || null,
|
|
1013
|
+
reason,
|
|
1014
|
+
}, null);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
740
1017
|
#pickModelPoint(clientX, clientY) {
|
|
741
1018
|
const model = this.viewer?.activeModel;
|
|
742
1019
|
const camera = this.viewer?.camera;
|
|
@@ -833,6 +1110,18 @@ export class LabelPlacementController {
|
|
|
833
1110
|
return this._markers.find((m) => String(m?.id) === String(this._selectedId)) || null;
|
|
834
1111
|
}
|
|
835
1112
|
|
|
1113
|
+
#handleMarkerClick(marker) {
|
|
1114
|
+
if (!marker) return;
|
|
1115
|
+
this.#closeContextMenu();
|
|
1116
|
+
this.#setSelectedMarker(marker);
|
|
1117
|
+
this.#dispatchLabelEvent("ifcviewer:label-click", {
|
|
1118
|
+
id: marker.id,
|
|
1119
|
+
sceneState: marker.sceneState || null,
|
|
1120
|
+
}, "ifcviewer:card-click");
|
|
1121
|
+
// "Долеталка" камеры: быстрый старт + мягкий конец
|
|
1122
|
+
this.#animateToSceneState(marker.sceneState, 550);
|
|
1123
|
+
}
|
|
1124
|
+
|
|
836
1125
|
#buildActionPayload(marker) {
|
|
837
1126
|
if (!marker) return null;
|
|
838
1127
|
return {
|
|
@@ -892,6 +1181,46 @@ export class LabelPlacementController {
|
|
|
892
1181
|
menu.style.display = "none";
|
|
893
1182
|
}
|
|
894
1183
|
|
|
1184
|
+
#openCanvasMenu(hit, clientX, clientY) {
|
|
1185
|
+
const menu = this._ui?.canvasMenu;
|
|
1186
|
+
if (!menu || !hit) return;
|
|
1187
|
+
|
|
1188
|
+
this.#closeCanvasMenu();
|
|
1189
|
+
|
|
1190
|
+
this._canvasMenu.open = true;
|
|
1191
|
+
this._canvasMenu.hit = hit;
|
|
1192
|
+
|
|
1193
|
+
if (!this._containerOffsetValid) this.#refreshContainerOffset();
|
|
1194
|
+
const x = clientX - this._containerOffset.left;
|
|
1195
|
+
const y = clientY - this._containerOffset.top;
|
|
1196
|
+
|
|
1197
|
+
menu.style.display = "block";
|
|
1198
|
+
menu.style.left = "0px";
|
|
1199
|
+
menu.style.top = "0px";
|
|
1200
|
+
menu.style.transform = `translate3d(${x}px, ${y}px, 0) translate(8px, 8px)`;
|
|
1201
|
+
|
|
1202
|
+
try {
|
|
1203
|
+
const rect = this.container?.getBoundingClientRect?.();
|
|
1204
|
+
const mw = menu.offsetWidth || 0;
|
|
1205
|
+
const mh = menu.offsetHeight || 0;
|
|
1206
|
+
if (rect && mw && mh) {
|
|
1207
|
+
let px = x + 8;
|
|
1208
|
+
let py = y + 8;
|
|
1209
|
+
if (px + mw > rect.width) px = Math.max(0, rect.width - mw - 4);
|
|
1210
|
+
if (py + mh > rect.height) py = Math.max(0, rect.height - mh - 4);
|
|
1211
|
+
menu.style.transform = `translate3d(${px}px, ${py}px, 0)`;
|
|
1212
|
+
}
|
|
1213
|
+
} catch (_) {}
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
#closeCanvasMenu() {
|
|
1217
|
+
const menu = this._ui?.canvasMenu;
|
|
1218
|
+
if (!menu) return;
|
|
1219
|
+
this._canvasMenu.open = false;
|
|
1220
|
+
this._canvasMenu.hit = null;
|
|
1221
|
+
menu.style.display = "none";
|
|
1222
|
+
}
|
|
1223
|
+
|
|
895
1224
|
#createMarkerFromData(data, emitPlacedEvent) {
|
|
896
1225
|
if (!data) return null;
|
|
897
1226
|
const localPoint = data.localPoint || {};
|
|
@@ -912,24 +1241,78 @@ export class LabelPlacementController {
|
|
|
912
1241
|
try { e.preventDefault(); } catch (_) {}
|
|
913
1242
|
try { e.stopPropagation(); } catch (_) {}
|
|
914
1243
|
try { e.stopImmediatePropagation?.(); } catch (_) {}
|
|
1244
|
+
try {
|
|
1245
|
+
this.logger?.log?.("[LabelDnD]", "marker", {
|
|
1246
|
+
type: e?.type,
|
|
1247
|
+
target: e?.target || null,
|
|
1248
|
+
closestMarker: e?.target?.closest?.(".ifc-label-marker") || null,
|
|
1249
|
+
button: e?.button,
|
|
1250
|
+
buttons: e?.buttons,
|
|
1251
|
+
clientX: e?.clientX,
|
|
1252
|
+
clientY: e?.clientY,
|
|
1253
|
+
defaultPrevented: !!e?.defaultPrevented,
|
|
1254
|
+
});
|
|
1255
|
+
} catch (_) {}
|
|
915
1256
|
// если были в режиме постановки — выходим
|
|
916
1257
|
try { this.cancelPlacement(); } catch (_) {}
|
|
917
1258
|
|
|
918
1259
|
if (e.button === 0) {
|
|
919
1260
|
this.#closeContextMenu();
|
|
920
1261
|
this.#setSelectedMarker(marker);
|
|
921
|
-
this.#
|
|
922
|
-
id: marker.id,
|
|
923
|
-
sceneState: marker.sceneState || null,
|
|
924
|
-
}, "ifcviewer:card-click");
|
|
925
|
-
// "Долеталка" камеры: быстрый старт + мягкий конец
|
|
926
|
-
this.#animateToSceneState(marker.sceneState, 550);
|
|
1262
|
+
this.#beginLabelDrag(marker, e);
|
|
927
1263
|
}
|
|
928
1264
|
};
|
|
929
1265
|
// capture-phase, чтобы обогнать любые handlers на canvas
|
|
930
1266
|
try { marker.el.addEventListener("pointerdown", onMarkerPointerDown, { capture: true, passive: false }); } catch (_) {
|
|
931
1267
|
try { marker.el.addEventListener("pointerdown", onMarkerPointerDown); } catch (_) {}
|
|
932
1268
|
}
|
|
1269
|
+
const onMarkerPointerUp = (e) => {
|
|
1270
|
+
try {
|
|
1271
|
+
this.logger?.log?.("[LabelDnD]", "marker", {
|
|
1272
|
+
type: e?.type,
|
|
1273
|
+
target: e?.target || null,
|
|
1274
|
+
closestMarker: e?.target?.closest?.(".ifc-label-marker") || null,
|
|
1275
|
+
button: e?.button,
|
|
1276
|
+
buttons: e?.buttons,
|
|
1277
|
+
clientX: e?.clientX,
|
|
1278
|
+
clientY: e?.clientY,
|
|
1279
|
+
defaultPrevented: !!e?.defaultPrevented,
|
|
1280
|
+
});
|
|
1281
|
+
} catch (_) {}
|
|
1282
|
+
};
|
|
1283
|
+
try { marker.el.addEventListener("pointerup", onMarkerPointerUp, { capture: true, passive: true }); } catch (_) {
|
|
1284
|
+
try { marker.el.addEventListener("pointerup", onMarkerPointerUp); } catch (_) {}
|
|
1285
|
+
}
|
|
1286
|
+
const onMarkerDragStart = (e) => {
|
|
1287
|
+
try {
|
|
1288
|
+
this.logger?.log?.("[LabelDnD]", "marker", {
|
|
1289
|
+
type: e?.type,
|
|
1290
|
+
target: e?.target || null,
|
|
1291
|
+
closestMarker: e?.target?.closest?.(".ifc-label-marker") || null,
|
|
1292
|
+
button: e?.button,
|
|
1293
|
+
buttons: e?.buttons,
|
|
1294
|
+
clientX: e?.clientX,
|
|
1295
|
+
clientY: e?.clientY,
|
|
1296
|
+
defaultPrevented: !!e?.defaultPrevented,
|
|
1297
|
+
});
|
|
1298
|
+
} catch (_) {}
|
|
1299
|
+
};
|
|
1300
|
+
const onMarkerDragEnd = (e) => {
|
|
1301
|
+
try {
|
|
1302
|
+
this.logger?.log?.("[LabelDnD]", "marker", {
|
|
1303
|
+
type: e?.type,
|
|
1304
|
+
target: e?.target || null,
|
|
1305
|
+
closestMarker: e?.target?.closest?.(".ifc-label-marker") || null,
|
|
1306
|
+
button: e?.button,
|
|
1307
|
+
buttons: e?.buttons,
|
|
1308
|
+
clientX: e?.clientX,
|
|
1309
|
+
clientY: e?.clientY,
|
|
1310
|
+
defaultPrevented: !!e?.defaultPrevented,
|
|
1311
|
+
});
|
|
1312
|
+
} catch (_) {}
|
|
1313
|
+
};
|
|
1314
|
+
try { marker.el.addEventListener("dragstart", onMarkerDragStart); } catch (_) {}
|
|
1315
|
+
try { marker.el.addEventListener("dragend", onMarkerDragEnd); } catch (_) {}
|
|
933
1316
|
|
|
934
1317
|
const onMarkerContextMenu = (e) => {
|
|
935
1318
|
try { e.preventDefault(); } catch (_) {}
|
package/src/viewer/Viewer.js
CHANGED
|
@@ -213,7 +213,7 @@ export class Viewer {
|
|
|
213
213
|
|
|
214
214
|
// RMB: перемещение модели относительно "оси" (pivot), pivot остаётся на месте
|
|
215
215
|
this._rmbModelMove = {
|
|
216
|
-
enabled:
|
|
216
|
+
enabled: false,
|
|
217
217
|
debug: false,
|
|
218
218
|
controller: null,
|
|
219
219
|
pivotAnchor: null, // THREE.Vector3|null (фиксированная ось после ПКМ)
|