@2112-lab/central-plant 0.3.49 → 0.3.50

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.
@@ -3,7 +3,7 @@ import * as THREE from 'three';
3
3
  import { Pathfinder } from '@2112-lab/pathfinder';
4
4
  import { BaseDisposable } from '../../core/baseDisposable.js';
5
5
  import { PathData } from '../../core/pathfindingData.js';
6
- import { computeFilteredBoundingBox, computeConnectorBoundingBoxes, computeIODeviceBoundingBoxes } from '../../utils/boundingBoxUtils.js';
6
+ import { computeFilteredBoundingBox, computeIODeviceBoundingBoxes, computeConnectorBoundingBoxes } from '../../utils/boundingBoxUtils.js';
7
7
  import { SceneDataManager } from './sceneDataManager.js';
8
8
  import { PathRenderingManager } from './PathRenderingManager.js';
9
9
  import { ConnectorManager } from './ConnectorManager.js';
@@ -202,204 +202,344 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
202
202
  }
203
203
 
204
204
  /**
205
- * Enrich scene data with world-space bounding boxes and positions for collision avoidance
206
- * @param {Object|Array} sceneData - Original scene data or array of children
207
- * @returns {Object|Array} Enriched scene data (shallow copy with modifications)
205
+ * Enrich sceneData with worldBoundingBox for segments and components
206
+ * Connectors remain with just position information.
207
+ *
208
+ * Uses _bboxCache to avoid recomputing filtered / io-device bboxes
209
+ * when the underlying object's world matrix has not changed.
210
+ *
211
+ * @param {Object} sceneData - Original scene data
212
+ * @returns {Object} Enriched scene data (shallow copy with modifications)
208
213
  * @private
209
214
  */
