ifc_lite_core/decoder/
schema_layout.rs1use super::{EntityDecoder, EntityScanner};
15use crate::generated::schema_registry::SchemaVersion;
16use rustc_hash::FxHashMap;
17use std::sync::{Arc, OnceLock};
18
19#[derive(Debug, Default)]
27pub struct GridAxisIndex(OnceLock<FxHashMap<u32, u32>>);
28
29impl EntityDecoder<'_> {
30 pub(crate) fn schema_version(&mut self) -> Option<SchemaVersion> {
34 if let Some(cached) = self.schema_version_cache {
35 return cached;
36 }
37 let version = crate::parser::header::declared_schemas_bounded(self.content)
38 .iter()
39 .find_map(|label| SchemaVersion::from_file_schema(label));
40 self.schema_version_cache = Some(version);
41 version
42 }
43
44 pub fn attribute_index(&mut self, entity_name: &str, attribute: &str) -> Option<usize> {
50 let schema = self.schema_version().unwrap_or(SchemaVersion::Ifc4x3);
51 schema
52 .attribute_names(entity_name)?
53 .iter()
54 .position(|name| name.eq_ignore_ascii_case(attribute))
55 }
56
57 pub fn grid_of_axis(&mut self, axis_id: u32) -> Option<u32> {
65 let slots: Vec<usize> = ["UAxes", "VAxes", "WAxes"]
66 .iter()
67 .filter_map(|name| self.attribute_index("IfcGrid", name))
68 .collect();
69 self.grid_axis_index
70 .0
71 .get_or_init(|| self.build_grid_axis_owners(&slots))
72 .get(&axis_id)
73 .copied()
74 }
75
76 pub fn set_grid_axis_index(&mut self, index: Arc<GridAxisIndex>) {
80 self.grid_axis_index = index;
81 }
82
83 fn build_grid_axis_owners(&self, axis_slots: &[usize]) -> FxHashMap<u32, u32> {
84 let mut owners = FxHashMap::default();
85 let mut scanner = EntityScanner::new(self.content);
86 while let Some((id, type_name, start, end)) = scanner.next_entity() {
87 if !type_name.eq_ignore_ascii_case("IFCGRID") {
89 continue;
90 }
91 let Ok(grid) = self.decode_at_uncached(start, end) else {
92 continue;
93 };
94 for &slot in axis_slots {
95 for axis in grid.get_refs(slot).unwrap_or_default() {
96 owners.entry(axis).or_insert(id);
97 }
98 }
99 }
100 owners
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::GridAxisIndex;
107 use crate::{ColumnarEntityIndex, EntityDecoder};
108 use std::sync::Arc;
109
110 fn source(schema: &str) -> String {
111 format!(
112 "ISO-10303-21;\nHEADER;\nFILE_SCHEMA(('{schema}'));\nENDSEC;\nDATA;\n\
113#1=IFCGRIDAXIS('A',$,.T.);\n#2=IFCGRIDAXIS('1',$,.T.);\n#3=IFCGRIDAXIS('X',$,.T.);\n\
114#9=IfcGrid('g',$,$,$,$,$,$,(#1),(#2),$);\nENDSEC;\nEND-ISO-10303-21;\n"
115 )
116 }
117
118 #[test]
119 fn grid_placement_layout_follows_the_declared_schema() {
120 for (schema, rel_to, location, ref_dir) in [
121 ("IFC2X3", None, Some(0), Some(1)),
122 ("IFC4", None, Some(0), Some(1)),
123 ("IFC4X3_ADD2", Some(0), Some(1), Some(2)),
124 ("NOT_A_SCHEMA", Some(0), Some(1), Some(2)),
125 ] {
126 let content = source(schema);
127 let mut decoder = EntityDecoder::new(&content);
128 assert_eq!(
129 decoder.attribute_index("IfcGridPlacement", "PlacementRelTo"),
130 rel_to,
131 "{schema}"
132 );
133 assert_eq!(
134 decoder.attribute_index("IfcGridPlacement", "PlacementLocation"),
135 location,
136 "{schema}"
137 );
138 assert_eq!(
139 decoder.attribute_index("IfcGridPlacement", "PlacementRefDirection"),
140 ref_dir,
141 "{schema}"
142 );
143 }
144 }
145
146 #[test]
147 fn grid_of_axis_is_the_inverse_of_the_grid_axis_lists() {
148 let content = source("IFC4");
149 let mut decoder = EntityDecoder::new(&content);
150 assert_eq!(decoder.grid_of_axis(1), Some(9), "U axis");
151 assert_eq!(decoder.grid_of_axis(2), Some(9), "V axis");
152 assert_eq!(decoder.grid_of_axis(3), None, "an axis no grid lists");
153 }
154
155 #[test]
156 fn grid_axis_owners_are_shared_through_the_columnar_index() {
157 let content = source("IFC2X3");
158 let index = Arc::new(ColumnarEntityIndex::from_scan(content.as_bytes()));
159 let mut first = EntityDecoder::with_arc_columnar_index(&content, index.clone());
160 assert_eq!(first.grid_of_axis(1), Some(9));
161 drop(first);
162 assert!(
163 index.grid_axis_index.0.get().is_some(),
164 "built once per source, not per decoder"
165 );
166 let mut second = EntityDecoder::with_arc_columnar_index(&content, index);
167 assert_eq!(second.grid_of_axis(2), Some(9));
168 }
169
170 #[test]
171 fn a_shared_grid_axis_index_is_built_once_across_decoders() {
172 let content = source("IFC4");
173 let shared = Arc::new(GridAxisIndex::default());
174 let mut first = EntityDecoder::new(&content);
175 first.set_grid_axis_index(shared.clone());
176 assert_eq!(first.grid_of_axis(1), Some(9));
177 assert!(shared.0.get().is_some());
178 let empty = "ISO-10303-21;\nHEADER;\nFILE_SCHEMA(('IFC4'));\nENDSEC;\nDATA;\nENDSEC;\n";
181 let mut second = EntityDecoder::new(empty);
182 second.set_grid_axis_index(shared);
183 assert_eq!(second.grid_of_axis(2), Some(9));
184 }
185}