ifc_lite_core/
schema_gen.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! IFC Schema - Dynamic type system
6//!
7//! Generated from IFC4 EXPRESS schema for maintainability.
8//! All types are handled generically through enum dispatch.
9
10use crate::schema::IfcType;
11use crate::parser::Token;
12use crate::error::{Error, Result};
13use std::collections::HashMap;
14
15/// IFC entity attribute value
16#[derive(Debug, Clone)]
17pub enum AttributeValue {
18    /// Entity reference
19    EntityRef(u32),
20    /// String value
21    String(String),
22    /// Integer value
23    Integer(i64),
24    /// Float value
25    Float(f64),
26    /// Enum value
27    Enum(String),
28    /// List of values
29    List(Vec<AttributeValue>),
30    /// Null/undefined
31    Null,
32    /// Derived value (*)
33    Derived,
34}
35
36impl AttributeValue {
37    /// Convert from Token
38    pub fn from_token(token: &Token) -> Self {
39        match token {
40            Token::EntityRef(id) => AttributeValue::EntityRef(*id),
41            Token::String(s) => AttributeValue::String(s.to_string()),
42            Token::Integer(i) => AttributeValue::Integer(*i),
43            Token::Float(f) => AttributeValue::Float(*f),
44            Token::Enum(e) => AttributeValue::Enum(e.to_string()),
45            Token::List(items) => {
46                AttributeValue::List(items.iter().map(Self::from_token).collect())
47            }
48            Token::TypedValue(type_name, args) => {
49                // For typed values like IFCPARAMETERVALUE(0.), extract the inner value
50                // Store as a list with the type name first, followed by args
51                let mut values = vec![AttributeValue::String(type_name.to_string())];
52                values.extend(args.iter().map(Self::from_token));
53                AttributeValue::List(values)
54            }
55            Token::Null => AttributeValue::Null,
56            Token::Derived => AttributeValue::Derived,
57        }
58    }
59
60    /// Get as entity reference
61    #[inline]
62    pub fn as_entity_ref(&self) -> Option<u32> {
63        match self {
64            AttributeValue::EntityRef(id) => Some(*id),
65            _ => None,
66        }
67    }
68
69    /// Get as string
70    #[inline]
71    pub fn as_string(&self) -> Option<&str> {
72        match self {
73            AttributeValue::String(s) => Some(s),
74            _ => None,
75        }
76    }
77
78    /// Get as float
79    #[inline]
80    pub fn as_float(&self) -> Option<f64> {
81        match self {
82            AttributeValue::Float(f) => Some(*f),
83            AttributeValue::Integer(i) => Some(*i as f64),
84            _ => None,
85        }
86    }
87
88    /// Get as integer (more efficient than as_float for indices)
89    #[inline]
90    pub fn as_int(&self) -> Option<i64> {
91        match self {
92            AttributeValue::Integer(i) => Some(*i),
93            AttributeValue::Float(f) => Some(*f as i64),
94            _ => None,
95        }
96    }
97
98    /// Get as list
99    #[inline]
100    pub fn as_list(&self) -> Option<&[AttributeValue]> {
101        match self {
102            AttributeValue::List(items) => Some(items),
103            _ => None,
104        }
105    }
106
107    /// Check if null/derived
108    #[inline]
109    pub fn is_null(&self) -> bool {
110        matches!(self, AttributeValue::Null | AttributeValue::Derived)
111    }
112
113    /// Batch parse 3D coordinates from a list of coordinate triples
114    /// Returns flattened f32 array: [x0, y0, z0, x1, y1, z1, ...]
115    /// Optimized for large coordinate lists
116    #[inline]
117    pub fn parse_coordinate_list_3d(coord_list: &[AttributeValue]) -> Vec<f32> {
118        let mut result = Vec::with_capacity(coord_list.len() * 3);
119
120        for coord_attr in coord_list {
121            if let Some(coord) = coord_attr.as_list() {
122                // Fast path: extract x, y, z directly
123                let x = coord.get(0).and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
124                let y = coord.get(1).and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
125                let z = coord.get(2).and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
126
127                result.push(x);
128                result.push(y);
129                result.push(z);
130            }
131        }
132
133        result
134    }
135
136    /// Batch parse 2D coordinates from a list of coordinate pairs
137    /// Returns flattened f32 array: [x0, y0, x1, y1, ...]
138    #[inline]
139    pub fn parse_coordinate_list_2d(coord_list: &[AttributeValue]) -> Vec<f32> {
140        let mut result = Vec::with_capacity(coord_list.len() * 2);
141
142        for coord_attr in coord_list {
143            if let Some(coord) = coord_attr.as_list() {
144                let x = coord.get(0).and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
145                let y = coord.get(1).and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
146
147                result.push(x);
148                result.push(y);
149            }
150        }
151
152        result
153    }
154
155    /// Batch parse triangle indices from a list of index triples
156    /// Converts from 1-based IFC indices to 0-based indices
157    /// Returns flattened u32 array: [i0, i1, i2, ...]
158    #[inline]
159    pub fn parse_index_list(face_list: &[AttributeValue]) -> Vec<u32> {
160        let mut result = Vec::with_capacity(face_list.len() * 3);
161
162        for face_attr in face_list {
163            if let Some(face) = face_attr.as_list() {
164                // Use as_int for faster parsing, convert from 1-based to 0-based
165                let i0 = (face.get(0).and_then(|v| v.as_int()).unwrap_or(1) - 1) as u32;
166                let i1 = (face.get(1).and_then(|v| v.as_int()).unwrap_or(1) - 1) as u32;
167                let i2 = (face.get(2).and_then(|v| v.as_int()).unwrap_or(1) - 1) as u32;
168
169                result.push(i0);
170                result.push(i1);
171                result.push(i2);
172            }
173        }
174
175        result
176    }
177
178    /// Batch parse coordinate list with f64 precision
179    /// Returns Vec of (x, y, z) tuples
180    #[inline]
181    pub fn parse_coordinate_list_3d_f64(coord_list: &[AttributeValue]) -> Vec<(f64, f64, f64)> {
182        coord_list.iter()
183            .filter_map(|coord_attr| {
184                let coord = coord_attr.as_list()?;
185                let x = coord.get(0).and_then(|v| v.as_float()).unwrap_or(0.0);
186                let y = coord.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
187                let z = coord.get(2).and_then(|v| v.as_float()).unwrap_or(0.0);
188                Some((x, y, z))
189            })
190            .collect()
191    }
192}
193
194/// Decoded IFC entity with attributes
195#[derive(Debug, Clone)]
196pub struct DecodedEntity {
197    pub id: u32,
198    pub ifc_type: IfcType,
199    pub attributes: Vec<AttributeValue>,
200}
201
202impl DecodedEntity {
203    /// Create new decoded entity
204    pub fn new(id: u32, ifc_type: IfcType, attributes: Vec<AttributeValue>) -> Self {
205        Self {
206            id,
207            ifc_type,
208            attributes,
209        }
210    }
211
212    /// Get attribute by index
213    pub fn get(&self, index: usize) -> Option<&AttributeValue> {
214        self.attributes.get(index)
215    }
216
217    /// Get entity reference attribute
218    pub fn get_ref(&self, index: usize) -> Option<u32> {
219        self.get(index).and_then(|v| v.as_entity_ref())
220    }
221
222    /// Get string attribute
223    pub fn get_string(&self, index: usize) -> Option<&str> {
224        self.get(index).and_then(|v| v.as_string())
225    }
226
227    /// Get float attribute
228    pub fn get_float(&self, index: usize) -> Option<f64> {
229        self.get(index).and_then(|v| v.as_float())
230    }
231
232    /// Get list attribute
233    pub fn get_list(&self, index: usize) -> Option<&[AttributeValue]> {
234        self.get(index).and_then(|v| v.as_list())
235    }
236}
237
238/// IFC schema metadata for dynamic processing
239#[derive(Clone)]
240pub struct IfcSchema {
241    /// Geometry representation types (for routing)
242    pub geometry_types: HashMap<IfcType, GeometryCategory>,
243    /// Profile types
244    pub profile_types: HashMap<IfcType, ProfileCategory>,
245}
246
247/// Geometry representation category
248#[derive(Debug, Clone, Copy, PartialEq, Eq)]
249pub enum GeometryCategory {
250    /// Swept solids (extrusion, revolution)
251    SweptSolid,
252    /// Boolean operations
253    Boolean,
254    /// Explicit meshes (Brep, triangulated)
255    ExplicitMesh,
256    /// Instanced geometry
257    MappedItem,
258    /// Other/unsupported
259    Other,
260}
261
262/// Profile category
263#[derive(Debug, Clone, Copy, PartialEq, Eq)]
264pub enum ProfileCategory {
265    /// Parametric profiles (rectangle, circle, I-shape, etc.)
266    Parametric,
267    /// Arbitrary profiles (polyline-based)
268    Arbitrary,
269    /// Composite profiles
270    Composite,
271    /// Other
272    Other,
273}
274
275impl IfcSchema {
276    /// Create schema with geometry type mappings
277    pub fn new() -> Self {
278        let mut geometry_types = HashMap::new();
279        let mut profile_types = HashMap::new();
280
281        // Swept solids (P0)
282        geometry_types.insert(IfcType::IfcExtrudedAreaSolid, GeometryCategory::SweptSolid);
283        geometry_types.insert(IfcType::IfcRevolvedAreaSolid, GeometryCategory::SweptSolid);
284
285        // Boolean operations (P0)
286        geometry_types.insert(IfcType::IfcBooleanResult, GeometryCategory::Boolean);
287        geometry_types.insert(IfcType::IfcBooleanClippingResult, GeometryCategory::Boolean);
288
289        // Explicit meshes (P0)
290        geometry_types.insert(IfcType::IfcFacetedBrep, GeometryCategory::ExplicitMesh);
291        geometry_types.insert(IfcType::IfcTriangulatedFaceSet, GeometryCategory::ExplicitMesh);
292        geometry_types.insert(IfcType::IfcPolygonalFaceSet, GeometryCategory::ExplicitMesh);
293
294        // Instancing (P0)
295        geometry_types.insert(IfcType::IfcMappedItem, GeometryCategory::MappedItem);
296
297        // Profile types - Parametric
298        profile_types.insert(IfcType::IfcRectangleProfileDef, ProfileCategory::Parametric);
299        profile_types.insert(IfcType::IfcCircleProfileDef, ProfileCategory::Parametric);
300        profile_types.insert(IfcType::IfcCircleHollowProfileDef, ProfileCategory::Parametric);
301        profile_types.insert(IfcType::IfcRectangleHollowProfileDef, ProfileCategory::Parametric);
302        profile_types.insert(IfcType::IfcIShapeProfileDef, ProfileCategory::Parametric);
303        profile_types.insert(IfcType::IfcLShapeProfileDef, ProfileCategory::Parametric);
304        profile_types.insert(IfcType::IfcUShapeProfileDef, ProfileCategory::Parametric);
305        profile_types.insert(IfcType::IfcTShapeProfileDef, ProfileCategory::Parametric);
306        profile_types.insert(IfcType::IfcCShapeProfileDef, ProfileCategory::Parametric);
307        profile_types.insert(IfcType::IfcZShapeProfileDef, ProfileCategory::Parametric);
308
309        // Profile types - Arbitrary
310        profile_types.insert(IfcType::IfcArbitraryClosedProfileDef, ProfileCategory::Arbitrary);
311        profile_types.insert(IfcType::IfcArbitraryProfileDefWithVoids, ProfileCategory::Arbitrary);
312
313        // Profile types - Composite
314        profile_types.insert(IfcType::IfcCompositeProfileDef, ProfileCategory::Composite);
315
316        Self {
317            geometry_types,
318            profile_types,
319        }
320    }
321
322    /// Get geometry category for a type
323    pub fn geometry_category(&self, ifc_type: &IfcType) -> Option<GeometryCategory> {
324        self.geometry_types.get(ifc_type).copied()
325    }
326
327    /// Get profile category for a type
328    pub fn profile_category(&self, ifc_type: &IfcType) -> Option<ProfileCategory> {
329        self.profile_types.get(ifc_type).copied()
330    }
331
332    /// Check if type is a geometry representation
333    pub fn is_geometry_type(&self, ifc_type: &IfcType) -> bool {
334        self.geometry_types.contains_key(ifc_type)
335    }
336
337    /// Check if type is a profile
338    pub fn is_profile_type(&self, ifc_type: &IfcType) -> bool {
339        self.profile_types.contains_key(ifc_type)
340    }
341
342    /// Check if type has geometry
343    pub fn has_geometry(&self, ifc_type: &IfcType) -> bool {
344        // Building elements, furnishing, etc.
345        ifc_type.is_building_element() ||
346        matches!(
347            ifc_type,
348            IfcType::IfcFurnishingElement
349                | IfcType::IfcFurniture
350                | IfcType::IfcDuctSegment
351                | IfcType::IfcPipeSegment
352                | IfcType::IfcCableSegment
353                | IfcType::IfcProxy // Generic placeholder that can contain geometry
354                | IfcType::IfcProduct // Base type for all products
355                | IfcType::IfcDistributionElement
356                | IfcType::IfcFlowSegment
357                | IfcType::IfcFlowFitting
358                | IfcType::IfcFlowTerminal
359        )
360    }
361}
362
363impl Default for IfcSchema {
364    fn default() -> Self {
365        Self::new()
366    }
367}
368
369// Note: IFC types are now defined as proper enum variants in schema.rs
370// This avoids the issue where from_str() would return Unknown(hash) instead of matching the constant.
371
372#[cfg(test)]
373mod tests {
374    use super::*;
375
376    #[test]
377    fn test_schema_geometry_categories() {
378        let schema = IfcSchema::new();
379
380        assert_eq!(
381            schema.geometry_category(&IfcType::IfcExtrudedAreaSolid),
382            Some(GeometryCategory::SweptSolid)
383        );
384
385        assert_eq!(
386            schema.geometry_category(&IfcType::IfcBooleanResult),
387            Some(GeometryCategory::Boolean)
388        );
389
390        assert_eq!(
391            schema.geometry_category(&IfcType::IfcTriangulatedFaceSet),
392            Some(GeometryCategory::ExplicitMesh)
393        );
394    }
395
396    #[test]
397    fn test_attribute_value_conversion() {
398        let token = Token::EntityRef(123);
399        let attr = AttributeValue::from_token(&token);
400        assert_eq!(attr.as_entity_ref(), Some(123));
401
402        let token = Token::String("test");
403        let attr = AttributeValue::from_token(&token);
404        assert_eq!(attr.as_string(), Some("test"));
405    }
406
407    #[test]
408    fn test_decoded_entity() {
409        let entity = DecodedEntity::new(
410            1,
411            IfcType::IfcWall,
412            vec![
413                AttributeValue::EntityRef(2),
414                AttributeValue::String("Wall-001".to_string()),
415                AttributeValue::Float(3.5),
416            ],
417        );
418
419        assert_eq!(entity.get_ref(0), Some(2));
420        assert_eq!(entity.get_string(1), Some("Wall-001"));
421        assert_eq!(entity.get_float(2), Some(3.5));
422    }
423}