Python: Support multiple custom script directories in Preferences #104876
|
@ -34,7 +34,7 @@ const UserDef U_default = {
|
|||
.renderdir = "//",
|
||||
.render_cachedir = "",
|
||||
.textudir = "//",
|
||||
.pythondir = "",
|
||||
.script_directories = {NULL, NULL},
|
||||
.sounddir = "//",
|
||||
.i18ndir = "",
|
||||
.image_editor = "",
|
||||
|
|
|
@ -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?
|
||||
JulianEisel marked this conversation as resolved
Outdated
|
||||
return script_paths_pref()[0]
|
||||
|
||||
|
||||
def script_paths_pref():
|
||||
"""returns the user preference script directory paths or None"""
|
||||
paths = []
|
||||
JulianEisel marked this conversation as resolved
Outdated
Campbell Barton
commented
Should read Should read `"Returns a list of user preference script directories."` or similar.
|
||||
for script_directory in _preferences.filepaths.script_directories:
|
||||
if script_directory.path:
|
||||
paths.append(_os.path.normpath(script_directory.path))
|
||||
JulianEisel marked this conversation as resolved
Outdated
Campbell Barton
commented
picky assign a value and avoid accessing *picky* assign a value and avoid accessing `script_directory.directory` twice.
|
||||
return paths
|
||||
|
||||
|
||||
def script_paths(*, subdir=None, user_pref=True, check_all=False, use_user=True):
|
||||
|
|
|
@ -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')))
|
||||
|
|
|
@ -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)
|
||||
JulianEisel marked this conversation as resolved
Outdated
Campbell Barton
commented
newer autopep8 removes parenthesis after assert (they're not needed), so prefer not to add them. newer autopep8 removes parenthesis after assert (they're not needed), so prefer not to add them.
|
||||
|
||||
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)
|
||||
JulianEisel marked this conversation as resolved
Outdated
Campbell Barton
commented
newer autopep8 removes parenthesis after assert (they're not needed), so prefer not to add them. newer autopep8 removes parenthesis after assert (they're not needed), so prefer not to add them.
|
||||
|
||||
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,
|
||||
)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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 */
|
||||
JulianEisel marked this conversation as resolved
Outdated
Campbell Barton
commented
Worth mentioning the name is to be unique, and I think it would be best to make this non-empty too (details in other comment). Worth mentioning the name is to be unique, and I think it would be best to make this non-empty too (details in other comment).
Julian Eisel
commented
I was going to make this struct a general utility, since I can see a number of use cases for a name + directory-path struct. Not all may require a unique name, so noted that accordingly. I was going to make this struct a general utility, since I can see a number of use cases for a name + directory-path struct. Not all may require a unique name, so noted that accordingly.
|
||||
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 */
|
||||
|
|
|
@ -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)
|
||||
JulianEisel marked this conversation as resolved
Campbell Barton
commented
If Disallowing empty strings is just a convention from data-block naming, which I think would be good to enforce here too since it's not expected and means the enum identifier for e.g. would be an empty string - probably it works for the most-part but could cause issues (empty strings have a special meaning for enum separators .. for e.g.). Python scripts may do truth checks on a value without realizing an empty string is a valid value... so we can avoid all this with a default name. If `DEFAULT` or an empty string is passed in, use a fallback name such as "Untitled" or "Path", this avoids having to account for unlikely corner cases - maybe the user has a points to a script dir called `DEFAULT` and gets an error in the operator.
Disallowing empty strings is just a convention from data-block naming, which I think would be good to enforce here too since it's not expected and means the enum identifier for e.g. would be an empty string - probably it works for the most-part but could cause issues (empty strings have a special meaning for enum separators .. for e.g.). Python scripts may do truth checks on a value without realizing an empty string is a valid value... so we can avoid all this with a default name.
|
||||
{
|
||||
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));
|
||||
JulianEisel marked this conversation as resolved
Campbell Barton
commented
Rather not warn as in the rare case a user runs into this - it's not as if there is anything to "fix", besides the script author adding explicit checks for "DEFAULT" which isn't useful. Over long names will also be clipped for e.g. which doesn't warn. In general it's possible the name requested in Blender is manipulated. It can't be assumed a string literal will be used verbatim. Rather not warn as in the rare case a user runs into this - it's not as if there is anything to "fix", besides the script author adding explicit checks for "DEFAULT" which isn't useful.
Over long names will also be clipped for e.g. which doesn't warn. In general it's possible the name requested in Blender is manipulated. It can't be assumed a string literal will be used verbatim.
|
||||
}
|
||||
|
||||
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");
|
||||
|
|
This comment can be removed and a deprecation warning can be shown, e.g.
Note that I think we could even consider removing the function, scripts that use it are likely not to function properly and I don't think its a widely used function either (none of the add-ons distributed with Blender use it for e.g.).
Will leave this up to you to decide. Can be removed in a separate change.