Refactor panel visibility to not use any helpers, operators, and shift config UI into add-on settings

To be consistent with all other settings, I've moved the visibility
config UI from inline into the add-on settings. This restores the
previous tab layout and no longer needs the "settings" icons to be
there. This also removes the need for a "enable UI config" checkbox.

Most of the code previously had dedicated operators to toggle booleans.
This has been removed. This new approach also means helpers aren't
needed.
This commit is contained in:
Dion Moult
2026-01-15 21:59:53 +11:00
parent ed81a0a4b3
commit 47a2c1d21a
6 changed files with 107 additions and 403 deletions
+3 -14
View File
@@ -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
-113
View File
@@ -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
+21 -226
View File
@@ -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"}
+15 -9
View File
@@ -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):
+65 -38
View File
@@ -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
+3 -3
View File
@@ -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