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