From 782e5077094a41146c3cb36a295d151eed4c62d5 Mon Sep 17 00:00:00 2001 From: doc-code Date: Sat, 7 Sep 2024 16:21:43 +0200 Subject: [PATCH] Add copy from scene menu, refactor, UI updates --- addon/__init__.py | 41 +++++++++++------------ addon/classes/funcs.py | 38 +++++++++------------- addon/classes/menu.py | 25 ++++++++------ addon/classes/ops.py | 66 ++++++++++++++++++++----------------- addon/classes/panel.py | 20 ++++++++---- addon/classes/prefs.py | 4 +-- addon/classes/topbar.py | 72 +++++++++++++++++++++++++++-------------- 7 files changed, 150 insertions(+), 116 deletions(-) diff --git a/addon/__init__.py b/addon/__init__.py index 0c4a666..22b9dcd 100644 --- a/addon/__init__.py +++ b/addon/__init__.py @@ -14,13 +14,12 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . import bpy -from bpy.app.handlers import persistent -from .classes.funcs import init_data, draw_workspace_menu -from .classes.ops import WBS_OT_workspace, WBS_OT_sel_workspace, WBS_OT_move, WBS_OT_sel_all, WBS_OT_remove, WBS_OT_switch, WBS_OT_rename +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 WBS_PT_select_scene_workspaces -from .classes.menu import WBS_MT_missing_workspaces -from .classes.prefs import WBSPreferences +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 bl_info = { "id": "scene_workspaces", @@ -33,21 +32,19 @@ bl_info = { "blender_manifest": "blender_manifest.toml" } -@persistent -def load_handler(dummy): - init_data() - classes = [ - WBS_OT_workspace, - WBS_OT_switch, - WBS_OT_move, - WBS_OT_remove, - WBS_OT_sel_all, - WBS_OT_sel_workspace, - WBS_OT_rename, - WBS_MT_missing_workspaces, - WBS_PT_select_scene_workspaces, - WBSPreferences, + SW_OT_workspace, + SW_OT_switch, + SW_OT_move, + SW_OT_remove, + SW_OT_link_all, + SW_OT_link_workspace, + SW_OT_rename, + SW_OT_copy_from_scene, + SW_MT_missing_workspaces, + SW_MT_copy_from, + SW_PT_select_scene_workspaces, + SWPreferences, TOPBAR_HT_upper_bar ] @@ -58,14 +55,12 @@ def register(): og_header = bpy.types.TOPBAR_HT_upper_bar; for c in classes: bpy.utils.register_class(c) - bpy.app.handlers.load_post.append(load_handler) - bpy.types.TOPBAR_MT_workspace_menu.append(draw_workspace_menu) + bpy.types.TOPBAR_MT_workspace_menu.prepend(draw_workspace_menu) def unregister(): for c in reversed(classes): bpy.utils.unregister_class(c) bpy.utils.register_class(og_header) - bpy.app.handlers.load_post.remove(load_handler) bpy.types.TOPBAR_MT_workspace_menu.remove(draw_workspace_menu) if __name__ == "__main__": diff --git a/addon/classes/funcs.py b/addon/classes/funcs.py index c1cc788..c07dc13 100644 --- a/addon/classes/funcs.py +++ b/addon/classes/funcs.py @@ -1,20 +1,12 @@ import bpy from .. import __package__ as base_package -def redraw_props(): - for area in bpy.context.window.screen.areas: - if area.type == 'TOPBAR': - area.tag_redraw() - for area in bpy.context.screen.areas: - if area.type == 'PROPERTIES': - area.tag_redraw() - def has_data(): return 'scene_workspaces' in bpy.data.scenes[bpy.context.scene.name] -def get_scene_workspaces(): +def get_scene_workspaces(scene = None): if has_data(): - return bpy.data.scenes[bpy.context.scene.name]['scene_workspaces'] + return bpy.data.scenes[scene if scene else bpy.context.scene.name]['scene_workspaces'] else: return [] @@ -23,46 +15,48 @@ def get_use_global(): def set_scene_workspaces(workspaces): bpy.data.scenes[bpy.context.scene.name]['scene_workspaces'] = workspaces - redraw_props() -def init_data(): +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( - "wbs.rename", text="Rename" if text else "", icon="TEXT") + "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("wbs.move", text="Reorder to Front" if text else "", + 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("wbs.move", text="Move Left" if text else "", + 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("wbs.move", text="Move Right" if text else "", + 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("wbs.move", text="Reorder to Back" if text else "", + 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 - layout.separator() - remove = layout.operator( - "wbs.remove", text="Unlink workspace from scene" if text else "", icon='X') - remove.index = i def draw_workspace_menu(self, context): layout = self.layout use_global = get_use_global() if not use_global and has_data(): - layout.separator() draw_workspace_ops(layout, get_scene_workspaces().index( context.window.workspace.name), True) + layout.separator() diff --git a/addon/classes/menu.py b/addon/classes/menu.py index 454b85f..3de9c46 100644 --- a/addon/classes/menu.py +++ b/addon/classes/menu.py @@ -2,17 +2,24 @@ import bpy from bpy.types import Menu from .funcs import get_scene_workspaces -class WBS_MT_missing_workspaces(Menu): - bl_label = "Link other workspaces" +class SW_MT_copy_from(Menu): + bl_label = "Copy and link workspaces from other scenes" def draw(self, context): layout = self.layout - present = False + for s in bpy.data.scenes: + if s.name != bpy.context.scene.name: + layout.operator("sw.copy_from_scene", text=s.name, icon="SCENE").scene = s.name + +class SW_MT_missing_workspaces(Menu): + bl_label = "Link other workspaces to this scene" + + def draw(self, context): + layout = self.layout + present = len(bpy.data.workspaces) > len(get_scene_workspaces()) + if present: + layout.operator("sw.link_all", text="Link all workspaces") + layout.separator() for w in bpy.data.workspaces: if w.name not in get_scene_workspaces(): - present = True - layout.operator("wbs.sel_workspace", text=w.name, icon="ADD").workspace = w.name - - if present: - layout.separator() - layout.operator("wbs.sel_all", text="Link all workspaces") + layout.operator("sw.link_workspace", text=w.name, icon="ADD").workspace = w.name diff --git a/addon/classes/ops.py b/addon/classes/ops.py index c0903de..04759e5 100644 --- a/addon/classes/ops.py +++ b/addon/classes/ops.py @@ -1,25 +1,23 @@ from .. import __package__ as base_package +import time import bpy from bpy.types import Operator from bpy.props import StringProperty, BoolProperty, IntProperty -from .topbar import TOPBAR_HT_upper_bar -from .funcs import init_data, redraw_props, set_scene_workspaces, draw_workspace_ops, get_use_global, has_data -from .get_workspaces import get_scene_workspaces -import time +from .funcs import select_all, get_scene_workspaces, set_scene_workspaces, get_use_global, has_data -class WBS_OT_switch(Operator): - bl_idname = "wbs.switch" - bl_label = "Scene Workspaces Switch" - bl_description = "Switch between Scene Workspaces and global workspaces" +class SW_OT_switch(Operator): + bl_idname = "sw.switch" + bl_label = "Scene Workspaces On/Off" + bl_description = "Switch workspaces topbar between Scene Workspaces and default" def execute(self, context): context.preferences.addons[base_package].preferences.use_global = not get_use_global() context.preferences.is_dirty = True return {'FINISHED'} -class WBS_OT_workspace(Operator): - bl_idname = "wbs.workspace" - bl_label = "Select Workspace" +class SW_OT_workspace(Operator): + bl_idname = "sw.workspace" + bl_label = "Workspace" bl_description = "Make this workspace active (or remove it from the scene, if missing)" workspace: StringProperty(name="workspace") @@ -33,18 +31,19 @@ class WBS_OT_workspace(Operator): set_scene_workspaces(l) return {'FINISHED'} -class WBS_OT_sel_all(Operator): - bl_idname = "wbs.sel_all" +class SW_OT_link_all(Operator): + bl_idname = "sw.link_all" bl_label = "Link all workspaces to this Scene" + bl_description = "Link all workspaces avaliable to this scene" def execute(self, context): - init_data() + select_all() return {'FINISHED'} -class WBS_OT_move(Operator): - bl_idname = "wbs.move" +class SW_OT_move(Operator): + bl_idname = "sw.move" bl_label = "Reorder Workspace" - bl_description = "Re-order this workspace in the desired location (only for this Scene)" + bl_description = "Reorder this workspace in the desired location (only for this Scene)" up: IntProperty(name="up", default=0) # 0 means using `top` for reorder back/front index: IntProperty(name="index", default=-1) @@ -63,10 +62,10 @@ class WBS_OT_move(Operator): self.top = False return {'FINISHED'} -class WBS_OT_rename(Operator): - bl_idname = "wbs.rename" +class SW_OT_rename(Operator): + bl_idname = "sw.rename" bl_label = "Rename workspace" - bl_description = "Rename the current workspace" + bl_description = "Rename the current workspace (this rename both the global workspace and the Scene workspace)" current_name: StringProperty(name="Current Name", options={'HIDDEN'}) new_name: StringProperty( @@ -86,10 +85,10 @@ class WBS_OT_rename(Operator): def invoke(self, context, event): return context.window_manager.invoke_props_dialog(self, title=f"Rename Workspace '{self.current_name}'") -class WBS_OT_remove(Operator): - bl_idname = "wbs.remove" +class SW_OT_remove(Operator): + bl_idname = "sw.remove" bl_label = "Unlink Workspace" - bl_description = "Unlink this workspace from this Scene" + bl_description = "Unlink this workspace from this scene (this will not delete the workspace)" index: IntProperty(name="index", default=-1) @@ -99,10 +98,22 @@ class WBS_OT_remove(Operator): set_scene_workspaces(l) return {'FINISHED'} -class WBS_OT_sel_workspace(Operator): - bl_idname = "wbs.sel_workspace" +class SW_OT_copy_from_scene(Operator): + bl_idname = "sw.copy_from_scene" + bl_label = "Copy from another Scene" + bl_description = "Copy linked workspaces from this scene" + + scene: StringProperty(name="scene") + + def execute(self, context): + l = get_scene_workspaces(self.scene) + set_scene_workspaces(l) + return {'FINISHED'} + +class SW_OT_link_workspace(Operator): + bl_idname = "sw.link_workspace" bl_label = "Link Workspace" - bl_description = "This is the active workspace, but it's not linked to the current Scene. Click to link" + bl_description = "Link this workspace to this Scene" workspace: StringProperty(name="workspace") @@ -111,9 +122,6 @@ class WBS_OT_sel_workspace(Operator): set_scene_workspaces([]) if self.workspace in get_scene_workspaces(): set_scene_workspaces([x for x in get_scene_workspaces() if x != self.workspace]) - self.report({'INFO'}, f"{self.workspace} unchecked!") else: set_scene_workspaces([*get_scene_workspaces(), self.workspace]) - self.report({'INFO'}, f"{self.workspace} checked!") return {'FINISHED'} - diff --git a/addon/classes/panel.py b/addon/classes/panel.py index 5de326b..e709b62 100644 --- a/addon/classes/panel.py +++ b/addon/classes/panel.py @@ -1,8 +1,9 @@ +import bpy from bpy.types import Panel from .funcs import get_scene_workspaces, draw_workspace_ops -class WBS_PT_select_scene_workspaces(Panel): - bl_idname = "wbs.select_scene_workspaces" +class SW_PT_select_scene_workspaces(Panel): + bl_idname = "sw.select_scene_workspaces" bl_label = "Scene Workspaces" bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' @@ -32,11 +33,16 @@ class WBS_PT_select_scene_workspaces(Panel): layout.label(text="Link other workspaces") l = [x for x in bpy.data.workspaces if x.name not in sw] - box2 = layout.box().split() - box2.label(text="All workspaces are currently linked.") + box2 = layout.box() + row = box2.row() + row_count = 0 for w in l: - row = box2.column() - row.operator("wbs.sel_workspace", text=w.name).workspace = w.name + if row_count > 0 and row_count % 4 == 0: + row = box2.row() + row.operator("sw.link_workspace", text=w.name).workspace = w.name + row_count += 1 if l: layout.separator() - layout.operator("wbs.sel_all") + layout.operator("sw.link_all") + else: + box2.label(text="All workspaces are currently linked.") diff --git a/addon/classes/prefs.py b/addon/classes/prefs.py index aa59c8e..1b321fb 100644 --- a/addon/classes/prefs.py +++ b/addon/classes/prefs.py @@ -3,10 +3,10 @@ from bpy.types import AddonPreferences from bpy.props import BoolProperty from .. import __package__ as base_package -class WBSPreferences(AddonPreferences): +class SWPreferences(AddonPreferences): bl_idname = base_package - use_global: BoolProperty(name="Use default workspaces", description="", default=False) + use_global: BoolProperty(name="Use default workspaces", description="Switch the workspace topbar between Scene Workspaces and the default one", default=False) def draw(self, context): layout = self.layout diff --git a/addon/classes/topbar.py b/addon/classes/topbar.py index e0fa666..f4e0b89 100644 --- a/addon/classes/topbar.py +++ b/addon/classes/topbar.py @@ -31,46 +31,70 @@ class TOPBAR_HT_upper_bar(Header): use_global = get_use_global() if not screen.show_fullscreen: + main_col = layout.column() + spacer_row = main_col.row() + spacer_row.scale_y = .33 + main_row = main_col.row() + sy = 1.18 + sx = 1.1 + col = main_row.column() + col.scale_y = sy + col.operator("sw.switch", text="", emboss=False, icon="RADIOBUT_OFF" if use_global else "RADIOBUT_ON") if use_global: # Original layout.template_ID_tabs(window, "workspace", new="workspace.add", menu="TOPBAR_MT_workspace_menu") else: - r = layout.row(align=True) - r.scale_x = 0.89 - r.scale_y = 1.24 + r = main_row.row(align=True) + r.scale_x = 0.87 + r.scale_y = sy ws = get_scene_workspaces() + if not ws 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) active = bpy.context.window.workspace.name == w + if active and i > 0: + r.separator() + ab = r.box() + ab = ab.row(align=True) if active: - col = r.column() - col.scale_x = 1.5 + col = ab.column() + col.scale_x = sx + col.operator("sw.remove", text="", icon="X", emboss=False).index = i + exist = w in [x.name for x in bpy.data.workspaces] + ab.operator("sw.workspace", text=w, icon="NONE" if exist else "ERROR", emboss=active, depress=active).workspace = w + if active: + col = ab.column() + col.scale_x = sx col.menu("TOPBAR_MT_workspace_menu", text="", icon="OPTIONS") active_not_here = False - box = r.box() - exist = w in [x.name for x in bpy.data.workspaces] - box.operator("wbs.workspace", text=w, icon="NONE" if exist else "ERROR", emboss=active, depress=active).workspace = w - if active_not_here: - r.separator() - col = r.column() - col.scale_x = 1.5 - col.menu("TOPBAR_MT_workspace_menu", text="", icon="OPTIONS") - b = r.box() - b.scale_x = 1.5 - b.operator("wbs.sel_workspace", text=bpy.context.window.workspace.name, icon="ADD", emboss=True, depress=True).workspace = bpy.context.window.workspace.name + r.separator() - r.separator() if [x for x in bpy.data.workspaces if x.name not in get_scene_workspaces()]: c = r.column() - c.scale_x = 1.3 - c.menu("WBS_MT_missing_workspaces", text="", icon="WORKSPACE") - r.separator() + c.scale_x = sx + c.menu("SW_MT_missing_workspaces", text="", icon="THREE_DOTS") c = r.column() - c.scale_x = 1.3 + c.scale_x = sx c.operator("workspace.add", text="", icon="ADD") - col = layout.column() - col.scale_y = 1.24 - col.operator("wbs.switch", text="", emboss=False, icon="RADIOBUT_OFF" if use_global else "RADIOBUT_ON") + if len(bpy.data.scenes) > 1: + c = r.column() + c.scale_x = sx + c.menu("SW_MT_copy_from", text="", icon="DUPLICATE") + + if active_not_here: + r.separator() + r = r.box() + r = r.row(align=True) + b = r.box() + b.scale_x = sx + b.operator("sw.link_workspace", text=f"{bpy.context.window.workspace.name} (not linked)", icon="ADD", emboss=True, depress=True).workspace = bpy.context.window.workspace.name + col = r.box() + col.scale_x = sx + col.menu("TOPBAR_MT_workspace_menu", text="", icon="OPTIONS") # End Edited else: layout.operator("screen.back_to_previous", icon='SCREEN_BACK', text="Back to Previous")