@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.
@@ -123,6 +123,9 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
123
123
  // stacking up independent renderComponents() runs
124
124
  _this2._refreshPending = false;
125
125
 
126
+ // Set of viewport keys pending refresh (collects keys across multiple refresh() calls in same frame)
127
+ _this2._pendingRefreshKeys = new Set();
128
+
126
129
  // Event listener reference for cleanup
127
130
  _this2._objectTransformedListener = null;
128
131
  console.log('🔲 Viewport2DManager initialized (multi-instance support)');
@@ -212,8 +215,10 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
212
215
  viewport._instanceKey = key;
213
216
  this.viewports.set(key, viewport);
214
217
 
215
- // Initialize the stage for this viewport
216
- this.initializeStage(viewport);
218
+ // Initialize the stage for this viewport (waits for DOM layout to settle)
219
+ _context.n = 4;
220
+ return this.initializeStage(viewport);
221
+ case 4:
217
222
  return _context.a(2, viewport.isReady);
218
223
  }
219
224
  }, _callee, this);
@@ -307,50 +312,67 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
307
312
  /**
308
313
  * Initialize Konva stage for a specific viewport instance
309
314
  * @param {Viewport2DInstance} viewport - The viewport instance to initialize
315
+ * @returns {Promise<void>} Resolves when viewport is fully ready
310
316
  */
311
317
  )
