@2112-lab/central-plant 0.3.12 → 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,7 +422,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
400
422
  }, {
401
423
  key: "setupZoomAndPanHandlers",
402
424
  value: function setupZoomAndPanHandlers(viewport) {
403
- var _this5 = this;
425
+ var _this6 = this;
404
426
  if (!viewport.stage) return;
405
427
 
406
428
  // Mouse wheel zoom
@@ -426,7 +448,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
426
448
  y: pointer.y - mousePointTo.y * clampedScale
427
449
  };
428
450
  viewport.stage.position(newPos);
429
- _this5.drawGrid(viewport);
451
+ _this6.drawGrid(viewport);
430
452
  viewport.stage.batchDraw();
431
453
  });
432
454
 
@@ -446,7 +468,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
446
468
  viewport.stage.x(viewport.stage.x() + dx);
447
469
  viewport.stage.y(viewport.stage.y() + dy);
448
470
  viewport.lastPanPoint = pos;
449
- _this5.drawGrid(viewport);
471
+ _this6.drawGrid(viewport);
450
472
  viewport.stage.batchDraw();
451
473
  });
452
474
  viewport.stage.on('mouseup', function () {
@@ -538,7 +560,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
538
560
  }, {
539
561
  key: "renderComponents",
540
562
  value: function renderComponents(viewport) {
541
- var _this6 = this;
563
+ var _this7 = this;
542
564
  if (!viewport.componentLayer || !viewport.stage || !this.sceneViewer) return;
543
565
  viewport.componentLayer.destroyChildren();
544
566
 
@@ -549,19 +571,18 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
549
571
  return;
550
572
  }
551
573
 
552
- // Only log on significant changes
553
- if (components.length !== viewport._lastComponentCount) {
554
- console.log("\uD83D\uDCE6 Rendering ".concat(components.length, " components in 2D view (").concat(viewport.viewType, ")"));
555
- viewport._lastComponentCount = components.length;
556
- }
574
+ // Track render count for debugging
575
+ if (viewport._renderCount === undefined) viewport._renderCount = 0;
576
+ viewport._renderCount++;
557
577
  var width = viewport.stage.width();
558
578
  var height = viewport.stage.height();
559
579
  var centerX = width / 2;
560
580
  var centerY = height / 2;
561
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, ")"));
562
583
  components.forEach(function (component) {
563
584
  try {
564
- _this6.renderComponent(viewport, component, centerX, centerY, scale);
585
+ _this7.renderComponent(viewport, component, centerX, centerY, scale);
565
586
  } catch (err) {
566
587
  console.warn('⚠️ Error rendering component in 2D:', component.name, err);
567
588
  }
@@ -592,6 +613,11 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
592
613
  var screenX = centerX + posX * scale;
593
614
  var screenY = centerY - posY * scale; // Flip Y for screen coords
594
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
+
595
621
  // Generate unique color for this component
596
622
  var colors = this.generateComponentColor(component.id);
597
623
 
@@ -670,7 +696,14 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
670
696
  if (this._bboxCache.has(object.uuid)) {
671
697
  return this._bboxCache.get(object.uuid);
672
698
  }
673
- 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']);
674
707
  var result;
675
708
  if (box.isEmpty()) {
676
709
  // Object has no geometry; fall back to a point at world position
@@ -801,7 +834,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
801
834
  }, {
802
835
  key: "addComponentInteractions",
803
836
  value: function addComponentInteractions(viewport, rect, componentGroup, component, worldWidth, worldDepth, worldHeight, bboxCenter, label) {
804
- var _this7 = this;
837
+ var _this8 = this;
805
838
  if (!this.Konva) return;
806
839
  var colors = this.generateComponentColor(component.id);
807
840
 
@@ -830,12 +863,12 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
830
863
  // CLICK EVENT
831
864
  rect.on('click', function () {
832
865
  if (!viewport.isDragging) {
833
- var _this7$sceneViewer;
866
+ var _this8$sceneViewer;
834
867
  console.log("\uD83C\uDFAF Component clicked: ".concat(component.name));
835
868
 
836
869
  // Use centralPlant API to select component
837
- if ((_this7$sceneViewer = _this7.sceneViewer) !== null && _this7$sceneViewer !== void 0 && _this7$sceneViewer.centralPlant && component.uuid) {
838
- _this7.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);
839
872
  }
840
873
  }
841
874
  });
@@ -865,7 +898,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
865
898
  var worldOriginY = stageHeight / 2;
866
899
 
867
900
  // Snap to grid
868
- var snappedPos = _this7.snapScreenToGrid(viewport, currentPos.x, currentPos.y, scale, worldOriginX, worldOriginY);
901
+ var snappedPos = _this8.snapScreenToGrid(viewport, currentPos.x, currentPos.y, scale, worldOriginX, worldOriginY);
869
902
  componentGroup.position(snappedPos);
870
903
  viewport.componentLayer.batchDraw();
871
904
  });
@@ -873,7 +906,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
873
906
  // DRAG END
874
907
  componentGroup.on('dragend', function () {
875
908
  setTimeout(function () {
876
- var _this7$sceneViewer2;
909
+ var _this8$sceneViewer2;
877
910
  viewport.isDragging = false;
878
911
  var finalPos = componentGroup.position();
879
912
  var stageWidth = viewport.stage.width();
@@ -883,17 +916,17 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
883
916
  var worldOriginY = stageHeight / 2;
884
917
 
885
918
  // Convert screen to world coordinates
886
- var worldCoords = _this7.screenToWorldCoords(viewport, finalPos.x, finalPos.y, scale, worldOriginX, worldOriginY);
919
+ var worldCoords = _this8.screenToWorldCoords(viewport, finalPos.x, finalPos.y, scale, worldOriginX, worldOriginY);
887
920
 
888
921
  // Calculate new position: delta from old bbox center to new bbox center
889
922
  var currentPos = component.position;
890
- var newPosition = _this7.worldCoordsToObjectPosition(viewport, worldCoords, currentPos, bboxCenter);
923
+ var newPosition = _this8.worldCoordsToObjectPosition(viewport, worldCoords, currentPos, bboxCenter);
891
924
 
892
925
  // Apply translation via centralPlant API
893
926
  var deltaX = newPosition.x - currentPos.x;
894
927
  var deltaY = newPosition.y - currentPos.y;
895
928
  var deltaZ = newPosition.z - currentPos.z;
896
- if ((_this7$sceneViewer2 = _this7.sceneViewer) !== null && _this7$sceneViewer2 !== void 0 && _this7$sceneViewer2.centralPlant && component.uuid) {
929
+ if ((_this8$sceneViewer2 = _this8.sceneViewer) !== null && _this8$sceneViewer2 !== void 0 && _this8$sceneViewer2.centralPlant && component.uuid) {
897
930
  var success = true;
898
931
 
899
932
  // Suppress per-axis path updates so the pathfinder only runs once,
@@ -901,32 +934,32 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
901
934
  // Running updatePaths() after each individual axis (the default
902
935
  // translateComponent behaviour) produces intermediate wrong results on
903
936
  // the first drag because no cached fingerprint exists yet to skip them.
904
- var wasAutoUpdate = _this7.sceneViewer.shouldUpdatePaths;
905
- _this7.sceneViewer.shouldUpdatePaths = false;
937
+ var wasAutoUpdate = _this8.sceneViewer.shouldUpdatePaths;
938
+ _this8.sceneViewer.shouldUpdatePaths = false;
906
939
  try {
907
940
  if (Math.abs(deltaX) > 0.01) {
908
- success = success && _this7.sceneViewer.centralPlant.translate(component.uuid, 'x', deltaX);
941
+ success = success && _this8.sceneViewer.centralPlant.translate(component.uuid, 'x', deltaX);
909
942
  }
910
943
  if (Math.abs(deltaY) > 0.01) {
911
- success = success && _this7.sceneViewer.centralPlant.translate(component.uuid, 'y', deltaY);
944
+ success = success && _this8.sceneViewer.centralPlant.translate(component.uuid, 'y', deltaY);
912
945
  }
913
946
  if (Math.abs(deltaZ) > 0.01) {
914
- success = success && _this7.sceneViewer.centralPlant.translate(component.uuid, 'z', deltaZ);
947
+ success = success && _this8.sceneViewer.centralPlant.translate(component.uuid, 'z', deltaZ);
915
948
  }
916
949
  } finally {
917
- _this7.sceneViewer.shouldUpdatePaths = wasAutoUpdate;
950
+ _this8.sceneViewer.shouldUpdatePaths = wasAutoUpdate;
918
951
  }
919
- if (!success && _this7.dragStartPosition) {
952
+ if (!success && _this8.dragStartPosition) {
920
953
  console.warn('⚠️ Failed to translate component, reverting position');
921
- componentGroup.position(_this7.dragStartPosition);
954
+ componentGroup.position(_this8.dragStartPosition);
922
955
  } else {
923
956
  console.log("\u2705 Component ".concat(component.name, " translated successfully in 2D viewport"));
924
957
 
925
958
  // Single path update with the final combined position
926
- if (wasAutoUpdate && _this7.sceneViewer) {
959
+ if (wasAutoUpdate && _this8.sceneViewer) {
927
960
  console.log('🔄 Auto-updating paths after 2D viewport translation...');
928
961
  try {
929
- _this7.sceneViewer.updatePaths();
962
+ _this8.sceneViewer.updatePaths();
930
963
  console.log('✅ Paths auto-updated successfully from 2D viewport');
931
964
  } catch (error) {
932
965
  console.error('❌ Error auto-updating paths from 2D viewport:', error);
@@ -1179,28 +1212,39 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
1179
1212
  }, {
1180
1213
  key: "refresh",
1181
1214
  value: function refresh() {
1182
- var _this8 = this;
1215
+ var _this9 = this;
1183
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
1184
1226
  if (this._refreshPending) return;
1185
1227
  this._refreshPending = true;
1186
1228
  requestAnimationFrame(function () {
1187
- _this8._refreshPending = false;
1229
+ _this9._refreshPending = false;
1230
+
1188
1231
  // Clear per-cycle caches so each component is measured/traversed once per paint
1189
- _this8._bboxCache.clear();
1190
- _this8._componentListCache = null;
1191
- if (key) {
1192
- var viewport = _this8.viewports.get(key);
1193
- if (viewport && viewport.isReady) {
1194
- _this8.renderComponents(viewport);
1195
- }
1196
- } else {
1197
- var _iterator = _rollupPluginBabelHelpers.createForOfIteratorHelper(_this8.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()),
1198
1242
  _step;
1199
1243
  try {
1200
1244
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
1201
- var _viewport = _step.value;
1202
- if (_viewport.isReady) {
1203
- _this8.renderComponents(_viewport);
1245
+ var viewport = _step.value;
1246
+ if (viewport.isReady) {
1247
+ _this9.renderComponents(viewport);
1204
1248
  }
1205
1249
  }
1206
1250
  } catch (err) {
@@ -1208,6 +1252,23 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
1208
1252
  } finally {
1209
1253
  _iterator.f();
1210
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
+ }
1211
1272
  }
1212
1273
  });
1213
1274
  }
@@ -1228,20 +1289,20 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
1228
1289
  }
1229
1290
 
1230
1291
  // Dispose all viewport instances
1231
- var _iterator2 = _rollupPluginBabelHelpers.createForOfIteratorHelper(this.viewports.entries()),
1232
- _step2;
1292
+ var _iterator3 = _rollupPluginBabelHelpers.createForOfIteratorHelper(this.viewports.entries()),
1293
+ _step3;
1233
1294
  try {
1234
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
1235
- var _step2$value = _rollupPluginBabelHelpers.slicedToArray(_step2.value, 2),
1236
- key = _step2$value[0],
1237
- 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];
1238
1299
  console.log("\uD83D\uDDD1\uFE0F Disposing viewport: ".concat(key));
1239
1300
  viewport.dispose();
1240
1301
  }
1241
1302
  } catch (err) {
1242
- _iterator2.e(err);
1303
+ _iterator3.e(err);
1243
1304
  } finally {
1244
- _iterator2.f();
1305
+ _iterator3.f();
1245
1306
  }
1246
1307
  this.viewports.clear();
1247
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.12
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']);