From a6e953a550b1163dde3d8dd8361676d6f5a555cc Mon Sep 17 00:00:00 2001 From: Gorgious56 Date: Thu, 9 Jan 2025 17:45:40 +0100 Subject: [PATCH 1/8] Update Type Manager Add back the type dropdown and looking glass icon in the toolbar Clicking on a type in the manager no longer closes the popup anymore. A visual indictor is displayed on the active type Changed the parameters icon in the individual type interface Fix bug in "Slab from Walls" Shift + A code --- src/bonsai/bonsai/bim/module/model/product.py | 1 - src/bonsai/bonsai/bim/module/model/ui.py | 16 +++++++--- .../bonsai/bim/module/model/workspace.py | 32 +++++++++---------- 3 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/model/product.py b/src/bonsai/bonsai/bim/module/model/product.py index efa39f3cbe..93fc8761c6 100644 --- a/src/bonsai/bonsai/bim/module/model/product.py +++ b/src/bonsai/bonsai/bim/module/model/product.py @@ -489,7 +489,6 @@ class SetActiveType(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): props = context.scene.BIMModelProperties props.relating_type_id = str(self.relating_type) - context.window.screen = context.window.screen # Closes the type manager popup class AlignProduct(bpy.types.Operator): diff --git a/src/bonsai/bonsai/bim/module/model/ui.py b/src/bonsai/bonsai/bim/module/model/ui.py index 0563c03a5b..286fa41794 100644 --- a/src/bonsai/bonsai/bim/module/model/ui.py +++ b/src/bonsai/bonsai/bim/module/model/ui.py @@ -84,7 +84,7 @@ class LaunchTypeManager(bpy.types.Operator): bl_idname = "bim.launch_type_manager" bl_label = "Launch Type Manager" bl_options = {"REGISTER", "UNDO"} - bl_description = "Display all available Construction Types to add new instances" + bl_description = "Display all available Construction Types to add new occurrences" def execute(self, context): bpy.ops.bim.hotkey("INVOKE_DEFAULT", hotkey="S_A") @@ -102,7 +102,7 @@ class LaunchTypeManager(bpy.types.Operator): if ifc_class is not None: bpy.ops.bim.load_type_thumbnails(ifc_class=ifc_class, offset=0, limit=9) return context.window_manager.invoke_props_dialog( - self, width=550, title="Type Manager", confirm_text="Add Type" + self, width=550, title="Type Manager", confirm_text="Add Occurrence" ) def draw(self, context): @@ -146,7 +146,7 @@ class LaunchTypeManager(bpy.types.Operator): op = row.operator("bim.set_active_type", text=relating_type["name"], icon="BLANK1", emboss=False) op.relating_type = relating_type["id"] - op = row.operator("bim.launch_type_menu", icon="DOWNARROW_HLT", text="", emboss=False) + op = row.operator("bim.launch_type_menu", icon="PREFERENCES", text="", emboss=False) op.relating_type_id = relating_type["id"] row = box.row() @@ -163,7 +163,15 @@ class LaunchTypeManager(bpy.types.Operator): row2.operator("bim.set_active_type", text="", emboss=False).relating_type = relating_type["id"] row2.operator("bim.set_active_type", text="", emboss=False).relating_type = relating_type["id"] row2.operator("bim.set_active_type", text="", emboss=False).relating_type = relating_type["id"] - row2.operator("bim.set_active_type", text="", emboss=False).relating_type = relating_type["id"] + is_current_relating_type = str(relating_type["id"]) == str( + AuthoringData.data["relating_type_id_current"] + ) + if is_current_relating_type: + active_row = row2.row() + active_row.alignment = "CENTER" + active_row.label(text="Active", icon="CHECKMARK") + else: + row2.operator("bim.set_active_type", text="", emboss=False).relating_type = relating_type["id"] else: row = box.row() op = box.operator("bim.load_type_thumbnails", text="", icon="FILE_REFRESH", emboss=False) diff --git a/src/bonsai/bonsai/bim/module/model/workspace.py b/src/bonsai/bonsai/bim/module/model/workspace.py index 5720c7df30..ecdf6d6044 100644 --- a/src/bonsai/bonsai/bim/module/model/workspace.py +++ b/src/bonsai/bonsai/bim/module/model/workspace.py @@ -509,16 +509,12 @@ class CreateObjectUI: if ifc_class: box = cls.layout.box() row = box.row(align=True) + prop_with_search(row, cls.props, "relating_type_id", text="") if AuthoringData.data["type_thumbnail"] and ui_context == "TOOL_HEADER": row.template_icon(icon_value=AuthoringData.data["type_thumbnail"]) - row.operator("bim.launch_type_manager", text=AuthoringData.data["relating_type_name"], emboss=False) + row.operator("bim.launch_type_manager", text="Type Manager", emboss=False) else: - row.operator( - "bim.launch_type_manager", - icon="BLANK1", - text=AuthoringData.data["relating_type_name"], - emboss=False, - ) + row.operator("bim.launch_type_manager", icon="BLANK1", text="Type Manager", emboss=False) row.operator( "bim.launch_type_manager", @@ -595,7 +591,7 @@ class EditObjectUI: op = row.operator("bim.disable_aggregate_mode", text="", icon="X") op = row.operator("bim.toggle_aggregate_mode_local_view", text="", icon="ZOOM_SELECTED") op = row.operator("bim.aggregate_assign_new_objects_in_aggregate_mode", text="", icon="CUBE") - + text = format_ifc_camel_case(AuthoringData.data["active_class"]) layout.label(text=f"{text} Edit Tools:", icon="RESTRICT_SELECT_OFF") cls.draw_parameter_adjustments(context) @@ -966,16 +962,18 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator): bpy.ops.wm.call_menu(name="BIM_MT_add_representation_item") else: # Slab from walls - walls = False + # Make sure only Ifc Walls are selected for obj in bpy.context.selected_objects: - walls = tool.Ifc.get_entity(obj).is_a("IfcWall") - if ( - walls - and relating_type_id - and tool.Model.get_usage_type(tool.Ifc.get().by_id(int(relating_type_id))) == "LAYER3" - ): - bpy.ops.bim.draw_slab_from_wall("INVOKE_DEFAULT") - return {"FINISHED"} + element = tool.Ifc.get_entity(obj) + if not element or not element.is_a("IfcWall"): + break + else: + if ( + relating_type_id + and tool.Model.get_usage_type(tool.Ifc.get().by_id(int(relating_type_id))) == "LAYER3" + ): + bpy.ops.bim.draw_slab_from_wall("INVOKE_DEFAULT") + return {"FINISHED"} # Walls from slab slab = tool.Ifc.get_entity(bpy.context.active_object) if ( From aafb6fc42619a36e51d612c5ecebb16e561b1c13 Mon Sep 17 00:00:00 2001 From: Gorgious56 Date: Fri, 10 Jan 2025 09:44:12 +0100 Subject: [PATCH 2/8] Add search bar with simple filter function to the type manager --- src/bonsai/bonsai/bim/module/model/data.py | 46 ++++++++++--------- src/bonsai/bonsai/bim/module/model/product.py | 4 +- src/bonsai/bonsai/bim/module/model/prop.py | 15 ++++++ src/bonsai/bonsai/bim/module/model/ui.py | 11 ++--- 4 files changed, 46 insertions(+), 30 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/model/data.py b/src/bonsai/bonsai/bim/module/model/data.py index bfe7c729e2..9a37bc55fa 100644 --- a/src/bonsai/bonsai/bim/module/model/data.py +++ b/src/bonsai/bonsai/bim/module/model/data.py @@ -57,20 +57,22 @@ class AuthoringData: cls.data["ifc_element_type"] = cls.ifc_element_type cls.data["ifc_classes"] = cls.ifc_classes() cls.data["ifc_class_current"] = cls.ifc_class_current() - cls.data["relating_type_id"] = cls.relating_type_id() # only after .ifc_classes() - cls.data["relating_type_id_current"] = cls.relating_type_id_current() # only after .ifc_classes() - cls.data["relating_type_name"] = cls.relating_type_name() # only after .relating_type_id() - cls.data["relating_type_description"] = cls.relating_type_description() # only after .relating_type_id() - cls.data["predefined_type"] = cls.predefined_type() # only after .relating_type_id() - - # Only after .ifc_classes() + # Make sure .ifc_classes() was run before next lines + cls.data["filtered_type_elements"] = cls.filtered_type_elements() + # Make sure .filtered_type_elements() was run before next lines + cls.data["relating_type_id"] = cls.relating_type_id() + # Make sure .relating_type_id() was run before next lines + cls.data["relating_type_id_current"] = cls.relating_type_id_current() + cls.data["relating_type_name"] = cls.relating_type_name() + cls.data["relating_type_description"] = cls.relating_type_description() + cls.data["predefined_type"] = cls.predefined_type() cls.data["total_types"] = cls.total_types() - cls.data["total_pages"] = cls.total_pages() + cls.data["total_pages"] = cls.total_pages() # Only after .total_types() cls.data["next_page"] = cls.next_page() cls.data["prev_page"] = cls.prev_page() cls.data["paginated_relating_types"] = cls.paginated_relating_types() - cls.data["type_thumbnail"] = cls.type_thumbnail() # Only after .relating_type_id() + cls.data["type_thumbnail"] = cls.type_thumbnail() cls.data["is_voidable_element"] = cls.is_voidable_element() cls.data["has_visible_openings"] = cls.has_visible_openings() cls.data["has_visible_boundaries"] = cls.has_visible_boundaries() @@ -114,13 +116,11 @@ class AuthoringData: @classmethod def total_types(cls): - ifc_class = cls.data["ifc_class_current"] - return len(tool.Ifc.get().by_type(ifc_class)) if ifc_class else 0 + return len(cls.data["filtered_type_elements"]) @classmethod def total_pages(cls): - ifc_class = cls.data["ifc_class_current"] - total_types = len(tool.Ifc.get().by_type(ifc_class)) if ifc_class else 0 + total_types = cls.data["total_types"] return math.ceil(total_types / cls.types_per_page) @classmethod @@ -134,12 +134,19 @@ class AuthoringData: return cls.props.type_page - 1 @classmethod - def paginated_relating_types(cls): + def filtered_type_elements(cls): ifc_class = cls.data["ifc_class_current"] if not ifc_class: return [] + elements = list(tool.Ifc.get().by_type(ifc_class)) + if cls.props.search_name: + elements = [e for e in elements if cls.props.search_name.lower() in (e.Name or "Unnamed").lower()] + return natsorted(elements, key=lambda s: (s.Name or "Unnamed").lower()) + + @classmethod + def paginated_relating_types(cls): results = [] - elements = natsorted(tool.Ifc.get().by_type(ifc_class), key=lambda e: e.Name or "Unnamed") + elements = cls.data["filtered_type_elements"] elements = elements[(cls.props.type_page - 1) * cls.types_per_page : cls.props.type_page * cls.types_per_page] for element in elements: predefined_type = ifcopenshell.util.element.get_predefined_type(element) @@ -266,13 +273,8 @@ class AuthoringData: @classmethod def relating_type_id(cls): - results = [] - ifc_class = cls.data["ifc_class_current"] - if ifc_class: - elements = natsorted(tool.Ifc.get().by_type(ifc_class), key=lambda s: (s.Name or "Unnamed").lower()) - results.extend(elements) - return [(str(e.id()), e.Name or "Unnamed", e.Description or "") for e in results] - return [] + elements = cls.data["filtered_type_elements"] + return [(str(e.id()), e.Name or "Unnamed", e.Description or "") for e in elements] @classmethod def relating_type_id_current(cls): diff --git a/src/bonsai/bonsai/bim/module/model/product.py b/src/bonsai/bonsai/bim/module/model/product.py index 93fc8761c6..9e9bafcaba 100644 --- a/src/bonsai/bonsai/bim/module/model/product.py +++ b/src/bonsai/bonsai/bim/module/model/product.py @@ -566,7 +566,9 @@ class LoadTypeThumbnails(bpy.types.Operator, tool.Ifc.Operator): processing = set() # Only process at most one paginated class at a time. # Large projects have hundreds of types which can lead to unnecessary lag. - queue = sorted(tool.Ifc.get().by_type(self.ifc_class), key=lambda e: e.Name or "Unnamed") + if not AuthoringData.is_loaded: + AuthoringData.load() + queue = AuthoringData.data["filtered_type_elements"] if self.limit: queue = queue[self.offset : self.offset + self.limit] else: diff --git a/src/bonsai/bonsai/bim/module/model/prop.py b/src/bonsai/bonsai/bim/module/model/prop.py index 27aa3151a4..c9f1de899c 100644 --- a/src/bonsai/bonsai/bim/module/model/prop.py +++ b/src/bonsai/bonsai/bim/module/model/prop.py @@ -97,11 +97,26 @@ def update_slab_direction_decorator(self, context): SlabDirectionDecorator.uninstall() +def update_search_name(self, context): + AuthoringData.load() + # Total number of pages may decrease when using the search bar : + if self.type_page > AuthoringData.data["total_pages"]: + self.type_page = AuthoringData.data["total_pages"] + bpy.ops.bim.load_type_thumbnails(ifc_class=self.ifc_class) + + class BIMModelProperties(PropertyGroup): ifc_class: bpy.props.EnumProperty(items=get_ifc_class, name="Construction Class", update=update_ifc_class) relating_type_id: bpy.props.EnumProperty( items=get_relating_type_id, name="Relating Type", update=update_relating_type_id ) + search_name: bpy.props.StringProperty( + name="Search Name", + default="", + description="Use this property to filter the list of available types", + update=update_search_name, + options={"SKIP_SAVE", "TEXTEDIT_UPDATE"}, + ) menu_relating_type_id: bpy.props.IntProperty() icon_id: bpy.props.IntProperty() updating: bpy.props.BoolProperty(default=False) diff --git a/src/bonsai/bonsai/bim/module/model/ui.py b/src/bonsai/bonsai/bim/module/model/ui.py index 286fa41794..08fc46cb6f 100644 --- a/src/bonsai/bonsai/bim/module/model/ui.py +++ b/src/bonsai/bonsai/bim/module/model/ui.py @@ -108,12 +108,10 @@ class LaunchTypeManager(bpy.types.Operator): def draw(self, context): props = context.scene.BIMModelProperties row = self.layout.row(align=True) + text = f"{AuthoringData.data['total_types']} {AuthoringData.data['ifc_element_type']}" if AuthoringData.data["total_types"] > 1: - text = f"{AuthoringData.data['total_types']} {AuthoringData.data['ifc_element_type']}s" - else: - text = f"{AuthoringData.data['total_types']} {AuthoringData.data['ifc_element_type']}" + text += "s" row.label(text=text, icon="FILE_VOLUME") - # prop_with_search(row, props, "ifc_class", text="", should_click_ok_to_validate=True) row.menu("BIM_MT_type_manager_menu", text="", icon="PREFERENCES") row = self.layout.row(align=True) row.operator("bim.launch_add_element", text=f"Create New {AuthoringData.data['ifc_element_type']}", icon="ADD") @@ -121,9 +119,6 @@ class LaunchTypeManager(bpy.types.Operator): columns = self.layout.column_flow(columns=3) row = columns.row(align=True) row.alignment = "CENTER" - ### In case you want something here in the future - - ### row = columns.row(align=True) row.alignment = "RIGHT" @@ -136,6 +131,8 @@ class LaunchTypeManager(bpy.types.Operator): op = row.operator("bim.change_type_page", icon="TRIA_RIGHT", text="") op.page = AuthoringData.data["next_page"] + self.layout.prop(props, "search_name", icon="VIEWZOOM", text="") + flow = self.layout.grid_flow(row_major=True, columns=3, even_columns=True, even_rows=True, align=True) for relating_type in AuthoringData.data["paginated_relating_types"]: From 4f4c557f61bb217c26b96fdc9f1feac47d4a8592 Mon Sep 17 00:00:00 2001 From: Gorgious56 Date: Fri, 10 Jan 2025 14:24:33 +0100 Subject: [PATCH 3/8] Another attempt. Went back to previous toolbar : icon + type name + dot grid icon which all launch the type manager. Validating the type manager popup now only closes it. Functionally is the same thing as clicking Cancel. Don't really know what to do to fix it. Does cancel make sense in the type manager ? Do we want to be able to undo what is possible to be done from within the type manager ? You can change the active type either with the dropdown in the popup and in the grid flow. I changed it so that changing the type in the dropdown jumps to the corresponding page. BTW first time using github copilot to figure out the maths and it worked magically :) . I'm not against removing the dropdown but I feel like it's nice to see a plain list to choose from. You can input a specific page number or scrub the page number field. Might be usefull if user has dozens of pages to flip through. Right now the search is very simple, it does not implement fuzzy search or anything fancy with * --- src/bonsai/bonsai/bim/module/model/data.py | 22 ++++++---- src/bonsai/bonsai/bim/module/model/product.py | 2 +- src/bonsai/bonsai/bim/module/model/prop.py | 8 +++- src/bonsai/bonsai/bim/module/model/ui.py | 43 +++++++++++-------- .../bonsai/bim/module/model/workspace.py | 10 ++--- 5 files changed, 48 insertions(+), 37 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/model/data.py b/src/bonsai/bonsai/bim/module/model/data.py index 9a37bc55fa..c19e21a6dc 100644 --- a/src/bonsai/bonsai/bim/module/model/data.py +++ b/src/bonsai/bonsai/bim/module/model/data.py @@ -58,14 +58,15 @@ class AuthoringData: cls.data["ifc_classes"] = cls.ifc_classes() cls.data["ifc_class_current"] = cls.ifc_class_current() # Make sure .ifc_classes() was run before next lines - cls.data["filtered_type_elements"] = cls.filtered_type_elements() - # Make sure .filtered_type_elements() was run before next lines + cls.data["type_elements"] = cls.type_elements() + cls.data["type_elements_filtered"] = cls.type_elements_filtered() cls.data["relating_type_id"] = cls.relating_type_id() # Make sure .relating_type_id() was run before next lines cls.data["relating_type_id_current"] = cls.relating_type_id_current() cls.data["relating_type_name"] = cls.relating_type_name() cls.data["relating_type_description"] = cls.relating_type_description() cls.data["predefined_type"] = cls.predefined_type() + # Make sure .type_elements_filtered() was run before next lines cls.data["total_types"] = cls.total_types() cls.data["total_pages"] = cls.total_pages() # Only after .total_types() cls.data["next_page"] = cls.next_page() @@ -116,7 +117,7 @@ class AuthoringData: @classmethod def total_types(cls): - return len(cls.data["filtered_type_elements"]) + return len(cls.data["type_elements_filtered"]) @classmethod def total_pages(cls): @@ -134,19 +135,24 @@ class AuthoringData: return cls.props.type_page - 1 @classmethod - def filtered_type_elements(cls): + def type_elements(cls): ifc_class = cls.data["ifc_class_current"] if not ifc_class: return [] elements = list(tool.Ifc.get().by_type(ifc_class)) - if cls.props.search_name: - elements = [e for e in elements if cls.props.search_name.lower() in (e.Name or "Unnamed").lower()] return natsorted(elements, key=lambda s: (s.Name or "Unnamed").lower()) + @classmethod + def type_elements_filtered(cls): + elements = cls.data["type_elements"] + if cls.props.search_name: + return [e for e in elements if cls.props.search_name.lower() in (e.Name or "Unnamed").lower()] + return elements + @classmethod def paginated_relating_types(cls): results = [] - elements = cls.data["filtered_type_elements"] + elements = cls.data["type_elements_filtered"] elements = elements[(cls.props.type_page - 1) * cls.types_per_page : cls.props.type_page * cls.types_per_page] for element in elements: predefined_type = ifcopenshell.util.element.get_predefined_type(element) @@ -273,7 +279,7 @@ class AuthoringData: @classmethod def relating_type_id(cls): - elements = cls.data["filtered_type_elements"] + elements = cls.data["type_elements"] return [(str(e.id()), e.Name or "Unnamed", e.Description or "") for e in elements] @classmethod diff --git a/src/bonsai/bonsai/bim/module/model/product.py b/src/bonsai/bonsai/bim/module/model/product.py index 9e9bafcaba..7d76e47d4a 100644 --- a/src/bonsai/bonsai/bim/module/model/product.py +++ b/src/bonsai/bonsai/bim/module/model/product.py @@ -568,7 +568,7 @@ class LoadTypeThumbnails(bpy.types.Operator, tool.Ifc.Operator): # Large projects have hundreds of types which can lead to unnecessary lag. if not AuthoringData.is_loaded: AuthoringData.load() - queue = AuthoringData.data["filtered_type_elements"] + queue = AuthoringData.data["type_elements_filtered"] if self.limit: queue = queue[self.offset : self.offset + self.limit] else: diff --git a/src/bonsai/bonsai/bim/module/model/prop.py b/src/bonsai/bonsai/bim/module/model/prop.py index c9f1de899c..3a164477d6 100644 --- a/src/bonsai/bonsai/bim/module/model/prop.py +++ b/src/bonsai/bonsai/bim/module/model/prop.py @@ -65,10 +65,14 @@ def update_relating_type_id(self, context): AuthoringData.data["relating_type_name"] = AuthoringData.relating_type_name() AuthoringData.data["type_thumbnail"] = AuthoringData.type_thumbnail() AuthoringData.data["predefined_type"] = AuthoringData.predefined_type() + self.type_page = [e[0] for e in AuthoringData.data["relating_type_id"]].index(self.relating_type_id) // 9 + 1 def update_type_page(self, context): AuthoringData.data["paginated_relating_types"] = AuthoringData.paginated_relating_types() + bpy.ops.bim.load_type_thumbnails(ifc_class=self.ifc_class, offset=9 * (self.type_page - 1), limit=9) + self["type_page"] = min(self["type_page"], AuthoringData.data["total_pages"]) + self["type_page"] = max(self["type_page"], 1) def update_relating_array_from_object(self, context): @@ -101,7 +105,7 @@ def update_search_name(self, context): AuthoringData.load() # Total number of pages may decrease when using the search bar : if self.type_page > AuthoringData.data["total_pages"]: - self.type_page = AuthoringData.data["total_pages"] + self.type_page = max(1, AuthoringData.data["total_pages"]) bpy.ops.bim.load_type_thumbnails(ifc_class=self.ifc_class) @@ -177,7 +181,7 @@ class BIMModelProperties(PropertyGroup): # Used for plan calculation points such as in room generation rl3: bpy.props.FloatProperty(name="RL", default=1, subtype="DISTANCE", description="Z offset for space calculation") x_angle: bpy.props.FloatProperty(name="X Angle", default=0, subtype="ANGLE", min=-pi / 180 * 89, max=pi / 180 * 89) - type_page: bpy.props.IntProperty(name="Type Page", default=1, update=update_type_page) + type_page: bpy.props.IntProperty(name="Type Page", default=1, min=1,update=update_type_page) type_name: bpy.props.StringProperty(name="Name", default="TYPEX") boundary_class: bpy.props.EnumProperty(items=get_boundary_class, name="Boundary Class") direction_sense: bpy.props.EnumProperty( diff --git a/src/bonsai/bonsai/bim/module/model/ui.py b/src/bonsai/bonsai/bim/module/model/ui.py index 08fc46cb6f..0534aa6b55 100644 --- a/src/bonsai/bonsai/bim/module/model/ui.py +++ b/src/bonsai/bonsai/bim/module/model/ui.py @@ -87,7 +87,6 @@ class LaunchTypeManager(bpy.types.Operator): bl_description = "Display all available Construction Types to add new occurrences" def execute(self, context): - bpy.ops.bim.hotkey("INVOKE_DEFAULT", hotkey="S_A") return {"FINISHED"} def invoke(self, context, event): @@ -101,9 +100,7 @@ class LaunchTypeManager(bpy.types.Operator): # will be None if project has no types if ifc_class is not None: bpy.ops.bim.load_type_thumbnails(ifc_class=ifc_class, offset=0, limit=9) - return context.window_manager.invoke_props_dialog( - self, width=550, title="Type Manager", confirm_text="Add Occurrence" - ) + return context.window_manager.invoke_props_dialog(self, width=550, title="Type Manager", confirm_text="Close") def draw(self, context): props = context.scene.BIMModelProperties @@ -113,25 +110,33 @@ class LaunchTypeManager(bpy.types.Operator): text += "s" row.label(text=text, icon="FILE_VOLUME") row.menu("BIM_MT_type_manager_menu", text="", icon="PREFERENCES") + row = self.layout.row(align=True) row.operator("bim.launch_add_element", text=f"Create New {AuthoringData.data['ifc_element_type']}", icon="ADD") + row = self.layout.row(align=True) + row.prop(props, "relating_type_id", text="") + # prop_with_search(row, props, "relating_type_id", text="", should_click_ok_to_validate=True) + row.prop(props, "search_name", icon="FILTER", text="") + columns = self.layout.column_flow(columns=3) - row = columns.row(align=True) - row.alignment = "CENTER" + if AuthoringData.data["total_pages"] > 0: + row = columns.row(align=True) + row.alignment = "RIGHT" + row2 = row.row(align=True) + row2.label(text="Page") + row2.prop(props, "type_page", text="", emboss=False) + row2.label(text=f"/{AuthoringData.data['total_pages']} ") - row = columns.row(align=True) - row.alignment = "RIGHT" - if AuthoringData.data["total_pages"] > 1: - row.label(text=f"Page {props.type_page}/{AuthoringData.data['total_pages']} ") - if AuthoringData.data["prev_page"]: - op = row.operator("bim.change_type_page", icon="TRIA_LEFT", text="") - op.page = AuthoringData.data["prev_page"] - if AuthoringData.data["next_page"]: - op = row.operator("bim.change_type_page", icon="TRIA_RIGHT", text="") - op.page = AuthoringData.data["next_page"] + prev_page_op = row.row(align=True) + op = prev_page_op.operator("bim.change_type_page", icon="TRIA_LEFT", text="") + op.page = AuthoringData.data["prev_page"] or 0 + prev_page_op.enabled = op.page > 0 - self.layout.prop(props, "search_name", icon="VIEWZOOM", text="") + next_page_op = row.row(align=True) + op = next_page_op.operator("bim.change_type_page", icon="TRIA_RIGHT", text="") + op.page = AuthoringData.data["next_page"] or 0 + next_page_op.enabled = op.page > 0 flow = self.layout.grid_flow(row_major=True, columns=3, even_columns=True, even_rows=True, align=True) @@ -139,11 +144,11 @@ class LaunchTypeManager(bpy.types.Operator): outer_col = flow.column() box = outer_col.box() - row = box.row(align=True) + row = box.row() op = row.operator("bim.set_active_type", text=relating_type["name"], icon="BLANK1", emboss=False) op.relating_type = relating_type["id"] - op = row.operator("bim.launch_type_menu", icon="PREFERENCES", text="", emboss=False) + op = row.operator("bim.launch_type_menu", icon="PREFERENCES", text="", emboss=True) op.relating_type_id = relating_type["id"] row = box.row() diff --git a/src/bonsai/bonsai/bim/module/model/workspace.py b/src/bonsai/bonsai/bim/module/model/workspace.py index ecdf6d6044..0e6e3bf4e9 100644 --- a/src/bonsai/bonsai/bim/module/model/workspace.py +++ b/src/bonsai/bonsai/bim/module/model/workspace.py @@ -509,13 +509,9 @@ class CreateObjectUI: if ifc_class: box = cls.layout.box() row = box.row(align=True) - prop_with_search(row, cls.props, "relating_type_id", text="") - if AuthoringData.data["type_thumbnail"] and ui_context == "TOOL_HEADER": - row.template_icon(icon_value=AuthoringData.data["type_thumbnail"]) - row.operator("bim.launch_type_manager", text="Type Manager", emboss=False) - else: - row.operator("bim.launch_type_manager", icon="BLANK1", text="Type Manager", emboss=False) - + if ui_context == "TOOL_HEADER": + row.template_icon(icon_value=AuthoringData.data.get("type_thumbnail", 0)) + row.operator("bim.launch_type_manager", text=AuthoringData.data["relating_type_name"], emboss=False) row.operator( "bim.launch_type_manager", icon=tool.Blender.TYPE_MANAGER_ICON, From 7db0196791651e1628ccdb8e24d01a94ff7496fc Mon Sep 17 00:00:00 2001 From: Gorgious56 Date: Fri, 10 Jan 2025 15:17:26 +0100 Subject: [PATCH 4/8] Fix bug where the thumbnail of the active class wasn't updated --- src/bonsai/bonsai/bim/module/model/data.py | 8 +- src/bonsai/bonsai/bim/module/model/product.py | 7 +- src/bonsai/bonsai/bim/module/model/ui.py | 2 +- .../bonsai/bim/module/model/workspace.py | 94 ++++++++++--------- 4 files changed, 57 insertions(+), 54 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/model/data.py b/src/bonsai/bonsai/bim/module/model/data.py index c19e21a6dc..d67d198ff3 100644 --- a/src/bonsai/bonsai/bim/module/model/data.py +++ b/src/bonsai/bonsai/bim/module/model/data.py @@ -107,13 +107,7 @@ class AuthoringData: @classmethod def type_thumbnail(cls): - if not cls.data["relating_type_id"]: - return 0 - relating_type_id = tool.Blender.get_enum_safe(cls.props, "relating_type_id") - if relating_type_id is None: - return 0 - element = tool.Ifc.get().by_id(int(relating_type_id)) - return cls.type_thumbnails.get(element.id(), None) or 0 + return cls.type_thumbnails.get(int(cls.data["relating_type_id_current"] or 0), 0) @classmethod def total_types(cls): diff --git a/src/bonsai/bonsai/bim/module/model/product.py b/src/bonsai/bonsai/bim/module/model/product.py index 7d76e47d4a..b888353cd9 100644 --- a/src/bonsai/bonsai/bim/module/model/product.py +++ b/src/bonsai/bonsai/bim/module/model/product.py @@ -563,7 +563,6 @@ class LoadTypeThumbnails(bpy.types.Operator, tool.Ifc.Operator): return props = bpy.context.scene.BIMModelProperties - processing = set() # Only process at most one paginated class at a time. # Large projects have hundreds of types which can lead to unnecessary lag. if not AuthoringData.is_loaded: @@ -577,6 +576,12 @@ class LoadTypeThumbnails(bpy.types.Operator, tool.Ifc.Operator): offset = 0 queue = queue[offset : offset + 9] + # The active type may be in another page than the active one : + if relating_type_id_current := AuthoringData.data["relating_type_id_current"]: + active_element = tool.Ifc.get_entity_by_id(int(relating_type_id_current)) + if active_element and active_element not in queue: + queue.append(active_element) + while queue: # if bpy.app.is_job_running("RENDER_PREVIEW") does not seem to reflect asset preview generation element = queue.pop() diff --git a/src/bonsai/bonsai/bim/module/model/ui.py b/src/bonsai/bonsai/bim/module/model/ui.py index 0534aa6b55..8cf01f0012 100644 --- a/src/bonsai/bonsai/bim/module/model/ui.py +++ b/src/bonsai/bonsai/bim/module/model/ui.py @@ -84,7 +84,7 @@ class LaunchTypeManager(bpy.types.Operator): bl_idname = "bim.launch_type_manager" bl_label = "Launch Type Manager" bl_options = {"REGISTER", "UNDO"} - bl_description = "Display all available Construction Types to add new occurrences" + bl_description = "Browse, Edit and Manage Types" def execute(self, context): return {"FINISHED"} diff --git a/src/bonsai/bonsai/bim/module/model/workspace.py b/src/bonsai/bonsai/bim/module/model/workspace.py index 0e6e3bf4e9..c8611beab4 100644 --- a/src/bonsai/bonsai/bim/module/model/workspace.py +++ b/src/bonsai/bonsai/bim/module/model/workspace.py @@ -504,54 +504,58 @@ class CreateObjectUI: row = cls.layout.row(align=True) if not AuthoringData.data["ifc_element_type"]: prop_with_search(row, cls.props, "ifc_class", text="Type Class" if ui_context != "TOOL_HEADER" else "") - if AuthoringData.data["ifc_classes"]: - ifc_class = AuthoringData.data["ifc_class_current"] - if ifc_class: - box = cls.layout.box() - row = box.row(align=True) - if ui_context == "TOOL_HEADER": - row.template_icon(icon_value=AuthoringData.data.get("type_thumbnail", 0)) - row.operator("bim.launch_type_manager", text=AuthoringData.data["relating_type_name"], emboss=False) - row.operator( - "bim.launch_type_manager", - icon=tool.Blender.TYPE_MANAGER_ICON, - text="", - emboss=False, - ) + if not AuthoringData.data["ifc_classes"]: + return + if not (ifc_class := AuthoringData.data["ifc_class_current"]): + return - if ui_context != "TOOL_HEADER": - row = box.row(align=True) - row.alignment = "CENTER" - row.operator( - "bim.launch_type_manager", - text=AuthoringData.data["relating_type_description"], - emboss=False, - ) + box = cls.layout.box() - if AuthoringData.data["type_thumbnail"]: - row1 = box.row() - row1.ui_units_y = 0.01 - row1.template_icon(icon_value=AuthoringData.data["type_thumbnail"], scale=4) - row2 = box.column(align=True) - row2.ui_units_y = 4 - for _ in range(4): - row2.operator("bim.launch_type_manager", text="", emboss=False) - else: - op = box.operator( - "bim.load_type_thumbnails", - text="", - icon="FILE_REFRESH", - emboss=False, - ) - op.ifc_class = ifc_class + row = box.row(align=True) + thumbnail: int = AuthoringData.data["type_thumbnail"] + row.template_icon(icon_value=thumbnail) + row.operator("bim.launch_type_manager", text=AuthoringData.data["relating_type_name"], emboss=False) + row.operator( + "bim.launch_type_manager", + icon=tool.Blender.TYPE_MANAGER_ICON, + text="", + emboss=False, + ) - row = box.row(align=True) - row.alignment = "CENTER" - row.operator( - "bim.launch_type_manager", - text=AuthoringData.data["predefined_type"], - emboss=False, - ) + if ui_context == "TOOL_HEADER": + return + row = box.row(align=True) + row.alignment = "CENTER" + row.operator( + "bim.launch_type_manager", + text=AuthoringData.data["relating_type_description"], + emboss=False, + ) + + if thumbnail != 0: + row1 = box.row() + row1.ui_units_y = 0.01 + row1.template_icon(icon_value=thumbnail, scale=4) + row2 = box.column(align=True) + row2.ui_units_y = 4 + for _ in range(4): + row2.operator("bim.launch_type_manager", text="", emboss=False) + else: + op = box.operator( + "bim.load_type_thumbnails", + text="", + icon="FILE_REFRESH", + emboss=False, + ) + op.ifc_class = ifc_class + + row = box.row(align=True) + row.alignment = "CENTER" + row.operator( + "bim.launch_type_manager", + text=AuthoringData.data["predefined_type"], + emboss=False, + ) class EditObjectUI: From a428169a131dd0af0cf330a4e58cc0da586d1858 Mon Sep 17 00:00:00 2001 From: Gorgious56 Date: Fri, 10 Jan 2025 15:29:31 +0100 Subject: [PATCH 5/8] Fix bug when duplicating an ifc type --- src/bonsai/bonsai/bim/module/model/prop.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/bonsai/bonsai/bim/module/model/prop.py b/src/bonsai/bonsai/bim/module/model/prop.py index 3a164477d6..c725c6178c 100644 --- a/src/bonsai/bonsai/bim/module/model/prop.py +++ b/src/bonsai/bonsai/bim/module/model/prop.py @@ -43,6 +43,9 @@ def get_boundary_class(self, context): def get_relating_type_id(self, context): if not AuthoringData.is_loaded: AuthoringData.load() + else: + AuthoringData.data["type_elements"] = AuthoringData.type_elements() + AuthoringData.data["relating_type_id"] = AuthoringData.relating_type_id() return AuthoringData.data["relating_type_id"] From 68f175fc71f7eada28c226b069612b17fea403dc Mon Sep 17 00:00:00 2001 From: Gorgious56 Date: Fri, 10 Jan 2025 15:42:37 +0100 Subject: [PATCH 6/8] Fix bug when trying to add an ifc type occurence from the toolbar in the properties editor --- src/bonsai/bonsai/bim/module/model/ui.py | 2 -- src/bonsai/bonsai/bim/module/model/workspace.py | 5 +++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/model/ui.py b/src/bonsai/bonsai/bim/module/model/ui.py index 8cf01f0012..9cb977e0a4 100644 --- a/src/bonsai/bonsai/bim/module/model/ui.py +++ b/src/bonsai/bonsai/bim/module/model/ui.py @@ -115,8 +115,6 @@ class LaunchTypeManager(bpy.types.Operator): row.operator("bim.launch_add_element", text=f"Create New {AuthoringData.data['ifc_element_type']}", icon="ADD") row = self.layout.row(align=True) - row.prop(props, "relating_type_id", text="") - # prop_with_search(row, props, "relating_type_id", text="", should_click_ok_to_validate=True) row.prop(props, "search_name", icon="FILTER", text="") columns = self.layout.column_flow(columns=3) diff --git a/src/bonsai/bonsai/bim/module/model/workspace.py b/src/bonsai/bonsai/bim/module/model/workspace.py index c8611beab4..98a9fc1921 100644 --- a/src/bonsai/bonsai/bim/module/model/workspace.py +++ b/src/bonsai/bonsai/bim/module/model/workspace.py @@ -440,8 +440,9 @@ class CreateObjectUI: row = cls.layout.row(align=True) if AuthoringData.data["relating_type_id"]: row = cls.layout.row(align=True) if ui_context != "TOOL_HEADER" else row - op = row.operator("bim.hotkey", text="Add", icon_value=custom_icon_previews["ADD"].icon_id) - op.hotkey = "S_A" + if context.space_data.type == "VIEW_3D": # Wall polyline tool works only in 3D Space + op = row.operator("bim.hotkey", text="Add", icon_value=custom_icon_previews["ADD"].icon_id) + op.hotkey = "S_A" else: row.label(text="No Construction Type", icon="FILE_3D") From 9eaf41c9f082b3e282fe7a0afaac6ef821c2954b Mon Sep 17 00:00:00 2001 From: Gorgious56 Date: Mon, 13 Jan 2025 09:28:56 +0100 Subject: [PATCH 7/8] The filter search field now also filters description and predefined type --- src/bonsai/bonsai/bim/module/model/data.py | 11 ++++++++++- src/bonsai/bonsai/bim/module/model/ui.py | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/bonsai/bonsai/bim/module/model/data.py b/src/bonsai/bonsai/bim/module/model/data.py index d67d198ff3..7d5a914b46 100644 --- a/src/bonsai/bonsai/bim/module/model/data.py +++ b/src/bonsai/bonsai/bim/module/model/data.py @@ -138,9 +138,18 @@ class AuthoringData: @classmethod def type_elements_filtered(cls): + search_query = cls.props.search_name.lower() + def filter_element(element): + if search_query in (element.Name or "Unnamed").lower(): + return True + if search_query in (element.Description or "").lower(): + return True + if search_query in ifcopenshell.util.element.get_predefined_type(element).lower(): + return True + return False elements = cls.data["type_elements"] if cls.props.search_name: - return [e for e in elements if cls.props.search_name.lower() in (e.Name or "Unnamed").lower()] + return [e for e in elements if filter_element(e)] return elements @classmethod diff --git a/src/bonsai/bonsai/bim/module/model/ui.py b/src/bonsai/bonsai/bim/module/model/ui.py index 9cb977e0a4..487711c30a 100644 --- a/src/bonsai/bonsai/bim/module/model/ui.py +++ b/src/bonsai/bonsai/bim/module/model/ui.py @@ -115,6 +115,7 @@ class LaunchTypeManager(bpy.types.Operator): row.operator("bim.launch_add_element", text=f"Create New {AuthoringData.data['ifc_element_type']}", icon="ADD") row = self.layout.row(align=True) + # row.prop(props, "relating_type_id", text="") row.prop(props, "search_name", icon="FILTER", text="") columns = self.layout.column_flow(columns=3) From 50ed75fcc253e020bfe8529724f6cbeafe7a0828 Mon Sep 17 00:00:00 2001 From: Gorgious56 Date: Mon, 13 Jan 2025 09:32:23 +0100 Subject: [PATCH 8/8] Fix potential bug where a class doesn't have a predefined type --- src/bonsai/bonsai/bim/module/model/data.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/model/data.py b/src/bonsai/bonsai/bim/module/model/data.py index 7d5a914b46..df2e66dc7c 100644 --- a/src/bonsai/bonsai/bim/module/model/data.py +++ b/src/bonsai/bonsai/bim/module/model/data.py @@ -73,7 +73,7 @@ class AuthoringData: cls.data["prev_page"] = cls.prev_page() cls.data["paginated_relating_types"] = cls.paginated_relating_types() - cls.data["type_thumbnail"] = cls.type_thumbnail() + cls.data["type_thumbnail"] = cls.type_thumbnail() # Only after .relating_type_id_current() cls.data["is_voidable_element"] = cls.is_voidable_element() cls.data["has_visible_openings"] = cls.has_visible_openings() cls.data["has_visible_boundaries"] = cls.has_visible_boundaries() @@ -139,14 +139,16 @@ class AuthoringData: @classmethod def type_elements_filtered(cls): search_query = cls.props.search_name.lower() + def filter_element(element): if search_query in (element.Name or "Unnamed").lower(): return True if search_query in (element.Description or "").lower(): return True - if search_query in ifcopenshell.util.element.get_predefined_type(element).lower(): + if search_query in (ifcopenshell.util.element.get_predefined_type(element) or "").lower(): return True return False + elements = cls.data["type_elements"] if cls.props.search_name: return [e for e in elements if filter_element(e)]