1use ifc_model::{Codec, Entity, EntityId, Model, Value};
22use openbim_step::ParseOptions;
23
24fn skip_text(bytes: &[u8], mut i: usize) -> usize {
37 i += 1;
38 while i < bytes.len() {
39 if bytes[i] == b'\'' {
40 if bytes.get(i + 1) == Some(&b'\'') {
41 i += 2;
42 continue;
43 }
44 return i + 1;
45 }
46 i += 1;
47 }
48 i
49}
50
51fn skip_comment(bytes: &[u8], mut i: usize) -> usize {
53 i += 2;
54 while i + 1 < bytes.len() {
55 if bytes[i] == b'*' && bytes[i + 1] == b'/' {
56 return i + 2;
57 }
58 i += 1;
59 }
60 bytes.len()
61}
62
63struct Scanned {
65 id: u64,
66 start: usize,
67 end: usize,
68 type_start: usize,
69 type_end: usize,
70}
71
72fn scan_records(bytes: &[u8]) -> Vec<Scanned> {
77 let mut out = Vec::new();
78 let mut i = find_data_section(bytes);
79 while i < bytes.len() {
80 match bytes[i] {
81 b'\'' => {
82 i = skip_text(bytes, i);
83 }
84 b'/' if bytes.get(i + 1) == Some(&b'*') => {
85 i = skip_comment(bytes, i);
86 }
87 b'#' => {
88 let Some(rec) = scan_one(bytes, i) else {
89 i += 1;
90 continue;
91 };
92 i = rec.end;
93 out.push(rec);
94 }
95 _ => {
96 i += 1;
97 }
98 }
99 }
100 out
101}
102
103fn scan_one(bytes: &[u8], start: usize) -> Option<Scanned> {
106 let mut i = start + 1;
107 let digits = i;
108 while i < bytes.len() && bytes[i].is_ascii_digit() {
109 i += 1;
110 }
111 if i == digits {
112 return None;
113 }
114 let id: u64 = std::str::from_utf8(&bytes[digits..i]).ok()?.parse().ok()?;
115 while i < bytes.len() && bytes[i].is_ascii_whitespace() {
116 i += 1;
117 }
118 if bytes.get(i) != Some(&b'=') {
119 return None;
120 }
121 i += 1;
122 while i < bytes.len() && bytes[i].is_ascii_whitespace() {
123 i += 1;
124 }
125 let type_start = i;
126 while i < bytes.len() && (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'_') {
127 i += 1;
128 }
129 let type_end = i;
130 while i < bytes.len() {
131 match bytes[i] {
132 b'\'' => {
133 i = skip_text(bytes, i);
134 }
135 b'/' if bytes.get(i + 1) == Some(&b'*') => {
136 i = skip_comment(bytes, i);
137 }
138 b';' => {
139 return Some(Scanned {
140 id,
141 start,
142 end: i + 1,
143 type_start,
144 type_end,
145 })
146 }
147 _ => {
148 i += 1;
149 }
150 }
151 }
152 None
153}
154
155fn find_data_section(bytes: &[u8]) -> usize {
157 let needle = b"DATA";
158 let mut i = 0;
159 while i + needle.len() <= bytes.len() {
160 if bytes[i..].starts_with(needle) {
161 let mut j = i + needle.len();
162 while j < bytes.len() && bytes[j].is_ascii_whitespace() {
163 j += 1;
164 }
165 if bytes.get(j) == Some(&b';') {
166 return j + 1;
167 }
168 }
169 i += 1;
170 }
171 0
172}
173
174pub struct Index<'a> {
183 source: &'a [u8],
184 ids: Vec<u64>,
185 starts: Vec<u64>,
186 lens: Vec<u32>,
187 type_ids: Vec<u32>,
188 type_names: Vec<String>,
189}
190
191impl<'a> Index<'a> {
192 #[must_use]
194 pub fn scan(source: &'a [u8]) -> Self {
195 let scanned = scan_records(source);
196 let mut ids = Vec::with_capacity(scanned.len());
197 let mut starts = Vec::with_capacity(scanned.len());
198 let mut lens = Vec::with_capacity(scanned.len());
199 let mut type_ids = Vec::with_capacity(scanned.len());
200 let mut type_names: Vec<String> = Vec::new();
201 let mut seen: std::collections::HashMap<&[u8], u32> = std::collections::HashMap::new();
202 for rec in scanned {
203 let name = &source[rec.type_start..rec.type_end];
204 let next = type_names.len() as u32;
205 let tid = *seen.entry(name).or_insert(next);
206 if tid == next {
207 type_names.push(String::from_utf8_lossy(name).to_ascii_uppercase());
208 }
209 ids.push(rec.id);
210 starts.push(rec.start as u64);
211 lens.push((rec.end - rec.start) as u32);
212 type_ids.push(tid);
213 }
214 Self {
215 source,
216 ids,
217 starts,
218 lens,
219 type_ids,
220 type_names,
221 }
222 }
223
224 #[must_use]
226 pub fn len(&self) -> usize {
227 self.ids.len()
228 }
229
230 #[must_use]
232 pub fn is_empty(&self) -> bool {
233 self.ids.is_empty()
234 }
235
236 pub fn ids(&self) -> impl Iterator<Item = EntityId> + '_ {
238 self.ids.iter().copied().map(EntityId)
239 }
240
241 #[must_use]
243 pub fn type_of(&self, id: EntityId) -> Option<&str> {
244 let pos = self.position(id)?;
245 Some(&self.type_names[self.type_ids[pos] as usize])
246 }
247
248 #[must_use]
250 pub fn count_by_type(&self) -> std::collections::BTreeMap<&str, usize> {
251 let mut out = std::collections::BTreeMap::new();
252 for tid in &self.type_ids {
253 *out.entry(self.type_names[*tid as usize].as_str())
254 .or_insert(0) += 1;
255 }
256 out
257 }
258
259 #[must_use]
261 pub fn ids_of_type(&self, name: &str) -> Vec<EntityId> {
262 let upper = name.to_ascii_uppercase();
263 let Some(tid) = self.type_names.iter().position(|n| *n == upper) else {
264 return Vec::new();
265 };
266 let tid = tid as u32;
267 self.type_ids
268 .iter()
269 .enumerate()
270 .filter(|(_, t)| **t == tid)
271 .map(|(i, _)| EntityId(self.ids[i]))
272 .collect()
273 }
274
275 fn position(&self, id: EntityId) -> Option<usize> {
276 self.ids.iter().position(|i| *i == id.0)
277 }
278
279 #[must_use]
285 pub fn entity(&self, id: EntityId) -> Option<Entity> {
286 let pos = self.position(id)?;
287 let start = self.starts[pos] as usize;
288 let end = start + self.lens[pos] as usize;
289 let model = self.decode_span(start, end)?;
290 model.get(id).cloned()
291 }
292
293 fn decode_span(&self, start: usize, end: usize) -> Option<Model> {
296 let mut buf = Vec::with_capacity(end - start + 160);
297 buf.extend_from_slice(ENVELOPE_HEAD);
298 buf.extend_from_slice(&self.source[start..end]);
299 buf.extend_from_slice(ENVELOPE_TAIL);
300 crate::StepReader::new(ParseOptions::strict())
301 .read_bytes(&buf)
302 .ok()
303 }
304
305 #[must_use]
315 pub fn materialize(&self, wanted: &[EntityId]) -> Model {
316 self.build(wanted)
317 }
318
319 #[must_use]
325 pub fn materialize_closure(&self, wanted: &[EntityId]) -> Model {
326 let mut needed: std::collections::BTreeSet<u64> = wanted.iter().map(|i| i.0).collect();
327 let mut frontier: Vec<u64> = needed.iter().copied().collect();
328 while let Some(id) = frontier.pop() {
329 let Some(entity) = self.entity(EntityId(id)) else {
330 continue;
331 };
332 for value in &entity.attributes {
333 collect_refs(value, &mut needed, &mut frontier);
334 }
335 }
336 let ids: Vec<EntityId> = needed.into_iter().map(EntityId).collect();
337 self.build(&ids)
338 }
339
340 fn build(&self, wanted: &[EntityId]) -> Model {
342 let mut buf = Vec::new();
343 buf.extend_from_slice(ENVELOPE_HEAD);
344 for id in wanted {
345 if let Some(pos) = self.position(*id) {
346 let start = self.starts[pos] as usize;
347 let end = start + self.lens[pos] as usize;
348 buf.extend_from_slice(&self.source[start..end]);
349 buf.push(b'\n');
350 }
351 }
352 buf.extend_from_slice(ENVELOPE_TAIL);
353 crate::StepReader::new(ParseOptions::strict())
354 .read_bytes(&buf)
355 .unwrap_or_default()
356 }
357}
358
359const ENVELOPE_HEAD: &[u8] = b"ISO-10303-21;\nHEADER;\nFILE_DESCRIPTION((\'\'),\'2;1\');\nFILE_NAME(\'\',\'\',(\'\'),(\'\'),\'\',\'\',\'\');\nFILE_SCHEMA((\'IFC4\'));\nENDSEC;\nDATA;\n";
363const ENVELOPE_TAIL: &[u8] = b"ENDSEC;\nEND-ISO-10303-21;\n";
364
365fn collect_refs(
367 value: &Value,
368 needed: &mut std::collections::BTreeSet<u64>,
369 frontier: &mut Vec<u64>,
370) {
371 match value {
372 Value::Ref(id) => {
373 if needed.insert(id.0) {
374 frontier.push(id.0);
375 }
376 }
377 Value::List(items) => {
378 for v in items {
379 collect_refs(v, needed, frontier);
380 }
381 }
382 Value::Typed { value, .. } => collect_refs(value, needed, frontier),
383 _ => {}
384 }
385}