@sequent-org/ifc-viewer 1.2.4-ci.51.0 → 1.2.4-ci.53.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/IfcViewer.js +34 -0
- package/src/ui/CardPlacementController.js +85 -20
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.53.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/IfcViewer.js
CHANGED
|
@@ -22,6 +22,7 @@ import { TdsModelLoader } from "./model-loading/loaders/TdsModelLoader.js";
|
|
|
22
22
|
import { StlModelLoader } from "./model-loading/loaders/StlModelLoader.js";
|
|
23
23
|
import { DaeModelLoader } from "./model-loading/loaders/DaeModelLoader.js";
|
|
24
24
|
import { ThreeDmModelLoader } from "./model-loading/loaders/ThreeDmModelLoader.js";
|
|
25
|
+
import { CardPlacementController } from "./ui/CardPlacementController.js";
|
|
25
26
|
import './style.css';
|
|
26
27
|
|
|
27
28
|
|
|
@@ -82,6 +83,7 @@ export class IfcViewer {
|
|
|
82
83
|
this.viewer = null;
|
|
83
84
|
this.ifcService = null;
|
|
84
85
|
this.ifcTreeView = null;
|
|
86
|
+
this.cardPlacement = null;
|
|
85
87
|
/** @type {ModelLoaderRegistry|null} */
|
|
86
88
|
this.modelLoaders = null;
|
|
87
89
|
this.isInitialized = false;
|
|
@@ -308,6 +310,11 @@ export class IfcViewer {
|
|
|
308
310
|
this.ifcService.dispose();
|
|
309
311
|
this.ifcService = null;
|
|
310
312
|
}
|
|
313
|
+
|
|
314
|
+
if (this.cardPlacement) {
|
|
315
|
+
try { this.cardPlacement.dispose(); } catch (_) {}
|
|
316
|
+
this.cardPlacement = null;
|
|
317
|
+
}
|
|
311
318
|
|
|
312
319
|
if (this.viewer) {
|
|
313
320
|
this.viewer.dispose();
|
|
@@ -353,6 +360,24 @@ export class IfcViewer {
|
|
|
353
360
|
return this.ifcService;
|
|
354
361
|
}
|
|
355
362
|
|
|
363
|
+
/**
|
|
364
|
+
* Устанавливает метки карточек извне.
|
|
365
|
+
* @param {Array<{id: (number|string), localPoint: {x:number,y:number,z:number}, sceneState: object}>} items
|
|
366
|
+
*/
|
|
367
|
+
setCardMarkers(items) {
|
|
368
|
+
if (!this.cardPlacement) return;
|
|
369
|
+
this.cardPlacement.setCardMarkers(items);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Возвращает текущие метки карточек.
|
|
374
|
+
* @returns {Array<{id: (number|string), localPoint: {x:number,y:number,z:number}, sceneState: object}>}
|
|
375
|
+
*/
|
|
376
|
+
getCardMarkers() {
|
|
377
|
+
if (!this.cardPlacement) return [];
|
|
378
|
+
return this.cardPlacement.getCardMarkers();
|
|
379
|
+
}
|
|
380
|
+
|
|
356
381
|
/**
|
|
357
382
|
* Устанавливает видимость боковой панели
|
|
358
383
|
* @param {boolean} visible - Показать или скрыть
|
|
@@ -508,6 +533,15 @@ export class IfcViewer {
|
|
|
508
533
|
|
|
509
534
|
this.viewer = new Viewer(this.elements.viewerContainer);
|
|
510
535
|
this.viewer.init();
|
|
536
|
+
|
|
537
|
+
// В пакете включаем UI "карточек" по умолчанию:
|
|
538
|
+
// кнопка "+ Добавить карточку" + режим постановки меток + сохранение/восстановление состояния.
|
|
539
|
+
try {
|
|
540
|
+
this.cardPlacement = new CardPlacementController({ viewer: this.viewer, container: this.elements.viewerContainer, logger: console });
|
|
541
|
+
} catch (e) {
|
|
542
|
+
console.warn('IfcViewer: CardPlacementController init failed', e);
|
|
543
|
+
this.cardPlacement = null;
|
|
544
|
+
}
|
|
511
545
|
}
|
|
512
546
|
|
|
513
547
|
/**
|
|
@@ -3,7 +3,7 @@ import * as THREE from "three";
|
|
|
3
3
|
class CardMarker {
|
|
4
4
|
/**
|
|
5
5
|
* @param {object} deps
|
|
6
|
-
* @param {number} deps.id
|
|
6
|
+
* @param {number|string} deps.id
|
|
7
7
|
* @param {THREE.Vector3} deps.localPoint
|
|
8
8
|
* @param {HTMLElement} deps.el
|
|
9
9
|
* @param {object|null} deps.sceneState
|
|
@@ -137,6 +137,13 @@ export class CardPlacementController {
|
|
|
137
137
|
} catch (_) {}
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
#dispatchCardEvent(name, detail) {
|
|
141
|
+
try {
|
|
142
|
+
const ev = new CustomEvent(name, { detail, bubbles: true });
|
|
143
|
+
this.container?.dispatchEvent?.(ev);
|
|
144
|
+
} catch (_) {}
|
|
145
|
+
}
|
|
146
|
+
|
|
140
147
|
#easeOutCubic(t) {
|
|
141
148
|
const x = Math.min(1, Math.max(0, Number(t) || 0));
|
|
142
149
|
const inv = 1 - x;
|
|
@@ -671,6 +678,26 @@ export class CardPlacementController {
|
|
|
671
678
|
if (!model) return;
|
|
672
679
|
|
|
673
680
|
const id = this._nextId++;
|
|
681
|
+
|
|
682
|
+
// Храним локальную координату модели, чтобы метка оставалась “приклеенной” к модели
|
|
683
|
+
this._tmpLocal.copy(hit.point);
|
|
684
|
+
model.worldToLocal(this._tmpLocal);
|
|
685
|
+
|
|
686
|
+
const sceneState = this.#captureSceneState();
|
|
687
|
+
const marker = this.#createMarkerFromData({
|
|
688
|
+
id,
|
|
689
|
+
localPoint: { x: this._tmpLocal.x, y: this._tmpLocal.y, z: this._tmpLocal.z },
|
|
690
|
+
sceneState,
|
|
691
|
+
}, true);
|
|
692
|
+
|
|
693
|
+
this.#log("placed", {
|
|
694
|
+
id,
|
|
695
|
+
local: { x: +marker.localPoint.x.toFixed(4), y: +marker.localPoint.y.toFixed(4), z: +marker.localPoint.z.toFixed(4) },
|
|
696
|
+
sceneState: sceneState ? { hasCamera: !!sceneState.camera, hasModel: !!sceneState.modelTransform, hasClip: !!sceneState.clipping } : null,
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
#createMarkerElement(id) {
|
|
674
701
|
const el = document.createElement("div");
|
|
675
702
|
el.className = "ifc-card-marker";
|
|
676
703
|
el.setAttribute("data-id", String(id));
|
|
@@ -679,42 +706,80 @@ export class CardPlacementController {
|
|
|
679
706
|
el.style.left = "0px";
|
|
680
707
|
el.style.top = "0px";
|
|
681
708
|
this.container.appendChild(el);
|
|
709
|
+
return el;
|
|
710
|
+
}
|
|
682
711
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
this._tmpLocal.copy(hit.point);
|
|
687
|
-
model.worldToLocal(this._tmpLocal);
|
|
688
|
-
|
|
712
|
+
#createMarkerFromData(data, emitPlacedEvent) {
|
|
713
|
+
if (!data) return null;
|
|
714
|
+
const localPoint = data.localPoint || {};
|
|
689
715
|
const marker = new CardMarker({
|
|
690
|
-
id,
|
|
691
|
-
localPoint:
|
|
692
|
-
|
|
693
|
-
|
|
716
|
+
id: data.id,
|
|
717
|
+
localPoint: new THREE.Vector3(
|
|
718
|
+
Number(localPoint.x) || 0,
|
|
719
|
+
Number(localPoint.y) || 0,
|
|
720
|
+
Number(localPoint.z) || 0
|
|
721
|
+
),
|
|
722
|
+
el: this.#createMarkerElement(data.id),
|
|
723
|
+
sceneState: data.sceneState || null,
|
|
694
724
|
});
|
|
695
725
|
this._markers.push(marker);
|
|
696
726
|
|
|
697
|
-
|
|
698
|
-
this._onMarkerPointerDown = (e) => {
|
|
727
|
+
const onMarkerPointerDown = (e) => {
|
|
699
728
|
// Важно: не даём клику попасть в canvas/OrbitControls
|
|
700
729
|
try { e.preventDefault(); } catch (_) {}
|
|
701
730
|
try { e.stopPropagation(); } catch (_) {}
|
|
702
731
|
try { e.stopImmediatePropagation?.(); } catch (_) {}
|
|
703
732
|
// если были в режиме постановки — выходим
|
|
704
733
|
try { this.cancelPlacement(); } catch (_) {}
|
|
734
|
+
|
|
735
|
+
this.#dispatchCardEvent("ifcviewer:card-click", {
|
|
736
|
+
id: marker.id,
|
|
737
|
+
sceneState: marker.sceneState || null,
|
|
738
|
+
});
|
|
705
739
|
// "Долеталка" камеры: быстрый старт + мягкий конец
|
|
706
740
|
this.#animateToSceneState(marker.sceneState, 550);
|
|
707
741
|
};
|
|
708
742
|
// capture-phase, чтобы обогнать любые handlers на canvas
|
|
709
|
-
try { el.addEventListener("pointerdown",
|
|
710
|
-
try { el.addEventListener("pointerdown",
|
|
743
|
+
try { marker.el.addEventListener("pointerdown", onMarkerPointerDown, { capture: true, passive: false }); } catch (_) {
|
|
744
|
+
try { marker.el.addEventListener("pointerdown", onMarkerPointerDown); } catch (_) {}
|
|
711
745
|
}
|
|
712
746
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
747
|
+
if (emitPlacedEvent) {
|
|
748
|
+
this.#dispatchCardEvent("ifcviewer:card-placed", {
|
|
749
|
+
id: marker.id,
|
|
750
|
+
localPoint: { x: marker.localPoint.x, y: marker.localPoint.y, z: marker.localPoint.z },
|
|
751
|
+
sceneState: marker.sceneState || null,
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
return marker;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
#clearMarkers() {
|
|
759
|
+
try { this._markers.forEach((m) => m?.el?.remove?.()); } catch (_) {}
|
|
760
|
+
this._markers.length = 0;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
setCardMarkers(items) {
|
|
764
|
+
if (!Array.isArray(items)) return;
|
|
765
|
+
this.#clearMarkers();
|
|
766
|
+
|
|
767
|
+
let maxNumericId = null;
|
|
768
|
+
for (const item of items) {
|
|
769
|
+
const marker = this.#createMarkerFromData(item, false);
|
|
770
|
+
if (marker && typeof marker.id === "number" && Number.isFinite(marker.id)) {
|
|
771
|
+
maxNumericId = (maxNumericId == null) ? marker.id : Math.max(maxNumericId, marker.id);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
if (maxNumericId != null) this._nextId = Math.max(1, Math.floor(maxNumericId) + 1);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
getCardMarkers() {
|
|
778
|
+
return this._markers.map((m) => ({
|
|
779
|
+
id: m.id,
|
|
780
|
+
localPoint: { x: m.localPoint.x, y: m.localPoint.y, z: m.localPoint.z },
|
|
781
|
+
sceneState: m.sceneState || null,
|
|
782
|
+
}));
|
|
718
783
|
}
|
|
719
784
|
|
|
720
785
|
#startRaf() {
|