@2112-lab/central-plant 0.3.11 → 0.3.13

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.
@@ -99,6 +99,9 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
99
99
  // stacking up independent renderComponents() runs
100
100
  _this2._refreshPending = false;
101
101
 
102
+ // Set of viewport keys pending refresh (collects keys across multiple refresh() calls in same frame)
103
+ _this2._pendingRefreshKeys = new Set();
104
+
102
105
  // Event listener reference for cleanup
103
106
  _this2._objectTransformedListener = null;
104
107
  console.log('🔲 Viewport2DManager initialized (multi-instance support)');
@@ -188,8 +191,10 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
188
191
  viewport._instanceKey = key;
189
192
  this.viewports.set(key, viewport);
190
193
 
191
- // Initialize the stage for this viewport
192
- this.initializeStage(viewport);
194
+ // Initialize the stage for this viewport (waits for DOM layout to settle)
195
+ _context.n = 4;
196
+ return this.initializeStage(viewport);
197
+ case 4:
193
198
  return _context.a(2, viewport.isReady);
194
199
  }
195
200
  }, _callee, this);
@@ -283,50 +288,67 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
283
288
  /**
284
289
  * Initialize Konva stage for a specific viewport instance
285
290
  * @param {Viewport2DInstance} viewport - The viewport instance to initialize
291
+ * @returns {Promise<void>} Resolves when viewport is fully ready
286
292
  */
287
293
  )
288
294
  }, {
289
295
  key: "initializeStage",
290
296
  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);
297
+ var _this4 = this;
298
+ return new Promise(function (resolve) {
299
+ if (!_this4.Konva || !viewport.container) {
300
+ console.error('❌ Cannot initialize stage: Konva or container missing');
301
+ resolve();
302
+ return;
303
+ }
322
304
 
323
- // Draw initial content
324
- this.drawGrid(viewport);
325
- this.renderComponents(viewport);
305
+ // Get container dimensions
306
+ var rect = viewport.container.getBoundingClientRect();
307
+ var width = rect.width || 800;
308
+ var height = rect.height || 600;
309
+ console.log("\uD83D\uDCD0 Initializing Konva stage (".concat(viewport.viewType, "): ").concat(width, "x").concat(height));
310
+
311
+ // Create Konva stage for this viewport
312
+ viewport.stage = new _this4.Konva.Stage({
313
+ container: viewport.container,
314
+ width: width,
315
+ height: height
316
+ });
326
317
 
327
- // Mark as ready
328
- viewport.isReady = true;
329
- console.log("\u2705 Viewport2DManager stage initialized (".concat(viewport.viewType, ")"));
318
+ // Create separate layers for grid and components
319
+ viewport.gridLayer = new _this4.Konva.Layer();
320
+ viewport.componentLayer = new _this4.Konva.Layer();
321
+
322
+ // Add layers to stage in order (grid first, then components)
323
+ viewport.stage.add(viewport.gridLayer);
324
+ viewport.stage.add(viewport.componentLayer);
325
+
326
+ // Setup resize handling
327
+ _this4.setupResizeListener(viewport);
328
+
329
+ // Setup zoom and pan handlers
330
+ _this4.setupZoomAndPanHandlers(viewport);
331
+
332
+ // Draw initial grid (lightweight, safe to do immediately)
333
+ _this4.drawGrid(viewport);
334
+
335
+ // Defer component rendering to next frame to ensure DOM layout is finalized.
336
+ // Without this, getBoundingClientRect() may return stale/zero dimensions
337
+ // if the container hasn't been fully laid out by CSS yet.
338
+ requestAnimationFrame(function () {
339
+ // Re-check container dimensions after layout settles
340
+ var finalRect = viewport.container.getBoundingClientRect();
341
+ if (finalRect.width > 0 && finalRect.height > 0) {
342
+ viewport.stage.width(finalRect.width);
343
+ viewport.stage.height(finalRect.height);
344
+ _this4.drawGrid(viewport);
345
+ }
346
+ _this4.renderComponents(viewport);
347
+ viewport.isReady = true;
348
+ console.log("\u2705 Viewport2DManager stage initialized (".concat(viewport.viewType, ")"));
349
+ resolve();
350
+ });
351
+ });
330
352
  }
331
353
 
