@2112-lab/central-plant 0.3.12 → 0.3.14

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.
@@ -3,6 +3,29 @@ import { BaseDisposable } from '../../core/baseDisposable.js';
3
3
  import * as THREE from 'three';
4
4
  import { computeFilteredBoundingBox } from '../../utils/boundingBoxUtils.js';
5
5
 
6
+ /**
7
+ * WeakMap cache for storing base position data per Object3D.
8
+ * Used by _getOrComputeWorldBoundingBox() for O(1) bbox offset calculations.
9
+ * WeakMap ensures automatic cleanup when objects are garbage collected.
10
+ * @type {WeakMap<THREE.Object3D, {x: number, y: number, z: number, worldBoundingBox: {min: number[], max: number[]}}>}
11
+ */
12
+ var _basePositionCache = new WeakMap();
13
+
14
+ /**
15
+ * Store base position and worldBoundingBox for an Object3D.
16
+ * Called by modelManager after GLB loading to enable fast bbox updates.
17
+ * @param {THREE.Object3D} object - The loaded GLB model
18
+ * @param {{min: number[], max: number[]}} worldBoundingBox - Computed world bounding box
19
+ */
20
+ function cacheBasePosition(object, worldBoundingBox) {
21
+ _basePositionCache.set(object, {
22
+ x: object.position.x,
23
+ y: object.position.y,
24
+ z: object.position.z,
25
+ worldBoundingBox: worldBoundingBox
26
+ });
27
+ }
28
+
6
29
  /**
7
30
  * Viewport2DInstance
8
31
  * Represents a single 2D viewport with its own Konva stage and configuration
@@ -99,6 +122,9 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
99
122
  // stacking up independent renderComponents() runs
100
123
  _this2._refreshPending = false;
101
124
 
125
+ // Set of viewport keys pending refresh (collects keys across multiple refresh() calls in same frame)
126
+ _this2._pendingRefreshKeys = new Set();
127
+
102
128
  // Event listener reference for cleanup
103
129
  _this2._objectTransformedListener = null;
104
130
  console.log('🔲 Viewport2DManager initialized (multi-instance support)');
@@ -188,8 +214,10 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
188
214
  viewport._instanceKey = key;
189
215
  this.viewports.set(key, viewport);
190
216
 
191
- // Initialize the stage for this viewport
192
- this.initializeStage(viewport);
217
+ // Initialize the stage for this viewport (waits for DOM layout to settle)
218
+ _context.n = 4;
219
+ return this.initializeStage(viewport);
220
+ case 4:
193
221
  return _context.a(2, viewport.isReady);
194
222
  }
195
223
  }, _callee, this);
@@ -283,50 +311,67 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
283
311
  /**
284
312
  * Initialize Konva stage for a specific viewport instance
285
313
  * @param {Viewport2DInstance} viewport - The viewport instance to initialize
314
+ * @returns {Promise<void>} Resolves when viewport is fully ready
286
315
  */
287
316
  )
