@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
@@ -737,13 +737,8 @@ var SceneOperationsManager = /*#__PURE__*/function () {
737
737
  phaseStart = performance.now();
738
738
  this._finalizeScene(data, crosscubeTextureSet, isImported);
739
739
  timers.phase5_finalize = performance.now() - phaseStart;
740
-
741
- // Phase 6: Load behaviors (after GLB models are present so output objects can be resolved)
742
- phaseStart = performance.now();
743
- this._processBehaviors(data);
744
- timers.phase6_behaviors = performance.now() - phaseStart;
745
740
  totalTime = performance.now() - totalStart;
746
- console.log("\u23F1\uFE0F Scene Loading Performance:\n Phase 1 (Prepare) : ".concat(timers.phase1_prepare.toFixed(0), "ms\n Phase 2 (Create Objects): ").concat(timers.phase2_createObjects.toFixed(0), "ms\n Phase 3 (GLB Models) : ").concat(timers.phase3_glbModels.toFixed(0), "ms\n Phase 4 (Pathfinding) : ").concat(timers.phase4_pathfinding.toFixed(0), "ms\n Phase 5 (Finalize) : ").concat(timers.phase5_finalize.toFixed(0), "ms\n Phase 6 (Behaviors) : ").concat(timers.phase6_behaviors.toFixed(0), "ms\n \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Total : ").concat(totalTime.toFixed(0), "ms"));
741
+ console.log("\u23F1\uFE0F Scene Loading Performance:\n Phase 1 (Prepare) : ".concat(timers.phase1_prepare.toFixed(0), "ms\n Phase 2 (Create Objects): ").concat(timers.phase2_createObjects.toFixed(0), "ms\n Phase 3 (GLB Models) : ").concat(timers.phase3_glbModels.toFixed(0), "ms\n Phase 4 (Pathfinding) : ").concat(timers.phase4_pathfinding.toFixed(0), "ms\n Phase 5 (Finalize) : ").concat(timers.phase5_finalize.toFixed(0), "ms\n \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Total : ").concat(totalTime.toFixed(0), "ms"));
747
742
  console.log('✅ Scene loaded successfully');
748
743
 
749
744
  // Notify UI components (e.g. SceneHierarchy) that the scene is fully loaded
@@ -1101,278 +1096,6 @@ var SceneOperationsManager = /*#__PURE__*/function () {
1101
1096
  }
1102
1097
  }
1103
1098
 
