iobroker.mywebui 1.42.32 → 1.42.34

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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "mywebui",
4
- "version": "1.42.32",
4
+ "version": "1.42.34",
5
5
  "titleLang": {
6
6
  "en": "mywebui",
7
7
  "de": "mywebui",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.mywebui",
3
- "version": "1.42.32",
3
+ "version": "1.42.34",
4
4
  "description": "ioBroker mywebui - Custom edited mywebui by gokturk413 with 3D Editor",
5
5
  "type": "module",
6
6
  "main": "dist/backend/main.js",
@@ -5,17 +5,43 @@ export class IobrokerWebui3DScreenViewer extends BaseCustomWebComponentConstruct
5
5
  static template = html`
6
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
+
9
+ <!-- Camera label (top-left) -->
10
+ <div id="camLabel" style="
11
+ position:absolute;top:10px;left:12px;
12
+ color:#ddd;font-size:11px;font-family:monospace;
13
+ background:rgba(0,0,0,0.45);padding:2px 7px;border-radius:3px;
14
+ pointer-events:none;user-select:none;">Persp</div>
15
+
16
+ <!-- Orientation gizmo canvas (top-right) -->
17
+ <canvas id="gizmoCanvas" width="96" height="96" style="
18
+ position:absolute;top:8px;right:8px;
19
+ width:96px;height:96px;cursor:pointer;
20
+ border-radius:4px;background:transparent;"></canvas>
21
+
22
+ <!-- Camera buttons (bottom-right) -->
23
+ <div id="camButtons" style="
24
+ position:absolute;bottom:12px;right:12px;
25
+ display:flex;gap:5px;flex-wrap:wrap;justify-content:flex-end;"></div>
8
26
  </div>
9
27
  `;
10
28
 
11
29
  static style = css`
12
30
  :host { display:block;width:100%;height:100%; }
31
+ #camButtons button {
32
+ background:rgba(30,30,30,0.82);color:#ddd;
33
+ border:1px solid #555;border-radius:4px;
34
+ padding:4px 11px;font-size:11px;cursor:pointer;
35
+ font-family:monospace;white-space:nowrap;
36
+ }
37
+ #camButtons button:hover { background:rgba(80,80,80,0.9);color:#fff; }
38
+ #camButtons button.active { background:rgba(0,120,200,0.85);border-color:#1177bb;color:#fff; }
13
39
  `;
14
40
 
15
41
  constructor() {
16
42
  super();
17
43
  this._animating = false;
18
- this._renderer = null;
44
+ this._renderer = null;
19
45
  }
20
46
 
21
47
  async connectedCallback() {
@@ -27,7 +53,7 @@ export class IobrokerWebui3DScreenViewer extends BaseCustomWebComponentConstruct
27
53
 
28
54
  disconnectedCallback() {
29
55
  this._animating = false;
30
- if (this._cleanup) { this._cleanup(); this._cleanup = null; }
56
+ if (this._cleanup) { this._cleanup(); this._cleanup = null; }
31
57
  if (this._renderer) { this._renderer.dispose(); this._renderer.domElement.remove(); this._renderer = null; }
32
58
  window.removeEventListener('resize', this._onResize);
33
59
  }
@@ -37,7 +63,6 @@ export class IobrokerWebui3DScreenViewer extends BaseCustomWebComponentConstruct
37
63
  const THREE = await import('three');
38
64
  const { OrbitControls } = await import('three/addons/controls/OrbitControls.js');
39
65
 
40
- // Load scene data
41
66
  const sceneData = sceneType === '3dcontrol'
42
67
  ? await iobrokerHandler.getWebuiObject('3dcontrol', sceneName)
43
68
  : await iobrokerHandler.get3DScreen(sceneName);
@@ -48,52 +73,42 @@ export class IobrokerWebui3DScreenViewer extends BaseCustomWebComponentConstruct
48
73
  const w = viewport.clientWidth || 800;
49
74
  const h = viewport.clientHeight || 600;
50
75
 
51
- // Renderer
52
76
  const renderer = new THREE.WebGLRenderer({ antialias: true });
53
77
  renderer.setPixelRatio(window.devicePixelRatio);
54
78
  renderer.setSize(w, h);
55
79
  renderer.shadowMap.enabled = true;
80
+ renderer.autoClear = false;
56
81
  viewport.appendChild(renderer.domElement);
57
82
  this._renderer = renderer;
58
83
 
59
84
  let scene, camera;
60
85
 
61
86
  if (sceneData.threeScene && sceneData.threeScene.scene) {
62
- // New format: three.js editor JSON → ObjectLoader
63
87
  const loader = new THREE.ObjectLoader();
64
88
  scene = loader.parse(sceneData.threeScene.scene);
65
89
 
66
- // Restore camera
67
90
  if (sceneData.threeScene.camera) {
68
91
  try { camera = loader.parse(sceneData.threeScene.camera); } catch (_) {}
69
92
  }
70
- if (!camera || !(camera.isCamera)) {
93
+ if (!camera || !camera.isCamera) {
71
94
  camera = new THREE.PerspectiveCamera(50, w / h, 0.1, 10000);
72
95
  camera.position.set(0, 0, 50);
73
96
  }
74
97
 
75
- // Renderer settings from project
76
98
  const proj = sceneData.threeScene.project || {};
77
- if (proj.shadows !== undefined) renderer.shadowMap.enabled = proj.shadows;
78
- if (proj.physicallyCorrectLights) renderer.useLegacyLights = false;
99
+ if (proj.shadows !== undefined) renderer.shadowMap.enabled = proj.shadows;
100
+ if (proj.physicallyCorrectLights) renderer.useLegacyLights = false;
79
101
  } else {
80
- // Old format backward compatibility
81
- scene = new THREE.Scene();
82
- scene.background = new THREE.Color(0x333333);
102
+ scene = new THREE.Scene();
83
103
  camera = new THREE.PerspectiveCamera(75, w / h, 0.1, 1000);
84
104
  camera.position.set(10, 10, 10);
85
105
 
86
- scene.add(new THREE.AmbientLight(0xffffff, 0.6));
87
- const dir = new THREE.DirectionalLight(0xffffff, 0.8);
88
- dir.position.set(10, 10, 10);
89
- scene.add(dir);
90
-
91
106
  if (sceneData.assets) {
92
107
  const { GLTFLoader } = await import('three/addons/loaders/GLTFLoader.js');
93
- const loader = new GLTFLoader();
108
+ const ldr = new GLTFLoader();
94
109
  for (const asset of sceneData.assets) {
95
110
  if (asset.type === 'model' && asset.glbPath) {
96
- await new Promise(res => loader.load(asset.glbPath, gltf => {
111
+ await new Promise(res => ldr.load(asset.glbPath, gltf => {
97
112
  const m = gltf.scene;
98
113
  if (asset.position) m.position.set(asset.position.x, asset.position.y, asset.position.z);
99
114
  if (asset.scale) m.scale.set(asset.scale.x, asset.scale.y, asset.scale.z);
@@ -104,27 +119,124 @@ export class IobrokerWebui3DScreenViewer extends BaseCustomWebComponentConstruct
104
119
  }
105
120
  }
106
121
 
122
+ if (!scene.background) scene.background = new THREE.Color(0x222222);
123
+
124
+ const existingLights = [];
125
+ scene.traverse(o => { if (o.isLight) existingLights.push(o); });
126
+ if (existingLights.length === 0) {
127
+ scene.add(new THREE.AmbientLight(0xffffff, 0.6));
128
+ const dl = new THREE.DirectionalLight(0xffffff, 1.0);
129
+ dl.position.set(5, 10, 7.5);
130
+ scene.add(dl);
131
+ }
132
+
107
133
  camera.aspect = w / h;
108
134
  camera.updateProjectionMatrix();
109
135
 
110
136
  const controls = new OrbitControls(camera, renderer.domElement);
111
137
  controls.enableDamping = true;
112
138
 
113
- // Script context helpers
139
+ // Auto-fit camera to scene mesh bounds
140
+ {
141
+ const box = new THREE.Box3();
142
+ scene.traverse(o => { if (o.isMesh || o.isPoints || o.isLine) box.expandByObject(o); });
143
+ if (!box.isEmpty()) {
144
+ const center = new THREE.Vector3();
145
+ const size = new THREE.Vector3();
146
+ box.getCenter(center);
147
+ box.getSize(size);
148
+ const maxDim = Math.max(size.x, size.y, size.z);
149
+ const dist = maxDim / (2 * Math.tan((camera.fov * Math.PI / 180) / 2)) * 1.5;
150
+ camera.position.set(center.x + dist * 0.6, center.y + dist * 0.4, center.z + dist);
151
+ camera.near = dist / 100;
152
+ camera.far = dist * 100;
153
+ camera.updateProjectionMatrix();
154
+ controls.target.copy(center);
155
+ controls.update();
156
+ }
157
+ }
158
+
159
+ // ── Camera label ─────────────────────────────────────
160
+ const camLabel = this._getDomElement('camLabel');
161
+ const updateCamLabel = (cam) => {
162
+ if (!camLabel) return;
163
+ camLabel.textContent = cam.isOrthographicCamera ? 'Ortho' : 'Persp';
164
+ };
165
+ updateCamLabel(camera);
166
+
167
+ // ── Collect named cameras from scene ─────────────────
168
+ let activeCamera = camera;
169
+ const sceneCameras = [];
170
+ scene.traverse(obj => {
171
+ if (obj.isCamera && obj !== camera) sceneCameras.push(obj);
172
+ });
173
+
174
+ const switchCamera = (cam, btnEl) => {
175
+ activeCamera = cam;
176
+ activeCamera.aspect = viewport.clientWidth / viewport.clientHeight;
177
+ activeCamera.updateProjectionMatrix?.();
178
+ controls.object = activeCamera;
179
+ controls.update();
180
+ updateCamLabel(activeCamera);
181
+ // Update active button style
182
+ this._getDomElement('camButtons')
183
+ ?.querySelectorAll('button')
184
+ .forEach(b => b.classList.remove('active'));
185
+ if (btnEl) btnEl.classList.add('active');
186
+ };
187
+
188
+ // Build camera buttons
189
+ const camBtns = this._getDomElement('camButtons');
190
+ if (camBtns) {
191
+ // Default camera button
192
+ const btnDefault = document.createElement('button');
193
+ btnDefault.textContent = 'HOME';
194
+ btnDefault.classList.add('active');
195
+ btnDefault.addEventListener('click', () => switchCamera(camera, btnDefault));
196
+ camBtns.appendChild(btnDefault);
197
+
198
+ // Named scene cameras
199
+ sceneCameras.forEach((cam, i) => {
200
+ const btn = document.createElement('button');
201
+ btn.textContent = cam.name || ('CAM ' + (i + 1));
202
+ btn.addEventListener('click', () => switchCamera(cam, btn));
203
+ camBtns.appendChild(btn);
204
+ });
205
+ }
206
+
207
+ // ── Orientation gizmo ────────────────────────────────
208
+ let viewHelper = null;
209
+ try {
210
+ const { ViewHelper } = await import('three/addons/helpers/ViewHelper.js');
211
+ const gizmoCanvas = this._getDomElement('gizmoCanvas');
212
+ if (gizmoCanvas) {
213
+ // ViewHelper renders into a sub-viewport of the main renderer
214
+ viewHelper = new ViewHelper(activeCamera, renderer.domElement);
215
+ viewHelper.center = controls.target;
216
+
217
+ // Click on gizmo → snap camera
218
+ gizmoCanvas.addEventListener('pointerdown', (e) => {
219
+ // Map click to renderer domElement coords
220
+ const rect = renderer.domElement.getBoundingClientRect();
221
+ const gRect = gizmoCanvas.getBoundingClientRect();
222
+ const synth = new PointerEvent('pointerdown', {
223
+ clientX: gRect.left + (e.offsetX / gizmoCanvas.width) * gRect.width,
224
+ clientY: gRect.top + (e.offsetY / gizmoCanvas.height) * gRect.height,
225
+ bubbles: true
226
+ });
227
+ renderer.domElement.dispatchEvent(synth);
228
+ });
229
+ }
230
+ } catch (_) { /* ViewHelper not available */ }
231
+
232
+ // ── Script context ───────────────────────────────────
114
233
  const frameCallbacks = [];
115
- const selectCallbacks = [];
116
234
  const _subscriptions = [];
117
-
118
235
  const onFrame = (fn) => frameCallbacks.push(fn);
119
- const onSelect = (fn) => selectCallbacks.push(fn);
236
+ const onSelect = (fn) => {};
120
237
  const log = (...args) => console.log('[3D]', ...args);
121
-
122
- const setState = (id, val) => {
123
- try { iobrokerHandler.setState(id, val); } catch (_) {}
124
- };
125
- const getState = async (id) => {
126
- try { return await iobrokerHandler.getState(id); } catch (_) { return null; }
127
- };
238
+ const setState = (id, val) => { try { iobrokerHandler.setState(id, val); } catch (_) {} };
239
+ const getState = async (id) => { try { return await iobrokerHandler.getState(id); } catch (_) { return null; } };
128
240
  const subscribe = (id, cb) => {
129
241
  try {
130
242
  const unsub = iobrokerHandler.subscribeState(id, cb);
@@ -132,11 +244,9 @@ export class IobrokerWebui3DScreenViewer extends BaseCustomWebComponentConstruct
132
244
  return unsub;
133
245
  } catch (_) { return () => {}; }
134
246
  };
247
+ const assets = new Map();
248
+ const mixers = new Map();
135
249
 
136
- const assets = new Map();
137
- const mixers = new Map();
138
-
139
- // Run scene script if present
140
250
  if (sceneData.script) {
141
251
  try {
142
252
  const fn = new Function(
@@ -145,31 +255,54 @@ export class IobrokerWebui3DScreenViewer extends BaseCustomWebComponentConstruct
145
255
  'assets', 'mixers', 'log',
146
256
  sceneData.script
147
257
  );
148
- fn(THREE, scene, camera, renderer, controls,
258
+ fn(THREE, scene, activeCamera, renderer, controls,
149
259
  onFrame, onSelect, setState, getState, subscribe,
150
260
  assets, mixers, log);
151
261
  } catch (e) { console.warn('3D scene script error:', e); }
152
262
  }
153
263
 
154
264
  this._animating = true;
155
- this._cleanup = () => { _subscriptions.forEach(u => { try { u(); } catch (_) {} }); };
156
- this._onResize = () => {
265
+ this._cleanup = () => { _subscriptions.forEach(u => { try { u(); } catch (_) {} }); };
266
+ this._onResize = () => {
157
267
  const vw = viewport.clientWidth, vh = viewport.clientHeight;
158
- camera.aspect = vw / vh;
159
- camera.updateProjectionMatrix();
268
+ if (activeCamera.isPerspectiveCamera) {
269
+ activeCamera.aspect = vw / vh;
270
+ activeCamera.updateProjectionMatrix();
271
+ }
160
272
  renderer.setSize(vw, vh);
273
+ if (viewHelper) viewHelper.camera = activeCamera;
161
274
  };
162
275
  window.addEventListener('resize', this._onResize);
163
276
 
277
+ // ── Render loop ──────────────────────────────────────
164
278
  let _lastTime = 0;
165
279
  const animate = (ts) => {
166
280
  if (!this._animating) return;
167
281
  requestAnimationFrame(animate);
168
282
  const dt = _lastTime ? (ts - _lastTime) / 1000 : 0;
169
283
  _lastTime = ts;
284
+
285
+ controls.object = activeCamera;
170
286
  controls.update(dt);
287
+
171
288
  for (const cb of frameCallbacks) { try { cb(dt, ts / 1000); } catch (_) {} }
172
- renderer.render(scene, camera);
289
+
290
+ renderer.clear();
291
+ renderer.render(scene, activeCamera);
292
+
293
+ // Draw gizmo overlay (top-right corner)
294
+ if (viewHelper) {
295
+ const size = 96 * window.devicePixelRatio;
296
+ const cw = renderer.domElement.width;
297
+ const ch = renderer.domElement.height;
298
+ renderer.setViewport(cw - size, ch - size, size, size);
299
+ renderer.setScissor(cw - size, ch - size, size, size);
300
+ renderer.setScissorTest(true);
301
+ viewHelper.camera = activeCamera;
302
+ viewHelper.render(renderer);
303
+ renderer.setScissorTest(false);
304
+ renderer.setViewport(0, 0, cw, ch);
305
+ }
173
306
  };
174
307
  animate(0);
175
308