mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-11 02:02:22 +00:00
IfcTester now supports occurs attributes on facets
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user