From c1c6dfc47e1fb5e71087ec375a36569ca073e086 Mon Sep 17 00:00:00 2001 From: Gorgious Date: Sun, 23 Jul 2023 13:03:06 +0200 Subject: [PATCH] Clicking on the "Select all array objects" button now sets the parent object as active #3492 Moved the array objects fetching code to tool.Blender Also a bit of housekeeping and fixing minor typos here and there --- src/blenderbim/blenderbim/bim/import_ifc.py | 4 +- .../bim/module/geometry/operator.py | 4 +- .../bim/module/material/operator.py | 8 +-- .../blenderbim/bim/module/model/array.py | 42 ++++++-------- src/blenderbim/blenderbim/bim/operator.py | 2 +- src/blenderbim/blenderbim/tool/blender.py | 58 +++++++++++++++---- 6 files changed, 73 insertions(+), 45 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py index 2a1bc11511..d52e9a6217 100644 --- a/src/blenderbim/blenderbim/bim/import_ifc.py +++ b/src/blenderbim/blenderbim/bim/import_ifc.py @@ -422,7 +422,7 @@ class IfcImporter: for representation in representations: items = representation["raw"].Items or [] # Be forgiving of invalid IFCs because Revit :( if len(items) == 1 and items[0].is_a("IfcSweptDiskSolid"): - if tool.Pset.get_element_pset(element, "BBIM_Railing"): + if tool.Blender.Modifier.is_railing(element): return False return True elif len(items) and ( # See #2508 why we accommodate for invalid IFCs here @@ -430,7 +430,7 @@ class IfcImporter: and len({i.is_a() for i in items}) == 1 and len({i.Radius for i in items}) == 1 ): - if tool.Pset.get_element_pset(element, "BBIM_Railing"): + if tool.Blender.Modifier.is_railing(element): return False return True return False diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 6d1f671d2d..464bfb6222 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -1433,9 +1433,9 @@ class OverrideModeSetObject(bpy.types.Operator): profile = tool.Ifc.get().by_id(profile_id) if tool.Ifc.get_object(profile): # We are editing an arbitrary profile bpy.ops.bim.edit_arbitrary_profile() - elif tool.Pset.get_element_pset(element, "BBIM_Railing"): + elif tool.Blender.Modifier.is_railing(element): bpy.ops.bim.finish_editing_railing_path() - elif tool.Pset.get_element_pset(element, "BBIM_Roof"): + elif tool.Blender.Modifier.is_roof(element): bpy.ops.bim.finish_editing_roof_path() elif tool.Model.get_usage_type(element) == "PROFILE": bpy.ops.bim.edit_extrusion_axis() diff --git a/src/blenderbim/blenderbim/bim/module/material/operator.py b/src/blenderbim/blenderbim/bim/module/material/operator.py index a5f0f8df62..4f7b77e4fc 100644 --- a/src/blenderbim/blenderbim/bim/module/material/operator.py +++ b/src/blenderbim/blenderbim/bim/module/material/operator.py @@ -217,10 +217,10 @@ class RemoveConstituent(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): constituent = tool.Ifc.get().by_id(self.constituent) - for material_set in layer.ToMaterialConstituentSet: + for material_set in constituent.ToMaterialConstituentSet: if len(material_set.MaterialConstituents) == 1: self.report({"ERROR"}, "At least one constituent must exist") - return {"ERROR"} + return {"CANCELLED"} ifcopenshell.api.run("material.remove_constituent", tool.Ifc.get(), constituent=constituent) @@ -255,7 +255,7 @@ class RemoveProfile(bpy.types.Operator, tool.Ifc.Operator): for material_set in profile.ToMaterialProfileSet: if len(material_set.MaterialProfiles) == 1: self.report({"ERROR"}, "At least one profile must exist") - return {"ERROR"} + return {"CANCELLED"} ifcopenshell.api.run("material.remove_profile", tool.Ifc.get(), profile=profile) @@ -319,7 +319,7 @@ class RemoveLayer(bpy.types.Operator, tool.Ifc.Operator): for material_set in layer.ToMaterialLayerSet: if len(material_set.MaterialLayers) == 1: self.report({"ERROR"}, "At least one layer must exist") - return {"ERROR"} + return {"CANCELLED"} ifcopenshell.api.run("material.remove_layer", tool.Ifc.get(), layer=layer) diff --git a/src/blenderbim/blenderbim/bim/module/model/array.py b/src/blenderbim/blenderbim/bim/module/model/array.py index 216f684eb9..f075188c17 100644 --- a/src/blenderbim/blenderbim/bim/module/model/array.py +++ b/src/blenderbim/blenderbim/bim/module/model/array.py @@ -152,9 +152,11 @@ class RemoveArray(bpy.types.Operator, tool.Ifc.Operator): pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array") data = json.loads(pset["Data"]) data[self.item]["count"] = 1 - + if (self.keep_objs) & (self.item < (len(data) - 1)): - self.report({"INFO"}, "Keeping the objects is only allowed when you are removing the last Array of the object") + self.report( + {"INFO"}, "Keeping the objects is only allowed when you are removing the last Array of the object" + ) return {"FINISHED"} props.is_editing = -1 @@ -188,46 +190,38 @@ class SelectArrayParent(bpy.types.Operator): bl_idname = "bim.select_array_parent" bl_label = "Select Array Parent" bl_options = {"REGISTER", "UNDO"} - parent: bpy.props.StringProperty() + parent: bpy.props.StringProperty(description="Parent Element GUID") def execute(self, context): try: element = tool.Ifc.get().by_guid(self.parent) except: - return {"FINISHED"} + self.report({"ERROR"}, f"Couldn't find array parent by guid '{self.parent}'") + return {"CANCELLED"} obj = tool.Ifc.get_object(element) if obj: - context.view_layer.objects.active = obj - obj.select_set(True) + tool.Blender.select_and_activate_single_object(context, active_object=obj) return {"FINISHED"} - + class SelectAllArrayObjects(bpy.types.Operator): bl_idname = "bim.select_all_array_objects" bl_label = "Select All Array Objects" bl_options = {"REGISTER", "UNDO"} - parent: bpy.props.StringProperty() + parent: bpy.props.StringProperty(description="Parent Element GUID") def execute(self, context): try: - element = tool.Ifc.get().by_guid(self.parent) - except: - return {"FINISHED"} - - obj = tool.Ifc.get_object(element) - obj.select_set(True) - - pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array") - data = json.loads(pset["Data"]) - for i in range(len(data)): - for child in data[i]["children"]: - element = tool.Ifc.get().by_guid(child) - obj = tool.Ifc.get_object(element) - if obj: - context.view_layer.objects.active = obj - obj.select_set(True) + parent_element = tool.Ifc.get().by_guid(self.parent) + except RuntimeError: + self.report({"ERROR"}, f"Couldn't find array parent by guid '{self.parent}'") + return {"CANCELLED"} + + array_objects = tool.Blender.Modifier.Array.get_all_objects(parent_element) + tool.Blender.set_objects_selection(context, active_object=array_objects[0], selected_objects=array_objects) return {"FINISHED"} + class Input3DCursorXArray(bpy.types.Operator): bl_idname = "bim.input_cursor_x_array" bl_label = "Get 3d Cursor X Input for Array" diff --git a/src/blenderbim/blenderbim/bim/operator.py b/src/blenderbim/blenderbim/bim/operator.py index 2b5cf9003f..2e8396c14c 100644 --- a/src/blenderbim/blenderbim/bim/operator.py +++ b/src/blenderbim/blenderbim/bim/operator.py @@ -150,7 +150,7 @@ class ReloadSelectedIfcFile(bpy.types.Operator): valid_file = os.path.exists(filepath) and "ifc" in os.path.splitext(filepath)[1].lower() if not valid_file: self.report({"ERROR"}, f"Couldn't find .ifc file by the path '{filepath}'") - return {"ERROR"} + return {"CANCELLED"} context.scene.BIMProperties.ifc_file = context.scene.BIMProperties.ifc_file return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/tool/blender.py b/src/blenderbim/blenderbim/tool/blender.py index 49cc54d878..669c8e6ab5 100644 --- a/src/blenderbim/blenderbim/tool/blender.py +++ b/src/blenderbim/blenderbim/tool/blender.py @@ -17,6 +17,7 @@ # along with BlenderBIM Add-on. If not, see . import bpy +import json import ifcopenshell.api import blenderbim.tool as tool from mathutils import Vector @@ -457,33 +458,40 @@ class Blender: else: return {"CANCELLED"} + @classmethod + def is_object_an_ifc_class(cls, obj, classes): + if not tool.Ifc.get(): + return False + element = tool.Ifc.get_entity(obj) + return element and element.is_a() in classes + + @classmethod + def get_object_from_guid(cls, guid): + element = tool.Ifc.get().by_guid(guid) + obj = tool.Ifc.get_object(element) + if obj: + return obj + class Modifier: - @classmethod - def is_object_an_ifc_class(cls, obj, classes): - if not tool.Ifc.get(): - return False - element = tool.Ifc.get_entity(obj) - return element and element.is_a() in classes - @classmethod def is_eligible_for_railing_modifier(cls, obj): - return cls.is_object_an_ifc_class(obj, ("IfcRailing", "IfcRailingType")) + return tool.Blender.is_object_an_ifc_class(obj, ("IfcRailing", "IfcRailingType")) @classmethod def is_eligible_for_stair_modifier(cls, obj): - return cls.is_object_an_ifc_class(obj, ("IfcStairFlight", "IfcStairFlightType")) + return tool.Blender.is_object_an_ifc_class(obj, ("IfcStairFlight", "IfcStairFlightType")) @classmethod def is_eligible_for_window_modifier(cls, obj): - return cls.is_object_an_ifc_class(obj, ("IfcWindow", "IfcWindowType", "IfcWindowStyle")) + return tool.Blender.is_object_an_ifc_class(obj, ("IfcWindow", "IfcWindowType", "IfcWindowStyle")) @classmethod def is_eligible_for_door_modifier(cls, obj): - return cls.is_object_an_ifc_class(obj, ("IfcDoor", "IfcDoorType", "IfcDoorStyle")) + return tool.Blender.is_object_an_ifc_class(obj, ("IfcDoor", "IfcDoorType", "IfcDoorStyle")) @classmethod def is_eligible_for_roof_modifier(cls, obj): - return cls.is_object_an_ifc_class(obj, ("IfcRoof", "IfcRoofType")) + return tool.Blender.is_object_an_ifc_class(obj, ("IfcRoof", "IfcRoofType")) @classmethod def is_railing(cls, element): @@ -512,3 +520,29 @@ class Blender: @classmethod def is_modifier_with_non_editable_path(cls, element): return cls.is_stair(element) or cls.is_door(element) or cls.is_window(element) + + class Array: + @classmethod + def get_all_objects(cls, parent_element): + parent_obj = tool.Ifc.get_object(parent_element) + children_objects = list(cls.get_all_children_objects(parent_element)) + array_objects = [parent_obj] + children_objects # We ensure the parent is at index 0 + return array_objects + + @classmethod + def get_all_children_objects(cls, parent_element): + for array_modifier in cls.get_modifiers_data(parent_element): + yield from cls.get_children_objects(array_modifier) + + @classmethod + def get_modifiers_data(cls, parent_element): + array_pset = ifcopenshell.util.element.get_pset(parent_element, "BBIM_Array") + for modifier_data in json.loads(array_pset["Data"]): + yield modifier_data + + @classmethod + def get_children_objects(cls, modifier_data): + for child_guid in modifier_data["children"]: + child_obj = tool.Blender.get_object_from_guid(child_guid) + if child_obj: + yield child_obj