mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-20 12:12:15 +00:00
typing
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user