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 = "",
|
||||
|
|
|
@ -30,7 +30,6 @@ __all__ = (
|
|||
"previews",
|
||||
"resource_path",
|
||||
"script_path_user",
|
||||
"script_path_pref",
|
||||
"script_paths",
|
||||
"smpte_from_frame",
|
||||
"smpte_from_seconds",
|
||||
|
@ -340,10 +339,14 @@ def script_path_user():
|
|||
return _os.path.normpath(path) if path else None
|
||||
|
||||
|
||||
def script_path_pref():
|
||||
"""returns the user preference or None"""
|
||||
path = _preferences.filepaths.script_directory
|
||||
return _os.path.normpath(path) if path else None
|
||||
def script_paths_pref():
|
||||
"""Returns a list of user preference script directories."""
|
||||
paths = []
|
||||
JulianEisel marked this conversation as resolved
|
||||
for script_directory in _preferences.filepaths.script_directories:
|
||||
directory = script_directory.directory
|
||||
if directory:
|
||||
paths.append(_os.path.normpath(directory))
|
||||
return paths
|
||||
|
||||
|
||||
def script_paths(*, subdir=None, user_pref=True, check_all=False, use_user=True):
|
||||
|
@ -384,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:
|
||||
|
|
|
@ -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.directory],
|
||||
)
|
||||
|
||||
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.directory, "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):
|
||||
import os
|
||||
|
||||
script_directories = context.preferences.filepaths.script_directories
|
||||
|
||||
new_dir = script_directories.new()
|
||||
# Assign path selected via file browser.
|
||||
new_dir.directory = self.directory
|
||||
new_dir.name = os.path.basename(self.directory.rstrip(os.sep))
|
||||
|
||||
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,
|
||||
)
|
||||
|
|
|
@ -1333,11 +1333,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.directory
|
||||
subrow.prop(script_directory, "directory", 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"
|
||||
|
||||
|
@ -1878,7 +1919,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"))
|
||||
|
@ -1910,7 +1951,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
|
||||
|
@ -2457,6 +2498,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,
|
||||
|
|
|
@ -25,7 +25,7 @@ extern "C" {
|
|||
|
||||
/* Blender file format version. */
|
||||
#define BLENDER_FILE_VERSION BLENDER_VERSION
|
||||
#define BLENDER_FILE_SUBVERSION 4
|
||||
#define BLENDER_FILE_SUBVERSION 5
|
||||
|
||||
/* 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);
|
||||
|
|
|
@ -3700,6 +3700,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) {
|
||||
|
|
|
@ -31,8 +31,12 @@
|
|||
|
||||
#include "BLO_readfile.h"
|
||||
|
||||
#include "BLT_translation.h"
|
||||
|
||||
#include "GPU_platform.h"
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "readfile.h" /* Own include. */
|
||||
|
||||
#include "WM_types.h"
|
||||
|
@ -798,6 +802,17 @@ void blo_do_versions_userdef(UserDef *userdef)
|
|||
}
|
||||
}
|
||||
|
||||
if (!USER_VERSION_ATLEAST(306, 5)) {
|
||||
JulianEisel marked this conversation as resolved
Outdated
Campbell Barton
commented
The name should be initialized (suggest "Untitled" ?) - otherwise no strong preference, just don't leave it blank. The name should be initialized (suggest "Untitled" ?) - otherwise no strong preference, just don't leave it blank.
Julian Eisel
commented
Good catch, missed that when introducing the name option. Good catch, missed that when introducing the name option.
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 bUserScriptDirectory *, script_dir, &userdef->script_directories) {
|
||||
BLO_write_struct(writer, bUserScriptDirectory, script_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 (bUserScriptDirectory *, script_dir, &U.script_directories) {
|
||||
fsmenu_insert_entry(fsmenu,
|
||||
FS_CATEGORY_OTHER,
|
||||
script_dir->dir_path,
|
||||
script_dir->name,
|
||||
ICON_FILE_SCRIPT,
|
||||
FS_INSERT_LAST);
|
||||
}
|
||||
FS_UDIR_PATH(U.sounddir, ICON_FILE_SOUND)
|
||||
FS_UDIR_PATH(U.tempdir, ICON_TEMP)
|
||||
|
||||
|
|
|
@ -679,6 +679,17 @@ 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 bUserScriptDirectory {
|
||||
struct bUserScriptDirectory *next, *prev;
|
||||
|
||||
/** Name must be unique. */
|
||||
char name[64]; /* MAX_NAME */
|
||||
char dir_path[768]; /* FILE_MAXDIR */
|
||||
} bUserScriptDirectory;
|
||||
|
||||
typedef struct UserDef {
|
||||
DNA_DEFINE_CXX_METHODS(UserDef)
|
||||
|
||||
|
@ -703,22 +714,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 `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_legacy[768] DNA_DEPRECATED;
|
||||
char sounddir[768];
|
||||
char i18ndir[768];
|
||||
/** 1024 = FILE_MAX. */
|
||||
|
@ -790,6 +787,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 `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; /* #bUserScriptDirectory */
|
||||
/** #bUserMenu. */
|
||||
struct ListBase user_menus;
|
||||
/** #bUserAssetLibrary */
|
||||
|
|
|
@ -148,6 +148,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)
|
||||
|
|
|
@ -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,52 @@ 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.
|
||||
{
|
||||
bUserScriptDirectory *script_dir = ptr->data;
|
||||
bool value_invalid = false;
|
||||
|
||||
if (!value[0]) {
|
||||
value_invalid = true;
|
||||
}
|
||||
if (STREQ(value, "DEFAULT")) {
|
||||
value_invalid = true;
|
||||
}
|
||||
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.
|
||||
|
||||
if (value_invalid) {
|
||||
value = DATA_("Untitled");
|
||||
}
|
||||
|
||||
BLI_strncpy_utf8(script_dir->name, value, sizeof(script_dir->name));
|
||||
BLI_uniquename(&U.script_directories,
|
||||
script_dir,
|
||||
value,
|
||||
'.',
|
||||
offsetof(bUserScriptDirectory, name),
|
||||
sizeof(script_dir->name));
|
||||
}
|
||||
|
||||
static bUserScriptDirectory *rna_userdef_script_directory_new(void)
|
||||
{
|
||||
bUserScriptDirectory *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)
|
||||
{
|
||||
bUserScriptDirectory *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;
|
||||
|
@ -6212,6 +6259,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, "bUserScriptDirectory");
|
||||
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, "directory", 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;
|
||||
|
@ -6311,14 +6409,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");
|
||||
|
|
Prefer this be removed as scripts that use it will have incorrect behavior.