From 0d7282e69b35d1cc7c67629ecddbd397b85ea727 Mon Sep 17 00:00:00 2001 From: Guillermo Venegas Date: Tue, 6 Feb 2024 20:02:27 +0100 Subject: [PATCH] Fix: Presets overriding drag-n-drop data Currently, internal I/O operators can be invoked with drag-n-drop path data, and when this happens a quick popup menu is shown to customize import settings. If these operators support operator presets, using a preset can override path data given by drag-n-drop, and that can be unwanted behavior. While this can be fixed by setting path properties to SKIP_SAVE, doing this would make these properties also to stop using ghost values. These ghost values are used by the file select window to open operator last import directory, and using this flag makes the file select windows always open the home directory. To fix that, add an explicit flag PROP_SKIP_PRESET that skips properties writing to presets. Also clarify that PROP_HIDDEN and PROP_SKIP_SAVE also avoid writing to presets. Added a operator that can clean operator's specific property presets. Importing presets from previous versions runs an automatic cleanup. Co-authored-by: Brecht Van Lommel Pull Request: https://projects.blender.org/blender/blender/pulls/117673 --- scripts/startup/bl_operators/presets.py | 73 ++++++++++++++++++- scripts/startup/bl_operators/userpref.py | 2 + scripts/startup/bl_ui/space_topbar.py | 1 + source/blender/makesrna/RNA_types.hh | 9 ++- source/blender/makesrna/intern/rna_rna.cc | 22 +++++- source/blender/windowmanager/WM_api.hh | 1 + .../windowmanager/intern/wm_operator_props.cc | 12 ++- 7 files changed, 109 insertions(+), 11 deletions(-) diff --git a/scripts/startup/bl_operators/presets.py b/scripts/startup/bl_operators/presets.py index 1f588367dc7..d8a493a22ad 100644 --- a/scripts/startup/bl_operators/presets.py +++ b/scripts/startup/bl_operators/presets.py @@ -6,10 +6,12 @@ import bpy from bpy.types import ( Menu, Operator, + OperatorFileListElement, WindowManager, ) from bpy.props import ( BoolProperty, + CollectionProperty, StringProperty, ) from bpy.app.translations import ( @@ -622,7 +624,7 @@ class AddPresetOperator(AddPresetBase, Operator): ret = [] for prop_id, prop in operator_rna.properties.items(): - if not (prop.is_hidden or prop.is_skip_save): + if not prop.is_skip_preset: if prop_id not in properties_blacklist: ret.append("op.%s" % prop_id) @@ -655,6 +657,74 @@ class WM_MT_operator_presets(Menu): preset_operator = "script.execute_preset" +class WM_OT_operator_presets_cleanup(Operator): + bl_idname = "wm.operator_presets_cleanup" + bl_label = "Clean Up Operator Presets" + bl_description = "Remove outdated operator properties from presets that may cause problems" + + operator: StringProperty(name="operator") + properties: CollectionProperty(name="properties", type=OperatorFileListElement) + + def cleanup_preset(self, filepath, properties): + from pathlib import Path + file = Path(filepath) + if not (file.is_file() and filepath.suffix == ".py"): + return + lines = file.read_text().splitlines(True) + if len(lines) == 0: + return + new_lines = [] + for line in lines: + if not any(line.startswith(("op.%s" % prop)) for prop in properties): + new_lines.append(line) + file.write_text("".join(new_lines)) + + def cleanup_operators_presets(self, operators, properties): + base_preset_directory = bpy.utils.user_resource( + 'SCRIPTS', path="presets", create=False) + for operator in operators: + from pathlib import Path + operator_path = AddPresetOperator.operator_path(operator) + directory = Path(base_preset_directory, operator_path) + + if not directory.is_dir(): + continue + + for filepath in directory.iterdir(): + self.cleanup_preset(filepath, properties) + + def execute(self, context): + properties = [] + operators = [] + if self.operator: + operators.append(self.operator) + for prop in self.properties: + properties.append(prop.name) + else: + # Cleanup by default I/O Operators Presets + operators = ['WM_OT_alembic_export', + 'WM_OT_alembic_import', + 'WM_OT_collada_export', + 'WM_OT_collada_import', + 'WM_OT_gpencil_export_svg', + 'WM_OT_gpencil_export_pdf', + 'WM_OT_gpencil_export_svg', + 'WM_OT_gpencil_import_svg', + 'WM_OT_obj_export', + 'WM_OT_obj_import', + 'WM_OT_ply_export', + 'WM_OT_ply_import', + 'WM_OT_stl_export', + 'WM_OT_stl_import', + 'WM_OT_usd_export', + 'WM_OT_usd_import', + ] + properties = ["filepath", "directory", "files", "filename"] + + self.cleanup_operators_presets(operators, properties) + return {'FINISHED'} + + class AddPresetGpencilBrush(AddPresetBase, Operator): """Add or remove grease pencil brush preset""" bl_idname = "scene.gpencil_brush_preset_add" @@ -748,4 +818,5 @@ classes = ( AddPresetEEVEERaytracing, ExecutePreset, WM_MT_operator_presets, + WM_OT_operator_presets_cleanup, ) diff --git a/scripts/startup/bl_operators/userpref.py b/scripts/startup/bl_operators/userpref.py index 60f096a99cb..059fae0b440 100644 --- a/scripts/startup/bl_operators/userpref.py +++ b/scripts/startup/bl_operators/userpref.py @@ -148,6 +148,8 @@ class PREFERENCES_OT_copy_prev(Operator): # Reload preferences and `recent-files.txt`. bpy.ops.wm.read_userpref() bpy.ops.wm.read_history() + # Fix operator presets that have unwanted filepath properties + bpy.ops.wm.operator_presets_cleanup() # don't loose users work if they open the splash later. if bpy.data.is_saved is bpy.data.is_dirty is False: diff --git a/scripts/startup/bl_ui/space_topbar.py b/scripts/startup/bl_ui/space_topbar.py index 54a07d36faf..d7ea640d3a8 100644 --- a/scripts/startup/bl_ui/space_topbar.py +++ b/scripts/startup/bl_ui/space_topbar.py @@ -454,6 +454,7 @@ class TOPBAR_MT_blender_system(Menu): layout.separator() layout.operator("screen.spacedata_cleanup") + layout.operator("wm.operator_presets_cleanup") class TOPBAR_MT_templates_more(Menu): diff --git a/source/blender/makesrna/RNA_types.hh b/source/blender/makesrna/RNA_types.hh index 8108b9d791e..a42b6c8169d 100644 --- a/source/blender/makesrna/RNA_types.hh +++ b/source/blender/makesrna/RNA_types.hh @@ -179,7 +179,7 @@ enum PropertySubType { /* Make sure enums are updated with these */ /* HIGHEST FLAG IN USE: 1 << 31 - * FREE FLAGS: 11, 13, 14, 15. */ + * FREE FLAGS: 13, 14, 15. */ enum PropertyFlag { /** * Editable means the property is editable in the user @@ -212,9 +212,9 @@ enum PropertyFlag { PROP_ICONS_CONSECUTIVE = (1 << 12), PROP_ICONS_REVERSE = (1 << 8), - /** Hidden in the user interface. */ + /** Hidden in the user interface. Inherits #ROP_SKIP_PRESET. */ PROP_HIDDEN = (1 << 19), - /** Do not write in presets. */ + /** Do not use ghost values. Inherits #PROP_SKIP_PRESET. */ PROP_SKIP_SAVE = (1 << 28), /* numbers */ @@ -311,6 +311,9 @@ enum PropertyFlag { * as having the +/- operators available in the file browser. */ PROP_PATH_OUTPUT = (1 << 2), + + /** Do not write in presets. */ + PROP_SKIP_PRESET = (1 << 11), }; ENUM_OPERATORS(PropertyFlag, PROP_TEXTEDIT_UPDATE) diff --git a/source/blender/makesrna/intern/rna_rna.cc b/source/blender/makesrna/intern/rna_rna.cc index 1a26eb905d3..21b559c06be 100644 --- a/source/blender/makesrna/intern/rna_rna.cc +++ b/source/blender/makesrna/intern/rna_rna.cc @@ -154,8 +154,13 @@ const EnumPropertyItem rna_enum_property_unit_items[] = { }; const EnumPropertyItem rna_enum_property_flag_items[] = { - {PROP_HIDDEN, "HIDDEN", 0, "Hidden", ""}, - {PROP_SKIP_SAVE, "SKIP_SAVE", 0, "Skip Save", ""}, + {PROP_HIDDEN, "HIDDEN", 0, "Hidden", "Hidden in the user interface. Inherits 'SKIP_PRESET'"}, + {PROP_SKIP_SAVE, + "SKIP_SAVE", + 0, + "Skip Save", + "Do not use ghost values. Inherits 'SKIP_PRESET'"}, + {PROP_SKIP_PRESET, "SKIP_PRESET", 0, "Skip Preset", "Do not write in presets"}, {PROP_ANIMATABLE, "ANIMATABLE", 0, "Animatable", ""}, {PROP_LIB_EXCEPTION, "LIBRARY_EDITABLE", 0, "Library Editable", ""}, {PROP_PROPORTIONAL, "PROPORTIONAL", 0, "Adjust values proportionally to each other", ""}, @@ -728,6 +733,12 @@ static bool rna_Property_is_skip_save_get(PointerRNA *ptr) return (prop->flag & PROP_SKIP_SAVE) != 0; } +static bool rna_Property_is_skip_preset_get(PointerRNA *ptr) +{ + PropertyRNA *prop = (PropertyRNA *)ptr->data; + return (prop->flag & (PROP_SKIP_SAVE | PROP_HIDDEN | PROP_SKIP_PRESET)) != 0; +} + static bool rna_Property_is_enum_flag_get(PointerRNA *ptr) { PropertyRNA *prop = (PropertyRNA *)ptr->data; @@ -3136,7 +3147,12 @@ static void rna_def_property(BlenderRNA *brna) prop = RNA_def_property(srna, "is_skip_save", PROP_BOOLEAN, PROP_NONE); RNA_def_property_clear_flag(prop, PROP_EDITABLE); RNA_def_property_boolean_funcs(prop, "rna_Property_is_skip_save_get", nullptr); - RNA_def_property_ui_text(prop, "Skip Save", "True when the property is not saved in presets"); + RNA_def_property_ui_text(prop, "Skip Save", "True when the property uses ghost values"); + + prop = RNA_def_property(srna, "is_skip_preset", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_boolean_funcs(prop, "rna_Property_is_skip_preset_get", nullptr); + RNA_def_property_ui_text(prop, "Skip Preset", "True when the property is not saved in presets"); prop = RNA_def_property(srna, "is_output", PROP_BOOLEAN, PROP_NONE); RNA_def_property_clear_flag(prop, PROP_EDITABLE); diff --git a/source/blender/windowmanager/WM_api.hh b/source/blender/windowmanager/WM_api.hh index cd55cc0b61c..f98ed202331 100644 --- a/source/blender/windowmanager/WM_api.hh +++ b/source/blender/windowmanager/WM_api.hh @@ -901,6 +901,7 @@ enum eFileSel_Flag { WM_FILESEL_FILES = 1 << 4, /** Show the properties sidebar by default. */ WM_FILESEL_SHOW_PROPS = 1 << 5, + WM_FILESEL_SKIP_PATHS_PRESETS = 1 << 5, }; ENUM_OPERATORS(eFileSel_Flag, WM_FILESEL_SHOW_PROPS) diff --git a/source/blender/windowmanager/intern/wm_operator_props.cc b/source/blender/windowmanager/intern/wm_operator_props.cc index f7096c7f697..fad16982f65 100644 --- a/source/blender/windowmanager/intern/wm_operator_props.cc +++ b/source/blender/windowmanager/intern/wm_operator_props.cc @@ -93,23 +93,27 @@ void WM_operator_properties_filesel(wmOperatorType *ot, }; if (flag & WM_FILESEL_FILEPATH) { - RNA_def_string_file_path(ot->srna, "filepath", nullptr, FILE_MAX, "File Path", "Path to file"); + prop = RNA_def_string_file_path( + ot->srna, "filepath", nullptr, FILE_MAX, "File Path", "Path to file"); + RNA_def_property_flag(prop, PROP_SKIP_PRESET); } if (flag & WM_FILESEL_DIRECTORY) { - RNA_def_string_dir_path( + prop = RNA_def_string_dir_path( ot->srna, "directory", nullptr, FILE_MAX, "Directory", "Directory of the file"); + RNA_def_property_flag(prop, PROP_SKIP_PRESET); } if (flag & WM_FILESEL_FILENAME) { - RNA_def_string_file_name( + prop = RNA_def_string_file_name( ot->srna, "filename", nullptr, FILE_MAX, "File Name", "Name of the file"); + RNA_def_property_flag(prop, PROP_SKIP_PRESET); } if (flag & WM_FILESEL_FILES) { prop = RNA_def_collection_runtime( ot->srna, "files", &RNA_OperatorFileListElement, "Files", ""); - RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE | PROP_SKIP_PRESET); } if ((flag & WM_FILESEL_SHOW_PROPS) == 0) {