288
317
  }, {
289
318
  key: "initializeStage",
290
319
  value: function initializeStage(viewport) {
291
- if (!this.Konva || !viewport.container) {
292
- console.error('❌ Cannot initialize stage: Konva or container missing');
293
- return;
294
- }
295
-
296
- // Get container dimensions
297
- var rect = viewport.container.getBoundingClientRect();
298
- var width = rect.width || 800;
299
- var height = rect.height || 600;
300
- console.log("\uD83D\uDCD0 Initializing Konva stage (".concat(viewport.viewType, "): ").concat(width, "x").concat(height));
301
-
302
- // Create Konva stage for this viewport
303
- viewport.stage = new this.Konva.Stage({
304
- container: viewport.container,
305
- width: width,
306
- height: height
307
- });
308
-
309
- // Create separate layers for grid and components
310
- viewport.gridLayer = new this.Konva.Layer();
311
- viewport.componentLayer = new this.Konva.Layer();
312
-
313
- // Add layers to stage in order (grid first, then components)
314
- viewport.stage.add(viewport.gridLayer);
315
- viewport.stage.add(viewport.componentLayer);
316
-
317
- // Setup resize handling
318
- this.setupResizeListener(viewport);
319
-
320
- // Setup zoom and pan handlers
321
- this.setupZoomAndPanHandlers(viewport);
320
+ var _this4 = this;
321
+ return new Promise(function (resolve) {
322
+ if (!_this4.Konva || !viewport.container) {
323
+ console.error('❌ Cannot initialize stage: Konva or container missing');
324
+ resolve();
325
+ return;
326
+ }
322
327
 
323
- // Draw initial content
324
- this.drawGrid(viewport);
325
- this.renderComponents(viewport);
328
+ // Get container dimensions
329
+ var rect = viewport.container.getBoundingClientRect();
330
+ var width = rect.width || 800;
331
+ var height = rect.height || 600;
332
+ console.log("\uD83D\uDCD0 Initializing Konva stage (".concat(viewport.viewType, "): ").concat(width, "x").concat(height));
333
+
334
+ // Create Konva stage for this viewport
335
+ viewport.stage = new _this4.Konva.Stage({
336
+ container: viewport.container,
337
+ width: width,
338
+ height: height
339
+ });
326
340
 
327
- // Mark as ready
328
- viewport.isReady = true;
329
- console.log("\u2705 Viewport2DManager stage initialized (".concat(viewport.viewType, ")"));
341
+ // Create separate layers for grid and components
342
+ viewport.gridLayer = new _this4.Konva.Layer();
343
+ viewport.componentLayer = new _this4.Konva.Layer();
344
+
345
+ // Add layers to stage in order (grid first, then components)
346
+ viewport.stage.add(viewport.gridLayer);
347
+ viewport.stage.add(viewport.componentLayer);
348
+
349
+ // Setup resize handling
350
+ _this4.setupResizeListener(viewport);
351
+
352
+ // Setup zoom and pan handlers
353
+ _this4.setupZoomAndPanHandlers(viewport);
354
+
355
+ // Draw initial grid (lightweight, safe to do immediately)
356
+ _this4.drawGrid(viewport);
357
+
358
+ // Defer component rendering to next frame to ensure DOM layout is finalized.
359
+ // Without this, getBoundingClientRect() may return stale/zero dimensions
360
+ // if the container hasn't been fully laid out by CSS yet.
361
+ requestAnimationFrame(function () {
362
+ // Re-check container dimensions after layout settles
363
+ var finalRect = viewport.container.getBoundingClientRect();
364
+ if (finalRect.width > 0 && finalRect.height > 0) {
365
+ viewport.stage.width(finalRect.width);
366
+ viewport.stage.height(finalRect.height);
367
+ _this4.drawGrid(viewport);
368
+ }
369
+ _this4.renderComponents(viewport);
370
+ viewport.isReady = true;
371
+ console.log("\u2705 Viewport2DManager stage initialized (".concat(viewport.viewType, ")"));
372
+ resolve();
373
+ });
374
+ });
330
375
  }
331
376
 
332
377
  /**
@@ -336,12 +381,12 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
336
381
  }, {
337
382
  key: "setupResizeListener",
338
383
  value: function setupResizeListener(viewport) {
339
- var _this4 = this;
384
+ var _this5 = this;
340
385
  if (typeof window === 'undefined' || !window.ResizeObserver || !viewport.container) {
341
386
  return;
342
387
  }
343
388
  viewport._resizeObserver = new ResizeObserver(function () {
344
- _this4.resizeStage(viewport);
389
+ _this5.resizeStage(viewport);
345
390
  });
346
391
  viewport._resizeObserver.observe(viewport.container);
347
392
  }
@@ -376,7 +421,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
376
421
  }, {
377
422
  key: "setupZoomAndPanHandlers",
378
423
  value: function setupZoomAndPanHandlers(viewport) {
379
- var _this5 = this;
424
+ var _this6 = this;
380
425
  if (!viewport.stage) return;
381
426
 
382
427
  // Mouse wheel zoom
@@ -402,7 +447,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
402
447
  y: pointer.y - mousePointTo.y * clampedScale
403
448
  };
404
449
  viewport.stage.position(newPos);
405
- _this5.drawGrid(viewport);
450
+ _this6.drawGrid(viewport);
406
451
  viewport.stage.batchDraw();
407
452
  });
408
453
 
@@ -422,7 +467,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
422
467
  viewport.stage.x(viewport.stage.x() + dx);
423
468
  viewport.stage.y(viewport.stage.y() + dy);
424
469
  viewport.lastPanPoint = pos;
425
- _this5.drawGrid(viewport);
470
+ _this6.drawGrid(viewport);
426
471
  viewport.stage.batchDraw();
427
472
  });
428
473
  viewport.stage.on('mouseup', function () {
@@ -514,7 +559,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
514
559
  }, {
515
560
  key: "renderComponents",
516
561
  value: function renderComponents(viewport) {
517
- var _this6 = this;
562
+ var _this7 = this;
518
563
  if (!viewport.componentLayer || !viewport.stage || !this.sceneViewer) return;
519
564
  viewport.componentLayer.destroyChildren();
520
565
 
@@ -525,19 +570,18 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
525
570
  return;
526
571
  }
527
572
 
528
- // Only log on significant changes
529
- if (components.length !== viewport._lastComponentCount) {
530
- console.log("\uD83D\uDCE6 Rendering ".concat(components.length, " components in 2D view (").concat(viewport.viewType, ")"));
531
- viewport._lastComponentCount = components.length;
532
- }
573
+ // Track render count for debugging
574
+ if (viewport._renderCount === undefined) viewport._renderCount = 0;
575
+ viewport._renderCount++;
533
576
  var width = viewport.stage.width();
534
577
  var height = viewport.stage.height();
535
578
  var centerX = width / 2;
536
579
  var centerY = height / 2;
537
580
  var scale = viewport.PIXELS_PER_UNIT;
581
+ console.log("\uD83C\uDFA8 RENDER #".concat(viewport._renderCount, " (").concat(viewport.viewType, "): ").concat(components.length, " components, stage=").concat(width, "x").concat(height, ", center=(").concat(centerX, ", ").concat(centerY, ")"));
538
582
  components.forEach(function (component) {
539
583
  try {
540
- _this6.renderComponent(viewport, component, centerX, centerY, scale);
584
+ _this7.renderComponent(viewport, component, centerX, centerY, scale);
541
585
  } catch (err) {
542
586
  console.warn('⚠️ Error rendering component in 2D:', component.name, err);
543
587
  }
@@ -568,6 +612,11 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
568
612
  var screenX = centerX + posX * scale;
569
613
  var screenY = centerY - posY * scale; // Flip Y for screen coords
570
614
 
615
+ // Debug: Log ALL component positions on first render only
616
+ if (viewport._renderCount === 1) {
617
+ console.log("\uD83D\uDD0D [".concat(viewport.viewType, "] ").concat(component.name, ": bbox.z=").concat(bboxCenter.z.toFixed(2), ", posY=").concat(posY.toFixed(2), ", screenY=").concat(screenY.toFixed(0), ", centerY=").concat(centerY.toFixed(0)));
618
+ }
619
+
571
620
  // Generate unique color for this component
572
621
  var colors = this.generateComponentColor(component.id);
573
622
 
@@ -625,16 +674,15 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
625
674
  }, {
626
675
  key: "_getOrComputeWorldBoundingBox",
627
676
  value: function _getOrComputeWorldBoundingBox(object) {
628
- var _object$userData, _object$userData2;
629
677
  // Fast path: offset the stored world bbox by the position delta since load time.
630
678
  // Translation only shifts the bbox center — extents stay identical — so this is O(1)
631
679
  // instead of O(meshes × vertices) from a full geometry traversal.
632
- var stored = (_object$userData = object.userData) === null || _object$userData === void 0 ? void 0 : _object$userData.worldBoundingBox;
633
- var basePos = (_object$userData2 = object.userData) === null || _object$userData2 === void 0 ? void 0 : _object$userData2._wbbBasePosition;
634
- if (stored && basePos) {
635
- var dx = object.position.x - basePos.x;
636
- var dy = object.position.y - basePos.y;
637
- var dz = object.position.z - basePos.z;
680
+ var cached = _basePositionCache.get(object);
681
+ if (cached) {
682
+ var dx = object.position.x - cached.x;
683
+ var dy = object.position.y - cached.y;
684
+ var dz = object.position.z - cached.z;
685
+ var stored = cached.worldBoundingBox;
638
686
  if (dx === 0 && dy === 0 && dz === 0) return stored;
639
687
  return {
640
688
  min: [stored.min[0] + dx, stored.min[1] + dy, stored.min[2] + dz],
@@ -646,7 +694,14 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
646
694
  if (this._bboxCache.has(object.uuid)) {
647
695
  return this._bboxCache.get(object.uuid);
648
696
  }
649
- var box = computeFilteredBoundingBox(object, []);
697
+
698
+ // Force matrix updates before computing bbox to ensure world-space accuracy
699
+ object.updateMatrix();
700
+ object.updateMatrixWorld(true);
701
+
702
+ // Exclude io-devices and connectors to match the stored worldBoundingBox
703
+ // computed in modelManager.replaceWithGLBModels()
704
+ var box = computeFilteredBoundingBox(object, ['io-device', 'connector']);
650
705
  var result;
651
706
  if (box.isEmpty()) {
652
707
  // Object has no geometry; fall back to a point at world position
@@ -777,7 +832,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
777
832
  }, {
778
833
  key: "addComponentInteractions",
779
834
  value: function addComponentInteractions(viewport, rect, componentGroup, component, worldWidth, worldDepth, worldHeight, bboxCenter, label) {
780
- var _this7 = this;
835
+ var _this8 = this;
781
836
  if (!this.Konva) return;
782
837
  var colors = this.generateComponentColor(component.id);
783
838
 
@@ -806,12 +861,12 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
806
861
  // CLICK EVENT
807
862
  rect.on('click', function () {
808
863
  if (!viewport.isDragging) {
809
- var _this7$sceneViewer;
864
+ var _this8$sceneViewer;
810
865
  console.log("\uD83C\uDFAF Component clicked: ".concat(component.name));
811
866
 
812
867
  // Use centralPlant API to select component
813
- if ((_this7$sceneViewer = _this7.sceneViewer) !== null && _this7$sceneViewer !== void 0 && _this7$sceneViewer.centralPlant && component.uuid) {
814
- _this7.sceneViewer.centralPlant.selectComponent(component.uuid);
868
+ if ((_this8$sceneViewer = _this8.sceneViewer) !== null && _this8$sceneViewer !== void 0 && _this8$sceneViewer.centralPlant && component.uuid) {
869
+ _this8.sceneViewer.centralPlant.selectComponent(component.uuid);
815
870
  }
816
871
  }
817
872
  });
@@ -841,7 +896,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
841
896
  var worldOriginY = stageHeight / 2;
842
897
 
843
898
  // Snap to grid
844
- var snappedPos = _this7.snapScreenToGrid(viewport, currentPos.x, currentPos.y, scale, worldOriginX, worldOriginY);
899
+ var snappedPos = _this8.snapScreenToGrid(viewport, currentPos.x, currentPos.y, scale, worldOriginX, worldOriginY);
845
900
  componentGroup.position(snappedPos);
846
901
  viewport.componentLayer.batchDraw();
847
902
  });
@@ -849,7 +904,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
849
904
  // DRAG END
850
905
  componentGroup.on('dragend', function () {
851
906
  setTimeout(function () {
852
- var _this7$sceneViewer2;
907
+ var _this8$sceneViewer2;
853
908
  viewport.isDragging = false;
854
909
  var finalPos = componentGroup.position();
855
910
  var stageWidth = viewport.stage.width();
@@ -859,17 +914,17 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
859
914
  var worldOriginY = stageHeight / 2;
860
915
 
861
916
  // Convert screen to world coordinates
862
- var worldCoords = _this7.screenToWorldCoords(viewport, finalPos.x, finalPos.y, scale, worldOriginX, worldOriginY);
917
+ var worldCoords = _this8.screenToWorldCoords(viewport, finalPos.x, finalPos.y, scale, worldOriginX, worldOriginY);
863
918
 
864
919
  // Calculate new position: delta from old bbox center to new bbox center
865
920
  var currentPos = component.position;
866
- var newPosition = _this7.worldCoordsToObjectPosition(viewport, worldCoords, currentPos, bboxCenter);
921
+ var newPosition = _this8.worldCoordsToObjectPosition(viewport, worldCoords, currentPos, bboxCenter);
867
922
 
868
923
  // Apply translation via centralPlant API
869
924
  var deltaX = newPosition.x - currentPos.x;
870
925
  var deltaY = newPosition.y - currentPos.y;
871
926
  var deltaZ = newPosition.z - currentPos.z;
872
- if ((_this7$sceneViewer2 = _this7.sceneViewer) !== null && _this7$sceneViewer2 !== void 0 && _this7$sceneViewer2.centralPlant && component.uuid) {
927
+ if ((_this8$sceneViewer2 = _this8.sceneViewer) !== null && _this8$sceneViewer2 !== void 0 && _this8$sceneViewer2.centralPlant && component.uuid) {
873
928
  var success = true;
874
929
 
875
930
  // Suppress per-axis path updates so the pathfinder only runs once,
@@ -877,32 +932,32 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
877
932
  // Running updatePaths() after each individual axis (the default
878
933
  // translateComponent behaviour) produces intermediate wrong results on
879
934
  // the first drag because no cached fingerprint exists yet to skip them.
880
- var wasAutoUpdate = _this7.sceneViewer.shouldUpdatePaths;
881
- _this7.sceneViewer.shouldUpdatePaths = false;
935
+ var wasAutoUpdate = _this8.sceneViewer.shouldUpdatePaths;
936
+ _this8.sceneViewer.shouldUpdatePaths = false;
882
937
  try {
883
938
  if (Math.abs(deltaX) > 0.01) {
884
- success = success && _this7.sceneViewer.centralPlant.translate(component.uuid, 'x', deltaX);
939
+ success = success && _this8.sceneViewer.centralPlant.translate(component.uuid, 'x', deltaX);
885
940
  }
886
941
  if (Math.abs(deltaY) > 0.01) {
887
- success = success && _this7.sceneViewer.centralPlant.translate(component.uuid, 'y', deltaY);
942
+ success = success && _this8.sceneViewer.centralPlant.translate(component.uuid, 'y', deltaY);
888
943
  }
889
944
  if (Math.abs(deltaZ) > 0.01) {
890
- success = success && _this7.sceneViewer.centralPlant.translate(component.uuid, 'z', deltaZ);
945
+ success = success && _this8.sceneViewer.centralPlant.translate(component.uuid, 'z', deltaZ);
891
946
  }
892
947
  } finally {
893
- _this7.sceneViewer.shouldUpdatePaths = wasAutoUpdate;
948
+ _this8.sceneViewer.shouldUpdatePaths = wasAutoUpdate;
894
949
  }
895
- if (!success && _this7.dragStartPosition) {
950
+ if (!success && _this8.dragStartPosition) {
896
951
  console.warn('⚠️ Failed to translate component, reverting position');
897
- componentGroup.position(_this7.dragStartPosition);
952
+ componentGroup.position(_this8.dragStartPosition);
898
953
  } else {
899
954
  console.log("\u2705 Component ".concat(component.name, " translated successfully in 2D viewport"));
900
955
 
901
956
  // Single path update with the final combined position
902
- if (wasAutoUpdate && _this7.sceneViewer) {
957
+ if (wasAutoUpdate && _this8.sceneViewer) {
903
958
  console.log('🔄 Auto-updating paths after 2D viewport translation...');
904
959
  try {
905
- _this7.sceneViewer.updatePaths();
960
+ _this8.sceneViewer.updatePaths();
906
961
  console.log('✅ Paths auto-updated successfully from 2D viewport');
907
962
  } catch (error) {
908
963
  console.error('❌ Error auto-updating paths from 2D viewport:', error);
@@ -1034,10 +1089,10 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
1034
1089
  }
1035
1090
  var components = [];
1036
1091
  this.sceneViewer.scene.traverse(function (object) {
1037
- var _object$userData3, _object$userData4;
1092
+ var _object$userData, _object$userData2;
1038
1093
  // Only match the ROOT component object — must have both objectType:'component'
1039
1094
  // AND libraryId (inner GLB mesh nodes don't have libraryId)
1040
- if (((_object$userData3 = object.userData) === null || _object$userData3 === void 0 ? void 0 : _object$userData3.objectType) === 'component' && (_object$userData4 = object.userData) !== null && _object$userData4 !== void 0 && _object$userData4.libraryId) {
1095
+ if (((_object$userData = object.userData) === null || _object$userData === void 0 ? void 0 : _object$userData.objectType) === 'component' && (_object$userData2 = object.userData) !== null && _object$userData2 !== void 0 && _object$userData2.libraryId) {
1041
1096
  components.push(object);
1042
1097
  }
1043
1098
  });
@@ -1155,28 +1210,39 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
1155
1210
  }, {
1156
1211
  key: "refresh",
1157
1212
  value: function refresh() {
1158
- var _this8 = this;
1213
+ var _this9 = this;
1159
1214
  var key = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
1215
+ // Collect viewport keys to refresh; null means "all viewports"
1216
+ if (key) {
1217
+ this._pendingRefreshKeys.add(key);
1218
+ } else {
1219
+ // null key means refresh all — mark with special sentinel
1220
+ this._pendingRefreshKeys.add('__ALL__');
1221
+ }
1222
+
1223
+ // If already scheduled, the rAF callback will process our newly-added key
1160
1224
  if (this._refreshPending) return;
1161
1225
  this._refreshPending = true;
1162
1226
  requestAnimationFrame(function () {
1163
- _this8._refreshPending = false;
1227
+ _this9._refreshPending = false;
1228
+
1164
1229
  // Clear per-cycle caches so each component is measured/traversed once per paint
1165
- _this8._bboxCache.clear();
1166
- _this8._componentListCache = null;
1167
- if (key) {
1168
- var viewport = _this8.viewports.get(key);
1169
- if (viewport && viewport.isReady) {
1170
- _this8.renderComponents(viewport);
1171
- }
1172
- } else {
1173
- var _iterator = _createForOfIteratorHelper(_this8.viewports.values()),
1230
+ _this9._bboxCache.clear();
1231
+ _this9._componentListCache = null;
1232
+
1233
+ // Check if we need to refresh all viewports
1234
+ var refreshAll = _this9._pendingRefreshKeys.has('__ALL__');
1235
+ var keysToRefresh = new Set(_this9._pendingRefreshKeys);
1236
+ _this9._pendingRefreshKeys.clear();
1237
+ if (refreshAll) {
1238
+ // Refresh all viewports
1239
+ var _iterator = _createForOfIteratorHelper(_this9.viewports.values()),
1174
1240
  _step;
1175
1241
  try {
1176
1242
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
1177
- var _viewport = _step.value;
1178
- if (_viewport.isReady) {
1179
- _this8.renderComponents(_viewport);
1243
+ var viewport = _step.value;
1244
+ if (viewport.isReady) {
1245
+ _this9.renderComponents(viewport);
1180
1246
  }
1181
1247
  }
1182
1248
  } catch (err) {
@@ -1184,6 +1250,23 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
1184
1250
  } finally {
1185
1251
  _iterator.f();
1186
1252
  }
1253
+ } else {
1254
+ // Refresh only the specific viewports that were requested
1255
+ var _iterator2 = _createForOfIteratorHelper(keysToRefresh),
1256
+ _step2;
1257
+ try {
1258
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
1259
+ var viewportKey = _step2.value;
1260
+ var _viewport = _this9.viewports.get(viewportKey);
1261
+ if (_viewport && _viewport.isReady) {
1262
+ _this9.renderComponents(_viewport);
1263
+ }
1264
+ }
1265
+ } catch (err) {
1266
+ _iterator2.e(err);
1267
+ } finally {
1268
+ _iterator2.f();
1269
+ }
1187
1270
  }
1188
1271
  });
1189
1272
  }
@@ -1204,20 +1287,20 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
1204
1287
  }
1205
1288
 
1206
1289
  // Dispose all viewport instances
1207
- var _iterator2 = _createForOfIteratorHelper(this.viewports.entries()),
1208
- _step2;
1290
+ var _iterator3 = _createForOfIteratorHelper(this.viewports.entries()),
1291
+ _step3;
1209
1292
  try {
1210
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
1211
- var _step2$value = _slicedToArray(_step2.value, 2),
1212
- key = _step2$value[0],
1213
- viewport = _step2$value[1];
1293
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
1294
+ var _step3$value = _slicedToArray(_step3.value, 2),
1295
+ key = _step3$value[0],
1296
+ viewport = _step3$value[1];
1214
1297
  console.log("\uD83D\uDDD1\uFE0F Disposing viewport: ".concat(key));
1215
1298
  viewport.dispose();
1216
1299
  }
1217
1300
  } catch (err) {
1218
- _iterator2.e(err);
1301
+ _iterator3.e(err);
1219
1302
  } finally {
1220
- _iterator2.f();
1303
+ _iterator3.f();
1221
1304
  }
1222
1305
  this.viewports.clear();
1223
1306
  this.Konva = null;
@@ -1228,4 +1311,4 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
1228
1311
  }]);
1229
1312
  }(BaseDisposable);
1230
1313
 
1231
- export { Viewport2DManager };
1314
+ export { Viewport2DManager, cacheBasePosition };
@@ -95,7 +95,12 @@ function computeFilteredBoundingBox(object) {
95
95
 
96
96
  // Build a Set for O(1) lookups
97
97
  var excludeSet = new Set(excludeTypes);
98
- object.updateWorldMatrix(false, true);
98
+
99
+ // Force matrix updates to ensure world-space coordinates are accurate.
100
+ // Using force=true ensures matrices are updated even if matrixWorldNeedsUpdate is false,
101
+ // which can happen after positioning a model before the render loop runs.
102
+ object.updateMatrix();
103
+ object.updateMatrixWorld(true);
99
104
  object.traverse(function (child) {
100
105
  // Only process nodes with geometry (Mesh, SkinnedMesh, etc.)
101
106
  if (!child.geometry) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2112-lab/central-plant",
3
- "version": "0.3.12",
3
+ "version": "0.3.14",
4
4
  "description": "Utility modules for the Central Plant Application",
5
5
  "main": "dist/bundle/index.js",
6
6
  "module": "dist/esm/src/index.js",