@sequent-org/ifc-viewer 1.2.4-ci.26.0 → 1.2.4-ci.28.0
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/README.md +18 -0
- package/package.json +1 -1
- package/src/IfcViewer.js +33 -3
- package/src/ifc/IfcService.js +10 -6
- package/src/main.js +495 -5
- package/src/viewer/Viewer.js +1471 -16
package/README.md
CHANGED
|
@@ -27,6 +27,24 @@ IFC 3D model viewer component for web applications. Основан на Three.js
|
|
|
27
27
|
|
|
28
28
|
**Готово!** Пакет полностью автоматический - никаких дополнительных настроек не требуется.
|
|
29
29
|
|
|
30
|
+
### Дефолтный пресет визуала (важно)
|
|
31
|
+
|
|
32
|
+
По умолчанию пакет включает **пресет "Тест"** — это рекомендованные настройки визуала (тени + самозатенение + ACES/sRGB + SSAO + Environment), чтобы модель выглядела корректно сразу после интеграции.
|
|
33
|
+
|
|
34
|
+
Если нужно отключить и вернуться к базовым настройкам:
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
import { IfcViewer } from '@sequent-org/ifc-viewer'
|
|
38
|
+
|
|
39
|
+
const viewer = new IfcViewer({
|
|
40
|
+
container: '#viewer-container',
|
|
41
|
+
ifcUrl: '/path/to/model.ifc',
|
|
42
|
+
useTestPreset: false,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
await viewer.init()
|
|
46
|
+
```
|
|
47
|
+
|
|
30
48
|
## 🚀 Установка
|
|
31
49
|
|
|
32
50
|
```bash
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sequent-org/ifc-viewer",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "1.2.4-ci.
|
|
4
|
+
"version": "1.2.4-ci.28.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "IFC 3D model viewer component for web applications - fully self-contained with local IFCLoader",
|
|
7
7
|
"main": "src/index.js",
|
package/src/IfcViewer.js
CHANGED
|
@@ -24,6 +24,7 @@ export class IfcViewer {
|
|
|
24
24
|
* @param {string} [options.ifcUrl] - URL для загрузки IFC файла
|
|
25
25
|
* @param {File} [options.ifcFile] - File объект для загрузки IFC файла
|
|
26
26
|
* @param {string} [options.wasmUrl] - URL для загрузки WASM файла web-ifc
|
|
27
|
+
* @param {boolean} [options.useTestPreset=true] - Включать ли пресет "Тест" по умолчанию (рекомендованные тени/визуал)
|
|
27
28
|
* @param {boolean} [options.showSidebar=false] - Показывать ли боковую панель с деревом
|
|
28
29
|
* @param {boolean} [options.showControls=false] - Показывать ли панель управления (нижние кнопки)
|
|
29
30
|
* @param {boolean} [options.showToolbar=true] - Показывать ли верхнюю панель инструментов
|
|
@@ -51,6 +52,8 @@ export class IfcViewer {
|
|
|
51
52
|
ifcUrl: options.ifcUrl || null,
|
|
52
53
|
ifcFile: options.ifcFile || null,
|
|
53
54
|
wasmUrl: options.wasmUrl || null,
|
|
55
|
+
// По умолчанию включаем пресет "Тест" для корректного вида теней (как в демо-настройках)
|
|
56
|
+
useTestPreset: options.useTestPreset !== false,
|
|
54
57
|
showSidebar: options.showSidebar === true, // по умолчанию false
|
|
55
58
|
showControls: options.showControls === true, // по умолчанию false
|
|
56
59
|
showToolbar: options.showToolbar !== false, // по умолчанию true
|
|
@@ -80,7 +83,7 @@ export class IfcViewer {
|
|
|
80
83
|
// Внутренние состояния управления
|
|
81
84
|
this.viewerState = {
|
|
82
85
|
quality: 'medium', // 'low' | 'medium' | 'high'
|
|
83
|
-
edgesVisible:
|
|
86
|
+
edgesVisible: false,
|
|
84
87
|
flatShading: true,
|
|
85
88
|
clipping: {
|
|
86
89
|
x: false,
|
|
@@ -112,6 +115,12 @@ export class IfcViewer {
|
|
|
112
115
|
this._initViewer();
|
|
113
116
|
this._initIfcService();
|
|
114
117
|
this._initTreeView();
|
|
118
|
+
|
|
119
|
+
// Применяем дефолтный пресет пакета (полностью независим от index.html)
|
|
120
|
+
// Важно: пресет должен примениться ДО загрузки модели, чтобы настройки подхватились при replaceWithModel()
|
|
121
|
+
if (this.options.useTestPreset && this.viewer?.setTestPresetEnabled) {
|
|
122
|
+
this.viewer.setTestPresetEnabled(true);
|
|
123
|
+
}
|
|
115
124
|
|
|
116
125
|
// Настраиваем обработчики событий
|
|
117
126
|
this._setupEventHandlers();
|
|
@@ -292,13 +301,34 @@ export class IfcViewer {
|
|
|
292
301
|
</div>
|
|
293
302
|
|
|
294
303
|
<!-- Верхняя панель управления -->
|
|
295
|
-
<div id="ifcToolbar" class="d-flex px-4" style="border:0px red solid; width:
|
|
304
|
+
<div id="ifcToolbar" class="d-flex px-4" style="border:0px red solid; width: 350px; position: absolute; z-index: 60; justify-content:space-between; bottom: 10px; left: calc(50% - 175px); ">
|
|
296
305
|
|
|
297
306
|
<div class="navbar-end flex gap-2">
|
|
298
307
|
|
|
299
308
|
<!-- Стили отображения -->
|
|
300
309
|
<div class="join">
|
|
301
|
-
<button class="btn btn-sm join-item
|
|
310
|
+
<button class="btn btn-sm join-item" id="ifcToggleEdges"><svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" class="c-tree__icon c-tree__icon--3d"><g fill="#252A3F" fill-rule="nonzero"><path d="M12.5 5L6.005 8.75v7.5L12.5 20l6.495-3.75v-7.5L12.5 5zm0-1.155l7.495 4.328v8.654L12.5 21.155l-7.495-4.328V8.173L12.5 3.845z"></path><path d="M12 12v8.059h1V12z"></path><path d="M5.641 9.157l7.045 4.025.496-.868-7.045-4.026z"></path><path d="M18.863 8.288l-7.045 4.026.496.868 7.045-4.025z"></path></g></svg></button>
|
|
311
|
+
<button class="btn btn-sm join-item btn-active" id="ifcToggleShadows" title="Тени">
|
|
312
|
+
<svg width="24" height="24" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
|
313
|
+
<path fill="#000000" d="M207.39 0.00 C 212.56 0.44 217.91 0.57 222.85 1.13 C 262.70 5.63 300.04 25.42 325.92 56.02 Q 361.61 98.22 364.04 153.58 A 0.23 0.22 11.8 0 1 363.71 153.79 L 349.62 146.87 A 0.90 0.90 0.0 0 1 349.13 146.22 C 347.61 137.34 346.80 130.78 344.73 123.45 C 332.00 78.38 299.83 39.42 256.32 20.94 C 235.21 11.97 211.55 8.21 189.00 11.72 Q 153.18 17.30 128.20 42.72 Q 110.60 60.63 102.05 85.78 C 88.10 126.83 95.83 172.91 118.73 209.60 Q 122.30 215.32 127.44 222.49 A 1.70 1.70 0.0 0 1 127.76 223.45 L 128.09 298.58 A 0.23 0.23 0.0 0 1 127.75 298.78 C 82.71 273.41 52.38 228.77 46.61 177.37 C 41.26 129.73 57.69 81.88 91.39 47.83 Q 129.76 9.07 184.30 1.42 C 189.99 0.63 196.32 0.47 202.38 0.00 L 207.39 0.00 Z"/>
|
|
314
|
+
<path fill="#000000" d="M312.50 512.00 L 311.56 512.00 Q 309.05 511.43 307.15 509.96 A 2.09 2.07 -19.8 0 0 306.27 509.55 Q 304.17 509.09 303.52 508.75 Q 206.21 457.82 153.89 430.39 Q 152.01 429.41 151.14 427.04 Q 150.58 425.52 150.56 422.64 Q 150.42 393.12 149.69 237.76 Q 149.69 236.82 150.36 233.09 Q 150.86 230.31 153.63 228.87 Q 250.38 178.34 303.97 150.48 Q 307.28 148.76 310.46 150.29 Q 339.28 164.17 462.71 224.01 Q 466.51 225.85 466.51 230.76 Q 466.50 321.20 466.49 424.75 C 466.49 428.40 464.08 430.42 460.80 432.20 Q 425.93 451.10 315.52 510.97 A 0.87 0.80 -65.8 0 1 315.31 511.06 L 312.50 512.00 Z M 444.21 230.96 A 0.32 0.32 0.0 0 0 444.19 230.39 L 307.84 163.41 A 0.32 0.32 0.0 0 0 307.55 163.42 L 171.77 234.43 A 0.32 0.32 0.0 0 0 171.78 235.00 L 311.71 304.85 A 0.32 0.32 0.0 0 0 312.01 304.85 L 444.21 230.96 Z M 318.55 493.80 A 0.34 0.34 0.0 0 0 319.05 494.10 L 453.17 421.35 A 0.34 0.34 0.0 0 0 453.35 421.05 L 453.35 241.55 A 0.34 0.34 0.0 0 0 452.84 241.25 L 318.72 316.20 A 0.34 0.34 0.0 0 0 318.55 316.50 L 318.55 493.80 Z"/>
|
|
315
|
+
</svg>
|
|
316
|
+
</button>
|
|
317
|
+
<button class="btn btn-sm join-item" id="ifcToggleProjection" title="Перспектива / Ортогонально (переключение)">
|
|
318
|
+
<!-- По умолчанию Ortho, поэтому показываем действие: включить Perspective -->
|
|
319
|
+
<svg width="24" height="24" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
|
320
|
+
<path fill="#000000" d="M365.50 333.29 A 0.30 0.30 0.0 0 0 365.95 333.55 L 492.36 259.80 A 0.47 0.47 0.0 0 0 492.51 259.12 Q 489.74 255.31 492.90 252.78 A 0.30 0.30 0.0 0 0 492.83 252.27 C 489.14 250.57 490.13 245.43 493.90 244.50 C 496.33 243.90 501.93 247.88 504.97 249.79 A 1.50 1.48 -85.3 0 1 505.54 250.47 L 505.97 251.53 A 0.72 0.71 76.6 0 0 506.67 251.97 C 509.70 251.84 512.28 254.84 511.15 257.67 Q 510.77 258.62 508.18 260.14 C 355.38 349.68 251.70 410.06 149.28 469.74 A 3.94 3.93 -44.9 0 1 145.31 469.74 Q 7.70 389.45 2.96 386.69 C 0.09 385.02 0.50 382.93 0.50 379.49 Q 0.50 259.79 0.50 128.77 C 0.50 127.21 1.85 125.96 3.27 125.13 Q 68.02 87.24 145.61 41.87 C 146.90 41.11 148.92 41.81 150.33 42.63 Q 219.34 82.64 289.83 124.16 C 291.25 125.00 292.80 126.11 294.76 127.15 Q 299.89 129.89 301.84 131.37 C 305.49 134.15 301.99 140.40 297.26 138.18 Q 295.67 137.42 294.41 136.58 A 0.26 0.26 0.0 0 0 294.00 136.80 L 294.00 209.83 A 0.44 0.44 0.0 0 0 294.36 210.26 Q 340.50 219.23 361.26 223.22 C 366.12 224.15 365.53 227.44 365.51 232.03 Q 365.50 234.52 365.49 251.11 A 0.73 0.73 0.0 0 0 366.22 251.84 L 370.02 251.84 A 3.64 3.64 0.0 0 1 373.66 255.48 L 373.66 256.72 A 3.45 3.44 0.0 0 1 370.21 260.16 L 366.15 260.16 A 0.65 0.65 0.0 0 0 365.50 260.81 L 365.50 333.29 Z M 9.05 131.40 A 0.30 0.30 0.0 0 0 8.90 131.66 L 8.90 380.18 A 0.30 0.30 0.0 0 0 9.05 380.44 L 142.74 458.43 A 0.30 0.30 0.0 0 0 143.19 458.17 L 143.19 53.67 A 0.30 0.30 0.0 0 0 142.74 53.41 L 9.05 131.40 Z M 285.68 380.52 A 0.32 0.32 0.0 0 0 285.84 380.25 L 285.84 131.66 A 0.32 0.32 0.0 0 0 285.68 131.39 L 151.98 53.39 A 0.32 0.32 0.0 0 0 151.50 53.67 L 151.50 458.24 A 0.32 0.32 0.0 0 0 151.98 458.52 L 285.68 380.52 Z M 294.62 218.77 A 0.36 0.36 0.0 0 0 294.19 219.13 L 294.19 374.90 A 0.36 0.36 0.0 0 0 294.73 375.21 L 357.13 338.81 A 0.36 0.36 0.0 0 0 357.31 338.50 L 357.31 231.30 A 0.36 0.36 0.0 0 0 357.02 230.94 L 294.62 218.77 Z"/>
|
|
321
|
+
<path fill="#000000" d="M 331.8028 153.6467 A 4.00 4.00 0.0 0 1 326.3286 155.0726 L 318.9110 150.7207 A 4.00 4.00 0.0 0 1 317.4851 145.2465 L 317.6572 144.9533 A 4.00 4.00 0.0 0 1 323.1314 143.5274 L 330.5490 147.8793 A 4.00 4.00 0.0 0 1 331.9749 153.3535 L 331.8028 153.6467 Z"/>
|
|
322
|
+
<path fill="#000000" d="M 360.6890 170.5463 A 4.00 4.00 0.0 0 1 355.2099 171.9531 L 347.8247 167.5855 A 4.00 4.00 0.0 0 1 346.4179 162.1064 L 346.5910 161.8137 A 4.00 4.00 0.0 0 1 352.0701 160.4069 L 359.4553 164.7745 A 4.00 4.00 0.0 0 1 360.8621 170.2536 L 360.6890 170.5463 Z"/>
|
|
323
|
+
<path fill="#000000" d="M 389.5811 187.4643 A 3.99 3.99 0.0 0 1 384.1181 188.8771 L 376.8287 184.5833 A 3.99 3.99 0.0 0 1 375.4159 179.1204 L 375.6189 178.7757 A 3.99 3.99 0.0 0 1 381.0819 177.3629 L 388.3713 181.6567 A 3.99 3.99 0.0 0 1 389.7841 187.1196 L 389.5811 187.4643 Z"/>
|
|
324
|
+
<path fill="#000000" d="M 418.5914 204.3586 A 3.99 3.99 0.0 0 1 413.1235 205.7523 L 405.7288 201.3617 A 3.99 3.99 0.0 0 1 404.3350 195.8938 L 404.5086 195.6014 A 3.99 3.99 0.0 0 1 409.9765 194.2077 L 417.3712 198.5983 A 3.99 3.99 0.0 0 1 418.7650 204.0662 L 418.5914 204.3586 Z"/>
|
|
325
|
+
<path fill="#000000" d="M 447.6480 221.1624 A 3.99 3.99 0.0 0 1 442.2027 222.6419 L 434.7225 218.3579 A 3.99 3.99 0.0 0 1 433.2431 212.9126 L 433.4120 212.6176 A 3.99 3.99 0.0 0 1 438.8573 211.1381 L 446.3375 215.4221 A 3.99 3.99 0.0 0 1 447.8169 220.8674 L 447.6480 221.1624 Z"/>
|
|
326
|
+
<path fill="#000000" d="M 476.5002 238.1477 A 3.99 3.99 0.0 0 1 471.0372 239.5605 L 463.6099 235.1855 A 3.99 3.99 0.0 0 1 462.1971 229.7225 L 462.3798 229.4123 A 3.99 3.99 0.0 0 1 467.8428 227.9995 L 475.2701 232.3745 A 3.99 3.99 0.0 0 1 476.6829 237.8375 L 476.5002 238.1477 Z"/>
|
|
327
|
+
<path fill="#000000" d="M 407.4604 256.3255 A 3.98 3.98 0.0 0 1 403.4873 260.3125 L 394.8874 260.3275 A 3.98 3.98 0.0 0 1 390.9004 256.3545 L 390.8996 255.8945 A 3.98 3.98 0.0 0 1 394.8727 251.9075 L 403.4726 251.8925 A 3.98 3.98 0.0 0 1 407.4596 255.8655 L 407.4604 256.3255 Z"/>
|
|
328
|
+
<path fill="#000000" d="M 440.9596 256.3545 A 3.98 3.98 0.0 0 1 436.9726 260.3275 L 428.3727 260.3125 A 3.98 3.98 0.0 0 1 424.3996 256.3255 L 424.4004 255.8655 A 3.98 3.98 0.0 0 1 428.3874 251.8925 L 436.9873 251.9075 A 3.98 3.98 0.0 0 1 440.9604 255.8945 L 440.9596 256.3545 Z"/>
|
|
329
|
+
<path fill="#000000" d="M 474.4604 256.3255 A 3.98 3.98 0.0 0 1 470.4873 260.3125 L 461.8874 260.3275 A 3.98 3.98 0.0 0 1 457.9004 256.3545 L 457.8996 255.8945 A 3.98 3.98 0.0 0 1 461.8727 251.9075 L 470.4726 251.8925 A 3.98 3.98 0.0 0 1 474.4596 255.8655 L 474.4604 256.3255 Z"/>
|
|
330
|
+
</svg>
|
|
331
|
+
</button>
|
|
302
332
|
</div>
|
|
303
333
|
|
|
304
334
|
<!-- Секущие плоскости -->
|
package/src/ifc/IfcService.js
CHANGED
|
@@ -71,7 +71,9 @@ export class IfcService {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
|
-
* Получает список путей к WASM
|
|
74
|
+
* Получает список путей к WASM в порядке приоритета.
|
|
75
|
+
* ВАЖНО: web-ifc сам добавляет имя файла web-ifc.wasm к переданному пути,
|
|
76
|
+
* поэтому здесь указываем директории, а не полный путь до файла.
|
|
75
77
|
* @private
|
|
76
78
|
*/
|
|
77
79
|
_getWasmPaths() {
|
|
@@ -83,12 +85,14 @@ export class IfcService {
|
|
|
83
85
|
}
|
|
84
86
|
|
|
85
87
|
// 2. Популярные пути по умолчанию (в порядке приоритета)
|
|
88
|
+
// Ожидаемый итоговый URL после SetWasmPath(path):
|
|
89
|
+
// path + 'web-ifc.wasm'
|
|
86
90
|
paths.push(
|
|
87
|
-
'/
|
|
88
|
-
'/
|
|
89
|
-
'/
|
|
90
|
-
'./
|
|
91
|
-
'
|
|
91
|
+
'/wasm/', // Стандартный путь: public/wasm/web-ifc.wasm
|
|
92
|
+
'/node_modules/web-ifc/', // Прямо из node_modules
|
|
93
|
+
'/', // Корень dev-сервера
|
|
94
|
+
'./', // Относительный путь
|
|
95
|
+
'' // Пусть библиотека сама определит путь
|
|
92
96
|
);
|
|
93
97
|
|
|
94
98
|
return paths;
|
package/src/main.js
CHANGED
|
@@ -8,6 +8,492 @@ const app = document.getElementById("app");
|
|
|
8
8
|
if (app) {
|
|
9
9
|
const viewer = new Viewer(app);
|
|
10
10
|
viewer.init();
|
|
11
|
+
|
|
12
|
+
// Панель свойств: тени
|
|
13
|
+
const shadowToggle = document.getElementById("shadowToggle");
|
|
14
|
+
const shadowGradToggle = document.getElementById("shadowGradToggle");
|
|
15
|
+
const shadowGradLen = document.getElementById("shadowGradLen");
|
|
16
|
+
const shadowGradLenValue = document.getElementById("shadowGradLenValue");
|
|
17
|
+
const shadowGradStr = document.getElementById("shadowGradStr");
|
|
18
|
+
const shadowGradStrValue = document.getElementById("shadowGradStrValue");
|
|
19
|
+
const shadowGradCurve = document.getElementById("shadowGradCurve");
|
|
20
|
+
const shadowGradCurveValue = document.getElementById("shadowGradCurveValue");
|
|
21
|
+
const shadowOpacity = document.getElementById("shadowOpacity");
|
|
22
|
+
const shadowOpacityValue = document.getElementById("shadowOpacityValue");
|
|
23
|
+
const shadowSoft = document.getElementById("shadowSoft");
|
|
24
|
+
const shadowSoftValue = document.getElementById("shadowSoftValue");
|
|
25
|
+
// Материалы
|
|
26
|
+
const matPreset = document.getElementById("matPreset");
|
|
27
|
+
const matRough = document.getElementById("matRough");
|
|
28
|
+
const matRoughValue = document.getElementById("matRoughValue");
|
|
29
|
+
const matMetal = document.getElementById("matMetal");
|
|
30
|
+
const matMetalValue = document.getElementById("matMetalValue");
|
|
31
|
+
// Визуал (диагностика)
|
|
32
|
+
const testPresetToggle = document.getElementById("testPresetToggle");
|
|
33
|
+
const rtQualityToggle = document.getElementById("rtQualityToggle");
|
|
34
|
+
const envToggle = document.getElementById("envToggle");
|
|
35
|
+
const envInt = document.getElementById("envInt");
|
|
36
|
+
const envIntValue = document.getElementById("envIntValue");
|
|
37
|
+
const toneToggle = document.getElementById("toneToggle");
|
|
38
|
+
const exposure = document.getElementById("exposure");
|
|
39
|
+
const exposureValue = document.getElementById("exposureValue");
|
|
40
|
+
const aoToggle = document.getElementById("aoToggle");
|
|
41
|
+
const aoInt = document.getElementById("aoInt");
|
|
42
|
+
const aoIntValue = document.getElementById("aoIntValue");
|
|
43
|
+
const aoRad = document.getElementById("aoRad");
|
|
44
|
+
const aoRadValue = document.getElementById("aoRadValue");
|
|
45
|
+
const dumpVisual = document.getElementById("dumpVisual");
|
|
46
|
+
// Цветокор
|
|
47
|
+
const ccToggle = document.getElementById("ccToggle");
|
|
48
|
+
const ccHue = document.getElementById("ccHue");
|
|
49
|
+
const ccHueValue = document.getElementById("ccHueValue");
|
|
50
|
+
const ccSat = document.getElementById("ccSat");
|
|
51
|
+
const ccSatValue = document.getElementById("ccSatValue");
|
|
52
|
+
const ccBri = document.getElementById("ccBri");
|
|
53
|
+
const ccBriValue = document.getElementById("ccBriValue");
|
|
54
|
+
const ccCon = document.getElementById("ccCon");
|
|
55
|
+
const ccConValue = document.getElementById("ccConValue");
|
|
56
|
+
|
|
57
|
+
// ===== Test preset ("Тест") - полностью изолированная настройка =====
|
|
58
|
+
const _testSnapshot = new Map();
|
|
59
|
+
const testSnapshotEl = (el) => {
|
|
60
|
+
if (!el) return;
|
|
61
|
+
_testSnapshot.set(el, {
|
|
62
|
+
checked: "checked" in el ? el.checked : undefined,
|
|
63
|
+
value: "value" in el ? el.value : undefined,
|
|
64
|
+
disabled: "disabled" in el ? el.disabled : undefined,
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
const testRestoreEl = (el) => {
|
|
68
|
+
if (!el) return;
|
|
69
|
+
const s = _testSnapshot.get(el);
|
|
70
|
+
if (!s) return;
|
|
71
|
+
if ("checked" in el && typeof s.checked === "boolean") el.checked = s.checked;
|
|
72
|
+
if ("value" in el && typeof s.value === "string") el.value = s.value;
|
|
73
|
+
if ("disabled" in el && typeof s.disabled === "boolean") el.disabled = s.disabled;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const getAllNonTestControls = () => ([
|
|
77
|
+
// Test preset toggle must stay enabled to allow turning it off
|
|
78
|
+
// Shadows + sun
|
|
79
|
+
shadowToggle, shadowGradToggle, shadowGradLen, shadowGradStr, shadowGradCurve, shadowOpacity, shadowSoft,
|
|
80
|
+
sunToggle, sunHeight,
|
|
81
|
+
// Materials
|
|
82
|
+
matPreset, matRough, matMetal,
|
|
83
|
+
// Visual
|
|
84
|
+
rtQualityToggle, envToggle, envInt, toneToggle, exposure, aoToggle, aoInt, aoRad,
|
|
85
|
+
dumpVisual,
|
|
86
|
+
// Color correction
|
|
87
|
+
ccToggle, ccHue, ccSat, ccBri, ccCon,
|
|
88
|
+
].filter(Boolean));
|
|
89
|
+
|
|
90
|
+
const setDisabled = (el, disabled) => { if (el && "disabled" in el) el.disabled = !!disabled; };
|
|
91
|
+
|
|
92
|
+
const applyTestUiLock = (enabled) => {
|
|
93
|
+
getAllNonTestControls().forEach((el) => setDisabled(el, enabled));
|
|
94
|
+
// сам тест-переключатель не блокируем
|
|
95
|
+
if (testPresetToggle) setDisabled(testPresetToggle, false);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
if (testPresetToggle) {
|
|
99
|
+
testPresetToggle.checked = false;
|
|
100
|
+
testPresetToggle.addEventListener("change", (e) => {
|
|
101
|
+
const on = !!e.target.checked;
|
|
102
|
+
if (on) {
|
|
103
|
+
_testSnapshot.clear();
|
|
104
|
+
// снимем снапшот со всех контролов, включая сам test (чтобы вернуть checked), но блокировать его не будем
|
|
105
|
+
[testPresetToggle, ...getAllNonTestControls()].forEach(testSnapshotEl);
|
|
106
|
+
viewer.setTestPresetEnabled?.(true);
|
|
107
|
+
applyTestUiLock(true);
|
|
108
|
+
} else {
|
|
109
|
+
viewer.setTestPresetEnabled?.(false);
|
|
110
|
+
// вернём UI
|
|
111
|
+
[testPresetToggle, ...getAllNonTestControls()].forEach(testRestoreEl);
|
|
112
|
+
applyTestUiLock(false);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ===== Realtime-quality preset (UI master toggle) =====
|
|
118
|
+
const _rtSnapshot = new Map();
|
|
119
|
+
const snapshotEl = (el) => {
|
|
120
|
+
if (!el) return;
|
|
121
|
+
_rtSnapshot.set(el, {
|
|
122
|
+
checked: "checked" in el ? el.checked : undefined,
|
|
123
|
+
value: "value" in el ? el.value : undefined,
|
|
124
|
+
disabled: "disabled" in el ? el.disabled : undefined,
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
const restoreEl = (el) => {
|
|
128
|
+
if (!el) return;
|
|
129
|
+
const s = _rtSnapshot.get(el);
|
|
130
|
+
if (!s) return;
|
|
131
|
+
if ("checked" in el && typeof s.checked === "boolean") el.checked = s.checked;
|
|
132
|
+
if ("value" in el && typeof s.value === "string") el.value = s.value;
|
|
133
|
+
if ("disabled" in el && typeof s.disabled === "boolean") el.disabled = s.disabled;
|
|
134
|
+
};
|
|
135
|
+
// Важно: делаем это функцией, чтобы не попасть в TDZ для переменных,
|
|
136
|
+
// которые объявляются ниже (например, sunToggle/sunHeight).
|
|
137
|
+
const getRtManagedControls = () => ([
|
|
138
|
+
// Shadows + sun
|
|
139
|
+
shadowToggle, shadowGradToggle, shadowGradLen, shadowGradStr, shadowGradCurve, shadowOpacity, shadowSoft,
|
|
140
|
+
sunToggle, sunHeight,
|
|
141
|
+
// Materials
|
|
142
|
+
matPreset, matRough, matMetal,
|
|
143
|
+
// Visual
|
|
144
|
+
envToggle, envInt, toneToggle, exposure, aoToggle, aoInt, aoRad,
|
|
145
|
+
// Color correction
|
|
146
|
+
ccToggle, ccHue, ccSat, ccBri, ccCon,
|
|
147
|
+
].filter(Boolean));
|
|
148
|
+
|
|
149
|
+
const applyRtQualityUiLock = (enabled) => {
|
|
150
|
+
// Блокируем все ручные контролы, кроме самого переключателя и кнопки dump
|
|
151
|
+
getRtManagedControls().forEach((el) => setDisabled(el, enabled));
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
if (rtQualityToggle) {
|
|
155
|
+
rtQualityToggle.checked = false;
|
|
156
|
+
rtQualityToggle.addEventListener("change", (e) => {
|
|
157
|
+
const on = !!e.target.checked;
|
|
158
|
+
if (on) {
|
|
159
|
+
// Снимем UI-снапшот, чтобы вернуть всё как было (включая disabled-состояния)
|
|
160
|
+
_rtSnapshot.clear();
|
|
161
|
+
getRtManagedControls().forEach(snapshotEl);
|
|
162
|
+
snapshotEl(dumpVisual); // dump не блокируем, но состояние тоже сохраним на всякий
|
|
163
|
+
|
|
164
|
+
viewer.setRealtimeQualityEnabled(true);
|
|
165
|
+
applyRtQualityUiLock(true);
|
|
166
|
+
} else {
|
|
167
|
+
viewer.setRealtimeQualityEnabled(false);
|
|
168
|
+
// Вернём UI
|
|
169
|
+
getRtManagedControls().forEach(restoreEl);
|
|
170
|
+
restoreEl(dumpVisual);
|
|
171
|
+
applyRtQualityUiLock(false);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
if (shadowToggle) {
|
|
176
|
+
// Дефолт (из текущих подобранных значений)
|
|
177
|
+
shadowToggle.checked = true;
|
|
178
|
+
viewer.setShadowsEnabled(true);
|
|
179
|
+
shadowToggle.addEventListener("change", (e) => {
|
|
180
|
+
const on = !!e.target.checked;
|
|
181
|
+
viewer.setShadowsEnabled(on);
|
|
182
|
+
// UI градиента имеет смысл только когда тени включены
|
|
183
|
+
if (shadowGradToggle) shadowGradToggle.disabled = !on;
|
|
184
|
+
if (shadowGradLen) shadowGradLen.disabled = !on;
|
|
185
|
+
if (shadowGradStr) shadowGradStr.disabled = !on;
|
|
186
|
+
if (shadowGradCurve) shadowGradCurve.disabled = !on;
|
|
187
|
+
if (shadowOpacity) shadowOpacity.disabled = !on;
|
|
188
|
+
if (shadowSoft) shadowSoft.disabled = !on;
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
// Градиент тени: по умолчанию включён, но элементы блокируем пока тени выключены
|
|
192
|
+
const syncGradUiEnabled = (enabled) => {
|
|
193
|
+
if (shadowGradToggle) shadowGradToggle.disabled = !enabled;
|
|
194
|
+
if (shadowGradLen) shadowGradLen.disabled = !enabled;
|
|
195
|
+
if (shadowGradStr) shadowGradStr.disabled = !enabled;
|
|
196
|
+
if (shadowGradCurve) shadowGradCurve.disabled = !enabled;
|
|
197
|
+
if (shadowOpacity) shadowOpacity.disabled = !enabled;
|
|
198
|
+
if (shadowSoft) shadowSoft.disabled = !enabled;
|
|
199
|
+
};
|
|
200
|
+
syncGradUiEnabled(true);
|
|
201
|
+
|
|
202
|
+
if (shadowGradToggle) {
|
|
203
|
+
shadowGradToggle.checked = true;
|
|
204
|
+
viewer.setShadowGradientEnabled(true);
|
|
205
|
+
shadowGradToggle.addEventListener("change", (e) => {
|
|
206
|
+
viewer.setShadowGradientEnabled(!!e.target.checked);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
if (shadowGradLen) {
|
|
210
|
+
shadowGradLen.value = "14.4";
|
|
211
|
+
if (shadowGradLenValue) shadowGradLenValue.textContent = "14.4";
|
|
212
|
+
viewer.setShadowGradientLength(14.4);
|
|
213
|
+
shadowGradLen.addEventListener("input", (e) => {
|
|
214
|
+
const v = Number(e.target.value);
|
|
215
|
+
if (shadowGradLenValue) shadowGradLenValue.textContent = v.toFixed(1);
|
|
216
|
+
viewer.setShadowGradientLength(v);
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
if (shadowGradStr) {
|
|
220
|
+
shadowGradStr.value = "1.00";
|
|
221
|
+
if (shadowGradStrValue) shadowGradStrValue.textContent = "1.00";
|
|
222
|
+
viewer.setShadowGradientStrength(1.0);
|
|
223
|
+
shadowGradStr.addEventListener("input", (e) => {
|
|
224
|
+
const v = Number(e.target.value);
|
|
225
|
+
if (shadowGradStrValue) shadowGradStrValue.textContent = v.toFixed(2);
|
|
226
|
+
viewer.setShadowGradientStrength(v);
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (shadowGradCurve) {
|
|
231
|
+
shadowGradCurve.value = "0.50";
|
|
232
|
+
if (shadowGradCurveValue) shadowGradCurveValue.textContent = "0.50";
|
|
233
|
+
viewer.setShadowGradientCurve(0.5);
|
|
234
|
+
shadowGradCurve.addEventListener("input", (e) => {
|
|
235
|
+
const v = Number(e.target.value);
|
|
236
|
+
if (shadowGradCurveValue) shadowGradCurveValue.textContent = v.toFixed(2);
|
|
237
|
+
viewer.setShadowGradientCurve(v);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Полупрозрачность тени на земле
|
|
242
|
+
if (shadowOpacity) {
|
|
243
|
+
shadowOpacity.value = "0.14";
|
|
244
|
+
if (shadowOpacityValue) shadowOpacityValue.textContent = "0.14";
|
|
245
|
+
viewer.setShadowOpacity(0.14);
|
|
246
|
+
shadowOpacity.addEventListener("input", (e) => {
|
|
247
|
+
const v = Number(e.target.value);
|
|
248
|
+
if (shadowOpacityValue) shadowOpacityValue.textContent = v.toFixed(2);
|
|
249
|
+
viewer.setShadowOpacity(v);
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Мягкость края тени
|
|
254
|
+
if (shadowSoft) {
|
|
255
|
+
shadowSoft.value = "0.0";
|
|
256
|
+
if (shadowSoftValue) shadowSoftValue.textContent = "0.0";
|
|
257
|
+
viewer.setShadowSoftness(0.0);
|
|
258
|
+
shadowSoft.addEventListener("input", (e) => {
|
|
259
|
+
const v = Number(e.target.value);
|
|
260
|
+
if (shadowSoftValue) shadowSoftValue.textContent = v.toFixed(1);
|
|
261
|
+
viewer.setShadowSoftness(v);
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Панель свойств: солнце (глобальное освещение)
|
|
266
|
+
const sunToggle = document.getElementById("sunToggle");
|
|
267
|
+
const sunHeight = document.getElementById("sunHeight");
|
|
268
|
+
const sunHeightValue = document.getElementById("sunHeightValue");
|
|
269
|
+
if (sunToggle) {
|
|
270
|
+
// По умолчанию включено
|
|
271
|
+
sunToggle.checked = true;
|
|
272
|
+
viewer.setSunEnabled(true);
|
|
273
|
+
sunToggle.addEventListener("change", (e) => {
|
|
274
|
+
const on = !!e.target.checked;
|
|
275
|
+
viewer.setSunEnabled(on);
|
|
276
|
+
if (sunHeight) sunHeight.disabled = !on;
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
if (sunHeight) {
|
|
280
|
+
// Дефолт (из текущих подобранных значений)
|
|
281
|
+
sunHeight.value = "5.9";
|
|
282
|
+
if (sunHeightValue) sunHeightValue.textContent = "5.9";
|
|
283
|
+
viewer.setSunHeight(5.9);
|
|
284
|
+
sunHeight.disabled = !(sunToggle ? !!sunToggle.checked : true);
|
|
285
|
+
sunHeight.addEventListener("input", (e) => {
|
|
286
|
+
const v = Number(e.target.value);
|
|
287
|
+
if (sunHeightValue) sunHeightValue.textContent = v.toFixed(1);
|
|
288
|
+
viewer.setSunHeight(v);
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// ===== Материалы =====
|
|
293
|
+
const MAT_DEFAULTS = {
|
|
294
|
+
original: { roughness: 0.90, metalness: 0.00, slidersEnabled: false },
|
|
295
|
+
matte: { roughness: 0.90, metalness: 0.00, slidersEnabled: true },
|
|
296
|
+
glossy: { roughness: 0.05, metalness: 0.00, slidersEnabled: true },
|
|
297
|
+
// Важно: "пластик" не должен быть металлом, иначе появятся резкие блики и "дёрганая" картинка при вращении
|
|
298
|
+
plastic: { roughness: 0.65, metalness: 0.00, slidersEnabled: true },
|
|
299
|
+
concrete: { roughness: 0.95, metalness: 0.00, slidersEnabled: true },
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const setMatUiEnabled = (enabled) => {
|
|
303
|
+
if (matRough) matRough.disabled = !enabled;
|
|
304
|
+
if (matMetal) matMetal.disabled = !enabled;
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
const applyMatPresetUi = (preset) => {
|
|
308
|
+
const d = MAT_DEFAULTS[preset] || MAT_DEFAULTS.original;
|
|
309
|
+
if (matRough) matRough.value = String(d.roughness.toFixed(2));
|
|
310
|
+
if (matRoughValue) matRoughValue.textContent = d.roughness.toFixed(2);
|
|
311
|
+
if (matMetal) matMetal.value = String(d.metalness.toFixed(2));
|
|
312
|
+
if (matMetalValue) matMetalValue.textContent = d.metalness.toFixed(2);
|
|
313
|
+
setMatUiEnabled(d.slidersEnabled);
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
if (matPreset) {
|
|
317
|
+
// Дефолт: Пластик (как на скрине)
|
|
318
|
+
matPreset.value = "plastic";
|
|
319
|
+
applyMatPresetUi("plastic");
|
|
320
|
+
viewer.setMaterialPreset("plastic");
|
|
321
|
+
viewer.setMaterialRoughness(MAT_DEFAULTS.plastic.roughness);
|
|
322
|
+
viewer.setMaterialMetalness(MAT_DEFAULTS.plastic.metalness);
|
|
323
|
+
matPreset.addEventListener("change", (e) => {
|
|
324
|
+
const preset = e.target.value;
|
|
325
|
+
viewer.setMaterialPreset(preset);
|
|
326
|
+
applyMatPresetUi(preset);
|
|
327
|
+
// применяем дефолтные параметры пресета как override
|
|
328
|
+
const d = MAT_DEFAULTS[preset] || MAT_DEFAULTS.original;
|
|
329
|
+
if (d.slidersEnabled) {
|
|
330
|
+
viewer.setMaterialRoughness(d.roughness);
|
|
331
|
+
viewer.setMaterialMetalness(d.metalness);
|
|
332
|
+
} else {
|
|
333
|
+
viewer.setMaterialRoughness(null);
|
|
334
|
+
viewer.setMaterialMetalness(null);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (matRough) {
|
|
340
|
+
matRough.addEventListener("input", (e) => {
|
|
341
|
+
const v = Number(e.target.value);
|
|
342
|
+
if (matRoughValue) matRoughValue.textContent = v.toFixed(2);
|
|
343
|
+
viewer.setMaterialRoughness(v);
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
if (matMetal) {
|
|
347
|
+
matMetal.addEventListener("input", (e) => {
|
|
348
|
+
const v = Number(e.target.value);
|
|
349
|
+
if (matMetalValue) matMetalValue.textContent = v.toFixed(2);
|
|
350
|
+
viewer.setMaterialMetalness(v);
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// ===== Визуал (диагностика) =====
|
|
355
|
+
const syncVisualUiEnabled = () => {
|
|
356
|
+
const envOn = !!(envToggle && envToggle.checked);
|
|
357
|
+
const toneOn = !!(toneToggle && toneToggle.checked);
|
|
358
|
+
const aoOn = !!(aoToggle && aoToggle.checked);
|
|
359
|
+
if (envInt) envInt.disabled = !envOn;
|
|
360
|
+
if (exposure) exposure.disabled = !toneOn;
|
|
361
|
+
if (aoInt) aoInt.disabled = !aoOn;
|
|
362
|
+
if (aoRad) aoRad.disabled = !aoOn;
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
const syncCcUiEnabled = () => {
|
|
366
|
+
const on = !!(ccToggle && ccToggle.checked);
|
|
367
|
+
if (ccHue) ccHue.disabled = !on;
|
|
368
|
+
if (ccSat) ccSat.disabled = !on;
|
|
369
|
+
if (ccBri) ccBri.disabled = !on;
|
|
370
|
+
if (ccCon) ccCon.disabled = !on;
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
// Дефолты (как на скрине)
|
|
374
|
+
if (envToggle) {
|
|
375
|
+
envToggle.checked = true;
|
|
376
|
+
viewer.setEnvironmentEnabled(true);
|
|
377
|
+
envToggle.addEventListener("change", (e) => {
|
|
378
|
+
viewer.setEnvironmentEnabled(!!e.target.checked);
|
|
379
|
+
syncVisualUiEnabled();
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
if (envInt) {
|
|
383
|
+
envInt.value = "0.65";
|
|
384
|
+
if (envIntValue) envIntValue.textContent = "0.65";
|
|
385
|
+
viewer.setEnvironmentIntensity(0.65);
|
|
386
|
+
envInt.addEventListener("input", (e) => {
|
|
387
|
+
const v = Number(e.target.value);
|
|
388
|
+
if (envIntValue) envIntValue.textContent = v.toFixed(2);
|
|
389
|
+
viewer.setEnvironmentIntensity(v);
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (toneToggle) {
|
|
394
|
+
toneToggle.checked = true;
|
|
395
|
+
viewer.setToneMappingEnabled(true);
|
|
396
|
+
toneToggle.addEventListener("change", (e) => {
|
|
397
|
+
viewer.setToneMappingEnabled(!!e.target.checked);
|
|
398
|
+
syncVisualUiEnabled();
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
if (exposure) {
|
|
402
|
+
exposure.value = "1.11";
|
|
403
|
+
if (exposureValue) exposureValue.textContent = "1.11";
|
|
404
|
+
viewer.setExposure(1.11);
|
|
405
|
+
exposure.addEventListener("input", (e) => {
|
|
406
|
+
const v = Number(e.target.value);
|
|
407
|
+
if (exposureValue) exposureValue.textContent = v.toFixed(2);
|
|
408
|
+
viewer.setExposure(v);
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (aoToggle) {
|
|
413
|
+
aoToggle.checked = true;
|
|
414
|
+
viewer.setAOEnabled(true);
|
|
415
|
+
aoToggle.addEventListener("change", (e) => {
|
|
416
|
+
viewer.setAOEnabled(!!e.target.checked);
|
|
417
|
+
syncVisualUiEnabled();
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
if (aoInt) {
|
|
421
|
+
aoInt.value = "0.52";
|
|
422
|
+
if (aoIntValue) aoIntValue.textContent = "0.52";
|
|
423
|
+
viewer.setAOIntensity(0.52);
|
|
424
|
+
aoInt.addEventListener("input", (e) => {
|
|
425
|
+
const v = Number(e.target.value);
|
|
426
|
+
if (aoIntValue) aoIntValue.textContent = v.toFixed(2);
|
|
427
|
+
viewer.setAOIntensity(v);
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
if (aoRad) {
|
|
431
|
+
aoRad.value = "8";
|
|
432
|
+
if (aoRadValue) aoRadValue.textContent = "8";
|
|
433
|
+
viewer.setAORadius(8);
|
|
434
|
+
aoRad.addEventListener("input", (e) => {
|
|
435
|
+
const v = Number(e.target.value);
|
|
436
|
+
if (aoRadValue) aoRadValue.textContent = String(Math.round(v));
|
|
437
|
+
viewer.setAORadius(v);
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
syncVisualUiEnabled();
|
|
442
|
+
|
|
443
|
+
if (dumpVisual) {
|
|
444
|
+
dumpVisual.addEventListener("click", () => viewer.dumpVisualDebug());
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// ===== Цветокор =====
|
|
448
|
+
if (ccToggle) {
|
|
449
|
+
ccToggle.checked = false;
|
|
450
|
+
viewer.setColorCorrectionEnabled(false);
|
|
451
|
+
ccToggle.addEventListener("change", (e) => {
|
|
452
|
+
viewer.setColorCorrectionEnabled(!!e.target.checked);
|
|
453
|
+
syncCcUiEnabled();
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
if (ccHue) {
|
|
457
|
+
ccHue.value = "0.00";
|
|
458
|
+
if (ccHueValue) ccHueValue.textContent = "0.00";
|
|
459
|
+
viewer.setColorHue(0.0);
|
|
460
|
+
ccHue.addEventListener("input", (e) => {
|
|
461
|
+
const v = Number(e.target.value);
|
|
462
|
+
if (ccHueValue) ccHueValue.textContent = v.toFixed(2);
|
|
463
|
+
viewer.setColorHue(v);
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
if (ccSat) {
|
|
467
|
+
ccSat.value = "0.00";
|
|
468
|
+
if (ccSatValue) ccSatValue.textContent = "0.00";
|
|
469
|
+
viewer.setColorSaturation(0.0);
|
|
470
|
+
ccSat.addEventListener("input", (e) => {
|
|
471
|
+
const v = Number(e.target.value);
|
|
472
|
+
if (ccSatValue) ccSatValue.textContent = v.toFixed(2);
|
|
473
|
+
viewer.setColorSaturation(v);
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
if (ccBri) {
|
|
477
|
+
ccBri.value = "0.00";
|
|
478
|
+
if (ccBriValue) ccBriValue.textContent = "0.00";
|
|
479
|
+
viewer.setColorBrightness(0.0);
|
|
480
|
+
ccBri.addEventListener("input", (e) => {
|
|
481
|
+
const v = Number(e.target.value);
|
|
482
|
+
if (ccBriValue) ccBriValue.textContent = v.toFixed(2);
|
|
483
|
+
viewer.setColorBrightness(v);
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
if (ccCon) {
|
|
487
|
+
ccCon.value = "0.00";
|
|
488
|
+
if (ccConValue) ccConValue.textContent = "0.00";
|
|
489
|
+
viewer.setColorContrast(0.0);
|
|
490
|
+
ccCon.addEventListener("input", (e) => {
|
|
491
|
+
const v = Number(e.target.value);
|
|
492
|
+
if (ccConValue) ccConValue.textContent = v.toFixed(2);
|
|
493
|
+
viewer.setColorContrast(v);
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
syncCcUiEnabled();
|
|
11
497
|
// IFC загрузка
|
|
12
498
|
const ifc = new IfcService(viewer);
|
|
13
499
|
ifc.init();
|
|
@@ -52,11 +538,13 @@ if (app) {
|
|
|
52
538
|
const qualLow = document.getElementById("qualLow");
|
|
53
539
|
const qualMed = document.getElementById("qualMed");
|
|
54
540
|
const qualHigh = document.getElementById("qualHigh");
|
|
55
|
-
|
|
541
|
+
// Нижний тулбар пакета (index.html): Edges
|
|
542
|
+
const toggleEdges = document.getElementById("ifcToggleEdges");
|
|
56
543
|
const toggleShading = document.getElementById("toggleShading");
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
const
|
|
544
|
+
// Нижний тулбар пакета (index.html): секущие плоскости
|
|
545
|
+
const clipXBtn = document.getElementById("ifcClipX");
|
|
546
|
+
const clipYBtn = document.getElementById("ifcClipY");
|
|
547
|
+
const clipZBtn = document.getElementById("ifcClipZ");
|
|
60
548
|
const clipXRange = document.getElementById("clipXRange");
|
|
61
549
|
const clipYRange = document.getElementById("clipYRange");
|
|
62
550
|
const clipZRange = document.getElementById("clipZRange");
|
|
@@ -69,7 +557,9 @@ if (app) {
|
|
|
69
557
|
qualMed?.addEventListener("click", () => { viewer.setQuality('medium'); setActive(qualMed); });
|
|
70
558
|
qualHigh?.addEventListener("click", () => { viewer.setQuality('high'); setActive(qualHigh); });
|
|
71
559
|
|
|
72
|
-
|
|
560
|
+
// Рёбра по умолчанию выключены
|
|
561
|
+
let edgesOn = false;
|
|
562
|
+
viewer.setEdgesVisible(edgesOn);
|
|
73
563
|
toggleEdges?.addEventListener("click", () => { edgesOn = !edgesOn; viewer.setEdgesVisible(edgesOn); });
|
|
74
564
|
let flatOn = true;
|
|
75
565
|
toggleShading?.addEventListener("click", () => { flatOn = !flatOn; viewer.setFlatShading(flatOn); });
|