IDS now supports failing inverse checks

This commit is contained in:
Dion Moult
2022-06-21 17:00:46 +10:00
parent 1de6a7e792
commit 4761c58571
2 changed files with 114 additions and 82 deletions
+10 -5
View File
@@ -21,6 +21,7 @@ import re
import logging import logging
import numpy as np import numpy as np
import datetime import datetime
import builtins
import ifcopenshell.util.unit import ifcopenshell.util.unit
import ifcopenshell.util.element import ifcopenshell.util.element
import ifcopenshell.util.placement import ifcopenshell.util.placement
@@ -643,11 +644,15 @@ class attribute(facet):
is_pass = False is_pass = False
elif value == tuple(): elif value == tuple():
is_pass = False is_pass = False
elif ( else:
inst.attribute_type(inst.wrapped_data.get_argument_index(names[i])) == "LOGICAL" argument_index = inst.wrapped_data.get_argument_index(names[i])
and value == "UNKNOWN" try:
): attribute_type = inst.attribute_type(argument_index)
is_pass = False if attribute_type == "LOGICAL" and value == "UNKNOWN":
is_pass = False
except:
if names[i] in inst.wrapped_data.get_inverse_attribute_names():
is_pass = False
if not is_pass: if not is_pass:
break break
+104 -77
View File
@@ -381,19 +381,16 @@ class TestIdsAuthoring(unittest.TestCase):
facet = ids.entity.create(name="IFCWALL", predefinedType="X") facet = ids.entity.create(name="IFCWALL", predefinedType="X")
run("Overridden predefined types should pass", facet=facet, inst=wall, expected=True) run("Overridden predefined types should pass", facet=facet, inst=wall, expected=True)
# Restrictions are allowed when matching the IFC class
restriction = ids.restriction.create(options=["IFCWALL", "IFCSLAB"], type="enumeration") restriction = ids.restriction.create(options=["IFCWALL", "IFCSLAB"], type="enumeration")
facet = ids.entity.create(name=restriction) facet = ids.entity.create(name=restriction)
run("Entities can be specified as an enumeration 1/3", facet=facet, inst=ifc.createIfcWall(), expected=True) run("Entities can be specified as an enumeration 1/3", facet=facet, inst=ifc.createIfcWall(), expected=True)
run("Entities can be specified as an enumeration 2/3", facet=facet, inst=ifc.createIfcSlab(), expected=True) run("Entities can be specified as an enumeration 2/3", facet=facet, inst=ifc.createIfcSlab(), expected=True)
run("Entities can be specified as an enumeration 3/3", facet=facet, inst=ifc.createIfcBeam(), expected=False) run("Entities can be specified as an enumeration 3/3", facet=facet, inst=ifc.createIfcBeam(), expected=False)
# Another example of how restrictions are allowed when matching the IFC class
# Note: Regex patterns follow the XSD flavour
restriction = ids.restriction.create(options="IFC.*TYPE", type="pattern") restriction = ids.restriction.create(options="IFC.*TYPE", type="pattern")
facet = ids.entity.create(name=restriction) facet = ids.entity.create(name=restriction)
run("Entities can be specified as a pattern 1/2", facet=facet, inst=ifc.createIfcWall(), expected=False) run("Entities can be specified as a XSD regex pattern 1/2", facet=facet, inst=ifc.createIfcWall(), expected=False)
run("Entities can be specified as a pattern 2/2", facet=facet, inst=ifc.createIfcWallType(), expected=True) run("Entities can be specified as a XSD regex pattern 2/2", facet=facet, inst=ifc.createIfcWallType(), expected=True)
restriction = ids.restriction.create(options="FOO.*", type="pattern") restriction = ids.restriction.create(options="FOO.*", type="pattern")
facet = ids.entity.create(name="IFCWALL", predefinedType=restriction) facet = ids.entity.create(name="IFCWALL", predefinedType=restriction)
@@ -462,87 +459,123 @@ class TestIdsAuthoring(unittest.TestCase):
element = ifc.createIfcTask(IsMilestone=True, TaskTime=ifc.createIfcTaskTime()) element = ifc.createIfcTask(IsMilestone=True, TaskTime=ifc.createIfcTaskTime())
run("Attributes referencing an object should pass", facet=facet, inst=element, expected=True) run("Attributes referencing an object should pass", facet=facet, inst=element, expected=True)
# Selects are only considered for truthy or falsey
facet = ids.attribute.create(name="DiffuseColour") facet = ids.attribute.create(name="DiffuseColour")
element = ifc.createIfcSurfaceStyleRendering() element = ifc.createIfcSurfaceStyleRendering()
rgb = ifc.createIfcColourRgb(None, 1, 1, 1) rgb = ifc.createIfcColourRgb(None, 1, 1, 1)
run("Attributes with a select referencing an object should pass", facet=facet, inst=ifc.createIfcSurfaceStyleRendering(SurfaceColour=rgb, ReflectanceMethod="FLAT", DiffuseColour=ifc.createIfcColourRgb(None, 1, 1, 1)), expected=True) run("Attributes with a select referencing an object should pass", facet=facet, inst=ifc.createIfcSurfaceStyleRendering(SurfaceColour=rgb, ReflectanceMethod="FLAT", DiffuseColour=ifc.createIfcColourRgb(None, 1, 1, 1)), expected=True)
run("Attributes with a select referencing a primitive should pass", facet=facet, inst=ifc.createIfcSurfaceStyleRendering(SurfaceColour=rgb, ReflectanceMethod="FLAT", DiffuseColour=ifc.createIfcNormalisedRatioMeasure(0.5)), expected=True) run("Attributes with a select referencing a primitive should pass", facet=facet, inst=ifc.createIfcSurfaceStyleRendering(SurfaceColour=rgb, ReflectanceMethod="FLAT", DiffuseColour=ifc.createIfcNormalisedRatioMeasure(0.5)), expected=True)
# When a value is specified, the value shall match case sensitively facet = ids.attribute.create(name="EngagedIn")
person = ifc.createIfcPerson()
organisation = ifc.createIfcOrganization(Name="Foo")
ifc.createIfcPersonAndOrganization(ThePerson=person, TheOrganization=organisation)
run("Inverse attributes cannot be checked and always fail", facet=facet, inst=person, expected=False)
facet = ids.attribute.create(name="Dim")
run("Derived attributes cannot be checked and always fail", facet=facet, inst=ifc.createIfcCartesianPoint([0., 0., 0.]), expected=False)
facet = ids.attribute.create(name="Name", value="Foobar") facet = ids.attribute.create(name="Name", value="Foobar")
run("Attributes should check strings case sensitively 1/2", facet=facet, inst=ifc.createIfcWall(Name="Foobar"), expected=True) run("Attributes should check strings case sensitively 1/2", facet=facet, inst=ifc.createIfcWall(Name="Foobar"), expected=True)
run("Attributes should check strings case sensitively 2/2", facet=facet, inst=ifc.createIfcWall(Name="foobar"), expected=False) run("Attributes should check strings case sensitively 2/2", facet=facet, inst=ifc.createIfcWall(Name="foobar"), expected=False)
# Value checks are meaningless for checking objects, selects, and lists so it always fails facet = ids.attribute.create(name="TaskTime", value="Foobar")
facet = ids.attribute.create(name="OwnerHistory", value="Foobar") run("Value checks always fail for objects", facet=facet, inst=ifc.createIfcTask(IsMilestone=False, TaskTime=ifc.createIfcTaskTime()), expected=False)
assert bool(facet(ifc.createIfcWall(OwnerHistory=ifc.createIfcOwnerHistory()))) is False
# Selects rgb = ifc.createIfcColourRgb(None, 1, 1, 1)
facet = ids.attribute.create(name="DiffuseColour", value="Foobar")
run("Value checks always fail for selects", facet=facet, inst=ifc.createIfcSurfaceStyleRendering(SurfaceColour=rgb, ReflectanceMethod="FLAT", DiffuseColour=ifc.createIfcNormalisedRatioMeasure(0.5)), expected=False)
# Lists facet = ids.attribute.create(name="Coordinates", value="Foobar")
run("Value checks always fail for lists", facet=facet, inst=ifc.createIfcCartesianPoint([0., 0., 0.]), expected=False)
# TODO continue from here # TODO continue from here
# TODO should we be allowed to check inverse attributes? global_id = ifcopenshell.guid.new()
# Nope - always false facet = ids.attribute.create(name="GlobalId", value=global_id)
run("GlobalIds are treated as strings and not expanded", facet=facet, inst=ifc.createIfcWall(GlobalId=global_id), expected=True)
# TODO should we be allowed to check derived attributes? # 255 characters
# Nope - always false identifier = "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345"
facet = ids.attribute.create(name="Identification", value=identifier + "_extra_characters")
run("IDS does not bother with string truncation such as for identifiers", facet=facet, inst=ifc.createIfcPerson(Identification=identifier), expected=False)
# Simple values which must be strings are checked with strict typing. No type casting shall occur. facet = ids.attribute.create(name="RefractionIndex", value="42")
facet = ids.attribute.create(name="Eastings", value="42") run("Numeric values are checked using type casting 1/4", facet=facet, inst=ifc.createIfcSurfaceStyleRefraction(RefractionIndex=42), expected=True)
run("", facet=facet, inst=ifc.createIfcMapConversion(Eastings=0), expected=False) facet = ids.attribute.create(name="RefractionIndex", value="42.")
run("", facet=facet, inst=ifc.createIfcMapConversion(Eastings=42), expected=False) run("Numeric values are checked using type casting 2/4", facet=facet, inst=ifc.createIfcSurfaceStyleRefraction(RefractionIndex=42.0), expected=True)
facet = ids.attribute.create(name="RefractionIndex", value="42.0")
run("Numeric values are checked using type casting 3/4", facet=facet, inst=ifc.createIfcSurfaceStyleRefraction(RefractionIndex=42.0), expected=True)
facet = ids.attribute.create(name="RefractionIndex", value="42")
run("Numeric values are checked using type casting 4/4", facet=facet, inst=ifc.createIfcSurfaceStyleRefraction(RefractionIndex=42.3), expected=False)
facet = ids.attribute.create(name="RefractionIndex", value="42,3")
run("Only specifically formatted numbers are allowed 1/4", facet=facet, inst=ifc.createIfcSurfaceStyleRefraction(RefractionIndex=42.3), expected=False)
facet = ids.attribute.create(name="RefractionIndex", value="123,4.5")
run("Only specifically formatted numbers are allowed 2/4", facet=facet, inst=ifc.createIfcSurfaceStyleRefraction(RefractionIndex=1234.5), expected=False)
facet = ids.attribute.create(name="RefractionIndex", value="1.2345e3")
run("Only specifically formatted numbers are allowed 3/4", facet=facet, inst=ifc.createIfcSurfaceStyleRefraction(RefractionIndex=1234.5), expected=True)
facet = ids.attribute.create(name="RefractionIndex", value="1.2345E3")
run("Only specifically formatted numbers are allowed 3/4", facet=facet, inst=ifc.createIfcSurfaceStyleRefraction(RefractionIndex=1234.5), expected=True)
# Strict type checking is done with restrictions. This is really ugly, but hopefully hidden to a user. facet = ids.attribute.create(name="NumberOfRisers", value="42")
restriction = ids.restriction.create(options=[42], type="enumeration", base="decimal") run("Integers follow the same rules as numbers", facet=facet, inst=ifc.createIfcStairFlight(NumberOfRisers=42), expected=True)
# Here is another uglier option: facet = ids.attribute.create(name="NumberOfRisers", value="42.0")
# restriction = ids.restriction.create( run("Integers follow the same rules as numbers 2/2", facet=facet, inst=ifc.createIfcStairFlight(NumberOfRisers=42), expected=True)
# options={"minInclusive": 42, "maxInclusive": 42}, type="bounds", base="decimal"
# ) facet = ids.attribute.create(name="NumberOfRisers", value="42.3")
facet = ids.attribute.create(name="Eastings", value=restriction) run("Integers are always floored when cast 1/2", facet=facet, inst=ifc.createIfcStairFlight(NumberOfRisers=42), expected=True)
run("", facet=facet, inst=ifc.createIfcMapConversion(Eastings=42), expected=True) facet = ids.attribute.create(name="NumberOfRisers", value="42.7")
run("Integers are always floored when cast 2/2", facet=facet, inst=ifc.createIfcStairFlight(NumberOfRisers=42), expected=True)
facet = ids.attribute.create(name="NumberOfRisers", value="42.7")
run("Integers are always floored when cast 2/2", facet=facet, inst=ifc.createIfcStairFlight(NumberOfRisers=42), expected=True)
facet = ids.attribute.create(name="IsMilestone", value="TRUE")
element = ifc.createIfcTask(IsMilestone=False)
run("Booleans must be specified as uppercase strings 1/3", facet=facet, inst=element, expected=False)
facet = ids.attribute.create(name="IsMilestone", value="FALSE")
run("Booleans must be specified as uppercase strings 2/3", facet=facet, inst=element, expected=True)
facet = ids.attribute.create(name="IsMilestone", value="False")
run("Booleans must be specified as uppercase strings 2/3", facet=facet, inst=element, expected=False)
facet = ids.attribute.create(name="EditionDate", value="2022-01-01")
run("Dates are treated as strings 1/2", facet=facet, inst=ifc.createIfcClassification(Name="Name", EditionDate="2022-01-01"), expected=True)
run("Dates are treated as strings 1/2", facet=facet, inst=ifc.createIfcClassification(Name="Name", EditionDate="2022-01-01+00:00"), expected=False)
facet = ids.attribute.create(name="ScheduleDuration", value="PT16H")
run("Durations are treated as strings 1/2", facet=facet, inst=ifc.createIfcClassification(Name="Name", EditionDate="PT16H"), expected=False)
run("Durations are treated as strings 2/2", facet=facet, inst=ifc.createIfcClassification(Name="Name", EditionDate="P2D"), expected=False)
# Restrictions are allowed for the name
restriction = ids.restriction.create(options=".*Name.*", type="pattern") restriction = ids.restriction.create(options=".*Name.*", type="pattern")
facet = ids.attribute.create(name=restriction) facet = ids.attribute.create(name=restriction)
run("", facet=facet, inst=ifc.createIfcMaterialLayerSet(LayerSetName="Foo"), expected=True) run("Name restrictions may be used 1/4", facet=facet, inst=ifc.createIfcMaterialLayerSet(MaterialLayers=[ifc.createIfcMaterialLayer(LayerThickness=1)], LayerSetName="Foo"), expected=True)
run("", facet=facet, inst=ifc.createIfcMaterialConstituentSet(Name="Foo"), expected=True) run("Name restrictions may be used 2/4", facet=facet, inst=ifc.createIfcMaterialConstituentSet(Name="Foo"), expected=True)
# Restrictions for the name imply that multiple names may be matched. All, not any, must pass the checks.
restriction = ids.restriction.create(options=["Name", "Description"], type="enumeration") restriction = ids.restriction.create(options=["Name", "Description"], type="enumeration")
facet = ids.attribute.create(name=restriction) facet = ids.attribute.create(name=restriction)
run("", facet=facet, inst=ifc.createIfcWall(Name="Foo"), expected=False) run("Name restrictions may be used 3/4", facet=facet, inst=ifc.createIfcWall(Name="Foo"), expected=False)
run("", facet=facet, inst=ifc.createIfcWall(Name="Foo", Description="Bar"), expected=True) run("Name restrictions may be used 4/4", facet=facet, inst=ifc.createIfcWall(Name="Foo", Description="Bar"), expected=True)
# Restrictions are allowed for the value
restriction = ids.restriction.create(options=["Foo", "Bar"], type="enumeration") restriction = ids.restriction.create(options=["Foo", "Bar"], type="enumeration")
facet = ids.attribute.create(name="Name", value=restriction) facet = ids.attribute.create(name="Name", value=restriction)
run("", facet=facet, inst=ifc.createIfcWall(Name="Foo"), expected=True) run("Value restrictions may be used 1/3", facet=facet, inst=ifc.createIfcWall(Name="Foo"), expected=True)
run("", facet=facet, inst=ifc.createIfcWall(Name="Bar"), expected=True) run("Value restrictions may be used 2/3", facet=facet, inst=ifc.createIfcWall(Name="Bar"), expected=True)
run("", facet=facet, inst=ifc.createIfcWall(Name="Foobar"), expected=False) run("Value restrictions may be used 3/3", facet=facet, inst=ifc.createIfcWall(Name="Foobar"), expected=False)
# The facet checks on attributes on the type, which may be inherited by the occurence
# TODO attribute inheritance is not defined IFC behaviour
facet = ids.attribute.create(name="Description", value="Foobar")
run("", facet=facet, inst=ifc.createIfcWall(Description="Foobar"), expected=True)
wall = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") wall = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall")
wall_type = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWallType") 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("type.assign_type", ifc, related_object=wall, relating_type=wall_type)
wall_type.Description = "Foobar" wall_type.Description = "Foobar"
run("", facet=facet, inst=wall, expected=True) run("Attributes are not inherited by the occurrence", facet=facet, inst=wall, expected=False)
# The facet checks on attributes on the type, which may be overriden by attributes on the occurence restriction = ids.restriction.create(options=["42"], type="enumeration", base="string")
# TODO attribute overriding is not defined IFC behaviour facet = ids.attribute.create(name="RefractionIndex", value=restriction)
facet = ids.attribute.create(name="Description", value="Foobar") run("Typecast checking may also occur within enumeration restrictions", facet=facet, inst=ifc.createIfcSurfaceStyleRefraction(RefractionIndex=42), expected=True)
run("", facet=facet, inst=ifc.createIfcWall(Description="Foobar"), expected=True)
wall = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") restriction = ids.restriction.create(
wall_type = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWallType") options={"minInclusive": 42, "maxInclusive": 42}, type="bounds", base="decimal"
ifcopenshell.api.run("type.assign_type", ifc, related_object=wall, relating_type=wall_type) )
wall_type.Description = "Foobaz" facet = ids.attribute.create(name="RefractionIndex", value=restriction)
wall.Description = "Foobar" run("Strict numeric checking may be done with a bounds restriction", facet=facet, inst=ifc.createIfcSurfaceStyleRefraction(RefractionIndex=42), expected=True)
run("", facet=facet, inst=wall, expected=True)
def test_creating_a_classification_facet(self): def test_creating_a_classification_facet(self):
facet = ids.classification.create() facet = ids.classification.create()
@@ -567,6 +600,8 @@ class TestIdsAuthoring(unittest.TestCase):
} }
def test_filtering_using_a_classification_facet(self): def test_filtering_using_a_classification_facet(self):
set_facet("classification")
library = ifcopenshell.file() library = ifcopenshell.file()
system = library.createIfcClassification(Name="Foobar") system = library.createIfcClassification(Name="Foobar")
ref1 = library.createIfcClassificationReference(Identification="1", ReferencedSource=system) ref1 = library.createIfcClassificationReference(Identification="1", ReferencedSource=system)
@@ -596,45 +631,37 @@ class TestIdsAuthoring(unittest.TestCase):
is_lightweight=False, is_lightweight=False,
) )
# A classification facet with no data matches any present classification
facet = ids.classification.create() facet = ids.classification.create()
run("", facet=facet, inst=element0, expected=False) run("A classification facet with no data matches any classification 1/2", facet=facet, inst=element0, expected=False)
run("", facet=facet, inst=element1, expected=True) run("A classification facet with no data matches any classification 2/2", facet=facet, inst=element1, expected=True)
# Values should match exactly if lightweight classifications are used.
facet = ids.classification.create(value="1") facet = ids.classification.create(value="1")
run("", facet=facet, inst=element1, expected=True) run("Values should match exactly if lightweight classifications are used", facet=facet, inst=element1, expected=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") facet = ids.classification.create(value="2")
run("", facet=facet, inst=element22, expected=True) run("Values match subreferences if full classifications are used (e.g. EF_25_10 should match EF_25_10_25, EF_25_10_30, etc)", facet=facet, inst=element22, expected=True)
# Systems should match exactly regardless of lightweight or full classifications
facet = ids.classification.create(system="Foobar") facet = ids.classification.create(system="Foobar")
run("", facet=facet, inst=project, expected=True) run("Systems should match exactly 1/5", facet=facet, inst=project, expected=True)
run("", facet=facet, inst=element0, expected=False) run("Systems should match exactly 2/5", facet=facet, inst=element0, expected=False)
run("", facet=facet, inst=element1, expected=True) run("Systems should match exactly 3/5", facet=facet, inst=element1, expected=True)
run("", facet=facet, inst=element11, expected=True) run("Systems should match exactly 4/5", facet=facet, inst=element11, expected=True)
run("", facet=facet, inst=element22, expected=True) run("Systems should match exactly 5/5", facet=facet, inst=element22, expected=True)
# Restrictions can be used for values
restriction = ids.restriction.create(options="1.*", type="pattern") restriction = ids.restriction.create(options="1.*", type="pattern")
facet = ids.classification.create(value=restriction) facet = ids.classification.create(value=restriction)
run("", facet=facet, inst=element1, expected=True) run("Restrictions can be used for values 1/3", facet=facet, inst=element1, expected=True)
run("", facet=facet, inst=element11, expected=True) run("Restrictions can be used for values 2/3", facet=facet, inst=element11, expected=True)
run("", facet=facet, inst=element22, expected=False) run("Restrictions can be used for values 3/3", facet=facet, inst=element22, expected=False)
# Restrictions can be used for systems
restriction = ids.restriction.create(options="Foo.*", type="pattern") restriction = ids.restriction.create(options="Foo.*", type="pattern")
facet = ids.classification.create(system=restriction) facet = ids.classification.create(system=restriction)
run("", facet=facet, inst=element0, expected=False) run("Restrictions can be used for systems 1/2", facet=facet, inst=element0, expected=False)
run("", facet=facet, inst=element1, expected=True) run("Restrictions can be used for systems 2/2", facet=facet, inst=element1, expected=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") facet = ids.classification.create(system="Foobar", value="1")
run("", facet=facet, inst=element1, expected=True) run("Both system and value must match (all, not any) if specified 1/2", facet=facet, inst=element1, expected=True)
run("", facet=facet, inst=element11, expected=False) run("Both system and value must match (all, not any) if specified 2/2", facet=facet, inst=element11, expected=False)
# The facet checks on either the type or instance. # The facet checks on either the type or instance.
# IFC doesn't specify how inheritance and overrides work here. Two options: # IFC doesn't specify how inheritance and overrides work here. Two options: