WIP: Basic Blender Project Support (experimental feature) #107655

Draft
Julian Eisel wants to merge 94 commits from blender-projects-basics into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
5 changed files with 102 additions and 3 deletions
Showing only changes of commit d5ae369efa - Show all commits

View File

@ -38,6 +38,8 @@ BlenderProject *BKE_project_active_load_from_path(const char *path) ATTR_NONNULL
const char *BKE_project_root_path_get(const BlenderProject *project) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
const char *BKE_project_name_get(const BlenderProject *project) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
#ifdef __cplusplus
}

View File

@ -43,9 +43,11 @@ class BlenderProject {
class ProjectSettings {
/* Path to the project root using slashes in the OS native format. */
std::string project_root_path_;
std::string project_name_;
public:
inline static const StringRefNull SETTINGS_DIRNAME = ".blender_project";
inline static const StringRefNull SETTINGS_FILENAME = "settings.json";
/**
* Initializes a blender project by creating a .blender_project directory at the given \a
@ -67,6 +69,7 @@ class ProjectSettings {
explicit ProjectSettings(StringRef project_root_path);
auto project_root_path [[nodiscard]] () const -> StringRefNull;
auto project_name [[nodiscard]] () const -> StringRefNull;
};
} // namespace blender::bke

View File

@ -4,10 +4,13 @@
* \ingroup bke
*/
#include <fstream>
#include "BKE_blender_project.h"
#include "BKE_blender_project.hh"
#include "BLI_path_util.h"
#include "BLI_serialize.hh"
#include "BLI_string.h"
#include "DNA_space_types.h"
@ -15,6 +18,8 @@
#include "BLI_fileops.h"
#include "BLI_path_util.h"
namespace serialize = blender::io::serialize;
namespace blender::bke {
static StringRef path_strip_trailing_native_slash(StringRef path);
@ -102,6 +107,51 @@ static bool path_contains_project_settings(StringRef path)
return BLI_exists(std::string(path + SEP_STR + ProjectSettings::SETTINGS_DIRNAME).c_str());
}
struct ExtractedSettings {
std::string project_name;
};
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();
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();
}
return extracted_settings;
}
std::unique_ptr<ProjectSettings> ProjectSettings::load_from_disk(StringRef project_path)
{
std::string project_path_native = project_path;
@ -122,7 +172,21 @@ std::unique_ptr<ProjectSettings> ProjectSettings::load_from_disk(StringRef proje
return nullptr;
}
return std::make_unique<ProjectSettings>(project_root_path);
std::string settings_filepath = project_path_native + SEP + SETTINGS_DIRNAME + SEP +
SETTINGS_FILENAME;
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>(project_root_path);
if (extracted_settings) {
loaded_settings->project_name_ = extracted_settings->project_name;
}
return loaded_settings;
}
StringRefNull ProjectSettings::project_root_path() const
@ -130,6 +194,11 @@ StringRefNull ProjectSettings::project_root_path() const
return project_root_path_;
}
StringRefNull ProjectSettings::project_name() const
{
return project_name_;
}
} // namespace blender::bke
/* ---------------------------------------------------------------------- */
@ -178,3 +247,10 @@ const char *BKE_project_root_path_get(const BlenderProject *project_handle)
project_handle);
return project->get_settings().project_root_path().c_str();
}
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();
}

View File

@ -127,6 +127,7 @@ TEST_F(ProjectTest, settings_load_from_project_root_path)
std::unique_ptr project_settings = ProjectSettings::load_from_disk(project_path);
EXPECT_NE(project_settings, nullptr);
EXPECT_EQ(project_settings->project_root_path(), project_path_native);
EXPECT_EQ(project_settings->project_name(), "");
});
}
@ -141,6 +142,7 @@ TEST_F(ProjectTest, settings_load_from_project_settings_path)
project_path + SEP_STR + ProjectSettings::SETTINGS_DIRNAME);
EXPECT_NE(project_settings, nullptr);
EXPECT_EQ(project_settings->project_root_path(), project_path_native);
EXPECT_EQ(project_settings->project_name(), "");
});
}
@ -188,6 +190,7 @@ TEST_F(BlendfileProjectLoadingTest, load_blend_file)
::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);
@ -204,6 +207,7 @@ TEST_F(BlendfileProjectLoadingTest, load_blend_file)
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. */

View File

@ -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"
@ -447,6 +448,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. */
@ -455,14 +458,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%s]",
wm->file_saved ? "" : "*",
project ? IFACE_("Has Project - ") : "",
project_name_hint,
blendfile_path,
G_MAIN->recovered ? " (Recovered)" : "");
GHOST_SetTitle(win->ghostwin, str);
@ -476,6 +488,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)