@sequent-org/ifc-viewer 1.2.4-ci.59.0 → 1.2.4-ci.61.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.61.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",
|
package/src/styles-local.css
CHANGED
|
@@ -111,7 +111,18 @@
|
|
|
111
111
|
.pointer-events-none { pointer-events: none; }
|
|
112
112
|
|
|
113
113
|
/* === IFC LABEL MARKERS (UI overlay) === */
|
|
114
|
+
.ifc-label-actions {
|
|
115
|
+
position: absolute;
|
|
116
|
+
left: 12px;
|
|
117
|
+
top: 12px;
|
|
118
|
+
z-index: 9999;
|
|
119
|
+
display: flex;
|
|
120
|
+
gap: 6px;
|
|
121
|
+
pointer-events: auto;
|
|
122
|
+
}
|
|
123
|
+
|
|
114
124
|
.ifc-label-add-btn,
|
|
125
|
+
.ifc-label-hide-btn,
|
|
115
126
|
.ifc-card-add-btn {
|
|
116
127
|
position: absolute;
|
|
117
128
|
left: 12px;
|
|
@@ -132,10 +143,35 @@
|
|
|
132
143
|
}
|
|
133
144
|
|
|
134
145
|
.ifc-label-add-btn:hover,
|
|
146
|
+
.ifc-label-hide-btn:hover,
|
|
135
147
|
.ifc-card-add-btn:hover {
|
|
136
148
|
background: rgba(255, 255, 255, 0.98);
|
|
137
149
|
}
|
|
138
150
|
|
|
151
|
+
.ifc-label-actions .ifc-label-add-btn,
|
|
152
|
+
.ifc-label-actions .ifc-label-hide-btn {
|
|
153
|
+
position: static;
|
|
154
|
+
left: auto;
|
|
155
|
+
top: auto;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.ifc-label-hide-btn--active {
|
|
159
|
+
background: rgba(37, 99, 235, 0.95);
|
|
160
|
+
border-color: rgba(37, 99, 235, 0.95);
|
|
161
|
+
color: #ffffff;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.ifc-label-hide-btn--active:hover {
|
|
165
|
+
background: rgba(29, 78, 216, 0.98);
|
|
166
|
+
border-color: rgba(29, 78, 216, 0.98);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.ifc-label-add-btn:disabled,
|
|
170
|
+
.ifc-label-add-btn--disabled {
|
|
171
|
+
opacity: 0.5;
|
|
172
|
+
cursor: not-allowed;
|
|
173
|
+
}
|
|
174
|
+
|
|
139
175
|
.ifc-label-ghost,
|
|
140
176
|
.ifc-label-marker,
|
|
141
177
|
.ifc-card-ghost,
|
|
@@ -232,6 +268,13 @@
|
|
|
232
268
|
background: rgba(37, 99, 235, 0.12);
|
|
233
269
|
}
|
|
234
270
|
|
|
271
|
+
.ifc-label-menu-item--disabled,
|
|
272
|
+
.ifc-label-menu-item--disabled:hover {
|
|
273
|
+
opacity: 0.5;
|
|
274
|
+
cursor: not-allowed;
|
|
275
|
+
background: transparent;
|
|
276
|
+
}
|
|
277
|
+
|
|
235
278
|
/* === NAVBAR COMPONENT === */
|
|
236
279
|
.navbar {
|
|
237
280
|
display: flex;
|
|
@@ -40,6 +40,7 @@ export class LabelPlacementController {
|
|
|
40
40
|
/** @type {LabelMarker[]} */
|
|
41
41
|
this._markers = [];
|
|
42
42
|
this._selectedId = null;
|
|
43
|
+
this._labelsHidden = false;
|
|
43
44
|
|
|
44
45
|
this._raycaster = new THREE.Raycaster();
|
|
45
46
|
this._ndc = new THREE.Vector2();
|
|
@@ -131,6 +132,7 @@ export class LabelPlacementController {
|
|
|
131
132
|
try { document.removeEventListener("pointerup", this._onLabelDragPointerUp); } catch (_) {}
|
|
132
133
|
try { document.removeEventListener("pointercancel", this._onLabelDragPointerCancel); } catch (_) {}
|
|
133
134
|
try { this._ui?.btn?.removeEventListener("click", this._onBtnClick); } catch (_) {}
|
|
135
|
+
try { this._ui?.hideBtn?.removeEventListener("click", this._onHideBtnClick); } catch (_) {}
|
|
134
136
|
try { this._ui?.menu?.removeEventListener("pointerdown", this._onMenuPointerDown); } catch (_) {}
|
|
135
137
|
try { this._ui?.menu?.removeEventListener("click", this._onMenuClick); } catch (_) {}
|
|
136
138
|
try { this._ui?.canvasMenu?.removeEventListener("pointerdown", this._onCanvasMenuPointerDown); } catch (_) {}
|
|
@@ -147,13 +149,13 @@ export class LabelPlacementController {
|
|
|
147
149
|
|
|
148
150
|
try { this._ui?.ghost?.remove?.(); } catch (_) {}
|
|
149
151
|
try { this._ui?.dragGhost?.remove?.(); } catch (_) {}
|
|
150
|
-
try { this._ui?.
|
|
152
|
+
try { this._ui?.actions?.remove?.(); } catch (_) {}
|
|
151
153
|
try { this._ui?.menu?.remove?.(); } catch (_) {}
|
|
152
154
|
try { this._ui?.canvasMenu?.remove?.(); } catch (_) {}
|
|
153
155
|
}
|
|
154
156
|
|
|
155
157
|
startPlacement() {
|
|
156
|
-
if (this._placing) return;
|
|
158
|
+
if (this._placing || this._labelsHidden) return;
|
|
157
159
|
this._placing = true;
|
|
158
160
|
|
|
159
161
|
const controls = this.viewer?.controls;
|
|
@@ -593,11 +595,24 @@ export class LabelPlacementController {
|
|
|
593
595
|
}
|
|
594
596
|
|
|
595
597
|
#createUi() {
|
|
598
|
+
const actions = document.createElement("div");
|
|
599
|
+
actions.className = "ifc-label-actions";
|
|
600
|
+
|
|
596
601
|
const btn = document.createElement("button");
|
|
597
602
|
btn.type = "button";
|
|
598
603
|
btn.className = "ifc-label-add-btn";
|
|
599
604
|
btn.textContent = "+ Добавить метку";
|
|
600
605
|
|
|
606
|
+
const hideBtn = document.createElement("button");
|
|
607
|
+
hideBtn.type = "button";
|
|
608
|
+
hideBtn.className = "ifc-label-hide-btn";
|
|
609
|
+
hideBtn.textContent = "Скрыть метки";
|
|
610
|
+
hideBtn.setAttribute("aria-pressed", "false");
|
|
611
|
+
hideBtn.style.display = "none";
|
|
612
|
+
|
|
613
|
+
actions.appendChild(btn);
|
|
614
|
+
actions.appendChild(hideBtn);
|
|
615
|
+
|
|
601
616
|
const ghost = document.createElement("div");
|
|
602
617
|
ghost.className = "ifc-label-ghost";
|
|
603
618
|
ghost.setAttribute("aria-hidden", "true");
|
|
@@ -670,12 +685,12 @@ export class LabelPlacementController {
|
|
|
670
685
|
|
|
671
686
|
canvasMenu.appendChild(menuAdd);
|
|
672
687
|
|
|
673
|
-
return { btn, ghost, dot, num, dragGhost, dragNum, menu, canvasMenu };
|
|
688
|
+
return { actions, btn, hideBtn, ghost, dot, num, dragGhost, dragNum, menu, canvasMenu, menuAdd };
|
|
674
689
|
}
|
|
675
690
|
|
|
676
691
|
#attachUi() {
|
|
677
692
|
// Важно: container должен быть position:relative (в index.html уже так).
|
|
678
|
-
this.container.appendChild(this._ui.
|
|
693
|
+
this.container.appendChild(this._ui.actions);
|
|
679
694
|
this.container.appendChild(this._ui.ghost);
|
|
680
695
|
// Призрак перетаскивания добавляем в body, чтобы не обрезался overflow контейнера
|
|
681
696
|
if (document?.body) {
|
|
@@ -699,6 +714,15 @@ export class LabelPlacementController {
|
|
|
699
714
|
};
|
|
700
715
|
this._ui.btn.addEventListener("click", this._onBtnClick, { passive: false });
|
|
701
716
|
|
|
717
|
+
this._onHideBtnClick = (e) => {
|
|
718
|
+
try { e.preventDefault(); } catch (_) {}
|
|
719
|
+
try { e.stopPropagation(); } catch (_) {}
|
|
720
|
+
try { e.stopImmediatePropagation?.(); } catch (_) {}
|
|
721
|
+
if (!this._markers.length) return;
|
|
722
|
+
this.#setLabelsHidden(!this._labelsHidden);
|
|
723
|
+
};
|
|
724
|
+
this._ui.hideBtn.addEventListener("click", this._onHideBtnClick, { passive: false });
|
|
725
|
+
|
|
702
726
|
this._onMenuPointerDown = (e) => {
|
|
703
727
|
// Не даём клику меню попасть в canvas/OrbitControls
|
|
704
728
|
try { e.preventDefault(); } catch (_) {}
|
|
@@ -731,6 +755,7 @@ export class LabelPlacementController {
|
|
|
731
755
|
const target = e.target;
|
|
732
756
|
const action = target?.getAttribute?.("data-action");
|
|
733
757
|
if (action !== "add") return;
|
|
758
|
+
if (this._labelsHidden) return;
|
|
734
759
|
try { e.preventDefault(); } catch (_) {}
|
|
735
760
|
try { e.stopPropagation(); } catch (_) {}
|
|
736
761
|
|
|
@@ -1281,6 +1306,7 @@ export class LabelPlacementController {
|
|
|
1281
1306
|
sceneState: data.sceneState || null,
|
|
1282
1307
|
});
|
|
1283
1308
|
this._markers.push(marker);
|
|
1309
|
+
this.#syncHideButton();
|
|
1284
1310
|
|
|
1285
1311
|
const onMarkerPointerDown = (e) => {
|
|
1286
1312
|
this.logger?.log?.("[LabelClickDbg]", {
|
|
@@ -1372,6 +1398,7 @@ export class LabelPlacementController {
|
|
|
1372
1398
|
}
|
|
1373
1399
|
if (maxNumericId != null) this._nextId = Math.max(1, Math.floor(maxNumericId) + 1);
|
|
1374
1400
|
if (prevSelectedId != null) this.selectLabel(prevSelectedId);
|
|
1401
|
+
this.#syncHideButton();
|
|
1375
1402
|
}
|
|
1376
1403
|
|
|
1377
1404
|
getLabelMarkers() {
|
|
@@ -1432,6 +1459,11 @@ export class LabelPlacementController {
|
|
|
1432
1459
|
for (const m of this._markers) {
|
|
1433
1460
|
if (!m || !m.el) continue;
|
|
1434
1461
|
|
|
1462
|
+
if (this._labelsHidden) {
|
|
1463
|
+
m.el.style.display = "none";
|
|
1464
|
+
continue;
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1435
1467
|
if (!model) {
|
|
1436
1468
|
m.el.style.display = "none";
|
|
1437
1469
|
continue;
|
|
@@ -1441,6 +1473,11 @@ export class LabelPlacementController {
|
|
|
1441
1473
|
this._tmpV.copy(m.localPoint);
|
|
1442
1474
|
model.localToWorld(this._tmpV);
|
|
1443
1475
|
|
|
1476
|
+
if (this.#isPointClippedBySection(this._tmpV)) {
|
|
1477
|
+
m.el.style.display = "none";
|
|
1478
|
+
continue;
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1444
1481
|
const ndc = this._tmpV.project(camera);
|
|
1445
1482
|
|
|
1446
1483
|
// Если точка за камерой или далеко за пределами — скрываем
|
|
@@ -1462,5 +1499,54 @@ export class LabelPlacementController {
|
|
|
1462
1499
|
m.el.style.transform = `translate3d(${x}px, ${y}px, 0) translate(-50%, -50%)`;
|
|
1463
1500
|
}
|
|
1464
1501
|
}
|
|
1502
|
+
|
|
1503
|
+
#isPointClippedBySection(pointWorld) {
|
|
1504
|
+
const planes = this.viewer?.clipping?.planes || [];
|
|
1505
|
+
for (const plane of planes) {
|
|
1506
|
+
if (!plane || !Number.isFinite(plane.constant)) continue;
|
|
1507
|
+
const signed = plane.distanceToPoint(pointWorld);
|
|
1508
|
+
if (signed < -1e-4) return true;
|
|
1509
|
+
}
|
|
1510
|
+
return false;
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
#setLabelsHidden(hidden) {
|
|
1514
|
+
const next = !!hidden;
|
|
1515
|
+
if (this._labelsHidden === next) return;
|
|
1516
|
+
this._labelsHidden = next;
|
|
1517
|
+
if (this._labelsHidden) {
|
|
1518
|
+
try { this.cancelPlacement(); } catch (_) {}
|
|
1519
|
+
try { this.#closeContextMenu(); } catch (_) {}
|
|
1520
|
+
try { this.#closeCanvasMenu(); } catch (_) {}
|
|
1521
|
+
}
|
|
1522
|
+
this.#syncHideButton();
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
#syncHideButton() {
|
|
1526
|
+
const btn = this._ui?.hideBtn;
|
|
1527
|
+
if (!btn) return;
|
|
1528
|
+
const hasMarkers = this._markers.length > 0;
|
|
1529
|
+
btn.style.display = hasMarkers ? "block" : "none";
|
|
1530
|
+
if (!hasMarkers && this._labelsHidden) {
|
|
1531
|
+
this._labelsHidden = false;
|
|
1532
|
+
}
|
|
1533
|
+
btn.setAttribute("aria-pressed", this._labelsHidden ? "true" : "false");
|
|
1534
|
+
try { btn.classList.toggle("ifc-label-hide-btn--active", this._labelsHidden); } catch (_) {}
|
|
1535
|
+
this.#syncAddAvailability();
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
#syncAddAvailability() {
|
|
1539
|
+
const disabled = !!this._labelsHidden;
|
|
1540
|
+
const btn = this._ui?.btn;
|
|
1541
|
+
if (btn) {
|
|
1542
|
+
btn.disabled = disabled;
|
|
1543
|
+
try { btn.classList.toggle("ifc-label-add-btn--disabled", disabled); } catch (_) {}
|
|
1544
|
+
}
|
|
1545
|
+
const menuAdd = this._ui?.menuAdd;
|
|
1546
|
+
if (menuAdd) {
|
|
1547
|
+
menuAdd.disabled = disabled;
|
|
1548
|
+
try { menuAdd.classList.toggle("ifc-label-menu-item--disabled", disabled); } catch (_) {}
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1465
1551
|
}
|
|
1466
1552
|
|