Features: add from folder, clear current templates, auto-naming; +refactor
This commit is contained in:
parent
c85b88d939
commit
f80b32a127
@ -16,7 +16,7 @@
|
|||||||
import bpy
|
import bpy
|
||||||
from .classes.draw import draw_file_new_templates, draw_file_default_operators
|
from .classes.draw import draw_file_new_templates, draw_file_default_operators
|
||||||
from .classes.splash import WM_MT_splash, CT_MT_splash_mode, CT_OT_splash_custom, CT_OT_splash_default
|
from .classes.splash import WM_MT_splash, CT_MT_splash_mode, CT_OT_splash_custom, CT_OT_splash_default
|
||||||
from .classes.ots import CustomTemplatesPreferences, TemplateItem, CT_OT_export_templates, CT_OT_import_templates, CT_MT_export, 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
|
from .classes.ots import CustomTemplatesPreferences, TemplateItem, CT_OT_export_templates, CT_OT_import_templates, CT_MT_templates_menu, CT_OT_select_template_popup, CT_OT_add_template_popup, CT_OT_add, CT_OT_remove, CT_OT_move_down, CT_OT_move_up, CT_OT_open_preferences, CT_OT_add_templates_from_folder, CT_OT_clear
|
||||||
|
|
||||||
bl_info = {
|
bl_info = {
|
||||||
"id": "custom_templates",
|
"id": "custom_templates",
|
||||||
@ -37,13 +37,15 @@ classes = [WM_MT_splash,
|
|||||||
CT_OT_splash_default,
|
CT_OT_splash_default,
|
||||||
CT_OT_open_preferences,
|
CT_OT_open_preferences,
|
||||||
CT_MT_splash_mode,
|
CT_MT_splash_mode,
|
||||||
CT_MT_export,
|
CT_MT_templates_menu,
|
||||||
CT_OT_move_up,
|
CT_OT_move_up,
|
||||||
CT_OT_move_down,
|
CT_OT_move_down,
|
||||||
CT_OT_add,
|
CT_OT_add,
|
||||||
CT_OT_remove,
|
CT_OT_remove,
|
||||||
|
CT_OT_clear,
|
||||||
CT_OT_add_template_popup,
|
CT_OT_add_template_popup,
|
||||||
CT_OT_select_template_popup,
|
CT_OT_select_template_popup,
|
||||||
|
CT_OT_add_templates_from_folder,
|
||||||
CustomTemplatesPreferences]
|
CustomTemplatesPreferences]
|
||||||
|
|
||||||
og_splash = None;
|
og_splash = None;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from .. import __package__ as base_package
|
from .. import __package__ as base_package
|
||||||
|
from .ots import name_from_path
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
def draw_file_new_templates(self, context):
|
def draw_file_new_templates(self, context):
|
||||||
@ -9,24 +10,30 @@ def draw_file_new_templates(self, context):
|
|||||||
layout.separator()
|
layout.separator()
|
||||||
draw_templates(layout, context)
|
draw_templates(layout, context)
|
||||||
|
|
||||||
def draw_templates(layout, context, splash_mode = False):
|
def draw_templates(layout, context, splash_mode=False):
|
||||||
prefs = context.preferences.addons[base_package].preferences
|
prefs = context.preferences.addons[base_package].preferences
|
||||||
for i in range(min(5, len(prefs.projects)) if splash_mode else len(prefs.projects)):
|
for i in range(min(5, len(prefs.projects)) if splash_mode else len(prefs.projects)):
|
||||||
project = prefs.projects[i]
|
project = prefs.projects[i]
|
||||||
|
if project.path:
|
||||||
layout.operator(
|
layout.operator(
|
||||||
"wm.read_homefile", text=project.name, icon="FILE_NEW" if splash_mode else "NONE").filepath = project.path
|
"wm.read_homefile", text=(project.name if project.name else name_from_path(project.path)), icon="FILE_NEW" if splash_mode else "NONE").filepath = project.path
|
||||||
|
|
||||||
def draw_file_default_operators(self, context):
|
def draw_file_default_operators(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
layout.separator()
|
layout.separator()
|
||||||
layout.operator("ct.open_preferences",
|
layout.operator("ct.open_preferences",
|
||||||
text="Manage templates")
|
text="Manage templates")
|
||||||
layout.menu("CT_MT_export", text="Import/Export")
|
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.separator()
|
||||||
layout.operator("ct.select_template_popup",
|
layout.operator("ct.select_template_popup",
|
||||||
text="Select new custom template")
|
text="Select new template")
|
||||||
if bpy.data.filepath != "":
|
if bpy.data.filepath != "":
|
||||||
# Only with an active saved .blend file
|
|
||||||
layout.operator("ct.add_template_popup",
|
layout.operator("ct.add_template_popup",
|
||||||
text="Use current as new template")
|
text="Use current file as template")
|
||||||
|
|
||||||
|
@ -5,18 +5,37 @@ import json
|
|||||||
from bpy.types import Operator, PropertyGroup, AddonPreferences
|
from bpy.types import Operator, PropertyGroup, AddonPreferences
|
||||||
from bpy.props import StringProperty, CollectionProperty, IntProperty, BoolProperty
|
from bpy.props import StringProperty, CollectionProperty, IntProperty, BoolProperty
|
||||||
|
|
||||||
# Preferences
|
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
|
||||||
|
|
||||||
class TemplateItem(PropertyGroup):
|
class TemplateItem(PropertyGroup):
|
||||||
name: StringProperty(
|
name: StringProperty(
|
||||||
name="Name", description="Display name for this template")
|
name="Name", description="Display name for this template")
|
||||||
path: StringProperty(
|
path: StringProperty(
|
||||||
name="Path", description="Path to the .blend file for this template", subtype='FILE_PATH')
|
name="Path", description="Path to the .blend file for this template", subtype='FILE_PATH', update=on_path_update)
|
||||||
|
|
||||||
override_splash_text = "Override Splash Screen's 'New File' list"
|
|
||||||
class CustomTemplatesPreferences(AddonPreferences):
|
class CustomTemplatesPreferences(AddonPreferences):
|
||||||
bl_idname = base_package
|
bl_idname = base_package
|
||||||
|
|
||||||
override_splash: BoolProperty(default=True, name=override_splash_text, description=override_splash_text)
|
override_splash: BoolProperty(
|
||||||
|
default=True, name="Override Splash Screen Templates", description="Override Splash Screen's 'New File' list with your Custom Templates")
|
||||||
projects: CollectionProperty(type=TemplateItem)
|
projects: CollectionProperty(type=TemplateItem)
|
||||||
active_template_index: IntProperty(
|
active_template_index: IntProperty(
|
||||||
description="Index of the selected template")
|
description="Index of the selected template")
|
||||||
@ -24,14 +43,16 @@ class CustomTemplatesPreferences(AddonPreferences):
|
|||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
|
|
||||||
layout.label(text="Your custom templates:")
|
layout.label(
|
||||||
|
text=f"Your custom templates ({len(self.projects)})")
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
row.template_list("UI_UL_list", "custom_templates",
|
row.template_list("UI_UL_list", "custom_templates",
|
||||||
self, "projects", self, "active_template_index")
|
self, "projects", self, "active_template_index")
|
||||||
|
|
||||||
col = row.column(align=True)
|
col = row.column(align=True)
|
||||||
col.menu("CT_MT_export", icon='DOWNARROW_HLT', text="")
|
col.menu("CT_MT_templates_menu", icon='DOWNARROW_HLT', text="")
|
||||||
col.separator()
|
col.separator()
|
||||||
|
col.operator("ct.add_templates_from_folder", text="", icon="FILE_FOLDER")
|
||||||
col.operator("ct.add", icon='ADD', text="")
|
col.operator("ct.add", icon='ADD', text="")
|
||||||
col.operator("ct.remove", icon='REMOVE', text="")
|
col.operator("ct.remove", icon='REMOVE', text="")
|
||||||
col.separator()
|
col.separator()
|
||||||
@ -40,8 +61,8 @@ class CustomTemplatesPreferences(AddonPreferences):
|
|||||||
|
|
||||||
if self.projects:
|
if self.projects:
|
||||||
project = self.projects[self.active_template_index]
|
project = self.projects[self.active_template_index]
|
||||||
layout.prop(project, "name")
|
|
||||||
layout.prop(project, "path")
|
layout.prop(project, "path")
|
||||||
|
layout.prop(project, "name")
|
||||||
|
|
||||||
layout.prop(self, "override_splash")
|
layout.prop(self, "override_splash")
|
||||||
box = layout.box()
|
box = layout.box()
|
||||||
@ -51,31 +72,38 @@ class CustomTemplatesPreferences(AddonPreferences):
|
|||||||
box.label(text="Custom templates will be shown in the Splash Screen.")
|
box.label(text="Custom templates will be shown in the Splash Screen.")
|
||||||
|
|
||||||
if not self.override_splash or len(self.projects) == 0:
|
if not self.override_splash or len(self.projects) == 0:
|
||||||
box.label(text="The default Blender list will be shown in the Splash Screen.")
|
box.label(
|
||||||
|
text="The default Blender list will be shown in the Splash Screen.")
|
||||||
|
|
||||||
if len(self.projects) > 5:
|
if len(self.projects) > 5:
|
||||||
box.label(text="Note: Only the first 5 templates in the list will be shown in the splash screen.")
|
box.label(
|
||||||
|
text="Note: Only the first 5 templates in the list will be shown in the splash screen.")
|
||||||
|
|
||||||
class CT_MT_export(bpy.types.Menu):
|
class CT_MT_templates_menu(bpy.types.Menu):
|
||||||
bl_label = "Import/Export custom templates"
|
bl_label = "Manage your custom templates"
|
||||||
bl_description = "Allows you to save al load your custom templates (using json format)"
|
bl_description = "Import, export, add from folder (with controllable recursion depth), clear current templates"
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
layout.operator("ct.export_templates", text="Export templates")
|
layout.operator("ct.add_templates_from_folder", text="Add from folder", icon="ADD")
|
||||||
layout.operator("ct.import_templates", text="Import templates")
|
layout.operator("ct.clear", text="Clear current templates", icon="TRASH")
|
||||||
|
layout.separator()
|
||||||
|
layout.operator("ct.import_templates", text="Import templates", icon="IMPORT")
|
||||||
|
layout.operator("ct.export_templates", text="Export templates", icon="EXPORT")
|
||||||
|
|
||||||
class CT_OT_export_templates(bpy.types.Operator):
|
class CT_OT_export_templates(bpy.types.Operator):
|
||||||
bl_idname = "ct.export_templates"
|
bl_idname = "ct.export_templates"
|
||||||
bl_label = "Export custom templates"
|
bl_label = "Export custom templates"
|
||||||
bl_description = "Export the current list of templates to JSON file"
|
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")
|
filepath: StringProperty(
|
||||||
|
subtype="FILE_PATH", description="Select the path for the exported file", default="templates.json")
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
prefs = context.preferences.addons[base_package].preferences
|
prefs = context.preferences.addons[base_package].preferences
|
||||||
with open(self.filepath, 'w') as f:
|
with open(self.filepath, 'w') as f:
|
||||||
projects = [{"name": project.name, "path": project.path} for project in prefs.projects]
|
projects = [{"name": project.name, "path": project.path}
|
||||||
|
for project in prefs.projects]
|
||||||
json.dump(projects, f, indent=4)
|
json.dump(projects, f, indent=4)
|
||||||
|
|
||||||
self.report({'INFO'}, f"Templates exported to {self.filepath}")
|
self.report({'INFO'}, f"Templates exported to {self.filepath}")
|
||||||
@ -94,7 +122,8 @@ class CT_OT_import_templates(bpy.types.Operator):
|
|||||||
bl_label = "Import custom 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)"
|
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")
|
filepath: StringProperty(
|
||||||
|
subtype="FILE_PATH", description="Select the .json file to load")
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
prefs = context.preferences.addons[base_package].preferences
|
prefs = context.preferences.addons[base_package].preferences
|
||||||
@ -113,14 +142,14 @@ class CT_OT_import_templates(bpy.types.Operator):
|
|||||||
|
|
||||||
self.report({'INFO'}, f"Projects imported from {self.filepath}")
|
self.report({'INFO'}, f"Projects imported from {self.filepath}")
|
||||||
else:
|
else:
|
||||||
self.report({'WARNING'}, f"Import cancelled: path not found ({self.filepath})")
|
self.report(
|
||||||
|
{'WARNING'}, f"Import cancelled: path not found ({self.filepath})")
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
context.window_manager.fileselect_add(self)
|
context.window_manager.fileselect_add(self)
|
||||||
return {'RUNNING_MODAL'}
|
return {'RUNNING_MODAL'}
|
||||||
|
|
||||||
# Templates list oeprators
|
|
||||||
class CT_OT_add(Operator):
|
class CT_OT_add(Operator):
|
||||||
bl_idname = "ct.add"
|
bl_idname = "ct.add"
|
||||||
bl_label = "Add Template"
|
bl_label = "Add Template"
|
||||||
@ -192,60 +221,120 @@ class CT_OT_move_down(Operator):
|
|||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
return len(context.preferences.addons[base_package].preferences.projects) > 0
|
return len(context.preferences.addons[base_package].preferences.projects) > 0
|
||||||
|
|
||||||
# Popups of File > Defaults
|
class CT_OT_clear(Operator):
|
||||||
def already_present(self, prefs, path):
|
bl_idname = "ct.clear"
|
||||||
for p in prefs.projects:
|
bl_label = "Clear Templates"
|
||||||
if p.path == path:
|
bl_description = "Clear the current list of templates (remove all templates)"
|
||||||
already_present = True
|
|
||||||
self.report(
|
def execute(self, context):
|
||||||
{'WARNING'}, f'Current file is already in the templates list as "{p.name}".')
|
prefs = context.preferences.addons[base_package].preferences
|
||||||
return True
|
prefs.projects.clear()
|
||||||
return False
|
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 len(context.preferences.addons[base_package].preferences.projects) > 0
|
||||||
|
|
||||||
class CT_OT_add_template_popup(Operator):
|
class CT_OT_add_template_popup(Operator):
|
||||||
bl_idname = "ct.add_template_popup"
|
bl_idname = "ct.add_template_popup"
|
||||||
bl_label = "Use as template"
|
bl_label = "Use as template"
|
||||||
bl_description = "Use the current .blend file to create a new template occurency"
|
bl_description = "Use the current .blend file to create a new template occurency"
|
||||||
|
|
||||||
project_name: StringProperty(name="Project Name")
|
name: StringProperty(name="Project Name")
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
prefs = context.preferences.addons[base_package].preferences
|
prefs = context.preferences.addons[base_package].preferences
|
||||||
current_file_path = bpy.data.filepath
|
if not already_present(self, prefs, bpy.data.filepath):
|
||||||
if not already_present(self, prefs, current_file_path):
|
if bpy.data.filepath:
|
||||||
if current_file_path:
|
|
||||||
new_project = prefs.projects.add()
|
new_project = prefs.projects.add()
|
||||||
new_project.name = self.project_name
|
new_project.name = self.name
|
||||||
new_project.path = current_file_path
|
new_project.path = bpy.data.filepath
|
||||||
|
self.name = ''
|
||||||
context.preferences.is_dirty = True
|
context.preferences.is_dirty = True
|
||||||
else:
|
else:
|
||||||
self.report({'ERROR'}, "Current file is not saved on disk.")
|
self.report({'ERROR'}, "Current file is not saved on disk.")
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
def invoke(self, context, event):
|
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)
|
return context.window_manager.invoke_props_dialog(self)
|
||||||
|
|
||||||
class CT_OT_select_template_popup(Operator):
|
class CT_OT_select_template_popup(Operator):
|
||||||
bl_idname = "ct.select_template_popup"
|
bl_idname = "ct.select_template_popup"
|
||||||
bl_label = "Select a new custom template"
|
bl_label = "Select a new custom template"
|
||||||
bl_description = "Create a new template occurency by selecting an existing .blend file"
|
bl_description = "Create a new template by selecting an existing .blend file"
|
||||||
|
|
||||||
project_name: StringProperty(name="Project Name")
|
path: StringProperty(name="Template Path", subtype="FILE_PATH", update=on_path_update)
|
||||||
project_path: StringProperty(name="Project Path", subtype="FILE_PATH")
|
name: StringProperty(name="Template Name")
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
prefs = context.preferences.addons[base_package].preferences
|
prefs = context.preferences.addons[base_package].preferences
|
||||||
if not already_present(self, prefs, self.project_path):
|
if not already_present(self, prefs, self.path):
|
||||||
new_project = prefs.projects.add()
|
template = prefs.projects.add()
|
||||||
new_project.name = self.project_name
|
template.name = self.name
|
||||||
new_project.path = self.project_path
|
template.path = self.path
|
||||||
context.preferences.is_dirty = True
|
context.preferences.is_dirty = True
|
||||||
|
self.name = ''
|
||||||
|
self.path = ''
|
||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
return context.window_manager.invoke_props_dialog(self)
|
return context.window_manager.invoke_props_dialog(self)
|
||||||
|
|
||||||
# Open Preferences
|
class CT_OT_add_templates_from_folder(bpy.types.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 = context.preferences.addons[base_package].preferences
|
||||||
|
|
||||||
|
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):
|
class CT_OT_open_preferences(Operator):
|
||||||
bl_idname = "ct.open_preferences"
|
bl_idname = "ct.open_preferences"
|
||||||
bl_label = "Open Custom Templates Preferences"
|
bl_label = "Open Custom Templates Preferences"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user