Add copy from scene menu, refactor, UI updates

This commit is contained in:
Francesco Bellini 2024-09-07 16:21:43 +02:00
parent c8acd91d45
commit 782e507709
7 changed files with 150 additions and 116 deletions

View File

@ -14,13 +14,12 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
import bpy import bpy
from bpy.app.handlers import persistent from .classes.funcs import select_all, draw_workspace_menu
from .classes.funcs import init_data, 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.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.topbar import TOPBAR_HT_upper_bar from .classes.topbar import TOPBAR_HT_upper_bar
from .classes.panel import WBS_PT_select_scene_workspaces from .classes.panel import SW_PT_select_scene_workspaces
from .classes.menu import WBS_MT_missing_workspaces from .classes.menu import SW_MT_missing_workspaces, SW_MT_copy_from
from .classes.prefs import WBSPreferences from .classes.prefs import SWPreferences
bl_info = { bl_info = {
"id": "scene_workspaces", "id": "scene_workspaces",
@ -33,21 +32,19 @@ bl_info = {
"blender_manifest": "blender_manifest.toml" "blender_manifest": "blender_manifest.toml"
} }
@persistent
def load_handler(dummy):
init_data()
classes = [ classes = [
WBS_OT_workspace, SW_OT_workspace,
WBS_OT_switch, SW_OT_switch,
WBS_OT_move, SW_OT_move,
WBS_OT_remove, SW_OT_remove,
WBS_OT_sel_all, SW_OT_link_all,
WBS_OT_sel_workspace, SW_OT_link_workspace,
WBS_OT_rename, SW_OT_rename,
WBS_MT_missing_workspaces, SW_OT_copy_from_scene,
WBS_PT_select_scene_workspaces, SW_MT_missing_workspaces,
WBSPreferences, SW_MT_copy_from,
SW_PT_select_scene_workspaces,
SWPreferences,
TOPBAR_HT_upper_bar TOPBAR_HT_upper_bar
] ]
@ -58,14 +55,12 @@ def register():
og_header = bpy.types.TOPBAR_HT_upper_bar; og_header = bpy.types.TOPBAR_HT_upper_bar;
for c in classes: for c in classes:
bpy.utils.register_class(c) bpy.utils.register_class(c)
bpy.app.handlers.load_post.append(load_handler) bpy.types.TOPBAR_MT_workspace_menu.prepend(draw_workspace_menu)
bpy.types.TOPBAR_MT_workspace_menu.append(draw_workspace_menu)
def unregister(): def unregister():
for c in reversed(classes): for c in reversed(classes):
bpy.utils.unregister_class(c) bpy.utils.unregister_class(c)
bpy.utils.register_class(og_header) bpy.utils.register_class(og_header)
bpy.app.handlers.load_post.remove(load_handler)
bpy.types.TOPBAR_MT_workspace_menu.remove(draw_workspace_menu) bpy.types.TOPBAR_MT_workspace_menu.remove(draw_workspace_menu)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -1,20 +1,12 @@
import bpy import bpy
from .. import __package__ as base_package 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(): def has_data():
return 'scene_workspaces' in bpy.data.scenes[bpy.context.scene.name] return 'scene_workspaces' in bpy.data.scenes[bpy.context.scene.name]
def get_scene_workspaces(): def get_scene_workspaces(scene = None):
if has_data(): 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: else:
return [] return []
@ -23,46 +15,48 @@ def get_use_global():
def set_scene_workspaces(workspaces): def set_scene_workspaces(workspaces):
bpy.data.scenes[bpy.context.scene.name]['scene_workspaces'] = workspaces bpy.data.scenes[bpy.context.scene.name]['scene_workspaces'] = workspaces
redraw_props()
def init_data(): def select_all():
workspaces = [] workspaces = []
for w in bpy.data.workspaces: for w in bpy.data.workspaces:
workspaces.append(w.name) workspaces.append(w.name)
set_scene_workspaces(workspaces) 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): 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() layout.separator()
rename = layout.operator( 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.current_name = name if name else bpy.context.window.workspace.name
rename.new_name = rename.current_name rename.new_name = rename.current_name
layout.separator() 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') icon='TRIA_LEFT_BAR' if text else 'TRIA_UP_BAR')
top.index = i top.index = i
top.top = True 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') icon='TRIA_LEFT' if text else 'TRIA_UP')
up.index = i up.index = i
up.up = 1 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') icon='TRIA_RIGHT' if text else 'TRIA_DOWN')
down.index = i down.index = i
down.up = -1 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') icon='TRIA_RIGHT_BAR' if text else 'TRIA_DOWN_BAR')
bottom.index = i bottom.index = i
bottom.top = False 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): def draw_workspace_menu(self, context):
layout = self.layout layout = self.layout
use_global = get_use_global() use_global = get_use_global()
if not use_global and has_data(): if not use_global and has_data():
layout.separator()
draw_workspace_ops(layout, get_scene_workspaces().index( draw_workspace_ops(layout, get_scene_workspaces().index(
context.window.workspace.name), True) context.window.workspace.name), True)
layout.separator()

