use super::*;
const HEADER: &str = r#"ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('Test'),'2;1');
FILE_NAME('test.ifc','2024-01-01',(''),(''),'','','');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
"#;
const FOOTER: &str = "ENDSEC;\nEND-ISO-10303-21;\n";
fn decode(body: &str, id: u32) -> DecodedEntity {
let content = format!("{HEADER}{body}{FOOTER}");
let mut decoder = EntityDecoder::new(&content);
decoder.decode_by_id(id).expect("entity should decode")
}
#[test]
fn non_placement_entity_is_not_silently_misread() {
let body = "\
#1=IFCAXIS1PLACEMENT(#2,#3);
#2=IFCCARTESIANPOINT((5.,6.,7.));
#3=IFCDIRECTION((0.,1.,0.));
";
let content = format!("{HEADER}{body}{FOOTER}");
let mut decoder = EntityDecoder::new(&content);
let entity = decoder.decode_by_id(1).expect("entity should decode");
assert_eq!(entity.ifc_type, IfcType::IfcAxis1Placement);
let result = parse_axis2_placement_2d(&entity, &mut decoder, 1.0);
assert!(
result.tz.is_nan(),
"expected unresolved() for a non-placement entity, got a real transform: {result:?}"
);
}
#[test]
fn genuine_3d_placement_still_parses() {
let body = "\
#1=IFCAXIS2PLACEMENT3D(#2,#3,#4);
#2=IFCCARTESIANPOINT((1.,2.,3.));
#3=IFCDIRECTION((0.,0.,1.));
#4=IFCDIRECTION((0.,1.,0.));
";
let entity = decode(body, 1);
let content = format!("{HEADER}{body}{FOOTER}");
let mut decoder = EntityDecoder::new(&content);
let result = parse_axis2_placement_2d(&entity, &mut decoder, 1.0);
assert!(result.tz.is_finite(), "genuine 3D placement must resolve: {result:?}");
assert!((result.tx - 1.0).abs() < 1e-4);
assert!((result.ty - 2.0).abs() < 1e-4);
assert!((result.tz - 3.0).abs() < 1e-4);
assert!((result.m10 - 1.0).abs() < 1e-4);
}
#[test]
fn genuine_2d_placement_still_parses() {
let body = "\
#1=IFCAXIS2PLACEMENT2D(#2,#3);
#2=IFCCARTESIANPOINT((10.,20.));
#3=IFCDIRECTION((0.,1.));
";
let entity = decode(body, 1);
let content = format!("{HEADER}{body}{FOOTER}");
let mut decoder = EntityDecoder::new(&content);
let result = parse_axis2_placement_2d(&entity, &mut decoder, 1.0);
assert!(result.tz.is_finite(), "genuine 2D placement must resolve: {result:?}");
assert!((result.tx - 10.0).abs() < 1e-4);
assert!((result.ty - 20.0).abs() < 1e-4);
assert!((result.m10 - 1.0).abs() < 1e-4);
}
#[test]
fn operator_local_origin_z_reaches_tz() {
let body = "\
#1=IFCCARTESIANTRANSFORMATIONOPERATOR3D($,$,#2,$,$);
#2=IFCCARTESIANPOINT((1.,2.,3.));
";
let entity = decode(body, 1);
let content = format!("{HEADER}{body}{FOOTER}");
let mut decoder = EntityDecoder::new(&content);
let result = parse_cartesian_transformation_operator(&entity, &mut decoder, 1.0);
assert!((result.tx - 1.0).abs() < 1e-4, "tx: {result:?}");
assert!((result.ty - 2.0).abs() < 1e-4, "ty: {result:?}");
assert!((result.tz - 3.0).abs() < 1e-4, "LocalOrigin Z must reach tz: {result:?}");
}
#[test]
fn operator_local_origin_z_is_unit_scaled_like_x_and_y() {
let body = "\
#1=IFCCARTESIANTRANSFORMATIONOPERATOR3D($,$,#2,$,$);
#2=IFCCARTESIANPOINT((1000.,2000.,3000.));
";
let entity = decode(body, 1);
let content = format!("{HEADER}{body}{FOOTER}");
let mut decoder = EntityDecoder::new(&content);
let result = parse_cartesian_transformation_operator(&entity, &mut decoder, 0.001);
assert!((result.tx - 1.0).abs() < 1e-4, "tx: {result:?}");
assert!((result.ty - 2.0).abs() < 1e-4, "ty: {result:?}");
assert!((result.tz - 3.0).abs() < 1e-4, "tz must take the same unit_scale: {result:?}");
}
#[test]
fn operator_2d_local_origin_leaves_tz_zero() {
let body = "\
#1=IFCCARTESIANTRANSFORMATIONOPERATOR2D($,$,#2,$);
#2=IFCCARTESIANPOINT((4.,5.));
";
let entity = decode(body, 1);
let content = format!("{HEADER}{body}{FOOTER}");
let mut decoder = EntityDecoder::new(&content);
let result = parse_cartesian_transformation_operator(&entity, &mut decoder, 1.0);
assert!((result.tx - 4.0).abs() < 1e-4, "tx: {result:?}");
assert!((result.ty - 5.0).abs() < 1e-4, "ty: {result:?}");
assert!(result.tz.abs() < 1e-6, "2D operator must leave tz at 0: {result:?}");
}