use crate::parallel_scan::ShardRecords;
use ifc_lite_core::EntityScanner;
pub const PREPASS_CLASS_NONE: u8 = 0;
pub const PREPASS_CLASS_STYLED_ITEM: u8 = 4;
pub const PREPASS_CLASS_INDEXED_COLOUR_MAP: u8 = 5;
pub const PREPASS_CLASS_MATERIAL_DEF_REPR: u8 = 6;
pub const PREPASS_CLASS_REL_ASSOCIATES_MATERIAL: u8 = 7;
pub const PREPASS_CLASS_REL_VOIDS: u8 = 8;
pub const PREPASS_CLASS_REL_FILLS: u8 = 9;
pub const PREPASS_CLASS_REL_AGGREGATES: u8 = 10;
pub const PREPASS_CLASS_PROJECT: u8 = 2;
pub const PREPASS_CLASS_SITE: u8 = 3;
pub const PREPASS_CLASS_MATERIAL_LAYER_SET: u8 = 13;
pub const PREPASS_CLASS_FLAG_GEOMETRY_JOB: u8 = 0x80;
pub const PREPASS_CLASS_FLAG_TYPE_CANDIDATE: u8 = 0x40;
pub const PREPASS_CLASS_MAPPED_ITEM: u8 = 11;
pub const PREPASS_CLASS_REL_DEFINES_BY_TYPE: u8 = 12;
pub const PREPASS_CLASS_CODE_MASK: u8 = 0x3F;
pub fn scan_shard_classified(
content: &[u8],
range_start: usize,
range_end: usize,
) -> (ShardRecords, Vec<u8>, Option<usize>) {
let mut scanner = if range_start == 0 {
EntityScanner::new(content)
} else {
EntityScanner::new_at(content, range_start)
};
let mut records = Vec::new();
let mut classes = Vec::new();
let mut handoff = None;
while let Some((id, type_name, start, entity_end)) = scanner.next_entity() {
if start >= range_end {
handoff = Some(start);
break;
}
records.push((id, start, entity_end));
classes.push(classify_type_name_with_content(
type_name,
&content[start..entity_end],
));
}
(records, classes, handoff)
}
pub fn classify_type_name_with_content(type_name: &str, entity_bytes: &[u8]) -> u8 {
let mut class = classify_type_name(type_name);
if class & PREPASS_CLASS_FLAG_GEOMETRY_JOB == 0
&& ifc_lite_core::is_representationless_spatial_container_by_name(type_name)
&& ifc_lite_core::nth_attribute_is_present(entity_bytes, 6)
{
class |= PREPASS_CLASS_FLAG_GEOMETRY_JOB;
}
class
}
pub fn classify_type_name(type_name: &str) -> u8 {
use ifc_lite_core::{has_geometry_by_name, IfcType};
let named = match type_name {
"IFCPROJECT" => PREPASS_CLASS_PROJECT,
"IFCSITE" => return PREPASS_CLASS_SITE, "IFCSTYLEDITEM" => PREPASS_CLASS_STYLED_ITEM,
"IFCINDEXEDCOLOURMAP" => PREPASS_CLASS_INDEXED_COLOUR_MAP,
"IFCMATERIALDEFINITIONREPRESENTATION" => PREPASS_CLASS_MATERIAL_DEF_REPR,
"IFCRELASSOCIATESMATERIAL" => PREPASS_CLASS_REL_ASSOCIATES_MATERIAL,
"IFCRELVOIDSELEMENT" => PREPASS_CLASS_REL_VOIDS,
"IFCRELFILLSELEMENT" => PREPASS_CLASS_REL_FILLS,
"IFCRELAGGREGATES" => PREPASS_CLASS_REL_AGGREGATES,
"IFCMAPPEDITEM" => PREPASS_CLASS_MAPPED_ITEM,
"IFCRELDEFINESBYTYPE" => PREPASS_CLASS_REL_DEFINES_BY_TYPE,
"IFCMATERIALLAYERSET" | "IFCMATERIALLAYERSETUSAGE" => PREPASS_CLASS_MATERIAL_LAYER_SET,
_ => PREPASS_CLASS_NONE,
};
if named != PREPASS_CLASS_NONE {
return named;
}
let mut class = PREPASS_CLASS_NONE;
if type_name.ends_with("TYPE") || type_name.ends_with("STYLE") {
let ty = IfcType::from_str(type_name);
if ty.is_subtype_of(IfcType::IfcTypeProduct) {
class |= PREPASS_CLASS_FLAG_TYPE_CANDIDATE;
}
}
if has_geometry_by_name(type_name) {
class |= PREPASS_CLASS_FLAG_GEOMETRY_JOB;
}
class
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn type_suffix_sets_type_candidate_flag() {
let class = classify_type_name("IFCWALLTYPE");
assert_eq!(
class & PREPASS_CLASS_FLAG_TYPE_CANDIDATE,
PREPASS_CLASS_FLAG_TYPE_CANDIDATE,
"IFCWALLTYPE is an IfcTypeProduct subtype ending in TYPE, not STYLE — \
it must set the type-candidate flag via the TYPE arm alone"
);
}
#[test]
fn style_suffix_without_type_product_subtype_does_not_set_flag() {
let class = classify_type_name("IFCSURFACESTYLE");
assert_eq!(
class & PREPASS_CLASS_FLAG_TYPE_CANDIDATE,
0,
"IFCSURFACESTYLE is not an IfcTypeProduct subtype, so the type-candidate \
flag must stay clear even though the name ends in STYLE"
);
}
#[test]
fn classify_type_name_pins_every_named_arm_to_a_distinct_code() {
let cases = [
("IFCPROJECT", PREPASS_CLASS_PROJECT),
("IFCSITE", PREPASS_CLASS_SITE),
("IFCSTYLEDITEM", PREPASS_CLASS_STYLED_ITEM),
("IFCINDEXEDCOLOURMAP", PREPASS_CLASS_INDEXED_COLOUR_MAP),
("IFCMATERIALDEFINITIONREPRESENTATION", PREPASS_CLASS_MATERIAL_DEF_REPR),
("IFCRELASSOCIATESMATERIAL", PREPASS_CLASS_REL_ASSOCIATES_MATERIAL),
("IFCRELVOIDSELEMENT", PREPASS_CLASS_REL_VOIDS),
("IFCRELFILLSELEMENT", PREPASS_CLASS_REL_FILLS),
("IFCRELAGGREGATES", PREPASS_CLASS_REL_AGGREGATES),
("IFCMAPPEDITEM", PREPASS_CLASS_MAPPED_ITEM),
("IFCRELDEFINESBYTYPE", PREPASS_CLASS_REL_DEFINES_BY_TYPE),
("IFCMATERIALLAYERSET", PREPASS_CLASS_MATERIAL_LAYER_SET),
("IFCMATERIALLAYERSETUSAGE", PREPASS_CLASS_MATERIAL_LAYER_SET),
];
for (keyword, expected) in cases {
assert_eq!(
classify_type_name(keyword),
expected,
"{keyword} classified as {} not {expected}",
classify_type_name(keyword)
);
}
let distinct_codes: std::collections::HashSet<u8> =
cases.iter().map(|(_, c)| *c).collect();
assert_eq!(distinct_codes.len(), 12, "expected 12 distinct codes across 13 keywords (2 share MATERIAL_LAYER_SET)");
assert_eq!(classify_type_name("IFCUNKNOWNTHING"), PREPASS_CLASS_NONE);
}
}