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:
minsung
2026-04-14 19:27:57 +09:00
parent 40857f39c5
commit bdacea5253
16 changed files with 1113 additions and 85 deletions

View File

@@ -0,0 +1,119 @@
//! 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, FeatureError> {
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<M>,
bearing_type: Option<BearingType>,
plan_length: Option<Mm>,
plan_width: Option<Mm>,
total_height: Option<Mm>,
capacity_vertical: Option<f64>,
}
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<Bearing, FeatureError> {
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 { .. }));
}
}