@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
@@ -40,14 +40,18 @@ var TransformControlsManager = /*#__PURE__*/function () {
40
40
 
41
41
  // Transform control instance
42
42
  this.transformControls = null;
43
- // Bounding box helper for visual feedback
44
- this.boundingBoxHelper = null;
43
+
44
+ // Array of bounding box helpers (one per selected object)
45
+ this.boundingBoxHelpers = [];
45
46
 
46
47
  // Cache for object bounding boxes to improve performance
47
48
  this.boundingBoxCache = new WeakMap();
48
49
 
49
- // Currently selected object
50
- this.selectedObject = null;
50
+ // Array of all selected objects (supports single or multi-selection)
51
+ this.selectedObjects = [];
52
+
53
+ // Group to hold selected objects for transform controls
54
+ this.multiSelectionGroup = null;
51
55
 
52
56
  // Transform mode: 'translate', 'rotate', 'scale'
53
57
  this.currentMode = 'translate';
@@ -145,9 +149,9 @@ var TransformControlsManager = /*#__PURE__*/function () {
145
149
  this._objectTransformedListener = function (eventData) {
146
150
  console.log('๐Ÿ”ฒ TransformControlsManager detected object transformation');
147
151
 
148
- // Only update bounding box if the transformed object is currently selected
149
- if (_this.selectedObject && eventData.object === _this.selectedObject) {
150
- console.log('๐Ÿ”ฒ Updating bounding box for selected object after transform');
152
+ // Update bounding box if the transformed object is in current selection
153
+ if (_this.selectedObjects.includes(eventData.object)) {
154
+ console.log('๐Ÿ”ฒ Updating bounding boxes for selected objects after transform');
151
155
  _this.updateBoundingBox();
152
156
  }
153
157
  };
@@ -236,13 +240,14 @@ var TransformControlsManager = /*#__PURE__*/function () {
236
240
  this.eventHandlers.transformStart = function () {
237
241
  console.log("transformControls transformStart");
238
242
  _this2.transformState.isTransforming = true;
239
- if (_this2.selectedObject) {
240
- // Store initial transform state
241
- _this2.transformState.initialTransform = {
242
- position: _this2.selectedObject.position.clone(),
243
- rotation: _this2.selectedObject.rotation.clone(),
244
- scale: _this2.selectedObject.scale.clone()
245
- };
243
+
244
+ // Store initial transforms for all selected objects
245
+ if (_this2.selectedObjects.length > 0 && _this2.multiSelectionGroup) {
246
+ _this2.selectedObjects.forEach(function (obj) {
247
+ obj.userData._multiSelectOriginalPosition = obj.position.clone();
248
+ obj.userData._multiSelectOriginalRotation = obj.rotation.clone();
249
+ obj.userData._multiSelectOriginalScale = obj.scale.clone();
250
+ });
246
251
  }
247
252
 
248
253
  // Disable orbit controls during transformation
@@ -252,7 +257,7 @@ var TransformControlsManager = /*#__PURE__*/function () {
252
257
 
253
258
  // Execute callback
254
259
  if (_this2.callbacks.onTransformStart) {
255
- _this2.callbacks.onTransformStart(_this2.selectedObject, _this2.currentMode);
260
+ _this2.callbacks.onTransformStart(_this2.selectedObjects[0] || null, _this2.currentMode);
256
261
  }
257
262
  console.log("\uD83D\uDD27 Transform started: ".concat(_this2.currentMode, " mode"));
258
263
  };
@@ -266,54 +271,31 @@ var TransformControlsManager = /*#__PURE__*/function () {
266
271
  _this2.orbitControls.enabled = true;
267
272
  }
268
273
 
269
- // Store final transform state and handle API-based transforms
270
- if (_this2.selectedObject && _this2.transformState.initialTransform) {
271
- _this2.transformState.currentTransform = {
272
- position: _this2.selectedObject.position.clone(),
273
- rotation: _this2.selectedObject.rotation.clone(),
274
- scale: _this2.selectedObject.scale.clone()
275
- };
276
-
277
- // If centralPlant API is available and we're in translate mode,
278
- // use the API to apply the translation properly
279
- // BUT skip API-based translation for pipe segments - they should stay where dragged
280
- if (_this2.centralPlant && _this2.currentMode === 'translate' && !objectTypes.isSegment(_this2.selectedObject)) {
281
- _this2.handleTranslateWithAPI();
282
- }
283
-
284
- // Clear bounding box cache for the transformed object to ensure selection uses updated position
285
- if (_this2.boundingBoxCache.has(_this2.selectedObject)) {
286
- _this2.boundingBoxCache.delete(_this2.selectedObject);
287
- }
274
+ // Apply multi-selection transforms (works for single or multiple objects)
275
+ if (_this2.selectedObjects.length > 0 && _this2.multiSelectionGroup) {
276
+ _this2.applyMultiSelectionTransform();
277
+ }
288
278
 
289
- // Auto-update paths if enabled
290
- // BUT skip auto-path update for pipe segments and gateways - they will be handled by their respective handlers
291
- if (_this2.sceneViewer && _this2.sceneViewer.shouldUpdatePaths && !objectTypes.isSegment(_this2.selectedObject) && !objectTypes.isGateway(_this2.selectedObject)) {
292
- console.log('๐Ÿ”„ Auto-updating paths after transform...');
279
+ // Update paths after translation is complete
280
+ // Only if we translated components (not segments/gateways - they handle paths internally)
281
+ if (_this2.currentMode === 'translate' && _this2.sceneViewer && typeof _this2.sceneViewer.updatePaths === 'function') {
282
+ // Check if any of the selected objects are components (not segments or gateways)
283
+ var hasComponents = _this2.selectedObjects.some(function (obj) {
284
+ return !objectTypes.isSegment(obj) && !objectTypes.isGateway(obj);
285
+ });
286
+ if (hasComponents) {
287
+ console.log('๐Ÿ”„ Updating paths after component translation...');
293
288
  try {
294
289
  _this2.sceneViewer.updatePaths();
295
- console.log('โœ… Paths auto-updated successfully');
290
+ console.log('โœ… Paths updated successfully after translation');
296
291
  } catch (error) {
297
- console.error('โŒ Error auto-updating paths:', error);
292
+ console.error('โŒ Error updating paths after translation:', error);
298
293
  }
294
+ } else {
295
+ console.log('โ„น๏ธ Skipping updatePaths - segments/gateways handle their own path updates');
299
296
  }
300
297
  }
301
298
 
302
- // Execute callback
303
- if (_this2.callbacks.onTransformEnd) {
304
- _this2.callbacks.onTransformEnd(_this2.selectedObject, _this2.transformState.initialTransform, _this2.transformState.currentTransform, _this2.currentMode);
305
- }
306
-
307
- // Handle pipe segment transformations (only in translate mode)
308
- if (_this2.currentMode === 'translate' && objectTypes.isSegment(_this2.selectedObject)) {
309
- _this2.handlePipeSegmentTransform(_this2.selectedObject);
310
- }
311
-
312
- // Handle gateway transformations (only in translate mode)
313
- if (_this2.currentMode === 'translate' && objectTypes.isGateway(_this2.selectedObject)) {
314
- _this2.handleGatewayTransform(_this2.selectedObject);
315
- }
316
-
317
299
  // Dispatch custom scene update event after transform completes
318
300
  if (typeof window !== 'undefined') {
319
301
  var _this2$selectedObject;
@@ -322,7 +304,8 @@ var TransformControlsManager = /*#__PURE__*/function () {
322
304
  detail: {
323
305
  timestamp: Date.now(),
324
306
  transformType: _this2.currentMode,
325
- objectName: ((_this2$selectedObject = _this2.selectedObject) === null || _this2$selectedObject === void 0 ? void 0 : _this2$selectedObject.name) || 'unknown'
307
+ objectName: ((_this2$selectedObject = _this2.selectedObjects[0]) === null || _this2$selectedObject === void 0 ? void 0 : _this2$selectedObject.name) || 'unknown',
308
+ objectCount: _this2.selectedObjects.length
326
309
  }
327
310
  });
328
311
  window.dispatchEvent(sceneCompleteEvent);
@@ -331,11 +314,18 @@ var TransformControlsManager = /*#__PURE__*/function () {
331
314
  };
332
315
  // Transform changing event
333
316
  this.eventHandlers.transforming = function () {
334
- // Clear the bounding box cache for the selected object during transformation
335
- if (_this2.selectedObject && _this2.boundingBoxCache.has(_this2.selectedObject)) {
336
- _this2.boundingBoxCache.delete(_this2.selectedObject);
317
+ // Apply real-time visual transformation to objects during drag
318
+ if (_this2.selectedObjects.length > 0 && _this2.multiSelectionGroup) {
319
+ _this2.applyRealtimeTransform();
337
320
  }
338
321
 
322
+ // Clear the bounding box cache for all selected objects during transformation
323
+ _this2.selectedObjects.forEach(function (obj) {
324
+ if (_this2.boundingBoxCache.has(obj)) {
325
+ _this2.boundingBoxCache.delete(obj);
326
+ }
327
+ });
328
+
339
329
  // Update bounding box during transformation
340
330
  _this2.updateBoundingBox();
341
331
 
@@ -346,7 +336,7 @@ var TransformControlsManager = /*#__PURE__*/function () {
346
336
  detail: {
347
337
  timestamp: Date.now(),
348
338
  transformType: _this2.currentMode,
349
- objectName: ((_this2$selectedObject2 = _this2.selectedObject) === null || _this2$selectedObject2 === void 0 ? void 0 : _this2$selectedObject2.name) || 'unknown',
339
+ objectName: ((_this2$selectedObject2 = _this2.selectedObjects[0]) === null || _this2$selectedObject2 === void 0 ? void 0 : _this2$selectedObject2.name) || 'unknown',
350
340
  isTransforming: true
351
341
  }
352
342
  });
@@ -416,6 +406,7 @@ var TransformControlsManager = /*#__PURE__*/function () {
416
406
 
417
407
  // Single click handler with double-click detection
418
408
  this.eventHandlers.click = function (event) {
409
+ var _targetObject$userDat;
419
410
  // Skip if currently transforming
420
411
  if (_this4.transformState.isTransforming) {
421
412
  return;
@@ -430,6 +421,36 @@ var TransformControlsManager = /*#__PURE__*/function () {
430
421
  // Find target object using appropriate selection method
431
422
  var targetObject = _this4._findTargetObject(raycaster, objectFilter);
432
423
 
424
+ // Check if shift key is pressed for multi-selection
425
+ var isShiftPressed = event.shiftKey;
426
+
427
+ // Skip selection if no target object found
428
+ if (!targetObject) {
429
+ // Deselect if clicking on empty space (and not holding shift)
430
+ if (!isShiftPressed) {
431
+ _this4.deselectObject();
432
+ }
433
+ _this4.clickTiming.lastClickTime = 0;
434
+ _this4.clickTiming.lastClickedObject = null;
435
+ return;
436
+ }
437
+
438
+ // Skip selection if not a component, gateway, or segment
439
+ var objectType = (_targetObject$userDat = targetObject.userData) === null || _targetObject$userDat === void 0 ? void 0 : _targetObject$userDat.objectType;
440
+ var isValidType = objectType === 'component' || objectType === 'gateway' || objectType === 'segment';
441
+ if (!isValidType) {
442
+ console.log('โŒ Object excluded from selection:', targetObject.name || targetObject.uuid, 'objectType:', objectType);
443
+ return false;
444
+ }
445
+
446
+ // Handle multi-selection with shift key
447
+ if (isShiftPressed) {
448
+ _this4.toggleObjectSelection(targetObject);
449
+ _this4.clickTiming.lastClickTime = currentTime;
450
+ _this4.clickTiming.lastClickedObject = targetObject;
451
+ return;
452
+ }
453
+
433
454
  // Check if this is a double-click: same object clicked within delay time
434
455
  var isSameObject = targetObject === _this4.clickTiming.lastClickedObject;
435
456
  var isWithinDelay = timeSinceLastClick < _this4.clickTiming.doubleClickDelay;
@@ -522,6 +543,7 @@ var TransformControlsManager = /*#__PURE__*/function () {
522
543
  }, {
523
544
  key: "_isValidSelectableObject",
524
545
  value: function _isValidSelectableObject(object) {
546
+ var _object$userData;
525
547
  // Safety check: ensure object is still valid and in the scene
526
548
  if (!object || !object.parent) {
527
549
  console.warn('โš ๏ธ Selected object is no longer valid or in scene');
@@ -533,6 +555,12 @@ var TransformControlsManager = /*#__PURE__*/function () {
533
555
  console.warn('โŒ Object missing required Three.js properties for transform controls');
534
556
  return false;
535
557
  }
558
+
559
+ // Check if object has explicit selectable flag set to false
560
+ if (((_object$userData = object.userData) === null || _object$userData === void 0 ? void 0 : _object$userData.selectable) === false) {
561
+ console.warn('โš ๏ธ Object is marked as non-selectable (stub segment)');
562
+ return false;
563
+ }
536
564
  return true;
537
565
  }
538
566
 
@@ -572,48 +600,41 @@ var TransformControlsManager = /*#__PURE__*/function () {
572
600
  }, {
573
601
  key: "isSelectableObject",
574
602
  value: function isSelectableObject(object) {
575
- var _object$userData, _object$userData2, _object$userData3, _object$userData4, _object$userData5, _object$userData6;
603
+ var _object$userData2, _object$userData3, _object$userData4, _object$userData5, _object$userData6, _object$userData7, _object$userData8;
576
604
  // Basic safety checks
577
605
  if (!object || !object.isObject3D) {
578
606
  return false;
579
607
  }
580
608
 
581
- // Debug logging for pipe segments
582
- if (objectTypes.isSegment(object)) {
583
- console.log('๐Ÿ” Found pipe segment in selection check:', object.name, object.userData);
584
- }
585
-
586
609
  // Skip transform controls and their children
587
610
  if (object.isTransformControls || object.isTransformControlsPlane || object.isTransformControlsGizmo) {
588
611
  return false;
589
612
  }
590
613
 
591
614
  // Skip helpers and special objects
592
- 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);
593
- var isBaseGround = (_object$userData3 = object.userData) === null || _object$userData3 === void 0 ? void 0 : _object$userData3.isBaseGround;
594
- var isBrickWall = (_object$userData4 = object.userData) === null || _object$userData4 === void 0 ? void 0 : _object$userData4.isBrickWall;
615
+ 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);
616
+ var isBaseGround = (_object$userData4 = object.userData) === null || _object$userData4 === void 0 ? void 0 : _object$userData4.isBaseGround;
617
+ var isBrickWall = (_object$userData5 = object.userData) === null || _object$userData5 === void 0 ? void 0 : _object$userData5.isBrickWall;
595
618
  if (isHelper || isBaseGround || isBrickWall || !object.visible) {
596
619
  return false;
597
620
  }
598
621
 
599
- // Allow pipe segments and junctions to be selected, but exclude parent polyline objects
600
- if (object.name && object.name.toLowerCase().includes('polyline') && !objectTypes.isSegment(object) && !((_object$userData5 = object.userData) !== null && _object$userData5 !== void 0 && _object$userData5.isPipeJunction)) {
601
- console.log('โŒ Polyline parent object excluded from selection:', object.name);
622
+ // Skip segment connectors - they should not be selectable for transformation
623
+ if (((_object$userData6 = object.userData) === null || _object$userData6 === void 0 ? void 0 : _object$userData6.objectType) === 'segment-connector') {
602
624
  return false;
603
625
  }
604
626
 
605
- // Check for pipe segments and junctions
606
- var isPipeJunction = ((_object$userData6 = object.userData) === null || _object$userData6 === void 0 ? void 0 : _object$userData6.isPipeJunction) === true;
607
-
608
- // Check for GLB models
609
- var isGLBModel = object.name && object.name.includes(' Component');
627
+ // Check if object has explicit selectable flag set to false
628
+ if (((_object$userData7 = object.userData) === null || _object$userData7 === void 0 ? void 0 : _object$userData7.selectable) === false) {
629
+ return false;
630
+ }
610
631
 
611
- // Check for connector orbs
612
- var isConnector = this.isConnectorOrb(object);
613
- return isGLBModel || isConnector || objectTypes.isSegment(object) || isPipeJunction;
632
+ // Allow components, gateways, and segments to be selected
633
+ var objectType = (_object$userData8 = object.userData) === null || _object$userData8 === void 0 ? void 0 : _object$userData8.objectType;
634
+ return objectType === 'component' || objectType === 'gateway' || objectTypes.isSegment(object);
614
635
  }
615
636
  /**
616
- * Select an object for transformation
637
+ * Select an object for transformation (with full callbacks including tooltips)
617
638
  * @param {THREE.Object3D} object - The object to select
618
639
  */
619
640
  }, {
@@ -623,37 +644,14 @@ var TransformControlsManager = /*#__PURE__*/function () {
623
644
  console.warn('โš ๏ธ Invalid object provided for selection');
624
645
  return false;
625
646
  }
626
- this.selectedObject = object;
627
-
628
- // Force object matrix update to ensure correct transforms
629
- object.updateMatrixWorld(true);
630
-
631
- // Refresh bounding box cache for this object
632
- var updatedBoundingBox = new THREE__namespace.Box3().setFromObject(object);
633
- this.boundingBoxCache.set(object, updatedBoundingBox);
634
-
635
- // Attach transform controls
636
- this.transformControls.attach(object);
637
-
638
- // Update interaction time to reset delay
639
- if (this.transformControls.updateInteractionTime) {
640
- this.transformControls.updateInteractionTime();
641
- }
642
647
 
643
- // Create and show bounding box if enabled
644
- this.createBoundingBox(object);
648
+ // Clear existing selection and select this object
649
+ this.selectedObjects = [object];
645
650
 
646
- // Only enable if not forcing invisibility
647
- if (!this.forceInvisible) {
648
- this.transformControls.enabled = this.config.enabled;
649
- this._showTransformControls();
650
- } else {
651
- console.log('๐Ÿ”’ Transform controls kept invisible despite object selection');
652
- this.transformControls.enabled = false;
653
- this._hideTransformControls();
654
- }
651
+ // Update the multi-selection display (works for single object too)
652
+ this.updateMultiSelection();
655
653
 
656
- // Execute callback
654
+ // Execute callback for tooltips and other UI updates
657
655
  if (this.callbacks.onObjectSelect) {
658
656
  this.callbacks.onObjectSelect(object);
659
657
  }
@@ -673,56 +671,198 @@ var TransformControlsManager = /*#__PURE__*/function () {
673
671
  console.warn('โš ๏ธ Invalid object provided for selection');
674
672
  return false;
675
673
  }
676
- this.selectedObject = object;
677
674
 
678
- // Force object matrix update to ensure correct transforms
679
- object.updateMatrixWorld(true);
675
+ // Clear existing selection and select this object
676
+ this.selectedObjects = [object];
680
677
 
681
- // Refresh bounding box cache for this object
682
- var updatedBoundingBox = new THREE__namespace.Box3().setFromObject(object);
683
- this.boundingBoxCache.set(object, updatedBoundingBox);
678
+ // Update the multi-selection display (works for single object too)
679
+ this.updateMultiSelection();
684
680
 
685
- // Attach transform controls
686
- this.transformControls.attach(object);
681
+ // NOTE: We deliberately do NOT call any callback here
682
+ // This allows transform controls and bounding box to show without triggering tooltips
683
+
684
+ console.log("\uD83C\uDFAF Object selected for transform only: ".concat(object.name || object.uuid));
685
+ return true;
686
+ }
687
+
688
+ /**
689
+ * Toggle object selection for multi-selection (shift+click)
690
+ * @param {THREE.Object3D} object - The object to toggle
691
+ */
692
+ }, {
693
+ key: "toggleObjectSelection",
694
+ value: function toggleObjectSelection(object) {
695
+ if (!object || !object.isObject3D) {
696
+ console.warn('โš ๏ธ Invalid object provided for multi-selection');
697
+ return;
698
+ }
699
+
700
+ // Check if object is already in selection
701
+ var index = this.selectedObjects.findIndex(function (obj) {
702
+ return obj === object;
703
+ });
704
+ if (index !== -1) {
705
+ // Object is already selected, remove it
706
+ this.selectedObjects.splice(index, 1);
707
+ console.log("\u2796 Removed from selection: ".concat(object.name || object.uuid));
708
+ } else {
709
+ // Object is not selected, add it
710
+ this.selectedObjects.push(object);
711
+ console.log("\u2795 Added to selection: ".concat(object.name || object.uuid));
712
+ }
713
+
714
+ // Update the multi-selection display
715
+ this.updateMultiSelection();
716
+ }
717
+
718
+ /**
719
+ * Update the multi-selection group and transform controls
720
+ * Works for both single and multiple object selection
721
+ */
722
+ }, {
723
+ key: "updateMultiSelection",
724
+ value: function updateMultiSelection() {
725
+ if (this.selectedObjects.length === 0) {
726
+ // No objects selected, deselect everything
727
+ this.deselectObject();
728
+ return;
729
+ }
730
+
731
+ // Create/update group for selected objects (even if just one)
732
+ this.createMultiSelectionGroup();
733
+
734
+ // Attach transform controls to the group
735
+ this.transformControls.attach(this.multiSelectionGroup);
687
736
 
688
737
  // Update interaction time to reset delay
689
738
  if (this.transformControls.updateInteractionTime) {
690
739
  this.transformControls.updateInteractionTime();
691
740
  }
692
741
 
693
- // Create and show bounding box if enabled
694
- this.createBoundingBox(object);
742
+ // Create bounding boxes for all selected objects
743
+ this.createMultiBoundingBox();
695
744
 
696
- // Only enable if not forcing invisibility
745
+ // Enable transform controls if not forcing invisibility
697
746
  if (!this.forceInvisible) {
698
747
  this.transformControls.enabled = true;
699
748
  this._showTransformControls();
700
- } else {
701
- console.log('๐Ÿ”’ Transform controls kept invisible despite object selection');
702
- this.transformControls.enabled = false;
703
- this._hideTransformControls();
704
749
  }
750
+ console.log("\uD83C\uDFAF Selection active: ".concat(this.selectedObjects.length, " object(s)"));
751
+ }
705
752
 
706
- // NOTE: We deliberately do NOT call any callback here
707
- // This allows transform controls and bounding box to show without triggering tooltips
753
+ /**
754
+ * Create or update the multi-selection group
755
+ */
756
+ }, {
757
+ key: "createMultiSelectionGroup",
758
+ value: function createMultiSelectionGroup() {
759
+ // Remove existing group if it exists
760
+ if (this.multiSelectionGroup) {
761
+ this.scene.remove(this.multiSelectionGroup);
762
+ }
763
+
764
+ // Create new group
765
+ this.multiSelectionGroup = new THREE__namespace.Group();
766
+ this.multiSelectionGroup.name = 'MultiSelectionGroup';
767
+ this.multiSelectionGroup.userData = {
768
+ isMultiSelectionGroup: true
769
+ };
708
770
 
709
- console.log("\uD83C\uDFAF Object selected for transform only: ".concat(object.name || object.uuid));
710
- return true;
771
+ // Calculate the centroid of all selected objects
772
+ var centroid = new THREE__namespace.Vector3();
773
+ this.selectedObjects.forEach(function (obj) {
774
+ var worldPos = new THREE__namespace.Vector3();
775
+ obj.getWorldPosition(worldPos);
776
+ centroid.add(worldPos);
777
+ });
778
+ centroid.divideScalar(this.selectedObjects.length);
779
+
780
+ // Position the group at the centroid
781
+ this.multiSelectionGroup.position.copy(centroid);
782
+
783
+ // Add group to scene
784
+ this.scene.add(this.multiSelectionGroup);
785
+
786
+ // Store original position, rotation, and scale for each object
787
+ // Note: We don't store the parent reference to avoid circular JSON issues
788
+ this.selectedObjects.forEach(function (obj) {
789
+ // Store local transforms (for final API calls)
790
+ obj.userData._multiSelectOriginalPosition = obj.position.clone();
791
+ obj.userData._multiSelectOriginalRotation = obj.rotation.clone();
792
+ obj.userData._multiSelectOriginalScale = obj.scale.clone();
793
+
794
+ // Store world position (for real-time visual updates)
795
+ var worldPos = new THREE__namespace.Vector3();
796
+ obj.getWorldPosition(worldPos);
797
+ obj.userData._multiSelectOriginalWorldPosition = worldPos;
798
+ });
799
+ console.log("\uD83D\uDCE6 Multi-selection group created at centroid:", centroid.toArray());
800
+ }
801
+
802
+ /**
803
+ * Create a bounding box that encompasses all selected objects
804
+ */
805
+ }, {
806
+ key: "createMultiBoundingBox",
807
+ value:
808
+ /**
809
+ * Create bounding boxes for all selected objects
810
+ */
811
+ function createMultiBoundingBox() {
812
+ var _this5 = this;
813
+ // Skip if bounding box is disabled
814
+ if (!this.config.showBoundingBox) {
815
+ return;
816
+ }
817
+
818
+ // Remove any existing bounding boxes first
819
+ this.removeBoundingBox();
820
+ if (this.selectedObjects.length === 0) {
821
+ return;
822
+ }
823
+ try {
824
+ // Create individual bounding boxes for each selected object
825
+ this.selectedObjects.forEach(function (obj) {
826
+ var boundingBoxHelper = new THREE__namespace.BoxHelper(obj, _this5.config.boundingBoxColor);
827
+
828
+ // Mark it as a helper to avoid selection
829
+ boundingBoxHelper.isHelper = true;
830
+ boundingBoxHelper.userData = {
831
+ isBoundingBox: true
832
+ };
833
+
834
+ // Add to scene
835
+ _this5.scene.add(boundingBoxHelper);
836
+
837
+ // Store in array for later cleanup
838
+ _this5.boundingBoxHelpers.push(boundingBoxHelper);
839
+ });
840
+ console.log("\uD83D\uDCE6 Bounding boxes created for ".concat(this.selectedObjects.length, " object(s)"));
841
+ } catch (error) {
842
+ console.warn('โš ๏ธ Failed to create bounding boxes:', error);
843
+ }
711
844
  }
712
845
 
713
846
  /**
714
- * Deselect the currently selected object
847
+ * Remove all bounding box helpers
715
848
  */
716
849
  }, {
717
850
  key: "deselectObject",
718
- value: function deselectObject() {
719
- if (this.selectedObject) {
720
- console.log("\uD83D\uDD04 Object deselected: ".concat(this.selectedObject.name || this.selectedObject.uuid));
851
+ value:
852
+ /**
853
+ * Deselect all currently selected objects
854
+ */
855
+ function deselectObject() {
856
+ if (this.selectedObjects.length > 0) {
857
+ console.log("\uD83D\uDD04 Objects deselected: ".concat(this.selectedObjects.length));
721
858
  }
722
859
 
723
- // Remove bounding box
860
+ // Clear multi-selection
861
+ this.clearMultiSelection();
862
+
863
+ // Remove bounding boxes
724
864
  this.removeBoundingBox();
725
- this.selectedObject = null;
865
+
726
866
  // Detach and hide transform controls
727
867
  if (this.transformControls) {
728
868
  // First detach from object
@@ -741,94 +881,97 @@ var TransformControlsManager = /*#__PURE__*/function () {
741
881
  }
742
882
 
743
883
  /**
744
- * Create and display a bounding box for the selected object
745
- * @param {THREE.Object3D} object - The object to create a bounding box for
884
+ * Clear selection and restore original object states
746
885
  */
747
886
  }, {
748
- key: "createBoundingBox",
749
- value: function createBoundingBox(object) {
750
- // Skip if bounding box is disabled
751
- if (!this.config.showBoundingBox) {
752
- return;
753
- }
887
+ key: "clearMultiSelection",
888
+ value: function clearMultiSelection() {
889
+ // Clean up userData for selected objects
890
+ this.selectedObjects.forEach(function (obj) {
891
+ if (obj.userData._multiSelectOriginalPosition) {
892
+ delete obj.userData._multiSelectOriginalPosition;
893
+ delete obj.userData._multiSelectOriginalRotation;
894
+ delete obj.userData._multiSelectOriginalScale;
895
+ delete obj.userData._multiSelectOriginalWorldPosition;
896
+ }
897
+ });
754
898
 
755
- // Remove any existing bounding box first
756
- this.removeBoundingBox();
757
- if (!object) {
758
- return;
899
+ // Remove selection group from scene
900
+ if (this.multiSelectionGroup) {
901
+ this.scene.remove(this.multiSelectionGroup);
902
+ this.multiSelectionGroup = null;
759
903
  }
760
- try {
761
- // Create a BoxHelper to show the bounding box
762
- this.boundingBoxHelper = new THREE__namespace.BoxHelper(object, this.config.boundingBoxColor);
763
-
764
- // Mark it as a helper to avoid selection
765
- this.boundingBoxHelper.isHelper = true;
766
- this.boundingBoxHelper.userData = {
767
- isBoundingBox: true
768
- };
769
- console.log("this.boundingBoxHelper object:", object);
770
-
771
- // Add to scene
772
- this.scene.add(this.boundingBoxHelper);
773
- console.log("\uD83D\uDCE6 Bounding box created for: ".concat(object.name || object.uuid));
774
- } catch (error) {
775
- console.warn('โš ๏ธ Failed to create bounding box:', error);
904
+
905
+ // Clear selection array
906
+ var count = this.selectedObjects.length;
907
+ this.selectedObjects = [];
908
+ if (count > 0) {
909
+ console.log("\uD83E\uDDF9 Cleared selection (".concat(count, " object(s))"));
776
910
  }
777
911
  }
778
-
779
- /**
780
- * Remove the current bounding box helper
781
- */
782
912
  }, {
783
913
  key: "removeBoundingBox",
784
914
  value: function removeBoundingBox() {
785
- if (this.boundingBoxHelper) {
786
- try {
787
- // Remove from scene
788
- if (this.boundingBoxHelper.parent) {
789
- this.boundingBoxHelper.parent.remove(this.boundingBoxHelper);
790
- }
915
+ // Remove all bounding box helpers
916
+ if (this.boundingBoxHelpers.length > 0) {
917
+ this.boundingBoxHelpers.forEach(function (helper) {
918
+ try {
919
+ // Remove from scene
920
+ if (helper.parent) {
921
+ helper.parent.remove(helper);
922
+ }
791
923
 
792
- // Dispose geometry and material if they exist
793
- if (this.boundingBoxHelper.geometry) {
794
- this.boundingBoxHelper.geometry.dispose();
795
- }
796
- if (this.boundingBoxHelper.material) {
797
- this.boundingBoxHelper.material.dispose();
924
+ // Dispose geometry and material if they exist
925
+ if (helper.geometry) {
926
+ helper.geometry.dispose();
927
+ }
928
+ if (helper.material) {
929
+ helper.material.dispose();
930
+ }
931
+ } catch (error) {
932
+ console.warn('โš ๏ธ Error removing bounding box helper:', error);
798
933
  }
799
- console.log('๐Ÿ“ฆ Bounding box removed');
800
- } catch (error) {
801
- console.warn('โš ๏ธ Error removing bounding box:', error);
802
- } finally {
803
- this.boundingBoxHelper = null;
934
+ });
935
+ var count = this.boundingBoxHelpers.length;
936
+ this.boundingBoxHelpers = [];
937
+ if (count > 0) {
938
+ console.log("\uD83D\uDCE6 Removed ".concat(count, " bounding box(es)"));
804
939
  }
805
940
  }
806
941
  }
807
942
 
808
943
  /**
809
- * Update the bounding box to match the current object state
810
- * Call this after any transformation to keep the bounding box in sync
944
+ * Update the bounding boxes to match the current object states
945
+ * Call this after any transformation to keep the bounding boxes in sync
811
946
  */
812
947
  }, {
813
948
  key: "updateBoundingBox",
814
949
  value: function updateBoundingBox() {
815
- if (this.boundingBoxHelper && this.selectedObject) {
950
+ var _this6 = this;
951
+ // Update bounding boxes for all selected objects
952
+ if (this.selectedObjects.length > 0 && this.boundingBoxHelpers.length > 0) {
816
953
  try {
817
- // Force object matrix update to ensure correct bounding box
818
- this.selectedObject.updateMatrixWorld(true);
819
-
820
- // Update bounding box
821
- this.boundingBoxHelper.update();
822
-
823
- // Also update the cached bounding box if it exists
824
- if (this.boundingBoxCache.has(this.selectedObject)) {
825
- var updatedBoundingBox = new THREE__namespace.Box3().setFromObject(this.selectedObject);
826
- this.boundingBoxCache.set(this.selectedObject, updatedBoundingBox);
827
- }
954
+ // Update each bounding box helper
955
+ this.boundingBoxHelpers.forEach(function (helper, index) {
956
+ var obj = _this6.selectedObjects[index];
957
+ if (obj) {
958
+ // Force object matrix update to ensure correct bounding box
959
+ obj.updateMatrixWorld(true);
960
+
961
+ // Update bounding box
962
+ helper.update();
963
+
964
+ // Also update the cached bounding box if it exists
965
+ if (_this6.boundingBoxCache.has(obj)) {
966
+ var updatedBoundingBox = new THREE__namespace.Box3().setFromObject(obj);
967
+ _this6.boundingBoxCache.set(obj, updatedBoundingBox);
968
+ }
969
+ }
970
+ });
828
971
  } catch (error) {
829
- console.warn('โš ๏ธ Error updating bounding box:', error);
830
- // If update fails, recreate the bounding box
831
- this.createBoundingBox(this.selectedObject);
972
+ console.warn('โš ๏ธ Error updating bounding boxes:', error);
973
+ // If update fails, recreate the bounding boxes
974
+ this.createMultiBoundingBox();
832
975
  }
833
976
  }
834
977
  }
@@ -930,33 +1073,34 @@ var TransformControlsManager = /*#__PURE__*/function () {
930
1073
  key: "setEnabled",
931
1074
  value: function setEnabled(enabled) {
932
1075
  this.config.enabled = enabled;
933
- if (this.selectedObject) {
1076
+ if (this.selectedObjects.length > 0) {
934
1077
  this.transformControls.enabled = enabled;
935
1078
  }
936
1079
  console.log("\uD83D\uDD04 Transform controls ".concat(enabled ? 'enabled' : 'disabled'));
937
1080
  }
938
1081
 
939
1082
  /**
940
- * Get world transformation data for the selected object
1083
+ * Get world transformation data for the first selected object
941
1084
  * @returns {Object} World transformation data
942
1085
  */
943
1086
  }, {
944
1087
  key: "getWorldTransformData",
945
1088
  value: function getWorldTransformData() {
946
- if (!this.selectedObject) {
1089
+ if (this.selectedObjects.length === 0) {
947
1090
  return null;
948
1091
  }
1092
+ var selectedObject = this.selectedObjects[0];
949
1093
 
950
1094
  // Ensure the object's world matrix is up to date
951
- this.selectedObject.updateMatrixWorld(true);
1095
+ selectedObject.updateMatrixWorld(true);
952
1096
 
953
1097
  // Get world position
954
1098
  var worldPosition = new THREE__namespace.Vector3();
955
- this.selectedObject.getWorldPosition(worldPosition);
1099
+ selectedObject.getWorldPosition(worldPosition);
956
1100
 
957
1101
  // Get world quaternion
958
1102
  var worldQuaternion = new THREE__namespace.Quaternion();
959
- this.selectedObject.getWorldQuaternion(worldQuaternion);
1103
+ selectedObject.getWorldQuaternion(worldQuaternion);
960
1104
 
961
1105
  // Convert quaternion to Euler rotation
962
1106
  var worldRotation = new THREE__namespace.Euler();
@@ -964,7 +1108,7 @@ var TransformControlsManager = /*#__PURE__*/function () {
964
1108
 
965
1109
  // Get world scale
966
1110
  var worldScale = new THREE__namespace.Vector3();
967
- this.selectedObject.getWorldScale(worldScale);
1111
+ selectedObject.getWorldScale(worldScale);
968
1112
  return {
969
1113
  position: worldPosition,
970
1114
  rotation: worldRotation,
@@ -983,11 +1127,11 @@ var TransformControlsManager = /*#__PURE__*/function () {
983
1127
  key: "updateObjectTransform",
984
1128
  value: function updateObjectTransform(type, axis, value) {
985
1129
  var useWorldCoords = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
986
- if (!this.selectedObject) {
1130
+ if (this.selectedObjects.length === 0) {
987
1131
  console.warn('โš ๏ธ No object selected for transformation');
988
1132
  return;
989
1133
  }
990
- var object = this.selectedObject;
1134
+ var object = this.selectedObjects[0];
991
1135
  var numericValue = parseFloat(value);
992
1136
  if (isNaN(numericValue)) {
993
1137
  console.warn('โš ๏ธ Invalid numeric value for transform:', value);
@@ -1013,7 +1157,9 @@ var TransformControlsManager = /*#__PURE__*/function () {
1013
1157
  console.warn("\u26A0\uFE0F Unknown transform type: ".concat(type));
1014
1158
  return;
1015
1159
  }
1016
- } // Update transform controls to reflect the change
1160
+ }
1161
+
1162
+ // Update transform controls to reflect the change
1017
1163
  if (this.transformControls) {
1018
1164
  this.transformControls.updateMatrixWorld();
1019
1165
  }
@@ -1141,13 +1287,13 @@ var TransformControlsManager = /*#__PURE__*/function () {
1141
1287
  }, {
1142
1288
  key: "getSelectableObjectsWithBounds",
1143
1289
  value: function getSelectableObjectsWithBounds() {
1144
- var _this5 = this;
1290
+ var _this7 = this;
1145
1291
  var objectFilter = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
1146
1292
  var objectsWithBounds = [];
1147
1293
 
1148
1294
  // Traverse scene to find selectable objects
1149
1295
  this.scene.traverse(function (object) {
1150
- var _object$userData7, _object$userData8;
1296
+ var _object$userData9, _object$userData0;
1151
1297
  // Skip invalid objects and helpers early
1152
1298
  if (!object || !object.isObject3D) {
1153
1299
  return;
@@ -1159,25 +1305,25 @@ var TransformControlsManager = /*#__PURE__*/function () {
1159
1305
  }
1160
1306
 
1161
1307
  // Skip other helpers and special objects
1162
- 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) {
1308
+ 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) {
1163
1309
  return;
1164
1310
  }
1165
1311
  try {
1166
1312
  // Apply filter (use provided filter or default isSelectableObject method)
1167
- var isSelectable = objectFilter ? objectFilter(object) : _this5.isSelectableObject(object);
1313
+ var isSelectable = objectFilter ? objectFilter(object) : _this7.isSelectableObject(object);
1168
1314
  if (isSelectable) {
1169
1315
  // Force object matrix update to ensure correct bounding box
1170
1316
  object.updateMatrixWorld(true);
1171
1317
 
1172
1318
  // Get or compute bounding box
1173
- var boundingBox = _this5.boundingBoxCache.get(object);
1319
+ var boundingBox = _this7.boundingBoxCache.get(object);
1174
1320
 
1175
1321
  // Always recompute bounding box for recently transformed objects
1176
1322
  // or if no cached bounding box exists
1177
- if (!boundingBox || object === _this5.selectedObject) {
1323
+ if (!boundingBox || _this7.selectedObjects.includes(object)) {
1178
1324
  // Compute and cache bounding box
1179
1325
  boundingBox = new THREE__namespace.Box3().setFromObject(object);
1180
- _this5.boundingBoxCache.set(object, boundingBox);
1326
+ _this7.boundingBoxCache.set(object, boundingBox);
1181
1327
  }
1182
1328
  objectsWithBounds.push({
1183
1329
  object: object,
@@ -1239,60 +1385,216 @@ var TransformControlsManager = /*#__PURE__*/function () {
1239
1385
  }
1240
1386
 
1241
1387
  /**
1242
- * Handle translate using centralPlant API instead of direct Three.js manipulation
1243
- * This method calculates the translation delta and resets the object position,
1244
- * then uses the centralPlant.translate() API to apply the change properly
1388
+ * Apply real-time visual transformation during drag (no API calls)
1389
+ * This updates the object positions/rotations visually while the user is dragging
1245
1390
  */
1246
1391
  }, {
1247
- key: "handleTranslateWithAPI",
1248
- value: function handleTranslateWithAPI() {
1249
- var _this$selectedObject$;
1250
- if (!this.selectedObject || !this.transformState.initialTransform || !this.transformState.currentTransform) {
1251
- console.warn('โš ๏ธ Cannot handle translate with API: missing required state');
1392
+ key: "applyRealtimeTransform",
1393
+ value: function applyRealtimeTransform() {
1394
+ var _this8 = this;
1395
+ if (!this.multiSelectionGroup || this.selectedObjects.length === 0) {
1252
1396
  return;
1253
1397
  }
1254
1398
 
1255
- // Calculate the translation deltas
1256
- var deltaX = this.transformState.currentTransform.position.x - this.transformState.initialTransform.position.x;
1257
- var deltaY = this.transformState.currentTransform.position.y - this.transformState.initialTransform.position.y;
1258
- var deltaZ = this.transformState.currentTransform.position.z - this.transformState.initialTransform.position.z;
1399
+ // Calculate the transformation delta from the group
1400
+ var groupPosition = this.multiSelectionGroup.position.clone();
1401
+ var groupRotation = this.multiSelectionGroup.rotation.clone();
1259
1402
 
1260
- // Reset the object to its initial position (undo the direct Three.js manipulation)
1261
- this.selectedObject.position.copy(this.transformState.initialTransform.position);
1262
- this.selectedObject.updateMatrix();
1263
- this.selectedObject.updateMatrixWorld(true);
1264
- console.log("\uD83D\uDD27 Using centralPlant API for translation:", {
1265
- deltaX: deltaX,
1266
- deltaY: deltaY,
1267
- deltaZ: deltaZ
1403
+ // Get the original centroid position (from when group was created)
1404
+ var originalCentroid = new THREE__namespace.Vector3();
1405
+ this.selectedObjects.forEach(function (obj) {
1406
+ var originalWorldPos = obj.userData._multiSelectOriginalWorldPosition;
1407
+ if (originalWorldPos) {
1408
+ originalCentroid.add(originalWorldPos);
1409
+ }
1410
+ });
1411
+ originalCentroid.divideScalar(this.selectedObjects.length);
1412
+
1413
+ // Calculate deltas
1414
+ var positionDelta = groupPosition.clone().sub(originalCentroid);
1415
+ var rotationDelta = groupRotation.clone();
1416
+
1417
+ // Apply transformation to each selected object visually
1418
+ this.selectedObjects.forEach(function (obj) {
1419
+ var originalWorldPos = obj.userData._multiSelectOriginalWorldPosition;
1420
+ var originalRot = obj.userData._multiSelectOriginalRotation;
1421
+ if (!originalWorldPos || !originalRot) {
1422
+ return;
1423
+ }
1424
+ if (_this8.currentMode === 'translate') {
1425
+ // Simple translation: original world position + delta
1426
+ var newWorldPos = originalWorldPos.clone().add(positionDelta);
1427
+
1428
+ // Convert to local position if object has a parent
1429
+ if (obj.parent && obj.parent !== _this8.scene) {
1430
+ // Update parent matrix if parent is also being transformed
1431
+ obj.parent.updateMatrixWorld(true);
1432
+ var parentMatrixInverse = new THREE__namespace.Matrix4();
1433
+ parentMatrixInverse.copy(obj.parent.matrixWorld).invert();
1434
+ newWorldPos.applyMatrix4(parentMatrixInverse);
1435
+ }
1436
+ obj.position.copy(newWorldPos);
1437
+ obj.updateMatrix();
1438
+ } else if (_this8.currentMode === 'rotate') {
1439
+ // Calculate offset from centroid
1440
+ var offsetFromCentroid = originalWorldPos.clone().sub(originalCentroid);
1441
+
1442
+ // Rotate the offset vector
1443
+ var rotatedOffset = offsetFromCentroid.clone();
1444
+ var rotationQuat = new THREE__namespace.Quaternion().setFromEuler(rotationDelta);
1445
+ rotatedOffset.applyQuaternion(rotationQuat);
1446
+
1447
+ // Calculate new world position
1448
+ var _newWorldPos = originalCentroid.clone().add(rotatedOffset).add(positionDelta);
1449
+
1450
+ // Convert to local position
1451
+ if (obj.parent && obj.parent !== _this8.scene) {
1452
+ // Update parent matrix if parent is also being transformed
1453
+ obj.parent.updateMatrixWorld(true);
1454
+ var _parentMatrixInverse = new THREE__namespace.Matrix4();
1455
+ _parentMatrixInverse.copy(obj.parent.matrixWorld).invert();
1456
+ _newWorldPos.applyMatrix4(_parentMatrixInverse);
1457
+ }
1458
+ obj.position.copy(_newWorldPos);
1459
+
1460
+ // Apply rotation to the object itself
1461
+ obj.rotation.x = originalRot.x + rotationDelta.x;
1462
+ obj.rotation.y = originalRot.y + rotationDelta.y;
1463
+ obj.rotation.z = originalRot.z + rotationDelta.z;
1464
+ obj.updateMatrix();
1465
+ }
1466
+
1467
+ // Mark world matrix as needing update (will propagate to children)
1468
+ obj.matrixWorldNeedsUpdate = true;
1268
1469
  });
1470
+ }
1269
1471
 
1270
- // Get the component ID (try both uuid and originalUuid)
1271
- var componentId = this.selectedObject.uuid || ((_this$selectedObject$ = this.selectedObject.userData) === null || _this$selectedObject$ === void 0 ? void 0 : _this$selectedObject$.originalUuid);
1272
- if (!componentId) {
1273
- console.error('โŒ Cannot find component ID for translation API call');
1472
+ /**
1473
+ * Apply transformation from multi-selection group to all selected objects
1474
+ * Simple approach: calculate delta once, apply to each object via API
1475
+ */
1476
+ }, {
1477
+ key: "applyMultiSelectionTransform",
1478
+ value: function applyMultiSelectionTransform() {
1479
+ var _this9 = this;
1480
+ if (!this.multiSelectionGroup || this.selectedObjects.length === 0) {
1274
1481
  return;
1275
1482
  }
1483
+ console.log("\uD83D\uDD27 Applying multi-selection transform to ".concat(this.selectedObjects.length, " objects"));
1276
1484
 
1277
- // Apply translations using centralPlant API if there's a meaningful delta
1278
- // Round to avoid floating point precision issues
1485
+ // Calculate the transformation delta from the group
1486
+ var groupPosition = this.multiSelectionGroup.position.clone();
1487
+
1488
+ // Get the original centroid position (from when group was created)
1489
+ var originalCentroid = new THREE__namespace.Vector3();
1490
+ this.selectedObjects.forEach(function (obj) {
1491
+ var originalWorldPos = obj.userData._multiSelectOriginalWorldPosition;
1492
+ if (originalWorldPos) {
1493
+ originalCentroid.add(originalWorldPos);
1494
+ }
1495
+ });
1496
+ originalCentroid.divideScalar(this.selectedObjects.length);
1497
+
1498
+ // Calculate position delta - this is the same for ALL objects
1499
+ var positionDelta = groupPosition.clone().sub(originalCentroid);
1500
+
1501
+ // Round deltas to avoid floating point precision issues
1502
+ var deltaX = Math.round(positionDelta.x * 2) / 2;
1503
+ var deltaY = Math.round(positionDelta.y * 2) / 2;
1504
+ var deltaZ = Math.round(positionDelta.z * 2) / 2;
1505
+ console.log('๐Ÿ”ง Transform delta:', {
1506
+ deltaX: deltaX,
1507
+ deltaY: deltaY,
1508
+ deltaZ: deltaZ
1509
+ });
1510
+
1511
+ // Only process if there's a meaningful translation
1279
1512
  var threshold = 0.001;
1280
- if (Math.abs(deltaX) > threshold) {
1281
- // Round to nearest 0.5 to match grid snapping
1282
- var roundedDeltaX = Math.round(deltaX * 2) / 2;
1283
- this.centralPlant.translate(componentId, 'x', roundedDeltaX);
1284
- }
1285
- if (Math.abs(deltaY) > threshold) {
1286
- // Round to nearest 0.5 to match grid snapping
1287
- var roundedDeltaY = Math.round(deltaY * 2) / 2;
1288
- this.centralPlant.translate(componentId, 'y', roundedDeltaY);
1289
- }
1290
- if (Math.abs(deltaZ) > threshold) {
1291
- // Round to nearest 0.5 to match grid snapping
1292
- var roundedDeltaZ = Math.round(deltaZ * 2) / 2;
1293
- this.centralPlant.translate(componentId, 'z', roundedDeltaZ);
1513
+ if (this.currentMode === 'translate' && positionDelta.length() > threshold) {
1514
+ // FIRST: Reset all objects to their original positions
1515
+ // This undoes the visual transform applied during drag
1516
+ this.selectedObjects.forEach(function (obj) {
1517
+ var originalPos = obj.userData._multiSelectOriginalPosition;
1518
+ if (originalPos) {
1519
+ obj.position.copy(originalPos);
1520
+ obj.updateMatrixWorld(true);
1521
+ }
1522
+ });
1523
+ console.log('๐Ÿ”„ Reset objects to original positions before API calls');
1524
+
1525
+ // THEN: Apply the delta to each object using the appropriate API
1526
+ this.selectedObjects.forEach(function (obj) {
1527
+ var _obj$userData;
1528
+ if (!_this9.centralPlant) {
1529
+ console.warn('โš ๏ธ CentralPlant API not available');
1530
+ return;
1531
+ }
1532
+ var componentId = obj.uuid || ((_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.originalUuid);
1533
+ if (objectTypes.isSegment(obj)) {
1534
+ // Use translateSegment API
1535
+ if (Math.abs(deltaX) > threshold) {
1536
+ _this9.centralPlant.translateSegment(componentId, 'x', deltaX);
1537
+ }
1538
+ if (Math.abs(deltaY) > threshold) {
1539
+ _this9.centralPlant.translateSegment(componentId, 'y', deltaY);
1540
+ }
1541
+ if (Math.abs(deltaZ) > threshold) {
1542
+ _this9.centralPlant.translateSegment(componentId, 'z', deltaZ);
1543
+ }
1544
+ console.log("\uD83D\uDCE6 Segment ".concat(obj.name, " translated:"), {
1545
+ deltaX: deltaX,
1546
+ deltaY: deltaY,
1547
+ deltaZ: deltaZ
1548
+ });
1549
+ } else if (objectTypes.isGateway(obj)) {
1550
+ // Use translateGateway API
1551
+ if (Math.abs(deltaX) > threshold) {
1552
+ _this9.centralPlant.translateGateway(componentId, 'x', deltaX);
1553
+ }
1554
+ if (Math.abs(deltaY) > threshold) {
1555
+ _this9.centralPlant.translateGateway(componentId, 'y', deltaY);
1556
+ }
1557
+ if (Math.abs(deltaZ) > threshold) {
1558
+ _this9.centralPlant.translateGateway(componentId, 'z', deltaZ);
1559
+ }
1560
+ console.log("\uD83D\uDEAA Gateway ".concat(obj.name, " translated:"), {
1561
+ deltaX: deltaX,
1562
+ deltaY: deltaY,
1563
+ deltaZ: deltaZ
1564
+ });
1565
+ } else {
1566
+ // Use standard translate API for components
1567
+ if (Math.abs(deltaX) > threshold) {
1568
+ _this9.centralPlant.translate(componentId, 'x', deltaX);
1569
+ }
1570
+ if (Math.abs(deltaY) > threshold) {
1571
+ _this9.centralPlant.translate(componentId, 'y', deltaY);
1572
+ }
1573
+ if (Math.abs(deltaZ) > threshold) {
1574
+ _this9.centralPlant.translate(componentId, 'z', deltaZ);
1575
+ }
1576
+ console.log("\uD83D\uDD27 Component ".concat(obj.name, " translated:"), {
1577
+ deltaX: deltaX,
1578
+ deltaY: deltaY,
1579
+ deltaZ: deltaZ
1580
+ });
1581
+ }
1582
+ });
1583
+ console.log("\u2705 All ".concat(this.selectedObjects.length, " objects translated with delta:"), {
1584
+ deltaX: deltaX,
1585
+ deltaY: deltaY,
1586
+ deltaZ: deltaZ
1587
+ });
1294
1588
  }
1295
- console.log("\u2705 Applied translation through centralPlant API");
1589
+
1590
+ // Reset the multi-selection group transform
1591
+ this.multiSelectionGroup.position.set(0, 0, 0);
1592
+ this.multiSelectionGroup.rotation.set(0, 0, 0);
1593
+ this.multiSelectionGroup.scale.set(1, 1, 1);
1594
+
1595
+ // Update the multi-selection display with new positions
1596
+ this.updateMultiSelection();
1597
+ console.log("\u2705 Multi-selection transform applied");
1296
1598
  }
1297
1599
 
1298
1600
  /**
@@ -1486,10 +1788,8 @@ var TransformControlsManager = /*#__PURE__*/function () {
1486
1788
  // Remove bounding box helper
1487
1789
  this.removeBoundingBox();
1488
1790
 
1489
- // Deselect any object first
1490
- if (this.selectedObject) {
1491
- this.deselectObject();
1492
- }
1791
+ // Clear multi-selection
1792
+ this.clearMultiSelection();
1493
1793
 
1494
1794
  // Dispose transform controls
1495
1795
  if (this.transformControls) {
@@ -1518,7 +1818,7 @@ var TransformControlsManager = /*#__PURE__*/function () {
1518
1818
  }
1519
1819
 
1520
1820
  // Clear references - but keep scene, camera, and renderer references for reuse
1521
- this.selectedObject = null;
1821
+ this.selectedObjects = [];
1522
1822
  // Don't null these out: this.scene, this.camera, this.renderer
1523
1823
  this.orbitControls = null;
1524
1824
  console.log('๐Ÿงน Transform controls disposed');