From 6660ee37880dccf4e88a4609c6f03d009a1c55e6 Mon Sep 17 00:00:00 2001 From: "Sigma Dimensions (Yass)" <79010126+myoualid@users.noreply.github.com> Date: Sat, 23 Dec 2023 09:14:08 +0100 Subject: [PATCH] New Clash management panel to load IFCClash collision results --- .../blenderbim/bim/module/clash/__init__.py | 21 ++- .../blenderbim/bim/module/clash/operator.py | 132 ++++++++++++++++-- .../blenderbim/bim/module/clash/prop.py | 17 +++ .../blenderbim/bim/module/clash/ui.py | 47 +++++++ 4 files changed, 202 insertions(+), 15 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/clash/__init__.py b/src/blenderbim/blenderbim/bim/module/clash/__init__.py index a865a7cf71..5cb662fa02 100644 --- a/src/blenderbim/blenderbim/bim/module/clash/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/clash/__init__.py @@ -20,26 +20,33 @@ import bpy from . import ui, prop, operator classes = ( + operator.AddClashSet, + operator.AddClashSource, + operator.ExecuteIfcClash, operator.ExportClashSets, operator.ImportClashSets, - operator.AddClashSet, + operator.LoadIfcClashes, + operator.SaveIfcClashes, + operator.LoadSmartGroupsForActiveClashSet, operator.RemoveClashSet, - operator.AddClashSource, operator.RemoveClashSource, - operator.SelectClashSource, - operator.ExecuteIfcClash, - operator.SelectIfcClashResults, + operator.SelectClash, operator.SelectClashResults, + operator.SelectClashSource, + operator.SelectIfcClashResults, + operator.SelectSmartGroup, operator.SelectSmartGroupedClashesPath, operator.SmartClashGroup, - operator.SelectSmartGroup, - operator.LoadSmartGroupsForActiveClashSet, + prop.Clash, prop.ClashSource, prop.ClashSet, prop.SmartClashGroup, prop.BIMClashProperties, ui.BIM_PT_ifcclash, ui.BIM_PT_clash_manager, + ui.BIM_PT_dumb_clash_manager, + ui.BIM_PT_smart_clash_manager, + ui.BIM_UL_clashes, ui.BIM_UL_clash_sets, ui.BIM_UL_smart_groups, ) diff --git a/src/blenderbim/blenderbim/bim/module/clash/operator.py b/src/blenderbim/blenderbim/bim/module/clash/operator.py index f721a2dff0..a56a45f560 100644 --- a/src/blenderbim/blenderbim/bim/module/clash/operator.py +++ b/src/blenderbim/blenderbim/bim/module/clash/operator.py @@ -26,6 +26,7 @@ import ifcopenshell from mathutils import Matrix, Vector from math import radians from blenderbim.bim.ifc import IfcStore +import blenderbim.tool as tool class ExportClashSets(bpy.types.Operator): @@ -245,12 +246,14 @@ class ExecuteIfcClash(bpy.types.Operator): y = Vector([y.x, y.y, y.z]) x = y.cross(z) - mat = Matrix([ - [x[0], y[0], z[0], p.x], - [x[1], y[1], z[1], p.y], - [x[2], y[2], z[2], p.z], - [0, 0, 0, 0], - ]) + mat = Matrix( + [ + [x[0], y[0], z[0], p.x], + [x[1], y[1], z[1], p.y], + [x[2], y[2], z[2], p.z], + [0, 0, 0, 0], + ] + ) camera.matrix_world = mat context.scene.camera = camera @@ -349,6 +352,119 @@ class SelectIfcClashResults(bpy.types.Operator): return {"FINISHED"} +class LoadIfcClashes(bpy.types.Operator): + bl_idname = "bim.load_ifc_clashes" + bl_label = "Load IFC Clashes" + bl_options = {"REGISTER", "UNDO"} + bl_description = "Load the clashing IFC geometry stored in a file" + filename_ext = ".json" + filepath: bpy.props.StringProperty(subtype="FILE_PATH") + + def invoke(self, context, event): + self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json") + WindowManager = context.window_manager + WindowManager.fileselect_add(self) + return {"RUNNING_MODAL"} + + def execute(self, context): + self.file = IfcStore.get_file() + self.filepath = bpy.path.ensure_ext(self.filepath, ".json") + with open(self.filepath) as f: + clash_sets_json = json.load(f) + active_clash_set = context.scene.BIMClashProperties.active_clash_set + + for clash_set in clash_sets_json: + if not "clashes" in clash_set.keys(): + self.report({"WARNING"}, "No clashes found for the selected Clash Set.") + return {"CANCELLED"} + active_clash_set.clashes.clear() + for clash in clash_set["clashes"].values(): + blender_clash = active_clash_set.clashes.add() + blender_clash.a_global_id = clash["a_global_id"] + blender_clash.b_global_id = clash["b_global_id"] + blender_clash.a_name = "{}/{}".format(clash["a_ifc_class"], clash["a_name"]) + blender_clash.b_name = "{}/{}".format(clash["b_ifc_class"], clash["b_name"]) + blender_clash.status = False if not "status" in clash.keys() else clash["status"] + return {"FINISHED"} + + +class SaveIfcClashes(bpy.types.Operator): + bl_idname = "bim.save_ifc_clashes" + bl_label = "Save IFC Clashes" + bl_options = {"REGISTER", "UNDO"} + bl_description = "Save the clashing IFC geometry stored in a file" + filename_ext = ".json" + filepath: bpy.props.StringProperty(subtype="FILE_PATH") + + def invoke(self, context, event): + self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json") + WindowManager = context.window_manager + WindowManager.fileselect_add(self) + return {"RUNNING_MODAL"} + + def execute(self, context): + self.file = IfcStore.get_file() + self.filepath = bpy.path.ensure_ext(self.filepath, ".json") + clash_sets_json = self.load_json() + active_clash_set = context.scene.BIMClashProperties.active_clash_set + self.update_clash_sets(clash_sets_json, active_clash_set) + self.save_json(clash_sets_json) + return {"FINISHED"} + + def load_json(self): + with open(self.filepath) as f: + return json.load(f) + + def update_clash_sets(self, clash_sets_json, active_clash_set): + for clash_set in clash_sets_json: + if clash_set["name"] != active_clash_set.name or "clashes" not in clash_set.keys(): + continue + for clash in active_clash_set.clashes: + clash_key = clash.a_global_id + "-" + clash.b_global_id + if clash_set.get("clashes", {}).get(clash_key, None) is not None: + clash_set["clashes"][clash_key]["status"] = clash.status + + def save_json(self, clash_sets_json): + with open(self.filepath, "w") as destination: + json.dump(clash_sets_json, destination, indent=4) + + +class SelectClash(bpy.types.Operator): + bl_idname = "bim.select_clash" + bl_label = "Select Clash" + bl_options = {"REGISTER", "UNDO"} + bl_description = "Select the clashing IFC geometry stored in a file" + index: bpy.props.IntProperty() + + def execute(self, context): + clash = context.scene.BIMClashProperties.active_clash + + products = [] + linked_objects = [] + try: + products.append(tool.Ifc.get().by_guid(clash.a_global_id)) + except: + linked_objects.append(clash.a_name) + try: + products.append(tool.Ifc.get().by_guid(clash.b_global_id)) + except: + linked_objects.append(clash.b_name) + + tool.Spatial.select_products(products, unhide=True) + print(linked_objects) + for obj_name in linked_objects: + obj = bpy.data.objects.get(obj_name) + if obj: + obj.select_set(True) + else: + print("Object not found:", obj_name) + if context.scene.BIMClashProperties.sould_focus_on_clash: + context_override = tool.Blender.get_viewport_context() + with bpy.context.temp_override(**context_override): + bpy.ops.view3d.view_selected() + return {"FINISHED"} + + class SmartClashGroup(bpy.types.Operator): bl_idname = "bim.smart_clash_group" bl_label = "Smart Group Clashes" @@ -433,9 +549,9 @@ class LoadSmartGroupsForActiveClashSet(bpy.types.Operator): new_group = context.scene.BIMClashProperties.smart_clash_groups.add() new_group.number = f"{smart_group}" for pair in global_id_pairs: - for id in pair: + for guid in pair: new_global_id = new_group.global_ids.add() - new_global_id.name = id + new_global_id.guid = guid return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/clash/prop.py b/src/blenderbim/blenderbim/bim/module/clash/prop.py index f057dcd70c..6bd8120fed 100644 --- a/src/blenderbim/blenderbim/bim/module/clash/prop.py +++ b/src/blenderbim/blenderbim/bim/module/clash/prop.py @@ -43,11 +43,20 @@ class ClashSource(PropertyGroup): ) +class Clash(PropertyGroup): + a_global_id: StringProperty(name="A") + b_global_id: StringProperty(name="B") + a_name: StringProperty(name="A Name") + b_name: StringProperty(name="B Name") + status: BoolProperty(name="Status", default=False) + + class ClashSet(PropertyGroup): name: StringProperty(name="Name") tolerance: FloatProperty(name="Tolerance") a: CollectionProperty(name="Group A", type=ClashSource) b: CollectionProperty(name="Group B", type=ClashSource) + clashes: CollectionProperty(name="Clashes", type=Clash) class SmartClashGroup(PropertyGroup): @@ -63,11 +72,13 @@ class BIMClashProperties(PropertyGroup): clash_results_path: StringProperty(name="Clash Results Path") smart_grouped_clashes_path: StringProperty(name="Smart Grouped Clashes Path") active_clash_set_index: IntProperty(name="Active Clash Set Index") + active_clash_index: IntProperty(name="Active Clash Index") smart_clash_groups: CollectionProperty(name="Smart Clash Groups", type=SmartClashGroup) active_smart_group_index: IntProperty(name="Active Smart Group Index") smart_clash_grouping_max_distance: IntProperty( name="Smart Clash Grouping Max Distance", default=3, soft_min=1, soft_max=10 ) + sould_focus_on_clash: BoolProperty(name="Show Focus Clash", default=False) @property def active_clash_set(self): @@ -80,3 +91,9 @@ class BIMClashProperties(PropertyGroup): if not self.smart_clash_groups: return None return self.smart_clash_groups[self.active_smart_group_index] + + @property + def active_clash(self): + if not self.active_clash_set: + return None + return self.active_clash_set.clashes[self.active_clash_index] diff --git a/src/blenderbim/blenderbim/bim/module/clash/ui.py b/src/blenderbim/blenderbim/bim/module/clash/ui.py index 4e846ae78f..8b7ba0110f 100644 --- a/src/blenderbim/blenderbim/bim/module/clash/ui.py +++ b/src/blenderbim/blenderbim/bim/module/clash/ui.py @@ -107,6 +107,42 @@ class BIM_PT_clash_manager(Panel): bl_context = "scene" bl_parent_id = "BIM_PT_tab_clash_detection" + def draw(self, context): + pass + + +class BIM_PT_dumb_clash_manager(Panel): + bl_idname = "BIM_PT_dumb_clash_manager" + bl_label = "Dumb Manager" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + bl_parent_id = "BIM_PT_clash_manager" + + def draw(self, context): + layout = self.layout + props = context.scene.BIMClashProperties + + row = layout.row(align=True) + row.operator("bim.load_ifc_clashes", text="LOAD IFC CLASHES", icon="IMPORT") + row.operator("bim.save_ifc_clashes", text="SAVE CLASH RESULTS", icon="EXPORT") + layout.template_list("BIM_UL_clashes", "", props.active_clash_set, "clashes", props, "active_clash_index") + # bim.select_clash + row = layout.row(align=True) + row.operator("bim.select_clash", text="SELECT CLASH", icon="IMPORT") + row.prop(props, "sould_focus_on_clash", icon="RESTRICT_VIEW_OFF") + + +class BIM_PT_smart_clash_manager(Panel): + bl_idname = "BIM_PT_smart_clash_manager" + bl_label = "Smart Clash Manager" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + bl_parent_id = "BIM_PT_clash_manager" + def draw(self, context): layout = self.layout props = context.scene.BIMClashProperties @@ -156,3 +192,14 @@ class BIM_UL_smart_groups(bpy.types.UIList): layout.label(text=str(item.number), translate=False, icon="NONE", icon_value=0) else: layout.label(text="", translate=False) + + +class BIM_UL_clashes(bpy.types.UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + if item: + row = layout.row(align=True) + row.label(text=str(item.a_name), translate=False, icon="NONE", icon_value=0) + row.label(text=str(item.b_name), translate=False, icon="NONE", icon_value=0) + row.prop(item, "status", text="") + else: + layout.label(text="", translate=False)