@2112-lab/central-plant 0.3.57 → 0.3.59
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.
- package/dist/bundle/index.js +1696 -34
- package/dist/cjs/src/core/centralPlant.js +80 -11
- package/dist/cjs/src/core/centralPlantInternals.js +4 -0
- package/dist/cjs/src/core/centralPlantValidator.js +10 -6
- package/dist/cjs/src/core/stateEngine.js +733 -0
- package/dist/cjs/src/core/stateEngineScene.js +97 -0
- package/dist/cjs/src/index.js +10 -0
- package/dist/cjs/src/managers/controls/transformControlsManager.js +61 -2
- package/dist/cjs/src/managers/pathfinding/PathFlowManager.js +94 -13
- package/dist/cjs/src/managers/pathfinding/PathRenderingManager.js +15 -0
- package/dist/cjs/src/managers/scene/componentTooltipManager.js +7 -1
- package/dist/cjs/src/managers/scene/controlPanelManager.js +377 -0
- package/dist/cjs/src/managers/scene/sceneExportManager.js +10 -1
- package/dist/cjs/src/managers/state/EffectiveVisualManager.js +270 -0
- package/dist/esm/src/core/centralPlant.js +80 -11
- package/dist/esm/src/core/centralPlantInternals.js +4 -0
- package/dist/esm/src/core/centralPlantValidator.js +10 -6
- package/dist/esm/src/core/stateEngine.js +725 -0
- package/dist/esm/src/core/stateEngineScene.js +90 -0
- package/dist/esm/src/index.js +2 -0
- package/dist/esm/src/managers/controls/transformControlsManager.js +61 -2
- package/dist/esm/src/managers/pathfinding/PathFlowManager.js +94 -13
- package/dist/esm/src/managers/pathfinding/PathRenderingManager.js +15 -0
- package/dist/esm/src/managers/scene/componentTooltipManager.js +7 -1
- package/dist/esm/src/managers/scene/controlPanelManager.js +352 -0
- package/dist/esm/src/managers/scene/sceneExportManager.js +10 -1
- package/dist/esm/src/managers/state/EffectiveVisualManager.js +266 -0
- package/dist/index.d.ts +85 -0
- package/package.json +5 -2
package/dist/bundle/index.js
CHANGED
|
@@ -2143,20 +2143,24 @@ var CentralPlantValidator = /*#__PURE__*/function () {
|
|
|
2143
2143
|
key: "validateImportedSceneData",
|
|
2144
2144
|
value: function validateImportedSceneData(sceneData) {
|
|
2145
2145
|
var results = [];
|
|
2146
|
-
var hardcodedVersion = "2.3";
|
|
2147
2146
|
|
|
2148
|
-
//
|
|
2149
|
-
|
|
2147
|
+
// Supported scene formats: 2.3 (legacy I/O behaviors) and 3.0 (State Engine:
|
|
2148
|
+
// adds plant/groups/chains/stateRegistry). Both load; older scenes keep working.
|
|
2149
|
+
var supportedVersions = ["2.3", "3.0"];
|
|
2150
|
+
|
|
2151
|
+
// Version validation - MUST be one of the supported versions
|
|
2152
|
+
if (!sceneData.version || !supportedVersions.includes(sceneData.version)) {
|
|
2150
2153
|
var providedVersion = sceneData.version || 'unknown';
|
|
2151
|
-
|
|
2154
|
+
var supportedList = supportedVersions.join(' or ');
|
|
2155
|
+
alert("The central-plant requires the input file to be version ".concat(supportedList));
|
|
2152
2156
|
results.push({
|
|
2153
2157
|
isValid: false,
|
|
2154
2158
|
severity: ValidationSeverity.ERROR,
|
|
2155
|
-
message: "Invalid version: \"".concat(providedVersion, "\".
|
|
2159
|
+
message: "Invalid version: \"".concat(providedVersion, "\". Supported versions are ").concat(supportedList),
|
|
2156
2160
|
code: 'INVALID_VERSION',
|
|
2157
2161
|
metadata: {
|
|
2158
2162
|
providedVersion: providedVersion,
|
|
2159
|
-
|
|
2163
|
+
supportedVersions: supportedVersions
|
|
2160
2164
|
}
|
|
2161
2165
|
});
|
|
2162
2166
|
return this._combineValidationResults(results, 'scene_import_validation');
|
|
@@ -4109,6 +4113,12 @@ var TransformControlsManager = /*#__PURE__*/function () {
|
|
|
4109
4113
|
// Update bounding box if the transformed object is in current selection
|
|
4110
4114
|
if (_this.selectedObjects.includes(eventData.object)) {
|
|
4111
4115
|
console.log('🔲 Updating bounding boxes for selected objects after transform');
|
|
4116
|
+
// Reposition the transform gizmo (and its selection group) to follow the
|
|
4117
|
+
// object's new position. Programmatic transforms (e.g. the Transform tab's
|
|
4118
|
+
// Translate/Rotate buttons) move the object directly without touching the
|
|
4119
|
+
// multi-selection group the gizmo is attached to, so without this the widget
|
|
4120
|
+
// would stay at the pre-transform centroid.
|
|
4121
|
+
_this.syncSelectionToObjects();
|
|
4112
4122
|
_this.updateBoundingBox();
|
|
4113
4123
|
}
|
|
4114
4124
|
};
|
|
@@ -5215,6 +5225,59 @@ var TransformControlsManager = /*#__PURE__*/function () {
|
|
|
5215
5225
|
console.log("\uD83D\uDCE6 Multi-selection group created at centroid:", centroid.toArray());
|
|
5216
5226
|
}
|
|
5217
5227
|
|
|
5228
|
+
/**
|
|
5229
|
+
* Re-sync the multi-selection group (and the attached transform gizmo) to the
|
|
5230
|
+
* current world positions of the selected objects, in place.
|
|
5231
|
+
*
|
|
5232
|
+
* Used after a *programmatic* transform (e.g. the Transform tab's Translate /
|
|
5233
|
+
* Rotate buttons), which mutates the object directly and does not move the
|
|
5234
|
+
* selection group the gizmo is attached to. Unlike a full updateMultiSelection()
|
|
5235
|
+
* this keeps the same group instance so the gizmo doesn't detach/re-attach.
|
|
5236
|
+
*
|
|
5237
|
+
* It also refreshes the cached original transforms so a subsequent gizmo drag
|
|
5238
|
+
* computes its delta from the object's new position rather than jumping.
|
|
5239
|
+
*/
|
|
5240
|
+
}, {
|
|
5241
|
+
key: "syncSelectionToObjects",
|
|
5242
|
+
value: function syncSelectionToObjects() {
|
|
5243
|
+
var _this$transformContro;
|
|
5244
|
+
if (!this.multiSelectionGroup || this.selectedObjects.length === 0) {
|
|
5245
|
+
return;
|
|
5246
|
+
}
|
|
5247
|
+
|
|
5248
|
+
// Recompute the centroid from the objects' current world positions.
|
|
5249
|
+
var centroid = new THREE__namespace.Vector3();
|
|
5250
|
+
this.selectedObjects.forEach(function (obj) {
|
|
5251
|
+
obj.updateMatrixWorld(true);
|
|
5252
|
+
var worldPos = new THREE__namespace.Vector3();
|
|
5253
|
+
obj.getWorldPosition(worldPos);
|
|
5254
|
+
centroid.add(worldPos);
|
|
5255
|
+
});
|
|
5256
|
+
centroid.divideScalar(this.selectedObjects.length);
|
|
5257
|
+
|
|
5258
|
+
// Move the group (and thus the attached gizmo) to the new centroid. Reset any
|
|
5259
|
+
// rotation so the gizmo matches the freshly-created-group convention.
|
|
5260
|
+
this.multiSelectionGroup.position.copy(centroid);
|
|
5261
|
+
this.multiSelectionGroup.rotation.set(0, 0, 0);
|
|
5262
|
+
this.multiSelectionGroup.updateMatrixWorld(true);
|
|
5263
|
+
|
|
5264
|
+
// Refresh cached originals so applyRealtimeTransform's delta stays correct on
|
|
5265
|
+
// the next drag (its "original centroid" is derived from these values).
|
|
5266
|
+
this.selectedObjects.forEach(function (obj) {
|
|
5267
|
+
obj.userData._multiSelectOriginalPosition = obj.position.clone();
|
|
5268
|
+
obj.userData._multiSelectOriginalRotation = obj.rotation.clone();
|
|
5269
|
+
obj.userData._multiSelectOriginalScale = obj.scale.clone();
|
|
5270
|
+
var worldPos = new THREE__namespace.Vector3();
|
|
5271
|
+
obj.getWorldPosition(worldPos);
|
|
5272
|
+
obj.userData._multiSelectOriginalWorldPosition = worldPos;
|
|
5273
|
+
});
|
|
5274
|
+
|
|
5275
|
+
// Keep the gizmo's world matrix in sync with the repositioned group.
|
|
5276
|
+
if ((_this$transformContro = this.transformControls) !== null && _this$transformContro !== void 0 && _this$transformContro.object) {
|
|
5277
|
+
this.transformControls.updateMatrixWorld(true);
|
|
5278
|
+
}
|
|
5279
|
+
}
|
|
5280
|
+
|
|
5218
5281
|
/**
|
|
5219
5282
|
* Create a bounding box that encompasses all selected objects
|
|
5220
5283
|
*/
|
|
@@ -5494,10 +5557,10 @@ var TransformControlsManager = /*#__PURE__*/function () {
|
|
|
5494
5557
|
}, {
|
|
5495
5558
|
key: "toggleTransformMode",
|
|
5496
5559
|
value: function toggleTransformMode() {
|
|
5497
|
-
var _this$
|
|
5560
|
+
var _this$transformContro2;
|
|
5498
5561
|
var newMode = this.currentMode === 'translate' ? 'rotate' : 'translate';
|
|
5499
5562
|
this.setMode(newMode);
|
|
5500
|
-
if ((_this$
|
|
5563
|
+
if ((_this$transformContro2 = this.transformControls) !== null && _this$transformContro2 !== void 0 && _this$transformContro2.object) {
|
|
5501
5564
|
this.transformControls.updateMatrixWorld(true);
|
|
5502
5565
|
}
|
|
5503
5566
|
}
|
|
@@ -12055,6 +12118,813 @@ function generateUniqueComponentId(libraryId, scene) {
|
|
|
12055
12118
|
return id;
|
|
12056
12119
|
}
|
|
12057
12120
|
|
|
12121
|
+
/**
|
|
12122
|
+
* @file stateEngine.js
|
|
12123
|
+
* @description Portable State Engine core — registry, desired/effective resolution,
|
|
12124
|
+
* plant (and later group) gates, change notifications. No Vue/Three.js.
|
|
12125
|
+
*
|
|
12126
|
+
* Phase 1 scaffold per STATE_ENGINE_FRAMEWORK_0.3.md / milestones doc.
|
|
12127
|
+
*/
|
|
12128
|
+
|
|
12129
|
+
/**
|
|
12130
|
+
* @typedef {'desired' | 'effective'} StateLayer
|
|
12131
|
+
*/
|
|
12132
|
+
|
|
12133
|
+
/**
|
|
12134
|
+
* Build a scoped registry address.
|
|
12135
|
+
* @param {string} scope - e.g. "plant", "group:chiller-loop-a", "pump-001"
|
|
12136
|
+
* @param {string} attribute
|
|
12137
|
+
* @returns {string}
|
|
12138
|
+
*/
|
|
12139
|
+
function makeAddress(scope, attribute) {
|
|
12140
|
+
return "".concat(scope, "::").concat(attribute);
|
|
12141
|
+
}
|
|
12142
|
+
|
|
12143
|
+
/**
|
|
12144
|
+
* Shallow equality for registry values (boolean, number, string, null).
|
|
12145
|
+
* @param {*} a
|
|
12146
|
+
* @param {*} b
|
|
12147
|
+
* @returns {boolean}
|
|
12148
|
+
*/
|
|
12149
|
+
function valuesEqual(a, b) {
|
|
12150
|
+
return Object.is(a, b);
|
|
12151
|
+
}
|
|
12152
|
+
|
|
12153
|
+
/**
|
|
12154
|
+
* Safe-off value for a gated operational attribute: 0 for numbers, false for
|
|
12155
|
+
* booleans, otherwise the value unchanged.
|
|
12156
|
+
* @param {*} desired
|
|
12157
|
+
* @returns {*}
|
|
12158
|
+
*/
|
|
12159
|
+
function offValue(desired) {
|
|
12160
|
+
if (typeof desired === 'number') return 0;
|
|
12161
|
+
if (typeof desired === 'boolean') return false;
|
|
12162
|
+
return desired;
|
|
12163
|
+
}
|
|
12164
|
+
|
|
12165
|
+
/**
|
|
12166
|
+
* Central Plant State Engine (logic only).
|
|
12167
|
+
*
|
|
12168
|
+
* Desired values are stored in the registry. Effective values are computed
|
|
12169
|
+
* from gates (plant / groups / object) without erasing desired.
|
|
12170
|
+
*/
|
|
12171
|
+
var StateEngine = /*#__PURE__*/function () {
|
|
12172
|
+
/**
|
|
12173
|
+
* @param {object} [options]
|
|
12174
|
+
* @param {number} [options.maxChainDepth=8] - Ripple hop limit (Phase 3)
|
|
12175
|
+
*/
|
|
12176
|
+
function StateEngine() {
|
|
12177
|
+
var _options$maxChainDept, _options$gatedOperati;
|
|
12178
|
+
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
12179
|
+
_classCallCheck(this, StateEngine);
|
|
12180
|
+
/** @type {Map<string, *>} address → desired value */
|
|
12181
|
+
this._desired = new Map();
|
|
12182
|
+
|
|
12183
|
+
/** @type {Map<string, Set<string>>} groupId → member object ids */
|
|
12184
|
+
this._groupMembers = new Map();
|
|
12185
|
+
|
|
12186
|
+
/** @type {Map<string, *>} groupId → group desired power (and later other attrs) */
|
|
12187
|
+
this._groupDesired = new Map();
|
|
12188
|
+
|
|
12189
|
+
/** @type {boolean} */
|
|
12190
|
+
this._plantPowerDesired = true;
|
|
12191
|
+
|
|
12192
|
+
/** @type {Set<(change: { address: string, desired: *, effective: * }) => void>} */
|
|
12193
|
+
this._listeners = new Set();
|
|
12194
|
+
|
|
12195
|
+
/** @type {number} */
|
|
12196
|
+
this.maxChainDepth = (_options$maxChainDept = options.maxChainDepth) !== null && _options$maxChainDept !== void 0 ? _options$maxChainDept : 8;
|
|
12197
|
+
|
|
12198
|
+
/**
|
|
12199
|
+
* Operational attributes that read a safe-off value when their object's
|
|
12200
|
+
* power is not effectively on (Framework §6/§10: fan level 5 + plant off → 0).
|
|
12201
|
+
* Passive/meta attributes stay ungated.
|
|
12202
|
+
* @type {Set<string>}
|
|
12203
|
+
*/
|
|
12204
|
+
this._gatedOperationalAttributes = new Set((_options$gatedOperati = options.gatedOperationalAttributes) !== null && _options$gatedOperati !== void 0 ? _options$gatedOperati : ['fan-level', 'rpm', 'level', 'speed']);
|
|
12205
|
+
|
|
12206
|
+
/** @type {Array<object>} declared chains (Phase 3) */
|
|
12207
|
+
this._chains = [];
|
|
12208
|
+
|
|
12209
|
+
/** @type {boolean} */
|
|
12210
|
+
this._batching = false;
|
|
12211
|
+
|
|
12212
|
+
/** @type {Map<string, { address: string, desired: *, effective: * }>} */
|
|
12213
|
+
this._pendingNotifications = new Map();
|
|
12214
|
+
}
|
|
12215
|
+
|
|
12216
|
+
// ─── Load / reset ─────────────────────────────────────────────────────────
|
|
12217
|
+
|
|
12218
|
+
/**
|
|
12219
|
+
* Load plant, groups, and initial desired values from a scene-like declaration.
|
|
12220
|
+
* @param {object} declaration
|
|
12221
|
+
* @param {object} [declaration.plant]
|
|
12222
|
+
* @param {object} [declaration.groups]
|
|
12223
|
+
* @param {object} [declaration.stateRegistry]
|
|
12224
|
+
* @param {Array<object>} [declaration.chains]
|
|
12225
|
+
*/
|
|
12226
|
+
return _createClass(StateEngine, [{
|
|
12227
|
+
key: "load",
|
|
12228
|
+
value: function load() {
|
|
12229
|
+
var _declaration$plant;
|
|
12230
|
+
var declaration = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
12231
|
+
this._desired.clear();
|
|
12232
|
+
this._groupMembers.clear();
|
|
12233
|
+
this._groupDesired.clear();
|
|
12234
|
+
this._chains = Array.isArray(declaration.chains) ? _toConsumableArray(declaration.chains) : [];
|
|
12235
|
+
var plantDefault = ((_declaration$plant = declaration.plant) === null || _declaration$plant === void 0 || (_declaration$plant = _declaration$plant.states) === null || _declaration$plant === void 0 || (_declaration$plant = _declaration$plant.power) === null || _declaration$plant === void 0 ? void 0 : _declaration$plant.default) !== undefined ? declaration.plant.states.power.default : true;
|
|
12236
|
+
this._plantPowerDesired = Boolean(plantDefault);
|
|
12237
|
+
this._desired.set(makeAddress('plant', 'power'), this._plantPowerDesired);
|
|
12238
|
+
var groups = declaration.groups || {};
|
|
12239
|
+
for (var _i = 0, _Object$entries = Object.entries(groups); _i < _Object$entries.length; _i++) {
|
|
12240
|
+
var _group$states;
|
|
12241
|
+
var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
|
|
12242
|
+
groupId = _Object$entries$_i[0],
|
|
12243
|
+
group = _Object$entries$_i[1];
|
|
12244
|
+
var members = Array.isArray(group.members) ? group.members : [];
|
|
12245
|
+
this._groupMembers.set(groupId, new Set(members));
|
|
12246
|
+
var groupPower = ((_group$states = group.states) === null || _group$states === void 0 || (_group$states = _group$states.power) === null || _group$states === void 0 ? void 0 : _group$states.default) !== undefined ? group.states.power.default : true;
|
|
12247
|
+
this._groupDesired.set(groupId, Boolean(groupPower));
|
|
12248
|
+
this._desired.set(makeAddress("group:".concat(groupId), 'power'), Boolean(groupPower));
|
|
12249
|
+
}
|
|
12250
|
+
var registry = declaration.stateRegistry || {};
|
|
12251
|
+
for (var _i2 = 0, _Object$entries2 = Object.entries(registry); _i2 < _Object$entries2.length; _i2++) {
|
|
12252
|
+
var _Object$entries2$_i = _slicedToArray(_Object$entries2[_i2], 2),
|
|
12253
|
+
address = _Object$entries2$_i[0],
|
|
12254
|
+
value = _Object$entries2$_i[1];
|
|
12255
|
+
this._desired.set(address, value);
|
|
12256
|
+
if (address === makeAddress('plant', 'power')) {
|
|
12257
|
+
this._plantPowerDesired = Boolean(value);
|
|
12258
|
+
}
|
|
12259
|
+
var groupMatch = /^group:([^:]+)::power$/.exec(address);
|
|
12260
|
+
if (groupMatch) {
|
|
12261
|
+
this._groupDesired.set(groupMatch[1], Boolean(value));
|
|
12262
|
+
}
|
|
12263
|
+
}
|
|
12264
|
+
}
|
|
12265
|
+
}, {
|
|
12266
|
+
key: "reset",
|
|
12267
|
+
value: function reset() {
|
|
12268
|
+
this._desired.clear();
|
|
12269
|
+
this._groupMembers.clear();
|
|
12270
|
+
this._groupDesired.clear();
|
|
12271
|
+
this._chains = [];
|
|
12272
|
+
this._plantPowerDesired = true;
|
|
12273
|
+
this._pendingNotifications.clear();
|
|
12274
|
+
this._batching = false;
|
|
12275
|
+
}
|
|
12276
|
+
|
|
12277
|
+
// ─── Desired / effective ──────────────────────────────────────────────────
|
|
12278
|
+
|
|
12279
|
+
/**
|
|
12280
|
+
* @param {string} address
|
|
12281
|
+
* @returns {*}
|
|
12282
|
+
*/
|
|
12283
|
+
}, {
|
|
12284
|
+
key: "getDesired",
|
|
12285
|
+
value: function getDesired(address) {
|
|
12286
|
+
return this._desired.has(address) ? this._desired.get(address) : undefined;
|
|
12287
|
+
}
|
|
12288
|
+
|
|
12289
|
+
/**
|
|
12290
|
+
* Compute effective value for an address.
|
|
12291
|
+
* Power-style gates: plant AND all containing groups AND object desired.
|
|
12292
|
+
* Non-power attributes: return desired when power gates allow, else a safe off value.
|
|
12293
|
+
*
|
|
12294
|
+
* @param {string} address
|
|
12295
|
+
* @returns {*}
|
|
12296
|
+
*/
|
|
12297
|
+
}, {
|
|
12298
|
+
key: "getEffective",
|
|
12299
|
+
value: function getEffective(address) {
|
|
12300
|
+
var desired = this.getDesired(address);
|
|
12301
|
+
if (desired === undefined) return undefined;
|
|
12302
|
+
if (address === makeAddress('plant', 'power')) {
|
|
12303
|
+
return Boolean(desired);
|
|
12304
|
+
}
|
|
12305
|
+
var groupPowerMatch = /^group:([^:]+)::power$/.exec(address);
|
|
12306
|
+
if (groupPowerMatch) {
|
|
12307
|
+
return this._plantPowerDesired && Boolean(desired);
|
|
12308
|
+
}
|
|
12309
|
+
var objectPowerMatch = /^([^:]+)::power$/.exec(address);
|
|
12310
|
+
if (objectPowerMatch && !address.startsWith('group:') && !address.startsWith('plant::')) {
|
|
12311
|
+
var objectId = objectPowerMatch[1];
|
|
12312
|
+
// Skip nested I/O addresses like pump-001::start-switch::mode
|
|
12313
|
+
if (address.split('::').length !== 2) {
|
|
12314
|
+
return desired;
|
|
12315
|
+
}
|
|
12316
|
+
return this._isPowerAllowedForObject(objectId) && Boolean(desired);
|
|
12317
|
+
}
|
|
12318
|
+
|
|
12319
|
+
// Flow and other attrs: when tied to an object id prefix, gate on that object's power path
|
|
12320
|
+
var parts = address.split('::');
|
|
12321
|
+
if (parts.length === 2) {
|
|
12322
|
+
var _parts = _slicedToArray(parts, 2),
|
|
12323
|
+
_objectId = _parts[0],
|
|
12324
|
+
attribute = _parts[1];
|
|
12325
|
+
if (attribute === 'flow') {
|
|
12326
|
+
var powerAllowed = this._isPowerAllowedForObject(_objectId);
|
|
12327
|
+
if (!powerAllowed) return false;
|
|
12328
|
+
return Boolean(desired);
|
|
12329
|
+
}
|
|
12330
|
+
// Operational values (fan level, rpm, …) collapse to a safe-off value when
|
|
12331
|
+
// the object's power is not effectively on; restored when power returns (§6, §10).
|
|
12332
|
+
if (this._gatedOperationalAttributes.has(attribute)) {
|
|
12333
|
+
if (!this._isPowerAllowedForObject(_objectId)) return offValue(desired);
|
|
12334
|
+
return desired;
|
|
12335
|
+
}
|
|
12336
|
+
}
|
|
12337
|
+
return desired;
|
|
12338
|
+
}
|
|
12339
|
+
|
|
12340
|
+
/**
|
|
12341
|
+
* @param {string} objectId
|
|
12342
|
+
* @returns {boolean}
|
|
12343
|
+
*/
|
|
12344
|
+
}, {
|
|
12345
|
+
key: "_isPowerAllowedForObject",
|
|
12346
|
+
value: function _isPowerAllowedForObject(objectId) {
|
|
12347
|
+
if (!this._plantPowerDesired) return false;
|
|
12348
|
+
var _iterator = _createForOfIteratorHelper(this._groupMembers),
|
|
12349
|
+
_step;
|
|
12350
|
+
try {
|
|
12351
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
12352
|
+
var _step$value = _slicedToArray(_step.value, 2),
|
|
12353
|
+
groupId = _step$value[0],
|
|
12354
|
+
members = _step$value[1];
|
|
12355
|
+
if (members.has(objectId)) {
|
|
12356
|
+
var groupOn = this._groupDesired.get(groupId);
|
|
12357
|
+
if (!groupOn) return false;
|
|
12358
|
+
}
|
|
12359
|
+
}
|
|
12360
|
+
} catch (err) {
|
|
12361
|
+
_iterator.e(err);
|
|
12362
|
+
} finally {
|
|
12363
|
+
_iterator.f();
|
|
12364
|
+
}
|
|
12365
|
+
return true;
|
|
12366
|
+
}
|
|
12367
|
+
|
|
12368
|
+
/**
|
|
12369
|
+
* Groups that contain an object.
|
|
12370
|
+
* @param {string} objectId
|
|
12371
|
+
* @returns {string[]}
|
|
12372
|
+
*/
|
|
12373
|
+
}, {
|
|
12374
|
+
key: "getGroupsForObject",
|
|
12375
|
+
value: function getGroupsForObject(objectId) {
|
|
12376
|
+
var result = [];
|
|
12377
|
+
var _iterator2 = _createForOfIteratorHelper(this._groupMembers),
|
|
12378
|
+
_step2;
|
|
12379
|
+
try {
|
|
12380
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
12381
|
+
var _step2$value = _slicedToArray(_step2.value, 2),
|
|
12382
|
+
groupId = _step2$value[0],
|
|
12383
|
+
members = _step2$value[1];
|
|
12384
|
+
if (members.has(objectId)) result.push(groupId);
|
|
12385
|
+
}
|
|
12386
|
+
} catch (err) {
|
|
12387
|
+
_iterator2.e(err);
|
|
12388
|
+
} finally {
|
|
12389
|
+
_iterator2.f();
|
|
12390
|
+
}
|
|
12391
|
+
return result;
|
|
12392
|
+
}
|
|
12393
|
+
|
|
12394
|
+
// ─── Writes ───────────────────────────────────────────────────────────────
|
|
12395
|
+
|
|
12396
|
+
/**
|
|
12397
|
+
* Set a desired value through the single write door and run one bounded
|
|
12398
|
+
* propagation pass: no-op stop, gate recompute, declared chains with depth
|
|
12399
|
+
* and visited-set guards, then batched notifications (Framework §11, §12).
|
|
12400
|
+
*
|
|
12401
|
+
* @param {string} address
|
|
12402
|
+
* @param {*} value
|
|
12403
|
+
* @returns {boolean} true if the desired value changed
|
|
12404
|
+
*/
|
|
12405
|
+
}, {
|
|
12406
|
+
key: "setDesired",
|
|
12407
|
+
value: function setDesired(address, value) {
|
|
12408
|
+
this._beginBatch();
|
|
12409
|
+
var ctx = {
|
|
12410
|
+
visited: new Map()
|
|
12411
|
+
};
|
|
12412
|
+
var changed = this._writeHop(address, value, 0, ctx);
|
|
12413
|
+
this._endBatch();
|
|
12414
|
+
return changed;
|
|
12415
|
+
}
|
|
12416
|
+
|
|
12417
|
+
/**
|
|
12418
|
+
* One hop of a propagation pass: apply the write, then walk declared chains.
|
|
12419
|
+
* @param {string} address
|
|
12420
|
+
* @param {*} value
|
|
12421
|
+
* @param {number} depth
|
|
12422
|
+
* @param {{ visited: Map<string, *> }} ctx
|
|
12423
|
+
* @returns {boolean} whether this hop changed the desired value
|
|
12424
|
+
*/
|
|
12425
|
+
}, {
|
|
12426
|
+
key: "_writeHop",
|
|
12427
|
+
value: function _writeHop(address, value, depth, ctx) {
|
|
12428
|
+
var result = this._applyWrite(address, value, ctx);
|
|
12429
|
+
if (!result.changed) return false;
|
|
12430
|
+
this._walkChains(result, depth, ctx);
|
|
12431
|
+
return true;
|
|
12432
|
+
}
|
|
12433
|
+
|
|
12434
|
+
/**
|
|
12435
|
+
* Atomic write door: no-op stop (§11.1), visited-set cycle guard (§11.5),
|
|
12436
|
+
* store desired, update gate mirrors, compute which effectives changed, and
|
|
12437
|
+
* queue notifications for changed addresses only.
|
|
12438
|
+
*
|
|
12439
|
+
* @param {string} address
|
|
12440
|
+
* @param {*} value
|
|
12441
|
+
* @param {{ visited: Map<string, *> }} ctx
|
|
12442
|
+
* @returns {{ changed: boolean, desiredChanged?: string, effectiveChanged?: Set<string> }}
|
|
12443
|
+
*/
|
|
12444
|
+
}, {
|
|
12445
|
+
key: "_applyWrite",
|
|
12446
|
+
value: function _applyWrite(address, value, ctx) {
|
|
12447
|
+
var previous = this.getDesired(address);
|
|
12448
|
+
if (previous !== undefined && valuesEqual(previous, value)) {
|
|
12449
|
+
return {
|
|
12450
|
+
changed: false
|
|
12451
|
+
}; // §11.1 no-op stop
|
|
12452
|
+
}
|
|
12453
|
+
|
|
12454
|
+
// §11.5 visited-set: writing the same pair a different value this pass = cycle conflict
|
|
12455
|
+
if (ctx.visited.has(address) && !valuesEqual(ctx.visited.get(address), value)) {
|
|
12456
|
+
console.warn("[StateEngine] cycle conflict: ".concat(address, " rewritten to a different value in one pass; stopping branch"));
|
|
12457
|
+
return {
|
|
12458
|
+
changed: false
|
|
12459
|
+
};
|
|
12460
|
+
}
|
|
12461
|
+
ctx.visited.set(address, value);
|
|
12462
|
+
|
|
12463
|
+
// Snapshot effective for every address this write might gate, before mutating.
|
|
12464
|
+
var affected = this._affectedByWrite(address);
|
|
12465
|
+
var before = new Map();
|
|
12466
|
+
var _iterator3 = _createForOfIteratorHelper(affected),
|
|
12467
|
+
_step3;
|
|
12468
|
+
try {
|
|
12469
|
+
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
|
|
12470
|
+
var a = _step3.value;
|
|
12471
|
+
before.set(a, this.getEffective(a));
|
|
12472
|
+
}
|
|
12473
|
+
|
|
12474
|
+
// Store desired + keep plant/group gate mirrors in sync.
|
|
12475
|
+
} catch (err) {
|
|
12476
|
+
_iterator3.e(err);
|
|
12477
|
+
} finally {
|
|
12478
|
+
_iterator3.f();
|
|
12479
|
+
}
|
|
12480
|
+
this._desired.set(address, value);
|
|
12481
|
+
if (address === makeAddress('plant', 'power')) {
|
|
12482
|
+
this._plantPowerDesired = Boolean(value);
|
|
12483
|
+
}
|
|
12484
|
+
var groupMatch = /^group:([^:]+)::power$/.exec(address);
|
|
12485
|
+
if (groupMatch) {
|
|
12486
|
+
this._groupDesired.set(groupMatch[1], Boolean(value));
|
|
12487
|
+
}
|
|
12488
|
+
|
|
12489
|
+
// Which effectives actually changed (§11.4 dedupe: notify once, changed only).
|
|
12490
|
+
var effectiveChanged = new Set();
|
|
12491
|
+
var _iterator4 = _createForOfIteratorHelper(affected),
|
|
12492
|
+
_step4;
|
|
12493
|
+
try {
|
|
12494
|
+
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
|
|
12495
|
+
var _a = _step4.value;
|
|
12496
|
+
if (!valuesEqual(before.get(_a), this.getEffective(_a))) effectiveChanged.add(_a);
|
|
12497
|
+
}
|
|
12498
|
+
} catch (err) {
|
|
12499
|
+
_iterator4.e(err);
|
|
12500
|
+
} finally {
|
|
12501
|
+
_iterator4.f();
|
|
12502
|
+
}
|
|
12503
|
+
this._queueNotification(address); // desired changed
|
|
12504
|
+
var _iterator5 = _createForOfIteratorHelper(effectiveChanged),
|
|
12505
|
+
_step5;
|
|
12506
|
+
try {
|
|
12507
|
+
for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
|
|
12508
|
+
var _a2 = _step5.value;
|
|
12509
|
+
if (_a2 !== address) this._queueNotification(_a2);
|
|
12510
|
+
}
|
|
12511
|
+
} catch (err) {
|
|
12512
|
+
_iterator5.e(err);
|
|
12513
|
+
} finally {
|
|
12514
|
+
_iterator5.f();
|
|
12515
|
+
}
|
|
12516
|
+
return {
|
|
12517
|
+
changed: true,
|
|
12518
|
+
desiredChanged: address,
|
|
12519
|
+
effectiveChanged: effectiveChanged
|
|
12520
|
+
};
|
|
12521
|
+
}
|
|
12522
|
+
|
|
12523
|
+
/**
|
|
12524
|
+
* Addresses whose effective value a write to `address` could change: the
|
|
12525
|
+
* address itself, plus gate dependents (all keys for plant power, member
|
|
12526
|
+
* keys for group power).
|
|
12527
|
+
* @param {string} address
|
|
12528
|
+
* @returns {string[]}
|
|
12529
|
+
*/
|
|
12530
|
+
}, {
|
|
12531
|
+
key: "_affectedByWrite",
|
|
12532
|
+
value: function _affectedByWrite(address) {
|
|
12533
|
+
var affected = new Set([address]);
|
|
12534
|
+
if (address === makeAddress('plant', 'power')) {
|
|
12535
|
+
var _iterator6 = _createForOfIteratorHelper(this._desired.keys()),
|
|
12536
|
+
_step6;
|
|
12537
|
+
try {
|
|
12538
|
+
for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
|
|
12539
|
+
var key = _step6.value;
|
|
12540
|
+
affected.add(key);
|
|
12541
|
+
}
|
|
12542
|
+
} catch (err) {
|
|
12543
|
+
_iterator6.e(err);
|
|
12544
|
+
} finally {
|
|
12545
|
+
_iterator6.f();
|
|
12546
|
+
}
|
|
12547
|
+
return _toConsumableArray(affected);
|
|
12548
|
+
}
|
|
12549
|
+
var groupMatch = /^group:([^:]+)::power$/.exec(address);
|
|
12550
|
+
if (groupMatch) {
|
|
12551
|
+
var members = this._groupMembers.get(groupMatch[1]);
|
|
12552
|
+
if (members) {
|
|
12553
|
+
var _iterator7 = _createForOfIteratorHelper(members),
|
|
12554
|
+
_step7;
|
|
12555
|
+
try {
|
|
12556
|
+
for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
|
|
12557
|
+
var memberId = _step7.value;
|
|
12558
|
+
var _iterator8 = _createForOfIteratorHelper(this._desired.keys()),
|
|
12559
|
+
_step8;
|
|
12560
|
+
try {
|
|
12561
|
+
for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
|
|
12562
|
+
var _key = _step8.value;
|
|
12563
|
+
if (_key.startsWith("".concat(memberId, "::"))) affected.add(_key);
|
|
12564
|
+
}
|
|
12565
|
+
} catch (err) {
|
|
12566
|
+
_iterator8.e(err);
|
|
12567
|
+
} finally {
|
|
12568
|
+
_iterator8.f();
|
|
12569
|
+
}
|
|
12570
|
+
}
|
|
12571
|
+
} catch (err) {
|
|
12572
|
+
_iterator7.e(err);
|
|
12573
|
+
} finally {
|
|
12574
|
+
_iterator7.f();
|
|
12575
|
+
}
|
|
12576
|
+
}
|
|
12577
|
+
}
|
|
12578
|
+
return _toConsumableArray(affected);
|
|
12579
|
+
}
|
|
12580
|
+
|
|
12581
|
+
/**
|
|
12582
|
+
* Walk declared chains triggered by this write (§10, §11.3). Desired-source
|
|
12583
|
+
* chains fire when the trigger's desired changed; effective-source chains fire
|
|
12584
|
+
* when the trigger's effective changed (so plant/group-off ripples through
|
|
12585
|
+
* `pump effective → pipe flow`, §17). Only `copy` is supported.
|
|
12586
|
+
*
|
|
12587
|
+
* @param {{ desiredChanged?: string, effectiveChanged?: Set<string> }} result
|
|
12588
|
+
* @param {number} depth
|
|
12589
|
+
* @param {{ visited: Map<string, *> }} ctx
|
|
12590
|
+
*/
|
|
12591
|
+
}, {
|
|
12592
|
+
key: "_walkChains",
|
|
12593
|
+
value: function _walkChains(result, depth, ctx) {
|
|
12594
|
+
if (this._chains.length === 0) return;
|
|
12595
|
+
var _iterator9 = _createForOfIteratorHelper(this._chains),
|
|
12596
|
+
_step9;
|
|
12597
|
+
try {
|
|
12598
|
+
for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) {
|
|
12599
|
+
var _result$effectiveChan;
|
|
12600
|
+
var chain = _step9.value;
|
|
12601
|
+
if (chain.action && chain.action !== 'copy') continue;
|
|
12602
|
+
var trigger = makeAddress(chain.when.address, chain.when.attribute);
|
|
12603
|
+
var useEffective = chain.when.source === 'effective';
|
|
12604
|
+
var fires = useEffective ? (_result$effectiveChan = result.effectiveChanged) === null || _result$effectiveChan === void 0 ? void 0 : _result$effectiveChan.has(trigger) : result.desiredChanged === trigger;
|
|
12605
|
+
if (!fires) continue;
|
|
12606
|
+
if (depth + 1 > this.maxChainDepth) {
|
|
12607
|
+
var _chain$id;
|
|
12608
|
+
console.warn("[StateEngine] ripple depth limit (".concat(this.maxChainDepth, ") reached at chain ") + "\"".concat((_chain$id = chain.id) !== null && _chain$id !== void 0 ? _chain$id : '<unnamed>', "\" (").concat(trigger, " \u2192 ").concat(makeAddress(chain.set.address, chain.set.attribute), "); stopping path"));
|
|
12609
|
+
continue;
|
|
12610
|
+
}
|
|
12611
|
+
var nextValue = useEffective ? this.getEffective(trigger) : this.getDesired(trigger);
|
|
12612
|
+
var target = makeAddress(chain.set.address, chain.set.attribute);
|
|
12613
|
+
this._writeHop(target, nextValue, depth + 1, ctx);
|
|
12614
|
+
}
|
|
12615
|
+
} catch (err) {
|
|
12616
|
+
_iterator9.e(err);
|
|
12617
|
+
} finally {
|
|
12618
|
+
_iterator9.f();
|
|
12619
|
+
}
|
|
12620
|
+
}
|
|
12621
|
+
|
|
12622
|
+
// ─── Pipe lifecycle (Framework §9, §15) ─────────────────────────────────────
|
|
12623
|
+
|
|
12624
|
+
/**
|
|
12625
|
+
* Register a pipe/path in the registry so its flow state exists and is tied to
|
|
12626
|
+
* pipe lifecycle (no orphan keys). Keyed on the pipe/path id, never on segment
|
|
12627
|
+
* declared/computed status (§9). No-op if already registered.
|
|
12628
|
+
* @param {string} pipeId
|
|
12629
|
+
* @param {object} [options]
|
|
12630
|
+
* @param {boolean} [options.defaultFlow=false]
|
|
12631
|
+
* @returns {string} the flow address
|
|
12632
|
+
*/
|
|
12633
|
+
}, {
|
|
12634
|
+
key: "registerPipe",
|
|
12635
|
+
value: function registerPipe(pipeId) {
|
|
12636
|
+
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
12637
|
+
var address = makeAddress(pipeId, 'flow');
|
|
12638
|
+
if (!this._desired.has(address)) {
|
|
12639
|
+
var _options$defaultFlow;
|
|
12640
|
+
this._beginBatch();
|
|
12641
|
+
this._desired.set(address, Boolean((_options$defaultFlow = options.defaultFlow) !== null && _options$defaultFlow !== void 0 ? _options$defaultFlow : false));
|
|
12642
|
+
this._queueNotification(address);
|
|
12643
|
+
this._endBatch();
|
|
12644
|
+
}
|
|
12645
|
+
return address;
|
|
12646
|
+
}
|
|
12647
|
+
|
|
12648
|
+
/**
|
|
12649
|
+
* Remove a pipe and all of its registry entries when the pipe is destroyed, so
|
|
12650
|
+
* keys do not linger (§15). Emits a final notification (desired/effective
|
|
12651
|
+
* undefined) per removed key.
|
|
12652
|
+
* @param {string} pipeId
|
|
12653
|
+
* @returns {number} number of registry keys removed
|
|
12654
|
+
*/
|
|
12655
|
+
}, {
|
|
12656
|
+
key: "unregisterPipe",
|
|
12657
|
+
value: function unregisterPipe(pipeId) {
|
|
12658
|
+
var prefix = "".concat(pipeId, "::");
|
|
12659
|
+
var removed = _toConsumableArray(this._desired.keys()).filter(function (k) {
|
|
12660
|
+
return k.startsWith(prefix);
|
|
12661
|
+
});
|
|
12662
|
+
if (removed.length === 0) return 0;
|
|
12663
|
+
this._beginBatch();
|
|
12664
|
+
var _iterator0 = _createForOfIteratorHelper(removed),
|
|
12665
|
+
_step0;
|
|
12666
|
+
try {
|
|
12667
|
+
for (_iterator0.s(); !(_step0 = _iterator0.n()).done;) {
|
|
12668
|
+
var key = _step0.value;
|
|
12669
|
+
this._desired.delete(key);
|
|
12670
|
+
}
|
|
12671
|
+
} catch (err) {
|
|
12672
|
+
_iterator0.e(err);
|
|
12673
|
+
} finally {
|
|
12674
|
+
_iterator0.f();
|
|
12675
|
+
}
|
|
12676
|
+
var _iterator1 = _createForOfIteratorHelper(removed),
|
|
12677
|
+
_step1;
|
|
12678
|
+
try {
|
|
12679
|
+
for (_iterator1.s(); !(_step1 = _iterator1.n()).done;) {
|
|
12680
|
+
var _key2 = _step1.value;
|
|
12681
|
+
this._queueNotification(_key2);
|
|
12682
|
+
}
|
|
12683
|
+
} catch (err) {
|
|
12684
|
+
_iterator1.e(err);
|
|
12685
|
+
} finally {
|
|
12686
|
+
_iterator1.f();
|
|
12687
|
+
}
|
|
12688
|
+
this._endBatch();
|
|
12689
|
+
return removed.length;
|
|
12690
|
+
}
|
|
12691
|
+
}, {
|
|
12692
|
+
key: "_beginBatch",
|
|
12693
|
+
value: function _beginBatch() {
|
|
12694
|
+
this._batching = true;
|
|
12695
|
+
this._pendingNotifications.clear();
|
|
12696
|
+
}
|
|
12697
|
+
}, {
|
|
12698
|
+
key: "_queueNotification",
|
|
12699
|
+
value: function _queueNotification(address) {
|
|
12700
|
+
var change = {
|
|
12701
|
+
address: address,
|
|
12702
|
+
desired: this.getDesired(address),
|
|
12703
|
+
effective: this.getEffective(address)
|
|
12704
|
+
};
|
|
12705
|
+
if (this._batching) {
|
|
12706
|
+
this._pendingNotifications.set(address, change);
|
|
12707
|
+
} else {
|
|
12708
|
+
this._emit(change);
|
|
12709
|
+
}
|
|
12710
|
+
}
|
|
12711
|
+
}, {
|
|
12712
|
+
key: "_endBatch",
|
|
12713
|
+
value: function _endBatch() {
|
|
12714
|
+
this._batching = false;
|
|
12715
|
+
var changes = _toConsumableArray(this._pendingNotifications.values());
|
|
12716
|
+
this._pendingNotifications.clear();
|
|
12717
|
+
var _iterator10 = _createForOfIteratorHelper(changes),
|
|
12718
|
+
_step10;
|
|
12719
|
+
try {
|
|
12720
|
+
for (_iterator10.s(); !(_step10 = _iterator10.n()).done;) {
|
|
12721
|
+
var change = _step10.value;
|
|
12722
|
+
this._emit(change);
|
|
12723
|
+
}
|
|
12724
|
+
} catch (err) {
|
|
12725
|
+
_iterator10.e(err);
|
|
12726
|
+
} finally {
|
|
12727
|
+
_iterator10.f();
|
|
12728
|
+
}
|
|
12729
|
+
}
|
|
12730
|
+
}, {
|
|
12731
|
+
key: "_emit",
|
|
12732
|
+
value: function _emit(change) {
|
|
12733
|
+
var _iterator11 = _createForOfIteratorHelper(this._listeners),
|
|
12734
|
+
_step11;
|
|
12735
|
+
try {
|
|
12736
|
+
for (_iterator11.s(); !(_step11 = _iterator11.n()).done;) {
|
|
12737
|
+
var listener = _step11.value;
|
|
12738
|
+
try {
|
|
12739
|
+
listener(change);
|
|
12740
|
+
} catch (err) {
|
|
12741
|
+
console.error('[StateEngine] listener error:', err);
|
|
12742
|
+
}
|
|
12743
|
+
}
|
|
12744
|
+
} catch (err) {
|
|
12745
|
+
_iterator11.e(err);
|
|
12746
|
+
} finally {
|
|
12747
|
+
_iterator11.f();
|
|
12748
|
+
}
|
|
12749
|
+
}
|
|
12750
|
+
|
|
12751
|
+
/**
|
|
12752
|
+
* @param {(change: { address: string, desired: *, effective: * }) => void} listener
|
|
12753
|
+
* @returns {() => void} unsubscribe
|
|
12754
|
+
*/
|
|
12755
|
+
}, {
|
|
12756
|
+
key: "subscribe",
|
|
12757
|
+
value: function subscribe(listener) {
|
|
12758
|
+
var _this = this;
|
|
12759
|
+
this._listeners.add(listener);
|
|
12760
|
+
return function () {
|
|
12761
|
+
return _this._listeners.delete(listener);
|
|
12762
|
+
};
|
|
12763
|
+
}
|
|
12764
|
+
|
|
12765
|
+
/**
|
|
12766
|
+
* Snapshot of all desired values (for save / debug).
|
|
12767
|
+
* @returns {Record<string, *>}
|
|
12768
|
+
*/
|
|
12769
|
+
}, {
|
|
12770
|
+
key: "toRegistry",
|
|
12771
|
+
value: function toRegistry() {
|
|
12772
|
+
var out = {};
|
|
12773
|
+
var _iterator12 = _createForOfIteratorHelper(this._desired),
|
|
12774
|
+
_step12;
|
|
12775
|
+
try {
|
|
12776
|
+
for (_iterator12.s(); !(_step12 = _iterator12.n()).done;) {
|
|
12777
|
+
var _step12$value = _slicedToArray(_step12.value, 2),
|
|
12778
|
+
k = _step12$value[0],
|
|
12779
|
+
v = _step12$value[1];
|
|
12780
|
+
out[k] = v;
|
|
12781
|
+
}
|
|
12782
|
+
} catch (err) {
|
|
12783
|
+
_iterator12.e(err);
|
|
12784
|
+
} finally {
|
|
12785
|
+
_iterator12.f();
|
|
12786
|
+
}
|
|
12787
|
+
return out;
|
|
12788
|
+
}
|
|
12789
|
+
|
|
12790
|
+
/**
|
|
12791
|
+
* Reconstruct the §17 scene declaration (plant / groups / chains /
|
|
12792
|
+
* stateRegistry) from current engine state, for scene save. Round-trips with
|
|
12793
|
+
* {@link StateEngine#load}.
|
|
12794
|
+
* @returns {{ version: string, plant: object, groups: object, chains: object[], stateRegistry: Record<string, *> }}
|
|
12795
|
+
*/
|
|
12796
|
+
}, {
|
|
12797
|
+
key: "toDeclaration",
|
|
12798
|
+
value: function toDeclaration() {
|
|
12799
|
+
var groups = {};
|
|
12800
|
+
var _iterator13 = _createForOfIteratorHelper(this._groupMembers),
|
|
12801
|
+
_step13;
|
|
12802
|
+
try {
|
|
12803
|
+
for (_iterator13.s(); !(_step13 = _iterator13.n()).done;) {
|
|
12804
|
+
var _this$_groupDesired$g;
|
|
12805
|
+
var _step13$value = _slicedToArray(_step13.value, 2),
|
|
12806
|
+
groupId = _step13$value[0],
|
|
12807
|
+
members = _step13$value[1];
|
|
12808
|
+
groups[groupId] = {
|
|
12809
|
+
members: _toConsumableArray(members),
|
|
12810
|
+
states: {
|
|
12811
|
+
power: {
|
|
12812
|
+
type: 'Boolean',
|
|
12813
|
+
default: (_this$_groupDesired$g = this._groupDesired.get(groupId)) !== null && _this$_groupDesired$g !== void 0 ? _this$_groupDesired$g : true,
|
|
12814
|
+
gate: true
|
|
12815
|
+
}
|
|
12816
|
+
}
|
|
12817
|
+
};
|
|
12818
|
+
}
|
|
12819
|
+
} catch (err) {
|
|
12820
|
+
_iterator13.e(err);
|
|
12821
|
+
} finally {
|
|
12822
|
+
_iterator13.f();
|
|
12823
|
+
}
|
|
12824
|
+
return {
|
|
12825
|
+
version: '3.0',
|
|
12826
|
+
plant: {
|
|
12827
|
+
states: {
|
|
12828
|
+
power: {
|
|
12829
|
+
type: 'Boolean',
|
|
12830
|
+
default: this._plantPowerDesired,
|
|
12831
|
+
gate: true
|
|
12832
|
+
}
|
|
12833
|
+
}
|
|
12834
|
+
},
|
|
12835
|
+
groups: groups,
|
|
12836
|
+
chains: _toConsumableArray(this._chains),
|
|
12837
|
+
stateRegistry: this.toRegistry()
|
|
12838
|
+
};
|
|
12839
|
+
}
|
|
12840
|
+
}]);
|
|
12841
|
+
}();
|
|
12842
|
+
|
|
12843
|
+
/**
|
|
12844
|
+
* Registry address for an I/O device data point.
|
|
12845
|
+
* Shape: `${parentUuid}::${attachmentId}::${stateId}` (matches §17
|
|
12846
|
+
* `pump-001::start-switch::mode`). The scoped key is `${parentUuid}::${attachmentId}`.
|
|
12847
|
+
*
|
|
12848
|
+
* @param {string} attachmentId
|
|
12849
|
+
* @param {string} stateId
|
|
12850
|
+
* @param {string} [parentUuid]
|
|
12851
|
+
* @returns {string}
|
|
12852
|
+
*/
|
|
12853
|
+
function ioAddress(attachmentId, stateId, parentUuid) {
|
|
12854
|
+
return makeAddress(getScopedAttachmentKey(attachmentId, parentUuid), stateId);
|
|
12855
|
+
}
|
|
12856
|
+
|
|
12857
|
+
/**
|
|
12858
|
+
* Parse an I/O device address back into its parts. Returns null for
|
|
12859
|
+
* plant/group/object/pipe addresses (2-part) — I/O device data points are 3-part
|
|
12860
|
+
* because a device always has a parent component (§8; free-floating I/O rejected).
|
|
12861
|
+
*
|
|
12862
|
+
* @param {string} address
|
|
12863
|
+
* @returns {{ parentUuid: string, attachmentId: string, stateId: string, scopedKey: string } | null}
|
|
12864
|
+
*/
|
|
12865
|
+
function parseIoAddress(address) {
|
|
12866
|
+
if (typeof address !== 'string') return null;
|
|
12867
|
+
var parts = address.split('::');
|
|
12868
|
+
if (parts.length !== 3) return null;
|
|
12869
|
+
var _parts = _slicedToArray(parts, 3),
|
|
12870
|
+
parentUuid = _parts[0],
|
|
12871
|
+
attachmentId = _parts[1],
|
|
12872
|
+
stateId = _parts[2];
|
|
12873
|
+
return {
|
|
12874
|
+
parentUuid: parentUuid,
|
|
12875
|
+
attachmentId: attachmentId,
|
|
12876
|
+
stateId: stateId,
|
|
12877
|
+
scopedKey: "".concat(parentUuid, "::").concat(attachmentId)
|
|
12878
|
+
};
|
|
12879
|
+
}
|
|
12880
|
+
|
|
12881
|
+
/**
|
|
12882
|
+
* Read a State Engine declaration out of scene data, tolerating legacy scenes.
|
|
12883
|
+
* v3.0 scenes carry `plant` / `groups` / `chains` / `stateRegistry`; older v2.3
|
|
12884
|
+
* scenes have none of these and load with safe defaults (plant power on, no
|
|
12885
|
+
* groups, no chains) so nothing crashes on unknown/missing fields (§15).
|
|
12886
|
+
*
|
|
12887
|
+
* @param {object} [sceneData]
|
|
12888
|
+
* @returns {{ plant: object, groups: object, chains: object[], stateRegistry: object }}
|
|
12889
|
+
*/
|
|
12890
|
+
function extractDeclaration() {
|
|
12891
|
+
var sceneData = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
12892
|
+
return {
|
|
12893
|
+
plant: sceneData.plant || {
|
|
12894
|
+
states: {
|
|
12895
|
+
power: {
|
|
12896
|
+
type: 'Boolean',
|
|
12897
|
+
default: true,
|
|
12898
|
+
gate: true
|
|
12899
|
+
}
|
|
12900
|
+
}
|
|
12901
|
+
},
|
|
12902
|
+
groups: sceneData.groups || {},
|
|
12903
|
+
chains: Array.isArray(sceneData.chains) ? sceneData.chains : [],
|
|
12904
|
+
stateRegistry: sceneData.stateRegistry || {}
|
|
12905
|
+
};
|
|
12906
|
+
}
|
|
12907
|
+
|
|
12908
|
+
/**
|
|
12909
|
+
* Merge a State Engine's current declaration (plant / groups / chains /
|
|
12910
|
+
* stateRegistry) into an export object, bumping it to version 3.0. Mutates and
|
|
12911
|
+
* returns `exportData`.
|
|
12912
|
+
*
|
|
12913
|
+
* @param {object} exportData - the scene export object being assembled
|
|
12914
|
+
* @param {{ toDeclaration: () => object }} engine
|
|
12915
|
+
* @returns {object} exportData
|
|
12916
|
+
*/
|
|
12917
|
+
function applyDeclarationToExport(exportData, engine) {
|
|
12918
|
+
if (!exportData || !engine || typeof engine.toDeclaration !== 'function') return exportData;
|
|
12919
|
+
var declaration = engine.toDeclaration();
|
|
12920
|
+
exportData.version = '3.0';
|
|
12921
|
+
exportData.plant = declaration.plant;
|
|
12922
|
+
exportData.groups = declaration.groups;
|
|
12923
|
+
if (declaration.chains.length > 0) exportData.chains = declaration.chains;
|
|
12924
|
+
exportData.stateRegistry = declaration.stateRegistry;
|
|
12925
|
+
return exportData;
|
|
12926
|
+
}
|
|
12927
|
+
|
|
12058
12928
|
var SceneExportManager = /*#__PURE__*/function () {
|
|
12059
12929
|
function SceneExportManager(sceneViewer) {
|
|
12060
12930
|
_classCallCheck(this, SceneExportManager);
|
|
@@ -12108,7 +12978,8 @@ var SceneExportManager = /*#__PURE__*/function () {
|
|
|
12108
12978
|
key: "exportSceneData",
|
|
12109
12979
|
value: function exportSceneData() {
|
|
12110
12980
|
var _this = this,
|
|
12111
|
-
_this$sceneViewer$cur2
|
|
12981
|
+
_this$sceneViewer$cur2,
|
|
12982
|
+
_this$sceneViewer$cen;
|
|
12112
12983
|
console.log('📤 Starting scene export...');
|
|
12113
12984
|
if (!this.sceneViewer.scene) {
|
|
12114
12985
|
console.warn('⚠️ No scene available for export');
|
|
@@ -12341,6 +13212,13 @@ var SceneExportManager = /*#__PURE__*/function () {
|
|
|
12341
13212
|
if (sceneBehaviors && sceneBehaviors.length > 0) {
|
|
12342
13213
|
exportData.behaviors = sceneBehaviors;
|
|
12343
13214
|
}
|
|
13215
|
+
|
|
13216
|
+
// Persist plant / groups / chains / stateRegistry from the State Engine and
|
|
13217
|
+
// bump the format to 3.0 (Framework §12; no-op if no engine present).
|
|
13218
|
+
var stateEngine = (_this$sceneViewer$cen = this.sceneViewer.centralPlant) === null || _this$sceneViewer$cen === void 0 ? void 0 : _this$sceneViewer$cen.stateEngine;
|
|
13219
|
+
if (stateEngine) {
|
|
13220
|
+
applyDeclarationToExport(exportData, stateEngine);
|
|
13221
|
+
}
|
|
12344
13222
|
console.log('✅ Scene export completed:', exportData);
|
|
12345
13223
|
console.log("\uD83D\uDCCA Exported ".concat(sceneChildren.length, " components and ").concat(exportData.connections.length, " connections"));
|
|
12346
13224
|
return exportData;
|
|
@@ -28180,6 +29058,21 @@ var PathRenderingManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
28180
29058
|
}
|
|
28181
29059
|
}
|
|
28182
29060
|
|
|
29061
|
+
/**
|
|
29062
|
+
* Read the current color of a path's shared material as a `#rrggbb` string,
|
|
29063
|
+
* or null if the path has no material. Used to snapshot a pipe's baseline
|
|
29064
|
+
* colour before it is temporarily overpainted (e.g. flow gating).
|
|
29065
|
+
*
|
|
29066
|
+
* @param {string} pathId - "${from}-->${to}"
|
|
29067
|
+
* @returns {string|null}
|
|
29068
|
+
*/
|
|
29069
|
+
}, {
|
|
29070
|
+
key: "getPathColorHex",
|
|
29071
|
+
value: function getPathColorHex(pathId) {
|
|
29072
|
+
var mat = this._pathMaterials.get(pathId);
|
|
29073
|
+
return mat ? "#".concat(mat.color.getHexString()) : null;
|
|
29074
|
+
}
|
|
29075
|
+
|
|
28183
29076
|
/**
|
|
28184
29077
|
* Get path colors for visual distinction
|
|
28185
29078
|
* @param {number} index - Path index
|
|
@@ -30711,6 +31604,9 @@ var TEMP_MIN = 0;
|
|
|
30711
31604
|
/** Maximum temperature in the colour ramp (maps to pure red). */
|
|
30712
31605
|
var TEMP_MAX = 100;
|
|
30713
31606
|
|
|
31607
|
+
/** Colour a pipe is painted when its effective flow is gated off (§6.1). */
|
|
31608
|
+
var PIPE_OFF_GRAY = '#3a3a3a';
|
|
31609
|
+
|
|
30714
31610
|
/**
|
|
30715
31611
|
* Convert a temperature value to a CSS hex colour string using a blue→red HSL
|
|
30716
31612
|
* ramp. Values are clamped to [TEMP_MIN, TEMP_MAX].
|
|
@@ -30748,22 +31644,78 @@ var PathFlowManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
30748
31644
|
* @type {Map<string, Record<string, any>>}
|
|
30749
31645
|
*/
|
|
30750
31646
|
_this._derivedStore = new Map();
|
|
31647
|
+
|
|
31648
|
+
/**
|
|
31649
|
+
* Paths whose effective flow is gated off (State Engine §6.1). While a path
|
|
31650
|
+
* is in this set it is painted {@link PIPE_OFF_GRAY} and temperature updates
|
|
31651
|
+
* are suppressed so they don't override the off-look.
|
|
31652
|
+
* @type {Set<string>}
|
|
31653
|
+
*/
|
|
31654
|
+
_this._flowGatedOff = new Set();
|
|
31655
|
+
|
|
31656
|
+
/**
|
|
31657
|
+
* Baseline (on-state) colour snapshotted the moment a path is gated off, so
|
|
31658
|
+
* it can be restored exactly when flow returns — including pipes with no
|
|
31659
|
+
* declared flowTemperature, whose colour is the base pipe material.
|
|
31660
|
+
* @type {Map<string, string>}
|
|
31661
|
+
*/
|
|
31662
|
+
_this._flowBaseColor = new Map();
|
|
30751
31663
|
return _this;
|
|
30752
31664
|
}
|
|
30753
31665
|
|
|
30754
|
-
// ──
|
|
31666
|
+
// ── Flow gating (State Engine effective → 3D bridge, §6.1) ─────────────────
|
|
30755
31667
|
|
|
30756
31668
|
/**
|
|
30757
|
-
*
|
|
30758
|
-
*
|
|
30759
|
-
*
|
|
31669
|
+
* Gate a path's flow visual on/off from the State Engine's effective `flow`.
|
|
31670
|
+
* Off → paint the shared per-path material gray; on → restore the temperature
|
|
31671
|
+
* colour. The off state is sticky (see {@link _flowGatedOff}).
|
|
30760
31672
|
*
|
|
30761
|
-
* @param {string} pathId
|
|
30762
|
-
* @param {
|
|
30763
|
-
* @param {any} value
|
|
31673
|
+
* @param {string} pathId - `${from}-->${to}`
|
|
31674
|
+
* @param {boolean} enabled - Effective flow
|
|
30764
31675
|
*/
|
|
30765
31676
|
_inherits(PathFlowManager, _BaseDisposable);
|
|
30766
31677
|
return _createClass(PathFlowManager, [{
|
|
31678
|
+
key: "setFlowEnabled",
|
|
31679
|
+
value: function setFlowEnabled(pathId, enabled) {
|
|
31680
|
+
var split = this._splitPathId(pathId);
|
|
31681
|
+
if (!split) return;
|
|
31682
|
+
var renderingManager = this._getRenderingManager();
|
|
31683
|
+
if (enabled) {
|
|
31684
|
+
this._flowGatedOff.delete(pathId);
|
|
31685
|
+
// Prefer a temperature-derived colour if one is declared; otherwise
|
|
31686
|
+
// restore the baseline snapshotted when the pipe was gated off (this is
|
|
31687
|
+
// what fixes plain, non-temperature pipes getting stuck gray).
|
|
31688
|
+
var temp = this.resolve(pathId, 'flowTemperature');
|
|
31689
|
+
if (temp !== null && temp !== undefined) {
|
|
31690
|
+
this.applyVisualizationForPath(pathId);
|
|
31691
|
+
} else if (this._flowBaseColor.has(pathId)) {
|
|
31692
|
+
renderingManager === null || renderingManager === void 0 || renderingManager.updatePathColor(split.from, split.to, this._flowBaseColor.get(pathId));
|
|
31693
|
+
}
|
|
31694
|
+
this._flowBaseColor.delete(pathId);
|
|
31695
|
+
} else {
|
|
31696
|
+
// Snapshot the current (on-state) colour once, before overpainting gray.
|
|
31697
|
+
if (!this._flowGatedOff.has(pathId) && renderingManager) {
|
|
31698
|
+
var _renderingManager$get;
|
|
31699
|
+
var base = (_renderingManager$get = renderingManager.getPathColorHex) === null || _renderingManager$get === void 0 ? void 0 : _renderingManager$get.call(renderingManager, pathId);
|
|
31700
|
+
if (base) this._flowBaseColor.set(pathId, base);
|
|
31701
|
+
}
|
|
31702
|
+
this._flowGatedOff.add(pathId);
|
|
31703
|
+
renderingManager === null || renderingManager === void 0 || renderingManager.updatePathColor(split.from, split.to, PIPE_OFF_GRAY);
|
|
31704
|
+
}
|
|
31705
|
+
}
|
|
31706
|
+
|
|
31707
|
+
// ── Public API — attribute write ──────────────────────────────────────────
|
|
31708
|
+
|
|
31709
|
+
/**
|
|
31710
|
+
* Set a declared (persistent) flow attribute on a path.
|
|
31711
|
+
* The value is stored in PathData and will be serialised with the scene.
|
|
31712
|
+
* Triggers a visual update.
|
|
31713
|
+
*
|
|
31714
|
+
* @param {string} pathId - e.g. "PUMP-1-CONN-1-->CHILLER-1-CONN-2"
|
|
31715
|
+
* @param {string} key - One of FLOW_ATTRIBUTE_KEYS
|
|
31716
|
+
* @param {any} value
|
|
31717
|
+
*/
|
|
31718
|
+
}, {
|
|
30767
31719
|
key: "setDeclared",
|
|
30768
31720
|
value: function setDeclared(pathId, key, value) {
|
|
30769
31721
|
var pd = this._getPathData(pathId);
|
|
@@ -30869,6 +31821,13 @@ var PathFlowManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
30869
31821
|
}, {
|
|
30870
31822
|
key: "applyVisualizationForPath",
|
|
30871
31823
|
value: function applyVisualizationForPath(pathId) {
|
|
31824
|
+
// Effective flow gated off (§6.1) — keep the off-look, ignore temperature.
|
|
31825
|
+
if (this._flowGatedOff.has(pathId)) {
|
|
31826
|
+
var _this$_getRenderingMa;
|
|
31827
|
+
var _split = this._splitPathId(pathId);
|
|
31828
|
+
if (_split) (_this$_getRenderingMa = this._getRenderingManager()) === null || _this$_getRenderingMa === void 0 || _this$_getRenderingMa.updatePathColor(_split.from, _split.to, PIPE_OFF_GRAY);
|
|
31829
|
+
return;
|
|
31830
|
+
}
|
|
30872
31831
|
var temp = this.resolve(pathId, 'flowTemperature');
|
|
30873
31832
|
if (temp === null || temp === undefined) {
|
|
30874
31833
|
return; // No temperature declared — leave default pipe color
|
|
@@ -30876,17 +31835,30 @@ var PathFlowManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
30876
31835
|
var color = temperatureToColor(temp);
|
|
30877
31836
|
var renderingManager = this._getRenderingManager();
|
|
30878
31837
|
if (!renderingManager) return;
|
|
31838
|
+
var split = this._splitPathId(pathId);
|
|
31839
|
+
if (!split) return;
|
|
31840
|
+
renderingManager.updatePathColor(split.from, split.to, color);
|
|
31841
|
+
console.log("\uD83C\uDF21\uFE0F PathFlowManager: \"".concat(pathId, "\" \u2192 flowTemperature ").concat(temp, " \u2192 ").concat(color));
|
|
31842
|
+
}
|
|
30879
31843
|
|
|
30880
|
-
|
|
31844
|
+
/**
|
|
31845
|
+
* Split a `${from}-->${to}` pathId into its two connector halves.
|
|
31846
|
+
* @param {string} pathId
|
|
31847
|
+
* @returns {{ from: string, to: string }|null} null if malformed
|
|
31848
|
+
* @private
|
|
31849
|
+
*/
|
|
31850
|
+
}, {
|
|
31851
|
+
key: "_splitPathId",
|
|
31852
|
+
value: function _splitPathId(pathId) {
|
|
30881
31853
|
var sepIdx = pathId.indexOf('-->');
|
|
30882
31854
|
if (sepIdx === -1) {
|
|
30883
31855
|
console.warn("PathFlowManager: malformed pathId \"".concat(pathId, "\""));
|
|
30884
|
-
return;
|
|
31856
|
+
return null;
|
|
30885
31857
|
}
|
|
30886
|
-
|
|
30887
|
-
|
|
30888
|
-
|
|
30889
|
-
|
|
31858
|
+
return {
|
|
31859
|
+
from: pathId.slice(0, sepIdx),
|
|
31860
|
+
to: pathId.slice(sepIdx + 3)
|
|
31861
|
+
};
|
|
30890
31862
|
}
|
|
30891
31863
|
|
|
30892
31864
|
/**
|
|
@@ -30918,6 +31890,8 @@ var PathFlowManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
30918
31890
|
key: "dispose",
|
|
30919
31891
|
value: function dispose() {
|
|
30920
31892
|
this._derivedStore.clear();
|
|
31893
|
+
this._flowGatedOff.clear();
|
|
31894
|
+
this._flowBaseColor.clear();
|
|
30921
31895
|
this.sceneViewer = null;
|
|
30922
31896
|
_superPropGet(PathFlowManager, "dispose", this, 3)([]);
|
|
30923
31897
|
}
|
|
@@ -37840,7 +38814,6 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
37840
38814
|
if (!ioDeviceObject || !this._stateAdapter) return;
|
|
37841
38815
|
var ud = ioDeviceObject.userData;
|
|
37842
38816
|
var attachmentId = ud === null || ud === void 0 ? void 0 : ud.attachmentId;
|
|
37843
|
-
var dataPoints = (ud === null || ud === void 0 ? void 0 : ud.dataPoints) || [];
|
|
37844
38817
|
if (!attachmentId) return;
|
|
37845
38818
|
|
|
37846
38819
|
// Walk up to find parent component UUID for scoped state/behavior handling
|
|
@@ -37855,6 +38828,13 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
37855
38828
|
obj = obj.parent;
|
|
37856
38829
|
}
|
|
37857
38830
|
|
|
38831
|
+
// Derive the device's data points from its behaviorConfig (same resolver the
|
|
38832
|
+
// drag path uses). `userData.dataPoints` is not populated for attached
|
|
38833
|
+
// devices, so reading it directly would find nothing — resolve instead, and
|
|
38834
|
+
// keep the legacy field only as a fallback.
|
|
38835
|
+
var resolved = resolveDataPoints(parentUuid, attachmentId, ud, getIoBehaviorManager(this.sceneViewer), null);
|
|
38836
|
+
var dataPoints = resolved !== null && resolved !== void 0 && resolved.length ? resolved : (ud === null || ud === void 0 ? void 0 : ud.dataPoints) || [];
|
|
38837
|
+
|
|
37858
38838
|
// Create a scoped attachment key to prevent state sharing between instances
|
|
37859
38839
|
// of the same smart component that share the same attachmentId
|
|
37860
38840
|
var scopedAttachmentId = getScopedAttachmentKey(attachmentId, parentUuid);
|
|
@@ -40269,6 +41249,611 @@ var IoOutlineManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
40269
41249
|
}]);
|
|
40270
41250
|
}(BaseDisposable);
|
|
40271
41251
|
|
|
41252
|
+
/** How far the albedo is pulled toward black for a gated-off mesh. */
|
|
41253
|
+
var OFF_COLOR_SCALE = 0.2;
|
|
41254
|
+
var EffectiveVisualManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
41255
|
+
/**
|
|
41256
|
+
* @param {Object} sceneViewer - The central sceneViewer hub
|
|
41257
|
+
*/
|
|
41258
|
+
function EffectiveVisualManager(sceneViewer) {
|
|
41259
|
+
var _this;
|
|
41260
|
+
_classCallCheck(this, EffectiveVisualManager);
|
|
41261
|
+
_this = _callSuper(this, EffectiveVisualManager);
|
|
41262
|
+
_this.sceneViewer = sceneViewer;
|
|
41263
|
+
|
|
41264
|
+
/**
|
|
41265
|
+
* Materials this manager has gated (cloned + cached), for restore on dispose.
|
|
41266
|
+
* @type {Set<THREE.Material>}
|
|
41267
|
+
*/
|
|
41268
|
+
_this._gatedMaterials = new Set();
|
|
41269
|
+
|
|
41270
|
+
// Bound handlers so we can unsubscribe in _doDispose().
|
|
41271
|
+
_this._onChange = _this._onChange.bind(_this);
|
|
41272
|
+
_this._regateAll = _this._regateAll.bind(_this);
|
|
41273
|
+
if (sceneViewer !== null && sceneViewer !== void 0 && sceneViewer.on) {
|
|
41274
|
+
sceneViewer.on('engine-state-changed', _this._onChange);
|
|
41275
|
+
sceneViewer.on('scene-loaded', _this._regateAll);
|
|
41276
|
+
}
|
|
41277
|
+
return _this;
|
|
41278
|
+
}
|
|
41279
|
+
|
|
41280
|
+
// ── Engine change handling ──────────────────────────────────────────────────
|
|
41281
|
+
|
|
41282
|
+
/**
|
|
41283
|
+
* React to one committed registry change. Only 2-part object / pipe addresses
|
|
41284
|
+
* are gated; everything else is ignored.
|
|
41285
|
+
* @param {{ address: string, desired: *, effective: * }} change
|
|
41286
|
+
* @private
|
|
41287
|
+
*/
|
|
41288
|
+
_inherits(EffectiveVisualManager, _BaseDisposable);
|
|
41289
|
+
return _createClass(EffectiveVisualManager, [{
|
|
41290
|
+
key: "_onChange",
|
|
41291
|
+
value: function _onChange(change) {
|
|
41292
|
+
if (!change || typeof change.address !== 'string') return;
|
|
41293
|
+
var parts = change.address.split('::');
|
|
41294
|
+
if (parts.length !== 2) return; // 3-part I/O is desired-driven; skip
|
|
41295
|
+
|
|
41296
|
+
var _parts = _slicedToArray(parts, 2),
|
|
41297
|
+
scope = _parts[0],
|
|
41298
|
+
attr = _parts[1];
|
|
41299
|
+
if (scope === 'plant' || scope.startsWith('group:')) return; // members self-report
|
|
41300
|
+
|
|
41301
|
+
if (attr === 'power') {
|
|
41302
|
+
this._gateEquipment(scope, change.effective);
|
|
41303
|
+
} else if (attr === 'flow') {
|
|
41304
|
+
this._gatePipe(scope, change.effective);
|
|
41305
|
+
}
|
|
41306
|
+
}
|
|
41307
|
+
|
|
41308
|
+
/**
|
|
41309
|
+
* Re-gate the whole scene from current effective state. Used after scene load,
|
|
41310
|
+
* where StateEngine.load() populates the registry without emitting per-address
|
|
41311
|
+
* events (and the meshes have just been (re)built).
|
|
41312
|
+
* @private
|
|
41313
|
+
*/
|
|
41314
|
+
}, {
|
|
41315
|
+
key: "_regateAll",
|
|
41316
|
+
value: function _regateAll() {
|
|
41317
|
+
var _this$sceneViewer;
|
|
41318
|
+
var engine = (_this$sceneViewer = this.sceneViewer) === null || _this$sceneViewer === void 0 || (_this$sceneViewer = _this$sceneViewer.centralPlant) === null || _this$sceneViewer === void 0 ? void 0 : _this$sceneViewer.stateEngine;
|
|
41319
|
+
if (!engine) return;
|
|
41320
|
+
var registry = engine.toRegistry();
|
|
41321
|
+
for (var _i = 0, _Object$keys = Object.keys(registry); _i < _Object$keys.length; _i++) {
|
|
41322
|
+
var address = _Object$keys[_i];
|
|
41323
|
+
var parts = address.split('::');
|
|
41324
|
+
if (parts.length !== 2) continue;
|
|
41325
|
+
var _parts2 = _slicedToArray(parts, 2),
|
|
41326
|
+
scope = _parts2[0],
|
|
41327
|
+
attr = _parts2[1];
|
|
41328
|
+
if (scope === 'plant' || scope.startsWith('group:')) continue;
|
|
41329
|
+
if (attr === 'power') {
|
|
41330
|
+
this._gateEquipment(scope, engine.getEffective(address));
|
|
41331
|
+
} else if (attr === 'flow') {
|
|
41332
|
+
this._gatePipe(scope, engine.getEffective(address));
|
|
41333
|
+
}
|
|
41334
|
+
}
|
|
41335
|
+
}
|
|
41336
|
+
|
|
41337
|
+
// ── Equipment gating ────────────────────────────────────────────────────────
|
|
41338
|
+
|
|
41339
|
+
/**
|
|
41340
|
+
* Darken (off) or restore (on) every mesh of an equipment object.
|
|
41341
|
+
* @param {string} objectId - Hardcoded object id (address scope)
|
|
41342
|
+
* @param {boolean} on - Effective power
|
|
41343
|
+
* @private
|
|
41344
|
+
*/
|
|
41345
|
+
}, {
|
|
41346
|
+
key: "_gateEquipment",
|
|
41347
|
+
value: function _gateEquipment(objectId, on) {
|
|
41348
|
+
var _this$sceneViewer2,
|
|
41349
|
+
_this2 = this;
|
|
41350
|
+
var scene = (_this$sceneViewer2 = this.sceneViewer) === null || _this$sceneViewer2 === void 0 ? void 0 : _this$sceneViewer2.scene;
|
|
41351
|
+
if (!scene) return;
|
|
41352
|
+
var obj = findObjectByHardcodedUuid(scene, objectId);
|
|
41353
|
+
if (!obj || typeof obj.traverse !== 'function') return;
|
|
41354
|
+
obj.traverse(function (mesh) {
|
|
41355
|
+
if (!mesh.isMesh && !mesh.material) return;
|
|
41356
|
+
// Leave attached I/O control panels (switches) lit and operable even when
|
|
41357
|
+
// their host is powered off — you must be able to see and click the switch
|
|
41358
|
+
// that turns it back on. (traverse can't prune, so guard per mesh.)
|
|
41359
|
+
if (_this2._isUnderIoDevice(mesh, obj)) return;
|
|
41360
|
+
var materials = _this2._prepareMeshMaterials(mesh);
|
|
41361
|
+
var _iterator = _createForOfIteratorHelper(materials),
|
|
41362
|
+
_step;
|
|
41363
|
+
try {
|
|
41364
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
41365
|
+
var material = _step.value;
|
|
41366
|
+
_this2._applyGate(material, on);
|
|
41367
|
+
}
|
|
41368
|
+
} catch (err) {
|
|
41369
|
+
_iterator.e(err);
|
|
41370
|
+
} finally {
|
|
41371
|
+
_iterator.f();
|
|
41372
|
+
}
|
|
41373
|
+
});
|
|
41374
|
+
}
|
|
41375
|
+
|
|
41376
|
+
/**
|
|
41377
|
+
* True if `mesh` is an io-device or nested under one, walking up to (but not
|
|
41378
|
+
* past) the equipment root `stopAt`.
|
|
41379
|
+
* @param {THREE.Object3D} mesh
|
|
41380
|
+
* @param {THREE.Object3D} stopAt - equipment root to stop the upward walk at
|
|
41381
|
+
* @returns {boolean}
|
|
41382
|
+
* @private
|
|
41383
|
+
*/
|
|
41384
|
+
}, {
|
|
41385
|
+
key: "_isUnderIoDevice",
|
|
41386
|
+
value: function _isUnderIoDevice(mesh, stopAt) {
|
|
41387
|
+
var node = mesh;
|
|
41388
|
+
while (node) {
|
|
41389
|
+
var _node$userData;
|
|
41390
|
+
if (((_node$userData = node.userData) === null || _node$userData === void 0 ? void 0 : _node$userData.objectType) === 'io-device') return true;
|
|
41391
|
+
if (node === stopAt) break;
|
|
41392
|
+
node = node.parent;
|
|
41393
|
+
}
|
|
41394
|
+
return false;
|
|
41395
|
+
}
|
|
41396
|
+
|
|
41397
|
+
/**
|
|
41398
|
+
* Ensure a mesh's material(s) are cloned-on-write and have their baseline
|
|
41399
|
+
* cached, then return the material list to gate.
|
|
41400
|
+
* @param {THREE.Mesh} mesh
|
|
41401
|
+
* @returns {THREE.Material[]}
|
|
41402
|
+
* @private
|
|
41403
|
+
*/
|
|
41404
|
+
}, {
|
|
41405
|
+
key: "_prepareMeshMaterials",
|
|
41406
|
+
value: function _prepareMeshMaterials(mesh) {
|
|
41407
|
+
if (!mesh.material) return [];
|
|
41408
|
+
|
|
41409
|
+
// Clone-on-write once per mesh so shared library materials aren't shared.
|
|
41410
|
+
if (!mesh.userData._effectiveGateCloned) {
|
|
41411
|
+
if (Array.isArray(mesh.material)) {
|
|
41412
|
+
mesh.material = mesh.material.map(function (m) {
|
|
41413
|
+
return m !== null && m !== void 0 && m.clone ? m.clone() : m;
|
|
41414
|
+
});
|
|
41415
|
+
} else if (mesh.material.clone) {
|
|
41416
|
+
mesh.material = mesh.material.clone();
|
|
41417
|
+
}
|
|
41418
|
+
mesh.userData._effectiveGateCloned = true;
|
|
41419
|
+
}
|
|
41420
|
+
var materials = Array.isArray(mesh.material) ? mesh.material : [mesh.material];
|
|
41421
|
+
var _iterator2 = _createForOfIteratorHelper(materials),
|
|
41422
|
+
_step2;
|
|
41423
|
+
try {
|
|
41424
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
41425
|
+
var _material$userData, _material$color$clone, _material$color, _material$color$clone2, _material$emissive$cl, _material$emissive, _material$emissive$cl2;
|
|
41426
|
+
var material = _step2.value;
|
|
41427
|
+
if (!material || (_material$userData = material.userData) !== null && _material$userData !== void 0 && _material$userData._effectiveGate) continue;
|
|
41428
|
+
material.userData = material.userData || {};
|
|
41429
|
+
material.userData._effectiveGate = {
|
|
41430
|
+
color: (_material$color$clone = (_material$color = material.color) === null || _material$color === void 0 || (_material$color$clone2 = _material$color.clone) === null || _material$color$clone2 === void 0 ? void 0 : _material$color$clone2.call(_material$color)) !== null && _material$color$clone !== void 0 ? _material$color$clone : null,
|
|
41431
|
+
emissive: (_material$emissive$cl = (_material$emissive = material.emissive) === null || _material$emissive === void 0 || (_material$emissive$cl2 = _material$emissive.clone) === null || _material$emissive$cl2 === void 0 ? void 0 : _material$emissive$cl2.call(_material$emissive)) !== null && _material$emissive$cl !== void 0 ? _material$emissive$cl : null,
|
|
41432
|
+
emissiveIntensity: typeof material.emissiveIntensity === 'number' ? material.emissiveIntensity : null
|
|
41433
|
+
};
|
|
41434
|
+
this._gatedMaterials.add(material);
|
|
41435
|
+
}
|
|
41436
|
+
} catch (err) {
|
|
41437
|
+
_iterator2.e(err);
|
|
41438
|
+
} finally {
|
|
41439
|
+
_iterator2.f();
|
|
41440
|
+
}
|
|
41441
|
+
return materials.filter(Boolean);
|
|
41442
|
+
}
|
|
41443
|
+
|
|
41444
|
+
/**
|
|
41445
|
+
* Darken or restore a single material from its cached baseline.
|
|
41446
|
+
* @param {THREE.Material} material
|
|
41447
|
+
* @param {boolean} on
|
|
41448
|
+
* @private
|
|
41449
|
+
*/
|
|
41450
|
+
}, {
|
|
41451
|
+
key: "_applyGate",
|
|
41452
|
+
value: function _applyGate(material, on) {
|
|
41453
|
+
var _material$userData2;
|
|
41454
|
+
var base = (_material$userData2 = material.userData) === null || _material$userData2 === void 0 ? void 0 : _material$userData2._effectiveGate;
|
|
41455
|
+
if (!base) return;
|
|
41456
|
+
if (on) {
|
|
41457
|
+
if (base.color && material.color) material.color.copy(base.color);
|
|
41458
|
+
if (base.emissive && material.emissive) material.emissive.copy(base.emissive);
|
|
41459
|
+
if (base.emissiveIntensity !== null) material.emissiveIntensity = base.emissiveIntensity;
|
|
41460
|
+
} else {
|
|
41461
|
+
if (base.color && material.color) material.color.copy(base.color).multiplyScalar(OFF_COLOR_SCALE);
|
|
41462
|
+
if (material.emissive) material.emissive.setScalar(0);
|
|
41463
|
+
if (typeof material.emissiveIntensity === 'number') material.emissiveIntensity = 0;
|
|
41464
|
+
}
|
|
41465
|
+
material.needsUpdate = true;
|
|
41466
|
+
}
|
|
41467
|
+
|
|
41468
|
+
// ── Pipe flow gating ────────────────────────────────────────────────────────
|
|
41469
|
+
|
|
41470
|
+
/**
|
|
41471
|
+
* Delegate pipe-flow gating to PathFlowManager (grays the shared per-path
|
|
41472
|
+
* material when off, restores the temperature colour when on).
|
|
41473
|
+
* @param {string} pathId - `${from}-->${to}`
|
|
41474
|
+
* @param {boolean} on - Effective flow
|
|
41475
|
+
* @private
|
|
41476
|
+
*/
|
|
41477
|
+
}, {
|
|
41478
|
+
key: "_gatePipe",
|
|
41479
|
+
value: function _gatePipe(pathId, on) {
|
|
41480
|
+
var _this$sceneViewer3, _pathFlowManager$setF;
|
|
41481
|
+
var pathFlowManager = (_this$sceneViewer3 = this.sceneViewer) === null || _this$sceneViewer3 === void 0 || (_this$sceneViewer3 = _this$sceneViewer3.managers) === null || _this$sceneViewer3 === void 0 ? void 0 : _this$sceneViewer3.pathFlowManager;
|
|
41482
|
+
pathFlowManager === null || pathFlowManager === void 0 || (_pathFlowManager$setF = pathFlowManager.setFlowEnabled) === null || _pathFlowManager$setF === void 0 || _pathFlowManager$setF.call(pathFlowManager, pathId, on);
|
|
41483
|
+
}
|
|
41484
|
+
|
|
41485
|
+
// ── Lifecycle ───────────────────────────────────────────────────────────────
|
|
41486
|
+
}, {
|
|
41487
|
+
key: "_doDispose",
|
|
41488
|
+
value: function _doDispose() {
|
|
41489
|
+
var _this$sceneViewer4;
|
|
41490
|
+
if ((_this$sceneViewer4 = this.sceneViewer) !== null && _this$sceneViewer4 !== void 0 && _this$sceneViewer4.off) {
|
|
41491
|
+
this.sceneViewer.off('engine-state-changed', this._onChange);
|
|
41492
|
+
this.sceneViewer.off('scene-loaded', this._regateAll);
|
|
41493
|
+
}
|
|
41494
|
+
// Restore any still-gated materials to their cached baseline.
|
|
41495
|
+
var _iterator3 = _createForOfIteratorHelper(this._gatedMaterials),
|
|
41496
|
+
_step3;
|
|
41497
|
+
try {
|
|
41498
|
+
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
|
|
41499
|
+
var material = _step3.value;
|
|
41500
|
+
this._applyGate(material, true);
|
|
41501
|
+
}
|
|
41502
|
+
} catch (err) {
|
|
41503
|
+
_iterator3.e(err);
|
|
41504
|
+
} finally {
|
|
41505
|
+
_iterator3.f();
|
|
41506
|
+
}
|
|
41507
|
+
this._gatedMaterials.clear();
|
|
41508
|
+
this.sceneViewer = null;
|
|
41509
|
+
}
|
|
41510
|
+
}]);
|
|
41511
|
+
}(BaseDisposable);
|
|
41512
|
+
|
|
41513
|
+
/**
|
|
41514
|
+
* X offsets for `count` switches evenly spaced and centered on 0.
|
|
41515
|
+
* @param {number} count
|
|
41516
|
+
* @param {number} spacing
|
|
41517
|
+
* @returns {number[]}
|
|
41518
|
+
*/
|
|
41519
|
+
function computeSwitchRowX(count, spacing) {
|
|
41520
|
+
var start = -((count - 1) * spacing) / 2;
|
|
41521
|
+
return Array.from({
|
|
41522
|
+
length: count
|
|
41523
|
+
}, function (_, i) {
|
|
41524
|
+
return start + i * spacing;
|
|
41525
|
+
});
|
|
41526
|
+
}
|
|
41527
|
+
var ControlPanelManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
41528
|
+
/**
|
|
41529
|
+
* @param {Object} sceneViewer - The central sceneViewer hub
|
|
41530
|
+
*/
|
|
41531
|
+
function ControlPanelManager(sceneViewer) {
|
|
41532
|
+
var _this;
|
|
41533
|
+
_classCallCheck(this, ControlPanelManager);
|
|
41534
|
+
_this = _callSuper(this, ControlPanelManager);
|
|
41535
|
+
_this.sceneViewer = sceneViewer;
|
|
41536
|
+
_this._panel = null;
|
|
41537
|
+
_this._labels = [];
|
|
41538
|
+
_this._panelId = null;
|
|
41539
|
+
_this._build = _this._build.bind(_this);
|
|
41540
|
+
if (sceneViewer !== null && sceneViewer !== void 0 && sceneViewer.on) {
|
|
41541
|
+
sceneViewer.on('scene-loaded', _this._build);
|
|
41542
|
+
}
|
|
41543
|
+
return _this;
|
|
41544
|
+
}
|
|
41545
|
+
|
|
41546
|
+
/**
|
|
41547
|
+
* Build (or rebuild) the panel from the current scene's `controlPanel` spec.
|
|
41548
|
+
* @private
|
|
41549
|
+
*/
|
|
41550
|
+
_inherits(ControlPanelManager, _BaseDisposable);
|
|
41551
|
+
return _createClass(ControlPanelManager, [{
|
|
41552
|
+
key: "_build",
|
|
41553
|
+
value: (function () {
|
|
41554
|
+
var _build2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
|
|
41555
|
+
var _this$sceneViewer,
|
|
41556
|
+
_centralPlant$importe,
|
|
41557
|
+
_this$sceneViewer2,
|
|
41558
|
+
_b$width,
|
|
41559
|
+
_b$height,
|
|
41560
|
+
_b$thickness,
|
|
41561
|
+
_desk$tilt,
|
|
41562
|
+
_desk$height,
|
|
41563
|
+
_desk$legThickness,
|
|
41564
|
+
_desk$legInset,
|
|
41565
|
+
_spec$switchSpacing,
|
|
41566
|
+
_this$sceneViewer3,
|
|
41567
|
+
_spec$labelSize,
|
|
41568
|
+
_this2 = this;
|
|
41569
|
+
var centralPlant, spec, scene, panelId, panel, p, r, d2r, scale, b, width, depth, thickness, desk, tilt, deskHeight, legThickness, legInset, legColor, top, board, legMat, halfW, halfD, _i, _arr, ly, cornerZ, legH, yPos, _i2, _arr2, lx, leg, switches, spacing, xs, topZ, attachedDevices, componentData, switchScale, ioBehavMgr, engine, _iterator, _step, sw, current, lo, labelSize;
|
|
41570
|
+
return _regenerator().w(function (_context) {
|
|
41571
|
+
while (1) switch (_context.n) {
|
|
41572
|
+
case 0:
|
|
41573
|
+
this._teardown();
|
|
41574
|
+
centralPlant = (_this$sceneViewer = this.sceneViewer) === null || _this$sceneViewer === void 0 ? void 0 : _this$sceneViewer.centralPlant;
|
|
41575
|
+
spec = centralPlant === null || centralPlant === void 0 || (_centralPlant$importe = centralPlant.importedSceneData) === null || _centralPlant$importe === void 0 ? void 0 : _centralPlant$importe.controlPanel;
|
|
41576
|
+
scene = (_this$sceneViewer2 = this.sceneViewer) === null || _this$sceneViewer2 === void 0 ? void 0 : _this$sceneViewer2.scene;
|
|
41577
|
+
if (!(!spec || !scene)) {
|
|
41578
|
+
_context.n = 1;
|
|
41579
|
+
break;
|
|
41580
|
+
}
|
|
41581
|
+
return _context.a(2);
|
|
41582
|
+
case 1:
|
|
41583
|
+
panelId = spec.id || 'CONTROL-PANEL-1';
|
|
41584
|
+
this._panelId = panelId;
|
|
41585
|
+
panel = new THREE__namespace.Group();
|
|
41586
|
+
panel.name = 'Control Panel';
|
|
41587
|
+
panel.uuid = panelId;
|
|
41588
|
+
panel.userData = {
|
|
41589
|
+
objectType: 'component',
|
|
41590
|
+
isControlPanel: true
|
|
41591
|
+
};
|
|
41592
|
+
p = spec.position || {};
|
|
41593
|
+
panel.position.set(p.x || 0, p.y || 0, p.z || 0);
|
|
41594
|
+
r = spec.rotation || {};
|
|
41595
|
+
d2r = Math.PI / 180;
|
|
41596
|
+
panel.rotation.set((r.x || 0) * d2r, (r.y || 0) * d2r, (r.z || 0) * d2r, 'XYZ');
|
|
41597
|
+
scale = Number.isFinite(spec.scale) && spec.scale > 0 ? spec.scale : 1;
|
|
41598
|
+
panel.scale.setScalar(scale);
|
|
41599
|
+
|
|
41600
|
+
// ── Drawing-desk geometry (Z-up scene) ───────────────────────────────────
|
|
41601
|
+
// A tilted top surface (the drafting board) raised on legs. Switches +
|
|
41602
|
+
// labels live on the tilted `top` group so they sit on the angled surface.
|
|
41603
|
+
b = spec.board || {};
|
|
41604
|
+
width = (_b$width = b.width) !== null && _b$width !== void 0 ? _b$width : 7; // x-extent of the top
|
|
41605
|
+
depth = (_b$height = b.height) !== null && _b$height !== void 0 ? _b$height : 2; // y-extent of the top (spec key kept as `height`)
|
|
41606
|
+
thickness = (_b$thickness = b.thickness) !== null && _b$thickness !== void 0 ? _b$thickness : 0.15;
|
|
41607
|
+
desk = spec.desk || {};
|
|
41608
|
+
tilt = ((_desk$tilt = desk.tilt) !== null && _desk$tilt !== void 0 ? _desk$tilt : 30) * d2r; // surface tilt; +raises the back (+y) edge
|
|
41609
|
+
deskHeight = (_desk$height = desk.height) !== null && _desk$height !== void 0 ? _desk$height : 1.3; // world height of the top's center
|
|
41610
|
+
legThickness = (_desk$legThickness = desk.legThickness) !== null && _desk$legThickness !== void 0 ? _desk$legThickness : 0.18;
|
|
41611
|
+
legInset = (_desk$legInset = desk.legInset) !== null && _desk$legInset !== void 0 ? _desk$legInset : 0.35;
|
|
41612
|
+
legColor = desk.legColor || '#22303c'; // Tilted top group: raised to deskHeight, rotated about X.
|
|
41613
|
+
top = new THREE__namespace.Group();
|
|
41614
|
+
top.position.set(0, 0, deskHeight);
|
|
41615
|
+
top.rotation.set(tilt, 0, 0, 'XYZ');
|
|
41616
|
+
panel.add(top);
|
|
41617
|
+
board = new THREE__namespace.Mesh(new THREE__namespace.BoxGeometry(width, depth, thickness), new THREE__namespace.MeshStandardMaterial({
|
|
41618
|
+
color: b.color || '#2c3e50',
|
|
41619
|
+
roughness: 0.6,
|
|
41620
|
+
metalness: 0.2
|
|
41621
|
+
}));
|
|
41622
|
+
board.userData = {
|
|
41623
|
+
objectType: 'control-panel-board'
|
|
41624
|
+
};
|
|
41625
|
+
top.add(board);
|
|
41626
|
+
|
|
41627
|
+
// Legs: one per corner, from the ground up to the tilted top's underside.
|
|
41628
|
+
legMat = new THREE__namespace.MeshStandardMaterial({
|
|
41629
|
+
color: legColor,
|
|
41630
|
+
roughness: 0.7,
|
|
41631
|
+
metalness: 0.1
|
|
41632
|
+
});
|
|
41633
|
+
halfW = Math.max(0.1, width / 2 - legInset);
|
|
41634
|
+
halfD = Math.max(0.1, depth / 2 - legInset);
|
|
41635
|
+
for (_i = 0, _arr = [halfD, -halfD]; _i < _arr.length; _i++) {
|
|
41636
|
+
ly = _arr[_i];
|
|
41637
|
+
// Underside height at this front/back row after the tilt.
|
|
41638
|
+
cornerZ = deskHeight + ly * Math.sin(tilt) - thickness / 2 * Math.cos(tilt);
|
|
41639
|
+
legH = Math.max(0.1, cornerZ);
|
|
41640
|
+
yPos = ly * Math.cos(tilt); // corner's y in panel space after tilt
|
|
41641
|
+
for (_i2 = 0, _arr2 = [halfW, -halfW]; _i2 < _arr2.length; _i2++) {
|
|
41642
|
+
lx = _arr2[_i2];
|
|
41643
|
+
leg = new THREE__namespace.Mesh(new THREE__namespace.BoxGeometry(legThickness, legThickness, legH), legMat);
|
|
41644
|
+
leg.position.set(lx, yPos, legH / 2);
|
|
41645
|
+
panel.add(leg);
|
|
41646
|
+
}
|
|
41647
|
+
}
|
|
41648
|
+
|
|
41649
|
+
// ── Switch layout: a row along X on the top face (+Z) of the board ───────
|
|
41650
|
+
switches = Array.isArray(spec.switches) ? spec.switches : [];
|
|
41651
|
+
spacing = (_spec$switchSpacing = spec.switchSpacing) !== null && _spec$switchSpacing !== void 0 ? _spec$switchSpacing : 1.3;
|
|
41652
|
+
xs = computeSwitchRowX(switches.length, spacing);
|
|
41653
|
+
topZ = thickness / 2;
|
|
41654
|
+
attachedDevices = {};
|
|
41655
|
+
switches.forEach(function (sw, i) {
|
|
41656
|
+
attachedDevices[sw.attachmentId] = {
|
|
41657
|
+
deviceId: spec.deviceId,
|
|
41658
|
+
attachmentLabel: sw.label || sw.attachmentId,
|
|
41659
|
+
attachmentPoint: {
|
|
41660
|
+
position: {
|
|
41661
|
+
x: xs[i],
|
|
41662
|
+
y: 0,
|
|
41663
|
+
z: topZ
|
|
41664
|
+
},
|
|
41665
|
+
rotation: spec.switchRotation || {
|
|
41666
|
+
x: 0,
|
|
41667
|
+
y: 0,
|
|
41668
|
+
z: 0
|
|
41669
|
+
}
|
|
41670
|
+
}
|
|
41671
|
+
};
|
|
41672
|
+
});
|
|
41673
|
+
componentData = {
|
|
41674
|
+
isSmart: true,
|
|
41675
|
+
attachedDevices: attachedDevices
|
|
41676
|
+
}; // Clone + tag the switch devices onto the tilted top (reuses smart-component path).
|
|
41677
|
+
_context.n = 2;
|
|
41678
|
+
return attachIODevicesToComponent(top, componentData, modelPreloader, panelId);
|
|
41679
|
+
case 2:
|
|
41680
|
+
// Optional switch-only scale (attach path always leaves devices at 1:1).
|
|
41681
|
+
switchScale = Number.isFinite(spec.switchScale) && spec.switchScale > 0 ? spec.switchScale : 1;
|
|
41682
|
+
if (switchScale !== 1) {
|
|
41683
|
+
top.traverse(function (obj) {
|
|
41684
|
+
var _obj$userData;
|
|
41685
|
+
if (((_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.objectType) === 'io-device') {
|
|
41686
|
+
obj.scale.setScalar(switchScale);
|
|
41687
|
+
}
|
|
41688
|
+
});
|
|
41689
|
+
}
|
|
41690
|
+
|
|
41691
|
+
// Panel must be in the scene before behaviors register (default-state seeding
|
|
41692
|
+
// scans the live scene graph for io-devices).
|
|
41693
|
+
scene.add(panel);
|
|
41694
|
+
this._panel = panel;
|
|
41695
|
+
ioBehavMgr = (_this$sceneViewer3 = this.sceneViewer) === null || _this$sceneViewer3 === void 0 || (_this$sceneViewer3 = _this$sceneViewer3.managers) === null || _this$sceneViewer3 === void 0 ? void 0 : _this$sceneViewer3.ioBehaviorManager;
|
|
41696
|
+
if (ioBehavMgr) {
|
|
41697
|
+
registerBehaviorsForComponent(ioBehavMgr, panelId, componentData, panel, modelPreloader.componentDictionary, centralPlant);
|
|
41698
|
+
|
|
41699
|
+
// Drive each switch's mesh to its current engine value so the initial
|
|
41700
|
+
// pose/colour reflects the state. `registerBehaviorsForComponent`'s
|
|
41701
|
+
// default-seeding writes through the engine, which no-ops when the value
|
|
41702
|
+
// already matches the seeded `stateRegistry` — leaving the button at its
|
|
41703
|
+
// neutral GLB rest pose. Trigger the animation directly to avoid that.
|
|
41704
|
+
engine = centralPlant === null || centralPlant === void 0 ? void 0 : centralPlant.stateEngine;
|
|
41705
|
+
_iterator = _createForOfIteratorHelper(switches);
|
|
41706
|
+
try {
|
|
41707
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
41708
|
+
sw = _step.value;
|
|
41709
|
+
current = engine === null || engine === void 0 ? void 0 : engine.getDesired("".concat(panelId, "::").concat(sw.attachmentId, "::mode"));
|
|
41710
|
+
ioBehavMgr.triggerState(sw.attachmentId, 'mode', current !== null && current !== void 0 ? current : true, panelId);
|
|
41711
|
+
}
|
|
41712
|
+
} catch (err) {
|
|
41713
|
+
_iterator.e(err);
|
|
41714
|
+
} finally {
|
|
41715
|
+
_iterator.f();
|
|
41716
|
+
}
|
|
41717
|
+
}
|
|
41718
|
+
|
|
41719
|
+
// ── Floating labels above each switch (on the tilted top) ────────────────
|
|
41720
|
+
lo = spec.labelOffset || {
|
|
41721
|
+
x: 0,
|
|
41722
|
+
y: 0,
|
|
41723
|
+
z: 0.7
|
|
41724
|
+
};
|
|
41725
|
+
labelSize = (_spec$labelSize = spec.labelSize) !== null && _spec$labelSize !== void 0 ? _spec$labelSize : 0.5;
|
|
41726
|
+
switches.forEach(function (sw, i) {
|
|
41727
|
+
var _lo$z;
|
|
41728
|
+
var label = _this2._makeLabel(sw.label || sw.attachmentId, labelSize);
|
|
41729
|
+
if (!label) return;
|
|
41730
|
+
label.position.set(xs[i] + (lo.x || 0), lo.y || 0, topZ + ((_lo$z = lo.z) !== null && _lo$z !== void 0 ? _lo$z : 0.7));
|
|
41731
|
+
top.add(label);
|
|
41732
|
+
_this2._labels.push(label);
|
|
41733
|
+
});
|
|
41734
|
+
case 3:
|
|
41735
|
+
return _context.a(2);
|
|
41736
|
+
}
|
|
41737
|
+
}, _callee, this);
|
|
41738
|
+
}));
|
|
41739
|
+
function _build() {
|
|
41740
|
+
return _build2.apply(this, arguments);
|
|
41741
|
+
}
|
|
41742
|
+
return _build;
|
|
41743
|
+
}()
|
|
41744
|
+
/**
|
|
41745
|
+
* Build one text label as a camera-facing `THREE.Sprite` (canvas texture),
|
|
41746
|
+
* drawn by the main WebGL renderer alongside the scene — no dependency on the
|
|
41747
|
+
* tooltip CSS2D renderer. Returns null when there is no DOM (headless tests).
|
|
41748
|
+
* @param {string} text
|
|
41749
|
+
* @param {number} worldHeight - label height in world units
|
|
41750
|
+
* @returns {THREE.Sprite|null}
|
|
41751
|
+
* @private
|
|
41752
|
+
*/
|
|
41753
|
+
)
|
|
41754
|
+
}, {
|
|
41755
|
+
key: "_makeLabel",
|
|
41756
|
+
value: function _makeLabel(text, worldHeight) {
|
|
41757
|
+
if (typeof document === 'undefined') return null;
|
|
41758
|
+
var dpr = 4; // supersample for crisp text
|
|
41759
|
+
var fontPx = 40;
|
|
41760
|
+
var padX = 14;
|
|
41761
|
+
var padY = 8;
|
|
41762
|
+
var font = "600 ".concat(fontPx, "px -apple-system, \"Segoe UI\", Roboto, sans-serif");
|
|
41763
|
+
var canvas = document.createElement('canvas');
|
|
41764
|
+
var ctx = canvas.getContext('2d');
|
|
41765
|
+
ctx.font = font;
|
|
41766
|
+
var textW = ctx.measureText(text).width;
|
|
41767
|
+
canvas.width = Math.ceil((textW + padX * 2) * dpr);
|
|
41768
|
+
canvas.height = Math.ceil((fontPx + padY * 2) * dpr);
|
|
41769
|
+
|
|
41770
|
+
// Re-apply after the resize (which clears the canvas), scaled by dpr.
|
|
41771
|
+
ctx.scale(dpr, dpr);
|
|
41772
|
+
ctx.font = font;
|
|
41773
|
+
var wCss = canvas.width / dpr;
|
|
41774
|
+
var hCss = canvas.height / dpr;
|
|
41775
|
+
ctx.fillStyle = 'rgba(20,28,38,0.86)';
|
|
41776
|
+
this._roundRect(ctx, 0, 0, wCss, hCss, 8);
|
|
41777
|
+
ctx.fill();
|
|
41778
|
+
ctx.fillStyle = '#eaf2f8';
|
|
41779
|
+
ctx.textAlign = 'center';
|
|
41780
|
+
ctx.textBaseline = 'middle';
|
|
41781
|
+
ctx.fillText(text, wCss / 2, hCss / 2);
|
|
41782
|
+
var texture = new THREE__namespace.CanvasTexture(canvas);
|
|
41783
|
+
texture.minFilter = THREE__namespace.LinearFilter;
|
|
41784
|
+
texture.anisotropy = 4;
|
|
41785
|
+
var material = new THREE__namespace.SpriteMaterial({
|
|
41786
|
+
map: texture,
|
|
41787
|
+
transparent: true,
|
|
41788
|
+
depthTest: false,
|
|
41789
|
+
// always readable, never occluded by the board/switch
|
|
41790
|
+
depthWrite: false
|
|
41791
|
+
});
|
|
41792
|
+
var sprite = new THREE__namespace.Sprite(material);
|
|
41793
|
+
var aspect = canvas.width / canvas.height;
|
|
41794
|
+
sprite.scale.set(worldHeight * aspect, worldHeight, 1);
|
|
41795
|
+
sprite.renderOrder = 999;
|
|
41796
|
+
return sprite;
|
|
41797
|
+
}
|
|
41798
|
+
|
|
41799
|
+
/** Trace a rounded-rect path (fill applied by caller). @private */
|
|
41800
|
+
}, {
|
|
41801
|
+
key: "_roundRect",
|
|
41802
|
+
value: function _roundRect(ctx, x, y, w, h, r) {
|
|
41803
|
+
var rr = Math.min(r, w / 2, h / 2);
|
|
41804
|
+
ctx.beginPath();
|
|
41805
|
+
ctx.moveTo(x + rr, y);
|
|
41806
|
+
ctx.arcTo(x + w, y, x + w, y + h, rr);
|
|
41807
|
+
ctx.arcTo(x + w, y + h, x, y + h, rr);
|
|
41808
|
+
ctx.arcTo(x, y + h, x, y, rr);
|
|
41809
|
+
ctx.arcTo(x, y, x + w, y, rr);
|
|
41810
|
+
ctx.closePath();
|
|
41811
|
+
}
|
|
41812
|
+
|
|
41813
|
+
/**
|
|
41814
|
+
* Remove the panel, its labels, and its registered behaviors (scene reload).
|
|
41815
|
+
* @private
|
|
41816
|
+
*/
|
|
41817
|
+
}, {
|
|
41818
|
+
key: "_teardown",
|
|
41819
|
+
value: function _teardown() {
|
|
41820
|
+
this._labels = [];
|
|
41821
|
+
if (this._panelId) {
|
|
41822
|
+
var _this$sceneViewer4;
|
|
41823
|
+
(_this$sceneViewer4 = this.sceneViewer) === null || _this$sceneViewer4 === void 0 || (_this$sceneViewer4 = _this$sceneViewer4.managers) === null || _this$sceneViewer4 === void 0 || (_this$sceneViewer4 = _this$sceneViewer4.ioBehaviorManager) === null || _this$sceneViewer4 === void 0 || _this$sceneViewer4.unloadForComponent(this._panelId);
|
|
41824
|
+
}
|
|
41825
|
+
if (this._panel) {
|
|
41826
|
+
var _this$sceneViewer5;
|
|
41827
|
+
(_this$sceneViewer5 = this.sceneViewer) === null || _this$sceneViewer5 === void 0 || (_this$sceneViewer5 = _this$sceneViewer5.scene) === null || _this$sceneViewer5 === void 0 || _this$sceneViewer5.remove(this._panel);
|
|
41828
|
+
this._panel.traverse(function (obj) {
|
|
41829
|
+
if (obj.isMesh || obj.isSprite) {
|
|
41830
|
+
var _obj$geometry, _obj$geometry$dispose;
|
|
41831
|
+
(_obj$geometry = obj.geometry) === null || _obj$geometry === void 0 || (_obj$geometry$dispose = _obj$geometry.dispose) === null || _obj$geometry$dispose === void 0 || _obj$geometry$dispose.call(_obj$geometry);
|
|
41832
|
+
var mats = Array.isArray(obj.material) ? obj.material : [obj.material];
|
|
41833
|
+
mats.forEach(function (m) {
|
|
41834
|
+
var _m$map, _m$map$dispose, _m$dispose;
|
|
41835
|
+
m === null || m === void 0 || (_m$map = m.map) === null || _m$map === void 0 || (_m$map$dispose = _m$map.dispose) === null || _m$map$dispose === void 0 || _m$map$dispose.call(_m$map); // sprite canvas texture
|
|
41836
|
+
m === null || m === void 0 || (_m$dispose = m.dispose) === null || _m$dispose === void 0 || _m$dispose.call(m);
|
|
41837
|
+
});
|
|
41838
|
+
}
|
|
41839
|
+
});
|
|
41840
|
+
this._panel = null;
|
|
41841
|
+
}
|
|
41842
|
+
this._panelId = null;
|
|
41843
|
+
}
|
|
41844
|
+
}, {
|
|
41845
|
+
key: "_doDispose",
|
|
41846
|
+
value: function _doDispose() {
|
|
41847
|
+
var _this$sceneViewer6;
|
|
41848
|
+
if ((_this$sceneViewer6 = this.sceneViewer) !== null && _this$sceneViewer6 !== void 0 && _this$sceneViewer6.off) {
|
|
41849
|
+
this.sceneViewer.off('scene-loaded', this._build);
|
|
41850
|
+
}
|
|
41851
|
+
this._teardown();
|
|
41852
|
+
this.sceneViewer = null;
|
|
41853
|
+
}
|
|
41854
|
+
}]);
|
|
41855
|
+
}(BaseDisposable);
|
|
41856
|
+
|
|
40272
41857
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
40273
41858
|
// Flow-direction helpers (module-level)
|
|
40274
41859
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -40392,6 +41977,8 @@ var CentralPlantInternals = /*#__PURE__*/function () {
|
|
|
40392
41977
|
this.centralPlant.managers.viewport2DManager = new Viewport2DManager(this.centralPlant.sceneViewer);
|
|
40393
41978
|
this.centralPlant.managers.ioBehaviorManager = new IoBehaviorManager(this.centralPlant.sceneViewer);
|
|
40394
41979
|
this.centralPlant.managers.ioOutlineManager = new IoOutlineManager(this.centralPlant.sceneViewer);
|
|
41980
|
+
this.centralPlant.managers.effectiveVisualManager = new EffectiveVisualManager(this.centralPlant.sceneViewer);
|
|
41981
|
+
this.centralPlant.managers.controlPanelManager = new ControlPanelManager(this.centralPlant.sceneViewer);
|
|
40395
41982
|
|
|
40396
41983
|
// All managers are now stored in the managers collection and will be attached via attachToComponent()
|
|
40397
41984
|
}
|
|
@@ -41825,7 +43412,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
41825
43412
|
* Initialize the CentralPlant manager
|
|
41826
43413
|
*
|
|
41827
43414
|
* @constructor
|
|
41828
|
-
* @version 0.3.
|
|
43415
|
+
* @version 0.3.59
|
|
41829
43416
|
* @updated 2025-10-22
|
|
41830
43417
|
*
|
|
41831
43418
|
* @description Creates a new CentralPlant instance and initializes internal managers and utilities.
|
|
@@ -41864,6 +43451,14 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
41864
43451
|
// Initialize internals handler
|
|
41865
43452
|
_this.internals = new CentralPlantInternals(_this);
|
|
41866
43453
|
|
|
43454
|
+
// Portable State Engine: single source of truth for desired/effective state
|
|
43455
|
+
// (plant/group/object/pipe). Owned here; the write door and scene load/save
|
|
43456
|
+
// route through it. The 3D layer/store mirror its output (Framework §12–§13).
|
|
43457
|
+
_this.stateEngine = new StateEngine();
|
|
43458
|
+
_this._unsubscribeStateEngine = _this.stateEngine.subscribe(function (change) {
|
|
43459
|
+
return _this._onEngineStateChange(change);
|
|
43460
|
+
});
|
|
43461
|
+
|
|
41867
43462
|
// Optional cache primer function (dependency injection)
|
|
41868
43463
|
_this.cachePrimer = null;
|
|
41869
43464
|
|
|
@@ -42040,6 +43635,16 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
42040
43635
|
timestamp: new Date().toISOString()
|
|
42041
43636
|
});
|
|
42042
43637
|
|
|
43638
|
+
// Load plant / groups / chains / stateRegistry into the State Engine. v3.0
|
|
43639
|
+
// scenes carry these; legacy v2.3 scenes load with safe defaults (§15).
|
|
43640
|
+
if (this.stateEngine) {
|
|
43641
|
+
try {
|
|
43642
|
+
this.stateEngine.load(extractDeclaration(sceneData));
|
|
43643
|
+
} catch (err) {
|
|
43644
|
+
console.warn('⚠️ setImportedSceneData(): stateEngine.load() failed:', err);
|
|
43645
|
+
}
|
|
43646
|
+
}
|
|
43647
|
+
|
|
42043
43648
|
// Scene behaviors loaded
|
|
42044
43649
|
|
|
42045
43650
|
// Cross-component behaviors are registered by sceneOperationsManager.loadSceneData()
|
|
@@ -42988,12 +44593,32 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
42988
44593
|
}
|
|
42989
44594
|
|
|
42990
44595
|
/**
|
|
42991
|
-
* Internal single path for I/O state changes
|
|
44596
|
+
* Internal single path for I/O state changes. Routes the write through the
|
|
44597
|
+
* State Engine (the one write door, Framework §12); the engine subscription
|
|
44598
|
+
* ({@link CentralPlant#_onEngineStateChange}) mirrors to the state adapter,
|
|
44599
|
+
* animates meshes, and emits. Falls back to a direct write only if the engine
|
|
44600
|
+
* is unavailable.
|
|
42992
44601
|
* @private
|
|
42993
44602
|
*/
|
|
42994
44603
|
}, {
|
|
42995
44604
|
key: "_dispatchIoState",
|
|
42996
44605
|
value: function _dispatchIoState(attachmentId, stateId, value, parentUuid) {
|
|
44606
|
+
if (this.stateEngine) {
|
|
44607
|
+
this.stateEngine.setDesired(ioAddress(attachmentId, stateId, parentUuid), value);
|
|
44608
|
+
return;
|
|
44609
|
+
}
|
|
44610
|
+
this._dispatchIoStateDirect(attachmentId, stateId, value, parentUuid);
|
|
44611
|
+
}
|
|
44612
|
+
|
|
44613
|
+
/**
|
|
44614
|
+
* Persist to the state adapter, animate meshes, and emit for one I/O data
|
|
44615
|
+
* point. This is the mirror side of the engine (driven from desired, since I/O
|
|
44616
|
+
* data points are ungated) and the legacy fallback when no engine is present.
|
|
44617
|
+
* @private
|
|
44618
|
+
*/
|
|
44619
|
+
}, {
|
|
44620
|
+
key: "_dispatchIoStateDirect",
|
|
44621
|
+
value: function _dispatchIoStateDirect(attachmentId, stateId, value, parentUuid) {
|
|
42997
44622
|
var _this$managers, _this$sceneViewer5, _this$sceneViewer6, _this$managers2, _this$sceneViewer7, _this$sceneViewer8, _this$sceneViewer9;
|
|
42998
44623
|
var tooltipMgr = ((_this$managers = this.managers) === null || _this$managers === void 0 ? void 0 : _this$managers.componentTooltipManager) || ((_this$sceneViewer5 = this.sceneViewer) === null || _this$sceneViewer5 === void 0 ? void 0 : _this$sceneViewer5.componentTooltipManager) || ((_this$sceneViewer6 = this.sceneViewer) === null || _this$sceneViewer6 === void 0 || (_this$sceneViewer6 = _this$sceneViewer6.managers) === null || _this$sceneViewer6 === void 0 ? void 0 : _this$sceneViewer6.componentTooltipManager);
|
|
42999
44624
|
var stateAdapter = tooltipMgr === null || tooltipMgr === void 0 ? void 0 : tooltipMgr._stateAdapter;
|
|
@@ -43017,6 +44642,25 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
43017
44642
|
});
|
|
43018
44643
|
}
|
|
43019
44644
|
|
|
44645
|
+
/**
|
|
44646
|
+
* State Engine subscriber: fan a committed registry change out to the 3D/store
|
|
44647
|
+
* layer. I/O device data points (3-part addresses) mirror to the adapter and
|
|
44648
|
+
* animate; every change (incl. plant/group/object/pipe effective) is surfaced
|
|
44649
|
+
* as `engine-state-changed` for gating-aware visual subscribers (§13).
|
|
44650
|
+
* @private
|
|
44651
|
+
* @param {{ address: string, desired: *, effective: * }} change
|
|
44652
|
+
*/
|
|
44653
|
+
}, {
|
|
44654
|
+
key: "_onEngineStateChange",
|
|
44655
|
+
value: function _onEngineStateChange(change) {
|
|
44656
|
+
var _this$sceneViewer0;
|
|
44657
|
+
var io = parseIoAddress(change.address);
|
|
44658
|
+
if (io) {
|
|
44659
|
+
this._dispatchIoStateDirect(io.attachmentId, io.stateId, change.desired, io.parentUuid);
|
|
44660
|
+
}
|
|
44661
|
+
(_this$sceneViewer0 = this.sceneViewer) === null || _this$sceneViewer0 === void 0 || _this$sceneViewer0.emit('engine-state-changed', change);
|
|
44662
|
+
}
|
|
44663
|
+
|
|
43020
44664
|
/**
|
|
43021
44665
|
* Configure the state adapter on both tooltip and behavior managers.
|
|
43022
44666
|
* @param {{ getState: Function, setState: Function }} stateAdapter
|
|
@@ -43024,16 +44668,16 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
43024
44668
|
}, {
|
|
43025
44669
|
key: "configureStateAdapter",
|
|
43026
44670
|
value: function configureStateAdapter(stateAdapter) {
|
|
43027
|
-
var _this$managers3, _this$
|
|
43028
|
-
var tooltipMgr = ((_this$managers3 = this.managers) === null || _this$managers3 === void 0 ? void 0 : _this$managers3.componentTooltipManager) || ((_this$
|
|
44671
|
+
var _this$managers3, _this$sceneViewer1, _this$managers4, _this$sceneViewer11, _this$sceneViewer12;
|
|
44672
|
+
var tooltipMgr = ((_this$managers3 = this.managers) === null || _this$managers3 === void 0 ? void 0 : _this$managers3.componentTooltipManager) || ((_this$sceneViewer1 = this.sceneViewer) === null || _this$sceneViewer1 === void 0 ? void 0 : _this$sceneViewer1.componentTooltipManager);
|
|
43029
44673
|
if (tooltipMgr !== null && tooltipMgr !== void 0 && tooltipMgr.configure) {
|
|
43030
|
-
var _this$
|
|
44674
|
+
var _this$sceneViewer10;
|
|
43031
44675
|
tooltipMgr.configure(stateAdapter);
|
|
43032
|
-
if ((_this$
|
|
44676
|
+
if ((_this$sceneViewer10 = this.sceneViewer) !== null && _this$sceneViewer10 !== void 0 && _this$sceneViewer10.managers) {
|
|
43033
44677
|
this.sceneViewer.managers.componentTooltipManager = tooltipMgr;
|
|
43034
44678
|
}
|
|
43035
44679
|
}
|
|
43036
|
-
var ioBehavMgr = ((_this$managers4 = this.managers) === null || _this$managers4 === void 0 ? void 0 : _this$managers4.ioBehaviorManager) || ((_this$
|
|
44680
|
+
var ioBehavMgr = ((_this$managers4 = this.managers) === null || _this$managers4 === void 0 ? void 0 : _this$managers4.ioBehaviorManager) || ((_this$sceneViewer11 = this.sceneViewer) === null || _this$sceneViewer11 === void 0 || (_this$sceneViewer11 = _this$sceneViewer11.managers) === null || _this$sceneViewer11 === void 0 ? void 0 : _this$sceneViewer11.ioBehaviorManager) || ((_this$sceneViewer12 = this.sceneViewer) === null || _this$sceneViewer12 === void 0 ? void 0 : _this$sceneViewer12.ioBehaviorManager);
|
|
43037
44681
|
if (ioBehavMgr !== null && ioBehavMgr !== void 0 && ioBehavMgr.configure) {
|
|
43038
44682
|
ioBehavMgr.configure(stateAdapter);
|
|
43039
44683
|
}
|
|
@@ -43068,8 +44712,8 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
43068
44712
|
}, {
|
|
43069
44713
|
key: "getSceneAttachments",
|
|
43070
44714
|
value: function getSceneAttachments() {
|
|
43071
|
-
var _this$
|
|
43072
|
-
var scene = (_this$
|
|
44715
|
+
var _this$sceneViewer13;
|
|
44716
|
+
var scene = (_this$sceneViewer13 = this.sceneViewer) === null || _this$sceneViewer13 === void 0 ? void 0 : _this$sceneViewer13.scene;
|
|
43073
44717
|
if (!scene) return [];
|
|
43074
44718
|
var results = [];
|
|
43075
44719
|
scene.traverse(function (obj) {
|
|
@@ -44850,12 +46494,14 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
44850
46494
|
}, {
|
|
44851
46495
|
key: "clearScene",
|
|
44852
46496
|
value: function clearScene() {
|
|
44853
|
-
var _this$
|
|
46497
|
+
var _this$sceneViewer14, _this$stateEngine;
|
|
44854
46498
|
this.importedSceneData = null;
|
|
44855
|
-
var ioBehavMgr = (_this$
|
|
46499
|
+
var ioBehavMgr = (_this$sceneViewer14 = this.sceneViewer) === null || _this$sceneViewer14 === void 0 || (_this$sceneViewer14 = _this$sceneViewer14.managers) === null || _this$sceneViewer14 === void 0 ? void 0 : _this$sceneViewer14.ioBehaviorManager;
|
|
44856
46500
|
if (ioBehavMgr) {
|
|
44857
46501
|
ioBehavMgr.setCrossComponentBehaviors([]);
|
|
44858
46502
|
}
|
|
46503
|
+
// Reset the registry so pipe/object keys from the old scene don't linger (§15).
|
|
46504
|
+
(_this$stateEngine = this.stateEngine) === null || _this$stateEngine === void 0 || _this$stateEngine.reset();
|
|
44859
46505
|
if (this.sceneViewer && this.sceneViewer.scene) {
|
|
44860
46506
|
this.sceneViewer.scene.clear();
|
|
44861
46507
|
}
|
|
@@ -44906,8 +46552,16 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
44906
46552
|
}, {
|
|
44907
46553
|
key: "_doDispose",
|
|
44908
46554
|
value: function _doDispose() {
|
|
46555
|
+
var _this$stateEngine2;
|
|
44909
46556
|
console.log('🧹 Disposing CentralPlant using enhanced disposal patterns...');
|
|
44910
46557
|
|
|
46558
|
+
// Tear down the State Engine subscription and registry.
|
|
46559
|
+
if (this._unsubscribeStateEngine) {
|
|
46560
|
+
this._unsubscribeStateEngine();
|
|
46561
|
+
this._unsubscribeStateEngine = null;
|
|
46562
|
+
}
|
|
46563
|
+
(_this$stateEngine2 = this.stateEngine) === null || _this$stateEngine2 === void 0 || _this$stateEngine2.reset();
|
|
46564
|
+
|
|
44911
46565
|
// Use batch disposal for all managers
|
|
44912
46566
|
var managers = Object.values(this.managers).filter(function (manager) {
|
|
44913
46567
|
return manager && typeof manager.dispose === 'function';
|
|
@@ -50781,8 +52435,10 @@ exports.SceneInitializationManager = SceneInitializationManager;
|
|
|
50781
52435
|
exports.SceneOperationsManager = SceneOperationsManager;
|
|
50782
52436
|
exports.SceneTooltipsManager = SceneTooltipsManager;
|
|
50783
52437
|
exports.SnapshotManager = SnapshotManager;
|
|
52438
|
+
exports.StateEngine = StateEngine;
|
|
50784
52439
|
exports.ThreeJSResourceManager = ThreeJSResourceManager;
|
|
50785
52440
|
exports.applyCrossComponentBehaviors = applyCrossComponentBehaviors;
|
|
52441
|
+
exports.applyDeclarationToExport = applyDeclarationToExport;
|
|
50786
52442
|
exports.applyDefaultIoDeviceStates = applyDefaultIoDeviceStates;
|
|
50787
52443
|
exports.applyModelRootTranslation = applyModelRootTranslation;
|
|
50788
52444
|
exports.applyModelRootTranslationFromWorldBase = applyModelRootTranslationFromWorldBase;
|
|
@@ -50796,6 +52452,7 @@ exports.clearS3Cache = clearS3Cache;
|
|
|
50796
52452
|
exports.collectSceneLibraryIds = collectSceneLibraryIds;
|
|
50797
52453
|
exports.createPathfindingRequest = createPathfindingRequest;
|
|
50798
52454
|
exports.createTransformControls = createTransformControls;
|
|
52455
|
+
exports.extractDeclaration = extractDeclaration;
|
|
50799
52456
|
exports.findObjectByHardcodedUuid = findObjectByHardcodedUuid;
|
|
50800
52457
|
exports.formatCacheExpiry = formatCacheExpiry;
|
|
50801
52458
|
exports.generateUniqueComponentId = generateUniqueComponentId;
|
|
@@ -50821,6 +52478,7 @@ exports.getObjectsByType = getObjectsByType;
|
|
|
50821
52478
|
exports.getScopedAttachmentKey = getScopedAttachmentKey;
|
|
50822
52479
|
exports.getThumbnailKey = getThumbnailKey;
|
|
50823
52480
|
exports.getUserOnlyCacheStats = getUserOnlyCacheStats;
|
|
52481
|
+
exports.ioAddress = ioAddress;
|
|
50824
52482
|
exports.isCached = isCached;
|
|
50825
52483
|
exports.isComponent = isComponent;
|
|
50826
52484
|
exports.isComputed = isComputed;
|
|
@@ -50832,14 +52490,17 @@ exports.isSegment = isSegment;
|
|
|
50832
52490
|
exports.isThumbnailCached = isThumbnailCached;
|
|
50833
52491
|
exports.loadCrossComponentBehaviors = loadCrossComponentBehaviors;
|
|
50834
52492
|
exports.loadTextureSetAndCreateMaterial = loadTextureSetAndCreateMaterial;
|
|
52493
|
+
exports.makeAddress = makeAddress;
|
|
50835
52494
|
exports.markAsComputed = markAsComputed;
|
|
50836
52495
|
exports.markAsDeclared = markAsDeclared;
|
|
50837
52496
|
exports.measureS3Transfer = measureS3Transfer;
|
|
50838
52497
|
exports.modelOffsetToWorldDelta = modelOffsetToWorldDelta;
|
|
50839
52498
|
exports.modelPreloader = modelPreloader;
|
|
50840
52499
|
exports.normalizeBehavior = normalizeBehavior;
|
|
52500
|
+
exports.offValue = offValue;
|
|
50841
52501
|
exports.parseCrossBehavior = parseCrossBehavior;
|
|
50842
52502
|
exports.parseIntraBehavior = parseIntraBehavior;
|
|
52503
|
+
exports.parseIoAddress = parseIoAddress;
|
|
50843
52504
|
exports.preloadLocalFiles = preloadLocalFiles;
|
|
50844
52505
|
exports.preloadS3Objects = preloadS3Objects;
|
|
50845
52506
|
exports.refreshSceneIntraBehaviors = refreshSceneIntraBehaviors;
|
|
@@ -50856,3 +52517,4 @@ exports.scanSceneIoEndpoints = scanSceneIoEndpoints;
|
|
|
50856
52517
|
exports.sceneViewer = sceneViewer;
|
|
50857
52518
|
exports.shouldRemoveOnRegeneration = shouldRemoveOnRegeneration;
|
|
50858
52519
|
exports.switchCachePartition = switchCachePartition;
|
|
52520
|
+
exports.valuesEqual = valuesEqual;
|