1use crate::{
10 extrusion::{apply_transform, extrude_profile}, profiles::ProfileProcessor, triangulation::triangulate_polygon,
11 Error, Mesh, Point3, Result, Vector3,
12};
13use ifc_lite_core::{DecodedEntity, EntityDecoder, GeometryCategory, IfcSchema, IfcType};
14use nalgebra::Matrix4;
15
16use super::router::GeometryProcessor;
17
18#[inline]
26fn extract_coord_index_bytes(bytes: &[u8]) -> Option<&[u8]> {
27 let eq_pos = bytes.iter().position(|&b| b == b'=')?;
29 let open_paren = bytes[eq_pos..].iter().position(|&b| b == b'(')?;
30 let args_start = eq_pos + open_paren + 1;
31
32 let mut depth = 1;
34 let mut attr_count = 0;
35 let mut attr_start = args_start;
36 let mut i = args_start;
37 let mut in_string = false;
38
39 while i < bytes.len() && depth > 0 {
40 let b = bytes[i];
41
42 if b == b'\'' {
44 in_string = !in_string;
45 i += 1;
46 continue;
47 }
48 if in_string {
49 i += 1;
50 continue;
51 }
52
53 if b == b'/' && i + 1 < bytes.len() && bytes[i + 1] == b'*' {
55 i += 2;
56 while i + 1 < bytes.len() && !(bytes[i] == b'*' && bytes[i + 1] == b'/') {
57 i += 1;
58 }
59 i += 2;
60 continue;
61 }
62
63 match b {
64 b'(' => {
65 if depth == 1 && attr_count == 3 {
66 attr_start = i;
68 }
69 depth += 1;
70 }
71 b')' => {
72 depth -= 1;
73 if depth == 1 && attr_count == 3 {
74 let candidate = &bytes[attr_start..i + 1];
76 if validate_coord_index_structure(candidate) {
77 return Some(candidate);
78 }
79 return None;
81 }
82 }
83 b',' if depth == 1 => {
84 attr_count += 1;
85 }
86 b'$' if depth == 1 && attr_count == 3 => {
87 return None;
89 }
90 _ => {}
91 }
92 i += 1;
93 }
94
95 None
96}
97
98#[inline]
104fn validate_coord_index_structure(bytes: &[u8]) -> bool {
105 if bytes.is_empty() {
106 return false;
107 }
108
109 let first = bytes.first().copied();
111 let last = bytes.last().copied();
112 if first != Some(b'(') || last != Some(b')') {
113 return false;
114 }
115
116 let mut depth = 0;
118 for &b in bytes {
119 match b {
120 b'(' => depth += 1,
121 b')' => {
122 if depth == 0 {
123 return false; }
125 depth -= 1;
126 }
127 b'0'..=b'9' | b',' | b' ' | b'\t' | b'\n' | b'\r' | b'-' => {}
128 b'$' | b'\'' | b'"' | b'/' | b'*' | b'#' => {
129 return false;
131 }
132 _ => {
133 if b.is_ascii_alphabetic() {
135 return false;
136 }
137 }
138 }
139 }
140
141 depth == 0
143}
144
145pub struct ExtrudedAreaSolidProcessor {
148 profile_processor: ProfileProcessor,
149}
150
151impl ExtrudedAreaSolidProcessor {
152 pub fn new(schema: IfcSchema) -> Self {
154 Self {
155 profile_processor: ProfileProcessor::new(schema),
156 }
157 }
158}
159
160impl GeometryProcessor for ExtrudedAreaSolidProcessor {
161 fn process(
162 &self,
163 entity: &DecodedEntity,
164 decoder: &mut EntityDecoder,
165 _schema: &IfcSchema,
166 ) -> Result<Mesh> {
167 let profile_attr = entity
175 .get(0)
176 .ok_or_else(|| Error::geometry("ExtrudedAreaSolid missing SweptArea".to_string()))?;
177
178 let profile_entity = decoder
179 .resolve_ref(profile_attr)?
180 .ok_or_else(|| Error::geometry("Failed to resolve SweptArea".to_string()))?;
181
182 let profile = self
183 .profile_processor
184 .process(&profile_entity, decoder)?;
185
186 if profile.outer.is_empty() {
187 return Ok(Mesh::new());
188 }
189
190 let direction_attr = entity
192 .get(2)
193 .ok_or_else(|| {
194 Error::geometry("ExtrudedAreaSolid missing ExtrudedDirection".to_string())
195 })?;
196
197 let direction_entity = decoder
198 .resolve_ref(direction_attr)?
199 .ok_or_else(|| Error::geometry("Failed to resolve ExtrudedDirection".to_string()))?;
200
201 if direction_entity.ifc_type != IfcType::IfcDirection {
202 return Err(Error::geometry(format!(
203 "Expected IfcDirection, got {}",
204 direction_entity.ifc_type
205 )));
206 }
207
208 let ratios_attr = direction_entity
210 .get(0)
211 .ok_or_else(|| Error::geometry("IfcDirection missing ratios".to_string()))?;
212
213 let ratios = ratios_attr
214 .as_list()
215 .ok_or_else(|| Error::geometry("Expected ratio list".to_string()))?;
216
217 use ifc_lite_core::AttributeValue;
218 let dir_x = ratios.get(0).and_then(|v: &AttributeValue| v.as_float()).unwrap_or(0.0);
219 let dir_y = ratios.get(1).and_then(|v: &AttributeValue| v.as_float()).unwrap_or(0.0);
220 let dir_z = ratios.get(2).and_then(|v: &AttributeValue| v.as_float()).unwrap_or(1.0);
221
222 let direction = Vector3::new(dir_x, dir_y, dir_z).normalize();
223
224 let depth = entity
226 .get_float(3)
227 .ok_or_else(|| Error::geometry("ExtrudedAreaSolid missing Depth".to_string()))?;
228
229 let transform = if direction.x.abs() < 0.001 && direction.y.abs() < 0.001 {
236 if direction.z < 0.0 {
244 Some(Matrix4::new_translation(&Vector3::new(0.0, 0.0, -depth)))
246 } else {
247 None
248 }
249 } else {
250 let new_z = direction.normalize();
252
253 let up = if new_z.z.abs() > 0.9 {
255 Vector3::new(0.0, 1.0, 0.0) } else {
257 Vector3::new(0.0, 0.0, 1.0) };
259
260 let new_x = up.cross(&new_z).normalize();
261 let new_y = new_z.cross(&new_x).normalize();
262
263 let mut transform_mat = Matrix4::identity();
264 transform_mat[(0, 0)] = new_x.x;
265 transform_mat[(1, 0)] = new_x.y;
266 transform_mat[(2, 0)] = new_x.z;
267 transform_mat[(0, 1)] = new_y.x;
268 transform_mat[(1, 1)] = new_y.y;
269 transform_mat[(2, 1)] = new_y.z;
270 transform_mat[(0, 2)] = new_z.x;
271 transform_mat[(1, 2)] = new_z.y;
272 transform_mat[(2, 2)] = new_z.z;
273
274 Some(transform_mat)
275 };
276
277 let mut mesh = extrude_profile(&profile, depth, transform)?;
279
280 if let Some(pos_attr) = entity.get(1) {
282 if !pos_attr.is_null() {
283 if let Some(pos_entity) = decoder.resolve_ref(pos_attr)? {
284 if pos_entity.ifc_type == IfcType::IfcAxis2Placement3D {
285 let pos_transform = self.parse_axis2_placement_3d(&pos_entity, decoder)?;
286 apply_transform(&mut mesh, &pos_transform);
287 }
288 }
289 }
290 }
291
292 Ok(mesh)
293 }
294
295 fn supported_types(&self) -> Vec<IfcType> {
296 vec![IfcType::IfcExtrudedAreaSolid]
297 }
298}
299
300impl ExtrudedAreaSolidProcessor {
301 #[inline]
303 fn parse_axis2_placement_3d(
304 &self,
305 placement: &DecodedEntity,
306 decoder: &mut EntityDecoder,
307 ) -> Result<Matrix4<f64>> {
308 let location = self.parse_cartesian_point(placement, decoder, 0)?;
310
311 let z_axis = if let Some(axis_attr) = placement.get(1) {
313 if !axis_attr.is_null() {
314 if let Some(axis_entity) = decoder.resolve_ref(axis_attr)? {
315 self.parse_direction(&axis_entity)?
316 } else {
317 Vector3::new(0.0, 0.0, 1.0)
318 }
319 } else {
320 Vector3::new(0.0, 0.0, 1.0)
321 }
322 } else {
323 Vector3::new(0.0, 0.0, 1.0)
324 };
325
326 let x_axis = if let Some(ref_dir_attr) = placement.get(2) {
327 if !ref_dir_attr.is_null() {
328 if let Some(ref_dir_entity) = decoder.resolve_ref(ref_dir_attr)? {
329 self.parse_direction(&ref_dir_entity)?
330 } else {
331 Vector3::new(1.0, 0.0, 0.0)
332 }
333 } else {
334 Vector3::new(1.0, 0.0, 0.0)
335 }
336 } else {
337 Vector3::new(1.0, 0.0, 0.0)
338 };
339
340 let z_axis_final = z_axis.normalize();
342 let x_axis_normalized = x_axis.normalize();
343
344 let dot_product = x_axis_normalized.dot(&z_axis_final);
346 let x_axis_orthogonal = x_axis_normalized - z_axis_final * dot_product;
347 let x_axis_final = if x_axis_orthogonal.norm() > 1e-6 {
348 x_axis_orthogonal.normalize()
349 } else {
350 if z_axis_final.z.abs() < 0.9 {
352 Vector3::new(0.0, 0.0, 1.0).cross(&z_axis_final).normalize()
353 } else {
354 Vector3::new(1.0, 0.0, 0.0).cross(&z_axis_final).normalize()
355 }
356 };
357
358 let y_axis = z_axis_final.cross(&x_axis_final).normalize();
360
361 let mut transform = Matrix4::identity();
364 transform[(0, 0)] = x_axis_final.x;
365 transform[(1, 0)] = x_axis_final.y;
366 transform[(2, 0)] = x_axis_final.z;
367 transform[(0, 1)] = y_axis.x;
368 transform[(1, 1)] = y_axis.y;
369 transform[(2, 1)] = y_axis.z;
370 transform[(0, 2)] = z_axis_final.x;
371 transform[(1, 2)] = z_axis_final.y;
372 transform[(2, 2)] = z_axis_final.z;
373 transform[(0, 3)] = location.x;
374 transform[(1, 3)] = location.y;
375 transform[(2, 3)] = location.z;
376
377 Ok(transform)
378 }
379
380 #[inline]
382 fn parse_cartesian_point(
383 &self,
384 parent: &DecodedEntity,
385 decoder: &mut EntityDecoder,
386 attr_index: usize,
387 ) -> Result<Point3<f64>> {
388 let point_attr = parent
389 .get(attr_index)
390 .ok_or_else(|| Error::geometry("Missing cartesian point".to_string()))?;
391
392 let point_entity = decoder
393 .resolve_ref(point_attr)?
394 .ok_or_else(|| Error::geometry("Failed to resolve cartesian point".to_string()))?;
395
396 if point_entity.ifc_type != IfcType::IfcCartesianPoint {
397 return Err(Error::geometry(format!(
398 "Expected IfcCartesianPoint, got {}",
399 point_entity.ifc_type
400 )));
401 }
402
403 let coords_attr = point_entity
405 .get(0)
406 .ok_or_else(|| Error::geometry("IfcCartesianPoint missing coordinates".to_string()))?;
407
408 let coords = coords_attr
409 .as_list()
410 .ok_or_else(|| Error::geometry("Expected coordinate list".to_string()))?;
411
412 let x = coords
413 .get(0)
414 .and_then(|v| v.as_float())
415 .unwrap_or(0.0);
416 let y = coords
417 .get(1)
418 .and_then(|v| v.as_float())
419 .unwrap_or(0.0);
420 let z = coords
421 .get(2)
422 .and_then(|v| v.as_float())
423 .unwrap_or(0.0);
424
425 Ok(Point3::new(x, y, z))
426 }
427
428 #[inline]
430 fn parse_direction(&self, direction_entity: &DecodedEntity) -> Result<Vector3<f64>> {
431 if direction_entity.ifc_type != IfcType::IfcDirection {
432 return Err(Error::geometry(format!(
433 "Expected IfcDirection, got {}",
434 direction_entity.ifc_type
435 )));
436 }
437
438 let ratios_attr = direction_entity
440 .get(0)
441 .ok_or_else(|| Error::geometry("IfcDirection missing ratios".to_string()))?;
442
443 let ratios = ratios_attr
444 .as_list()
445 .ok_or_else(|| Error::geometry("Expected ratio list".to_string()))?;
446
447 let x = ratios.get(0).and_then(|v| v.as_float()).unwrap_or(0.0);
448 let y = ratios.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
449 let z = ratios.get(2).and_then(|v| v.as_float()).unwrap_or(0.0);
450
451 Ok(Vector3::new(x, y, z))
452 }
453}
454
455pub struct TriangulatedFaceSetProcessor;
458
459impl TriangulatedFaceSetProcessor {
460 pub fn new() -> Self {
461 Self
462 }
463}
464
465impl GeometryProcessor for TriangulatedFaceSetProcessor {
466 #[inline]
467 fn process(
468 &self,
469 entity: &DecodedEntity,
470 decoder: &mut EntityDecoder,
471 _schema: &IfcSchema,
472 ) -> Result<Mesh> {
473 let coords_attr = entity
481 .get(0)
482 .ok_or_else(|| {
483 Error::geometry("TriangulatedFaceSet missing Coordinates".to_string())
484 })?;
485
486 let coord_entity_id = coords_attr
487 .as_entity_ref()
488 .ok_or_else(|| Error::geometry("Expected entity reference for Coordinates".to_string()))?;
489
490 use ifc_lite_core::{extract_coordinate_list_from_entity, parse_indices_direct};
493
494 let positions = if let Some(raw_bytes) = decoder.get_raw_bytes(coord_entity_id) {
495 extract_coordinate_list_from_entity(raw_bytes).unwrap_or_default()
498 } else {
499 let coords_entity = decoder
501 .decode_by_id(coord_entity_id)?;
502
503 let coord_list_attr = coords_entity
504 .get(0)
505 .ok_or_else(|| Error::geometry("CartesianPointList3D missing CoordList".to_string()))?;
506
507 let coord_list = coord_list_attr
508 .as_list()
509 .ok_or_else(|| Error::geometry("Expected coordinate list".to_string()))?;
510
511 use ifc_lite_core::AttributeValue;
512 AttributeValue::parse_coordinate_list_3d(coord_list)
513 };
514
515 let indices_attr = entity
517 .get(3)
518 .ok_or_else(|| Error::geometry("TriangulatedFaceSet missing CoordIndex".to_string()))?;
519
520 let indices = if let Some(raw_entity_bytes) = decoder.get_raw_bytes(entity.id) {
523 if let Some(coord_index_bytes) = extract_coord_index_bytes(raw_entity_bytes) {
526 parse_indices_direct(coord_index_bytes)
527 } else {
528 let face_list = indices_attr
530 .as_list()
531 .ok_or_else(|| Error::geometry("Expected face index list".to_string()))?;
532 use ifc_lite_core::AttributeValue;
533 AttributeValue::parse_index_list(face_list)
534 }
535 } else {
536 let face_list = indices_attr
537 .as_list()
538 .ok_or_else(|| Error::geometry("Expected face index list".to_string()))?;
539 use ifc_lite_core::AttributeValue;
540 AttributeValue::parse_index_list(face_list)
541 };
542
543 Ok(Mesh {
545 positions,
546 normals: Vec::new(),
547 indices,
548 })
549 }
550
551 fn supported_types(&self) -> Vec<IfcType> {
552 vec![IfcType::IfcTriangulatedFaceSet]
553 }
554}
555
556impl Default for TriangulatedFaceSetProcessor {
557 fn default() -> Self {
558 Self::new()
559 }
560}
561
562struct FaceData {
564 outer_points: Vec<Point3<f64>>,
565 hole_points: Vec<Vec<Point3<f64>>>,
566}
567
568struct FaceResult {
570 positions: Vec<f32>,
571 indices: Vec<u32>,
572}
573
574pub struct FacetedBrepProcessor;
579
580impl FacetedBrepProcessor {
581 pub fn new() -> Self {
582 Self
583 }
584
585 #[inline]
588 fn extract_loop_points(
589 &self,
590 loop_entity: &DecodedEntity,
591 decoder: &mut EntityDecoder,
592 ) -> Option<Vec<Point3<f64>>> {
593 let polygon_attr = loop_entity.get(0)?;
595
596 let point_refs = polygon_attr.as_list()?;
598
599 let mut polygon_points = Vec::with_capacity(point_refs.len());
601
602 for point_ref in point_refs {
603 let point_id = point_ref.as_entity_ref()?;
604
605 if let Some((x, y, z)) = decoder.get_cartesian_point_fast(point_id) {
607 polygon_points.push(Point3::new(x, y, z));
608 } else {
609 let point = decoder.decode_by_id(point_id).ok()?;
611 let coords_attr = point.get(0)?;
612 let coords = coords_attr.as_list()?;
613 use ifc_lite_core::AttributeValue;
614 let x = coords.get(0).and_then(|v: &AttributeValue| v.as_float())?;
615 let y = coords.get(1).and_then(|v: &AttributeValue| v.as_float())?;
616 let z = coords.get(2).and_then(|v: &AttributeValue| v.as_float())?;
617 polygon_points.push(Point3::new(x, y, z));
618 }
619 }
620
621 if polygon_points.len() >= 3 {
622 Some(polygon_points)
623 } else {
624 None
625 }
626 }
627
628 #[inline]
631 fn extract_loop_points_fast(
632 &self,
633 loop_entity_id: u32,
634 decoder: &mut EntityDecoder,
635 ) -> Option<Vec<Point3<f64>>> {
636 let point_ids = decoder.get_polyloop_point_ids_fast(loop_entity_id)?;
638
639 let mut polygon_points = Vec::with_capacity(point_ids.len());
641
642 for point_id in point_ids {
643 let (x, y, z) = decoder.get_cartesian_point_fast(point_id)?;
646 polygon_points.push(Point3::new(x, y, z));
647 }
648
649 if polygon_points.len() >= 3 {
650 Some(polygon_points)
651 } else {
652 None
653 }
654 }
655
656 #[inline]
659 fn triangulate_face(face: &FaceData) -> FaceResult {
660 let n = face.outer_points.len();
661
662 if n == 3 && face.hole_points.is_empty() {
664 let mut positions = Vec::with_capacity(9);
665 for point in &face.outer_points {
666 positions.push(point.x as f32);
667 positions.push(point.y as f32);
668 positions.push(point.z as f32);
669 }
670 return FaceResult {
671 positions,
672 indices: vec![0, 1, 2],
673 };
674 }
675
676 if n == 4 && face.hole_points.is_empty() {
678 let mut positions = Vec::with_capacity(12);
679 for point in &face.outer_points {
680 positions.push(point.x as f32);
681 positions.push(point.y as f32);
682 positions.push(point.z as f32);
683 }
684 return FaceResult {
685 positions,
686 indices: vec![0, 1, 2, 0, 2, 3],
687 };
688 }
689
690 if face.hole_points.is_empty() && n <= 8 {
692 let mut is_convex = true;
694 if n > 4 {
695 use crate::triangulation::calculate_polygon_normal;
696 let normal = calculate_polygon_normal(&face.outer_points);
697 let mut sign = 0i8;
698
699 for i in 0..n {
700 let p0 = &face.outer_points[i];
701 let p1 = &face.outer_points[(i + 1) % n];
702 let p2 = &face.outer_points[(i + 2) % n];
703
704 let v1 = p1 - p0;
705 let v2 = p2 - p1;
706 let cross = v1.cross(&v2);
707 let dot = cross.dot(&normal);
708
709 if dot.abs() > 1e-10 {
710 let current_sign = if dot > 0.0 { 1i8 } else { -1i8 };
711 if sign == 0 {
712 sign = current_sign;
713 } else if sign != current_sign {
714 is_convex = false;
715 break;
716 }
717 }
718 }
719 }
720
721 if is_convex {
722 let mut positions = Vec::with_capacity(n * 3);
723 for point in &face.outer_points {
724 positions.push(point.x as f32);
725 positions.push(point.y as f32);
726 positions.push(point.z as f32);
727 }
728 let mut indices = Vec::with_capacity((n - 2) * 3);
729 for i in 1..n - 1 {
730 indices.push(0);
731 indices.push(i as u32);
732 indices.push(i as u32 + 1);
733 }
734 return FaceResult { positions, indices };
735 }
736 }
737
738 use crate::triangulation::{triangulate_polygon_with_holes, calculate_polygon_normal, project_to_2d, project_to_2d_with_basis};
740
741 let mut positions = Vec::new();
742 let mut indices = Vec::new();
743
744 let normal = calculate_polygon_normal(&face.outer_points);
746
747 let (outer_2d, u_axis, v_axis, origin) = project_to_2d(&face.outer_points, &normal);
749
750 let holes_2d: Vec<Vec<nalgebra::Point2<f64>>> = face.hole_points
752 .iter()
753 .map(|hole| project_to_2d_with_basis(hole, &u_axis, &v_axis, &origin))
754 .collect();
755
756 let tri_indices = match triangulate_polygon_with_holes(&outer_2d, &holes_2d) {
758 Ok(idx) => idx,
759 Err(_) => {
760 for point in &face.outer_points {
762 positions.push(point.x as f32);
763 positions.push(point.y as f32);
764 positions.push(point.z as f32);
765 }
766 for i in 1..face.outer_points.len() - 1 {
767 indices.push(0);
768 indices.push(i as u32);
769 indices.push(i as u32 + 1);
770 }
771 return FaceResult { positions, indices };
772 }
773 };
774
775 let mut all_points_3d: Vec<&Point3<f64>> = face.outer_points.iter().collect();
777 for hole in &face.hole_points {
778 all_points_3d.extend(hole.iter());
779 }
780
781 for point in &all_points_3d {
783 positions.push(point.x as f32);
784 positions.push(point.y as f32);
785 positions.push(point.z as f32);
786 }
787
788 for i in (0..tri_indices.len()).step_by(3) {
790 indices.push(tri_indices[i] as u32);
791 indices.push(tri_indices[i + 1] as u32);
792 indices.push(tri_indices[i + 2] as u32);
793 }
794
795 FaceResult { positions, indices }
796 }
797
798 pub fn process_batch(
802 &self,
803 brep_ids: &[u32],
804 decoder: &mut EntityDecoder,
805 ) -> Vec<(usize, Mesh)> {
806 use rayon::prelude::*;
807
808 let mut all_faces: Vec<(usize, FaceData)> = Vec::with_capacity(brep_ids.len() * 10);
811
812 for (brep_idx, &brep_id) in brep_ids.iter().enumerate() {
813 let brep_entity = match decoder.decode_by_id(brep_id) {
815 Ok(e) => e,
816 Err(_) => continue,
817 };
818
819 let shell_id = match brep_entity.get(0).and_then(|a| a.as_entity_ref()) {
821 Some(id) => id,
822 None => continue,
823 };
824
825 let face_ids = match decoder.get_entity_ref_list_fast(shell_id) {
827 Some(ids) => ids,
828 None => continue,
829 };
830
831 for face_id in face_ids {
833 let bound_ids = match decoder.get_entity_ref_list_fast(face_id) {
834 Some(ids) => ids,
835 None => continue,
836 };
837
838 let mut outer_bound_points: Option<Vec<Point3<f64>>> = None;
839 let mut hole_points: Vec<Vec<Point3<f64>>> = Vec::new();
840
841 for bound_id in bound_ids {
842 let bound = match decoder.decode_by_id(bound_id) {
843 Ok(b) => b,
844 Err(_) => continue,
845 };
846
847 let loop_attr = match bound.get(0) {
848 Some(attr) => attr,
849 None => continue,
850 };
851
852 let orientation = bound.get(1)
853 .and_then(|v| match v {
854 ifc_lite_core::AttributeValue::Enum(e) => Some(e != "F" && e != ".F."),
856 _ => Some(true),
857 })
858 .unwrap_or(true);
859
860 let mut points = if let Some(loop_id) = loop_attr.as_entity_ref() {
861 match self.extract_loop_points_fast(loop_id, decoder) {
862 Some(p) => p,
863 None => continue,
864 }
865 } else {
866 continue
867 };
868
869 if !orientation {
870 points.reverse();
871 }
872
873 let is_outer = match bound.ifc_type {
874 IfcType::IfcFaceOuterBound => true,
875 IfcType::IfcFaceBound => false,
876 _ => bound.ifc_type.as_str().contains("OUTER"),
877 };
878
879 if is_outer || outer_bound_points.is_none() {
880 if outer_bound_points.is_some() && is_outer {
881 if let Some(prev_outer) = outer_bound_points.take() {
882 hole_points.push(prev_outer);
883 }
884 }
885 outer_bound_points = Some(points);
886 } else {
887 hole_points.push(points);
888 }
889 }
890
891 if let Some(outer_points) = outer_bound_points {
892 all_faces.push((brep_idx, FaceData {
893 outer_points,
894 hole_points,
895 }));
896 }
897 }
898 }
899
900 let face_results: Vec<(usize, FaceResult)> = all_faces
902 .par_iter()
903 .map(|(brep_idx, face)| (*brep_idx, Self::triangulate_face(face)))
904 .collect();
905
906 let mut face_counts = vec![0usize; brep_ids.len()];
909 for (brep_idx, _) in &face_results {
910 face_counts[*brep_idx] += 1;
911 }
912
913 let mut mesh_builders: Vec<(Vec<f32>, Vec<u32>)> = face_counts
915 .iter()
916 .map(|&count| {
917 (Vec::with_capacity(count * 100), Vec::with_capacity(count * 50))
918 })
919 .collect();
920
921 for (brep_idx, result) in face_results {
923 let (positions, indices) = &mut mesh_builders[brep_idx];
924 let base_idx = (positions.len() / 3) as u32;
925 positions.extend(result.positions);
926 for idx in result.indices {
927 indices.push(base_idx + idx);
928 }
929 }
930
931 mesh_builders
933 .into_iter()
934 .enumerate()
935 .filter(|(_, (positions, _))| !positions.is_empty())
936 .map(|(brep_idx, (positions, indices))| {
937 (brep_idx, Mesh {
938 positions,
939 normals: Vec::new(),
940 indices,
941 })
942 })
943 .collect()
944 }
945}
946
947impl GeometryProcessor for FacetedBrepProcessor {
948 fn process(
949 &self,
950 entity: &DecodedEntity,
951 decoder: &mut EntityDecoder,
952 _schema: &IfcSchema,
953 ) -> Result<Mesh> {
954 use rayon::prelude::*;
955
956 let shell_attr = entity
961 .get(0)
962 .ok_or_else(|| Error::geometry("FacetedBrep missing Outer shell".to_string()))?;
963
964 let shell_id = shell_attr
965 .as_entity_ref()
966 .ok_or_else(|| Error::geometry("Expected entity ref for Outer shell".to_string()))?;
967
968 let face_ids = decoder
970 .get_entity_ref_list_fast(shell_id)
971 .ok_or_else(|| Error::geometry("Failed to get faces from ClosedShell".to_string()))?;
972
973 let mut face_data_list: Vec<FaceData> = Vec::with_capacity(face_ids.len());
975
976 for face_id in face_ids {
977 let bound_ids = match decoder.get_entity_ref_list_fast(face_id) {
979 Some(ids) => ids,
980 None => continue,
981 };
982
983 let mut outer_bound_points: Option<Vec<Point3<f64>>> = None;
985 let mut hole_points: Vec<Vec<Point3<f64>>> = Vec::new();
986
987 for bound_id in bound_ids {
988 let bound = match decoder.decode_by_id(bound_id) {
990 Ok(b) => b,
991 Err(_) => continue,
992 };
993
994 let loop_attr = match bound.get(0) {
995 Some(attr) => attr,
996 None => continue,
997 };
998
999 let orientation = bound.get(1)
1001 .and_then(|v| match v {
1002 ifc_lite_core::AttributeValue::Enum(e) => Some(e != "F" && e != ".F."),
1004 _ => Some(true),
1005 })
1006 .unwrap_or(true);
1007
1008 let mut points = if let Some(loop_id) = loop_attr.as_entity_ref() {
1010 match self.extract_loop_points_fast(loop_id, decoder) {
1011 Some(p) => p,
1012 None => continue,
1013 }
1014 } else {
1015 continue
1016 };
1017
1018 if !orientation {
1019 points.reverse();
1020 }
1021
1022 let is_outer = match bound.ifc_type {
1023 IfcType::IfcFaceOuterBound => true,
1024 IfcType::IfcFaceBound => false,
1025 _ => bound.ifc_type.as_str().contains("OUTER"),
1026 };
1027
1028 if is_outer || outer_bound_points.is_none() {
1029 if outer_bound_points.is_some() && is_outer {
1030 if let Some(prev_outer) = outer_bound_points.take() {
1031 hole_points.push(prev_outer);
1032 }
1033 }
1034 outer_bound_points = Some(points);
1035 } else {
1036 hole_points.push(points);
1037 }
1038 }
1039
1040 if let Some(outer_points) = outer_bound_points {
1041 face_data_list.push(FaceData {
1042 outer_points,
1043 hole_points,
1044 });
1045 }
1046 }
1047
1048 let face_results: Vec<FaceResult> = face_data_list
1051 .par_iter()
1052 .map(Self::triangulate_face)
1053 .collect();
1054
1055 let total_positions: usize = face_results.iter().map(|r| r.positions.len()).sum();
1058 let total_indices: usize = face_results.iter().map(|r| r.indices.len()).sum();
1059
1060 let mut positions = Vec::with_capacity(total_positions);
1061 let mut indices = Vec::with_capacity(total_indices);
1062
1063 for result in face_results {
1064 let base_idx = (positions.len() / 3) as u32;
1065 positions.extend(result.positions);
1066
1067 for idx in result.indices {
1069 indices.push(base_idx + idx);
1070 }
1071 }
1072
1073 Ok(Mesh {
1074 positions,
1075 normals: Vec::new(),
1076 indices,
1077 })
1078 }
1079
1080 fn supported_types(&self) -> Vec<IfcType> {
1081 vec![IfcType::IfcFacetedBrep]
1082 }
1083}
1084
1085impl Default for FacetedBrepProcessor {
1086 fn default() -> Self {
1087 Self::new()
1088 }
1089}
1090
1091pub struct BooleanClippingProcessor {
1095 schema: IfcSchema,
1096}
1097
1098impl BooleanClippingProcessor {
1099 pub fn new() -> Self {
1100 Self {
1101 schema: IfcSchema::new(),
1102 }
1103 }
1104
1105 fn process_operand(
1107 &self,
1108 operand: &DecodedEntity,
1109 decoder: &mut EntityDecoder,
1110 ) -> Result<Mesh> {
1111 match operand.ifc_type {
1112 IfcType::IfcExtrudedAreaSolid => {
1113 let processor = ExtrudedAreaSolidProcessor::new(self.schema.clone());
1114 processor.process(operand, decoder, &self.schema)
1115 }
1116 IfcType::IfcFacetedBrep => {
1117 let processor = FacetedBrepProcessor::new();
1118 processor.process(operand, decoder, &self.schema)
1119 }
1120 IfcType::IfcTriangulatedFaceSet => {
1121 let processor = TriangulatedFaceSetProcessor::new();
1122 processor.process(operand, decoder, &self.schema)
1123 }
1124 IfcType::IfcSweptDiskSolid => {
1125 let processor = SweptDiskSolidProcessor::new(self.schema.clone());
1126 processor.process(operand, decoder, &self.schema)
1127 }
1128 IfcType::IfcRevolvedAreaSolid => {
1129 let processor = RevolvedAreaSolidProcessor::new(self.schema.clone());
1130 processor.process(operand, decoder, &self.schema)
1131 }
1132 IfcType::IfcBooleanResult | IfcType::IfcBooleanClippingResult => {
1133 self.process(operand, decoder, &self.schema)
1135 }
1136 _ => Ok(Mesh::new()),
1137 }
1138 }
1139
1140 fn parse_half_space_solid(
1143 &self,
1144 half_space: &DecodedEntity,
1145 decoder: &mut EntityDecoder,
1146 ) -> Result<(Point3<f64>, Vector3<f64>, bool)> {
1147 let surface_attr = half_space.get(0)
1152 .ok_or_else(|| Error::geometry("HalfSpaceSolid missing BaseSurface".to_string()))?;
1153
1154 let surface = decoder.resolve_ref(surface_attr)?
1155 .ok_or_else(|| Error::geometry("Failed to resolve BaseSurface".to_string()))?;
1156
1157 let agreement = half_space.get(1)
1159 .and_then(|v| match v {
1160 ifc_lite_core::AttributeValue::Enum(e) => Some(e != "F" && e != ".F."),
1162 _ => Some(true),
1163 })
1164 .unwrap_or(true);
1165
1166 if surface.ifc_type != IfcType::IfcPlane {
1168 return Err(Error::geometry(format!(
1169 "Expected IfcPlane for HalfSpaceSolid, got {}",
1170 surface.ifc_type
1171 )));
1172 }
1173
1174 let position_attr = surface.get(0)
1176 .ok_or_else(|| Error::geometry("IfcPlane missing Position".to_string()))?;
1177
1178 let position = decoder.resolve_ref(position_attr)?
1179 .ok_or_else(|| Error::geometry("Failed to resolve Plane position".to_string()))?;
1180
1181 let location = {
1184 let loc_attr = position.get(0)
1185 .ok_or_else(|| Error::geometry("Axis2Placement3D missing Location".to_string()))?;
1186 let loc = decoder.resolve_ref(loc_attr)?
1187 .ok_or_else(|| Error::geometry("Failed to resolve plane location".to_string()))?;
1188 let coords = loc.get(0).and_then(|v| v.as_list())
1189 .ok_or_else(|| Error::geometry("Location missing coordinates".to_string()))?;
1190 Point3::new(
1191 coords.get(0).and_then(|v| v.as_float()).unwrap_or(0.0),
1192 coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0),
1193 coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0),
1194 )
1195 };
1196
1197 let normal = {
1198 if let Some(axis_attr) = position.get(1) {
1199 if !axis_attr.is_null() {
1200 let axis = decoder.resolve_ref(axis_attr)?
1201 .ok_or_else(|| Error::geometry("Failed to resolve plane axis".to_string()))?;
1202 let coords = axis.get(0).and_then(|v| v.as_list())
1203 .ok_or_else(|| Error::geometry("Axis missing direction ratios".to_string()))?;
1204 Vector3::new(
1205 coords.get(0).and_then(|v| v.as_float()).unwrap_or(0.0),
1206 coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0),
1207 coords.get(2).and_then(|v| v.as_float()).unwrap_or(1.0),
1208 ).normalize()
1209 } else {
1210 Vector3::new(0.0, 0.0, 1.0)
1211 }
1212 } else {
1213 Vector3::new(0.0, 0.0, 1.0)
1214 }
1215 };
1216
1217 Ok((location, normal, agreement))
1218 }
1219
1220 fn clip_mesh_with_half_space(
1222 &self,
1223 mesh: &Mesh,
1224 plane_point: Point3<f64>,
1225 plane_normal: Vector3<f64>,
1226 agreement: bool,
1227 ) -> Result<Mesh> {
1228 use crate::csg::{ClippingProcessor, Plane};
1229
1230 let clip_normal = if agreement {
1235 -plane_normal } else {
1237 plane_normal };
1239
1240 let plane = Plane::new(plane_point, clip_normal);
1241 let processor = ClippingProcessor::new();
1242 processor.clip_mesh(mesh, &plane)
1243 }
1244}
1245
1246impl GeometryProcessor for BooleanClippingProcessor {
1247 fn process(
1248 &self,
1249 entity: &DecodedEntity,
1250 decoder: &mut EntityDecoder,
1251 _schema: &IfcSchema,
1252 ) -> Result<Mesh> {
1253 let operator = entity.get(0)
1260 .and_then(|v| match v {
1261 ifc_lite_core::AttributeValue::Enum(e) => Some(e.as_str()),
1262 _ => None,
1263 })
1264 .unwrap_or(".DIFFERENCE.");
1265
1266 let first_operand_attr = entity.get(1)
1268 .ok_or_else(|| Error::geometry("BooleanResult missing FirstOperand".to_string()))?;
1269
1270 let first_operand = decoder.resolve_ref(first_operand_attr)?
1271 .ok_or_else(|| Error::geometry("Failed to resolve FirstOperand".to_string()))?;
1272
1273 let mut mesh = self.process_operand(&first_operand, decoder)?;
1275
1276 if mesh.is_empty() {
1277 return Ok(mesh);
1278 }
1279
1280 let second_operand_attr = entity.get(2)
1282 .ok_or_else(|| Error::geometry("BooleanResult missing SecondOperand".to_string()))?;
1283
1284 let second_operand = decoder.resolve_ref(second_operand_attr)?
1285 .ok_or_else(|| Error::geometry("Failed to resolve SecondOperand".to_string()))?;
1286
1287 if operator == ".DIFFERENCE." {
1289 if second_operand.ifc_type == IfcType::IfcHalfSpaceSolid
1291 || second_operand.ifc_type == IfcType::IfcPolygonalBoundedHalfSpace {
1292 let (plane_point, plane_normal, agreement) =
1293 self.parse_half_space_solid(&second_operand, decoder)?;
1294 return self.clip_mesh_with_half_space(&mesh, plane_point, plane_normal, agreement);
1295 }
1296
1297 #[cfg(debug_assertions)]
1300 eprintln!("[WARN] CSG operation {} not fully supported, returning first operand only", operator);
1301 }
1302
1303 #[cfg(debug_assertions)]
1306 if operator != ".DIFFERENCE." {
1307 eprintln!("[WARN] CSG operation {} not fully supported, returning first operand only", operator);
1308 }
1309 Ok(mesh)
1310 }
1311
1312 fn supported_types(&self) -> Vec<IfcType> {
1313 vec![
1314 IfcType::IfcBooleanResult,
1315 IfcType::IfcBooleanClippingResult,
1316 ]
1317 }
1318}
1319
1320impl Default for BooleanClippingProcessor {
1321 fn default() -> Self {
1322 Self::new()
1323 }
1324}
1325
1326pub struct MappedItemProcessor;
1329
1330impl MappedItemProcessor {
1331 pub fn new() -> Self {
1332 Self
1333 }
1334}
1335
1336impl GeometryProcessor for MappedItemProcessor {
1337 fn process(
1338 &self,
1339 entity: &DecodedEntity,
1340 decoder: &mut EntityDecoder,
1341 schema: &IfcSchema,
1342 ) -> Result<Mesh> {
1343 let source_attr = entity
1349 .get(0)
1350 .ok_or_else(|| Error::geometry("MappedItem missing MappingSource".to_string()))?;
1351
1352 let source_entity = decoder
1353 .resolve_ref(source_attr)?
1354 .ok_or_else(|| Error::geometry("Failed to resolve MappingSource".to_string()))?;
1355
1356 let mapped_rep_attr = source_entity
1361 .get(1)
1362 .ok_or_else(|| {
1363 Error::geometry("RepresentationMap missing MappedRepresentation".to_string())
1364 })?;
1365
1366 let mapped_rep = decoder
1367 .resolve_ref(mapped_rep_attr)?
1368 .ok_or_else(|| Error::geometry("Failed to resolve MappedRepresentation".to_string()))?;
1369
1370 let items_attr = mapped_rep
1372 .get(3)
1373 .ok_or_else(|| Error::geometry("Representation missing Items".to_string()))?;
1374
1375 let items = decoder.resolve_ref_list(items_attr)?;
1376
1377 let mut mesh = Mesh::new();
1379 for item in items {
1380 let item_mesh = match item.ifc_type {
1381 IfcType::IfcExtrudedAreaSolid => {
1382 let processor = ExtrudedAreaSolidProcessor::new(schema.clone());
1383 processor.process(&item, decoder, schema)?
1384 }
1385 IfcType::IfcTriangulatedFaceSet => {
1386 let processor = TriangulatedFaceSetProcessor::new();
1387 processor.process(&item, decoder, schema)?
1388 }
1389 IfcType::IfcFacetedBrep => {
1390 let processor = FacetedBrepProcessor::new();
1391 processor.process(&item, decoder, schema)?
1392 }
1393 IfcType::IfcSweptDiskSolid => {
1394 let processor = SweptDiskSolidProcessor::new(schema.clone());
1395 processor.process(&item, decoder, schema)?
1396 }
1397 IfcType::IfcBooleanClippingResult | IfcType::IfcBooleanResult => {
1398 let processor = BooleanClippingProcessor::new();
1399 processor.process(&item, decoder, schema)?
1400 }
1401 IfcType::IfcRevolvedAreaSolid => {
1402 let processor = RevolvedAreaSolidProcessor::new(schema.clone());
1403 processor.process(&item, decoder, schema)?
1404 }
1405 _ => continue, };
1407 mesh.merge(&item_mesh);
1408 }
1409
1410 Ok(mesh)
1415 }
1416
1417 fn supported_types(&self) -> Vec<IfcType> {
1418 vec![IfcType::IfcMappedItem]
1419 }
1420}
1421
1422impl Default for MappedItemProcessor {
1423 fn default() -> Self {
1424 Self::new()
1425 }
1426}
1427
1428pub struct SweptDiskSolidProcessor {
1431 profile_processor: ProfileProcessor,
1432}
1433
1434impl SweptDiskSolidProcessor {
1435 pub fn new(schema: IfcSchema) -> Self {
1436 Self {
1437 profile_processor: ProfileProcessor::new(schema),
1438 }
1439 }
1440}
1441
1442impl GeometryProcessor for SweptDiskSolidProcessor {
1443 fn process(
1444 &self,
1445 entity: &DecodedEntity,
1446 decoder: &mut EntityDecoder,
1447 _schema: &IfcSchema,
1448 ) -> Result<Mesh> {
1449 let directrix_attr = entity
1457 .get(0)
1458 .ok_or_else(|| Error::geometry("SweptDiskSolid missing Directrix".to_string()))?;
1459
1460 let radius = entity
1461 .get_float(1)
1462 .ok_or_else(|| Error::geometry("SweptDiskSolid missing Radius".to_string()))?;
1463
1464 let inner_radius = entity.get_float(2);
1466
1467 let directrix = decoder
1469 .resolve_ref(directrix_attr)?
1470 .ok_or_else(|| Error::geometry("Failed to resolve Directrix".to_string()))?;
1471
1472 let curve_points = self.profile_processor.get_curve_points(&directrix, decoder)?;
1474
1475 if curve_points.len() < 2 {
1476 return Ok(Mesh::new()); }
1478
1479 let segments = 12; let mut positions = Vec::new();
1482 let mut indices = Vec::new();
1483
1484 for i in 0..curve_points.len() {
1486 let p = curve_points[i];
1487
1488 let tangent = if i == 0 {
1490 (curve_points[1] - curve_points[0]).normalize()
1491 } else if i == curve_points.len() - 1 {
1492 (curve_points[i] - curve_points[i - 1]).normalize()
1493 } else {
1494 ((curve_points[i + 1] - curve_points[i - 1]) / 2.0).normalize()
1495 };
1496
1497 let up = if tangent.x.abs() < 0.9 {
1500 Vector3::new(1.0, 0.0, 0.0)
1501 } else {
1502 Vector3::new(0.0, 1.0, 0.0)
1503 };
1504
1505 let perp1 = tangent.cross(&up).normalize();
1506 let perp2 = tangent.cross(&perp1).normalize();
1507
1508 for j in 0..segments {
1510 let angle = 2.0 * std::f64::consts::PI * j as f64 / segments as f64;
1511 let offset = perp1 * (radius * angle.cos()) + perp2 * (radius * angle.sin());
1512 let vertex = p + offset;
1513
1514 positions.push(vertex.x as f32);
1515 positions.push(vertex.y as f32);
1516 positions.push(vertex.z as f32);
1517 }
1518
1519 if i < curve_points.len() - 1 {
1521 let base = (i * segments) as u32;
1522 let next_base = ((i + 1) * segments) as u32;
1523
1524 for j in 0..segments {
1525 let j_next = (j + 1) % segments;
1526
1527 indices.push(base + j as u32);
1529 indices.push(next_base + j as u32);
1530 indices.push(next_base + j_next as u32);
1531
1532 indices.push(base + j as u32);
1533 indices.push(next_base + j_next as u32);
1534 indices.push(base + j_next as u32);
1535 }
1536 }
1537 }
1538
1539 let center_idx = (positions.len() / 3) as u32;
1542 let start = curve_points[0];
1543 positions.push(start.x as f32);
1544 positions.push(start.y as f32);
1545 positions.push(start.z as f32);
1546
1547 for j in 0..segments {
1548 let j_next = (j + 1) % segments;
1549 indices.push(center_idx);
1550 indices.push(j_next as u32);
1551 indices.push(j as u32);
1552 }
1553
1554 let end_center_idx = (positions.len() / 3) as u32;
1556 let end_base = ((curve_points.len() - 1) * segments) as u32;
1557 let end = curve_points[curve_points.len() - 1];
1558 positions.push(end.x as f32);
1559 positions.push(end.y as f32);
1560 positions.push(end.z as f32);
1561
1562 for j in 0..segments {
1563 let j_next = (j + 1) % segments;
1564 indices.push(end_center_idx);
1565 indices.push(end_base + j as u32);
1566 indices.push(end_base + j_next as u32);
1567 }
1568
1569 Ok(Mesh {
1570 positions,
1571 normals: Vec::new(),
1572 indices,
1573 })
1574 }
1575
1576 fn supported_types(&self) -> Vec<IfcType> {
1577 vec![IfcType::IfcSweptDiskSolid]
1578 }
1579}
1580
1581impl Default for SweptDiskSolidProcessor {
1582 fn default() -> Self {
1583 Self::new(IfcSchema::new())
1584 }
1585}
1586
1587pub struct RevolvedAreaSolidProcessor {
1590 profile_processor: ProfileProcessor,
1591}
1592
1593impl RevolvedAreaSolidProcessor {
1594 pub fn new(schema: IfcSchema) -> Self {
1595 Self {
1596 profile_processor: ProfileProcessor::new(schema),
1597 }
1598 }
1599}
1600
1601impl GeometryProcessor for RevolvedAreaSolidProcessor {
1602 fn process(
1603 &self,
1604 entity: &DecodedEntity,
1605 decoder: &mut EntityDecoder,
1606 _schema: &IfcSchema,
1607 ) -> Result<Mesh> {
1608 let profile_attr = entity
1615 .get(0)
1616 .ok_or_else(|| Error::geometry("RevolvedAreaSolid missing SweptArea".to_string()))?;
1617
1618 let profile = decoder
1619 .resolve_ref(profile_attr)?
1620 .ok_or_else(|| Error::geometry("Failed to resolve SweptArea".to_string()))?;
1621
1622 let axis_attr = entity
1624 .get(2)
1625 .ok_or_else(|| Error::geometry("RevolvedAreaSolid missing Axis".to_string()))?;
1626
1627 let axis_placement = decoder
1628 .resolve_ref(axis_attr)?
1629 .ok_or_else(|| Error::geometry("Failed to resolve Axis".to_string()))?;
1630
1631 let angle = entity
1633 .get_float(3)
1634 .ok_or_else(|| Error::geometry("RevolvedAreaSolid missing Angle".to_string()))?;
1635
1636 let profile_2d = self.profile_processor.process(&profile, decoder)?;
1638 if profile_2d.outer.is_empty() {
1639 return Ok(Mesh::new());
1640 }
1641
1642 let axis_location = {
1645 let loc_attr = axis_placement.get(0).ok_or_else(|| {
1646 Error::geometry("Axis1Placement missing Location".to_string())
1647 })?;
1648 let loc = decoder.resolve_ref(loc_attr)?.ok_or_else(|| {
1649 Error::geometry("Failed to resolve axis location".to_string())
1650 })?;
1651 let coords = loc.get(0).and_then(|v| v.as_list()).ok_or_else(|| {
1652 Error::geometry("Axis location missing coordinates".to_string())
1653 })?;
1654 Point3::new(
1655 coords.get(0).and_then(|v| v.as_float()).unwrap_or(0.0),
1656 coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0),
1657 coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0),
1658 )
1659 };
1660
1661 let axis_direction = {
1662 if let Some(dir_attr) = axis_placement.get(1) {
1663 if !dir_attr.is_null() {
1664 let dir = decoder.resolve_ref(dir_attr)?.ok_or_else(|| {
1665 Error::geometry("Failed to resolve axis direction".to_string())
1666 })?;
1667 let coords = dir.get(0).and_then(|v| v.as_list()).ok_or_else(|| {
1668 Error::geometry("Axis direction missing coordinates".to_string())
1669 })?;
1670 Vector3::new(
1671 coords.get(0).and_then(|v| v.as_float()).unwrap_or(0.0),
1672 coords.get(1).and_then(|v| v.as_float()).unwrap_or(1.0),
1673 coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0),
1674 ).normalize()
1675 } else {
1676 Vector3::new(0.0, 1.0, 0.0) }
1678 } else {
1679 Vector3::new(0.0, 1.0, 0.0) }
1681 };
1682
1683 let full_circle = angle.abs() >= std::f64::consts::PI * 1.99;
1686 let segments = if full_circle {
1687 24 } else {
1689 ((angle.abs() / std::f64::consts::PI * 12.0).ceil() as usize).max(4)
1690 };
1691
1692 let profile_points = &profile_2d.outer;
1693 let num_profile_points = profile_points.len();
1694
1695 let mut positions = Vec::new();
1696 let mut indices = Vec::new();
1697
1698 for i in 0..=segments {
1700 let t = if full_circle && i == segments {
1701 0.0 } else {
1703 angle * i as f64 / segments as f64
1704 };
1705
1706 let cos_t = t.cos();
1708 let sin_t = t.sin();
1709 let (ax, ay, az) = (axis_direction.x, axis_direction.y, axis_direction.z);
1710
1711 let k_matrix = |v: Vector3<f64>| -> Vector3<f64> {
1713 Vector3::new(
1714 ay * v.z - az * v.y,
1715 az * v.x - ax * v.z,
1716 ax * v.y - ay * v.x,
1717 )
1718 };
1719
1720 for (j, p2d) in profile_points.iter().enumerate() {
1722 let radius = p2d.x;
1725 let height = p2d.y;
1726
1727 let v = Vector3::new(radius, 0.0, 0.0);
1729
1730 let k_cross_v = k_matrix(v);
1732 let k_dot_v = ax * v.x + ay * v.y + az * v.z;
1733
1734 let v_rot = v * cos_t + k_cross_v * sin_t + axis_direction * k_dot_v * (1.0 - cos_t);
1735
1736 let pos = axis_location + axis_direction * height + v_rot;
1738
1739 positions.push(pos.x as f32);
1740 positions.push(pos.y as f32);
1741 positions.push(pos.z as f32);
1742
1743 if i < segments && j < num_profile_points - 1 {
1745 let current = (i * num_profile_points + j) as u32;
1746 let next_seg = ((i + 1) * num_profile_points + j) as u32;
1747 let current_next = current + 1;
1748 let next_seg_next = next_seg + 1;
1749
1750 indices.push(current);
1752 indices.push(next_seg);
1753 indices.push(next_seg_next);
1754
1755 indices.push(current);
1756 indices.push(next_seg_next);
1757 indices.push(current_next);
1758 }
1759 }
1760 }
1761
1762 if !full_circle {
1764 let start_center_idx = (positions.len() / 3) as u32;
1766 let start_center = axis_location + axis_direction * (profile_points.iter().map(|p| p.y).sum::<f64>() / profile_points.len() as f64);
1767 positions.push(start_center.x as f32);
1768 positions.push(start_center.y as f32);
1769 positions.push(start_center.z as f32);
1770
1771 for j in 0..num_profile_points - 1 {
1772 indices.push(start_center_idx);
1773 indices.push(j as u32 + 1);
1774 indices.push(j as u32);
1775 }
1776
1777 let end_center_idx = (positions.len() / 3) as u32;
1779 let end_base = (segments * num_profile_points) as u32;
1780 positions.push(start_center.x as f32);
1781 positions.push(start_center.y as f32);
1782 positions.push(start_center.z as f32);
1783
1784 for j in 0..num_profile_points - 1 {
1785 indices.push(end_center_idx);
1786 indices.push(end_base + j as u32);
1787 indices.push(end_base + j as u32 + 1);
1788 }
1789 }
1790
1791 Ok(Mesh {
1792 positions,
1793 normals: Vec::new(),
1794 indices,
1795 })
1796 }
1797
1798 fn supported_types(&self) -> Vec<IfcType> {
1799 vec![IfcType::IfcRevolvedAreaSolid]
1800 }
1801}
1802
1803impl Default for RevolvedAreaSolidProcessor {
1804 fn default() -> Self {
1805 Self::new(IfcSchema::new())
1806 }
1807}
1808
1809pub struct AdvancedBrepProcessor;
1813
1814impl AdvancedBrepProcessor {
1815 pub fn new() -> Self {
1816 Self
1817 }
1818
1819 #[inline]
1821 fn bspline_basis(i: usize, p: usize, u: f64, knots: &[f64]) -> f64 {
1822 if p == 0 {
1823 if knots[i] <= u && u < knots[i + 1] {
1824 1.0
1825 } else {
1826 0.0
1827 }
1828 } else {
1829 let left = {
1830 let denom = knots[i + p] - knots[i];
1831 if denom.abs() < 1e-10 {
1832 0.0
1833 } else {
1834 (u - knots[i]) / denom * Self::bspline_basis(i, p - 1, u, knots)
1835 }
1836 };
1837 let right = {
1838 let denom = knots[i + p + 1] - knots[i + 1];
1839 if denom.abs() < 1e-10 {
1840 0.0
1841 } else {
1842 (knots[i + p + 1] - u) / denom * Self::bspline_basis(i + 1, p - 1, u, knots)
1843 }
1844 };
1845 left + right
1846 }
1847 }
1848
1849 fn evaluate_bspline_surface(
1851 u: f64,
1852 v: f64,
1853 u_degree: usize,
1854 v_degree: usize,
1855 control_points: &[Vec<Point3<f64>>],
1856 u_knots: &[f64],
1857 v_knots: &[f64],
1858 ) -> Point3<f64> {
1859 let n_u = control_points.len();
1860 let n_v = if n_u > 0 { control_points[0].len() } else { 0 };
1861
1862 let mut result = Point3::new(0.0, 0.0, 0.0);
1863
1864 for i in 0..n_u {
1865 let n_i = Self::bspline_basis(i, u_degree, u, u_knots);
1866 for j in 0..n_v {
1867 let n_j = Self::bspline_basis(j, v_degree, v, v_knots);
1868 let weight = n_i * n_j;
1869 if weight.abs() > 1e-10 {
1870 let cp = &control_points[i][j];
1871 result.x += weight * cp.x;
1872 result.y += weight * cp.y;
1873 result.z += weight * cp.z;
1874 }
1875 }
1876 }
1877
1878 result
1879 }
1880
1881 fn tessellate_bspline_surface(
1883 u_degree: usize,
1884 v_degree: usize,
1885 control_points: &[Vec<Point3<f64>>],
1886 u_knots: &[f64],
1887 v_knots: &[f64],
1888 u_segments: usize,
1889 v_segments: usize,
1890 ) -> (Vec<f32>, Vec<u32>) {
1891 let mut positions = Vec::new();
1892 let mut indices = Vec::new();
1893
1894 let u_min = u_knots[u_degree];
1896 let u_max = u_knots[u_knots.len() - u_degree - 1];
1897 let v_min = v_knots[v_degree];
1898 let v_max = v_knots[v_knots.len() - v_degree - 1];
1899
1900 for i in 0..=u_segments {
1902 let u = u_min + (u_max - u_min) * (i as f64 / u_segments as f64);
1903 let u = u.min(u_max - 1e-6).max(u_min);
1905
1906 for j in 0..=v_segments {
1907 let v = v_min + (v_max - v_min) * (j as f64 / v_segments as f64);
1908 let v = v.min(v_max - 1e-6).max(v_min);
1909
1910 let point = Self::evaluate_bspline_surface(
1911 u, v, u_degree, v_degree, control_points, u_knots, v_knots,
1912 );
1913
1914 positions.push(point.x as f32);
1915 positions.push(point.y as f32);
1916 positions.push(point.z as f32);
1917
1918 if i < u_segments && j < v_segments {
1920 let base = (i * (v_segments + 1) + j) as u32;
1921 let next_u = base + (v_segments + 1) as u32;
1922
1923 indices.push(base);
1925 indices.push(base + 1);
1926 indices.push(next_u + 1);
1927
1928 indices.push(base);
1929 indices.push(next_u + 1);
1930 indices.push(next_u);
1931 }
1932 }
1933 }
1934
1935 (positions, indices)
1936 }
1937
1938 fn parse_control_points(
1940 &self,
1941 bspline: &DecodedEntity,
1942 decoder: &mut EntityDecoder,
1943 ) -> Result<Vec<Vec<Point3<f64>>>> {
1944 let cp_list_attr = bspline.get(2).ok_or_else(|| {
1946 Error::geometry("BSplineSurface missing ControlPointsList".to_string())
1947 })?;
1948
1949 let rows = cp_list_attr.as_list().ok_or_else(|| {
1950 Error::geometry("Expected control point list".to_string())
1951 })?;
1952
1953 let mut result = Vec::with_capacity(rows.len());
1954
1955 for row in rows {
1956 let cols = row.as_list().ok_or_else(|| {
1957 Error::geometry("Expected control point row".to_string())
1958 })?;
1959
1960 let mut row_points = Vec::with_capacity(cols.len());
1961 for col in cols {
1962 if let Some(point_id) = col.as_entity_ref() {
1963 let point = decoder.decode_by_id(point_id)?;
1964 let coords = point.get(0).and_then(|v| v.as_list()).ok_or_else(|| {
1965 Error::geometry("CartesianPoint missing coordinates".to_string())
1966 })?;
1967
1968 let x = coords.get(0).and_then(|v| v.as_float()).unwrap_or(0.0);
1969 let y = coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
1970 let z = coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0);
1971
1972 row_points.push(Point3::new(x, y, z));
1973 }
1974 }
1975 result.push(row_points);
1976 }
1977
1978 Ok(result)
1979 }
1980
1981 fn expand_knots(knot_values: &[f64], multiplicities: &[i64]) -> Vec<f64> {
1983 let mut expanded = Vec::new();
1984 for (knot, &mult) in knot_values.iter().zip(multiplicities.iter()) {
1985 for _ in 0..mult {
1986 expanded.push(*knot);
1987 }
1988 }
1989 expanded
1990 }
1991
1992 fn parse_knot_vectors(
1994 &self,
1995 bspline: &DecodedEntity,
1996 ) -> Result<(Vec<f64>, Vec<f64>)> {
1997 let u_mult_attr = bspline.get(7).ok_or_else(|| {
2013 Error::geometry("BSplineSurface missing UMultiplicities".to_string())
2014 })?;
2015 let u_mults: Vec<i64> = u_mult_attr
2016 .as_list()
2017 .ok_or_else(|| Error::geometry("Expected U multiplicities list".to_string()))?
2018 .iter()
2019 .filter_map(|v| v.as_int())
2020 .collect();
2021
2022 let v_mult_attr = bspline.get(8).ok_or_else(|| {
2024 Error::geometry("BSplineSurface missing VMultiplicities".to_string())
2025 })?;
2026 let v_mults: Vec<i64> = v_mult_attr
2027 .as_list()
2028 .ok_or_else(|| Error::geometry("Expected V multiplicities list".to_string()))?
2029 .iter()
2030 .filter_map(|v| v.as_int())
2031 .collect();
2032
2033 let u_knots_attr = bspline.get(9).ok_or_else(|| {
2035 Error::geometry("BSplineSurface missing UKnots".to_string())
2036 })?;
2037 let u_knot_values: Vec<f64> = u_knots_attr
2038 .as_list()
2039 .ok_or_else(|| Error::geometry("Expected U knots list".to_string()))?
2040 .iter()
2041 .filter_map(|v| v.as_float())
2042 .collect();
2043
2044 let v_knots_attr = bspline.get(10).ok_or_else(|| {
2046 Error::geometry("BSplineSurface missing VKnots".to_string())
2047 })?;
2048 let v_knot_values: Vec<f64> = v_knots_attr
2049 .as_list()
2050 .ok_or_else(|| Error::geometry("Expected V knots list".to_string()))?
2051 .iter()
2052 .filter_map(|v| v.as_float())
2053 .collect();
2054
2055 let u_knots = Self::expand_knots(&u_knot_values, &u_mults);
2057 let v_knots = Self::expand_knots(&v_knot_values, &v_mults);
2058
2059 Ok((u_knots, v_knots))
2060 }
2061
2062 fn process_planar_face(
2064 &self,
2065 face: &DecodedEntity,
2066 decoder: &mut EntityDecoder,
2067 ) -> Result<(Vec<f32>, Vec<u32>)> {
2068 let bounds_attr = face.get(0).ok_or_else(|| {
2070 Error::geometry("AdvancedFace missing Bounds".to_string())
2071 })?;
2072
2073 let bounds = bounds_attr.as_list().ok_or_else(|| {
2074 Error::geometry("Expected bounds list".to_string())
2075 })?;
2076
2077 let mut positions = Vec::new();
2078 let mut indices = Vec::new();
2079
2080 for bound in bounds {
2081 if let Some(bound_id) = bound.as_entity_ref() {
2082 let bound_entity = decoder.decode_by_id(bound_id)?;
2083
2084 let loop_attr = bound_entity.get(0).ok_or_else(|| {
2086 Error::geometry("FaceBound missing Bound".to_string())
2087 })?;
2088
2089 let loop_entity = decoder.resolve_ref(loop_attr)?.ok_or_else(|| {
2090 Error::geometry("Failed to resolve loop".to_string())
2091 })?;
2092
2093 if loop_entity.ifc_type.as_str().eq_ignore_ascii_case("IFCEDGELOOP") {
2095 let edges_attr = loop_entity.get(0).ok_or_else(|| {
2096 Error::geometry("EdgeLoop missing EdgeList".to_string())
2097 })?;
2098
2099 let edges = edges_attr.as_list().ok_or_else(|| {
2100 Error::geometry("Expected edge list".to_string())
2101 })?;
2102
2103 let mut polygon_points = Vec::new();
2104
2105 for edge_ref in edges {
2106 if let Some(edge_id) = edge_ref.as_entity_ref() {
2107 let oriented_edge = decoder.decode_by_id(edge_id)?;
2108
2109 let start_attr = oriented_edge.get(0).ok_or_else(|| {
2112 Error::geometry("OrientedEdge missing EdgeStart".to_string())
2113 })?;
2114
2115 if let Some(vertex) = decoder.resolve_ref(start_attr)? {
2116 let point_attr = vertex.get(0).ok_or_else(|| {
2118 Error::geometry("VertexPoint missing geometry".to_string())
2119 })?;
2120
2121 if let Some(point) = decoder.resolve_ref(point_attr)? {
2122 let coords = point.get(0).and_then(|v| v.as_list()).ok_or_else(|| {
2123 Error::geometry("CartesianPoint missing coords".to_string())
2124 })?;
2125
2126 let x = coords.get(0).and_then(|v| v.as_float()).unwrap_or(0.0);
2127 let y = coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
2128 let z = coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0);
2129
2130 polygon_points.push(Point3::new(x, y, z));
2131 }
2132 }
2133 }
2134 }
2135
2136 if polygon_points.len() >= 3 {
2138 let base_idx = (positions.len() / 3) as u32;
2139
2140 for point in &polygon_points {
2141 positions.push(point.x as f32);
2142 positions.push(point.y as f32);
2143 positions.push(point.z as f32);
2144 }
2145
2146 for i in 1..polygon_points.len() - 1 {
2150 indices.push(base_idx);
2151 indices.push(base_idx + i as u32);
2152 indices.push(base_idx + i as u32 + 1);
2153 }
2154 }
2155 }
2156 }
2157 }
2158
2159 Ok((positions, indices))
2160 }
2161
2162 fn process_bspline_face(
2164 &self,
2165 bspline: &DecodedEntity,
2166 decoder: &mut EntityDecoder,
2167 ) -> Result<(Vec<f32>, Vec<u32>)> {
2168 let u_degree = bspline.get_float(0).unwrap_or(3.0) as usize;
2170 let v_degree = bspline.get_float(1).unwrap_or(1.0) as usize;
2171
2172 let control_points = self.parse_control_points(bspline, decoder)?;
2174
2175 let (u_knots, v_knots) = self.parse_knot_vectors(bspline)?;
2177
2178 let u_segments = (control_points.len() * 3).max(8).min(24);
2180 let v_segments = if !control_points.is_empty() {
2181 (control_points[0].len() * 3).max(4).min(24)
2182 } else {
2183 4
2184 };
2185
2186 let (positions, indices) = Self::tessellate_bspline_surface(
2188 u_degree,
2189 v_degree,
2190 &control_points,
2191 &u_knots,
2192 &v_knots,
2193 u_segments,
2194 v_segments,
2195 );
2196
2197 Ok((positions, indices))
2198 }
2199}
2200
2201impl GeometryProcessor for AdvancedBrepProcessor {
2202 fn process(
2203 &self,
2204 entity: &DecodedEntity,
2205 decoder: &mut EntityDecoder,
2206 _schema: &IfcSchema,
2207 ) -> Result<Mesh> {
2208 let shell_attr = entity.get(0).ok_or_else(|| {
2213 Error::geometry("AdvancedBrep missing Outer shell".to_string())
2214 })?;
2215
2216 let shell = decoder.resolve_ref(shell_attr)?.ok_or_else(|| {
2217 Error::geometry("Failed to resolve Outer shell".to_string())
2218 })?;
2219
2220 let faces_attr = shell.get(0).ok_or_else(|| {
2222 Error::geometry("ClosedShell missing CfsFaces".to_string())
2223 })?;
2224
2225 let faces = faces_attr.as_list().ok_or_else(|| {
2226 Error::geometry("Expected face list".to_string())
2227 })?;
2228
2229 let mut all_positions = Vec::new();
2230 let mut all_indices = Vec::new();
2231
2232 for face_ref in faces {
2233 if let Some(face_id) = face_ref.as_entity_ref() {
2234 let face = decoder.decode_by_id(face_id)?;
2235
2236 let surface_attr = face.get(1).ok_or_else(|| {
2242 Error::geometry("AdvancedFace missing FaceSurface".to_string())
2243 })?;
2244
2245 let surface = decoder.resolve_ref(surface_attr)?.ok_or_else(|| {
2246 Error::geometry("Failed to resolve FaceSurface".to_string())
2247 })?;
2248
2249 let surface_type = surface.ifc_type.as_str().to_uppercase();
2250 let (positions, indices) = if surface_type == "IFCPLANE" {
2251 self.process_planar_face(&face, decoder)?
2253 } else if surface_type == "IFCBSPLINESURFACEWITHKNOTS"
2254 || surface_type == "IFCRATIONALBSPLINESURFACEWITHKNOTS" {
2255 self.process_bspline_face(&surface, decoder)?
2257 } else {
2258 continue;
2260 };
2261
2262 let base_idx = (all_positions.len() / 3) as u32;
2264 all_positions.extend(positions);
2265 for idx in indices {
2266 all_indices.push(base_idx + idx);
2267 }
2268 }
2269 }
2270
2271 Ok(Mesh {
2272 positions: all_positions,
2273 normals: Vec::new(),
2274 indices: all_indices,
2275 })
2276 }
2277
2278 fn supported_types(&self) -> Vec<IfcType> {
2279 vec![
2280 IfcType::IfcAdvancedBrep,
2281 IfcType::IfcAdvancedBrepWithVoids,
2282 ]
2283 }
2284}
2285
2286impl Default for AdvancedBrepProcessor {
2287 fn default() -> Self {
2288 Self::new()
2289 }
2290}
2291
2292#[cfg(test)]
2293mod tests {
2294 use super::*;
2295
2296 #[test]
2297 fn test_advanced_brep_file() {
2298 use crate::router::GeometryRouter;
2299
2300 let content = std::fs::read_to_string(
2302 "../../tests/benchmark/models/ifcopenshell/advanced_brep.ifc"
2303 ).expect("Failed to read test file");
2304
2305 let entity_index = ifc_lite_core::build_entity_index(&content);
2306 let mut decoder = EntityDecoder::with_index(&content, entity_index);
2307 let router = GeometryRouter::new();
2308
2309 let element = decoder.decode_by_id(181).expect("Failed to decode element");
2311 assert_eq!(element.ifc_type, IfcType::IfcBuildingElementProxy);
2312
2313 let mesh = router.process_element(&element, &mut decoder)
2314 .expect("Failed to process advanced brep");
2315
2316 assert!(!mesh.is_empty(), "AdvancedBrep should produce geometry");
2318 assert!(mesh.positions.len() >= 3 * 100, "Should have significant geometry");
2319 assert!(mesh.indices.len() >= 3 * 100, "Should have many triangles");
2320 }
2321
2322 #[test]
2323 fn test_extruded_area_solid() {
2324 let content = r#"
2325#1=IFCRECTANGLEPROFILEDEF(.AREA.,$,$,100.0,200.0);
2326#2=IFCDIRECTION((0.0,0.0,1.0));
2327#3=IFCEXTRUDEDAREASOLID(#1,$,#2,300.0);
2328"#;
2329
2330 let mut decoder = EntityDecoder::new(content);
2331 let schema = IfcSchema::new();
2332 let processor = ExtrudedAreaSolidProcessor::new(schema.clone());
2333
2334 let entity = decoder.decode_by_id(3).unwrap();
2335 let mesh = processor.process(&entity, &mut decoder, &schema).unwrap();
2336
2337 assert!(!mesh.is_empty());
2338 assert!(mesh.positions.len() > 0);
2339 assert!(mesh.indices.len() > 0);
2340 }
2341
2342 #[test]
2343 fn test_triangulated_face_set() {
2344 let content = r#"
2345#1=IFCCARTESIANPOINTLIST3D(((0.0,0.0,0.0),(100.0,0.0,0.0),(50.0,100.0,0.0)));
2346#2=IFCTRIANGULATEDFACESET(#1,$,$,((1,2,3)),$);
2347"#;
2348
2349 let mut decoder = EntityDecoder::new(content);
2350 let schema = IfcSchema::new();
2351 let processor = TriangulatedFaceSetProcessor::new();
2352
2353 let entity = decoder.decode_by_id(2).unwrap();
2354 let mesh = processor.process(&entity, &mut decoder, &schema).unwrap();
2355
2356 assert_eq!(mesh.positions.len(), 9); assert_eq!(mesh.indices.len(), 3); }
2359
2360 #[test]
2361 fn test_boolean_result_with_half_space() {
2362 let content = r#"
2364#1=IFCRECTANGLEPROFILEDEF(.AREA.,$,$,100.0,200.0);
2365#2=IFCDIRECTION((0.0,0.0,1.0));
2366#3=IFCEXTRUDEDAREASOLID(#1,$,#2,300.0);
2367#4=IFCCARTESIANPOINT((0.0,0.0,150.0));
2368#5=IFCDIRECTION((0.0,0.0,1.0));
2369#6=IFCAXIS2PLACEMENT3D(#4,#5,$);
2370#7=IFCPLANE(#6);
2371#8=IFCHALFSPACESOLID(#7,.T.);
2372#9=IFCBOOLEANRESULT(.DIFFERENCE.,#3,#8);
2373"#;
2374
2375 let mut decoder = EntityDecoder::new(content);
2376 let schema = IfcSchema::new();
2377 let processor = BooleanClippingProcessor::new();
2378
2379 let bool_result = decoder.decode_by_id(9).unwrap();
2381 println!("BooleanResult type: {:?}", bool_result.ifc_type);
2382 assert_eq!(bool_result.ifc_type, IfcType::IfcBooleanResult);
2383
2384 let half_space = decoder.decode_by_id(8).unwrap();
2385 println!("HalfSpaceSolid type: {:?}", half_space.ifc_type);
2386 assert_eq!(half_space.ifc_type, IfcType::IfcHalfSpaceSolid);
2387
2388 let mesh = processor.process(&bool_result, &mut decoder, &schema).unwrap();
2390 println!("Mesh vertices: {}", mesh.positions.len() / 3);
2391 println!("Mesh triangles: {}", mesh.indices.len() / 3);
2392
2393 assert!(!mesh.is_empty(), "BooleanResult should produce geometry");
2395 assert!(mesh.positions.len() > 0);
2396 }
2397
2398 #[test]
2399 fn test_764_column_file() {
2400 use crate::router::GeometryRouter;
2401
2402 let content = std::fs::read_to_string(
2404 "../../tests/benchmark/models/ifcopenshell/764--column--no-materials-or-surface-styles-found--augmented.ifc"
2405 ).expect("Failed to read test file");
2406
2407 let entity_index = ifc_lite_core::build_entity_index(&content);
2408 let mut decoder = EntityDecoder::with_index(&content, entity_index);
2409 let router = GeometryRouter::new();
2410
2411 let column = decoder.decode_by_id(8930).expect("Failed to decode column");
2413 println!("Column type: {:?}", column.ifc_type);
2414 assert_eq!(column.ifc_type, IfcType::IfcColumn);
2415
2416 let rep_attr = column.get(6).expect("Column missing representation attribute");
2418 println!("Representation attr: {:?}", rep_attr);
2419
2420 match router.process_element(&column, &mut decoder) {
2422 Ok(mesh) => {
2423 println!("Mesh vertices: {}", mesh.positions.len() / 3);
2424 println!("Mesh triangles: {}", mesh.indices.len() / 3);
2425 assert!(!mesh.is_empty(), "Column should produce geometry");
2426 }
2427 Err(e) => {
2428 panic!("Failed to process column: {:?}", e);
2429 }
2430 }
2431 }
2432
2433 #[test]
2434 fn test_wall_with_opening_file() {
2435 use crate::router::GeometryRouter;
2436
2437 let content = std::fs::read_to_string(
2439 "../../tests/benchmark/models/buildingsmart/wall-with-opening-and-window.ifc"
2440 ).expect("Failed to read test file");
2441
2442 let entity_index = ifc_lite_core::build_entity_index(&content);
2443 let mut decoder = EntityDecoder::with_index(&content, entity_index);
2444 let router = GeometryRouter::new();
2445
2446 let wall = match decoder.decode_by_id(45) {
2448 Ok(w) => w,
2449 Err(e) => panic!("Failed to decode wall: {:?}", e),
2450 };
2451 println!("Wall type: {:?}", wall.ifc_type);
2452 assert_eq!(wall.ifc_type, IfcType::IfcWall);
2453
2454 let rep_attr = wall.get(6).expect("Wall missing representation attribute");
2456 println!("Representation attr: {:?}", rep_attr);
2457
2458 match router.process_element(&wall, &mut decoder) {
2460 Ok(mesh) => {
2461 println!("Wall mesh vertices: {}", mesh.positions.len() / 3);
2462 println!("Wall mesh triangles: {}", mesh.indices.len() / 3);
2463 assert!(!mesh.is_empty(), "Wall should produce geometry");
2464 }
2465 Err(e) => {
2466 panic!("Failed to process wall: {:?}", e);
2467 }
2468 }
2469
2470 let window = decoder.decode_by_id(102).expect("Failed to decode window");
2472 println!("Window type: {:?}", window.ifc_type);
2473 assert_eq!(window.ifc_type, IfcType::IfcWindow);
2474
2475 match router.process_element(&window, &mut decoder) {
2476 Ok(mesh) => {
2477 println!("Window mesh vertices: {}", mesh.positions.len() / 3);
2478 println!("Window mesh triangles: {}", mesh.indices.len() / 3);
2479 }
2480 Err(e) => {
2481 println!("Window error (might be expected): {:?}", e);
2482 }
2483 }
2484 }
2485}