@2112-lab/central-plant 0.3.58 → 0.3.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/dist/bundle/index.js +1635 -32
  2. package/dist/cjs/src/core/centralPlant.js +80 -11
  3. package/dist/cjs/src/core/centralPlantInternals.js +4 -0
  4. package/dist/cjs/src/core/centralPlantValidator.js +10 -6
  5. package/dist/cjs/src/core/stateEngine.js +733 -0
  6. package/dist/cjs/src/core/stateEngineScene.js +97 -0
  7. package/dist/cjs/src/index.js +10 -0
  8. package/dist/cjs/src/managers/pathfinding/PathFlowManager.js +94 -13
  9. package/dist/cjs/src/managers/pathfinding/PathRenderingManager.js +15 -0
  10. package/dist/cjs/src/managers/scene/componentTooltipManager.js +7 -1
  11. package/dist/cjs/src/managers/scene/controlPanelManager.js +377 -0
  12. package/dist/cjs/src/managers/scene/sceneExportManager.js +10 -1
  13. package/dist/cjs/src/managers/state/EffectiveVisualManager.js +270 -0
  14. package/dist/esm/src/core/centralPlant.js +80 -11
  15. package/dist/esm/src/core/centralPlantInternals.js +4 -0
  16. package/dist/esm/src/core/centralPlantValidator.js +10 -6
  17. package/dist/esm/src/core/stateEngine.js +725 -0
  18. package/dist/esm/src/core/stateEngineScene.js +90 -0
  19. package/dist/esm/src/index.js +2 -0
  20. package/dist/esm/src/managers/pathfinding/PathFlowManager.js +94 -13
  21. package/dist/esm/src/managers/pathfinding/PathRenderingManager.js +15 -0
  22. package/dist/esm/src/managers/scene/componentTooltipManager.js +7 -1
  23. package/dist/esm/src/managers/scene/controlPanelManager.js +352 -0
  24. package/dist/esm/src/managers/scene/sceneExportManager.js +10 -1
  25. package/dist/esm/src/managers/state/EffectiveVisualManager.js +266 -0
  26. package/dist/index.d.ts +85 -0
  27. package/package.json +5 -2
