REQUEST: Save last used preset name, display 'Update' button icon on overwrite
KirilStrezikozin opened this issue · 2 comments
This feature request is:
- not a duplicate
- implemented
Context
Users can overwrite presets in BakeMaster by creating a new one with an existing name. BakeMaster does not show any UI signs of this behavior, so this issue is about enhancing this.
Describe the solution you'd like to be implemented
-
mike_fr (Discord) suggested auto-populating the New Preset Name field by the name of the last used preset in the preset menu pop-ups. This allows users to overwrite presets quicker by not manually entering the whole name. To create new presets the user would click the Preset Name field and replace its value with the desired preset name.
Example:
The last used preset is namedAO_4k_highq
. The user applied this preset. When they open the preset menu, they seeAO_4k_highq
instead ofNew Preset
. This indicates that they previously usedAO_4k_highq
preset. From there, they can press 'Update' button ('Update' icon is displayed instead of 'Plus' becauseAO_4k_highq
preset exists) to overwrite the preset. They can erase the value in the Name field and overwrite any other preset or create a new one. -
Display 'Update' button on the left to the 'Remove' button to allow users to update any preset without manually entering their name. When pressing 'Update' in the list, the current settings are read and fed into the preset on which the 'Update' button was pressed. Internally, this involves creating a new preset with an existing name (this name is taken from the preset on which the 'Update' button was pressed).
BakeMaster draws presets by utilizing a BM_PresetPanel
helper class for preset panels. It provides a classmethod
call to draw a preset header (a button in panel headers with a preset icon to invoke a preset panel), and calls Blender's Menu.draw_preset(...)
to draw a preset menu:
BakeMaster-Blender-Addon/presets.py
Lines 1251 to 1286 in acf0b5b
This issue will replace Menu.draw_preset(...)
call with a custom written draw_preset
method similar to Blender's Menu.draw_preset
implementation, but with functionality described in todos above:
class Menu(StructRNA, _GenericUI, metaclass=RNAMeta):
__slots__ = ()
def path_menu(self, searchpaths, operator, *,
props_default=None, prop_filepath="filepath",
filter_ext=None, filter_path=None, display_name=None,
add_operator=None):
"""
Populate a menu from a list of paths.
:arg searchpaths: Paths to scan.
:type searchpaths: sequence of strings.
:arg operator: The operator id to use with each file.
:type operator: string
:arg prop_filepath: Optional operator filepath property (defaults to "filepath").
:type prop_filepath: string
:arg props_default: Properties to assign to each operator.
:type props_default: dict
:arg filter_ext: Optional callback that takes the file extensions.
Returning false excludes the file from the list.
:type filter_ext: Callable that takes a string and returns a bool.
:arg display_name: Optional callback that takes the full path, returns the name to display.
:type display_name: Callable that takes a string and returns a string.
"""
layout = self.layout
import os
import re
import bpy.utils
from bpy.app.translations import pgettext_iface as iface_
layout = self.layout
if not searchpaths:
layout.label(text="* Missing Paths *")
# collect paths
files = []
for directory in searchpaths:
files.extend([
(f, os.path.join(directory, f))
for f in os.listdir(directory)
if (not f.startswith("."))
if ((filter_ext is None) or
(filter_ext(os.path.splitext(f)[1])))
if ((filter_path is None) or
(filter_path(f)))
])
# Perform a "natural sort", so 20 comes after 3 (for example).
files.sort(
key=lambda file_path:
tuple(int(t) if t.isdigit() else t for t in re.split(r"(\d+)", file_path[0].lower())),
)
col = layout.column(align=True)
for f, filepath in files:
# Intentionally pass the full path to 'display_name' callback,
# since the callback may want to use part a directory in the name.
row = col.row(align=True)
name = display_name(filepath) if display_name else bpy.path.display_name(f)
props = row.operator(
operator,
text=iface_(name),
translate=False,
)
if props_default is not None:
for attr, value in props_default.items():
setattr(props, attr, value)
setattr(props, prop_filepath, filepath)
if operator == "script.execute_preset":
props.menu_idname = self.bl_idname
if add_operator:
props = row.operator(add_operator, text="", icon='REMOVE')
props.name = name
props.remove_name = True
if add_operator:
wm = bpy.data.window_managers[0]
layout.separator()
row = layout.row()
sub = row.row()
sub.emboss = 'NORMAL'
sub.prop(wm, "preset_name", text="")
props = row.operator(add_operator, text="", icon='ADD')
props.name = wm.preset_name
def draw_preset(self, _context):
"""
Define these on the subclass:
- preset_operator (string)
- preset_subdir (string)
Optionally:
- preset_add_operator (string)
- preset_extensions (set of strings)
- preset_operator_defaults (dict of keyword args)
"""
import bpy
ext_valid = getattr(self, "preset_extensions", {".py", ".xml"})
props_default = getattr(self, "preset_operator_defaults", None)
add_operator = getattr(self, "preset_add_operator", None)
self.path_menu(
bpy.utils.preset_paths(self.preset_subdir),
self.preset_operator,
props_default=props_default,
filter_ext=lambda ext: ext.lower() in ext_valid,
add_operator=add_operator,
display_name=lambda name: bpy.path.display_name(name, title_case=False)
)
@classmethod
def draw_collapsible(cls, context, layout):
# helper function for (optionally) collapsed header menus
# only usable within headers
if context.area.show_menus:
# Align menus to space them closely.
layout.row(align=True).menu_contents(cls.__name__)
else:
layout.menu(cls.__name__, icon='COLLAPSEMENU')