Files
ParaWiki/cimery/crates/kernel/src/abutment.rs
minsung bdacea5253 Sprint 3 — Must Feature 5종 추가 (상부→하부 순서)
상부 구조물:
- DeckSlabIR + DeckSlabBuilder + 기하학 (직사각형 슬래브 스위프)

연결부:
- BearingIR + BearingBuilder (카탈로그 기반, KDS 기본값 포함)

하부 구조물:
- PierIR + PierBuilder + 기하학 (다주 지원, 코핑 포함)
- AbutmentIR + AbutmentBuilder + 기하학 (흉벽 + 기초 + 날개벽)

core에 BearingType·PierType·ColumnShape·AbutmentType 열거형 추가
kernel: sweep.rs 공유 모듈 (sweep_profile_flat·box·prism·merge)
psc_i.rs → sweep.rs 의존으로 리팩터
GeomKernel trait에 4개 메서드 추가 (상부→하부 문서화 주석)

cargo test 57개 전부 통과

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 19:27:57 +09:00

82 lines
2.6 KiB
Rust

//! Abutment geometry — PureRustKernel.
//! Breast wall + footing + wing walls (simplified rectangular shapes).
use cimery_ir::AbutmentIR;
use crate::{KernelError, Mesh, sweep};
pub fn build_abutment_mesh(ir: &AbutmentIR) -> Result<Mesh, KernelError> {
if ir.breast_wall_height <= 0.0 {
return Err(KernelError::InvalidInput(
format!("breast_wall_height must be > 0, got {}", ir.breast_wall_height),
));
}
let bw_h = ir.breast_wall_height as f32;
let bw_t = ir.breast_wall_thickness as f32;
let bw_w = ir.breast_wall_width as f32;
let ft_l = ir.footing_length as f32;
let ft_w = ir.footing_width as f32;
let ft_t = ir.footing_thickness as f32;
let mut parts: Vec<Mesh> = Vec::new();
// Breast wall: along transverse (Z), thickness along span (X), height (Y)
parts.push(sweep::centred_box(0.0, bw_h * 0.5, bw_t * 0.5, bw_h * 0.5, bw_w));
// Footing: below grade, spans along X (span direction)
{
let profile = vec![
[-ft_l * 0.5, -ft_t],
[ ft_l * 0.5, -ft_t],
[ ft_l * 0.5, 0.0 ],
[-ft_l * 0.5, 0.0 ],
];
parts.push(sweep::sweep_profile_flat(&profile, ft_w));
}
// Wing walls (left and right)
for side in [&ir.wing_wall_left, &ir.wing_wall_right] {
let wl = side.length as f32;
let wh = side.height as f32;
let wt = side.thickness as f32;
// Simplified: vertical rectangle, oriented in XY, length along Z
parts.push(sweep::centred_box(0.0, wh * 0.5, wt * 0.5, wh * 0.5, wl));
}
Ok(sweep::merge_meshes(parts))
}
#[cfg(test)]
mod tests {
use super::*;
use cimery_core::{AbutmentType, MaterialGrade};
use cimery_ir::{AbutmentIR, FeatureId, WingWallIR};
fn test_ir() -> AbutmentIR {
let wing = WingWallIR { length: 4000.0, height: 3000.0, thickness: 500.0 };
AbutmentIR {
id: FeatureId::new(),
station: 0.0, skew_angle: 0.0,
abutment_type: AbutmentType::ReverseT,
breast_wall_height: 5000.0, breast_wall_thickness: 800.0,
breast_wall_width: 12000.0,
footing_length: 4000.0, footing_width: 13000.0, footing_thickness: 1000.0,
wing_wall_left: wing.clone(), wing_wall_right: wing,
material: MaterialGrade::C40,
}
}
#[test]
fn produces_mesh() {
let mesh = build_abutment_mesh(&test_ir()).unwrap();
assert!(mesh.triangle_count() > 0);
}
#[test]
fn zero_height_fails() {
let mut ir = test_ir();
ir.breast_wall_height = 0.0;
assert!(build_abutment_mesh(&ir).is_err());
}
}