mod caching;
mod rep_filter;
mod content_hash;
mod diagnostics;
mod instancing;
mod layers;
mod processing;
mod rtc_offset;
mod textured;
mod transforms;
mod voids;
pub use transforms::local_frame_set_enabled_override;
pub use voids::{take_bool2d_stats, take_prism_defers, take_prism_stats, RectParam};
pub use diagnostics::{
GEOMETRY_DIAGNOSTICS_SCHEMA_VERSION,
aggregate_diagnostics, ClassificationStats, ClassificationSummary, GeometryDiagnostics,
HostOpeningDiagnostic, OpeningDiagnostic, OpeningKindDiag, ReasonCount, RectFastSummary,
WorstHost,
};
pub(crate) use diagnostics::ClassificationKind;
pub(super) use rep_filter::{effective_rep_type, is_body_representation, is_direct_body_representation};
#[cfg(test)]
mod tests;
use crate::material_layer_index::MaterialLayerIndex;
use crate::processors::{
AdvancedBrepProcessor, BSplineSurfaceProcessor, BlockProcessor, BooleanClippingProcessor,
CsgSolidProcessor, ExtrudedAreaSolidProcessor, ExtrudedAreaSolidTaperedProcessor,
FaceBasedSurfaceModelProcessor, FacetedBrepProcessor, IfcAlignmentProcessor,
PolygonalFaceSetProcessor, RevolvedAreaSolidProcessor,
SectionedSolidHorizontalProcessor, ShellBasedSurfaceModelProcessor, SphereProcessor,
SurfaceCurveSweptAreaSolidProcessor, SweptDiskSolidProcessor, TriangulatedFaceSetProcessor,
};
use crate::tessellation::TessellationQuality;
use crate::{BoolFailure, Mesh, Result};
use ifc_lite_core::{DecodedEntity, EntityDecoder, IfcSchema, IfcType};
use nalgebra::Matrix4;
use rustc_hash::{FxHashMap, FxHashSet};
use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
pub trait GeometryProcessor {
fn process(
&self,
entity: &DecodedEntity,
decoder: &mut EntityDecoder,
schema: &IfcSchema,
quality: TessellationQuality,
) -> Result<Mesh>;
fn supported_types(&self) -> Vec<IfcType>;
}
pub type ItemDedupCache = Arc<Mutex<FxHashMap<u128, Arc<(Mesh, Option<u128>)>>>>;
static DEDUP_EXTRA_OVERRIDE: std::sync::atomic::AtomicI8 = std::sync::atomic::AtomicI8::new(-1);
pub type SharedMappedItemCache = Arc<Mutex<FxHashMap<u32, Arc<Mesh>>>>;
pub type MappedInstancePlan = Arc<FxHashMap<u32, (u32, u32)>>;
pub struct GeometryRouter {
schema: IfcSchema,
processors: HashMap<IfcType, Arc<dyn GeometryProcessor>>,
mapped_item_cache: RefCell<FxHashMap<u32, Arc<Mesh>>>,
shared_mapped_item_cache: Option<SharedMappedItemCache>,
geometry_hash_cache: RefCell<FxHashMap<u64, Arc<Mesh>>>,
item_dedup_cache: Option<ItemDedupCache>,
content_sig_memo: RefCell<FxHashMap<u32, u128>>,
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>>,
rect_fast_stats: RefCell<crate::rect_fast::RectFastStats>,
layer_slice_diag: RefCell<Vec<(u32, &'static str)>>,
voids_consumed_hosts: RefCell<FxHashSet<u32>>,
tessellation_quality: TessellationQuality,
skip_small_cuts: bool,
output_instancing_plan: Option<MappedInstancePlan>,
indexed_colour_split_ids: Option<Arc<FxHashSet<u32>>>,
instancing_batch_local: bool,
instanced_sources_materialized: RefCell<FxHashSet<u32>>,
}
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()),
shared_mapped_item_cache: None, geometry_hash_cache: RefCell::new(FxHashMap::default()),
item_dedup_cache: None, content_sig_memo: 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()),
rect_fast_stats: RefCell::new(crate::rect_fast::RectFastStats::default()),
layer_slice_diag: RefCell::new(Vec::new()),
voids_consumed_hosts: RefCell::new(FxHashSet::default()),
tessellation_quality: TessellationQuality::Medium,
skip_small_cuts: false,
output_instancing_plan: None, indexed_colour_split_ids: None, instancing_batch_local: false, instanced_sources_materialized: RefCell::new(FxHashSet::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(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(SurfaceCurveSweptAreaSolidProcessor::new(
schema_clone.clone(),
)));
router.register(Box::new(SectionedSolidHorizontalProcessor::new(
schema_clone.clone(),
)));
router.register(Box::new(AdvancedBrepProcessor::new()));
router.register(Box::new(BSplineSurfaceProcessor::new()));
router.register(Box::new(ShellBasedSurfaceModelProcessor::new()));
router.register(Box::new(FaceBasedSurfaceModelProcessor::new()));
router.register(Box::new(BlockProcessor::new()));
router.register(Box::new(SphereProcessor::new()));
router.register(Box::new(CsgSolidProcessor::new()));
router.register(Box::new(IfcAlignmentProcessor::new()));
router
}
pub fn with_units<T>(content: &T, decoder: &mut EntityDecoder) -> Self
where
T: AsRef<[u8]> + ?Sized,
{
let scale = Self::scan_unit_scale(content.as_ref(), decoder);
let mut router = Self::with_scale(scale);
router.arm_content_dedup();
router
}
fn scan_unit_scale(content: &[u8], decoder: &mut EntityDecoder) -> f64 {
let mut scanner = ifc_lite_core::EntityScanner::new(content);
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) {
return s;
}
break;
}
}
1.0
}
pub fn with_units_and_rtc<T>(
content: &T,
decoder: &mut ifc_lite_core::EntityDecoder,
rtc_offset: (f64, f64, f64),
) -> Self
where
T: AsRef<[u8]> + ?Sized,
{
let scale = Self::scan_unit_scale(content.as_ref(), decoder);
let mut router = Self::with_scale_and_rtc(scale, rtc_offset);
router.arm_content_dedup();
router
}
pub fn with_scale(unit_scale: f64) -> Self {
let mut router = Self::new();
router.unit_scale = unit_scale;
router
}
fn arm_content_dedup(&mut self) {
self.item_dedup_cache = Some(Self::new_dedup_cache());
}
pub fn new_dedup_cache() -> ItemDedupCache {
Arc::new(Mutex::new(FxHashMap::default()))
}
pub fn build_dedup_extra_enabled() -> bool {
match DEDUP_EXTRA_OVERRIDE.load(std::sync::atomic::Ordering::Relaxed) {
0 => return false,
1 => return true,
_ => {}
}
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| std::env::var("IFC_LITE_DEDUP_EXTRA").as_deref() == Ok("1"))
}
pub fn set_build_dedup_extra_override(v: Option<bool>) {
DEDUP_EXTRA_OVERRIDE.store(
match v {
None => -1,
Some(false) => 0,
Some(true) => 1,
},
std::sync::atomic::Ordering::Relaxed,
);
}
pub fn enable_content_dedup_shared(&mut self, cache: ItemDedupCache) {
self.item_dedup_cache = Some(cache);
}
pub fn new_mapped_item_cache() -> SharedMappedItemCache {
Arc::new(Mutex::new(FxHashMap::default()))
}
pub fn enable_shared_mapped_item_cache(&mut self, cache: SharedMappedItemCache) {
self.shared_mapped_item_cache = Some(cache);
}
pub fn enable_output_instancing(&mut self, plan: MappedInstancePlan) {
self.output_instancing_plan = Some(plan);
}
pub(super) fn output_instancing_plan(&self) -> Option<&MappedInstancePlan> {
self.output_instancing_plan.as_ref()
}
pub fn enable_indexed_colour_split_guard(&mut self, ids: Arc<FxHashSet<u32>>) {
self.indexed_colour_split_ids = Some(ids);
}
pub(super) fn is_indexed_colour_split_source(&self, geometry_id: u32) -> bool {
self.indexed_colour_split_ids
.as_ref()
.is_some_and(|ids| ids.contains(&geometry_id))
}
pub fn set_instancing_batch_local(&mut self, on: bool) {
self.instancing_batch_local = on;
}
#[inline]
pub(super) fn instancing_batch_local(&self) -> bool {
self.instancing_batch_local
}
pub(super) fn mark_source_materialized_if_first(&self, source_id: u32) -> bool {
self.instanced_sources_materialized
.borrow_mut()
.insert(source_id)
}
pub fn disable_content_dedup(&mut self) {
self.item_dedup_cache = None;
}
pub fn dedup_unique_count(&self) -> usize {
self.item_dedup_cache
.as_ref()
.map(|c| c.lock().unwrap_or_else(|e| e.into_inner()).len())
.unwrap_or(0)
}
pub fn mapped_shared_unique_count(&self) -> usize {
self.shared_mapped_item_cache
.as_ref()
.map(|c| c.lock().unwrap_or_else(|e| e.into_inner()).len())
.unwrap_or(0)
}
pub fn geometry_routing_key(
&self,
element: &DecodedEntity,
decoder: &mut EntityDecoder,
) -> Option<u128> {
let rep = element.get(6)?.as_entity_ref()?;
let mut memo = self.content_sig_memo.borrow_mut();
Some(content_hash::item_signature(decoder, rep, &mut memo))
}
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 with_quality(quality: TessellationQuality) -> Self {
let mut router = Self::new();
router.tessellation_quality = quality;
router
}
pub fn with_scale_and_quality(unit_scale: f64, quality: TessellationQuality) -> Self {
let mut router = Self::new();
router.unit_scale = unit_scale;
router.tessellation_quality = quality;
router
}
pub fn set_tessellation_quality(&mut self, quality: TessellationQuality) {
if self.tessellation_quality == quality {
return;
}
self.tessellation_quality = quality;
self.mapped_item_cache.get_mut().clear();
}
#[inline]
pub fn tessellation_quality(&self) -> TessellationQuality {
self.tessellation_quality
}
pub fn set_skip_small_cuts(&mut self, on: bool) {
if self.skip_small_cuts == on {
return;
}
self.skip_small_cuts = on;
self.register_skip_dependent_processors();
}
#[inline]
pub fn skip_small_cuts(&self) -> bool {
self.skip_small_cuts
}
fn register_skip_dependent_processors(&mut self) {
self.register(Box::new(BooleanClippingProcessor::with_skip_small_cuts(
self.skip_small_cuts,
)));
self.register(Box::new(CsgSolidProcessor::with_skip_small_cuts(
self.skip_small_cuts,
)));
}
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]
pub fn is_material_layer_sliceable(&self, element_id: u32) -> bool {
self.material_layer_index()
.is_some_and(|idx| idx.is_sliceable(element_id))
}
#[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 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
}
}
impl Default for GeometryRouter {
fn default() -> Self {
Self::new()
}
}