use ifc_lite_core::{DecodedEntity, EntityDecoder, EntityScanner, IfcType};
use rustc_hash::FxHashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LayerAxis {
Axis1,
Axis2,
Axis3,
}
impl LayerAxis {
pub fn unit_vector(self) -> [f64; 3] {
match self {
LayerAxis::Axis1 => [1.0, 0.0, 0.0],
LayerAxis::Axis2 => [0.0, 1.0, 0.0],
LayerAxis::Axis3 => [0.0, 0.0, 1.0],
}
}
}
#[derive(Debug, Clone)]
pub struct LayerInfo {
pub material_id: u32,
pub thickness: f64,
}
#[derive(Debug, Clone)]
pub enum LayerBuildup {
Sliceable {
layers: Vec<LayerInfo>,
axis: LayerAxis,
direction_sense: f64,
offset_from_reference_line: f64,
},
NotSliceable,
}
impl LayerBuildup {
pub fn is_sliceable(&self) -> bool {
matches!(self, LayerBuildup::Sliceable { .. })
}
}
#[derive(Debug, Default, Clone)]
pub struct MaterialLayerIndex {
element_to_buildup: FxHashMap<u32, LayerBuildup>,
}
impl MaterialLayerIndex {
pub fn new() -> Self {
Self::default()
}
pub fn from_content(content: &str, decoder: &mut EntityDecoder) -> Self {
let mut index = Self::new();
let mut scanner = EntityScanner::new(content);
while let Some((id, type_name, start, end)) = scanner.next_entity() {
if type_name != "IFCRELASSOCIATESMATERIAL" {
continue;
}
let entity = match decoder.decode_at_with_id(id, start, end) {
Ok(e) => e,
Err(_) => continue,
};
let relating_id = match entity.get_ref(5) {
Some(id) => id,
None => continue,
};
let related_attr = match entity.get(4) {
Some(a) => a,
None => continue,
};
let related_ids: Vec<u32> = match related_attr.as_list() {
Some(list) => list.iter().filter_map(|v| v.as_entity_ref()).collect(),
None => continue,
};
if related_ids.is_empty() {
continue;
}
let buildup = resolve_buildup(relating_id, decoder);
for obj_id in related_ids {
match index.element_to_buildup.get(&obj_id) {
Some(LayerBuildup::Sliceable { .. }) => continue,
_ => {
index.element_to_buildup.insert(obj_id, buildup.clone());
}
}
}
}
index
}
pub fn get(&self, element_id: u32) -> Option<&LayerBuildup> {
self.element_to_buildup.get(&element_id)
}
pub fn is_sliceable(&self, element_id: u32) -> bool {
matches!(
self.element_to_buildup.get(&element_id),
Some(LayerBuildup::Sliceable { .. })
)
}
pub fn len(&self) -> usize {
self.element_to_buildup.len()
}
pub fn is_empty(&self) -> bool {
self.element_to_buildup.is_empty()
}
pub fn sliceable_count(&self) -> usize {
self.element_to_buildup
.values()
.filter(|b| b.is_sliceable())
.count()
}
}
fn resolve_buildup(material_select_id: u32, decoder: &mut EntityDecoder) -> LayerBuildup {
let entity = match decoder.decode_by_id(material_select_id) {
Ok(e) => e,
Err(_) => return LayerBuildup::NotSliceable,
};
match entity.ifc_type {
IfcType::IfcMaterialLayerSetUsage => resolve_layer_set_usage(&entity, decoder),
_ => LayerBuildup::NotSliceable,
}
}
fn resolve_layer_set_usage(
usage: &DecodedEntity,
decoder: &mut EntityDecoder,
) -> LayerBuildup {
let layer_set_id = match usage.get_ref(0) {
Some(id) => id,
None => return LayerBuildup::NotSliceable,
};
let axis = match usage.get(1).and_then(|a| a.as_enum()).map(str::to_ascii_uppercase) {
Some(s) if s == "AXIS1" => LayerAxis::Axis1,
Some(s) if s == "AXIS2" => LayerAxis::Axis2,
Some(s) if s == "AXIS3" => LayerAxis::Axis3,
_ => return LayerBuildup::NotSliceable,
};
let direction_sense = match usage.get(2).and_then(|a| a.as_enum()).map(str::to_ascii_uppercase)
{
Some(s) if s == "POSITIVE" => 1.0_f64,
Some(s) if s == "NEGATIVE" => -1.0_f64,
_ => return LayerBuildup::NotSliceable,
};
let offset = usage.get_float(3).unwrap_or(0.0);
let layer_set_entity = match decoder.decode_by_id(layer_set_id) {
Ok(e) => e,
Err(_) => return LayerBuildup::NotSliceable,
};
if layer_set_entity.ifc_type != IfcType::IfcMaterialLayerSet {
return LayerBuildup::NotSliceable;
}
let layer_ids: Vec<u32> = match layer_set_entity.get(0).and_then(|a| a.as_list()) {
Some(list) => list.iter().filter_map(|v| v.as_entity_ref()).collect(),
None => return LayerBuildup::NotSliceable,
};
if layer_ids.is_empty() {
return LayerBuildup::NotSliceable;
}
let mut layers = Vec::with_capacity(layer_ids.len());
for layer_id in &layer_ids {
let layer = match decoder.decode_by_id(*layer_id) {
Ok(e) => e,
Err(_) => return LayerBuildup::NotSliceable,
};
if layer.ifc_type != IfcType::IfcMaterialLayer {
return LayerBuildup::NotSliceable;
}
let material_id = layer.get_ref(0).unwrap_or(0);
let thickness = layer.get_float(1).unwrap_or(0.0);
if !thickness.is_finite() || thickness <= 0.0 {
continue;
}
layers.push(LayerInfo { material_id, thickness });
}
if layers.len() < 2 {
return LayerBuildup::NotSliceable;
}
LayerBuildup::Sliceable {
layers,
axis,
direction_sense,
offset_from_reference_line: offset,
}
}