use crate::generated::IfcType;
use crate::parser::Token;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum GeometryCategory {
SweptSolid,
Boolean,
ExplicitMesh,
MappedItem,
Surface,
Curve,
Other,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ProfileCategory {
Parametric,
Arbitrary,
Composite,
}
#[derive(Debug, Clone)]
pub enum AttributeValue {
EntityRef(u32),
String(String),
Integer(i64),
Float(f64),
Enum(String),
List(Vec<AttributeValue>),
Null,
Derived,
}
impl AttributeValue {
pub fn from_token(token: &Token) -> Self {
match token {
Token::EntityRef(id) => AttributeValue::EntityRef(*id),
Token::String(s) => AttributeValue::String(s.to_string()),
Token::Integer(i) => AttributeValue::Integer(*i),
Token::Float(f) => AttributeValue::Float(*f),
Token::Enum(e) => AttributeValue::Enum(e.to_string()),
Token::List(items) => {
AttributeValue::List(items.iter().map(Self::from_token).collect())
}
Token::TypedValue(type_name, args) => {
let mut values = vec![AttributeValue::String(type_name.to_string())];
values.extend(args.iter().map(Self::from_token));
AttributeValue::List(values)
}
Token::Null => AttributeValue::Null,
Token::Derived => AttributeValue::Derived,
}
}
#[inline]
pub fn as_entity_ref(&self) -> Option<u32> {
match self {
AttributeValue::EntityRef(id) => Some(*id),
_ => None,
}
}
#[inline]
pub fn as_string(&self) -> Option<&str> {
match self {
AttributeValue::String(s) => Some(s),
_ => None,
}
}
#[inline]
pub fn as_enum(&self) -> Option<&str> {
match self {
AttributeValue::Enum(s) => Some(s),
_ => None,
}
}
#[inline]
pub fn as_float(&self) -> Option<f64> {
match self {
AttributeValue::Float(f) => Some(*f),
AttributeValue::Integer(i) => Some(*i as f64),
AttributeValue::List(items) if items.len() >= 2 => {
if matches!(items.first(), Some(AttributeValue::String(_))) {
match items.get(1) {
Some(AttributeValue::Float(f)) => Some(*f),
Some(AttributeValue::Integer(i)) => Some(*i as f64),
_ => None,
}
} else {
None
}
}
_ => None,
}
}
#[inline]
pub fn as_int(&self) -> Option<i64> {
match self {
AttributeValue::Integer(i) => Some(*i),
AttributeValue::Float(f) => Some(*f as i64),
_ => None,
}
}
#[inline]
pub fn as_list(&self) -> Option<&[AttributeValue]> {
match self {
AttributeValue::List(items) => Some(items),
_ => None,
}
}
#[inline]
pub fn is_null(&self) -> bool {
matches!(self, AttributeValue::Null | AttributeValue::Derived)
}
#[inline]
pub fn parse_coordinate_list_3d(coord_list: &[AttributeValue]) -> Vec<f32> {
let mut result = Vec::with_capacity(coord_list.len() * 3);
for coord_attr in coord_list {
if let Some(coord) = coord_attr.as_list() {
let x = coord.first().and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
let y = coord.get(1).and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
let z = coord.get(2).and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
result.push(x);
result.push(y);
result.push(z);
}
}
result
}
#[inline]
pub fn parse_coordinate_list_2d(coord_list: &[AttributeValue]) -> Vec<f32> {
let mut result = Vec::with_capacity(coord_list.len() * 2);
for coord_attr in coord_list {
if let Some(coord) = coord_attr.as_list() {
let x = coord.first().and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
let y = coord.get(1).and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
result.push(x);
result.push(y);
}
}
result
}
#[inline]
pub fn parse_index_list(face_list: &[AttributeValue]) -> Vec<u32> {
let mut result = Vec::with_capacity(face_list.len() * 3);
for face_attr in face_list {
if let Some(face) = face_attr.as_list() {
let i0 = (face.first().and_then(|v| v.as_int()).unwrap_or(1) - 1) as u32;
let i1 = (face.get(1).and_then(|v| v.as_int()).unwrap_or(1) - 1) as u32;
let i2 = (face.get(2).and_then(|v| v.as_int()).unwrap_or(1) - 1) as u32;
result.push(i0);
result.push(i1);
result.push(i2);
}
}
result
}
#[inline]
pub fn parse_coordinate_list_3d_f64(coord_list: &[AttributeValue]) -> Vec<(f64, f64, f64)> {
coord_list
.iter()
.filter_map(|coord_attr| {
let coord = coord_attr.as_list()?;
let x = coord.first().and_then(|v| v.as_float()).unwrap_or(0.0);
let y = coord.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
let z = coord.get(2).and_then(|v| v.as_float()).unwrap_or(0.0);
Some((x, y, z))
})
.collect()
}
}
#[derive(Debug, Clone)]
pub struct DecodedEntity {
pub id: u32,
pub ifc_type: IfcType,
pub attributes: Vec<AttributeValue>,
}
impl DecodedEntity {
pub fn new(id: u32, ifc_type: IfcType, attributes: Vec<AttributeValue>) -> Self {
Self {
id,
ifc_type,
attributes,
}
}
pub fn get(&self, index: usize) -> Option<&AttributeValue> {
self.attributes.get(index)
}
pub fn get_ref(&self, index: usize) -> Option<u32> {
self.get(index).and_then(|v| v.as_entity_ref())
}
pub fn get_string(&self, index: usize) -> Option<&str> {
self.get(index).and_then(|v| v.as_string())
}
pub fn get_float(&self, index: usize) -> Option<f64> {
self.get(index).and_then(|v| v.as_float())
}
pub fn get_list(&self, index: usize) -> Option<&[AttributeValue]> {
self.get(index).and_then(|v| v.as_list())
}
}
#[derive(Clone)]
pub struct IfcSchema {
pub geometry_types: HashMap<IfcType, GeometryCategory>,
pub profile_types: HashMap<IfcType, ProfileCategory>,
}
impl IfcSchema {
pub fn new() -> Self {
let mut geometry_types = HashMap::new();
let mut profile_types = HashMap::new();
geometry_types.insert(IfcType::IfcExtrudedAreaSolid, GeometryCategory::SweptSolid);
geometry_types.insert(IfcType::IfcRevolvedAreaSolid, GeometryCategory::SweptSolid);
geometry_types.insert(IfcType::IfcBooleanResult, GeometryCategory::Boolean);
geometry_types.insert(IfcType::IfcBooleanClippingResult, GeometryCategory::Boolean);
geometry_types.insert(IfcType::IfcFacetedBrep, GeometryCategory::ExplicitMesh);
geometry_types.insert(
IfcType::IfcTriangulatedFaceSet,
GeometryCategory::ExplicitMesh,
);
geometry_types.insert(IfcType::IfcPolygonalFaceSet, GeometryCategory::ExplicitMesh);
geometry_types.insert(IfcType::IfcFaceBasedSurfaceModel, GeometryCategory::Surface);
geometry_types.insert(
IfcType::IfcSurfaceOfLinearExtrusion,
GeometryCategory::Surface,
);
geometry_types.insert(
IfcType::IfcShellBasedSurfaceModel,
GeometryCategory::Surface,
);
geometry_types.insert(IfcType::IfcMappedItem, GeometryCategory::MappedItem);
profile_types.insert(IfcType::IfcRectangleProfileDef, ProfileCategory::Parametric);
profile_types.insert(IfcType::IfcCircleProfileDef, ProfileCategory::Parametric);
profile_types.insert(
IfcType::IfcCircleHollowProfileDef,
ProfileCategory::Parametric,
);
profile_types.insert(
IfcType::IfcRectangleHollowProfileDef,
ProfileCategory::Parametric,
);
profile_types.insert(IfcType::IfcIShapeProfileDef, ProfileCategory::Parametric);
profile_types.insert(IfcType::IfcLShapeProfileDef, ProfileCategory::Parametric);
profile_types.insert(IfcType::IfcUShapeProfileDef, ProfileCategory::Parametric);
profile_types.insert(IfcType::IfcTShapeProfileDef, ProfileCategory::Parametric);
profile_types.insert(IfcType::IfcCShapeProfileDef, ProfileCategory::Parametric);
profile_types.insert(IfcType::IfcZShapeProfileDef, ProfileCategory::Parametric);
profile_types.insert(
IfcType::IfcArbitraryClosedProfileDef,
ProfileCategory::Arbitrary,
);
profile_types.insert(
IfcType::IfcArbitraryProfileDefWithVoids,
ProfileCategory::Arbitrary,
);
profile_types.insert(IfcType::IfcCompositeProfileDef, ProfileCategory::Composite);
Self {
geometry_types,
profile_types,
}
}
pub fn geometry_category(&self, ifc_type: &IfcType) -> Option<GeometryCategory> {
self.geometry_types.get(ifc_type).copied()
}
pub fn profile_category(&self, ifc_type: &IfcType) -> Option<ProfileCategory> {
self.profile_types.get(ifc_type).copied()
}
pub fn is_geometry_type(&self, ifc_type: &IfcType) -> bool {
self.geometry_types.contains_key(ifc_type)
}
pub fn is_profile_type(&self, ifc_type: &IfcType) -> bool {
self.profile_types.contains_key(ifc_type)
}
pub fn has_geometry(&self, ifc_type: &IfcType) -> bool {
let name = ifc_type.name();
(matches!(
ifc_type,
IfcType::IfcWall
| IfcType::IfcWallStandardCase
| IfcType::IfcSlab
| IfcType::IfcBeam
| IfcType::IfcColumn
| IfcType::IfcRoof
| IfcType::IfcStair
| IfcType::IfcRamp
| IfcType::IfcRailing
| IfcType::IfcPlate
| IfcType::IfcMember
| IfcType::IfcFooting
| IfcType::IfcPile
| IfcType::IfcCovering
| IfcType::IfcCurtainWall
| IfcType::IfcDoor
| IfcType::IfcWindow
| IfcType::IfcChimney
| IfcType::IfcShadingDevice
| IfcType::IfcBuildingElementProxy
| IfcType::IfcBuildingElementPart
) || name.contains("Reinforc"))
|| matches!(
ifc_type,
IfcType::IfcFurnishingElement
| IfcType::IfcFurniture
| IfcType::IfcDuctSegment
| IfcType::IfcPipeSegment
| IfcType::IfcCableSegment
| IfcType::IfcProduct | IfcType::IfcDistributionElement
| IfcType::IfcFlowSegment
| IfcType::IfcFlowFitting
| IfcType::IfcFlowTerminal
)
|| matches!(
ifc_type,
IfcType::IfcSpace
| IfcType::IfcOpeningElement
| IfcType::IfcSite
)
}
}
impl Default for IfcSchema {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_schema_geometry_categories() {
let schema = IfcSchema::new();
assert_eq!(
schema.geometry_category(&IfcType::IfcExtrudedAreaSolid),
Some(GeometryCategory::SweptSolid)
);
assert_eq!(
schema.geometry_category(&IfcType::IfcBooleanResult),
Some(GeometryCategory::Boolean)
);
assert_eq!(
schema.geometry_category(&IfcType::IfcTriangulatedFaceSet),
Some(GeometryCategory::ExplicitMesh)
);
}
#[test]
fn test_attribute_value_conversion() {
let token = Token::EntityRef(123);
let attr = AttributeValue::from_token(&token);
assert_eq!(attr.as_entity_ref(), Some(123));
let token = Token::String("test");
let attr = AttributeValue::from_token(&token);
assert_eq!(attr.as_string(), Some("test"));
}
#[test]
fn test_decoded_entity() {
let entity = DecodedEntity::new(
1,
IfcType::IfcWall,
vec![
AttributeValue::EntityRef(2),
AttributeValue::String("Wall-001".to_string()),
AttributeValue::Float(3.5),
],
);
assert_eq!(entity.get_ref(0), Some(2));
assert_eq!(entity.get_string(1), Some("Wall-001"));
assert_eq!(entity.get_float(2), Some(3.5));
}
#[test]
fn test_as_float_with_typed_value() {
let plain_float = AttributeValue::Float(0.5);
assert_eq!(plain_float.as_float(), Some(0.5));
let integer = AttributeValue::Integer(42);
assert_eq!(integer.as_float(), Some(42.0));
let typed_value = AttributeValue::List(vec![
AttributeValue::String("IFCNORMALISEDRATIOMEASURE".to_string()),
AttributeValue::Float(0.5),
]);
assert_eq!(typed_value.as_float(), Some(0.5));
let typed_int = AttributeValue::List(vec![
AttributeValue::String("IFCINTEGER".to_string()),
AttributeValue::Integer(100),
]);
assert_eq!(typed_int.as_float(), Some(100.0));
let regular_list =
AttributeValue::List(vec![AttributeValue::Float(1.0), AttributeValue::Float(2.0)]);
assert_eq!(regular_list.as_float(), None);
let empty_list = AttributeValue::List(vec![]);
assert_eq!(empty_list.as_float(), None);
}
}