@2112-lab/central-plant 0.1.79 → 0.1.80
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.
- package/dist/bundle/index.js +103 -21
- package/dist/cjs/src/core/centralPlant.js +1 -1
- package/dist/cjs/src/core/centralPlantInternals.js +15 -2
- package/dist/cjs/src/managers/controls/componentDragManager.js +48 -13
- package/dist/cjs/src/managers/scene/animationManager.js +15 -6
- package/dist/cjs/src/rendering/rendering3D.js +25 -0
- package/dist/esm/src/core/centralPlant.js +1 -1
- package/dist/esm/src/core/centralPlantInternals.js +15 -2
- package/dist/esm/src/managers/controls/componentDragManager.js +48 -13
- package/dist/esm/src/managers/scene/animationManager.js +15 -6
- package/dist/esm/src/rendering/rendering3D.js +25 -0
- package/package.json +1 -1
package/dist/bundle/index.js
CHANGED
|
@@ -30911,12 +30911,21 @@ var AnimationManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
30911
30911
|
if (sceneViewer.performanceMonitorManager) {
|
|
30912
30912
|
sceneViewer.performanceMonitorManager.beginFrame();
|
|
30913
30913
|
}
|
|
30914
|
+
try {
|
|
30915
|
+
// Update controls
|
|
30916
|
+
sceneViewer.controls.update();
|
|
30914
30917
|
|
|
30915
|
-
|
|
30916
|
-
|
|
30917
|
-
|
|
30918
|
-
|
|
30919
|
-
|
|
30918
|
+
// Render the scene
|
|
30919
|
+
sceneViewer.renderer.render(sceneViewer.scene, sceneViewer.camera);
|
|
30920
|
+
} catch (renderError) {
|
|
30921
|
+
// Catch WebGL or rendering errors to prevent the animation loop from
|
|
30922
|
+
// producing a permanent white screen. Log once and continue so that
|
|
30923
|
+
// subsequent frames can recover if the problematic object is removed.
|
|
30924
|
+
if (!this._hasLoggedRenderError) {
|
|
30925
|
+
console.error('❌ AnimationManager: Render error caught — scene may contain disposed resources:', renderError);
|
|
30926
|
+
this._hasLoggedRenderError = true;
|
|
30927
|
+
}
|
|
30928
|
+
}
|
|
30920
30929
|
|
|
30921
30930
|
// Render tooltips if tooltipsManager exists
|
|
30922
30931
|
if (sceneViewer.tooltipsManager) {
|
|
@@ -31298,7 +31307,10 @@ var ComponentDragManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
31298
31307
|
}
|
|
31299
31308
|
this.dragData.previewObject = cachedModel.clone();
|
|
31300
31309
|
|
|
31301
|
-
// Clone materials to
|
|
31310
|
+
// Clone geometries and materials to fully isolate the preview from the cache.
|
|
31311
|
+
// Without this, disposing the preview would invalidate shared GPU buffers
|
|
31312
|
+
// and cause the scene to go white.
|
|
31313
|
+
this._cloneGeometries(this.dragData.previewObject);
|
|
31302
31314
|
this._cloneMaterials(this.dragData.previewObject);
|
|
31303
31315
|
|
|
31304
31316
|
// Store original colors BEFORE making transparent
|
|
@@ -31437,6 +31449,7 @@ var ComponentDragManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
31437
31449
|
return _context3.a(3, 11);
|
|
31438
31450
|
case 9:
|
|
31439
31451
|
deviceModel = cachedDevice.clone();
|
|
31452
|
+
this._cloneGeometries(deviceModel);
|
|
31440
31453
|
this._cloneMaterials(deviceModel);
|
|
31441
31454
|
this._storeOriginalColors(deviceModel);
|
|
31442
31455
|
deviceModel.userData = {
|
|
@@ -31489,16 +31502,38 @@ var ComponentDragManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
31489
31502
|
if (Array.isArray(child.material)) {
|
|
31490
31503
|
// Handle material arrays
|
|
31491
31504
|
child.material = child.material.map(function (material) {
|
|
31492
|
-
|
|
31505
|
+
var cloned = material.clone();
|
|
31506
|
+
cloned.userData._isClonedMaterial = true;
|
|
31507
|
+
return cloned;
|
|
31493
31508
|
});
|
|
31494
31509
|
} else {
|
|
31495
31510
|
// Handle single materials
|
|
31496
31511
|
child.material = child.material.clone();
|
|
31512
|
+
child.material.userData._isClonedMaterial = true;
|
|
31497
31513
|
}
|
|
31498
31514
|
}
|
|
31499
31515
|
});
|
|
31500
31516
|
}
|
|
31501
31517
|
|
|
31518
|
+
/**
|
|
31519
|
+
* Clone all geometries in an object hierarchy to avoid shared geometry buffer issues.
|
|
31520
|
+
* Three.js Object3D.clone() shares geometry references between the original and clone.
|
|
31521
|
+
* Disposing shared geometry invalidates GPU buffers for ALL objects using that geometry,
|
|
31522
|
+
* which causes the scene to go white. This method gives each mesh its own geometry copy.
|
|
31523
|
+
* @param {THREE.Object3D} object - The object to clone geometries for
|
|
31524
|
+
* @private
|
|
31525
|
+
*/
|
|
31526
|
+
}, {
|
|
31527
|
+
key: "_cloneGeometries",
|
|
31528
|
+
value: function _cloneGeometries(object) {
|
|
31529
|
+
object.traverse(function (child) {
|
|
31530
|
+
if (child.isMesh && child.geometry) {
|
|
31531
|
+
child.geometry = child.geometry.clone();
|
|
31532
|
+
child.userData._isClonedGeometry = true;
|
|
31533
|
+
}
|
|
31534
|
+
});
|
|
31535
|
+
}
|
|
31536
|
+
|
|
31502
31537
|
/**
|
|
31503
31538
|
* Store original colors from all materials in an object hierarchy
|
|
31504
31539
|
* @param {THREE.Object3D} object - The object to store colors from
|
|
@@ -31877,18 +31912,27 @@ var ComponentDragManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
31877
31912
|
if (this.dragData.previewObject) {
|
|
31878
31913
|
this.sceneViewer.scene.remove(this.dragData.previewObject);
|
|
31879
31914
|
|
|
31880
|
-
// Dispose of geometry and materials
|
|
31915
|
+
// Dispose of CLONED geometry and materials only.
|
|
31916
|
+
// Both geometries and materials were cloned during preview creation
|
|
31917
|
+
// via _cloneGeometries() and _cloneMaterials(), so they are safe to dispose.
|
|
31918
|
+
// IMPORTANT: Never dispose geometry/materials that are shared with the model cache.
|
|
31881
31919
|
this.dragData.previewObject.traverse(function (child) {
|
|
31882
|
-
if (child.
|
|
31883
|
-
|
|
31884
|
-
|
|
31885
|
-
|
|
31886
|
-
|
|
31887
|
-
|
|
31888
|
-
|
|
31889
|
-
|
|
31890
|
-
|
|
31891
|
-
|
|
31920
|
+
if (child.isMesh) {
|
|
31921
|
+
// Only dispose geometry if it was cloned for this preview
|
|
31922
|
+
if (child.geometry && child.userData._isClonedGeometry) {
|
|
31923
|
+
child.geometry.dispose();
|
|
31924
|
+
}
|
|
31925
|
+
// Only dispose material if it was cloned for this preview
|
|
31926
|
+
if (child.material) {
|
|
31927
|
+
if (Array.isArray(child.material)) {
|
|
31928
|
+
child.material.forEach(function (material) {
|
|
31929
|
+
if (material.userData._isClonedMaterial) {
|
|
31930
|
+
material.dispose();
|
|
31931
|
+
}
|
|
31932
|
+
});
|
|
31933
|
+
} else if (child.material.userData._isClonedMaterial) {
|
|
31934
|
+
child.material.dispose();
|
|
31935
|
+
}
|
|
31892
31936
|
}
|
|
31893
31937
|
}
|
|
31894
31938
|
});
|
|
@@ -35308,8 +35352,21 @@ var CentralPlantInternals = /*#__PURE__*/function () {
|
|
|
35308
35352
|
// Clone the cached model to create a new instance
|
|
35309
35353
|
var componentModel = cachedModel;
|
|
35310
35354
|
|
|
35311
|
-
//
|
|
35312
|
-
|
|
35355
|
+
// Clone materials to isolate this component instance from the model cache.
|
|
35356
|
+
// Without this, shared materials would cause visual side-effects when one
|
|
35357
|
+
// component's material is modified (e.g., selection highlighting).
|
|
35358
|
+
componentModel.traverse(function (child) {
|
|
35359
|
+
if (child.isMesh && child.material) {
|
|
35360
|
+
if (Array.isArray(child.material)) {
|
|
35361
|
+
child.material = child.material.map(function (m) {
|
|
35362
|
+
return m.clone();
|
|
35363
|
+
});
|
|
35364
|
+
} else {
|
|
35365
|
+
child.material = child.material.clone();
|
|
35366
|
+
}
|
|
35367
|
+
}
|
|
35368
|
+
});
|
|
35369
|
+
|
|
35313
35370
|
// Set the component properties
|
|
35314
35371
|
componentModel.uuid = componentId;
|
|
35315
35372
|
|
|
@@ -35666,7 +35723,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
35666
35723
|
* Initialize the CentralPlant manager
|
|
35667
35724
|
*
|
|
35668
35725
|
* @constructor
|
|
35669
|
-
* @version 0.1.
|
|
35726
|
+
* @version 0.1.80
|
|
35670
35727
|
* @updated 2025-10-22
|
|
35671
35728
|
*
|
|
35672
35729
|
* @description Creates a new CentralPlant instance and initializes internal managers and utilities.
|
|
@@ -41274,6 +41331,19 @@ var Rendering3D = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
41274
41331
|
this.renderer.useLegacyLights = false;
|
|
41275
41332
|
container.appendChild(this.renderer.domElement);
|
|
41276
41333
|
|
|
41334
|
+
// Register WebGL context lost/restored handlers for resilience.
|
|
41335
|
+
// If the GPU context is lost (e.g., due to disposed buffers or driver issues),
|
|
41336
|
+
// these handlers prevent a permanent white screen.
|
|
41337
|
+
this._onContextLost = function (event) {
|
|
41338
|
+
event.preventDefault();
|
|
41339
|
+
console.warn('⚠️ WebGL context lost — rendering paused. The browser may restore it automatically.');
|
|
41340
|
+
};
|
|
41341
|
+
this._onContextRestored = function () {
|
|
41342
|
+
console.log('✅ WebGL context restored — rendering will resume.');
|
|
41343
|
+
};
|
|
41344
|
+
this.renderer.domElement.addEventListener('webglcontextlost', this._onContextLost, false);
|
|
41345
|
+
this.renderer.domElement.addEventListener('webglcontextrestored', this._onContextRestored, false);
|
|
41346
|
+
|
|
41277
41347
|
// Register resources for automatic cleanup
|
|
41278
41348
|
this.registerDOMElement(this.renderer.domElement);
|
|
41279
41349
|
this.registerDisposable(this.renderer, 'WebGLRenderer');
|
|
@@ -41484,6 +41554,18 @@ var Rendering3D = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
41484
41554
|
frameTime: 0
|
|
41485
41555
|
};
|
|
41486
41556
|
|
|
41557
|
+
// Remove WebGL context event listeners
|
|
41558
|
+
if (this.renderer && this.renderer.domElement) {
|
|
41559
|
+
if (this._onContextLost) {
|
|
41560
|
+
this.renderer.domElement.removeEventListener('webglcontextlost', this._onContextLost, false);
|
|
41561
|
+
}
|
|
41562
|
+
if (this._onContextRestored) {
|
|
41563
|
+
this.renderer.domElement.removeEventListener('webglcontextrestored', this._onContextRestored, false);
|
|
41564
|
+
}
|
|
41565
|
+
}
|
|
41566
|
+
this._onContextLost = null;
|
|
41567
|
+
this._onContextRestored = null;
|
|
41568
|
+
|
|
41487
41569
|
// Clear post processing
|
|
41488
41570
|
this.postProcessing = {
|
|
41489
41571
|
composer: null,
|
|
@@ -19,7 +19,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
19
19
|
* Initialize the CentralPlant manager
|
|
20
20
|
*
|
|
21
21
|
* @constructor
|
|
22
|
-
* @version 0.1.
|
|
22
|
+
* @version 0.1.80
|
|
23
23
|
* @updated 2025-10-22
|
|
24
24
|
*
|
|
25
25
|
* @description Creates a new CentralPlant instance and initializes internal managers and utilities.
|
|
@@ -931,8 +931,21 @@ var CentralPlantInternals = /*#__PURE__*/function () {
|
|
|
931
931
|
// Clone the cached model to create a new instance
|
|
932
932
|
var componentModel = cachedModel;
|
|
933
933
|
|
|
934
|
-
//
|
|
935
|
-
|
|
934
|
+
// Clone materials to isolate this component instance from the model cache.
|
|
935
|
+
// Without this, shared materials would cause visual side-effects when one
|
|
936
|
+
// component's material is modified (e.g., selection highlighting).
|
|
937
|
+
componentModel.traverse(function (child) {
|
|
938
|
+
if (child.isMesh && child.material) {
|
|
939
|
+
if (Array.isArray(child.material)) {
|
|
940
|
+
child.material = child.material.map(function (m) {
|
|
941
|
+
return m.clone();
|
|
942
|
+
});
|
|
943
|
+
} else {
|
|
944
|
+
child.material = child.material.clone();
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
});
|
|
948
|
+
|
|
936
949
|
// Set the component properties
|
|
937
950
|
componentModel.uuid = componentId;
|
|
938
951
|
|
|
@@ -252,7 +252,10 @@ var ComponentDragManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
252
252
|
}
|
|
253
253
|
this.dragData.previewObject = cachedModel.clone();
|
|
254
254
|
|
|
255
|
-
// Clone materials to
|
|
255
|
+
// Clone geometries and materials to fully isolate the preview from the cache.
|
|
256
|
+
// Without this, disposing the preview would invalidate shared GPU buffers
|
|
257
|
+
// and cause the scene to go white.
|
|
258
|
+
this._cloneGeometries(this.dragData.previewObject);
|
|
256
259
|
this._cloneMaterials(this.dragData.previewObject);
|
|
257
260
|
|
|
258
261
|
// Store original colors BEFORE making transparent
|
|
@@ -391,6 +394,7 @@ var ComponentDragManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
391
394
|
return _context3.a(3, 11);
|
|
392
395
|
case 9:
|
|
393
396
|
deviceModel = cachedDevice.clone();
|
|
397
|
+
this._cloneGeometries(deviceModel);
|
|
394
398
|
this._cloneMaterials(deviceModel);
|
|
395
399
|
this._storeOriginalColors(deviceModel);
|
|
396
400
|
deviceModel.userData = {
|
|
@@ -443,16 +447,38 @@ var ComponentDragManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
443
447
|
if (Array.isArray(child.material)) {
|
|
444
448
|
// Handle material arrays
|
|
445
449
|
child.material = child.material.map(function (material) {
|
|
446
|
-
|
|
450
|
+
var cloned = material.clone();
|
|
451
|
+
cloned.userData._isClonedMaterial = true;
|
|
452
|
+
return cloned;
|
|
447
453
|
});
|
|
448
454
|
} else {
|
|
449
455
|
// Handle single materials
|
|
450
456
|
child.material = child.material.clone();
|
|
457
|
+
child.material.userData._isClonedMaterial = true;
|
|
451
458
|
}
|
|
452
459
|
}
|
|
453
460
|
});
|
|
454
461
|
}
|
|
455
462
|
|
|
463
|
+
/**
|
|
464
|
+
* Clone all geometries in an object hierarchy to avoid shared geometry buffer issues.
|
|
465
|
+
* Three.js Object3D.clone() shares geometry references between the original and clone.
|
|
466
|
+
* Disposing shared geometry invalidates GPU buffers for ALL objects using that geometry,
|
|
467
|
+
* which causes the scene to go white. This method gives each mesh its own geometry copy.
|
|
468
|
+
* @param {THREE.Object3D} object - The object to clone geometries for
|
|
469
|
+
* @private
|
|
470
|
+
*/
|
|
471
|
+
}, {
|
|
472
|
+
key: "_cloneGeometries",
|
|
473
|
+
value: function _cloneGeometries(object) {
|
|
474
|
+
object.traverse(function (child) {
|
|
475
|
+
if (child.isMesh && child.geometry) {
|
|
476
|
+
child.geometry = child.geometry.clone();
|
|
477
|
+
child.userData._isClonedGeometry = true;
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
456
482
|
/**
|
|
457
483
|
* Store original colors from all materials in an object hierarchy
|
|
458
484
|
* @param {THREE.Object3D} object - The object to store colors from
|
|
@@ -831,18 +857,27 @@ var ComponentDragManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
831
857
|
if (this.dragData.previewObject) {
|
|
832
858
|
this.sceneViewer.scene.remove(this.dragData.previewObject);
|
|
833
859
|
|
|
834
|
-
// Dispose of geometry and materials
|
|
860
|
+
// Dispose of CLONED geometry and materials only.
|
|
861
|
+
// Both geometries and materials were cloned during preview creation
|
|
862
|
+
// via _cloneGeometries() and _cloneMaterials(), so they are safe to dispose.
|
|
863
|
+
// IMPORTANT: Never dispose geometry/materials that are shared with the model cache.
|
|
835
864
|
this.dragData.previewObject.traverse(function (child) {
|
|
836
|
-
if (child.
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
865
|
+
if (child.isMesh) {
|
|
866
|
+
// Only dispose geometry if it was cloned for this preview
|
|
867
|
+
if (child.geometry && child.userData._isClonedGeometry) {
|
|
868
|
+
child.geometry.dispose();
|
|
869
|
+
}
|
|
870
|
+
// Only dispose material if it was cloned for this preview
|
|
871
|
+
if (child.material) {
|
|
872
|
+
if (Array.isArray(child.material)) {
|
|
873
|
+
child.material.forEach(function (material) {
|
|
874
|
+
if (material.userData._isClonedMaterial) {
|
|
875
|
+
material.dispose();
|
|
876
|
+
}
|
|
877
|
+
});
|
|
878
|
+
} else if (child.material.userData._isClonedMaterial) {
|
|
879
|
+
child.material.dispose();
|
|
880
|
+
}
|
|
846
881
|
}
|
|
847
882
|
}
|
|
848
883
|
});
|
|
@@ -62,12 +62,21 @@ var AnimationManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
62
62
|
if (sceneViewer.performanceMonitorManager) {
|
|
63
63
|
sceneViewer.performanceMonitorManager.beginFrame();
|
|
64
64
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
65
|
+
try {
|
|
66
|
+
// Update controls
|
|
67
|
+
sceneViewer.controls.update();
|
|
68
|
+
|
|
69
|
+
// Render the scene
|
|
70
|
+
sceneViewer.renderer.render(sceneViewer.scene, sceneViewer.camera);
|
|
71
|
+
} catch (renderError) {
|
|
72
|
+
// Catch WebGL or rendering errors to prevent the animation loop from
|
|
73
|
+
// producing a permanent white screen. Log once and continue so that
|
|
74
|
+
// subsequent frames can recover if the problematic object is removed.
|
|
75
|
+
if (!this._hasLoggedRenderError) {
|
|
76
|
+
console.error('❌ AnimationManager: Render error caught — scene may contain disposed resources:', renderError);
|
|
77
|
+
this._hasLoggedRenderError = true;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
71
80
|
|
|
72
81
|
// Render tooltips if tooltipsManager exists
|
|
73
82
|
if (sceneViewer.tooltipsManager) {
|
|
@@ -480,6 +480,19 @@ var Rendering3D = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
480
480
|
this.renderer.useLegacyLights = false;
|
|
481
481
|
container.appendChild(this.renderer.domElement);
|
|
482
482
|
|
|
483
|
+
// Register WebGL context lost/restored handlers for resilience.
|
|
484
|
+
// If the GPU context is lost (e.g., due to disposed buffers or driver issues),
|
|
485
|
+
// these handlers prevent a permanent white screen.
|
|
486
|
+
this._onContextLost = function (event) {
|
|
487
|
+
event.preventDefault();
|
|
488
|
+
console.warn('⚠️ WebGL context lost — rendering paused. The browser may restore it automatically.');
|
|
489
|
+
};
|
|
490
|
+
this._onContextRestored = function () {
|
|
491
|
+
console.log('✅ WebGL context restored — rendering will resume.');
|
|
492
|
+
};
|
|
493
|
+
this.renderer.domElement.addEventListener('webglcontextlost', this._onContextLost, false);
|
|
494
|
+
this.renderer.domElement.addEventListener('webglcontextrestored', this._onContextRestored, false);
|
|
495
|
+
|
|
483
496
|
// Register resources for automatic cleanup
|
|
484
497
|
this.registerDOMElement(this.renderer.domElement);
|
|
485
498
|
this.registerDisposable(this.renderer, 'WebGLRenderer');
|
|
@@ -690,6 +703,18 @@ var Rendering3D = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
690
703
|
frameTime: 0
|
|
691
704
|
};
|
|
692
705
|
|
|
706
|
+
// Remove WebGL context event listeners
|
|
707
|
+
if (this.renderer && this.renderer.domElement) {
|
|
708
|
+
if (this._onContextLost) {
|
|
709
|
+
this.renderer.domElement.removeEventListener('webglcontextlost', this._onContextLost, false);
|
|
710
|
+
}
|
|
711
|
+
if (this._onContextRestored) {
|
|
712
|
+
this.renderer.domElement.removeEventListener('webglcontextrestored', this._onContextRestored, false);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
this._onContextLost = null;
|
|
716
|
+
this._onContextRestored = null;
|
|
717
|
+
|
|
693
718
|
// Clear post processing
|
|
694
719
|
this.postProcessing = {
|
|
695
720
|
composer: null,
|
|
@@ -15,7 +15,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
15
15
|
* Initialize the CentralPlant manager
|
|
16
16
|
*
|
|
17
17
|
* @constructor
|
|
18
|
-
* @version 0.1.
|
|
18
|
+
* @version 0.1.80
|
|
19
19
|
* @updated 2025-10-22
|
|
20
20
|
*
|
|
21
21
|
* @description Creates a new CentralPlant instance and initializes internal managers and utilities.
|
|
@@ -907,8 +907,21 @@ var CentralPlantInternals = /*#__PURE__*/function () {
|
|
|
907
907
|
// Clone the cached model to create a new instance
|
|
908
908
|
var componentModel = cachedModel;
|
|
909
909
|
|
|
910
|
-
//
|
|
911
|
-
|
|
910
|
+
// Clone materials to isolate this component instance from the model cache.
|
|
911
|
+
// Without this, shared materials would cause visual side-effects when one
|
|
912
|
+
// component's material is modified (e.g., selection highlighting).
|
|
913
|
+
componentModel.traverse(function (child) {
|
|
914
|
+
if (child.isMesh && child.material) {
|
|
915
|
+
if (Array.isArray(child.material)) {
|
|
916
|
+
child.material = child.material.map(function (m) {
|
|
917
|
+
return m.clone();
|
|
918
|
+
});
|
|
919
|
+
} else {
|
|
920
|
+
child.material = child.material.clone();
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
});
|
|
924
|
+
|
|
912
925
|
// Set the component properties
|
|
913
926
|
componentModel.uuid = componentId;
|
|
914
927
|
|
|
@@ -228,7 +228,10 @@ var ComponentDragManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
228
228
|
}
|
|
229
229
|
this.dragData.previewObject = cachedModel.clone();
|
|
230
230
|
|
|
231
|
-
// Clone materials to
|
|
231
|
+
// Clone geometries and materials to fully isolate the preview from the cache.
|
|
232
|
+
// Without this, disposing the preview would invalidate shared GPU buffers
|
|
233
|
+
// and cause the scene to go white.
|
|
234
|
+
this._cloneGeometries(this.dragData.previewObject);
|
|
232
235
|
this._cloneMaterials(this.dragData.previewObject);
|
|
233
236
|
|
|
234
237
|
// Store original colors BEFORE making transparent
|
|
@@ -367,6 +370,7 @@ var ComponentDragManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
367
370
|
return _context3.a(3, 11);
|
|
368
371
|
case 9:
|
|
369
372
|
deviceModel = cachedDevice.clone();
|
|
373
|
+
this._cloneGeometries(deviceModel);
|
|
370
374
|
this._cloneMaterials(deviceModel);
|
|
371
375
|
this._storeOriginalColors(deviceModel);
|
|
372
376
|
deviceModel.userData = {
|
|
@@ -419,16 +423,38 @@ var ComponentDragManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
419
423
|
if (Array.isArray(child.material)) {
|
|
420
424
|
// Handle material arrays
|
|
421
425
|
child.material = child.material.map(function (material) {
|
|
422
|
-
|
|
426
|
+
var cloned = material.clone();
|
|
427
|
+
cloned.userData._isClonedMaterial = true;
|
|
428
|
+
return cloned;
|
|
423
429
|
});
|
|
424
430
|
} else {
|
|
425
431
|
// Handle single materials
|
|
426
432
|
child.material = child.material.clone();
|
|
433
|
+
child.material.userData._isClonedMaterial = true;
|
|
427
434
|
}
|
|
428
435
|
}
|
|
429
436
|
});
|
|
430
437
|
}
|
|
431
438
|
|
|
439
|
+
/**
|
|
440
|
+
* Clone all geometries in an object hierarchy to avoid shared geometry buffer issues.
|
|
441
|
+
* Three.js Object3D.clone() shares geometry references between the original and clone.
|
|
442
|
+
* Disposing shared geometry invalidates GPU buffers for ALL objects using that geometry,
|
|
443
|
+
* which causes the scene to go white. This method gives each mesh its own geometry copy.
|
|
444
|
+
* @param {THREE.Object3D} object - The object to clone geometries for
|
|
445
|
+
* @private
|
|
446
|
+
*/
|
|
447
|
+
}, {
|
|
448
|
+
key: "_cloneGeometries",
|
|
449
|
+
value: function _cloneGeometries(object) {
|
|
450
|
+
object.traverse(function (child) {
|
|
451
|
+
if (child.isMesh && child.geometry) {
|
|
452
|
+
child.geometry = child.geometry.clone();
|
|
453
|
+
child.userData._isClonedGeometry = true;
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
|
|
432
458
|
/**
|
|
433
459
|
* Store original colors from all materials in an object hierarchy
|
|
434
460
|
* @param {THREE.Object3D} object - The object to store colors from
|
|
@@ -807,18 +833,27 @@ var ComponentDragManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
807
833
|
if (this.dragData.previewObject) {
|
|
808
834
|
this.sceneViewer.scene.remove(this.dragData.previewObject);
|
|
809
835
|
|
|
810
|
-
// Dispose of geometry and materials
|
|
836
|
+
// Dispose of CLONED geometry and materials only.
|
|
837
|
+
// Both geometries and materials were cloned during preview creation
|
|
838
|
+
// via _cloneGeometries() and _cloneMaterials(), so they are safe to dispose.
|
|
839
|
+
// IMPORTANT: Never dispose geometry/materials that are shared with the model cache.
|
|
811
840
|
this.dragData.previewObject.traverse(function (child) {
|
|
812
|
-
if (child.
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
841
|
+
if (child.isMesh) {
|
|
842
|
+
// Only dispose geometry if it was cloned for this preview
|
|
843
|
+
if (child.geometry && child.userData._isClonedGeometry) {
|
|
844
|
+
child.geometry.dispose();
|
|
845
|
+
}
|
|
846
|
+
// Only dispose material if it was cloned for this preview
|
|
847
|
+
if (child.material) {
|
|
848
|
+
if (Array.isArray(child.material)) {
|
|
849
|
+
child.material.forEach(function (material) {
|
|
850
|
+
if (material.userData._isClonedMaterial) {
|
|
851
|
+
material.dispose();
|
|
852
|
+
}
|
|
853
|
+
});
|
|
854
|
+
} else if (child.material.userData._isClonedMaterial) {
|
|
855
|
+
child.material.dispose();
|
|
856
|
+
}
|
|
822
857
|
}
|
|
823
858
|
}
|
|
824
859
|
});
|
|
@@ -58,12 +58,21 @@ var AnimationManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
58
58
|
if (sceneViewer.performanceMonitorManager) {
|
|
59
59
|
sceneViewer.performanceMonitorManager.beginFrame();
|
|
60
60
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
61
|
+
try {
|
|
62
|
+
// Update controls
|
|
63
|
+
sceneViewer.controls.update();
|
|
64
|
+
|
|
65
|
+
// Render the scene
|
|
66
|
+
sceneViewer.renderer.render(sceneViewer.scene, sceneViewer.camera);
|
|
67
|
+
} catch (renderError) {
|
|
68
|
+
// Catch WebGL or rendering errors to prevent the animation loop from
|
|
69
|
+
// producing a permanent white screen. Log once and continue so that
|
|
70
|
+
// subsequent frames can recover if the problematic object is removed.
|
|
71
|
+
if (!this._hasLoggedRenderError) {
|
|
72
|
+
console.error('❌ AnimationManager: Render error caught — scene may contain disposed resources:', renderError);
|
|
73
|
+
this._hasLoggedRenderError = true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
67
76
|
|
|
68
77
|
// Render tooltips if tooltipsManager exists
|
|
69
78
|
if (sceneViewer.tooltipsManager) {
|
|
@@ -456,6 +456,19 @@ var Rendering3D = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
456
456
|
this.renderer.useLegacyLights = false;
|
|
457
457
|
container.appendChild(this.renderer.domElement);
|
|
458
458
|
|
|
459
|
+
// Register WebGL context lost/restored handlers for resilience.
|
|
460
|
+
// If the GPU context is lost (e.g., due to disposed buffers or driver issues),
|
|
461
|
+
// these handlers prevent a permanent white screen.
|
|
462
|
+
this._onContextLost = function (event) {
|
|
463
|
+
event.preventDefault();
|
|
464
|
+
console.warn('⚠️ WebGL context lost — rendering paused. The browser may restore it automatically.');
|
|
465
|
+
};
|
|
466
|
+
this._onContextRestored = function () {
|
|
467
|
+
console.log('✅ WebGL context restored — rendering will resume.');
|
|
468
|
+
};
|
|
469
|
+
this.renderer.domElement.addEventListener('webglcontextlost', this._onContextLost, false);
|
|
470
|
+
this.renderer.domElement.addEventListener('webglcontextrestored', this._onContextRestored, false);
|
|
471
|
+
|
|
459
472
|
// Register resources for automatic cleanup
|
|
460
473
|
this.registerDOMElement(this.renderer.domElement);
|
|
461
474
|
this.registerDisposable(this.renderer, 'WebGLRenderer');
|
|
@@ -666,6 +679,18 @@ var Rendering3D = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
666
679
|
frameTime: 0
|
|
667
680
|
};
|
|
668
681
|
|
|
682
|
+
// Remove WebGL context event listeners
|
|
683
|
+
if (this.renderer && this.renderer.domElement) {
|
|
684
|
+
if (this._onContextLost) {
|
|
685
|
+
this.renderer.domElement.removeEventListener('webglcontextlost', this._onContextLost, false);
|
|
686
|
+
}
|
|
687
|
+
if (this._onContextRestored) {
|
|
688
|
+
this.renderer.domElement.removeEventListener('webglcontextrestored', this._onContextRestored, false);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
this._onContextLost = null;
|
|
692
|
+
this._onContextRestored = null;
|
|
693
|
+
|
|
669
694
|
// Clear post processing
|
|
670
695
|
this.postProcessing = {
|
|
671
696
|
composer: null,
|