ifc_lite_server/services/data_model/
metadata.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//! Entity metadata extraction.
6
7use super::generated::{root_attr_indices, RootAttrIndices};
8use super::types::{EntityJob, EntityMetadata};
9use ifc_lite_core::{DecodedEntity, EntityDecoder};
10use rayon::prelude::*;
11use std::sync::Arc;
12
13/// Unknown-type fallback, mirroring the WASM path's
14/// `extractRootAttributesFromEntity`: GlobalId 0, Name 2, Description 3,
15/// ObjectType 4, Tag 7 (the IfcElement/IfcRoot layout), and NO PredefinedType.
16/// Applied only when the type is absent from the schema registry — a KNOWN
17/// type without an attribute keeps its -1 and stays empty on both parse
18/// paths (issue #3949 extended this fallback to GlobalId/Name).
19const UNKNOWN_TYPE_FALLBACK: RootAttrIndices = RootAttrIndices {
20    global_id: 0,
21    name: 2,
22    description: 3,
23    object_type: 4,
24    tag: 7,
25    predefined_type: -1,
26};
27
28/// Non-empty string attribute at a registry index (-1 = not declared).
29fn string_at(entity: &DecodedEntity, idx: i8) -> Option<String> {
30    if idx < 0 {
31        return None;
32    }
33    entity
34        .get_string(idx as usize)
35        .filter(|s| !s.is_empty())
36        .map(|s| s.to_string())
37}
38
39/// PredefinedType enum token at a registry index. STEP enums decode to
40/// `AttributeValue::Enum` with the dots already stripped (`.SOLIDWALL.` →
41/// `SOLIDWALL`), matching the WASM `extractAllEntityAttributes` display value —
42/// read via `as_enum()`, never `get_string()`.
43fn enum_at(entity: &DecodedEntity, idx: i8) -> Option<String> {
44    if idx < 0 {
45        return None;
46    }
47    entity
48        .get(idx as usize)
49        .and_then(|v| v.as_enum())
50        .filter(|s| !s.is_empty())
51        .map(|s| s.to_string())
52}
53
54/// Extract entity metadata for all entities.
55pub(super) fn extract_entity_metadata(
56    jobs: &[EntityJob],
57    content: &Arc<Vec<u8>>,
58    entity_index: &Arc<ifc_lite_core::EntityIndex>,
59) -> Vec<EntityMetadata> {
60    jobs.par_iter()
61        .filter_map(|job| {
62            let mut local_decoder =
63                EntityDecoder::with_arc_index(content.as_slice(), entity_index.clone());
64            let entity = local_decoder.decode_at(job.start, job.end).ok()?;
65            let has_geometry = ifc_lite_core::has_geometry_by_name(&job.type_name);
66
67            // Root attributes at the SAME schema-registry positions the WASM
68            // path resolves them (issue #1765) — see generated/attr_indices.rs.
69            // GlobalId/Name are NOT fixed at 0/2 for every entity: that only
70            // holds for `IfcRoot` subtypes. `IfcClassification`, for example,
71            // has no GlobalId at all and its Name sits at index 3, not 2
72            // (issue #3949).
73            let upper = job.type_name.to_uppercase();
74            let idx = root_attr_indices(&upper).unwrap_or(UNKNOWN_TYPE_FALLBACK);
75            let global_id = string_at(&entity, idx.global_id);
76            let name = string_at(&entity, idx.name);
77            let description = string_at(&entity, idx.description);
78            let object_type = string_at(&entity, idx.object_type);
79            let tag = string_at(&entity, idx.tag);
80            let predefined_type = enum_at(&entity, idx.predefined_type);
81
82            Some(EntityMetadata {
83                entity_id: job.id,
84                type_name: job.type_name.clone(),
85                global_id,
86                name,
87                description,
88                object_type,
89                tag,
90                predefined_type,
91                has_geometry,
92            })
93        })
94        .collect()
95}