@@ -0,0 +1,377 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var _rollupPluginBabelHelpers = require('../../../_virtual/_rollupPluginBabelHelpers.js');
6
+ var THREE = require('three');
7
+ var baseDisposable = require('../../core/baseDisposable.js');
8
+ var modelPreloader = require('../../rendering/modelPreloader.js');
9
+ var ioDeviceUtils = require('../../utils/ioDeviceUtils.js');
10
+ var behaviorRegistration = require('../../utils/behaviorRegistration.js');
11
+
12
+ function _interopNamespace(e) {
13
+ if (e && e.__esModule) return e;
14
+ var n = Object.create(null);
15
+ if (e) {
16
+ Object.keys(e).forEach(function (k) {
17
+ if (k !== 'default') {
18
+ var d = Object.getOwnPropertyDescriptor(e, k);
19
+ Object.defineProperty(n, k, d.get ? d : {
20
+ enumerable: true,
21
+ get: function () { return e[k]; }
22
+ });
23
+ }
24
+ });
25
+ }
26
+ n["default"] = e;
27
+ return Object.freeze(n);
28
+ }
29
+
30
+ var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
31
+
32
+ /**
33
+ * X offsets for `count` switches evenly spaced and centered on 0.
34
+ * @param {number} count
35
+ * @param {number} spacing
36
+ * @returns {number[]}
37
+ */
38
+ function computeSwitchRowX(count, spacing) {
39
+ var start = -((count - 1) * spacing) / 2;
40
+ return Array.from({
41
+ length: count
42
+ }, function (_, i) {
43
+ return start + i * spacing;
44
+ });
45
+ }
46
+ var ControlPanelManager = /*#__PURE__*/function (_BaseDisposable) {
47
+ /**
48
+ * @param {Object} sceneViewer - The central sceneViewer hub
49
+ */
50
+ function ControlPanelManager(sceneViewer) {
51
+ var _this;
52
+ _rollupPluginBabelHelpers.classCallCheck(this, ControlPanelManager);
53
+ _this = _rollupPluginBabelHelpers.callSuper(this, ControlPanelManager);
54
+ _this.sceneViewer = sceneViewer;
55
+ _this._panel = null;
56
+ _this._labels = [];
57
+ _this._panelId = null;
58
+ _this._build = _this._build.bind(_this);
59
+ if (sceneViewer !== null && sceneViewer !== void 0 && sceneViewer.on) {
60
+ sceneViewer.on('scene-loaded', _this._build);
61
+ }
62
+ return _this;
63
+ }
64
+
65
+ /**
66
+ * Build (or rebuild) the panel from the current scene's `controlPanel` spec.
67
+ * @private
68
+ */
69
+ _rollupPluginBabelHelpers.inherits(ControlPanelManager, _BaseDisposable);
70
+ return _rollupPluginBabelHelpers.createClass(ControlPanelManager, [{
71
+ key: "_build",
72
+ value: (function () {
73
+ var _build2 = _rollupPluginBabelHelpers.asyncToGenerator(/*#__PURE__*/_rollupPluginBabelHelpers.regenerator().m(function _callee() {
74
+ var _this$sceneViewer,
75
+ _centralPlant$importe,
76
+ _this$sceneViewer2,
77
+ _b$width,
78
+ _b$height,
79
+ _b$thickness,
80
+ _desk$tilt,
81
+ _desk$height,
82
+ _desk$legThickness,
83
+ _desk$legInset,
84
+ _spec$switchSpacing,
85
+ _this$sceneViewer3,
86
+ _spec$labelSize,
87
+ _this2 = this;
88
+ var centralPlant, spec, scene, panelId, panel, p, r, d2r, scale, b, width, depth, thickness, desk, tilt, deskHeight, legThickness, legInset, legColor, top, board, legMat, halfW, halfD, _i, _arr, ly, cornerZ, legH, yPos, _i2, _arr2, lx, leg, switches, spacing, xs, topZ, attachedDevices, componentData, switchScale, ioBehavMgr, engine, _iterator, _step, sw, current, lo, labelSize;
89
+ return _rollupPluginBabelHelpers.regenerator().w(function (_context) {
90
+ while (1) switch (_context.n) {
91
+ case 0:
92
+ this._teardown();
93
+ centralPlant = (_this$sceneViewer = this.sceneViewer) === null || _this$sceneViewer === void 0 ? void 0 : _this$sceneViewer.centralPlant;
94
+ spec = centralPlant === null || centralPlant === void 0 || (_centralPlant$importe = centralPlant.importedSceneData) === null || _centralPlant$importe === void 0 ? void 0 : _centralPlant$importe.controlPanel;
95
+ scene = (_this$sceneViewer2 = this.sceneViewer) === null || _this$sceneViewer2 === void 0 ? void 0 : _this$sceneViewer2.scene;
96
+ if (!(!spec || !scene)) {
97
+ _context.n = 1;
98
+ break;
99
+ }
100
+ return _context.a(2);
101
+ case 1:
102
+ panelId = spec.id || 'CONTROL-PANEL-1';
103
+ this._panelId = panelId;
104
+ panel = new THREE__namespace.Group();
105
+ panel.name = 'Control Panel';
106
+ panel.uuid = panelId;
107
+ panel.userData = {
108
+ objectType: 'component',
109
+ isControlPanel: true
110
+ };
111
+ p = spec.position || {};
112
+ panel.position.set(p.x || 0, p.y || 0, p.z || 0);
113
+ r = spec.rotation || {};
114
+ d2r = Math.PI / 180;
115
+ panel.rotation.set((r.x || 0) * d2r, (r.y || 0) * d2r, (r.z || 0) * d2r, 'XYZ');
116
+ scale = Number.isFinite(spec.scale) && spec.scale > 0 ? spec.scale : 1;
117
+ panel.scale.setScalar(scale);
118
+
119
+ // ── Drawing-desk geometry (Z-up scene) ───────────────────────────────────
120
+ // A tilted top surface (the drafting board) raised on legs. Switches +
121
+ // labels live on the tilted `top` group so they sit on the angled surface.
122
+ b = spec.board || {};
123
+ width = (_b$width = b.width) !== null && _b$width !== void 0 ? _b$width : 7; // x-extent of the top
124
+ depth = (_b$height = b.height) !== null && _b$height !== void 0 ? _b$height : 2; // y-extent of the top (spec key kept as `height`)
125
+ thickness = (_b$thickness = b.thickness) !== null && _b$thickness !== void 0 ? _b$thickness : 0.15;
126
+ desk = spec.desk || {};
127
+ tilt = ((_desk$tilt = desk.tilt) !== null && _desk$tilt !== void 0 ? _desk$tilt : 30) * d2r; // surface tilt; +raises the back (+y) edge
128
+ deskHeight = (_desk$height = desk.height) !== null && _desk$height !== void 0 ? _desk$height : 1.3; // world height of the top's center
129
+ legThickness = (_desk$legThickness = desk.legThickness) !== null && _desk$legThickness !== void 0 ? _desk$legThickness : 0.18;
130
+ legInset = (_desk$legInset = desk.legInset) !== null && _desk$legInset !== void 0 ? _desk$legInset : 0.35;
131
+ legColor = desk.legColor || '#22303c'; // Tilted top group: raised to deskHeight, rotated about X.
132
+ top = new THREE__namespace.Group();
133
+ top.position.set(0, 0, deskHeight);
134
+ top.rotation.set(tilt, 0, 0, 'XYZ');
135
+ panel.add(top);
136
+ board = new THREE__namespace.Mesh(new THREE__namespace.BoxGeometry(width, depth, thickness), new THREE__namespace.MeshStandardMaterial({
137
+ color: b.color || '#2c3e50',
138
+ roughness: 0.6,
139
+ metalness: 0.2
140
+ }));
141
+ board.userData = {
142
+ objectType: 'control-panel-board'
143
+ };
144
+ top.add(board);
145
+
146
+ // Legs: one per corner, from the ground up to the tilted top's underside.
147
+ legMat = new THREE__namespace.MeshStandardMaterial({
148
+ color: legColor,
149
+ roughness: 0.7,
150
+ metalness: 0.1
151
+ });
152
+ halfW = Math.max(0.1, width / 2 - legInset);
153
+ halfD = Math.max(0.1, depth / 2 - legInset);
154
+ for (_i = 0, _arr = [halfD, -halfD]; _i < _arr.length; _i++) {
155
+ ly = _arr[_i];
156
+ // Underside height at this front/back row after the tilt.
157
+ cornerZ = deskHeight + ly * Math.sin(tilt) - thickness / 2 * Math.cos(tilt);
158
+ legH = Math.max(0.1, cornerZ);
159
+ yPos = ly * Math.cos(tilt); // corner's y in panel space after tilt
160
+ for (_i2 = 0, _arr2 = [halfW, -halfW]; _i2 < _arr2.length; _i2++) {
161
+ lx = _arr2[_i2];
162
+ leg = new THREE__namespace.Mesh(new THREE__namespace.BoxGeometry(legThickness, legThickness, legH), legMat);
163
+ leg.position.set(lx, yPos, legH / 2);
164
+ panel.add(leg);
165
+ }
166
+ }
167
+
168
+ // ── Switch layout: a row along X on the top face (+Z) of the board ───────
169
+ switches = Array.isArray(spec.switches) ? spec.switches : [];
170
+ spacing = (_spec$switchSpacing = spec.switchSpacing) !== null && _spec$switchSpacing !== void 0 ? _spec$switchSpacing : 1.3;
171
+ xs = computeSwitchRowX(switches.length, spacing);
172
+ topZ = thickness / 2;
173
+ attachedDevices = {};
174
+ switches.forEach(function (sw, i) {
175
+ attachedDevices[sw.attachmentId] = {
176
+ deviceId: spec.deviceId,
177
+ attachmentLabel: sw.label || sw.attachmentId,
178
+ attachmentPoint: {
179
+ position: {
180
+ x: xs[i],
181
+ y: 0,
182
+ z: topZ
183
+ },
184
+ rotation: spec.switchRotation || {
185
+ x: 0,
186
+ y: 0,
187
+ z: 0
188
+ }
189
+ }
190
+ };
191
+ });
192
+ componentData = {
193
+ isSmart: true,
194
+ attachedDevices: attachedDevices
195
+ }; // Clone + tag the switch devices onto the tilted top (reuses smart-component path).
196
+ _context.n = 2;
197
+ return ioDeviceUtils.attachIODevicesToComponent(top, componentData, modelPreloader["default"], panelId);
198
+ case 2:
199
+ // Optional switch-only scale (attach path always leaves devices at 1:1).
200
+ switchScale = Number.isFinite(spec.switchScale) && spec.switchScale > 0 ? spec.switchScale : 1;
201
+ if (switchScale !== 1) {
202
+ top.traverse(function (obj) {
203
+ var _obj$userData;
204
+ if (((_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.objectType) === 'io-device') {
205
+ obj.scale.setScalar(switchScale);
206
+ }
207
+ });
208
+ }
209
+
210
+ // Panel must be in the scene before behaviors register (default-state seeding
211
+ // scans the live scene graph for io-devices).
212
+ scene.add(panel);
213
+ this._panel = panel;
214
+ ioBehavMgr = (_this$sceneViewer3 = this.sceneViewer) === null || _this$sceneViewer3 === void 0 || (_this$sceneViewer3 = _this$sceneViewer3.managers) === null || _this$sceneViewer3 === void 0 ? void 0 : _this$sceneViewer3.ioBehaviorManager;
215
+ if (ioBehavMgr) {
216
+ behaviorRegistration.registerBehaviorsForComponent(ioBehavMgr, panelId, componentData, panel, modelPreloader["default"].componentDictionary, centralPlant);
217
+
218
+ // Drive each switch's mesh to its current engine value so the initial
219
+ // pose/colour reflects the state. `registerBehaviorsForComponent`'s
220
+ // default-seeding writes through the engine, which no-ops when the value
221
+ // already matches the seeded `stateRegistry` — leaving the button at its
222
+ // neutral GLB rest pose. Trigger the animation directly to avoid that.
223
+ engine = centralPlant === null || centralPlant === void 0 ? void 0 : centralPlant.stateEngine;
224
+ _iterator = _rollupPluginBabelHelpers.createForOfIteratorHelper(switches);
225
+ try {
226
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
227
+ sw = _step.value;
228
+ current = engine === null || engine === void 0 ? void 0 : engine.getDesired("".concat(panelId, "::").concat(sw.attachmentId, "::mode"));
229
+ ioBehavMgr.triggerState(sw.attachmentId, 'mode', current !== null && current !== void 0 ? current : true, panelId);
230
+ }
231
+ } catch (err) {
232
+ _iterator.e(err);
233
+ } finally {
234
+ _iterator.f();
235
+ }
236
+ }
237
+
238
+ // ── Floating labels above each switch (on the tilted top) ────────────────
239
+ lo = spec.labelOffset || {
240
+ x: 0,
241
+ y: 0,
242
+ z: 0.7
243
+ };
244
+ labelSize = (_spec$labelSize = spec.labelSize) !== null && _spec$labelSize !== void 0 ? _spec$labelSize : 0.5;
245
+ switches.forEach(function (sw, i) {
246
+ var _lo$z;
247
+ var label = _this2._makeLabel(sw.label || sw.attachmentId, labelSize);
248
+ if (!label) return;
249
+ label.position.set(xs[i] + (lo.x || 0), lo.y || 0, topZ + ((_lo$z = lo.z) !== null && _lo$z !== void 0 ? _lo$z : 0.7));
250
+ top.add(label);
251
+ _this2._labels.push(label);
252
+ });
253
+ case 3:
254
+ return _context.a(2);
255
+ }
256
+ }, _callee, this);
257
+ }));
258
+ function _build() {
259
+ return _build2.apply(this, arguments);
260
+ }
261
+ return _build;
262
+ }()
263
+ /**
264
+ * Build one text label as a camera-facing `THREE.Sprite` (canvas texture),
265
+ * drawn by the main WebGL renderer alongside the scene — no dependency on the
266
+ * tooltip CSS2D renderer. Returns null when there is no DOM (headless tests).
267
+ * @param {string} text
268
+ * @param {number} worldHeight - label height in world units
269
+ * @returns {THREE.Sprite|null}
270
+ * @private
271
+ */
272
+ )
273
+ }, {
274
+ key: "_makeLabel",
275
+ value: function _makeLabel(text, worldHeight) {
276
+ if (typeof document === 'undefined') return null;
277
+ var dpr = 4; // supersample for crisp text
278
+ var fontPx = 40;
279
+ var padX = 14;
280
+ var padY = 8;
281
+ var font = "600 ".concat(fontPx, "px -apple-system, \"Segoe UI\", Roboto, sans-serif");
282
+ var canvas = document.createElement('canvas');
283
+ var ctx = canvas.getContext('2d');
284
+ ctx.font = font;
285
+ var textW = ctx.measureText(text).width;
286
+ canvas.width = Math.ceil((textW + padX * 2) * dpr);
287
+ canvas.height = Math.ceil((fontPx + padY * 2) * dpr);
288
+
289
+ // Re-apply after the resize (which clears the canvas), scaled by dpr.
290
+ ctx.scale(dpr, dpr);
291
+ ctx.font = font;
292
+ var wCss = canvas.width / dpr;
293
+ var hCss = canvas.height / dpr;
294
+ ctx.fillStyle = 'rgba(20,28,38,0.86)';
295
+ this._roundRect(ctx, 0, 0, wCss, hCss, 8);
296
+ ctx.fill();
297
+ ctx.fillStyle = '#eaf2f8';
298
+ ctx.textAlign = 'center';
299
+ ctx.textBaseline = 'middle';
300
+ ctx.fillText(text, wCss / 2, hCss / 2);
301
+ var texture = new THREE__namespace.CanvasTexture(canvas);
302
+ texture.minFilter = THREE__namespace.LinearFilter;
303
+ texture.anisotropy = 4;
304
+ var material = new THREE__namespace.SpriteMaterial({
305
+ map: texture,
306
+ transparent: true,
307
+ depthTest: false,
308
+ // always readable, never occluded by the board/switch
309
+ depthWrite: false
310
+ });
311
+ var sprite = new THREE__namespace.Sprite(material);
312
+ var aspect = canvas.width / canvas.height;
313
+ sprite.scale.set(worldHeight * aspect, worldHeight, 1);
314
+ sprite.renderOrder = 999;
315
+ return sprite;
316
+ }
317
+
318
+ /** Trace a rounded-rect path (fill applied by caller). @private */
319
+ }, {
320
+ key: "_roundRect",
321
+ value: function _roundRect(ctx, x, y, w, h, r) {
322
+ var rr = Math.min(r, w / 2, h / 2);
323
+ ctx.beginPath();
324
+ ctx.moveTo(x + rr, y);
325
+ ctx.arcTo(x + w, y, x + w, y + h, rr);
326
+ ctx.arcTo(x + w, y + h, x, y + h, rr);
327
+ ctx.arcTo(x, y + h, x, y, rr);
328
+ ctx.arcTo(x, y, x + w, y, rr);
329
+ ctx.closePath();
330
+ }
331
+
332
+ /**
333
+ * Remove the panel, its labels, and its registered behaviors (scene reload).
334
+ * @private
335
+ */
336
+ }, {
337
+ key: "_teardown",
338
+ value: function _teardown() {
339
+ this._labels = [];
340
+ if (this._panelId) {
341
+ var _this$sceneViewer4;
342
+ (_this$sceneViewer4 = this.sceneViewer) === null || _this$sceneViewer4 === void 0 || (_this$sceneViewer4 = _this$sceneViewer4.managers) === null || _this$sceneViewer4 === void 0 || (_this$sceneViewer4 = _this$sceneViewer4.ioBehaviorManager) === null || _this$sceneViewer4 === void 0 || _this$sceneViewer4.unloadForComponent(this._panelId);
343
+ }
344
+ if (this._panel) {
345
+ var _this$sceneViewer5;
346
+ (_this$sceneViewer5 = this.sceneViewer) === null || _this$sceneViewer5 === void 0 || (_this$sceneViewer5 = _this$sceneViewer5.scene) === null || _this$sceneViewer5 === void 0 || _this$sceneViewer5.remove(this._panel);
347
+ this._panel.traverse(function (obj) {
348
+ if (obj.isMesh || obj.isSprite) {
349
+ var _obj$geometry, _obj$geometry$dispose;
350
+ (_obj$geometry = obj.geometry) === null || _obj$geometry === void 0 || (_obj$geometry$dispose = _obj$geometry.dispose) === null || _obj$geometry$dispose === void 0 || _obj$geometry$dispose.call(_obj$geometry);
351
+ var mats = Array.isArray(obj.material) ? obj.material : [obj.material];
352
+ mats.forEach(function (m) {
353
+ var _m$map, _m$map$dispose, _m$dispose;
354
+ m === null || m === void 0 || (_m$map = m.map) === null || _m$map === void 0 || (_m$map$dispose = _m$map.dispose) === null || _m$map$dispose === void 0 || _m$map$dispose.call(_m$map); // sprite canvas texture
355
+ m === null || m === void 0 || (_m$dispose = m.dispose) === null || _m$dispose === void 0 || _m$dispose.call(m);
356
+ });
357
+ }
358
+ });
359
+ this._panel = null;
360
+ }
361
+ this._panelId = null;
362
+ }
363
+ }, {
364
+ key: "_doDispose",
365
+ value: function _doDispose() {
366
+ var _this$sceneViewer6;
367
+ if ((_this$sceneViewer6 = this.sceneViewer) !== null && _this$sceneViewer6 !== void 0 && _this$sceneViewer6.off) {
368
+ this.sceneViewer.off('scene-loaded', this._build);
369
+ }
370
+ this._teardown();
371
+ this.sceneViewer = null;
372
+ }
373
+ }]);
374
+ }(baseDisposable.BaseDisposable);
375
+
376
+ exports.ControlPanelManager = ControlPanelManager;
377
+ exports.computeSwitchRowX = computeSwitchRowX;
@@ -6,6 +6,7 @@ var _rollupPluginBabelHelpers = require('../../../_virtual/_rollupPluginBabelHel
6
6
  require('three');
7
7
  var GLTFExporter = require('../../../node_modules/three/examples/jsm/exporters/GLTFExporter.js');
8
8
  var nameUtils = require('../../utils/nameUtils.js');
9
+ var stateEngineScene = require('../../core/stateEngineScene.js');
9
10
 
10
11
  function _interopNamespace(e) {
11
12
  if (e && e.__esModule) return e;
@@ -78,7 +79,8 @@ var SceneExportManager = /*#__PURE__*/function () {
78
79
  key: "exportSceneData",
79
80
  value: function exportSceneData() {
80
81
  var _this = this,
81
- _this$sceneViewer$cur2;
82
+ _this$sceneViewer$cur2,
83
+ _this$sceneViewer$cen;
82
84
  console.log('📤 Starting scene export...');
83
85
  if (!this.sceneViewer.scene) {
84
86
  console.warn('⚠️ No scene available for export');
@@ -311,6 +313,13 @@ var SceneExportManager = /*#__PURE__*/function () {
311
313
  if (sceneBehaviors && sceneBehaviors.length > 0) {
312
314
  exportData.behaviors = sceneBehaviors;
313
315
  }
316
+
317
+ // Persist plant / groups / chains / stateRegistry from the State Engine and
318
+ // bump the format to 3.0 (Framework §12; no-op if no engine present).
319
+ var stateEngine = (_this$sceneViewer$cen = this.sceneViewer.centralPlant) === null || _this$sceneViewer$cen === void 0 ? void 0 : _this$sceneViewer$cen.stateEngine;
320
+ if (stateEngine) {
321
+ stateEngineScene.applyDeclarationToExport(exportData, stateEngine);
322
+ }
314
323
  console.log('✅ Scene export completed:', exportData);
315
324
  console.log("\uD83D\uDCCA Exported ".concat(sceneChildren.length, " components and ").concat(exportData.connections.length, " connections"));
316
325
  return exportData;
@@ -0,0 +1,270 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var _rollupPluginBabelHelpers = require('../../../_virtual/_rollupPluginBabelHelpers.js');
6
+ var baseDisposable = require('../../core/baseDisposable.js');
7
+ var nameUtils = require('../../utils/nameUtils.js');
8
+
9
+ /** How far the albedo is pulled toward black for a gated-off mesh. */
10
+ var OFF_COLOR_SCALE = 0.2;
11
+ var EffectiveVisualManager = /*#__PURE__*/function (_BaseDisposable) {
12
+ /**
13
+ * @param {Object} sceneViewer - The central sceneViewer hub
14
+ */
15
+ function EffectiveVisualManager(sceneViewer) {
16
+ var _this;
17
+ _rollupPluginBabelHelpers.classCallCheck(this, EffectiveVisualManager);
18
+ _this = _rollupPluginBabelHelpers.callSuper(this, EffectiveVisualManager);
19
+ _this.sceneViewer = sceneViewer;
20
+
21
+ /**
22
+ * Materials this manager has gated (cloned + cached), for restore on dispose.
23
+ * @type {Set<THREE.Material>}
24
+ */
25
+ _this._gatedMaterials = new Set();
26
+
27
+ // Bound handlers so we can unsubscribe in _doDispose().
28
+ _this._onChange = _this._onChange.bind(_this);
29
+ _this._regateAll = _this._regateAll.bind(_this);
30
+ if (sceneViewer !== null && sceneViewer !== void 0 && sceneViewer.on) {
31
+ sceneViewer.on('engine-state-changed', _this._onChange);
32
+ sceneViewer.on('scene-loaded', _this._regateAll);
33
+ }
34
+ return _this;
35
+ }
36
+
37
+ // ── Engine change handling ──────────────────────────────────────────────────
38
+
39
+ /**
40
+ * React to one committed registry change. Only 2-part object / pipe addresses
41
+ * are gated; everything else is ignored.
42
+ * @param {{ address: string, desired: *, effective: * }} change
43
+ * @private
44
+ */
45
+ _rollupPluginBabelHelpers.inherits(EffectiveVisualManager, _BaseDisposable);
46
+ return _rollupPluginBabelHelpers.createClass(EffectiveVisualManager, [{
47
+ key: "_onChange",
48
+ value: function _onChange(change) {
49
+ if (!change || typeof change.address !== 'string') return;
50
+ var parts = change.address.split('::');
51
+ if (parts.length !== 2) return; // 3-part I/O is desired-driven; skip
52
+
53
+ var _parts = _rollupPluginBabelHelpers.slicedToArray(parts, 2),
54
+ scope = _parts[0],
55
+ attr = _parts[1];
56
+ if (scope === 'plant' || scope.startsWith('group:')) return; // members self-report
57
+
58
+ if (attr === 'power') {
59
+ this._gateEquipment(scope, change.effective);
60
+ } else if (attr === 'flow') {
61
+ this._gatePipe(scope, change.effective);
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Re-gate the whole scene from current effective state. Used after scene load,
67
+ * where StateEngine.load() populates the registry without emitting per-address
68
+ * events (and the meshes have just been (re)built).
69
+ * @private
70
+ */
71
+ }, {
72
+ key: "_regateAll",
73
+ value: function _regateAll() {
74
+ var _this$sceneViewer;
75
+ var engine = (_this$sceneViewer = this.sceneViewer) === null || _this$sceneViewer === void 0 || (_this$sceneViewer = _this$sceneViewer.centralPlant) === null || _this$sceneViewer === void 0 ? void 0 : _this$sceneViewer.stateEngine;
76
+ if (!engine) return;
77
+ var registry = engine.toRegistry();
78
+ for (var _i = 0, _Object$keys = Object.keys(registry); _i < _Object$keys.length; _i++) {
79
+ var address = _Object$keys[_i];
80
+ var parts = address.split('::');
81
+ if (parts.length !== 2) continue;
82
+ var _parts2 = _rollupPluginBabelHelpers.slicedToArray(parts, 2),
83
+ scope = _parts2[0],
84
+ attr = _parts2[1];
85
+ if (scope === 'plant' || scope.startsWith('group:')) continue;
86
+ if (attr === 'power') {
87
+ this._gateEquipment(scope, engine.getEffective(address));
88
+ } else if (attr === 'flow') {
89
+ this._gatePipe(scope, engine.getEffective(address));
90
+ }
91
+ }
92
+ }
93
+
94
+ // ── Equipment gating ────────────────────────────────────────────────────────
95
+
96
+ /**
97
+ * Darken (off) or restore (on) every mesh of an equipment object.
98
+ * @param {string} objectId - Hardcoded object id (address scope)
99
+ * @param {boolean} on - Effective power
100
+ * @private
101
+ */
102
+ }, {
103
+ key: "_gateEquipment",
104
+ value: function _gateEquipment(objectId, on) {
105
+ var _this$sceneViewer2,
106
+ _this2 = this;
107
+ var scene = (_this$sceneViewer2 = this.sceneViewer) === null || _this$sceneViewer2 === void 0 ? void 0 : _this$sceneViewer2.scene;
108
+ if (!scene) return;
109
+ var obj = nameUtils.findObjectByHardcodedUuid(scene, objectId);
110
+ if (!obj || typeof obj.traverse !== 'function') return;
111
+ obj.traverse(function (mesh) {
112
+ if (!mesh.isMesh && !mesh.material) return;
113
+ // Leave attached I/O control panels (switches) lit and operable even when
114
+ // their host is powered off — you must be able to see and click the switch
115
+ // that turns it back on. (traverse can't prune, so guard per mesh.)
116
+ if (_this2._isUnderIoDevice(mesh, obj)) return;
117
+ var materials = _this2._prepareMeshMaterials(mesh);
118
+ var _iterator = _rollupPluginBabelHelpers.createForOfIteratorHelper(materials),
119
+ _step;
120
+ try {
121
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
122
+ var material = _step.value;
123
+ _this2._applyGate(material, on);
124
+ }
125
+ } catch (err) {
126
+ _iterator.e(err);
127
+ } finally {
128
+ _iterator.f();
129
+ }
130
+ });
131
+ }
132
+
133
+ /**
134
+ * True if `mesh` is an io-device or nested under one, walking up to (but not
135
+ * past) the equipment root `stopAt`.
136
+ * @param {THREE.Object3D} mesh
137
+ * @param {THREE.Object3D} stopAt - equipment root to stop the upward walk at
138
+ * @returns {boolean}
139
+ * @private
140
+ */
141
+ }, {
142
+ key: "_isUnderIoDevice",
143
+ value: function _isUnderIoDevice(mesh, stopAt) {
144
+ var node = mesh;
145
+ while (node) {
146
+ var _node$userData;
147
+ if (((_node$userData = node.userData) === null || _node$userData === void 0 ? void 0 : _node$userData.objectType) === 'io-device') return true;
148
+ if (node === stopAt) break;
149
+ node = node.parent;
150
+ }
151
+ return false;
152
+ }
153
+
154
+ /**
155
+ * Ensure a mesh's material(s) are cloned-on-write and have their baseline
156
+ * cached, then return the material list to gate.
157
+ * @param {THREE.Mesh} mesh
158
+ * @returns {THREE.Material[]}
159
+ * @private
160
+ */
161
+ }, {
162
+ key: "_prepareMeshMaterials",
163
+ value: function _prepareMeshMaterials(mesh) {
164
+ if (!mesh.material) return [];
165
+
166
+ // Clone-on-write once per mesh so shared library materials aren't shared.
167
+ if (!mesh.userData._effectiveGateCloned) {
168
+ if (Array.isArray(mesh.material)) {
169
+ mesh.material = mesh.material.map(function (m) {
170
+ return m !== null && m !== void 0 && m.clone ? m.clone() : m;
171
+ });
172
+ } else if (mesh.material.clone) {
173
+ mesh.material = mesh.material.clone();
174
+ }
175
+ mesh.userData._effectiveGateCloned = true;
176
+ }
177
+ var materials = Array.isArray(mesh.material) ? mesh.material : [mesh.material];
178
+ var _iterator2 = _rollupPluginBabelHelpers.createForOfIteratorHelper(materials),
179
+ _step2;
180
+ try {
181
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
182
+ var _material$userData, _material$color$clone, _material$color, _material$color$clone2, _material$emissive$cl, _material$emissive, _material$emissive$cl2;
183
+ var material = _step2.value;
184
+ if (!material || (_material$userData = material.userData) !== null && _material$userData !== void 0 && _material$userData._effectiveGate) continue;
185
+ material.userData = material.userData || {};
186
+ material.userData._effectiveGate = {
187
+ color: (_material$color$clone = (_material$color = material.color) === null || _material$color === void 0 || (_material$color$clone2 = _material$color.clone) === null || _material$color$clone2 === void 0 ? void 0 : _material$color$clone2.call(_material$color)) !== null && _material$color$clone !== void 0 ? _material$color$clone : null,
188
+ emissive: (_material$emissive$cl = (_material$emissive = material.emissive) === null || _material$emissive === void 0 || (_material$emissive$cl2 = _material$emissive.clone) === null || _material$emissive$cl2 === void 0 ? void 0 : _material$emissive$cl2.call(_material$emissive)) !== null && _material$emissive$cl !== void 0 ? _material$emissive$cl : null,
189
+ emissiveIntensity: typeof material.emissiveIntensity === 'number' ? material.emissiveIntensity : null
190
+ };
191
+ this._gatedMaterials.add(material);
192
+ }
193
+ } catch (err) {
194
+ _iterator2.e(err);
195
+ } finally {
196
+ _iterator2.f();
197
+ }
198
+ return materials.filter(Boolean);
199
+ }
200
+
201
+ /**
202
+ * Darken or restore a single material from its cached baseline.
203
+ * @param {THREE.Material} material
204
+ * @param {boolean} on
205
+ * @private
206
+ */
207
+ }, {
208
+ key: "_applyGate",
209
+ value: function _applyGate(material, on) {
210
+ var _material$userData2;
211
+ var base = (_material$userData2 = material.userData) === null || _material$userData2 === void 0 ? void 0 : _material$userData2._effectiveGate;
212
+ if (!base) return;
213
+ if (on) {
214
+ if (base.color && material.color) material.color.copy(base.color);
215
+ if (base.emissive && material.emissive) material.emissive.copy(base.emissive);
216
+ if (base.emissiveIntensity !== null) material.emissiveIntensity = base.emissiveIntensity;
217
+ } else {
218
+ if (base.color && material.color) material.color.copy(base.color).multiplyScalar(OFF_COLOR_SCALE);
219
+ if (material.emissive) material.emissive.setScalar(0);
220
+ if (typeof material.emissiveIntensity === 'number') material.emissiveIntensity = 0;
221
+ }
222
+ material.needsUpdate = true;
223
+ }
224
+
225
+ // ── Pipe flow gating ────────────────────────────────────────────────────────
226
+
227
+ /**
228
+ * Delegate pipe-flow gating to PathFlowManager (grays the shared per-path
229
+ * material when off, restores the temperature colour when on).
230
+ * @param {string} pathId - `${from}-->${to}`
231
+ * @param {boolean} on - Effective flow
232
+ * @private
233
+ */
234
+ }, {
235
+ key: "_gatePipe",
236
+ value: function _gatePipe(pathId, on) {
237
+ var _this$sceneViewer3, _pathFlowManager$setF;
238
+ var pathFlowManager = (_this$sceneViewer3 = this.sceneViewer) === null || _this$sceneViewer3 === void 0 || (_this$sceneViewer3 = _this$sceneViewer3.managers) === null || _this$sceneViewer3 === void 0 ? void 0 : _this$sceneViewer3.pathFlowManager;
239
+ pathFlowManager === null || pathFlowManager === void 0 || (_pathFlowManager$setF = pathFlowManager.setFlowEnabled) === null || _pathFlowManager$setF === void 0 || _pathFlowManager$setF.call(pathFlowManager, pathId, on);
240
+ }
241
+
242
+ // ── Lifecycle ───────────────────────────────────────────────────────────────
243
+ }, {
244
+ key: "_doDispose",
245
+ value: function _doDispose() {
246
+ var _this$sceneViewer4;
247
+ if ((_this$sceneViewer4 = this.sceneViewer) !== null && _this$sceneViewer4 !== void 0 && _this$sceneViewer4.off) {
248
+ this.sceneViewer.off('engine-state-changed', this._onChange);
249
+ this.sceneViewer.off('scene-loaded', this._regateAll);
250
+ }
251
+ // Restore any still-gated materials to their cached baseline.
252
+ var _iterator3 = _rollupPluginBabelHelpers.createForOfIteratorHelper(this._gatedMaterials),
253
+ _step3;
254
+ try {
255
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
256
+ var material = _step3.value;
257
+ this._applyGate(material, true);
258
+ }
259
+ } catch (err) {
260
+ _iterator3.e(err);
261
+ } finally {
262
+ _iterator3.f();
263
+ }
264
+ this._gatedMaterials.clear();
265
+ this.sceneViewer = null;
266
+ }
267
+ }]);
268
+ }(baseDisposable.BaseDisposable);
269
+
270
+ exports.EffectiveVisualManager = EffectiveVisualManager;