@sequent-org/ifc-viewer 1.2.4-ci.55.0 → 1.2.4-ci.57.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 +1 -1
- package/src/ui/LabelPlacementController.js +339 -21
- package/src/viewer/Viewer.js +1 -1
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.57.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 };
|
|
@@ -96,10 +111,23 @@ export class LabelPlacementController {
|
|
|
96
111
|
try { dom?.removeEventListener("pointerdown", this._onPointerDownCapture, { capture: true }); } catch (_) {
|
|
97
112
|
try { dom?.removeEventListener("pointerdown", this._onPointerDownCapture); } catch (_) {}
|
|
98
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 (_) {}
|
|
99
117
|
try { window.removeEventListener("keydown", this._onKeyDown); } catch (_) {}
|
|
100
118
|
try { window.removeEventListener("resize", this._onWindowResize); } catch (_) {}
|
|
101
119
|
try { window.removeEventListener("scroll", this._onWindowScroll, true); } catch (_) {}
|
|
102
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 (_) {}
|
|
103
131
|
try { this._ui?.btn?.removeEventListener("click", this._onBtnClick); } catch (_) {}
|
|
104
132
|
try { this._ui?.menu?.removeEventListener("pointerdown", this._onMenuPointerDown); } catch (_) {}
|
|
105
133
|
try { this._ui?.menu?.removeEventListener("click", this._onMenuClick); } catch (_) {}
|
|
@@ -116,6 +144,7 @@ export class LabelPlacementController {
|
|
|
116
144
|
this._markers.length = 0;
|
|
117
145
|
|
|
118
146
|
try { this._ui?.ghost?.remove?.(); } catch (_) {}
|
|
147
|
+
try { this._ui?.dragGhost?.remove?.(); } catch (_) {}
|
|
119
148
|
try { this._ui?.btn?.remove?.(); } catch (_) {}
|
|
120
149
|
try { this._ui?.menu?.remove?.(); } catch (_) {}
|
|
121
150
|
try { this._ui?.canvasMenu?.remove?.(); } catch (_) {}
|
|
@@ -253,11 +282,16 @@ export class LabelPlacementController {
|
|
|
253
282
|
planes?.[1]?.constant,
|
|
254
283
|
planes?.[2]?.constant,
|
|
255
284
|
];
|
|
285
|
+
const clipPlanes = {
|
|
286
|
+
x: this.#getAxisPlaneState('x', planes?.[0]),
|
|
287
|
+
y: this.#getAxisPlaneState('y', planes?.[1]),
|
|
288
|
+
z: this.#getAxisPlaneState('z', planes?.[2]),
|
|
289
|
+
};
|
|
256
290
|
|
|
257
291
|
return {
|
|
258
292
|
camera: cam,
|
|
259
293
|
modelTransform,
|
|
260
|
-
clipping: { constants: clipConstants },
|
|
294
|
+
clipping: { constants: clipConstants, planes: clipPlanes },
|
|
261
295
|
};
|
|
262
296
|
}
|
|
263
297
|
|
|
@@ -332,17 +366,7 @@ export class LabelPlacementController {
|
|
|
332
366
|
} catch (_) {}
|
|
333
367
|
|
|
334
368
|
// 5) Клиппинг (как Home: сначала восстановили камеру+модель, затем planes)
|
|
335
|
-
|
|
336
|
-
const constants = sceneState?.clipping?.constants || [];
|
|
337
|
-
if (typeof viewer.setSection === "function") {
|
|
338
|
-
["x", "y", "z"].forEach((axis, i) => {
|
|
339
|
-
const c = constants[i];
|
|
340
|
-
const enabled = Number.isFinite(c);
|
|
341
|
-
const dist = -Number(c);
|
|
342
|
-
viewer.setSection(axis, enabled, enabled ? dist : 0);
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
} catch (_) {}
|
|
369
|
+
this.#applyClippingFromState(sceneState);
|
|
346
370
|
|
|
347
371
|
try { camera?.updateProjectionMatrix?.(); } catch (_) {}
|
|
348
372
|
try { controls?.update?.(); } catch (_) {}
|
|
@@ -372,18 +396,65 @@ export class LabelPlacementController {
|
|
|
372
396
|
const viewer = this.viewer;
|
|
373
397
|
if (!viewer || !sceneState) return;
|
|
374
398
|
try {
|
|
399
|
+
const byAxis = sceneState?.clipping?.planes || null;
|
|
400
|
+
if (byAxis && typeof viewer.setSection === "function") {
|
|
401
|
+
["x", "y", "z"].forEach((axis) => {
|
|
402
|
+
const s = byAxis?.[axis] || null;
|
|
403
|
+
const enabled = !!s?.enabled;
|
|
404
|
+
const dist = Number(s?.distance);
|
|
405
|
+
viewer.setSection(axis, enabled, (enabled && Number.isFinite(dist)) ? dist : 0);
|
|
406
|
+
});
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
375
409
|
const constants = sceneState?.clipping?.constants || [];
|
|
376
410
|
if (typeof viewer.setSection === "function") {
|
|
377
411
|
["x", "y", "z"].forEach((axis, i) => {
|
|
378
412
|
const c = constants[i];
|
|
379
413
|
const enabled = Number.isFinite(c);
|
|
380
|
-
const dist =
|
|
381
|
-
viewer.setSection(axis, enabled, enabled ? dist : 0);
|
|
414
|
+
const dist = this.#getAxisDistanceFromConstant(axis, c);
|
|
415
|
+
viewer.setSection(axis, enabled, (enabled && Number.isFinite(dist)) ? dist : 0);
|
|
382
416
|
});
|
|
383
417
|
}
|
|
384
418
|
} catch (_) {}
|
|
385
419
|
}
|
|
386
420
|
|
|
421
|
+
#getAxisPlaneState(axis, plane) {
|
|
422
|
+
if (!plane) return { enabled: false, distance: null };
|
|
423
|
+
const constant = plane.constant;
|
|
424
|
+
const enabled = Number.isFinite(constant);
|
|
425
|
+
if (!enabled) return { enabled: false, distance: null };
|
|
426
|
+
const distance = this.#getAxisDistanceFromPlane(axis, plane);
|
|
427
|
+
return { enabled: true, distance };
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
#getAxisDistanceFromPlane(axis, plane) {
|
|
431
|
+
if (!plane || !Number.isFinite(plane.constant)) return null;
|
|
432
|
+
const n = plane.normal || null;
|
|
433
|
+
const nx = Number(n?.x ?? 1);
|
|
434
|
+
const ny = Number(n?.y ?? 1);
|
|
435
|
+
const nz = Number(n?.z ?? 1);
|
|
436
|
+
const sign = (axis === 'x') ? (nx >= 0 ? 1 : -1)
|
|
437
|
+
: (axis === 'y') ? (ny >= 0 ? 1 : -1)
|
|
438
|
+
: (nz >= 0 ? 1 : -1);
|
|
439
|
+
return (sign > 0) ? -Number(plane.constant) : Number(plane.constant);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
#getAxisDistanceFromConstant(axis, constant) {
|
|
443
|
+
if (!Number.isFinite(constant)) return null;
|
|
444
|
+
const viewer = this.viewer;
|
|
445
|
+
const planes = viewer?.clipping?.planes || [];
|
|
446
|
+
const idx = axis === 'x' ? 0 : axis === 'y' ? 1 : 2;
|
|
447
|
+
const plane = planes[idx] || null;
|
|
448
|
+
const n = plane?.normal || null;
|
|
449
|
+
const nx = Number(n?.x ?? 1);
|
|
450
|
+
const ny = Number(n?.y ?? 1);
|
|
451
|
+
const nz = Number(n?.z ?? 1);
|
|
452
|
+
const sign = (axis === 'x') ? (nx >= 0 ? 1 : -1)
|
|
453
|
+
: (axis === 'y') ? (ny >= 0 ? 1 : -1)
|
|
454
|
+
: (nz >= 0 ? 1 : -1);
|
|
455
|
+
return (sign > 0) ? -Number(constant) : Number(constant);
|
|
456
|
+
}
|
|
457
|
+
|
|
387
458
|
#applyViewOffset(camera, viewOffset) {
|
|
388
459
|
if (!camera || !viewOffset) return;
|
|
389
460
|
try {
|
|
@@ -543,6 +614,20 @@ export class LabelPlacementController {
|
|
|
543
614
|
ghost.appendChild(dot);
|
|
544
615
|
ghost.appendChild(num);
|
|
545
616
|
|
|
617
|
+
const dragGhost = document.createElement("div");
|
|
618
|
+
dragGhost.className = "ifc-label-ghost ifc-label-ghost--drag";
|
|
619
|
+
dragGhost.setAttribute("aria-hidden", "true");
|
|
620
|
+
dragGhost.style.display = "none";
|
|
621
|
+
dragGhost.style.left = "0px";
|
|
622
|
+
dragGhost.style.top = "0px";
|
|
623
|
+
|
|
624
|
+
const dragDot = document.createElement("div");
|
|
625
|
+
dragDot.className = "ifc-label-dot";
|
|
626
|
+
const dragNum = document.createElement("div");
|
|
627
|
+
dragNum.className = "ifc-label-num";
|
|
628
|
+
dragGhost.appendChild(dragDot);
|
|
629
|
+
dragGhost.appendChild(dragNum);
|
|
630
|
+
|
|
546
631
|
const menu = document.createElement("div");
|
|
547
632
|
menu.className = "ifc-label-menu";
|
|
548
633
|
menu.style.display = "none";
|
|
@@ -583,13 +668,14 @@ export class LabelPlacementController {
|
|
|
583
668
|
|
|
584
669
|
canvasMenu.appendChild(menuAdd);
|
|
585
670
|
|
|
586
|
-
return { btn, ghost, dot, num, menu, canvasMenu };
|
|
671
|
+
return { btn, ghost, dot, num, dragGhost, dragNum, menu, canvasMenu };
|
|
587
672
|
}
|
|
588
673
|
|
|
589
674
|
#attachUi() {
|
|
590
675
|
// Важно: container должен быть position:relative (в index.html уже так).
|
|
591
676
|
this.container.appendChild(this._ui.btn);
|
|
592
677
|
this.container.appendChild(this._ui.ghost);
|
|
678
|
+
this.container.appendChild(this._ui.dragGhost);
|
|
593
679
|
this.container.appendChild(this._ui.menu);
|
|
594
680
|
this.container.appendChild(this._ui.canvasMenu);
|
|
595
681
|
}
|
|
@@ -705,9 +791,64 @@ export class LabelPlacementController {
|
|
|
705
791
|
// scroll слушаем в capture, чтобы ловить скролл вложенных контейнеров
|
|
706
792
|
window.addEventListener("scroll", this._onWindowScroll, true);
|
|
707
793
|
|
|
794
|
+
const logDnDEvent = (scope, e) => {
|
|
795
|
+
const target = e?.target || null;
|
|
796
|
+
const closestMarker = (target && typeof target.closest === "function")
|
|
797
|
+
? target.closest(".ifc-label-marker")
|
|
798
|
+
: null;
|
|
799
|
+
if (!closestMarker) return;
|
|
800
|
+
const payload = {
|
|
801
|
+
type: e?.type,
|
|
802
|
+
target,
|
|
803
|
+
closestMarker,
|
|
804
|
+
button: e?.button,
|
|
805
|
+
buttons: e?.buttons,
|
|
806
|
+
clientX: e?.clientX,
|
|
807
|
+
clientY: e?.clientY,
|
|
808
|
+
defaultPrevented: !!e?.defaultPrevented,
|
|
809
|
+
};
|
|
810
|
+
try {
|
|
811
|
+
this.logger?.log?.("[LabelDnD]", scope, payload);
|
|
812
|
+
} catch (_) {
|
|
813
|
+
try { console.log("[LabelDnD]", scope, payload); } catch (_) {}
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
|
|
817
|
+
this._onDbgDocPointerDown = (e) => logDnDEvent("document", e);
|
|
818
|
+
this._onDbgDocPointerMove = (e) => logDnDEvent("document", e);
|
|
819
|
+
this._onDbgDocPointerUp = (e) => logDnDEvent("document", e);
|
|
820
|
+
this._onDbgDocDragStart = (e) => logDnDEvent("document", e);
|
|
821
|
+
this._onDbgDocDragEnd = (e) => logDnDEvent("document", e);
|
|
822
|
+
this._onDbgDocDragOver = (e) => logDnDEvent("document", e);
|
|
823
|
+
this._onDbgDocDrop = (e) => logDnDEvent("document", e);
|
|
824
|
+
|
|
825
|
+
document.addEventListener("pointerdown", this._onDbgDocPointerDown);
|
|
826
|
+
document.addEventListener("pointermove", this._onDbgDocPointerMove);
|
|
827
|
+
document.addEventListener("pointerup", this._onDbgDocPointerUp);
|
|
828
|
+
document.addEventListener("dragstart", this._onDbgDocDragStart);
|
|
829
|
+
document.addEventListener("dragend", this._onDbgDocDragEnd);
|
|
830
|
+
document.addEventListener("dragover", this._onDbgDocDragOver);
|
|
831
|
+
document.addEventListener("drop", this._onDbgDocDrop);
|
|
832
|
+
|
|
833
|
+
this._onLabelDragPointerMove = (e) => this.#updateLabelDrag(e);
|
|
834
|
+
this._onLabelDragPointerUp = (e) => this.#finishLabelDrag(e, "pointerup");
|
|
835
|
+
this._onLabelDragPointerCancel = (e) => this.#finishLabelDrag(e, "pointercancel");
|
|
836
|
+
|
|
837
|
+
document.addEventListener("pointermove", this._onLabelDragPointerMove, { passive: true });
|
|
838
|
+
document.addEventListener("pointerup", this._onLabelDragPointerUp, { passive: true });
|
|
839
|
+
document.addEventListener("pointercancel", this._onLabelDragPointerCancel, { passive: true });
|
|
840
|
+
|
|
708
841
|
const dom = this.viewer?.renderer?.domElement;
|
|
709
842
|
if (!dom) return;
|
|
710
843
|
|
|
844
|
+
this._onDbgDomPointerDown = (e) => logDnDEvent("ifc3dHost", e);
|
|
845
|
+
this._onDbgDomPointerMove = (e) => logDnDEvent("ifc3dHost", e);
|
|
846
|
+
this._onDbgDomPointerUp = (e) => logDnDEvent("ifc3dHost", e);
|
|
847
|
+
|
|
848
|
+
dom.addEventListener("pointerdown", this._onDbgDomPointerDown, { passive: true });
|
|
849
|
+
dom.addEventListener("pointermove", this._onDbgDomPointerMove, { passive: true });
|
|
850
|
+
dom.addEventListener("pointerup", this._onDbgDomPointerUp, { passive: true });
|
|
851
|
+
|
|
711
852
|
this._onPointerMove = (e) => {
|
|
712
853
|
if (!this._placing) return;
|
|
713
854
|
this.#updateGhostFromClient(e.clientX, e.clientY);
|
|
@@ -804,6 +945,117 @@ export class LabelPlacementController {
|
|
|
804
945
|
this.#updateGhostFromClient(this._lastPointer.x, this._lastPointer.y);
|
|
805
946
|
}
|
|
806
947
|
|
|
948
|
+
#setDragGhostVisible(visible) {
|
|
949
|
+
if (!this._ui?.dragGhost) return;
|
|
950
|
+
this._ui.dragGhost.style.display = visible ? "block" : "none";
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
#applyDragGhostTransform() {
|
|
954
|
+
const g = this._ui?.dragGhost;
|
|
955
|
+
if (!g) return;
|
|
956
|
+
g.style.transform = `translate3d(${this._labelDrag.ghostPos.x}px, ${this._labelDrag.ghostPos.y}px, 0) translate(-50%, -50%)`;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
#updateDragGhostFromClient(clientX, clientY) {
|
|
960
|
+
this._labelDrag.last = { x: clientX, y: clientY };
|
|
961
|
+
if (!this._containerOffsetValid) this.#refreshContainerOffset();
|
|
962
|
+
const x = (clientX - this._containerOffset.left);
|
|
963
|
+
const y = (clientY - this._containerOffset.top);
|
|
964
|
+
this._labelDrag.ghostPos.x = x;
|
|
965
|
+
this._labelDrag.ghostPos.y = y;
|
|
966
|
+
this.#applyDragGhostTransform();
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
#beginLabelDrag(marker, e) {
|
|
970
|
+
if (!marker || !e || e.button !== 0) return;
|
|
971
|
+
this._labelDrag.active = true;
|
|
972
|
+
this._labelDrag.moved = false;
|
|
973
|
+
this._labelDrag.pointerId = e.pointerId ?? null;
|
|
974
|
+
this._labelDrag.id = marker.id;
|
|
975
|
+
this._labelDrag.start = { x: e.clientX, y: e.clientY };
|
|
976
|
+
this._labelDrag.last = { x: e.clientX, y: e.clientY };
|
|
977
|
+
this._labelDrag.clickMarker = marker;
|
|
978
|
+
|
|
979
|
+
try {
|
|
980
|
+
this._labelDrag.prevControlsEnabled = this.viewer?.controls?.enabled ?? null;
|
|
981
|
+
if (this.viewer?.controls) this.viewer.controls.enabled = false;
|
|
982
|
+
} catch (_) {}
|
|
983
|
+
|
|
984
|
+
if (this._ui?.dragNum) this._ui.dragNum.textContent = String(marker.id);
|
|
985
|
+
this.#setDragGhostVisible(false);
|
|
986
|
+
|
|
987
|
+
try { marker.el?.setPointerCapture?.(e.pointerId); } catch (_) {}
|
|
988
|
+
|
|
989
|
+
this.#dispatchLabelEvent("ifcviewer:label-drag-start", {
|
|
990
|
+
id: marker.id,
|
|
991
|
+
clientX: e.clientX,
|
|
992
|
+
clientY: e.clientY,
|
|
993
|
+
}, null);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
#updateLabelDrag(e) {
|
|
997
|
+
if (!this._labelDrag.active || !e) return;
|
|
998
|
+
if (this._labelDrag.pointerId != null && e.pointerId != null && e.pointerId !== this._labelDrag.pointerId) return;
|
|
999
|
+
|
|
1000
|
+
const dx = (e.clientX - this._labelDrag.start.x);
|
|
1001
|
+
const dy = (e.clientY - this._labelDrag.start.y);
|
|
1002
|
+
const dist = Math.hypot(dx, dy);
|
|
1003
|
+
if (!this._labelDrag.moved && dist >= this._labelDrag.threshold) {
|
|
1004
|
+
this._labelDrag.moved = true;
|
|
1005
|
+
this.#setDragGhostVisible(true);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
if (this._labelDrag.moved) {
|
|
1009
|
+
this.#updateDragGhostFromClient(e.clientX, e.clientY);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
#resolveDropTarget(clientX, clientY) {
|
|
1014
|
+
const el = document.elementFromPoint?.(clientX, clientY) || null;
|
|
1015
|
+
if (!el) return { element: null, card: null };
|
|
1016
|
+
if (!this._labelDragDropSelector) return { element: el, card: null };
|
|
1017
|
+
const card = el.closest?.(this._labelDragDropSelector) || null;
|
|
1018
|
+
return { element: el, card };
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
#finishLabelDrag(e, reason = "pointerup") {
|
|
1022
|
+
if (!this._labelDrag.active) return;
|
|
1023
|
+
const marker = this._labelDrag.clickMarker;
|
|
1024
|
+
const moved = !!this._labelDrag.moved;
|
|
1025
|
+
const clientX = e?.clientX ?? this._labelDrag.last.x;
|
|
1026
|
+
const clientY = e?.clientY ?? this._labelDrag.last.y;
|
|
1027
|
+
|
|
1028
|
+
this._labelDrag.active = false;
|
|
1029
|
+
this._labelDrag.moved = false;
|
|
1030
|
+
this._labelDrag.pointerId = null;
|
|
1031
|
+
this._labelDrag.id = null;
|
|
1032
|
+
this._labelDrag.clickMarker = null;
|
|
1033
|
+
|
|
1034
|
+
this.#setDragGhostVisible(false);
|
|
1035
|
+
|
|
1036
|
+
try {
|
|
1037
|
+
if (this.viewer?.controls && this._labelDrag.prevControlsEnabled != null) {
|
|
1038
|
+
this.viewer.controls.enabled = !!this._labelDrag.prevControlsEnabled;
|
|
1039
|
+
}
|
|
1040
|
+
} catch (_) {}
|
|
1041
|
+
this._labelDrag.prevControlsEnabled = null;
|
|
1042
|
+
|
|
1043
|
+
if (!moved) {
|
|
1044
|
+
if (marker) this.#handleMarkerClick(marker);
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
const drop = this.#resolveDropTarget(clientX, clientY);
|
|
1049
|
+
this.#dispatchLabelEvent("ifcviewer:label-drop", {
|
|
1050
|
+
id: marker?.id ?? this._labelDrag.id,
|
|
1051
|
+
clientX,
|
|
1052
|
+
clientY,
|
|
1053
|
+
dropTarget: drop.card || null,
|
|
1054
|
+
elementFromPoint: drop.element || null,
|
|
1055
|
+
reason,
|
|
1056
|
+
}, null);
|
|
1057
|
+
}
|
|
1058
|
+
|
|
807
1059
|
#pickModelPoint(clientX, clientY) {
|
|
808
1060
|
const model = this.viewer?.activeModel;
|
|
809
1061
|
const camera = this.viewer?.camera;
|
|
@@ -900,6 +1152,18 @@ export class LabelPlacementController {
|
|
|
900
1152
|
return this._markers.find((m) => String(m?.id) === String(this._selectedId)) || null;
|
|
901
1153
|
}
|
|
902
1154
|
|
|
1155
|
+
#handleMarkerClick(marker) {
|
|
1156
|
+
if (!marker) return;
|
|
1157
|
+
this.#closeContextMenu();
|
|
1158
|
+
this.#setSelectedMarker(marker);
|
|
1159
|
+
this.#dispatchLabelEvent("ifcviewer:label-click", {
|
|
1160
|
+
id: marker.id,
|
|
1161
|
+
sceneState: marker.sceneState || null,
|
|
1162
|
+
}, "ifcviewer:card-click");
|
|
1163
|
+
// "Долеталка" камеры: быстрый старт + мягкий конец
|
|
1164
|
+
this.#animateToSceneState(marker.sceneState, 550);
|
|
1165
|
+
}
|
|
1166
|
+
|
|
903
1167
|
#buildActionPayload(marker) {
|
|
904
1168
|
if (!marker) return null;
|
|
905
1169
|
return {
|
|
@@ -1019,24 +1283,78 @@ export class LabelPlacementController {
|
|
|
1019
1283
|
try { e.preventDefault(); } catch (_) {}
|
|
1020
1284
|
try { e.stopPropagation(); } catch (_) {}
|
|
1021
1285
|
try { e.stopImmediatePropagation?.(); } catch (_) {}
|
|
1286
|
+
try {
|
|
1287
|
+
this.logger?.log?.("[LabelDnD]", "marker", {
|
|
1288
|
+
type: e?.type,
|
|
1289
|
+
target: e?.target || null,
|
|
1290
|
+
closestMarker: e?.target?.closest?.(".ifc-label-marker") || null,
|
|
1291
|
+
button: e?.button,
|
|
1292
|
+
buttons: e?.buttons,
|
|
1293
|
+
clientX: e?.clientX,
|
|
1294
|
+
clientY: e?.clientY,
|
|
1295
|
+
defaultPrevented: !!e?.defaultPrevented,
|
|
1296
|
+
});
|
|
1297
|
+
} catch (_) {}
|
|
1022
1298
|
// если были в режиме постановки — выходим
|
|
1023
1299
|
try { this.cancelPlacement(); } catch (_) {}
|
|
1024
1300
|
|
|
1025
1301
|
if (e.button === 0) {
|
|
1026
1302
|
this.#closeContextMenu();
|
|
1027
1303
|
this.#setSelectedMarker(marker);
|
|
1028
|
-
this.#
|
|
1029
|
-
id: marker.id,
|
|
1030
|
-
sceneState: marker.sceneState || null,
|
|
1031
|
-
}, "ifcviewer:card-click");
|
|
1032
|
-
// "Долеталка" камеры: быстрый старт + мягкий конец
|
|
1033
|
-
this.#animateToSceneState(marker.sceneState, 550);
|
|
1304
|
+
this.#beginLabelDrag(marker, e);
|
|
1034
1305
|
}
|
|
1035
1306
|
};
|
|
1036
1307
|
// capture-phase, чтобы обогнать любые handlers на canvas
|
|
1037
1308
|
try { marker.el.addEventListener("pointerdown", onMarkerPointerDown, { capture: true, passive: false }); } catch (_) {
|
|
1038
1309
|
try { marker.el.addEventListener("pointerdown", onMarkerPointerDown); } catch (_) {}
|
|
1039
1310
|
}
|
|
1311
|
+
const onMarkerPointerUp = (e) => {
|
|
1312
|
+
try {
|
|
1313
|
+
this.logger?.log?.("[LabelDnD]", "marker", {
|
|
1314
|
+
type: e?.type,
|
|
1315
|
+
target: e?.target || null,
|
|
1316
|
+
closestMarker: e?.target?.closest?.(".ifc-label-marker") || null,
|
|
1317
|
+
button: e?.button,
|
|
1318
|
+
buttons: e?.buttons,
|
|
1319
|
+
clientX: e?.clientX,
|
|
1320
|
+
clientY: e?.clientY,
|
|
1321
|
+
defaultPrevented: !!e?.defaultPrevented,
|
|
1322
|
+
});
|
|
1323
|
+
} catch (_) {}
|
|
1324
|
+
};
|
|
1325
|
+
try { marker.el.addEventListener("pointerup", onMarkerPointerUp, { capture: true, passive: true }); } catch (_) {
|
|
1326
|
+
try { marker.el.addEventListener("pointerup", onMarkerPointerUp); } catch (_) {}
|
|
1327
|
+
}
|
|
1328
|
+
const onMarkerDragStart = (e) => {
|
|
1329
|
+
try {
|
|
1330
|
+
this.logger?.log?.("[LabelDnD]", "marker", {
|
|
1331
|
+
type: e?.type,
|
|
1332
|
+
target: e?.target || null,
|
|
1333
|
+
closestMarker: e?.target?.closest?.(".ifc-label-marker") || null,
|
|
1334
|
+
button: e?.button,
|
|
1335
|
+
buttons: e?.buttons,
|
|
1336
|
+
clientX: e?.clientX,
|
|
1337
|
+
clientY: e?.clientY,
|
|
1338
|
+
defaultPrevented: !!e?.defaultPrevented,
|
|
1339
|
+
});
|
|
1340
|
+
} catch (_) {}
|
|
1341
|
+
};
|
|
1342
|
+
const onMarkerDragEnd = (e) => {
|
|
1343
|
+
try {
|
|
1344
|
+
this.logger?.log?.("[LabelDnD]", "marker", {
|
|
1345
|
+
type: e?.type,
|
|
1346
|
+
target: e?.target || null,
|
|
1347
|
+
closestMarker: e?.target?.closest?.(".ifc-label-marker") || null,
|
|
1348
|
+
button: e?.button,
|
|
1349
|
+
buttons: e?.buttons,
|
|
1350
|
+
clientX: e?.clientX,
|
|
1351
|
+
clientY: e?.clientY,
|
|
1352
|
+
defaultPrevented: !!e?.defaultPrevented,
|
|
1353
|
+
});
|
|
1354
|
+
} catch (_) {}
|
|
1355
|
+
};
|
|
1356
|
+
try { marker.el.addEventListener("dragstart", onMarkerDragStart); } catch (_) {}
|
|
1357
|
+
try { marker.el.addEventListener("dragend", onMarkerDragEnd); } catch (_) {}
|
|
1040
1358
|
|
|
1041
1359
|
const onMarkerContextMenu = (e) => {
|
|
1042
1360
|
try { e.preventDefault(); } catch (_) {}
|
package/src/viewer/Viewer.js
CHANGED
|
@@ -3464,7 +3464,7 @@ export class Viewer {
|
|
|
3464
3464
|
const s = plane.normal.dot(c) + plane.constant;
|
|
3465
3465
|
if (s < minSigned) minSigned = s;
|
|
3466
3466
|
}
|
|
3467
|
-
if (minSigned < 0) {
|
|
3467
|
+
if (minSigned < 0 && minSigned >= -1e-3) {
|
|
3468
3468
|
plane.constant -= (minSigned - (-1e-4)); // сдвинем чуть так, чтобы все вершины имели s >= -1e-4
|
|
3469
3469
|
}
|
|
3470
3470
|
}
|