@2112-lab/central-plant 0.3.4 → 0.3.5

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.
@@ -29813,15 +29813,21 @@ var ModelManager = /*#__PURE__*/function () {
29813
29813
  _context5.n = 2;
29814
29814
  return Promise.all(glbLoadingPromises);
29815
29815
  case 2:
29816
- // Update world bounding boxes for loaded models
29816
+ // Update world bounding boxes for loaded models and propagate to the live Three.js objects
29817
29817
  libraryObjectsToReplace.forEach(function (_ref2) {
29818
- var _jsonData$userData2;
29819
29818
  var jsonData = _ref2.jsonData,
29820
29819
  glbModel = _ref2.glbModel;
29821
- if (glbModel && (_jsonData$userData2 = jsonData.userData) !== null && _jsonData$userData2 !== void 0 && _jsonData$userData2.worldBoundingBox) {
29822
- var worldBoundingBox = _this3._calculateWorldBoundingBox(glbModel);
29823
- jsonData.userData.worldBoundingBox = worldBoundingBox;
29824
- }
29820
+ if (!glbModel) return;
29821
+ // Use filtered bbox (excludes connectors + io-devices) so it matches
29822
+ // what pathfindingManager._enrichSceneDataWithBoundingBoxes produces
29823
+ var filteredBox = computeFilteredBoundingBox(glbModel, ['io-device', 'connector']);
29824
+ var worldBoundingBox = filteredBox.isEmpty() ? _this3._calculateWorldBoundingBox(glbModel) : {
29825
+ min: [filteredBox.min.x, filteredBox.min.y, filteredBox.min.z],
29826
+ max: [filteredBox.max.x, filteredBox.max.y, filteredBox.max.z]
29827
+ };
29828
+ // Update both the JSON data object AND the live scene object
29829
+ jsonData.userData.worldBoundingBox = worldBoundingBox;
29830
+ glbModel.userData.worldBoundingBox = worldBoundingBox;
29825
29831
  });
29826
29832
 
29827
29833
  // Dispatch completion event
@@ -35922,32 +35928,20 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
35922
35928
  }, {
35923
35929
  key: "renderComponent",
35924
35930
  value: function renderComponent(viewport, component, centerX, centerY, scale) {
35925
- var _component$position$x, _component$position, _component$position$y, _component$position2, _component$position$z, _component$position3, _component$rotation$x, _component$rotation, _component$rotation$y, _component$rotation2, _component$rotation$z, _component$rotation3;
35926
- // Get component position and rotation
35927
- var pos3D = {
35928
- x: (_component$position$x = (_component$position = component.position) === null || _component$position === void 0 ? void 0 : _component$position.x) !== null && _component$position$x !== void 0 ? _component$position$x : 0,
35929
- y: (_component$position$y = (_component$position2 = component.position) === null || _component$position2 === void 0 ? void 0 : _component$position2.y) !== null && _component$position$y !== void 0 ? _component$position$y : 0,
35930
- z: (_component$position$z = (_component$position3 = component.position) === null || _component$position3 === void 0 ? void 0 : _component$position3.z) !== null && _component$position$z !== void 0 ? _component$position$z : 0
35931
- };
35932
- var rot3D = {
35933
- x: (_component$rotation$x = (_component$rotation = component.rotation) === null || _component$rotation === void 0 ? void 0 : _component$rotation.x) !== null && _component$rotation$x !== void 0 ? _component$rotation$x : 0,
35934
- y: (_component$rotation$y = (_component$rotation2 = component.rotation) === null || _component$rotation2 === void 0 ? void 0 : _component$rotation2.y) !== null && _component$rotation$y !== void 0 ? _component$rotation$y : 0,
35935
- z: (_component$rotation$z = (_component$rotation3 = component.rotation) === null || _component$rotation3 === void 0 ? void 0 : _component$rotation3.z) !== null && _component$rotation$z !== void 0 ? _component$rotation$z : 0
35936
- };
35937
-
35938
- // Get bounding box dimensions
35931
+ // Get world-space bounding box dimensions and center
35939
35932
  var _this$getComponentDim = this.getComponentDimensions(component),
35940
35933
  worldWidth = _this$getComponentDim.worldWidth,
35941
35934
  worldDepth = _this$getComponentDim.worldDepth,
35942
- worldHeight = _this$getComponentDim.worldHeight;
35935
+ worldHeight = _this$getComponentDim.worldHeight,
35936
+ bboxCenter = _this$getComponentDim.bboxCenter;
35937
+ console.log("[2D] ".concat(component.name, " | w=").concat(worldWidth.toFixed(3), " d=").concat(worldDepth.toFixed(3), " h=").concat(worldHeight.toFixed(3), " | center=(").concat(bboxCenter.x.toFixed(2), ",").concat(bboxCenter.y.toFixed(2), ",").concat(bboxCenter.z.toFixed(2), ")"));
35943
35938
 
35944
- // Project 3D coordinates to 2D based on view type
35945
- var _this$project3DTo2D = this.project3DTo2D(viewport, pos3D, rot3D, worldWidth, worldDepth, worldHeight),
35939
+ // Project 3D bbox center to 2D based on view type
35940
+ var _this$project3DTo2D = this.project3DTo2D(viewport, bboxCenter, worldWidth, worldDepth, worldHeight),
35946
35941
  posX = _this$project3DTo2D.posX,
35947
35942
  posY = _this$project3DTo2D.posY,
35948
35943
  rectWidth = _this$project3DTo2D.rectWidth,
35949
35944
  rectHeight = _this$project3DTo2D.rectHeight;
35950
- _this$project3DTo2D.rotationDegrees;
35951
35945
  var screenX = centerX + posX * scale;
35952
35946
  var screenY = centerY - posY * scale; // Flip Y for screen coords
35953
35947
 
@@ -35991,105 +35985,131 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
35991
35985
  });
35992
35986
 
35993
35987
  // Add mouse event handlers
35994
- this.addComponentInteractions(viewport, rect, componentGroup, component, worldWidth, worldDepth, worldHeight);
35988
+ this.addComponentInteractions(viewport, rect, componentGroup, component, worldWidth, worldDepth, worldHeight, bboxCenter);
35995
35989
  componentGroup.add(rect);
35996
35990
  componentGroup.add(label);
35997
35991
  viewport.componentLayer.add(componentGroup);
35998
35992
  }
35999
35993
 
