fix: [pmis] 방화셔터를 Jamb+하우징+패널 구조로 개선

- 내화시간 1시간→2시간으로 변경
- 좌/우 Jamb(문틀) + 상부 하우징(셔터박스) + 닫힌 패널 4파트 구조
- Jamb/하우징은 불투명, 셔터 패널만 반투명 빨간색
- 요소 통계에서 패널만 카운트 (부품 제외)
- 선택 시 부위/구역 정보 표시 추가
This commit is contained in:
김보곤
2026-03-12 22:56:34 +09:00
parent 695b6627c4
commit 824ea6f494

View File

@@ -331,7 +331,7 @@ class BimScene {
}
box(gx, gy, gz, pos, userData, group) {
const isTransparent = userData.type === 'window' || userData.type === 'fireShutter';
const isTransparent = userData.type === 'window' || userData.transparent;
const mesh = new THREE.Mesh(new THREE.BoxGeometry(gx, gy, gz), this.mat(COLORS[userData.type], isTransparent));
mesh.position.set(pos[0], pos[1], pos[2]);
mesh.castShadow = true; mesh.receiveShadow = true; mesh.userData = userData;
@@ -393,16 +393,42 @@ class BimScene {
});
this.box(sw - 0.4, 0.15, sd - 0.4, [sx, baseY + wh / 2, sz], { type: 'stair', name: `STAIR-LAND-${f + 1}F`, material: '철근콘크리트', floor: `${f + 1}F` }, 'stair');
}
// 방화셔터 — 계단실 입구 + 방화구획 경계
// 방화셔터 — Jamb(문틀) + 상부하우징 + 닫힌 셔터패널
const JW = 0.2, JD = 0.25, HH = 0.35; // Jamb 폭/깊이, Housing 높이
const fsCommon = { type: 'fireShutter', material: '방화셔터 (내화 2시간)' };
for (let f = 0; f < NF; f++) {
const baseY = f * FH + 0.3, sh = FH - 0.6;
// 계단실 입구 방화셔터 (X방향 개구부)
this.box(0.08, sh, sd - 0.6, [sx - sw / 2 - 0.1, baseY + sh / 2, sz],
{ type: 'fireShutter', name: `FS-STAIR-${f + 1}F`, material: '방화셔터 (내화 1시간)', floor: `${f + 1}F`, dimensions: `${(sd - 0.6).toFixed(1)}m × ${sh.toFixed(1)}m`, zone: '계단실 입구' }, 'fireShutter');
// 방화구획 경계 셔터 (X=20m, 40m 지점 — 건물을 3개 방화구획으로 분리)
// ── 계단실 입구 방화셔터 (Z방향 개구부) ──
const stX = sx - sw / 2 - 0.1, stW = sd - 0.6;
const stZ1 = sz - stW / 2, stZ2 = sz + stW / 2;
// 좌 Jamb
this.box(JD, sh, JW, [stX, baseY + sh / 2, stZ1 - JW / 2],
{ ...fsCommon, name: `FS-STAIR-${f + 1}F-JL`, floor: `${f + 1}F`, part: '좌측 Jamb' }, 'fireShutter');
// 우 Jamb
this.box(JD, sh, JW, [stX, baseY + sh / 2, stZ2 + JW / 2],
{ ...fsCommon, name: `FS-STAIR-${f + 1}F-JR`, floor: `${f + 1}F`, part: '우측 Jamb' }, 'fireShutter');
// 상부 하우징 (셔터박스)
this.box(JD + 0.1, HH, stW + JW * 2, [stX, baseY + sh - HH / 2, sz],
{ ...fsCommon, name: `FS-STAIR-${f + 1}F-BOX`, floor: `${f + 1}F`, part: '상부 하우징' }, 'fireShutter');
// 셔터 패널 (닫힘 상태, 반투명)
this.box(0.06, sh - HH, stW, [stX, baseY + (sh - HH) / 2, sz],
{ ...fsCommon, name: `FS-STAIR-${f + 1}F`, floor: `${f + 1}F`, transparent: true, dimensions: `${stW.toFixed(1)}m × ${(sh - HH).toFixed(1)}m`, zone: '계단실 입구' }, 'fireShutter');
// ── 방화구획 경계 셔터 (Z방향, X=20m/40m) ──
[20, 40].forEach((zoneX, zi) => {
this.box(0.08, sh, D - 1, [zoneX, baseY + sh / 2, D / 2],
{ type: 'fireShutter', name: `FS-ZONE${zi + 1}-${f + 1}F`, material: '방화셔터 (내화 1시간)', floor: `${f + 1}F`, dimensions: `${D - 1}m × ${sh.toFixed(1)}m`, zone: `방화구획 ${zi + 1}구간 경계` }, 'fireShutter');
const zW = D - 1, zZ1 = D / 2 - zW / 2, zZ2 = D / 2 + zW / 2;
// 좌 Jamb
this.box(JD, sh, JW, [zoneX, baseY + sh / 2, zZ1 - JW / 2],
{ ...fsCommon, name: `FS-ZONE${zi + 1}-${f + 1}F-JL`, floor: `${f + 1}F`, part: '좌측 Jamb' }, 'fireShutter');
// 우 Jamb
this.box(JD, sh, JW, [zoneX, baseY + sh / 2, zZ2 + JW / 2],
{ ...fsCommon, name: `FS-ZONE${zi + 1}-${f + 1}F-JR`, floor: `${f + 1}F`, part: '우측 Jamb' }, 'fireShutter');
// 상부 하우징
this.box(JD + 0.1, HH, zW + JW * 2, [zoneX, baseY + sh - HH / 2, D / 2],
{ ...fsCommon, name: `FS-ZONE${zi + 1}-${f + 1}F-BOX`, floor: `${f + 1}F`, part: '상부 하우징' }, 'fireShutter');
// 셔터 패널 (닫힘, 반투명)
this.box(0.06, sh - HH, zW, [zoneX, baseY + (sh - HH) / 2, D / 2],
{ ...fsCommon, name: `FS-ZONE${zi + 1}-${f + 1}F`, floor: `${f + 1}F`, transparent: true, dimensions: `${zW}m × ${(sh - HH).toFixed(1)}m`, zone: `방화구획 ${zi + 1}구간 경계` }, 'fireShutter');
});
}
}
@@ -561,7 +587,11 @@ class BimScene {
getCounts() {
if (this.mode === 'ifc') return this.ifcCounts;
const c = {};
this.meshes.forEach(m => { const t = m.userData.type; if (t) c[t] = (c[t] || 0) + 1; });
this.meshes.forEach(m => {
const t = m.userData.type; if (!t) return;
if (t === 'fireShutter' && m.userData.part) return; // Jamb/Housing 제외, 패널만 카운트
c[t] = (c[t] || 0) + 1;
});
return c;
}
@@ -789,9 +819,9 @@ function BimInfoPanel({ selected, counts, mode, ifcModelInfo }) {
<tr key={k} className="border-b border-gray-200/50"><td className="py-1.5 pr-2 text-gray-500 font-medium">{k}</td><td className="py-1.5 text-gray-700 break-all">{v}</td></tr>
))
) : (
[['유형', TYPE_LABEL[selected.type] || selected.type], ['재질', selected.material],
['층', selected.floor], ['치수', selected.dimensions], ['면', selected.face],
['방향', selected.direction], ['격자', selected.grid],
[['유형', TYPE_LABEL[selected.type] || selected.type], ['부위', selected.part],
['재질', selected.material], ['층', selected.floor], ['치수', selected.dimensions],
['구역', selected.zone], ['면', selected.face], ['방향', selected.direction], ['격자', selected.grid],
].filter(([, v]) => v).map(([k, v]) => (
<tr key={k} className="border-b border-gray-200/50"><td className="py-1.5 pr-2 text-gray-500 font-medium">{k}</td><td className="py-1.5 text-gray-700">{v}</td></tr>
))