use super::*;
fn refs(ids: &[u32]) -> FxHashSet<u32> {
ids.iter().copied().collect()
}
#[test]
fn plan_type_geometry_orphan_type_emits_unreferenced_maps_as_class_1() {
for mode in [TypeGeometryMode::SuppressInstanced, TypeGeometryMode::EmitTagged] {
let planned = plan_type_geometry(&[10, 11, 12], &refs(&[11]), false, mode);
assert_eq!(
planned,
vec![(10, 1), (12, 1)],
"orphan type: unreferenced maps render as class 1 in {mode:?}",
);
}
}
#[test]
fn plan_type_geometry_instantiated_type_suppressed_for_export_tagged_for_viewer() {
let suppress = plan_type_geometry(
&[10, 11],
&refs(&[]),
true,
TypeGeometryMode::SuppressInstanced,
);
assert!(
suppress.is_empty(),
"an export must never duplicate an instanced type's geometry"
);
let tagged =
plan_type_geometry(&[10, 11], &refs(&[]), true, TypeGeometryMode::EmitTagged);
assert_eq!(
tagged,
vec![(10, 2), (11, 2)],
"the viewer renders instanced type maps tagged class 2 for the Types view"
);
}
#[test]
fn plan_type_geometry_referenced_maps_never_emit() {
let planned = plan_type_geometry(
&[10],
&refs(&[10]),
false,
TypeGeometryMode::EmitTagged,
);
assert!(
planned.is_empty(),
"a map an IfcMappedItem instantiates draws through its occurrence"
);
}
#[test]
fn find_geometry_item_color_follows_mapped_item() {
const IFC: &str = r#"ISO-10303-21;
HEADER;
FILE_DESCRIPTION((''),'2;1');
FILE_NAME('m.ifc','2026-06-04T00:00:00',(''),(''),'','','');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#2=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.0E-5,$,$);
#100=IFCMAPPEDITEM(#101,#105);
#101=IFCREPRESENTATIONMAP(#102,#103);
#102=IFCAXIS2PLACEMENT3D(#104,$,$);
#103=IFCSHAPEREPRESENTATION(#2,'Body','MappedRepresentation',(#110));
#104=IFCCARTESIANPOINT((0.,0.,0.));
#105=IFCCARTESIANTRANSFORMATIONOPERATOR3D($,$,#104,$,$);
ENDSEC;
END-ISO-10303-21;
"#;
let blue = [0.1, 0.2, 0.9, 1.0];
let mut styles: FxHashMap<u32, GeometryStyleInfo> = FxHashMap::default();
styles.insert(110, GeometryStyleInfo::from_color(blue));
let mut decoder = EntityDecoder::new(IFC);
assert_eq!(find_geometry_item_color(100, &styles, &mut decoder), Some(blue));
assert_eq!(find_geometry_item_color(110, &styles, &mut decoder), Some(blue));
assert_eq!(find_geometry_item_color(101, &styles, &mut decoder), None);
}
#[test]
fn infer_opening_material_names_glass_vs_frame() {
let glass =
infer_opening_subpart_material_name(&IfcType::IfcWindow, [0.7, 0.9, 0.5, 0.3], 42);
assert_eq!(glass.as_deref(), Some("Window_Glass"));
let frame =
infer_opening_subpart_material_name(&IfcType::IfcDoor, [0.5, 0.5, 0.5, 1.0], 7);
assert_eq!(frame.as_deref(), Some("Door_Frame_7"));
let none = infer_opening_subpart_material_name(&IfcType::IfcWall, [1.0; 4], 1);
assert!(none.is_none(), "only windows/doors get inferred part names");
}
#[test]
fn find_geometry_item_color_terminates_on_cyclic_mapping() {
const IFC: &str = r#"ISO-10303-21;
HEADER;
FILE_DESCRIPTION((''),'2;1');
FILE_NAME('m.ifc','2026-06-04T00:00:00',(''),(''),'','','');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#2=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.0E-5,$,$);
#100=IFCMAPPEDITEM(#101,$);
#101=IFCREPRESENTATIONMAP($,#103);
#103=IFCSHAPEREPRESENTATION(#2,'Body','MappedRepresentation',(#100));
ENDSEC;
END-ISO-10303-21;
"#;
let styles: FxHashMap<u32, GeometryStyleInfo> = FxHashMap::default();
let mut decoder = EntityDecoder::new(IFC);
assert_eq!(find_geometry_item_color(100, &styles, &mut decoder), None);
}
fn nested_mapped_chain(hops: u32) -> String {
let mut s = String::from(
"ISO-10303-21;\nHEADER;\nFILE_DESCRIPTION((''),'2;1');\n\
FILE_NAME('m.ifc','2026-06-04T00:00:00',(''),(''),'','','');\n\
FILE_SCHEMA(('IFC4'));\nENDSEC;\nDATA;\n\
#2=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.0E-5,$,$);\n",
);
for i in 0..hops {
let map_item = 200 + i;
let rep_map = 1000 + i;
let shape = 2000 + i;
let inner = if i + 1 == hops { 999 } else { 200 + i + 1 };
s.push_str(&format!("#{map_item}=IFCMAPPEDITEM(#{rep_map},$);\n"));
s.push_str(&format!("#{rep_map}=IFCREPRESENTATIONMAP($,#{shape});\n"));
s.push_str(&format!(
"#{shape}=IFCSHAPEREPRESENTATION(#2,'Body','MappedRepresentation',(#{inner}));\n"
));
}
s.push_str("ENDSEC;\nEND-ISO-10303-21;\n");
s
}
#[test]
fn find_geometry_item_color_resolves_exactly_at_the_depth_cap() {
let green = [0.0, 0.8, 0.2, 1.0];
let mut styles: FxHashMap<u32, GeometryStyleInfo> = FxHashMap::default();
styles.insert(999, GeometryStyleInfo::from_color(green));
let ifc = nested_mapped_chain(MAX_MAPPED_ITEM_DEPTH);
let mut decoder = EntityDecoder::new(&ifc);
assert_eq!(
find_geometry_item_color(200, &styles, &mut decoder),
Some(green),
"a chain exactly MAX_MAPPED_ITEM_DEPTH hops deep must still resolve"
);
}
#[test]
fn find_geometry_item_color_stops_one_hop_past_the_depth_cap() {
let green = [0.0, 0.8, 0.2, 1.0];
let mut styles: FxHashMap<u32, GeometryStyleInfo> = FxHashMap::default();
styles.insert(999, GeometryStyleInfo::from_color(green));
let ifc = nested_mapped_chain(MAX_MAPPED_ITEM_DEPTH + 1);
let mut decoder = EntityDecoder::new(&ifc);
assert_eq!(
find_geometry_item_color(200, &styles, &mut decoder),
None,
"one hop past the cap the chase gives up rather than recursing on"
);
}
#[test]
fn find_geometry_item_color_resolves_ordinary_nesting_depth() {
let green = [0.0, 0.8, 0.2, 1.0];
let mut styles: FxHashMap<u32, GeometryStyleInfo> = FxHashMap::default();
styles.insert(999, GeometryStyleInfo::from_color(green));
let ifc = nested_mapped_chain(8);
let mut decoder = EntityDecoder::new(&ifc);
assert_eq!(
find_geometry_item_color(200, &styles, &mut decoder),
Some(green),
"8 mapped-item hops is ordinary nesting; the cap must not swallow it"
);
}
#[test]
fn resolve_color_for_representation_map_terminates_on_cyclic_mapping() {
const IFC: &str = r#"ISO-10303-21;
HEADER;
FILE_DESCRIPTION((''),'2;1');
FILE_NAME('m.ifc','2026-06-04T00:00:00',(''),(''),'','','');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#2=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.0E-5,$,$);
#100=IFCMAPPEDITEM(#101,$);
#101=IFCREPRESENTATIONMAP($,#103);
#103=IFCSHAPEREPRESENTATION(#2,'Body','MappedRepresentation',(#100));
ENDSEC;
END-ISO-10303-21;
"#;
let styles: FxHashMap<u32, GeometryStyleInfo> = FxHashMap::default();
let mut decoder = EntityDecoder::new(IFC);
assert_eq!(
resolve_color_for_representation_map(101, &styles, &mut decoder),
None
);
}
#[test]
fn find_geometry_item_color_bounds_cyclic_fan_out_not_just_depth() {
const IFC: &str = r#"ISO-10303-21;
HEADER;
FILE_DESCRIPTION((''),'2;1');
FILE_NAME('m.ifc','2026-06-04T00:00:00',(''),(''),'','','');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#2=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.0E-5,$,$);
#100=IFCMAPPEDITEM(#101,$);
#101=IFCREPRESENTATIONMAP($,#103);
#103=IFCSHAPEREPRESENTATION(#2,'Body','MappedRepresentation',(#110,#111,#112,#113));
#110=IFCMAPPEDITEM(#101,$);
#111=IFCMAPPEDITEM(#101,$);
#112=IFCMAPPEDITEM(#101,$);
#113=IFCMAPPEDITEM(#101,$);
ENDSEC;
END-ISO-10303-21;
"#;
let styles: FxHashMap<u32, GeometryStyleInfo> = FxHashMap::default();
let mut decoder = EntityDecoder::new(IFC);
assert_eq!(find_geometry_item_color(100, &styles, &mut decoder), None);
}
#[test]
fn mapped_item_depth_cap_matches_the_geometry_router() {
assert_eq!(
MAX_MAPPED_ITEM_DEPTH, 32,
"must equal ifc_lite_geometry::router::processing::MAX_MAPPED_ITEM_DEPTH"
);
}
#[test]
fn a_shared_item_resolves_via_the_shallow_branch() {
let mut body = String::from(
"#200=IFCMAPPEDITEM(#201,$);\n\
#201=IFCREPRESENTATIONMAP($,#202);\n\
#202=IFCSHAPEREPRESENTATION(#2,'Body','MappedRepresentation',(#300,#900));\n",
);
for i in 0..30u32 {
let (item, rm, sh) = (300 + i, 400 + i, 500 + i);
let inner = if i == 29 { 900 } else { 300 + i + 1 };
body.push_str(&format!("#{item}=IFCMAPPEDITEM(#{rm},$);\n"));
body.push_str(&format!("#{rm}=IFCREPRESENTATIONMAP($,#{sh});\n"));
body.push_str(&format!(
"#{sh}=IFCSHAPEREPRESENTATION(#2,'Body','MappedRepresentation',(#{inner}));\n"
));
}
body.push_str(
"#900=IFCMAPPEDITEM(#901,$);\n\
#901=IFCREPRESENTATIONMAP($,#902);\n\
#902=IFCSHAPEREPRESENTATION(#2,'Body','MappedRepresentation',(#950));\n\
#950=IFCMAPPEDITEM(#951,$);\n\
#951=IFCREPRESENTATIONMAP($,#952);\n\
#952=IFCSHAPEREPRESENTATION(#2,'Body','MappedRepresentation',(#999));\n",
);
let ifc = format!(
"ISO-10303-21;\nHEADER;\nFILE_DESCRIPTION((''),'2;1');\n\
FILE_NAME('m.ifc','2026-06-04T00:00:00',(''),(''),'','','');\n\
FILE_SCHEMA(('IFC4'));\nENDSEC;\nDATA;\n\
#2=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.0E-5,$,$);\n{body}ENDSEC;\nEND-ISO-10303-21;\n"
);
let green = [0.0, 0.8, 0.2, 1.0];
let mut styles: FxHashMap<u32, GeometryStyleInfo> = FxHashMap::default();
styles.insert(999, GeometryStyleInfo::from_color(green));
let mut decoder = EntityDecoder::new(&ifc);
assert_eq!(
find_geometry_item_color(200, &styles, &mut decoder),
Some(green),
"the shallow branch reaches the styled leaf well inside the cap"
);
}