@2112-lab/central-plant 0.2.8 → 0.2.10

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.
@@ -25783,10 +25783,19 @@ function createPathfindingRequest(startConnector, endConnector) {
25783
25783
  };
25784
25784
  }
25785
25785
 
25786
+ /**
25787
+ * The official set of flow-related attribute keys that can be set on a path.
25788
+ * These attributes belong to the full path and are inherited by all segments.
25789
+ *
25790
+ * @type {string[]}
25791
+ */
25792
+ var FLOW_ATTRIBUTE_KEYS = ['flowDirection', 'flowSpeed', 'flowTemperature', 'flowMaterial'];
25793
+
25786
25794
  /**
25787
25795
  * Data structure for storing path information with segments.
25788
25796
  * Tracks both computed and declared (manually edited) segments.
25789
- *
25797
+ * Also stores path-level flow attributes shared by all segments.
25798
+ *
25790
25799
  * Business logic layer - stores coordinate data without Three.js dependencies.
25791
25800
  */
25792
25801
  var PathData = /*#__PURE__*/function () {
@@ -25806,16 +25815,60 @@ var PathData = /*#__PURE__*/function () {
25806
25815
  * @type {Array<{start: {x, y, z}, end: {x, y, z}, isDeclared: boolean, modifiedAt: number|null}>}
25807
25816
  */
25808
25817
  this.segments = [];
25818
+
25819
+ /**
25820
+ * Path-level flow attributes shared by all segments within this path.
25821
+ * Keys must come from FLOW_ATTRIBUTE_KEYS. Values are application-defined.
25822
+ * @type {Record<string, any>}
25823
+ */
25824
+ this.flowAttributes = {};
25809
25825
  this.createdAt = Date.now();
25810
25826
  }
25811
25827
 
25812
25828
  /**
25813
- * Add a computed segment (from pathfinding algorithm)
25814
- *
25815
- * @param {{x: number, y: number, z: number}} start - Start coordinate
25816
- * @param {{x: number, y: number, z: number}} end - End coordinate
25829
+ * Set a flow attribute on this path.
25830
+ * All segments within this path inherit the value.
25831
+ *
25832
+ * @param {string} key - Attribute key (should be one of FLOW_ATTRIBUTE_KEYS)
25833
+ * @param {any} value - Attribute value
25817
25834
  */
25818
25835
  return _createClass(PathData, [{
25836
+ key: "setFlowAttribute",
25837
+ value: function setFlowAttribute(key, value) {
25838
+ this.flowAttributes[key] = value;
25839
+ }
25840
+
25841
+ /**
25842
+ * Get the declared value of a flow attribute.
25843
+ * Returns null if the attribute has not been set.
25844
+ *
25845
+ * @param {string} key - Attribute key
25846
+ * @returns {any|null}
25847
+ */
25848
+ }, {
25849
+ key: "getFlowAttribute",
25850
+ value: function getFlowAttribute(key) {
25851
+ return Object.prototype.hasOwnProperty.call(this.flowAttributes, key) ? this.flowAttributes[key] : null;
25852
+ }
25853
+
25854
+ /**
25855
+ * Get a shallow copy of all declared flow attributes.
25856
+ *
25857
+ * @returns {Record<string, any>}
25858
+ */
25859
+ }, {
25860
+ key: "getAllFlowAttributes",
25861
+ value: function getAllFlowAttributes() {
25862
+ return _objectSpread2({}, this.flowAttributes);
25863
+ }
25864
+
25865
+ /**
25866
+ * Add a computed segment (from pathfinding algorithm)
25867
+ *
25868
+ * @param {{x: number, y: number, z: number}} start - Start coordinate
25869
+ * @param {{x: number, y: number, z: number}} end - End coordinate
25870
+ */
25871
+ }, {
25819
25872
  key: "addSegment",
25820
25873
  value: function addSegment(start, end) {
25821
25874
  this.segments.push({
@@ -25892,6 +25945,7 @@ var PathData = /*#__PURE__*/function () {
25892
25945
  segments: this.segments.map(function (seg) {
25893
25946
  return _objectSpread2({}, seg);
25894
25947
  }),
25948
+ flowAttributes: _objectSpread2({}, this.flowAttributes),
25895
25949
  createdAt: this.createdAt
25896
25950
  };
25897
25951
  }
@@ -25952,6 +26006,7 @@ var PathData = /*#__PURE__*/function () {
25952
26006
  pathData.segments = json.segments.map(function (seg) {
25953
26007
  return _objectSpread2({}, seg);
25954
26008
  });
26009
+ pathData.flowAttributes = json.flowAttributes ? _objectSpread2({}, json.flowAttributes) : {};
25955
26010
  pathData.createdAt = json.createdAt || Date.now();
25956
26011
  return pathData;
25957
26012
  }
@@ -26109,16 +26164,58 @@ var PathRenderingManager = /*#__PURE__*/function (_BaseDisposable) {
26109
26164
  emissive: 0
26110
26165
  });
26111
26166
  _this.registerDisposable(_this._gatewayGeometry, _this._gatewayMaterial);
26167
+
26168
+ /**
26169
+ * Map of pathId -> THREE.Material for per-path color control.
26170
+ * pathId format: "${from}-->${to}"
26171
+ * All segments within a path share the same material instance.
26172
+ * @type {Map<string, THREE.Material>}
26173
+ */
26174
+ _this._pathMaterials = new Map();
26112
26175
  return _this;
26113
26176
  }
26114
26177
 
26115
26178
  /**
26116
- * Get path colors for visual distinction
26117
- * @param {number} index - Path index
26118
- * @returns {string} Hex color string
26179
+ * Dispose all tracked per-path materials and clear the map.
26180
+ * Called at the start of createPipePaths to clean up stale materials.
26181
+ * @private
26119
26182
  */
26120
26183
  _inherits(PathRenderingManager, _BaseDisposable);
26121
26184
  return _createClass(PathRenderingManager, [{
26185
+ key: "_clearPathMaterials",
26186
+ value: function _clearPathMaterials() {
26187
+ this._pathMaterials.forEach(function (mat) {
26188
+ return mat.dispose();
26189
+ });
26190
+ this._pathMaterials.clear();
26191
+ }
26192
+
26193
+ /**
26194
+ * Update the color of all pipe segments belonging to a specific path.
26195
+ * Operates on the shared per-path material — no scene traversal needed.
26196
+ *
26197
+ * @param {string} from - Source connector ID
26198
+ * @param {string} to - Target connector ID
26199
+ * @param {string|number} color - Any value accepted by THREE.Color.set()
26200
+ */
26201
+ }, {
26202
+ key: "updatePathColor",
26203
+ value: function updatePathColor(from, to, color) {
26204
+ var pathId = "".concat(from, "-->").concat(to);
26205
+ var mat = this._pathMaterials.get(pathId);
26206
+ if (mat) {
26207
+ mat.color.set(color);
26208
+ } else {
26209
+ console.warn("\u26A0\uFE0F PathRenderingManager.updatePathColor: no material found for path \"".concat(pathId, "\""));
26210
+ }
26211
+ }
26212
+
26213
+ /**
26214
+ * Get path colors for visual distinction
26215
+ * @param {number} index - Path index
26216
+ * @returns {string} Hex color string
26217
+ */
26218
+ }, {
26122
26219
  key: "getPathColor",
26123
26220
  value: function getPathColor(index) {
26124
26221
  var colors = [
@@ -26273,7 +26370,11 @@ var PathRenderingManager = /*#__PURE__*/function (_BaseDisposable) {
26273
26370
  var globalSegmentIndex = maxExistingIndex + 1;
26274
26371
  console.log("\uD83D\uDD22 Starting segment index at ".concat(globalSegmentIndex, " (max existing: ").concat(maxExistingIndex, ")"));
26275
26372
  var pipeRadius = 0.1;
26276
- var pipeMaterial = this.createPipeMaterial(crosscubeTextureSet);
26373
+ // Base material created once; per-path materials are cloned from it below.
26374
+ var baseMaterial = this.createPipeMaterial(crosscubeTextureSet);
26375
+
26376
+ // Dispose previous path materials (they were attached to now-removed segments)
26377
+ this._clearPathMaterials();
26277
26378
  paths.forEach(function (pathData, index) {
26278
26379
  if (pathData.path && pathData.path.length >= 2) {
26279
26380
  // Convert path points to Vector3 objects for consistent handling
@@ -26290,6 +26391,12 @@ var PathRenderingManager = /*#__PURE__*/function (_BaseDisposable) {
26290
26391
  }
26291
26392
  });
26292
26393
 
26394
+ // Create a per-path material clone so each path can have its color updated independently.
26395
+ var pathId = "".concat(pathData.from, "-->").concat(pathData.to);
26396
+ var perPathMaterial = baseMaterial.clone();
26397
+ _this3._pathMaterials.set(pathId, perPathMaterial);
26398
+ console.log("\uD83C\uDFA8 Created per-path material for \"".concat(pathId, "\""));
26399
+
26293
26400
  // Check if endpoints are component connectors (from pathfinder result)
26294
26401
  var fromIsComponentConnector = pathData.fromObjectType === 'component-connector';
26295
26402
  var toIsComponentConnector = pathData.toObjectType === 'component-connector';
@@ -26314,13 +26421,13 @@ var PathRenderingManager = /*#__PURE__*/function (_BaseDisposable) {
26314
26421
  var cylinderGeometry = new THREE__namespace.CylinderGeometry(pipeRadius, pipeRadius, length, 16, 1, false);
26315
26422
 
26316
26423
  // Determine material (debug red if rectified and in dev mode)
26317
- var materialToUse = pipeMaterial;
26424
+ var materialToUse = perPathMaterial;
26318
26425
 
26319
26426
  // Check for dev mode (strict localhost only)
26320
26427
  var isDev = typeof window !== 'undefined' && (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1');
26321
26428
  if (isDev && pathData.rectifiedSegments && pathData.rectifiedSegments.includes(j)) {
26322
- // Create red debug material by cloning the pipe material to match the look
26323
- materialToUse = pipeMaterial.clone();
26429
+ // Create red debug material by cloning the per-path material to match the look
26430
+ materialToUse = perPathMaterial.clone();
26324
26431
  materialToUse.color.setHex(0xff0000);
26325
26432
  console.log("\uD83C\uDFA8 Coloring rectified segment ".concat(j, " red"));
26326
26433
  }
@@ -26382,7 +26489,7 @@ var PathRenderingManager = /*#__PURE__*/function (_BaseDisposable) {
26382
26489
  sceneViewer.scene.add(cylinder);
26383
26490
 
26384
26491
  // Add smooth elbow joints only at actual direction changes (not at every point)
26385
- _this3.createAndAddElbowIfNeeded(pathPoints, j, pipeRadius, pipeMaterial, pathData, index, cylinder // Pass the segment so elbow can be added as a child
26492
+ _this3.createAndAddElbowIfNeeded(pathPoints, j, pipeRadius, perPathMaterial, pathData, index, cylinder // Pass the segment so elbow can be added as a child
26386
26493
  );
26387
26494
  }
26388
26495
  }
@@ -26487,6 +26594,9 @@ var PathRenderingManager = /*#__PURE__*/function (_BaseDisposable) {
26487
26594
  value: function dispose() {
26488
26595
  console.log('🗑️ Disposing PathRenderingManager...');
26489
26596
 
26597
+ // Dispose per-path materials
26598
+ this._clearPathMaterials();
26599
+
26490
26600
  // Call parent dispose to clean up registered resources (shared gateway geometry/material)
26491
26601
  _superPropGet(PathRenderingManager, "dispose", this, 3)([]);
26492
26602
 
@@ -27923,7 +28033,11 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
27923
28033
  key: "_executePathfinding",
27924
28034
  value: (function () {
27925
28035
  var _executePathfinding2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(sceneData, connections) {
27926
- var _sceneDataCopy$childr, _sceneDataCopy$childr2, _pathfindingResult$pa, _pathfindingResult$pa2;
28036
+ var _this4 = this,
28037
+ _sceneDataCopy$childr,
28038
+ _sceneDataCopy$childr2,
28039
+ _pathfindingResult$pa,
28040
+ _pathfindingResult$pa2;
27927
28041
  var options,
27928
28042
  _options$context,
27929
28043
  context,
@@ -27984,6 +28098,15 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
27984
28098
 
27985
28099
  // Create pipe paths with materials using the paths from pathfinder
27986
28100
  this.renderingManager.createPipePaths(pathfindingResult.paths, this.crosscubeTextureSet);
28101
+
28102
+ // ── Stage 3b: Ensure PathData entries exist for all rendered paths ──────
28103
+ // This preserves any flowAttributes already set on existing entries.
28104
+ if (pathfindingResult.paths) {
28105
+ pathfindingResult.paths.forEach(function (path) {
28106
+ var pathId = "".concat(path.from, "-->").concat(path.to);
28107
+ _this4._getOrCreatePathData(pathId, path.from, path.to);
28108
+ });
28109
+ }
27987
28110
  timers.pathRendering = performance.now() - renderStart;
27988
28111
 
27989
28112
  // ── Performance Summary ────────────────────────────────────────────
@@ -28021,6 +28144,26 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
28021
28144
  return this.sceneDataManager.getSimplifiedSceneData();
28022
28145
  }
28023
28146
 
28147
+ /**
28148
+ * Return an existing PathData for pathId from the store, or create and register a new one.
28149
+ * Preserves existing flowAttributes when the entry already exists.
28150
+ *
28151
+ * @param {string} pathId - Canonical path identifier "${from}-->${to}"
28152
+ * @param {string} from - Source connector ID
28153
+ * @param {string} to - Target connector ID
28154
+ * @returns {PathData}
28155
+ * @private
28156
+ */
28157
+ }, {
28158
+ key: "_getOrCreatePathData",
28159
+ value: function _getOrCreatePathData(pathId, from, to) {
28160
+ if (!this.pathDataStore.has(pathId)) {
28161
+ var pd = new PathData(pathId, from, to);
28162
+ this.pathDataStore.set(pathId, pd);
28163
+ }
28164
+ return this.pathDataStore.get(pathId);
28165
+ }
28166
+
28024
28167
  /**
28025
28168
  * Initialize pathfinder and create paths
28026
28169
  */
@@ -28028,12 +28171,33 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
28028
28171
  key: "initializePathfinder",
28029
28172
  value: (function () {
28030
28173
  var _initializePathfinder = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2(data, crosscubeTextureSet) {
28031
- var pathfindingResult;
28174
+ var _this5 = this,
28175
+ _this$sceneViewer;
28176
+ var pathfindingResult, originalToSubPaths, fromEndpointAttrs, flowMgr;
28032
28177
  return _regenerator().w(function (_context2) {
28033
28178
  while (1) switch (_context2.n) {
28034
28179
  case 0:
28035
28180
  this.crosscubeTextureSet = crosscubeTextureSet;
28036
28181
 
28182
+ // ── Pre-load declared flowAttributes from connections JSON ─────────────
28183
+ // This must happen before pathfinding so that PathData entries carrying
28184
+ // flowAttributes are available for visualization right after rendering.
28185
+ if (Array.isArray(data.connections)) {
28186
+ data.connections.forEach(function (conn) {
28187
+ if (conn.flowAttributes && _typeof(conn.flowAttributes) === 'object') {
28188
+ var pathId = "".concat(conn.from, "-->").concat(conn.to);
28189
+ var pd = _this5._getOrCreatePathData(pathId, conn.from, conn.to);
28190
+ Object.entries(conn.flowAttributes).forEach(function (_ref) {
28191
+ var _ref2 = _slicedToArray(_ref, 2),
28192
+ key = _ref2[0],
28193
+ value = _ref2[1];
28194
+ pd.setFlowAttribute(key, value);
28195
+ });
28196
+ console.log("\uD83C\uDF0A Loaded flowAttributes for path \"".concat(pathId, "\":"), conn.flowAttributes);
28197
+ }
28198
+ });
28199
+ }
28200
+
28037
28201
  // Use shared pathfinding logic with gateway creation enabled
28038
28202
  _context2.n = 1;
28039
28203
  return this._executePathfinding(data.scene, data.connections, {
@@ -28042,6 +28206,83 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
28042
28206
  });
28043
28207
  case 1:
28044
28208
  pathfindingResult = _context2.v;
28209
+ // ── Propagate flowAttributes to gateway-split rendered paths ──────────
28210
+ // The pathfinder may split a connection A→B through gateway waypoints,
28211
+ // producing sub-paths like A→G and G→B with different pathIds.
28212
+ // We need to copy the original connection's flowAttributes onto every
28213
+ // rendered sub-path entry in pathDataStore so the visualisation can
28214
+ // find them by their rendered pathId.
28215
+ if (Array.isArray(data.connections) && pathfindingResult) {
28216
+ // Pass 1: use the explicit gateway connection mappings to get a precise
28217
+ // original→sub-path map.
28218
+ originalToSubPaths = new Map(); // origPathId → Set<renderedPathId>
28219
+ if (pathfindingResult.gateways) {
28220
+ pathfindingResult.gateways.forEach(function (gateway) {
28221
+ var _ref3 = gateway.connections || {},
28222
+ removed = _ref3.removed,
28223
+ added = _ref3.added;
28224
+ if (!(removed !== null && removed !== void 0 && removed.length) || !(added !== null && added !== void 0 && added.length)) return;
28225
+ removed.forEach(function (removedConn) {
28226
+ var origId = "".concat(removedConn.from, "-->").concat(removedConn.to);
28227
+ if (!originalToSubPaths.has(origId)) originalToSubPaths.set(origId, new Set());
28228
+ added.forEach(function (addedConn) {
28229
+ originalToSubPaths.get(origId).add("".concat(addedConn.from, "-->").concat(addedConn.to));
28230
+ });
28231
+ });
28232
+ });
28233
+ }
28234
+ data.connections.forEach(function (conn) {
28235
+ if (!conn.flowAttributes) return;
28236
+ var origId = "".concat(conn.from, "-->").concat(conn.to);
28237
+ var subPathIds = new Set(originalToSubPaths.get(origId) || []);
28238
+ subPathIds.add(origId); // include the direct path if it wasn't rewired
28239
+
28240
+ subPathIds.forEach(function (subPathId) {
28241
+ var pd = _this5.pathDataStore.get(subPathId);
28242
+ if (pd && Object.keys(pd.flowAttributes).length === 0) {
28243
+ Object.entries(conn.flowAttributes).forEach(function (_ref4) {
28244
+ var _ref5 = _slicedToArray(_ref4, 2),
28245
+ key = _ref5[0],
28246
+ value = _ref5[1];
28247
+ pd.setFlowAttribute(key, value);
28248
+ });
28249
+ console.log("\uD83C\uDF0A Propagated flowAttributes to sub-path \"".concat(subPathId, "\" from \"").concat(origId, "\""));
28250
+ }
28251
+ });
28252
+ });
28253
+
28254
+ // Pass 2: endpoint fallback for any rendered path still missing attributes
28255
+ // (covers Gateway→Gateway intermediate segments not captured by gateway.connections.added)
28256
+ fromEndpointAttrs = new Map(); // connectorId → flowAttributes
28257
+ data.connections.forEach(function (conn) {
28258
+ if (conn.flowAttributes && !fromEndpointAttrs.has(conn.from)) {
28259
+ fromEndpointAttrs.set(conn.from, conn.flowAttributes);
28260
+ }
28261
+ });
28262
+ this.pathDataStore.forEach(function (pd, pathId) {
28263
+ if (Object.keys(pd.flowAttributes).length > 0) return;
28264
+ var sepIdx = pathId.indexOf('-->');
28265
+ if (sepIdx === -1) return;
28266
+ var from = pathId.slice(0, sepIdx);
28267
+ var attrs = fromEndpointAttrs.get(from);
28268
+ if (attrs) {
28269
+ Object.entries(attrs).forEach(function (_ref6) {
28270
+ var _ref7 = _slicedToArray(_ref6, 2),
28271
+ key = _ref7[0],
28272
+ value = _ref7[1];
28273
+ return pd.setFlowAttribute(key, value);
28274
+ });
28275
+ console.log("\uD83C\uDF0A Endpoint-matched flowAttributes for path \"".concat(pathId, "\""));
28276
+ }
28277
+ });
28278
+ }
28279
+
28280
+ // ── Apply flow visualizations now that paths are rendered ──────────────
28281
+ flowMgr = (_this$sceneViewer = this.sceneViewer) === null || _this$sceneViewer === void 0 || (_this$sceneViewer = _this$sceneViewer.managers) === null || _this$sceneViewer === void 0 ? void 0 : _this$sceneViewer.pathFlowManager;
28282
+ if (flowMgr) {
28283
+ flowMgr.applyAllVisualizations();
28284
+ }
28285
+
28045
28286
  // Update connections with rewired connections
28046
28287
  if (pathfindingResult.rewiredConnections) {
28047
28288
  // data.connections = pathfindingResult.rewiredConnections;
@@ -28221,6 +28462,262 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
28221
28462
  }]);
28222
28463
  }(BaseDisposable);
28223
28464
 
28465
+ // ── Temperature visualisation constants ────────────────────────────────────
28466
+
28467
+ /** Minimum temperature in the colour ramp (maps to pure blue). */
28468
+ var TEMP_MIN = 0;
28469
+
28470
+ /** Maximum temperature in the colour ramp (maps to pure red). */
28471
+ var TEMP_MAX = 100;
28472
+
28473
+ /**
28474
+ * Convert a temperature value to a CSS hex colour string using a blue→red HSL
28475
+ * ramp. Values are clamped to [TEMP_MIN, TEMP_MAX].
28476
+ *
28477
+ * • TEMP_MIN → hsl(240, 80%, 50%) — blue
28478
+ * • TEMP_MAX → hsl(0, 80%, 50%) — red
28479
+ *
28480
+ * @param {number} temp
28481
+ * @returns {string} CSS hex colour string e.g. '#2255cc'
28482
+ */
28483
+ function temperatureToColor(temp) {
28484
+ var clamped = Math.max(TEMP_MIN, Math.min(TEMP_MAX, temp));
28485
+ var t = (clamped - TEMP_MIN) / (TEMP_MAX - TEMP_MIN); // 0 (cold) → 1 (hot)
28486
+ // Map t=0 → hue 240 (blue), t=1 → hue 0 (red)
28487
+ var hue = Math.round(240 - t * 240);
28488
+ return "hsl(".concat(hue, ", 80%, 50%)");
28489
+ }
28490
+
28491
+ // ── PathFlowManager ─────────────────────────────────────────────────────────
28492
+
28493
+ var PathFlowManager = /*#__PURE__*/function (_BaseDisposable) {
28494
+ /**
28495
+ * @param {Object} sceneViewer - The central sceneViewer hub
28496
+ */
28497
+ function PathFlowManager(sceneViewer) {
28498
+ var _this;
28499
+ _classCallCheck(this, PathFlowManager);
28500
+ _this = _callSuper(this, PathFlowManager);
28501
+ _this.sceneViewer = sceneViewer;
28502
+
28503
+ /**
28504
+ * Runtime-only attribute overrides.
28505
+ * Map<pathId, Record<attributeKey, value>>
28506
+ * Cleared when the session ends or resetDerived() is called.
28507
+ * @type {Map<string, Record<string, any>>}
28508
+ */
28509
+ _this._derivedStore = new Map();
28510
+ return _this;
28511
+ }
28512
+
28513
+ // ── Public API — attribute write ──────────────────────────────────────────
28514
+
28515
+ /**
28516
+ * Set a declared (persistent) flow attribute on a path.
28517
+ * The value is stored in PathData and will be serialised with the scene.
28518
+ * Triggers a visual update.
28519
+ *
28520
+ * @param {string} pathId - e.g. "PUMP-1-CONN-1-->CHILLER-1-CONN-2"
28521
+ * @param {string} key - One of FLOW_ATTRIBUTE_KEYS
28522
+ * @param {any} value
28523
+ */
28524
+ _inherits(PathFlowManager, _BaseDisposable);
28525
+ return _createClass(PathFlowManager, [{
28526
+ key: "setDeclared",
28527
+ value: function setDeclared(pathId, key, value) {
28528
+ var pd = this._getPathData(pathId);
28529
+ if (!pd) {
28530
+ console.warn("PathFlowManager.setDeclared: no PathData found for \"".concat(pathId, "\""));
28531
+ return;
28532
+ }
28533
+ pd.setFlowAttribute(key, value);
28534
+ this.applyVisualizationForPath(pathId);
28535
+ }
28536
+
28537
+ /**
28538
+ * Set a derived (runtime) flow attribute on a path.
28539
+ * Overrides the declared value during this session but is not persisted.
28540
+ * Triggers a visual update.
28541
+ *
28542
+ * @param {string} pathId
28543
+ * @param {string} key
28544
+ * @param {any} value
28545
+ */
28546
+ }, {
28547
+ key: "setDerived",
28548
+ value: function setDerived(pathId, key, value) {
28549
+ if (!this._derivedStore.has(pathId)) {
28550
+ this._derivedStore.set(pathId, {});
28551
+ }
28552
+ this._derivedStore.get(pathId)[key] = value;
28553
+ this.applyVisualizationForPath(pathId);
28554
+ }
28555
+
28556
+ /**
28557
+ * Clear derived overrides for one path or all paths, then re-apply
28558
+ * visualizations so that declared values take effect again.
28559
+ *
28560
+ * @param {string} [pathId] - Omit to reset all paths.
28561
+ */
28562
+ }, {
28563
+ key: "resetDerived",
28564
+ value: function resetDerived(pathId) {
28565
+ var _this2 = this;
28566
+ if (pathId !== undefined) {
28567
+ this._derivedStore.delete(pathId);
28568
+ this.applyVisualizationForPath(pathId);
28569
+ } else {
28570
+ var affected = _toConsumableArray(this._derivedStore.keys());
28571
+ this._derivedStore.clear();
28572
+ affected.forEach(function (id) {
28573
+ return _this2.applyVisualizationForPath(id);
28574
+ });
28575
+ }
28576
+ }
28577
+
28578
+ // ── Public API — attribute read ───────────────────────────────────────────
28579
+
28580
+ /**
28581
+ * Resolve the effective value of a single attribute for a path.
28582
+ * Resolution order: derived > declared > null.
28583
+ *
28584
+ * @param {string} pathId
28585
+ * @param {string} key
28586
+ * @returns {any|null}
28587
+ */
28588
+ }, {
28589
+ key: "resolve",
28590
+ value: function resolve(pathId, key) {
28591
+ var derived = this._derivedStore.get(pathId);
28592
+ if (derived && Object.prototype.hasOwnProperty.call(derived, key)) {
28593
+ return derived[key];
28594
+ }
28595
+ var pd = this._getPathData(pathId);
28596
+ if (pd) {
28597
+ return pd.getFlowAttribute(key);
28598
+ }
28599
+ return null;
28600
+ }
28601
+
28602
+ /**
28603
+ * Resolve all four flow attributes for a path as a plain object.
28604
+ * Each key is either the effective value or null if unset.
28605
+ *
28606
+ * @param {string} pathId
28607
+ * @returns {{ flowDirection: any, flowSpeed: any, flowTemperature: any, flowMaterial: any }}
28608
+ */
28609
+ }, {
28610
+ key: "resolveAll",
28611
+ value: function resolveAll(pathId) {
28612
+ var _this3 = this;
28613
+ return Object.fromEntries(FLOW_ATTRIBUTE_KEYS.map(function (key) {
28614
+ return [key, _this3.resolve(pathId, key)];
28615
+ }));
28616
+ }
28617
+
28618
+ // ── Visualization ─────────────────────────────────────────────────────────
28619
+
28620
+ /**
28621
+ * Compute and apply the visual colour for a single path based on its resolved
28622
+ * flow attributes. Currently maps flowTemperature to a blue→red colour ramp.
28623
+ * No-ops gracefully if the path has no renderable attributes or no pipe
28624
+ * material exists yet.
28625
+ *
28626
+ * @param {string} pathId
28627
+ */
28628
+ }, {
28629
+ key: "applyVisualizationForPath",
28630
+ value: function applyVisualizationForPath(pathId) {
28631
+ var temp = this.resolve(pathId, 'flowTemperature');
28632
+ if (temp === null || temp === undefined) {
28633
+ return; // No temperature declared — leave default pipe color
28634
+ }
28635
+ var color = temperatureToColor(temp);
28636
+ var renderingManager = this._getRenderingManager();
28637
+ if (!renderingManager) return;
28638
+
28639
+ // pathId = "from-->to"; extract the two halves
28640
+ var sepIdx = pathId.indexOf('-->');
28641
+ if (sepIdx === -1) {
28642
+ console.warn("PathFlowManager: malformed pathId \"".concat(pathId, "\""));
28643
+ return;
28644
+ }
28645
+ var from = pathId.slice(0, sepIdx);
28646
+ var to = pathId.slice(sepIdx + 3);
28647
+ renderingManager.updatePathColor(from, to, color);
28648
+ console.log("\uD83C\uDF21\uFE0F PathFlowManager: \"".concat(pathId, "\" \u2192 flowTemperature ").concat(temp, " \u2192 ").concat(color));
28649
+ }
28650
+
28651
+ /**
28652
+ * Apply visualizations for every known path (union of pathDataStore and derived store).
28653
+ * Call this after scene load or after bulk attribute changes.
28654
+ */
28655
+ }, {
28656
+ key: "applyAllVisualizations",
28657
+ value: function applyAllVisualizations() {
28658
+ var _this4 = this;
28659
+ var pathIds = new Set();
28660
+ var pfMgr = this._getPathfindingManager();
28661
+ if (pfMgr !== null && pfMgr !== void 0 && pfMgr.pathDataStore) {
28662
+ pfMgr.pathDataStore.forEach(function (_, id) {
28663
+ return pathIds.add(id);
28664
+ });
28665
+ }
28666
+ this._derivedStore.forEach(function (_, id) {
28667
+ return pathIds.add(id);
28668
+ });
28669
+ pathIds.forEach(function (id) {
28670
+ return _this4.applyVisualizationForPath(id);
28671
+ });
28672
+ console.log("\uD83C\uDF0A PathFlowManager.applyAllVisualizations: processed ".concat(pathIds.size, " path(s)"));
28673
+ }
28674
+
28675
+ // ── Lifecycle ─────────────────────────────────────────────────────────────
28676
+ }, {
28677
+ key: "dispose",
28678
+ value: function dispose() {
28679
+ this._derivedStore.clear();
28680
+ this.sceneViewer = null;
28681
+ _superPropGet(PathFlowManager, "dispose", this, 3)([]);
28682
+ }
28683
+
28684
+ // ── Private helpers ───────────────────────────────────────────────────────
28685
+
28686
+ /**
28687
+ * @returns {import('../../core/pathfindingData.js').PathData|null}
28688
+ * @private
28689
+ */
28690
+ }, {
28691
+ key: "_getPathData",
28692
+ value: function _getPathData(pathId) {
28693
+ var _this$_getPathfinding, _this$_getPathfinding2;
28694
+ return (_this$_getPathfinding = (_this$_getPathfinding2 = this._getPathfindingManager()) === null || _this$_getPathfinding2 === void 0 || (_this$_getPathfinding2 = _this$_getPathfinding2.pathDataStore) === null || _this$_getPathfinding2 === void 0 ? void 0 : _this$_getPathfinding2.get(pathId)) !== null && _this$_getPathfinding !== void 0 ? _this$_getPathfinding : null;
28695
+ }
28696
+
28697
+ /**
28698
+ * @returns {import('../pathfinding/pathfindingManager.js').PathfindingManager|null}
28699
+ * @private
28700
+ */
28701
+ }, {
28702
+ key: "_getPathfindingManager",
28703
+ value: function _getPathfindingManager() {
28704
+ var _this$sceneViewer$man, _this$sceneViewer;
28705
+ return (_this$sceneViewer$man = (_this$sceneViewer = this.sceneViewer) === null || _this$sceneViewer === void 0 || (_this$sceneViewer = _this$sceneViewer.managers) === null || _this$sceneViewer === void 0 ? void 0 : _this$sceneViewer.pathfindingManager) !== null && _this$sceneViewer$man !== void 0 ? _this$sceneViewer$man : null;
28706
+ }
28707
+
28708
+ /**
28709
+ * @returns {import('../pathfinding/PathRenderingManager.js').PathRenderingManager|null}
28710
+ * @private
28711
+ */
28712
+ }, {
28713
+ key: "_getRenderingManager",
28714
+ value: function _getRenderingManager() {
28715
+ var _this$_getPathfinding3, _this$_getPathfinding4;
28716
+ return (_this$_getPathfinding3 = (_this$_getPathfinding4 = this._getPathfindingManager()) === null || _this$_getPathfinding4 === void 0 ? void 0 : _this$_getPathfinding4.renderingManager) !== null && _this$_getPathfinding3 !== void 0 ? _this$_getPathfinding3 : null;
28717
+ }
28718
+ }]);
28719
+ }(BaseDisposable);
28720
+
28224
28721
  var BehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
28225
28722
  function BehaviorManager(sceneViewer) {
28226
28723
  var _this;
@@ -36156,6 +36653,7 @@ var CentralPlantInternals = /*#__PURE__*/function () {
36156
36653
  this.centralPlant.managers.environmentManager = new EnvironmentManager(this.centralPlant.sceneViewer);
36157
36654
  this.centralPlant.managers.keyboardControlsManager = new KeyboardControlsManager(this.centralPlant.sceneViewer);
36158
36655
  this.centralPlant.managers.pathfindingManager = new PathfindingManager(this.centralPlant.sceneViewer);
36656
+ this.centralPlant.managers.pathFlowManager = new PathFlowManager(this.centralPlant.sceneViewer);
36159
36657
  this.centralPlant.managers.behaviorManager = new BehaviorManager(this.centralPlant.sceneViewer);
36160
36658
  this.centralPlant.managers.sceneOperationsManager = new SceneOperationsManager(this.centralPlant.sceneViewer);
36161
36659
  this.centralPlant.managers.animationManager = new AnimationManager(this.centralPlant.sceneViewer);
@@ -37318,7 +37816,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
37318
37816
  * Initialize the CentralPlant manager
37319
37817
  *
37320
37818
  * @constructor
37321
- * @version 0.2.8
37819
+ * @version 0.2.10
37322
37820
  * @updated 2025-10-22
37323
37821
  *
37324
37822
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -40335,7 +40833,7 @@ var sceneViewer = /*#__PURE__*/function (_BaseDisposable) {
40335
40833
  this.centralPlant.attachToComponent();
40336
40834
 
40337
40835
  // Sync our managers tracking object after attachment
40338
- managerKeys = ['threeJSResourceManager', 'performanceMonitorManager', 'settingsManager', 'sceneExportManager', 'componentManager', 'sceneInitializationManager', 'environmentManager', 'keyboardControlsManager', 'pathfindingManager', 'behaviorManager', 'sceneOperationsManager', 'animationManager', 'cameraControlsManager', 'componentDragManager', 'tooltipsManager', 'componentTooltipManager']; // Populate our managers tracking object
40836
+ managerKeys = ['threeJSResourceManager', 'performanceMonitorManager', 'settingsManager', 'sceneExportManager', 'componentManager', 'sceneInitializationManager', 'environmentManager', 'keyboardControlsManager', 'pathfindingManager', 'pathFlowManager', 'behaviorManager', 'sceneOperationsManager', 'animationManager', 'cameraControlsManager', 'componentDragManager', 'tooltipsManager', 'componentTooltipManager']; // Populate our managers tracking object
40339
40837
  managerKeys.forEach(function (key) {
40340
40838
  if (_this2[key]) {
40341
40839
  _this2.managers[key] = _this2[key];
@@ -43951,10 +44449,12 @@ exports.ComponentDragManager = ComponentDragManager;
43951
44449
  exports.ComponentManager = ComponentManager;
43952
44450
  exports.ComponentTooltipManager = ComponentTooltipManager;
43953
44451
  exports.EnvironmentManager = EnvironmentManager;
44452
+ exports.FLOW_ATTRIBUTE_KEYS = FLOW_ATTRIBUTE_KEYS;
43954
44453
  exports.KeyboardControlsManager = KeyboardControlsManager;
43955
44454
  exports.ModelManager = ModelManager;
43956
44455
  exports.OperationHistoryManager = OperationHistoryManager;
43957
44456
  exports.PathData = PathData;
44457
+ exports.PathFlowManager = PathFlowManager;
43958
44458
  exports.PathfindingManager = PathfindingManager;
43959
44459
  exports.PerformanceMonitorManager = PerformanceMonitorManager;
43960
44460
  exports.Rendering2D = rendering2D;