From ba8689468ee448624ab6421b6f6cbc252100e577 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 2 Nov 2020 22:05:32 +1100 Subject: [PATCH 1/9] You can now add attributes in bulk by selecting multiple before adding. See #1060. --- .../blenderbim/bim/operator.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 5a21e09955..3a3f06f3a4 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -1221,9 +1221,22 @@ class AddAttribute(bpy.types.Operator): bl_label = "Add Attribute" def execute(self, context): - if bpy.context.active_object.BIMObjectProperties.applicable_attributes: - attribute = bpy.context.active_object.BIMObjectProperties.attributes.add() - attribute.name = bpy.context.active_object.BIMObjectProperties.applicable_attributes + if not bpy.context.active_object.BIMObjectProperties.applicable_attributes: + return {"FINISHED"} + name = bpy.context.active_object.BIMObjectProperties.applicable_attributes + schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name( + bpy.context.scene.BIMProperties.export_schema) + for obj in bpy.context.selected_objects: + if ( + '/' not in obj.name + or obj.BIMObjectProperties.attributes.find(name) != -1 + ): + continue + entity = schema.declaration_by_name(obj.name.split('/')[0]) + if name not in [a.name() for a in entity.all_attributes()]: + continue + attribute = obj.BIMObjectProperties.attributes.add() + attribute.name = name if attribute.name == "GlobalId": attribute.string_value = ifcopenshell.guid.new() return {"FINISHED"} From 96b8bc638b82f3d9583aaebcd8d8a0e338a67009 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 2 Nov 2020 22:12:31 +1100 Subject: [PATCH 2/9] Add support for deleting attributes in bulk. See #1060. --- .../blenderbim/bim/operator.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 3a3f06f3a4..ffb73219e1 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -1224,15 +1224,11 @@ class AddAttribute(bpy.types.Operator): if not bpy.context.active_object.BIMObjectProperties.applicable_attributes: return {"FINISHED"} name = bpy.context.active_object.BIMObjectProperties.applicable_attributes - schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name( - bpy.context.scene.BIMProperties.export_schema) + schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(bpy.context.scene.BIMProperties.export_schema) for obj in bpy.context.selected_objects: - if ( - '/' not in obj.name - or obj.BIMObjectProperties.attributes.find(name) != -1 - ): + if "/" not in obj.name or obj.BIMObjectProperties.attributes.find(name) != -1: continue - entity = schema.declaration_by_name(obj.name.split('/')[0]) + entity = schema.declaration_by_name(obj.name.split("/")[0]) if name not in [a.name() for a in entity.all_attributes()]: continue attribute = obj.BIMObjectProperties.attributes.add() @@ -1259,7 +1255,13 @@ class RemoveAttribute(bpy.types.Operator): attribute_index: bpy.props.IntProperty() def execute(self, context): - bpy.context.active_object.BIMObjectProperties.attributes.remove(self.attribute_index) + name = bpy.context.active_object.BIMObjectProperties.attributes[self.attribute_index].name + for obj in bpy.context.selected_objects: + if "/" not in obj.name: + continue + index = obj.BIMObjectProperties.attributes.find(name) + if index != -1: + obj.BIMObjectProperties.attributes.remove(index) return {"FINISHED"} From 67baad51826876f1ff74fb021e21db37b4886606 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 3 Nov 2020 09:53:03 +1100 Subject: [PATCH 3/9] Fix #971. New utility function to query which psets/qtos apply to a class. --- .../blenderbim/bim/export_ifc.py | 20 +++++----- .../blenderbim/bim/import_ifc.py | 13 ++++--- .../blenderbim/bim/operator.py | 26 ++++++------- src/ifcblenderexport/blenderbim/bim/prop.py | 21 +++++----- src/ifcblenderexport/blenderbim/bim/schema.py | 20 ++-------- .../ifcopenshell/util/pset.py | 39 +++++++++++++++++++ 6 files changed, 78 insertions(+), 61 deletions(-) create mode 100644 src/ifcopenshell-python/ifcopenshell/util/pset.py diff --git a/src/ifcblenderexport/blenderbim/bim/export_ifc.py b/src/ifcblenderexport/blenderbim/bim/export_ifc.py index 3e8f1edf23..f43d4cfef6 100644 --- a/src/ifcblenderexport/blenderbim/bim/export_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/export_ifc.py @@ -6,12 +6,13 @@ import datetime import os import zipfile import tempfile +import ifcopenshell +import ifcopenshell.util.pset from pathlib import Path from mathutils import Vector, Matrix from .helper import SIUnitHelper, get_representation_elements from . import schema from . import ifc -import ifcopenshell import addon_utils @@ -466,10 +467,10 @@ class IfcParser: return qto_names = self.get_applicable_qtos(ifc_class) for name in qto_names: - if name not in schema.ifc.qtos: + if name not in ifcopenshell.util.pset.qtos: continue has_automatic_value = False - props = schema.ifc.qtos[name]["HasPropertyTemplates"].keys() + props = ifcopenshell.util.pset.qtos[name]["HasPropertyTemplates"].keys() guessed_values = {} for prop_name in props: value = self.qto_calculator.guess_quantity(prop_name, props, obj) @@ -1666,12 +1667,12 @@ class IfcExporter: ) def create_qto_properties(self, qto): - if qto["attributes"]["Name"] in schema.ifc.qtos: + if qto["attributes"]["Name"] in ifcopenshell.util.pset.qtos: return self.create_templated_qto_properties(qto) return self.create_custom_qto_properties(qto) def create_pset_properties(self, pset): - if pset["attributes"]["Name"] in schema.ifc.psets: + if pset["attributes"]["Name"] in ifcopenshell.util.pset.psets: return self.create_templated_pset_properties(pset) return self.create_custom_pset_properties(pset) @@ -1704,7 +1705,7 @@ class IfcExporter: def create_templated_pset_properties(self, pset): properties = [] - templates = schema.ifc.psets[pset["attributes"]["Name"]]["HasPropertyTemplates"] + templates = ifcopenshell.util.pset.psets[pset["attributes"]["Name"]]["HasPropertyTemplates"] for name, data in templates.items(): if name not in pset["raw"]: continue @@ -1731,7 +1732,7 @@ class IfcExporter: def create_templated_qto_properties(self, qto): properties = [] - templates = schema.ifc.qtos[qto["attributes"]["Name"]]["HasPropertyTemplates"] + templates = ifcopenshell.util.pset.qtos[qto["attributes"]["Name"]]["HasPropertyTemplates"] for name, data in templates.items(): if name not in qto["raw"]: continue @@ -3157,10 +3158,7 @@ class IfcExporter: ) def relate_spaces_to_boundary_elements(self): - for ( - relating_space_index, - relationships, - ) in self.ifc_parser.rel_space_boundaries.items(): + for (relating_space_index, relationships,) in self.ifc_parser.rel_space_boundaries.items(): for relationship in relationships: relationship["attributes"]["GlobalId"] = ifcopenshell.guid.new() relationship["attributes"]["RelatedBuildingElement"] = self.ifc_parser.products[ diff --git a/src/ifcblenderexport/blenderbim/bim/import_ifc.py b/src/ifcblenderexport/blenderbim/bim/import_ifc.py index 4056ac18f3..0fe458f506 100644 --- a/src/ifcblenderexport/blenderbim/bim/import_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/import_ifc.py @@ -3,6 +3,7 @@ import ifcopenshell.geom import ifcopenshell.util.geolocation import ifcopenshell.util.selector import ifcopenshell.util.element +import ifcopenshell.util.pset import bpy import bmesh import os @@ -206,8 +207,8 @@ class MaterialCreator: def add_pset(self, pset, obj): new_pset = obj.BIMMaterialProperties.psets.add() new_pset.name = pset.Name - if new_pset.name in schema.ifc.psets: - for prop_name in schema.ifc.psets[new_pset.name]["HasPropertyTemplates"].keys(): + if new_pset.name in ifcopenshell.util.pset.psets: + for prop_name in ifcopenshell.util.pset.psets[new_pset.name]["HasPropertyTemplates"].keys(): prop = new_pset.properties.add() prop.name = prop_name for prop in pset.Properties: @@ -1338,8 +1339,8 @@ class IfcImporter: def add_pset(self, pset, obj): new_pset = obj.BIMObjectProperties.psets.add() new_pset.name = pset.Name - if new_pset.name in schema.ifc.psets: - for prop_name in schema.ifc.psets[new_pset.name]["HasPropertyTemplates"].keys(): + if new_pset.name in ifcopenshell.util.pset.psets: + for prop_name in ifcopenshell.util.pset.psets[new_pset.name]["HasPropertyTemplates"].keys(): prop = new_pset.properties.add() prop.name = prop_name # Invalid IFC, but some vendors like Solidworks do this so we accomodate it @@ -1358,8 +1359,8 @@ 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.qtos: - for prop_name in schema.ifc.qtos[new_qto.name]["HasPropertyTemplates"].keys(): + if new_qto.name in ifcopenshell.util.pset.qtos: + for prop_name in ifcopenshell.util.pset.qtos[new_qto.name]["HasPropertyTemplates"].keys(): prop = new_qto.properties.add() prop.name = prop_name for prop in qto.Quantities: diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index ffb73219e1..4ddd4229dc 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -9,6 +9,7 @@ import subprocess import ifcopenshell import ifcopenshell.util.selector import ifcopenshell.util.geolocation +import ifcopenshell.util.pset import tempfile from . import export_ifc from . import import_ifc @@ -836,11 +837,11 @@ class AddQto(bpy.types.Operator): def execute(self, context): name = bpy.context.active_object.BIMObjectProperties.qto_name - if name not in schema.ifc.qtos: + if name not in ifcopenshell.util.pset.qtos: return {"FINISHED"} qto = bpy.context.active_object.BIMObjectProperties.qtos.add() qto.name = name - for prop_name in schema.ifc.qtos[name]["HasPropertyTemplates"].keys(): + for prop_name in ifcopenshell.util.pset.qtos[name]["HasPropertyTemplates"].keys(): prop = qto.properties.add() prop.name = prop_name return {"FINISHED"} @@ -852,11 +853,11 @@ class AddPset(bpy.types.Operator): def execute(self, context): pset_name = bpy.context.active_object.BIMObjectProperties.pset_name - if pset_name not in schema.ifc.psets: + if pset_name not in ifcopenshell.util.pset.psets: return {"FINISHED"} pset = bpy.context.active_object.BIMObjectProperties.psets.add() pset.name = pset_name - for prop_name in schema.ifc.psets[pset_name]["HasPropertyTemplates"].keys(): + for prop_name in ifcopenshell.util.pset.psets[pset_name]["HasPropertyTemplates"].keys(): prop = pset.properties.add() prop.name = prop_name return {"FINISHED"} @@ -2729,7 +2730,6 @@ class CopyPropertyToSelection(bpy.types.Operator): def execute(self, context): self.applicable_psets_cache = {} - self.empty = ifcopenshell.file() for obj in bpy.context.selected_objects: if "/" not in obj.name: continue @@ -2740,7 +2740,7 @@ class CopyPropertyToSelection(bpy.types.Operator): continue pset = obj.BIMObjectProperties.psets.add() pset.name = self.pset_name - for template_prop_name in schema.ifc.psets[self.pset_name]["HasPropertyTemplates"].keys(): + for template_prop_name in ifcopenshell.util.pset.psets[self.pset_name]["HasPropertyTemplates"].keys(): prop = pset.properties.add() prop.name = template_prop_name prop = pset.properties.get(self.prop_name) @@ -2748,15 +2748,11 @@ class CopyPropertyToSelection(bpy.types.Operator): prop.string_value = self.prop_value return {"FINISHED"} - # TODO: move into util module. See bug #971 def get_applicable_psets(self, element_class): if element_class not in self.applicable_psets_cache: - element = self.empty.create_entity(element_class) - applicable_psets = [] - for ifc_class, pset_names in schema.ifc.applicable_psets.items(): - if element.is_a(ifc_class): - applicable_psets.extend(pset_names) - self.applicable_psets_cache[element_class] = applicable_psets + self.applicable_psets_cache[element_class] = ifcopenshell.util.pset.get_applicable_psetqtos( + bpy.context.scene.BIMProperties.export_schema, element_class, is_pset=True + ) return self.applicable_psets_cache[element_class] @@ -3604,11 +3600,11 @@ class GuessQuantity(bpy.types.Operator): prop.string_value = str(round(quantity, 3)) def add_qto(self, obj, name): - if name not in schema.ifc.qtos: + if name not in ifcopenshell.util.pset.qtos: return qto = obj.BIMObjectProperties.qtos.add() qto.name = name - for prop_name in schema.ifc.qtos[name]["HasPropertyTemplates"].keys(): + for prop_name in ifcopenshell.util.pset.qtos[name]["HasPropertyTemplates"].keys(): prop = qto.properties.add() prop.name = prop_name return qto diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index bdd76e8ff2..99d12d58d5 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -2,6 +2,7 @@ import json import os import csv import ifcopenshell +import ifcopenshell.util.pset from pathlib import Path from . import export_ifc from . import schema @@ -429,29 +430,25 @@ def refreshReferences(self, context): context.scene.BIMProperties.classification_references.root = "" -# TODO: move into util module. See bug #971 def getPsetNames(self, context): global psetnames_enum psetnames_enum.clear() if "/" in context.active_object.name and context.active_object.name.split("/")[0] in schema.ifc.elements: - empty = ifcopenshell.file() - element = empty.create_entity(context.active_object.name.split("/")[0]) - for ifc_class, pset_names in schema.ifc.applicable_psets.items(): - if element.is_a(ifc_class): - psetnames_enum.extend([(p, p, "") for p in pset_names]) + pset_names = ifcopenshell.util.pset.get_applicable_psetqtos( + bpy.context.scene.BIMProperties.export_schema, context.active_object.name.split("/")[0], is_pset=True + ) + psetnames_enum.extend([(p, p, "") for p in pset_names]) return psetnames_enum -# TODO: move into util module. See bug #971 def getQtoNames(self, context): global qtonames_enum qtonames_enum.clear() if "/" in context.active_object.name and context.active_object.name.split("/")[0] in schema.ifc.elements: - empty = ifcopenshell.file() - element = empty.create_entity(context.active_object.name.split("/")[0]) - for ifc_class, qto_names in schema.ifc.applicable_qtos.items(): - if element.is_a(ifc_class): - qtonames_enum.extend([(q, q, "") for q in qto_names]) + qto_names = ifcopenshell.util.pset.get_applicable_psetqtos( + bpy.context.scene.BIMProperties.export_schema, context.active_object.name.split("/")[0], is_qto=True + ) + qtonames_enum.extend([(q, q, "") for q in qto_names]) return qtonames_enum diff --git a/src/ifcblenderexport/blenderbim/bim/schema.py b/src/ifcblenderexport/blenderbim/bim/schema.py index 4a34a042d3..958567ac09 100644 --- a/src/ifcblenderexport/blenderbim/bim/schema.py +++ b/src/ifcblenderexport/blenderbim/bim/schema.py @@ -1,6 +1,7 @@ import os import json import ifcopenshell +import ifcopenshell.util.pset import bpy from pathlib import Path @@ -30,14 +31,10 @@ class IfcSchema: self.property_files = [] property_paths = Path(os.path.join(self.data_dir, "pset")).glob("*.ifc") for path in property_paths: - self.property_files.append(ifcopenshell.open(path)) - self.property_files.append(ifcopenshell.open(os.path.join(self.schema_dir, "Pset_IFC4_ADD2.ifc"))) + ifcopenshell.util.pset.load_property_set_template(path) + ifcopenshell.util.pset.load_property_set_template(os.path.join(self.schema_dir, "Pset_IFC4_ADD2.ifc")) self.classification_files = {} - self.psets = {} - self.qtos = {} - self.applicable_psets = {} - self.applicable_qtos = {} self.classifications = {} self.load() @@ -50,17 +47,6 @@ class IfcSchema: with open(os.path.join(self.schema_dir, "ifc_types_IFC4.json")) as f: self.type_map = json.load(f) - for property_file in self.property_files: - for prop in property_file.by_type("IfcPropertySetTemplate"): - if prop.Name[0:4] == "Qto_": - self.qtos[prop.Name] = {"HasPropertyTemplates": {p.Name: p for p in prop.HasPropertyTemplates}} - entity = prop.ApplicableEntity if prop.ApplicableEntity else "IfcRoot" - self.applicable_qtos.setdefault(entity, []).append(prop.Name) - else: - self.psets[prop.Name] = {"HasPropertyTemplates": {p.Name: p for p in prop.HasPropertyTemplates}} - entity = prop.ApplicableEntity if prop.ApplicableEntity else "IfcRoot" - self.applicable_psets.setdefault(entity, []).append(prop.Name) - def load_classification(self, name, classification_index=None): if name not in self.classifications: if classification_index is not None: diff --git a/src/ifcopenshell-python/ifcopenshell/util/pset.py b/src/ifcopenshell-python/ifcopenshell/util/pset.py new file mode 100644 index 0000000000..5e5a541528 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/util/pset.py @@ -0,0 +1,39 @@ +import ifcopenshell + +property_set_template_files = [] +psets = {} +qtos = {} +applicable_psets = {} +applicable_qtos = {} + + +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) + + +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 + + 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 From cde2d556949ce0a37269de3422c19888595d4933 Mon Sep 17 00:00:00 2001 From: htlcnn Date: Tue, 3 Nov 2020 12:55:45 +0700 Subject: [PATCH 4/9] add .git-blame-ignore-revs --- .git-blame-ignore-revs | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000000..fab942429d --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,8 @@ +c14f5eeca042232025797990396232e4feab53e7 +d6881e833da0b6278313870d37d42ca6ece8686a +892be5444c5bd01a601c99243ac8d3e7b2f0e779 +d169a964dd7e9ab5c98b35a82bcbc76dc6229a7a +4a6ec11f6f84a3d1fed2ee9b2f85d783ac3daa9d +2c9d6a47f4d24a923069775206bf734feeb14830 +8f1743ed64a2a52c201a1ef5e312b4d3a7a922cf +ad7f030344c405e10cfa7d5cf06d82c121eeb825 From 8b0f0fe60fc8499fe368113e4bfd7602be38b5b5 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 3 Nov 2020 20:59:48 +1100 Subject: [PATCH 5/9] You can now add / remove psets in bulk. See #1060. --- .../blenderbim/bim/operator.py | 46 +++++++++++++------ 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 4ddd4229dc..c089abab20 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -852,16 +852,30 @@ class AddPset(bpy.types.Operator): bl_label = "Add Pset" def execute(self, context): - pset_name = bpy.context.active_object.BIMObjectProperties.pset_name - if pset_name not in ifcopenshell.util.pset.psets: + self.applicable_psets_cache = {} + name = bpy.context.active_object.BIMObjectProperties.pset_name + if name not in ifcopenshell.util.pset.psets: return {"FINISHED"} - pset = bpy.context.active_object.BIMObjectProperties.psets.add() - pset.name = pset_name - for prop_name in ifcopenshell.util.pset.psets[pset_name]["HasPropertyTemplates"].keys(): - prop = pset.properties.add() - prop.name = prop_name + 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]) + if name not in applicable_psets: + continue + pset = obj.BIMObjectProperties.psets.add() + pset.name = name + for prop_name in ifcopenshell.util.pset.psets[name]["HasPropertyTemplates"].keys(): + 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] = ifcopenshell.util.pset.get_applicable_psetqtos( + bpy.context.scene.BIMProperties.export_schema, ifc_class, is_pset=True + ) + return self.applicable_psets_cache[ifc_class] + class RemovePset(bpy.types.Operator): bl_idname = "bim.remove_pset" @@ -869,7 +883,13 @@ class RemovePset(bpy.types.Operator): pset_index: bpy.props.IntProperty() def execute(self, context): - bpy.context.active_object.BIMObjectProperties.psets.remove(self.pset_index) + name = bpy.context.active_object.BIMObjectProperties.psets[self.pset_index].name + for obj in bpy.context.selected_objects: + if "/" not in obj.name: + continue + index = obj.BIMObjectProperties.psets.find(name) + if index != -1: + obj.BIMObjectProperties.psets.remove(index) return {"FINISHED"} @@ -2748,12 +2768,12 @@ class CopyPropertyToSelection(bpy.types.Operator): prop.string_value = self.prop_value return {"FINISHED"} - def get_applicable_psets(self, element_class): - if element_class not in self.applicable_psets_cache: - self.applicable_psets_cache[element_class] = ifcopenshell.util.pset.get_applicable_psetqtos( - bpy.context.scene.BIMProperties.export_schema, element_class, is_pset=True + def get_applicable_psets(self, ifc_class): + if ifc_class not in self.applicable_psets_cache: + self.applicable_psets_cache[ifc_class] = ifcopenshell.util.pset.get_applicable_psetqtos( + bpy.context.scene.BIMProperties.export_schema, ifc_class, is_pset=True ) - return self.applicable_psets_cache[element_class] + return self.applicable_psets_cache[ifc_class] class BIM_OT_ChangeClassificationLevel(bpy.types.Operator): From 81cba70f33efd316e7c82963c39e73783980e5f3 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 3 Nov 2020 21:04:50 +1100 Subject: [PATCH 6/9] You can now add / remove qtos in bulk. See #1060. --- src/ifcblenderexport/Makefile | 2 ++ .../blenderbim/bim/operator.py | 32 +++++++++++++++---- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/ifcblenderexport/Makefile b/src/ifcblenderexport/Makefile index f3af203803..197ddb8e4d 100644 --- a/src/ifcblenderexport/Makefile +++ b/src/ifcblenderexport/Makefile @@ -38,10 +38,12 @@ endif cd dist/blenderbim/libs/site/packages/ifcopenshell/util && rm -f geolocation.py cd dist/blenderbim/libs/site/packages/ifcopenshell/util && rm -f selector.py cd dist/blenderbim/libs/site/packages/ifcopenshell/util && rm -f unit.py + cd dist/blenderbim/libs/site/packages/ifcopenshell/util && rm -f pset.py cd dist/blenderbim/libs/site/packages/ifcopenshell/util && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcopenshell-python/ifcopenshell/util/element.py cd dist/blenderbim/libs/site/packages/ifcopenshell/util && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcopenshell-python/ifcopenshell/util/geolocation.py cd dist/blenderbim/libs/site/packages/ifcopenshell/util && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcopenshell-python/ifcopenshell/util/selector.py cd dist/blenderbim/libs/site/packages/ifcopenshell/util && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcopenshell-python/ifcopenshell/util/unit.py + cd dist/blenderbim/libs/site/packages/ifcopenshell/util && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcopenshell-python/ifcopenshell/util/pset.py # Provides Python OCC functionality for cutting IFC geometry for construction documentation mkdir dist/working diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index c089abab20..943bf0d5f3 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -836,16 +836,30 @@ 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 ifcopenshell.util.pset.qtos: return {"FINISHED"} - qto = bpy.context.active_object.BIMObjectProperties.qtos.add() - qto.name = name - for prop_name in ifcopenshell.util.pset.qtos[name]["HasPropertyTemplates"].keys(): - prop = qto.properties.add() - prop.name = prop_name + 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]) + if name not in applicable_qtos: + continue + qto = obj.BIMObjectProperties.qtos.add() + qto.name = name + for prop_name in ifcopenshell.util.pset.qtos[name]["HasPropertyTemplates"].keys(): + 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] = ifcopenshell.util.pset.get_applicable_psetqtos( + bpy.context.scene.BIMProperties.export_schema, ifc_class, is_qto=True + ) + return self.applicable_qtos_cache[ifc_class] + class AddPset(bpy.types.Operator): bl_idname = "bim.add_pset" @@ -899,7 +913,13 @@ class RemoveQto(bpy.types.Operator): index: bpy.props.IntProperty() def execute(self, context): - bpy.context.active_object.BIMObjectProperties.qtos.remove(self.index) + name = bpy.context.active_object.BIMObjectProperties.qtos[self.index].name + for obj in bpy.context.selected_objects: + if "/" not in obj.name: + continue + index = obj.BIMObjectProperties.qtos.find(name) + if index != -1: + obj.BIMObjectProperties.qtos.remove(index) return {"FINISHED"} From ad1cc7f0b93c9c5cd791cb8ac62b8ae46a131dc4 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 3 Nov 2020 21:14:39 +1100 Subject: [PATCH 7/9] Fix #1011. Add example tooltips of add-on preferences external commands. --- src/ifcblenderexport/blenderbim/bim/ui.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index dfca8e7705..b92cae0513 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -1891,10 +1891,10 @@ class BIM_UL_classifications(bpy.types.UIList): class BIM_ADDON_preferences(bpy.types.AddonPreferences): bl_idname = "blenderbim" - svg2pdf_command: StringProperty(name="SVG to PDF Command") - svg2dxf_command: StringProperty(name="SVG to DXF Command") - svg_command: StringProperty(name="SVG Command") - pdf_command: StringProperty(name="PDF Command") + svg2pdf_command: StringProperty(name="SVG to PDF Command", description="E.g. [['inkscape', svg, '-o', pdf]]") + svg2dxf_command: StringProperty(name="SVG to DXF Command", description="E.g. [['inkscape', svg, '-o', eps], ['pstoedit', '-dt', '-f', 'dxf:-polyaslines -mm', eps, dxf, '-psarg', '-dNOSAFER']]") + svg_command: StringProperty(name="SVG Command", description="E.g. [['firefox-bin', path]]") + pdf_command: StringProperty(name="PDF Command", description="E.g. [['firefox-bin', path]]") def draw(self, context): layout = self.layout From b07d999bced1a2d84334350de4c2848054c05c16 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 3 Nov 2020 22:59:35 +1100 Subject: [PATCH 8/9] Fix #946. More intuitive UI for reassigning IFC class of the active object. --- .../blenderbim/bim/__init__.py | 1 + .../blenderbim/bim/operator.py | 15 +++++++ src/ifcblenderexport/blenderbim/bim/prop.py | 1 + src/ifcblenderexport/blenderbim/bim/ui.py | 39 +++++++++++-------- 4 files changed, 40 insertions(+), 16 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index e52d3ec08b..c6aded9647 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -21,6 +21,7 @@ if bpy is not None: from .module.model import opening as model_opening classes = ( + operator.ReassignClass, operator.AssignClass, operator.UnassignClass, operator.SelectClass, diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 943bf0d5f3..0355a5fddf 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -214,6 +214,20 @@ class SelectPset(bpy.types.Operator): return {"FINISHED"} +class ReassignClass(bpy.types.Operator): + bl_idname = "bim.reassign_class" + bl_label = "Reassign IFC Class" + + def execute(self, context): + obj = bpy.context.active_object + bpy.context.active_object.BIMObjectProperties.is_reassigning_class = True + bpy.context.scene.BIMProperties.ifc_class = obj.name.split("/")[0] + predefined_type = obj.BIMObjectProperties.attributes.get("PredefinedType") + if predefined_type: + bpy.context.scene.BIMProperties.ifc_predefined_type = predefined_type.string_value + return {"FINISHED"} + + class AssignClass(bpy.types.Operator): bl_idname = "bim.assign_class" bl_label = "Assign IFC Class" @@ -222,6 +236,7 @@ class AssignClass(bpy.types.Operator): def execute(self, context): if self.object_name: objects = [bpy.data.objects.get(self.object_name)] + objects[0].BIMObjectProperties.is_reassigning_class = False else: objects = bpy.context.selected_objects for obj in objects: diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 99d12d58d5..0be6873209 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -1589,6 +1589,7 @@ class BoundaryCondition(PropertyGroup): class BIMObjectProperties(PropertyGroup): + is_reassigning_class: BoolProperty(name="Is Reassigning Class") global_ids: CollectionProperty(name="GlobalIds", type=GlobalId) attributes: CollectionProperty(name="Attributes", type=Attribute) relating_type: PointerProperty(name="Type Product", type=bpy.types.Object) diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index b92cae0513..4077b84ae0 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -21,24 +21,28 @@ class BIM_PT_object(Panel): props = context.active_object.BIMObjectProperties bim_properties = context.scene.BIMProperties - row = layout.row() - row.prop(bim_properties, "ifc_product") - row = layout.row() - row.prop(bim_properties, "ifc_class") - if bim_properties.ifc_predefined_type: + if props.is_reassigning_class or "/" not in context.active_object.name: row = layout.row() - row.prop(bim_properties, "ifc_predefined_type") - if bim_properties.ifc_predefined_type == "USERDEFINED": + row.prop(bim_properties, "ifc_product") row = layout.row() - row.prop(bim_properties, "ifc_userdefined_type") - row = layout.row(align=True) - if "Ifc" not in context.active_object.name: - op = row.operator("bim.assign_class") + row.prop(bim_properties, "ifc_class") + if bim_properties.ifc_predefined_type: + row = layout.row() + row.prop(bim_properties, "ifc_predefined_type") + if bim_properties.ifc_predefined_type == "USERDEFINED": + row = layout.row() + row.prop(bim_properties, "ifc_userdefined_type") + row = layout.row(align=True) + if "Ifc" not in context.active_object.name: + op = row.operator("bim.assign_class") + else: + op = row.operator("bim.assign_class") + op.object_name = context.active_object.name + op = row.operator("bim.unassign_class", icon="X", text="") + op.object_name = context.active_object.name else: - op = row.operator("bim.assign_class", text="Reassign IFC Class") - op.object_name = context.active_object.name - op = row.operator("bim.unassign_class", icon="X", text="") - op.object_name = context.active_object.name + row = layout.row() + row.operator("bim.reassign_class", text="Reassign IFC Class") if "Ifc" not in context.active_object.name: return @@ -1892,7 +1896,10 @@ class BIM_UL_classifications(bpy.types.UIList): class BIM_ADDON_preferences(bpy.types.AddonPreferences): bl_idname = "blenderbim" svg2pdf_command: StringProperty(name="SVG to PDF Command", description="E.g. [['inkscape', svg, '-o', pdf]]") - svg2dxf_command: StringProperty(name="SVG to DXF Command", description="E.g. [['inkscape', svg, '-o', eps], ['pstoedit', '-dt', '-f', 'dxf:-polyaslines -mm', eps, dxf, '-psarg', '-dNOSAFER']]") + svg2dxf_command: StringProperty( + name="SVG to DXF Command", + description="E.g. [['inkscape', svg, '-o', eps], ['pstoedit', '-dt', '-f', 'dxf:-polyaslines -mm', eps, dxf, '-psarg', '-dNOSAFER']]", + ) svg_command: StringProperty(name="SVG Command", description="E.g. [['firefox-bin', path]]") pdf_command: StringProperty(name="PDF Command", description="E.g. [['firefox-bin', path]]") From 3240ec63b77c614619645fcf19029c3bb6decbbd Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 4 Nov 2020 16:47:49 +1100 Subject: [PATCH 9/9] New recipe to extract out elements into its own IFC --- src/ifcpatch/recipes/ExtractElements.py | 58 +++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/ifcpatch/recipes/ExtractElements.py diff --git a/src/ifcpatch/recipes/ExtractElements.py b/src/ifcpatch/recipes/ExtractElements.py new file mode 100644 index 0000000000..c42ee0ddef --- /dev/null +++ b/src/ifcpatch/recipes/ExtractElements.py @@ -0,0 +1,58 @@ +import ifcopenshell +import ifcopenshell.util.selector + + +class Patcher: + def __init__(self, src, file, logger, args=None): + self.src = src + self.file = file + self.logger = logger + self.args = args + + def patch(self): + self.contained_ins = {} + self.aggregates = {} + self.new = ifcopenshell.file(schema=self.file.wrapped_data.schema) + self.owner_history = None + for owner_history in self.file.by_type("IfcOwnerHistory"): + self.owner_history = self.new.add(owner_history) + break + selector = ifcopenshell.util.selector.Selector() + for element in selector.parse(self.file, self.args[0]): + self.add_element(element) + self.create_spatial_tree() + self.file = self.new + + def add_element(self, element): + new_element = self.new.add(element) + for rel in element.ContainedInStructure: + spatial_element = rel.RelatingStructure + new_spatial_element = self.new.add(spatial_element) + self.contained_ins.setdefault(spatial_element.GlobalId, set()).add(new_element) + self.add_spatial_tree(spatial_element, new_spatial_element) + + def add_spatial_tree(self, spatial_element, new_spatial_element): + for rel in spatial_element.Decomposes: + new = self.new.add(rel.RelatingObject) + self.aggregates.setdefault(rel.RelatingObject.GlobalId, set()).add(new_spatial_element) + self.add_spatial_tree(rel.RelatingObject, new) + + def create_spatial_tree(self): + for relating_structure, related_elements in self.contained_ins.items(): + self.new.createIfcRelContainedInSpatialStructure( + ifcopenshell.guid.new(), + self.owner_history, + None, + None, + list(related_elements), + self.new.by_guid(relating_structure), + ) + for relating_object, related_objects in self.aggregates.items(): + self.new.createIfcRelAggregates( + ifcopenshell.guid.new(), + self.owner_history, + None, + None, + self.new.by_guid(relating_object), + list(related_objects), + )