//! Pier (교각) Feature builder. use cimery_core::{ ColumnShape, FeatureError, MaterialGrade, Mm, M, PierType, UnitExt, }; use cimery_ir::{CapBeamIR, FeatureId, PierIR}; #[derive(Debug)] pub struct Pier { pub ir: PierIR, } impl Pier { pub fn builder() -> PierBuilder { PierBuilder::default() } } /// Builder for a Pier Feature. #[derive(Default)] pub struct PierBuilder { station: Option, skew_angle: Option, pier_type: Option, column_shape: Option, column_count: Option, column_spacing: Option, column_diameter: Option, column_depth: Option, column_height: Option, cap_length: Option, cap_width: Option, cap_depth: Option, material: Option, } impl PierBuilder { /// #[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=PierType, default=SingleColumn)] pub fn pier_type(mut self, t: PierType) -> Self { self.pier_type = Some(t); self } /// #[param(enum=ColumnShape, default=Circular)] pub fn column_shape(mut self, s: ColumnShape) -> Self { self.column_shape = Some(s); self } /// #[param(unit="count", range=1..=6, default=1)] pub fn column_count(mut self, n: u32) -> Self { self.column_count = Some(n); self } /// #[param(unit="mm", range=500.0..=5000.0, default=3000.0)] Column c/c spacing pub fn column_spacing(mut self, v: Mm) -> Self { self.column_spacing = Some(v); self } /// #[param(unit="mm", range=800.0..=5000.0, default=1500.0)] Diameter or width pub fn column_diameter(mut self, v: Mm) -> Self { self.column_diameter = Some(v); self } /// #[param(unit="mm", range=800.0..=5000.0, default=1500.0)] Depth (rectangular only) pub fn column_depth(mut self, v: Mm) -> Self { self.column_depth = Some(v); self } /// #[param(unit="mm", range=3000.0..=40_000.0)] Foundation top to cap soffit pub fn column_height(mut self, v: Mm) -> Self { self.column_height = Some(v); self } /// #[param(unit="mm")] Cap beam total transverse length pub fn cap_length(mut self, v: Mm) -> Self { self.cap_length = Some(v); self } /// #[param(unit="mm", range=800.0..=3000.0, default=1200.0)] Along span pub fn cap_width(mut self, v: Mm) -> Self { self.cap_width = Some(v); self } /// #[param(unit="mm", range=800.0..=3000.0, default=1500.0)] pub fn cap_depth(mut self, v: Mm) -> Self { self.cap_depth = Some(v); self } /// #[param(enum=MaterialGrade, default=C40)] 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("pier.station"))?.value(); let pier_type = self.pier_type.unwrap_or(PierType::SingleColumn); let col_shape = self.column_shape.unwrap_or(ColumnShape::Circular); let col_count = self.column_count.unwrap_or(1); let col_dia = self.column_diameter .ok_or_else(|| FeatureError::missing("pier.column_diameter"))?.value(); let col_h = self.column_height .ok_or_else(|| FeatureError::missing("pier.column_height"))?.value(); let cap_w = self.cap_width.unwrap_or(1200.0.mm()).value(); let cap_d = self.cap_depth.unwrap_or(1500.0.mm()).value(); let cap_l = self.cap_length.unwrap_or_else(|| { let span = col_count as f64 * col_dia + 2.0 * 1000.0; Mm(span) }).value(); if col_dia < 500.0 { return Err(FeatureError::validation("pier.column_diameter", format!("minimum 500 mm, got {col_dia:.0} mm"))); } if col_h < 2000.0 { return Err(FeatureError::validation("pier.column_height", format!("minimum 2000 mm, got {col_h:.0} mm"))); } if col_count > 1 && self.column_spacing.is_none() { return Err(FeatureError::missing("pier.column_spacing")); } Ok(Pier { ir: PierIR { id: FeatureId::new(), station, skew_angle: self.skew_angle.unwrap_or(0.0), pier_type, column_shape: col_shape, column_count: col_count, column_spacing: self.column_spacing.unwrap_or(0.0.mm()).value(), column_diameter: col_dia, column_depth: self.column_depth.unwrap_or(Mm(col_dia)).value(), column_height: col_h, cap_beam: CapBeamIR { length: cap_l, width: cap_w, depth: cap_d, cantilever_left: 1000.0, cantilever_right: 1000.0, }, material: self.material.unwrap_or(MaterialGrade::C40), }}) } } #[cfg(test)] mod tests { use super::*; use cimery_core::{ColumnShape, PierType, UnitExt}; #[test] fn single_column_pier() { let p = Pier::builder() .station(100.0.m()) .pier_type(PierType::SingleColumn) .column_shape(ColumnShape::Circular) .column_diameter(1500.0.mm()) .column_height(8000.0.mm()) .build() .unwrap(); assert_eq!(p.ir.column_count, 1); assert!((p.ir.column_height - 8000.0).abs() < f64::EPSILON); } #[test] fn multi_column_needs_spacing() { let e = Pier::builder() .station(100.0.m()) .column_count(3) .column_diameter(1200.0.mm()) .column_height(8000.0.mm()) .build().unwrap_err(); assert!(matches!(e, FeatureError::MissingField { .. })); } }