From b5d672ba423fa913f3b0e0fcc04eca3dba8e630d Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 1 Apr 2024 15:28:34 +1100 Subject: [PATCH] Fix #4069. IfcClash Blender interface now uses the new filter UI. --- .../blenderbim/bim/module/clash/data.py | 136 +++--------------- .../blenderbim/bim/module/clash/operator.py | 14 +- .../blenderbim/bim/module/clash/prop.py | 9 +- .../blenderbim/bim/module/clash/ui.py | 21 ++- src/blenderbim/blenderbim/tool/clash.py | 5 +- src/blenderbim/blenderbim/tool/search.py | 5 + 6 files changed, 55 insertions(+), 135 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/clash/data.py b/src/blenderbim/blenderbim/bim/module/clash/data.py index f6e788b82a..7f61222f7b 100644 --- a/src/blenderbim/blenderbim/bim/module/clash/data.py +++ b/src/blenderbim/blenderbim/bim/module/clash/data.py @@ -17,138 +17,36 @@ # along with BlenderBIM Add-on. If not, see . import bpy -import ifcopenshell -import ifcopenshell.util.date -import ifcopenshell.util.classification +import json +import ifcopenshell.util.element import blenderbim.tool as tool -from blenderbim.bim.ifc import IfcStore def refresh(): - ClassificationsData.is_loaded = False - ClassificationReferencesData.is_loaded = False - MaterialClassificationsData.is_loaded = False - CostClassificationsData.is_loaded = False + ClashData.is_loaded = False -class ClassificationsData: +class ClashData: data = {} is_loaded = False @classmethod def load(cls): cls.is_loaded = True - cls.data["has_classification_file"] = cls.has_classification_file() - cls.data["classifications"] = cls.classifications() - cls.data["available_classifications"] = cls.available_classifications() + cls.data = {} + cls.data["saved_searches"] = cls.saved_searches() @classmethod - def has_classification_file(cls): - return bool(IfcStore.classification_file) - - @classmethod - def classifications(cls): - results = [] - for element in tool.Ifc.get().by_type("IfcClassification"): - data = element.get_info() - if tool.Ifc.get().schema == "IFC2X3" and element.EditionDate: - data["EditionDate"] = ifcopenshell.util.date.ifc2datetime(data["EditionDate"]) - results.append(data) - return results - - @classmethod - def available_classifications(cls): - if not IfcStore.classification_file: + def saved_searches(cls): + if not tool.Ifc.get(): return [] - return [(str(e.id()), e.Name, "") for e in IfcStore.classification_file.by_type("IfcClassification")] - - -class ReferencesData: - @classmethod - def active_classification_library(cls): - if not IfcStore.classification_file or not IfcStore.classification_file.by_type("IfcClassification"): - return False - props = bpy.context.scene.BIMClassificationProperties - name = IfcStore.classification_file.by_id(int(props.available_classifications)).Name - if name in [e.Name for e in tool.Ifc.get().by_type("IfcClassification")]: - return name - - @classmethod - def classifications(cls): - return [(str(e.id()), e.Name, "") for e in tool.Ifc.get().by_type("IfcClassification")] - - -class ClassificationReferencesData(ReferencesData): - data = {} - is_loaded = False - - @classmethod - def load(cls): - cls.is_loaded = True - cls.data["references"] = cls.references() - cls.data["active_classification_library"] = cls.active_classification_library() - cls.data["classifications"] = cls.classifications() - cls.data["object_type"] = "OBJECT" - - @classmethod - def references(cls): + groups = tool.Ifc.get().by_type("IfcGroup") results = [] - element = tool.Ifc.get_entity(bpy.context.active_object) - if element: - for reference in ifcopenshell.util.classification.get_references(element): - data = reference.get_info() - del data["ReferencedSource"] - results.append(data) - return results - - -class MaterialClassificationsData(ReferencesData): - data = {} - is_loaded = False - - @classmethod - def load(cls): - cls.is_loaded = True - cls.data["references"] = cls.references() - cls.data["active_classification_library"] = cls.active_classification_library() - cls.data["classifications"] = cls.classifications() - cls.data["object_type"] = "MATERIAL" - - @classmethod - def references(cls): - results = [] - element = tool.Ifc.get_entity(bpy.context.active_object.active_material) - if element: - for reference in ifcopenshell.util.classification.get_references(element): - data = reference.get_info() - del data["ReferencedSource"] - results.append(data) - return results - - -class CostClassificationsData(ReferencesData): - data = {} - is_loaded = False - - @classmethod - def load(cls): - cls.is_loaded = True - cls.data["references"] = cls.references() - cls.data["active_classification_library"] = cls.active_classification_library() - cls.data["classifications"] = cls.classifications() - cls.data["object_type"] = "COST" - - @classmethod - def references(cls): - results = [] - element = tool.Ifc.get().by_id( - bpy.context.scene.BIMCostProperties.cost_items[ - bpy.context.scene.BIMCostProperties.active_cost_item_index - ].ifc_definition_id - ) - if element: - for reference in ifcopenshell.util.classification.get_references(element): - data = reference.get_info() - del data["ReferencedSource"] - results.append(data) - return results + for group in groups: + try: + data = json.loads(group.Description) + if isinstance(data, dict) and data.get("type", None) == "BBIM_Search" and data.get("query", None): + results.append(group) + except: + pass + return [(str(g.id()), g.Name or "Unnamed", "") for g in sorted(results, key=lambda x: x.Name or "Unnamed")] diff --git a/src/blenderbim/blenderbim/bim/module/clash/operator.py b/src/blenderbim/blenderbim/bim/module/clash/operator.py index 05e82c7f77..f32e7bfad3 100644 --- a/src/blenderbim/blenderbim/bim/module/clash/operator.py +++ b/src/blenderbim/blenderbim/bim/module/clash/operator.py @@ -86,14 +86,14 @@ class ImportClashSets(bpy.types.Operator): new_source = new.a.add() new_source.name = clash_source["file"] if "selector" in clash_source: - new_source.selector = clash_source["selector"] + tool.Search.import_filter_query(clash_source["selector"], new_source.filter_groups) new_source.mode = clash_source["mode"] if "b" in clash_set and clash_set["b"]: for clash_source in clash_set["b"]: new_source = new.b.add() new_source.name = clash_source["file"] if "selector" in clash_source: - new_source.selector = clash_source["selector"] + tool.Search.import_filter_query(clash_source["selector"], new_source.filter_groups) new_source.mode = clash_source["mode"] tool.Clash.import_active_clashes() return {"FINISHED"} @@ -209,8 +209,11 @@ class ExecuteIfcClash(bpy.types.Operator): def invoke(self, context, event): _, extension = os.path.splitext(self.filepath) - if extension != ".json": - self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".bcf") + if extension != ".bcf": + self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json") + # TODO Temporarily until BCF support comes back + # if extension != ".json": + # self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".bcf") WindowManager = context.window_manager WindowManager.fileselect_add(self) return {"RUNNING_MODAL"} @@ -221,6 +224,9 @@ class ExecuteIfcClash(bpy.types.Operator): self.props = context.scene.BIMClashProperties _, extension = os.path.splitext(self.filepath) + if extension != ".bcf": + self.filepath = bpy.path.ensure_ext(self.filepath, ".json") + # TODO Temporarily until BCF support comes back if extension != ".json": self.filepath = bpy.path.ensure_ext(self.filepath, ".bcf") diff --git a/src/blenderbim/blenderbim/bim/module/clash/prop.py b/src/blenderbim/blenderbim/bim/module/clash/prop.py index f6e798a3f2..0f7e7c2275 100644 --- a/src/blenderbim/blenderbim/bim/module/clash/prop.py +++ b/src/blenderbim/blenderbim/bim/module/clash/prop.py @@ -17,7 +17,7 @@ # along with BlenderBIM Add-on. If not, see . import bpy -from blenderbim.bim.prop import StrProperty, Attribute +from blenderbim.bim.prop import StrProperty, Attribute, BIMFilterGroup from bpy.types import PropertyGroup from bpy.props import ( PointerProperty, @@ -33,11 +33,12 @@ from bpy.props import ( class ClashSource(PropertyGroup): name: StringProperty(name="File") - selector: StringProperty(name="Selector") + filter_groups: CollectionProperty(type=BIMFilterGroup, name="Filter Groups") mode: EnumProperty( items=[ - ("i", "Include", "Only the selected objects are included for clashing"), - ("e", "Exclude", "All objects except the selected objects are included for clashing"), + ("a", "All Elements", "All elements will be used for clashing"), + ("i", "Include", "Only the selected elements are included for clashing"), + ("e", "Exclude", "All elements except the selected elements are included for clashing"), ], name="Mode", ) diff --git a/src/blenderbim/blenderbim/bim/module/clash/ui.py b/src/blenderbim/blenderbim/bim/module/clash/ui.py index 204ece028d..d5322a568b 100644 --- a/src/blenderbim/blenderbim/bim/module/clash/ui.py +++ b/src/blenderbim/blenderbim/bim/module/clash/ui.py @@ -17,7 +17,9 @@ # along with BlenderBIM Add-on. If not, see . import bpy +import blenderbim.bim.helper from bpy.types import Panel +from blenderbim.bim.module.clash.data import ClashData class BIM_PT_ifcclash(Panel): @@ -30,6 +32,9 @@ class BIM_PT_ifcclash(Panel): bl_parent_id = "BIM_PT_tab_clash_detection" def draw(self, context): + if not ClashData.is_loaded: + ClashData.load() + layout = self.layout scene = context.scene @@ -78,6 +83,7 @@ class BIM_PT_ifcclash(Panel): for index, source in enumerate(clash_set.a): row = layout.row(align=True) row.prop(source, "name", text="") + row.prop(source, "mode", text="") op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="") op.index = index op.group = "a" @@ -85,9 +91,10 @@ class BIM_PT_ifcclash(Panel): op.index = index op.group = "a" - row = layout.row(align=True) - row.prop(source, "mode", text="") - row.prop(source, "selector", text="") + if source.mode != "a": + blenderbim.bim.helper.draw_filter( + layout, source.filter_groups, ClashData, f"clash_{props.active_clash_set_index}_a_{index}" + ) row = layout.row(align=True) row.label(text="Group B:", icon="OUTLINER_OB_POINTCLOUD") @@ -96,6 +103,7 @@ class BIM_PT_ifcclash(Panel): for index, source in enumerate(clash_set.b): row = layout.row(align=True) row.prop(source, "name", text="") + row.prop(source, "mode", text="") op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="") op.index = index op.group = "b" @@ -103,9 +111,10 @@ class BIM_PT_ifcclash(Panel): op.index = index op.group = "b" - row = layout.row(align=True) - row.prop(source, "mode", text="") - row.prop(source, "selector", text="") + if source.mode != "a": + blenderbim.bim.helper.draw_filter( + layout, source.filter_groups, ClashData, f"clash_{props.active_clash_set_index}_b_{index}" + ) row = layout.row() row.prop(props, "should_create_clash_snapshots") diff --git a/src/blenderbim/blenderbim/tool/clash.py b/src/blenderbim/blenderbim/tool/clash.py index 021c275851..c83c3f9deb 100644 --- a/src/blenderbim/blenderbim/tool/clash.py +++ b/src/blenderbim/blenderbim/tool/clash.py @@ -37,8 +37,9 @@ class Clash(blenderbim.core.tool.Clash): for ab in ["a", "b"]: for data in getattr(clash_set, ab): clash_source = {"file": data.name} - if data.selector: - clash_source["selector"] = data.selector + query = tool.Search.export_filter_query(data.filter_groups) + if query and data.mode != "a": + clash_source["selector"] = query clash_source["mode"] = data.mode if ab == "a": a.append(clash_source) diff --git a/src/blenderbim/blenderbim/tool/search.py b/src/blenderbim/blenderbim/tool/search.py index 4a3f9b3f1b..e666158720 100644 --- a/src/blenderbim/blenderbim/tool/search.py +++ b/src/blenderbim/blenderbim/tool/search.py @@ -26,6 +26,11 @@ class Search(blenderbim.core.tool.Search): return bpy.context.active_object.data.BIMCameraProperties.include_filter_groups elif module == "drawing_exclude": return bpy.context.active_object.data.BIMCameraProperties.exclude_filter_groups + elif module.startswith("clash"): + _, clash_set_index, ab, clash_source_index = module.split("_") + return getattr(bpy.context.scene.BIMClashProperties.clash_sets[int(clash_set_index)], ab)[ + int(clash_source_index) + ].filter_groups @classmethod def import_filter_query(cls, query: str, filter_groups: bpy.types.bpy_prop_collection) -> None: