@2112-lab/central-plant 0.3.56 → 0.3.58

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.
@@ -4109,6 +4109,12 @@ var TransformControlsManager = /*#__PURE__*/function () {
4109
4109
  // Update bounding box if the transformed object is in current selection
4110
4110
  if (_this.selectedObjects.includes(eventData.object)) {
4111
4111
  console.log('🔲 Updating bounding boxes for selected objects after transform');
4112
+ // Reposition the transform gizmo (and its selection group) to follow the
4113
+ // object's new position. Programmatic transforms (e.g. the Transform tab's
4114
+ // Translate/Rotate buttons) move the object directly without touching the
4115
+ // multi-selection group the gizmo is attached to, so without this the widget
4116
+ // would stay at the pre-transform centroid.
4117
+ _this.syncSelectionToObjects();
4112
4118
  _this.updateBoundingBox();
4113
4119
  }
4114
4120
  };
@@ -5215,6 +5221,59 @@ var TransformControlsManager = /*#__PURE__*/function () {
5215
5221
  console.log("\uD83D\uDCE6 Multi-selection group created at centroid:", centroid.toArray());
5216
5222
  }
5217
5223
 
5224
+ /**
5225
+ * Re-sync the multi-selection group (and the attached transform gizmo) to the
5226
+ * current world positions of the selected objects, in place.
5227
+ *
5228
+ * Used after a *programmatic* transform (e.g. the Transform tab's Translate /
5229
+ * Rotate buttons), which mutates the object directly and does not move the
5230
+ * selection group the gizmo is attached to. Unlike a full updateMultiSelection()
5231
+ * this keeps the same group instance so the gizmo doesn't detach/re-attach.
5232
+ *
5233
+ * It also refreshes the cached original transforms so a subsequent gizmo drag
5234
+ * computes its delta from the object's new position rather than jumping.
5235
+ */
5236
+ }, {
5237
+ key: "syncSelectionToObjects",
5238
+ value: function syncSelectionToObjects() {
5239
+ var _this$transformContro;
5240
+ if (!this.multiSelectionGroup || this.selectedObjects.length === 0) {
5241
+ return;
5242
+ }
5243
+
5244
+ // Recompute the centroid from the objects' current world positions.
5245
+ var centroid = new THREE__namespace.Vector3();
5246
+ this.selectedObjects.forEach(function (obj) {
5247
+ obj.updateMatrixWorld(true);
5248
+ var worldPos = new THREE__namespace.Vector3();
5249
+ obj.getWorldPosition(worldPos);
5250
+ centroid.add(worldPos);
5251
+ });
5252
+ centroid.divideScalar(this.selectedObjects.length);
5253
+
5254
+ // Move the group (and thus the attached gizmo) to the new centroid. Reset any
5255
+ // rotation so the gizmo matches the freshly-created-group convention.
5256
+ this.multiSelectionGroup.position.copy(centroid);
5257
+ this.multiSelectionGroup.rotation.set(0, 0, 0);
5258
+ this.multiSelectionGroup.updateMatrixWorld(true);
5259
+
5260
+ // Refresh cached originals so applyRealtimeTransform's delta stays correct on
5261
+ // the next drag (its "original centroid" is derived from these values).
5262
+ this.selectedObjects.forEach(function (obj) {
5263
+ obj.userData._multiSelectOriginalPosition = obj.position.clone();
5264
+ obj.userData._multiSelectOriginalRotation = obj.rotation.clone();
5265
+ obj.userData._multiSelectOriginalScale = obj.scale.clone();
5266
+ var worldPos = new THREE__namespace.Vector3();
5267
+ obj.getWorldPosition(worldPos);
5268
+ obj.userData._multiSelectOriginalWorldPosition = worldPos;
5269
+ });
5270
+
5271
+ // Keep the gizmo's world matrix in sync with the repositioned group.
5272
+ if ((_this$transformContro = this.transformControls) !== null && _this$transformContro !== void 0 && _this$transformContro.object) {
5273
+ this.transformControls.updateMatrixWorld(true);
5274
+ }
5275
+ }
5276
+
5218
5277
  /**
5219
5278
  * Create a bounding box that encompasses all selected objects
5220
5279
  */
@@ -5494,10 +5553,10 @@ var TransformControlsManager = /*#__PURE__*/function () {
5494
5553
  }, {
5495
5554
  key: "toggleTransformMode",
5496
5555
  value: function toggleTransformMode() {
5497
- var _this$transformContro;
5556
+ var _this$transformContro2;
5498
5557
  var newMode = this.currentMode === 'translate' ? 'rotate' : 'translate';
5499
5558
  this.setMode(newMode);
5500
- if ((_this$transformContro = this.transformControls) !== null && _this$transformContro !== void 0 && _this$transformContro.object) {
5559
+ if ((_this$transformContro2 = this.transformControls) !== null && _this$transformContro2 !== void 0 && _this$transformContro2.object) {
5501
5560
  this.transformControls.updateMatrixWorld(true);
5502
5561
  }
5503
5562
  }
@@ -27361,13 +27420,15 @@ var EnvironmentManager = /*#__PURE__*/function () {
27361
27420
  return this.createSkybox();
27362
27421
  case 1:
27363
27422
  this.setupLighting();
27364
- _context4.n = 2;
27365
- return this.addTexturedGround();
27366
- case 2:
27367
- // await this.addBrickWalls()
27368
27423
  this.addHorizonFog();
27369
- console.log('Environment initialization completed');
27370
- case 3:
27424
+
27425
+ // 2. Add ground (synchronously adds basic mesh)
27426
+ // We do NOT await the texture loading here to speed up scene "Ready" state
27427
+ this.addTexturedGround().catch(function (err) {
27428
+ return console.warn('⚠️ Ground texture load failed:', err);
27429
+ });
27430
+ console.log('Environment initialization completed (textures loading in background)');
27431
+ case 2:
27371
27432
  return _context4.a(2);
27372
27433
  }
27373
27434
  }, _callee4, this);
@@ -41823,7 +41884,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
41823
41884
  * Initialize the CentralPlant manager
41824
41885
  *
41825
41886
  * @constructor
41826
- * @version 0.3.56
41887
+ * @version 0.3.58
41827
41888
  * @updated 2025-10-22
41828
41889
  *
41829
41890
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -44441,23 +44502,18 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
44441
44502
  /**
44442
44503
  * Initialize model preloading from component dictionary
44443
44504
  * @param {string} [basePath='/library/'] - Base path for both dictionary and models (can be local path or S3 URL)
44444
- * @returns {Promise<Object>} Preloading progress results
44445
- * @description Loads the component dictionary and preloads all models for better performance
44446
- * @example
44447
- * // Local files (default)
44448
- * await centralPlant.initializeModelPreloading()
44449
- *
44450
- * // S3 bucket
44451
- * await centralPlant.initializeModelPreloading('https://my-bucket.s3.amazonaws.com/library/')
44452
- *
44453
- * // Custom local path
44454
- * await centralPlant.initializeModelPreloading('/custom/assets/')
44505
+ * @param {Object} [options={}] - Optimization options
44506
+ * @param {boolean} [options.autoPreloadModels=true] - If false, only loads the dictionary and skips preloading GLBs (improves initial load)
44507
+ * @returns {Promise<Object>} Preloading progress results or dictionary summary
44455
44508
  */
44456
44509
  }, {
44457
44510
  key: "initializeModelPreloading",
44458
44511
  value: (function () {
44459
44512
  var _initializeModelPreloading = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee10() {
44460
44513
  var basePath,
44514
+ options,
44515
+ _options$autoPreloadM,
44516
+ autoPreloadModels,
44461
44517
  normalizedBasePath,
44462
44518
  dictionaryPath,
44463
44519
  modelsBasePath,
@@ -44471,6 +44527,8 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
44471
44527
  while (1) switch (_context10.n) {
44472
44528
  case 0:
44473
44529
  basePath = _args10.length > 0 && _args10[0] !== undefined ? _args10[0] : '/library/';
44530
+ options = _args10.length > 1 && _args10[1] !== undefined ? _args10[1] : {};
44531
+ _options$autoPreloadM = options.autoPreloadModels, autoPreloadModels = _options$autoPreloadM === void 0 ? true : _options$autoPreloadM;
44474
44532
  _context10.p = 1;
44475
44533
  // Ensure basePath ends with a slash
44476
44534
  normalizedBasePath = basePath.endsWith('/') ? basePath : "".concat(basePath, "/");
@@ -44480,6 +44538,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
44480
44538
  console.log("\uD83D\uDCC2 Base path: ".concat(normalizedBasePath));
44481
44539
  console.log("\uD83D\uDCDA Dictionary path: ".concat(dictionaryPath));
44482
44540
  console.log("\uFFFD Models path: ".concat(modelsBasePath));
44541
+ console.log("\u26A1 Auto-preload models: ".concat(autoPreloadModels));
44483
44542
 
44484
44543
  // Load the component dictionary
44485
44544
  _context10.n = 2;
@@ -44496,25 +44555,39 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
44496
44555
  return response.json();
44497
44556
  case 4:
44498
44557
  componentDictionary = _context10.v;
44499
- console.log('📚 Component dictionary loaded:', Object.keys(componentDictionary));
44558
+ console.log('📚 Component dictionary loaded:', Object.keys(componentDictionary).length);
44500
44559
 
44501
- // Start preloading all models with the specified base path
44560
+ // Get the model preloader utility
44502
44561
  _modelPreloader2 = this.getUtility('modelPreloader');
44503
- _context10.n = 5;
44504
- return _modelPreloader2.preloadAllModels(componentDictionary, modelsBasePath);
44562
+ if (autoPreloadModels) {
44563
+ _context10.n = 5;
44564
+ break;
44565
+ }
44566
+ console.log('⚡ Lazy loading enabled: skipping automatic GLB preloading');
44567
+ // We still need to give the preloader the dictionary and base path for later use
44568
+ _modelPreloader2.componentDictionary = componentDictionary;
44569
+ _modelPreloader2.setModelsBasePath(modelsBasePath);
44570
+ return _context10.a(2, {
44571
+ successCount: 0,
44572
+ failureCount: 0,
44573
+ dictionaryCount: Object.keys(componentDictionary).length
44574
+ });
44505
44575
  case 5:
44576
+ _context10.n = 6;
44577
+ return _modelPreloader2.preloadAllModels(componentDictionary, modelsBasePath);
44578
+ case 6:
44506
44579
  progress = _context10.v;
44507
44580
  console.log('🎉 Model preloading completed:', progress);
44508
44581
  return _context10.a(2, progress);
44509
- case 6:
44510
- _context10.p = 6;
44582
+ case 7:
44583
+ _context10.p = 7;
44511
44584
  _t3 = _context10.v;
44512
44585
  console.error('❌ Failed to initialize model preloading:', _t3);
44513
44586
  throw _t3;
44514
- case 7:
44587
+ case 8:
44515
44588
  return _context10.a(2);
44516
44589
  }
44517
- }, _callee10, this, [[1, 6]]);
44590
+ }, _callee10, this, [[1, 7]]);
44518
44591
  }));