1104
- /**
1105
- * Process behaviors from the scene data, expand any defaultBehaviors defined
1106
- * on component dictionary entries, resolve behaviorRef entries against device
1107
- * assets, inject per-instance component UUIDs, and hand the flat resolved
1108
- * array to BehaviorManager.
1109
- *
1110
- * Resolution cascade:
1111
- * 1. Explicit behaviors in data.behaviors — passed through as-is (behaviorRef
1112
- * entries are resolved against the component/device dictionaries).
1113
- * 2. defaultBehaviors[] on each placed smart component's dictionary entry —
1114
- * expanded per instance with the instance UUID injected. Compact
1115
- * behaviorRef-derived entries are tagged _isDefaultBehavior:true (the
1116
- * exporter can re-derive them); full L2 behavior objects are tagged false
1117
- * so they are exported verbatim and survive round-trips.
1118
- *
1119
- * @param {Object} data - Scene JSON data
1120
- */
1121
- }, {
1122
- key: "_processBehaviors",
1123
- value: function _processBehaviors(data) {
1124
- var _this$sceneViewer2,
1125
- _this$sceneViewer$cen2,
1126
- _this5 = this,
1127
- _data$scene3;
1128
- var behaviorManager = (_this$sceneViewer2 = this.sceneViewer) === null || _this$sceneViewer2 === void 0 || (_this$sceneViewer2 = _this$sceneViewer2.managers) === null || _this$sceneViewer2 === void 0 ? void 0 : _this$sceneViewer2.behaviorManager;
1129
- if (!behaviorManager) {
1130
- console.warn('⚠️ _processBehaviors: BehaviorManager not available');
1131
- return;
1132
- }
1133
-
1134
- // Obtain the component dictionary (extended = includes S3/smart components)
1135
- var componentDictionary = ((_this$sceneViewer$cen2 = this.sceneViewer.centralPlant) === null || _this$sceneViewer$cen2 === void 0 || (_this$sceneViewer$cen2 = _this$sceneViewer$cen2.managers) === null || _this$sceneViewer$cen2 === void 0 || (_this$sceneViewer$cen2 = _this$sceneViewer$cen2.componentDataManager) === null || _this$sceneViewer$cen2 === void 0 ? void 0 : _this$sceneViewer$cen2.componentDictionary) || {};
1136
-
1137
- // ── Step A: Resolve any behaviorRef entries from data.behaviors ─────────
1138
- var explicitBehaviors = [];
1139
- if (Array.isArray(data === null || data === void 0 ? void 0 : data.behaviors)) {
1140
- data.behaviors.forEach(function (entry) {
1141
- if (entry.behaviorRef) {
1142
- var resolved = _this5._resolveBehaviorRef(entry, componentDictionary);
1143
- if (resolved) explicitBehaviors.push(resolved);
1144
- } else {
1145
- explicitBehaviors.push(entry);
1146
- }
1147
- });
1148
- }
1149
-
1150
- // Build a set of explicit behavior ids for deduplication
1151
- var explicitIds = new Set(explicitBehaviors.map(function (b) {
1152
- return b.id;
1153
- }));
1154
-
1155
- // Build a set of (component::attachment::state) tuples covered by explicit
1156
- // behaviors. If an expanded default behavior would produce the SAME input
1157
- // tuple, the explicit behavior is treated as the intentional override and
1158
- // the default expansion is skipped. This prevents a component's built-in
1159
- // switch→LED wiring from doubling-up when the user has deliberately authored
1160
- // cross-component behaviors that re-wire the same switch.
1161
- var explicitInputTuples = new Set(explicitBehaviors.filter(function (b) {
1162
- var _b$input, _b$input2, _b$input3;
1163
- return ((_b$input = b.input) === null || _b$input === void 0 ? void 0 : _b$input.component) && ((_b$input2 = b.input) === null || _b$input2 === void 0 ? void 0 : _b$input2.attachment) && ((_b$input3 = b.input) === null || _b$input3 === void 0 ? void 0 : _b$input3.state);
1164
- }).map(function (b) {
1165
- return "".concat(b.input.component, "::").concat(b.input.attachment, "::").concat(b.input.state);
1166
- }));
1167
-
1168
- // Build a set of instanceUuids already covered by behaviorRef entries in
1169
- // data.behaviors (written by the exporter for smart component defaults).
1170
- // Step B skips these instances to avoid loading the same behaviors twice —
1171
- // once via Step A (resolved from data.behaviors refs) and once via Step B
1172
- // (auto-expanded from the component dictionary).
1173
- var coveredInstances = new Set();
1174
- if (Array.isArray(data === null || data === void 0 ? void 0 : data.behaviors)) {
1175
- data.behaviors.forEach(function (entry) {
1176
- if (entry.behaviorRef && entry.component) {
1177
- coveredInstances.add(entry.component);
1178
- }
1179
- });
1180
- }
1181
-
1182
- // ── Step B: Walk placed components and expand their defaultBehaviors ─────
1183
- var instanceBehaviors = [];
1184
- if (Array.isArray(data === null || data === void 0 || (_data$scene3 = data.scene) === null || _data$scene3 === void 0 ? void 0 : _data$scene3.children)) {
1185
- data.scene.children.forEach(function (child) {
1186
- var _child$userData0, _compData$defaultBeha;
1187
- var libraryId = (_child$userData0 = child.userData) === null || _child$userData0 === void 0 ? void 0 : _child$userData0.libraryId;
1188
- if (!libraryId) return;
1189
- var instanceUuid = child.uuid;
1190
- // Skip instances whose defaults were already resolved by Step A
1191
- // (they have explicit behaviorRef entries in data.behaviors).
1192
- if (coveredInstances.has(instanceUuid)) return;
1193
- var compData = componentDictionary[libraryId];
1194
- if (!(compData !== null && compData !== void 0 && (_compData$defaultBeha = compData.defaultBehaviors) !== null && _compData$defaultBeha !== void 0 && _compData$defaultBeha.length)) return;
1195
- compData.defaultBehaviors.forEach(function (template) {
1196
- var _expanded$input, _expanded$input2, _expanded$input3;
1197
- var expanded = _this5._expandDefaultBehavior(template, instanceUuid, componentDictionary);
1198
- if (!expanded) return;
1199
- // Skip if an explicit scene behavior already covers this id
1200
- if (explicitIds.has(expanded.id)) return;
1201
- // Skip if an explicit scene behavior already covers the same
1202
- // (component, attachment, state) input tuple. This prevents a
1203
- // component's built-in default wiring (e.g. switch → own LED) from
1204
- // double-firing when the user has authored a cross-component override
1205
- // that rewires the same switch to a different target.
1206
- if ((_expanded$input = expanded.input) !== null && _expanded$input !== void 0 && _expanded$input.component && (_expanded$input2 = expanded.input) !== null && _expanded$input2 !== void 0 && _expanded$input2.attachment && (_expanded$input3 = expanded.input) !== null && _expanded$input3 !== void 0 && _expanded$input3.state) {
1207
- var tuple = "".concat(expanded.input.component, "::").concat(expanded.input.attachment, "::").concat(expanded.input.state);
1208
- if (explicitInputTuples.has(tuple)) return;
1209
- }
1210
- // All component defaultBehaviors are re-derivable from the component
1211
- // asset at export time (via compact behaviorRef), so mark them all.
1212
- expanded._isDefaultBehavior = true;
1213
- instanceBehaviors.push(expanded);
1214
- });
1215
- });
1216
- }
1217
- var allBehaviors = [].concat(explicitBehaviors, instanceBehaviors);
1218
- if (allBehaviors.length === 0) return;
1219
- behaviorManager.loadBehaviors(allBehaviors);
1220
- console.log("\u2705 _processBehaviors: loaded ".concat(explicitBehaviors.length, " explicit + ").concat(instanceBehaviors.length, " default-expanded behavior(s)"));
1221
- }
1222
-
1223
- /**
1224
- * Register the defaultBehaviors of a single newly placed component instance
1225
- * into the BehaviorManager. Called from addComponent() (drag-drop), where
1226
- * _processBehaviors() (scene-load path) is not invoked.
1227
- *
1228
- * @param {Object} componentData - Entry from the component dictionary
1229
- * @param {string} instanceUuid - UUID of the placed component (componentModel.uuid)
1230
- */
1231
- }, {
1232
- key: "registerBehaviorsForComponentInstance",
1233
- value: function registerBehaviorsForComponentInstance(componentData, instanceUuid) {
1234
- var _this$sceneViewer3,
1235
- _componentData$defaul,
1236
- _this$sceneViewer$cen3,
1237
- _this6 = this;
1238
- var behaviorManager = (_this$sceneViewer3 = this.sceneViewer) === null || _this$sceneViewer3 === void 0 || (_this$sceneViewer3 = _this$sceneViewer3.managers) === null || _this$sceneViewer3 === void 0 ? void 0 : _this$sceneViewer3.behaviorManager;
1239
- if (!behaviorManager) return;
1240
- if (!(componentData !== null && componentData !== void 0 && (_componentData$defaul = componentData.defaultBehaviors) !== null && _componentData$defaul !== void 0 && _componentData$defaul.length)) return;
1241
- var componentDictionary = ((_this$sceneViewer$cen3 = this.sceneViewer.centralPlant) === null || _this$sceneViewer$cen3 === void 0 || (_this$sceneViewer$cen3 = _this$sceneViewer$cen3.managers) === null || _this$sceneViewer$cen3 === void 0 || (_this$sceneViewer$cen3 = _this$sceneViewer$cen3.componentDataManager) === null || _this$sceneViewer$cen3 === void 0 ? void 0 : _this$sceneViewer$cen3.componentDictionary) || {};
1242
- var registered = 0;
1243
- componentData.defaultBehaviors.forEach(function (template) {
1244
- var expanded = _this6._expandDefaultBehavior(template, instanceUuid, componentDictionary);
1245
- if (!expanded) return;
1246
- expanded._isDefaultBehavior = true;
1247
- behaviorManager.addBehavior(expanded);
1248
- registered++;
1249
- });
1250
- if (registered > 0) {
1251
- console.log("\u2705 registerBehaviorsForComponentInstance: registered ".concat(registered, " behavior(s) for instance ").concat(instanceUuid));
1252
- }
1253
- }
1254
-
1255
- /**
1256
- * Resolve a scene-level or component-level behaviorRef entry into a full
1257
- * behavior definition by looking up the referenced template in the device
1258
- * asset's defaultBehaviors[] and substituting 'self' with the real
1259
- * attachmentId.
1260
- *
1261
- * Ref shape: { behaviorRef, deviceId, attachment, component? }
1262
- *
1263
- * @param {Object} ref
1264
- * @param {Object} componentDictionary
1265
- * @returns {Object|null} Resolved behavior or null if not found
1266
- */
1267
- }, {
1268
- key: "_resolveBehaviorRef",
1269
- value: function _resolveBehaviorRef(ref, componentDictionary) {
1270
- var _resolved$input, _resolved$output;
1271
- if (!ref.behaviorRef) {
1272
- console.warn('⚠️ _resolveBehaviorRef: missing behaviorRef', ref);
1273
- return null;
1274
- }
1275
-
1276
- // ── Component-level L2 ref: { behaviorRef, libraryId, component } ──────
1277
- // The full behavior template lives on the smart component's defaultBehaviors[].
1278
- // No 'self' substitution needed — attachment IDs are already real.
1279
- if (ref.libraryId) {
1280
- var compData = componentDictionary[ref.libraryId];
1281
- if (!compData) {
1282
- console.warn("\u26A0\uFE0F _resolveBehaviorRef: component \"".concat(ref.libraryId, "\" not in dictionary"));
1283
- return null;
1284
- }
1285
- var _template = (compData.defaultBehaviors || []).find(function (b) {
1286
- return b.id === ref.behaviorRef;
1287
- });
1288
- if (!_template) {
1289
- console.warn("\u26A0\uFE0F _resolveBehaviorRef: behavior \"".concat(ref.behaviorRef, "\" not found on component \"").concat(ref.libraryId, "\""));
1290
- return null;
1291
- }
1292
- var _resolved = JSON.parse(JSON.stringify(_template));
1293
- _resolved.id = "".concat(_resolved.id, "::").concat(ref.component);
1294
- if (ref.component) {
1295
- _resolved.input = _objectSpread2(_objectSpread2({}, _resolved.input), {}, {
1296
- component: ref.component
1297
- });
1298
- _resolved.output = _objectSpread2(_objectSpread2({}, _resolved.output), {}, {
1299
- component: ref.component
1300
- });
1301
- }
1302
- return _resolved;
1303
- }
1304
-
1305
- // ── Device-level L1 ref: { behaviorRef, deviceId, attachment, component } ─
1306
- if (!ref.deviceId || !ref.attachment) {
1307
- console.warn('⚠️ _resolveBehaviorRef: incomplete ref', ref);
1308
- return null;
1309
- }
1310
- var deviceData = componentDictionary[ref.deviceId];
1311
- if (!deviceData) {
1312
- console.warn("\u26A0\uFE0F _resolveBehaviorRef: device \"".concat(ref.deviceId, "\" not in dictionary"));
1313
- return null;
1314
- }
1315
- var template = (deviceData.defaultBehaviors || []).find(function (b) {
1316
- return b.id === ref.behaviorRef;
1317
- });
1318
- if (!template) {
1319
- console.warn("\u26A0\uFE0F _resolveBehaviorRef: behavior \"".concat(ref.behaviorRef, "\" not found on device \"").concat(ref.deviceId, "\""));
1320
- return null;
1321
- }
1322
- // Deep clone and substitute 'self' -> real attachmentId
1323
- var resolved = JSON.parse(JSON.stringify(template));
1324
- resolved.id = "".concat(resolved.id, "::").concat(ref.attachment);
1325
- if (((_resolved$input = resolved.input) === null || _resolved$input === void 0 ? void 0 : _resolved$input.attachment) === 'self') resolved.input.attachment = ref.attachment;
1326
- if (((_resolved$output = resolved.output) === null || _resolved$output === void 0 ? void 0 : _resolved$output.attachment) === 'self') resolved.output.attachment = ref.attachment;
1327
- // Inject component UUID guard if the ref carries one
1328
- if (ref.component) {
1329
- resolved.input = _objectSpread2(_objectSpread2({}, resolved.input), {}, {
1330
- component: ref.component
1331
- });
1332
- resolved.output = _objectSpread2(_objectSpread2({}, resolved.output), {}, {
1333
- component: ref.component
1334
- });
1335
- }
1336
- return resolved;
1337
- }
1338
-
1339
- /**
1340
- * Expand a single defaultBehaviors[] template entry for a specific component
1341
- * instance placed in the scene.
1342
- *
1343
- * If the entry is a behaviorRef, it is first resolved against the device
1344
- * library; otherwise it is treated as a full behavior definition.
1345
- * In both cases the instanceUuid is injected as input.component and
1346
- * output.component, and the behavior id is suffixed with ::instanceUuid.
1347
- *
1348
- * @param {Object} template - Entry from compData.defaultBehaviors[]
1349
- * @param {string} instanceUuid - UUID of the placed component instance
1350
- * @param {Object} componentDictionary
1351
- * @returns {Object|null}
1352
- */
1353
- }, {
1354
- key: "_expandDefaultBehavior",
1355
- value: function _expandDefaultBehavior(template, instanceUuid, componentDictionary) {
1356
- var base;
1357
- if (template.behaviorRef) {
1358
- // Resolve the device ref first (substitutes 'self' -> attachment)
1359
- base = this._resolveBehaviorRef(template, componentDictionary);
1360
- if (!base) return null;
1361
- } else {
1362
- base = JSON.parse(JSON.stringify(template));
1363
- }
1364
- // Suffix the id so each instance gets a unique behavior id
1365
- base.id = "".concat(base.id, "::").concat(instanceUuid);
1366
- // Inject the instance UUID as the component scope guard
1367
- base.input = _objectSpread2(_objectSpread2({}, base.input), {}, {
1368
- component: instanceUuid
1369
- });
1370
- base.output = _objectSpread2(_objectSpread2({}, base.output), {}, {
1371
- component: instanceUuid
1372
- });
1373
- return base;
1374
- }
1375
-
1376
1099
  /**
1377
1100
  * Save original world matrices for direction calculations
1378
1101
  */
@@ -1441,7 +1164,7 @@ var SceneOperationsManager = /*#__PURE__*/function () {
1441
1164
  key: "loadSceneFromData",
1442
1165
  value: (function () {
1443
1166
  var _loadSceneFromData = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee9(data) {
1444
- var _data$scene4, _data$scene5, _data$scene6, _data$scene7;
1167
+ var _data$scene3, _data$scene4, _data$scene5, _data$scene6;
1445
1168
  return _regenerator().w(function (_context9) {
1446
1169
  while (1) switch (_context9.n) {
1447
1170
  case 0:
@@ -1451,10 +1174,10 @@ var SceneOperationsManager = /*#__PURE__*/function () {
1451
1174
  dataType: _typeof(data),
1452
1175
  hasScene: !!(data !== null && data !== void 0 && data.scene),
1453
1176
  sceneType: _typeof(data === null || data === void 0 ? void 0 : data.scene),
1454
- hasChildren: !!(data !== null && data !== void 0 && (_data$scene4 = data.scene) !== null && _data$scene4 !== void 0 && _data$scene4.children),
1455
- childrenType: data !== null && data !== void 0 && (_data$scene5 = data.scene) !== null && _data$scene5 !== void 0 && _data$scene5.children ? _typeof(data.scene.children) : 'undefined',
1456
- isArray: Array.isArray(data === null || data === void 0 || (_data$scene6 = data.scene) === null || _data$scene6 === void 0 ? void 0 : _data$scene6.children),
1457
- childrenLength: data === null || data === void 0 || (_data$scene7 = data.scene) === null || _data$scene7 === void 0 || (_data$scene7 = _data$scene7.children) === null || _data$scene7 === void 0 ? void 0 : _data$scene7.length,
1177
+ hasChildren: !!(data !== null && data !== void 0 && (_data$scene3 = data.scene) !== null && _data$scene3 !== void 0 && _data$scene3.children),
1178
+ childrenType: data !== null && data !== void 0 && (_data$scene4 = data.scene) !== null && _data$scene4 !== void 0 && _data$scene4.children ? _typeof(data.scene.children) : 'undefined',
1179
+ isArray: Array.isArray(data === null || data === void 0 || (_data$scene5 = data.scene) === null || _data$scene5 === void 0 ? void 0 : _data$scene5.children),
1180
+ childrenLength: data === null || data === void 0 || (_data$scene6 = data.scene) === null || _data$scene6 === void 0 || (_data$scene6 = _data$scene6.children) === null || _data$scene6 === void 0 ? void 0 : _data$scene6.length,
1458
1181
  dataKeys: data ? Object.keys(data) : [],
1459
1182
  sceneKeys: data !== null && data !== void 0 && data.scene ? Object.keys(data.scene) : []
1460
1183
  });
@@ -1585,8 +1308,8 @@ var SceneOperationsManager = /*#__PURE__*/function () {
1585
1308
  // Process children (connectors, etc.) if they exist
1586
1309
  if (componentModel.children && componentModel.children.length > 0) {
1587
1310
  componentModel.children.forEach(function (child) {
1588
- var _child$userData1, _child$userData10;
1589
- var childType = ((_child$userData1 = child.userData) === null || _child$userData1 === void 0 ? void 0 : _child$userData1.objectType) || ((_child$userData10 = child.userData) === null || _child$userData10 === void 0 ? void 0 : _child$userData10.objectType);
1311
+ var _child$userData0, _child$userData1;
1312
+ var childType = ((_child$userData0 = child.userData) === null || _child$userData0 === void 0 ? void 0 : _child$userData0.objectType) || ((_child$userData1 = child.userData) === null || _child$userData1 === void 0 ? void 0 : _child$userData1.objectType);
1590
1313
  if (childType === 'connector') {
1591
1314
  var _child$geometry;
1592
1315
  var childBoundingBox = new THREE.Box3().setFromObject(child);
@@ -1671,8 +1394,8 @@ var SceneOperationsManager = /*#__PURE__*/function () {
1671
1394
  if (segment.children && segment.children.length > 0) {
1672
1395
  var childrenToRemove = _toConsumableArray(segment.children);
1673
1396
  childrenToRemove.forEach(function (child) {
1674
- var _child$userData11;
1675
- if ((_child$userData11 = child.userData) !== null && _child$userData11 !== void 0 && _child$userData11.isPipeElbow) {
1397
+ var _child$userData10;
1398
+ if ((_child$userData10 = child.userData) !== null && _child$userData10 !== void 0 && _child$userData10.isPipeElbow) {
1676
1399
  console.log("\uD83D\uDDD1\uFE0F Removing elbow child from segment before manualization: ".concat(child.uuid));
1677
1400
  segment.remove(child);
1678
1401
  if (child.geometry) child.geometry.dispose();
@@ -1823,7 +1546,7 @@ var SceneOperationsManager = /*#__PURE__*/function () {
1823
1546
  value: function _convertConnectedGatewaysToManual(connectors, currentSceneData) {
1824
1547
  var _connectors$,
1825
1548
  _segment$userData2,
1826
- _this7 = this;
1549
+ _this5 = this;
1827
1550
  console.log('🔍 Checking for connected gateways to convert to manual...');
1828
1551
  var sceneViewer = this.sceneViewer;
1829
1552
  var convertedGateways = [];
@@ -1860,7 +1583,7 @@ var SceneOperationsManager = /*#__PURE__*/function () {
1860
1583
  console.log("\uD83D\uDD27 Found computed gateway at endpoint: ".concat(endpointObject.uuid, " - converting to manual"));
1861
1584
 
1862
1585
  // Convert gateway to manual (declared) using manualizeGateway for consistency
1863
- _this7.manualizeGateway(endpointObject, currentSceneData);
1586
+ _this5.manualizeGateway(endpointObject, currentSceneData);
1864
1587
  convertedGateways.push(endpointObject);
1865
1588
  } else if (((_endpointObject$userD5 = endpointObject.userData) === null || _endpointObject$userD5 === void 0 ? void 0 : _endpointObject$userD5.objectType) === 'gateway' && ((_endpointObject$userD6 = endpointObject.userData) === null || _endpointObject$userD6 === void 0 ? void 0 : _endpointObject$userD6.isDeclared) === true) {
1866
1589
  console.log("\u2139\uFE0F Gateway ".concat(endpointObject.uuid, " is already declared (manual), skipping conversion"));
@@ -1,4 +1,4 @@
1
- import { createForOfIteratorHelper as _createForOfIteratorHelper } from '../../_virtual/_rollupPluginBabelHelpers.js';
1
+ import { createForOfIteratorHelper as _createForOfIteratorHelper, construct as _construct, toConsumableArray as _toConsumableArray } from '../../_virtual/_rollupPluginBabelHelpers.js';
2
2
  import * as THREE from 'three';
3
3
 
4
4
  /**
@@ -209,6 +209,34 @@ function computeIODeviceBoundingBoxes(componentObject) {
209
209
  * const helpers = createSelectionBoxHelpers(pumpModel, 0x00ff00)
210
210
  * helpers.forEach(h => scene.add(h))
211
211
  */
212
+ /**
213
+ * Returns a filtered bounding box for `object`, using a cache stored on
214
+ * `object.userData._filteredBBoxCache`. The cache key is the serialised
215
+ * world-matrix elements string; if the object has moved the cache is
216
+ * automatically invalidated and recomputed.
217
+ *
218
+ * This avoids the expensive full-traverse on every selection event for
219
+ * large smart components with many child meshes.
220
+ *
221
+ * @param {THREE.Object3D} object
222
+ * @param {string[]} excludeTypes
223
+ * @returns {THREE.Box3}
224
+ */
225
+ function computeFilteredBoundingBoxCached(object, excludeTypes) {
226
+ object.updateMatrixWorld(true);
227
+ var matrixKey = object.matrixWorld.elements.join(',');
228
+ var cache = object.userData._filteredBBoxCache;
229
+ if (cache && cache.matrixKey === matrixKey) {
230
+ return new THREE.Box3(_construct(THREE.Vector3, _toConsumableArray(cache.min)), _construct(THREE.Vector3, _toConsumableArray(cache.max)));
231
+ }
232
+ var box = computeFilteredBoundingBox(object, excludeTypes);
233
+ object.userData._filteredBBoxCache = {
234
+ matrixKey: matrixKey,
235
+ min: box.min.toArray(),
236
+ max: box.max.toArray()
237
+ };
238
+ return box;
239
+ }
212
240
  function createSelectionBoxHelpers(object) {
213
241
  var _object$children;
214
242
  var color = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0x00ff00;
@@ -222,7 +250,7 @@ function createSelectionBoxHelpers(object) {
222
250
  });
223
251
  if (hasIODevices) {
224
252
  // 1. Create filtered helper for the component body
225
- var filteredBox = computeFilteredBoundingBox(object, excludeTypes);
253
+ var filteredBox = computeFilteredBoundingBoxCached(object, excludeTypes);
226
254
  var componentHelper = _createBoxHelperFromBox3(filteredBox, color);
227
255
  componentHelper.isHelper = true;
228
256
  componentHelper.userData = {
@@ -232,33 +260,6 @@ function createSelectionBoxHelpers(object) {
232
260
  excludeTypes: excludeTypes
233
261
  };
234
262
  helpers.push(componentHelper);
235
-
236
- // 2. Create individual helpers for each io-device
237
- var _iterator2 = _createForOfIteratorHelper(object.children),
238
- _step2;
239
- try {
240
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
241
- var _child$userData3;
242
- var child = _step2.value;
243
- if (((_child$userData3 = child.userData) === null || _child$userData3 === void 0 ? void 0 : _child$userData3.objectType) !== 'io-device') continue;
244
- var deviceBox = new THREE.Box3().setFromObject(child);
245
- if (deviceBox.isEmpty()) continue;
246
- var deviceHelper = _createBoxHelperFromBox3(deviceBox, color);
247
- deviceHelper.isHelper = true;
248
- deviceHelper.userData = {
249
- isBoundingBox: true,
250
- sourceObjectUuid: child.uuid,
251
- isFiltered: false,
252
- isIODevice: true,
253
- parentComponentUuid: object.uuid
254
- };
255
- helpers.push(deviceHelper);
256
- }
257
- } catch (err) {
258
- _iterator2.e(err);
259
- } finally {
260
- _iterator2.f();
261
- }
262
263
  } else {
263
264
  // Standard BoxHelper for non-smart objects
264
265
  var boxHelper = new THREE.BoxHelper(object, color);
@@ -282,11 +283,11 @@ function createSelectionBoxHelpers(object) {
282
283
  * @param {THREE.Scene} scene - The scene (for finding objects by uuid)
283
284
  */
284
285
  function updateSelectionBoxHelpers(helpers, selectedObjects, scene) {
285
- var _iterator3 = _createForOfIteratorHelper(helpers),
286
- _step3;
286
+ var _iterator2 = _createForOfIteratorHelper(helpers),
287
+ _step2;
287
288
  try {
288
289
  var _loop = function _loop() {
289
- var helper = _step3.value;
290
+ var helper = _step2.value;
290
291
  var _helper$userData = helper.userData,
291
292
  sourceObjectUuid = _helper$userData.sourceObjectUuid,
292
293
  isFiltered = _helper$userData.isFiltered,
@@ -309,26 +310,22 @@ function updateSelectionBoxHelpers(helpers, selectedObjects, scene) {
309
310
  if (!sourceObject) return 1; // continue
310
311
  sourceObject.updateMatrixWorld(true);
311
312
  if (isFiltered && excludeTypes) {
312
- // Recompute filtered bbox
313
- var box = computeFilteredBoundingBox(sourceObject, excludeTypes);
313
+ // Recompute filtered bbox (uses cache when the object hasn't moved)
314
+ var box = computeFilteredBoundingBoxCached(sourceObject, excludeTypes);
314
315
  _updateBoxHelperPositions(helper, box);
315
- } else if (isIODevice) {
316
- // Recompute io-device bbox
317
- var _box = new THREE.Box3().setFromObject(sourceObject);
318
- _updateBoxHelperPositions(helper, _box);
319
316
  } else if (helper.update) {
320
317
  // Standard BoxHelper — use built-in update
321
318
  helper.update();
322
319
  }
323
320
  };
324
- for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
321
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
325
322
  if (_loop()) continue;
326
323
  }
327
324
  } catch (err) {
328
- _iterator3.e(err);
325
+ _iterator2.e(err);
329
326
  } finally {
330
- _iterator3.f();
327
+ _iterator2.f();
331
328
  }
332
329
  }
333
330
 
334
- export { computeFilteredBoundingBox, computeIODeviceBoundingBoxes, createSelectionBoxHelpers, updateSelectionBoxHelpers };
331
+ export { computeFilteredBoundingBox, computeFilteredBoundingBoxCached, computeIODeviceBoundingBoxes, createSelectionBoxHelpers, updateSelectionBoxHelpers };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2112-lab/central-plant",
3
- "version": "0.3.26",
3
+ "version": "0.3.28",
4
4
  "description": "Utility modules for the Central Plant Application",
5
5
  "main": "dist/bundle/index.js",
6
6
  "module": "dist/esm/src/index.js",