Files
test2/source/blender/blenlib/intern/serialize.cc
Lukas Tönne 92cf9dd2f2 ID properties: Support enum values with items
Add support for enum values in ID properties.

This is needed for the "Menu Switch" node implementation (#113445) which
relies on ID properties for the top-level modifier UI.

Enums items can optionally be added to the UI data of integer
properties. Each property stores a full set of the enum items to keep
things simple.

Enum items can be added to properties using the `id_properties_ui`
function in the python API. A detailed example can be found in the
`bl_pyapi_idprop.py` test.

There is currently no support yet for editing enum items through the UI.
This is because the "Edit Property" feature is implemented entirely
through a single operator (`WM_OT_properties_edit`) and its properties.
Buttons to add/remove/move items would be operators changing another
operator's properties. A refactor of the custom properties UI is likely
required to make this work.

Pull Request: https://projects.blender.org/blender/blender/pulls/114362
2023-12-15 10:20:44 +01:00

394 lines
9.8 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_fileops.hh"
#include "BLI_serialize.hh"
#include "json.hpp"
namespace blender::io::serialize {
const StringValue *Value::as_string_value() const
{
if (type_ != eValueType::String) {
return nullptr;
}
return static_cast<const StringValue *>(this);
}
const IntValue *Value::as_int_value() const
{
if (type_ != eValueType::Int) {
return nullptr;
}
return static_cast<const IntValue *>(this);
}
const DoubleValue *Value::as_double_value() const
{
if (type_ != eValueType::Double) {
return nullptr;
}
return static_cast<const DoubleValue *>(this);
}
const BooleanValue *Value::as_boolean_value() const
{
if (type_ != eValueType::Boolean) {
return nullptr;
}
return static_cast<const BooleanValue *>(this);
}
const EnumValue *Value::as_enum_value() const
{
if (type_ != eValueType::Enum) {
return nullptr;
}
return static_cast<const EnumValue *>(this);
}
const ArrayValue *Value::as_array_value() const
{
if (type_ != eValueType::Array) {
return nullptr;
}
return static_cast<const ArrayValue *>(this);
}
const DictionaryValue *Value::as_dictionary_value() const
{
if (type_ != eValueType::Dictionary) {
return nullptr;
}
return static_cast<const DictionaryValue *>(this);
}
static void convert_to_json(nlohmann::ordered_json &j, const Value &value);
static void convert_to_json(nlohmann::ordered_json &j, const ArrayValue &value)
{
const ArrayValue::Items &items = value.elements();
/* Create a json array to store the elements. If this isn't done and items is empty it would
* return use a null value, in stead of an empty array. */
j = "[]"_json;
for (const ArrayValue::Item &item_value : items) {
nlohmann::ordered_json json_item;
convert_to_json(json_item, *item_value);
j.push_back(json_item);
}
}
static void convert_to_json(nlohmann::ordered_json &j, const DictionaryValue &value)
{
const DictionaryValue::Items &attributes = value.elements();
/* Create a json object to store the attributes. If this isn't done and attributes is empty it
* would return use a null value, in stead of an empty object. */
j = "{}"_json;
for (const DictionaryValue::Item &attribute : attributes) {
nlohmann::ordered_json json_item;
convert_to_json(json_item, *attribute.second);
j[attribute.first] = json_item;
}
}
static void convert_to_json(nlohmann::ordered_json &j, const Value &value)
{
switch (value.type()) {
case eValueType::String: {
j = value.as_string_value()->value();
break;
}
case eValueType::Int: {
j = value.as_int_value()->value();
break;
}
case eValueType::Array: {
const ArrayValue &array = *value.as_array_value();
convert_to_json(j, array);
break;
}
case eValueType::Dictionary: {
const DictionaryValue &object = *value.as_dictionary_value();
convert_to_json(j, object);
break;
}
case eValueType::Null: {
j = nullptr;
break;
}
case eValueType::Boolean: {
j = value.as_boolean_value()->value();
break;
}
case eValueType::Double: {
j = value.as_double_value()->value();
break;
}
case eValueType::Enum: {
j = value.as_enum_value()->value();
break;
}
}
}
static std::unique_ptr<Value> convert_from_json(const nlohmann::ordered_json &j);
static std::unique_ptr<ArrayValue> convert_from_json_to_array(const nlohmann::ordered_json &j)
{
std::unique_ptr<ArrayValue> array = std::make_unique<ArrayValue>();
ArrayValue::Items &elements = array->elements();
for (auto element : j.items()) {
nlohmann::ordered_json element_json = element.value();
std::unique_ptr<Value> value = convert_from_json(element_json);
elements.append_as(value.release());
}
return array;
}
static std::unique_ptr<DictionaryValue> convert_from_json_to_object(
const nlohmann::ordered_json &j)
{
std::unique_ptr<DictionaryValue> object = std::make_unique<DictionaryValue>();
DictionaryValue::Items &elements = object->elements();
for (auto element : j.items()) {
std::string key = element.key();
nlohmann::ordered_json element_json = element.value();
std::unique_ptr<Value> value = convert_from_json(element_json);
elements.append_as(std::pair(key, value.release()));
}
return object;
}
static std::unique_ptr<Value> convert_from_json(const nlohmann::ordered_json &j)
{
switch (j.type()) {
case nlohmann::json::value_t::array: {
return convert_from_json_to_array(j);
}
case nlohmann::json::value_t::object: {
return convert_from_json_to_object(j);
}
case nlohmann::json::value_t::string: {
std::string value = j;
return std::make_unique<StringValue>(value);
}
case nlohmann::json::value_t::null: {
return std::make_unique<NullValue>();
}
case nlohmann::json::value_t::boolean: {
return std::make_unique<BooleanValue>(j);
}
case nlohmann::json::value_t::number_integer:
case nlohmann::json::value_t::number_unsigned: {
return std::make_unique<IntValue>(j);
}
case nlohmann::json::value_t::number_float: {
return std::make_unique<DoubleValue>(j);
}
case nlohmann::json::value_t::binary:
case nlohmann::json::value_t::discarded:
/*
* Binary data isn't supported.
* Discarded is an internal type of nlohmann.
*
* Assert in case we need to parse them.
*/
BLI_assert_unreachable();
return std::make_unique<NullValue>();
}
BLI_assert_unreachable();
return std::make_unique<NullValue>();
}
void ArrayValue::append(std::shared_ptr<Value> value)
{
this->elements().append(std::move(value));
}
void ArrayValue::append_bool(const bool value)
{
this->append(std::make_shared<BooleanValue>(value));
}
void ArrayValue::append_int(const int value)
{
this->append(std::make_shared<IntValue>(value));
}
void ArrayValue::append_double(const double value)
{
this->append(std::make_shared<DoubleValue>(value));
}
void ArrayValue::append_str(std::string value)
{
this->append(std::make_shared<StringValue>(std::move(value)));
}
void ArrayValue::append_null()
{
this->append(std::make_shared<NullValue>());
}
std::shared_ptr<DictionaryValue> ArrayValue::append_dict()
{
auto value = std::make_shared<DictionaryValue>();
this->append(value);
return value;
}
std::shared_ptr<ArrayValue> ArrayValue::append_array()
{
auto value = std::make_shared<ArrayValue>();
this->append(value);
return value;
}
const DictionaryValue::Lookup DictionaryValue::create_lookup() const
{
Lookup result;
for (const Item &item : elements()) {
result.add_as(item.first, item.second);
}
return result;
}
const std::shared_ptr<Value> *DictionaryValue::lookup(const StringRef key) const
{
for (const auto &item : this->elements()) {
if (item.first == key) {
return &item.second;
}
}
return nullptr;
}
std::optional<StringRefNull> DictionaryValue::lookup_str(const StringRef key) const
{
if (const std::shared_ptr<Value> *value = this->lookup(key)) {
if (const StringValue *str_value = (*value)->as_string_value()) {
return StringRefNull(str_value->value());
}
}
return std::nullopt;
}
std::optional<int64_t> DictionaryValue::lookup_int(const StringRef key) const
{
if (const std::shared_ptr<Value> *value = this->lookup(key)) {
if (const IntValue *int_value = (*value)->as_int_value()) {
return int_value->value();
}
}
return std::nullopt;
}
std::optional<double> DictionaryValue::lookup_double(const StringRef key) const
{
if (const std::shared_ptr<Value> *value = this->lookup(key)) {
if (const DoubleValue *double_value = (*value)->as_double_value()) {
return double_value->value();
}
}
return std::nullopt;
}
const DictionaryValue *DictionaryValue::lookup_dict(const StringRef key) const
{
if (const std::shared_ptr<Value> *value = this->lookup(key)) {
return (*value)->as_dictionary_value();
}
return nullptr;
}
const ArrayValue *DictionaryValue::lookup_array(const StringRef key) const
{
if (const std::shared_ptr<Value> *value = this->lookup(key)) {
return (*value)->as_array_value();
}
return nullptr;
}
void DictionaryValue::append(std::string key, std::shared_ptr<Value> value)
{
this->elements().append({std::move(key), std::move(value)});
}
void DictionaryValue::append_int(std::string key, const int64_t value)
{
this->append(std::move(key), std::make_shared<IntValue>(value));
}
void DictionaryValue::append_double(std::string key, const double value)
{
this->append(std::move(key), std::make_shared<DoubleValue>(value));
}
void DictionaryValue::append_str(std::string key, const std::string value)
{
this->append(std::move(key), std::make_shared<StringValue>(value));
}
std::shared_ptr<DictionaryValue> DictionaryValue::append_dict(std::string key)
{
auto value = std::make_shared<DictionaryValue>();
this->append(std::move(key), value);
return value;
}
std::shared_ptr<ArrayValue> DictionaryValue::append_array(std::string key)
{
auto value = std::make_shared<ArrayValue>();
this->append(std::move(key), value);
return value;
}
void JsonFormatter::serialize(std::ostream &os, const Value &value)
{
nlohmann::ordered_json j;
convert_to_json(j, value);
if (indentation_len) {
os << j.dump(indentation_len);
}
else {
os << j.dump();
}
}
std::unique_ptr<Value> JsonFormatter::deserialize(std::istream &is)
{
nlohmann::ordered_json j;
is >> j;
return convert_from_json(j);
}
void write_json_file(const StringRef path, const Value &value)
{
JsonFormatter formatter;
fstream stream(path, std::ios::out);
formatter.serialize(stream, value);
}
std::shared_ptr<Value> read_json_file(const StringRef path)
{
JsonFormatter formatter;
fstream stream(path, std::ios::in);
return formatter.deserialize(stream);
}
} // namespace blender::io::serialize