@2112-lab/central-plant 0.1.55 → 0.1.59

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.
@@ -19059,10 +19059,15 @@ var TransformOperationsManager = /*#__PURE__*/function () {
19059
19059
  console.error("\u274C translateComponent(): Object with ID '".concat(componentId, "' is not a valid component"));
19060
19060
  return false;
19061
19061
  }
19062
-
19063
- // Apply the translation
19064
19062
  console.log("\uD83D\uDD04 translateComponent(): Translating component ".concat(componentId, " on ").concat(axis, " axis by ").concat(value));
19065
19063
 
19064
+ // Run collision validation checks before applying the translation
19065
+ var collisionValidation = this.validateTranslateComponentCollisions(component, axis, value);
19066
+ if (!collisionValidation) {
19067
+ return false;
19068
+ }
19069
+
19070
+ // All validations passed - apply the translation
19066
19071
  // Update the Three.js object position
19067
19072
  component.position[axis] += value;
19068
19073
 
@@ -19106,6 +19111,48 @@ var TransformOperationsManager = /*#__PURE__*/function () {
19106
19111
  return true;
19107
19112
  }
19108
19113
 
19114
+ /**
19115
+ * Validate collision checks for translateComponent operation
19116
+ * @param {THREE.Object3D} component - The component to translate
19117
+ * @param {string} axis - The axis to translate on ('x', 'y', or 'z')
19118
+ * @param {number} value - The value to translate by
19119
+ * @returns {boolean} True if no collisions detected, false if translation would cause collision
19120
+ * @private
19121
+ */
19122
+ }, {
19123
+ key: "validateTranslateComponentCollisions",
19124
+ value: function validateTranslateComponentCollisions(component, axis, value) {
19125
+ // Store original position for reverting
19126
+ var originalPosition = component.position[axis];
19127
+
19128
+ // Temporarily apply the translation to check for collisions
19129
+ component.position[axis] += value;
19130
+ component.updateMatrix();
19131
+ component.updateMatrixWorld(true);
19132
+ console.log("\uD83D\uDD0D Checking collisions with translated position: ".concat(axis, "=").concat(component.position[axis].toFixed(3)));
19133
+
19134
+ // Check for collision with manual segments
19135
+ var manualSegmentCollision = this.checkComponentManualSegmentCollision(component);
19136
+ if (manualSegmentCollision) {
19137
+ // Revert the translation
19138
+ component.position[axis] = originalPosition;
19139
+ component.updateMatrix();
19140
+ component.updateMatrixWorld(true);
19141
+ console.warn("\u26A0\uFE0F translateComponent(): Translation canceled - component would collide with manual segment");
19142
+ console.warn(" Segment ID: ".concat(manualSegmentCollision.segmentId));
19143
+ console.warn(" Distance to segment: ".concat(manualSegmentCollision.distance.toFixed(3), " (max allowed: 0)"));
19144
+ console.warn(" Segment endpoints: [".concat(manualSegmentCollision.segmentStart.x.toFixed(3), ", ").concat(manualSegmentCollision.segmentStart.y.toFixed(3), ", ").concat(manualSegmentCollision.segmentStart.z.toFixed(3), "] to [").concat(manualSegmentCollision.segmentEnd.x.toFixed(3), ", ").concat(manualSegmentCollision.segmentEnd.y.toFixed(3), ", ").concat(manualSegmentCollision.segmentEnd.z.toFixed(3), "]"));
19145
+ return false;
19146
+ }
19147
+ console.log('✅ No collisions detected, proceeding with translation');
19148
+
19149
+ // Revert the temporary translation
19150
+ component.position[axis] = originalPosition;
19151
+ component.updateMatrix();
19152
+ component.updateMatrixWorld(true);
19153
+ return true;
19154
+ }
19155
+
19109
19156
  /**
19110
19157
  * Translate a pipe segment by segmentId
19111
19158
  * @param {string} segmentId - The UUID of the pipe segment to translate
@@ -20185,6 +20232,90 @@ var TransformOperationsManager = /*#__PURE__*/function () {
20185
20232
  return collision;
20186
20233
  }
20187
20234
 
20235
+ /**
20236
+ * Check if a component would collide with any manual (declared) segment
20237
+ * @param {THREE.Object3D} component - The component to check for collisions
20238
+ * @returns {Object|null} Collision info {segmentId, distance, segmentStart, segmentEnd} if collision detected, null otherwise
20239
+ * @private
20240
+ */
20241
+ }, {
20242
+ key: "checkComponentManualSegmentCollision",
20243
+ value: function checkComponentManualSegmentCollision(component) {
20244
+ var _this$sceneViewer8,
20245
+ _this3 = this;
20246
+ if (!((_this$sceneViewer8 = this.sceneViewer) !== null && _this$sceneViewer8 !== void 0 && _this$sceneViewer8.scene) || !component) {
20247
+ return null;
20248
+ }
20249
+
20250
+ // Create a bounding box for the component
20251
+ var componentBBox = new THREE__namespace.Box3().setFromObject(component);
20252
+ var collision = null;
20253
+
20254
+ // Traverse scene to find all manual segments (isDeclared === true)
20255
+ this.sceneViewer.scene.traverse(function (child) {
20256
+ var _child$userData15, _child$userData16;
20257
+ if (collision) return; // Stop if collision already found
20258
+
20259
+ // Only check manual segments (isDeclared === true)
20260
+ if (((_child$userData15 = child.userData) === null || _child$userData15 === void 0 ? void 0 : _child$userData15.objectType) === 'segment' && ((_child$userData16 = child.userData) === null || _child$userData16 === void 0 ? void 0 : _child$userData16.isDeclared) === true) {
20261
+ // Get segment endpoints
20262
+ var segmentEndpoints = _this3.getSegmentEndpoints(child);
20263
+
20264
+ // Check if segment intersects with component's bounding box
20265
+ // We'll use a line-box intersection test
20266
+ var line = new THREE__namespace.Line3(segmentEndpoints.start, segmentEndpoints.end);
20267
+ var closestPoint = new THREE__namespace.Vector3();
20268
+ line.closestPointToPoint(componentBBox.getCenter(new THREE__namespace.Vector3()), true, closestPoint);
20269
+
20270
+ // Check if the closest point on the line is within the bounding box
20271
+ if (componentBBox.containsPoint(closestPoint)) {
20272
+ console.log('⚠️ TransformOperationsManager: Component bounding box collision with manual segment:', child.uuid);
20273
+ collision = {
20274
+ segmentId: child.uuid,
20275
+ distance: 0,
20276
+ // Inside the bounding box
20277
+ segmentStart: segmentEndpoints.start.clone(),
20278
+ segmentEnd: segmentEndpoints.end.clone()
20279
+ };
20280
+ return;
20281
+ }
20282
+
20283
+ // Also check if either endpoint of the segment is inside the component bounding box
20284
+ if (componentBBox.containsPoint(segmentEndpoints.start) || componentBBox.containsPoint(segmentEndpoints.end)) {
20285
+ console.log('⚠️ TransformOperationsManager: Component bounding box contains manual segment endpoint:', child.uuid);
20286
+ collision = {
20287
+ segmentId: child.uuid,
20288
+ distance: 0,
20289
+ // Inside the bounding box
20290
+ segmentStart: segmentEndpoints.start.clone(),
20291
+ segmentEnd: segmentEndpoints.end.clone()
20292
+ };
20293
+ return;
20294
+ }
20295
+
20296
+ // Additionally, check if the segment line intersects any of the bounding box faces
20297
+ // This catches cases where the segment passes through the box without endpoints inside
20298
+ var ray = new THREE__namespace.Ray(segmentEndpoints.start, new THREE__namespace.Vector3().subVectors(segmentEndpoints.end, segmentEndpoints.start).normalize());
20299
+ var segmentLength = segmentEndpoints.start.distanceTo(segmentEndpoints.end);
20300
+ var intersection = ray.intersectBox(componentBBox, new THREE__namespace.Vector3());
20301
+ if (intersection) {
20302
+ var distanceToIntersection = segmentEndpoints.start.distanceTo(intersection);
20303
+ if (distanceToIntersection <= segmentLength) {
20304
+ console.log('⚠️ TransformOperationsManager: Manual segment intersects component bounding box:', child.uuid);
20305
+ collision = {
20306
+ segmentId: child.uuid,
20307
+ distance: 0,
20308
+ // Intersects the bounding box
20309
+ segmentStart: segmentEndpoints.start.clone(),
20310
+ segmentEnd: segmentEndpoints.end.clone()
20311
+ };
20312
+ }
20313
+ }
20314
+ }
20315
+ });
20316
+ return collision;
20317
+ }
20318
+
20188
20319
  /**
20189
20320
  * Calculate the minimum distance between two line segments in 3D space
20190
20321
  * @param {THREE.Vector3} p1 - Start point of first segment
@@ -20294,10 +20425,10 @@ var TransformOperationsManager = /*#__PURE__*/function () {
20294
20425
  }, {
20295
20426
  key: "snapSegmentConnectorsToNearbyEndpoints",
20296
20427
  value: function snapSegmentConnectorsToNearbyEndpoints(movedSegment) {
20297
- var _this$sceneViewer8,
20298
- _this$sceneViewer9,
20299
- _this3 = this;
20300
- if (!movedSegment || !((_this$sceneViewer8 = this.sceneViewer) !== null && _this$sceneViewer8 !== void 0 && _this$sceneViewer8.scene)) {
20428
+ var _this$sceneViewer9,
20429
+ _this$sceneViewer0,
20430
+ _this4 = this;
20431
+ if (!movedSegment || !((_this$sceneViewer9 = this.sceneViewer) !== null && _this$sceneViewer9 !== void 0 && _this$sceneViewer9.scene)) {
20301
20432
  return [];
20302
20433
  }
20303
20434
  console.log('🔗 Finding adjacent segments connected to moved segment...');
@@ -20305,8 +20436,8 @@ var TransformOperationsManager = /*#__PURE__*/function () {
20305
20436
  // Get the moved segment's connectors
20306
20437
  var movedConnectors = [];
20307
20438
  movedSegment.traverse(function (child) {
20308
- var _child$userData15;
20309
- if (((_child$userData15 = child.userData) === null || _child$userData15 === void 0 ? void 0 : _child$userData15.objectType) === 'segment-connector') {
20439
+ var _child$userData17;
20440
+ if (((_child$userData17 = child.userData) === null || _child$userData17 === void 0 ? void 0 : _child$userData17.objectType) === 'segment-connector') {
20310
20441
  movedConnectors.push(child);
20311
20442
  }
20312
20443
  });
@@ -20319,7 +20450,7 @@ var TransformOperationsManager = /*#__PURE__*/function () {
20319
20450
  var newEndpoints = this.calculateSegmentEndpoints(movedSegment);
20320
20451
 
20321
20452
  // Check scene data for connections involving the moved segment's connectors
20322
- var connections = ((_this$sceneViewer9 = this.sceneViewer) === null || _this$sceneViewer9 === void 0 || (_this$sceneViewer9 = _this$sceneViewer9.currentSceneData) === null || _this$sceneViewer9 === void 0 ? void 0 : _this$sceneViewer9.connections) || [];
20453
+ var connections = ((_this$sceneViewer0 = this.sceneViewer) === null || _this$sceneViewer0 === void 0 || (_this$sceneViewer0 = _this$sceneViewer0.currentSceneData) === null || _this$sceneViewer0 === void 0 ? void 0 : _this$sceneViewer0.connections) || [];
20323
20454
  var movedConnectorIds = movedConnectors.map(function (c) {
20324
20455
  return c.uuid;
20325
20456
  });
@@ -20354,7 +20485,7 @@ var TransformOperationsManager = /*#__PURE__*/function () {
20354
20485
  // Find the adjacent connector in the scene
20355
20486
  var adjacentConnector = null;
20356
20487
  var adjacentSegment = null;
20357
- _this3.sceneViewer.scene.traverse(function (object) {
20488
+ _this4.sceneViewer.scene.traverse(function (object) {
20358
20489
  var _object$userData;
20359
20490
  if (object.uuid === pair.adjacentConnectorId && ((_object$userData = object.userData) === null || _object$userData === void 0 ? void 0 : _object$userData.objectType) === 'segment-connector') {
20360
20491
  adjacentConnector = object;
@@ -20376,8 +20507,8 @@ var TransformOperationsManager = /*#__PURE__*/function () {
20376
20507
  // Get all connectors of the adjacent segment
20377
20508
  var adjacentConnectors = [];
20378
20509
  adjacentSegment.traverse(function (child) {
20379
- var _child$userData16;
20380
- if (((_child$userData16 = child.userData) === null || _child$userData16 === void 0 ? void 0 : _child$userData16.objectType) === 'segment-connector') {
20510
+ var _child$userData18;
20511
+ if (((_child$userData18 = child.userData) === null || _child$userData18 === void 0 ? void 0 : _child$userData18.objectType) === 'segment-connector') {
20381
20512
  adjacentConnectors.push(child);
20382
20513
  }
20383
20514
  });
@@ -20403,12 +20534,12 @@ var TransformOperationsManager = /*#__PURE__*/function () {
20403
20534
  // Constrain movement to maintain orthogonal alignment
20404
20535
  // Horizontal segments: only adjust X and Y, keep Z constant
20405
20536
  // Vertical segments: only adjust Z, keep X and Y constant
20406
- var constrainedPosition = _this3.constrainPositionToOrthogonal(pair.newPosition, stationaryWorldPos, adjacentSegment);
20537
+ var constrainedPosition = _this4.constrainPositionToOrthogonal(pair.newPosition, stationaryWorldPos, adjacentSegment);
20407
20538
 
20408
20539
  // Recreate the segment mesh with new length using explicit endpoint positions
20409
20540
  // Pass the intended positions, not the connector objects (which may have temporary positions)
20410
20541
  // Pass movedSegment as activeSegment context so zero-length removal knows which segment to extend
20411
- _this3.recreateSegmentMeshWithNewLength(adjacentSegment, adjacentConnectors, constrainedPosition, stationaryWorldPos, movedSegment);
20542
+ _this4.recreateSegmentMeshWithNewLength(adjacentSegment, adjacentConnectors, constrainedPosition, stationaryWorldPos, movedSegment);
20412
20543
 
20413
20544
  // CRITICAL: After recreating the mesh, BOTH connectors need to be repositioned
20414
20545
  // because the segment's center and rotation have changed
@@ -20417,13 +20548,13 @@ var TransformOperationsManager = /*#__PURE__*/function () {
20417
20548
  var movingLocalPos = adjacentSegment.worldToLocal(constrainedPosition.clone());
20418
20549
  movingConnector.position.copy(movingLocalPos);
20419
20550
  movingConnector.updateMatrixWorld(true);
20420
- _this3.updateConnectorPositionInSceneData(movingConnector, constrainedPosition, adjacentSegment);
20551
+ _this4.updateConnectorPositionInSceneData(movingConnector, constrainedPosition, adjacentSegment);
20421
20552
 
20422
20553
  // Position stationary connector at its original world position
20423
20554
  var stationaryLocalPos = adjacentSegment.worldToLocal(stationaryWorldPos.clone());
20424
20555
  stationaryConnector.position.copy(stationaryLocalPos);
20425
20556
  stationaryConnector.updateMatrixWorld(true);
20426
- _this3.updateConnectorPositionInSceneData(stationaryConnector, stationaryWorldPos, adjacentSegment);
20557
+ _this4.updateConnectorPositionInSceneData(stationaryConnector, stationaryWorldPos, adjacentSegment);
20427
20558
 
20428
20559
  // Record this connection as satisfied
20429
20560
  satisfiedConnections.push({
@@ -20473,8 +20604,8 @@ var TransformOperationsManager = /*#__PURE__*/function () {
20473
20604
  }, {
20474
20605
  key: "updateComponentPositionInSceneData",
20475
20606
  value: function updateComponentPositionInSceneData(component) {
20476
- var _this$sceneViewer0;
20477
- if (!((_this$sceneViewer0 = this.sceneViewer) !== null && _this$sceneViewer0 !== void 0 && (_this$sceneViewer0 = _this$sceneViewer0.currentSceneData) !== null && _this$sceneViewer0 !== void 0 && _this$sceneViewer0.scene)) {
20607
+ var _this$sceneViewer1;
20608
+ if (!((_this$sceneViewer1 = this.sceneViewer) !== null && _this$sceneViewer1 !== void 0 && (_this$sceneViewer1 = _this$sceneViewer1.currentSceneData) !== null && _this$sceneViewer1 !== void 0 && _this$sceneViewer1.scene)) {
20478
20609
  console.warn('⚠️ Cannot update component position: currentSceneData not available');
20479
20610
  return;
20480
20611
  }
@@ -20500,8 +20631,8 @@ var TransformOperationsManager = /*#__PURE__*/function () {
20500
20631
  // Also update child connectors' positions if they exist
20501
20632
  if (sceneDataComponent.children) {
20502
20633
  sceneDataComponent.children.forEach(function (child) {
20503
- var _child$userData17;
20504
- if (((_child$userData17 = child.userData) === null || _child$userData17 === void 0 ? void 0 : _child$userData17.objectType) === 'connector') {
20634
+ var _child$userData19;
20635
+ if (((_child$userData19 = child.userData) === null || _child$userData19 === void 0 ? void 0 : _child$userData19.objectType) === 'connector') {
20505
20636
  // Find the actual connector object in the scene
20506
20637
  var connectorObj = component.children.find(function (c) {
20507
20638
  var _c$userData;
@@ -20537,9 +20668,9 @@ var TransformOperationsManager = /*#__PURE__*/function () {
20537
20668
  }, {
20538
20669
  key: "updateConnectorPositionInSceneData",
20539
20670
  value: function updateConnectorPositionInSceneData(connector, worldPosition, parentSegment) {
20540
- var _this$sceneViewer1;
20671
+ var _this$sceneViewer10;
20541
20672
  // Update scene data if available
20542
- if (!((_this$sceneViewer1 = this.sceneViewer) !== null && _this$sceneViewer1 !== void 0 && (_this$sceneViewer1 = _this$sceneViewer1.currentSceneData) !== null && _this$sceneViewer1 !== void 0 && _this$sceneViewer1.scene)) {
20673
+ if (!((_this$sceneViewer10 = this.sceneViewer) !== null && _this$sceneViewer10 !== void 0 && (_this$sceneViewer10 = _this$sceneViewer10.currentSceneData) !== null && _this$sceneViewer10 !== void 0 && _this$sceneViewer10.scene)) {
20543
20674
  return;
20544
20675
  }
20545
20676
  var cleanPosition = function cleanPosition(value) {
@@ -20830,8 +20961,8 @@ var TransformOperationsManager = /*#__PURE__*/function () {
20830
20961
  // Check if either external connector belongs to the active segment
20831
20962
  var activeSegmentConnectors = [];
20832
20963
  activeSegment.traverse(function (child) {
20833
- var _child$userData18;
20834
- if (((_child$userData18 = child.userData) === null || _child$userData18 === void 0 ? void 0 : _child$userData18.objectType) === 'segment-connector') {
20964
+ var _child$userData20;
20965
+ if (((_child$userData20 = child.userData) === null || _child$userData20 === void 0 ? void 0 : _child$userData20.objectType) === 'segment-connector') {
20835
20966
  activeSegmentConnectors.push(child.uuid);
20836
20967
  }
20837
20968
  });
@@ -20879,8 +21010,8 @@ var TransformOperationsManager = /*#__PURE__*/function () {
20879
21010
  // Get all connectors of the active segment
20880
21011
  var activeConnectors = [];
20881
21012
  activeSegment.traverse(function (child) {
20882
- var _child$userData19;
20883
- if (((_child$userData19 = child.userData) === null || _child$userData19 === void 0 ? void 0 : _child$userData19.objectType) === 'segment-connector') {
21013
+ var _child$userData21;
21014
+ if (((_child$userData21 = child.userData) === null || _child$userData21 === void 0 ? void 0 : _child$userData21.objectType) === 'segment-connector') {
20884
21015
  activeConnectors.push(child);
20885
21016
  }
20886
21017
  });
@@ -26405,7 +26536,7 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
26405
26536
  // ============================================================================
26406
26537
 
26407
26538
  /**
26408
- * Enrich sceneData with worldBoundingBox for segments
26539
+ * Enrich sceneData with worldBoundingBox for segments and components
26409
26540
  * Connectors remain with just position information
26410
26541
  * @param {Object} sceneData - Original scene data
26411
26542
  * @returns {Object} Enriched scene data (shallow copy with modifications)
@@ -26422,9 +26553,9 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
26422
26553
  return enriched;
26423
26554
  }
26424
26555
 
26425
- // Process children to add worldBoundingBox to segments
26556
+ // Process children to add worldBoundingBox to segments and components
26426
26557
  enriched.children = sceneData.children.map(function (child) {
26427
- // Only enrich segments (check if objectType is 'segment' in userData)
26558
+ // Enrich segments (check if objectType is 'segment' in userData)
26428
26559
  if (child.userData && child.userData.objectType === 'segment') {
26429
26560
  // Find the actual segment object in the scene
26430
26561
  var segmentObject = _this3.sceneViewer.scene.getObjectByProperty('uuid', child.uuid);
@@ -26447,7 +26578,31 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
26447
26578
  }
26448
26579
  }
26449
26580
 
26450
- // For non-segments (including connectors), return as-is
26581
+ // Enrich components (check if objectType is 'component' in userData)
26582
+ if (child.userData && child.userData.objectType === 'component') {
26583
+ // Find the actual component object in the scene
26584
+ var componentObject = _this3.sceneViewer.scene.getObjectByProperty('uuid', child.uuid);
26585
+ if (componentObject) {
26586
+ // Compute world bounding box
26587
+ var _worldBBox = new THREE__namespace.Box3().setFromObject(componentObject);
26588
+ console.log("\uD83D\uDD04 Updated worldBoundingBox for component ".concat(child.uuid, ": min=[").concat(_worldBBox.min.x.toFixed(2), ", ").concat(_worldBBox.min.y.toFixed(2), ", ").concat(_worldBBox.min.z.toFixed(2), "], max=[").concat(_worldBBox.max.x.toFixed(2), ", ").concat(_worldBBox.max.y.toFixed(2), ", ").concat(_worldBBox.max.z.toFixed(2), "]"));
26589
+
26590
+ // Return enriched component data with worldBoundingBox in userData
26591
+ // Note: pathfinder expects arrays [x, y, z] format for min/max
26592
+ return _objectSpread2(_objectSpread2({}, child), {}, {
26593
+ userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
26594
+ worldBoundingBox: {
26595
+ min: [_worldBBox.min.x, _worldBBox.min.y, _worldBBox.min.z],
26596
+ max: [_worldBBox.max.x, _worldBBox.max.y, _worldBBox.max.z]
26597
+ }
26598
+ })
26599
+ });
26600
+ } else {
26601
+ console.warn("\u26A0\uFE0F Could not find component object in scene: ".concat(child.uuid));
26602
+ }
26603
+ }
26604
+
26605
+ // For non-segments and non-components (including connectors), return as-is
26451
26606
  return child;
26452
26607
  });
26453
26608
  return enriched;
@@ -33427,7 +33582,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
33427
33582
  * Initialize the CentralPlant manager
33428
33583
  *
33429
33584
  * @constructor
33430
- * @version 0.1.55
33585
+ * @version 0.1.59
33431
33586
  * @updated 2025-10-22
33432
33587
  *
33433
33588
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -19,7 +19,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
19
19
  * Initialize the CentralPlant manager
20
20
  *
21
21
  * @constructor
22
- * @version 0.1.55
22
+ * @version 0.1.59
23
23
  * @updated 2025-10-22
24
24
  *
25
25
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -86,10 +86,15 @@ var TransformOperationsManager = /*#__PURE__*/function () {
86
86
  console.error("\u274C translateComponent(): Object with ID '".concat(componentId, "' is not a valid component"));
87
87
  return false;
88
88
  }
89
-
90
- // Apply the translation
91
89
  console.log("\uD83D\uDD04 translateComponent(): Translating component ".concat(componentId, " on ").concat(axis, " axis by ").concat(value));
92
90
 
91
+ // Run collision validation checks before applying the translation
92
+ var collisionValidation = this.validateTranslateComponentCollisions(component, axis, value);
93
+ if (!collisionValidation) {
94
+ return false;
95
+ }
96
+
97
+ // All validations passed - apply the translation
93
98
  // Update the Three.js object position
94
99
  component.position[axis] += value;
95
100
 
@@ -133,6 +138,48 @@ var TransformOperationsManager = /*#__PURE__*/function () {
133
138
  return true;
134
139
  }
135
140
 
141
+ /**
142
+ * Validate collision checks for translateComponent operation
143
+ * @param {THREE.Object3D} component - The component to translate
144
+ * @param {string} axis - The axis to translate on ('x', 'y', or 'z')
145
+ * @param {number} value - The value to translate by
146
+ * @returns {boolean} True if no collisions detected, false if translation would cause collision
147
+ * @private
148
+ */
149
+ }, {
150
+ key: "validateTranslateComponentCollisions",
151
+ value: function validateTranslateComponentCollisions(component, axis, value) {
152
+ // Store original position for reverting
153
+ var originalPosition = component.position[axis];
154
+
155
+ // Temporarily apply the translation to check for collisions
156
+ component.position[axis] += value;
157
+ component.updateMatrix();
158
+ component.updateMatrixWorld(true);
159
+ console.log("\uD83D\uDD0D Checking collisions with translated position: ".concat(axis, "=").concat(component.position[axis].toFixed(3)));
160
+
161
+ // Check for collision with manual segments
162
+ var manualSegmentCollision = this.checkComponentManualSegmentCollision(component);
163
+ if (manualSegmentCollision) {
164
+ // Revert the translation
165
+ component.position[axis] = originalPosition;
166
+ component.updateMatrix();
167
+ component.updateMatrixWorld(true);
168
+ console.warn("\u26A0\uFE0F translateComponent(): Translation canceled - component would collide with manual segment");
169
+ console.warn(" Segment ID: ".concat(manualSegmentCollision.segmentId));
170
+ console.warn(" Distance to segment: ".concat(manualSegmentCollision.distance.toFixed(3), " (max allowed: 0)"));
171
+ console.warn(" Segment endpoints: [".concat(manualSegmentCollision.segmentStart.x.toFixed(3), ", ").concat(manualSegmentCollision.segmentStart.y.toFixed(3), ", ").concat(manualSegmentCollision.segmentStart.z.toFixed(3), "] to [").concat(manualSegmentCollision.segmentEnd.x.toFixed(3), ", ").concat(manualSegmentCollision.segmentEnd.y.toFixed(3), ", ").concat(manualSegmentCollision.segmentEnd.z.toFixed(3), "]"));
172
+ return false;
173
+ }
174
+ console.log('✅ No collisions detected, proceeding with translation');
175
+
176
+ // Revert the temporary translation
177
+ component.position[axis] = originalPosition;
178
+ component.updateMatrix();
179
+ component.updateMatrixWorld(true);
180
+ return true;
181
+ }
182
+
136
183
  /**
137
184
  * Translate a pipe segment by segmentId
138
185
  * @param {string} segmentId - The UUID of the pipe segment to translate
@@ -1212,6 +1259,90 @@ var TransformOperationsManager = /*#__PURE__*/function () {
1212
1259
  return collision;
1213
1260
  }
1214
1261
 
1262
+ /**
1263
+ * Check if a component would collide with any manual (declared) segment
1264
+ * @param {THREE.Object3D} component - The component to check for collisions
1265
+ * @returns {Object|null} Collision info {segmentId, distance, segmentStart, segmentEnd} if collision detected, null otherwise
1266
+ * @private
1267
+ */
1268
+ }, {
1269
+ key: "checkComponentManualSegmentCollision",
1270
+ value: function checkComponentManualSegmentCollision(component) {
1271
+ var _this$sceneViewer8,
1272
+ _this3 = this;
1273
+ if (!((_this$sceneViewer8 = this.sceneViewer) !== null && _this$sceneViewer8 !== void 0 && _this$sceneViewer8.scene) || !component) {
1274
+ return null;
1275
+ }
1276
+
1277
+ // Create a bounding box for the component
1278
+ var componentBBox = new THREE__namespace.Box3().setFromObject(component);
1279
+ var collision = null;
1280
+
1281
+ // Traverse scene to find all manual segments (isDeclared === true)
1282
+ this.sceneViewer.scene.traverse(function (child) {
1283
+ var _child$userData15, _child$userData16;
1284
+ if (collision) return; // Stop if collision already found
1285
+
1286
+ // Only check manual segments (isDeclared === true)
1287
+ if (((_child$userData15 = child.userData) === null || _child$userData15 === void 0 ? void 0 : _child$userData15.objectType) === 'segment' && ((_child$userData16 = child.userData) === null || _child$userData16 === void 0 ? void 0 : _child$userData16.isDeclared) === true) {
1288
+ // Get segment endpoints
1289
+ var segmentEndpoints = _this3.getSegmentEndpoints(child);
1290
+
1291
+ // Check if segment intersects with component's bounding box
1292
+ // We'll use a line-box intersection test
1293
+ var line = new THREE__namespace.Line3(segmentEndpoints.start, segmentEndpoints.end);
1294
+ var closestPoint = new THREE__namespace.Vector3();
1295
+ line.closestPointToPoint(componentBBox.getCenter(new THREE__namespace.Vector3()), true, closestPoint);
1296
+
1297
+ // Check if the closest point on the line is within the bounding box
1298
+ if (componentBBox.containsPoint(closestPoint)) {
1299
+ console.log('⚠️ TransformOperationsManager: Component bounding box collision with manual segment:', child.uuid);
1300
+ collision = {
1301
+ segmentId: child.uuid,
1302
+ distance: 0,
1303
+ // Inside the bounding box
1304
+ segmentStart: segmentEndpoints.start.clone(),
1305
+ segmentEnd: segmentEndpoints.end.clone()
1306
+ };
1307
+ return;
1308
+ }
1309
+
1310
+ // Also check if either endpoint of the segment is inside the component bounding box
1311
+ if (componentBBox.containsPoint(segmentEndpoints.start) || componentBBox.containsPoint(segmentEndpoints.end)) {
1312
+ console.log('⚠️ TransformOperationsManager: Component bounding box contains manual segment endpoint:', child.uuid);
1313
+ collision = {
1314
+ segmentId: child.uuid,
1315
+ distance: 0,
1316
+ // Inside the bounding box
1317
+ segmentStart: segmentEndpoints.start.clone(),
1318
+ segmentEnd: segmentEndpoints.end.clone()
1319
+ };
1320
+ return;
1321
+ }
1322
+
1323
+ // Additionally, check if the segment line intersects any of the bounding box faces
1324
+ // This catches cases where the segment passes through the box without endpoints inside
1325
+ var ray = new THREE__namespace.Ray(segmentEndpoints.start, new THREE__namespace.Vector3().subVectors(segmentEndpoints.end, segmentEndpoints.start).normalize());
1326
+ var segmentLength = segmentEndpoints.start.distanceTo(segmentEndpoints.end);
1327
+ var intersection = ray.intersectBox(componentBBox, new THREE__namespace.Vector3());
1328
+ if (intersection) {
1329
+ var distanceToIntersection = segmentEndpoints.start.distanceTo(intersection);
1330
+ if (distanceToIntersection <= segmentLength) {
1331
+ console.log('⚠️ TransformOperationsManager: Manual segment intersects component bounding box:', child.uuid);
1332
+ collision = {
1333
+ segmentId: child.uuid,
1334
+ distance: 0,
1335
+ // Intersects the bounding box
1336
+ segmentStart: segmentEndpoints.start.clone(),
1337
+ segmentEnd: segmentEndpoints.end.clone()
1338
+ };
1339
+ }
1340
+ }
1341
+ }
1342
+ });
1343
+ return collision;
1344
+ }
1345
+
1215
1346
  /**
1216
1347
  * Calculate the minimum distance between two line segments in 3D space
1217
1348
  * @param {THREE.Vector3} p1 - Start point of first segment
@@ -1321,10 +1452,10 @@ var TransformOperationsManager = /*#__PURE__*/function () {
1321
1452
  }, {
1322
1453
  key: "snapSegmentConnectorsToNearbyEndpoints",
1323
1454
  value: function snapSegmentConnectorsToNearbyEndpoints(movedSegment) {
1324
- var _this$sceneViewer8,
1325
- _this$sceneViewer9,
1326
- _this3 = this;
1327
- if (!movedSegment || !((_this$sceneViewer8 = this.sceneViewer) !== null && _this$sceneViewer8 !== void 0 && _this$sceneViewer8.scene)) {
1455
+ var _this$sceneViewer9,
1456
+ _this$sceneViewer0,
1457
+ _this4 = this;
1458
+ if (!movedSegment || !((_this$sceneViewer9 = this.sceneViewer) !== null && _this$sceneViewer9 !== void 0 && _this$sceneViewer9.scene)) {
1328
1459
  return [];
1329
1460
  }
1330
1461
  console.log('🔗 Finding adjacent segments connected to moved segment...');
@@ -1332,8 +1463,8 @@ var TransformOperationsManager = /*#__PURE__*/function () {
1332
1463
  // Get the moved segment's connectors
1333
1464
  var movedConnectors = [];
1334
1465
  movedSegment.traverse(function (child) {
1335
- var _child$userData15;
1336
- if (((_child$userData15 = child.userData) === null || _child$userData15 === void 0 ? void 0 : _child$userData15.objectType) === 'segment-connector') {
1466
+ var _child$userData17;
1467
+ if (((_child$userData17 = child.userData) === null || _child$userData17 === void 0 ? void 0 : _child$userData17.objectType) === 'segment-connector') {
1337
1468
  movedConnectors.push(child);
1338
1469
  }
1339
1470
  });
@@ -1346,7 +1477,7 @@ var TransformOperationsManager = /*#__PURE__*/function () {
1346
1477
  var newEndpoints = this.calculateSegmentEndpoints(movedSegment);
1347
1478
 
1348
1479
  // Check scene data for connections involving the moved segment's connectors
1349
- var connections = ((_this$sceneViewer9 = this.sceneViewer) === null || _this$sceneViewer9 === void 0 || (_this$sceneViewer9 = _this$sceneViewer9.currentSceneData) === null || _this$sceneViewer9 === void 0 ? void 0 : _this$sceneViewer9.connections) || [];
1480
+ var connections = ((_this$sceneViewer0 = this.sceneViewer) === null || _this$sceneViewer0 === void 0 || (_this$sceneViewer0 = _this$sceneViewer0.currentSceneData) === null || _this$sceneViewer0 === void 0 ? void 0 : _this$sceneViewer0.connections) || [];
1350
1481
  var movedConnectorIds = movedConnectors.map(function (c) {
1351
1482
  return c.uuid;
1352
1483
  });
@@ -1381,7 +1512,7 @@ var TransformOperationsManager = /*#__PURE__*/function () {
1381
1512
  // Find the adjacent connector in the scene
1382
1513
  var adjacentConnector = null;
1383
1514
  var adjacentSegment = null;
1384
- _this3.sceneViewer.scene.traverse(function (object) {
1515
+ _this4.sceneViewer.scene.traverse(function (object) {
1385
1516
  var _object$userData;
1386
1517
  if (object.uuid === pair.adjacentConnectorId && ((_object$userData = object.userData) === null || _object$userData === void 0 ? void 0 : _object$userData.objectType) === 'segment-connector') {
1387
1518
  adjacentConnector = object;
@@ -1403,8 +1534,8 @@ var TransformOperationsManager = /*#__PURE__*/function () {
1403
1534
  // Get all connectors of the adjacent segment
1404
1535
  var adjacentConnectors = [];
1405
1536
  adjacentSegment.traverse(function (child) {
1406
- var _child$userData16;
1407
- if (((_child$userData16 = child.userData) === null || _child$userData16 === void 0 ? void 0 : _child$userData16.objectType) === 'segment-connector') {
1537
+ var _child$userData18;
1538
+ if (((_child$userData18 = child.userData) === null || _child$userData18 === void 0 ? void 0 : _child$userData18.objectType) === 'segment-connector') {
1408
1539
  adjacentConnectors.push(child);
1409
1540
  }
1410
1541
  });
@@ -1430,12 +1561,12 @@ var TransformOperationsManager = /*#__PURE__*/function () {
1430
1561
  // Constrain movement to maintain orthogonal alignment
1431
1562
  // Horizontal segments: only adjust X and Y, keep Z constant
1432
1563
  // Vertical segments: only adjust Z, keep X and Y constant
1433
- var constrainedPosition = _this3.constrainPositionToOrthogonal(pair.newPosition, stationaryWorldPos, adjacentSegment);
1564
+ var constrainedPosition = _this4.constrainPositionToOrthogonal(pair.newPosition, stationaryWorldPos, adjacentSegment);
1434
1565
 
1435
1566
  // Recreate the segment mesh with new length using explicit endpoint positions
1436
1567
  // Pass the intended positions, not the connector objects (which may have temporary positions)
1437
1568
  // Pass movedSegment as activeSegment context so zero-length removal knows which segment to extend
1438
- _this3.recreateSegmentMeshWithNewLength(adjacentSegment, adjacentConnectors, constrainedPosition, stationaryWorldPos, movedSegment);
1569
+ _this4.recreateSegmentMeshWithNewLength(adjacentSegment, adjacentConnectors, constrainedPosition, stationaryWorldPos, movedSegment);
1439
1570
 
1440
1571
  // CRITICAL: After recreating the mesh, BOTH connectors need to be repositioned
1441
1572
  // because the segment's center and rotation have changed
@@ -1444,13 +1575,13 @@ var TransformOperationsManager = /*#__PURE__*/function () {
1444
1575
  var movingLocalPos = adjacentSegment.worldToLocal(constrainedPosition.clone());
1445
1576
  movingConnector.position.copy(movingLocalPos);
1446
1577
  movingConnector.updateMatrixWorld(true);
1447
- _this3.updateConnectorPositionInSceneData(movingConnector, constrainedPosition, adjacentSegment);
1578
+ _this4.updateConnectorPositionInSceneData(movingConnector, constrainedPosition, adjacentSegment);
1448
1579
 
1449
1580
  // Position stationary connector at its original world position
1450
1581
  var stationaryLocalPos = adjacentSegment.worldToLocal(stationaryWorldPos.clone());
1451
1582
  stationaryConnector.position.copy(stationaryLocalPos);
1452
1583
  stationaryConnector.updateMatrixWorld(true);
1453
- _this3.updateConnectorPositionInSceneData(stationaryConnector, stationaryWorldPos, adjacentSegment);
1584
+ _this4.updateConnectorPositionInSceneData(stationaryConnector, stationaryWorldPos, adjacentSegment);
1454
1585
 
1455
1586
  // Record this connection as satisfied
1456
1587
  satisfiedConnections.push({
@@ -1500,8 +1631,8 @@ var TransformOperationsManager = /*#__PURE__*/function () {
1500
1631
  }, {
1501
1632
  key: "updateComponentPositionInSceneData",
1502
1633
  value: function updateComponentPositionInSceneData(component) {
1503
- var _this$sceneViewer0;
1504
- if (!((_this$sceneViewer0 = this.sceneViewer) !== null && _this$sceneViewer0 !== void 0 && (_this$sceneViewer0 = _this$sceneViewer0.currentSceneData) !== null && _this$sceneViewer0 !== void 0 && _this$sceneViewer0.scene)) {
1634
+ var _this$sceneViewer1;
1635
+ if (!((_this$sceneViewer1 = this.sceneViewer) !== null && _this$sceneViewer1 !== void 0 && (_this$sceneViewer1 = _this$sceneViewer1.currentSceneData) !== null && _this$sceneViewer1 !== void 0 && _this$sceneViewer1.scene)) {
1505
1636
  console.warn('⚠️ Cannot update component position: currentSceneData not available');
1506
1637
  return;
1507
1638
  }
@@ -1527,8 +1658,8 @@ var TransformOperationsManager = /*#__PURE__*/function () {
1527
1658
  // Also update child connectors' positions if they exist
1528
1659
  if (sceneDataComponent.children) {
1529
1660
  sceneDataComponent.children.forEach(function (child) {
1530
- var _child$userData17;
1531
- if (((_child$userData17 = child.userData) === null || _child$userData17 === void 0 ? void 0 : _child$userData17.objectType) === 'connector') {
1661
+ var _child$userData19;
1662
+ if (((_child$userData19 = child.userData) === null || _child$userData19 === void 0 ? void 0 : _child$userData19.objectType) === 'connector') {
1532
1663
  // Find the actual connector object in the scene
1533
1664
  var connectorObj = component.children.find(function (c) {
1534
1665
  var _c$userData;
@@ -1564,9 +1695,9 @@ var TransformOperationsManager = /*#__PURE__*/function () {
1564
1695
  }, {
1565
1696
  key: "updateConnectorPositionInSceneData",
1566
1697
  value: function updateConnectorPositionInSceneData(connector, worldPosition, parentSegment) {
1567
- var _this$sceneViewer1;
1698
+ var _this$sceneViewer10;
1568
1699
  // Update scene data if available
1569
- if (!((_this$sceneViewer1 = this.sceneViewer) !== null && _this$sceneViewer1 !== void 0 && (_this$sceneViewer1 = _this$sceneViewer1.currentSceneData) !== null && _this$sceneViewer1 !== void 0 && _this$sceneViewer1.scene)) {
1700
+ if (!((_this$sceneViewer10 = this.sceneViewer) !== null && _this$sceneViewer10 !== void 0 && (_this$sceneViewer10 = _this$sceneViewer10.currentSceneData) !== null && _this$sceneViewer10 !== void 0 && _this$sceneViewer10.scene)) {
1570
1701
  return;
1571
1702
  }
1572
1703
  var cleanPosition = function cleanPosition(value) {
@@ -1857,8 +1988,8 @@ var TransformOperationsManager = /*#__PURE__*/function () {
1857
1988
  // Check if either external connector belongs to the active segment
1858
1989
  var activeSegmentConnectors = [];
1859
1990
  activeSegment.traverse(function (child) {
1860
- var _child$userData18;
1861
- if (((_child$userData18 = child.userData) === null || _child$userData18 === void 0 ? void 0 : _child$userData18.objectType) === 'segment-connector') {
1991
+ var _child$userData20;
1992
+ if (((_child$userData20 = child.userData) === null || _child$userData20 === void 0 ? void 0 : _child$userData20.objectType) === 'segment-connector') {
1862
1993
  activeSegmentConnectors.push(child.uuid);
1863
1994
  }
1864
1995
  });
@@ -1906,8 +2037,8 @@ var TransformOperationsManager = /*#__PURE__*/function () {
1906
2037
  // Get all connectors of the active segment
1907
2038
  var activeConnectors = [];
1908
2039
  activeSegment.traverse(function (child) {
1909
- var _child$userData19;
1910
- if (((_child$userData19 = child.userData) === null || _child$userData19 === void 0 ? void 0 : _child$userData19.objectType) === 'segment-connector') {
2040
+ var _child$userData21;
2041
+ if (((_child$userData21 = child.userData) === null || _child$userData21 === void 0 ? void 0 : _child$userData21.objectType) === 'segment-connector') {
1911
2042
  activeConnectors.push(child);
1912
2043
  }
1913
2044
  });
@@ -130,7 +130,7 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
130
130
  // ============================================================================
131
131
 
132
132
  /**
133
- * Enrich sceneData with worldBoundingBox for segments
133
+ * Enrich sceneData with worldBoundingBox for segments and components
134
134
  * Connectors remain with just position information
135
135
  * @param {Object} sceneData - Original scene data
136
136
  * @returns {Object} Enriched scene data (shallow copy with modifications)
@@ -147,9 +147,9 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
147
147
  return enriched;
148
148
  }
149
149
 
150
- // Process children to add worldBoundingBox to segments
150
+ // Process children to add worldBoundingBox to segments and components
151
151
  enriched.children = sceneData.children.map(function (child) {
152
- // Only enrich segments (check if objectType is 'segment' in userData)
152
+ // Enrich segments (check if objectType is 'segment' in userData)
153
153
  if (child.userData && child.userData.objectType === 'segment') {
154
154
  // Find the actual segment object in the scene
155
155
  var segmentObject = _this3.sceneViewer.scene.getObjectByProperty('uuid', child.uuid);
@@ -172,7 +172,31 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
172
172
  }
173
173
  }
174
174
 
175
- // For non-segments (including connectors), return as-is
175
+ // Enrich components (check if objectType is 'component' in userData)
176
+ if (child.userData && child.userData.objectType === 'component') {
177
+ // Find the actual component object in the scene
178
+ var componentObject = _this3.sceneViewer.scene.getObjectByProperty('uuid', child.uuid);
179
+ if (componentObject) {
180
+ // Compute world bounding box
181
+ var _worldBBox = new THREE__namespace.Box3().setFromObject(componentObject);
182
+ console.log("\uD83D\uDD04 Updated worldBoundingBox for component ".concat(child.uuid, ": min=[").concat(_worldBBox.min.x.toFixed(2), ", ").concat(_worldBBox.min.y.toFixed(2), ", ").concat(_worldBBox.min.z.toFixed(2), "], max=[").concat(_worldBBox.max.x.toFixed(2), ", ").concat(_worldBBox.max.y.toFixed(2), ", ").concat(_worldBBox.max.z.toFixed(2), "]"));
183
+
184
+ // Return enriched component data with worldBoundingBox in userData
185
+ // Note: pathfinder expects arrays [x, y, z] format for min/max
186
+ return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, child), {}, {
187
+ userData: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, child.userData), {}, {
188
+ worldBoundingBox: {
189
+ min: [_worldBBox.min.x, _worldBBox.min.y, _worldBBox.min.z],
190
+ max: [_worldBBox.max.x, _worldBBox.max.y, _worldBBox.max.z]
191
+ }
192
+ })
193
+ });
194
+ } else {
195
+ console.warn("\u26A0\uFE0F Could not find component object in scene: ".concat(child.uuid));
196
+ }
197
+ }
198
+
199
+ // For non-segments and non-components (including connectors), return as-is
176
200
  return child;
177
201
  });
178
202
  return enriched;
@@ -15,7 +15,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
15
15
  * Initialize the CentralPlant manager
16
16
  *
17
17
  * @constructor
18
- * @version 0.1.55
18
+ * @version 0.1.59
19
19
  * @updated 2025-10-22
20
20
  *
21
21
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -62,10 +62,15 @@ var TransformOperationsManager = /*#__PURE__*/function () {
62
62
  console.error("\u274C translateComponent(): Object with ID '".concat(componentId, "' is not a valid component"));
63
63
  return false;
64
64
  }
65
-
66
- // Apply the translation
67
65
  console.log("\uD83D\uDD04 translateComponent(): Translating component ".concat(componentId, " on ").concat(axis, " axis by ").concat(value));
68
66
 
67
+ // Run collision validation checks before applying the translation
68
+ var collisionValidation = this.validateTranslateComponentCollisions(component, axis, value);
69
+ if (!collisionValidation) {
70
+ return false;
71
+ }
72
+
73
+ // All validations passed - apply the translation
69
74
  // Update the Three.js object position
70
75
  component.position[axis] += value;
71
76
 
@@ -109,6 +114,48 @@ var TransformOperationsManager = /*#__PURE__*/function () {
109
114
  return true;
110
115
  }
111
116
 
117
+ /**
118
+ * Validate collision checks for translateComponent operation
119
+ * @param {THREE.Object3D} component - The component to translate
120
+ * @param {string} axis - The axis to translate on ('x', 'y', or 'z')
121
+ * @param {number} value - The value to translate by
122
+ * @returns {boolean} True if no collisions detected, false if translation would cause collision
123
+ * @private
124
+ */
125
+ }, {
126
+ key: "validateTranslateComponentCollisions",
127
+ value: function validateTranslateComponentCollisions(component, axis, value) {
128
+ // Store original position for reverting
129
+ var originalPosition = component.position[axis];
130
+
131
+ // Temporarily apply the translation to check for collisions
132
+ component.position[axis] += value;
133
+ component.updateMatrix();
134
+ component.updateMatrixWorld(true);
135
+ console.log("\uD83D\uDD0D Checking collisions with translated position: ".concat(axis, "=").concat(component.position[axis].toFixed(3)));
136
+
137
+ // Check for collision with manual segments
138
+ var manualSegmentCollision = this.checkComponentManualSegmentCollision(component);
139
+ if (manualSegmentCollision) {
140
+ // Revert the translation
141
+ component.position[axis] = originalPosition;
142
+ component.updateMatrix();
143
+ component.updateMatrixWorld(true);
144
+ console.warn("\u26A0\uFE0F translateComponent(): Translation canceled - component would collide with manual segment");
145
+ console.warn(" Segment ID: ".concat(manualSegmentCollision.segmentId));
146
+ console.warn(" Distance to segment: ".concat(manualSegmentCollision.distance.toFixed(3), " (max allowed: 0)"));
147
+ console.warn(" Segment endpoints: [".concat(manualSegmentCollision.segmentStart.x.toFixed(3), ", ").concat(manualSegmentCollision.segmentStart.y.toFixed(3), ", ").concat(manualSegmentCollision.segmentStart.z.toFixed(3), "] to [").concat(manualSegmentCollision.segmentEnd.x.toFixed(3), ", ").concat(manualSegmentCollision.segmentEnd.y.toFixed(3), ", ").concat(manualSegmentCollision.segmentEnd.z.toFixed(3), "]"));
148
+ return false;
149
+ }
150
+ console.log('✅ No collisions detected, proceeding with translation');
151
+
152
+ // Revert the temporary translation
153
+ component.position[axis] = originalPosition;
154
+ component.updateMatrix();
155
+ component.updateMatrixWorld(true);
156
+ return true;
157
+ }
158
+
112
159
  /**
113
160
  * Translate a pipe segment by segmentId
114
161
  * @param {string} segmentId - The UUID of the pipe segment to translate
@@ -1188,6 +1235,90 @@ var TransformOperationsManager = /*#__PURE__*/function () {
1188
1235
  return collision;
1189
1236
  }
1190
1237
 
1238
+ /**
1239
+ * Check if a component would collide with any manual (declared) segment
1240
+ * @param {THREE.Object3D} component - The component to check for collisions
1241
+ * @returns {Object|null} Collision info {segmentId, distance, segmentStart, segmentEnd} if collision detected, null otherwise
1242
+ * @private
1243
+ */
1244
+ }, {
1245
+ key: "checkComponentManualSegmentCollision",
1246
+ value: function checkComponentManualSegmentCollision(component) {
1247
+ var _this$sceneViewer8,
1248
+ _this3 = this;
1249
+ if (!((_this$sceneViewer8 = this.sceneViewer) !== null && _this$sceneViewer8 !== void 0 && _this$sceneViewer8.scene) || !component) {
1250
+ return null;
1251
+ }
1252
+
1253
+ // Create a bounding box for the component
1254
+ var componentBBox = new THREE.Box3().setFromObject(component);
1255
+ var collision = null;
1256
+
1257
+ // Traverse scene to find all manual segments (isDeclared === true)
1258
+ this.sceneViewer.scene.traverse(function (child) {
1259
+ var _child$userData15, _child$userData16;
1260
+ if (collision) return; // Stop if collision already found
1261
+
1262
+ // Only check manual segments (isDeclared === true)
1263
+ if (((_child$userData15 = child.userData) === null || _child$userData15 === void 0 ? void 0 : _child$userData15.objectType) === 'segment' && ((_child$userData16 = child.userData) === null || _child$userData16 === void 0 ? void 0 : _child$userData16.isDeclared) === true) {
1264
+ // Get segment endpoints
1265
+ var segmentEndpoints = _this3.getSegmentEndpoints(child);
1266
+
1267
+ // Check if segment intersects with component's bounding box
1268
+ // We'll use a line-box intersection test
1269
+ var line = new THREE.Line3(segmentEndpoints.start, segmentEndpoints.end);
1270
+ var closestPoint = new THREE.Vector3();
1271
+ line.closestPointToPoint(componentBBox.getCenter(new THREE.Vector3()), true, closestPoint);
1272
+
1273
+ // Check if the closest point on the line is within the bounding box
1274
+ if (componentBBox.containsPoint(closestPoint)) {
1275
+ console.log('⚠️ TransformOperationsManager: Component bounding box collision with manual segment:', child.uuid);
1276
+ collision = {
1277
+ segmentId: child.uuid,
1278
+ distance: 0,
1279
+ // Inside the bounding box
1280
+ segmentStart: segmentEndpoints.start.clone(),
1281
+ segmentEnd: segmentEndpoints.end.clone()
1282
+ };
1283
+ return;
1284
+ }
1285
+
1286
+ // Also check if either endpoint of the segment is inside the component bounding box
1287
+ if (componentBBox.containsPoint(segmentEndpoints.start) || componentBBox.containsPoint(segmentEndpoints.end)) {
1288
+ console.log('⚠️ TransformOperationsManager: Component bounding box contains manual segment endpoint:', child.uuid);
1289
+ collision = {
1290
+ segmentId: child.uuid,
1291
+ distance: 0,
1292
+ // Inside the bounding box
1293
+ segmentStart: segmentEndpoints.start.clone(),
1294
+ segmentEnd: segmentEndpoints.end.clone()
1295
+ };
1296
+ return;
1297
+ }
1298
+
1299
+ // Additionally, check if the segment line intersects any of the bounding box faces
1300
+ // This catches cases where the segment passes through the box without endpoints inside
1301
+ var ray = new THREE.Ray(segmentEndpoints.start, new THREE.Vector3().subVectors(segmentEndpoints.end, segmentEndpoints.start).normalize());
1302
+ var segmentLength = segmentEndpoints.start.distanceTo(segmentEndpoints.end);
1303
+ var intersection = ray.intersectBox(componentBBox, new THREE.Vector3());
1304
+ if (intersection) {
1305
+ var distanceToIntersection = segmentEndpoints.start.distanceTo(intersection);
1306
+ if (distanceToIntersection <= segmentLength) {
1307
+ console.log('⚠️ TransformOperationsManager: Manual segment intersects component bounding box:', child.uuid);
1308
+ collision = {
1309
+ segmentId: child.uuid,
1310
+ distance: 0,
1311
+ // Intersects the bounding box
1312
+ segmentStart: segmentEndpoints.start.clone(),
1313
+ segmentEnd: segmentEndpoints.end.clone()
1314
+ };
1315
+ }
1316
+ }
1317
+ }
1318
+ });
1319
+ return collision;
1320
+ }
1321
+
1191
1322
  /**
1192
1323
  * Calculate the minimum distance between two line segments in 3D space
1193
1324
  * @param {THREE.Vector3} p1 - Start point of first segment
@@ -1297,10 +1428,10 @@ var TransformOperationsManager = /*#__PURE__*/function () {
1297
1428
  }, {
1298
1429
  key: "snapSegmentConnectorsToNearbyEndpoints",
1299
1430
  value: function snapSegmentConnectorsToNearbyEndpoints(movedSegment) {
1300
- var _this$sceneViewer8,
1301
- _this$sceneViewer9,
1302
- _this3 = this;
1303
- if (!movedSegment || !((_this$sceneViewer8 = this.sceneViewer) !== null && _this$sceneViewer8 !== void 0 && _this$sceneViewer8.scene)) {
1431
+ var _this$sceneViewer9,
1432
+ _this$sceneViewer0,
1433
+ _this4 = this;
1434
+ if (!movedSegment || !((_this$sceneViewer9 = this.sceneViewer) !== null && _this$sceneViewer9 !== void 0 && _this$sceneViewer9.scene)) {
1304
1435
  return [];
1305
1436
  }
1306
1437
  console.log('🔗 Finding adjacent segments connected to moved segment...');
@@ -1308,8 +1439,8 @@ var TransformOperationsManager = /*#__PURE__*/function () {
1308
1439
  // Get the moved segment's connectors
1309
1440
  var movedConnectors = [];
1310
1441
  movedSegment.traverse(function (child) {
1311
- var _child$userData15;
1312
- if (((_child$userData15 = child.userData) === null || _child$userData15 === void 0 ? void 0 : _child$userData15.objectType) === 'segment-connector') {
1442
+ var _child$userData17;
1443
+ if (((_child$userData17 = child.userData) === null || _child$userData17 === void 0 ? void 0 : _child$userData17.objectType) === 'segment-connector') {
1313
1444
  movedConnectors.push(child);
1314
1445
  }
1315
1446
  });
@@ -1322,7 +1453,7 @@ var TransformOperationsManager = /*#__PURE__*/function () {
1322
1453
  var newEndpoints = this.calculateSegmentEndpoints(movedSegment);
1323
1454
 
1324
1455
  // Check scene data for connections involving the moved segment's connectors
1325
- var connections = ((_this$sceneViewer9 = this.sceneViewer) === null || _this$sceneViewer9 === void 0 || (_this$sceneViewer9 = _this$sceneViewer9.currentSceneData) === null || _this$sceneViewer9 === void 0 ? void 0 : _this$sceneViewer9.connections) || [];
1456
+ var connections = ((_this$sceneViewer0 = this.sceneViewer) === null || _this$sceneViewer0 === void 0 || (_this$sceneViewer0 = _this$sceneViewer0.currentSceneData) === null || _this$sceneViewer0 === void 0 ? void 0 : _this$sceneViewer0.connections) || [];
1326
1457
  var movedConnectorIds = movedConnectors.map(function (c) {
1327
1458
  return c.uuid;
1328
1459
  });
@@ -1357,7 +1488,7 @@ var TransformOperationsManager = /*#__PURE__*/function () {
1357
1488
  // Find the adjacent connector in the scene
1358
1489
  var adjacentConnector = null;
1359
1490
  var adjacentSegment = null;
1360
- _this3.sceneViewer.scene.traverse(function (object) {
1491
+ _this4.sceneViewer.scene.traverse(function (object) {
1361
1492
  var _object$userData;
1362
1493
  if (object.uuid === pair.adjacentConnectorId && ((_object$userData = object.userData) === null || _object$userData === void 0 ? void 0 : _object$userData.objectType) === 'segment-connector') {
1363
1494
  adjacentConnector = object;
@@ -1379,8 +1510,8 @@ var TransformOperationsManager = /*#__PURE__*/function () {
1379
1510
  // Get all connectors of the adjacent segment
1380
1511
  var adjacentConnectors = [];
1381
1512
  adjacentSegment.traverse(function (child) {
1382
- var _child$userData16;
1383
- if (((_child$userData16 = child.userData) === null || _child$userData16 === void 0 ? void 0 : _child$userData16.objectType) === 'segment-connector') {
1513
+ var _child$userData18;
1514
+ if (((_child$userData18 = child.userData) === null || _child$userData18 === void 0 ? void 0 : _child$userData18.objectType) === 'segment-connector') {
1384
1515
  adjacentConnectors.push(child);
1385
1516
  }
1386
1517
  });
@@ -1406,12 +1537,12 @@ var TransformOperationsManager = /*#__PURE__*/function () {
1406
1537
  // Constrain movement to maintain orthogonal alignment
1407
1538
  // Horizontal segments: only adjust X and Y, keep Z constant
1408
1539
  // Vertical segments: only adjust Z, keep X and Y constant
1409
- var constrainedPosition = _this3.constrainPositionToOrthogonal(pair.newPosition, stationaryWorldPos, adjacentSegment);
1540
+ var constrainedPosition = _this4.constrainPositionToOrthogonal(pair.newPosition, stationaryWorldPos, adjacentSegment);
1410
1541
 
1411
1542
  // Recreate the segment mesh with new length using explicit endpoint positions
1412
1543
  // Pass the intended positions, not the connector objects (which may have temporary positions)
1413
1544
  // Pass movedSegment as activeSegment context so zero-length removal knows which segment to extend
1414
- _this3.recreateSegmentMeshWithNewLength(adjacentSegment, adjacentConnectors, constrainedPosition, stationaryWorldPos, movedSegment);
1545
+ _this4.recreateSegmentMeshWithNewLength(adjacentSegment, adjacentConnectors, constrainedPosition, stationaryWorldPos, movedSegment);
1415
1546
 
1416
1547
  // CRITICAL: After recreating the mesh, BOTH connectors need to be repositioned
1417
1548
  // because the segment's center and rotation have changed
@@ -1420,13 +1551,13 @@ var TransformOperationsManager = /*#__PURE__*/function () {
1420
1551
  var movingLocalPos = adjacentSegment.worldToLocal(constrainedPosition.clone());
1421
1552
  movingConnector.position.copy(movingLocalPos);
1422
1553
  movingConnector.updateMatrixWorld(true);
1423
- _this3.updateConnectorPositionInSceneData(movingConnector, constrainedPosition, adjacentSegment);
1554
+ _this4.updateConnectorPositionInSceneData(movingConnector, constrainedPosition, adjacentSegment);
1424
1555
 
1425
1556
  // Position stationary connector at its original world position
1426
1557
  var stationaryLocalPos = adjacentSegment.worldToLocal(stationaryWorldPos.clone());
1427
1558
  stationaryConnector.position.copy(stationaryLocalPos);
1428
1559
  stationaryConnector.updateMatrixWorld(true);
1429
- _this3.updateConnectorPositionInSceneData(stationaryConnector, stationaryWorldPos, adjacentSegment);
1560
+ _this4.updateConnectorPositionInSceneData(stationaryConnector, stationaryWorldPos, adjacentSegment);
1430
1561
 
1431
1562
  // Record this connection as satisfied
1432
1563
  satisfiedConnections.push({
@@ -1476,8 +1607,8 @@ var TransformOperationsManager = /*#__PURE__*/function () {
1476
1607
  }, {
1477
1608
  key: "updateComponentPositionInSceneData",
1478
1609
  value: function updateComponentPositionInSceneData(component) {
1479
- var _this$sceneViewer0;
1480
- if (!((_this$sceneViewer0 = this.sceneViewer) !== null && _this$sceneViewer0 !== void 0 && (_this$sceneViewer0 = _this$sceneViewer0.currentSceneData) !== null && _this$sceneViewer0 !== void 0 && _this$sceneViewer0.scene)) {
1610
+ var _this$sceneViewer1;
1611
+ if (!((_this$sceneViewer1 = this.sceneViewer) !== null && _this$sceneViewer1 !== void 0 && (_this$sceneViewer1 = _this$sceneViewer1.currentSceneData) !== null && _this$sceneViewer1 !== void 0 && _this$sceneViewer1.scene)) {
1481
1612
  console.warn('⚠️ Cannot update component position: currentSceneData not available');
1482
1613
  return;
1483
1614
  }
@@ -1503,8 +1634,8 @@ var TransformOperationsManager = /*#__PURE__*/function () {
1503
1634
  // Also update child connectors' positions if they exist
1504
1635
  if (sceneDataComponent.children) {
1505
1636
  sceneDataComponent.children.forEach(function (child) {
1506
- var _child$userData17;
1507
- if (((_child$userData17 = child.userData) === null || _child$userData17 === void 0 ? void 0 : _child$userData17.objectType) === 'connector') {
1637
+ var _child$userData19;
1638
+ if (((_child$userData19 = child.userData) === null || _child$userData19 === void 0 ? void 0 : _child$userData19.objectType) === 'connector') {
1508
1639
  // Find the actual connector object in the scene
1509
1640
  var connectorObj = component.children.find(function (c) {
1510
1641
  var _c$userData;
@@ -1540,9 +1671,9 @@ var TransformOperationsManager = /*#__PURE__*/function () {
1540
1671
  }, {
1541
1672
  key: "updateConnectorPositionInSceneData",
1542
1673
  value: function updateConnectorPositionInSceneData(connector, worldPosition, parentSegment) {
1543
- var _this$sceneViewer1;
1674
+ var _this$sceneViewer10;
1544
1675
  // Update scene data if available
1545
- if (!((_this$sceneViewer1 = this.sceneViewer) !== null && _this$sceneViewer1 !== void 0 && (_this$sceneViewer1 = _this$sceneViewer1.currentSceneData) !== null && _this$sceneViewer1 !== void 0 && _this$sceneViewer1.scene)) {
1676
+ if (!((_this$sceneViewer10 = this.sceneViewer) !== null && _this$sceneViewer10 !== void 0 && (_this$sceneViewer10 = _this$sceneViewer10.currentSceneData) !== null && _this$sceneViewer10 !== void 0 && _this$sceneViewer10.scene)) {
1546
1677
  return;
1547
1678
  }
1548
1679
  var cleanPosition = function cleanPosition(value) {
@@ -1833,8 +1964,8 @@ var TransformOperationsManager = /*#__PURE__*/function () {
1833
1964
  // Check if either external connector belongs to the active segment
1834
1965
  var activeSegmentConnectors = [];
1835
1966
  activeSegment.traverse(function (child) {
1836
- var _child$userData18;
1837
- if (((_child$userData18 = child.userData) === null || _child$userData18 === void 0 ? void 0 : _child$userData18.objectType) === 'segment-connector') {
1967
+ var _child$userData20;
1968
+ if (((_child$userData20 = child.userData) === null || _child$userData20 === void 0 ? void 0 : _child$userData20.objectType) === 'segment-connector') {
1838
1969
  activeSegmentConnectors.push(child.uuid);
1839
1970
  }
1840
1971
  });
@@ -1882,8 +2013,8 @@ var TransformOperationsManager = /*#__PURE__*/function () {
1882
2013
  // Get all connectors of the active segment
1883
2014
  var activeConnectors = [];
1884
2015
  activeSegment.traverse(function (child) {
1885
- var _child$userData19;
1886
- if (((_child$userData19 = child.userData) === null || _child$userData19 === void 0 ? void 0 : _child$userData19.objectType) === 'segment-connector') {
2016
+ var _child$userData21;
2017
+ if (((_child$userData21 = child.userData) === null || _child$userData21 === void 0 ? void 0 : _child$userData21.objectType) === 'segment-connector') {
1887
2018
  activeConnectors.push(child);
1888
2019
  }
1889
2020
  });
@@ -106,7 +106,7 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
106
106
  // ============================================================================
107
107
 
108
108
  /**
109
- * Enrich sceneData with worldBoundingBox for segments
109
+ * Enrich sceneData with worldBoundingBox for segments and components
110
110
  * Connectors remain with just position information
111
111
  * @param {Object} sceneData - Original scene data
112
112
  * @returns {Object} Enriched scene data (shallow copy with modifications)
@@ -123,9 +123,9 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
123
123
  return enriched;
124
124
  }
125
125
 
126
- // Process children to add worldBoundingBox to segments
126
+ // Process children to add worldBoundingBox to segments and components
127
127
  enriched.children = sceneData.children.map(function (child) {
128
- // Only enrich segments (check if objectType is 'segment' in userData)
128
+ // Enrich segments (check if objectType is 'segment' in userData)
129
129
  if (child.userData && child.userData.objectType === 'segment') {
130
130
  // Find the actual segment object in the scene
131
131
  var segmentObject = _this3.sceneViewer.scene.getObjectByProperty('uuid', child.uuid);
@@ -148,7 +148,31 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
148
148
  }
149
149
  }
150
150
 
151
- // For non-segments (including connectors), return as-is
151
+ // Enrich components (check if objectType is 'component' in userData)
152
+ if (child.userData && child.userData.objectType === 'component') {
153
+ // Find the actual component object in the scene
154
+ var componentObject = _this3.sceneViewer.scene.getObjectByProperty('uuid', child.uuid);
155
+ if (componentObject) {
156
+ // Compute world bounding box
157
+ var _worldBBox = new THREE.Box3().setFromObject(componentObject);
158
+ console.log("\uD83D\uDD04 Updated worldBoundingBox for component ".concat(child.uuid, ": min=[").concat(_worldBBox.min.x.toFixed(2), ", ").concat(_worldBBox.min.y.toFixed(2), ", ").concat(_worldBBox.min.z.toFixed(2), "], max=[").concat(_worldBBox.max.x.toFixed(2), ", ").concat(_worldBBox.max.y.toFixed(2), ", ").concat(_worldBBox.max.z.toFixed(2), "]"));
159
+
160
+ // Return enriched component data with worldBoundingBox in userData
161
+ // Note: pathfinder expects arrays [x, y, z] format for min/max
162
+ return _objectSpread2(_objectSpread2({}, child), {}, {
163
+ userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
164
+ worldBoundingBox: {
165
+ min: [_worldBBox.min.x, _worldBBox.min.y, _worldBBox.min.z],
166
+ max: [_worldBBox.max.x, _worldBBox.max.y, _worldBBox.max.z]
167
+ }
168
+ })
169
+ });
170
+ } else {
171
+ console.warn("\u26A0\uFE0F Could not find component object in scene: ".concat(child.uuid));
172
+ }
173
+ }
174
+
175
+ // For non-segments and non-components (including connectors), return as-is
152
176
  return child;
153
177
  });
154
178
  return enriched;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2112-lab/central-plant",
3
- "version": "0.1.55",
3
+ "version": "0.1.59",
4
4
  "description": "Utility modules for the Central Plant Application",
5
5
  "main": "dist/bundle/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -19,7 +19,8 @@
19
19
  "build:types": "node -e \"require('fs').copyFileSync('src/index.d.ts', 'dist/index.d.ts')\"",
20
20
  "dev": "rollup -c -w",
21
21
  "prepublishOnly": "npm run build",
22
- "docs": "jsdoc -c jsdoc.json"
22
+ "docs": "jsdoc -c jsdoc.json",
23
+ "release": "npm install @2112-lab/pathfinder@latest && npm version patch && npm publish"
23
24
  },
24
25
  "keywords": [
25
26
  "central-plant",
@@ -30,7 +31,7 @@
30
31
  "author": "CentralPlant Team",
31
32
  "license": "MIT",
32
33
  "dependencies": {
33
- "@2112-lab/pathfinder": "1.0.34",
34
+ "@2112-lab/pathfinder": "^1.0.34",
34
35
  "stats.js": "^0.17.0",
35
36
  "three": "^0.177.0"
36
37
  },