@sequent-org/ifc-viewer 1.2.4-ci.52.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sequent-org/ifc-viewer",
3
3
  "private": false,
4
- "version": "1.2.4-ci.52.0",
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
@@ -360,6 +360,24 @@ export class IfcViewer {
360
360
  return this.ifcService;
361
361
  }
362
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
+
363
381
  /**
364
382
  * Устанавливает видимость боковой панели
365
383
  * @param {boolean} visible - Показать или скрыть
@@ -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
- const sceneState = this.#captureSceneState();
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: this._tmpLocal.clone(),
692
- el,
693
- sceneState,
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", this._onMarkerPointerDown, { capture: true, passive: false }); } catch (_) {
710
- try { el.addEventListener("pointerdown", this._onMarkerPointerDown); } catch (_) {}
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
- this.#log("placed", {
714
- id,
715
- local: { x: +marker.localPoint.x.toFixed(4), y: +marker.localPoint.y.toFixed(4), z: +marker.localPoint.z.toFixed(4) },
716
- sceneState: sceneState ? { hasCamera: !!sceneState.camera, hasModel: !!sceneState.modelTransform, hasClip: !!sceneState.clipping } : null,
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() {