@2112-lab/central-plant 0.3.38 → 0.3.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 (29) hide show
  1. package/dist/bundle/index.js +1096 -562
  2. package/dist/cjs/src/core/centralPlant.js +115 -68
  3. package/dist/cjs/src/core/centralPlantInternals.js +20 -36
  4. package/dist/cjs/src/index.js +23 -0
  5. package/dist/cjs/src/managers/behaviors/IoBehaviorManager.js +175 -234
  6. package/dist/cjs/src/managers/components/componentDataManager.js +63 -11
  7. package/dist/cjs/src/managers/scene/componentTooltipManager.js +95 -65
  8. package/dist/cjs/src/managers/scene/modelManager.js +93 -145
  9. package/dist/cjs/src/managers/scene/sceneOperationsManager.js +8 -3
  10. package/dist/cjs/src/utils/animationTransformUtils.js +82 -0
  11. package/dist/cjs/src/utils/behaviorDispatch.js +62 -0
  12. package/dist/cjs/src/utils/behaviorRegistration.js +76 -0
  13. package/dist/cjs/src/utils/behaviorSceneUtils.js +155 -0
  14. package/dist/cjs/src/utils/behaviorSchema.js +209 -0
  15. package/dist/esm/src/core/centralPlant.js +115 -68
  16. package/dist/esm/src/core/centralPlantInternals.js +21 -37
  17. package/dist/esm/src/index.js +5 -0
  18. package/dist/esm/src/managers/behaviors/IoBehaviorManager.js +176 -235
  19. package/dist/esm/src/managers/components/componentDataManager.js +63 -11
  20. package/dist/esm/src/managers/scene/componentTooltipManager.js +95 -65
  21. package/dist/esm/src/managers/scene/modelManager.js +94 -146
  22. package/dist/esm/src/managers/scene/sceneOperationsManager.js +8 -3
  23. package/dist/esm/src/utils/animationTransformUtils.js +56 -0
  24. package/dist/esm/src/utils/behaviorDispatch.js +56 -0
  25. package/dist/esm/src/utils/behaviorRegistration.js +71 -0
  26. package/dist/esm/src/utils/behaviorSceneUtils.js +147 -0
  27. package/dist/esm/src/utils/behaviorSchema.js +201 -0
  28. package/dist/index.d.ts +186 -1
  29. package/package.json +1 -1
@@ -5,6 +5,9 @@ Object.defineProperty(exports, '__esModule', { value: true });
5
5
  var _rollupPluginBabelHelpers = require('../../../_virtual/_rollupPluginBabelHelpers.js');
6
6
  var THREE = require('three');
7
7
  var baseDisposable = require('../../core/baseDisposable.js');
8
+ var behaviorSchema = require('../../utils/behaviorSchema.js');
9
+ var behaviorDispatch = require('../../utils/behaviorDispatch.js');
10
+ var animationTransformUtils = require('../../utils/animationTransformUtils.js');
8
11
 
