@2112-lab/central-plant 0.3.2 → 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.
@@ -11477,40 +11477,17 @@ var SceneExportManager = /*#__PURE__*/function () {
11477
11477
  };
11478
11478
  }
11479
11479
 
11480
- // For components: only export connector/gateway children (not mesh geometry)
11481
- // For manual segments: export the segment's connector children
11482
- // For connectors/gateways: no children (they're leaf nodes)
11480
+ // For components: no children exported — connector positions are defined in the component
11481
+ // dictionary GLB and will be re-injected at import time via _injectConnectorChildrenFromDictionary.
11482
+ // Exporting them here would prevent that injection (it skips if children already exist)
11483
+ // and would leave the pathfinder with connectors in incompatible local-position format.
11483
11484
  if (threeObject.children && threeObject.children.length > 0) {
11484
11485
  var exportableChildren = [];
11485
- if (threeObject.userData.objectType === 'component') {
11486
- // Export connector children (skip mesh geometry - it lives in the component dictionary GLB)
11487
- threeObject.children.forEach(function (child) {
11488
- var _child$userData;
11489
- if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'connector') {
11490
- var connectorJson = {
11491
- // Use the raw Three.js UUID — connections in currentSceneData reference this exact value
11492
- uuid: child.uuid,
11493
- userData: _objectSpread2(_objectSpread2({
11494
- objectType: 'connector'
11495
- }, child.userData.direction ? {
11496
- direction: child.userData.direction
11497
- } : {}), child.userData.group ? {
11498
- group: child.userData.group
11499
- } : {}),
11500
- position: {
11501
- x: roundIfClose(child.position.x),
11502
- y: roundIfClose(child.position.y),
11503
- z: roundIfClose(child.position.z)
11504
- }
11505
- };
11506
- exportableChildren.push(connectorJson);
11507
- }
11508
- });
11509
- } else if (isManualSegment) {
11486
+ if (isManualSegment) {
11510
11487
  // For manual segments, export their connector children
11511
11488
  threeObject.children.forEach(function (child) {
11512
- var _child$userData2;
11513
- if (((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType) === 'segment-connector') {
11489
+ var _child$userData;
11490
+ if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'segment-connector') {
11514
11491
  // Call the class method using bound reference
11515
11492
  var connectorJson = convertSegmentConnectorToJson(child, threeObject);
11516
11493
  if (connectorJson) {
@@ -11529,38 +11506,18 @@ var SceneExportManager = /*#__PURE__*/function () {
11529
11506
  // Bind the convertSegmentConnectorToJson method for use in nested function
11530
11507
  var convertSegmentConnectorToJson = this.convertSegmentConnectorToJson.bind(this);
11531
11508
 
11532
- // Extract main scene objects (components and standalone connectors/gateways)
11509
+ // Extract main scene objects (components and standalone connectors)
11533
11510
  var sceneChildren = [];
11534
-
11535
- // Helper to recursively find manual segments within polylines
11536
- var findManualSegments = function findManualSegments(object) {
11537
- var manualSegments = [];
11538
- object.traverse(function (child) {
11539
- var _child$userData3;
11540
- // Check if this is a manual segment
11541
- if (((_child$userData3 = child.userData) === null || _child$userData3 === void 0 ? void 0 : _child$userData3.objectType) === 'segment') {
11542
- manualSegments.push(child);
11543
- }
11544
- });
11545
- return manualSegments;
11546
- };
11547
11511
  this.sceneViewer.scene.children.forEach(function (child) {
11548
- var _child$name;
11549
- // Check if this is a polyline - if so, extract manual segments from it
11550
- if ((_child$name = child.name) !== null && _child$name !== void 0 && _child$name.includes('Polyline')) {
11551
- var manualSegments = findManualSegments(child);
11552
- manualSegments.forEach(function (segment) {
11553
- var jsonSegment = convertObjectToJson(segment);
11554
- if (jsonSegment) {
11555
- sceneChildren.push(jsonSegment);
11556
- }
11557
- });
11558
- } else {
11559
- // Regular scene object export
11560
- var jsonChild = convertObjectToJson(child);
11561
- if (jsonChild) {
11562
- sceneChildren.push(jsonChild);
11563
- }
11512
+ var _child$userData2;
11513
+ // Only export components and connectors; skip segments, gateways, polylines, etc.
11514
+ var objectType = (_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType;
11515
+ if (objectType !== 'component' && objectType !== 'connector') {
11516
+ return;
11517
+ }
11518
+ var jsonChild = convertObjectToJson(child);
11519
+ if (jsonChild) {
11520
+ sceneChildren.push(jsonChild);
11564
11521
  }
11565
11522
  });
11566
11523
 
@@ -11738,14 +11695,14 @@ var SceneExportManager = /*#__PURE__*/function () {
11738
11695
  BufferGeometryUtils$1 = BufferGeometryUtilsModule.BufferGeometryUtils || BufferGeometryUtilsModule.default || BufferGeometryUtilsModule; // Create a new scene for export instead of cloning
11739
11696
  exportScene = new _THREE.Scene(); // Helper function to check if an object should be exported
11740
11697
  shouldExport = function shouldExport(child) {
11741
- var _child$name2, _child$userData4, _child$userData5, _child$userData6, _child$userData7;
11742
- if ((_child$name2 = child.name) !== null && _child$name2 !== void 0 && _child$name2.includes('Polyline')) return false; // Will handle separately
11698
+ var _child$name, _child$userData3, _child$userData4, _child$userData5, _child$userData6;
11699
+ if ((_child$name = child.name) !== null && _child$name !== void 0 && _child$name.includes('Polyline')) return false; // Will handle separately
11743
11700
  if (child.name === 'fogPlane') return false; // Skip fog plane
11744
- if ((_child$userData4 = child.userData) !== null && _child$userData4 !== void 0 && _child$userData4.isBrickWall) return false; // Skip environment
11745
- if ((_child$userData5 = child.userData) !== null && _child$userData5 !== void 0 && _child$userData5.isBaseGround) return false; // Skip environment
11746
- if ((_child$userData6 = child.userData) !== null && _child$userData6 !== void 0 && _child$userData6.isBaseGrid) return false; // Skip environment
11701
+ if ((_child$userData3 = child.userData) !== null && _child$userData3 !== void 0 && _child$userData3.isBrickWall) return false; // Skip environment
11702
+ if ((_child$userData4 = child.userData) !== null && _child$userData4 !== void 0 && _child$userData4.isBaseGround) return false; // Skip environment
11703
+ if ((_child$userData5 = child.userData) !== null && _child$userData5 !== void 0 && _child$userData5.isBaseGrid) return false; // Skip environment
11747
11704
  if (child.isLight) return false; // Skip lights
11748
- if ((_child$userData7 = child.userData) !== null && _child$userData7 !== void 0 && _child$userData7.isTransformControls) return false; // Skip transform controls
11705
+ if ((_child$userData6 = child.userData) !== null && _child$userData6 !== void 0 && _child$userData6.isTransformControls) return false; // Skip transform controls
11749
11706
  if (child.isTransformControls) return false; // Skip transform controls
11750
11707
  if (child.type && child.type.includes('TransformControls')) return false;
11751
11708
  if (child.type && child.type.includes('Helper')) return false; // Skip helpers
@@ -11754,8 +11711,8 @@ var SceneExportManager = /*#__PURE__*/function () {
11754
11711
  pipeGeometries = [];
11755
11712
  pipeMaterial = null;
11756
11713
  this.sceneViewer.scene.children.forEach(function (child) {
11757
- var _child$name3;
11758
- if ((_child$name3 = child.name) !== null && _child$name3 !== void 0 && _child$name3.includes('Polyline')) {
11714
+ var _child$name2;
11715
+ if ((_child$name2 = child.name) !== null && _child$name2 !== void 0 && _child$name2.includes('Polyline')) {
11759
11716
  // Traverse the polyline to collect all mesh geometries
11760
11717
  child.traverse(function (obj) {
11761
11718
  if (obj.isMesh && obj.geometry) {
@@ -29587,6 +29544,11 @@ var ModelManager = /*#__PURE__*/function () {
29587
29544
  if (!libraryModel.userData) libraryModel.userData = {};
29588
29545
  Object.assign(libraryModel.userData, jsonEntry.userData);
29589
29546
 
29547
+ // Preserve the original hardcoded UUID so getHardcodedUuid() can find it during export.
29548
+ // jsonEntry is the raw JSON object (no originalUuid), so we must set it explicitly from
29549
+ // originalProps.uuid (which equals the uuid string from the scene JSON, e.g. "PUMP-1").
29550
+ libraryModel.userData.originalUuid = originalProps.uuid;
29551
+
29590
29552
  // Apply bounding box configurations
29591
29553
  if (componentData.boundingBox) {
29592
29554
  libraryModel.userData.boundingBox = componentData.boundingBox;
@@ -29851,15 +29813,21 @@ var ModelManager = /*#__PURE__*/function () {
29851
29813
  _context5.n = 2;
29852
29814
  return Promise.all(glbLoadingPromises);
29853
29815
  case 2:
29854
- // Update world bounding boxes for loaded models
29816
+ // Update world bounding boxes for loaded models and propagate to the live Three.js objects
29855
29817
  libraryObjectsToReplace.forEach(function (_ref2) {
29856
- var _jsonData$userData2;
29857
29818
  var jsonData = _ref2.jsonData,
29858
29819
  glbModel = _ref2.glbModel;
29859
- if (glbModel && (_jsonData$userData2 = jsonData.userData) !== null && _jsonData$userData2 !== void 0 && _jsonData$userData2.worldBoundingBox) {
29860
- var worldBoundingBox = _this3._calculateWorldBoundingBox(glbModel);
29861
- jsonData.userData.worldBoundingBox = worldBoundingBox;
29862
- }
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;
29863
29831
  });
29864
29832
 
29865
29833
  // Dispatch completion event
@@ -35960,32 +35928,20 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
35960
35928
  }, {
35961
35929
  key: "renderComponent",
35962
35930
  value: function renderComponent(viewport, component, centerX, centerY, scale) {
35963
- 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;
35964
- // Get component position and rotation
35965
- var pos3D = {
35966
- 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,
35967
- 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,
35968
- 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
35969
- };
35970
- var rot3D = {
35971
- 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,
35972
- 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,
35973
- 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
35974
- };
35975
-
35976
- // Get bounding box dimensions
35931
+ // Get world-space bounding box dimensions and center
35977
35932
  var _this$getComponentDim = this.getComponentDimensions(component),
35978
35933
  worldWidth = _this$getComponentDim.worldWidth,
35979
35934
  worldDepth = _this$getComponentDim.worldDepth,
35980
- 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), ")"));
35981
35938
 
35982
- // Project 3D coordinates to 2D based on view type
35983
- 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),
35984
35941
  posX = _this$project3DTo2D.posX,
35985
35942
  posY = _this$project3DTo2D.posY,
35986
35943
  rectWidth = _this$project3DTo2D.rectWidth,
35987
35944
  rectHeight = _this$project3DTo2D.rectHeight;
35988
- _this$project3DTo2D.rotationDegrees;
35989
35945
  var screenX = centerX + posX * scale;
35990
35946
  var screenY = centerY - posY * scale; // Flip Y for screen coords
35991
35947
 
@@ -36029,105 +35985,131 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
36029
35985
  });
36030
35986
 
36031
35987
  // Add mouse event handlers
36032
- this.addComponentInteractions(viewport, rect, componentGroup, component, worldWidth, worldDepth, worldHeight);
35988
+ this.addComponentInteractions(viewport, rect, componentGroup, component, worldWidth, worldDepth, worldHeight, bboxCenter);
36033
35989
  componentGroup.add(rect);
36034
35990
  componentGroup.add(label);
36035
35991
  viewport.componentLayer.add(componentGroup);
36036
35992
  }
36037
35993
 
36038
35994
  /**
36039
- * 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
36040
36023
  */
36041
36024
  }, {
36042
36025
  key: "getComponentDimensions",
36043
36026
  value: function getComponentDimensions(component) {
36044
- var _component$userData, _component$userData2, _component$geometry;
36045
- var worldWidth = 1,
36046
- worldHeight = 1,
36047
- worldDepth = 1;
36048
-
36049
- // Try adaptedBoundingBox first
36050
- if ((_component$userData = component.userData) !== null && _component$userData !== void 0 && _component$userData.adaptedBoundingBox) {
36051
- var bbox = component.userData.adaptedBoundingBox;
36052
- if (bbox.max && bbox.min) {
36053
- worldWidth = Math.abs(bbox.max.x - bbox.min.x);
36054
- worldDepth = Math.abs(bbox.max.y - bbox.min.y);
36055
- worldHeight = Math.abs(bbox.max.z - bbox.min.z);
36056
- }
36057
- }
36058
- // Fallback to dimensions from userData
36059
- else if ((_component$userData2 = component.userData) !== null && _component$userData2 !== void 0 && _component$userData2.dimensions) {
36060
- var dims = component.userData.dimensions;
36061
- worldWidth = Math.abs(dims.x);
36062
- worldDepth = Math.abs(dims.y);
36063
- worldHeight = Math.abs(dims.z);
36064
- }
36065
- // Last resort: geometry bounding box
36066
- else if ((_component$geometry = component.geometry) !== null && _component$geometry !== void 0 && _component$geometry.boundingBox) {
36067
- var _bbox = component.geometry.boundingBox;
36068
- worldWidth = Math.abs(_bbox.max.x - _bbox.min.x);
36069
- worldDepth = Math.abs(_bbox.max.y - _bbox.min.y);
36070
- 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
+ }
36071
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);
36072
36061
  return {
36073
- worldWidth: worldWidth,
36074
- worldDepth: worldDepth,
36075
- 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
+ }
36076
36070
  };
36077
36071
  }
36078
36072
 
36079
36073
  /**
36080
- * 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.
36081
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])
36082
36082
  */
36083
36083
  }, {
36084
36084
  key: "project3DTo2D",
36085
- value: function project3DTo2D(viewport, pos3D, rot3D, worldWidth, worldDepth, worldHeight) {
36086
- var posX, posY, rectWidth, rectHeight;
36087
- var rotationAngle = rot3D.z;
36088
- var rotationDegrees = rotationAngle * 180 / Math.PI;
36085
+ value: function project3DTo2D(viewport, bboxCenter, worldWidth, worldDepth, worldHeight) {
36089
36086
  var scale = viewport.PIXELS_PER_UNIT;
36087
+ var posX, posY, rectWidth, rectHeight;
36090
36088
  switch (viewport.viewType) {
36091
36089
  case 'top':
36092
- // Top view: Looking down Z-axis, X-Y plane
36093
- posX = pos3D.x;
36094
- posY = pos3D.y;
36095
-
36096
- // Swap width and depth when rotated 90° or 270°
36097
- if (Math.abs(rotationDegrees) === 90 || Math.abs(rotationDegrees) === 270) {
36098
- rectWidth = worldDepth * scale;
36099
- rectHeight = worldWidth * scale;
36100
- } else {
36101
- rectWidth = worldWidth * scale;
36102
- rectHeight = worldDepth * scale;
36103
- }
36090
+ // Looking down Z-axis X/Y plane
36091
+ posX = bboxCenter.x;
36092
+ posY = bboxCenter.y;
36093
+ rectWidth = worldWidth * scale;
36094
+ rectHeight = worldDepth * scale;
36104
36095
  break;
36105
36096
  case 'front':
36106
- // Front view: Looking along Y-axis, X-Z plane
36107
- posX = pos3D.x;
36108
- posY = pos3D.z + worldHeight / 2; // Offset Z by half height
36109
-
36110
- if (Math.abs(rotationDegrees) === 90 || Math.abs(rotationDegrees) === 270) {
36111
- rectWidth = worldDepth * scale;
36112
- } else {
36113
- rectWidth = worldWidth * scale;
36114
- }
36097
+ // Looking along Y-axis X/Z plane
36098
+ posX = bboxCenter.x;
36099
+ posY = bboxCenter.z;
36100
+ rectWidth = worldWidth * scale;
36115
36101
  rectHeight = worldHeight * scale;
36116
36102
  break;
36117
36103
  case 'side':
36118
- // Side view: Looking along X-axis, Y-Z plane (flipped)
36119
- posX = -pos3D.y; // Flipped
36120
- posY = pos3D.z + worldHeight / 2;
36121
- if (Math.abs(rotationDegrees) === 90 || Math.abs(rotationDegrees) === 270) {
36122
- rectWidth = worldWidth * scale;
36123
- } else {
36124
- rectWidth = worldDepth * scale;
36125
- }
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;
36126
36108
  rectHeight = worldHeight * scale;
36127
36109
  break;
36128
36110
  default:
36129
- posX = pos3D.x;
36130
- posY = pos3D.y;
36111
+ posX = bboxCenter.x;
36112
+ posY = bboxCenter.y;
36131
36113
  rectWidth = worldWidth * scale;
36132
36114
  rectHeight = worldDepth * scale;
36133
36115
  }
@@ -36135,8 +36117,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
36135
36117
  posX: posX,
36136
36118
  posY: posY,
36137
36119
  rectWidth: rectWidth,
36138
- rectHeight: rectHeight,
36139
- rotationDegrees: rotationDegrees
36120
+ rectHeight: rectHeight
36140
36121
  };
36141
36122
  }
36142
36123
 
@@ -36146,7 +36127,7 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
36146
36127
  */
36147
36128
  }, {
36148
36129
  key: "addComponentInteractions",
36149
- value: function addComponentInteractions(viewport, rect, componentGroup, component, worldWidth, worldDepth, worldHeight) {
36130
+ value: function addComponentInteractions(viewport, rect, componentGroup, component, worldWidth, worldDepth, worldHeight, bboxCenter) {
36150
36131
  var _this6 = this;
36151
36132
  if (!this.Konva) return;
36152
36133
  var colors = this.generateComponentColor(component.id);
@@ -36229,9 +36210,9 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
36229
36210
  // Convert screen to world coordinates
36230
36211
  var worldCoords = _this6.screenToWorldCoords(viewport, finalPos.x, finalPos.y, scale, worldOriginX, worldOriginY);
36231
36212
 
36232
- // Calculate new position
36213
+ // Calculate new position: delta from old bbox center to new bbox center
36233
36214
  var currentPos = component.position;
36234
- var newPosition = _this6.worldCoordsToObjectPosition(viewport, worldCoords, currentPos, worldWidth, worldDepth, worldHeight);
36215
+ var newPosition = _this6.worldCoordsToObjectPosition(viewport, worldCoords, currentPos, bboxCenter);
36235
36216
 
36236
36217
  // Apply translation via centralPlant API
36237
36218
  var deltaX = newPosition.x - currentPos.x;
@@ -36334,37 +36315,45 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
36334
36315
  }
36335
36316
 
36336
36317
  /**
36337
- * 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.
36338
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
36339
36325
  */
36340
36326
  }, {
36341
36327
  key: "worldCoordsToObjectPosition",
36342
- value: function worldCoordsToObjectPosition(viewport, worldCoords, currentPosition, worldWidth, worldDepth, worldHeight) {
36328
+ value: function worldCoordsToObjectPosition(viewport, worldCoords, currentPosition, bboxCenter) {
36343
36329
  var coord1 = worldCoords.coord1,
36344
36330
  coord2 = worldCoords.coord2;
36345
36331
  switch (viewport.viewType) {
36346
36332
  case 'top':
36333
+ // coord1=X, coord2=Y in world space
36347
36334
  return {
36348
- x: coord1,
36349
- y: coord2,
36335
+ x: currentPosition.x + (coord1 - bboxCenter.x),
36336
+ y: currentPosition.y + (coord2 - bboxCenter.y),
36350
36337
  z: currentPosition.z
36351
36338
  };
36352
36339
  case 'front':
36340
+ // coord1=X, coord2=Z in world space
36353
36341
  return {
36354
- x: coord1,
36342
+ x: currentPosition.x + (coord1 - bboxCenter.x),
36355
36343
  y: currentPosition.y,
36356
- z: coord2 - worldHeight / 2
36344
+ z: currentPosition.z + (coord2 - bboxCenter.z)
36357
36345
  };
36358
36346
  case 'side':
36347
+ // coord1=-Y (negated), coord2=Z in world space
36359
36348
  return {
36360
36349
  x: currentPosition.x,
36361
- y: -coord1,
36362
- z: coord2 - worldHeight / 2
36350
+ y: currentPosition.y + (-coord1 - bboxCenter.y),
36351
+ z: currentPosition.z + (coord2 - bboxCenter.z)
36363
36352
  };
36364
36353
  default:
36365
36354
  return {
36366
- x: coord1,
36367
- y: coord2,
36355
+ x: currentPosition.x + (coord1 - bboxCenter.x),
36356
+ y: currentPosition.y + (coord2 - bboxCenter.y),
36368
36357
  z: currentPosition.z
36369
36358
  };
36370
36359
  }
@@ -36382,8 +36371,9 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
36382
36371
  var components = [];
36383
36372
  this.sceneViewer.scene.traverse(function (object) {
36384
36373
  var _object$userData, _object$userData2;
36385
- 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);
36386
- 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) {
36387
36377
  components.push(object);
36388
36378
  }
36389
36379
  });
@@ -37840,7 +37830,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
37840
37830
  * Initialize the CentralPlant manager
37841
37831
  *
37842
37832
  * @constructor
37843
- * @version 0.3.2
37833
+ * @version 0.3.5
37844
37834
  * @updated 2025-10-22
37845
37835
  *
37846
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.2
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;
@@ -310,6 +311,11 @@ var ModelManager = /*#__PURE__*/function () {
310
311
  if (!libraryModel.userData) libraryModel.userData = {};
311
312
  Object.assign(libraryModel.userData, jsonEntry.userData);
312
313
 
314
+ // Preserve the original hardcoded UUID so getHardcodedUuid() can find it during export.
315
+ // jsonEntry is the raw JSON object (no originalUuid), so we must set it explicitly from
316
+ // originalProps.uuid (which equals the uuid string from the scene JSON, e.g. "PUMP-1").
317
+ libraryModel.userData.originalUuid = originalProps.uuid;
318
+
313
319
  // Apply bounding box configurations
314
320
  if (componentData.boundingBox) {
315
321
  libraryModel.userData.boundingBox = componentData.boundingBox;
@@ -574,15 +580,21 @@ var ModelManager = /*#__PURE__*/function () {
574
580
  _context5.n = 2;
575
581
  return Promise.all(glbLoadingPromises);
576
582
  case 2:
577
- // Update world bounding boxes for loaded models
583
+ // Update world bounding boxes for loaded models and propagate to the live Three.js objects
578
584
  libraryObjectsToReplace.forEach(function (_ref2) {
579
- var _jsonData$userData2;
580
585
  var jsonData = _ref2.jsonData,
581
586
  glbModel = _ref2.glbModel;
582
- if (glbModel && (_jsonData$userData2 = jsonData.userData) !== null && _jsonData$userData2 !== void 0 && _jsonData$userData2.worldBoundingBox) {
583
- var worldBoundingBox = _this3._calculateWorldBoundingBox(glbModel);
584
- jsonData.userData.worldBoundingBox = worldBoundingBox;
585
- }
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;
586
598
  });
587
599
 
588
600
  // Dispatch completion event