Files
ParaWiki/cimery/crates/macros/src/lib.rs
minsung 0e4701de79 Sprint 36~39 — IFC Alignment/Camber + proc-macro 스캐폴딩 + 변단면 거더
## Sprint 36: IfcAlignment (IFC Phase 3b)
- write_straight_alignment(): 직선 horizontal(.LINE.) + 평지
  vertical(.CONSTANTGRADIENT.) 세그먼트 + IfcRelNests 계층.
- IfcAlignmentSegment × IfcAlignmentHorizontalSegment × IfcAlignmentVerticalSegment.
- Site aggregate 에 Bridge 와 Alignment 동시 포함.

## Sprint 37: IFC 거더 Camber 반영
- BridgeExportParams.camber_mid_mm 추가.
- camber > 0 일 때: 거더 1 개를 CAMBER_SEGMENTS(10) 세그먼트로 분할, 각 세그먼트
  Y 에 포물선 값 적용 → 곡선 거더 근사. Pset 는 첫 세그먼트에 한 번만 부착.
- viewer scene_params_to_ifc() 에서 camber_mid_mm 매핑.

## Sprint 38: cimery-macros 크레이트 (proc-macro 스캐폴딩)
- 신규 크레이트, proc-macro = true, deps: syn/quote/proc-macro2.
- #[derive(ParamSummary)] 구현:
  · 구조체 named field 의 PARAM_COUNT (usize) + PARAM_NAMES (&[&str]) 생성.
  · 선언 순서 보존, 빈 구조체 지원, tuple/enum 은 컴파일 에러.
- 테스트 3개 (tests/derive_test.rs).
- ADR-002 D 로드맵: #[param(unit, range, default)] 전면 attribute 는 후속.

## Sprint 39: 변단면 거더 (Variable Depth)
- SceneParams.variable_depth_mm (0~800mm) 추가.
- apply_variable_depth(mesh, z0, z1, max, girder_h):
  · lift(u) = 4·max·u·(span-u)/span²  (중앙에서 최대)
  · y_new = y + lift(u)·(1 - y/h)
  · 상면 y=h 는 고정, 소핏 y=0 을 최대 lift 만큼 올림. 연속교 중앙부
    web 축소 관례와 정합. camber 와 독립 조합 가능.
- build_bridge_scene / build_selectable_scene 거더 생성 루프에 각각 적용
  (거더 local 좌표계에서 먼저 → translate → camber 순).
- UI "변단면 (mm)" 슬라이더 (선형·기하 섹션).
- ProjectFile variable_depth_mm 필드 (default 0).

모든 테스트 통과: kernel 18 + ifc 20 + macros 3.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 19:48:07 +09:00

83 lines
2.7 KiB
Rust

//! cimery-macros — Feature DSL proc-macro 인프라 (Sprint 38).
//!
//! # 현재 범위
//! - `#[derive(ParamSummary)]` — 구조체의 필드 이름·개수·타입을 컴파일 타임에
//! 요약 제공. UI 자동생성·파라미터 인스펙터의 기반.
//!
//! # ADR-002 D 로드맵 (미래)
//! - `#[param(unit="mm", range=1000..=3000, default=1800)]` 속성 attribute
//! - `#[derive(Feature)]` — validation / builder / IFC 매핑 자동 생성
//! - 선언적 `feature!` 매크로 — DSL 설탕
//!
//! # 현 시점 사용 예
//! ```ignore
//! use cimery_macros::ParamSummary;
//!
//! #[derive(ParamSummary)]
//! pub struct GirderParams {
//! pub total_height: f64,
//! pub top_flange_width: f64,
//! }
//!
//! fn main() {
//! assert_eq!(GirderParams::PARAM_COUNT, 2);
//! assert_eq!(GirderParams::PARAM_NAMES, &["total_height", "top_flange_width"]);
//! }
//! ```
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Fields};
/// `#[derive(ParamSummary)]` — named field 구조체의 필드 이름·개수 상수 생성.
///
/// - `Self::PARAM_COUNT: usize`
/// - `Self::PARAM_NAMES: &'static [&'static str]`
///
/// # 제약
/// - named field 구조체만 지원 (tuple/unit/enum 은 컴파일 에러).
/// - 모든 필드가 노출됨 (private 필드 포함). 필터링은 Phase 2.
#[proc_macro_derive(ParamSummary)]
pub fn derive_param_summary(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let fields = match &input.data {
Data::Struct(s) => match &s.fields {
Fields::Named(named) => &named.named,
_ => {
return syn::Error::new_spanned(
name,
"ParamSummary only supports structs with named fields",
)
.to_compile_error()
.into();
}
},
_ => {
return syn::Error::new_spanned(
name,
"ParamSummary only supports structs, not enums or unions",
)
.to_compile_error()
.into();
}
};
let names: Vec<String> = fields
.iter()
.filter_map(|f| f.ident.as_ref().map(|i| i.to_string()))
.collect();
let count = names.len();
let expanded = quote! {
impl #name {
/// `#[derive(ParamSummary)]` 로 생성된 필드 개수.
pub const PARAM_COUNT: usize = #count;
/// `#[derive(ParamSummary)]` 로 생성된 필드 이름 목록 (선언 순서).
pub const PARAM_NAMES: &'static [&'static str] = &[ #( #names ),* ];
}
};
expanded.into()
}