@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.
@@ -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, slicedToArray as _slicedToArray, superPropGet as _superPropGet, classCallCheck as _classCallCheck, callSuper as _callSuper, toConsumableArray as _toConsumableArray, asyncToGenerator as _asyncToGenerator, regenerator as _regenerator, 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,123 @@ 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
+
520
+ /**
521
+ * Propagate flowAttributes from the original connection list to any gateway-split
522
+ * rendered sub-paths in pathDataStore, then apply all flow visualizations.
523
+ * Called after every pathfinding execution (initial load and transform updates).
524
+ *
525
+ * @param {Array} connections - Original connections array (may include flowAttributes)
526
+ * @param {object} pathfindingResult - Result returned by _executePathfinding
527
+ * @private
528
+ */
529
+ }, {
530
+ key: "_propagateFlowAttributesAndApplyVisualizations",
531
+ value: function _propagateFlowAttributesAndApplyVisualizations(connections, pathfindingResult) {
532
+ var _this5 = this,
533
+ _this$sceneViewer;
534
+ if (!Array.isArray(connections) || !pathfindingResult) return;
535
+
536
+ // Pass 1: use the explicit gateway connection mappings to build a precise
537
+ // original-pathId → Set<renderedPathId> map.
538
+ var originalToSubPaths = new Map();
539
+ if (pathfindingResult.gateways) {
540
+ pathfindingResult.gateways.forEach(function (gateway) {
541
+ var _ref = gateway.connections || {},
542
+ removed = _ref.removed,
543
+ added = _ref.added;
544
+ if (!(removed !== null && removed !== void 0 && removed.length) || !(added !== null && added !== void 0 && added.length)) return;
545
+ removed.forEach(function (removedConn) {
546
+ var origId = "".concat(removedConn.from, "-->").concat(removedConn.to);
547
+ if (!originalToSubPaths.has(origId)) originalToSubPaths.set(origId, new Set());
548
+ added.forEach(function (addedConn) {
549
+ originalToSubPaths.get(origId).add("".concat(addedConn.from, "-->").concat(addedConn.to));
550
+ });
551
+ });
552
+ });
553
+ }
554
+ connections.forEach(function (conn) {
555
+ if (!conn.flowAttributes) return;
556
+ var origId = "".concat(conn.from, "-->").concat(conn.to);
557
+ var subPathIds = new Set(originalToSubPaths.get(origId) || []);
558
+ subPathIds.add(origId);
559
+ subPathIds.forEach(function (subPathId) {
560
+ var pd = _this5.pathDataStore.get(subPathId);
561
+ if (pd && Object.keys(pd.flowAttributes).length === 0) {
562
+ Object.entries(conn.flowAttributes).forEach(function (_ref2) {
563
+ var _ref3 = _slicedToArray(_ref2, 2),
564
+ key = _ref3[0],
565
+ value = _ref3[1];
566
+ return pd.setFlowAttribute(key, value);
567
+ });
568
+ }
569
+ });
570
+ });
571
+
572
+ // Pass 2: endpoint fallback for Gateway→Gateway intermediate segments and
573
+ // for paths whose connections were restructured by manualizeSegment/manualizeGateway.
574
+ // After manualization, currentSceneData.connections no longer contains the original
575
+ // A→B entry with flowAttributes (it's been split into new sub-connections), so we
576
+ // seed the endpoint maps from both the connections array AND from existing pathDataStore
577
+ // entries that already carry flowAttributes — those persist across re-executions.
578
+ var fromEndpointAttrs = new Map();
579
+ var toEndpointAttrs = new Map();
580
+
581
+ // Seed from persisted pathDataStore entries first (covers post-manualization re-runs)
582
+ this.pathDataStore.forEach(function (pd, pathId) {
583
+ if (Object.keys(pd.flowAttributes).length === 0) return;
584
+ var sepIdx = pathId.indexOf('-->');
585
+ if (sepIdx === -1) return;
586
+ var from = pathId.slice(0, sepIdx);
587
+ var to = pathId.slice(sepIdx + 3);
588
+ if (!fromEndpointAttrs.has(from)) fromEndpointAttrs.set(from, pd.flowAttributes);
589
+ if (!toEndpointAttrs.has(to)) toEndpointAttrs.set(to, pd.flowAttributes);
590
+ });
591
+
592
+ // Also seed from connections array (covers initial load before pathDataStore has any attrs)
593
+ connections.forEach(function (conn) {
594
+ if (!conn.flowAttributes) return;
595
+ if (!fromEndpointAttrs.has(conn.from)) fromEndpointAttrs.set(conn.from, conn.flowAttributes);
596
+ if (!toEndpointAttrs.has(conn.to)) toEndpointAttrs.set(conn.to, conn.flowAttributes);
597
+ });
598
+ this.pathDataStore.forEach(function (pd, pathId) {
599
+ if (Object.keys(pd.flowAttributes).length > 0) return;
600
+ var sepIdx = pathId.indexOf('-->');
601
+ if (sepIdx === -1) return;
602
+ var from = pathId.slice(0, sepIdx);
603
+ var to = pathId.slice(sepIdx + 3);
604
+ var attrs = fromEndpointAttrs.get(from) || toEndpointAttrs.get(to);
605
+ if (attrs) Object.entries(attrs).forEach(function (_ref4) {
606
+ var _ref5 = _slicedToArray(_ref4, 2),
607
+ key = _ref5[0],
608
+ value = _ref5[1];
609
+ return pd.setFlowAttribute(key, value);
610
+ });
611
+ });
612
+
613
+ // Apply visualizations
614
+ (_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();
615
+ }
616
+
487
617
  /**
488
618
  * Initialize pathfinder and create paths
489
619
  */
@@ -491,12 +621,32 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
491
621
  key: "initializePathfinder",
492
622
  value: (function () {
493
623
  var _initializePathfinder = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2(data, crosscubeTextureSet) {
624
+ var _this6 = this;
494
625
  var pathfindingResult;
495
626
  return _regenerator().w(function (_context2) {
496
627
  while (1) switch (_context2.n) {
497
628
  case 0:
498
629
  this.crosscubeTextureSet = crosscubeTextureSet;
499
630
 
631
+ // ── Pre-load declared flowAttributes from connections JSON ─────────────
632
+ // This must happen before pathfinding so that PathData entries carrying
633
+ // flowAttributes are available for visualization right after rendering.
634
+ if (Array.isArray(data.connections)) {
635
+ data.connections.forEach(function (conn) {
636
+ if (conn.flowAttributes && _typeof(conn.flowAttributes) === 'object') {
637
+ var pathId = "".concat(conn.from, "-->").concat(conn.to);
638
+ var pd = _this6._getOrCreatePathData(pathId, conn.from, conn.to);
639
+ Object.entries(conn.flowAttributes).forEach(function (_ref6) {
640
+ var _ref7 = _slicedToArray(_ref6, 2),
641
+ key = _ref7[0],
642
+ value = _ref7[1];
643
+ pd.setFlowAttribute(key, value);
644
+ });
645
+ console.log("\uD83C\uDF0A Loaded flowAttributes for path \"".concat(pathId, "\":"), conn.flowAttributes);
646
+ }
647
+ });
648
+ }
649
+
500
650
  // Use shared pathfinding logic with gateway creation enabled
501
651
  _context2.n = 1;
502
652
  return this._executePathfinding(data.scene, data.connections, {
@@ -505,6 +655,8 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
505
655
  });
506
656
  case 1:
507
657
  pathfindingResult = _context2.v;
658
+ this._propagateFlowAttributesAndApplyVisualizations(data.connections, pathfindingResult);
659
+
508
660
  // Update connections with rewired connections
509
661
  if (pathfindingResult.rewiredConnections) {
510
662
  // data.connections = pathfindingResult.rewiredConnections;
@@ -629,6 +781,9 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
629
781
  });
630
782
  case 2:
631
783
  result = _context3.v;
784
+ // Re-apply flow attribute colors (new materials are created on each execution)
785
+ this._propagateFlowAttributesAndApplyVisualizations(currentSceneData.connections, result);
786
+
632
787
  // Cache fingerprint + result for next comparison
633
788
  this._lastPathfindingFingerprint = fingerprint;
634
789
  this._lastPathfindingResult = result;
@@ -1747,7 +1747,7 @@ var SceneOperationsManager = /*#__PURE__*/function () {
1747
1747
  if (typeof window !== 'undefined') {
1748
1748
  isDev = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';
1749
1749
  }
1750
- if (isDev && segment.material) {
1750
+ if (isDev && process.env.DISABLE_MANUALIZED_SEGMENT_COLORING !== 'true' && segment.material) {
1751
1751
  segment.material.color.setHex(0x0000ff); // Blue
1752
1752
  console.log('🎨 Set manual segment material color to blue (dev mode)');
1753
1753
  }
@@ -1778,7 +1778,7 @@ var SceneOperationsManager = /*#__PURE__*/function () {
1778
1778
  console.log('🔌 Created connectors:', connectors);
1779
1779
 
1780
1780
  // In dev mode, enlarge connectors by 1.5x and turn them red
1781
- if (isDev) {
1781
+ if (isDev && process.env.DISABLE_MANUALIZED_SEGMENT_COLORING !== 'true') {
1782
1782
  connectors.forEach(function (connector) {
1783
1783
  if (connector) {
1784
1784
  connector.scale.set(1.25, 1.25, 1.25);
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.12",
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",