Refactor IDProperty UI data storage

The storage of IDProperty UI data (min, max, default value, etc) is
quite complicated. For every property, retrieving a single one of these
values involves three string lookups. First for the "_RNA_UI" group
property, then another for a group with the property's name, then for
the data value name. Not only is this inefficient, it's hard to reason
about, unintuitive, and not at all self-explanatory.

This commit replaces that system with a UI data struct directly in the
IDProperty. If it's not used, the only cost is of a NULL pointer. Beyond
storing the description, name, and RNA subtype, derived structs are used
to store type specific UI data like min and max.

Note that this means that addons using (abusing) the `_RNA_UI` custom
property will have to be changed. A few places in the addons repository
will be changed after this commit with D9919.

**Before**
Before, first the _RNA_UI subgroup is retrieved the _RNA_UI group,
then the subgroup for the original property, then specific UI data
is accessed like any other IDProperty.
```
prop = rna_idprop_ui_prop_get(idproperties_owner, "prop_name", create=True)
prop["min"] = 1.0
```

**After**
After, the `id_properties_ui` function for RNA structs returns a python
object specifically for managing an IDProperty's UI data.
```
ui_data = idproperties_owner.id_properties_ui("prop_name")
ui_data.update(min=1.0)
```
In addition to `update`, there are now other functions:
 - `as_dict`: Returns a dictionary of the property's UI data.
 - `clear`: Removes the property's UI data.
 - `update_from`: Copy UI data between properties,
   even if they have different owners.

Differential Revision: https://developer.blender.org/D9697
This commit is contained in:
Hans Goudey 2021-08-27 08:27:24 -05:00
parent 3f5e0f7d91
commit 8b9a3b94fc
20 changed files with 1933 additions and 807 deletions

View File

