@2112-lab/central-plant 0.3.58 → 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.
Files changed (27) hide show
  1. package/dist/bundle/index.js +1635 -32
  2. package/dist/cjs/src/core/centralPlant.js +80 -11
  3. package/dist/cjs/src/core/centralPlantInternals.js +4 -0
  4. package/dist/cjs/src/core/centralPlantValidator.js +10 -6
  5. package/dist/cjs/src/core/stateEngine.js +733 -0
  6. package/dist/cjs/src/core/stateEngineScene.js +97 -0
  7. package/dist/cjs/src/index.js +10 -0
  8. package/dist/cjs/src/managers/pathfinding/PathFlowManager.js +94 -13
  9. package/dist/cjs/src/managers/pathfinding/PathRenderingManager.js +15 -0
  10. package/dist/cjs/src/managers/scene/componentTooltipManager.js +7 -1
  11. package/dist/cjs/src/managers/scene/controlPanelManager.js +377 -0
  12. package/dist/cjs/src/managers/scene/sceneExportManager.js +10 -1
  13. package/dist/cjs/src/managers/state/EffectiveVisualManager.js +270 -0
  14. package/dist/esm/src/core/centralPlant.js +80 -11
  15. package/dist/esm/src/core/centralPlantInternals.js +4 -0
  16. package/dist/esm/src/core/centralPlantValidator.js +10 -6
  17. package/dist/esm/src/core/stateEngine.js +725 -0
  18. package/dist/esm/src/core/stateEngineScene.js +90 -0
  19. package/dist/esm/src/index.js +2 -0
  20. package/dist/esm/src/managers/pathfinding/PathFlowManager.js +94 -13
  21. package/dist/esm/src/managers/pathfinding/PathRenderingManager.js +15 -0
  22. package/dist/esm/src/managers/scene/componentTooltipManager.js +7 -1
  23. package/dist/esm/src/managers/scene/controlPanelManager.js +352 -0
  24. package/dist/esm/src/managers/scene/sceneExportManager.js +10 -1
  25. package/dist/esm/src/managers/state/EffectiveVisualManager.js +266 -0
  26. package/dist/index.d.ts +85 -0
  27. package/package.json +5 -2
