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}