use super::*;
use ifc_lite_core::{EntityIndex, EntityScanner};
#[test]
fn find_ifcproject_id_late_in_file() {
let ifc = b"ISO-10303-21;\nDATA;\n#1=IFCWALL('x',$,$,$,$,$,$,$,$);\n#999123=IFCPROJECT('g',$,'P',$,$,$,$,$,$);\nENDSEC;\n";
assert_eq!(find_ifcproject_id(ifc), Some(999123));
}
#[test]
fn find_ifcproject_id_absent() {
let ifc = b"ISO-10303-21;\nDATA;\n#1=IFCWALL('x',$,$,$,$,$,$,$,$);\nENDSEC;\n";
assert_eq!(find_ifcproject_id(ifc), None);
}
#[test]
fn find_ifcproject_id_skips_string_decoys() {
let ifc = b"DATA;\n#5=IFCWALL('decoy =IFCPROJECT( in a name',$);\n#7=IFCPROJECT('g',$);\n";
assert_eq!(find_ifcproject_id(ifc), Some(7));
}
#[test]
fn find_ifcproject_id_handles_whitespace_around_equals() {
let space_after = b"DATA;\n#1=IFCWALL('x',$);\n#1593796= IFCPROJECT('g',$,'P',$,$,$,$,$,$);\n";
assert_eq!(find_ifcproject_id(space_after), Some(1593796));
let space_both = b"DATA;\n#42 = IFCPROJECT('g',$);\n";
assert_eq!(find_ifcproject_id(space_both), Some(42));
let crs_only = b"DATA;\n#9= IFCPROJECTEDCRS('EPSG:32632',$,'WGS84',$,'UTM','32N',$);\n";
assert_eq!(find_ifcproject_id(crs_only), None);
}
#[test]
fn find_ifcproject_id_ordinary_id_is_unaffected() {
let ifc = b"DATA;\n#42=IFCPROJECT('g',$,'P',$,$,$,$,$,$);\n";
assert_eq!(find_ifcproject_id(ifc), Some(42));
}
#[test]
fn find_ifcproject_id_accepts_a_ref_at_exactly_u32_max() {
let ifc = b"DATA;\n#4294967295=IFCPROJECT('g',$,'P',$,$,$,$,$,$);\n";
assert_eq!(find_ifcproject_id(ifc), Some(u32::MAX));
}
#[test]
fn find_ifcproject_id_refuses_a_ref_above_u32_max_instead_of_wrapping_onto_a_real_entity() {
let ifc = b"DATA;\n#1=IFCWALL('x',$,$,$,$,$,$,$,$);\n#4294967297=IFCPROJECT('g',$,'P',$,$,$,$,$,$);\nENDSEC;\n";
assert_eq!(
find_ifcproject_id(ifc),
None,
"an oversized IFCPROJECT id must be refused, never aliased onto id 1 (the IFCWALL)"
);
}
#[test]
fn resolve_unit_scales_recovers_degrees_when_measure_past_partial_index() {
const IFC: &[u8] = br#"ISO-10303-21;
HEADER;
FILE_DESCRIPTION((''),'2;1');
FILE_NAME('u.ifc','2026-06-26T00:00:00',(''),(''),'','','');
FILE_SCHEMA(('IFC2X3'));
ENDSEC;
DATA;
#10= IFCPROJECT('g',$,'P',$,$,$,$,$,#11);
#11= IFCUNITASSIGNMENT((#12,#13));
#12= IFCSIUNIT(*,.LENGTHUNIT.,.MILLI.,.METRE.);
#13= IFCCONVERSIONBASEDUNIT(#14,.PLANEANGLEUNIT.,'DEGREE',#15);
#14= IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
#16= IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
#15= IFCMEASUREWITHUNIT(IFCRATIOMEASURE(0.0174532925199433),#16);
ENDSEC;
END-ISO-10303-21;
"#;
let mut partial = EntityIndex::default();
let mut scanner = EntityScanner::new(&IFC);
while let Some((id, _t, start, end)) = scanner.next_entity() {
if id == 15 || id == 14 {
continue; }
partial.insert(id, (start, end));
}
let mut decoder = EntityDecoder::with_index(IFC, partial);
let scales = resolve_unit_scales(IFC, Some(10), &mut decoder);
assert_eq!(scales.project_id, Some(10));
assert!((scales.length_unit_scale - 0.001).abs() < 1e-12);
assert!(
(scales.plane_angle_to_radians - 0.0174532925199433).abs() < 1e-12,
"expected degrees via full-index retry, got {}",
scales.plane_angle_to_radians
);
}
#[test]
fn material_colors_flat_round_trip() {
let mut map: FxHashMap<u32, Vec<[f32; 4]>> = FxHashMap::default();
map.insert(10, vec![[0.5, 0.5, 0.5, 1.0], [0.7, 0.9, 0.5, 0.2]]);
map.insert(42, vec![[1.0, 0.0, 0.0, 1.0]]);
let (ids, counts, rgba) = flat_material_colors(&map);
let back = material_colors_from_flat(&ids, &counts, &rgba);
assert_eq!(back.len(), 2);
assert_eq!(back[&42].len(), 1);
assert_eq!(back[&10].len(), 2);
for (orig, round) in map[&10].iter().zip(back[&10].iter()) {
for (a, b) in orig.iter().zip(round.iter()) {
assert!((a - b).abs() <= 1.0 / 255.0 + 1e-6);
}
}
}
#[test]
fn flat_wire_arrays_are_sorted_by_id() {
let mut voids: FxHashMap<u32, Vec<u32>> = FxHashMap::default();
voids.insert(300, vec![301, 302]);
voids.insert(7, vec![8]);
voids.insert(90, vec![91]);
let (keys, counts, values) = flat_voids(&voids);
assert_eq!(keys, vec![7, 90, 300]);
assert_eq!(counts, vec![1, 1, 2]);
assert_eq!(values, vec![8, 91, 301, 302]);
let mut colors: FxHashMap<u32, Vec<[f32; 4]>> = FxHashMap::default();
colors.insert(42, vec![[1.0, 0.0, 0.0, 1.0]]);
colors.insert(10, vec![[0.0, 1.0, 0.0, 1.0], [0.0, 0.0, 1.0, 0.5]]);
let (ids, counts, rgba) = flat_material_colors(&colors);
assert_eq!(ids, vec![10, 42]);
assert_eq!(counts, vec![2, 1]);
assert_eq!(rgba.len(), 12);
assert_eq!(&rgba[0..4], &[0, 255, 0, 255]);
}
#[test]
fn resolve_unit_scales_resolves_degrees_and_millimetres() {
const IFC: &[u8] = br#"ISO-10303-21;
HEADER;
FILE_DESCRIPTION((''),'2;1');
FILE_NAME('u.ifc','2026-06-12T00:00:00',(''),(''),'','','');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCWALL('w',$,$,$,$,$,$,$,$);
#10=IFCPROJECT('g',$,'P',$,$,$,$,$,#11);
#11=IFCUNITASSIGNMENT((#12,#13));
#12=IFCSIUNIT(*,.LENGTHUNIT.,.MILLI.,.METRE.);
#13=IFCCONVERSIONBASEDUNIT(#14,.PLANEANGLEUNIT.,'DEGREE',#15);
#14=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
#15=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16);
#16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
ENDSEC;
END-ISO-10303-21;
"#;
let mut decoder = EntityDecoder::new(IFC);
let scales = resolve_unit_scales(IFC, None, &mut decoder);
assert_eq!(scales.project_id, Some(10));
assert!((scales.length_unit_scale - 0.001).abs() < 1e-12);
assert!((scales.plane_angle_to_radians - 0.017_453_292_519_943_295).abs() < 1e-12);
}