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 origin: [0.0; 3], instance_meta: None, local_bounds: None, local_to_world: None })
133 }
134
135 fn supported_types(&self) -> Vec<IfcType> {
136 vec![IfcType::IfcSurfaceOfLinearExtrusion]
137 }
138}
139
140impl SurfaceOfLinearExtrusionProcessor {
141 const MAX_CURVE_NESTING_DEPTH: u32 = 32;
145
146 fn get_profile_curve_points(
147 profile_id: u32,
148 decoder: &mut EntityDecoder,
149 ) -> Result<Vec<Point2<f64>>> {
150 let mut walk = CurveWalk::new();
151 Self::profile_curve_points_guarded(profile_id, decoder, &mut walk)
152 }
153
154 fn profile_curve_points_guarded(
155 profile_id: u32,
156 decoder: &mut EntityDecoder,
157 walk: &mut CurveWalk,
158 ) -> Result<Vec<Point2<f64>>> {
159 let profile = decoder.decode_by_id(profile_id)?;
160
161 let curve_attr = profile
164 .get(2)
165 .ok_or_else(|| Error::geometry("Profile missing curve".to_string()))?;
166
167 let curve_id = curve_attr
168 .as_entity_ref()
169 .ok_or_else(|| Error::geometry("Expected entity reference for curve".to_string()))?;
170
171 Self::curve_points_guarded(curve_id, decoder, 0, walk)
172 }
173
174 fn curve_points_guarded(
194 curve_id: u32,
195 decoder: &mut EntityDecoder,
196 depth: u32,
197 walk: &mut CurveWalk,
198 ) -> Result<Vec<Point2<f64>>> {
199 if depth >= Self::MAX_CURVE_NESTING_DEPTH || !walk.seen.insert(curve_id) {
200 return Ok(Vec::new());
201 }
202 walk.spend()?;
203 let out = Self::curve_points_inner(curve_id, decoder, depth, walk);
204 walk.seen.remove(&curve_id);
210 out
211 }
212
213 fn curve_points_inner(
214 curve_id: u32,
215 decoder: &mut EntityDecoder,
216 depth: u32,
217 walk: &mut CurveWalk,
218 ) -> Result<Vec<Point2<f64>>> {
219
220 let curve = decoder.decode_by_id(curve_id)?;
222
223 match curve.ifc_type {
224 IfcType::IfcPolyline => {
225 let point_ids = decoder
227 .get_polyloop_point_ids_fast(curve_id)
228 .ok_or_else(|| Error::geometry("Failed to get polyline points".to_string()))?;
229
230 let mut points = Vec::with_capacity(point_ids.len());
231 for point_id in point_ids {
232 if let Some((x, y, _z)) = decoder.get_cartesian_point_fast(point_id) {
233 points.push(Point2::new(x, y));
234 }
235 }
236 Ok(points)
237 }
238 IfcType::IfcCompositeCurve => {
239 Self::extract_composite_curve_points(curve_id, decoder, depth, walk)
241 }
242 _ => {
243 if let Some(point_ids) = decoder.get_polyloop_point_ids_fast(curve_id) {
245 let mut points = Vec::with_capacity(point_ids.len());
246 for point_id in point_ids {
247 if let Some((x, y, _z)) = decoder.get_cartesian_point_fast(point_id) {
248 points.push(Point2::new(x, y));
249 }
250 }
251 Ok(points)
252 } else {
253 Ok(Vec::new())
254 }
255 }
256 }
257 }
258
259 fn extract_composite_curve_points(
261 curve_id: u32,
262 decoder: &mut EntityDecoder,
263 depth: u32,
264 walk: &mut CurveWalk,
265 ) -> Result<Vec<Point2<f64>>> {
266 let curve = decoder.decode_by_id(curve_id)?;
267
268 let segments_attr = curve
270 .get(0)
271 .ok_or_else(|| Error::geometry("CompositeCurve missing Segments".to_string()))?;
272
273 let segment_refs = segments_attr
274 .as_list()
275 .ok_or_else(|| Error::geometry("Expected segment list".to_string()))?;
276
277 let mut all_points: Vec<Point2<f64>> = Vec::new();
278
279 for seg_ref in segment_refs {
280 let seg_id = seg_ref.as_entity_ref().ok_or_else(|| {
281 Error::geometry("Expected entity reference for segment".to_string())
282 })?;
283
284 let segment = decoder.decode_by_id(seg_id)?;
285
286 let parent_curve_attr = segment
288 .get(2)
289 .ok_or_else(|| Error::geometry("Segment missing ParentCurve".to_string()))?;
290
291 let parent_curve_id = parent_curve_attr.as_entity_ref().ok_or_else(|| {
292 Error::geometry("Expected entity reference for parent curve".to_string())
293 })?;
294
295 let same_sense = segment
301 .get(1)
302 .map(|v| match v {
303 ifc_lite_core::AttributeValue::Enum(e) => e != "F" && e != ".F.",
304 _ => true,
305 })
306 .unwrap_or(true);
307
308 if let Ok(mut segment_points) =
312 Self::curve_points_guarded(parent_curve_id, decoder, depth + 1, walk)
313 {
314 if !same_sense {
315 segment_points.reverse();
316 }
317 let drop_seam = match (all_points.last(), segment_points.first()) {
322 (Some(prev), Some(next)) => {
323 (prev.x - next.x).abs() < SEAM_EPS && (prev.y - next.y).abs() < SEAM_EPS
324 }
325 _ => false,
326 };
327 let start_idx = usize::from(drop_seam);
328 all_points.extend(segment_points.into_iter().skip(start_idx));
329 }
330
331 if walk.exhausted {
338 return Err(Error::geometry(format!(
339 "Curve traversal exceeded {MAX_CURVE_NODES} nested curves"
340 )));
341 }
342 }
343
344 Ok(all_points)
345 }
346}
347
348impl Default for SurfaceOfLinearExtrusionProcessor {
349 fn default() -> Self {
350 Self::new()
351 }
352}
353
354#[cfg(test)]
355#[path = "surface_cycle_tests.rs"]
356mod surface_cycle_tests;