1use crate::properties::PropertyReaderImpl;
8use crate::resolver::ResolverImpl;
9use crate::scanner::{parse_header, EntityScanner};
10use crate::spatial::SpatialQueryImpl;
11use crate::units::extract_unit_scale;
12
13use bimifc_model::{
14 EntityId, EntityResolver, IfcModel, IfcType, ModelMetadata, ProgressCallback, PropertyReader,
15 Result, SpatialQuery,
16};
17use rustc_hash::FxHashMap;
18use std::sync::Arc;
19
20pub struct ParsedModel {
25 resolver: Arc<ResolverImpl>,
27 properties: Arc<PropertyReaderImpl>,
29 spatial: Arc<SpatialQueryImpl>,
31 unit_scale: f64,
33 metadata: ModelMetadata,
35}
36
37impl ParsedModel {
38 pub fn parse(content: &str, build_spatial: bool, _extract_properties: bool) -> Result<Self> {
40 let index = EntityScanner::build_index(content);
42
43 let mut type_index: FxHashMap<IfcType, Vec<EntityId>> = FxHashMap::default();
45 let mut scanner = EntityScanner::new(content);
46 while let Some((id, type_name, _, _)) = scanner.next_entity() {
47 let ifc_type = IfcType::parse(type_name);
48 type_index.entry(ifc_type).or_default().push(EntityId(id));
49 }
50
51 let resolver = Arc::new(ResolverImpl::with_type_index(
53 content.to_string(),
54 index,
55 type_index,
56 ));
57
58 let unit_scale = extract_unit_scale(resolver.as_ref());
60
61 let properties = Arc::new(PropertyReaderImpl::new(resolver.clone()));
63
64 let spatial = if build_spatial {
66 Arc::new(SpatialQueryImpl::build(resolver.as_ref()))
67 } else {
68 Arc::new(SpatialQueryImpl::empty())
69 };
70
71 let header = parse_header(content);
73 let metadata = ModelMetadata {
74 schema_version: header.schema_version,
75 originating_system: header.originating_system,
76 preprocessor_version: header.preprocessor_version,
77 file_name: header.file_name,
78 file_description: None, author: header.author,
80 organization: header.organization,
81 timestamp: header.timestamp,
82 };
83
84 Ok(Self {
85 resolver,
86 properties,
87 spatial,
88 unit_scale,
89 metadata,
90 })
91 }
92
93 pub fn parse_with_progress(
95 content: &str,
96 build_spatial: bool,
97 _extract_properties: bool,
98 on_progress: ProgressCallback,
99 ) -> Result<Self> {
100 on_progress("Scanning entities", 0.0);
101
102 let index = EntityScanner::build_index(content);
104 on_progress("Building index", 20.0);
105
106 let mut type_index: FxHashMap<IfcType, Vec<EntityId>> = FxHashMap::default();
108 let mut scanner = EntityScanner::new(content);
109 while let Some((id, type_name, _, _)) = scanner.next_entity() {
110 let ifc_type = IfcType::parse(type_name);
111 type_index.entry(ifc_type).or_default().push(EntityId(id));
112 }
113 on_progress("Indexing types", 40.0);
114
115 let resolver = Arc::new(ResolverImpl::with_type_index(
117 content.to_string(),
118 index,
119 type_index,
120 ));
121
122 let unit_scale = extract_unit_scale(resolver.as_ref());
124 on_progress("Extracting units", 50.0);
125
126 let properties = Arc::new(PropertyReaderImpl::new(resolver.clone()));
128 on_progress("Building property index", 60.0);
129
130 let spatial = if build_spatial {
132 on_progress("Building spatial structure", 70.0);
133 Arc::new(SpatialQueryImpl::build(resolver.as_ref()))
134 } else {
135 Arc::new(SpatialQueryImpl::empty())
136 };
137 on_progress("Processing metadata", 90.0);
138
139 let header = parse_header(content);
141 let metadata = ModelMetadata {
142 schema_version: header.schema_version,
143 originating_system: header.originating_system,
144 preprocessor_version: header.preprocessor_version,
145 file_name: header.file_name,
146 file_description: None, author: header.author,
148 organization: header.organization,
149 timestamp: header.timestamp,
150 };
151
152 on_progress("Complete", 100.0);
153
154 Ok(Self {
155 resolver,
156 properties,
157 spatial,
158 unit_scale,
159 metadata,
160 })
161 }
162
163 pub fn resolver_arc(&self) -> Arc<ResolverImpl> {
165 self.resolver.clone()
166 }
167}
168
169impl IfcModel for ParsedModel {
170 fn resolver(&self) -> &dyn EntityResolver {
171 self.resolver.as_ref()
172 }
173
174 fn properties(&self) -> &dyn PropertyReader {
175 self.properties.as_ref()
176 }
177
178 fn spatial(&self) -> &dyn SpatialQuery {
179 self.spatial.as_ref()
180 }
181
182 fn unit_scale(&self) -> f64 {
183 self.unit_scale
184 }
185
186 fn metadata(&self) -> &ModelMetadata {
187 &self.metadata
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194
195 const TEST_IFC: &str = r#"ISO-10303-21;
196HEADER;
197FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
198FILE_NAME('test.ifc','2024-01-01T00:00:00',('Author'),('Org'),'Preprocessor','App','');
199FILE_SCHEMA(('IFC2X3'));
200ENDSEC;
201DATA;
202#1=IFCPROJECT('guid',$,'Test Project',$,$,$,$,$,#2);
203#2=IFCUNITASSIGNMENT((#3));
204#3=IFCSIUNIT(*,.LENGTHUNIT.,.MILLI.,.METRE.);
205#4=IFCSITE('guid2',$,'Site',$,$,$,$,$,$,$,$,$,$,$);
206#5=IFCRELAGGREGATES('guid3',$,$,$,#1,(#4));
207#6=IFCBUILDING('guid4',$,'Building',$,$,$,$,$,$,$,$,$);
208#7=IFCRELAGGREGATES('guid5',$,$,$,#4,(#6));
209#8=IFCBUILDINGSTOREY('guid6',$,'Ground Floor',$,$,$,$,$,.ELEMENT.,0.0);
210#9=IFCRELAGGREGATES('guid7',$,$,$,#6,(#8));
211#10=IFCWALL('guid8',$,'Wall 1',$,$,$,$,$);
212#11=IFCRELCONTAINEDINSPATIALSTRUCTURE('guid9',$,$,$,(#10),#8);
213ENDSEC;
214END-ISO-10303-21;
215"#;
216
217 #[test]
218 fn test_parse_model() {
219 let model = ParsedModel::parse(TEST_IFC, true, true).unwrap();
220
221 assert_eq!(model.metadata().schema_version, "IFC2X3");
223 assert_eq!(model.metadata().file_name, Some("test.ifc".to_string()));
224
225 assert!((model.unit_scale() - 0.001).abs() < 1e-10);
227
228 let walls = model.resolver().find_by_type_name("IFCWALL");
230 assert_eq!(walls.len(), 1);
231
232 let tree = model.spatial().spatial_tree();
234 assert!(tree.is_some());
235 let project = tree.unwrap();
236 assert_eq!(project.name, "Test Project");
237
238 let storeys = model.spatial().storeys();
240 assert_eq!(storeys.len(), 1);
241 assert_eq!(storeys[0].name, "Ground Floor");
242 }
243
244 #[test]
245 fn test_search() {
246 let model = ParsedModel::parse(TEST_IFC, true, true).unwrap();
247
248 let results = model.spatial().search("wall");
250 assert!(!results.is_empty());
251
252 let results = model.spatial().search("IFCWALL");
254 assert!(!results.is_empty());
255 }
256
257 #[test]
258 fn test_elements_in_storey() {
259 let model = ParsedModel::parse(TEST_IFC, true, true).unwrap();
260
261 let storeys = model.spatial().storeys();
262 assert!(!storeys.is_empty());
263
264 let elements = model.spatial().elements_in_storey(storeys[0].id);
265 assert!(!elements.is_empty());
266 }
267}