IDS classification facets not handle inheritance, full refs, and restrictions

This commit is contained in:
Dion Moult
2022-05-11 14:49:47 +10:00
parent 95b48b02e4
commit 0774100606
2 changed files with 150 additions and 31 deletions
+28 -31
View File
@@ -25,6 +25,7 @@ import datetime
import ifcopenshell.util.element
import ifcopenshell.util.placement
import ifcopenshell.util.classification
from bcf.v2.bcfxml import BcfXml
from bcf.v2 import data as bcf
@@ -524,9 +525,7 @@ class entity(facet):
if self.predefinedType:
self.message = "an entity name '%(name)s' of predefined type '%(predefinedType)s'"
return facet_evaluation(
is_pass, self.message % {"name": inst.is_a(), "predefinedType": predefined_type}
)
return facet_evaluation(is_pass, self.message % {"name": inst.is_a(), "predefinedType": predefined_type})
else:
self.message = "an entity name '%(name)s'"
return facet_evaluation(is_pass, self.message % {"name": inst.is_a()})
@@ -590,6 +589,7 @@ class attribute(facet):
:return: result of the validation as bool and message
:rtype: facet_evaluation(bool, str)
"""
def get_values(element, name):
if isinstance(name, str):
return [getattr(element, name, None)]
@@ -675,7 +675,7 @@ class classification(facet):
results["@instructions"] = self.instructions
return results
def __call__(self, inst, logger):
def __call__(self, inst, logger=None):
"""Validate an ifc instance against that classification facet.
:param inst: IFC entity element
@@ -685,42 +685,39 @@ class classification(facet):
:return: result of the validation as bool and message
:rtype: facet_evaluation(bool, str)
"""
if self.location == "instance":
leaf_references = ifcopenshell.util.classification.get_references(inst, should_inherit=False)
elif self.location == "type":
element_type = ifcopenshell.util.element.get_type(inst)
leaf_references = ifcopenshell.util.classification.get_references(element_type) if element_type else set()
elif self.location == "any":
leaf_references = ifcopenshell.util.classification.get_references(inst)
instance_classiciations = inst.HasAssociations
if ifcopenshell.util.element.get_type(inst):
type_classifications = ifcopenshell.util.element.get_type(inst).HasAssociations
else:
type_classifications = ()
references = leaf_references.copy()
for leaf_reference in leaf_references:
references.update(ifcopenshell.util.classification.get_inherited_references(leaf_reference))
if self.location == "instance" and instance_classiciations:
associations = instance_classiciations
elif self.location == "type" and type_classifications:
associations = type_classifications
elif self.location == "any" and (instance_classiciations or type_classifications):
associations = instance_classiciations + type_classifications
else:
associations = ()
refs = []
for association in associations:
if association.is_a("IfcRelAssociatesClassification"):
cref = association.RelatingClassification
if hasattr(cref, "ItemReference"): # IFC2x3
refs.append((cref.ReferencedSource.Name, cref.ItemReference))
elif hasattr(cref, "Identification"): # IFC4
refs.append((cref.ReferencedSource.Name, cref.Identification))
is_pass = bool(references)
if is_pass and self.value:
is_pass = any(
[self.value == getattr(r, "Identification", getattr(r, "ItemReference", None)) for r in references]
)
if is_pass and self.system:
is_pass = any(
[self.system == ifcopenshell.util.classification.get_classification(r).Name for r in references]
)
self.location_msg = location[self.location]
if refs:
if references:
return facet_evaluation(
(self.system, self.value) in refs,
is_pass,
self.message
% {
"system": refs[0][0],
"value": "'" + refs[0][1] + "'",
"system": list(references)[0][0],
"value": list(references)[0][1],
"location": self.location_msg,
}, # what if not first item of refs?
}, # TODO Fix this 0 index reference assumption when I refactor out the messages
)
else:
return facet_evaluation(False, "does not have %sclassification reference" % self.location_msg)
+122
View File
@@ -480,6 +480,128 @@ class TestIdsAuthoring(unittest.TestCase):
"@instructions": "instructions",
}
def test_filtering_using_a_classification_facet(self):
library = ifcopenshell.file()
system = library.createIfcClassification(Name="Foobar")
ref1 = library.createIfcClassificationReference(Identification="1", ReferencedSource=system)
ref11 = library.createIfcClassificationReference(Identification="11", ReferencedSource=ref1)
ref2 = library.createIfcClassificationReference(Identification="2", ReferencedSource=system)
ref22 = library.createIfcClassificationReference(Identification="22", ReferencedSource=ref2)
ifc = ifcopenshell.file()
project = ifc.createIfcProject()
ifcopenshell.api.run("classification.add_classification", ifc, classification=system)
element0 = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall")
element1 = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall")
ifcopenshell.api.run(
"classification.add_reference", ifc, product=element1, reference=ref1, classification=system
)
element11 = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall")
ifcopenshell.api.run(
"classification.add_reference", ifc, product=element11, reference=ref11, classification=system
)
element22 = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall")
ifcopenshell.api.run(
"classification.add_reference",
ifc,
product=element22,
reference=ref22,
classification=system,
is_lightweight=False,
)
# A classification facet with no data matches any present classification
facet = ids.classification.create()
assert bool(facet(element0)) is False
assert bool(facet(element1)) is True
# Values should match exactly if lightweight classifications are used.
facet = ids.classification.create(value="1")
assert bool(facet(element1)) is True
# Values should match subreferences if full classifications are used.
# E.g. a facet searching for Uniclass EF_25_10 Walls will match Uniclass EF_25_10_25, EF_25_10_30, etc
facet = ids.classification.create(value="2")
assert bool(facet(element22)) is True
# Systems should match exactly regardless of lightweight or full classifications
facet = ids.classification.create(system="Foobar")
assert bool(facet(project)) is True
assert bool(facet(element0)) is False
assert bool(facet(element1)) is True
assert bool(facet(element11)) is True
assert bool(facet(element22)) is True
# Restrictions can be used for values
restriction = ids.restriction.create(options="1.*", type="pattern", base="string")
facet = ids.classification.create(value=restriction)
assert bool(facet(element1)) is True
assert bool(facet(element11)) is True
assert bool(facet(element22)) is False
# Restrictions can be used for systems
restriction = ids.restriction.create(options="Foo.*", type="pattern", base="string")
facet = ids.classification.create(system=restriction)
assert bool(facet(element0)) is False
assert bool(facet(element1)) is True
# Specifying both a value and a system means that both (as opposed to either) requirements must be met
facet = ids.classification.create(system="Foobar", value="1")
assert bool(facet(element1)) is True
assert bool(facet(element11)) is False
# Location instance only checks on the instance (even if it's a type), this seems strange though
wall = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall")
wall_type = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWallType")
ifcopenshell.api.run("type.assign_type", ifc, related_object=wall, relating_type=wall_type)
ifcopenshell.api.run(
"classification.add_reference", ifc, product=wall_type, reference=ref1, classification=system
)
facet = ids.classification.create(value="1", location="instance")
assert bool(facet(wall_type)) is True
assert bool(facet(wall)) is False
ifcopenshell.api.run(
"classification.add_reference", ifc, product=wall, reference=ref1, classification=system
)
assert bool(facet(wall)) is True
# Location type only checks on the type
wall = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall")
wall_type = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWallType")
ifcopenshell.api.run("type.assign_type", ifc, related_object=wall, relating_type=wall_type)
ifcopenshell.api.run(
"classification.add_reference", ifc, product=wall, reference=ref1, classification=system
)
facet = ids.classification.create(value="1", location="type")
assert bool(facet(wall)) is False
assert bool(facet(wall_type)) is False
ifcopenshell.api.run(
"classification.add_reference", ifc, product=wall_type, reference=ref1, classification=system
)
assert bool(facet(wall)) is True
assert bool(facet(wall_type)) is True
# Location any checks on either the type or instance.
# IFC doesn't specify how inheritance and overrides work here. Two options:
# Option 1) Occurrences replace inherited type references
# Option 2) Occurrences union with inherited type references
# Option 2 is followed here.
wall = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall")
wall_type = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWallType")
ifcopenshell.api.run("type.assign_type", ifc, related_object=wall, relating_type=wall_type)
ifcopenshell.api.run(
"classification.add_reference", ifc, product=wall, reference=ref11, classification=system
)
ifcopenshell.api.run(
"classification.add_reference", ifc, product=wall_type, reference=ref22, classification=system
)
facet = ids.classification.create(value="11", location="any")
assert bool(facet(wall)) is True
assert bool(facet(wall_type)) is False
facet = ids.classification.create(value="22", location="any")
assert bool(facet(wall)) is True
assert bool(facet(wall_type)) is True
def test_property_create(self):
p = ids.property.create(
location="any", propertySet="Test_PropertySet", name="Test_Parameter", value="Test_Value"