312
318
  }, {
313
319
  key: "initializeStage",
314
320
  value: function initializeStage(viewport) {
315
- if (!this.Konva || !viewport.container) {
316
- console.error('❌ Cannot initialize stage: Konva or container missing');
317
- return;
318
- }
319
-
320
- // Get container dimensions
321
- var rect = viewport.container.getBoundingClientRect();
322
- var width = rect.width || 800;
323
- var height = rect.height || 600;
324
- console.log("\uD83D\uDCD0 Initializing Konva stage (".concat(viewport.viewType, "): ").concat(width, "x").concat(height));
325
-
326
- // Create Konva stage for this viewport
327
- viewport.stage = new this.Konva.Stage({
328
- container: viewport.container,
329
- width: width,
330
- height: height
331
- });
332
-
333
- // Create separate layers for grid and components
334
- viewport.gridLayer = new this.Konva.Layer();
335
- viewport.componentLayer = new this.Konva.Layer();
336
-
337
- // Add layers to stage in order (grid first, then components)
338
- viewport.stage.add(viewport.gridLayer);
339
- viewport.stage.add(viewport.componentLayer);
340
-
341
- // Setup resize handling
342
- this.setupResizeListener(viewport);
343
-
344
- // Setup zoom and pan handlers
345
- this.setupZoomAndPanHandlers(viewport);
321
+ var _this4 = this;
322
+ return new Promise(function (resolve) {
323
+ if (!_this4.Konva || !viewport.container) {
324
+ console.error('❌ Cannot initialize stage: Konva or container missing');
325
+ resolve();
326
+ return;
327
+ }
346
328
 
347
- // Draw initial content
348
- this.drawGrid(viewport);
349
- this.renderComponents(viewport);
329
+ // Get container dimensions
330
+ var rect = viewport.container.getBoundingClientRect();
331
+ var width = rect.width || 800;
332
+ var height = rect.height || 600;
333
+ console.log("\uD83D\uDCD0 Initializing Konva stage (".concat(viewport.viewType, "): ").concat(width, "x").concat(height));
334
+
335
+ // Create Konva stage for this viewport
336
+ viewport.stage = new _this4.Konva.Stage({
337
+ container: viewport.container,
338
+ width: width,
339
+ height: height
340
+ });
350
341
 
351
- // Mark as ready
352
- viewport.isReady = true;
353
- console.log("\u2705 Viewport2DManager stage initialized (".concat(viewport.viewType, ")"));
342
+ // Create separate layers for grid and components
343
+ viewport.gridLayer = new _this4.Konva.Layer();
344
+ viewport.componentLayer = new _this4.Konva.Layer();
345
+
346
+ // Add layers to stage in order (grid first, then components)
347
+ viewport.stage.add(viewport.gridLayer);
348
+ viewport.stage.add(viewport.componentLayer);
349
+
350
+ // Setup resize handling
351
+ _this4.setupResizeListener(viewport);
352
+
353
+ // Setup zoom and pan handlers
354
+ _this4.setupZoomAndPanHandlers(viewport);
355
+
356
+ // Draw initial grid (lightweight, safe to do immediately)
357
+ _this4.drawGrid(viewport);
358
+
359
+ // Defer component rendering to next frame to ensure DOM layout is finalized.
360
+ // Without this, getBoundingClientRect() may return stale/zero dimensions
361
+ // if the container hasn't been fully laid out by CSS yet.
362
+ requestAnimationFrame(function () {
363
+ // Re-check container dimensions after layout settles
364
+ var finalRect = viewport.container.getBoundingClientRect();
365
+ if (finalRect.width > 0 && finalRect.height > 0) {
366
+ viewport.stage.width(finalRect.width);
367
+ viewport.stage.height(finalRect.height);
368
+ _this4.drawGrid(viewport);
369
+ }
370
+ _this4.renderComponents(viewport);
371
+ viewport.isReady = true;
372
+ console.log("\u2705 Viewport2DManager stage initialized (".concat(viewport.viewType, ")"));
373
+ resolve();
374
+ });
375
+ });
354
376
  }
355
377
 
356
378
  /**
@@ -360,12 +382,12 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
360
382
  }, {
361
383
  key: "setupResizeListener",
362
384
  value: function setupResizeListener(viewport) {
363
- var _this4 = this;
385
+ var _this5 = this;
364
386
  if (typeof window === 'undefined' || !window.ResizeObserver || !viewport.container) {
365
387
  return;
366
388
  }
367
389
  viewport._resizeObserver = new ResizeObserver(function () {
368
- _this4.resizeStage(viewport);
390
+ _this5.resizeStage(viewport);
369
391
  });
370
392
  viewport._resizeObserver.observe(viewport.container);
371
393
  }
@@ -400,6 +422,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
400
422
  }, {
401
423
  key: "setupZoomAndPanHandlers",
402
424
  value: function setupZoomAndPanHandlers(viewport) {
425
+ var _this6 = this;
403
426
  if (!viewport.stage) return;
404
427
 
405
428
  // Mouse wheel zoom
@@ -425,6 +448,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
425
448
  y: pointer.y - mousePointTo.y * clampedScale
426
449
  };
427
450
  viewport.stage.position(newPos);
451
+ _this6.drawGrid(viewport);
428
452
  viewport.stage.batchDraw();
429
453
  });
430
454
 
@@ -444,6 +468,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
444
468
  viewport.stage.x(viewport.stage.x() + dx);
445
469
  viewport.stage.y(viewport.stage.y() + dy);
446
470
  viewport.lastPanPoint = pos;
471
+ _this6.drawGrid(viewport);
447
472
  viewport.stage.batchDraw();
448
473
  });
449
474
  viewport.stage.on('mouseup', function () {
@@ -468,33 +493,40 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
468
493
  var width = viewport.stage.width();
469
494
  var height = viewport.stage.height();
470
495
  var gridSize = viewport.PIXELS_PER_UNIT;
471
-
472
- // Center of the viewport represents world origin (0,0,0)
473
- var centerX = width / 2;
474
- var centerY = height / 2;
496
+ var stageX = viewport.stage.x();
497
+ var stageY = viewport.stage.y();
498
+ var scale = viewport.stage.scaleX();
499
+
500
+ // World origin sits at (width/2, height/2) in stage-local coordinates
501
+ var originX = width / 2;
502
+ var originY = height / 2;
503
+
504
+ // Compute the visible area in stage-local coordinates, with 1-cell padding
505
+ var visLeft = -stageX / scale - gridSize;
506
+ var visTop = -stageY / scale - gridSize;
507
+ var visRight = (width - stageX) / scale + gridSize;
508
+ var visBottom = (height - stageY) / scale + gridSize;
475
509
  var gridGroup = new this.Konva.Group();
476
-
477
- // Draw vertical lines centered at origin
478
- var startX = centerX % gridSize - gridSize;
479
510
  var gridColor = '#dddddd';
480
- for (var x = startX; x < width; x += gridSize) {
481
- var distFromCenter = Math.abs(x - centerX);
482
- var isOriginLine = distFromCenter < 1;
511
+
512
+ // Draw vertical lines start at the first grid column left of the visible area
513
+ var firstX = Math.floor((visLeft - originX) / gridSize) * gridSize + originX;
514
+ for (var x = firstX; x <= visRight; x += gridSize) {
515
+ var isOriginLine = Math.abs(x - originX) < 0.5;
483
516
  gridGroup.add(new this.Konva.Line({
484
- points: [x, 0, x, height],
517
+ points: [x, visTop, x, visBottom],
485
518
  stroke: gridColor,
486
519
  strokeWidth: isOriginLine ? 2 : 1,
487
520
  listening: false
488
521
  }));
489
522
  }
490
523
 
491
- // Draw horizontal lines centered at origin
492
- var startY = centerY % gridSize - gridSize;
493
- for (var y = startY; y < height; y += gridSize) {
494
- var _distFromCenter = Math.abs(y - centerY);
495
- var _isOriginLine = _distFromCenter < 1;
524
+ // Draw horizontal lines start at the first grid row above the visible area
525
+ var firstY = Math.floor((visTop - originY) / gridSize) * gridSize + originY;
526
+ for (var y = firstY; y <= visBottom; y += gridSize) {
527
+ var _isOriginLine = Math.abs(y - originY) < 0.5;
496
528
  gridGroup.add(new this.Konva.Line({
497
- points: [0, y, width, y],
529
+ points: [visLeft, y, visRight, y],
498
530
  stroke: gridColor,
499
531
  strokeWidth: _isOriginLine ? 2 : 1,
500
532
  listening: false
@@ -504,14 +536,14 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
504
536
  // Draw center crosshair at world origin
505
537
  var crosshairSize = gridSize;
506
538
  gridGroup.add(new this.Konva.Line({
507
- points: [centerX, centerY - crosshairSize, centerX, centerY + crosshairSize],
539
+ points: [originX, originY - crosshairSize, originX, originY + crosshairSize],
508
540
  stroke: '#333',
509
541
  strokeWidth: 1,
510
542
  listening: false,
511
543
  dash: [5, 5]
512
544
  }));
513
545
  gridGroup.add(new this.Konva.Line({
514
- points: [centerX - crosshairSize, centerY, centerX + crosshairSize, centerY],
546
+ points: [originX - crosshairSize, originY, originX + crosshairSize, originY],
515
547
  stroke: '#333',
516
548
  strokeWidth: 1,
517
549
  listening: false,
@@ -528,7 +560,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
528
560
  }, {
529
561
  key: "renderComponents",
530
562
  value: function renderComponents(viewport) {
531
- var _this5 = this;
563
+ var _this7 = this;
532
564
  if (!viewport.componentLayer || !viewport.stage || !this.sceneViewer) return;
533
565
  viewport.componentLayer.destroyChildren();
534
566
 
@@ -539,19 +571,18 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
539
571
  return;
540
572
  }
541
573
 
542
- // Only log on significant changes
543
- if (components.length !== viewport._lastComponentCount) {
544
- console.log("\uD83D\uDCE6 Rendering ".concat(components.length, " components in 2D view (").concat(viewport.viewType, ")"));
545
- viewport._lastComponentCount = components.length;
546
- }
574
+ // Track render count for debugging
575
+ if (viewport._renderCount === undefined) viewport._renderCount = 0;
576
+ viewport._renderCount++;
547
577
  var width = viewport.stage.width();
548
578
  var height = viewport.stage.height();
549
579
  var centerX = width / 2;
550
580
  var centerY = height / 2;
551
581
  var scale = viewport.PIXELS_PER_UNIT;
582
+ 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, ")"));
552
583
  components.forEach(function (component) {
553
584
  try {
554
- _this5.renderComponent(viewport, component, centerX, centerY, scale);
585
+ _this7.renderComponent(viewport, component, centerX, centerY, scale);
555
586
  } catch (err) {
556
587
  console.warn('⚠️ Error rendering component in 2D:', component.name, err);
557
588
  }
@@ -582,6 +613,11 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
582
613
  var screenX = centerX + posX * scale;
583
614
  var screenY = centerY - posY * scale; // Flip Y for screen coords
584
615
 
616
+ // Debug: Log ALL component positions on first render only
617
+ if (viewport._renderCount === 1) {
618
+ 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)));
619
+ }
620
+
585
621
  // Generate unique color for this component
586
622
  var colors = this.generateComponentColor(component.id);
587
623
 
@@ -611,18 +647,19 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
611
647
  draggable: false
612
648
  });
613
649
 
614
- // Add label
650
+ // Add label (hidden by default; shown on hover)
615
651
  var label = new this.Konva.Text({
616
652
  x: -rectWidth / 2,
617
653
  y: -rectHeight / 2 - 20,
618
654
  text: component.name || 'Component',
619
655
  fontSize: 12,
620
656
  fill: '#333',
621
- listening: false
657
+ listening: false,
658
+ visible: false
622
659
  });
623
660
 
624
661
  // Add mouse event handlers
625
- this.addComponentInteractions(viewport, rect, componentGroup, component, worldWidth, worldDepth, worldHeight, bboxCenter);
662
+ this.addComponentInteractions(viewport, rect, componentGroup, component, worldWidth, worldDepth, worldHeight, bboxCenter, label);
626
663
  componentGroup.add(rect);
627
664
  componentGroup.add(label);
628
665
  viewport.componentLayer.add(componentGroup);
@@ -659,7 +696,14 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
659
696
  if (this._bboxCache.has(object.uuid)) {
660
697
  return this._bboxCache.get(object.uuid);
661
698
  }
662
- var box = boundingBoxUtils.computeFilteredBoundingBox(object, []);
699
+
700
+ // Force matrix updates before computing bbox to ensure world-space accuracy
701
+ object.updateMatrix();
702
+ object.updateMatrixWorld(true);
703
+
704
+ // Exclude io-devices and connectors to match the stored worldBoundingBox
705
+ // computed in modelManager.replaceWithGLBModels()
706
+ var box = boundingBoxUtils.computeFilteredBoundingBox(object, ['io-device', 'connector']);
663
707
  var result;
664
708
  if (box.isEmpty()) {
665
709
  // Object has no geometry; fall back to a point at world position
@@ -789,8 +833,8 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
789
833
  */
790
834
  }, {
791
835
  key: "addComponentInteractions",
792
- value: function addComponentInteractions(viewport, rect, componentGroup, component, worldWidth, worldDepth, worldHeight, bboxCenter) {
793
- var _this6 = this;
836
+ value: function addComponentInteractions(viewport, rect, componentGroup, component, worldWidth, worldDepth, worldHeight, bboxCenter, label) {
837
+ var _this8 = this;
794
838
  if (!this.Konva) return;
795
839
  var colors = this.generateComponentColor(component.id);
796
840
 
@@ -801,6 +845,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
801
845
  rect.stroke('#007bff');
802
846
  rect.strokeWidth(3);
803
847
  viewport.stage.container().style.cursor = 'grab';
848
+ if (label) label.visible(true);
804
849
  viewport.componentLayer.batchDraw();
805
850
  }
806
851
  });
