mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-22 07:28:38 +00:00
Add IDS support for restrictions in attribute facets and handle inheritance
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
# IDS - Information Delivery Specification.
|
# IDS - Information Delivery Specification.
|
||||||
# Copyright (C) 2021 Artur Tomczak <artomczak@gmail.com>, Thomas Krijnen <mail@thomaskrijnen.com>
|
# Copyright (C) 2021 Artur Tomczak <artomczak@gmail.com>, Thomas Krijnen <mail@thomaskrijnen.com>, Dion Moult <dion@thinkmoult.com>
|
||||||
#
|
#
|
||||||
# This file is part of IfcOpenShell.
|
# This file is part of IfcOpenShell.
|
||||||
#
|
#
|
||||||
@@ -211,7 +211,13 @@ class specification:
|
|||||||
"""Represents the XML <specification> node and its two children <applicability> and <requirements>"""
|
"""Represents the XML <specification> node and its two children <applicability> and <requirements>"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, name="Unnamed", use="required", ifcVersion=["IFC2X3", "IFC4"], identifier=None, description=None, instructions=None
|
self,
|
||||||
|
name="Unnamed",
|
||||||
|
use="required",
|
||||||
|
ifcVersion=["IFC2X3", "IFC4"],
|
||||||
|
identifier=None,
|
||||||
|
description=None,
|
||||||
|
instructions=None,
|
||||||
):
|
):
|
||||||
"""Create a specification to be added in ids.
|
"""Create a specification to be added in ids.
|
||||||
|
|
||||||
@@ -509,19 +515,21 @@ class entity(facet):
|
|||||||
:rtype: facet_evaluation(bool, str)
|
:rtype: facet_evaluation(bool, str)
|
||||||
"""
|
"""
|
||||||
if isinstance(self.name, str):
|
if isinstance(self.name, str):
|
||||||
is_class = inst.is_a().lower() == self.name.lower()
|
is_pass = inst.is_a().lower() == self.name.lower()
|
||||||
else:
|
else:
|
||||||
is_class = inst.is_a() == self.name
|
is_pass = inst.is_a() == self.name
|
||||||
if self.predefinedType:
|
if is_pass and self.predefinedType:
|
||||||
predefined_type = ifcopenshell.util.element.get_predefined_type(inst)
|
predefined_type = ifcopenshell.util.element.get_predefined_type(inst)
|
||||||
|
is_pass = predefined_type == 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_class and predefined_type == self.predefinedType,
|
is_pass, self.message % {"name": inst.is_a(), "predefinedType": predefined_type}
|
||||||
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_class, self.message % {"name": inst.is_a()})
|
return facet_evaluation(is_pass, self.message % {"name": inst.is_a()})
|
||||||
|
|
||||||
|
|
||||||
class attribute(facet):
|
class attribute(facet):
|
||||||
@@ -573,7 +581,7 @@ class attribute(facet):
|
|||||||
return fac_dict
|
return fac_dict
|
||||||
|
|
||||||
def __call__(self, inst, logger=None):
|
def __call__(self, inst, logger=None):
|
||||||
"""Validate an ifc instance against that entity facet.
|
"""Validate an ifc instance.
|
||||||
|
|
||||||
:param inst: IFC entity element
|
:param inst: IFC entity element
|
||||||
:type inst: IFC entity
|
:type inst: IFC entity
|
||||||
@@ -582,19 +590,40 @@ 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)
|
||||||
"""
|
"""
|
||||||
element_type = ifcopenshell.util.element.get_type(inst)
|
def get_values(element, name):
|
||||||
|
if isinstance(name, str):
|
||||||
|
return [getattr(element, name, None)]
|
||||||
|
return [v for k, v in element.get_info().items() if k == name]
|
||||||
|
|
||||||
if self.location == "instance":
|
if self.location == "instance":
|
||||||
value = getattr(inst, self.name, None)
|
values = get_values(inst, self.name)
|
||||||
elif self.location == "type":
|
elif self.location == "type":
|
||||||
value = getattr(element_type, self.name, None) if element_type else None
|
element_type = ifcopenshell.util.element.get_type(inst)
|
||||||
|
values = get_values(element_type, self.name) if element_type else []
|
||||||
elif self.location == "any":
|
elif self.location == "any":
|
||||||
value = getattr(element_type, self.name, None) if element_type else None
|
element_type = ifcopenshell.util.element.get_type(inst)
|
||||||
value = getattr(inst, self.name, value)
|
|
||||||
|
if isinstance(self.name, str):
|
||||||
|
type_value = getattr(element_type, self.name, None) if element_type else None
|
||||||
|
occurrence_value = getattr(inst, self.name, None)
|
||||||
|
values = [occurrence_value if occurrence_value is not None else type_value]
|
||||||
|
else:
|
||||||
|
if element_type:
|
||||||
|
info = element_type.get_info()
|
||||||
|
info.update({k: v for k, v in inst.get_info().items() if v is not None})
|
||||||
|
else:
|
||||||
|
info = inst.get_info()
|
||||||
|
values = [v for k, v in info.items() if k == self.name]
|
||||||
|
|
||||||
|
is_pass = bool(values) and all([v is not None and v != "" for v in values])
|
||||||
|
if is_pass and self.value:
|
||||||
|
is_pass = all([v == self.value for v in values])
|
||||||
|
|
||||||
if self.value:
|
if self.value:
|
||||||
self.message = "foo"
|
self.message = "foo"
|
||||||
return facet_evaluation(value == self.value, f"an entity with {self.name} set to '{value}'")
|
return facet_evaluation(is_pass, f"an entity with {self.name} set to something wrong")
|
||||||
else:
|
else:
|
||||||
return facet_evaluation(value is not None and value != "", f"an entity with {self.name}")
|
return facet_evaluation(is_pass, f"an entity with {self.name}")
|
||||||
|
|
||||||
|
|
||||||
class classification(facet):
|
class classification(facet):
|
||||||
|
|||||||
@@ -22,8 +22,9 @@ import logging
|
|||||||
import unittest
|
import unittest
|
||||||
import tempfile
|
import tempfile
|
||||||
import xmlschema
|
import xmlschema
|
||||||
from bcf import bcfxml
|
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
|
import ifcopenshell.api
|
||||||
|
from bcf import bcfxml
|
||||||
from ifcopenshell import ids
|
from ifcopenshell import ids
|
||||||
|
|
||||||
|
|
||||||
@@ -275,42 +276,77 @@ class TestIdsAuthoring(unittest.TestCase):
|
|||||||
def test_filtering_using_an_entity_facet(self):
|
def test_filtering_using_an_entity_facet(self):
|
||||||
ifc = ifcopenshell.file()
|
ifc = ifcopenshell.file()
|
||||||
|
|
||||||
|
# Wrong IFC classes are never matched.
|
||||||
|
facet = ids.entity.create(name="IfcRabbit")
|
||||||
|
assert bool(facet(ifc.createIfcWall())) is False
|
||||||
|
|
||||||
|
# IFC class is checked for an exact match. Subclasses should not match.
|
||||||
facet = ids.entity.create(name="IfcWall")
|
facet = ids.entity.create(name="IfcWall")
|
||||||
assert bool(facet(ifc.createIfcWall())) is True
|
assert bool(facet(ifc.createIfcWall())) is True
|
||||||
assert bool(facet(ifc.createIfcWall(PredefinedType="SOLIDWALL"))) is True
|
assert bool(facet(ifc.createIfcWall(PredefinedType="SOLIDWALL"))) is True
|
||||||
assert bool(facet(ifc.createIfcSlab())) is False
|
assert bool(facet(ifc.createIfcSlab())) is False
|
||||||
assert bool(facet(ifc.createIfcWallStandardCase())) is False
|
assert bool(facet(ifc.createIfcWallStandardCase())) is False
|
||||||
|
|
||||||
|
# IFC class is case insensitive.
|
||||||
facet = ids.entity.create(name="IFCWALL")
|
facet = ids.entity.create(name="IFCWALL")
|
||||||
assert bool(facet(ifc.createIfcWall())) is True
|
assert bool(facet(ifc.createIfcWall())) is True
|
||||||
assert bool(facet(ifc.createIfcWall(PredefinedType="SOLIDWALL"))) is True
|
assert bool(facet(ifc.createIfcWall(PredefinedType="SOLIDWALL"))) is True
|
||||||
assert bool(facet(ifc.createIfcSlab())) is False
|
assert bool(facet(ifc.createIfcSlab())) is False
|
||||||
|
|
||||||
|
# Predefined types are checked from standard enumeration values
|
||||||
facet = ids.entity.create(name="IfcWall", predefinedType="SOLIDWALL")
|
facet = ids.entity.create(name="IfcWall", predefinedType="SOLIDWALL")
|
||||||
assert bool(facet(ifc.createIfcWall())) is False
|
assert bool(facet(ifc.createIfcWall())) is False
|
||||||
assert bool(facet(ifc.createIfcWall(PredefinedType="SOLIDWALL"))) is True
|
assert bool(facet(ifc.createIfcWall(PredefinedType="SOLIDWALL"))) is True
|
||||||
assert bool(facet(ifc.createIfcWall(PredefinedType="PARTITIONING"))) is False
|
assert bool(facet(ifc.createIfcWall(PredefinedType="PARTITIONING"))) is False
|
||||||
|
|
||||||
|
# Predefined types are checked from the object type field if not standard
|
||||||
facet = ids.entity.create(name="IfcWall", predefinedType="WALDO")
|
facet = ids.entity.create(name="IfcWall", predefinedType="WALDO")
|
||||||
assert bool(facet(ifc.createIfcWall(PredefinedType="USERDEFINED", ObjectType="WALDO"))) is True
|
assert bool(facet(ifc.createIfcWall(PredefinedType="USERDEFINED", ObjectType="WALDO"))) is True
|
||||||
|
|
||||||
|
# Predefined types are checked from the element type field if not standard for types
|
||||||
facet = ids.entity.create(name="IfcWallType", predefinedType="WALDO")
|
facet = ids.entity.create(name="IfcWallType", predefinedType="WALDO")
|
||||||
assert bool(facet(ifc.createIfcWallType(PredefinedType="USERDEFINED", ElementType="WALDO"))) is True
|
assert bool(facet(ifc.createIfcWallType(PredefinedType="USERDEFINED", ElementType="WALDO"))) is True
|
||||||
|
|
||||||
|
# Userdefined is not an allowed filter because userdefined implies a specified object or element type
|
||||||
facet = ids.entity.create(name="IfcWall", predefinedType="USERDEFINED")
|
facet = ids.entity.create(name="IfcWall", predefinedType="USERDEFINED")
|
||||||
assert bool(facet(ifc.createIfcWall(PredefinedType="USERDEFINED", ObjectType="WALDO"))) is False
|
assert bool(facet(ifc.createIfcWall(PredefinedType="USERDEFINED", ObjectType="WALDO"))) is False
|
||||||
|
|
||||||
|
# Predefined types should match inherited predefined types from the element type
|
||||||
|
wall = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall")
|
||||||
|
wall_type = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWallType", predefined_type="X")
|
||||||
|
ifcopenshell.api.run("type.assign_type", ifc, related_object=wall, relating_type=wall_type)
|
||||||
|
facet = ids.entity.create(name="IfcWall", predefinedType="X")
|
||||||
|
assert bool(facet(wall)) is True
|
||||||
|
|
||||||
|
# Predefined types should match overridden predefined types from the element type
|
||||||
|
wall = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall", predefined_type="X")
|
||||||
|
wall_type = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWallType", predefined_type="NOTDEFINED")
|
||||||
|
ifcopenshell.api.run("type.assign_type", ifc, related_object=wall, relating_type=wall_type)
|
||||||
|
facet = ids.entity.create(name="IfcWall", predefinedType="X")
|
||||||
|
assert bool(facet(wall)) is True
|
||||||
|
|
||||||
|
# Restrictions are allowed when matching the IFC class
|
||||||
restriction = ids.restriction.create(options=["IfcWall", "IfcSlab"], type="enumeration", base="string")
|
restriction = ids.restriction.create(options=["IfcWall", "IfcSlab"], type="enumeration", base="string")
|
||||||
facet = ids.entity.create(name=restriction)
|
facet = ids.entity.create(name=restriction)
|
||||||
assert bool(facet(ifc.createIfcWall())) is True
|
assert bool(facet(ifc.createIfcWall())) is True
|
||||||
assert bool(facet(ifc.createIfcSlab())) is True
|
assert bool(facet(ifc.createIfcSlab())) is True
|
||||||
assert bool(facet(ifc.createIfcBeam())) is False
|
assert bool(facet(ifc.createIfcBeam())) is False
|
||||||
|
|
||||||
|
# Another example of how restrictions are allowed when matching the IFC class
|
||||||
restriction = ids.restriction.create(options="Ifc.*Type", type="pattern", base="string")
|
restriction = ids.restriction.create(options="Ifc.*Type", type="pattern", base="string")
|
||||||
facet = ids.entity.create(name=restriction)
|
facet = ids.entity.create(name=restriction)
|
||||||
assert bool(facet(ifc.createIfcWall())) is False
|
assert bool(facet(ifc.createIfcWall())) is False
|
||||||
assert bool(facet(ifc.createIfcWallType())) is True
|
assert bool(facet(ifc.createIfcWallType())) is True
|
||||||
|
|
||||||
|
# Restrictions are allowed when matching the predefined type
|
||||||
|
restriction = ids.restriction.create(options="FOO.*", type="pattern", base="string")
|
||||||
|
facet = ids.entity.create(name="IfcWall", predefinedType=restriction)
|
||||||
|
wall = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall", predefined_type="FOOBAR")
|
||||||
|
wall2 = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall", predefined_type="FOOBAZ")
|
||||||
|
assert bool(facet(wall)) is True
|
||||||
|
assert bool(facet(wall2)) is True
|
||||||
|
|
||||||
|
|
||||||
def test_creating_an_attribute_facet(self):
|
def test_creating_an_attribute_facet(self):
|
||||||
attribute = ids.attribute.create(name="name")
|
attribute = ids.attribute.create(name="name")
|
||||||
assert attribute.asdict() == {"name": {"simpleValue": "name"}, "@location": "any"}
|
assert attribute.asdict() == {"name": {"simpleValue": "name"}, "@location": "any"}
|
||||||
@@ -332,32 +368,90 @@ class TestIdsAuthoring(unittest.TestCase):
|
|||||||
def test_filtering_using_an_attribute_facet(self):
|
def test_filtering_using_an_attribute_facet(self):
|
||||||
ifc = ifcopenshell.file()
|
ifc = ifcopenshell.file()
|
||||||
|
|
||||||
|
# Attribute names that don't exist are never matched
|
||||||
facet = ids.attribute.create(name="Foobar")
|
facet = ids.attribute.create(name="Foobar")
|
||||||
assert bool(facet(ifc.createIfcWall())) is False
|
assert bool(facet(ifc.createIfcWall())) is False
|
||||||
|
|
||||||
|
# Attribute names that are either null or empty string are not matched.
|
||||||
|
# The logic is that unfortunately most BIM users cannot differentiate between the two.
|
||||||
facet = ids.attribute.create(name="Name")
|
facet = ids.attribute.create(name="Name")
|
||||||
assert bool(facet(ifc.createIfcWall())) is False
|
assert bool(facet(ifc.createIfcWall())) is False
|
||||||
assert bool(facet(ifc.createIfcWall(Name=""))) is False
|
assert bool(facet(ifc.createIfcWall(Name=""))) is False
|
||||||
assert bool(facet(ifc.createIfcWall(Name="Foobar"))) is True
|
assert bool(facet(ifc.createIfcWall(Name="Foobar"))) is True
|
||||||
|
|
||||||
|
# When a value is specified, the value shall match case sensitively
|
||||||
facet = ids.attribute.create(name="Name", value="Foobar")
|
facet = ids.attribute.create(name="Name", value="Foobar")
|
||||||
assert bool(facet(ifc.createIfcWall(Name="Foobar"))) is True
|
assert bool(facet(ifc.createIfcWall(Name="Foobar"))) is True
|
||||||
assert bool(facet(ifc.createIfcWall(Name="Foobaz"))) is False
|
assert bool(facet(ifc.createIfcWall(Name="Foobaz"))) is False
|
||||||
|
|
||||||
|
# A value that is 0 is still considered a value, not "null-like", so it is matched
|
||||||
facet = ids.attribute.create(name="Eastings")
|
facet = ids.attribute.create(name="Eastings")
|
||||||
assert bool(facet(ifc.createIfcMapConversion(Eastings=0))) is True
|
assert bool(facet(ifc.createIfcMapConversion(Eastings=0))) is True
|
||||||
|
|
||||||
|
# Values are checked with strict typing. No type casting shall occur.
|
||||||
facet = ids.attribute.create(name="Eastings", value=42)
|
facet = ids.attribute.create(name="Eastings", value=42)
|
||||||
assert bool(facet(ifc.createIfcMapConversion(Eastings=0))) is False
|
assert bool(facet(ifc.createIfcMapConversion(Eastings=0))) is False
|
||||||
assert bool(facet(ifc.createIfcMapConversion(Eastings=42))) is True
|
assert bool(facet(ifc.createIfcMapConversion(Eastings=42))) is True
|
||||||
|
facet = ids.attribute.create(name="Eastings", value="42")
|
||||||
|
assert bool(facet(ifc.createIfcMapConversion(Eastings=42))) is False
|
||||||
|
|
||||||
|
# Restrictions are allowed for the name
|
||||||
|
restriction = ids.restriction.create(options=".*Name.*", type="pattern", base="string")
|
||||||
|
facet = ids.attribute.create(name=restriction)
|
||||||
|
assert bool(facet(ifc.createIfcMaterialLayerSet(LayerSetName="Foo"))) is True
|
||||||
|
assert bool(facet(ifc.createIfcMaterialConstituentSet(Name="Foo"))) is 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", base="string")
|
||||||
|
facet = ids.attribute.create(name=restriction)
|
||||||
|
assert bool(facet(ifc.createIfcWall(Name="Foo"))) is False
|
||||||
|
assert bool(facet(ifc.createIfcWall(Name="Foo", Description="Bar"))) is True
|
||||||
|
|
||||||
|
# Restrictions are allowed for the value
|
||||||
restriction = ids.restriction.create(options=["Foo", "Bar"], type="enumeration", base="string")
|
restriction = ids.restriction.create(options=["Foo", "Bar"], type="enumeration", base="string")
|
||||||
facet = ids.attribute.create(name="Name", value=restriction)
|
facet = ids.attribute.create(name="Name", value=restriction)
|
||||||
assert bool(facet(ifc.createIfcWall(Name="Foo"))) is True
|
assert bool(facet(ifc.createIfcWall(Name="Foo"))) is True
|
||||||
assert bool(facet(ifc.createIfcWall(Name="Bar"))) is True
|
assert bool(facet(ifc.createIfcWall(Name="Bar"))) is True
|
||||||
assert bool(facet(ifc.createIfcWall(Name="Foobar"))) is False
|
assert bool(facet(ifc.createIfcWall(Name="Foobar"))) is False
|
||||||
|
|
||||||
def test_classification_create(self):
|
# Location instance only checks on the instance, this seems like intuitive behaviour
|
||||||
|
facet = ids.attribute.create(name="Name", value="Foobar", location="instance")
|
||||||
|
assert bool(facet(ifc.createIfcWall(Name="Foobar"))) is True
|
||||||
|
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)
|
||||||
|
wall_type.Name = "Foobar"
|
||||||
|
assert bool(facet(wall)) is False
|
||||||
|
|
||||||
|
# Location type only checks on the type. This seems a bit weird honestly.
|
||||||
|
facet = ids.attribute.create(name="Name", value="Foobar", location="type")
|
||||||
|
assert bool(facet(ifc.createIfcWall(Name="Foobar"))) is False
|
||||||
|
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)
|
||||||
|
wall_type.Name = "Foobar"
|
||||||
|
assert bool(facet(wall)) is True
|
||||||
|
|
||||||
|
# Location any checks on attributes on the type, which may be inherited by the occurence
|
||||||
|
facet = ids.attribute.create(name="Description", value="Foobar", location="any")
|
||||||
|
assert bool(facet(ifc.createIfcWall(Description="Foobar"))) is True
|
||||||
|
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)
|
||||||
|
wall_type.Description = "Foobar"
|
||||||
|
assert bool(facet(wall)) is True
|
||||||
|
|
||||||
|
# Location any checks on attributes on the type, which may be overriden by attributes on the occurence
|
||||||
|
facet = ids.attribute.create(name="Description", value="Foobar", location="any")
|
||||||
|
assert bool(facet(ifc.createIfcWall(Description="Foobar"))) is True
|
||||||
|
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)
|
||||||
|
wall_type.Description = "Foobaz"
|
||||||
|
wall.Description = "Foobar"
|
||||||
|
assert bool(facet(wall)) is True
|
||||||
|
|
||||||
|
def test_creating_a_classification_facet(self):
|
||||||
c = ids.classification.create(location="any", value="Test_Value", system="Test_System")
|
c = ids.classification.create(location="any", value="Test_Value", system="Test_System")
|
||||||
self.assertEqual(c.location, "any")
|
self.assertEqual(c.location, "any")
|
||||||
self.assertEqual(c.value, "Test_Value")
|
self.assertEqual(c.value, "Test_Value")
|
||||||
|
|||||||
Reference in New Issue
Block a user