@2112-lab/central-plant 0.1.78 → 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 +647 -334
- package/dist/cjs/src/core/centralPlant.js +1 -1
- package/dist/cjs/src/core/centralPlantInternals.js +21 -2
- package/dist/cjs/src/core/sceneViewer.js +11 -1
- package/dist/cjs/src/index.js +2 -0
- package/dist/cjs/src/managers/controls/componentDragManager.js +48 -13
- package/dist/cjs/src/managers/environment/environmentManager.js +188 -310
- package/dist/cjs/src/managers/environment/textureConfig.js +23 -2
- package/dist/cjs/src/managers/scene/animationManager.js +20 -6
- package/dist/cjs/src/managers/scene/componentTooltipManager.js +340 -0
- 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 +21 -2
- package/dist/esm/src/core/sceneViewer.js +11 -1
- package/dist/esm/src/index.js +1 -0
- package/dist/esm/src/managers/controls/componentDragManager.js +48 -13
- package/dist/esm/src/managers/environment/environmentManager.js +189 -311
- package/dist/esm/src/managers/environment/textureConfig.js +24 -3
- package/dist/esm/src/managers/scene/animationManager.js +20 -6
- package/dist/esm/src/managers/scene/componentTooltipManager.js +316 -0
- package/dist/esm/src/rendering/rendering3D.js +25 -0
- package/package.json +1 -1
|
@@ -25,6 +25,8 @@ function _interopNamespace(e) {
|
|
|
25
25
|
|
|
26
26
|
var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
|
|
27
27
|
|
|
28
|
+
/** Maximum anisotropy to apply (caps GPU memory for mip storage) */
|
|
29
|
+
var MAX_ANISOTROPY = 4;
|
|
28
30
|
var TEXTURE_SETS = {
|
|
29
31
|
// Light metallic texture using the gravel_embedded_concrete with metallic properties
|
|
30
32
|
light_metal: {
|
|
@@ -161,10 +163,29 @@ function _loadTextureSet() {
|
|
|
161
163
|
return Promise.all(promises);
|
|
162
164
|
case 2:
|
|
163
165
|
loadedTextures = _context2.v;
|
|
164
|
-
textureMap = Object.fromEntries(loadedTextures); // Apply texture settings
|
|
165
|
-
Object.
|
|
166
|
+
textureMap = Object.fromEntries(loadedTextures); // Apply texture settings with memory-conscious defaults
|
|
167
|
+
Object.entries(textureMap).forEach(function (_ref4) {
|
|
168
|
+
var _ref5 = _rollupPluginBabelHelpers.slicedToArray(_ref4, 2),
|
|
169
|
+
type = _ref5[0],
|
|
170
|
+
texture = _ref5[1];
|
|
166
171
|
texture.wrapS = texture.wrapT = THREE__namespace.RepeatWrapping;
|
|
167
172
|
texture.repeat.set(textureSet.repeat.x, textureSet.repeat.y);
|
|
173
|
+
|
|
174
|
+
// Correct colour-space: diffuse is sRGB, everything else is linear data
|
|
175
|
+
if (type === 'diffuse') {
|
|
176
|
+
texture.colorSpace = THREE__namespace.SRGBColorSpace;
|
|
177
|
+
} else {
|
|
178
|
+
texture.colorSpace = THREE__namespace.LinearSRGBColorSpace;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Roughness maps don't benefit much from mipmaps — skip them to save GPU memory
|
|
182
|
+
if (type === 'roughness') {
|
|
183
|
+
texture.generateMipmaps = false;
|
|
184
|
+
texture.minFilter = THREE__namespace.LinearFilter;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Cap anisotropy to balance quality vs memory
|
|
188
|
+
texture.anisotropy = MAX_ANISOTROPY;
|
|
168
189
|
});
|
|
169
190
|
console.log("Texture set ".concat(setName, " loaded successfully"));
|
|
170
191
|
return _context2.a(2, {
|
|
@@ -62,18 +62,32 @@ 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) {
|
|
74
83
|
sceneViewer.tooltipsManager.render();
|
|
75
84
|
}
|
|
76
85
|
|
|
86
|
+
// Update component tooltip screen position
|
|
87
|
+
if (sceneViewer.componentTooltipManager) {
|
|
88
|
+
sceneViewer.componentTooltipManager.update();
|
|
89
|
+
}
|
|
90
|
+
|
|
77
91
|
// End performance monitoring frame
|
|
78
92
|
if (sceneViewer.performanceMonitorManager) {
|
|
79
93
|
sceneViewer.performanceMonitorManager.endFrame();
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var _rollupPluginBabelHelpers = require('../../../_virtual/_rollupPluginBabelHelpers.js');
|
|
6
|
+
var THREE = require('three');
|
|
7
|
+
var baseDisposable = require('../../core/baseDisposable.js');
|
|
8
|
+
|
|
9
|
+
function _interopNamespace(e) {
|
|
10
|
+
if (e && e.__esModule) return e;
|
|
11
|
+
var n = Object.create(null);
|
|
12
|
+
if (e) {
|
|
13
|
+
Object.keys(e).forEach(function (k) {
|
|
14
|
+
if (k !== 'default') {
|
|
15
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
16
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
17
|
+
enumerable: true,
|
|
18
|
+
get: function () { return e[k]; }
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
n["default"] = e;
|
|
24
|
+
return Object.freeze(n);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Inline styles (injected once into the document head)
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
var TOOLTIP_STYLES = "\n.cp-tooltip {\n position: absolute;\n pointer-events: auto;\n z-index: 10000;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 13px;\n line-height: 1.4;\n user-select: none;\n min-width: 200px;\n max-width: 280px;\n transform: translate(-50%, -100%);\n margin-top: -12px;\n transition: opacity 0.15s ease;\n}\n\n.cp-tooltip__card {\n background: rgba(28, 32, 40, 0.95);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 10px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.45);\n overflow: hidden;\n}\n\n/* \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__header {\n padding: 10px 14px;\n font-weight: 600;\n font-size: 14px;\n color: #e8ecf1;\n background: rgba(255, 255, 255, 0.04);\n border-bottom: 1px solid rgba(255, 255, 255, 0.08);\n letter-spacing: 0.2px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* \u2500\u2500 I/O Devices section \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__io-section {\n position: relative;\n}\n\n.cp-tooltip__io-trigger {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 14px;\n color: #a4adba;\n cursor: pointer;\n transition: background 0.12s ease, color 0.12s ease;\n}\n\n.cp-tooltip__io-trigger:hover {\n background: rgba(255, 255, 255, 0.06);\n color: #e8ecf1;\n}\n\n.cp-tooltip__io-trigger-label {\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: 0.6px;\n font-weight: 500;\n}\n\n.cp-tooltip__io-arrow {\n font-size: 10px;\n transition: transform 0.2s ease;\n}\n\n.cp-tooltip__io-section.expanded .cp-tooltip__io-arrow {\n transform: rotate(90deg);\n}\n\n/* \u2500\u2500 Device list \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__device-list {\n max-height: 0;\n overflow: hidden;\n transition: max-height 0.25s ease;\n}\n\n.cp-tooltip__io-section.expanded .cp-tooltip__device-list {\n max-height: 300px;\n}\n\n.cp-tooltip__device-item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 14px 6px 20px;\n color: #c1c8d1;\n font-size: 12px;\n border-top: 1px solid rgba(255, 255, 255, 0.04);\n transition: background 0.1s ease;\n}\n\n.cp-tooltip__device-item:hover {\n background: rgba(255, 255, 255, 0.04);\n}\n\n.cp-tooltip__device-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: #4fc3f7;\n flex-shrink: 0;\n}\n\n.cp-tooltip__device-name {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* \u2500\u2500 Empty state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__no-devices {\n padding: 8px 14px;\n color: #6b7280;\n font-size: 12px;\n font-style: italic;\n}\n\n/* \u2500\u2500 Caret arrow pointing down \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__caret {\n width: 0;\n height: 0;\n border-left: 7px solid transparent;\n border-right: 7px solid transparent;\n border-top: 7px solid rgba(28, 32, 40, 0.95);\n margin: 0 auto;\n position: relative;\n top: -1px;\n}\n";
|
|
33
|
+
var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
34
|
+
/**
|
|
35
|
+
* @param {Object} sceneViewer - The sceneViewer instance
|
|
36
|
+
*/
|
|
37
|
+
function ComponentTooltipManager(sceneViewer) {
|
|
38
|
+
var _this;
|
|
39
|
+
_rollupPluginBabelHelpers.classCallCheck(this, ComponentTooltipManager);
|
|
40
|
+
_this = _rollupPluginBabelHelpers.callSuper(this, ComponentTooltipManager);
|
|
41
|
+
_this.sceneViewer = sceneViewer;
|
|
42
|
+
_this.selectedObject = null;
|
|
43
|
+
_this.tooltipEl = null;
|
|
44
|
+
_this._styleInjected = false;
|
|
45
|
+
_this._ioExpanded = false;
|
|
46
|
+
_this._injectStyles();
|
|
47
|
+
return _this;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// -----------------------------------------------------------------------
|
|
51
|
+
// Lifecycle
|
|
52
|
+
// -----------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Called automatically by BaseDisposable.dispose()
|
|
56
|
+
* @override
|
|
57
|
+
*/
|
|
58
|
+
_rollupPluginBabelHelpers.inherits(ComponentTooltipManager, _BaseDisposable);
|
|
59
|
+
return _rollupPluginBabelHelpers.createClass(ComponentTooltipManager, [{
|
|
60
|
+
key: "_doDispose",
|
|
61
|
+
value: function _doDispose() {
|
|
62
|
+
this.hide();
|
|
63
|
+
this._removeStyleTag();
|
|
64
|
+
this.nullifyProperties('sceneViewer', 'selectedObject', 'tooltipEl');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// -----------------------------------------------------------------------
|
|
68
|
+
// Public API
|
|
69
|
+
// -----------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Should be called when an object is selected or deselected.
|
|
73
|
+
* @param {THREE.Object3D|null} object
|
|
74
|
+
*/
|
|
75
|
+
}, {
|
|
76
|
+
key: "onSelectionChanged",
|
|
77
|
+
value: function onSelectionChanged(object) {
|
|
78
|
+
var _object$userData;
|
|
79
|
+
if (!object) {
|
|
80
|
+
this.hide();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
var objectType = (_object$userData = object.userData) === null || _object$userData === void 0 ? void 0 : _object$userData.objectType;
|
|
84
|
+
if (objectType !== 'component') {
|
|
85
|
+
this.hide();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
this.selectedObject = object;
|
|
89
|
+
this._ioExpanded = false;
|
|
90
|
+
this._buildTooltip(object);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Update tooltip screen position. Call once per frame (from the animation loop).
|
|
95
|
+
*/
|
|
96
|
+
}, {
|
|
97
|
+
key: "update",
|
|
98
|
+
value: function update() {
|
|
99
|
+
if (!this.tooltipEl || !this.selectedObject) return;
|
|
100
|
+
this._positionTooltip();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Remove the tooltip from the DOM.
|
|
105
|
+
*/
|
|
106
|
+
}, {
|
|
107
|
+
key: "hide",
|
|
108
|
+
value: function hide() {
|
|
109
|
+
if (this.tooltipEl) {
|
|
110
|
+
this.tooltipEl.remove();
|
|
111
|
+
this.tooltipEl = null;
|
|
112
|
+
}
|
|
113
|
+
this.selectedObject = null;
|
|
114
|
+
this._ioExpanded = false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// -----------------------------------------------------------------------
|
|
118
|
+
// Internal — build
|
|
119
|
+
// -----------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
/** Inject the stylesheet once into the document head */
|
|
122
|
+
}, {
|
|
123
|
+
key: "_injectStyles",
|
|
124
|
+
value: function _injectStyles() {
|
|
125
|
+
if (this._styleInjected) return;
|
|
126
|
+
this._styleTag = document.createElement('style');
|
|
127
|
+
this._styleTag.setAttribute('data-cp-tooltip', '');
|
|
128
|
+
this._styleTag.textContent = TOOLTIP_STYLES;
|
|
129
|
+
document.head.appendChild(this._styleTag);
|
|
130
|
+
this._styleInjected = true;
|
|
131
|
+
this.registerDOMElement(this._styleTag);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Remove the injected stylesheet */
|
|
135
|
+
}, {
|
|
136
|
+
key: "_removeStyleTag",
|
|
137
|
+
value: function _removeStyleTag() {
|
|
138
|
+
if (this._styleTag && this._styleTag.parentNode) {
|
|
139
|
+
this._styleTag.parentNode.removeChild(this._styleTag);
|
|
140
|
+
}
|
|
141
|
+
this._styleTag = null;
|
|
142
|
+
this._styleInjected = false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Gather I/O device children from a component's Three.js hierarchy.
|
|
147
|
+
* @param {THREE.Object3D} object
|
|
148
|
+
* @returns {{ label: string, deviceId: string }[]}
|
|
149
|
+
*/
|
|
150
|
+
}, {
|
|
151
|
+
key: "_getIODevices",
|
|
152
|
+
value: function _getIODevices(object) {
|
|
153
|
+
var devices = [];
|
|
154
|
+
object.traverse(function (child) {
|
|
155
|
+
var _child$userData;
|
|
156
|
+
if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'io-device') {
|
|
157
|
+
devices.push({
|
|
158
|
+
label: child.userData.attachmentLabel || child.name || child.userData.deviceId || 'Unknown Device',
|
|
159
|
+
deviceId: child.userData.deviceId || ''
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
return devices;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Build and show the tooltip for the given object.
|
|
168
|
+
* @param {THREE.Object3D} object
|
|
169
|
+
*/
|
|
170
|
+
}, {
|
|
171
|
+
key: "_buildTooltip",
|
|
172
|
+
value: function _buildTooltip(object) {
|
|
173
|
+
var _this2 = this;
|
|
174
|
+
// Remove any existing tooltip first
|
|
175
|
+
this.hide();
|
|
176
|
+
// Re-assign selected object since hide() clears it
|
|
177
|
+
this.selectedObject = object;
|
|
178
|
+
var container = this._getContainer();
|
|
179
|
+
if (!container) return;
|
|
180
|
+
|
|
181
|
+
// Ensure the container supports absolute positioning
|
|
182
|
+
var containerStyle = window.getComputedStyle(container);
|
|
183
|
+
if (containerStyle.position === 'static') {
|
|
184
|
+
container.style.position = 'relative';
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Gather data — extract the friendly name, stripping the " (component-id)" suffix
|
|
188
|
+
var rawName = object.name || '';
|
|
189
|
+
var componentName = rawName.replace(/\s*\([^)]*\)\s*$/, '') || 'Unnamed Component';
|
|
190
|
+
var devices = this._getIODevices(object);
|
|
191
|
+
var isSmart = devices.length > 0;
|
|
192
|
+
|
|
193
|
+
// Root element
|
|
194
|
+
var root = document.createElement('div');
|
|
195
|
+
root.className = 'cp-tooltip';
|
|
196
|
+
|
|
197
|
+
// Card
|
|
198
|
+
var card = document.createElement('div');
|
|
199
|
+
card.className = 'cp-tooltip__card';
|
|
200
|
+
|
|
201
|
+
// Header
|
|
202
|
+
var header = document.createElement('div');
|
|
203
|
+
header.className = 'cp-tooltip__header';
|
|
204
|
+
header.textContent = componentName;
|
|
205
|
+
card.appendChild(header);
|
|
206
|
+
|
|
207
|
+
// I/O Devices section
|
|
208
|
+
if (isSmart) {
|
|
209
|
+
var ioSection = document.createElement('div');
|
|
210
|
+
ioSection.className = 'cp-tooltip__io-section';
|
|
211
|
+
|
|
212
|
+
// Trigger row
|
|
213
|
+
var trigger = document.createElement('div');
|
|
214
|
+
trigger.className = 'cp-tooltip__io-trigger';
|
|
215
|
+
var label = document.createElement('span');
|
|
216
|
+
label.className = 'cp-tooltip__io-trigger-label';
|
|
217
|
+
label.textContent = "I/O Devices (".concat(devices.length, ")");
|
|
218
|
+
var arrow = document.createElement('span');
|
|
219
|
+
arrow.className = 'cp-tooltip__io-arrow';
|
|
220
|
+
arrow.textContent = '▶';
|
|
221
|
+
trigger.appendChild(label);
|
|
222
|
+
trigger.appendChild(arrow);
|
|
223
|
+
|
|
224
|
+
// Device list
|
|
225
|
+
var list = document.createElement('div');
|
|
226
|
+
list.className = 'cp-tooltip__device-list';
|
|
227
|
+
devices.forEach(function (device) {
|
|
228
|
+
var item = document.createElement('div');
|
|
229
|
+
item.className = 'cp-tooltip__device-item';
|
|
230
|
+
var dot = document.createElement('span');
|
|
231
|
+
dot.className = 'cp-tooltip__device-dot';
|
|
232
|
+
var name = document.createElement('span');
|
|
233
|
+
name.className = 'cp-tooltip__device-name';
|
|
234
|
+
name.textContent = device.label;
|
|
235
|
+
item.appendChild(dot);
|
|
236
|
+
item.appendChild(name);
|
|
237
|
+
list.appendChild(item);
|
|
238
|
+
});
|
|
239
|
+
ioSection.appendChild(trigger);
|
|
240
|
+
ioSection.appendChild(list);
|
|
241
|
+
|
|
242
|
+
// Hover expand/collapse
|
|
243
|
+
trigger.addEventListener('mouseenter', function () {
|
|
244
|
+
ioSection.classList.add('expanded');
|
|
245
|
+
_this2._ioExpanded = true;
|
|
246
|
+
});
|
|
247
|
+
ioSection.addEventListener('mouseleave', function () {
|
|
248
|
+
ioSection.classList.remove('expanded');
|
|
249
|
+
_this2._ioExpanded = false;
|
|
250
|
+
});
|
|
251
|
+
card.appendChild(ioSection);
|
|
252
|
+
} else {
|
|
253
|
+
// Non-smart: show empty state
|
|
254
|
+
var noDevices = document.createElement('div');
|
|
255
|
+
noDevices.className = 'cp-tooltip__no-devices';
|
|
256
|
+
noDevices.textContent = 'No I/O devices attached';
|
|
257
|
+
card.appendChild(noDevices);
|
|
258
|
+
}
|
|
259
|
+
root.appendChild(card);
|
|
260
|
+
|
|
261
|
+
// Caret
|
|
262
|
+
var caret = document.createElement('div');
|
|
263
|
+
caret.className = 'cp-tooltip__caret';
|
|
264
|
+
root.appendChild(caret);
|
|
265
|
+
|
|
266
|
+
// Prevent clicks on tooltip from propagating to the scene
|
|
267
|
+
root.addEventListener('pointerdown', function (e) {
|
|
268
|
+
return e.stopPropagation();
|
|
269
|
+
});
|
|
270
|
+
root.addEventListener('click', function (e) {
|
|
271
|
+
return e.stopPropagation();
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// Add to the renderer container so coordinates are relative to the viewport
|
|
275
|
+
container.appendChild(root);
|
|
276
|
+
this.tooltipEl = root;
|
|
277
|
+
|
|
278
|
+
// Initial positioning
|
|
279
|
+
this._positionTooltip();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// -----------------------------------------------------------------------
|
|
283
|
+
// Internal — positioning
|
|
284
|
+
// -----------------------------------------------------------------------
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Project the selected object's world position to screen space and
|
|
288
|
+
* position the tooltip element accordingly.
|
|
289
|
+
*/
|
|
290
|
+
}, {
|
|
291
|
+
key: "_positionTooltip",
|
|
292
|
+
value: function _positionTooltip() {
|
|
293
|
+
var _this$sceneViewer, _this$sceneViewer2;
|
|
294
|
+
if (!this.tooltipEl || !this.selectedObject) return;
|
|
295
|
+
var container = this._getContainer();
|
|
296
|
+
var camera = (_this$sceneViewer = this.sceneViewer) === null || _this$sceneViewer === void 0 ? void 0 : _this$sceneViewer.camera;
|
|
297
|
+
var renderer = (_this$sceneViewer2 = this.sceneViewer) === null || _this$sceneViewer2 === void 0 ? void 0 : _this$sceneViewer2.renderer;
|
|
298
|
+
if (!container || !camera || !renderer) return;
|
|
299
|
+
|
|
300
|
+
// Compute bounding box to position above the component
|
|
301
|
+
var box = new THREE__namespace.Box3().setFromObject(this.selectedObject);
|
|
302
|
+
var center = box.getCenter(new THREE__namespace.Vector3());
|
|
303
|
+
// Use top of bounding box (Z-up)
|
|
304
|
+
var topZ = box.max.z;
|
|
305
|
+
var worldPos = new THREE__namespace.Vector3(center.x, center.y, topZ);
|
|
306
|
+
|
|
307
|
+
// Project to NDC
|
|
308
|
+
var ndc = worldPos.clone().project(camera);
|
|
309
|
+
|
|
310
|
+
// NDC to pixel coords relative to the container
|
|
311
|
+
var rect = container.getBoundingClientRect();
|
|
312
|
+
var x = (ndc.x + 1) / 2 * rect.width;
|
|
313
|
+
var y = (-ndc.y + 1) / 2 * rect.height;
|
|
314
|
+
|
|
315
|
+
// Hide if behind camera
|
|
316
|
+
if (ndc.z > 1) {
|
|
317
|
+
this.tooltipEl.style.display = 'none';
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
this.tooltipEl.style.display = '';
|
|
321
|
+
this.tooltipEl.style.left = "".concat(x, "px");
|
|
322
|
+
this.tooltipEl.style.top = "".concat(y, "px");
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Get the DOM element where the Three.js canvas currently lives.
|
|
327
|
+
* In QuadViewport the canvas is moved out of the original hidden
|
|
328
|
+
* container, so we must use the renderer's actual parent element.
|
|
329
|
+
* @returns {HTMLElement|null}
|
|
330
|
+
*/
|
|
331
|
+
}, {
|
|
332
|
+
key: "_getContainer",
|
|
333
|
+
value: function _getContainer() {
|
|
334
|
+
var _this$sceneViewer3;
|
|
335
|
+
return ((_this$sceneViewer3 = this.sceneViewer) === null || _this$sceneViewer3 === void 0 || (_this$sceneViewer3 = _this$sceneViewer3.renderer) === null || _this$sceneViewer3 === void 0 || (_this$sceneViewer3 = _this$sceneViewer3.domElement) === null || _this$sceneViewer3 === void 0 ? void 0 : _this$sceneViewer3.parentElement) || null;
|
|
336
|
+
}
|
|
337
|
+
}]);
|
|
338
|
+
}(baseDisposable.BaseDisposable);
|
|
339
|
+
|
|
340
|
+
exports.ComponentTooltipManager = ComponentTooltipManager;
|
|
@@ -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.
|
|
@@ -19,6 +19,7 @@ import { AnimationManager } from '../managers/scene/animationManager.js';
|
|
|
19
19
|
import { CameraControlsManager } from '../managers/controls/cameraControlsManager.js';
|
|
20
20
|
import { ComponentDragManager } from '../managers/controls/componentDragManager.js';
|
|
21
21
|
import { SceneTooltipsManager } from '../managers/scene/sceneTooltipsManager.js';
|
|
22
|
+
import { ComponentTooltipManager } from '../managers/scene/componentTooltipManager.js';
|
|
22
23
|
import { Viewport2DManager } from '../managers/scene/viewport2DManager.js';
|
|
23
24
|
import { generateUuidFromName, getHardcodedUuid, findObjectByHardcodedUuid, generateUniqueComponentId } from '../utils/nameUtils.js';
|
|
24
25
|
import modelPreloader from '../rendering/modelPreloader.js';
|
|
@@ -151,6 +152,11 @@ var CentralPlantInternals = /*#__PURE__*/function () {
|
|
|
151
152
|
this.centralPlant.managers.tooltipsManager = new SceneTooltipsManager(this.centralPlant.sceneViewer.$refs.container, this.centralPlant.sceneViewer.camera, this.centralPlant.sceneViewer.scene);
|
|
152
153
|
this.centralPlant.sceneViewer.tooltipsManager = this.centralPlant.managers.tooltipsManager;
|
|
153
154
|
console.log('🔍 Tooltip manager initialized:', this.centralPlant.managers.tooltipsManager);
|
|
155
|
+
|
|
156
|
+
// Initialize the component tooltip manager (screen-space tooltip on selection)
|
|
157
|
+
this.centralPlant.managers.componentTooltipManager = new ComponentTooltipManager(this.centralPlant.sceneViewer);
|
|
158
|
+
this.centralPlant.sceneViewer.componentTooltipManager = this.centralPlant.managers.componentTooltipManager;
|
|
159
|
+
console.log('🔍 Component tooltip manager initialized');
|
|
154
160
|
}
|
|
155
161
|
}
|
|
156
162
|
|
|
@@ -901,8 +907,21 @@ var CentralPlantInternals = /*#__PURE__*/function () {
|
|
|
901
907
|
// Clone the cached model to create a new instance
|
|
902
908
|
var componentModel = cachedModel;
|
|
903
909
|
|
|
904
|
-
//
|
|
905
|
-
|
|
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
|
+
|
|
906
925
|
// Set the component properties
|
|
907
926
|
componentModel.uuid = componentId;
|
|
908
927
|
|
|
@@ -98,7 +98,7 @@ var sceneViewer = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
98
98
|
this.centralPlant.attachToComponent();
|
|
99
99
|
|
|
100
100
|
// Sync our managers tracking object after attachment
|
|
101
|
-
managerKeys = ['threeJSResourceManager', 'performanceMonitorManager', 'settingsManager', 'sceneExportManager', 'componentManager', 'sceneInitializationManager', 'environmentManager', 'keyboardControlsManager', 'pathfindingManager', 'sceneOperationsManager', 'animationManager', 'cameraControlsManager', 'componentDragManager', 'tooltipsManager']; // Populate our managers tracking object
|
|
101
|
+
managerKeys = ['threeJSResourceManager', 'performanceMonitorManager', 'settingsManager', 'sceneExportManager', 'componentManager', 'sceneInitializationManager', 'environmentManager', 'keyboardControlsManager', 'pathfindingManager', 'sceneOperationsManager', 'animationManager', 'cameraControlsManager', 'componentDragManager', 'tooltipsManager', 'componentTooltipManager']; // Populate our managers tracking object
|
|
102
102
|
managerKeys.forEach(function (key) {
|
|
103
103
|
if (_this2[key]) {
|
|
104
104
|
_this2.managers[key] = _this2[key];
|
|
@@ -376,6 +376,10 @@ var sceneViewer = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
376
376
|
this.transformManager.on({
|
|
377
377
|
onModeChange: this.onModeChange.bind(this),
|
|
378
378
|
onSelectionChanged: function onSelectionChanged(object) {
|
|
379
|
+
// Update the component tooltip on selection changes
|
|
380
|
+
if (_this4.componentTooltipManager) {
|
|
381
|
+
_this4.componentTooltipManager.onSelectionChanged(object);
|
|
382
|
+
}
|
|
379
383
|
if (object) {
|
|
380
384
|
var _object$userData;
|
|
381
385
|
// Object selected
|
|
@@ -801,6 +805,12 @@ var sceneViewer = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
801
805
|
this.tooltipsManager = null;
|
|
802
806
|
}
|
|
803
807
|
|
|
808
|
+
// Clean up component tooltip manager
|
|
809
|
+
if (this.componentTooltipManager) {
|
|
810
|
+
this.componentTooltipManager.dispose('ComponentTooltipManager');
|
|
811
|
+
this.componentTooltipManager = null;
|
|
812
|
+
}
|
|
813
|
+
|
|
804
814
|
// Fallback cleanup if Three.js resource manager not available
|
|
805
815
|
if (!this.threeJSResourceManager) {
|
|
806
816
|
// Fallback cleanup if disposal manager not available
|
package/dist/esm/src/index.js
CHANGED
|
@@ -7,6 +7,7 @@ export { SceneInitializationManager } from './managers/scene/sceneInitialization
|
|
|
7
7
|
export { SceneOperationsManager } from './managers/scene/sceneOperationsManager.js';
|
|
8
8
|
export { SceneExportManager } from './managers/scene/sceneExportManager.js';
|
|
9
9
|
export { SceneTooltipsManager } from './managers/scene/sceneTooltipsManager.js';
|
|
10
|
+
export { ComponentTooltipManager } from './managers/scene/componentTooltipManager.js';
|
|
10
11
|
export { SceneHierarchyManager } from './managers/scene/sceneHierarchyManager.js';
|
|
11
12
|
export { ComponentManager } from './managers/components/componentManager.js';
|
|
12
13
|
export { AnimationManager } from './managers/scene/animationManager.js';
|
|
@@ -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
|
});
|