#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Mapping {
pub revit_class: &'static str,
pub ifc_type: &'static str,
pub predefined_type: Option<&'static str>,
}
pub const MAPPINGS: &[Mapping] = &[
Mapping {
revit_class: "Level",
ifc_type: "IFCBUILDINGSTOREY",
predefined_type: None,
},
Mapping {
revit_class: "Grid",
ifc_type: "IFCGRID",
predefined_type: None,
},
Mapping {
revit_class: "Wall",
ifc_type: "IFCWALL",
predefined_type: Some("NOTDEFINED"),
},
Mapping {
revit_class: "ArcWall",
ifc_type: "IFCWALL",
predefined_type: Some("NOTDEFINED"),
},
Mapping {
revit_class: "CurtainWall",
ifc_type: "IFCCURTAINWALL",
predefined_type: Some("NOTDEFINED"),
},
Mapping {
revit_class: "CurtainPanelWall",
ifc_type: "IFCCURTAINWALL",
predefined_type: Some("NOTDEFINED"),
},
Mapping {
revit_class: "Door",
ifc_type: "IFCDOOR",
predefined_type: Some("DOOR"),
},
Mapping {
revit_class: "Window",
ifc_type: "IFCWINDOW",
predefined_type: Some("WINDOW"),
},
Mapping {
revit_class: "ArcWallRectOpening",
ifc_type: "IFCOPENINGELEMENT",
predefined_type: None,
},
Mapping {
revit_class: "Floor",
ifc_type: "IFCSLAB",
predefined_type: Some("FLOOR"),
},
Mapping {
revit_class: "BuildingPad",
ifc_type: "IFCSLAB",
predefined_type: Some("FLOOR"),
},
Mapping {
revit_class: "Roof",
ifc_type: "IFCROOF",
predefined_type: None,
},
Mapping {
revit_class: "Ceiling",
ifc_type: "IFCCOVERING",
predefined_type: Some("CEILING"),
},
Mapping {
revit_class: "Stair",
ifc_type: "IFCSTAIR",
predefined_type: None,
},
Mapping {
revit_class: "Railing",
ifc_type: "IFCRAILING",
predefined_type: None,
},
Mapping {
revit_class: "CurtainWallMullion",
ifc_type: "IFCMEMBER",
predefined_type: Some("MULLION"),
},
Mapping {
revit_class: "CurtainWallPanel",
ifc_type: "IFCPLATE",
predefined_type: Some("CURTAIN_PANEL"),
},
Mapping {
revit_class: "WallSweep",
ifc_type: "IFCBUILDINGELEMENTPROXY",
predefined_type: Some("NOTDEFINED"),
},
Mapping {
revit_class: "Ramp",
ifc_type: "IFCRAMP",
predefined_type: None,
},
Mapping {
revit_class: "Column",
ifc_type: "IFCCOLUMN",
predefined_type: Some("COLUMN"),
},
Mapping {
revit_class: "StructuralColumn",
ifc_type: "IFCCOLUMN",
predefined_type: Some("COLUMN"),
},
Mapping {
revit_class: "StructuralFraming",
ifc_type: "IFCBEAM",
predefined_type: Some("BEAM"),
},
Mapping {
revit_class: "StructuralFoundation",
ifc_type: "IFCFOOTING",
predefined_type: None,
},
Mapping {
revit_class: "Rebar",
ifc_type: "IFCREINFORCINGBAR",
predefined_type: None,
},
Mapping {
revit_class: "Brace",
ifc_type: "IFCMEMBER",
predefined_type: Some("BRACE"),
},
Mapping {
revit_class: "StructuralTruss",
ifc_type: "IFCMEMBER",
predefined_type: Some("CHORD"),
},
Mapping {
revit_class: "Purlin",
ifc_type: "IFCMEMBER",
predefined_type: Some("PURLIN"),
},
Mapping {
revit_class: "Post",
ifc_type: "IFCMEMBER",
predefined_type: Some("POST"),
},
Mapping {
revit_class: "Stud",
ifc_type: "IFCMEMBER",
predefined_type: Some("STUD"),
},
Mapping {
revit_class: "Strut",
ifc_type: "IFCMEMBER",
predefined_type: Some("STRUT"),
},
Mapping {
revit_class: "Girt",
ifc_type: "IFCMEMBER",
predefined_type: Some("MEMBER"),
},
Mapping {
revit_class: "Rafter",
ifc_type: "IFCMEMBER",
predefined_type: Some("RAFTER"),
},
Mapping {
revit_class: "Room",
ifc_type: "IFCSPACE",
predefined_type: Some("SPACE"),
},
Mapping {
revit_class: "Area",
ifc_type: "IFCSPACE",
predefined_type: None,
},
Mapping {
revit_class: "Space",
ifc_type: "IFCSPACE",
predefined_type: None,
},
Mapping {
revit_class: "Furniture",
ifc_type: "IFCFURNITURE",
predefined_type: None,
},
Mapping {
revit_class: "FurnitureSystem",
ifc_type: "IFCFURNITURE",
predefined_type: None,
},
Mapping {
revit_class: "Casework",
ifc_type: "IFCFURNITURE",
predefined_type: None,
},
Mapping {
revit_class: "LightingFixture",
ifc_type: "IFCLIGHTFIXTURE",
predefined_type: None,
},
Mapping {
revit_class: "ElectricalEquipment",
ifc_type: "IFCBUILDINGELEMENTPROXY",
predefined_type: None,
},
Mapping {
revit_class: "ElectricalFixture",
ifc_type: "IFCBUILDINGELEMENTPROXY",
predefined_type: None,
},
Mapping {
revit_class: "MechanicalEquipment",
ifc_type: "IFCBUILDINGELEMENTPROXY",
predefined_type: None,
},
Mapping {
revit_class: "LightingDevice",
ifc_type: "IFCBUILDINGELEMENTPROXY",
predefined_type: None,
},
Mapping {
revit_class: "FireAlarmDevice",
ifc_type: "IFCALARM",
predefined_type: None,
},
Mapping {
revit_class: "DataDevice",
ifc_type: "IFCELECTRICAPPLIANCE",
predefined_type: None,
},
Mapping {
revit_class: "PlumbingFixture",
ifc_type: "IFCSANITARYTERMINAL",
predefined_type: None,
},
Mapping {
revit_class: "SpecialtyEquipment",
ifc_type: "IFCBUILDINGELEMENTPROXY",
predefined_type: None,
},
Mapping {
revit_class: "Duct",
ifc_type: "IFCDUCTSEGMENT",
predefined_type: None,
},
Mapping {
revit_class: "DuctFitting",
ifc_type: "IFCDUCTFITTING",
predefined_type: None,
},
Mapping {
revit_class: "Pipe",
ifc_type: "IFCPIPESEGMENT",
predefined_type: None,
},
Mapping {
revit_class: "PipeFitting",
ifc_type: "IFCPIPEFITTING",
predefined_type: None,
},
Mapping {
revit_class: "Mass",
ifc_type: "IFCBUILDINGELEMENTPROXY",
predefined_type: None,
},
Mapping {
revit_class: "GenericModel",
ifc_type: "IFCBUILDINGELEMENTPROXY",
predefined_type: None,
},
Mapping {
revit_class: "DuctTerminal",
ifc_type: "IFCAIRTERMINAL",
predefined_type: None,
},
Mapping {
revit_class: "FoodServiceEquipment",
ifc_type: "IFCELECTRICAPPLIANCE",
predefined_type: None,
},
Mapping {
revit_class: "Planting",
ifc_type: "IFCBUILDINGELEMENTPROXY",
predefined_type: None,
},
Mapping {
revit_class: "Parking",
ifc_type: "IFCBUILDINGELEMENTPROXY",
predefined_type: None,
},
Mapping {
revit_class: "Entourage",
ifc_type: "IFCBUILDINGELEMENTPROXY",
predefined_type: None,
},
Mapping {
revit_class: "Hardscape",
ifc_type: "IFCBUILDINGELEMENTPROXY",
predefined_type: None,
},
Mapping {
revit_class: "VerticalCirculation",
ifc_type: "IFCTRANSPORTELEMENT",
predefined_type: None,
},
Mapping {
revit_class: "SlabEdge",
ifc_type: "IFCBUILDINGELEMENTPROXY",
predefined_type: None,
},
Mapping {
revit_class: "StairsRun",
ifc_type: "IFCSTAIRFLIGHT",
predefined_type: None,
},
Mapping {
revit_class: "StairsLanding",
ifc_type: "IFCSLAB",
predefined_type: Some("LANDING"),
},
Mapping {
revit_class: "StairsStringer",
ifc_type: "IFCMEMBER",
predefined_type: Some("STRINGER"),
},
];
pub fn lookup(revit_class: &str) -> Option<&'static Mapping> {
MAPPINGS.iter().find(|m| m.revit_class == revit_class)
}
pub const EXPORT_OVERRIDE_TARGETS: &[Mapping] = &[
Mapping {
revit_class: "IfcShadingDevice",
ifc_type: "IFCSHADINGDEVICE",
predefined_type: Some("NOTDEFINED"),
},
Mapping {
revit_class: "IfcSlab",
ifc_type: "IFCSLAB",
predefined_type: Some("FLOOR"),
},
Mapping {
revit_class: "IfcCovering",
ifc_type: "IFCCOVERING",
predefined_type: Some("NOTDEFINED"),
},
];
const PREDEFINED_TYPE_ENUMERATORS: &[(&str, &[&str])] = &[
(
"IFCSLAB",
&[
"FLOOR",
"ROOF",
"LANDING",
"BASESLAB",
"USERDEFINED",
"NOTDEFINED",
],
),
(
"IFCCOVERING",
&[
"CEILING",
"FLOORING",
"CLADDING",
"ROOFING",
"MOLDING",
"SKIRTINGBOARD",
"INSULATION",
"MEMBRANE",
"SLEEVING",
"WRAPPING",
"USERDEFINED",
"NOTDEFINED",
],
),
(
"IFCSHADINGDEVICE",
&["JALOUSIE", "SHUTTER", "AWNING", "USERDEFINED", "NOTDEFINED"],
),
(
"IFCROOF",
&[
"FLAT_ROOF",
"SHED_ROOF",
"GABLE_ROOF",
"HIP_ROOF",
"HIPPED_GABLE_ROOF",
"GAMBREL_ROOF",
"MANSARD_ROOF",
"BARREL_ROOF",
"RAINBOW_ROOF",
"BUTTERFLY_ROOF",
"PAVILION_ROOF",
"DOME_ROOF",
"FREEFORM",
"USERDEFINED",
"NOTDEFINED",
],
),
];
pub fn predefined_type_for(ifc_type: &str, value: &str) -> Option<&'static str> {
let value = value.trim();
PREDEFINED_TYPE_ENUMERATORS
.iter()
.find(|(entity, _)| *entity == ifc_type)?
.1
.iter()
.copied()
.find(|enumerator| enumerator.eq_ignore_ascii_case(value))
}
pub fn lookup_export_override(value: &str) -> Option<&'static Mapping> {
EXPORT_OVERRIDE_TARGETS
.iter()
.find(|m| m.revit_class.eq_ignore_ascii_case(value.trim()))
}
pub fn is_ifc_member(revit_class: &str) -> bool {
lookup(revit_class)
.map(|m| m.ifc_type == "IFCMEMBER")
.unwrap_or(false)
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::BTreeSet;
#[test]
fn export_override_maps_the_one_proven_value() {
let m = lookup_export_override("IfcShadingDevice").unwrap();
assert_eq!(m.ifc_type, "IFCSHADINGDEVICE");
assert_eq!(m.predefined_type, Some("NOTDEFINED"));
assert!(lookup_export_override(" ifcshadingdevice ").is_some());
}
#[test]
fn export_override_maps_the_re45_values() {
assert_eq!(
lookup_export_override("ifcSlab").unwrap().ifc_type,
"IFCSLAB"
);
assert_eq!(
lookup_export_override("IfcCovering").unwrap().ifc_type,
"IFCCOVERING"
);
assert!(lookup_export_override("IfcFooting").is_none());
}
#[test]
fn predefined_type_override_must_be_an_enumerator_of_the_entity() {
assert_eq!(predefined_type_for("IFCSLAB", "ROOF"), Some("ROOF"));
assert_eq!(
predefined_type_for("IFCCOVERING", "cladding"),
Some("CLADDING")
);
assert_eq!(predefined_type_for("IFCSLAB", "CLADDING"), None);
assert_eq!(predefined_type_for("IFCFOOTING", "PAD_FOOTING"), None);
assert_eq!(predefined_type_for("IFCSLAB", "Sample"), None);
}
#[test]
fn unproven_export_override_values_are_not_honoured() {
assert!(lookup_export_override("IfcSpace").is_none());
assert!(lookup_export_override("ifcFooting").is_none());
assert!(lookup_export_override("").is_none());
}
#[test]
fn building_pad_maps_to_ifcslab_floor() {
let m = lookup("BuildingPad").unwrap();
assert_eq!(m.ifc_type, "IFCSLAB");
assert_eq!(m.predefined_type, Some("FLOOR"));
}
#[test]
fn lookup_known_class() {
let m = lookup("Wall").unwrap();
assert_eq!(m.ifc_type, "IFCWALL");
assert_eq!(m.predefined_type, Some("NOTDEFINED"));
}
#[test]
fn witness_following_predefined_types() {
for (class, ifc_type, expected) in [
("Wall", "IFCWALL", "NOTDEFINED"),
("ArcWall", "IFCWALL", "NOTDEFINED"),
("Room", "IFCSPACE", "SPACE"),
("Door", "IFCDOOR", "DOOR"),
("Window", "IFCWINDOW", "WINDOW"),
] {
let m = lookup(class).unwrap();
assert_eq!(m.ifc_type, ifc_type, "{class} entity type");
assert_eq!(m.predefined_type, Some(expected), "{class} PredefinedType");
}
}
#[test]
fn lookup_unknown_class_returns_none() {
assert!(lookup("DefinitelyNotARevitClass").is_none());
}
#[test]
fn revit_classes_unique() {
let mut seen = BTreeSet::new();
for m in MAPPINGS {
assert!(
seen.insert(m.revit_class),
"duplicate mapping for {}",
m.revit_class
);
}
}
#[test]
fn ifc_type_names_uppercase_step_format() {
for m in MAPPINGS {
assert!(
m.ifc_type.starts_with("IFC"),
"{} should start with IFC",
m.ifc_type
);
assert_eq!(
m.ifc_type.to_uppercase(),
m.ifc_type,
"{} should be uppercase",
m.ifc_type
);
}
}
#[test]
fn door_and_window_follow_the_witness_predefined_type() {
assert_eq!(lookup("Door").unwrap().predefined_type, Some("DOOR"));
assert_eq!(lookup("Window").unwrap().predefined_type, Some("WINDOW"));
}
#[test]
fn structural_framing_maps_to_ifcbeam() {
let m = lookup("StructuralFraming").unwrap();
assert_eq!(m.ifc_type, "IFCBEAM");
}
#[test]
fn room_area_space_all_map_to_ifcspace() {
assert_eq!(lookup("Room").unwrap().ifc_type, "IFCSPACE");
assert_eq!(lookup("Area").unwrap().ifc_type, "IFCSPACE");
assert_eq!(lookup("Space").unwrap().ifc_type, "IFCSPACE");
}
#[test]
fn brace_maps_to_ifcmember_brace() {
let m = lookup("Brace").unwrap();
assert_eq!(m.ifc_type, "IFCMEMBER");
assert_eq!(m.predefined_type, Some("BRACE"));
}
#[test]
fn structural_truss_maps_to_ifcmember_chord() {
let m = lookup("StructuralTruss").unwrap();
assert_eq!(m.ifc_type, "IFCMEMBER");
assert_eq!(m.predefined_type, Some("CHORD"));
}
#[test]
fn secondary_members_route_to_ifcmember() {
let cases = [
("Brace", "BRACE"),
("StructuralTruss", "CHORD"),
("Purlin", "PURLIN"),
("Post", "POST"),
("Stud", "STUD"),
("Strut", "STRUT"),
("Girt", "MEMBER"),
("Rafter", "RAFTER"),
];
for (revit, expected_pt) in cases {
let m = lookup(revit).unwrap_or_else(|| panic!("{revit} missing from MAPPINGS"));
assert_eq!(m.ifc_type, "IFCMEMBER", "{revit} should be IFCMEMBER");
assert_eq!(
m.predefined_type,
Some(expected_pt),
"{revit} predefined_type mismatch"
);
}
}
#[test]
fn is_ifc_member_true_for_member_classes() {
assert!(is_ifc_member("Brace"));
assert!(is_ifc_member("StructuralTruss"));
assert!(is_ifc_member("Stud"));
}
#[test]
fn is_ifc_member_false_for_primary_beams() {
assert!(!is_ifc_member("StructuralFraming"));
assert!(!is_ifc_member("Column"));
assert!(!is_ifc_member("Beam"));
}
#[test]
fn is_ifc_member_false_for_unknown_classes() {
assert!(!is_ifc_member("DefinitelyNotARevitClass"));
}
#[test]
fn ifc_member_predefined_types_are_spec_legal() {
let legal = [
"BRACE",
"CHORD",
"COLLAR",
"MEMBER",
"MULLION",
"PLATE",
"POST",
"PURLIN",
"RAFTER",
"STRINGER",
"STRUT",
"STUD",
"USERDEFINED",
"NOTDEFINED",
];
for m in MAPPINGS.iter().filter(|m| m.ifc_type == "IFCMEMBER") {
let pt = m
.predefined_type
.expect("IfcMember entries must have an explicit PredefinedType");
assert!(
legal.contains(&pt),
"{} has illegal IfcMember PredefinedType: {}",
m.revit_class,
pt
);
}
}
}