1use crate::generated::IfcType;
11use crate::parser::Token;
12use std::borrow::Cow;
13use std::collections::HashMap;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub enum GeometryCategory {
18 SweptSolid,
19 Boolean,
20 ExplicitMesh,
21 MappedItem,
22 Surface,
23 Curve,
24 Other,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
29pub enum ProfileCategory {
30 Parametric,
31 Arbitrary,
32 Composite,
33}
34
35#[derive(Debug, Clone)]
37pub enum AttributeValue {
38 EntityRef(u32),
40 String(String),
42 Integer(i64),
44 Float(f64),
46 Enum(String),
48 List(Vec<AttributeValue>),
50 Null,
52 Derived,
54}
55
56impl AttributeValue {
57 pub fn from_token(token: &Token) -> Self {
59 match token {
60 Token::EntityRef(id) => AttributeValue::EntityRef(*id),
61 Token::String(s) => {
62 let raw = String::from_utf8_lossy(s);
79 let undoubled = if raw.contains("''") {
80 Cow::Owned(raw.replace("''", "'"))
81 } else {
82 raw
83 };
84 AttributeValue::String(
85 crate::step_encoding::decode_ifc_string(&undoubled).into_owned(),
86 )
87 }
88 Token::Integer(i) => AttributeValue::Integer(*i),
89 Token::Float(f) => AttributeValue::Float(*f),
90 Token::Enum(e) => AttributeValue::Enum(String::from_utf8_lossy(e).into_owned()),
91 Token::List(items) => {
92 AttributeValue::List(items.iter().map(Self::from_token).collect())
93 }
94 Token::TypedValue(type_name, args) => {
95 let mut values = vec![AttributeValue::String(
98 String::from_utf8_lossy(type_name).into_owned(),
99 )];
100 values.extend(args.iter().map(Self::from_token));
101 AttributeValue::List(values)
102 }
103 Token::Null => AttributeValue::Null,
104 Token::Derived => AttributeValue::Derived,
105 }
106 }
107
108 #[inline]
110 pub fn as_entity_ref(&self) -> Option<u32> {
111 match self {
112 AttributeValue::EntityRef(id) => Some(*id),
113 _ => None,
114 }
115 }
116
117 #[inline]
119 pub fn as_string(&self) -> Option<&str> {
120 match self {
121 AttributeValue::String(s) => Some(s),
122 _ => None,
123 }
124 }
125
126 #[inline]
128 pub fn as_enum(&self) -> Option<&str> {
129 match self {
130 AttributeValue::Enum(s) => Some(s),
131 _ => None,
132 }
133 }
134
135 #[inline]
139 pub fn as_float(&self) -> Option<f64> {
140 match self {
141 AttributeValue::Float(f) => Some(*f),
142 AttributeValue::Integer(i) => Some(*i as f64),
143 AttributeValue::List(items) if items.len() >= 2 => {
145 if matches!(items.first(), Some(AttributeValue::String(_))) {
147 match items.get(1) {
149 Some(AttributeValue::Float(f)) => Some(*f),
150 Some(AttributeValue::Integer(i)) => Some(*i as f64),
151 _ => None,
152 }
153 } else {
154 None
155 }
156 }
157 _ => None,
158 }
159 }
160
161 #[inline]
163 pub fn as_int(&self) -> Option<i64> {
164 match self {
165 AttributeValue::Integer(i) => Some(*i),
166 AttributeValue::Float(f) => Some(*f as i64),
167 _ => None,
168 }
169 }
170
171 #[inline]
173 pub fn as_list(&self) -> Option<&[AttributeValue]> {
174 match self {
175 AttributeValue::List(items) => Some(items),
176 _ => None,
177 }
178 }
179
180 #[inline]
182 pub fn is_null(&self) -> bool {
183 matches!(self, AttributeValue::Null | AttributeValue::Derived)
184 }
185
186 #[inline]
190 pub fn parse_coordinate_list_3d(coord_list: &[AttributeValue]) -> Vec<f32> {
191 let mut result = Vec::with_capacity(coord_list.len() * 3);
192
193 for coord_attr in coord_list {
194 if let Some(coord) = coord_attr.as_list() {
195 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 let z = coord.get(2).and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
199
200 result.push(x);
201 result.push(y);
202 result.push(z);
203 }
204 }
205
206 result
207 }
208
209 #[inline]
212 pub fn parse_coordinate_list_2d(coord_list: &[AttributeValue]) -> Vec<f32> {
213 let mut result = Vec::with_capacity(coord_list.len() * 2);
214
215 for coord_attr in coord_list {
216 if let Some(coord) = coord_attr.as_list() {
217 let x = coord.first().and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
218 let y = coord.get(1).and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
219
220 result.push(x);
221 result.push(y);
222 }
223 }
224
225 result
226 }
227
228 #[inline]
232 pub fn parse_index_list(face_list: &[AttributeValue]) -> Vec<u32> {
233 let mut result = Vec::with_capacity(face_list.len() * 3);
234
235 let to_zero_based = |i: i64| -> u32 {
243 i.checked_sub(1)
244 .and_then(|z| u32::try_from(z).ok())
245 .unwrap_or(u32::MAX)
246 };
247
248 for face_attr in face_list {
249 if let Some(face) = face_attr.as_list() {
250 let i0 = to_zero_based(face.first().and_then(|v| v.as_int()).unwrap_or(1));
252 let i1 = to_zero_based(face.get(1).and_then(|v| v.as_int()).unwrap_or(1));
253 let i2 = to_zero_based(face.get(2).and_then(|v| v.as_int()).unwrap_or(1));
254
255 result.push(i0);
256 result.push(i1);
257 result.push(i2);
258 }
259 }
260
261 result
262 }
263
264 #[inline]
267 pub fn parse_coordinate_list_3d_f64(coord_list: &[AttributeValue]) -> Vec<(f64, f64, f64)> {
268 coord_list
269 .iter()
270 .filter_map(|coord_attr| {
271 let coord = coord_attr.as_list()?;
272 let x = coord.first().and_then(|v| v.as_float()).unwrap_or(0.0);
273 let y = coord.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
274 let z = coord.get(2).and_then(|v| v.as_float()).unwrap_or(0.0);
275 Some((x, y, z))
276 })
277 .collect()
278 }
279}
280
281#[derive(Debug, Clone)]
286pub struct DecodedEntity {
287 pub id: u32,
288 pub ifc_type: IfcType,
289 pub attributes: std::sync::Arc<Vec<AttributeValue>>,
290}
291
292impl DecodedEntity {
293 pub fn new(id: u32, ifc_type: IfcType, attributes: Vec<AttributeValue>) -> Self {
295 Self {
296 id,
297 ifc_type,
298 attributes: std::sync::Arc::new(attributes),
299 }
300 }
301
302 pub fn get(&self, index: usize) -> Option<&AttributeValue> {
304 self.attributes.get(index)
305 }
306
307 pub fn get_ref(&self, index: usize) -> Option<u32> {
309 self.get(index).and_then(|v| v.as_entity_ref())
310 }
311
312 pub fn get_string(&self, index: usize) -> Option<&str> {
314 self.get(index).and_then(|v| v.as_string())
315 }
316
317 pub fn get_float(&self, index: usize) -> Option<f64> {
319 self.get(index).and_then(|v| v.as_float())
320 }
321
322 pub fn get_list(&self, index: usize) -> Option<&[AttributeValue]> {
324 self.get(index).and_then(|v| v.as_list())
325 }
326}
327
328#[derive(Clone)]
330pub struct IfcSchema {
331 pub geometry_types: HashMap<IfcType, GeometryCategory>,
333 pub profile_types: HashMap<IfcType, ProfileCategory>,
335}
336
337impl IfcSchema {
338 pub fn new() -> Self {
340 let mut geometry_types = HashMap::new();
341 let mut profile_types = HashMap::new();
342
343 geometry_types.insert(IfcType::IfcExtrudedAreaSolid, GeometryCategory::SweptSolid);
345 geometry_types.insert(IfcType::IfcRevolvedAreaSolid, GeometryCategory::SweptSolid);
346
347 geometry_types.insert(IfcType::IfcBooleanResult, GeometryCategory::Boolean);
349 geometry_types.insert(IfcType::IfcBooleanClippingResult, GeometryCategory::Boolean);
350
351 geometry_types.insert(IfcType::IfcFacetedBrep, GeometryCategory::ExplicitMesh);
353 geometry_types.insert(
354 IfcType::IfcTriangulatedFaceSet,
355 GeometryCategory::ExplicitMesh,
356 );
357 geometry_types.insert(IfcType::IfcPolygonalFaceSet, GeometryCategory::ExplicitMesh);
358 geometry_types.insert(IfcType::IfcFaceBasedSurfaceModel, GeometryCategory::Surface);
359 geometry_types.insert(
360 IfcType::IfcSurfaceOfLinearExtrusion,
361 GeometryCategory::Surface,
362 );
363 geometry_types.insert(
364 IfcType::IfcShellBasedSurfaceModel,
365 GeometryCategory::Surface,
366 );
367
368 geometry_types.insert(IfcType::IfcMappedItem, GeometryCategory::MappedItem);
370
371 profile_types.insert(IfcType::IfcRectangleProfileDef, ProfileCategory::Parametric);
373 profile_types.insert(
374 IfcType::IfcRoundedRectangleProfileDef,
375 ProfileCategory::Parametric,
376 );
377 profile_types.insert(IfcType::IfcCircleProfileDef, ProfileCategory::Parametric);
378 profile_types.insert(
379 IfcType::IfcCircleHollowProfileDef,
380 ProfileCategory::Parametric,
381 );
382 profile_types.insert(
383 IfcType::IfcRectangleHollowProfileDef,
384 ProfileCategory::Parametric,
385 );
386 profile_types.insert(IfcType::IfcIShapeProfileDef, ProfileCategory::Parametric);
387 profile_types.insert(
388 IfcType::IfcAsymmetricIShapeProfileDef,
389 ProfileCategory::Parametric,
390 );
391 profile_types.insert(IfcType::IfcLShapeProfileDef, ProfileCategory::Parametric);
392 profile_types.insert(IfcType::IfcUShapeProfileDef, ProfileCategory::Parametric);
393 profile_types.insert(IfcType::IfcTShapeProfileDef, ProfileCategory::Parametric);
394 profile_types.insert(IfcType::IfcCShapeProfileDef, ProfileCategory::Parametric);
395 profile_types.insert(IfcType::IfcZShapeProfileDef, ProfileCategory::Parametric);
396
397 profile_types.insert(
399 IfcType::IfcArbitraryClosedProfileDef,
400 ProfileCategory::Arbitrary,
401 );
402 profile_types.insert(
403 IfcType::IfcArbitraryProfileDefWithVoids,
404 ProfileCategory::Arbitrary,
405 );
406
407 profile_types.insert(IfcType::IfcCompositeProfileDef, ProfileCategory::Composite);
409
410 Self {
411 geometry_types,
412 profile_types,
413 }
414 }
415
416 pub fn geometry_category(&self, ifc_type: &IfcType) -> Option<GeometryCategory> {
418 self.geometry_types.get(ifc_type).copied()
419 }
420
421 pub fn profile_category(&self, ifc_type: &IfcType) -> Option<ProfileCategory> {
423 self.profile_types.get(ifc_type).copied()
424 }
425
426 pub fn is_geometry_type(&self, ifc_type: &IfcType) -> bool {
428 self.geometry_types.contains_key(ifc_type)
429 }
430
431 pub fn is_profile_type(&self, ifc_type: &IfcType) -> bool {
433 self.profile_types.contains_key(ifc_type)
434 }
435
436 pub fn has_geometry(&self, ifc_type: &IfcType) -> bool {
438 let name = ifc_type.name();
440 (matches!(
441 ifc_type,
442 IfcType::IfcWall
443 | IfcType::IfcWallStandardCase
444 | IfcType::IfcSlab
445 | IfcType::IfcBeam
446 | IfcType::IfcColumn
447 | IfcType::IfcRoof
448 | IfcType::IfcStair
449 | IfcType::IfcRamp
450 | IfcType::IfcRailing
451 | IfcType::IfcPlate
452 | IfcType::IfcMember
453 | IfcType::IfcFooting
454 | IfcType::IfcPile
455 | IfcType::IfcCovering
456 | IfcType::IfcCurtainWall
457 | IfcType::IfcDoor
458 | IfcType::IfcWindow
459 | IfcType::IfcChimney
460 | IfcType::IfcShadingDevice
461 | IfcType::IfcBuildingElementProxy
462 | IfcType::IfcBuildingElementPart
463 ) || name.contains("Reinforc"))
464 || matches!(
465 ifc_type,
466 IfcType::IfcFurnishingElement
467 | IfcType::IfcFurniture
468 | IfcType::IfcDuctSegment
469 | IfcType::IfcPipeSegment
470 | IfcType::IfcCableSegment
471 | IfcType::IfcProduct | IfcType::IfcDistributionElement
473 | IfcType::IfcFlowSegment
474 | IfcType::IfcFlowFitting
475 | IfcType::IfcFlowTerminal
476 )
477 || matches!(
479 ifc_type,
480 IfcType::IfcSpace
481 | IfcType::IfcOpeningElement
482 | IfcType::IfcSite
483 )
484 }
485}
486
487impl Default for IfcSchema {
488 fn default() -> Self {
489 Self::new()
490 }
491}
492
493#[cfg(test)]
497#[path = "schema_gen_tests.rs"]
498mod tests;