use ifc_lite_core::{build_entity_index, EntityDecoder, EntityScanner, IfcType};
mod color;
mod fill;
mod grid;
mod items;
mod primitives;
mod text;
mod transform;
mod trimmed_curve;
pub use primitives::{
SymbolicCircle, SymbolicData, SymbolicFillArea, SymbolicGridAxis, SymbolicPolyline, SymbolicText,
};
use color::build_styled_item_index;
use grid::extract_grid;
use items::extract_symbolic_item;
use transform::{compose_transforms, parse_axis2_placement_2d, resolve_object_placement, Transform2D};
pub fn extract_symbolic_data<T>(content: &T) -> SymbolicData
where
T: AsRef<[u8]> + ?Sized,
{
let content = content.as_ref();
let entity_index = build_entity_index(content);
let mut decoder = EntityDecoder::with_index(content, entity_index);
let router = ifc_lite_geometry::GeometryRouter::with_units(content, &mut decoder);
let unit_scale = router.unit_scale() as f32;
let rtc_offset = router.detect_rtc_offset_from_first_element(content, &mut decoder);
let needs_rtc = rtc_offset.0.abs() > 10_000.0
|| rtc_offset.1.abs() > 10_000.0
|| rtc_offset.2.abs() > 10_000.0;
let rtc_x = if needs_rtc { rtc_offset.0 as f32 } else { 0.0 };
let rtc_z = if needs_rtc { rtc_offset.2 as f32 } else { 0.0 };
let styled_items = build_styled_item_index(content, &mut decoder);
let mut out = SymbolicData::default();
let mut scanner = EntityScanner::new(content);
while let Some((id, type_name, start, end)) = scanner.next_entity() {
let is_grid = type_name == "IFCGRID";
if !is_grid && !ifc_lite_core::has_geometry_by_name(type_name) {
continue;
}
let Ok(entity) = decoder.decode_at_with_id(id, start, end) else {
continue;
};
if is_grid {
let grid_transform = resolve_object_placement(&entity, &mut decoder, unit_scale);
extract_grid(
&entity,
id,
&mut decoder,
unit_scale,
&grid_transform,
rtc_x,
rtc_z,
&mut out,
);
continue;
}
let Some(representation_attr) = entity.get(6) else {
continue;
};
if representation_attr.is_null() {
continue;
}
let Ok(Some(representation)) = decoder.resolve_ref(representation_attr) else {
continue;
};
let Some(reps_attr) = representation.get(2) else {
continue;
};
let Ok(representations) = decoder.resolve_ref_list(reps_attr) else {
continue;
};
let ifc_type_name = entity.ifc_type.name().to_string();
for shape_rep in representations {
if shape_rep.ifc_type != IfcType::IfcShapeRepresentation {
continue;
}
let rep_identifier = shape_rep
.get(1)
.and_then(|a| a.as_string())
.unwrap_or("")
.to_string();
if !matches!(
rep_identifier.as_str(),
"Plan" | "Annotation" | "FootPrint" | "Axis"
) {
continue;
}
let placement_transform = resolve_object_placement(&entity, &mut decoder, unit_scale);
let context_transform = match shape_rep.get_ref(0) {
Some(context_ref) => match decoder.decode_by_id(context_ref) {
Ok(context) if context.ifc_type == IfcType::IfcGeometricRepresentationContext => {
match context.get_ref(4) {
Some(wcs_ref) => match decoder.decode_by_id(wcs_ref) {
Ok(wcs) => parse_axis2_placement_2d(&wcs, &mut decoder, unit_scale),
Err(_) => Transform2D::unresolved(),
},
None => Transform2D::unresolved(),
}
}
Ok(context) if context.ifc_type == IfcType::IfcGeometricRepresentationSubContext => {
Transform2D::identity()
}
_ => Transform2D::unresolved(),
},
None => Transform2D::unresolved(),
};
let combined_transform = if context_transform.tx.abs() > 0.001
|| context_transform.ty.abs() > 0.001
|| context_transform.tz.abs() > 0.001
|| (context_transform.m00 - 1.0).abs() > 0.0001
|| context_transform.m01.abs() > 0.0001
|| context_transform.m10.abs() > 0.0001
|| (context_transform.m11 - 1.0).abs() > 0.0001
|| context_transform.tz.is_nan()
{
compose_transforms(&context_transform, &placement_transform)
} else {
placement_transform
};
let Some(items_attr) = shape_rep.get(3) else {
continue;
};
let Ok(items) = decoder.resolve_ref_list(items_attr) else {
continue;
};
for item in items {
extract_symbolic_item(
&item,
&mut decoder,
id,
&ifc_type_name,
&rep_identifier,
unit_scale,
&combined_transform,
rtc_x,
rtc_z,
&styled_items,
&mut out,
);
}
}
}
out
}