@sequent-org/ifc-viewer 1.0.3-ci.7.0 → 1.0.5-ci.9.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/package.json +1 -1
- package/src/ifc/IfcService.js +228 -55
package/package.json
CHANGED
package/src/ifc/IfcService.js
CHANGED
|
@@ -26,50 +26,115 @@ export class IfcService {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
init() {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
try {
|
|
30
|
+
this.loader = new IFCLoader();
|
|
31
|
+
// Отключаем Web Worker: временно парсим в главном потоке для стабильности
|
|
32
|
+
try { this.loader.ifcManager.useWebWorkers?.(false); } catch(_) {}
|
|
33
|
+
|
|
34
|
+
// Настройка пути к WASM файлу с улучшенной обработкой ошибок
|
|
35
|
+
this._setupWasmPath();
|
|
36
|
+
|
|
37
|
+
// Настройка конфигурации web-ifc
|
|
38
|
+
this._setupWebIfcConfig();
|
|
39
|
+
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error('IfcService: критическая ошибка инициализации:', error);
|
|
42
|
+
this._handleCriticalError(error);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Настройка пути к WASM файлу с fallback
|
|
48
|
+
* @private
|
|
49
|
+
*/
|
|
50
|
+
_setupWasmPath() {
|
|
51
|
+
const wasmPaths = this._getWasmPaths();
|
|
32
52
|
|
|
33
|
-
|
|
34
|
-
if (this.wasmUrl) {
|
|
35
|
-
// Используем переданный пользователем URL
|
|
53
|
+
for (let i = 0; i < wasmPaths.length; i++) {
|
|
36
54
|
try {
|
|
37
|
-
this.loader.ifcManager.setWasmPath(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
55
|
+
this.loader.ifcManager.setWasmPath(wasmPaths[i]);
|
|
56
|
+
console.log(`IfcService: WASM путь установлен: ${wasmPaths[i]}`);
|
|
57
|
+
return; // Успешно установлен
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.warn(`IfcService: не удалось установить WASM путь ${wasmPaths[i]}:`, error.message);
|
|
60
|
+
if (i === wasmPaths.length - 1) {
|
|
61
|
+
// Последний путь тоже не сработал
|
|
62
|
+
throw new Error('Все пути к WASM файлу недоступны');
|
|
63
|
+
}
|
|
41
64
|
}
|
|
42
|
-
} else {
|
|
43
|
-
// Используем путь по умолчанию
|
|
44
|
-
this._setDefaultWasmPath();
|
|
45
65
|
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Получает список путей к WASM файлу в порядке приоритета
|
|
70
|
+
* @private
|
|
71
|
+
*/
|
|
72
|
+
_getWasmPaths() {
|
|
73
|
+
const paths = [];
|
|
74
|
+
|
|
75
|
+
// 1. Пользовательский путь (если указан)
|
|
76
|
+
if (this.wasmUrl) {
|
|
77
|
+
paths.push(this.wasmUrl);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 2. Пути по умолчанию
|
|
81
|
+
try {
|
|
82
|
+
const wasmDir = new URL('.', WEBIFC_WASM_URL).href;
|
|
83
|
+
paths.push(wasmDir);
|
|
84
|
+
} catch (_) {}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
paths.push(WEBIFC_WASM_URL);
|
|
88
|
+
} catch (_) {}
|
|
89
|
+
|
|
90
|
+
// 3. Резервные пути
|
|
91
|
+
paths.push('/wasm/', '/wasm/web-ifc.wasm', '/web-ifc.wasm');
|
|
92
|
+
|
|
93
|
+
return paths;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Настройка конфигурации web-ifc
|
|
98
|
+
* @private
|
|
99
|
+
*/
|
|
100
|
+
_setupWebIfcConfig() {
|
|
46
101
|
try {
|
|
47
102
|
this.loader.ifcManager.applyWebIfcConfig?.({
|
|
48
103
|
COORDINATE_TO_ORIGIN: true,
|
|
49
104
|
USE_FAST_BOOLS: true,
|
|
50
105
|
// Порог игнорирования очень мелких полигонов (уменьшаем шум)
|
|
51
|
-
// Некоторые сборки поддерживают SMALL_TRIANGLE_THRESHOLD
|
|
52
106
|
SMALL_TRIANGLE_THRESHOLD: 1e-9,
|
|
53
107
|
});
|
|
54
|
-
} catch(
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.warn('IfcService: не удалось применить конфигурацию web-ifc:', error.message);
|
|
110
|
+
}
|
|
55
111
|
}
|
|
56
112
|
|
|
57
113
|
/**
|
|
58
|
-
*
|
|
114
|
+
* Обработка критических ошибок инициализации
|
|
59
115
|
* @private
|
|
60
116
|
*/
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
117
|
+
_handleCriticalError(error) {
|
|
118
|
+
// Создаем заглушку для loader, чтобы избежать падения
|
|
119
|
+
this.loader = {
|
|
120
|
+
ifcManager: {
|
|
121
|
+
setWasmPath: () => {},
|
|
122
|
+
applyWebIfcConfig: () => {},
|
|
123
|
+
useWebWorkers: () => {},
|
|
124
|
+
load: () => Promise.reject(new Error('WASM не инициализирован'))
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// Уведомляем о критической ошибке
|
|
129
|
+
this.viewer?.container?.dispatchEvent(new CustomEvent('ifcviewer:error', {
|
|
130
|
+
detail: {
|
|
131
|
+
error: new Error('Критическая ошибка инициализации WASM: ' + error.message),
|
|
132
|
+
type: 'wasm_init_error'
|
|
133
|
+
}
|
|
134
|
+
}));
|
|
71
135
|
}
|
|
72
136
|
|
|
137
|
+
|
|
73
138
|
/**
|
|
74
139
|
* Возвращает пространственную структуру IFC (иерархия) для активной модели
|
|
75
140
|
* Структура: { expressID, type, children: [...] }
|
|
@@ -141,33 +206,36 @@ export class IfcService {
|
|
|
141
206
|
* @param {File} file
|
|
142
207
|
*/
|
|
143
208
|
async loadFile(file) {
|
|
144
|
-
if (!this.loader) this.init();
|
|
145
|
-
// Проверка расширения: поддерживаются .ifc и .ifczip
|
|
146
|
-
const name = (file?.name || "").toLowerCase();
|
|
147
|
-
const isIFC = name.endsWith(".ifc");
|
|
148
|
-
const isIFS = name.endsWith(".ifs");
|
|
149
|
-
const isZIP = name.endsWith(".ifczip") || name.endsWith(".zip");
|
|
150
|
-
if (!isIFC && !isIFS && !isZIP) {
|
|
151
|
-
alert("Формат не поддерживается. Используйте .ifc, .ifs или .ifczip");
|
|
152
|
-
return null;
|
|
153
|
-
}
|
|
154
|
-
const url = URL.createObjectURL(file);
|
|
155
209
|
try {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
210
|
+
if (!this.loader) this.init();
|
|
211
|
+
|
|
212
|
+
// Проверка расширения: поддерживаются .ifc и .ifczip
|
|
213
|
+
const name = (file?.name || "").toLowerCase();
|
|
214
|
+
const isIFC = name.endsWith(".ifc");
|
|
215
|
+
const isIFS = name.endsWith(".ifs");
|
|
216
|
+
const isZIP = name.endsWith(".ifczip") || name.endsWith(".zip");
|
|
217
|
+
if (!isIFC && !isIFS && !isZIP) {
|
|
218
|
+
throw new Error("Формат не поддерживается. Используйте .ifc, .ifs или .ifczip");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const url = URL.createObjectURL(file);
|
|
222
|
+
try {
|
|
223
|
+
const model = await this._loadModelWithFallback(url);
|
|
224
|
+
// Показать модель вместо демо-куба
|
|
225
|
+
if (this.viewer.replaceWithModel) this.viewer.replaceWithModel(model);
|
|
226
|
+
if (this.viewer.focusObject) this.viewer.focusObject(model);
|
|
227
|
+
this.lastModel = model;
|
|
228
|
+
this.lastFileName = file?.name || null;
|
|
229
|
+
// Сообщим, что модель загружена
|
|
230
|
+
this._dispatchModelLoaded(model);
|
|
231
|
+
return model;
|
|
232
|
+
} finally {
|
|
233
|
+
URL.revokeObjectURL(url);
|
|
234
|
+
}
|
|
165
235
|
} catch (err) {
|
|
166
236
|
console.error("IFC load error:", err);
|
|
167
|
-
|
|
237
|
+
this._handleLoadError(err, 'loadFile');
|
|
168
238
|
return null;
|
|
169
|
-
} finally {
|
|
170
|
-
URL.revokeObjectURL(url);
|
|
171
239
|
}
|
|
172
240
|
}
|
|
173
241
|
|
|
@@ -176,15 +244,17 @@ export class IfcService {
|
|
|
176
244
|
* @param {string} url
|
|
177
245
|
*/
|
|
178
246
|
async loadUrl(url) {
|
|
179
|
-
if (!this.loader) this.init();
|
|
180
|
-
if (!url) return null;
|
|
181
247
|
try {
|
|
182
|
-
|
|
183
|
-
|
|
248
|
+
if (!this.loader) this.init();
|
|
249
|
+
if (!url) return null;
|
|
250
|
+
|
|
251
|
+
const model = await this._loadModelWithFallback(url);
|
|
184
252
|
if (!model || !model.geometry) throw new Error('IFC model returned without geometry');
|
|
253
|
+
|
|
185
254
|
if (this.viewer.replaceWithModel) this.viewer.replaceWithModel(model);
|
|
186
255
|
if (this.viewer.focusObject) this.viewer.focusObject(model);
|
|
187
256
|
this.lastModel = model;
|
|
257
|
+
|
|
188
258
|
try {
|
|
189
259
|
// Показать имя файла из URL
|
|
190
260
|
const u = new URL(url, window.location.origin);
|
|
@@ -192,12 +262,13 @@ export class IfcService {
|
|
|
192
262
|
} catch (_) {
|
|
193
263
|
this.lastFileName = url;
|
|
194
264
|
}
|
|
265
|
+
|
|
195
266
|
// Сообщим, что модель загружена
|
|
196
|
-
|
|
267
|
+
this._dispatchModelLoaded(model);
|
|
197
268
|
return model;
|
|
198
269
|
} catch (err) {
|
|
199
270
|
console.error("IFC loadUrl error:", err);
|
|
200
|
-
|
|
271
|
+
this._handleLoadError(err, 'loadUrl');
|
|
201
272
|
return null;
|
|
202
273
|
}
|
|
203
274
|
}
|
|
@@ -285,6 +356,108 @@ export class IfcService {
|
|
|
285
356
|
this.lastModel.visible = true;
|
|
286
357
|
}
|
|
287
358
|
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Загружает модель с fallback обработкой ошибок WASM
|
|
362
|
+
* @private
|
|
363
|
+
*/
|
|
364
|
+
async _loadModelWithFallback(url) {
|
|
365
|
+
try {
|
|
366
|
+
return await this.loader.loadAsync(url);
|
|
367
|
+
} catch (error) {
|
|
368
|
+
// Проверяем, связана ли ошибка с WASM
|
|
369
|
+
if (this._isWasmError(error)) {
|
|
370
|
+
console.warn('IfcService: обнаружена ошибка WASM, пытаемся переинициализировать...');
|
|
371
|
+
await this._reinitializeWithFallback();
|
|
372
|
+
// Повторная попытка загрузки
|
|
373
|
+
return await this.loader.loadAsync(url);
|
|
374
|
+
}
|
|
375
|
+
throw error;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Проверяет, связана ли ошибка с WASM
|
|
381
|
+
* @private
|
|
382
|
+
*/
|
|
383
|
+
_isWasmError(error) {
|
|
384
|
+
const message = error?.message?.toLowerCase() || '';
|
|
385
|
+
return message.includes('wasm') ||
|
|
386
|
+
message.includes('webassembly') ||
|
|
387
|
+
message.includes('module') ||
|
|
388
|
+
message.includes('instantiate');
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Переинициализирует loader с fallback путями
|
|
393
|
+
* @private
|
|
394
|
+
*/
|
|
395
|
+
async _reinitializeWithFallback() {
|
|
396
|
+
try {
|
|
397
|
+
// Попробуем переинициализировать с другими путями
|
|
398
|
+
this.loader = new IFCLoader();
|
|
399
|
+
this._setupWasmPath();
|
|
400
|
+
this._setupWebIfcConfig();
|
|
401
|
+
} catch (error) {
|
|
402
|
+
console.error('IfcService: не удалось переинициализировать:', error);
|
|
403
|
+
throw new Error('Критическая ошибка WASM: невозможно загрузить модель');
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Отправляет событие о загрузке модели
|
|
409
|
+
* @private
|
|
410
|
+
*/
|
|
411
|
+
_dispatchModelLoaded(model) {
|
|
412
|
+
try {
|
|
413
|
+
document.dispatchEvent(new CustomEvent('ifc:model-loaded', {
|
|
414
|
+
detail: { modelID: model.modelID }
|
|
415
|
+
}));
|
|
416
|
+
} catch (_) {}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Обрабатывает ошибки загрузки
|
|
421
|
+
* @private
|
|
422
|
+
*/
|
|
423
|
+
_handleLoadError(error, method) {
|
|
424
|
+
const errorMessage = `Ошибка загрузки IFC (${method}): ${error?.message || error}`;
|
|
425
|
+
|
|
426
|
+
// Отправляем событие об ошибке
|
|
427
|
+
this.viewer?.container?.dispatchEvent(new CustomEvent('ifcviewer:error', {
|
|
428
|
+
detail: {
|
|
429
|
+
error: new Error(errorMessage),
|
|
430
|
+
type: 'load_error',
|
|
431
|
+
method: method
|
|
432
|
+
}
|
|
433
|
+
}));
|
|
434
|
+
|
|
435
|
+
// Показываем пользователю понятное сообщение
|
|
436
|
+
const userMessage = this._getUserFriendlyMessage(error);
|
|
437
|
+
alert(userMessage);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Возвращает понятное пользователю сообщение об ошибке
|
|
442
|
+
* @private
|
|
443
|
+
*/
|
|
444
|
+
_getUserFriendlyMessage(error) {
|
|
445
|
+
const message = error?.message?.toLowerCase() || '';
|
|
446
|
+
|
|
447
|
+
if (message.includes('wasm') || message.includes('webassembly')) {
|
|
448
|
+
return 'Ошибка загрузки WASM модуля. Проверьте доступность файла web-ifc.wasm';
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (message.includes('network') || message.includes('fetch')) {
|
|
452
|
+
return 'Ошибка сети при загрузке файла. Проверьте подключение к интернету';
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (message.includes('format') || message.includes('parse')) {
|
|
456
|
+
return 'Ошибка формата файла. Убедитесь, что файл является корректным IFC файлом';
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return `Ошибка загрузки модели: ${error?.message || 'Неизвестная ошибка'}`;
|
|
460
|
+
}
|
|
288
461
|
}
|
|
289
462
|
|
|
290
463
|
|