mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 01:41:57 +00:00
Rework python pset module (#1221)
New features ============ * Handle all syntax for ApplicableEntity as defined in IFC documentation. (drawback it increase request time by ~2/3) * Allow to get applicable psets directly from template definition without caching. This allow more general purpose usages. (drawback it increase request time by ~2.4) To notice / discuss =================== * `ifcwrap/CmakeLists` was installing only `.py` and `.bnf` files from `ifcopenshell-python`. Why ? It now copy all files. Is it an issue ? * property template file is now stored in ifcopenshell python module to allow usage from every softwares. It was before only available for blenderbim addon. * psets and qtos are now optionnally cached at object instead of module level. This allow to work with multiple schema in the same python interpreter. (eg. might help conversion between schemas) * in blenderbim addon psetqto are now stored in `schema.psetqto`.
This commit is contained in:
@@ -1,39 +1,97 @@
|
||||
import pathlib
|
||||
import re
|
||||
from typing import List, Generator
|
||||
|
||||
import ifcopenshell
|
||||
|
||||
property_set_template_files = []
|
||||
psets = {}
|
||||
qtos = {}
|
||||
applicable_psets = {}
|
||||
applicable_qtos = {}
|
||||
from ifcopenshell.entity_instance import entity_instance
|
||||
|
||||
|
||||
def load_property_set_template(path):
|
||||
property_set_template_files.append(ifcopenshell.open(path))
|
||||
for prop in property_set_template_files[-1].by_type("IfcPropertySetTemplate"):
|
||||
if prop.Name[0:4] == "Qto_":
|
||||
qtos[prop.Name] = {"HasPropertyTemplates": {p.Name: p for p in prop.HasPropertyTemplates}}
|
||||
entity = prop.ApplicableEntity if prop.ApplicableEntity else "IfcRoot"
|
||||
applicable_qtos.setdefault(entity, []).append(prop.Name)
|
||||
else:
|
||||
psets[prop.Name] = {"HasPropertyTemplates": {p.Name: p for p in prop.HasPropertyTemplates}}
|
||||
entity = prop.ApplicableEntity if prop.ApplicableEntity else "IfcRoot"
|
||||
applicable_psets.setdefault(entity, []).append(prop.Name)
|
||||
class PsetQto:
|
||||
templates_path = {
|
||||
"IFC4": "Pset_IFC4_ADD2.ifc",
|
||||
}
|
||||
|
||||
def __init__(self, schema: str, templates=None, use_cache=False) -> None:
|
||||
self.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema)
|
||||
if not templates:
|
||||
folder_path = pathlib.Path(__file__).parent.absolute()
|
||||
path = folder_path.joinpath("schema", self.templates_path[schema])
|
||||
templates = [ifcopenshell.open(path)]
|
||||
self.templates = templates
|
||||
# Caching reduce request time. For 100 get_applicable_names requests ~3.6 s -> ~2 s
|
||||
self.use_cache = use_cache
|
||||
self.psets = {}
|
||||
self.qtos = {}
|
||||
self.applicable_psets = {}
|
||||
self.applicable_qtos = {}
|
||||
if use_cache:
|
||||
for template in templates:
|
||||
self.cache_template(template)
|
||||
|
||||
def get_applicable_psetqtos(schema_version, ifc_class, is_pset=False, is_qto=False):
|
||||
def is_a(entity, ifc_class):
|
||||
if entity.name() == ifc_class:
|
||||
return True
|
||||
return is_a(entity.supertype(), ifc_class) if entity.supertype() else False
|
||||
def cache_template(self, template):
|
||||
for prop_set in template.by_type("IfcPropertySetTemplate"):
|
||||
if prop_set.Name[0:4] == "Qto_":
|
||||
self.qtos[prop_set.Name] = {"HasPropertyTemplates": {p.Name: p for p in prop_set.HasPropertyTemplates}}
|
||||
entity = prop_set.ApplicableEntity if prop_set.ApplicableEntity else "IfcRoot"
|
||||
self.applicable_qtos.setdefault(entity, []).append(prop_set.Name)
|
||||
else:
|
||||
self.psets[prop_set.Name] = {"HasPropertyTemplates": {p.Name: p for p in prop_set.HasPropertyTemplates}}
|
||||
entity = prop_set.ApplicableEntity if prop_set.ApplicableEntity else "IfcRoot"
|
||||
self.applicable_psets.setdefault(entity, []).append(prop_set.Name)
|
||||
|
||||
results = []
|
||||
schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_version)
|
||||
entity = schema.declaration_by_name(ifc_class)
|
||||
if is_pset:
|
||||
search_items = applicable_psets.items()
|
||||
elif is_qto:
|
||||
search_items = applicable_qtos.items()
|
||||
for ifc_class, pset_names in search_items:
|
||||
if is_a(entity, ifc_class):
|
||||
results.extend(pset_names)
|
||||
return results
|
||||
def get_applicable(
|
||||
self, ifc_class="", predefined_type="", pset_only=False, qto_only=False
|
||||
) -> Generator[entity_instance, entity_instance, None]:
|
||||
any_class = not ifc_class
|
||||
if not any_class:
|
||||
entity = self.schema.declaration_by_name(ifc_class)
|
||||
for template in self.templates:
|
||||
for prop_set in template.by_type("IfcPropertySetTemplate"):
|
||||
if pset_only:
|
||||
if prop_set.Name.startswith("Qto_"):
|
||||
continue
|
||||
if qto_only:
|
||||
if not prop_set.Name.startswith("Qto_"):
|
||||
continue
|
||||
if any_class or self.is_applicable(entity, prop_set.ApplicableEntity or "IfcRoot", predefined_type):
|
||||
yield prop_set
|
||||
|
||||
def get_applicable_names(self, ifc_class: str, predefined_type="", pset_only=False, qto_only=False) -> List[str]:
|
||||
"""Return names instead of objects for other use eg. enum"""
|
||||
if self.use_cache:
|
||||
results = []
|
||||
entity = self.schema.declaration_by_name(ifc_class)
|
||||
if not qto_only:
|
||||
for applicable_class, pset_names in self.applicable_psets.items():
|
||||
if self.is_applicable(entity, applicable_class):
|
||||
results.extend(pset_names)
|
||||
if not pset_only:
|
||||
for applicable_class, pset_names in self.applicable_qtos.items():
|
||||
if self.is_applicable(entity, applicable_class):
|
||||
results.extend(pset_names)
|
||||
return results
|
||||
|
||||
return [prop_set.Name for prop_set in self.get_applicable(ifc_class, predefined_type, pset_only, qto_only)]
|
||||
|
||||
def is_applicable(self, entity: entity_instance, applicables: str, predefined_type=""):
|
||||
"""applicables can have multiple possible patterns :
|
||||
IfcBoilerType (IfcClass)
|
||||
IfcBoilerType/STEAM (IfcClass/PREDEFINEDTYPE)
|
||||
IfcBoilerType[PerformanceHistory] (IfcClass[PerformanceHistory])
|
||||
IfcBoilerType/STEAM[PerformanceHistory] (IfcClass/PREDEFINEDTYPE[PerformanceHistory])
|
||||
"""
|
||||
for applicable in applicables.split(","):
|
||||
match = re.match(r"(\w+)(\[\w+\])*/*(\w+)*(\[\w+\])*", applicable)
|
||||
if not match:
|
||||
continue
|
||||
# Uncomment if usage found
|
||||
# applicable_perf_history = match.group(2) or match.group(4)
|
||||
if predefined_type and predefined_type != match.group(3):
|
||||
continue
|
||||
|
||||
applicable_class = match.group(1)
|
||||
if entity.name() == applicable_class:
|
||||
return True
|
||||
if entity.supertype():
|
||||
return self.is_applicable(entity.supertype(), applicable_class)
|
||||
return False
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user