Skip to main content

ifc_lite_geometry/processors/
advanced.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! AdvancedBrep processor - NURBS/B-spline surfaces.
6//!
7//! Handles IfcAdvancedBrep and IfcAdvancedBrepWithVoids.
8//! Delegates per-face processing to shared advanced_face module.
9
10use crate::{Error, Mesh, Result, TessellationQuality};
11use ifc_lite_core::{DecodedEntity, EntityDecoder, IfcSchema, IfcType};
12
13use crate::router::GeometryProcessor;
14use super::advanced_face::{parse_rational_weights, process_advanced_face, process_bspline_face};
15
16/// AdvancedBrep processor
17/// Handles IfcAdvancedBrep and IfcAdvancedBrepWithVoids - NURBS/B-spline surfaces
18/// Supports planar faces and B-spline surface tessellation
19pub struct AdvancedBrepProcessor;
20
21impl AdvancedBrepProcessor {
22    pub fn new() -> Self {
23        Self
24    }
25}
26
27impl GeometryProcessor for AdvancedBrepProcessor {
28    fn process(
29        &self,
30        entity: &DecodedEntity,
31        decoder: &mut EntityDecoder,
32        _schema: &IfcSchema,
33        quality: TessellationQuality,
34    ) -> Result<Mesh> {
35        // IfcAdvancedBrep attributes:
36        // 0: Outer (IfcClosedShell)
37
38        // Get the outer shell
39        let shell_attr = entity
40            .get(0)
41            .ok_or_else(|| Error::geometry("AdvancedBrep missing Outer shell".to_string()))?;
42
43        let shell = decoder
44            .resolve_ref(shell_attr)?
45            .ok_or_else(|| Error::geometry("Failed to resolve Outer shell".to_string()))?;
46
47        // Get faces from the shell (IfcClosedShell.CfsFaces)
48        let faces_attr = shell
49            .get(0)
50            .ok_or_else(|| Error::geometry("ClosedShell missing CfsFaces".to_string()))?;
51
52        let faces = faces_attr
53            .as_list()
54            .ok_or_else(|| Error::geometry("Expected face list".to_string()))?;
55
56        let mut all_positions = Vec::new();
57        let mut all_indices = Vec::new();
58
59        #[cfg(any(feature = "debug_geometry", feature = "observability"))]
60        let mut empty_faces: Vec<(u32, String)> = Vec::new();
61
62        for face_ref in faces {
63            if let Some(face_id) = face_ref.as_entity_ref() {
64                let face = decoder.decode_by_id(face_id)?;
65
66                // Delegate to shared advanced face processing
67                let (positions, indices) = process_advanced_face(&face, decoder, quality)?;
68
69                if !positions.is_empty() {
70                    // Merge into combined mesh
71                    let base_idx = (all_positions.len() / 3) as u32;
72                    all_positions.extend(positions);
73                    for idx in indices {
74                        all_indices.push(base_idx + idx);
75                    }
76                } else {
77                    #[cfg(any(feature = "debug_geometry", feature = "observability"))]
78                    {
79                        let surface_kind = face
80                            .get(1)
81                            .and_then(|a| decoder.resolve_ref(a).ok().flatten())
82                            .map(|s| s.ifc_type.as_str().to_string())
83                            .unwrap_or_else(|| "<unknown>".to_string());
84                        empty_faces.push((face_id, surface_kind));
85                    }
86                }
87            }
88        }
89
90        // Geometry loss on an advanced brep (faces that meshed to nothing) is
91        // a genuine anomaly: warn-level under observability. The legacy
92        // stderr line stays gated behind debug_geometry as before.
93        #[cfg(any(feature = "debug_geometry", feature = "observability"))]
94        if !empty_faces.is_empty() {
95            crate::diag::diag_warn!(
96                { entity_id = entity.id, empty_faces = empty_faces.len(),
97                  faces = ?empty_faces, "advanced_brep: entity produced empty faces" }
98                else {
99                    #[cfg(feature = "debug_geometry")]
100                    eprintln!(
101                        "[ifc-lite][advanced_brep] entity #{} produced {} empty face(s): {:?}",
102                        entity.id,
103                        empty_faces.len(),
104                        empty_faces
105                    );
106                }
107            );
108        }
109
110        Ok(Mesh {
111            positions: all_positions,
112            normals: Vec::new(),
113            indices: all_indices,
114            rtc_applied: false, 
115            origin: [0.0; 3],        instance_meta: None, local_bounds: None, local_to_world: None })
116    }
117
118    fn supported_types(&self) -> Vec<IfcType> {
119        vec![IfcType::IfcAdvancedBrep, IfcType::IfcAdvancedBrepWithVoids]
120    }
121}
122
123impl Default for AdvancedBrepProcessor {
124    fn default() -> Self {
125        Self::new()
126    }
127}
128
129/// Standalone B-spline surface processor.
130///
131/// Handles `IfcBSplineSurfaceWithKnots` and `IfcRationalBSplineSurfaceWithKnots`
132/// when they appear directly as items inside an `IfcShapeRepresentation` (e.g.
133/// a `Surface3D` rep), without being wrapped in an `IfcAdvancedFace`.
134pub struct BSplineSurfaceProcessor;
135
136impl BSplineSurfaceProcessor {
137    pub fn new() -> Self {
138        Self
139    }
140}
141
142impl Default for BSplineSurfaceProcessor {
143    fn default() -> Self {
144        Self::new()
145    }
146}
147
148impl GeometryProcessor for BSplineSurfaceProcessor {
149    fn process(
150        &self,
151        entity: &DecodedEntity,
152        decoder: &mut EntityDecoder,
153        _schema: &IfcSchema,
154        quality: TessellationQuality,
155    ) -> Result<Mesh> {
156        let weights = if entity.ifc_type == IfcType::IfcRationalBSplineSurfaceWithKnots {
157            parse_rational_weights(entity)
158        } else {
159            None
160        };
161
162        let (positions, indices) =
163            process_bspline_face(entity, decoder, weights.as_deref(), quality)?;
164
165        Ok(Mesh {
166            positions,
167            normals: Vec::new(),
168            indices,
169            rtc_applied: false, 
170            origin: [0.0; 3],        instance_meta: None, local_bounds: None, local_to_world: None })
171    }
172
173    fn supported_types(&self) -> Vec<IfcType> {
174        vec![
175            IfcType::IfcBSplineSurfaceWithKnots,
176            IfcType::IfcRationalBSplineSurfaceWithKnots,
177        ]
178    }
179}