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) => {
61 let raw = String::from_utf8_lossy(s);
65 AttributeValue::String(crate::step_encoding::decode_ifc_string(&raw).into_owned())
66 }
67 Token::Integer(i) => AttributeValue::Integer(*i),
68 Token::Float(f) => AttributeValue::Float(*f),
69 Token::Enum(e) => AttributeValue::Enum(String::from_utf8_lossy(e).into_owned()),
70 Token::List(items) => {
71 AttributeValue::List(items.iter().map(Self::from_token).collect())
72 }
73 Token::TypedValue(type_name, args) => {
74 let mut values = vec![AttributeValue::String(
77 String::from_utf8_lossy(type_name).into_owned(),
78 )];
79 values.extend(args.iter().map(Self::from_token));
80 AttributeValue::List(values)
81 }
82 Token::Null => AttributeValue::Null,
83 Token::Derived => AttributeValue::Derived,
84 }
85 }
86
87 #[inline]
89 pub fn as_entity_ref(&self) -> Option<u32> {
90 match self {
91 AttributeValue::EntityRef(id) => Some(*id),
92 _ => None,
93 }
94 }
95
96 #[inline]
98 pub fn as_string(&self) -> Option<&str> {
99 match self {
100 AttributeValue::String(s) => Some(s),
101 _ => None,
102 }
103 }
104
105 #[inline]
107 pub fn as_enum(&self) -> Option<&str> {
108 match self {
109 AttributeValue::Enum(s) => Some(s),
110 _ => None,
111 }
112 }
113
114 #[inline]
118 pub fn as_float(&self) -> Option<f64> {
119 match self {
120 AttributeValue::Float(f) => Some(*f),
121 AttributeValue::Integer(i) => Some(*i as f64),
122 AttributeValue::List(items) if items.len() >= 2 => {
124 if matches!(items.first(), Some(AttributeValue::String(_))) {
126 match items.get(1) {
128 Some(AttributeValue::Float(f)) => Some(*f),
129 Some(AttributeValue::Integer(i)) => Some(*i as f64),
130 _ => None,
131 }
132 } else {
133 None
134 }
135 }
136 _ => None,
137 }
138 }
139
140 #[inline]
142 pub fn as_int(&self) -> Option<i64> {
143 match self {
144 AttributeValue::Integer(i) => Some(*i),
145 AttributeValue::Float(f) => Some(*f as i64),
146 _ => None,
147 }
148 }
149
150 #[inline]
152 pub fn as_list(&self) -> Option<&[AttributeValue]> {
153 match self {
154 AttributeValue::List(items) => Some(items),
155 _ => None,
156 }
157 }
158
159 #[inline]
161 pub fn is_null(&self) -> bool {
162 matches!(self, AttributeValue::Null | AttributeValue::Derived)
163 }
164
165 #[inline]
169 pub fn parse_coordinate_list_3d(coord_list: &[AttributeValue]) -> Vec<f32> {
170 let mut result = Vec::with_capacity(coord_list.len() * 3);
171
172 for coord_attr in coord_list {
173 if let Some(coord) = coord_attr.as_list() {
174 let x = coord.first().and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
176 let y = coord.get(1).and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
177 let z = coord.get(2).and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
178
179 result.push(x);
180 result.push(y);
181 result.push(z);
182 }
183 }
184
185 result
186 }
187
188 #[inline]
191 pub fn parse_coordinate_list_2d(coord_list: &[AttributeValue]) -> Vec<f32> {
192 let mut result = Vec::with_capacity(coord_list.len() * 2);
193
194 for coord_attr in coord_list {
195 if let Some(coord) = coord_attr.as_list() {
196 let x = coord.first().and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
197 let y = coord.get(1).and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
198
199 result.push(x);
200 result.push(y);
201 }
202 }
203
204 result
205 }
206
207 #[inline]
211 pub fn parse_index_list(face_list: &[AttributeValue]) -> Vec<u32> {
212 let mut result = Vec::with_capacity(face_list.len() * 3);
213
214 let to_zero_based = |i: i64| -> u32 {
222 i.checked_sub(1)
223 .and_then(|z| u32::try_from(z).ok())
224 .unwrap_or(u32::MAX)
225 };
226
227 for face_attr in face_list {
228 if let Some(face) = face_attr.as_list() {
229 let i0 = to_zero_based(face.first().and_then(|v| v.as_int()).unwrap_or(1));
231 let i1 = to_zero_based(face.get(1).and_then(|v| v.as_int()).unwrap_or(1));
232 let i2 = to_zero_based(face.get(2).and_then(|v| v.as_int()).unwrap_or(1));
233
234 result.push(i0);
235 result.push(i1);
236 result.push(i2);
237 }
238 }
239
240 result
241 }
242
243 #[inline]
246 pub fn parse_coordinate_list_3d_f64(coord_list: &[AttributeValue]) -> Vec<(f64, f64, f64)> {
247 coord_list
248 .iter()
249 .filter_map(|coord_attr| {
250 let coord = coord_attr.as_list()?;
251 let x = coord.first().and_then(|v| v.as_float()).unwrap_or(0.0);
252 let y = coord.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
253 let z = coord.get(2).and_then(|v| v.as_float()).unwrap_or(0.0);
254 Some((x, y, z))
255 })
256 .collect()
257 }
258}
259
260#[derive(Debug, Clone)]
265pub struct DecodedEntity {
266 pub id: u32,
267 pub ifc_type: IfcType,
268 pub attributes: std::sync::Arc<Vec<AttributeValue>>,
269}
270
271impl DecodedEntity {
272 pub fn new(id: u32, ifc_type: IfcType, attributes: Vec<AttributeValue>) -> Self {
274 Self {
275 id,
276 ifc_type,
277 attributes: std::sync::Arc::new(attributes),
278 }
279 }
280
281 pub fn get(&self, index: usize) -> Option<&AttributeValue> {
283 self.attributes.get(index)
284 }
285
286 pub fn get_ref(&self, index: usize) -> Option<u32> {
288 self.get(index).and_then(|v| v.as_entity_ref())
289 }
290
291 pub fn get_string(&self, index: usize) -> Option<&str> {
293 self.get(index).and_then(|v| v.as_string())
294 }
295
296 pub fn get_float(&self, index: usize) -> Option<f64> {
298 self.get(index).and_then(|v| v.as_float())
299 }
300
301 pub fn get_list(&self, index: usize) -> Option<&[AttributeValue]> {
303 self.get(index).and_then(|v| v.as_list())
304 }
305}
306
307#[derive(Clone)]
309pub struct IfcSchema {
310 pub geometry_types: HashMap<IfcType, GeometryCategory>,
312 pub profile_types: HashMap<IfcType, ProfileCategory>,
314}
315
316impl IfcSchema {
317 pub fn new() -> Self {
319 let mut geometry_types = HashMap::new();
320 let mut profile_types = HashMap::new();
321
322 geometry_types.insert(IfcType::IfcExtrudedAreaSolid, GeometryCategory::SweptSolid);
324 geometry_types.insert(IfcType::IfcRevolvedAreaSolid, GeometryCategory::SweptSolid);
325
326 geometry_types.insert(IfcType::IfcBooleanResult, GeometryCategory::Boolean);
328 geometry_types.insert(IfcType::IfcBooleanClippingResult, GeometryCategory::Boolean);
329
330 geometry_types.insert(IfcType::IfcFacetedBrep, GeometryCategory::ExplicitMesh);
332 geometry_types.insert(
333 IfcType::IfcTriangulatedFaceSet,
334 GeometryCategory::ExplicitMesh,
335 );
336 geometry_types.insert(IfcType::IfcPolygonalFaceSet, GeometryCategory::ExplicitMesh);
337 geometry_types.insert(IfcType::IfcFaceBasedSurfaceModel, GeometryCategory::Surface);
338 geometry_types.insert(
339 IfcType::IfcSurfaceOfLinearExtrusion,
340 GeometryCategory::Surface,
341 );
342 geometry_types.insert(
343 IfcType::IfcShellBasedSurfaceModel,
344 GeometryCategory::Surface,
345 );
346
347 geometry_types.insert(IfcType::IfcMappedItem, GeometryCategory::MappedItem);
349
350 profile_types.insert(IfcType::IfcRectangleProfileDef, ProfileCategory::Parametric);
352 profile_types.insert(
353 IfcType::IfcRoundedRectangleProfileDef,
354 ProfileCategory::Parametric,
355 );
356 profile_types.insert(IfcType::IfcCircleProfileDef, ProfileCategory::Parametric);
357 profile_types.insert(
358 IfcType::IfcCircleHollowProfileDef,
359 ProfileCategory::Parametric,
360 );
361 profile_types.insert(
362 IfcType::IfcRectangleHollowProfileDef,
363 ProfileCategory::Parametric,
364 );
365 profile_types.insert(IfcType::IfcIShapeProfileDef, ProfileCategory::Parametric);
366 profile_types.insert(
367 IfcType::IfcAsymmetricIShapeProfileDef,
368 ProfileCategory::Parametric,
369 );
370 profile_types.insert(IfcType::IfcLShapeProfileDef, ProfileCategory::Parametric);
371 profile_types.insert(IfcType::IfcUShapeProfileDef, ProfileCategory::Parametric);
372 profile_types.insert(IfcType::IfcTShapeProfileDef, ProfileCategory::Parametric);
373 profile_types.insert(IfcType::IfcCShapeProfileDef, ProfileCategory::Parametric);
374 profile_types.insert(IfcType::IfcZShapeProfileDef, ProfileCategory::Parametric);
375
376 profile_types.insert(
378 IfcType::IfcArbitraryClosedProfileDef,
379 ProfileCategory::Arbitrary,
380 );
381 profile_types.insert(
382 IfcType::IfcArbitraryProfileDefWithVoids,
383 ProfileCategory::Arbitrary,
384 );
385
386 profile_types.insert(IfcType::IfcCompositeProfileDef, ProfileCategory::Composite);
388
389 Self {
390 geometry_types,
391 profile_types,
392 }
393 }
394
395 pub fn geometry_category(&self, ifc_type: &IfcType) -> Option<GeometryCategory> {
397 self.geometry_types.get(ifc_type).copied()
398 }
399
400 pub fn profile_category(&self, ifc_type: &IfcType) -> Option<ProfileCategory> {
402 self.profile_types.get(ifc_type).copied()
403 }
404
405 pub fn is_geometry_type(&self, ifc_type: &IfcType) -> bool {
407 self.geometry_types.contains_key(ifc_type)
408 }
409
410 pub fn is_profile_type(&self, ifc_type: &IfcType) -> bool {
412 self.profile_types.contains_key(ifc_type)
413 }
414
415 pub fn has_geometry(&self, ifc_type: &IfcType) -> bool {
417 let name = ifc_type.name();
419 (matches!(
420 ifc_type,
421 IfcType::IfcWall
422 | IfcType::IfcWallStandardCase
423 | IfcType::IfcSlab
424 | IfcType::IfcBeam
425 | IfcType::IfcColumn
426 | IfcType::IfcRoof
427 | IfcType::IfcStair
428 | IfcType::IfcRamp
429 | IfcType::IfcRailing
430 | IfcType::IfcPlate
431 | IfcType::IfcMember
432 | IfcType::IfcFooting
433 | IfcType::IfcPile
434 | IfcType::IfcCovering
435 | IfcType::IfcCurtainWall
436 | IfcType::IfcDoor
437 | IfcType::IfcWindow
438 | IfcType::IfcChimney
439 | IfcType::IfcShadingDevice
440 | IfcType::IfcBuildingElementProxy
441 | IfcType::IfcBuildingElementPart
442 ) || name.contains("Reinforc"))
443 || matches!(
444 ifc_type,
445 IfcType::IfcFurnishingElement
446 | IfcType::IfcFurniture
447 | IfcType::IfcDuctSegment
448 | IfcType::IfcPipeSegment
449 | IfcType::IfcCableSegment
450 | IfcType::IfcProduct | IfcType::IfcDistributionElement
452 | IfcType::IfcFlowSegment
453 | IfcType::IfcFlowFitting
454 | IfcType::IfcFlowTerminal
455 )
456 || matches!(
458 ifc_type,
459 IfcType::IfcSpace
460 | IfcType::IfcOpeningElement
461 | IfcType::IfcSite
462 )
463 }
464}
465
466impl Default for IfcSchema {
467 fn default() -> Self {
468 Self::new()
469 }
470}
471
472#[cfg(test)]
476#[path = "schema_gen_tests.rs"]
477mod tests;