332
354
  /**
@@ -336,12 +358,12 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
336
358
  }, {
337
359
  key: "setupResizeListener",
338
360
  value: function setupResizeListener(viewport) {
339
- var _this4 = this;
361
+ var _this5 = this;
340
362
  if (typeof window === 'undefined' || !window.ResizeObserver || !viewport.container) {
341
363
  return;
342
364
  }
343
365
  viewport._resizeObserver = new ResizeObserver(function () {
344
- _this4.resizeStage(viewport);
366
+ _this5.resizeStage(viewport);
345
367
  });
346
368
  viewport._resizeObserver.observe(viewport.container);
347
369
  }
@@ -376,6 +398,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
376
398
  }, {
377
399
  key: "setupZoomAndPanHandlers",
378
400
  value: function setupZoomAndPanHandlers(viewport) {
401
+ var _this6 = this;
379
402
  if (!viewport.stage) return;
380
403
 
381
404
  // Mouse wheel zoom
@@ -401,6 +424,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
401
424
  y: pointer.y - mousePointTo.y * clampedScale
402
425
  };
403
426
  viewport.stage.position(newPos);
427
+ _this6.drawGrid(viewport);
404
428
  viewport.stage.batchDraw();
405
429
  });
406
430
 
@@ -420,6 +444,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
420
444
  viewport.stage.x(viewport.stage.x() + dx);
421
445
  viewport.stage.y(viewport.stage.y() + dy);
422
446
  viewport.lastPanPoint = pos;
447
+ _this6.drawGrid(viewport);
423
448
  viewport.stage.batchDraw();
424
449
  });
425
450
  viewport.stage.on('mouseup', function () {
@@ -444,33 +469,40 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
444
469
  var width = viewport.stage.width();
445
470
  var height = viewport.stage.height();
446
471
  var gridSize = viewport.PIXELS_PER_UNIT;
447
-
448
- // Center of the viewport represents world origin (0,0,0)
449
- var centerX = width / 2;
450
- var centerY = height / 2;
472
+ var stageX = viewport.stage.x();
473
+ var stageY = viewport.stage.y();
474
+ var scale = viewport.stage.scaleX();
475
+
476
+ // World origin sits at (width/2, height/2) in stage-local coordinates
477
+ var originX = width / 2;
478
+ var originY = height / 2;
479
+
480
+ // Compute the visible area in stage-local coordinates, with 1-cell padding
481
+ var visLeft = -stageX / scale - gridSize;
482
+ var visTop = -stageY / scale - gridSize;
483
+ var visRight = (width - stageX) / scale + gridSize;
484
+ var visBottom = (height - stageY) / scale + gridSize;
451
485
  var gridGroup = new this.Konva.Group();
452
-
453
- // Draw vertical lines centered at origin
454
- var startX = centerX % gridSize - gridSize;
455
486
  var gridColor = '#dddddd';
456
- for (var x = startX; x < width; x += gridSize) {
457
- var distFromCenter = Math.abs(x - centerX);
458
- var isOriginLine = distFromCenter < 1;
487
+
488
+ // Draw vertical lines start at the first grid column left of the visible area
489
+ var firstX = Math.floor((visLeft - originX) / gridSize) * gridSize + originX;
490
+ for (var x = firstX; x <= visRight; x += gridSize) {
491
+ var isOriginLine = Math.abs(x - originX) < 0.5;
459
492
  gridGroup.add(new this.Konva.Line({
460
- points: [x, 0, x, height],
493
+ points: [x, visTop, x, visBottom],
461
494
  stroke: gridColor,
462
495
  strokeWidth: isOriginLine ? 2 : 1,
463
496
  listening: false
464
497
  }));
465
498
  }
466
499
 
467
- // Draw horizontal lines centered at origin
468
- var startY = centerY % gridSize - gridSize;
469
- for (var y = startY; y < height; y += gridSize) {
470
- var _distFromCenter = Math.abs(y - centerY);
471
- var _isOriginLine = _distFromCenter < 1;
500
+ // Draw horizontal lines start at the first grid row above the visible area
501
+ var firstY = Math.floor((visTop - originY) / gridSize) * gridSize + originY;
502
+ for (var y = firstY; y <= visBottom; y += gridSize) {
503
+ var _isOriginLine = Math.abs(y - originY) < 0.5;
472
504
  gridGroup.add(new this.Konva.Line({
473
- points: [0, y, width, y],
505
+ points: [visLeft, y, visRight, y],
474
506
  stroke: gridColor,
475
507
  strokeWidth: _isOriginLine ? 2 : 1,
476
508
  listening: false
@@ -480,14 +512,14 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
480
512
  // Draw center crosshair at world origin
481
513
  var crosshairSize = gridSize;
482
514
  gridGroup.add(new this.Konva.Line({
483
- points: [centerX, centerY - crosshairSize, centerX, centerY + crosshairSize],
515
+ points: [originX, originY - crosshairSize, originX, originY + crosshairSize],
484
516
  stroke: '#333',
485
517
  strokeWidth: 1,
486
518
  listening: false,
487
519
  dash: [5, 5]
488
520
  }));
489
521
  gridGroup.add(new this.Konva.Line({
490
- points: [centerX - crosshairSize, centerY, centerX + crosshairSize, centerY],
522
+ points: [originX - crosshairSize, originY, originX + crosshairSize, originY],
491
523
  stroke: '#333',
492
524
  strokeWidth: 1,
493
525
  listening: false,
@@ -504,7 +536,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
504
536
  }, {
505
537
  key: "renderComponents",
506
538
  value: function renderComponents(viewport) {
507
- var _this5 = this;
539
+ var _this7 = this;
508
540
  if (!viewport.componentLayer || !viewport.stage || !this.sceneViewer) return;
509
541
  viewport.componentLayer.destroyChildren();
510
542
 
@@ -515,19 +547,18 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
515
547
  return;
516
548
  }
517
549
 
518
- // Only log on significant changes
519
- if (components.length !== viewport._lastComponentCount) {
520
- console.log("\uD83D\uDCE6 Rendering ".concat(components.length, " components in 2D view (").concat(viewport.viewType, ")"));
521
- viewport._lastComponentCount = components.length;
522
- }
550
+ // Track render count for debugging
551
+ if (viewport._renderCount === undefined) viewport._renderCount = 0;
552
+ viewport._renderCount++;
523
553
  var width = viewport.stage.width();
524
554
  var height = viewport.stage.height();
525
555
  var centerX = width / 2;
526
556
  var centerY = height / 2;
527
557
  var scale = viewport.PIXELS_PER_UNIT;
558
+ 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, ")"));
528
559
  components.forEach(function (component) {
529
560
  try {
530
- _this5.renderComponent(viewport, component, centerX, centerY, scale);
561
+ _this7.renderComponent(viewport, component, centerX, centerY, scale);
531
562
  } catch (err) {
532
563
  console.warn('⚠️ Error rendering component in 2D:', component.name, err);
533
564
  }
@@ -558,6 +589,11 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
558
589
  var screenX = centerX + posX * scale;
559
590
  var screenY = centerY - posY * scale; // Flip Y for screen coords
560
591
 
592
+ // Debug: Log ALL component positions on first render only
593
+ if (viewport._renderCount === 1) {
594
+ 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)));
595
+ }
596
+
561
597
  // Generate unique color for this component
562
598
  var colors = this.generateComponentColor(component.id);
563
599
 
@@ -587,18 +623,19 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
587
623
  draggable: false
588
624
  });
589
625
 
590
- // Add label
626
+ // Add label (hidden by default; shown on hover)
591
627
  var label = new this.Konva.Text({
592
628
  x: -rectWidth / 2,
593
629
  y: -rectHeight / 2 - 20,
594
630
  text: component.name || 'Component',
595
631
  fontSize: 12,
596
632
  fill: '#333',
597
- listening: false
633
+ listening: false,
634
+ visible: false
598
635
  });
599
636
 
600
637
  // Add mouse event handlers
601
- this.addComponentInteractions(viewport, rect, componentGroup, component, worldWidth, worldDepth, worldHeight, bboxCenter);
638
+ this.addComponentInteractions(viewport, rect, componentGroup, component, worldWidth, worldDepth, worldHeight, bboxCenter, label);
602
639
  componentGroup.add(rect);
603
640
  componentGroup.add(label);
604
641
  viewport.componentLayer.add(componentGroup);
@@ -635,7 +672,14 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
635
672
  if (this._bboxCache.has(object.uuid)) {
636
673
  return this._bboxCache.get(object.uuid);
637
674
  }
638
- var box = computeFilteredBoundingBox(object, []);
675
+
676
+ // Force matrix updates before computing bbox to ensure world-space accuracy
677
+ object.updateMatrix();
678
+ object.updateMatrixWorld(true);
679
+
680
+ // Exclude io-devices and connectors to match the stored worldBoundingBox
681
+ // computed in modelManager.replaceWithGLBModels()
682
+ var box = computeFilteredBoundingBox(object, ['io-device', 'connector']);
639
683
  var result;
640
684
  if (box.isEmpty()) {
641
685
  // Object has no geometry; fall back to a point at world position
@@ -765,8 +809,8 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
765
809
  */
