Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

REQUEST: Save last used preset name, display 'Update' button icon on overwrite #88

Closed
4 tasks done
KirilStrezikozin opened this issue Mar 14, 2024 · 2 comments · Fixed by #92
Closed
4 tasks done
Assignees
Labels
enhancement old stuff sucks, new stuff rocks solved How could you? GPT helped?

Comments

@KirilStrezikozin
Copy link
Owner

KirilStrezikozin commented Mar 14, 2024

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 named AO_4k_highq. The user applied this preset. When they open the preset menu, they see AO_4k_highq instead of New Preset. This indicates that they previously used AO_4k_highq preset. From there, they can press 'Update' button ('Update' icon is displayed instead of 'Plus' because AO_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).

@KirilStrezikozin KirilStrezikozin added the enhancement old stuff sucks, new stuff rocks label Mar 14, 2024
@KirilStrezikozin KirilStrezikozin added this to the BakeMaster 2.6.2 milestone Mar 14, 2024
@KirilStrezikozin KirilStrezikozin self-assigned this Mar 14, 2024
@KirilStrezikozin KirilStrezikozin added the in progress All night I stay not sleeping because I'm thinking about this label Mar 19, 2024
@KirilStrezikozin
Copy link
Owner Author

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:

class BM_PresetPanel:
bl_space_type = 'PROPERTIES'
bl_region_type = 'HEADER'
bl_label = "Presets"
path_menu = Menu.path_menu
@classmethod
def draw_panel_header(cls, layout):
layout.emboss = 'NONE'
layout.popover(
panel=cls.__name__,
icon='PRESET',
text="",
)
@classmethod
def draw_menu(cls, layout, text=None):
if text is None:
text = cls.bl_label
layout.popover(
panel=cls.__name__,
icon='PRESET',
text=text,
)
def draw(self, context):
layout = self.layout
layout.emboss = 'PULLDOWN_MENU'
# from https://docs.blender.org/api/current/bpy.ops.html#execution-context
# EXEC_DEFAULT is used by default, running only the execute() method,
# but you may want the operator to take user interaction with
# INVOKE_DEFAULT which will also call invoke() if existing.
layout.operator_context = 'INVOKE_DEFAULT'
Menu.draw_preset(self, context)

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')

@KirilStrezikozin KirilStrezikozin added in progress All night I stay not sleeping because I'm thinking about this and removed in progress All night I stay not sleeping because I'm thinking about this labels Mar 19, 2024
@KirilStrezikozin
Copy link
Owner Author

image
image

KirilStrezikozin referenced this issue Mar 20, 2024
updated to include 'or new preset name' label
@KirilStrezikozin KirilStrezikozin added solved How could you? GPT helped? close on release done here, but let's wait for the release migrate: public->dev I'm doing cps now until I manage my repos better. and removed in progress All night I stay not sleeping because I'm thinking about this migrate: public->dev I'm doing cps now until I manage my repos better. labels Mar 20, 2024
@KirilStrezikozin KirilStrezikozin removed the close on release done here, but let's wait for the release label Mar 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement old stuff sucks, new stuff rocks solved How could you? GPT helped?
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant