ifc_lite_server/services/data_model/
quantities.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//! Quantity set extraction.
6
7use super::types::{EntityJob, Quantity, QuantitySet};
8use ifc_lite_core::{DecodedEntity, EntityDecoder};
9use rayon::prelude::*;
10use std::sync::Arc;
11
12/// Extract all quantity sets (IfcElementQuantity) and their quantities.
13pub(super) fn extract_quantities(
14    jobs: &[EntityJob],
15    content: &Arc<Vec<u8>>,
16    entity_index: &Arc<ifc_lite_core::EntityIndex>,
17) -> Vec<QuantitySet> {
18    // First, collect all IfcElementQuantity entities
19    // PERF: Use eq_ignore_ascii_case to avoid string allocation per comparison
20    let qset_jobs: Vec<_> = jobs
21        .iter()
22        .filter(|job| job.type_name.eq_ignore_ascii_case("IFCELEMENTQUANTITY"))
23        .collect();
24
25    tracing::debug!(count = qset_jobs.len(), "Extracting quantity sets");
26
27    qset_jobs
28        .par_iter()
29        .filter_map(|job| {
30            let mut local_decoder =
31                EntityDecoder::with_arc_index(content.as_slice(), entity_index.clone());
32            let entity = local_decoder.decode_at(job.start, job.end).ok()?;
33
34            // IfcElementQuantity: [0]=GlobalId, [1]=OwnerHistory, [2]=Name, [3]=Description, [4]=MethodOfMeasurement, [5]=Quantities
35            let qset_name = entity.get_string(2)?.to_string();
36            let method_of_measurement = entity.get_string(4).map(|s| s.to_string());
37            let has_quantities = entity.get_list(5)?;
38
39            let mut quantities = Vec::new();
40
41            // Extract quantities from Quantities list
42            for quant_ref in has_quantities.iter() {
43                if let Some(quant_id) = quant_ref.as_entity_ref() {
44                    if let Ok(quant_entity) = local_decoder.decode_by_id(quant_id) {
45                        if let Some(quant) = extract_quantity_value(&quant_entity) {
46                            quantities.push(quant);
47                        }
48                    }
49                }
50            }
51
52            if quantities.is_empty() {
53                return None;
54            }
55
56            Some(QuantitySet {
57                qset_id: job.id,
58                qset_name,
59                method_of_measurement,
60                quantities,
61            })
62        })
63        .collect()
64}
65
66/// Extract a single quantity value from IfcPhysicalQuantity entity.
67/// Supports: IfcQuantityLength, IfcQuantityArea, IfcQuantityVolume,
68///           IfcQuantityCount, IfcQuantityWeight, IfcQuantityTime,
69///           IfcQuantityNumber
70fn extract_quantity_value(entity: &DecodedEntity) -> Option<Quantity> {
71    // PERF: Use eq_ignore_ascii_case to avoid string allocation per comparison
72    let ifc_type = entity.ifc_type.as_str();
73
74    // Map IFC type to quantity type string
75    let quantity_type = if ifc_type.eq_ignore_ascii_case("IFCQUANTITYLENGTH") {
76        "length"
77    } else if ifc_type.eq_ignore_ascii_case("IFCQUANTITYAREA") {
78        "area"
79    } else if ifc_type.eq_ignore_ascii_case("IFCQUANTITYVOLUME") {
80        "volume"
81    } else if ifc_type.eq_ignore_ascii_case("IFCQUANTITYCOUNT") {
82        "count"
83    } else if ifc_type.eq_ignore_ascii_case("IFCQUANTITYWEIGHT") {
84        "weight"
85    } else if ifc_type.eq_ignore_ascii_case("IFCQUANTITYTIME") {
86        "time"
87    } else if ifc_type.eq_ignore_ascii_case("IFCQUANTITYNUMBER") {
88        "number"
89    } else {
90        return None; // Not a recognized quantity type
91    };
92
93    // All IFC quantity types have:
94    // [0]=Name, [1]=Description, [2]=Unit, [3]=*Value, [4]=Formula (optional, IFC4)
95    let quantity_name = entity.get_string(0)?.to_string();
96
97    // Value is at index 3 for all quantity types
98    let quantity_value = entity.get_float(3)?;
99
100    Some(Quantity {
101        quantity_name,
102        quantity_value,
103        quantity_type: quantity_type.to_string(),
104    })
105}