1use crate::{
10 extrusion::{apply_transform, extrude_profile},
11 profiles::ProfileProcessor,
12 Error, Mesh, Point3, Result, Vector3,
13};
14use ifc_lite_core::{DecodedEntity, EntityDecoder, IfcSchema, IfcType};
15use nalgebra::Matrix4;
16
17use super::router::GeometryProcessor;
18
19#[inline]
27fn extract_coord_index_bytes(bytes: &[u8]) -> Option<&[u8]> {
28 let eq_pos = bytes.iter().position(|&b| b == b'=')?;
30 let open_paren = bytes[eq_pos..].iter().position(|&b| b == b'(')?;
31 let args_start = eq_pos + open_paren + 1;
32
33 let mut depth = 1;
35 let mut attr_count = 0;
36 let mut attr_start = args_start;
37 let mut i = args_start;
38 let mut in_string = false;
39
40 while i < bytes.len() && depth > 0 {
41 let b = bytes[i];
42
43 if b == b'\'' {
45 in_string = !in_string;
46 i += 1;
47 continue;
48 }
49 if in_string {
50 i += 1;
51 continue;
52 }
53
54 if b == b'/' && i + 1 < bytes.len() && bytes[i + 1] == b'*' {
56 i += 2;
57 while i + 1 < bytes.len() && !(bytes[i] == b'*' && bytes[i + 1] == b'/') {
58 i += 1;
59 }
60 i += 2;
61 continue;
62 }
63
64 match b {
65 b'(' => {
66 if depth == 1 && attr_count == 3 {
67 attr_start = i;
69 }
70 depth += 1;
71 }
72 b')' => {
73 depth -= 1;
74 if depth == 1 && attr_count == 3 {
75 let candidate = &bytes[attr_start..i + 1];
77 if validate_coord_index_structure(candidate) {
78 return Some(candidate);
79 }
80 return None;
82 }
83 }
84 b',' if depth == 1 => {
85 attr_count += 1;
86 }
87 b'$' if depth == 1 && attr_count == 3 => {
88 return None;
90 }
91 _ => {}
92 }
93 i += 1;
94 }
95
96 None
97}
98
99#[inline]
105fn validate_coord_index_structure(bytes: &[u8]) -> bool {
106 if bytes.is_empty() {
107 return false;
108 }
109
110 let first = bytes.first().copied();
112 let last = bytes.last().copied();
113 if first != Some(b'(') || last != Some(b')') {
114 return false;
115 }
116
117 let mut depth = 0;
119 for &b in bytes {
120 match b {
121 b'(' => depth += 1,
122 b')' => {
123 if depth == 0 {
124 return false; }
126 depth -= 1;
127 }
128 b'0'..=b'9' | b',' | b' ' | b'\t' | b'\n' | b'\r' | b'-' => {}
129 b'$' | b'\'' | b'"' | b'/' | b'*' | b'#' => {
130 return false;
132 }
133 _ => {
134 if b.is_ascii_alphabetic() {
136 return false;
137 }
138 }
139 }
140 }
141
142 depth == 0
144}
145
146pub struct ExtrudedAreaSolidProcessor {
149 profile_processor: ProfileProcessor,
150}
151
152impl ExtrudedAreaSolidProcessor {
153 pub fn new(schema: IfcSchema) -> Self {
155 Self {
156 profile_processor: ProfileProcessor::new(schema),
157 }
158 }
159}
160
161impl GeometryProcessor for ExtrudedAreaSolidProcessor {
162 fn process(
163 &self,
164 entity: &DecodedEntity,
165 decoder: &mut EntityDecoder,
166 _schema: &IfcSchema,
167 ) -> Result<Mesh> {
168 let profile_attr = entity
176 .get(0)
177 .ok_or_else(|| Error::geometry("ExtrudedAreaSolid missing SweptArea".to_string()))?;
178
179 let profile_entity = decoder
180 .resolve_ref(profile_attr)?
181 .ok_or_else(|| Error::geometry("Failed to resolve SweptArea".to_string()))?;
182
183 let profile = self.profile_processor.process(&profile_entity, decoder)?;
184
185 if profile.outer.is_empty() {
186 return Ok(Mesh::new());
187 }
188
189 let direction_attr = entity.get(2).ok_or_else(|| {
191 Error::geometry("ExtrudedAreaSolid missing ExtrudedDirection".to_string())
192 })?;
193
194 let direction_entity = decoder
195 .resolve_ref(direction_attr)?
196 .ok_or_else(|| Error::geometry("Failed to resolve ExtrudedDirection".to_string()))?;
197
198 if direction_entity.ifc_type != IfcType::IfcDirection {
199 return Err(Error::geometry(format!(
200 "Expected IfcDirection, got {}",
201 direction_entity.ifc_type
202 )));
203 }
204
205 let ratios_attr = direction_entity
207 .get(0)
208 .ok_or_else(|| Error::geometry("IfcDirection missing ratios".to_string()))?;
209
210 let ratios = ratios_attr
211 .as_list()
212 .ok_or_else(|| Error::geometry("Expected ratio list".to_string()))?;
213
214 use ifc_lite_core::AttributeValue;
215 let dir_x = ratios
216 .first()
217 .and_then(|v: &AttributeValue| v.as_float())
218 .unwrap_or(0.0);
219 let dir_y = ratios
220 .get(1)
221 .and_then(|v: &AttributeValue| v.as_float())
222 .unwrap_or(0.0);
223 let dir_z = ratios
224 .get(2)
225 .and_then(|v: &AttributeValue| v.as_float())
226 .unwrap_or(1.0);
227
228 let direction = Vector3::new(dir_x, dir_y, dir_z).normalize();
229
230 let depth = entity
232 .get_float(3)
233 .ok_or_else(|| Error::geometry("ExtrudedAreaSolid missing Depth".to_string()))?;
234
235 let transform = if direction.x.abs() < 0.001 && direction.y.abs() < 0.001 {
242 if direction.z < 0.0 {
250 Some(Matrix4::new_translation(&Vector3::new(0.0, 0.0, -depth)))
252 } else {
253 None
254 }
255 } else {
256 let new_z = direction.normalize();
258
259 let up = if new_z.z.abs() > 0.9 {
261 Vector3::new(0.0, 1.0, 0.0) } else {
263 Vector3::new(0.0, 0.0, 1.0) };
265
266 let new_x = up.cross(&new_z).normalize();
267 let new_y = new_z.cross(&new_x).normalize();
268
269 let mut transform_mat = Matrix4::identity();
270 transform_mat[(0, 0)] = new_x.x;
271 transform_mat[(1, 0)] = new_x.y;
272 transform_mat[(2, 0)] = new_x.z;
273 transform_mat[(0, 1)] = new_y.x;
274 transform_mat[(1, 1)] = new_y.y;
275 transform_mat[(2, 1)] = new_y.z;
276 transform_mat[(0, 2)] = new_z.x;
277 transform_mat[(1, 2)] = new_z.y;
278 transform_mat[(2, 2)] = new_z.z;
279
280 Some(transform_mat)
281 };
282
283 let mut mesh = extrude_profile(&profile, depth, transform)?;
285
286 if let Some(pos_attr) = entity.get(1) {
288 if !pos_attr.is_null() {
289 if let Some(pos_entity) = decoder.resolve_ref(pos_attr)? {
290 if pos_entity.ifc_type == IfcType::IfcAxis2Placement3D {
291 let pos_transform = self.parse_axis2_placement_3d(&pos_entity, decoder)?;
292 apply_transform(&mut mesh, &pos_transform);
293 }
294 }
295 }
296 }
297
298 Ok(mesh)
299 }
300
301 fn supported_types(&self) -> Vec<IfcType> {
302 vec![IfcType::IfcExtrudedAreaSolid]
303 }
304}
305
306impl ExtrudedAreaSolidProcessor {
307 #[inline]
309 fn parse_axis2_placement_3d(
310 &self,
311 placement: &DecodedEntity,
312 decoder: &mut EntityDecoder,
313 ) -> Result<Matrix4<f64>> {
314 let location = self.parse_cartesian_point(placement, decoder, 0)?;
316
317 let z_axis = if let Some(axis_attr) = placement.get(1) {
319 if !axis_attr.is_null() {
320 if let Some(axis_entity) = decoder.resolve_ref(axis_attr)? {
321 self.parse_direction(&axis_entity)?
322 } else {
323 Vector3::new(0.0, 0.0, 1.0)
324 }
325 } else {
326 Vector3::new(0.0, 0.0, 1.0)
327 }
328 } else {
329 Vector3::new(0.0, 0.0, 1.0)
330 };
331
332 let x_axis = if let Some(ref_dir_attr) = placement.get(2) {
333 if !ref_dir_attr.is_null() {
334 if let Some(ref_dir_entity) = decoder.resolve_ref(ref_dir_attr)? {
335 self.parse_direction(&ref_dir_entity)?
336 } else {
337 Vector3::new(1.0, 0.0, 0.0)
338 }
339 } else {
340 Vector3::new(1.0, 0.0, 0.0)
341 }
342 } else {
343 Vector3::new(1.0, 0.0, 0.0)
344 };
345
346 let z_axis_final = z_axis.normalize();
348 let x_axis_normalized = x_axis.normalize();
349
350 let dot_product = x_axis_normalized.dot(&z_axis_final);
352 let x_axis_orthogonal = x_axis_normalized - z_axis_final * dot_product;
353 let x_axis_final = if x_axis_orthogonal.norm() > 1e-6 {
354 x_axis_orthogonal.normalize()
355 } else {
356 if z_axis_final.z.abs() < 0.9 {
358 Vector3::new(0.0, 0.0, 1.0).cross(&z_axis_final).normalize()
359 } else {
360 Vector3::new(1.0, 0.0, 0.0).cross(&z_axis_final).normalize()
361 }
362 };
363
364 let y_axis = z_axis_final.cross(&x_axis_final).normalize();
366
367 let mut transform = Matrix4::identity();
370 transform[(0, 0)] = x_axis_final.x;
371 transform[(1, 0)] = x_axis_final.y;
372 transform[(2, 0)] = x_axis_final.z;
373 transform[(0, 1)] = y_axis.x;
374 transform[(1, 1)] = y_axis.y;
375 transform[(2, 1)] = y_axis.z;
376 transform[(0, 2)] = z_axis_final.x;
377 transform[(1, 2)] = z_axis_final.y;
378 transform[(2, 2)] = z_axis_final.z;
379 transform[(0, 3)] = location.x;
380 transform[(1, 3)] = location.y;
381 transform[(2, 3)] = location.z;
382
383 Ok(transform)
384 }
385
386 #[inline]
388 fn parse_cartesian_point(
389 &self,
390 parent: &DecodedEntity,
391 decoder: &mut EntityDecoder,
392 attr_index: usize,
393 ) -> Result<Point3<f64>> {
394 let point_attr = parent
395 .get(attr_index)
396 .ok_or_else(|| Error::geometry("Missing cartesian point".to_string()))?;
397
398 let point_entity = decoder
399 .resolve_ref(point_attr)?
400 .ok_or_else(|| Error::geometry("Failed to resolve cartesian point".to_string()))?;
401
402 if point_entity.ifc_type != IfcType::IfcCartesianPoint {
403 return Err(Error::geometry(format!(
404 "Expected IfcCartesianPoint, got {}",
405 point_entity.ifc_type
406 )));
407 }
408
409 let coords_attr = point_entity
411 .get(0)
412 .ok_or_else(|| Error::geometry("IfcCartesianPoint missing coordinates".to_string()))?;
413
414 let coords = coords_attr
415 .as_list()
416 .ok_or_else(|| Error::geometry("Expected coordinate list".to_string()))?;
417
418 let x = coords.first().and_then(|v| v.as_float()).unwrap_or(0.0);
419 let y = coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
420 let z = coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0);
421
422 Ok(Point3::new(x, y, z))
423 }
424
425 #[inline]
427 fn parse_direction(&self, direction_entity: &DecodedEntity) -> Result<Vector3<f64>> {
428 if direction_entity.ifc_type != IfcType::IfcDirection {
429 return Err(Error::geometry(format!(
430 "Expected IfcDirection, got {}",
431 direction_entity.ifc_type
432 )));
433 }
434
435 let ratios_attr = direction_entity
437 .get(0)
438 .ok_or_else(|| Error::geometry("IfcDirection missing ratios".to_string()))?;
439
440 let ratios = ratios_attr
441 .as_list()
442 .ok_or_else(|| Error::geometry("Expected ratio list".to_string()))?;
443
444 let x = ratios.first().and_then(|v| v.as_float()).unwrap_or(0.0);
445 let y = ratios.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
446 let z = ratios.get(2).and_then(|v| v.as_float()).unwrap_or(0.0);
447
448 Ok(Vector3::new(x, y, z))
449 }
450}
451
452pub struct TriangulatedFaceSetProcessor;
455
456impl TriangulatedFaceSetProcessor {
457 pub fn new() -> Self {
458 Self
459 }
460}
461
462impl GeometryProcessor for TriangulatedFaceSetProcessor {
463 #[inline]
464 fn process(
465 &self,
466 entity: &DecodedEntity,
467 decoder: &mut EntityDecoder,
468 _schema: &IfcSchema,
469 ) -> Result<Mesh> {
470 let coords_attr = entity.get(0).ok_or_else(|| {
478 Error::geometry("TriangulatedFaceSet missing Coordinates".to_string())
479 })?;
480
481 let coord_entity_id = coords_attr.as_entity_ref().ok_or_else(|| {
482 Error::geometry("Expected entity reference for Coordinates".to_string())
483 })?;
484
485 use ifc_lite_core::{extract_coordinate_list_from_entity, parse_indices_direct};
488
489 let positions = if let Some(raw_bytes) = decoder.get_raw_bytes(coord_entity_id) {
490 extract_coordinate_list_from_entity(raw_bytes).unwrap_or_default()
493 } else {
494 let coords_entity = decoder.decode_by_id(coord_entity_id)?;
496
497 let coord_list_attr = coords_entity.get(0).ok_or_else(|| {
498 Error::geometry("CartesianPointList3D missing CoordList".to_string())
499 })?;
500
501 let coord_list = coord_list_attr
502 .as_list()
503 .ok_or_else(|| Error::geometry("Expected coordinate list".to_string()))?;
504
505 use ifc_lite_core::AttributeValue;
506 AttributeValue::parse_coordinate_list_3d(coord_list)
507 };
508
509 let indices_attr = entity
511 .get(3)
512 .ok_or_else(|| Error::geometry("TriangulatedFaceSet missing CoordIndex".to_string()))?;
513
514 let indices = if let Some(raw_entity_bytes) = decoder.get_raw_bytes(entity.id) {
517 if let Some(coord_index_bytes) = extract_coord_index_bytes(raw_entity_bytes) {
520 parse_indices_direct(coord_index_bytes)
521 } else {
522 let face_list = indices_attr
524 .as_list()
525 .ok_or_else(|| Error::geometry("Expected face index list".to_string()))?;
526 use ifc_lite_core::AttributeValue;
527 AttributeValue::parse_index_list(face_list)
528 }
529 } else {
530 let face_list = indices_attr
531 .as_list()
532 .ok_or_else(|| Error::geometry("Expected face index list".to_string()))?;
533 use ifc_lite_core::AttributeValue;
534 AttributeValue::parse_index_list(face_list)
535 };
536
537 Ok(Mesh {
539 positions,
540 normals: Vec::new(),
541 indices,
542 })
543 }
544
545 fn supported_types(&self) -> Vec<IfcType> {
546 vec![IfcType::IfcTriangulatedFaceSet]
547 }
548}
549
550impl Default for TriangulatedFaceSetProcessor {
551 fn default() -> Self {
552 Self::new()
553 }
554}
555
556struct FaceData {
558 outer_points: Vec<Point3<f64>>,
559 hole_points: Vec<Vec<Point3<f64>>>,
560}
561
562struct FaceResult {
564 positions: Vec<f32>,
565 indices: Vec<u32>,
566}
567
568pub struct FacetedBrepProcessor;
573
574impl FacetedBrepProcessor {
575 pub fn new() -> Self {
576 Self
577 }
578
579 #[allow(dead_code)]
582 #[inline]
583 fn extract_loop_points(
584 &self,
585 loop_entity: &DecodedEntity,
586 decoder: &mut EntityDecoder,
587 ) -> Option<Vec<Point3<f64>>> {
588 let polygon_attr = loop_entity.get(0)?;
590
591 let point_refs = polygon_attr.as_list()?;
593
594 let mut polygon_points = Vec::with_capacity(point_refs.len());
596
597 for point_ref in point_refs {
598 let point_id = point_ref.as_entity_ref()?;
599
600 if let Some((x, y, z)) = decoder.get_cartesian_point_fast(point_id) {
602 polygon_points.push(Point3::new(x, y, z));
603 } else {
604 let point = decoder.decode_by_id(point_id).ok()?;
606 let coords_attr = point.get(0)?;
607 let coords = coords_attr.as_list()?;
608 use ifc_lite_core::AttributeValue;
609 let x = coords.first().and_then(|v: &AttributeValue| v.as_float())?;
610 let y = coords.get(1).and_then(|v: &AttributeValue| v.as_float())?;
611 let z = coords.get(2).and_then(|v: &AttributeValue| v.as_float())?;
612 polygon_points.push(Point3::new(x, y, z));
613 }
614 }
615
616 if polygon_points.len() >= 3 {
617 Some(polygon_points)
618 } else {
619 None
620 }
621 }
622
623 #[inline]
626 fn extract_loop_points_fast(
627 &self,
628 loop_entity_id: u32,
629 decoder: &mut EntityDecoder,
630 ) -> Option<Vec<Point3<f64>>> {
631 let point_ids = decoder.get_polyloop_point_ids_fast(loop_entity_id)?;
633
634 let mut polygon_points = Vec::with_capacity(point_ids.len());
636
637 for point_id in point_ids {
638 let (x, y, z) = decoder.get_cartesian_point_fast(point_id)?;
641 polygon_points.push(Point3::new(x, y, z));
642 }
643
644 if polygon_points.len() >= 3 {
645 Some(polygon_points)
646 } else {
647 None
648 }
649 }
650
651 #[inline]
654 fn triangulate_face(face: &FaceData) -> FaceResult {
655 let n = face.outer_points.len();
656
657 if n == 3 && face.hole_points.is_empty() {
659 let mut positions = Vec::with_capacity(9);
660 for point in &face.outer_points {
661 positions.push(point.x as f32);
662 positions.push(point.y as f32);
663 positions.push(point.z as f32);
664 }
665 return FaceResult {
666 positions,
667 indices: vec![0, 1, 2],
668 };
669 }
670
671 if n == 4 && face.hole_points.is_empty() {
673 let mut positions = Vec::with_capacity(12);
674 for point in &face.outer_points {
675 positions.push(point.x as f32);
676 positions.push(point.y as f32);
677 positions.push(point.z as f32);
678 }
679 return FaceResult {
680 positions,
681 indices: vec![0, 1, 2, 0, 2, 3],
682 };
683 }
684
685 if face.hole_points.is_empty() && n <= 8 {
687 let mut is_convex = true;
689 if n > 4 {
690 use crate::triangulation::calculate_polygon_normal;
691 let normal = calculate_polygon_normal(&face.outer_points);
692 let mut sign = 0i8;
693
694 for i in 0..n {
695 let p0 = &face.outer_points[i];
696 let p1 = &face.outer_points[(i + 1) % n];
697 let p2 = &face.outer_points[(i + 2) % n];
698
699 let v1 = p1 - p0;
700 let v2 = p2 - p1;
701 let cross = v1.cross(&v2);
702 let dot = cross.dot(&normal);
703
704 if dot.abs() > 1e-10 {
705 let current_sign = if dot > 0.0 { 1i8 } else { -1i8 };
706 if sign == 0 {
707 sign = current_sign;
708 } else if sign != current_sign {
709 is_convex = false;
710 break;
711 }
712 }
713 }
714 }
715
716 if is_convex {
717 let mut positions = Vec::with_capacity(n * 3);
718 for point in &face.outer_points {
719 positions.push(point.x as f32);
720 positions.push(point.y as f32);
721 positions.push(point.z as f32);
722 }
723 let mut indices = Vec::with_capacity((n - 2) * 3);
724 for i in 1..n - 1 {
725 indices.push(0);
726 indices.push(i as u32);
727 indices.push(i as u32 + 1);
728 }
729 return FaceResult { positions, indices };
730 }
731 }
732
733 use crate::triangulation::{
735 calculate_polygon_normal, project_to_2d, project_to_2d_with_basis,
736 triangulate_polygon_with_holes,
737 };
738
739 let mut positions = Vec::new();
740 let mut indices = Vec::new();
741
742 let normal = calculate_polygon_normal(&face.outer_points);
744
745 let (outer_2d, u_axis, v_axis, origin) = project_to_2d(&face.outer_points, &normal);
747
748 let holes_2d: Vec<Vec<nalgebra::Point2<f64>>> = face
750 .hole_points
751 .iter()
752 .map(|hole| project_to_2d_with_basis(hole, &u_axis, &v_axis, &origin))
753 .collect();
754
755 let tri_indices = match triangulate_polygon_with_holes(&outer_2d, &holes_2d) {
757 Ok(idx) => idx,
758 Err(_) => {
759 for point in &face.outer_points {
761 positions.push(point.x as f32);
762 positions.push(point.y as f32);
763 positions.push(point.z as f32);
764 }
765 for i in 1..face.outer_points.len() - 1 {
766 indices.push(0);
767 indices.push(i as u32);
768 indices.push(i as u32 + 1);
769 }
770 return FaceResult { positions, indices };
771 }
772 };
773
774 let mut all_points_3d: Vec<&Point3<f64>> = face.outer_points.iter().collect();
776 for hole in &face.hole_points {
777 all_points_3d.extend(hole.iter());
778 }
779
780 for point in &all_points_3d {
782 positions.push(point.x as f32);
783 positions.push(point.y as f32);
784 positions.push(point.z as f32);
785 }
786
787 for i in (0..tri_indices.len()).step_by(3) {
789 indices.push(tri_indices[i] as u32);
790 indices.push(tri_indices[i + 1] as u32);
791 indices.push(tri_indices[i + 2] as u32);
792 }
793
794 FaceResult { positions, indices }
795 }
796
797 pub fn process_batch(
801 &self,
802 brep_ids: &[u32],
803 decoder: &mut EntityDecoder,
804 ) -> Vec<(usize, Mesh)> {
805 use rayon::prelude::*;
806
807 let mut all_faces: Vec<(usize, FaceData)> = Vec::with_capacity(brep_ids.len() * 10);
810
811 for (brep_idx, &brep_id) in brep_ids.iter().enumerate() {
812 let brep_entity = match decoder.decode_by_id(brep_id) {
814 Ok(e) => e,
815 Err(_) => continue,
816 };
817
818 let shell_id = match brep_entity.get(0).and_then(|a| a.as_entity_ref()) {
820 Some(id) => id,
821 None => continue,
822 };
823
824 let face_ids = match decoder.get_entity_ref_list_fast(shell_id) {
826 Some(ids) => ids,
827 None => continue,
828 };
829
830 for face_id in face_ids {
832 let bound_ids = match decoder.get_entity_ref_list_fast(face_id) {
833 Some(ids) => ids,
834 None => continue,
835 };
836
837 let mut outer_bound_points: Option<Vec<Point3<f64>>> = None;
838 let mut hole_points: Vec<Vec<Point3<f64>>> = Vec::new();
839
840 for bound_id in bound_ids {
841 let bound = match decoder.decode_by_id(bound_id) {
842 Ok(b) => b,
843 Err(_) => continue,
844 };
845
846 let loop_attr = match bound.get(0) {
847 Some(attr) => attr,
848 None => continue,
849 };
850
851 let orientation = bound
852 .get(1)
853 .map(|v| match v {
854 ifc_lite_core::AttributeValue::Enum(e) => e != "F" && e != ".F.",
856 _ => 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((
893 brep_idx,
894 FaceData {
895 outer_points,
896 hole_points,
897 },
898 ));
899 }
900 }
901 }
902
903 let face_results: Vec<(usize, FaceResult)> = all_faces
905 .par_iter()
906 .map(|(brep_idx, face)| (*brep_idx, Self::triangulate_face(face)))
907 .collect();
908
909 let mut face_counts = vec![0usize; brep_ids.len()];
912 for (brep_idx, _) in &face_results {
913 face_counts[*brep_idx] += 1;
914 }
915
916 let mut mesh_builders: Vec<(Vec<f32>, Vec<u32>)> = face_counts
918 .iter()
919 .map(|&count| {
920 (
921 Vec::with_capacity(count * 100),
922 Vec::with_capacity(count * 50),
923 )
924 })
925 .collect();
926
927 for (brep_idx, result) in face_results {
929 let (positions, indices) = &mut mesh_builders[brep_idx];
930 let base_idx = (positions.len() / 3) as u32;
931 positions.extend(result.positions);
932 for idx in result.indices {
933 indices.push(base_idx + idx);
934 }
935 }
936
937 mesh_builders
939 .into_iter()
940 .enumerate()
941 .filter(|(_, (positions, _))| !positions.is_empty())
942 .map(|(brep_idx, (positions, indices))| {
943 (
944 brep_idx,
945 Mesh {
946 positions,
947 normals: Vec::new(),
948 indices,
949 },
950 )
951 })
952 .collect()
953 }
954}
955
956impl GeometryProcessor for FacetedBrepProcessor {
957 fn process(
958 &self,
959 entity: &DecodedEntity,
960 decoder: &mut EntityDecoder,
961 _schema: &IfcSchema,
962 ) -> Result<Mesh> {
963 use rayon::prelude::*;
964
965 let shell_attr = entity
970 .get(0)
971 .ok_or_else(|| Error::geometry("FacetedBrep missing Outer shell".to_string()))?;
972
973 let shell_id = shell_attr
974 .as_entity_ref()
975 .ok_or_else(|| Error::geometry("Expected entity ref for Outer shell".to_string()))?;
976
977 let face_ids = decoder
979 .get_entity_ref_list_fast(shell_id)
980 .ok_or_else(|| Error::geometry("Failed to get faces from ClosedShell".to_string()))?;
981
982 let mut face_data_list: Vec<FaceData> = Vec::with_capacity(face_ids.len());
984
985 for face_id in face_ids {
986 let bound_ids = match decoder.get_entity_ref_list_fast(face_id) {
988 Some(ids) => ids,
989 None => continue,
990 };
991
992 let mut outer_bound_points: Option<Vec<Point3<f64>>> = None;
994 let mut hole_points: Vec<Vec<Point3<f64>>> = Vec::new();
995
996 for bound_id in bound_ids {
997 let bound = match decoder.decode_by_id(bound_id) {
999 Ok(b) => b,
1000 Err(_) => continue,
1001 };
1002
1003 let loop_attr = match bound.get(0) {
1004 Some(attr) => attr,
1005 None => continue,
1006 };
1007
1008 let orientation = bound
1010 .get(1)
1011 .map(|v| match v {
1012 ifc_lite_core::AttributeValue::Enum(e) => e != "F" && e != ".F.",
1014 _ => true,
1015 })
1016 .unwrap_or(true);
1017
1018 let mut points = if let Some(loop_id) = loop_attr.as_entity_ref() {
1020 match self.extract_loop_points_fast(loop_id, decoder) {
1021 Some(p) => p,
1022 None => continue,
1023 }
1024 } else {
1025 continue;
1026 };
1027
1028 if !orientation {
1029 points.reverse();
1030 }
1031
1032 let is_outer = match bound.ifc_type {
1033 IfcType::IfcFaceOuterBound => true,
1034 IfcType::IfcFaceBound => false,
1035 _ => bound.ifc_type.as_str().contains("OUTER"),
1036 };
1037
1038 if is_outer || outer_bound_points.is_none() {
1039 if outer_bound_points.is_some() && is_outer {
1040 if let Some(prev_outer) = outer_bound_points.take() {
1041 hole_points.push(prev_outer);
1042 }
1043 }
1044 outer_bound_points = Some(points);
1045 } else {
1046 hole_points.push(points);
1047 }
1048 }
1049
1050 if let Some(outer_points) = outer_bound_points {
1051 face_data_list.push(FaceData {
1052 outer_points,
1053 hole_points,
1054 });
1055 }
1056 }
1057
1058 let face_results: Vec<FaceResult> = face_data_list
1061 .par_iter()
1062 .map(Self::triangulate_face)
1063 .collect();
1064
1065 let total_positions: usize = face_results.iter().map(|r| r.positions.len()).sum();
1068 let total_indices: usize = face_results.iter().map(|r| r.indices.len()).sum();
1069
1070 let mut positions = Vec::with_capacity(total_positions);
1071 let mut indices = Vec::with_capacity(total_indices);
1072
1073 for result in face_results {
1074 let base_idx = (positions.len() / 3) as u32;
1075 positions.extend(result.positions);
1076
1077 for idx in result.indices {
1079 indices.push(base_idx + idx);
1080 }
1081 }
1082
1083 Ok(Mesh {
1084 positions,
1085 normals: Vec::new(),
1086 indices,
1087 })
1088 }
1089
1090 fn supported_types(&self) -> Vec<IfcType> {
1091 vec![IfcType::IfcFacetedBrep]
1092 }
1093}
1094
1095impl Default for FacetedBrepProcessor {
1096 fn default() -> Self {
1097 Self::new()
1098 }
1099}
1100
1101pub struct BooleanClippingProcessor {
1105 schema: IfcSchema,
1106}
1107
1108impl BooleanClippingProcessor {
1109 pub fn new() -> Self {
1110 Self {
1111 schema: IfcSchema::new(),
1112 }
1113 }
1114
1115 fn process_operand(
1117 &self,
1118 operand: &DecodedEntity,
1119 decoder: &mut EntityDecoder,
1120 ) -> Result<Mesh> {
1121 match operand.ifc_type {
1122 IfcType::IfcExtrudedAreaSolid => {
1123 let processor = ExtrudedAreaSolidProcessor::new(self.schema.clone());
1124 processor.process(operand, decoder, &self.schema)
1125 }
1126 IfcType::IfcFacetedBrep => {
1127 let processor = FacetedBrepProcessor::new();
1128 processor.process(operand, decoder, &self.schema)
1129 }
1130 IfcType::IfcTriangulatedFaceSet => {
1131 let processor = TriangulatedFaceSetProcessor::new();
1132 processor.process(operand, decoder, &self.schema)
1133 }
1134 IfcType::IfcSweptDiskSolid => {
1135 let processor = SweptDiskSolidProcessor::new(self.schema.clone());
1136 processor.process(operand, decoder, &self.schema)
1137 }
1138 IfcType::IfcRevolvedAreaSolid => {
1139 let processor = RevolvedAreaSolidProcessor::new(self.schema.clone());
1140 processor.process(operand, decoder, &self.schema)
1141 }
1142 IfcType::IfcBooleanResult | IfcType::IfcBooleanClippingResult => {
1143 self.process(operand, decoder, &self.schema)
1145 }
1146 _ => Ok(Mesh::new()),
1147 }
1148 }
1149
1150 fn parse_half_space_solid(
1153 &self,
1154 half_space: &DecodedEntity,
1155 decoder: &mut EntityDecoder,
1156 ) -> Result<(Point3<f64>, Vector3<f64>, bool)> {
1157 let surface_attr = half_space
1162 .get(0)
1163 .ok_or_else(|| Error::geometry("HalfSpaceSolid missing BaseSurface".to_string()))?;
1164
1165 let surface = decoder
1166 .resolve_ref(surface_attr)?
1167 .ok_or_else(|| Error::geometry("Failed to resolve BaseSurface".to_string()))?;
1168
1169 let agreement = half_space
1171 .get(1)
1172 .map(|v| match v {
1173 ifc_lite_core::AttributeValue::Enum(e) => e != "F" && e != ".F.",
1175 _ => true,
1176 })
1177 .unwrap_or(true);
1178
1179 if surface.ifc_type != IfcType::IfcPlane {
1181 return Err(Error::geometry(format!(
1182 "Expected IfcPlane for HalfSpaceSolid, got {}",
1183 surface.ifc_type
1184 )));
1185 }
1186
1187 let position_attr = surface
1189 .get(0)
1190 .ok_or_else(|| Error::geometry("IfcPlane missing Position".to_string()))?;
1191
1192 let position = decoder
1193 .resolve_ref(position_attr)?
1194 .ok_or_else(|| Error::geometry("Failed to resolve Plane position".to_string()))?;
1195
1196 let location = {
1199 let loc_attr = position
1200 .get(0)
1201 .ok_or_else(|| Error::geometry("Axis2Placement3D missing Location".to_string()))?;
1202 let loc = decoder
1203 .resolve_ref(loc_attr)?
1204 .ok_or_else(|| Error::geometry("Failed to resolve plane location".to_string()))?;
1205 let coords = loc
1206 .get(0)
1207 .and_then(|v| v.as_list())
1208 .ok_or_else(|| Error::geometry("Location missing coordinates".to_string()))?;
1209 Point3::new(
1210 coords.first().and_then(|v| v.as_float()).unwrap_or(0.0),
1211 coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0),
1212 coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0),
1213 )
1214 };
1215
1216 let normal = {
1217 if let Some(axis_attr) = position.get(1) {
1218 if !axis_attr.is_null() {
1219 let axis = decoder.resolve_ref(axis_attr)?.ok_or_else(|| {
1220 Error::geometry("Failed to resolve plane axis".to_string())
1221 })?;
1222 let coords = axis.get(0).and_then(|v| v.as_list()).ok_or_else(|| {
1223 Error::geometry("Axis missing direction ratios".to_string())
1224 })?;
1225 Vector3::new(
1226 coords.first().and_then(|v| v.as_float()).unwrap_or(0.0),
1227 coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0),
1228 coords.get(2).and_then(|v| v.as_float()).unwrap_or(1.0),
1229 )
1230 .normalize()
1231 } else {
1232 Vector3::new(0.0, 0.0, 1.0)
1233 }
1234 } else {
1235 Vector3::new(0.0, 0.0, 1.0)
1236 }
1237 };
1238
1239 Ok((location, normal, agreement))
1240 }
1241
1242 fn clip_mesh_with_half_space(
1244 &self,
1245 mesh: &Mesh,
1246 plane_point: Point3<f64>,
1247 plane_normal: Vector3<f64>,
1248 agreement: bool,
1249 ) -> Result<Mesh> {
1250 use crate::csg::{ClippingProcessor, Plane};
1251
1252 let clip_normal = if agreement {
1257 -plane_normal } else {
1259 plane_normal };
1261
1262 let plane = Plane::new(plane_point, clip_normal);
1263 let processor = ClippingProcessor::new();
1264 processor.clip_mesh(mesh, &plane)
1265 }
1266}
1267
1268impl GeometryProcessor for BooleanClippingProcessor {
1269 fn process(
1270 &self,
1271 entity: &DecodedEntity,
1272 decoder: &mut EntityDecoder,
1273 _schema: &IfcSchema,
1274 ) -> Result<Mesh> {
1275 let operator = entity
1282 .get(0)
1283 .and_then(|v| match v {
1284 ifc_lite_core::AttributeValue::Enum(e) => Some(e.as_str()),
1285 _ => None,
1286 })
1287 .unwrap_or(".DIFFERENCE.");
1288
1289 let first_operand_attr = entity
1291 .get(1)
1292 .ok_or_else(|| Error::geometry("BooleanResult missing FirstOperand".to_string()))?;
1293
1294 let first_operand = decoder
1295 .resolve_ref(first_operand_attr)?
1296 .ok_or_else(|| Error::geometry("Failed to resolve FirstOperand".to_string()))?;
1297
1298 let mesh = self.process_operand(&first_operand, decoder)?;
1300
1301 if mesh.is_empty() {
1302 return Ok(mesh);
1303 }
1304
1305 let second_operand_attr = entity
1307 .get(2)
1308 .ok_or_else(|| Error::geometry("BooleanResult missing SecondOperand".to_string()))?;
1309
1310 let second_operand = decoder
1311 .resolve_ref(second_operand_attr)?
1312 .ok_or_else(|| Error::geometry("Failed to resolve SecondOperand".to_string()))?;
1313
1314 if operator == ".DIFFERENCE." {
1316 if second_operand.ifc_type == IfcType::IfcHalfSpaceSolid
1318 || second_operand.ifc_type == IfcType::IfcPolygonalBoundedHalfSpace
1319 {
1320 let (plane_point, plane_normal, agreement) =
1321 self.parse_half_space_solid(&second_operand, decoder)?;
1322 return self.clip_mesh_with_half_space(&mesh, plane_point, plane_normal, agreement);
1323 }
1324
1325 #[cfg(debug_assertions)]
1328 eprintln!(
1329 "[WARN] CSG operation {} not fully supported, returning first operand only",
1330 operator
1331 );
1332 }
1333
1334 #[cfg(debug_assertions)]
1337 if operator != ".DIFFERENCE." {
1338 eprintln!(
1339 "[WARN] CSG operation {} not fully supported, returning first operand only",
1340 operator
1341 );
1342 }
1343 Ok(mesh)
1344 }
1345
1346 fn supported_types(&self) -> Vec<IfcType> {
1347 vec![IfcType::IfcBooleanResult, IfcType::IfcBooleanClippingResult]
1348 }
1349}
1350
1351impl Default for BooleanClippingProcessor {
1352 fn default() -> Self {
1353 Self::new()
1354 }
1355}
1356
1357pub struct MappedItemProcessor;
1360
1361impl MappedItemProcessor {
1362 pub fn new() -> Self {
1363 Self
1364 }
1365}
1366
1367impl GeometryProcessor for MappedItemProcessor {
1368 fn process(
1369 &self,
1370 entity: &DecodedEntity,
1371 decoder: &mut EntityDecoder,
1372 schema: &IfcSchema,
1373 ) -> Result<Mesh> {
1374 let source_attr = entity
1380 .get(0)
1381 .ok_or_else(|| Error::geometry("MappedItem missing MappingSource".to_string()))?;
1382
1383 let source_entity = decoder
1384 .resolve_ref(source_attr)?
1385 .ok_or_else(|| Error::geometry("Failed to resolve MappingSource".to_string()))?;
1386
1387 let mapped_rep_attr = source_entity.get(1).ok_or_else(|| {
1392 Error::geometry("RepresentationMap missing MappedRepresentation".to_string())
1393 })?;
1394
1395 let mapped_rep = decoder
1396 .resolve_ref(mapped_rep_attr)?
1397 .ok_or_else(|| Error::geometry("Failed to resolve MappedRepresentation".to_string()))?;
1398
1399 let items_attr = mapped_rep
1401 .get(3)
1402 .ok_or_else(|| Error::geometry("Representation missing Items".to_string()))?;
1403
1404 let items = decoder.resolve_ref_list(items_attr)?;
1405
1406 let mut mesh = Mesh::new();
1408 for item in items {
1409 let item_mesh = match item.ifc_type {
1410 IfcType::IfcExtrudedAreaSolid => {
1411 let processor = ExtrudedAreaSolidProcessor::new(schema.clone());
1412 processor.process(&item, decoder, schema)?
1413 }
1414 IfcType::IfcTriangulatedFaceSet => {
1415 let processor = TriangulatedFaceSetProcessor::new();
1416 processor.process(&item, decoder, schema)?
1417 }
1418 IfcType::IfcFacetedBrep => {
1419 let processor = FacetedBrepProcessor::new();
1420 processor.process(&item, decoder, schema)?
1421 }
1422 IfcType::IfcSweptDiskSolid => {
1423 let processor = SweptDiskSolidProcessor::new(schema.clone());
1424 processor.process(&item, decoder, schema)?
1425 }
1426 IfcType::IfcBooleanClippingResult | IfcType::IfcBooleanResult => {
1427 let processor = BooleanClippingProcessor::new();
1428 processor.process(&item, decoder, schema)?
1429 }
1430 IfcType::IfcRevolvedAreaSolid => {
1431 let processor = RevolvedAreaSolidProcessor::new(schema.clone());
1432 processor.process(&item, decoder, schema)?
1433 }
1434 _ => continue, };
1436 mesh.merge(&item_mesh);
1437 }
1438
1439 Ok(mesh)
1444 }
1445
1446 fn supported_types(&self) -> Vec<IfcType> {
1447 vec![IfcType::IfcMappedItem]
1448 }
1449}
1450
1451impl Default for MappedItemProcessor {
1452 fn default() -> Self {
1453 Self::new()
1454 }
1455}
1456
1457pub struct SweptDiskSolidProcessor {
1460 profile_processor: ProfileProcessor,
1461}
1462
1463impl SweptDiskSolidProcessor {
1464 pub fn new(schema: IfcSchema) -> Self {
1465 Self {
1466 profile_processor: ProfileProcessor::new(schema),
1467 }
1468 }
1469}
1470
1471impl GeometryProcessor for SweptDiskSolidProcessor {
1472 fn process(
1473 &self,
1474 entity: &DecodedEntity,
1475 decoder: &mut EntityDecoder,
1476 _schema: &IfcSchema,
1477 ) -> Result<Mesh> {
1478 let directrix_attr = entity
1486 .get(0)
1487 .ok_or_else(|| Error::geometry("SweptDiskSolid missing Directrix".to_string()))?;
1488
1489 let radius = entity
1490 .get_float(1)
1491 .ok_or_else(|| Error::geometry("SweptDiskSolid missing Radius".to_string()))?;
1492
1493 let _inner_radius = entity.get_float(2);
1495
1496 let directrix = decoder
1498 .resolve_ref(directrix_attr)?
1499 .ok_or_else(|| Error::geometry("Failed to resolve Directrix".to_string()))?;
1500
1501 let curve_points = self
1503 .profile_processor
1504 .get_curve_points(&directrix, decoder)?;
1505
1506 if curve_points.len() < 2 {
1507 return Ok(Mesh::new()); }
1509
1510 let segments = 12; let mut positions = Vec::new();
1513 let mut indices = Vec::new();
1514
1515 for i in 0..curve_points.len() {
1517 let p = curve_points[i];
1518
1519 let tangent = if i == 0 {
1521 (curve_points[1] - curve_points[0]).normalize()
1522 } else if i == curve_points.len() - 1 {
1523 (curve_points[i] - curve_points[i - 1]).normalize()
1524 } else {
1525 ((curve_points[i + 1] - curve_points[i - 1]) / 2.0).normalize()
1526 };
1527
1528 let up = if tangent.x.abs() < 0.9 {
1531 Vector3::new(1.0, 0.0, 0.0)
1532 } else {
1533 Vector3::new(0.0, 1.0, 0.0)
1534 };
1535
1536 let perp1 = tangent.cross(&up).normalize();
1537 let perp2 = tangent.cross(&perp1).normalize();
1538
1539 for j in 0..segments {
1541 let angle = 2.0 * std::f64::consts::PI * j as f64 / segments as f64;
1542 let offset = perp1 * (radius * angle.cos()) + perp2 * (radius * angle.sin());
1543 let vertex = p + offset;
1544
1545 positions.push(vertex.x as f32);
1546 positions.push(vertex.y as f32);
1547 positions.push(vertex.z as f32);
1548 }
1549
1550 if i < curve_points.len() - 1 {
1552 let base = (i * segments) as u32;
1553 let next_base = ((i + 1) * segments) as u32;
1554
1555 for j in 0..segments {
1556 let j_next = (j + 1) % segments;
1557
1558 indices.push(base + j as u32);
1560 indices.push(next_base + j as u32);
1561 indices.push(next_base + j_next as u32);
1562
1563 indices.push(base + j as u32);
1564 indices.push(next_base + j_next as u32);
1565 indices.push(base + j_next as u32);
1566 }
1567 }
1568 }
1569
1570 let center_idx = (positions.len() / 3) as u32;
1573 let start = curve_points[0];
1574 positions.push(start.x as f32);
1575 positions.push(start.y as f32);
1576 positions.push(start.z as f32);
1577
1578 for j in 0..segments {
1579 let j_next = (j + 1) % segments;
1580 indices.push(center_idx);
1581 indices.push(j_next as u32);
1582 indices.push(j as u32);
1583 }
1584
1585 let end_center_idx = (positions.len() / 3) as u32;
1587 let end_base = ((curve_points.len() - 1) * segments) as u32;
1588 let end = curve_points[curve_points.len() - 1];
1589 positions.push(end.x as f32);
1590 positions.push(end.y as f32);
1591 positions.push(end.z as f32);
1592
1593 for j in 0..segments {
1594 let j_next = (j + 1) % segments;
1595 indices.push(end_center_idx);
1596 indices.push(end_base + j as u32);
1597 indices.push(end_base + j_next as u32);
1598 }
1599
1600 Ok(Mesh {
1601 positions,
1602 normals: Vec::new(),
1603 indices,
1604 })
1605 }
1606
1607 fn supported_types(&self) -> Vec<IfcType> {
1608 vec![IfcType::IfcSweptDiskSolid]
1609 }
1610}
1611
1612impl Default for SweptDiskSolidProcessor {
1613 fn default() -> Self {
1614 Self::new(IfcSchema::new())
1615 }
1616}
1617
1618pub struct RevolvedAreaSolidProcessor {
1621 profile_processor: ProfileProcessor,
1622}
1623
1624impl RevolvedAreaSolidProcessor {
1625 pub fn new(schema: IfcSchema) -> Self {
1626 Self {
1627 profile_processor: ProfileProcessor::new(schema),
1628 }
1629 }
1630}
1631
1632impl GeometryProcessor for RevolvedAreaSolidProcessor {
1633 fn process(
1634 &self,
1635 entity: &DecodedEntity,
1636 decoder: &mut EntityDecoder,
1637 _schema: &IfcSchema,
1638 ) -> Result<Mesh> {
1639 let profile_attr = entity
1646 .get(0)
1647 .ok_or_else(|| Error::geometry("RevolvedAreaSolid missing SweptArea".to_string()))?;
1648
1649 let profile = decoder
1650 .resolve_ref(profile_attr)?
1651 .ok_or_else(|| Error::geometry("Failed to resolve SweptArea".to_string()))?;
1652
1653 let axis_attr = entity
1655 .get(2)
1656 .ok_or_else(|| Error::geometry("RevolvedAreaSolid missing Axis".to_string()))?;
1657
1658 let axis_placement = decoder
1659 .resolve_ref(axis_attr)?
1660 .ok_or_else(|| Error::geometry("Failed to resolve Axis".to_string()))?;
1661
1662 let angle = entity
1664 .get_float(3)
1665 .ok_or_else(|| Error::geometry("RevolvedAreaSolid missing Angle".to_string()))?;
1666
1667 let profile_2d = self.profile_processor.process(&profile, decoder)?;
1669 if profile_2d.outer.is_empty() {
1670 return Ok(Mesh::new());
1671 }
1672
1673 let axis_location = {
1676 let loc_attr = axis_placement
1677 .get(0)
1678 .ok_or_else(|| Error::geometry("Axis1Placement missing Location".to_string()))?;
1679 let loc = decoder
1680 .resolve_ref(loc_attr)?
1681 .ok_or_else(|| Error::geometry("Failed to resolve axis location".to_string()))?;
1682 let coords = loc
1683 .get(0)
1684 .and_then(|v| v.as_list())
1685 .ok_or_else(|| Error::geometry("Axis location missing coordinates".to_string()))?;
1686 Point3::new(
1687 coords.first().and_then(|v| v.as_float()).unwrap_or(0.0),
1688 coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0),
1689 coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0),
1690 )
1691 };
1692
1693 let axis_direction = {
1694 if let Some(dir_attr) = axis_placement.get(1) {
1695 if !dir_attr.is_null() {
1696 let dir = decoder.resolve_ref(dir_attr)?.ok_or_else(|| {
1697 Error::geometry("Failed to resolve axis direction".to_string())
1698 })?;
1699 let coords = dir.get(0).and_then(|v| v.as_list()).ok_or_else(|| {
1700 Error::geometry("Axis direction missing coordinates".to_string())
1701 })?;
1702 Vector3::new(
1703 coords.first().and_then(|v| v.as_float()).unwrap_or(0.0),
1704 coords.get(1).and_then(|v| v.as_float()).unwrap_or(1.0),
1705 coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0),
1706 )
1707 .normalize()
1708 } else {
1709 Vector3::new(0.0, 1.0, 0.0) }
1711 } else {
1712 Vector3::new(0.0, 1.0, 0.0) }
1714 };
1715
1716 let full_circle = angle.abs() >= std::f64::consts::PI * 1.99;
1719 let segments = if full_circle {
1720 24 } else {
1722 ((angle.abs() / std::f64::consts::PI * 12.0).ceil() as usize).max(4)
1723 };
1724
1725 let profile_points = &profile_2d.outer;
1726 let num_profile_points = profile_points.len();
1727
1728 let mut positions = Vec::new();
1729 let mut indices = Vec::new();
1730
1731 for i in 0..=segments {
1733 let t = if full_circle && i == segments {
1734 0.0 } else {
1736 angle * i as f64 / segments as f64
1737 };
1738
1739 let cos_t = t.cos();
1741 let sin_t = t.sin();
1742 let (ax, ay, az) = (axis_direction.x, axis_direction.y, axis_direction.z);
1743
1744 let k_matrix = |v: Vector3<f64>| -> Vector3<f64> {
1746 Vector3::new(
1747 ay * v.z - az * v.y,
1748 az * v.x - ax * v.z,
1749 ax * v.y - ay * v.x,
1750 )
1751 };
1752
1753 for (j, p2d) in profile_points.iter().enumerate() {
1755 let radius = p2d.x;
1758 let height = p2d.y;
1759
1760 let v = Vector3::new(radius, 0.0, 0.0);
1762
1763 let k_cross_v = k_matrix(v);
1765 let k_dot_v = ax * v.x + ay * v.y + az * v.z;
1766
1767 let v_rot =
1768 v * cos_t + k_cross_v * sin_t + axis_direction * k_dot_v * (1.0 - cos_t);
1769
1770 let pos = axis_location + axis_direction * height + v_rot;
1772
1773 positions.push(pos.x as f32);
1774 positions.push(pos.y as f32);
1775 positions.push(pos.z as f32);
1776
1777 if i < segments && j < num_profile_points - 1 {
1779 let current = (i * num_profile_points + j) as u32;
1780 let next_seg = ((i + 1) * num_profile_points + j) as u32;
1781 let current_next = current + 1;
1782 let next_seg_next = next_seg + 1;
1783
1784 indices.push(current);
1786 indices.push(next_seg);
1787 indices.push(next_seg_next);
1788
1789 indices.push(current);
1790 indices.push(next_seg_next);
1791 indices.push(current_next);
1792 }
1793 }
1794 }
1795
1796 if !full_circle {
1798 let start_center_idx = (positions.len() / 3) as u32;
1800 let start_center = axis_location
1801 + axis_direction
1802 * (profile_points.iter().map(|p| p.y).sum::<f64>()
1803 / profile_points.len() as f64);
1804 positions.push(start_center.x as f32);
1805 positions.push(start_center.y as f32);
1806 positions.push(start_center.z as f32);
1807
1808 for j in 0..num_profile_points - 1 {
1809 indices.push(start_center_idx);
1810 indices.push(j as u32 + 1);
1811 indices.push(j as u32);
1812 }
1813
1814 let end_center_idx = (positions.len() / 3) as u32;
1816 let end_base = (segments * num_profile_points) as u32;
1817 positions.push(start_center.x as f32);
1818 positions.push(start_center.y as f32);
1819 positions.push(start_center.z as f32);
1820
1821 for j in 0..num_profile_points - 1 {
1822 indices.push(end_center_idx);
1823 indices.push(end_base + j as u32);
1824 indices.push(end_base + j as u32 + 1);
1825 }
1826 }
1827
1828 Ok(Mesh {
1829 positions,
1830 normals: Vec::new(),
1831 indices,
1832 })
1833 }
1834
1835 fn supported_types(&self) -> Vec<IfcType> {
1836 vec![IfcType::IfcRevolvedAreaSolid]
1837 }
1838}
1839
1840impl Default for RevolvedAreaSolidProcessor {
1841 fn default() -> Self {
1842 Self::new(IfcSchema::new())
1843 }
1844}
1845
1846pub struct AdvancedBrepProcessor;
1850
1851impl AdvancedBrepProcessor {
1852 pub fn new() -> Self {
1853 Self
1854 }
1855
1856 #[inline]
1858 fn bspline_basis(i: usize, p: usize, u: f64, knots: &[f64]) -> f64 {
1859 if p == 0 {
1860 if knots[i] <= u && u < knots[i + 1] {
1861 1.0
1862 } else {
1863 0.0
1864 }
1865 } else {
1866 let left = {
1867 let denom = knots[i + p] - knots[i];
1868 if denom.abs() < 1e-10 {
1869 0.0
1870 } else {
1871 (u - knots[i]) / denom * Self::bspline_basis(i, p - 1, u, knots)
1872 }
1873 };
1874 let right = {
1875 let denom = knots[i + p + 1] - knots[i + 1];
1876 if denom.abs() < 1e-10 {
1877 0.0
1878 } else {
1879 (knots[i + p + 1] - u) / denom * Self::bspline_basis(i + 1, p - 1, u, knots)
1880 }
1881 };
1882 left + right
1883 }
1884 }
1885
1886 fn evaluate_bspline_surface(
1888 u: f64,
1889 v: f64,
1890 u_degree: usize,
1891 v_degree: usize,
1892 control_points: &[Vec<Point3<f64>>],
1893 u_knots: &[f64],
1894 v_knots: &[f64],
1895 ) -> Point3<f64> {
1896 let _n_u = control_points.len();
1897
1898 let mut result = Point3::new(0.0, 0.0, 0.0);
1899
1900 for (i, row) in control_points.iter().enumerate() {
1901 let n_i = Self::bspline_basis(i, u_degree, u, u_knots);
1902 for (j, cp) in row.iter().enumerate() {
1903 let n_j = Self::bspline_basis(j, v_degree, v, v_knots);
1904 let weight = n_i * n_j;
1905 if weight.abs() > 1e-10 {
1906 result.x += weight * cp.x;
1907 result.y += weight * cp.y;
1908 result.z += weight * cp.z;
1909 }
1910 }
1911 }
1912
1913 result
1914 }
1915
1916 fn tessellate_bspline_surface(
1918 u_degree: usize,
1919 v_degree: usize,
1920 control_points: &[Vec<Point3<f64>>],
1921 u_knots: &[f64],
1922 v_knots: &[f64],
1923 u_segments: usize,
1924 v_segments: usize,
1925 ) -> (Vec<f32>, Vec<u32>) {
1926 let mut positions = Vec::new();
1927 let mut indices = Vec::new();
1928
1929 let u_min = u_knots[u_degree];
1931 let u_max = u_knots[u_knots.len() - u_degree - 1];
1932 let v_min = v_knots[v_degree];
1933 let v_max = v_knots[v_knots.len() - v_degree - 1];
1934
1935 for i in 0..=u_segments {
1937 let u = u_min + (u_max - u_min) * (i as f64 / u_segments as f64);
1938 let u = u.min(u_max - 1e-6).max(u_min);
1940
1941 for j in 0..=v_segments {
1942 let v = v_min + (v_max - v_min) * (j as f64 / v_segments as f64);
1943 let v = v.min(v_max - 1e-6).max(v_min);
1944
1945 let point = Self::evaluate_bspline_surface(
1946 u,
1947 v,
1948 u_degree,
1949 v_degree,
1950 control_points,
1951 u_knots,
1952 v_knots,
1953 );
1954
1955 positions.push(point.x as f32);
1956 positions.push(point.y as f32);
1957 positions.push(point.z as f32);
1958
1959 if i < u_segments && j < v_segments {
1961 let base = (i * (v_segments + 1) + j) as u32;
1962 let next_u = base + (v_segments + 1) as u32;
1963
1964 indices.push(base);
1966 indices.push(base + 1);
1967 indices.push(next_u + 1);
1968
1969 indices.push(base);
1970 indices.push(next_u + 1);
1971 indices.push(next_u);
1972 }
1973 }
1974 }
1975
1976 (positions, indices)
1977 }
1978
1979 fn parse_control_points(
1981 &self,
1982 bspline: &DecodedEntity,
1983 decoder: &mut EntityDecoder,
1984 ) -> Result<Vec<Vec<Point3<f64>>>> {
1985 let cp_list_attr = bspline.get(2).ok_or_else(|| {
1987 Error::geometry("BSplineSurface missing ControlPointsList".to_string())
1988 })?;
1989
1990 let rows = cp_list_attr
1991 .as_list()
1992 .ok_or_else(|| Error::geometry("Expected control point list".to_string()))?;
1993
1994 let mut result = Vec::with_capacity(rows.len());
1995
1996 for row in rows {
1997 let cols = row
1998 .as_list()
1999 .ok_or_else(|| Error::geometry("Expected control point row".to_string()))?;
2000
2001 let mut row_points = Vec::with_capacity(cols.len());
2002 for col in cols {
2003 if let Some(point_id) = col.as_entity_ref() {
2004 let point = decoder.decode_by_id(point_id)?;
2005 let coords = point.get(0).and_then(|v| v.as_list()).ok_or_else(|| {
2006 Error::geometry("CartesianPoint missing coordinates".to_string())
2007 })?;
2008
2009 let x = coords.first().and_then(|v| v.as_float()).unwrap_or(0.0);
2010 let y = coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
2011 let z = coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0);
2012
2013 row_points.push(Point3::new(x, y, z));
2014 }
2015 }
2016 result.push(row_points);
2017 }
2018
2019 Ok(result)
2020 }
2021
2022 fn expand_knots(knot_values: &[f64], multiplicities: &[i64]) -> Vec<f64> {
2024 let mut expanded = Vec::new();
2025 for (knot, &mult) in knot_values.iter().zip(multiplicities.iter()) {
2026 for _ in 0..mult {
2027 expanded.push(*knot);
2028 }
2029 }
2030 expanded
2031 }
2032
2033 fn parse_knot_vectors(&self, bspline: &DecodedEntity) -> Result<(Vec<f64>, Vec<f64>)> {
2035 let u_mult_attr = bspline
2051 .get(7)
2052 .ok_or_else(|| Error::geometry("BSplineSurface missing UMultiplicities".to_string()))?;
2053 let u_mults: Vec<i64> = u_mult_attr
2054 .as_list()
2055 .ok_or_else(|| Error::geometry("Expected U multiplicities list".to_string()))?
2056 .iter()
2057 .filter_map(|v| v.as_int())
2058 .collect();
2059
2060 let v_mult_attr = bspline
2062 .get(8)
2063 .ok_or_else(|| Error::geometry("BSplineSurface missing VMultiplicities".to_string()))?;
2064 let v_mults: Vec<i64> = v_mult_attr
2065 .as_list()
2066 .ok_or_else(|| Error::geometry("Expected V multiplicities list".to_string()))?
2067 .iter()
2068 .filter_map(|v| v.as_int())
2069 .collect();
2070
2071 let u_knots_attr = bspline
2073 .get(9)
2074 .ok_or_else(|| Error::geometry("BSplineSurface missing UKnots".to_string()))?;
2075 let u_knot_values: Vec<f64> = u_knots_attr
2076 .as_list()
2077 .ok_or_else(|| Error::geometry("Expected U knots list".to_string()))?
2078 .iter()
2079 .filter_map(|v| v.as_float())
2080 .collect();
2081
2082 let v_knots_attr = bspline
2084 .get(10)
2085 .ok_or_else(|| Error::geometry("BSplineSurface missing VKnots".to_string()))?;
2086 let v_knot_values: Vec<f64> = v_knots_attr
2087 .as_list()
2088 .ok_or_else(|| Error::geometry("Expected V knots list".to_string()))?
2089 .iter()
2090 .filter_map(|v| v.as_float())
2091 .collect();
2092
2093 let u_knots = Self::expand_knots(&u_knot_values, &u_mults);
2095 let v_knots = Self::expand_knots(&v_knot_values, &v_mults);
2096
2097 Ok((u_knots, v_knots))
2098 }
2099
2100 fn process_planar_face(
2102 &self,
2103 face: &DecodedEntity,
2104 decoder: &mut EntityDecoder,
2105 ) -> Result<(Vec<f32>, Vec<u32>)> {
2106 let bounds_attr = face
2108 .get(0)
2109 .ok_or_else(|| Error::geometry("AdvancedFace missing Bounds".to_string()))?;
2110
2111 let bounds = bounds_attr
2112 .as_list()
2113 .ok_or_else(|| Error::geometry("Expected bounds list".to_string()))?;
2114
2115 let mut positions = Vec::new();
2116 let mut indices = Vec::new();
2117
2118 for bound in bounds {
2119 if let Some(bound_id) = bound.as_entity_ref() {
2120 let bound_entity = decoder.decode_by_id(bound_id)?;
2121
2122 let loop_attr = bound_entity
2124 .get(0)
2125 .ok_or_else(|| Error::geometry("FaceBound missing Bound".to_string()))?;
2126
2127 let loop_entity = decoder
2128 .resolve_ref(loop_attr)?
2129 .ok_or_else(|| Error::geometry("Failed to resolve loop".to_string()))?;
2130
2131 if loop_entity
2133 .ifc_type
2134 .as_str()
2135 .eq_ignore_ascii_case("IFCEDGELOOP")
2136 {
2137 let edges_attr = loop_entity
2138 .get(0)
2139 .ok_or_else(|| Error::geometry("EdgeLoop missing EdgeList".to_string()))?;
2140
2141 let edges = edges_attr
2142 .as_list()
2143 .ok_or_else(|| Error::geometry("Expected edge list".to_string()))?;
2144
2145 let mut polygon_points = Vec::new();
2146
2147 for edge_ref in edges {
2148 if let Some(edge_id) = edge_ref.as_entity_ref() {
2149 let oriented_edge = decoder.decode_by_id(edge_id)?;
2150
2151 let start_attr = oriented_edge.get(0).ok_or_else(|| {
2154 Error::geometry("OrientedEdge missing EdgeStart".to_string())
2155 })?;
2156
2157 if let Some(vertex) = decoder.resolve_ref(start_attr)? {
2158 let point_attr = vertex.get(0).ok_or_else(|| {
2160 Error::geometry("VertexPoint missing geometry".to_string())
2161 })?;
2162
2163 if let Some(point) = decoder.resolve_ref(point_attr)? {
2164 let coords = point
2165 .get(0)
2166 .and_then(|v| v.as_list())
2167 .ok_or_else(|| {
2168 Error::geometry(
2169 "CartesianPoint missing coords".to_string(),
2170 )
2171 })?;
2172
2173 let x =
2174 coords.first().and_then(|v| v.as_float()).unwrap_or(0.0);
2175 let y = coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
2176 let z = coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0);
2177
2178 polygon_points.push(Point3::new(x, y, z));
2179 }
2180 }
2181 }
2182 }
2183
2184 if polygon_points.len() >= 3 {
2186 let base_idx = (positions.len() / 3) as u32;
2187
2188 for point in &polygon_points {
2189 positions.push(point.x as f32);
2190 positions.push(point.y as f32);
2191 positions.push(point.z as f32);
2192 }
2193
2194 for i in 1..polygon_points.len() - 1 {
2198 indices.push(base_idx);
2199 indices.push(base_idx + i as u32);
2200 indices.push(base_idx + i as u32 + 1);
2201 }
2202 }
2203 }
2204 }
2205 }
2206
2207 Ok((positions, indices))
2208 }
2209
2210 fn process_bspline_face(
2212 &self,
2213 bspline: &DecodedEntity,
2214 decoder: &mut EntityDecoder,
2215 ) -> Result<(Vec<f32>, Vec<u32>)> {
2216 let u_degree = bspline.get_float(0).unwrap_or(3.0) as usize;
2218 let v_degree = bspline.get_float(1).unwrap_or(1.0) as usize;
2219
2220 let control_points = self.parse_control_points(bspline, decoder)?;
2222
2223 let (u_knots, v_knots) = self.parse_knot_vectors(bspline)?;
2225
2226 let u_segments = (control_points.len() * 3).clamp(8, 24);
2228 let v_segments = if !control_points.is_empty() {
2229 (control_points[0].len() * 3).clamp(4, 24)
2230 } else {
2231 4
2232 };
2233
2234 let (positions, indices) = Self::tessellate_bspline_surface(
2236 u_degree,
2237 v_degree,
2238 &control_points,
2239 &u_knots,
2240 &v_knots,
2241 u_segments,
2242 v_segments,
2243 );
2244
2245 Ok((positions, indices))
2246 }
2247}
2248
2249impl GeometryProcessor for AdvancedBrepProcessor {
2250 fn process(
2251 &self,
2252 entity: &DecodedEntity,
2253 decoder: &mut EntityDecoder,
2254 _schema: &IfcSchema,
2255 ) -> Result<Mesh> {
2256 let shell_attr = entity
2261 .get(0)
2262 .ok_or_else(|| Error::geometry("AdvancedBrep missing Outer shell".to_string()))?;
2263
2264 let shell = decoder
2265 .resolve_ref(shell_attr)?
2266 .ok_or_else(|| Error::geometry("Failed to resolve Outer shell".to_string()))?;
2267
2268 let faces_attr = shell
2270 .get(0)
2271 .ok_or_else(|| Error::geometry("ClosedShell missing CfsFaces".to_string()))?;
2272
2273 let faces = faces_attr
2274 .as_list()
2275 .ok_or_else(|| Error::geometry("Expected face list".to_string()))?;
2276
2277 let mut all_positions = Vec::new();
2278 let mut all_indices = Vec::new();
2279
2280 for face_ref in faces {
2281 if let Some(face_id) = face_ref.as_entity_ref() {
2282 let face = decoder.decode_by_id(face_id)?;
2283
2284 let surface_attr = face.get(1).ok_or_else(|| {
2290 Error::geometry("AdvancedFace missing FaceSurface".to_string())
2291 })?;
2292
2293 let surface = decoder
2294 .resolve_ref(surface_attr)?
2295 .ok_or_else(|| Error::geometry("Failed to resolve FaceSurface".to_string()))?;
2296
2297 let surface_type = surface.ifc_type.as_str().to_uppercase();
2298 let (positions, indices) = if surface_type == "IFCPLANE" {
2299 self.process_planar_face(&face, decoder)?
2301 } else if surface_type == "IFCBSPLINESURFACEWITHKNOTS"
2302 || surface_type == "IFCRATIONALBSPLINESURFACEWITHKNOTS"
2303 {
2304 self.process_bspline_face(&surface, decoder)?
2306 } else {
2307 continue;
2309 };
2310
2311 let base_idx = (all_positions.len() / 3) as u32;
2313 all_positions.extend(positions);
2314 for idx in indices {
2315 all_indices.push(base_idx + idx);
2316 }
2317 }
2318 }
2319
2320 Ok(Mesh {
2321 positions: all_positions,
2322 normals: Vec::new(),
2323 indices: all_indices,
2324 })
2325 }
2326
2327 fn supported_types(&self) -> Vec<IfcType> {
2328 vec![IfcType::IfcAdvancedBrep, IfcType::IfcAdvancedBrepWithVoids]
2329 }
2330}
2331
2332impl Default for AdvancedBrepProcessor {
2333 fn default() -> Self {
2334 Self::new()
2335 }
2336}
2337
2338#[cfg(test)]
2339mod tests {
2340 use super::*;
2341
2342 #[test]
2343 fn test_advanced_brep_file() {
2344 use crate::router::GeometryRouter;
2345
2346 let content =
2348 std::fs::read_to_string("../../tests/benchmark/models/ifcopenshell/advanced_brep.ifc")
2349 .expect("Failed to read test file");
2350
2351 let entity_index = ifc_lite_core::build_entity_index(&content);
2352 let mut decoder = EntityDecoder::with_index(&content, entity_index);
2353 let router = GeometryRouter::new();
2354
2355 let element = decoder.decode_by_id(181).expect("Failed to decode element");
2357 assert_eq!(element.ifc_type, IfcType::IfcBuildingElementProxy);
2358
2359 let mesh = router
2360 .process_element(&element, &mut decoder)
2361 .expect("Failed to process advanced brep");
2362
2363 assert!(!mesh.is_empty(), "AdvancedBrep should produce geometry");
2365 assert!(
2366 mesh.positions.len() >= 3 * 100,
2367 "Should have significant geometry"
2368 );
2369 assert!(mesh.indices.len() >= 3 * 100, "Should have many triangles");
2370 }
2371
2372 #[test]
2373 fn test_extruded_area_solid() {
2374 let content = r#"
2375#1=IFCRECTANGLEPROFILEDEF(.AREA.,$,$,100.0,200.0);
2376#2=IFCDIRECTION((0.0,0.0,1.0));
2377#3=IFCEXTRUDEDAREASOLID(#1,$,#2,300.0);
2378"#;
2379
2380 let mut decoder = EntityDecoder::new(content);
2381 let schema = IfcSchema::new();
2382 let processor = ExtrudedAreaSolidProcessor::new(schema.clone());
2383
2384 let entity = decoder.decode_by_id(3).unwrap();
2385 let mesh = processor.process(&entity, &mut decoder, &schema).unwrap();
2386
2387 assert!(!mesh.is_empty());
2388 assert!(!mesh.positions.is_empty());
2389 assert!(!mesh.indices.is_empty());
2390 }
2391
2392 #[test]
2393 fn test_triangulated_face_set() {
2394 let content = r#"
2395#1=IFCCARTESIANPOINTLIST3D(((0.0,0.0,0.0),(100.0,0.0,0.0),(50.0,100.0,0.0)));
2396#2=IFCTRIANGULATEDFACESET(#1,$,$,((1,2,3)),$);
2397"#;
2398
2399 let mut decoder = EntityDecoder::new(content);
2400 let schema = IfcSchema::new();
2401 let processor = TriangulatedFaceSetProcessor::new();
2402
2403 let entity = decoder.decode_by_id(2).unwrap();
2404 let mesh = processor.process(&entity, &mut decoder, &schema).unwrap();
2405
2406 assert_eq!(mesh.positions.len(), 9); assert_eq!(mesh.indices.len(), 3); }
2409
2410 #[test]
2411 fn test_boolean_result_with_half_space() {
2412 let content = r#"
2414#1=IFCRECTANGLEPROFILEDEF(.AREA.,$,$,100.0,200.0);
2415#2=IFCDIRECTION((0.0,0.0,1.0));
2416#3=IFCEXTRUDEDAREASOLID(#1,$,#2,300.0);
2417#4=IFCCARTESIANPOINT((0.0,0.0,150.0));
2418#5=IFCDIRECTION((0.0,0.0,1.0));
2419#6=IFCAXIS2PLACEMENT3D(#4,#5,$);
2420#7=IFCPLANE(#6);
2421#8=IFCHALFSPACESOLID(#7,.T.);
2422#9=IFCBOOLEANRESULT(.DIFFERENCE.,#3,#8);
2423"#;
2424
2425 let mut decoder = EntityDecoder::new(content);
2426 let schema = IfcSchema::new();
2427 let processor = BooleanClippingProcessor::new();
2428
2429 let bool_result = decoder.decode_by_id(9).unwrap();
2431 println!("BooleanResult type: {:?}", bool_result.ifc_type);
2432 assert_eq!(bool_result.ifc_type, IfcType::IfcBooleanResult);
2433
2434 let half_space = decoder.decode_by_id(8).unwrap();
2435 println!("HalfSpaceSolid type: {:?}", half_space.ifc_type);
2436 assert_eq!(half_space.ifc_type, IfcType::IfcHalfSpaceSolid);
2437
2438 let mesh = processor
2440 .process(&bool_result, &mut decoder, &schema)
2441 .unwrap();
2442 println!("Mesh vertices: {}", mesh.positions.len() / 3);
2443 println!("Mesh triangles: {}", mesh.indices.len() / 3);
2444
2445 assert!(!mesh.is_empty(), "BooleanResult should produce geometry");
2447 assert!(!mesh.positions.is_empty());
2448 }
2449
2450 #[test]
2451 fn test_764_column_file() {
2452 use crate::router::GeometryRouter;
2453
2454 let content = std::fs::read_to_string(
2456 "../../tests/benchmark/models/ifcopenshell/764--column--no-materials-or-surface-styles-found--augmented.ifc"
2457 ).expect("Failed to read test file");
2458
2459 let entity_index = ifc_lite_core::build_entity_index(&content);
2460 let mut decoder = EntityDecoder::with_index(&content, entity_index);
2461 let router = GeometryRouter::new();
2462
2463 let column = decoder.decode_by_id(8930).expect("Failed to decode column");
2465 println!("Column type: {:?}", column.ifc_type);
2466 assert_eq!(column.ifc_type, IfcType::IfcColumn);
2467
2468 let rep_attr = column
2470 .get(6)
2471 .expect("Column missing representation attribute");
2472 println!("Representation attr: {:?}", rep_attr);
2473
2474 match router.process_element(&column, &mut decoder) {
2476 Ok(mesh) => {
2477 println!("Mesh vertices: {}", mesh.positions.len() / 3);
2478 println!("Mesh triangles: {}", mesh.indices.len() / 3);
2479 assert!(!mesh.is_empty(), "Column should produce geometry");
2480 }
2481 Err(e) => {
2482 panic!("Failed to process column: {:?}", e);
2483 }
2484 }
2485 }
2486
2487 #[test]
2488 fn test_wall_with_opening_file() {
2489 use crate::router::GeometryRouter;
2490
2491 let content = std::fs::read_to_string(
2493 "../../tests/benchmark/models/buildingsmart/wall-with-opening-and-window.ifc",
2494 )
2495 .expect("Failed to read test file");
2496
2497 let entity_index = ifc_lite_core::build_entity_index(&content);
2498 let mut decoder = EntityDecoder::with_index(&content, entity_index);
2499 let router = GeometryRouter::new();
2500
2501 let wall = match decoder.decode_by_id(45) {
2503 Ok(w) => w,
2504 Err(e) => panic!("Failed to decode wall: {:?}", e),
2505 };
2506 println!("Wall type: {:?}", wall.ifc_type);
2507 assert_eq!(wall.ifc_type, IfcType::IfcWall);
2508
2509 let rep_attr = wall.get(6).expect("Wall missing representation attribute");
2511 println!("Representation attr: {:?}", rep_attr);
2512
2513 match router.process_element(&wall, &mut decoder) {
2515 Ok(mesh) => {
2516 println!("Wall mesh vertices: {}", mesh.positions.len() / 3);
2517 println!("Wall mesh triangles: {}", mesh.indices.len() / 3);
2518 assert!(!mesh.is_empty(), "Wall should produce geometry");
2519 }
2520 Err(e) => {
2521 panic!("Failed to process wall: {:?}", e);
2522 }
2523 }
2524
2525 let window = decoder.decode_by_id(102).expect("Failed to decode window");
2527 println!("Window type: {:?}", window.ifc_type);
2528 assert_eq!(window.ifc_type, IfcType::IfcWindow);
2529
2530 match router.process_element(&window, &mut decoder) {
2531 Ok(mesh) => {
2532 println!("Window mesh vertices: {}", mesh.positions.len() / 3);
2533 println!("Window mesh triangles: {}", mesh.indices.len() / 3);
2534 }
2535 Err(e) => {
2536 println!("Window error (might be expected): {:?}", e);
2537 }
2538 }
2539 }
2540}