mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-20 23:36:20 +00:00
Fix multiple specifications in IDS
This commit is contained in:
@@ -19,18 +19,14 @@
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import logging
|
import logging
|
||||||
import operator
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
import ifcopenshell.util.unit
|
import ifcopenshell.util.unit
|
||||||
import ifcopenshell.util.element
|
import ifcopenshell.util.element
|
||||||
import ifcopenshell.util.placement
|
import ifcopenshell.util.placement
|
||||||
import ifcopenshell.util.classification
|
import ifcopenshell.util.classification
|
||||||
|
|
||||||
from bcf.v2.bcfxml import BcfXml
|
from bcf.v2.bcfxml import BcfXml
|
||||||
from bcf.v2 import data as bcf
|
from bcf.v2 import data as bcf
|
||||||
|
|
||||||
from xmlschema import XMLSchema
|
from xmlschema import XMLSchema
|
||||||
from xmlschema import etree_tostring
|
from xmlschema import etree_tostring
|
||||||
from xmlschema.validators import identities
|
from xmlschema.validators import identities
|
||||||
@@ -112,10 +108,10 @@ class ids:
|
|||||||
"@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
|
"@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
|
||||||
"@xsi:schemaLocation": "http://standards.buildingsmart.org/IDS/ids_05.xsd",
|
"@xsi:schemaLocation": "http://standards.buildingsmart.org/IDS/ids_05.xsd",
|
||||||
"info": self.info,
|
"info": self.info,
|
||||||
"specifications": [],
|
"specifications": {"specification": []},
|
||||||
}
|
}
|
||||||
for spec in self.specifications:
|
for spec in self.specifications:
|
||||||
ids_dict["specifications"].append({"specification": spec.asdict()}) # TEST!
|
ids_dict["specifications"]["specification"].append(spec.asdict())
|
||||||
return ids_dict
|
return ids_dict
|
||||||
|
|
||||||
def to_string(self, ids_schema=ids_schema):
|
def to_string(self, ids_schema=ids_schema):
|
||||||
@@ -139,6 +135,7 @@ class ids:
|
|||||||
:return: Result of the newly created file validation against the schema.
|
:return: Result of the newly created file validation against the schema.
|
||||||
:rtype: bool
|
:rtype: bool
|
||||||
"""
|
"""
|
||||||
|
ET.register_namespace("", "http://standards.buildingsmart.org/IDS")
|
||||||
ET.ElementTree(ids_schema.encode(self.asdict())).write(filepath, encoding="utf-8", xml_declaration=True)
|
ET.ElementTree(ids_schema.encode(self.asdict())).write(filepath, encoding="utf-8", xml_declaration=True)
|
||||||
return ids_schema.is_valid(filepath)
|
return ids_schema.is_valid(filepath)
|
||||||
|
|
||||||
@@ -162,6 +159,29 @@ class ids:
|
|||||||
ids_file.specifications = [specification.parse(s) for s in ids_content["specifications"]["specification"]]
|
ids_file.specifications = [specification.parse(s) for s in ids_content["specifications"]["specification"]]
|
||||||
return ids_file
|
return ids_file
|
||||||
|
|
||||||
|
def validate2(self, ifc_file):
|
||||||
|
"""Use to validate IFC model against IDS specifications.
|
||||||
|
|
||||||
|
:param ifc_file: path to ifc file
|
||||||
|
:type ifc_file: str
|
||||||
|
:param logger: Logging object with handlers, defaults to None
|
||||||
|
:type logger: logging, optional
|
||||||
|
"""
|
||||||
|
for specification in self.specifications:
|
||||||
|
specification.applicable_entities.clear()
|
||||||
|
specification.failed_entities.clear()
|
||||||
|
specification.status = None
|
||||||
|
for element in ifc_file:
|
||||||
|
for specification in self.specifications:
|
||||||
|
if not specification.applicability(element, None):
|
||||||
|
continue
|
||||||
|
specification.applicable_entities.append(element)
|
||||||
|
if specification.requirements(element, None):
|
||||||
|
specification.status = True
|
||||||
|
else:
|
||||||
|
specification.status = False
|
||||||
|
specification.failed_entities.append(element)
|
||||||
|
|
||||||
def validate(self, ifc_file, logger=None):
|
def validate(self, ifc_file, logger=None):
|
||||||
"""Use to validate IFC model against IDS specifications.
|
"""Use to validate IFC model against IDS specifications.
|
||||||
|
|
||||||
@@ -237,6 +257,10 @@ class specification:
|
|||||||
self.description = description
|
self.description = description
|
||||||
self.instructions = instructions
|
self.instructions = instructions
|
||||||
|
|
||||||
|
self.applicable_entities = []
|
||||||
|
self.failed_entities = []
|
||||||
|
self.status = None
|
||||||
|
|
||||||
def asdict(self):
|
def asdict(self):
|
||||||
"""Converts object to a dictionary, adding required attributes.
|
"""Converts object to a dictionary, adding required attributes.
|
||||||
|
|
||||||
@@ -337,7 +361,6 @@ class specification:
|
|||||||
:rtype: [bool,bool]
|
:rtype: [bool,bool]
|
||||||
"""
|
"""
|
||||||
if self.applicability(inst, logger):
|
if self.applicability(inst, logger):
|
||||||
|
|
||||||
valid = self.requirements(inst, logger)
|
valid = self.requirements(inst, logger)
|
||||||
|
|
||||||
if valid:
|
if valid:
|
||||||
@@ -473,10 +496,10 @@ class facet(metaclass=meta_facet):
|
|||||||
class entity(facet):
|
class entity(facet):
|
||||||
"""The IDS entity facet currently *with* inheritance"""
|
"""The IDS entity facet currently *with* inheritance"""
|
||||||
|
|
||||||
parameters = ["name", "predefinedType"]
|
parameters = ["name", "predefinedType", "instructions"]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create(name=None, predefinedType=None):
|
def create(name=None, predefinedType=None, instructions=None):
|
||||||
"""Create an entity facet that can be added to applicability or requirements of IDS specification.
|
"""Create an entity facet that can be added to applicability or requirements of IDS specification.
|
||||||
|
|
||||||
:param name: IFC entity name that is required. e.g. IfcWall, defaults to None
|
:param name: IFC entity name that is required. e.g. IfcWall, defaults to None
|
||||||
@@ -490,6 +513,7 @@ class entity(facet):
|
|||||||
inst = entity()
|
inst = entity()
|
||||||
inst.name = name
|
inst.name = name
|
||||||
inst.predefinedType = predefinedType
|
inst.predefinedType = predefinedType
|
||||||
|
inst.instructions = instructions
|
||||||
return inst
|
return inst
|
||||||
|
|
||||||
def asdict(self):
|
def asdict(self):
|
||||||
@@ -501,6 +525,8 @@ class entity(facet):
|
|||||||
results = {"name": parameter_asdict(self.name)}
|
results = {"name": parameter_asdict(self.name)}
|
||||||
if self.predefinedType:
|
if self.predefinedType:
|
||||||
results["predefinedType"] = parameter_asdict(self.predefinedType)
|
results["predefinedType"] = parameter_asdict(self.predefinedType)
|
||||||
|
if self.instructions:
|
||||||
|
results["@instructions"] = self.instructions
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def __call__(self, inst, logger=None):
|
def __call__(self, inst, logger=None):
|
||||||
@@ -569,16 +595,16 @@ class attribute(facet):
|
|||||||
:return: Xmlschema compliant dictionary.
|
:return: Xmlschema compliant dictionary.
|
||||||
:rtype: dict
|
:rtype: dict
|
||||||
"""
|
"""
|
||||||
fac_dict = {"name": parameter_asdict(self.name)}
|
results = {"name": parameter_asdict(self.name)}
|
||||||
if self.value:
|
if self.value:
|
||||||
fac_dict["value"] = parameter_asdict(self.value)
|
results["value"] = parameter_asdict(self.value)
|
||||||
if self.location:
|
if self.location:
|
||||||
fac_dict["@location"] = self.location
|
results["@location"] = self.location
|
||||||
if self.use:
|
if self.use:
|
||||||
fac_dict["@use"] = self.use
|
results["@use"] = self.use
|
||||||
if self.instructions:
|
if self.instructions:
|
||||||
fac_dict["@instructions"] = self.instructions
|
results["@instructions"] = self.instructions
|
||||||
return fac_dict
|
return results
|
||||||
|
|
||||||
def __call__(self, inst, logger=None):
|
def __call__(self, inst, logger=None):
|
||||||
"""Validate an ifc instance.
|
"""Validate an ifc instance.
|
||||||
@@ -752,7 +778,7 @@ class partOf(facet):
|
|||||||
:return: Xmlschema compliant dictionary.
|
:return: Xmlschema compliant dictionary.
|
||||||
:rtype: dict
|
:rtype: dict
|
||||||
"""
|
"""
|
||||||
return {"@entity": parameter_asdict(self.entity)}
|
return {"@entity": self.entity}
|
||||||
|
|
||||||
def __call__(self, inst, logger=None):
|
def __call__(self, inst, logger=None):
|
||||||
"""Validate an ifc instance against that partOf facet.
|
"""Validate an ifc instance against that partOf facet.
|
||||||
@@ -1149,7 +1175,7 @@ class restriction:
|
|||||||
rest_dict["xs:enumeration"].append({"@value": option})
|
rest_dict["xs:enumeration"].append({"@value": option})
|
||||||
elif self.type == "bounds":
|
elif self.type == "bounds":
|
||||||
for option in self.options:
|
for option in self.options:
|
||||||
rest_dict["xs:" + option] = [{"@value": self.options[option], "@fixed": False}]
|
rest_dict["xs:" + option] = [{"@value": str(self.options[option]), "@fixed": False}]
|
||||||
elif self.type == "pattern":
|
elif self.type == "pattern":
|
||||||
if "xs:pattern" not in rest_dict:
|
if "xs:pattern" not in rest_dict:
|
||||||
rest_dict["xs:pattern"] = [{"@value": self.options}]
|
rest_dict["xs:pattern"] = [{"@value": self.options}]
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ class TestIdsAuthoring(unittest.TestCase):
|
|||||||
"@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
|
"@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
|
||||||
"@xsi:schemaLocation": "http://standards.buildingsmart.org/IDS/ids_05.xsd",
|
"@xsi:schemaLocation": "http://standards.buildingsmart.org/IDS/ids_05.xsd",
|
||||||
"info": {"title": "Untitled"},
|
"info": {"title": "Untitled"},
|
||||||
"specifications": [],
|
"specifications": {'specification': []},
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_create_an_ids_with_all_possible_information(self):
|
def test_create_an_ids_with_all_possible_information(self):
|
||||||
@@ -269,7 +269,7 @@ class TestIdsAuthoring(unittest.TestCase):
|
|||||||
"purpose": "purpose",
|
"purpose": "purpose",
|
||||||
"milestone": "milestone",
|
"milestone": "milestone",
|
||||||
},
|
},
|
||||||
"specifications": [],
|
"specifications": {'specification': []},
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_check_invalid_ids_information(self):
|
def test_check_invalid_ids_information(self):
|
||||||
@@ -280,7 +280,7 @@ class TestIdsAuthoring(unittest.TestCase):
|
|||||||
"@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
|
"@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
|
||||||
"@xsi:schemaLocation": "http://standards.buildingsmart.org/IDS/ids_05.xsd",
|
"@xsi:schemaLocation": "http://standards.buildingsmart.org/IDS/ids_05.xsd",
|
||||||
"info": {"title": "Untitled"},
|
"info": {"title": "Untitled"},
|
||||||
"specifications": [],
|
"specifications": {'specification': []},
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_authoring_an_ids_with_no_specifications_is_invalid(self):
|
def test_authoring_an_ids_with_no_specifications_is_invalid(self):
|
||||||
@@ -363,6 +363,7 @@ class TestIdsAuthoring(unittest.TestCase):
|
|||||||
|
|
||||||
# IFC class is case insensitive.
|
# IFC class is case insensitive.
|
||||||
# WRONG WRONG WRONG should be UPPERCASE
|
# WRONG WRONG WRONG should be UPPERCASE
|
||||||
|
# But in that case why are the enumerations for things like partOf using the IFC capitalisation?
|
||||||
facet = ids.entity.create(name="IFCWALL")
|
facet = ids.entity.create(name="IFCWALL")
|
||||||
case("Entity case 0", facet=facet, inst=ifc.createIfcWall(), expected=True)
|
case("Entity case 0", facet=facet, inst=ifc.createIfcWall(), expected=True)
|
||||||
case("Entity case 1", facet=facet, inst=ifc.createIfcWall(PredefinedType="SOLIDWALL"), expected=True)
|
case("Entity case 1", facet=facet, inst=ifc.createIfcWall(PredefinedType="SOLIDWALL"), expected=True)
|
||||||
@@ -1103,9 +1104,9 @@ class TestIdsAuthoring(unittest.TestCase):
|
|||||||
|
|
||||||
def test_creating_a_partof_facet(self):
|
def test_creating_a_partof_facet(self):
|
||||||
facet = ids.partOf.create()
|
facet = ids.partOf.create()
|
||||||
assert facet.asdict() == {"@entity": {"simpleValue": "IfcSystem"}}
|
assert facet.asdict() == {"@entity": "IfcSystem"}
|
||||||
facet = ids.partOf.create(entity="IfcGroup")
|
facet = ids.partOf.create(entity="IfcGroup")
|
||||||
assert facet.asdict() == {"@entity": {"simpleValue": "IfcGroup"}}
|
assert facet.asdict() == {"@entity": "IfcGroup"}
|
||||||
|
|
||||||
def test_filtering_using_a_partof_facet(self):
|
def test_filtering_using_a_partof_facet(self):
|
||||||
ifc = ifcopenshell.file()
|
ifc = ifcopenshell.file()
|
||||||
@@ -1274,6 +1275,28 @@ class TestIfcValidation(unittest.TestCase):
|
|||||||
assert set(spec.applicable_entities) == {wall, waldo}
|
assert set(spec.applicable_entities) == {wall, waldo}
|
||||||
assert spec.failed_entities == [wall]
|
assert spec.failed_entities == [wall]
|
||||||
|
|
||||||
|
def test_creating_multiple_specifications(self):
|
||||||
|
specs = ids.ids(title="Title")
|
||||||
|
spec = ids.specification(name="Name")
|
||||||
|
spec.add_applicability(ids.entity.create(name="IfcWall"))
|
||||||
|
spec.add_requirement(ids.attribute.create(name="Name", value="Waldo"))
|
||||||
|
specs.specifications.append(spec)
|
||||||
|
|
||||||
|
spec2 = ids.specification(name="Name")
|
||||||
|
spec2.add_applicability(ids.entity.create(name="IfcWall"))
|
||||||
|
spec2.add_requirement(ids.attribute.create(name="Name", value="Waldo"))
|
||||||
|
specs.specifications.append(spec2)
|
||||||
|
|
||||||
|
model = ifcopenshell.file()
|
||||||
|
wall = model.createIfcWall()
|
||||||
|
waldo = model.createIfcWall(Name="Waldo")
|
||||||
|
specs.validate2(model)
|
||||||
|
|
||||||
|
assert spec.status == False
|
||||||
|
assert set(spec.applicable_entities) == {wall, waldo}
|
||||||
|
assert spec.failed_entities == [wall]
|
||||||
|
assert spec2.failed_entities == [wall]
|
||||||
|
|
||||||
def test_validate_simple(self):
|
def test_validate_simple(self):
|
||||||
# Same test as in reporting...
|
# Same test as in reporting...
|
||||||
ids_file = ids.ids.open(IDS_URL)
|
ids_file = ids.ids.open(IDS_URL)
|
||||||
|
|||||||
Reference in New Issue
Block a user