Add Workspace from Template feature, +refactor

This commit is contained in:
doc-code 2024-09-03 23:21:50 +02:00
parent 079487299b
commit 28bc4dac8f
3 changed files with 95 additions and 28 deletions

View File

@ -14,22 +14,25 @@
# 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 .classes.draw import draw_file_new_templates, draw_file_default_operators from .classes.draw import draw_file_new_templates, draw_file_default_operators, draw_ws_menu_add
from .classes.splash import WM_MT_splash, CT_MT_splash_mode, CT_OT_splash_custom, CT_OT_splash_default from .classes.splash import WM_MT_splash, CT_MT_splash_mode, CT_OT_splash_custom, CT_OT_splash_default
from .classes.ots import CustomTemplatesPreferences, TemplateItem, CT_OT_export_templates, CT_OT_import_templates, CT_MT_templates_menu, CT_OT_select_template_popup, CT_OT_add_template_popup, CT_OT_add, CT_OT_remove, CT_OT_move_down, CT_OT_move_up, CT_OT_open_preferences, CT_OT_add_templates_from_folder, CT_OT_clear from .classes.ots import CustomTemplatesPreferences, TemplateItem, CT_OT_export_templates, CT_OT_import_templates, CT_MT_templates_menu, CT_OT_select_template_popup, CT_OT_add_template_popup, CT_OT_add, CT_OT_remove, CT_OT_move_down, CT_OT_move_up, CT_OT_open_preferences, CT_OT_add_templates_from_folder, CT_OT_clear, CT_MT_workspace_add, CT_OT_template_workspaces, CT_OT_add_workspace
bl_info = { bl_info = {
"id": "custom_templates", "id": "custom_templates",
"name": "Custom Templates", "name": "Custom Templates",
"tagline": "Add your own .blend files as template options for new projects", "tagline": "Add your own .blend files as template options for new projects",
"blender": (4, 2, 0), "blender": (4, 2, 0),
"location": "File > New & File > Defaults", "location": "File > New, File > Defaults, Splash Screen",
"category": "System", "category": "System",
"support": "COMMUNITY", "support": "COMMUNITY",
"blender_manifest": "blender_manifest.toml" "blender_manifest": "blender_manifest.toml"
} }
classes = [WM_MT_splash, classes = [WM_MT_splash,
CT_OT_add_workspace,
CT_OT_template_workspaces,
CT_MT_workspace_add,
TemplateItem, TemplateItem,
CT_OT_export_templates, CT_OT_export_templates,
CT_OT_import_templates, CT_OT_import_templates,
@ -57,6 +60,7 @@ def register():
bpy.utils.register_class(c) bpy.utils.register_class(c)
bpy.types.TOPBAR_MT_file_new.append(draw_file_new_templates) bpy.types.TOPBAR_MT_file_new.append(draw_file_new_templates)
bpy.types.TOPBAR_MT_file_defaults.append(draw_file_default_operators) bpy.types.TOPBAR_MT_file_defaults.append(draw_file_default_operators)
bpy.types.TOPBAR_MT_workspace_menu.append(draw_ws_menu_add)
def unregister(): def unregister():
for c in reversed(classes): for c in reversed(classes):
@ -64,6 +68,7 @@ def unregister():
bpy.utils.register_class(og_splash) bpy.utils.register_class(og_splash)
bpy.types.TOPBAR_MT_file_new.remove(draw_file_new_templates) bpy.types.TOPBAR_MT_file_new.remove(draw_file_new_templates)
bpy.types.TOPBAR_MT_file_defaults.remove(draw_file_default_operators) bpy.types.TOPBAR_MT_file_defaults.remove(draw_file_default_operators)
bpy.types.TOPBAR_MT_workspace_menu.remove(draw_ws_menu_add)
if __name__ == "__main__": if __name__ == "__main__":
register() registers()

View File

@ -4,7 +4,6 @@ import bpy
def draw_file_new_templates(self, context): def draw_file_new_templates(self, context):
layout = self.layout layout = self.layout
prefs = context.preferences.addons[base_package].preferences prefs = context.preferences.addons[base_package].preferences
if len(prefs.projects) > 0: if len(prefs.projects) > 0:
layout.separator() layout.separator()
@ -37,3 +36,11 @@ def draw_file_default_operators(self, context):
if bpy.data.filepath != "": if bpy.data.filepath != "":
layout.operator("ct.add_template_popup", layout.operator("ct.add_template_popup",
text="Use current file as template") text="Use current file as template")
def draw_ws_menu_add(self, context):
layout = self.layout
prefs = context.preferences.addons[base_package].preferences
if prefs.projects:
layout.separator()
layout.menu("CT_MT_workspace_add", text="Add from Custom Templates", icon="WORKSPACE")

View File

@ -2,7 +2,7 @@ from .. import __package__ as base_package
import os import os
import bpy import bpy
import json import json
from bpy.types import Operator, PropertyGroup, AddonPreferences from bpy.types import Operator, Menu, PropertyGroup, AddonPreferences
from bpy.props import StringProperty, CollectionProperty, IntProperty, BoolProperty from bpy.props import StringProperty, CollectionProperty, IntProperty, BoolProperty
def already_present(self, prefs, path, report=True): def already_present(self, prefs, path, report=True):
@ -27,7 +27,7 @@ def on_path_update(self, context):
if self.path and self.path.startswith('//'): if self.path and self.path.startswith('//'):
self.path = bpy.path.abspath(self.path) self.path = bpy.path.abspath(self.path)
context.preferences.is_dirty = True context.preferences.is_dirty = True
class TemplateItem(PropertyGroup): class TemplateItem(PropertyGroup):
name: StringProperty( name: StringProperty(
name="Name", description="Display name for this template") name="Name", description="Display name for this template")
@ -38,7 +38,7 @@ class CustomTemplatesPreferences(AddonPreferences):
bl_idname = base_package bl_idname = base_package
override_splash: BoolProperty( override_splash: BoolProperty(
default=True, name="Override Splash Screen Templates", description="Override Splash Screen's 'New File' list with your Custom Templates") default=True, name="Use Custom Templates", description="Override Splash Screen's 'New File' list with your Custom Templates")
projects: CollectionProperty(type=TemplateItem) projects: CollectionProperty(type=TemplateItem)
active_template_index: IntProperty( active_template_index: IntProperty(
description="Index of the selected template") description="Index of the selected template")
@ -55,7 +55,8 @@ class CustomTemplatesPreferences(AddonPreferences):
col = row.column(align=True) col = row.column(align=True)
col.menu("CT_MT_templates_menu", icon='DOWNARROW_HLT', text="") col.menu("CT_MT_templates_menu", icon='DOWNARROW_HLT', text="")
col.separator() col.separator()
col.operator("ct.add_templates_from_folder", text="", icon="FILE_FOLDER") col.operator("ct.add_templates_from_folder",
text="", icon="FILE_FOLDER")
col.operator("ct.add", icon='ADD', text="") col.operator("ct.add", icon='ADD', text="")
col.operator("ct.remove", icon='REMOVE', text="") col.operator("ct.remove", icon='REMOVE', text="")
col.separator() col.separator()
@ -66,35 +67,39 @@ class CustomTemplatesPreferences(AddonPreferences):
project = self.projects[self.active_template_index] project = self.projects[self.active_template_index]
layout.prop(project, "path") layout.prop(project, "path")
layout.prop(project, "name") layout.prop(project, "name")
layout.prop(self, "override_splash") b = layout.box()
box = layout.box() b.label(text="Splash Screen", icon="SETTINGS")
if self.override_splash and len(self.projects) == 0: b.prop(self, "override_splash")
box.label(text="There are currently no templates.") box = b.box()
elif self.override_splash: if self.override_splash and self.projects:
box.label(text="Custom templates will be shown in the Splash Screen.") box.label(text="Custom templates will be shown in the Splash Screen.")
if not self.override_splash or len(self.projects) == 0: if not self.override_splash or len(self.projects) == 0:
box.label( box.label(
text="The default Blender list will be shown in the Splash Screen.") text="The default Blender list will be shown in the Splash Screen.", icon=("ERROR" if not self.projects else "NONE"))
if len(self.projects) > 5: if len(self.projects) > 5:
box.label( box.label(
text="Note: Only the first 5 templates in the list will be shown in the splash screen.") text="Note: Only the first 5 templates in the list will be shown in the splash screen.")
class CT_MT_templates_menu(bpy.types.Menu): class CT_MT_templates_menu(Menu):
bl_label = "Manage your custom templates" bl_label = "Manage your custom templates"
bl_description = "Import, export, add from folder (with controllable recursion depth), clear current templates" bl_description = "Import, export, add from folder (with controllable recursion depth), clear current templates"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
layout.operator("ct.add_templates_from_folder", text="Add from folder", icon="ADD") layout.operator("ct.add_templates_from_folder",
layout.operator("ct.clear", text="Clear current templates", icon="TRASH") text="Add from folder", icon="ADD")
layout.operator(
"ct.clear", text="Clear current templates", icon="TRASH")
layout.separator() layout.separator()
layout.operator("ct.import_templates", text="Import templates", icon="IMPORT") layout.operator("ct.import_templates",
layout.operator("ct.export_templates", text="Export templates", icon="EXPORT") text="Import templates", icon="IMPORT")
layout.operator("ct.export_templates",
text="Export templates", icon="EXPORT")
class CT_OT_export_templates(bpy.types.Operator): class CT_OT_export_templates(Operator):
bl_idname = "ct.export_templates" bl_idname = "ct.export_templates"
bl_label = "Export custom templates" bl_label = "Export custom templates"
bl_description = "Export the current list of templates to JSON file" bl_description = "Export the current list of templates to JSON file"
@ -120,7 +125,7 @@ class CT_OT_export_templates(bpy.types.Operator):
def poll(cls, context): def poll(cls, context):
return len(context.preferences.addons[base_package].preferences.projects) > 0 return len(context.preferences.addons[base_package].preferences.projects) > 0
class CT_OT_import_templates(bpy.types.Operator): class CT_OT_import_templates(Operator):
bl_idname = "ct.import_templates" bl_idname = "ct.import_templates"
bl_label = "Import custom templates" bl_label = "Import custom templates"
bl_description = "Import a list of templates from JSON file (note that this will override the current templates list)" bl_description = "Import a list of templates from JSON file (note that this will override the current templates list)"
@ -235,7 +240,7 @@ class CT_OT_clear(Operator):
prefs.active_template_index = 0 prefs.active_template_index = 0
context.preferences.is_dirty = True context.preferences.is_dirty = True
return {'FINISHED'} return {'FINISHED'}
def invoke(self, context, event): def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self) return context.window_manager.invoke_props_dialog(self)
@ -273,7 +278,8 @@ class CT_OT_select_template_popup(Operator):
bl_label = "Select a new custom template" bl_label = "Select a new custom template"
bl_description = "Create a new template by selecting an existing .blend file" bl_description = "Create a new template by selecting an existing .blend file"
path: StringProperty(name="Template Path", subtype="FILE_PATH", update=on_path_update) path: StringProperty(name="Template Path",
subtype="FILE_PATH", update=on_path_update)
name: StringProperty(name="Template Name") name: StringProperty(name="Template Name")
def execute(self, context): def execute(self, context):
@ -285,7 +291,7 @@ class CT_OT_select_template_popup(Operator):
context.preferences.is_dirty = True context.preferences.is_dirty = True
self.name = '' self.name = ''
self.path = '' self.path = ''
return {'FINISHED'} return {'FINISHED'}
def invoke(self, context, event): def invoke(self, context, event):
@ -348,3 +354,52 @@ class CT_OT_open_preferences(Operator):
context.preferences.active_section = 'ADDONS' context.preferences.active_section = 'ADDONS'
context.window_manager.addon_search = "Custom Templates" context.window_manager.addon_search = "Custom Templates"
return {'FINISHED'} return {'FINISHED'}
class CT_OT_add_workspace(Operator):
bl_idname = "ct.add_workspace"
bl_label = "Add this workspace from your template"
bl_description = "Add to the current project, the selected workspace from your Custom Template"
workspace: StringProperty(name="workspace")
path: StringProperty(name="path")
def execute(self, context):
bpy.ops.workspace.append_activate(idname=self.workspace, filepath=self.path)
return {'FINISHED'}
class CT_OT_template_workspaces(Operator):
bl_idname = "ct.template_workspaces"
bl_label = "Add workspace from this template"
bl_description = "Click to select one of the workspaces from this Custom Template"
index: IntProperty(name='index', default=0)
def draw_ws(self, s, context):
layout = s.layout
template = context.preferences.addons[base_package].preferences.projects[self.index]
with bpy.data.libraries.load(template.path) as (data, _):
for w in data.workspaces:
op = layout.operator("ct.add_workspace", text=w)
op.workspace = w
op.path = template.path
def execute(self, context):
return {'RUNNING_MODAL'}
def invoke(self, context, event):
wm = context.window_manager
template = context.preferences.addons[base_package].preferences.projects[self.index]
wm.popup_menu(self.draw_ws, title=f"Workspaces from '{template.name}'", icon="ADD")
return {'RUNNING_MODAL'}
class CT_MT_workspace_add(Menu):
bl_label = "Add workspace from Custom Templates"
def draw(self, context):
layout = self.layout
templates = context.preferences.addons[base_package].preferences.projects
layout.label(text="Add workspace from Custom Templates", icon="ADD")
layout.separator()
for i in range(len(templates)):
t = templates[i]
layout.operator("CT_OT_template_workspaces", text=t.name).index = i