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(String::from_utf8_lossy(s).into_owned()),
61 Token::Integer(i) => AttributeValue::Integer(*i),
62 Token::Float(f) => AttributeValue::Float(*f),
63 Token::Enum(e) => AttributeValue::Enum(String::from_utf8_lossy(e).into_owned()),
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(
71 String::from_utf8_lossy(type_name).into_owned(),
72 )];
73 values.extend(args.iter().map(Self::from_token));
74 AttributeValue::List(values)
75 }
76 Token::Null => AttributeValue::Null,
77 Token::Derived => AttributeValue::Derived,
78 }
79 }
80
81 #[inline]
83 pub fn as_entity_ref(&self) -> Option<u32> {
84 match self {
85 AttributeValue::EntityRef(id) => Some(*id),
86 _ => None,
87 }
88 }
89
90 #[inline]
92 pub fn as_string(&self) -> Option<&str> {
93 match self {
94 AttributeValue::String(s) => Some(s),
95 _ => None,
96 }
97 }
98
99 #[inline]
101 pub fn as_enum(&self) -> Option<&str> {
102 match self {
103 AttributeValue::Enum(s) => Some(s),
104 _ => None,
105 }
106 }
107
108 #[inline]
112 pub fn as_float(&self) -> Option<f64> {
113 match self {
114 AttributeValue::Float(f) => Some(*f),
115 AttributeValue::Integer(i) => Some(*i as f64),
116 AttributeValue::List(items) if items.len() >= 2 => {
118 if matches!(items.first(), Some(AttributeValue::String(_))) {
120 match items.get(1) {
122 Some(AttributeValue::Float(f)) => Some(*f),
123 Some(AttributeValue::Integer(i)) => Some(*i as f64),
124 _ => None,
125 }
126 } else {
127 None
128 }
129 }
130 _ => None,
131 }
132 }
133
134 #[inline]
136 pub fn as_int(&self) -> Option<i64> {
137 match self {
138 AttributeValue::Integer(i) => Some(*i),
139 AttributeValue::Float(f) => Some(*f as i64),
140 _ => None,
141 }
142 }
143
144 #[inline]
146 pub fn as_list(&self) -> Option<&[AttributeValue]> {
147 match self {
148 AttributeValue::List(items) => Some(items),
149 _ => None,
150 }
151 }
152
153 #[inline]
155 pub fn is_null(&self) -> bool {
156 matches!(self, AttributeValue::Null | AttributeValue::Derived)
157 }
158
159 #[inline]
163 pub fn parse_coordinate_list_3d(coord_list: &[AttributeValue]) -> Vec<f32> {
164 let mut result = Vec::with_capacity(coord_list.len() * 3);
165
166 for coord_attr in coord_list {
167 if let Some(coord) = coord_attr.as_list() {
168 let x = coord.first().and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
170 let y = coord.get(1).and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
171 let z = coord.get(2).and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
172
173 result.push(x);
174 result.push(y);
175 result.push(z);
176 }
177 }
178
179 result
180 }
181
182 #[inline]
185 pub fn parse_coordinate_list_2d(coord_list: &[AttributeValue]) -> Vec<f32> {
186 let mut result = Vec::with_capacity(coord_list.len() * 2);
187
188 for coord_attr in coord_list {
189 if let Some(coord) = coord_attr.as_list() {
190 let x = coord.first().and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
191 let y = coord.get(1).and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
192
193 result.push(x);
194 result.push(y);
195 }
196 }
197
198 result
199 }
200
201 #[inline]
205 pub fn parse_index_list(face_list: &[AttributeValue]) -> Vec<u32> {
206 let mut result = Vec::with_capacity(face_list.len() * 3);
207
208 for face_attr in face_list {
209 if let Some(face) = face_attr.as_list() {
210 let i0 = (face.first().and_then(|v| v.as_int()).unwrap_or(1) - 1) as u32;
212 let i1 = (face.get(1).and_then(|v| v.as_int()).unwrap_or(1) - 1) as u32;
213 let i2 = (face.get(2).and_then(|v| v.as_int()).unwrap_or(1) - 1) as u32;
214
215 result.push(i0);
216 result.push(i1);
217 result.push(i2);
218 }
219 }
220
221 result
222 }
223
224 #[inline]
227 pub fn parse_coordinate_list_3d_f64(coord_list: &[AttributeValue]) -> Vec<(f64, f64, f64)> {
228 coord_list
229 .iter()
230 .filter_map(|coord_attr| {
231 let coord = coord_attr.as_list()?;
232 let x = coord.first().and_then(|v| v.as_float()).unwrap_or(0.0);
233 let y = coord.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
234 let z = coord.get(2).and_then(|v| v.as_float()).unwrap_or(0.0);
235 Some((x, y, z))
236 })
237 .collect()
238 }
239}
240
241#[derive(Debug, Clone)]
243pub struct DecodedEntity {
244 pub id: u32,
245 pub ifc_type: IfcType,
246 pub attributes: Vec<AttributeValue>,
247}
248
249impl DecodedEntity {
250 pub fn new(id: u32, ifc_type: IfcType, attributes: Vec<AttributeValue>) -> Self {
252 Self {
253 id,
254 ifc_type,
255 attributes,
256 }
257 }
258
259 pub fn get(&self, index: usize) -> Option<&AttributeValue> {
261 self.attributes.get(index)
262 }
263
264 pub fn get_ref(&self, index: usize) -> Option<u32> {
266 self.get(index).and_then(|v| v.as_entity_ref())
267 }
268
269 pub fn get_string(&self, index: usize) -> Option<&str> {
271 self.get(index).and_then(|v| v.as_string())
272 }
273
274 pub fn get_float(&self, index: usize) -> Option<f64> {
276 self.get(index).and_then(|v| v.as_float())
277 }
278
279 pub fn get_list(&self, index: usize) -> Option<&[AttributeValue]> {
281 self.get(index).and_then(|v| v.as_list())
282 }
283}
284
285#[derive(Clone)]
287pub struct IfcSchema {
288 pub geometry_types: HashMap<IfcType, GeometryCategory>,
290 pub profile_types: HashMap<IfcType, ProfileCategory>,
292}
293
294impl IfcSchema {
295 pub fn new() -> Self {
297 let mut geometry_types = HashMap::new();
298 let mut profile_types = HashMap::new();
299
300 geometry_types.insert(IfcType::IfcExtrudedAreaSolid, GeometryCategory::SweptSolid);
302 geometry_types.insert(IfcType::IfcRevolvedAreaSolid, GeometryCategory::SweptSolid);
303
304 geometry_types.insert(IfcType::IfcBooleanResult, GeometryCategory::Boolean);
306 geometry_types.insert(IfcType::IfcBooleanClippingResult, GeometryCategory::Boolean);
307
308 geometry_types.insert(IfcType::IfcFacetedBrep, GeometryCategory::ExplicitMesh);
310 geometry_types.insert(
311 IfcType::IfcTriangulatedFaceSet,
312 GeometryCategory::ExplicitMesh,
313 );
314 geometry_types.insert(IfcType::IfcPolygonalFaceSet, GeometryCategory::ExplicitMesh);
315 geometry_types.insert(IfcType::IfcFaceBasedSurfaceModel, GeometryCategory::Surface);
316 geometry_types.insert(
317 IfcType::IfcSurfaceOfLinearExtrusion,
318 GeometryCategory::Surface,
319 );
320 geometry_types.insert(
321 IfcType::IfcShellBasedSurfaceModel,
322 GeometryCategory::Surface,
323 );
324
325 geometry_types.insert(IfcType::IfcMappedItem, GeometryCategory::MappedItem);
327
328 profile_types.insert(IfcType::IfcRectangleProfileDef, ProfileCategory::Parametric);
330 profile_types.insert(
331 IfcType::IfcRoundedRectangleProfileDef,
332 ProfileCategory::Parametric,
333 );
334 profile_types.insert(IfcType::IfcCircleProfileDef, ProfileCategory::Parametric);
335 profile_types.insert(
336 IfcType::IfcCircleHollowProfileDef,
337 ProfileCategory::Parametric,
338 );
339 profile_types.insert(
340 IfcType::IfcRectangleHollowProfileDef,
341 ProfileCategory::Parametric,
342 );
343 profile_types.insert(IfcType::IfcIShapeProfileDef, ProfileCategory::Parametric);
344 profile_types.insert(
345 IfcType::IfcAsymmetricIShapeProfileDef,
346 ProfileCategory::Parametric,
347 );
348 profile_types.insert(IfcType::IfcLShapeProfileDef, ProfileCategory::Parametric);
349 profile_types.insert(IfcType::IfcUShapeProfileDef, ProfileCategory::Parametric);
350 profile_types.insert(IfcType::IfcTShapeProfileDef, ProfileCategory::Parametric);
351 profile_types.insert(IfcType::IfcCShapeProfileDef, ProfileCategory::Parametric);
352 profile_types.insert(IfcType::IfcZShapeProfileDef, ProfileCategory::Parametric);
353
354 profile_types.insert(
356 IfcType::IfcArbitraryClosedProfileDef,
357 ProfileCategory::Arbitrary,
358 );
359 profile_types.insert(
360 IfcType::IfcArbitraryProfileDefWithVoids,
361 ProfileCategory::Arbitrary,
362 );
363
364 profile_types.insert(IfcType::IfcCompositeProfileDef, ProfileCategory::Composite);
366
367 Self {
368 geometry_types,
369 profile_types,
370 }
371 }
372
373 pub fn geometry_category(&self, ifc_type: &IfcType) -> Option<GeometryCategory> {
375 self.geometry_types.get(ifc_type).copied()
376 }
377
378 pub fn profile_category(&self, ifc_type: &IfcType) -> Option<ProfileCategory> {
380 self.profile_types.get(ifc_type).copied()
381 }
382
383 pub fn is_geometry_type(&self, ifc_type: &IfcType) -> bool {
385 self.geometry_types.contains_key(ifc_type)
386 }
387
388 pub fn is_profile_type(&self, ifc_type: &IfcType) -> bool {
390 self.profile_types.contains_key(ifc_type)
391 }
392
393 pub fn has_geometry(&self, ifc_type: &IfcType) -> bool {
395 let name = ifc_type.name();
397 (matches!(
398 ifc_type,
399 IfcType::IfcWall
400 | IfcType::IfcWallStandardCase
401 | IfcType::IfcSlab
402 | IfcType::IfcBeam
403 | IfcType::IfcColumn
404 | IfcType::IfcRoof
405 | IfcType::IfcStair
406 | IfcType::IfcRamp
407 | IfcType::IfcRailing
408 | IfcType::IfcPlate
409 | IfcType::IfcMember
410 | IfcType::IfcFooting
411 | IfcType::IfcPile
412 | IfcType::IfcCovering
413 | IfcType::IfcCurtainWall
414 | IfcType::IfcDoor
415 | IfcType::IfcWindow
416 | IfcType::IfcChimney
417 | IfcType::IfcShadingDevice
418 | IfcType::IfcBuildingElementProxy
419 | IfcType::IfcBuildingElementPart
420 ) || name.contains("Reinforc"))
421 || matches!(
422 ifc_type,
423 IfcType::IfcFurnishingElement
424 | IfcType::IfcFurniture
425 | IfcType::IfcDuctSegment
426 | IfcType::IfcPipeSegment
427 | IfcType::IfcCableSegment
428 | IfcType::IfcProduct | IfcType::IfcDistributionElement
430 | IfcType::IfcFlowSegment
431 | IfcType::IfcFlowFitting
432 | IfcType::IfcFlowTerminal
433 )
434 || matches!(
436 ifc_type,
437 IfcType::IfcSpace
438 | IfcType::IfcOpeningElement
439 | IfcType::IfcSite
440 )
441 }
442}
443
444impl Default for IfcSchema {
445 fn default() -> Self {
446 Self::new()
447 }
448}
449
450#[cfg(test)]
454mod tests {
455 use super::*;
456
457 #[test]
458 fn test_schema_geometry_categories() {
459 let schema = IfcSchema::new();
460
461 assert_eq!(
462 schema.geometry_category(&IfcType::IfcExtrudedAreaSolid),
463 Some(GeometryCategory::SweptSolid)
464 );
465
466 assert_eq!(
467 schema.geometry_category(&IfcType::IfcBooleanResult),
468 Some(GeometryCategory::Boolean)
469 );
470
471 assert_eq!(
472 schema.geometry_category(&IfcType::IfcTriangulatedFaceSet),
473 Some(GeometryCategory::ExplicitMesh)
474 );
475
476 assert_eq!(
477 schema.profile_category(&IfcType::IfcRoundedRectangleProfileDef),
478 Some(ProfileCategory::Parametric)
479 );
480 }
481
482 #[test]
483 fn test_attribute_value_conversion() {
484 let token = Token::EntityRef(123);
485 let attr = AttributeValue::from_token(&token);
486 assert_eq!(attr.as_entity_ref(), Some(123));
487
488 let token = Token::String(b"test");
489 let attr = AttributeValue::from_token(&token);
490 assert_eq!(attr.as_string(), Some("test"));
491 }
492
493 #[test]
494 fn test_decoded_entity() {
495 let entity = DecodedEntity::new(
496 1,
497 IfcType::IfcWall,
498 vec![
499 AttributeValue::EntityRef(2),
500 AttributeValue::String("Wall-001".to_string()),
501 AttributeValue::Float(3.5),
502 ],
503 );
504
505 assert_eq!(entity.get_ref(0), Some(2));
506 assert_eq!(entity.get_string(1), Some("Wall-001"));
507 assert_eq!(entity.get_float(2), Some(3.5));
508 }
509
510 #[test]
511 fn test_as_float_with_typed_value() {
512 let plain_float = AttributeValue::Float(0.5);
514 assert_eq!(plain_float.as_float(), Some(0.5));
515
516 let integer = AttributeValue::Integer(42);
518 assert_eq!(integer.as_float(), Some(42.0));
519
520 let typed_value = AttributeValue::List(vec![
523 AttributeValue::String("IFCNORMALISEDRATIOMEASURE".to_string()),
524 AttributeValue::Float(0.5),
525 ]);
526 assert_eq!(typed_value.as_float(), Some(0.5));
527
528 let typed_int = AttributeValue::List(vec![
530 AttributeValue::String("IFCINTEGER".to_string()),
531 AttributeValue::Integer(100),
532 ]);
533 assert_eq!(typed_int.as_float(), Some(100.0));
534
535 let regular_list =
537 AttributeValue::List(vec![AttributeValue::Float(1.0), AttributeValue::Float(2.0)]);
538 assert_eq!(regular_list.as_float(), None);
539
540 let empty_list = AttributeValue::List(vec![]);
542 assert_eq!(empty_list.as_float(), None);
543 }
544}