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>
This commit is contained in:
81
cimery/crates/kernel/src/abutment.rs
Normal file
81
cimery/crates/kernel/src/abutment.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
//! 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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user