mod caching;
mod clipping;
mod layers;
mod processing;
mod transforms;
mod voids;
mod voids_2d;
#[cfg(test)]
mod tests;
use crate::material_layer_index::MaterialLayerIndex;
use crate::processors::{
AdvancedBrepProcessor, BooleanClippingProcessor, ExtrudedAreaSolidProcessor,
ExtrudedAreaSolidTaperedProcessor, FaceBasedSurfaceModelProcessor, FacetedBrepProcessor,
MappedItemProcessor, PolygonalFaceSetProcessor, RevolvedAreaSolidProcessor,
ShellBasedSurfaceModelProcessor, SweptDiskSolidProcessor, TriangulatedFaceSetProcessor,
};
use crate::{BoolFailure, Mesh, Result};
use ifc_lite_core::{DecodedEntity, EntityDecoder, IfcSchema, IfcType};
use nalgebra::Matrix4;
use rustc_hash::FxHashMap;
use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::Arc;
pub trait GeometryProcessor {
fn process(
&self,
entity: &DecodedEntity,
decoder: &mut EntityDecoder,
schema: &IfcSchema,
) -> Result<Mesh>;
fn supported_types(&self) -> Vec<IfcType>;
}
pub struct GeometryRouter {
schema: IfcSchema,
processors: HashMap<IfcType, Arc<dyn GeometryProcessor>>,
mapped_item_cache: RefCell<FxHashMap<u32, Arc<Mesh>>>,
faceted_brep_cache: RefCell<FxHashMap<u32, Mesh>>,
geometry_hash_cache: RefCell<FxHashMap<u64, Arc<Mesh>>>,
unit_scale: f64,
rtc_offset: (f64, f64, f64),
material_layer_index: Option<Arc<MaterialLayerIndex>>,
csg_failures: RefCell<FxHashMap<u32, Vec<BoolFailure>>>,
classification_stats: RefCell<ClassificationStats>,
host_opening_diagnostics: RefCell<FxHashMap<u32, HostOpeningDiagnostic>>,
}
#[derive(Debug, Default, Clone, Copy)]
pub struct ClassificationStats {
pub rectangular: usize,
pub diagonal: usize,
pub non_rectangular: usize,
pub floor_opening_guard_saved: usize,
}
#[derive(Debug, Clone, Default)]
pub struct HostOpeningDiagnostic {
pub host_type: String,
pub openings: Vec<OpeningDiagnostic>,
pub csg_failure_count: usize,
pub first_failure_label: Option<String>,
pub tris_before: Option<usize>,
pub tris_after: Option<usize>,
pub rect_boxes_processed: usize,
pub host_bounds: Option<((f32, f32, f32), (f32, f32, f32))>,
}
#[derive(Debug, Clone)]
pub struct OpeningDiagnostic {
pub opening_id: u32,
pub kind: OpeningKindDiag,
pub vertex_count: usize,
pub guard_saved: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OpeningKindDiag {
Rectangular,
Diagonal,
NonRectangular,
}
impl OpeningKindDiag {
pub fn as_str(self) -> &'static str {
match self {
OpeningKindDiag::Rectangular => "Rectangular",
OpeningKindDiag::Diagonal => "Diagonal",
OpeningKindDiag::NonRectangular => "NonRectangular",
}
}
}
impl GeometryRouter {
pub fn new() -> Self {
let schema = IfcSchema::new();
let schema_clone = schema.clone();
let mut router = Self {
schema,
processors: HashMap::new(),
mapped_item_cache: RefCell::new(FxHashMap::default()),
faceted_brep_cache: RefCell::new(FxHashMap::default()),
geometry_hash_cache: RefCell::new(FxHashMap::default()),
unit_scale: 1.0, rtc_offset: (0.0, 0.0, 0.0), material_layer_index: None,
csg_failures: RefCell::new(FxHashMap::default()),
classification_stats: RefCell::new(ClassificationStats::default()),
host_opening_diagnostics: RefCell::new(FxHashMap::default()),
};
router.register(Box::new(ExtrudedAreaSolidProcessor::new(
schema_clone.clone(),
)));
router.register(Box::new(ExtrudedAreaSolidTaperedProcessor::new(
schema_clone.clone(),
)));
router.register(Box::new(TriangulatedFaceSetProcessor::new()));
router.register(Box::new(PolygonalFaceSetProcessor::new()));
router.register(Box::new(MappedItemProcessor::new()));
router.register(Box::new(FacetedBrepProcessor::new()));
router.register(Box::new(BooleanClippingProcessor::new()));
router.register(Box::new(SweptDiskSolidProcessor::new(schema_clone.clone())));
router.register(Box::new(RevolvedAreaSolidProcessor::new(
schema_clone.clone(),
)));
router.register(Box::new(AdvancedBrepProcessor::new()));
router.register(Box::new(ShellBasedSurfaceModelProcessor::new()));
router.register(Box::new(FaceBasedSurfaceModelProcessor::new()));
router
}
pub fn with_units(content: &str, decoder: &mut EntityDecoder) -> Self {
let mut scanner = ifc_lite_core::EntityScanner::new(content);
let mut scale = 1.0;
while let Some((id, type_name, _, _)) = scanner.next_entity() {
if type_name == "IFCPROJECT" {
if let Ok(s) = ifc_lite_core::extract_length_unit_scale(decoder, id) {
scale = s;
}
break;
}
}
Self::with_scale(scale)
}
pub fn with_units_and_rtc(
content: &str,
decoder: &mut ifc_lite_core::EntityDecoder,
rtc_offset: (f64, f64, f64),
) -> Self {
let mut scanner = ifc_lite_core::EntityScanner::new(content);
let mut scale = 1.0;
while let Some((id, type_name, _, _)) = scanner.next_entity() {
if type_name == "IFCPROJECT" {
if let Ok(s) = ifc_lite_core::extract_length_unit_scale(decoder, id) {
scale = s;
}
break;
}
}
Self::with_scale_and_rtc(scale, rtc_offset)
}
pub fn with_scale(unit_scale: f64) -> Self {
let mut router = Self::new();
router.unit_scale = unit_scale;
router
}
pub fn with_rtc(rtc_offset: (f64, f64, f64)) -> Self {
let mut router = Self::new();
router.rtc_offset = rtc_offset;
router
}
pub fn with_scale_and_rtc(unit_scale: f64, rtc_offset: (f64, f64, f64)) -> Self {
let mut router = Self::new();
router.unit_scale = unit_scale;
router.rtc_offset = rtc_offset;
router
}
pub fn set_rtc_offset(&mut self, offset: (f64, f64, f64)) {
self.rtc_offset = offset;
}
pub fn rtc_offset(&self) -> (f64, f64, f64) {
self.rtc_offset
}
#[inline]
pub fn has_rtc_offset(&self) -> bool {
self.rtc_offset.0 != 0.0 || self.rtc_offset.1 != 0.0 || self.rtc_offset.2 != 0.0
}
pub fn unit_scale(&self) -> f64 {
self.unit_scale
}
pub fn set_material_layer_index(&mut self, index: Arc<MaterialLayerIndex>) {
self.material_layer_index = Some(index);
}
#[inline]
pub(crate) fn material_layer_index(&self) -> Option<&MaterialLayerIndex> {
self.material_layer_index.as_deref()
}
#[inline]
fn scale_mesh(&self, mesh: &mut Mesh) {
if self.unit_scale != 1.0 {
let scale = self.unit_scale as f32;
for pos in mesh.positions.iter_mut() {
*pos *= scale;
}
}
}
#[inline]
fn scale_transform(&self, transform: &mut Matrix4<f64>) {
if self.unit_scale != 1.0 {
transform[(0, 3)] *= self.unit_scale;
transform[(1, 3)] *= self.unit_scale;
transform[(2, 3)] *= self.unit_scale;
}
}
pub fn register(&mut self, processor: Box<dyn GeometryProcessor>) {
let processor_arc: Arc<dyn GeometryProcessor> = Arc::from(processor);
for ifc_type in processor_arc.supported_types() {
self.processors.insert(ifc_type, Arc::clone(&processor_arc));
}
}
pub fn preprocess_faceted_breps(&self, brep_ids: &[u32], decoder: &mut EntityDecoder) {
if brep_ids.is_empty() {
return;
}
let processor = FacetedBrepProcessor::new();
let rtc_file_units = (
self.rtc_offset.0 / self.unit_scale,
self.rtc_offset.1 / self.unit_scale,
self.rtc_offset.2 / self.unit_scale,
);
let large_coord_threshold_file_units = 10000.0 / self.unit_scale;
let results = processor.process_batch(
brep_ids,
decoder,
rtc_file_units,
large_coord_threshold_file_units,
);
let mut cache = self.faceted_brep_cache.borrow_mut();
cache.reserve(results.len());
for (brep_idx, mesh) in results {
let brep_id = brep_ids[brep_idx];
cache.insert(brep_id, mesh);
}
}
#[inline]
pub fn take_cached_faceted_brep(&self, brep_id: u32) -> Option<Mesh> {
self.faceted_brep_cache.borrow_mut().remove(&brep_id)
}
pub fn resolve_scaled_placement(
&self,
entity: &DecodedEntity,
decoder: &mut EntityDecoder,
) -> Result<[f64; 16]> {
let mut transform = self.get_placement_transform_from_element(entity, decoder)?;
self.scale_transform(&mut transform);
let mut result = [0.0f64; 16];
result.copy_from_slice(transform.as_slice());
Ok(result)
}
pub fn schema(&self) -> &IfcSchema {
&self.schema
}
pub fn take_csg_failures(&self) -> FxHashMap<u32, Vec<BoolFailure>> {
let pending = crate::diagnostics::take_pending_mapped_bool_failures();
if !pending.is_empty() {
self.csg_failures
.borrow_mut()
.entry(0)
.or_default()
.extend(pending);
}
std::mem::take(&mut *self.csg_failures.borrow_mut())
}
pub fn csg_failure_product_count(&self) -> usize {
self.csg_failures.borrow().len()
}
pub fn csg_failure_total(&self) -> usize {
self.csg_failures
.borrow()
.values()
.map(|v| v.len())
.sum()
}
pub(crate) fn record_csg_failures(&self, product_id: u32, failures: Vec<BoolFailure>) {
if failures.is_empty() {
return;
}
let attributed: Vec<BoolFailure> = failures
.into_iter()
.map(|f| f.with_product_id(product_id))
.collect();
self.csg_failures
.borrow_mut()
.entry(product_id)
.or_default()
.extend(attributed);
}
pub fn take_classification_stats(&self) -> ClassificationStats {
std::mem::take(&mut *self.classification_stats.borrow_mut())
}
pub fn take_host_opening_diagnostics(&self) -> FxHashMap<u32, HostOpeningDiagnostic> {
std::mem::take(&mut *self.host_opening_diagnostics.borrow_mut())
}
pub fn host_opening_diagnostic_count(&self) -> usize {
self.host_opening_diagnostics.borrow().len()
}
pub(crate) fn bump_classification(&self, kind: ClassificationKind) {
let mut s = self.classification_stats.borrow_mut();
match kind {
ClassificationKind::Rectangular => s.rectangular += 1,
ClassificationKind::Diagonal => s.diagonal += 1,
ClassificationKind::NonRectangular => s.non_rectangular += 1,
ClassificationKind::FloorOpeningGuardSaved => s.floor_opening_guard_saved += 1,
}
}
pub(crate) fn record_host_opening_diagnostic(
&self,
host_id: u32,
host_type: &str,
openings: Vec<OpeningDiagnostic>,
) {
let mut log = self.host_opening_diagnostics.borrow_mut();
let entry = log.entry(host_id).or_default();
if entry.host_type.is_empty() {
entry.host_type = host_type.to_string();
}
entry.openings.extend(openings);
}
pub(crate) fn record_host_cut_effect(
&self,
host_id: u32,
tris_before: usize,
tris_after: usize,
rect_boxes_processed: usize,
host_bounds: ((f32, f32, f32), (f32, f32, f32)),
) {
let mut log = self.host_opening_diagnostics.borrow_mut();
let entry = log.entry(host_id).or_default();
entry.tris_before = Some(tris_before);
entry.tris_after = Some(tris_after);
entry.rect_boxes_processed = rect_boxes_processed;
entry.host_bounds = Some(host_bounds);
}
pub(crate) fn record_host_failure_summary(
&self,
host_id: u32,
failures: &[BoolFailure],
) {
if failures.is_empty() {
return;
}
let mut log = self.host_opening_diagnostics.borrow_mut();
let entry = log.entry(host_id).or_default();
entry.csg_failure_count += failures.len();
if entry.first_failure_label.is_none() {
let label = match &failures[0].reason {
crate::diagnostics::BoolFailureReason::OperandTooLarge { .. } => {
"OperandTooLarge"
}
crate::diagnostics::BoolFailureReason::EmptyOperand => "EmptyOperand",
crate::diagnostics::BoolFailureReason::DegenerateOperand => "DegenerateOperand",
crate::diagnostics::BoolFailureReason::NoBoundsOverlap => "NoBoundsOverlap",
crate::diagnostics::BoolFailureReason::KernelOutputInvalid => {
"KernelOutputInvalid"
}
crate::diagnostics::BoolFailureReason::SolidSolidDifferenceSkipped => {
"SolidSolidDifferenceSkipped"
}
crate::diagnostics::BoolFailureReason::PolygonalBoundedHalfSpaceFallback => {
"PolygonalBoundedHalfSpaceFallback"
}
crate::diagnostics::BoolFailureReason::UnknownBooleanOperator(_) => {
"UnknownBooleanOperator"
}
crate::diagnostics::BoolFailureReason::ManifoldOutputDegenerate { .. } => {
"ManifoldOutputDegenerate"
}
crate::diagnostics::BoolFailureReason::KernelError(_) => "KernelError",
};
entry.first_failure_label = Some(label.to_string());
}
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum ClassificationKind {
Rectangular,
Diagonal,
NonRectangular,
#[allow(dead_code)]
FloorOpeningGuardSaved,
}
impl Default for GeometryRouter {
fn default() -> Self {
Self::new()
}
}