Compare commits
95 Commits
Author | SHA1 | Date | |
---|---|---|---|
04ffbc52a2 | |||
215aba3866 | |||
70709ef27c | |||
65695286a5 | |||
af0c68a09c | |||
3c9ac708a9 | |||
a8c30de813 | |||
05e8eab320 | |||
06bf637da9 | |||
7636f7611b | |||
5fb7fa1a9b | |||
![]() |
c4878e1209 | ||
![]() |
52650546b5 | ||
![]() |
78d25f4682 | ||
![]() |
1360818b57 | ||
![]() |
181cb5cff9 | ||
![]() |
6f1bf8be6d | ||
![]() |
38947bbc7d | ||
![]() |
ae3e42263e | ||
![]() |
3c2e31948e | ||
![]() |
bec320f707 | ||
![]() |
600efc1ea6 | ||
![]() |
28bc4dac8f | ||
079487299b | |||
b79f008d23 | |||
125c9d6b91 | |||
0ab6f0b24d | |||
706b86a975 | |||
2f24258c06 | |||
7f8a074ccf | |||
a867ebb73d | |||
2965688087 | |||
![]() |
3fc8da77d2 | ||
![]() |
28ed1a909a | ||
![]() |
0476c641ea | ||
![]() |
2580b0aae4 | ||
![]() |
477d526443 | ||
![]() |
a7ffba8082 | ||
![]() |
f80b32a127 | ||
![]() |
c85b88d939 | ||
![]() |
2fdb853b8c | ||
![]() |
f372dd05c8 | ||
![]() |
ae8fd3c3d2 | ||
![]() |
45fc4ebf78 | ||
![]() |
dcc0a75c97 | ||
![]() |
929f2f5e26 | ||
![]() |
090b6f7775 | ||
![]() |
439aac2702 | ||
![]() |
814b755bb7 | ||
![]() |
e71ab3ecba | ||
![]() |
119717bfbb | ||
![]() |
ad2b549ea2 | ||
![]() |
eb8b4aac82 | ||
![]() |
a0203dc56a | ||
![]() |
727b535cba | ||
![]() |
98aa91d3e5 | ||
![]() |
6b87e447d0 | ||
![]() |
55ceb10701 | ||
![]() |
a911ba7994 | ||
![]() |
68e7b18fb2 | ||
![]() |
06bde7a6b3 | ||
![]() |
2992585cd5 | ||
![]() |
ba9e026291 | ||
![]() |
f6c8730062 | ||
fb3898c25f | |||
8e7c93a829 | |||
b8289b9ac3 | |||
31fa1c2b4d | |||
5a4a336e5a | |||
78c384c850 | |||
3dc5dca176 | |||
b79c7862b1 | |||
7fb69dcf9c | |||
b0ced5013a | |||
2a60a876bf | |||
c9cb3c576b | |||
bfcbd2e266 | |||
2f0eaaef13 | |||
bab5a57099 | |||
d6fa3dd76b | |||
d3ed9be3b9 | |||
a906c8b948 | |||
797e720fa0 | |||
12d173b2ce | |||
2531be905c | |||
2ce9613e74 | |||
db42669de1 | |||
ec3af34472 | |||
47cd466a6d | |||
f7eea625fd | |||
8f5034fbf6 | |||
56daf65b80 | |||
b5d9bdb9f0 | |||
ab648889dc | |||
df63af65b1 |
2
.gitignore
vendored
@ -1 +1,3 @@
|
|||||||
*.xcf
|
*.xcf
|
||||||
|
*.blend*
|
||||||
|
/images/utils/**
|
||||||
|
22
CHANGELOG
@ -1,22 +0,0 @@
|
|||||||
## 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
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
## 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)
|
51
README.md
@ -1,51 +1,28 @@
|
|||||||
# Custom Templates - Add-On
|
# Custom Templates
|
||||||
|
|
||||||
### Have you ever wanted to customize new projects?
|
## The Freedom to *Template*
|
||||||
|
|
||||||
Custom Templates Add-On 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.
|
||||||
|
|
||||||

|
---
|
||||||
|
|
||||||
### Why this add-on?
|
- [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)
|
||||||
|
|
||||||
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.
|
Your templates will be avaliable in the Splash Screen and the `File > New` menu.
|
||||||
|
|
||||||
### Preferences
|
### Changelog
|
||||||
|
|
||||||
In the add-on preferences, you can manage the list of your custom template files.
|
Visit the [CHANGELOG](./CHANGELOG.md) file to see the changes over new version.
|
||||||
|
|
||||||
You can add, remove and reorder templates.
|
|
||||||
|
|
||||||
Each template has it's Name, and Path to the .blend file.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
*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.*
|
|
||||||
|
|
||||||
### How to use
|
|
||||||
|
|
||||||
You can always open the add-on preferences using the button `File > Defaults > Manage templates`.
|
|
||||||
|
|
||||||
Instead of using preferences, you can select a new template file using `File > Defaults > Select new custom template`, or, if you have a .blend file open, you can use `File > Defaults > Use current as new template`.
|
|
||||||
*This functions will open a popup where you can also set the Name for the new template*
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### 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
|
### Copyright
|
||||||
|
|
||||||
This add-on is developed under GPL-3.0 license by Francesco Bellini from 2024.
|
This add-on is developed under GPL-3.0 license by Francesco Bellini from 2024.
|
||||||
*See the [LICENSE](./LICENSE) file.*
|
*See the [LICENSE](./LICENSE) file.*
|
||||||
|
|
||||||
#### Changelog
|
|
||||||
|
|
||||||
Visit the [CHANGELOG](./CHANGELOG) file to se the changes over new version.
|
|
||||||
|
53
README_EXT.md
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# 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
|
@ -13,267 +13,65 @@
|
|||||||
|
|
||||||
# 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 os
|
|
||||||
import bpy
|
import bpy
|
||||||
from bpy.types import AddonPreferences, PropertyGroup, Operator
|
from bpy.types import TOPBAR_MT_file_new, TOPBAR_MT_file_defaults, TOPBAR_MT_workspace_menu
|
||||||
from bpy.props import CollectionProperty, IntProperty, StringProperty
|
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 = {
|
bl_info = {
|
||||||
"id": "custom_templates",
|
"id": "custom_templates",
|
||||||
"name": "Custom Templates",
|
"name": "Custom Templates",
|
||||||
"tagline": "Add your own .blend files as template options for new projects",
|
"tagline": "Add your own .blend files as template options for new projects",
|
||||||
"blender": (4, 2, 0),
|
"blender": (4, 2, 0),
|
||||||
"location": "File > New & File > Defaults",
|
"location": "File > New, File > Defaults, Splash Screen",
|
||||||
"category": "System",
|
"category": "System",
|
||||||
"support": "COMMUNITY",
|
"support": "COMMUNITY",
|
||||||
"blender_manifest": "blender_manifest.toml"
|
"blender_manifest": "blender_manifest.toml"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
classes = [WM_MT_splash,
|
||||||
|
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]
|
||||||
|
|
||||||
class TemplateItem(PropertyGroup):
|
og_splash = None
|
||||||
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 OT_SelectTemplatePopup(Operator):
|
|
||||||
bl_idname = "wm.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[__package__].preferences
|
|
||||||
for p in prefs.projects:
|
|
||||||
if p.path == self.project_path:
|
|
||||||
already_present = True
|
|
||||||
self.report(
|
|
||||||
{'WARNING'}, f'Selected file is already in the templates list as "{p.name}".')
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
class OT_AddTemplatePopup(Operator):
|
|
||||||
bl_idname = "wm.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[__package__].preferences
|
|
||||||
current_file_path = bpy.data.filepath
|
|
||||||
for p in prefs.projects:
|
|
||||||
if p.path == current_file_path:
|
|
||||||
already_present = True
|
|
||||||
self.report(
|
|
||||||
{'WARNING'}, f'Current file is already in the templates list as "{p.name}".')
|
|
||||||
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_AddTemplateItem(Operator):
|
|
||||||
bl_idname = "custom_templates.add"
|
|
||||||
bl_label = "Add Template"
|
|
||||||
bl_description = "Add new template"
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
prefs = context.preferences.addons[__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[__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(bpy.types.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[__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(bpy.types.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[__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'}
|
|
||||||
|
|
||||||
class OT_OpenAddonPreferences(bpy.types.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'}
|
|
||||||
|
|
||||||
# Add-On Preferences
|
|
||||||
class CustomTemplatesPreferences(AddonPreferences):
|
|
||||||
bl_idname = __package__
|
|
||||||
|
|
||||||
projects: CollectionProperty(type=TemplateItem)
|
|
||||||
active_template_index: IntProperty(
|
|
||||||
description="Index of the selected template")
|
|
||||||
|
|
||||||
def draw(self, context):
|
|
||||||
layout = self.layout
|
|
||||||
|
|
||||||
layout.label(
|
|
||||||
text="Here you can setup your own .blend files that will be shown in the `File > New` menu.")
|
|
||||||
layout.label(
|
|
||||||
text="You can also use `File > Defaults > Select new custom template` and `File > Defaults > Use current as new template` to update this preferences.")
|
|
||||||
|
|
||||||
row = layout.row()
|
|
||||||
row.template_list("UI_UL_list", "custom_templates",
|
|
||||||
self, "projects", self, "active_template_index")
|
|
||||||
|
|
||||||
col = row.column(align=True)
|
|
||||||
col.operator("custom_templates.add", icon='ADD', text="")
|
|
||||||
col.operator("custom_templates.remove", icon='REMOVE', text="")
|
|
||||||
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")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def draw_addon_separator(layout):
|
|
||||||
layout.separator()
|
|
||||||
layout.label(text="Custom Templates")
|
|
||||||
layout.separator()
|
|
||||||
|
|
||||||
|
|
||||||
def draw_new_menu(self, context):
|
|
||||||
layout = self.layout
|
|
||||||
prefs = context.preferences.addons[__package__].preferences
|
|
||||||
|
|
||||||
if len(prefs.projects) > 0:
|
|
||||||
draw_addon_separator(layout)
|
|
||||||
|
|
||||||
for project in prefs.projects:
|
|
||||||
layout.operator(
|
|
||||||
"wm.read_homefile", text=project.name).filepath = project.path
|
|
||||||
|
|
||||||
|
|
||||||
# File > Defaults > Add-On Buttons
|
|
||||||
def draw_add_template(self, context):
|
|
||||||
layout = self.layout
|
|
||||||
|
|
||||||
draw_addon_separator(layout)
|
|
||||||
|
|
||||||
# Manage Template
|
|
||||||
layout.operator("custom_templates.open_preferences",
|
|
||||||
text="Manage templates")
|
|
||||||
# Select new custom template
|
|
||||||
layout.operator("wm.select_template_popup",
|
|
||||||
text="Select new custom template")
|
|
||||||
if bpy.data.filepath != "":
|
|
||||||
# Use current as new template (only with an active saved .blend file opened)
|
|
||||||
layout.operator("wm.add_template_popup",
|
|
||||||
text="Use current as new template")
|
|
||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
bpy.utils.register_class(TemplateItem)
|
global og_splash
|
||||||
bpy.utils.register_class(OT_MoveUpTemplateItem)
|
og_splash = bpy.types.WM_MT_splash
|
||||||
bpy.utils.register_class(OT_MoveDownTemplateItem)
|
for c in classes:
|
||||||
bpy.utils.register_class(OT_AddTemplateItem)
|
bpy.utils.register_class(c)
|
||||||
bpy.utils.register_class(OT_RemoveTemplateItem)
|
TOPBAR_MT_file_new.append(draw_file_new_templates)
|
||||||
bpy.utils.register_class(OT_AddTemplatePopup)
|
TOPBAR_MT_file_defaults.append(draw_file_default_operators)
|
||||||
bpy.utils.register_class(OT_SelectTemplatePopup)
|
TOPBAR_MT_workspace_menu.append(draw_ws_menu_add)
|
||||||
bpy.utils.register_class(OT_OpenAddonPreferences)
|
|
||||||
bpy.utils.register_class(CustomTemplatesPreferences)
|
|
||||||
bpy.types.TOPBAR_MT_file_new.remove(draw_new_menu)
|
|
||||||
bpy.types.TOPBAR_MT_file_new.remove(draw_add_template)
|
|
||||||
bpy.types.TOPBAR_MT_file_new.append(draw_new_menu)
|
|
||||||
bpy.types.TOPBAR_MT_file_defaults.append(draw_add_template)
|
|
||||||
|
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
bpy.utils.unregister_class(CustomTemplatesPreferences)
|
for c in reversed(classes):
|
||||||
bpy.utils.unregister_class(OT_MoveUpTemplateItem)
|
bpy.utils.unregister_class(c)
|
||||||
bpy.utils.unregister_class(OT_MoveDownTemplateItem)
|
bpy.utils.register_class(og_splash)
|
||||||
bpy.utils.unregister_class(OT_AddTemplateItem)
|
TOPBAR_MT_file_new.remove(draw_file_new_templates)
|
||||||
bpy.utils.unregister_class(OT_AddTemplatePopup)
|
TOPBAR_MT_file_defaults.remove(draw_file_default_operators)
|
||||||
bpy.utils.unregister_class(OT_RemoveTemplateItem)
|
TOPBAR_MT_workspace_menu.remove(draw_ws_menu_add)
|
||||||
bpy.utils.unregister_class(OT_SelectTemplatePopup)
|
|
||||||
bpy.utils.unregister_class(OT_OpenAddonPreferences)
|
|
||||||
bpy.utils.unregister_class(TemplateItem)
|
|
||||||
bpy.types.TOPBAR_MT_file_new.remove(draw_new_menu)
|
|
||||||
bpy.types.TOPBAR_MT_file_defaults.remove(draw_add_template)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
register()
|
registers()
|
||||||
|
@ -1,27 +1,14 @@
|
|||||||
schema_version = "1.0.0"
|
schema_version = "1.0.0"
|
||||||
id = "custom_templates"
|
id = "custom_templates"
|
||||||
version = "1.0.2"
|
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>"
|
||||||
type = "add-on"
|
type = "add-on"
|
||||||
|
|
||||||
# Repository on projects.blender.org
|
|
||||||
website = "https://projects.blender.org/Francesco-Bellini/custom_templates_addon"
|
website = "https://projects.blender.org/Francesco-Bellini/custom_templates_addon"
|
||||||
|
tags = ["System", "User Interface"]
|
||||||
# Optional list defined by Blender and server, see:
|
|
||||||
# https://docs.blender.org/manual/en/dev/advanced/extensions/tags.html
|
|
||||||
tags = ["System"]
|
|
||||||
|
|
||||||
blender_version_min = "4.2.0"
|
blender_version_min = "4.2.0"
|
||||||
|
license = ["SPDX:GPL-3.0-or-later"]
|
||||||
# License conforming to https://spdx.org/licenses/ (use "SPDX: prefix)
|
copyright = ["2024 Francesco Bellini"]
|
||||||
# https://docs.blender.org/manual/en/dev/advanced/extensions/licenses.html
|
[permissions]
|
||||||
license = [
|
files = "JSON Import/Export of templates list / Add templates from folder"
|
||||||
"SPDX:GPL-3.0-or-later",
|
|
||||||
]
|
|
||||||
# Optional: required by some licenses.
|
|
||||||
copyright = [
|
|
||||||
"2024 Francesco Bellini",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
77
addon/src/funcs.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
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")
|
60
addon/src/menus.py
Normal 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
|
328
addon/src/ops.py
Normal file
@ -0,0 +1,328 @@
|
|||||||
|
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'}
|
60
addon/src/prefs.py
Normal 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")
|
73
addon/src/splash.py
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
# 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
|
||||||
|
# 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 = pref()
|
||||||
|
layout.operator_context = 'EXEC_DEFAULT'
|
||||||
|
layout.emboss = 'PULLDOWN_MENU'
|
||||||
|
split = layout.split()
|
||||||
|
|
||||||
|
# Templates
|
||||||
|
col1 = split.column()
|
||||||
|
ct_split = col1.split(factor=0.9)
|
||||||
|
colA = ct_split.column()
|
||||||
|
if prefs.override_splash and len(prefs.projects) > 0:
|
||||||
|
colA.label(text="Custom Templates")
|
||||||
|
col1.operator_context = 'INVOKE_DEFAULT'
|
||||||
|
draw_templates(col1, context, True)
|
||||||
|
else:
|
||||||
|
# Call original code
|
||||||
|
colA.label(text="New File")
|
||||||
|
bpy.types.TOPBAR_MT_file_new.draw_ex(col1, context, use_splash=True)
|
||||||
|
colB = ct_split.column()
|
||||||
|
colB.menu("CT_MT_splash_mode", icon='DOWNARROW_HLT', text="")
|
||||||
|
|
||||||
|
# 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", text="Tutorials", icon='URL').url = "https://www.blender.org/tutorials/"
|
||||||
|
col2.operator("wm.url_open", text="Support", icon='URL').url = "https://www.blender.org/support/"
|
||||||
|
col2.operator("wm.url_open", text="User Communities", icon='URL').url = "https://www.blender.org/community/"
|
||||||
|
col2.operator("wm.url_open_preset", text="Blender Website", icon='URL').type = 'BLENDER'
|
||||||
|
|
||||||
|
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="Donate", icon='FUND').type = 'FUND'
|
||||||
|
col2.operator("wm.url_open_preset", text="What's New", icon='URL').type = 'RELEASE_NOTES'
|
||||||
|
|
||||||
|
layout.separator()
|
||||||
|
|
||||||
|
if (not bpy.app.online_access) and bpy.app.online_access_override:
|
||||||
|
self.layout.label(text="Running in Offline Mode", icon='INTERNET_OFFLINE')
|
||||||
|
|
||||||
|
layout.separator()
|
27
dev/TEST_WORKFLOW.md
Normal 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!
|
BIN
images/Addons_Preferences - v2.png
Normal file
After Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 68 KiB |
BIN
images/Featured Image - v2.png
Normal file
After Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.8 MiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 40 KiB |
BIN
images/Preview Image - Add Workspace.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
images/Preview Image - FileNew.png
Normal file
After Width: | Height: | Size: 1.3 MiB |
BIN
images/Preview Image - Splash.png
Normal file
After Width: | Height: | Size: 1.3 MiB |
BIN
images/Preview Image - v2.png
Normal file
After Width: | Height: | Size: 1.5 MiB |
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 1.8 MiB |
78
legacy/__init__.py
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
# 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()
|
77
legacy/src/funcs.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
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")
|
60
legacy/src/menus.py
Normal 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
|
328
legacy/src/ops.py
Normal file
@ -0,0 +1,328 @@
|
|||||||
|
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'}
|
60
legacy/src/prefs.py
Normal 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")
|
69
legacy/src/splash.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# 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
release-legacy.sh
Normal file
@ -0,0 +1 @@
|
|||||||
|
zip -9 -r ./releases/1.x.x/legacy/custom-templates-legacy-1.5.0.zip ./legacy/
|