This commit is contained in:
Andrej730
2024-03-29 10:56:06 +05:00
parent 7f57649d10
commit 3c0405249b
3 changed files with 71 additions and 41 deletions
+51 -31
View File
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with IfcTester. If not, see <http://www.gnu.org/licenses/>. # along with IfcTester. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import re import re
import builtins import builtins
import ifcopenshell.util.unit import ifcopenshell.util.unit
@@ -23,7 +24,11 @@ import ifcopenshell.util.element
import ifcopenshell.util.classification import ifcopenshell.util.classification
from functools import lru_cache from functools import lru_cache
from xmlschema.validators import identities from xmlschema.validators import identities
from typing import List, Union from typing import Union, Optional, Any, Literal, TYPE_CHECKING
from logging import Logger
if TYPE_CHECKING:
from .ids import Specification
def cast_to_value(from_value, to_value): def cast_to_value(from_value, to_value):
@@ -53,14 +58,19 @@ def get_psets(element):
return ifcopenshell.util.element.get_psets(element) return ifcopenshell.util.element.get_psets(element)
Cardinality = Literal["required", "optional", "prohibited"]
class Facet: class Facet:
cardinality: Cardinality
def __init__(self, *parameters): def __init__(self, *parameters):
self.status = None self.status = None
self.failures = [] self.failures = []
for i, name in enumerate(self.parameters): for i, name in enumerate(self.parameters):
setattr(self, name.replace("@", ""), parameters[i]) setattr(self, name.replace("@", ""), parameters[i])
def asdict(self, clause_type): def asdict(self, clause_type: str) -> dict[str, Any]:
results = {} results = {}
for name in self.parameters: for name in self.parameters:
value = getattr(self, name.replace("@", "")) value = getattr(self, name.replace("@", ""))
@@ -86,11 +96,16 @@ class Facet:
return self return self
def filter( def filter(
self, ifc_file: ifcopenshell.file, elements: List[ifcopenshell.entity_instance] self, ifc_file: ifcopenshell.file, elements: list[ifcopenshell.entity_instance]
) -> List[ifcopenshell.entity_instance]: ) -> list[ifcopenshell.entity_instance]:
return [e for e in elements if self(e)] return [e for e in elements if self(e)]
def to_string(self, clause_type, specification=None, requirement=None): def to_string(
self,
clause_type: str,
specification: Optional[Specification] = None,
requirement: Optional[Facet] = None,
):
if clause_type == "applicability": if clause_type == "applicability":
templates = self.applicability_templates templates = self.applicability_templates
elif clause_type == "requirement": elif clause_type == "requirement":
@@ -114,7 +129,7 @@ class Facet:
if total_replacements == total_variables: if total_replacements == total_variables:
return template return template
def to_ids_value(self, parameter): def to_ids_value(self, parameter: Union[str, Restriction, list]) -> dict[str, Any]:
if isinstance(parameter, str): if isinstance(parameter, str):
parameter_dict = {"simpleValue": parameter} parameter_dict = {"simpleValue": parameter}
elif isinstance(parameter, Restriction): elif isinstance(parameter, Restriction):
@@ -129,9 +144,12 @@ class Facet:
raise Exception(str(parameter) + " was not able to be converted into 'Parameter_dict'") raise Exception(str(parameter) + " was not able to be converted into 'Parameter_dict'")
return parameter_dict return parameter_dict
def get_usage(self): def get_usage(self) -> Cardinality:
return self.cardinality return self.cardinality
def __call__(self, inst: ifcopenshell.entity_instance, logger: Optional[Logger] = None) -> Result:
raise NotImplementedError
class Entity(Facet): class Entity(Facet):
def __init__(self, name="IFCWALL", predefinedType=None, instructions=None): def __init__(self, name="IFCWALL", predefinedType=None, instructions=None):
@@ -150,7 +168,9 @@ class Entity(Facet):
] ]
super().__init__(name, predefinedType, instructions) super().__init__(name, predefinedType, instructions)
def filter(self, ifc_file, elements): def filter(
self, ifc_file: ifcopenshell.file, elements: Optional[list[ifcopenshell.entity_instance]] = None
) -> list[ifcopenshell.entity_instance]:
if isinstance(elements, list): if isinstance(elements, list):
return super().filter(ifc_file, elements) return super().filter(ifc_file, elements)
@@ -173,7 +193,7 @@ class Entity(Facet):
return [r for r in results if self(r)] return [r for r in results if self(r)]
return results return results
def __call__(self, inst, logger=None): def __call__(self, inst: ifcopenshell.entity_instance, logger: Optional[Logger] = None) -> EntityResult:
is_pass = inst.is_a().upper() == self.name is_pass = inst.is_a().upper() == self.name
reason = None reason = None
@@ -191,7 +211,7 @@ class Entity(Facet):
class Attribute(Facet): class Attribute(Facet):
def __init__(self, name="Name", value=None, cardinality="required", instructions=None): def __init__(self, name="Name", value=None, cardinality: Cardinality = "required", instructions=None):
self.parameters = ["name", "value", "@cardinality", "@instructions"] self.parameters = ["name", "value", "@cardinality", "@instructions"]
self.applicability_templates = [ self.applicability_templates = [
"Data where the {name} is {value}", "Data where the {name} is {value}",
@@ -208,8 +228,8 @@ class Attribute(Facet):
super().__init__(name, value, cardinality, instructions) super().__init__(name, value, cardinality, instructions)
def filter( def filter(
self, ifc_file: ifcopenshell.file, elements: Union[ifcopenshell.entity_instance, None] self, ifc_file: ifcopenshell.file, elements: Optional[list[ifcopenshell.entity_instance]]
) -> List[ifcopenshell.entity_instance]: ) -> list[ifcopenshell.entity_instance]:
if isinstance(elements, list): if isinstance(elements, list):
return super().filter(ifc_file, elements) return super().filter(ifc_file, elements)
@@ -235,7 +255,7 @@ class Attribute(Facet):
return results return results
def __call__(self, inst, logger=None): def __call__(self, inst: ifcopenshell.entity_instance, logger: Optional[Logger] = None) -> AttributeResult:
if self.cardinality == "optional": if self.cardinality == "optional":
return AttributeResult(True) return AttributeResult(True)
@@ -323,7 +343,7 @@ class Attribute(Facet):
class Classification(Facet): class Classification(Facet):
def __init__(self, value=None, system=None, uri=None, cardinality="required", instructions=None): def __init__(self, value=None, system=None, uri=None, cardinality: Cardinality = "required", instructions=None):
self.parameters = ["value", "system", "@uri", "@cardinality", "@instructions"] self.parameters = ["value", "system", "@uri", "@cardinality", "@instructions"]
self.applicability_templates = [ self.applicability_templates = [
"Data having a {system} reference of {value}", "Data having a {system} reference of {value}",
@@ -344,13 +364,13 @@ class Classification(Facet):
super().__init__(value, system, uri, cardinality, instructions) super().__init__(value, system, uri, cardinality, instructions)
def filter( def filter(
self, ifc_file: ifcopenshell.file, elements: Union[ifcopenshell.entity_instance, None] self, ifc_file: ifcopenshell.file, elements: Optional[list[ifcopenshell.entity_instance]]
) -> List[ifcopenshell.entity_instance]: ) -> list[ifcopenshell.entity_instance]:
if isinstance(elements, list): if isinstance(elements, list):
return super().filter(ifc_file, elements) return super().filter(ifc_file, elements)
return ifc_file.by_type("IfcObjectDefinition") return ifc_file.by_type("IfcObjectDefinition")
def __call__(self, inst, logger=None): def __call__(self, inst: ifcopenshell.entity_instance, logger: Optional[Logger] = None) -> ClassificationResult:
if self.cardinality == "optional": if self.cardinality == "optional":
return ClassificationResult(True) # Is this really the correct behaviour? return ClassificationResult(True) # Is this really the correct behaviour?
@@ -389,7 +409,7 @@ class PartOf(Facet):
name="IFCWALL", name="IFCWALL",
predefinedType=None, predefinedType=None,
relation=None, relation=None,
cardinality="required", cardinality: Cardinality = "required",
instructions=None, instructions=None,
): ):
self.parameters = ["name", "predefinedType", "@relation", "@cardinality", "@instructions"] self.parameters = ["name", "predefinedType", "@relation", "@cardinality", "@instructions"]
@@ -408,13 +428,13 @@ class PartOf(Facet):
super().__init__(name, predefinedType, relation, cardinality, instructions) super().__init__(name, predefinedType, relation, cardinality, instructions)
def filter( def filter(
self, ifc_file: ifcopenshell.file, elements: Union[ifcopenshell.entity_instance, None] self, ifc_file: ifcopenshell.file, elements: Optional[list[ifcopenshell.entity_instance]]
) -> List[ifcopenshell.entity_instance]: ) -> list[ifcopenshell.entity_instance]:
if isinstance(elements, list): if isinstance(elements, list):
return super().filter(ifc_file, elements) return super().filter(ifc_file, elements)
return list(ifc_file) # Lazy return list(ifc_file) # Lazy
def asdict(self, clause_type): def asdict(self, clause_type: str) -> dict[str, Any]:
results = super().asdict(clause_type) results = super().asdict(clause_type)
entity = {} entity = {}
if "name" in results: if "name" in results:
@@ -433,7 +453,7 @@ class PartOf(Facet):
del xml["entity"] del xml["entity"]
return super().parse(xml) return super().parse(xml)
def __call__(self, inst, logger=None): def __call__(self, inst: ifcopenshell.entity_instance, logger: Optional[Logger] = None) -> PartOfResult:
reason = None reason = None
if not self.relation: if not self.relation:
is_pass = False is_pass = False
@@ -587,7 +607,7 @@ class Property(Facet):
value=None, value=None,
dataType=None, dataType=None,
uri=None, uri=None,
cardinality="required", cardinality: Cardinality = "required",
instructions=None, instructions=None,
): ):
self.parameters = [ self.parameters = [
@@ -614,8 +634,8 @@ class Property(Facet):
super().__init__(propertySet, baseName, value, dataType, uri, cardinality, instructions) super().__init__(propertySet, baseName, value, dataType, uri, cardinality, instructions)
def filter( def filter(
self, ifc_file: ifcopenshell.file, elements: Union[ifcopenshell.entity_instance, None] self, ifc_file: ifcopenshell.file, elements: Optional[list[ifcopenshell.entity_instance]]
) -> List[ifcopenshell.entity_instance]: ) -> list[ifcopenshell.entity_instance]:
if isinstance(elements, list): if isinstance(elements, list):
return super().filter(ifc_file, elements) return super().filter(ifc_file, elements)
if ifc_file.schema == "IFC2X3": if ifc_file.schema == "IFC2X3":
@@ -626,7 +646,7 @@ class Property(Facet):
+ ifc_file.by_type("IfcProfileDef") + ifc_file.by_type("IfcProfileDef")
) )
def __call__(self, inst, logger=None): def __call__(self, inst: ifcopenshell.entity_instance, logger: Optional[Logger] = None) -> PropertyResult:
if self.cardinality == "optional": if self.cardinality == "optional":
return PropertyResult(True) return PropertyResult(True)
@@ -864,7 +884,7 @@ class Property(Facet):
class Material(Facet): class Material(Facet):
def __init__(self, value=None, uri=None, cardinality="required", instructions=None): def __init__(self, value=None, uri=None, cardinality: Cardinality = "required", instructions=None):
self.parameters = ["value", "@uri", "@cardinality", "@instructions"] self.parameters = ["value", "@uri", "@cardinality", "@instructions"]
self.applicability_templates = [ self.applicability_templates = [
"All data with a {value} material", "All data with a {value} material",
@@ -881,13 +901,13 @@ class Material(Facet):
super().__init__(value, uri, cardinality, instructions) super().__init__(value, uri, cardinality, instructions)
def filter( def filter(
self, ifc_file: ifcopenshell.file, elements: Union[ifcopenshell.entity_instance, None] self, ifc_file: ifcopenshell.file, elements: Optional[list[ifcopenshell.entity_instance]]
) -> List[ifcopenshell.entity_instance]: ) -> list[ifcopenshell.entity_instance]:
if isinstance(elements, list): if isinstance(elements, list):
return super().filter(ifc_file, elements) return super().filter(ifc_file, elements)
return ifc_file.by_type("IfcObjectDefinition") return ifc_file.by_type("IfcObjectDefinition")
def __call__(self, inst, logger=None): def __call__(self, inst: ifcopenshell.entity_instance, logger: Optional[Logger] = None) -> MaterialResult:
if self.cardinality == "optional": if self.cardinality == "optional":
return MaterialResult(True) return MaterialResult(True)
@@ -965,7 +985,7 @@ class Restriction:
self.options[key] = [v["@value"] for v in value] self.options[key] = [v["@value"] for v in value]
return self return self
def asdict(self): def asdict(self) -> dict[str, Any]:
result = {"@base": "xs:" + self.base} result = {"@base": "xs:" + self.base}
for constraint, value in self.options.items(): for constraint, value in self.options.items():
value = [value] if not isinstance(value, list) else value value = [value] if not isinstance(value, list) else value
+11 -9
View File
@@ -18,6 +18,7 @@
import os import os
import datetime import datetime
import ifcopenshell
from xmlschema import XMLSchema from xmlschema import XMLSchema
from xmlschema import etree_tostring from xmlschema import etree_tostring
from xml.etree import ElementTree as ET from xml.etree import ElementTree as ET
@@ -32,8 +33,9 @@ from .facet import (
Restriction, Restriction,
get_pset, get_pset,
get_psets, get_psets,
Cardinality,
) )
from typing import List, Set from typing import List, Optional, Union
cwd = os.path.dirname(os.path.realpath(__file__)) cwd = os.path.dirname(os.path.realpath(__file__))
schema = None schema = None
@@ -57,7 +59,7 @@ def get_schema():
class Ids: class Ids:
def __init__( def __init__(
self, self,
title="Untitled", title: Optional[str] = "Untitled",
copyright=None, copyright=None,
version=None, version=None,
description=None, description=None,
@@ -131,7 +133,7 @@ class Ids:
ET.ElementTree(get_schema().encode(self.asdict())).write(filepath, encoding="utf-8", xml_declaration=True) ET.ElementTree(get_schema().encode(self.asdict())).write(filepath, encoding="utf-8", xml_declaration=True)
return get_schema().is_valid(filepath) return get_schema().is_valid(filepath)
def validate(self, ifc_file, filter_version=False, filepath=None): def validate(self, ifc_file: ifcopenshell.file, filter_version=False, filepath: Optional[str] = None) -> None:
if filepath: if filepath:
self.filepath = filepath self.filepath = filepath
self.filename = os.path.basename(filepath) self.filename = os.path.basename(filepath)
@@ -158,14 +160,14 @@ class Specification:
self.name = name or "Unnamed" self.name = name or "Unnamed"
self.applicability: List[Facet] = [] self.applicability: List[Facet] = []
self.requirements: List[Facet] = [] self.requirements: List[Facet] = []
self.minOccurs = minOccurs self.minOccurs: Union[int, str] = minOccurs
self.maxOccurs = maxOccurs self.maxOccurs: Union[int, str] = maxOccurs
self.ifcVersion = ifcVersion self.ifcVersion = ifcVersion
self.identifier = identifier self.identifier = identifier
self.description = description self.description = description
self.instructions = instructions self.instructions = instructions
self.applicable_entities: List[Entity] = [] self.applicable_entities: list[ifcopenshell.entity_instance] = []
self.status = None self.status = None
def asdict(self): def asdict(self):
@@ -229,13 +231,13 @@ class Specification:
def reset_status(self): def reset_status(self):
self.applicable_entities.clear() self.applicable_entities.clear()
self.failed_entities: Set[Entity] = set() self.failed_entities: set[ifcopenshell.entity_instance] = set()
for facet in self.requirements: for facet in self.requirements:
facet.status = None facet.status = None
facet.failures.clear() facet.failures.clear()
self.status = None self.status = None
def validate(self, ifc_file, filter_version=False): def validate(self, ifc_file: ifcopenshell.file, filter_version=False) -> None:
if filter_version and ifc_file.schema not in self.ifcVersion: if filter_version and ifc_file.schema not in self.ifcVersion:
return return
@@ -286,7 +288,7 @@ class Specification:
if self.applicable_entities and not self.requirements: if self.applicable_entities and not self.requirements:
self.status = False self.status = False
def get_usage(self): def get_usage(self) -> Cardinality:
if self.minOccurs != 0: if self.minOccurs != 0:
return "required" return "required"
elif self.minOccurs == 0 and self.maxOccurs != 0: elif self.minOccurs == 0 and self.maxOccurs != 0:
+9 -1
View File
@@ -21,9 +21,17 @@ import pytest
import xmlschema import xmlschema
import ifcopenshell import ifcopenshell
from ifctester import ids from ifctester import ids
from typing import Optional
def run(name, ids, ifc, expected, applicable_entities=None, failed_entities=None): def run(
name: str,
ids: ids.Ids,
ifc: ifcopenshell.file,
expected: bool,
applicable_entities: Optional[list[ifcopenshell.entity_instance]] = None,
failed_entities: Optional[list[ifcopenshell.entity_instance]] = None,
):
ids.validate(ifc) ids.validate(ifc)
all_applicable = set() all_applicable = set()
all_failures = set() all_failures = set()