766
810
  }, {
767
811
  key: "addComponentInteractions",
768
- value: function addComponentInteractions(viewport, rect, componentGroup, component, worldWidth, worldDepth, worldHeight, bboxCenter) {
769
- var _this6 = this;
812
+ value: function addComponentInteractions(viewport, rect, componentGroup, component, worldWidth, worldDepth, worldHeight, bboxCenter, label) {
813
+ var _this8 = this;
770
814
  if (!this.Konva) return;
771
815
  var colors = this.generateComponentColor(component.id);
772
816
 
@@ -777,6 +821,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
777
821
  rect.stroke('#007bff');
778
822
  rect.strokeWidth(3);
779
823
  viewport.stage.container().style.cursor = 'grab';
824
+ if (label) label.visible(true);
780
825
  viewport.componentLayer.batchDraw();
781
826
  }
782
827
  });
@@ -786,6 +831,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
786
831
  rect.stroke(colors.stroke);
787
832
  rect.strokeWidth(2);
788
833
  viewport.stage.container().style.cursor = 'default';
834
+ if (label) label.visible(false);
789
835
  viewport.componentLayer.batchDraw();
790
836
  }
791
837
  });
@@ -793,12 +839,12 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
793
839
  // CLICK EVENT
794
840
  rect.on('click', function () {
795
841
  if (!viewport.isDragging) {
796
- var _this6$sceneViewer;
842
+ var _this8$sceneViewer;
797
843
  console.log("\uD83C\uDFAF Component clicked: ".concat(component.name));
798
844
 
799
845
  // Use centralPlant API to select component
800
- if ((_this6$sceneViewer = _this6.sceneViewer) !== null && _this6$sceneViewer !== void 0 && _this6$sceneViewer.centralPlant && component.uuid) {
801
- _this6.sceneViewer.centralPlant.selectComponent(component.uuid);
846
+ if ((_this8$sceneViewer = _this8.sceneViewer) !== null && _this8$sceneViewer !== void 0 && _this8$sceneViewer.centralPlant && component.uuid) {
847
+ _this8.sceneViewer.centralPlant.selectComponent(component.uuid);
802
848
  }
803
849
  }
804
850
  });
@@ -828,7 +874,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
828
874
  var worldOriginY = stageHeight / 2;
829
875
 
830
876
  // Snap to grid
831
- var snappedPos = _this6.snapScreenToGrid(viewport, currentPos.x, currentPos.y, scale, worldOriginX, worldOriginY);
877
+ var snappedPos = _this8.snapScreenToGrid(viewport, currentPos.x, currentPos.y, scale, worldOriginX, worldOriginY);
832
878
  componentGroup.position(snappedPos);
833
879
  viewport.componentLayer.batchDraw();
834
880
  });