@@ -810,6 +855,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
810
855
  rect.stroke(colors.stroke);
811
856
  rect.strokeWidth(2);
812
857
  viewport.stage.container().style.cursor = 'default';
858
+ if (label) label.visible(false);
813
859
  viewport.componentLayer.batchDraw();
814
860
  }
815
861
  });
@@ -817,12 +863,12 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
817
863
  // CLICK EVENT
818
864
  rect.on('click', function () {
819
865
  if (!viewport.isDragging) {
820
- var _this6$sceneViewer;
866
+ var _this8$sceneViewer;
821
867
  console.log("\uD83C\uDFAF Component clicked: ".concat(component.name));
822
868
 
823
869
  // Use centralPlant API to select component
824
- if ((_this6$sceneViewer = _this6.sceneViewer) !== null && _this6$sceneViewer !== void 0 && _this6$sceneViewer.centralPlant && component.uuid) {
825
- _this6.sceneViewer.centralPlant.selectComponent(component.uuid);
870
+ if ((_this8$sceneViewer = _this8.sceneViewer) !== null && _this8$sceneViewer !== void 0 && _this8$sceneViewer.centralPlant && component.uuid) {
871
+ _this8.sceneViewer.centralPlant.selectComponent(component.uuid);
826
872
  }
827
873
  }
828
874
  });
@@ -852,7 +898,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
852
898
  var worldOriginY = stageHeight / 2;
853
899
 
854
900
  // Snap to grid
855
- var snappedPos = _this6.snapScreenToGrid(viewport, currentPos.x, currentPos.y, scale, worldOriginX, worldOriginY);
901
+ var snappedPos = _this8.snapScreenToGrid(viewport, currentPos.x, currentPos.y, scale, worldOriginX, worldOriginY);
856
902
  componentGroup.position(snappedPos);
857
903
  viewport.componentLayer.batchDraw();
858
904
  });
