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:
@@ -12,6 +12,11 @@
|
||||
## 타임라인
|
||||
|
||||
### 2026-04-15 (계속)
|
||||
- code — Sprint 35: IFC 뷰어 통합 + Pset_BeamCommon.
|
||||
- `cimery-viewer` 에 `cimery-ifc` 의존성 추가. `project_file::scene_params_to_ifc()` 변환 함수 (SceneParams → BridgeExportParams 전 필드 매핑).
|
||||
- 프로젝트 섹션에 "📤 IFC4X3 익스포트" 버튼. `projects/bridge.ifc` 로 저장, 현재 파라미터(경간 수·교각 형식·skew·헌치·단면 등) 그대로 반영.
|
||||
- `write_pset_beam_common()` 추가: Reference(이름) + Span(mm) + LoadBearing + IsExternal 4 속성, `IFCRELDEFINESBYPROPERTIES` 로 거더 각 beam 에 연결.
|
||||
- `IfcSectionKind` public re-export. 테스트 17개 통과.
|
||||
- code — Sprint 34: IFC4X3 Add2 익스포터 Phase 2. 정확도·커버리지 확장.
|
||||
- PSC-I 실제 14점 단면 `IFCARBITRARYCLOSEDPROFILEDEF` + `IFCPOLYLINE` 구현 (도심 중심화 Y 평행이동). `IfcSectionKind` enum 으로 단면 종류 분기.
|
||||
- Skew 회전 `write_local_placement_skewed()`: `IFCAXIS2PLACEMENT3D` RefDirection 을 Y축 회전 X축으로 설정. 교대·피어·받침·신축이음에 적용. 거더·데크는 직선 유지.
|
||||
|
||||
@@ -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 로 확인.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -19,6 +19,7 @@ path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
cimery-kernel = { workspace = true }
|
||||
cimery-ifc = { workspace = true }
|
||||
log = { workspace = true }
|
||||
env_logger = { workspace = true }
|
||||
wgpu = "22"
|
||||
|
||||
@@ -764,6 +764,18 @@ impl RenderState {
|
||||
}
|
||||
}
|
||||
});
|
||||
// Sprint 35: IFC4X3 Add2 익스포트 (현재 파라미터 기준).
|
||||
if ui.button("📤 IFC4X3 익스포트").clicked() {
|
||||
let params = project_file::scene_params_to_ifc(&p, "bridge");
|
||||
let ifc = cimery_ifc::export_bridge(¶ms);
|
||||
let path = project_file::default_ifc_path("bridge");
|
||||
match std::fs::write(&path, &ifc) {
|
||||
Ok(_) => log::info!(
|
||||
"IFC exported: {:?} ({} bytes)", path, ifc.len()
|
||||
),
|
||||
Err(e) => log::error!("IFC export failed: {e}"),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
||||
@@ -131,3 +131,37 @@ pub fn default_save_path(name: &str) -> std::path::PathBuf {
|
||||
p.push(format!("{}.cimery.json", name));
|
||||
p
|
||||
}
|
||||
|
||||
/// IFC 파일 기본 저장 경로.
|
||||
pub fn default_ifc_path(name: &str) -> std::path::PathBuf {
|
||||
let mut p = std::path::PathBuf::from("projects");
|
||||
std::fs::create_dir_all(&p).ok();
|
||||
p.push(format!("{}.ifc", name));
|
||||
p
|
||||
}
|
||||
|
||||
/// SceneParams → cimery-ifc::BridgeExportParams 변환.
|
||||
///
|
||||
/// viewer 의 SceneParams 에 있는 모든 파라미터를 IFC 익스포터 입력으로 매핑.
|
||||
/// 선형(alignment)·camber 는 IFC Phase 3 로드맵(미반영).
|
||||
pub fn scene_params_to_ifc(p: &SceneParams, name: &str) -> cimery_ifc::BridgeExportParams {
|
||||
use cimery_ifc::{BridgeExportParams, IfcSectionKind};
|
||||
BridgeExportParams {
|
||||
name: name.to_owned(),
|
||||
span_m: p.span_m,
|
||||
span_count: p.span_count,
|
||||
girder_count: p.girder_count,
|
||||
girder_spacing: p.girder_spacing as f64,
|
||||
girder_height: p.girder_height as f64,
|
||||
slab_thickness: p.slab_thickness as f64,
|
||||
bearing_height: 60.0,
|
||||
section_kind: match p.section_type {
|
||||
GirderSectionType::PscI => IfcSectionKind::PscI,
|
||||
GirderSectionType::SteelBox => IfcSectionKind::SteelBox,
|
||||
},
|
||||
skew_deg: p.skew_deg as f64,
|
||||
haunch_depth: p.haunch_depth as f64,
|
||||
show_parapets: true,
|
||||
show_joints: p.show_expansion_joints,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user