9
12
  function _interopNamespace(e) {
10
13
  if (e && e.__esModule) return e;
@@ -43,12 +46,11 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
43
46
  */
44
47
  _this._entries = new Map();
45
48
  _this._crossComponentBehaviors = [];
46
-
47
- /**
48
- * Map: `${componentUuid}` → Array<{ id, input, outputs }>
49
- * Component-level behaviors for intra-component io-device linking
50
- */
51
49
  _this._componentBehaviors = new Map();
50
+ /** @type {Map<string, string>} parentUuid::attachmentId → parentUuid */
51
+ _this._attachmentParentMap = new Map();
52
+ /** @type {Map<string, Object[]>} cache key → data point definitions */
53
+ _this._dataPointsCache = new Map();
52
54
 
53
55
  /**
54
56
  * Injected by the host application to read and write I/O device state.
@@ -138,7 +140,10 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
138
140
  }
139
141
  if (entries.length) {
140
142
  this._entries.set(key, entries);
141
- // Loaded ${entries.length} animation(s) for attachment "${attachmentId}"
143
+ if (parentUuid && attachmentId) {
144
+ this._attachmentParentMap.set(this._key(parentUuid, attachmentId), parentUuid);
145
+ }
146
+ this._invalidateDataPointsCache(parentUuid, attachmentId);
142
147
  } else {
143
148
  console.warn("[IoBehaviorManager] No mesh entries resolved for attachment \"".concat(attachmentId, "\" \u2014 behaviorConfig had ").concat(anims.length, " entries but none matched a mesh"));
144
149
  }
@@ -146,7 +151,6 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
146
151
 
147
152
  /**
148
153
  * Apply animations triggered by an IO device state change.
149
- * Should be called in parallel with BehaviorManager.triggerState().
150
154
  *
151
155
  * @param {string} attachmentId - Raw attachment key (not scoped)
152
156
  * @param {string} dataPointId - The data point / state variable id that changed
@@ -156,70 +160,36 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
156
160
  }, {
157
161
  key: "triggerState",
158
162
  value: function triggerState(attachmentId, dataPointId, value, parentUuid) {
159
- // triggerState: ${attachmentId}.${dataPointId} = ${value}
160
-
161
- if (parentUuid) {
162
- var key = this._key(parentUuid, attachmentId);
163
- var entries = this._entries.get(key);
164
- if (entries) {
165
- var _iterator2 = _rollupPluginBabelHelpers.createForOfIteratorHelper(entries),
166
- _step2;
167
- try {
168
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
169
- var entry = _step2.value;
170
- if (entry.anim.stateVariable !== dataPointId) continue;
171
- this._applyAnimation(entry, value);
172
- }
173
- } catch (err) {
174
- _iterator2.e(err);
175
- } finally {
176
- _iterator2.f();
177
- }
178
- }
179
- } else {
180
- // Fallback when parentUuid is not provided: match by attachmentId suffix or exact key match
181
- var suffix = "::".concat(attachmentId);
182
- var _iterator3 = _rollupPluginBabelHelpers.createForOfIteratorHelper(this._entries.entries()),
183
- _step3;
163
+ var _this$_crossComponent;
164
+ var resolvedParent = parentUuid || this._findComponentByAttachment(attachmentId);
165
+ if (!resolvedParent) return;
166
+ var key = this._key(resolvedParent, attachmentId);
167
+ var entries = this._entries.get(key);
168
+ if (entries) {
169
+ var _iterator2 = _rollupPluginBabelHelpers.createForOfIteratorHelper(entries),
170
+ _step2;
184
171
  try {
185
- for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
186
- var _step3$value = _rollupPluginBabelHelpers.slicedToArray(_step3.value, 2),
187
- _key2 = _step3$value[0],
188
- _entries = _step3$value[1];
189
- if (_key2 === attachmentId || _key2.endsWith(suffix)) {
190
- var _iterator4 = _rollupPluginBabelHelpers.createForOfIteratorHelper(_entries),
191
- _step4;
192
- try {
193
- for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
194
- var _entry = _step4.value;
195
- if (_entry.anim.stateVariable !== dataPointId) continue;
196
- this._applyAnimation(_entry, value);
197
- }
198
- } catch (err) {
199
- _iterator4.e(err);
200
- } finally {
201
- _iterator4.f();
202
- }
203
- }
172
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
173
+ var entry = _step2.value;
174
+ if (entry.anim.stateVariable !== dataPointId) continue;
175
+ this._applyAnimation(entry, value);
204
176
  }
205
177
  } catch (err) {
206
- _iterator3.e(err);
178
+ _iterator2.e(err);
207
179
  } finally {
208
- _iterator3.f();
180
+ _iterator2.f();
209
181
  }
210
182
  }
211
183
 
212
184
  // Evaluate component-level behaviors (intra-component io-device linking)
213
- if (parentUuid && this._componentBehaviors.has(parentUuid)) {
214
- var componentBehaviors = this._componentBehaviors.get(parentUuid);
215
- // Checking component-level behaviors (count: ${componentBehaviors.length})
216
- this.triggerCrossComponentBehaviors(componentBehaviors, parentUuid, attachmentId, dataPointId, value);
185
+ if (this._componentBehaviors.has(resolvedParent)) {
186
+ var componentBehaviors = this._componentBehaviors.get(resolvedParent);
187
+ this.triggerCrossComponentBehaviors(componentBehaviors, resolvedParent, attachmentId, dataPointId, value, {
188
+ sameComponent: true
189
+ });
217
190
  }
218
-
219
- // Evaluate cross-component behaviors if any are registered
220
- // Checking cross-component behaviors (count: ${this._crossComponentBehaviors?.length || 0})
221
- if (this._crossComponentBehaviors && this._crossComponentBehaviors.length > 0) {
222
- this.triggerCrossComponentBehaviors(this._crossComponentBehaviors, parentUuid, attachmentId, dataPointId, value);
191
+ if (((_this$_crossComponent = this._crossComponentBehaviors) === null || _this$_crossComponent === void 0 ? void 0 : _this$_crossComponent.length) > 0) {
192
+ this.triggerCrossComponentBehaviors(this._crossComponentBehaviors, resolvedParent, attachmentId, dataPointId, value);
223
193
  }
224
194
  }
225
195
 
@@ -232,9 +202,8 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
232
202
  }, {
233
203
  key: "setCrossComponentBehaviors",
234
204
  value: function setCrossComponentBehaviors(behaviors) {
235
- var _this2 = this;
236
205
  this._crossComponentBehaviors = (behaviors || []).map(function (b) {
237
- return _this2._normalizeBehavior(b);
206
+ return behaviorSchema.normalizeBehavior(b);
238
207
  });
239
208
  // Loaded ${this._crossComponentBehaviors.length} cross-component behavior(s)
240
209
  }
@@ -249,72 +218,17 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
249
218
  }, {
250
219
  key: "registerComponentBehaviors",
251
220
  value: function registerComponentBehaviors(componentUuid, behaviors) {
252
- var _this3 = this;
253
- if (!behaviors || behaviors.length === 0) return;
221
+ if (!behaviors || behaviors.length === 0) {
222
+ this._componentBehaviors.delete(componentUuid);
223
+ return;
224
+ }
254
225
  var normalized = behaviors.map(function (b) {
255
- return _this3._normalizeBehavior(b);
226
+ return behaviorSchema.normalizeBehavior(b);
256
227
  });
257
228
  this._componentBehaviors.set(componentUuid, normalized);
258
229
  // Registered ${normalized.length} component-level behavior(s) for component ${componentUuid}
259
230
  }
260
231
 
261
- /**
262
- * Normalize behavior from shorthand to full format.
263
- * Supports:
264
- * - input: "attachment.state" → { attachment, state }
265
- * - outputs: ["attachment.state", ...] → converted to individual behaviors
266
- *
267
- * @param {Object} behavior - Raw behavior from scene JSON
268
- * @returns {Object} Normalized behavior
269
- */
270
- }, {
271
- key: "_normalizeBehavior",
272
- value: function _normalizeBehavior(behavior) {
273
- var normalized = _rollupPluginBabelHelpers.objectSpread2({}, behavior);
274
-
275
- // Parse shorthand input: "attachment.state"
276
- if (typeof behavior.input === 'string') {
277
- var _behavior$input$split = behavior.input.split('.'),
278
- _behavior$input$split2 = _rollupPluginBabelHelpers.slicedToArray(_behavior$input$split, 2),
279
- attachment = _behavior$input$split2[0],
280
- state = _behavior$input$split2[1];
281
- normalized.input = {
282
- attachment: attachment,
283
- state: state
284
- };
285
- }
286
-
287
- // Parse shorthand output/outputs
288
- if (behavior.outputs) {
289
- // Multiple outputs - expand to array
290
- normalized._outputs = behavior.outputs.map(function (out) {
291
- if (typeof out === 'string') {
292
- var _out$split = out.split('.'),
293
- _out$split2 = _rollupPluginBabelHelpers.slicedToArray(_out$split, 2),
294
- _attachment = _out$split2[0],
295
- _state = _out$split2[1];
296
- return {
297
- attachment: _attachment,
298
- state: _state
299
- };
300
- }
301
- return out;
302
- });
303
- delete normalized.outputs;
304
- } else if (typeof behavior.output === 'string') {
305
- // Single output string
306
- var _behavior$output$spli = behavior.output.split('.'),
307
- _behavior$output$spli2 = _rollupPluginBabelHelpers.slicedToArray(_behavior$output$spli, 2),
308
- _attachment2 = _behavior$output$spli2[0],
309
- _state2 = _behavior$output$spli2[1];
310
- normalized.output = {
311
- attachment: _attachment2,
312
- state: _state2
313
- };
314
- }
315
- return normalized;
316
- }
317
-
318
232
  /**
319
233
  * Find the parent component UUID for a given attachment ID.
320
234
  * Searches the scene tree for the io-device with this attachment ID,
@@ -328,12 +242,10 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
328
242
  value: function _findComponentByAttachment(attachmentId) {
329
243
  var _this$sceneViewer;
330
244
  if (!((_this$sceneViewer = this.sceneViewer) !== null && _this$sceneViewer !== void 0 && _this$sceneViewer.scene)) return null;
331
- var scene = this.sceneViewer.scene;
332
245
  var found = null;
333
- scene.traverse(function (obj) {
246
+ this.sceneViewer.scene.traverse(function (obj) {
334
247
  var _obj$userData, _obj$userData2;
335
248
  if (found) return;
336
- // Find the io-device object with this attachmentId
337
249
  if (((_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.objectType) === 'io-device' && ((_obj$userData2 = obj.userData) === null || _obj$userData2 === void 0 ? void 0 : _obj$userData2.attachmentId) === attachmentId) {
338
250
  found = obj.userData.parentComponentId;
339
251
  }
@@ -377,20 +289,21 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
377
289
  * @param {string} triggerAttachmentId - Attachment ID of the triggering device
378
290
  * @param {string} triggerStateId - The state variable ID that changed
379
291
  * @param {*} value - The new state value
292
+ * @param {{ sameComponent?: boolean }} [options] - Intra-component links share one parent instance
380
293
  */
381
294
  }, {
382
295
  key: "triggerCrossComponentBehaviors",
383
296
  value: function triggerCrossComponentBehaviors(behaviors, triggerParentUuid, triggerAttachmentId, triggerStateId, value) {
297
+ var options = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {};
384
298
  if (!behaviors || !Array.isArray(behaviors)) {
385
299
  return;
386
300
  }
387
-
388
- // Evaluating ${behaviors.length} behavior(s)
389
- var _iterator5 = _rollupPluginBabelHelpers.createForOfIteratorHelper(behaviors),
390
- _step5;
301
+ var sameComponent = options.sameComponent === true;
302
+ var _iterator3 = _rollupPluginBabelHelpers.createForOfIteratorHelper(behaviors),
303
+ _step3;
391
304
  try {
392
- for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
393
- var behavior = _step5.value;
305
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
306
+ var behavior = _step3.value;
394
307
  var input = behavior.input,
395
308
  output = behavior.output,
396
309
  _outputs = behavior._outputs,
@@ -399,94 +312,69 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
399
312
  console.warn('[Behavior] Skipping behavior - missing input or output(s):', behavior);
400
313
  continue;
401
314
  }
402
-
403
- // Auto-lookup component if not specified
404
- var inputComponent = input.component || this._findComponentByAttachment(input.attachment);
405
- console.log("[Behavior] Checking behavior \"".concat(behavior.id, "\":"), {
406
- inputMatch: inputComponent === triggerParentUuid,
407
- attachmentMatch: input.attachment === triggerAttachmentId,
408
- stateMatch: input.state === triggerStateId,
409
- expected: {
410
- component: inputComponent,
411
- attachment: input.attachment,
412
- state: input.state
413
- },
414
- actual: {
415
- component: triggerParentUuid,
416
- attachment: triggerAttachmentId,
417
- state: triggerStateId
418
- }
419
- });
420
-
421
- // Verify that the input matches the triggering source
422
- if (inputComponent === triggerParentUuid && input.attachment === triggerAttachmentId && input.state === triggerStateId) {
315
+ var inputComponent = input.component || (sameComponent ? triggerParentUuid : this._findComponentByAttachment(input.attachment));
316
+ var inputMatches = sameComponent ? input.attachment === triggerAttachmentId && input.state === triggerStateId : inputComponent === triggerParentUuid && input.attachment === triggerAttachmentId && input.state === triggerStateId;
317
+ if (inputMatches) {
423
318
  // Behavior "${behavior.id}" matched
424
319
 
425
320
  // Collect all outputs (single or multiple)
426
321
  var outputs = _outputs || (output ? [output] : []);
427
322
 
428
323
  // Process each output
429
- var _iterator6 = _rollupPluginBabelHelpers.createForOfIteratorHelper(outputs),
430
- _step6;
324
+ var _iterator4 = _rollupPluginBabelHelpers.createForOfIteratorHelper(outputs),
325
+ _step4;
431
326
  try {
432
- for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
433
- var out = _step6.value;
327
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
328
+ var out = _step4.value;
434
329
  // NEW: State-to-state pass-through pattern
435
330
  if (out.state) {
436
- // Auto-lookup output component if not specified
437
- var outputComponent = out.component || this._findComponentByAttachment(out.attachment);
438
-
439
- // Direct state mapping without conditions
331
+ var outputComponent = out.component || (sameComponent ? triggerParentUuid : this._findComponentByAttachment(out.attachment));
440
332
  if (this._stateAdapter) {
441
- // Dispatching state-to-state: ${out.attachment}.${out.state} = ${value}
442
- this._stateAdapter.setState(out.attachment, out.state, value);
443
-
444
- // Trigger animations on the output component
445
- // triggerState(attachmentId, dataPointId, value, parentUuid)
333
+ var scopedKey = behaviorDispatch.getScopedAttachmentKey(out.attachment, outputComponent);
334
+ this._stateAdapter.setState(scopedKey, out.state, value);
446
335
  if (outputComponent) {
447
- // Triggering animations on output component
448
336
  this.triggerState(out.attachment, out.state, value, outputComponent);
449
337
  } else {
450
338
  console.warn("[Behavior] Could not find component for attachment \"".concat(out.attachment, "\""));
451
339
  }
452
340
  } else {
453
- console.warn('[Behavior] State adapter not configured for state-to-state behavior');
341
+ console.warn('[Behavior] State adapter not configured for state-to-state behavior');
454
342
  }
455
343
  }
456
344
  // LEGACY: Mesh-based pattern with conditions
457
345
  else if (conditions && out.child) {
458
346
  // Using legacy mesh-based pattern with conditions
459
347
  // Evaluate conditions
460
- var _iterator7 = _rollupPluginBabelHelpers.createForOfIteratorHelper(conditions),
461
- _step7;
348
+ var _iterator5 = _rollupPluginBabelHelpers.createForOfIteratorHelper(conditions),
349
+ _step5;
462
350
  try {
463
- for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
464
- var condition = _step7.value;
351
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
352
+ var condition = _step5.value;
465
353
  if (this._evaluateCondition(condition.when, value)) {
466
354
  // Apply actions to the target output component/attachment/child mesh
467
355
  this._applyCrossComponentActions(out, condition.actions);
468
356
  }
469
357
  }
470
358
  } catch (err) {
471
- _iterator7.e(err);
359
+ _iterator5.e(err);
472
360
  } finally {
473
- _iterator7.f();
361
+ _iterator5.f();
474
362
  }
475
363
  } else {
476
364
  console.warn('[Behavior] Output has neither state nor (child + conditions):', out);
477
365
  }
478
366
  } // end outputs loop
479
367
  } catch (err) {
480
- _iterator6.e(err);
368
+ _iterator4.e(err);
481
369
  } finally {
482
- _iterator6.f();
370
+ _iterator4.f();
483
371
  }
484
372
  }
485
373
  }
