@2112-lab/central-plant 0.3.47 → 0.3.48

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.
@@ -1,5 +1,6 @@
1
1
  import { createClass as _createClass, objectSpread2 as _objectSpread2, toConsumableArray as _toConsumableArray, typeof as _typeof, classCallCheck as _classCallCheck, asyncToGenerator as _asyncToGenerator, regenerator as _regenerator, createForOfIteratorHelper as _createForOfIteratorHelper } from '../../../_virtual/_rollupPluginBabelHelpers.js';
2
2
  import * as THREE from 'three';
3
+ import { getHardcodedUuid } from '../../utils/nameUtils.js';
3
4
  import { attachIODevicesToComponent } from '../../utils/ioDeviceUtils.js';
4
5
  import { registerBehaviorsForComponent } from '../../utils/behaviorRegistration.js';
5
6
  import modelPreloader from '../../rendering/modelPreloader.js';
@@ -138,36 +139,58 @@ var ModelManager = /*#__PURE__*/function () {
138
139
  * @param {THREE.Object3D} targetMesh - The mesh whose connectors to preserve
139
140
  * @param {string} parentUuid - The UUID of the parent component
140
141
  */
142
+ /**
143
+ * Identifies all connector objects within a model hierarchy and prepares them
144
+ * to be preserved when the model is replaced or modified.
145
+ * Uses traverse() to find connectors at any depth in the hierarchy.
146
+ * @param {THREE.Object3D} targetMesh - The root of the model to search
147
+ * @param {string} parentUuid - The UUID of the parent component
148
+ * @returns {Array<THREE.Object3D>} Array of cloned and prepared connector objects
149
+ */
141
150
  }, {
142
151
  key: "_preserveConnectorChildren",
143
152
  value: function _preserveConnectorChildren(targetMesh, parentUuid) {
144
153
  var _this = this;
145
154
  var connectorChildren = [];
146
- targetMesh.children.forEach(function (child, index) {
155
+ var connectorIndex = 0;
156
+
157
+ // Use traverse to find connectors at ANY depth in the hierarchy
158
+ targetMesh.traverse(function (child) {
147
159
  var _child$userData;
148
160
  var isConnectorGeometry = child.geometry && (child.geometry.uuid === 'CONNECTOR-GEO' || child.geometry.type === 'SphereGeometry' && child.geometry.parameters);
149
161
  var isConnectorByName = child.name && child.name.toLowerCase().includes('connector');
150
- // Also recognise connectors declared via userData (e.g. inline connectors from SAMPLE_1.json
151
- // whose geometry may be a fallback BufferGeometry rather than a SphereGeometry).
152
162
  var isConnectorByUserData = ((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'connector';
153
163
  if (isConnectorGeometry && isConnectorByName || isConnectorByUserData) {
164
+ connectorIndex++;
165
+
154
166
  // Ensure userData exists and has worldBoundingBox
155
167
  if (!child.userData) child.userData = {};
156
168
  if (!child.userData.worldBoundingBox) {
157
169
  child.userData.worldBoundingBox = _this._calculateWorldBoundingBox(child);
158
170
  }
159
171
 
160
- // Clone and preserve critical userData
172
+ // Clone the connector
161
173
  var clonedConnector = child.clone();
162
174
 
163
- // Generate proper connector UUID based on parent component UUID
164
- // Format: COMPONENT-UUID-CONNECTOR-INDEX (matching JSON import pattern)
165
- var connectorUuid = "".concat(parentUuid, "-CONNECTOR-").concat(index + 1);
175
+ // CRITICAL: Convert nested world position to root-relative position
176
+ // This ensures the connector stays in the same place when re-added as a direct child of targetMesh
177
+ var targetInverse = targetMesh.matrixWorld.clone().invert();
178
+ var worldPos = new THREE.Vector3();
179
+ child.getWorldPosition(worldPos);
180
+ clonedConnector.position.copy(worldPos.applyMatrix4(targetInverse));
181
+
182
+ // Get hardcoded UUID if it exists (e.g. from JSON mesh placeholder)
183
+ // This ensures compatibility with existing scene JSON naming conventions
184
+ var existingUuid = getHardcodedUuid(child);
185
+
186
+ // Generate fallback UUID only if existing one isn't already parent-prefixed
187
+ // Format: COMPONENT-UUID-CONNECTOR-INDEX (stable fallback)
188
+ var connectorUuid = existingUuid && existingUuid.includes(parentUuid) ? existingUuid : "".concat(parentUuid, "-CONNECTOR-").concat(connectorIndex);
166
189
  clonedConnector.uuid = connectorUuid;
167
190
  if (child.userData) {
168
191
  clonedConnector.userData = _objectSpread2({}, child.userData);
169
192
 
170
- // Deep copy critical data - handle both array [x,y,z] and object {x,y,z} formats
193
+ // Deep copy critical data
171
194
  if (child.userData.worldBoundingBox) {
172
195
  var wbb = child.userData.worldBoundingBox;
173
196
  clonedConnector.userData.worldBoundingBox = {
@@ -176,7 +199,6 @@ var ModelManager = /*#__PURE__*/function () {
176
199
  };
177
200
  }
178
201
  if (child.userData.direction) {
179
- // Handle both array [x,y,z] and object {x,y,z} formats
180
202
  if (Array.isArray(child.userData.direction)) {
181
203
  clonedConnector.userData.direction = _toConsumableArray(child.userData.direction);
182
204
  } else if (_typeof(child.userData.direction) === 'object') {
@@ -188,11 +210,12 @@ var ModelManager = /*#__PURE__*/function () {
188
210
 
189
211
  // Set originalUuid to match the actual uuid (maintains consistency)
190
212
  clonedConnector.userData.originalUuid = connectorUuid;
191
- clonedConnector.userData.objectType = child.userData.objectType || (child.name.toLowerCase().includes('connector') ? 'connector' : 'gateway');
213
+ clonedConnector.userData.objectType = child.userData.objectType || 'connector';
192
214
  }
193
215
  connectorChildren.push(clonedConnector);
194
216
  }
195
217
  });
218
+ console.log("\uD83D\uDEE1\uFE0F Preserved ".concat(connectorChildren.length, " connector(s) for component ").concat(parentUuid));
196
219
  return connectorChildren;
197
220
  }
198
221
 
@@ -1,5 +1,5 @@
1
1
  import { createClass as _createClass, classCallCheck as _classCallCheck, toConsumableArray as _toConsumableArray, objectSpread2 as _objectSpread2, asyncToGenerator as _asyncToGenerator, regenerator as _regenerator } from '../../../_virtual/_rollupPluginBabelHelpers.js';
2
- import 'three';
2
+ import * as THREE from 'three';
3
3
  import { GLTFExporter } from '../../../node_modules/three/examples/jsm/exporters/GLTFExporter.js';
4
4
  import { getHardcodedUuid } from '../../utils/nameUtils.js';
5
5
 
@@ -81,6 +81,11 @@ var SceneExportManager = /*#__PURE__*/function () {
81
81
  // Helper function to convert Three.js object to minimal JSON format
82
82
  var convertObjectToJson = function convertObjectToJson(threeObject) {
83
83
  var _threeObject$name, _threeObject$userData, _threeObject$userData2, _threeObject$userData3, _threeObject$userData4, _threeObject$userData5, _threeObject$userData6, _threeObject$userData7, _threeObject$userData8, _threeObject$userData9, _threeObject$userData0, _threeObject$userData1, _threeObject$userData10;
84
+ // Ensure world matrices are updated for this subtree before exporting
85
+ if (threeObject.updateMatrixWorld) {
86
+ threeObject.updateMatrixWorld(true);
87
+ }
88
+
84
89
  // Skip certain objects that shouldn't be exported
85
90
  if (!threeObject || (_threeObject$name = threeObject.name) !== null && _threeObject$name !== void 0 && _threeObject$name.includes('Polyline') ||
86
91
  // Skip pipe paths
@@ -238,19 +243,23 @@ var SceneExportManager = /*#__PURE__*/function () {
238
243
  }
239
244
  });
240
245
  } else if (((_threeObject$userData11 = threeObject.userData) === null || _threeObject$userData11 === void 0 ? void 0 : _threeObject$userData11.objectType) === 'component') {
241
- // For components, only export connectors that have objectType='connector'
242
- // Standard dictionary-injected connectors should be exported so connections work on re-import
243
- threeObject.children.forEach(function (child) {
246
+ // For components, export all connectors (including deep children within GLFs)
247
+ threeObject.traverse(function (child) {
244
248
  var _child$userData2;
245
249
  if (((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType) === 'connector') {
250
+ // Calculate position relative to component root
251
+ var componentMatrixWorldInverse = threeObject.matrixWorld.clone().invert();
252
+ var childWorldPos = new THREE.Vector3();
253
+ child.getWorldPosition(childWorldPos);
254
+ var relativePos = childWorldPos.applyMatrix4(componentMatrixWorldInverse);
246
255
  exportableChildren.push({
247
256
  uuid: child.uuid,
248
257
  name: child.name,
249
258
  type: 'Mesh',
250
259
  position: {
251
- x: roundIfClose(child.position.x),
252
- y: roundIfClose(child.position.y),
253
- z: roundIfClose(child.position.z)
260
+ x: roundIfClose(relativePos.x),
261
+ y: roundIfClose(relativePos.y),
262
+ z: roundIfClose(relativePos.z)
254
263
  },
255
264
  userData: _objectSpread2({}, child.userData)
256
265
  });
@@ -1324,24 +1324,29 @@ var SceneOperationsManager = /*#__PURE__*/function () {
1324
1324
  children: [] // Initialize children array
1325
1325
  };
1326
1326
 
1327
- // Collect children that are connectors
1328
- if (componentModel.children) {
1329
- componentModel.children.forEach(function (child) {
1330
- if (child.userData && child.userData.objectType === 'connector') {
1331
- componentSceneData.children.push({
1332
- uuid: child.uuid,
1333
- name: child.name,
1334
- type: 'Mesh',
1335
- position: {
1336
- x: child.position.x,
1337
- y: child.position.y,
1338
- z: child.position.z
1339
- },
1340
- userData: _objectSpread2({}, child.userData)
1341
- });
1342
- }
1343
- });
1344
- }
1327
+ // Collect children that are connectors (using traverse to find deep children in models)
1328
+ componentModel.traverse(function (child) {
1329
+ if (child.userData && child.userData.objectType === 'connector') {
1330
+ // Calculate position relative to component root
1331
+ // This ensures that when we re-add it as a direct child of the component,
1332
+ // it maintains the same world position relative to the component.
1333
+ var componentMatrixWorldInverse = componentModel.matrixWorld.clone().invert();
1334
+ var childWorldPos = new THREE.Vector3();
1335
+ child.getWorldPosition(childWorldPos);
1336
+ var relativePos = childWorldPos.applyMatrix4(componentMatrixWorldInverse);
1337
+ componentSceneData.children.push({
1338
+ uuid: child.uuid,
1339
+ name: child.name,
1340
+ type: 'Mesh',
1341
+ position: {
1342
+ x: relativePos.x,
1343
+ y: relativePos.y,
1344
+ z: relativePos.z
1345
+ },
1346
+ userData: _objectSpread2({}, child.userData)
1347
+ });
1348
+ }
1349
+ });
1345
1350
 
1346
1351
  // Add the component to the scene data
1347
1352
  if (!currentSceneData.scene.children) {
@@ -155,41 +155,36 @@ function computeFilteredBoundingBox(object) {
155
155
  */
156
156
  function computeIODeviceBoundingBoxes(componentObject) {
157
157
  var results = [];
158
- var _iterator = _createForOfIteratorHelper(componentObject.children),
159
- _step;
160
- try {
161
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
162
- var _child$userData;
163
- var child = _step.value;
164
- if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) !== 'io-device') continue;
165
- var bbox = new THREE.Box3().setFromObject(child);
166
- if (!bbox.isEmpty()) {
167
- results.push({
168
- uuid: child.uuid,
169
- userData: {
170
- objectType: 'io-device',
171
- deviceId: child.userData.deviceId || null,
172
- attachmentId: child.userData.attachmentId || null,
173
- parentComponentId: child.userData.parentComponentId || componentObject.uuid
174
- },
175
- worldBoundingBox: {
176
- min: [bbox.min.x, bbox.min.y, bbox.min.z],
177
- max: [bbox.max.x, bbox.max.y, bbox.max.z]
178
- }
179
- });
180
- }
158
+ componentObject.traverse(function (child) {
159
+ var _child$userData;
160
+ if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) !== 'io-device') return;
161
+ var bbox = new THREE.Box3().setFromObject(child);
162
+ var worldPos = new THREE.Vector3();
163
+ child.getWorldPosition(worldPos);
164
+ if (!bbox.isEmpty()) {
165
+ results.push({
166
+ uuid: child.uuid,
167
+ userData: {
168
+ objectType: 'io-device',
169
+ deviceId: child.userData.deviceId || null,
170
+ attachmentId: child.userData.attachmentId || null,
171
+ parentComponentId: child.userData.parentComponentId || componentObject.uuid,
172
+ // Sync position for pathfinder
173
+ position: [worldPos.x, worldPos.y, worldPos.z]
174
+ },
175
+ worldBoundingBox: {
176
+ min: [bbox.min.x, bbox.min.y, bbox.min.z],
177
+ max: [bbox.max.x, bbox.max.y, bbox.max.z]
178
+ }
179
+ });
181
180
  }
182
- } catch (err) {
183
- _iterator.e(err);
184
- } finally {
185
- _iterator.f();
186
- }
181
+ });
187
182
  return results;
188
183
  }
189
184
 
190
185
  /**
191
186
  * Computes individual world-space bounding boxes for each connector child
192
- * of a component.
187
+ * of a component. Supports deep children (e.g. within GLB model hierarchy).
193
188
  *
194
189
  * @param {THREE.Object3D} componentObject - The component's Three.js object
195
190
  * @returns {Array<{uuid: string, userData: Object, worldBoundingBox: {min: number[], max: number[]}}>}
@@ -197,38 +192,40 @@ function computeIODeviceBoundingBoxes(componentObject) {
197
192
  */
198
193
  function computeConnectorBoundingBoxes(componentObject) {
199
194
  var results = [];
200
- var _iterator2 = _createForOfIteratorHelper(componentObject.children),
201
- _step2;
202
- try {
203
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
204
- var _child$userData2;
205
- var child = _step2.value;
206
- if (((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType) !== 'connector') continue;
207
- var bbox = new THREE.Box3().setFromObject(child);
195
+ componentObject.traverse(function (child) {
196
+ var _child$userData2;
197
+ if (((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType) !== 'connector') return;
198
+ var bbox = new THREE.Box3().setFromObject(child);
199
+ var worldPos = new THREE.Vector3();
200
+ child.getWorldPosition(worldPos);
208
201
 
209
- // Fallback if mesh is too small or empty (sometimes connectors are just points)
210
- if (bbox.isEmpty() || bbox.getSize(new THREE.Vector3()).length() < 0.01) {
211
- var worldPos = new THREE.Vector3();
212
- child.getWorldPosition(worldPos);
213
- var size = 0.1;
214
- bbox.setFromCenterAndSize(worldPos, new THREE.Vector3(size, size, size));
215
- }
216
- results.push({
217
- uuid: child.uuid,
218
- userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
219
- objectType: 'connector'
220
- }),
221
- worldBoundingBox: {
222
- min: [bbox.min.x, bbox.min.y, bbox.min.z],
223
- max: [bbox.max.x, bbox.max.y, bbox.max.z]
224
- }
225
- });
202
+ // Compute world-space direction vector (Crucial for rotation-aware pathfinding)
203
+ // Default to [0, 0, 1] if not specified (Standard for our coordinate system)
204
+ var localDirData = child.userData && Array.isArray(child.userData.direction) ? child.userData.direction : [0, 0, 1];
205
+ var localDir = new THREE.Vector3(localDirData[0], localDirData[1], localDirData[2]);
206
+ var worldQuat = new THREE.Quaternion();
207
+ child.getWorldQuaternion(worldQuat);
208
+ var worldDir = localDir.clone().applyQuaternion(worldQuat).normalize();
209
+
210
+ // Fallback if mesh is too small or empty (sometimes connectors are just points)
211
+ if (bbox.isEmpty() || bbox.getSize(new THREE.Vector3()).length() < 0.01) {
212
+ var size = 0.1;
213
+ bbox.setFromCenterAndSize(worldPos, new THREE.Vector3(size, size, size));
226
214
  }
227
- } catch (err) {
228
- _iterator2.e(err);
229
- } finally {
230
- _iterator2.f();
231
- }
215
+ results.push({
216
+ uuid: child.uuid,
217
+ userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
218
+ objectType: 'connector',
219
+ // Update both position AND direction for pathfinder
220
+ position: [worldPos.x, worldPos.y, worldPos.z],
221
+ direction: [worldDir.x, worldDir.y, worldDir.z]
222
+ }),
223
+ worldBoundingBox: {
224
+ min: [bbox.min.x, bbox.min.y, bbox.min.z],
225
+ max: [bbox.max.x, bbox.max.y, bbox.max.z]
226
+ }
227
+ });
228
+ });
232
229
  return results;
233
230
  }
234
231
 
@@ -328,11 +325,11 @@ function createSelectionBoxHelpers(object) {
328
325
  * @param {THREE.Scene} scene - The scene (for finding objects by uuid)
329
326
  */
330
327
  function updateSelectionBoxHelpers(helpers, selectedObjects, scene) {
331
- var _iterator3 = _createForOfIteratorHelper(helpers),
332
- _step3;
328
+ var _iterator = _createForOfIteratorHelper(helpers),
329
+ _step;
333
330
  try {
334
331
  var _loop = function _loop() {
335
- var helper = _step3.value;
332
+ var helper = _step.value;
336
333
  var _helper$userData = helper.userData,
337
334
  sourceObjectUuid = _helper$userData.sourceObjectUuid,
338
335
  isFiltered = _helper$userData.isFiltered,
@@ -363,13 +360,13 @@ function updateSelectionBoxHelpers(helpers, selectedObjects, scene) {
363
360
  helper.update();
364
361
  }
365
362
  };
366
- for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
363
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
367
364
  if (_loop()) continue;
368
365
  }
369
366
  } catch (err) {
370
- _iterator3.e(err);
367
+ _iterator.e(err);
371
368
  } finally {
372
- _iterator3.f();
369
+ _iterator.f();
373
370
  }
374
371
  }
375
372
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2112-lab/central-plant",
3
- "version": "0.3.47",
3
+ "version": "0.3.48",
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",