mod refs;
mod release;
mod value;
use std::{collections::BTreeMap, fmt, sync::Arc};
use ifc_model::{EntityId, Model};
use ifc_schema::SchemaVersion;
use refs::{
nonempty_refs_at, optional_refs_at, property_definition_refs_at, ref_at, refs_at, require_ref,
text_at,
};
use release::{validate_model, Release};
use value::{exact_property_value, ResolvedValue};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ExactSource {
Occurrence,
Type(EntityId),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExactLogical {
False,
Unknown,
True,
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum ExactValue {
Null,
Bool(bool),
Logical(ExactLogical),
Binary(Arc<str>),
Integer(i64),
Real(f64),
Text(Arc<str>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct ExactProperty {
pub source: ExactSource,
pub property_set: Arc<str>,
pub set_id: EntityId,
pub property_id: EntityId,
pub value_type: Option<Arc<str>>,
pub unit_id: Option<EntityId>,
pub value: ExactValue,
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum ExactResolution {
Present(ExactProperty),
Absent,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ExactPropertyError {
IncompleteModel {
diagnostics: usize,
},
MissingSchema,
MultipleSchemas {
schemas: usize,
},
UnsupportedSchema {
schema: String,
},
MissingReference {
from: EntityId,
to: EntityId,
},
MalformedAggregate {
entity: EntityId,
attribute: &'static str,
},
MultipleTypeAssignments {
object: EntityId,
first: EntityId,
second: EntityId,
},
InvalidOccurrenceTarget {
relationship: EntityId,
object: EntityId,
},
InvalidTypeTarget {
relationship: EntityId,
object: EntityId,
},
InvalidQueryObject {
object: EntityId,
type_name: Arc<str>,
},
DuplicateAggregateMember {
entity: EntityId,
attribute: &'static str,
member: EntityId,
},
DuplicateMatchingSets {
source: ExactSource,
first: EntityId,
second: EntityId,
},
DuplicateMatchingProperties {
set: EntityId,
first: EntityId,
second: EntityId,
},
MalformedName {
entity: EntityId,
attribute: &'static str,
},
UnsupportedDefinition {
entity: EntityId,
type_name: Arc<str>,
},
UnsupportedProperty {
entity: EntityId,
type_name: Arc<str>,
},
MissingValueSlot {
property: EntityId,
},
MalformedEntitySlots {
entity: EntityId,
type_name: Arc<str>,
expected: usize,
actual: usize,
},
UnsupportedUnit {
property: EntityId,
},
UnsupportedValue {
property: EntityId,
},
NonFiniteReal {
property: EntityId,
},
NotInSchema {
entity: EntityId,
name: Arc<str>,
schema: SchemaVersion,
},
UnsupportedRelationship {
relationship: EntityId,
type_name: Arc<str>,
},
}
impl fmt::Display for ExactPropertyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "exact IFC property resolution failed: {self:?}")
}
}
pub fn exact_schema(model: &Model) -> Result<SchemaVersion, ExactPropertyError> {
validate_model(model).map(|release| release.version)
}
impl std::error::Error for ExactPropertyError {}
pub fn exact_property(
model: &Model,
object: EntityId,
set_name: Option<&str>,
property_name: &str,
) -> Result<ExactResolution, ExactPropertyError> {
let release = validate_model(model)?;
let schema = release.schema;
let query_entity = model
.get(object)
.ok_or(ExactPropertyError::MissingReference {
from: object,
to: object,
})?;
if schema.entity(query_entity.type_name.as_ref()).is_none() {
return Err(release.not_in_schema(object, query_entity.type_name.clone()));
}
let occurrence_domain = |type_name: &str| {
release.slot_accepts("IFCRELDEFINESBYPROPERTIES", 4, type_name)
&& !schema.is_a(type_name, "IFCTYPEOBJECT")
};
if !occurrence_domain(query_entity.type_name.as_ref()) {
return Err(ExactPropertyError::InvalidQueryObject {
object,
type_name: query_entity.type_name.clone(),
});
}
release.require_exact_slots(object, query_entity)?;
refuse_unsupported_relationships(model, release, object)?;
let mut occurrence_sets = Vec::new();
let mut assigned_type = None;
for relation_id in model.ids_of_type("IFCRELDEFINESBYPROPERTIES") {
let r = model.get(*relation_id).expect("type index is current");
release.require_exact_slots(*relation_id, r)?;
let related = nonempty_refs_at(*relation_id, r.attributes.get(4), "RelatedObjects")?;
for related_id in &related {
require_ref(model, *relation_id, *related_id)?;
let related_object = model.get(*related_id).expect("checked reference");
if schema.entity(related_object.type_name.as_ref()).is_none() {
return Err(release.not_in_schema(*related_id, related_object.type_name.clone()));
}
if !occurrence_domain(related_object.type_name.as_ref()) {
return Err(ExactPropertyError::InvalidOccurrenceTarget {
relationship: *relation_id,
object: *related_id,
});
}
release.require_exact_slots(*related_id, related_object)?;
}
let definitions = property_definition_refs_at(
release,
*relation_id,
r.attributes.get(5),
"RelatingPropertyDefinition",
)?;
for definition in definitions {
require_ref(model, *relation_id, definition)?;
let definition_entity = model.get(definition).expect("checked reference");
if schema
.entity(definition_entity.type_name.as_ref())
.is_none()
{
return Err(release.not_in_schema(definition, definition_entity.type_name.clone()));
}
if !schema.is_a(
definition_entity.type_name.as_ref(),
"IFCPROPERTYSETDEFINITION",
) {
return Err(ExactPropertyError::UnsupportedDefinition {
entity: definition,
type_name: definition_entity.type_name.clone(),
});
}
if related.contains(&object) {
occurrence_sets.push(definition);
}
}
}
for relation_id in model.ids_of_type("IFCRELDEFINESBYTYPE") {
let r = model.get(*relation_id).expect("type index is current");
release.require_exact_slots(*relation_id, r)?;
let related = nonempty_refs_at(*relation_id, r.attributes.get(4), "RelatedObjects")?;
for related_id in &related {
require_ref(model, *relation_id, *related_id)?;
let related_object = model.get(*related_id).expect("checked reference");
if schema.entity(related_object.type_name.as_ref()).is_none() {
return Err(release.not_in_schema(*related_id, related_object.type_name.clone()));
}
if !schema.is_a(related_object.type_name.as_ref(), "IFCOBJECT") {
return Err(ExactPropertyError::InvalidTypeTarget {
relationship: *relation_id,
object: *related_id,
});
}
release.require_exact_slots(*related_id, related_object)?;
}
let type_id = ref_at(*relation_id, r.attributes.get(5), "RelatingType")?;
require_ref(model, *relation_id, type_id)?;
let type_object = model.get(type_id).expect("checked reference");
if schema.entity(type_object.type_name.as_ref()).is_none() {
return Err(release.not_in_schema(type_id, type_object.type_name.clone()));
}
if !schema.is_a(type_object.type_name.as_ref(), "IFCTYPEOBJECT") {
return Err(ExactPropertyError::UnsupportedDefinition {
entity: type_id,
type_name: type_object.type_name.clone(),
});
}
release.require_exact_slots(type_id, type_object)?;
if related.contains(&object) {
if let Some(first) = assigned_type.replace(type_id) {
return Err(ExactPropertyError::MultipleTypeAssignments {
object,
first,
second: type_id,
});
}
}
}
let occurrence = find_property(
model,
release,
&occurrence_sets,
ExactSource::Occurrence,
set_name,
property_name,
)?;
let inherited = match assigned_type {
Some(type_id) => {
let type_object = model.get(type_id).expect("checked reference");
let sets = optional_refs_at(type_id, type_object.attributes.get(5), "HasPropertySets")?;
find_property(
model,
release,
&sets,
ExactSource::Type(type_id),
set_name,
property_name,
)?
}
None => None,
};
Ok(occurrence
.or(inherited)
.map_or(ExactResolution::Absent, ExactResolution::Present))
}
fn refuse_unsupported_relationships(
model: &Model,
release: Release,
object: EntityId,
) -> Result<(), ExactPropertyError> {
for parent in ["IFCRELDEFINESBYPROPERTIES", "IFCRELDEFINESBYTYPE"] {
for subtype in release.schema.subtypes(parent) {
for relation_id in model.ids_of_type(subtype) {
let r = model.get(*relation_id).expect("type index is current");
let related = refs_at(*relation_id, r.attributes.get(4), "RelatedObjects")?;
if related.contains(&object) {
return Err(ExactPropertyError::UnsupportedRelationship {
relationship: *relation_id,
type_name: r.type_name.clone(),
});
}
}
}
}
Ok(())
}
fn find_property(
model: &Model,
release: Release,
sets: &[EntityId],
source: ExactSource,
wanted_set: Option<&str>,
wanted_property: &str,
) -> Result<Option<ExactProperty>, ExactPropertyError> {
let schema = release.schema;
let mut result = None;
let mut matching_sets = BTreeMap::new();
for &set_id in sets {
let set = model
.get(set_id)
.ok_or(ExactPropertyError::MissingReference {
from: set_id,
to: set_id,
})?;
release.require_exact_slots(set_id, set)?;
if !set.is_type("IFCPROPERTYSET") && schema.is_a(&set.type_name, "IFCPROPERTYSETDEFINITION")
{
continue;
}
if !set.is_type("IFCPROPERTYSET") {
return Err(ExactPropertyError::UnsupportedDefinition {
entity: set_id,
type_name: set.type_name.clone(),
});
}
let set_name = text_at(set_id, set.attributes.get(2), "Name")?;
if let Some(name) = wanted_set {
if set_name != name {
continue;
}
}
if let Some(first) = matching_sets.insert(set_name.to_owned(), set_id) {
return Err(ExactPropertyError::DuplicateMatchingSets {
source,
first,
second: set_id,
});
}
let mut matching = None;
for property_id in nonempty_refs_at(set_id, set.attributes.get(4), "HasProperties")? {
let property = model
.get(property_id)
.ok_or(ExactPropertyError::MissingReference {
from: set_id,
to: property_id,
})?;
if schema.entity(property.type_name.as_ref()).is_none() {
return Err(release.not_in_schema(property_id, property.type_name.clone()));
}
if !schema.is_a(property.type_name.as_ref(), "IFCPROPERTY") {
return Err(ExactPropertyError::UnsupportedProperty {
entity: property_id,
type_name: property.type_name.clone(),
});
}
release.require_exact_slots(property_id, property)?;
if text_at(property_id, property.attributes.first(), "Name")? != wanted_property {
continue;
}
if let Some(first) = matching.replace(property_id) {
return Err(ExactPropertyError::DuplicateMatchingProperties {
set: set_id,
first,
second: property_id,
});
}
}
if let Some(property_id) = matching {
let property = model.get(property_id).expect("checked reference");
if !property.is_type("IFCPROPERTYSINGLEVALUE") {
return Err(ExactPropertyError::UnsupportedProperty {
entity: property_id,
type_name: property.type_name.clone(),
});
}
let ResolvedValue {
value,
value_type,
unit_id,
} = exact_property_value(model, release, property_id, property)?;
let candidate = ExactProperty {
source,
property_set: Arc::from(set_name),
set_id,
property_id,
value_type,
unit_id,
value,
};
if let Some(first) = result.replace(candidate) {
return Err(ExactPropertyError::DuplicateMatchingSets {
source,
first: first.set_id,
second: set_id,
});
}
}
}
Ok(result)
}