WIP update to support current IDS v0.6

This commit is contained in:
Dion Moult
2022-05-31 15:35:47 +10:00
parent 3e1c2c4daf
commit 5ca19ce2f7
3 changed files with 238 additions and 510 deletions
+78 -108
View File
@@ -206,7 +206,7 @@ class ids:
if comply:
self.ifc_passed += 1
if self.ifc_applicable == 0:
if spec.use == "required":
if spec.minOccurs != "0":
logger.error("No applicable elements found. Minimum 1 applicable element required.")
else:
logger.debug("No applicable elements found. None required.")
@@ -235,7 +235,8 @@ class specification:
def __init__(
self,
name="Unnamed",
use="required",
minOccurs=None,
maxOccurs=None,
ifcVersion=["IFC2X3", "IFC4"],
identifier=None,
description=None,
@@ -245,13 +246,16 @@ class specification:
:param name: Name describing the specification to a contract reader
:type name: str
:param use: 'required'|'optional', defaults to "required"
:type use: str, optional
:param minOccurs: The minimum total entities that should pass as an integer >= 0
:type minOccurs: str, optional
:param maxOccurs: The maximum total entities that should pass as an integer >= 0 or "unbounded"
:type maxOccurs: str, optional
"""
self.name = name or "Unnamed"
self.applicability = None
self.requirements = None
self.use = use
self.minOccurs = minOccurs
self.maxOccurs = maxOccurs
self.ifcVersion = ifcVersion
self.identifier = identifier
self.description = description
@@ -270,12 +274,11 @@ class specification:
# if older python collections.OrderedDict()
results = {
"@name": self.name,
"@use": self.use,
"@ifcVersion": self.ifcVersion,
"applicability": {},
"requirements": {},
}
for attribute in ["identifier", "description", "instructions"]:
for attribute in ["identifier", "description", "instructions", "minOccurs", "maxOccurs"]:
value = getattr(self, attribute)
if value:
results[f"@{attribute}"] = value
@@ -311,7 +314,8 @@ class specification:
spec.name = ids_dict["@name"]
except KeyError:
spec.name = ""
spec.use = ids_dict["@use"]
spec.minOccurs = ids_dict["@minOccurs"]
spec.maxOccurs = ids_dict["@maxOccurs"]
spec.ifcVersion = ids_dict["@ifcVersion"]
spec.applicability = boolean_and(parse_rules(ids_dict["applicability"]))
spec.requirements = boolean_and(parse_rules(ids_dict["requirements"]))
@@ -447,19 +451,9 @@ class facet(metaclass=meta_facet):
Use child classes instead: entity, classification, property and material.
"""
def __init__(self, node=None, location=None):
def __init__(self, node=None):
if node:
self.node = node
if "@location" in self:
self.location = self.node["@location"]
else:
self.location = "any"
if location:
self.location = location
else:
self.location = "any"
def __getattr__(self, attr):
@@ -561,20 +555,20 @@ class entity(facet):
class attribute(facet):
"""The IDS attribute facet"""
parameters = ["name", "value", "location", "use", "instructions"]
parameters = ["name", "value", "minOccurs", "maxOccurs", "instructions"]
@staticmethod
def create(name="Name", value=None, location="any", use=None, instructions=None):
def create(name="Name", value=None, minOccurs=None, maxOccurs=None, instructions=None):
"""Create an attribute facet that can be added to applicability or requirements of IDS specification.
:param name: Attribute name, such as "Description"
:type name: str
:param value: Attribute value, with type being strictly checked
:type value: str, optional
:param location: Where to check for the parameter. One of "any"|"instance"|"type", defaults to "any"
:type location: str, optional
:param use: 'required'|'optional', defaults to "required"
:type use: str, optional
:param minOccurs: The minimum total entities that should pass as an integer >= 0
:type minOccurs: str, optional
:param maxOccurs: The maximum total entities that should pass as an integer >= 0 or "unbounded"
:type maxOccurs: str, optional
:param instructions: Instructions as a guide for model authors when reading the requirements
:type instructions: str, optional
:return: entity object
@@ -584,8 +578,8 @@ class attribute(facet):
inst = attribute()
inst.name = name
inst.value = value
inst.location = location
inst.use = use
inst.minOccurs = minOccurs
inst.maxOccurs = maxOccurs
inst.instructions = instructions
return inst
@@ -598,10 +592,10 @@ class attribute(facet):
results = {"name": parameter_asdict(self.name)}
if self.value:
results["value"] = parameter_asdict(self.value)
if self.location:
results["@location"] = self.location
if self.use:
results["@use"] = self.use
if self.minOccurs:
results["@minOccurs"] = self.minOccurs
if self.maxOccurs:
results["@maxOccurs"] = self.maxOccurs
if self.instructions:
results["@instructions"] = self.instructions
return results
@@ -622,25 +616,19 @@ class attribute(facet):
return [getattr(element, name, None)]
return [v for k, v in element.get_info().items() if k == name]
if self.location == "instance":
values = get_values(inst, self.name)
elif self.location == "type":
element_type = ifcopenshell.util.element.get_type(inst)
values = get_values(element_type, self.name) if element_type else []
elif self.location == "any":
element_type = ifcopenshell.util.element.get_type(inst)
element_type = ifcopenshell.util.element.get_type(inst)
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]
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:
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]
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:
@@ -658,28 +646,30 @@ class classification(facet):
The IDS classification facet by traversing the HasAssociations inverse attribute
"""
parameters = ["system", "value", "location", "uri", "use", "instructions"]
message = "%(location)sclassification reference %(value)s from '%(system)s'"
parameters = ["system", "value", "uri", "minOccurs", "maxOccurs" "instructions"]
message = "sclassification reference %(value)s from '%(system)s'"
@staticmethod
def create(value=None, system=None, location="any", uri=None, use=None, instructions=None):
def create(value=None, system=None, uri=None, minOccurs=None, maxOccurs=None, instructions=None):
"""Create a classification facet that can be added to applicability or requirements of IDS specification.
:param location: Where to check for the parameter. One of "any"|"instance"|"type", defaults to "any"
:type location: str, optional
:param value: Value that is required. Could be alphanumeric or restriction object, defaults to None
:type value: restriction|alphanumeric, optional
:param system: System that is required. Could be alphanumeric or restriction object, defaults to None
:type system: restriction|alphanumeric, optional
:param minOccurs: The minimum total entities that should pass as an integer >= 0
:type minOccurs: str, optional
:param maxOccurs: The maximum total entities that should pass as an integer >= 0 or "unbounded"
:type maxOccurs: str, optional
:return: classification object
:rtype: classification
"""
inst = classification()
inst.value = value
inst.system = system
inst.location = location
inst.uri = uri
inst.use = use
inst.minOccurs = minOccurs
inst.maxOccurs = maxOccurs
inst.instructions = instructions
return inst
@@ -689,15 +679,17 @@ class classification(facet):
:return: Xmlschema compliant dictionary.
:rtype: dict
"""
results = {"@location": self.location}
results = {}
if self.value:
results["value"] = parameter_asdict(self.value)
if self.system:
results["system"] = parameter_asdict(self.system)
if self.uri:
results["@uri"] = self.uri
if self.use:
results["@use"] = self.use
if self.minOccurs:
results["@minOccurs"] = self.minOccurs
if self.maxOccurs:
results["@maxOccurs"] = self.maxOccurs
if self.instructions:
results["@instructions"] = self.instructions
return results
@@ -712,13 +704,7 @@ class classification(facet):
:return: result of the validation as bool and message
: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)
leaf_references = ifcopenshell.util.classification.get_references(inst)
references = leaf_references.copy()
for leaf_reference in leaf_references:
@@ -734,8 +720,6 @@ class classification(facet):
[self.system == ifcopenshell.util.classification.get_classification(r).Name for r in references]
)
self.location_msg = location[self.location]
if references:
return facet_evaluation(
is_pass,
@@ -743,11 +727,10 @@ class classification(facet):
% {
"system": list(references)[0][0],
"value": list(references)[0][1],
"location": self.location_msg,
}, # TODO Fix this 0 index reference assumption when I refactor out the messages
)
else:
return facet_evaluation(False, "does not have %sclassification reference" % self.location_msg)
return facet_evaluation(False, "does not have classification reference")
class partOf(facet):
@@ -812,30 +795,32 @@ class property(facet):
The IDS property facet implemented using `ifcopenshell.util.element`
"""
parameters = ["name", "propertySet", "value", "location"]
message = "%(location)sproperty '%(name)s' in '%(propertySet)s' with a value %(value)s"
parameters = ["name", "propertySet", "value"]
message = "property '%(name)s' in '%(propertySet)s' with a value %(value)s"
@staticmethod
def create(
propertySet="Property_Set",
name="PropertyName",
value=None,
location="any",
measure=None,
uri=None,
use=None,
minOccurs=None,
maxOccurs=None,
instructions=None,
):
"""Create a property facet that can be added to applicability or requirements of IDS specification.
:param location: Where to check for the parameter. One of "any"|"instance"|"type", defaults to "any"
:type location: str, optional
:param propertySet: Propertyset that is required. Could be alphanumeric or restriction object, defaults to None
:type propertySet: restriction|alphanumeric, optional
:param name: Name that is required. Could be alphanumeric or restriction object, defaults to None
:type name: restriction|alphanumeric, optional
:param value: Value that is required. Could be alphanumeric or restriction object, defaults to None
:type value: restriction|alphanumeric, optional
:param minOccurs: The minimum total entities that should pass as an integer >= 0
:type minOccurs: str, optional
:param maxOccurs: The maximum total entities that should pass as an integer >= 0 or "unbounded"
:type maxOccurs: str, optional
:return: property object
:rtype: property
"""
@@ -843,10 +828,10 @@ class property(facet):
inst.propertySet = propertySet
inst.name = name
inst.value = value
inst.location = location
inst.measure = measure
inst.uri = uri
inst.use = use
inst.minOccurs = minOccurs
inst.maxOccurs = maxOccurs
inst.instructions = instructions
return inst
@@ -857,7 +842,6 @@ class property(facet):
:rtype: dict
"""
results = {
"@location": self.location,
"propertySet": parameter_asdict(self.propertySet),
"name": parameter_asdict(self.name),
}
@@ -867,8 +851,10 @@ class property(facet):
results["@measure"] = self.measure
if self.uri:
results["@uri"] = self.uri
if self.use:
results["@use"] = self.use
if self.minOccurs:
results["@minOccurs"] = self.minOccurs
if self.maxOccurs:
results["@maxOccurs"] = self.maxOccurs
if self.instructions:
results["@instructions"] = self.instructions
# TODO '@href': 'http://identifier.buildingsmart.org/uri/buildingsmart/ifc-4.3/prop/FireRating', #https://identifier.buildingsmart.org/uri/something
@@ -884,15 +870,7 @@ class property(facet):
:return: result of the validation as bool and message
:rtype: facet_evaluation(bool, str)
"""
all_psets = {}
if self.location == "instance":
all_psets = ifcopenshell.util.element.get_psets(inst, should_inherit=False)
elif self.location == "type":
element_type = ifcopenshell.util.element.get_type(inst)
if element_type:
all_psets = ifcopenshell.util.element.get_psets(element_type, should_inherit=False)
elif self.location == "any":
all_psets = ifcopenshell.util.element.get_psets(inst)
all_psets = ifcopenshell.util.element.get_psets(inst)
if isinstance(self.propertySet, str):
pset = all_psets.get(self.propertySet, None)
@@ -968,15 +946,13 @@ class property(facet):
class material(facet):
"""The IDS material facet used to traverse the HasAssociations inverse attribute."""
parameters = ["value", "location"]
message = "%(location)smaterial '%(value)s'"
parameters = ["value"]
message = "material '%(value)s'"
@staticmethod
def create(value=None, location="any", uri=None, use=None, instructions=None):
def create(value=None, uri=None, minOccurs=None, maxOccurs=None, instructions=None):
"""Create a material facet that can be added to applicability or requirements of IDS specification.
:param location: Where to check for the parameter. One of "any"|"instance"|"type", defaults to "any"
:type location: str, optional
:param value: Value that is required. Could be alphanumeric or restriction object, defaults to None
:type value: restriction|alphanumeric, optional
:return: material object
@@ -984,9 +960,9 @@ class material(facet):
"""
inst = material()
inst.value = value
inst.location = location
inst.uri = uri
inst.use = use
inst.minOccurs = minOccurs
inst.maxOccurs = maxOccurs
inst.instructions = instructions
return inst
@@ -996,13 +972,15 @@ class material(facet):
:return: Xmlschema compliant dictionary.
:rtype: dict
"""
results = {"@location": self.location}
results = {}
if self.value:
results["value"] = parameter_asdict(self.value)
if self.uri:
results["@uri"] = self.uri
if self.use:
results["@use"] = self.use
if self.minOccurs:
results["@minOccurs"] = self.minOccurs
if self.maxOccurs:
results["@maxOccurs"] = self.maxOccurs
if self.instructions:
results["@instructions"] = self.instructions
return results
@@ -1017,15 +995,7 @@ class material(facet):
:return: result of the validation as bool and message
:rtype: facet_evaluation(bool, str)
"""
material = None
if self.location == "instance":
material = ifcopenshell.util.element.get_material(inst, should_skip_usage=True, should_inherit=False)
elif self.location == "type":
element_type = ifcopenshell.util.element.get_type(inst)
if element_type:
material = ifcopenshell.util.element.get_material(element_type, should_skip_usage=True)
elif self.location == "any":
material = ifcopenshell.util.element.get_material(inst, should_skip_usage=True)
material = ifcopenshell.util.element.get_material(inst, should_skip_usage=True)
is_pass = material is not None