cimery Sprint 1 — Rust 워크스페이스 + 전 계층 파이프라인
8개 크레이트 구현, cargo test 32개 전부 통과: - core: Mm/M 단위 newtype, UnitExt 리터럴, FeatureError - ir: GirderIR + 전 단면 파라미터(PSC-I/U/SteelBox/PlateI) serde JSON - dsl: Girder builder + 검증 (경간 범위·count·spacing) - kernel: GeomKernel trait + StubKernel (box mesh, AABB) - incremental: dirty-tracking IncrementalDb (salsa 업그레이드 경로 주석) - evaluator: 상태 없는 IR→kernel 브리지 - usd: USDA 1.0 텍스트 익스포트 (CimeryBridgeAPI·GirderAPI schema) - viewer: wgpu 22 + winit 0.30 컬러 삼각형 (Sprint 1 proof-of-concept) Sprint 2 다음 단계: - opencascade-rs로 StubKernel 교체 (실제 PSC-I sweep) - viewer에서 Girder Mesh 렌더 + 카메라 orbit Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
10
cimery/crates/ir/Cargo.toml
Normal file
10
cimery/crates/ir/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "cimery-ir"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
cimery-core = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
185
cimery/crates/ir/src/lib.rs
Normal file
185
cimery/crates/ir/src/lib.rs
Normal file
@@ -0,0 +1,185 @@
|
||||
//! cimery-ir — deterministic intermediate representation.
|
||||
//!
|
||||
//! Rules:
|
||||
//! - Structural values stored as raw `f64` in **mm**.
|
||||
//! - Alignment values stored as raw `f64` in **m**.
|
||||
//! - Fully serializable: JSON ↔ IR round-trip is lossless.
|
||||
//! - Serde field ordering is deterministic (struct field declaration order).
|
||||
|
||||
use cimery_core::{MaterialGrade, SectionType};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
// ─── Feature identity ─────────────────────────────────────────────────────────
|
||||
|
||||
/// Stable identifier for a Feature instance across changes.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct FeatureId(pub Uuid);
|
||||
|
||||
impl FeatureId {
|
||||
pub fn new() -> Self { Self(Uuid::new_v4()) }
|
||||
}
|
||||
|
||||
impl Default for FeatureId {
|
||||
fn default() -> Self { Self::new() }
|
||||
}
|
||||
|
||||
impl std::fmt::Display for FeatureId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Girder IR ────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Fully-resolved Girder specification ready for geometry kernel invocation.
|
||||
///
|
||||
/// All values are raw primitives — no unit types here (kernel doesn't need them).
|
||||
/// Convention: linear = metres, structural = millimetres.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct GirderIR {
|
||||
pub id: FeatureId,
|
||||
/// Station along alignment [m].
|
||||
pub station_start: f64,
|
||||
/// Station along alignment [m].
|
||||
pub station_end: f64,
|
||||
/// Lateral offset from alignment centreline [mm]. Positive = left.
|
||||
pub offset_from_alignment: f64,
|
||||
pub section_type: SectionType,
|
||||
pub section: SectionParams,
|
||||
/// Number of girders placed side by side.
|
||||
pub count: u32,
|
||||
/// Centre-to-centre spacing between girders [mm].
|
||||
pub spacing: f64,
|
||||
pub material: MaterialGrade,
|
||||
}
|
||||
|
||||
impl GirderIR {
|
||||
/// Span length in metres.
|
||||
pub fn span_m(&self) -> f64 { self.station_end - self.station_start }
|
||||
/// Span length in millimetres.
|
||||
pub fn span_mm(&self) -> f64 { self.span_m() * 1_000.0 }
|
||||
}
|
||||
|
||||
// ─── Section params ───────────────────────────────────────────────────────────
|
||||
|
||||
/// Cross-section geometry. Tag is the section type name.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum SectionParams {
|
||||
PscI(PscISectionParams),
|
||||
PscU(PscUSectionParams),
|
||||
SteelBox(SteelBoxParams),
|
||||
SteelPlateI(SteelPlateIParams),
|
||||
}
|
||||
|
||||
/// PSC I-girder section dimensions [all mm].
|
||||
///
|
||||
/// #[param(unit="mm", range=1200..=3000, default=1800)] total_height
|
||||
/// #[param(unit="mm", range=400..=800, default=600)] top_flange_width
|
||||
/// #[param(unit="mm", range=100..=300, default=150)] top_flange_thickness
|
||||
/// #[param(unit="mm", range=400..=900, default=700)] bottom_flange_width
|
||||
/// #[param(unit="mm", range=120..=300, default=180)] bottom_flange_thickness
|
||||
/// #[param(unit="mm", range=150..=350, default=200)] web_thickness
|
||||
/// #[param(unit="mm", range=0..=100, default=50)] haunch
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PscISectionParams {
|
||||
pub total_height: f64,
|
||||
pub top_flange_width: f64,
|
||||
pub top_flange_thickness: f64,
|
||||
pub bottom_flange_width: f64,
|
||||
pub bottom_flange_thickness:f64,
|
||||
pub web_thickness: f64,
|
||||
pub haunch: f64,
|
||||
}
|
||||
|
||||
impl PscISectionParams {
|
||||
/// Korean KDS standard I-girder, 40 m span.
|
||||
pub fn kds_standard() -> Self {
|
||||
Self {
|
||||
total_height: 1800.0,
|
||||
top_flange_width: 600.0,
|
||||
top_flange_thickness: 150.0,
|
||||
bottom_flange_width: 700.0,
|
||||
bottom_flange_thickness: 180.0,
|
||||
web_thickness: 200.0,
|
||||
haunch: 50.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// PSC U-girder section [all mm].
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PscUSectionParams {
|
||||
pub total_height: f64,
|
||||
pub top_width: f64,
|
||||
pub bottom_width: f64,
|
||||
pub web_thickness: f64,
|
||||
pub flange_thickness: f64,
|
||||
}
|
||||
|
||||
/// Steel box girder section [all mm].
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SteelBoxParams {
|
||||
pub total_height: f64,
|
||||
pub top_width: f64,
|
||||
pub bottom_width: f64,
|
||||
pub web_thickness: f64,
|
||||
pub top_flange_thickness: f64,
|
||||
pub bottom_flange_thickness:f64,
|
||||
}
|
||||
|
||||
/// Steel plate I-girder section [all mm].
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SteelPlateIParams {
|
||||
pub total_height: f64,
|
||||
pub flange_width: f64,
|
||||
pub flange_thickness: f64,
|
||||
pub web_thickness: f64,
|
||||
}
|
||||
|
||||
// ─── Tests ────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use cimery_core::{MaterialGrade, SectionType};
|
||||
|
||||
pub fn sample_girder() -> GirderIR {
|
||||
GirderIR {
|
||||
id: FeatureId::new(),
|
||||
station_start: 100.0,
|
||||
station_end: 140.0,
|
||||
offset_from_alignment: 0.0,
|
||||
section_type: SectionType::PscI,
|
||||
section: SectionParams::PscI(PscISectionParams::kds_standard()),
|
||||
count: 5,
|
||||
spacing: 2500.0,
|
||||
material: MaterialGrade::C50,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn span_lengths() {
|
||||
let g = sample_girder();
|
||||
assert!((g.span_m() - 40.0).abs() < f64::EPSILON);
|
||||
assert!((g.span_mm() - 40_000.0).abs() < f64::EPSILON);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_roundtrip() {
|
||||
let g = sample_girder();
|
||||
let json = serde_json::to_string_pretty(&g).unwrap();
|
||||
let g2: GirderIR = serde_json::from_str(&json).unwrap();
|
||||
assert!((g.span_m() - g2.span_m()).abs() < f64::EPSILON);
|
||||
assert_eq!(g.count, g2.count);
|
||||
assert_eq!(g.section_type, g2.section_type);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feature_id_is_unique() {
|
||||
let a = FeatureId::new();
|
||||
let b = FeatureId::new();
|
||||
assert_ne!(a, b);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user