//! Abutment (교대) Feature builder. use cimery_core::{AbutmentType, FeatureError, MaterialGrade, Mm, M, UnitExt}; use cimery_ir::{AbutmentIR, FeatureId, WingWallIR}; #[derive(Debug)] pub struct Abutment { pub ir: AbutmentIR, } impl Abutment { pub fn builder() -> AbutmentBuilder { AbutmentBuilder::default() } } /// Builder for an Abutment Feature. #[derive(Default)] pub struct AbutmentBuilder { station: Option, skew_angle: Option, abutment_type: Option, breast_wall_height: Option, breast_wall_thickness: Option, breast_wall_width: Option, footing_length: Option, footing_width: Option, footing_thickness: Option, wing_length: Option, // simplified: same left & right material: Option, } impl AbutmentBuilder { /// #[param(unit="m")] Station along alignment pub fn station(mut self, v: M) -> Self { self.station = Some(v); self } /// #[param(unit="deg", range=-45.0..=45.0, default=0.0)] pub fn skew_angle(mut self, v: f64) -> Self { self.skew_angle = Some(v); self } /// #[param(enum=AbutmentType, default=ReverseT)] pub fn abutment_type(mut self, t: AbutmentType) -> Self { self.abutment_type = Some(t); self } /// #[param(unit="mm", range=2000.0..=12_000.0)] Breast wall height pub fn breast_wall_height(mut self, v: Mm) -> Self { self.breast_wall_height = Some(v); self } /// #[param(unit="mm", range=500.0..=3000.0, default=800.0)] pub fn breast_wall_thickness(mut self, v: Mm) -> Self { self.breast_wall_thickness = Some(v); self } /// #[param(unit="mm")] Breast wall transverse width pub fn breast_wall_width(mut self, v: Mm) -> Self { self.breast_wall_width = Some(v); self } /// #[param(unit="mm", range=2000.0..=8000.0, default=4000.0)] Footing along span pub fn footing_length(mut self, v: Mm) -> Self { self.footing_length = Some(v); self } /// #[param(unit="mm")] Footing transverse width pub fn footing_width(mut self, v: Mm) -> Self { self.footing_width = Some(v); self } /// #[param(unit="mm", range=500.0..=2000.0, default=1000.0)] pub fn footing_thickness(mut self, v: Mm) -> Self { self.footing_thickness = Some(v); self } /// #[param(unit="mm")] Wing wall length (both sides equal) pub fn wing_length(mut self, v: Mm) -> Self { self.wing_length = Some(v); self } /// #[param(enum=MaterialGrade, default=C30)] pub fn material(mut self, m: MaterialGrade) -> Self { self.material = Some(m); self } pub fn build(self) -> Result { let station = self.station .ok_or_else(|| FeatureError::missing("abutment.station"))?.value(); let bw_h = self.breast_wall_height .ok_or_else(|| FeatureError::missing("abutment.breast_wall_height"))?.value(); let bw_w = self.breast_wall_width .ok_or_else(|| FeatureError::missing("abutment.breast_wall_width"))?.value(); let bw_t = self.breast_wall_thickness.unwrap_or(800.0.mm()).value(); let foot_l = self.footing_length.unwrap_or(4000.0.mm()).value(); let foot_w = self.footing_width.unwrap_or_else(|| Mm(bw_w + 1000.0)).value(); let foot_t = self.footing_thickness.unwrap_or(1000.0.mm()).value(); let wing_l = self.wing_length.unwrap_or_else(|| Mm(bw_h * 0.8)).value(); let wing_h = bw_h * 0.5; // simplified: half the breast wall height let wing_t = bw_t; if bw_h < 1000.0 { return Err(FeatureError::validation("abutment.breast_wall_height", format!("minimum 1000 mm, got {bw_h:.0} mm"))); } let wing = WingWallIR { length: wing_l, height: wing_h, thickness: wing_t }; Ok(Abutment { ir: AbutmentIR { id: FeatureId::new(), station, skew_angle: self.skew_angle.unwrap_or(0.0), abutment_type: self.abutment_type.unwrap_or(AbutmentType::ReverseT), breast_wall_height: bw_h, breast_wall_thickness: bw_t, breast_wall_width: bw_w, footing_length: foot_l, footing_width: foot_w, footing_thickness: foot_t, wing_wall_left: wing.clone(), wing_wall_right: wing, material: self.material.unwrap_or(MaterialGrade::C40), }}) } } #[cfg(test)] mod tests { use super::*; use cimery_core::{AbutmentType, UnitExt}; #[test] fn build_reverse_t() { let a = Abutment::builder() .station(100.0.m()) .abutment_type(AbutmentType::ReverseT) .breast_wall_height(5000.0.mm()) .breast_wall_width(12000.0.mm()) .build() .unwrap(); assert_eq!(a.ir.abutment_type, AbutmentType::ReverseT); assert!((a.ir.breast_wall_height - 5000.0).abs() < f64::EPSILON); } #[test] fn too_short_breast_wall_fails() { let e = Abutment::builder() .station(0.0.m()) .breast_wall_height(500.0.mm()) // < 1000 .breast_wall_width(10000.0.mm()) .build().unwrap_err(); assert!(matches!(e, FeatureError::Validation { .. })); } }