@sequent-org/ifc-viewer 1.2.4-ci.54.0 → 1.2.4-ci.55.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.55.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",
|
|
@@ -59,6 +59,10 @@ export class LabelPlacementController {
|
|
|
59
59
|
open: false,
|
|
60
60
|
marker: null,
|
|
61
61
|
};
|
|
62
|
+
this._canvasMenu = {
|
|
63
|
+
open: false,
|
|
64
|
+
hit: null,
|
|
65
|
+
};
|
|
62
66
|
|
|
63
67
|
this._fly = {
|
|
64
68
|
raf: 0,
|
|
@@ -99,6 +103,11 @@ export class LabelPlacementController {
|
|
|
99
103
|
try { this._ui?.btn?.removeEventListener("click", this._onBtnClick); } catch (_) {}
|
|
100
104
|
try { this._ui?.menu?.removeEventListener("pointerdown", this._onMenuPointerDown); } catch (_) {}
|
|
101
105
|
try { this._ui?.menu?.removeEventListener("click", this._onMenuClick); } catch (_) {}
|
|
106
|
+
try { this._ui?.canvasMenu?.removeEventListener("pointerdown", this._onCanvasMenuPointerDown); } catch (_) {}
|
|
107
|
+
try { this._ui?.canvasMenu?.removeEventListener("click", this._onCanvasMenuClick); } catch (_) {}
|
|
108
|
+
try { dom?.removeEventListener("contextmenu", this._onCanvasContextMenu, { capture: true }); } catch (_) {
|
|
109
|
+
try { dom?.removeEventListener("contextmenu", this._onCanvasContextMenu); } catch (_) {}
|
|
110
|
+
}
|
|
102
111
|
|
|
103
112
|
if (this._raf) cancelAnimationFrame(this._raf);
|
|
104
113
|
this._raf = 0;
|
|
@@ -109,6 +118,7 @@ export class LabelPlacementController {
|
|
|
109
118
|
try { this._ui?.ghost?.remove?.(); } catch (_) {}
|
|
110
119
|
try { this._ui?.btn?.remove?.(); } catch (_) {}
|
|
111
120
|
try { this._ui?.menu?.remove?.(); } catch (_) {}
|
|
121
|
+
try { this._ui?.canvasMenu?.remove?.(); } catch (_) {}
|
|
112
122
|
}
|
|
113
123
|
|
|
114
124
|
startPlacement() {
|
|
@@ -560,7 +570,20 @@ export class LabelPlacementController {
|
|
|
560
570
|
menu.appendChild(menuMove);
|
|
561
571
|
menu.appendChild(menuDelete);
|
|
562
572
|
|
|
563
|
-
|
|
573
|
+
const canvasMenu = document.createElement("div");
|
|
574
|
+
canvasMenu.className = "ifc-label-menu";
|
|
575
|
+
canvasMenu.style.display = "none";
|
|
576
|
+
canvasMenu.setAttribute("role", "menu");
|
|
577
|
+
|
|
578
|
+
const menuAdd = document.createElement("button");
|
|
579
|
+
menuAdd.type = "button";
|
|
580
|
+
menuAdd.className = "ifc-label-menu-item";
|
|
581
|
+
menuAdd.textContent = "Добавить метку";
|
|
582
|
+
menuAdd.setAttribute("data-action", "add");
|
|
583
|
+
|
|
584
|
+
canvasMenu.appendChild(menuAdd);
|
|
585
|
+
|
|
586
|
+
return { btn, ghost, dot, num, menu, canvasMenu };
|
|
564
587
|
}
|
|
565
588
|
|
|
566
589
|
#attachUi() {
|
|
@@ -568,6 +591,7 @@ export class LabelPlacementController {
|
|
|
568
591
|
this.container.appendChild(this._ui.btn);
|
|
569
592
|
this.container.appendChild(this._ui.ghost);
|
|
570
593
|
this.container.appendChild(this._ui.menu);
|
|
594
|
+
this.container.appendChild(this._ui.canvasMenu);
|
|
571
595
|
}
|
|
572
596
|
|
|
573
597
|
#bindEvents() {
|
|
@@ -600,6 +624,27 @@ export class LabelPlacementController {
|
|
|
600
624
|
};
|
|
601
625
|
this._ui.menu.addEventListener("click", this._onMenuClick);
|
|
602
626
|
|
|
627
|
+
this._onCanvasMenuPointerDown = (e) => {
|
|
628
|
+
// Не даём клику меню попасть в canvas/OrbitControls
|
|
629
|
+
try { e.preventDefault(); } catch (_) {}
|
|
630
|
+
try { e.stopPropagation(); } catch (_) {}
|
|
631
|
+
try { e.stopImmediatePropagation?.(); } catch (_) {}
|
|
632
|
+
};
|
|
633
|
+
this._ui.canvasMenu.addEventListener("pointerdown", this._onCanvasMenuPointerDown, { passive: false });
|
|
634
|
+
|
|
635
|
+
this._onCanvasMenuClick = (e) => {
|
|
636
|
+
const target = e.target;
|
|
637
|
+
const action = target?.getAttribute?.("data-action");
|
|
638
|
+
if (action !== "add") return;
|
|
639
|
+
try { e.preventDefault(); } catch (_) {}
|
|
640
|
+
try { e.stopPropagation(); } catch (_) {}
|
|
641
|
+
|
|
642
|
+
const hit = this._canvasMenu?.hit || null;
|
|
643
|
+
if (hit) this.#createMarkerAtHit(hit);
|
|
644
|
+
this.#closeCanvasMenu();
|
|
645
|
+
};
|
|
646
|
+
this._ui.canvasMenu.addEventListener("click", this._onCanvasMenuClick);
|
|
647
|
+
|
|
603
648
|
this._onKeyDown = (e) => {
|
|
604
649
|
if (this._placing) {
|
|
605
650
|
if (e.key === "Escape") this.cancelPlacement();
|
|
@@ -634,10 +679,13 @@ export class LabelPlacementController {
|
|
|
634
679
|
window.addEventListener("keydown", this._onKeyDown);
|
|
635
680
|
|
|
636
681
|
this._onWindowPointerDown = (e) => {
|
|
637
|
-
if (!this._contextMenu.open) return;
|
|
682
|
+
if (!this._contextMenu.open && !this._canvasMenu.open) return;
|
|
638
683
|
const menu = this._ui?.menu;
|
|
684
|
+
const canvasMenu = this._ui?.canvasMenu;
|
|
639
685
|
if (menu && menu.contains(e.target)) return;
|
|
686
|
+
if (canvasMenu && canvasMenu.contains(e.target)) return;
|
|
640
687
|
this.#closeContextMenu();
|
|
688
|
+
this.#closeCanvasMenu();
|
|
641
689
|
};
|
|
642
690
|
window.addEventListener("pointerdown", this._onWindowPointerDown, true);
|
|
643
691
|
|
|
@@ -692,6 +740,25 @@ export class LabelPlacementController {
|
|
|
692
740
|
this.cancelPlacement(); // по ТЗ: “вторым кликом устанавливаем”
|
|
693
741
|
};
|
|
694
742
|
dom.addEventListener("pointerdown", this._onPointerDownCapture, { capture: true, passive: false });
|
|
743
|
+
|
|
744
|
+
this._onCanvasContextMenu = (e) => {
|
|
745
|
+
// Контекстное меню добавления метки по ПКМ на модели (если нет метки под курсором).
|
|
746
|
+
try { e.preventDefault(); } catch (_) {}
|
|
747
|
+
try { e.stopPropagation(); } catch (_) {}
|
|
748
|
+
try { e.stopImmediatePropagation?.(); } catch (_) {}
|
|
749
|
+
try { this.cancelPlacement(); } catch (_) {}
|
|
750
|
+
|
|
751
|
+
const el = document.elementFromPoint?.(e.clientX, e.clientY);
|
|
752
|
+
if (el && el.closest?.(".ifc-label-marker")) return;
|
|
753
|
+
if (el && el.closest?.(".ifc-label-menu")) return;
|
|
754
|
+
|
|
755
|
+
const hit = this.#pickModelPoint(e.clientX, e.clientY);
|
|
756
|
+
if (!hit) return;
|
|
757
|
+
|
|
758
|
+
this.#closeContextMenu();
|
|
759
|
+
this.#openCanvasMenu(hit, e.clientX, e.clientY);
|
|
760
|
+
};
|
|
761
|
+
dom.addEventListener("contextmenu", this._onCanvasContextMenu, { capture: true, passive: false });
|
|
695
762
|
}
|
|
696
763
|
|
|
697
764
|
#setGhostVisible(visible) {
|
|
@@ -892,6 +959,46 @@ export class LabelPlacementController {
|
|
|
892
959
|
menu.style.display = "none";
|
|
893
960
|
}
|
|
894
961
|
|
|
962
|
+
#openCanvasMenu(hit, clientX, clientY) {
|
|
963
|
+
const menu = this._ui?.canvasMenu;
|
|
964
|
+
if (!menu || !hit) return;
|
|
965
|
+
|
|
966
|
+
this.#closeCanvasMenu();
|
|
967
|
+
|
|
968
|
+
this._canvasMenu.open = true;
|
|
969
|
+
this._canvasMenu.hit = hit;
|
|
970
|
+
|
|
971
|
+
if (!this._containerOffsetValid) this.#refreshContainerOffset();
|
|
972
|
+
const x = clientX - this._containerOffset.left;
|
|
973
|
+
const y = clientY - this._containerOffset.top;
|
|
974
|
+
|
|
975
|
+
menu.style.display = "block";
|
|
976
|
+
menu.style.left = "0px";
|
|
977
|
+
menu.style.top = "0px";
|
|
978
|
+
menu.style.transform = `translate3d(${x}px, ${y}px, 0) translate(8px, 8px)`;
|
|
979
|
+
|
|
980
|
+
try {
|
|
981
|
+
const rect = this.container?.getBoundingClientRect?.();
|
|
982
|
+
const mw = menu.offsetWidth || 0;
|
|
983
|
+
const mh = menu.offsetHeight || 0;
|
|
984
|
+
if (rect && mw && mh) {
|
|
985
|
+
let px = x + 8;
|
|
986
|
+
let py = y + 8;
|
|
987
|
+
if (px + mw > rect.width) px = Math.max(0, rect.width - mw - 4);
|
|
988
|
+
if (py + mh > rect.height) py = Math.max(0, rect.height - mh - 4);
|
|
989
|
+
menu.style.transform = `translate3d(${px}px, ${py}px, 0)`;
|
|
990
|
+
}
|
|
991
|
+
} catch (_) {}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
#closeCanvasMenu() {
|
|
995
|
+
const menu = this._ui?.canvasMenu;
|
|
996
|
+
if (!menu) return;
|
|
997
|
+
this._canvasMenu.open = false;
|
|
998
|
+
this._canvasMenu.hit = null;
|
|
999
|
+
menu.style.display = "none";
|
|
1000
|
+
}
|
|
1001
|
+
|
|
895
1002
|
#createMarkerFromData(data, emitPlacedEvent) {
|
|
896
1003
|
if (!data) return null;
|
|
897
1004
|
const localPoint = data.localPoint || {};
|
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 (фиксированная ось после ПКМ)
|