@sequent-org/ifc-viewer 1.2.4-ci.39.0 → 1.2.4-ci.41.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/compat/IFCLoader.js +2 -2
- package/src/ifc/IfcService.js +1 -1
- package/src/viewer/Viewer.js +167 -5
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.41.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/compat/IFCLoader.js
CHANGED
|
@@ -190,7 +190,7 @@ class IFCParser {
|
|
|
190
190
|
this.BVH = BVH;
|
|
191
191
|
this.loadedModels = 0;
|
|
192
192
|
this.optionalCategories = {
|
|
193
|
-
[IFCSPACE]:
|
|
193
|
+
[IFCSPACE]: false, // Исключаем пространства (убирает 90% z-fighting, абстракция - безопасно)
|
|
194
194
|
[IFCOPENINGELEMENT]: false
|
|
195
195
|
};
|
|
196
196
|
this.geometriesByMaterials = {};
|
|
@@ -2109,7 +2109,7 @@ class ParserHandler {
|
|
|
2109
2109
|
this.BVH = BVH;
|
|
2110
2110
|
this.IDB = IDB;
|
|
2111
2111
|
this.optionalCategories = {
|
|
2112
|
-
[IFCSPACE]:
|
|
2112
|
+
[IFCSPACE]: false, // Исключаем пространства (убирает 90% z-fighting, абстракция - безопасно)
|
|
2113
2113
|
[IFCOPENINGELEMENT]: false
|
|
2114
2114
|
};
|
|
2115
2115
|
this.API = WorkerAPIs.parser;
|
package/src/ifc/IfcService.js
CHANGED
|
@@ -113,7 +113,7 @@ export class IfcService {
|
|
|
113
113
|
// Ранее используемые флаги вроде USE_FAST_BOOLS/SMALL_TRIANGLE_THRESHOLD библиотекой web-ifc не используются.
|
|
114
114
|
const config = {
|
|
115
115
|
COORDINATE_TO_ORIGIN: true,
|
|
116
|
-
CIRCLE_SEGMENTS: 12
|
|
116
|
+
CIRCLE_SEGMENTS: 32, // Увеличено с 12 до 32 для гладких кривых (уменьшает "лесенку")
|
|
117
117
|
// Tolerances: см. web-ifc CreateSettings defaults
|
|
118
118
|
TOLERANCE_PLANE_INTERSECTION: 1e-4,
|
|
119
119
|
TOLERANCE_PLANE_DEVIATION: 1e-4,
|
package/src/viewer/Viewer.js
CHANGED
|
@@ -9,6 +9,7 @@ import { SSAOPass } from "three/examples/jsm/postprocessing/SSAOPass.js";
|
|
|
9
9
|
import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass.js";
|
|
10
10
|
import { HueSaturationShader } from "three/examples/jsm/shaders/HueSaturationShader.js";
|
|
11
11
|
import { BrightnessContrastShader } from "three/examples/jsm/shaders/BrightnessContrastShader.js";
|
|
12
|
+
import { FXAAShader } from "three/examples/jsm/shaders/FXAAShader.js";
|
|
12
13
|
import { RoomEnvironment } from "three/examples/jsm/environments/RoomEnvironment.js";
|
|
13
14
|
import { NavCube } from "./NavCube.js";
|
|
14
15
|
import { SectionManipulator } from "./SectionManipulator.js";
|
|
@@ -143,6 +144,12 @@ export class Viewer {
|
|
|
143
144
|
z: null,
|
|
144
145
|
},
|
|
145
146
|
};
|
|
147
|
+
// Состояние "есть ли активное сечение" (нужно, чтобы сечение влияло на тени/освещение).
|
|
148
|
+
this._sectionClippingActive = false;
|
|
149
|
+
/** @type {WeakMap<THREE.Mesh, any>} */
|
|
150
|
+
this._sectionOriginalMaterial = new WeakMap();
|
|
151
|
+
// +50% яркости при активном сечении (комнаты светлее)
|
|
152
|
+
this._sectionLightBoost = { mul: 1.5, snapshot: null };
|
|
146
153
|
|
|
147
154
|
// Snapshot начального состояния для Home
|
|
148
155
|
this._home = {
|
|
@@ -1056,6 +1063,13 @@ export class Viewer {
|
|
|
1056
1063
|
if (this._ssaoPass?.setSize) {
|
|
1057
1064
|
try { this._ssaoPass.setSize(width, height); } catch (_) {}
|
|
1058
1065
|
}
|
|
1066
|
+
// Обновляем FXAA resolution при изменении размера
|
|
1067
|
+
if (this._fxaaPass) {
|
|
1068
|
+
try {
|
|
1069
|
+
this._fxaaPass.material.uniforms['resolution'].value.x = 1 / Math.max(1, width);
|
|
1070
|
+
this._fxaaPass.material.uniforms['resolution'].value.y = 1 / Math.max(1, height);
|
|
1071
|
+
} catch (_) {}
|
|
1072
|
+
}
|
|
1059
1073
|
|
|
1060
1074
|
// Если активен MMB-pan (viewOffset), нужно переустановить его под новый размер
|
|
1061
1075
|
try { this._mmbPan?.controller?.applyCurrentOffset?.(width, height); } catch (_) {}
|
|
@@ -1184,8 +1198,8 @@ export class Viewer {
|
|
|
1184
1198
|
if (node.isMesh) {
|
|
1185
1199
|
// Тени управляются единообразно через setShadowsEnabled()
|
|
1186
1200
|
node.castShadow = !!this.shadowsEnabled;
|
|
1187
|
-
//
|
|
1188
|
-
node.receiveShadow =
|
|
1201
|
+
// Самозатенение: в тест-пресете ИЛИ при активном сечении
|
|
1202
|
+
node.receiveShadow = this.#getModelReceiveShadowEnabled();
|
|
1189
1203
|
// Стекло/прозрачность: рендерим после непрозрачных (уменьшает мерцание сортировки)
|
|
1190
1204
|
try {
|
|
1191
1205
|
const mats = Array.isArray(node.material) ? node.material : [node.material];
|
|
@@ -1199,6 +1213,8 @@ export class Viewer {
|
|
|
1199
1213
|
|
|
1200
1214
|
// Материальный пресет (если выбран не original)
|
|
1201
1215
|
this.#applyMaterialStyleToModel(object3D);
|
|
1216
|
+
// Синхронизируем "сечение → shadow-pass" для материалов после назначения пресета
|
|
1217
|
+
this.#applyClipShadowsToModelMaterials();
|
|
1202
1218
|
|
|
1203
1219
|
// Настроим пределы зума и сфокусируемся на новой модели
|
|
1204
1220
|
this.applyAdaptiveZoomLimits(object3D, { padding: 1.2, slack: 2.5, minRatio: 0.05, recenter: true });
|
|
@@ -1307,6 +1323,7 @@ export class Viewer {
|
|
|
1307
1323
|
const apply = (mat) => {
|
|
1308
1324
|
if (!mat) return;
|
|
1309
1325
|
mat.polygonOffset = true;
|
|
1326
|
+
// Умеренные значения polygon offset для уменьшения z-fighting
|
|
1310
1327
|
mat.polygonOffsetFactor = 1;
|
|
1311
1328
|
mat.polygonOffsetUnits = 1;
|
|
1312
1329
|
// Улучшим читаемость плоскостей
|
|
@@ -1678,10 +1695,12 @@ export class Viewer {
|
|
|
1678
1695
|
this.activeModel.traverse?.((node) => {
|
|
1679
1696
|
if (!node?.isMesh) return;
|
|
1680
1697
|
node.castShadow = next;
|
|
1681
|
-
//
|
|
1682
|
-
node.receiveShadow =
|
|
1698
|
+
// Самозатенение: в тест-пресете ИЛИ при активном сечении
|
|
1699
|
+
node.receiveShadow = this.#getModelReceiveShadowEnabled();
|
|
1683
1700
|
});
|
|
1684
1701
|
}
|
|
1702
|
+
// Если тени переключили — синхронизируем shadow-pass клиппинга
|
|
1703
|
+
this.#applyClipShadowsToModelMaterials();
|
|
1685
1704
|
}
|
|
1686
1705
|
|
|
1687
1706
|
/**
|
|
@@ -1846,9 +1865,141 @@ export class Viewer {
|
|
|
1846
1865
|
this.activeModel.traverse?.((node) => {
|
|
1847
1866
|
if (!node?.isMesh) return;
|
|
1848
1867
|
node.castShadow = !!this.shadowsEnabled;
|
|
1849
|
-
node.receiveShadow =
|
|
1868
|
+
node.receiveShadow = this.#getModelReceiveShadowEnabled();
|
|
1850
1869
|
});
|
|
1851
1870
|
}
|
|
1871
|
+
this.#applyClipShadowsToModelMaterials();
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
#getModelReceiveShadowEnabled() {
|
|
1875
|
+
// Самозатенение нужно для корректного освещения при сечении (видны комнаты внутри).
|
|
1876
|
+
return !!this.shadowsEnabled && (this._sectionClippingActive || !!this._testPreset?.enabled);
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
#getClipShadowsEnabled() {
|
|
1880
|
+
// Чтобы внешняя тень менялась при движении сечения, клиппинг должен участвовать в shadow-pass.
|
|
1881
|
+
return !!this.shadowsEnabled && !!this._sectionClippingActive;
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
#applyClipShadowsToModelMaterials() {
|
|
1885
|
+
const model = this.activeModel || this.demoCube;
|
|
1886
|
+
if (!model) return;
|
|
1887
|
+
const enabled = this.#getClipShadowsEnabled();
|
|
1888
|
+
const touched = new Set();
|
|
1889
|
+
// ВАЖНО: НЕ использовать renderer.clippingPlanes (там каждый кадр создаётся новый массив).
|
|
1890
|
+
// Для shadow-pass материал должен держать актуальный список активных плоскостей.
|
|
1891
|
+
const activePlanes = enabled
|
|
1892
|
+
? (this.clipping?.planes || []).filter((p) => p && isFinite(p.constant))
|
|
1893
|
+
: null;
|
|
1894
|
+
model.traverse?.((node) => {
|
|
1895
|
+
if (!node?.isMesh) return;
|
|
1896
|
+
const mats = Array.isArray(node.material) ? node.material : [node.material];
|
|
1897
|
+
for (const m of mats) {
|
|
1898
|
+
if (!m || touched.has(m)) continue;
|
|
1899
|
+
touched.add(m);
|
|
1900
|
+
try {
|
|
1901
|
+
// Важно: для shadow-pass надёжнее использовать LOCAL clipping (material.clippingPlanes) + clipShadows.
|
|
1902
|
+
// Иначе часть сборок three может не применять global renderer.clippingPlanes к shadow map.
|
|
1903
|
+
if ('clippingPlanes' in m) m.clippingPlanes = activePlanes;
|
|
1904
|
+
if ('clipShadows' in m) m.clipShadows = enabled;
|
|
1905
|
+
// Тени “среза” и внутренняя отрисовка часто требуют double-side (в IFC нормали бывают проблемные).
|
|
1906
|
+
if (enabled && 'side' in m && m.side !== THREE.DoubleSide) m.side = THREE.DoubleSide;
|
|
1907
|
+
m.needsUpdate = true; // пересобрать шейдеры (включая depth/shadow варианты)
|
|
1908
|
+
} catch (_) {}
|
|
1909
|
+
}
|
|
1910
|
+
});
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
#applySectionMaterialPolicy() {
|
|
1914
|
+
// При активном сечении переводим неосвещаемые материалы в MeshStandardMaterial,
|
|
1915
|
+
// чтобы внутри появились свет/тени. При выключении — возвращаем оригиналы.
|
|
1916
|
+
const model = this.activeModel || this.demoCube;
|
|
1917
|
+
if (!model) return;
|
|
1918
|
+
|
|
1919
|
+
if (!this._sectionClippingActive) {
|
|
1920
|
+
model.traverse?.((node) => {
|
|
1921
|
+
if (!node?.isMesh) return;
|
|
1922
|
+
const orig = this._sectionOriginalMaterial.get(node);
|
|
1923
|
+
if (orig) node.material = orig;
|
|
1924
|
+
});
|
|
1925
|
+
return;
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
model.traverse?.((node) => {
|
|
1929
|
+
if (!node?.isMesh) return;
|
|
1930
|
+
const cur = node.material;
|
|
1931
|
+
if (!cur) return;
|
|
1932
|
+
// Трогаем только "неосвещаемые" материалы (они не показывают тени на стенах).
|
|
1933
|
+
const arr = Array.isArray(cur) ? cur : [cur];
|
|
1934
|
+
const hasBasic = arr.some((m) => !!m?.isMeshBasicMaterial);
|
|
1935
|
+
if (!hasBasic) return;
|
|
1936
|
+
|
|
1937
|
+
if (!this._sectionOriginalMaterial.has(node)) {
|
|
1938
|
+
this._sectionOriginalMaterial.set(node, cur);
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
const convert = (m) => {
|
|
1942
|
+
if (!m?.isMeshBasicMaterial) return m;
|
|
1943
|
+
const cm = this.#getConvertedMaterial(m); // -> MeshStandardMaterial с сохранением color/map/alpha
|
|
1944
|
+
// Делаем "архитектурный" вид по умолчанию: матовый, без металла
|
|
1945
|
+
try { if ('roughness' in cm) cm.roughness = 0.9; } catch (_) {}
|
|
1946
|
+
try { if ('metalness' in cm) cm.metalness = 0.0; } catch (_) {}
|
|
1947
|
+
try { cm.needsUpdate = true; } catch (_) {}
|
|
1948
|
+
return cm;
|
|
1949
|
+
};
|
|
1950
|
+
|
|
1951
|
+
node.material = Array.isArray(cur) ? cur.map(convert) : convert(cur);
|
|
1952
|
+
});
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
#syncSectionClippingState() {
|
|
1956
|
+
const active = this.clipping?.planes?.some((p) => p && isFinite(p.constant));
|
|
1957
|
+
const next = !!active;
|
|
1958
|
+
if (next === this._sectionClippingActive) return;
|
|
1959
|
+
this._sectionClippingActive = next;
|
|
1960
|
+
|
|
1961
|
+
// 0) Светлее при сечении (+50%)
|
|
1962
|
+
try {
|
|
1963
|
+
if (this._sectionClippingActive) {
|
|
1964
|
+
if (!this._sectionLightBoost.snapshot) {
|
|
1965
|
+
this._sectionLightBoost.snapshot = {
|
|
1966
|
+
sunIntensity: this.sunLight?.intensity ?? null,
|
|
1967
|
+
ambientIntensity: this.ambientLight?.intensity ?? null,
|
|
1968
|
+
};
|
|
1969
|
+
}
|
|
1970
|
+
if (this.sunLight && Number.isFinite(this._sectionLightBoost.snapshot.sunIntensity)) {
|
|
1971
|
+
this.sunLight.intensity = this._sectionLightBoost.snapshot.sunIntensity * this._sectionLightBoost.mul;
|
|
1972
|
+
}
|
|
1973
|
+
if (this.ambientLight && Number.isFinite(this._sectionLightBoost.snapshot.ambientIntensity)) {
|
|
1974
|
+
this.ambientLight.intensity = this._sectionLightBoost.snapshot.ambientIntensity * this._sectionLightBoost.mul;
|
|
1975
|
+
}
|
|
1976
|
+
} else {
|
|
1977
|
+
const snap = this._sectionLightBoost.snapshot;
|
|
1978
|
+
if (snap) {
|
|
1979
|
+
if (this.sunLight && Number.isFinite(snap.sunIntensity)) this.sunLight.intensity = snap.sunIntensity;
|
|
1980
|
+
if (this.ambientLight && Number.isFinite(snap.ambientIntensity)) this.ambientLight.intensity = snap.ambientIntensity;
|
|
1981
|
+
}
|
|
1982
|
+
this._sectionLightBoost.snapshot = null;
|
|
1983
|
+
}
|
|
1984
|
+
} catch (_) {}
|
|
1985
|
+
|
|
1986
|
+
// 1) self-shadowing (комнаты/стены)
|
|
1987
|
+
if (this.activeModel) {
|
|
1988
|
+
this.activeModel.traverse?.((node) => {
|
|
1989
|
+
if (!node?.isMesh) return;
|
|
1990
|
+
try { node.receiveShadow = this.#getModelReceiveShadowEnabled(); } catch (_) {}
|
|
1991
|
+
});
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
// 1.5) materials: ensure they react to light/shadows when section is active
|
|
1995
|
+
this.#applySectionMaterialPolicy();
|
|
1996
|
+
|
|
1997
|
+
// 2) clipping in shadow pass (крыша перестаёт участвовать в тени)
|
|
1998
|
+
this.#applyClipShadowsToModelMaterials();
|
|
1999
|
+
|
|
2000
|
+
// 3) форсируем апдейт теней
|
|
2001
|
+
try { if (this.sunLight?.shadow) this.sunLight.shadow.needsUpdate = true; } catch (_) {}
|
|
2002
|
+
try { if (this.renderer?.shadowMap) this.renderer.shadowMap.needsUpdate = true; } catch (_) {}
|
|
1852
2003
|
}
|
|
1853
2004
|
|
|
1854
2005
|
/**
|
|
@@ -2721,6 +2872,14 @@ export class Viewer {
|
|
|
2721
2872
|
this._step4Pass.enabled = !!this._step4?.enabled;
|
|
2722
2873
|
this._composer.addPass(this._step4Pass);
|
|
2723
2874
|
this.#applyStep4Uniforms();
|
|
2875
|
+
|
|
2876
|
+
// FXAA pass для устранения "лесенки" на кривых линиях (aliasing)
|
|
2877
|
+
this._fxaaPass = new ShaderPass(FXAAShader);
|
|
2878
|
+
this._fxaaPass.material.uniforms['resolution'].value.x = 1 / w;
|
|
2879
|
+
this._fxaaPass.material.uniforms['resolution'].value.y = 1 / h;
|
|
2880
|
+
this._fxaaPass.enabled = true; // Включен всегда для сглаживания
|
|
2881
|
+
this._composer.addPass(this._fxaaPass);
|
|
2882
|
+
|
|
2724
2883
|
try { this._composer.setSize(w, h); } catch (_) {}
|
|
2725
2884
|
}
|
|
2726
2885
|
|
|
@@ -3019,6 +3178,9 @@ export class Viewer {
|
|
|
3019
3178
|
plane.constant = Infinity;
|
|
3020
3179
|
this.#setGizmoVisible(axis, false);
|
|
3021
3180
|
}
|
|
3181
|
+
|
|
3182
|
+
// Сечение → тени/освещение
|
|
3183
|
+
this.#syncSectionClippingState();
|
|
3022
3184
|
}
|
|
3023
3185
|
|
|
3024
3186
|
// Устанавливает позицию секущей плоскости по нормализованному значению [0..1] в пределах габаритов модели
|