@2112-lab/central-plant 0.3.26 → 0.3.28

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 (28) hide show
  1. package/dist/bundle/index.js +922 -974
  2. package/dist/cjs/src/core/centralPlant.js +8 -115
  3. package/dist/cjs/src/core/centralPlantInternals.js +23 -17
  4. package/dist/cjs/src/core/sceneViewer.js +55 -2
  5. package/dist/cjs/src/index.js +0 -2
  6. package/dist/cjs/src/managers/behaviors/IoAnimationManager.js +24 -1
  7. package/dist/cjs/src/managers/behaviors/IoOutlineManager.js +258 -0
  8. package/dist/cjs/src/managers/controls/transformControlsManager.js +319 -43
  9. package/dist/cjs/src/managers/scene/animationManager.js +9 -2
  10. package/dist/cjs/src/managers/scene/componentTooltipManager.js +190 -34
  11. package/dist/cjs/src/managers/scene/modelManager.js +15 -1
  12. package/dist/cjs/src/managers/scene/sceneExportManager.js +3 -29
  13. package/dist/cjs/src/managers/scene/sceneOperationsManager.js +12 -289
  14. package/dist/cjs/src/utils/boundingBoxUtils.js +38 -40
  15. package/dist/esm/src/core/centralPlant.js +8 -115
  16. package/dist/esm/src/core/centralPlantInternals.js +23 -17
  17. package/dist/esm/src/core/sceneViewer.js +55 -2
  18. package/dist/esm/src/index.js +0 -1
  19. package/dist/esm/src/managers/behaviors/IoAnimationManager.js +24 -1
  20. package/dist/esm/src/managers/behaviors/IoOutlineManager.js +234 -0
  21. package/dist/esm/src/managers/controls/transformControlsManager.js +319 -43
  22. package/dist/esm/src/managers/scene/animationManager.js +9 -2
  23. package/dist/esm/src/managers/scene/componentTooltipManager.js +191 -35
  24. package/dist/esm/src/managers/scene/modelManager.js +16 -2
  25. package/dist/esm/src/managers/scene/sceneExportManager.js +4 -30
  26. package/dist/esm/src/managers/scene/sceneOperationsManager.js +12 -289
  27. package/dist/esm/src/utils/boundingBoxUtils.js +39 -42
  28. package/package.json +1 -1
@@ -35,7 +35,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
35
35
  * Initialize the CentralPlant manager
36
36
  *
37
37
  * @constructor
38
- * @version 0.3.26
38
+ * @version 0.3.28
39
39
  * @updated 2025-10-22
40
40
  *
41
41
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -1069,107 +1069,6 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
1069
1069
  };
1070
1070
  }
1071
1071
 