210
215
  }, {
211
216
  key: "_enrichSceneDataWithBoundingBoxes",
212
217
  value: function _enrichSceneDataWithBoundingBoxes(sceneData) {
213
218
  var _this4 = this;
214
- if (!sceneData) return sceneData;
215
-
216
- // Recursive helper to enrich a node and its children
217
- var _enrichNode = function enrichNode(node) {
218
- if (!node) return node;
219
- var enrichedNode = _objectSpread2({}, node);
219
+ // Create a shallow copy of sceneData structure
220
+ var enriched = _objectSpread2({}, sceneData);
221
+ if (!sceneData.children || !Array.isArray(sceneData.children)) {
222
+ console.warn('⚠️ sceneData has no children array');
223
+ return enriched;
224
+ }
220
225
 
226
+ // Process children to add worldBoundingBox to segments and components
227
+ enriched.children = sceneData.children.map(function (child) {
221
228
  // Skip computed objects ( elbows etc )
222
- if (node.userData && (node.userData.isComputed === true || node.userData.objectType === 'elbow')) {
223
- return node;
229
+ if (child.userData && (child.userData.isComputed === true || child.userData.objectType === 'elbow')) {
230
+ return child;
224
231
  }
225
232
 
226
- // ── Enrich Segments ──
227
- if (node.userData && node.userData.objectType === 'segment') {
228
- var segmentObject = _this4.sceneViewer.scene.getObjectByProperty('uuid', node.uuid);
233
+ // Enrich segments (check if objectType is 'segment' in userData)
234
+ if (child.userData && child.userData.objectType === 'segment') {
235
+ // Find the actual segment object in the scene
236
+ var segmentObject = _this4.sceneViewer.scene.getObjectByProperty('uuid', child.uuid);
229
237
  if (segmentObject) {
238
+ // ── Cache check ──
230
239
  var hash = _this4._matrixHash(segmentObject);
231
- var cached = _this4._bboxCache.get(node.uuid);
240
+ var cached = _this4._bboxCache.get(child.uuid);
232
241
  if (cached && cached.matrixHash === hash && cached.segmentBBox) {
233
- enrichedNode.userData = _objectSpread2(_objectSpread2({}, node.userData), {}, {
234
- worldBoundingBox: cached.segmentBBox
235
- });
236
- } else {
237
- var worldBBox = new THREE.Box3().setFromObject(segmentObject);
238
- var bboxData = {
239
- min: [worldBBox.min.x, worldBBox.min.y, worldBBox.min.z],
240
- max: [worldBBox.max.x, worldBBox.max.y, worldBBox.max.z]
241
- };
242
- _this4._bboxCache.set(node.uuid, {
243
- matrixHash: hash,
244
- segmentBBox: bboxData
245
- });
246
- enrichedNode.userData = _objectSpread2(_objectSpread2({}, node.userData), {}, {
247
- worldBoundingBox: bboxData
242
+ return _objectSpread2(_objectSpread2({}, child), {}, {
243
+ userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
244
+ worldBoundingBox: cached.segmentBBox
245
+ })
248
246
  });
249
247
  }
248
+
249
+ // Compute world bounding box
250
+ var worldBBox = new THREE.Box3().setFromObject(segmentObject);
251
+ var bboxData = {
252
+ min: [worldBBox.min.x, worldBBox.min.y, worldBBox.min.z],
253
+ max: [worldBBox.max.x, worldBBox.max.y, worldBBox.max.z]
254
+ };
255
+
256
+ // Store in cache
257
+ _this4._bboxCache.set(child.uuid, {
258
+ matrixHash: hash,
259
+ segmentBBox: bboxData
260
+ });
261
+ return _objectSpread2(_objectSpread2({}, child), {}, {
262
+ userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
263
+ worldBoundingBox: bboxData
264
+ })
265
+ });
266
+ } else {
267
+ console.warn("\u26A0\uFE0F Could not find segment object in scene: ".concat(child.uuid));
250
268
  }
251
269
  }
252
270
 
253
- // ── Enrich Components ──
254
- else if (node.userData && node.userData.objectType === 'component') {
255
- var componentObject = _this4.sceneViewer.scene.getObjectByProperty('uuid', node.uuid);
271
+ // Enrich components (check if objectType is 'component' in userData)
272
+ if (child.userData && child.userData.objectType === 'component') {
273
+ // Find the actual component object in the scene
274
+ var componentObject = _this4.sceneViewer.scene.getObjectByProperty('uuid', child.uuid);
256
275
  if (componentObject) {
276
+ // Explicitly update matrices for this component subtree before computing bounding boxes
257
277
  componentObject.updateMatrixWorld(true);
278
+
279
+ // ── Cache check ──
258
280
  var _hash = _this4._matrixHash(componentObject);
259
- var _cached = _this4._bboxCache.get(node.uuid);
281
+ var _cached = _this4._bboxCache.get(child.uuid);
260
282
  if (_cached && _cached.matrixHash === _hash && _cached.filteredBBox) {
261
- enrichedNode.position = {
262
- x: componentObject.position.x,
263
- y: componentObject.position.y,
264
- z: componentObject.position.z
265
- };
266
- enrichedNode.rotation = {
267
- x: THREE.MathUtils.radToDeg(componentObject.rotation.x),
268
- y: THREE.MathUtils.radToDeg(componentObject.rotation.y),
269
- z: THREE.MathUtils.radToDeg(componentObject.rotation.z)
270
- };
271
- enrichedNode.userData = _objectSpread2(_objectSpread2({}, node.userData), {}, {
272
- worldBoundingBox: _cached.filteredBBox,
273
- position: [componentObject.position.x, componentObject.position.y, componentObject.position.z]
283
+ // Rebuild enriched child from cached data
284
+ var _enrichedChild = _objectSpread2(_objectSpread2({}, child), {}, {
285
+ // Sync live transform even on cache hit just in case the manifest (child) is stale
286
+ position: {
287
+ x: componentObject.position.x,
288
+ y: componentObject.position.y,
289
+ z: componentObject.position.z
290
+ },
291
+ rotation: {
292
+ x: THREE.MathUtils.radToDeg(componentObject.rotation.x),
293
+ y: THREE.MathUtils.radToDeg(componentObject.rotation.y),
294
+ z: THREE.MathUtils.radToDeg(componentObject.rotation.z)
295
+ },
296
+ userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
297
+ worldBoundingBox: _cached.filteredBBox,
298
+ position: [componentObject.position.x, componentObject.position.y, componentObject.position.z]
299
+ })
274
300
  });
275
-
276
- // Re-merge cached connectors and devices if they exist
277
- if (!enrichedNode.children) enrichedNode.children = [];
278
- if (_cached.connectorBBoxes) {
279
- _cached.connectorBBoxes.forEach(function (conn) {
280
- return _this4._mergeEnrichedChild(enrichedNode.children, conn);
301
+ if (_cached.ioDeviceBBoxes && _cached.ioDeviceBBoxes.length > 0) {
302
+ if (!_enrichedChild.children) _enrichedChild.children = [];
303
+ _cached.ioDeviceBBoxes.forEach(function (deviceBBox) {
304
+ var existingIndex = _enrichedChild.children.findIndex(function (c) {
305
+ return c.uuid === deviceBBox.uuid;
306
+ });
307
+ if (existingIndex >= 0) {
308
+ _enrichedChild.children[existingIndex] = _objectSpread2(_objectSpread2({}, _enrichedChild.children[existingIndex]), {}, {
309
+ position: {
310
+ x: deviceBBox.userData.position[0],
311
+ y: deviceBBox.userData.position[1],
312
+ z: deviceBBox.userData.position[2]
313
+ },
314
+ userData: _objectSpread2(_objectSpread2({}, _enrichedChild.children[existingIndex].userData), {}, {
315
+ objectType: 'io-device',
316
+ worldBoundingBox: deviceBBox.worldBoundingBox,
317
+ position: deviceBBox.userData.position
318
+ })
319
+ });
320
+ } else {
321
+ _enrichedChild.children.push({
322
+ uuid: deviceBBox.uuid,
323
+ position: {
324
+ x: deviceBBox.userData.position[0],
325
+ y: deviceBBox.userData.position[1],
326
+ z: deviceBBox.userData.position[2]
327
+ },
328
+ userData: _objectSpread2(_objectSpread2({}, deviceBBox.userData), {}, {
329
+ worldBoundingBox: deviceBBox.worldBoundingBox,
330
+ position: deviceBBox.userData.position
331
+ }),
332
+ children: []
333
+ });
334
+ }
281
335
  });
282
336
  }
283
- if (_cached.ioDeviceBBoxes) {
284
- _cached.ioDeviceBBoxes.forEach(function (dev) {
285
- return _this4._mergeEnrichedChild(enrichedNode.children, dev);
337
+
338
+ // Also reinject connectors from cache
339
+ if (_cached.connectorBBoxes && _cached.connectorBBoxes.length > 0) {
340
+ if (!_enrichedChild.children) _enrichedChild.children = [];
341
+ _cached.connectorBBoxes.forEach(function (connBBox) {
342
+ var existingIndex = _enrichedChild.children.findIndex(function (c) {
343
+ return c.uuid === connBBox.uuid;
344
+ });
345
+ if (existingIndex >= 0) {
346
+ _enrichedChild.children[existingIndex] = _objectSpread2(_objectSpread2({}, _enrichedChild.children[existingIndex]), {}, {
347
+ position: {
348
+ x: connBBox.userData.position[0],
349
+ y: connBBox.userData.position[1],
350
+ z: connBBox.userData.position[2]
351
+ },
352
+ userData: _objectSpread2(_objectSpread2({}, _enrichedChild.children[existingIndex].userData), {}, {
353
+ worldBoundingBox: connBBox.worldBoundingBox,
354
+ position: connBBox.userData.position
355
+ })
356
+ });
357
+ } else {
358
+ _enrichedChild.children.push({
359
+ uuid: connBBox.uuid,
360
+ position: {
361
+ x: connBBox.userData.position[0],
362
+ y: connBBox.userData.position[1],
363
+ z: connBBox.userData.position[2]
364
+ },
365
+ userData: _objectSpread2(_objectSpread2({}, connBBox.userData), {}, {
366
+ worldBoundingBox: connBBox.worldBoundingBox,
367
+ position: connBBox.userData.position
368
+ }),
369
+ children: []
370
+ });
371
+ }
286
372
  });
287
373
  }
288
- } else {
289
- var filteredBBox = computeFilteredBoundingBox(componentObject, ['io-device', 'connector']);
290
- var _bboxData = {
291
- min: [filteredBBox.min.x, filteredBBox.min.y, filteredBBox.min.z],
292
- max: [filteredBBox.max.x, filteredBBox.max.y, filteredBBox.max.z]
293
- };
294
- enrichedNode.position = {
374
+ return _enrichedChild;
375
+ }
376
+
377
+ // Compute FILTERED bounding box — excludes io-device and connector subtrees
378
+ // so the component body bbox is tight-fitting and doesn't envelop attached devices
379
+ var filteredBBox = computeFilteredBoundingBox(componentObject, ['io-device', 'connector']);
380
+ console.log("\uD83D\uDD04 Updated worldBoundingBox for component ".concat(child.uuid, " (filtered): min=[").concat(filteredBBox.min.x.toFixed(2), ", ").concat(filteredBBox.min.y.toFixed(2), ", ").concat(filteredBBox.min.z.toFixed(2), "], max=[").concat(filteredBBox.max.x.toFixed(2), ", ").concat(filteredBBox.max.y.toFixed(2), ", ").concat(filteredBBox.max.z.toFixed(2), "]"));
381
+ var _bboxData = {
382
+ min: [filteredBBox.min.x, filteredBBox.min.y, filteredBBox.min.z],
383
+ max: [filteredBBox.max.x, filteredBBox.max.y, filteredBBox.max.z]
384
+ };
385
+
386
+ // Build the enriched component entry
387
+ var enrichedChild = _objectSpread2(_objectSpread2({}, child), {}, {
388
+ // Sync live transform to ensure pathfinder has latest coordinates
389
+ position: {
295
390
  x: componentObject.position.x,
296
391
  y: componentObject.position.y,
297
392
  z: componentObject.position.z
298
- };
299
- enrichedNode.rotation = {
393
+ },
394
+ rotation: {
300
395
  x: THREE.MathUtils.radToDeg(componentObject.rotation.x),
301
396
  y: THREE.MathUtils.radToDeg(componentObject.rotation.y),
302
397
  z: THREE.MathUtils.radToDeg(componentObject.rotation.z)
303
- };
304
- enrichedNode.userData = _objectSpread2(_objectSpread2({}, node.userData), {}, {
398
+ },
399
+ userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
305
400
  worldBoundingBox: _bboxData,
306
401
  position: [componentObject.position.x, componentObject.position.y, componentObject.position.z]
402
+ })
403
+ });
404
+
405
+ // Compute separate bounding boxes for each attached io-device
406
+ var ioDeviceBBoxes = computeIODeviceBoundingBoxes(componentObject);
407
+ if (ioDeviceBBoxes.length > 0) {
408
+ // Ensure children array exists (may already contain connectors)
409
+ if (!enrichedChild.children) {
410
+ enrichedChild.children = [];
411
+ }
412
+
413
+ // Inject io-device entries with their own worldBoundingBox
414
+ ioDeviceBBoxes.forEach(function (deviceBBox) {
415
+ var existingIndex = enrichedChild.children.findIndex(function (c) {
416
+ return c.uuid === deviceBBox.uuid;
417
+ });
418
+ if (existingIndex >= 0) {
419
+ enrichedChild.children[existingIndex] = _objectSpread2(_objectSpread2({}, enrichedChild.children[existingIndex]), {}, {
420
+ position: {
421
+ x: deviceBBox.userData.position[0],
422
+ y: deviceBBox.userData.position[1],
423
+ z: deviceBBox.userData.position[2]
424
+ },
425
+ userData: _objectSpread2(_objectSpread2({}, enrichedChild.children[existingIndex].userData), {}, {
426
+ objectType: 'io-device',
427
+ worldBoundingBox: deviceBBox.worldBoundingBox,
428
+ position: deviceBBox.userData.position
429
+ })
430
+ });
431
+ } else {
432
+ enrichedChild.children.push({
433
+ uuid: deviceBBox.uuid,
434
+ position: {
435
+ x: deviceBBox.userData.position[0],
436
+ y: deviceBBox.userData.position[1],
437
+ z: deviceBBox.userData.position[2]
438
+ },
439
+ userData: _objectSpread2(_objectSpread2({}, deviceBBox.userData), {}, {
440
+ worldBoundingBox: deviceBBox.worldBoundingBox,
441
+ position: deviceBBox.userData.position
442
+ }),
443
+ children: []
444
+ });
445
+ }
446
+ console.log("\uD83D\uDCE6 Injected io-device bbox for ".concat(deviceBBox.uuid, ": min=[").concat(deviceBBox.worldBoundingBox.min.map(function (v) {
447
+ return v.toFixed(2);
448
+ }).join(', '), "], max=[").concat(deviceBBox.worldBoundingBox.max.map(function (v) {
449
+ return v.toFixed(2);
450
+ }).join(', '), "]"));
307
451
  });
308
- var connectorBBoxes = computeConnectorBoundingBoxes(componentObject);
309
- var ioDeviceBBoxes = computeIODeviceBoundingBoxes(componentObject);
310
- if (!enrichedNode.children) enrichedNode.children = [];
311
- connectorBBoxes.forEach(function (conn) {
312
- return _this4._mergeEnrichedChild(enrichedNode.children, conn);
313
- });
314
- ioDeviceBBoxes.forEach(function (dev) {
315
- return _this4._mergeEnrichedChild(enrichedNode.children, dev);
316
- });
317
- _this4._bboxCache.set(node.uuid, {
318
- matrixHash: _hash,
319
- filteredBBox: _bboxData,
320
- ioDeviceBBoxes: ioDeviceBBoxes,
321
- connectorBBoxes: connectorBBoxes
452
+ console.log("\uD83D\uDCE6 Injected ".concat(ioDeviceBBoxes.length, " io-device bounding box(es) for component ").concat(child.uuid));
453
+ }
454
+
455
+ // PHASE 2: Enrich Connectors (CRITICAL for pathfinding API)
456
+ // Also compute world bounding boxes for connectors and inject into children.
457
+ // This ensures endpoints have world coordinates in sceneDataCopy.
458
+ var connectorBBoxes = computeConnectorBoundingBoxes(componentObject);
459
+ if (connectorBBoxes.length > 0) {
460
+ if (!enrichedChild.children) enrichedChild.children = [];
461
+ connectorBBoxes.forEach(function (connBBox) {
462
+ var existingIndex = enrichedChild.children.findIndex(function (c) {
463
+ return c.uuid === connBBox.uuid;
464
+ });
465
+ if (existingIndex >= 0) {
466
+ enrichedChild.children[existingIndex] = _objectSpread2(_objectSpread2({}, enrichedChild.children[existingIndex]), {}, {
467
+ position: {
468
+ x: connBBox.userData.position[0],
469
+ y: connBBox.userData.position[1],
470
+ z: connBBox.userData.position[2]
471
+ },
472
+ userData: _objectSpread2(_objectSpread2({}, enrichedChild.children[existingIndex].userData), {}, {
473
+ worldBoundingBox: connBBox.worldBoundingBox,
474
+ position: connBBox.userData.position
475
+ })
476
+ });
477
+ } else {
478
+ enrichedChild.children.push({
479
+ uuid: connBBox.uuid,
480
+ position: {
481
+ x: connBBox.userData.position[0],
482
+ y: connBBox.userData.position[1],
483
+ z: connBBox.userData.position[2]
484
+ },
485
+ userData: _objectSpread2(_objectSpread2({}, connBBox.userData), {}, {
486
+ worldBoundingBox: connBBox.worldBoundingBox,
487
+ position: connBBox.userData.position
488
+ }),
489
+ children: []
490
+ });
491
+ }
322
492
  });
323
493
  }
494
+
495
+ // Store in cache
496
+ _this4._bboxCache.set(child.uuid, {
497
+ matrixHash: _hash,
498
+ filteredBBox: _bboxData,
499
+ ioDeviceBBoxes: ioDeviceBBoxes,
500
+ connectorBBoxes: connectorBBoxes
501
+ });
502
+ return enrichedChild;
503
+ } else {
504
+ console.warn("\u26A0\uFE0F Could not find component object in scene: ".concat(child.uuid));
324
505
  }
325
506
  }
326
507
 
327
- // ── Enrich Standalone Objects (Gateways/Connectors) ──
328
- else if (node.userData && (node.userData.objectType === 'gateway' || node.userData.objectType === 'connector')) {
329
- var _node$userData;
330
- var object = _this4.sceneViewer.scene.getObjectByProperty('uuid', node.uuid) || _this4.sceneViewer.scene.getObjectByProperty('uuid', (_node$userData = node.userData) === null || _node$userData === void 0 ? void 0 : _node$userData.originalUuid);
508
+ // ────────────────────────────────────────────────────────────────────────
509
+ // PHASE 3: Handle top-level Gateways and Connectors
510
+ // ────────────────────────────────────────────────────────────────────────
511
+ if (child.userData && (child.userData.objectType === 'gateway' || child.userData.objectType === 'connector')) {
512
+ var _child$userData;
513
+ var object = _this4.sceneViewer.scene.getObjectByProperty('uuid', child.uuid) || _this4.sceneViewer.scene.getObjectByProperty('uuid', (_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.originalUuid);
331
514
  if (object) {
332
515
  var worldPos = new THREE.Vector3();
333
516
  object.getWorldPosition(worldPos);
334
- enrichedNode.position = {
335
- x: worldPos.x,
336
- y: worldPos.y,
337
- z: worldPos.z
338
- };
339
- enrichedNode.userData = _objectSpread2(_objectSpread2({}, node.userData), {}, {
340
- position: [worldPos.x, worldPos.y, worldPos.z]
517
+ return _objectSpread2(_objectSpread2({}, child), {}, {
518
+ // Sync live transform to ensure pathfinder has latest coordinates
519
+ position: {
520
+ x: worldPos.x,
521
+ y: worldPos.y,
522
+ z: worldPos.z
523
+ },
524
+ userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
525
+ position: [worldPos.x, worldPos.y, worldPos.z]
526
+ })
341
527
  });
342
-
343
- // If it's a connector, also sync direction
344
- if (node.userData.objectType === 'connector') {
345
- var worldDir = new THREE.Vector3(0, 0, 1);
346
- if (node.userData.direction) worldDir.set(node.userData.direction[0], node.userData.direction[1], node.userData.direction[2]);
347
- worldDir.applyQuaternion(object.getWorldQuaternion(new THREE.Quaternion())).normalize();
348
- enrichedNode.userData.direction = [worldDir.x, worldDir.y, worldDir.z];
349
- }
350
528
  }
351
529
  }
352
530
 
353
- // Recurse into children
354
- if (node.children && Array.isArray(node.children)) {
355
- enrichedNode.children = node.children.map(_enrichNode);
356
- }
357
- return enrichedNode;
358
- };
359
-
360
- // Handle root being an array or object
361
- if (Array.isArray(sceneData)) {
362
- return sceneData.map(_enrichNode);
363
- } else if (sceneData.children && Array.isArray(sceneData.children)) {
364
- var enrichedRoot = _objectSpread2({}, sceneData);
365
- enrichedRoot.children = sceneData.children.map(_enrichNode);
366
- return enrichedRoot;
367
- } else {
368
- return _enrichNode(sceneData);
369
- }
531
+ // For non-segments and non-components (and if object search failed), return as-is
532
+ return child;
533
+ });
534
+ return enriched;
370
535
  }
371
536
 
372
537
  /**
373
- * Helper to merge an enriched child (connector/device) into a children array
374
- * @private
538
+ * Core pathfinding logic shared across initialization and updates
375
539
  */
376
- }, {
377
- key: "_mergeEnrichedChild",
378
- value: function _mergeEnrichedChild(childrenArray, enrichedData) {
379
- var existingIndex = childrenArray.findIndex(function (c) {
380
- return c.uuid === enrichedData.uuid;
381
- });
382
- var nodeToMerge = {
383
- uuid: enrichedData.uuid,
384
- position: {
385
- x: enrichedData.userData.position[0],
386
- y: enrichedData.userData.position[1],
387
- z: enrichedData.userData.position[2]
388
- },
389
- userData: _objectSpread2(_objectSpread2({}, enrichedData.userData), {}, {
390
- worldBoundingBox: enrichedData.worldBoundingBox
391
- }),
392
- children: []
393
- };
394
- if (existingIndex >= 0) {
395
- childrenArray[existingIndex] = _objectSpread2(_objectSpread2({}, childrenArray[existingIndex]), nodeToMerge);
396
- } else {
397
- childrenArray.push(nodeToMerge);
398
- }
399
- }
400
540
  }, {
401
541
  key: "_executePathfinding",
402
- value: function () {
542
+ value: (function () {
403
543
  var _executePathfinding2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(sceneData, connections) {
404
544
  var _this5 = this,
405
545
  _sceneDataCopy$childr,
@@ -574,7 +714,7 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
574
714
  return _executePathfinding2.apply(this, arguments);
575
715
  }
576
716
  return _executePathfinding;
577
- }()
717
+ }())
578
718
  }, {
579
719
  key: "getSimplifiedSceneData",
580
720
  value: function getSimplifiedSceneData() {
@@ -2,6 +2,7 @@ import { createClass as _createClass, classCallCheck as _classCallCheck, asyncTo
2
2
  import * as THREE from 'three';
3
3
  import { OrbitControls } from '../../../node_modules/three/examples/jsm/controls/OrbitControls.js';
4
4
  import { GLTFLoader } from '../../../node_modules/three/examples/jsm/loaders/GLTFLoader.js';
5
+ import { DEFAULT_HOME_VIEW_DIRECTION, HOME_EMPTY_CAMERA_HEIGHT_ABOVE_GROUND } from '../controls/cameraControlsManager.js';
5
6
 
6
7
  var SceneInitializationManager = /*#__PURE__*/function () {
7
8
  function SceneInitializationManager(sceneViewer) {
@@ -30,7 +31,7 @@ var SceneInitializationManager = /*#__PURE__*/function () {
30
31
  containerWidth = containerRect.width;
31
32
  containerHeight = containerRect.height; // Create camera (Z-up coordinate system with flipped Y)
32
33
  component.camera = new THREE.PerspectiveCamera(50, containerWidth / containerHeight, 0.01, 1000);
33
- component.camera.position.set(-8, -9, 2); // Flipped Y direction
34
+ component.camera.position.set(DEFAULT_HOME_VIEW_DIRECTION.x, DEFAULT_HOME_VIEW_DIRECTION.y, HOME_EMPTY_CAMERA_HEIGHT_ABOVE_GROUND);
34
35
  component.camera.up.set(0, 0, 1); // Set Z as up vector
35
36
 
36
37
  // Create renderer
@@ -3,6 +3,7 @@ import * as THREE from 'three';
3
3
  import { loadTextureSet } from '../environment/textureConfig.js';
4
4
  import { ModelManager } from './modelManager.js';
5
5
  import { SceneClearingUtility } from '../../utils/sceneClearingUtility.js';
6
+ import { DEFAULT_HOME_VIEW_DIRECTION } from '../controls/cameraControlsManager.js';
6
7
 
7
8
  var _excluded = ["direction"],
8
9
  _excluded2 = ["direction"];
@@ -1118,6 +1119,7 @@ var SceneOperationsManager = /*#__PURE__*/function () {
1118
1119
  }, {
1119
1120
  key: "_finalizeScene",
1120
1121
  value: function _finalizeScene(data, crosscubeTextureSet, isImported) {
1122
+ var _component$cameraCont;
1121
1123
  var component = this.sceneViewer;
1122
1124
  component.currentSceneData = data;
1123
1125
  component.crosscubeTextureSet = crosscubeTextureSet;
@@ -1129,6 +1131,14 @@ var SceneOperationsManager = /*#__PURE__*/function () {
1129
1131
  }
1130
1132
  this._setTransformControlsState(true, false); // Enable but keep hidden initially
1131
1133
  }
1134
+
1135
+ // Frame the camera so all components fit perfectly in view (low, ground-level angle)
1136
+ if ((_component$cameraCont = component.cameraControlsManager) !== null && _component$cameraCont !== void 0 && _component$cameraCont.fitCameraToScene) {
1137
+ component.cameraControlsManager.fitCameraToScene({
1138
+ direction: DEFAULT_HOME_VIEW_DIRECTION,
1139
+ groundLevel: true
1140
+ });
1141
+ }
1132
1142
  }
1133
1143
 
1134
1144
  /**
@@ -1,4 +1,4 @@
1
- import { createForOfIteratorHelper as _createForOfIteratorHelper, objectSpread2 as _objectSpread2, construct as _construct, toConsumableArray as _toConsumableArray } from '../../_virtual/_rollupPluginBabelHelpers.js';
1
+ import { objectSpread2 as _objectSpread2, createForOfIteratorHelper as _createForOfIteratorHelper, construct as _construct, toConsumableArray as _toConsumableArray } from '../../_virtual/_rollupPluginBabelHelpers.js';
2
2
  import * as THREE from 'three';
3
3
 
4
4
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2112-lab/central-plant",
3
- "version": "0.3.49",
3
+ "version": "0.3.50",
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",