pub mod bspline;
pub mod composite;
pub mod conic;
pub mod line;
pub mod offset;
pub mod polyline;
pub mod trimmed;
pub use bspline::{BSplineCurve, BSplineCurveForm, KnotType, KnotVector};
pub use composite::{CompositeCurve, CompositeCurveSegment, TransitionCode};
pub use conic::{Circle, Ellipse};
pub use line::Line;
pub use offset::{
OffsetCurve2D, OffsetCurve3D, PCurve, PreferredSurfaceCurveRepresentation, SurfaceCurve,
SurfaceCurveKind,
};
pub use polyline::{IndexedPolyCurve, PolySegment, Polyline};
pub use trimmed::{Trim, TrimPoint, TrimSpec, TrimmedCurve, TrimmingPreference};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CurveKind {
Line,
Circle,
Ellipse,
Polyline,
IndexedPolyCurve,
TrimmedCurve,
CompositeCurve,
BSplineCurve,
OffsetCurve2D,
OffsetCurve3D,
PCurve,
SurfaceCurve,
}
impl CurveKind {
pub fn classify(type_name: &str) -> Option<Self> {
match type_name.to_ascii_uppercase().as_str() {
"IFCLINE" => Some(Self::Line),
"IFCCIRCLE" => Some(Self::Circle),
"IFCELLIPSE" => Some(Self::Ellipse),
"IFCPOLYLINE" => Some(Self::Polyline),
"IFCINDEXEDPOLYCURVE" => Some(Self::IndexedPolyCurve),
"IFCTRIMMEDCURVE" => Some(Self::TrimmedCurve),
"IFCCOMPOSITECURVE"
| "IFCCOMPOSITECURVEONSURFACE"
| "IFCBOUNDARYCURVE"
| "IFCOUTERBOUNDARYCURVE" => Some(Self::CompositeCurve),
"IFCBSPLINECURVE" | "IFCBSPLINECURVEWITHKNOTS" | "IFCRATIONALBSPLINECURVEWITHKNOTS" => {
Some(Self::BSplineCurve)
}
"IFCOFFSETCURVE2D" => Some(Self::OffsetCurve2D),
"IFCOFFSETCURVE3D" => Some(Self::OffsetCurve3D),
"IFCPCURVE" => Some(Self::PCurve),
"IFCSURFACECURVE" | "IFCINTERSECTIONCURVE" | "IFCSEAMCURVE" => Some(Self::SurfaceCurve),
_ => None,
}
}
pub fn is_bounded(&self) -> bool {
matches!(
self,
Self::Polyline
| Self::IndexedPolyCurve
| Self::TrimmedCurve
| Self::CompositeCurve
| Self::BSplineCurve
)
}
pub fn is_closed_conic(&self) -> bool {
matches!(self, Self::Circle | Self::Ellipse)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_ifc4_curve_type_name_classifies() {
let names = [
"IFCLINE",
"IFCCIRCLE",
"IFCELLIPSE",
"IFCPOLYLINE",
"IFCINDEXEDPOLYCURVE",
"IFCTRIMMEDCURVE",
"IFCCOMPOSITECURVE",
"IFCCOMPOSITECURVEONSURFACE",
"IFCBOUNDARYCURVE",
"IFCOUTERBOUNDARYCURVE",
"IFCBSPLINECURVE",
"IFCBSPLINECURVEWITHKNOTS",
"IFCRATIONALBSPLINECURVEWITHKNOTS",
"IFCOFFSETCURVE2D",
"IFCOFFSETCURVE3D",
"IFCPCURVE",
"IFCSURFACECURVE",
"IFCINTERSECTIONCURVE",
"IFCSEAMCURVE",
];
for name in names {
assert!(
CurveKind::classify(name).is_some(),
"{name} is an IfcCurve subtype but does not classify"
);
}
}
#[test]
fn a_non_curve_entity_does_not_classify_as_a_curve() {
assert_eq!(CurveKind::classify("IFCWALL"), None);
assert_eq!(CurveKind::classify("IFCPLANE"), None);
assert_eq!(CurveKind::classify("IFCCARTESIANPOINT"), None);
}
#[test]
fn ifc4x3_curve_segment_is_an_honest_miss_rather_than_a_wrong_match() {
assert_eq!(CurveKind::classify("IFCCURVESEGMENT"), None);
}
#[test]
fn classification_ignores_case() {
assert_eq!(CurveKind::classify("IfcCircle"), Some(CurveKind::Circle));
assert_eq!(
CurveKind::classify("ifcpolyline"),
Some(CurveKind::Polyline)
);
}
#[test]
fn unbounded_curves_are_distinguished_from_drawable_ones() {
for kind in [
CurveKind::Line,
CurveKind::Circle,
CurveKind::Ellipse,
CurveKind::OffsetCurve2D,
CurveKind::OffsetCurve3D,
CurveKind::PCurve,
CurveKind::SurfaceCurve,
] {
assert!(!kind.is_bounded(), "{kind:?} must not claim to be bounded");
}
for kind in [
CurveKind::Polyline,
CurveKind::IndexedPolyCurve,
CurveKind::TrimmedCurve,
CurveKind::CompositeCurve,
CurveKind::BSplineCurve,
] {
assert!(kind.is_bounded(), "{kind:?} is bounded");
}
}
#[test]
fn only_conics_are_flagged_as_sense_dependent_when_trimmed() {
assert!(CurveKind::Circle.is_closed_conic());
assert!(CurveKind::Ellipse.is_closed_conic());
assert!(!CurveKind::Line.is_closed_conic());
assert!(!CurveKind::BSplineCurve.is_closed_conic());
}
}