@2112-lab/central-plant 0.1.39 โ†’ 0.1.41

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.
Files changed (45) hide show
  1. package/dist/bundle/index.js +7991 -7054
  2. package/dist/cjs/src/core/centralPlant.js +48 -3
  3. package/dist/cjs/src/core/centralPlantInternals.js +75 -566
  4. package/dist/cjs/src/core/sceneViewer.js +38 -13
  5. package/dist/cjs/src/index.js +6 -4
  6. package/dist/cjs/src/managers/components/pathfindingManager.js +75 -60
  7. package/dist/cjs/src/managers/components/transformOperationsManager.js +929 -0
  8. package/dist/cjs/src/managers/controls/keyboardControlsManager.js +57 -1
  9. package/dist/cjs/src/managers/controls/transformControls.js +11 -3
  10. package/dist/cjs/src/managers/controls/transformControlsManager.js +563 -263
  11. package/dist/cjs/src/managers/pathfinding/ConnectorManager.js +385 -0
  12. package/dist/cjs/src/managers/pathfinding/PathIntersectionDetector.js +387 -0
  13. package/dist/cjs/src/managers/pathfinding/PathRenderingManager.js +401 -0
  14. package/dist/cjs/src/managers/pathfinding/pathfindingManager.js +378 -0
  15. package/dist/cjs/src/managers/pathfinding/sceneDataManager.js +256 -0
  16. package/dist/cjs/src/managers/scene/animationManager.js +145 -0
  17. package/dist/cjs/src/managers/scene/sceneExportManager.js +14 -13
  18. package/dist/cjs/src/managers/scene/sceneOperationsManager.js +516 -21
  19. package/dist/cjs/src/managers/scene/sceneTooltipsManager.js +1 -8
  20. package/dist/cjs/src/managers/system/operationHistoryManager.js +414 -0
  21. package/dist/cjs/src/managers/system/settingsManager.js +2 -1
  22. package/dist/cjs/src/utils/objectTypes.js +5 -7
  23. package/dist/esm/src/core/centralPlant.js +48 -3
  24. package/dist/esm/src/core/centralPlantInternals.js +76 -567
  25. package/dist/esm/src/core/sceneViewer.js +38 -13
  26. package/dist/esm/src/index.js +4 -3
  27. package/dist/esm/src/managers/components/pathfindingManager.js +75 -60
  28. package/dist/esm/src/managers/components/transformOperationsManager.js +904 -0
  29. package/dist/esm/src/managers/controls/keyboardControlsManager.js +57 -1
  30. package/dist/esm/src/managers/controls/transformControls.js +11 -3
  31. package/dist/esm/src/managers/controls/transformControlsManager.js +564 -264
  32. package/dist/esm/src/managers/pathfinding/ConnectorManager.js +361 -0
  33. package/dist/esm/src/managers/pathfinding/PathIntersectionDetector.js +363 -0
  34. package/dist/esm/src/managers/pathfinding/PathRenderingManager.js +377 -0
  35. package/dist/esm/src/managers/pathfinding/pathfindingManager.js +374 -0
  36. package/dist/esm/src/managers/pathfinding/sceneDataManager.js +232 -0
  37. package/dist/esm/src/managers/scene/animationManager.js +141 -0
  38. package/dist/esm/src/managers/scene/sceneExportManager.js +14 -13
  39. package/dist/esm/src/managers/scene/sceneOperationsManager.js +516 -21
  40. package/dist/esm/src/managers/scene/sceneTooltipsManager.js +1 -8
  41. package/dist/esm/src/managers/system/operationHistoryManager.js +409 -0
  42. package/dist/esm/src/managers/system/settingsManager.js +2 -1
  43. package/dist/esm/src/utils/objectTypes.js +5 -7
  44. package/dist/index.d.ts +2 -2
  45. package/package.json +1 -1
