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    ("IFCSURFACECURVESWEPTAREASOLID", BodyKind::DirectrixSweep),
76    ("IFCSWEPTDISKSOLID", BodyKind::SweptDisk),
77    ("IFCSWEPTDISKSOLIDPOLYGONAL", BodyKind::SweptDisk),
78    ("IFCSECTIONEDSPINE", BodyKind::SectionedSpine),
79    ("IFCFACETEDBREP", BodyKind::Brep),
80    ("IFCFACETEDBREPWITHVOIDS", BodyKind::Brep),
81    ("IFCADVANCEDBREP", BodyKind::Brep),
82    ("IFCADVANCEDBREPWITHVOIDS", BodyKind::Brep),
83    ("IFCCSGSOLID", BodyKind::Csg),
84    ("IFCBOOLEANRESULT", BodyKind::Csg),
85    ("IFCBOOLEANCLIPPINGRESULT", BodyKind::Csg),
86    ("IFCBLOCK", BodyKind::CsgPrimitive),
87    ("IFCSPHERE", BodyKind::CsgPrimitive),
88    ("IFCRIGHTCIRCULARCYLINDER", BodyKind::CsgPrimitive),
89    ("IFCRIGHTCIRCULARCONE", BodyKind::CsgPrimitive),
90    ("IFCRECTANGULARPYRAMID", BodyKind::CsgPrimitive),
91    ("IFCHALFSPACESOLID", BodyKind::HalfSpace),
92    ("IFCBOXEDHALFSPACE", BodyKind::HalfSpace),
93    ("IFCPOLYGONALBOUNDEDHALFSPACE", BodyKind::HalfSpace),
94    ("IFCBOUNDINGBOX", BodyKind::BoundingBox),
95    ("IFCTRIANGULATEDFACESET", BodyKind::Tessellated),
96    ("IFCPOLYGONALFACESET", BodyKind::Tessellated),
97    ("IFCSHELLBASEDSURFACEMODEL", BodyKind::SurfaceModel),
98    ("IFCFACEBASEDSURFACEMODEL", BodyKind::SurfaceModel),
99    ("IFCFACESURFACE", BodyKind::Face),
100    ("IFCADVANCEDFACE", BodyKind::Face),
101    ("IFCGEOMETRICSET", BodyKind::GeometricSet),
102    ("IFCGEOMETRICCURVESET", BodyKind::GeometricSet),
103    ("IFCPOINTONCURVE", BodyKind::Point),
104    ("IFCPOINTONSURFACE", BodyKind::Point),
105];
106
107impl BodyKind {
108    /// Classify a representation item's IFC type name, case-insensitively.
109    ///
110    /// `None` for `IfcMappedItem` (resolved, not classified) and for anything
111    /// that is not a representation item family this crate interprets.
112    pub fn classify(type_name: &str) -> Option<Self> {
113        let upper = type_name.to_ascii_uppercase();
114        // Inheritance first, exactly as `lower::dispatch` routes: every
115        // IfcCurve and IfcSurface subtype is a valid item of its own.
116        if is_a(&upper, "IFCCURVE") {
117            return Some(Self::Curve);
118        }
119        if is_a(&upper, "IFCSURFACE") {
120            return Some(Self::Surface);
121        }
122        FAMILIES
123            .iter()
124            .find(|(name, _)| *name == upper)
125            .map(|(_, kind)| *kind)
126    }
127
128    /// Is this a swept-area family, whose profile [`crate::SweptSolid`]
129    /// describes?
130    pub fn is_swept_area(self) -> bool {
131        matches!(
132            self,
133            Self::Extrusion
134                | Self::TaperedExtrusion
135                | Self::Revolution
136                | Self::TaperedRevolution
137                | Self::DirectrixSweep
138        )
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145    use crate::solid::SolidKind;
146
147    /// The finer kind never contradicts the coarser solid classifier.
148    #[test]
149    fn body_kinds_refine_solid_kinds() {
150        for (name, kind) in FAMILIES {
151            let Some(solid) = SolidKind::classify(name) else {
152                continue;
153            };
154            let expected = match kind {
155                BodyKind::Extrusion
156                | BodyKind::TaperedExtrusion
157                | BodyKind::Revolution
158                | BodyKind::TaperedRevolution
159                | BodyKind::DirectrixSweep
160                | BodyKind::SweptDisk => SolidKind::Swept,
161                BodyKind::SectionedSpine => SolidKind::SectionedSpine,
162                BodyKind::Brep => SolidKind::Brep,
163                BodyKind::Csg if *name == "IFCCSGSOLID" => SolidKind::Csg,
164                BodyKind::Csg => SolidKind::Boolean,
165                BodyKind::CsgPrimitive => SolidKind::Csg,
166                BodyKind::HalfSpace => SolidKind::HalfSpace,
167                BodyKind::BoundingBox => SolidKind::BoundingBox,
168                BodyKind::Tessellated => SolidKind::Tessellated,
169                BodyKind::SurfaceModel | BodyKind::GeometricSet => SolidKind::SurfaceModel,
170                other => panic!("{name} classified {other:?} is not a solid family"),
171            };
172            assert_eq!(solid, expected, "{name}");
173        }
174    }
175
176    #[test]
177    fn curves_and_surfaces_classify_by_inheritance() {
178        assert_eq!(BodyKind::classify("IfcTrimmedCurve"), Some(BodyKind::Curve));
179        assert_eq!(BodyKind::classify("IFCPLANE"), Some(BodyKind::Surface));
180        // IfcFaceSurface is an IfcFace, not an IfcSurface, in every release.
181        assert_eq!(BodyKind::classify("IfcFaceSurface"), Some(BodyKind::Face));
182        assert_eq!(BodyKind::classify("IFCADVANCEDFACE"), Some(BodyKind::Face));
183        assert_eq!(BodyKind::classify("IFCMAPPEDITEM"), None);
184        assert_eq!(BodyKind::classify("IFCWALL"), None);
185    }
186}