pub(crate) struct PrePassData {
pub resolved: ifc_lite_processing::prepass::ResolvedPrepass,
pub project_id: Option<u32>,
pub site_position: Option<(u32, usize, usize)>,
pub simple_jobs: Vec<(u32, usize, usize, ifc_lite_core::IfcType)>,
pub complex_jobs: Vec<(u32, usize, usize, ifc_lite_core::IfcType)>,
}
pub(crate) fn combined_pre_pass(
content: &[u8],
decoder: &mut ifc_lite_core::EntityDecoder,
) -> PrePassData {
use ifc_lite_core::EntityScanner;
use ifc_lite_processing::prepass::{resolve_prepass, PrepassSpans, ResolveOptions};
let estimated_elements = content.len() / 2000;
let mut spans = PrepassSpans::default();
let mut project_id: Option<u32> = None;
let mut site_position: Option<(u32, usize, usize)> = None;
let mut simple_jobs = Vec::with_capacity(estimated_elements / 2);
let mut complex_jobs = Vec::with_capacity(estimated_elements / 2);
let mut scanner = EntityScanner::new(content);
while let Some((id, type_name, start, end)) = scanner.next_entity() {
match type_name {
"IFCSTYLEDITEM" => spans.styled_items.push((id, start, end)),
"IFCINDEXEDCOLOURMAP" => spans.indexed_colour_maps.push((id, start, end)),
"IFCMATERIALDEFINITIONREPRESENTATION" => {
spans.material_def_reprs.push((id, start, end))
}
"IFCRELASSOCIATESMATERIAL" => spans.rel_associates_material.push((id, start, end)),
"IFCRELVOIDSELEMENT" => spans.void_rels.push((id, start, end)),
"IFCRELFILLSELEMENT" => spans.fills_rels.push((id, start, end)),
"IFCRELAGGREGATES" => spans.aggregate_rels.push((id, start, end)),
"IFCPROJECT" => {
if project_id.is_none() {
project_id = Some(id);
}
}
"IFCSITE" => {
if site_position.is_none() {
site_position = Some((id, start, end));
}
let ifc_type = ifc_lite_core::IfcType::from_str(type_name);
complex_jobs.push((id, start, end, ifc_type));
}
_ => {
if ifc_lite_core::has_geometry_by_name(type_name) {
let ifc_type = ifc_lite_core::IfcType::from_str(type_name);
if ifc_lite_core::is_simple_geometry_type(type_name) {
simple_jobs.push((id, start, end, ifc_type));
} else {
complex_jobs.push((id, start, end, ifc_type));
}
}
}
}
}
let resolved = resolve_prepass(
&spans,
decoder,
ResolveOptions {
collect_indexed_colour_full: false,
defer_attached_styles: false,
},
);
complex_jobs.extend(collect_type_geometry_jobs(content, decoder));
PrePassData {
resolved,
project_id,
site_position,
simple_jobs,
complex_jobs,
}
}
pub(crate) fn collect_type_geometry_jobs(
content: &[u8],
decoder: &mut ifc_lite_core::EntityDecoder,
) -> Vec<(u32, usize, usize, ifc_lite_core::IfcType)> {
use ifc_lite_core::{EntityScanner, IfcType};
if !content
.windows(b"IFCREPRESENTATIONMAP".len())
.any(|window| window == b"IFCREPRESENTATIONMAP")
{
return Vec::new();
}
let mut referenced: rustc_hash::FxHashSet<u32> = rustc_hash::FxHashSet::default();
let mut candidates: Vec<(u32, usize, usize, IfcType, Vec<u32>)> = Vec::new();
let mut scanner = EntityScanner::new(content);
while let Some((id, type_name, start, end)) = scanner.next_entity() {
if type_name == "IFCMAPPEDITEM" {
if let Ok(entity) = decoder.decode_at_with_id(id, start, end) {
if let Some(source_id) = entity.get_ref(0) {
referenced.insert(source_id);
}
}
} else if type_name.ends_with("TYPE") || type_name.ends_with("STYLE") {
let ifc_type = IfcType::from_str(type_name);
if !ifc_type.is_subtype_of(IfcType::IfcTypeProduct) {
continue;
}
if let Ok(entity) = decoder.decode_at_with_id(id, start, end) {
let rep_maps: Vec<u32> = entity
.get(6)
.and_then(|a| a.as_list())
.map(|list| list.iter().filter_map(|v| v.as_entity_ref()).collect())
.unwrap_or_default();
if !rep_maps.is_empty() {
candidates.push((id, start, end, ifc_type, rep_maps));
}
}
}
}
candidates
.into_iter()
.filter(|(_, _, _, _, maps)| maps.iter().any(|rm| !referenced.contains(rm)))
.map(|(id, start, end, ifc_type, _)| (id, start, end, ifc_type))
.collect()
}
pub(crate) fn collect_type_geometry_jobs_from_spans(
mapped_item_spans: &[(u32, usize, usize)],
type_candidate_spans: &[(u32, usize, usize, ifc_lite_core::IfcType)],
decoder: &mut ifc_lite_core::EntityDecoder,
) -> Vec<(u32, usize, usize, ifc_lite_core::IfcType)> {
let mut referenced: rustc_hash::FxHashSet<u32> = rustc_hash::FxHashSet::default();
for &(id, start, end) in mapped_item_spans {
if let Ok(entity) = decoder.decode_at_with_id(id, start, end) {
if let Some(source_id) = entity.get_ref(0) {
referenced.insert(source_id);
}
}
}
let mut candidates: Vec<(u32, usize, usize, ifc_lite_core::IfcType, Vec<u32>)> = Vec::new();
for &(id, start, end, ifc_type) in type_candidate_spans {
if let Ok(entity) = decoder.decode_at_with_id(id, start, end) {
let rep_maps: Vec<u32> = entity
.get(6)
.and_then(|a| a.as_list())
.map(|list| list.iter().filter_map(|v| v.as_entity_ref()).collect())
.unwrap_or_default();
if !rep_maps.is_empty() {
candidates.push((id, start, end, ifc_type, rep_maps));
}
}
}
candidates
.into_iter()
.filter(|(_, _, _, _, maps)| maps.iter().any(|rm| !referenced.contains(rm)))
.map(|(id, start, end, ifc_type, _)| (id, start, end, ifc_type))
.collect()
}
pub(crate) fn build_referenced_representation_maps(
content: &[u8],
decoder: &mut ifc_lite_core::EntityDecoder,
) -> rustc_hash::FxHashSet<u32> {
use ifc_lite_core::EntityScanner;
let mut spans: Vec<(u32, usize, usize)> = Vec::new();
let mut scanner = EntityScanner::new(content);
while let Some((id, type_name, start, end)) = scanner.next_entity() {
if type_name == "IFCMAPPEDITEM" {
spans.push((id, start, end));
}
}
build_referenced_representation_maps_from_spans(&spans, decoder)
}
pub(crate) fn build_referenced_representation_maps_from_spans(
spans: &[(u32, usize, usize)],
decoder: &mut ifc_lite_core::EntityDecoder,
) -> rustc_hash::FxHashSet<u32> {
let mut referenced: rustc_hash::FxHashSet<u32> = rustc_hash::FxHashSet::default();
for &(id, start, end) in spans {
if let Ok(entity) = decoder.decode_at_with_id(id, start, end) {
if let Some(source_id) = entity.get_ref(0) {
referenced.insert(source_id);
}
}
}
referenced
}
pub(crate) fn build_mapped_instance_plan_from_spans(
spans: &[(u32, usize, usize)],
decoder: &mut ifc_lite_core::EntityDecoder,
) -> Vec<u32> {
let mut counts: rustc_hash::FxHashMap<u32, u32> = rustc_hash::FxHashMap::default();
for &(id, start, end) in spans {
if let Ok(entity) = decoder.decode_at_with_id(id, start, end) {
if let Some(source_id) = entity.get_ref(0) {
*counts.entry(source_id).or_insert(0) += 1;
}
}
}
let mut eligible: Vec<u32> = counts
.into_iter()
.filter(|&(_, count)| count >= 2)
.map(|(source_id, _)| source_id)
.collect();
eligible.sort_unstable();
eligible
}
pub(crate) fn build_instantiated_type_ids(
content: &[u8],
decoder: &mut ifc_lite_core::EntityDecoder,
) -> rustc_hash::FxHashSet<u32> {
use ifc_lite_core::EntityScanner;
let mut spans: Vec<(u32, usize, usize)> = Vec::new();
let mut scanner = EntityScanner::new(content);
while let Some((id, type_name, start, end)) = scanner.next_entity() {
if type_name == "IFCRELDEFINESBYTYPE" {
spans.push((id, start, end));
}
}
build_instantiated_type_ids_from_spans(&spans, decoder)
}
pub(crate) fn build_instantiated_type_ids_from_spans(
spans: &[(u32, usize, usize)],
decoder: &mut ifc_lite_core::EntityDecoder,
) -> rustc_hash::FxHashSet<u32> {
let mut instantiated: rustc_hash::FxHashSet<u32> = rustc_hash::FxHashSet::default();
for &(id, start, end) in spans {
if let Ok(entity) = decoder.decode_at_with_id(id, start, end) {
if let Some(type_id) = entity.get_ref(5) {
instantiated.insert(type_id);
}
}
}
instantiated
}
#[cfg(test)]
mod orphan_type_from_spans_tests {
use super::{collect_type_geometry_jobs, collect_type_geometry_jobs_from_spans};
use ifc_lite_core::{build_entity_index, EntityDecoder, EntityScanner, IfcType};
fn assert_match(content: &[u8]) -> usize {
let index = std::sync::Arc::new(build_entity_index(content));
let mut d1 = EntityDecoder::with_arc_index(content, index.clone());
let old = collect_type_geometry_jobs(content, &mut d1);
let mut mapped: Vec<(u32, usize, usize)> = Vec::new();
let mut cands: Vec<(u32, usize, usize, IfcType)> = Vec::new();
let mut sc = EntityScanner::new(content);
while let Some((id, tn, st, en)) = sc.next_entity() {
if tn == "IFCMAPPEDITEM" {
mapped.push((id, st, en));
} else if tn.ends_with("TYPE") || tn.ends_with("STYLE") {
let t = IfcType::from_str(tn);
if t.is_subtype_of(IfcType::IfcTypeProduct) {
cands.push((id, st, en, t));
}
}
}
let mut d2 = EntityDecoder::with_arc_index(content, index);
let new = collect_type_geometry_jobs_from_spans(&mapped, &cands, &mut d2);
assert_eq!(old, new, "orphan type jobs diverged");
old.len()
}
const ORPHAN: &str = r#"ISO-10303-21;
HEADER;
FILE_DESCRIPTION((''),'2;1');
FILE_NAME('t.ifc','',(''),(''),'','','');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROJECT('0Project0000000000000A',$,'P',$,$,$,$,(#2),#3);
#2=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.0E-5,#5,$);
#3=IFCUNITASSIGNMENT((#6));
#4=IFCCARTESIANPOINT((0.,0.,0.));
#5=IFCAXIS2PLACEMENT3D(#4,$,$);
#6=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
#8=IFCCARTESIANPOINTLIST3D(((0.,0.,0.),(1.,0.,0.),(0.,1.,0.),(0.,0.,1.)));
#10=IFCREPRESENTATIONMAP(#5,#12);
#12=IFCSHAPEREPRESENTATION(#2,'Body','Tessellation',(#13));
#13=IFCTRIANGULATEDFACESET(#8,$,.T.,((1,2,3),(1,2,4),(1,4,3),(2,3,4)),$);
#20=IFCCOLUMNTYPE('0ColType00000000000A',$,'ColType',$,$,$,(#10),$,$,.COLUMN.);
ENDSEC;
END-ISO-10303-21;
"#;
const REFERENCED: &str = r#"ISO-10303-21;
HEADER;
FILE_DESCRIPTION((''),'2;1');
FILE_NAME('t.ifc','',(''),(''),'','','');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROJECT('0Project0000000000000A',$,'P',$,$,$,$,(#2),#3);
#2=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.0E-5,#5,$);
#3=IFCUNITASSIGNMENT((#6));
#4=IFCCARTESIANPOINT((0.,0.,0.));
#5=IFCAXIS2PLACEMENT3D(#4,$,$);
#6=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
#8=IFCCARTESIANPOINTLIST3D(((0.,0.,0.),(1.,0.,0.),(0.,1.,0.),(0.,0.,1.)));
#10=IFCREPRESENTATIONMAP(#5,#12);
#12=IFCSHAPEREPRESENTATION(#2,'Body','Tessellation',(#13));
#13=IFCTRIANGULATEDFACESET(#8,$,.T.,((1,2,3),(1,2,4),(1,4,3),(2,3,4)),$);
#20=IFCCOLUMNTYPE('0ColType00000000000A',$,'ColType',$,$,$,(#10),$,$,.COLUMN.);
#30=IFCMAPPEDITEM(#10,#31);
#31=IFCCARTESIANTRANSFORMATIONOPERATOR3D($,$,#4,$,$);
ENDSEC;
END-ISO-10303-21;
"#;
#[test]
fn from_spans_matches_full_scan_orphan_case() {
let n = assert_match(ORPHAN.as_bytes());
assert_eq!(n, 1, "the orphan IfcColumnType should yield one type job");
}
#[test]
fn from_spans_matches_full_scan_referenced_case() {
let n = assert_match(REFERENCED.as_bytes());
assert_eq!(n, 0, "a referenced RepresentationMap yields no orphan type job");
}
}