ifc_lite_server/services/data_model/
documents.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//! Document association extraction.
6
7use super::types::{DocumentAssociation, EntityJob};
8use ifc_lite_core::EntityDecoder;
9use rayon::prelude::*;
10use std::sync::Arc;
11
12/// Resolve an `IfcDocumentReference` / `IfcDocumentInformation` into
13/// `(identification, name, location, description)`.
14fn resolve_document(
15    decoder: &mut EntityDecoder,
16    id: u32,
17) -> (
18    Option<String>,
19    Option<String>,
20    Option<String>,
21    Option<String>,
22) {
23    let Ok(entity) = decoder.decode_by_id(id) else {
24        return (None, None, None, None);
25    };
26    let ty = entity.ifc_type.as_str().to_ascii_uppercase();
27
28    if ty == "IFCDOCUMENTINFORMATION" {
29        // Identification(0), Name(1), Description(2), Location(3).
30        return (
31            entity.get_string(0).map(|s| s.to_string()),
32            entity.get_string(1).map(|s| s.to_string()),
33            entity.get_string(3).map(|s| s.to_string()),
34            entity.get_string(2).map(|s| s.to_string()),
35        );
36    }
37
38    // IfcDocumentReference: Location(0), Identification(1), Name(2),
39    // Description(3), ReferencedDocument(4).
40    let mut location = entity.get_string(0).map(|s| s.to_string());
41    let mut identification = entity.get_string(1).map(|s| s.to_string());
42    let mut name = entity.get_string(2).map(|s| s.to_string());
43    let mut description = entity.get_string(3).map(|s| s.to_string());
44
45    // Backfill missing fields from the referenced IfcDocumentInformation.
46    if let Some(info_id) = entity.get_ref(4) {
47        if let Ok(info) = decoder.decode_by_id(info_id) {
48            if info
49                .ifc_type
50                .as_str()
51                .eq_ignore_ascii_case("IFCDOCUMENTINFORMATION")
52            {
53                identification =
54                    identification.or_else(|| info.get_string(0).map(|s| s.to_string()));
55                name = name.or_else(|| info.get_string(1).map(|s| s.to_string()));
56                description = description.or_else(|| info.get_string(2).map(|s| s.to_string()));
57                location = location.or_else(|| info.get_string(3).map(|s| s.to_string()));
58            }
59        }
60    }
61
62    (identification, name, location, description)
63}
64
65/// Extract document associations (`IfcRelAssociatesDocument`).
66pub(super) fn extract_documents(
67    jobs: &[EntityJob],
68    content: &Arc<Vec<u8>>,
69    entity_index: &Arc<ifc_lite_core::EntityIndex>,
70) -> Vec<DocumentAssociation> {
71    let rel_jobs: Vec<_> = jobs
72        .iter()
73        .filter(|job| {
74            job.type_name
75                .eq_ignore_ascii_case("IFCRELASSOCIATESDOCUMENT")
76        })
77        .collect();
78
79    tracing::debug!(count = rel_jobs.len(), "Extracting documents");
80
81    rel_jobs
82        .par_iter()
83        .flat_map(|job| {
84            let mut decoder =
85                EntityDecoder::with_arc_index(content.as_slice(), entity_index.clone());
86            let Ok(rel) = decoder.decode_at(job.start, job.end) else {
87                return Vec::new();
88            };
89            let related = super::related_object_ids(&rel);
90            // RelatingDocument is attribute 5.
91            let Some(doc_id) = rel.get_ref(5) else {
92                return Vec::new();
93            };
94            let (identification, name, location, description) =
95                resolve_document(&mut decoder, doc_id);
96
97            related
98                .into_iter()
99                .map(|element_id| DocumentAssociation {
100                    element_id,
101                    identification: identification.clone(),
102                    name: name.clone(),
103                    location: location.clone(),
104                    description: description.clone(),
105                })
106                .collect()
107        })
108        .collect()
109}