Skip to main content

ifc_geometry/input/body/
kind.rs

1//! How a body representation item is modelled.
2//!
3//! The table below classifies every representation-item family the lowering
4//! dispatcher (`lower::dispatch::IMPLEMENTED`) accepts, except
5//! `IfcMappedItem`, which is not a kind: [`crate::body_description`] resolves
6//! it to the items it maps. `tests/body_description.rs` fails if the two
7//! lists drift, so a family cannot become lowerable without also becoming
8//! describable.
9
10use crate::select::is_a;
11
12/// How one representation item models its shape.
13///
14/// Finer than [`crate::solid::SolidKind`], which groups every sweep as
15/// `Swept`: a rule check that allows extrusions but not directrix sweeps needs
16/// the distinction. The concrete IFC type is reported beside the kind, so a
17/// caller that needs more detail than the kind states can read it.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19#[non_exhaustive]
20pub enum BodyKind {
21    /// `IfcExtrudedAreaSolid`: a profile swept along a straight direction.
22    Extrusion,
23    /// `IfcExtrudedAreaSolidTapered`: an extrusion lofted to an end profile.
24    TaperedExtrusion,
25    /// `IfcRevolvedAreaSolid`: a profile swept about an axis.
26    Revolution,
27    /// `IfcRevolvedAreaSolidTapered`: a revolution lofted to an end profile.
28    TaperedRevolution,
29    /// `IfcFixedReferenceSweptAreaSolid` or `IfcSurfaceCurveSweptAreaSolid`:
30    /// a profile swept along a directrix curve.
31    DirectrixSweep,
32    /// `IfcSweptDiskSolid` or `IfcSweptDiskSolidPolygonal`: a circular disk
33    /// swept along a curve.
34    SweptDisk,
35    /// `IfcSectionedSpine`: cross sections interpolated along a spine.
36    SectionedSpine,
37    /// A faceted or advanced boundary representation, with or without voids.
38    Brep,
39    /// `IfcCsgSolid`, `IfcBooleanResult` or `IfcBooleanClippingResult`: a
40    /// boolean tree.
41    Csg,
42    /// A CSG primitive: `IfcBlock`, `IfcSphere`, `IfcRightCircularCylinder`,
43    /// `IfcRightCircularCone` or `IfcRectangularPyramid`.
44    CsgPrimitive,
45    /// A half space. Infinite; only meaningful as a boolean operand.
46    HalfSpace,
47    /// `IfcBoundingBox`: a proxy extent, not a shape.
48    BoundingBox,
49    /// `IfcTriangulatedFaceSet` or `IfcPolygonalFaceSet`.
50    Tessellated,
51    /// `IfcShellBasedSurfaceModel` or `IfcFaceBasedSurfaceModel`. Never a
52    /// solid, even when every shell is closed.
53    SurfaceModel,
54    /// `IfcFaceSurface` or `IfcAdvancedFace`: one bounded face on a carrier
55    /// surface. Never a solid: a single face encloses nothing.
56    Face,
57    /// `IfcGeometricSet` or `IfcGeometricCurveSet`.
58    GeometricSet,
59    /// Any `IfcCurve`.
60    Curve,
61    /// Any `IfcSurface`.
62    Surface,
63    /// `IfcPointOnCurve` or `IfcPointOnSurface`.
64    Point,
65}
66
67/// Concrete families by kind. Curves and surfaces are classified by
68/// inheritance in [`BodyKind::classify`] instead, as the dispatcher does.
69const FAMILIES: &[(&str, BodyKind)] = &[
70    ("IFCEXTRUDEDAREASOLID", BodyKind::Extrusion),
71    ("IFCEXTRUDEDAREASOLIDTAPERED", BodyKind::TaperedExtrusion),
72    ("IFCREVOLVEDAREASOLID", BodyKind::Revolution),
73    ("IFCREVOLVEDAREASOLIDTAPERED", BodyKind::TaperedRevolution),
74    ("IFCFIXEDREFERENCESWEPTAREASOLID", BodyKind::DirectrixSweep),
75    // IFC4X3 subtype with the same slots.
76    (
77        "IFCDIRECTRIXDERIVEDREFERENCESWEPTAREASOLID",
78        BodyKind::DirectrixSweep,
79    ),
80    ("IFCSURFACECURVESWEPTAREASOLID", BodyKind::DirectrixSweep),
81    ("IFCSWEPTDISKSOLID", BodyKind::SweptDisk),
82    ("IFCSWEPTDISKSOLIDPOLYGONAL", BodyKind::SweptDisk),
83    ("IFCSECTIONEDSPINE", BodyKind::SectionedSpine),
84    ("IFCFACETEDBREP", BodyKind::Brep),
85    ("IFCFACETEDBREPWITHVOIDS", BodyKind::Brep),
86    ("IFCADVANCEDBREP", BodyKind::Brep),
87    ("IFCADVANCEDBREPWITHVOIDS", BodyKind::Brep),
88    ("IFCCSGSOLID", BodyKind::Csg),
89    ("IFCBOOLEANRESULT", BodyKind::Csg),
90    ("IFCBOOLEANCLIPPINGRESULT", BodyKind::Csg),
91    ("IFCBLOCK", BodyKind::CsgPrimitive),
92    ("IFCSPHERE", BodyKind::CsgPrimitive),
93    ("IFCRIGHTCIRCULARCYLINDER", BodyKind::CsgPrimitive),
94    ("IFCRIGHTCIRCULARCONE", BodyKind::CsgPrimitive),
95    ("IFCRECTANGULARPYRAMID", BodyKind::CsgPrimitive),
96    ("IFCHALFSPACESOLID", BodyKind::HalfSpace),
97    ("IFCBOXEDHALFSPACE", BodyKind::HalfSpace),
98    ("IFCPOLYGONALBOUNDEDHALFSPACE", BodyKind::HalfSpace),
99    ("IFCBOUNDINGBOX", BodyKind::BoundingBox),
100    ("IFCTRIANGULATEDFACESET", BodyKind::Tessellated),
101    // IFC4X3 subtype; lowered through `lower::dispatch::SPECIALISATIONS`.
102    ("IFCTRIANGULATEDIRREGULARNETWORK", BodyKind::Tessellated),
103    ("IFCPOLYGONALFACESET", BodyKind::Tessellated),
104    ("IFCSHELLBASEDSURFACEMODEL", BodyKind::SurfaceModel),
105    ("IFCFACEBASEDSURFACEMODEL", BodyKind::SurfaceModel),
106    ("IFCFACESURFACE", BodyKind::Face),
107    ("IFCADVANCEDFACE", BodyKind::Face),
108    ("IFCGEOMETRICSET", BodyKind::GeometricSet),
109    ("IFCGEOMETRICCURVESET", BodyKind::GeometricSet),
110    ("IFCPOINTONCURVE", BodyKind::Point),
111    ("IFCPOINTONSURFACE", BodyKind::Point),
112    // IFC4X3: an IfcSegment, not an IfcCurve, so inheritance does not route
113    // it; it lowers as a curve (#243).
114    ("IFCCURVESEGMENT", BodyKind::Curve),
115];
116
117impl BodyKind {
118    /// Classify a representation item's IFC type name, case-insensitively.
119    ///
120    /// `None` for `IfcMappedItem` (resolved, not classified) and for anything
121    /// that is not a representation item family this crate interprets.
122    pub fn classify(type_name: &str) -> Option<Self> {
123        let upper = type_name.to_ascii_uppercase();
124        // Inheritance first, exactly as `lower::dispatch` routes: every
125        // IfcCurve and IfcSurface subtype is a valid item of its own.
126        if is_a(&upper, "IFCCURVE") {
127            return Some(Self::Curve);
128        }
129        if is_a(&upper, "IFCSURFACE") {
130            return Some(Self::Surface);
131        }
132        FAMILIES
133            .iter()
134            .find(|(name, _)| *name == upper)
135            .map(|(_, kind)| *kind)
136    }
137
138    /// Is this a swept-area family, whose profile [`crate::SweptSolid`]
139    /// describes?
140    pub fn is_swept_area(self) -> bool {
141        matches!(
142            self,
143            Self::Extrusion
144                | Self::TaperedExtrusion
145                | Self::Revolution
146                | Self::TaperedRevolution
147                | Self::DirectrixSweep
148        )
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155    use crate::solid::SolidKind;
156
157    /// The finer kind never contradicts the coarser solid classifier.
158    #[test]
159    fn body_kinds_refine_solid_kinds() {
160        for (name, kind) in FAMILIES {
161            let Some(solid) = SolidKind::classify(name) else {
162                continue;
163            };
164            let expected = match kind {
165                BodyKind::Extrusion
166                | BodyKind::TaperedExtrusion
167                | BodyKind::Revolution
168                | BodyKind::TaperedRevolution
169                | BodyKind::DirectrixSweep
170                | BodyKind::SweptDisk => SolidKind::Swept,
171                BodyKind::SectionedSpine => SolidKind::SectionedSpine,
172                BodyKind::Brep => SolidKind::Brep,
173                BodyKind::Csg if *name == "IFCCSGSOLID" => SolidKind::Csg,
174                BodyKind::Csg => SolidKind::Boolean,
175                BodyKind::CsgPrimitive => SolidKind::Csg,
176                BodyKind::HalfSpace => SolidKind::HalfSpace,
177                BodyKind::BoundingBox => SolidKind::BoundingBox,
178                BodyKind::Tessellated => SolidKind::Tessellated,
179                BodyKind::SurfaceModel | BodyKind::GeometricSet => SolidKind::SurfaceModel,
180                other => panic!("{name} classified {other:?} is not a solid family"),
181            };
182            assert_eq!(solid, expected, "{name}");
183        }
184    }
185
186    #[test]
187    fn curves_and_surfaces_classify_by_inheritance() {
188        assert_eq!(BodyKind::classify("IfcTrimmedCurve"), Some(BodyKind::Curve));
189        assert_eq!(BodyKind::classify("IFCPLANE"), Some(BodyKind::Surface));
190        // IfcFaceSurface is an IfcFace, not an IfcSurface, in every release.
191        assert_eq!(BodyKind::classify("IfcFaceSurface"), Some(BodyKind::Face));
192        assert_eq!(BodyKind::classify("IFCADVANCEDFACE"), Some(BodyKind::Face));
193        assert_eq!(BodyKind::classify("IFCMAPPEDITEM"), None);
194        assert_eq!(BodyKind::classify("IFCWALL"), None);
195    }
196
197    /// IFC4X3-only curves and surfaces classify by inheritance too (#293);
198    /// before the subtype table carried IFC4X3 chains they had no kind.
199    #[test]
200    fn ifc4x3_curves_and_surfaces_classify_by_inheritance() {
201        for curve in [
202            "IFCCLOTHOID",
203            "IFCCOSINESPIRAL",
204            "IFCSINESPIRAL",
205            "IFCSECONDORDERPOLYNOMIALSPIRAL",
206            "IFCTHIRDORDERPOLYNOMIALSPIRAL",
207            "IFCSEVENTHORDERPOLYNOMIALSPIRAL",
208            "IFCPOLYNOMIALCURVE",
209            "IFCOFFSETCURVEBYDISTANCES",
210            "IFCSEGMENTEDREFERENCECURVE",
211            "IFCGRADIENTCURVE",
212        ] {
213            assert_eq!(BodyKind::classify(curve), Some(BodyKind::Curve), "{curve}");
214        }
215        assert_eq!(
216            BodyKind::classify("IFCSECTIONEDSURFACE"),
217            Some(BodyKind::Surface)
218        );
219        // An IfcCurveSegment is an IfcSegment, not an IfcCurve; it keeps its
220        // named row.
221        assert_eq!(BodyKind::classify("IFCCURVESEGMENT"), Some(BodyKind::Curve));
222        assert!(!crate::select::is_a("IFCCURVESEGMENT", "IFCCURVE"));
223    }
224}