36000
35994
  /**
36001
- * Get component dimensions from various sources
35995
+ * Compute worldBoundingBox for a live Three.js Object3D using the same
35996
+ * vertex-accurate approach as computeFilteredBoundingBox (explicitly calls
35997
+ * geometry.computeBoundingBox() on each mesh before measuring).
35998
+ * @param {THREE.Object3D} object
35999
+ * @returns {{min: number[], max: number[]}}
36000
+ */
36001
+ }, {
36002
+ key: "_getOrComputeWorldBoundingBox",
36003
+ value: function _getOrComputeWorldBoundingBox(object) {
36004
+ // computeFilteredBoundingBox with no exclusions = full object bbox
36005
+ var box = computeFilteredBoundingBox(object, []);
36006
+ if (box.isEmpty()) {
36007
+ // Object has no geometry; fall back to a point at world position
36008
+ var wp = new THREE__namespace.Vector3();
36009
+ object.getWorldPosition(wp);
36010
+ return {
36011
+ min: [wp.x, wp.y, wp.z],
36012
+ max: [wp.x, wp.y, wp.z]
36013
+ };
36014
+ }
36015
+ return {
36016
+ min: [box.min.x, box.min.y, box.min.z],
36017
+ max: [box.max.x, box.max.y, box.max.z]
36018
+ };
36019
+ }
36020
+
36021
+ /**
36022
+ * Get component dimensions and world-space center from worldBoundingBox
36002
36023
  */
36003
36024
  }, {
36004
36025
  key: "getComponentDimensions",
36005
36026
  value: function getComponentDimensions(component) {
36006
- var _component$userData, _component$userData2, _component$geometry;
36007
- var worldWidth = 1,
36008
- worldHeight = 1,
36009
- worldDepth = 1;
36010
-
36011
- // Try adaptedBoundingBox first
36012
- if ((_component$userData = component.userData) !== null && _component$userData !== void 0 && _component$userData.adaptedBoundingBox) {
36013
- var bbox = component.userData.adaptedBoundingBox;
36014
- if (bbox.max && bbox.min) {
36015
- worldWidth = Math.abs(bbox.max.x - bbox.min.x);
36016
- worldDepth = Math.abs(bbox.max.y - bbox.min.y);
36017
- worldHeight = Math.abs(bbox.max.z - bbox.min.z);
36018
- }
36019
- }
36020
- // Fallback to dimensions from userData
36021
- else if ((_component$userData2 = component.userData) !== null && _component$userData2 !== void 0 && _component$userData2.dimensions) {
36022
- var dims = component.userData.dimensions;
36023
- worldWidth = Math.abs(dims.x);
36024
- worldDepth = Math.abs(dims.y);
36025
- worldHeight = Math.abs(dims.z);
36026
- }
36027
- // Last resort: geometry bounding box
36028
- else if ((_component$geometry = component.geometry) !== null && _component$geometry !== void 0 && _component$geometry.boundingBox) {
36029
- var _bbox = component.geometry.boundingBox;
36030
- worldWidth = Math.abs(_bbox.max.x - _bbox.min.x);
36031
- worldDepth = Math.abs(_bbox.max.y - _bbox.min.y);
36032
- worldHeight = Math.abs(_bbox.max.z - _bbox.min.z);
36027
+ var _component$userData$w, _component$userData, _component$getWorldPo;
36028
+ // Prefer worldBoundingBox already stored on the object — set after GLB loading
36029
+ // by modelManager.replaceWithGLBModels using computeFilteredBoundingBox.
36030
+ // Fall back to computing live if not yet available (e.g. first render before GLB load).
36031
+ var wbb = (_component$userData$w = (_component$userData = component.userData) === null || _component$userData === void 0 ? void 0 : _component$userData.worldBoundingBox) !== null && _component$userData$w !== void 0 ? _component$userData$w : this._getOrComputeWorldBoundingBox(component);
36032
+ if (wbb !== null && wbb !== void 0 && wbb.min && wbb !== null && wbb !== void 0 && wbb.max) {
36033
+ var _wbb$min = _slicedToArray(wbb.min, 3),
36034
+ minX = _wbb$min[0],
36035
+ minY = _wbb$min[1],
36036
+ minZ = _wbb$min[2];
36037
+ var _wbb$max = _slicedToArray(wbb.max, 3),
36038
+ maxX = _wbb$max[0],
36039
+ maxY = _wbb$max[1],
36040
+ maxZ = _wbb$max[2];
36041
+ var cx = (minX + maxX) / 2;
36042
+ var cy = (minY + maxY) / 2;
36043
+ var cz = (minZ + maxZ) / 2;
36044
+ // Guard against Infinity/NaN from empty or degenerate boxes
36045
+ if (isFinite(cx) && isFinite(cy) && isFinite(cz)) {
36046
+ return {
36047
+ worldWidth: Math.max(maxX - minX, 0.01),
36048
+ worldDepth: Math.max(maxY - minY, 0.01),
36049
+ worldHeight: Math.max(maxZ - minZ, 0.01),
36050
+ bboxCenter: {
36051
+ x: cx,
36052
+ y: cy,
36053
+ z: cz
36054
+ }
36055
+ };
36056
+ }
36033
36057
  }
36058
+ // Fallback: world position of the object, unit dimensions
36059
+ var wp = new THREE__namespace.Vector3();
36060
+ (_component$getWorldPo = component.getWorldPosition) === null || _component$getWorldPo === void 0 || _component$getWorldPo.call(component, wp);
36034
36061
  return {
36035
- worldWidth: worldWidth,
36036
- worldDepth: worldDepth,
36037
- worldHeight: worldHeight
36062
+ worldWidth: 1,
36063
+ worldDepth: 1,
36064
+ worldHeight: 1,
36065
+ bboxCenter: {
36066
+ x: wp.x,
36067
+ y: wp.y,
36068
+ z: wp.z
36069
+ }
36038
36070
  };
36039
36071
  }
36040
36072
 
36041
36073
  /**
36042
- * Project 3D coordinates to 2D based on view type
36074
+ * Project world-space bbox center to 2D based on view type.
36075
+ * worldBoundingBox is an AABB so rotation is already encoded in the extents —
36076
+ * no separate rotation correction is needed.
36043
36077
  * @param {Viewport2DInstance} viewport - The viewport instance
36078
+ * @param {Object} bboxCenter - World-space center {x, y, z}
36079
+ * @param {number} worldWidth - X extent (max[0] - min[0])
36080
+ * @param {number} worldDepth - Y extent (max[1] - min[1])
36081
+ * @param {number} worldHeight - Z extent (max[2] - min[2])
36044
36082
  */
36045
36083
  }, {
36046
36084
  key: "project3DTo2D",
36047
- value: function project3DTo2D(viewport, pos3D, rot3D, worldWidth, worldDepth, worldHeight) {
36048
- var posX, posY, rectWidth, rectHeight;
36049
- var rotationAngle = rot3D.z;
36050
- var rotationDegrees = rotationAngle * 180 / Math.PI;
36085
+ value: function project3DTo2D(viewport, bboxCenter, worldWidth, worldDepth, worldHeight) {
36051
36086
  var scale = viewport.PIXELS_PER_UNIT;
36087
+ var posX, posY, rectWidth, rectHeight;
36052
36088
  switch (viewport.viewType) {
36053
36089
  case 'top':
36054
- // Top view: Looking down Z-axis, X-Y plane
36055
- posX = pos3D.x;
36056
- posY = pos3D.y;
36057
-
36058
- // Swap width and depth when rotated 90° or 270°
36059
- if (Math.abs(rotationDegrees) === 90 || Math.abs(rotationDegrees) === 270) {
36060
- rectWidth = worldDepth * scale;
36061
- rectHeight = worldWidth * scale;
36062
- } else {
36063
- rectWidth = worldWidth * scale;
36064
- rectHeight = worldDepth * scale;
36065
- }
36090
+ // Looking down Z-axis X/Y plane
36091
+ posX = bboxCenter.x;
36092
+ posY = bboxCenter.y;
36093
+ rectWidth = worldWidth * scale;
36094
+ rectHeight = worldDepth * scale;
36066
36095
  break;
36067
36096
  case 'front':
36068
- // Front view: Looking along Y-axis, X-Z plane
36069
- posX = pos3D.x;
36070
- posY = pos3D.z + worldHeight / 2; // Offset Z by half height
36071
-
36072
- if (Math.abs(rotationDegrees) === 90 || Math.abs(rotationDegrees) === 270) {
36073
- rectWidth = worldDepth * scale;
36074
- } else {
36075
- rectWidth = worldWidth * scale;
36076
- }
36097
+ // Looking along Y-axis X/Z plane
36098
+ posX = bboxCenter.x;
36099
+ posY = bboxCenter.z;
36100
+ rectWidth = worldWidth * scale;
36077
36101
  rectHeight = worldHeight * scale;
36078
36102
  break;
36079
36103
  case 'side':
36080
- // Side view: Looking along X-axis, Y-Z plane (flipped)
36081
- posX = -pos3D.y; // Flipped
36082
- posY = pos3D.z + worldHeight / 2;
36083
- if (Math.abs(rotationDegrees) === 90 || Math.abs(rotationDegrees) === 270) {
36084
- rectWidth = worldWidth * scale;
36085
- } else {
36086
- rectWidth = worldDepth * scale;
36087
- }
36104
+ // Looking along X-axis Y/Z plane (Y negated for left-right orientation)
36105
+ posX = -bboxCenter.y;
36106
+ posY = bboxCenter.z;
36107
+ rectWidth = worldDepth * scale;
36088
36108
  rectHeight = worldHeight * scale;
36089
36109
  break;
36090
36110
  default:
36091
- posX = pos3D.x;
36092
- posY = pos3D.y;
36111
+ posX = bboxCenter.x;
36112
+ posY = bboxCenter.y;
36093
36113
  rectWidth = worldWidth * scale;
36094
36114
  rectHeight = worldDepth * scale;
36095
36115
  }
@@ -36097,8 +36117,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
36097
36117
  posX: posX,
36098
36118
  posY: posY,
36099
36119
  rectWidth: rectWidth,
36100
- rectHeight: rectHeight,
36101
- rotationDegrees: rotationDegrees
36120
+ rectHeight: rectHeight
36102
36121
  };
36103
36122
  }
36104
36123
 
@@ -36108,7 +36127,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
36108
36127
  */
36109
36128
  }, {
36110
36129
  key: "addComponentInteractions",
36111
- value: function addComponentInteractions(viewport, rect, componentGroup, component, worldWidth, worldDepth, worldHeight) {
36130
+ value: function addComponentInteractions(viewport, rect, componentGroup, component, worldWidth, worldDepth, worldHeight, bboxCenter) {
36112
36131
  var _this6 = this;
36113
36132
  if (!this.Konva) return;
36114
36133
  var colors = this.generateComponentColor(component.id);
@@ -36191,9 +36210,9 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
36191
36210
  // Convert screen to world coordinates
36192
36211
  var worldCoords = _this6.screenToWorldCoords(viewport, finalPos.x, finalPos.y, scale, worldOriginX, worldOriginY);
36193
36212
 
36194
- // Calculate new position
36213
+ // Calculate new position: delta from old bbox center to new bbox center
36195
36214
  var currentPos = component.position;
36196
- var newPosition = _this6.worldCoordsToObjectPosition(viewport, worldCoords, currentPos, worldWidth, worldDepth, worldHeight);
36215
+ var newPosition = _this6.worldCoordsToObjectPosition(viewport, worldCoords, currentPos, bboxCenter);
36197
36216
 
36198
36217
  // Apply translation via centralPlant API
36199
36218
  var deltaX = newPosition.x - currentPos.x;
@@ -36296,37 +36315,45 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
36296
36315
  }
36297
36316
 
36298
36317
  /**
36299
- * Convert world coordinates to 3D object position
36318
+ * Convert dragged 2D world coordinates to a new 3D object position.
36319
+ * coord1/coord2 represent where the bbox CENTER should be in the view's 2D plane;
36320
+ * we compute the delta from the old bbox center and apply it to the pivot position.
36300
36321
  * @param {Viewport2DInstance} viewport - The viewport instance
36322
+ * @param {{coord1, coord2}} worldCoords - Projected world coordinates from screen
36323
+ * @param {{x,y,z}} currentPosition - Current Three.js object position (pivot)
36324
+ * @param {{x,y,z}} bboxCenter - Old world-space bbox center
36301
36325
  */
36302
36326
  }, {
36303
36327
  key: "worldCoordsToObjectPosition",
36304
- value: function worldCoordsToObjectPosition(viewport, worldCoords, currentPosition, worldWidth, worldDepth, worldHeight) {
36328
+ value: function worldCoordsToObjectPosition(viewport, worldCoords, currentPosition, bboxCenter) {
36305
36329
  var coord1 = worldCoords.coord1,
36306
36330
  coord2 = worldCoords.coord2;
36307
36331
  switch (viewport.viewType) {
36308
36332
  case 'top':
36333
+ // coord1=X, coord2=Y in world space
36309
36334
  return {
36310
- x: coord1,
36311
- y: coord2,
36335
+ x: currentPosition.x + (coord1 - bboxCenter.x),
36336
+ y: currentPosition.y + (coord2 - bboxCenter.y),
36312
36337
  z: currentPosition.z
36313
36338
  };
36314
36339
  case 'front':
36340
+ // coord1=X, coord2=Z in world space
36315
36341
  return {
36316
- x: coord1,
36342
+ x: currentPosition.x + (coord1 - bboxCenter.x),
36317
36343
  y: currentPosition.y,
36318
- z: coord2 - worldHeight / 2
36344
+ z: currentPosition.z + (coord2 - bboxCenter.z)
36319
36345
  };
36320
36346
  case 'side':
36347
+ // coord1=-Y (negated), coord2=Z in world space
36321
36348
  return {
36322
36349
  x: currentPosition.x,
36323
- y: -coord1,
36324
- z: coord2 - worldHeight / 2
36350
+ y: currentPosition.y + (-coord1 - bboxCenter.y),
36351
+ z: currentPosition.z + (coord2 - bboxCenter.z)
36325
36352
  };
36326
36353
  default:
36327
36354
  return {
36328
- x: coord1,
36329
- y: coord2,
36355
+ x: currentPosition.x + (coord1 - bboxCenter.x),
36356
+ y: currentPosition.y + (coord2 - bboxCenter.y),
36330
36357
  z: currentPosition.z
36331
36358
  };
36332
36359
  }
@@ -36344,8 +36371,9 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
36344
36371
  var components = [];
36345
36372
  this.sceneViewer.scene.traverse(function (object) {
36346
36373
  var _object$userData, _object$userData2;
36347
- var objectType = ((_object$userData = object.userData) === null || _object$userData === void 0 ? void 0 : _object$userData.objectType) || ((_object$userData2 = object.userData) === null || _object$userData2 === void 0 ? void 0 : _object$userData2.objectType);
36348
- if (object.userData && objectType === 'component') {
36374
+ // Only match the ROOT component object must have both objectType:'component'
36375
+ // AND libraryId (inner GLB mesh nodes don't have libraryId)
36376
+ if (((_object$userData = object.userData) === null || _object$userData === void 0 ? void 0 : _object$userData.objectType) === 'component' && (_object$userData2 = object.userData) !== null && _object$userData2 !== void 0 && _object$userData2.libraryId) {
36349
36377
  components.push(object);
36350
36378
  }
36351
36379
  });
@@ -37802,7 +37830,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
37802
37830
  * Initialize the CentralPlant manager
37803
37831
  *
37804
37832
  * @constructor
37805
- * @version 0.3.4
37833
+ * @version 0.3.5
37806
37834
  * @updated 2025-10-22
37807
37835
  *
37808
37836
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -35,7 +35,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
35
35
  * Initialize the CentralPlant manager
36
36
  *
37
37
  * @constructor
38
- * @version 0.3.4
38
+ * @version 0.3.5
39
39
  * @updated 2025-10-22
40
40
  *
41
41
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -6,6 +6,7 @@ var _rollupPluginBabelHelpers = require('../../../_virtual/_rollupPluginBabelHel
6
6
  var THREE = require('three');
7
7
  var ioDeviceUtils = require('../../utils/ioDeviceUtils.js');
8
8
  var modelPreloader = require('../../rendering/modelPreloader.js');
9
+ var boundingBoxUtils = require('../../utils/boundingBoxUtils.js');
9
10
 
10
11
  function _interopNamespace(e) {
11
12
  if (e && e.__esModule) return e;
@@ -579,15 +580,21 @@ var ModelManager = /*#__PURE__*/function () {
579
580
  _context5.n = 2;
580
581
  return Promise.all(glbLoadingPromises);
581
582
  case 2:
582
- // Update world bounding boxes for loaded models
583
+ // Update world bounding boxes for loaded models and propagate to the live Three.js objects
583
584
  libraryObjectsToReplace.forEach(function (_ref2) {
584
- var _jsonData$userData2;
585
585
  var jsonData = _ref2.jsonData,
586
586
  glbModel = _ref2.glbModel;
587
- if (glbModel && (_jsonData$userData2 = jsonData.userData) !== null && _jsonData$userData2 !== void 0 && _jsonData$userData2.worldBoundingBox) {
588
- var worldBoundingBox = _this3._calculateWorldBoundingBox(glbModel);
589
- jsonData.userData.worldBoundingBox = worldBoundingBox;
590
- }
587
+ if (!glbModel) return;
588
+ // Use filtered bbox (excludes connectors + io-devices) so it matches
589
+ // what pathfindingManager._enrichSceneDataWithBoundingBoxes produces
590
+ var filteredBox = boundingBoxUtils.computeFilteredBoundingBox(glbModel, ['io-device', 'connector']);
591
+ var worldBoundingBox = filteredBox.isEmpty() ? _this3._calculateWorldBoundingBox(glbModel) : {
592
+ min: [filteredBox.min.x, filteredBox.min.y, filteredBox.min.z],
593
+ max: [filteredBox.max.x, filteredBox.max.y, filteredBox.max.z]
594
+ };
595
+ // Update both the JSON data object AND the live scene object
596
+ jsonData.userData.worldBoundingBox = worldBoundingBox;
597
+ glbModel.userData.worldBoundingBox = worldBoundingBox;
591
598
  });
592
599
 
593
600
  // Dispatch completion event
@@ -4,6 +4,8 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var _rollupPluginBabelHelpers = require('../../../_virtual/_rollupPluginBabelHelpers.js');
6
6
  var baseDisposable = require('../../core/baseDisposable.js');
7
+ var THREE = require('three');
8
+ var boundingBoxUtils = require('../../utils/boundingBoxUtils.js');
7
9
 
8
10
  function _interopNamespace(e) {
9
11
  if (e && e.__esModule) return e;
@@ -23,6 +25,8 @@ function _interopNamespace(e) {
23
25
  return Object.freeze(n);
24
26
  }
25
27
 
28
+ var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
29
+
26
30
  /**
27
31
  * Viewport2DInstance
28
32
  * Represents a single 2D viewport with its own Konva stage and configuration
@@ -550,32 +554,20 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
550
554
  }, {
551
555
  key: "renderComponent",
552
556
  value: function renderComponent(viewport, component, centerX, centerY, scale) {
553
- var _component$position$x, _component$position, _component$position$y, _component$position2, _component$position$z, _component$position3, _component$rotation$x, _component$rotation, _component$rotation$y, _component$rotation2, _component$rotation$z, _component$rotation3;
554
- // Get component position and rotation
555
- var pos3D = {
556
- x: (_component$position$x = (_component$position = component.position) === null || _component$position === void 0 ? void 0 : _component$position.x) !== null && _component$position$x !== void 0 ? _component$position$x : 0,
557
- y: (_component$position$y = (_component$position2 = component.position) === null || _component$position2 === void 0 ? void 0 : _component$position2.y) !== null && _component$position$y !== void 0 ? _component$position$y : 0,
558
- z: (_component$position$z = (_component$position3 = component.position) === null || _component$position3 === void 0 ? void 0 : _component$position3.z) !== null && _component$position$z !== void 0 ? _component$position$z : 0
559
- };
560
- var rot3D = {
561
- x: (_component$rotation$x = (_component$rotation = component.rotation) === null || _component$rotation === void 0 ? void 0 : _component$rotation.x) !== null && _component$rotation$x !== void 0 ? _component$rotation$x : 0,
562
- y: (_component$rotation$y = (_component$rotation2 = component.rotation) === null || _component$rotation2 === void 0 ? void 0 : _component$rotation2.y) !== null && _component$rotation$y !== void 0 ? _component$rotation$y : 0,
563
- z: (_component$rotation$z = (_component$rotation3 = component.rotation) === null || _component$rotation3 === void 0 ? void 0 : _component$rotation3.z) !== null && _component$rotation$z !== void 0 ? _component$rotation$z : 0
564
- };
565
-
566
- // Get bounding box dimensions
557
+ // Get world-space bounding box dimensions and center
567
558
  var _this$getComponentDim = this.getComponentDimensions(component),
568
559
  worldWidth = _this$getComponentDim.worldWidth,
569
560
  worldDepth = _this$getComponentDim.worldDepth,
570
- worldHeight = _this$getComponentDim.worldHeight;
561
+ worldHeight = _this$getComponentDim.worldHeight,
562
+ bboxCenter = _this$getComponentDim.bboxCenter;
563
+ console.log("[2D] ".concat(component.name, " | w=").concat(worldWidth.toFixed(3), " d=").concat(worldDepth.toFixed(3), " h=").concat(worldHeight.toFixed(3), " | center=(").concat(bboxCenter.x.toFixed(2), ",").concat(bboxCenter.y.toFixed(2), ",").concat(bboxCenter.z.toFixed(2), ")"));
571
564
 
572
- // Project 3D coordinates to 2D based on view type
573
- var _this$project3DTo2D = this.project3DTo2D(viewport, pos3D, rot3D, worldWidth, worldDepth, worldHeight),
565
+ // Project 3D bbox center to 2D based on view type
566
+ var _this$project3DTo2D = this.project3DTo2D(viewport, bboxCenter, worldWidth, worldDepth, worldHeight),
574
567
  posX = _this$project3DTo2D.posX,
575
568
  posY = _this$project3DTo2D.posY,
576
569
  rectWidth = _this$project3DTo2D.rectWidth,
577
570
  rectHeight = _this$project3DTo2D.rectHeight;
578
- _this$project3DTo2D.rotationDegrees;
579
571
  var screenX = centerX + posX * scale;
580
572
  var screenY = centerY - posY * scale; // Flip Y for screen coords
581
573
 
@@ -619,105 +611,131 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
619
611
  });
620
612
 
621
613
  // Add mouse event handlers
622
- this.addComponentInteractions(viewport, rect, componentGroup, component, worldWidth, worldDepth, worldHeight);
614
+ this.addComponentInteractions(viewport, rect, componentGroup, component, worldWidth, worldDepth, worldHeight, bboxCenter);
623
615
  componentGroup.add(rect);
624
616
  componentGroup.add(label);
625
617
  viewport.componentLayer.add(componentGroup);
626
618
  }
627
619
 
628
620
  /**
629
- * Get component dimensions from various sources
621
+ * Compute worldBoundingBox for a live Three.js Object3D using the same
622
+ * vertex-accurate approach as computeFilteredBoundingBox (explicitly calls
623
+ * geometry.computeBoundingBox() on each mesh before measuring).
624
+ * @param {THREE.Object3D} object
625
+ * @returns {{min: number[], max: number[]}}
626
+ */
627
+ }, {
628
+ key: "_getOrComputeWorldBoundingBox",
629
+ value: function _getOrComputeWorldBoundingBox(object) {
630
+ // computeFilteredBoundingBox with no exclusions = full object bbox
631
+ var box = boundingBoxUtils.computeFilteredBoundingBox(object, []);
632
+ if (box.isEmpty()) {
633
+ // Object has no geometry; fall back to a point at world position
634
+ var wp = new THREE__namespace.Vector3();
635
+ object.getWorldPosition(wp);
636
+ return {
637
+ min: [wp.x, wp.y, wp.z],
638
+ max: [wp.x, wp.y, wp.z]
639
+ };
640
+ }
641
+ return {
642
+ min: [box.min.x, box.min.y, box.min.z],
643
+ max: [box.max.x, box.max.y, box.max.z]
644
+ };
645
+ }
646
+
647
+ /**
648
+ * Get component dimensions and world-space center from worldBoundingBox
630
649
  */
631
650
  }, {
632
651
  key: "getComponentDimensions",
633
652
  value: function getComponentDimensions(component) {
634
- var _component$userData, _component$userData2, _component$geometry;
635
- var worldWidth = 1,
636
- worldHeight = 1,
637
- worldDepth = 1;
638
-
639
- // Try adaptedBoundingBox first
640
- if ((_component$userData = component.userData) !== null && _component$userData !== void 0 && _component$userData.adaptedBoundingBox) {
641
- var bbox = component.userData.adaptedBoundingBox;
642
- if (bbox.max && bbox.min) {
643
- worldWidth = Math.abs(bbox.max.x - bbox.min.x);
644
- worldDepth = Math.abs(bbox.max.y - bbox.min.y);
645
- worldHeight = Math.abs(bbox.max.z - bbox.min.z);
653
+ var _component$userData$w, _component$userData, _component$getWorldPo;
654
+ // Prefer worldBoundingBox already stored on the object — set after GLB loading
655
+ // by modelManager.replaceWithGLBModels using computeFilteredBoundingBox.
656
+ // Fall back to computing live if not yet available (e.g. first render before GLB load).
657
+ var wbb = (_component$userData$w = (_component$userData = component.userData) === null || _component$userData === void 0 ? void 0 : _component$userData.worldBoundingBox) !== null && _component$userData$w !== void 0 ? _component$userData$w : this._getOrComputeWorldBoundingBox(component);
658
+ if (wbb !== null && wbb !== void 0 && wbb.min && wbb !== null && wbb !== void 0 && wbb.max) {
659
+ var _wbb$min = _rollupPluginBabelHelpers.slicedToArray(wbb.min, 3),
660
+ minX = _wbb$min[0],
661
+ minY = _wbb$min[1],
662
+ minZ = _wbb$min[2];
663
+ var _wbb$max = _rollupPluginBabelHelpers.slicedToArray(wbb.max, 3),
664
+ maxX = _wbb$max[0],
665
+ maxY = _wbb$max[1],
666
+ maxZ = _wbb$max[2];
667
+ var cx = (minX + maxX) / 2;
668
+ var cy = (minY + maxY) / 2;
669
+ var cz = (minZ + maxZ) / 2;
670
+ // Guard against Infinity/NaN from empty or degenerate boxes
671
+ if (isFinite(cx) && isFinite(cy) && isFinite(cz)) {
672
+ return {
673
+ worldWidth: Math.max(maxX - minX, 0.01),
674
+ worldDepth: Math.max(maxY - minY, 0.01),
675
+ worldHeight: Math.max(maxZ - minZ, 0.01),
676
+ bboxCenter: {
677
+ x: cx,
678
+ y: cy,
679
+ z: cz
680
+ }
681
+ };
646
682
  }
647
683
  }
648
- // Fallback to dimensions from userData
649
- else if ((_component$userData2 = component.userData) !== null && _component$userData2 !== void 0 && _component$userData2.dimensions) {
650
- var dims = component.userData.dimensions;
651
- worldWidth = Math.abs(dims.x);
652
- worldDepth = Math.abs(dims.y);
653
- worldHeight = Math.abs(dims.z);
654
- }
655
- // Last resort: geometry bounding box
656
- else if ((_component$geometry = component.geometry) !== null && _component$geometry !== void 0 && _component$geometry.boundingBox) {
657
- var _bbox = component.geometry.boundingBox;
658
- worldWidth = Math.abs(_bbox.max.x - _bbox.min.x);
659
- worldDepth = Math.abs(_bbox.max.y - _bbox.min.y);
660
- worldHeight = Math.abs(_bbox.max.z - _bbox.min.z);
661
- }
684
+ // Fallback: world position of the object, unit dimensions
685
+ var wp = new THREE__namespace.Vector3();
686
+ (_component$getWorldPo = component.getWorldPosition) === null || _component$getWorldPo === void 0 || _component$getWorldPo.call(component, wp);
662
687
  return {
663
- worldWidth: worldWidth,
664
- worldDepth: worldDepth,
665
- worldHeight: worldHeight
688
+ worldWidth: 1,
689
+ worldDepth: 1,
690
+ worldHeight: 1,
691
+ bboxCenter: {
692
+ x: wp.x,
693
+ y: wp.y,
694
+ z: wp.z
695
+ }
666
696
  };
667
697
  }
668
698
 
669
699
  /**
670
- * Project 3D coordinates to 2D based on view type
700
+ * Project world-space bbox center to 2D based on view type.
701
+ * worldBoundingBox is an AABB so rotation is already encoded in the extents —
702
+ * no separate rotation correction is needed.
671
703
  * @param {Viewport2DInstance} viewport - The viewport instance
704
+ * @param {Object} bboxCenter - World-space center {x, y, z}
705
+ * @param {number} worldWidth - X extent (max[0] - min[0])
706
+ * @param {number} worldDepth - Y extent (max[1] - min[1])
707
+ * @param {number} worldHeight - Z extent (max[2] - min[2])
672
708
  */
673
709
  }, {
674
710
  key: "project3DTo2D",
675
- value: function project3DTo2D(viewport, pos3D, rot3D, worldWidth, worldDepth, worldHeight) {
676
- var posX, posY, rectWidth, rectHeight;
677
- var rotationAngle = rot3D.z;
678
- var rotationDegrees = rotationAngle * 180 / Math.PI;
711
+ value: function project3DTo2D(viewport, bboxCenter, worldWidth, worldDepth, worldHeight) {
679
712
  var scale = viewport.PIXELS_PER_UNIT;
713
+ var posX, posY, rectWidth, rectHeight;
680
714
  switch (viewport.viewType) {
681
715
  case 'top':
682
- // Top view: Looking down Z-axis, X-Y plane
683
- posX = pos3D.x;
684
- posY = pos3D.y;
685
-
686
- // Swap width and depth when rotated 90° or 270°
687
- if (Math.abs(rotationDegrees) === 90 || Math.abs(rotationDegrees) === 270) {
688
- rectWidth = worldDepth * scale;
689
- rectHeight = worldWidth * scale;
690
- } else {
691
- rectWidth = worldWidth * scale;
692
- rectHeight = worldDepth * scale;
693
- }
716
+ // Looking down Z-axis X/Y plane
717
+ posX = bboxCenter.x;
718
+ posY = bboxCenter.y;
719
+ rectWidth = worldWidth * scale;
720
+ rectHeight = worldDepth * scale;
694
721
  break;
695
722
  case 'front':
696
- // Front view: Looking along Y-axis, X-Z plane
697
- posX = pos3D.x;
698
- posY = pos3D.z + worldHeight / 2; // Offset Z by half height
699
-
700
- if (Math.abs(rotationDegrees) === 90 || Math.abs(rotationDegrees) === 270) {
701
- rectWidth = worldDepth * scale;
702
- } else {
703
- rectWidth = worldWidth * scale;
704
- }
723
+ // Looking along Y-axis X/Z plane
724
+ posX = bboxCenter.x;
725
+ posY = bboxCenter.z;
726
+ rectWidth = worldWidth * scale;
705
727
  rectHeight = worldHeight * scale;
706
728
  break;
707
729
  case 'side':
708
- // Side view: Looking along X-axis, Y-Z plane (flipped)
709
- posX = -pos3D.y; // Flipped
710
- posY = pos3D.z + worldHeight / 2;
711
- if (Math.abs(rotationDegrees) === 90 || Math.abs(rotationDegrees) === 270) {
712
- rectWidth = worldWidth * scale;
713
- } else {
714
- rectWidth = worldDepth * scale;
715
- }
730
+ // Looking along X-axis Y/Z plane (Y negated for left-right orientation)
731
+ posX = -bboxCenter.y;
732
+ posY = bboxCenter.z;
733
+ rectWidth = worldDepth * scale;
716
734
  rectHeight = worldHeight * scale;
717
735
  break;
718
736
  default:
719
- posX = pos3D.x;
720
- posY = pos3D.y;
737
+ posX = bboxCenter.x;
738
+ posY = bboxCenter.y;
721
739
  rectWidth = worldWidth * scale;
722
740
  rectHeight = worldDepth * scale;
723
741
  }
@@ -725,8 +743,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
725
743
  posX: posX,
726
744
  posY: posY,
727
745
  rectWidth: rectWidth,
728
- rectHeight: rectHeight,
729
- rotationDegrees: rotationDegrees
746
+ rectHeight: rectHeight
730
747
  };
731
748
  }
732
749
 
@@ -736,7 +753,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
736
753
  */
737
754
  }, {
738
755
  key: "addComponentInteractions",
739
- value: function addComponentInteractions(viewport, rect, componentGroup, component, worldWidth, worldDepth, worldHeight) {
756
+ value: function addComponentInteractions(viewport, rect, componentGroup, component, worldWidth, worldDepth, worldHeight, bboxCenter) {
740
757
  var _this6 = this;
741
758
  if (!this.Konva) return;
742
759
  var colors = this.generateComponentColor(component.id);
@@ -819,9 +836,9 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
819
836
  // Convert screen to world coordinates
820
837
  var worldCoords = _this6.screenToWorldCoords(viewport, finalPos.x, finalPos.y, scale, worldOriginX, worldOriginY);
821
838
 
822
- // Calculate new position
839
+ // Calculate new position: delta from old bbox center to new bbox center
823
840
  var currentPos = component.position;
824
- var newPosition = _this6.worldCoordsToObjectPosition(viewport, worldCoords, currentPos, worldWidth, worldDepth, worldHeight);
841
+ var newPosition = _this6.worldCoordsToObjectPosition(viewport, worldCoords, currentPos, bboxCenter);
825
842
 
826
843
  // Apply translation via centralPlant API
827
844
  var deltaX = newPosition.x - currentPos.x;
@@ -924,37 +941,45 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
924
941
  }
925
942
 
926
943
  /**
927
- * Convert world coordinates to 3D object position
944
+ * Convert dragged 2D world coordinates to a new 3D object position.
945
+ * coord1/coord2 represent where the bbox CENTER should be in the view's 2D plane;
946
+ * we compute the delta from the old bbox center and apply it to the pivot position.
928
947
  * @param {Viewport2DInstance} viewport - The viewport instance
948
+ * @param {{coord1, coord2}} worldCoords - Projected world coordinates from screen
949
+ * @param {{x,y,z}} currentPosition - Current Three.js object position (pivot)
950
+ * @param {{x,y,z}} bboxCenter - Old world-space bbox center
929
951
  */
930
952
  }, {
931
953
  key: "worldCoordsToObjectPosition",
932
- value: function worldCoordsToObjectPosition(viewport, worldCoords, currentPosition, worldWidth, worldDepth, worldHeight) {
954
+ value: function worldCoordsToObjectPosition(viewport, worldCoords, currentPosition, bboxCenter) {
933
955
  var coord1 = worldCoords.coord1,
934
956
  coord2 = worldCoords.coord2;
935
957
  switch (viewport.viewType) {
936
958
  case 'top':
959
+ // coord1=X, coord2=Y in world space
937
960
  return {
938
- x: coord1,
939
- y: coord2,
961
+ x: currentPosition.x + (coord1 - bboxCenter.x),
962
+ y: currentPosition.y + (coord2 - bboxCenter.y),
940
963
  z: currentPosition.z
941
964
  };
942
965
  case 'front':
966
+ // coord1=X, coord2=Z in world space
943
967
  return {
944
- x: coord1,
968
+ x: currentPosition.x + (coord1 - bboxCenter.x),
945
969
  y: currentPosition.y,
946
- z: coord2 - worldHeight / 2
970
+ z: currentPosition.z + (coord2 - bboxCenter.z)
947
971
  };
948
972
  case 'side':
973
+ // coord1=-Y (negated), coord2=Z in world space
949
974
  return {
950
975
  x: currentPosition.x,
951
- y: -coord1,
952
- z: coord2 - worldHeight / 2
976
+ y: currentPosition.y + (-coord1 - bboxCenter.y),
977
+ z: currentPosition.z + (coord2 - bboxCenter.z)
953
978
  };
954
979
  default:
955
980
  return {
956
- x: coord1,
957
- y: coord2,
981
+ x: currentPosition.x + (coord1 - bboxCenter.x),
982
+ y: currentPosition.y + (coord2 - bboxCenter.y),
958
983
  z: currentPosition.z
959
984
  };
960
985
  }
@@ -972,8 +997,9 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
972
997
  var components = [];
973
998
  this.sceneViewer.scene.traverse(function (object) {
974
999
  var _object$userData, _object$userData2;
975
- var objectType = ((_object$userData = object.userData) === null || _object$userData === void 0 ? void 0 : _object$userData.objectType) || ((_object$userData2 = object.userData) === null || _object$userData2 === void 0 ? void 0 : _object$userData2.objectType);
976
- if (object.userData && objectType === 'component') {
1000
+ // Only match the ROOT component object must have both objectType:'component'
1001
+ // AND libraryId (inner GLB mesh nodes don't have libraryId)
1002
+ if (((_object$userData = object.userData) === null || _object$userData === void 0 ? void 0 : _object$userData.objectType) === 'component' && (_object$userData2 = object.userData) !== null && _object$userData2 !== void 0 && _object$userData2.libraryId) {
977
1003
  components.push(object);
978
1004
  }
979
1005
  });
@@ -31,7 +31,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
31
31
  * Initialize the CentralPlant manager
32
32
  *
33
33
  * @constructor
34
- * @version 0.3.4
34
+ * @version 0.3.5
35
35
  * @updated 2025-10-22
36
36
  *
37
37
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -2,6 +2,7 @@ import { createClass as _createClass, objectSpread2 as _objectSpread2, toConsuma
2
2
  import * as THREE from 'three';
3
3
  import { attachIODevicesToComponent } from '../../utils/ioDeviceUtils.js';
4
4
  import modelPreloader from '../../rendering/modelPreloader.js';
5
+ import { computeFilteredBoundingBox } from '../../utils/boundingBoxUtils.js';
5
6
 
6
7
  var ModelManager = /*#__PURE__*/function () {
7
8
  function ModelManager(sceneViewer) {
@@ -555,15 +556,21 @@ var ModelManager = /*#__PURE__*/function () {
555
556
  _context5.n = 2;
556
557
  return Promise.all(glbLoadingPromises);
557
558
  case 2:
558
- // Update world bounding boxes for loaded models
559
+ // Update world bounding boxes for loaded models and propagate to the live Three.js objects
559
560
  libraryObjectsToReplace.forEach(function (_ref2) {
560
- var _jsonData$userData2;
561
561
  var jsonData = _ref2.jsonData,
562
562
  glbModel = _ref2.glbModel;
563
- if (glbModel && (_jsonData$userData2 = jsonData.userData) !== null && _jsonData$userData2 !== void 0 && _jsonData$userData2.worldBoundingBox) {
564
- var worldBoundingBox = _this3._calculateWorldBoundingBox(glbModel);
565
- jsonData.userData.worldBoundingBox = worldBoundingBox;
566
- }
563
+ if (!glbModel) return;
564
+ // Use filtered bbox (excludes connectors + io-devices) so it matches
565
+ // what pathfindingManager._enrichSceneDataWithBoundingBoxes produces
566
+ var filteredBox = computeFilteredBoundingBox(glbModel, ['io-device', 'connector']);
567
+ var worldBoundingBox = filteredBox.isEmpty() ? _this3._calculateWorldBoundingBox(glbModel) : {
568
+ min: [filteredBox.min.x, filteredBox.min.y, filteredBox.min.z],
569
+ max: [filteredBox.max.x, filteredBox.max.y, filteredBox.max.z]
570
+ };
571
+ // Update both the JSON data object AND the live scene object
572
+ jsonData.userData.worldBoundingBox = worldBoundingBox;
573
+ glbModel.userData.worldBoundingBox = worldBoundingBox;
567
574
  });
568
575
 
569
576
  // Dispatch completion event
@@ -1,5 +1,7 @@
1
- import { inherits as _inherits, createClass as _createClass, createForOfIteratorHelper as _createForOfIteratorHelper, slicedToArray as _slicedToArray, superPropGet as _superPropGet, classCallCheck as _classCallCheck, callSuper as _callSuper, asyncToGenerator as _asyncToGenerator, regenerator as _regenerator } from '../../../_virtual/_rollupPluginBabelHelpers.js';
1
+ import { inherits as _inherits, createClass as _createClass, slicedToArray as _slicedToArray, createForOfIteratorHelper as _createForOfIteratorHelper, superPropGet as _superPropGet, classCallCheck as _classCallCheck, callSuper as _callSuper, asyncToGenerator as _asyncToGenerator, regenerator as _regenerator } from '../../../_virtual/_rollupPluginBabelHelpers.js';
2
2
  import { BaseDisposable } from '../../core/baseDisposable.js';
3
+ import * as THREE from 'three';
4
+ import { computeFilteredBoundingBox } from '../../utils/boundingBoxUtils.js';
3
5
 
4
6
  /**
5
7
  * Viewport2DInstance
@@ -528,32 +530,20 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
528
530
  }, {
529
531
  key: "renderComponent",
530
532
  value: function renderComponent(viewport, component, centerX, centerY, scale) {
531
- var _component$position$x, _component$position, _component$position$y, _component$position2, _component$position$z, _component$position3, _component$rotation$x, _component$rotation, _component$rotation$y, _component$rotation2, _component$rotation$z, _component$rotation3;
532
- // Get component position and rotation
533
- var pos3D = {
534
- x: (_component$position$x = (_component$position = component.position) === null || _component$position === void 0 ? void 0 : _component$position.x) !== null && _component$position$x !== void 0 ? _component$position$x : 0,
535
- y: (_component$position$y = (_component$position2 = component.position) === null || _component$position2 === void 0 ? void 0 : _component$position2.y) !== null && _component$position$y !== void 0 ? _component$position$y : 0,
536
- z: (_component$position$z = (_component$position3 = component.position) === null || _component$position3 === void 0 ? void 0 : _component$position3.z) !== null && _component$position$z !== void 0 ? _component$position$z : 0
537
- };
538
- var rot3D = {
539
- x: (_component$rotation$x = (_component$rotation = component.rotation) === null || _component$rotation === void 0 ? void 0 : _component$rotation.x) !== null && _component$rotation$x !== void 0 ? _component$rotation$x : 0,
540
- y: (_component$rotation$y = (_component$rotation2 = component.rotation) === null || _component$rotation2 === void 0 ? void 0 : _component$rotation2.y) !== null && _component$rotation$y !== void 0 ? _component$rotation$y : 0,
541
- z: (_component$rotation$z = (_component$rotation3 = component.rotation) === null || _component$rotation3 === void 0 ? void 0 : _component$rotation3.z) !== null && _component$rotation$z !== void 0 ? _component$rotation$z : 0
542
- };
543
-
544
- // Get bounding box dimensions
533
+ // Get world-space bounding box dimensions and center
545
534
  var _this$getComponentDim = this.getComponentDimensions(component),
546
535
  worldWidth = _this$getComponentDim.worldWidth,
547
536
  worldDepth = _this$getComponentDim.worldDepth,
548
- worldHeight = _this$getComponentDim.worldHeight;
537
+ worldHeight = _this$getComponentDim.worldHeight,
538
+ bboxCenter = _this$getComponentDim.bboxCenter;
539
+ console.log("[2D] ".concat(component.name, " | w=").concat(worldWidth.toFixed(3), " d=").concat(worldDepth.toFixed(3), " h=").concat(worldHeight.toFixed(3), " | center=(").concat(bboxCenter.x.toFixed(2), ",").concat(bboxCenter.y.toFixed(2), ",").concat(bboxCenter.z.toFixed(2), ")"));
549
540
 
550
- // Project 3D coordinates to 2D based on view type
551
- var _this$project3DTo2D = this.project3DTo2D(viewport, pos3D, rot3D, worldWidth, worldDepth, worldHeight),
541
+ // Project 3D bbox center to 2D based on view type
542
+ var _this$project3DTo2D = this.project3DTo2D(viewport, bboxCenter, worldWidth, worldDepth, worldHeight),
552
543
  posX = _this$project3DTo2D.posX,
553
544
  posY = _this$project3DTo2D.posY,
554
545
  rectWidth = _this$project3DTo2D.rectWidth,
555
546
  rectHeight = _this$project3DTo2D.rectHeight;
556
- _this$project3DTo2D.rotationDegrees;
557
547
  var screenX = centerX + posX * scale;
558
548
  var screenY = centerY - posY * scale; // Flip Y for screen coords
559
549
 
@@ -597,105 +587,131 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
597
587
  });
598
588
 
599
589
  // Add mouse event handlers
600
- this.addComponentInteractions(viewport, rect, componentGroup, component, worldWidth, worldDepth, worldHeight);
590
+ this.addComponentInteractions(viewport, rect, componentGroup, component, worldWidth, worldDepth, worldHeight, bboxCenter);
601
591
  componentGroup.add(rect);
602
592
  componentGroup.add(label);
603
593
  viewport.componentLayer.add(componentGroup);
604
594
  }
605
595
 
606
596
  /**
607
- * Get component dimensions from various sources
597
+ * Compute worldBoundingBox for a live Three.js Object3D using the same
598
+ * vertex-accurate approach as computeFilteredBoundingBox (explicitly calls
599
+ * geometry.computeBoundingBox() on each mesh before measuring).
600
+ * @param {THREE.Object3D} object
601
+ * @returns {{min: number[], max: number[]}}
602
+ */
603
+ }, {
604
+ key: "_getOrComputeWorldBoundingBox",
605
+ value: function _getOrComputeWorldBoundingBox(object) {
606
+ // computeFilteredBoundingBox with no exclusions = full object bbox
607
+ var box = computeFilteredBoundingBox(object, []);
608
+ if (box.isEmpty()) {
609
+ // Object has no geometry; fall back to a point at world position
610
+ var wp = new THREE.Vector3();
611
+ object.getWorldPosition(wp);
612
+ return {
613
+ min: [wp.x, wp.y, wp.z],
614
+ max: [wp.x, wp.y, wp.z]
615
+ };
616
+ }
617
+ return {
618
+ min: [box.min.x, box.min.y, box.min.z],
619
+ max: [box.max.x, box.max.y, box.max.z]
620
+ };
621
+ }
622
+
623
+ /**
624
+ * Get component dimensions and world-space center from worldBoundingBox
608
625
  */
609
626
  }, {
610
627
  key: "getComponentDimensions",
611
628
  value: function getComponentDimensions(component) {
612
- var _component$userData, _component$userData2, _component$geometry;
613
- var worldWidth = 1,
614
- worldHeight = 1,
615
- worldDepth = 1;
616
-
617
- // Try adaptedBoundingBox first
618
- if ((_component$userData = component.userData) !== null && _component$userData !== void 0 && _component$userData.adaptedBoundingBox) {
619
- var bbox = component.userData.adaptedBoundingBox;
620
- if (bbox.max && bbox.min) {
621
- worldWidth = Math.abs(bbox.max.x - bbox.min.x);
622
- worldDepth = Math.abs(bbox.max.y - bbox.min.y);
623
- worldHeight = Math.abs(bbox.max.z - bbox.min.z);
629
+ var _component$userData$w, _component$userData, _component$getWorldPo;
630
+ // Prefer worldBoundingBox already stored on the object — set after GLB loading
631
+ // by modelManager.replaceWithGLBModels using computeFilteredBoundingBox.
632
+ // Fall back to computing live if not yet available (e.g. first render before GLB load).
633
+ var wbb = (_component$userData$w = (_component$userData = component.userData) === null || _component$userData === void 0 ? void 0 : _component$userData.worldBoundingBox) !== null && _component$userData$w !== void 0 ? _component$userData$w : this._getOrComputeWorldBoundingBox(component);
634
+ if (wbb !== null && wbb !== void 0 && wbb.min && wbb !== null && wbb !== void 0 && wbb.max) {
635
+ var _wbb$min = _slicedToArray(wbb.min, 3),
636
+ minX = _wbb$min[0],
637
+ minY = _wbb$min[1],
638
+ minZ = _wbb$min[2];
639
+ var _wbb$max = _slicedToArray(wbb.max, 3),
640
+ maxX = _wbb$max[0],
641
+ maxY = _wbb$max[1],
642
+ maxZ = _wbb$max[2];
643
+ var cx = (minX + maxX) / 2;
644
+ var cy = (minY + maxY) / 2;
645
+ var cz = (minZ + maxZ) / 2;
646
+ // Guard against Infinity/NaN from empty or degenerate boxes
647
+ if (isFinite(cx) && isFinite(cy) && isFinite(cz)) {
648
+ return {
649
+ worldWidth: Math.max(maxX - minX, 0.01),
650
+ worldDepth: Math.max(maxY - minY, 0.01),
651
+ worldHeight: Math.max(maxZ - minZ, 0.01),
652
+ bboxCenter: {
653
+ x: cx,
654
+ y: cy,
655
+ z: cz
656
+ }
657
+ };
624
658
  }
625
659
  }
626
- // Fallback to dimensions from userData
627
- else if ((_component$userData2 = component.userData) !== null && _component$userData2 !== void 0 && _component$userData2.dimensions) {
628
- var dims = component.userData.dimensions;
629
- worldWidth = Math.abs(dims.x);
630
- worldDepth = Math.abs(dims.y);
631
- worldHeight = Math.abs(dims.z);
632
- }
633
- // Last resort: geometry bounding box
634
- else if ((_component$geometry = component.geometry) !== null && _component$geometry !== void 0 && _component$geometry.boundingBox) {
635
- var _bbox = component.geometry.boundingBox;
636
- worldWidth = Math.abs(_bbox.max.x - _bbox.min.x);
637
- worldDepth = Math.abs(_bbox.max.y - _bbox.min.y);
638
- worldHeight = Math.abs(_bbox.max.z - _bbox.min.z);
639
- }
660
+ // Fallback: world position of the object, unit dimensions
661
+ var wp = new THREE.Vector3();
662
+ (_component$getWorldPo = component.getWorldPosition) === null || _component$getWorldPo === void 0 || _component$getWorldPo.call(component, wp);
640
663
  return {
641
- worldWidth: worldWidth,
642
- worldDepth: worldDepth,
643
- worldHeight: worldHeight
664
+ worldWidth: 1,
665
+ worldDepth: 1,
666
+ worldHeight: 1,
667
+ bboxCenter: {
668
+ x: wp.x,
669
+ y: wp.y,
670
+ z: wp.z
671
+ }
644
672
  };
645
673
  }
646
674
 
647
675
  /**
648
- * Project 3D coordinates to 2D based on view type
676
+ * Project world-space bbox center to 2D based on view type.
677
+ * worldBoundingBox is an AABB so rotation is already encoded in the extents —
678
+ * no separate rotation correction is needed.
649
679
  * @param {Viewport2DInstance} viewport - The viewport instance
680
+ * @param {Object} bboxCenter - World-space center {x, y, z}
681
+ * @param {number} worldWidth - X extent (max[0] - min[0])
682
+ * @param {number} worldDepth - Y extent (max[1] - min[1])
683
+ * @param {number} worldHeight - Z extent (max[2] - min[2])
650
684
  */
651
685
  }, {
652
686
  key: "project3DTo2D",
653
- value: function project3DTo2D(viewport, pos3D, rot3D, worldWidth, worldDepth, worldHeight) {
654
- var posX, posY, rectWidth, rectHeight;
655
- var rotationAngle = rot3D.z;
656
- var rotationDegrees = rotationAngle * 180 / Math.PI;
687
+ value: function project3DTo2D(viewport, bboxCenter, worldWidth, worldDepth, worldHeight) {
657
688
  var scale = viewport.PIXELS_PER_UNIT;
689
+ var posX, posY, rectWidth, rectHeight;
658
690
  switch (viewport.viewType) {
659
691
  case 'top':
660
- // Top view: Looking down Z-axis, X-Y plane
661
- posX = pos3D.x;
662
- posY = pos3D.y;
663
-
664
- // Swap width and depth when rotated 90° or 270°
665
- if (Math.abs(rotationDegrees) === 90 || Math.abs(rotationDegrees) === 270) {
666
- rectWidth = worldDepth * scale;
667
- rectHeight = worldWidth * scale;
668
- } else {
669
- rectWidth = worldWidth * scale;
670
- rectHeight = worldDepth * scale;
671
- }
692
+ // Looking down Z-axis X/Y plane
693
+ posX = bboxCenter.x;
694
+ posY = bboxCenter.y;
695
+ rectWidth = worldWidth * scale;
696
+ rectHeight = worldDepth * scale;
672
697
  break;
673
698
  case 'front':
674
- // Front view: Looking along Y-axis, X-Z plane
675
- posX = pos3D.x;
676
- posY = pos3D.z + worldHeight / 2; // Offset Z by half height
677
-
678
- if (Math.abs(rotationDegrees) === 90 || Math.abs(rotationDegrees) === 270) {
679
- rectWidth = worldDepth * scale;
680
- } else {
681
- rectWidth = worldWidth * scale;
682
- }
699
+ // Looking along Y-axis X/Z plane
700
+ posX = bboxCenter.x;
701
+ posY = bboxCenter.z;
702
+ rectWidth = worldWidth * scale;
683
703
  rectHeight = worldHeight * scale;
684
704
  break;
685
705
  case 'side':
686
- // Side view: Looking along X-axis, Y-Z plane (flipped)
687
- posX = -pos3D.y; // Flipped
688
- posY = pos3D.z + worldHeight / 2;
689
- if (Math.abs(rotationDegrees) === 90 || Math.abs(rotationDegrees) === 270) {
690
- rectWidth = worldWidth * scale;
691
- } else {
692
- rectWidth = worldDepth * scale;
693
- }
706
+ // Looking along X-axis Y/Z plane (Y negated for left-right orientation)
707
+ posX = -bboxCenter.y;
708
+ posY = bboxCenter.z;
709
+ rectWidth = worldDepth * scale;
694
710
  rectHeight = worldHeight * scale;
695
711
  break;
696
712
  default:
697
- posX = pos3D.x;
698
- posY = pos3D.y;
713
+ posX = bboxCenter.x;
714
+ posY = bboxCenter.y;
699
715
  rectWidth = worldWidth * scale;
700
716
  rectHeight = worldDepth * scale;
701
717
  }
@@ -703,8 +719,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
703
719
  posX: posX,
704
720
  posY: posY,
705
721
  rectWidth: rectWidth,
706
- rectHeight: rectHeight,
707
- rotationDegrees: rotationDegrees
722
+ rectHeight: rectHeight
708
723
  };
709
724
  }
710
725
 
@@ -714,7 +729,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
714
729
  */
715
730
  }, {
716
731
  key: "addComponentInteractions",
717
- value: function addComponentInteractions(viewport, rect, componentGroup, component, worldWidth, worldDepth, worldHeight) {
732
+ value: function addComponentInteractions(viewport, rect, componentGroup, component, worldWidth, worldDepth, worldHeight, bboxCenter) {
718
733
  var _this6 = this;
719
734
  if (!this.Konva) return;
720
735
  var colors = this.generateComponentColor(component.id);
@@ -797,9 +812,9 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
797
812
  // Convert screen to world coordinates
798
813
  var worldCoords = _this6.screenToWorldCoords(viewport, finalPos.x, finalPos.y, scale, worldOriginX, worldOriginY);
799
814
 
800
- // Calculate new position
815
+ // Calculate new position: delta from old bbox center to new bbox center
801
816
  var currentPos = component.position;
802
- var newPosition = _this6.worldCoordsToObjectPosition(viewport, worldCoords, currentPos, worldWidth, worldDepth, worldHeight);
817
+ var newPosition = _this6.worldCoordsToObjectPosition(viewport, worldCoords, currentPos, bboxCenter);
803
818
 
804
819
  // Apply translation via centralPlant API
805
820
  var deltaX = newPosition.x - currentPos.x;
@@ -902,37 +917,45 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
902
917
  }
903
918
 
904
919
  /**
905
- * Convert world coordinates to 3D object position
920
+ * Convert dragged 2D world coordinates to a new 3D object position.
921
+ * coord1/coord2 represent where the bbox CENTER should be in the view's 2D plane;
922
+ * we compute the delta from the old bbox center and apply it to the pivot position.
906
923
  * @param {Viewport2DInstance} viewport - The viewport instance
924
+ * @param {{coord1, coord2}} worldCoords - Projected world coordinates from screen
925
+ * @param {{x,y,z}} currentPosition - Current Three.js object position (pivot)
926
+ * @param {{x,y,z}} bboxCenter - Old world-space bbox center
907
927
  */
908
928
  }, {
909
929
  key: "worldCoordsToObjectPosition",
910
- value: function worldCoordsToObjectPosition(viewport, worldCoords, currentPosition, worldWidth, worldDepth, worldHeight) {
930
+ value: function worldCoordsToObjectPosition(viewport, worldCoords, currentPosition, bboxCenter) {
911
931
  var coord1 = worldCoords.coord1,
912
932
  coord2 = worldCoords.coord2;
913
933
  switch (viewport.viewType) {
914
934
  case 'top':
935
+ // coord1=X, coord2=Y in world space
915
936
  return {
916
- x: coord1,
917
- y: coord2,
937
+ x: currentPosition.x + (coord1 - bboxCenter.x),
938
+ y: currentPosition.y + (coord2 - bboxCenter.y),
918
939
  z: currentPosition.z
919
940
  };
920
941
  case 'front':
942
+ // coord1=X, coord2=Z in world space
921
943
  return {
922
- x: coord1,
944
+ x: currentPosition.x + (coord1 - bboxCenter.x),
923
945
  y: currentPosition.y,
924
- z: coord2 - worldHeight / 2
946
+ z: currentPosition.z + (coord2 - bboxCenter.z)
925
947
  };
926
948
  case 'side':
949
+ // coord1=-Y (negated), coord2=Z in world space
927
950
  return {
928
951
  x: currentPosition.x,
929
- y: -coord1,
930
- z: coord2 - worldHeight / 2
952
+ y: currentPosition.y + (-coord1 - bboxCenter.y),
953
+ z: currentPosition.z + (coord2 - bboxCenter.z)
931
954
  };
932
955
  default:
933
956
  return {
934
- x: coord1,
935
- y: coord2,
957
+ x: currentPosition.x + (coord1 - bboxCenter.x),
958
+ y: currentPosition.y + (coord2 - bboxCenter.y),
936
959
  z: currentPosition.z
937
960
  };
938
961
  }
@@ -950,8 +973,9 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
950
973
  var components = [];
951
974
  this.sceneViewer.scene.traverse(function (object) {
952
975
  var _object$userData, _object$userData2;
953
- var objectType = ((_object$userData = object.userData) === null || _object$userData === void 0 ? void 0 : _object$userData.objectType) || ((_object$userData2 = object.userData) === null || _object$userData2 === void 0 ? void 0 : _object$userData2.objectType);
954
- if (object.userData && objectType === 'component') {
976
+ // Only match the ROOT component object must have both objectType:'component'
977
+ // AND libraryId (inner GLB mesh nodes don't have libraryId)
978
+ if (((_object$userData = object.userData) === null || _object$userData === void 0 ? void 0 : _object$userData.objectType) === 'component' && (_object$userData2 = object.userData) !== null && _object$userData2 !== void 0 && _object$userData2.libraryId) {
955
979
  components.push(object);
956
980
  }
957
981
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2112-lab/central-plant",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
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",