View File

@ -2,17 +2,24 @@ import bpy
from bpy.types import Menu from bpy.types import Menu
from .funcs import get_scene_workspaces from .funcs import get_scene_workspaces
class WBS_MT_missing_workspaces(Menu): class SW_MT_copy_from(Menu):
bl_label = "Link other workspaces" bl_label = "Copy and link workspaces from other scenes"
def draw(self, context): def draw(self, context):
layout = self.layout 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: for w in bpy.data.workspaces:
if w.name not in get_scene_workspaces(): if w.name not in get_scene_workspaces():
present = True layout.operator("sw.link_workspace", text=w.name, icon="ADD").workspace = w.name
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")

View File

@ -1,25 +1,23 @@
from .. import __package__ as base_package from .. import __package__ as base_package
import time
import bpy import bpy
from bpy.types import Operator from bpy.types import Operator
from bpy.props import StringProperty, BoolProperty, IntProperty from bpy.props import StringProperty, BoolProperty, IntProperty
from .topbar import TOPBAR_HT_upper_bar from .funcs import select_all, get_scene_workspaces, set_scene_workspaces, get_use_global, has_data
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
class WBS_OT_switch(Operator): class SW_OT_switch(Operator):
bl_idname = "wbs.switch" bl_idname = "sw.switch"
bl_label = "Scene Workspaces Switch" bl_label = "Scene Workspaces On/Off"
bl_description = "Switch between Scene Workspaces and global workspaces" bl_description = "Switch workspaces topbar between Scene Workspaces and default"
def execute(self, context): def execute(self, context):
context.preferences.addons[base_package].preferences.use_global = not get_use_global() context.preferences.addons[base_package].preferences.use_global = not get_use_global()
context.preferences.is_dirty = True context.preferences.is_dirty = True
return {'FINISHED'} return {'FINISHED'}
class WBS_OT_workspace(Operator): class SW_OT_workspace(Operator):
bl_idname = "wbs.workspace" bl_idname = "sw.workspace"
bl_label = "Select Workspace" bl_label = "Workspace"
bl_description = "Make this workspace active (or remove it from the scene, if missing)" bl_description = "Make this workspace active (or remove it from the scene, if missing)"
workspace: StringProperty(name="workspace") workspace: StringProperty(name="workspace")
@ -33,18 +31,19 @@ class WBS_OT_workspace(Operator):
set_scene_workspaces(l) set_scene_workspaces(l)
return {'FINISHED'} return {'FINISHED'}
class WBS_OT_sel_all(Operator): class SW_OT_link_all(Operator):
bl_idname = "wbs.sel_all" bl_idname = "sw.link_all"
bl_label = "Link all workspaces to this Scene" bl_label = "Link all workspaces to this Scene"
bl_description = "Link all workspaces avaliable to this scene"
def execute(self, context): def execute(self, context):
init_data() select_all()
return {'FINISHED'} return {'FINISHED'}
class WBS_OT_move(Operator): class SW_OT_move(Operator):
bl_idname = "wbs.move" bl_idname = "sw.move"
bl_label = "Reorder Workspace" 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 up: IntProperty(name="up", default=0) # 0 means using `top` for reorder back/front
index: IntProperty(name="index", default=-1) index: IntProperty(name="index", default=-1)
@ -63,10 +62,10 @@ class WBS_OT_move(Operator):
self.top = False self.top = False
return {'FINISHED'} return {'FINISHED'}
class WBS_OT_rename(Operator): class SW_OT_rename(Operator):
bl_idname = "wbs.rename" bl_idname = "sw.rename"
bl_label = "Rename workspace" 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'}) current_name: StringProperty(name="Current Name", options={'HIDDEN'})
new_name: StringProperty( new_name: StringProperty(
@ -86,10 +85,10 @@ class WBS_OT_rename(Operator):
def invoke(self, context, event): def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self, title=f"Rename Workspace '{self.current_name}'") return context.window_manager.invoke_props_dialog(self, title=f"Rename Workspace '{self.current_name}'")
class WBS_OT_remove(Operator): class SW_OT_remove(Operator):
bl_idname = "wbs.remove" bl_idname = "sw.remove"
bl_label = "Unlink Workspace" 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) index: IntProperty(name="index", default=-1)
@ -99,10 +98,22 @@ class WBS_OT_remove(Operator):
set_scene_workspaces(l) set_scene_workspaces(l)
return {'FINISHED'} return {'FINISHED'}
class WBS_OT_sel_workspace(Operator): class SW_OT_copy_from_scene(Operator):
bl_idname = "wbs.sel_workspace" 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_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") workspace: StringProperty(name="workspace")
@ -111,9 +122,6 @@ class WBS_OT_sel_workspace(Operator):
set_scene_workspaces([]) set_scene_workspaces([])
if self.workspace in get_scene_workspaces(): if self.workspace in get_scene_workspaces():
set_scene_workspaces([x for x in get_scene_workspaces() if x != self.workspace]) set_scene_workspaces([x for x in get_scene_workspaces() if x != self.workspace])
self.report({'INFO'}, f"{self.workspace} unchecked!")
else: else:
set_scene_workspaces([*get_scene_workspaces(), self.workspace]) set_scene_workspaces([*get_scene_workspaces(), self.workspace])
self.report({'INFO'}, f"{self.workspace} checked!")
return {'FINISHED'} return {'FINISHED'}

