from .. import __package__ as base_package import bpy import json from bpy.types import Operator, PropertyGroup, AddonPreferences from bpy.props import StringProperty, CollectionProperty, IntProperty # Preferences class TemplateItem(PropertyGroup): name: StringProperty( name="Name", description="Display name for this template") path: StringProperty( name="Path", description="Path to the .blend file for this template", subtype='FILE_PATH') class CustomTemplatesPreferences(AddonPreferences): bl_idname = base_package 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.menu("custom_templates.export_menu", icon='DOWNARROW_HLT', text="") col.separator() col.operator("custom_templates.add", icon='ADD', text="") col.operator("custom_templates.remove", icon='REMOVE', text="") col.separator() col.operator("custom_templates.move_up", icon='TRIA_UP', text="") col.operator("custom_templates.move_down", icon='TRIA_DOWN', text="") if self.projects: project = self.projects[self.active_template_index] layout.prop(project, "name") layout.prop(project, "path") class CUSTOM_MT_Export_Menu(bpy.types.Menu): bl_idname = "custom_templates.export_menu" bl_label = "Import/Export custom templates" bl_description = "Allows you to save al load your custom templates (using json format)" def draw(self, context): layout = self.layout layout.operator("custom_templates.export_templates", text="Export templates") layout.operator("custom_templates.import_templates", text="Import templates") class OT_ExportTemplates(bpy.types.Operator): bl_idname = "custom_templates.export_templates" bl_label = "Export custom templates" bl_description = "Export the current list of templates to JSON file" filepath: StringProperty(subtype="FILE_PATH", description="Select the path for the exported file", default="custom_templates.json") def execute(self, context): prefs = context.preferences.addons[base_package].preferences with open(self.filepath, 'w') as f: projects = [{"name": project.name, "path": project.path} for project in prefs.projects] json.dump(projects, f, indent=4) self.report({'INFO'}, f"Templates exported to {self.filepath}") return {'FINISHED'} def invoke(self, context, event): context.window_manager.fileselect_add(self) return {'RUNNING_MODAL'} class OT_ImportTemplates(bpy.types.Operator): bl_idname = "custom_templates.import_templates" bl_label = "Import custom templates" bl_description = "Import a list of templates from JSON file (note that this will override the current templates list)" filepath: StringProperty(subtype="FILE_PATH", description="Select the .json file to load") def execute(self, context): prefs = context.preferences.addons[base_package].preferences with open(self.filepath, 'r') as f: projects = json.load(f) prefs.projects.clear() for project in projects: item = prefs.projects.add() item.name = project["name"] item.path = project["path"] self.report({'INFO'}, f"Projects imported from {self.filepath}") return {'FINISHED'} def invoke(self, context, event): context.window_manager.fileselect_add(self) return {'RUNNING_MODAL'} # Templates list oeprators class OT_AddTemplateItem(Operator): bl_idname = "custom_templates.add" bl_label = "Add Template" bl_description = "Add new template" def execute(self, context): prefs = context.preferences.addons[base_package].preferences prefs.projects.add() prefs.active_template_index = len(prefs.projects) - 1 self.report({'INFO'}, f"Empty template added") return {'FINISHED'} class OT_RemoveTemplateItem(Operator): bl_idname = "custom_templates.remove" bl_label = "Remove Template" bl_description = "Remove selected template" def execute(self, context): prefs = context.preferences.addons[base_package].preferences self.report( {'INFO'}, f'Template "{prefs.projects[prefs.active_template_index].name}" removed{" (`"+prefs.projects[prefs.active_template_index].path+"`)" if prefs.projects[prefs.active_template_index].path != "" else "" }') prefs.projects.remove(prefs.active_template_index) prefs.active_template_index = min( max(0, prefs.active_template_index - 1), len(prefs.projects) - 1) return {'FINISHED'} class OT_MoveUpTemplateItem(Operator): bl_idname = "custom_templates.move_up" bl_label = "Move Up" bl_description = "Move the selected template up in the list" def execute(self, context): prefs = context.preferences.addons[base_package].preferences index = prefs.active_template_index if index > 0: prefs.projects.move(index, index - 1) prefs.active_template_index -= 1 self.report({'INFO'}, f"Templates list re-ordered") else: self.report({'WARNING'}, "Template is already at the top") return {'FINISHED'} class OT_MoveDownTemplateItem(Operator): bl_idname = "custom_templates.move_down" bl_label = "Move Down" bl_description = "Move the selected template down in the list" def execute(self, context): prefs = context.preferences.addons[base_package].preferences index = prefs.active_template_index if index < len(prefs.projects) - 1: prefs.projects.move(index, index + 1) prefs.active_template_index += 1 self.report({'INFO'}, f"Templates list re-ordered") else: self.report({'WARNING'}, "Template is already at the bottom") return {'FINISHED'} # Popups of File > Defaults def check_if_present(self, prefs, path): for p in prefs.projects: if p.path == path: already_present = True self.report( {'WARNING'}, f'Current file is already in the templates list as "{p.name}".') return True return False class OT_AddTemplatePopup(Operator): bl_idname = "custom_templates.add_template_popup" bl_label = "Use as template" bl_description = "Use the current .blend file to create a new template occurency" project_name: StringProperty(name="Project Name") def execute(self, context): prefs = context.preferences.addons[base_package].preferences current_file_path = bpy.data.filepath if check_if_present(self, prefs, current_file_path): return {'FINISHED'} if current_file_path: new_project = prefs.projects.add() new_project.name = self.project_name new_project.path = current_file_path self.report( {'INFO'}, f"Template '{self.project_name}' added successfully!") else: self.report({'ERROR'}, "Current file is not saved on disk.") return {'FINISHED'} def invoke(self, context, event): return context.window_manager.invoke_props_dialog(self) class OT_SelectTemplatePopup(Operator): bl_idname = "custom_templates.select_template_popup" bl_label = "Select a new custom template" bl_description = "Create a new template occurency by selecting an existing .blend file" project_name: StringProperty(name="Project Name") project_path: StringProperty(name="Project Path", subtype="FILE_PATH") def execute(self, context): prefs = context.preferences.addons[base_package].preferences if check_if_present(self, prefs, self.project_path): return {'FINISHED'} new_project = prefs.projects.add() new_project.name = self.project_name new_project.path = self.project_path self.report( {'INFO'}, f"Template '{self.project_name}' selected and added successfully!") return {'FINISHED'} def invoke(self, context, event): return context.window_manager.invoke_props_dialog(self) # Open Preferences class OT_OpenAddonPreferences(Operator): bl_idname = "custom_templates.open_preferences" bl_label = "Open Custom Templates Preferences" bl_description = "Open the preferences for the Custom Templates add-on" def execute(self, context): bpy.ops.screen.userpref_show('INVOKE_DEFAULT') context.preferences.active_section = 'ADDONS' context.window_manager.addon_search = "Custom Templates" return {'FINISHED'}