1072
- // ─────────────────────────────────────────────────────────────────────────
1073
- // BEHAVIORS API
1074
- // ─────────────────────────────────────────────────────────────────────────
1075
-
1076
- /**
1077
- * Get all behavior definitions currently stored in the BehaviorManager.
1078
- * @returns {Array<Object>} Array of behavior definition objects, or empty array.
1079
- * @example
1080
- * const behaviors = centralPlant.getBehaviors()
1081
- * behaviors.forEach(b => console.log(b.id, b.input, b.output))
1082
- */
1083
- }, {
1084
- key: "getBehaviors",
1085
- value: function getBehaviors() {
1086
- var _this$sceneViewer4;
1087
- var bm = (_this$sceneViewer4 = this.sceneViewer) === null || _this$sceneViewer4 === void 0 || (_this$sceneViewer4 = _this$sceneViewer4.managers) === null || _this$sceneViewer4 === void 0 ? void 0 : _this$sceneViewer4.behaviorManager;
1088
- if (!bm) {
1089
- console.warn('⚠️ getBehaviors(): BehaviorManager not available');
1090
- return [];
1091
- }
1092
- return bm.getBehaviors();
1093
- }
1094
-
1095
- /**
1096
- * Add a behavior definition at runtime.
1097
- * @param {Object} behaviorDef - Full behavior definition object (must have unique `id`)
1098
- * @returns {boolean} True if added successfully, false otherwise
1099
- * @example
1100
- * centralPlant.addBehavior({
1101
- * id: 'my-behavior',
1102
- * input: { attachment: 'device-01', dataPoint: 'boolean-status-01' },
1103
- * output: { attachment: 'light-01', child: 'indicator-mesh-01' },
1104
- * conditions: [{ when: 'dataPoint.value === true', actions: [{ set: 'material.emissiveIntensity', value: 1.0 }] }]
1105
- * })
1106
- */
1107
- }, {
1108
- key: "addBehavior",
1109
- value: function addBehavior(behaviorDef) {
1110
- var _this$sceneViewer5, _this$sceneViewer6;
1111
- var bm = (_this$sceneViewer5 = this.sceneViewer) === null || _this$sceneViewer5 === void 0 || (_this$sceneViewer5 = _this$sceneViewer5.managers) === null || _this$sceneViewer5 === void 0 ? void 0 : _this$sceneViewer5.behaviorManager;
1112
- if (!bm) {
1113
- console.warn('⚠️ addBehavior(): BehaviorManager not available');
1114
- return false;
1115
- }
1116
- // Also persist into currentSceneData so export includes it
1117
- if ((_this$sceneViewer6 = this.sceneViewer) !== null && _this$sceneViewer6 !== void 0 && _this$sceneViewer6.currentSceneData) {
1118
- if (!Array.isArray(this.sceneViewer.currentSceneData.behaviors)) {
1119
- this.sceneViewer.currentSceneData.behaviors = [];
1120
- }
1121
- this.sceneViewer.currentSceneData.behaviors.push(behaviorDef);
1122
- }
1123
- return bm.addBehavior(behaviorDef);
1124
- }
1125
-
1126
- /**
1127
- * Remove a behavior definition by id.
1128
- * @param {string} behaviorId
1129
- * @returns {boolean} True if removed, false if not found
1130
- * @example
1131
- * centralPlant.removeBehavior('my-behavior')
1132
- */
1133
- }, {
1134
- key: "removeBehavior",
1135
- value: function removeBehavior(behaviorId) {
1136
- var _this$sceneViewer7, _this$sceneViewer8;
1137
- var bm = (_this$sceneViewer7 = this.sceneViewer) === null || _this$sceneViewer7 === void 0 || (_this$sceneViewer7 = _this$sceneViewer7.managers) === null || _this$sceneViewer7 === void 0 ? void 0 : _this$sceneViewer7.behaviorManager;
1138
- if (!bm) {
1139
- console.warn('⚠️ removeBehavior(): BehaviorManager not available');
1140
- return false;
1141
- }
1142
- // Also remove from currentSceneData
1143
- if ((_this$sceneViewer8 = this.sceneViewer) !== null && _this$sceneViewer8 !== void 0 && (_this$sceneViewer8 = _this$sceneViewer8.currentSceneData) !== null && _this$sceneViewer8 !== void 0 && _this$sceneViewer8.behaviors) {
1144
- var idx = this.sceneViewer.currentSceneData.behaviors.findIndex(function (b) {
1145
- return b.id === behaviorId;
1146
- });
1147
- if (idx !== -1) this.sceneViewer.currentSceneData.behaviors.splice(idx, 1);
1148
- }
1149
- return bm.removeBehavior(behaviorId);
1150
- }
1151
-
1152
- /**
1153
- * Simulate an IO device state value arriving and trigger any matching behaviors.
1154
- * Useful for live testing in the UI or for integration with real data feeds.
1155
- * @param {string} attachmentId - The attachment id of the input io-device
1156
- * @param {string} stateId - The state id on that device
1157
- * @param {*} value - The new state value
1158
- * @example
1159
- * centralPlant.triggerState('pump-push-button-01', 'power', true)
1160
- */
1161
- }, {
1162
- key: "triggerState",
1163
- value: function triggerState(attachmentId, stateId, value, parentUuid) {
1164
- var _this$sceneViewer9;
1165
- var bm = (_this$sceneViewer9 = this.sceneViewer) === null || _this$sceneViewer9 === void 0 || (_this$sceneViewer9 = _this$sceneViewer9.managers) === null || _this$sceneViewer9 === void 0 ? void 0 : _this$sceneViewer9.behaviorManager;
1166
- if (!bm) {
1167
- console.warn('⚠️ triggerState(): BehaviorManager not available');
1168
- return;
1169
- }
1170
- bm.triggerState(attachmentId, stateId, value, parentUuid);
1171
- }
1172
-
1173
1072
  /**
1174
1073
  * Set the state of an I/O device instance in the Three.js scene.
1175
1074
  *
@@ -1202,15 +1101,9 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
1202
1101
  }, {
1203
1102
  key: "setIoDeviceState",
1204
1103
  value: function setIoDeviceState(attachmentId, stateId, value, parentUuid) {
1205
- var _this$sceneViewer0, _this$sceneViewer1, _this$sceneViewer10;
1206
- var bm = (_this$sceneViewer0 = this.sceneViewer) === null || _this$sceneViewer0 === void 0 || (_this$sceneViewer0 = _this$sceneViewer0.managers) === null || _this$sceneViewer0 === void 0 ? void 0 : _this$sceneViewer0.behaviorManager;
1207
- if (!bm) {
1208
- console.warn('⚠️ setIoDeviceState(): BehaviorManager not available');
1209
- return false;
1210
- }
1211
-
1104
+ var _this$sceneViewer4, _this$sceneViewer5, _this$sceneViewer6;
1212
1105
  // 1. Persist via state adapter if one has been configured
1213
- var stateAdapter = (_this$sceneViewer1 = this.sceneViewer) === null || _this$sceneViewer1 === void 0 || (_this$sceneViewer1 = _this$sceneViewer1.managers) === null || _this$sceneViewer1 === void 0 || (_this$sceneViewer1 = _this$sceneViewer1.componentTooltipManager) === null || _this$sceneViewer1 === void 0 ? void 0 : _this$sceneViewer1._stateAdapter;
1106
+ var stateAdapter = (_this$sceneViewer4 = this.sceneViewer) === null || _this$sceneViewer4 === void 0 || (_this$sceneViewer4 = _this$sceneViewer4.managers) === null || _this$sceneViewer4 === void 0 || (_this$sceneViewer4 = _this$sceneViewer4.componentTooltipManager) === null || _this$sceneViewer4 === void 0 ? void 0 : _this$sceneViewer4._stateAdapter;
1214
1107
  if (stateAdapter !== null && stateAdapter !== void 0 && stateAdapter.setState) {
1215
1108
  var scopedKey = parentUuid ? "".concat(parentUuid, "::").concat(attachmentId) : attachmentId;
1216
1109
  try {
@@ -1220,11 +1113,11 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
1220
1113
  }
1221
1114
  }
1222
1115
 
1223
- // 2. Apply Three.js behavior changes
1224
- bm.triggerState(attachmentId, stateId, value, parentUuid);
1116
+ // 2. Apply io-animation changes
1117
+ (_this$sceneViewer5 = this.sceneViewer) === null || _this$sceneViewer5 === void 0 || (_this$sceneViewer5 = _this$sceneViewer5.managers) === null || _this$sceneViewer5 === void 0 || (_this$sceneViewer5 = _this$sceneViewer5.ioAnimationManager) === null || _this$sceneViewer5 === void 0 || _this$sceneViewer5.triggerState(attachmentId, stateId, value, parentUuid);
1225
1118
 
1226
1119
  // 3. Emit event for host apps that don't use the state adapter (e.g. cp3d-viewer)
1227
- (_this$sceneViewer10 = this.sceneViewer) === null || _this$sceneViewer10 === void 0 || _this$sceneViewer10.emit('io-device-state-changed', {
1120
+ (_this$sceneViewer6 = this.sceneViewer) === null || _this$sceneViewer6 === void 0 || _this$sceneViewer6.emit('io-device-state-changed', {
1228
1121
  attachmentId: attachmentId,
1229
1122
  stateId: stateId,
1230
1123
  value: value,
@@ -1246,8 +1139,8 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
1246
1139
  }, {
1247
1140
  key: "getSceneAttachments",
1248
1141
  value: function getSceneAttachments() {
1249
- var _this$sceneViewer11;
1250
- var scene = (_this$sceneViewer11 = this.sceneViewer) === null || _this$sceneViewer11 === void 0 ? void 0 : _this$sceneViewer11.scene;
1142
+ var _this$sceneViewer7;
1143
+ var scene = (_this$sceneViewer7 = this.sceneViewer) === null || _this$sceneViewer7 === void 0 ? void 0 : _this$sceneViewer7.scene;
1251
1144
  if (!scene) return [];
1252
1145
  var results = [];
1253
1146
  scene.traverse(function (obj) {
@@ -19,7 +19,6 @@ var environmentManager = require('../managers/environment/environmentManager.js'
19
19
  var keyboardControlsManager = require('../managers/controls/keyboardControlsManager.js');
20
20
  var pathfindingManager = require('../managers/pathfinding/pathfindingManager.js');
21
21
  var PathFlowManager = require('../managers/pathfinding/PathFlowManager.js');
22
- var BehaviorManager = require('../managers/behaviors/BehaviorManager.js');
23
22
  var sceneOperationsManager = require('../managers/scene/sceneOperationsManager.js');
24
23
  var animationManager = require('../managers/scene/animationManager.js');
25
24
  var cameraControlsManager = require('../managers/controls/cameraControlsManager.js');
@@ -28,8 +27,10 @@ var sceneTooltipsManager = require('../managers/scene/sceneTooltipsManager.js');
28
27
  var componentTooltipManager = require('../managers/scene/componentTooltipManager.js');
29
28
  var viewport2DManager = require('../managers/scene/viewport2DManager.js');
30
29
  var IoAnimationManager = require('../managers/behaviors/IoAnimationManager.js');
30
+ var IoOutlineManager = require('../managers/behaviors/IoOutlineManager.js');
31
31
  var nameUtils = require('../utils/nameUtils.js');
32
32
  var ioDeviceUtils = require('../utils/ioDeviceUtils.js');
33
+ var boundingBoxUtils = require('../utils/boundingBoxUtils.js');
33
34
  var modelPreloader = require('../rendering/modelPreloader.js');
34
35
 
35
36
  function _interopNamespace(e) {
@@ -167,13 +168,13 @@ var CentralPlantInternals = /*#__PURE__*/function () {
167
168
  this.centralPlant.managers.keyboardControlsManager = new keyboardControlsManager.KeyboardControlsManager(this.centralPlant.sceneViewer);
168
169
  this.centralPlant.managers.pathfindingManager = new pathfindingManager.PathfindingManager(this.centralPlant.sceneViewer);
169
170
  this.centralPlant.managers.pathFlowManager = new PathFlowManager.PathFlowManager(this.centralPlant.sceneViewer);
170
- this.centralPlant.managers.behaviorManager = new BehaviorManager.BehaviorManager(this.centralPlant.sceneViewer);
171
171
  this.centralPlant.managers.sceneOperationsManager = new sceneOperationsManager.SceneOperationsManager(this.centralPlant.sceneViewer);
172
172
  this.centralPlant.managers.animationManager = new animationManager.AnimationManager(this.centralPlant.sceneViewer);
173
173
  this.centralPlant.managers.cameraControlsManager = new cameraControlsManager.CameraControlsManager(this.centralPlant.sceneViewer);
174
174
  this.centralPlant.managers.componentDragManager = new componentDragManager.ComponentDragManager(this.centralPlant.sceneViewer);
175
175
  this.centralPlant.managers.viewport2DManager = new viewport2DManager.Viewport2DManager(this.centralPlant.sceneViewer);
176
176
  this.centralPlant.managers.ioAnimationManager = new IoAnimationManager.IoAnimationManager(this.centralPlant.sceneViewer);
177
+ this.centralPlant.managers.ioOutlineManager = new IoOutlineManager.IoOutlineManager(this.centralPlant.sceneViewer);
177
178
 
178
179
  // All managers are now stored in the managers collection and will be attached via attachToComponent()
179
180
  }
@@ -944,7 +945,7 @@ var CentralPlantInternals = /*#__PURE__*/function () {
944
945
  return false;
945
946
  }
946
947
  try {
947
- var _componentData$childr, _componentData$childr2, _this$centralPlant$sc7, _componentData$childr3, _componentData$defaul;
948
+ var _componentData$childr, _componentData$childr2, _this$centralPlant$sc7, _componentData$childr3;
948
949
  // Generate a unique component ID if not provided
949
950
  var componentId = options.customId || this.generateUniqueComponentId(libraryId);
950
951
 
@@ -1176,20 +1177,25 @@ var CentralPlantInternals = /*#__PURE__*/function () {
1176
1177
  }
1177
1178
  }
1178
1179
 
1179
- // Register default behaviors for smart components so the BehaviorManager
1180
- // responds to tooltip-driven state changes immediately after drop.
1181
- // (The scene-load path uses _processBehaviors instead, which runs on loadSceneData.)
1182
- if ((_componentData$defaul = componentData.defaultBehaviors) !== null && _componentData$defaul !== void 0 && _componentData$defaul.length) {
1183
- var _this$centralPlant$sc9, _som$registerBehavior;
1184
- var som = (_this$centralPlant$sc9 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc9 === void 0 ? void 0 : _this$centralPlant$sc9.sceneOperationsManager;
1185
- som === null || som === void 0 || (_som$registerBehavior = som.registerBehaviorsForComponentInstance) === null || _som$registerBehavior === void 0 || _som$registerBehavior.call(som, componentData, componentId);
1186
- }
1187
-
1188
1180
  // Notify the component manager about the new component
1189
1181
  if (componentManager.registerComponent) {
1190
1182
  componentManager.registerComponent(componentModel);
1191
1183
  }
1192
1184
 
1185
+ // Pre-warm the filtered bounding-box cache for smart components so the
1186
+ // first selection is instant. Deferred to idle time so it does not
1187
+ // block the current frame.
1188
+ if (componentData.isSmart) {
1189
+ var warmFn = function warmFn() {
1190
+ return boundingBoxUtils.computeFilteredBoundingBoxCached(componentModel, ['io-device', 'connector']);
1191
+ };
1192
+ if (typeof requestIdleCallback !== 'undefined') {
1193
+ requestIdleCallback(warmFn);
1194
+ } else {
1195
+ setTimeout(warmFn, 0);
1196
+ }
1197
+ }
1198
+
1193
1199
  // EMIT COMPONENT ADDED EVENT
1194
1200
  // This allows UI components (like SceneHierarchy) to update reactively
1195
1201
  if (this.centralPlant.sceneViewer.emit) {
@@ -1243,18 +1249,18 @@ var CentralPlantInternals = /*#__PURE__*/function () {
1243
1249
  }, {
1244
1250
  key: "deleteComponent",
1245
1251
  value: function deleteComponent(componentId) {
1246
- var _this$centralPlant$sc0;
1252
+ var _this$centralPlant$sc9;
1247
1253
  // Check if component manager is available
1248
- var componentManager = (_this$centralPlant$sc0 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc0 === void 0 ? void 0 : _this$centralPlant$sc0.componentManager;
1254
+ var componentManager = (_this$centralPlant$sc9 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc9 === void 0 ? void 0 : _this$centralPlant$sc9.componentManager;
1249
1255
  if (!componentManager) {
1250
1256
  console.error('❌ deleteComponent(): Component manager not available');
1251
1257
  return false;
1252
1258
  }
1253
1259
  try {
1254
- var _this$centralPlant$sc1, _this$centralPlant$sc10, _sceneData$scene2, _sceneData$scene3;
1260
+ var _this$centralPlant$sc0, _this$centralPlant$sc1, _sceneData$scene2, _sceneData$scene3;
1255
1261
  console.log("\uD83D\uDDD1\uFE0F deleteComponent(): Deleting component ".concat(componentId));
1256
- var threeScene = (_this$centralPlant$sc1 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc1 === void 0 ? void 0 : _this$centralPlant$sc1.scene;
1257
- var sceneData = (_this$centralPlant$sc10 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc10 === void 0 ? void 0 : _this$centralPlant$sc10.currentSceneData;
1262
+ var threeScene = (_this$centralPlant$sc0 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc0 === void 0 ? void 0 : _this$centralPlant$sc0.scene;
1263
+ var sceneData = (_this$centralPlant$sc1 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc1 === void 0 ? void 0 : _this$centralPlant$sc1.currentSceneData;
1258
1264
 
1259
1265
  // Step 1: Resolve the actual Three.js UUID from componentId.
1260
1266
  // The UI emits object.name (e.g. "Pump (PUMP-1)") as the selection ID, but
@@ -102,7 +102,7 @@ var sceneViewer = /*#__PURE__*/function (_BaseDisposable) {
102
102
  this.centralPlant.attachToComponent();
103
103
 
104
104
  // Sync our managers tracking object after attachment
105
- managerKeys = ['threeJSResourceManager', 'performanceMonitorManager', 'settingsManager', 'sceneExportManager', 'componentManager', 'sceneInitializationManager', 'environmentManager', 'keyboardControlsManager', 'pathfindingManager', 'pathFlowManager', 'behaviorManager', 'ioAnimationManager', 'sceneOperationsManager', 'animationManager', 'cameraControlsManager', 'componentDragManager', 'tooltipsManager', 'componentTooltipManager']; // Populate our managers tracking object
105
+ managerKeys = ['threeJSResourceManager', 'performanceMonitorManager', 'settingsManager', 'sceneExportManager', 'componentManager', 'sceneInitializationManager', 'environmentManager', 'keyboardControlsManager', 'pathfindingManager', 'pathFlowManager', 'ioAnimationManager', 'ioOutlineManager', 'sceneOperationsManager', 'animationManager', 'cameraControlsManager', 'componentDragManager', 'tooltipsManager', 'componentTooltipManager']; // Populate our managers tracking object
106
106
  managerKeys.forEach(function (key) {
107
107
  if (_this2[key]) {
108
108
  _this2.managers[key] = _this2[key];
@@ -290,7 +290,21 @@ var sceneViewer = /*#__PURE__*/function (_BaseDisposable) {
290
290
  }
291
291
 
292
292
  // Update camera aspect ratio
293
- this.camera.aspect = width / height;
293
+ if (this.camera.isPerspectiveCamera) {
294
+ this.camera.aspect = width / height;
295
+ } else if (this.camera.isOrthographicCamera) {
296
+ var _this$camera$userData;
297
+ var aspect = width / height;
298
+ var extents = (_this$camera$userData = this.camera.userData) === null || _this$camera$userData === void 0 ? void 0 : _this$camera$userData._orthoHalfExtents;
299
+ if (extents) {
300
+ var frustumHalfH = Math.max(extents.halfH, extents.halfW / aspect);
301
+ var frustumHalfW = frustumHalfH * aspect;
302
+ this.camera.left = -frustumHalfW;
303
+ this.camera.right = frustumHalfW;
304
+ this.camera.top = frustumHalfH;
305
+ this.camera.bottom = -frustumHalfH;
306
+ }
307
+ }
294
308
  this.camera.updateProjectionMatrix();
295
309
 
296
310
  // Update renderer size (updateStyle=true to sync canvas CSS)
@@ -417,6 +431,45 @@ var sceneViewer = /*#__PURE__*/function (_BaseDisposable) {
417
431
  if (_this4.componentTooltipManager) {
418
432
  _this4.componentTooltipManager.toggleIODeviceBinaryState(ioDeviceObject);
419
433
  }
434
+ },
435
+ onIODeviceDrag: function onIODeviceDrag(ioDeviceObject, signedDelta, isStart) {
436
+ if (isStart) {
437
+ var _ioDeviceObject$userD, _this4$managers$ioAni, _this4$managers, _this4$managers2;
438
+ // Resolve parentUuid by walking up to the host component.
439
+ // Use userData.originalUuid (the custom componentId) because that
440
+ // is what IoAnimationManager uses as the map key — NOT obj.uuid.
441
+ var parentUuid = null;
442
+ var obj = ioDeviceObject.parent;
443
+ while (obj) {
444
+ var _obj$userData;
445
+ if (((_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.objectType) === 'component') {
446
+ parentUuid = obj.userData.originalUuid || obj.uuid;
447
+ break;
448
+ }
449
+ obj = obj.parent;
450
+ }
451
+ var attachmentId = (_ioDeviceObject$userD = ioDeviceObject.userData) === null || _ioDeviceObject$userD === void 0 ? void 0 : _ioDeviceObject$userD.attachmentId;
452
+ // When animated meshes are available, outline ONLY them so their
453
+ // silhouette is isolated and the outline ring is visible around
454
+ // them specifically (not swallowed by the larger device body).
455
+ // Fall back to the whole device group when none are registered.
456
+ var animatedMeshes = attachmentId && parentUuid ? (_this4$managers$ioAni = (_this4$managers = _this4.managers) === null || _this4$managers === void 0 || (_this4$managers = _this4$managers.ioAnimationManager) === null || _this4$managers === void 0 ? void 0 : _this4$managers.getAnimatedMeshes(parentUuid, attachmentId)) !== null && _this4$managers$ioAni !== void 0 ? _this4$managers$ioAni : [] : [];
457
+ var targets = animatedMeshes.length > 0 ? animatedMeshes : [ioDeviceObject];
458
+ (_this4$managers2 = _this4.managers) === null || _this4$managers2 === void 0 || (_this4$managers2 = _this4$managers2.ioOutlineManager) === null || _this4$managers2 === void 0 || _this4$managers2.setTargets(targets);
459
+ }
460
+ if (!_this4.componentTooltipManager) return;
461
+ if (isStart) {
462
+ _this4.componentTooltipManager.startIODeviceDrag(ioDeviceObject);
463
+ } else {
464
+ _this4.componentTooltipManager.updateIODeviceDrag(signedDelta);
465
+ }
466
+ },
467
+ onIODeviceDragEnd: function onIODeviceDragEnd(ioDeviceObject) {
468
+ var _this4$managers3;
469
+ (_this4$managers3 = _this4.managers) === null || _this4$managers3 === void 0 || (_this4$managers3 = _this4$managers3.ioOutlineManager) === null || _this4$managers3 === void 0 || _this4$managers3.setTargets([]);
470
+ if (_this4.componentTooltipManager) {
471
+ _this4.componentTooltipManager.endIODeviceDrag();
472
+ }
420
473
  }
421
474
  });
422
475
 
@@ -13,7 +13,6 @@ var sceneExportManager = require('./managers/scene/sceneExportManager.js');
13
13
  var sceneTooltipsManager = require('./managers/scene/sceneTooltipsManager.js');
14
14
  var componentTooltipManager = require('./managers/scene/componentTooltipManager.js');
15
15
  var sceneHierarchyManager = require('./managers/scene/sceneHierarchyManager.js');
16
- var BehaviorManager = require('./managers/behaviors/BehaviorManager.js');
17
16
  var IoAnimationManager = require('./managers/behaviors/IoAnimationManager.js');
18
17
  var componentManager = require('./managers/components/componentManager.js');
19
18
  var animationManager = require('./managers/scene/animationManager.js');
@@ -68,7 +67,6 @@ exports.SceneExportManager = sceneExportManager.SceneExportManager;
68
67
  exports.SceneTooltipsManager = sceneTooltipsManager.SceneTooltipsManager;
69
68
  exports.ComponentTooltipManager = componentTooltipManager.ComponentTooltipManager;
70
69
  exports.SceneHierarchyManager = sceneHierarchyManager.SceneHierarchyManager;
71
- exports.BehaviorManager = BehaviorManager.BehaviorManager;
72
70
  exports.IoAnimationManager = IoAnimationManager.IoAnimationManager;
73
71
  exports.ComponentManager = componentManager.ComponentManager;
74
72
  exports.AnimationManager = animationManager.AnimationManager;
@@ -243,6 +243,26 @@ var IoAnimationManager = /*#__PURE__*/function (_BaseDisposable) {
243
243
  return dps;
244
244
  }
245
245
 
246
+ /**
247
+ * Return the Three.js mesh objects that are animated for a given attachment.
248
+ * Used by IoOutlineManager to include animated meshes in the outline.
249
+ *
250
+ * @param {string} parentUuid
251
+ * @param {string} attachmentId
252
+ * @returns {THREE.Object3D[]}
253
+ */
254
+ }, {
255
+ key: "getAnimatedMeshes",
256
+ value: function getAnimatedMeshes(parentUuid, attachmentId) {
257
+ var key = this._key(parentUuid, attachmentId);
258
+ var entries = this._entries.get(key);
259
+ if (!(entries !== null && entries !== void 0 && entries.length)) return [];
260
+ // Deduplicate — multiple animations can target the same mesh
261
+ return _rollupPluginBabelHelpers.toConsumableArray(new Set(entries.map(function (e) {
262
+ return e.mesh;
263
+ })));
264
+ }
265
+
246
266
  /**
247
267
  * Remove all animation entries associated with a given host component.
248
268
  * Call when a component is removed from the scene.
@@ -474,7 +494,10 @@ var IoAnimationManager = /*#__PURE__*/function (_BaseDisposable) {
474
494
  value: function _applyTranslation(mesh, origPos, transform) {
475
495
  var _transform$x, _transform$y, _transform$z;
476
496
  if (!transform) return;
477
- mesh.position.set(origPos.x + ((_transform$x = transform.x) !== null && _transform$x !== void 0 ? _transform$x : 0), origPos.y + ((_transform$y = transform.y) !== null && _transform$y !== void 0 ? _transform$y : 0), origPos.z + ((_transform$z = transform.z) !== null && _transform$z !== void 0 ? _transform$z : 0));
497
+ // X and Y are negated to match the sign convention used in the AnimateDevicesDialog
498
+ // preview (_syncViewerTransform negates x and y before calling setMeshPreviewOffset).
499
+ // Z is added directly (no negation) — matching the dialog's z handling.
500
+ mesh.position.set(origPos.x - ((_transform$x = transform.x) !== null && _transform$x !== void 0 ? _transform$x : 0), origPos.y - ((_transform$y = transform.y) !== null && _transform$y !== void 0 ? _transform$y : 0), origPos.z + ((_transform$z = transform.z) !== null && _transform$z !== void 0 ? _transform$z : 0));
478
501
  }
479
502
 
480
503
  /**
@@ -0,0 +1,258 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var _rollupPluginBabelHelpers = require('../../../_virtual/_rollupPluginBabelHelpers.js');
6
+ var THREE = require('three');
7
+ var baseDisposable = require('../../core/baseDisposable.js');
8
+
9
+ function _interopNamespace(e) {
10
+ if (e && e.__esModule) return e;
11
+ var n = Object.create(null);
12
+ if (e) {
13
+ Object.keys(e).forEach(function (k) {
14
+ if (k !== 'default') {
15
+ var d = Object.getOwnPropertyDescriptor(e, k);
16
+ Object.defineProperty(n, k, d.get ? d : {
17
+ enumerable: true,
18
+ get: function () { return e[k]; }
19
+ });
20
+ }
21
+ });
22
+ }
23
+ n["default"] = e;
24
+ return Object.freeze(n);
25
+ }
26
+
27
+ var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
28
+
29
+ /**
30
+ * Three.js layer reserved exclusively for the outline mask pass.
31
+ * Layer 20 is high enough to be well clear of typical app layer usage.
32
+ */
33
+ var OUTLINE_LAYER = 20;
34
+
35
+ // ── Shaders ──────────────────────────────────────────────────────────────────
36
+
37
+ var VERT_SHADER = /* glsl */"\nvarying vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position.xy, 0.0, 1.0);\n}\n";
38
+
39
+ /**
40
+ * Screen-space outline shader.
41
+ *
42
+ * Samples in a circular kernel to find the nearest filled (silhouette) pixel.
43
+ * Applies a smooth alpha falloff at the outer edge using smoothstep so the
44
+ * outline fades cleanly rather than cutting off hard.
45
+ *
46
+ * RADIUS — maximum distance in pixels from the silhouette edge to draw.
47
+ * SAMPLES — kernel half-extent; must be an integer ≥ ceil(RADIUS).
48
+ */
49
+ var FRAG_SHADER = /* glsl */"\nuniform sampler2D tMask;\nuniform vec2 uInvSize;\nvarying vec2 vUv;\n\nconst float RADIUS = 3.0; // total outline width in pixels\nconst int SAMPLES = 4; // kernel half-extent (\u2265 ceil(RADIUS))\n\nvoid main() {\n float center = texture2D(tMask, vUv).r;\n\n // Pixels inside the silhouette: the main render already drew them.\n if (center > 0.5) discard;\n\n // Find the closest filled neighbour within the circular kernel.\n float minDist = 99.0;\n for (int x = -SAMPLES; x <= SAMPLES; x++) {\n for (int y = -SAMPLES; y <= SAMPLES; y++) {\n float dist = length(vec2(float(x), float(y)));\n if (dist > RADIUS + 1.0) continue; // skip corners outside circle\n vec2 offset = vec2(float(x), float(y)) * uInvSize;\n if (texture2D(tMask, vUv + offset).r > 0.5) {\n minDist = min(minDist, dist);\n }\n }\n }\n\n if (minDist > RADIUS + 0.5) discard;\n\n // Smooth alpha: full opacity near the edge, fades to 0 at RADIUS pixels out.\n float alpha = 1.0 - smoothstep(RADIUS - 1.0, RADIUS + 0.5, minDist);\n gl_FragColor = vec4(1.0, 1.0, 1.0, alpha);\n}\n";
50
+ var IoOutlineManager = /*#__PURE__*/function (_BaseDisposable) {
51
+ function IoOutlineManager(sceneViewer) {
52
+ var _this;
53
+ _rollupPluginBabelHelpers.classCallCheck(this, IoOutlineManager);
54
+ _this = _rollupPluginBabelHelpers.callSuper(this, IoOutlineManager);
55
+ _this.sceneViewer = sceneViewer;
56
+ _this._maskTarget = null;
57
+ _this._maskMat = null;
58
+ _this._overlayScene = null;
59
+ _this._overlayCamera = null;
60
+ _this._overlayMat = null;
61
+
62
+ /**
63
+ * Meshes that have been moved onto OUTLINE_LAYER, stored with their
64
+ * original layers.mask so they can be restored on clearance.
65
+ * @type {{ mesh: THREE.Mesh, originalMask: number }[]}
66
+ */
67
+ _this._layeredMeshes = [];
68
+
69
+ /** @type {boolean} Whether an outline is currently active. */
70
+ _this.isActive = false;
71
+ return _this;
72
+ }
73
+
74
+ // ─────────────────────────────────────────────────────────────────────────
75
+ // PUBLIC API
76
+ // ─────────────────────────────────────────────────────────────────────────
77
+
78
+ /**
79
+ * Set the objects to outline. Pass an empty array (or call with no args)
80
+ * to remove the outline.
81
+ * @param {THREE.Object3D[]} objects
82
+ */
83
+ _rollupPluginBabelHelpers.inherits(IoOutlineManager, _BaseDisposable);
84
+ return _rollupPluginBabelHelpers.createClass(IoOutlineManager, [{
85
+ key: "setTargets",
86
+ value: function setTargets() {
87
+ var _this2 = this;
88
+ var objects = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
89
+ this._clearLayeredMeshes();
90
+ if (!objects.length) {
91
+ this.isActive = false;
92
+ return;
93
+ }
94
+ objects.forEach(function (obj) {
95
+ obj.traverse(function (child) {
96
+ if (!child.isMesh) return;
97
+ var originalMask = child.layers.mask;
98
+ child.layers.enable(OUTLINE_LAYER);
99
+ _this2._layeredMeshes.push({
100
+ mesh: child,
101
+ originalMask: originalMask
102
+ });
103
+ });
104
+ });
105
+ this.isActive = this._layeredMeshes.length > 0;
106
+ }
107
+
108
+ /**
109
+ * Render one frame: main scene → mask pass → outline composite.
110
+ * Must be called instead of renderer.render() when isActive is true.
111
+ */
112
+ }, {
113
+ key: "render",
114
+ value: function render() {
115
+ var sv = this.sceneViewer;
116
+ var renderer = sv.renderer,
117
+ scene = sv.scene,
118
+ camera = sv.camera;
119
+ if (!renderer || !scene || !camera) return;
120
+
121
+ // ── Step 1: Normal scene render — sky, transparency, all unaffected ──
122
+ renderer.render(scene, camera);
123
+ if (!this.isActive) return;
124
+ this._ensureResources();
125
+ if (!this._maskTarget) return;
126
+
127
+ // Stash renderer / scene / camera state we are about to mutate
128
+ var prevBg = scene.background;
129
+ var prevOverride = scene.overrideMaterial;
130
+ var prevLayerMask = camera.layers.mask;
131
+ var prevAutoClear = renderer.autoClear;
132
+ var prevClearClr = renderer.getClearColor(new THREE__namespace.Color());
133
+ var prevClearA = renderer.getClearAlpha();
134
+
135
+ // ── Step 2: Render the device silhouette into the private mask target ──
136
+ scene.background = null; // transparent clear
137
+ scene.overrideMaterial = this._maskMat; // flat white for all geometry
138
+ camera.layers.set(OUTLINE_LAYER); // only see the device meshes
139
+ renderer.setClearColor(0x000000, 0);
140
+ renderer.autoClear = true;
141
+ renderer.setRenderTarget(this._maskTarget);
142
+ renderer.render(scene, camera);
143
+ renderer.setRenderTarget(null);
144
+
145
+ // Restore mutated state
146
+ scene.background = prevBg;
147
+ scene.overrideMaterial = prevOverride;
148
+ camera.layers.mask = prevLayerMask;
149
+ renderer.setClearColor(prevClearClr, prevClearA);
150
+
151
+ // ── Step 3: Composite the outline on top without clearing the framebuffer
152
+ renderer.autoClear = false;
153
+ renderer.render(this._overlayScene, this._overlayCamera);
154
+ renderer.autoClear = prevAutoClear;
155
+ }
156
+
157
+ /**
158
+ * Call when the canvas is resized to keep the mask target and shader in sync.
159
+ * @param {number} width
160
+ * @param {number} height
161
+ */
162
+ }, {
163
+ key: "setSize",
164
+ value: function setSize(width, height) {
165
+ if (this._maskTarget) this._maskTarget.setSize(width, height);
166
+ if (this._overlayMat) {
167
+ this._overlayMat.uniforms.uInvSize.value.set(1 / width, 1 / height);
168
+ }
169
+ }
170
+ }, {
171
+ key: "dispose",
172
+ value: function dispose() {
173
+ this._clearLayeredMeshes();
174
+ if (this._maskTarget) {
175
+ this._maskTarget.dispose();
176
+ this._maskTarget = null;
177
+ }
178
+ if (this._maskMat) {
179
+ this._maskMat.dispose();
180
+ this._maskMat = null;
181
+ }
182
+ if (this._overlayMat) {
183
+ this._overlayMat.dispose();
184
+ this._overlayMat = null;
185
+ }
186
+ _rollupPluginBabelHelpers.superPropGet(IoOutlineManager, "dispose", this, 3)([]);
187
+ }
188
+
189
+ // ─────────────────────────────────────────────────────────────────────────
190
+ // PRIVATE
191
+ // ─────────────────────────────────────────────────────────────────────────
192
+
193
+ /** Lazy-initialise GPU resources on first use. */
194
+ }, {
195
+ key: "_ensureResources",
196
+ value: function _ensureResources() {
197
+ if (this._maskTarget) return;
198
+ var renderer = this.sceneViewer.renderer;
199
+ if (!renderer) return;
200
+ var size = renderer.getSize(new THREE__namespace.Vector2());
201
+ var w = size.x;
202
+ var h = size.y;
203
+
204
+ // Private render target — receives the flat white device silhouette
205
+ this._maskTarget = new THREE__namespace.WebGLRenderTarget(w, h);
206
+
207
+ // Flat white material used during the mask pass
208
+ this._maskMat = new THREE__namespace.MeshBasicMaterial({
209
+ color: 0xffffff
210
+ });
211
+
212
+ // Screen-space overlay: reads the mask and draws only the outline pixels
213
+ this._overlayMat = new THREE__namespace.ShaderMaterial({
214
+ uniforms: {
215
+ tMask: {
216
+ value: this._maskTarget.texture
217
+ },
218
+ uInvSize: {
219
+ value: new THREE__namespace.Vector2(1 / w, 1 / h)
220
+ }
221
+ },
222
+ vertexShader: VERT_SHADER,
223
+ fragmentShader: FRAG_SHADER,
224
+ transparent: true,
225
+ depthTest: false,
226
+ depthWrite: false
227
+ });
228
+ var quad = new THREE__namespace.Mesh(new THREE__namespace.PlaneGeometry(2, 2), this._overlayMat);
229
+ quad.frustumCulled = false;
230
+ this._overlayScene = new THREE__namespace.Scene();
231
+ this._overlayScene.add(quad);
232
+ this._overlayCamera = new THREE__namespace.OrthographicCamera(-1, 1, 1, -1, 0, 1);
233
+ }
234
+
235
+ /** Remove OUTLINE_LAYER from all tracked meshes and clear the list. */
236
+ }, {
237
+ key: "_clearLayeredMeshes",
238
+ value: function _clearLayeredMeshes() {
239
+ var _iterator = _rollupPluginBabelHelpers.createForOfIteratorHelper(this._layeredMeshes),
240
+ _step;
241
+ try {
242
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
243
+ var _step$value = _step.value,
244
+ mesh = _step$value.mesh,
245
+ originalMask = _step$value.originalMask;
246
+ mesh.layers.mask = originalMask;
247
+ }
248
+ } catch (err) {
249
+ _iterator.e(err);
250
+ } finally {
251
+ _iterator.f();
252
+ }
253
+ this._layeredMeshes = [];
254
+ }
255
+ }]);
256
+ }(baseDisposable.BaseDisposable);
257
+
258
+ exports.IoOutlineManager = IoOutlineManager;