Merge branch 'v0.6.0' of github.com:IfcOpenShell/IfcOpenShell into v0.6.0

This commit is contained in:
Dion Moult
2021-01-04 13:07:36 +11:00
5 changed files with 72 additions and 108 deletions
@@ -471,39 +471,28 @@ class IfcParser:
continue
results[item_key] = {"ifc": None, "raw": raw, "material": material, "attributes": {"Name": item.name}}
def add_automatic_qtos(self, ifc_class, obj):
def add_automatic_qtos(self, ifc_class: str, obj):
if not obj.data:
return
qto_names = self.get_applicable_qtos(ifc_class)
for name in qto_names:
if name not in schema.ifc.psetqto.qtos:
continue
applicable_qtos = schema.ifc.psetqto.get_applicable(ifc_class, qto_only=True)
for applicable_qto in applicable_qtos:
has_automatic_value = False
props = schema.ifc.psetqto.qtos[name]["HasPropertyTemplates"].keys()
guessed_values = {}
for prop_name in props:
value = self.qto_calculator.guess_quantity(prop_name, props, obj)
prop_names = [p.Name for p in applicable_qto.HasPropertyTemplates]
for prop_name in prop_names:
value = self.qto_calculator.guess_quantity(prop_name, prop_names, obj)
if value:
guessed_values[prop_name] = value
has_automatic_value = True
if has_automatic_value:
qto = obj.BIMObjectProperties.qtos.add()
qto.name = name
for prop_name in props:
qto.name = applicable_qto.Name
for prop_name in prop_names:
prop = qto.properties.add()
prop.name = prop_name
if prop_name in guessed_values:
prop.string_value = str(guessed_values[prop_name])
def get_applicable_qtos(self, ifc_class):
results = []
empty = ifcopenshell.file(schema=self.ifc_export_settings.schema)
element = empty.create_entity(ifc_class)
for ifc_class, qto_names in schema.ifc.applicable_qtos.items():
if element.is_a(ifc_class):
results.extend(qto_names)
return results
def get_product_relating_structure(self, product, obj):
relating_structure = obj.BIMObjectProperties.relating_structure
if relating_structure:
@@ -1701,13 +1690,15 @@ class IfcExporter:
pset["ifc"] = self.file.create_entity("IfcMaterialProperties", **pset["attributes"])
def create_qto_properties(self, qto):
if qto["attributes"]["Name"] in schema.ifc.psetqto.qtos:
return self.create_templated_qto_properties(qto)
qto_template = schema.ifc.psetqto.get_by_name(qto["attributes"]["Name"])
if qto_template:
return self.create_templated_qto_properties(qto, qto_template)
return self.create_custom_qto_properties(qto)
def create_pset_properties(self, pset):
if pset["attributes"]["Name"] in schema.ifc.psetqto.psets:
return self.create_templated_pset_properties(pset)
pset_template = schema.ifc.psetqto.get_by_name(pset["attributes"]["Name"])
if pset_template:
return self.create_templated_pset_properties(pset, pset_template)
return self.create_custom_pset_properties(pset)
def create_custom_pset_properties(self, pset):
@@ -1737,15 +1728,15 @@ class IfcExporter:
)
return properties
def create_templated_pset_properties(self, pset):
def create_templated_pset_properties(self, pset, pset_template):
properties = []
templates = schema.ifc.psetqto.psets[pset["attributes"]["Name"]]["HasPropertyTemplates"]
for name, data in templates.items():
for prop in pset_template.HasPropertyTemplates:
name = prop.Name
if name not in pset["raw"]:
continue
if data.TemplateType == "P_SINGLEVALUE" or data.TemplateType == "P_ENUMERATEDVALUE":
if data.PrimaryMeasureType:
value_type = data.PrimaryMeasureType
if prop.TemplateType == "P_SINGLEVALUE" or prop.TemplateType == "P_ENUMERATEDVALUE":
if prop.PrimaryMeasureType:
value_type = prop.PrimaryMeasureType
else:
# The IFC spec is missing some, so we provide a fallback
value_type = "IfcLabel"
@@ -1755,7 +1746,8 @@ class IfcExporter:
properties.append(
self.file.create_entity("IfcPropertySingleValue", **{"Name": name, "NominalValue": nominal_value})
)
invalid_pset_keys = [k for k in pset["raw"].keys() if k not in templates.keys()]
templates_names = [prop.Name for prop in qto_template.HasPropertyTemplates]
invalid_pset_keys = [k for k in pset["raw"].keys() if k not in templates_names]
if invalid_pset_keys:
self.ifc_export_settings.logger.error(
"One or more properties were invalid in the pset {}: {}".format(
@@ -1764,20 +1756,21 @@ class IfcExporter:
)
return properties
def create_templated_qto_properties(self, qto):
def create_templated_qto_properties(self, qto, qto_template):
properties = []
templates = schema.ifc.psetqto.qtos[qto["attributes"]["Name"]]["HasPropertyTemplates"]
for name, data in templates.items():
for prop in qto_template.HasPropertyTemplates:
name = prop.Name
if name not in qto["raw"]:
continue
if data.TemplateType[0:2] == "Q_":
value_basename = data.TemplateType[2:].title()
if prop.TemplateType[0:2] == "Q_":
value_basename = prop.TemplateType[2:].title()
value_name = f"{value_basename}Value"
class_name = f"IfcQuantity{value_basename}"
properties.append(
self.file.create_entity(class_name, **{"Name": name, value_name: float(qto["raw"][name])})
)
invalid_qto_keys = [k for k in qto["raw"].keys() if k not in templates.keys()]
templates_names = [prop.Name for prop in qto_template.HasPropertyTemplates]
invalid_qto_keys = [k for k in qto["raw"].keys() if k not in templates_names]
if invalid_qto_keys:
self.ifc_export_settings.logger.error(
"One or more properties were invalid in the qto {}/{}: {}".format(
@@ -1382,8 +1382,9 @@ class IfcImporter:
def add_pset(self, pset, props):
new_pset = props.psets.add()
new_pset.name = pset.Name
if new_pset.name in schema.ifc.psetqto.psets:
for prop_name in schema.ifc.psetqto.psets[new_pset.name]["HasPropertyTemplates"].keys():
pset_template = schema.ifc.psetqto.get_by_name(new_pset.name)
if pset_template:
for prop_name in (p.Name for p in pset_template.HasPropertyTemplates):
prop = new_pset.properties.add()
prop.name = prop_name
try:
@@ -1409,8 +1410,9 @@ class IfcImporter:
def add_qto(self, qto, obj):
new_qto = obj.BIMObjectProperties.qtos.add()
new_qto.name = str(qto.Name)
if new_qto.name in schema.ifc.psetqto.qtos:
for prop_name in schema.ifc.psetqto.qtos[new_qto.name]["HasPropertyTemplates"].keys():
qto_template = schema.ifc.psetqto.get_by_name(new_qto.name)
if qto_template:
for prop_name in (p.Name for p in qto_template.HasPropertyTemplates):
prop = new_qto.properties.add()
prop.name = prop_name
for prop in qto.Quantities:
+20 -31
View File
@@ -492,56 +492,46 @@ class AddQto(bpy.types.Operator):
bl_label = "Add Qto"
def execute(self, context):
self.applicable_qtos_cache = {}
name = bpy.context.active_object.BIMObjectProperties.qto_name
if name not in schema.ifc.psetqto.qtos:
qto_template = schema.ifc.psetqto.get_by_name(name)
if not qto_template:
return {"FINISHED"}
for obj in bpy.context.selected_objects:
if "/" not in obj.name or obj.BIMObjectProperties.qtos.find(name) != -1:
continue
applicable_qtos = self.get_applicable_qtos(obj.name.split("/")[0])
applicable_qtos = schema.ifc.psetqto.get_applicable_names(obj.name.split("/")[0], qto_only=True)
if name not in applicable_qtos:
continue
qto = obj.BIMObjectProperties.qtos.add()
qto.name = name
for prop_name in schema.ifc.psetqto.qtos[name]["HasPropertyTemplates"].keys():
for prop_name in (p.Name for p in qto_template.HasPropertyTemplates):
prop = qto.properties.add()
prop.name = prop_name
return {"FINISHED"}
def get_applicable_qtos(self, ifc_class):
if ifc_class not in self.applicable_qtos_cache:
self.applicable_qtos_cache[ifc_class] = schema.ifc.psetqto.get_applicable_names(ifc_class, qto_only=True)
return self.applicable_qtos_cache[ifc_class]
class AddPset(bpy.types.Operator):
bl_idname = "bim.add_pset"
bl_label = "Add Pset"
def execute(self, context):
self.applicable_psets_cache = {}
name = bpy.context.active_object.BIMObjectProperties.pset_name
if name not in schema.ifc.psetqto.psets:
pset_template = schema.ifc.psetqto.get_by_name(name)
if not pset_template:
return {"FINISHED"}
for obj in bpy.context.selected_objects:
if "/" not in obj.name or obj.BIMObjectProperties.psets.find(name) != -1:
continue
applicable_psets = self.get_applicable_psets(obj.name.split("/")[0])
applicable_psets = schema.ifc.psetqto.get_applicable_names(obj.name.split("/")[0], pset_only=True)
if name not in applicable_psets:
continue
pset = obj.BIMObjectProperties.psets.add()
pset.name = name
for prop_name in schema.ifc.psetqto.psets[name]["HasPropertyTemplates"].keys():
for prop_name in (p.Name for p in pset_template.HasPropertyTemplates):
prop = pset.properties.add()
prop.name = prop_name
return {"FINISHED"}
def get_applicable_psets(self, ifc_class):
if ifc_class not in self.applicable_psets_cache:
self.applicable_psets_cache[ifc_class] = schema.ifc.psetqto.get_applicable_names(ifc_class, pset_only=True)
return self.applicable_psets_cache[ifc_class]
class RemovePset(bpy.types.Operator):
bl_idname = "bim.remove_pset"
@@ -582,13 +572,14 @@ class AddMaterialPset(bpy.types.Operator):
def execute(self, context):
material = bpy.context.active_object.active_material
name = material.BIMMaterialProperties.pset_name
if name not in schema.ifc.psetqto.psets:
pset_template = schema.ifc.psetqto.get_by_name(name)
if not pset_template:
return {"FINISHED"}
if material.BIMMaterialProperties.psets.find(name) != -1:
return {"FINISHED"}
pset = material.BIMMaterialProperties.psets.add()
pset.name = name
for prop_name in schema.ifc.psetqto.psets[name]["HasPropertyTemplates"].keys():
for prop_name in (p.Name for p in pset_template.HasPropertyTemplates):
prop = pset.properties.add()
prop.name = prop_name
return {"FINISHED"}
@@ -2371,18 +2362,20 @@ class CopyPropertyToSelection(bpy.types.Operator):
prop_value: bpy.props.StringProperty()
def execute(self, context):
self.applicable_psets_cache = {}
for obj in bpy.context.selected_objects:
if "/" not in obj.name:
continue
pset = obj.BIMObjectProperties.psets.get(self.pset_name)
if not pset:
applicable_psets = self.get_applicable_psets(obj.name.split("/")[0])
if self.pset_name not in applicable_psets:
applicable_psets = schema.ifc.psetqto.get_applicable(obj.name.split("/")[0], pset_only=True)
for pset_template in applicable_psets:
if pset_template.Name == self.pset_name:
break
else:
continue
pset = obj.BIMObjectProperties.psets.add()
pset.name = self.pset_name
for template_prop_name in schema.ifc.psetqto.psets[self.pset_name]["HasPropertyTemplates"].keys():
for template_prop_name in (p.Name for p in pset_template.HasPropertyTemplates):
prop = pset.properties.add()
prop.name = template_prop_name
prop = pset.properties.get(self.prop_name)
@@ -2390,11 +2383,6 @@ class CopyPropertyToSelection(bpy.types.Operator):
prop.string_value = self.prop_value
return {"FINISHED"}
def get_applicable_psets(self, ifc_class):
if ifc_class not in self.applicable_psets_cache:
self.applicable_psets_cache[ifc_class] = schema.ifc.psetqto.get_applicable_names(ifc_class, pset_only=True)
return self.applicable_psets_cache[ifc_class]
class CopyAttributeToSelection(bpy.types.Operator):
bl_idname = "bim.copy_attribute_to_selection"
@@ -3193,11 +3181,12 @@ class GuessQuantity(bpy.types.Operator):
prop.string_value = str(round(quantity, 3))
def add_qto(self, obj, name):
if name not in schema.ifc.psetqto.qtos:
qto_template = schema.ifc.psetqto.get_by_name(name)
if not qto_template:
return
qto = obj.BIMObjectProperties.qtos.add()
qto.name = name
for prop_name in schema.ifc.psetqto.qtos[name]["HasPropertyTemplates"].keys():
for prop_name in (p.Name for p in qto_template.HasPropertyTemplates):
prop = qto.properties.add()
prop.name = prop_name
return qto
@@ -30,7 +30,7 @@ class IfcSchema:
self.property_files = []
property_paths = self.data_dir.joinpath("pset").glob("*.ifc")
self.psetqto = ifcopenshell.util.pset.PsetQto("IFC4", use_cache=True)
self.psetqto = ifcopenshell.util.pset.PsetQto("IFC4")
for path in property_paths:
self.psetqto.templates.append(ifcopenshell.open(path))
@@ -1,6 +1,7 @@
import pathlib
import re
from typing import List, Generator
from functools import lru_cache
from typing import List, Generator, Optional
import ifcopenshell
from ifcopenshell.entity_instance import entity_instance
@@ -11,34 +12,15 @@ class PsetQto:
"IFC4": "Pset_IFC4_ADD2.ifc",
}
def __init__(self, schema: str, templates=None, use_cache=False) -> None:
def __init__(self, schema: str, templates=None) -> 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 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)
@lru_cache
def get_applicable(
self, ifc_class="", predefined_type="", pset_only=False, qto_only=False
) -> Generator[entity_instance, entity_instance, None]:
@@ -58,22 +40,9 @@ class PsetQto:
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=""):
def is_applicable(self, entity: entity_instance, applicables: str, predefined_type="") -> bool:
"""applicables can have multiple possible patterns :
IfcBoilerType (IfcClass)
IfcBoilerType/STEAM (IfcClass/PREDEFINEDTYPE)
@@ -95,3 +64,14 @@ class PsetQto:
if entity.supertype():
return self.is_applicable(entity.supertype(), applicable_class)
return False
@lru_cache
def get_by_name(self, name: str) -> Optional[entity_instance]:
for template in self.templates:
for prop_set in template.by_type("IfcPropertySetTemplate"):
if prop_set.Name == name:
return prop_set
return None
def is_templated(self, name: str) -> bool:
return bool(self.get_by_name(name))