@@ -860,7 +906,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
860
906
  // DRAG END
861
907
  componentGroup.on('dragend', function () {
862
908
  setTimeout(function () {
863
- var _this6$sceneViewer2;
909
+ var _this8$sceneViewer2;
864
910
  viewport.isDragging = false;
865
911
  var finalPos = componentGroup.position();
866
912
  var stageWidth = viewport.stage.width();
@@ -870,17 +916,17 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
870
916
  var worldOriginY = stageHeight / 2;
871
917
 
872
918
  // Convert screen to world coordinates
873
- var worldCoords = _this6.screenToWorldCoords(viewport, finalPos.x, finalPos.y, scale, worldOriginX, worldOriginY);
919
+ var worldCoords = _this8.screenToWorldCoords(viewport, finalPos.x, finalPos.y, scale, worldOriginX, worldOriginY);
874
920
 
875
921
  // Calculate new position: delta from old bbox center to new bbox center
876
922
  var currentPos = component.position;
877
- var newPosition = _this6.worldCoordsToObjectPosition(viewport, worldCoords, currentPos, bboxCenter);
923
+ var newPosition = _this8.worldCoordsToObjectPosition(viewport, worldCoords, currentPos, bboxCenter);
878
924
 
879
925
  // Apply translation via centralPlant API
880
926
  var deltaX = newPosition.x - currentPos.x;
881
927
  var deltaY = newPosition.y - currentPos.y;
882
928
  var deltaZ = newPosition.z - currentPos.z;
883
- if ((_this6$sceneViewer2 = _this6.sceneViewer) !== null && _this6$sceneViewer2 !== void 0 && _this6$sceneViewer2.centralPlant && component.uuid) {
929
+ if ((_this8$sceneViewer2 = _this8.sceneViewer) !== null && _this8$sceneViewer2 !== void 0 && _this8$sceneViewer2.centralPlant && component.uuid) {
884
930
  var success = true;
885
931
 
886
932
  // Suppress per-axis path updates so the pathfinder only runs once,
@@ -888,32 +934,32 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
888
934
  // Running updatePaths() after each individual axis (the default
889
935
  // translateComponent behaviour) produces intermediate wrong results on
890
936
  // the first drag because no cached fingerprint exists yet to skip them.
891
- var wasAutoUpdate = _this6.sceneViewer.shouldUpdatePaths;
892
- _this6.sceneViewer.shouldUpdatePaths = false;
937
+ var wasAutoUpdate = _this8.sceneViewer.shouldUpdatePaths;
938
+ _this8.sceneViewer.shouldUpdatePaths = false;
893
939
  try {
894
940
  if (Math.abs(deltaX) > 0.01) {
895
- success = success && _this6.sceneViewer.centralPlant.translate(component.uuid, 'x', deltaX);
941
+ success = success && _this8.sceneViewer.centralPlant.translate(component.uuid, 'x', deltaX);
896
942
  }
897
943
  if (Math.abs(deltaY) > 0.01) {
898
- success = success && _this6.sceneViewer.centralPlant.translate(component.uuid, 'y', deltaY);
944
+ success = success && _this8.sceneViewer.centralPlant.translate(component.uuid, 'y', deltaY);
899
945
  }
900
946
  if (Math.abs(deltaZ) > 0.01) {
901
- success = success && _this6.sceneViewer.centralPlant.translate(component.uuid, 'z', deltaZ);
947
+ success = success && _this8.sceneViewer.centralPlant.translate(component.uuid, 'z', deltaZ);
902
948
  }
903
949
  } finally {
904
- _this6.sceneViewer.shouldUpdatePaths = wasAutoUpdate;
950
+ _this8.sceneViewer.shouldUpdatePaths = wasAutoUpdate;
905
951
  }
906
- if (!success && _this6.dragStartPosition) {
952
+ if (!success && _this8.dragStartPosition) {
907
953
  console.warn('⚠️ Failed to translate component, reverting position');
908
- componentGroup.position(_this6.dragStartPosition);
954
+ componentGroup.position(_this8.dragStartPosition);
909
955
  } else {
910
956
  console.log("\u2705 Component ".concat(component.name, " translated successfully in 2D viewport"));
911
957
 
912
958
  // Single path update with the final combined position
913
- if (wasAutoUpdate && _this6.sceneViewer) {
959
+ if (wasAutoUpdate && _this8.sceneViewer) {
914
960
  console.log('🔄 Auto-updating paths after 2D viewport translation...');
915
961
  try {
916
- _this6.sceneViewer.updatePaths();
962
+ _this8.sceneViewer.updatePaths();
917
963
  console.log('✅ Paths auto-updated successfully from 2D viewport');
918
964
  } catch (error) {
919
965
  console.error('❌ Error auto-updating paths from 2D viewport:', error);
@@ -1166,28 +1212,39 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
1166
1212
  }, {
1167
1213
  key: "refresh",
1168
1214
  value: function refresh() {
1169
- var _this7 = this;
1215
+ var _this9 = this;
1170
1216
  var key = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
1217
+ // Collect viewport keys to refresh; null means "all viewports"
1218
+ if (key) {
1219
+ this._pendingRefreshKeys.add(key);
1220
+ } else {
1221
+ // null key means refresh all — mark with special sentinel
1222
+ this._pendingRefreshKeys.add('__ALL__');
1223
+ }
1224
+
1225
+ // If already scheduled, the rAF callback will process our newly-added key
1171
1226
  if (this._refreshPending) return;
1172
1227
  this._refreshPending = true;
1173
1228
  requestAnimationFrame(function () {
1174
- _this7._refreshPending = false;
1229
+ _this9._refreshPending = false;
1230
+
1175
1231
  // Clear per-cycle caches so each component is measured/traversed once per paint
1176
- _this7._bboxCache.clear();
1177
- _this7._componentListCache = null;
1178
- if (key) {
1179
- var viewport = _this7.viewports.get(key);
1180
- if (viewport && viewport.isReady) {
1181
- _this7.renderComponents(viewport);
1182
- }
1183
- } else {
1184
- var _iterator = _rollupPluginBabelHelpers.createForOfIteratorHelper(_this7.viewports.values()),
1232
+ _this9._bboxCache.clear();
1233
+ _this9._componentListCache = null;
1234
+
1235
+ // Check if we need to refresh all viewports
1236
+ var refreshAll = _this9._pendingRefreshKeys.has('__ALL__');
1237
+ var keysToRefresh = new Set(_this9._pendingRefreshKeys);
1238
+ _this9._pendingRefreshKeys.clear();
1239
+ if (refreshAll) {
1240
+ // Refresh all viewports
1241
+ var _iterator = _rollupPluginBabelHelpers.createForOfIteratorHelper(_this9.viewports.values()),
1185
1242
  _step;
1186
1243
  try {
1187
1244
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
1188
- var _viewport = _step.value;
1189
- if (_viewport.isReady) {
1190
- _this7.renderComponents(_viewport);
1245
+ var viewport = _step.value;
1246
+ if (viewport.isReady) {
1247
+ _this9.renderComponents(viewport);
1191
1248
  }
1192
1249
  }
1193
1250
  } catch (err) {
@@ -1195,6 +1252,23 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
1195
1252
  } finally {
1196
1253
  _iterator.f();
1197
1254
  }
1255
+ } else {
1256
+ // Refresh only the specific viewports that were requested
1257
+ var _iterator2 = _rollupPluginBabelHelpers.createForOfIteratorHelper(keysToRefresh),
1258
+ _step2;
1259
+ try {
1260
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
1261
+ var viewportKey = _step2.value;
1262
+ var _viewport = _this9.viewports.get(viewportKey);
1263
+ if (_viewport && _viewport.isReady) {
1264
+ _this9.renderComponents(_viewport);
1265
+ }
1266
+ }
1267
+ } catch (err) {
1268
+ _iterator2.e(err);
1269
+ } finally {
1270
+ _iterator2.f();
1271
+ }
1198
1272
  }
1199
1273
  });
1200
1274
  }
@@ -1215,20 +1289,20 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
1215
1289
  }
1216
1290
 
1217
1291
  // Dispose all viewport instances
1218
- var _iterator2 = _rollupPluginBabelHelpers.createForOfIteratorHelper(this.viewports.entries()),
1219
- _step2;
1292
+ var _iterator3 = _rollupPluginBabelHelpers.createForOfIteratorHelper(this.viewports.entries()),
1293
+ _step3;
1220
1294
  try {
1221
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
1222
- var _step2$value = _rollupPluginBabelHelpers.slicedToArray(_step2.value, 2),
1223
- key = _step2$value[0],
1224
- viewport = _step2$value[1];
1295
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
1296
+ var _step3$value = _rollupPluginBabelHelpers.slicedToArray(_step3.value, 2),
1297
+ key = _step3$value[0],
1298
+ viewport = _step3$value[1];
1225
1299
  console.log("\uD83D\uDDD1\uFE0F Disposing viewport: ".concat(key));
1226
1300
  viewport.dispose();
1227
1301
  }
1228
1302
  } catch (err) {
1229
- _iterator2.e(err);
1303
+ _iterator3.e(err);
1230
1304
  } finally {
1231
- _iterator2.f();
1305
+ _iterator3.f();
1232
1306
  }
1233
1307
  this.viewports.clear();
1234
1308
  this.Konva = null;
@@ -119,7 +119,12 @@ function computeFilteredBoundingBox(object) {
119
119
 
120
120
  // Build a Set for O(1) lookups
121
121
  var excludeSet = new Set(excludeTypes);
122
- object.updateWorldMatrix(false, true);
122
+
123
+ // Force matrix updates to ensure world-space coordinates are accurate.
124
+ // Using force=true ensures matrices are updated even if matrixWorldNeedsUpdate is false,
125
+ // which can happen after positioning a model before the render loop runs.
126
+ object.updateMatrix();
127
+ object.updateMatrixWorld(true);
123
128
  object.traverse(function (child) {
124
129
  // Only process nodes with geometry (Mesh, SkinnedMesh, etc.)
125
130
  if (!child.geometry) return;
@@ -31,7 +31,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
31
31
  * Initialize the CentralPlant manager
32
32
  *
33
33
  * @constructor
34
- * @version 0.3.11
34
+ * @version 0.3.13
35
35
  * @updated 2025-10-22
36
36
  *
37
37
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -561,6 +561,15 @@ var ModelManager = /*#__PURE__*/function () {
561
561
  var jsonData = _ref2.jsonData,
562
562
  glbModel = _ref2.glbModel;
563
563
  if (!glbModel) return;
564
+
565
+ // CRITICAL: Force matrix updates before computing bbox.
566
+ // After loadLibraryModel positions the model, the world matrices may not be
567
+ // invalidated yet. computeFilteredBoundingBox uses updateWorldMatrix(false, true)
568
+ // which only updates if matrixWorldNeedsUpdate is true. Force the update here
569
+ // to ensure the bbox is computed with correct world-space coordinates.
570
+ glbModel.updateMatrix();
571
+ glbModel.updateMatrixWorld(true);
572
+
564
573
  // Use filtered bbox (excludes connectors + io-devices) so it matches
565
574
  // what pathfindingManager._enrichSceneDataWithBoundingBoxes produces
566
575
  var filteredBox = computeFilteredBoundingBox(glbModel, ['io-device', 'connector']);