44519
44592
  function initializeModelPreloading() {
44520
44593
  return _initializeModelPreloading.apply(this, arguments);
@@ -46484,7 +46557,7 @@ var CacheManager = /*#__PURE__*/function () {
46484
46557
  key: "execute",
46485
46558
  value: (function () {
46486
46559
  var _execute = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4(_ref) {
46487
- var cacheKey, sourceKey, expiryMs, fetcher, processor, _ref$metadata, metadata, cacheName, cache, cachedResponse, cachedTime, age, result, fetchResult, blobOrJson, contentType, contentSize, responseBody, headers, cacheResponse, fallbackResult, _t2;
46560
+ var cacheKey, sourceKey, expiryMs, fetcher, processor, _ref$metadata, metadata, cacheName, cache, cachedResponse, cachedTime, age, result, fetchResult, blobOrJson, error, contentType, contentSize, responseBody, headers, cacheResponse, fallbackResult, _t2;
46488
46561
  return _regenerator().w(function (_context4) {
46489
46562
  while (1) switch (_context4.n) {
46490
46563
  case 0:
@@ -46537,7 +46610,9 @@ var CacheManager = /*#__PURE__*/function () {
46537
46610
  _context4.n = 9;
46538
46611
  break;
46539
46612
  }
46540
- throw new Error("Fetch failed: ".concat(fetchResult.status));
46613
+ error = new Error("Fetch failed: ".concat(fetchResult.status)); // Do not retry 404s - the file is genuinely missing
46614
+ if (fetchResult.status === 404) error.noRetry = true;
46615
+ throw error;
46541
46616
  case 9:
46542
46617
  _context4.n = 10;
46543
46618
  return processor(fetchResult.clone());
@@ -46588,20 +46663,29 @@ var CacheManager = /*#__PURE__*/function () {
46588
46663
  case 15:
46589
46664
  fallbackResult = _context4.v;
46590
46665
  if (!(fallbackResult instanceof Response)) {
46666
+ _context4.n = 18;
46667
+ break;
46668
+ }
46669
+ if (fallbackResult.ok) {
46591
46670
  _context4.n = 16;
46592
46671
  break;
46593
46672
  }
46594
- return _context4.a(2, processor(fallbackResult));
46673
+ throw new Error("Fallback fetch failed: ".concat(fallbackResult.status));
46595
46674
  case 16:
46596
- return _context4.a(2, fallbackResult);
46675
+ _context4.n = 17;
46676
+ return processor(fallbackResult);
46597
46677
  case 17:
46598
- _context4.p = 17;
46678
+ return _context4.a(2, _context4.v);
46679
+ case 18:
46680
+ return _context4.a(2, fallbackResult);
46681
+ case 19:
46682
+ _context4.p = 19;
46599
46683
  _context4.v;
46600
46684
  throw _t2;
46601
- case 18:
46685
+ case 20:
46602
46686
  return _context4.a(2);
46603
46687
  }
46604
- }, _callee4, this, [[14, 17], [1, 13]]);
46688
+ }, _callee4, this, [[14, 19], [1, 13]]);
46605
46689
  }));
46606
46690
  function execute(_x2) {
46607
46691
  return _execute.apply(this, arguments);
@@ -58,7 +58,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
58
58
  * Initialize the CentralPlant manager
59
59
  *
60
60
  * @constructor
61
- * @version 0.3.56
61
+ * @version 0.3.58
62
62
  * @updated 2025-10-22
63
63
  *
64
64
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -2676,23 +2676,18 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
2676
2676
  /**
2677
2677
  * Initialize model preloading from component dictionary
2678
2678
  * @param {string} [basePath='/library/'] - Base path for both dictionary and models (can be local path or S3 URL)
2679
- * @returns {Promise<Object>} Preloading progress results
2680
- * @description Loads the component dictionary and preloads all models for better performance
2681
- * @example
2682
- * // Local files (default)
2683
- * await centralPlant.initializeModelPreloading()
2684
- *
2685
- * // S3 bucket
2686
- * await centralPlant.initializeModelPreloading('https://my-bucket.s3.amazonaws.com/library/')
2687
- *
2688
- * // Custom local path
2689
- * await centralPlant.initializeModelPreloading('/custom/assets/')
2679
+ * @param {Object} [options={}] - Optimization options
2680
+ * @param {boolean} [options.autoPreloadModels=true] - If false, only loads the dictionary and skips preloading GLBs (improves initial load)
2681
+ * @returns {Promise<Object>} Preloading progress results or dictionary summary
2690
2682
  */
2691
2683
  }, {
2692
2684
  key: "initializeModelPreloading",
2693
2685
  value: (function () {
2694
2686
  var _initializeModelPreloading = _rollupPluginBabelHelpers.asyncToGenerator(/*#__PURE__*/_rollupPluginBabelHelpers.regenerator().m(function _callee10() {
2695
2687
  var basePath,
2688
+ options,
2689
+ _options$autoPreloadM,
2690
+ autoPreloadModels,
2696
2691
  normalizedBasePath,
2697
2692
  dictionaryPath,
2698
2693
  modelsBasePath,
@@ -2706,6 +2701,8 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
2706
2701
  while (1) switch (_context10.n) {
2707
2702
  case 0:
2708
2703
  basePath = _args10.length > 0 && _args10[0] !== undefined ? _args10[0] : '/library/';
2704
+ options = _args10.length > 1 && _args10[1] !== undefined ? _args10[1] : {};
2705
+ _options$autoPreloadM = options.autoPreloadModels, autoPreloadModels = _options$autoPreloadM === void 0 ? true : _options$autoPreloadM;
2709
2706
  _context10.p = 1;
2710
2707
  // Ensure basePath ends with a slash
2711
2708
  normalizedBasePath = basePath.endsWith('/') ? basePath : "".concat(basePath, "/");
@@ -2715,6 +2712,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
2715
2712
  console.log("\uD83D\uDCC2 Base path: ".concat(normalizedBasePath));
2716
2713
  console.log("\uD83D\uDCDA Dictionary path: ".concat(dictionaryPath));
2717
2714
  console.log("\uFFFD Models path: ".concat(modelsBasePath));
2715
+ console.log("\u26A1 Auto-preload models: ".concat(autoPreloadModels));
2718
2716
 
2719
2717
  // Load the component dictionary
2720
2718
  _context10.n = 2;
@@ -2731,25 +2729,39 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
2731
2729
  return response.json();
2732
2730
  case 4:
2733
2731
  componentDictionary = _context10.v;
2734
- console.log('📚 Component dictionary loaded:', Object.keys(componentDictionary));
2732
+ console.log('📚 Component dictionary loaded:', Object.keys(componentDictionary).length);
2735
2733
 
2736
- // Start preloading all models with the specified base path
2734
+ // Get the model preloader utility
2737
2735
  _modelPreloader2 = this.getUtility('modelPreloader');
2738
- _context10.n = 5;
2739
- return _modelPreloader2.preloadAllModels(componentDictionary, modelsBasePath);
2736
+ if (autoPreloadModels) {
2737
+ _context10.n = 5;
2738
+ break;
2739
+ }
2740
+ console.log('⚡ Lazy loading enabled: skipping automatic GLB preloading');
2741
+ // We still need to give the preloader the dictionary and base path for later use
2742
+ _modelPreloader2.componentDictionary = componentDictionary;
2743
+ _modelPreloader2.setModelsBasePath(modelsBasePath);
2744
+ return _context10.a(2, {
2745
+ successCount: 0,
2746
+ failureCount: 0,
2747
+ dictionaryCount: Object.keys(componentDictionary).length
2748
+ });
2740
2749
  case 5:
2750
+ _context10.n = 6;
2751
+ return _modelPreloader2.preloadAllModels(componentDictionary, modelsBasePath);
2752
+ case 6:
2741
2753
  progress = _context10.v;
2742
2754
  console.log('🎉 Model preloading completed:', progress);
2743
2755
  return _context10.a(2, progress);
2744
- case 6:
2745
- _context10.p = 6;
2756
+ case 7:
2757
+ _context10.p = 7;
2746
2758
  _t3 = _context10.v;
2747
2759
  console.error('❌ Failed to initialize model preloading:', _t3);
2748
2760
  throw _t3;
2749
- case 7:
2761
+ case 8:
2750
2762
  return _context10.a(2);
2751
2763
  }
2752
- }, _callee10, this, [[1, 6]]);
2764
+ }, _callee10, this, [[1, 7]]);
2753
2765
  }));
