@2112-lab/central-plant 0.2.5 → 0.2.8

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.
@@ -36042,6 +36042,59 @@ var Viewport2DManager = /*#__PURE__*/function (_BaseDisposable2) {
36042
36042
  }]);
36043
36043
  }(BaseDisposable);
36044
36044
 
36045
+ // ─────────────────────────────────────────────────────────────────────────────
36046
+ // Flow-direction helpers (module-level)
36047
+ // ─────────────────────────────────────────────────────────────────────────────
36048
+
36049
+ /**
36050
+ * Returns the flow direction of a connector from the current scene data.
36051
+ * @param {Object} sceneData - currentSceneData object
36052
+ * @param {string} connectorId
36053
+ * @returns {'in'|'out'|'bi'} Defaults to 'bi' if not set.
36054
+ */
36055
+ function _getConnectorFlow(sceneData, connectorId) {
36056
+ var _sceneData$scene;
36057
+ var children = (sceneData === null || sceneData === void 0 || (_sceneData$scene = sceneData.scene) === null || _sceneData$scene === void 0 ? void 0 : _sceneData$scene.children) || [];
36058
+ var _iterator = _createForOfIteratorHelper(children),
36059
+ _step;
36060
+ try {
36061
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
36062
+ var component = _step.value;
36063
+ var _iterator2 = _createForOfIteratorHelper(component.children || []),
36064
+ _step2;
36065
+ try {
36066
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
36067
+ var _child$userData;
36068
+ var child = _step2.value;
36069
+ if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'connector' && child.uuid === connectorId) {
36070
+ return child.userData.flow || 'bi';
36071
+ }
36072
+ }
36073
+ } catch (err) {
36074
+ _iterator2.e(err);
36075
+ } finally {
36076
+ _iterator2.f();
36077
+ }
36078
+ }
36079
+ } catch (err) {
36080
+ _iterator.e(err);
36081
+ } finally {
36082
+ _iterator.f();
36083
+ }
36084
+ return 'bi';
36085
+ }
36086
+
36087
+ /**
36088
+ * Returns true if fromFlow → toFlow is a valid connection.
36089
+ * @param {'in'|'out'|'bi'} fromFlow
36090
+ * @param {'in'|'out'|'bi'} toFlow
36091
+ * @returns {boolean}
36092
+ */
36093
+ function _areFlowsCompatible$1(fromFlow, toFlow) {
36094
+ if (fromFlow === 'bi' || toFlow === 'bi') return true;
36095
+ return fromFlow !== toFlow;
36096
+ }
36097
+
36045
36098
  /**
36046
36099
  * CentralPlantInternals class containing internal methods and functionality
36047
36100
  */
