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 name = String::from_utf8_lossy(type_name);
104 let mut values = vec![AttributeValue::String(
105 crate::schema_helpers::normalise_uppercase(&name).into_owned(),
106 )];
107 values.extend(args.iter().map(Self::from_token));
108 AttributeValue::List(values)
109 }
110 Token::Null => AttributeValue::Null,
111 Token::Derived => AttributeValue::Derived,
112 }
113 }
114
115 #[inline]
117 pub fn as_entity_ref(&self) -> Option<u32> {
118 match self {
119 AttributeValue::EntityRef(id) => Some(*id),
120 _ => None,
121 }
122 }
123
124 #[inline]
126 pub fn as_string(&self) -> Option<&str> {
127 match self {
128 AttributeValue::String(s) => Some(s),
129 _ => None,
130 }
131 }
132
133 #[inline]
135 pub fn as_enum(&self) -> Option<&str> {
136 match self {
137 AttributeValue::Enum(s) => Some(s),
138 _ => None,
139 }
140 }
141
142 #[inline]
146 pub fn as_float(&self) -> Option<f64> {
147 match self {
148 AttributeValue::Float(f) => Some(*f),
149 AttributeValue::Integer(i) => Some(*i as f64),
150 AttributeValue::List(items) if items.len() >= 2 => {
152 if matches!(items.first(), Some(AttributeValue::String(_))) {
154 match items.get(1) {
156 Some(AttributeValue::Float(f)) => Some(*f),
157 Some(AttributeValue::Integer(i)) => Some(*i as f64),
158 _ => None,
159 }
160 } else {
161 None
162 }
163 }
164 _ => None,
165 }
166 }
167
168 #[inline]
170 pub fn as_int(&self) -> Option<i64> {
171 match self {
172 AttributeValue::Integer(i) => Some(*i),
173 AttributeValue::Float(f) => Some(*f as i64),
174 _ => None,
175 }
176 }
177
178 #[inline]
180 pub fn as_list(&self) -> Option<&[AttributeValue]> {
181 match self {
182 AttributeValue::List(items) => Some(items),
183 _ => None,
184 }
185 }
186
187 #[inline]
189 pub fn is_null(&self) -> bool {
190 matches!(self, AttributeValue::Null | AttributeValue::Derived)
191 }
192
193 #[inline]
197 pub fn parse_coordinate_list_3d(coord_list: &[AttributeValue]) -> Vec<f32> {
198 let mut result = Vec::with_capacity(coord_list.len() * 3);
199
200 for coord_attr in coord_list {
201 if let Some(coord) = coord_attr.as_list() {
202 let x = coord.first().and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
204 let y = coord.get(1).and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
205 let z = coord.get(2).and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
206
207 result.push(x);
208 result.push(y);
209 result.push(z);
210 }
211 }
212
213 result
214 }
215
216 #[inline]
219 pub fn parse_coordinate_list_2d(coord_list: &[AttributeValue]) -> Vec<f32> {
220 let mut result = Vec::with_capacity(coord_list.len() * 2);
221
222 for coord_attr in coord_list {
223 if let Some(coord) = coord_attr.as_list() {
224 let x = coord.first().and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
225 let y = coord.get(1).and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
226
227 result.push(x);
228 result.push(y);
229 }
230 }
231
232 result
233 }
234
235 #[inline]
239 pub fn parse_index_list(face_list: &[AttributeValue]) -> Vec<u32> {
240 let mut result = Vec::with_capacity(face_list.len() * 3);
241
242 let to_zero_based = |i: i64| -> u32 {
250 i.checked_sub(1)
251 .and_then(|z| u32::try_from(z).ok())
252 .unwrap_or(u32::MAX)
253 };
254
255 for face_attr in face_list {
256 if let Some(face) = face_attr.as_list() {
257 let i0 = to_zero_based(face.first().and_then(|v| v.as_int()).unwrap_or(1));
259 let i1 = to_zero_based(face.get(1).and_then(|v| v.as_int()).unwrap_or(1));
260 let i2 = to_zero_based(face.get(2).and_then(|v| v.as_int()).unwrap_or(1));
261
262 result.push(i0);
263 result.push(i1);
264 result.push(i2);
265 }
266 }
267
268 result
269 }
270
271 #[inline]
274 pub fn parse_coordinate_list_3d_f64(coord_list: &[AttributeValue]) -> Vec<(f64, f64, f64)> {
275 coord_list
276 .iter()
277 .filter_map(|coord_attr| {
278 let coord = coord_attr.as_list()?;
279 let x = coord.first().and_then(|v| v.as_float()).unwrap_or(0.0);
280 let y = coord.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
281 let z = coord.get(2).and_then(|v| v.as_float()).unwrap_or(0.0);
282 Some((x, y, z))
283 })
284 .collect()
285 }
286}
287
288#[derive(Debug, Clone)]
293pub struct DecodedEntity {
294 pub id: u32,
295 pub ifc_type: IfcType,
296 pub attributes: std::sync::Arc<Vec<AttributeValue>>,
297}
298
299impl DecodedEntity {
300 pub fn new(id: u32, ifc_type: IfcType, attributes: Vec<AttributeValue>) -> Self {
302 Self {
303 id,
304 ifc_type,
305 attributes: std::sync::Arc::new(attributes),
306 }
307 }
308
309 pub fn get(&self, index: usize) -> Option<&AttributeValue> {
311 self.attributes.get(index)
312 }
313
314 pub fn get_ref(&self, index: usize) -> Option<u32> {
316 self.get(index).and_then(|v| v.as_entity_ref())
317 }
318
319 pub fn get_string(&self, index: usize) -> Option<&str> {
321 self.get(index).and_then(|v| v.as_string())
322 }
323
324 pub fn get_float(&self, index: usize) -> Option<f64> {
326 self.get(index).and_then(|v| v.as_float())
327 }
328
329 pub fn get_list(&self, index: usize) -> Option<&[AttributeValue]> {
331 self.get(index).and_then(|v| v.as_list())
332 }
333
334 pub fn get_refs(&self, index: usize) -> Option<Vec<u32>> {
337 let attr = self.get(index)?;
338 let refs = match attr.as_list() {
339 Some(list) => list.iter().filter_map(AttributeValue::as_entity_ref).collect(),
340 None => vec![attr.as_entity_ref()?],
341 };
342 (!refs.is_empty()).then_some(refs)
343 }
344}
345
346#[derive(Clone)]
348pub struct IfcSchema {
349 pub geometry_types: HashMap<IfcType, GeometryCategory>,
351 pub profile_types: HashMap<IfcType, ProfileCategory>,
353}
354
355impl IfcSchema {
356 pub fn new() -> Self {
358 let mut geometry_types = HashMap::new();
359 let mut profile_types = HashMap::new();
360
361 geometry_types.insert(IfcType::IfcExtrudedAreaSolid, GeometryCategory::SweptSolid);
363 geometry_types.insert(IfcType::IfcRevolvedAreaSolid, GeometryCategory::SweptSolid);
364
365 geometry_types.insert(IfcType::IfcBooleanResult, GeometryCategory::Boolean);
367 geometry_types.insert(IfcType::IfcBooleanClippingResult, GeometryCategory::Boolean);
368
369 geometry_types.insert(IfcType::IfcFacetedBrep, GeometryCategory::ExplicitMesh);
371 geometry_types.insert(
372 IfcType::IfcTriangulatedFaceSet,
373 GeometryCategory::ExplicitMesh,
374 );
375 geometry_types.insert(IfcType::IfcPolygonalFaceSet, GeometryCategory::ExplicitMesh);
376 geometry_types.insert(IfcType::IfcFaceBasedSurfaceModel, GeometryCategory::Surface);
377 geometry_types.insert(
378 IfcType::IfcSurfaceOfLinearExtrusion,
379 GeometryCategory::Surface,
380 );
381 geometry_types.insert(
382 IfcType::IfcShellBasedSurfaceModel,
383 GeometryCategory::Surface,
384 );
385
386 geometry_types.insert(IfcType::IfcMappedItem, GeometryCategory::MappedItem);
388
389 profile_types.insert(IfcType::IfcRectangleProfileDef, ProfileCategory::Parametric);
391 profile_types.insert(
392 IfcType::IfcRoundedRectangleProfileDef,
393 ProfileCategory::Parametric,
394 );
395 profile_types.insert(IfcType::IfcCircleProfileDef, ProfileCategory::Parametric);
396 profile_types.insert(
397 IfcType::IfcCircleHollowProfileDef,
398 ProfileCategory::Parametric,
399 );
400 profile_types.insert(
401 IfcType::IfcRectangleHollowProfileDef,
402 ProfileCategory::Parametric,
403 );
404 profile_types.insert(IfcType::IfcIShapeProfileDef, ProfileCategory::Parametric);
405 profile_types.insert(
406 IfcType::IfcAsymmetricIShapeProfileDef,
407 ProfileCategory::Parametric,
408 );
409 profile_types.insert(IfcType::IfcLShapeProfileDef, ProfileCategory::Parametric);
410 profile_types.insert(IfcType::IfcUShapeProfileDef, ProfileCategory::Parametric);
411 profile_types.insert(IfcType::IfcTShapeProfileDef, ProfileCategory::Parametric);
412 profile_types.insert(IfcType::IfcCShapeProfileDef, ProfileCategory::Parametric);
413 profile_types.insert(IfcType::IfcZShapeProfileDef, ProfileCategory::Parametric);
414
415 profile_types.insert(
417 IfcType::IfcArbitraryClosedProfileDef,
418 ProfileCategory::Arbitrary,
419 );
420 profile_types.insert(
421 IfcType::IfcArbitraryProfileDefWithVoids,
422 ProfileCategory::Arbitrary,
423 );
424
425 profile_types.insert(IfcType::IfcCompositeProfileDef, ProfileCategory::Composite);
427
428 Self {
429 geometry_types,
430 profile_types,
431 }
432 }
433
434 pub fn geometry_category(&self, ifc_type: &IfcType) -> Option<GeometryCategory> {
436 self.geometry_types.get(ifc_type).copied()
437 }
438
439 pub fn profile_category(&self, ifc_type: &IfcType) -> Option<ProfileCategory> {
441 self.profile_types.get(ifc_type).copied()
442 }
443
444 pub fn is_geometry_type(&self, ifc_type: &IfcType) -> bool {
446 self.geometry_types.contains_key(ifc_type)
447 }
448
449 pub fn is_profile_type(&self, ifc_type: &IfcType) -> bool {
451 self.profile_types.contains_key(ifc_type)
452 }
453}
454
455impl Default for IfcSchema {
456 fn default() -> Self {
457 Self::new()
458 }
459}
460
461#[cfg(test)]
465#[path = "schema_gen_tests.rs"]
466mod tests;