1use crate::generated::IfcType;
11use crate::parser::Token;
12use std::collections::HashMap;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub enum GeometryCategory {
17 SweptSolid,
18 Boolean,
19 ExplicitMesh,
20 MappedItem,
21 Surface,
22 Curve,
23 Other,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28pub enum ProfileCategory {
29 Parametric,
30 Arbitrary,
31 Composite,
32}
33
34#[derive(Debug, Clone)]
36pub enum AttributeValue {
37 EntityRef(u32),
39 String(String),
41 Integer(i64),
43 Float(f64),
45 Enum(String),
47 List(Vec<AttributeValue>),
49 Null,
51 Derived,
53}
54
55impl AttributeValue {
56 pub fn from_token(token: &Token) -> Self {
58 match token {
59 Token::EntityRef(id) => AttributeValue::EntityRef(*id),
60 Token::String(s) => AttributeValue::String(s.to_string()),
61 Token::Integer(i) => AttributeValue::Integer(*i),
62 Token::Float(f) => AttributeValue::Float(*f),
63 Token::Enum(e) => AttributeValue::Enum(e.to_string()),
64 Token::List(items) => {
65 AttributeValue::List(items.iter().map(Self::from_token).collect())
66 }
67 Token::TypedValue(type_name, args) => {
68 let mut values = vec![AttributeValue::String(type_name.to_string())];
71 values.extend(args.iter().map(Self::from_token));
72 AttributeValue::List(values)
73 }
74 Token::Null => AttributeValue::Null,
75 Token::Derived => AttributeValue::Derived,
76 }
77 }
78
79 #[inline]
81 pub fn as_entity_ref(&self) -> Option<u32> {
82 match self {
83 AttributeValue::EntityRef(id) => Some(*id),
84 _ => None,
85 }
86 }
87
88 #[inline]
90 pub fn as_string(&self) -> Option<&str> {
91 match self {
92 AttributeValue::String(s) => Some(s),
93 _ => None,
94 }
95 }
96
97 #[inline]
99 pub fn as_enum(&self) -> Option<&str> {
100 match self {
101 AttributeValue::Enum(s) => Some(s),
102 _ => None,
103 }
104 }
105
106 #[inline]
110 pub fn as_float(&self) -> Option<f64> {
111 match self {
112 AttributeValue::Float(f) => Some(*f),
113 AttributeValue::Integer(i) => Some(*i as f64),
114 AttributeValue::List(items) if items.len() >= 2 => {
116 if matches!(items.first(), Some(AttributeValue::String(_))) {
118 match items.get(1) {
120 Some(AttributeValue::Float(f)) => Some(*f),
121 Some(AttributeValue::Integer(i)) => Some(*i as f64),
122 _ => None,
123 }
124 } else {
125 None
126 }
127 }
128 _ => None,
129 }
130 }
131
132 #[inline]
134 pub fn as_int(&self) -> Option<i64> {
135 match self {
136 AttributeValue::Integer(i) => Some(*i),
137 AttributeValue::Float(f) => Some(*f as i64),
138 _ => None,
139 }
140 }
141
142 #[inline]
144 pub fn as_list(&self) -> Option<&[AttributeValue]> {
145 match self {
146 AttributeValue::List(items) => Some(items),
147 _ => None,
148 }
149 }
150
151 #[inline]
153 pub fn is_null(&self) -> bool {
154 matches!(self, AttributeValue::Null | AttributeValue::Derived)
155 }
156
157 #[inline]
161 pub fn parse_coordinate_list_3d(coord_list: &[AttributeValue]) -> Vec<f32> {
162 let mut result = Vec::with_capacity(coord_list.len() * 3);
163
164 for coord_attr in coord_list {
165 if let Some(coord) = coord_attr.as_list() {
166 let x = coord.first().and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
168 let y = coord.get(1).and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
169 let z = coord.get(2).and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
170
171 result.push(x);
172 result.push(y);
173 result.push(z);
174 }
175 }
176
177 result
178 }
179
180 #[inline]
183 pub fn parse_coordinate_list_2d(coord_list: &[AttributeValue]) -> Vec<f32> {
184 let mut result = Vec::with_capacity(coord_list.len() * 2);
185
186 for coord_attr in coord_list {
187 if let Some(coord) = coord_attr.as_list() {
188 let x = coord.first().and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
189 let y = coord.get(1).and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
190
191 result.push(x);
192 result.push(y);
193 }
194 }
195
196 result
197 }
198
199 #[inline]
203 pub fn parse_index_list(face_list: &[AttributeValue]) -> Vec<u32> {
204 let mut result = Vec::with_capacity(face_list.len() * 3);
205
206 for face_attr in face_list {
207 if let Some(face) = face_attr.as_list() {
208 let i0 = (face.first().and_then(|v| v.as_int()).unwrap_or(1) - 1) as u32;
210 let i1 = (face.get(1).and_then(|v| v.as_int()).unwrap_or(1) - 1) as u32;
211 let i2 = (face.get(2).and_then(|v| v.as_int()).unwrap_or(1) - 1) as u32;
212
213 result.push(i0);
214 result.push(i1);
215 result.push(i2);
216 }
217 }
218
219 result
220 }
221
222 #[inline]
225 pub fn parse_coordinate_list_3d_f64(coord_list: &[AttributeValue]) -> Vec<(f64, f64, f64)> {
226 coord_list
227 .iter()
228 .filter_map(|coord_attr| {
229 let coord = coord_attr.as_list()?;
230 let x = coord.first().and_then(|v| v.as_float()).unwrap_or(0.0);
231 let y = coord.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
232 let z = coord.get(2).and_then(|v| v.as_float()).unwrap_or(0.0);
233 Some((x, y, z))
234 })
235 .collect()
236 }
237}
238
239#[derive(Debug, Clone)]
241pub struct DecodedEntity {
242 pub id: u32,
243 pub ifc_type: IfcType,
244 pub attributes: Vec<AttributeValue>,
245}
246
247impl DecodedEntity {
248 pub fn new(id: u32, ifc_type: IfcType, attributes: Vec<AttributeValue>) -> Self {
250 Self {
251 id,
252 ifc_type,
253 attributes,
254 }
255 }
256
257 pub fn get(&self, index: usize) -> Option<&AttributeValue> {
259 self.attributes.get(index)
260 }
261
262 pub fn get_ref(&self, index: usize) -> Option<u32> {
264 self.get(index).and_then(|v| v.as_entity_ref())
265 }
266
267 pub fn get_string(&self, index: usize) -> Option<&str> {
269 self.get(index).and_then(|v| v.as_string())
270 }
271
272 pub fn get_float(&self, index: usize) -> Option<f64> {
274 self.get(index).and_then(|v| v.as_float())
275 }
276
277 pub fn get_list(&self, index: usize) -> Option<&[AttributeValue]> {
279 self.get(index).and_then(|v| v.as_list())
280 }
281}
282
283#[derive(Clone)]
285pub struct IfcSchema {
286 pub geometry_types: HashMap<IfcType, GeometryCategory>,
288 pub profile_types: HashMap<IfcType, ProfileCategory>,
290}
291
292impl IfcSchema {
293 pub fn new() -> Self {
295 let mut geometry_types = HashMap::new();
296 let mut profile_types = HashMap::new();
297
298 geometry_types.insert(IfcType::IfcExtrudedAreaSolid, GeometryCategory::SweptSolid);
300 geometry_types.insert(IfcType::IfcRevolvedAreaSolid, GeometryCategory::SweptSolid);
301
302 geometry_types.insert(IfcType::IfcBooleanResult, GeometryCategory::Boolean);
304 geometry_types.insert(IfcType::IfcBooleanClippingResult, GeometryCategory::Boolean);
305
306 geometry_types.insert(IfcType::IfcFacetedBrep, GeometryCategory::ExplicitMesh);
308 geometry_types.insert(
309 IfcType::IfcTriangulatedFaceSet,
310 GeometryCategory::ExplicitMesh,
311 );
312 geometry_types.insert(IfcType::IfcPolygonalFaceSet, GeometryCategory::ExplicitMesh);
313 geometry_types.insert(IfcType::IfcFaceBasedSurfaceModel, GeometryCategory::Surface);
314 geometry_types.insert(
315 IfcType::IfcSurfaceOfLinearExtrusion,
316 GeometryCategory::Surface,
317 );
318 geometry_types.insert(
319 IfcType::IfcShellBasedSurfaceModel,
320 GeometryCategory::Surface,
321 );
322
323 geometry_types.insert(IfcType::IfcMappedItem, GeometryCategory::MappedItem);
325
326 profile_types.insert(IfcType::IfcRectangleProfileDef, ProfileCategory::Parametric);
328 profile_types.insert(IfcType::IfcCircleProfileDef, ProfileCategory::Parametric);
329 profile_types.insert(
330 IfcType::IfcCircleHollowProfileDef,
331 ProfileCategory::Parametric,
332 );
333 profile_types.insert(
334 IfcType::IfcRectangleHollowProfileDef,
335 ProfileCategory::Parametric,
336 );
337 profile_types.insert(IfcType::IfcIShapeProfileDef, ProfileCategory::Parametric);
338 profile_types.insert(IfcType::IfcLShapeProfileDef, ProfileCategory::Parametric);
339 profile_types.insert(IfcType::IfcUShapeProfileDef, ProfileCategory::Parametric);
340 profile_types.insert(IfcType::IfcTShapeProfileDef, ProfileCategory::Parametric);
341 profile_types.insert(IfcType::IfcCShapeProfileDef, ProfileCategory::Parametric);
342 profile_types.insert(IfcType::IfcZShapeProfileDef, ProfileCategory::Parametric);
343
344 profile_types.insert(
346 IfcType::IfcArbitraryClosedProfileDef,
347 ProfileCategory::Arbitrary,
348 );
349 profile_types.insert(
350 IfcType::IfcArbitraryProfileDefWithVoids,
351 ProfileCategory::Arbitrary,
352 );
353
354 profile_types.insert(IfcType::IfcCompositeProfileDef, ProfileCategory::Composite);
356
357 Self {
358 geometry_types,
359 profile_types,
360 }
361 }
362
363 pub fn geometry_category(&self, ifc_type: &IfcType) -> Option<GeometryCategory> {
365 self.geometry_types.get(ifc_type).copied()
366 }
367
368 pub fn profile_category(&self, ifc_type: &IfcType) -> Option<ProfileCategory> {
370 self.profile_types.get(ifc_type).copied()
371 }
372
373 pub fn is_geometry_type(&self, ifc_type: &IfcType) -> bool {
375 self.geometry_types.contains_key(ifc_type)
376 }
377
378 pub fn is_profile_type(&self, ifc_type: &IfcType) -> bool {
380 self.profile_types.contains_key(ifc_type)
381 }
382
383 pub fn has_geometry(&self, ifc_type: &IfcType) -> bool {
385 let name = ifc_type.name();
387 (matches!(
388 ifc_type,
389 IfcType::IfcWall
390 | IfcType::IfcWallStandardCase
391 | IfcType::IfcSlab
392 | IfcType::IfcBeam
393 | IfcType::IfcColumn
394 | IfcType::IfcRoof
395 | IfcType::IfcStair
396 | IfcType::IfcRamp
397 | IfcType::IfcRailing
398 | IfcType::IfcPlate
399 | IfcType::IfcMember
400 | IfcType::IfcFooting
401 | IfcType::IfcPile
402 | IfcType::IfcCovering
403 | IfcType::IfcCurtainWall
404 | IfcType::IfcDoor
405 | IfcType::IfcWindow
406 | IfcType::IfcChimney
407 | IfcType::IfcShadingDevice
408 | IfcType::IfcBuildingElementProxy
409 | IfcType::IfcBuildingElementPart
410 ) || name.contains("Reinforc"))
411 || matches!(
412 ifc_type,
413 IfcType::IfcFurnishingElement
414 | IfcType::IfcFurniture
415 | IfcType::IfcDuctSegment
416 | IfcType::IfcPipeSegment
417 | IfcType::IfcCableSegment
418 | IfcType::IfcProduct | IfcType::IfcDistributionElement
420 | IfcType::IfcFlowSegment
421 | IfcType::IfcFlowFitting
422 | IfcType::IfcFlowTerminal
423 )
424 || matches!(
426 ifc_type,
427 IfcType::IfcSpace
428 | IfcType::IfcOpeningElement
429 | IfcType::IfcSite
430 )
431 }
432}
433
434impl Default for IfcSchema {
435 fn default() -> Self {
436 Self::new()
437 }
438}
439
440#[cfg(test)]
444mod tests {
445 use super::*;
446
447 #[test]
448 fn test_schema_geometry_categories() {
449 let schema = IfcSchema::new();
450
451 assert_eq!(
452 schema.geometry_category(&IfcType::IfcExtrudedAreaSolid),
453 Some(GeometryCategory::SweptSolid)
454 );
455
456 assert_eq!(
457 schema.geometry_category(&IfcType::IfcBooleanResult),
458 Some(GeometryCategory::Boolean)
459 );
460
461 assert_eq!(
462 schema.geometry_category(&IfcType::IfcTriangulatedFaceSet),
463 Some(GeometryCategory::ExplicitMesh)
464 );
465 }
466
467 #[test]
468 fn test_attribute_value_conversion() {
469 let token = Token::EntityRef(123);
470 let attr = AttributeValue::from_token(&token);
471 assert_eq!(attr.as_entity_ref(), Some(123));
472
473 let token = Token::String("test");
474 let attr = AttributeValue::from_token(&token);
475 assert_eq!(attr.as_string(), Some("test"));
476 }
477
478 #[test]
479 fn test_decoded_entity() {
480 let entity = DecodedEntity::new(
481 1,
482 IfcType::IfcWall,
483 vec![
484 AttributeValue::EntityRef(2),
485 AttributeValue::String("Wall-001".to_string()),
486 AttributeValue::Float(3.5),
487 ],
488 );
489
490 assert_eq!(entity.get_ref(0), Some(2));
491 assert_eq!(entity.get_string(1), Some("Wall-001"));
492 assert_eq!(entity.get_float(2), Some(3.5));
493 }
494
495 #[test]
496 fn test_as_float_with_typed_value() {
497 let plain_float = AttributeValue::Float(0.5);
499 assert_eq!(plain_float.as_float(), Some(0.5));
500
501 let integer = AttributeValue::Integer(42);
503 assert_eq!(integer.as_float(), Some(42.0));
504
505 let typed_value = AttributeValue::List(vec![
508 AttributeValue::String("IFCNORMALISEDRATIOMEASURE".to_string()),
509 AttributeValue::Float(0.5),
510 ]);
511 assert_eq!(typed_value.as_float(), Some(0.5));
512
513 let typed_int = AttributeValue::List(vec![
515 AttributeValue::String("IFCINTEGER".to_string()),
516 AttributeValue::Integer(100),
517 ]);
518 assert_eq!(typed_int.as_float(), Some(100.0));
519
520 let regular_list =
522 AttributeValue::List(vec![AttributeValue::Float(1.0), AttributeValue::Float(2.0)]);
523 assert_eq!(regular_list.as_float(), None);
524
525 let empty_list = AttributeValue::List(vec![]);
527 assert_eq!(empty_list.as_float(), None);
528 }
529}