ifc_lite_geometry/processors/
extrusion.rs1use crate::{
8 extrusion::{apply_transform, extrude_profile},
9 profiles::ProfileProcessor,
10 Error, Mesh, Result, TessellationQuality, Vector3,
11};
12use ifc_lite_core::{DecodedEntity, EntityDecoder, IfcSchema, IfcType};
13use nalgebra::Matrix4;
14
15use super::helpers::parse_axis2_placement_3d;
16use crate::router::GeometryProcessor;
17use crate::scalar::{magnitude_squared3, GeomScalar};
18
19#[inline]
35pub(crate) fn extrusion_local_transform<S: GeomScalar>(
36 direction: &Vector3<S>,
37 depth: S,
38) -> Option<Matrix4<S>> {
39 let zero = S::from_f64(0.0);
40 let one = S::from_f64(1.0);
41 let norm = magnitude_squared3(direction).sqrt();
43 let local_direction = Vector3::new(
44 direction.x / norm,
45 direction.y / norm,
46 direction.z / norm,
47 );
48
49 let is_local_z_aligned =
54 local_direction.x.abs().value() < 0.001 && local_direction.y.abs().value() < 0.001;
55
56 if is_local_z_aligned {
57 if local_direction.z.value() < 0.0 {
61 #[rustfmt::skip]
63 let m = Matrix4::new(
64 one, zero, zero, zero,
65 zero, one, zero, zero,
66 zero, zero, one, -depth,
67 zero, zero, zero, one,
68 );
69 Some(m)
70 } else {
71 None
72 }
73 } else {
74 #[rustfmt::skip]
85 let shear_mat = Matrix4::new(
86 one, zero, local_direction.x, zero,
87 zero, one, local_direction.y, zero,
88 zero, zero, local_direction.z, zero,
89 zero, zero, zero, one,
90 );
91 Some(shear_mat)
92 }
93}
94
95pub struct ExtrudedAreaSolidProcessor {
98 profile_processor: ProfileProcessor,
99}
100
101impl ExtrudedAreaSolidProcessor {
102 pub fn new(schema: IfcSchema) -> Self {
104 Self {
105 profile_processor: ProfileProcessor::new(schema),
106 }
107 }
108}
109
110impl GeometryProcessor for ExtrudedAreaSolidProcessor {
111 fn process(
112 &self,
113 entity: &DecodedEntity,
114 decoder: &mut EntityDecoder,
115 _schema: &IfcSchema,
116 quality: TessellationQuality,
117 ) -> Result<Mesh> {
118 let profile_attr = entity
126 .get(0)
127 .ok_or_else(|| Error::geometry("ExtrudedAreaSolid missing SweptArea".to_string()))?;
128
129 let profile_entity = decoder
130 .resolve_ref(profile_attr)?
131 .ok_or_else(|| Error::geometry("Failed to resolve SweptArea".to_string()))?;
132
133 let profile = self
134 .profile_processor
135 .process(&profile_entity, decoder, quality)?;
136
137 if profile.outer.is_empty() {
138 return Ok(Mesh::new());
139 }
140
141 let direction_attr = entity.get(2).ok_or_else(|| {
143 Error::geometry("ExtrudedAreaSolid missing ExtrudedDirection".to_string())
144 })?;
145
146 let direction_entity = decoder
147 .resolve_ref(direction_attr)?
148 .ok_or_else(|| Error::geometry("Failed to resolve ExtrudedDirection".to_string()))?;
149
150 if direction_entity.ifc_type != IfcType::IfcDirection {
151 return Err(Error::geometry(format!(
152 "Expected IfcDirection, got {}",
153 direction_entity.ifc_type
154 )));
155 }
156
157 let ratios_attr = direction_entity
159 .get(0)
160 .ok_or_else(|| Error::geometry("IfcDirection missing ratios".to_string()))?;
161
162 let ratios = ratios_attr
163 .as_list()
164 .ok_or_else(|| Error::geometry("Expected ratio list".to_string()))?;
165
166 use ifc_lite_core::AttributeValue;
167 let dir_x = ratios
168 .first()
169 .and_then(|v: &AttributeValue| v.as_float())
170 .unwrap_or(0.0);
171 let dir_y = ratios
172 .get(1)
173 .and_then(|v: &AttributeValue| v.as_float())
174 .unwrap_or(0.0);
175 let dir_z = ratios
176 .get(2)
177 .and_then(|v: &AttributeValue| v.as_float())
178 .unwrap_or(1.0);
179
180 let direction = Vector3::new(dir_x, dir_y, dir_z);
181 if direction.norm_squared() <= f64::EPSILON {
182 return Err(Error::geometry(
183 "ExtrudedAreaSolid has zero-length ExtrudedDirection".to_string(),
184 ));
185 }
186
187 let depth = entity
189 .get_float(3)
190 .ok_or_else(|| Error::geometry("ExtrudedAreaSolid missing Depth".to_string()))?;
191
192 let pos_transform = if let Some(pos_attr) = entity.get(1) {
195 if !pos_attr.is_null() {
196 if let Some(pos_entity) = decoder.resolve_ref(pos_attr)? {
197 if pos_entity.ifc_type == IfcType::IfcAxis2Placement3D {
198 Some(parse_axis2_placement_3d(&pos_entity, decoder)?)
199 } else {
200 None
201 }
202 } else {
203 None
204 }
205 } else {
206 None
207 }
208 } else {
209 None
210 };
211
212 let transform = extrusion_local_transform(&direction, depth);
213
214 let mut mesh = extrude_profile(&profile, depth, transform)?;
216
217 if let Some(pos) = pos_transform {
219 apply_transform(&mut mesh, &pos);
220 }
221
222 Ok(mesh)
223 }
224
225 fn supported_types(&self) -> Vec<IfcType> {
226 vec![IfcType::IfcExtrudedAreaSolid]
227 }
228}