상부 구조물: - 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>
82 lines
2.6 KiB
Rust
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());
|
|
}
|
|
}
|