#![cfg(feature = "step")]
use std::sync::Arc;
use ifc::{Codec, Entity, Model, StepCodec, Value};
const SOURCE: &[u8] = b"ISO-10303-21;\n\
HEADER;\n\
FILE_DESCRIPTION((''),'2;1');\n\
FILE_NAME('plan.ifc','',(''),(''),'','','');\n\
FILE_SCHEMA(('IFC4'));\n\
ENDSEC;\n\
DATA;\n\
#1= IFCANNOTATION('3vB2YO$MX4xv5uCqZZG05x',$,'Brandwand',$,$,$,$);\n\
#2= IFCPOLYLINE((#3,#4));\n\
#3= IFCCARTESIANPOINT((0.,0.));\n\
#4= IFCCARTESIANPOINT((1000.,0.));\n\
ENDSEC;\n\
END-ISO-10303-21;\n";
#[test]
fn overview_snippet_reads_a_model() {
let model = StepCodec.read_bytes(SOURCE).expect("read");
assert_eq!(model.len(), 4);
}
#[test]
fn unknown_entities_survive_a_round_trip() {
let model = StepCodec.read_bytes(SOURCE).expect("read");
let annotations = model.ids_of_type("IFCANNOTATION");
assert_eq!(annotations.len(), 1);
let out = StepCodec.write_bytes(&model).expect("write");
let text = String::from_utf8_lossy(&out);
assert!(text.contains("IFCANNOTATION"));
assert!(text.contains("Brandwand"));
}
#[test]
fn authoring_example_builds_a_serializable_annotation() {
let mut model = Model::new();
let id = model.push(Entity::new(
"IFCANNOTATION",
vec![
Value::Text(Arc::from("3vB2YO$MX4xv5uCqZZG05x")), Value::Null, Value::Text(Arc::from("Brandwand")), Value::Null, Value::Null, Value::Null, Value::Null, ],
));
let entity = model.get(id).expect("entity present");
assert!(entity.is_type("IFCANNOTATION"));
assert_eq!(entity.text(2), Some("Brandwand"));
let out = StepCodec.write_bytes(&model).expect("write");
assert!(String::from_utf8_lossy(&out).contains("IFCANNOTATION"));
}
#[cfg(feature = "author")]
#[test]
fn documented_authoring_example_resolves_slots_and_refuses_typos() {
use ifc::EntityBuilder;
let schema = ifc::Schema::from_express(
"SCHEMA IFC4;\n\
TYPE IfcGloballyUniqueId = STRING; END_TYPE;\n\
TYPE IfcLabel = STRING; END_TYPE;\n\
ENTITY IfcRoot;\n\
GlobalId : IfcGloballyUniqueId;\n\
Name : OPTIONAL IfcLabel;\n\
END_ENTITY;\n\
ENTITY IfcAnnotation SUBTYPE OF (IfcRoot);\n\
ObjectType : OPTIONAL IfcLabel;\n\
END_ENTITY;\n\
END_SCHEMA;",
);
let mut model = Model::new();
let id = EntityBuilder::new(&schema, "IfcAnnotation")
.text("GlobalId", "3vB2YO$MX4xv5uCqZZG05x")
.text("Name", "Brandwand")
.insert(&mut model)
.expect("the documented example builds");
let entity = model.get(id).expect("inserted");
assert_eq!(entity.text(0), Some("3vB2YO$MX4xv5uCqZZG05x"));
assert_eq!(entity.text(1), Some("Brandwand"));
assert!(EntityBuilder::new(&schema, "IfcAnnotaton")
.text("GlobalId", "3vB2YO$MX4xv5uCqZZG05x")
.build()
.is_err());
}
#[test]
fn positional_construction_remains_available_unchecked() {
let mut model = Model::new();
let id = model.push(Entity::new("IFCWALL", vec![Value::Null; 8]));
assert_eq!(
model.get(id).map(|e| e.attribute(0)),
Some(Some(&Value::Null))
);
}
#[test]
fn documented_reverse_index_example_reports_referrer_and_slot() {
use ifc_model::{EntityId, Model, ReverseIndex};
fn print_referrers(model: &Model, target: EntityId) {
let reverse = ReverseIndex::build(model);
for hit in reverse.referrers(target) {
println!(
"referenced by {:?} in attribute slot {}",
hit.from, hit.slot
);
}
}
let target = EntityId(1);
let from = EntityId(2);
let mut model = Model::new();
model.insert(target, Entity::new("IFCWALL", vec![]));
model.insert(
from,
Entity::new("IFCRELAGGREGATES", vec![Value::Ref(target)]),
);
print_referrers(&model, target);
let reverse = ReverseIndex::build(&model);
assert_eq!(reverse.referrers(target).len(), 1);
assert_eq!(reverse.referrers(target)[0].from, from);
assert_eq!(reverse.referrers(target)[0].slot, 0);
}
#[cfg(feature = "spatial")]
#[test]
fn documented_spatial_example_groups_elements_by_storey() {
use ifc::{SpatialKind, SpatialTree};
let mut model = Model::new();
let building = ifc::EntityId(1);
let storey = ifc::EntityId(2);
let wall = ifc::EntityId(3);
model.insert(building, Entity::new("IFCBUILDING", vec![]));
model.insert(storey, Entity::new("IFCBUILDINGSTOREY", vec![]));
model.insert(wall, Entity::new("IFCWALL", vec![]));
model.insert(
ifc::EntityId(10),
Entity::new(
"IFCRELAGGREGATES",
vec![
Value::Null,
Value::Null,
Value::Null,
Value::Null,
Value::Ref(building),
Value::List(vec![Value::Ref(storey)]),
],
),
);
model.insert(
ifc::EntityId(11),
Entity::new(
"IFCRELCONTAINEDINSPATIALSTRUCTURE",
vec![
Value::Null,
Value::Null,
Value::Null,
Value::Null,
Value::List(vec![Value::Ref(wall)]),
Value::Ref(storey),
],
),
);
let tree = SpatialTree::build(&model);
let storeys: Vec<_> = tree.of_kind(SpatialKind::Storey).map(|n| n.id).collect();
assert_eq!(storeys, [storey]);
assert_eq!(tree.elements_of(storey), [wall]);
assert_eq!(tree.container_of(wall), Some(storey));
assert_eq!(tree.ancestors(storey), [building]);
assert!(tree.node(wall).is_none(), "a wall is not a container");
}
#[cfg(feature = "geometry-select")]
#[test]
fn documented_placement_example_resolves_world_coordinates() {
use ifc::{product_world_transform, products_world_transforms};
let mut model = Model::new();
model.insert(
ifc::EntityId(1),
Entity::new(
"IFCCARTESIANPOINT",
vec![Value::List(vec![
Value::Real(0.0),
Value::Real(0.0),
Value::Real(3.0),
])],
),
);
model.insert(
ifc::EntityId(2),
Entity::new(
"IFCAXIS2PLACEMENT3D",
vec![Value::Ref(ifc::EntityId(1)), Value::Null, Value::Null],
),
);
model.insert(
ifc::EntityId(3),
Entity::new(
"IFCLOCALPLACEMENT",
vec![Value::Null, Value::Ref(ifc::EntityId(2))],
),
);
let mut wall = vec![Value::Null; 7];
wall[5] = Value::Ref(ifc::EntityId(3));
model.insert(ifc::EntityId(4), Entity::new("IFCWALL", wall));
let units = ifc::geometry::units::resolve(&model);
let world = product_world_transform(&model, &units, ifc::EntityId(4)).unwrap();
assert_eq!(world.origin, [0.0, 0.0, 3.0]);
let batch = products_world_transforms(&model, &units, [ifc::EntityId(4)]);
assert_eq!(batch.len(), 1);
assert_eq!(batch[0].1.as_ref().unwrap().origin, world.origin);
}
#[cfg(feature = "geometry-select")]
#[test]
fn documented_context_example_inherits_and_selects() {
use ifc::{plan_contexts, select_plan_representation, select_shape_representation};
let mut model = Model::new();
let placement = ifc::EntityId(1);
let root = ifc::EntityId(2);
let plan = ifc::EntityId(3);
model.insert(placement, Entity::new("IFCAXIS2PLACEMENT3D", vec![]));
model.insert(
root,
Entity::new(
"IFCGEOMETRICREPRESENTATIONCONTEXT",
vec![
Value::Null,
Value::Text("Model".into()),
Value::Integer(3),
Value::Real(1.0e-5),
Value::Ref(placement),
Value::Null,
],
),
);
model.insert(
plan,
Entity::new(
"IFCGEOMETRICREPRESENTATIONSUBCONTEXT",
vec![
Value::Text("Plan".into()),
Value::Text("Plan".into()),
Value::Derived,
Value::Derived,
Value::Derived,
Value::Derived,
Value::Ref(root),
Value::Real(0.01),
Value::Enum("PLAN_VIEW".into()),
Value::Null,
],
),
);
let plans = plan_contexts(&model);
assert_eq!(plans.len(), 1, "only the PLAN_VIEW sub-context");
assert_eq!(plans[0].target_scale(), Some(0.01));
assert_eq!(
plans[0].precision(&model),
Some(1.0e-5),
"written as `*`, inherited from the parent"
);
let footprint = ifc::EntityId(10);
let body = ifc::EntityId(11);
for (id, identifier, kind) in [
(footprint, "FootPrint", "Curve2D"),
(body, "Body", "SweptSolid"),
] {
model.insert(
id,
Entity::new(
"IFCSHAPEREPRESENTATION",
vec![
Value::Ref(root),
Value::Text(identifier.into()),
Value::Text(kind.into()),
Value::List(vec![]),
],
),
);
}
let shape = ifc::EntityId(12);
model.insert(
shape,
Entity::new(
"IFCPRODUCTDEFINITIONSHAPE",
vec![
Value::Null,
Value::Null,
Value::List(vec![Value::Ref(footprint), Value::Ref(body)]),
],
),
);
let wall = ifc::EntityId(13);
model.insert(
wall,
Entity::new(
"IFCWALL",
vec![
Value::Text("3vB2YO$MX4xv5uCqZZG05x".into()),
Value::Null,
Value::Null,
Value::Null,
Value::Null,
Value::Null,
Value::Ref(shape),
],
),
);
assert_eq!(
select_shape_representation(&model, wall).unwrap(),
Some(body),
"the 3D selector draws the solid"
);
assert_eq!(
select_plan_representation(&model, wall).unwrap(),
Some(footprint),
"the 2D selector draws the outline"
);
}
#[test]
#[cfg(all(feature = "spatial", feature = "geometry-select"))]
fn documented_unreachable_example_reports_plan_only_geometry() {
use ifc::{unreachable_products, Unreachable};
let mut model = Model::new();
let mut id = 0u64;
let mut add = |model: &mut Model, name: &str, attributes: Vec<Value>| {
id += 1;
model.insert(ifc::EntityId(id), Entity::new(name, attributes));
ifc::EntityId(id)
};
let mut ctx = vec![Value::Null; 10];
ctx[0] = Value::Text(Arc::from("Annotation"));
ctx[1] = Value::Text(Arc::from("Plan"));
ctx[8] = Value::Enum(Arc::from(".PLAN_VIEW."));
let context = add(&mut model, "IFCGEOMETRICREPRESENTATIONSUBCONTEXT", ctx);
let mut rep = vec![Value::Null; 4];
rep[0] = Value::Ref(context);
rep[1] = Value::Text(Arc::from("Annotation"));
rep[2] = Value::Text(Arc::from("Curve2D"));
let representation = add(&mut model, "IFCSHAPEREPRESENTATION", rep);
let mut shp = vec![Value::Null; 3];
shp[2] = Value::List(vec![Value::Ref(representation)]);
let shape = add(&mut model, "IFCPRODUCTDEFINITIONSHAPE", shp);
let mut product = vec![Value::Null; 7];
product[0] = Value::Text(Arc::from("3vB2YO$MX4xv5uCqZZG05x"));
product[6] = Value::Ref(shape);
let sign = add(&mut model, "IFCANNOTATION", product);
let storey = add(
&mut model,
"IFCBUILDINGSTOREY",
vec![
Value::Text(Arc::from("1storey0000000000000001")),
Value::Null,
Value::Null,
Value::Null,
],
);
let mut rel = vec![Value::Null; 6];
rel[4] = Value::List(vec![Value::Ref(sign)]);
rel[5] = Value::Ref(storey);
add(&mut model, "IFCRELCONTAINEDINSPATIALSTRUCTURE", rel);
let findings = unreachable_products(&model);
assert_eq!(findings.len(), 1, "the sign is the only finding");
assert_eq!(findings[0].0, sign);
assert!(matches!(
findings[0].1,
Unreachable::NoRepresentationInModelContext { .. }
));
assert!(
findings[0].1.message().contains("model viewer"),
"the documented message explains the defect: {}",
findings[0].1.message()
);
}
#[cfg(feature = "resource")]
#[test]
fn documented_resource_example_round_trips_authored_composition() {
use ifc::resource::{
AllocationDraft, NestingDraft, ResourceDraft, ResourceEditor, ResourceKind,
ResourceTimeDraft, ResourceView,
};
let mut model = Model::new();
model.header_mut().schema.push("IFC4".into());
let (crew, carpenter, usage, allocation) = {
let mut editor = ResourceEditor::for_model(&mut model).expect("IFC4 editor");
let usage = editor
.create_time(
ResourceTimeDraft::new()
.name("Day shift")
.schedule_work("PT8H")
.schedule_usage(1.0),
)
.expect("usage");
let crew = editor
.create_resource(
ResourceDraft::new(ResourceKind::Crew, "2O2Fr$t4X7Zf8NOew3FLOH")
.name("Envelope crew")
.predefined_type("OFFICE")
.usage(usage),
)
.expect("crew");
let carpenter = editor
.create_resource(
ResourceDraft::new(ResourceKind::Labor, "1O2Fr$t4X7Zf8NOew3FLOH")
.name("Carpenter")
.predefined_type("CARPENTRY"),
)
.expect("labor");
let allocation = editor
.create_allocation(
AllocationDraft::new("3O2Fr$t4X7Zf8NOew3FLOH", carpenter, vec![crew])
.related_objects_type("RESOURCE"),
)
.expect("allocation");
editor
.create_nesting(NestingDraft::new(
"0O2Fr$t4X7Zf8NOew3FLOH",
crew,
vec![carpenter],
))
.expect("nesting");
(crew, carpenter, usage, allocation)
};
let resources = ResourceView::for_model(&model).expect("view");
assert_eq!(
resources
.allocation(allocation)
.expect("allocation")
.related_objects_type(),
Some("RESOURCE")
);
assert_eq!(
resources
.resource(carpenter)
.expect("resource")
.name()
.expect("name"),
Some("Carpenter")
);
assert_eq!(
resources
.resource_time(usage)
.expect("time")
.schedule_work()
.expect("work"),
Some("PT8H")
);
assert_eq!(
resources
.descendants(crew, Default::default())
.expect("composition"),
vec![carpenter]
);
let bytes = StepCodec
.write_bytes(&model)
.expect("write authored resources");
let reparsed = StepCodec
.read_bytes(&bytes)
.expect("read authored resources");
let resources = ResourceView::for_model(&reparsed).expect("reparsed view");
assert_eq!(
resources
.allocation(allocation)
.expect("reparsed allocation")
.related_objects_type(),
Some("RESOURCE")
);
assert_eq!(
resources
.resource(carpenter)
.expect("reparsed resource")
.name()
.expect("reparsed name"),
Some("Carpenter")
);
assert_eq!(
resources
.descendants(crew, Default::default())
.expect("reparsed composition"),
vec![carpenter]
);
}