Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender')
-rw-r--r--source/blender/blenkernel/BKE_asset_library_custom.h69
-rw-r--r--source/blender/blenkernel/BKE_blender_project.h75
-rw-r--r--source/blender/blenkernel/BKE_blender_project.hh204
-rw-r--r--source/blender/blenkernel/BKE_context.h3
-rw-r--r--source/blender/blenkernel/BKE_preferences.h46
-rw-r--r--source/blender/blenkernel/CMakeLists.txt8
-rw-r--r--source/blender/blenkernel/intern/asset_catalog_test.cc8
-rw-r--r--source/blender/blenkernel/intern/asset_library.cc6
-rw-r--r--source/blender/blenkernel/intern/asset_library_custom.cc112
-rw-r--r--source/blender/blenkernel/intern/blender_project.cc311
-rw-r--r--source/blender/blenkernel/intern/blender_project_settings.cc318
-rw-r--r--source/blender/blenkernel/intern/blender_project_test.cc325
-rw-r--r--source/blender/blenkernel/intern/blendfile.c14
-rw-r--r--source/blender/blenkernel/intern/context.c15
-rw-r--r--source/blender/blenkernel/intern/preferences.c91
-rw-r--r--source/blender/blenloader/intern/versioning_userdef.c10
-rw-r--r--source/blender/blenloader/intern/writefile.cc12
-rw-r--r--source/blender/editors/CMakeLists.txt2
-rw-r--r--source/blender/editors/asset/ED_asset_library.h2
-rw-r--r--source/blender/editors/asset/intern/asset_indexer.cc1
-rw-r--r--source/blender/editors/asset/intern/asset_library_reference.cc36
-rw-r--r--source/blender/editors/asset/intern/asset_library_reference_enum.cc103
-rw-r--r--source/blender/editors/asset/intern/asset_list.cc33
-rw-r--r--source/blender/editors/asset/intern/asset_ops.cc21
-rw-r--r--source/blender/editors/include/ED_project.h40
-rw-r--r--source/blender/editors/include/ED_screen.h4
-rw-r--r--source/blender/editors/include/ED_space_api.h1
-rw-r--r--source/blender/editors/interface/interface_handlers.c2
-rw-r--r--source/blender/editors/interface/interface_template_search_menu.cc1
-rw-r--r--source/blender/editors/interface/resources.c3
-rw-r--r--source/blender/editors/project/CMakeLists.txt28
-rw-r--r--source/blender/editors/project/project.cc84
-rw-r--r--source/blender/editors/project/project_ops.cc299
-rw-r--r--source/blender/editors/screen/screen_ops.c119
-rw-r--r--source/blender/editors/space_api/CMakeLists.txt2
-rw-r--r--source/blender/editors/space_api/spacetypes.c3
-rw-r--r--source/blender/editors/space_file/file_draw.c25
-rw-r--r--source/blender/editors/space_file/filelist.cc30
-rw-r--r--source/blender/editors/space_file/filesel.c42
-rw-r--r--source/blender/editors/space_node/add_node_search.cc5
-rw-r--r--source/blender/editors/space_node/link_drag_search.cc5
-rw-r--r--source/blender/editors/space_project_settings/CMakeLists.txt33
-rw-r--r--source/blender/editors/space_project_settings/space_project_settings.cc259
-rw-r--r--source/blender/editors/space_userpref/userpref_ops.c10
-rw-r--r--source/blender/editors/util/CMakeLists.txt1
-rw-r--r--source/blender/makesdna/DNA_asset_types.h38
-rw-r--r--source/blender/makesdna/DNA_space_types.h31
-rw-r--r--source/blender/makesdna/DNA_userdef_types.h13
-rw-r--r--source/blender/makesdna/intern/dna_rename_defs.h1
-rw-r--r--source/blender/makesrna/RNA_enum_items.h2
-rw-r--r--source/blender/makesrna/intern/CMakeLists.txt1
-rw-r--r--source/blender/makesrna/intern/makesrna.c1
-rw-r--r--source/blender/makesrna/intern/rna_asset.c58
-rw-r--r--source/blender/makesrna/intern/rna_blender_project.c158
-rw-r--r--source/blender/makesrna/intern/rna_context.c14
-rw-r--r--source/blender/makesrna/intern/rna_internal.h1
-rw-r--r--source/blender/makesrna/intern/rna_screen.c4
-rw-r--r--source/blender/makesrna/intern/rna_space.c31
-rw-r--r--source/blender/makesrna/intern/rna_userdef.c72
-rw-r--r--source/blender/python/intern/bpy_rna_callback.c3
-rw-r--r--source/blender/windowmanager/WM_types.h5
-rw-r--r--source/blender/windowmanager/intern/wm_draw.c4
-rw-r--r--source/blender/windowmanager/intern/wm_files.c45
-rw-r--r--source/blender/windowmanager/intern/wm_window.c51
-rw-r--r--source/blender/windowmanager/wm_window.h2
65 files changed, 3020 insertions, 336 deletions
diff --git a/source/blender/blenkernel/BKE_asset_library_custom.h b/source/blender/blenkernel/BKE_asset_library_custom.h
new file mode 100644
index 00000000000..738729ce957
--- /dev/null
+++ b/source/blender/blenkernel/BKE_asset_library_custom.h
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/** \file
+ * \ingroup bke
+ *
+ * API to manage a list of #CustomAssetLibraryDefinition items.
+ */
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "BLI_compiler_attrs.h"
+#include "BLI_utildefines.h"
+
+struct CustomAssetLibraryDefinition;
+struct ListBase;
+
+struct CustomAssetLibraryDefinition *BKE_asset_library_custom_add(
+ struct ListBase *custom_libraries,
+ const char *name CPP_ARG_DEFAULT(nullptr),
+ const char *path CPP_ARG_DEFAULT(nullptr)) ATTR_NONNULL(1);
+/**
+ * Unlink and free a library preference member.
+ * \note Free's \a library itself.
+ */
+void BKE_asset_library_custom_remove(struct ListBase *custom_libraries,
+ struct CustomAssetLibraryDefinition *library) ATTR_NONNULL();
+
+void BKE_asset_library_custom_name_set(struct ListBase *custom_libraries,
+ struct CustomAssetLibraryDefinition *library,
+ const char *name) ATTR_NONNULL();
+
+/**
+ * Set the library path, ensuring it is pointing to a directory.
+ * Single blend files can only act as "Current File" library; libraries on disk
+ * should always be directories. Blindly sets the path without additional checks. The asset system
+ * can ignore libraries that it can't resolve to a valid location. If the path does not exist,
+ * that's fine; it can created as directory if necessary later.
+ */
+void BKE_asset_library_custom_path_set(struct CustomAssetLibraryDefinition *library,
+ const char *path) ATTR_NONNULL();
+
+struct CustomAssetLibraryDefinition *BKE_asset_library_custom_find_from_index(
+ const struct ListBase *custom_libraries, int index) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
+struct CustomAssetLibraryDefinition *BKE_asset_library_custom_find_from_name(
+ const struct ListBase *custom_libraries, const char *name)
+ ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
+
+/**
+ * Return the #CustomAssetLibraryDefinition that contains the given file/directory path. The given
+ * path can be the library's top-level directory, or any path inside that directory.
+ *
+ * When more than one asset libraries match, the first matching one is returned (no smartness when
+ * there nested asset libraries).
+ *
+ * Return NULL when no such asset library is found. */
+struct CustomAssetLibraryDefinition *BKE_asset_library_custom_containing_path(
+ const struct ListBase *custom_libraries, const char *path)
+ ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
+
+int BKE_asset_library_custom_get_index(
+ const struct ListBase /*#CustomAssetLibraryDefinition*/ *custom_libraries,
+ const struct CustomAssetLibraryDefinition *library) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
+#ifdef __cplusplus
+}
+#endif
diff --git a/source/blender/blenkernel/BKE_blender_project.h b/source/blender/blenkernel/BKE_blender_project.h
new file mode 100644
index 00000000000..4851e249884
--- /dev/null
+++ b/source/blender/blenkernel/BKE_blender_project.h
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/** \file
+ * \ingroup bke
+ */
+
+#pragma once
+
+#include "DNA_space_types.h"
+
+#include "BLI_compiler_attrs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* C-handle for #bke::BlenderProject. */
+typedef struct BlenderProject BlenderProject;
+
+/** See #bke::ProjectSettings::create_settings_directory(). */
+bool BKE_project_create_settings_directory(const char *project_root_path) ATTR_NONNULL();
+/** See #bke::ProjectSettings::delete_settings_directory(). */
+bool BKE_project_delete_settings_directory(BlenderProject *project) ATTR_NONNULL();
+
+BlenderProject *BKE_project_active_get(void) ATTR_WARN_UNUSED_RESULT;
+/**
+ * \note: When unsetting an active project, the previously active one will be destroyed, so
+ * pointers may dangle.
+ */
+void BKE_project_active_unset(void);
+/**
+ * Check if \a path references a project root directory. Will return false for paths pointing into
+ * the project root directory.
+ */
+bool BKE_project_is_path_project_root(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
+/**
+ * Check if \a path points to or into a project root path (i.e. if one of the ancestors of the
+ * referenced file/directory is a project root directory).
+ */
+bool BKE_project_contains_path(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
+
+/**
+ * Attempt to load and activate a project based on the given path. If the path doesn't lead
+ * into a project, the active project is unset. Note that the project will be unset on any
+ * failure when loading the project.
+ *
+ * \note: When setting an active project, the previously active one will be destroyed, so
+ * pointers may dangle.
+ */
+BlenderProject *BKE_project_active_load_from_path(const char *path) ATTR_NONNULL();
+
+bool BKE_project_settings_save(const BlenderProject *project) ATTR_NONNULL();
+
+const char *BKE_project_root_path_get(const BlenderProject *project) ATTR_WARN_UNUSED_RESULT
+ ATTR_NONNULL();
+/**
+ * \param name The new name to set, expected to be 0 terminated.
+ */
+void BKE_project_name_set(const BlenderProject *project_handle, const char *name) ATTR_NONNULL();
+const char *BKE_project_name_get(const BlenderProject *project) ATTR_WARN_UNUSED_RESULT
+ ATTR_NONNULL();
+ListBase *BKE_project_custom_asset_libraries_get(const BlenderProject *project)
+ ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
+void BKE_project_tag_has_unsaved_changes(const BlenderProject *project) ATTR_NONNULL();
+/**
+ * Check if the project is marked as having unsaved changes. For convenience this allows passing
+ * null as the project (returns false then), so a call like
+ * `BKE_project_has_unsaved_changes(CTX_wm_project())` can be done without having to null-check the
+ * project first.
+ */
+bool BKE_project_has_unsaved_changes(const BlenderProject *project) ATTR_WARN_UNUSED_RESULT;
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/source/blender/blenkernel/BKE_blender_project.hh b/source/blender/blenkernel/BKE_blender_project.hh
new file mode 100644
index 00000000000..185860674c6
--- /dev/null
+++ b/source/blender/blenkernel/BKE_blender_project.hh
@@ -0,0 +1,204 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/** \file
+ * \ingroup bke
+ */
+
+#pragma once
+
+#include <memory>
+
+#include "BLI_listbase.h"
+#include "BLI_string_ref.hh"
+#include "BLI_utility_mixins.hh"
+
+struct BlenderProject;
+
+namespace blender::io::serialize {
+class DictionaryValue;
+}
+
+namespace blender::bke {
+
+class ProjectSettings;
+struct CustomAssetLibraries;
+
+/**
+ * Entry point / API for core Blender project management.
+ *
+ * Responsibilities:
+ * - Own and give access to the active project.
+ * - Manage the .blender_project/ directory.
+ * - Store and manage (including reading & writing) of the .blender_project/settings.json file. The
+ * implementation of this can be found in the internal #ProjectSettings class.
+ * - Tag for unsaved changes as needed.
+ */
+class BlenderProject {
+ friend class ProjectSettings;
+
+ /* Path to the project root using native slashes plus a trailing slash. */
+ std::string root_path_;
+ std::unique_ptr<ProjectSettings> settings_;
+
+ public:
+ inline static const StringRefNull SETTINGS_DIRNAME = ".blender_project";
+ inline static const StringRefNull SETTINGS_FILENAME = "settings.json";
+
+ public:
+ static auto get_active [[nodiscard]] () -> BlenderProject *;
+ static auto set_active(std::unique_ptr<BlenderProject> settings) -> BlenderProject *;
+
+ /**
+ * Read project settings from the given \a path, which may point to some directory or file inside
+ * of the project directory. Both Unix and Windows style slashes are allowed. Path is expected to
+ * be normalized.
+ *
+ * Attempt to read project data from the given \a project_path, which may be either a project
+ * root directory or the .blender_project directory, and load it into runtime data. Letting the
+ * returned #unique_pointer run out of scope cleanly destructs the runtime project data.
+ *
+ * \note Does NOT set the loaded project active.
+ *
+ * \return The loaded project or null on failure.
+ */
+ static auto load_from_path(StringRef project_path) -> std::unique_ptr<BlenderProject>;
+
+ /**
+ * Initializes a blender project by creating a .blender_project directory at the given \a
+ * project_root_path.
+ * Both Unix and Windows style slashes are allowed.
+ *
+ * \return True if the settings directory was created, or already existed. False on failure.
+ */
+ static auto create_settings_directory(StringRef project_root_path) -> bool;
+ /**
+ * Remove the .blender_project directory with all of its contents at the given \a
+ * project_root_path. If this is the path of the active project, it is marked as having changed
+ * but it is not unloaded. Runtime project data is still valid at this point.
+ *
+ * \return True on success.
+ */
+ static auto delete_settings_directory(StringRef project_root_path) -> bool;
+
+ /**
+ * Check if the directory given by \a path contains a .blender_project directory and should thus
+ * be considered a project root directory.
+ */
+ static auto path_is_project_root(StringRef path) -> bool;
+
+ /**
+ * Check if \a path points into a project and return the root directory path of that project (the
+ * one containing the .blender_project directory). Walks "upwards" through the path and returns
+ * the first project found, so if a project is nested inside another one, the nested project is
+ * used.
+ * Both Unix and Windows style slashes are allowed.
+ *
+ * \return The project root path or an empty path if not found. The referenced string points into
+ * the input \a path, so slashes are not converted in the returned value.
+ */
+ static auto project_root_path_find_from_path [[nodiscard]] (StringRef path) -> StringRef;
+
+ /* --- Non-static member functions. --- */
+
+ BlenderProject(StringRef project_root_path, std::unique_ptr<ProjectSettings> settings);
+
+ /**
+ * Version of the static #delete_settings_directory() that deletes the settings directory of this
+ * project. Always tags as having unsaved changes after successful deletion.
+ */
+ auto delete_settings_directory() -> bool;
+
+ auto root_path [[nodiscard]] () const -> StringRefNull;
+ auto get_settings [[nodiscard]] () const -> ProjectSettings &;
+
+ private:
+ static auto active_project_ptr() -> std::unique_ptr<BlenderProject> &;
+ /**
+ * Get the project root path from a path that is either already the project root, or the
+ * .blender_project directory. Returns the path with native slashes plus a trailing slash.
+ */
+ static auto project_path_to_native_project_root_path(StringRef project_path) -> std::string;
+ /**
+ * Get the .blender_project directory path from a project root path. Returns the path with native
+ * slashes plus a trailing slash. Assumes the path already ends with a native trailing slash.
+ */
+ static auto project_root_path_to_settings_path(StringRef project_root_path) -> std::string;
+ /**
+ * Returns the path with native slashes.
+ * Assumes the path already ends with a native trailing slash.
+ */
+ static auto project_root_path_to_settings_filepath(StringRef project_root_path) -> std::string;
+};
+
+/**
+ * Runtime representation of the project settings (`.blender_project/settings.json`) with IO
+ * functionality.
+ */
+class ProjectSettings {
+ std::string project_name_;
+ std::unique_ptr<CustomAssetLibraries> asset_libraries_;
+
+ bool has_unsaved_changes_ = false;
+
+ public:
+ /**
+ * Read project settings from the given \a project_path, which may be either a project root
+ * directory or the .blender_project directory.
+ * Both Unix and Windows style slashes are allowed. Path is expected to be normalized.
+ *
+ * \return The read project settings or null in case of failure.
+ */
+ static auto load_from_disk [[nodiscard]] (StringRef project_path)
+ -> std::unique_ptr<ProjectSettings>;
+ /**
+ * Read project settings from the given \a path, which may point to some directory or file inside
+ * of the project directory. Both Unix and Windows style slashes are allowed. Path is expected to
+ * be normalized.
+ *
+ * \return The read project settings or null in case of failure.
+ */
+ static auto load_from_path [[nodiscard]] (StringRef path) -> std::unique_ptr<ProjectSettings>;
+
+ /** Explicit constructor and destructor needed to manage the CustomAssetLibraries unique_ptr. */
+ ProjectSettings();
+ /* Implementation defaulted. */
+ ~ProjectSettings();
+
+ /**
+ * Write project settings to the given \a project_path, which may be either a project root
+ * directory or the .blender_project directory. The .blender_project directory must exist.
+ * Both Unix and Windows style slashes are allowed. Path is expected to be normalized.
+ *
+ * \return True on success. If the .blender_project directory doesn't exist, that's treated
+ * as failure.
+ */
+ auto save_to_disk(StringRef project_path) -> bool;
+
+ void project_name(StringRef new_name);
+ auto project_name [[nodiscard]] () const -> StringRefNull;
+ auto asset_library_definitions() const -> const ListBase &;
+ auto asset_library_definitions() -> ListBase &;
+ /**
+ * Forcefully tag the project settings for having unsaved changes. This needs to be done if
+ * project settings data is modified directly by external code, not via a project settings API
+ * function. The API functions set the tag for all changes they manage.
+ */
+ void tag_has_unsaved_changes();
+ /**
+ * Returns true if there were any changes done to the settings that have not been written to
+ * disk yet. Project settings API functions that change data set this, however when external
+ * code modifies project settings data it may have to manually set the tag, see
+ * #tag_has_unsaved_changes().
+ */
+ auto has_unsaved_changes [[nodiscard]] () const -> bool;
+
+ private:
+ auto to_dictionary() const -> std::unique_ptr<io::serialize::DictionaryValue>;
+};
+
+} // namespace blender::bke
+
+inline ::BlenderProject *BKE_project_c_handle(blender::bke::BlenderProject *project)
+{
+ return reinterpret_cast<::BlenderProject *>(project);
+}
diff --git a/source/blender/blenkernel/BKE_context.h b/source/blender/blenkernel/BKE_context.h
index e1406e63ce1..512982adb7c 100644
--- a/source/blender/blenkernel/BKE_context.h
+++ b/source/blender/blenkernel/BKE_context.h
@@ -22,6 +22,7 @@ extern "C" {
struct ARegion;
struct Base;
+struct BlenderProject;
struct CacheFile;
struct Collection;
struct Depsgraph;
@@ -173,6 +174,7 @@ struct ARegion *CTX_wm_menu(const bContext *C);
struct wmGizmoGroup *CTX_wm_gizmo_group(const bContext *C);
struct wmMsgBus *CTX_wm_message_bus(const bContext *C);
struct ReportList *CTX_wm_reports(const bContext *C);
+struct BlenderProject *CTX_wm_project(void);
struct View3D *CTX_wm_view3d(const bContext *C);
struct RegionView3D *CTX_wm_region_view3d(const bContext *C);
@@ -192,6 +194,7 @@ struct SpaceUserPref *CTX_wm_space_userpref(const bContext *C);
struct SpaceClip *CTX_wm_space_clip(const bContext *C);
struct SpaceTopBar *CTX_wm_space_topbar(const bContext *C);
struct SpaceSpreadsheet *CTX_wm_space_spreadsheet(const bContext *C);
+struct SpaceProjectSettings *CTX_wm_space_project_settings(const bContext *C);
void CTX_wm_manager_set(bContext *C, struct wmWindowManager *wm);
void CTX_wm_window_set(bContext *C, struct wmWindow *win);
diff --git a/source/blender/blenkernel/BKE_preferences.h b/source/blender/blenkernel/BKE_preferences.h
index 9d33848b3d1..93119af0710 100644
--- a/source/blender/blenkernel/BKE_preferences.h
+++ b/source/blender/blenkernel/BKE_preferences.h
@@ -13,55 +13,11 @@ extern "C" {
#include "BLI_compiler_attrs.h"
struct UserDef;
-struct bUserAssetLibrary;
/** Name of the asset library added by default. Needs translation with `DATA_()` still. */
#define BKE_PREFS_ASSET_LIBRARY_DEFAULT_NAME N_("User Library")
-struct bUserAssetLibrary *BKE_preferences_asset_library_add(struct UserDef *userdef,
- const char *name,
- const char *path) ATTR_NONNULL(1);
-/**
- * Unlink and free a library preference member.
- * \note Free's \a library itself.
- */
-void BKE_preferences_asset_library_remove(struct UserDef *userdef,
- struct bUserAssetLibrary *library) ATTR_NONNULL();
-
-void BKE_preferences_asset_library_name_set(struct UserDef *userdef,
- struct bUserAssetLibrary *library,
- const char *name) ATTR_NONNULL();
-
-/**
- * Set the library path, ensuring it is pointing to a directory.
- * Single blend files can only act as "Current File" library; libraries on disk
- * should always be directories. If the path does not exist, that's fine; it can
- * created as directory if necessary later.
- */
-void BKE_preferences_asset_library_path_set(struct bUserAssetLibrary *library, const char *path)
- ATTR_NONNULL();
-
-struct bUserAssetLibrary *BKE_preferences_asset_library_find_from_index(
- const struct UserDef *userdef, int index) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
-struct bUserAssetLibrary *BKE_preferences_asset_library_find_from_name(
- const struct UserDef *userdef, const char *name) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
-
-/**
- * Return the bUserAssetLibrary that contains the given file/directory path. The given path can be
- * the library's top-level directory, or any path inside that directory.
- *
- * When more than one asset libraries match, the first matching one is returned (no smartness when
- * there nested asset libraries).
- *
- * Return NULL when no such asset library is found. */
-struct bUserAssetLibrary *BKE_preferences_asset_library_containing_path(
- const struct UserDef *userdef, const char *path) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
-
-int BKE_preferences_asset_library_get_index(const struct UserDef *userdef,
- const struct bUserAssetLibrary *library)
- ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
-
-void BKE_preferences_asset_library_default_add(struct UserDef *userdef) ATTR_NONNULL();
+void BKE_preferences_custom_asset_library_default_add(struct UserDef *userdef) ATTR_NONNULL();
#ifdef __cplusplus
}
diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt
index 7d43fa7e6af..b93aef01eb6 100644
--- a/source/blender/blenkernel/CMakeLists.txt
+++ b/source/blender/blenkernel/CMakeLists.txt
@@ -74,6 +74,7 @@ set(SRC
intern/asset_catalog.cc
intern/asset_catalog_path.cc
intern/asset_library.cc
+ intern/asset_library_custom.cc
intern/asset_library_service.cc
intern/attribute.cc
intern/attribute_access.cc
@@ -81,6 +82,8 @@ set(SRC
intern/autoexec.c
intern/blender.c
intern/blender_copybuffer.c
+ intern/blender_project.cc
+ intern/blender_project_settings.cc
intern/blender_undo.c
intern/blender_user_menu.c
intern/blendfile.c
@@ -324,12 +327,15 @@ set(SRC
BKE_asset_catalog_path.hh
BKE_asset_library.h
BKE_asset_library.hh
+ BKE_asset_library_custom.h
BKE_attribute.h
BKE_attribute.hh
BKE_attribute_math.hh
BKE_autoexec.h
BKE_blender.h
BKE_blender_copybuffer.h
+ BKE_blender_project.h
+ BKE_blender_project.hh
BKE_blender_undo.h
BKE_blender_user_menu.h
BKE_blender_version.h
@@ -835,6 +841,7 @@ if(WITH_GTESTS)
intern/asset_library_service_test.cc
intern/asset_library_test.cc
intern/asset_test.cc
+ intern/blender_project_test.cc
intern/bpath_test.cc
intern/cryptomatte_test.cc
intern/curves_geometry_test.cc
@@ -851,6 +858,7 @@ if(WITH_GTESTS)
)
set(TEST_INC
../editors/include
+ ../blenloader/tests
)
include(GTestTesting)
blender_add_test_lib(bf_blenkernel_tests "${TEST_SRC}" "${INC};${TEST_INC}" "${INC_SYS}" "${LIB}")
diff --git a/source/blender/blenkernel/intern/asset_catalog_test.cc b/source/blender/blenkernel/intern/asset_catalog_test.cc
index ee2dd652b61..ee8b9e28117 100644
--- a/source/blender/blenkernel/intern/asset_catalog_test.cc
+++ b/source/blender/blenkernel/intern/asset_catalog_test.cc
@@ -3,7 +3,7 @@
#include "BKE_appdir.h"
#include "BKE_asset_catalog.hh"
-#include "BKE_preferences.h"
+#include "BKE_asset_library_custom.h"
#include "BLI_fileops.h"
#include "BLI_path_util.h"
@@ -214,8 +214,8 @@ class AssetCatalogTest : public testing::Test {
BLI_path_slash_native(cdf_in_subdir.data());
/* Set up a temporary asset library for testing. */
- bUserAssetLibrary *asset_lib_pref = BKE_preferences_asset_library_add(
- &U, "Test", registered_asset_lib.c_str());
+ CustomAssetLibraryDefinition *asset_lib_pref = BKE_asset_library_custom_add(
+ &U.asset_libraries, "Test", registered_asset_lib.c_str());
ASSERT_NE(nullptr, asset_lib_pref);
ASSERT_TRUE(BLI_dir_create_recursive(asset_lib_subdir.c_str()));
@@ -266,7 +266,7 @@ class AssetCatalogTest : public testing::Test {
/* Test that the "red herring" CDF has not been touched. */
EXPECT_EQ(0, BLI_file_size(cdf_in_subdir.c_str()));
- BKE_preferences_asset_library_remove(&U, asset_lib_pref);
+ BKE_asset_library_custom_remove(&U.asset_libraries, asset_lib_pref);
}
};
diff --git a/source/blender/blenkernel/intern/asset_library.cc b/source/blender/blenkernel/intern/asset_library.cc
index b8420af1168..6a667d572a0 100644
--- a/source/blender/blenkernel/intern/asset_library.cc
+++ b/source/blender/blenkernel/intern/asset_library.cc
@@ -7,8 +7,8 @@
#include <memory>
#include "BKE_asset_library.hh"
+#include "BKE_asset_library_custom.h"
#include "BKE_main.h"
-#include "BKE_preferences.h"
#include "BLI_fileops.h"
#include "BLI_path_util.h"
@@ -53,8 +53,8 @@ bool BKE_asset_library_has_any_unsaved_catalogs()
bool BKE_asset_library_find_suitable_root_path_from_path(const char *input_path,
char *r_library_path)
{
- if (bUserAssetLibrary *preferences_lib = BKE_preferences_asset_library_containing_path(
- &U, input_path)) {
+ if (CustomAssetLibraryDefinition *preferences_lib = BKE_asset_library_custom_containing_path(
+ &U.asset_libraries, input_path)) {
BLI_strncpy(r_library_path, preferences_lib->path, FILE_MAXDIR);
return true;
}
diff --git a/source/blender/blenkernel/intern/asset_library_custom.cc b/source/blender/blenkernel/intern/asset_library_custom.cc
new file mode 100644
index 00000000000..4e9cb2ae50d
--- /dev/null
+++ b/source/blender/blenkernel/intern/asset_library_custom.cc
@@ -0,0 +1,112 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/** \file
+ * \ingroup bke
+ */
+
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/** \file
+ * \ingroup bke
+ */
+
+#include <string.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_fileops.h"
+#include "BLI_listbase.h"
+#include "BLI_path_util.h"
+#include "BLI_string.h"
+#include "BLI_string_utf8.h"
+#include "BLI_string_utils.h"
+
+#include "BKE_appdir.h"
+
+#include "BLT_translation.h"
+
+#include "DNA_asset_types.h"
+#include "DNA_userdef_types.h"
+
+#include "BKE_asset_library_custom.h"
+
+using namespace blender;
+
+/* -------------------------------------------------------------------- */
+/** \name Asset Libraries
+ * \{ */
+
+CustomAssetLibraryDefinition *BKE_asset_library_custom_add(ListBase *custom_libraries,
+ const char *name,
+ const char *path)
+{
+ CustomAssetLibraryDefinition *library = MEM_cnew<CustomAssetLibraryDefinition>(
+ "CustomAssetLibraryDefinition");
+
+ BLI_addtail(custom_libraries, library);
+
+ if (name) {
+ BKE_asset_library_custom_name_set(custom_libraries, library, name);
+ }
+ if (path) {
+ BLI_strncpy(library->path, path, sizeof(library->path));
+ }
+
+ return library;
+}
+
+void BKE_asset_library_custom_remove(ListBase *custom_libraries,
+ CustomAssetLibraryDefinition *library)
+{
+ BLI_freelinkN(custom_libraries, library);
+}
+
+void BKE_asset_library_custom_name_set(ListBase *custom_libraries,
+ CustomAssetLibraryDefinition *library,
+ const char *name)
+{
+ BLI_strncpy_utf8(library->name, name, sizeof(library->name));
+ BLI_uniquename(custom_libraries,
+ library,
+ name,
+ '.',
+ offsetof(CustomAssetLibraryDefinition, name),
+ sizeof(library->name));
+}
+
+void BKE_asset_library_custom_path_set(CustomAssetLibraryDefinition *library, const char *path)
+{
+ BLI_strncpy(library->path, path, sizeof(library->path));
+}
+
+CustomAssetLibraryDefinition *BKE_asset_library_custom_find_from_index(
+ const ListBase *custom_libraries, int index)
+{
+ return static_cast<CustomAssetLibraryDefinition *>(BLI_findlink(custom_libraries, index));
+}
+
+CustomAssetLibraryDefinition *BKE_asset_library_custom_find_from_name(
+ const ListBase *custom_libraries, const char *name)
+{
+ return static_cast<CustomAssetLibraryDefinition *>(
+ BLI_findstring(custom_libraries, name, offsetof(CustomAssetLibraryDefinition, name)));
+}
+
+CustomAssetLibraryDefinition *BKE_asset_library_custom_containing_path(
+ const ListBase *custom_libraries, const char *path)
+{
+ LISTBASE_FOREACH (CustomAssetLibraryDefinition *, asset_lib_pref, custom_libraries) {
+ if (BLI_path_contains(asset_lib_pref->path, path)) {
+ return asset_lib_pref;
+ }
+ }
+ return NULL;
+}
+
+int BKE_asset_library_custom_get_index(const ListBase *custom_libraries,
+ const CustomAssetLibraryDefinition *library)
+{
+ return BLI_findindex(custom_libraries, library);
+}
+
+/** \} */
diff --git a/source/blender/blenkernel/intern/blender_project.cc b/source/blender/blenkernel/intern/blender_project.cc
new file mode 100644
index 00000000000..b54b8f47e32
--- /dev/null
+++ b/source/blender/blenkernel/intern/blender_project.cc
@@ -0,0 +1,311 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/** \file
+ * \ingroup bke
+ */
+
+#include "BKE_asset_library_custom.h"
+
+#include "BLI_path_util.h"
+#include "BLI_string.h"
+
+#include "BLI_fileops.h"
+
+#include "BKE_blender_project.h"
+#include "BKE_blender_project.hh"
+
+namespace blender::bke {
+
+BlenderProject::BlenderProject(const StringRef project_root_path,
+ std::unique_ptr<ProjectSettings> settings)
+ : settings_(std::move(settings))
+{
+ root_path_ = BlenderProject::project_path_to_native_project_root_path(project_root_path);
+ BLI_assert(root_path_.back() == SEP);
+}
+
+/* ---------------------------------------------------------------------- */
+/** \name Active project management (static storage)
+ * \{ */
+
+/* Construct on First Use idiom. */
+std::unique_ptr<BlenderProject> &BlenderProject::active_project_ptr()
+{
+ static std::unique_ptr<BlenderProject> active_;
+ return active_;
+}
+
+BlenderProject *BlenderProject::set_active(std::unique_ptr<BlenderProject> project)
+{
+ std::unique_ptr<BlenderProject> &active = active_project_ptr();
+ if (project) {
+ active = std::move(project);
+ }
+ else {
+ active = nullptr;
+ }
+
+ return active.get();
+}
+
+BlenderProject *BlenderProject::get_active()
+{
+ std::unique_ptr<BlenderProject> &active = active_project_ptr();
+ return active.get();
+}
+
+/** \} */
+
+/* ---------------------------------------------------------------------- */
+/** \name Project and project settings management.
+ * \{ */
+
+std::unique_ptr<BlenderProject> BlenderProject::load_from_path(StringRef project_path)
+{
+ const StringRef project_root_path = project_root_path_find_from_path(project_path);
+
+ std::unique_ptr<bke::ProjectSettings> project_settings = bke::ProjectSettings::load_from_path(
+ project_root_path);
+ if (!project_settings) {
+ return nullptr;
+ }
+
+ return std::make_unique<BlenderProject>(project_root_path, std::move(project_settings));
+}
+
+bool BlenderProject::create_settings_directory(StringRef project_path)
+{
+ std::string project_root_path = project_path_to_native_project_root_path(project_path);
+ std::string settings_path = project_root_path_to_settings_path(project_root_path);
+
+ return BLI_dir_create_recursive(settings_path.c_str());
+}
+
+bool BlenderProject::delete_settings_directory(StringRef project_path)
+{
+ std::string project_root_path = project_path_to_native_project_root_path(project_path);
+ std::string settings_path = project_root_path_to_settings_path(project_root_path);
+
+ /* Returns 0 on success. */
+ if (BLI_delete(settings_path.c_str(), true, true)) {
+ return false;
+ }
+
+ BlenderProject *active_project = get_active();
+ if (active_project &&
+ BLI_path_cmp_normalized(project_root_path.c_str(), active_project->root_path().c_str())) {
+ active_project->settings_->tag_has_unsaved_changes();
+ }
+ return true;
+}
+
+bool BlenderProject::delete_settings_directory()
+{
+ if (!delete_settings_directory(root_path_)) {
+ return false;
+ }
+
+ settings_->tag_has_unsaved_changes();
+ return true;
+}
+
+/** \} */
+
+/* ---------------------------------------------------------------------- */
+/** \name Simple getters & setters
+ * \{ */
+
+StringRefNull BlenderProject::root_path() const
+{
+ return root_path_;
+}
+
+ProjectSettings &BlenderProject::get_settings() const
+{
+ BLI_assert(settings_ != nullptr);
+ return *settings_;
+}
+
+/** \} */
+
+/* ---------------------------------------------------------------------- */
+/** \name Path stuff
+ * \{ */
+
+StringRef BlenderProject::project_root_path_find_from_path(StringRef path)
+{
+ /* There are two versions of the path used here: One copy that is converted to native slashes,
+ * and the unmodified original path from the input. */
+
+ std::string path_native = path;
+ BLI_path_slash_native(path_native.data());
+
+ StringRef cur_path = path;
+
+ while (cur_path.size()) {
+ std::string cur_path_native = StringRef(path_native.c_str(), cur_path.size());
+ if (path_is_project_root(cur_path_native)) {
+ return path.substr(0, cur_path.size());
+ }
+
+ /* Walk "up the path" (check the parent next). */
+ const int64_t pos_last_slash = cur_path_native.find_last_of(SEP);
+ if (pos_last_slash == StringRef::not_found) {
+ break;
+ }
+ cur_path = cur_path.substr(0, pos_last_slash);
+ }
+
+ return "";
+}
+
+static StringRef path_strip_trailing_native_slash(StringRef path)
+{
+ const int64_t pos_before_trailing_slash = path.find_last_not_of(SEP);
+ return (pos_before_trailing_slash == StringRef::not_found) ?
+ path :
+ path.substr(0, pos_before_trailing_slash + 1);
+}
+
+bool BlenderProject::path_is_project_root(StringRef path)
+{
+ path = path_strip_trailing_native_slash(path);
+ return BLI_exists(std::string(path + SEP_STR + SETTINGS_DIRNAME).c_str());
+}
+
+std::string BlenderProject::project_path_to_native_project_root_path(StringRef project_path)
+{
+ std::string project_path_native = project_path;
+ BLI_path_slash_native(project_path_native.data());
+
+ const StringRef path_no_trailing_slashes = path_strip_trailing_native_slash(project_path_native);
+ if (path_no_trailing_slashes.endswith(SETTINGS_DIRNAME)) {
+ return StringRef(path_no_trailing_slashes).drop_suffix(SETTINGS_DIRNAME.size());
+ }
+
+ return std::string(path_no_trailing_slashes) + SEP;
+}
+
+std::string BlenderProject::project_root_path_to_settings_path(StringRef project_root_path)
+{
+ BLI_assert(project_root_path.back() == SEP);
+ return project_root_path + SETTINGS_DIRNAME + SEP;
+}
+
+std::string BlenderProject::project_root_path_to_settings_filepath(StringRef project_root_path)
+{
+ BLI_assert(project_root_path.back() == SEP);
+ return project_root_path_to_settings_path(project_root_path) + SETTINGS_FILENAME;
+}
+
+/** \} */
+
+} // namespace blender::bke
+
+/* ---------------------------------------------------------------------- */
+/** \name C-API
+ * \{ */
+
+using namespace blender;
+
+bool BKE_project_create_settings_directory(const char *project_root_path)
+{
+ return bke::BlenderProject::create_settings_directory(project_root_path);
+}
+
+bool BKE_project_delete_settings_directory(BlenderProject *project_handle)
+{
+ bke::BlenderProject *project = reinterpret_cast<bke::BlenderProject *>(project_handle);
+ return project->delete_settings_directory();
+}
+
+BlenderProject *BKE_project_active_get(void)
+{
+ return reinterpret_cast<BlenderProject *>(bke::BlenderProject::get_active());
+}
+
+void BKE_project_active_unset(void)
+{
+ bke::BlenderProject::set_active(nullptr);
+}
+
+bool BKE_project_is_path_project_root(const char *path)
+{
+ return bke::BlenderProject::path_is_project_root(path);
+}
+
+bool BKE_project_contains_path(const char *path)
+{
+ const StringRef found_root_path = bke::BlenderProject::project_root_path_find_from_path(path);
+ return !found_root_path.is_empty();
+}
+
+BlenderProject *BKE_project_active_load_from_path(const char *path)
+{
+ /* Project should be unset if the path doesn't contain a project root. Unset in the beginning so
+ * early exiting behaves correctly. */
+ BKE_project_active_unset();
+
+ std::unique_ptr<bke::BlenderProject> project = bke::BlenderProject::load_from_path(path);
+
+ return reinterpret_cast<::BlenderProject *>(bke::BlenderProject::set_active(std::move(project)));
+}
+
+bool BKE_project_settings_save(const BlenderProject *project_handle)
+{
+ const bke::BlenderProject *project = reinterpret_cast<const bke::BlenderProject *>(
+ project_handle);
+ bke::ProjectSettings &settings = project->get_settings();
+ return settings.save_to_disk(project->root_path());
+}
+
+const char *BKE_project_root_path_get(const BlenderProject *project_handle)
+{
+ const bke::BlenderProject *project = reinterpret_cast<const bke::BlenderProject *>(
+ project_handle);
+ return project->root_path().c_str();
+}
+
+void BKE_project_name_set(const BlenderProject *project_handle, const char *name)
+{
+ const bke::BlenderProject *project = reinterpret_cast<const bke::BlenderProject *>(
+ project_handle);
+ project->get_settings().project_name(name);
+}
+
+const char *BKE_project_name_get(const BlenderProject *project_handle)
+{
+ const bke::BlenderProject *project = reinterpret_cast<const bke::BlenderProject *>(
+ project_handle);
+ return project->get_settings().project_name().c_str();
+}
+
+ListBase *BKE_project_custom_asset_libraries_get(const BlenderProject *project_handle)
+{
+ const bke::BlenderProject *project = reinterpret_cast<const bke::BlenderProject *>(
+ project_handle);
+ bke::ProjectSettings &settings = project->get_settings();
+ return &settings.asset_library_definitions();
+}
+
+void BKE_project_tag_has_unsaved_changes(const BlenderProject *project_handle)
+{
+ const bke::BlenderProject *project = reinterpret_cast<const bke::BlenderProject *>(
+ project_handle);
+ bke::ProjectSettings &settings = project->get_settings();
+ settings.tag_has_unsaved_changes();
+}
+
+bool BKE_project_has_unsaved_changes(const BlenderProject *project_handle)
+{
+ if (!project_handle) {
+ return false;
+ }
+
+ const bke::BlenderProject *project = reinterpret_cast<const bke::BlenderProject *>(
+ project_handle);
+ const bke::ProjectSettings &settings = project->get_settings();
+ return settings.has_unsaved_changes();
+}
+
+/** \} */
diff --git a/source/blender/blenkernel/intern/blender_project_settings.cc b/source/blender/blenkernel/intern/blender_project_settings.cc
new file mode 100644
index 00000000000..9c55d050283
--- /dev/null
+++ b/source/blender/blenkernel/intern/blender_project_settings.cc
@@ -0,0 +1,318 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/** \file
+ * \ingroup bke
+ */
+
+#include <fstream>
+#include <memory>
+#include <string>
+
+#include "BLI_fileops.h"
+#include "BLI_listbase.h"
+#include "BLI_serialize.hh"
+
+#include "BKE_asset_library_custom.h"
+
+#include "DNA_asset_types.h"
+
+#include "BKE_blender_project.hh"
+
+namespace serialize = blender::io::serialize;
+
+namespace blender::bke {
+
+/* ---------------------------------------------------------------------- */
+
+struct CustomAssetLibraries : NonCopyable {
+ ListBase asset_libraries = {nullptr, nullptr}; /* CustomAssetLibraryDefinition */
+
+ CustomAssetLibraries() = default;
+ CustomAssetLibraries(ListBase asset_libraries);
+ CustomAssetLibraries(CustomAssetLibraries &&);
+ ~CustomAssetLibraries();
+ auto operator=(CustomAssetLibraries &&) -> CustomAssetLibraries &;
+};
+
+CustomAssetLibraries::CustomAssetLibraries(ListBase asset_libraries)
+ : asset_libraries(asset_libraries)
+{
+}
+
+CustomAssetLibraries::CustomAssetLibraries(CustomAssetLibraries &&other)
+{
+ *this = std::move(other);
+}
+
+CustomAssetLibraries &CustomAssetLibraries::operator=(CustomAssetLibraries &&other)
+{
+ asset_libraries = other.asset_libraries;
+ BLI_listbase_clear(&other.asset_libraries);
+ return *this;
+}
+
+CustomAssetLibraries::~CustomAssetLibraries()
+{
+ LISTBASE_FOREACH_MUTABLE (CustomAssetLibraryDefinition *, library, &asset_libraries) {
+ BKE_asset_library_custom_remove(&asset_libraries, library);
+ }
+}
+
+/* ---------------------------------------------------------------------- */
+/** \name settings.json Reading (Deserializing)
+ * \{ */
+
+struct ExtractedSettings {
+ std::string project_name;
+ ListBase asset_libraries = {nullptr, nullptr}; /* CustomAssetLibraryDefinition */
+};
+
+static std::unique_ptr<serialize::Value> read_settings_file(StringRef settings_filepath)
+{
+ std::ifstream is;
+ is.open(settings_filepath);
+ if (is.fail()) {
+ return nullptr;
+ }
+
+ serialize::JsonFormatter formatter;
+ /* Will not be a dictionary in case of error (corrupted file). */
+ std::unique_ptr<serialize::Value> deserialized_values = formatter.deserialize(is);
+ is.close();
+
+ if (deserialized_values->type() != serialize::eValueType::Dictionary) {
+ return nullptr;
+ }
+
+ return deserialized_values;
+}
+
+static std::unique_ptr<ExtractedSettings> extract_settings(
+ const serialize::DictionaryValue &dictionary)
+{
+ using namespace serialize;
+
+ std::unique_ptr extracted_settings = std::make_unique<ExtractedSettings>();
+
+ const DictionaryValue::Lookup attributes = dictionary.create_lookup();
+
+ /* "project": */ {
+ const DictionaryValue::LookupValue *project_value = attributes.lookup_ptr("project");
+ BLI_assert(project_value != nullptr);
+
+ const DictionaryValue *project_dict = (*project_value)->as_dictionary_value();
+ const StringValue *project_name_value =
+ project_dict->create_lookup().lookup("name")->as_string_value();
+ if (project_name_value) {
+ extracted_settings->project_name = project_name_value->value();
+ }
+ }
+ /* "asset_libraries": */ {
+ const DictionaryValue::LookupValue *asset_libraries_value = attributes.lookup_ptr(
+ "asset_libraries");
+ if (asset_libraries_value) {
+ const ArrayValue *asset_libraries_array = (*asset_libraries_value)->as_array_value();
+ if (!asset_libraries_array) {
+ throw std::runtime_error(
+ "Unexpected asset_library format in settings.json, expected array");
+ }
+
+ for (const ArrayValue::Item &element : asset_libraries_array->elements()) {
+ const DictionaryValue *object_value = element->as_dictionary_value();
+ if (!object_value) {
+ throw std::runtime_error(
+ "Unexpected asset_library entry in settings.json, expected dictionary entries only");
+ }
+ const DictionaryValue::Lookup element_lookup = object_value->create_lookup();
+ const DictionaryValue::LookupValue *name_value = element_lookup.lookup_ptr("name");
+ if (name_value && (*name_value)->type() != eValueType::String) {
+ throw std::runtime_error(
+ "Unexpected asset_library entry in settings.json, expected name to be string");
+ }
+ const DictionaryValue::LookupValue *path_value = element_lookup.lookup_ptr("path");
+ if (path_value && (*path_value)->type() != eValueType::String) {
+ throw std::runtime_error(
+ "Unexpected asset_library entry in settings.json, expected path to be string");
+ }
+
+ /* TODO this isn't really extracting, should be creating data from the settings be a
+ * separate step? */
+ CustomAssetLibraryDefinition *library = BKE_asset_library_custom_add(
+ &extracted_settings->asset_libraries);
+ /* Name or path may not be set, this is fine. */
+ if (name_value) {
+ std::string name = (*name_value)->as_string_value()->value();
+ BKE_asset_library_custom_name_set(
+ &extracted_settings->asset_libraries, library, name.c_str());
+ }
+ if (path_value) {
+ std::string path = (*path_value)->as_string_value()->value();
+ BKE_asset_library_custom_path_set(library, path.c_str());
+ }
+ }
+ }
+ }
+
+ return extracted_settings;
+}
+
+/** \} */
+
+/* ---------------------------------------------------------------------- */
+/** \name settings.json Writing (Serializing)
+ * \{ */
+
+std::unique_ptr<serialize::DictionaryValue> ProjectSettings::to_dictionary() const
+{
+ using namespace serialize;
+
+ std::unique_ptr<DictionaryValue> root = std::make_unique<DictionaryValue>();
+ DictionaryValue::Items &root_attributes = root->elements();
+
+ /* "project": */ {
+ std::unique_ptr<DictionaryValue> project_dict = std::make_unique<DictionaryValue>();
+ DictionaryValue::Items &project_attributes = project_dict->elements();
+ project_attributes.append_as("name", new StringValue(project_name_));
+ root_attributes.append_as("project", std::move(project_dict));
+ }
+ /* "asset_libraries": */ {
+ if (!BLI_listbase_is_empty(&asset_libraries_->asset_libraries)) {
+ std::unique_ptr<ArrayValue> asset_libs_array = std::make_unique<ArrayValue>();
+ ArrayValue::Items &asset_libs_elements = asset_libs_array->elements();
+ LISTBASE_FOREACH (
+ const CustomAssetLibraryDefinition *, library, &asset_libraries_->asset_libraries) {
+ std::unique_ptr<DictionaryValue> library_dict = std::make_unique<DictionaryValue>();
+ DictionaryValue::Items &library_attributes = library_dict->elements();
+
+ library_attributes.append_as("name", new StringValue(library->name));
+ library_attributes.append_as("path", new StringValue(library->path));
+ asset_libs_elements.append_as(std::move(library_dict));
+ }
+ root_attributes.append_as("asset_libraries", std::move(asset_libs_array));
+ }
+ }
+
+ return root;
+}
+
+static void write_settings_file(StringRef settings_filepath,
+ std::unique_ptr<serialize::DictionaryValue> dictionary)
+{
+ using namespace serialize;
+
+ JsonFormatter formatter;
+
+ std::ofstream os;
+ os.open(settings_filepath, std::ios::out | std::ios::trunc);
+ formatter.serialize(os, *dictionary);
+ os.close();
+}
+
+/** \} */
+
+/* ---------------------------------------------------------------------- */
+
+std::unique_ptr<ProjectSettings> ProjectSettings::load_from_disk(StringRef project_path)
+{
+ const std::string project_root_path = BlenderProject::project_path_to_native_project_root_path(
+ project_path);
+
+ if (!BLI_exists(project_root_path.c_str())) {
+ return nullptr;
+ }
+ if (!BlenderProject::path_is_project_root(project_root_path.c_str())) {
+ return nullptr;
+ }
+
+ const std::string settings_filepath = BlenderProject::project_root_path_to_settings_filepath(
+ project_root_path);
+ std::unique_ptr<serialize::Value> values = read_settings_file(settings_filepath);
+ std::unique_ptr<ExtractedSettings> extracted_settings = nullptr;
+ if (values) {
+ BLI_assert(values->as_dictionary_value() != nullptr);
+ extracted_settings = extract_settings(*values->as_dictionary_value());
+ }
+
+ std::unique_ptr loaded_settings = std::make_unique<ProjectSettings>();
+ if (extracted_settings) {
+ loaded_settings->project_name_ = extracted_settings->project_name;
+ /* Moves ownership. */
+ loaded_settings->asset_libraries_ = std::make_unique<CustomAssetLibraries>(
+ extracted_settings->asset_libraries);
+ }
+
+ return loaded_settings;
+}
+
+std::unique_ptr<ProjectSettings> ProjectSettings::load_from_path(StringRef path)
+{
+ StringRef project_root = BlenderProject::project_root_path_find_from_path(path);
+ if (project_root.is_empty()) {
+ return nullptr;
+ }
+
+ return bke::ProjectSettings::load_from_disk(project_root);
+}
+
+bool ProjectSettings::save_to_disk(StringRef project_path)
+{
+ const std::string project_root_path = BlenderProject::project_path_to_native_project_root_path(
+ project_path);
+
+ if (!BLI_exists(project_root_path.c_str())) {
+ return false;
+ }
+ if (!BlenderProject::path_is_project_root(project_root_path.c_str())) {
+ return false;
+ }
+
+ const std::string settings_filepath = BlenderProject::project_root_path_to_settings_filepath(
+ project_root_path);
+ std::unique_ptr settings_as_dict = to_dictionary();
+ write_settings_file(settings_filepath, std::move(settings_as_dict));
+
+ has_unsaved_changes_ = false;
+
+ return true;
+}
+
+/* ---------------------------------------------------------------------- */
+
+ProjectSettings::ProjectSettings() : asset_libraries_(std::make_unique<CustomAssetLibraries>())
+{
+}
+
+ProjectSettings::~ProjectSettings() = default;
+
+void ProjectSettings::project_name(StringRef new_name)
+{
+ project_name_ = new_name;
+ has_unsaved_changes_ = true;
+}
+
+StringRefNull ProjectSettings::project_name() const
+{
+ return project_name_;
+}
+
+const ListBase &ProjectSettings::asset_library_definitions() const
+{
+ return asset_libraries_->asset_libraries;
+}
+
+ListBase &ProjectSettings::asset_library_definitions()
+{
+ return asset_libraries_->asset_libraries;
+}
+
+void ProjectSettings::tag_has_unsaved_changes()
+{
+ has_unsaved_changes_ = true;
+}
+
+bool ProjectSettings::has_unsaved_changes() const
+{
+ return has_unsaved_changes_;
+}
+
+} // namespace blender::bke
diff --git a/source/blender/blenkernel/intern/blender_project_test.cc b/source/blender/blenkernel/intern/blender_project_test.cc
new file mode 100644
index 00000000000..e4daf062d8c
--- /dev/null
+++ b/source/blender/blenkernel/intern/blender_project_test.cc
@@ -0,0 +1,325 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright 2022 Blender Foundation. All rights reserved. */
+
+#include "BKE_appdir.h"
+#include "BKE_blender_project.h"
+#include "BKE_blender_project.hh"
+#include "BKE_main.h"
+
+#include "BLI_fileops.h"
+#include "BLI_function_ref.hh"
+#include "BLI_path_util.h"
+#include "BLI_vector.hh"
+
+#include "BLO_readfile.h"
+
+#include "blendfile_loading_base_test.h"
+
+#include "testing/testing.h"
+
+namespace blender::bke::tests {
+
+struct SVNFiles {
+ const std::string svn_root = blender::tests::flags_test_asset_dir();
+ const std::string project_root_rel = "blender_project/the_project";
+ const std::string project_root = svn_root + "/blender_project/the_project";
+};
+
+class ProjectTest : public testing::Test {
+ /* RAII helper to delete the created directories reliably after testing or on errors. */
+ struct ProjectDirectoryRAIIWrapper {
+ std::string project_path_;
+ /* Path with OS preferred slashes ('/' on Unix, '\' on Windows). Important for some file
+ * operations. */
+ std::string native_project_path_;
+ std::string base_path_;
+
+ ProjectDirectoryRAIIWrapper(StringRefNull base_path, StringRefNull relative_project_path)
+ {
+ BLI_assert_msg(ELEM(base_path.back(), SEP, ALTSEP),
+ "Expected base_path to have trailing slash");
+ std::string project_path = base_path + relative_project_path;
+
+ native_project_path_ = project_path;
+ BLI_path_slash_native(native_project_path_.data());
+ if (native_project_path_.back() != SEP) {
+ native_project_path_ += SEP;
+ }
+
+ /** Assert would be preferable but that would only run in debug builds, and #ASSERT_TRUE()
+ * doesn't support printing a message. */
+ if (BLI_exists(native_project_path_.c_str())) {
+ throw std::runtime_error("Can't execute test, temporary path '" + project_path +
+ "' already exists");
+ }
+
+ BLI_dir_create_recursive(native_project_path_.c_str());
+ if (!BLI_exists(native_project_path_.c_str())) {
+ throw std::runtime_error("Can't execute test, failed to create path '" +
+ native_project_path_ + "'");
+ }
+
+ base_path_ = base_path;
+ project_path_ = project_path;
+ BLI_assert(StringRef{&project_path_[base_path.size()]} == relative_project_path);
+ }
+
+ ~ProjectDirectoryRAIIWrapper()
+ {
+ if (!project_path_.empty()) {
+ /* Cut the path off at the first slash after the base path, so we delete the directory
+ * created for the test. */
+ const size_t first_slash_pos = native_project_path_.find_first_of(SEP, base_path_.size());
+ std::string path_to_delete = native_project_path_;
+ if (first_slash_pos != std::string::npos) {
+ path_to_delete.erase(first_slash_pos);
+ }
+ BLI_delete(path_to_delete.c_str(), true, true);
+ BLI_assert(!BLI_exists(native_project_path_.c_str()));
+ }
+ }
+ };
+
+ public:
+ /* Run the test on multiple paths or variations of the same path. Useful to test things like
+ * unicode paths, with or without trailing slash, non-native slashes, etc. The callback gets both
+ * the unmodified path (possibly with non-native slashes), and the path converted to native
+ * slashes passed. Call functions under test with the former, and use the latter to check the
+ * results with BLI_fileops.h functions */
+ void test_foreach_project_path(FunctionRef<void(StringRefNull /* project_path */,
+ StringRefNull /* project_path_native */)> fn)
+ {
+ const Vector<StringRefNull> subpaths = {
+ "temporary-project-root",
+ "test-temporary-unicode-dir-новый/temporary-project-root",
+ /* Same but with trailing slash. */
+ "test-temporary-unicode-dir-новый/temporary-project-root/",
+ /* Windows style slash. */
+ "test-temporary-unicode-dir-новый\\temporary-project-root",
+ /* Windows style trailing slash. */
+ "test-temporary-unicode-dir-новый\\temporary-project-root\\",
+ };
+
+ BKE_tempdir_init("");
+
+ const std::string tempdir = BKE_tempdir_session();
+ for (StringRefNull subpath : subpaths) {
+ ProjectDirectoryRAIIWrapper temp_project_path(tempdir, subpath);
+ fn(temp_project_path.project_path_, temp_project_path.native_project_path_);
+ }
+ }
+
+ void TearDown() override
+ {
+ BKE_project_active_unset();
+ }
+};
+
+TEST_F(ProjectTest, settings_create)
+{
+ test_foreach_project_path([](StringRefNull project_path, StringRefNull project_path_native) {
+ if (!BlenderProject::create_settings_directory(project_path)) {
+ /* Not a regular test failure, this may fail if there is a permission issue for example. */
+ FAIL() << "Failed to create project directory in '" << project_path
+ << "', check permissions";
+ }
+ std::string project_settings_dir = project_path_native + SEP_STR +
+ BlenderProject::SETTINGS_DIRNAME;
+ EXPECT_TRUE(BLI_exists(project_settings_dir.c_str()) &&
+ BLI_is_dir(project_settings_dir.c_str()))
+ << project_settings_dir + " was not created";
+ });
+}
+
+/* Load the project by pointing to the project root directory (as opposed to the .blender_project
+ * directory). */
+TEST_F(ProjectTest, load_from_project_root_path)
+{
+ test_foreach_project_path([](StringRefNull project_path, StringRefNull project_path_native) {
+ BlenderProject::create_settings_directory(project_path);
+
+ std::unique_ptr project = BlenderProject::load_from_path(project_path);
+ EXPECT_NE(project, nullptr);
+ EXPECT_EQ(project->root_path(), project_path_native);
+ EXPECT_EQ(project->get_settings().project_name(), "");
+ });
+}
+
+/* Load the project by pointing to the .blender_project directory (as opposed to the project root
+ * directory). */
+TEST_F(ProjectTest, load_from_project_settings_path)
+{
+ test_foreach_project_path([](StringRefNull project_path, StringRefNull project_path_native) {
+ BlenderProject::create_settings_directory(project_path);
+
+ std::unique_ptr project = BlenderProject::load_from_path(
+ project_path + (ELEM(project_path.back(), SEP, ALTSEP) ? "" : SEP_STR) +
+ BlenderProject::SETTINGS_DIRNAME);
+ EXPECT_NE(project, nullptr);
+ EXPECT_EQ(project->root_path(), project_path_native);
+ EXPECT_EQ(project->get_settings().project_name(), "");
+ });
+}
+
+static void project_settings_match_expected_from_svn(const ProjectSettings &project_settings)
+{
+ EXPECT_EQ(project_settings.project_name(), "Ružena");
+
+ const ListBase &asset_libraries = project_settings.asset_library_definitions();
+ CustomAssetLibraryDefinition *first = (CustomAssetLibraryDefinition *)asset_libraries.first;
+ EXPECT_STREQ(first->name, "Lorem Ipsum");
+ EXPECT_STREQ(first->path, "assets");
+ EXPECT_EQ(first->next, asset_libraries.last);
+ CustomAssetLibraryDefinition *last = (CustomAssetLibraryDefinition *)asset_libraries.last;
+ EXPECT_EQ(last->prev, asset_libraries.first);
+ EXPECT_STREQ(last->name, "Материалы");
+ EXPECT_STREQ(last->path, "новый\\assets");
+}
+
+TEST_F(ProjectTest, settings_json_read)
+{
+ SVNFiles svn_files{};
+ std::unique_ptr from_project_settings = ProjectSettings::load_from_disk(svn_files.project_root);
+ EXPECT_NE(from_project_settings, nullptr);
+ project_settings_match_expected_from_svn(*from_project_settings);
+}
+
+TEST_F(ProjectTest, settings_json_write)
+{
+ SVNFiles svn_files{};
+ std::unique_ptr from_project_settings = ProjectSettings::load_from_disk(svn_files.project_root);
+
+ /* Take the settings read from the SVN files and write it to /tmp/ projects. */
+ test_foreach_project_path(
+ [&from_project_settings](StringRefNull to_project_path, StringRefNull) {
+ BlenderProject::create_settings_directory(to_project_path);
+
+ if (!from_project_settings->save_to_disk(to_project_path)) {
+ FAIL();
+ }
+
+ /* Now check if the settings written to disk match the expectations. */
+ std::unique_ptr written_settings = ProjectSettings::load_from_disk(to_project_path);
+ EXPECT_NE(written_settings, nullptr);
+ project_settings_match_expected_from_svn(*written_settings);
+ });
+}
+
+TEST_F(ProjectTest, settings_read_change_write)
+{
+ SVNFiles svn_files{};
+ std::unique_ptr from_project_settings = ProjectSettings::load_from_disk(svn_files.project_root);
+
+ EXPECT_FALSE(from_project_settings->has_unsaved_changes());
+
+ /* Take the settings read from the SVN files and write it to /tmp/ projects. */
+ test_foreach_project_path(
+ [&from_project_settings](StringRefNull to_project_path, StringRefNull) {
+ BlenderProject::create_settings_directory(to_project_path);
+
+ from_project_settings->project_name("новый");
+ EXPECT_TRUE(from_project_settings->has_unsaved_changes());
+
+ if (!from_project_settings->save_to_disk(to_project_path)) {
+ FAIL();
+ }
+ EXPECT_FALSE(from_project_settings->has_unsaved_changes());
+
+ /* Now check if the settings written to disk match the expectations. */
+ std::unique_ptr written_settings = ProjectSettings::load_from_disk(to_project_path);
+ EXPECT_NE(written_settings, nullptr);
+ EXPECT_EQ(written_settings->project_name(), "новый");
+ EXPECT_FALSE(from_project_settings->has_unsaved_changes());
+ });
+}
+
+TEST_F(ProjectTest, project_root_path_find_from_path)
+{
+ /* Test the temporarily created directories with their various path formats. */
+ test_foreach_project_path([](StringRefNull project_path, StringRefNull /*project_path_native*/) {
+ /* First test without a .blender_project directory present. */
+ EXPECT_EQ(BlenderProject::project_root_path_find_from_path(project_path), "");
+
+ BlenderProject::create_settings_directory(project_path);
+ EXPECT_EQ(BlenderProject::project_root_path_find_from_path(project_path), project_path);
+ });
+
+ SVNFiles svn_files{};
+
+ /* Test the prepared project directory from the libs SVN repository. */
+ EXPECT_EQ(BlenderProject::project_root_path_find_from_path(svn_files.project_root +
+ "/some_project_file.blend"),
+ svn_files.project_root);
+ EXPECT_EQ(BlenderProject::project_root_path_find_from_path(
+ svn_files.project_root +
+ "/unicode_subdirectory_новый/another_subdirectory/some_nested_project_file.blend"),
+ svn_files.project_root);
+}
+
+class BlendfileProjectLoadingTest : public BlendfileLoadingBaseTest {
+};
+
+/* Test if loading the blend file loads the project data as expected. */
+TEST_F(BlendfileProjectLoadingTest, load_blend_file)
+{
+ EXPECT_EQ(BKE_project_active_get(), nullptr);
+
+ if (!blendfile_load("blender_project/the_project/some_project_file.blend")) {
+ FAIL();
+ }
+
+ ::BlenderProject *svn_project = BKE_project_active_load_from_path(bfile->main->filepath);
+ EXPECT_NE(svn_project, nullptr);
+ EXPECT_EQ(BKE_project_active_get(), svn_project);
+ EXPECT_STREQ("Ružena", BKE_project_name_get(svn_project));
+ /* Note: The project above will be freed once a different active project is set. So get the path
+ * for future comparisons. */
+ std::string svn_project_path = BKE_project_root_path_get(svn_project);
+
+ blendfile_free();
+
+ /* Check if loading a different .blend updates the project properly */
+ if (!blendfile_load("blender_project/the_project/unicode_subdirectory_новый/"
+ "another_subdirectory/some_nested_project_file.blend")) {
+ FAIL();
+ }
+ ::BlenderProject *svn_project_from_nested = BKE_project_active_load_from_path(
+ bfile->main->filepath);
+ EXPECT_NE(svn_project_from_nested, nullptr);
+ EXPECT_EQ(BKE_project_active_get(), svn_project_from_nested);
+ EXPECT_STREQ(svn_project_path.c_str(), BKE_project_root_path_get(svn_project_from_nested));
+ EXPECT_STREQ("Ružena", BKE_project_name_get(svn_project_from_nested));
+ blendfile_free();
+
+ /* Check if loading a .blend that's not in the project unsets the project properly. */
+ if (!blendfile_load("blender_project/not_a_project_file.blend")) {
+ FAIL();
+ }
+ BKE_project_active_load_from_path(bfile->main->filepath);
+ EXPECT_EQ(BKE_project_active_get(), nullptr);
+}
+
+TEST_F(ProjectTest, project_load_and_delete)
+{
+ test_foreach_project_path([](StringRefNull project_path, StringRefNull project_path_native) {
+ BlenderProject::create_settings_directory(project_path);
+
+ ::BlenderProject *project = BKE_project_active_load_from_path(project_path.c_str());
+ EXPECT_NE(project, nullptr);
+ EXPECT_FALSE(BKE_project_has_unsaved_changes(project));
+
+ std::string project_settings_dir = project_path_native + SEP_STR +
+ BlenderProject::SETTINGS_DIRNAME;
+
+ EXPECT_TRUE(BLI_exists(project_settings_dir.c_str()));
+ if (!BKE_project_delete_settings_directory(project)) {
+ FAIL();
+ }
+ /* Runtime project should still exist, but with unsaved changes. */
+ EXPECT_NE(project, nullptr);
+ EXPECT_TRUE(BKE_project_has_unsaved_changes(project));
+ EXPECT_FALSE(BLI_exists(project_settings_dir.c_str()));
+ });
+}
+
+} // namespace blender::bke::tests
diff --git a/source/blender/blenkernel/intern/blendfile.c b/source/blender/blenkernel/intern/blendfile.c
index 8b0d3f2e92e..f625f06d4d0 100644
--- a/source/blender/blenkernel/intern/blendfile.c
+++ b/source/blender/blenkernel/intern/blendfile.c
@@ -30,6 +30,7 @@
#include "BKE_addon.h"
#include "BKE_appdir.h"
#include "BKE_blender.h"
+#include "BKE_blender_project.h"
#include "BKE_blender_version.h"
#include "BKE_blendfile.h"
#include "BKE_bpath.h"
@@ -445,6 +446,16 @@ static void setup_app_blend_file_data(bContext *C,
}
}
+static void setup_app_project_data(BlendFileData *bfd, const struct BlendFileReadParams *params)
+{
+ if (!U.experimental.use_blender_projects) {
+ return;
+ }
+ if ((params->skip_flags & BLO_READ_SKIP_DATA) == 0) {
+ BKE_project_active_load_from_path(bfd->main->filepath);
+ }
+}
+
static void handle_subversion_warning(Main *main, BlendFileReadReport *reports)
{
if (main->minversionfile > BLENDER_FILE_VERSION ||
@@ -471,6 +482,7 @@ void BKE_blendfile_read_setup_ex(bContext *C,
BLO_update_defaults_startup_blend(bfd->main, startup_app_template);
}
}
+ setup_app_project_data(bfd, params);
setup_app_blend_file_data(C, bfd, params, reports);
BLO_blendfiledata_free(bfd);
}
@@ -682,7 +694,7 @@ UserDef *BKE_blendfile_userdef_from_defaults(void)
/* Default studio light. */
BKE_studiolight_default(userdef->light_param, userdef->light_ambient);
- BKE_preferences_asset_library_default_add(userdef);
+ BKE_preferences_custom_asset_library_default_add(userdef);
return userdef;
}
diff --git a/source/blender/blenkernel/intern/context.c b/source/blender/blenkernel/intern/context.c
index 1d6092849cc..ed6f55cc8f9 100644
--- a/source/blender/blenkernel/intern/context.c
+++ b/source/blender/blenkernel/intern/context.c
@@ -30,6 +30,7 @@
#include "BLT_translation.h"
+#include "BKE_blender_project.h"
#include "BKE_context.h"
#include "BKE_layer.h"
#include "BKE_main.h"
@@ -782,6 +783,11 @@ struct ReportList *CTX_wm_reports(const bContext *C)
return NULL;
}
+BlenderProject *CTX_wm_project(void)
+{
+ return BKE_project_active_get();
+}
+
View3D *CTX_wm_view3d(const bContext *C)
{
ScrArea *area = CTX_wm_area(C);
@@ -948,6 +954,15 @@ struct SpaceSpreadsheet *CTX_wm_space_spreadsheet(const bContext *C)
return NULL;
}
+SpaceProjectSettings *CTX_wm_space_project_settings(const bContext *C)
+{
+ ScrArea *area = CTX_wm_area(C);
+ if (area && area->spacetype == SPACE_PROJECT_SETTINGS) {
+ return area->spacedata.first;
+ }
+ return NULL;
+}
+
void CTX_wm_manager_set(bContext *C, wmWindowManager *wm)
{
C->wm.manager = wm;
diff --git a/source/blender/blenkernel/intern/preferences.c b/source/blender/blenkernel/intern/preferences.c
index dd76f9eddc1..fa894529f97 100644
--- a/source/blender/blenkernel/intern/preferences.c
+++ b/source/blender/blenkernel/intern/preferences.c
@@ -2,26 +2,17 @@
/** \file
* \ingroup bke
- *
- * User defined asset library API.
*/
-#include <string.h>
-
-#include "MEM_guardedalloc.h"
-
-#include "BLI_fileops.h"
-#include "BLI_listbase.h"
#include "BLI_path_util.h"
-#include "BLI_string.h"
-#include "BLI_string_utf8.h"
-#include "BLI_string_utils.h"
#include "BKE_appdir.h"
+#include "BKE_asset_library_custom.h"
#include "BKE_preferences.h"
#include "BLT_translation.h"
+#include "DNA_asset_types.h"
#include "DNA_userdef_types.h"
#define U BLI_STATIC_ASSERT(false, "Global 'U' not allowed, only use arguments passed in!")
@@ -30,79 +21,7 @@
/** \name Asset Libraries
* \{ */
-bUserAssetLibrary *BKE_preferences_asset_library_add(UserDef *userdef,
- const char *name,
- const char *path)
-{
- bUserAssetLibrary *library = MEM_callocN(sizeof(*library), "bUserAssetLibrary");
-
- BLI_addtail(&userdef->asset_libraries, library);
-
- if (name) {
- BKE_preferences_asset_library_name_set(userdef, library, name);
- }
- if (path) {
- BLI_strncpy(library->path, path, sizeof(library->path));
- }
-
- return library;
-}
-
-void BKE_preferences_asset_library_remove(UserDef *userdef, bUserAssetLibrary *library)
-{
- BLI_freelinkN(&userdef->asset_libraries, library);
-}
-
-void BKE_preferences_asset_library_name_set(UserDef *userdef,
- bUserAssetLibrary *library,
- const char *name)
-{
- BLI_strncpy_utf8(library->name, name, sizeof(library->name));
- BLI_uniquename(&userdef->asset_libraries,
- library,
- name,
- '.',
- offsetof(bUserAssetLibrary, name),
- sizeof(library->name));
-}
-
-void BKE_preferences_asset_library_path_set(bUserAssetLibrary *library, const char *path)
-{
- BLI_strncpy(library->path, path, sizeof(library->path));
- if (BLI_is_file(library->path)) {
- BLI_path_parent_dir(library->path);
- }
-}
-
-bUserAssetLibrary *BKE_preferences_asset_library_find_from_index(const UserDef *userdef, int index)
-{
- return BLI_findlink(&userdef->asset_libraries, index);
-}
-
-bUserAssetLibrary *BKE_preferences_asset_library_find_from_name(const UserDef *userdef,
- const char *name)
-{
- return BLI_findstring(&userdef->asset_libraries, name, offsetof(bUserAssetLibrary, name));
-}
-
-bUserAssetLibrary *BKE_preferences_asset_library_containing_path(const UserDef *userdef,
- const char *path)
-{
- LISTBASE_FOREACH (bUserAssetLibrary *, asset_lib_pref, &userdef->asset_libraries) {
- if (BLI_path_contains(asset_lib_pref->path, path)) {
- return asset_lib_pref;
- }
- }
- return NULL;
-}
-
-int BKE_preferences_asset_library_get_index(const UserDef *userdef,
- const bUserAssetLibrary *library)
-{
- return BLI_findindex(&userdef->asset_libraries, library);
-}
-
-void BKE_preferences_asset_library_default_add(UserDef *userdef)
+void BKE_preferences_custom_asset_library_default_add(UserDef *userdef)
{
char documents_path[FILE_MAXDIR];
@@ -111,8 +30,8 @@ void BKE_preferences_asset_library_default_add(UserDef *userdef)
return;
}
- bUserAssetLibrary *library = BKE_preferences_asset_library_add(
- userdef, DATA_(BKE_PREFS_ASSET_LIBRARY_DEFAULT_NAME), NULL);
+ CustomAssetLibraryDefinition *library = BKE_asset_library_custom_add(
+ &userdef->asset_libraries, DATA_(BKE_PREFS_ASSET_LIBRARY_DEFAULT_NAME), NULL);
/* Add new "Default" library under '[doc_path]/Blender/Assets'. */
BLI_path_join(library->path, sizeof(library->path), documents_path, N_("Blender"), N_("Assets"));
diff --git a/source/blender/blenloader/intern/versioning_userdef.c b/source/blender/blenloader/intern/versioning_userdef.c
index b4890131861..4b7375bc12b 100644
--- a/source/blender/blenloader/intern/versioning_userdef.c
+++ b/source/blender/blenloader/intern/versioning_userdef.c
@@ -22,6 +22,7 @@
#include "DNA_windowmanager_types.h"
#include "BKE_addon.h"
+#include "BKE_asset_library_custom.h"
#include "BKE_blender_version.h"
#include "BKE_colorband.h"
#include "BKE_idprop.h"
@@ -98,6 +99,7 @@ static void do_versions_theme(const UserDef *userdef, bTheme *btheme)
*/
{
/* Keep this block, even when empty. */
+ btheme->space_project_settings = btheme->space_preferences;
}
#undef FROM_DEFAULT_V4_UCHAR
@@ -678,7 +680,7 @@ void blo_do_versions_userdef(UserDef *userdef)
if (!USER_VERSION_ATLEAST(292, 9)) {
if (BLI_listbase_is_empty(&userdef->asset_libraries)) {
- BKE_preferences_asset_library_default_add(userdef);
+ BKE_preferences_custom_asset_library_default_add(userdef);
}
}
@@ -731,11 +733,11 @@ void blo_do_versions_userdef(UserDef *userdef)
* since it doesn't handle translations and ignores user changes. But this was an alpha build
* (experimental) feature and the name is just for display in the UI anyway. So it doesn't have
* to work perfectly at all. */
- LISTBASE_FOREACH (bUserAssetLibrary *, asset_library, &userdef->asset_libraries) {
+ LISTBASE_FOREACH (CustomAssetLibraryDefinition *, asset_library, &userdef->asset_libraries) {
/* Ignores translations, since that would depend on the current preferences (global `U`). */
if (STREQ(asset_library->name, "Default")) {
- BKE_preferences_asset_library_name_set(
- userdef, asset_library, BKE_PREFS_ASSET_LIBRARY_DEFAULT_NAME);
+ BKE_asset_library_custom_name_set(
+ &userdef->asset_libraries, asset_library, BKE_PREFS_ASSET_LIBRARY_DEFAULT_NAME);
}
}
}
diff --git a/source/blender/blenloader/intern/writefile.cc b/source/blender/blenloader/intern/writefile.cc
index 42bc884098e..01a729732a1 100644
--- a/source/blender/blenloader/intern/writefile.cc
+++ b/source/blender/blenloader/intern/writefile.cc
@@ -94,6 +94,8 @@
#include "BLI_threads.h"
#include "MEM_guardedalloc.h" /* MEM_freeN */
+#include "BKE_blender_project.h"
+#include "BKE_blender_project.hh"
#include "BKE_blender_version.h"
#include "BKE_bpath.h"
#include "BKE_global.h" /* for G */
@@ -921,8 +923,9 @@ static void write_userdef(BlendWriter *writer, const UserDef *userdef)
BLO_write_struct(writer, bPathCompare, path_cmp);
}
- LISTBASE_FOREACH (const bUserAssetLibrary *, asset_library_ref, &userdef->asset_libraries) {
- BLO_write_struct(writer, bUserAssetLibrary, asset_library_ref);
+ LISTBASE_FOREACH (
+ const CustomAssetLibraryDefinition *, asset_library_ref, &userdef->asset_libraries) {
+ BLO_write_struct(writer, CustomAssetLibraryDefinition, asset_library_ref);
}
LISTBASE_FOREACH (const uiStyle *, style, &userdef->uistyles) {
@@ -1438,6 +1441,11 @@ bool BLO_write_file(Main *mainvar,
}
}
+ /* Update active project information based on the new file location. */
+ if (U.experimental.use_blender_projects) {
+ BKE_project_active_load_from_path(filepath);
+ }
+
/* actual file writing */
const bool err = write_file_handle(
mainvar, &ww, nullptr, nullptr, write_flags, use_userdef, thumb);
diff --git a/source/blender/editors/CMakeLists.txt b/source/blender/editors/CMakeLists.txt
index 9fff8bf861c..fab403a2923 100644
--- a/source/blender/editors/CMakeLists.txt
+++ b/source/blender/editors/CMakeLists.txt
@@ -21,6 +21,7 @@ if(WITH_BLENDER)
add_subdirectory(metaball)
add_subdirectory(object)
add_subdirectory(physics)
+ add_subdirectory(project)
add_subdirectory(render)
add_subdirectory(scene)
add_subdirectory(sculpt_paint)
@@ -37,6 +38,7 @@ if(WITH_BLENDER)
add_subdirectory(space_nla)
add_subdirectory(space_node)
add_subdirectory(space_outliner)
+ add_subdirectory(space_project_settings)
add_subdirectory(space_script)
add_subdirectory(space_sequencer)
add_subdirectory(space_spreadsheet)
diff --git a/source/blender/editors/asset/ED_asset_library.h b/source/blender/editors/asset/ED_asset_library.h
index cc0d97054b6..c5f526e18d5 100644
--- a/source/blender/editors/asset/ED_asset_library.h
+++ b/source/blender/editors/asset/ED_asset_library.h
@@ -34,6 +34,8 @@ AssetLibraryReference ED_asset_library_reference_from_enum_value(int value);
const struct EnumPropertyItem *ED_asset_library_reference_to_rna_enum_itemf(
bool include_local_library);
+struct CustomAssetLibraryDefinition *ED_asset_library_find_custom_library_from_reference(
+ const AssetLibraryReference *library_ref);
#ifdef __cplusplus
}
#endif
diff --git a/source/blender/editors/asset/intern/asset_indexer.cc b/source/blender/editors/asset/intern/asset_indexer.cc
index cc06fa80429..2f301d55007 100644
--- a/source/blender/editors/asset/intern/asset_indexer.cc
+++ b/source/blender/editors/asset/intern/asset_indexer.cc
@@ -26,7 +26,6 @@
#include "BKE_asset.h"
#include "BKE_asset_catalog.hh"
#include "BKE_idprop.hh"
-#include "BKE_preferences.h"
#include "CLG_log.h"
diff --git a/source/blender/editors/asset/intern/asset_library_reference.cc b/source/blender/editors/asset/intern/asset_library_reference.cc
index 5096b9d653d..ce3327ff421 100644
--- a/source/blender/editors/asset/intern/asset_library_reference.cc
+++ b/source/blender/editors/asset/intern/asset_library_reference.cc
@@ -4,8 +4,15 @@
* \ingroup edasset
*/
+#include "BKE_asset_library_custom.h"
+#include "BKE_blender_project.h"
+#include "BKE_context.h"
+
+#include "DNA_userdef_types.h"
+
#include "BLI_hash.hh"
+#include "ED_asset_library.h"
#include "asset_library_reference.hh"
namespace blender::ed::asset {
@@ -18,14 +25,15 @@ AssetLibraryReferenceWrapper::AssetLibraryReferenceWrapper(const AssetLibraryRef
bool operator==(const AssetLibraryReferenceWrapper &a, const AssetLibraryReferenceWrapper &b)
{
return (a.type == b.type) &&
- ((a.type == ASSET_LIBRARY_CUSTOM) ? (a.custom_library_index == b.custom_library_index) :
- true);
+ (ELEM(a.type, ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES, ASSET_LIBRARY_CUSTOM_FROM_PROJECT) ?
+ (a.custom_library_index == b.custom_library_index) :
+ true);
}
uint64_t AssetLibraryReferenceWrapper::hash() const
{
uint64_t hash1 = DefaultHash<decltype(type)>{}(type);
- if (type != ASSET_LIBRARY_CUSTOM) {
+ if (!ELEM(type, ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES, ASSET_LIBRARY_CUSTOM_FROM_PROJECT)) {
return hash1;
}
@@ -34,3 +42,25 @@ uint64_t AssetLibraryReferenceWrapper::hash() const
}
} // namespace blender::ed::asset
+
+CustomAssetLibraryDefinition *ED_asset_library_find_custom_library_from_reference(
+ const AssetLibraryReference *library_ref)
+{
+ switch (library_ref->type) {
+ case ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES:
+ return BKE_asset_library_custom_find_from_index(&U.asset_libraries,
+ library_ref->custom_library_index);
+ case ASSET_LIBRARY_CUSTOM_FROM_PROJECT: {
+ BlenderProject *project = CTX_wm_project();
+ if (project) {
+ return BKE_asset_library_custom_find_from_index(
+ BKE_project_custom_asset_libraries_get(project), library_ref->custom_library_index);
+ }
+ break;
+ }
+ case ASSET_LIBRARY_LOCAL:
+ return nullptr;
+ }
+
+ return nullptr;
+}
diff --git a/source/blender/editors/asset/intern/asset_library_reference_enum.cc b/source/blender/editors/asset/intern/asset_library_reference_enum.cc
index 773838a54cd..7d75dc83854 100644
--- a/source/blender/editors/asset/intern/asset_library_reference_enum.cc
+++ b/source/blender/editors/asset/intern/asset_library_reference_enum.cc
@@ -11,7 +11,9 @@
#include "BLI_listbase.h"
-#include "BKE_preferences.h"
+#include "BKE_asset_library_custom.h"
+#include "BKE_blender_project.h"
+#include "BKE_context.h"
#include "DNA_userdef_types.h"
@@ -23,17 +25,15 @@
int ED_asset_library_reference_to_enum_value(const AssetLibraryReference *library)
{
- /* Simple case: Predefined repository, just set the value. */
- if (library->type < ASSET_LIBRARY_CUSTOM) {
+ /* Simple case: Predefined library, just set the value. */
+ if (library->type < ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES) {
return library->type;
}
/* Note that the path isn't checked for validity here. If an invalid library path is used, the
* Asset Browser can give a nice hint on what's wrong. */
- const bUserAssetLibrary *user_library = BKE_preferences_asset_library_find_from_index(
- &U, library->custom_library_index);
- if (user_library) {
- return ASSET_LIBRARY_CUSTOM + library->custom_library_index;
+ if (ED_asset_library_find_custom_library_from_reference(library)) {
+ return library->type + library->custom_library_index;
}
return ASSET_LIBRARY_LOCAL;
@@ -44,32 +44,63 @@ AssetLibraryReference ED_asset_library_reference_from_enum_value(int value)
AssetLibraryReference library;
/* Simple case: Predefined repository, just set the value. */
- if (value < ASSET_LIBRARY_CUSTOM) {
+ if (value < ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES) {
library.type = value;
library.custom_library_index = -1;
BLI_assert(ELEM(value, ASSET_LIBRARY_LOCAL));
return library;
}
- const bUserAssetLibrary *user_library = BKE_preferences_asset_library_find_from_index(
- &U, value - ASSET_LIBRARY_CUSTOM);
+ const eAssetLibraryType type = (value < ASSET_LIBRARY_CUSTOM_FROM_PROJECT) ?
+ ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES :
+ ASSET_LIBRARY_CUSTOM_FROM_PROJECT;
- /* Note that there is no check if the path exists here. If an invalid library path is used, the
- * Asset Browser can give a nice hint on what's wrong. */
- if (!user_library) {
- library.type = ASSET_LIBRARY_LOCAL;
- library.custom_library_index = -1;
- }
- else {
- const bool is_valid = (user_library->name[0] && user_library->path[0]);
- if (is_valid) {
- library.custom_library_index = value - ASSET_LIBRARY_CUSTOM;
- library.type = ASSET_LIBRARY_CUSTOM;
+ const CustomAssetLibraryDefinition *custom_library = nullptr;
+
+ library.type = type;
+ library.custom_library_index = value - type;
+
+ {
+ custom_library = ED_asset_library_find_custom_library_from_reference(&library);
+
+ /* Note that there is no check if the path exists here. If an invalid library path is used, the
+ * Asset Browser can give a nice hint on what's wrong. */
+ const bool is_valid = custom_library && (custom_library->name[0] && custom_library->path[0]);
+ if (!is_valid) {
+ library.custom_library_index = -1;
}
}
+
return library;
}
+static void add_custom_asset_library_enum_items(
+ const ListBase * /*CustomAssetLibraryDefinition*/ libraries,
+ const eAssetLibraryType library_type,
+ EnumPropertyItem **items,
+ int *totitem)
+{
+ int i;
+ LISTBASE_FOREACH_INDEX (CustomAssetLibraryDefinition *, custom_library, libraries, i) {
+ /* Note that the path itself isn't checked for validity here. If an invalid library path is
+ * used, the Asset Browser can give a nice hint on what's wrong. */
+ const bool is_valid = (custom_library->name[0] && custom_library->path[0]);
+ if (!is_valid) {
+ continue;
+ }
+
+ AssetLibraryReference library_reference;
+ library_reference.type = library_type;
+ library_reference.custom_library_index = i;
+
+ const int enum_value = ED_asset_library_reference_to_enum_value(&library_reference);
+ /* Use library path as description, it's a nice hint for users. */
+ EnumPropertyItem tmp = {
+ enum_value, custom_library->name, ICON_NONE, custom_library->name, custom_library->path};
+ RNA_enum_item_add(items, totitem, &tmp);
+ }
+}
+
const EnumPropertyItem *ED_asset_library_reference_to_rna_enum_itemf(
const bool include_local_library)
{
@@ -92,29 +123,21 @@ const EnumPropertyItem *ED_asset_library_reference_to_rna_enum_itemf(
RNA_enum_items_add(&item, &totitem, predefined_items);
}
- /* Add separator if needed. */
- if (!BLI_listbase_is_empty(&U.asset_libraries)) {
+ BlenderProject *project = CTX_wm_project();
+ if (project && !BLI_listbase_is_empty(BKE_project_custom_asset_libraries_get(project))) {
RNA_enum_item_add_separator(&item, &totitem);
- }
- int i;
- LISTBASE_FOREACH_INDEX (bUserAssetLibrary *, user_library, &U.asset_libraries, i) {
- /* Note that the path itself isn't checked for validity here. If an invalid library path is
- * used, the Asset Browser can give a nice hint on what's wrong. */
- const bool is_valid = (user_library->name[0] && user_library->path[0]);
- if (!is_valid) {
- continue;
- }
+ add_custom_asset_library_enum_items(BKE_project_custom_asset_libraries_get(project),
+ ASSET_LIBRARY_CUSTOM_FROM_PROJECT,
+ &item,
+ &totitem);
+ }
- AssetLibraryReference library_reference;
- library_reference.type = ASSET_LIBRARY_CUSTOM;
- library_reference.custom_library_index = i;
+ if (!BLI_listbase_is_empty(&U.asset_libraries)) {
+ RNA_enum_item_add_separator(&item, &totitem);
- const int enum_value = ED_asset_library_reference_to_enum_value(&library_reference);
- /* Use library path as description, it's a nice hint for users. */
- EnumPropertyItem tmp = {
- enum_value, user_library->name, ICON_NONE, user_library->name, user_library->path};
- RNA_enum_item_add(&item, &totitem, &tmp);
+ add_custom_asset_library_enum_items(
+ &U.asset_libraries, ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES, &item, &totitem);
}
RNA_enum_item_end(&item, &totitem);
diff --git a/source/blender/editors/asset/intern/asset_list.cc b/source/blender/editors/asset/intern/asset_list.cc
index bb72c5cc1bb..fc71882d88f 100644
--- a/source/blender/editors/asset/intern/asset_list.cc
+++ b/source/blender/editors/asset/intern/asset_list.cc
@@ -12,6 +12,8 @@
#include <optional>
#include <string>
+#include "BKE_asset_library_custom.h"
+#include "BKE_blender_project.h"
#include "BKE_context.h"
#include "BLI_map.hh"
@@ -20,8 +22,6 @@
#include "DNA_space_types.h"
-#include "BKE_preferences.h"
-
#include "ED_fileselect.h"
#include "WM_api.h"
@@ -32,6 +32,7 @@
#include "ED_asset_handle.h"
#include "ED_asset_indexer.h"
+#include "ED_asset_library.h"
#include "ED_asset_list.h"
#include "ED_asset_list.hh"
#include "asset_library_reference.hh"
@@ -131,15 +132,8 @@ void AssetList::setup()
{
FileList *files = filelist_;
- bUserAssetLibrary *user_library = nullptr;
-
- /* Ensure valid repository, or fall-back to local one. */
- if (library_ref_.type == ASSET_LIBRARY_CUSTOM) {
- BLI_assert(library_ref_.custom_library_index >= 0);
-
- user_library = BKE_preferences_asset_library_find_from_index(
- &U, library_ref_.custom_library_index);
- }
+ CustomAssetLibraryDefinition *custom_library =
+ ED_asset_library_find_custom_library_from_reference(&library_ref_);
/* Relevant bits from file_refresh(). */
/* TODO pass options properly. */
@@ -161,8 +155,18 @@ void AssetList::setup()
filelist_setindexer(files, use_asset_indexer ? &file_indexer_asset : &file_indexer_noop);
char path[FILE_MAXDIR] = "";
- if (user_library) {
- BLI_strncpy(path, user_library->path, sizeof(path));
+ if (custom_library) {
+ /* Project asset libraries typically use relative paths (relative to project root directory).
+ */
+ if ((library_ref_.type == ASSET_LIBRARY_CUSTOM_FROM_PROJECT) &&
+ BLI_path_is_rel(custom_library->path)) {
+ BlenderProject *project = CTX_wm_project();
+ const char *project_root_path = BKE_project_root_path_get(project);
+ BLI_path_join(path, sizeof(path), project_root_path, custom_library->path);
+ }
+ else {
+ BLI_strncpy(path, custom_library->path, sizeof(path));
+ }
filelist_setdir(files, path);
}
else {
@@ -377,7 +381,8 @@ std::optional<eFileSelectType> AssetListStorage::asset_library_reference_to_file
const AssetLibraryReference &library_reference)
{
switch (library_reference.type) {
- case ASSET_LIBRARY_CUSTOM:
+ case ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES:
+ case ASSET_LIBRARY_CUSTOM_FROM_PROJECT:
return FILE_ASSET_LIBRARY;
case ASSET_LIBRARY_LOCAL:
return FILE_MAIN_ASSET;
diff --git a/source/blender/editors/asset/intern/asset_ops.cc b/source/blender/editors/asset/intern/asset_ops.cc
index d1c46a8259f..9b38911a993 100644
--- a/source/blender/editors/asset/intern/asset_ops.cc
+++ b/source/blender/editors/asset/intern/asset_ops.cc
@@ -5,12 +5,13 @@
*/
#include "BKE_asset_library.hh"
+#include "BKE_asset_library_custom.h"
+#include "BKE_blender_project.hh"
#include "BKE_bpath.h"
#include "BKE_context.h"
#include "BKE_global.h"
#include "BKE_lib_id.h"
#include "BKE_main.h"
-#include "BKE_preferences.h"
#include "BKE_report.h"
#include "BLI_fileops.h"
@@ -669,7 +670,7 @@ static void ASSET_OT_catalogs_save(struct wmOperatorType *ot)
/* -------------------------------------------------------------------- */
static bool could_be_asset_bundle(const Main *bmain);
-static const bUserAssetLibrary *selected_asset_library(struct wmOperator *op);
+static const CustomAssetLibraryDefinition *selected_asset_library(struct wmOperator *op);
static bool is_contained_in_selected_asset_library(struct wmOperator *op, const char *filepath);
static bool set_filepath_for_asset_lib(const Main *bmain, struct wmOperator *op);
static bool has_external_files(Main *bmain, struct ReportList *reports);
@@ -691,8 +692,8 @@ static bool asset_bundle_install_poll(bContext *C)
}
/* Check whether this file is already located inside any asset library. */
- const struct bUserAssetLibrary *asset_lib = BKE_preferences_asset_library_containing_path(
- &U, bmain->filepath);
+ const struct CustomAssetLibraryDefinition *asset_lib = BKE_asset_library_custom_containing_path(
+ &U.asset_libraries, bmain->filepath);
if (asset_lib) {
return false;
}
@@ -763,7 +764,7 @@ static int asset_bundle_install_exec(bContext *C, wmOperator *op)
return operator_result;
}
- const bUserAssetLibrary *lib = selected_asset_library(op);
+ const CustomAssetLibraryDefinition *lib = selected_asset_library(op);
BLI_assert_msg(lib, "If the asset library is not known, how did we get here?");
BKE_reportf(op->reports,
RPT_INFO,
@@ -823,18 +824,18 @@ static bool could_be_asset_bundle(const Main *bmain)
return fnmatch("*_bundle.blend", bmain->filepath, FNM_CASEFOLD) == 0;
}
-static const bUserAssetLibrary *selected_asset_library(struct wmOperator *op)
+static const CustomAssetLibraryDefinition *selected_asset_library(struct wmOperator *op)
{
const int enum_value = RNA_enum_get(op->ptr, "asset_library_ref");
const AssetLibraryReference lib_ref = ED_asset_library_reference_from_enum_value(enum_value);
- const bUserAssetLibrary *lib = BKE_preferences_asset_library_find_from_index(
- &U, lib_ref.custom_library_index);
+ const CustomAssetLibraryDefinition *lib = BKE_asset_library_custom_find_from_index(
+ &U.asset_libraries, lib_ref.custom_library_index);
return lib;
}
static bool is_contained_in_selected_asset_library(struct wmOperator *op, const char *filepath)
{
- const bUserAssetLibrary *lib = selected_asset_library(op);
+ const CustomAssetLibraryDefinition *lib = selected_asset_library(op);
if (!lib) {
return false;
}
@@ -848,7 +849,7 @@ static bool is_contained_in_selected_asset_library(struct wmOperator *op, const
static bool set_filepath_for_asset_lib(const Main *bmain, struct wmOperator *op)
{
/* Find the directory path of the selected asset library. */
- const bUserAssetLibrary *lib = selected_asset_library(op);
+ const CustomAssetLibraryDefinition *lib = selected_asset_library(op);
if (lib == nullptr) {
return false;
}
diff --git a/source/blender/editors/include/ED_project.h b/source/blender/editors/include/ED_project.h
new file mode 100644
index 00000000000..6a78f964ca2
--- /dev/null
+++ b/source/blender/editors/include/ED_project.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/** \file
+ * \ingroup editors
+ *
+ * Editor level functions for Blender projects.
+ */
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct BlenderProject;
+struct Main;
+struct ReportList;
+
+void ED_operatortypes_project(void);
+
+/** Sets the project name to the directory name it is located in and registers a "Project Library"
+ * asset library pointing to `//assets/`. */
+void ED_project_set_defaults(struct BlenderProject *project);
+/**
+ * High level project creation (create project + load if needed + write default settings if
+ * needed).
+ *
+ * Initializes a new project in \a project_root_dir by creating the `.blender_project/` directory
+ * there. The new project will only be loaded if \a bmain represents a file within the project
+ * directory.
+ *
+ * \return True on success.
+ */
+bool ED_project_new(const struct Main *bmain,
+ const char *project_root_dir,
+ struct ReportList *reports);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/source/blender/editors/include/ED_screen.h b/source/blender/editors/include/ED_screen.h
index e1fd53ccdee..69ad6f8abba 100644
--- a/source/blender/editors/include/ED_screen.h
+++ b/source/blender/editors/include/ED_screen.h
@@ -28,6 +28,7 @@ struct Depsgraph;
struct IDProperty;
struct Main;
struct MenuType;
+struct ReportList;
struct Scene;
struct SpaceLink;
struct WorkSpace;
@@ -491,6 +492,9 @@ int ED_screen_animation_play(struct bContext *C, int sync, int mode);
bScreen *ED_screen_animation_playing(const struct wmWindowManager *wm);
bScreen *ED_screen_animation_no_scrub(const struct wmWindowManager *wm);
+/** \return True on success. */
+bool ED_project_settings_window_show(struct bContext *C, struct ReportList *reports);
+
/* screen keymaps */
/* called in spacetypes.c */
void ED_operatortypes_screen(void);
diff --git a/source/blender/editors/include/ED_space_api.h b/source/blender/editors/include/ED_space_api.h
index 07d4f43bb2b..e36cbd25e74 100644
--- a/source/blender/editors/include/ED_space_api.h
+++ b/source/blender/editors/include/ED_space_api.h
@@ -46,6 +46,7 @@ void ED_spacetype_clip(void);
void ED_spacetype_statusbar(void);
void ED_spacetype_topbar(void);
void ED_spacetype_spreadsheet(void);
+void ED_spacetype_project_settings(void);
/** \} */
diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c
index ac16d41dabe..a5c786358d6 100644
--- a/source/blender/editors/interface/interface_handlers.c
+++ b/source/blender/editors/interface/interface_handlers.c
@@ -676,7 +676,7 @@ static bool ui_rna_is_userdef(PointerRNA *ptr, PropertyRNA *prop)
&RNA_AddonPreferences,
&RNA_KeyConfigPreferences,
&RNA_KeyMapItem,
- &RNA_UserAssetLibrary);
+ &RNA_CustomAssetLibraryDefinition);
}
bool UI_but_is_userdef(const uiBut *but)
diff --git a/source/blender/editors/interface/interface_template_search_menu.cc b/source/blender/editors/interface/interface_template_search_menu.cc
index 38a7e025572..849144d9166 100644
--- a/source/blender/editors/interface/interface_template_search_menu.cc
+++ b/source/blender/editors/interface/interface_template_search_menu.cc
@@ -634,6 +634,7 @@ static MenuSearch_Data *menu_items_from_ui_create(
SPACE_MENU_MAP(SPACE_NODE, "NODE_MT_editor_menus");
SPACE_MENU_MAP(SPACE_CONSOLE, "CONSOLE_MT_editor_menus");
SPACE_MENU_MAP(SPACE_USERPREF, "USERPREF_MT_editor_menus");
+ SPACE_MENU_MAP(SPACE_PROJECT_SETTINGS, "PROJECTSETTINGS_MT_editor_menus");
SPACE_MENU_MAP(SPACE_CLIP,
(((const SpaceClip *)sl)->mode == SC_MODE_TRACKING) ?
"CLIP_MT_tracking_editor_menus" :
diff --git a/source/blender/editors/interface/resources.c b/source/blender/editors/interface/resources.c
index 93b94d42d39..2f460de4ebc 100644
--- a/source/blender/editors/interface/resources.c
+++ b/source/blender/editors/interface/resources.c
@@ -129,6 +129,9 @@ const uchar *UI_ThemeGetColorPtr(bTheme *btheme, int spacetype, int colorid)
case SPACE_USERPREF:
ts = &btheme->space_preferences;
break;
+ case SPACE_PROJECT_SETTINGS:
+ ts = &btheme->space_project_settings;
+ break;
case SPACE_CONSOLE:
ts = &btheme->space_console;
break;
diff --git a/source/blender/editors/project/CMakeLists.txt b/source/blender/editors/project/CMakeLists.txt
new file mode 100644
index 00000000000..fd85c307f06
--- /dev/null
+++ b/source/blender/editors/project/CMakeLists.txt
@@ -0,0 +1,28 @@
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+set(INC
+ .
+ ../include
+ ../../blenkernel
+ ../../blenlib
+ ../../blentranslation
+ ../../makesdna
+ ../../makesrna
+ ../../windowmanager
+ ../../../../intern/guardedalloc
+)
+
+set(INC_SYS
+)
+
+set(SRC
+ project.cc
+ project_ops.cc
+)
+
+set(LIB
+ bf_blenkernel
+)
+
+blender_add_lib(bf_editor_project "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
diff --git a/source/blender/editors/project/project.cc b/source/blender/editors/project/project.cc
new file mode 100644
index 00000000000..5ecad2a9e03
--- /dev/null
+++ b/source/blender/editors/project/project.cc
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/** \file
+ * \ingroup edproject
+ */
+
+#include "BLI_path_util.h"
+#include "BLI_string.h"
+#include "BLI_string_ref.hh"
+
+#include "BLT_translation.h"
+
+#include "BKE_asset_library_custom.h"
+#include "BKE_blender_project.h"
+#include "BKE_blender_project.hh"
+#include "BKE_context.h"
+#include "BKE_main.h"
+#include "BKE_report.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "ED_project.h"
+
+using namespace blender;
+
+/** Name of the asset library added by default. Needs translation with `DATA_()`. */
+inline static const char *DEFAULT_ASSET_LIBRARY_NAME = N_("Project Library");
+inline static const char *DEFAULT_ASSET_LIBRARY_PATH = "//assets/";
+
+void ED_project_set_defaults(BlenderProject *project)
+{
+ char project_root_dir[FILE_MAXDIR];
+ BLI_strncpy(project_root_dir, BKE_project_root_path_get(project), sizeof(project_root_dir));
+
+ /* Set directory name as default project name. */
+ char dirname[FILE_MAXFILE];
+ BLI_path_slash_rstrip(project_root_dir);
+ BLI_split_file_part(project_root_dir, dirname, sizeof(dirname));
+ BKE_project_name_set(project, dirname);
+
+ ListBase *libraries = BKE_project_custom_asset_libraries_get(project);
+ BKE_asset_library_custom_add(
+ libraries, DATA_(DEFAULT_ASSET_LIBRARY_NAME), DEFAULT_ASSET_LIBRARY_PATH);
+}
+
+bool ED_project_new(const Main *bmain, const char *project_root_dir, ReportList *reports)
+{
+ if (!BKE_project_create_settings_directory(project_root_dir)) {
+ BKE_reportf(reports,
+ RPT_ERROR,
+ "Failed to create project with unknown error. Is the directory read-only?");
+ return false;
+ }
+
+ std::unique_ptr<bke::BlenderProject> loaded_project = bke::BlenderProject::load_from_path(
+ project_root_dir);
+
+ /* Some default settings for the project. */
+ if (loaded_project) {
+ BlenderProject *loaded_project_c = BKE_project_c_handle(loaded_project.get());
+
+ ED_project_set_defaults(loaded_project_c);
+ /* Write defaults to the hard drive. */
+ BKE_project_settings_save(loaded_project_c);
+ }
+
+ BKE_reportf(reports, RPT_INFO, "Project created and loaded successfully");
+
+ const char *blend_path = BKE_main_blendfile_path(bmain);
+ const bool blend_is_in_project = blend_path[0] &&
+ BLI_path_contains(project_root_dir, blend_path);
+ if (blend_is_in_project) {
+ bke::BlenderProject::set_active(std::move(loaded_project));
+ }
+ else {
+ BKE_reportf(reports,
+ RPT_WARNING,
+ "The current file is not located inside of the new project. This means the new "
+ "project is not active");
+ }
+
+ return true;
+}
diff --git a/source/blender/editors/project/project_ops.cc b/source/blender/editors/project/project_ops.cc
new file mode 100644
index 00000000000..b9398aa9e95
--- /dev/null
+++ b/source/blender/editors/project/project_ops.cc
@@ -0,0 +1,299 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/** \file
+ * \ingroup edproject
+ */
+
+#include <climits>
+
+#include "BKE_asset_library_custom.h"
+#include "BKE_blender_project.h"
+#include "BKE_context.h"
+#include "BKE_main.h"
+#include "BKE_report.h"
+
+#include "BLI_path_util.h"
+#include "BLT_translation.h"
+
+#include "DNA_space_types.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "ED_project.h"
+#include "ED_screen.h"
+
+using namespace blender;
+
+static bool has_active_project_poll(bContext *C)
+{
+ const BlenderProject *active_project = CTX_wm_project();
+ CTX_wm_operator_poll_msg_set(C, TIP_("No active project loaded"));
+ return active_project != NULL;
+}
+
+/* -------------------------------------------------------------------- */
+/** \name New project operator
+ * \{ */
+
+static bool new_project_poll(bContext *C)
+{
+ if (!U.experimental.use_blender_projects) {
+ CTX_wm_operator_poll_msg_set(C, "Experimental project support is not enabled");
+ return false;
+ }
+ return true;
+}
+
+static int new_project_exec(bContext *C, wmOperator *op)
+{
+ const Main *bmain = CTX_data_main(C);
+
+ if (!RNA_struct_property_is_set(op->ptr, "directory")) {
+ BKE_report(op->reports, RPT_ERROR, "No path defined for creating a new project in");
+ return OPERATOR_CANCELLED;
+ }
+ char project_root_dir[FILE_MAXDIR];
+ RNA_string_get(op->ptr, "directory", project_root_dir);
+
+ if (!ED_project_new(bmain, project_root_dir, op->reports)) {
+ return OPERATOR_CANCELLED;
+ }
+
+ PropertyRNA *prop_open_settings = RNA_struct_find_property(op->ptr, "open_settings_after");
+ if (RNA_property_is_set(op->ptr, prop_open_settings) &&
+ RNA_property_boolean_get(op->ptr, prop_open_settings)) {
+ ED_project_settings_window_show(C, op->reports);
+ }
+
+ WM_main_add_notifier(NC_PROJECT, NULL);
+ /* Update the window title. */
+ WM_main_add_notifier(NC_WM | ND_DATACHANGED, NULL);
+
+ return OPERATOR_FINISHED;
+}
+
+static int new_project_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+{
+ const Main *bmain = CTX_data_main(C);
+ const char *blendfile_path = BKE_main_blendfile_path(bmain);
+ if (blendfile_path[0]) {
+ /* Open at the .blend file location if any. */
+ RNA_string_set(op->ptr, "directory", blendfile_path);
+ }
+
+ WM_event_add_fileselect(C, op);
+ return OPERATOR_RUNNING_MODAL;
+}
+
+static void PROJECT_OT_new(wmOperatorType *ot)
+{
+ ot->name = "New Project";
+ ot->idname = "PROJECT_OT_new";
+ ot->description = "Choose a directory to use as the root of a project";
+
+ ot->invoke = new_project_invoke;
+ ot->exec = new_project_exec;
+ /* omit window poll so this can work in background mode */
+ ot->poll = new_project_poll;
+
+ WM_operator_properties_filesel(ot,
+ FILE_TYPE_FOLDER,
+ FILE_BLENDER,
+ FILE_OPENFILE,
+ WM_FILESEL_DIRECTORY,
+ FILE_DEFAULTDISPLAY,
+ FILE_SORT_DEFAULT);
+
+ PropertyRNA *prop;
+ prop = RNA_def_boolean(ot->srna,
+ "open_settings_after",
+ false,
+ "",
+ "Open the project settings window after successfully creating a project");
+ RNA_def_property_flag(prop, PROP_HIDDEN);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Write Project Settings Operator
+ * \{ */
+
+static int save_settings_exec(bContext *UNUSED(C), wmOperator *UNUSED(op))
+{
+ BlenderProject *active_project = CTX_wm_project();
+
+ if (!BKE_project_settings_save(active_project)) {
+ return OPERATOR_CANCELLED;
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+static void PROJECT_OT_save_settings(wmOperatorType *ot)
+{
+ ot->name = "Save Project Settings";
+ ot->idname = "PROJECT_OT_save_settings";
+ ot->description = "Make the current changes to the project settings permanent";
+
+ ot->invoke = WM_operator_confirm;
+ ot->poll = has_active_project_poll;
+ ot->exec = save_settings_exec;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Delete project setup operator
+ * \{ */
+
+static int delete_project_setup_exec(bContext *C, wmOperator *op)
+{
+ if (!BKE_project_delete_settings_directory(CTX_wm_project())) {
+ BKE_report(op->reports,
+ RPT_ERROR,
+ "Failed to delete project settings. Is the project directory read-only?");
+ return OPERATOR_CANCELLED;
+ }
+ BKE_project_active_unset();
+
+ WM_main_add_notifier(NC_PROJECT, NULL);
+ /* Update the window title. */
+ WM_event_add_notifier_ex(CTX_wm_manager(C), CTX_wm_window(C), NC_WM | ND_DATACHANGED, NULL);
+
+ return OPERATOR_FINISHED;
+}
+
+static void PROJECT_OT_delete_setup(wmOperatorType *ot)
+{
+ ot->name = "Delete Project Setup";
+ ot->idname = "PROJECT_OT_delete_setup";
+ ot->description =
+ "Remove the configuration of the current project with all settings, but keep project files "
+ "(such as .blend files) untouched";
+
+ ot->invoke = WM_operator_confirm;
+ ot->exec = delete_project_setup_exec;
+ /* omit window poll so this can work in background mode */
+ ot->poll = has_active_project_poll;
+}
+
+/** \} */
+/* -------------------------------------------------------------------- */
+/** \name Add Custom Asset Library
+ * \{ */
+
+static int custom_asset_library_add_exec(bContext *UNUSED(C), wmOperator *op)
+{
+ BlenderProject *project = CTX_wm_project();
+
+ char path[FILE_MAXDIR];
+ char dirname[FILE_MAXFILE];
+
+ RNA_string_get(op->ptr, "directory", path);
+
+ BLI_path_slash_rstrip(path);
+ /* Always keep project paths relative for now. Adds the "//" prefix which usually denotes a path
+ * that's relative to the current .blend, for now use it for project relative paths as well. */
+ BLI_path_rel(path, BKE_project_root_path_get(project));
+ BLI_split_file_part(path, dirname, sizeof(dirname));
+
+ ListBase *asset_libraries = BKE_project_custom_asset_libraries_get(project);
+ /* NULL is a valid directory path here. A library without path will be created then. */
+ BKE_asset_library_custom_add(asset_libraries, dirname, path);
+ BKE_project_tag_has_unsaved_changes(project);
+
+ WM_main_add_notifier(NC_ASSET | ND_ASSET_LIBRARY, NULL);
+ WM_main_add_notifier(NC_PROJECT, NULL);
+
+ return OPERATOR_FINISHED;
+}
+
+static int custom_asset_library_add_invoke(bContext *C,
+ wmOperator *op,
+ const wmEvent *UNUSED(event))
+{
+ if (!RNA_struct_property_is_set(op->ptr, "directory")) {
+ WM_event_add_fileselect(C, op);
+ return OPERATOR_RUNNING_MODAL;
+ }
+
+ return custom_asset_library_add_exec(C, op);
+}
+
+/* Similar to #PREFERENCES_OT_asset_library_add. */
+static void PROJECT_OT_custom_asset_library_add(wmOperatorType *ot)
+{
+ ot->name = "Add Asset Library";
+ ot->idname = "PROJECT_OT_custom_asset_library_add";
+ ot->description = "Register a directory to be used by the Asset Browser as source of assets";
+
+ ot->exec = custom_asset_library_add_exec;
+ ot->invoke = custom_asset_library_add_invoke;
+ ot->poll = has_active_project_poll;
+
+ ot->flag = OPTYPE_INTERNAL;
+
+ WM_operator_properties_filesel(ot,
+ FILE_TYPE_FOLDER,
+ FILE_SPECIAL,
+ FILE_OPENFILE,
+ WM_FILESEL_DIRECTORY,
+ FILE_DEFAULTDISPLAY,
+ FILE_SORT_DEFAULT);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Remove Custom Asset Library
+ * \{ */
+
+static int custom_asset_library_remove_exec(bContext *UNUSED(C), wmOperator *op)
+{
+ const int index = RNA_int_get(op->ptr, "index");
+
+ BlenderProject *project = CTX_wm_project();
+ ListBase *asset_libraries = BKE_project_custom_asset_libraries_get(project);
+ CustomAssetLibraryDefinition *library = BKE_asset_library_custom_find_from_index(asset_libraries,
+ index);
+ BKE_asset_library_custom_remove(asset_libraries, library);
+ BKE_project_tag_has_unsaved_changes(project);
+
+ WM_main_add_notifier(NC_ASSET | ND_ASSET_LIBRARY, NULL);
+ WM_main_add_notifier(NC_PROJECT, NULL);
+
+ return OPERATOR_FINISHED;
+}
+
+/* Similar to #PREFERENCES_OT_asset_library_remove. */
+static void PROJECT_OT_custom_asset_library_remove(wmOperatorType *ot)
+{
+ ot->name = "Remove Asset Library";
+ ot->idname = "PROJECT_OT_custom_asset_library_remove";
+ ot->description =
+ "Unregister a path to a .blend file, so the Asset Browser will not attempt to show it "
+ "anymore";
+
+ ot->exec = custom_asset_library_remove_exec;
+ ot->poll = has_active_project_poll;
+
+ ot->flag = OPTYPE_INTERNAL;
+
+ RNA_def_int(ot->srna, "index", 0, 0, INT_MAX, "Index", "", 0, 1000);
+}
+
+/** \} */
+
+void ED_operatortypes_project()
+{
+ WM_operatortype_append(PROJECT_OT_new);
+ WM_operatortype_append(PROJECT_OT_save_settings);
+ WM_operatortype_append(PROJECT_OT_delete_setup);
+ WM_operatortype_append(PROJECT_OT_custom_asset_library_add);
+ WM_operatortype_append(PROJECT_OT_custom_asset_library_remove);
+}
diff --git a/source/blender/editors/screen/screen_ops.c b/source/blender/editors/screen/screen_ops.c
index 6a910170e44..5d8864d1b25 100644
--- a/source/blender/editors/screen/screen_ops.c
+++ b/source/blender/editors/screen/screen_ops.c
@@ -5031,7 +5031,14 @@ static void SCREEN_OT_back_to_previous(struct wmOperatorType *ot)
/** \name Show User Preferences Operator
* \{ */
-static int userpref_show_exec(bContext *C, wmOperator *op)
+/**
+ * Shared window opening logic for preferences and project settings.
+ * \return True on success.
+ */
+static bool settings_window_show(bContext *C,
+ eSpace_Type space_type,
+ const char *window_title,
+ ReportList *reports)
{
wmWindow *win_cur = CTX_wm_window(C);
/* Use eventstate, not event from _invoke, so this can be called through exec(). */
@@ -5039,26 +5046,14 @@ static int userpref_show_exec(bContext *C, wmOperator *op)
int sizex = (500 + UI_NAVIGATION_REGION_WIDTH) * UI_DPI_FAC;
int sizey = 520 * UI_DPI_FAC;
- PropertyRNA *prop = RNA_struct_find_property(op->ptr, "section");
- if (prop && RNA_property_is_set(op->ptr, prop)) {
- /* Set active section via RNA, so it can fail properly. */
-
- PointerRNA pref_ptr;
- RNA_pointer_create(NULL, &RNA_Preferences, &U, &pref_ptr);
- PropertyRNA *active_section_prop = RNA_struct_find_property(&pref_ptr, "active_section");
-
- RNA_property_enum_set(&pref_ptr, active_section_prop, RNA_property_enum_get(op->ptr, prop));
- RNA_property_update(C, &pref_ptr, active_section_prop);
- }
-
/* changes context! */
if (WM_window_open(C,
- IFACE_("Blender Preferences"),
+ window_title,
event->xy[0],
event->xy[1],
sizex,
sizey,
- SPACE_USERPREF,
+ space_type,
false,
false,
true,
@@ -5067,18 +5062,43 @@ static int userpref_show_exec(bContext *C, wmOperator *op)
* So hiding in the temp window makes sense. */
ScrArea *area = CTX_wm_area(C);
ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_HEADER);
-
- region->flag |= RGN_FLAG_HIDDEN;
- ED_region_visibility_change_update(C, area, region);
+ if (region) {
+ region->flag |= RGN_FLAG_HIDDEN;
+ ED_region_visibility_change_update(C, area, region);
+ }
/* And also show the region with "Load & Save" buttons. */
region = BKE_area_find_region_type(area, RGN_TYPE_EXECUTE);
- region->flag &= ~RGN_FLAG_HIDDEN;
- ED_region_visibility_change_update(C, area, region);
+ if (region) {
+ region->flag &= ~RGN_FLAG_HIDDEN;
+ ED_region_visibility_change_update(C, area, region);
+ }
+
+ return true;
+ }
+
+ BKE_report(reports, RPT_ERROR, "Failed to open window!");
+ return false;
+}
+
+static int userpref_show_exec(bContext *C, wmOperator *op)
+{
+ PropertyRNA *prop = RNA_struct_find_property(op->ptr, "section");
+ if (prop && RNA_property_is_set(op->ptr, prop)) {
+ /* Set active section via RNA, so it can fail properly. */
+
+ PointerRNA pref_ptr;
+ RNA_pointer_create(NULL, &RNA_Preferences, &U, &pref_ptr);
+ PropertyRNA *active_section_prop = RNA_struct_find_property(&pref_ptr, "active_section");
+
+ RNA_property_enum_set(&pref_ptr, active_section_prop, RNA_property_enum_get(op->ptr, prop));
+ RNA_property_update(C, &pref_ptr, active_section_prop);
+ }
+ if (settings_window_show(C, SPACE_USERPREF, IFACE_("Blender Preferences"), op->reports)) {
return OPERATOR_FINISHED;
}
- BKE_report(op->reports, RPT_ERROR, "Failed to open window!");
+
return OPERATOR_CANCELLED;
}
@@ -5107,6 +5127,62 @@ static void SCREEN_OT_userpref_show(struct wmOperatorType *ot)
/** \} */
/* -------------------------------------------------------------------- */
+/** \name Show Project Settings Operator
+ * \{ */
+
+bool ED_project_settings_window_show(bContext *C, ReportList *reports)
+{
+ return settings_window_show(
+ C, SPACE_PROJECT_SETTINGS, IFACE_("Blender Project Settings"), reports);
+}
+
+static int project_settings_show_exec(bContext *C, wmOperator *op)
+{
+ if (!ED_project_settings_window_show(C, op->reports)) {
+ return OPERATOR_CANCELLED;
+ }
+
+ PropertyRNA *prop = RNA_struct_find_property(op->ptr, "section");
+ SpaceProjectSettings *space_project = CTX_wm_space_project_settings(C);
+ if (space_project && prop && RNA_property_is_set(op->ptr, prop)) {
+ /* Set active section via RNA, so it can fail properly. */
+
+ bScreen *screen = CTX_wm_screen(C);
+ PointerRNA space_ptr;
+ RNA_pointer_create(&screen->id, &RNA_SpaceProjectSettings, space_project, &space_ptr);
+ PropertyRNA *active_section_prop = RNA_struct_find_property(&space_ptr, "active_section");
+
+ RNA_property_enum_set(&space_ptr, active_section_prop, RNA_property_enum_get(op->ptr, prop));
+ RNA_property_update(C, &space_ptr, active_section_prop);
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+static void SCREEN_OT_project_settings_show(struct wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Open Project Settings...";
+ ot->description = "Edit configuration for the active Blender project";
+ ot->idname = "SCREEN_OT_project_settings_show";
+
+ /* api callbacks */
+ ot->exec = project_settings_show_exec;
+ ot->poll = ED_operator_screenactive_nobackground; /* Not in background as this opens a window. */
+
+ PropertyRNA *prop;
+ prop = RNA_def_enum(ot->srna,
+ "section",
+ rna_enum_project_settings_section_items,
+ 0,
+ "",
+ "Section to activate in the project settings");
+ RNA_def_property_flag(prop, PROP_HIDDEN);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
/** \name Show Drivers Editor Operator
* \{ */
@@ -5694,6 +5770,7 @@ void ED_operatortypes_screen(void)
WM_operatortype_append(SCREEN_OT_screenshot);
WM_operatortype_append(SCREEN_OT_screenshot_area);
WM_operatortype_append(SCREEN_OT_userpref_show);
+ WM_operatortype_append(SCREEN_OT_project_settings_show);
WM_operatortype_append(SCREEN_OT_drivers_editor_show);
WM_operatortype_append(SCREEN_OT_info_log_show);
WM_operatortype_append(SCREEN_OT_region_blend);
diff --git a/source/blender/editors/space_api/CMakeLists.txt b/source/blender/editors/space_api/CMakeLists.txt
index 0024e7935d8..6e04d3493d7 100644
--- a/source/blender/editors/space_api/CMakeLists.txt
+++ b/source/blender/editors/space_api/CMakeLists.txt
@@ -22,6 +22,7 @@ set(SRC
set(LIB
bf_editor_geometry
+ bf_editor_project
bf_editor_space_action
bf_editor_space_buttons
bf_editor_space_clip
@@ -33,6 +34,7 @@ set(LIB
bf_editor_space_nla
bf_editor_space_node
bf_editor_space_outliner
+ bf_editor_space_project_settings
bf_editor_space_script
bf_editor_space_sequencer
bf_editor_space_spreadsheet
diff --git a/source/blender/editors/space_api/spacetypes.c b/source/blender/editors/space_api/spacetypes.c
index 3d964a95bc0..ffd490e3c1e 100644
--- a/source/blender/editors/space_api/spacetypes.c
+++ b/source/blender/editors/space_api/spacetypes.c
@@ -43,6 +43,7 @@
#include "ED_object.h"
#include "ED_paint.h"
#include "ED_physics.h"
+#include "ED_project.h"
#include "ED_render.h"
#include "ED_scene.h"
#include "ED_screen.h"
@@ -82,9 +83,11 @@ void ED_spacetypes_init(void)
ED_spacetype_statusbar();
ED_spacetype_topbar();
ED_spacetype_spreadsheet();
+ ED_spacetype_project_settings();
/* Register operator types for screen and all spaces. */
ED_operatortypes_userpref();
+ ED_operatortypes_project();
ED_operatortypes_workspace();
ED_operatortypes_scene();
ED_operatortypes_screen();
diff --git a/source/blender/editors/space_file/file_draw.c b/source/blender/editors/space_file/file_draw.c
index 240901318b5..c3249b26fdd 100644
--- a/source/blender/editors/space_file/file_draw.c
+++ b/source/blender/editors/space_file/file_draw.c
@@ -1165,13 +1165,17 @@ static void file_draw_invalid_library_hint(const bContext *C,
{
UI_icon_draw(sx, sy - UI_UNIT_Y, ICON_INFO);
+ uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS);
+
const char *suggestion = TIP_(
"Asset Libraries are local directories that can contain .blend files with assets inside.\n"
- "Manage Asset Libraries from the File Paths section in Preferences");
+ "Manage Asset Libraries from the File Paths section in the Preferences or in the Project "
+ "Settings.");
file_draw_string_multiline(
sx + UI_UNIT_X, sy, suggestion, width - UI_UNIT_X, line_height, text_col, NULL, &sy);
- uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS);
+ const short but_offset_y = line_height + UI_UNIT_Y * 1.2f;
+ const short but_width = UI_UNIT_X * 8;
uiBut *but = uiDefIconTextButO(block,
UI_BTYPE_BUT,
"SCREEN_OT_userpref_show",
@@ -1179,13 +1183,26 @@ static void file_draw_invalid_library_hint(const bContext *C,
ICON_PREFERENCES,
NULL,
sx + UI_UNIT_X,
- sy - line_height - UI_UNIT_Y * 1.2f,
- UI_UNIT_X * 8,
+ sy - but_offset_y,
+ but_width,
UI_UNIT_Y,
NULL);
PointerRNA *but_opptr = UI_but_operator_ptr_get(but);
RNA_enum_set(but_opptr, "section", USER_SECTION_FILE_PATHS);
+ but = uiDefButO(block,
+ UI_BTYPE_BUT,
+ "SCREEN_OT_project_settings_show",
+ WM_OP_INVOKE_DEFAULT,
+ NULL,
+ sx + UI_UNIT_X + but_width + UI_UNIT_X,
+ sy - but_offset_y,
+ but_width,
+ UI_UNIT_Y,
+ NULL);
+ but_opptr = UI_but_operator_ptr_get(but);
+ RNA_enum_set(but_opptr, "section", PROJECT_SETTINGS_SECTION_ASSET_LIBRARIES);
+
UI_block_end(C, block);
UI_block_draw(C, block);
}
diff --git a/source/blender/editors/space_file/filelist.cc b/source/blender/editors/space_file/filelist.cc
index 3257534f94d..d4a42c3f4f2 100644
--- a/source/blender/editors/space_file/filelist.cc
+++ b/source/blender/editors/space_file/filelist.cc
@@ -42,6 +42,8 @@
#include "BKE_asset.h"
#include "BKE_asset_library.h"
+#include "BKE_asset_library_custom.h"
+#include "BKE_blender_project.h"
#include "BKE_context.h"
#include "BKE_global.h"
#include "BKE_icons.h"
@@ -49,12 +51,12 @@
#include "BKE_lib_id.h"
#include "BKE_main.h"
#include "BKE_main_idmap.h"
-#include "BKE_preferences.h"
#include "BLO_readfile.h"
#include "DNA_asset_types.h"
#include "DNA_space_types.h"
+#include "ED_asset_library.h"
#include "ED_datafiles.h"
#include "ED_fileselect.h"
#include "ED_screen.h"
@@ -1040,12 +1042,18 @@ static bool filelist_compare_asset_libraries(const AssetLibraryReference *librar
if (library_a->type != library_b->type) {
return false;
}
- if (library_a->type == ASSET_LIBRARY_CUSTOM) {
+
+ const bool is_custom_library = ELEM(
+ library_a->type, ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES, ASSET_LIBRARY_CUSTOM_FROM_PROJECT);
+ if (is_custom_library) {
+ if (library_a->custom_library_index != library_b->custom_library_index) {
+ return false;
+ }
+
/* Don't only check the index, also check that it's valid. */
- bUserAssetLibrary *library_ptr_a = BKE_preferences_asset_library_find_from_index(
- &U, library_a->custom_library_index);
- return (library_ptr_a != nullptr) &&
- (library_a->custom_library_index == library_b->custom_library_index);
+ if (!ED_asset_library_find_custom_library_from_reference(library_a)) {
+ return false;
+ }
}
return true;
@@ -1174,6 +1182,10 @@ static int filelist_geticon_ex(const FileDirEntry *file,
{
const eFileSel_File_Types typeflag = (eFileSel_File_Types)file->typeflag;
+ if ((typeflag & FILE_TYPE_DIR) && (typeflag & FILE_TYPE_BLENDER_PROJECT)) {
+ return ICON_FUND;
+ }
+
if ((typeflag & FILE_TYPE_DIR) &&
!(ignore_libdir && (typeflag & (FILE_TYPE_BLENDERLIB | FILE_TYPE_BLENDER)))) {
if (FILENAME_IS_PARENT(file->relpath)) {
@@ -2955,6 +2967,12 @@ static int filelist_readjob_list_dir(const char *root,
}
}
+ if ((entry->typeflag & FILE_TYPE_DIR) && !(entry->typeflag & FILE_TYPE_BLENDER)) {
+ if (BKE_project_is_path_project_root(target)) {
+ entry->typeflag |= FILE_TYPE_BLENDER_PROJECT;
+ }
+ }
+
#ifndef WIN32
/* Set linux-style dot files hidden too. */
if (is_hidden_dot_filename(entry->relpath, entry)) {
diff --git a/source/blender/editors/space_file/filesel.c b/source/blender/editors/space_file/filesel.c
index af2c9d4e757..6dd4e37fea8 100644
--- a/source/blender/editors/space_file/filesel.c
+++ b/source/blender/editors/space_file/filesel.c
@@ -39,13 +39,15 @@
#include "BLT_translation.h"
#include "BKE_appdir.h"
+#include "BKE_asset_library_custom.h"
+#include "BKE_blender_project.h"
#include "BKE_context.h"
#include "BKE_idtype.h"
#include "BKE_main.h"
-#include "BKE_preferences.h"
#include "BLF_api.h"
+#include "ED_asset_library.h"
#include "ED_fileselect.h"
#include "ED_screen.h"
@@ -409,27 +411,39 @@ static void fileselect_refresh_asset_params(FileAssetSelectParams *asset_params)
{
AssetLibraryReference *library = &asset_params->asset_library_ref;
FileSelectParams *base_params = &asset_params->base_params;
- bUserAssetLibrary *user_library = NULL;
+ CustomAssetLibraryDefinition *custom_library =
+ ED_asset_library_find_custom_library_from_reference(library);
- /* Ensure valid repository, or fall-back to local one. */
- if (library->type == ASSET_LIBRARY_CUSTOM) {
- BLI_assert(library->custom_library_index >= 0);
-
- user_library = BKE_preferences_asset_library_find_from_index(&U,
- library->custom_library_index);
- if (!user_library) {
- library->type = ASSET_LIBRARY_LOCAL;
- }
+ /* Ensure valid asset library, or fall-back to local one. */
+ if (!custom_library) {
+ library->type = ASSET_LIBRARY_LOCAL;
}
switch (library->type) {
case ASSET_LIBRARY_LOCAL:
base_params->dir[0] = '\0';
break;
- case ASSET_LIBRARY_CUSTOM:
- BLI_assert(user_library);
- BLI_strncpy(base_params->dir, user_library->path, sizeof(base_params->dir));
+ case ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES:
+ BLI_assert(custom_library);
+ BLI_strncpy(base_params->dir, custom_library->path, sizeof(base_params->dir));
break;
+ /* Project asset libraries typically use relative paths (relative to project root directory).
+ */
+ case ASSET_LIBRARY_CUSTOM_FROM_PROJECT: {
+ BlenderProject *project = CTX_wm_project();
+ BLI_assert(custom_library);
+ BLI_assert(project);
+
+ if (BLI_path_is_rel(custom_library->path)) {
+ const char *project_root_path = BKE_project_root_path_get(project);
+ BLI_path_join(
+ base_params->dir, sizeof(base_params->dir), project_root_path, custom_library->path);
+ }
+ else {
+ BLI_strncpy(base_params->dir, custom_library->path, sizeof(base_params->dir));
+ }
+ break;
+ }
}
base_params->type = (library->type == ASSET_LIBRARY_LOCAL) ? FILE_MAIN_ASSET :
FILE_ASSET_LIBRARY;
diff --git a/source/blender/editors/space_node/add_node_search.cc b/source/blender/editors/space_node/add_node_search.cc
index 28e18c20f46..3751e4cb9b8 100644
--- a/source/blender/editors/space_node/add_node_search.cc
+++ b/source/blender/editors/space_node/add_node_search.cc
@@ -123,10 +123,11 @@ static void gather_search_items_for_all_assets(const bContext &C,
Vector<AddNodeItem> &search_items)
{
int i;
- LISTBASE_FOREACH_INDEX (const bUserAssetLibrary *, asset_library, &U.asset_libraries, i) {
+ LISTBASE_FOREACH_INDEX (
+ const CustomAssetLibraryDefinition *, asset_library, &U.asset_libraries, i) {
AssetLibraryReference library_ref{};
library_ref.custom_library_index = i;
- library_ref.type = ASSET_LIBRARY_CUSTOM;
+ library_ref.type = ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES;
/* Skip local assets to avoid duplicates when the asset is part of the local file library. */
gather_search_items_for_asset_library(C, node_tree, library_ref, r_added_assets, search_items);
}
diff --git a/source/blender/editors/space_node/link_drag_search.cc b/source/blender/editors/space_node/link_drag_search.cc
index ffa2d377056..53e1093914e 100644
--- a/source/blender/editors/space_node/link_drag_search.cc
+++ b/source/blender/editors/space_node/link_drag_search.cc
@@ -243,10 +243,11 @@ static void gather_search_link_ops_for_all_assets(const bContext &C,
Vector<SocketLinkOperation> &search_link_ops)
{
int i;
- LISTBASE_FOREACH_INDEX (const bUserAssetLibrary *, asset_library, &U.asset_libraries, i) {
+ LISTBASE_FOREACH_INDEX (
+ const CustomAssetLibraryDefinition *, asset_library, &U.asset_libraries, i) {
AssetLibraryReference library_ref{};
library_ref.custom_library_index = i;
- library_ref.type = ASSET_LIBRARY_CUSTOM;
+ library_ref.type = ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES;
/* Skip local assets to avoid duplicates when the asset is part of the local file library. */
gather_search_link_ops_for_asset_library(
C, node_tree, socket, library_ref, true, search_link_ops);
diff --git a/source/blender/editors/space_project_settings/CMakeLists.txt b/source/blender/editors/space_project_settings/CMakeLists.txt
new file mode 100644
index 00000000000..95c7bff9adc
--- /dev/null
+++ b/source/blender/editors/space_project_settings/CMakeLists.txt
@@ -0,0 +1,33 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+set(INC
+ ../include
+ ../../blenfont
+ ../../blenkernel
+ ../../blenlib
+ ../../blenloader
+ ../../blentranslation
+ ../../depsgraph
+ ../../makesdna
+ ../../makesrna
+ ../../windowmanager
+ ../../../../intern/guardedalloc
+
+ # dna_type_offsets.h
+ ${CMAKE_CURRENT_BINARY_DIR}/../../makesdna/intern
+ # RNA_prototypes.h
+ ${CMAKE_BINARY_DIR}/source/blender/makesrna
+)
+
+set(SRC
+ space_project_settings.cc
+)
+
+set(LIB
+)
+
+blender_add_lib(bf_editor_space_project_settings "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
+
+# RNA_prototypes.h dna_type_offsets.h
+ add_dependencies(bf_editor_space_project_settings bf_rna)
+ add_dependencies(bf_editor_space_project_settings bf_dna)
diff --git a/source/blender/editors/space_project_settings/space_project_settings.cc b/source/blender/editors/space_project_settings/space_project_settings.cc
new file mode 100644
index 00000000000..3c62bbaf3e4
--- /dev/null
+++ b/source/blender/editors/space_project_settings/space_project_settings.cc
@@ -0,0 +1,259 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "BKE_screen.h"
+
+#include "BLI_string.h"
+#include "BLI_string_ref.hh"
+
+#include "BLO_read_write.h"
+
+#include "DNA_screen_types.h"
+#include "DNA_space_types.h"
+
+#include "ED_screen.h"
+#include "ED_space_api.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "RNA_access.h"
+#include "RNA_enum_types.h"
+
+#include "UI_interface.h"
+
+using namespace blender;
+
+static SpaceLink *project_settings_create(const ScrArea *area, const Scene *UNUSED(scene))
+{
+ SpaceProjectSettings *project_settings_space = MEM_cnew<SpaceProjectSettings>(
+ "project settings space");
+ project_settings_space->spacetype = SPACE_PROJECT_SETTINGS;
+
+ {
+ /* Header. */
+ ARegion *region = MEM_cnew<ARegion>("project settings header");
+ BLI_addtail(&project_settings_space->regionbase, region);
+ region->regiontype = RGN_TYPE_HEADER;
+ /* Ignore preference "USER_HEADER_BOTTOM" here (always show bottom for new types). */
+ region->alignment = RGN_ALIGN_BOTTOM;
+ }
+
+ {
+ /* navigation region */
+ ARegion *region = MEM_cnew<ARegion>("project settings navigation region");
+ BLI_addtail(&project_settings_space->regionbase, region);
+ region->regiontype = RGN_TYPE_NAV_BAR;
+ region->alignment = RGN_ALIGN_LEFT;
+
+ /* Use smaller size when opened in area like properties editor (same as preferences do). */
+ if (area->winx && area->winx < 3.0f * UI_NAVIGATION_REGION_WIDTH * UI_DPI_FAC) {
+ region->sizex = UI_NARROW_NAVIGATION_REGION_WIDTH;
+ }
+ }
+ {
+ /* execution region */
+ ARegion *region = MEM_cnew<ARegion>("project settings execution region");
+ BLI_addtail(&project_settings_space->regionbase, region);
+ region->regiontype = RGN_TYPE_EXECUTE;
+ region->alignment = RGN_ALIGN_BOTTOM | RGN_SPLIT_PREV;
+ region->flag |= RGN_FLAG_DYNAMIC_SIZE | RGN_FLAG_HIDDEN;
+ }
+
+ {
+ /* Main window. */
+ ARegion *region = MEM_cnew<ARegion>("project settings main region");
+ BLI_addtail(&project_settings_space->regionbase, region);
+ region->regiontype = RGN_TYPE_WINDOW;
+ }
+
+ return reinterpret_cast<SpaceLink *>(project_settings_space);
+}
+
+static void project_settings_free(SpaceLink *UNUSED(sl))
+{
+}
+
+static void project_settings_init(wmWindowManager *UNUSED(wm), ScrArea *UNUSED(area))
+{
+}
+
+static SpaceLink *project_settings_duplicate(SpaceLink *sl)
+{
+ const SpaceProjectSettings *sproject_settings_old = reinterpret_cast<SpaceProjectSettings *>(sl);
+ SpaceProjectSettings *sproject_settings_new = reinterpret_cast<SpaceProjectSettings *>(
+ MEM_dupallocN(sproject_settings_old));
+
+ return reinterpret_cast<SpaceLink *>(sproject_settings_new);
+}
+
+static void project_settings_listener(const wmSpaceTypeListenerParams *params)
+{
+ const wmNotifier *wmn = params->notifier;
+ ScrArea *area = params->area;
+
+ switch (wmn->category) {
+ case NC_PROJECT:
+ ED_area_tag_redraw(area);
+ break;
+ }
+}
+
+static void project_settings_operatortypes(void)
+{
+}
+
+static void project_settings_keymap(struct wmKeyConfig *UNUSED(keyconf))
+{
+}
+
+static void project_settings_blend_write(BlendWriter *writer, SpaceLink *sl)
+{
+ BLO_write_struct(writer, SpaceProjectSettings, sl);
+}
+
+/* add handlers, stuff you only do once or on area/region changes */
+static void project_settings_main_region_init(wmWindowManager *wm, ARegion *region)
+{
+ /* do not use here, the properties changed in user-preferences do a system-wide refresh,
+ * then scroller jumps back */
+ // region->v2d.flag &= ~V2D_IS_INIT;
+
+ region->v2d.scroll = V2D_SCROLL_RIGHT | V2D_SCROLL_VERTICAL_HIDE;
+
+ ED_region_panels_init(wm, region);
+}
+
+static void project_settings_main_region_layout(const bContext *C, ARegion *region)
+{
+ SpaceProjectSettings *sproject_settings = CTX_wm_space_project_settings(C);
+
+ char id_lower[64];
+ const char *contexts[2] = {id_lower, NULL};
+
+ if (!CTX_wm_project()) {
+ /* Special context for when there is no project. UI can draw a special panel then. */
+ STRNCPY(id_lower, "no_project");
+ }
+ else {
+ /* Avoid duplicating identifiers, use existing RNA enum. */
+ const EnumPropertyItem *items = rna_enum_project_settings_section_items;
+ int i = RNA_enum_from_value(items, sproject_settings->active_section);
+ /* Enum value not found: File is from the future. */
+ if (i == -1) {
+ i = 0;
+ }
+ StringRefNull id = items[i].identifier;
+ BLI_assert(id.size() < (int64_t)sizeof(id_lower));
+ STRNCPY(id_lower, id.c_str());
+ BLI_str_tolower_ascii(id_lower, strlen(id_lower));
+ }
+
+ ED_region_panels_layout_ex(C, region, &region->type->paneltypes, contexts, NULL);
+}
+
+static void project_settings_main_region_listener(const wmRegionListenerParams *UNUSED(params))
+{
+}
+
+static void project_settings_header_region_init(wmWindowManager *UNUSED(wm), ARegion *region)
+{
+ ED_region_header_init(region);
+}
+
+static void project_settings_header_region_listener(const wmRegionListenerParams *UNUSED(params))
+{
+}
+
+/* add handlers, stuff you only do once or on area/region changes */
+static void project_settings_navigation_region_init(wmWindowManager *wm, ARegion *region)
+{
+ region->v2d.scroll = V2D_SCROLL_RIGHT | V2D_SCROLL_VERTICAL_HIDE;
+
+ ED_region_panels_init(wm, region);
+}
+
+static void project_settings_navigation_region_draw(const bContext *C, ARegion *region)
+{
+ ED_region_panels(C, region);
+}
+
+static void project_settings_navigation_region_listener(
+ const wmRegionListenerParams *UNUSED(params))
+{
+}
+
+/* add handlers, stuff you only do once or on area/region changes */
+static void project_settings_execute_region_init(wmWindowManager *wm, ARegion *region)
+{
+ ED_region_panels_init(wm, region);
+ region->v2d.keepzoom |= V2D_LOCKZOOM_X | V2D_LOCKZOOM_Y;
+}
+
+static void project_settings_execute_region_listener(const wmRegionListenerParams *UNUSED(params))
+{
+}
+
+void ED_spacetype_project_settings()
+{
+ SpaceType *st = MEM_cnew<SpaceType>("spacetype project settings");
+
+ st->spaceid = SPACE_PROJECT_SETTINGS;
+ STRNCPY(st->name, "Project Settings");
+
+ st->create = project_settings_create;
+ st->free = project_settings_free;
+ st->init = project_settings_init;
+ st->duplicate = project_settings_duplicate;
+ st->listener = project_settings_listener;
+ st->operatortypes = project_settings_operatortypes;
+ st->keymap = project_settings_keymap;
+ st->blend_write = project_settings_blend_write;
+
+ ARegionType *art;
+
+ /* regions: main window */
+ art = MEM_cnew<ARegionType>("spacetype project settings region");
+ art->regionid = RGN_TYPE_WINDOW;
+ art->keymapflag = ED_KEYMAP_UI;
+
+ art->init = project_settings_main_region_init;
+ art->layout = project_settings_main_region_layout;
+ art->draw = ED_region_panels_draw;
+ art->listener = project_settings_main_region_listener;
+ BLI_addhead(&st->regiontypes, art);
+
+ /* regions: header */
+ art = MEM_cnew<ARegionType>("spacetype project settings header region");
+ art->regionid = RGN_TYPE_HEADER;
+ art->prefsizey = HEADERY;
+ art->keymapflag = ED_KEYMAP_UI | ED_KEYMAP_VIEW2D | ED_KEYMAP_HEADER;
+
+ art->listener = project_settings_header_region_listener;
+ art->init = project_settings_header_region_init;
+ art->draw = ED_region_header;
+ BLI_addhead(&st->regiontypes, art);
+
+ /* regions: navigation window */
+ art = MEM_cnew<ARegionType>("spacetype project settings navigation region");
+ art->regionid = RGN_TYPE_NAV_BAR;
+ art->prefsizex = UI_NAVIGATION_REGION_WIDTH;
+ art->keymapflag = ED_KEYMAP_UI | ED_KEYMAP_NAVBAR;
+
+ art->init = project_settings_navigation_region_init;
+ art->draw = project_settings_navigation_region_draw;
+ art->listener = project_settings_navigation_region_listener;
+ BLI_addhead(&st->regiontypes, art);
+
+ /* regions: execution window */
+ art = MEM_cnew<ARegionType>("spacetype project settings execute region");
+ art->regionid = RGN_TYPE_EXECUTE;
+ art->prefsizey = HEADERY;
+ art->keymapflag = ED_KEYMAP_UI;
+
+ art->init = project_settings_execute_region_init;
+ art->layout = ED_region_panels_layout;
+ art->draw = ED_region_panels_draw;
+ art->listener = project_settings_execute_region_listener;
+ BLI_addhead(&st->regiontypes, art);
+
+ BKE_spacetype_register(st);
+}
diff --git a/source/blender/editors/space_userpref/userpref_ops.c b/source/blender/editors/space_userpref/userpref_ops.c
index a7a741b3ce2..010bddfb4a7 100644
--- a/source/blender/editors/space_userpref/userpref_ops.c
+++ b/source/blender/editors/space_userpref/userpref_ops.c
@@ -16,9 +16,9 @@
#endif
#include "BLI_path_util.h"
+#include "BKE_asset_library_custom.h"
#include "BKE_context.h"
#include "BKE_main.h"
-#include "BKE_preferences.h"
#include "BKE_report.h"
@@ -134,7 +134,7 @@ static int preferences_asset_library_add_exec(bContext *UNUSED(C), wmOperator *o
BLI_split_file_part(path, dirname, sizeof(dirname));
/* NULL is a valid directory path here. A library without path will be created then. */
- BKE_preferences_asset_library_add(&U, dirname, path);
+ BKE_asset_library_custom_add(&U.asset_libraries, dirname, path);
U.runtime.is_dirty = true;
/* There's no dedicated notifier for the Preferences. */
@@ -156,6 +156,7 @@ static int preferences_asset_library_add_invoke(bContext *C,
return preferences_asset_library_add_exec(C, op);
}
+/* Similar to #PROJECT_OT_custom_asset_library_add. */
static void PREFERENCES_OT_asset_library_add(wmOperatorType *ot)
{
ot->name = "Add Asset Library";
@@ -185,9 +186,9 @@ static void PREFERENCES_OT_asset_library_add(wmOperatorType *ot)
static int preferences_asset_library_remove_exec(bContext *UNUSED(C), wmOperator *op)
{
const int index = RNA_int_get(op->ptr, "index");
- bUserAssetLibrary *library = BLI_findlink(&U.asset_libraries, index);
+ CustomAssetLibraryDefinition *library = BLI_findlink(&U.asset_libraries, index);
if (library) {
- BKE_preferences_asset_library_remove(&U, library);
+ BKE_asset_library_custom_remove(&U.asset_libraries, library);
U.runtime.is_dirty = true;
/* Trigger refresh for the Asset Browser. */
WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, NULL);
@@ -195,6 +196,7 @@ static int preferences_asset_library_remove_exec(bContext *UNUSED(C), wmOperator
return OPERATOR_FINISHED;
}
+/* Similar to #PROJECT_OT_custom_asset_library_add. */
static void PREFERENCES_OT_asset_library_remove(wmOperatorType *ot)
{
ot->name = "Remove Asset Library";
diff --git a/source/blender/editors/util/CMakeLists.txt b/source/blender/editors/util/CMakeLists.txt
index 128cb897ac4..5661fac74b2 100644
--- a/source/blender/editors/util/CMakeLists.txt
+++ b/source/blender/editors/util/CMakeLists.txt
@@ -68,6 +68,7 @@ set(SRC
../include/ED_paint.h
../include/ED_particle.h
../include/ED_physics.h
+ ../include/ED_project.h
../include/ED_render.h
../include/ED_scene.h
../include/ED_screen.h
diff --git a/source/blender/makesdna/DNA_asset_types.h b/source/blender/makesdna/DNA_asset_types.h
index 29795519719..2bbf38b108e 100644
--- a/source/blender/makesdna/DNA_asset_types.h
+++ b/source/blender/makesdna/DNA_asset_types.h
@@ -2,6 +2,9 @@
/** \file
* \ingroup DNA
+ *
+ * Only contains types that need writing to files or that are accessed directly via RNA (as opposed
+ * to being opaque types accessed via an API).
*/
#pragma once
@@ -88,32 +91,43 @@ typedef enum eAssetLibraryType {
// ASSET_LIBRARY_PROJECT = 2,
/** Display assets from custom asset libraries, as defined in the preferences
- * (#bUserAssetLibrary). The name will be taken from #FileSelectParams.asset_library_ref.idname
- * then.
- * In RNA, we add the index of the custom library to this to identify it by index. So keep
- * this last! */
- ASSET_LIBRARY_CUSTOM = 100,
+ * (#CustomAssetLibraryDefinition). In RNA, we add the index of the custom library to this to
+ * identify it by index. */
+ ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES = 100,
+ /** Same as #ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES, but the library is defined for a specific
+ project only. */
+ ASSET_LIBRARY_CUSTOM_FROM_PROJECT = 500,
} eAssetLibraryType;
/**
- * Information to identify an asset library. May be either one of the predefined types (current
- * 'Main', builtin library, project library), or a custom type as defined in the Preferences.
- *
- * If the type is set to #ASSET_LIBRARY_CUSTOM, `custom_library_index` must be set to identify the
- * custom library. Otherwise it is not used.
+ * Information to identify an asset library. May be either one of the predefined types ("Current
+ * File" builtin library), or a custom type as defined in the Preferences/Project.
*/
typedef struct AssetLibraryReference {
+ /* If set to #ASSET_LIBRARY_CUSTOM_XXX, `custom_library_index` must be set to identify
+ * the custom library. Otherwise it is not used. */
short type; /* eAssetLibraryType */
char _pad1[2];
/**
- * If showing a custom asset library (#ASSET_LIBRARY_CUSTOM), this is the index of the
- * #bUserAssetLibrary within #UserDef.asset_libraries.
+ * If showing a custom asset library (#ASSET_LIBRARY_CUSTOM_XXX), this is the index of the
+ * #CustomAssetLibraryDefinition within its owner (preferences or project settings).
* Should be ignored otherwise (but better set to -1 then, for sanity and debugging).
*/
int custom_library_index;
} AssetLibraryReference;
/**
+ * Custom asset libraries can be registered in the preferences or project settings. This is the
+ * container to hold these settings.
+ */
+typedef struct CustomAssetLibraryDefinition {
+ struct CustomAssetLibraryDefinition *next, *prev;
+
+ char name[64]; /* MAX_NAME */
+ char path[1024]; /* FILE_MAX */
+} CustomAssetLibraryDefinition;
+
+/**
* Not part of the core design, we should try to get rid of it. Only needed to wrap FileDirEntry
* into a type with PropertyGroup as base, so we can have an RNA collection of #AssetHandle's to
* pass to the UI.
diff --git a/source/blender/makesdna/DNA_space_types.h b/source/blender/makesdna/DNA_space_types.h
index 4bb92e6fcc5..d05ba0f7e2e 100644
--- a/source/blender/makesdna/DNA_space_types.h
+++ b/source/blender/makesdna/DNA_space_types.h
@@ -1083,6 +1083,8 @@ typedef enum eFileSel_File_Types {
FILE_TYPE_VOLUME = (1 << 19),
FILE_TYPE_ASSET = (1 << 28),
+ /** Directory is a Blender project root directory. */
+ FILE_TYPE_BLENDER_PROJECT = (1 << 29),
/** An FS directory (i.e. S_ISDIR on its path is true). */
FILE_TYPE_DIR = (1 << 30),
FILE_TYPE_BLENDERLIB = (1u << 31),
@@ -1722,6 +1724,30 @@ typedef struct SpaceUserPref {
/** \} */
/* -------------------------------------------------------------------- */
+/** \name Project Settings
+ * \{ */
+
+typedef struct SpaceProjectSettings {
+ SpaceLink *next, *prev;
+ /** Storage of regions for inactive spaces. */
+ ListBase regionbase;
+ char spacetype;
+ char link_flag;
+ char _pad0[6];
+ /* End 'SpaceLink' header. */
+
+ char active_section; /* eSpaceProjectSettings_Section */
+ char _pad1[7];
+} SpaceProjectSettings;
+
+typedef enum eSpaceProjectSettings_Section {
+ PROJECT_SETTINGS_SECTION_GENERAL = 0,
+ PROJECT_SETTINGS_SECTION_ASSET_LIBRARIES = 1,
+} eSpaceProjectSettings_Section;
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
/** \name Motion Tracking
* \{ */
@@ -2057,9 +2083,10 @@ typedef enum eSpace_Type {
SPACE_CLIP = 20,
SPACE_TOPBAR = 21,
SPACE_STATUSBAR = 22,
- SPACE_SPREADSHEET = 23
+ SPACE_SPREADSHEET = 23,
+ SPACE_PROJECT_SETTINGS = 24
-#define SPACE_TYPE_NUM (SPACE_SPREADSHEET + 1)
+#define SPACE_TYPE_NUM (SPACE_PROJECT_SETTINGS + 1)
} eSpace_Type;
/* use for function args */
diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h
index 9d8b75450ca..5a6c7992a3e 100644
--- a/source/blender/makesdna/DNA_userdef_types.h
+++ b/source/blender/makesdna/DNA_userdef_types.h
@@ -487,6 +487,7 @@ typedef struct bTheme {
ThemeSpace space_outliner;
ThemeSpace space_node;
ThemeSpace space_preferences;
+ ThemeSpace space_project_settings;
ThemeSpace space_console;
ThemeSpace space_clip;
ThemeSpace space_topbar;
@@ -571,13 +572,6 @@ enum {
USER_MENU_TYPE_PROP = 4,
};
-typedef struct bUserAssetLibrary {
- struct bUserAssetLibrary *next, *prev;
-
- char name[64]; /* MAX_NAME */
- char path[1024]; /* FILE_MAX */
-} bUserAssetLibrary;
-
typedef struct SolidLight {
int flag;
float smooth;
@@ -653,7 +647,8 @@ typedef struct UserDef_Experimental {
char use_sculpt_texture_paint;
char use_draw_manager_acquire_lock;
char use_realtime_compositor;
- char _pad[7];
+ char use_blender_projects;
+ char _pad[6];
/** `makesdna` does not allow empty structs. */
} UserDef_Experimental;
@@ -771,7 +766,7 @@ typedef struct UserDef {
struct ListBase autoexec_paths;
/** #bUserMenu. */
struct ListBase user_menus;
- /** #bUserAssetLibrary */
+ /** #CustomAssetLibraryDefinition */
struct ListBase asset_libraries;
char keyconfigstr[64];
diff --git a/source/blender/makesdna/intern/dna_rename_defs.h b/source/blender/makesdna/intern/dna_rename_defs.h
index 4a009a88f1a..864fbc45b13 100644
--- a/source/blender/makesdna/intern/dna_rename_defs.h
+++ b/source/blender/makesdna/intern/dna_rename_defs.h
@@ -41,6 +41,7 @@ DNA_STRUCT_RENAME(Lamp, Light)
DNA_STRUCT_RENAME(SpaceButs, SpaceProperties)
DNA_STRUCT_RENAME(SpaceIpo, SpaceGraph)
DNA_STRUCT_RENAME(SpaceOops, SpaceOutliner)
+DNA_STRUCT_RENAME(bUserAssetLibrary, CustomAssetLibraryDefinition)
DNA_STRUCT_RENAME_ELEM(BPoint, alfa, tilt)
DNA_STRUCT_RENAME_ELEM(BezTriple, alfa, tilt)
DNA_STRUCT_RENAME_ELEM(Bone, curveInX, curve_in_x)
diff --git a/source/blender/makesrna/RNA_enum_items.h b/source/blender/makesrna/RNA_enum_items.h
index 53241c4a809..7c7b2c9b985 100644
--- a/source/blender/makesrna/RNA_enum_items.h
+++ b/source/blender/makesrna/RNA_enum_items.h
@@ -206,6 +206,8 @@ DEF_ENUM(rna_enum_context_mode_items)
DEF_ENUM(rna_enum_preference_section_items)
+DEF_ENUM(rna_enum_project_settings_section_items)
+
DEF_ENUM(rna_enum_attribute_type_items)
DEF_ENUM(rna_enum_color_attribute_type_items)
DEF_ENUM(rna_enum_attribute_type_with_auto_items)
diff --git a/source/blender/makesrna/intern/CMakeLists.txt b/source/blender/makesrna/intern/CMakeLists.txt
index f028b199098..4fbb246e056 100644
--- a/source/blender/makesrna/intern/CMakeLists.txt
+++ b/source/blender/makesrna/intern/CMakeLists.txt
@@ -20,6 +20,7 @@ set(DEFSRC
rna_armature.c
rna_asset.c
rna_attribute.c
+ rna_blender_project.c
rna_boid.c
rna_brush.c
rna_cachefile.c
diff --git a/source/blender/makesrna/intern/makesrna.c b/source/blender/makesrna/intern/makesrna.c
index 3592ecd84c8..95f20592152 100644
--- a/source/blender/makesrna/intern/makesrna.c
+++ b/source/blender/makesrna/intern/makesrna.c
@@ -4494,6 +4494,7 @@ static RNAProcessItem PROCESS_ITEMS[] = {
{"rna_armature.c", "rna_armature_api.c", RNA_def_armature},
{"rna_attribute.c", NULL, RNA_def_attribute},
{"rna_asset.c", NULL, RNA_def_asset},
+ {"rna_blender_project.c", NULL, RNA_def_blender_project},
{"rna_boid.c", NULL, RNA_def_boid},
{"rna_brush.c", NULL, RNA_def_brush},
{"rna_cachefile.c", NULL, RNA_def_cachefile},
diff --git a/source/blender/makesrna/intern/rna_asset.c b/source/blender/makesrna/intern/rna_asset.c
index 3c7eb3d208f..7fbb6926cf1 100644
--- a/source/blender/makesrna/intern/rna_asset.c
+++ b/source/blender/makesrna/intern/rna_asset.c
@@ -19,6 +19,7 @@
# include "BKE_asset.h"
# include "BKE_asset_library.h"
+# include "BKE_asset_library_custom.h"
# include "BKE_context.h"
# include "BKE_idprop.h"
@@ -30,6 +31,9 @@
# include "RNA_access.h"
+# include "WM_api.h"
+# include "WM_types.h"
+
static bool rna_AssetMetaData_editable_from_owner_id(const ID *owner_id,
const AssetMetaData *asset_data,
const char **r_info)
@@ -254,6 +258,35 @@ void rna_AssetMetaData_catalog_id_update(struct bContext *C, struct PointerRNA *
BKE_asset_library_refresh_catalog_simplename(asset_library, asset_data);
}
+static void rna_CustomAssetLibraryDefinition_name_set(PointerRNA *ptr, const char *value)
+{
+ CustomAssetLibraryDefinition *library = (CustomAssetLibraryDefinition *)ptr->data;
+
+ /* We can't cleanly access the owning listbase here, but reconstructing the list from the link is
+ * fine. */
+ ListBase asset_libraries = BLI_listbase_from_link((Link *)library);
+ BKE_asset_library_custom_name_set(&asset_libraries, library, value);
+}
+
+static void rna_CustomAssetLibraryDefinition_path_set(PointerRNA *ptr, const char *value)
+{
+ CustomAssetLibraryDefinition *library = (CustomAssetLibraryDefinition *)ptr->data;
+
+ char dirpath[FILE_MAX];
+ BLI_strncpy(dirpath, value, sizeof(dirpath));
+ if (BLI_is_file(dirpath)) {
+ BLI_path_parent_dir(dirpath);
+ }
+ BKE_asset_library_custom_path_set(library, dirpath);
+}
+
+void rna_AssetLibrary_settings_update(Main *UNUSED(bmain),
+ Scene *UNUSED(scene),
+ PointerRNA *UNUSED(ptr))
+{
+ WM_main_add_notifier(NC_ASSET | ND_ASSET_LIBRARY, NULL);
+}
+
static PointerRNA rna_AssetHandle_file_data_get(PointerRNA *ptr)
{
AssetHandle *asset_handle = ptr->data;
@@ -485,6 +518,30 @@ static void rna_def_asset_library_reference(BlenderRNA *brna)
srna, "Asset Library Reference", "Identifier to refer to the asset library");
}
+static void rna_def_asset_library_reference_custom(BlenderRNA *brna)
+{
+ StructRNA *srna;
+ PropertyRNA *prop;
+
+ srna = RNA_def_struct(brna, "CustomAssetLibraryDefinition", NULL);
+ RNA_def_struct_clear_flag(srna, STRUCT_UNDO);
+ RNA_def_struct_ui_text(
+ srna, "Asset Library", "Settings to define a reusable library for Asset Browsers to use");
+
+ prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE);
+ RNA_def_property_ui_text(
+ prop, "Name", "Identifier (not necessarily unique) for the asset library");
+ RNA_def_property_string_funcs(prop, NULL, NULL, "rna_CustomAssetLibraryDefinition_name_set");
+ RNA_def_struct_name_property(srna, prop);
+ RNA_def_property_update(prop, 0, "rna_AssetLibrary_settings_update");
+
+ prop = RNA_def_property(srna, "path", PROP_STRING, PROP_DIRPATH);
+ RNA_def_property_ui_text(
+ prop, "Path", "Path to a directory with .blend files to use as an asset library");
+ RNA_def_property_string_funcs(prop, NULL, NULL, "rna_CustomAssetLibraryDefinition_path_set");
+ RNA_def_property_update(prop, 0, "rna_AssetLibrary_settings_update");
+}
+
PropertyRNA *rna_def_asset_library_reference_common(struct StructRNA *srna,
const char *get,
const char *set)
@@ -503,6 +560,7 @@ void RNA_def_asset(BlenderRNA *brna)
rna_def_asset_tag(brna);
rna_def_asset_data(brna);
rna_def_asset_library_reference(brna);
+ rna_def_asset_library_reference_custom(brna);
rna_def_asset_handle(brna);
rna_def_asset_catalog_path(brna);
diff --git a/source/blender/makesrna/intern/rna_blender_project.c b/source/blender/makesrna/intern/rna_blender_project.c
new file mode 100644
index 00000000000..b8fc7f58bc7
--- /dev/null
+++ b/source/blender/makesrna/intern/rna_blender_project.c
@@ -0,0 +1,158 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/** \file
+ * \ingroup RNA
+ */
+
+#include "RNA_define.h"
+
+#include "rna_internal.h"
+
+#ifdef RNA_RUNTIME
+
+# include "BKE_asset_library_custom.h"
+# include "BKE_blender_project.h"
+
+# include "BLT_translation.h"
+
+# include "WM_api.h"
+
+static void rna_BlenderProject_update(Main *UNUSED(bmain),
+ Scene *UNUSED(scene),
+ PointerRNA *UNUSED(ptr))
+{
+ /* TODO evaluate which props should send which notifiers. */
+ /* Force full redraw of all windows. */
+ WM_main_add_notifier(NC_WINDOW, NULL);
+}
+
+static void rna_BlenderProject_name_get(PointerRNA *ptr, char *value)
+{
+ BlenderProject *project = ptr->data;
+ if (!project) {
+ value[0] = '\0';
+ return;
+ }
+
+ strcpy(value, BKE_project_name_get(project));
+}
+
+static int rna_BlenderProject_name_length(PointerRNA *ptr)
+{
+ BlenderProject *project = ptr->data;
+ if (!project) {
+ return 0;
+ }
+
+ return strlen(BKE_project_name_get(project));
+}
+
+static void rna_BlenderProject_name_set(PointerRNA *ptr, const char *value)
+{
+ BlenderProject *project = ptr->data;
+
+ if (!project) {
+ return;
+ }
+
+ BKE_project_name_set(project, value);
+}
+
+static void rna_BlenderProject_root_path_get(PointerRNA *ptr, char *value)
+{
+ BlenderProject *project = ptr->data;
+ if (!project) {
+ value[0] = '\0';
+ return;
+ }
+
+ strcpy(value, BKE_project_root_path_get(project));
+}
+
+static int rna_BlenderProject_root_path_length(PointerRNA *ptr)
+{
+ BlenderProject *project = ptr->data;
+ if (!project) {
+ return 0;
+ }
+
+ return strlen(BKE_project_root_path_get(project));
+}
+
+static void rna_BlenderProject_root_path_set(PointerRNA *UNUSED(ptr), const char *UNUSED(value))
+{
+ /* Property is not editable, see #rna_BlenderProject_root_path_editable(). */
+ BLI_assert_unreachable();
+}
+
+static int rna_BlenderProject_root_path_editable(PointerRNA *UNUSED(ptr), const char **r_info)
+{
+ /* Path is never editable (setting up a project is an operation), but return a nicer disabled
+ * hint. */
+ *r_info = N_("Project location cannot be changed, displayed for informal purposes only");
+ return 0;
+}
+
+static void rna_BlenderProject_asset_libraries_begin(CollectionPropertyIterator *iter,
+ PointerRNA *ptr)
+{
+ BlenderProject *project = ptr->data;
+ ListBase *asset_libraries = BKE_project_custom_asset_libraries_get(project);
+ rna_iterator_listbase_begin(iter, asset_libraries, NULL);
+}
+
+static bool rna_BlenderProject_is_dirty_get(PointerRNA *ptr)
+{
+ const BlenderProject *project = ptr->data;
+ return BKE_project_has_unsaved_changes(project);
+}
+
+#else
+
+void RNA_def_blender_project(BlenderRNA *brna)
+{
+ StructRNA *srna = RNA_def_struct(brna, "BlenderProject", NULL);
+ RNA_def_struct_ui_text(srna, "Blender Project", "");
+
+ PropertyRNA *prop;
+
+ prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE);
+ RNA_def_property_string_funcs(prop,
+ "rna_BlenderProject_name_get",
+ "rna_BlenderProject_name_length",
+ "rna_BlenderProject_name_set");
+ RNA_def_property_ui_text(prop, "Name", "The identifier for the project");
+ RNA_def_struct_name_property(srna, prop);
+ RNA_def_property_update(prop, 0, "rna_BlenderProject_update");
+
+ prop = RNA_def_property(srna, "root_path", PROP_STRING, PROP_NONE);
+ RNA_def_property_string_funcs(prop,
+ "rna_BlenderProject_root_path_get",
+ "rna_BlenderProject_root_path_length",
+ "rna_BlenderProject_root_path_set");
+ RNA_def_property_editable_func(prop, "rna_BlenderProject_root_path_editable");
+ RNA_def_property_ui_text(prop, "Location", "The location of the project on disk");
+
+ prop = RNA_def_property(srna, "asset_libraries", PROP_COLLECTION, PROP_NONE);
+ RNA_def_property_struct_type(prop, "CustomAssetLibraryDefinition");
+ RNA_def_property_collection_funcs(prop,
+ "rna_BlenderProject_asset_libraries_begin",
+ "rna_iterator_listbase_next",
+ "rna_iterator_listbase_end",
+ "rna_iterator_listbase_get",
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ RNA_def_property_ui_text(prop, "Asset Libraries", "");
+
+ prop = RNA_def_property(srna, "is_dirty", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_funcs(prop, "rna_BlenderProject_is_dirty_get", NULL);
+ RNA_def_property_clear_flag(prop, PROP_EDITABLE);
+ RNA_def_property_ui_text(
+ prop,
+ "Dirty",
+ "Project settings have changed since read from disk. Save the settings to keep them");
+}
+
+#endif
diff --git a/source/blender/makesrna/intern/rna_context.c b/source/blender/makesrna/intern/rna_context.c
index e723be2ab71..2b910a9eaac 100644
--- a/source/blender/makesrna/intern/rna_context.c
+++ b/source/blender/makesrna/intern/rna_context.c
@@ -203,6 +203,15 @@ static PointerRNA rna_Context_preferences_get(PointerRNA *UNUSED(ptr))
return newptr;
}
+static PointerRNA rna_Context_project_get(PointerRNA *UNUSED(ptr))
+{
+ struct BlenderProject *project = CTX_wm_project();
+
+ PointerRNA newptr;
+ RNA_pointer_create(NULL, &RNA_BlenderProject, project, &newptr);
+ return newptr;
+}
+
static int rna_Context_mode_get(PointerRNA *ptr)
{
bContext *C = (bContext *)ptr->data;
@@ -338,6 +347,11 @@ void RNA_def_context(BlenderRNA *brna)
RNA_def_property_struct_type(prop, "Preferences");
RNA_def_property_pointer_funcs(prop, "rna_Context_preferences_get", NULL, NULL, NULL);
+ prop = RNA_def_property(srna, "project", PROP_POINTER, PROP_NONE);
+ RNA_def_property_clear_flag(prop, PROP_EDITABLE);
+ RNA_def_property_struct_type(prop, "BlenderProject");
+ RNA_def_property_pointer_funcs(prop, "rna_Context_project_get", NULL, NULL, NULL);
+
prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, rna_enum_context_mode_items);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
diff --git a/source/blender/makesrna/intern/rna_internal.h b/source/blender/makesrna/intern/rna_internal.h
index ea829e5cd86..9b7534d081d 100644
--- a/source/blender/makesrna/intern/rna_internal.h
+++ b/source/blender/makesrna/intern/rna_internal.h
@@ -144,6 +144,7 @@ void RNA_def_animviz(struct BlenderRNA *brna);
void RNA_def_armature(struct BlenderRNA *brna);
void RNA_def_attribute(struct BlenderRNA *brna);
void RNA_def_asset(struct BlenderRNA *brna);
+void RNA_def_blender_project(struct BlenderRNA *brna);
void RNA_def_boid(struct BlenderRNA *brna);
void RNA_def_brush(struct BlenderRNA *brna);
void RNA_def_cachefile(struct BlenderRNA *brna);
diff --git a/source/blender/makesrna/intern/rna_screen.c b/source/blender/makesrna/intern/rna_screen.c
index a65bd613ecf..2051045cd73 100644
--- a/source/blender/makesrna/intern/rna_screen.c
+++ b/source/blender/makesrna/intern/rna_screen.c
@@ -186,6 +186,10 @@ static const EnumPropertyItem *rna_Area_ui_type_itemf(bContext *C,
continue;
}
+ if (!U.experimental.use_blender_projects && (item_from->value == SPACE_PROJECT_SETTINGS)) {
+ continue;
+ }
+
SpaceType *st = item_from->identifier[0] ? BKE_spacetype_from_id(item_from->value) : NULL;
int totitem_prev = totitem;
if (st && st->space_subtype_item_extend != NULL) {
diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c
index b2663b89333..2ac30c44ea8 100644
--- a/source/blender/makesrna/intern/rna_space.c
+++ b/source/blender/makesrna/intern/rna_space.c
@@ -171,11 +171,16 @@ const EnumPropertyItem rna_enum_space_type_items[] = {
ICON_SPREADSHEET,
"Spreadsheet",
"Explore geometry data in a table"},
+ {SPACE_PROJECT_SETTINGS,
+ "PROJECT_SETTINGS",
+ ICON_PREFERENCES,
+ "Project Settings",
+ "Edit persistent configuration settings for the active project"},
{SPACE_USERPREF,
"PREFERENCES",
ICON_PREFERENCES,
"Preferences",
- "Edit persistent configuration settings"},
+ "Edit persistent configuration settings for personal use"},
{0, NULL, 0, NULL, NULL},
};
@@ -528,6 +533,12 @@ static const EnumPropertyItem rna_enum_curve_display_handle_items[] = {
{0, NULL, 0, NULL, NULL},
};
+const EnumPropertyItem rna_enum_project_settings_section_items[] = {
+ {PROJECT_SETTINGS_SECTION_GENERAL, "GENERAL", 0, "General", ""},
+ {PROJECT_SETTINGS_SECTION_ASSET_LIBRARIES, "ASSET_LIBRARIES", 0, "Asset Libraries", ""},
+ {0, NULL, 0, NULL, NULL},
+};
+
#ifdef RNA_RUNTIME
# include "DNA_anim_types.h"
@@ -549,7 +560,6 @@ static const EnumPropertyItem rna_enum_curve_display_handle_items[] = {
# include "BKE_layer.h"
# include "BKE_nla.h"
# include "BKE_paint.h"
-# include "BKE_preferences.h"
# include "BKE_scene.h"
# include "BKE_screen.h"
# include "BKE_workspace.h"
@@ -609,6 +619,8 @@ static StructRNA *rna_Space_refine(struct PointerRNA *ptr)
return &RNA_SpaceConsole;
case SPACE_USERPREF:
return &RNA_SpacePreferences;
+ case SPACE_PROJECT_SETTINGS:
+ return &RNA_SpaceProjectSettings;
case SPACE_CLIP:
return &RNA_SpaceClipEditor;
case SPACE_SPREADSHEET:
@@ -7159,6 +7171,20 @@ static void rna_def_space_userpref(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Filter", "Search term for filtering in the UI");
}
+static void rna_def_space_project_settings(BlenderRNA *brna)
+{
+ StructRNA *srna;
+
+ srna = RNA_def_struct(brna, "SpaceProjectSettings", "Space");
+ RNA_def_struct_ui_text(srna, "Space Project Settings", "Blender project space data");
+
+ PropertyRNA *prop;
+
+ prop = RNA_def_property(srna, "active_section", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_items(prop, rna_enum_project_settings_section_items);
+ RNA_def_property_ui_text(prop, "Active Section", "Choose the category of options to display");
+}
+
static void rna_def_node_tree_path(BlenderRNA *brna)
{
StructRNA *srna;
@@ -8078,6 +8104,7 @@ void RNA_def_space(BlenderRNA *brna)
rna_def_console_line(brna);
rna_def_space_info(brna);
rna_def_space_userpref(brna);
+ rna_def_space_project_settings(brna);
rna_def_node_tree_path(brna);
rna_def_space_node(brna);
rna_def_space_clip(brna);
diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c
index 58189ab2478..72722053a64 100644
--- a/source/blender/makesrna/intern/rna_userdef.c
+++ b/source/blender/makesrna/intern/rna_userdef.c
@@ -154,7 +154,6 @@ static const EnumPropertyItem rna_enum_userdef_viewport_aa_items[] = {
# include "BKE_object.h"
# include "BKE_paint.h"
# include "BKE_pbvh.h"
-# include "BKE_preferences.h"
# include "BKE_screen.h"
# include "DEG_depsgraph.h"
@@ -308,18 +307,6 @@ static void rna_userdef_language_update(Main *UNUSED(bmain),
USERDEF_TAG_DIRTY;
}
-static void rna_userdef_asset_library_name_set(PointerRNA *ptr, const char *value)
-{
- bUserAssetLibrary *library = (bUserAssetLibrary *)ptr->data;
- BKE_preferences_asset_library_name_set(&U, library, value);
-}
-
-static void rna_userdef_asset_library_path_set(PointerRNA *ptr, const char *value)
-{
- bUserAssetLibrary *library = (bUserAssetLibrary *)ptr->data;
- BKE_preferences_asset_library_path_set(library, value);
-}
-
static void rna_userdef_script_autoexec_update(Main *UNUSED(bmain),
Scene *UNUSED(scene),
PointerRNA *ptr)
@@ -2514,6 +2501,21 @@ static void rna_def_userdef_theme_space_userpref(BlenderRNA *brna)
rna_def_userdef_theme_spaces_main(srna);
}
+static void rna_def_userdef_theme_space_project_settings(BlenderRNA *brna)
+{
+ StructRNA *srna;
+
+ /* space_userpref */
+
+ srna = RNA_def_struct(brna, "ThemeProjectSettings", NULL);
+ RNA_def_struct_sdna(srna, "ThemeSpace");
+ RNA_def_struct_clear_flag(srna, STRUCT_UNDO);
+ RNA_def_struct_ui_text(
+ srna, "Theme Project Settings", "Theme settings for the Blender project settings editor");
+
+ rna_def_userdef_theme_spaces_main(srna);
+}
+
static void rna_def_userdef_theme_space_console(BlenderRNA *brna)
{
StructRNA *srna;
@@ -3854,6 +3856,8 @@ static void rna_def_userdef_themes(BlenderRNA *brna)
{11, "PROPERTIES", ICON_PROPERTIES, "Properties", ""},
{12, "OUTLINER", ICON_OUTLINER, "Outliner", ""},
{14, "PREFERENCES", ICON_PREFERENCES, "Preferences", ""},
+ /* TODO icon */
+ {24, "PROJECT_SETTINGS", ICON_PREFERENCES, "Project Settings", ""},
{15, "INFO", ICON_INFO, "Info", ""},
{16, "FILE_BROWSER", ICON_FILEBROWSER, "File Browser", ""},
{17, "CONSOLE", ICON_CONSOLE, "Python Console", ""},
@@ -3966,6 +3970,12 @@ static void rna_def_userdef_themes(BlenderRNA *brna)
RNA_def_property_struct_type(prop, "ThemePreferences");
RNA_def_property_ui_text(prop, "Preferences", "");
+ prop = RNA_def_property(srna, "project_settings", PROP_POINTER, PROP_NONE);
+ RNA_def_property_flag(prop, PROP_NEVER_NULL);
+ RNA_def_property_pointer_sdna(prop, NULL, "space_project_settings");
+ RNA_def_property_struct_type(prop, "ThemeProjectSettings");
+ RNA_def_property_ui_text(prop, "Project Settings", "");
+
prop = RNA_def_property(srna, "console", PROP_POINTER, PROP_NONE);
RNA_def_property_flag(prop, PROP_NEVER_NULL);
RNA_def_property_pointer_sdna(prop, NULL, "space_console");
@@ -4252,6 +4262,7 @@ static void rna_def_userdef_dothemes(BlenderRNA *brna)
rna_def_userdef_theme_space_outliner(brna);
rna_def_userdef_theme_space_info(brna);
rna_def_userdef_theme_space_userpref(brna);
+ rna_def_userdef_theme_space_project_settings(brna);
rna_def_userdef_theme_space_console(brna);
rna_def_userdef_theme_space_clip(brna);
rna_def_userdef_theme_space_topbar(brna);
@@ -6051,31 +6062,6 @@ static void rna_def_userdef_keymap(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Key Config", "The name of the active key configuration");
}
-static void rna_def_userdef_filepaths_asset_library(BlenderRNA *brna)
-{
- StructRNA *srna;
- PropertyRNA *prop;
-
- srna = RNA_def_struct(brna, "UserAssetLibrary", NULL);
- RNA_def_struct_sdna(srna, "bUserAssetLibrary");
- RNA_def_struct_clear_flag(srna, STRUCT_UNDO);
- RNA_def_struct_ui_text(
- srna, "Asset Library", "Settings to define a reusable library for Asset Browsers to use");
-
- prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE);
- RNA_def_property_ui_text(
- prop, "Name", "Identifier (not necessarily unique) for the asset library");
- RNA_def_property_string_funcs(prop, NULL, NULL, "rna_userdef_asset_library_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_ui_text(
- prop, "Path", "Path to a directory with .blend files to use as an asset library");
- RNA_def_property_string_funcs(prop, NULL, NULL, "rna_userdef_asset_library_path_set");
- RNA_def_property_update(prop, 0, "rna_userdef_update");
-}
-
static void rna_def_userdef_filepaths(BlenderRNA *brna)
{
PropertyRNA *prop;
@@ -6256,10 +6242,8 @@ static void rna_def_userdef_filepaths(BlenderRNA *brna)
RNA_def_property_enum_items(prop, preview_type_items);
RNA_def_property_ui_text(prop, "File Preview Type", "What type of blend preview to create");
- rna_def_userdef_filepaths_asset_library(brna);
-
prop = RNA_def_property(srna, "asset_libraries", PROP_COLLECTION, PROP_NONE);
- RNA_def_property_struct_type(prop, "UserAssetLibrary");
+ RNA_def_property_struct_type(prop, "CustomAssetLibraryDefinition");
RNA_def_property_ui_text(prop, "Asset Libraries", "");
}
@@ -6385,6 +6369,12 @@ static void rna_def_userdef_experimental(BlenderRNA *brna)
RNA_def_property_ui_text(
prop, "Override Templates", "Enable library override template in the python API");
+ prop = RNA_def_property(srna, "use_blender_projects", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_ui_text(prop,
+ "Blender Projects",
+ "Enable support for Blender project directories, consisting out of "
+ "multiple .blend files and dedicated project settings");
+
prop = RNA_def_property(srna, "enable_eevee_next", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "enable_eevee_next", 1);
RNA_def_property_ui_text(prop, "EEVEE Next", "Enable the new EEVEE codebase, requires restart");
diff --git a/source/blender/python/intern/bpy_rna_callback.c b/source/blender/python/intern/bpy_rna_callback.c
index 4409e3ae828..f7f038d8efd 100644
--- a/source/blender/python/intern/bpy_rna_callback.c
+++ b/source/blender/python/intern/bpy_rna_callback.c
@@ -239,6 +239,9 @@ static eSpace_Type rna_Space_refine_reverse(StructRNA *srna)
if (srna == &RNA_SpacePreferences) {
return SPACE_USERPREF;
}
+ if (srna == &RNA_SpaceProjectSettings) {
+ return SPACE_PROJECT_SETTINGS;
+ }
if (srna == &RNA_SpaceClipEditor) {
return SPACE_CLIP;
}
diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h
index e4050397aaa..038438ff151 100644
--- a/source/blender/windowmanager/WM_types.h
+++ b/source/blender/windowmanager/WM_types.h
@@ -355,6 +355,8 @@ typedef struct wmNotifier {
#define NC_ASSET (27 << 24)
/* Changes to the active viewer path. */
#define NC_VIEWER_PATH (28 << 24)
+/* Changes related to the active project. */
+#define NC_PROJECT (29 << 24)
/* data type, 256 entries is enough, it can overlap */
#define NOTE_DATA 0x00FF0000
@@ -496,6 +498,9 @@ typedef struct wmNotifier {
* reloading of asset libraries & their catalogs should happen. That only happens on explicit user
* action. */
#define ND_ASSET_CATALOGS (4 << 16)
+/* Some settings of an asset library were changed, and UIs showing asset library information should
+ * redraw. */
+#define ND_ASSET_LIBRARY (5 << 16)
/* subtype, 256 entries too */
#define NOTE_SUBTYPE 0x0000FF00
diff --git a/source/blender/windowmanager/intern/wm_draw.c b/source/blender/windowmanager/intern/wm_draw.c
index 663a41212ba..8cd13b572d1 100644
--- a/source/blender/windowmanager/intern/wm_draw.c
+++ b/source/blender/windowmanager/intern/wm_draw.c
@@ -536,6 +536,7 @@ static const char *wm_area_name(ScrArea *area)
SPACE_NAME(SPACE_CLIP);
SPACE_NAME(SPACE_TOPBAR);
SPACE_NAME(SPACE_STATUSBAR);
+ SPACE_NAME(SPACE_PROJECT_SETTINGS);
default:
return "Unknown Space";
}
@@ -1344,6 +1345,9 @@ void wm_draw_update(bContext *C)
BKE_image_free_unused_gpu_textures();
+ /* Subscribe to messages when drawing, #ED_region_do_draw() does the same on region level. */
+ wm_messages_subscribe(wm);
+
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
#ifdef WIN32
GHOST_TWindowState state = GHOST_GetWindowState(win->ghostwin);
diff --git a/source/blender/windowmanager/intern/wm_files.c b/source/blender/windowmanager/intern/wm_files.c
index b009a67efba..1d55e4369f8 100644
--- a/source/blender/windowmanager/intern/wm_files.c
+++ b/source/blender/windowmanager/intern/wm_files.c
@@ -62,6 +62,7 @@
#include "BKE_asset_library.h"
#include "BKE_autoexec.h"
#include "BKE_blender.h"
+#include "BKE_blender_project.h"
#include "BKE_blendfile.h"
#include "BKE_callbacks.h"
#include "BKE_context.h"
@@ -159,7 +160,8 @@ void WM_file_tag_modified(void)
bool wm_file_or_session_data_has_unsaved_changes(const Main *bmain, const wmWindowManager *wm)
{
return !wm->file_saved || ED_image_should_save_modified(bmain) ||
- BKE_asset_library_has_any_unsaved_catalogs();
+ BKE_asset_library_has_any_unsaved_catalogs() ||
+ BKE_project_has_unsaved_changes(CTX_wm_project());
}
/** \} */
@@ -3150,6 +3152,16 @@ static int wm_save_as_mainfile_exec(bContext *C, wmOperator *op)
return OPERATOR_CANCELLED;
}
+ const BlenderProject *active_project = CTX_wm_project();
+ if (active_project && !BKE_project_contains_path(path)) {
+ BKE_reportf(
+ op->reports,
+ RPT_WARNING,
+ "File saved outside of the active project's path (\"%s\"). Active project changed.",
+ BKE_project_root_path_get(active_project));
+ /* Don't cancel. Otherwise there's no way to save files outside of the active project. */
+ }
+
const int fileflags_orig = G.fileflags;
int fileflags = G.fileflags;
@@ -3565,6 +3577,7 @@ void wm_test_autorun_warning(bContext *C)
* \{ */
static char save_images_when_file_is_closed = true;
+static char save_project_settings_when_file_is_closed = true;
static void wm_block_file_close_cancel(bContext *C, void *arg_block, void *UNUSED(arg_data))
{
@@ -3607,6 +3620,12 @@ static void wm_block_file_close_save(bContext *C, void *arg_block, void *arg_dat
}
}
+ BlenderProject *project = CTX_wm_project();
+ if (project && BKE_project_has_unsaved_changes(project) &&
+ save_project_settings_when_file_is_closed) {
+ BKE_project_settings_save(project);
+ }
+
bool file_has_been_saved_before = BKE_main_blendfile_path(bmain)[0] != '\0';
if (file_has_been_saved_before) {
@@ -3746,6 +3765,30 @@ static uiBlock *block_create__close_file_dialog(struct bContext *C,
has_extra_checkboxes = true;
}
+ if (BKE_project_has_unsaved_changes(CTX_wm_project())) {
+ /* Only the first checkbox should get extra separation. */
+ if (!has_extra_checkboxes) {
+ uiItemS(layout);
+ }
+
+ uiDefButBitC(block,
+ UI_BTYPE_CHECKBOX,
+ 1,
+ 0,
+ "Save modified project settings",
+ 0,
+ 0,
+ 0,
+ UI_UNIT_Y,
+ &save_project_settings_when_file_is_closed,
+ 0,
+ 0,
+ 0,
+ 0,
+ "");
+ has_extra_checkboxes = true;
+ }
+
if (BKE_asset_library_has_any_unsaved_catalogs()) {
static char save_catalogs_when_file_is_closed;
diff --git a/source/blender/windowmanager/intern/wm_window.c b/source/blender/windowmanager/intern/wm_window.c
index 265aa08a6b1..292d58b80d1 100644
--- a/source/blender/windowmanager/intern/wm_window.c
+++ b/source/blender/windowmanager/intern/wm_window.c
@@ -28,6 +28,7 @@
#include "BLT_translation.h"
+#include "BKE_blender_project.h"
#include "BKE_context.h"
#include "BKE_global.h"
#include "BKE_icons.h"
@@ -42,6 +43,7 @@
#include "RNA_enum_types.h"
#include "WM_api.h"
+#include "WM_message.h"
#include "WM_types.h"
#include "wm.h"
#include "wm_draw.h"
@@ -450,6 +452,8 @@ void wm_window_close(bContext *C, wmWindowManager *wm, wmWindow *win)
void wm_window_title(wmWindowManager *wm, wmWindow *win)
{
+#define MAX_PROJECT_NAME_HINT (MAX_NAME + 4)
+
if (WM_window_is_temp_screen(win)) {
/* Nothing to do for 'temp' windows,
* because #WM_window_open always sets window title. */
@@ -458,11 +462,23 @@ void wm_window_title(wmWindowManager *wm, wmWindow *win)
/* this is set to 1 if you don't have startup.blend open */
const char *blendfile_path = BKE_main_blendfile_path_from_global();
if (blendfile_path[0] != '\0') {
- char str[sizeof(((Main *)NULL)->filepath) + 24];
+ char project_name_hint[MAX_PROJECT_NAME_HINT] = "";
+ char str[sizeof(((Main *)NULL)->filepath) + sizeof(project_name_hint) + 24];
+
+ struct BlenderProject *project = CTX_wm_project();
+ if (project) {
+ const char *name = BKE_project_name_get(project);
+ BLI_snprintf(project_name_hint,
+ sizeof(project_name_hint),
+ "%s - ",
+ (name && name[0]) ? name : IFACE_("Unnamed project"));
+ }
+
BLI_snprintf(str,
sizeof(str),
- "Blender%s [%s%s]",
+ "Blender%s [%s%s%s]",
wm->file_saved ? "" : "*",
+ project_name_hint,
blendfile_path,
G_MAIN->recovered ? " (Recovered)" : "");
GHOST_SetTitle(win->ghostwin, str);
@@ -476,6 +492,8 @@ void wm_window_title(wmWindowManager *wm, wmWindow *win)
* terminate request (e.g. OS Shortcut Alt+F4, Command+Q, (...), or session end). */
GHOST_SetWindowModifiedState(win->ghostwin, (bool)!wm->file_saved);
}
+
+#undef MAX_PROJECT_NAME_HINT
}
void WM_window_set_dpi(const wmWindow *win)
@@ -1531,6 +1549,35 @@ void wm_window_process_events(const bContext *C)
}
/* -------------------------------------------------------------------- */
+/** \name WM level message bus subscribers
+ * \{ */
+
+static void wm_msg_windows_title_update_fn(bContext *C,
+ wmMsgSubscribeKey *UNUSED(msg_key),
+ wmMsgSubscribeValue *UNUSED(msg_val))
+{
+ wmWindowManager *wm = CTX_wm_manager(C);
+ LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
+ wm_window_title(wm, win);
+ }
+}
+
+void wm_messages_subscribe(wmWindowManager *wm)
+{
+ WM_msgbus_clear_by_owner(wm->message_bus, wm);
+
+ wmMsgSubscribeValue msg_sub_value_update_win_titles = {0};
+ msg_sub_value_update_win_titles.owner = wm;
+ msg_sub_value_update_win_titles.notify = wm_msg_windows_title_update_fn;
+
+ /* Update window titles on project name change. */
+ WM_msg_subscribe_rna_anon_prop(
+ wm->message_bus, BlenderProject, name, &msg_sub_value_update_win_titles);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
/** \name Ghost Init/Exit
* \{ */
diff --git a/source/blender/windowmanager/wm_window.h b/source/blender/windowmanager/wm_window.h
index 036a34a5140..81bb1b37384 100644
--- a/source/blender/windowmanager/wm_window.h
+++ b/source/blender/windowmanager/wm_window.h
@@ -23,6 +23,8 @@ void wm_ghost_init(bContext *C);
void wm_ghost_init_background(void);
void wm_ghost_exit(void);
+void wm_messages_subscribe(wmWindowManager *wm);
+
/**
* This one should correctly check for apple top header...
* done for Cocoa: returns window contents (and not frame) max size.