@2112-lab/central-plant 0.1.25 → 0.1.27

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.
@@ -25861,12 +25861,133 @@ var CentralPlant = /*#__PURE__*/function () {
25861
25861
  return true;
25862
25862
  }
25863
25863
 
25864
+ /**
25865
+ * Rotate a component by componentId
25866
+ * @param {string} componentId - The UUID of the component to rotate
25867
+ * @param {string} axis - The axis to rotate around ('x', 'y', or 'z')
25868
+ * @param {number} value - The value to rotate by in degrees
25869
+ * @returns {boolean} True if rotation was successful, false otherwise
25870
+ * @description Rotates a component around the specified axis by the given angle in degrees.
25871
+ * The rotation is applied incrementally to the component's current rotation.
25872
+ * @example
25873
+ * // Rotate a component 45 degrees around Y axis
25874
+ * const success = centralPlant.rotate('component-uuid-123', 'y', 45);
25875
+ *
25876
+ * // Rotate a component -90 degrees around Z axis
25877
+ * centralPlant.rotate('component-uuid-456', 'z', -90);
25878
+ *
25879
+ * // Rotate a component 180 degrees around X axis
25880
+ * centralPlant.rotate('component-uuid-789', 'x', 180);
25881
+ *
25882
+ * @since 0.1.25
25883
+ */
25884
+ }, {
25885
+ key: "rotate",
25886
+ value: function rotate(componentId, axis, value) {
25887
+ // Validate input parameters
25888
+ if (!componentId || typeof componentId !== 'string') {
25889
+ console.error('❌ rotate(): Invalid componentId provided. Must be a non-empty string:', componentId);
25890
+ return false;
25891
+ }
25892
+ if (!['x', 'y', 'z'].includes(axis)) {
25893
+ console.error('❌ rotate(): Invalid axis provided. Must be "x", "y", or "z":', axis);
25894
+ return false;
25895
+ }
25896
+ if (typeof value !== 'number' || isNaN(value)) {
25897
+ console.error('❌ rotate(): Invalid value provided. Must be a number:', value);
25898
+ return false;
25899
+ }
25900
+
25901
+ // Check if scene viewer and scene are available
25902
+ if (!this.sceneViewer || !this.sceneViewer.scene) {
25903
+ console.error('❌ rotate(): Scene viewer or scene not available');
25904
+ return false;
25905
+ }
25906
+
25907
+ // Find the component in the scene
25908
+ var component = null;
25909
+ this.sceneViewer.scene.traverse(function (child) {
25910
+ var _child$userData2;
25911
+ if (child.uuid === componentId || ((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.originalUuid) === componentId) {
25912
+ component = child;
25913
+ }
25914
+ });
25915
+ if (!component) {
25916
+ console.error("\u274C rotate(): Component with ID '".concat(componentId, "' not found in scene"));
25917
+ return false;
25918
+ }
25919
+ if (!component.userData.libraryId) {
25920
+ console.error("\u274C rotate(): Object with ID '".concat(componentId, "' is not a valid component"));
25921
+ return false;
25922
+ }
25923
+
25924
+ // Store previous rotation for potential undo
25925
+ var previousRotation = {
25926
+ x: component.rotation.x,
25927
+ y: component.rotation.y,
25928
+ z: component.rotation.z
25929
+ };
25930
+
25931
+ // Convert degrees to radians
25932
+ var radians = value * Math.PI / 180;
25933
+ console.log("\uD83D\uDD04 rotate(): Rotating component ".concat(componentId, " around ").concat(axis, " axis by ").concat(value, " degrees (").concat(radians, " radians)"));
25934
+
25935
+ // Apply the rotation
25936
+ component.rotation[axis] += radians;
25937
+
25938
+ // Update the associated JSON object in currentSceneData if it exists
25939
+ if (this.sceneViewer.currentSceneData && this.sceneViewer.currentSceneData.scene) {
25940
+ if (component.userData.associatedJsonObject) {
25941
+ // Convert back to degrees for JSON storage
25942
+ component.userData.associatedJsonObject.rotation = component.userData.associatedJsonObject.rotation || {
25943
+ x: 0,
25944
+ y: 0,
25945
+ z: 0
25946
+ };
25947
+ component.userData.associatedJsonObject.rotation[axis] = component.rotation[axis] * 180 / Math.PI;
25948
+ console.log("\uD83D\uDD04 Updated associated JSON object rotation for ".concat(componentId));
25949
+ }
25950
+ }
25951
+
25952
+ // Update matrices
25953
+ component.updateMatrix();
25954
+ component.updateMatrixWorld(true);
25955
+
25956
+ // Record the transform for potential undo
25957
+ this.internals.recordTransform({
25958
+ type: 'rotate',
25959
+ object: component,
25960
+ values: {
25961
+ axis: axis,
25962
+ value: value,
25963
+ radians: radians
25964
+ },
25965
+ previousValues: {
25966
+ rotation: previousRotation
25967
+ }
25968
+ });
25969
+
25970
+ // Emit transform event if available
25971
+ if (this.sceneViewer.emit && typeof this.sceneViewer.emit === 'function') {
25972
+ this.sceneViewer.emit('objectTransformed', {
25973
+ object: component,
25974
+ transformType: 'rotate',
25975
+ axis: axis,
25976
+ value: value,
25977
+ radians: radians,
25978
+ previousRotation: previousRotation
25979
+ });
25980
+ }
25981
+ console.log("\u2705 rotate(): Component ".concat(componentId, " rotated successfully"));
25982
+ return true;
25983
+ }
25984
+
25864
25985
  /**
25865
25986
  * Update paths in the scene
25866
25987
  * @returns {boolean} True if paths were updated successfully, false otherwise
25867
25988
  * @description Triggers an update of all connection paths/pipes in the scene to reflect
25868
- * current component positions and connections. This should be called after moving components
25869
- * or adding/removing connections.
25989
+ * current component positions and connections. This method ensures all scene data is
25990
+ * synchronized before updating paths for guaranteed accuracy.
25870
25991
  * @example
25871
25992
  * // After moving components or changing connections
25872
25993
  * const success = centralPlant.updatePaths();
@@ -25882,8 +26003,15 @@ var CentralPlant = /*#__PURE__*/function () {
25882
26003
  return false;
25883
26004
  }
25884
26005
  try {
26006
+ // Step 1: Ensure scene data is synchronized before updating paths
26007
+ console.log('🔄 updatePaths(): Synchronizing scene data before path update...');
26008
+ if (!this.syncSceneData()) {
26009
+ console.warn('⚠️ updatePaths(): Scene data synchronization failed, proceeding with caution');
26010
+ }
26011
+
26012
+ // Step 2: Update paths with synchronized data
25885
26013
  this.sceneViewer.updatePaths();
25886
- console.log('✅ updatePaths(): Paths updated successfully');
26014
+ console.log('✅ updatePaths(): Paths updated successfully with synchronized data');
25887
26015
  return true;
25888
26016
  } catch (error) {
25889
26017
  console.error('❌ updatePaths(): Error updating paths:', error);
@@ -25891,6 +26019,262 @@ var CentralPlant = /*#__PURE__*/function () {
25891
26019
  }
25892
26020
  }
25893
26021
 
26022
+ /**
26023
+ * Synchronize scene data to ensure all component positions and properties are up-to-date
26024
+ * @returns {boolean} True if synchronization was successful, false otherwise
26025
+ * @description Updates the currentSceneData to match the actual Three.js scene state.
26026
+ * This includes component positions, rotations, scales, and world bounding boxes.
26027
+ * Should be called before operations that depend on accurate scene data.
26028
+ * @example
26029
+ * // Ensure scene data is current before pathfinding
26030
+ * const success = centralPlant.syncSceneData();
26031
+ * if (success) {
26032
+ * console.log('Scene data synchronized with Three.js scene');
26033
+ * }
26034
+ */
26035
+ }, {
26036
+ key: "syncSceneData",
26037
+ value: function syncSceneData() {
26038
+ var _this = this;
26039
+ // Check if scene viewer and currentSceneData are available
26040
+ if (!this.sceneViewer || !this.sceneViewer.currentSceneData) {
26041
+ console.warn('⚠️ syncSceneData(): Scene viewer or current scene data not available');
26042
+ return false;
26043
+ }
26044
+ if (!this.sceneViewer.scene) {
26045
+ console.warn('⚠️ syncSceneData(): Three.js scene not available');
26046
+ return false;
26047
+ }
26048
+ try {
26049
+ console.log('🔄 syncSceneData(): Starting scene data synchronization...');
26050
+
26051
+ // Get reference to currentSceneData
26052
+ var currentSceneData = this.sceneViewer.currentSceneData;
26053
+ if (!currentSceneData.scene || !currentSceneData.scene.object || !currentSceneData.scene.object.children) {
26054
+ console.warn('⚠️ syncSceneData(): Invalid currentSceneData structure');
26055
+ return false;
26056
+ }
26057
+
26058
+ // Update each component in scene data to match Three.js scene
26059
+ var updatedCount = 0;
26060
+ currentSceneData.scene.object.children.forEach(function (sceneDataChild, index) {
26061
+ // Find matching Three.js object
26062
+ var matchingThreeObject = null;
26063
+ _this.sceneViewer.scene.traverse(function (threeObject) {
26064
+ var _sceneDataChild$userD, _threeObject$userData, _threeObject$userData2;
26065
+ // Check various UUID matching strategies
26066
+ if (threeObject.uuid === sceneDataChild.uuid || threeObject.uuid === ((_sceneDataChild$userD = sceneDataChild.userData) === null || _sceneDataChild$userD === void 0 ? void 0 : _sceneDataChild$userD.originalUuid) || ((_threeObject$userData = threeObject.userData) === null || _threeObject$userData === void 0 ? void 0 : _threeObject$userData.originalUuid) === sceneDataChild.uuid || ((_threeObject$userData2 = threeObject.userData) === null || _threeObject$userData2 === void 0 ? void 0 : _threeObject$userData2.hardcodedUuid) === sceneDataChild.uuid) {
26067
+ matchingThreeObject = threeObject;
26068
+ }
26069
+ });
26070
+ if (matchingThreeObject) {
26071
+ // Update position, rotation, and scale
26072
+ sceneDataChild.position = {
26073
+ x: matchingThreeObject.position.x,
26074
+ y: matchingThreeObject.position.y,
26075
+ z: matchingThreeObject.position.z
26076
+ };
26077
+ sceneDataChild.rotation = {
26078
+ x: matchingThreeObject.rotation.x,
26079
+ y: matchingThreeObject.rotation.y,
26080
+ z: matchingThreeObject.rotation.z
26081
+ };
26082
+ sceneDataChild.scale = {
26083
+ x: matchingThreeObject.scale.x,
26084
+ y: matchingThreeObject.scale.y,
26085
+ z: matchingThreeObject.scale.z
26086
+ };
26087
+
26088
+ // Update matrix
26089
+ sceneDataChild.matrix = matchingThreeObject.matrix.elements;
26090
+
26091
+ // Update world bounding box
26092
+ if (matchingThreeObject.isMesh) {
26093
+ var boundingBox = new THREE__namespace.Box3().setFromObject(matchingThreeObject);
26094
+ if (!sceneDataChild.userData) sceneDataChild.userData = {};
26095
+ sceneDataChild.userData.worldBoundingBox = {
26096
+ min: boundingBox.min.toArray(),
26097
+ max: boundingBox.max.toArray()
26098
+ };
26099
+ }
26100
+
26101
+ // Sync children (connectors) if they exist
26102
+ if (sceneDataChild.children && matchingThreeObject.children) {
26103
+ sceneDataChild.children.forEach(function (sceneChild, childIndex) {
26104
+ var matchingChild = matchingThreeObject.children.find(function (threeChild) {
26105
+ var _sceneChild$userData, _threeChild$userData;
26106
+ return threeChild.uuid === sceneChild.uuid || threeChild.uuid === ((_sceneChild$userData = sceneChild.userData) === null || _sceneChild$userData === void 0 ? void 0 : _sceneChild$userData.originalUuid) || ((_threeChild$userData = threeChild.userData) === null || _threeChild$userData === void 0 ? void 0 : _threeChild$userData.originalUuid) === sceneChild.uuid;
26107
+ });
26108
+ if (matchingChild) {
26109
+ var _matchingChild$userDa;
26110
+ // Update child position, rotation, and scale
26111
+ sceneChild.position = {
26112
+ x: matchingChild.position.x,
26113
+ y: matchingChild.position.y,
26114
+ z: matchingChild.position.z
26115
+ };
26116
+ sceneChild.rotation = {
26117
+ x: matchingChild.rotation.x,
26118
+ y: matchingChild.rotation.y,
26119
+ z: matchingChild.rotation.z
26120
+ };
26121
+ sceneChild.scale = {
26122
+ x: matchingChild.scale.x,
26123
+ y: matchingChild.scale.y,
26124
+ z: matchingChild.scale.z
26125
+ };
26126
+
26127
+ // Update direction vector for connectors
26128
+ if ((_matchingChild$userDa = matchingChild.userData) !== null && _matchingChild$userDa !== void 0 && _matchingChild$userDa.direction) {
26129
+ if (!sceneChild.userData) sceneChild.userData = {};
26130
+ sceneChild.userData.direction = _toConsumableArray(matchingChild.userData.direction);
26131
+ }
26132
+ }
26133
+ });
26134
+ }
26135
+ updatedCount++;
26136
+ } else {
26137
+ console.warn("\u26A0\uFE0F syncSceneData(): No matching Three.js object found for scene data child: ".concat(sceneDataChild.uuid));
26138
+ }
26139
+ });
26140
+
26141
+ // Force recompute world bounding boxes for pathfinding accuracy
26142
+ if (this.sceneViewer.pathfindingManager && typeof this.sceneViewer.pathfindingManager.recomputeWorldBoundingBoxes === 'function') {
26143
+ this.sceneViewer.pathfindingManager.recomputeWorldBoundingBoxes(currentSceneData);
26144
+ console.log('🔄 syncSceneData(): Recomputed world bounding boxes for pathfinding');
26145
+ }
26146
+ console.log("\u2705 syncSceneData(): Synchronized ".concat(updatedCount, " components successfully"));
26147
+ return true;
26148
+ } catch (error) {
26149
+ console.error('❌ syncSceneData(): Error synchronizing scene data:', error);
26150
+ return false;
26151
+ }
26152
+ }
26153
+
26154
+ /**
26155
+ * Force complete scene data synchronization using SceneOperationsManager
26156
+ * @returns {boolean} True if forced synchronization was successful, false otherwise
26157
+ * @description Uses the SceneOperationsManager's updateSceneDataAfterTransform method
26158
+ * to update scene data for every component. This is more thorough than syncSceneData()
26159
+ * and should be used when maximum accuracy is required.
26160
+ * @example
26161
+ * // Force complete synchronization before critical operations
26162
+ * const success = centralPlant.forceSyncSceneData();
26163
+ * if (success) {
26164
+ * console.log('Complete scene data synchronization completed');
26165
+ * }
26166
+ */
26167
+ }, {
26168
+ key: "forceSyncSceneData",
26169
+ value: function forceSyncSceneData() {
26170
+ var _this2 = this;
26171
+ // Check if scene viewer and sceneOperationsManager are available
26172
+ if (!this.sceneViewer || !this.sceneViewer.sceneOperationsManager) {
26173
+ console.warn('⚠️ forceSyncSceneData(): Scene viewer or scene operations manager not available');
26174
+ return false;
26175
+ }
26176
+ if (!this.sceneViewer.currentSceneData) {
26177
+ console.warn('⚠️ forceSyncSceneData(): Current scene data not available');
26178
+ return false;
26179
+ }
26180
+ if (!this.sceneViewer.scene) {
26181
+ console.warn('⚠️ forceSyncSceneData(): Three.js scene not available');
26182
+ return false;
26183
+ }
26184
+ try {
26185
+ console.log('🔄 forceSyncSceneData(): Starting forced scene data synchronization...');
26186
+ var syncedCount = 0;
26187
+ var currentSceneData = this.sceneViewer.currentSceneData;
26188
+
26189
+ // Traverse all objects in the Three.js scene and update their scene data
26190
+ this.sceneViewer.scene.traverse(function (threeObject) {
26191
+ // Only sync objects that have component-related userData
26192
+ if (threeObject.userData && (threeObject.userData.componentType === 'component' || threeObject.userData.componentType === 'gateway' || threeObject.userData.libraryId)) {
26193
+ console.log("\uD83D\uDD04 Syncing object: ".concat(threeObject.uuid, " (").concat(threeObject.userData.componentType || 'unknown', ")"));
26194
+
26195
+ // Use the existing updateSceneDataAfterTransform method
26196
+ var success = _this2.sceneViewer.sceneOperationsManager.updateSceneDataAfterTransform(threeObject, currentSceneData);
26197
+ if (success) {
26198
+ syncedCount++;
26199
+ } else {
26200
+ console.warn("\u26A0\uFE0F Failed to sync object: ".concat(threeObject.uuid));
26201
+ }
26202
+ }
26203
+ });
26204
+ console.log("\u2705 forceSyncSceneData(): Force synchronized ".concat(syncedCount, " objects successfully"));
26205
+ return true;
26206
+ } catch (error) {
26207
+ console.error('❌ forceSyncSceneData(): Error during forced scene data synchronization:', error);
26208
+ return false;
26209
+ }
26210
+ }
26211
+
26212
+ /**
26213
+ * Update paths with optional scene data synchronization level
26214
+ * @param {string} [syncLevel='auto'] - Level of synchronization: 'none', 'basic', 'complete', or 'auto'
26215
+ * @returns {boolean} True if paths were updated successfully, false otherwise
26216
+ * @description Enhanced updatePaths method that allows choosing the level of scene data
26217
+ * synchronization before updating paths. The 'auto' level intelligently chooses based
26218
+ * on scene complexity and recent changes.
26219
+ * @example
26220
+ * // Update paths with automatic synchronization (recommended)
26221
+ * centralPlant.updatePathsWithSync('auto');
26222
+ *
26223
+ * // Update paths with complete synchronization (most accurate)
26224
+ * centralPlant.updatePathsWithSync('complete');
26225
+ *
26226
+ * // Update paths without synchronization (fastest)
26227
+ * centralPlant.updatePathsWithSync('none');
26228
+ */
26229
+ }, {
26230
+ key: "updatePathsWithSync",
26231
+ value: function updatePathsWithSync() {
26232
+ var syncLevel = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'auto';
26233
+ if (!this.sceneViewer || !this.sceneViewer.updatePaths) {
26234
+ console.warn('⚠️ updatePathsWithSync(): Scene viewer or updatePaths method not available');
26235
+ return false;
26236
+ }
26237
+ try {
26238
+ // Determine sync level automatically if needed
26239
+ if (syncLevel === 'auto') {
26240
+ var _this$transformHistor;
26241
+ // Use basic sync by default, but upgrade to complete if recent transforms detected
26242
+ syncLevel = (_this$transformHistor = this.transformHistory) !== null && _this$transformHistor !== void 0 && _this$transformHistor.lastTransform && Date.now() - new Date(this.transformHistory.lastTransform.timestamp).getTime() < 5000 ? 'complete' : 'basic';
26243
+ console.log("\uD83E\uDD16 updatePathsWithSync(): Auto-selected sync level: ".concat(syncLevel));
26244
+ }
26245
+
26246
+ // Perform synchronization based on level
26247
+ var syncSuccess = true;
26248
+ switch (syncLevel) {
26249
+ case 'none':
26250
+ console.log('⚡ updatePathsWithSync(): Skipping synchronization for speed');
26251
+ break;
26252
+ case 'basic':
26253
+ console.log('🔄 updatePathsWithSync(): Performing basic scene data synchronization...');
26254
+ syncSuccess = this.syncSceneData();
26255
+ break;
26256
+ case 'complete':
26257
+ console.log('🔄 updatePathsWithSync(): Performing complete scene data synchronization...');
26258
+ syncSuccess = this.forceSyncSceneData();
26259
+ break;
26260
+ default:
26261
+ console.warn("\u26A0\uFE0F updatePathsWithSync(): Unknown sync level '".concat(syncLevel, "', defaulting to basic"));
26262
+ syncSuccess = this.syncSceneData();
26263
+ }
26264
+ if (!syncSuccess && syncLevel !== 'none') {
26265
+ console.warn('⚠️ updatePathsWithSync(): Scene data synchronization failed, proceeding with caution');
26266
+ }
26267
+
26268
+ // Update paths
26269
+ this.sceneViewer.updatePaths();
26270
+ console.log("\u2705 updatePathsWithSync(): Paths updated successfully with '".concat(syncLevel, "' synchronization"));
26271
+ return true;
26272
+ } catch (error) {
26273
+ console.error('❌ updatePathsWithSync(): Error updating paths with sync:', error);
26274
+ return false;
26275
+ }
26276
+ }
26277
+
25894
26278
  /**
25895
26279
  * Add a connection between two connector IDs to the connections list
25896
26280
  * @param {string} fromConnectorId - The UUID of the source connector
@@ -565,12 +565,133 @@ var CentralPlant = /*#__PURE__*/function () {
565
565
  return true;
566
566
  }
567
567
 
568
+ /**
569
+ * Rotate a component by componentId
570
+ * @param {string} componentId - The UUID of the component to rotate
571
+ * @param {string} axis - The axis to rotate around ('x', 'y', or 'z')
572
+ * @param {number} value - The value to rotate by in degrees
573
+ * @returns {boolean} True if rotation was successful, false otherwise
574
+ * @description Rotates a component around the specified axis by the given angle in degrees.
575
+ * The rotation is applied incrementally to the component's current rotation.
576
+ * @example
577
+ * // Rotate a component 45 degrees around Y axis
578
+ * const success = centralPlant.rotate('component-uuid-123', 'y', 45);
579
+ *
580
+ * // Rotate a component -90 degrees around Z axis
581
+ * centralPlant.rotate('component-uuid-456', 'z', -90);
582
+ *
583
+ * // Rotate a component 180 degrees around X axis
584
+ * centralPlant.rotate('component-uuid-789', 'x', 180);
585
+ *
586
+ * @since 0.1.25
587
+ */
588
+ }, {
589
+ key: "rotate",
590
+ value: function rotate(componentId, axis, value) {
591
+ // Validate input parameters
592
+ if (!componentId || typeof componentId !== 'string') {
593
+ console.error('❌ rotate(): Invalid componentId provided. Must be a non-empty string:', componentId);
594
+ return false;
595
+ }
596
+ if (!['x', 'y', 'z'].includes(axis)) {
597
+ console.error('❌ rotate(): Invalid axis provided. Must be "x", "y", or "z":', axis);
598
+ return false;
599
+ }
600
+ if (typeof value !== 'number' || isNaN(value)) {
601
+ console.error('❌ rotate(): Invalid value provided. Must be a number:', value);
602
+ return false;
603
+ }
604
+
605
+ // Check if scene viewer and scene are available
606
+ if (!this.sceneViewer || !this.sceneViewer.scene) {
607
+ console.error('❌ rotate(): Scene viewer or scene not available');
608
+ return false;
609
+ }
610
+
611
+ // Find the component in the scene
612
+ var component = null;
613
+ this.sceneViewer.scene.traverse(function (child) {
614
+ var _child$userData2;
615
+ if (child.uuid === componentId || ((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.originalUuid) === componentId) {
616
+ component = child;
617
+ }
618
+ });
619
+ if (!component) {
620
+ console.error("\u274C rotate(): Component with ID '".concat(componentId, "' not found in scene"));
621
+ return false;
622
+ }
623
+ if (!component.userData.libraryId) {
624
+ console.error("\u274C rotate(): Object with ID '".concat(componentId, "' is not a valid component"));
625
+ return false;
626
+ }
627
+
628
+ // Store previous rotation for potential undo
629
+ var previousRotation = {
630
+ x: component.rotation.x,
631
+ y: component.rotation.y,
632
+ z: component.rotation.z
633
+ };
634
+
635
+ // Convert degrees to radians
636
+ var radians = value * Math.PI / 180;
637
+ console.log("\uD83D\uDD04 rotate(): Rotating component ".concat(componentId, " around ").concat(axis, " axis by ").concat(value, " degrees (").concat(radians, " radians)"));
638
+
639
+ // Apply the rotation
640
+ component.rotation[axis] += radians;
641
+
642
+ // Update the associated JSON object in currentSceneData if it exists
643
+ if (this.sceneViewer.currentSceneData && this.sceneViewer.currentSceneData.scene) {
644
+ if (component.userData.associatedJsonObject) {
645
+ // Convert back to degrees for JSON storage
646
+ component.userData.associatedJsonObject.rotation = component.userData.associatedJsonObject.rotation || {
647
+ x: 0,
648
+ y: 0,
649
+ z: 0
650
+ };
651
+ component.userData.associatedJsonObject.rotation[axis] = component.rotation[axis] * 180 / Math.PI;
652
+ console.log("\uD83D\uDD04 Updated associated JSON object rotation for ".concat(componentId));
653
+ }
654
+ }
655
+
656
+ // Update matrices
657
+ component.updateMatrix();
658
+ component.updateMatrixWorld(true);
659
+
660
+ // Record the transform for potential undo
661
+ this.internals.recordTransform({
662
+ type: 'rotate',
663
+ object: component,
664
+ values: {
665
+ axis: axis,
666
+ value: value,
667
+ radians: radians
668
+ },
669
+ previousValues: {
670
+ rotation: previousRotation
671
+ }
672
+ });
673
+
674
+ // Emit transform event if available
675
+ if (this.sceneViewer.emit && typeof this.sceneViewer.emit === 'function') {
676
+ this.sceneViewer.emit('objectTransformed', {
677
+ object: component,
678
+ transformType: 'rotate',
679
+ axis: axis,
680
+ value: value,
681
+ radians: radians,
682
+ previousRotation: previousRotation
683
+ });
684
+ }
685
+ console.log("\u2705 rotate(): Component ".concat(componentId, " rotated successfully"));
686
+ return true;
687
+ }
688
+
568
689
  /**
569
690
  * Update paths in the scene
570
691
  * @returns {boolean} True if paths were updated successfully, false otherwise
571
692
  * @description Triggers an update of all connection paths/pipes in the scene to reflect
572
- * current component positions and connections. This should be called after moving components
573
- * or adding/removing connections.
693
+ * current component positions and connections. This method ensures all scene data is
694
+ * synchronized before updating paths for guaranteed accuracy.
574
695
  * @example
575
696
  * // After moving components or changing connections
576
697
  * const success = centralPlant.updatePaths();
@@ -586,8 +707,15 @@ var CentralPlant = /*#__PURE__*/function () {
586
707
  return false;
587
708
  }
588
709
  try {
710
+ // Step 1: Ensure scene data is synchronized before updating paths
711
+ console.log('🔄 updatePaths(): Synchronizing scene data before path update...');
712
+ if (!this.syncSceneData()) {
713
+ console.warn('⚠️ updatePaths(): Scene data synchronization failed, proceeding with caution');
714
+ }
715
+
716
+ // Step 2: Update paths with synchronized data
589
717
  this.sceneViewer.updatePaths();
590
- console.log('✅ updatePaths(): Paths updated successfully');
718
+ console.log('✅ updatePaths(): Paths updated successfully with synchronized data');
591
719
  return true;
592
720
  } catch (error) {
593
721
  console.error('❌ updatePaths(): Error updating paths:', error);
@@ -595,6 +723,262 @@ var CentralPlant = /*#__PURE__*/function () {
595
723
  }
596
724
  }
597
725
 
726
+ /**
727
+ * Synchronize scene data to ensure all component positions and properties are up-to-date
728
+ * @returns {boolean} True if synchronization was successful, false otherwise
729
+ * @description Updates the currentSceneData to match the actual Three.js scene state.
730
+ * This includes component positions, rotations, scales, and world bounding boxes.
731
+ * Should be called before operations that depend on accurate scene data.
732
+ * @example
733
+ * // Ensure scene data is current before pathfinding
734
+ * const success = centralPlant.syncSceneData();
735
+ * if (success) {
736
+ * console.log('Scene data synchronized with Three.js scene');
737
+ * }
738
+ */
739
+ }, {
740
+ key: "syncSceneData",
741
+ value: function syncSceneData() {
742
+ var _this = this;
743
+ // Check if scene viewer and currentSceneData are available
744
+ if (!this.sceneViewer || !this.sceneViewer.currentSceneData) {
745
+ console.warn('⚠️ syncSceneData(): Scene viewer or current scene data not available');
746
+ return false;
747
+ }
748
+ if (!this.sceneViewer.scene) {
749
+ console.warn('⚠️ syncSceneData(): Three.js scene not available');
750
+ return false;
751
+ }
752
+ try {
753
+ console.log('🔄 syncSceneData(): Starting scene data synchronization...');
754
+
755
+ // Get reference to currentSceneData
756
+ var currentSceneData = this.sceneViewer.currentSceneData;
757
+ if (!currentSceneData.scene || !currentSceneData.scene.object || !currentSceneData.scene.object.children) {
758
+ console.warn('⚠️ syncSceneData(): Invalid currentSceneData structure');
759
+ return false;
760
+ }
761
+
762
+ // Update each component in scene data to match Three.js scene
763
+ var updatedCount = 0;
764
+ currentSceneData.scene.object.children.forEach(function (sceneDataChild, index) {
765
+ // Find matching Three.js object
766
+ var matchingThreeObject = null;
767
+ _this.sceneViewer.scene.traverse(function (threeObject) {
768
+ var _sceneDataChild$userD, _threeObject$userData, _threeObject$userData2;
769
+ // Check various UUID matching strategies
770
+ if (threeObject.uuid === sceneDataChild.uuid || threeObject.uuid === ((_sceneDataChild$userD = sceneDataChild.userData) === null || _sceneDataChild$userD === void 0 ? void 0 : _sceneDataChild$userD.originalUuid) || ((_threeObject$userData = threeObject.userData) === null || _threeObject$userData === void 0 ? void 0 : _threeObject$userData.originalUuid) === sceneDataChild.uuid || ((_threeObject$userData2 = threeObject.userData) === null || _threeObject$userData2 === void 0 ? void 0 : _threeObject$userData2.hardcodedUuid) === sceneDataChild.uuid) {
771
+ matchingThreeObject = threeObject;
772
+ }
773
+ });
774
+ if (matchingThreeObject) {
775
+ // Update position, rotation, and scale
776
+ sceneDataChild.position = {
777
+ x: matchingThreeObject.position.x,
778
+ y: matchingThreeObject.position.y,
779
+ z: matchingThreeObject.position.z
780
+ };
781
+ sceneDataChild.rotation = {
782
+ x: matchingThreeObject.rotation.x,
783
+ y: matchingThreeObject.rotation.y,
784
+ z: matchingThreeObject.rotation.z
785
+ };
786
+ sceneDataChild.scale = {
787
+ x: matchingThreeObject.scale.x,
788
+ y: matchingThreeObject.scale.y,
789
+ z: matchingThreeObject.scale.z
790
+ };
791
+
792
+ // Update matrix
793
+ sceneDataChild.matrix = matchingThreeObject.matrix.elements;
794
+
795
+ // Update world bounding box
796
+ if (matchingThreeObject.isMesh) {
797
+ var boundingBox = new THREE__namespace.Box3().setFromObject(matchingThreeObject);
798
+ if (!sceneDataChild.userData) sceneDataChild.userData = {};
799
+ sceneDataChild.userData.worldBoundingBox = {
800
+ min: boundingBox.min.toArray(),
801
+ max: boundingBox.max.toArray()
802
+ };
803
+ }
804
+
805
+ // Sync children (connectors) if they exist
806
+ if (sceneDataChild.children && matchingThreeObject.children) {
807
+ sceneDataChild.children.forEach(function (sceneChild, childIndex) {
808
+ var matchingChild = matchingThreeObject.children.find(function (threeChild) {
809
+ var _sceneChild$userData, _threeChild$userData;
810
+ return threeChild.uuid === sceneChild.uuid || threeChild.uuid === ((_sceneChild$userData = sceneChild.userData) === null || _sceneChild$userData === void 0 ? void 0 : _sceneChild$userData.originalUuid) || ((_threeChild$userData = threeChild.userData) === null || _threeChild$userData === void 0 ? void 0 : _threeChild$userData.originalUuid) === sceneChild.uuid;
811
+ });
812
+ if (matchingChild) {
813
+ var _matchingChild$userDa;
814
+ // Update child position, rotation, and scale
815
+ sceneChild.position = {
816
+ x: matchingChild.position.x,
817
+ y: matchingChild.position.y,
818
+ z: matchingChild.position.z
819
+ };
820
+ sceneChild.rotation = {
821
+ x: matchingChild.rotation.x,
822
+ y: matchingChild.rotation.y,
823
+ z: matchingChild.rotation.z
824
+ };
825
+ sceneChild.scale = {
826
+ x: matchingChild.scale.x,
827
+ y: matchingChild.scale.y,
828
+ z: matchingChild.scale.z
829
+ };
830
+
831
+ // Update direction vector for connectors
832
+ if ((_matchingChild$userDa = matchingChild.userData) !== null && _matchingChild$userDa !== void 0 && _matchingChild$userDa.direction) {
833
+ if (!sceneChild.userData) sceneChild.userData = {};
834
+ sceneChild.userData.direction = _rollupPluginBabelHelpers.toConsumableArray(matchingChild.userData.direction);
835
+ }
836
+ }
837
+ });
838
+ }
839
+ updatedCount++;
840
+ } else {
841
+ console.warn("\u26A0\uFE0F syncSceneData(): No matching Three.js object found for scene data child: ".concat(sceneDataChild.uuid));
842
+ }
843
+ });
844
+
845
+ // Force recompute world bounding boxes for pathfinding accuracy
846
+ if (this.sceneViewer.pathfindingManager && typeof this.sceneViewer.pathfindingManager.recomputeWorldBoundingBoxes === 'function') {
847
+ this.sceneViewer.pathfindingManager.recomputeWorldBoundingBoxes(currentSceneData);
848
+ console.log('🔄 syncSceneData(): Recomputed world bounding boxes for pathfinding');
849
+ }
850
+ console.log("\u2705 syncSceneData(): Synchronized ".concat(updatedCount, " components successfully"));
851
+ return true;
852
+ } catch (error) {
853
+ console.error('❌ syncSceneData(): Error synchronizing scene data:', error);
854
+ return false;
855
+ }
856
+ }
857
+
858
+ /**
859
+ * Force complete scene data synchronization using SceneOperationsManager
860
+ * @returns {boolean} True if forced synchronization was successful, false otherwise
861
+ * @description Uses the SceneOperationsManager's updateSceneDataAfterTransform method
862
+ * to update scene data for every component. This is more thorough than syncSceneData()
863
+ * and should be used when maximum accuracy is required.
864
+ * @example
865
+ * // Force complete synchronization before critical operations
866
+ * const success = centralPlant.forceSyncSceneData();
867
+ * if (success) {
868
+ * console.log('Complete scene data synchronization completed');
869
+ * }
870
+ */
871
+ }, {
872
+ key: "forceSyncSceneData",
873
+ value: function forceSyncSceneData() {
874
+ var _this2 = this;
875
+ // Check if scene viewer and sceneOperationsManager are available
876
+ if (!this.sceneViewer || !this.sceneViewer.sceneOperationsManager) {
877
+ console.warn('⚠️ forceSyncSceneData(): Scene viewer or scene operations manager not available');
878
+ return false;
879
+ }
880
+ if (!this.sceneViewer.currentSceneData) {
881
+ console.warn('⚠️ forceSyncSceneData(): Current scene data not available');
882
+ return false;
883
+ }
884
+ if (!this.sceneViewer.scene) {
885
+ console.warn('⚠️ forceSyncSceneData(): Three.js scene not available');
886
+ return false;
887
+ }
888
+ try {
889
+ console.log('🔄 forceSyncSceneData(): Starting forced scene data synchronization...');
890
+ var syncedCount = 0;
891
+ var currentSceneData = this.sceneViewer.currentSceneData;
892
+
893
+ // Traverse all objects in the Three.js scene and update their scene data
894
+ this.sceneViewer.scene.traverse(function (threeObject) {
895
+ // Only sync objects that have component-related userData
896
+ if (threeObject.userData && (threeObject.userData.componentType === 'component' || threeObject.userData.componentType === 'gateway' || threeObject.userData.libraryId)) {
897
+ console.log("\uD83D\uDD04 Syncing object: ".concat(threeObject.uuid, " (").concat(threeObject.userData.componentType || 'unknown', ")"));
898
+
899
+ // Use the existing updateSceneDataAfterTransform method
900
+ var success = _this2.sceneViewer.sceneOperationsManager.updateSceneDataAfterTransform(threeObject, currentSceneData);
901
+ if (success) {
902
+ syncedCount++;
903
+ } else {
904
+ console.warn("\u26A0\uFE0F Failed to sync object: ".concat(threeObject.uuid));
905
+ }
906
+ }
907
+ });
908
+ console.log("\u2705 forceSyncSceneData(): Force synchronized ".concat(syncedCount, " objects successfully"));
909
+ return true;
910
+ } catch (error) {
911
+ console.error('❌ forceSyncSceneData(): Error during forced scene data synchronization:', error);
912
+ return false;
913
+ }
914
+ }
915
+
916
+ /**
917
+ * Update paths with optional scene data synchronization level
918
+ * @param {string} [syncLevel='auto'] - Level of synchronization: 'none', 'basic', 'complete', or 'auto'
919
+ * @returns {boolean} True if paths were updated successfully, false otherwise
920
+ * @description Enhanced updatePaths method that allows choosing the level of scene data
921
+ * synchronization before updating paths. The 'auto' level intelligently chooses based
922
+ * on scene complexity and recent changes.
923
+ * @example
924
+ * // Update paths with automatic synchronization (recommended)
925
+ * centralPlant.updatePathsWithSync('auto');
926
+ *
927
+ * // Update paths with complete synchronization (most accurate)
928
+ * centralPlant.updatePathsWithSync('complete');
929
+ *
930
+ * // Update paths without synchronization (fastest)
931
+ * centralPlant.updatePathsWithSync('none');
932
+ */
933
+ }, {
934
+ key: "updatePathsWithSync",
935
+ value: function updatePathsWithSync() {
936
+ var syncLevel = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'auto';
937
+ if (!this.sceneViewer || !this.sceneViewer.updatePaths) {
938
+ console.warn('⚠️ updatePathsWithSync(): Scene viewer or updatePaths method not available');
939
+ return false;
940
+ }
941
+ try {
942
+ // Determine sync level automatically if needed
943
+ if (syncLevel === 'auto') {
944
+ var _this$transformHistor;
945
+ // Use basic sync by default, but upgrade to complete if recent transforms detected
946
+ syncLevel = (_this$transformHistor = this.transformHistory) !== null && _this$transformHistor !== void 0 && _this$transformHistor.lastTransform && Date.now() - new Date(this.transformHistory.lastTransform.timestamp).getTime() < 5000 ? 'complete' : 'basic';
947
+ console.log("\uD83E\uDD16 updatePathsWithSync(): Auto-selected sync level: ".concat(syncLevel));
948
+ }
949
+
950
+ // Perform synchronization based on level
951
+ var syncSuccess = true;
952
+ switch (syncLevel) {
953
+ case 'none':
954
+ console.log('⚡ updatePathsWithSync(): Skipping synchronization for speed');
955
+ break;
956
+ case 'basic':
957
+ console.log('🔄 updatePathsWithSync(): Performing basic scene data synchronization...');
958
+ syncSuccess = this.syncSceneData();
959
+ break;
960
+ case 'complete':
961
+ console.log('🔄 updatePathsWithSync(): Performing complete scene data synchronization...');
962
+ syncSuccess = this.forceSyncSceneData();
963
+ break;
964
+ default:
965
+ console.warn("\u26A0\uFE0F updatePathsWithSync(): Unknown sync level '".concat(syncLevel, "', defaulting to basic"));
966
+ syncSuccess = this.syncSceneData();
967
+ }
968
+ if (!syncSuccess && syncLevel !== 'none') {
969
+ console.warn('⚠️ updatePathsWithSync(): Scene data synchronization failed, proceeding with caution');
970
+ }
971
+
972
+ // Update paths
973
+ this.sceneViewer.updatePaths();
974
+ console.log("\u2705 updatePathsWithSync(): Paths updated successfully with '".concat(syncLevel, "' synchronization"));
975
+ return true;
976
+ } catch (error) {
977
+ console.error('❌ updatePathsWithSync(): Error updating paths with sync:', error);
978
+ return false;
979
+ }
980
+ }
981
+
598
982
  /**
599
983
  * Add a connection between two connector IDs to the connections list
600
984
  * @param {string} fromConnectorId - The UUID of the source connector
@@ -1,4 +1,4 @@
1
- import { createClass as _createClass, objectSpread2 as _objectSpread2, classCallCheck as _classCallCheck } from '../../_virtual/_rollupPluginBabelHelpers.js';
1
+ import { createClass as _createClass, objectSpread2 as _objectSpread2, toConsumableArray as _toConsumableArray, classCallCheck as _classCallCheck } from '../../_virtual/_rollupPluginBabelHelpers.js';
2
2
  import * as THREE from 'three';
3
3
  import { CentralPlantInternals } from './centralPlantInternals.js';
4
4
  import '../rendering/modelPreloader.js';
@@ -541,12 +541,133 @@ var CentralPlant = /*#__PURE__*/function () {
541
541
  return true;
542
542
  }
543
543
 
544
+ /**
545
+ * Rotate a component by componentId
546
+ * @param {string} componentId - The UUID of the component to rotate
547
+ * @param {string} axis - The axis to rotate around ('x', 'y', or 'z')
548
+ * @param {number} value - The value to rotate by in degrees
549
+ * @returns {boolean} True if rotation was successful, false otherwise
550
+ * @description Rotates a component around the specified axis by the given angle in degrees.
551
+ * The rotation is applied incrementally to the component's current rotation.
552
+ * @example
553
+ * // Rotate a component 45 degrees around Y axis
554
+ * const success = centralPlant.rotate('component-uuid-123', 'y', 45);
555
+ *
556
+ * // Rotate a component -90 degrees around Z axis
557
+ * centralPlant.rotate('component-uuid-456', 'z', -90);
558
+ *
559
+ * // Rotate a component 180 degrees around X axis
560
+ * centralPlant.rotate('component-uuid-789', 'x', 180);
561
+ *
562
+ * @since 0.1.25
563
+ */
564
+ }, {
565
+ key: "rotate",
566
+ value: function rotate(componentId, axis, value) {
567
+ // Validate input parameters
568
+ if (!componentId || typeof componentId !== 'string') {
569
+ console.error('❌ rotate(): Invalid componentId provided. Must be a non-empty string:', componentId);
570
+ return false;
571
+ }
572
+ if (!['x', 'y', 'z'].includes(axis)) {
573
+ console.error('❌ rotate(): Invalid axis provided. Must be "x", "y", or "z":', axis);
574
+ return false;
575
+ }
576
+ if (typeof value !== 'number' || isNaN(value)) {
577
+ console.error('❌ rotate(): Invalid value provided. Must be a number:', value);
578
+ return false;
579
+ }
580
+
581
+ // Check if scene viewer and scene are available
582
+ if (!this.sceneViewer || !this.sceneViewer.scene) {
583
+ console.error('❌ rotate(): Scene viewer or scene not available');
584
+ return false;
585
+ }
586
+
587
+ // Find the component in the scene
588
+ var component = null;
589
+ this.sceneViewer.scene.traverse(function (child) {
590
+ var _child$userData2;
591
+ if (child.uuid === componentId || ((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.originalUuid) === componentId) {
592
+ component = child;
593
+ }
594
+ });
595
+ if (!component) {
596
+ console.error("\u274C rotate(): Component with ID '".concat(componentId, "' not found in scene"));
597
+ return false;
598
+ }
599
+ if (!component.userData.libraryId) {
600
+ console.error("\u274C rotate(): Object with ID '".concat(componentId, "' is not a valid component"));
601
+ return false;
602
+ }
603
+
604
+ // Store previous rotation for potential undo
605
+ var previousRotation = {
606
+ x: component.rotation.x,
607
+ y: component.rotation.y,
608
+ z: component.rotation.z
609
+ };
610
+
611
+ // Convert degrees to radians
612
+ var radians = value * Math.PI / 180;
613
+ console.log("\uD83D\uDD04 rotate(): Rotating component ".concat(componentId, " around ").concat(axis, " axis by ").concat(value, " degrees (").concat(radians, " radians)"));
614
+
615
+ // Apply the rotation
616
+ component.rotation[axis] += radians;
617
+
618
+ // Update the associated JSON object in currentSceneData if it exists
619
+ if (this.sceneViewer.currentSceneData && this.sceneViewer.currentSceneData.scene) {
620
+ if (component.userData.associatedJsonObject) {
621
+ // Convert back to degrees for JSON storage
622
+ component.userData.associatedJsonObject.rotation = component.userData.associatedJsonObject.rotation || {
623
+ x: 0,
624
+ y: 0,
625
+ z: 0
626
+ };
627
+ component.userData.associatedJsonObject.rotation[axis] = component.rotation[axis] * 180 / Math.PI;
628
+ console.log("\uD83D\uDD04 Updated associated JSON object rotation for ".concat(componentId));
629
+ }
630
+ }
631
+
632
+ // Update matrices
633
+ component.updateMatrix();
634
+ component.updateMatrixWorld(true);
635
+
636
+ // Record the transform for potential undo
637
+ this.internals.recordTransform({
638
+ type: 'rotate',
639
+ object: component,
640
+ values: {
641
+ axis: axis,
642
+ value: value,
643
+ radians: radians
644
+ },
645
+ previousValues: {
646
+ rotation: previousRotation
647
+ }
648
+ });
649
+
650
+ // Emit transform event if available
651
+ if (this.sceneViewer.emit && typeof this.sceneViewer.emit === 'function') {
652
+ this.sceneViewer.emit('objectTransformed', {
653
+ object: component,
654
+ transformType: 'rotate',
655
+ axis: axis,
656
+ value: value,
657
+ radians: radians,
658
+ previousRotation: previousRotation
659
+ });
660
+ }
661
+ console.log("\u2705 rotate(): Component ".concat(componentId, " rotated successfully"));
662
+ return true;
663
+ }
664
+
544
665
  /**
545
666
  * Update paths in the scene
546
667
  * @returns {boolean} True if paths were updated successfully, false otherwise
547
668
  * @description Triggers an update of all connection paths/pipes in the scene to reflect
548
- * current component positions and connections. This should be called after moving components
549
- * or adding/removing connections.
669
+ * current component positions and connections. This method ensures all scene data is
670
+ * synchronized before updating paths for guaranteed accuracy.
550
671
  * @example
551
672
  * // After moving components or changing connections
552
673
  * const success = centralPlant.updatePaths();
@@ -562,8 +683,15 @@ var CentralPlant = /*#__PURE__*/function () {
562
683
  return false;
563
684
  }
564
685
  try {
686
+ // Step 1: Ensure scene data is synchronized before updating paths
687
+ console.log('🔄 updatePaths(): Synchronizing scene data before path update...');
688
+ if (!this.syncSceneData()) {
689
+ console.warn('⚠️ updatePaths(): Scene data synchronization failed, proceeding with caution');
690
+ }
691
+
692
+ // Step 2: Update paths with synchronized data
565
693
  this.sceneViewer.updatePaths();
566
- console.log('✅ updatePaths(): Paths updated successfully');
694
+ console.log('✅ updatePaths(): Paths updated successfully with synchronized data');
567
695
  return true;
568
696
  } catch (error) {
569
697
  console.error('❌ updatePaths(): Error updating paths:', error);
@@ -571,6 +699,262 @@ var CentralPlant = /*#__PURE__*/function () {
571
699
  }
572
700
  }
573
701
 
702
+ /**
703
+ * Synchronize scene data to ensure all component positions and properties are up-to-date
704
+ * @returns {boolean} True if synchronization was successful, false otherwise
705
+ * @description Updates the currentSceneData to match the actual Three.js scene state.
706
+ * This includes component positions, rotations, scales, and world bounding boxes.
707
+ * Should be called before operations that depend on accurate scene data.
708
+ * @example
709
+ * // Ensure scene data is current before pathfinding
710
+ * const success = centralPlant.syncSceneData();
711
+ * if (success) {
712
+ * console.log('Scene data synchronized with Three.js scene');
713
+ * }
714
+ */
715
+ }, {
716
+ key: "syncSceneData",
717
+ value: function syncSceneData() {
718
+ var _this = this;
719
+ // Check if scene viewer and currentSceneData are available
720
+ if (!this.sceneViewer || !this.sceneViewer.currentSceneData) {
721
+ console.warn('⚠️ syncSceneData(): Scene viewer or current scene data not available');
722
+ return false;
723
+ }
724
+ if (!this.sceneViewer.scene) {
725
+ console.warn('⚠️ syncSceneData(): Three.js scene not available');
726
+ return false;
727
+ }
728
+ try {
729
+ console.log('🔄 syncSceneData(): Starting scene data synchronization...');
730
+
731
+ // Get reference to currentSceneData
732
+ var currentSceneData = this.sceneViewer.currentSceneData;
733
+ if (!currentSceneData.scene || !currentSceneData.scene.object || !currentSceneData.scene.object.children) {
734
+ console.warn('⚠️ syncSceneData(): Invalid currentSceneData structure');
735
+ return false;
736
+ }
737
+
738
+ // Update each component in scene data to match Three.js scene
739
+ var updatedCount = 0;
740
+ currentSceneData.scene.object.children.forEach(function (sceneDataChild, index) {
741
+ // Find matching Three.js object
742
+ var matchingThreeObject = null;
743
+ _this.sceneViewer.scene.traverse(function (threeObject) {
744
+ var _sceneDataChild$userD, _threeObject$userData, _threeObject$userData2;
745
+ // Check various UUID matching strategies
746
+ if (threeObject.uuid === sceneDataChild.uuid || threeObject.uuid === ((_sceneDataChild$userD = sceneDataChild.userData) === null || _sceneDataChild$userD === void 0 ? void 0 : _sceneDataChild$userD.originalUuid) || ((_threeObject$userData = threeObject.userData) === null || _threeObject$userData === void 0 ? void 0 : _threeObject$userData.originalUuid) === sceneDataChild.uuid || ((_threeObject$userData2 = threeObject.userData) === null || _threeObject$userData2 === void 0 ? void 0 : _threeObject$userData2.hardcodedUuid) === sceneDataChild.uuid) {
747
+ matchingThreeObject = threeObject;
748
+ }
749
+ });
750
+ if (matchingThreeObject) {
751
+ // Update position, rotation, and scale
752
+ sceneDataChild.position = {
753
+ x: matchingThreeObject.position.x,
754
+ y: matchingThreeObject.position.y,
755
+ z: matchingThreeObject.position.z
756
+ };
757
+ sceneDataChild.rotation = {
758
+ x: matchingThreeObject.rotation.x,
759
+ y: matchingThreeObject.rotation.y,
760
+ z: matchingThreeObject.rotation.z
761
+ };
762
+ sceneDataChild.scale = {
763
+ x: matchingThreeObject.scale.x,
764
+ y: matchingThreeObject.scale.y,
765
+ z: matchingThreeObject.scale.z
766
+ };
767
+
768
+ // Update matrix
769
+ sceneDataChild.matrix = matchingThreeObject.matrix.elements;
770
+
771
+ // Update world bounding box
772
+ if (matchingThreeObject.isMesh) {
773
+ var boundingBox = new THREE.Box3().setFromObject(matchingThreeObject);
774
+ if (!sceneDataChild.userData) sceneDataChild.userData = {};
775
+ sceneDataChild.userData.worldBoundingBox = {
776
+ min: boundingBox.min.toArray(),
777
+ max: boundingBox.max.toArray()
778
+ };
779
+ }
780
+
781
+ // Sync children (connectors) if they exist
782
+ if (sceneDataChild.children && matchingThreeObject.children) {
783
+ sceneDataChild.children.forEach(function (sceneChild, childIndex) {
784
+ var matchingChild = matchingThreeObject.children.find(function (threeChild) {
785
+ var _sceneChild$userData, _threeChild$userData;
786
+ return threeChild.uuid === sceneChild.uuid || threeChild.uuid === ((_sceneChild$userData = sceneChild.userData) === null || _sceneChild$userData === void 0 ? void 0 : _sceneChild$userData.originalUuid) || ((_threeChild$userData = threeChild.userData) === null || _threeChild$userData === void 0 ? void 0 : _threeChild$userData.originalUuid) === sceneChild.uuid;
787
+ });
788
+ if (matchingChild) {
789
+ var _matchingChild$userDa;
790
+ // Update child position, rotation, and scale
791
+ sceneChild.position = {
792
+ x: matchingChild.position.x,
793
+ y: matchingChild.position.y,
794
+ z: matchingChild.position.z
795
+ };
796
+ sceneChild.rotation = {
797
+ x: matchingChild.rotation.x,
798
+ y: matchingChild.rotation.y,
799
+ z: matchingChild.rotation.z
800
+ };
801
+ sceneChild.scale = {
802
+ x: matchingChild.scale.x,
803
+ y: matchingChild.scale.y,
804
+ z: matchingChild.scale.z
805
+ };
806
+
807
+ // Update direction vector for connectors
808
+ if ((_matchingChild$userDa = matchingChild.userData) !== null && _matchingChild$userDa !== void 0 && _matchingChild$userDa.direction) {
809
+ if (!sceneChild.userData) sceneChild.userData = {};
810
+ sceneChild.userData.direction = _toConsumableArray(matchingChild.userData.direction);
811
+ }
812
+ }
813
+ });
814
+ }
815
+ updatedCount++;
816
+ } else {
817
+ console.warn("\u26A0\uFE0F syncSceneData(): No matching Three.js object found for scene data child: ".concat(sceneDataChild.uuid));
818
+ }
819
+ });
820
+
821
+ // Force recompute world bounding boxes for pathfinding accuracy
822
+ if (this.sceneViewer.pathfindingManager && typeof this.sceneViewer.pathfindingManager.recomputeWorldBoundingBoxes === 'function') {
823
+ this.sceneViewer.pathfindingManager.recomputeWorldBoundingBoxes(currentSceneData);
824
+ console.log('🔄 syncSceneData(): Recomputed world bounding boxes for pathfinding');
825
+ }
826
+ console.log("\u2705 syncSceneData(): Synchronized ".concat(updatedCount, " components successfully"));
827
+ return true;
828
+ } catch (error) {
829
+ console.error('❌ syncSceneData(): Error synchronizing scene data:', error);
830
+ return false;
831
+ }
832
+ }
833
+
834
+ /**
835
+ * Force complete scene data synchronization using SceneOperationsManager
836
+ * @returns {boolean} True if forced synchronization was successful, false otherwise
837
+ * @description Uses the SceneOperationsManager's updateSceneDataAfterTransform method
838
+ * to update scene data for every component. This is more thorough than syncSceneData()
839
+ * and should be used when maximum accuracy is required.
840
+ * @example
841
+ * // Force complete synchronization before critical operations
842
+ * const success = centralPlant.forceSyncSceneData();
843
+ * if (success) {
844
+ * console.log('Complete scene data synchronization completed');
845
+ * }
846
+ */
847
+ }, {
848
+ key: "forceSyncSceneData",
849
+ value: function forceSyncSceneData() {
850
+ var _this2 = this;
851
+ // Check if scene viewer and sceneOperationsManager are available
852
+ if (!this.sceneViewer || !this.sceneViewer.sceneOperationsManager) {
853
+ console.warn('⚠️ forceSyncSceneData(): Scene viewer or scene operations manager not available');
854
+ return false;
855
+ }
856
+ if (!this.sceneViewer.currentSceneData) {
857
+ console.warn('⚠️ forceSyncSceneData(): Current scene data not available');
858
+ return false;
859
+ }
860
+ if (!this.sceneViewer.scene) {
861
+ console.warn('⚠️ forceSyncSceneData(): Three.js scene not available');
862
+ return false;
863
+ }
864
+ try {
865
+ console.log('🔄 forceSyncSceneData(): Starting forced scene data synchronization...');
866
+ var syncedCount = 0;
867
+ var currentSceneData = this.sceneViewer.currentSceneData;
868
+
869
+ // Traverse all objects in the Three.js scene and update their scene data
870
+ this.sceneViewer.scene.traverse(function (threeObject) {
871
+ // Only sync objects that have component-related userData
872
+ if (threeObject.userData && (threeObject.userData.componentType === 'component' || threeObject.userData.componentType === 'gateway' || threeObject.userData.libraryId)) {
873
+ console.log("\uD83D\uDD04 Syncing object: ".concat(threeObject.uuid, " (").concat(threeObject.userData.componentType || 'unknown', ")"));
874
+
875
+ // Use the existing updateSceneDataAfterTransform method
876
+ var success = _this2.sceneViewer.sceneOperationsManager.updateSceneDataAfterTransform(threeObject, currentSceneData);
877
+ if (success) {
878
+ syncedCount++;
879
+ } else {
880
+ console.warn("\u26A0\uFE0F Failed to sync object: ".concat(threeObject.uuid));
881
+ }
882
+ }
883
+ });
884
+ console.log("\u2705 forceSyncSceneData(): Force synchronized ".concat(syncedCount, " objects successfully"));
885
+ return true;
886
+ } catch (error) {
887
+ console.error('❌ forceSyncSceneData(): Error during forced scene data synchronization:', error);
888
+ return false;
889
+ }
890
+ }
891
+
892
+ /**
893
+ * Update paths with optional scene data synchronization level
894
+ * @param {string} [syncLevel='auto'] - Level of synchronization: 'none', 'basic', 'complete', or 'auto'
895
+ * @returns {boolean} True if paths were updated successfully, false otherwise
896
+ * @description Enhanced updatePaths method that allows choosing the level of scene data
897
+ * synchronization before updating paths. The 'auto' level intelligently chooses based
898
+ * on scene complexity and recent changes.
899
+ * @example
900
+ * // Update paths with automatic synchronization (recommended)
901
+ * centralPlant.updatePathsWithSync('auto');
902
+ *
903
+ * // Update paths with complete synchronization (most accurate)
904
+ * centralPlant.updatePathsWithSync('complete');
905
+ *
906
+ * // Update paths without synchronization (fastest)
907
+ * centralPlant.updatePathsWithSync('none');
908
+ */
909
+ }, {
910
+ key: "updatePathsWithSync",
911
+ value: function updatePathsWithSync() {
912
+ var syncLevel = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'auto';
913
+ if (!this.sceneViewer || !this.sceneViewer.updatePaths) {
914
+ console.warn('⚠️ updatePathsWithSync(): Scene viewer or updatePaths method not available');
915
+ return false;
916
+ }
917
+ try {
918
+ // Determine sync level automatically if needed
919
+ if (syncLevel === 'auto') {
920
+ var _this$transformHistor;
921
+ // Use basic sync by default, but upgrade to complete if recent transforms detected
922
+ syncLevel = (_this$transformHistor = this.transformHistory) !== null && _this$transformHistor !== void 0 && _this$transformHistor.lastTransform && Date.now() - new Date(this.transformHistory.lastTransform.timestamp).getTime() < 5000 ? 'complete' : 'basic';
923
+ console.log("\uD83E\uDD16 updatePathsWithSync(): Auto-selected sync level: ".concat(syncLevel));
924
+ }
925
+
926
+ // Perform synchronization based on level
927
+ var syncSuccess = true;
928
+ switch (syncLevel) {
929
+ case 'none':
930
+ console.log('⚡ updatePathsWithSync(): Skipping synchronization for speed');
931
+ break;
932
+ case 'basic':
933
+ console.log('🔄 updatePathsWithSync(): Performing basic scene data synchronization...');
934
+ syncSuccess = this.syncSceneData();
935
+ break;
936
+ case 'complete':
937
+ console.log('🔄 updatePathsWithSync(): Performing complete scene data synchronization...');
938
+ syncSuccess = this.forceSyncSceneData();
939
+ break;
940
+ default:
941
+ console.warn("\u26A0\uFE0F updatePathsWithSync(): Unknown sync level '".concat(syncLevel, "', defaulting to basic"));
942
+ syncSuccess = this.syncSceneData();
943
+ }
944
+ if (!syncSuccess && syncLevel !== 'none') {
945
+ console.warn('⚠️ updatePathsWithSync(): Scene data synchronization failed, proceeding with caution');
946
+ }
947
+
948
+ // Update paths
949
+ this.sceneViewer.updatePaths();
950
+ console.log("\u2705 updatePathsWithSync(): Paths updated successfully with '".concat(syncLevel, "' synchronization"));
951
+ return true;
952
+ } catch (error) {
953
+ console.error('❌ updatePathsWithSync(): Error updating paths with sync:', error);
954
+ return false;
955
+ }
956
+ }
957
+
574
958
  /**
575
959
  * Add a connection between two connector IDs to the connections list
576
960
  * @param {string} fromConnectorId - The UUID of the source connector
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2112-lab/central-plant",
3
- "version": "0.1.25",
3
+ "version": "0.1.27",
4
4
  "description": "Utility modules for the Central Plant Application",
5
5
  "main": "dist/bundle/index.js",
6
6
  "module": "dist/esm/index.js",