use super::*;
use ifc_lite_core::EntityDecoder;
const IFC: &str = "\
ISO-10303-21;
HEADER;
ENDSEC;
DATA;
#1=IFCPROJECT('p',$,'P',$,$,$,$,(#5),#8);
#5=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.0E-5,#6,$);
#6=IFCAXIS2PLACEMENT3D(#7,$,$);
#7=IFCCARTESIANPOINT((0.,0.,0.));
#8=IFCUNITASSIGNMENT((#9));
#9=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
#20=IFCSITE('site',$,$,$,$,#21,$,$,.ELEMENT.,$,$,$,$,$);
#21=IFCLOCALPLACEMENT($,#22);
#22=IFCAXIS2PLACEMENT3D(#23,$,$);
#23=IFCCARTESIANPOINT((800000.,900000.,0.));
#40=IFCWALL('wall',$,$,$,$,#41,#50,$,$);
#41=IFCLOCALPLACEMENT($,#42);
#42=IFCAXIS2PLACEMENT3D(#43,$,$);
#43=IFCCARTESIANPOINT((800000.,900000.,0.));
#50=IFCPRODUCTDEFINITIONSHAPE($,$,(#51));
#51=IFCSHAPEREPRESENTATION(#5,'Body','SweptSolid',(#52));
#52=IFCEXTRUDEDAREASOLID(#53,#56,#60,1.0);
#53=IFCRECTANGLEPROFILEDEF(.AREA.,$,#54,1.0,1.0);
#54=IFCAXIS2PLACEMENT2D(#55,$);
#55=IFCCARTESIANPOINT((0.,0.));
#56=IFCAXIS2PLACEMENT3D(#57,$,$);
#57=IFCCARTESIANPOINT((0.,0.,0.));
#60=IFCDIRECTION((0.,0.,1.));
ENDSEC;
END-ISO-10303-21;
";
fn wall_job(content: &[u8]) -> Job {
let needle = "#40=IFCWALL";
let start = content
.windows(needle.len())
.position(|w| w == needle.as_bytes())
.expect("wall present");
let rel_end = content[start..]
.iter()
.position(|&b| b == b';')
.expect("stmt end");
(40, start, start + rel_end + 1, IfcType::IfcWall)
}
#[test]
fn small_file_single_resolves_scale_and_offset() {
let content = IFC.as_bytes();
let full_index = ifc_lite_core::build_entity_index(content);
let mut decoder = EntityDecoder::with_index(content, full_index);
let jobs = vec![wall_job(content)];
let meta = resolve_stream_meta(
MetaMode::SmallFileSingle,
content,
Some(1),
None,
&jobs,
&mut decoder,
);
assert_eq!(meta.length_unit_scale, 1.0, "metric project → scale 1");
assert!(meta.needs_shift, "800 km offset must trigger a shift");
assert!(
coord_is_large(meta.rtc_offset),
"resolved RTC must exceed the large-coordinate threshold, got {:?}",
meta.rtc_offset
);
}
#[test]
fn streaming_partial_full_index_fallback_recovers_offset() {
let content = IFC.as_bytes();
let partial_index = ifc_lite_core::EntityIndex::default();
let mut decoder = EntityDecoder::with_index(content, partial_index);
let jobs = vec![wall_job(content)];
let meta = resolve_stream_meta(
MetaMode::StreamingPartial,
content,
Some(1),
None, &jobs,
&mut decoder,
);
assert!(
meta.needs_shift,
"3-stage fallback must recover the large offset from the full index, got {:?}",
meta.rtc_offset
);
assert!(coord_is_large(meta.rtc_offset));
}
#[test]
fn streaming_partial_first_pass_success_suppresses_fallback() {
let near = IFC.replace(
"#43=IFCCARTESIANPOINT((800000.,900000.,0.));",
"#43=IFCCARTESIANPOINT((1.,2.,0.));",
);
let content = near.as_bytes();
let full = ifc_lite_core::build_entity_index(content);
let partial = || {
let mut p = ifc_lite_core::EntityIndex::default();
for id in [1u32, 8, 9, 20, 40, 41, 42, 43, 50, 51] {
if let Some(&s) = full.get(&id) {
p.insert(id, s);
}
}
p
};
let jobs = vec![wall_job(content)];
let mut probe = EntityDecoder::with_index(content, partial());
assert_eq!(
GeometryRouter::with_scale(1.0).detect_rtc_offset_from_jobs(&jobs, &mut probe),
Some((0.0, 0.0, 0.0)),
"first pass must succeed on the partial index"
);
assert!(
coord_is_large(ifc_lite_core::scan_placement_bounds(content).rtc_offset()),
"placement-bounds fallback would shift (large) if taken"
);
let site = *full.get(&20).expect("site present");
let mut decoder = EntityDecoder::with_index(content, partial());
let meta = resolve_stream_meta(
MetaMode::StreamingPartial,
content,
Some(1),
Some((20, site.0, site.1)),
&jobs,
&mut decoder,
);
assert!(!meta.needs_shift, "partial-pass success suppresses the shift");
assert_eq!(
meta.rtc_offset,
(0.0, 0.0, 0.0),
"offset comes from the partial pass, not the placement-bounds fallback"
);
}
const IFC_STAGE3: &str = "\
ISO-10303-21;
HEADER;
ENDSEC;
DATA;
#1=IFCPROJECT('p',$,'P',$,$,$,$,$,#8);
#8=IFCUNITASSIGNMENT((#9));
#9=IFCSIUNIT(*,.LENGTHUNIT.,.MILLI.,.METRE.);
#40=IFCWALL('wall',$,$,$,$,#41,$,$,$);
#41=IFCLOCALPLACEMENT($,#42);
#42=IFCAXIS2PLACEMENT3D(#43,$,$);
#43=IFCCARTESIANPOINT((80000000.,90000000.,0.));
ENDSEC;
END-ISO-10303-21;
";
#[test]
fn streaming_partial_stage3_placement_bounds_fallback() {
let content = IFC_STAGE3.as_bytes();
let full_index = ifc_lite_core::build_entity_index(content);
let mut decoder = EntityDecoder::with_index(content, full_index);
let jobs = vec![wall_job(content)];
let meta = resolve_stream_meta(
MetaMode::StreamingPartial,
content,
Some(1),
None,
&jobs,
&mut decoder,
);
let scale = meta.length_unit_scale;
assert!((scale - 0.001).abs() < 1e-12, "expected mm scale, got {scale}");
let raw = ifc_lite_core::scan_placement_bounds(content).rtc_offset();
assert_eq!(raw, (80_000_000.0, 90_000_000.0, 0.0), "raw mm bounds");
let expected = (raw.0 * scale, raw.1 * scale, raw.2 * scale);
assert_eq!(meta.rtc_offset, expected, "stage 3 unit-scales raw bounds");
assert_ne!(meta.rtc_offset, raw, "scaling changed the value");
assert!(meta.needs_shift);
}