diff --git a/addon/__init__.py b/addon/__init__.py index f226838..89c66c7 100644 --- a/addon/__init__.py +++ b/addon/__init__.py @@ -14,20 +14,20 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . import bpy -from .classes.funcs import select_all, draw_workspace_menu -from .classes.ops import SW_OT_workspace, SW_OT_link_workspace, SW_OT_move, SW_OT_link_all, SW_OT_remove, SW_OT_switch, SW_OT_rename, SW_OT_copy_from_scene -from .classes.topbar import TOPBAR_HT_upper_bar -from .classes.panel import SW_PT_select_scene_workspaces -from .classes.menu import SW_MT_missing_workspaces, SW_MT_copy_from -from .classes.prefs import SWPreferences +from .sw.funcs import select_all, workspace_menu +from .sw.ops import SW_OT_workspace, SW_OT_link_workspace, SW_OT_move, SW_OT_link_all, SW_OT_remove, SW_OT_switch, SW_OT_rename, SW_OT_copy_from_scene +from .sw.topbar import TOPBAR_HT_upper_bar +from .sw.panel import SW_PT_select_scene_workspaces +from .sw.menu import SW_MT_missing_workspaces, SW_MT_copy_from +from .sw.prefs import SW_Preferences bl_info = { "id": "scene_workspaces", "name": "Scene Workspaces", - "tagline": "Filter and rearrange workspaces individually for each scene", + "tagline": "Filter and sort your workspaces for each scene independently", "blender": (4, 2, 0), "location": "Workspaces topbar, Properties > Scene", - "category": ["System", "User Interface", "Scene"], + "category": ["Scene", "System", "User Interface"], "support": "COMMUNITY", "blender_manifest": "blender_manifest.toml" } @@ -37,14 +37,14 @@ classes = [ SW_OT_switch, SW_OT_move, SW_OT_remove, - SW_OT_link_all, - SW_OT_link_workspace, SW_OT_rename, + SW_OT_link_workspace, + SW_OT_link_all, SW_OT_copy_from_scene, - SW_MT_missing_workspaces, SW_MT_copy_from, + SW_MT_missing_workspaces, SW_PT_select_scene_workspaces, - SWPreferences, + SW_Preferences, TOPBAR_HT_upper_bar ] @@ -55,13 +55,13 @@ def register(): og_header = bpy.types.TOPBAR_HT_upper_bar; for c in classes: bpy.utils.register_class(c) - bpy.types.TOPBAR_MT_workspace_menu.prepend(draw_workspace_menu) + bpy.types.TOPBAR_MT_workspace_menu.prepend(workspace_menu) def unregister(): for c in reversed(classes): bpy.utils.unregister_class(c) bpy.utils.register_class(og_header) - bpy.types.TOPBAR_MT_workspace_menu.remove(draw_workspace_menu) + bpy.types.TOPBAR_MT_workspace_menu.remove(workspace_menu) if __name__ == "__main__": register() diff --git a/addon/blender_manifest.toml b/addon/blender_manifest.toml index 22df8d6..5c2cf5f 100755 --- a/addon/blender_manifest.toml +++ b/addon/blender_manifest.toml @@ -2,7 +2,7 @@ schema_version = "1.0.0" id = "scene_workspaces" version = "1.0.0" name = "Scene Workspaces" -tagline = "Filter and rearrange workspaces individually for each scene" +tagline = "Filter and sort your workspaces for each scene independently" maintainer = "Francesco Bellini " type = "add-on" website = "https://projects.blender.org/Francesco-Bellini/scene_workspaces_addon" diff --git a/addon/classes/funcs.py b/addon/classes/funcs.py deleted file mode 100644 index c8c6842..0000000 --- a/addon/classes/funcs.py +++ /dev/null @@ -1,74 +0,0 @@ -import bpy -from .. import __package__ as base_package - -def has_data(): - return 'scene_workspaces' in bpy.data.scenes[bpy.context.scene.name] - -def get_scene_workspaces(scene = None): - if has_data(): - return bpy.data.scenes[scene if scene else bpy.context.scene.name]['scene_workspaces'] - else: - return [] - -def prefs(): - return bpy.context.preferences.addons[base_package].preferences - -def get_use_global(): - return prefs().use_global - -def get_show_switch(): - return prefs().show_switch - -def get_active_spacing(): - return prefs().active_spacing - -def get_quick_unlink(): - return prefs().quick_unlink - -def set_scene_workspaces(workspaces): - bpy.data.scenes[bpy.context.scene.name]['scene_workspaces'] = workspaces - -def select_all(): - workspaces = [] - for w in bpy.data.workspaces: - workspaces.append(w.name) - set_scene_workspaces(workspaces) - -def get_other_scene_names(): - return [s.name for s in bpy.data.scenes if s.name != bpy.context.scene.name] - -def draw_workspace_ops(layout, i, text=False, name=None): - layout.separator() - remove = layout.operator( - "sw.remove", text="Unlink workspace from scene" if text else "", icon='X') - remove.index = i - layout.separator() - rename = layout.operator( - "sw.rename", text="Rename" if text else "", icon="TEXT") - rename.current_name = name if name else bpy.context.window.workspace.name - rename.new_name = rename.current_name - layout.separator() - top = layout.operator("sw.move", text="Reorder to Front" if text else "", - icon='TRIA_LEFT_BAR' if text else 'TRIA_UP_BAR') - top.index = i - top.top = True - up = layout.operator("sw.move", text="Move Left" if text else "", - icon='TRIA_LEFT' if text else 'TRIA_UP') - up.index = i - up.up = 1 - down = layout.operator("sw.move", text="Move Right" if text else "", - icon='TRIA_RIGHT' if text else 'TRIA_DOWN') - down.index = i - down.up = -1 - bottom = layout.operator("sw.move", text="Reorder to Back" if text else "", - icon='TRIA_RIGHT_BAR' if text else 'TRIA_DOWN_BAR') - bottom.index = i - bottom.top = False - -def draw_workspace_menu(self, context): - layout = self.layout - use_global = get_use_global() - if not use_global and has_data(): - draw_workspace_ops(layout, get_scene_workspaces().index( - context.window.workspace.name), True) - layout.separator() diff --git a/addon/classes/prefs.py b/addon/classes/prefs.py deleted file mode 100644 index eb234cf..0000000 --- a/addon/classes/prefs.py +++ /dev/null @@ -1,23 +0,0 @@ -import bpy -from bpy.types import AddonPreferences -from bpy.props import BoolProperty -from .. import __package__ as base_package - -class SWPreferences(AddonPreferences): - bl_idname = base_package - - use_global: BoolProperty(name="Use default workspaces", description="Switch the workspace topbar between Scene Workspaces and the default one", default=False) - show_switch: BoolProperty(name="Show Switch", description="Show/Hide the switch button next to the workspaces topbar. Useful for quickly lookup the original workspaces topbar", default=True) - active_spacing: BoolProperty(name="Active workspace spacing", description="Add space around the active workspace", default=True) - quick_unlink: BoolProperty(name="Show Quick Unlink", description="Show/Hide the unlink button at left of the active workspace", default=True) - - def draw(self, context): - layout = self.layout - layout.prop(self, "use_global") - layout.prop(self, "show_switch") - - box = layout.box() - box.label(text="UI Options") - box.prop(self, "quick_unlink") - box.prop(self, "active_spacing") - \ No newline at end of file diff --git a/addon/sw/funcs.py b/addon/sw/funcs.py new file mode 100644 index 0000000..eecc97d --- /dev/null +++ b/addon/sw/funcs.py @@ -0,0 +1,80 @@ +from .. import __package__ as base_package +import bpy + +def has_data(): + return 'scene_workspaces' in bpy.data.scenes[bpy.context.scene.name] + +def get_scene_workspaces(scene = None): + if has_data(): + return bpy.data.scenes[scene if scene else bpy.context.scene.name]['scene_workspaces'] + else: + return [] + +def prefs(): + return bpy.context.preferences.addons[base_package].preferences + +def get_use_topbar(): + return prefs().use_topbar + +def get_switch(): + return prefs().switch + +def get_compact(): + return prefs().compact + +def set_scene_workspaces(workspaces, scene = None): + bpy.data.scenes[scene if scene else bpy.context.scene.name]['scene_workspaces'] = workspaces + +def select_all(): + workspaces = [] + for w in bpy.data.workspaces: + workspaces.append(w.name) + set_scene_workspaces(workspaces) + +def get_other_scene_names(): + return [s.name for s in bpy.data.scenes if s.name != bpy.context.scene.name] + +def workspace_ops(layout, i, menu_mode=False, name=None): + layout.separator() + if menu_mode: + layout.prop(prefs(), "compact", text="Compact Mode") + layout.separator() + def remove(l): + remove = l.operator( + "sw.remove", text="Unlink workspace from scene" if menu_mode else "", icon='X') + remove.index = i + if menu_mode: + remove(layout) + layout.separator() + rename = layout.operator( + "sw.rename", text="Rename" if menu_mode else "", icon="TEXT") + rename.current_name = name if name else bpy.context.window.workspace.name + rename.new_name = rename.current_name + layout.separator() + top = layout.operator("sw.move", text="Reorder to Front" if menu_mode else "", + icon='TRIA_LEFT_BAR' if menu_mode else 'TRIA_UP_BAR') + top.index = i + top.top = True + up = layout.operator("sw.move", text="Move Left" if menu_mode else "", + icon='TRIA_LEFT' if menu_mode else 'TRIA_UP') + up.index = i + up.up = 1 + down = layout.operator("sw.move", text="Move Right" if menu_mode else "", + icon='TRIA_RIGHT' if menu_mode else 'TRIA_DOWN') + down.index = i + down.up = -1 + bottom = layout.operator("sw.move", text="Reorder to Back" if menu_mode else "", + icon='TRIA_RIGHT_BAR' if menu_mode else 'TRIA_DOWN_BAR') + bottom.index = i + bottom.top = False + if not menu_mode: + layout.separator() + remove(layout) + +def workspace_menu(self, context): + layout = self.layout + use_topbar = get_use_topbar() + if use_topbar and has_data(): + workspace_ops(layout, get_scene_workspaces().index( + context.window.workspace.name), True) + layout.separator() diff --git a/addon/classes/menu.py b/addon/sw/menu.py similarity index 96% rename from addon/classes/menu.py rename to addon/sw/menu.py index 89c9e0e..eef69ed 100644 --- a/addon/classes/menu.py +++ b/addon/sw/menu.py @@ -22,4 +22,4 @@ class SW_MT_missing_workspaces(Menu): layout.separator() for w in bpy.data.workspaces: if w.name not in get_scene_workspaces(): - layout.operator("sw.link_workspace", text=w.name, icon="ADD").workspace = w.name + layout.operator("sw.link_workspace", text=w.name, icon="LINKED").workspace = w.name diff --git a/addon/classes/ops.py b/addon/sw/ops.py similarity index 88% rename from addon/classes/ops.py rename to addon/sw/ops.py index a3ec9dd..7dbd519 100644 --- a/addon/classes/ops.py +++ b/addon/sw/ops.py @@ -1,9 +1,8 @@ from .. import __package__ as base_package -import time import bpy from bpy.types import Operator from bpy.props import StringProperty, BoolProperty, IntProperty -from .funcs import select_all, get_scene_workspaces, set_scene_workspaces, get_use_global, has_data, prefs +from .funcs import select_all, get_scene_workspaces, set_scene_workspaces, get_use_topbar, has_data, prefs class SW_OT_switch(Operator): bl_idname = "sw.switch" @@ -11,7 +10,7 @@ class SW_OT_switch(Operator): bl_description = "Switch workspaces topbar between Scene Workspaces and default" def execute(self, context): - prefs().use_global = not get_use_global() + prefs().use_topbar = not get_use_topbar() context.preferences.is_dirty = True return {'FINISHED'} @@ -65,7 +64,7 @@ class SW_OT_move(Operator): class SW_OT_rename(Operator): bl_idname = "sw.rename" bl_label = "Rename workspace" - bl_description = "Rename the current workspace (this rename both the global workspace and the Scene workspace)" + bl_description = "Rename the current workspace (this rename both the global workspace as the scene workspace)" current_name: StringProperty(name="Current Name", options={'HIDDEN'}) new_name: StringProperty( @@ -74,12 +73,18 @@ class SW_OT_rename(Operator): def execute(self, context): w = bpy.data.workspaces[self.current_name] + # Update global workspace name w.name = self.new_name - l = get_scene_workspaces() - i = l.index(self.current_name) - l.pop(i) - l.insert(i, self.new_name) - set_scene_workspaces(l) + # Update name in all scenes, if present, to avoid missing workspace + for s in bpy.data.scenes: + l = get_scene_workspaces(s.name) + try: + i = l.index(self.current_name) + l.pop(i) + l.insert(i, self.new_name) + set_scene_workspaces(l, s.name) + finally: + continue return {'FINISHED'} def invoke(self, context, event): diff --git a/addon/classes/panel.py b/addon/sw/panel.py similarity index 93% rename from addon/classes/panel.py rename to addon/sw/panel.py index 310aa37..1eae9d5 100644 --- a/addon/classes/panel.py +++ b/addon/sw/panel.py @@ -1,6 +1,6 @@ import bpy from bpy.types import Panel -from .funcs import get_scene_workspaces, draw_workspace_ops +from .funcs import get_scene_workspaces, workspace_ops class SW_PT_select_scene_workspaces(Panel): bl_idname = "sw.select_scene_workspaces" @@ -12,10 +12,10 @@ class SW_PT_select_scene_workspaces(Panel): def draw(self, context): layout = self.layout - layout.label(text="Workspaces linked to this Scene:") - sw = get_scene_workspaces() + layout.label(text="Workspaces linked to this Scene:") box = layout.box() + sw = get_scene_workspaces() if not sw: box.label(text="There are no workspaces linked right now...") box.operator("sw.link_all") @@ -27,17 +27,17 @@ class SW_PT_select_scene_workspaces(Panel): row = box2.row(align=True) row.scale_y = 2.1 row.label(text=f"{w}") - draw_workspace_ops(row, i, False, w) + workspace_ops(row, i, False, w) i += 1 box.separator() layout.separator() - layout.label(text="Link other workspaces") - l = [x for x in bpy.data.workspaces if x.name not in sw] + layout.label(text="Link other workspaces") box2 = layout.box() row = box2.row() row_count = 0 + l = [x for x in bpy.data.workspaces if x.name not in sw] for w in l: if row_count > 0 and row_count % 4 == 0: row = box2.row() diff --git a/addon/sw/prefs.py b/addon/sw/prefs.py new file mode 100644 index 0000000..aa439f8 --- /dev/null +++ b/addon/sw/prefs.py @@ -0,0 +1,20 @@ +from .. import __package__ as base_package +import bpy +from bpy.types import AddonPreferences +from bpy.props import BoolProperty + +class SW_Preferences(AddonPreferences): + bl_idname = base_package + + use_topbar: BoolProperty(name="Use Scene Workspaces topbar", description="Switch the workspace topbar between Scene Workspaces and the default one", default=True) + switch: BoolProperty(name="Show Switch", description="Show/Hide the switch button next to the workspaces topbar. Useful for quickly lookup the original workspaces topbar", default=True) + compact: BoolProperty(name="Use Compact Mode (for active workspace)", description="If disabled, adds spacing around the active workspace and a quick unlink button", default=True) + + def draw(self, context): + layout = self.layout + layout.prop(self, "use_topbar") + layout.prop(self, "switch") + + box = layout.box() + box.label(text="UI Options") + box.prop(self, "compact") diff --git a/addon/classes/topbar.py b/addon/sw/topbar.py similarity index 79% rename from addon/classes/topbar.py rename to addon/sw/topbar.py index 41a7ddb..607228f 100644 --- a/addon/classes/topbar.py +++ b/addon/sw/topbar.py @@ -1,7 +1,7 @@ from .. import __package__ as base_package import bpy from bpy.types import Header, Panel -from .funcs import get_scene_workspaces, get_use_global, get_show_switch, get_quick_unlink, get_active_spacing +from .funcs import get_scene_workspaces, get_use_topbar, get_switch, get_compact # Ref TOPBAR_HT_upper_bar https://projects.blender.org/blender/blender/src/commit/2204157a2c9fc926643b0e39968602c750d9b5e6/scripts/startup/bl_ui/space_topbar.py#L14 class TOPBAR_HT_upper_bar(Header): @@ -16,7 +16,6 @@ class TOPBAR_HT_upper_bar(Header): self.draw_left(context) def draw_left(self, context): - global workspace_order layout = self.layout window = context.window @@ -26,46 +25,45 @@ class TOPBAR_HT_upper_bar(Header): layout.separator() - - # Edited - use_global = get_use_global() - quick_unlink = get_quick_unlink() - active_spacing = get_active_spacing() + # Edit start + use_topbar = get_use_topbar() + compact = get_compact() + switch= get_switch() if not screen.show_fullscreen: main_col = layout.column() spacer_row = main_col.row() - spacer_row.scale_y = .33 + spacer_row.scale_y = 1 main_row = main_col.row() sy = 1.18 - sx = 1.12 - if get_show_switch(): + sx = 1.18 + if switch: col = main_row.column() # Show/Hide Custom Topbar - col.operator("sw.switch", text="", emboss=False, icon="CHECKBOX_DEHLT" if use_global else "CHECKBOX_HLT") - if use_global: + col.operator("sw.switch", text="", emboss=False, icon="CHECKBOX_HLT" if use_topbar else "CHECKBOX_DEHLT") + if not use_topbar: # Original layout.template_ID_tabs(window, "workspace", new="workspace.add", menu="TOPBAR_MT_workspace_menu") else: r = main_row.row(align=True) r.scale_x = 0.87 r.scale_y = sy - ws = get_scene_workspaces() + sw = get_scene_workspaces() # Link all (with no linked workspaces) - if not ws and bpy.data.workspaces: + if not sw and bpy.data.workspaces: c = r.column() c.scale_x = 1.4 c.operator("sw.link_all", text="Link all workspaces", icon="LINKED") active_not_here = True - for w in ws: - i = ws.index(w) + for w in sw: + i = sw.index(w) active = bpy.context.window.workspace.name == w - if active and i > 0 and active_spacing: + if active and i > 0 and not compact: r.separator() ab = r.box() ab = ab.row(align=True) # Workspace unlink - if active and quick_unlink: + if active and not compact: col = ab.column() col.scale_x = sx col.operator("sw.remove", text="", icon="X", emboss=False).index = i @@ -78,23 +76,32 @@ class TOPBAR_HT_upper_bar(Header): col.scale_x = sx col.menu("TOPBAR_MT_workspace_menu", text="", icon="OPTIONS") active_not_here = False - if active_spacing: + if not compact: r.separator() - + + if not compact: + r.separator() + size = sx if compact else sx * 1.25 # Link other workspaces - if [x for x in bpy.data.workspaces if x.name not in get_scene_workspaces()]: + if len(bpy.data.workspaces) > len(sw): c = r.column() - c.scale_x = sx + c.scale_x = size c.menu("SW_MT_missing_workspaces", text="", icon="WORKSPACE") + if not compact: + r.separator() # Duplicate linked workspaces from other scenes if len(bpy.data.scenes) > 1: c = r.column() - c.scale_x = sx + c.scale_x = size c.menu("SW_MT_copy_from", text="", icon="DUPLICATE") + if not compact: + r.separator() # Original Add Workspace menu c = r.column() - c.scale_x = sx + c.scale_x = size c.operator("workspace.add", text="", icon="ADD") + if not compact: + r.separator() # Active (but not linked) workspace if active_not_here: @@ -108,7 +115,7 @@ class TOPBAR_HT_upper_bar(Header): col = r.box() col.scale_x = sx col.menu("TOPBAR_MT_workspace_menu", text="", icon="OPTIONS") - # End Edited + # Edit - End else: layout.operator("screen.back_to_previous", icon='SCREEN_BACK', text="Back to Previous")