@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.
@@ -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,7 +398,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
376
398
  }, {
377
399
  key: "setupZoomAndPanHandlers",
378
400
  value: function setupZoomAndPanHandlers(viewport) {
379
- var _this5 = this;
401
+ var _this6 = this;
380
402
  if (!viewport.stage) return;
381
403
 
382
404
  // Mouse wheel zoom
@@ -402,7 +424,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
402
424
  y: pointer.y - mousePointTo.y * clampedScale
403
425
  };
404
426
  viewport.stage.position(newPos);
405
- _this5.drawGrid(viewport);
427
+ _this6.drawGrid(viewport);
406
428
  viewport.stage.batchDraw();
407
429
  });
408
430
 
@@ -422,7 +444,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
422
444
  viewport.stage.x(viewport.stage.x() + dx);
423
445
  viewport.stage.y(viewport.stage.y() + dy);
424
446
  viewport.lastPanPoint = pos;
425
- _this5.drawGrid(viewport);
447
+ _this6.drawGrid(viewport);
426
448
  viewport.stage.batchDraw();
427
449
  });
428
450
  viewport.stage.on('mouseup', function () {
@@ -514,7 +536,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
514
536
  }, {
515
537
  key: "renderComponents",
516
538
  value: function renderComponents(viewport) {
517
- var _this6 = this;
539
+ var _this7 = this;
518
540
  if (!viewport.componentLayer || !viewport.stage || !this.sceneViewer) return;
519
541
  viewport.componentLayer.destroyChildren();
520
542
 
@@ -525,19 +547,18 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
525
547
  return;
526
548
  }
527
549
 
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
- }
550
+ // Track render count for debugging
551
+ if (viewport._renderCount === undefined) viewport._renderCount = 0;
552
+ viewport._renderCount++;
533
553
  var width = viewport.stage.width();
534
554
  var height = viewport.stage.height();
535
555
  var centerX = width / 2;
536
556
  var centerY = height / 2;
537
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, ")"));
538
559
  components.forEach(function (component) {
539
560
  try {
540
- _this6.renderComponent(viewport, component, centerX, centerY, scale);
561
+ _this7.renderComponent(viewport, component, centerX, centerY, scale);
541
562
  } catch (err) {
542
563
  console.warn('⚠️ Error rendering component in 2D:', component.name, err);
543
564
  }
@@ -568,6 +589,11 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
568
589
  var screenX = centerX + posX * scale;
569
590
  var screenY = centerY - posY * scale; // Flip Y for screen coords
570
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
+
571
597
  // Generate unique color for this component
572
598
  var colors = this.generateComponentColor(component.id);
573
599
 
@@ -646,7 +672,14 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
646
672
  if (this._bboxCache.has(object.uuid)) {
647
673
  return this._bboxCache.get(object.uuid);
648
674
  }
649
- 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']);
650
683
  var result;
651
684
  if (box.isEmpty()) {
652
685
  // Object has no geometry; fall back to a point at world position
@@ -777,7 +810,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
777
810
  }, {
778
811
  key: "addComponentInteractions",
779
812
  value: function addComponentInteractions(viewport, rect, componentGroup, component, worldWidth, worldDepth, worldHeight, bboxCenter, label) {
780
- var _this7 = this;
813
+ var _this8 = this;
781
814
  if (!this.Konva) return;
782
815
  var colors = this.generateComponentColor(component.id);
783
816
 
@@ -806,12 +839,12 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
806
839
  // CLICK EVENT
807
840
  rect.on('click', function () {
808
841
  if (!viewport.isDragging) {
809
- var _this7$sceneViewer;
842
+ var _this8$sceneViewer;
810
843
  console.log("\uD83C\uDFAF Component clicked: ".concat(component.name));
811
844
 
812
845
  // 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);
846
+ if ((_this8$sceneViewer = _this8.sceneViewer) !== null && _this8$sceneViewer !== void 0 && _this8$sceneViewer.centralPlant && component.uuid) {
847
+ _this8.sceneViewer.centralPlant.selectComponent(component.uuid);
815
848
  }
816
849
  }
817
850
  });
@@ -841,7 +874,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
841
874
  var worldOriginY = stageHeight / 2;
842
875
 
843
876
  // Snap to grid
844
- var snappedPos = _this7.snapScreenToGrid(viewport, currentPos.x, currentPos.y, scale, worldOriginX, worldOriginY);
877
+ var snappedPos = _this8.snapScreenToGrid(viewport, currentPos.x, currentPos.y, scale, worldOriginX, worldOriginY);
845
878
  componentGroup.position(snappedPos);
846
879
  viewport.componentLayer.batchDraw();
847
880
  });
@@ -849,7 +882,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
849
882
  // DRAG END
850
883
  componentGroup.on('dragend', function () {
851
884
  setTimeout(function () {
852
- var _this7$sceneViewer2;
885
+ var _this8$sceneViewer2;
853
886
  viewport.isDragging = false;
854
887
  var finalPos = componentGroup.position();
855
888
  var stageWidth = viewport.stage.width();
@@ -859,17 +892,17 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
859
892
  var worldOriginY = stageHeight / 2;
860
893
 
861
894
  // Convert screen to world coordinates
862
- var worldCoords = _this7.screenToWorldCoords(viewport, finalPos.x, finalPos.y, scale, worldOriginX, worldOriginY);
895
+ var worldCoords = _this8.screenToWorldCoords(viewport, finalPos.x, finalPos.y, scale, worldOriginX, worldOriginY);
863
896
 
864
897
  // Calculate new position: delta from old bbox center to new bbox center
865
898
  var currentPos = component.position;
866
- var newPosition = _this7.worldCoordsToObjectPosition(viewport, worldCoords, currentPos, bboxCenter);
899
+ var newPosition = _this8.worldCoordsToObjectPosition(viewport, worldCoords, currentPos, bboxCenter);
867
900
 
868
901
  // Apply translation via centralPlant API
869
902
  var deltaX = newPosition.x - currentPos.x;
870
903
  var deltaY = newPosition.y - currentPos.y;
871
904
  var deltaZ = newPosition.z - currentPos.z;
872
- if ((_this7$sceneViewer2 = _this7.sceneViewer) !== null && _this7$sceneViewer2 !== void 0 && _this7$sceneViewer2.centralPlant && component.uuid) {
905
+ if ((_this8$sceneViewer2 = _this8.sceneViewer) !== null && _this8$sceneViewer2 !== void 0 && _this8$sceneViewer2.centralPlant && component.uuid) {
873
906
  var success = true;
874
907
 
875
908
  // Suppress per-axis path updates so the pathfinder only runs once,
@@ -877,32 +910,32 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
877
910
  // Running updatePaths() after each individual axis (the default
878
911
  // translateComponent behaviour) produces intermediate wrong results on
879
912
  // the first drag because no cached fingerprint exists yet to skip them.
880
- var wasAutoUpdate = _this7.sceneViewer.shouldUpdatePaths;
881
- _this7.sceneViewer.shouldUpdatePaths = false;
913
+ var wasAutoUpdate = _this8.sceneViewer.shouldUpdatePaths;
914
+ _this8.sceneViewer.shouldUpdatePaths = false;
882
915
  try {
883
916
  if (Math.abs(deltaX) > 0.01) {
884
- success = success && _this7.sceneViewer.centralPlant.translate(component.uuid, 'x', deltaX);
917
+ success = success && _this8.sceneViewer.centralPlant.translate(component.uuid, 'x', deltaX);
885
918
  }
886
919
  if (Math.abs(deltaY) > 0.01) {
887
- success = success && _this7.sceneViewer.centralPlant.translate(component.uuid, 'y', deltaY);
920
+ success = success && _this8.sceneViewer.centralPlant.translate(component.uuid, 'y', deltaY);
888
921
  }
889
922
  if (Math.abs(deltaZ) > 0.01) {
890
- success = success && _this7.sceneViewer.centralPlant.translate(component.uuid, 'z', deltaZ);
923
+ success = success && _this8.sceneViewer.centralPlant.translate(component.uuid, 'z', deltaZ);
891
924
  }
892
925
  } finally {
893
- _this7.sceneViewer.shouldUpdatePaths = wasAutoUpdate;
926
+ _this8.sceneViewer.shouldUpdatePaths = wasAutoUpdate;
894
927
  }
895
- if (!success && _this7.dragStartPosition) {
928
+ if (!success && _this8.dragStartPosition) {
896
929
  console.warn('⚠️ Failed to translate component, reverting position');
897
- componentGroup.position(_this7.dragStartPosition);
930
+ componentGroup.position(_this8.dragStartPosition);
898
931
  } else {
899
932
  console.log("\u2705 Component ".concat(component.name, " translated successfully in 2D viewport"));
900
933
 
901
934
  // Single path update with the final combined position
902
- if (wasAutoUpdate && _this7.sceneViewer) {
935
+ if (wasAutoUpdate && _this8.sceneViewer) {
903
936
  console.log('🔄 Auto-updating paths after 2D viewport translation...');
904
937
  try {
905
- _this7.sceneViewer.updatePaths();
938
+ _this8.sceneViewer.updatePaths();
906
939
  console.log('✅ Paths auto-updated successfully from 2D viewport');
907
940
  } catch (error) {
908
941
  console.error('❌ Error auto-updating paths from 2D viewport:', error);
@@ -1155,28 +1188,39 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
1155
1188
  }, {
1156
1189
  key: "refresh",
1157
1190
  value: function refresh() {
1158
- var _this8 = this;
1191
+ var _this9 = this;
1159
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
1160
1202
  if (this._refreshPending) return;
1161
1203
  this._refreshPending = true;
1162
1204
  requestAnimationFrame(function () {
1163
- _this8._refreshPending = false;
1205
+ _this9._refreshPending = false;
1206
+
1164
1207
  // 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()),
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()),
1174
1218
  _step;
1175
1219
  try {
1176
1220
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
1177
- var _viewport = _step.value;
1178
- if (_viewport.isReady) {
1179
- _this8.renderComponents(_viewport);
1221
+ var viewport = _step.value;
1222
+ if (viewport.isReady) {
1223
+ _this9.renderComponents(viewport);
1180
1224
  }
1181
1225
  }
1182
1226
  } catch (err) {
@@ -1184,6 +1228,23 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
1184
1228
  } finally {
1185
1229
  _iterator.f();
1186
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
+ }
1187
1248
  }
1188
1249
  });
1189
1250
  }
@@ -1204,20 +1265,20 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
1204
1265
  }
1205
1266
 
1206
1267
  // Dispose all viewport instances
1207
- var _iterator2 = _createForOfIteratorHelper(this.viewports.entries()),
1208
- _step2;
1268
+ var _iterator3 = _createForOfIteratorHelper(this.viewports.entries()),
1269
+ _step3;
1209
1270
  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];
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];
1214
1275
  console.log("\uD83D\uDDD1\uFE0F Disposing viewport: ".concat(key));
1215
1276
  viewport.dispose();
1216
1277
  }
1217
1278
  } catch (err) {
1218
- _iterator2.e(err);
1279
+ _iterator3.e(err);
1219
1280
  } finally {
1220
- _iterator2.f();
1281
+ _iterator3.f();
1221
1282
  }
1222
1283
  this.viewports.clear();
1223
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.12",
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",