2754
2766
  function initializeModelPreloading() {
2755
2767
  return _initializeModelPreloading.apply(this, arguments);
@@ -202,7 +202,7 @@ var CacheManager = /*#__PURE__*/function () {
202
202
  key: "execute",
203
203
  value: (function () {
204
204
  var _execute = _rollupPluginBabelHelpers.asyncToGenerator(/*#__PURE__*/_rollupPluginBabelHelpers.regenerator().m(function _callee4(_ref) {
205
- var cacheKey, sourceKey, expiryMs, fetcher, processor, _ref$metadata, metadata, cacheName, cache, cachedResponse, cachedTime, age, result, fetchResult, blobOrJson, contentType, contentSize, responseBody, headers, cacheResponse, fallbackResult, _t2;
205
+ var cacheKey, sourceKey, expiryMs, fetcher, processor, _ref$metadata, metadata, cacheName, cache, cachedResponse, cachedTime, age, result, fetchResult, blobOrJson, error, contentType, contentSize, responseBody, headers, cacheResponse, fallbackResult, _t2;
206
206
  return _rollupPluginBabelHelpers.regenerator().w(function (_context4) {
207
207
  while (1) switch (_context4.n) {
208
208
  case 0:
@@ -255,7 +255,9 @@ var CacheManager = /*#__PURE__*/function () {
255
255
  _context4.n = 9;
256
256
  break;
257
257
  }
258
- throw new Error("Fetch failed: ".concat(fetchResult.status));
258
+ error = new Error("Fetch failed: ".concat(fetchResult.status)); // Do not retry 404s - the file is genuinely missing
259
+ if (fetchResult.status === 404) error.noRetry = true;
260
+ throw error;
259
261
  case 9:
260
262
  _context4.n = 10;
261
263
  return processor(fetchResult.clone());
@@ -306,20 +308,29 @@ var CacheManager = /*#__PURE__*/function () {
306
308
  case 15:
307
309
  fallbackResult = _context4.v;
308
310
  if (!(fallbackResult instanceof Response)) {
311
+ _context4.n = 18;
312
+ break;
313
+ }
314
+ if (fallbackResult.ok) {
309
315
  _context4.n = 16;
310
316
  break;
311
317
  }
312
- return _context4.a(2, processor(fallbackResult));
318
+ throw new Error("Fallback fetch failed: ".concat(fallbackResult.status));
313
319
  case 16:
314
- return _context4.a(2, fallbackResult);
320
+ _context4.n = 17;
321
+ return processor(fallbackResult);
315
322
  case 17:
316
- _context4.p = 17;
323
+ return _context4.a(2, _context4.v);
324
+ case 18:
325
+ return _context4.a(2, fallbackResult);
326
+ case 19:
327
+ _context4.p = 19;
317
328
  _context4.v;
318
329
  throw _t2;
319
- case 18:
330
+ case 20:
320
331
  return _context4.a(2);
321
332
  }
322
- }, _callee4, this, [[14, 17], [1, 13]]);
333
+ }, _callee4, this, [[14, 19], [1, 13]]);
323
334
  }));
324
335
  function execute(_x2) {
325
336
  return _execute.apply(this, arguments);
@@ -164,6 +164,12 @@ var TransformControlsManager = /*#__PURE__*/function () {
164
164
  // Update bounding box if the transformed object is in current selection
165
165
  if (_this.selectedObjects.includes(eventData.object)) {
166
166
  console.log('🔲 Updating bounding boxes for selected objects after transform');
167
+ // Reposition the transform gizmo (and its selection group) to follow the
168
+ // object's new position. Programmatic transforms (e.g. the Transform tab's
169
+ // Translate/Rotate buttons) move the object directly without touching the
170
+ // multi-selection group the gizmo is attached to, so without this the widget
171
+ // would stay at the pre-transform centroid.
172
+ _this.syncSelectionToObjects();
167
173
  _this.updateBoundingBox();
168
174
  }
169
175
  };
@@ -1270,6 +1276,59 @@ var TransformControlsManager = /*#__PURE__*/function () {
1270
1276
  console.log("\uD83D\uDCE6 Multi-selection group created at centroid:", centroid.toArray());
1271
1277
  }
1272
1278
 
1279
+ /**
1280
+ * Re-sync the multi-selection group (and the attached transform gizmo) to the
1281
+ * current world positions of the selected objects, in place.
1282
+ *
1283
+ * Used after a *programmatic* transform (e.g. the Transform tab's Translate /
1284
+ * Rotate buttons), which mutates the object directly and does not move the
1285
+ * selection group the gizmo is attached to. Unlike a full updateMultiSelection()
1286
+ * this keeps the same group instance so the gizmo doesn't detach/re-attach.
1287
+ *
1288
+ * It also refreshes the cached original transforms so a subsequent gizmo drag
1289
+ * computes its delta from the object's new position rather than jumping.
1290
+ */
1291
+ }, {
1292
+ key: "syncSelectionToObjects",
1293
+ value: function syncSelectionToObjects() {
1294
+ var _this$transformContro;
1295
+ if (!this.multiSelectionGroup || this.selectedObjects.length === 0) {
1296
+ return;
1297
+ }
1298
+
1299
+ // Recompute the centroid from the objects' current world positions.
1300
+ var centroid = new THREE__namespace.Vector3();
1301
+ this.selectedObjects.forEach(function (obj) {
1302
+ obj.updateMatrixWorld(true);
1303
+ var worldPos = new THREE__namespace.Vector3();
1304
+ obj.getWorldPosition(worldPos);
1305
+ centroid.add(worldPos);
1306
+ });
1307
+ centroid.divideScalar(this.selectedObjects.length);
1308
+
1309
+ // Move the group (and thus the attached gizmo) to the new centroid. Reset any
1310
+ // rotation so the gizmo matches the freshly-created-group convention.
1311
+ this.multiSelectionGroup.position.copy(centroid);
1312
+ this.multiSelectionGroup.rotation.set(0, 0, 0);
1313
+ this.multiSelectionGroup.updateMatrixWorld(true);
1314
+
1315
+ // Refresh cached originals so applyRealtimeTransform's delta stays correct on
1316
+ // the next drag (its "original centroid" is derived from these values).
1317
+ this.selectedObjects.forEach(function (obj) {
1318
+ obj.userData._multiSelectOriginalPosition = obj.position.clone();
1319
+ obj.userData._multiSelectOriginalRotation = obj.rotation.clone();
1320
+ obj.userData._multiSelectOriginalScale = obj.scale.clone();
1321
+ var worldPos = new THREE__namespace.Vector3();
1322
+ obj.getWorldPosition(worldPos);
1323
+ obj.userData._multiSelectOriginalWorldPosition = worldPos;
1324
+ });
1325
+
1326
+ // Keep the gizmo's world matrix in sync with the repositioned group.
1327
+ if ((_this$transformContro = this.transformControls) !== null && _this$transformContro !== void 0 && _this$transformContro.object) {
1328
+ this.transformControls.updateMatrixWorld(true);
1329
+ }
1330
+ }
1331
+
1273
1332
  /**
1274
1333
  * Create a bounding box that encompasses all selected objects
1275
1334
  */
@@ -1549,10 +1608,10 @@ var TransformControlsManager = /*#__PURE__*/function () {
1549
1608
  }, {
1550
1609
  key: "toggleTransformMode",
1551
1610
  value: function toggleTransformMode() {
1552
- var _this$transformContro;
1611
+ var _this$transformContro2;
1553
1612
  var newMode = this.currentMode === 'translate' ? 'rotate' : 'translate';
1554
1613
  this.setMode(newMode);
1555
- if ((_this$transformContro = this.transformControls) !== null && _this$transformContro !== void 0 && _this$transformContro.object) {
1614
+ if ((_this$transformContro2 = this.transformControls) !== null && _this$transformContro2 !== void 0 && _this$transformContro2.object) {
1556
1615
  this.transformControls.updateMatrixWorld(true);
1557
1616
  }
1558
1617
  }
@@ -423,13 +423,15 @@ var EnvironmentManager = /*#__PURE__*/function () {
423
423
  return this.createSkybox();
424
424
  case 1:
425
425
  this.setupLighting();
426
- _context4.n = 2;
427
- return this.addTexturedGround();
428
- case 2:
429
- // await this.addBrickWalls()
430
426
  this.addHorizonFog();
431
- console.log('Environment initialization completed');
432
- case 3:
427
+
428
+ // 2. Add ground (synchronously adds basic mesh)
429
+ // We do NOT await the texture loading here to speed up scene "Ready" state
430
+ this.addTexturedGround().catch(function (err) {
431
+ return console.warn('⚠️ Ground texture load failed:', err);
432
+ });
433
+ console.log('Environment initialization completed (textures loading in background)');
434
+ case 2:
433
435
  return _context4.a(2);
434
436
  }
435
437
  }, _callee4, this);
@@ -34,7 +34,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
34
34
  * Initialize the CentralPlant manager
35
35
  *
36
36
  * @constructor
37
- * @version 0.3.56
37
+ * @version 0.3.58
38
38
  * @updated 2025-10-22
39
39
  *
40
40
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -2652,23 +2652,18 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
2652
2652
  /**
2653
2653
  * Initialize model preloading from component dictionary
2654
2654
  * @param {string} [basePath='/library/'] - Base path for both dictionary and models (can be local path or S3 URL)
2655
- * @returns {Promise<Object>} Preloading progress results
2656
- * @description Loads the component dictionary and preloads all models for better performance
2657
- * @example
2658
- * // Local files (default)
2659
- * await centralPlant.initializeModelPreloading()
2660
- *
2661
- * // S3 bucket
2662
- * await centralPlant.initializeModelPreloading('https://my-bucket.s3.amazonaws.com/library/')
2663
- *
2664
- * // Custom local path
2665
- * await centralPlant.initializeModelPreloading('/custom/assets/')
2655
+ * @param {Object} [options={}] - Optimization options
2656
+ * @param {boolean} [options.autoPreloadModels=true] - If false, only loads the dictionary and skips preloading GLBs (improves initial load)
2657
+ * @returns {Promise<Object>} Preloading progress results or dictionary summary
2666
2658
  */
2667
2659
  }, {
2668
2660
  key: "initializeModelPreloading",
2669
2661
  value: (function () {
2670
2662
  var _initializeModelPreloading = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee10() {
2671
2663
  var basePath,
2664
+ options,
2665
+ _options$autoPreloadM,
2666
+ autoPreloadModels,
2672
2667
  normalizedBasePath,
2673
2668
  dictionaryPath,
2674
2669
  modelsBasePath,
@@ -2682,6 +2677,8 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
2682
2677
  while (1) switch (_context10.n) {
2683
2678
  case 0:
2684
2679
  basePath = _args10.length > 0 && _args10[0] !== undefined ? _args10[0] : '/library/';
2680
+ options = _args10.length > 1 && _args10[1] !== undefined ? _args10[1] : {};
2681
+ _options$autoPreloadM = options.autoPreloadModels, autoPreloadModels = _options$autoPreloadM === void 0 ? true : _options$autoPreloadM;
2685
2682
  _context10.p = 1;
2686
2683
  // Ensure basePath ends with a slash
2687
2684
  normalizedBasePath = basePath.endsWith('/') ? basePath : "".concat(basePath, "/");
@@ -2691,6 +2688,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
2691
2688
  console.log("\uD83D\uDCC2 Base path: ".concat(normalizedBasePath));
2692
2689
  console.log("\uD83D\uDCDA Dictionary path: ".concat(dictionaryPath));
2693
2690
  console.log("\uFFFD Models path: ".concat(modelsBasePath));
2691
+ console.log("\u26A1 Auto-preload models: ".concat(autoPreloadModels));
2694
2692
 
2695
2693
  // Load the component dictionary
2696
2694
  _context10.n = 2;
@@ -2707,25 +2705,39 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
2707
2705
  return response.json();
2708
2706
  case 4:
2709
2707
  componentDictionary = _context10.v;
2710
- console.log('📚 Component dictionary loaded:', Object.keys(componentDictionary));
2708
+ console.log('📚 Component dictionary loaded:', Object.keys(componentDictionary).length);
2711
2709
 
2712
- // Start preloading all models with the specified base path
2710
+ // Get the model preloader utility
2713
2711
  _modelPreloader2 = this.getUtility('modelPreloader');
2714
- _context10.n = 5;
2715
- return _modelPreloader2.preloadAllModels(componentDictionary, modelsBasePath);
2712
+ if (autoPreloadModels) {
2713
+ _context10.n = 5;
2714
+ break;
2715
+ }
2716
+ console.log('⚡ Lazy loading enabled: skipping automatic GLB preloading');
2717
+ // We still need to give the preloader the dictionary and base path for later use
2718
+ _modelPreloader2.componentDictionary = componentDictionary;
2719
+ _modelPreloader2.setModelsBasePath(modelsBasePath);
2720
+ return _context10.a(2, {
2721
+ successCount: 0,
2722
+ failureCount: 0,
2723
+ dictionaryCount: Object.keys(componentDictionary).length
2724
+ });
2716
2725
  case 5:
2726
+ _context10.n = 6;
2727
+ return _modelPreloader2.preloadAllModels(componentDictionary, modelsBasePath);
2728
+ case 6:
2717
2729
  progress = _context10.v;
2718
2730
  console.log('🎉 Model preloading completed:', progress);
2719
2731
  return _context10.a(2, progress);
2720
- case 6:
2721
- _context10.p = 6;
2732
+ case 7:
2733
+ _context10.p = 7;
2722
2734
  _t3 = _context10.v;
2723
2735
  console.error('❌ Failed to initialize model preloading:', _t3);
2724
2736
  throw _t3;
2725
- case 7:
2737
+ case 8:
2726
2738
  return _context10.a(2);
2727
2739
  }
2728
- }, _callee10, this, [[1, 6]]);
2740
+ }, _callee10, this, [[1, 7]]);
2729
2741
  }));