@ -30,24 +30,6 @@ ARRAY_TYPES = (list, tuple, IDPropertyArray, Vector)
MAX_DISPLAY_ROWS = 4
def rna_idprop_ui_get(item, *, create=True):
try:
return item['_RNA_UI']
except:
if create:
item['_RNA_UI'] = {}
return item['_RNA_UI']
else:
return None
def rna_idprop_ui_del(item):
try:
del item['_RNA_UI']
except KeyError:
pass
def rna_idprop_quote_path(prop):
return "[\"%s\"]" % bpy.utils.escape_identifier(prop)
@ -59,32 +41,9 @@ def rna_idprop_ui_prop_update(item, prop):
prop_rna.update()
def rna_idprop_ui_prop_get(item, prop, *, create=True):
rna_ui = rna_idprop_ui_get(item, create=create)
if rna_ui is None:
return None
try:
return rna_ui[prop]
except:
rna_ui[prop] = {}
return rna_ui[prop]
def rna_idprop_ui_prop_clear(item, prop, *, remove=True):
rna_ui = rna_idprop_ui_get(item, create=False)
if rna_ui is None:
return
try:
del rna_ui[prop]
except KeyError:
pass
if remove and len(item.keys()) == 1:
rna_idprop_ui_del(item)
def rna_idprop_ui_prop_clear(item, prop):
ui_data = item.id_properties_ui(prop)
ui_data.clear()
def rna_idprop_context_value(context, context_member, property_type):
@ -106,8 +65,7 @@ def rna_idprop_context_value(context, context_member, property_type):
def rna_idprop_has_properties(rna_item):
keys = rna_item.keys()
nbr_props = len(keys)
return (nbr_props > 1) or (nbr_props and '_RNA_UI' not in keys)
return bool(keys)
def rna_idprop_value_to_python(value):
@ -126,31 +84,8 @@ def rna_idprop_value_item_type(value):
def rna_idprop_ui_prop_default_set(item, prop, value):
defvalue = None
try:
prop_type, is_array = rna_idprop_value_item_type(item[prop])
if prop_type in {int, float, str}:
if is_array and isinstance(value, ARRAY_TYPES):
value = [prop_type(item) for item in value]
if any(value):
defvalue = value
else:
defvalue = prop_type(value)
except KeyError:
pass
except ValueError:
pass
if defvalue:
rna_ui = rna_idprop_ui_prop_get(item, prop, create=True)
rna_ui["default"] = defvalue
else:
rna_ui = rna_idprop_ui_prop_get(item, prop)
if rna_ui:
rna_ui.pop("default", None)
return defvalue
ui_data = item.id_properties_ui(prop)
ui_data.update(default=value)
def rna_idprop_ui_create(
@ -163,7 +98,7 @@ def rna_idprop_ui_create(
):
"""Create and initialize a custom property with limits, defaults and other settings."""
proptype, is_array = rna_idprop_value_item_type(default)
proptype, _ = rna_idprop_value_item_type(default)
# Sanitize limits
if proptype is bool:
@ -180,35 +115,22 @@ def rna_idprop_ui_create(
rna_idprop_ui_prop_update(item, prop)
# Clear the UI settings
rna_ui_group = rna_idprop_ui_get(item, create=True)
rna_ui_group[prop] = {}
rna_ui = rna_ui_group[prop]
# Assign limits and default
if proptype in {int, float, bool}:
# The type must be exactly the same
rna_ui["min"] = proptype(min)
rna_ui["soft_min"] = proptype(soft_min)
rna_ui["max"] = proptype(max)
rna_ui["soft_max"] = proptype(soft_max)
if default and (not is_array or any(default)):
rna_ui["default"] = default
if is_array and subtype and subtype != 'NONE':
rna_ui["subtype"] = subtype
# Assign other settings
if description is not None:
rna_ui["description"] = description
# Update the UI settings.
ui_data = item.id_properties_ui(prop)
ui_data.update(
subtype=subtype,
min=min,
max=max,
soft_min=soft_min,
soft_max=soft_max,
description=description,
default=default,
)
prop_path = rna_idprop_quote_path(prop)
item.property_overridable_library_set(prop_path, overridable)
return rna_ui
def draw(layout, context, context_member, property_type, *, use_edit=True):
@ -254,10 +176,6 @@ def draw(layout, context, context_member, property_type, *, use_edit=True):
flow = layout.grid_flow(row_major=False, columns=0, even_columns=True, even_rows=False, align=True)
for key, val in items:
if key == '_RNA_UI':
continue
is_rna = (key in rna_properties)
# only show API defined for developers

View File

@ -970,7 +970,7 @@ class OBJECT_OT_assign_property_defaults(Operator):
def assign_defaults(obj):
from rna_prop_ui import rna_idprop_ui_prop_default_set
rna_properties = {'_RNA_UI'} | {prop.identifier for prop in obj.bl_rna.properties if prop.is_runtime}
rna_properties = {prop.identifier for prop in obj.bl_rna.properties if prop.is_runtime}
for prop, value in obj.items():
if prop not in rna_properties:

View File

@ -1388,10 +1388,8 @@ class WM_OT_properties_edit(Operator):
def execute(self, context):
from rna_prop_ui import (
rna_idprop_ui_prop_get,
rna_idprop_ui_prop_clear,
rna_idprop_ui_prop_update,
rna_idprop_ui_prop_default_set,
rna_idprop_value_item_type,
)
@ -1431,27 +1429,35 @@ class WM_OT_properties_edit(Operator):
prop_type_new = type(prop_value)
prop_type, is_array = rna_idprop_value_item_type(prop_value)
prop_ui = rna_idprop_ui_prop_get(item, prop)
ui_data = item.id_properties_ui(prop)
ui_data.update(subtype=self.subtype, description=self.description)
if prop_type in {float, int}:
prop_ui["min"] = prop_type(self.min)
prop_ui["max"] = prop_type(self.max)
if self.use_soft_limits:
prop_ui["soft_min"] = prop_type(self.soft_min)
prop_ui["soft_max"] = prop_type(self.soft_max)
else:
prop_ui["soft_min"] = prop_type(self.min)
prop_ui["soft_max"] = prop_type(self.max)
if prop_type == float and is_array and self.subtype != 'NONE':
prop_ui["subtype"] = self.subtype
else:
prop_ui.pop("subtype", None)
prop_ui["description"] = self.description
rna_idprop_ui_prop_default_set(item, prop, default_eval)
if prop_type == int:
if type(default_eval) == str:
self.report({'WARNING'}, "Could not evaluate number from default value")
default_eval = None
elif hasattr(default_eval, "__len__"):
default_eval = [int(round(value)) for value in default_eval]
ui_data.update(
min=int(round(self.min)),
max=int(round(self.max)),
soft_min=int(round(self.soft_min)),
soft_max=int(round(self.soft_max)),
default=default_eval,
)
elif prop_type == float:
if type(default_eval) == str:
self.report({'WARNING'}, "Could not evaluate number from default value")
default_eval = None
ui_data.update(
min=self.min,
max=self.max,
soft_min=self.soft_min,
soft_max=self.soft_max,
default=default_eval,
)
elif prop_type == str:
ui_data.update(default=self.default)
# If we have changed the type of the property, update its potential anim curves!
if prop_type_old != prop_type_new:
@ -1492,7 +1498,6 @@ class WM_OT_properties_edit(Operator):
def invoke(self, context, _event):
from rna_prop_ui import (
rna_idprop_ui_prop_get,
rna_idprop_value_to_python,
rna_idprop_value_item_type
)
@ -1526,28 +1531,22 @@ class WM_OT_properties_edit(Operator):
self.default = ""
# setup defaults
prop_ui = rna_idprop_ui_prop_get(item, prop, create=False)
if prop_ui:
self.min = prop_ui.get("min", -1000000000)
self.max = prop_ui.get("max", 1000000000)
self.description = prop_ui.get("description", "")
defval = prop_ui.get("default", None)
if defval is not None:
self.default = str(rna_idprop_value_to_python(defval))
self.soft_min = prop_ui.get("soft_min", self.min)
self.soft_max = prop_ui.get("soft_max", self.max)
ui_data = item.id_properties_ui(prop)
rna_data = ui_data.as_dict()
self.subtype = rna_data["subtype"]
if prop_type in {int, float}:
self.min = rna_data["min"]
self.max = rna_data["max"]
self.soft_min = rna_data["soft_min"]
self.soft_max = rna_data["soft_max"]
self.use_soft_limits = (
self.min != self.soft_min or
self.max != self.soft_max
)
if prop_type in {int, float, str}:
self.default = str(rna_data["default"])
subtype = prop_ui.get("subtype", None)
else:
subtype = None
self._init_subtype(prop_type, is_array, subtype)
self._init_subtype(prop_type, is_array, self.subtype)
# store for comparison
self._cmp_props = self._cmp_props_get()
@ -1688,7 +1687,6 @@ class WM_OT_properties_remove(Operator):
def execute(self, context):
from rna_prop_ui import (
rna_idprop_ui_prop_clear,
rna_idprop_ui_prop_update,
)
data_path = self.data_path
@ -1701,7 +1699,6 @@ class WM_OT_properties_remove(Operator):
prop = self.property
rna_idprop_ui_prop_update(item, prop)
del item[prop]
rna_idprop_ui_prop_clear(item, prop)
return {'FINISHED'}

View File

@ -523,10 +523,6 @@ class WholeCharacterMixin:
# go over all custom properties for bone
for prop in bone.keys():
# ignore special "_RNA_UI" used for UI editing
if prop == "_RNA_UI":
continue
# for now, just add all of 'em
prop_rna = type(bone).bl_rna.properties.get(prop, None)
if prop_rna is None:

View File

@ -32,6 +32,7 @@ struct BlendLibReader;
struct BlendWriter;
struct ID;
struct IDProperty;
struct IDPropertyUIData;
typedef union IDPropertyTemplate {
int i;
@ -183,6 +184,10 @@ void IDP_Reset(struct IDProperty *prop, const struct IDProperty *reference);
# define IDP_Id(prop) ((ID *)(prop)->data.pointer)
#endif
int IDP_coerce_to_int_or_zero(const struct IDProperty *prop);
float IDP_coerce_to_float_or_zero(const struct IDProperty *prop);
double IDP_coerce_to_double_or_zero(const struct IDProperty *prop);
/**
* Call a callback for each idproperty in the hierarchy under given root one (included).
*
@ -209,6 +214,28 @@ void IDP_BlendReadData_impl(struct BlendDataReader *reader,
void IDP_BlendReadLib(struct BlendLibReader *reader, struct IDProperty *prop);
void IDP_BlendReadExpand(struct BlendExpander *expander, struct IDProperty *prop);
typedef enum eIDPropertyUIDataType {
/** Other properties types that don't support RNA UI data. */
IDP_UI_DATA_TYPE_UNSUPPORTED = -1,
/** IDP_INT or IDP_ARRAY with subtype IDP_INT. */
IDP_UI_DATA_TYPE_INT = 0,
/** IDP_FLOAT and IDP_DOUBLE or IDP_ARRAY properties with a float or double subtypes. */
IDP_UI_DATA_TYPE_FLOAT = 1,
/** IDP_STRING properties. */
IDP_UI_DATA_TYPE_STRING = 2,
/** IDP_ID. */
IDP_UI_DATA_TYPE_ID = 3,
} eIDPropertyUIDataType;
bool IDP_ui_data_supported(const struct IDProperty *prop);
eIDPropertyUIDataType IDP_ui_data_type(const struct IDProperty *prop);
void IDP_ui_data_free(struct IDProperty *prop);
void IDP_ui_data_free_unique_contents(struct IDPropertyUIData *ui_data,
eIDPropertyUIDataType type,
const struct IDPropertyUIData *other);
struct IDPropertyUIData *IDP_ui_data_ensure(struct IDProperty *prop);
struct IDPropertyUIData *IDP_ui_data_copy(const struct IDProperty *prop);
#ifdef __cplusplus
}
#endif

View File

@ -21,6 +21,7 @@
* \ingroup bke
*/
#include <limits.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
@ -274,6 +275,43 @@ void IDP_FreeArray(IDProperty *prop)
}
}
IDPropertyUIData *IDP_ui_data_copy(const IDProperty *prop)
{
IDPropertyUIData *dst_ui_data = MEM_dupallocN(prop->ui_data);
/* Copy extra type specific data. */
switch (IDP_ui_data_type(prop)) {
case IDP_UI_DATA_TYPE_STRING: {
const IDPropertyUIDataString *src = (const IDPropertyUIDataString *)prop->ui_data;
IDPropertyUIDataString *dst = (IDPropertyUIDataString *)dst_ui_data;
dst->default_value = MEM_dupallocN(src->default_value);
break;
}
case IDP_UI_DATA_TYPE_ID: {
break;
}
case IDP_UI_DATA_TYPE_INT: {
const IDPropertyUIDataInt *src = (const IDPropertyUIDataInt *)prop->ui_data;
IDPropertyUIDataInt *dst = (IDPropertyUIDataInt *)dst_ui_data;
dst->default_array = MEM_dupallocN(src->default_array);
break;
}
case IDP_UI_DATA_TYPE_FLOAT: {
const IDPropertyUIDataFloat *src = (const IDPropertyUIDataFloat *)prop->ui_data;
IDPropertyUIDataFloat *dst = (IDPropertyUIDataFloat *)dst_ui_data;
dst->default_array = MEM_dupallocN(src->default_array);
break;
}
case IDP_UI_DATA_TYPE_UNSUPPORTED: {
break;
}
}
dst_ui_data->description = MEM_dupallocN(prop->ui_data->description);
return dst_ui_data;
}
static IDProperty *idp_generic_copy(const IDProperty *prop, const int UNUSED(flag))
{
IDProperty *newp = MEM_callocN(sizeof(IDProperty), __func__);
@ -284,6 +322,10 @@ static IDProperty *idp_generic_copy(const IDProperty *prop, const int UNUSED(fla
newp->data.val = prop->data.val;
newp->data.val2 = prop->data.val2;
if (prop->ui_data != NULL) {
newp->ui_data = IDP_ui_data_copy(prop);
}
return newp;
}
@ -679,6 +721,7 @@ bool IDP_InsertToGroup(IDProperty *group, IDProperty *previous, IDProperty *pnew
void IDP_RemoveFromGroup(IDProperty *group, IDProperty *prop)
{
BLI_assert(group->type == IDP_GROUP);
BLI_assert(BLI_findindex(&group->data.group, prop) != -1);
group->len--;
BLI_remlink(&group->data.group, prop);
@ -725,6 +768,60 @@ static void IDP_FreeGroup(IDProperty *prop, const bool do_id_user)
/** \name Main Functions (IDProperty Main API)
* \{ */
/**
* Return an int from an IDProperty with a compatible type. This should be avoided, but
* it's sometimes necessary, for example when legacy files have incorrect property types.
*/
int IDP_coerce_to_int_or_zero(const IDProperty *prop)
{
switch (prop->type) {
case IDP_INT:
return IDP_Int(prop);
case IDP_DOUBLE:
return (int)IDP_Double(prop);
case IDP_FLOAT:
return (int)IDP_Float(prop);
default:
return 0;
}
}
/**
* Return a double from an IDProperty with a compatible type. This should be avoided, but
* it's sometimes necessary, for example when legacy files have incorrect property types.
*/
double IDP_coerce_to_double_or_zero(const IDProperty *prop)
{
switch (prop->type) {
case IDP_DOUBLE:
return IDP_Double(prop);
case IDP_FLOAT:
return (double)IDP_Float(prop);
case IDP_INT:
return (double)IDP_Int(prop);
default:
return 0.0;
}
}
/**
* Return a float from an IDProperty with a compatible type. This should be avoided, but
* it's sometimes necessary, for example when legacy files have incorrect property types.
*/
float IDP_coerce_to_float_or_zero(const IDProperty *prop)
{
switch (prop->type) {
case IDP_FLOAT:
return IDP_Float(prop);
case IDP_DOUBLE:
return (float)IDP_Double(prop);
case IDP_INT:
return (float)IDP_Int(prop);
default:
return 0.0f;
}
}
IDProperty *IDP_CopyProperty_ex(const IDProperty *prop, const int flag)
{
switch (prop->type) {
@ -999,6 +1096,85 @@ IDProperty *IDP_New(const char type, const IDPropertyTemplate *val, const char *
return prop;
}
/**
* Free allocated pointers in the UI data that isn't shared with the UI data in the #other
* argument. Useful for returning early on failure when updating UI data in place, or when
* replacing a subset of the UI data's allocated pointers.
*/
void IDP_ui_data_free_unique_contents(IDPropertyUIData *ui_data,
const eIDPropertyUIDataType type,
const IDPropertyUIData *other)
{
if (ui_data->description != other->description) {
MEM_SAFE_FREE(ui_data->description);
}
switch (type) {
case IDP_UI_DATA_TYPE_STRING: {
const IDPropertyUIDataString *other_string = (const IDPropertyUIDataString *)other;
IDPropertyUIDataString *ui_data_string = (IDPropertyUIDataString *)ui_data;
if (ui_data_string->default_value != other_string->default_value) {
MEM_SAFE_FREE(ui_data_string->default_value);
}
break;
}
case IDP_UI_DATA_TYPE_ID: {
break;
}
case IDP_UI_DATA_TYPE_INT: {
const IDPropertyUIDataInt *other_int = (const IDPropertyUIDataInt *)other;
IDPropertyUIDataInt *ui_data_int = (IDPropertyUIDataInt *)ui_data;
if (ui_data_int->default_array != other_int->default_array) {
MEM_SAFE_FREE(ui_data_int->default_array);
}
break;
}
case IDP_UI_DATA_TYPE_FLOAT: {
const IDPropertyUIDataFloat *other_float = (const IDPropertyUIDataFloat *)other;
IDPropertyUIDataFloat *ui_data_float = (IDPropertyUIDataFloat *)ui_data;
if (ui_data_float->default_array != other_float->default_array) {
MEM_SAFE_FREE(ui_data_float->default_array);
}
break;
}
case IDP_UI_DATA_TYPE_UNSUPPORTED: {
break;
}
}
}
void IDP_ui_data_free(IDProperty *prop)
{
switch (IDP_ui_data_type(prop)) {
case IDP_UI_DATA_TYPE_STRING: {
IDPropertyUIDataString *ui_data_string = (IDPropertyUIDataString *)prop->ui_data;
MEM_SAFE_FREE(ui_data_string->default_value);
break;
}
case IDP_UI_DATA_TYPE_ID: {
break;
}
case IDP_UI_DATA_TYPE_INT: {
IDPropertyUIDataInt *ui_data_int = (IDPropertyUIDataInt *)prop->ui_data;
MEM_SAFE_FREE(ui_data_int->default_array);
break;
}
case IDP_UI_DATA_TYPE_FLOAT: {
IDPropertyUIDataFloat *ui_data_float = (IDPropertyUIDataFloat *)prop->ui_data;
MEM_SAFE_FREE(ui_data_float->default_array);
break;
}
case IDP_UI_DATA_TYPE_UNSUPPORTED: {
break;
}
}
MEM_SAFE_FREE(prop->ui_data->description);
MEM_freeN(prop->ui_data);
prop->ui_data = NULL;
}
/**
* \note This will free allocated data, all child properties of arrays and groups, and unlink IDs!
* But it does not free the actual IDProperty struct itself.
@ -1024,6 +1200,10 @@ void IDP_FreePropertyContent_ex(IDProperty *prop, const bool do_id_user)
}
break;
}
if (prop->ui_data != NULL) {
IDP_ui_data_free(prop);
}
}
void IDP_FreePropertyContent(IDProperty *prop)
@ -1104,6 +1284,48 @@ void IDP_foreach_property(IDProperty *id_property_root,
void IDP_WriteProperty_OnlyData(const IDProperty *prop, BlendWriter *writer);
static void write_ui_data(const IDProperty *prop, BlendWriter *writer)
{
IDPropertyUIData *ui_data = prop->ui_data;
BLO_write_string(writer, ui_data->description);
switch (IDP_ui_data_type(prop)) {
case IDP_UI_DATA_TYPE_STRING: {
IDPropertyUIDataString *ui_data_string = (IDPropertyUIDataString *)ui_data;
BLO_write_string(writer, ui_data_string->default_value);
BLO_write_struct(writer, IDPropertyUIDataString, ui_data);
break;
}
case IDP_UI_DATA_TYPE_ID: {
BLO_write_struct(writer, IDPropertyUIDataID, ui_data);
break;
}
case IDP_UI_DATA_TYPE_INT: {
IDPropertyUIDataInt *ui_data_int = (IDPropertyUIDataInt *)ui_data;
if (prop->type == IDP_ARRAY) {
BLO_write_int32_array(
writer, (uint)ui_data_int->default_array_len, (int32_t *)ui_data_int->default_array);
}
BLO_write_struct(writer, IDPropertyUIDataInt, ui_data);
break;
}
case IDP_UI_DATA_TYPE_FLOAT: {
IDPropertyUIDataFloat *ui_data_float = (IDPropertyUIDataFloat *)ui_data;
if (prop->type == IDP_ARRAY) {
BLO_write_double_array(
writer, (uint)ui_data_float->default_array_len, ui_data_float->default_array);
}
BLO_write_struct(writer, IDPropertyUIDataFloat, ui_data);
break;
}
case IDP_UI_DATA_TYPE_UNSUPPORTED: {
BLI_assert_unreachable();
break;
}
}
}
static void IDP_WriteArray(const IDProperty *prop, BlendWriter *writer)
{
/* Remember to set #IDProperty.totallen to len in the linking code! */
@ -1165,6 +1387,9 @@ void IDP_WriteProperty_OnlyData(const IDProperty *prop, BlendWriter *writer)
IDP_WriteIDPArray(prop, writer);
break;
}
if (prop->ui_data != NULL) {
write_ui_data(prop, writer);
}
}
void IDP_BlendWrite(BlendWriter *writer, const IDProperty *prop)
@ -1175,6 +1400,43 @@ void IDP_BlendWrite(BlendWriter *writer, const IDProperty *prop)
static void IDP_DirectLinkProperty(IDProperty *prop, BlendDataReader *reader);
static void read_ui_data(IDProperty *prop, BlendDataReader *reader)
{
BLO_read_data_address(reader, &prop->ui_data);
BLO_read_data_address(reader, &prop->ui_data->description);
switch (IDP_ui_data_type(prop)) {
case IDP_UI_DATA_TYPE_STRING: {
IDPropertyUIDataString *ui_data_string = (IDPropertyUIDataString *)prop->ui_data;
BLO_read_data_address(reader, &ui_data_string->default_value);
break;
}
case IDP_UI_DATA_TYPE_ID: {
break;
}
case IDP_UI_DATA_TYPE_INT: {
IDPropertyUIDataInt *ui_data_int = (IDPropertyUIDataInt *)prop->ui_data;
if (prop->type == IDP_ARRAY) {
BLO_read_int32_array(
reader, ui_data_int->default_array_len, (int **)&ui_data_int->default_array);
}
break;
}
case IDP_UI_DATA_TYPE_FLOAT: {
IDPropertyUIDataFloat *ui_data_float = (IDPropertyUIDataFloat *)prop->ui_data;
if (prop->type == IDP_ARRAY) {
BLO_read_double_array(
reader, ui_data_float->default_array_len, (double **)&ui_data_float->default_array);
}
break;
}
case IDP_UI_DATA_TYPE_UNSUPPORTED: {
BLI_assert_unreachable();
break;
}
}
}
static void IDP_DirectLinkIDPArray(IDProperty *prop, BlendDataReader *reader)
{
/* since we didn't save the extra buffer, set totallen to len */
@ -1279,6 +1541,10 @@ static void IDP_DirectLinkProperty(IDProperty *prop, BlendDataReader *reader)
prop->subtype = 0;
IDP_Int(prop) = 0;
}
if (prop->ui_data != NULL) {
read_ui_data(prop, reader);
}
}
void IDP_BlendReadData_impl(BlendDataReader *reader, IDProperty **prop, const char *caller_func_id)
@ -1358,4 +1624,74 @@ void IDP_BlendReadExpand(struct BlendExpander *expander, IDProperty *prop)
}
}
eIDPropertyUIDataType IDP_ui_data_type(const IDProperty *prop)
{
if (prop->type == IDP_STRING) {
return IDP_UI_DATA_TYPE_STRING;
}
if (prop->type == IDP_ID) {
return IDP_UI_DATA_TYPE_ID;
}
if (prop->type == IDP_INT || (prop->type == IDP_ARRAY && prop->subtype == IDP_INT)) {
return IDP_UI_DATA_TYPE_INT;
}
if (ELEM(prop->type, IDP_FLOAT, IDP_DOUBLE) ||
(prop->type == IDP_ARRAY && ELEM(prop->subtype, IDP_FLOAT, IDP_DOUBLE))) {
return IDP_UI_DATA_TYPE_FLOAT;
}
return IDP_UI_DATA_TYPE_UNSUPPORTED;
}
bool IDP_ui_data_supported(const IDProperty *prop)
{
return IDP_ui_data_type(prop) != IDP_UI_DATA_TYPE_UNSUPPORTED;
}
IDPropertyUIData *IDP_ui_data_ensure(IDProperty *prop)
{
if (prop->ui_data != NULL) {
return prop->ui_data;
}
switch (IDP_ui_data_type(prop)) {
case IDP_UI_DATA_TYPE_STRING: {
prop->ui_data = MEM_callocN(sizeof(IDPropertyUIDataString), __func__);
break;
}
case IDP_UI_DATA_TYPE_ID: {
IDPropertyUIDataID *ui_data = MEM_callocN(sizeof(IDPropertyUIDataID), __func__);
prop->ui_data = (IDPropertyUIData *)ui_data;
break;
}
case IDP_UI_DATA_TYPE_INT: {
IDPropertyUIDataInt *ui_data = MEM_callocN(sizeof(IDPropertyUIDataInt), __func__);
ui_data->min = -INT_MAX;
ui_data->max = INT_MAX;
ui_data->soft_min = -INT_MAX;
ui_data->soft_max = INT_MAX;
ui_data->step = 1;
prop->ui_data = (IDPropertyUIData *)ui_data;
break;
}
case IDP_UI_DATA_TYPE_FLOAT: {
IDPropertyUIDataFloat *ui_data = MEM_callocN(sizeof(IDPropertyUIDataFloat), __func__);
ui_data->min = -FLT_MAX;
ui_data->max = FLT_MAX;
ui_data->soft_min = -FLT_MAX;
ui_data->soft_max = FLT_MAX;
ui_data->step = 1.0f;
ui_data->precision = 3;
prop->ui_data = (IDPropertyUIData *)ui_data;
break;
}
case IDP_UI_DATA_TYPE_UNSUPPORTED: {
/* UI data not supported for remaining types, this shouldn't be called in those cases. */
BLI_assert_unreachable();
break;
}
}
return prop->ui_data;
}
/** \} */

View File

@ -20,6 +20,10 @@
/* allow readfile to use deprecated functionality */
#define DNA_DEPRECATED_ALLOW
#include <string.h>
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
#include "BLI_math_vector.h"
#include "BLI_path_util.h"
@ -46,10 +50,14 @@
#include "BKE_deform.h"
#include "BKE_fcurve.h"
#include "BKE_fcurve_driver.h"
#include "BKE_idprop.h"
#include "BKE_lib_id.h"
#include "BKE_main.h"
#include "BKE_node.h"
#include "RNA_access.h"
#include "RNA_enum_types.h"
#include "BLO_readfile.h"
#include "MEM_guardedalloc.h"
#include "readfile.h"
@ -60,6 +68,238 @@
#include "versioning_common.h"
static IDProperty *idproperty_find_ui_container(IDProperty *idprop_group)
{
LISTBASE_FOREACH (IDProperty *, prop, &idprop_group->data.group) {
if (prop->type == IDP_GROUP && STREQ(prop->name, "_RNA_UI")) {
return prop;
}
}
return NULL;
}
static void version_idproperty_move_data_int(IDPropertyUIDataInt *ui_data,
const IDProperty *prop_ui_data)
{
IDProperty *min = IDP_GetPropertyFromGroup(prop_ui_data, "min");
if (min != NULL) {
ui_data->min = ui_data->soft_min = IDP_coerce_to_int_or_zero(min);
}
IDProperty *max = IDP_GetPropertyFromGroup(prop_ui_data, "max");
if (max != NULL) {
ui_data->max = ui_data->soft_max = IDP_coerce_to_int_or_zero(max);
}
IDProperty *soft_min = IDP_GetPropertyFromGroup(prop_ui_data, "soft_min");
if (soft_min != NULL) {
ui_data->soft_min = IDP_coerce_to_int_or_zero(soft_min);
ui_data->soft_min = MIN2(ui_data->soft_min, ui_data->min);
}
IDProperty *soft_max = IDP_GetPropertyFromGroup(prop_ui_data, "soft_max");
if (soft_max != NULL) {
ui_data->soft_max = IDP_coerce_to_int_or_zero(soft_max);
ui_data->soft_max = MAX2(ui_data->soft_max, ui_data->max);
}
IDProperty *step = IDP_GetPropertyFromGroup(prop_ui_data, "step");
if (step != NULL) {
ui_data->step = IDP_coerce_to_int_or_zero(soft_max);
}
IDProperty *default_value = IDP_GetPropertyFromGroup(prop_ui_data, "default");
if (default_value != NULL) {
if (default_value->type == IDP_ARRAY) {
if (default_value->subtype == IDP_INT) {
ui_data->default_array = MEM_dupallocN(IDP_Array(default_value));
ui_data->default_array_len = default_value->len;
}
}
else if (default_value->type == IDP_INT) {
ui_data->default_value = IDP_coerce_to_int_or_zero(default_value);
}
}
}
static void version_idproperty_move_data_float(IDPropertyUIDataFloat *ui_data,
const IDProperty *prop_ui_data)
{
IDProperty *min = IDP_GetPropertyFromGroup(prop_ui_data, "min");
if (min != NULL) {
ui_data->min = ui_data->soft_min = IDP_coerce_to_double_or_zero(min);
}
IDProperty *max = IDP_GetPropertyFromGroup(prop_ui_data, "max");
if (max != NULL) {
ui_data->max = ui_data->soft_max = IDP_coerce_to_double_or_zero(min);
}
IDProperty *soft_min = IDP_GetPropertyFromGroup(prop_ui_data, "soft_min");
if (soft_min != NULL) {
ui_data->soft_min = IDP_coerce_to_double_or_zero(soft_min);
ui_data->soft_min = MAX2(ui_data->soft_min, ui_data->min);
}
IDProperty *soft_max = IDP_GetPropertyFromGroup(prop_ui_data, "soft_max");
if (soft_max != NULL) {
ui_data->soft_max = IDP_coerce_to_double_or_zero(soft_max);
ui_data->soft_max = MIN2(ui_data->soft_max, ui_data->max);
}
IDProperty *step = IDP_GetPropertyFromGroup(prop_ui_data, "step");
if (step != NULL) {
ui_data->step = IDP_coerce_to_float_or_zero(step);
}
IDProperty *precision = IDP_GetPropertyFromGroup(prop_ui_data, "precision");
if (precision != NULL) {
ui_data->precision = IDP_coerce_to_int_or_zero(precision);
}
IDProperty *default_value = IDP_GetPropertyFromGroup(prop_ui_data, "default");
if (default_value != NULL) {
if (default_value->type == IDP_ARRAY) {
if (ELEM(default_value->subtype, IDP_FLOAT, IDP_DOUBLE)) {
ui_data->default_array = MEM_dupallocN(IDP_Array(default_value));
ui_data->default_array_len = default_value->len;
}
}
else if (ELEM(default_value->type, IDP_DOUBLE, IDP_FLOAT)) {
ui_data->default_value = IDP_coerce_to_double_or_zero(default_value);
}
}
}
static void version_idproperty_move_data_string(IDPropertyUIDataString *ui_data,
const IDProperty *prop_ui_data)
{
IDProperty *default_value = IDP_GetPropertyFromGroup(prop_ui_data, "default");
if (default_value != NULL && default_value->type == IDP_STRING) {
ui_data->default_value = BLI_strdup(IDP_String(default_value));
}
}
static void version_idproperty_ui_data(IDProperty *idprop_group)
{
if (idprop_group == NULL) { /* NULL check here to reduce verbosity of calls to this function. */
return;
}
IDProperty *ui_container = idproperty_find_ui_container(idprop_group);
if (ui_container == NULL) {
return;
}
LISTBASE_FOREACH (IDProperty *, prop, &idprop_group->data.group) {
IDProperty *prop_ui_data = IDP_GetPropertyFromGroup(ui_container, prop->name);
if (prop_ui_data == NULL) {
continue;
}
if (!IDP_ui_data_supported(prop)) {
continue;
}
IDPropertyUIData *ui_data = IDP_ui_data_ensure(prop);
IDProperty *subtype = IDP_GetPropertyFromGroup(prop_ui_data, "subtype");
if (subtype != NULL && subtype->type == IDP_STRING) {
const char *subtype_string = IDP_String(subtype);
int result = PROP_NONE;
RNA_enum_value_from_id(rna_enum_property_subtype_items, subtype_string, &result);
ui_data->rna_subtype = result;
}
IDProperty *description = IDP_GetPropertyFromGroup(prop_ui_data, "description");
if (description != NULL && description->type == IDP_STRING) {
ui_data->description = BLI_strdup(IDP_String(description));
}
/* Type specific data. */
switch (IDP_ui_data_type(prop)) {
case IDP_UI_DATA_TYPE_STRING:
version_idproperty_move_data_string((IDPropertyUIDataString *)ui_data, prop_ui_data);
break;
case IDP_UI_DATA_TYPE_ID:
break;
case IDP_UI_DATA_TYPE_INT:
version_idproperty_move_data_int((IDPropertyUIDataInt *)ui_data, prop_ui_data);
break;
case IDP_UI_DATA_TYPE_FLOAT:
version_idproperty_move_data_float((IDPropertyUIDataFloat *)ui_data, prop_ui_data);
break;
case IDP_UI_DATA_TYPE_UNSUPPORTED:
BLI_assert_unreachable();
break;
}
IDP_FreeFromGroup(ui_container, prop_ui_data);
}
IDP_FreeFromGroup(idprop_group, ui_container);
}
static void do_versions_idproperty_bones_recursive(Bone *bone)
{
version_idproperty_ui_data(bone->prop);
LISTBASE_FOREACH (Bone *, child_bone, &bone->childbase) {
do_versions_idproperty_bones_recursive(child_bone);
}
}
/**
* For every data block that supports them, initialize the new IDProperty UI data struct based on
* the old more complicated storage. Assumes only the top level of IDProperties below the parent
* group had UI data in a "_RNA_UI" group.
*
* \note The following IDProperty groups in DNA aren't exposed in the UI or are runtime-only, so
* they don't have UI data: wmOperator, bAddon, bUserMenuItem_Op, wmKeyMapItem, wmKeyConfigPref,
* uiList, FFMpegCodecData, View3DShading, bToolRef, TimeMarker, ViewLayer, bPoseChannel.
*/
static void do_versions_idproperty_ui_data(Main *bmain)
{
/* ID data. */
ID *id;
FOREACH_MAIN_ID_BEGIN (bmain, id) {
IDProperty *idprop_group = IDP_GetProperties(id, false);
if (idprop_group == NULL) {
continue;
}
version_idproperty_ui_data(idprop_group);
}
FOREACH_MAIN_ID_END;
/* Bones. */
LISTBASE_FOREACH (bArmature *, armature, &bmain->armatures) {
LISTBASE_FOREACH (Bone *, bone, &armature->bonebase) {
do_versions_idproperty_bones_recursive(bone);
}
}
/* Nodes and node sockets. */
LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) {
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
version_idproperty_ui_data(node->prop);
}
LISTBASE_FOREACH (bNodeSocket *, socket, &ntree->inputs) {
version_idproperty_ui_data(socket->prop);
}
LISTBASE_FOREACH (bNodeSocket *, socket, &ntree->outputs) {
version_idproperty_ui_data(socket->prop);
}
}
/* The UI data from exposed node modifier properties is just copied from the corresponding node
* group, but the copying only runs when necessary, so we still need to version UI data here. */
LISTBASE_FOREACH (Object *, ob, &bmain->objects) {
LISTBASE_FOREACH (ModifierData *, md, &ob->modifiers) {
if (md->type == eModifierType_Nodes) {
NodesModifierData *nmd = (NodesModifierData *)md;
version_idproperty_ui_data(nmd->settings.properties);
}
}
}
/* Sequences. */
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
if (scene->ed != NULL) {
LISTBASE_FOREACH (Sequence *, seq, &scene->ed->seqbase) {
version_idproperty_ui_data(seq->prop);
}
}
}
}
static void sort_linked_ids(Main *bmain)
{
ListBase *lb;
@ -252,6 +492,7 @@ void do_versions_after_linking_300(Main *bmain, ReportList *UNUSED(reports))
*/
{
/* Keep this block, even when empty. */
do_versions_idproperty_ui_data(bmain);
}
}

