ifc_lite_server/services/data_model/
mod.rs1mod classifications;
8mod generated;
9mod documents;
10mod materials;
11mod material_units;
12mod metadata;
13mod properties;
14mod property_value;
15mod quantities;
16mod relationships;
17mod rooted;
18mod spatial;
19mod types;
20
21pub use rooted::DataModelEntities;
22pub use types::*;
23
24use classifications::extract_classifications;
25use documents::extract_documents;
26use ifc_lite_core::{
27 build_entity_index, extract_length_unit_scale, DecodedEntity, EntityDecoder, EntityScanner,
28};
29use materials::extract_materials;
30use material_units::MaterialUnitContext;
31use metadata::extract_entity_metadata;
32use properties::extract_properties;
33use quantities::extract_quantities;
34use relationships::extract_relationships;
35use spatial::build_spatial_hierarchy;
36use std::sync::Arc;
37
38pub fn extract_data_model<T>(content: &T) -> DataModel
40where
41 T: AsRef<[u8]> + ?Sized,
42{
43 let content = content.as_ref();
44 let extract_start = std::time::Instant::now();
45 tracing::info!(
46 content_size = content.len(),
47 "Starting data model extraction"
48 );
49
50 let entity_index = Arc::new(build_entity_index(content));
52
53 let mut scanner = EntityScanner::new(content);
55 let mut all_entities: Vec<EntityJob> = Vec::new();
56 let mut total_entities = 0usize;
57
58 let mut last_id = 0u32;
59 let mut last_type = String::new();
60 let mut max_id = 0u32;
61 let mut last_end = 0usize;
62 let content_len = content.len();
63
64 while let Some((id, type_name, start, end)) = scanner.next_entity() {
65 total_entities += 1;
66 last_id = id;
67 last_type = type_name.to_string();
68 last_end = end;
69 if id > max_id {
70 max_id = id;
71 }
72 all_entities.push(EntityJob {
73 id,
74 type_name: type_name.to_string(),
75 start,
76 end,
77 });
78 }
79
80 let remaining_bytes = content_len.saturating_sub(last_end);
81 tracing::debug!(
82 total_entities = total_entities,
83 last_id = last_id,
84 max_id = max_id,
85 last_type = %last_type,
86 last_end = last_end,
87 content_len = content_len,
88 remaining_bytes = remaining_bytes,
89 "Scanned all entities"
90 );
91
92 if tracing::enabled!(tracing::Level::DEBUG) {
94 let sample_types: Vec<&str> = all_entities
95 .iter()
96 .take(20)
97 .map(|j| j.type_name.as_str())
98 .collect();
99 tracing::debug!(?sample_types, "Sample entity types from scan");
100
101 let has_property_like = all_entities
103 .iter()
104 .any(|j| j.type_name.to_uppercase().contains("PROPERTY"));
105 let has_rel_like = all_entities
106 .iter()
107 .any(|j| j.type_name.to_uppercase().starts_with("IFCREL"));
108 tracing::debug!(
109 has_property_like = has_property_like,
110 has_rel_like = has_rel_like,
111 "Entity type pattern check"
112 );
113
114 let pset_count = all_entities
116 .iter()
117 .filter(|j| j.type_name.to_uppercase() == "IFCPROPERTYSET")
118 .count();
119 let rel_count = all_entities
120 .iter()
121 .filter(|j| {
122 let t = j.type_name.to_uppercase();
123 t == "IFCRELDEFINESBYPROPERTIES"
124 || t == "IFCRELAGGREGATES"
125 || t == "IFCRELCONTAINEDINSPATIALSTRUCTURE"
126 })
127 .count();
128 tracing::debug!(
129 pset_count = pset_count,
130 rel_count = rel_count,
131 "Entity type counts before extraction"
132 );
133 }
134
135 let content_arc = Arc::new(content.to_vec());
137 let (entities, ((property_sets, quantity_sets), relationships)) = rayon::join(
138 || extract_entity_metadata(&all_entities, &content_arc, &entity_index),
139 || {
140 rayon::join(
141 || {
142 rayon::join(
143 || extract_properties(&all_entities, &content_arc, &entity_index),
144 || extract_quantities(&all_entities, &content_arc, &entity_index),
145 )
146 },
147 || extract_relationships(&all_entities, &content_arc, &entity_index),
148 )
149 },
150 );
151
152 let mut unit_decoder = EntityDecoder::with_arc_index(content, entity_index.clone());
154 let project_id_for_units = entities
155 .iter()
156 .find(|e| e.type_name.to_uppercase() == "IFCPROJECT")
157 .map(|e| e.entity_id)
158 .unwrap_or(0);
159 let length_unit_scale = if project_id_for_units > 0 {
160 extract_length_unit_scale(&mut unit_decoder, project_id_for_units).unwrap_or(1.0)
161 } else {
162 1.0
163 };
164 tracing::debug!(
165 length_unit_scale = length_unit_scale,
166 "Extracted length unit scale"
167 );
168 let material_units = MaterialUnitContext::new(
169 &entities, &relationships, &mut unit_decoder, length_unit_scale,
170 );
171
172 let ((classifications, materials), documents) = rayon::join(
178 || {
179 rayon::join(
180 || extract_classifications(&all_entities, &content_arc, &entity_index),
181 || {
182 extract_materials(
183 &all_entities,
184 &content_arc,
185 &entity_index,
186 &material_units,
187 )
188 },
189 )
190 },
191 || extract_documents(&all_entities, &content_arc, &entity_index),
192 );
193
194 let spatial_hierarchy = build_spatial_hierarchy(
196 &relationships,
197 &entities,
198 content,
199 &entity_index,
200 length_unit_scale,
201 );
202
203 let extract_time = extract_start.elapsed();
204 tracing::info!(
205 entities = entities.len(),
206 property_sets = property_sets.len(),
207 quantity_sets = quantity_sets.len(),
208 relationships = relationships.len(),
209 classifications = classifications.len(),
210 materials = materials.len(),
211 documents = documents.len(),
212 spatial_nodes = spatial_hierarchy.nodes.len(),
213 extract_time_ms = extract_time.as_millis(),
214 "Data model extraction complete"
215 );
216
217 DataModel {
218 entities,
219 property_sets,
220 quantity_sets,
221 relationships,
222 classifications,
223 materials,
224 documents,
225 spatial_hierarchy,
226 }
227}
228
229pub(super) fn read_logical(entity: &DecodedEntity, index: usize) -> Option<bool> {
232 let token = entity.get(index)?.as_enum()?;
233 match token {
234 "T" | "TRUE" | "true" => Some(true),
235 "F" | "FALSE" | "false" => Some(false),
236 _ => None,
237 }
238}
239
240pub(super) fn related_object_ids(rel: &DecodedEntity) -> Vec<u32> {
242 rel.get_list(4)
243 .map(|list| list.iter().filter_map(|v| v.as_entity_ref()).collect())
244 .unwrap_or_default()
245}
246
247
248#[cfg(test)]
249#[path = "tests.rs"]
250mod tests;