iobroker.mywebui 1.42.22 → 1.42.24
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/io-package.json
CHANGED
package/package.json
CHANGED
package/www/3d-editor/index.html
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"three/webgpu": "../3d-lib/three.module.js",
|
|
16
16
|
"three/addons/": "../3d-lib/jsm/",
|
|
17
17
|
"three/examples/jsm/": "../3d-lib/jsm/",
|
|
18
|
-
"three-gpu-pathtracer": "data:text/javascript,export
|
|
18
|
+
"three-gpu-pathtracer": "data:text/javascript,export class WebGLPathTracer{constructor(){}setScene(){}renderSample(){}updateCamera(){}updateEnvironment(){}updateMaterials(){}get samples(){return 0;}filterGlossyFactor=0.5;}",
|
|
19
19
|
"three-mesh-bvh": "data:text/javascript,export const MeshBVH={};export const acceleratedRaycast=()=>{};export const computeBoundsTree=()=>{};export const disposeBoundsTree=()=>{};"
|
|
20
20
|
}
|
|
21
21
|
}
|
|
@@ -16,6 +16,15 @@ function Script( editor ) {
|
|
|
16
16
|
container.setBackgroundColor( '#272822' );
|
|
17
17
|
container.setDisplay( 'none' );
|
|
18
18
|
|
|
19
|
+
// If CodeMirror is not loaded (e.g. iobroker integration uses external Monaco editor),
|
|
20
|
+
// return an empty container — script editing is handled via parent postMessage bridge.
|
|
21
|
+
if ( typeof CodeMirror === 'undefined' ) {
|
|
22
|
+
|
|
23
|
+
signals.editorCleared.add( function () { container.setDisplay( 'none' ); } );
|
|
24
|
+
return container;
|
|
25
|
+
|
|
26
|
+
}
|
|
27
|
+
|
|
19
28
|
const header = new UIPanel();
|
|
20
29
|
header.setPadding( '10px' );
|
|
21
30
|
container.add( header );
|
|
@@ -3,247 +3,139 @@ import { iobrokerHandler } from "../common/IobrokerHandler.js";
|
|
|
3
3
|
|
|
4
4
|
export class IobrokerWebui3DScreenViewer extends BaseCustomWebComponentConstructorAppend {
|
|
5
5
|
static template = html`
|
|
6
|
-
<div id="container" style="width:100%;height:100%;position:relative;overflow:hidden;background:#
|
|
6
|
+
<div id="container" style="width:100%;height:100%;position:relative;overflow:hidden;background:#1a1a1a;">
|
|
7
7
|
<div id="viewport" style="width:100%;height:100%;"></div>
|
|
8
|
-
<div id="status" style="position:absolute;bottom:10px;left:10px;background:rgba(0,0,0,0.7);color:#0f0;padding:10px;font-family:monospace;font-size:12px;z-index:10;border-radius:3px;">Ready</div>
|
|
9
8
|
</div>
|
|
10
9
|
`;
|
|
11
10
|
|
|
12
11
|
static style = css`
|
|
13
|
-
:host {
|
|
14
|
-
display: block;
|
|
15
|
-
width: 100%;
|
|
16
|
-
height: 100%;
|
|
17
|
-
background: #333;
|
|
18
|
-
}
|
|
12
|
+
:host { display:block;width:100%;height:100%; }
|
|
19
13
|
`;
|
|
20
14
|
|
|
21
15
|
constructor() {
|
|
22
16
|
super();
|
|
23
|
-
this.
|
|
24
|
-
this.
|
|
25
|
-
this.renderer = null;
|
|
26
|
-
this.controls = null;
|
|
27
|
-
this.THREE = null;
|
|
28
|
-
this.gltfLoader = null;
|
|
29
|
-
this.sceneData = null;
|
|
30
|
-
this.sceneNameAttr = null;
|
|
17
|
+
this._animating = false;
|
|
18
|
+
this._renderer = null;
|
|
31
19
|
}
|
|
32
20
|
|
|
33
21
|
async connectedCallback() {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
22
|
+
const sceneName = this.getAttribute('scene-name');
|
|
23
|
+
const sceneType = this.getAttribute('scene-type') || '3dscreen';
|
|
24
|
+
if (!sceneName) return;
|
|
25
|
+
await this._init(sceneName, sceneType);
|
|
26
|
+
}
|
|
39
27
|
|
|
40
|
-
|
|
28
|
+
disconnectedCallback() {
|
|
29
|
+
this._animating = false;
|
|
30
|
+
if (this._renderer) { this._renderer.dispose(); this._renderer.domElement.remove(); this._renderer = null; }
|
|
31
|
+
window.removeEventListener('resize', this._onResize);
|
|
41
32
|
}
|
|
42
33
|
|
|
43
|
-
async
|
|
34
|
+
async _init(sceneName, sceneType) {
|
|
44
35
|
try {
|
|
45
|
-
const statusEl = this._getDomElement('status');
|
|
46
|
-
statusEl.textContent = '⏳ Loading...';
|
|
47
|
-
|
|
48
|
-
// Load Three.js
|
|
49
36
|
const THREE = await import('three');
|
|
50
37
|
const { OrbitControls } = await import('three/addons/controls/OrbitControls.js');
|
|
51
|
-
const { GLTFLoader } = await import('three/addons/loaders/GLTFLoader.js');
|
|
52
38
|
|
|
53
|
-
|
|
54
|
-
|
|
39
|
+
// Load scene data
|
|
40
|
+
const sceneData = sceneType === '3dcontrol'
|
|
41
|
+
? await iobrokerHandler.getWebuiObject('3dcontrol', sceneName)
|
|
42
|
+
: await iobrokerHandler.get3DScreen(sceneName);
|
|
55
43
|
|
|
56
|
-
|
|
57
|
-
this.initThreeJS(THREE, OrbitControls);
|
|
44
|
+
if (!sceneData) { console.warn('3D scene not found:', sceneName); return; }
|
|
58
45
|
|
|
59
|
-
|
|
60
|
-
|
|
46
|
+
const viewport = this._getDomElement('viewport');
|
|
47
|
+
const w = viewport.clientWidth || 800;
|
|
48
|
+
const h = viewport.clientHeight || 600;
|
|
61
49
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
50
|
+
// Renderer
|
|
51
|
+
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
52
|
+
renderer.setPixelRatio(window.devicePixelRatio);
|
|
53
|
+
renderer.setSize(w, h);
|
|
54
|
+
renderer.shadowMap.enabled = true;
|
|
55
|
+
viewport.appendChild(renderer.domElement);
|
|
56
|
+
this._renderer = renderer;
|
|
68
57
|
|
|
69
|
-
|
|
70
|
-
const viewport = this._getDomElement('viewport');
|
|
71
|
-
const width = viewport.clientWidth;
|
|
72
|
-
const height = viewport.clientHeight;
|
|
73
|
-
|
|
74
|
-
// Scene
|
|
75
|
-
this.scene = new THREE.Scene();
|
|
76
|
-
this.scene.background = new THREE.Color(0x333333);
|
|
77
|
-
|
|
78
|
-
// Camera
|
|
79
|
-
this.camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
|
|
80
|
-
this.camera.position.set(10, 10, 10);
|
|
81
|
-
|
|
82
|
-
// Renderer
|
|
83
|
-
this.renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
84
|
-
this.renderer.setSize(width, height);
|
|
85
|
-
this.renderer.shadowMap.enabled = true;
|
|
86
|
-
viewport.appendChild(this.renderer.domElement);
|
|
87
|
-
|
|
88
|
-
// Lighting
|
|
89
|
-
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
|
|
90
|
-
this.scene.add(ambientLight);
|
|
91
|
-
|
|
92
|
-
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
|
|
93
|
-
directionalLight.position.set(10, 10, 10);
|
|
94
|
-
directionalLight.castShadow = true;
|
|
95
|
-
directionalLight.shadow.mapSize.width = 2048;
|
|
96
|
-
directionalLight.shadow.mapSize.height = 2048;
|
|
97
|
-
this.scene.add(directionalLight);
|
|
98
|
-
|
|
99
|
-
// Grid
|
|
100
|
-
const gridHelper = new THREE.GridHelper(20, 20, 0x888888, 0x444444);
|
|
101
|
-
gridHelper.position.y = -0.01;
|
|
102
|
-
this.scene.add(gridHelper);
|
|
103
|
-
|
|
104
|
-
// Axes
|
|
105
|
-
const axesHelper = new THREE.AxesHelper(5);
|
|
106
|
-
this.scene.add(axesHelper);
|
|
107
|
-
|
|
108
|
-
// Controls
|
|
109
|
-
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
|
|
110
|
-
this.controls.autoRotate = false;
|
|
111
|
-
this.controls.enableZoom = true;
|
|
112
|
-
this.controls.enablePan = true;
|
|
113
|
-
|
|
114
|
-
// Animation loop
|
|
115
|
-
let animating = true;
|
|
116
|
-
const animate = () => {
|
|
117
|
-
if (animating) {
|
|
118
|
-
requestAnimationFrame(animate);
|
|
119
|
-
this.controls.update();
|
|
120
|
-
this.renderer.render(this.scene, this.camera);
|
|
121
|
-
}
|
|
122
|
-
};
|
|
123
|
-
animate();
|
|
58
|
+
let scene, camera;
|
|
124
59
|
|
|
125
|
-
|
|
126
|
-
|
|
60
|
+
if (sceneData.threeScene && sceneData.threeScene.scene) {
|
|
61
|
+
// New format: three.js editor JSON → ObjectLoader
|
|
62
|
+
const loader = new THREE.ObjectLoader();
|
|
63
|
+
scene = loader.parse(sceneData.threeScene.scene);
|
|
127
64
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
async loadScene(sceneName) {
|
|
132
|
-
try {
|
|
133
|
-
this.sceneData = await iobrokerHandler.get3DScreen(sceneName);
|
|
134
|
-
if (!this.sceneData) {
|
|
135
|
-
throw new Error(`Scene not found: ${sceneName}`);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Load assets
|
|
139
|
-
if (this.sceneData.assets) {
|
|
140
|
-
for (const asset of this.sceneData.assets) {
|
|
141
|
-
if (asset.type === 'model' && asset.glbPath) {
|
|
142
|
-
await this.loadAsset(asset);
|
|
143
|
-
}
|
|
65
|
+
// Restore camera
|
|
66
|
+
if (sceneData.threeScene.camera) {
|
|
67
|
+
try { camera = loader.parse(sceneData.threeScene.camera); } catch (_) {}
|
|
144
68
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (this.sceneData.lights) {
|
|
149
|
-
for (const light of this.sceneData.lights) {
|
|
150
|
-
this.addLightToScene(light);
|
|
69
|
+
if (!camera || !(camera.isCamera)) {
|
|
70
|
+
camera = new THREE.PerspectiveCamera(50, w / h, 0.1, 10000);
|
|
71
|
+
camera.position.set(0, 0, 50);
|
|
151
72
|
}
|
|
152
|
-
}
|
|
153
73
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
);
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
);
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
model.scale.set(asset.scale.x, asset.scale.y, asset.scale.z);
|
|
184
|
-
this.scene.add(model);
|
|
185
|
-
console.log('✅ Asset loaded:', asset.name);
|
|
186
|
-
resolve();
|
|
187
|
-
},
|
|
188
|
-
undefined,
|
|
189
|
-
(error) => {
|
|
190
|
-
console.error('Error loading asset:', error);
|
|
191
|
-
reject(error);
|
|
74
|
+
// Renderer settings from project
|
|
75
|
+
const proj = sceneData.threeScene.project || {};
|
|
76
|
+
if (proj.shadows !== undefined) renderer.shadowMap.enabled = proj.shadows;
|
|
77
|
+
if (proj.physicallyCorrectLights) renderer.useLegacyLights = false;
|
|
78
|
+
} else {
|
|
79
|
+
// Old format backward compatibility
|
|
80
|
+
scene = new THREE.Scene();
|
|
81
|
+
scene.background = new THREE.Color(0x333333);
|
|
82
|
+
camera = new THREE.PerspectiveCamera(75, w / h, 0.1, 1000);
|
|
83
|
+
camera.position.set(10, 10, 10);
|
|
84
|
+
|
|
85
|
+
scene.add(new THREE.AmbientLight(0xffffff, 0.6));
|
|
86
|
+
const dir = new THREE.DirectionalLight(0xffffff, 0.8);
|
|
87
|
+
dir.position.set(10, 10, 10);
|
|
88
|
+
scene.add(dir);
|
|
89
|
+
|
|
90
|
+
if (sceneData.assets) {
|
|
91
|
+
const { GLTFLoader } = await import('three/addons/loaders/GLTFLoader.js');
|
|
92
|
+
const loader = new GLTFLoader();
|
|
93
|
+
for (const asset of sceneData.assets) {
|
|
94
|
+
if (asset.type === 'model' && asset.glbPath) {
|
|
95
|
+
await new Promise(res => loader.load(asset.glbPath, gltf => {
|
|
96
|
+
const m = gltf.scene;
|
|
97
|
+
if (asset.position) m.position.set(asset.position.x, asset.position.y, asset.position.z);
|
|
98
|
+
if (asset.scale) m.scale.set(asset.scale.x, asset.scale.y, asset.scale.z);
|
|
99
|
+
scene.add(m); res();
|
|
100
|
+
}, undefined, res));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
192
103
|
}
|
|
193
|
-
|
|
194
|
-
});
|
|
195
|
-
}
|
|
104
|
+
}
|
|
196
105
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
switch (light.type) {
|
|
201
|
-
case 'ambient':
|
|
202
|
-
threeLight = new this.THREE.AmbientLight(light.color, light.intensity);
|
|
203
|
-
break;
|
|
204
|
-
case 'directional':
|
|
205
|
-
threeLight = new this.THREE.DirectionalLight(light.color, light.intensity);
|
|
206
|
-
if (light.position) {
|
|
207
|
-
threeLight.position.set(light.position.x, light.position.y, light.position.z);
|
|
208
|
-
}
|
|
209
|
-
if (light.castShadow) {
|
|
210
|
-
threeLight.castShadow = true;
|
|
211
|
-
}
|
|
212
|
-
break;
|
|
213
|
-
case 'point':
|
|
214
|
-
threeLight = new this.THREE.PointLight(light.color, light.intensity);
|
|
215
|
-
if (light.position) {
|
|
216
|
-
threeLight.position.set(light.position.x, light.position.y, light.position.z);
|
|
217
|
-
}
|
|
218
|
-
break;
|
|
219
|
-
case 'spot':
|
|
220
|
-
threeLight = new this.THREE.SpotLight(light.color, light.intensity);
|
|
221
|
-
if (light.position) {
|
|
222
|
-
threeLight.position.set(light.position.x, light.position.y, light.position.z);
|
|
223
|
-
}
|
|
224
|
-
break;
|
|
225
|
-
}
|
|
106
|
+
camera.aspect = w / h;
|
|
107
|
+
camera.updateProjectionMatrix();
|
|
226
108
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
}
|
|
109
|
+
const controls = new OrbitControls(camera, renderer.domElement);
|
|
110
|
+
controls.enableDamping = true;
|
|
231
111
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
112
|
+
// Run scene script if present
|
|
113
|
+
if (sceneData.script) {
|
|
114
|
+
try {
|
|
115
|
+
const fn = new Function('THREE', 'scene', 'camera', 'renderer', 'controls', sceneData.script);
|
|
116
|
+
fn(THREE, scene, camera, renderer, controls);
|
|
117
|
+
} catch (e) { console.warn('3D scene script error:', e); }
|
|
118
|
+
}
|
|
236
119
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
120
|
+
this._animating = true;
|
|
121
|
+
this._onResize = () => {
|
|
122
|
+
const vw = viewport.clientWidth, vh = viewport.clientHeight;
|
|
123
|
+
camera.aspect = vw / vh;
|
|
124
|
+
camera.updateProjectionMatrix();
|
|
125
|
+
renderer.setSize(vw, vh);
|
|
126
|
+
};
|
|
127
|
+
window.addEventListener('resize', this._onResize);
|
|
128
|
+
|
|
129
|
+
const animate = () => {
|
|
130
|
+
if (!this._animating) return;
|
|
131
|
+
requestAnimationFrame(animate);
|
|
132
|
+
controls.update();
|
|
133
|
+
renderer.render(scene, camera);
|
|
134
|
+
};
|
|
135
|
+
animate();
|
|
241
136
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
if (this.renderer) {
|
|
245
|
-
this.renderer.dispose();
|
|
246
|
-
this.renderer.domElement.remove();
|
|
137
|
+
} catch (err) {
|
|
138
|
+
console.error('3D Viewer error:', err);
|
|
247
139
|
}
|
|
248
140
|
}
|
|
249
141
|
}
|