View File

@ -60,9 +60,6 @@ void CustomPropertiesExporter::write_all(const IDProperty *group)
/* Loop over the properties, just like IDP_foreach_property() does, but without the recursion. */
LISTBASE_FOREACH (IDProperty *, id_property, &group->data.group) {
if (STREQ(id_property->name, "_RNA_UI")) {
continue;
}
write(id_property);
}
}

View File

@ -59,6 +59,58 @@ typedef struct DrawDataList {
struct DrawData *first, *last;
} DrawDataList;
typedef struct IDPropertyUIData {
/** Tooltip / property description pointer. Owned by the IDProperty. */
char *description;
/** RNA subtype, used for every type except string properties (PropertySubType). */
int rna_subtype;
char _pad[4];
} IDPropertyUIData;
/* IDP_UI_DATA_TYPE_INT */
typedef struct IDPropertyUIDataInt {
IDPropertyUIData base;
int *default_array; /* Only for array properties. */
int default_array_len;
char _pad[4];
int min;
int max;
int soft_min;
int soft_max;
int step;
int default_value;
} IDPropertyUIDataInt;
/* IDP_UI_DATA_TYPE_FLOAT */
typedef struct IDPropertyUIDataFloat {
IDPropertyUIData base;
double *default_array; /* Only for array properties. */
int default_array_len;
char _pad[4];
float step;
int precision;
double min;
double max;
double soft_min;
double soft_max;
double default_value;
} IDPropertyUIDataFloat;
/* IDP_UI_DATA_TYPE_STRING */
typedef struct IDPropertyUIDataString {
IDPropertyUIData base;
char *default_value;
} IDPropertyUIDataString;
/* IDP_UI_DATA_TYPE_ID */
typedef struct IDPropertyUIDataID {
IDPropertyUIData base;
} IDPropertyUIDataID;
typedef struct IDPropertyData {
void *pointer;
ListBase group;
@ -87,6 +139,8 @@ typedef struct IDProperty {
/* totallen is total length of allocated array/string, including a buffer.
* Note that the buffering is mild; the code comes from python's list implementation. */
int totallen;
IDPropertyUIData *ui_data;
} IDProperty;
#define MAX_IDPROP_NAME 64

View File

@ -1006,7 +1006,7 @@ int RNA_property_int_get_index(PointerRNA *ptr, PropertyRNA *prop, int index);
void RNA_property_int_set_array(PointerRNA *ptr, PropertyRNA *prop, const int *values);
void RNA_property_int_set_index(PointerRNA *ptr, PropertyRNA *prop, int index, int value);
int RNA_property_int_get_default(PointerRNA *ptr, PropertyRNA *prop);
bool RNA_property_int_set_default(PointerRNA *ptr, PropertyRNA *prop, int value);
bool RNA_property_int_set_default(PropertyRNA *prop, int value);
void RNA_property_int_get_default_array(PointerRNA *ptr, PropertyRNA *prop, int *values);
int RNA_property_int_get_default_index(PointerRNA *ptr, PropertyRNA *prop, int index);
@ -1018,7 +1018,7 @@ float RNA_property_float_get_index(PointerRNA *ptr, PropertyRNA *prop, int index
void RNA_property_float_set_array(PointerRNA *ptr, PropertyRNA *prop, const float *values);
void RNA_property_float_set_index(PointerRNA *ptr, PropertyRNA *prop, int index, float value);
float RNA_property_float_get_default(PointerRNA *ptr, PropertyRNA *prop);
bool RNA_property_float_set_default(PointerRNA *ptr, PropertyRNA *prop, float value);
bool RNA_property_float_set_default(PropertyRNA *prop, float value);
void RNA_property_float_get_default_array(PointerRNA *ptr, PropertyRNA *prop, float *values);
float RNA_property_float_get_default_index(PointerRNA *ptr, PropertyRNA *prop, int index);
@ -1028,7 +1028,7 @@ char *RNA_property_string_get_alloc(
void RNA_property_string_set(PointerRNA *ptr, PropertyRNA *prop, const char *value);
void RNA_property_string_set_bytes(PointerRNA *ptr, PropertyRNA *prop, const char *value, int len);
int RNA_property_string_length(PointerRNA *ptr, PropertyRNA *prop);
void RNA_property_string_get_default(PointerRNA *ptr, PropertyRNA *prop, char *value);
void RNA_property_string_get_default(PropertyRNA *prop, char *value, int max_len);
char *RNA_property_string_get_default_alloc(
PointerRNA *ptr, PropertyRNA *prop, char *fixedbuf, int fixedlen, int *r_len);
int RNA_property_string_default_length(PointerRNA *ptr, PropertyRNA *prop);

View File

@ -246,130 +246,6 @@ void rna_idproperty_touch(IDProperty *idprop)
idprop->flag &= ~IDP_FLAG_GHOST;
}
static IDProperty *rna_idproperty_ui_container(PropertyRNA *prop)
{
IDProperty *idprop;
for (idprop = ((IDProperty *)prop)->prev; idprop; idprop = idprop->prev) {
if (STREQ(RNA_IDP_UI, idprop->name)) {
break;
}
}
if (idprop == NULL) {
for (idprop = ((IDProperty *)prop)->next; idprop; idprop = idprop->next) {
if (STREQ(RNA_IDP_UI, idprop->name)) {
break;
}
}
}
return idprop;
}
/* return a UI local ID prop definition for this prop */
static const IDProperty *rna_idproperty_ui(const PropertyRNA *prop)
{
IDProperty *idprop = rna_idproperty_ui_container((PropertyRNA *)prop);
if (idprop) {
return IDP_GetPropertyTypeFromGroup(idprop, ((IDProperty *)prop)->name, IDP_GROUP);
}
return NULL;
}
/* return or create a UI local ID prop definition for this prop */
static IDProperty *rna_idproperty_ui_ensure(PointerRNA *ptr, PropertyRNA *prop, bool create)
{
IDProperty *idprop = rna_idproperty_ui_container(prop);
IDPropertyTemplate dummy = {0};
if (idprop == NULL && create) {
IDProperty *props = RNA_struct_idprops(ptr, false);
/* Sanity check: props is the actual container of this property. */
if (props != NULL && BLI_findindex(&props->data.group, prop) >= 0) {
idprop = IDP_New(IDP_GROUP, &dummy, RNA_IDP_UI);
if (!IDP_AddToGroup(props, idprop)) {
IDP_FreePropertyContent(idprop);
return NULL;
}
}
}
if (idprop) {
const char *name = ((IDProperty *)prop)->name;
IDProperty *rv = IDP_GetPropertyTypeFromGroup(idprop, name, IDP_GROUP);
if (rv == NULL && create) {
rv = IDP_New(IDP_GROUP, &dummy, name);
if (!IDP_AddToGroup(idprop, rv)) {
IDP_FreePropertyContent(rv);
return NULL;
}
}
return rv;
}
return NULL;
}
static bool rna_idproperty_ui_set_default(PointerRNA *ptr,
PropertyRNA *prop,
const char type,
IDPropertyTemplate *value)
{
BLI_assert(ELEM(type, IDP_INT, IDP_DOUBLE));
if (prop->magic == RNA_MAGIC) {
return false;
}
/* attempt to get the local ID values */
IDProperty *idp_ui = rna_idproperty_ui_ensure(ptr, prop, value != NULL);
if (idp_ui == NULL) {
return (value == NULL);
}
IDProperty *item = IDP_GetPropertyTypeFromGroup(idp_ui, "default", type);
if (value == NULL) {
if (item != NULL) {
IDP_RemoveFromGroup(idp_ui, item);
}
}
else {
if (item != NULL) {
switch (type) {
case IDP_INT:
IDP_Int(item) = value->i;
break;
case IDP_DOUBLE:
IDP_Double(item) = value->d;
break;
default:
BLI_assert(false);
return false;
}
}
else {
item = IDP_New(type, value, "default");
if (!IDP_AddToGroup(idp_ui, item)) {
IDP_FreePropertyContent(item);
return false;
}
}
}
return true;
}
IDProperty **RNA_struct_idprops_p(PointerRNA *ptr)
{
StructRNA *type = ptr->type;
@ -693,28 +569,17 @@ static const char *rna_ensure_property_identifier(const PropertyRNA *prop)
static const char *rna_ensure_property_description(const PropertyRNA *prop)
{
const char *description = NULL;
if (prop->magic == RNA_MAGIC) {
description = prop->description;
}
else {
/* attempt to get the local ID values */
const IDProperty *idp_ui = rna_idproperty_ui(prop);
if (idp_ui) {
IDProperty *item = IDP_GetPropertyTypeFromGroup(idp_ui, "description", IDP_STRING);
if (item) {
description = IDP_String(item);
}
}
if (description == NULL) {
description = ((IDProperty *)prop)->name; /* XXX: not correct. */
}
return prop->description;
}
return description;
const IDProperty *idprop = (const IDProperty *)prop;
if (idprop->ui_data) {
const IDPropertyUIData *ui_data = idprop->ui_data;
return ui_data->description;
}
return "";
}
static const char *rna_ensure_property_name(const PropertyRNA *prop)
@ -1196,19 +1061,9 @@ PropertySubType RNA_property_subtype(PropertyRNA *prop)
if (prop->magic != RNA_MAGIC) {
IDProperty *idprop = (IDProperty *)prop;
if (ELEM(idprop->type, IDP_INT, IDP_FLOAT, IDP_DOUBLE) ||
((idprop->type == IDP_ARRAY) && ELEM(idprop->subtype, IDP_INT, IDP_FLOAT, IDP_DOUBLE))) {
const IDProperty *idp_ui = rna_idproperty_ui(prop);
if (idp_ui) {
IDProperty *item = IDP_GetPropertyTypeFromGroup(idp_ui, "subtype", IDP_STRING);
if (item) {
int result = PROP_NONE;
RNA_enum_value_from_id(rna_enum_property_subtype_items, IDP_String(item), &result);
return (PropertySubType)result;
}
}
if (idprop->ui_data) {
IDPropertyUIData *ui_data = idprop->ui_data;
return (PropertySubType)ui_data->rna_subtype;
}
}
@ -1387,20 +1242,17 @@ void RNA_property_int_range(PointerRNA *ptr, PropertyRNA *prop, int *hardmin, in
int softmin, softmax;
if (prop->magic != RNA_MAGIC) {
/* attempt to get the local ID values */
const IDProperty *idp_ui = rna_idproperty_ui(prop);
if (idp_ui) {
IDProperty *item;
item = IDP_GetPropertyTypeFromGroup(idp_ui, "min", IDP_INT);
*hardmin = item ? IDP_Int(item) : INT_MIN;
item = IDP_GetPropertyTypeFromGroup(idp_ui, "max", IDP_INT);
*hardmax = item ? IDP_Int(item) : INT_MAX;
return;
const IDProperty *idprop = (IDProperty *)prop;
if (idprop->ui_data) {
IDPropertyUIDataInt *ui_data = (IDPropertyUIDataInt *)idprop->ui_data;
*hardmin = ui_data->min;
*hardmax = ui_data->max;
}
else {
*hardmin = INT_MIN;
*hardmax = INT_MAX;
}
return;
}
if (iprop->range) {
@ -1428,23 +1280,19 @@ void RNA_property_int_ui_range(
int hardmin, hardmax;
if (prop->magic != RNA_MAGIC) {
/* attempt to get the local ID values */
const IDProperty *idp_ui = rna_idproperty_ui(prop);
if (idp_ui) {
IDProperty *item;
item = IDP_GetPropertyTypeFromGroup(idp_ui, "soft_min", IDP_INT);
*softmin = item ? IDP_Int(item) : INT_MIN;
item = IDP_GetPropertyTypeFromGroup(idp_ui, "soft_max", IDP_INT);
*softmax = item ? IDP_Int(item) : INT_MAX;
item = IDP_GetPropertyTypeFromGroup(idp_ui, "step", IDP_INT);
*step = item ? IDP_Int(item) : 1;
return;
const IDProperty *idprop = (IDProperty *)prop;
if (idprop->ui_data) {
IDPropertyUIDataInt *ui_data_int = (IDPropertyUIDataInt *)idprop->ui_data;
*softmin = ui_data_int->soft_min;
*softmax = ui_data_int->soft_max;
*step = ui_data_int->step;
}
else {
*softmin = INT_MIN;
*softmax = INT_MAX;
*step = 1;
}
return;
}
*softmin = iprop->softmin;
@ -1478,20 +1326,17 @@ void RNA_property_float_range(PointerRNA *ptr, PropertyRNA *prop, float *hardmin
float softmin, softmax;
if (prop->magic != RNA_MAGIC) {
/* attempt to get the local ID values */
const IDProperty *idp_ui = rna_idproperty_ui(prop);
if (idp_ui) {
IDProperty *item;
item = IDP_GetPropertyTypeFromGroup(idp_ui, "min", IDP_DOUBLE);
*hardmin = item ? (float)IDP_Double(item) : -FLT_MAX;
item = IDP_GetPropertyTypeFromGroup(idp_ui, "max", IDP_DOUBLE);
*hardmax = item ? (float)IDP_Double(item) : FLT_MAX;
return;
const IDProperty *idprop = (IDProperty *)prop;
if (idprop->ui_data) {
IDPropertyUIDataFloat *ui_data = (IDPropertyUIDataFloat *)idprop->ui_data;
*hardmin = (float)ui_data->min;
*hardmax = (float)ui_data->max;
}
else {
*hardmin = FLT_MIN;
*hardmax = FLT_MAX;
}
return;
}
if (fprop->range) {
@ -1523,26 +1368,21 @@ void RNA_property_float_ui_range(PointerRNA *ptr,
float hardmin, hardmax;
if (prop->magic != RNA_MAGIC) {
/* attempt to get the local ID values */
const IDProperty *idp_ui = rna_idproperty_ui(prop);
if (idp_ui) {
IDProperty *item;
item = IDP_GetPropertyTypeFromGroup(idp_ui, "soft_min", IDP_DOUBLE);
*softmin = item ? (float)IDP_Double(item) : -FLT_MAX;
item = IDP_GetPropertyTypeFromGroup(idp_ui, "soft_max", IDP_DOUBLE);
*softmax = item ? (float)IDP_Double(item) : FLT_MAX;
item = IDP_GetPropertyTypeFromGroup(idp_ui, "step", IDP_DOUBLE);
*step = item ? (float)IDP_Double(item) : 1.0f;
item = IDP_GetPropertyTypeFromGroup(idp_ui, "precision", IDP_DOUBLE);
*precision = item ? (float)IDP_Double(item) : 3.0f;
return;
const IDProperty *idprop = (IDProperty *)prop;
if (idprop->ui_data) {
IDPropertyUIDataFloat *ui_data = (IDPropertyUIDataFloat *)idprop->ui_data;
*softmin = (float)ui_data->soft_min;
*softmax = (float)ui_data->soft_max;
*step = ui_data->step;
*precision = (float)ui_data->precision;
}
else {
*softmin = FLT_MIN;
*softmax = FLT_MAX;
*step = 1.0f;
*precision = 3.0f;
}
return;
}
*softmin = fprop->softmin;
@ -2905,29 +2745,28 @@ int RNA_property_int_get_default(PointerRNA *UNUSED(ptr), PropertyRNA *prop)
IntPropertyRNA *iprop = (IntPropertyRNA *)rna_ensure_property(prop);
if (prop->magic != RNA_MAGIC) {
/* attempt to get the local ID values */
const IDProperty *idp_ui = rna_idproperty_ui(prop);
if (idp_ui) {
IDProperty *item;
item = IDP_GetPropertyTypeFromGroup(idp_ui, "default", IDP_INT);
return item ? IDP_Int(item) : iprop->defaultvalue;
const IDProperty *idprop = (const IDProperty *)prop;
if (idprop->ui_data) {
const IDPropertyUIDataInt *ui_data = (const IDPropertyUIDataInt *)idprop->ui_data;
return ui_data->default_value;
}
}
return iprop->defaultvalue;
}
bool RNA_property_int_set_default(PointerRNA *ptr, PropertyRNA *prop, int value)
bool RNA_property_int_set_default(PropertyRNA *prop, int value)
{
if (value != 0) {
IDPropertyTemplate val = {
.i = value,
};
return rna_idproperty_ui_set_default(ptr, prop, IDP_INT, &val);
if (prop->magic == RNA_MAGIC) {
return false;
}
return rna_idproperty_ui_set_default(ptr, prop, IDP_INT, NULL);
IDProperty *idprop = (IDProperty *)prop;
BLI_assert(idprop->type == IDP_INT);
IDPropertyUIDataInt *ui_data = (IDPropertyUIDataInt *)IDP_ui_data_ensure(idprop);
ui_data->default_value = value;
return true;
}
void RNA_property_int_get_default_array(PointerRNA *ptr, PropertyRNA *prop, int *values)
@ -2940,17 +2779,22 @@ void RNA_property_int_get_default_array(PointerRNA *ptr, PropertyRNA *prop, int
if (prop->magic != RNA_MAGIC) {
int length = rna_ensure_property_array_length(ptr, prop);
const IDProperty *idp_ui = rna_idproperty_ui(prop);
IDProperty *item = idp_ui ? IDP_GetPropertyFromGroup(idp_ui, "default") : NULL;
int defval = (item && item->type == IDP_INT) ? IDP_Int(item) : iprop->defaultvalue;
if (item && item->type == IDP_ARRAY && item->subtype == IDP_INT) {
rna_property_int_fill_default_array_values(
IDP_Array(item), item->len, defval, length, values);
}
else {
rna_property_int_fill_default_array_values(NULL, 0, defval, length, values);
const IDProperty *idprop = (const IDProperty *)prop;
if (idprop->ui_data) {
BLI_assert(idprop->type == IDP_ARRAY);
BLI_assert(idprop->subtype == IDP_INT);
const IDPropertyUIDataInt *ui_data = (const IDPropertyUIDataInt *)idprop->ui_data;
if (ui_data->default_array) {
rna_property_int_fill_default_array_values(ui_data->default_array,
ui_data->default_array_len,
ui_data->default_value,
length,
values);
}
else {
rna_property_int_fill_default_array_values(
NULL, 0, ui_data->default_value, length, values);
}
}
}
else if (prop->arraydimension == 0) {
@ -3066,6 +2910,26 @@ static void rna_property_float_fill_default_array_values(
}
}
/**
* The same logic as #rna_property_float_fill_default_array_values for a double array.
*/
static void rna_property_float_fill_default_array_values_double(const double *default_array,
const int default_array_len,
const double default_value,
const int out_length,
float *r_values)
{
const int array_copy_len = MIN2(out_length, default_array_len);
for (int i = 0; i < array_copy_len; i++) {
r_values[i] = (float)default_array[i];
}
for (int i = array_copy_len; i < out_length; i++) {
r_values[i] = (float)default_value;
}
}
static void rna_property_float_get_default_array_values(PointerRNA *ptr,
FloatPropertyRNA *fprop,
float *r_values)
@ -3268,29 +3132,29 @@ float RNA_property_float_get_default(PointerRNA *UNUSED(ptr), PropertyRNA *prop)
BLI_assert(RNA_property_array_check(prop) == false);
if (prop->magic != RNA_MAGIC) {
/* attempt to get the local ID values */
const IDProperty *idp_ui = rna_idproperty_ui(prop);
if (idp_ui) {
IDProperty *item;
item = IDP_GetPropertyTypeFromGroup(idp_ui, "default", IDP_DOUBLE);
return item ? IDP_Double(item) : fprop->defaultvalue;
const IDProperty *idprop = (const IDProperty *)prop;
if (idprop->ui_data) {
BLI_assert(ELEM(idprop->type, IDP_FLOAT, IDP_DOUBLE));
const IDPropertyUIDataFloat *ui_data = (const IDPropertyUIDataFloat *)idprop->ui_data;
return (float)ui_data->default_value;
}
}
return fprop->defaultvalue;
}
bool RNA_property_float_set_default(PointerRNA *ptr, PropertyRNA *prop, float value)
bool RNA_property_float_set_default(PropertyRNA *prop, float value)
{
if (value != 0) {
IDPropertyTemplate val = {
.d = value,
};
return rna_idproperty_ui_set_default(ptr, prop, IDP_DOUBLE, &val);
if (prop->magic == RNA_MAGIC) {
return false;
}
return rna_idproperty_ui_set_default(ptr, prop, IDP_DOUBLE, NULL);
IDProperty *idprop = (IDProperty *)prop;
BLI_assert(idprop->type == IDP_FLOAT);
IDPropertyUIDataFloat *ui_data = (IDPropertyUIDataFloat *)IDP_ui_data_ensure(idprop);
ui_data->default_value = (double)value;
return true;
}
void RNA_property_float_get_default_array(PointerRNA *ptr, PropertyRNA *prop, float *values)
@ -3303,23 +3167,16 @@ void RNA_property_float_get_default_array(PointerRNA *ptr, PropertyRNA *prop, fl
if (prop->magic != RNA_MAGIC) {
int length = rna_ensure_property_array_length(ptr, prop);
const IDProperty *idp_ui = rna_idproperty_ui(prop);
IDProperty *item = idp_ui ? IDP_GetPropertyFromGroup(idp_ui, "default") : NULL;
float defval = (item && item->type == IDP_DOUBLE) ? IDP_Double(item) : fprop->defaultvalue;
if (item && item->type == IDP_ARRAY && item->subtype == IDP_DOUBLE) {
double *defarr = IDP_Array(item);
for (int i = 0; i < length; i++) {
values[i] = (i < item->len) ? (float)defarr[i] : defval;
}
}
else if (item && item->type == IDP_ARRAY && item->subtype == IDP_FLOAT) {
rna_property_float_fill_default_array_values(
IDP_Array(item), item->len, defval, length, values);
}
else {
rna_property_float_fill_default_array_values(NULL, 0, defval, length, values);
const IDProperty *idprop = (const IDProperty *)prop;
if (idprop->ui_data) {
BLI_assert(idprop->type == IDP_ARRAY);
BLI_assert(ELEM(idprop->subtype, IDP_FLOAT, IDP_DOUBLE));
const IDPropertyUIDataFloat *ui_data = (const IDPropertyUIDataFloat *)idprop->ui_data;
rna_property_float_fill_default_array_values_double(ui_data->default_array,
ui_data->default_array_len,
ui_data->default_value,
length,
values);
}
}
else if (prop->arraydimension == 0) {
@ -3510,22 +3367,17 @@ void RNA_property_string_set_bytes(PointerRNA *ptr, PropertyRNA *prop, const cha
}
}
void RNA_property_string_get_default(PointerRNA *UNUSED(ptr), PropertyRNA *prop, char *value)
void RNA_property_string_get_default(PropertyRNA *prop, char *value, const int max_len)
{
StringPropertyRNA *sprop = (StringPropertyRNA *)rna_ensure_property(prop);
if (prop->magic != RNA_MAGIC) {
/* attempt to get the local ID values */
const IDProperty *idp_ui = rna_idproperty_ui(prop);
if (idp_ui) {
IDProperty *item;
item = IDP_GetPropertyTypeFromGroup(idp_ui, "default", IDP_STRING);
if (item) {
strcpy(value, IDP_String(item));
return;
}
const IDProperty *idprop = (const IDProperty *)prop;
if (idprop->ui_data) {
BLI_assert(idprop->type == IDP_STRING);
const IDPropertyUIDataString *ui_data = (const IDPropertyUIDataString *)idprop->ui_data;
BLI_strncpy(value, ui_data->default_value, max_len);
return;
}
strcpy(value, "");
@ -3554,7 +3406,7 @@ char *RNA_property_string_get_default_alloc(
buf = MEM_callocN(sizeof(char) * (length + 1), __func__);
}
RNA_property_string_get_default(ptr, prop, buf);
RNA_property_string_get_default(prop, buf, length + 1);
if (r_len) {
*r_len = length;
@ -3569,15 +3421,12 @@ int RNA_property_string_default_length(PointerRNA *UNUSED(ptr), PropertyRNA *pro
StringPropertyRNA *sprop = (StringPropertyRNA *)rna_ensure_property(prop);
if (prop->magic != RNA_MAGIC) {
/* attempt to get the local ID values */
const IDProperty *idp_ui = rna_idproperty_ui(prop);
if (idp_ui) {
IDProperty *item;
item = IDP_GetPropertyTypeFromGroup(idp_ui, "default", IDP_STRING);
if (item) {
return strlen(IDP_String(item));
const IDProperty *idprop = (const IDProperty *)prop;
if (idprop->ui_data) {
BLI_assert(idprop->type == IDP_STRING);
const IDPropertyUIDataString *ui_data = (const IDPropertyUIDataString *)idprop->ui_data;
if (ui_data->default_value != NULL) {
return strlen(ui_data->default_value);
}
}
@ -8200,12 +8049,12 @@ bool RNA_property_assign_default(PointerRNA *ptr, PropertyRNA *prop)
switch (RNA_property_type(prop)) {
case PROP_INT: {
int value = RNA_property_int_get(ptr, prop);
return RNA_property_int_set_default(ptr, prop, value);
return RNA_property_int_set_default(prop, value);
}
case PROP_FLOAT: {
float value = RNA_property_float_get(ptr, prop);
return RNA_property_float_set_default(ptr, prop, value);
return RNA_property_float_set_default(prop, value);
}
default:

View File

@ -42,9 +42,6 @@ struct bContext;
typedef struct IDProperty IDProperty;
/* store local properties here */
#define RNA_IDP_UI "_RNA_UI"
/* Function Callbacks */
typedef void (*UpdateFunc)(struct Main *main, struct Scene *scene, struct PointerRNA *ptr);

View File

@ -289,348 +289,178 @@ static bool logging_enabled(const ModifierEvalContext *ctx)
return true;
}
/**
* This code is responsible for creating the new property and also creating the group of
* properties in the prop_ui_container group for the UI info, the mapping for which is
* scattered about in RNA_access.c.
*
* TODO(Hans): Codify this with some sort of table or refactor IDProperty use in RNA_access.c.
*/
struct SocketPropertyType {
/* Create the actual property used to store the data for the modifier. */
IDProperty *(*create_prop)(const bNodeSocket &socket, const char *name);
/* Reused to build the "soft_min" property too. */
IDProperty *(*create_min_ui_prop)(const bNodeSocket &socket, const char *name);
/* Reused to build the "soft_max" property too. */
IDProperty *(*create_max_ui_prop)(const bNodeSocket &socket, const char *name);
/* This uses the same values as #create_prop, but sometimes the type is different, so it can't
* be the same function. */
IDProperty *(*create_default_ui_prop)(const bNodeSocket &socket, const char *name);
PropertyType (*rna_subtype_get)(const bNodeSocket &socket);
bool (*is_correct_type)(const IDProperty &property);
void (*init_cpp_value)(const IDProperty &property, void *r_value);
};
static IDProperty *socket_add_property(IDProperty *settings_prop_group,
IDProperty *ui_container,
const SocketPropertyType &property_type,
const bNodeSocket &socket)
static IDProperty *id_property_create_from_socket(const bNodeSocket &socket)
{
const char *new_prop_name = socket.identifier;
/* Add the property actually storing the data to the modifier's group. */
IDProperty *prop = property_type.create_prop(socket, new_prop_name);
IDP_AddToGroup(settings_prop_group, prop);
prop->flag |= IDP_FLAG_OVERRIDABLE_LIBRARY;
/* Make the group in the UI container group to hold the property's UI settings. */
IDProperty *prop_ui_group;
{
IDPropertyTemplate idprop = {0};
prop_ui_group = IDP_New(IDP_GROUP, &idprop, new_prop_name);
IDP_AddToGroup(ui_container, prop_ui_group);
}
/* Set property description (tooltip). */
IDPropertyTemplate property_description_template;
property_description_template.string.str = socket.description;
property_description_template.string.len = BLI_strnlen(socket.description, MAX_NAME) + 1;
property_description_template.string.subtype = IDP_STRING_SUB_UTF8;
IDProperty *description = IDP_New(IDP_STRING, &property_description_template, "description");
IDP_AddToGroup(prop_ui_group, description);
/* Create the properties for the socket's UI settings. */
if (property_type.create_min_ui_prop != nullptr) {
IDP_AddToGroup(prop_ui_group, property_type.create_min_ui_prop(socket, "min"));
IDP_AddToGroup(prop_ui_group, property_type.create_min_ui_prop(socket, "soft_min"));
}
if (property_type.create_max_ui_prop != nullptr) {
IDP_AddToGroup(prop_ui_group, property_type.create_max_ui_prop(socket, "max"));
IDP_AddToGroup(prop_ui_group, property_type.create_max_ui_prop(socket, "soft_max"));
}
if (property_type.create_default_ui_prop != nullptr) {
IDP_AddToGroup(prop_ui_group, property_type.create_default_ui_prop(socket, "default"));
}
if (property_type.rna_subtype_get != nullptr) {
const char *subtype_identifier = nullptr;
RNA_enum_identifier(rna_enum_property_subtype_items,
property_type.rna_subtype_get(socket),
&subtype_identifier);
if (subtype_identifier != nullptr) {
IDPropertyTemplate idprop = {0};
idprop.string.str = subtype_identifier;
idprop.string.len = BLI_strnlen(subtype_identifier, MAX_NAME) + 1;
IDP_AddToGroup(prop_ui_group, IDP_New(IDP_STRING, &idprop, "subtype"));
}
}
return prop;
}
static const SocketPropertyType *get_socket_property_type(const bNodeSocket &bsocket)
{
switch (bsocket.type) {
switch (socket.type) {
case SOCK_FLOAT: {
static const SocketPropertyType float_type = {
[](const bNodeSocket &socket, const char *name) {
bNodeSocketValueFloat *value = (bNodeSocketValueFloat *)socket.default_value;
IDPropertyTemplate idprop = {0};
idprop.f = value->value;
return IDP_New(IDP_FLOAT, &idprop, name);
},
[](const bNodeSocket &socket, const char *name) {
bNodeSocketValueFloat *value = (bNodeSocketValueFloat *)socket.default_value;
IDPropertyTemplate idprop = {0};
idprop.d = value->min;
return IDP_New(IDP_DOUBLE, &idprop, name);
},
[](const bNodeSocket &socket, const char *name) {
bNodeSocketValueFloat *value = (bNodeSocketValueFloat *)socket.default_value;
IDPropertyTemplate idprop = {0};
idprop.d = value->max;
return IDP_New(IDP_DOUBLE, &idprop, name);
},
[](const bNodeSocket &socket, const char *name) {
bNodeSocketValueFloat *value = (bNodeSocketValueFloat *)socket.default_value;
IDPropertyTemplate idprop = {0};
idprop.d = value->value;
return IDP_New(IDP_DOUBLE, &idprop, name);
},
[](const bNodeSocket &socket) {
return (PropertyType)((bNodeSocketValueFloat *)socket.default_value)->subtype;
},
[](const IDProperty &property) { return ELEM(property.type, IDP_FLOAT, IDP_DOUBLE); },
[](const IDProperty &property, void *r_value) {
if (property.type == IDP_FLOAT) {
*(float *)r_value = IDP_Float(&property);
}
else if (property.type == IDP_DOUBLE) {
*(float *)r_value = (float)IDP_Double(&property);
}
},
};
return &float_type;
bNodeSocketValueFloat *value = (bNodeSocketValueFloat *)socket.default_value;
IDPropertyTemplate idprop = {0};
idprop.f = value->value;
IDProperty *property = IDP_New(IDP_FLOAT, &idprop, socket.identifier);
IDPropertyUIDataFloat *ui_data = (IDPropertyUIDataFloat *)IDP_ui_data_ensure(property);
ui_data->base.rna_subtype = value->subtype;
ui_data->min = ui_data->soft_min = (double)value->min;
ui_data->max = ui_data->soft_max = (double)value->max;
ui_data->default_value = value->value;
return property;
}
case SOCK_INT: {
static const SocketPropertyType int_type = {
[](const bNodeSocket &socket, const char *name) {
bNodeSocketValueInt *value = (bNodeSocketValueInt *)socket.default_value;
IDPropertyTemplate idprop = {0};
idprop.i = value->value;
return IDP_New(IDP_INT, &idprop, name);
},
[](const bNodeSocket &socket, const char *name) {
bNodeSocketValueInt *value = (bNodeSocketValueInt *)socket.default_value;
IDPropertyTemplate idprop = {0};
idprop.i = value->min;
return IDP_New(IDP_INT, &idprop, name);
},
[](const bNodeSocket &socket, const char *name) {
bNodeSocketValueInt *value = (bNodeSocketValueInt *)socket.default_value;
IDPropertyTemplate idprop = {0};
idprop.i = value->max;
return IDP_New(IDP_INT, &idprop, name);
},
[](const bNodeSocket &socket, const char *name) {
bNodeSocketValueInt *value = (bNodeSocketValueInt *)socket.default_value;
IDPropertyTemplate idprop = {0};
idprop.i = value->value;
return IDP_New(IDP_INT, &idprop, name);
},
[](const bNodeSocket &socket) {
return (PropertyType)((bNodeSocketValueInt *)socket.default_value)->subtype;
},
[](const IDProperty &property) { return property.type == IDP_INT; },
[](const IDProperty &property, void *r_value) { *(int *)r_value = IDP_Int(&property); },
};
return &int_type;
bNodeSocketValueInt *value = (bNodeSocketValueInt *)socket.default_value;
IDPropertyTemplate idprop = {0};
idprop.i = value->value;
IDProperty *property = IDP_New(IDP_INT, &idprop, socket.identifier);
IDPropertyUIDataInt *ui_data = (IDPropertyUIDataInt *)IDP_ui_data_ensure(property);
ui_data->base.rna_subtype = value->subtype;
ui_data->min = ui_data->soft_min = value->min;
ui_data->max = ui_data->soft_max = value->max;
ui_data->default_value = value->value;
return property;
}
case SOCK_VECTOR: {
static const SocketPropertyType vector_type = {
[](const bNodeSocket &socket, const char *name) {
bNodeSocketValueVector *value = (bNodeSocketValueVector *)socket.default_value;
IDPropertyTemplate idprop = {0};
idprop.array.len = 3;
idprop.array.type = IDP_FLOAT;
IDProperty *property = IDP_New(IDP_ARRAY, &idprop, name);
copy_v3_v3((float *)IDP_Array(property), value->value);
return property;
},
[](const bNodeSocket &socket, const char *name) {
bNodeSocketValueVector *value = (bNodeSocketValueVector *)socket.default_value;
IDPropertyTemplate idprop = {0};
idprop.d = value->min;
return IDP_New(IDP_DOUBLE, &idprop, name);
},
[](const bNodeSocket &socket, const char *name) {
bNodeSocketValueVector *value = (bNodeSocketValueVector *)socket.default_value;
IDPropertyTemplate idprop = {0};
idprop.d = value->max;
return IDP_New(IDP_DOUBLE, &idprop, name);
},
[](const bNodeSocket &socket, const char *name) {
bNodeSocketValueVector *value = (bNodeSocketValueVector *)socket.default_value;
IDPropertyTemplate idprop = {0};
idprop.array.len = 3;
idprop.array.type = IDP_FLOAT;
IDProperty *property = IDP_New(IDP_ARRAY, &idprop, name);
copy_v3_v3((float *)IDP_Array(property), value->value);
return property;
},
[](const bNodeSocket &socket) {
return (PropertyType)((bNodeSocketValueVector *)socket.default_value)->subtype;
},
[](const IDProperty &property) {
return property.type == IDP_ARRAY && property.subtype == IDP_FLOAT &&
property.len == 3;
},
[](const IDProperty &property, void *r_value) {
copy_v3_v3((float *)r_value, (const float *)IDP_Array(&property));
},
};
return &vector_type;
bNodeSocketValueVector *value = (bNodeSocketValueVector *)socket.default_value;
IDPropertyTemplate idprop = {0};
idprop.array.len = 3;
idprop.array.type = IDP_FLOAT;
IDProperty *property = IDP_New(IDP_ARRAY, &idprop, socket.identifier);
copy_v3_v3((float *)IDP_Array(property), value->value);
IDPropertyUIDataFloat *ui_data = (IDPropertyUIDataFloat *)IDP_ui_data_ensure(property);
ui_data->base.rna_subtype = value->subtype;
ui_data->min = ui_data->soft_min = (double)value->min;
ui_data->max = ui_data->soft_max = (double)value->max;
ui_data->default_array = (double *)MEM_mallocN(sizeof(double[3]), "mod_prop_default");
ui_data->default_array_len = 3;
for (int i = 3; i < 3; i++) {
ui_data->default_array[i] = (double)value->value[i];
}
return property;
}
case SOCK_BOOLEAN: {
static const SocketPropertyType boolean_type = {
[](const bNodeSocket &socket, const char *name) {
bNodeSocketValueBoolean *value = (bNodeSocketValueBoolean *)socket.default_value;
IDPropertyTemplate idprop = {0};
idprop.i = value->value != 0;
return IDP_New(IDP_INT, &idprop, name);
},
[](const bNodeSocket &UNUSED(socket), const char *name) {
IDPropertyTemplate idprop = {0};
idprop.i = 0;
return IDP_New(IDP_INT, &idprop, name);
},
[](const bNodeSocket &UNUSED(socket), const char *name) {
IDPropertyTemplate idprop = {0};
idprop.i = 1;
return IDP_New(IDP_INT, &idprop, name);
},
[](const bNodeSocket &socket, const char *name) {
bNodeSocketValueBoolean *value = (bNodeSocketValueBoolean *)socket.default_value;
IDPropertyTemplate idprop = {0};
idprop.i = value->value != 0;
return IDP_New(IDP_INT, &idprop, name);
},
nullptr,
[](const IDProperty &property) { return property.type == IDP_INT; },
[](const IDProperty &property, void *r_value) {
*(bool *)r_value = IDP_Int(&property) != 0;
},
};
return &boolean_type;
bNodeSocketValueBoolean *value = (bNodeSocketValueBoolean *)socket.default_value;
IDPropertyTemplate idprop = {0};
idprop.i = value->value != 0;
IDProperty *property = IDP_New(IDP_INT, &idprop, socket.identifier);
IDPropertyUIDataInt *ui_data = (IDPropertyUIDataInt *)IDP_ui_data_ensure(property);
ui_data->min = ui_data->soft_min = 0;
ui_data->max = ui_data->soft_max = 1;
ui_data->default_value = value->value != 0;
return property;
}
case SOCK_STRING: {
static const SocketPropertyType string_type = {
[](const bNodeSocket &socket, const char *name) {
bNodeSocketValueString *value = (bNodeSocketValueString *)socket.default_value;
return IDP_NewString(
value->value, name, BLI_strnlen(value->value, sizeof(value->value)) + 1);
},
nullptr,
nullptr,
[](const bNodeSocket &socket, const char *name) {
bNodeSocketValueString *value = (bNodeSocketValueString *)socket.default_value;
return IDP_NewString(
value->value, name, BLI_strnlen(value->value, sizeof(value->value)) + 1);
},
nullptr,
[](const IDProperty &property) { return property.type == IDP_STRING; },
[](const IDProperty &property, void *r_value) {
new (r_value) std::string(IDP_String(&property));
},
};
return &string_type;
bNodeSocketValueString *value = (bNodeSocketValueString *)socket.default_value;
IDProperty *property = IDP_NewString(
value->value, socket.identifier, BLI_strnlen(value->value, sizeof(value->value)) + 1);
IDPropertyUIDataString *ui_data = (IDPropertyUIDataString *)IDP_ui_data_ensure(property);
ui_data->default_value = BLI_strdup(value->value);
return property;
}
case SOCK_OBJECT: {
static const SocketPropertyType object_type = {
[](const bNodeSocket &socket, const char *name) {
bNodeSocketValueObject *value = (bNodeSocketValueObject *)socket.default_value;
IDPropertyTemplate idprop = {0};
idprop.id = (ID *)value->value;
return IDP_New(IDP_ID, &idprop, name);
},
nullptr,
nullptr,
nullptr,
nullptr,
[](const IDProperty &property) { return property.type == IDP_ID; },
[](const IDProperty &property, void *r_value) {
ID *id = IDP_Id(&property);
Object *object = (id && GS(id->name) == ID_OB) ? (Object *)id : nullptr;
*(Object **)r_value = object;
},
};
return &object_type;
bNodeSocketValueObject *value = (bNodeSocketValueObject *)socket.default_value;
IDPropertyTemplate idprop = {0};
idprop.id = (ID *)value->value;
return IDP_New(IDP_ID, &idprop, socket.identifier);
}
case SOCK_COLLECTION: {
static const SocketPropertyType collection_type = {
[](const bNodeSocket &socket, const char *name) {
bNodeSocketValueCollection *value = (bNodeSocketValueCollection *)socket.default_value;
IDPropertyTemplate idprop = {0};
idprop.id = (ID *)value->value;
return IDP_New(IDP_ID, &idprop, name);
},
nullptr,
nullptr,
nullptr,
nullptr,
[](const IDProperty &property) { return property.type == IDP_ID; },
[](const IDProperty &property, void *r_value) {
ID *id = IDP_Id(&property);
Collection *collection = (id && GS(id->name) == ID_GR) ? (Collection *)id : nullptr;
*(Collection **)r_value = collection;
},
};
return &collection_type;
bNodeSocketValueCollection *value = (bNodeSocketValueCollection *)socket.default_value;
IDPropertyTemplate idprop = {0};
idprop.id = (ID *)value->value;
return IDP_New(IDP_ID, &idprop, socket.identifier);
}
case SOCK_TEXTURE: {
static const SocketPropertyType texture_type = {
[](const bNodeSocket &socket, const char *name) {
bNodeSocketValueTexture *value = (bNodeSocketValueTexture *)socket.default_value;
IDPropertyTemplate idprop = {0};
idprop.id = (ID *)value->value;
return IDP_New(IDP_ID, &idprop, name);
},
nullptr,
nullptr,
nullptr,
nullptr,
[](const IDProperty &property) { return property.type == IDP_ID; },
[](const IDProperty &property, void *r_value) {
ID *id = IDP_Id(&property);
Tex *texture = (id && GS(id->name) == ID_TE) ? (Tex *)id : nullptr;
*(Tex **)r_value = texture;
},
};
return &texture_type;
bNodeSocketValueTexture *value = (bNodeSocketValueTexture *)socket.default_value;
IDPropertyTemplate idprop = {0};
idprop.id = (ID *)value->value;
return IDP_New(IDP_ID, &idprop, socket.identifier);
}
case SOCK_MATERIAL: {
static const SocketPropertyType material_type = {
[](const bNodeSocket &socket, const char *name) {
bNodeSocketValueMaterial *value = (bNodeSocketValueMaterial *)socket.default_value;
IDPropertyTemplate idprop = {0};
idprop.id = (ID *)value->value;
return IDP_New(IDP_ID, &idprop, name);
},
nullptr,
nullptr,
nullptr,
nullptr,
[](const IDProperty &property) { return property.type == IDP_ID; },
[](const IDProperty &property, void *r_value) {
ID *id = IDP_Id(&property);
Material *material = (id && GS(id->name) == ID_MA) ? (Material *)id : nullptr;
*(Material **)r_value = material;
},
};
return &material_type;
bNodeSocketValueMaterial *value = (bNodeSocketValueMaterial *)socket.default_value;
IDPropertyTemplate idprop = {0};
idprop.id = (ID *)value->value;
return IDP_New(IDP_ID, &idprop, socket.identifier);
}
}
return nullptr;
}
static bool id_property_type_matches_socket(const bNodeSocket &socket, const IDProperty &property)
{
switch (socket.type) {
case SOCK_FLOAT:
return ELEM(property.type, IDP_FLOAT, IDP_DOUBLE);
case SOCK_INT:
return property.type == IDP_INT;
case SOCK_VECTOR:
return property.type == IDP_ARRAY && property.subtype == IDP_FLOAT && property.len == 3;
case SOCK_BOOLEAN:
return property.type == IDP_INT;
case SOCK_STRING:
return property.type == IDP_STRING;
case SOCK_OBJECT:
case SOCK_COLLECTION:
case SOCK_TEXTURE:
case SOCK_MATERIAL:
return property.type == IDP_ID;
}
BLI_assert_unreachable();
return false;
}
static void init_socket_cpp_value_from_property(const IDProperty &property,
const eNodeSocketDatatype socket_value_type,
void *r_value)
{
switch (socket_value_type) {
case SOCK_FLOAT: {
if (property.type == IDP_FLOAT) {
*(float *)r_value = IDP_Float(&property);
}
else if (property.type == IDP_DOUBLE) {
*(float *)r_value = (float)IDP_Double(&property);
}
break;
}
case SOCK_INT: {
*(int *)r_value = IDP_Int(&property);
break;
}
case SOCK_VECTOR: {
copy_v3_v3((float *)r_value, (const float *)IDP_Array(&property));
break;
}
case SOCK_BOOLEAN: {
*(bool *)r_value = IDP_Int(&property) != 0;
break;
}
case SOCK_STRING: {
new (r_value) std::string(IDP_String(&property));
break;
}
case SOCK_OBJECT: {
ID *id = IDP_Id(&property);
Object *object = (id && GS(id->name) == ID_OB) ? (Object *)id : nullptr;
*(Object **)r_value = object;
break;
}
case SOCK_COLLECTION: {
ID *id = IDP_Id(&property);
Collection *collection = (id && GS(id->name) == ID_GR) ? (Collection *)id : nullptr;
*(Collection **)r_value = collection;
break;
}
case SOCK_TEXTURE: {
ID *id = IDP_Id(&property);
Tex *texture = (id && GS(id->name) == ID_TE) ? (Tex *)id : nullptr;
*(Tex **)r_value = texture;
break;
}
case SOCK_MATERIAL: {
ID *id = IDP_Id(&property);
Material *material = (id && GS(id->name) == ID_MA) ? (Material *)id : nullptr;
*(Material **)r_value = material;
break;
}
default: {
return nullptr;
BLI_assert_unreachable();
break;
}
}
}
@ -647,32 +477,39 @@ void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd)
}
IDProperty *old_properties = nmd->settings.properties;
{
IDPropertyTemplate idprop = {0};
nmd->settings.properties = IDP_New(IDP_GROUP, &idprop, "Nodes Modifier Settings");
}
IDProperty *ui_container_group;
{
IDPropertyTemplate idprop = {0};
ui_container_group = IDP_New(IDP_GROUP, &idprop, "_RNA_UI");
IDP_AddToGroup(nmd->settings.properties, ui_container_group);
}
LISTBASE_FOREACH (bNodeSocket *, socket, &nmd->node_group->inputs) {
const SocketPropertyType *property_type = get_socket_property_type(*socket);
if (property_type == nullptr) {
IDProperty *new_prop = id_property_create_from_socket(*socket);
if (new_prop == nullptr) {
/* Out of the set of supported input sockets, only
* geometry sockets aren't added to the modifier. */
BLI_assert(socket->type == SOCK_GEOMETRY);
continue;
}
IDProperty *new_prop = socket_add_property(
nmd->settings.properties, ui_container_group, *property_type, *socket);
new_prop->flag |= IDP_FLAG_OVERRIDABLE_LIBRARY;
if (socket->description[0] != '\0') {
IDPropertyUIData *ui_data = IDP_ui_data_ensure(new_prop);
ui_data->description = BLI_strdup(socket->description);
}
IDP_AddToGroup(nmd->settings.properties, new_prop);
if (old_properties != nullptr) {
IDProperty *old_prop = IDP_GetPropertyFromGroup(old_properties, socket->identifier);
if (old_prop != nullptr && property_type->is_correct_type(*old_prop)) {
if (old_prop != nullptr && id_property_type_matches_socket(*socket, *old_prop)) {
/* #IDP_CopyPropertyContent replaces the UI data as well, which we don't (we only
* want to replace the values). So release it temporarily and replace it after. */
IDPropertyUIData *ui_data = new_prop->ui_data;
new_prop->ui_data = nullptr;
IDP_CopyPropertyContent(new_prop, old_prop);
if (new_prop->ui_data != nullptr) {
IDP_ui_data_free(new_prop);
}
new_prop->ui_data = ui_data;
}
}
}
@ -713,14 +550,8 @@ void MOD_nodes_init(Main *bmain, NodesModifierData *nmd)
static void initialize_group_input(NodesModifierData &nmd,
const bNodeSocket &socket,
const CPPType &cpp_type,
void *r_value)
{
const SocketPropertyType *property_type = get_socket_property_type(socket);
if (property_type == nullptr) {
cpp_type.copy_construct(cpp_type.default_value(), r_value);
return;
}
if (nmd.settings.properties == nullptr) {
socket.typeinfo->get_geometry_nodes_cpp_value(socket, r_value);
return;
@ -731,11 +562,13 @@ static void initialize_group_input(NodesModifierData &nmd,
socket.typeinfo->get_geometry_nodes_cpp_value(socket, r_value);
return;
}
if (!property_type->is_correct_type(*property)) {
if (!id_property_type_matches_socket(socket, *property)) {
socket.typeinfo->get_geometry_nodes_cpp_value(socket, r_value);
return;
}
property_type->init_cpp_value(*property, r_value);
init_socket_cpp_value_from_property(
*property, static_cast<eNodeSocketDatatype>(socket.type), r_value);
}
static Vector<SpaceSpreadsheet *> find_spreadsheet_editors(Main *bmain)
@ -886,7 +719,7 @@ static GeometrySet compute_geometry(const DerivedNodeTree &tree,
for (const OutputSocketRef *socket : remaining_input_sockets) {
const CPPType &cpp_type = *socket->typeinfo()->get_geometry_nodes_cpp_type();
void *value_in = allocator.allocate(cpp_type.size(), cpp_type.alignment());
initialize_group_input(*nmd, *socket->bsocket(), cpp_type, value_in);
initialize_group_input(*nmd, *socket->bsocket(), value_in);
group_inputs.add_new({root_context, socket}, {cpp_type, value_in});
}
}
@ -954,8 +787,7 @@ static void check_property_socket_sync(const Object *ob, ModifierData *md)
continue;
}
const SocketPropertyType *property_type = get_socket_property_type(*socket);
if (!property_type->is_correct_type(*property)) {
if (!id_property_type_matches_socket(*socket, *property)) {
BKE_modifier_set_error(
ob, md, "Property type does not match input socket \"(%s)\"", socket->name);
continue;
@ -1051,17 +883,12 @@ static void draw_property_for_socket(uiLayout *layout,
const IDProperty *modifier_props,
const bNodeSocket &socket)
{
const SocketPropertyType *property_type = get_socket_property_type(socket);
if (property_type == nullptr) {
return;
}
/* The property should be created in #MOD_nodes_update_interface with the correct type. */
IDProperty *property = IDP_GetPropertyFromGroup(modifier_props, socket.identifier);
/* IDProperties can be removed with python, so there could be a situation where
* there isn't a property for a socket or it doesn't have the correct type. */
if (property == nullptr || !property_type->is_correct_type(*property)) {
if (property == nullptr || !id_property_type_matches_socket(socket, *property)) {
return;
}

View File

@ -21,6 +21,7 @@ set(INC
../../blenlib
../../gpu
../../makesdna
../../makesrna
../../../../intern/glew-mx
../../../../intern/guardedalloc
)
@ -36,6 +37,7 @@ set(SRC
blf_py_api.c
bpy_threads.c
idprop_py_api.c
idprop_py_ui_api.c
imbuf_py_api.c
py_capi_utils.c
@ -43,6 +45,7 @@ set(SRC
bl_math_py_api.h
blf_py_api.h
idprop_py_api.h
idprop_py_ui_api.h
imbuf_py_api.h
py_capi_utils.h

View File

@ -25,6 +25,7 @@
#include "BLI_utildefines.h"
#include "idprop_py_api.h"
#include "idprop_py_ui_api.h"
#include "BKE_idprop.h"
@ -2135,6 +2136,7 @@ static PyObject *BPyInit_idprop_types(void)
submodule = PyModule_Create(&IDProp_types_module_def);
IDProp_Init_Types();
IDPropertyUIData_Init_Types();
/* bmesh_py_types.c */
PyModule_AddType(submodule, &BPy_IDGroup_Type);

View File

@ -0,0 +1,734 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup pygen
*/
#include <Python.h>
#include "MEM_guardedalloc.h"
#include "BLI_string.h"
#include "BLI_utildefines.h"
#include "idprop_py_ui_api.h"
#include "BKE_idprop.h"
#include "DNA_ID.h"
#include "RNA_access.h"
#include "RNA_enum_types.h"
#include "../intern/bpy_rna.h"
#define USE_STRING_COERCE
#ifdef USE_STRING_COERCE
# include "py_capi_utils.h"
#endif
#include "python_utildefines.h"
/* -------------------------------------------------------------------- */
/** \name UI Data Update
* \{ */
static bool args_contain_key(PyObject *kwargs, const char *name)
{
PyObject *py_key = PyUnicode_FromString(name);
const bool result = PyDict_Contains(kwargs, py_key) == 1;
Py_DECREF(py_key);
return result;
}
/**
* \return False when parsing fails, in which case caller should return NULL.
*/
static bool idprop_ui_data_update_base(IDPropertyUIData *ui_data,
const char *rna_subtype,
const char *description)
{
if (rna_subtype != NULL) {
if (pyrna_enum_value_from_id(rna_enum_property_subtype_items,
rna_subtype,
&ui_data->rna_subtype,
"IDPropertyUIManager.update") == -1) {
return false;
}
}
if (description != NULL) {
ui_data->description = BLI_strdup(description);
}
return true;
}
/**
* \note The default value needs special handling because for array IDProperties it can
* be a single value or an array, but for non-array properties it can only be a value.
*/
static bool idprop_ui_data_update_int_default(IDProperty *idprop,
IDPropertyUIDataInt *ui_data,
PyObject *default_value)
{
if (PySequence_Check(default_value)) {
if (idprop->type != IDP_ARRAY) {
PyErr_SetString(PyExc_TypeError, "Only array properties can have array default values");
return false;
}
Py_ssize_t len = PySequence_Size(default_value);
int *new_default_array = (int *)MEM_malloc_arrayN(len, sizeof(int), __func__);
if (PyC_AsArray(
new_default_array, sizeof(int), default_value, len, &PyLong_Type, "ui_data_update") ==
-1) {
MEM_freeN(new_default_array);
return false;
}
ui_data->default_array_len = len;
ui_data->default_array = new_default_array;
}
else {
const int value = PyC_Long_AsI32(default_value);
if ((value == -1) && PyErr_Occurred()) {
PyErr_SetString(PyExc_ValueError, "Error converting \"default\" argument to integer");
return false;
}
ui_data->default_value = value;
}
return true;
}
/**
* \return False when parsing fails, in which case caller should return NULL.
*/
static bool idprop_ui_data_update_int(IDProperty *idprop, PyObject *args, PyObject *kwargs)
{
const char *rna_subtype = NULL;
const char *description = NULL;
int min, max, soft_min, soft_max, step;
PyObject *default_value = NULL;
const char *kwlist[] = {
"min", "max", "soft_min", "soft_max", "step", "default", "subtype", "description", NULL};
if (!PyArg_ParseTupleAndKeywords(args,
kwargs,
"|$iiiiiOzz:update",
(char **)kwlist,
&min,
&max,
&soft_min,
&soft_max,
&step,
&default_value,
&rna_subtype,
&description)) {
return false;
}
/* Write to a temporary copy of the UI data in case some part of the parsing fails. */
IDPropertyUIDataInt *ui_data_orig = (IDPropertyUIDataInt *)idprop->ui_data;
IDPropertyUIDataInt ui_data = *ui_data_orig;
if (!idprop_ui_data_update_base(&ui_data.base, rna_subtype, description)) {
IDP_ui_data_free_unique_contents(&ui_data.base, IDP_ui_data_type(idprop), &ui_data_orig->base);
return false;
}
if (args_contain_key(kwargs, "min")) {
ui_data.min = min;
ui_data.soft_min = MAX2(ui_data.soft_min, ui_data.min);
ui_data.max = MAX2(ui_data.min, ui_data.max);
}
if (args_contain_key(kwargs, "max")) {
ui_data.max = max;
ui_data.soft_max = MIN2(ui_data.soft_max, ui_data.max);
ui_data.min = MIN2(ui_data.min, ui_data.max);
}
if (args_contain_key(kwargs, "soft_min")) {
ui_data.soft_min = soft_min;
ui_data.soft_min = MAX2(ui_data.soft_min, ui_data.min);
ui_data.soft_max = MAX2(ui_data.soft_min, ui_data.soft_max);
}
if (args_contain_key(kwargs, "soft_max")) {
ui_data.soft_max = soft_max;
ui_data.soft_max = MIN2(ui_data.soft_max, ui_data.max);
ui_data.soft_min = MIN2(ui_data.soft_min, ui_data.soft_max);
}
if (args_contain_key(kwargs, "step")) {
ui_data.step = step;
}
if (!ELEM(default_value, NULL, Py_None)) {
if (!idprop_ui_data_update_int_default(idprop, &ui_data, default_value)) {
IDP_ui_data_free_unique_contents(
&ui_data.base, IDP_ui_data_type(idprop), &ui_data_orig->base);
return false;
}
}
/* Write back to the proeprty's UI data. */
IDP_ui_data_free_unique_contents(&ui_data_orig->base, IDP_ui_data_type(idprop), &ui_data.base);
*ui_data_orig = ui_data;
return true;
}
/**
* \note The default value needs special handling because for array IDProperties it can
* be a single value or an array, but for non-array properties it can only be a value.
*/
static bool idprop_ui_data_update_float_default(IDProperty *idprop,
IDPropertyUIDataFloat *ui_data,
PyObject *default_value)
{
if (PySequence_Check(default_value)) {
if (idprop->type != IDP_ARRAY) {
PyErr_SetString(PyExc_TypeError, "Only array properties can have array default values");
return false;
}
Py_ssize_t len = PySequence_Size(default_value);
double *new_default_array = (double *)MEM_malloc_arrayN(len, sizeof(double), __func__);
if (PyC_AsArray(new_default_array,
sizeof(double),
default_value,
len,
&PyFloat_Type,
"ui_data_update") == -1) {
MEM_freeN(new_default_array);
return false;
}
ui_data->default_array_len = len;
ui_data->default_array = new_default_array;
}
else {
const double value = PyFloat_AsDouble(default_value);
if ((value == -1.0) && PyErr_Occurred()) {
PyErr_SetString(PyExc_ValueError, "Error converting \"default\" argument to double");
return false;
}
ui_data->default_value = value;
}
return true;
}
/**
* \return False when parsing fails, in which case caller should return NULL.
*/
static bool idprop_ui_data_update_float(IDProperty *idprop, PyObject *args, PyObject *kwargs)
{
const char *rna_subtype = NULL;
const char *description = NULL;
int precision;
double min, max, soft_min, soft_max, step;
PyObject *default_value = NULL;
const char *kwlist[] = {"min",
"max",
"soft_min",
"soft_max",
"step",
"precision",
"default",
"subtype",
"description",
NULL};
if (!PyArg_ParseTupleAndKeywords(args,
kwargs,
"|$dddddiOzz:update",
(char **)kwlist,
&min,
&max,
&soft_min,
&soft_max,
&step,
&precision,
&default_value,
&rna_subtype,
&description)) {
return false;
}
/* Write to a temporary copy of the UI data in case some part of the parsing fails. */
IDPropertyUIDataFloat *ui_data_orig = (IDPropertyUIDataFloat *)idprop->ui_data;
IDPropertyUIDataFloat ui_data = *ui_data_orig;
if (!idprop_ui_data_update_base(&ui_data.base, rna_subtype, description)) {
IDP_ui_data_free_unique_contents(&ui_data.base, IDP_ui_data_type(idprop), &ui_data_orig->base);
return false;
}
if (args_contain_key(kwargs, "min")) {
ui_data.min = min;
ui_data.soft_min = MAX2(ui_data.soft_min, ui_data.min);
ui_data.max = MAX2(ui_data.min, ui_data.max);
}
if (args_contain_key(kwargs, "max")) {
ui_data.max = max;
ui_data.soft_max = MIN2(ui_data.soft_max, ui_data.max);
ui_data.min = MIN2(ui_data.min, ui_data.max);
}
if (args_contain_key(kwargs, "soft_min")) {
ui_data.soft_min = soft_min;
ui_data.soft_min = MAX2(ui_data.soft_min, ui_data.min);
ui_data.soft_max = MAX2(ui_data.soft_min, ui_data.soft_max);
}
if (args_contain_key(kwargs, "soft_max")) {
ui_data.soft_max = soft_max;
ui_data.soft_max = MIN2(ui_data.soft_max, ui_data.max);
ui_data.soft_min = MIN2(ui_data.soft_min, ui_data.soft_max);
}
if (args_contain_key(kwargs, "step")) {
ui_data.step = (float)step;
}
if (args_contain_key(kwargs, "precision")) {
ui_data.precision = precision;
}
if (!ELEM(default_value, NULL, Py_None)) {
if (!idprop_ui_data_update_float_default(idprop, &ui_data, default_value)) {
IDP_ui_data_free_unique_contents(
&ui_data.base, IDP_ui_data_type(idprop), &ui_data_orig->base);
return false;
}
}
/* Write back to the proeprty's UI data. */
IDP_ui_data_free_unique_contents(&ui_data_orig->base, IDP_ui_data_type(idprop), &ui_data.base);
*ui_data_orig = ui_data;
return true;
}
/**
* \return False when parsing fails, in which case caller should return NULL.
*/
static bool idprop_ui_data_update_string(IDProperty *idprop, PyObject *args, PyObject *kwargs)
{
const char *rna_subtype = NULL;
const char *description = NULL;
const char *default_value;
const char *kwlist[] = {"default", "subtype", "description", NULL};
if (!PyArg_ParseTupleAndKeywords(args,
kwargs,
"|$zzz:update",
(char **)kwlist,
&default_value,
&rna_subtype,
&description)) {
return false;
}
/* Write to a temporary copy of the UI data in case some part of the parsing fails. */
IDPropertyUIDataString *ui_data_orig = (IDPropertyUIDataString *)idprop->ui_data;
IDPropertyUIDataString ui_data = *ui_data_orig;
if (!idprop_ui_data_update_base(&ui_data.base, rna_subtype, description)) {
IDP_ui_data_free_unique_contents(&ui_data.base, IDP_ui_data_type(idprop), &ui_data_orig->base);
return false;
}
if (default_value != NULL) {
ui_data.default_value = BLI_strdup(default_value);
}
/* Write back to the proeprty's UI data. */
IDP_ui_data_free_unique_contents(&ui_data_orig->base, IDP_ui_data_type(idprop), &ui_data.base);
*ui_data_orig = ui_data;
return true;
}
/**
* \return False when parsing fails, in which case caller should return NULL.
*/
static bool idprop_ui_data_update_id(IDProperty *idprop, PyObject *args, PyObject *kwargs)
{
const char *rna_subtype = NULL;
const char *description = NULL;
const char *kwlist[] = {"subtype", "description", NULL};
if (!PyArg_ParseTupleAndKeywords(
args, kwargs, "|$zz:update", (char **)kwlist, &rna_subtype, &description)) {
return false;
}
/* Write to a temporary copy of the UI data in case some part of the parsing fails. */
IDPropertyUIDataID *ui_data_orig = (IDPropertyUIDataID *)idprop->ui_data;
IDPropertyUIDataID ui_data = *ui_data_orig;
if (!idprop_ui_data_update_base(&ui_data.base, rna_subtype, description)) {
IDP_ui_data_free_unique_contents(&ui_data.base, IDP_ui_data_type(idprop), &ui_data_orig->base);
return false;
}
/* Write back to the proeprty's UI data. */
IDP_ui_data_free_unique_contents(&ui_data_orig->base, IDP_ui_data_type(idprop), &ui_data.base);
*ui_data_orig = ui_data;
return true;
}
PyDoc_STRVAR(BPy_IDPropertyUIManager_update_doc,
".. method:: update( "
"subtype=None, "
"min=None, "
"max=None, "
"soft_min=None, "
"soft_max=None, "
"precision=None, "
"step=None, "
"default=None, "
"description=None)\n"
"\n"
" Update the RNA information of the IDProperty used for interaction and\n"
" display in the user interface. The required types for many of the keyword\n"
" arguments depend on the type of the property.\n ");
static PyObject *BPy_IDPropertyUIManager_update(BPy_IDPropertyUIManager *self,
PyObject *args,
PyObject *kwargs)
{
IDProperty *property = self->property;
BLI_assert(IDP_ui_data_supported(property));
switch (IDP_ui_data_type(property)) {
case IDP_UI_DATA_TYPE_INT:
IDP_ui_data_ensure(property);
if (!idprop_ui_data_update_int(property, args, kwargs)) {
return NULL;
}
Py_RETURN_NONE;
case IDP_UI_DATA_TYPE_FLOAT:
IDP_ui_data_ensure(property);
if (!idprop_ui_data_update_float(property, args, kwargs)) {
return NULL;
}
Py_RETURN_NONE;
case IDP_UI_DATA_TYPE_STRING:
IDP_ui_data_ensure(property);
if (!idprop_ui_data_update_string(property, args, kwargs)) {
return NULL;
}
Py_RETURN_NONE;
case IDP_UI_DATA_TYPE_ID:
IDP_ui_data_ensure(property);
if (!idprop_ui_data_update_id(property, args, kwargs)) {
return NULL;
}
Py_RETURN_NONE;
case IDP_UI_DATA_TYPE_UNSUPPORTED:
PyErr_Format(PyExc_TypeError, "IDProperty \"%s\" does not support RNA data", property->name);
return NULL;
}
BLI_assert_unreachable();
Py_RETURN_NONE;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name UI Data As Dictionary
* \{ */
static void idprop_ui_data_to_dict_int(IDProperty *property, PyObject *dict)
{
IDPropertyUIDataInt *ui_data = (IDPropertyUIDataInt *)property->ui_data;
PyObject *item;
PyDict_SetItemString(dict, "min", item = PyLong_FromLong(ui_data->min));
Py_DECREF(item);
PyDict_SetItemString(dict, "max", item = PyLong_FromLong(ui_data->max));
Py_DECREF(item);
PyDict_SetItemString(dict, "soft_min", item = PyLong_FromLong(ui_data->soft_min));
Py_DECREF(item);
PyDict_SetItemString(dict, "soft_max", item = PyLong_FromLong(ui_data->soft_max));
Py_DECREF(item);
PyDict_SetItemString(dict, "step", item = PyLong_FromLong(ui_data->step));
Py_DECREF(item);
if (property->type == IDP_ARRAY) {
PyObject *list = PyList_New(ui_data->default_array_len);
for (int i = 0; i < ui_data->default_array_len; i++) {
PyList_SET_ITEM(list, i, PyLong_FromLong(ui_data->default_array[i]));
}
PyDict_SetItemString(dict, "default", list);
Py_DECREF(list);
}
else {
PyDict_SetItemString(dict, "default", item = PyLong_FromLong(ui_data->step));
Py_DECREF(item);
}
}
static void idprop_ui_data_to_dict_float(IDProperty *property, PyObject *dict)
{
IDPropertyUIDataFloat *ui_data = (IDPropertyUIDataFloat *)property->ui_data;
PyObject *item;
PyDict_SetItemString(dict, "min", item = PyFloat_FromDouble(ui_data->min));
Py_DECREF(item);
PyDict_SetItemString(dict, "max", item = PyFloat_FromDouble(ui_data->max));
Py_DECREF(item);
PyDict_SetItemString(dict, "soft_min", item = PyFloat_FromDouble(ui_data->soft_min));
Py_DECREF(item);
PyDict_SetItemString(dict, "soft_max", item = PyFloat_FromDouble(ui_data->soft_max));
Py_DECREF(item);
PyDict_SetItemString(dict, "step", item = PyFloat_FromDouble((double)ui_data->step));
Py_DECREF(item);
PyDict_SetItemString(dict, "precision", item = PyFloat_FromDouble((double)ui_data->precision));
Py_DECREF(item);
if (property->type == IDP_ARRAY) {
PyObject *list = PyList_New(ui_data->default_array_len);
for (int i = 0; i < ui_data->default_array_len; i++) {
PyList_SET_ITEM(list, i, PyFloat_FromDouble(ui_data->default_array[i]));
}
PyDict_SetItemString(dict, "default", list);
Py_DECREF(list);
}
else {
PyDict_SetItemString(dict, "default", item = PyFloat_FromDouble(ui_data->step));
Py_DECREF(item);
}
}
static void idprop_ui_data_to_dict_string(IDProperty *property, PyObject *dict)
{
IDPropertyUIDataString *ui_data = (IDPropertyUIDataString *)property->ui_data;
PyObject *item;
const char *default_value = (ui_data->default_value == NULL) ? "" : ui_data->default_value;
PyDict_SetItemString(dict, "default", item = PyUnicode_FromString(default_value));
Py_DECREF(item);
}
PyDoc_STRVAR(BPy_IDPropertyUIManager_as_dict_doc,
".. method:: as_dict()\n"
"\n"
" Return a dictionary of the property's RNA UI data. The fields in the\n"
" returned dictionary and their types will depend on the property's type.\n");
static PyObject *BPy_IDIDPropertyUIManager_as_dict(BPy_IDPropertyUIManager *self)
{
IDProperty *property = self->property;
BLI_assert(IDP_ui_data_supported(property));
IDPropertyUIData *ui_data = IDP_ui_data_ensure(property);
PyObject *dict = PyDict_New();
/* RNA subtype. */
{
const char *subtype_id = NULL;
RNA_enum_identifier(rna_enum_property_subtype_items, ui_data->rna_subtype, &subtype_id);
PyObject *item = PyUnicode_FromString(subtype_id);
PyDict_SetItemString(dict, "subtype", item);
Py_DECREF(item);
}
/* Description. */
if (ui_data->description != NULL) {
PyObject *item = PyUnicode_FromString(ui_data->description);
PyDict_SetItemString(dict, "description", item);
Py_DECREF(item);
}
/* Type specific data. */
switch (IDP_ui_data_type(property)) {
case IDP_UI_DATA_TYPE_STRING:
idprop_ui_data_to_dict_string(property, dict);
break;
case IDP_UI_DATA_TYPE_ID:
break;
case IDP_UI_DATA_TYPE_INT:
idprop_ui_data_to_dict_int(property, dict);
break;
case IDP_UI_DATA_TYPE_FLOAT:
idprop_ui_data_to_dict_float(property, dict);
break;
case IDP_UI_DATA_TYPE_UNSUPPORTED:
BLI_assert_unreachable();
break;
}
return dict;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name UI Data Clear
* \{ */
PyDoc_STRVAR(BPy_IDPropertyUIManager_clear_doc,
".. method:: clear()\n"
"\n"
" Remove the RNA UI data from this IDProperty.\n");
static PyObject *BPy_IDPropertyUIManager_clear(BPy_IDPropertyUIManager *self)
{
IDProperty *property = self->property;
BLI_assert(IDP_ui_data_supported(property));
if (property == NULL) {
PyErr_SetString(PyExc_RuntimeError, "IDPropertyUIManager missing property");
BLI_assert_unreachable();
return NULL;
}
if (property->ui_data != NULL) {
IDP_ui_data_free(property);
}
Py_RETURN_NONE;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name UI Data Copying
* \{ */
PyDoc_STRVAR(
BPy_IDPropertyUIManager_update_from_doc,
".. method:: update_from(ui_manager_source)\n"
"\n"
" Copy UI data from an IDProperty in the source group to a property in this group.\n "
" If the source property has no UI data, the target UI data will be reset if it exists.\n"
"\n"
" :raises TypeError: If the types of the two properties don't match.\n");
static PyObject *BPy_IDPropertyUIManager_update_from(BPy_IDPropertyUIManager *self, PyObject *args)
{
IDProperty *property = self->property;
BLI_assert(IDP_ui_data_supported(property));
BPy_IDPropertyUIManager *ui_manager_src;
if (!PyArg_ParseTuple(args, "O!:update_from", &BPy_IDPropertyUIManager_Type, &ui_manager_src)) {
return NULL;
}
if (property->ui_data != NULL) {
IDP_ui_data_free(property);
}
property->ui_data = IDP_ui_data_copy(ui_manager_src->property);
Py_RETURN_NONE;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name UI Data Manager Definition
* \{ */
static struct PyMethodDef BPy_IDPropertyUIManager_methods[] = {
{"update",
(PyCFunction)BPy_IDPropertyUIManager_update,
METH_VARARGS | METH_KEYWORDS,
BPy_IDPropertyUIManager_update_doc},
{"as_dict",
(PyCFunction)BPy_IDIDPropertyUIManager_as_dict,
METH_NOARGS,
BPy_IDPropertyUIManager_as_dict_doc},
{"clear",
(PyCFunction)BPy_IDPropertyUIManager_clear,
METH_NOARGS,
BPy_IDPropertyUIManager_clear_doc},
{"update_from",
(PyCFunction)BPy_IDPropertyUIManager_update_from,
METH_VARARGS,
BPy_IDPropertyUIManager_update_from_doc},
{NULL, NULL, 0, NULL},
};
static PyObject *BPy_IDPropertyUIManager_repr(BPy_IDPropertyUIManager *self)
{
return PyUnicode_FromFormat(
"<bpy id prop ui manager: name=\"%s\", address=%p>", self->property->name, self->property);
}
static Py_hash_t BPy_IDPropertyUIManager_hash(BPy_IDPropertyUIManager *self)
{
return _Py_HashPointer(self->property);
}
PyTypeObject BPy_IDPropertyUIManager_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
/* For printing, in format "<module>.<name>" */
"IDPropertyUIManager", /* char *tp_name; */
sizeof(BPy_IDPropertyUIManager), /* int tp_basicsize; */
0, /* tp_itemsize; For allocation */
/* Methods to implement standard operations */
NULL, /* destructor tp_dealloc; */
0, /* tp_vectorcall_offset */
NULL, /* getattrfunc tp_getattr; */
NULL, /* setattrfunc tp_setattr; */
NULL, /* cmpfunc tp_compare; */
(reprfunc)BPy_IDPropertyUIManager_repr, /* reprfunc tp_repr; */
/* Method suites for standard classes */
NULL, /* PyNumberMethods *tp_as_number; */
NULL, /* PySequenceMethods *tp_as_sequence; */
NULL, /* PyMappingMethods *tp_as_mapping; */
/* More standard operations (here for binary compatibility) */
(hashfunc)BPy_IDPropertyUIManager_hash, /* hashfunc tp_hash; */
NULL, /* ternaryfunc tp_call; */
NULL, /* reprfunc tp_str; */
NULL, /* getattrofunc tp_getattro; */
NULL, /* setattrofunc tp_setattro; */
/* Functions to access object as input/output buffer */
NULL, /* PyBufferProcs *tp_as_buffer; */
/*** Flags to define presence of optional/expanded features ***/
Py_TPFLAGS_DEFAULT, /* long tp_flags; */
NULL, /* char *tp_doc; Documentation string */
/*** Assigned meaning in release 2.0 ***/
/* call function for all accessible objects */
NULL, /* traverseproc tp_traverse; */
/* delete references to contained objects */
NULL, /* inquiry tp_clear; */
/*** Assigned meaning in release 2.1 ***/
/*** rich comparisons ***/
NULL, /* richcmpfunc tp_richcompare; */
/*** weak reference enabler ***/
0, /* long tp_weaklistoffset; */
/*** Added in release 2.2 ***/
/* Iterators */
NULL, /* getiterfunc tp_iter; */
NULL, /* iternextfunc tp_iternext; */
/*** Attribute descriptor and subclassing stuff ***/
BPy_IDPropertyUIManager_methods, /* struct PyMethodDef *tp_methods; */
NULL, /* struct PyMemberDef *tp_members; */
NULL, /* struct PyGetSetDef *tp_getset; */
};
void IDPropertyUIData_Init_Types()
{
PyType_Ready(&BPy_IDPropertyUIManager_Type);
}
/** \} */

View File

@ -0,0 +1,33 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup pygen
*/
#pragma once
struct ID;
struct IDProperty;
extern PyTypeObject BPy_IDPropertyUIManager_Type;
typedef struct BPy_IDPropertyUIManager {
PyObject_VAR_HEAD
struct IDProperty *property;
} BPy_IDPropertyUIManager;
void IDPropertyUIData_Init_Types(void);

View File

@ -56,6 +56,7 @@
/* external util modules */
#include "../generic/idprop_py_api.h"
#include "../generic/idprop_py_ui_api.h"
#include "bpy_msgbus.h"
#ifdef WITH_FREESTYLE
@ -407,6 +408,7 @@ void BPy_init_modules(struct bContext *C)
}
/* stand alone utility modules not related to blender directly */
IDProp_Init_Types(); /* not actually a submodule, just types */
IDPropertyUIData_Init_Types();
#ifdef WITH_FREESTYLE
Freestyle_Init();
#endif

View File

@ -73,6 +73,7 @@
#include "DEG_depsgraph_query.h"
#include "../generic/idprop_py_api.h" /* For IDprop lookups. */
#include "../generic/idprop_py_ui_api.h"
#include "../generic/py_capi_utils.h"
#include "../generic/python_utildefines.h"
@ -4309,6 +4310,51 @@ static PyObject *pyrna_struct_id_properties_ensure(BPy_StructRNA *self)
return (PyObject *)group;
}
PyDoc_STRVAR(pyrna_struct_id_properties_ui_doc,
".. method:: id_properties_ui(key)\n"
"\n"
" :return: Return an object used to manage an IDProperty's UI data.\n"
" :arg key: String name of the property.\n"
" :rtype: :class:`bpy.types.IDPropertyUIManager`\n");
static PyObject *pyrna_struct_id_properties_ui(BPy_StructRNA *self, PyObject *args)
{
PYRNA_STRUCT_CHECK_OBJ(self);
if (RNA_struct_idprops_check(self->ptr.type) == 0) {
PyErr_SetString(PyExc_TypeError, "This type doesn't support IDProperties");
return NULL;
}
const char *key;
if (!PyArg_ParseTuple(args, "s:ui_data", &key)) {
return NULL;
}
IDProperty *parent_group = RNA_struct_idprops(&self->ptr, true);
/* This is a paranoid check that theoretically might not be necessary.
* It allows the possibility that some structs can't ensure IDProperties. */
if (parent_group == NULL) {
return Py_None;
}
IDProperty *property = IDP_GetPropertyFromGroup(parent_group, key);
if (property == NULL) {
PyErr_SetString(PyExc_KeyError, "Property not found in IDProperty group");
return NULL;
}
if (!IDP_ui_data_supported(property)) {
PyErr_Format(PyExc_TypeError, "IDProperty \"%s\" does not support UI data", property->name);
return NULL;
}
BPy_IDPropertyUIManager *ui_manager = PyObject_New(BPy_IDPropertyUIManager,
&BPy_IDPropertyUIManager_Type);
ui_manager->property = property;
return (PyObject *)ui_manager;
}
PyDoc_STRVAR(pyrna_struct_id_properties_clear_doc,
".. method:: id_properties_clear()\n\n"
" :return: Remove the parent group for an RNA struct's custom IDProperties.\n");
@ -5829,6 +5875,10 @@ static struct PyMethodDef pyrna_struct_methods[] = {
(PyCFunction)pyrna_struct_id_properties_clear,
METH_NOARGS,
pyrna_struct_id_properties_clear_doc},
{"id_properties_ui",
(PyCFunction)pyrna_struct_id_properties_ui,
METH_VARARGS,
pyrna_struct_id_properties_ui_doc},
/* experimental */
/* unused for now */

View File

@ -246,6 +246,69 @@ class TestBufferProtocol(TestHelper, unittest.TestCase):
self.assertEqual(list(view1), list(view2))
self.assertEqual(view1.tobytes(), view2.tobytes())
class TestRNAData(TestHelper, unittest.TestCase):
def test_custom_properties_none(self):
bpy.data.objects.new("test", None)
test_object = bpy.data.objects["test"]
# Access default RNA data values.
test_object.id_properties_clear()
test_object["test_prop"] = 0.5
ui_data_test_prop = test_object.id_properties_ui("test_prop")
rna_data = ui_data_test_prop.as_dict()
self.assertTrue("min" in rna_data)
self.assertLess(rna_data["min"], -10000.0)
self.assertEqual(rna_data["subtype"], "NONE")
self.assertGreater(rna_data["soft_max"], 10000.0)
# Change RNA data values.
ui_data_test_prop.update(subtype="TEMPERATURE", min=0, soft_min=0.1)
rna_data = ui_data_test_prop.as_dict()
self.assertEqual(rna_data["min"], 0)
self.assertEqual(rna_data["soft_min"], 0.1)
self.assertEqual(rna_data["subtype"], "TEMPERATURE")
# Copy RNA data values from one property to another.
test_object["test_prop_2"] = 11.7
ui_data_test_prop_2 = test_object.id_properties_ui("test_prop_2")
ui_data_test_prop_2.update_from(ui_data_test_prop)
rna_data = ui_data_test_prop_2.as_dict()
self.assertEqual(rna_data["min"], 0)
self.assertEqual(rna_data["soft_min"], 0.1)
self.assertEqual(rna_data["subtype"], "TEMPERATURE")
self.assertGreater(rna_data["soft_max"], 10000.0)
# Copy RNA data values to another object's property.
bpy.data.objects.new("test_2", None)
test_object_2 = bpy.data.objects["test_2"]
test_object_2["test_prop_3"] = 20.1
ui_data_test_prop_3 = test_object_2.id_properties_ui("test_prop_3")
ui_data_test_prop_3.update_from(ui_data_test_prop_2)
rna_data = ui_data_test_prop_3.as_dict()
self.assertEqual(rna_data["min"], 0)
self.assertEqual(rna_data["soft_min"], 0.1)
self.assertEqual(rna_data["subtype"], "TEMPERATURE")
self.assertGreater(rna_data["soft_max"], 10000.0)
# Test RNA data for string property.
test_object.id_properties_clear()
test_object["test_string_prop"] = "Hello there!"
ui_data_test_prop_string = test_object.id_properties_ui("test_string_prop")
ui_data_test_prop_string.update(default="Goodbye where?")
rna_data = ui_data_test_prop_string.as_dict()
self.assertEqual(rna_data["default"], "Goodbye where?")
# Test RNA data for array property.
test_object.id_properties_clear()
test_object["test_array_prop"] = [1, 2, 3]
ui_data_test_prop_array = test_object.id_properties_ui("test_array_prop")
ui_data_test_prop_array.update(default=[1, 2])
rna_data = ui_data_test_prop_array.as_dict()
self.assertEqual(rna_data["default"], [1, 2])
if __name__ == '__main__':
import sys