use std::sync::{OnceLock, RwLock};
use rustc_hash::FxHashMap;
use crate::generated::IfcType;
use crate::legacy_entities::get_legacy_entity_info;
fn normalise_uppercase(type_name: &str) -> std::borrow::Cow<'_, str> {
if type_name.bytes().any(|b| b.is_ascii_lowercase()) {
std::borrow::Cow::Owned(type_name.to_ascii_uppercase())
} else {
std::borrow::Cow::Borrowed(type_name)
}
}
fn cached<F>(cache: &RwLock<FxHashMap<String, bool>>, key: &str, f: F) -> bool
where
F: FnOnce() -> bool,
{
if let Ok(read) = cache.read() {
if let Some(&v) = read.get(key) {
return v;
}
}
let value = f();
if let Ok(mut write) = cache.write() {
write.insert(key.to_owned(), value);
}
value
}
pub fn has_geometry_by_name(type_name: &str) -> bool {
static CACHE: OnceLock<RwLock<FxHashMap<String, bool>>> = OnceLock::new();
let cache = CACHE.get_or_init(|| RwLock::new(FxHashMap::default()));
let upper = normalise_uppercase(type_name);
cached(cache, upper.as_ref(), || compute_has_geometry(upper.as_ref()))
}
fn compute_has_geometry(upper: &str) -> bool {
if let Some(info) = get_legacy_entity_info(upper) {
return info.has_geometry;
}
let t = IfcType::from_str(upper);
if matches!(t, IfcType::Unknown(_)) {
return upper.starts_with("IFCREINFORCING") || upper.starts_with("IFCREINFORCED");
}
if !t.is_subtype_of(IfcType::IfcProduct) {
return false;
}
!is_non_geometric_spatial(t)
}
fn is_non_geometric_spatial(t: IfcType) -> bool {
if t.is_subtype_of(IfcType::IfcSpace)
|| t.is_subtype_of(IfcType::IfcSite)
|| t.is_subtype_of(IfcType::IfcSpatialZone)
|| t.is_subtype_of(IfcType::IfcBuilding)
{
return false;
}
t.is_subtype_of(IfcType::IfcSpatialElement)
}
pub fn is_representationless_spatial_container_by_name(type_name: &str) -> bool {
static CACHE: OnceLock<RwLock<FxHashMap<String, bool>>> = OnceLock::new();
let cache = CACHE.get_or_init(|| RwLock::new(FxHashMap::default()));
let upper = normalise_uppercase(type_name);
cached(cache, upper.as_ref(), || {
compute_is_representationless_spatial_container(upper.as_ref())
})
}
fn compute_is_representationless_spatial_container(upper: &str) -> bool {
if get_legacy_entity_info(upper).is_some() {
return false;
}
let t = IfcType::from_str(upper);
if matches!(t, IfcType::Unknown(_)) || !t.is_subtype_of(IfcType::IfcProduct) {
return false;
}
is_non_geometric_spatial(t)
}
pub fn nth_attribute_is_present(entity_bytes: &[u8], index: usize) -> bool {
let Some(open_idx) = entity_bytes.iter().position(|byte| *byte == b'(') else {
return false;
};
let Some(close_idx) = entity_bytes.iter().rposition(|byte| *byte == b')') else {
return false;
};
if close_idx <= open_idx {
return false;
}
let args = &entity_bytes[open_idx + 1..close_idx];
let mut in_string = false;
let mut depth = 0i32;
let mut start = 0usize;
let mut attr_idx = 0usize;
let mut i = 0usize;
while i < args.len() {
match args[i] {
b'\'' => {
if in_string && i + 1 < args.len() && args[i + 1] == b'\'' {
i += 1;
} else {
in_string = !in_string;
}
}
b'(' if !in_string => depth += 1,
b')' if !in_string => depth -= 1,
b',' if !in_string && depth == 0 => {
if attr_idx == index {
let token = trim_ascii(&args[start..i]);
return !token.is_empty() && token != b"$";
}
attr_idx += 1;
start = i + 1;
}
_ => {}
}
i += 1;
}
if attr_idx == index {
let token = trim_ascii(&args[start..]);
return !token.is_empty() && token != b"$";
}
false
}
fn trim_ascii(bytes: &[u8]) -> &[u8] {
let mut s = 0usize;
let mut e = bytes.len();
while s < e && bytes[s].is_ascii_whitespace() {
s += 1;
}
while e > s && bytes[e - 1].is_ascii_whitespace() {
e -= 1;
}
&bytes[s..e]
}
pub fn is_simple_geometry_type(type_name: &str) -> bool {
static CACHE: OnceLock<RwLock<FxHashMap<String, bool>>> = OnceLock::new();
let cache = CACHE.get_or_init(|| RwLock::new(FxHashMap::default()));
let upper = normalise_uppercase(type_name);
cached(cache, upper.as_ref(), || compute_is_simple(upper.as_ref()))
}
pub fn legacy_aware_ifc_type(type_name: &str) -> IfcType {
let upper = normalise_uppercase(type_name);
match get_legacy_entity_info(upper.as_ref()) {
Some(info) => info.base_type,
None => IfcType::from_str(upper.as_ref()),
}
}
fn compute_is_simple(upper: &str) -> bool {
let t = legacy_aware_ifc_type(upper);
if matches!(t, IfcType::Unknown(_)) {
return true;
}
let is_secondary = t.is_subtype_of(IfcType::IfcOpeningElement)
|| t.is_subtype_of(IfcType::IfcWindow)
|| t.is_subtype_of(IfcType::IfcDoor)
|| t.is_subtype_of(IfcType::IfcFurnishingElement)
|| t.is_subtype_of(IfcType::IfcDistributionElement)
|| matches!(
t,
IfcType::IfcSpace
| IfcType::IfcSpatialZone
| IfcType::IfcSite
| IfcType::IfcAnnotation
| IfcType::IfcVirtualElement
| IfcType::IfcBuildingElementProxy
);
!is_secondary
}
#[cfg(test)]
#[path = "schema_helpers_tests.rs"]
mod tests;