Support saving project settings to .blender_project/settings.json
This commit is contained in:
parent
3ab935c8a9
commit
cd15fbbed6
|
@ -19,8 +19,8 @@ class PROJECTSETTINGS_HT_header(Header):
|
|||
# Show '*' to let users know the settings have been modified.
|
||||
# TODO, wrong operator
|
||||
layout.operator(
|
||||
"wm.save_userpref",
|
||||
text=iface_("Save Project Settings") + (" *" if is_dirty else ""),
|
||||
"wm.save_project_settings",
|
||||
text=iface_("Save Settings") + (" *" if is_dirty else ""),
|
||||
translate=False,
|
||||
)
|
||||
|
||||
|
|
|
@ -36,6 +36,8 @@ void BKE_project_active_unset(void);
|
|||
*/
|
||||
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();
|
||||
const char *BKE_project_name_get(const BlenderProject *project) ATTR_WARN_UNUSED_RESULT
|
||||
|
|
|
@ -10,6 +10,10 @@
|
|||
|
||||
#include "BLI_string_ref.hh"
|
||||
|
||||
namespace blender::io::serialize {
|
||||
class DictionaryValue;
|
||||
}
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
class ProjectSettings;
|
||||
|
@ -60,16 +64,27 @@ class ProjectSettings {
|
|||
/**
|
||||
* 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.
|
||||
* 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>;
|
||||
/**
|
||||
* 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) const -> bool;
|
||||
|
||||
explicit ProjectSettings(StringRef project_root_path);
|
||||
|
||||
auto project_root_path [[nodiscard]] () const -> StringRefNull;
|
||||
auto project_name [[nodiscard]] () const -> StringRefNull;
|
||||
|
||||
private:
|
||||
auto to_dictionary() const -> std::unique_ptr<io::serialize::DictionaryValue>;
|
||||
};
|
||||
|
||||
} // namespace blender::bke
|
||||
|
|
|
@ -152,36 +152,55 @@ static std::unique_ptr<ExtractedSettings> extract_settings(
|
|||
return extracted_settings;
|
||||
}
|
||||
|
||||
std::unique_ptr<ProjectSettings> ProjectSettings::load_from_disk(StringRef project_path)
|
||||
struct ResolvedPaths {
|
||||
std::string settings_filepath;
|
||||
std::string project_root_path;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returned paths can be assumed to use native slashes.
|
||||
*/
|
||||
static ResolvedPaths resolve_paths_from_project_path(StringRef project_path)
|
||||
{
|
||||
std::string project_path_native = project_path;
|
||||
BLI_path_slash_native(project_path_native.data());
|
||||
|
||||
if (!BLI_exists(project_path_native.c_str())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
StringRef project_root_path = project_path_native;
|
||||
ResolvedPaths resolved_paths{};
|
||||
|
||||
const StringRef path_no_trailing_slashes = path_strip_trailing_native_slash(project_path_native);
|
||||
if (path_no_trailing_slashes.endswith(SETTINGS_DIRNAME)) {
|
||||
project_root_path = StringRef(project_path_native).drop_suffix(SETTINGS_DIRNAME.size() + 1);
|
||||
if (path_no_trailing_slashes.endswith(ProjectSettings::SETTINGS_DIRNAME)) {
|
||||
resolved_paths.project_root_path =
|
||||
StringRef(path_no_trailing_slashes).drop_suffix(ProjectSettings::SETTINGS_DIRNAME.size());
|
||||
}
|
||||
else {
|
||||
resolved_paths.project_root_path = std::string(path_no_trailing_slashes) + SEP;
|
||||
}
|
||||
resolved_paths.settings_filepath = resolved_paths.project_root_path +
|
||||
ProjectSettings::SETTINGS_DIRNAME + SEP +
|
||||
ProjectSettings::SETTINGS_FILENAME;
|
||||
|
||||
if (!path_contains_project_settings(project_root_path)) {
|
||||
return resolved_paths;
|
||||
}
|
||||
|
||||
std::unique_ptr<ProjectSettings> ProjectSettings::load_from_disk(StringRef project_path)
|
||||
{
|
||||
ResolvedPaths paths = resolve_paths_from_project_path(project_path);
|
||||
|
||||
if (!BLI_exists(paths.project_root_path.c_str())) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!path_contains_project_settings(paths.project_root_path.c_str())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
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<serialize::Value> values = read_settings_file(paths.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);
|
||||
std::unique_ptr loaded_settings = std::make_unique<ProjectSettings>(paths.project_root_path);
|
||||
if (extracted_settings) {
|
||||
loaded_settings->project_name_ = extracted_settings->project_name;
|
||||
}
|
||||
|
@ -189,6 +208,50 @@ std::unique_ptr<ProjectSettings> ProjectSettings::load_from_disk(StringRef proje
|
|||
return loaded_settings;
|
||||
}
|
||||
|
||||
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();
|
||||
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));
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
bool ProjectSettings::save_to_disk(StringRef project_path) const
|
||||
{
|
||||
ResolvedPaths paths = resolve_paths_from_project_path(project_path);
|
||||
|
||||
if (!BLI_exists(paths.project_root_path.c_str())) {
|
||||
return false;
|
||||
}
|
||||
if (!path_contains_project_settings(paths.project_root_path.c_str())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr settings_as_dict = to_dictionary();
|
||||
write_settings_file(paths.settings_filepath, std::move(settings_as_dict));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
StringRefNull ProjectSettings::project_root_path() const
|
||||
{
|
||||
return project_root_path_;
|
||||
|
@ -241,6 +304,14 @@ BlenderProject *BKE_project_active_load_from_path(const char *path)
|
|||
return BKE_project_active_get();
|
||||
}
|
||||
|
||||
bool BKE_project_settings_save(const BlenderProject *project_handle)
|
||||
{
|
||||
const bke::BlenderProject *project = reinterpret_cast<const bke::BlenderProject *>(
|
||||
project_handle);
|
||||
const bke::ProjectSettings &settings = project->get_settings();
|
||||
return settings.save_to_disk(settings.project_root_path());
|
||||
}
|
||||
|
||||
const char *BKE_project_root_path_get(const BlenderProject *project_handle)
|
||||
{
|
||||
const bke::BlenderProject *project = reinterpret_cast<const bke::BlenderProject *>(
|
||||
|
|
|
@ -19,6 +19,12 @@
|
|||
|
||||
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 {
|
||||
|
@ -36,6 +42,9 @@ class ProjectTest : public testing::Test {
|
|||
|
||||
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. */
|
||||
|
@ -139,18 +148,42 @@ TEST_F(ProjectTest, settings_load_from_project_settings_path)
|
|||
ProjectSettings::create_settings_directory(project_path);
|
||||
|
||||
std::unique_ptr project_settings = ProjectSettings::load_from_disk(
|
||||
project_path + SEP_STR + ProjectSettings::SETTINGS_DIRNAME);
|
||||
project_path + (ELEM(project_path.back(), SEP, ALTSEP) ? "" : 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(), "");
|
||||
});
|
||||
}
|
||||
|
||||
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";
|
||||
};
|
||||
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);
|
||||
EXPECT_EQ(from_project_settings->project_name(), "Ružena");
|
||||
}
|
||||
|
||||
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) {
|
||||
ProjectSettings::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);
|
||||
EXPECT_EQ(written_settings->project_name(), "Ružena");
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ProjectTest, project_root_path_find_from_path)
|
||||
{
|
||||
|
|
|
@ -2312,6 +2312,37 @@ void WM_OT_read_factory_userpref(wmOperatorType *ot)
|
|||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Write Project Settings Operator
|
||||
* \{ */
|
||||
|
||||
/* Only save the prefs block. operator entry */
|
||||
static int wm_save_project_settings_exec(bContext *UNUSED(C), wmOperator *UNUSED(op))
|
||||
{
|
||||
BlenderProject *active_project = CTX_wm_project();
|
||||
if (!active_project) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
if (!BKE_project_settings_save(active_project)) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
void WM_OT_save_project_settings(wmOperatorType *ot)
|
||||
{
|
||||
ot->name = "Save Project Settings";
|
||||
ot->idname = "WM_OT_save_project_settings";
|
||||
ot->description = "Make the current changes to the project settings permanent";
|
||||
|
||||
ot->invoke = WM_operator_confirm;
|
||||
ot->exec = wm_save_project_settings_exec;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Read File History Operator
|
||||
* \{ */
|
||||
|
|
|
@ -3742,6 +3742,7 @@ void wm_operatortypes_register(void)
|
|||
WM_operatortype_append(WM_OT_read_factory_settings);
|
||||
WM_operatortype_append(WM_OT_save_homefile);
|
||||
WM_operatortype_append(WM_OT_save_userpref);
|
||||
WM_operatortype_append(WM_OT_save_project_settings);
|
||||
WM_operatortype_append(WM_OT_read_userpref);
|
||||
WM_operatortype_append(WM_OT_read_factory_userpref);
|
||||
WM_operatortype_append(WM_OT_window_fullscreen_toggle);
|
||||
|
|
|
@ -90,6 +90,7 @@ bool wm_file_or_session_data_has_unsaved_changes(const Main *bmain, const wmWind
|
|||
void WM_OT_save_homefile(struct wmOperatorType *ot);
|
||||
void WM_OT_save_userpref(struct wmOperatorType *ot);
|
||||
void WM_OT_read_userpref(struct wmOperatorType *ot);
|
||||
void WM_OT_save_project_settings(struct wmOperatorType *ot);
|
||||
void WM_OT_read_factory_userpref(struct wmOperatorType *ot);
|
||||
void WM_OT_read_history(struct wmOperatorType *ot);
|
||||
void WM_OT_read_homefile(struct wmOperatorType *ot);
|
||||
|
|
Loading…
Reference in New Issue