use super::*;
use crate::router::GeometryProcessor;
use ifc_lite_core::{EntityDecoder, IfcSchema, IfcType};
#[test]
fn test_advanced_brep_file() {
use crate::router::GeometryRouter;
let content = std::fs::read_to_string("../../tests/models/ifcopenshell/advanced_brep.ifc")
.expect("Failed to read test file");
let entity_index = ifc_lite_core::build_entity_index(&content);
let mut decoder = EntityDecoder::with_index(&content, entity_index);
let router = GeometryRouter::new();
let element = decoder.decode_by_id(181).expect("Failed to decode element");
assert_eq!(element.ifc_type, IfcType::IfcBuildingElementProxy);
let mesh = router
.process_element(&element, &mut decoder)
.expect("Failed to process advanced brep");
assert!(!mesh.is_empty(), "AdvancedBrep should produce geometry");
assert!(
mesh.positions.len() >= 3 * 100,
"Should have significant geometry"
);
assert!(mesh.indices.len() >= 3 * 100, "Should have many triangles");
}
#[test]
fn test_extruded_area_solid() {
let content = r#"
#1=IFCRECTANGLEPROFILEDEF(.AREA.,$,$,100.0,200.0);
#2=IFCDIRECTION((0.0,0.0,1.0));
#3=IFCEXTRUDEDAREASOLID(#1,$,#2,300.0);
"#;
let mut decoder = EntityDecoder::new(content);
let schema = IfcSchema::new();
let processor = ExtrudedAreaSolidProcessor::new(schema.clone());
let entity = decoder.decode_by_id(3).unwrap();
let mesh = processor.process(&entity, &mut decoder, &schema).unwrap();
assert!(!mesh.is_empty());
assert!(!mesh.positions.is_empty());
assert!(!mesh.indices.is_empty());
}
#[test]
fn test_triangulated_face_set() {
let content = r#"
#1=IFCCARTESIANPOINTLIST3D(((0.0,0.0,0.0),(100.0,0.0,0.0),(50.0,100.0,0.0)));
#2=IFCTRIANGULATEDFACESET(#1,$,$,((1,2,3)),$);
"#;
let mut decoder = EntityDecoder::new(content);
let schema = IfcSchema::new();
let processor = TriangulatedFaceSetProcessor::new();
let entity = decoder.decode_by_id(2).unwrap();
let mesh = processor.process(&entity, &mut decoder, &schema).unwrap();
assert_eq!(mesh.positions.len(), 9); assert_eq!(mesh.indices.len(), 3); }
#[test]
fn test_boolean_result_with_half_space() {
let content = r#"
#1=IFCRECTANGLEPROFILEDEF(.AREA.,$,$,100.0,200.0);
#2=IFCDIRECTION((0.0,0.0,1.0));
#3=IFCEXTRUDEDAREASOLID(#1,$,#2,300.0);
#4=IFCCARTESIANPOINT((0.0,0.0,150.0));
#5=IFCDIRECTION((0.0,0.0,1.0));
#6=IFCAXIS2PLACEMENT3D(#4,#5,$);
#7=IFCPLANE(#6);
#8=IFCHALFSPACESOLID(#7,.T.);
#9=IFCBOOLEANRESULT(.DIFFERENCE.,#3,#8);
"#;
let mut decoder = EntityDecoder::new(content);
let schema = IfcSchema::new();
let processor = BooleanClippingProcessor::new();
let bool_result = decoder.decode_by_id(9).unwrap();
println!("BooleanResult type: {:?}", bool_result.ifc_type);
assert_eq!(bool_result.ifc_type, IfcType::IfcBooleanResult);
let half_space = decoder.decode_by_id(8).unwrap();
println!("HalfSpaceSolid type: {:?}", half_space.ifc_type);
assert_eq!(half_space.ifc_type, IfcType::IfcHalfSpaceSolid);
let mesh = processor
.process(&bool_result, &mut decoder, &schema)
.unwrap();
println!("Mesh vertices: {}", mesh.positions.len() / 3);
println!("Mesh triangles: {}", mesh.indices.len() / 3);
assert!(!mesh.is_empty(), "BooleanResult should produce geometry");
assert!(!mesh.positions.is_empty());
}
#[test]
fn test_polygonal_bounded_half_space_respects_boundary() {
let content = r#"
#1=IFCRECTANGLEPROFILEDEF(.AREA.,$,$,10.0,4.0);
#2=IFCDIRECTION((0.0,0.0,1.0));
#3=IFCEXTRUDEDAREASOLID(#1,$,#2,5.0);
#4=IFCCARTESIANPOINT((-5.0,0.0));
#5=IFCCARTESIANPOINT((5.0,0.0));
#6=IFCCARTESIANPOINT((5.0,3.0));
#7=IFCCARTESIANPOINT((-5.0,3.0));
#8=IFCPOLYLINE((#4,#5,#6,#7,#4));
#9=IFCCARTESIANPOINT((0.0,0.0,5.0));
#10=IFCDIRECTION((0.0,1.0,0.0));
#11=IFCDIRECTION((1.0,0.0,0.0));
#12=IFCAXIS2PLACEMENT3D(#9,#10,#11);
#13=IFCPLANE(#12);
#14=IFCAXIS2PLACEMENT3D(#9,#10,#11);
#15=IFCPOLYGONALBOUNDEDHALFSPACE(#13,.F.,#14,#8);
#16=IFCBOOLEANCLIPPINGRESULT(.DIFFERENCE.,#3,#15);
"#;
let mut decoder = EntityDecoder::new(content);
let schema = IfcSchema::new();
let processor = BooleanClippingProcessor::new();
let entity = decoder.decode_by_id(16).unwrap();
let mesh = processor.process(&entity, &mut decoder, &schema).unwrap();
assert!(
!mesh.is_empty(),
"Bounded half-space should still produce geometry"
);
let mut has_outer_base = false;
let mut has_outer_top = false;
let mut has_clipped_top = false;
for position in mesh.positions.chunks_exact(3) {
let y = position[1] as f64;
let z = position[2] as f64;
if y > 1.9 && z < 0.1 {
has_outer_base = true;
}
if y > 1.9 && z > 4.9 {
has_outer_top = true;
}
if y.abs() < 0.1 && z > 4.9 {
has_clipped_top = true;
}
}
assert!(
has_outer_base,
"The polygon boundary should only clip the upper strip, not the whole wall side"
);
assert!(
!has_outer_top,
"The clipped strip should be removed at the top of the bounded region"
);
assert!(
has_clipped_top,
"The bounded clip should create a new top edge at the cut boundary"
);
}
#[test]
fn test_764_column_file() {
use crate::router::GeometryRouter;
let content = std::fs::read_to_string(
"../../tests/models/ifcopenshell/764--column--no-materials-or-surface-styles-found--augmented.ifc"
).expect("Failed to read test file");
let entity_index = ifc_lite_core::build_entity_index(&content);
let mut decoder = EntityDecoder::with_index(&content, entity_index);
let router = GeometryRouter::new();
let column = decoder.decode_by_id(8930).expect("Failed to decode column");
println!("Column type: {:?}", column.ifc_type);
assert_eq!(column.ifc_type, IfcType::IfcColumn);
let rep_attr = column
.get(6)
.expect("Column missing representation attribute");
println!("Representation attr: {:?}", rep_attr);
match router.process_element(&column, &mut decoder) {
Ok(mesh) => {
println!("Mesh vertices: {}", mesh.positions.len() / 3);
println!("Mesh triangles: {}", mesh.indices.len() / 3);
assert!(!mesh.is_empty(), "Column should produce geometry");
}
Err(e) => {
panic!("Failed to process column: {:?}", e);
}
}
}
#[test]
fn test_wall_with_opening_file() {
use crate::router::GeometryRouter;
let content = std::fs::read_to_string(
"../../tests/models/buildingsmart/wall-with-opening-and-window.ifc",
)
.expect("Failed to read test file");
let entity_index = ifc_lite_core::build_entity_index(&content);
let mut decoder = EntityDecoder::with_index(&content, entity_index);
let router = GeometryRouter::new();
let wall = match decoder.decode_by_id(45) {
Ok(w) => w,
Err(e) => panic!("Failed to decode wall: {:?}", e),
};
println!("Wall type: {:?}", wall.ifc_type);
assert_eq!(wall.ifc_type, IfcType::IfcWall);
let rep_attr = wall.get(6).expect("Wall missing representation attribute");
println!("Representation attr: {:?}", rep_attr);
match router.process_element(&wall, &mut decoder) {
Ok(mesh) => {
println!("Wall mesh vertices: {}", mesh.positions.len() / 3);
println!("Wall mesh triangles: {}", mesh.indices.len() / 3);
assert!(!mesh.is_empty(), "Wall should produce geometry");
}
Err(e) => {
panic!("Failed to process wall: {:?}", e);
}
}
let window = decoder.decode_by_id(102).expect("Failed to decode window");
println!("Window type: {:?}", window.ifc_type);
assert_eq!(window.ifc_type, IfcType::IfcWindow);
match router.process_element(&window, &mut decoder) {
Ok(mesh) => {
println!("Window mesh vertices: {}", mesh.positions.len() / 3);
println!("Window mesh triangles: {}", mesh.indices.len() / 3);
}
Err(e) => {
println!("Window error (might be expected): {:?}", e);
}
}
}
#[test]
fn test_shell_based_surface_model_with_advanced_faces() {
let content = r#"
#1=IFCCARTESIANPOINT((0.,0.,0.));
#2=IFCCARTESIANPOINT((100.,0.,0.));
#3=IFCCARTESIANPOINT((100.,100.,0.));
#4=IFCCARTESIANPOINT((0.,100.,0.));
#5=IFCVERTEXPOINT(#1);
#6=IFCVERTEXPOINT(#2);
#7=IFCVERTEXPOINT(#3);
#8=IFCVERTEXPOINT(#4);
#9=IFCDIRECTION((0.,0.,1.));
#10=IFCDIRECTION((1.,0.,0.));
#11=IFCAXIS2PLACEMENT3D(#1,#9,#10);
#12=IFCPLANE(#11);
#13=IFCLINE(#1,#20);
#14=IFCLINE(#2,#21);
#15=IFCLINE(#3,#22);
#16=IFCLINE(#4,#23);
#20=IFCVECTOR(#24,1.);
#21=IFCVECTOR(#25,1.);
#22=IFCVECTOR(#26,1.);
#23=IFCVECTOR(#27,1.);
#24=IFCDIRECTION((1.,0.,0.));
#25=IFCDIRECTION((0.,1.,0.));
#26=IFCDIRECTION((-1.,0.,0.));
#27=IFCDIRECTION((0.,-1.,0.));
#30=IFCEDGECURVE(#5,#6,#13,.T.);
#31=IFCEDGECURVE(#6,#7,#14,.T.);
#32=IFCEDGECURVE(#7,#8,#15,.T.);
#33=IFCEDGECURVE(#8,#5,#16,.T.);
#40=IFCORIENTEDEDGE(*,*,#30,.T.);
#41=IFCORIENTEDEDGE(*,*,#31,.T.);
#42=IFCORIENTEDEDGE(*,*,#32,.T.);
#43=IFCORIENTEDEDGE(*,*,#33,.T.);
#50=IFCEDGELOOP((#40,#41,#42,#43));
#51=IFCFACEOUTERBOUND(#50,.T.);
#52=IFCADVANCEDFACE((#51),#12,.T.);
#53=IFCOPENSHELL((#52));
#54=IFCSHELLBASEDSURFACEMODEL((#53));
"#;
let mut decoder = EntityDecoder::new(content);
let schema = IfcSchema::new();
let processor = ShellBasedSurfaceModelProcessor::new();
let entity = decoder.decode_by_id(54).unwrap();
assert_eq!(entity.ifc_type, IfcType::IfcShellBasedSurfaceModel);
let mesh = processor
.process(&entity, &mut decoder, &schema)
.expect("Failed to process ShellBasedSurfaceModel with AdvancedFace");
assert!(
!mesh.is_empty(),
"ShellBasedSurfaceModel with AdvancedFace should produce geometry"
);
assert!(
mesh.positions.len() >= 12,
"Should have at least 4 vertices (quad face): got {} floats",
mesh.positions.len()
);
assert!(
mesh.indices.len() >= 6,
"Should have at least 2 triangles: got {} indices",
mesh.indices.len()
);
}
#[test]
fn test_shell_based_surface_model_with_polyloop() {
let content = r#"
#1=IFCCARTESIANPOINT((0.,0.,0.));
#2=IFCCARTESIANPOINT((100.,0.,0.));
#3=IFCCARTESIANPOINT((100.,100.,0.));
#4=IFCCARTESIANPOINT((0.,100.,0.));
#10=IFCPOLYLOOP((#1,#2,#3,#4));
#11=IFCFACEOUTERBOUND(#10,.T.);
#12=IFCFACE((#11));
#13=IFCOPENSHELL((#12));
#14=IFCSHELLBASEDSURFACEMODEL((#13));
"#;
let mut decoder = EntityDecoder::new(content);
let schema = IfcSchema::new();
let processor = ShellBasedSurfaceModelProcessor::new();
let entity = decoder.decode_by_id(14).unwrap();
let mesh = processor
.process(&entity, &mut decoder, &schema)
.expect("Failed to process ShellBasedSurfaceModel with PolyLoop");
assert!(
!mesh.is_empty(),
"ShellBasedSurfaceModel with PolyLoop should still produce geometry"
);
assert_eq!(
mesh.positions.len(),
12,
"Should have 4 vertices (12 floats)"
);
assert_eq!(mesh.indices.len(), 6, "Should have 2 triangles (6 indices)");
}
#[test]
fn test_catia_surface_model_file() {
use crate::router::GeometryRouter;
let path = "../../tests/models/various/2222.ifc";
let content = match std::fs::read_to_string(path) {
Ok(c) => c,
Err(_) => {
eprintln!("Skipping test_catia_surface_model_file: {} not found", path);
return;
}
};
let entity_index = ifc_lite_core::build_entity_index(&content);
let mut decoder = EntityDecoder::with_index(&content, entity_index);
let router = GeometryRouter::with_units(&content, &mut decoder);
let ramp = decoder.decode_by_id(7963).expect("Failed to decode IfcRamp #7963");
assert_eq!(ramp.ifc_type, IfcType::IfcRamp);
let mesh = router
.process_element(&ramp, &mut decoder)
.expect("Failed to process CATIA IfcRamp SurfaceModel");
println!(
"CATIA IfcRamp: {} vertices, {} triangles",
mesh.positions.len() / 3,
mesh.indices.len() / 3
);
assert!(
!mesh.is_empty(),
"CATIA SurfaceModel should produce geometry"
);
assert!(
mesh.positions.len() / 3 > 500,
"Should have >500 vertices from AdvancedFaces, got {}",
mesh.positions.len() / 3
);
}
#[test]
fn test_triangulated_face_set_out_of_bounds_indices() {
let content = r#"
#1=IFCCARTESIANPOINTLIST3D(((0.0,0.0,0.0),(100.0,0.0,0.0),(50.0,100.0,0.0)));
#2=IFCTRIANGULATEDFACESET(#1,$,$,((1,2,3),(1,2,99)),$);
"#;
let mut decoder = EntityDecoder::new(content);
let schema = IfcSchema::new();
let processor = TriangulatedFaceSetProcessor::new();
let entity = decoder.decode_by_id(2).unwrap();
let mesh = processor.process(&entity, &mut decoder, &schema).unwrap();
assert!(!mesh.is_empty());
assert_eq!(mesh.indices.len(), 3, "Should have exactly 1 valid triangle");
assert!(mesh.indices.iter().all(|&i| (i as usize) < mesh.positions.len() / 3));
}
#[test]
fn test_triangulated_face_set_all_invalid_indices() {
let content = r#"
#1=IFCCARTESIANPOINTLIST3D(((0.0,0.0,0.0),(1.0,0.0,0.0),(0.0,1.0,0.0)));
#2=IFCTRIANGULATEDFACESET(#1,$,$,((10,20,30)),$);
"#;
let mut decoder = EntityDecoder::new(content);
let schema = IfcSchema::new();
let processor = TriangulatedFaceSetProcessor::new();
let entity = decoder.decode_by_id(2).unwrap();
let mesh = processor.process(&entity, &mut decoder, &schema).unwrap();
assert!(mesh.indices.is_empty(), "All invalid indices should be stripped");
}