Sprint 11/12/13 — 선택하이라이트 + 저장/로드 + Tauri앱 스켈레톤

Sprint 11 (Selection highlight + 단면 UI):
- FeatureDraw: CPU 정점 저장, update_highlight() — 선택 시 yellow-orange
- 렌더 루프: background mesh(지면+선형) + 피처별 독립 draw call 분리
- SceneParams: GirderSectionType (PscI / SteelBox), show_alignment
- egui: 단면형식 ComboBox, 선형표시 checkbox
- SteelBox 단면 지원 (span 비례 자동 치수)
- build_background_scene(): 지면+선형만 반환

Sprint 12 (Project save/load):
- project_file.rs: ProjectFile struct, to_params/from_params, save/load JSON
- egui: 💾 저장 / 📂 불러오기 버튼
- projects/ 폴더 자동 생성

Sprint 13 (Tauri app skeleton):
- crates/app/: Cargo.toml + main.rs (Tauri v2 통합 scaffold)
- 기동 시 PureRustKernel 동작 검증
- Tauri setup checklist 주석으로 문서화
- workspace에 cimery-app 추가

cargo check --workspace 통과

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
minsung
2026-04-14 22:59:11 +09:00
parent 5d89db5117
commit 81349c97d2
7 changed files with 339 additions and 32 deletions

View File

@@ -0,0 +1,79 @@
//! cimery project file — JSON save/load of SceneParams.
//!
//! Format: `cimery/projects/*.cimery.json`
//! Sprint 12: SceneParams only. Sprint 13+: Feature IRs + Alignment.
use serde::{Deserialize, Serialize};
use super::bridge_scene::{GirderSectionType, SceneParams};
// ─── Serialisable form of SceneParams ────────────────────────────────────────
#[derive(Serialize, Deserialize)]
struct SectionTypeStr(String);
#[derive(Serialize, Deserialize)]
pub struct ProjectFile {
pub version: u32,
pub name: String,
pub span_m: f64,
pub girder_count: usize,
pub girder_spacing: f32,
pub girder_height: f32,
pub slab_thickness: f32,
pub section_type: String, // "psc_i" | "steel_box"
pub show_alignment: bool,
}
impl ProjectFile {
pub fn from_params(name: &str, p: &SceneParams) -> Self {
Self {
version: 1,
name: name.to_owned(),
span_m: p.span_m,
girder_count: p.girder_count,
girder_spacing: p.girder_spacing,
girder_height: p.girder_height,
slab_thickness: p.slab_thickness,
section_type: match p.section_type {
GirderSectionType::PscI => "psc_i".into(),
GirderSectionType::SteelBox => "steel_box".into(),
},
show_alignment: p.show_alignment,
}
}
pub fn to_params(&self) -> SceneParams {
SceneParams {
span_m: self.span_m,
girder_count: self.girder_count,
girder_spacing: self.girder_spacing,
girder_height: self.girder_height,
slab_thickness: self.slab_thickness,
section_type: match self.section_type.as_str() {
"steel_box" => GirderSectionType::SteelBox,
_ => GirderSectionType::PscI,
},
show_alignment: self.show_alignment,
}
}
pub fn save(&self, path: &std::path::Path) -> std::io::Result<()> {
let json = serde_json::to_string_pretty(self)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
std::fs::write(path, json)
}
pub fn load(path: &std::path::Path) -> std::io::Result<Self> {
let json = std::fs::read_to_string(path)?;
serde_json::from_str(&json)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
}
}
/// Default project save path (relative to cimery workspace root).
pub fn default_save_path(name: &str) -> std::path::PathBuf {
let mut p = std::path::PathBuf::from("projects");
std::fs::create_dir_all(&p).ok();
p.push(format!("{}.cimery.json", name));
p
}