@@ -0,0 +1,97 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var _rollupPluginBabelHelpers = require('../../_virtual/_rollupPluginBabelHelpers.js');
6
+ var stateEngine = require('./stateEngine.js');
7
+ var behaviorDispatch = require('../utils/behaviorDispatch.js');
8
+
9
+ /**
10
+ * Registry address for an I/O device data point.
11
+ * Shape: `${parentUuid}::${attachmentId}::${stateId}` (matches §17
12
+ * `pump-001::start-switch::mode`). The scoped key is `${parentUuid}::${attachmentId}`.
13
+ *
14
+ * @param {string} attachmentId
15
+ * @param {string} stateId
16
+ * @param {string} [parentUuid]
17
+ * @returns {string}
18
+ */
19
+ function ioAddress(attachmentId, stateId, parentUuid) {
20
+ return stateEngine.makeAddress(behaviorDispatch.getScopedAttachmentKey(attachmentId, parentUuid), stateId);
21
+ }
22
+
23
+ /**
24
+ * Parse an I/O device address back into its parts. Returns null for
25
+ * plant/group/object/pipe addresses (2-part) — I/O device data points are 3-part
26
+ * because a device always has a parent component (§8; free-floating I/O rejected).
27
+ *
28
+ * @param {string} address
29
+ * @returns {{ parentUuid: string, attachmentId: string, stateId: string, scopedKey: string } | null}
30
+ */
31
+ function parseIoAddress(address) {
32
+ if (typeof address !== 'string') return null;
33
+ var parts = address.split('::');
34
+ if (parts.length !== 3) return null;
35
+ var _parts = _rollupPluginBabelHelpers.slicedToArray(parts, 3),
36
+ parentUuid = _parts[0],
37
+ attachmentId = _parts[1],
38
+ stateId = _parts[2];
39
+ return {
40
+ parentUuid: parentUuid,
41
+ attachmentId: attachmentId,
42
+ stateId: stateId,
43
+ scopedKey: "".concat(parentUuid, "::").concat(attachmentId)
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Read a State Engine declaration out of scene data, tolerating legacy scenes.
49
+ * v3.0 scenes carry `plant` / `groups` / `chains` / `stateRegistry`; older v2.3
50
+ * scenes have none of these and load with safe defaults (plant power on, no
51
+ * groups, no chains) so nothing crashes on unknown/missing fields (§15).
52
+ *
53
+ * @param {object} [sceneData]
54
+ * @returns {{ plant: object, groups: object, chains: object[], stateRegistry: object }}
55
+ */
56
+ function extractDeclaration() {
57
+ var sceneData = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
58
+ return {
59
+ plant: sceneData.plant || {
60
+ states: {
61
+ power: {
62
+ type: 'Boolean',
63
+ default: true,
64
+ gate: true
65
+ }
66
+ }
67
+ },
68
+ groups: sceneData.groups || {},
69
+ chains: Array.isArray(sceneData.chains) ? sceneData.chains : [],
70
+ stateRegistry: sceneData.stateRegistry || {}
71
+ };
72
+ }
73
+
74
+ /**
75
+ * Merge a State Engine's current declaration (plant / groups / chains /
76
+ * stateRegistry) into an export object, bumping it to version 3.0. Mutates and
77
+ * returns `exportData`.
78
+ *
79
+ * @param {object} exportData - the scene export object being assembled
80
+ * @param {{ toDeclaration: () => object }} engine
81
+ * @returns {object} exportData
82
+ */
83
+ function applyDeclarationToExport(exportData, engine) {
84
+ if (!exportData || !engine || typeof engine.toDeclaration !== 'function') return exportData;
85
+ var declaration = engine.toDeclaration();
86
+ exportData.version = '3.0';
87
+ exportData.plant = declaration.plant;
88
+ exportData.groups = declaration.groups;
89
+ if (declaration.chains.length > 0) exportData.chains = declaration.chains;
90
+ exportData.stateRegistry = declaration.stateRegistry;
91
+ return exportData;
92
+ }
93
+
94
+ exports.applyDeclarationToExport = applyDeclarationToExport;
95
+ exports.extractDeclaration = extractDeclaration;
96
+ exports.ioAddress = ioAddress;
97
+ exports.parseIoAddress = parseIoAddress;
@@ -4,6 +4,8 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var centralPlant = require('./core/centralPlant.js');
6
6
  var sceneViewer = require('./core/sceneViewer.js');
7
+ var stateEngine = require('./core/stateEngine.js');
8
+ var stateEngineScene = require('./core/stateEngineScene.js');
7
9
  var nameUtils = require('./utils/nameUtils.js');
8
10
  var pathfindingData = require('./core/pathfindingData.js');
9
11
  var objectTypes = require('./utils/objectTypes.js');
@@ -48,6 +50,14 @@ var rendering3D = require('./rendering/rendering3D.js');
48
50
 
49
51
  exports.CentralPlant = centralPlant["default"];
50
52
  exports.sceneViewer = sceneViewer["default"];
53
+ exports.StateEngine = stateEngine.StateEngine;
54
+ exports.makeAddress = stateEngine.makeAddress;
55
+ exports.offValue = stateEngine.offValue;
56
+ exports.valuesEqual = stateEngine.valuesEqual;
57
+ exports.applyDeclarationToExport = stateEngineScene.applyDeclarationToExport;
58
+ exports.extractDeclaration = stateEngineScene.extractDeclaration;
59
+ exports.ioAddress = stateEngineScene.ioAddress;
60
+ exports.parseIoAddress = stateEngineScene.parseIoAddress;
51
61
  exports.findObjectByHardcodedUuid = nameUtils.findObjectByHardcodedUuid;
52
62
  exports.generateUniqueComponentId = nameUtils.generateUniqueComponentId;
53
63
  exports.generateUuidFromName = nameUtils.generateUuidFromName;
@@ -14,6 +14,9 @@ var TEMP_MIN = 0;
14
14
  /** Maximum temperature in the colour ramp (maps to pure red). */
15
15
  var TEMP_MAX = 100;
16
16
 
17
+ /** Colour a pipe is painted when its effective flow is gated off (§6.1). */
18
+ var PIPE_OFF_GRAY = '#3a3a3a';
19
+
17
20
  /**
18
21
  * Convert a temperature value to a CSS hex colour string using a blue→red HSL
19
22
  * ramp. Values are clamped to [TEMP_MIN, TEMP_MAX].
@@ -51,22 +54,78 @@ var PathFlowManager = /*#__PURE__*/function (_BaseDisposable) {
51
54
  * @type {Map<string, Record<string, any>>}
52
55
  */
53
56
  _this._derivedStore = new Map();
57
+
58
+ /**
59
+ * Paths whose effective flow is gated off (State Engine §6.1). While a path
60
+ * is in this set it is painted {@link PIPE_OFF_GRAY} and temperature updates
61
+ * are suppressed so they don't override the off-look.
62
+ * @type {Set<string>}
63
+ */
64
+ _this._flowGatedOff = new Set();
65
+
66
+ /**
67
+ * Baseline (on-state) colour snapshotted the moment a path is gated off, so
68
+ * it can be restored exactly when flow returns — including pipes with no
69
+ * declared flowTemperature, whose colour is the base pipe material.
70
+ * @type {Map<string, string>}
71
+ */
72
+ _this._flowBaseColor = new Map();
54
73
  return _this;
55
74
  }
56
75
 
57
- // ── Public API attribute write ──────────────────────────────────────────
76
+ // ── Flow gating (State Engine effective → 3D bridge, §6.1) ─────────────────
58
77
 
59
78
  /**
60
- * Set a declared (persistent) flow attribute on a path.
61
- * The value is stored in PathData and will be serialised with the scene.
62
- * Triggers a visual update.
79
+ * Gate a path's flow visual on/off from the State Engine's effective `flow`.
80
+ * Off paint the shared per-path material gray; on restore the temperature
81
+ * colour. The off state is sticky (see {@link _flowGatedOff}).
63
82
  *
64
- * @param {string} pathId - e.g. "PUMP-1-CONN-1-->CHILLER-1-CONN-2"
65
- * @param {string} key - One of FLOW_ATTRIBUTE_KEYS
66
- * @param {any} value
83
+ * @param {string} pathId - `${from}-->${to}`
84
+ * @param {boolean} enabled - Effective flow
67
85
  */
68
86
  _rollupPluginBabelHelpers.inherits(PathFlowManager, _BaseDisposable);
69
87
  return _rollupPluginBabelHelpers.createClass(PathFlowManager, [{
88
+ key: "setFlowEnabled",
89
+ value: function setFlowEnabled(pathId, enabled) {
90
+ var split = this._splitPathId(pathId);
91
+ if (!split) return;
92
+ var renderingManager = this._getRenderingManager();
93
+ if (enabled) {
94
+ this._flowGatedOff.delete(pathId);
95
+ // Prefer a temperature-derived colour if one is declared; otherwise
96
+ // restore the baseline snapshotted when the pipe was gated off (this is
97
+ // what fixes plain, non-temperature pipes getting stuck gray).
98
+ var temp = this.resolve(pathId, 'flowTemperature');
99
+ if (temp !== null && temp !== undefined) {
100
+ this.applyVisualizationForPath(pathId);
101
+ } else if (this._flowBaseColor.has(pathId)) {
102
+ renderingManager === null || renderingManager === void 0 || renderingManager.updatePathColor(split.from, split.to, this._flowBaseColor.get(pathId));
103
+ }
104
+ this._flowBaseColor.delete(pathId);
105
+ } else {
106
+ // Snapshot the current (on-state) colour once, before overpainting gray.
107
+ if (!this._flowGatedOff.has(pathId) && renderingManager) {
108
+ var _renderingManager$get;
109
+ var base = (_renderingManager$get = renderingManager.getPathColorHex) === null || _renderingManager$get === void 0 ? void 0 : _renderingManager$get.call(renderingManager, pathId);
110
+ if (base) this._flowBaseColor.set(pathId, base);
111
+ }
112
+ this._flowGatedOff.add(pathId);
113
+ renderingManager === null || renderingManager === void 0 || renderingManager.updatePathColor(split.from, split.to, PIPE_OFF_GRAY);
114
+ }
115
+ }
116
+
117
+ // ── Public API — attribute write ──────────────────────────────────────────
118
+
119
+ /**
120
+ * Set a declared (persistent) flow attribute on a path.
121
+ * The value is stored in PathData and will be serialised with the scene.
122
+ * Triggers a visual update.
123
+ *
124
+ * @param {string} pathId - e.g. "PUMP-1-CONN-1-->CHILLER-1-CONN-2"
125
+ * @param {string} key - One of FLOW_ATTRIBUTE_KEYS
126
+ * @param {any} value
127
+ */
128
+ }, {
70
129
  key: "setDeclared",
71
130
  value: function setDeclared(pathId, key, value) {
72
131
  var pd = this._getPathData(pathId);
@@ -172,6 +231,13 @@ var PathFlowManager = /*#__PURE__*/function (_BaseDisposable) {
172
231
  }, {
173
232
  key: "applyVisualizationForPath",
174
233
  value: function applyVisualizationForPath(pathId) {
234
+ // Effective flow gated off (§6.1) — keep the off-look, ignore temperature.
235
+ if (this._flowGatedOff.has(pathId)) {
236
+ var _this$_getRenderingMa;
237
+ var _split = this._splitPathId(pathId);
238
+ if (_split) (_this$_getRenderingMa = this._getRenderingManager()) === null || _this$_getRenderingMa === void 0 || _this$_getRenderingMa.updatePathColor(_split.from, _split.to, PIPE_OFF_GRAY);
239
+ return;
240
+ }
175
241
  var temp = this.resolve(pathId, 'flowTemperature');
176
242
  if (temp === null || temp === undefined) {
177
243
  return; // No temperature declared — leave default pipe color
@@ -179,17 +245,30 @@ var PathFlowManager = /*#__PURE__*/function (_BaseDisposable) {
179
245
  var color = temperatureToColor(temp);
180
246
  var renderingManager = this._getRenderingManager();
181
247
  if (!renderingManager) return;
248
+ var split = this._splitPathId(pathId);
249
+ if (!split) return;
250
+ renderingManager.updatePathColor(split.from, split.to, color);
251
+ console.log("\uD83C\uDF21\uFE0F PathFlowManager: \"".concat(pathId, "\" \u2192 flowTemperature ").concat(temp, " \u2192 ").concat(color));
252
+ }
182
253
 
183
- // pathId = "from-->to"; extract the two halves
254
+ /**
255
+ * Split a `${from}-->${to}` pathId into its two connector halves.
256
+ * @param {string} pathId
257
+ * @returns {{ from: string, to: string }|null} null if malformed
258
+ * @private
259
+ */
260
+ }, {
261
+ key: "_splitPathId",
262
+ value: function _splitPathId(pathId) {
184
263
  var sepIdx = pathId.indexOf('-->');
185
264
  if (sepIdx === -1) {
186
265
  console.warn("PathFlowManager: malformed pathId \"".concat(pathId, "\""));
187
- return;
266
+ return null;
188
267
  }
189
- var from = pathId.slice(0, sepIdx);
190
- var to = pathId.slice(sepIdx + 3);
191
- renderingManager.updatePathColor(from, to, color);
192
- console.log("\uD83C\uDF21\uFE0F PathFlowManager: \"".concat(pathId, "\" \u2192 flowTemperature ").concat(temp, " \u2192 ").concat(color));
268
+ return {
269
+ from: pathId.slice(0, sepIdx),
270
+ to: pathId.slice(sepIdx + 3)
271
+ };
193
272
  }
194
273
 
195
274
  /**
@@ -221,6 +300,8 @@ var PathFlowManager = /*#__PURE__*/function (_BaseDisposable) {
221
300
  key: "dispose",
222
301
  value: function dispose() {
223
302
  this._derivedStore.clear();
303
+ this._flowGatedOff.clear();
304
+ this._flowBaseColor.clear();
224
305
  this.sceneViewer = null;
225
306
  _rollupPluginBabelHelpers.superPropGet(PathFlowManager, "dispose", this, 3)([]);
226
307
  }
@@ -88,6 +88,21 @@ var PathRenderingManager = /*#__PURE__*/function (_BaseDisposable) {
88
88
  }
89
89
  }
90
90
 
91
+ /**
92
+ * Read the current color of a path's shared material as a `#rrggbb` string,
93
+ * or null if the path has no material. Used to snapshot a pipe's baseline
94
+ * colour before it is temporarily overpainted (e.g. flow gating).
95
+ *
96
+ * @param {string} pathId - "${from}-->${to}"
97
+ * @returns {string|null}
98
+ */
99
+ }, {
100
+ key: "getPathColorHex",
101
+ value: function getPathColorHex(pathId) {
102
+ var mat = this._pathMaterials.get(pathId);
103
+ return mat ? "#".concat(mat.color.getHexString()) : null;
104
+ }
105
+
91
106
  /**
92
107
  * Get path colors for visual distinction
93
108
  * @param {number} index - Path index
@@ -144,7 +144,6 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
144
144
  if (!ioDeviceObject || !this._stateAdapter) return;
145
145
  var ud = ioDeviceObject.userData;
146
146
  var attachmentId = ud === null || ud === void 0 ? void 0 : ud.attachmentId;
147
- var dataPoints = (ud === null || ud === void 0 ? void 0 : ud.dataPoints) || [];
148
147
  if (!attachmentId) return;
149
148
 
150
149
  // Walk up to find parent component UUID for scoped state/behavior handling
@@ -159,6 +158,13 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
159
158
  obj = obj.parent;
160
159
  }
161
160
 
161
+ // Derive the device's data points from its behaviorConfig (same resolver the
162
+ // drag path uses). `userData.dataPoints` is not populated for attached
163
+ // devices, so reading it directly would find nothing — resolve instead, and
164
+ // keep the legacy field only as a fallback.
165
+ var resolved = behaviorDispatch.resolveDataPoints(parentUuid, attachmentId, ud, behaviorDispatch.getIoBehaviorManager(this.sceneViewer), null);
166
+ var dataPoints = resolved !== null && resolved !== void 0 && resolved.length ? resolved : (ud === null || ud === void 0 ? void 0 : ud.dataPoints) || [];
167
+
162
168
  // Create a scoped attachment key to prevent state sharing between instances
163
169
  // of the same smart component that share the same attachmentId
164
170
  var scopedAttachmentId = behaviorDispatch.getScopedAttachmentKey(attachmentId, parentUuid);