Remove old blender manifest file Update README for v1.0.0 Update script name for .zip to release.sh Remove operations that was specific for user_default extensions Update README with Support info and .blend path notice Update zip for v1.0.0 Fix select template popup Override first release 1.0.0
280 lines
10 KiB
Python
280 lines
10 KiB
Python
# 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 os
|
|
import bpy
|
|
from bpy.types import AddonPreferences, PropertyGroup, Operator
|
|
from bpy.props import CollectionProperty, IntProperty, StringProperty
|
|
|
|
|
|
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",
|
|
"category": "System",
|
|
"support": "COMMUNITY",
|
|
"blender_manifest": "blender_manifest.toml"
|
|
}
|
|
|
|
|
|
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 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[__name__].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[__name__].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[__name__].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[__name__].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[__name__].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[__name__].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 = __name__
|
|
|
|
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[__name__].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():
|
|
bpy.utils.register_class(TemplateItem)
|
|
bpy.utils.register_class(OT_MoveUpTemplateItem)
|
|
bpy.utils.register_class(OT_MoveDownTemplateItem)
|
|
bpy.utils.register_class(OT_AddTemplateItem)
|
|
bpy.utils.register_class(OT_RemoveTemplateItem)
|
|
bpy.utils.register_class(OT_AddTemplatePopup)
|
|
bpy.utils.register_class(OT_SelectTemplatePopup)
|
|
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():
|
|
bpy.utils.unregister_class(CustomTemplatesPreferences)
|
|
bpy.utils.unregister_class(OT_MoveUpTemplateItem)
|
|
bpy.utils.unregister_class(OT_MoveDownTemplateItem)
|
|
bpy.utils.unregister_class(OT_AddTemplateItem)
|
|
bpy.utils.unregister_class(OT_AddTemplatePopup)
|
|
bpy.utils.unregister_class(OT_RemoveTemplateItem)
|
|
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__":
|
|
register()
|