Skip to main content

bimifc_model/
properties.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//! Property and quantity access for IFC entities
6
7use crate::EntityId;
8use serde::{Deserialize, Serialize};
9
10/// A single property value with optional unit
11#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
12pub struct Property {
13    /// Property name
14    pub name: String,
15    /// Property value as formatted string
16    pub value: String,
17    /// Unit of measurement (if applicable)
18    pub unit: Option<String>,
19}
20
21impl Property {
22    /// Create a new property
23    pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
24        Self {
25            name: name.into(),
26            value: value.into(),
27            unit: None,
28        }
29    }
30
31    /// Create a property with unit
32    pub fn with_unit(
33        name: impl Into<String>,
34        value: impl Into<String>,
35        unit: impl Into<String>,
36    ) -> Self {
37        Self {
38            name: name.into(),
39            value: value.into(),
40            unit: Some(unit.into()),
41        }
42    }
43}
44
45/// A property set containing multiple properties
46#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
47pub struct PropertySet {
48    /// Property set name (e.g., "Pset_WallCommon")
49    pub name: String,
50    /// Properties in this set
51    pub properties: Vec<Property>,
52}
53
54impl PropertySet {
55    /// Create a new property set
56    pub fn new(name: impl Into<String>) -> Self {
57        Self {
58            name: name.into(),
59            properties: Vec::new(),
60        }
61    }
62
63    /// Add a property to this set
64    pub fn add(&mut self, property: Property) {
65        self.properties.push(property);
66    }
67
68    /// Get a property by name
69    pub fn get(&self, name: &str) -> Option<&Property> {
70        self.properties.iter().find(|p| p.name == name)
71    }
72}
73
74/// Quantity types supported in IFC
75#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
76pub enum QuantityType {
77    /// Linear measurement (IfcQuantityLength)
78    Length,
79    /// Area measurement (IfcQuantityArea)
80    Area,
81    /// Volume measurement (IfcQuantityVolume)
82    Volume,
83    /// Count (IfcQuantityCount)
84    Count,
85    /// Weight/mass measurement (IfcQuantityWeight)
86    Weight,
87    /// Time measurement (IfcQuantityTime)
88    Time,
89}
90
91impl QuantityType {
92    /// Get default unit for this quantity type
93    pub fn default_unit(&self) -> &'static str {
94        match self {
95            QuantityType::Length => "m",
96            QuantityType::Area => "m²",
97            QuantityType::Volume => "m³",
98            QuantityType::Count => "",
99            QuantityType::Weight => "kg",
100            QuantityType::Time => "s",
101        }
102    }
103}
104
105/// A quantity value with type and unit
106#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
107pub struct Quantity {
108    /// Quantity name
109    pub name: String,
110    /// Numeric value
111    pub value: f64,
112    /// Unit of measurement
113    pub unit: String,
114    /// Type of quantity
115    pub quantity_type: QuantityType,
116}
117
118impl Quantity {
119    /// Create a new quantity
120    pub fn new(name: impl Into<String>, value: f64, quantity_type: QuantityType) -> Self {
121        Self {
122            name: name.into(),
123            value,
124            unit: quantity_type.default_unit().to_string(),
125            quantity_type,
126        }
127    }
128
129    /// Create a quantity with custom unit
130    pub fn with_unit(
131        name: impl Into<String>,
132        value: f64,
133        unit: impl Into<String>,
134        quantity_type: QuantityType,
135    ) -> Self {
136        Self {
137            name: name.into(),
138            value,
139            unit: unit.into(),
140            quantity_type,
141        }
142    }
143
144    /// Format the value with unit
145    pub fn formatted(&self) -> String {
146        if self.unit.is_empty() {
147            format!("{}", self.value)
148        } else {
149            format!("{} {}", self.value, self.unit)
150        }
151    }
152}
153
154/// Property and quantity reader trait
155///
156/// Provides access to property sets and quantities associated with IFC entities.
157/// Property sets come from IfcPropertySet entities linked via IfcRelDefinesByProperties.
158/// Quantities come from IfcElementQuantity entities.
159///
160/// # Example
161///
162/// ```ignore
163/// use bimifc_model::{PropertyReader, EntityId};
164///
165/// fn show_wall_properties(props: &dyn PropertyReader, wall_id: EntityId) {
166///     // Get all property sets
167///     for pset in props.property_sets(wall_id) {
168///         println!("Property Set: {}", pset.name);
169///         for prop in &pset.properties {
170///             println!("  {}: {}", prop.name, prop.value);
171///         }
172///     }
173///
174///     // Get quantities
175///     for qty in props.quantities(wall_id) {
176///         println!("{}: {} {}", qty.name, qty.value, qty.unit);
177///     }
178///
179///     // Get specific property
180///     if let Some(fire_rating) = props.get_property(wall_id, "FireRating") {
181///         println!("Fire Rating: {}", fire_rating.value);
182///     }
183/// }
184/// ```
185pub trait PropertyReader: Send + Sync {
186    /// Get all property sets associated with an entity
187    ///
188    /// # Arguments
189    /// * `id` - The entity ID to get property sets for
190    ///
191    /// # Returns
192    /// A vector of property sets (empty if none found)
193    fn property_sets(&self, id: EntityId) -> Vec<PropertySet>;
194
195    /// Get all quantities associated with an entity
196    ///
197    /// # Arguments
198    /// * `id` - The entity ID to get quantities for
199    ///
200    /// # Returns
201    /// A vector of quantities (empty if none found)
202    fn quantities(&self, id: EntityId) -> Vec<Quantity>;
203
204    /// Get a specific property by name
205    ///
206    /// Searches all property sets for the entity and returns the first
207    /// property with the matching name.
208    ///
209    /// # Arguments
210    /// * `id` - The entity ID to search
211    /// * `name` - The property name to find
212    ///
213    /// # Returns
214    /// The property if found
215    fn get_property(&self, id: EntityId, name: &str) -> Option<Property> {
216        self.property_sets(id)
217            .into_iter()
218            .flat_map(|pset| pset.properties)
219            .find(|p| p.name == name)
220    }
221
222    /// Get a specific quantity by name
223    ///
224    /// # Arguments
225    /// * `id` - The entity ID to search
226    /// * `name` - The quantity name to find
227    ///
228    /// # Returns
229    /// The quantity if found
230    fn get_quantity(&self, id: EntityId, name: &str) -> Option<Quantity> {
231        self.quantities(id).into_iter().find(|q| q.name == name)
232    }
233
234    /// Get entity's GlobalId (GUID)
235    ///
236    /// The GlobalId is a unique identifier assigned to IFC entities,
237    /// typically a 22-character base64-encoded GUID.
238    ///
239    /// # Arguments
240    /// * `id` - The entity ID
241    ///
242    /// # Returns
243    /// The GlobalId string if available
244    fn global_id(&self, id: EntityId) -> Option<String>;
245
246    /// Get entity's Name attribute
247    ///
248    /// Most IFC entities have a Name attribute (typically at index 2).
249    ///
250    /// # Arguments
251    /// * `id` - The entity ID
252    ///
253    /// # Returns
254    /// The name string if available
255    fn name(&self, id: EntityId) -> Option<String>;
256
257    /// Get entity's Description attribute
258    ///
259    /// # Arguments
260    /// * `id` - The entity ID
261    ///
262    /// # Returns
263    /// The description string if available
264    fn description(&self, id: EntityId) -> Option<String>;
265
266    /// Get entity's ObjectType attribute
267    ///
268    /// ObjectType is often used as a more specific type indicator
269    /// beyond the IFC class name.
270    ///
271    /// # Arguments
272    /// * `id` - The entity ID
273    ///
274    /// # Returns
275    /// The object type string if available
276    fn object_type(&self, _id: EntityId) -> Option<String> {
277        None
278    }
279
280    /// Get entity's Tag attribute
281    ///
282    /// Tag is often used for element identification/marking.
283    ///
284    /// # Arguments
285    /// * `id` - The entity ID
286    ///
287    /// # Returns
288    /// The tag string if available
289    fn tag(&self, _id: EntityId) -> Option<String> {
290        None
291    }
292}