@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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/ifc/IfcService.js +228 -55
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sequent-org/ifc-viewer",
3
3
  "private": false,
4
- "version": "1.0.3-ci.7.0",
4
+ "version": "1.0.5-ci.9.0",
5
5
  "type": "module",
6
6
  "description": "IFC 3D model viewer component for web applications",
7
7
  "main": "src/index.js",
@@ -26,50 +26,115 @@ export class IfcService {
26
26
  }
27
27
 
28
28
  init() {
29
- this.loader = new IFCLoader();
30
- // Отключаем Web Worker: временно парсим в главном потоке для стабильности
31
- try { this.loader.ifcManager.useWebWorkers?.(false); } catch(_) {}
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
- // Настройка пути к WASM файлу
34
- if (this.wasmUrl) {
35
- // Используем переданный пользователем URL
53
+ for (let i = 0; i < wasmPaths.length; i++) {
36
54
  try {
37
- this.loader.ifcManager.setWasmPath(this.wasmUrl);
38
- } catch (_) {
39
- console.warn('IfcService: не удалось установить пользовательский wasmUrl:', this.wasmUrl);
40
- this._setDefaultWasmPath();
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
- * Устанавливает путь к WASM файлу по умолчанию
114
+ * Обработка критических ошибок инициализации
59
115
  * @private
60
116
  */
61
- _setDefaultWasmPath() {
62
- try {
63
- // Преобразуем URL файла wasm в URL каталога и передадим в воркер
64
- const wasmDir = new URL('.', WEBIFC_WASM_URL).href;
65
- this.loader.ifcManager.setWasmPath(wasmDir);
66
- // Дополнительно подстрахуемся передачей полного файла, если версия это поддерживает
67
- try { this.loader.ifcManager.setWasmPath(WEBIFC_WASM_URL); } catch(_) {}
68
- } catch (_) {
69
- this.loader.ifcManager.setWasmPath('/wasm/');
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
- const model = await this.loader.loadAsync(url);
157
- // Показать модель вместо демо-куба
158
- if (this.viewer.replaceWithModel) this.viewer.replaceWithModel(model);
159
- if (this.viewer.focusObject) this.viewer.focusObject(model);
160
- this.lastModel = model;
161
- this.lastFileName = file?.name || null;
162
- // Сообщим, что модель загружена
163
- try { document.dispatchEvent(new CustomEvent('ifc:model-loaded', { detail: { modelID: model.modelID } })); } catch(_) {}
164
- return model;
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
- alert("Ошибка загрузки IFC: " + (err?.message || err));
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
- const model = await this.loader.loadAsync(url);
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
- try { document.dispatchEvent(new CustomEvent('ifc:model-loaded', { detail: { modelID: model.modelID } })); } catch(_) {}
267
+ this._dispatchModelLoaded(model);
197
268
  return model;
198
269
  } catch (err) {
199
270
  console.error("IFC loadUrl error:", err);
200
- alert("Ошибка загрузки IFC по URL: " + (err?.message || err));
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