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 ## v1.4.0
### New Feature ### 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. **Custom Templates** allows you to use your own .blend files as template options for new projects.
--- ---
- [Getting Started](#getting-started) - [Getting Started](https://projects.blender.org/Francesco-Bellini/custom_templates_addon/wiki/Getting-Started)
- [Why *Custom Templates*?](#why-custom-templates) - [Why this add-on?](https://projects.blender.org/Francesco-Bellini/custom_templates_addon/wiki/Why+this+add-on%3F.-)
- [Support](#support) - [Support](https://projects.blender.org/Francesco-Bellini/custom_templates_addon/wiki/Support)
- [Changelog](./CHANGELOG.md) - [Changelog](./CHANGELOG.md)
- [Copyright](#copyright) - [Copyright](#copyright)
@ -16,83 +18,6 @@
Your templates will be avaliable in the Splash Screen and the `File > New` menu. 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 ### Changelog
Visit the [CHANGELOG](./CHANGELOG.md) file to see the changes over new version. 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. 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**? 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. **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. *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)*.
--- ---
Create your own templates or find the ones that best suit your needs online. 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.* *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. *The only limit is your immagination!*
This way, you will be able to easily restore your templates, if necessary.
*For example, after disabling and re-enabling the add-on...*
--- ---
#### Features #### Features
- Show your templates in `File > New` menu and in the Splash Screen - 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 - 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) - Add templates from folder (lookup `.blend` files in folder)
- Select new template - Select new template
- Use current file as template - Use current file as template
- Clear current templates - Clear current templates
- Add workspace from templates *(right-click on a workspace and use `Add from Custom Templates` menu)* - Add workspace from templates *(right-click on a workspace and use `Add from Custom Templates` menu)*
##### Legacy Support ##### 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).* *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 # 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, draw_ws_menu_add from bpy.types import TOPBAR_MT_file_new, TOPBAR_MT_file_defaults, TOPBAR_MT_workspace_menu
from .classes.splash import WM_MT_splash, CT_MT_splash_mode, CT_OT_splash_custom, CT_OT_splash_default from .src.funcs import draw_file_new_templates, draw_file_default_operators, draw_ws_menu_add
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 .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 = { bl_info = {
"id": "custom_templates", "id": "custom_templates",
@ -30,17 +33,16 @@ bl_info = {
} }
classes = [WM_MT_splash, classes = [WM_MT_splash,
CT_OT_add_workspace,
CT_OT_template_workspaces,
CT_MT_workspace_add, 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_splash_mode,
CT_MT_templates_menu, 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_up,
CT_OT_move_down, CT_OT_move_down,
CT_OT_add, CT_OT_add,
@ -49,26 +51,27 @@ classes = [WM_MT_splash,
CT_OT_add_template_popup, CT_OT_add_template_popup,
CT_OT_select_template_popup, CT_OT_select_template_popup,
CT_OT_add_templates_from_folder, CT_OT_add_templates_from_folder,
TemplateItem,
CustomTemplatesPreferences] CustomTemplatesPreferences]
og_splash = None; og_splash = None
def register(): def register():
global og_splash global og_splash
og_splash = bpy.types.WM_MT_splash; og_splash = bpy.types.WM_MT_splash
for c in classes: for c in classes:
bpy.utils.register_class(c) bpy.utils.register_class(c)
bpy.types.TOPBAR_MT_file_new.append(draw_file_new_templates) TOPBAR_MT_file_new.append(draw_file_new_templates)
bpy.types.TOPBAR_MT_file_defaults.append(draw_file_default_operators) TOPBAR_MT_file_defaults.append(draw_file_default_operators)
bpy.types.TOPBAR_MT_workspace_menu.append(draw_ws_menu_add) TOPBAR_MT_workspace_menu.append(draw_ws_menu_add)
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_splash) bpy.utils.register_class(og_splash)
bpy.types.TOPBAR_MT_file_new.remove(draw_file_new_templates) TOPBAR_MT_file_new.remove(draw_file_new_templates)
bpy.types.TOPBAR_MT_file_defaults.remove(draw_file_default_operators) TOPBAR_MT_file_defaults.remove(draw_file_default_operators)
bpy.types.TOPBAR_MT_workspace_menu.remove(draw_ws_menu_add) TOPBAR_MT_workspace_menu.remove(draw_ws_menu_add)
if __name__ == "__main__": if __name__ == "__main__":
registers() registers()

View File

@ -1,6 +1,6 @@
schema_version = "1.0.0" schema_version = "1.0.0"
id = "custom_templates" id = "custom_templates"
version = "1.4.0" version = "1.5.0"
name = "Custom Templates" name = "Custom Templates"
tagline = "Use your own .blend files as template options for new projects" tagline = "Use your own .blend files as template options for new projects"
maintainer = "Francesco Bellini <doc.open.dev@gmail.com>" 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"] license = ["SPDX:GPL-3.0-or-later"]
copyright = ["2024 Francesco Bellini"] copyright = ["2024 Francesco Bellini"]
[permissions] [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 import os
from .ots import name_from_path
import bpy 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): def draw_file_new_templates(self, context):
layout = self.layout layout = self.layout
prefs = context.preferences.addons[base_package].preferences if has_templates():
if len(prefs.projects) > 0:
layout.separator() layout.separator()
draw_templates(layout, context) draw_templates(layout, context)
def draw_templates(layout, context, splash_mode=False): def draw_templates(layout, context, splash_mode=False):
prefs = context.preferences.addons[base_package].preferences prefs = pref()
for i in range(min(5, len(prefs.projects)) if splash_mode else len(prefs.projects)): 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] project = prefs.projects[i]
if project.path: if project.path:
layout.operator( 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 "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): def draw_file_default_operators(self, context):
layout = self.layout layout = self.layout
@ -39,8 +72,6 @@ def draw_file_default_operators(self, context):
def draw_ws_menu_add(self, context): def draw_ws_menu_add(self, context):
layout = self.layout layout = self.layout
prefs = context.preferences.addons[base_package].preferences if has_templates():
if prefs.projects:
layout.separator() layout.separator()
layout.menu("CT_MT_workspace_add", text="Add from Custom Templates", icon="WORKSPACE") 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 os
import bpy import bpy
import json import json
from bpy.types import Operator, Menu, PropertyGroup, AddonPreferences from bpy.types import Operator
from bpy.props import StringProperty, CollectionProperty, IntProperty, BoolProperty from bpy.props import StringProperty, IntProperty, BoolProperty
from .funcs import pref, has_templates, name_from_path, already_present, on_path_update
def already_present(self, prefs, path, report=True): from .. import __package__ as base_package
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")
class CT_OT_export_templates(Operator): class CT_OT_export_templates(Operator):
bl_idname = "ct.export_templates" 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") subtype="FILE_PATH", description="Select the path for the exported file", default="templates.json")
def execute(self, context): def execute(self, context):
prefs = context.preferences.addons[base_package].preferences prefs = pref()
with open(self.filepath, 'w') as f: with open(self.filepath, 'w') as f:
projects = [{"name": project.name, "path": project.path} projects = [{"name": project.name, "path": project.path}
for project in prefs.projects] for project in prefs.projects]
@ -123,7 +30,7 @@ class CT_OT_export_templates(Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return len(context.preferences.addons[base_package].preferences.projects) > 0 return has_templates()
class CT_OT_import_templates(Operator): class CT_OT_import_templates(Operator):
bl_idname = "ct.import_templates" 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") subtype="FILE_PATH", description="Select the .json file to load")
def execute(self, context): def execute(self, context):
prefs = context.preferences.addons[base_package].preferences prefs = pref()
if os.path.exists(self.filepath): if os.path.exists(self.filepath):
with open(self.filepath, 'r') as f: with open(self.filepath, 'r') as f:
@ -164,7 +71,7 @@ class CT_OT_add(Operator):
bl_description = "Add new template" bl_description = "Add new template"
def execute(self, context): def execute(self, context):
prefs = context.preferences.addons[base_package].preferences prefs = pref()
prefs.projects.add() prefs.projects.add()
prefs.active_template_index = len(prefs.projects) - 1 prefs.active_template_index = len(prefs.projects) - 1
context.preferences.is_dirty = True context.preferences.is_dirty = True
@ -176,7 +83,7 @@ class CT_OT_remove(Operator):
bl_description = "Remove selected template" bl_description = "Remove selected template"
def execute(self, context): def execute(self, context):
prefs = context.preferences.addons[base_package].preferences prefs = pref()
prefs.projects.remove(prefs.active_template_index) prefs.projects.remove(prefs.active_template_index)
prefs.active_template_index = min( prefs.active_template_index = min(
max(0, prefs.active_template_index - 1), len(prefs.projects) - 1) max(0, prefs.active_template_index - 1), len(prefs.projects) - 1)
@ -185,7 +92,7 @@ class CT_OT_remove(Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return len(context.preferences.addons[base_package].preferences.projects) > 0 return has_templates()
class CT_OT_move_up(Operator): class CT_OT_move_up(Operator):
bl_idname = "ct.move_up" 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" bl_description = "Move the selected template up in the list"
def execute(self, context): def execute(self, context):
prefs = context.preferences.addons[base_package].preferences prefs = pref()
index = prefs.active_template_index index = prefs.active_template_index
if index > 0: if index > 0:
@ -206,7 +113,7 @@ class CT_OT_move_up(Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return len(context.preferences.addons[base_package].preferences.projects) > 0 return has_templates()
class CT_OT_move_down(Operator): class CT_OT_move_down(Operator):
bl_idname = "ct.move_down" 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" bl_description = "Move the selected template down in the list"
def execute(self, context): def execute(self, context):
prefs = context.preferences.addons[base_package].preferences prefs = pref()
index = prefs.active_template_index index = prefs.active_template_index
if index < len(prefs.projects) - 1: if index < len(prefs.projects) - 1:
@ -227,7 +134,7 @@ class CT_OT_move_down(Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return len(context.preferences.addons[base_package].preferences.projects) > 0 return has_templates()
class CT_OT_clear(Operator): class CT_OT_clear(Operator):
bl_idname = "ct.clear" bl_idname = "ct.clear"
@ -235,7 +142,7 @@ class CT_OT_clear(Operator):
bl_description = "Clear the current list of templates (remove all templates)" bl_description = "Clear the current list of templates (remove all templates)"
def execute(self, context): def execute(self, context):
prefs = context.preferences.addons[base_package].preferences prefs = pref()
prefs.projects.clear() prefs.projects.clear()
prefs.active_template_index = 0 prefs.active_template_index = 0
context.preferences.is_dirty = True context.preferences.is_dirty = True
@ -246,7 +153,7 @@ class CT_OT_clear(Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return len(context.preferences.addons[base_package].preferences.projects) > 0 return has_templates()
class CT_OT_add_template_popup(Operator): class CT_OT_add_template_popup(Operator):
bl_idname = "ct.add_template_popup" bl_idname = "ct.add_template_popup"
@ -256,7 +163,7 @@ class CT_OT_add_template_popup(Operator):
name: StringProperty(name="Project Name") name: StringProperty(name="Project Name")
def execute(self, context): def execute(self, context):
prefs = context.preferences.addons[base_package].preferences prefs = pref()
if not already_present(self, prefs, bpy.data.filepath): if not already_present(self, prefs, bpy.data.filepath):
if bpy.data.filepath: if bpy.data.filepath:
new_project = prefs.projects.add() new_project = prefs.projects.add()
@ -283,7 +190,7 @@ class CT_OT_select_template_popup(Operator):
name: StringProperty(name="Template Name") name: StringProperty(name="Template Name")
def execute(self, context): def execute(self, context):
prefs = context.preferences.addons[base_package].preferences prefs = pref()
if not already_present(self, prefs, self.path): if not already_present(self, prefs, self.path):
template = prefs.projects.add() template = prefs.projects.add()
template.name = self.name template.name = self.name
@ -297,7 +204,7 @@ class CT_OT_select_template_popup(Operator):
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)
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_idname = "ct.add_templates_from_folder"
bl_label = "Add Templates from Folder" bl_label = "Add Templates from Folder"
bl_description = "Add templates from a specified folder containing .blend files" 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) name="Depth", description="Depth of recursion (default 1)", default=1, min=1)
def execute(self, context): def execute(self, context):
prefs = context.preferences.addons[base_package].preferences prefs = pref()
if os.path.exists(self.directory): if os.path.exists(self.directory):
blend_files = [] blend_files = []
@ -376,7 +283,7 @@ class CT_OT_template_workspaces(Operator):
def draw_ws(self, s, context): def draw_ws(self, s, context):
layout = s.layout 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, _): with bpy.data.libraries.load(template.path) as (data, _):
for w in data.workspaces: for w in data.workspaces:
op = layout.operator("ct.add_workspace", text=w) op = layout.operator("ct.add_workspace", text=w)
@ -388,18 +295,34 @@ class CT_OT_template_workspaces(Operator):
def invoke(self, context, event): def invoke(self, context, event):
wm = context.window_manager 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") wm.popup_menu(self.draw_ws, title=f"Workspaces from '{template.name}'", icon="ADD")
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
class CT_MT_workspace_add(Menu): class CT_OT_start_from(Operator):
bl_label = "Add workspace from Custom Templates" 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): filepath: StringProperty(
layout = self.layout subtype="FILE_PATH", description="Select the .json file to load", update=on_path_update)
templates = context.preferences.addons[base_package].preferences.projects add: BoolProperty(default=False, name="Add template", description="Add this file in your templates")
layout.label(text="Add workspace from Custom Templates", icon="ADD") name: StringProperty(name="Name", description="The name for this template (if empty, file name will be used)")
layout.separator()
for i in range(len(templates)): def execute(self, context):
t = templates[i] prefs = pref()
layout.operator("CT_OT_template_workspaces", text=t.name).index = i 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 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 # 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 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) # 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 # 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. # 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): def draw(self, context):
layout = self.layout layout = self.layout
prefs = context.preferences.addons[base_package].preferences prefs = pref()
layout.operator_context = 'EXEC_DEFAULT' layout.operator_context = 'EXEC_DEFAULT'
layout.emboss = 'PULLDOWN_MENU' layout.emboss = 'PULLDOWN_MENU'
split = layout.split() split = layout.split()
@ -26,8 +26,8 @@ class WM_MT_splash(bpy.types.Menu):
col1.operator_context = 'INVOKE_DEFAULT' col1.operator_context = 'INVOKE_DEFAULT'
draw_templates(col1, context, True) draw_templates(col1, context, True)
else: else:
colA.label(text="New File")
# Call original code # Call original code
colA.label(text="New File")
bpy.types.TOPBAR_MT_file_new.draw_ex(col1, context, use_splash=True) bpy.types.TOPBAR_MT_file_new.draw_ex(col1, context, use_splash=True)
colB = ct_split.column() colB = ct_split.column()
colB.menu("CT_MT_splash_mode", icon='DOWNARROW_HLT', text="") 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') self.layout.label(text="Running in Offline Mode", icon='INTERNET_OFFLINE')
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'}

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 # 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, draw_ws_menu_add from bpy.types import TOPBAR_MT_file_new, TOPBAR_MT_file_defaults, TOPBAR_MT_workspace_menu
from .classes.splash import WM_MT_splash, CT_MT_splash_mode, CT_OT_splash_custom, CT_OT_splash_default from .src.funcs import draw_file_new_templates, draw_file_default_operators, draw_ws_menu_add
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 .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 = { 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",
"version": (1, 5, 0),
"blender": (2, 83, 0), "blender": (2, 83, 0),
"location": "File > New, File > Defaults, Splash Screen", "location": "File > New, File > Defaults, Splash Screen",
"category": "System", "category": "System",
"support": "COMMUNITY", "support": "COMMUNITY",
"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, 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_splash_mode,
CT_MT_templates_menu, 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_up,
CT_OT_move_down, CT_OT_move_down,
CT_OT_add, CT_OT_add,
@ -48,26 +52,27 @@ classes = [WM_MT_splash,
CT_OT_add_template_popup, CT_OT_add_template_popup,
CT_OT_select_template_popup, CT_OT_select_template_popup,
CT_OT_add_templates_from_folder, CT_OT_add_templates_from_folder,
TemplateItem,
CustomTemplatesPreferences] CustomTemplatesPreferences]
og_splash = None; og_splash = None
def register(): def register():
global og_splash global og_splash
og_splash = bpy.types.WM_MT_splash; og_splash = bpy.types.WM_MT_splash
for c in classes: for c in classes:
bpy.utils.register_class(c) bpy.utils.register_class(c)
bpy.types.TOPBAR_MT_file_new.append(draw_file_new_templates) TOPBAR_MT_file_new.append(draw_file_new_templates)
bpy.types.TOPBAR_MT_file_defaults.append(draw_file_default_operators) TOPBAR_MT_file_defaults.append(draw_file_default_operators)
bpy.types.TOPBAR_MT_workspace_menu.append(draw_ws_menu_add) TOPBAR_MT_workspace_menu.append(draw_ws_menu_add)
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_splash) bpy.utils.register_class(og_splash)
bpy.types.TOPBAR_MT_file_new.remove(draw_file_new_templates) TOPBAR_MT_file_new.remove(draw_file_new_templates)
bpy.types.TOPBAR_MT_file_defaults.remove(draw_file_default_operators) TOPBAR_MT_file_defaults.remove(draw_file_default_operators)
bpy.types.TOPBAR_MT_workspace_menu.remove(draw_ws_menu_add) TOPBAR_MT_workspace_menu.remove(draw_ws_menu_add)
if __name__ == "__main__": if __name__ == "__main__":
registers() registers()

View File

@ -1,21 +1,54 @@
from .. import __name__ as base_package import os
from .ots import name_from_path
import bpy 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): def draw_file_new_templates(self, context):
layout = self.layout layout = self.layout
prefs = context.preferences.addons[base_package].preferences if has_templates():
if len(prefs.projects) > 0:
layout.separator() layout.separator()
draw_templates(layout, context) draw_templates(layout, context)
def draw_templates(layout, context, splash_mode=False): def draw_templates(layout, context, splash_mode=False):
prefs = context.preferences.addons[base_package].preferences prefs = pref()
for i in range(min(5, len(prefs.projects)) if splash_mode else len(prefs.projects)): 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] project = prefs.projects[i]
if project.path: if project.path:
layout.operator( 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 "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): def draw_file_default_operators(self, context):
layout = self.layout layout = self.layout
@ -39,8 +72,6 @@ def draw_file_default_operators(self, context):
def draw_ws_menu_add(self, context): def draw_ws_menu_add(self, context):
layout = self.layout layout = self.layout
prefs = context.preferences.addons[base_package].preferences if has_templates():
if prefs.projects:
layout.separator() layout.separator()
layout.menu("CT_MT_workspace_add", text="Add from Custom Templates", icon="WORKSPACE") 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 os
import bpy import bpy
import json import json
from bpy.types import Operator, Menu, Header, PropertyGroup, AddonPreferences from bpy.types import Operator
from bpy.props import StringProperty, CollectionProperty, IntProperty, BoolProperty from bpy.props import StringProperty, IntProperty, BoolProperty
from .funcs import pref, has_templates, name_from_path, already_present, on_path_update
def already_present(self, prefs, path, report=True): from .. import __name__ as base_package
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")
class CT_OT_export_templates(Operator): class CT_OT_export_templates(Operator):
bl_idname = "ct.export_templates" 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") subtype="FILE_PATH", description="Select the path for the exported file", default="templates.json")
def execute(self, context): def execute(self, context):
prefs = context.preferences.addons[base_package].preferences prefs = pref()
with open(self.filepath, 'w') as f: with open(self.filepath, 'w') as f:
projects = [{"name": project.name, "path": project.path} projects = [{"name": project.name, "path": project.path}
for project in prefs.projects] for project in prefs.projects]
@ -123,7 +30,7 @@ class CT_OT_export_templates(Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return len(context.preferences.addons[base_package].preferences.projects) > 0 return has_templates()
class CT_OT_import_templates(Operator): class CT_OT_import_templates(Operator):
bl_idname = "ct.import_templates" 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") subtype="FILE_PATH", description="Select the .json file to load")
def execute(self, context): def execute(self, context):
prefs = context.preferences.addons[base_package].preferences prefs = pref()
if os.path.exists(self.filepath): if os.path.exists(self.filepath):
with open(self.filepath, 'r') as f: with open(self.filepath, 'r') as f:
@ -164,7 +71,7 @@ class CT_OT_add(Operator):
bl_description = "Add new template" bl_description = "Add new template"
def execute(self, context): def execute(self, context):
prefs = context.preferences.addons[base_package].preferences prefs = pref()
prefs.projects.add() prefs.projects.add()
prefs.active_template_index = len(prefs.projects) - 1 prefs.active_template_index = len(prefs.projects) - 1
context.preferences.is_dirty = True context.preferences.is_dirty = True
@ -176,7 +83,7 @@ class CT_OT_remove(Operator):
bl_description = "Remove selected template" bl_description = "Remove selected template"
def execute(self, context): def execute(self, context):
prefs = context.preferences.addons[base_package].preferences prefs = pref()
prefs.projects.remove(prefs.active_template_index) prefs.projects.remove(prefs.active_template_index)
prefs.active_template_index = min( prefs.active_template_index = min(
max(0, prefs.active_template_index - 1), len(prefs.projects) - 1) max(0, prefs.active_template_index - 1), len(prefs.projects) - 1)
@ -185,7 +92,7 @@ class CT_OT_remove(Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return len(context.preferences.addons[base_package].preferences.projects) > 0 return has_templates()
class CT_OT_move_up(Operator): class CT_OT_move_up(Operator):
bl_idname = "ct.move_up" 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" bl_description = "Move the selected template up in the list"
def execute(self, context): def execute(self, context):
prefs = context.preferences.addons[base_package].preferences prefs = pref()
index = prefs.active_template_index index = prefs.active_template_index
if index > 0: if index > 0:
@ -206,7 +113,7 @@ class CT_OT_move_up(Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return len(context.preferences.addons[base_package].preferences.projects) > 0 return has_templates()
class CT_OT_move_down(Operator): class CT_OT_move_down(Operator):
bl_idname = "ct.move_down" 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" bl_description = "Move the selected template down in the list"
def execute(self, context): def execute(self, context):
prefs = context.preferences.addons[base_package].preferences prefs = pref()
index = prefs.active_template_index index = prefs.active_template_index
if index < len(prefs.projects) - 1: if index < len(prefs.projects) - 1:
@ -227,7 +134,7 @@ class CT_OT_move_down(Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return len(context.preferences.addons[base_package].preferences.projects) > 0 return has_templates()
class CT_OT_clear(Operator): class CT_OT_clear(Operator):
bl_idname = "ct.clear" bl_idname = "ct.clear"
@ -235,7 +142,7 @@ class CT_OT_clear(Operator):
bl_description = "Clear the current list of templates (remove all templates)" bl_description = "Clear the current list of templates (remove all templates)"
def execute(self, context): def execute(self, context):
prefs = context.preferences.addons[base_package].preferences prefs = pref()
prefs.projects.clear() prefs.projects.clear()
prefs.active_template_index = 0 prefs.active_template_index = 0
context.preferences.is_dirty = True context.preferences.is_dirty = True
@ -246,7 +153,7 @@ class CT_OT_clear(Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return len(context.preferences.addons[base_package].preferences.projects) > 0 return has_templates()
class CT_OT_add_template_popup(Operator): class CT_OT_add_template_popup(Operator):
bl_idname = "ct.add_template_popup" bl_idname = "ct.add_template_popup"
@ -256,7 +163,7 @@ class CT_OT_add_template_popup(Operator):
name: StringProperty(name="Project Name") name: StringProperty(name="Project Name")
def execute(self, context): def execute(self, context):
prefs = context.preferences.addons[base_package].preferences prefs = pref()
if not already_present(self, prefs, bpy.data.filepath): if not already_present(self, prefs, bpy.data.filepath):
if bpy.data.filepath: if bpy.data.filepath:
new_project = prefs.projects.add() new_project = prefs.projects.add()
@ -283,7 +190,7 @@ class CT_OT_select_template_popup(Operator):
name: StringProperty(name="Template Name") name: StringProperty(name="Template Name")
def execute(self, context): def execute(self, context):
prefs = context.preferences.addons[base_package].preferences prefs = pref()
if not already_present(self, prefs, self.path): if not already_present(self, prefs, self.path):
template = prefs.projects.add() template = prefs.projects.add()
template.name = self.name template.name = self.name
@ -297,7 +204,7 @@ class CT_OT_select_template_popup(Operator):
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)
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_idname = "ct.add_templates_from_folder"
bl_label = "Add Templates from Folder" bl_label = "Add Templates from Folder"
bl_description = "Add templates from a specified folder containing .blend files" 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) name="Depth", description="Depth of recursion (default 1)", default=1, min=1)
def execute(self, context): def execute(self, context):
prefs = context.preferences.addons[base_package].preferences prefs = pref()
if os.path.exists(self.directory): if os.path.exists(self.directory):
blend_files = [] blend_files = []
@ -376,7 +283,7 @@ class CT_OT_template_workspaces(Operator):
def draw_ws(self, s, context): def draw_ws(self, s, context):
layout = s.layout 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, _): with bpy.data.libraries.load(template.path) as (data, _):
for w in data.workspaces: for w in data.workspaces:
op = layout.operator("ct.add_workspace", text=w) op = layout.operator("ct.add_workspace", text=w)
@ -388,18 +295,34 @@ class CT_OT_template_workspaces(Operator):
def invoke(self, context, event): def invoke(self, context, event):
wm = context.window_manager 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") wm.popup_menu(self.draw_ws, title=f"Workspaces from '{template.name}'", icon="ADD")
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
class CT_MT_workspace_add(Menu): class CT_OT_start_from(Operator):
bl_label = "Add workspace from Custom Templates" 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): filepath: StringProperty(
layout = self.layout subtype="FILE_PATH", description="Select the .json file to load", update=on_path_update)
templates = context.preferences.addons[base_package].preferences.projects add: BoolProperty(default=False, name="Add template", description="Add this file in your templates")
layout.label(text="Add workspace from Custom Templates", icon="ADD") name: StringProperty(name="Name", description="The name for this template (if empty, file name will be used)")
layout.separator()
for i in range(len(templates)): def execute(self, context):
t = templates[i] prefs = pref()
layout.operator("CT_OT_template_workspaces", text=t.name).index = i 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 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 # 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 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) # 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 # 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. # 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' colx.operator_context = 'INVOKE_DEFAULT'
draw_templates(colx, context, True) draw_templates(colx, context, True)
else: else:
colA.label(text="New File")
# Call original code # Call original code
colA.label(text="New File")
bpy.types.TOPBAR_MT_file_new.draw_ex(colx, context, use_splash=True) bpy.types.TOPBAR_MT_file_new.draw_ex(colx, context, use_splash=True)
colB = ct_split.column() colB = ct_split.column()
colB.menu("CT_MT_splash_mode", icon='DOWNARROW_HLT', text="") 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()
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.4.0.zip ./legacy/ zip -9 -r ./releases/1.x.x/legacy/custom-templates-legacy-1.5.0.zip ./legacy/

Binary file not shown.