use crate::properties::PropertyReaderImpl;
use crate::resolver::ResolverImpl;
use crate::scanner::{parse_header, EntityScanner};
use crate::spatial::SpatialQueryImpl;
use crate::units::extract_unit_scale;
use bimifc_model::{
EntityId, EntityResolver, IfcModel, IfcType, ModelMetadata, ProgressCallback, PropertyReader,
Result, SpatialQuery,
};
use rustc_hash::FxHashMap;
use std::sync::Arc;
pub struct ParsedModel {
resolver: Arc<ResolverImpl>,
properties: Arc<PropertyReaderImpl>,
spatial: Arc<SpatialQueryImpl>,
unit_scale: f64,
metadata: ModelMetadata,
}
impl ParsedModel {
pub fn parse(content: &str, build_spatial: bool, _extract_properties: bool) -> Result<Self> {
let index = EntityScanner::build_index(content);
let mut type_index: FxHashMap<IfcType, Vec<EntityId>> = FxHashMap::default();
let mut scanner = EntityScanner::new(content);
while let Some((id, type_name, _, _)) = scanner.next_entity() {
let ifc_type = IfcType::parse(type_name);
type_index.entry(ifc_type).or_default().push(EntityId(id));
}
let resolver = Arc::new(ResolverImpl::with_type_index(
content.to_string(),
index,
type_index,
));
let unit_scale = extract_unit_scale(resolver.as_ref());
let properties = Arc::new(PropertyReaderImpl::new(resolver.clone()));
let spatial = if build_spatial {
Arc::new(SpatialQueryImpl::build(resolver.as_ref()))
} else {
Arc::new(SpatialQueryImpl::empty())
};
let header = parse_header(content);
let metadata = ModelMetadata {
schema_version: header.schema_version,
originating_system: header.originating_system,
preprocessor_version: header.preprocessor_version,
file_name: header.file_name,
file_description: None, author: header.author,
organization: header.organization,
timestamp: header.timestamp,
};
Ok(Self {
resolver,
properties,
spatial,
unit_scale,
metadata,
})
}
pub fn parse_with_progress(
content: &str,
build_spatial: bool,
_extract_properties: bool,
on_progress: ProgressCallback,
) -> Result<Self> {
on_progress("Scanning entities", 0.0);
let index = EntityScanner::build_index(content);
on_progress("Building index", 20.0);
let mut type_index: FxHashMap<IfcType, Vec<EntityId>> = FxHashMap::default();
let mut scanner = EntityScanner::new(content);
while let Some((id, type_name, _, _)) = scanner.next_entity() {
let ifc_type = IfcType::parse(type_name);
type_index.entry(ifc_type).or_default().push(EntityId(id));
}
on_progress("Indexing types", 40.0);
let resolver = Arc::new(ResolverImpl::with_type_index(
content.to_string(),
index,
type_index,
));
let unit_scale = extract_unit_scale(resolver.as_ref());
on_progress("Extracting units", 50.0);
let properties = Arc::new(PropertyReaderImpl::new(resolver.clone()));
on_progress("Building property index", 60.0);
let spatial = if build_spatial {
on_progress("Building spatial structure", 70.0);
Arc::new(SpatialQueryImpl::build(resolver.as_ref()))
} else {
Arc::new(SpatialQueryImpl::empty())
};
on_progress("Processing metadata", 90.0);
let header = parse_header(content);
let metadata = ModelMetadata {
schema_version: header.schema_version,
originating_system: header.originating_system,
preprocessor_version: header.preprocessor_version,
file_name: header.file_name,
file_description: None, author: header.author,
organization: header.organization,
timestamp: header.timestamp,
};
on_progress("Complete", 100.0);
Ok(Self {
resolver,
properties,
spatial,
unit_scale,
metadata,
})
}
pub fn resolver_arc(&self) -> Arc<ResolverImpl> {
self.resolver.clone()
}
}
impl IfcModel for ParsedModel {
fn resolver(&self) -> &dyn EntityResolver {
self.resolver.as_ref()
}
fn properties(&self) -> &dyn PropertyReader {
self.properties.as_ref()
}
fn spatial(&self) -> &dyn SpatialQuery {
self.spatial.as_ref()
}
fn unit_scale(&self) -> f64 {
self.unit_scale
}
fn metadata(&self) -> &ModelMetadata {
&self.metadata
}
}
#[cfg(test)]
mod tests {
use super::*;
const TEST_IFC: &str = r#"ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
FILE_NAME('test.ifc','2024-01-01T00:00:00',('Author'),('Org'),'Preprocessor','App','');
FILE_SCHEMA(('IFC2X3'));
ENDSEC;
DATA;
#1=IFCPROJECT('guid',$,'Test Project',$,$,$,$,$,#2);
#2=IFCUNITASSIGNMENT((#3));
#3=IFCSIUNIT(*,.LENGTHUNIT.,.MILLI.,.METRE.);
#4=IFCSITE('guid2',$,'Site',$,$,$,$,$,$,$,$,$,$,$);
#5=IFCRELAGGREGATES('guid3',$,$,$,#1,(#4));
#6=IFCBUILDING('guid4',$,'Building',$,$,$,$,$,$,$,$,$);
#7=IFCRELAGGREGATES('guid5',$,$,$,#4,(#6));
#8=IFCBUILDINGSTOREY('guid6',$,'Ground Floor',$,$,$,$,$,.ELEMENT.,0.0);
#9=IFCRELAGGREGATES('guid7',$,$,$,#6,(#8));
#10=IFCWALL('guid8',$,'Wall 1',$,$,$,$,$);
#11=IFCRELCONTAINEDINSPATIALSTRUCTURE('guid9',$,$,$,(#10),#8);
ENDSEC;
END-ISO-10303-21;
"#;
#[test]
fn test_parse_model() {
let model = ParsedModel::parse(TEST_IFC, true, true).unwrap();
assert_eq!(model.metadata().schema_version, "IFC2X3");
assert_eq!(model.metadata().file_name, Some("test.ifc".to_string()));
assert!((model.unit_scale() - 0.001).abs() < 1e-10);
let walls = model.resolver().find_by_type_name("IFCWALL");
assert_eq!(walls.len(), 1);
let tree = model.spatial().spatial_tree();
assert!(tree.is_some());
let project = tree.unwrap();
assert_eq!(project.name, "Test Project");
let storeys = model.spatial().storeys();
assert_eq!(storeys.len(), 1);
assert_eq!(storeys[0].name, "Ground Floor");
}
#[test]
fn test_search() {
let model = ParsedModel::parse(TEST_IFC, true, true).unwrap();
let results = model.spatial().search("wall");
assert!(!results.is_empty());
let results = model.spatial().search("IFCWALL");
assert!(!results.is_empty());
}
#[test]
fn test_elements_in_storey() {
let model = ParsedModel::parse(TEST_IFC, true, true).unwrap();
let storeys = model.spatial().storeys();
assert!(!storeys.is_empty());
let elements = model.spatial().elements_in_storey(storeys[0].id);
assert!(!elements.is_empty());
}
}