Sprint 35 — 뷰어 IFC 익스포트 연결 + Pset_BeamCommon

## 뷰어 통합
- `cimery-viewer` → `cimery-ifc` 의존성 추가.
- `project_file::scene_params_to_ifc()` 변환 함수:
  SceneParams 의 모든 파라미터(경간 수·교각 형식·skew·헌치·단면 종류·신축이음)
  를 BridgeExportParams 로 전부 매핑.
- egui 프로젝트 섹션에 "📤 IFC4X3 익스포트" 버튼.
  현재 파라미터 상태로 즉시 `projects/bridge.ifc` 생성.
- `project_file::default_ifc_path()` 헬퍼.

## Pset_BeamCommon (IFC Phase 3a)
- `write_pset_beam_common()`: 4개 속성
  · Reference (IFCIDENTIFIER) — 거더 라벨
  · Span (IFCLENGTHMEASURE) — mm
  · LoadBearing (IFCBOOLEAN) — .T.
  · IsExternal (IFCBOOLEAN) — .F.
- IFCRELDEFINESBYPROPERTIES 로 각 IFCBEAM 에 연결.
- `IfcSectionKind` public re-export (viewer 에서 직접 참조).

## 테스트
- pset_beam_common_attached_to_girders 추가. 17개 전체 통과.
- cargo check --workspace --features occt: 0 errors.

Phase 3 남은 로드맵:
- IfcAlignment + IfcLinearPlacement
- Camber 반영 (현재 직선 girder 만)
- Pset_BearingCommon, Pset_SlabCommon
- IfcElementAssembly 로 Pier(column+capbeam) 그룹화

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
minsung
2026-04-15 19:31:36 +09:00
parent 567345e933
commit 693c95dc6f
6 changed files with 107 additions and 2 deletions

View File

@@ -154,16 +154,19 @@ pub fn export_bridge(p: &BridgeExportParams) -> String {
let profile = write_girder_profile(&mut w, p.section_kind, p.girder_height);
let shape = write_extrude_shape(&mut w, geom_ctx, profile, span_mm);
let beam = w.alloc();
let girder_label = format!("Girder S{}-G{}", s + 1, i + 1);
w.emit(
beam,
&format!(
"IFCBEAM({},$,{},$,$,{},{},$,.BEAM.)",
lit(&new_ifc_guid()),
lit(&format!("Girder S{}-G{}", s + 1, i + 1)),
lit(&girder_label),
placement,
shape,
),
);
// Pset_BeamCommon (Sprint 35).
write_pset_beam_common(&mut w, beam, &girder_label, span_mm);
elements.push(beam);
}
}
@@ -438,6 +441,46 @@ fn write_girder_profile(
}
}
/// Pset_BeamCommon 생성 + `IfcRelDefinesByProperties` 로 beam 에 연결 (Sprint 35 Phase 3a).
///
/// # 속성
/// - `Reference`: 거더 식별자 (프로젝트별 시퀀스)
/// - `Span`: 경간 길이 [mm] — IfcLengthMeasure
/// - `LoadBearing`: `.T.` (true)
/// - `IsExternal`: `.F.`
fn write_pset_beam_common(
w: &mut IfcWriter,
beam: Ref,
reference: &str,
span_mm: f64,
) {
let p_ref = w.write(&format!(
"IFCPROPERTYSINGLEVALUE('Reference',$,IFCIDENTIFIER({}),$)",
lit(reference),
));
let p_span = w.write(&format!(
"IFCPROPERTYSINGLEVALUE('Span',$,IFCLENGTHMEASURE({}),$)",
real(span_mm),
));
let p_lb = w.write(
"IFCPROPERTYSINGLEVALUE('LoadBearing',$,IFCBOOLEAN(.T.),$)",
);
let p_ext = w.write(
"IFCPROPERTYSINGLEVALUE('IsExternal',$,IFCBOOLEAN(.F.),$)",
);
let pset = w.write(&format!(
"IFCPROPERTYSET({},$,'Pset_BeamCommon',$,{})",
lit(&new_ifc_guid()),
ref_list(&[p_ref, p_span, p_lb, p_ext]),
));
w.write(&format!(
"IFCRELDEFINESBYPROPERTIES({},$,$,$,({}),{})",
lit(&new_ifc_guid()),
beam,
pset,
));
}
/// Skew 회전 + 평행이동 LocalPlacement — 지점부 요소(교대·피어·받침·joint)에 적용.
/// skew_rad 는 Y축 중심 회전, pivot_z 기준 반지름 오프셋은 placement origin 에 반영.
fn write_local_placement_skewed(
@@ -568,6 +611,16 @@ mod tests {
assert_ne!(zero, skewed);
}
#[test]
fn pset_beam_common_attached_to_girders() {
let ifc = export_bridge(&BridgeExportParams::default());
assert!(ifc.contains("IFCPROPERTYSET"), "no Pset emitted");
assert!(ifc.contains("'Pset_BeamCommon'"), "Pset name missing");
assert!(ifc.contains("IFCRELDEFINESBYPROPERTIES"), "Pset not attached");
assert!(ifc.contains("LoadBearing"));
assert!(ifc.contains("Span"));
}
#[test]
fn haunch_moves_deck_up() {
// haunch 0 vs 200 → deck slab 중심 Y 위치가 다름. ifc text diff 로 확인.

View File

@@ -27,5 +27,5 @@ pub mod writer;
pub mod guid;
pub mod bridge_export;
pub use bridge_export::{BridgeExportParams, export_bridge};
pub use bridge_export::{BridgeExportParams, IfcSectionKind, export_bridge};
pub use writer::IfcWriter;