2730
2742
  function initializeModelPreloading() {
2731
2743
  return _initializeModelPreloading.apply(this, arguments);
@@ -198,7 +198,7 @@ var CacheManager = /*#__PURE__*/function () {
198
198
  key: "execute",
199
199
  value: (function () {
200
200
  var _execute = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4(_ref) {
201
- var cacheKey, sourceKey, expiryMs, fetcher, processor, _ref$metadata, metadata, cacheName, cache, cachedResponse, cachedTime, age, result, fetchResult, blobOrJson, contentType, contentSize, responseBody, headers, cacheResponse, fallbackResult, _t2;
201
+ var cacheKey, sourceKey, expiryMs, fetcher, processor, _ref$metadata, metadata, cacheName, cache, cachedResponse, cachedTime, age, result, fetchResult, blobOrJson, error, contentType, contentSize, responseBody, headers, cacheResponse, fallbackResult, _t2;
202
202
  return _regenerator().w(function (_context4) {
203
203
  while (1) switch (_context4.n) {
204
204
  case 0:
@@ -251,7 +251,9 @@ var CacheManager = /*#__PURE__*/function () {
251
251
  _context4.n = 9;
252
252
  break;
253
253
  }
254
- throw new Error("Fetch failed: ".concat(fetchResult.status));
254
+ error = new Error("Fetch failed: ".concat(fetchResult.status)); // Do not retry 404s - the file is genuinely missing
255
+ if (fetchResult.status === 404) error.noRetry = true;
256
+ throw error;
255
257
  case 9:
256
258
  _context4.n = 10;
257
259
  return processor(fetchResult.clone());
@@ -302,20 +304,29 @@ var CacheManager = /*#__PURE__*/function () {
302
304
  case 15:
303
305
  fallbackResult = _context4.v;
304
306
  if (!(fallbackResult instanceof Response)) {
307
+ _context4.n = 18;
308
+ break;
309
+ }
310
+ if (fallbackResult.ok) {
305
311
  _context4.n = 16;
306
312
  break;
307
313
  }
308
- return _context4.a(2, processor(fallbackResult));
314
+ throw new Error("Fallback fetch failed: ".concat(fallbackResult.status));
309
315
  case 16:
310
- return _context4.a(2, fallbackResult);
316
+ _context4.n = 17;
317
+ return processor(fallbackResult);
311
318
  case 17:
312
- _context4.p = 17;
319
+ return _context4.a(2, _context4.v);
320
+ case 18:
321
+ return _context4.a(2, fallbackResult);
322
+ case 19:
323
+ _context4.p = 19;
313
324
  _context4.v;
314
325
  throw _t2;
315
- case 18:
326
+ case 20:
316
327
  return _context4.a(2);
317
328
  }
318
- }, _callee4, this, [[14, 17], [1, 13]]);
329
+ }, _callee4, this, [[14, 19], [1, 13]]);
319
330
  }));
320
331
  function execute(_x2) {
321
332
  return _execute.apply(this, arguments);
@@ -140,6 +140,12 @@ var TransformControlsManager = /*#__PURE__*/function () {
140
140
  // Update bounding box if the transformed object is in current selection
141
141
  if (_this.selectedObjects.includes(eventData.object)) {
142
142
  console.log('🔲 Updating bounding boxes for selected objects after transform');
143
+ // Reposition the transform gizmo (and its selection group) to follow the
144
+ // object's new position. Programmatic transforms (e.g. the Transform tab's
145
+ // Translate/Rotate buttons) move the object directly without touching the
146
+ // multi-selection group the gizmo is attached to, so without this the widget
147
+ // would stay at the pre-transform centroid.
148
+ _this.syncSelectionToObjects();
143
149
  _this.updateBoundingBox();
144
150
  }
145
151
  };
@@ -1246,6 +1252,59 @@ var TransformControlsManager = /*#__PURE__*/function () {
1246
1252
  console.log("\uD83D\uDCE6 Multi-selection group created at centroid:", centroid.toArray());
1247
1253
  }
1248
1254
 
1255
+ /**
1256
+ * Re-sync the multi-selection group (and the attached transform gizmo) to the
1257
+ * current world positions of the selected objects, in place.
1258
+ *
1259
+ * Used after a *programmatic* transform (e.g. the Transform tab's Translate /
1260
+ * Rotate buttons), which mutates the object directly and does not move the
1261
+ * selection group the gizmo is attached to. Unlike a full updateMultiSelection()
1262
+ * this keeps the same group instance so the gizmo doesn't detach/re-attach.
1263
+ *
1264
+ * It also refreshes the cached original transforms so a subsequent gizmo drag
1265
+ * computes its delta from the object's new position rather than jumping.
1266
+ */
1267
+ }, {
1268
+ key: "syncSelectionToObjects",
1269
+ value: function syncSelectionToObjects() {
1270
+ var _this$transformContro;
1271
+ if (!this.multiSelectionGroup || this.selectedObjects.length === 0) {
1272
+ return;
1273
+ }
1274
+
1275
+ // Recompute the centroid from the objects' current world positions.
1276
+ var centroid = new THREE.Vector3();
1277
+ this.selectedObjects.forEach(function (obj) {
1278
+ obj.updateMatrixWorld(true);
1279
+ var worldPos = new THREE.Vector3();
1280
+ obj.getWorldPosition(worldPos);
1281
+ centroid.add(worldPos);
1282
+ });
1283
+ centroid.divideScalar(this.selectedObjects.length);
1284
+
1285
+ // Move the group (and thus the attached gizmo) to the new centroid. Reset any
1286
+ // rotation so the gizmo matches the freshly-created-group convention.
1287
+ this.multiSelectionGroup.position.copy(centroid);
1288
+ this.multiSelectionGroup.rotation.set(0, 0, 0);
1289
+ this.multiSelectionGroup.updateMatrixWorld(true);
1290
+
1291
+ // Refresh cached originals so applyRealtimeTransform's delta stays correct on
1292
+ // the next drag (its "original centroid" is derived from these values).
1293
+ this.selectedObjects.forEach(function (obj) {
1294
+ obj.userData._multiSelectOriginalPosition = obj.position.clone();
1295
+ obj.userData._multiSelectOriginalRotation = obj.rotation.clone();
1296
+ obj.userData._multiSelectOriginalScale = obj.scale.clone();
1297
+ var worldPos = new THREE.Vector3();
1298
+ obj.getWorldPosition(worldPos);
1299
+ obj.userData._multiSelectOriginalWorldPosition = worldPos;
1300
+ });
1301
+
1302
+ // Keep the gizmo's world matrix in sync with the repositioned group.
1303
+ if ((_this$transformContro = this.transformControls) !== null && _this$transformContro !== void 0 && _this$transformContro.object) {
1304
+ this.transformControls.updateMatrixWorld(true);
1305
+ }
1306
+ }
1307
+
1249
1308
  /**
1250
1309
  * Create a bounding box that encompasses all selected objects
1251
1310
  */
@@ -1525,10 +1584,10 @@ var TransformControlsManager = /*#__PURE__*/function () {
1525
1584
  }, {
1526
1585
  key: "toggleTransformMode",
1527
1586
  value: function toggleTransformMode() {
1528
- var _this$transformContro;
1587
+ var _this$transformContro2;
1529
1588
  var newMode = this.currentMode === 'translate' ? 'rotate' : 'translate';
1530
1589
  this.setMode(newMode);
1531
- if ((_this$transformContro = this.transformControls) !== null && _this$transformContro !== void 0 && _this$transformContro.object) {
1590
+ if ((_this$transformContro2 = this.transformControls) !== null && _this$transformContro2 !== void 0 && _this$transformContro2.object) {
1532
1591
  this.transformControls.updateMatrixWorld(true);
1533
1592
  }
1534
1593
  }
@@ -399,13 +399,15 @@ var EnvironmentManager = /*#__PURE__*/function () {
399
399
  return this.createSkybox();
400
400
  case 1:
401
401
  this.setupLighting();
402
- _context4.n = 2;
403
- return this.addTexturedGround();
404
- case 2:
405
- // await this.addBrickWalls()
406
402
  this.addHorizonFog();
407
- console.log('Environment initialization completed');
408
- case 3:
403
+
404
+ // 2. Add ground (synchronously adds basic mesh)
405
+ // We do NOT await the texture loading here to speed up scene "Ready" state
406
+ this.addTexturedGround().catch(function (err) {
407
+ return console.warn('⚠️ Ground texture load failed:', err);
408
+ });
409
+ console.log('Environment initialization completed (textures loading in background)');
410
+ case 2:
409
411
  return _context4.a(2);
410
412
  }
411
413
  }, _callee4, this);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2112-lab/central-plant",
3
- "version": "0.3.56",
3
+ "version": "0.3.58",
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",