#![cfg(feature = "lowering")]
use ifc_geometry::lower::{
lower_product_net, lower_product_net_with, AppliedReason, LoweringSession, NetLowering,
NetOptions, ReferenceOnlyOpenings,
};
use ifc_geometry::{units, GeometryError, GeometryResult};
use ifc_model::{Codec, EntityId, Model, Value};
use std::path::PathBuf;
fn fixture() -> Model {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../test/fixtures/synthetic-lowering/reference_view_openings.ifc");
ifc_step::StepCodec
.read_path(&path)
.expect("fixture parses")
}
fn named(model: &Model, type_name: &str, name: &str) -> EntityId {
let found: Vec<EntityId> = model
.of_type(type_name)
.filter(|(_, entity)| matches!(entity.attributes.get(2), Some(Value::Text(text)) if text.as_ref() == name))
.map(|(id, _)| id)
.collect();
assert_eq!(found.len(), 1, "exactly one {type_name} named {name}");
found[0]
}
fn relation_of(model: &Model, opening: EntityId) -> EntityId {
model
.of_type("IFCRELVOIDSELEMENT")
.find(|(_, entity)| entity.attributes.get(5) == Some(&Value::Ref(opening)))
.map(|(id, _)| id)
.expect("the opening is related")
}
fn net(model: &Model, wall: &str, options: NetOptions) -> GeometryResult<NetLowering> {
let scale = units::resolve(model);
let mut session = LoweringSession::new(model, &scale);
let host = named(model, "IFCWALL", wall);
Ok(lower_product_net_with(&mut session, host, options)?.expect("the wall has a Body"))
}
fn take_as_applied() -> NetOptions {
NetOptions::default().with_reference_only_openings(ReferenceOnlyOpenings::TakeAsApplied)
}
fn refused_opening(error: GeometryError) -> (EntityId, EntityId) {
match error {
GeometryError::OpeningNotSubtracted { host, opening, .. } => (host, opening),
other => panic!("expected OpeningNotSubtracted, got {other:?}"),
}
}
#[test]
fn reference_view_openings_without_the_opt_in_refuse_the_host_as_before() {
let model = fixture();
let wall = named(&model, "IFCWALL", "W1");
let opening = named(&model, "IFCOPENINGELEMENT", "O1-reference-only");
let scale = units::resolve(&model);
let mut session = LoweringSession::new(&model, &scale);
let error = lower_product_net(&mut session, wall).expect_err("no opt-in, no net body");
assert_eq!(refused_opening(error), (wall, opening));
let error = net(&model, "W1", NetOptions::default()).expect_err("default options refuse");
assert_eq!(refused_opening(error), (wall, opening));
}
#[test]
fn reference_view_openings_a_reference_only_opening_is_taken_as_applied() {
let model = fixture();
let opening = named(&model, "IFCOPENINGELEMENT", "O1-reference-only");
let lowered = net(&model, "W1", take_as_applied()).expect("the host lowers");
assert!(lowered.subtractions.is_empty(), "nothing is subtracted");
assert_eq!(
lowered.root, lowered.gross,
"the gross Body is the net Body"
);
assert_eq!(lowered.taken_as_applied.len(), 1);
let applied = lowered.taken_as_applied[0];
assert_eq!(applied.opening, opening);
assert_eq!(applied.relation, relation_of(&model, opening));
assert_eq!(applied.reason, AppliedReason::ReferenceRepresentationOnly);
}
#[test]
fn reference_view_openings_an_opening_without_a_representation_is_still_refused() {
let model = fixture();
let wall = named(&model, "IFCWALL", "W2");
let opening = named(&model, "IFCOPENINGELEMENT", "O2-no-representation");
let error = net(&model, "W2", take_as_applied())
.expect_err("an opening that states nothing is not already applied");
assert_eq!(refused_opening(error), (wall, opening));
}
#[test]
fn reference_view_openings_a_body_opening_beside_one_is_still_subtracted() {
let model = fixture();
let reference = named(&model, "IFCOPENINGELEMENT", "O3a-reference-only");
let body = named(&model, "IFCOPENINGELEMENT", "O3b-body");
let lowered = net(&model, "W3", take_as_applied()).expect("the host lowers");
assert_eq!(lowered.openings(), vec![body], "the Body opening is cut");
assert_eq!(
lowered
.taken_as_applied
.iter()
.map(|applied| applied.opening)
.collect::<Vec<_>>(),
vec![reference]
);
assert_ne!(
lowered.root, lowered.gross,
"the Body opening changes the result"
);
assert_eq!(lowered.root, lowered.subtractions[0].result);
}
#[test]
fn reference_view_openings_reference_beside_another_representation_is_refused() {
let model = fixture();
let wall = named(&model, "IFCWALL", "W4");
let opening = named(&model, "IFCOPENINGELEMENT", "O4-reference-and-box");
let error = net(&model, "W4", take_as_applied())
.expect_err("only a Reference-only opening is taken as applied");
assert_eq!(refused_opening(error), (wall, opening));
}