Compare commits

...

14 Commits
v1.4.0 ... main

22 changed files with 540 additions and 504 deletions

View File

@ -1,3 +1,15 @@
## v1.5.0
### New Features
- New option: **`File > New > Start from...`** (opt-in from preferences)
*Select any .blend file to use as template. Optionally, you save the template in your list, on the fly.*
- Splash Screen now supports 5+ templates with a submenu
---
- Minor UI update
*Now use a single checkbox for switching templates in Splash Screen menu.*
## v1.4.0
### New Feature

View File

@ -1,12 +1,14 @@
# Custom Templates - Add-On
# Custom Templates
## The Freedom to *Template*
**Custom Templates** allows you to use your own .blend files as template options for new projects.
---
- [Getting Started](#getting-started)
- [Why *Custom Templates*?](#why-custom-templates)
- [Support](#support)
- [Getting Started](https://projects.blender.org/Francesco-Bellini/custom_templates_addon/wiki/Getting-Started)
- [Why this add-on?](https://projects.blender.org/Francesco-Bellini/custom_templates_addon/wiki/Why+this+add-on%3F.-)
- [Support](https://projects.blender.org/Francesco-Bellini/custom_templates_addon/wiki/Support)
- [Changelog](./CHANGELOG.md)
- [Copyright](#copyright)
@ -16,83 +18,6 @@
Your templates will be avaliable in the Splash Screen and the `File > New` menu.
## Getting Started
- **Prepare your templates** and save them **in `.blend` files**
*or find online `.blend` templates that fits your needs*
- **Define your templates** in the add-on's preferences
*It's simple. Select a `.blend` file and name your template.*
You can do this:
1) Directly from add-on preferences, in the templates list
2) By using `File > Defaults > Add from folder`
*Select a folder (and optionally a recursion depth, default 1) and find `.blend` files to use as template. By default, the clean file name will be used as name for the new templates.*
3) By using `File > Defaults > Select new template`
*Popup to specify name and path for the new template.*
4) By using `File > Defaults > Use current file as template` *(only avaliable when a saved .blend file is currently loaded)*
*Popup to specify the name for the new template. The path will be the one of the current .blend file.*
5) By importing a previously exported templates list from JSON file
*(Either using `File > Defaults > Import Templates` as well as from the menu located in the top right of the templates list in the preferences)*
*Note: the import process override the current list. Be sure to export the current list, if necessary.*
> *You can add, remove, reorder and update your templates any time...*
- *(Optionally)* **Export** your templates list to **JSON file**
*From `File > Defaults > Export templates`, or from the add-on preferences menu.*
*Useful after disabling and re-enabling the add-on, re-installing blender, or simply to prepare different sets of templates to import when needed.*
At this point your templates will be avaliable in the `File > New` menu and in the Splash Screen.
> You can update the contents of your `.blend` templates at any time. The file is read each time you open the template.
#### Splash Screen
From the Splash Screen (or from preferences), you can easily switch between your Custom Templates and Blender's default templates.
> *This only applies to the splash screen.
> `File > New` menu will always show all your templates.*
Notes:
- With more than 5 templates, only the first 5 will be shown in the splash. If this is your case, be sure to reorder on top the 5 you want to see in the splash.
- If you have no templates currently defined, Blender's default list will always be used (to avoid empty list in splash)
### Why *Custom Templates*?
Blender allows you to save you own .blend file to use on startup.
This is great, but wouldn't it be nice to be able to create your own templates?
With **Custom Templates** you can setup as many .blend file as you like, as templates.
---
Create your own templates or find the ones that best suit your needs online.
*Customize workspaces, preset your usual materials, define additional windows or anything else you would like to have from the beginning.*
*The only limit is your immagination!*
### Support
The versions avaliable at extensions.blender.org and directly from Blender's preferences, supports Blender 4.2 and later versions.
---
#### Legacy
From `v1.3.1`, a [legacy version](https://projects.blender.org/Francesco-Bellini/custom_templates_addon/releases/tag/v1.3.1) has been made and tested on the following LTS previous versions:
- Blender `3.6 LTS`
- Blender `3.3 LTS`
- Blender `2.93 LTS`
- Blender `2.83 LTS`
- *And may of may not work on other not-LTS previous versions*
If you are *not* using a version of Blender that supports the Extensions Platform, you can:
[Download the legacy version](https://projects.blender.org/Francesco-Bellini/custom_templates_addon/releases/tag/v1.3.1).
> **Note**
> Only setup templates compatible with your current version of blender, otherwise you might get a warning or a crash (especially on Blender 2.x versions) while opening your templates.
### Changelog
Visit the [CHANGELOG](./CHANGELOG.md) file to see the changes over new version.

View File

@ -1,50 +1,52 @@
# The Freedom to *Template*
Blender allows you to save you own .blend file to use on startup.
This is great, but wouldn't it be nice to be able to create **your own templates**?
**Custom Templates** allows you to use your own .blend files as template options for new projects.
> Your templates will be added in the `File > New` menu and in the Splash Screen *(according to preferences)*.
*You can follow this [steps to get started](https://projects.blender.org/Francesco-Bellini/custom_templates_addon/wiki/Getting-Started).*
---
> Instead of being limited to the usual options Blender offers to start a new project *(General, 2D Animation, Sculpting, VFX, Video Editing)*, you will be able to configure your own list of .blend files.
Your templates will be added in the `File > New` menu and in the Splash Screen *(according to preferences)*.
*Instead of being limited to the usual options Blender offers to start a new project (General, 2D Animation, Sculpting, VFX, Video Editing), you will be able to configure your own list of .blend files.*
---
Create your own templates or find the ones that best suit your needs online.
---
*Customize workspaces, preset your usual materials, define additional windows or anything else you would like to have from the beginning.*
*The only limit is your immagination!*
#### Import/Export
When your templates are ready, you can export the list to JSON file.
This way, you will be able to easily restore your templates, if necessary.
*For example, after disabling and re-enabling the add-on...*
*The only limit is your immagination!*
---
#### Features
- Show your templates in `File > New` menu and in the Splash Screen
- Add, remove, update, reorder your templates from add-on preferences
- Option `File > New > Start from...`:
Pick any .blend file as template (optional)
*While selecting, you can optionally add the template.*
*Activate this option from add-on preferences.*
- Import/Export templates from/to JSON file
- Add, remove, update, reorder your templates from add-on preferences
- Add templates from folder (lookup `.blend` files in folder)
- Select new template
- Use current file as template
- Clear current templates
- Add workspace from templates *(right-click on a workspace and use `Add from Custom Templates` menu)*
##### Legacy Support
From version `1.3.1` it's also avaliable a [legacy version](https://projects.blender.org/Francesco-Bellini/custom_templates_addon/releases/tag/v1.3.1), for previous Blender versions.
For previous Blender versions, you can install the [legacy version](https://projects.blender.org/Francesco-Bellini/custom_templates_addon/wiki/Support#legacy-version).
*Tested on all LTS versions from 2.83 to 3.6 (remember to use compatible templates).*
---
*For more details checkout the project's [README](https://projects.blender.org/Francesco-Bellini/custom_templates_addon/src/branch/main/README.md) ([Getting Started](https://projects.blender.org/Francesco-Bellini/custom_templates_addon/src/branch/main/README.md#getting-started))*
*For more details checkout the project's [Wiki](https://projects.blender.org/Francesco-Bellini/custom_templates_addon/wiki).*
---

View File

@ -14,9 +14,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import bpy
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.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
from bpy.types import TOPBAR_MT_file_new, TOPBAR_MT_file_defaults, TOPBAR_MT_workspace_menu
from .src.funcs import draw_file_new_templates, draw_file_default_operators, draw_ws_menu_add
from .src.splash import WM_MT_splash
from .src.menus import CT_MT_splash_mode, CT_MT_templates_menu, CT_MT_workspace_add, CT_MT_more_templates
from .src.ops import CT_OT_export_templates, CT_OT_start_from, CT_OT_import_templates, 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_OT_template_workspaces, CT_OT_add_workspace
from .src.prefs import CustomTemplatesPreferences, TemplateItem
bl_info = {
"id": "custom_templates",
@ -30,17 +33,16 @@ bl_info = {
}
classes = [WM_MT_splash,
CT_OT_add_workspace,
CT_OT_template_workspaces,
CT_MT_workspace_add,
TemplateItem,
CT_OT_export_templates,
CT_OT_import_templates,
CT_OT_splash_custom,
CT_OT_splash_default,
CT_OT_open_preferences,
CT_MT_splash_mode,
CT_MT_templates_menu,
CT_MT_more_templates,
CT_OT_add_workspace,
CT_OT_start_from,
CT_OT_template_workspaces,
CT_OT_export_templates,
CT_OT_import_templates,
CT_OT_open_preferences,
CT_OT_move_up,
CT_OT_move_down,
CT_OT_add,
@ -49,26 +51,27 @@ classes = [WM_MT_splash,
CT_OT_add_template_popup,
CT_OT_select_template_popup,
CT_OT_add_templates_from_folder,
TemplateItem,
CustomTemplatesPreferences]
og_splash = None;
og_splash = None
def register():
global og_splash
og_splash = bpy.types.WM_MT_splash;
og_splash = bpy.types.WM_MT_splash
for c in classes:
bpy.utils.register_class(c)
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_workspace_menu.append(draw_ws_menu_add)
TOPBAR_MT_file_new.append(draw_file_new_templates)
TOPBAR_MT_file_defaults.append(draw_file_default_operators)
TOPBAR_MT_workspace_menu.append(draw_ws_menu_add)
def unregister():
for c in reversed(classes):
bpy.utils.unregister_class(c)
bpy.utils.register_class(og_splash)
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_workspace_menu.remove(draw_ws_menu_add)
TOPBAR_MT_file_new.remove(draw_file_new_templates)
TOPBAR_MT_file_defaults.remove(draw_file_default_operators)
TOPBAR_MT_workspace_menu.remove(draw_ws_menu_add)
if __name__ == "__main__":
registers()

View File

@ -1,6 +1,6 @@
schema_version = "1.0.0"
id = "custom_templates"
version = "1.4.0"
version = "1.5.0"
name = "Custom Templates"
tagline = "Use your own .blend files as template options for new projects"
maintainer = "Francesco Bellini <doc.open.dev@gmail.com>"
@ -11,4 +11,4 @@ blender_version_min = "4.2.0"
license = ["SPDX:GPL-3.0-or-later"]
copyright = ["2024 Francesco Bellini"]
[permissions]
files = "JSON Import/Export of templates list / Add from folder feature"
files = "JSON Import/Export of templates list / Add templates from folder"

View File

@ -1,21 +1,54 @@
from .. import __package__ as base_package
from .ots import name_from_path
import os
import bpy
from .. import __package__ as base_package
def already_present(self, prefs, path, report=True):
for p in prefs.projects:
if p.path == path:
if report:
self.report(
{'WARNING'}, f'Current file is already in the templates list as "{p.name}".')
return True
return False
def name_from_path(path):
if path:
return os.path.splitext(os.path.basename(path))[0]
else:
return ""
def on_path_update(self, context):
if not self.name and self.path:
self.name = name_from_path(self.path)
context.preferences.is_dirty = True
if self.path and self.path.startswith('//'):
self.path = bpy.path.abspath(self.path)
context.preferences.is_dirty = True
def pref():
return bpy.context.preferences.addons[base_package].preferences
def has_templates():
return len(pref().projects) > 0
def draw_file_new_templates(self, context):
layout = self.layout
prefs = context.preferences.addons[base_package].preferences
if len(prefs.projects) > 0:
if has_templates():
layout.separator()
draw_templates(layout, context)
def draw_templates(layout, context, splash_mode=False):
prefs = context.preferences.addons[base_package].preferences
for i in range(min(5, len(prefs.projects)) if splash_mode else len(prefs.projects)):
prefs = pref()
for i in range(min(5 if len(prefs.projects) <= 5 else 4, len(prefs.projects)) if splash_mode else len(prefs.projects)):
project = prefs.projects[i]
if project.path:
layout.operator(
"wm.read_homefile", text=(project.name if project.name else name_from_path(project.path)), icon="FILE_NEW" if splash_mode else "NONE").filepath = project.path
if splash_mode and len(prefs.projects) > 5:
layout.menu("CT_MT_more_templates", text="More...", icon="FILE_NEW")
if not splash_mode and prefs.start_from and context.area.type == 'TOPBAR':
layout.separator()
layout.operator("ct.start_from", icon="FILE_FOLDER")
def draw_file_default_operators(self, context):
layout = self.layout
@ -39,8 +72,6 @@ def draw_file_default_operators(self, context):
def draw_ws_menu_add(self, context):
layout = self.layout
prefs = context.preferences.addons[base_package].preferences
if prefs.projects:
if has_templates():
layout.separator()
layout.menu("CT_MT_workspace_add", text="Add from Custom Templates", icon="WORKSPACE")

60
addon/src/menus.py Normal file
View File

@ -0,0 +1,60 @@
from .. import __package__ as base_package
import os
import bpy
import json
from bpy.types import Menu
from .funcs import pref, name_from_path
class CT_MT_templates_menu(Menu):
bl_label = "Manage your custom templates"
bl_description = "Import, export, add from folder (with controllable recursion depth), clear current templates"
def draw(self, context):
layout = self.layout
layout.operator("ct.add_templates_from_folder",
text="Add from folder", icon="ADD")
layout.operator(
"ct.clear", text="Clear current templates", icon="TRASH")
layout.separator()
layout.operator("ct.import_templates",
text="Import templates", icon="IMPORT")
layout.operator("ct.export_templates",
text="Export templates", icon="EXPORT")
class CT_MT_splash_mode(Menu):
bl_idname = "CT_MT_splash_mode"
bl_label = "Custom Templates Switch"
bl_description = "Swtich between Blender's default and your Custom Templates"
def draw(self, context):
layout = self.layout
prefs = pref()
layout.operator("ct.open_preferences", text="Manage templates")
layout.separator()
layout.prop(prefs, "override_splash")
class CT_MT_more_templates(Menu):
bl_idname = "CT_MT_more_templates"
bl_label = "Other Custom Templates"
bl_description = "Show the other templates in the splash screen"
def draw(self, context):
layout = self.layout
prefs = pref()
for t in prefs.projects[4:]:
layout.operator(
"wm.read_homefile", text=(t.name if t.name else name_from_path(t.path)), icon="FILE_NEW").filepath = t.path
class CT_MT_workspace_add(Menu):
bl_label = "Add workspace from Custom Templates"
def draw(self, context):
layout = self.layout
templates = pref().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

View File

@ -1,103 +1,10 @@
from .. import __package__ as base_package
import os
import bpy
import json
from bpy.types import Operator, Menu, PropertyGroup, AddonPreferences
from bpy.props import StringProperty, CollectionProperty, IntProperty, BoolProperty
def already_present(self, prefs, path, report=True):
for p in prefs.projects:
if p.path == path:
if report:
self.report(
{'WARNING'}, f'Current file is already in the templates list as "{p.name}".')
return True
return False
def name_from_path(path):
if path:
return os.path.splitext(os.path.basename(path))[0]
else:
return ""
def on_path_update(self, context):
if not self.name and self.path:
self.name = name_from_path(self.path)
context.preferences.is_dirty = True
if self.path and self.path.startswith('//'):
self.path = bpy.path.abspath(self.path)
context.preferences.is_dirty = True
class TemplateItem(PropertyGroup):
name: StringProperty(
name="Name", description="Display name for this template")
path: StringProperty(
name="Path", description="Path to the .blend file for this template", subtype='FILE_PATH', update=on_path_update)
class CustomTemplatesPreferences(AddonPreferences):
bl_idname = base_package
override_splash: BoolProperty(
default=True, name="Use Custom Templates", description="Override Splash Screen's 'New File' list with your Custom Templates")
projects: CollectionProperty(type=TemplateItem)
active_template_index: IntProperty(
description="Index of the selected template")
def draw(self, context):
layout = self.layout
layout.label(
text=f"Your custom templates ({len(self.projects)})")
row = layout.row()
row.template_list("UI_UL_list", "custom_templates",
self, "projects", self, "active_template_index", rows=6, maxrows=6)
col = row.column(align=True)
col.menu("CT_MT_templates_menu", icon='DOWNARROW_HLT', text="")
col.separator()
col.operator("ct.add_templates_from_folder",
text="", icon="FILE_FOLDER")
col.operator("ct.add", icon='ADD', text="")
col.operator("ct.remove", icon='REMOVE', text="")
col.separator()
col.operator("ct.move_up", icon='TRIA_UP', text="")
col.operator("ct.move_down", icon='TRIA_DOWN', text="")
if self.projects:
project = self.projects[self.active_template_index]
layout.prop(project, "path")
layout.prop(project, "name")
b = layout.box()
b.label(text="Splash Screen", icon="SETTINGS")
b.prop(self, "override_splash")
box = b.box()
if self.override_splash and self.projects:
box.label(text="Custom templates will be shown in the Splash Screen.")
if not self.override_splash or len(self.projects) == 0:
box.label(
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:
box.label(
text="Note: Only the first 5 templates in the list will be shown in the splash screen.")
class CT_MT_templates_menu(Menu):
bl_label = "Manage your custom templates"
bl_description = "Import, export, add from folder (with controllable recursion depth), clear current templates"
def draw(self, context):
layout = self.layout
layout.operator("ct.add_templates_from_folder",
text="Add from folder", icon="ADD")
layout.operator(
"ct.clear", text="Clear current templates", icon="TRASH")
layout.separator()
layout.operator("ct.import_templates",
text="Import templates", icon="IMPORT")
layout.operator("ct.export_templates",
text="Export templates", icon="EXPORT")
from bpy.types import Operator
from bpy.props import StringProperty, IntProperty, BoolProperty
from .funcs import pref, has_templates, name_from_path, already_present, on_path_update
from .. import __package__ as base_package
class CT_OT_export_templates(Operator):
bl_idname = "ct.export_templates"
@ -108,7 +15,7 @@ class CT_OT_export_templates(Operator):
subtype="FILE_PATH", description="Select the path for the exported file", default="templates.json")
def execute(self, context):
prefs = context.preferences.addons[base_package].preferences
prefs = pref()
with open(self.filepath, 'w') as f:
projects = [{"name": project.name, "path": project.path}
for project in prefs.projects]
@ -123,7 +30,7 @@ class CT_OT_export_templates(Operator):
@classmethod
def poll(cls, context):
return len(context.preferences.addons[base_package].preferences.projects) > 0
return has_templates()
class CT_OT_import_templates(Operator):
bl_idname = "ct.import_templates"
@ -134,7 +41,7 @@ class CT_OT_import_templates(Operator):
subtype="FILE_PATH", description="Select the .json file to load")
def execute(self, context):
prefs = context.preferences.addons[base_package].preferences
prefs = pref()
if os.path.exists(self.filepath):
with open(self.filepath, 'r') as f:
@ -164,7 +71,7 @@ class CT_OT_add(Operator):
bl_description = "Add new template"
def execute(self, context):
prefs = context.preferences.addons[base_package].preferences
prefs = pref()
prefs.projects.add()
prefs.active_template_index = len(prefs.projects) - 1
context.preferences.is_dirty = True
@ -176,7 +83,7 @@ class CT_OT_remove(Operator):
bl_description = "Remove selected template"
def execute(self, context):
prefs = context.preferences.addons[base_package].preferences
prefs = pref()
prefs.projects.remove(prefs.active_template_index)
prefs.active_template_index = min(
max(0, prefs.active_template_index - 1), len(prefs.projects) - 1)
@ -185,7 +92,7 @@ class CT_OT_remove(Operator):
@classmethod
def poll(cls, context):
return len(context.preferences.addons[base_package].preferences.projects) > 0
return has_templates()
class CT_OT_move_up(Operator):
bl_idname = "ct.move_up"
@ -193,7 +100,7 @@ class CT_OT_move_up(Operator):
bl_description = "Move the selected template up in the list"
def execute(self, context):
prefs = context.preferences.addons[base_package].preferences
prefs = pref()
index = prefs.active_template_index
if index > 0:
@ -206,7 +113,7 @@ class CT_OT_move_up(Operator):
@classmethod
def poll(cls, context):
return len(context.preferences.addons[base_package].preferences.projects) > 0
return has_templates()
class CT_OT_move_down(Operator):
bl_idname = "ct.move_down"
@ -214,7 +121,7 @@ class CT_OT_move_down(Operator):
bl_description = "Move the selected template down in the list"
def execute(self, context):
prefs = context.preferences.addons[base_package].preferences
prefs = pref()
index = prefs.active_template_index
if index < len(prefs.projects) - 1:
@ -227,7 +134,7 @@ class CT_OT_move_down(Operator):
@classmethod
def poll(cls, context):
return len(context.preferences.addons[base_package].preferences.projects) > 0
return has_templates()
class CT_OT_clear(Operator):
bl_idname = "ct.clear"
@ -235,7 +142,7 @@ class CT_OT_clear(Operator):
bl_description = "Clear the current list of templates (remove all templates)"
def execute(self, context):
prefs = context.preferences.addons[base_package].preferences
prefs = pref()
prefs.projects.clear()
prefs.active_template_index = 0
context.preferences.is_dirty = True
@ -246,7 +153,7 @@ class CT_OT_clear(Operator):
@classmethod
def poll(cls, context):
return len(context.preferences.addons[base_package].preferences.projects) > 0
return has_templates()
class CT_OT_add_template_popup(Operator):
bl_idname = "ct.add_template_popup"
@ -256,7 +163,7 @@ class CT_OT_add_template_popup(Operator):
name: StringProperty(name="Project Name")
def execute(self, context):
prefs = context.preferences.addons[base_package].preferences
prefs = pref()
if not already_present(self, prefs, bpy.data.filepath):
if bpy.data.filepath:
new_project = prefs.projects.add()
@ -283,7 +190,7 @@ class CT_OT_select_template_popup(Operator):
name: StringProperty(name="Template Name")
def execute(self, context):
prefs = context.preferences.addons[base_package].preferences
prefs = pref()
if not already_present(self, prefs, self.path):
template = prefs.projects.add()
template.name = self.name
@ -297,7 +204,7 @@ class CT_OT_select_template_popup(Operator):
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
class CT_OT_add_templates_from_folder(bpy.types.Operator):
class CT_OT_add_templates_from_folder(Operator):
bl_idname = "ct.add_templates_from_folder"
bl_label = "Add Templates from Folder"
bl_description = "Add templates from a specified folder containing .blend files"
@ -308,7 +215,7 @@ class CT_OT_add_templates_from_folder(bpy.types.Operator):
name="Depth", description="Depth of recursion (default 1)", default=1, min=1)
def execute(self, context):
prefs = context.preferences.addons[base_package].preferences
prefs = pref()
if os.path.exists(self.directory):
blend_files = []
@ -376,7 +283,7 @@ class CT_OT_template_workspaces(Operator):
def draw_ws(self, s, context):
layout = s.layout
template = context.preferences.addons[base_package].preferences.projects[self.index]
template = pref().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)
@ -388,18 +295,34 @@ class CT_OT_template_workspaces(Operator):
def invoke(self, context, event):
wm = context.window_manager
template = context.preferences.addons[base_package].preferences.projects[self.index]
template = pref().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"
class CT_OT_start_from(Operator):
bl_idname = "ct.start_from"
bl_label = "Start from..."
bl_description = "Use any selected .blend file as template (and optionally add it to the list)"
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
filepath: StringProperty(
subtype="FILE_PATH", description="Select the .json file to load", update=on_path_update)
add: BoolProperty(default=False, name="Add template", description="Add this file in your templates")
name: StringProperty(name="Name", description="The name for this template (if empty, file name will be used)")
def execute(self, context):
prefs = pref()
if self.filepath:
if self.add and not already_present(self, prefs, self.filepath):
template = prefs.projects.add()
template.name = self.name if self.name else name_from_path(self.filepath)
template.path = self.filepath
context.preferences.is_dirty = True
bpy.ops.wm.read_homefile(filepath=self.filepath)
else:
self.report({'WARNING'}, "This option can only be used from File > New menu")
return {'FINISHED'}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}

60
addon/src/prefs.py Normal file
View File

@ -0,0 +1,60 @@
import bpy
from bpy.types import PropertyGroup, AddonPreferences
from bpy.props import StringProperty, CollectionProperty, IntProperty, BoolProperty
from .funcs import on_path_update
from .. import __package__ as base_package
class TemplateItem(PropertyGroup):
name: StringProperty(
name="Name", description="Display name for this template")
path: StringProperty(
name="Path", description="Path to the .blend file for this template", subtype='FILE_PATH', update=on_path_update)
class CustomTemplatesPreferences(AddonPreferences):
bl_idname = base_package
override_splash: BoolProperty(
default=True, name="Use Custom Templates", description="Override Splash Screen's 'New File' list with your Custom Templates")
start_from: BoolProperty(
default=False, name="Add option: File > New > Start from...", description="Show an option in File > New menu to start a new project from any .blend file")
projects: CollectionProperty(type=TemplateItem)
active_template_index: IntProperty(
description="Index of the selected template")
def draw(self, context):
layout = self.layout
layout.label(
text=f"Your custom templates ({len(self.projects)})")
row = layout.row()
row.template_list("UI_UL_list", "custom_templates",
self, "projects", self, "active_template_index", rows=6, maxrows=6)
col = row.column(align=True)
col.menu("CT_MT_templates_menu", icon='DOWNARROW_HLT', text="")
col.separator()
col.operator("ct.add_templates_from_folder", text="", icon="FILE_FOLDER")
col.operator("ct.add", icon='ADD', text="")
col.operator("ct.remove", icon='REMOVE', text="")
col.separator()
col.operator("ct.move_up", icon='TRIA_UP', text="")
col.operator("ct.move_down", icon='TRIA_DOWN', text="")
if self.projects:
project = self.projects[self.active_template_index]
layout.prop(project, "path")
layout.prop(project, "name")
b = layout.box()
b.label(text="Splash Screen", icon="SETTINGS")
b.prop(self, "override_splash")
box = b.box()
if self.override_splash and self.projects:
box.label(text="Custom templates will be shown in the Splash Screen.")
if not self.override_splash or len(self.projects) == 0:
box.label(
text="The default Blender list will be shown in the Splash Screen.", icon=("ERROR" if not self.projects else "NONE"))
b = layout.box()
b.label(text="Extra", icon="SETTINGS")
b.prop(self, "start_from")

View File

@ -1,8 +1,8 @@
# Ref WM_MT_splash https://projects.blender.org/blender/blender/src/commit/5a9fe638dedb179050c4929ea8fcdec80d221af2/scripts/startup/bl_operators/wm.py#L3296
# Ref TOPBAR_MT_file_new https://projects.blender.org/blender/blender/src/commit/5a9fe638dedb179050c4929ea8fcdec80d221af2/scripts/startup/bl_ui/space_topbar.py#L286
from .. import __package__ as base_package
from .draw import draw_templates
import bpy
from .funcs import draw_templates, pref
from .. import __package__ as base_package
# Clone of the WM_MT_splash (the section under the image of the Splash)
# It's edited (only in #Templates part) to add a menu in the New File section
# to switch between blender's original templates and Custom Templates.
@ -12,7 +12,7 @@ class WM_MT_splash(bpy.types.Menu):
def draw(self, context):
layout = self.layout
prefs = context.preferences.addons[base_package].preferences
prefs = pref()
layout.operator_context = 'EXEC_DEFAULT'
layout.emboss = 'PULLDOWN_MENU'
split = layout.split()
@ -26,8 +26,8 @@ class WM_MT_splash(bpy.types.Menu):
col1.operator_context = 'INVOKE_DEFAULT'
draw_templates(col1, context, True)
else:
colA.label(text="New File")
# Call original code
colA.label(text="New File")
bpy.types.TOPBAR_MT_file_new.draw_ex(col1, context, use_splash=True)
colB = ct_split.column()
colB.menu("CT_MT_splash_mode", icon='DOWNARROW_HLT', text="")
@ -71,46 +71,3 @@ class WM_MT_splash(bpy.types.Menu):
self.layout.label(text="Running in Offline Mode", icon='INTERNET_OFFLINE')
layout.separator()
class CT_MT_splash_mode(bpy.types.Menu):
bl_idname = "CT_MT_splash_mode"
bl_label = "Custom Templates Switch"
bl_description = "Swtich between Blender's default and your Custom Templates"
def draw(self, context):
layout = self.layout
prefs = context.preferences.addons[base_package].preferences
def_check = 'NONE'
ct_check = 'NONE'
if prefs.override_splash:
ct_check = "CHECKMARK"
else:
def_check = "CHECKMARK"
layout.operator("ct.open_preferences", text="Manage templates")
layout.separator()
layout.operator("ct.splash_custom", text="Use Custom Templates", icon=ct_check)
layout.operator("ct.splash_default", text="Use Blender's Default", icon=def_check)
class CT_OT_splash_default(bpy.types.Operator):
bl_idname = "ct.splash_default"
bl_label = "Display default Blender's templates"
bl_description = "Use Blender's default templates in the Splash Screen"
def execute(self, context):
prefs = context.preferences.addons[base_package].preferences
prefs.override_splash = False
context.preferences.is_dirty = True
return {'FINISHED'}
class CT_OT_splash_custom(bpy.types.Operator):
bl_idname = "ct.splash_custom"
bl_label = "Display your custom templates"
bl_description = "Use your custom templates in the Splash Screen (limited to first 5)"
def execute(self, context):
prefs = context.preferences.addons[base_package].preferences
prefs.override_splash = True
context.preferences.is_dirty = True
return {'FINISHED'}

27
dev/TEST_WORKFLOW.md Normal file
View File

@ -0,0 +1,27 @@
# Workflow for testing new versions
- Install new version, and ensure no errors/previous setting preserved
- Disable/Re-enable add-on for preferences reset
- Ensure `File > New`/Splash Screen empty
- Ensure operators are disabled (if they need templates)
- Add single template from prefs (select path, name should auto complete from filename)
- Ensure operators are enabled (if they need templates)
- Add from folder (the same of the previous template, but with others)
*The folder templates (with optional nested levels) should be added, excluding aready present ones.*
- Ensure `File > New`/Splash Screen match
- Ensure `File > New`/Splash Screen works opening
- Test the Splash Screen Switch (optionally checking preference persistence across restart)
- Export the list to JSON file
- Clear all templates (from settings)
- Import the exported file
- Ensure template list match previous
- Test reordering/removing
- Enable Extra option `File > New > Start From...`
- Ensure it works (optionally checking add templates)
- Ensure it doesn't show up with ctrl + N (which would not work properly because of context)
- Test `File > Defaults > Select new template`
- Test `File > Defaults > Use current as template`
- Test `File > Defaults > Manage templates`/same as Splash Screen
- Test `Workspace menu > Add from Custom Templates` feature
- Ensure `File > New`/Splash Screen match
- Nice, version ready!

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@ -14,32 +14,36 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import bpy
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.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
from bpy.types import TOPBAR_MT_file_new, TOPBAR_MT_file_defaults, TOPBAR_MT_workspace_menu
from .src.funcs import draw_file_new_templates, draw_file_default_operators, draw_ws_menu_add
from .src.splash import WM_MT_splash
from .src.menus import CT_MT_splash_mode, CT_MT_templates_menu, CT_MT_workspace_add, CT_MT_more_templates
from .src.ops import CT_OT_export_templates, CT_OT_start_from, CT_OT_import_templates, 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_OT_template_workspaces, CT_OT_add_workspace
from .src.prefs import CustomTemplatesPreferences, TemplateItem
bl_info = {
"id": "custom_templates",
"name": "Custom Templates",
"tagline": "Add your own .blend files as template options for new projects",
"version": (1, 5, 0),
"blender": (2, 83, 0),
"location": "File > New, File > Defaults, Splash Screen",
"category": "System",
"support": "COMMUNITY",
"blender_manifest": "blender_manifest.toml"
}
classes = [WM_MT_splash,
CT_OT_add_workspace,
CT_OT_template_workspaces,
CT_MT_workspace_add,
TemplateItem,
CT_OT_export_templates,
CT_OT_import_templates,
CT_OT_splash_custom,
CT_OT_splash_default,
CT_OT_open_preferences,
CT_MT_splash_mode,
CT_MT_templates_menu,
CT_MT_more_templates,
CT_OT_add_workspace,
CT_OT_start_from,
CT_OT_template_workspaces,
CT_OT_export_templates,
CT_OT_import_templates,
CT_OT_open_preferences,
CT_OT_move_up,
CT_OT_move_down,
CT_OT_add,
@ -48,26 +52,27 @@ classes = [WM_MT_splash,
CT_OT_add_template_popup,
CT_OT_select_template_popup,
CT_OT_add_templates_from_folder,
TemplateItem,
CustomTemplatesPreferences]
og_splash = None;
og_splash = None
def register():
global og_splash
og_splash = bpy.types.WM_MT_splash;
og_splash = bpy.types.WM_MT_splash
for c in classes:
bpy.utils.register_class(c)
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_workspace_menu.append(draw_ws_menu_add)
TOPBAR_MT_file_new.append(draw_file_new_templates)
TOPBAR_MT_file_defaults.append(draw_file_default_operators)
TOPBAR_MT_workspace_menu.append(draw_ws_menu_add)
def unregister():
for c in reversed(classes):
bpy.utils.unregister_class(c)
bpy.utils.register_class(og_splash)
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_workspace_menu.remove(draw_ws_menu_add)
TOPBAR_MT_file_new.remove(draw_file_new_templates)
TOPBAR_MT_file_defaults.remove(draw_file_default_operators)
TOPBAR_MT_workspace_menu.remove(draw_ws_menu_add)
if __name__ == "__main__":
registers()

View File

@ -1,21 +1,54 @@
from .. import __name__ as base_package
from .ots import name_from_path
import os
import bpy
from .. import __name__ as base_package
def already_present(self, prefs, path, report=True):
for p in prefs.projects:
if p.path == path:
if report:
self.report(
{'WARNING'}, f'Current file is already in the templates list as "{p.name}".')
return True
return False
def name_from_path(path):
if path:
return os.path.splitext(os.path.basename(path))[0]
else:
return ""
def on_path_update(self, context):
if not self.name and self.path:
self.name = name_from_path(self.path)
context.preferences.is_dirty = True
if self.path and self.path.startswith('//'):
self.path = bpy.path.abspath(self.path)
context.preferences.is_dirty = True
def pref():
return bpy.context.preferences.addons[base_package].preferences
def has_templates():
return len(pref().projects) > 0
def draw_file_new_templates(self, context):
layout = self.layout
prefs = context.preferences.addons[base_package].preferences
if len(prefs.projects) > 0:
if has_templates():
layout.separator()
draw_templates(layout, context)
def draw_templates(layout, context, splash_mode=False):
prefs = context.preferences.addons[base_package].preferences
for i in range(min(5, len(prefs.projects)) if splash_mode else len(prefs.projects)):
prefs = pref()
for i in range(min(5 if len(prefs.projects) <= 5 else 4, len(prefs.projects)) if splash_mode else len(prefs.projects)):
project = prefs.projects[i]
if project.path:
layout.operator(
"wm.read_homefile", text=(project.name if project.name else name_from_path(project.path)), icon="FILE_NEW" if splash_mode else "NONE").filepath = project.path
if splash_mode and len(prefs.projects) > 5:
layout.menu("CT_MT_more_templates", text="More...", icon="FILE_NEW")
if not splash_mode and prefs.start_from and context.area.type == 'TOPBAR':
layout.separator()
layout.operator("ct.start_from", icon="FILE_FOLDER")
def draw_file_default_operators(self, context):
layout = self.layout
@ -39,8 +72,6 @@ def draw_file_default_operators(self, context):
def draw_ws_menu_add(self, context):
layout = self.layout
prefs = context.preferences.addons[base_package].preferences
if prefs.projects:
if has_templates():
layout.separator()
layout.menu("CT_MT_workspace_add", text="Add from Custom Templates", icon="WORKSPACE")

60
legacy/src/menus.py Normal file
View File

@ -0,0 +1,60 @@
from .. import __name__ as base_package
import os
import bpy
import json
from bpy.types import Menu
from .funcs import pref, name_from_path
class CT_MT_templates_menu(Menu):
bl_label = "Manage your custom templates"
bl_description = "Import, export, add from folder (with controllable recursion depth), clear current templates"
def draw(self, context):
layout = self.layout
layout.operator("ct.add_templates_from_folder",
text="Add from folder", icon="ADD")
layout.operator(
"ct.clear", text="Clear current templates", icon="TRASH")
layout.separator()
layout.operator("ct.import_templates",
text="Import templates", icon="IMPORT")
layout.operator("ct.export_templates",
text="Export templates", icon="EXPORT")
class CT_MT_splash_mode(Menu):
bl_idname = "CT_MT_splash_mode"
bl_label = "Custom Templates Switch"
bl_description = "Swtich between Blender's default and your Custom Templates"
def draw(self, context):
layout = self.layout
prefs = pref()
layout.operator("ct.open_preferences", text="Manage templates")
layout.separator()
layout.prop(prefs, "override_splash")
class CT_MT_more_templates(Menu):
bl_idname = "CT_MT_more_templates"
bl_label = "Other Custom Templates"
bl_description = "Show the other templates in the splash screen"
def draw(self, context):
layout = self.layout
prefs = pref()
for t in prefs.projects[4:]:
layout.operator(
"wm.read_homefile", text=(t.name if t.name else name_from_path(t.path)), icon="FILE_NEW").filepath = t.path
class CT_MT_workspace_add(Menu):
bl_label = "Add workspace from Custom Templates"
def draw(self, context):
layout = self.layout
templates = pref().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

View File

@ -1,103 +1,10 @@
from .. import __name__ as base_package
import os
import bpy
import json
from bpy.types import Operator, Menu, Header, PropertyGroup, AddonPreferences
from bpy.props import StringProperty, CollectionProperty, IntProperty, BoolProperty
def already_present(self, prefs, path, report=True):
for p in prefs.projects:
if p.path == path:
if report:
self.report(
{'WARNING'}, f'Current file is already in the templates list as "{p.name}".')
return True
return False
def name_from_path(path):
if path:
return os.path.splitext(os.path.basename(path))[0]
else:
return ""
def on_path_update(self, context):
if not self.name and self.path:
self.name = name_from_path(self.path)
context.preferences.is_dirty = True
if self.path and self.path.startswith('//'):
self.path = bpy.path.abspath(self.path)
context.preferences.is_dirty = True
class TemplateItem(PropertyGroup):
name: StringProperty(
name="Name", description="Display name for this template")
path: StringProperty(
name="Path", description="Path to the .blend file for this template", subtype='FILE_PATH', update=on_path_update)
class CustomTemplatesPreferences(AddonPreferences):
bl_idname = base_package
override_splash: BoolProperty(
default=True, name="Use Custom Templates", description="Override Splash Screen's 'New File' list with your Custom Templates")
projects: CollectionProperty(type=TemplateItem)
active_template_index: IntProperty(
description="Index of the selected template")
def draw(self, context):
layout = self.layout
layout.label(
text=f"Your custom templates ({len(self.projects)})")
row = layout.row()
row.template_list("UI_UL_list", "custom_templates",
self, "projects", self, "active_template_index", rows=6, maxrows=6)
col = row.column(align=True)
col.menu("CT_MT_templates_menu", icon='DOWNARROW_HLT', text="")
col.separator()
col.operator("ct.add_templates_from_folder",
text="", icon="FILE_FOLDER")
col.operator("ct.add", icon='ADD', text="")
col.operator("ct.remove", icon='REMOVE', text="")
col.separator()
col.operator("ct.move_up", icon='TRIA_UP', text="")
col.operator("ct.move_down", icon='TRIA_DOWN', text="")
if self.projects:
project = self.projects[self.active_template_index]
layout.prop(project, "path")
layout.prop(project, "name")
b = layout.box()
b.label(text="Splash Screen", icon="SETTINGS")
b.prop(self, "override_splash")
box = b.box()
if self.override_splash and self.projects:
box.label(text="Custom templates will be shown in the Splash Screen.")
if not self.override_splash or len(self.projects) == 0:
box.label(
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:
box.label(
text="Note: Only the first 5 templates in the list will be shown in the splash screen.")
class CT_MT_templates_menu(Menu):
bl_label = "Manage your custom templates"
bl_description = "Import, export, add from folder (with controllable recursion depth), clear current templates"
def draw(self, context):
layout = self.layout
layout.operator("ct.add_templates_from_folder",
text="Add from folder", icon="ADD")
layout.operator(
"ct.clear", text="Clear current templates", icon="TRASH")
layout.separator()
layout.operator("ct.import_templates",
text="Import templates", icon="IMPORT")
layout.operator("ct.export_templates",
text="Export templates", icon="EXPORT")
from bpy.types import Operator
from bpy.props import StringProperty, IntProperty, BoolProperty
from .funcs import pref, has_templates, name_from_path, already_present, on_path_update
from .. import __name__ as base_package
class CT_OT_export_templates(Operator):
bl_idname = "ct.export_templates"
@ -108,7 +15,7 @@ class CT_OT_export_templates(Operator):
subtype="FILE_PATH", description="Select the path for the exported file", default="templates.json")
def execute(self, context):
prefs = context.preferences.addons[base_package].preferences
prefs = pref()
with open(self.filepath, 'w') as f:
projects = [{"name": project.name, "path": project.path}
for project in prefs.projects]
@ -123,7 +30,7 @@ class CT_OT_export_templates(Operator):
@classmethod
def poll(cls, context):
return len(context.preferences.addons[base_package].preferences.projects) > 0
return has_templates()
class CT_OT_import_templates(Operator):
bl_idname = "ct.import_templates"
@ -134,7 +41,7 @@ class CT_OT_import_templates(Operator):
subtype="FILE_PATH", description="Select the .json file to load")
def execute(self, context):
prefs = context.preferences.addons[base_package].preferences
prefs = pref()
if os.path.exists(self.filepath):
with open(self.filepath, 'r') as f:
@ -164,7 +71,7 @@ class CT_OT_add(Operator):
bl_description = "Add new template"
def execute(self, context):
prefs = context.preferences.addons[base_package].preferences
prefs = pref()
prefs.projects.add()
prefs.active_template_index = len(prefs.projects) - 1
context.preferences.is_dirty = True
@ -176,7 +83,7 @@ class CT_OT_remove(Operator):
bl_description = "Remove selected template"
def execute(self, context):
prefs = context.preferences.addons[base_package].preferences
prefs = pref()
prefs.projects.remove(prefs.active_template_index)
prefs.active_template_index = min(
max(0, prefs.active_template_index - 1), len(prefs.projects) - 1)
@ -185,7 +92,7 @@ class CT_OT_remove(Operator):
@classmethod
def poll(cls, context):
return len(context.preferences.addons[base_package].preferences.projects) > 0
return has_templates()
class CT_OT_move_up(Operator):
bl_idname = "ct.move_up"
@ -193,7 +100,7 @@ class CT_OT_move_up(Operator):
bl_description = "Move the selected template up in the list"
def execute(self, context):
prefs = context.preferences.addons[base_package].preferences
prefs = pref()
index = prefs.active_template_index
if index > 0:
@ -206,7 +113,7 @@ class CT_OT_move_up(Operator):
@classmethod
def poll(cls, context):
return len(context.preferences.addons[base_package].preferences.projects) > 0
return has_templates()
class CT_OT_move_down(Operator):
bl_idname = "ct.move_down"
@ -214,7 +121,7 @@ class CT_OT_move_down(Operator):
bl_description = "Move the selected template down in the list"
def execute(self, context):
prefs = context.preferences.addons[base_package].preferences
prefs = pref()
index = prefs.active_template_index
if index < len(prefs.projects) - 1:
@ -227,7 +134,7 @@ class CT_OT_move_down(Operator):
@classmethod
def poll(cls, context):
return len(context.preferences.addons[base_package].preferences.projects) > 0
return has_templates()
class CT_OT_clear(Operator):
bl_idname = "ct.clear"
@ -235,7 +142,7 @@ class CT_OT_clear(Operator):
bl_description = "Clear the current list of templates (remove all templates)"
def execute(self, context):
prefs = context.preferences.addons[base_package].preferences
prefs = pref()
prefs.projects.clear()
prefs.active_template_index = 0
context.preferences.is_dirty = True
@ -246,7 +153,7 @@ class CT_OT_clear(Operator):
@classmethod
def poll(cls, context):
return len(context.preferences.addons[base_package].preferences.projects) > 0
return has_templates()
class CT_OT_add_template_popup(Operator):
bl_idname = "ct.add_template_popup"
@ -256,7 +163,7 @@ class CT_OT_add_template_popup(Operator):
name: StringProperty(name="Project Name")
def execute(self, context):
prefs = context.preferences.addons[base_package].preferences
prefs = pref()
if not already_present(self, prefs, bpy.data.filepath):
if bpy.data.filepath:
new_project = prefs.projects.add()
@ -283,7 +190,7 @@ class CT_OT_select_template_popup(Operator):
name: StringProperty(name="Template Name")
def execute(self, context):
prefs = context.preferences.addons[base_package].preferences
prefs = pref()
if not already_present(self, prefs, self.path):
template = prefs.projects.add()
template.name = self.name
@ -297,7 +204,7 @@ class CT_OT_select_template_popup(Operator):
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
class CT_OT_add_templates_from_folder(bpy.types.Operator):
class CT_OT_add_templates_from_folder(Operator):
bl_idname = "ct.add_templates_from_folder"
bl_label = "Add Templates from Folder"
bl_description = "Add templates from a specified folder containing .blend files"
@ -308,7 +215,7 @@ class CT_OT_add_templates_from_folder(bpy.types.Operator):
name="Depth", description="Depth of recursion (default 1)", default=1, min=1)
def execute(self, context):
prefs = context.preferences.addons[base_package].preferences
prefs = pref()
if os.path.exists(self.directory):
blend_files = []
@ -376,7 +283,7 @@ class CT_OT_template_workspaces(Operator):
def draw_ws(self, s, context):
layout = s.layout
template = context.preferences.addons[base_package].preferences.projects[self.index]
template = pref().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)
@ -388,18 +295,34 @@ class CT_OT_template_workspaces(Operator):
def invoke(self, context, event):
wm = context.window_manager
template = context.preferences.addons[base_package].preferences.projects[self.index]
template = pref().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"
class CT_OT_start_from(Operator):
bl_idname = "ct.start_from"
bl_label = "Start from..."
bl_description = "Use any selected .blend file as template (and optionally add it to the list)"
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
filepath: StringProperty(
subtype="FILE_PATH", description="Select the .json file to load", update=on_path_update)
add: BoolProperty(default=False, name="Add template", description="Add this file in your templates")
name: StringProperty(name="Name", description="The name for this template (if empty, file name will be used)")
def execute(self, context):
prefs = pref()
if self.filepath:
if self.add and not already_present(self, prefs, self.filepath):
template = prefs.projects.add()
template.name = self.name if self.name else name_from_path(self.filepath)
template.path = self.filepath
context.preferences.is_dirty = True
bpy.ops.wm.read_homefile(filepath=self.filepath)
else:
self.report({'WARNING'}, "This option can only be used from File > New menu")
return {'FINISHED'}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}

60
legacy/src/prefs.py Normal file
View File

@ -0,0 +1,60 @@
import bpy
from bpy.types import PropertyGroup, AddonPreferences
from bpy.props import StringProperty, CollectionProperty, IntProperty, BoolProperty
from .funcs import on_path_update
from .. import __name__ as base_package
class TemplateItem(PropertyGroup):
name: StringProperty(
name="Name", description="Display name for this template")
path: StringProperty(
name="Path", description="Path to the .blend file for this template", subtype='FILE_PATH', update=on_path_update)
class CustomTemplatesPreferences(AddonPreferences):
bl_idname = base_package
override_splash: BoolProperty(
default=True, name="Use Custom Templates", description="Override Splash Screen's 'New File' list with your Custom Templates")
start_from: BoolProperty(
default=False, name="Add option: File > New > Start from...", description="Show an option in File > New menu to start a new project from any .blend file")
projects: CollectionProperty(type=TemplateItem)
active_template_index: IntProperty(
description="Index of the selected template")
def draw(self, context):
layout = self.layout
layout.label(
text=f"Your custom templates ({len(self.projects)})")
row = layout.row()
row.template_list("UI_UL_list", "custom_templates",
self, "projects", self, "active_template_index", rows=6, maxrows=6)
col = row.column(align=True)
col.menu("CT_MT_templates_menu", icon='DOWNARROW_HLT', text="")
col.separator()
col.operator("ct.add_templates_from_folder", text="", icon="FILE_FOLDER")
col.operator("ct.add", icon='ADD', text="")
col.operator("ct.remove", icon='REMOVE', text="")
col.separator()
col.operator("ct.move_up", icon='TRIA_UP', text="")
col.operator("ct.move_down", icon='TRIA_DOWN', text="")
if self.projects:
project = self.projects[self.active_template_index]
layout.prop(project, "path")
layout.prop(project, "name")
b = layout.box()
b.label(text="Splash Screen", icon="SETTINGS")
b.prop(self, "override_splash")
box = b.box()
if self.override_splash and self.projects:
box.label(text="Custom templates will be shown in the Splash Screen.")
if not self.override_splash or len(self.projects) == 0:
box.label(
text="The default Blender list will be shown in the Splash Screen.", icon=("ERROR" if not self.projects else "NONE"))
b = layout.box()
b.label(text="Extra", icon="SETTINGS")
b.prop(self, "start_from")

View File

@ -1,8 +1,8 @@
# Ref WM_MT_splash https://projects.blender.org/blender/blender/src/commit/5a9fe638dedb179050c4929ea8fcdec80d221af2/scripts/startup/bl_operators/wm.py#L3296
# Ref TOPBAR_MT_file_new https://projects.blender.org/blender/blender/src/commit/5a9fe638dedb179050c4929ea8fcdec80d221af2/scripts/startup/bl_ui/space_topbar.py#L286
from .. import __name__ as base_package
from .draw import draw_templates
import bpy
from .funcs import draw_templates, pref
from .. import __name__ as base_package
# Clone of the WM_MT_splash (the section under the image of the Splash)
# It's edited (only in #Templates part) to add a menu in the New File section
# to switch between blender's original templates and Custom Templates.
@ -27,8 +27,8 @@ class WM_MT_splash(bpy.types.Menu):
colx.operator_context = 'INVOKE_DEFAULT'
draw_templates(colx, context, True)
else:
colA.label(text="New File")
# Call original code
colA.label(text="New File")
bpy.types.TOPBAR_MT_file_new.draw_ex(colx, context, use_splash=True)
colB = ct_split.column()
colB.menu("CT_MT_splash_mode", icon='DOWNARROW_HLT', text="")
@ -67,46 +67,3 @@ class WM_MT_splash(bpy.types.Menu):
layout.separator()
layout.separator()
class CT_MT_splash_mode(bpy.types.Menu):
bl_idname = "CT_MT_splash_mode"
bl_label = "Custom Templates Switch"
bl_description = "Swtich between Blender's default and your Custom Templates"
def draw(self, context):
layout = self.layout
prefs = context.preferences.addons[base_package].preferences
def_check = 'NONE'
ct_check = 'NONE'
if prefs.override_splash:
ct_check = "CHECKMARK"
else:
def_check = "CHECKMARK"
layout.operator("ct.open_preferences", text="Manage templates")
layout.separator()
layout.operator("ct.splash_custom", text="Use Custom Templates", icon=ct_check)
layout.operator("ct.splash_default", text="Use Blender's Default", icon=def_check)
class CT_OT_splash_default(bpy.types.Operator):
bl_idname = "ct.splash_default"
bl_label = "Display default Blender's templates"
bl_description = "Use Blender's default templates in the Splash Screen"
def execute(self, context):
prefs = context.preferences.addons[base_package].preferences
prefs.override_splash = False
context.preferences.is_dirty = True
return {'FINISHED'}
class CT_OT_splash_custom(bpy.types.Operator):
bl_idname = "ct.splash_custom"
bl_label = "Display your custom templates"
bl_description = "Use your custom templates in the Splash Screen (limited to first 5)"
def execute(self, context):
prefs = context.preferences.addons[base_package].preferences
prefs.override_splash = True
context.preferences.is_dirty = True
return {'FINISHED'}

View File

@ -1 +1 @@
zip -9 -r ./releases/1.x.x/legacy/custom-templates-legacy-1.3.1.zip ./legacy/
zip -9 -r ./releases/1.x.x/legacy/custom-templates-legacy-1.5.0.zip ./legacy/

Binary file not shown.