mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
Merge branch 'v0.6.0' of github.com:IfcOpenShell/IfcOpenShell into v0.6.0
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user