use ifc_model::{Codec, Entity, EntityId, Model, Value};
use openbim_step::ParseOptions;
fn skip_text(bytes: &[u8], mut i: usize) -> usize {
i += 1;
while i < bytes.len() {
if bytes[i] == b'\'' {
if bytes.get(i + 1) == Some(&b'\'') {
i += 2;
continue;
}
return i + 1;
}
i += 1;
}
i
}
fn skip_comment(bytes: &[u8], mut i: usize) -> usize {
i += 2;
while i + 1 < bytes.len() {
if bytes[i] == b'*' && bytes[i + 1] == b'/' {
return i + 2;
}
i += 1;
}
bytes.len()
}
struct Scanned {
id: u64,
start: usize,
end: usize,
type_start: usize,
type_end: usize,
}
fn scan_records(bytes: &[u8]) -> Vec<Scanned> {
let mut out = Vec::new();
let mut i = find_data_section(bytes);
while i < bytes.len() {
match bytes[i] {
b'\'' => {
i = skip_text(bytes, i);
}
b'/' if bytes.get(i + 1) == Some(&b'*') => {
i = skip_comment(bytes, i);
}
b'#' => {
let Some(rec) = scan_one(bytes, i) else {
i += 1;
continue;
};
i = rec.end;
out.push(rec);
}
_ => {
i += 1;
}
}
}
out
}
fn scan_one(bytes: &[u8], start: usize) -> Option<Scanned> {
let mut i = start + 1;
let digits = i;
while i < bytes.len() && bytes[i].is_ascii_digit() {
i += 1;
}
if i == digits {
return None;
}
let id: u64 = std::str::from_utf8(&bytes[digits..i]).ok()?.parse().ok()?;
while i < bytes.len() && bytes[i].is_ascii_whitespace() {
i += 1;
}
if bytes.get(i) != Some(&b'=') {
return None;
}
i += 1;
while i < bytes.len() && bytes[i].is_ascii_whitespace() {
i += 1;
}
let type_start = i;
while i < bytes.len() && (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'_') {
i += 1;
}
let type_end = i;
while i < bytes.len() {
match bytes[i] {
b'\'' => {
i = skip_text(bytes, i);
}
b'/' if bytes.get(i + 1) == Some(&b'*') => {
i = skip_comment(bytes, i);
}
b';' => {
return Some(Scanned {
id,
start,
end: i + 1,
type_start,
type_end,
})
}
_ => {
i += 1;
}
}
}
None
}
fn find_data_section(bytes: &[u8]) -> usize {
let needle = b"DATA";
let mut i = 0;
while i + needle.len() <= bytes.len() {
if bytes[i..].starts_with(needle) {
let mut j = i + needle.len();
while j < bytes.len() && bytes[j].is_ascii_whitespace() {
j += 1;
}
if bytes.get(j) == Some(&b';') {
return j + 1;
}
}
i += 1;
}
0
}
pub struct Index<'a> {
source: &'a [u8],
ids: Vec<u64>,
starts: Vec<u64>,
lens: Vec<u32>,
type_ids: Vec<u32>,
type_names: Vec<String>,
}
impl<'a> Index<'a> {
#[must_use]
pub fn scan(source: &'a [u8]) -> Self {
let scanned = scan_records(source);
let mut ids = Vec::with_capacity(scanned.len());
let mut starts = Vec::with_capacity(scanned.len());
let mut lens = Vec::with_capacity(scanned.len());
let mut type_ids = Vec::with_capacity(scanned.len());
let mut type_names: Vec<String> = Vec::new();
let mut seen: std::collections::HashMap<&[u8], u32> = std::collections::HashMap::new();
for rec in scanned {
let name = &source[rec.type_start..rec.type_end];
let next = type_names.len() as u32;
let tid = *seen.entry(name).or_insert(next);
if tid == next {
type_names.push(String::from_utf8_lossy(name).to_ascii_uppercase());
}
ids.push(rec.id);
starts.push(rec.start as u64);
lens.push((rec.end - rec.start) as u32);
type_ids.push(tid);
}
Self {
source,
ids,
starts,
lens,
type_ids,
type_names,
}
}
#[must_use]
pub fn len(&self) -> usize {
self.ids.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.ids.is_empty()
}
pub fn ids(&self) -> impl Iterator<Item = EntityId> + '_ {
self.ids.iter().copied().map(EntityId)
}
#[must_use]
pub fn type_of(&self, id: EntityId) -> Option<&str> {
let pos = self.position(id)?;
Some(&self.type_names[self.type_ids[pos] as usize])
}
#[must_use]
pub fn count_by_type(&self) -> std::collections::BTreeMap<&str, usize> {
let mut out = std::collections::BTreeMap::new();
for tid in &self.type_ids {
*out.entry(self.type_names[*tid as usize].as_str())
.or_insert(0) += 1;
}
out
}
#[must_use]
pub fn ids_of_type(&self, name: &str) -> Vec<EntityId> {
let upper = name.to_ascii_uppercase();
let Some(tid) = self.type_names.iter().position(|n| *n == upper) else {
return Vec::new();
};
let tid = tid as u32;
self.type_ids
.iter()
.enumerate()
.filter(|(_, t)| **t == tid)
.map(|(i, _)| EntityId(self.ids[i]))
.collect()
}
fn position(&self, id: EntityId) -> Option<usize> {
self.ids.iter().position(|i| *i == id.0)
}
#[must_use]
pub fn entity(&self, id: EntityId) -> Option<Entity> {
let pos = self.position(id)?;
let start = self.starts[pos] as usize;
let end = start + self.lens[pos] as usize;
let model = self.decode_span(start, end)?;
model.get(id).cloned()
}
fn decode_span(&self, start: usize, end: usize) -> Option<Model> {
let mut buf = Vec::with_capacity(end - start + 160);
buf.extend_from_slice(ENVELOPE_HEAD);
buf.extend_from_slice(&self.source[start..end]);
buf.extend_from_slice(ENVELOPE_TAIL);
crate::StepReader::new(ParseOptions::strict())
.read_bytes(&buf)
.ok()
}
#[must_use]
pub fn materialize(&self, wanted: &[EntityId]) -> Model {
self.build(wanted)
}
#[must_use]
pub fn materialize_closure(&self, wanted: &[EntityId]) -> Model {
let mut needed: std::collections::BTreeSet<u64> = wanted.iter().map(|i| i.0).collect();
let mut frontier: Vec<u64> = needed.iter().copied().collect();
while let Some(id) = frontier.pop() {
let Some(entity) = self.entity(EntityId(id)) else {
continue;
};
for value in &entity.attributes {
collect_refs(value, &mut needed, &mut frontier);
}
}
let ids: Vec<EntityId> = needed.into_iter().map(EntityId).collect();
self.build(&ids)
}
fn build(&self, wanted: &[EntityId]) -> Model {
let mut buf = Vec::new();
buf.extend_from_slice(ENVELOPE_HEAD);
for id in wanted {
if let Some(pos) = self.position(*id) {
let start = self.starts[pos] as usize;
let end = start + self.lens[pos] as usize;
buf.extend_from_slice(&self.source[start..end]);
buf.push(b'\n');
}
}
buf.extend_from_slice(ENVELOPE_TAIL);
crate::StepReader::new(ParseOptions::strict())
.read_bytes(&buf)
.unwrap_or_default()
}
}
const ENVELOPE_HEAD: &[u8] = b"ISO-10303-21;\nHEADER;\nFILE_DESCRIPTION((\'\'),\'2;1\');\nFILE_NAME(\'\',\'\',(\'\'),(\'\'),\'\',\'\',\'\');\nFILE_SCHEMA((\'IFC4\'));\nENDSEC;\nDATA;\n";
const ENVELOPE_TAIL: &[u8] = b"ENDSEC;\nEND-ISO-10303-21;\n";
fn collect_refs(
value: &Value,
needed: &mut std::collections::BTreeSet<u64>,
frontier: &mut Vec<u64>,
) {
match value {
Value::Ref(id) => {
if needed.insert(id.0) {
frontier.push(id.0);
}
}
Value::List(items) => {
for v in items {
collect_refs(v, needed, frontier);
}
}
Value::Typed { value, .. } => collect_refs(value, needed, frontier),
_ => {}
}
}