pub(crate) fn build_geometry_style_index(
content: &str,
decoder: &mut ifc_lite_core::EntityDecoder,
) -> rustc_hash::FxHashMap<u32, [f32; 4]> {
use ifc_lite_core::EntityScanner;
use rustc_hash::FxHashMap;
let mut style_index: FxHashMap<u32, [f32; 4]> = FxHashMap::default();
let mut scanner = EntityScanner::new(content);
while let Some((id, type_name, start, end)) = scanner.next_entity() {
if type_name != "IFCSTYLEDITEM" {
continue;
}
let styled_item = match decoder.decode_at_with_id(id, start, end) {
Ok(entity) => entity,
Err(_) => continue,
};
let geometry_id = match styled_item.get_ref(0) {
Some(id) => id,
None => continue,
};
if style_index.contains_key(&geometry_id) {
continue;
}
let styles_attr = match styled_item.get(1) {
Some(attr) => attr,
None => continue,
};
if let Some(color) = extract_color_from_styles(styles_attr, decoder) {
style_index.insert(geometry_id, color);
}
}
style_index
}
pub(crate) fn build_element_style_index(
content: &str,
geometry_styles: &rustc_hash::FxHashMap<u32, [f32; 4]>,
decoder: &mut ifc_lite_core::EntityDecoder,
) -> rustc_hash::FxHashMap<u32, [f32; 4]> {
use ifc_lite_core::EntityScanner;
use rustc_hash::FxHashMap;
let mut element_styles: FxHashMap<u32, [f32; 4]> = FxHashMap::default();
if geometry_styles.is_empty() {
return element_styles;
}
let mut scanner = EntityScanner::new(content);
while let Some((element_id, type_name, start, end)) = scanner.next_entity() {
if !ifc_lite_core::has_geometry_by_name(type_name) {
continue;
}
let element = match decoder.decode_at_with_id(element_id, start, end) {
Ok(entity) => entity,
Err(_) => continue,
};
let repr_id = match element.get_ref(6) {
Some(id) => id,
None => continue,
};
let product_shape = match decoder.decode_by_id(repr_id) {
Ok(entity) => entity,
Err(_) => continue,
};
let reprs_attr = match product_shape.get(2) {
Some(attr) => attr,
None => continue,
};
let reprs_list = match reprs_attr.as_list() {
Some(list) => list,
None => continue,
};
'repr_loop: for repr_item in reprs_list {
let shape_repr_id = match repr_item.as_entity_ref() {
Some(id) => id,
None => continue,
};
let shape_repr = match decoder.decode_by_id(shape_repr_id) {
Ok(entity) => entity,
Err(_) => continue,
};
let items_attr = match shape_repr.get(3) {
Some(attr) => attr,
None => continue,
};
let items_list = match items_attr.as_list() {
Some(list) => list,
None => continue,
};
for geom_item in items_list {
let geom_id = match geom_item.as_entity_ref() {
Some(id) => id,
None => continue,
};
if let Some(color) =
find_color_for_geometry(geom_id, geometry_styles, decoder)
{
element_styles.insert(element_id, color);
break 'repr_loop; }
}
}
}
element_styles
}
pub(crate) fn find_color_for_geometry(
geom_id: u32,
geometry_styles: &rustc_hash::FxHashMap<u32, [f32; 4]>,
decoder: &mut ifc_lite_core::EntityDecoder,
) -> Option<[f32; 4]> {
use ifc_lite_core::IfcType;
if let Some(&color) = geometry_styles.get(&geom_id) {
return Some(color);
}
let geom = decoder.decode_by_id(geom_id).ok()?;
if geom.ifc_type == IfcType::IfcMappedItem {
let map_source_id = geom.get_ref(0)?;
let rep_map = decoder.decode_by_id(map_source_id).ok()?;
let mapped_repr_id = rep_map.get_ref(1)?;
let mapped_repr = decoder.decode_by_id(mapped_repr_id).ok()?;
let items_attr = mapped_repr.get(3)?;
let items_list = items_attr.as_list()?;
for item in items_list {
if let Some(underlying_geom_id) = item.as_entity_ref() {
if let Some(color) =
find_color_for_geometry(underlying_geom_id, geometry_styles, decoder)
{
return Some(color);
}
}
}
}
None
}
fn extract_color_from_styles(
styles_attr: &ifc_lite_core::AttributeValue,
decoder: &mut ifc_lite_core::EntityDecoder,
) -> Option<[f32; 4]> {
if let Some(list) = styles_attr.as_list() {
for item in list {
if let Some(style_id) = item.as_entity_ref() {
if let Some(color) = extract_color_from_style_assignment(style_id, decoder) {
return Some(color);
}
}
}
} else if let Some(style_id) = styles_attr.as_entity_ref() {
return extract_color_from_style_assignment(style_id, decoder);
}
None
}
fn extract_color_from_style_assignment(
style_id: u32,
decoder: &mut ifc_lite_core::EntityDecoder,
) -> Option<[f32; 4]> {
use ifc_lite_core::IfcType;
let style = decoder.decode_by_id(style_id).ok()?;
match style.ifc_type {
IfcType::IfcPresentationStyle => {
let styles_attr = style.get(0)?;
if let Some(list) = styles_attr.as_list() {
for item in list {
if let Some(inner_id) = item.as_entity_ref() {
if let Some(color) = extract_color_from_surface_style(inner_id, decoder) {
return Some(color);
}
}
}
}
}
IfcType::IfcSurfaceStyle => {
return extract_color_from_surface_style(style_id, decoder);
}
_ => {
let styles_attr = style.get(0)?;
if let Some(list) = styles_attr.as_list() {
for item in list {
if let Some(inner_id) = item.as_entity_ref() {
if let Some(color) = extract_color_from_surface_style(inner_id, decoder) {
return Some(color);
}
}
}
}
}
}
None
}
fn extract_color_from_surface_style(
style_id: u32,
decoder: &mut ifc_lite_core::EntityDecoder,
) -> Option<[f32; 4]> {
use ifc_lite_core::IfcType;
let style = decoder.decode_by_id(style_id).ok()?;
if style.ifc_type != IfcType::IfcSurfaceStyle {
return None;
}
let styles_attr = style.get(2)?;
if let Some(list) = styles_attr.as_list() {
for item in list {
if let Some(element_id) = item.as_entity_ref() {
if let Some(color) = extract_color_from_rendering(element_id, decoder) {
return Some(color);
}
}
}
}
None
}
fn extract_color_from_rendering(
rendering_id: u32,
decoder: &mut ifc_lite_core::EntityDecoder,
) -> Option<[f32; 4]> {
use ifc_lite_core::IfcType;
let rendering = decoder.decode_by_id(rendering_id).ok()?;
match rendering.ifc_type {
IfcType::IfcSurfaceStyleRendering | IfcType::IfcSurfaceStyleShading => {
let color_ref = rendering.get_ref(0)?;
let [r, g, b, _] = extract_color_rgb(color_ref, decoder)?;
let transparency = rendering.get_float(1).unwrap_or(0.0);
let alpha = 1.0 - transparency as f32;
return Some([r, g, b, alpha.max(0.0).min(1.0)]);
}
_ => {}
}
None
}
fn extract_color_rgb(
color_id: u32,
decoder: &mut ifc_lite_core::EntityDecoder,
) -> Option<[f32; 4]> {
use ifc_lite_core::IfcType;
let color = decoder.decode_by_id(color_id).ok()?;
if color.ifc_type != IfcType::IfcColourRgb {
return None;
}
let red = color.get_float(1).unwrap_or(0.8);
let green = color.get_float(2).unwrap_or(0.8);
let blue = color.get_float(3).unwrap_or(0.8);
Some([red as f32, green as f32, blue as f32, 1.0])
}
pub(crate) struct PrePassData {
pub geometry_styles: rustc_hash::FxHashMap<u32, [f32; 4]>,
pub void_index: rustc_hash::FxHashMap<u32, Vec<u32>>,
pub faceted_brep_ids: Vec<u32>,
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: &str,
decoder: &mut ifc_lite_core::EntityDecoder,
) -> PrePassData {
use ifc_lite_core::EntityScanner;
use rustc_hash::FxHashMap;
let estimated_elements = content.len() / 2000;
let mut geometry_styles: FxHashMap<u32, [f32; 4]> = FxHashMap::default();
let mut void_index: FxHashMap<u32, Vec<u32>> = FxHashMap::default();
let mut faceted_brep_ids: Vec<u32> = Vec::with_capacity(estimated_elements / 10);
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" => {
if let Ok(styled_item) = decoder.decode_at_with_id(id, start, end) {
if let Some(geometry_id) = styled_item.get_ref(0) {
if !geometry_styles.contains_key(&geometry_id) {
if let Some(styles_attr) = styled_item.get(1) {
if let Some(color) =
extract_color_from_styles(styles_attr, decoder)
{
geometry_styles.insert(geometry_id, color);
}
}
}
}
}
}
"IFCRELVOIDSELEMENT" => {
if let Ok(entity) = decoder.decode_at_with_id(id, start, end) {
if let (Some(host_id), Some(opening_id)) =
(entity.get_ref(4), entity.get_ref(5))
{
void_index.entry(host_id).or_default().push(opening_id);
}
}
}
"IFCFACETEDBREP" => {
faceted_brep_ids.push(id);
}
"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 is_simple_geometry_type(type_name) {
simple_jobs.push((id, start, end, ifc_type));
} else {
complex_jobs.push((id, start, end, ifc_type));
}
}
}
}
}
PrePassData {
geometry_styles,
void_index,
faceted_brep_ids,
project_id,
site_position,
simple_jobs,
complex_jobs,
}
}
fn is_simple_geometry_type(type_name: &str) -> bool {
matches!(
type_name,
"IFCWALL"
| "IFCWALLSTANDARDCASE"
| "IFCSLAB"
| "IFCBEAM"
| "IFCCOLUMN"
| "IFCPLATE"
| "IFCROOF"
| "IFCCOVERING"
| "IFCFOOTING"
| "IFCRAILING"
| "IFCSTAIR"
| "IFCSTAIRFLIGHT"
| "IFCRAMP"
| "IFCRAMPFLIGHT"
)
}
pub(crate) fn resolve_element_color(
entity: &ifc_lite_core::DecodedEntity,
geometry_styles: &rustc_hash::FxHashMap<u32, [f32; 4]>,
decoder: &mut ifc_lite_core::EntityDecoder,
) -> Option<[f32; 4]> {
if geometry_styles.is_empty() {
return None;
}
let repr_id = entity.get_ref(6)?;
let product_shape = decoder.decode_by_id(repr_id).ok()?;
let reprs_list = product_shape.get(2)?.as_list()?;
for repr_item in reprs_list {
let shape_repr_id = repr_item.as_entity_ref()?;
let shape_repr = decoder.decode_by_id(shape_repr_id).ok()?;
let items_list = shape_repr.get(3)?.as_list()?;
for geom_item in items_list {
let geom_id = geom_item.as_entity_ref()?;
if let Some(color) = find_color_for_geometry(geom_id, geometry_styles, decoder) {
return Some(color);
}
}
}
None
}
pub(crate) fn get_default_color_for_type(ifc_type: &ifc_lite_core::IfcType) -> [f32; 4] {
use ifc_lite_core::IfcType;
match ifc_type {
IfcType::IfcWall | IfcType::IfcWallStandardCase => [0.85, 0.85, 0.85, 1.0],
IfcType::IfcSlab => [0.7, 0.7, 0.7, 1.0],
IfcType::IfcRoof => [0.6, 0.5, 0.4, 1.0],
IfcType::IfcColumn | IfcType::IfcBeam | IfcType::IfcMember => [0.6, 0.65, 0.7, 1.0],
IfcType::IfcWindow => [0.6, 0.8, 1.0, 0.4],
IfcType::IfcDoor => [0.6, 0.45, 0.3, 1.0],
IfcType::IfcStair => [0.75, 0.75, 0.75, 1.0],
IfcType::IfcRailing => [0.4, 0.4, 0.45, 1.0],
IfcType::IfcPlate | IfcType::IfcCovering => [0.8, 0.8, 0.8, 1.0],
IfcType::IfcCurtainWall => [0.5, 0.7, 0.9, 0.5],
IfcType::IfcFurnishingElement => [0.7, 0.55, 0.4, 1.0],
IfcType::IfcSpace => [0.2, 0.85, 1.0, 0.3],
IfcType::IfcOpeningElement => [1.0, 0.42, 0.29, 0.4],
IfcType::IfcSite => [0.4, 0.8, 0.3, 1.0],
_ => [0.8, 0.8, 0.8, 1.0],
}
}
pub(crate) fn extract_building_rotation_from_site(
site_pos: (u32, usize, usize),
decoder: &mut ifc_lite_core::EntityDecoder,
) -> Option<f64> {
let (site_id, start, end) = site_pos;
let site_entity = decoder.decode_at_with_id(site_id, start, end).ok()?;
let placement_attr = site_entity.get(5).filter(|a| !a.is_null())?;
let placement = decoder.resolve_ref(placement_attr).ok()??;
let top_level_placement = find_top_level_placement(&placement, decoder);
extract_rotation_from_placement(&top_level_placement, decoder)
}
pub(crate) fn extract_building_rotation(
content: &str,
decoder: &mut ifc_lite_core::EntityDecoder,
) -> Option<f64> {
use ifc_lite_core::EntityScanner;
let mut scanner = EntityScanner::new(content);
while let Some((site_id, type_name, start, end)) = scanner.next_entity() {
if type_name != "IFCSITE" {
continue;
}
if let Ok(site_entity) = decoder.decode_at_with_id(site_id, start, end) {
let placement_attr = match site_entity.get(5) {
Some(attr) if !attr.is_null() => attr,
_ => continue,
};
let placement = match decoder.resolve_ref(placement_attr) {
Ok(Some(p)) => p,
_ => continue,
};
let top_level_placement = find_top_level_placement(&placement, decoder);
if let Some(rotation) = extract_rotation_from_placement(&top_level_placement, decoder) {
return Some(rotation);
}
}
}
None
}
fn find_top_level_placement(
placement: &ifc_lite_core::DecodedEntity,
decoder: &mut ifc_lite_core::EntityDecoder,
) -> ifc_lite_core::DecodedEntity {
use ifc_lite_core::IfcType;
if placement.ifc_type != IfcType::IfcLocalPlacement {
return placement.clone();
}
let parent_attr = match placement.get(0) {
Some(attr) if !attr.is_null() => attr,
_ => return placement.clone(), };
if let Ok(Some(parent)) = decoder.resolve_ref(parent_attr) {
find_top_level_placement(&parent, decoder)
} else {
placement.clone() }
}
fn extract_rotation_from_placement(
placement: &ifc_lite_core::DecodedEntity,
decoder: &mut ifc_lite_core::EntityDecoder,
) -> Option<f64> {
use ifc_lite_core::IfcType;
let rel_attr = match placement.get(1) {
Some(attr) if !attr.is_null() => attr,
_ => return None,
};
let axis_placement = match decoder.resolve_ref(rel_attr) {
Ok(Some(p)) => p,
_ => return None,
};
if axis_placement.ifc_type != IfcType::IfcAxis2Placement3D {
return None;
}
let ref_dir_attr = match axis_placement.get(2) {
Some(attr) if !attr.is_null() => attr,
_ => return None,
};
let ref_dir = match decoder.resolve_ref(ref_dir_attr) {
Ok(Some(d)) => d,
_ => return None,
};
if ref_dir.ifc_type != IfcType::IfcDirection {
return None;
}
let ratios_attr = match ref_dir.get(0) {
Some(attr) => attr,
_ => return None,
};
let ratios = match ratios_attr.as_list() {
Some(list) => list,
_ => return None,
};
let dx = ratios.first().and_then(|v| v.as_float()).unwrap_or(0.0);
let dy = ratios.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
let len_sq = dx * dx + dy * dy;
if len_sq < 1e-10 {
return None; }
let rotation = dy.atan2(dx);
Some(rotation)
}