@@ -36493,12 +36546,12 @@ var CentralPlantInternals = /*#__PURE__*/function () {
36493
36546
  console.log("\uD83D\uDD27 Translating ".concat(selectedObjects.length, " selected object(s) on ").concat(axis, " axis by ").concat(value));
36494
36547
 
36495
36548
  // Translate each selected object using the appropriate method
36496
- var _iterator = _createForOfIteratorHelper(selectedObjects),
36497
- _step;
36549
+ var _iterator3 = _createForOfIteratorHelper(selectedObjects),
36550
+ _step3;
36498
36551
  try {
36499
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
36552
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
36500
36553
  var _obj$userData;
36501
- var obj = _step.value;
36554
+ var obj = _step3.value;
36502
36555
  var objectType = (_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.objectType;
36503
36556
  var objectId = obj.uuid;
36504
36557
  var success = false;
@@ -36525,9 +36578,9 @@ var CentralPlantInternals = /*#__PURE__*/function () {
36525
36578
  }
36526
36579
  }
36527
36580
  } catch (err) {
36528
- _iterator.e(err);
36581
+ _iterator3.e(err);
36529
36582
  } finally {
36530
- _iterator.f();
36583
+ _iterator3.f();
36531
36584
  }
36532
36585
  result.success = result.translatedCount === result.totalCount;
36533
36586
  if (result.success) {
@@ -36711,7 +36764,7 @@ var CentralPlantInternals = /*#__PURE__*/function () {
36711
36764
  }, {
36712
36765
  key: "addConnection",
36713
36766
  value: function addConnection(fromConnectorId, toConnectorId) {
36714
- var _this$centralPlant$sc4;
36767
+ var _this$centralPlant$sc4, _this$centralPlant$sc5;
36715
36768
  // Use centralized validation for connection parameters
36716
36769
  var existingConnections = ((_this$centralPlant$sc4 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc4 === void 0 || (_this$centralPlant$sc4 = _this$centralPlant$sc4.currentSceneData) === null || _this$centralPlant$sc4 === void 0 ? void 0 : _this$centralPlant$sc4.connections) || [];
36717
36770
  var validation = this.validator.validateConnectionParams(fromConnectorId, toConnectorId, existingConnections);
@@ -36719,6 +36772,17 @@ var CentralPlantInternals = /*#__PURE__*/function () {
36719
36772
  return false; // Validator already logged the error
36720
36773
  }
36721
36774
 
36775
+ // Validate flow direction compatibility
36776
+ var sceneData = (_this$centralPlant$sc5 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc5 === void 0 ? void 0 : _this$centralPlant$sc5.currentSceneData;
36777
+ if (sceneData) {
36778
+ var fromFlow = _getConnectorFlow(sceneData, fromConnectorId);
36779
+ var toFlow = _getConnectorFlow(sceneData, toConnectorId);
36780
+ if (!_areFlowsCompatible$1(fromFlow, toFlow)) {
36781
+ console.error("\u274C addConnection(): Incompatible flow directions \u2014 '".concat(fromConnectorId, "' is '").concat(fromFlow, "' and '").concat(toConnectorId, "' is '").concat(toFlow, "'. ") + "'".concat(fromFlow, "' \u2192 '").concat(toFlow, "' connections are not allowed."));
36782
+ return false;
36783
+ }
36784
+ }
36785
+
36722
36786
  // Validate scene availability
36723
36787
  var sceneValidation = this.validator.validateSceneViewer(this.centralPlant.sceneViewer);
36724
36788
  if (!sceneValidation.isValid) {
@@ -36839,7 +36903,7 @@ var CentralPlantInternals = /*#__PURE__*/function () {
36839
36903
  }, {
36840
36904
  key: "addComponent",
36841
36905
  value: function addComponent(libraryId) {
36842
- var _this$centralPlant$sc5;
36906
+ var _this$centralPlant$sc6;
36843
36907
  var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
36844
36908
  // Use centralized validation for component addition parameters
36845
36909
  var existingIds = this.getComponentIds();
@@ -36849,7 +36913,7 @@ var CentralPlantInternals = /*#__PURE__*/function () {
36849
36913
  }
36850
36914
 
36851
36915
  // Validate scene availability
36852
- var sceneValidation = this.validator.validateSceneViewer(this.centralPlant.sceneViewer, (_this$centralPlant$sc5 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc5 === void 0 ? void 0 : _this$centralPlant$sc5.scene);
36916
+ var sceneValidation = this.validator.validateSceneViewer(this.centralPlant.sceneViewer, (_this$centralPlant$sc6 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc6 === void 0 ? void 0 : _this$centralPlant$sc6.scene);
36853
36917
  if (!sceneValidation.isValid) {
36854
36918
  return false;
36855
36919
  }
@@ -36868,7 +36932,7 @@ var CentralPlantInternals = /*#__PURE__*/function () {
36868
36932
  return false;
36869
36933
  }
36870
36934
  try {
36871
- var _componentData$childr, _componentData$childr2, _this$centralPlant$sc6, _componentData$childr3, _componentData$defaul;
36935
+ var _componentData$childr, _componentData$childr2, _this$centralPlant$sc7, _componentData$childr3, _componentData$defaul;
36872
36936
  // Generate a unique component ID if not provided
36873
36937
  var componentId = options.customId || this.generateUniqueComponentId(libraryId);
36874
36938
 
@@ -36980,7 +37044,7 @@ var CentralPlantInternals = /*#__PURE__*/function () {
36980
37044
  componentModel.updateMatrixWorld(true);
36981
37045
 
36982
37046
  // Check if component is underground and fix if needed (based on settings)
36983
- var checkUnderground = (_this$centralPlant$sc6 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc6 === void 0 || (_this$centralPlant$sc6 = _this$centralPlant$sc6.managers) === null || _this$centralPlant$sc6 === void 0 || (_this$centralPlant$sc6 = _this$centralPlant$sc6.settingsManager) === null || _this$centralPlant$sc6 === void 0 ? void 0 : _this$centralPlant$sc6.getSetting('scene', 'checkUnderground');
37047
+ var checkUnderground = (_this$centralPlant$sc7 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc7 === void 0 || (_this$centralPlant$sc7 = _this$centralPlant$sc7.managers) === null || _this$centralPlant$sc7 === void 0 || (_this$centralPlant$sc7 = _this$centralPlant$sc7.settingsManager) === null || _this$centralPlant$sc7 === void 0 ? void 0 : _this$centralPlant$sc7.getSetting('scene', 'checkUnderground');
36984
37048
  if (checkUnderground) {
36985
37049
  var wasFixed = this.fixUndergroundComponent(componentModel);
36986
37050
  if (wasFixed) {
@@ -37078,8 +37142,8 @@ var CentralPlantInternals = /*#__PURE__*/function () {
37078
37142
  // responds to tooltip-driven state changes immediately after drop.
37079
37143
  // (The scene-load path uses _processBehaviors instead, which runs on loadSceneData.)
37080
37144
  if ((_componentData$defaul = componentData.defaultBehaviors) !== null && _componentData$defaul !== void 0 && _componentData$defaul.length) {
37081
- var _this$centralPlant$sc7, _som$registerBehavior;
37082
- var som = (_this$centralPlant$sc7 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc7 === void 0 ? void 0 : _this$centralPlant$sc7.sceneOperationsManager;
37145
+ var _this$centralPlant$sc8, _som$registerBehavior;
37146
+ var som = (_this$centralPlant$sc8 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc8 === void 0 ? void 0 : _this$centralPlant$sc8.sceneOperationsManager;
37083
37147
  som === null || som === void 0 || (_som$registerBehavior = som.registerBehaviorsForComponentInstance) === null || _som$registerBehavior === void 0 || _som$registerBehavior.call(som, componentData, componentId);
37084
37148
  }
37085
37149
 
@@ -37141,9 +37205,9 @@ var CentralPlantInternals = /*#__PURE__*/function () {
37141
37205
  }, {
37142
37206
  key: "deleteComponent",
37143
37207
  value: function deleteComponent(componentId) {
37144
- var _this$centralPlant$sc8;
37208
+ var _this$centralPlant$sc9;
37145
37209
  // Check if component manager is available
37146
- var componentManager = (_this$centralPlant$sc8 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc8 === void 0 ? void 0 : _this$centralPlant$sc8.componentManager;
37210
+ var componentManager = (_this$centralPlant$sc9 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc9 === void 0 ? void 0 : _this$centralPlant$sc9.componentManager;
37147
37211
  if (!componentManager) {
37148
37212
  console.error('❌ deleteComponent(): Component manager not available');
37149
37213
  return false;
@@ -37218,8 +37282,8 @@ var CentralPlantInternals = /*#__PURE__*/function () {
37218
37282
  }
37219
37283
  var componentIds = [];
37220
37284
  this.centralPlant.sceneViewer.scene.traverse(function (child) {
37221
- var _child$userData;
37222
- if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'component') {
37285
+ var _child$userData2;
37286
+ if (((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType) === 'component') {
37223
37287
  componentIds.push(child.uuid || child.userData.originalUuid);
37224
37288
  }
37225
37289
  });
@@ -37228,6 +37292,22 @@ var CentralPlantInternals = /*#__PURE__*/function () {
37228
37292
  }]);
37229
37293
  }();
37230
37294
 
37295
+ // ─────────────────────────────────────────────────────────────────────────────
37296
+ // Flow-direction compatibility helper (module-level, no class dependency)
37297
+ // ─────────────────────────────────────────────────────────────────────────────
37298
+
37299
+ /**
37300
+ * Returns true if the two flow directions are compatible for a connection.
37301
+ * @param {string} fromFlow - 'in' | 'out' | 'bi'
37302
+ * @param {string} toFlow - 'in' | 'out' | 'bi'
37303
+ * @returns {boolean}
37304
+ */
37305
+ function _areFlowsCompatible(fromFlow, toFlow) {
37306
+ if (fromFlow === 'bi' || toFlow === 'bi') return true;
37307
+ // in ↔ out are compatible; in → in and out → out are not
37308
+ return fromFlow !== toFlow;
37309
+ }
37310
+
37231
37311
  /**
37232
37312
  * CentralPlant class that manages all scene utility instances and provides public API
37233
37313
  *
@@ -37238,7 +37318,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
37238
37318
  * Initialize the CentralPlant manager
37239
37319
  *
37240
37320
  * @constructor
37241
- * @version 0.2.5
37321
+ * @version 0.2.8
37242
37322
  * @updated 2025-10-22
37243
37323
  *
37244
37324
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -38171,6 +38251,107 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
38171
38251
  return availableConnectorIds;
38172
38252
  }
38173
38253
 
38254
+ /**
38255
+ * Get available connectors with their flow direction metadata.
38256
+ * Same filtering logic as getAvailableConnections() but returns objects instead of strings.
38257
+ * @returns {Array<{id: string, flow: string}>} Array of connector info objects.
38258
+ * flow is one of 'in', 'out', 'bi'. Defaults to 'bi' if not set in userData.
38259
+ * @example
38260
+ * const infos = centralPlant.getAvailableConnectionsInfo()
38261
+ * // [{ id: 'PUMP-1-CONNECTOR-1', flow: 'out' }, ...]
38262
+ */
38263
+ }, {
38264
+ key: "getAvailableConnectionsInfo",
38265
+ value: function getAvailableConnectionsInfo() {
38266
+ if (!this.sceneViewer || !this.sceneViewer.currentSceneData) {
38267
+ console.warn('⚠️ getAvailableConnectionsInfo(): Scene viewer or current scene data not available');
38268
+ return [];
38269
+ }
38270
+ var sceneData = this.sceneViewer.currentSceneData;
38271
+ if (!sceneData.scene || !sceneData.scene.children) {
38272
+ console.warn('⚠️ getAvailableConnectionsInfo(): Invalid scene data structure');
38273
+ return [];
38274
+ }
38275
+ var allConnectorInfos = [];
38276
+ sceneData.scene.children.forEach(function (component) {
38277
+ if (component.children && Array.isArray(component.children)) {
38278
+ component.children.forEach(function (child) {
38279
+ if (child.userData && child.userData.objectType === 'connector' && child.uuid) {
38280
+ allConnectorInfos.push({
38281
+ id: child.uuid,
38282
+ flow: child.userData.flow || 'bi'
38283
+ });
38284
+ }
38285
+ });
38286
+ }
38287
+ });
38288
+ var existingConnections = this.getConnections();
38289
+ var usedConnectorIds = new Set();
38290
+ existingConnections.forEach(function (connection) {
38291
+ if (connection.from) usedConnectorIds.add(connection.from);
38292
+ if (connection.to) usedConnectorIds.add(connection.to);
38293
+ });
38294
+ return allConnectorInfos.filter(function (info) {
38295
+ return !usedConnectorIds.has(info.id);
38296
+ });
38297
+ }
38298
+
38299
+ /**
38300
+ * Validate all connections in the current scene for flow direction compatibility.
38301
+ * @returns {{ valid: Array<Object>, invalid: Array<{connection: Object, reason: string}> }}
38302
+ * @example
38303
+ * const result = centralPlant.validateConnections()
38304
+ * result.invalid.forEach(({ connection, reason }) => console.warn(reason, connection))
38305
+ */
38306
+ }, {
38307
+ key: "validateConnections",
38308
+ value: function validateConnections() {
38309
+ if (!this.sceneViewer || !this.sceneViewer.currentSceneData) {
38310
+ console.warn('⚠️ validateConnections(): Scene viewer or current scene data not available');
38311
+ return {
38312
+ valid: [],
38313
+ invalid: []
38314
+ };
38315
+ }
38316
+ var connections = this.getConnections();
38317
+ var sceneData = this.sceneViewer.currentSceneData;
38318
+
38319
+ // Build lookup map: connectorId → flow
38320
+ var flowMap = {};
38321
+ var scene = sceneData.scene || {};
38322
+ var children = scene.children || [];
38323
+ children.forEach(function (component) {
38324
+ if (component.children && Array.isArray(component.children)) {
38325
+ component.children.forEach(function (child) {
38326
+ if (child.userData && child.userData.objectType === 'connector' && child.uuid) {
38327
+ flowMap[child.uuid] = child.userData.flow || 'bi';
38328
+ }
38329
+ });
38330
+ }
38331
+ });
38332
+ var valid = [];
38333
+ var invalid = [];
38334
+ connections.forEach(function (connection) {
38335
+ var fromFlow = flowMap[connection.from] || 'bi';
38336
+ var toFlow = flowMap[connection.to] || 'bi';
38337
+ if (_areFlowsCompatible(fromFlow, toFlow)) {
38338
+ valid.push(connection);
38339
+ } else {
38340
+ var reason = "Incompatible flow directions: connector '".concat(connection.from, "' is '").concat(fromFlow, "' and connector '").concat(connection.to, "' is '").concat(toFlow, "' \u2014 ").concat(fromFlow, " \u2192 ").concat(toFlow, " is not allowed");
38341
+ console.warn("\u26A0\uFE0F validateConnections(): ".concat(reason));
38342
+ invalid.push({
38343
+ connection: connection,
38344
+ reason: reason
38345
+ });
38346
+ }
38347
+ });
38348
+ console.log("\u2705 validateConnections(): ".concat(valid.length, " valid, ").concat(invalid.length, " invalid connections"));
38349
+ return {
38350
+ valid: valid,
38351
+ invalid: invalid
38352
+ };
38353
+ }
38354
+
38174
38355
  // ─────────────────────────────────────────────────────────────────────────
38175
38356
  // BEHAVIORS API
38176
38357
  // ─────────────────────────────────────────────────────────────────────────
@@ -9,6 +9,22 @@ var DisposalUtilities = require('../utils/DisposalUtilities.js');
9
9
  var centralPlantInternals = require('./centralPlantInternals.js');
10
10
  require('../rendering/modelPreloader.js');
11
11
 
12
+ // ─────────────────────────────────────────────────────────────────────────────
13
+ // Flow-direction compatibility helper (module-level, no class dependency)
14
+ // ─────────────────────────────────────────────────────────────────────────────
15
+
16
+ /**
17
+ * Returns true if the two flow directions are compatible for a connection.
18
+ * @param {string} fromFlow - 'in' | 'out' | 'bi'
19
+ * @param {string} toFlow - 'in' | 'out' | 'bi'
20
+ * @returns {boolean}
21
+ */
22
+ function _areFlowsCompatible(fromFlow, toFlow) {
23
+ if (fromFlow === 'bi' || toFlow === 'bi') return true;
24
+ // in ↔ out are compatible; in → in and out → out are not
25
+ return fromFlow !== toFlow;
26
+ }
27
+
12
28
  /**
13
29
  * CentralPlant class that manages all scene utility instances and provides public API
14
30
  *
@@ -19,7 +35,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
19
35
  * Initialize the CentralPlant manager
20
36
  *
21
37
  * @constructor
22
- * @version 0.2.5
38
+ * @version 0.2.8
23
39
  * @updated 2025-10-22
24
40
  *
25
41
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -952,6 +968,107 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
952
968
  return availableConnectorIds;
953
969
  }
954
970
 
971
+ /**
972
+ * Get available connectors with their flow direction metadata.
973
+ * Same filtering logic as getAvailableConnections() but returns objects instead of strings.
974
+ * @returns {Array<{id: string, flow: string}>} Array of connector info objects.
975
+ * flow is one of 'in', 'out', 'bi'. Defaults to 'bi' if not set in userData.
976
+ * @example
977
+ * const infos = centralPlant.getAvailableConnectionsInfo()
978
+ * // [{ id: 'PUMP-1-CONNECTOR-1', flow: 'out' }, ...]
979
+ */
980
+ }, {
981
+ key: "getAvailableConnectionsInfo",
982
+ value: function getAvailableConnectionsInfo() {
983
+ if (!this.sceneViewer || !this.sceneViewer.currentSceneData) {
984
+ console.warn('⚠️ getAvailableConnectionsInfo(): Scene viewer or current scene data not available');
985
+ return [];
986
+ }
987
+ var sceneData = this.sceneViewer.currentSceneData;
988
+ if (!sceneData.scene || !sceneData.scene.children) {
989
+ console.warn('⚠️ getAvailableConnectionsInfo(): Invalid scene data structure');
990
+ return [];
991
+ }
992
+ var allConnectorInfos = [];
993
+ sceneData.scene.children.forEach(function (component) {
994
+ if (component.children && Array.isArray(component.children)) {
995
+ component.children.forEach(function (child) {
996
+ if (child.userData && child.userData.objectType === 'connector' && child.uuid) {
997
+ allConnectorInfos.push({
998
+ id: child.uuid,
999
+ flow: child.userData.flow || 'bi'
1000
+ });
1001
+ }
1002
+ });
1003
+ }
1004
+ });
1005
+ var existingConnections = this.getConnections();
1006
+ var usedConnectorIds = new Set();
1007
+ existingConnections.forEach(function (connection) {
1008
+ if (connection.from) usedConnectorIds.add(connection.from);
1009
+ if (connection.to) usedConnectorIds.add(connection.to);
1010
+ });
1011
+ return allConnectorInfos.filter(function (info) {
1012
+ return !usedConnectorIds.has(info.id);
1013
+ });
1014
+ }
1015
+
1016
+ /**
1017
+ * Validate all connections in the current scene for flow direction compatibility.
1018
+ * @returns {{ valid: Array<Object>, invalid: Array<{connection: Object, reason: string}> }}
1019
+ * @example
1020
+ * const result = centralPlant.validateConnections()
1021
+ * result.invalid.forEach(({ connection, reason }) => console.warn(reason, connection))
1022
+ */
1023
+ }, {
1024
+ key: "validateConnections",
1025
+ value: function validateConnections() {
1026
+ if (!this.sceneViewer || !this.sceneViewer.currentSceneData) {
1027
+ console.warn('⚠️ validateConnections(): Scene viewer or current scene data not available');
1028
+ return {
1029
+ valid: [],
1030
+ invalid: []
1031
+ };
1032
+ }
1033
+ var connections = this.getConnections();
1034
+ var sceneData = this.sceneViewer.currentSceneData;
1035
+
1036
+ // Build lookup map: connectorId → flow
1037
+ var flowMap = {};
1038
+ var scene = sceneData.scene || {};
1039
+ var children = scene.children || [];
1040
+ children.forEach(function (component) {
1041
+ if (component.children && Array.isArray(component.children)) {
1042
+ component.children.forEach(function (child) {
1043
+ if (child.userData && child.userData.objectType === 'connector' && child.uuid) {
1044
+ flowMap[child.uuid] = child.userData.flow || 'bi';
1045
+ }
1046
+ });
1047
+ }
1048
+ });
1049
+ var valid = [];
1050
+ var invalid = [];
1051
+ connections.forEach(function (connection) {
1052
+ var fromFlow = flowMap[connection.from] || 'bi';
1053
+ var toFlow = flowMap[connection.to] || 'bi';
1054
+ if (_areFlowsCompatible(fromFlow, toFlow)) {
1055
+ valid.push(connection);
1056
+ } else {
1057
+ var reason = "Incompatible flow directions: connector '".concat(connection.from, "' is '").concat(fromFlow, "' and connector '").concat(connection.to, "' is '").concat(toFlow, "' \u2014 ").concat(fromFlow, " \u2192 ").concat(toFlow, " is not allowed");
1058
+ console.warn("\u26A0\uFE0F validateConnections(): ".concat(reason));
1059
+ invalid.push({
1060
+ connection: connection,
1061
+ reason: reason
1062
+ });
1063
+ }
1064
+ });
1065
+ console.log("\u2705 validateConnections(): ".concat(valid.length, " valid, ").concat(invalid.length, " invalid connections"));
1066
+ return {
1067
+ valid: valid,
1068
+ invalid: invalid
1069
+ };
1070
+ }
1071
+
955
1072
  // ─────────────────────────────────────────────────────────────────────────
956
1073
  // BEHAVIORS API
957
1074
  // ─────────────────────────────────────────────────────────────────────────
@@ -50,6 +50,59 @@ function _interopNamespace(e) {
50
50
 
51
51
  var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
52
52
 
53
+ // ─────────────────────────────────────────────────────────────────────────────
54
+ // Flow-direction helpers (module-level)
55
+ // ─────────────────────────────────────────────────────────────────────────────
56
+
57
+ /**
58
+ * Returns the flow direction of a connector from the current scene data.
59
+ * @param {Object} sceneData - currentSceneData object
60
+ * @param {string} connectorId
61
+ * @returns {'in'|'out'|'bi'} Defaults to 'bi' if not set.
62
+ */
63
+ function _getConnectorFlow(sceneData, connectorId) {
64
+ var _sceneData$scene;
65
+ var children = (sceneData === null || sceneData === void 0 || (_sceneData$scene = sceneData.scene) === null || _sceneData$scene === void 0 ? void 0 : _sceneData$scene.children) || [];
66
+ var _iterator = _rollupPluginBabelHelpers.createForOfIteratorHelper(children),
67
+ _step;
68
+ try {
69
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
70
+ var component = _step.value;
71
+ var _iterator2 = _rollupPluginBabelHelpers.createForOfIteratorHelper(component.children || []),
72
+ _step2;
73
+ try {
74
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
75
+ var _child$userData;
76
+ var child = _step2.value;
77
+ if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'connector' && child.uuid === connectorId) {
78
+ return child.userData.flow || 'bi';
79
+ }
80
+ }
81
+ } catch (err) {
82
+ _iterator2.e(err);
83
+ } finally {
84
+ _iterator2.f();
85
+ }
86
+ }
87
+ } catch (err) {
88
+ _iterator.e(err);
89
+ } finally {
90
+ _iterator.f();
91
+ }
92
+ return 'bi';
93
+ }
94
+
95
+ /**
96
+ * Returns true if fromFlow → toFlow is a valid connection.
97
+ * @param {'in'|'out'|'bi'} fromFlow
98
+ * @param {'in'|'out'|'bi'} toFlow
99
+ * @returns {boolean}
100
+ */
101
+ function _areFlowsCompatible(fromFlow, toFlow) {
102
+ if (fromFlow === 'bi' || toFlow === 'bi') return true;
103
+ return fromFlow !== toFlow;
104
+ }
105
+
53
106
  /**
54
107
  * CentralPlantInternals class containing internal methods and functionality
55
108
  */
@@ -501,12 +554,12 @@ var CentralPlantInternals = /*#__PURE__*/function () {
501
554
  console.log("\uD83D\uDD27 Translating ".concat(selectedObjects.length, " selected object(s) on ").concat(axis, " axis by ").concat(value));
502
555
 
503
556
  // Translate each selected object using the appropriate method
504
- var _iterator = _rollupPluginBabelHelpers.createForOfIteratorHelper(selectedObjects),
505
- _step;
557
+ var _iterator3 = _rollupPluginBabelHelpers.createForOfIteratorHelper(selectedObjects),
558
+ _step3;
506
559
  try {
507
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
560
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
508
561
  var _obj$userData;
509
- var obj = _step.value;
562
+ var obj = _step3.value;
510
563
  var objectType = (_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.objectType;
511
564
  var objectId = obj.uuid;
512
565
  var success = false;
@@ -533,9 +586,9 @@ var CentralPlantInternals = /*#__PURE__*/function () {
533
586
  }
534
587
  }
535
588
  } catch (err) {
536
- _iterator.e(err);
589
+ _iterator3.e(err);
537
590
  } finally {
538
- _iterator.f();
591
+ _iterator3.f();
539
592
  }
540
593
  result.success = result.translatedCount === result.totalCount;
541
594
  if (result.success) {
@@ -719,7 +772,7 @@ var CentralPlantInternals = /*#__PURE__*/function () {
719
772
  }, {
720
773
  key: "addConnection",
721
774
  value: function addConnection(fromConnectorId, toConnectorId) {
722
- var _this$centralPlant$sc4;
775
+ var _this$centralPlant$sc4, _this$centralPlant$sc5;
723
776
  // Use centralized validation for connection parameters
724
777
  var existingConnections = ((_this$centralPlant$sc4 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc4 === void 0 || (_this$centralPlant$sc4 = _this$centralPlant$sc4.currentSceneData) === null || _this$centralPlant$sc4 === void 0 ? void 0 : _this$centralPlant$sc4.connections) || [];
725
778
  var validation = this.validator.validateConnectionParams(fromConnectorId, toConnectorId, existingConnections);
@@ -727,6 +780,17 @@ var CentralPlantInternals = /*#__PURE__*/function () {
727
780
  return false; // Validator already logged the error
728
781
  }
729
782
 
783
+ // Validate flow direction compatibility
784
+ var sceneData = (_this$centralPlant$sc5 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc5 === void 0 ? void 0 : _this$centralPlant$sc5.currentSceneData;
785
+ if (sceneData) {
786
+ var fromFlow = _getConnectorFlow(sceneData, fromConnectorId);
787
+ var toFlow = _getConnectorFlow(sceneData, toConnectorId);
788
+ if (!_areFlowsCompatible(fromFlow, toFlow)) {
789
+ console.error("\u274C addConnection(): Incompatible flow directions \u2014 '".concat(fromConnectorId, "' is '").concat(fromFlow, "' and '").concat(toConnectorId, "' is '").concat(toFlow, "'. ") + "'".concat(fromFlow, "' \u2192 '").concat(toFlow, "' connections are not allowed."));
790
+ return false;
791
+ }
792
+ }
793
+
730
794
  // Validate scene availability
731
795
  var sceneValidation = this.validator.validateSceneViewer(this.centralPlant.sceneViewer);
732
796
  if (!sceneValidation.isValid) {
@@ -847,7 +911,7 @@ var CentralPlantInternals = /*#__PURE__*/function () {
847
911
  }, {
848
912
  key: "addComponent",
849
913
  value: function addComponent(libraryId) {
850
- var _this$centralPlant$sc5;
914
+ var _this$centralPlant$sc6;
851
915
  var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
852
916
  // Use centralized validation for component addition parameters
853
917
  var existingIds = this.getComponentIds();
@@ -857,7 +921,7 @@ var CentralPlantInternals = /*#__PURE__*/function () {
857
921
  }
858
922
 
859
923
  // Validate scene availability
860
- var sceneValidation = this.validator.validateSceneViewer(this.centralPlant.sceneViewer, (_this$centralPlant$sc5 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc5 === void 0 ? void 0 : _this$centralPlant$sc5.scene);
924
+ var sceneValidation = this.validator.validateSceneViewer(this.centralPlant.sceneViewer, (_this$centralPlant$sc6 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc6 === void 0 ? void 0 : _this$centralPlant$sc6.scene);
861
925
  if (!sceneValidation.isValid) {
862
926
  return false;
863
927
  }
@@ -876,7 +940,7 @@ var CentralPlantInternals = /*#__PURE__*/function () {
876
940
  return false;
877
941
  }
878
942
  try {
879
- var _componentData$childr, _componentData$childr2, _this$centralPlant$sc6, _componentData$childr3, _componentData$defaul;
943
+ var _componentData$childr, _componentData$childr2, _this$centralPlant$sc7, _componentData$childr3, _componentData$defaul;
880
944
  // Generate a unique component ID if not provided
881
945
  var componentId = options.customId || this.generateUniqueComponentId(libraryId);
882
946
 
@@ -988,7 +1052,7 @@ var CentralPlantInternals = /*#__PURE__*/function () {
988
1052
  componentModel.updateMatrixWorld(true);
989
1053
 
990
1054
  // Check if component is underground and fix if needed (based on settings)
991
- var checkUnderground = (_this$centralPlant$sc6 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc6 === void 0 || (_this$centralPlant$sc6 = _this$centralPlant$sc6.managers) === null || _this$centralPlant$sc6 === void 0 || (_this$centralPlant$sc6 = _this$centralPlant$sc6.settingsManager) === null || _this$centralPlant$sc6 === void 0 ? void 0 : _this$centralPlant$sc6.getSetting('scene', 'checkUnderground');
1055
+ var checkUnderground = (_this$centralPlant$sc7 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc7 === void 0 || (_this$centralPlant$sc7 = _this$centralPlant$sc7.managers) === null || _this$centralPlant$sc7 === void 0 || (_this$centralPlant$sc7 = _this$centralPlant$sc7.settingsManager) === null || _this$centralPlant$sc7 === void 0 ? void 0 : _this$centralPlant$sc7.getSetting('scene', 'checkUnderground');
992
1056
  if (checkUnderground) {
993
1057
  var wasFixed = this.fixUndergroundComponent(componentModel);
994
1058
  if (wasFixed) {
@@ -1086,8 +1150,8 @@ var CentralPlantInternals = /*#__PURE__*/function () {
1086
1150
  // responds to tooltip-driven state changes immediately after drop.
1087
1151
  // (The scene-load path uses _processBehaviors instead, which runs on loadSceneData.)
1088
1152
  if ((_componentData$defaul = componentData.defaultBehaviors) !== null && _componentData$defaul !== void 0 && _componentData$defaul.length) {
1089
- var _this$centralPlant$sc7, _som$registerBehavior;
1090
- var som = (_this$centralPlant$sc7 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc7 === void 0 ? void 0 : _this$centralPlant$sc7.sceneOperationsManager;
1153
+ var _this$centralPlant$sc8, _som$registerBehavior;
1154
+ var som = (_this$centralPlant$sc8 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc8 === void 0 ? void 0 : _this$centralPlant$sc8.sceneOperationsManager;
1091
1155
  som === null || som === void 0 || (_som$registerBehavior = som.registerBehaviorsForComponentInstance) === null || _som$registerBehavior === void 0 || _som$registerBehavior.call(som, componentData, componentId);
1092
1156
  }
1093
1157
 
@@ -1149,9 +1213,9 @@ var CentralPlantInternals = /*#__PURE__*/function () {
1149
1213
  }, {
1150
1214
  key: "deleteComponent",
1151
1215
  value: function deleteComponent(componentId) {
1152
- var _this$centralPlant$sc8;
1216
+ var _this$centralPlant$sc9;
1153
1217
  // Check if component manager is available
1154
- var componentManager = (_this$centralPlant$sc8 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc8 === void 0 ? void 0 : _this$centralPlant$sc8.componentManager;
1218
+ var componentManager = (_this$centralPlant$sc9 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc9 === void 0 ? void 0 : _this$centralPlant$sc9.componentManager;
1155
1219
  if (!componentManager) {
1156
1220
  console.error('❌ deleteComponent(): Component manager not available');
1157
1221
  return false;
@@ -1226,8 +1290,8 @@ var CentralPlantInternals = /*#__PURE__*/function () {
1226
1290
  }
1227
1291
  var componentIds = [];
1228
1292
  this.centralPlant.sceneViewer.scene.traverse(function (child) {
1229
- var _child$userData;
1230
- if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'component') {
1293
+ var _child$userData2;
1294
+ if (((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType) === 'component') {
1231
1295
  componentIds.push(child.uuid || child.userData.originalUuid);
1232
1296
  }
1233
1297
  });
@@ -5,6 +5,22 @@ import { DisposalUtilities } from '../utils/DisposalUtilities.js';
5
5
  import { CentralPlantInternals } from './centralPlantInternals.js';
6
6
  import '../rendering/modelPreloader.js';
7
7
 
8
+ // ─────────────────────────────────────────────────────────────────────────────
9
+ // Flow-direction compatibility helper (module-level, no class dependency)
10
+ // ─────────────────────────────────────────────────────────────────────────────
11
+
12
+ /**
13
+ * Returns true if the two flow directions are compatible for a connection.
14
+ * @param {string} fromFlow - 'in' | 'out' | 'bi'
15
+ * @param {string} toFlow - 'in' | 'out' | 'bi'
16
+ * @returns {boolean}
17
+ */
18
+ function _areFlowsCompatible(fromFlow, toFlow) {
19
+ if (fromFlow === 'bi' || toFlow === 'bi') return true;
20
+ // in ↔ out are compatible; in → in and out → out are not
21
+ return fromFlow !== toFlow;
22
+ }
23
+
8
24
  /**
9
25
  * CentralPlant class that manages all scene utility instances and provides public API
10
26
  *
@@ -15,7 +31,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
15
31
  * Initialize the CentralPlant manager
16
32
  *
17
33
  * @constructor
18
- * @version 0.2.5
34
+ * @version 0.2.8
19
35
  * @updated 2025-10-22
20
36
  *
21
37
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -948,6 +964,107 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
948
964
  return availableConnectorIds;
949
965
  }
950
966
 
967
+ /**
968
+ * Get available connectors with their flow direction metadata.
969
+ * Same filtering logic as getAvailableConnections() but returns objects instead of strings.
970
+ * @returns {Array<{id: string, flow: string}>} Array of connector info objects.
971
+ * flow is one of 'in', 'out', 'bi'. Defaults to 'bi' if not set in userData.
972
+ * @example
973
+ * const infos = centralPlant.getAvailableConnectionsInfo()
974
+ * // [{ id: 'PUMP-1-CONNECTOR-1', flow: 'out' }, ...]
975
+ */
976
+ }, {
977
+ key: "getAvailableConnectionsInfo",
978
+ value: function getAvailableConnectionsInfo() {
979
+ if (!this.sceneViewer || !this.sceneViewer.currentSceneData) {
980
+ console.warn('⚠️ getAvailableConnectionsInfo(): Scene viewer or current scene data not available');
981
+ return [];
982
+ }
983
+ var sceneData = this.sceneViewer.currentSceneData;
984
+ if (!sceneData.scene || !sceneData.scene.children) {
985
+ console.warn('⚠️ getAvailableConnectionsInfo(): Invalid scene data structure');
986
+ return [];
987
+ }
988
+ var allConnectorInfos = [];
989
+ sceneData.scene.children.forEach(function (component) {
990
+ if (component.children && Array.isArray(component.children)) {
991
+ component.children.forEach(function (child) {
992
+ if (child.userData && child.userData.objectType === 'connector' && child.uuid) {
993
+ allConnectorInfos.push({
994
+ id: child.uuid,
995
+ flow: child.userData.flow || 'bi'
996
+ });
997
+ }
998
+ });
999
+ }
1000
+ });
1001
+ var existingConnections = this.getConnections();
1002
+ var usedConnectorIds = new Set();
1003
+ existingConnections.forEach(function (connection) {
1004
+ if (connection.from) usedConnectorIds.add(connection.from);
1005
+ if (connection.to) usedConnectorIds.add(connection.to);
1006
+ });
1007
+ return allConnectorInfos.filter(function (info) {
1008
+ return !usedConnectorIds.has(info.id);
1009
+ });
1010
+ }
1011
+
1012
+ /**
1013
+ * Validate all connections in the current scene for flow direction compatibility.
1014
+ * @returns {{ valid: Array<Object>, invalid: Array<{connection: Object, reason: string}> }}
1015
+ * @example
1016
+ * const result = centralPlant.validateConnections()
1017
+ * result.invalid.forEach(({ connection, reason }) => console.warn(reason, connection))
1018
+ */
1019
+ }, {
1020
+ key: "validateConnections",
1021
+ value: function validateConnections() {
1022
+ if (!this.sceneViewer || !this.sceneViewer.currentSceneData) {
1023
+ console.warn('⚠️ validateConnections(): Scene viewer or current scene data not available');
1024
+ return {
1025
+ valid: [],
1026
+ invalid: []
1027
+ };
1028
+ }
1029
+ var connections = this.getConnections();
1030
+ var sceneData = this.sceneViewer.currentSceneData;
1031
+
1032
+ // Build lookup map: connectorId → flow
1033
+ var flowMap = {};
1034
+ var scene = sceneData.scene || {};
1035
+ var children = scene.children || [];
1036
+ children.forEach(function (component) {
1037
+ if (component.children && Array.isArray(component.children)) {
1038
+ component.children.forEach(function (child) {
1039
+ if (child.userData && child.userData.objectType === 'connector' && child.uuid) {
1040
+ flowMap[child.uuid] = child.userData.flow || 'bi';
1041
+ }
1042
+ });
1043
+ }
1044
+ });
1045
+ var valid = [];
1046
+ var invalid = [];
1047
+ connections.forEach(function (connection) {
1048
+ var fromFlow = flowMap[connection.from] || 'bi';
1049
+ var toFlow = flowMap[connection.to] || 'bi';
1050
+ if (_areFlowsCompatible(fromFlow, toFlow)) {
1051
+ valid.push(connection);
1052
+ } else {
1053
+ var reason = "Incompatible flow directions: connector '".concat(connection.from, "' is '").concat(fromFlow, "' and connector '").concat(connection.to, "' is '").concat(toFlow, "' \u2014 ").concat(fromFlow, " \u2192 ").concat(toFlow, " is not allowed");
1054
+ console.warn("\u26A0\uFE0F validateConnections(): ".concat(reason));
1055
+ invalid.push({
1056
+ connection: connection,
1057
+ reason: reason
1058
+ });
1059
+ }
1060
+ });
1061
+ console.log("\u2705 validateConnections(): ".concat(valid.length, " valid, ").concat(invalid.length, " invalid connections"));
1062
+ return {
1063
+ valid: valid,
1064
+ invalid: invalid
1065
+ };
1066
+ }
1067
+
951
1068
  // ─────────────────────────────────────────────────────────────────────────
952
1069
  // BEHAVIORS API
953
1070
  // ─────────────────────────────────────────────────────────────────────────
@@ -26,6 +26,59 @@ import { generateUuidFromName, getHardcodedUuid, findObjectByHardcodedUuid, gene
26
26
  import { attachIODevicesToComponent } from '../utils/ioDeviceUtils.js';
27
27
  import modelPreloader from '../rendering/modelPreloader.js';
28
28
 
29
+ // ─────────────────────────────────────────────────────────────────────────────
30
+ // Flow-direction helpers (module-level)
31
+ // ─────────────────────────────────────────────────────────────────────────────
32
+
33
+ /**
34
+ * Returns the flow direction of a connector from the current scene data.
35
+ * @param {Object} sceneData - currentSceneData object
36
+ * @param {string} connectorId
37
+ * @returns {'in'|'out'|'bi'} Defaults to 'bi' if not set.
38
+ */
39
+ function _getConnectorFlow(sceneData, connectorId) {
40
+ var _sceneData$scene;
41
+ var children = (sceneData === null || sceneData === void 0 || (_sceneData$scene = sceneData.scene) === null || _sceneData$scene === void 0 ? void 0 : _sceneData$scene.children) || [];
42
+ var _iterator = _createForOfIteratorHelper(children),
43
+ _step;
44
+ try {
45
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
46
+ var component = _step.value;
47
+ var _iterator2 = _createForOfIteratorHelper(component.children || []),
48
+ _step2;
49
+ try {
50
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
51
+ var _child$userData;
52
+ var child = _step2.value;
53
+ if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'connector' && child.uuid === connectorId) {
54
+ return child.userData.flow || 'bi';
55
+ }
56
+ }
57
+ } catch (err) {
58
+ _iterator2.e(err);
59
+ } finally {
60
+ _iterator2.f();
61
+ }
62
+ }
63
+ } catch (err) {
64
+ _iterator.e(err);
65
+ } finally {
66
+ _iterator.f();
67
+ }
68
+ return 'bi';
69
+ }
70
+
71
+ /**
72
+ * Returns true if fromFlow → toFlow is a valid connection.
73
+ * @param {'in'|'out'|'bi'} fromFlow
74
+ * @param {'in'|'out'|'bi'} toFlow
75
+ * @returns {boolean}
76
+ */
77
+ function _areFlowsCompatible(fromFlow, toFlow) {
78
+ if (fromFlow === 'bi' || toFlow === 'bi') return true;
79
+ return fromFlow !== toFlow;
80
+ }
81
+
29
82
  /**
30
83
  * CentralPlantInternals class containing internal methods and functionality
31
84
  */
@@ -477,12 +530,12 @@ var CentralPlantInternals = /*#__PURE__*/function () {
477
530
  console.log("\uD83D\uDD27 Translating ".concat(selectedObjects.length, " selected object(s) on ").concat(axis, " axis by ").concat(value));
478
531
 
479
532
  // Translate each selected object using the appropriate method
480
- var _iterator = _createForOfIteratorHelper(selectedObjects),
481
- _step;
533
+ var _iterator3 = _createForOfIteratorHelper(selectedObjects),
534
+ _step3;
482
535
  try {
483
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
536
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
484
537
  var _obj$userData;
485
- var obj = _step.value;
538
+ var obj = _step3.value;
486
539
  var objectType = (_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.objectType;
487
540
  var objectId = obj.uuid;
488
541
  var success = false;
@@ -509,9 +562,9 @@ var CentralPlantInternals = /*#__PURE__*/function () {
509
562
  }
510
563
  }
511
564
  } catch (err) {
512
- _iterator.e(err);
565
+ _iterator3.e(err);
513
566
  } finally {
514
- _iterator.f();
567
+ _iterator3.f();
515
568
  }
516
569
  result.success = result.translatedCount === result.totalCount;
517
570
  if (result.success) {
@@ -695,7 +748,7 @@ var CentralPlantInternals = /*#__PURE__*/function () {
695
748
  }, {
696
749
  key: "addConnection",
697
750
  value: function addConnection(fromConnectorId, toConnectorId) {
698
- var _this$centralPlant$sc4;
751
+ var _this$centralPlant$sc4, _this$centralPlant$sc5;
699
752
  // Use centralized validation for connection parameters
700
753
  var existingConnections = ((_this$centralPlant$sc4 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc4 === void 0 || (_this$centralPlant$sc4 = _this$centralPlant$sc4.currentSceneData) === null || _this$centralPlant$sc4 === void 0 ? void 0 : _this$centralPlant$sc4.connections) || [];
701
754
  var validation = this.validator.validateConnectionParams(fromConnectorId, toConnectorId, existingConnections);
@@ -703,6 +756,17 @@ var CentralPlantInternals = /*#__PURE__*/function () {
703
756
  return false; // Validator already logged the error
704
757
  }
705
758
 
759
+ // Validate flow direction compatibility
760
+ var sceneData = (_this$centralPlant$sc5 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc5 === void 0 ? void 0 : _this$centralPlant$sc5.currentSceneData;
761
+ if (sceneData) {
762
+ var fromFlow = _getConnectorFlow(sceneData, fromConnectorId);
763
+ var toFlow = _getConnectorFlow(sceneData, toConnectorId);
764
+ if (!_areFlowsCompatible(fromFlow, toFlow)) {
765
+ console.error("\u274C addConnection(): Incompatible flow directions \u2014 '".concat(fromConnectorId, "' is '").concat(fromFlow, "' and '").concat(toConnectorId, "' is '").concat(toFlow, "'. ") + "'".concat(fromFlow, "' \u2192 '").concat(toFlow, "' connections are not allowed."));
766
+ return false;
767
+ }
768
+ }
769
+
706
770
  // Validate scene availability
707
771
  var sceneValidation = this.validator.validateSceneViewer(this.centralPlant.sceneViewer);
708
772
  if (!sceneValidation.isValid) {
@@ -823,7 +887,7 @@ var CentralPlantInternals = /*#__PURE__*/function () {
823
887
  }, {
824
888
  key: "addComponent",
825
889
  value: function addComponent(libraryId) {
826
- var _this$centralPlant$sc5;
890
+ var _this$centralPlant$sc6;
827
891
  var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
828
892
  // Use centralized validation for component addition parameters
829
893
  var existingIds = this.getComponentIds();
@@ -833,7 +897,7 @@ var CentralPlantInternals = /*#__PURE__*/function () {
833
897
  }
834
898
 
835
899
  // Validate scene availability
836
- var sceneValidation = this.validator.validateSceneViewer(this.centralPlant.sceneViewer, (_this$centralPlant$sc5 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc5 === void 0 ? void 0 : _this$centralPlant$sc5.scene);
900
+ var sceneValidation = this.validator.validateSceneViewer(this.centralPlant.sceneViewer, (_this$centralPlant$sc6 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc6 === void 0 ? void 0 : _this$centralPlant$sc6.scene);
837
901
  if (!sceneValidation.isValid) {
838
902
  return false;
839
903
  }
@@ -852,7 +916,7 @@ var CentralPlantInternals = /*#__PURE__*/function () {
852
916
  return false;
853
917
  }
854
918
  try {
855
- var _componentData$childr, _componentData$childr2, _this$centralPlant$sc6, _componentData$childr3, _componentData$defaul;
919
+ var _componentData$childr, _componentData$childr2, _this$centralPlant$sc7, _componentData$childr3, _componentData$defaul;
856
920
  // Generate a unique component ID if not provided
857
921
  var componentId = options.customId || this.generateUniqueComponentId(libraryId);
858
922
 
@@ -964,7 +1028,7 @@ var CentralPlantInternals = /*#__PURE__*/function () {
964
1028
  componentModel.updateMatrixWorld(true);
965
1029
 
966
1030
  // Check if component is underground and fix if needed (based on settings)
967
- var checkUnderground = (_this$centralPlant$sc6 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc6 === void 0 || (_this$centralPlant$sc6 = _this$centralPlant$sc6.managers) === null || _this$centralPlant$sc6 === void 0 || (_this$centralPlant$sc6 = _this$centralPlant$sc6.settingsManager) === null || _this$centralPlant$sc6 === void 0 ? void 0 : _this$centralPlant$sc6.getSetting('scene', 'checkUnderground');
1031
+ var checkUnderground = (_this$centralPlant$sc7 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc7 === void 0 || (_this$centralPlant$sc7 = _this$centralPlant$sc7.managers) === null || _this$centralPlant$sc7 === void 0 || (_this$centralPlant$sc7 = _this$centralPlant$sc7.settingsManager) === null || _this$centralPlant$sc7 === void 0 ? void 0 : _this$centralPlant$sc7.getSetting('scene', 'checkUnderground');
968
1032
  if (checkUnderground) {
969
1033
  var wasFixed = this.fixUndergroundComponent(componentModel);
970
1034
  if (wasFixed) {
@@ -1062,8 +1126,8 @@ var CentralPlantInternals = /*#__PURE__*/function () {
1062
1126
  // responds to tooltip-driven state changes immediately after drop.
1063
1127
  // (The scene-load path uses _processBehaviors instead, which runs on loadSceneData.)
1064
1128
  if ((_componentData$defaul = componentData.defaultBehaviors) !== null && _componentData$defaul !== void 0 && _componentData$defaul.length) {
1065
- var _this$centralPlant$sc7, _som$registerBehavior;
1066
- var som = (_this$centralPlant$sc7 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc7 === void 0 ? void 0 : _this$centralPlant$sc7.sceneOperationsManager;
1129
+ var _this$centralPlant$sc8, _som$registerBehavior;
1130
+ var som = (_this$centralPlant$sc8 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc8 === void 0 ? void 0 : _this$centralPlant$sc8.sceneOperationsManager;
1067
1131
  som === null || som === void 0 || (_som$registerBehavior = som.registerBehaviorsForComponentInstance) === null || _som$registerBehavior === void 0 || _som$registerBehavior.call(som, componentData, componentId);
1068
1132
  }
1069
1133
 
@@ -1125,9 +1189,9 @@ var CentralPlantInternals = /*#__PURE__*/function () {
1125
1189
  }, {
1126
1190
  key: "deleteComponent",
1127
1191
  value: function deleteComponent(componentId) {
1128
- var _this$centralPlant$sc8;
1192
+ var _this$centralPlant$sc9;
1129
1193
  // Check if component manager is available
1130
- var componentManager = (_this$centralPlant$sc8 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc8 === void 0 ? void 0 : _this$centralPlant$sc8.componentManager;
1194
+ var componentManager = (_this$centralPlant$sc9 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc9 === void 0 ? void 0 : _this$centralPlant$sc9.componentManager;
1131
1195
  if (!componentManager) {
1132
1196
  console.error('❌ deleteComponent(): Component manager not available');
1133
1197
  return false;
@@ -1202,8 +1266,8 @@ var CentralPlantInternals = /*#__PURE__*/function () {
1202
1266
  }
1203
1267
  var componentIds = [];
1204
1268
  this.centralPlant.sceneViewer.scene.traverse(function (child) {
1205
- var _child$userData;
1206
- if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'component') {
1269
+ var _child$userData2;
1270
+ if (((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType) === 'component') {
1207
1271
  componentIds.push(child.uuid || child.userData.originalUuid);
1208
1272
  }
1209
1273
  });
package/dist/index.d.ts CHANGED
@@ -5,6 +5,25 @@
5
5
  * organized by their functional categories.
6
6
  */
7
7
 
8
+ // ─── Connector flow types ─────────────────────────────────────────────────────
9
+
10
+ /** The flow direction of a connector port. */
11
+ export type FlowDirection = 'in' | 'out' | 'bi'
12
+
13
+ /** Connector descriptor returned by getAvailableConnectionsInfo(). */
14
+ export interface ConnectorInfo {
15
+ /** The connector's UUID as it appears in the scene JSON. */
16
+ id: string
17
+ /** Flow direction. Defaults to 'bi' for connectors that do not declare one. */
18
+ flow: FlowDirection
19
+ }
20
+
21
+ /** Result of validateConnections(). */
22
+ export interface ConnectionValidationResult {
23
+ valid: Array<{ from: string; to: string }>
24
+ invalid: Array<{ connection: { from: string; to: string }; reason: string }>
25
+ }
26
+
8
27
  // ─── I/O Device types ────────────────────────────────────────────────────────
9
28
 
10
29
  export interface IoDeviceState {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2112-lab/central-plant",
3
- "version": "0.2.5",
3
+ "version": "0.2.8",
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",