use super::GeometryRouter;
use crate::{Error, Mesh, Point3, Result, Vector3};
use ifc_lite_core::{DecodedEntity, EntityDecoder, IfcType};
use nalgebra::Matrix4;
impl GeometryRouter {
#[inline]
pub(super) fn has_clipping_planes(
&self,
element: &DecodedEntity,
decoder: &mut EntityDecoder,
) -> bool {
let representation_attr = match element.get(6) {
Some(attr) => attr,
None => return false,
};
let representation = match decoder.resolve_ref(representation_attr) {
Ok(Some(r)) if r.ifc_type == IfcType::IfcProductDefinitionShape => r,
_ => return false,
};
let representations_attr = match representation.get(2) {
Some(attr) => attr,
None => return false,
};
let representations = match decoder.resolve_ref_list(representations_attr) {
Ok(r) => r,
Err(_) => return false,
};
for shape_rep in &representations {
if shape_rep.ifc_type != IfcType::IfcShapeRepresentation {
continue;
}
let items_attr = match shape_rep.get(3) {
Some(attr) => attr,
None => continue,
};
let items = match decoder.resolve_ref_list(items_attr) {
Ok(i) => i,
Err(_) => continue,
};
for item in &items {
if item.ifc_type == IfcType::IfcBooleanClippingResult {
return true;
}
}
}
false
}
pub(super) fn extract_base_profile_and_clips(
&self,
element: &DecodedEntity,
decoder: &mut EntityDecoder,
) -> Result<(
crate::profile::Profile2D,
f64,
u8,
f64,
Option<Matrix4<f64>>,
Vec<(Point3<f64>, Vector3<f64>, bool)>,
)> {
use nalgebra::Vector3;
let mut clipping_planes: Vec<(Point3<f64>, Vector3<f64>, bool)> = Vec::new();
let representation_attr = element.get(6)
.ok_or_else(|| Error::geometry("Element missing representation".to_string()))?;
let representation = decoder.resolve_ref(representation_attr)?
.ok_or_else(|| Error::geometry("Failed to resolve representation".to_string()))?;
if representation.ifc_type != IfcType::IfcProductDefinitionShape {
return Err(Error::geometry("Element representation is not ProductDefinitionShape".to_string()));
}
let representations_attr = representation.get(2)
.ok_or_else(|| Error::geometry("Missing representations".to_string()))?;
let representations = decoder.resolve_ref_list(representations_attr)?;
for shape_rep in &representations {
if shape_rep.ifc_type != IfcType::IfcShapeRepresentation {
continue;
}
let items_attr = match shape_rep.get(3) {
Some(attr) => attr,
None => continue,
};
let items = decoder.resolve_ref_list(items_attr)?;
for item in &items {
if item.ifc_type == IfcType::IfcBooleanClippingResult {
let (profile, depth, axis, origin, transform, clips) =
self.extract_profile_from_boolean_result(item, decoder)?;
clipping_planes.extend(clips);
return Ok((profile, depth, axis, origin, transform, clipping_planes));
}
if item.ifc_type == IfcType::IfcExtrudedAreaSolid {
let (profile, depth, axis, origin, transform) =
self.extract_profile_from_extruded_solid(item, decoder)?;
return Ok((profile, depth, axis, origin, transform, clipping_planes));
}
}
}
Err(Error::geometry("Could not find IfcExtrudedAreaSolid in representation".to_string()))
}
fn extract_profile_from_boolean_result(
&self,
boolean_result: &DecodedEntity,
decoder: &mut EntityDecoder,
) -> Result<(
crate::profile::Profile2D,
f64,
u8,
f64,
Option<Matrix4<f64>>,
Vec<(Point3<f64>, Vector3<f64>, bool)>,
)> {
use nalgebra::Vector3;
let mut clipping_planes: Vec<(Point3<f64>, Vector3<f64>, bool)> = Vec::new();
let first_operand_attr = boolean_result.get(1)
.ok_or_else(|| Error::geometry("BooleanResult missing FirstOperand".to_string()))?;
let first_operand = decoder.resolve_ref(first_operand_attr)?
.ok_or_else(|| Error::geometry("Failed to resolve FirstOperand".to_string()))?;
if let Some(second_operand_attr) = boolean_result.get(2) {
if let Ok(Some(second_operand)) = decoder.resolve_ref(second_operand_attr) {
if let Some(clip) = self.extract_half_space_plane(&second_operand, decoder) {
clipping_planes.push(clip);
}
}
}
if first_operand.ifc_type == IfcType::IfcBooleanClippingResult {
let (profile, depth, axis, origin, transform, nested_clips) =
self.extract_profile_from_boolean_result(&first_operand, decoder)?;
clipping_planes.extend(nested_clips);
return Ok((profile, depth, axis, origin, transform, clipping_planes));
}
if first_operand.ifc_type == IfcType::IfcExtrudedAreaSolid {
let (profile, depth, axis, origin, transform) =
self.extract_profile_from_extruded_solid(&first_operand, decoder)?;
return Ok((profile, depth, axis, origin, transform, clipping_planes));
}
Err(Error::geometry(format!(
"Unsupported base solid type in boolean result: {:?}",
first_operand.ifc_type
)))
}
fn extract_profile_from_extruded_solid(
&self,
extruded_solid: &DecodedEntity,
decoder: &mut EntityDecoder,
) -> Result<(crate::profile::Profile2D, f64, u8, f64, Option<Matrix4<f64>>)> {
let swept_area_attr = extruded_solid.get(0)
.ok_or_else(|| Error::geometry("ExtrudedAreaSolid missing SweptArea".to_string()))?;
let profile_entity = decoder.resolve_ref(swept_area_attr)?
.ok_or_else(|| Error::geometry("Failed to resolve SweptArea".to_string()))?;
let profile = self.extract_profile_2d(&profile_entity, decoder)?;
let depth = extruded_solid.get_float(3)
.ok_or_else(|| Error::geometry("ExtrudedAreaSolid missing Depth".to_string()))?;
let direction_attr = extruded_solid.get(2)
.ok_or_else(|| Error::geometry("ExtrudedAreaSolid missing ExtrudedDirection".to_string()))?;
let direction_entity = decoder.resolve_ref(direction_attr)?
.ok_or_else(|| Error::geometry("Failed to resolve ExtrudedDirection".to_string()))?;
let ratios_attr = direction_entity.get(0)
.ok_or_else(|| Error::geometry("Direction missing DirectionRatios".to_string()))?;
let ratios = ratios_attr.as_list()
.ok_or_else(|| Error::geometry("DirectionRatios is not a list".to_string()))?;
let dx = ratios.get(0).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 dz = ratios.get(2).and_then(|v| v.as_float()).unwrap_or(1.0);
let thickness_axis = if dx.abs() >= dy.abs() && dx.abs() >= dz.abs() {
0 } else if dy.abs() >= dz.abs() {
1 } else {
2 };
let wall_origin = 0.0;
let position_transform = if let Some(pos_attr) = extruded_solid.get(1) {
if !pos_attr.is_null() {
if let Ok(Some(pos_entity)) = decoder.resolve_ref(pos_attr) {
if pos_entity.ifc_type == IfcType::IfcAxis2Placement3D {
match self.parse_axis2_placement_3d(&pos_entity, decoder) {
Ok(transform) => Some(transform),
Err(_) => None,
}
} else {
None
}
} else {
None
}
} else {
None
}
} else {
None
};
Ok((profile, depth, thickness_axis, wall_origin, position_transform))
}
#[allow(dead_code)] fn extract_base_from_boolean_result(
&self,
boolean_result: &DecodedEntity,
decoder: &mut EntityDecoder,
) -> Result<(Mesh, Vec<(Point3<f64>, Vector3<f64>, bool)>)> {
use nalgebra::Vector3;
let mut clipping_planes: Vec<(Point3<f64>, Vector3<f64>, bool)> = Vec::new();
let first_operand_attr = boolean_result.get(1)
.ok_or_else(|| Error::geometry("BooleanResult missing FirstOperand".to_string()))?;
let first_operand = decoder.resolve_ref(first_operand_attr)?
.ok_or_else(|| Error::geometry("Failed to resolve FirstOperand".to_string()))?;
if let Some(second_operand_attr) = boolean_result.get(2) {
if let Ok(Some(second_operand)) = decoder.resolve_ref(second_operand_attr) {
if let Some(clip) = self.extract_half_space_plane(&second_operand, decoder) {
clipping_planes.push(clip);
}
}
}
if first_operand.ifc_type == IfcType::IfcBooleanClippingResult {
let (base_mesh, nested_clips) = self.extract_base_from_boolean_result(&first_operand, decoder)?;
clipping_planes.extend(nested_clips);
return Ok((base_mesh, clipping_planes));
}
if let Some(processor) = self.processors.get(&first_operand.ifc_type) {
let mut mesh = processor.process(&first_operand, decoder, &self.schema)?;
self.scale_mesh(&mut mesh);
return Ok((mesh, clipping_planes));
}
Err(Error::geometry(format!(
"Unsupported base solid type: {:?}",
first_operand.ifc_type
)))
}
fn extract_half_space_plane(
&self,
half_space: &DecodedEntity,
decoder: &mut EntityDecoder,
) -> Option<(Point3<f64>, Vector3<f64>, bool)> {
use nalgebra::Vector3;
if half_space.ifc_type != IfcType::IfcHalfSpaceSolid
&& half_space.ifc_type != IfcType::IfcPolygonalBoundedHalfSpace {
return None;
}
let base_surface_attr = half_space.get(0)?;
let base_surface = decoder.resolve_ref(base_surface_attr).ok()??;
if base_surface.ifc_type != IfcType::IfcPlane {
return None;
}
let position_attr = base_surface.get(0)?;
let position = decoder.resolve_ref(position_attr).ok()??;
let location_attr = position.get(0)?;
let location = decoder.resolve_ref(location_attr).ok()??;
let coords_attr = location.get(0)?;
let coords = coords_attr.as_list()?;
let px = coords.first()?.as_float()?;
let py = coords.get(1)?.as_float()?;
let pz = coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0);
let plane_point = Point3::new(px, py, pz);
let plane_normal = if let Some(axis_attr) = position.get(1) {
if !axis_attr.is_null() {
if let Ok(Some(axis)) = decoder.resolve_ref(axis_attr) {
if let Some(dir_attr) = axis.get(0) {
if let Some(dir) = dir_attr.as_list() {
let nx = dir.first().and_then(|v| v.as_float()).unwrap_or(0.0);
let ny = dir.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
let nz = dir.get(2).and_then(|v| v.as_float()).unwrap_or(1.0);
Vector3::new(nx, ny, nz).normalize()
} else {
Vector3::new(0.0, 0.0, 1.0)
}
} else {
Vector3::new(0.0, 0.0, 1.0)
}
} else {
Vector3::new(0.0, 0.0, 1.0)
}
} else {
Vector3::new(0.0, 0.0, 1.0)
}
} else {
Vector3::new(0.0, 0.0, 1.0)
};
let agreement = half_space.get(1)
.map(|v| match v {
ifc_lite_core::AttributeValue::Enum(e) => e != "F" && e != ".F.",
_ => true,
})
.unwrap_or(true);
Some((plane_point, plane_normal, agreement))
}
}