@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.
@@ -18,16 +18,58 @@ var PathRenderingManager = /*#__PURE__*/function (_BaseDisposable) {
18
18
  emissive: 0
19
19
  });
20
20
  _this.registerDisposable(_this._gatewayGeometry, _this._gatewayMaterial);
21
+
22
+ /**
23
+ * Map of pathId -> THREE.Material for per-path color control.
24
+ * pathId format: "${from}-->${to}"
25
+ * All segments within a path share the same material instance.
26
+ * @type {Map<string, THREE.Material>}
27
+ */
28
+ _this._pathMaterials = new Map();
21
29
  return _this;
22
30
  }
23
31
 
24
32
  /**
25
- * Get path colors for visual distinction
26
- * @param {number} index - Path index
27
- * @returns {string} Hex color string
33
+ * Dispose all tracked per-path materials and clear the map.
34
+ * Called at the start of createPipePaths to clean up stale materials.
35
+ * @private
28
36
  */
29
37
  _inherits(PathRenderingManager, _BaseDisposable);
30
38
  return _createClass(PathRenderingManager, [{
39
+ key: "_clearPathMaterials",
40
+ value: function _clearPathMaterials() {
41
+ this._pathMaterials.forEach(function (mat) {
42
+ return mat.dispose();
43
+ });
44
+ this._pathMaterials.clear();
45
+ }
46
+
47
+ /**
48
+ * Update the color of all pipe segments belonging to a specific path.
49
+ * Operates on the shared per-path material — no scene traversal needed.
50
+ *
51
+ * @param {string} from - Source connector ID
52
+ * @param {string} to - Target connector ID
53
+ * @param {string|number} color - Any value accepted by THREE.Color.set()
54
+ */
55
+ }, {
56
+ key: "updatePathColor",
57
+ value: function updatePathColor(from, to, color) {
58
+ var pathId = "".concat(from, "-->").concat(to);
59
+ var mat = this._pathMaterials.get(pathId);
60
+ if (mat) {
61
+ mat.color.set(color);
62
+ } else {
63
+ console.warn("\u26A0\uFE0F PathRenderingManager.updatePathColor: no material found for path \"".concat(pathId, "\""));
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Get path colors for visual distinction
69
+ * @param {number} index - Path index
70
+ * @returns {string} Hex color string
71
+ */
72
+ }, {
31
73
  key: "getPathColor",
32
74
  value: function getPathColor(index) {
33
75
  var colors = [
@@ -182,7 +224,11 @@ var PathRenderingManager = /*#__PURE__*/function (_BaseDisposable) {
182
224
  var globalSegmentIndex = maxExistingIndex + 1;
183
225
  console.log("\uD83D\uDD22 Starting segment index at ".concat(globalSegmentIndex, " (max existing: ").concat(maxExistingIndex, ")"));
184
226
  var pipeRadius = 0.1;
185
- var pipeMaterial = this.createPipeMaterial(crosscubeTextureSet);
227
+ // Base material created once; per-path materials are cloned from it below.
228
+ var baseMaterial = this.createPipeMaterial(crosscubeTextureSet);
229
+
230
+ // Dispose previous path materials (they were attached to now-removed segments)
231
+ this._clearPathMaterials();
186
232
  paths.forEach(function (pathData, index) {
187
233
  if (pathData.path && pathData.path.length >= 2) {
188
234
  // Convert path points to Vector3 objects for consistent handling
@@ -199,6 +245,12 @@ var PathRenderingManager = /*#__PURE__*/function (_BaseDisposable) {
199
245
  }
200
246
  });
201
247
 
248
+ // Create a per-path material clone so each path can have its color updated independently.
249
+ var pathId = "".concat(pathData.from, "-->").concat(pathData.to);
250
+ var perPathMaterial = baseMaterial.clone();
251
+ _this3._pathMaterials.set(pathId, perPathMaterial);
252
+ console.log("\uD83C\uDFA8 Created per-path material for \"".concat(pathId, "\""));
253
+
202
254
  // Check if endpoints are component connectors (from pathfinder result)
203
255
  var fromIsComponentConnector = pathData.fromObjectType === 'component-connector';
204
256
  var toIsComponentConnector = pathData.toObjectType === 'component-connector';
@@ -223,13 +275,13 @@ var PathRenderingManager = /*#__PURE__*/function (_BaseDisposable) {
223
275
  var cylinderGeometry = new THREE.CylinderGeometry(pipeRadius, pipeRadius, length, 16, 1, false);
224
276
 
225
277
  // Determine material (debug red if rectified and in dev mode)
226
- var materialToUse = pipeMaterial;
278
+ var materialToUse = perPathMaterial;
227
279
 
228
280
  // Check for dev mode (strict localhost only)
229
281
  var isDev = typeof window !== 'undefined' && (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1');
230
282
  if (isDev && pathData.rectifiedSegments && pathData.rectifiedSegments.includes(j)) {
231
- // Create red debug material by cloning the pipe material to match the look
232
- materialToUse = pipeMaterial.clone();
283
+ // Create red debug material by cloning the per-path material to match the look
284
+ materialToUse = perPathMaterial.clone();
233
285
  materialToUse.color.setHex(0xff0000);
234
286
  console.log("\uD83C\uDFA8 Coloring rectified segment ".concat(j, " red"));
235
287
  }
@@ -291,7 +343,7 @@ var PathRenderingManager = /*#__PURE__*/function (_BaseDisposable) {
291
343
  sceneViewer.scene.add(cylinder);
292
344
 
293
345
  // Add smooth elbow joints only at actual direction changes (not at every point)
294
- _this3.createAndAddElbowIfNeeded(pathPoints, j, pipeRadius, pipeMaterial, pathData, index, cylinder // Pass the segment so elbow can be added as a child
346
+ _this3.createAndAddElbowIfNeeded(pathPoints, j, pipeRadius, perPathMaterial, pathData, index, cylinder // Pass the segment so elbow can be added as a child
295
347
  );
296
348
  }
297
349
  }
@@ -396,6 +448,9 @@ var PathRenderingManager = /*#__PURE__*/function (_BaseDisposable) {
396
448
  value: function dispose() {
397
449
  console.log('🗑️ Disposing PathRenderingManager...');
398
450
 
451
+ // Dispose per-path materials
452
+ this._clearPathMaterials();
453
+
399
454
  // Call parent dispose to clean up registered resources (shared gateway geometry/material)
400
455
  _superPropGet(PathRenderingManager, "dispose", this, 3)([]);
401
456
 
@@ -1,4 +1,4 @@
1
- import { inherits as _inherits, createClass as _createClass, createForOfIteratorHelper as _createForOfIteratorHelper, objectSpread2 as _objectSpread2, superPropGet as _superPropGet, classCallCheck as _classCallCheck, callSuper as _callSuper, toConsumableArray as _toConsumableArray, asyncToGenerator as _asyncToGenerator, regenerator as _regenerator, slicedToArray as _slicedToArray } from '../../../_virtual/_rollupPluginBabelHelpers.js';
1
+ import { inherits as _inherits, createClass as _createClass, createForOfIteratorHelper as _createForOfIteratorHelper, objectSpread2 as _objectSpread2, superPropGet as _superPropGet, classCallCheck as _classCallCheck, callSuper as _callSuper, toConsumableArray as _toConsumableArray, asyncToGenerator as _asyncToGenerator, regenerator as _regenerator, slicedToArray as _slicedToArray, typeof as _typeof } from '../../../_virtual/_rollupPluginBabelHelpers.js';
2
2
  import * as THREE from 'three';
3
3
  import { Pathfinder } from '@2112-lab/pathfinder';
4
4
  import { BaseDisposable } from '../../core/baseDisposable.js';
@@ -386,7 +386,11 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
386
386
  key: "_executePathfinding",
387
387
  value: (function () {
388
388
  var _executePathfinding2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(sceneData, connections) {
389
- var _sceneDataCopy$childr, _sceneDataCopy$childr2, _pathfindingResult$pa, _pathfindingResult$pa2;
389
+ var _this4 = this,
390
+ _sceneDataCopy$childr,
391
+ _sceneDataCopy$childr2,
392
+ _pathfindingResult$pa,
393
+ _pathfindingResult$pa2;
390
394
  var options,
391
395
  _options$context,
392
396
  context,
@@ -447,6 +451,15 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
447
451
 
448
452
  // Create pipe paths with materials using the paths from pathfinder
449
453
  this.renderingManager.createPipePaths(pathfindingResult.paths, this.crosscubeTextureSet);
454
+
455
+ // ── Stage 3b: Ensure PathData entries exist for all rendered paths ──────
456
+ // This preserves any flowAttributes already set on existing entries.
457
+ if (pathfindingResult.paths) {
458
+ pathfindingResult.paths.forEach(function (path) {
459
+ var pathId = "".concat(path.from, "-->").concat(path.to);
460
+ _this4._getOrCreatePathData(pathId, path.from, path.to);
461
+ });
462
+ }
450
463
  timers.pathRendering = performance.now() - renderStart;
451
464
 
452
465
  // ── Performance Summary ────────────────────────────────────────────
@@ -484,6 +497,26 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
484
497
  return this.sceneDataManager.getSimplifiedSceneData();
485
498
  }
486
499
 
500
+ /**
501
+ * Return an existing PathData for pathId from the store, or create and register a new one.
502
+ * Preserves existing flowAttributes when the entry already exists.
503
+ *
504
+ * @param {string} pathId - Canonical path identifier "${from}-->${to}"
505
+ * @param {string} from - Source connector ID
506
+ * @param {string} to - Target connector ID
507
+ * @returns {PathData}
508
+ * @private
509
+ */
510
+ }, {
511
+ key: "_getOrCreatePathData",
512
+ value: function _getOrCreatePathData(pathId, from, to) {
513
+ if (!this.pathDataStore.has(pathId)) {
514
+ var pd = new PathData(pathId, from, to);
515
+ this.pathDataStore.set(pathId, pd);
516
+ }
517
+ return this.pathDataStore.get(pathId);
518
+ }
519
+
487
520
  /**
488
521
  * Initialize pathfinder and create paths
489
522
  */
@@ -491,12 +524,33 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
491
524
  key: "initializePathfinder",
492
525
  value: (function () {
493
526
  var _initializePathfinder = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2(data, crosscubeTextureSet) {
494
- var pathfindingResult;
527
+ var _this5 = this,
528
+ _this$sceneViewer;
529
+ var pathfindingResult, originalToSubPaths, fromEndpointAttrs, flowMgr;
495
530
  return _regenerator().w(function (_context2) {
496
531
  while (1) switch (_context2.n) {
497
532
  case 0:
498
533
  this.crosscubeTextureSet = crosscubeTextureSet;
499
534
 
535
+ // ── Pre-load declared flowAttributes from connections JSON ─────────────
536
+ // This must happen before pathfinding so that PathData entries carrying
537
+ // flowAttributes are available for visualization right after rendering.
538
+ if (Array.isArray(data.connections)) {
539
+ data.connections.forEach(function (conn) {
540
+ if (conn.flowAttributes && _typeof(conn.flowAttributes) === 'object') {
541
+ var pathId = "".concat(conn.from, "-->").concat(conn.to);
542
+ var pd = _this5._getOrCreatePathData(pathId, conn.from, conn.to);
543
+ Object.entries(conn.flowAttributes).forEach(function (_ref) {
544
+ var _ref2 = _slicedToArray(_ref, 2),
545
+ key = _ref2[0],
546
+ value = _ref2[1];
547
+ pd.setFlowAttribute(key, value);
548
+ });
549
+ console.log("\uD83C\uDF0A Loaded flowAttributes for path \"".concat(pathId, "\":"), conn.flowAttributes);
550
+ }
551
+ });
552
+ }
553
+
500
554
  // Use shared pathfinding logic with gateway creation enabled
501
555
  _context2.n = 1;
502
556
  return this._executePathfinding(data.scene, data.connections, {
@@ -505,6 +559,83 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
505
559
  });
506
560
  case 1:
507
561
  pathfindingResult = _context2.v;
562
+ // ── Propagate flowAttributes to gateway-split rendered paths ──────────
563
+ // The pathfinder may split a connection A→B through gateway waypoints,
564
+ // producing sub-paths like A→G and G→B with different pathIds.
565
+ // We need to copy the original connection's flowAttributes onto every
566
+ // rendered sub-path entry in pathDataStore so the visualisation can
567
+ // find them by their rendered pathId.
568
+ if (Array.isArray(data.connections) && pathfindingResult) {
569
+ // Pass 1: use the explicit gateway connection mappings to get a precise
570
+ // original→sub-path map.
571
+ originalToSubPaths = new Map(); // origPathId → Set<renderedPathId>
572
+ if (pathfindingResult.gateways) {
573
+ pathfindingResult.gateways.forEach(function (gateway) {
574
+ var _ref3 = gateway.connections || {},
575
+ removed = _ref3.removed,
576
+ added = _ref3.added;
577
+ if (!(removed !== null && removed !== void 0 && removed.length) || !(added !== null && added !== void 0 && added.length)) return;
578
+ removed.forEach(function (removedConn) {
579
+ var origId = "".concat(removedConn.from, "-->").concat(removedConn.to);
580
+ if (!originalToSubPaths.has(origId)) originalToSubPaths.set(origId, new Set());
581
+ added.forEach(function (addedConn) {
582
+ originalToSubPaths.get(origId).add("".concat(addedConn.from, "-->").concat(addedConn.to));
583
+ });
584
+ });
585
+ });
586
+ }
587
+ data.connections.forEach(function (conn) {
588
+ if (!conn.flowAttributes) return;
589
+ var origId = "".concat(conn.from, "-->").concat(conn.to);
590
+ var subPathIds = new Set(originalToSubPaths.get(origId) || []);
591
+ subPathIds.add(origId); // include the direct path if it wasn't rewired
592
+
593
+ subPathIds.forEach(function (subPathId) {
594
+ var pd = _this5.pathDataStore.get(subPathId);
595
+ if (pd && Object.keys(pd.flowAttributes).length === 0) {
596
+ Object.entries(conn.flowAttributes).forEach(function (_ref4) {
597
+ var _ref5 = _slicedToArray(_ref4, 2),
598
+ key = _ref5[0],
599
+ value = _ref5[1];
600
+ pd.setFlowAttribute(key, value);
601
+ });
602
+ console.log("\uD83C\uDF0A Propagated flowAttributes to sub-path \"".concat(subPathId, "\" from \"").concat(origId, "\""));
603
+ }
604
+ });
605
+ });
606
+
607
+ // Pass 2: endpoint fallback for any rendered path still missing attributes
608
+ // (covers Gateway→Gateway intermediate segments not captured by gateway.connections.added)
609
+ fromEndpointAttrs = new Map(); // connectorId → flowAttributes
610
+ data.connections.forEach(function (conn) {
611
+ if (conn.flowAttributes && !fromEndpointAttrs.has(conn.from)) {
612
+ fromEndpointAttrs.set(conn.from, conn.flowAttributes);
613
+ }
614
+ });
615
+ this.pathDataStore.forEach(function (pd, pathId) {
616
+ if (Object.keys(pd.flowAttributes).length > 0) return;
617
+ var sepIdx = pathId.indexOf('-->');
618
+ if (sepIdx === -1) return;
619
+ var from = pathId.slice(0, sepIdx);
620
+ var attrs = fromEndpointAttrs.get(from);
621
+ if (attrs) {
622
+ Object.entries(attrs).forEach(function (_ref6) {
623
+ var _ref7 = _slicedToArray(_ref6, 2),
624
+ key = _ref7[0],
625
+ value = _ref7[1];
626
+ return pd.setFlowAttribute(key, value);
627
+ });
628
+ console.log("\uD83C\uDF0A Endpoint-matched flowAttributes for path \"".concat(pathId, "\""));
629
+ }
630
+ });
631
+ }
632
+
633
+ // ── Apply flow visualizations now that paths are rendered ──────────────
634
+ 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;
635
+ if (flowMgr) {
636
+ flowMgr.applyAllVisualizations();
637
+ }
638
+
508
639
  // Update connections with rewired connections
509
640
  if (pathfindingResult.rewiredConnections) {
510
641
  // data.connections = pathfindingResult.rewiredConnections;
package/dist/index.d.ts CHANGED
@@ -82,6 +82,62 @@ export interface IAssetService {
82
82
  removeComponentAttachment(options: { componentUuid: string; attachmentId: string }): Promise<SmartComponentAsset>
83
83
  }
84
84
 
85
+ // ─── Flow attribute types ─────────────────────────────────────────────────────
86
+
87
+ /** The canonical set of flow-related attribute keys for a path. */
88
+ export declare const FLOW_ATTRIBUTE_KEYS: ['flowDirection', 'flowSpeed', 'flowTemperature', 'flowMaterial']
89
+
90
+ /** Union type of all valid flow attribute keys. */
91
+ export type FlowAttributeKey = 'flowDirection' | 'flowSpeed' | 'flowTemperature' | 'flowMaterial'
92
+
93
+ /** Resolved values for all four flow attributes on a path (null = unset). */
94
+ export interface ResolvedFlowAttributes {
95
+ flowDirection: any | null
96
+ flowSpeed: any | null
97
+ flowTemperature: any | null
98
+ flowMaterial: any | null
99
+ }
100
+
101
+ /**
102
+ * Manages flow-related attributes (flowDirection, flowSpeed, flowTemperature,
103
+ * flowMaterial) for rendered paths at the central-plant level.
104
+ *
105
+ * Two layers:
106
+ * - Declared — persisted in PathData.flowAttributes, serialised with the scene
107
+ * - Derived — ephemeral runtime overrides (e.g. driven by I/O device state)
108
+ *
109
+ * Resolution: derived > declared > null
110
+ */
111
+ export declare class PathFlowManager {
112
+ constructor(sceneViewer: object)
113
+
114
+ /** Set a declared (persistent) flow attribute on a path. */
115
+ setDeclared(pathId: string, key: FlowAttributeKey, value: any): void
116
+
117
+ /** Set a derived (runtime) flow attribute on a path. */
118
+ setDerived(pathId: string, key: FlowAttributeKey, value: any): void
119
+
120
+ /**
121
+ * Clear derived overrides for one path or all paths.
122
+ * @param pathId - Omit to reset all paths.
123
+ */
124
+ resetDerived(pathId?: string): void
125
+
126
+ /** Resolve the effective value of a single attribute (derived > declared > null). */
127
+ resolve(pathId: string, key: FlowAttributeKey): any | null
128
+
129
+ /** Resolve all four flow attributes for a path. */
130
+ resolveAll(pathId: string): ResolvedFlowAttributes
131
+
132
+ /** Apply temperature-based colour to the pipe material of a single path. */
133
+ applyVisualizationForPath(pathId: string): void
134
+
135
+ /** Apply temperature-based colour to every known path. */
136
+ applyAllVisualizations(): void
137
+
138
+ dispose(): void
139
+ }
140
+
85
141
  // ─── Re-exports ───────────────────────────────────────────────────────────────
86
142
  export { CentralPlant } from './core/centralPlant.js';
87
143
  export { DebugLogger, logger, transformLogger, pathfinderLogger, modelLogger } from './core/debugLogger.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2112-lab/central-plant",
3
- "version": "0.2.8",
3
+ "version": "0.2.10",
4
4
  "description": "Utility modules for the Central Plant Application",
5
5
  "main": "dist/bundle/index.js",
6
6
  "module": "dist/esm/src/index.js",