IfcTester now supports occurs attributes on facets

This commit is contained in:
Dion Moult
2022-09-12 11:51:03 +10:00
parent f745222d37
commit 85e123fadb
3 changed files with 86 additions and 8 deletions
+30 -3
View File
@@ -162,6 +162,9 @@ class Attribute(Facet):
super().__init__(name, value, minOccurs, maxOccurs, instructions)
def __call__(self, inst, logger=None):
if self.minOccurs == 0 and self.maxOccurs != 0:
return AttributeResult(True)
def get_values(element, name):
if isinstance(name, str):
return [getattr(element, name, None)]
@@ -245,6 +248,8 @@ class Attribute(Facet):
reason = {"type": "VALUE", "actual": value}
break
if self.maxOccurs == 0:
return AttributeResult(not is_pass, {"type": "PROHIBITED"})
return AttributeResult(is_pass, reason)
@@ -267,6 +272,9 @@ class Classification(Facet):
pass
def __call__(self, inst, logger=None):
if self.minOccurs == 0 and self.maxOccurs != 0:
return ClassificationResult(True)
leaf_references = ifcopenshell.util.classification.get_references(inst)
references = leaf_references.copy()
@@ -291,12 +299,14 @@ class Classification(Facet):
if not is_pass:
reason = {"type": "SYSTEM", "actual": systems}
if self.maxOccurs == 0:
return ClassificationResult(not is_pass, {"type": "PROHIBITED"})
return ClassificationResult(is_pass, reason)
class PartOf(Facet):
def __init__(self, entity=None, relation="IfcRelAggregates", instructions=None):
self.parameters = ["entity", "@relation", "@instructions"]
def __init__(self, entity=None, relation="IfcRelAggregates", minOccurs=None, maxOccurs=None, instructions=None):
self.parameters = ["entity", "@relation", "@minOccurs", "@maxOccurs", "@instructions"]
self.applicability_templates = [
"An element with an {relation} relationship with an {entity}",
"An element with an {relation} relationship",
@@ -305,9 +315,12 @@ class PartOf(Facet):
"An element must have an {relation} relationship with an {entity}",
"An element must have an {relation} relationship",
]
super().__init__(entity, relation, instructions)
super().__init__(entity, relation, minOccurs, maxOccurs, instructions)
def __call__(self, inst, logger=None):
if self.minOccurs == 0 and self.maxOccurs != 0:
return PartOfResult(True)
reason = None
if self.relation == "IfcRelAggregates":
aggregate = ifcopenshell.util.element.get_aggregate(inst)
@@ -363,6 +376,9 @@ class PartOf(Facet):
nest = self.get_nested_whole(nest)
if not is_pass:
reason = {"type": "ENTITY", "actual": ancestors}
if self.maxOccurs == 0:
return PartOfResult(not is_pass, {"type": "PROHIBITED"})
return PartOfResult(is_pass, reason)
def get_nested_whole(self, element):
@@ -403,6 +419,9 @@ class Property(Facet):
super().__init__(propertySet, name, value, measure, uri, minOccurs, maxOccurs, instructions)
def __call__(self, inst, logger=None):
if self.minOccurs == 0 and self.maxOccurs != 0:
return PropertyResult(True)
all_psets = ifcopenshell.util.element.get_psets(inst)
if isinstance(self.propertySet, str):
@@ -488,6 +507,9 @@ class Property(Facet):
is_pass = False
reason = {"type": "VALUE", "actual": value}
break
if self.maxOccurs == 0:
return PropertyResult(not is_pass, {"type": "PROHIBITED"})
return PropertyResult(is_pass, reason)
@@ -505,6 +527,9 @@ class Material(Facet):
super().__init__(value, uri, minOccurs, maxOccurs, instructions)
def __call__(self, inst, logger=None):
if self.minOccurs == 0 and self.maxOccurs != 0:
return MaterialResult(True)
material = ifcopenshell.util.element.get_material(inst, should_skip_usage=True)
is_pass = material is not None
@@ -542,6 +567,8 @@ class Material(Facet):
if not is_pass:
reason = {"type": "VALUE", "actual": values}
if self.maxOccurs == 0:
return MaterialResult(not is_pass, {"type": "PROHIBITED"})
return MaterialResult(is_pass, reason)
+3 -3
View File
@@ -63,7 +63,7 @@ class DocGenerator:
basename = f"{result}-" + re.sub("[^0-9a-zA-Z]", "_", name.lower())
# Write IFC to disk
f.write(os.path.join(outdir, "testcases", f"{basename}.ifc"))
f.write(os.path.join(outdir, "testcases", self.facet, f"{basename}.ifc"))
# Create an IDS with the applicability selecting exactly
# the entity type passed to us in `inst`.
@@ -74,7 +74,7 @@ class DocGenerator:
specs.specifications.append(spec)
# Write IDS to disk
with open(os.path.join(outdir, "testcases", f"{basename}.ids"), "w", encoding="utf-8") as ids_file:
with open(os.path.join(outdir, "testcases", self.facet, f"{basename}.ids"), "w", encoding="utf-8") as ids_file:
ids_file.write(specs.to_string())
xml_text = "\n".join(
@@ -123,7 +123,7 @@ for facet, testcases in test_facet.run.testcases.items():
write("~~~")
write()
write(
f"[Sample IDS](testcases/{testcase['basename']}.ids) - [Sample IFC: {testcase['id']}](testcases/{testcase['basename']}.ifc)"
f"[Sample IDS](testcases/{facet}/{testcase['basename']}.ids) - [Sample IFC: {testcase['id']}](testcases/{facet}/{testcase['basename']}.ifc)"
)
write()
+53 -2
View File
@@ -238,7 +238,7 @@ class TestAttribute:
set_facet("attribute")
ifc = ifcopenshell.file()
facet = Attribute(name="Foobar")
facet = Attribute(name="Rabbit")
run("Invalid attribute names always fail", facet=facet, inst=ifc.createIfcWall(), expected=False)
ifc = ifcopenshell.file()
@@ -249,7 +249,20 @@ class TestAttribute:
inst=ifc.createIfcWall(Name="Foobar"),
expected=True,
)
ifc = ifcopenshell.file()
facet = Attribute(name="Name")
element = ifc.createIfcWall(Name="Foobar")
run("A required facet checks all parameters as normal", facet=facet, inst=element, expected=True)
facet = Attribute(name="Name", minOccurs=0, maxOccurs=0)
run("A prohibited facet returns the opposite of a required facet", facet=facet, inst=element, expected=False)
facet = Attribute(name="Name", minOccurs=0)
run("An optional facet always passes regardless of outcome 1/2", facet=facet, inst=element, expected=True)
facet = Attribute(name="Rabbit", minOccurs=0)
run("An optional facet always passes regardless of outcome 2/2", facet=facet, inst=element, expected=True)
ifc = ifcopenshell.file()
facet = Attribute(name="Name")
run("Attributes with null values always fail", facet=facet, inst=ifc.createIfcWall(), expected=False)
# The logic is that unfortunately most BIM users cannot differentiate between the two.
ifc = ifcopenshell.file()
@@ -724,6 +737,14 @@ class TestClassification:
expected=True,
)
run("A required facet checks all parameters as normal", facet=facet, inst=element1, expected=True)
facet = Classification(minOccurs=0, maxOccurs=0)
run("A prohibited facet returns the opposite of a required facet", facet=facet, inst=element1, expected=False)
facet = Attribute(name="Name", minOccurs=0)
run("An optional facet always passes regardless of outcome 1/2", facet=facet, inst=element0, expected=True)
facet = Attribute(name="Rabbit", minOccurs=0)
run("An optional facet always passes regardless of outcome 2/2", facet=facet, inst=element1, expected=True)
facet = Classification(value="1")
run(
"Values should match exactly if lightweight classifications are used",
@@ -859,6 +880,15 @@ class TestProperty:
run("Properties with a null value fail", facet=facet, inst=element, expected=False)
ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": "Bar"})
run("A name check will match any property with any string value", facet=facet, inst=element, expected=True)
run("A required facet checks all parameters as normal", facet=facet, inst=element, expected=True)
facet = Property(propertySet="Foo_Bar", name="Foo", measure="IfcLabel", minOccurs=0, maxOccurs=0)
run("A prohibited facet returns the opposite of a required facet", facet=facet, inst=element, expected=False)
facet = Property(propertySet="Foo_Bar", name="Foo", measure="IfcLabel", minOccurs=0)
run("An optional facet always passes regardless of outcome 1/2", facet=facet, inst=element, expected=True)
facet = Property(propertySet="Foo_Bar", name="Bar", measure="IfcLabel", minOccurs=0)
run("An optional facet always passes regardless of outcome 2/2", facet=facet, inst=element, expected=True)
ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": ""})
facet = Property(propertySet="Foo_Bar", name="Foo", measure="IfcLogical")
run("An empty string is considered falsey and will not pass", facet=facet, inst=element, expected=False)
@@ -1091,6 +1121,14 @@ class TestMaterial:
ifcopenshell.api.run("material.assign_material", ifc, product=element, material=material)
run("Elements with any material will pass an empty material facet", facet=facet, inst=element, expected=True)
run("A required facet checks all parameters as normal", facet=facet, inst=element, expected=True)
facet = Material(minOccurs=0, maxOccurs=0)
run("A prohibited facet returns the opposite of a required facet", facet=facet, inst=element, expected=False)
facet = Material(minOccurs=0)
run("An optional facet always passes regardless of outcome 1/2", facet=facet, inst=element, expected=True)
facet = Material(value="Foo", minOccurs=0)
run("An optional facet always passes regardless of outcome 1/2", facet=facet, inst=element, expected=True)
ifc = ifcopenshell.file()
facet = Material(value="Foo")
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall")
@@ -1221,10 +1259,18 @@ class TestPartOf:
def test_creating_a_partof_facet(self):
facet = PartOf()
assert facet.asdict() == {"@relation": "IfcRelAggregates"}
facet = PartOf(entity="IfcGroup", relation="IfcRelAssignsToGroup", instructions="instructions")
facet = PartOf(
entity="IfcGroup",
relation="IfcRelAssignsToGroup",
minOccurs="0",
maxOccurs="unbounded",
instructions="instructions",
)
assert facet.asdict() == {
"entity": {"simpleValue": "IfcGroup"},
"@relation": "IfcRelAssignsToGroup",
"@minOccurs": "0",
"@maxOccurs": "unbounded",
"@instructions": "instructions",
}
@@ -1241,6 +1287,11 @@ class TestPartOf:
run("The aggregated whole fails an aggregate relationship", facet=facet, inst=element, expected=False)
run("The aggregated part passes an aggregate relationship", facet=facet, inst=subelement, expected=True)
run("A required facet checks all parameters as normal", facet=facet, inst=subelement, expected=True)
run("A prohibited facet returns the opposite of a required facet", facet=facet, inst=subelement, expected=False)
run("An optional facet always passes regardless of outcome 1/2", facet=facet, inst=element, expected=True)
run("An optional facet always passes regardless of outcome 2/2", facet=facet, inst=subelement, expected=True)
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcSlab")
subelement = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcBeam")
ifcopenshell.api.run("aggregate.assign_object", ifc, product=subelement, relating_object=element)