From 7062daea2557edf4f018a88ed58cfee935c1338f Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Fri, 17 Feb 2023 12:52:04 +0100 Subject: [PATCH 1/6] Python: Support multiple custom script directories in Preferences --- release/datafiles/userdef/userdef_default.c | 2 +- release/scripts/modules/bpy/utils/__init__.py | 18 +++- release/scripts/modules/sys_info.py | 4 +- .../scripts/startup/bl_operators/userpref.py | 78 ++++++++++++-- .../scripts/startup/bl_ui/space_userpref.py | 48 ++++++++- .../blender/blenkernel/BKE_blender_version.h | 2 +- source/blender/blenkernel/intern/blender.c | 1 + source/blender/blenloader/intern/readfile.cc | 1 + .../blenloader/intern/versioning_userdef.c | 11 ++ source/blender/blenloader/intern/writefile.cc | 4 + source/blender/editors/space_file/fsmenu.c | 9 +- source/blender/makesdna/DNA_userdef_types.h | 44 +++++--- source/blender/makesrna/intern/rna_userdef.c | 100 ++++++++++++++++-- 13 files changed, 281 insertions(+), 41 deletions(-) diff --git a/release/datafiles/userdef/userdef_default.c b/release/datafiles/userdef/userdef_default.c index d75a0974f74..23ab9e431a0 100644 --- a/release/datafiles/userdef/userdef_default.c +++ b/release/datafiles/userdef/userdef_default.c @@ -34,7 +34,7 @@ const UserDef U_default = { .renderdir = "//", .render_cachedir = "", .textudir = "//", - .pythondir = "", + .script_directories = {NULL, NULL}, .sounddir = "//", .i18ndir = "", .image_editor = "", diff --git a/release/scripts/modules/bpy/utils/__init__.py b/release/scripts/modules/bpy/utils/__init__.py index c86e97153aa..300c8d063b4 100644 --- a/release/scripts/modules/bpy/utils/__init__.py +++ b/release/scripts/modules/bpy/utils/__init__.py @@ -341,9 +341,21 @@ def script_path_user(): def script_path_pref(): - """returns the user preference or None""" - path = _preferences.filepaths.script_directory - return _os.path.normpath(path) if path else None + """ + DEPRECATED. Use `script_paths_pref` which supports multiple script paths now. Returns the + first valid of these script paths for now, for compatibility. + """ + # TODO how to handle deprecation of this? + return script_paths_pref()[0] + + +def script_paths_pref(): + """returns the user preference script directory paths or None""" + paths = [] + for script_directory in _preferences.filepaths.script_directories: + if script_directory.path: + paths.append(_os.path.normpath(script_directory.path)) + return paths def script_paths(*, subdir=None, user_pref=True, check_all=False, use_user=True): diff --git a/release/scripts/modules/sys_info.py b/release/scripts/modules/sys_info.py index 5bd38acb19c..368531d4ce2 100644 --- a/release/scripts/modules/sys_info.py +++ b/release/scripts/modules/sys_info.py @@ -88,7 +88,9 @@ def write_sysinfo(filepath): for p in bpy.utils.script_paths(): output.write("\t%r\n" % p) output.write("user scripts: %r\n" % (bpy.utils.script_path_user())) - output.write("pref scripts: %r\n" % (bpy.utils.script_path_pref())) + output.write("pref scripts:\n") + for p in bpy.utils.script_paths_pref(): + output.write("\t%r\n" % p) output.write("datafiles: %r\n" % (bpy.utils.user_resource('DATAFILES'))) output.write("config: %r\n" % (bpy.utils.user_resource('CONFIG'))) output.write("scripts : %r\n" % (bpy.utils.user_resource('SCRIPTS'))) diff --git a/release/scripts/startup/bl_operators/userpref.py b/release/scripts/startup/bl_operators/userpref.py index d0134bd076f..2938ab56b09 100644 --- a/release/scripts/startup/bl_operators/userpref.py +++ b/release/scripts/startup/bl_operators/userpref.py @@ -587,12 +587,18 @@ class PREFERENCES_OT_addon_install(Operator): description="Remove existing add-ons with the same ID", default=True, ) + + def _target_path_items(_self, context): + paths = context.preferences.filepaths + return ( + ('DEFAULT', "Default", ""), + None, + *[(item.name, item.name, "") for index, item in enumerate(paths.script_directories) if item.path], + ) + target: EnumProperty( name="Target Path", - items=( - ('DEFAULT', "Default", ""), - ('PREFS', "Preferences", ""), - ), + items=_target_path_items, ) filepath: StringProperty( @@ -626,9 +632,11 @@ class PREFERENCES_OT_addon_install(Operator): # Don't use `bpy.utils.script_paths(path="addons")` because we may not be able to write to it. path_addons = bpy.utils.user_resource('SCRIPTS', path="addons", create=True) else: - path_addons = context.preferences.filepaths.script_directory - if path_addons: - path_addons = os.path.join(path_addons, "addons") + paths = context.preferences.filepaths + for script_directory in paths.script_directories: + if script_directory.name == self.target: + path_addons = os.path.join(script_directory.path, "addons") + break if not path_addons: self.report({'ERROR'}, "Failed to get add-ons path") @@ -1139,6 +1147,60 @@ class PREFERENCES_OT_studiolight_show(Operator): return {'FINISHED'} +class PREFERENCES_OT_script_directory_new(Operator): + bl_idname = "preferences.script_directory_add" + bl_label = "Add Python Script Directory" + + directory: StringProperty( + subtype='DIR_PATH', + ) + filter_folder: BoolProperty( + name="Filter Folders", + default=True, + options={'HIDDEN'}, + ) + + def execute(self, context): + from pathlib import Path + + script_directories = context.preferences.filepaths.script_directories + + new_dir = script_directories.new() + # Assign path selected via file browser. + new_dir.path = self.directory + new_dir.name = Path(self.directory).name + + assert(context.preferences.is_dirty == True) + + return {'FINISHED'} + + def invoke(self, context, _event): + wm = context.window_manager + + wm.fileselect_add(self) + return {'RUNNING_MODAL'} + + +class PREFERENCES_OT_script_directory_remove(Operator): + bl_idname = "preferences.script_directory_remove" + bl_label = "Remove Python Script Directory" + + index: IntProperty( + name="Index", + description="Index of the script directory to remove", + ) + + def execute(self, context): + script_directories = context.preferences.filepaths.script_directories + for search_index, script_directory in enumerate(script_directories): + if search_index == self.index: + script_directories.remove(script_directory) + break + + assert(context.preferences.is_dirty == True) + + return {'FINISHED'} + classes = ( PREFERENCES_OT_addon_disable, PREFERENCES_OT_addon_enable, @@ -1164,4 +1226,6 @@ classes = ( PREFERENCES_OT_studiolight_uninstall, PREFERENCES_OT_studiolight_copy_settings, PREFERENCES_OT_studiolight_show, + PREFERENCES_OT_script_directory_new, + PREFERENCES_OT_script_directory_remove, ) diff --git a/release/scripts/startup/bl_ui/space_userpref.py b/release/scripts/startup/bl_ui/space_userpref.py index f0dedc42c54..095b42ec535 100644 --- a/release/scripts/startup/bl_ui/space_userpref.py +++ b/release/scripts/startup/bl_ui/space_userpref.py @@ -1330,11 +1330,52 @@ class USERPREF_PT_file_paths_data(FilePathsPanel, Panel): col = self.layout.column() col.prop(paths, "font_directory", text="Fonts") col.prop(paths, "texture_directory", text="Textures") - col.prop(paths, "script_directory", text="Scripts") col.prop(paths, "sound_directory", text="Sounds") col.prop(paths, "temporary_directory", text="Temporary Files") +class USERPREF_PT_file_paths_script_directories(FilePathsPanel, Panel): + bl_label = "Script Directories" + + def draw(self, context): + layout = self.layout + + paths = context.preferences.filepaths + + if len(paths.script_directories) == 0: + layout.operator("preferences.script_directory_add", text="Add", icon='ADD') + return + + layout.use_property_split = False + layout.use_property_decorate = False + + box = layout.box() + split = box.split(factor=0.35) + name_col = split.column() + path_col = split.column() + + row = name_col.row(align=True) # Padding + row.separator() + row.label(text="Name") + + row = path_col.row(align=True) # Padding + row.separator() + row.label(text="Path") + + row.operator("preferences.script_directory_add", text="", icon='ADD', emboss=False) + + for i, script_directory in enumerate(paths.script_directories): + row = name_col.row() + row.alert = not script_directory.name + row.prop(script_directory, "name", text="") + + row = path_col.row() + subrow = row.row() + subrow.alert = not script_directory.path + subrow.prop(script_directory, "path", text="") + row.operator("preferences.script_directory_remove", text="", icon='X', emboss=False).index = i + + class USERPREF_PT_file_paths_render(FilePathsPanel, Panel): bl_label = "Render" @@ -1875,7 +1916,7 @@ class USERPREF_PT_addons(AddOnPanel, Panel): if not user_addon_paths: for path in ( bpy.utils.script_path_user(), - bpy.utils.script_path_pref(), + *bpy.utils.script_paths_pref(), ): if path is not None: user_addon_paths.append(os.path.join(path, "addons")) @@ -1907,7 +1948,7 @@ class USERPREF_PT_addons(AddOnPanel, Panel): addon_user_dirs = tuple( p for p in ( - os.path.join(prefs.filepaths.script_directory, "addons"), + *[os.path.join(pref_p, "addons") for pref_p in bpy.utils.script_path_user()], bpy.utils.user_resource('SCRIPTS', path="addons"), ) if p @@ -2454,6 +2495,7 @@ classes = ( USERPREF_PT_theme_strip_colors, USERPREF_PT_file_paths_data, + USERPREF_PT_file_paths_script_directories, USERPREF_PT_file_paths_render, USERPREF_PT_file_paths_applications, USERPREF_PT_file_paths_development, diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 672054b7c52..e15c9314e95 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -27,7 +27,7 @@ extern "C" { /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 0 +#define BLENDER_FILE_SUBVERSION 1 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and show a warning if the file diff --git a/source/blender/blenkernel/intern/blender.c b/source/blender/blenkernel/intern/blender.c index 3598201d906..305ac5f62cc 100644 --- a/source/blender/blenkernel/intern/blender.c +++ b/source/blender/blenkernel/intern/blender.c @@ -296,6 +296,7 @@ void BKE_blender_userdef_data_free(UserDef *userdef, bool clear_fonts) } BLI_freelistN(&userdef->autoexec_paths); + BLI_freelistN(&userdef->script_directories); BLI_freelistN(&userdef->asset_libraries); BLI_freelistN(&userdef->uistyles); diff --git a/source/blender/blenloader/intern/readfile.cc b/source/blender/blenloader/intern/readfile.cc index 29730f7f10a..955023e3045 100644 --- a/source/blender/blenloader/intern/readfile.cc +++ b/source/blender/blenloader/intern/readfile.cc @@ -3733,6 +3733,7 @@ static BHead *read_userdef(BlendFileData *bfd, FileData *fd, BHead *bhead) BLO_read_list(reader, &user->user_menus); BLO_read_list(reader, &user->addons); BLO_read_list(reader, &user->autoexec_paths); + BLO_read_list(reader, &user->script_directories); BLO_read_list(reader, &user->asset_libraries); LISTBASE_FOREACH (wmKeyMap *, keymap, &user->user_keymaps) { diff --git a/source/blender/blenloader/intern/versioning_userdef.c b/source/blender/blenloader/intern/versioning_userdef.c index 3c4b1a04d63..3c81609a0a8 100644 --- a/source/blender/blenloader/intern/versioning_userdef.c +++ b/source/blender/blenloader/intern/versioning_userdef.c @@ -33,6 +33,8 @@ #include "GPU_platform.h" +#include "MEM_guardedalloc.h" + #include "readfile.h" /* Own include. */ #include "WM_types.h" @@ -779,6 +781,15 @@ void blo_do_versions_userdef(UserDef *userdef) } } + if (!USER_VERSION_ATLEAST(306, 1)) { + if (userdef->pythondir[0]) { + NamedDirectoryPathEntry *script_path = MEM_callocN(sizeof(*script_path), + "Versioning user script path"); + STRNCPY(script_path->dir_path, userdef->pythondir); + BLI_addhead(&userdef->script_directories, script_path); + } + } + /** * Versioning code until next subversion bump goes here. * diff --git a/source/blender/blenloader/intern/writefile.cc b/source/blender/blenloader/intern/writefile.cc index a95e3180b67..ea6119976a5 100644 --- a/source/blender/blenloader/intern/writefile.cc +++ b/source/blender/blenloader/intern/writefile.cc @@ -922,6 +922,10 @@ static void write_userdef(BlendWriter *writer, const UserDef *userdef) BLO_write_struct(writer, bPathCompare, path_cmp); } + LISTBASE_FOREACH (const NamedDirectoryPathEntry *, python_dir, &userdef->script_directories) { + BLO_write_struct(writer, NamedDirectoryPathEntry, python_dir); + } + LISTBASE_FOREACH (const bUserAssetLibrary *, asset_library_ref, &userdef->asset_libraries) { BLO_write_struct(writer, bUserAssetLibrary, asset_library_ref); } diff --git a/source/blender/editors/space_file/fsmenu.c b/source/blender/editors/space_file/fsmenu.c index 0e46a2eafe3..c62a6eb80ad 100644 --- a/source/blender/editors/space_file/fsmenu.c +++ b/source/blender/editors/space_file/fsmenu.c @@ -1033,7 +1033,14 @@ void fsmenu_read_system(struct FSMenu *fsmenu, int read_bookmarks) FS_UDIR_PATH(U.fontdir, ICON_FILE_FONT) FS_UDIR_PATH(U.textudir, ICON_FILE_IMAGE) - FS_UDIR_PATH(U.pythondir, ICON_FILE_SCRIPT) + LISTBASE_FOREACH (NamedDirectoryPathEntry *, pythondir, &U.script_directories) { + fsmenu_insert_entry(fsmenu, + FS_CATEGORY_OTHER, + pythondir->dir_path, + pythondir->name, + ICON_FILE_SCRIPT, + FS_INSERT_LAST); + } FS_UDIR_PATH(U.sounddir, ICON_FILE_SOUND) FS_UDIR_PATH(U.tempdir, ICON_TEMP) diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index 519510cd18a..5651d3a71cc 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -665,6 +665,16 @@ typedef struct UserDef_Experimental { #define USER_EXPERIMENTAL_TEST(userdef, member) \ (((userdef)->flag & USER_DEVELOPER_UI) && ((userdef)->experimental).member) +/** + * Container to store multiple directory paths and a name for each as a #ListBase. + */ +typedef struct NamedDirectoryPathEntry { + struct NamedDirectoryPathEntry *next, *prev; + + char name[64]; /* MAX_NAME */ + char dir_path[768]; /* FILE_MAXDIR */ +} NamedDirectoryPathEntry; + typedef struct UserDef { DNA_DEFINE_CXX_METHODS(UserDef) @@ -689,22 +699,8 @@ typedef struct UserDef { /** 768 = FILE_MAXDIR. */ char render_cachedir[768]; char textudir[768]; - /** - * Optional user location for scripts. - * - * This supports the same layout as Blender's scripts directory `release/scripts`. - * - * \note Unlike most paths, changing this is not fully supported at run-time, - * requiring a restart to properly take effect. Supporting this would cause complications as - * the script path can contain `startup`, `addons` & `modules` etc. properly unwinding the - * Python environment to the state it _would_ have been in gets complicated. - * - * Although this is partially supported as the `sys.path` is refreshed when loading preferences. - * This is done to support #PREFERENCES_OT_copy_prev which is available to the user when they - * launch with a new version of Blender. In this case setting the script path on top of - * factory settings will work without problems. - */ - char pythondir[768]; + /* Deprecated, use #UserDef.script_directories instead. */ + char pythondir[768] DNA_DEPRECATED; char sounddir[768]; char i18ndir[768]; /** 1024 = FILE_MAX. */ @@ -776,6 +772,22 @@ typedef struct UserDef { struct ListBase user_keyconfig_prefs; struct ListBase addons; struct ListBase autoexec_paths; + /** + * Optional user locations for Python scripts. + * + * This supports the same layout as Blender's scripts directory `release/scripts`. + * + * \note Unlike most paths, changing this is not fully supported at run-time, + * requiring a restart to properly take effect. Supporting this would cause complications as + * the script path can contain `startup`, `addons` & `modules` etc. properly unwinding the + * Python environment to the state it _would_ have been in gets complicated. + * + * Although this is partially supported as the `sys.path` is refreshed when loading preferences. + * This is done to support #PREFERENCES_OT_copy_prev which is available to the user when they + * launch with a new version of Blender. In this case setting the script path on top of + * factory settings will work without problems. + */ + ListBase script_directories; /* #NamedDirectoryPathEntry */ /** #bUserMenu. */ struct ListBase user_menus; /** #bUserAssetLibrary */ diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c index f9e10a4eaf1..f61b3f79bff 100644 --- a/source/blender/makesrna/intern/rna_userdef.c +++ b/source/blender/makesrna/intern/rna_userdef.c @@ -150,6 +150,7 @@ static const EnumPropertyItem rna_enum_preference_gpu_backend_items[] = { #ifdef RNA_RUNTIME # include "BLI_math_vector.h" +# include "BLI_string_utils.h" # include "DNA_object_types.h" # include "DNA_screen_types.h" @@ -344,6 +345,40 @@ static void rna_userdef_script_autoexec_update(Main *UNUSED(bmain), USERDEF_TAG_DIRTY; } +static void rna_userdef_script_directory_name_set(PointerRNA *ptr, const char *value) +{ + NamedDirectoryPathEntry *script_dir = ptr->data; + + BLI_strncpy_utf8(script_dir->name, value, sizeof(script_dir->name)); + BLI_uniquename(&U.script_directories, + script_dir, + value, + '.', + offsetof(NamedDirectoryPathEntry, name), + sizeof(script_dir->name)); +} + +static NamedDirectoryPathEntry *rna_userdef_script_directory_new(void) +{ + NamedDirectoryPathEntry *script_dir = MEM_callocN(sizeof(*script_dir), __func__); + BLI_addtail(&U.script_directories, script_dir); + USERDEF_TAG_DIRTY; + return script_dir; +} + +static void rna_userdef_script_directory_remove(ReportList *reports, PointerRNA *ptr) +{ + NamedDirectoryPathEntry *script_dir = ptr->data; + if (BLI_findindex(&U.script_directories, script_dir) == -1) { + BKE_report(reports, RPT_ERROR, "Script directory not found"); + return; + } + + BLI_freelinkN(&U.script_directories, script_dir); + RNA_POINTER_INVALIDATE(ptr); + USERDEF_TAG_DIRTY; +} + static void rna_userdef_load_ui_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr) { UserDef *userdef = (UserDef *)ptr->data; @@ -6161,6 +6196,57 @@ static void rna_def_userdef_filepaths_asset_library(BlenderRNA *brna) RNA_def_property_update(prop, 0, "rna_userdef_update"); } +static void rna_def_userdef_script_directory(BlenderRNA *brna) +{ + StructRNA *srna = RNA_def_struct(brna, "ScriptDirectory", NULL); + RNA_def_struct_sdna(srna, "NamedDirectoryPathEntry"); + RNA_def_struct_clear_flag(srna, STRUCT_UNDO); + RNA_def_struct_ui_text(srna, "Python Scripts Directory", ""); + + PropertyRNA *prop; + + prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE); + RNA_def_property_ui_text(prop, "Name", "Identifier for the Python scripts directory"); + RNA_def_property_string_funcs(prop, NULL, NULL, "rna_userdef_script_directory_name_set"); + RNA_def_struct_name_property(srna, prop); + RNA_def_property_update(prop, 0, "rna_userdef_update"); + + prop = RNA_def_property(srna, "path", PROP_STRING, PROP_DIRPATH); + RNA_def_property_string_sdna(prop, NULL, "dir_path"); + RNA_def_property_ui_text( + prop, + "Python Scripts Directory", + "Alternate script path, matching the default layout with sub-directories: startup, add-ons, " + "modules, and presets (requires restart)"); + /* TODO: editing should reset sys.path! */ +} + +static void rna_def_userdef_script_directory_collection(BlenderRNA *brna, PropertyRNA *cprop) +{ + StructRNA *srna; + FunctionRNA *func; + PropertyRNA *parm; + + RNA_def_property_srna(cprop, "ScriptDirectoryCollection"); + srna = RNA_def_struct(brna, "ScriptDirectoryCollection", NULL); + RNA_def_struct_clear_flag(srna, STRUCT_UNDO); + RNA_def_struct_ui_text(srna, "Python Scripts Directories", ""); + + func = RNA_def_function(srna, "new", "rna_userdef_script_directory_new"); + RNA_def_function_flag(func, FUNC_NO_SELF); + RNA_def_function_ui_description(func, "Add a new python script directory"); + /* return type */ + parm = RNA_def_pointer(func, "script_directory", "ScriptDirectory", "", ""); + RNA_def_function_return(func, parm); + + func = RNA_def_function(srna, "remove", "rna_userdef_script_directory_remove"); + RNA_def_function_flag(func, FUNC_NO_SELF | FUNC_USE_REPORTS); + RNA_def_function_ui_description(func, "Remove a python script directory"); + parm = RNA_def_pointer(func, "script_directory", "ScriptDirectory", "", ""); + RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR); + RNA_def_parameter_clear_flags(parm, PROP_THICK_WRAP, 0); +} + static void rna_def_userdef_filepaths(BlenderRNA *brna) { PropertyRNA *prop; @@ -6260,14 +6346,12 @@ static void rna_def_userdef_filepaths(BlenderRNA *brna) "Render Output Directory", "The default directory for rendering output, for new scenes"); - prop = RNA_def_property(srna, "script_directory", PROP_STRING, PROP_DIRPATH); - RNA_def_property_string_sdna(prop, NULL, "pythondir"); - RNA_def_property_ui_text( - prop, - "Python Scripts Directory", - "Alternate script path, matching the default layout with subdirectories: " - "`startup`, `addons`, `modules`, and `presets` (requires restart)"); - /* TODO: editing should reset sys.path! */ + rna_def_userdef_script_directory(brna); + + prop = RNA_def_property(srna, "script_directories", PROP_COLLECTION, PROP_NONE); + RNA_def_property_struct_type(prop, "ScriptDirectory"); + RNA_def_property_ui_text(prop, "Python Scripts Directory", ""); + rna_def_userdef_script_directory_collection(brna, prop); prop = RNA_def_property(srna, "i18n_branches_directory", PROP_STRING, PROP_DIRPATH); RNA_def_property_string_sdna(prop, NULL, "i18ndir"); -- 2.30.2 From 7feb808e291ce16b545c9638c52d5f7665fa5c70 Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Fri, 17 Feb 2023 12:52:33 +0100 Subject: [PATCH 2/6] Fix Python error on startup with no script path configured --- release/scripts/modules/bpy/utils/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/release/scripts/modules/bpy/utils/__init__.py b/release/scripts/modules/bpy/utils/__init__.py index 300c8d063b4..b049769431d 100644 --- a/release/scripts/modules/bpy/utils/__init__.py +++ b/release/scripts/modules/bpy/utils/__init__.py @@ -346,7 +346,8 @@ def script_path_pref(): first valid of these script paths for now, for compatibility. """ # TODO how to handle deprecation of this? - return script_paths_pref()[0] + script_paths = script_paths_pref() + return script_paths[0] if len(script_paths) > 0 else "" def script_paths_pref(): -- 2.30.2 From 0631840f5db119848a2a2958faadf43c28182787 Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Fri, 17 Feb 2023 14:56:02 +0100 Subject: [PATCH 3/6] Address review comments --- release/scripts/modules/bpy/utils/__init__.py | 4 ++-- .../scripts/startup/bl_operators/userpref.py | 10 ++++----- .../scripts/startup/bl_ui/space_userpref.py | 22 +++++++++---------- .../blenloader/intern/versioning_userdef.c | 4 ++-- source/blender/makesdna/DNA_userdef_types.h | 2 +- .../blender/makesdna/intern/dna_rename_defs.h | 1 + source/blender/makesrna/intern/rna_userdef.c | 7 +++++- 7 files changed, 28 insertions(+), 22 deletions(-) diff --git a/release/scripts/modules/bpy/utils/__init__.py b/release/scripts/modules/bpy/utils/__init__.py index b049769431d..ef89fec5d9f 100644 --- a/release/scripts/modules/bpy/utils/__init__.py +++ b/release/scripts/modules/bpy/utils/__init__.py @@ -354,8 +354,8 @@ def script_paths_pref(): """returns the user preference script directory paths or None""" paths = [] for script_directory in _preferences.filepaths.script_directories: - if script_directory.path: - paths.append(_os.path.normpath(script_directory.path)) + if script_directory.directory: + paths.append(_os.path.normpath(script_directory.directory)) return paths diff --git a/release/scripts/startup/bl_operators/userpref.py b/release/scripts/startup/bl_operators/userpref.py index 2938ab56b09..bf5f558f272 100644 --- a/release/scripts/startup/bl_operators/userpref.py +++ b/release/scripts/startup/bl_operators/userpref.py @@ -593,7 +593,7 @@ class PREFERENCES_OT_addon_install(Operator): return ( ('DEFAULT', "Default", ""), None, - *[(item.name, item.name, "") for index, item in enumerate(paths.script_directories) if item.path], + *[(item.name, item.name, "") for index, item in enumerate(paths.script_directories) if item.directory], ) target: EnumProperty( @@ -635,7 +635,7 @@ class PREFERENCES_OT_addon_install(Operator): paths = context.preferences.filepaths for script_directory in paths.script_directories: if script_directory.name == self.target: - path_addons = os.path.join(script_directory.path, "addons") + path_addons = os.path.join(script_directory.directory, "addons") break if not path_addons: @@ -1161,14 +1161,14 @@ class PREFERENCES_OT_script_directory_new(Operator): ) def execute(self, context): - from pathlib import Path + import os script_directories = context.preferences.filepaths.script_directories new_dir = script_directories.new() # Assign path selected via file browser. - new_dir.path = self.directory - new_dir.name = Path(self.directory).name + new_dir.directory = self.directory + new_dir.name = os.path.basename(self.directory.rstrip(os.sep)) assert(context.preferences.is_dirty == True) diff --git a/release/scripts/startup/bl_ui/space_userpref.py b/release/scripts/startup/bl_ui/space_userpref.py index 095b42ec535..588a9a21ac9 100644 --- a/release/scripts/startup/bl_ui/space_userpref.py +++ b/release/scripts/startup/bl_ui/space_userpref.py @@ -1336,16 +1336,16 @@ class USERPREF_PT_file_paths_data(FilePathsPanel, Panel): class USERPREF_PT_file_paths_script_directories(FilePathsPanel, Panel): bl_label = "Script Directories" - + def draw(self, context): layout = self.layout - + paths = context.preferences.filepaths - + if len(paths.script_directories) == 0: layout.operator("preferences.script_directory_add", text="Add", icon='ADD') return - + layout.use_property_split = False layout.use_property_decorate = False @@ -1353,26 +1353,26 @@ class USERPREF_PT_file_paths_script_directories(FilePathsPanel, Panel): split = box.split(factor=0.35) name_col = split.column() path_col = split.column() - + row = name_col.row(align=True) # Padding row.separator() row.label(text="Name") - + row = path_col.row(align=True) # Padding row.separator() row.label(text="Path") - + row.operator("preferences.script_directory_add", text="", icon='ADD', emboss=False) - + for i, script_directory in enumerate(paths.script_directories): row = name_col.row() row.alert = not script_directory.name row.prop(script_directory, "name", text="") - + row = path_col.row() subrow = row.row() - subrow.alert = not script_directory.path - subrow.prop(script_directory, "path", text="") + subrow.alert = not script_directory.directory + subrow.prop(script_directory, "directory", text="") row.operator("preferences.script_directory_remove", text="", icon='X', emboss=False).index = i diff --git a/source/blender/blenloader/intern/versioning_userdef.c b/source/blender/blenloader/intern/versioning_userdef.c index 3c81609a0a8..b18ad3ba1dd 100644 --- a/source/blender/blenloader/intern/versioning_userdef.c +++ b/source/blender/blenloader/intern/versioning_userdef.c @@ -782,10 +782,10 @@ void blo_do_versions_userdef(UserDef *userdef) } if (!USER_VERSION_ATLEAST(306, 1)) { - if (userdef->pythondir[0]) { + if (userdef->pythondir_legacy[0]) { NamedDirectoryPathEntry *script_path = MEM_callocN(sizeof(*script_path), "Versioning user script path"); - STRNCPY(script_path->dir_path, userdef->pythondir); + STRNCPY(script_path->dir_path, userdef->pythondir_legacy); BLI_addhead(&userdef->script_directories, script_path); } } diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index 5651d3a71cc..c66391320ca 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -700,7 +700,7 @@ typedef struct UserDef { char render_cachedir[768]; char textudir[768]; /* Deprecated, use #UserDef.script_directories instead. */ - char pythondir[768] DNA_DEPRECATED; + char pythondir_legacy[768] DNA_DEPRECATED; char sounddir[768]; char i18ndir[768]; /** 1024 = FILE_MAX. */ diff --git a/source/blender/makesdna/intern/dna_rename_defs.h b/source/blender/makesdna/intern/dna_rename_defs.h index ab543eb611f..65b856fd793 100644 --- a/source/blender/makesdna/intern/dna_rename_defs.h +++ b/source/blender/makesdna/intern/dna_rename_defs.h @@ -139,6 +139,7 @@ DNA_STRUCT_RENAME_ELEM(ThemeSpace, scrubbing_background, time_scrub_background) DNA_STRUCT_RENAME_ELEM(ThemeSpace, show_back_grad, background_type) DNA_STRUCT_RENAME_ELEM(UVProjectModifierData, num_projectors, projectors_num) DNA_STRUCT_RENAME_ELEM(UserDef, gp_manhattendist, gp_manhattandist) +DNA_STRUCT_RENAME_ELEM(UserDef, pythondir, pythondir_legacy) DNA_STRUCT_RENAME_ELEM(VFont, name, filepath) DNA_STRUCT_RENAME_ELEM(View3D, far, clip_end) DNA_STRUCT_RENAME_ELEM(View3D, near, clip_start) diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c index f61b3f79bff..c15e3467039 100644 --- a/source/blender/makesrna/intern/rna_userdef.c +++ b/source/blender/makesrna/intern/rna_userdef.c @@ -349,6 +349,11 @@ static void rna_userdef_script_directory_name_set(PointerRNA *ptr, const char *v { NamedDirectoryPathEntry *script_dir = ptr->data; + if (STREQ(value, "DEFAULT")) { + BKE_report(NULL, RPT_ERROR, "Name 'DEFAULT' is reserved for internal use and cannot be used"); + return; + } + BLI_strncpy_utf8(script_dir->name, value, sizeof(script_dir->name)); BLI_uniquename(&U.script_directories, script_dir, @@ -6211,7 +6216,7 @@ static void rna_def_userdef_script_directory(BlenderRNA *brna) RNA_def_struct_name_property(srna, prop); RNA_def_property_update(prop, 0, "rna_userdef_update"); - prop = RNA_def_property(srna, "path", PROP_STRING, PROP_DIRPATH); + prop = RNA_def_property(srna, "directory", PROP_STRING, PROP_DIRPATH); RNA_def_property_string_sdna(prop, NULL, "dir_path"); RNA_def_property_ui_text( prop, -- 2.30.2 From 135c8aa032b868a6b979ad8f9e5df93645ceeacb Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Fri, 24 Mar 2023 16:02:23 +0100 Subject: [PATCH 4/6] Address points from review --- scripts/modules/bpy/utils/__init__.py | 14 ++++++++++---- scripts/startup/bl_operators/userpref.py | 4 ++-- source/blender/makesdna/DNA_userdef_types.h | 1 + source/blender/makesrna/intern/rna_userdef.c | 13 +++++++++++-- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/scripts/modules/bpy/utils/__init__.py b/scripts/modules/bpy/utils/__init__.py index 34189689335..a2a77d5a011 100644 --- a/scripts/modules/bpy/utils/__init__.py +++ b/scripts/modules/bpy/utils/__init__.py @@ -345,17 +345,23 @@ def script_path_pref(): DEPRECATED. Use `script_paths_pref` which supports multiple script paths now. Returns the first valid of these script paths for now, for compatibility. """ - # TODO how to handle deprecation of this? + from warnings import warn + warn( + "bpy.utils.script_path_pref() is deprecated use script_paths_pref() instead!", + DeprecationWarning, + stacklevel=2, + ) script_paths = script_paths_pref() return script_paths[0] if len(script_paths) > 0 else "" def script_paths_pref(): - """returns the user preference script directory paths or None""" + """Returns a list of user preference script directories.""" paths = [] for script_directory in _preferences.filepaths.script_directories: - if script_directory.directory: - paths.append(_os.path.normpath(script_directory.directory)) + directory = script_directory.directory + if directory: + paths.append(_os.path.normpath(directory)) return paths diff --git a/scripts/startup/bl_operators/userpref.py b/scripts/startup/bl_operators/userpref.py index bf5f558f272..a6efa7f149a 100644 --- a/scripts/startup/bl_operators/userpref.py +++ b/scripts/startup/bl_operators/userpref.py @@ -1170,7 +1170,7 @@ class PREFERENCES_OT_script_directory_new(Operator): new_dir.directory = self.directory new_dir.name = os.path.basename(self.directory.rstrip(os.sep)) - assert(context.preferences.is_dirty == True) + assert context.preferences.is_dirty == True return {'FINISHED'} @@ -1197,7 +1197,7 @@ class PREFERENCES_OT_script_directory_remove(Operator): script_directories.remove(script_directory) break - assert(context.preferences.is_dirty == True) + assert context.preferences.is_dirty == True return {'FINISHED'} diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index 313c09c77d2..7f2ab256500 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -685,6 +685,7 @@ typedef struct UserDef_Experimental { typedef struct NamedDirectoryPathEntry { struct NamedDirectoryPathEntry *next, *prev; + /** Depending on the use case of this struct, this should probably be a unique name. */ char name[64]; /* MAX_NAME */ char dir_path[768]; /* FILE_MAXDIR */ } NamedDirectoryPathEntry; diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c index b2f69f08e3a..5327960e8ee 100644 --- a/source/blender/makesrna/intern/rna_userdef.c +++ b/source/blender/makesrna/intern/rna_userdef.c @@ -348,10 +348,19 @@ static void rna_userdef_script_autoexec_update(Main *UNUSED(bmain), static void rna_userdef_script_directory_name_set(PointerRNA *ptr, const char *value) { NamedDirectoryPathEntry *script_dir = ptr->data; + bool value_invalid = false; + if (!value[0]) { + value_invalid = true; + } if (STREQ(value, "DEFAULT")) { - BKE_report(NULL, RPT_ERROR, "Name 'DEFAULT' is reserved for internal use and cannot be used"); - return; + BKE_report( + NULL, RPT_WARNING, "Name 'DEFAULT' is reserved for internal use and cannot be used"); + value_invalid = true; + } + + if (value_invalid) { + value = DATA_("Untitled"); } BLI_strncpy_utf8(script_dir->name, value, sizeof(script_dir->name)); -- 2.30.2 From 2663599d78b2b4e0eedc136093e4fbdbb08100d5 Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Fri, 24 Mar 2023 16:14:26 +0100 Subject: [PATCH 5/6] Make generic DNA struct specific to script directories While it may seem useful to have this kind of generic struct, it would cause issues if the generic struct wouldn't be enough in future anymore. Changing the DNA struct typw would be hard to do versioning for. --- .../blender/blenloader/intern/versioning_userdef.c | 8 ++++---- source/blender/blenloader/intern/writefile.cc | 4 ++-- source/blender/editors/space_file/fsmenu.c | 6 +++--- source/blender/makesdna/DNA_userdef_types.h | 10 +++++----- source/blender/makesrna/intern/rna_userdef.c | 12 ++++++------ 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/source/blender/blenloader/intern/versioning_userdef.c b/source/blender/blenloader/intern/versioning_userdef.c index 479ca2ce616..ef8b98333bb 100644 --- a/source/blender/blenloader/intern/versioning_userdef.c +++ b/source/blender/blenloader/intern/versioning_userdef.c @@ -802,10 +802,10 @@ void blo_do_versions_userdef(UserDef *userdef) if (!USER_VERSION_ATLEAST(306, 5)) { if (userdef->pythondir_legacy[0]) { - NamedDirectoryPathEntry *script_path = MEM_callocN(sizeof(*script_path), - "Versioning user script path"); - STRNCPY(script_path->dir_path, userdef->pythondir_legacy); - BLI_addhead(&userdef->script_directories, script_path); + bUserScriptDirectory *script_dir = MEM_callocN(sizeof(*script_dir), + "Versioning user script path"); + STRNCPY(script_dir->dir_path, userdef->pythondir_legacy); + BLI_addhead(&userdef->script_directories, script_dir); } } diff --git a/source/blender/blenloader/intern/writefile.cc b/source/blender/blenloader/intern/writefile.cc index 83cdecc829c..88434a65d1b 100644 --- a/source/blender/blenloader/intern/writefile.cc +++ b/source/blender/blenloader/intern/writefile.cc @@ -922,8 +922,8 @@ static void write_userdef(BlendWriter *writer, const UserDef *userdef) BLO_write_struct(writer, bPathCompare, path_cmp); } - LISTBASE_FOREACH (const NamedDirectoryPathEntry *, python_dir, &userdef->script_directories) { - BLO_write_struct(writer, NamedDirectoryPathEntry, python_dir); + LISTBASE_FOREACH (const bUserScriptDirectory *, script_dir, &userdef->script_directories) { + BLO_write_struct(writer, bUserScriptDirectory, script_dir); } LISTBASE_FOREACH (const bUserAssetLibrary *, asset_library_ref, &userdef->asset_libraries) { diff --git a/source/blender/editors/space_file/fsmenu.c b/source/blender/editors/space_file/fsmenu.c index c62a6eb80ad..41b78ff3562 100644 --- a/source/blender/editors/space_file/fsmenu.c +++ b/source/blender/editors/space_file/fsmenu.c @@ -1033,11 +1033,11 @@ void fsmenu_read_system(struct FSMenu *fsmenu, int read_bookmarks) FS_UDIR_PATH(U.fontdir, ICON_FILE_FONT) FS_UDIR_PATH(U.textudir, ICON_FILE_IMAGE) - LISTBASE_FOREACH (NamedDirectoryPathEntry *, pythondir, &U.script_directories) { + LISTBASE_FOREACH (bUserScriptDirectory *, script_dir, &U.script_directories) { fsmenu_insert_entry(fsmenu, FS_CATEGORY_OTHER, - pythondir->dir_path, - pythondir->name, + script_dir->dir_path, + script_dir->name, ICON_FILE_SCRIPT, FS_INSERT_LAST); } diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index 7f2ab256500..70aed0eab5b 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -682,13 +682,13 @@ typedef struct UserDef_Experimental { /** * Container to store multiple directory paths and a name for each as a #ListBase. */ -typedef struct NamedDirectoryPathEntry { - struct NamedDirectoryPathEntry *next, *prev; +typedef struct bUserScriptDirectory { + struct bUserScriptDirectory *next, *prev; - /** Depending on the use case of this struct, this should probably be a unique name. */ + /** Name must be unique. */ char name[64]; /* MAX_NAME */ char dir_path[768]; /* FILE_MAXDIR */ -} NamedDirectoryPathEntry; +} bUserScriptDirectory; typedef struct UserDef { DNA_DEFINE_CXX_METHODS(UserDef) @@ -802,7 +802,7 @@ typedef struct UserDef { * launch with a new version of Blender. In this case setting the script path on top of * factory settings will work without problems. */ - ListBase script_directories; /* #NamedDirectoryPathEntry */ + ListBase script_directories; /* #bUserScriptDirectory */ /** #bUserMenu. */ struct ListBase user_menus; /** #bUserAssetLibrary */ diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c index 5327960e8ee..1bafc5283fd 100644 --- a/source/blender/makesrna/intern/rna_userdef.c +++ b/source/blender/makesrna/intern/rna_userdef.c @@ -347,7 +347,7 @@ static void rna_userdef_script_autoexec_update(Main *UNUSED(bmain), static void rna_userdef_script_directory_name_set(PointerRNA *ptr, const char *value) { - NamedDirectoryPathEntry *script_dir = ptr->data; + bUserScriptDirectory *script_dir = ptr->data; bool value_invalid = false; if (!value[0]) { @@ -368,13 +368,13 @@ static void rna_userdef_script_directory_name_set(PointerRNA *ptr, const char *v script_dir, value, '.', - offsetof(NamedDirectoryPathEntry, name), + offsetof(bUserScriptDirectory, name), sizeof(script_dir->name)); } -static NamedDirectoryPathEntry *rna_userdef_script_directory_new(void) +static bUserScriptDirectory *rna_userdef_script_directory_new(void) { - NamedDirectoryPathEntry *script_dir = MEM_callocN(sizeof(*script_dir), __func__); + bUserScriptDirectory *script_dir = MEM_callocN(sizeof(*script_dir), __func__); BLI_addtail(&U.script_directories, script_dir); USERDEF_TAG_DIRTY; return script_dir; @@ -382,7 +382,7 @@ static NamedDirectoryPathEntry *rna_userdef_script_directory_new(void) static void rna_userdef_script_directory_remove(ReportList *reports, PointerRNA *ptr) { - NamedDirectoryPathEntry *script_dir = ptr->data; + bUserScriptDirectory *script_dir = ptr->data; if (BLI_findindex(&U.script_directories, script_dir) == -1) { BKE_report(reports, RPT_ERROR, "Script directory not found"); return; @@ -6264,7 +6264,7 @@ static void rna_def_userdef_filepaths_asset_library(BlenderRNA *brna) static void rna_def_userdef_script_directory(BlenderRNA *brna) { StructRNA *srna = RNA_def_struct(brna, "ScriptDirectory", NULL); - RNA_def_struct_sdna(srna, "NamedDirectoryPathEntry"); + RNA_def_struct_sdna(srna, "bUserScriptDirectory"); RNA_def_struct_clear_flag(srna, STRUCT_UNDO); RNA_def_struct_ui_text(srna, "Python Scripts Directory", ""); -- 2.30.2 From f1ae58b8e9dd924c5d7572dc87798295e909e03d Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Fri, 7 Apr 2023 17:04:45 +0200 Subject: [PATCH 6/6] Address points from review --- scripts/modules/bpy/utils/__init__.py | 19 ------------------- .../blenloader/intern/versioning_userdef.c | 4 ++++ source/blender/makesrna/intern/rna_userdef.c | 2 -- 3 files changed, 4 insertions(+), 21 deletions(-) diff --git a/scripts/modules/bpy/utils/__init__.py b/scripts/modules/bpy/utils/__init__.py index a2a77d5a011..406016d82a0 100644 --- a/scripts/modules/bpy/utils/__init__.py +++ b/scripts/modules/bpy/utils/__init__.py @@ -30,7 +30,6 @@ __all__ = ( "previews", "resource_path", "script_path_user", - "script_path_pref", "script_paths", "smpte_from_frame", "smpte_from_seconds", @@ -340,21 +339,6 @@ def script_path_user(): return _os.path.normpath(path) if path else None -def script_path_pref(): - """ - DEPRECATED. Use `script_paths_pref` which supports multiple script paths now. Returns the - first valid of these script paths for now, for compatibility. - """ - from warnings import warn - warn( - "bpy.utils.script_path_pref() is deprecated use script_paths_pref() instead!", - DeprecationWarning, - stacklevel=2, - ) - script_paths = script_paths_pref() - return script_paths[0] if len(script_paths) > 0 else "" - - def script_paths_pref(): """Returns a list of user preference script directories.""" paths = [] @@ -403,9 +387,6 @@ def script_paths(*, subdir=None, user_pref=True, check_all=False, use_user=True) if use_user: base_paths.append(path_user) - if user_pref: - base_paths.append(script_path_pref()) - scripts = [] for path in base_paths: if not path: diff --git a/source/blender/blenloader/intern/versioning_userdef.c b/source/blender/blenloader/intern/versioning_userdef.c index ef8b98333bb..9b7468636cf 100644 --- a/source/blender/blenloader/intern/versioning_userdef.c +++ b/source/blender/blenloader/intern/versioning_userdef.c @@ -31,6 +31,8 @@ #include "BLO_readfile.h" +#include "BLT_translation.h" + #include "GPU_platform.h" #include "MEM_guardedalloc.h" @@ -804,7 +806,9 @@ void blo_do_versions_userdef(UserDef *userdef) if (userdef->pythondir_legacy[0]) { bUserScriptDirectory *script_dir = MEM_callocN(sizeof(*script_dir), "Versioning user script path"); + STRNCPY(script_dir->dir_path, userdef->pythondir_legacy); + STRNCPY(script_dir->name, DATA_("Untitled")); BLI_addhead(&userdef->script_directories, script_dir); } } diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c index 0b29cd8e7ae..24b8b368551 100644 --- a/source/blender/makesrna/intern/rna_userdef.c +++ b/source/blender/makesrna/intern/rna_userdef.c @@ -354,8 +354,6 @@ static void rna_userdef_script_directory_name_set(PointerRNA *ptr, const char *v value_invalid = true; } if (STREQ(value, "DEFAULT")) { - BKE_report( - NULL, RPT_WARNING, "Name 'DEFAULT' is reserved for internal use and cannot be used"); value_invalid = true; } -- 2.30.2