486
374
  } catch (err) {
487
- _iterator5.e(err);
375
+ _iterator3.e(err);
488
376
  } finally {
489
- _iterator5.f();
377
+ _iterator3.f();
490
378
  }
491
379
  }
492
380
 
@@ -556,17 +444,17 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
556
444
  }
557
445
 
558
446
  // 4. Apply actions to targetObj
559
- var _iterator8 = _rollupPluginBabelHelpers.createForOfIteratorHelper(actions),
560
- _step8;
447
+ var _iterator6 = _rollupPluginBabelHelpers.createForOfIteratorHelper(actions),
448
+ _step6;
561
449
  try {
562
- for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
563
- var action = _step8.value;
450
+ for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
451
+ var action = _step6.value;
564
452
  this._applyCrossComponentAction(targetObj, action);
565
453
  }
566
454
  } catch (err) {
567
- _iterator8.e(err);
455
+ _iterator6.e(err);
568
456
  } finally {
569
- _iterator8.f();
457
+ _iterator6.f();
570
458
  }
571
459
  }
572
460
 
@@ -658,8 +546,12 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
658
546
  }, {
659
547
  key: "getAnimationDataPoints",
660
548
  value: function getAnimationDataPoints(parentUuid, attachmentId) {
661
- var _this4 = this;
549
+ var _this2 = this;
662
550
  var hitMesh = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
551
+ var cacheKey = "".concat(this._key(parentUuid, attachmentId), "::").concat((hitMesh === null || hitMesh === void 0 ? void 0 : hitMesh.uuid) || '');
552
+ if (this._dataPointsCache.has(cacheKey)) {
553
+ return this._dataPointsCache.get(cacheKey);
554
+ }
663
555
  var key = this._key(parentUuid, attachmentId);
664
556
  var entries = this._entries.get(key);
665
557
  if (!(entries !== null && entries !== void 0 && entries.length)) return [];
@@ -670,35 +562,35 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
670
562
  var filtered = entries;
671
563
  if (hitMesh) {
672
564
  var matching = entries.filter(function (e) {
673
- return _this4._isMeshOrDescendant(hitMesh, e.mesh);
565
+ return _this2._isMeshOrDescendant(hitMesh, e.mesh);
674
566
  });
675
567
  if (matching.length > 0) filtered = matching;
676
568
  }
677
569
 
678
570
  // Collapse multiple mesh entries that share the same stateVariable
679
571
  var seen = new Map(); // stateVariable → anim
680
- var _iterator9 = _rollupPluginBabelHelpers.createForOfIteratorHelper(filtered),
681
- _step9;
572
+ var _iterator7 = _rollupPluginBabelHelpers.createForOfIteratorHelper(filtered),
573
+ _step7;
682
574
  try {
683
- for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) {
684
- var anim = _step9.value.anim;
575
+ for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
576
+ var anim = _step7.value.anim;
685
577
  if (!seen.has(anim.stateVariable)) {
686
578
  seen.set(anim.stateVariable, anim);
687
579
  }
688
580
  }
689
581
  } catch (err) {
690
- _iterator9.e(err);
582
+ _iterator7.e(err);
691
583
  } finally {
692
- _iterator9.f();
584
+ _iterator7.f();
693
585
  }
694
586
  var dps = [];
695
- var _iterator0 = _rollupPluginBabelHelpers.createForOfIteratorHelper(seen),
696
- _step0;
587
+ var _iterator8 = _rollupPluginBabelHelpers.createForOfIteratorHelper(seen),
588
+ _step8;
697
589
  try {
698
- for (_iterator0.s(); !(_step0 = _iterator0.n()).done;) {
699
- var _step0$value = _rollupPluginBabelHelpers.slicedToArray(_step0.value, 2),
700
- stateVar = _step0$value[0],
701
- _anim = _step0$value[1];
590
+ for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
591
+ var _step8$value = _rollupPluginBabelHelpers.slicedToArray(_step8.value, 2),
592
+ stateVar = _step8$value[0],
593
+ _anim = _step8$value[1];
702
594
  // Normalise stateType from AnimateDevicesDialog variants
703
595
  var stateType = void 0;
704
596
  var raw = (_anim.stateType || '').toLowerCase();
@@ -749,10 +641,11 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
749
641
  });
750
642
  }
