diff --git a/src/bonsai/bonsai/bim/__init__.py b/src/bonsai/bonsai/bim/__init__.py index 785d7eac66..a7dc729881 100644 --- a/src/bonsai/bonsai/bim/__init__.py +++ b/src/bonsai/bonsai/bim/__init__.py @@ -130,12 +130,7 @@ classes = [ prop.StrProperty, operator.BIM_OT_enum_property_search, # /!\ Register AFTER prop.StrProperty operator.BIM_OT_attribute_search_values, - operator.BIM_UL_tab_panels, - operator.BIM_OT_toggle_panel_visibility, - operator.BIM_OT_bookmark_panel, - operator.BIM_OT_manage_tab_panels, operator.BIM_OT_manage_tab_visibility, - operator.BIM_OT_toggle_tab_visibility, operator.BIM_OT_reset_ui_layout, prop.ObjProperty, prop.MultipleFileSelect, @@ -144,7 +139,7 @@ classes = [ prop.BIMAreaProperties, prop.BIMTabProperties, prop.BIMTabVisibility, # Must be registered before BIMProperties - prop.BIMPanelProperties, # Must be registered before BIMProperties + prop.BIMPanelVisibility, # Must be registered before BIMProperties prop.BIMProperties, prop.IfcParameter, prop.PsetQto, @@ -157,6 +152,8 @@ classes = [ prop.BIMSnapGroups, ui.BIM_UL_clipping_plane, ui.BIM_UL_generic, + ui.BIM_UL_tab_visibilities, + ui.BIM_UL_panel_visibilities, ui.DocPreferences, ui.GizmoPreferencesDoor, # Register before GizmoPreferences ui.GizmoPreferencesWindow, # Register before GizmoPreferences @@ -318,10 +315,6 @@ def register(): # RestrictedContext doesn't allow accessing scene attribute, postpone it for a bit. bpy.app.timers.register(tool.Blender.setup_user_data_dir, first_interval=0.1) - bpy.types.Scene.active_tab_name = bpy.props.StringProperty() - bpy.types.Scene.tab_panels = bpy.props.CollectionProperty(type=bpy.types.PropertyGroup) - bpy.types.Scene.active_tab_panel_index = bpy.props.IntProperty() - def unregister(): global icons @@ -363,7 +356,3 @@ def unregister(): tool.Blender.remove_scene_panel_override(panel) bpy.app.translations.unregister("bonsai") - - del bpy.types.Scene.active_tab_name - del bpy.types.Scene.tab_panels - del bpy.types.Scene.active_tab_panel_index diff --git a/src/bonsai/bonsai/bim/helper.py b/src/bonsai/bonsai/bim/helper.py index aed0e09ca4..1a92078be3 100644 --- a/src/bonsai/bonsai/bim/helper.py +++ b/src/bonsai/bonsai/bim/helper.py @@ -792,116 +792,3 @@ def draw_filter( op.group_index = i op.index = j op.module = module - - -# ============================================================================ -# UI Panel Visibility Helpers -# ============================================================================ - - -def get_tab_names(): - from bonsai.bim.prop import get_tab - - enum_items = get_tab(None, None) - # Exclude None separators and the BLENDER tab (not part of BIM tab system) - return [item[0] for item in enum_items if item is not None and item[0] != "BLENDER"] - - -def get_panel_tab_name(panel_class): - if hasattr(panel_class, "bim_tab_name"): - return panel_class.bim_tab_name - return "PROJECT" # Default fallback - - -def get_tab_visibility(tab_name): - bim_props = tool.Blender.get_bim_props() - tab_vis = bim_props.tab_visibilities.get(tab_name) - return tab_vis.is_visible if tab_vis else True - - -def set_tab_visibility(tab_name, visible): - bim_props = tool.Blender.get_bim_props() - tab_vis = bim_props.tab_visibilities.get(tab_name) - if tab_vis: - tab_vis.is_visible = visible - else: - new_tab = bim_props.tab_visibilities.add() - new_tab.name = tab_name - new_tab.is_visible = visible - - -def get_panel_config(panel_id, create_if_missing=False): - try: - bim_props = tool.Blender.get_bim_props() - except (AttributeError, AssertionError): - return None - - for prop in bim_props.panel_properties: - if prop.name == panel_id: - return prop - - if create_if_missing: - try: - prop = bim_props.panel_properties.add() - prop.name = panel_id - return prop - except AttributeError: - pass - - return None - - -def get_all_tab_panels(force_refresh=False): - panels = {tab_name: [] for tab_name in get_tab_names() if tab_name != "BOOKMARK"} - panels["BOOKMARK"] = [] - - bim_props = tool.Blender.get_bim_props() - for prop in bim_props.panel_properties: - panel_class = getattr(bpy.types, prop.name, None) - if panel_class: - tab_name = get_panel_tab_name(panel_class) - if tab_name and tab_name != "BOOKMARK": - bl_label = getattr(panel_class, "bl_label", prop.name) - panels[tab_name].append({"bl_idname": prop.name, "bl_label": bl_label}) - - if prop.is_bookmarked: - panel_class = getattr(bpy.types, prop.name, None) - if panel_class: - bl_label = getattr(panel_class, "bl_label", prop.name) - panels["BOOKMARK"].append({"bl_idname": prop.name, "bl_label": bl_label}) - - if not panels["BOOKMARK"]: - panels["BOOKMARK"] = [{}] - - return panels - - -def initialize_tab_visibilities(): - bim_props = tool.Blender.get_bim_props() - - if len(bim_props.tab_visibilities) > 0: - return - - for tab_name in get_tab_names(): - tab_vis = bim_props.tab_visibilities.add() - tab_vis.name = tab_name - tab_vis.is_visible = True - - -def initialize_panel_properties(): - - bim_props = tool.Blender.get_bim_props() - - if len(bim_props.panel_properties) > 0: - return - - for attr_name in dir(bpy.types): - if attr_name.startswith("BIM_PT_tab_"): - panel_class = getattr(bpy.types, attr_name) - if not hasattr(panel_class, "bl_idname"): - continue - - panel_id = panel_class.bl_idname - - prop = bim_props.panel_properties.add() - prop.name = panel_id diff --git a/src/bonsai/bonsai/bim/operator.py b/src/bonsai/bonsai/bim/operator.py index 8cfc3fb0d4..3621344542 100644 --- a/src/bonsai/bonsai/bim/operator.py +++ b/src/bonsai/bonsai/bim/operator.py @@ -33,15 +33,6 @@ import bonsai.bim import bonsai.tool as tool import bonsai.bim.handler from enum import Enum -from bonsai.bim.helper import ( - get_all_tab_panels, - get_tab_visibility, - set_tab_visibility, - get_tab_names, - get_panel_config, - initialize_panel_properties, - initialize_tab_visibilities, -) from bpy_extras.io_utils import ImportHelper from bonsai.bim import import_ifc from bonsai.bim.prop import StrProperty @@ -1735,156 +1726,6 @@ class BIM_OT_attribute_remove_subitem(bpy.types.Operator): return {"FINISHED"} -class BIM_UL_tab_panels(bpy.types.UIList): - """UIList for Tab Panels""" - - def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): - row = layout.row(align=True) - row.label(text=item["bl_label"]) - - row.operator( - "bim.toggle_panel_visibility", - text="", - icon="HIDE_OFF" if item.get("visible", True) else "HIDE_ON", - ).action = f"TOGGLE_VISIBILITY_{item.name}" - - row.operator( - "bim.bookmark_panel", - text="", - icon="SOLO_ON" if item.get("bookmarked", False) else "SOLO_OFF", - ).action = f"BOOKMARK_{item.name}" - - -class BIM_OT_toggle_panel_visibility(bpy.types.Operator): - """Toggle Panel Visibility""" - - bl_idname = "bim.toggle_panel_visibility" - bl_label = "Toggle Panel Visibility" - bl_options = {"REGISTER", "UNDO"} - - action: bpy.props.StringProperty() - - def execute(self, context): - panel_name = self.action.replace("TOGGLE_VISIBILITY_", "") - active_tab = getattr(context.scene, "active_tab_name", None) or getattr( - tool.Blender.get_bim_props(), "tab", None - ) - is_bookmark_tab = active_tab == "BOOKMARK" - - panel_config = get_panel_config(panel_name, create_if_missing=True) - if panel_config: - if is_bookmark_tab: - panel_config.is_visible_in_bookmarks = not panel_config.is_visible_in_bookmarks - new_value = panel_config.is_visible_in_bookmarks - else: - panel_config.is_visible_in_tab = not panel_config.is_visible_in_tab - new_value = panel_config.is_visible_in_tab - - for item in context.scene.tab_panels: - if item.name == panel_name: - item["visible"] = new_value - break - - for area in bpy.context.window.screen.areas: - if area.type == "PROPERTIES": - area.tag_redraw() - - tab_context = "Bookmarks" if is_bookmark_tab else "Tab" - self.report({"INFO"}, f"Toggled visibility for {panel_name} in {tab_context}.") - return {"FINISHED"} - - -class BIM_OT_bookmark_panel(bpy.types.Operator): - """Bookmark Panel""" - - bl_idname = "bim.bookmark_panel" - bl_label = "Bookmark Panel" - bl_options = {"REGISTER", "UNDO"} - - action: bpy.props.StringProperty() - - def execute(self, context): - panel_name = self.action.replace("BOOKMARK_", "") - panel_config = get_panel_config(panel_name, create_if_missing=True) - - if panel_config: - panel_config.is_bookmarked = not panel_config.is_bookmarked - - for item in context.scene.tab_panels: - if item.name == panel_name: - item["bookmarked"] = panel_config.is_bookmarked - break - - for area in bpy.context.window.screen.areas: - if area.type == "PROPERTIES": - area.tag_redraw() - - self.report({"INFO"}, f"Toggled bookmark for {panel_name}.") - return {"FINISHED"} - - -class BIM_OT_manage_tab_panels(bpy.types.Operator): - """Manage Tab Panels""" - - bl_idname = "bim.manage_tab_panels" - bl_label = "Manage Tab Panels" - bl_options = {"REGISTER", "UNDO"} - - tab_name: bpy.props.StringProperty() - - def invoke(self, context, event): - - context.scene.active_tab_name = self.tab_name - context.scene.tab_panels.clear() - - initialize_tab_visibilities() - initialize_panel_properties() - all_panels = get_all_tab_panels(force_refresh=True) - - for panel_data in all_panels.get(self.tab_name, []): - panel_name = panel_data.get("bl_idname", "") - panel_label = panel_data.get("bl_label", "") - if not panel_name or not panel_label: - continue - - item = context.scene.tab_panels.add() - item.name = panel_name - item["bl_label"] = panel_label - - panel_config = get_panel_config(panel_name, create_if_missing=True) - if panel_config: - if self.tab_name == "BOOKMARK": - item["visible"] = panel_config.is_visible_in_bookmarks - else: - item["visible"] = panel_config.is_visible_in_tab - item["bookmarked"] = panel_config.is_bookmarked - else: - item["visible"] = True - item["bookmarked"] = False - - return context.window_manager.invoke_popup(self) - - def draw(self, context): - layout = self.layout - layout.label(text=f"Manage Panels for {self.tab_name} Tab") - - row = layout.row() - row.template_list("BIM_UL_tab_panels", "", context.scene, "tab_panels", context.scene, "active_tab_panel_index") - - def execute(self, context): - for item in context.scene.tab_panels: - panel_config = get_panel_config(item.name, create_if_missing=True) - if panel_config: - if self.tab_name == "BOOKMARK": - panel_config.is_visible_in_bookmarks = item["visible"] - else: - panel_config.is_visible_in_tab = item["visible"] - panel_config.is_bookmarked = item["bookmarked"] - - self.report({"INFO"}, f"Panels for {self.tab_name} managed successfully.") - return {"FINISHED"} - - class BIM_OT_manage_tab_visibility(bpy.types.Operator): """Manage Tab Visibility""" @@ -1892,51 +1733,26 @@ class BIM_OT_manage_tab_visibility(bpy.types.Operator): bl_label = "Manage Tab Visibility" bl_options = {"REGISTER", "UNDO"} - def draw(self, context): - layout = self.layout - row = layout.row() - row = self.layout.row(align=True) - row.alignment = "RIGHT" - - row.operator("bim.reset_ui_layout", icon="FILE_REFRESH", text="") - row = layout.row() - row = self.layout.row(align=True) - row.alignment = "CENTER" - - for tab_name in get_tab_names(): - row = layout.row() - row.label(text=tab_name) - is_visible = get_tab_visibility(tab_name) - icon = "HIDE_OFF" if is_visible else "HIDE_ON" - op = row.operator("bim.toggle_tab_visibility", text="", icon=icon) - op.tab_name = tab_name - def execute(self, context): - return {"FINISHED"} + from bonsai.bim.prop import get_tab - def invoke(self, context, event): - return context.window_manager.invoke_popup(self) + bprops = tool.Blender.get_bim_props() + bprops.tab_visibilities.clear() + bprops.panel_visibilities.clear() + tabs = [item[0] for item in get_tab(None, None) if item and item[0] != "BLENDER"] + for tab in tabs: + new = bprops.tab_visibilities.add() + new.name = tab - -class BIM_OT_toggle_tab_visibility(bpy.types.Operator): - """Toggle Tab Visibility""" - - bl_idname = "bim.toggle_tab_visibility" - bl_label = "Toggle Tab Visibility" - bl_options = {"REGISTER", "UNDO"} - - tab_name: bpy.props.StringProperty() - - def execute(self, context): - if self.tab_name in get_tab_names(): - current_visibility = get_tab_visibility(self.tab_name) - set_tab_visibility(self.tab_name, not current_visibility) - - for area in bpy.context.window.screen.areas: - if area.type == "PROPERTIES": - area.tag_redraw() - - self.report({"INFO"}, f"Toggled visibility for {self.tab_name}.") + for attr_name in dir(bpy.types): + if attr_name.startswith("BIM_PT_tab_"): + panel_class = getattr(bpy.types, attr_name) + if not hasattr(panel_class, "bl_idname"): + assert False, panel_class + new = bprops.panel_visibilities.add() + new.name = panel_class.bl_idname + new.label = panel_class.bl_label + new.tab_name = panel_class.bim_tab_name return {"FINISHED"} @@ -1948,29 +1764,8 @@ class BIM_OT_reset_ui_layout(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - - for tab_name in get_tab_names(): - set_tab_visibility(tab_name, True) - - get_all_tab_panels()["BOOKMARK"] = [{}] - - for tab_name, panels in get_all_tab_panels().items(): - for panel in panels: - panel_name = panel.get("bl_idname", "") - if not panel_name: - continue - - show_prop_name = f"show_{panel_name.lower()}" - if hasattr(context.scene, show_prop_name): - setattr(context.scene, show_prop_name, True) - - bookmark_prop_name = f"bookmark_{panel_name.lower()}" - if hasattr(context.scene, bookmark_prop_name): - setattr(context.scene, bookmark_prop_name, False) - - for area in bpy.context.window.screen.areas: - if area.type == "PROPERTIES": - area.tag_redraw() - - self.report({"INFO"}, "UI layout reset to default.") + bprops = tool.Blender.get_bim_props() + bprops.tab_visibilities.clear() + bprops.panel_visibilities.clear() + bonsai.bim.handler.refresh_ui_data() return {"FINISHED"} diff --git a/src/bonsai/bonsai/bim/prop.py b/src/bonsai/bonsai/bim/prop.py index 0b513882c6..ec8154b167 100644 --- a/src/bonsai/bonsai/bim/prop.py +++ b/src/bonsai/bonsai/bim/prop.py @@ -548,14 +548,17 @@ class BIMTabVisibility(PropertyGroup): is_visible: bool -class BIMPanelProperties(PropertyGroup): - is_visible_in_tab: BoolProperty(name="Is Visible in Tab", default=True) - is_visible_in_bookmarks: BoolProperty(name="Is Visible in Bookmarks", default=True) - is_bookmarked: BoolProperty(name="Is Bookmarked", default=False) +class BIMPanelVisibility(PropertyGroup): + name: StringProperty(name="Name") + label: StringProperty(name="Label") + tab_name: StringProperty(name="Tab Name") + is_visible: BoolProperty(name="Is Visible in Tab", default=True, update=update_is_visible) + is_bookmarked: BoolProperty(name="Is Bookmarked", default=False, update=update_is_visible) if TYPE_CHECKING: - is_visible_in_tab: bool - is_visible_in_bookmarks: bool + name: str + tab_name: str + is_visible: bool is_bookmarked: bool @@ -632,7 +635,6 @@ class BIMProperties(PropertyGroup): name="Mass Unit", default="KILOGRAM", ) - time_unit: EnumProperty( items=[ ("SECOND", "Second", "Seconds"), @@ -644,7 +646,9 @@ class BIMProperties(PropertyGroup): default="HOUR", ) tab_visibilities: CollectionProperty(type=BIMTabVisibility, name="Tab Visibilities") - panel_properties: CollectionProperty(type=BIMPanelProperties, name="Panel Properties") + active_tab_visibility_index: IntProperty(name="Active Tab Visibility Index") + panel_visibilities: CollectionProperty(type=BIMPanelVisibility, name="Panel Properties") + active_panel_visibility_index: IntProperty(name="Active Panel Property Index") if TYPE_CHECKING: is_dirty: bool @@ -661,7 +665,9 @@ class BIMProperties(PropertyGroup): mass_unit: str time_unit: str tab_visibilities: bpy.types.bpy_prop_collection_idprop[BIMTabVisibility] - panel_properties: bpy.types.bpy_prop_collection_idprop[BIMPanelProperties] + active_tab_visibility_index: int + panel_visibilities: bpy.types.bpy_prop_collection_idprop[BIMPanelVisibility] + active_panel_visibility_index: int class IfcParameter(PropertyGroup): diff --git a/src/bonsai/bonsai/bim/ui.py b/src/bonsai/bonsai/bim/ui.py index a5eb91521b..c550c00872 100644 --- a/src/bonsai/bonsai/bim/ui.py +++ b/src/bonsai/bonsai/bim/ui.py @@ -242,6 +242,39 @@ class BIM_UL_generic(bpy.types.UIList): layout.label(text="", translate=False) +class BIM_UL_tab_visibilities(bpy.types.UIList): + def draw_item( + self, + context, + layout: bpy.types.UILayout, + data: bpy.types.PropertyGroup, + item: bpy.types.PropertyGroup, + icon, + active_data, + active_propname, + ) -> None: + row = layout.row() + row.prop(item, "name", text="", emboss=False) + row.prop(item, "is_visible", text="", icon="HIDE_OFF" if item.is_visible else "HIDE_ON", emboss=False) + + +class BIM_UL_panel_visibilities(bpy.types.UIList): + def draw_item( + self, + context, + layout: bpy.types.UILayout, + data: bpy.types.PropertyGroup, + item: bpy.types.PropertyGroup, + icon, + active_data, + active_propname, + ) -> None: + row = layout.row() + row.prop(item, "label", text="", emboss=False) + row.prop(item, "is_visible", text="", icon="HIDE_OFF" if item.is_visible else "HIDE_ON", emboss=False) + row.prop(item, "is_bookmarked", text="", icon="SOLO_ON" if item.is_bookmarked else "SOLO_OFF", emboss=False) + + class GizmoPreferencesDoor(bpy.types.PropertyGroup): """Property group for door gizmo visibility settings.""" @@ -711,11 +744,6 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences): description="Custom suffix for the metadata blend file. Will be appended to the filename (without .ifc).", default=".ifc.metadata.blend", ) - user_ui_customization: BoolProperty( - name="User UI Customization", - description="Enable user interface customization features (hide/show tabs and panels, bookmark panels) and save the session settings as part of the metadata blend file", - default=False, - ) if TYPE_CHECKING: svg2pdf_command: str @@ -757,7 +785,6 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences): chain_filter_with_set_operations: bool default_filter_with_set_operations_for_globalid_and_class: bool save_metadata_blend_file: bool - user_ui_customization: bool def draw(self, context: bpy.types.Context) -> None: layout = self.layout @@ -965,9 +992,26 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences): row = layout.row() row.separator() row.prop(self, "metadata_blend_file_suffix") - row = layout.row() - row.separator() - row.prop(self, "user_ui_customization") + + bprops = tool.Blender.get_bim_props() + if tab_visibilities := bprops.tab_visibilities: + row = layout.row() + row.operator("bim.reset_ui_layout", icon="LOOP_BACK") + row = layout.row(align=True) + row.template_list( + "BIM_UL_tab_visibilities", "", bprops, "tab_visibilities", bprops, "active_tab_visibility_index" + ) + row.template_list( + "BIM_UL_panel_visibilities", + "", + bprops, + "panel_visibilities", + bprops, + "active_panel_visibility_index", + ) + else: + row = layout.row() + row.operator("bim.manage_tab_visibility", icon="PREFERENCES") # Scene panel groups @@ -983,48 +1027,31 @@ class BIM_PT_tabs(Panel): def draw(self, context): if not UIData.is_loaded: UIData.load() - is_ifc_project = bool(tool.Ifc.get()) aprops = tool.Blender.get_area_props(context) addon_prefs = tool.Blender.get_addon_preferences() - split = self.layout.split(factor=0.9) - col_left = split.column(align=True) - row_left = col_left.row(align=True) - row_left.alignment = "CENTER" + row = self.layout.row() + row.alignment = "CENTER" for tab in UIData.data["tabs"]: - self.draw_tab_entry(row_left, tab[1], tab[0], tab[2], aprops.tab == tab[0]) - row_left.operator("bim.switch_tab", text="", emboss=False, icon="UV_SYNC_SELECT") + self.draw_tab_entry(row, tab[1], tab[0], tab[2], aprops.tab == tab[0]) + row.operator("bim.switch_tab", text="", emboss=False, icon="UV_SYNC_SELECT") - row_left = col_left.row(align=True) + row = self.layout.row() # Yes, that's right. - row_left.alignment = "CENTER" - row_left.scale_y = 0.2 - - if not (addon_prefs.save_metadata_blend_file and addon_prefs.user_ui_customization): - row_left.prop(aprops, "inactive_tab", text="", icon="BLANK1", emboss=False) + row.alignment = "CENTER" + row.scale_y = 0.2 for tab in UIData.data["tabs"]: # Draw a little underscore below the active tab icon. - if aprops.tab == tab: - row_left.prop(aprops, "active_tab", text="", icon="BLANK1") + if aprops.tab == tab[0]: + row.prop(aprops, "active_tab", text="", icon="BLANK1") else: - row_left.prop(aprops, "inactive_tab", text="", icon="BLANK1", emboss=False) - row_left.prop(aprops, "inactive_tab", text="", icon="BLANK1", emboss=False) # space for Switch - col_right = split.column(align=True) - row_right = col_right.row(align=True) - row_right.alignment = "RIGHT" + row.prop(aprops, "inactive_tab", text="", icon="BLANK1", emboss=False) + row.prop(aprops, "inactive_tab", text="", icon="BLANK1", emboss=False) # space for Switch - if addon_prefs.save_metadata_blend_file and addon_prefs.user_ui_customization: - row_right.operator("bim.manage_tab_visibility", icon="PREFERENCES", text="") - - row = self.layout.row(align=True) + row = self.layout.row() row.prop(aprops, "tab", text="") - if addon_prefs.save_metadata_blend_file and addon_prefs.user_ui_customization: - for tab in UIData.data["tabs"]: - if aprops.tab == tab: - row.operator("bim.manage_tab_panels", text="", icon="PREFERENCES").tab_name = tab - if bonsai.REINSTALLED_BBIM_VERSION: box = self.layout.box() box.alert = True diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py index 2f6190c448..24cd5071fa 100644 --- a/src/bonsai/bonsai/tool/blender.py +++ b/src/bonsai/bonsai/tool/blender.py @@ -167,12 +167,12 @@ class Blender(bonsai.core.tool.Blender): return True if (is_bookmark_tab := aprops.tab == "BOOKMARK") or aprops.tab == tab: bprops = tool.Blender.get_bim_props() - if not (panel_visibility := bprops.panel_properties.get(panel)): + if not (panel_visibility := bprops.panel_visibilities.get(panel)): return not is_bookmark_tab if is_bookmark_tab: - if panel_visibility.is_bookmarked and panel_visibility.is_visible_in_bookmarks: + if panel_visibility.is_bookmarked: return True - elif panel_visibility.is_visible_in_tab: + elif panel_visibility.is_visible: return True @classmethod