ifc_lite_geometry/router/
voids_2d.rs1use super::GeometryRouter;
8use crate::bool2d::subtract_multiple_2d;
9use crate::csg::ClippingProcessor;
10use crate::profile::{Profile2D, Profile2DWithVoids, VoidInfo};
11use crate::void_analysis::{extract_coplanar_voids, extract_nonplanar_voids, VoidAnalyzer};
12use crate::void_index::VoidIndex;
13use crate::{Error, Mesh, Result, Vector3};
14use ifc_lite_core::{DecodedEntity, EntityDecoder, IfcType};
15use nalgebra::{Matrix4, Point2};
16use rustc_hash::FxHashMap;
17
18impl GeometryRouter {
19 #[inline]
31 pub fn process_element_with_voids_2d(
32 &self,
33 element: &DecodedEntity,
34 decoder: &mut EntityDecoder,
35 void_index: &VoidIndex,
36 ) -> Result<Mesh> {
37 let opening_ids = void_index.get_voids(element.id);
39
40 if opening_ids.is_empty() {
41 return self.process_element(element, decoder);
43 }
44
45 match self.try_process_extrusion_with_voids_2d(element, decoder, opening_ids) {
48 Ok(Some(mesh)) => Ok(mesh),
49 Ok(None) | Err(_) => {
50 let void_map: FxHashMap<u32, Vec<u32>> =
52 [(element.id, opening_ids.to_vec())].into_iter().collect();
53 self.process_element_with_voids(element, decoder, &void_map)
54 }
55 }
56 }
57
58 fn try_process_extrusion_with_voids_2d(
64 &self,
65 element: &DecodedEntity,
66 decoder: &mut EntityDecoder,
67 opening_ids: &[u32],
68 ) -> Result<Option<Mesh>> {
69 let representation_attr = match element.get(6) {
71 Some(attr) if !attr.is_null() => attr,
72 _ => return Ok(None),
73 };
74
75 let representation = match decoder.resolve_ref(representation_attr)? {
76 Some(r) => r,
77 None => return Ok(None),
78 };
79
80 if representation.ifc_type != IfcType::IfcProductDefinitionShape {
81 return Ok(None);
82 }
83
84 let representations_attr = match representation.get(2) {
86 Some(attr) => attr,
87 None => return Ok(None),
88 };
89
90 let representations = decoder.resolve_ref_list(representations_attr)?;
91
92 for shape_rep in &representations {
94 if shape_rep.ifc_type != IfcType::IfcShapeRepresentation {
95 continue;
96 }
97
98 let items_attr = match shape_rep.get(3) {
99 Some(attr) => attr,
100 None => continue,
101 };
102
103 let items = decoder.resolve_ref_list(items_attr)?;
104
105 for item in &items {
106 if item.ifc_type == IfcType::IfcExtrudedAreaSolid {
107 return self.process_extrusion_with_voids_2d_impl(
109 element,
110 item,
111 decoder,
112 opening_ids,
113 );
114 }
115 }
116 }
117
118 Ok(None)
119 }
120
121 fn process_extrusion_with_voids_2d_impl(
123 &self,
124 element: &DecodedEntity,
125 extrusion: &DecodedEntity,
126 decoder: &mut EntityDecoder,
127 opening_ids: &[u32],
128 ) -> Result<Option<Mesh>> {
129 let depth = match extrusion.get_float(3) {
134 Some(d) if d > 0.0 => d,
135 _ => return Ok(None),
136 };
137
138 let direction_attr = match extrusion.get(2) {
140 Some(attr) if !attr.is_null() => attr,
141 _ => return Ok(None),
142 };
143
144 let direction_entity = match decoder.resolve_ref(direction_attr)? {
145 Some(e) => e,
146 None => return Ok(None),
147 };
148
149 let local_extrusion_direction = self.parse_direction(&direction_entity)?;
150
151 let position_transform = if let Some(pos_attr) = extrusion.get(1) {
153 if !pos_attr.is_null() {
154 if let Some(pos_entity) = decoder.resolve_ref(pos_attr)? {
155 self.parse_axis2_placement_3d(&pos_entity, decoder)?
156 } else {
157 Matrix4::identity()
158 }
159 } else {
160 Matrix4::identity()
161 }
162 } else {
163 Matrix4::identity()
164 };
165
166 let extrusion_direction = {
169 let rot_x = Vector3::new(
170 position_transform[(0, 0)],
171 position_transform[(1, 0)],
172 position_transform[(2, 0)],
173 );
174 let rot_y = Vector3::new(
175 position_transform[(0, 1)],
176 position_transform[(1, 1)],
177 position_transform[(2, 1)],
178 );
179 let rot_z = Vector3::new(
180 position_transform[(0, 2)],
181 position_transform[(1, 2)],
182 position_transform[(2, 2)],
183 );
184 (rot_x * local_extrusion_direction.x
185 + rot_y * local_extrusion_direction.y
186 + rot_z * local_extrusion_direction.z)
187 .normalize()
188 };
189
190 let element_transform = self.get_placement_transform_from_element(element, decoder)?;
192 let combined_transform = element_transform * position_transform;
193
194 let profile_attr = match extrusion.get(0) {
196 Some(attr) if !attr.is_null() => attr,
197 _ => return Ok(None),
198 };
199
200 let profile_entity = match decoder.resolve_ref(profile_attr)? {
201 Some(e) => e,
202 None => return Ok(None),
203 };
204
205 let base_profile = match self.extract_profile_2d(&profile_entity, decoder) {
207 Ok(p) => p,
208 Err(_) => return Ok(None),
209 };
210
211 let mut void_meshes: Vec<Mesh> = Vec::new();
213
214 for &opening_id in opening_ids {
215 let opening_entity = match decoder.decode_by_id(opening_id) {
216 Ok(e) => e,
217 Err(_) => continue,
218 };
219
220 let opening_mesh = match self.process_element(&opening_entity, decoder) {
221 Ok(m) if !m.is_empty() => m,
222 _ => continue,
223 };
224
225 void_meshes.push(opening_mesh);
226 }
227
228 if void_meshes.is_empty() {
229 let processor = self.processors.get(&IfcType::IfcExtrudedAreaSolid);
231 if let Some(proc) = processor {
232 let mut mesh = proc.process(extrusion, decoder, &self.schema)?;
233 self.scale_mesh(&mut mesh);
234 self.apply_placement(element, decoder, &mut mesh)?;
235 return Ok(Some(mesh));
236 }
237 return Ok(None);
238 }
239
240 let analyzer = VoidAnalyzer::new();
243
244 let classifications: Vec<crate::void_analysis::VoidClassification> = void_meshes
245 .iter()
246 .map(|mesh| {
247 analyzer.classify_void(
248 mesh,
249 &combined_transform,
250 &extrusion_direction.normalize(),
251 depth,
252 )
253 })
254 .collect();
255
256 let coplanar_voids = extract_coplanar_voids(&classifications);
258 let nonplanar_voids = extract_nonplanar_voids(classifications);
259
260 let profile_with_voids = if !coplanar_voids.is_empty() {
262 let through_contours: Vec<Vec<Point2<f64>>> = coplanar_voids
264 .iter()
265 .filter(|v| v.is_through)
266 .map(|v| v.contour.clone())
267 .collect();
268
269 let modified_profile = if !through_contours.is_empty() {
271 match subtract_multiple_2d(&base_profile, &through_contours) {
272 Ok(p) => p,
273 Err(_) => base_profile.clone(),
274 }
275 } else {
276 base_profile.clone()
277 };
278
279 let partial_voids: Vec<VoidInfo> = coplanar_voids
281 .into_iter()
282 .filter(|v| !v.is_through)
283 .map(|v| VoidInfo {
284 contour: v.contour,
285 depth_start: v.depth_start,
286 depth_end: v.depth_end,
287 is_through: false,
288 })
289 .collect();
290
291 Profile2DWithVoids::new(modified_profile, partial_voids)
292 } else {
293 Profile2DWithVoids::from_profile(base_profile)
294 };
295
296 use crate::extrusion::extrude_profile_with_voids;
298
299 let mut mesh = match extrude_profile_with_voids(&profile_with_voids, depth, None) {
300 Ok(m) => m,
301 Err(_) => {
302 let processor = self.processors.get(&IfcType::IfcExtrudedAreaSolid);
304 if let Some(proc) = processor {
305 proc.process(extrusion, decoder, &self.schema)?
306 } else {
307 return Ok(None);
308 }
309 }
310 };
311
312 if position_transform != Matrix4::identity() {
314 self.transform_mesh_local(&mut mesh, &position_transform);
315 }
316
317 self.scale_mesh(&mut mesh);
319
320 self.apply_placement(element, decoder, &mut mesh)?;
322
323 if !nonplanar_voids.is_empty() {
325 let clipper = ClippingProcessor::new();
326 mesh = clipper.subtract_meshes_with_fallback(&mesh, &nonplanar_voids);
327 }
328
329 Ok(Some(mesh))
330 }
331
332 pub(super) fn extract_profile_2d(
334 &self,
335 profile_entity: &DecodedEntity,
336 decoder: &mut EntityDecoder,
337 ) -> Result<Profile2D> {
338 use crate::profile::create_rectangle;
339
340 match profile_entity.ifc_type {
341 IfcType::IfcRectangleProfileDef => {
342 let x_dim = profile_entity.get_float(3).unwrap_or(1.0);
344 let y_dim = profile_entity.get_float(4).unwrap_or(1.0);
345 Ok(create_rectangle(x_dim, y_dim))
346 }
347
348 IfcType::IfcCircleProfileDef => {
349 use crate::profile::create_circle;
350 let radius = profile_entity.get_float(3).unwrap_or(1.0);
351 Ok(create_circle(radius, None))
352 }
353
354 IfcType::IfcArbitraryClosedProfileDef => {
355 let curve_attr = profile_entity.get(2).ok_or_else(|| {
357 Error::geometry("ArbitraryClosedProfileDef missing OuterCurve".to_string())
358 })?;
359
360 let curve = decoder
361 .resolve_ref(curve_attr)?
362 .ok_or_else(|| Error::geometry("Failed to resolve OuterCurve".to_string()))?;
363
364 let points = self.extract_curve_points(&curve, decoder)?;
365 Ok(Profile2D::new(points))
366 }
367
368 IfcType::IfcArbitraryProfileDefWithVoids => {
369 let outer_attr = profile_entity.get(2).ok_or_else(|| {
371 Error::geometry("ArbitraryProfileDefWithVoids missing OuterCurve".to_string())
372 })?;
373
374 let outer_curve = decoder
375 .resolve_ref(outer_attr)?
376 .ok_or_else(|| Error::geometry("Failed to resolve OuterCurve".to_string()))?;
377
378 let outer_points = self.extract_curve_points(&outer_curve, decoder)?;
379 let mut profile = Profile2D::new(outer_points);
380
381 if let Some(inner_attr) = profile_entity.get(3) {
383 let inner_curves = decoder.resolve_ref_list(inner_attr)?;
384 for inner_curve in inner_curves {
385 if let Ok(hole_points) = self.extract_curve_points(&inner_curve, decoder) {
386 profile.add_hole(hole_points);
387 }
388 }
389 }
390
391 Ok(profile)
392 }
393
394 _ => Err(Error::geometry(format!(
395 "Unsupported profile type for 2D extraction: {}",
396 profile_entity.ifc_type
397 ))),
398 }
399 }
400
401 fn extract_curve_points(
403 &self,
404 curve: &DecodedEntity,
405 decoder: &mut EntityDecoder,
406 ) -> Result<Vec<Point2<f64>>> {
407 match curve.ifc_type {
408 IfcType::IfcPolyline => {
409 let points_attr = curve
411 .get(0)
412 .ok_or_else(|| Error::geometry("IfcPolyline missing Points".to_string()))?;
413
414 let point_entities = decoder.resolve_ref_list(points_attr)?;
415 let mut points = Vec::with_capacity(point_entities.len());
416
417 for (_i, point_entity) in point_entities.iter().enumerate() {
418 if point_entity.ifc_type == IfcType::IfcCartesianPoint {
419 if let Some(coords_attr) = point_entity.get(0) {
420 if let Some(coords) = coords_attr.as_list() {
421 let x = coords.first().and_then(|v| v.as_float()).unwrap_or(0.0);
422 let y = coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
423 points.push(Point2::new(x, y));
424 }
425 }
426 }
427 }
428
429 Ok(points)
430 }
431
432 IfcType::IfcIndexedPolyCurve => {
433 let points_attr = curve.get(0).ok_or_else(|| {
435 Error::geometry("IfcIndexedPolyCurve missing Points".to_string())
436 })?;
437
438 let point_list = decoder
439 .resolve_ref(points_attr)?
440 .ok_or_else(|| Error::geometry("Failed to resolve Points".to_string()))?;
441
442 if let Some(coord_attr) = point_list.get(0) {
444 if let Some(coord_list) = coord_attr.as_list() {
445 let mut points = Vec::with_capacity(coord_list.len());
446
447 for coord in coord_list {
448 if let Some(pair) = coord.as_list() {
449 let x = pair.first().and_then(|v| v.as_float()).unwrap_or(0.0);
450 let y = pair.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
451 points.push(Point2::new(x, y));
452 }
453 }
454
455 return Ok(points);
456 }
457 }
458
459 Err(Error::geometry(
460 "Failed to extract points from IfcIndexedPolyCurve".to_string(),
461 ))
462 }
463
464 IfcType::IfcCompositeCurve => {
465 let segments_attr = curve.get(0).ok_or_else(|| {
467 Error::geometry("IfcCompositeCurve missing Segments".to_string())
468 })?;
469
470 let segments = decoder.resolve_ref_list(segments_attr)?;
471 let mut all_points = Vec::new();
472
473 for segment in segments {
474 if let Some(parent_attr) = segment.get(2) {
476 if let Some(parent_curve) = decoder.resolve_ref(parent_attr)? {
477 if let Ok(points) = self.extract_curve_points(&parent_curve, decoder) {
478 all_points.extend(points);
479 }
480 }
481 }
482 }
483
484 Ok(all_points)
485 }
486
487 _ => Err(Error::geometry(format!(
488 "Unsupported curve type: {}",
489 curve.ifc_type
490 ))),
491 }
492 }
493}