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