Files
ParaWiki/cimery/crates/kernel/src/occt.rs
minsung 3645b85828 받침 중심 정렬 + 부재별 색상 구분
bearing.rs: X 중심, Y 하방향 (-h to 0) 기하학 수정
bridge_scene.rs: 받침 X 오프셋 제거 (girder 정렬)
Mesh: colors 필드 추가 + recolor() 메서드
sweep.rs / occt.rs: 기본 콘크리트 색 자동 채움
bridge_scene: 부재별 색상 (거더/슬래브/받침/교대)
shader.wgsl: base_color 입력 → 조명 계산에 적용

선택(Selection) 기능은 계획대로 별도 Sprint에 구현.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 20:26:40 +09:00

182 lines
6.8 KiB
Rust

//! OcctKernel — geometry backend using OpenCASCADE Technology (OCCT).
//!
//! Only compiled with `--features occt` (ADR-001, ADR-002 optional feature).
//!
//! Produces proper B-rep solids:
//! - Accurate PSC-I cross-section with haunches
//! - Higher-quality tessellation (BRepMesh_IncrementalMesh)
//! - Foundation for fillets, boolean ops in later sprints
#[cfg(feature = "occt")]
mod inner {
use glam::DVec3;
use opencascade::{
mesh::Mesher,
workplane::Workplane,
};
use cimery_ir::{
AbutmentIR, BearingIR, DeckSlabIR, GirderIR, PierIR,
PscISectionParams, SectionParams,
};
use crate::{KernelError, Mesh};
// ── Girder (PSC-I B-rep sweep) ──────────────────────────────────────────────
pub fn girder_mesh(ir: &GirderIR) -> Result<Mesh, KernelError> {
if ir.span_m() <= 0.0 {
return Err(KernelError::InvalidInput(
format!("span must be positive, got {} m", ir.span_m()),
));
}
match &ir.section {
SectionParams::PscI(p) => psc_i_extrude(p, ir.span_mm()),
// Other sections: fall back to PureRustKernel until implemented
_ => {
log::warn!(
"OcctKernel: section {:?} not yet implemented, using PureRust fallback",
ir.section_type
);
crate::psc_i::build_psc_i_mesh(
match &ir.section {
SectionParams::PscI(p) => p,
_ => unreachable!(),
},
ir.span_mm(),
)
}
}
}
fn psc_i_extrude(p: &PscISectionParams, span_mm: f64) -> Result<Mesh, KernelError> {
let hw = p.top_flange_width / 2.0;
let hbw = p.bottom_flange_width / 2.0;
let hwb = p.web_thickness / 2.0;
let h = p.total_height;
let tft = p.top_flange_thickness;
let bft = p.bottom_flange_thickness;
let hch = p.haunch;
// PSC-I 14-vertex profile in XY plane (mm), CCW
let wire = Workplane::xy()
.sketch()
.move_to(-hbw, 0.0 )
.line_to( hbw, 0.0 )
.line_to( hbw, bft )
.line_to( hwb, bft )
.line_to( hwb, h - tft - hch )
.line_to( hwb + hch, h - tft )
.line_to( hw, h - tft )
.line_to( hw, h )
.line_to(-hw, h )
.line_to(-hw, h - tft )
.line_to(-(hwb + hch), h - tft )
.line_to(-hwb, h - tft - hch )
.line_to(-hwb, bft )
.line_to(-hbw, bft )
.close()
.to_face();
// Extrude along Z axis by span_mm
let solid = wire.extrude(DVec3::new(0.0, 0.0, span_mm));
// Tessellate with engineering-grade tolerance (1 mm)
let shape = solid.into();
let occt_mesh = Mesher::try_new(&shape, 1.0)
.map_err(|e| KernelError::Computation(format!("OCCT mesher init: {e}")))?
.mesh()
.map_err(|e| KernelError::Computation(format!("OCCT tessellation: {e}")))?;
occt_mesh_to_cimery(occt_mesh)
}
// ── Other features (use PureRustKernel geometry for now) ─────────────────────
pub fn deck_slab_mesh(ir: &DeckSlabIR) -> Result<Mesh, KernelError> {
crate::deck_slab::build_deck_slab_mesh(ir)
}
pub fn bearing_mesh(ir: &BearingIR) -> Result<Mesh, KernelError> {
crate::bearing::build_bearing_mesh(ir)
}
pub fn pier_mesh(ir: &PierIR) -> Result<Mesh, KernelError> {
crate::pier::build_pier_mesh(ir)
}
pub fn abutment_mesh(ir: &AbutmentIR) -> Result<Mesh, KernelError> {
crate::abutment::build_abutment_mesh(ir)
}
// ── Conversion ────────────────────────────────────────────────────────────────
fn occt_mesh_to_cimery(m: opencascade::mesh::Mesh) -> Result<Mesh, KernelError> {
if m.vertices.is_empty() {
return Err(KernelError::Computation(
"OCCT tessellation produced no vertices".into(),
));
}
let vertices: Vec<[f32; 3]> = m.vertices.iter()
.map(|v| [v.x as f32, v.y as f32, v.z as f32])
.collect();
// normals array may be shorter (known OCCT API quirk, see mesh.rs)
let normals: Vec<[f32; 3]> = if m.normals.len() == vertices.len() {
m.normals.iter()
.map(|n| {
let len = (n.x*n.x + n.y*n.y + n.z*n.z).sqrt();
if len < 1e-10 {
[0.0, 1.0, 0.0]
} else {
[(n.x/len) as f32, (n.y/len) as f32, (n.z/len) as f32]
}
})
.collect()
} else {
// Fallback: flat normals per vertex (less ideal but correct)
vec![[0.0_f32, 1.0, 0.0]; vertices.len()]
};
let indices: Vec<u32> = m.indices.iter()
.map(|&i| i as u32)
.collect();
let colors = vec![crate::COLOR_CONCRETE; vertices.len()];
Ok(Mesh { vertices, normals, indices, colors })
}
}
// ── Public struct (only when feature is active) ────────────────────────────────
#[cfg(feature = "occt")]
pub use self::occt_kernel::OcctKernel;
#[cfg(feature = "occt")]
mod occt_kernel {
use cimery_ir::{AbutmentIR, BearingIR, DeckSlabIR, GirderIR, PierIR};
use crate::{GeomKernel, KernelError, Mesh};
use super::inner;
/// Full B-rep geometry backend using OpenCASCADE Technology.
/// Enabled with `--features occt`. See ADR-001.
pub struct OcctKernel;
impl GeomKernel for OcctKernel {
fn girder_mesh(&self, ir: &GirderIR) -> Result<Mesh, KernelError> {
inner::girder_mesh(ir)
}
fn deck_slab_mesh(&self, ir: &DeckSlabIR) -> Result<Mesh, KernelError> {
inner::deck_slab_mesh(ir)
}
fn bearing_mesh(&self, ir: &BearingIR) -> Result<Mesh, KernelError> {
inner::bearing_mesh(ir)
}
fn pier_mesh(&self, ir: &PierIR) -> Result<Mesh, KernelError> {
inner::pier_mesh(ir)
}
fn abutment_mesh(&self, ir: &AbutmentIR) -> Result<Mesh, KernelError> {
inner::abutment_mesh(ir)
}
}
}