//! Bearing (받침) Feature builder. //! //! Bearing is a catalogue-based Feature: select a type and verify capacity //! rather than computing section geometry from scratch. use cimery_core::{BearingType, FeatureError, Mm, M}; use cimery_ir::{BearingIR, FeatureId}; #[derive(Debug)] pub struct Bearing { pub ir: BearingIR, } impl Bearing { pub fn builder() -> BearingBuilder { BearingBuilder::default() } /// KDS 24.14.3 elastomeric bearing defaults for PSC-I girder. pub fn elastomeric_kds() -> Result { Self::builder() .station(M(0.0)) .bearing_type(BearingType::Elastomeric) .plan_length(Mm(350.0)) .plan_width(Mm(450.0)) .total_height(Mm(60.0)) .capacity_vertical(1500.0) .build() } } /// Builder for a Bearing Feature. /// /// Bearings are typically selected from a catalogue. /// Provide dimensions and capacity from the manufacturer/catalogue data. #[derive(Default)] pub struct BearingBuilder { station: Option, bearing_type: Option, plan_length: Option, plan_width: Option, total_height: Option, capacity_vertical: Option, } impl BearingBuilder { /// #[param(unit="m")] Station along alignment pub fn station(mut self, v: M) -> Self { self.station = Some(v); self } /// #[param(enum=BearingType, default=Elastomeric)] pub fn bearing_type(mut self, t: BearingType) -> Self { self.bearing_type = Some(t); self } /// #[param(unit="mm", range=150.0..=800.0, default=350.0)] Along span pub fn plan_length(mut self, v: Mm) -> Self { self.plan_length = Some(v); self } /// #[param(unit="mm", range=150.0..=800.0, default=450.0)] Transverse pub fn plan_width(mut self, v: Mm) -> Self { self.plan_width = Some(v); self } /// #[param(unit="mm", range=30.0..=300.0, default=60.0)] pub fn total_height(mut self, v: Mm) -> Self { self.total_height = Some(v); self } /// #[param(unit="kN", range=100.0..=50_000.0)] pub fn capacity_vertical(mut self, v: f64) -> Self { self.capacity_vertical = Some(v); self } pub fn build(self) -> Result { let station = self.station.ok_or_else(|| FeatureError::missing("bearing.station"))?.value(); let bearing_type = self.bearing_type.ok_or_else(|| FeatureError::missing("bearing.bearing_type"))?; let plan_length = self.plan_length.ok_or_else(|| FeatureError::missing("bearing.plan_length"))?.value(); let plan_width = self.plan_width.ok_or_else(|| FeatureError::missing("bearing.plan_width"))?.value(); let total_height = self.total_height.ok_or_else(|| FeatureError::missing("bearing.total_height"))?.value(); let cap_v = self.capacity_vertical.ok_or_else(|| FeatureError::missing("bearing.capacity_vertical"))?; if plan_length < 100.0 || plan_width < 100.0 { return Err(FeatureError::validation("bearing.plan", "minimum plan dimension 100 mm")); } if cap_v <= 0.0 { return Err(FeatureError::validation("bearing.capacity_vertical", "must be > 0 kN")); } Ok(Bearing { ir: BearingIR { id: FeatureId::new(), station, bearing_type, plan_length, plan_width, total_height, capacity_vertical: cap_v, }}) } } #[cfg(test)] mod tests { use super::*; use cimery_core::BearingType; #[test] fn build_elastomeric() { let b = Bearing::builder() .station(M(100.0)) .bearing_type(BearingType::Elastomeric) .plan_length(Mm(350.0)) .plan_width(Mm(450.0)) .total_height(Mm(60.0)) .capacity_vertical(1500.0) .build() .unwrap(); assert_eq!(b.ir.bearing_type, BearingType::Elastomeric); } #[test] fn kds_default() { assert!(Bearing::elastomeric_kds().is_ok()); } #[test] fn zero_capacity_fails() { let e = Bearing::builder() .station(M(0.0)) .bearing_type(BearingType::Pot) .plan_length(Mm(400.0)).plan_width(Mm(400.0)).total_height(Mm(100.0)) .capacity_vertical(0.0) .build().unwrap_err(); assert!(matches!(e, FeatureError::Validation { .. })); } }