use ifc_lite_processing::extract_symbolic_data;
const FIXTURE: &str = r#"ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('issue-2256 mod.rs WCS fixture'),'2;1');
FILE_NAME('test.ifc','2026-08-07T00:00:00',(''),(''),'','','');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROJECT('0$ScRe4drECQ4DMSqUjd6d',$,'P',$,$,$,$,(#12),#3);
#3=IFCUNITASSIGNMENT((#6));
#6=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
#4=IFCCARTESIANPOINT((0.,0.,0.));
#5=IFCAXIS2PLACEMENT3D(#4,$,$);
#40=IFCLOCALPLACEMENT($,#5);
/* Context A: pure Z-offset WCS (the critical case — hits both defects). */
#10=IFCCARTESIANPOINT((0.,0.,7.));
#11=IFCAXIS2PLACEMENT3D(#10,$,$);
#12=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Plan',3,1.0E-5,#11,$);
#20=IFCCARTESIANPOINT((0.,0.));
#21=IFCCARTESIANPOINT((1.,0.));
#22=IFCCARTESIANPOINT((1.,1.));
#23=IFCCARTESIANPOINT((0.,1.));
#24=IFCCARTESIANPOINT((0.,0.));
#25=IFCPOLYLINE((#20,#21,#22,#23,#24));
#26=IFCSHAPEREPRESENTATION(#12,'Annotation','Annotation2D',(#25));
#27=IFCPRODUCTDEFINITIONSHAPE($,$,(#26));
#28=IFCANNOTATION('1xScRe4drECQ4DMSqUjdZA',$,'ZOffsetNote',$,$,#40,#27);
/* Context B: pure X/Y-offset WCS (proves x/y also compose, not just tz). */
#30=IFCCARTESIANPOINT((10.,20.,0.));
#31=IFCAXIS2PLACEMENT3D(#30,$,$);
#32=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Plan',3,1.0E-5,#31,$);
#33=IFCCARTESIANPOINT((0.,0.));
#34=IFCCARTESIANPOINT((1.,0.));
#35=IFCCARTESIANPOINT((1.,1.));
#36=IFCCARTESIANPOINT((0.,1.));
#37=IFCCARTESIANPOINT((0.,0.));
#38=IFCPOLYLINE((#33,#34,#35,#36,#37));
#39=IFCSHAPEREPRESENTATION(#32,'Annotation','Annotation2D',(#38));
#41=IFCPRODUCTDEFINITIONSHAPE($,$,(#39));
#42=IFCANNOTATION('2xScRe4drECQ4DMSqUjdXY',$,'XYOffsetNote',$,$,#40,#41);
/* Context C: WorldCoordinateSystem is a dangling ref (#9999 doesn't exist). */
#50=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Plan',3,1.0E-5,#9999,$);
#51=IFCCARTESIANPOINT((0.,0.));
#52=IFCCARTESIANPOINT((1.,0.));
#53=IFCCARTESIANPOINT((1.,1.));
#54=IFCCARTESIANPOINT((0.,1.));
#55=IFCCARTESIANPOINT((0.,0.));
#56=IFCPOLYLINE((#51,#52,#53,#54,#55));
#57=IFCSHAPEREPRESENTATION(#50,'Annotation','Annotation2D',(#56));
#58=IFCPRODUCTDEFINITIONSHAPE($,$,(#57));
#59=IFCANNOTATION('3xScRe4drECQ4DMSqUjdWC',$,'DanglingWcsNote',$,$,#40,#58);
/* Context D: WorldCoordinateSystem attribute is null ($) — mandatory but absent. */
#61=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Plan',3,1.0E-5,$,$);
#62=IFCCARTESIANPOINT((0.,0.));
#63=IFCCARTESIANPOINT((1.,0.));
#64=IFCCARTESIANPOINT((1.,1.));
#65=IFCCARTESIANPOINT((0.,1.));
#66=IFCCARTESIANPOINT((0.,0.));
#67=IFCPOLYLINE((#62,#63,#64,#65,#66));
#68=IFCSHAPEREPRESENTATION(#61,'Annotation','Annotation2D',(#67));
#69=IFCPRODUCTDEFINITIONSHAPE($,$,(#68));
#70=IFCANNOTATION('4xScRe4drECQ4DMSqUjdWN',$,'NullWcsNote',$,$,#40,#69);
/* ContextOfItems is a dangling ref (#9998 doesn't exist). */
#82=IFCCARTESIANPOINT((0.,0.));
#83=IFCCARTESIANPOINT((1.,0.));
#84=IFCCARTESIANPOINT((1.,1.));
#85=IFCCARTESIANPOINT((0.,1.));
#86=IFCCARTESIANPOINT((0.,0.));
#87=IFCPOLYLINE((#82,#83,#84,#85,#86));
#88=IFCSHAPEREPRESENTATION(#9998,'Annotation','Annotation2D',(#87));
#89=IFCPRODUCTDEFINITIONSHAPE($,$,(#88));
#90=IFCANNOTATION('5xScRe4drECQ4DMSqUjdDC',$,'DanglingContextNote',$,$,#40,#89);
/* ContextOfItems attribute is null ($) — mandatory but absent. */
#92=IFCCARTESIANPOINT((0.,0.));
#93=IFCCARTESIANPOINT((1.,0.));
#94=IFCCARTESIANPOINT((1.,1.));
#95=IFCCARTESIANPOINT((0.,1.));
#96=IFCCARTESIANPOINT((0.,0.));
#97=IFCPOLYLINE((#92,#93,#94,#95,#96));
#98=IFCSHAPEREPRESENTATION($,'Annotation','Annotation2D',(#97));
#99=IFCPRODUCTDEFINITIONSHAPE($,$,(#98));
#100=IFCANNOTATION('6xScRe4drECQ4DMSqUjdNC',$,'NullContextNote',$,$,#40,#99);
/* SubContext of Context A — must NOT inherit A's Z offset (bounding control). */
#105=IFCGEOMETRICREPRESENTATIONSUBCONTEXT($,'Body',*,*,*,*,#12,$,.MODEL_VIEW.,$);
#106=IFCCARTESIANPOINT((0.,0.));
#107=IFCCARTESIANPOINT((1.,0.));
#108=IFCCARTESIANPOINT((1.,1.));
#109=IFCCARTESIANPOINT((0.,1.));
#110=IFCCARTESIANPOINT((0.,0.));
#111=IFCPOLYLINE((#106,#107,#108,#109,#110));
#112=IFCSHAPEREPRESENTATION(#105,'Annotation','Annotation2D',(#111));
#113=IFCPRODUCTDEFINITIONSHAPE($,$,(#112));
#114=IFCANNOTATION('7xScRe4drECQ4DMSqUjdSC',$,'SubContextNote',$,$,#40,#113);
/* Context F: identity WCS (Location at origin, no rotation) — bounding
control (a): a model with an identity WorldCoordinateSystem must produce
byte-identical placement to today, proving the fix does not move every
annotation in every existing model. */
#150=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Plan',3,1.0E-5,#5,$);
#151=IFCCARTESIANPOINT((0.,0.));
#152=IFCCARTESIANPOINT((1.,0.));
#153=IFCCARTESIANPOINT((1.,1.));
#154=IFCCARTESIANPOINT((0.,1.));
#155=IFCCARTESIANPOINT((0.,0.));
#156=IFCPOLYLINE((#151,#152,#153,#154,#155));
#157=IFCSHAPEREPRESENTATION(#150,'Annotation','Annotation2D',(#156));
#158=IFCPRODUCTDEFINITIONSHAPE($,$,(#157));
#159=IFCANNOTATION('8xScRe4drECQ4DMSqUjdID',$,'IdentityNote',$,$,#40,#158);
ENDSEC;
END-ISO-10303-21;
"#;
fn polyline_for(express_id: u32) -> ifc_lite_processing::SymbolicPolyline {
let data = extract_symbolic_data(FIXTURE);
data.polylines
.into_iter()
.find(|p| p.express_id == express_id)
.unwrap_or_else(|| panic!("annotation #{express_id} should produce a polyline"))
}
#[test]
fn pure_z_offset_wcs_is_composed_into_world_y() {
let pl = polyline_for(28);
assert_eq!(
pl.world_y, 7.0,
"IfcGeometricRepresentationContext #12's WorldCoordinateSystem is a \
pure Z=7 offset with identity ObjectPlacement; world_y must reflect \
it. Got: {}",
pl.world_y
);
}
#[test]
fn xy_offset_wcs_is_composed_into_plan_coordinates() {
let pl = polyline_for(42);
assert_eq!(pl.points[0], 10.0, "x should be shifted by WCS tx=10");
assert_eq!(pl.points[1], -20.0, "y (screen, flipped) should be shifted by WCS ty=20");
}
#[test]
fn dangling_wcs_ref_is_not_finite() {
let pl = polyline_for(59);
assert!(
!pl.world_y.is_finite(),
"WorldCoordinateSystem #9999 does not exist. world_y must be \
non-finite, not silently 0.0. Got: {}",
pl.world_y
);
}
#[test]
fn null_wcs_on_mandatory_attr_is_not_finite() {
let pl = polyline_for(70);
assert!(
!pl.world_y.is_finite(),
"WorldCoordinateSystem is null on a mandatory attribute. world_y \
must be non-finite, not silently 0.0. Got: {}",
pl.world_y
);
}
#[test]
fn dangling_context_ref_is_not_finite() {
let pl = polyline_for(90);
assert!(
!pl.world_y.is_finite(),
"ContextOfItems #9998 does not exist. world_y must be non-finite, \
not silently 0.0. Got: {}",
pl.world_y
);
}
#[test]
fn null_context_on_mandatory_attr_is_not_finite() {
let pl = polyline_for(100);
assert!(
!pl.world_y.is_finite(),
"ContextOfItems is null on a mandatory attribute. world_y must be \
non-finite, not silently 0.0. Got: {}",
pl.world_y
);
}
#[test]
fn subcontext_stays_identity_and_does_not_inherit_parent_wcs() {
let pl = polyline_for(114);
assert_eq!(
pl.world_y, 0.0,
"a resolved SubContext must resolve to identity(): finite zero, not \
the parent context's Z=7 offset and not unresolved(). Got: {}",
pl.world_y
);
assert!(pl.world_y.is_finite());
}
#[test]
fn identity_wcs_context_is_byte_identical_to_pre_fix_placement() {
let pl = polyline_for(159);
assert_eq!(pl.points[0], 0.0, "identity WCS must not move x");
assert_eq!(pl.points[1], 0.0, "identity WCS must not move y");
assert_eq!(
pl.world_y, 0.0,
"identity WCS: a genuine zero elevation must stay exactly 0.0, not \
non-finite. Got: {}",
pl.world_y
);
assert!(pl.world_y.is_finite());
}
const ORDER_FIXTURE: &str = r#"ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('issue-2256 composition-order fixture'),'2;1');
FILE_NAME('test.ifc','2026-08-10T00:00:00',(''),(''),'','','');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROJECT('0$ScRe4drECQ4DMSqUjdOR',$,'P',$,$,$,$,(#12),#3);
#3=IFCUNITASSIGNMENT((#6));
#6=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
/* Non-identity ObjectPlacement: translation (3,4,0), identity rotation. */
#4=IFCCARTESIANPOINT((3.,4.,0.));
#5=IFCAXIS2PLACEMENT3D(#4,$,$);
#40=IFCLOCALPLACEMENT($,#5);
/* Non-identity WorldCoordinateSystem: translation (10,20,0), 90-degree
rotation (RefDirection along Y). */
#10=IFCCARTESIANPOINT((10.,20.,0.));
#11=IFCDIRECTION((0.,1.,0.));
#13=IFCAXIS2PLACEMENT3D(#10,$,#11);
#12=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Plan',3,1.0E-5,#13,$);
#20=IFCCARTESIANPOINT((0.,0.));
#21=IFCCARTESIANPOINT((1.,0.));
#25=IFCPOLYLINE((#20,#21));
#26=IFCSHAPEREPRESENTATION(#12,'Annotation','Annotation2D',(#25));
#27=IFCPRODUCTDEFINITIONSHAPE($,$,(#26));
#28=IFCANNOTATION('1xScRe4drECQ4DMSqUjdOR',$,'CompositionOrderNote',$,$,#40,#27);
ENDSEC;
END-ISO-10303-21;
"#;
#[test]
fn composition_order_is_context_outer_placement_inner() {
let data = extract_symbolic_data(ORDER_FIXTURE);
let pl = data
.polylines
.into_iter()
.find(|p| p.express_id == 28)
.unwrap_or_else(|| panic!("annotation #28 should produce a polyline"));
assert_eq!(
pl.points,
vec![6.0, -23.0, 6.0, -24.0],
"context-outer composition order gives a specific result; got {:?}. \
(The flipped order would give [13, -24, 13, -25] instead.)",
pl.points
);
}