ifc_lite_geometry/analytic/
extrusion.rs1use super::{extract_analytic_profile, AnalyticProfile, AnalyticStatus};
8use crate::transform::parse_axis2_placement_3d;
9use ifc_lite_core::{DecodedEntity, EntityDecoder, IfcType};
10use serde::Serialize;
11
12#[derive(Debug, Clone, PartialEq, Serialize)]
16pub struct AnalyticExtrusion {
17 pub solid_id: u32,
18 #[serde(rename = "SweptArea")]
19 pub swept_area_id: Option<u32>,
20 pub profile: Option<AnalyticProfile>,
21 #[serde(rename = "Position")]
22 pub position_id: Option<u32>,
23 pub position_matrix: Option<[f64; 16]>,
25 #[serde(rename = "ExtrudedDirection")]
26 pub extruded_direction_id: Option<u32>,
27 #[serde(rename = "DirectionRatios")]
29 pub direction_ratios: Option<[f64; 3]>,
30 pub axis_unit_vector: Option<[f64; 3]>,
32 #[serde(rename = "Depth")]
33 pub depth: Option<f64>,
34 pub status: AnalyticStatus,
35}
36
37pub fn extract_analytic_extrusion(
40 solid: &DecodedEntity, decoder: &mut EntityDecoder,
41) -> AnalyticExtrusion {
42 let mut result = AnalyticExtrusion {
43 solid_id: solid.id, swept_area_id: solid.get_ref(0), profile: None,
44 position_id: solid.get_ref(1), position_matrix: None,
45 extruded_direction_id: solid.get_ref(2), direction_ratios: None,
46 axis_unit_vector: None, depth: None, status: AnalyticStatus::Complete,
47 };
48 let parsed = parse_extrusion(solid, decoder, &mut result);
49 if let Err(reason) = parsed {
50 result.status = AnalyticStatus::Unsupported(reason);
51 }
52 result
53}
54
55fn parse_extrusion(
56 solid: &DecodedEntity, decoder: &mut EntityDecoder, result: &mut AnalyticExtrusion,
57) -> Result<(), String> {
58 match solid.ifc_type {
59 IfcType::IfcExtrudedAreaSolid => {}
60 IfcType::IfcExtrudedAreaSolidTapered =>
61 return Err("tapered extrusion has distinct start and end profiles".into()),
62 _ => return Err(format!("{} is not IfcExtrudedAreaSolid", solid.ifc_type.name())),
63 }
64 let area_id = result.swept_area_id.ok_or("missing SweptArea")?;
65 let area = decoder.decode_by_id(area_id)
66 .map_err(|error| format!("SweptArea #{area_id}: {error}"))?;
67 if !area.ifc_type.is_subtype_of(IfcType::IfcProfileDef) {
68 return Err(format!("SweptArea #{area_id} is not IfcProfileDef"));
69 }
70 let profile = extract_analytic_profile(&area, decoder);
71 if profile.profile_type.as_deref() != Some("AREA") {
72 let profile_type = profile.profile_type.clone().unwrap_or_else(|| "missing".into());
73 result.profile = Some(profile);
74 return Err(format!("SweptArea #{area_id} has ProfileType {profile_type}, expected AREA"));
75 }
76 if let AnalyticStatus::Unsupported(reason) = &profile.status {
77 let reason = format!("SweptArea #{area_id}: {reason}");
78 result.profile = Some(profile);
79 return Err(reason);
80 }
81 result.profile = Some(profile);
82
83 if let Some(position_attr) = solid.get(1).filter(|attribute| !attribute.is_null()) {
84 let id = position_attr.as_entity_ref().ok_or("invalid Position reference")?;
85 result.position_id = Some(id);
86 let position = decoder.decode_by_id(id)
87 .map_err(|error| format!("Position #{id}: {error}"))?;
88 if position.ifc_type != IfcType::IfcAxis2Placement3D {
89 return Err(format!("Position #{id} is not IfcAxis2Placement3D"));
90 }
91 let matrix = parse_axis2_placement_3d(&position, decoder)
92 .map_err(|error| format!("Position #{id}: {error}"))?;
93 if matrix.iter().any(|value| !value.is_finite()) {
94 return Err(format!("Position #{id} has non-finite coordinates"));
95 }
96 let mut values = [0.0; 16];
97 values.copy_from_slice(matrix.as_slice());
98 result.position_matrix = Some(values);
99 }
100
101 let direction_id = solid.get_ref(2).ok_or("missing ExtrudedDirection")?;
102 let direction = decoder.decode_by_id(direction_id)
103 .map_err(|error| format!("ExtrudedDirection #{direction_id}: {error}"))?;
104 if direction.ifc_type != IfcType::IfcDirection {
105 return Err(format!("ExtrudedDirection #{direction_id} is not IfcDirection"));
106 }
107 let ratios = direction.get_list(0).ok_or("ExtrudedDirection has no DirectionRatios")?;
108 if ratios.len() < 2 || ratios.len() > 3 {
109 return Err("ExtrudedDirection has invalid coordinate count".into());
110 }
111 let mut vector = [0.0; 3];
112 for (index, ratio) in ratios.iter().enumerate() {
113 vector[index] = ratio.as_float().ok_or("ExtrudedDirection has invalid ratio")?;
114 }
115 result.direction_ratios = Some(vector);
116 let norm = vector[0].hypot(vector[1]).hypot(vector[2]);
117 if !norm.is_finite() || norm <= 0.0 {
118 return Err("ExtrudedDirection is non-finite or zero".into());
119 }
120 let unit_axis = vector.map(|value| value / norm);
121 if unit_axis[2] == 0.0 {
124 return Err("ExtrudedDirection is perpendicular to local Z".into());
125 }
126 result.axis_unit_vector = Some(unit_axis);
127
128 let depth = solid.get_float(3).ok_or("missing Depth")?;
129 if !depth.is_finite() || depth <= 0.0 {
130 return Err("invalid Depth".into());
131 }
132 result.depth = Some(depth);
133 Ok(())
134}
135
136#[cfg(test)]
137#[path = "extrusion_tests.rs"]
138mod tests;