Implement identifier, desc, and instructions for specification node in IDS

This commit is contained in:
Dion Moult
2022-05-10 19:26:12 +10:00
parent a24c4b269a
commit ae298de8dc
2 changed files with 145 additions and 82 deletions
+53 -41
View File
@@ -49,7 +49,7 @@ class ids:
def __init__( def __init__(
self, self,
title="Title", title="Untitled",
copyright=None, copyright=None,
version=None, version=None,
description=None, description=None,
@@ -79,7 +79,7 @@ class ids:
""" """
self.specifications = [] self.specifications = []
self.info = {} self.info = {}
self.info["title"] = title or "Unnamed" self.info["title"] = title or "Untitled"
if copyright: if copyright:
self.info["copyright"] = copyright self.info["copyright"] = copyright
if version: if version:
@@ -113,7 +113,7 @@ class ids:
"specifications": [], "specifications": [],
} }
for spec in self.specifications: for spec in self.specifications:
ids_dict["specifications"].append({"specification": spec.asdict()}) #TEST! ids_dict["specifications"].append({"specification": spec.asdict()}) # TEST!
return ids_dict return ids_dict
def to_string(self, ids_schema=ids_schema): def to_string(self, ids_schema=ids_schema):
@@ -211,7 +211,9 @@ class ids:
class specification: 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__(self, name="Specification", use="required", ifcVersion="IFC2X3"): def __init__(
self, name="Unnamed", use="required", ifcVersion="IFC2X3", identifier=None, description=None, instructions=None
):
"""Create a specification to be added in ids. """Create a specification to be added in ids.
:param name:, defaults to "Specification" :param name:, defaults to "Specification"
@@ -219,11 +221,14 @@ class specification:
:param use: 'required'|'optional', defaults to "required" :param use: 'required'|'optional', defaults to "required"
:type use: str, optional :type use: str, optional
""" """
self.name = name self.name = name or "Unnamed"
self.applicability = None self.applicability = None
self.requirements = None self.requirements = None
self.ifcVersion = ifcVersion
self.use = use self.use = use
self.ifcVersion = ifcVersion
self.identifier = identifier
self.description = description
self.instructions = instructions
def asdict(self): def asdict(self):
"""Converts object to a dictionary, adding required attributes. """Converts object to a dictionary, adding required attributes.
@@ -232,21 +237,28 @@ class specification:
:rtype: dict :rtype: dict
""" """
# if older python collections.OrderedDict() # if older python collections.OrderedDict()
spec_dict = { results = {
"@name": self.name, "@name": self.name,
"@use": self.use, "@use": self.use,
"@ifcVersion": self.ifcVersion, "@ifcVersion": self.ifcVersion,
"applicability": {}, "applicability": {},
"requirements": {}, "requirements": {},
} }
for x in ["applicability", "requirements"]: for attribute in ["identifier", "description", "instructions"]:
for fac in (getattr(self, x)).terms: value = getattr(self, attribute)
if value:
results[f"@{attribute}"] = value
for clause_type in ["applicability", "requirements"]:
clause = getattr(self, clause_type)
if not clause:
continue
for fac in clause.terms:
fclass = type(fac).__name__ fclass = type(fac).__name__
if fclass in spec_dict[x]: if fclass in results[clause_type]:
spec_dict[x][fclass].append(fac.asdict()) results[clause_type][fclass].append(fac.asdict())
else: else:
spec_dict[x][fclass] = [fac.asdict()] results[clause_type][fclass] = [fac.asdict()]
return spec_dict return results
@staticmethod @staticmethod
def parse(ids_dict): def parse(ids_dict):
@@ -417,7 +429,7 @@ class facet(metaclass=meta_facet):
def __getattr__(self, attr): def __getattr__(self, attr):
if attr in getattr(self, 'node', None): if attr in getattr(self, "node", None):
v = self.node[attr] v = self.node[attr]
# BUG list of dictionaries should not happen # BUG list of dictionaries should not happen
@@ -671,11 +683,11 @@ class partOf(facet):
parameters = ["entity"] parameters = ["entity"]
message = "relation as part of %(entity)s" message = "relation as part of %(entity)s"
#TODO temp default # TODO temp default
entity = "IfcElementAssembly" entity = "IfcElementAssembly"
@staticmethod @staticmethod
#TODO should not assume IfcElementAssembly # TODO should not assume IfcElementAssembly
# def create(entity=None): # def create(entity=None):
def create(entity="IfcElementAssembly"): def create(entity="IfcElementAssembly"):
"""Create a partOf facet that can be added to applicability or requirements of IDS specification. """Create a partOf facet that can be added to applicability or requirements of IDS specification.
@@ -713,33 +725,33 @@ class partOf(facet):
:rtype: facet_evaluation(bool, str) :rtype: facet_evaluation(bool, str)
""" """
#TODO handle partOf facet # TODO handle partOf facet
# instance_classiciations = inst.HasAssociations # instance_classiciations = inst.HasAssociations
# if ifcopenshell.util.element.get_type(inst): # if ifcopenshell.util.element.get_type(inst):
# type_classifications = ifcopenshell.util.element.get_type(inst).HasAssociations # type_classifications = ifcopenshell.util.element.get_type(inst).HasAssociations
# else: # else:
# type_classifications = () # type_classifications = ()
# if self.location == "instance" and instance_classiciations: # if self.location == "instance" and instance_classiciations:
# associations = instance_classiciations # associations = instance_classiciations
# elif self.location == "type" and type_classifications: # elif self.location == "type" and type_classifications:
# associations = type_classifications # associations = type_classifications
# elif self.location == "any" and (instance_classiciations or type_classifications): # elif self.location == "any" and (instance_classiciations or type_classifications):
# associations = instance_classiciations + type_classifications # associations = instance_classiciations + type_classifications
# else: # else:
# associations = () # associations = ()
refs = [] refs = []
# for association in associations: # for association in associations:
# if association.is_a("IfcRelAssociatesClassification"): # if association.is_a("IfcRelAssociatesClassification"):
# cref = association.RelatingClassification # cref = association.RelatingClassification
# if hasattr(cref, "ItemReference"): # IFC2x3 # if hasattr(cref, "ItemReference"): # IFC2x3
# refs.append((cref.ReferencedSource.Name, cref.ItemReference)) # refs.append((cref.ReferencedSource.Name, cref.ItemReference))
# elif hasattr(cref, "Identification"): # IFC4 # elif hasattr(cref, "Identification"): # IFC4
# refs.append((cref.ReferencedSource.Name, cref.Identification)) # refs.append((cref.ReferencedSource.Name, cref.Identification))
# self.location_msg = location[self.location] # self.location_msg = location[self.location]
if refs: if refs:
pass pass
@@ -753,7 +765,7 @@ class partOf(facet):
# }, # what if not first item of refs? # }, # what if not first item of refs?
# ) # )
else: else:
return facet_evaluation(False, "is not a part of %s" % self.node['@entity']) return facet_evaluation(False, "is not a part of %s" % self.node["@entity"])
class property(facet): class property(facet):
@@ -818,7 +830,7 @@ class property(facet):
# self.location = self.node["@location"] # self.location = self.node["@location"]
#TODO add documentation that attributes should have "attribute" as propertySets # TODO add documentation that attributes should have "attribute" as propertySets
if self.propertySet == "attribute": if self.propertySet == "attribute":
val = {k.lower(): v for k, v in inst.get_info().items()}.get(self.name, None) val = {k.lower(): v for k, v in inst.get_info().items()}.get(self.name, None)
else: else:
@@ -1168,7 +1180,7 @@ class restriction:
result = True result = True
elif self.type == "pattern": elif self.type == "pattern":
if isinstance(self.options, list): if isinstance(self.options, list):
#TODO handle case with multiple pattern options # TODO handle case with multiple pattern options
translated_pattern = identities.translate_pattern(self.options[0]) translated_pattern = identities.translate_pattern(self.options[0])
else: else:
translated_pattern = identities.translate_pattern(self.options) translated_pattern = identities.translate_pattern(self.options)
+86 -35
View File
@@ -17,10 +17,11 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import os import os
import pytest
import logging import logging
import unittest import unittest
import tempfile import tempfile
import requests import xmlschema
from bcf import bcfxml from bcf import bcfxml
import ifcopenshell import ifcopenshell
from ifcopenshell import ids from ifcopenshell import ids
@@ -163,8 +164,15 @@ class TestIdsAuthoring(unittest.TestCase):
# TODO test this without resorting to hooking into logger output # TODO test this without resorting to hooking into logger output
def test_create_an_ids_with_minimal_information(self): def test_create_an_ids_with_minimal_information(self):
specs = ids.ids(title="title") specs = ids.ids()
assert specs.info == {"title": "title"} assert specs.asdict() == {
"@xmlns": "http://standards.buildingsmart.org/IDS",
"@xmlns:xs": "http://www.w3.org/2001/XMLSchema",
"@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
"@xsi:schemaLocation": "http://standards.buildingsmart.org/IDS/ids_05.xsd",
"info": {"title": "Untitled"},
"specifications": [],
}
def test_create_an_ids_with_all_possible_information(self): def test_create_an_ids_with_all_possible_information(self):
specs = ids.ids( specs = ids.ids(
@@ -177,22 +185,83 @@ class TestIdsAuthoring(unittest.TestCase):
purpose="purpose", purpose="purpose",
milestone="milestone", milestone="milestone",
) )
assert specs.info["title"] == "title" assert specs.asdict() == {
assert specs.info["copyright"] == "copyright" "@xmlns": "http://standards.buildingsmart.org/IDS",
assert specs.info["version"] == "version" "@xmlns:xs": "http://www.w3.org/2001/XMLSchema",
assert specs.info["description"] == "description" "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
assert specs.info["author"] == "author@test.com" "@xsi:schemaLocation": "http://standards.buildingsmart.org/IDS/ids_05.xsd",
assert specs.info["date"] == "2020-01-01" "info": {
assert specs.info["purpose"] == "purpose" "title": "title",
assert specs.info["milestone"] == "milestone" "copyright": "copyright",
"version": "version",
"description": "description",
"author": "author@test.com",
"date": "2020-01-01",
"purpose": "purpose",
"milestone": "milestone",
},
"specifications": [],
}
def test_check_invalid_ids_information(self): def test_check_invalid_ids_information(self):
specs = ids.ids(title=None) specs = ids.ids(title=None, author="author", date="9999-99-99")
assert specs.info["title"] == "Unnamed" assert specs.asdict() == {
specs = ids.ids(author="author") "@xmlns": "http://standards.buildingsmart.org/IDS",
assert not specs.info.get("author") "@xmlns:xs": "http://www.w3.org/2001/XMLSchema",
specs = ids.ids(date="9999-99-99") "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
assert not specs.info.get("date") "@xsi:schemaLocation": "http://standards.buildingsmart.org/IDS/ids_05.xsd",
"info": {"title": "Untitled"},
"specifications": [],
}
def test_authoring_an_ids_with_no_specifications_is_invalid(self):
specs = ids.ids()
with pytest.raises(xmlschema.validators.exceptions.XMLSchemaChildrenValidationError):
specs.to_string()
def test_create_specification_with_minimal_information(self):
spec = ids.specification()
assert spec.asdict() == {
"@name": "Unnamed",
"@use": "required",
"@ifcVersion": "IFC2X3", # :(
"applicability": {},
"requirements": {},
}
def test_create_specification_with_all_possible_information(self):
spec = ids.specification(
name="name",
use="use",
ifcVersion="version",
identifier="identifier",
description="description",
instructions="instructions",
)
assert spec.asdict() == {
"@name": "name",
"@use": "use",
"@ifcVersion": "version",
"@identifier": "identifier",
"@description": "description",
"@instructions": "instructions",
"applicability": {},
"requirements": {},
}
def test_ids_add_content(self):
i = ids.ids(title="My IDS")
i.specifications.append(ids.specification(name="Test_Specification"))
self.assertEqual(i.specifications[0].name, "Test_Specification")
m = ids.material.create(location="any", value="Test_Value")
i.specifications[0].add_applicability(m)
self.assertEqual(i.specifications[0].applicability.terms[0].value, "Test_Value")
i.specifications[0].add_applicability(m)
self.assertEqual(i.specifications[0].applicability.terms[1].value, "Test_Value")
i.specifications[0].add_requirement(m)
self.assertEqual(i.specifications[0].requirements.terms[0].value, "Test_Value")
i.specifications[0].add_requirement(m)
self.assertEqual(i.specifications[0].requirements.terms[1].value, "Test_Value")
def test_entity_create(self): def test_entity_create(self):
e = ids.entity.create(name="Test_Name", predefinedType="Test_PredefinedType") e = ids.entity.create(name="Test_Name", predefinedType="Test_PredefinedType")
@@ -229,24 +298,6 @@ class TestIdsAuthoring(unittest.TestCase):
self.assertEqual(m.location, "any") self.assertEqual(m.location, "any")
self.assertEqual(m.value, "Test_Value") self.assertEqual(m.value, "Test_Value")
def test_specification_create(self):
s = ids.specification(name="Test_Specification")
self.assertEqual(s.name, "Test_Specification")
def test_ids_add_content(self):
i = ids.ids(title="My IDS")
i.specifications.append(ids.specification(name="Test_Specification"))
self.assertEqual(i.specifications[0].name, "Test_Specification")
m = ids.material.create(location="any", value="Test_Value")
i.specifications[0].add_applicability(m)
self.assertEqual(i.specifications[0].applicability.terms[0].value, "Test_Value")
i.specifications[0].add_applicability(m)
self.assertEqual(i.specifications[0].applicability.terms[1].value, "Test_Value")
i.specifications[0].add_requirement(m)
self.assertEqual(i.specifications[0].requirements.terms[0].value, "Test_Value")
i.specifications[0].add_requirement(m)
self.assertEqual(i.specifications[0].requirements.terms[1].value, "Test_Value")
""" Creating IDS with restrictions """ """ Creating IDS with restrictions """
def test_create_restrictions_enumeration(self): def test_create_restrictions_enumeration(self):