OcctKernel 구현 — PSC-I B-rep sweep + BRepMesh 테셀레이션
- cimery-kernel/src/occt.rs: OcctKernel (--features occt 전용) - Workplane::xy().sketch()로 PSC-I 14-vertex 2D 프로파일 생성 - Face::extrude(DVec3) → Solid (OCCT B-rep) - Mesher::try_new() + mesh() → 테셀레이션 - cimery Mesh로 변환 (vertices/normals/indices) - 기타 Feature: PureRustKernel 위임 - kernel/Cargo.toml: glam 0.24 (opencascade-rs 동일 버전) - cargo build --features occt 빌드 확인 완료 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
180
cimery/crates/kernel/src/occt.rs
Normal file
180
cimery/crates/kernel/src/occt.rs
Normal file
@@ -0,0 +1,180 @@
|
||||
//! 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();
|
||||
|
||||
Ok(Mesh { vertices, normals, indices })
|
||||
}
|
||||
}
|
||||
|
||||
// ── 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user