@2112-lab/central-plant 0.2.8 → 0.2.12

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,123 @@ 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
+
28167
+ /**
28168
+ * Propagate flowAttributes from the original connection list to any gateway-split
28169
+ * rendered sub-paths in pathDataStore, then apply all flow visualizations.
28170
+ * Called after every pathfinding execution (initial load and transform updates).
28171
+ *
28172
+ * @param {Array} connections - Original connections array (may include flowAttributes)
28173
+ * @param {object} pathfindingResult - Result returned by _executePathfinding
28174
+ * @private
28175
+ */
28176
+ }, {
28177
+ key: "_propagateFlowAttributesAndApplyVisualizations",
28178
+ value: function _propagateFlowAttributesAndApplyVisualizations(connections, pathfindingResult) {
28179
+ var _this5 = this,
28180
+ _this$sceneViewer;
28181
+ if (!Array.isArray(connections) || !pathfindingResult) return;
28182
+
28183
+ // Pass 1: use the explicit gateway connection mappings to build a precise
28184
+ // original-pathId → Set<renderedPathId> map.
28185
+ var originalToSubPaths = new Map();
28186
+ if (pathfindingResult.gateways) {
28187
+ pathfindingResult.gateways.forEach(function (gateway) {
28188
+ var _ref = gateway.connections || {},
28189
+ removed = _ref.removed,
28190
+ added = _ref.added;
28191
+ if (!(removed !== null && removed !== void 0 && removed.length) || !(added !== null && added !== void 0 && added.length)) return;
28192
+ removed.forEach(function (removedConn) {
28193
+ var origId = "".concat(removedConn.from, "-->").concat(removedConn.to);
28194
+ if (!originalToSubPaths.has(origId)) originalToSubPaths.set(origId, new Set());
28195
+ added.forEach(function (addedConn) {
28196
+ originalToSubPaths.get(origId).add("".concat(addedConn.from, "-->").concat(addedConn.to));
28197
+ });
28198
+ });
28199
+ });
28200
+ }
28201
+ connections.forEach(function (conn) {
28202
+ if (!conn.flowAttributes) return;
28203
+ var origId = "".concat(conn.from, "-->").concat(conn.to);
28204
+ var subPathIds = new Set(originalToSubPaths.get(origId) || []);
28205
+ subPathIds.add(origId);
28206
+ subPathIds.forEach(function (subPathId) {
28207
+ var pd = _this5.pathDataStore.get(subPathId);
28208
+ if (pd && Object.keys(pd.flowAttributes).length === 0) {
28209
+ Object.entries(conn.flowAttributes).forEach(function (_ref2) {
28210
+ var _ref3 = _slicedToArray(_ref2, 2),
28211
+ key = _ref3[0],
28212
+ value = _ref3[1];
28213
+ return pd.setFlowAttribute(key, value);
28214
+ });
28215
+ }
28216
+ });
28217
+ });
28218
+
28219
+ // Pass 2: endpoint fallback for Gateway→Gateway intermediate segments and
28220
+ // for paths whose connections were restructured by manualizeSegment/manualizeGateway.
28221
+ // After manualization, currentSceneData.connections no longer contains the original
28222
+ // A→B entry with flowAttributes (it's been split into new sub-connections), so we
28223
+ // seed the endpoint maps from both the connections array AND from existing pathDataStore
28224
+ // entries that already carry flowAttributes — those persist across re-executions.
28225
+ var fromEndpointAttrs = new Map();
28226
+ var toEndpointAttrs = new Map();
28227
+
28228
+ // Seed from persisted pathDataStore entries first (covers post-manualization re-runs)
28229
+ this.pathDataStore.forEach(function (pd, pathId) {
28230
+ if (Object.keys(pd.flowAttributes).length === 0) return;
28231
+ var sepIdx = pathId.indexOf('-->');
28232
+ if (sepIdx === -1) return;
28233
+ var from = pathId.slice(0, sepIdx);
28234
+ var to = pathId.slice(sepIdx + 3);
28235
+ if (!fromEndpointAttrs.has(from)) fromEndpointAttrs.set(from, pd.flowAttributes);
28236
+ if (!toEndpointAttrs.has(to)) toEndpointAttrs.set(to, pd.flowAttributes);
28237
+ });
28238
+
28239
+ // Also seed from connections array (covers initial load before pathDataStore has any attrs)
28240
+ connections.forEach(function (conn) {
28241
+ if (!conn.flowAttributes) return;
28242
+ if (!fromEndpointAttrs.has(conn.from)) fromEndpointAttrs.set(conn.from, conn.flowAttributes);
28243
+ if (!toEndpointAttrs.has(conn.to)) toEndpointAttrs.set(conn.to, conn.flowAttributes);
28244
+ });
28245
+ this.pathDataStore.forEach(function (pd, pathId) {
28246
+ if (Object.keys(pd.flowAttributes).length > 0) return;
28247
+ var sepIdx = pathId.indexOf('-->');
28248
+ if (sepIdx === -1) return;
28249
+ var from = pathId.slice(0, sepIdx);
28250
+ var to = pathId.slice(sepIdx + 3);
28251
+ var attrs = fromEndpointAttrs.get(from) || toEndpointAttrs.get(to);
28252
+ if (attrs) Object.entries(attrs).forEach(function (_ref4) {
28253
+ var _ref5 = _slicedToArray(_ref4, 2),
28254
+ key = _ref5[0],
28255
+ value = _ref5[1];
28256
+ return pd.setFlowAttribute(key, value);
28257
+ });
28258
+ });
28259
+
28260
+ // Apply visualizations
28261
+ (_this$sceneViewer = this.sceneViewer) === null || _this$sceneViewer === void 0 || (_this$sceneViewer = _this$sceneViewer.managers) === null || _this$sceneViewer === void 0 || (_this$sceneViewer = _this$sceneViewer.pathFlowManager) === null || _this$sceneViewer === void 0 || _this$sceneViewer.applyAllVisualizations();
28262
+ }
28263
+
28024
28264
  /**
28025
28265
  * Initialize pathfinder and create paths
28026
28266
  */
@@ -28028,12 +28268,32 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
28028
28268
  key: "initializePathfinder",
28029
28269
  value: (function () {
28030
28270
  var _initializePathfinder = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2(data, crosscubeTextureSet) {
28271
+ var _this6 = this;
28031
28272
  var pathfindingResult;
28032
28273
  return _regenerator().w(function (_context2) {
28033
28274
  while (1) switch (_context2.n) {
28034
28275
  case 0:
28035
28276
  this.crosscubeTextureSet = crosscubeTextureSet;
28036
28277
 
28278
+ // ── Pre-load declared flowAttributes from connections JSON ─────────────
28279
+ // This must happen before pathfinding so that PathData entries carrying
28280
+ // flowAttributes are available for visualization right after rendering.
28281
+ if (Array.isArray(data.connections)) {
28282
+ data.connections.forEach(function (conn) {
28283
+ if (conn.flowAttributes && _typeof(conn.flowAttributes) === 'object') {
28284
+ var pathId = "".concat(conn.from, "-->").concat(conn.to);
28285
+ var pd = _this6._getOrCreatePathData(pathId, conn.from, conn.to);
28286
+ Object.entries(conn.flowAttributes).forEach(function (_ref6) {
28287
+ var _ref7 = _slicedToArray(_ref6, 2),
28288
+ key = _ref7[0],
28289
+ value = _ref7[1];
28290
+ pd.setFlowAttribute(key, value);
28291
+ });
28292
+ console.log("\uD83C\uDF0A Loaded flowAttributes for path \"".concat(pathId, "\":"), conn.flowAttributes);
28293
+ }
28294
+ });
28295
+ }
28296
+
28037
28297
  // Use shared pathfinding logic with gateway creation enabled
28038
28298
  _context2.n = 1;
28039
28299
  return this._executePathfinding(data.scene, data.connections, {
@@ -28042,6 +28302,8 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
28042
28302
  });
28043
28303
  case 1:
28044
28304
  pathfindingResult = _context2.v;
28305
+ this._propagateFlowAttributesAndApplyVisualizations(data.connections, pathfindingResult);
28306
+
28045
28307
  // Update connections with rewired connections
28046
28308
  if (pathfindingResult.rewiredConnections) {
28047
28309
  // data.connections = pathfindingResult.rewiredConnections;
@@ -28166,6 +28428,9 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
28166
28428
  });
28167
28429
  case 2:
28168
28430
  result = _context3.v;
28431
+ // Re-apply flow attribute colors (new materials are created on each execution)
28432
+ this._propagateFlowAttributesAndApplyVisualizations(currentSceneData.connections, result);
28433
+
28169
28434
  // Cache fingerprint + result for next comparison
28170
28435
  this._lastPathfindingFingerprint = fingerprint;
28171
28436
  this._lastPathfindingResult = result;
@@ -28221,6 +28486,262 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
28221
28486
  }]);
28222
28487
  }(BaseDisposable);
28223
28488
 
28489
+ // ── Temperature visualisation constants ────────────────────────────────────
28490
+
28491
+ /** Minimum temperature in the colour ramp (maps to pure blue). */
28492
+ var TEMP_MIN = 0;
28493
+
28494
+ /** Maximum temperature in the colour ramp (maps to pure red). */
28495
+ var TEMP_MAX = 100;
28496
+
28497
+ /**
28498
+ * Convert a temperature value to a CSS hex colour string using a blue→red HSL
28499
+ * ramp. Values are clamped to [TEMP_MIN, TEMP_MAX].
28500
+ *
28501
+ * • TEMP_MIN → hsl(240, 80%, 50%) — blue
28502
+ * • TEMP_MAX → hsl(0, 80%, 50%) — red
28503
+ *
28504
+ * @param {number} temp
28505
+ * @returns {string} CSS hex colour string e.g. '#2255cc'
28506
+ */
28507
+ function temperatureToColor(temp) {
28508
+ var clamped = Math.max(TEMP_MIN, Math.min(TEMP_MAX, temp));
28509
+ var t = (clamped - TEMP_MIN) / (TEMP_MAX - TEMP_MIN); // 0 (cold) → 1 (hot)
28510
+ // Map t=0 → hue 240 (blue), t=1 → hue 0 (red)
28511
+ var hue = Math.round(240 - t * 240);
28512
+ return "hsl(".concat(hue, ", 80%, 50%)");
28513
+ }
28514
+
28515
+ // ── PathFlowManager ─────────────────────────────────────────────────────────
28516
+
28517
+ var PathFlowManager = /*#__PURE__*/function (_BaseDisposable) {
28518
+ /**
28519
+ * @param {Object} sceneViewer - The central sceneViewer hub
28520
+ */
28521
+ function PathFlowManager(sceneViewer) {
28522
+ var _this;
28523
+ _classCallCheck(this, PathFlowManager);
28524
+ _this = _callSuper(this, PathFlowManager);
28525
+ _this.sceneViewer = sceneViewer;
28526
+
28527
+ /**
28528
+ * Runtime-only attribute overrides.
28529
+ * Map<pathId, Record<attributeKey, value>>
28530
+ * Cleared when the session ends or resetDerived() is called.
28531
+ * @type {Map<string, Record<string, any>>}
28532
+ */
28533
+ _this._derivedStore = new Map();
28534
+ return _this;
28535
+ }
28536
+
28537
+ // ── Public API — attribute write ──────────────────────────────────────────
28538
+
28539
+ /**
28540
+ * Set a declared (persistent) flow attribute on a path.
28541
+ * The value is stored in PathData and will be serialised with the scene.
28542
+ * Triggers a visual update.
28543
+ *
28544
+ * @param {string} pathId - e.g. "PUMP-1-CONN-1-->CHILLER-1-CONN-2"
28545
+ * @param {string} key - One of FLOW_ATTRIBUTE_KEYS
28546
+ * @param {any} value
28547
+ */
28548
+ _inherits(PathFlowManager, _BaseDisposable);
28549
+ return _createClass(PathFlowManager, [{
28550
+ key: "setDeclared",
28551
+ value: function setDeclared(pathId, key, value) {
28552
+ var pd = this._getPathData(pathId);
28553
+ if (!pd) {
28554
+ console.warn("PathFlowManager.setDeclared: no PathData found for \"".concat(pathId, "\""));
28555
+ return;
28556
+ }
28557
+ pd.setFlowAttribute(key, value);
28558
+ this.applyVisualizationForPath(pathId);
28559
+ }
28560
+
28561
+ /**
28562
+ * Set a derived (runtime) flow attribute on a path.
28563
+ * Overrides the declared value during this session but is not persisted.
28564
+ * Triggers a visual update.
28565
+ *
28566
+ * @param {string} pathId
28567
+ * @param {string} key
28568
+ * @param {any} value
28569
+ */
28570
+ }, {
28571
+ key: "setDerived",
28572
+ value: function setDerived(pathId, key, value) {
28573
+ if (!this._derivedStore.has(pathId)) {
28574
+ this._derivedStore.set(pathId, {});
28575
+ }
28576
+ this._derivedStore.get(pathId)[key] = value;
28577
+ this.applyVisualizationForPath(pathId);
28578
+ }
28579
+
28580
+ /**
28581
+ * Clear derived overrides for one path or all paths, then re-apply
28582
+ * visualizations so that declared values take effect again.
28583
+ *
28584
+ * @param {string} [pathId] - Omit to reset all paths.
28585
+ */
28586
+ }, {
28587
+ key: "resetDerived",
28588
+ value: function resetDerived(pathId) {
28589
+ var _this2 = this;
28590
+ if (pathId !== undefined) {
28591
+ this._derivedStore.delete(pathId);
28592
+ this.applyVisualizationForPath(pathId);
28593
+ } else {
28594
+ var affected = _toConsumableArray(this._derivedStore.keys());
28595
+ this._derivedStore.clear();
28596
+ affected.forEach(function (id) {
28597
+ return _this2.applyVisualizationForPath(id);
28598
+ });
28599
+ }
28600
+ }
28601
+
28602
+ // ── Public API — attribute read ───────────────────────────────────────────
28603
+
28604
+ /**
28605
+ * Resolve the effective value of a single attribute for a path.
28606
+ * Resolution order: derived > declared > null.
28607
+ *
28608
+ * @param {string} pathId
28609
+ * @param {string} key
28610
+ * @returns {any|null}
28611
+ */
28612
+ }, {
28613
+ key: "resolve",
28614
+ value: function resolve(pathId, key) {
28615
+ var derived = this._derivedStore.get(pathId);
28616
+ if (derived && Object.prototype.hasOwnProperty.call(derived, key)) {
28617
+ return derived[key];
28618
+ }
28619
+ var pd = this._getPathData(pathId);
28620
+ if (pd) {
28621
+ return pd.getFlowAttribute(key);
28622
+ }
28623
+ return null;
28624
+ }
28625
+
28626
+ /**
28627
+ * Resolve all four flow attributes for a path as a plain object.
28628
+ * Each key is either the effective value or null if unset.
28629
+ *
28630
+ * @param {string} pathId
28631
+ * @returns {{ flowDirection: any, flowSpeed: any, flowTemperature: any, flowMaterial: any }}
28632
+ */
28633
+ }, {
28634
+ key: "resolveAll",
28635
+ value: function resolveAll(pathId) {
28636
+ var _this3 = this;
28637
+ return Object.fromEntries(FLOW_ATTRIBUTE_KEYS.map(function (key) {
28638
+ return [key, _this3.resolve(pathId, key)];
28639
+ }));
28640
+ }
28641
+
28642
+ // ── Visualization ─────────────────────────────────────────────────────────
28643
+
28644
+ /**
28645
+ * Compute and apply the visual colour for a single path based on its resolved
28646
+ * flow attributes. Currently maps flowTemperature to a blue→red colour ramp.
28647
+ * No-ops gracefully if the path has no renderable attributes or no pipe
28648
+ * material exists yet.
28649
+ *
28650
+ * @param {string} pathId
28651
+ */
28652
+ }, {
28653
+ key: "applyVisualizationForPath",
28654
+ value: function applyVisualizationForPath(pathId) {
28655
+ var temp = this.resolve(pathId, 'flowTemperature');
28656
+ if (temp === null || temp === undefined) {
28657
+ return; // No temperature declared — leave default pipe color
28658
+ }
28659
+ var color = temperatureToColor(temp);
28660
+ var renderingManager = this._getRenderingManager();
28661
+ if (!renderingManager) return;
28662
+
28663
+ // pathId = "from-->to"; extract the two halves
28664
+ var sepIdx = pathId.indexOf('-->');
28665
+ if (sepIdx === -1) {
28666
+ console.warn("PathFlowManager: malformed pathId \"".concat(pathId, "\""));
28667
+ return;
28668
+ }
28669
+ var from = pathId.slice(0, sepIdx);
28670
+ var to = pathId.slice(sepIdx + 3);
28671
+ renderingManager.updatePathColor(from, to, color);
28672
+ console.log("\uD83C\uDF21\uFE0F PathFlowManager: \"".concat(pathId, "\" \u2192 flowTemperature ").concat(temp, " \u2192 ").concat(color));
28673
+ }
28674
+
28675
+ /**
28676
+ * Apply visualizations for every known path (union of pathDataStore and derived store).
28677
+ * Call this after scene load or after bulk attribute changes.
28678
+ */
28679
+ }, {
28680
+ key: "applyAllVisualizations",
28681
+ value: function applyAllVisualizations() {
28682
+ var _this4 = this;
28683
+ var pathIds = new Set();
28684
+ var pfMgr = this._getPathfindingManager();
28685
+ if (pfMgr !== null && pfMgr !== void 0 && pfMgr.pathDataStore) {
28686
+ pfMgr.pathDataStore.forEach(function (_, id) {
28687
+ return pathIds.add(id);
28688
+ });
28689
+ }
28690
+ this._derivedStore.forEach(function (_, id) {
28691
+ return pathIds.add(id);
28692
+ });
28693
+ pathIds.forEach(function (id) {
28694
+ return _this4.applyVisualizationForPath(id);
28695
+ });
28696
+ console.log("\uD83C\uDF0A PathFlowManager.applyAllVisualizations: processed ".concat(pathIds.size, " path(s)"));
28697
+ }
28698
+
28699
+ // ── Lifecycle ─────────────────────────────────────────────────────────────
28700
+ }, {
28701
+ key: "dispose",
28702
+ value: function dispose() {
28703
+ this._derivedStore.clear();
28704
+ this.sceneViewer = null;
28705
+ _superPropGet(PathFlowManager, "dispose", this, 3)([]);
28706
+ }
28707
+
28708
+ // ── Private helpers ───────────────────────────────────────────────────────
28709
+
28710
+ /**
28711
+ * @returns {import('../../core/pathfindingData.js').PathData|null}
28712
+ * @private
28713
+ */
28714
+ }, {
28715
+ key: "_getPathData",
28716
+ value: function _getPathData(pathId) {
28717
+ var _this$_getPathfinding, _this$_getPathfinding2;
28718
+ 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;
28719
+ }
28720
+
28721
+ /**
28722
+ * @returns {import('../pathfinding/pathfindingManager.js').PathfindingManager|null}
28723
+ * @private
28724
+ */
28725
+ }, {
28726
+ key: "_getPathfindingManager",
28727
+ value: function _getPathfindingManager() {
28728
+ var _this$sceneViewer$man, _this$sceneViewer;
28729
+ 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;
28730
+ }
28731
+
28732
+ /**
28733
+ * @returns {import('../pathfinding/PathRenderingManager.js').PathRenderingManager|null}
28734
+ * @private
28735
+ */
28736
+ }, {
28737
+ key: "_getRenderingManager",
28738
+ value: function _getRenderingManager() {
28739
+ var _this$_getPathfinding3, _this$_getPathfinding4;
28740
+ 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;
28741
+ }
28742
+ }]);
28743
+ }(BaseDisposable);
28744
+
28224
28745
  var BehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
28225
28746
  function BehaviorManager(sceneViewer) {
28226
28747
  var _this;
@@ -31758,7 +32279,7 @@ var SceneOperationsManager = /*#__PURE__*/function () {
31758
32279
  if (typeof window !== 'undefined') {
31759
32280
  isDev = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';
31760
32281
  }
31761
- if (isDev && segment.material) {
32282
+ if (isDev && process.env.DISABLE_MANUALIZED_SEGMENT_COLORING !== 'true' && segment.material) {
31762
32283
  segment.material.color.setHex(0x0000ff); // Blue
31763
32284
  console.log('🎨 Set manual segment material color to blue (dev mode)');
31764
32285
  }
@@ -31789,7 +32310,7 @@ var SceneOperationsManager = /*#__PURE__*/function () {
31789
32310
  console.log('🔌 Created connectors:', connectors);
31790
32311
 
31791
32312
  // In dev mode, enlarge connectors by 1.5x and turn them red
31792
- if (isDev) {
32313
+ if (isDev && process.env.DISABLE_MANUALIZED_SEGMENT_COLORING !== 'true') {
31793
32314
  connectors.forEach(function (connector) {
31794
32315
  if (connector) {
31795
32316
  connector.scale.set(1.25, 1.25, 1.25);
@@ -36156,6 +36677,7 @@ var CentralPlantInternals = /*#__PURE__*/function () {
36156
36677
  this.centralPlant.managers.environmentManager = new EnvironmentManager(this.centralPlant.sceneViewer);
36157
36678
  this.centralPlant.managers.keyboardControlsManager = new KeyboardControlsManager(this.centralPlant.sceneViewer);
36158
36679
  this.centralPlant.managers.pathfindingManager = new PathfindingManager(this.centralPlant.sceneViewer);
36680
+ this.centralPlant.managers.pathFlowManager = new PathFlowManager(this.centralPlant.sceneViewer);
36159
36681
  this.centralPlant.managers.behaviorManager = new BehaviorManager(this.centralPlant.sceneViewer);
36160
36682
  this.centralPlant.managers.sceneOperationsManager = new SceneOperationsManager(this.centralPlant.sceneViewer);
36161
36683
  this.centralPlant.managers.animationManager = new AnimationManager(this.centralPlant.sceneViewer);
@@ -37318,7 +37840,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
37318
37840
  * Initialize the CentralPlant manager
37319
37841
  *
37320
37842
  * @constructor
37321
- * @version 0.2.8
37843
+ * @version 0.2.12
37322
37844
  * @updated 2025-10-22
37323
37845
  *
37324
37846
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -40335,7 +40857,7 @@ var sceneViewer = /*#__PURE__*/function (_BaseDisposable) {
40335
40857
  this.centralPlant.attachToComponent();
40336
40858
 
40337
40859
  // 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
40860
+ managerKeys = ['threeJSResourceManager', 'performanceMonitorManager', 'settingsManager', 'sceneExportManager', 'componentManager', 'sceneInitializationManager', 'environmentManager', 'keyboardControlsManager', 'pathfindingManager', 'pathFlowManager', 'behaviorManager', 'sceneOperationsManager', 'animationManager', 'cameraControlsManager', 'componentDragManager', 'tooltipsManager', 'componentTooltipManager']; // Populate our managers tracking object
40339
40861
  managerKeys.forEach(function (key) {
40340
40862
  if (_this2[key]) {
40341
40863
  _this2.managers[key] = _this2[key];
@@ -43951,10 +44473,12 @@ exports.ComponentDragManager = ComponentDragManager;
43951
44473
  exports.ComponentManager = ComponentManager;
43952
44474
  exports.ComponentTooltipManager = ComponentTooltipManager;
43953
44475
  exports.EnvironmentManager = EnvironmentManager;
44476
+ exports.FLOW_ATTRIBUTE_KEYS = FLOW_ATTRIBUTE_KEYS;
43954
44477
  exports.KeyboardControlsManager = KeyboardControlsManager;
43955
44478
  exports.ModelManager = ModelManager;
43956
44479
  exports.OperationHistoryManager = OperationHistoryManager;
43957
44480
  exports.PathData = PathData;
44481
+ exports.PathFlowManager = PathFlowManager;
43958
44482
  exports.PathfindingManager = PathfindingManager;
43959
44483
  exports.PerformanceMonitorManager = PerformanceMonitorManager;
43960
44484
  exports.Rendering2D = rendering2D;