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:
minsung
2026-04-14 17:46:14 +09:00
parent 919855c1e8
commit 62ddf3aea6
24 changed files with 1779 additions and 3 deletions

View 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
View 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);
}
}