ifc_lite_geometry/processors/
surface.rs1use crate::{Error, Mesh, Point2, Point3, Result, TessellationQuality, Vector3};
8use ifc_lite_core::{DecodedEntity, EntityDecoder, IfcSchema, IfcType};
9use nalgebra::Matrix4;
10
11use super::helpers::{get_axis2_placement_transform_by_id, get_direction_by_id};
12use crate::router::GeometryProcessor;
13
14pub struct SurfaceOfLinearExtrusionProcessor;
17
18#[path = "curve_walk.rs"]
19mod curve_walk;
20use curve_walk::{CurveWalk, MAX_CURVE_NODES, SEAM_EPS};
21
22impl SurfaceOfLinearExtrusionProcessor {
23 pub fn new() -> Self {
24 Self
25 }
26}
27
28impl GeometryProcessor for SurfaceOfLinearExtrusionProcessor {
29 fn process(
30 &self,
31 entity: &DecodedEntity,
32 decoder: &mut EntityDecoder,
33 _schema: &IfcSchema,
34 _quality: TessellationQuality,
35 ) -> Result<Mesh> {
36 let curve_attr = entity.get(0).ok_or_else(|| {
44 Error::geometry("SurfaceOfLinearExtrusion missing SweptCurve".to_string())
45 })?;
46
47 let curve_id = curve_attr.as_entity_ref().ok_or_else(|| {
48 Error::geometry("Expected entity reference for SweptCurve".to_string())
49 })?;
50
51 let position_attr = entity.get(1);
53 let position_transform = if let Some(attr) = position_attr {
54 if let Some(pos_id) = attr.as_entity_ref() {
55 get_axis2_placement_transform_by_id(pos_id, decoder)?
56 } else {
57 Matrix4::identity()
58 }
59 } else {
60 Matrix4::identity()
61 };
62
63 let direction_attr = entity.get(2).ok_or_else(|| {
65 Error::geometry("SurfaceOfLinearExtrusion missing ExtrudedDirection".to_string())
66 })?;
67
68 let direction = if let Some(dir_id) = direction_attr.as_entity_ref() {
69 get_direction_by_id(dir_id, decoder)
70 .ok_or_else(|| Error::geometry("Failed to get direction".to_string()))?
71 } else {
72 Vector3::new(0.0, 0.0, 1.0) };
74
75 let depth = entity
77 .get(3)
78 .and_then(|v| v.as_float())
79 .ok_or_else(|| Error::geometry("SurfaceOfLinearExtrusion missing Depth".to_string()))?;
80
81 let curve_points = Self::get_profile_curve_points(curve_id, decoder)?;
83
84 if curve_points.len() < 2 {
85 return Ok(Mesh::new());
86 }
87
88 let extrusion = direction.normalize() * depth;
90
91 let mut positions = Vec::with_capacity(curve_points.len() * 2 * 3);
92 let mut indices = Vec::with_capacity((curve_points.len() - 1) * 6);
93
94 for point in &curve_points {
96 let p3d = position_transform.transform_point(&Point3::new(point.x, point.y, 0.0));
98 positions.push(p3d.x as f32);
99 positions.push(p3d.y as f32);
100 positions.push(p3d.z as f32);
101 }
102
103 for point in &curve_points {
104 let p3d = position_transform.transform_point(&Point3::new(point.x, point.y, 0.0));
106 let p_extruded = p3d + extrusion;
107 positions.push(p_extruded.x as f32);
108 positions.push(p_extruded.y as f32);
109 positions.push(p_extruded.z as f32);
110 }
111
112 let n = curve_points.len() as u32;
114 for i in 0..n - 1 {
115 indices.push(i);
118 indices.push(i + 1);
119 indices.push(i + n);
120
121 indices.push(i + 1);
123 indices.push(i + n + 1);
124 indices.push(i + n);
125 }
126
127 Ok(Mesh {
128 positions,
129 normals: Vec::new(),
130 indices,
131 rtc_applied: false,
132 welded_in_object_frame: false,
133 plane_tags: None,
134 origin: [0.0; 3], instance_meta: None, local_bounds: None, local_to_world: None })
135 }
136
137 fn supported_types(&self) -> Vec<IfcType> {
138 vec![IfcType::IfcSurfaceOfLinearExtrusion]
139 }
140}
141
142impl SurfaceOfLinearExtrusionProcessor {
143 const MAX_CURVE_NESTING_DEPTH: u32 = 32;
147
148 fn get_profile_curve_points(
149 profile_id: u32,
150 decoder: &mut EntityDecoder,
151 ) -> Result<Vec<Point2<f64>>> {
152 let mut walk = CurveWalk::new();
153 Self::profile_curve_points_guarded(profile_id, decoder, &mut walk)
154 }
155
156 fn profile_curve_points_guarded(
157 profile_id: u32,
158 decoder: &mut EntityDecoder,
159 walk: &mut CurveWalk,
160 ) -> Result<Vec<Point2<f64>>> {
161 let profile = decoder.decode_by_id(profile_id)?;
162
163 let curve_attr = profile
166 .get(2)
167 .ok_or_else(|| Error::geometry("Profile missing curve".to_string()))?;
168
169 let curve_id = curve_attr
170 .as_entity_ref()
171 .ok_or_else(|| Error::geometry("Expected entity reference for curve".to_string()))?;
172
173 Self::curve_points_guarded(curve_id, decoder, 0, walk)
174 }
175
176 fn curve_points_guarded(
196 curve_id: u32,
197 decoder: &mut EntityDecoder,
198 depth: u32,
199 walk: &mut CurveWalk,
200 ) -> Result<Vec<Point2<f64>>> {
201 if depth >= Self::MAX_CURVE_NESTING_DEPTH || !walk.seen.insert(curve_id) {
202 return Ok(Vec::new());
203 }
204 walk.spend()?;
205 let out = Self::curve_points_inner(curve_id, decoder, depth, walk);
206 walk.seen.remove(&curve_id);
212 out
213 }
214
215 fn curve_points_inner(
216 curve_id: u32,
217 decoder: &mut EntityDecoder,
218 depth: u32,
219 walk: &mut CurveWalk,
220 ) -> Result<Vec<Point2<f64>>> {
221
222 let curve = decoder.decode_by_id(curve_id)?;
224
225 match curve.ifc_type {
226 IfcType::IfcPolyline => {
227 let point_ids = decoder
229 .get_polyloop_point_ids_fast(curve_id)
230 .ok_or_else(|| Error::geometry("Failed to get polyline points".to_string()))?;
231
232 let mut points = Vec::with_capacity(point_ids.len());
237 for point_id in point_ids {
238 let (x, y, _z) = decoder.get_cartesian_point_fast(point_id).ok_or_else(|| {
239 Error::geometry(format!("Polyline point #{point_id} did not resolve"))
240 })?;
241 points.push(Point2::new(x, y));
242 }
243 Ok(points)
244 }
245 IfcType::IfcCompositeCurve => {
246 Self::extract_composite_curve_points(curve_id, decoder, depth, walk)
248 }
249 _ => {
250 if let Some(point_ids) = decoder.get_polyloop_point_ids_fast(curve_id) {
252 let mut points = Vec::with_capacity(point_ids.len());
253 for point_id in point_ids {
254 let (x, y, _z) = decoder.get_cartesian_point_fast(point_id).ok_or_else(|| {
255 Error::geometry(format!("Curve point #{point_id} did not resolve"))
256 })?;
257 points.push(Point2::new(x, y));
258 }
259 Ok(points)
260 } else {
261 Ok(Vec::new())
262 }
263 }
264 }
265 }
266
267 fn extract_composite_curve_points(
269 curve_id: u32,
270 decoder: &mut EntityDecoder,
271 depth: u32,
272 walk: &mut CurveWalk,
273 ) -> Result<Vec<Point2<f64>>> {
274 let curve = decoder.decode_by_id(curve_id)?;
275
276 let segments_attr = curve
278 .get(0)
279 .ok_or_else(|| Error::geometry("CompositeCurve missing Segments".to_string()))?;
280
281 let segment_refs = segments_attr
282 .as_list()
283 .ok_or_else(|| Error::geometry("Expected segment list".to_string()))?;
284
285 let mut all_points: Vec<Point2<f64>> = Vec::new();
286
287 for seg_ref in segment_refs {
288 let seg_id = seg_ref.as_entity_ref().ok_or_else(|| {
289 Error::geometry("Expected entity reference for segment".to_string())
290 })?;
291
292 let segment = decoder.decode_by_id(seg_id)?;
293
294 let parent_curve_attr = segment
296 .get(2)
297 .ok_or_else(|| Error::geometry("Segment missing ParentCurve".to_string()))?;
298
299 let parent_curve_id = parent_curve_attr.as_entity_ref().ok_or_else(|| {
300 Error::geometry("Expected entity reference for parent curve".to_string())
301 })?;
302
303 let same_sense = segment
309 .get(1)
310 .map(|v| match v {
311 ifc_lite_core::AttributeValue::Enum(e) => e != "F" && e != ".F.",
312 _ => true,
313 })
314 .unwrap_or(true);
315
316 if let Ok(mut segment_points) =
320 Self::curve_points_guarded(parent_curve_id, decoder, depth + 1, walk)
321 {
322 if !same_sense {
323 segment_points.reverse();
324 }
325 let drop_seam = match (all_points.last(), segment_points.first()) {
330 (Some(prev), Some(next)) => {
331 (prev.x - next.x).abs() < SEAM_EPS && (prev.y - next.y).abs() < SEAM_EPS
332 }
333 _ => false,
334 };
335 let start_idx = usize::from(drop_seam);
336 all_points.extend(segment_points.into_iter().skip(start_idx));
337 }
338
339 if walk.exhausted {
346 return Err(Error::geometry(format!(
347 "Curve traversal exceeded {MAX_CURVE_NODES} nested curves"
348 )));
349 }
350 }
351
352 Ok(all_points)
353 }
354}
355
356impl Default for SurfaceOfLinearExtrusionProcessor {
357 fn default() -> Self {
358 Self::new()
359 }
360}
361
362#[cfg(test)]
363#[path = "surface_cycle_tests.rs"]
364mod surface_cycle_tests;