Compare commits
No commits in common. "main" and "v1.2.3" have entirely different histories.
2
.gitignore
vendored
@ -1,3 +1 @@
|
||||
*.xcf
|
||||
*.blend*
|
||||
/images/utils/**
|
||||
|
50
CHANGELOG
Normal file
@ -0,0 +1,50 @@
|
||||
## v1.2.3
|
||||
|
||||
- Fix Splash Screen issue after disabling the add-on
|
||||
- Fix index access problems in a couple of situations
|
||||
- Minor UI updates
|
||||
|
||||
## v1.2.2
|
||||
|
||||
Add files permission in manifest *(for import/export)*
|
||||
|
||||
## v1.2.1
|
||||
|
||||
Minor code refactor
|
||||
|
||||
## v1.2.0
|
||||
|
||||
- Implement Splash Screen 'New File' list override:
|
||||
1) Add new preference for Splash Screen Override
|
||||
2) Add little menu for switching between Blender's default and Custom Templates list right from the splash screen.
|
||||
*The add-on will only show your first 5 templates (while `File > New` always shows all your templates)*
|
||||
*If you have no custom templates, the default Blender's one will be used.*
|
||||
|
||||
## v1.1.0
|
||||
|
||||
- Implement Import/Export functionality (to and from json file)
|
||||
- Re-organized source code, and reduce size where possibile
|
||||
*This update does not impact on previous functionalities*
|
||||
|
||||
## v1.0.2
|
||||
|
||||
Swich from `zip` to `blender -c extension built` for building the .zip file (now without useless /addon folder in the zip).
|
||||
*This update does not impact on functionalities*
|
||||
|
||||
## v1.0.1
|
||||
|
||||
Update preferences identifier from `__name__` to `__package__` as documented [here](https://docs.blender.org/manual/en/dev/advanced/extensions/addons.html#user-preferences-and-package)
|
||||
*This update does not impact on functionalities*
|
||||
|
||||
## v1.0.0
|
||||
|
||||
This is the first official version of the extension.
|
||||
|
||||
Current features:
|
||||
|
||||
- Add-on preferences with the list of custom templates (each with display name & path)
|
||||
- The list of Custom Templates is added to the `File > New` menu
|
||||
- The add-on functions are added to the `File > Defaults` menu, which are:
|
||||
1) Manage Templates (simple link to the Custom Templates add-on preferences)
|
||||
2) Select new custom template (open a popup requesting name and path and add it to the preferences
|
||||
3) Use current file as template (open a popup requesting name, and using the path of the current .blend file and add it to the preferences. Note: This function is only visible when a saved .blend file is currently loaded)
|
107
CHANGELOG.md
@ -1,107 +0,0 @@
|
||||
## 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
|
||||
- **Add workspace** from your Custom Templates
|
||||
*Usually, while adding a new workspace, you can choose between the workspaces of the 5 default blender templates.*
|
||||
*Now you can also add workspaces from your Custom Templates!*
|
||||
Right-click one of your workspaces and look for the `Add from Custom Templates` menu.
|
||||
- Minor preferences UI update
|
||||
|
||||
*Legacy update*
|
||||
|
||||
- *Update Splash Screen UI for legacy to match Blender <=3.6*
|
||||
|
||||
## v1.3.1
|
||||
|
||||
- Fix file path selector
|
||||
*Always use absolute path when selecting, even when a .blend file is loaded.*
|
||||
|
||||
## v1.3.0
|
||||
|
||||
### New Features
|
||||
|
||||
- Add templates from folder
|
||||
*Automatically add the `.blend` files from a given folder and auto-name them with the clean file name.*
|
||||
*(with optional recursion depth parameter, default 1)*
|
||||
- Clear current templates (with safety confirm)
|
||||
*Easily remove all the current templates from the add-on preferences menu.*
|
||||
- Auto-naming from the path (when adding from folder, or selecting the path before setting the name)
|
||||
- Added current number of templates in the add-on preferences
|
||||
- Update `File > Default` menu with new functions:
|
||||
1) Clear current templates
|
||||
2) Add from folder functions
|
||||
|
||||
## v1.2.6
|
||||
|
||||
- Fix preferences persistence issues on restart
|
||||
- Used poll function to disable operators instead of hiding them
|
||||
|
||||
## v1.2.5
|
||||
|
||||
- Fix override splash preference persistence when updating from splash
|
||||
*by marking preferences as dirty, after altering the value.*
|
||||
|
||||
## v1.2.3 - 1.2.4
|
||||
|
||||
- Fix Splash Screen issue after disabling the add-on
|
||||
- Remove some `INFO` messages
|
||||
- Fix index access problems in a couple of situations
|
||||
- Update classes id prefix (shorter and removing build warnings for menus naming)
|
||||
- Minor UI updates
|
||||
|
||||
## v1.2.2
|
||||
|
||||
Add files permission in manifest *(for import/export)*
|
||||
|
||||
## v1.2.1
|
||||
|
||||
Minor code refactor
|
||||
|
||||
## v1.2.0
|
||||
|
||||
- Implement Splash Screen 'New File' list override:
|
||||
1) Add new preference for Splash Screen Override
|
||||
2) Add little menu for switching between Blender's default and Custom Templates list right from the splash screen.
|
||||
*The add-on will only show your first 5 templates (while `File > New` always shows all your templates)*
|
||||
*If you have no custom templates, the default Blender's one will be used.*
|
||||
|
||||
## v1.1.0
|
||||
|
||||
- Implement Import/Export functionality (to and from json file)
|
||||
- Re-organized source code, and reduce size where possibile
|
||||
*This update does not impact on previous functionalities*
|
||||
|
||||
## v1.0.2
|
||||
|
||||
Swich from `zip` to `blender -c extension built` for building the .zip file (now without useless /addon folder in the zip).
|
||||
*This update does not impact on functionalities*
|
||||
|
||||
## v1.0.1
|
||||
|
||||
Update preferences identifier from `__name__` to `__package__` as documented [here](https://docs.blender.org/manual/en/dev/advanced/extensions/addons.html#user-preferences-and-package)
|
||||
*This update does not impact on functionalities*
|
||||
|
||||
## v1.0.0
|
||||
|
||||
This is the first official version of the extension.
|
||||
|
||||
Current features:
|
||||
|
||||
- Add-on preferences with the list of custom templates (each with display name & path)
|
||||
- The list of Custom Templates is added to the `File > New` menu
|
||||
- The add-on functions are added to the `File > Defaults` menu, which are:
|
||||
1) Manage Templates (simple link to the Custom Templates add-on preferences)
|
||||
2) Select new custom template (open a popup requesting name and path and add it to the preferences
|
||||
3) Use current file as template (open a popup requesting name, and using the path of the current .blend file and add it to the preferences. Note: This function is only visible when a saved .blend file is currently loaded)
|
88
README.md
@ -1,28 +1,82 @@
|
||||
# Custom Templates
|
||||
# Custom Templates - Add-On
|
||||
|
||||
## The Freedom to *Template*
|
||||

|
||||
|
||||
**Custom Templates** allows you to use your own .blend files as template options for new projects.
|
||||
|
||||
---
|
||||
|
||||
- [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)
|
||||
|
||||
---
|
||||
|
||||

|
||||
Custom Templates Add-On allows you to use your own .blend files as template options for new projects.
|
||||
|
||||
Your templates will be avaliable in the Splash Screen and the `File > New` menu.
|
||||
|
||||
### Changelog
|
||||
#### Why this add-on?
|
||||
|
||||
Visit the [CHANGELOG](./CHANGELOG.md) file to see the changes over new version.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
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 that will be added in the Splash Screen and the `File > New` menu.
|
||||
|
||||
Create your own version of General, 2D Animation or any other template you might need.
|
||||
|
||||
*Customize workspaces, preset your usual materials, define additional windows and anything else you would like to have from the beginning.*
|
||||
|
||||
### Preferences
|
||||
|
||||
In the add-on preferences, you can manage the list of your custom template files and the splash screen mode.
|
||||
|
||||
Preferences:
|
||||
|
||||
- Templates: List of your custom templates (each with Name and Path to .blend file)
|
||||
- Override Splash Screen 'New File' list: When active, replaces the list of templates in the Splash Screen
|
||||
*(you can update this settings directly from the splash screen menu of the add-on)*
|
||||
|
||||
You can add, remove and reorder templates as you wish.
|
||||
|
||||
It's possible to Import and Export from and to JSON file (see [Import/Export just below](#importexport)).
|
||||
|
||||
*Changes will be immidiatly visible.*
|
||||
|
||||

|
||||
|
||||
You can have as many template as you like.
|
||||
|
||||
> *Note that, as of now, using wrong file paths will NOT show any warning and you will get an error while opening the template.*
|
||||
> *Be sure to use only valid `.blend` files as path.*
|
||||
|
||||
### Features
|
||||
|
||||
##### Open Template
|
||||
Of course, the main feature of the add-on is to show your list of templates in the `File > New` menu and in the Splash Screen, according to preferences.
|
||||
They will open in an unsaved windows, just like the Blender's default ones.
|
||||
|
||||
##### Manage templates
|
||||
You can open the add-on preferences using the button `File > Defaults > Manage templates` or by manually open Blender's Preferences, going in the add-ons tab, and looking for the Custom Templates add-on preferences.
|
||||
|
||||
##### Import/Export
|
||||
Both from the add-on preferences menu and the `File > Default > Import/Export` menu, is possibile to export the current custom templates list to a JSON file, or importing an existing JSON file and using it to override the current custom templates list.
|
||||
|
||||
> *Note that this will only backup names and paths. This is not intended as a backup for your .blend files.*
|
||||
|
||||
##### Select new custom template
|
||||
Instead of using preferences, you can select a new template file using `File > Defaults > Select new custom template`.
|
||||
*This functions will open a popup where you will select the file path and specify the name for the new template.*
|
||||
|
||||
##### Use current as new template
|
||||
If you have a .blend file already open, you can use `File > Defaults > Use current as new template` (which is visible only when a saved .blend file is currently loaded).
|
||||
*This functions will open a popup where you will specify the name for the new template. The path will be the one of the current .blend file.*
|
||||
|
||||
### Support
|
||||
|
||||
As of today, the add-on has been only tested on Blender 4.2.0.
|
||||
*It may or may not work on previous version.*
|
||||
|
||||
### Copyright
|
||||
|
||||
This add-on is developed under GPL-3.0 license by Francesco Bellini from 2024.
|
||||
*See the [LICENSE](./LICENSE) file.*
|
||||
|
||||
#### Changelog
|
||||
|
||||
Visit the [CHANGELOG](./CHANGELOG) file to see the changes over new version.
|
||||
|
@ -1,53 +0,0 @@
|
||||
# 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.*
|
||||
|
||||
---
|
||||
|
||||
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!*
|
||||
|
||||
---
|
||||
|
||||
#### Features
|
||||
|
||||
- Show your templates in `File > New` menu and in the Splash Screen
|
||||
- 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
|
||||
|
||||
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 [Wiki](https://projects.blender.org/Francesco-Bellini/custom_templates_addon/wiki).*
|
||||
|
||||
---
|
||||
|
||||
#### Make your new projects easier from the start
|
@ -14,64 +14,54 @@
|
||||
# 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 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
|
||||
from .classes.ots import CustomTemplatesPreferences, TemplateItem, OT_ExportTemplates, OT_ImportTemplates, CUSTOM_MT_Export_Menu, OT_SelectTemplatePopup, OT_AddTemplatePopup, OT_AddTemplateItem, OT_RemoveTemplateItem, OT_MoveDownTemplateItem, OT_MoveUpTemplateItem, OT_OpenAddonPreferences
|
||||
from .classes.draw import draw_file_new_templates, draw_file_default_operators
|
||||
from .classes.splash import WM_MT_splash, CUSTOM_MT_splash_mode, OT_SplashCustom, OT_SplashDefault
|
||||
|
||||
bl_info = {
|
||||
"id": "custom_templates",
|
||||
"name": "Custom Templates",
|
||||
"tagline": "Add your own .blend files as template options for new projects",
|
||||
"blender": (4, 2, 0),
|
||||
"location": "File > New, File > Defaults, Splash Screen",
|
||||
"location": "File > New & File > Defaults",
|
||||
"category": "System",
|
||||
"support": "COMMUNITY",
|
||||
"blender_manifest": "blender_manifest.toml"
|
||||
}
|
||||
|
||||
classes = [WM_MT_splash,
|
||||
CT_MT_workspace_add,
|
||||
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,
|
||||
CT_OT_remove,
|
||||
CT_OT_clear,
|
||||
CT_OT_add_template_popup,
|
||||
CT_OT_select_template_popup,
|
||||
CT_OT_add_templates_from_folder,
|
||||
TemplateItem,
|
||||
OT_ExportTemplates,
|
||||
OT_ImportTemplates,
|
||||
OT_SplashCustom,
|
||||
OT_SplashDefault,
|
||||
OT_OpenAddonPreferences,
|
||||
CUSTOM_MT_splash_mode,
|
||||
CUSTOM_MT_Export_Menu,
|
||||
OT_MoveUpTemplateItem,
|
||||
OT_MoveDownTemplateItem,
|
||||
OT_AddTemplateItem,
|
||||
OT_RemoveTemplateItem,
|
||||
OT_AddTemplatePopup,
|
||||
OT_SelectTemplatePopup,
|
||||
CustomTemplatesPreferences]
|
||||
|
||||
og_splash = None
|
||||
original_splash = None;
|
||||
|
||||
def register():
|
||||
global og_splash
|
||||
og_splash = bpy.types.WM_MT_splash
|
||||
global original_splash
|
||||
original_splash = bpy.types.WM_MT_splash;
|
||||
for c in classes:
|
||||
bpy.utils.register_class(c)
|
||||
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)
|
||||
bpy.types.TOPBAR_MT_file_new.append(draw_file_new_templates)
|
||||
bpy.types.TOPBAR_MT_file_defaults.append(draw_file_default_operators)
|
||||
|
||||
def unregister():
|
||||
for c in reversed(classes):
|
||||
bpy.utils.unregister_class(c)
|
||||
bpy.utils.register_class(og_splash)
|
||||
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)
|
||||
bpy.utils.register_class(original_splash)
|
||||
bpy.types.TOPBAR_MT_file_new.remove(draw_file_new_templates)
|
||||
bpy.types.TOPBAR_MT_file_defaults.remove(draw_file_default_operators)
|
||||
|
||||
if __name__ == "__main__":
|
||||
registers()
|
||||
register()
|
||||
|
@ -1,6 +1,6 @@
|
||||
schema_version = "1.0.0"
|
||||
id = "custom_templates"
|
||||
version = "1.5.0"
|
||||
version = "1.2.3"
|
||||
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 templates from folder"
|
||||
files = "Import/Export your custom templates as JSON from/to disk"
|
||||
|
35
addon/classes/draw.py
Normal file
@ -0,0 +1,35 @@
|
||||
from .. import __package__ as base_package
|
||||
import bpy
|
||||
|
||||
def draw_file_new_templates(self, context):
|
||||
layout = self.layout
|
||||
|
||||
prefs = context.preferences.addons[base_package].preferences
|
||||
if len(prefs.projects) > 0:
|
||||
layout.separator()
|
||||
draw_templates_on_col(layout, context)
|
||||
|
||||
def draw_templates_on_col(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)):
|
||||
project = prefs.projects[i]
|
||||
layout.operator(
|
||||
"wm.read_homefile", text=project.name, icon="FILE_NEW" if splash_mode else "NONE").filepath = project.path
|
||||
|
||||
def draw_file_default_operators(self, context):
|
||||
layout = self.layout
|
||||
layout.separator()
|
||||
# Manage Template
|
||||
layout.operator("custom_templates.open_preferences",
|
||||
text="Manage templates")
|
||||
# Import/Export
|
||||
layout.menu("custom_templates.export_menu", text="Import/Export")
|
||||
layout.separator()
|
||||
# Select new custom template
|
||||
layout.operator("custom_templates.select_template_popup",
|
||||
text="Select new custom template")
|
||||
# Use current as new template (only with an active saved .blend file opened)
|
||||
if bpy.data.filepath != "":
|
||||
layout.operator("custom_templates.add_template_popup",
|
||||
text="Use current as new template")
|
||||
|
245
addon/classes/ots.py
Normal file
@ -0,0 +1,245 @@
|
||||
from .. import __package__ as base_package
|
||||
import bpy
|
||||
import json
|
||||
from bpy.types import Operator, PropertyGroup, AddonPreferences
|
||||
from bpy.props import StringProperty, CollectionProperty, IntProperty, BoolProperty
|
||||
|
||||
# Preferences
|
||||
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')
|
||||
|
||||
class CustomTemplatesPreferences(AddonPreferences):
|
||||
bl_idname = base_package
|
||||
|
||||
override_splash: BoolProperty(default=True, name="Override Splash Screen's 'New File' list", description="Override splashscreen's 'New File' list")
|
||||
projects: CollectionProperty(type=TemplateItem)
|
||||
active_template_index: IntProperty(
|
||||
description="Index of the selected template")
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
layout.label(text="Your custom templates:")
|
||||
row = layout.row()
|
||||
row.template_list("UI_UL_list", "custom_templates",
|
||||
self, "projects", self, "active_template_index")
|
||||
|
||||
col = row.column(align=True)
|
||||
col.menu("custom_templates.export_menu", icon='DOWNARROW_HLT', text="")
|
||||
col.separator()
|
||||
col.operator("custom_templates.add", icon='ADD', text="")
|
||||
if self.projects:
|
||||
col.operator("custom_templates.remove", icon='REMOVE', text="")
|
||||
col.separator()
|
||||
col.operator("custom_templates.move_up", icon='TRIA_UP', text="")
|
||||
col.operator("custom_templates.move_down", icon='TRIA_DOWN', text="")
|
||||
|
||||
if self.projects:
|
||||
project = self.projects[self.active_template_index]
|
||||
layout.prop(project, "name")
|
||||
layout.prop(project, "path")
|
||||
|
||||
layout.prop(self, "override_splash")
|
||||
box = layout.box()
|
||||
if self.override_splash and len(self.projects) == 0:
|
||||
box.label(text="There are currently no templates.")
|
||||
elif self.override_splash:
|
||||
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.")
|
||||
|
||||
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 CUSTOM_MT_Export_Menu(bpy.types.Menu):
|
||||
bl_idname = "custom_templates.export_menu"
|
||||
bl_label = "Import/Export custom templates"
|
||||
bl_description = "Allows you to save al load your custom templates (using json format)"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.operator("custom_templates.export_templates", text="Export templates")
|
||||
layout.operator("custom_templates.import_templates", text="Import templates")
|
||||
|
||||
class OT_ExportTemplates(bpy.types.Operator):
|
||||
bl_idname = "custom_templates.export_templates"
|
||||
bl_label = "Export custom templates"
|
||||
bl_description = "Export the current list of templates to JSON file"
|
||||
|
||||
filepath: StringProperty(subtype="FILE_PATH", description="Select the path for the exported file", default="custom_templates.json")
|
||||
|
||||
def execute(self, context):
|
||||
prefs = context.preferences.addons[base_package].preferences
|
||||
with open(self.filepath, 'w') as f:
|
||||
projects = [{"name": project.name, "path": project.path} for project in prefs.projects]
|
||||
json.dump(projects, f, indent=4)
|
||||
|
||||
self.report({'INFO'}, f"Templates exported to {self.filepath}")
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
class OT_ImportTemplates(bpy.types.Operator):
|
||||
bl_idname = "custom_templates.import_templates"
|
||||
bl_label = "Import custom templates"
|
||||
bl_description = "Import a list of templates from JSON file (note that this will override the current templates list)"
|
||||
|
||||
filepath: StringProperty(subtype="FILE_PATH", description="Select the .json file to load")
|
||||
|
||||
def execute(self, context):
|
||||
prefs = context.preferences.addons[base_package].preferences
|
||||
|
||||
with open(self.filepath, 'r') as f:
|
||||
projects = json.load(f)
|
||||
|
||||
prefs.projects.clear()
|
||||
for project in projects:
|
||||
item = prefs.projects.add()
|
||||
item.name = project["name"]
|
||||
item.path = project["path"]
|
||||
prefs.active_template_index = 0
|
||||
|
||||
self.report({'INFO'}, f"Projects imported from {self.filepath}")
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
# Templates list oeprators
|
||||
class OT_AddTemplateItem(Operator):
|
||||
bl_idname = "custom_templates.add"
|
||||
bl_label = "Add Template"
|
||||
bl_description = "Add new template"
|
||||
|
||||
def execute(self, context):
|
||||
prefs = context.preferences.addons[base_package].preferences
|
||||
prefs.projects.add()
|
||||
prefs.active_template_index = len(prefs.projects) - 1
|
||||
self.report({'INFO'}, f"Empty template added")
|
||||
return {'FINISHED'}
|
||||
|
||||
class OT_RemoveTemplateItem(Operator):
|
||||
bl_idname = "custom_templates.remove"
|
||||
bl_label = "Remove Template"
|
||||
bl_description = "Remove selected template"
|
||||
|
||||
def execute(self, context):
|
||||
prefs = context.preferences.addons[base_package].preferences
|
||||
self.report(
|
||||
{'INFO'}, f'Template "{prefs.projects[prefs.active_template_index].name}" removed{" (`"+prefs.projects[prefs.active_template_index].path+"`)" if prefs.projects[prefs.active_template_index].path != "" else "" }')
|
||||
prefs.projects.remove(prefs.active_template_index)
|
||||
prefs.active_template_index = min(
|
||||
max(0, prefs.active_template_index - 1), len(prefs.projects) - 1)
|
||||
return {'FINISHED'}
|
||||
|
||||
class OT_MoveUpTemplateItem(Operator):
|
||||
bl_idname = "custom_templates.move_up"
|
||||
bl_label = "Move Up"
|
||||
bl_description = "Move the selected template up in the list"
|
||||
|
||||
def execute(self, context):
|
||||
prefs = context.preferences.addons[base_package].preferences
|
||||
index = prefs.active_template_index
|
||||
|
||||
if index > 0:
|
||||
prefs.projects.move(index, index - 1)
|
||||
prefs.active_template_index -= 1
|
||||
self.report({'INFO'}, f"Templates list re-ordered")
|
||||
else:
|
||||
self.report({'WARNING'}, "Template is already at the top")
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
class OT_MoveDownTemplateItem(Operator):
|
||||
bl_idname = "custom_templates.move_down"
|
||||
bl_label = "Move Down"
|
||||
bl_description = "Move the selected template down in the list"
|
||||
|
||||
def execute(self, context):
|
||||
prefs = context.preferences.addons[base_package].preferences
|
||||
index = prefs.active_template_index
|
||||
|
||||
if index < len(prefs.projects) - 1:
|
||||
prefs.projects.move(index, index + 1)
|
||||
prefs.active_template_index += 1
|
||||
self.report({'INFO'}, f"Templates list re-ordered")
|
||||
else:
|
||||
self.report({'WARNING'}, "Template is already at the bottom")
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
# Popups of File > Defaults
|
||||
def check_if_present(self, prefs, path):
|
||||
for p in prefs.projects:
|
||||
if p.path == path:
|
||||
already_present = True
|
||||
self.report(
|
||||
{'WARNING'}, f'Current file is already in the templates list as "{p.name}".')
|
||||
return True
|
||||
return False
|
||||
|
||||
class OT_AddTemplatePopup(Operator):
|
||||
bl_idname = "custom_templates.add_template_popup"
|
||||
bl_label = "Use as template"
|
||||
bl_description = "Use the current .blend file to create a new template occurency"
|
||||
|
||||
project_name: StringProperty(name="Project Name")
|
||||
|
||||
def execute(self, context):
|
||||
prefs = context.preferences.addons[base_package].preferences
|
||||
current_file_path = bpy.data.filepath
|
||||
if check_if_present(self, prefs, current_file_path):
|
||||
return {'FINISHED'}
|
||||
if current_file_path:
|
||||
new_project = prefs.projects.add()
|
||||
new_project.name = self.project_name
|
||||
new_project.path = current_file_path
|
||||
self.report(
|
||||
{'INFO'}, f"Template '{self.project_name}' added successfully!")
|
||||
else:
|
||||
self.report({'ERROR'}, "Current file is not saved on disk.")
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
class OT_SelectTemplatePopup(Operator):
|
||||
bl_idname = "custom_templates.select_template_popup"
|
||||
bl_label = "Select a new custom template"
|
||||
bl_description = "Create a new template occurency by selecting an existing .blend file"
|
||||
|
||||
project_name: StringProperty(name="Project Name")
|
||||
project_path: StringProperty(name="Project Path", subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
prefs = context.preferences.addons[base_package].preferences
|
||||
if check_if_present(self, prefs, self.project_path):
|
||||
return {'FINISHED'}
|
||||
new_project = prefs.projects.add()
|
||||
new_project.name = self.project_name
|
||||
new_project.path = self.project_path
|
||||
self.report(
|
||||
{'INFO'}, f"Template '{self.project_name}' selected and added successfully!")
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
# Open Preferences
|
||||
class OT_OpenAddonPreferences(Operator):
|
||||
bl_idname = "custom_templates.open_preferences"
|
||||
bl_label = "Open Custom Templates Preferences"
|
||||
bl_description = "Open the preferences for the Custom Templates add-on"
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.screen.userpref_show('INVOKE_DEFAULT')
|
||||
context.preferences.active_section = 'ADDONS'
|
||||
context.window_manager.addon_search = "Custom Templates"
|
||||
return {'FINISHED'}
|
@ -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
|
||||
import bpy
|
||||
from .funcs import draw_templates, pref
|
||||
from .. import __package__ as base_package
|
||||
from .draw import draw_templates_on_col
|
||||
import bpy
|
||||
# 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 = pref()
|
||||
prefs = context.preferences.addons[base_package].preferences
|
||||
layout.operator_context = 'EXEC_DEFAULT'
|
||||
layout.emboss = 'PULLDOWN_MENU'
|
||||
split = layout.split()
|
||||
@ -24,13 +24,13 @@ class WM_MT_splash(bpy.types.Menu):
|
||||
if prefs.override_splash and len(prefs.projects) > 0:
|
||||
colA.label(text="Custom Templates")
|
||||
col1.operator_context = 'INVOKE_DEFAULT'
|
||||
draw_templates(col1, context, True)
|
||||
draw_templates_on_col(col1, context, True)
|
||||
else:
|
||||
# Call original code
|
||||
colA.label(text="New File")
|
||||
# Call original code
|
||||
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="")
|
||||
colB.menu("custom_templates.splash_mode", icon='DOWNARROW_HLT', text="")
|
||||
|
||||
# Recent
|
||||
col2 = split.column()
|
||||
@ -71,3 +71,44 @@ class WM_MT_splash(bpy.types.Menu):
|
||||
self.layout.label(text="Running in Offline Mode", icon='INTERNET_OFFLINE')
|
||||
|
||||
layout.separator()
|
||||
|
||||
class CUSTOM_MT_splash_mode(bpy.types.Menu):
|
||||
bl_idname = "custom_templates.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("custom_templates.open_preferences", text="Manage templates")
|
||||
layout.separator()
|
||||
layout.operator("custom_templates.splash_custom", text="Use Custom Templates", icon=ct_check)
|
||||
layout.operator("custom_templates.splash_default", text="Use Blender's Default", icon=def_check)
|
||||
|
||||
class OT_SplashDefault(bpy.types.Operator):
|
||||
bl_idname = "custom_templates.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
|
||||
return {'FINISHED'}
|
||||
|
||||
class OT_SplashCustom(bpy.types.Operator):
|
||||
bl_idname = "custom_templates.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
|
||||
return {'FINISHED'}
|
||||
|
@ -1,77 +0,0 @@
|
||||
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
|
||||
if has_templates():
|
||||
layout.separator()
|
||||
draw_templates(layout, context)
|
||||
|
||||
def draw_templates(layout, context, splash_mode=False):
|
||||
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
|
||||
layout.separator()
|
||||
layout.operator("ct.open_preferences",
|
||||
text="Manage templates")
|
||||
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")
|
||||
layout.separator()
|
||||
layout.operator("ct.select_template_popup",
|
||||
text="Select new template")
|
||||
if bpy.data.filepath != "":
|
||||
layout.operator("ct.add_template_popup",
|
||||
text="Use current file as template")
|
||||
|
||||
def draw_ws_menu_add(self, context):
|
||||
layout = self.layout
|
||||
if has_templates():
|
||||
layout.separator()
|
||||
layout.menu("CT_MT_workspace_add", text="Add from Custom Templates", icon="WORKSPACE")
|
@ -1,60 +0,0 @@
|
||||
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
|
328
addon/src/ops.py
@ -1,328 +0,0 @@
|
||||
import os
|
||||
import bpy
|
||||
import json
|
||||
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"
|
||||
bl_label = "Export custom templates"
|
||||
bl_description = "Export the current list of templates to JSON file"
|
||||
|
||||
filepath: StringProperty(
|
||||
subtype="FILE_PATH", description="Select the path for the exported file", default="templates.json")
|
||||
|
||||
def execute(self, context):
|
||||
prefs = pref()
|
||||
with open(self.filepath, 'w') as f:
|
||||
projects = [{"name": project.name, "path": project.path}
|
||||
for project in prefs.projects]
|
||||
json.dump(projects, f, indent=4)
|
||||
|
||||
self.report({'INFO'}, f"Templates exported to {self.filepath}")
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return has_templates()
|
||||
|
||||
class CT_OT_import_templates(Operator):
|
||||
bl_idname = "ct.import_templates"
|
||||
bl_label = "Import custom templates"
|
||||
bl_description = "Import a list of templates from JSON file (note that this will override the current templates list)"
|
||||
|
||||
filepath: StringProperty(
|
||||
subtype="FILE_PATH", description="Select the .json file to load")
|
||||
|
||||
def execute(self, context):
|
||||
prefs = pref()
|
||||
|
||||
if os.path.exists(self.filepath):
|
||||
with open(self.filepath, 'r') as f:
|
||||
projects = json.load(f)
|
||||
|
||||
prefs.projects.clear()
|
||||
for project in projects:
|
||||
item = prefs.projects.add()
|
||||
item.name = project["name"]
|
||||
item.path = project["path"]
|
||||
prefs.active_template_index = 0
|
||||
context.preferences.is_dirty = True
|
||||
|
||||
self.report({'INFO'}, f"Projects imported from {self.filepath}")
|
||||
else:
|
||||
self.report(
|
||||
{'WARNING'}, f"Import cancelled: path not found ({self.filepath})")
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
class CT_OT_add(Operator):
|
||||
bl_idname = "ct.add"
|
||||
bl_label = "Add Template"
|
||||
bl_description = "Add new template"
|
||||
|
||||
def execute(self, context):
|
||||
prefs = pref()
|
||||
prefs.projects.add()
|
||||
prefs.active_template_index = len(prefs.projects) - 1
|
||||
context.preferences.is_dirty = True
|
||||
return {'FINISHED'}
|
||||
|
||||
class CT_OT_remove(Operator):
|
||||
bl_idname = "ct.remove"
|
||||
bl_label = "Remove Template"
|
||||
bl_description = "Remove selected template"
|
||||
|
||||
def execute(self, context):
|
||||
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)
|
||||
context.preferences.is_dirty = True
|
||||
return {'FINISHED'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return has_templates()
|
||||
|
||||
class CT_OT_move_up(Operator):
|
||||
bl_idname = "ct.move_up"
|
||||
bl_label = "Move Up"
|
||||
bl_description = "Move the selected template up in the list"
|
||||
|
||||
def execute(self, context):
|
||||
prefs = pref()
|
||||
index = prefs.active_template_index
|
||||
|
||||
if index > 0:
|
||||
prefs.projects.move(index, index - 1)
|
||||
prefs.active_template_index -= 1
|
||||
context.preferences.is_dirty = True
|
||||
else:
|
||||
self.report({'WARNING'}, "Template is already at the top")
|
||||
return {'FINISHED'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return has_templates()
|
||||
|
||||
class CT_OT_move_down(Operator):
|
||||
bl_idname = "ct.move_down"
|
||||
bl_label = "Move Down"
|
||||
bl_description = "Move the selected template down in the list"
|
||||
|
||||
def execute(self, context):
|
||||
prefs = pref()
|
||||
index = prefs.active_template_index
|
||||
|
||||
if index < len(prefs.projects) - 1:
|
||||
prefs.projects.move(index, index + 1)
|
||||
prefs.active_template_index += 1
|
||||
context.preferences.is_dirty = True
|
||||
else:
|
||||
self.report({'WARNING'}, "Template is already at the bottom")
|
||||
return {'FINISHED'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return has_templates()
|
||||
|
||||
class CT_OT_clear(Operator):
|
||||
bl_idname = "ct.clear"
|
||||
bl_label = "Clear Templates"
|
||||
bl_description = "Clear the current list of templates (remove all templates)"
|
||||
|
||||
def execute(self, context):
|
||||
prefs = pref()
|
||||
prefs.projects.clear()
|
||||
prefs.active_template_index = 0
|
||||
context.preferences.is_dirty = True
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return has_templates()
|
||||
|
||||
class CT_OT_add_template_popup(Operator):
|
||||
bl_idname = "ct.add_template_popup"
|
||||
bl_label = "Use as template"
|
||||
bl_description = "Use the current .blend file to create a new template occurency"
|
||||
|
||||
name: StringProperty(name="Project Name")
|
||||
|
||||
def execute(self, context):
|
||||
prefs = pref()
|
||||
if not already_present(self, prefs, bpy.data.filepath):
|
||||
if bpy.data.filepath:
|
||||
new_project = prefs.projects.add()
|
||||
new_project.name = self.name
|
||||
new_project.path = bpy.data.filepath
|
||||
self.name = ''
|
||||
context.preferences.is_dirty = True
|
||||
else:
|
||||
self.report({'ERROR'}, "Current file is not saved on disk.")
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
if bpy.data.filepath:
|
||||
self.name = name_from_path(bpy.data.filepath)
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
class CT_OT_select_template_popup(Operator):
|
||||
bl_idname = "ct.select_template_popup"
|
||||
bl_label = "Select a new custom template"
|
||||
bl_description = "Create a new template by selecting an existing .blend file"
|
||||
|
||||
path: StringProperty(name="Template Path",
|
||||
subtype="FILE_PATH", update=on_path_update)
|
||||
name: StringProperty(name="Template Name")
|
||||
|
||||
def execute(self, context):
|
||||
prefs = pref()
|
||||
if not already_present(self, prefs, self.path):
|
||||
template = prefs.projects.add()
|
||||
template.name = self.name
|
||||
template.path = self.path
|
||||
context.preferences.is_dirty = True
|
||||
self.name = ''
|
||||
self.path = ''
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
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"
|
||||
|
||||
directory: StringProperty(
|
||||
subtype="DIR_PATH", description="Select the folder containing .blend files")
|
||||
depth: IntProperty(
|
||||
name="Depth", description="Depth of recursion (default 1)", default=1, min=1)
|
||||
|
||||
def execute(self, context):
|
||||
prefs = pref()
|
||||
|
||||
if os.path.exists(self.directory):
|
||||
blend_files = []
|
||||
for root, dirs, files in os.walk(self.directory):
|
||||
# Calculate the current depth
|
||||
current_depth = root[len(self.directory):].count(os.sep)
|
||||
if current_depth < self.depth:
|
||||
for file in files:
|
||||
if file.endswith('.blend'):
|
||||
blend_files.append(os.path.join(root, file))
|
||||
|
||||
if not blend_files:
|
||||
self.report(
|
||||
{'WARNING'}, "No .blend files found in the specified directory")
|
||||
return {'CANCELLED'}
|
||||
new_t = 0
|
||||
for path in blend_files:
|
||||
if not already_present(self, prefs, path, False):
|
||||
new_t += 1
|
||||
item = prefs.projects.add()
|
||||
item.name = name_from_path(path)
|
||||
item.path = path
|
||||
|
||||
context.preferences.is_dirty = True
|
||||
self.report(
|
||||
{'INFO'}, f"{new_t} new templates added from {self.directory} ({len(blend_files) - new_t} already present)")
|
||||
else:
|
||||
self.report(
|
||||
{'WARNING'}, f"Import cancelled: directory not found ({self.directory})")
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
class CT_OT_open_preferences(Operator):
|
||||
bl_idname = "ct.open_preferences"
|
||||
bl_label = "Open Custom Templates Preferences"
|
||||
bl_description = "Open the preferences for the Custom Templates add-on"
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.screen.userpref_show('INVOKE_DEFAULT')
|
||||
context.preferences.active_section = 'ADDONS'
|
||||
context.window_manager.addon_search = "Custom Templates"
|
||||
return {'FINISHED'}
|
||||
|
||||
class CT_OT_add_workspace(Operator):
|
||||
bl_idname = "ct.add_workspace"
|
||||
bl_label = "Add this workspace from your template"
|
||||
bl_description = "Add to the current project, the selected workspace from your Custom Template"
|
||||
|
||||
workspace: StringProperty(name="workspace")
|
||||
path: StringProperty(name="path")
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.workspace.append_activate(idname=self.workspace, filepath=self.path)
|
||||
return {'FINISHED'}
|
||||
|
||||
class CT_OT_template_workspaces(Operator):
|
||||
bl_idname = "ct.template_workspaces"
|
||||
bl_label = "Add workspace from this template"
|
||||
bl_description = "Click to select one of the workspaces from this Custom Template"
|
||||
|
||||
index: IntProperty(name='index', default=0)
|
||||
|
||||
def draw_ws(self, s, context):
|
||||
layout = s.layout
|
||||
template = 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)
|
||||
op.workspace = w
|
||||
op.path = template.path
|
||||
|
||||
def execute(self, context):
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
template = pref().projects[self.index]
|
||||
wm.popup_menu(self.draw_ws, title=f"Workspaces from '{template.name}'", icon="ADD")
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
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)"
|
||||
|
||||
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'}
|
@ -1,60 +0,0 @@
|
||||
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")
|
@ -1,27 +0,0 @@
|
||||
# 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!
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 77 KiB |
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 1.4 MiB |
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.2 MiB |
@ -1,78 +0,0 @@
|
||||
# Custom Templates - Blender Add-On
|
||||
# Copyright (C) 2024 Francesco Bellini
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# 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 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_MT_workspace_add,
|
||||
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,
|
||||
CT_OT_remove,
|
||||
CT_OT_clear,
|
||||
CT_OT_add_template_popup,
|
||||
CT_OT_select_template_popup,
|
||||
CT_OT_add_templates_from_folder,
|
||||
TemplateItem,
|
||||
CustomTemplatesPreferences]
|
||||
|
||||
og_splash = None
|
||||
|
||||
def register():
|
||||
global og_splash
|
||||
og_splash = bpy.types.WM_MT_splash
|
||||
for c in classes:
|
||||
bpy.utils.register_class(c)
|
||||
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)
|
||||
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()
|
@ -1,77 +0,0 @@
|
||||
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
|
||||
if has_templates():
|
||||
layout.separator()
|
||||
draw_templates(layout, context)
|
||||
|
||||
def draw_templates(layout, context, splash_mode=False):
|
||||
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
|
||||
layout.separator()
|
||||
layout.operator("ct.open_preferences",
|
||||
text="Manage templates")
|
||||
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")
|
||||
layout.separator()
|
||||
layout.operator("ct.select_template_popup",
|
||||
text="Select new template")
|
||||
if bpy.data.filepath != "":
|
||||
layout.operator("ct.add_template_popup",
|
||||
text="Use current file as template")
|
||||
|
||||
def draw_ws_menu_add(self, context):
|
||||
layout = self.layout
|
||||
if has_templates():
|
||||
layout.separator()
|
||||
layout.menu("CT_MT_workspace_add", text="Add from Custom Templates", icon="WORKSPACE")
|
@ -1,60 +0,0 @@
|
||||
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
|
@ -1,328 +0,0 @@
|
||||
import os
|
||||
import bpy
|
||||
import json
|
||||
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"
|
||||
bl_label = "Export custom templates"
|
||||
bl_description = "Export the current list of templates to JSON file"
|
||||
|
||||
filepath: StringProperty(
|
||||
subtype="FILE_PATH", description="Select the path for the exported file", default="templates.json")
|
||||
|
||||
def execute(self, context):
|
||||
prefs = pref()
|
||||
with open(self.filepath, 'w') as f:
|
||||
projects = [{"name": project.name, "path": project.path}
|
||||
for project in prefs.projects]
|
||||
json.dump(projects, f, indent=4)
|
||||
|
||||
self.report({'INFO'}, f"Templates exported to {self.filepath}")
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return has_templates()
|
||||
|
||||
class CT_OT_import_templates(Operator):
|
||||
bl_idname = "ct.import_templates"
|
||||
bl_label = "Import custom templates"
|
||||
bl_description = "Import a list of templates from JSON file (note that this will override the current templates list)"
|
||||
|
||||
filepath: StringProperty(
|
||||
subtype="FILE_PATH", description="Select the .json file to load")
|
||||
|
||||
def execute(self, context):
|
||||
prefs = pref()
|
||||
|
||||
if os.path.exists(self.filepath):
|
||||
with open(self.filepath, 'r') as f:
|
||||
projects = json.load(f)
|
||||
|
||||
prefs.projects.clear()
|
||||
for project in projects:
|
||||
item = prefs.projects.add()
|
||||
item.name = project["name"]
|
||||
item.path = project["path"]
|
||||
prefs.active_template_index = 0
|
||||
context.preferences.is_dirty = True
|
||||
|
||||
self.report({'INFO'}, f"Projects imported from {self.filepath}")
|
||||
else:
|
||||
self.report(
|
||||
{'WARNING'}, f"Import cancelled: path not found ({self.filepath})")
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
class CT_OT_add(Operator):
|
||||
bl_idname = "ct.add"
|
||||
bl_label = "Add Template"
|
||||
bl_description = "Add new template"
|
||||
|
||||
def execute(self, context):
|
||||
prefs = pref()
|
||||
prefs.projects.add()
|
||||
prefs.active_template_index = len(prefs.projects) - 1
|
||||
context.preferences.is_dirty = True
|
||||
return {'FINISHED'}
|
||||
|
||||
class CT_OT_remove(Operator):
|
||||
bl_idname = "ct.remove"
|
||||
bl_label = "Remove Template"
|
||||
bl_description = "Remove selected template"
|
||||
|
||||
def execute(self, context):
|
||||
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)
|
||||
context.preferences.is_dirty = True
|
||||
return {'FINISHED'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return has_templates()
|
||||
|
||||
class CT_OT_move_up(Operator):
|
||||
bl_idname = "ct.move_up"
|
||||
bl_label = "Move Up"
|
||||
bl_description = "Move the selected template up in the list"
|
||||
|
||||
def execute(self, context):
|
||||
prefs = pref()
|
||||
index = prefs.active_template_index
|
||||
|
||||
if index > 0:
|
||||
prefs.projects.move(index, index - 1)
|
||||
prefs.active_template_index -= 1
|
||||
context.preferences.is_dirty = True
|
||||
else:
|
||||
self.report({'WARNING'}, "Template is already at the top")
|
||||
return {'FINISHED'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return has_templates()
|
||||
|
||||
class CT_OT_move_down(Operator):
|
||||
bl_idname = "ct.move_down"
|
||||
bl_label = "Move Down"
|
||||
bl_description = "Move the selected template down in the list"
|
||||
|
||||
def execute(self, context):
|
||||
prefs = pref()
|
||||
index = prefs.active_template_index
|
||||
|
||||
if index < len(prefs.projects) - 1:
|
||||
prefs.projects.move(index, index + 1)
|
||||
prefs.active_template_index += 1
|
||||
context.preferences.is_dirty = True
|
||||
else:
|
||||
self.report({'WARNING'}, "Template is already at the bottom")
|
||||
return {'FINISHED'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return has_templates()
|
||||
|
||||
class CT_OT_clear(Operator):
|
||||
bl_idname = "ct.clear"
|
||||
bl_label = "Clear Templates"
|
||||
bl_description = "Clear the current list of templates (remove all templates)"
|
||||
|
||||
def execute(self, context):
|
||||
prefs = pref()
|
||||
prefs.projects.clear()
|
||||
prefs.active_template_index = 0
|
||||
context.preferences.is_dirty = True
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return has_templates()
|
||||
|
||||
class CT_OT_add_template_popup(Operator):
|
||||
bl_idname = "ct.add_template_popup"
|
||||
bl_label = "Use as template"
|
||||
bl_description = "Use the current .blend file to create a new template occurency"
|
||||
|
||||
name: StringProperty(name="Project Name")
|
||||
|
||||
def execute(self, context):
|
||||
prefs = pref()
|
||||
if not already_present(self, prefs, bpy.data.filepath):
|
||||
if bpy.data.filepath:
|
||||
new_project = prefs.projects.add()
|
||||
new_project.name = self.name
|
||||
new_project.path = bpy.data.filepath
|
||||
self.name = ''
|
||||
context.preferences.is_dirty = True
|
||||
else:
|
||||
self.report({'ERROR'}, "Current file is not saved on disk.")
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
if bpy.data.filepath:
|
||||
self.name = name_from_path(bpy.data.filepath)
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
class CT_OT_select_template_popup(Operator):
|
||||
bl_idname = "ct.select_template_popup"
|
||||
bl_label = "Select a new custom template"
|
||||
bl_description = "Create a new template by selecting an existing .blend file"
|
||||
|
||||
path: StringProperty(name="Template Path",
|
||||
subtype="FILE_PATH", update=on_path_update)
|
||||
name: StringProperty(name="Template Name")
|
||||
|
||||
def execute(self, context):
|
||||
prefs = pref()
|
||||
if not already_present(self, prefs, self.path):
|
||||
template = prefs.projects.add()
|
||||
template.name = self.name
|
||||
template.path = self.path
|
||||
context.preferences.is_dirty = True
|
||||
self.name = ''
|
||||
self.path = ''
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
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"
|
||||
|
||||
directory: StringProperty(
|
||||
subtype="DIR_PATH", description="Select the folder containing .blend files")
|
||||
depth: IntProperty(
|
||||
name="Depth", description="Depth of recursion (default 1)", default=1, min=1)
|
||||
|
||||
def execute(self, context):
|
||||
prefs = pref()
|
||||
|
||||
if os.path.exists(self.directory):
|
||||
blend_files = []
|
||||
for root, dirs, files in os.walk(self.directory):
|
||||
# Calculate the current depth
|
||||
current_depth = root[len(self.directory):].count(os.sep)
|
||||
if current_depth < self.depth:
|
||||
for file in files:
|
||||
if file.endswith('.blend'):
|
||||
blend_files.append(os.path.join(root, file))
|
||||
|
||||
if not blend_files:
|
||||
self.report(
|
||||
{'WARNING'}, "No .blend files found in the specified directory")
|
||||
return {'CANCELLED'}
|
||||
new_t = 0
|
||||
for path in blend_files:
|
||||
if not already_present(self, prefs, path, False):
|
||||
new_t += 1
|
||||
item = prefs.projects.add()
|
||||
item.name = name_from_path(path)
|
||||
item.path = path
|
||||
|
||||
context.preferences.is_dirty = True
|
||||
self.report(
|
||||
{'INFO'}, f"{new_t} new templates added from {self.directory} ({len(blend_files) - new_t} already present)")
|
||||
else:
|
||||
self.report(
|
||||
{'WARNING'}, f"Import cancelled: directory not found ({self.directory})")
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
class CT_OT_open_preferences(Operator):
|
||||
bl_idname = "ct.open_preferences"
|
||||
bl_label = "Open Custom Templates Preferences"
|
||||
bl_description = "Open the preferences for the Custom Templates add-on"
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.screen.userpref_show('INVOKE_DEFAULT')
|
||||
context.preferences.active_section = 'ADDONS'
|
||||
context.window_manager.addon_search = "Custom Templates"
|
||||
return {'FINISHED'}
|
||||
|
||||
class CT_OT_add_workspace(Operator):
|
||||
bl_idname = "ct.add_workspace"
|
||||
bl_label = "Add this workspace from your template"
|
||||
bl_description = "Add to the current project, the selected workspace from your Custom Template"
|
||||
|
||||
workspace: StringProperty(name="workspace")
|
||||
path: StringProperty(name="path")
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.workspace.append_activate(idname=self.workspace, filepath=self.path)
|
||||
return {'FINISHED'}
|
||||
|
||||
class CT_OT_template_workspaces(Operator):
|
||||
bl_idname = "ct.template_workspaces"
|
||||
bl_label = "Add workspace from this template"
|
||||
bl_description = "Click to select one of the workspaces from this Custom Template"
|
||||
|
||||
index: IntProperty(name='index', default=0)
|
||||
|
||||
def draw_ws(self, s, context):
|
||||
layout = s.layout
|
||||
template = 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)
|
||||
op.workspace = w
|
||||
op.path = template.path
|
||||
|
||||
def execute(self, context):
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
template = pref().projects[self.index]
|
||||
wm.popup_menu(self.draw_ws, title=f"Workspaces from '{template.name}'", icon="ADD")
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
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)"
|
||||
|
||||
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'}
|
@ -1,60 +0,0 @@
|
||||
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")
|
@ -1,69 +0,0 @@
|
||||
# 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
|
||||
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.
|
||||
# If you have no custom templates, the default templates are displayed
|
||||
class WM_MT_splash(bpy.types.Menu):
|
||||
bl_label = "Splash"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
prefs = context.preferences.addons[base_package].preferences
|
||||
layout.operator_context = 'EXEC_DEFAULT'
|
||||
layout.emboss = 'PULLDOWN_MENU'
|
||||
|
||||
split = layout.split()
|
||||
|
||||
# Templates
|
||||
colx = split.column()
|
||||
ct_split = colx.split(factor=0.9)
|
||||
colA = ct_split.column()
|
||||
if prefs.override_splash and len(prefs.projects) > 0:
|
||||
colA.label(text="Custom Templates")
|
||||
colx.operator_context = 'INVOKE_DEFAULT'
|
||||
draw_templates(colx, context, True)
|
||||
else:
|
||||
# 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="")
|
||||
|
||||
# Recent
|
||||
col2 = split.column()
|
||||
col2_title = col2.row()
|
||||
|
||||
found_recent = col2.template_recent_files()
|
||||
|
||||
if found_recent:
|
||||
col2_title.label(text="Recent Files")
|
||||
else:
|
||||
|
||||
# Links if no recent files
|
||||
col2_title.label(text="Getting Started")
|
||||
|
||||
col2.operator("wm.url_open_preset", text="Manual", icon='URL').type = 'MANUAL'
|
||||
col2.operator("wm.url_open_preset", text="Blender Website", icon='URL').type = 'BLENDER'
|
||||
col2.operator("wm.url_open_preset", text="Credits", icon='URL').type = 'CREDITS'
|
||||
|
||||
layout.separator()
|
||||
|
||||
split = layout.split()
|
||||
|
||||
col1 = split.column()
|
||||
sub = col1.row()
|
||||
sub.operator_context = 'INVOKE_DEFAULT'
|
||||
sub.operator("wm.open_mainfile", text="Open...", icon='FILE_FOLDER')
|
||||
col1.operator("wm.recover_last_session", icon='RECOVER_LAST')
|
||||
|
||||
col2 = split.column()
|
||||
|
||||
col2.operator("wm.url_open_preset", text="Release Notes", icon='URL').type = 'RELEASE_NOTES'
|
||||
col2.operator("wm.url_open_preset", text="Development Fund", icon='FUND').type = 'FUND'
|
||||
|
||||
layout.separator()
|
||||
layout.separator()
|
@ -1 +0,0 @@
|
||||
zip -9 -r ./releases/1.x.x/legacy/custom-templates-legacy-1.5.0.zip ./legacy/
|