Add UI for setting up project asset libraries

Looks just like the UI for setting up custom asset libraries in the
Preferences. However project asset libraries use paths relative to the
project root directory.

Had to do some changes to project data storage to avoid memory issues.
This commit is contained in:
Julian Eisel 2022-10-13 16:42:58 +02:00
parent f22cc99944
commit 1f819df7df
14 changed files with 351 additions and 19 deletions

View File

@ -152,6 +152,48 @@ class PROJECTSETTINGS_PT_setup(CenterAlignMixIn, Panel):
layout.prop(project, "root_path", text="Location")
class PROJECTSETTINGS_PT_asset_libraries(Panel):
bl_space_type = 'PROJECT_SETTINGS'
bl_region_type = 'WINDOW'
bl_context = "asset_libraries"
bl_label = "Asset Libraries"
def draw(self, context):
layout = self.layout
layout.use_property_split = False
layout.use_property_decorate = False
project = context.project
box = layout.box()
split = box.split(factor=0.35)
name_col = split.column()
path_col = split.column()
row = name_col.row(align=True) # Padding
row.separator()
row.label(text="Name")
row = path_col.row(align=True) # Padding
row.separator()
row.label(text="Path")
for i, library in enumerate(project.asset_libraries):
row = name_col.row()
row.alert = not library.name
row.prop(library, "name", text="")
row = path_col.row()
subrow = row.row()
subrow.alert = not library.path
subrow.prop(library, "path", text="")
row.operator("project.custom_asset_library_remove", text="", icon='X', emboss=False).index = i
row = box.row()
row.alignment = 'RIGHT'
row.operator("project.custom_asset_library_add", text="", icon='ADD', emboss=False)
classes = (
PROJECTSETTINGS_HT_header,
PROJECTSETTINGS_MT_editor_menus,
@ -161,6 +203,7 @@ classes = (
PROJECTSETTINGS_PT_save_project_settings,
PROJECTSETTINGS_PT_no_project,
PROJECTSETTINGS_PT_setup,
PROJECTSETTINGS_PT_asset_libraries,
)
if __name__ == "__main__": # only for live edit.

View File

@ -58,6 +58,9 @@ const char *BKE_project_root_path_get(const BlenderProject *project) ATTR_WARN_U
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();
bool BKE_project_has_unsaved_changes(const BlenderProject *project) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();

View File

@ -10,6 +10,7 @@
#include "BLI_listbase.h"
#include "BLI_string_ref.hh"
#include "BLI_utility_mixins.hh"
namespace blender::io::serialize {
class DictionaryValue;
@ -21,8 +22,6 @@ class ProjectSettings;
struct CustomAssetLibraries;
class BlenderProject {
inline static std::unique_ptr<BlenderProject> active_;
std::unique_ptr<ProjectSettings> settings_;
public:
@ -44,13 +43,24 @@ class BlenderProject {
private:
explicit BlenderProject(std::unique_ptr<ProjectSettings> settings);
static std::unique_ptr<BlenderProject> &active_project_ptr();
};
struct CustomAssetLibraries : NonCopyable {
ListBase asset_libraries = {nullptr, nullptr}; /* CustomAssetLibraryDefinition */
CustomAssetLibraries() = default;
CustomAssetLibraries(ListBase asset_libraries);
CustomAssetLibraries(CustomAssetLibraries &&);
~CustomAssetLibraries();
auto operator=(CustomAssetLibraries &&) -> CustomAssetLibraries &;
};
class ProjectSettings {
/* Path to the project root using slashes in the OS native format. */
std::string project_root_path_;
std::string project_name_;
std::unique_ptr<CustomAssetLibraries> asset_libraries_;
CustomAssetLibraries asset_libraries_ = {};
bool has_unsaved_changes_ = false;
public:
@ -94,17 +104,23 @@ class ProjectSettings {
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>;
};
struct CustomAssetLibraries {
ListBase asset_libraries = {nullptr, nullptr}; /* CustomAssetLibraryDefinition */
CustomAssetLibraries(ListBase asset_libraries);
~CustomAssetLibraries();
};
} // namespace blender::bke

View File

@ -31,21 +31,30 @@ BlenderProject::BlenderProject(std::unique_ptr<ProjectSettings> settings)
{
}
/* 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_from_settings(std::unique_ptr<ProjectSettings> settings)
{
std::unique_ptr<BlenderProject> &active = active_project_ptr();
if (settings) {
active_ = std::make_unique<BlenderProject>(BlenderProject(std::move(settings)));
active = std::make_unique<BlenderProject>(BlenderProject(std::move(settings)));
}
else {
active_ = nullptr;
active = nullptr;
}
return active_.get();
return active.get();
}
BlenderProject *BlenderProject::get_active()
{
return active_.get();
std::unique_ptr<BlenderProject> &active = active_project_ptr();
return active.get();
}
StringRef BlenderProject::project_root_path_find_from_path(StringRef path)
@ -252,8 +261,7 @@ std::unique_ptr<ProjectSettings> ProjectSettings::load_from_disk(StringRef proje
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);
loaded_settings->asset_libraries_ = CustomAssetLibraries(extracted_settings->asset_libraries);
}
return loaded_settings;
@ -273,11 +281,11 @@ std::unique_ptr<serialize::DictionaryValue> ProjectSettings::to_dictionary() con
root_attributes.append_as("project", std::move(project_dict));
}
/* "asset_libraries": */ {
if (asset_libraries_ && !BLI_listbase_is_empty(&asset_libraries_->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) {
const CustomAssetLibraryDefinition *, library, &asset_libraries_.asset_libraries) {
std::unique_ptr<DictionaryValue> library_dict = std::make_unique<DictionaryValue>();
DictionaryValue::Items &library_attributes = library_dict->elements();
@ -356,7 +364,17 @@ StringRefNull ProjectSettings::project_name() const
const ListBase &ProjectSettings::asset_library_definitions() const
{
return asset_libraries_->asset_libraries;
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
@ -371,6 +389,18 @@ CustomAssetLibraries::CustomAssetLibraries(ListBase 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) {
@ -468,6 +498,22 @@ const char *BKE_project_name_get(const 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)
{
const bke::BlenderProject *project = reinterpret_cast<const bke::BlenderProject *>(

View File

@ -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)

View File

@ -6,6 +6,7 @@
#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"

View File

@ -0,0 +1,19 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup editors
*
* Editor level functions for Blender projects.
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
void ED_operatortypes_project(void);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,26 @@
# SPDX-License-Identifier: GPL-2.0-or-later
set(INC
.
../include
../../blenkernel
../../blenlib
../../makesdna
../../makesrna
../../windowmanager
../../../../intern/guardedalloc
)
set(INC_SYS
)
set(SRC
project_ops.cc
)
set(LIB
bf_blenkernel
)
blender_add_lib(bf_editor_project "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")

View File

@ -0,0 +1,149 @@
/* 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_report.h"
#include "BLI_path_util.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"
using namespace blender;
/* -------------------------------------------------------------------- */
/** \name Add Custom Asset Library
* \{ */
static int custom_asset_library_add_exec(bContext *UNUSED(C), wmOperator *op)
{
BlenderProject *project = CTX_wm_project();
if (!project) {
BKE_report(op->reports,
RPT_ERROR,
"Couldn't create project asset library, there is no active project");
return OPERATOR_CANCELLED;
}
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->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();
if (!project) {
BKE_report(op->reports,
RPT_ERROR,
"Couldn't remove project asset library, there is no active project");
return OPERATOR_CANCELLED;
}
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->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_custom_asset_library_add);
WM_operatortype_append(PROJECT_OT_custom_asset_library_remove);
}

View File

@ -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

View File

@ -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"
@ -86,6 +87,7 @@ void ED_spacetypes_init(void)
/* Register operator types for screen and all spaces. */
ED_operatortypes_userpref();
ED_operatortypes_project();
ED_operatortypes_workspace();
ED_operatortypes_scene();
ED_operatortypes_screen();

View File

@ -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";
@ -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";

View File

@ -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

View File

@ -10,6 +10,7 @@
#ifdef RNA_RUNTIME
# include "BKE_asset_library_custom.h"
# include "BKE_blender_project.h"
# include "BLT_translation.h"
@ -92,6 +93,14 @@ static int rna_BlenderProject_root_path_editable(PointerRNA *UNUSED(ptr), const
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;
@ -128,6 +137,19 @@ void RNA_def_blender_project(BlenderRNA *brna)
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);