@@ -836,7 +882,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
836
882
  // DRAG END
837
883
  componentGroup.on('dragend', function () {
838
884
  setTimeout(function () {
839
- var _this6$sceneViewer2;
885
+ var _this8$sceneViewer2;
840
886
  viewport.isDragging = false;
841
887
  var finalPos = componentGroup.position();
842
888
  var stageWidth = viewport.stage.width();
@@ -846,17 +892,17 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
846
892
  var worldOriginY = stageHeight / 2;
847
893
 
848
894
  // Convert screen to world coordinates
849
- var worldCoords = _this6.screenToWorldCoords(viewport, finalPos.x, finalPos.y, scale, worldOriginX, worldOriginY);
895
+ var worldCoords = _this8.screenToWorldCoords(viewport, finalPos.x, finalPos.y, scale, worldOriginX, worldOriginY);
850
896
 
851
897
  // Calculate new position: delta from old bbox center to new bbox center
852
898
  var currentPos = component.position;
853
- var newPosition = _this6.worldCoordsToObjectPosition(viewport, worldCoords, currentPos, bboxCenter);
899
+ var newPosition = _this8.worldCoordsToObjectPosition(viewport, worldCoords, currentPos, bboxCenter);
854
900
 
855
901
  // Apply translation via centralPlant API
856
902
  var deltaX = newPosition.x - currentPos.x;
857
903
  var deltaY = newPosition.y - currentPos.y;
858
904
  var deltaZ = newPosition.z - currentPos.z;
859
- if ((_this6$sceneViewer2 = _this6.sceneViewer) !== null && _this6$sceneViewer2 !== void 0 && _this6$sceneViewer2.centralPlant && component.uuid) {
905
+ if ((_this8$sceneViewer2 = _this8.sceneViewer) !== null && _this8$sceneViewer2 !== void 0 && _this8$sceneViewer2.centralPlant && component.uuid) {
860
906
  var success = true;
861
907
 
862
908
  // Suppress per-axis path updates so the pathfinder only runs once,
@@ -864,32 +910,32 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
864
910
  // Running updatePaths() after each individual axis (the default
865
911
  // translateComponent behaviour) produces intermediate wrong results on
866
912
  // the first drag because no cached fingerprint exists yet to skip them.
867
- var wasAutoUpdate = _this6.sceneViewer.shouldUpdatePaths;
868
- _this6.sceneViewer.shouldUpdatePaths = false;
913
+ var wasAutoUpdate = _this8.sceneViewer.shouldUpdatePaths;
914
+ _this8.sceneViewer.shouldUpdatePaths = false;
869
915
  try {
870
916
  if (Math.abs(deltaX) > 0.01) {
871
- success = success && _this6.sceneViewer.centralPlant.translate(component.uuid, 'x', deltaX);
917
+ success = success && _this8.sceneViewer.centralPlant.translate(component.uuid, 'x', deltaX);
872
918
  }
873
919
  if (Math.abs(deltaY) > 0.01) {
874
- success = success && _this6.sceneViewer.centralPlant.translate(component.uuid, 'y', deltaY);
920
+ success = success && _this8.sceneViewer.centralPlant.translate(component.uuid, 'y', deltaY);
875
921
  }
876
922
  if (Math.abs(deltaZ) > 0.01) {
877
- success = success && _this6.sceneViewer.centralPlant.translate(component.uuid, 'z', deltaZ);
923
+ success = success && _this8.sceneViewer.centralPlant.translate(component.uuid, 'z', deltaZ);
878
924
  }
879
925
  } finally {
880
- _this6.sceneViewer.shouldUpdatePaths = wasAutoUpdate;
926
+ _this8.sceneViewer.shouldUpdatePaths = wasAutoUpdate;
881
927
  }
882
- if (!success && _this6.dragStartPosition) {
928
+ if (!success && _this8.dragStartPosition) {
883
929
  console.warn('⚠️ Failed to translate component, reverting position');
884
- componentGroup.position(_this6.dragStartPosition);
930
+ componentGroup.position(_this8.dragStartPosition);
885
931
  } else {
886
932
  console.log("\u2705 Component ".concat(component.name, " translated successfully in 2D viewport"));
887
933
 
888
934
  // Single path update with the final combined position
889
- if (wasAutoUpdate && _this6.sceneViewer) {
935
+ if (wasAutoUpdate && _this8.sceneViewer) {
890
936
  console.log('🔄 Auto-updating paths after 2D viewport translation...');
891
937
  try {
892
- _this6.sceneViewer.updatePaths();
938
+ _this8.sceneViewer.updatePaths();
893
939
  console.log('✅ Paths auto-updated successfully from 2D viewport');
894
940
  } catch (error) {
895
941
  console.error('❌ Error auto-updating paths from 2D viewport:', error);
@@ -1142,28 +1188,39 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
1142
1188
  }, {
1143
1189
  key: "refresh",
1144
1190
  value: function refresh() {
1145
- var _this7 = this;
1191
+ var _this9 = this;
1146
1192
  var key = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
1193
+ // Collect viewport keys to refresh; null means "all viewports"
1194
+ if (key) {
1195
+ this._pendingRefreshKeys.add(key);
1196
+ } else {
1197
+ // null key means refresh all — mark with special sentinel
1198
+ this._pendingRefreshKeys.add('__ALL__');
1199
+ }
1200
+
1201
+ // If already scheduled, the rAF callback will process our newly-added key
1147
1202
  if (this._refreshPending) return;
1148
1203
  this._refreshPending = true;
1149
1204
  requestAnimationFrame(function () {
1150
- _this7._refreshPending = false;
1205
+ _this9._refreshPending = false;
1206
+
1151
1207
  // Clear per-cycle caches so each component is measured/traversed once per paint
1152
- _this7._bboxCache.clear();
1153
- _this7._componentListCache = null;
1154
- if (key) {
1155
- var viewport = _this7.viewports.get(key);
1156
- if (viewport && viewport.isReady) {
1157
- _this7.renderComponents(viewport);
1158
- }
1159
- } else {
1160
- var _iterator = _createForOfIteratorHelper(_this7.viewports.values()),
1208
+ _this9._bboxCache.clear();
1209
+ _this9._componentListCache = null;
1210
+
1211
+ // Check if we need to refresh all viewports
1212
+ var refreshAll = _this9._pendingRefreshKeys.has('__ALL__');
1213
+ var keysToRefresh = new Set(_this9._pendingRefreshKeys);
1214
+ _this9._pendingRefreshKeys.clear();
1215
+ if (refreshAll) {
1216
+ // Refresh all viewports
1217
+ var _iterator = _createForOfIteratorHelper(_this9.viewports.values()),
1161
1218
  _step;
1162
1219
  try {
1163
1220
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
1164
- var _viewport = _step.value;
1165
- if (_viewport.isReady) {
1166
- _this7.renderComponents(_viewport);
1221
+ var viewport = _step.value;
1222
+ if (viewport.isReady) {
1223
+ _this9.renderComponents(viewport);
1167
1224
  }
1168
1225
  }
1169
1226
  } catch (err) {
@@ -1171,6 +1228,23 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
1171
1228
  } finally {
1172
1229
  _iterator.f();
1173
1230
  }
1231
+ } else {
1232
+ // Refresh only the specific viewports that were requested
1233
+ var _iterator2 = _createForOfIteratorHelper(keysToRefresh),
1234
+ _step2;
1235
+ try {
1236
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
1237
+ var viewportKey = _step2.value;
1238
+ var _viewport = _this9.viewports.get(viewportKey);
1239
+ if (_viewport && _viewport.isReady) {
1240
+ _this9.renderComponents(_viewport);
1241
+ }
1242
+ }
1243
+ } catch (err) {
1244
+ _iterator2.e(err);
1245
+ } finally {
1246
+ _iterator2.f();
1247
+ }
1174
1248
  }
1175
1249
  });
1176
1250
  }
@@ -1191,20 +1265,20 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
1191
1265
  }
1192
1266
 
1193
1267
  // Dispose all viewport instances
1194
- var _iterator2 = _createForOfIteratorHelper(this.viewports.entries()),
1195
- _step2;
1268
+ var _iterator3 = _createForOfIteratorHelper(this.viewports.entries()),
1269
+ _step3;
1196
1270
  try {
1197
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
1198
- var _step2$value = _slicedToArray(_step2.value, 2),
1199
- key = _step2$value[0],
1200
- viewport = _step2$value[1];
1271
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
1272
+ var _step3$value = _slicedToArray(_step3.value, 2),
1273
+ key = _step3$value[0],
1274
+ viewport = _step3$value[1];
1201
1275
  console.log("\uD83D\uDDD1\uFE0F Disposing viewport: ".concat(key));
1202
1276
  viewport.dispose();
1203
1277
  }
1204
1278
  } catch (err) {
1205
- _iterator2.e(err);
1279
+ _iterator3.e(err);
1206
1280
  } finally {
1207
- _iterator2.f();
1281
+ _iterator3.f();
1208
1282
  }
1209
1283
  this.viewports.clear();
1210
1284
  this.Konva = null;
@@ -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.11",
3
+ "version": "0.3.13",
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",