View File

@ -1,8 +1,9 @@
import bpy
from bpy.types import Panel from bpy.types import Panel
from .funcs import get_scene_workspaces, draw_workspace_ops from .funcs import get_scene_workspaces, draw_workspace_ops
class WBS_PT_select_scene_workspaces(Panel): class SW_PT_select_scene_workspaces(Panel):
bl_idname = "wbs.select_scene_workspaces" bl_idname = "sw.select_scene_workspaces"
bl_label = "Scene Workspaces" bl_label = "Scene Workspaces"
bl_space_type = 'PROPERTIES' bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW' bl_region_type = 'WINDOW'
@ -32,11 +33,16 @@ class WBS_PT_select_scene_workspaces(Panel):
layout.label(text="Link other workspaces") layout.label(text="Link other workspaces")
l = [x for x in bpy.data.workspaces if x.name not in sw] l = [x for x in bpy.data.workspaces if x.name not in sw]
box2 = layout.box().split() box2 = layout.box()
box2.label(text="All workspaces are currently linked.") row = box2.row()
row_count = 0
for w in l: for w in l:
row = box2.column() if row_count > 0 and row_count % 4 == 0:
row.operator("wbs.sel_workspace", text=w.name).workspace = w.name row = box2.row()
row.operator("sw.link_workspace", text=w.name).workspace = w.name
row_count += 1
if l: if l:
layout.separator() layout.separator()
layout.operator("wbs.sel_all") layout.operator("sw.link_all")
else:
box2.label(text="All workspaces are currently linked.")

View File

@ -3,10 +3,10 @@ from bpy.types import AddonPreferences
from bpy.props import BoolProperty from bpy.props import BoolProperty
from .. import __package__ as base_package from .. import __package__ as base_package
class WBSPreferences(AddonPreferences): class SWPreferences(AddonPreferences):
bl_idname = base_package 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): def draw(self, context):
layout = self.layout layout = self.layout

View File

@ -31,46 +31,70 @@ class TOPBAR_HT_upper_bar(Header):
use_global = get_use_global() use_global = get_use_global()
if not screen.show_fullscreen: 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: if use_global:
# Original # Original
layout.template_ID_tabs(window, "workspace", new="workspace.add", menu="TOPBAR_MT_workspace_menu") layout.template_ID_tabs(window, "workspace", new="workspace.add", menu="TOPBAR_MT_workspace_menu")
else: else:
r = layout.row(align=True) r = main_row.row(align=True)
r.scale_x = 0.89 r.scale_x = 0.87
r.scale_y = 1.24 r.scale_y = sy
ws = get_scene_workspaces() 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 active_not_here = True
for w in ws: for w in ws:
i = ws.index(w)
active = bpy.context.window.workspace.name == 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: if active:
col = r.column() col = ab.column()
col.scale_x = 1.5 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") col.menu("TOPBAR_MT_workspace_menu", text="", icon="OPTIONS")
active_not_here = False 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() 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()
if [x for x in bpy.data.workspaces if x.name not in get_scene_workspaces()]: if [x for x in bpy.data.workspaces if x.name not in get_scene_workspaces()]:
c = r.column() c = r.column()
c.scale_x = 1.3 c.scale_x = sx
c.menu("WBS_MT_missing_workspaces", text="", icon="WORKSPACE") c.menu("SW_MT_missing_workspaces", text="", icon="THREE_DOTS")
r.separator()
c = r.column() c = r.column()
c.scale_x = 1.3 c.scale_x = sx
c.operator("workspace.add", text="", icon="ADD") c.operator("workspace.add", text="", icon="ADD")
col = layout.column() if len(bpy.data.scenes) > 1:
col.scale_y = 1.24 c = r.column()
col.operator("wbs.switch", text="", emboss=False, icon="RADIOBUT_OFF" if use_global else "RADIOBUT_ON") 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 # End Edited
else: else:
layout.operator("screen.back_to_previous", icon='SCREEN_BACK', text="Back to Previous") layout.operator("screen.back_to_previous", icon='SCREEN_BACK', text="Back to Previous")