@@ -16,14 +16,18 @@ var TransformControlsManager = /*#__PURE__*/function () {
16
16
 
17
17
  // Transform control instance
18
18
  this.transformControls = null;
19
- // Bounding box helper for visual feedback
20
- this.boundingBoxHelper = null;
19
+
20
+ // Array of bounding box helpers (one per selected object)
21
+ this.boundingBoxHelpers = [];
21
22
 
22
23
  // Cache for object bounding boxes to improve performance
23
24
  this.boundingBoxCache = new WeakMap();
24
25
 
25
- // Currently selected object
26
- this.selectedObject = null;
26
+ // Array of all selected objects (supports single or multi-selection)
27
+ this.selectedObjects = [];
28
+
29
+ // Group to hold selected objects for transform controls
30
+ this.multiSelectionGroup = null;
27
31
 
28
32
  // Transform mode: 'translate', 'rotate', 'scale'
29
33
  this.currentMode = 'translate';
@@ -121,9 +125,9 @@ var TransformControlsManager = /*#__PURE__*/function () {
121
125
  this._objectTransformedListener = function (eventData) {
122
126
  console.log('๐Ÿ”ฒ TransformControlsManager detected object transformation');
123
127
 
124
- // Only update bounding box if the transformed object is currently selected
125
- if (_this.selectedObject && eventData.object === _this.selectedObject) {
126
- console.log('๐Ÿ”ฒ Updating bounding box for selected object after transform');
128
+ // Update bounding box if the transformed object is in current selection
129
+ if (_this.selectedObjects.includes(eventData.object)) {
130
+ console.log('๐Ÿ”ฒ Updating bounding boxes for selected objects after transform');
127
131
  _this.updateBoundingBox();
128
132
  }
129
133
  };
@@ -212,13 +216,14 @@ var TransformControlsManager = /*#__PURE__*/function () {
212
216
  this.eventHandlers.transformStart = function () {
213
217
  console.log("transformControls transformStart");
214
218
  _this2.transformState.isTransforming = true;
215
- if (_this2.selectedObject) {
216
- // Store initial transform state
217
- _this2.transformState.initialTransform = {
218
- position: _this2.selectedObject.position.clone(),
219
- rotation: _this2.selectedObject.rotation.clone(),
220
- scale: _this2.selectedObject.scale.clone()
221
- };
219
+
220
+ // Store initial transforms for all selected objects
221
+ if (_this2.selectedObjects.length > 0 && _this2.multiSelectionGroup) {
222
+ _this2.selectedObjects.forEach(function (obj) {
223
+ obj.userData._multiSelectOriginalPosition = obj.position.clone();
224
+ obj.userData._multiSelectOriginalRotation = obj.rotation.clone();
225
+ obj.userData._multiSelectOriginalScale = obj.scale.clone();
226
+ });
222
227
  }
223
228
 
224
229
  // Disable orbit controls during transformation
@@ -228,7 +233,7 @@ var TransformControlsManager = /*#__PURE__*/function () {
228
233
 
229
234
  // Execute callback
230
235
  if (_this2.callbacks.onTransformStart) {
231
- _this2.callbacks.onTransformStart(_this2.selectedObject, _this2.currentMode);
236
+ _this2.callbacks.onTransformStart(_this2.selectedObjects[0] || null, _this2.currentMode);
232
237
  }
233
238
  console.log("\uD83D\uDD27 Transform started: ".concat(_this2.currentMode, " mode"));
234
239
  };
@@ -242,54 +247,31 @@ var TransformControlsManager = /*#__PURE__*/function () {
242
247
  _this2.orbitControls.enabled = true;
243
248
  }
244
249
 
245
- // Store final transform state and handle API-based transforms
246
- if (_this2.selectedObject && _this2.transformState.initialTransform) {
247
- _this2.transformState.currentTransform = {
248
- position: _this2.selectedObject.position.clone(),
249
- rotation: _this2.selectedObject.rotation.clone(),
250
- scale: _this2.selectedObject.scale.clone()
251
- };
252
-
253
- // If centralPlant API is available and we're in translate mode,
254
- // use the API to apply the translation properly
255
- // BUT skip API-based translation for pipe segments - they should stay where dragged
256
- if (_this2.centralPlant && _this2.currentMode === 'translate' && !isSegment(_this2.selectedObject)) {
257
- _this2.handleTranslateWithAPI();
258
- }
259
-
260
- // Clear bounding box cache for the transformed object to ensure selection uses updated position
261
- if (_this2.boundingBoxCache.has(_this2.selectedObject)) {
262
- _this2.boundingBoxCache.delete(_this2.selectedObject);
263
- }
250
+ // Apply multi-selection transforms (works for single or multiple objects)
251
+ if (_this2.selectedObjects.length > 0 && _this2.multiSelectionGroup) {
252
+ _this2.applyMultiSelectionTransform();
253
+ }
264
254
 
265
- // Auto-update paths if enabled
266
- // BUT skip auto-path update for pipe segments and gateways - they will be handled by their respective handlers
267
- if (_this2.sceneViewer && _this2.sceneViewer.shouldUpdatePaths && !isSegment(_this2.selectedObject) && !isGateway(_this2.selectedObject)) {
268
- console.log('๐Ÿ”„ Auto-updating paths after transform...');
255
+ // Update paths after translation is complete
256
+ // Only if we translated components (not segments/gateways - they handle paths internally)
257
+ if (_this2.currentMode === 'translate' && _this2.sceneViewer && typeof _this2.sceneViewer.updatePaths === 'function') {
258
+ // Check if any of the selected objects are components (not segments or gateways)
259
+ var hasComponents = _this2.selectedObjects.some(function (obj) {
260
+ return !isSegment(obj) && !isGateway(obj);
261
+ });
262
+ if (hasComponents) {
263
+ console.log('๐Ÿ”„ Updating paths after component translation...');
269
264
  try {
270
265
  _this2.sceneViewer.updatePaths();
271
- console.log('โœ… Paths auto-updated successfully');
266
+ console.log('โœ… Paths updated successfully after translation');
272
267
  } catch (error) {
273
- console.error('โŒ Error auto-updating paths:', error);
268
+ console.error('โŒ Error updating paths after translation:', error);
274
269
  }
270
+ } else {
271
+ console.log('โ„น๏ธ Skipping updatePaths - segments/gateways handle their own path updates');
275
272
  }
276
273
  }
277
274
 
278
- // Execute callback
279
- if (_this2.callbacks.onTransformEnd) {
280
- _this2.callbacks.onTransformEnd(_this2.selectedObject, _this2.transformState.initialTransform, _this2.transformState.currentTransform, _this2.currentMode);
281
- }
282
-
283
- // Handle pipe segment transformations (only in translate mode)
284
- if (_this2.currentMode === 'translate' && isSegment(_this2.selectedObject)) {
285
- _this2.handlePipeSegmentTransform(_this2.selectedObject);
286
- }
287
-
288
- // Handle gateway transformations (only in translate mode)
289
- if (_this2.currentMode === 'translate' && isGateway(_this2.selectedObject)) {
290
- _this2.handleGatewayTransform(_this2.selectedObject);
291
- }
292
-
293
275
  // Dispatch custom scene update event after transform completes
294
276
  if (typeof window !== 'undefined') {
295
277
  var _this2$selectedObject;
@@ -298,7 +280,8 @@ var TransformControlsManager = /*#__PURE__*/function () {
298
280
  detail: {
299
281
  timestamp: Date.now(),
300
282
  transformType: _this2.currentMode,
301
- objectName: ((_this2$selectedObject = _this2.selectedObject) === null || _this2$selectedObject === void 0 ? void 0 : _this2$selectedObject.name) || 'unknown'
283
+ objectName: ((_this2$selectedObject = _this2.selectedObjects[0]) === null || _this2$selectedObject === void 0 ? void 0 : _this2$selectedObject.name) || 'unknown',
284
+ objectCount: _this2.selectedObjects.length
302
285
  }
303
286
  });
304
287
  window.dispatchEvent(sceneCompleteEvent);
@@ -307,11 +290,18 @@ var TransformControlsManager = /*#__PURE__*/function () {
307
290
  };
308
291
  // Transform changing event
309
292
  this.eventHandlers.transforming = function () {
310
- // Clear the bounding box cache for the selected object during transformation
311
- if (_this2.selectedObject && _this2.boundingBoxCache.has(_this2.selectedObject)) {
312
- _this2.boundingBoxCache.delete(_this2.selectedObject);
293
+ // Apply real-time visual transformation to objects during drag
294
+ if (_this2.selectedObjects.length > 0 && _this2.multiSelectionGroup) {
295
+ _this2.applyRealtimeTransform();
313
296
  }
314
297
 
298
+ // Clear the bounding box cache for all selected objects during transformation
299
+ _this2.selectedObjects.forEach(function (obj) {
300
+ if (_this2.boundingBoxCache.has(obj)) {
301
+ _this2.boundingBoxCache.delete(obj);
302
+ }
303
+ });
304
+
315
305
  // Update bounding box during transformation
316
306
  _this2.updateBoundingBox();
317
307
 
@@ -322,7 +312,7 @@ var TransformControlsManager = /*#__PURE__*/function () {
322
312
  detail: {
323
313
  timestamp: Date.now(),
324
314
  transformType: _this2.currentMode,
325
- objectName: ((_this2$selectedObject2 = _this2.selectedObject) === null || _this2$selectedObject2 === void 0 ? void 0 : _this2$selectedObject2.name) || 'unknown',
315
+ objectName: ((_this2$selectedObject2 = _this2.selectedObjects[0]) === null || _this2$selectedObject2 === void 0 ? void 0 : _this2$selectedObject2.name) || 'unknown',
326
316
  isTransforming: true
327
317
  }
328
318
  });
@@ -392,6 +382,7 @@ var TransformControlsManager = /*#__PURE__*/function () {
392
382
 
393
383
  // Single click handler with double-click detection
394
384
  this.eventHandlers.click = function (event) {
385
+ var _targetObject$userDat;
395
386
  // Skip if currently transforming
396
387
  if (_this4.transformState.isTransforming) {
397
388
  return;
@@ -406,6 +397,36 @@ var TransformControlsManager = /*#__PURE__*/function () {
406
397
  // Find target object using appropriate selection method
407
398
  var targetObject = _this4._findTargetObject(raycaster, objectFilter);
408
399
 
400
+ // Check if shift key is pressed for multi-selection
401
+ var isShiftPressed = event.shiftKey;
402
+
403
+ // Skip selection if no target object found
404
+ if (!targetObject) {
405
+ // Deselect if clicking on empty space (and not holding shift)
406
+ if (!isShiftPressed) {
407
+ _this4.deselectObject();
408
+ }
409
+ _this4.clickTiming.lastClickTime = 0;
410
+ _this4.clickTiming.lastClickedObject = null;
411
+ return;
412
+ }
413
+
414
+ // Skip selection if not a component, gateway, or segment
415
+ var objectType = (_targetObject$userDat = targetObject.userData) === null || _targetObject$userDat === void 0 ? void 0 : _targetObject$userDat.objectType;
416
+ var isValidType = objectType === 'component' || objectType === 'gateway' || objectType === 'segment';
417
+ if (!isValidType) {
418
+ console.log('โŒ Object excluded from selection:', targetObject.name || targetObject.uuid, 'objectType:', objectType);
419
+ return false;
420
+ }
421
+
422
+ // Handle multi-selection with shift key
423
+ if (isShiftPressed) {
424
+ _this4.toggleObjectSelection(targetObject);
425
+ _this4.clickTiming.lastClickTime = currentTime;
426
+ _this4.clickTiming.lastClickedObject = targetObject;
427
+ return;
428
+ }
429
+
409
430
  // Check if this is a double-click: same object clicked within delay time
410
431
  var isSameObject = targetObject === _this4.clickTiming.lastClickedObject;
411
432
  var isWithinDelay = timeSinceLastClick < _this4.clickTiming.doubleClickDelay;
@@ -498,6 +519,7 @@ var TransformControlsManager = /*#__PURE__*/function () {
498
519
  }, {
499
520
  key: "_isValidSelectableObject",
500
521
  value: function _isValidSelectableObject(object) {
522
+ var _object$userData;
501
523
  // Safety check: ensure object is still valid and in the scene
502
524
  if (!object || !object.parent) {
503
525
  console.warn('โš ๏ธ Selected object is no longer valid or in scene');
@@ -509,6 +531,12 @@ var TransformControlsManager = /*#__PURE__*/function () {
509
531
  console.warn('โŒ Object missing required Three.js properties for transform controls');
510
532
  return false;
511
533
  }
534
+
535
+ // Check if object has explicit selectable flag set to false
536
+ if (((_object$userData = object.userData) === null || _object$userData === void 0 ? void 0 : _object$userData.selectable) === false) {
537
+ console.warn('โš ๏ธ Object is marked as non-selectable (stub segment)');
538
+ return false;
539
+ }
512
540
  return true;
513
541
  }
514
542
 
@@ -548,48 +576,41 @@ var TransformControlsManager = /*#__PURE__*/function () {
548
576
  }, {
549
577
  key: "isSelectableObject",
550
578
  value: function isSelectableObject(object) {
551
- var _object$userData, _object$userData2, _object$userData3, _object$userData4, _object$userData5, _object$userData6;
579
+ var _object$userData2, _object$userData3, _object$userData4, _object$userData5, _object$userData6, _object$userData7, _object$userData8;
552
580
  // Basic safety checks
553
581
  if (!object || !object.isObject3D) {
554
582
  return false;
555
583
  }
556
584
 
557
- // Debug logging for pipe segments
558
- if (isSegment(object)) {
559
- console.log('๐Ÿ” Found pipe segment in selection check:', object.name, object.userData);
560
- }
561
-
562
585
  // Skip transform controls and their children
563
586
  if (object.isTransformControls || object.isTransformControlsPlane || object.isTransformControlsGizmo) {
564
587
  return false;
565
588
  }
566
589
 
567
590
  // Skip helpers and special objects
568
- var isHelper = object.isHelper || ((_object$userData = object.userData) === null || _object$userData === void 0 ? void 0 : _object$userData.isHelper) || ((_object$userData2 = object.userData) === null || _object$userData2 === void 0 ? void 0 : _object$userData2.isBoundingBox);
569
- var isBaseGround = (_object$userData3 = object.userData) === null || _object$userData3 === void 0 ? void 0 : _object$userData3.isBaseGround;
570
- var isBrickWall = (_object$userData4 = object.userData) === null || _object$userData4 === void 0 ? void 0 : _object$userData4.isBrickWall;
591
+ var isHelper = object.isHelper || ((_object$userData2 = object.userData) === null || _object$userData2 === void 0 ? void 0 : _object$userData2.isHelper) || ((_object$userData3 = object.userData) === null || _object$userData3 === void 0 ? void 0 : _object$userData3.isBoundingBox);
592
+ var isBaseGround = (_object$userData4 = object.userData) === null || _object$userData4 === void 0 ? void 0 : _object$userData4.isBaseGround;
593
+ var isBrickWall = (_object$userData5 = object.userData) === null || _object$userData5 === void 0 ? void 0 : _object$userData5.isBrickWall;
571
594
  if (isHelper || isBaseGround || isBrickWall || !object.visible) {
572
595
  return false;
573
596
  }
574
597
 
575
- // Allow pipe segments and junctions to be selected, but exclude parent polyline objects
576
- if (object.name && object.name.toLowerCase().includes('polyline') && !isSegment(object) && !((_object$userData5 = object.userData) !== null && _object$userData5 !== void 0 && _object$userData5.isPipeJunction)) {
577
- console.log('โŒ Polyline parent object excluded from selection:', object.name);
598
+ // Skip segment connectors - they should not be selectable for transformation
599
+ if (((_object$userData6 = object.userData) === null || _object$userData6 === void 0 ? void 0 : _object$userData6.objectType) === 'segment-connector') {
578
600
  return false;
579
601
  }
580
602
 
581
- // Check for pipe segments and junctions
582
- var isPipeJunction = ((_object$userData6 = object.userData) === null || _object$userData6 === void 0 ? void 0 : _object$userData6.isPipeJunction) === true;
583
-
584
- // Check for GLB models
585
- var isGLBModel = object.name && object.name.includes(' Component');
603
+ // Check if object has explicit selectable flag set to false
604
+ if (((_object$userData7 = object.userData) === null || _object$userData7 === void 0 ? void 0 : _object$userData7.selectable) === false) {
605
+ return false;
606
+ }
586
607
 
587
- // Check for connector orbs
588
- var isConnector = this.isConnectorOrb(object);
589
- return isGLBModel || isConnector || isSegment(object) || isPipeJunction;
608
+ // Allow components, gateways, and segments to be selected
609
+ var objectType = (_object$userData8 = object.userData) === null || _object$userData8 === void 0 ? void 0 : _object$userData8.objectType;
610
+ return objectType === 'component' || objectType === 'gateway' || isSegment(object);
590
611
  }
591
612
  /**
592
- * Select an object for transformation
613
+ * Select an object for transformation (with full callbacks including tooltips)
593
614
  * @param {THREE.Object3D} object - The object to select
594
615
  */
595
616
  }, {
@@ -599,37 +620,14 @@ var TransformControlsManager = /*#__PURE__*/function () {
599
620
  console.warn('โš ๏ธ Invalid object provided for selection');
600
621
  return false;
601
622
  }
602
- this.selectedObject = object;
603
-
604
- // Force object matrix update to ensure correct transforms
605
- object.updateMatrixWorld(true);
606
-
607
- // Refresh bounding box cache for this object
608
- var updatedBoundingBox = new THREE.Box3().setFromObject(object);
609
- this.boundingBoxCache.set(object, updatedBoundingBox);
610
623
 
611
- // Attach transform controls
612
- this.transformControls.attach(object);
624
+ // Clear existing selection and select this object
625
+ this.selectedObjects = [object];
613
626
 
614
- // Update interaction time to reset delay
615
- if (this.transformControls.updateInteractionTime) {
616
- this.transformControls.updateInteractionTime();
617
- }
618
-
619
- // Create and show bounding box if enabled
620
- this.createBoundingBox(object);
621
-
622
- // Only enable if not forcing invisibility
623
- if (!this.forceInvisible) {
624
- this.transformControls.enabled = this.config.enabled;
625
- this._showTransformControls();
626
- } else {
627
- console.log('๐Ÿ”’ Transform controls kept invisible despite object selection');
628
- this.transformControls.enabled = false;
629
- this._hideTransformControls();
630
- }
627
+ // Update the multi-selection display (works for single object too)
628
+ this.updateMultiSelection();
631
629
 
632
- // Execute callback
630
+ // Execute callback for tooltips and other UI updates
633
631
  if (this.callbacks.onObjectSelect) {
634
632
  this.callbacks.onObjectSelect(object);
635
633
  }
@@ -649,56 +647,198 @@ var TransformControlsManager = /*#__PURE__*/function () {
649
647
  console.warn('โš ๏ธ Invalid object provided for selection');
650
648
  return false;
651
649
  }
652
- this.selectedObject = object;
653
650
 
654
- // Force object matrix update to ensure correct transforms
655
- object.updateMatrixWorld(true);
651
+ // Clear existing selection and select this object
652
+ this.selectedObjects = [object];
653
+
654
+ // Update the multi-selection display (works for single object too)
655
+ this.updateMultiSelection();
656
+
657
+ // NOTE: We deliberately do NOT call any callback here
658
+ // This allows transform controls and bounding box to show without triggering tooltips
659
+
660
+ console.log("\uD83C\uDFAF Object selected for transform only: ".concat(object.name || object.uuid));
661
+ return true;
662
+ }
663
+
664
+ /**
665
+ * Toggle object selection for multi-selection (shift+click)
666
+ * @param {THREE.Object3D} object - The object to toggle
667
+ */
668
+ }, {
669
+ key: "toggleObjectSelection",
670
+ value: function toggleObjectSelection(object) {
671
+ if (!object || !object.isObject3D) {
672
+ console.warn('โš ๏ธ Invalid object provided for multi-selection');
673
+ return;
674
+ }
656
675
 
657
- // Refresh bounding box cache for this object
658
- var updatedBoundingBox = new THREE.Box3().setFromObject(object);
659
- this.boundingBoxCache.set(object, updatedBoundingBox);
676
+ // Check if object is already in selection
677
+ var index = this.selectedObjects.findIndex(function (obj) {
678
+ return obj === object;
679
+ });
680
+ if (index !== -1) {
681
+ // Object is already selected, remove it
682
+ this.selectedObjects.splice(index, 1);
683
+ console.log("\u2796 Removed from selection: ".concat(object.name || object.uuid));
684
+ } else {
685
+ // Object is not selected, add it
686
+ this.selectedObjects.push(object);
687
+ console.log("\u2795 Added to selection: ".concat(object.name || object.uuid));
688
+ }
660
689
 
661
- // Attach transform controls
662
- this.transformControls.attach(object);
690
+ // Update the multi-selection display
691
+ this.updateMultiSelection();
692
+ }
693
+
694
+ /**
695
+ * Update the multi-selection group and transform controls
696
+ * Works for both single and multiple object selection
697
+ */
698
+ }, {
699
+ key: "updateMultiSelection",
700
+ value: function updateMultiSelection() {
701
+ if (this.selectedObjects.length === 0) {
702
+ // No objects selected, deselect everything
703
+ this.deselectObject();
704
+ return;
705
+ }
706
+
707
+ // Create/update group for selected objects (even if just one)
708
+ this.createMultiSelectionGroup();
709
+
710
+ // Attach transform controls to the group
711
+ this.transformControls.attach(this.multiSelectionGroup);
663
712
 
664
713
  // Update interaction time to reset delay
665
714
  if (this.transformControls.updateInteractionTime) {
666
715
  this.transformControls.updateInteractionTime();
667
716
  }
668
717
 
669
- // Create and show bounding box if enabled
670
- this.createBoundingBox(object);
718
+ // Create bounding boxes for all selected objects
719
+ this.createMultiBoundingBox();
671
720
 
672
- // Only enable if not forcing invisibility
721
+ // Enable transform controls if not forcing invisibility
673
722
  if (!this.forceInvisible) {
674
723
  this.transformControls.enabled = true;
675
724
  this._showTransformControls();
676
- } else {
677
- console.log('๐Ÿ”’ Transform controls kept invisible despite object selection');
678
- this.transformControls.enabled = false;
679
- this._hideTransformControls();
680
725
  }
726
+ console.log("\uD83C\uDFAF Selection active: ".concat(this.selectedObjects.length, " object(s)"));
727
+ }
681
728
 
682
- // NOTE: We deliberately do NOT call any callback here
683
- // This allows transform controls and bounding box to show without triggering tooltips
729
+ /**
730
+ * Create or update the multi-selection group
731
+ */
732
+ }, {
733
+ key: "createMultiSelectionGroup",
734
+ value: function createMultiSelectionGroup() {
735
+ // Remove existing group if it exists
736
+ if (this.multiSelectionGroup) {
737
+ this.scene.remove(this.multiSelectionGroup);
738
+ }
739
+
740
+ // Create new group
741
+ this.multiSelectionGroup = new THREE.Group();
742
+ this.multiSelectionGroup.name = 'MultiSelectionGroup';
743
+ this.multiSelectionGroup.userData = {
744
+ isMultiSelectionGroup: true
745
+ };
684
746
 
685
- console.log("\uD83C\uDFAF Object selected for transform only: ".concat(object.name || object.uuid));
686
- return true;
747
+ // Calculate the centroid of all selected objects
748
+ var centroid = new THREE.Vector3();
749
+ this.selectedObjects.forEach(function (obj) {
750
+ var worldPos = new THREE.Vector3();
751
+ obj.getWorldPosition(worldPos);
752
+ centroid.add(worldPos);
753
+ });
754
+ centroid.divideScalar(this.selectedObjects.length);
755
+
756
+ // Position the group at the centroid
757
+ this.multiSelectionGroup.position.copy(centroid);
758
+
759
+ // Add group to scene
760
+ this.scene.add(this.multiSelectionGroup);
761
+
762
+ // Store original position, rotation, and scale for each object
763
+ // Note: We don't store the parent reference to avoid circular JSON issues
764
+ this.selectedObjects.forEach(function (obj) {
765
+ // Store local transforms (for final API calls)
766
+ obj.userData._multiSelectOriginalPosition = obj.position.clone();
767
+ obj.userData._multiSelectOriginalRotation = obj.rotation.clone();
768
+ obj.userData._multiSelectOriginalScale = obj.scale.clone();
769
+
770
+ // Store world position (for real-time visual updates)
771
+ var worldPos = new THREE.Vector3();
772
+ obj.getWorldPosition(worldPos);
773
+ obj.userData._multiSelectOriginalWorldPosition = worldPos;
774
+ });
775
+ console.log("\uD83D\uDCE6 Multi-selection group created at centroid:", centroid.toArray());
776
+ }
777
+
778
+ /**
779
+ * Create a bounding box that encompasses all selected objects
780
+ */
781
+ }, {
782
+ key: "createMultiBoundingBox",
783
+ value:
784
+ /**
785
+ * Create bounding boxes for all selected objects
786
+ */
787
+ function createMultiBoundingBox() {
788
+ var _this5 = this;
789
+ // Skip if bounding box is disabled
790
+ if (!this.config.showBoundingBox) {
791
+ return;
792
+ }
793
+
794
+ // Remove any existing bounding boxes first
795
+ this.removeBoundingBox();
796
+ if (this.selectedObjects.length === 0) {
797
+ return;
798
+ }
799
+ try {
800
+ // Create individual bounding boxes for each selected object
801
+ this.selectedObjects.forEach(function (obj) {
802
+ var boundingBoxHelper = new THREE.BoxHelper(obj, _this5.config.boundingBoxColor);
803
+
804
+ // Mark it as a helper to avoid selection
805
+ boundingBoxHelper.isHelper = true;
806
+ boundingBoxHelper.userData = {
807
+ isBoundingBox: true
808
+ };
809
+
810
+ // Add to scene
811
+ _this5.scene.add(boundingBoxHelper);
812
+
813
+ // Store in array for later cleanup
814
+ _this5.boundingBoxHelpers.push(boundingBoxHelper);
815
+ });
816
+ console.log("\uD83D\uDCE6 Bounding boxes created for ".concat(this.selectedObjects.length, " object(s)"));
817
+ } catch (error) {
818
+ console.warn('โš ๏ธ Failed to create bounding boxes:', error);
819
+ }
687
820
  }
688
821
 
689
822
  /**
690
- * Deselect the currently selected object
823
+ * Remove all bounding box helpers
691
824
  */
692
825
  }, {
693
826
  key: "deselectObject",
694
- value: function deselectObject() {
695
- if (this.selectedObject) {
696
- console.log("\uD83D\uDD04 Object deselected: ".concat(this.selectedObject.name || this.selectedObject.uuid));
827
+ value:
828
+ /**
829
+ * Deselect all currently selected objects
830
+ */
831
+ function deselectObject() {
832
+ if (this.selectedObjects.length > 0) {
833
+ console.log("\uD83D\uDD04 Objects deselected: ".concat(this.selectedObjects.length));
697
834
  }
698
835
 
699
- // Remove bounding box
836
+ // Clear multi-selection
837
+ this.clearMultiSelection();
838
+
839
+ // Remove bounding boxes
700
840
  this.removeBoundingBox();
701
- this.selectedObject = null;
841
+
702
842
  // Detach and hide transform controls
703
843
  if (this.transformControls) {
704
844
  // First detach from object
@@ -717,94 +857,97 @@ var TransformControlsManager = /*#__PURE__*/function () {
717
857
  }
718
858
 
719
859
  /**
720
- * Create and display a bounding box for the selected object
721
- * @param {THREE.Object3D} object - The object to create a bounding box for
860
+ * Clear selection and restore original object states
722
861
  */
723
862
  }, {
724
- key: "createBoundingBox",
725
- value: function createBoundingBox(object) {
726
- // Skip if bounding box is disabled
727
- if (!this.config.showBoundingBox) {
728
- return;
729
- }
863
+ key: "clearMultiSelection",
864
+ value: function clearMultiSelection() {
865
+ // Clean up userData for selected objects
866
+ this.selectedObjects.forEach(function (obj) {
867
+ if (obj.userData._multiSelectOriginalPosition) {
868
+ delete obj.userData._multiSelectOriginalPosition;
869
+ delete obj.userData._multiSelectOriginalRotation;
870
+ delete obj.userData._multiSelectOriginalScale;
871
+ delete obj.userData._multiSelectOriginalWorldPosition;
872
+ }
873
+ });
730
874
 
731
- // Remove any existing bounding box first
732
- this.removeBoundingBox();
733
- if (!object) {
734
- return;
875
+ // Remove selection group from scene
876
+ if (this.multiSelectionGroup) {
877
+ this.scene.remove(this.multiSelectionGroup);
878
+ this.multiSelectionGroup = null;
735
879
  }
736
- try {
737
- // Create a BoxHelper to show the bounding box
738
- this.boundingBoxHelper = new THREE.BoxHelper(object, this.config.boundingBoxColor);
739
-
740
- // Mark it as a helper to avoid selection
741
- this.boundingBoxHelper.isHelper = true;
742
- this.boundingBoxHelper.userData = {
743
- isBoundingBox: true
744
- };
745
- console.log("this.boundingBoxHelper object:", object);
746
-
747
- // Add to scene
748
- this.scene.add(this.boundingBoxHelper);
749
- console.log("\uD83D\uDCE6 Bounding box created for: ".concat(object.name || object.uuid));
750
- } catch (error) {
751
- console.warn('โš ๏ธ Failed to create bounding box:', error);
880
+
881
+ // Clear selection array
882
+ var count = this.selectedObjects.length;
883
+ this.selectedObjects = [];
884
+ if (count > 0) {
885
+ console.log("\uD83E\uDDF9 Cleared selection (".concat(count, " object(s))"));
752
886
  }
753
887
  }
754
-
755
- /**
756
- * Remove the current bounding box helper
757
- */
758
888
  }, {
759
889
  key: "removeBoundingBox",
760
890
  value: function removeBoundingBox() {
761
- if (this.boundingBoxHelper) {
762
- try {
763
- // Remove from scene
764
- if (this.boundingBoxHelper.parent) {
765
- this.boundingBoxHelper.parent.remove(this.boundingBoxHelper);
766
- }
891
+ // Remove all bounding box helpers
892
+ if (this.boundingBoxHelpers.length > 0) {
893
+ this.boundingBoxHelpers.forEach(function (helper) {
894
+ try {
895
+ // Remove from scene
896
+ if (helper.parent) {
897
+ helper.parent.remove(helper);
898
+ }
767
899
 
768
- // Dispose geometry and material if they exist
769
- if (this.boundingBoxHelper.geometry) {
770
- this.boundingBoxHelper.geometry.dispose();
771
- }
772
- if (this.boundingBoxHelper.material) {
773
- this.boundingBoxHelper.material.dispose();
900
+ // Dispose geometry and material if they exist
901
+ if (helper.geometry) {
902
+ helper.geometry.dispose();
903
+ }
904
+ if (helper.material) {
905
+ helper.material.dispose();
906
+ }
907
+ } catch (error) {
908
+ console.warn('โš ๏ธ Error removing bounding box helper:', error);
774
909
  }
775
- console.log('๐Ÿ“ฆ Bounding box removed');
776
- } catch (error) {
777
- console.warn('โš ๏ธ Error removing bounding box:', error);
778
- } finally {
779
- this.boundingBoxHelper = null;
910
+ });
911
+ var count = this.boundingBoxHelpers.length;
912
+ this.boundingBoxHelpers = [];
913
+ if (count > 0) {
914
+ console.log("\uD83D\uDCE6 Removed ".concat(count, " bounding box(es)"));
780
915
  }
781
916
  }
782
917
  }
783
918
 
784
919
  /**
785
- * Update the bounding box to match the current object state
786
- * Call this after any transformation to keep the bounding box in sync
920
+ * Update the bounding boxes to match the current object states
921
+ * Call this after any transformation to keep the bounding boxes in sync
787
922
  */
788
923
  }, {
789
924
  key: "updateBoundingBox",
790
925
  value: function updateBoundingBox() {
791
- if (this.boundingBoxHelper && this.selectedObject) {
926
+ var _this6 = this;
927
+ // Update bounding boxes for all selected objects
928
+ if (this.selectedObjects.length > 0 && this.boundingBoxHelpers.length > 0) {
792
929
  try {
793
- // Force object matrix update to ensure correct bounding box
794
- this.selectedObject.updateMatrixWorld(true);
795
-
796
- // Update bounding box
797
- this.boundingBoxHelper.update();
798
-
799
- // Also update the cached bounding box if it exists
800
- if (this.boundingBoxCache.has(this.selectedObject)) {
801
- var updatedBoundingBox = new THREE.Box3().setFromObject(this.selectedObject);
802
- this.boundingBoxCache.set(this.selectedObject, updatedBoundingBox);
803
- }
930
+ // Update each bounding box helper
931
+ this.boundingBoxHelpers.forEach(function (helper, index) {
932
+ var obj = _this6.selectedObjects[index];
933
+ if (obj) {
934
+ // Force object matrix update to ensure correct bounding box
935
+ obj.updateMatrixWorld(true);
936
+
937
+ // Update bounding box
938
+ helper.update();
939
+
940
+ // Also update the cached bounding box if it exists
941
+ if (_this6.boundingBoxCache.has(obj)) {
942
+ var updatedBoundingBox = new THREE.Box3().setFromObject(obj);
943
+ _this6.boundingBoxCache.set(obj, updatedBoundingBox);
944
+ }
945
+ }
946
+ });
804
947
  } catch (error) {
805
- console.warn('โš ๏ธ Error updating bounding box:', error);
806
- // If update fails, recreate the bounding box
807
- this.createBoundingBox(this.selectedObject);
948
+ console.warn('โš ๏ธ Error updating bounding boxes:', error);
949
+ // If update fails, recreate the bounding boxes
950
+ this.createMultiBoundingBox();
808
951
  }
809
952
  }
810
953
  }
@@ -906,33 +1049,34 @@ var TransformControlsManager = /*#__PURE__*/function () {
906
1049
  key: "setEnabled",
907
1050
  value: function setEnabled(enabled) {
908
1051
  this.config.enabled = enabled;
909
- if (this.selectedObject) {
1052
+ if (this.selectedObjects.length > 0) {
910
1053
  this.transformControls.enabled = enabled;
911
1054
  }
912
1055
  console.log("\uD83D\uDD04 Transform controls ".concat(enabled ? 'enabled' : 'disabled'));
913
1056
  }
914
1057
 
915
1058
  /**
916
- * Get world transformation data for the selected object
1059
+ * Get world transformation data for the first selected object
917
1060
  * @returns {Object} World transformation data
918
1061
  */
919
1062
  }, {
920
1063
  key: "getWorldTransformData",
921
1064
  value: function getWorldTransformData() {
922
- if (!this.selectedObject) {
1065
+ if (this.selectedObjects.length === 0) {
923
1066
  return null;
924
1067
  }
1068
+ var selectedObject = this.selectedObjects[0];
925
1069
 
926
1070
  // Ensure the object's world matrix is up to date
927
- this.selectedObject.updateMatrixWorld(true);
1071
+ selectedObject.updateMatrixWorld(true);
928
1072
 
929
1073
  // Get world position
930
1074
  var worldPosition = new THREE.Vector3();
931
- this.selectedObject.getWorldPosition(worldPosition);
1075
+ selectedObject.getWorldPosition(worldPosition);
932
1076
 
933
1077
  // Get world quaternion
934
1078
  var worldQuaternion = new THREE.Quaternion();
935
- this.selectedObject.getWorldQuaternion(worldQuaternion);
1079
+ selectedObject.getWorldQuaternion(worldQuaternion);
936
1080
 
937
1081
  // Convert quaternion to Euler rotation
938
1082
  var worldRotation = new THREE.Euler();
@@ -940,7 +1084,7 @@ var TransformControlsManager = /*#__PURE__*/function () {
940
1084
 
941
1085
  // Get world scale
942
1086
  var worldScale = new THREE.Vector3();
943
- this.selectedObject.getWorldScale(worldScale);
1087
+ selectedObject.getWorldScale(worldScale);
944
1088
  return {
945
1089
  position: worldPosition,
946
1090
  rotation: worldRotation,
@@ -959,11 +1103,11 @@ var TransformControlsManager = /*#__PURE__*/function () {
959
1103
  key: "updateObjectTransform",
960
1104
  value: function updateObjectTransform(type, axis, value) {
961
1105
  var useWorldCoords = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
962
- if (!this.selectedObject) {
1106
+ if (this.selectedObjects.length === 0) {
963
1107
  console.warn('โš ๏ธ No object selected for transformation');
964
1108
  return;
965
1109
  }
966
- var object = this.selectedObject;
1110
+ var object = this.selectedObjects[0];
967
1111
  var numericValue = parseFloat(value);
968
1112
  if (isNaN(numericValue)) {
969
1113
  console.warn('โš ๏ธ Invalid numeric value for transform:', value);
@@ -989,7 +1133,9 @@ var TransformControlsManager = /*#__PURE__*/function () {
989
1133
  console.warn("\u26A0\uFE0F Unknown transform type: ".concat(type));
990
1134
  return;
991
1135
  }
992
- } // Update transform controls to reflect the change
1136
+ }
1137
+
1138
+ // Update transform controls to reflect the change
993
1139
  if (this.transformControls) {
994
1140
  this.transformControls.updateMatrixWorld();
995
1141
  }
@@ -1117,13 +1263,13 @@ var TransformControlsManager = /*#__PURE__*/function () {
1117
1263
  }, {
1118
1264
  key: "getSelectableObjectsWithBounds",
1119
1265
  value: function getSelectableObjectsWithBounds() {
1120
- var _this5 = this;
1266
+ var _this7 = this;
1121
1267
  var objectFilter = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
1122
1268
  var objectsWithBounds = [];
1123
1269
 
1124
1270
  // Traverse scene to find selectable objects
1125
1271
  this.scene.traverse(function (object) {
1126
- var _object$userData7, _object$userData8;
1272
+ var _object$userData9, _object$userData0;
1127
1273
  // Skip invalid objects and helpers early
1128
1274
  if (!object || !object.isObject3D) {
1129
1275
  return;
@@ -1135,25 +1281,25 @@ var TransformControlsManager = /*#__PURE__*/function () {
1135
1281
  }
1136
1282
 
1137
1283
  // Skip other helpers and special objects
1138
- if (object.isHelper || (_object$userData7 = object.userData) !== null && _object$userData7 !== void 0 && _object$userData7.isHelper || (_object$userData8 = object.userData) !== null && _object$userData8 !== void 0 && _object$userData8.isBoundingBox) {
1284
+ if (object.isHelper || (_object$userData9 = object.userData) !== null && _object$userData9 !== void 0 && _object$userData9.isHelper || (_object$userData0 = object.userData) !== null && _object$userData0 !== void 0 && _object$userData0.isBoundingBox) {
1139
1285
  return;
1140
1286
  }
1141
1287
  try {
1142
1288
  // Apply filter (use provided filter or default isSelectableObject method)
1143
- var isSelectable = objectFilter ? objectFilter(object) : _this5.isSelectableObject(object);
1289
+ var isSelectable = objectFilter ? objectFilter(object) : _this7.isSelectableObject(object);
1144
1290
  if (isSelectable) {
1145
1291
  // Force object matrix update to ensure correct bounding box
1146
1292
  object.updateMatrixWorld(true);
1147
1293
 
1148
1294
  // Get or compute bounding box
1149
- var boundingBox = _this5.boundingBoxCache.get(object);
1295
+ var boundingBox = _this7.boundingBoxCache.get(object);
1150
1296
 
1151
1297
  // Always recompute bounding box for recently transformed objects
1152
1298
  // or if no cached bounding box exists
1153
- if (!boundingBox || object === _this5.selectedObject) {
1299
+ if (!boundingBox || _this7.selectedObjects.includes(object)) {
1154
1300
  // Compute and cache bounding box
1155
1301
  boundingBox = new THREE.Box3().setFromObject(object);
1156
- _this5.boundingBoxCache.set(object, boundingBox);
1302
+ _this7.boundingBoxCache.set(object, boundingBox);
1157
1303
  }
1158
1304
  objectsWithBounds.push({
1159
1305
  object: object,
@@ -1215,60 +1361,216 @@ var TransformControlsManager = /*#__PURE__*/function () {
1215
1361
  }
1216
1362
 
1217
1363
  /**
1218
- * Handle translate using centralPlant API instead of direct Three.js manipulation
1219
- * This method calculates the translation delta and resets the object position,
1220
- * then uses the centralPlant.translate() API to apply the change properly
1364
+ * Apply real-time visual transformation during drag (no API calls)
1365
+ * This updates the object positions/rotations visually while the user is dragging
1221
1366
  */
1222
1367
  }, {
1223
- key: "handleTranslateWithAPI",
1224
- value: function handleTranslateWithAPI() {
1225
- var _this$selectedObject$;
1226
- if (!this.selectedObject || !this.transformState.initialTransform || !this.transformState.currentTransform) {
1227
- console.warn('โš ๏ธ Cannot handle translate with API: missing required state');
1368
+ key: "applyRealtimeTransform",
1369
+ value: function applyRealtimeTransform() {
1370
+ var _this8 = this;
1371
+ if (!this.multiSelectionGroup || this.selectedObjects.length === 0) {
1228
1372
  return;
1229
1373
  }
1230
1374
 
1231
- // Calculate the translation deltas
1232
- var deltaX = this.transformState.currentTransform.position.x - this.transformState.initialTransform.position.x;
1233
- var deltaY = this.transformState.currentTransform.position.y - this.transformState.initialTransform.position.y;
1234
- var deltaZ = this.transformState.currentTransform.position.z - this.transformState.initialTransform.position.z;
1375
+ // Calculate the transformation delta from the group
1376
+ var groupPosition = this.multiSelectionGroup.position.clone();
1377
+ var groupRotation = this.multiSelectionGroup.rotation.clone();
1235
1378
 
1236
- // Reset the object to its initial position (undo the direct Three.js manipulation)
1237
- this.selectedObject.position.copy(this.transformState.initialTransform.position);
1238
- this.selectedObject.updateMatrix();
1239
- this.selectedObject.updateMatrixWorld(true);
1240
- console.log("\uD83D\uDD27 Using centralPlant API for translation:", {
1241
- deltaX: deltaX,
1242
- deltaY: deltaY,
1243
- deltaZ: deltaZ
1379
+ // Get the original centroid position (from when group was created)
1380
+ var originalCentroid = new THREE.Vector3();
1381
+ this.selectedObjects.forEach(function (obj) {
1382
+ var originalWorldPos = obj.userData._multiSelectOriginalWorldPosition;
1383
+ if (originalWorldPos) {
1384
+ originalCentroid.add(originalWorldPos);
1385
+ }
1244
1386
  });
1387
+ originalCentroid.divideScalar(this.selectedObjects.length);
1245
1388
 
1246
- // Get the component ID (try both uuid and originalUuid)
1247
- var componentId = this.selectedObject.uuid || ((_this$selectedObject$ = this.selectedObject.userData) === null || _this$selectedObject$ === void 0 ? void 0 : _this$selectedObject$.originalUuid);
1248
- if (!componentId) {
1249
- console.error('โŒ Cannot find component ID for translation API call');
1389
+ // Calculate deltas
1390
+ var positionDelta = groupPosition.clone().sub(originalCentroid);
1391
+ var rotationDelta = groupRotation.clone();
1392
+
1393
+ // Apply transformation to each selected object visually
1394
+ this.selectedObjects.forEach(function (obj) {
1395
+ var originalWorldPos = obj.userData._multiSelectOriginalWorldPosition;
1396
+ var originalRot = obj.userData._multiSelectOriginalRotation;
1397
+ if (!originalWorldPos || !originalRot) {
1398
+ return;
1399
+ }
1400
+ if (_this8.currentMode === 'translate') {
1401
+ // Simple translation: original world position + delta
1402
+ var newWorldPos = originalWorldPos.clone().add(positionDelta);
1403
+
1404
+ // Convert to local position if object has a parent
1405
+ if (obj.parent && obj.parent !== _this8.scene) {
1406
+ // Update parent matrix if parent is also being transformed
1407
+ obj.parent.updateMatrixWorld(true);
1408
+ var parentMatrixInverse = new THREE.Matrix4();
1409
+ parentMatrixInverse.copy(obj.parent.matrixWorld).invert();
1410
+ newWorldPos.applyMatrix4(parentMatrixInverse);
1411
+ }
1412
+ obj.position.copy(newWorldPos);
1413
+ obj.updateMatrix();
1414
+ } else if (_this8.currentMode === 'rotate') {
1415
+ // Calculate offset from centroid
1416
+ var offsetFromCentroid = originalWorldPos.clone().sub(originalCentroid);
1417
+
1418
+ // Rotate the offset vector
1419
+ var rotatedOffset = offsetFromCentroid.clone();
1420
+ var rotationQuat = new THREE.Quaternion().setFromEuler(rotationDelta);
1421
+ rotatedOffset.applyQuaternion(rotationQuat);
1422
+
1423
+ // Calculate new world position
1424
+ var _newWorldPos = originalCentroid.clone().add(rotatedOffset).add(positionDelta);
1425
+
1426
+ // Convert to local position
1427
+ if (obj.parent && obj.parent !== _this8.scene) {
1428
+ // Update parent matrix if parent is also being transformed
1429
+ obj.parent.updateMatrixWorld(true);
1430
+ var _parentMatrixInverse = new THREE.Matrix4();
1431
+ _parentMatrixInverse.copy(obj.parent.matrixWorld).invert();
1432
+ _newWorldPos.applyMatrix4(_parentMatrixInverse);
1433
+ }
1434
+ obj.position.copy(_newWorldPos);
1435
+
1436
+ // Apply rotation to the object itself
1437
+ obj.rotation.x = originalRot.x + rotationDelta.x;
1438
+ obj.rotation.y = originalRot.y + rotationDelta.y;
1439
+ obj.rotation.z = originalRot.z + rotationDelta.z;
1440
+ obj.updateMatrix();
1441
+ }
1442
+
1443
+ // Mark world matrix as needing update (will propagate to children)
1444
+ obj.matrixWorldNeedsUpdate = true;
1445
+ });
1446
+ }
1447
+
1448
+ /**
1449
+ * Apply transformation from multi-selection group to all selected objects
1450
+ * Simple approach: calculate delta once, apply to each object via API
1451
+ */
1452
+ }, {
1453
+ key: "applyMultiSelectionTransform",
1454
+ value: function applyMultiSelectionTransform() {
1455
+ var _this9 = this;
1456
+ if (!this.multiSelectionGroup || this.selectedObjects.length === 0) {
1250
1457
  return;
1251
1458
  }
1459
+ console.log("\uD83D\uDD27 Applying multi-selection transform to ".concat(this.selectedObjects.length, " objects"));
1252
1460
 
1253
- // Apply translations using centralPlant API if there's a meaningful delta
1254
- // Round to avoid floating point precision issues
1461
+ // Calculate the transformation delta from the group
1462
+ var groupPosition = this.multiSelectionGroup.position.clone();
1463
+
1464
+ // Get the original centroid position (from when group was created)
1465
+ var originalCentroid = new THREE.Vector3();
1466
+ this.selectedObjects.forEach(function (obj) {
1467
+ var originalWorldPos = obj.userData._multiSelectOriginalWorldPosition;
1468
+ if (originalWorldPos) {
1469
+ originalCentroid.add(originalWorldPos);
1470
+ }
1471
+ });
1472
+ originalCentroid.divideScalar(this.selectedObjects.length);
1473
+
1474
+ // Calculate position delta - this is the same for ALL objects
1475
+ var positionDelta = groupPosition.clone().sub(originalCentroid);
1476
+
1477
+ // Round deltas to avoid floating point precision issues
1478
+ var deltaX = Math.round(positionDelta.x * 2) / 2;
1479
+ var deltaY = Math.round(positionDelta.y * 2) / 2;
1480
+ var deltaZ = Math.round(positionDelta.z * 2) / 2;
1481
+ console.log('๐Ÿ”ง Transform delta:', {
1482
+ deltaX: deltaX,
1483
+ deltaY: deltaY,
1484
+ deltaZ: deltaZ
1485
+ });
1486
+
1487
+ // Only process if there's a meaningful translation
1255
1488
  var threshold = 0.001;
1256
- if (Math.abs(deltaX) > threshold) {
1257
- // Round to nearest 0.5 to match grid snapping
1258
- var roundedDeltaX = Math.round(deltaX * 2) / 2;
1259
- this.centralPlant.translate(componentId, 'x', roundedDeltaX);
1260
- }
1261
- if (Math.abs(deltaY) > threshold) {
1262
- // Round to nearest 0.5 to match grid snapping
1263
- var roundedDeltaY = Math.round(deltaY * 2) / 2;
1264
- this.centralPlant.translate(componentId, 'y', roundedDeltaY);
1265
- }
1266
- if (Math.abs(deltaZ) > threshold) {
1267
- // Round to nearest 0.5 to match grid snapping
1268
- var roundedDeltaZ = Math.round(deltaZ * 2) / 2;
1269
- this.centralPlant.translate(componentId, 'z', roundedDeltaZ);
1270
- }
1271
- console.log("\u2705 Applied translation through centralPlant API");
1489
+ if (this.currentMode === 'translate' && positionDelta.length() > threshold) {
1490
+ // FIRST: Reset all objects to their original positions
1491
+ // This undoes the visual transform applied during drag
1492
+ this.selectedObjects.forEach(function (obj) {
1493
+ var originalPos = obj.userData._multiSelectOriginalPosition;
1494
+ if (originalPos) {
1495
+ obj.position.copy(originalPos);
1496
+ obj.updateMatrixWorld(true);
1497
+ }
1498
+ });
1499
+ console.log('๐Ÿ”„ Reset objects to original positions before API calls');
1500
+
1501
+ // THEN: Apply the delta to each object using the appropriate API
1502
+ this.selectedObjects.forEach(function (obj) {
1503
+ var _obj$userData;
1504
+ if (!_this9.centralPlant) {
1505
+ console.warn('โš ๏ธ CentralPlant API not available');
1506
+ return;
1507
+ }
1508
+ var componentId = obj.uuid || ((_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.originalUuid);
1509
+ if (isSegment(obj)) {
1510
+ // Use translateSegment API
1511
+ if (Math.abs(deltaX) > threshold) {
1512
+ _this9.centralPlant.translateSegment(componentId, 'x', deltaX);
1513
+ }
1514
+ if (Math.abs(deltaY) > threshold) {
1515
+ _this9.centralPlant.translateSegment(componentId, 'y', deltaY);
1516
+ }
1517
+ if (Math.abs(deltaZ) > threshold) {
1518
+ _this9.centralPlant.translateSegment(componentId, 'z', deltaZ);
1519
+ }
1520
+ console.log("\uD83D\uDCE6 Segment ".concat(obj.name, " translated:"), {
1521
+ deltaX: deltaX,
1522
+ deltaY: deltaY,
1523
+ deltaZ: deltaZ
1524
+ });
1525
+ } else if (isGateway(obj)) {
1526
+ // Use translateGateway API
1527
+ if (Math.abs(deltaX) > threshold) {
1528
+ _this9.centralPlant.translateGateway(componentId, 'x', deltaX);
1529
+ }
1530
+ if (Math.abs(deltaY) > threshold) {
1531
+ _this9.centralPlant.translateGateway(componentId, 'y', deltaY);
1532
+ }
1533
+ if (Math.abs(deltaZ) > threshold) {
1534
+ _this9.centralPlant.translateGateway(componentId, 'z', deltaZ);
1535
+ }
1536
+ console.log("\uD83D\uDEAA Gateway ".concat(obj.name, " translated:"), {
1537
+ deltaX: deltaX,
1538
+ deltaY: deltaY,
1539
+ deltaZ: deltaZ
1540
+ });
1541
+ } else {
1542
+ // Use standard translate API for components
1543
+ if (Math.abs(deltaX) > threshold) {
1544
+ _this9.centralPlant.translate(componentId, 'x', deltaX);
1545
+ }
1546
+ if (Math.abs(deltaY) > threshold) {
1547
+ _this9.centralPlant.translate(componentId, 'y', deltaY);
1548
+ }
1549
+ if (Math.abs(deltaZ) > threshold) {
1550
+ _this9.centralPlant.translate(componentId, 'z', deltaZ);
1551
+ }
1552
+ console.log("\uD83D\uDD27 Component ".concat(obj.name, " translated:"), {
1553
+ deltaX: deltaX,
1554
+ deltaY: deltaY,
1555
+ deltaZ: deltaZ
1556
+ });
1557
+ }
1558
+ });
1559
+ console.log("\u2705 All ".concat(this.selectedObjects.length, " objects translated with delta:"), {
1560
+ deltaX: deltaX,
1561
+ deltaY: deltaY,
1562
+ deltaZ: deltaZ
1563
+ });
1564
+ }
1565
+
1566
+ // Reset the multi-selection group transform
1567
+ this.multiSelectionGroup.position.set(0, 0, 0);
1568
+ this.multiSelectionGroup.rotation.set(0, 0, 0);
1569
+ this.multiSelectionGroup.scale.set(1, 1, 1);
1570
+
1571
+ // Update the multi-selection display with new positions
1572
+ this.updateMultiSelection();
1573
+ console.log("\u2705 Multi-selection transform applied");
1272
1574
  }
1273
1575
 
1274
1576
  /**
@@ -1462,10 +1764,8 @@ var TransformControlsManager = /*#__PURE__*/function () {
1462
1764
  // Remove bounding box helper
1463
1765
  this.removeBoundingBox();
1464
1766
 
1465
- // Deselect any object first
1466
- if (this.selectedObject) {
1467
- this.deselectObject();
1468
- }
1767
+ // Clear multi-selection
1768
+ this.clearMultiSelection();
1469
1769
 
1470
1770
  // Dispose transform controls
1471
1771
  if (this.transformControls) {
@@ -1494,7 +1794,7 @@ var TransformControlsManager = /*#__PURE__*/function () {
1494
1794
  }
1495
1795
 
1496
1796
  // Clear references - but keep scene, camera, and renderer references for reuse
1497
- this.selectedObject = null;
1797
+ this.selectedObjects = [];
1498
1798
  // Don't null these out: this.scene, this.camera, this.renderer
1499
1799
  this.orbitControls = null;
1500
1800
  console.log('๐Ÿงน Transform controls disposed');