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.element
import ifcopenshell.util.placement import ifcopenshell.util.placement
import ifcopenshell.util.classification
from bcf.v2.bcfxml import BcfXml from bcf.v2.bcfxml import BcfXml
from bcf.v2 import data as bcf from bcf.v2 import data as bcf
@@ -524,9 +525,7 @@ class entity(facet):
if self.predefinedType: if self.predefinedType:
self.message = "an entity name '%(name)s' of predefined type '%(predefinedType)s'" self.message = "an entity name '%(name)s' of predefined type '%(predefinedType)s'"
return facet_evaluation( return facet_evaluation(is_pass, self.message % {"name": inst.is_a(), "predefinedType": predefined_type})
is_pass, self.message % {"name": inst.is_a(), "predefinedType": predefined_type}
)
else: else:
self.message = "an entity name '%(name)s'" self.message = "an entity name '%(name)s'"
return facet_evaluation(is_pass, self.message % {"name": inst.is_a()}) 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 :return: result of the validation as bool and message
:rtype: facet_evaluation(bool, str) :rtype: facet_evaluation(bool, str)
""" """
def get_values(element, name): def get_values(element, name):
if isinstance(name, str): if isinstance(name, str):
return [getattr(element, name, None)] return [getattr(element, name, None)]
@@ -675,7 +675,7 @@ class classification(facet):
results["@instructions"] = self.instructions results["@instructions"] = self.instructions
return results return results
def __call__(self, inst, logger): def __call__(self, inst, logger=None):
"""Validate an ifc instance against that classification facet. """Validate an ifc instance against that classification facet.
:param inst: IFC entity element :param inst: IFC entity element
@@ -685,42 +685,39 @@ class classification(facet):
:return: result of the validation as bool and message :return: result of the validation as bool and message
:rtype: facet_evaluation(bool, str) :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 references = leaf_references.copy()
if ifcopenshell.util.element.get_type(inst): for leaf_reference in leaf_references:
type_classifications = ifcopenshell.util.element.get_type(inst).HasAssociations references.update(ifcopenshell.util.classification.get_inherited_references(leaf_reference))
else:
type_classifications = ()
if self.location == "instance" and instance_classiciations: is_pass = bool(references)
associations = instance_classiciations if is_pass and self.value:
elif self.location == "type" and type_classifications: is_pass = any(
associations = type_classifications [self.value == getattr(r, "Identification", getattr(r, "ItemReference", None)) for r in references]
elif self.location == "any" and (instance_classiciations or type_classifications): )
associations = instance_classiciations + type_classifications if is_pass and self.system:
else: is_pass = any(
associations = () [self.system == ifcopenshell.util.classification.get_classification(r).Name for r in references]
)
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))
self.location_msg = location[self.location] self.location_msg = location[self.location]
if refs: if references:
return facet_evaluation( return facet_evaluation(
(self.system, self.value) in refs, is_pass,
self.message self.message
% { % {
"system": refs[0][0], "system": list(references)[0][0],
"value": "'" + refs[0][1] + "'", "value": list(references)[0][1],
"location": self.location_msg, "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: else:
return facet_evaluation(False, "does not have %sclassification reference" % self.location_msg) 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", "@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): def test_property_create(self):
p = ids.property.create( p = ids.property.create(
location="any", propertySet="Test_PropertySet", name="Test_Parameter", value="Test_Value" location="any", propertySet="Test_PropertySet", name="Test_Parameter", value="Test_Value"