751
643
  } catch (err) {
752
- _iterator0.e(err);
644
+ _iterator8.e(err);
753
645
  } finally {
754
- _iterator0.f();
646
+ _iterator8.f();
755
647
  }
648
+ this._dataPointsCache.set(cacheKey, dps);
756
649
  return dps;
757
650
  }
758
651
 
@@ -786,25 +679,46 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
786
679
  key: "unloadForComponent",
787
680
  value: function unloadForComponent(parentUuid) {
788
681
  var prefix = "".concat(parentUuid, "::");
789
- var _iterator1 = _rollupPluginBabelHelpers.createForOfIteratorHelper(this._entries.keys()),
790
- _step1;
791
- try {
792
- for (_iterator1.s(); !(_step1 = _iterator1.n()).done;) {
793
- var key = _step1.value;
794
- if (key.startsWith(prefix)) {
795
- this._entries.delete(key);
796
- }
682
+ for (var _i = 0, _arr = _rollupPluginBabelHelpers.toConsumableArray(this._entries.keys()); _i < _arr.length; _i++) {
683
+ var key = _arr[_i];
684
+ if (key.startsWith(prefix)) {
685
+ this._attachmentParentMap.delete(key);
686
+ this._entries.delete(key);
797
687
  }
798
- } catch (err) {
799
- _iterator1.e(err);
800
- } finally {
801
- _iterator1.f();
802
688
  }
689
+ this._componentBehaviors.delete(parentUuid);
690
+ this._invalidateDataPointsCacheForParent(parentUuid);
691
+ }
692
+
693
+ /**
694
+ * Remove animation entries for a single attachment on a component instance.
695
+ */
696
+ }, {
697
+ key: "unloadForAttachment",
698
+ value: function unloadForAttachment(parentUuid, attachmentId) {
699
+ var key = this._key(parentUuid, attachmentId);
700
+ this._entries.delete(key);
701
+ this._attachmentParentMap.delete(key);
702
+ this._invalidateDataPointsCache(parentUuid, attachmentId);
703
+ }
704
+
705
+ /**
706
+ * Clear all runtime behavior state when the scene is cleared or replaced.
707
+ */
708
+ }, {
709
+ key: "resetForScene",
710
+ value: function resetForScene() {
711
+ this._entries.clear();
712
+ this._componentBehaviors.clear();
713
+ this._crossComponentBehaviors = [];
714
+ this._attachmentParentMap.clear();
715
+ this._dataPointsCache.clear();
803
716
  }
804
717
  }, {
805
718
  key: "dispose",
806
719
  value: function dispose() {
807
- this._entries.clear();
720
+ this.resetForScene();
721
+ this._stateAdapter = null;
808
722
  _rollupPluginBabelHelpers.superPropGet(IoBehaviorManager, "dispose", this, 3)([]);
809
723
  }
810
724
 
@@ -871,20 +785,20 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
871
785
  key: "_applyAnimation",
872
786
  value: function _applyAnimation(entry, value) {
873
787
  var anim = entry.anim,
874
- mesh = entry.mesh,
875
- origPos = entry.origPos;
788
+ mesh = entry.mesh;
789
+ entry.origPos;
876
790
  entry.origRot;
877
791
  var viewerMaxDim = entry.viewerMaxDim;
878
792
  var mapping = this._resolveMapping(anim, value);
879
793
  if (!mapping) return;
880
794
  var types = anim.transformTypes || [];
881
- var _iterator10 = _rollupPluginBabelHelpers.createForOfIteratorHelper(types),
882
- _step10;
795
+ var _iterator9 = _rollupPluginBabelHelpers.createForOfIteratorHelper(types),
796
+ _step9;
883
797
  try {
884
- for (_iterator10.s(); !(_step10 = _iterator10.n()).done;) {
885
- var type = _step10.value;
798
+ for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) {
799
+ var type = _step9.value;
886
800
  if (type === 'translation') {
887
- this._applyTranslation(mesh, origPos, mapping.transform);
801
+ this._applyTranslation(entry, mapping.transform);
888
802
  } else if (type === 'rotation') {
889
803
  this._applyRotation(entry, anim, mapping.rotationTransform, viewerMaxDim);
890
804
  } else if (type === 'color') {
@@ -892,9 +806,9 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
892
806
  }
893
807
  }
894
808
  } catch (err) {
895
- _iterator10.e(err);
809
+ _iterator9.e(err);
896
810
  } finally {
897
- _iterator10.f();
811
+ _iterator9.f();
898
812
  }
899
813
  }
900
814
 
@@ -1014,20 +928,21 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
1014
928
  // ─────────────────────────────────────────────────────────────────────────
1015
929
 
1016
930
  /**
1017
- * Apply a position delta relative to the mesh's original position.
1018
- * @param {THREE.Object3D} mesh
1019
- * @param {THREE.Vector3} origPos
1020
- * @param {{ x, y, z }} transform - Deltas
931
+ * Apply a translation offset in device-model-root space so every animated mesh
932
+ * shares the same axis directions regardless of intermediate parent transforms.
933
+ *
934
+ * @param {{ mesh, origPos, deviceModelRoot }} entry
935
+ * @param {{ x, y, z }} transform - Offset in model-root local space
1021
936
  */
1022
937
  }, {
1023
938
  key: "_applyTranslation",
1024
- value: function _applyTranslation(mesh, origPos, transform) {
1025
- var _transform$x, _transform$y, _transform$z;
939
+ value: function _applyTranslation(entry, transform) {
1026
940
  if (!transform) return;
1027
- // X and Y are negated to match the sign convention used in the AnimateDevicesDialog
1028
- // preview (_syncViewerTransform negates x and y before calling setMeshPreviewOffset).
1029
- // Z is added directly (no negation) — matching the dialog's z handling.
1030
- 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));
941
+ var mesh = entry.mesh,
942
+ origPos = entry.origPos,
943
+ deviceModelRoot = entry.deviceModelRoot;
944
+ if (!mesh || !origPos || !deviceModelRoot) return;
945
+ animationTransformUtils.applyModelRootTranslation(mesh, deviceModelRoot, origPos, transform);
1031
946
  }
1032
947
 
1033
948
  /**
@@ -1046,6 +961,9 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
1046
961
  origRot = entry.origRot,
1047
962
  deviceModelRoot = entry.deviceModelRoot;
1048
963
  if (!mesh || !origPos || !origRot) return null;
964
+ if (entry._restWorldCache) {
965
+ return entry._restWorldCache;
966
+ }
1049
967
  var savedPos = mesh.position.clone();
1050
968
  var savedQuat = mesh.quaternion.clone();
1051
969
  mesh.position.copy(origPos);
@@ -1064,12 +982,35 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
1064
982
  }
1065
983
  mesh.position.copy(savedPos);
1066
984
  mesh.quaternion.copy(savedQuat);
1067
- return {
985
+ entry._restWorldCache = {
1068
986
  origWorldPos: origWorldPos,
1069
987
  origWorldQuat: origWorldQuat,
1070
988
  origWorldCenter: origWorldCenter,
1071
989
  deviceWorldQuat: deviceWorldQuat
1072
990
  };
991
+ return entry._restWorldCache;
992
+ }
993
+ }, {
994
+ key: "_invalidateDataPointsCache",
995
+ value: function _invalidateDataPointsCache(parentUuid, attachmentId) {
996
+ var prefix = "".concat(this._key(parentUuid, attachmentId), "::");
997
+ for (var _i2 = 0, _arr2 = _rollupPluginBabelHelpers.toConsumableArray(this._dataPointsCache.keys()); _i2 < _arr2.length; _i2++) {
998
+ var key = _arr2[_i2];
999
+ if (key.startsWith(prefix)) {
1000
+ this._dataPointsCache.delete(key);
1001
+ }
1002
+ }
1003
+ }
1004
+ }, {
1005
+ key: "_invalidateDataPointsCacheForParent",
1006
+ value: function _invalidateDataPointsCacheForParent(parentUuid) {
1007
+ var prefix = "".concat(parentUuid, "::");
1008
+ for (var _i3 = 0, _arr3 = _rollupPluginBabelHelpers.toConsumableArray(this._dataPointsCache.keys()); _i3 < _arr3.length; _i3++) {
1009
+ var key = _arr3[_i3];
1010
+ if (key.startsWith(prefix)) {
1011
+ this._dataPointsCache.delete(key);
1012
+ }
1013
+ }
1073
1014
  }
1074
1015
 
1075
1016
  /**