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` or the IFC4X3 `IfcSectionedSolidHorizontal`:
36    /// cross sections interpolated along a spine.
37    SectionedSpine,
38    /// A faceted or advanced boundary representation, with or without voids.
39    Brep,
40    /// `IfcCsgSolid`, `IfcBooleanResult` or `IfcBooleanClippingResult`: a
41    /// boolean tree.
42    Csg,
43    /// A CSG primitive: `IfcBlock`, `IfcSphere`, `IfcRightCircularCylinder`,
44    /// `IfcRightCircularCone` or `IfcRectangularPyramid`.
45    CsgPrimitive,
46    /// A half space. Infinite; only meaningful as a boolean operand.
47    HalfSpace,
48    /// `IfcBoundingBox`: a proxy extent, not a shape.
49    BoundingBox,
50    /// `IfcTriangulatedFaceSet` or `IfcPolygonalFaceSet`.
51    Tessellated,
52    /// `IfcShellBasedSurfaceModel` or `IfcFaceBasedSurfaceModel`. Never a
53    /// solid, even when every shell is closed.
54    SurfaceModel,
55    /// `IfcFaceSurface` or `IfcAdvancedFace`: one bounded face on a carrier
56    /// surface. Never a solid: a single face encloses nothing.
57    Face,
58    /// `IfcGeometricSet` or `IfcGeometricCurveSet`.
59    GeometricSet,
60    /// Any `IfcCurve`.
61    Curve,
62    /// Any `IfcSurface`.
63    Surface,
64    /// `IfcPointOnCurve` or `IfcPointOnSurface`, or the IFC4X3
65    /// `IfcPointByDistanceExpression` and `IfcAxis2PlacementLinear` (a
66    /// point, and a point with a frame, at a station along a curve).
67    Point,
68}
69
70/// Concrete families by kind. Curves and surfaces are classified by
71/// inheritance in [`BodyKind::classify`] instead, as the dispatcher does.
72const FAMILIES: &[(&str, BodyKind)] = &[
73    ("IFCEXTRUDEDAREASOLID", BodyKind::Extrusion),
74    ("IFCEXTRUDEDAREASOLIDTAPERED", BodyKind::TaperedExtrusion),
75    ("IFCREVOLVEDAREASOLID", BodyKind::Revolution),
76    ("IFCREVOLVEDAREASOLIDTAPERED", BodyKind::TaperedRevolution),
77    ("IFCFIXEDREFERENCESWEPTAREASOLID", BodyKind::DirectrixSweep),
78    // IFC4X3 subtype with the same slots.
79    (
80        "IFCDIRECTRIXDERIVEDREFERENCESWEPTAREASOLID",
81        BodyKind::DirectrixSweep,
82    ),
83    ("IFCSURFACECURVESWEPTAREASOLID", BodyKind::DirectrixSweep),
84    ("IFCSWEPTDISKSOLID", BodyKind::SweptDisk),
85    ("IFCSWEPTDISKSOLIDPOLYGONAL", BodyKind::SweptDisk),
86    ("IFCSECTIONEDSPINE", BodyKind::SectionedSpine),
87    // IFC4X3 (#307): sections at stations along a directrix.
88    ("IFCSECTIONEDSOLIDHORIZONTAL", BodyKind::SectionedSpine),
89    ("IFCFACETEDBREP", BodyKind::Brep),
90    ("IFCFACETEDBREPWITHVOIDS", BodyKind::Brep),
91    ("IFCADVANCEDBREP", BodyKind::Brep),
92    ("IFCADVANCEDBREPWITHVOIDS", BodyKind::Brep),
93    ("IFCCSGSOLID", BodyKind::Csg),
94    ("IFCBOOLEANRESULT", BodyKind::Csg),
95    ("IFCBOOLEANCLIPPINGRESULT", BodyKind::Csg),
96    ("IFCBLOCK", BodyKind::CsgPrimitive),
97    ("IFCSPHERE", BodyKind::CsgPrimitive),
98    ("IFCRIGHTCIRCULARCYLINDER", BodyKind::CsgPrimitive),
99    ("IFCRIGHTCIRCULARCONE", BodyKind::CsgPrimitive),
100    ("IFCRECTANGULARPYRAMID", BodyKind::CsgPrimitive),
101    ("IFCHALFSPACESOLID", BodyKind::HalfSpace),
102    ("IFCBOXEDHALFSPACE", BodyKind::HalfSpace),
103    ("IFCPOLYGONALBOUNDEDHALFSPACE", BodyKind::HalfSpace),
104    ("IFCBOUNDINGBOX", BodyKind::BoundingBox),
105    ("IFCTRIANGULATEDFACESET", BodyKind::Tessellated),
106    // IFC4X3 subtype; lowered through `lower::dispatch::SPECIALISATIONS`.
107    ("IFCTRIANGULATEDIRREGULARNETWORK", BodyKind::Tessellated),
108    ("IFCPOLYGONALFACESET", BodyKind::Tessellated),
109    ("IFCSHELLBASEDSURFACEMODEL", BodyKind::SurfaceModel),
110    ("IFCFACEBASEDSURFACEMODEL", BodyKind::SurfaceModel),
111    ("IFCFACESURFACE", BodyKind::Face),
112    ("IFCADVANCEDFACE", BodyKind::Face),
113    ("IFCGEOMETRICSET", BodyKind::GeometricSet),
114    ("IFCGEOMETRICCURVESET", BodyKind::GeometricSet),
115    ("IFCPOINTONCURVE", BodyKind::Point),
116    ("IFCPOINTONSURFACE", BodyKind::Point),
117    // IFC4X3 stations (#307).
118    ("IFCPOINTBYDISTANCEEXPRESSION", BodyKind::Point),
119    ("IFCAXIS2PLACEMENTLINEAR", BodyKind::Point),
120    // IFC4X3: an IfcSegment, not an IfcCurve, so inheritance does not route
121    // it; it lowers as a curve (#243).
122    ("IFCCURVESEGMENT", BodyKind::Curve),
123];
124
125impl BodyKind {
126    /// Classify a representation item's IFC type name, case-insensitively.
127    ///
128    /// `None` for `IfcMappedItem` (resolved, not classified) and for anything
129    /// that is not a representation item family this crate interprets.
130    pub fn classify(type_name: &str) -> Option<Self> {
131        let upper = type_name.to_ascii_uppercase();
132        // Inheritance first, exactly as `lower::dispatch` routes: every
133        // IfcCurve and IfcSurface subtype is a valid item of its own.
134        if is_a(&upper, "IFCCURVE") {
135            return Some(Self::Curve);
136        }
137        if is_a(&upper, "IFCSURFACE") {
138            return Some(Self::Surface);
139        }
140        FAMILIES
141            .iter()
142            .find(|(name, _)| *name == upper)
143            .map(|(_, kind)| *kind)
144    }
145
146    /// Is this a swept-area family, whose profile [`crate::SweptSolid`]
147    /// describes?
148    pub fn is_swept_area(self) -> bool {
149        matches!(
150            self,
151            Self::Extrusion
152                | Self::TaperedExtrusion
153                | Self::Revolution
154                | Self::TaperedRevolution
155                | Self::DirectrixSweep
156        )
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163    use crate::solid::SolidKind;
164
165    /// The finer kind never contradicts the coarser solid classifier.
166    #[test]
167    fn body_kinds_refine_solid_kinds() {
168        for (name, kind) in FAMILIES {
169            let Some(solid) = SolidKind::classify(name) else {
170                continue;
171            };
172            let expected = match kind {
173                BodyKind::Extrusion
174                | BodyKind::TaperedExtrusion
175                | BodyKind::Revolution
176                | BodyKind::TaperedRevolution
177                | BodyKind::DirectrixSweep
178                | BodyKind::SweptDisk => SolidKind::Swept,
179                BodyKind::SectionedSpine => SolidKind::SectionedSpine,
180                BodyKind::Brep => SolidKind::Brep,
181                BodyKind::Csg if *name == "IFCCSGSOLID" => SolidKind::Csg,
182                BodyKind::Csg => SolidKind::Boolean,
183                BodyKind::CsgPrimitive => SolidKind::Csg,
184                BodyKind::HalfSpace => SolidKind::HalfSpace,
185                BodyKind::BoundingBox => SolidKind::BoundingBox,
186                BodyKind::Tessellated => SolidKind::Tessellated,
187                BodyKind::SurfaceModel | BodyKind::GeometricSet => SolidKind::SurfaceModel,
188                other => panic!("{name} classified {other:?} is not a solid family"),
189            };
190            assert_eq!(solid, expected, "{name}");
191        }
192    }
193
194    #[test]
195    fn curves_and_surfaces_classify_by_inheritance() {
196        assert_eq!(BodyKind::classify("IfcTrimmedCurve"), Some(BodyKind::Curve));
197        assert_eq!(BodyKind::classify("IFCPLANE"), Some(BodyKind::Surface));
198        // IfcFaceSurface is an IfcFace, not an IfcSurface, in every release.
199        assert_eq!(BodyKind::classify("IfcFaceSurface"), Some(BodyKind::Face));
200        assert_eq!(BodyKind::classify("IFCADVANCEDFACE"), Some(BodyKind::Face));
201        assert_eq!(BodyKind::classify("IFCMAPPEDITEM"), None);
202        assert_eq!(BodyKind::classify("IFCWALL"), None);
203    }
204
205    /// IFC4X3-only curves and surfaces classify by inheritance too (#293);
206    /// before the subtype table carried IFC4X3 chains they had no kind.
207    #[test]
208    fn ifc4x3_curves_and_surfaces_classify_by_inheritance() {
209        for curve in [
210            "IFCCLOTHOID",
211            "IFCCOSINESPIRAL",
212            "IFCSINESPIRAL",
213            "IFCSECONDORDERPOLYNOMIALSPIRAL",
214            "IFCTHIRDORDERPOLYNOMIALSPIRAL",
215            "IFCSEVENTHORDERPOLYNOMIALSPIRAL",
216            "IFCPOLYNOMIALCURVE",
217            "IFCOFFSETCURVEBYDISTANCES",
218            "IFCSEGMENTEDREFERENCECURVE",
219            "IFCGRADIENTCURVE",
220        ] {
221            assert_eq!(BodyKind::classify(curve), Some(BodyKind::Curve), "{curve}");
222        }
223        assert_eq!(
224            BodyKind::classify("IFCSECTIONEDSURFACE"),
225            Some(BodyKind::Surface)
226        );
227        // An IfcCurveSegment is an IfcSegment, not an IfcCurve; it keeps its
228        // named row.
229        assert_eq!(BodyKind::classify("IFCCURVESEGMENT"), Some(BodyKind::Curve));
230        assert!(!crate::select::is_a("IFCCURVESEGMENT", "IFCCURVE"));
231    }
232}