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

@ -30,24 +30,6 @@ ARRAY_TYPES = (list, tuple, IDPropertyArray, Vector)
MAX_DISPLAY_ROWS = 4 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): def rna_idprop_quote_path(prop):
return "[\"%s\"]" % bpy.utils.escape_identifier(prop) return "[\"%s\"]" % bpy.utils.escape_identifier(prop)
@ -59,32 +41,9 @@ def rna_idprop_ui_prop_update(item, prop):
prop_rna.update() prop_rna.update()
def rna_idprop_ui_prop_get(item, prop, *, create=True): def rna_idprop_ui_prop_clear(item, prop):
ui_data = item.id_properties_ui(prop)
rna_ui = rna_idprop_ui_get(item, create=create) ui_data.clear()
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_context_value(context, context_member, property_type): 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): def rna_idprop_has_properties(rna_item):
keys = rna_item.keys() keys = rna_item.keys()
nbr_props = len(keys) return bool(keys)
return (nbr_props > 1) or (nbr_props and '_RNA_UI' not in keys)
def rna_idprop_value_to_python(value): 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): def rna_idprop_ui_prop_default_set(item, prop, value):
defvalue = None ui_data = item.id_properties_ui(prop)
try: ui_data.update(default=value)
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
def rna_idprop_ui_create( 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.""" """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 # Sanitize limits
if proptype is bool: if proptype is bool:
@ -180,35 +115,22 @@ def rna_idprop_ui_create(
rna_idprop_ui_prop_update(item, prop) rna_idprop_ui_prop_update(item, prop)
# Clear the UI settings # Update the UI settings.
rna_ui_group = rna_idprop_ui_get(item, create=True) ui_data = item.id_properties_ui(prop)
rna_ui_group[prop] = {} ui_data.update(
rna_ui = rna_ui_group[prop] subtype=subtype,
min=min,
# Assign limits and default max=max,
if proptype in {int, float, bool}: soft_min=soft_min,
# The type must be exactly the same soft_max=soft_max,
rna_ui["min"] = proptype(min) description=description,
rna_ui["soft_min"] = proptype(soft_min) default=default,
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
prop_path = rna_idprop_quote_path(prop) prop_path = rna_idprop_quote_path(prop)
item.property_overridable_library_set(prop_path, overridable) item.property_overridable_library_set(prop_path, overridable)
return rna_ui
def draw(layout, context, context_member, property_type, *, use_edit=True): 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) flow = layout.grid_flow(row_major=False, columns=0, even_columns=True, even_rows=False, align=True)
for key, val in items: for key, val in items:
if key == '_RNA_UI':
continue
is_rna = (key in rna_properties) is_rna = (key in rna_properties)
# only show API defined for developers # only show API defined for developers

@ -970,7 +970,7 @@ class OBJECT_OT_assign_property_defaults(Operator):
def assign_defaults(obj): def assign_defaults(obj):
from rna_prop_ui import rna_idprop_ui_prop_default_set 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(): for prop, value in obj.items():
if prop not in rna_properties: if prop not in rna_properties:

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

@ -523,10 +523,6 @@ class WholeCharacterMixin:
# go over all custom properties for bone # go over all custom properties for bone
for prop in bone.keys(): 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 # for now, just add all of 'em
prop_rna = type(bone).bl_rna.properties.get(prop, None) prop_rna = type(bone).bl_rna.properties.get(prop, None)
if prop_rna is None: if prop_rna is None:

@ -32,6 +32,7 @@ struct BlendLibReader;
struct BlendWriter; struct BlendWriter;
struct ID; struct ID;
struct IDProperty; struct IDProperty;
struct IDPropertyUIData;
typedef union IDPropertyTemplate { typedef union IDPropertyTemplate {
int i; int i;
@ -183,6 +184,10 @@ void IDP_Reset(struct IDProperty *prop, const struct IDProperty *reference);
# define IDP_Id(prop) ((ID *)(prop)->data.pointer) # define IDP_Id(prop) ((ID *)(prop)->data.pointer)
#endif #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). * 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_BlendReadLib(struct BlendLibReader *reader, struct IDProperty *prop);
void IDP_BlendReadExpand(struct BlendExpander *expander, 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 #ifdef __cplusplus
} }
#endif #endif

@ -21,6 +21,7 @@
* \ingroup bke * \ingroup bke
*/ */
#include <limits.h>
#include <stddef.h> #include <stddef.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.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)) static IDProperty *idp_generic_copy(const IDProperty *prop, const int UNUSED(flag))
{ {
IDProperty *newp = MEM_callocN(sizeof(IDProperty), __func__); 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.val = prop->data.val;
newp->data.val2 = prop->data.val2; newp->data.val2 = prop->data.val2;
if (prop->ui_data != NULL) {
newp->ui_data = IDP_ui_data_copy(prop);
}
return newp; return newp;
} }
@ -679,6 +721,7 @@ bool IDP_InsertToGroup(IDProperty *group, IDProperty *previous, IDProperty *pnew
void IDP_RemoveFromGroup(IDProperty *group, IDProperty *prop) void IDP_RemoveFromGroup(IDProperty *group, IDProperty *prop)
{ {
BLI_assert(group->type == IDP_GROUP); BLI_assert(group->type == IDP_GROUP);
BLI_assert(BLI_findindex(&group->data.group, prop) != -1);
group->len--; group->len--;
BLI_remlink(&group->data.group, prop); 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) /** \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) IDProperty *IDP_CopyProperty_ex(const IDProperty *prop, const int flag)
{ {
switch (prop->type) { switch (prop->type) {
@ -999,6 +1096,85 @@ IDProperty *IDP_New(const char type, const IDPropertyTemplate *val, const char *
return prop; 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! * \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. * 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; break;
} }
if (prop->ui_data != NULL) {
IDP_ui_data_free(prop);
}
} }
void IDP_FreePropertyContent(IDProperty *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); 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) static void IDP_WriteArray(const IDProperty *prop, BlendWriter *writer)
{ {
/* Remember to set #IDProperty.totallen to len in the linking code! */ /* 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); IDP_WriteIDPArray(prop, writer);
break; break;
} }
if (prop->ui_data != NULL) {
write_ui_data(prop, writer);
}
} }
void IDP_BlendWrite(BlendWriter *writer, const IDProperty *prop) 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 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) static void IDP_DirectLinkIDPArray(IDProperty *prop, BlendDataReader *reader)
{ {
/* since we didn't save the extra buffer, set totallen to len */ /* 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; prop->subtype = 0;
IDP_Int(prop) = 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) 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;
}
/** \} */ /** \} */

@ -20,6 +20,10 @@
/* allow readfile to use deprecated functionality */ /* allow readfile to use deprecated functionality */
#define DNA_DEPRECATED_ALLOW #define DNA_DEPRECATED_ALLOW
#include <string.h>
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h" #include "BLI_listbase.h"
#include "BLI_math_vector.h" #include "BLI_math_vector.h"
#include "BLI_path_util.h" #include "BLI_path_util.h"
@ -46,10 +50,14 @@
#include "BKE_deform.h" #include "BKE_deform.h"
#include "BKE_fcurve.h" #include "BKE_fcurve.h"
#include "BKE_fcurve_driver.h" #include "BKE_fcurve_driver.h"
#include "BKE_idprop.h"
#include "BKE_lib_id.h" #include "BKE_lib_id.h"
#include "BKE_main.h" #include "BKE_main.h"
#include "BKE_node.h" #include "BKE_node.h"
#include "RNA_access.h"
#include "RNA_enum_types.h"
#include "BLO_readfile.h" #include "BLO_readfile.h"
#include "MEM_guardedalloc.h" #include "MEM_guardedalloc.h"
#include "readfile.h" #include "readfile.h"
@ -60,6 +68,238 @@
#include "versioning_common.h" #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) static void sort_linked_ids(Main *bmain)
{ {
ListBase *lb; ListBase *lb;
@ -252,6 +492,7 @@ void do_versions_after_linking_300(Main *bmain, ReportList *UNUSED(reports))
*/ */
{ {
/* Keep this block, even when empty. */ /* Keep this block, even when empty. */
do_versions_idproperty_ui_data(bmain);
} }
} }

@ -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. */ /* Loop over the properties, just like IDP_foreach_property() does, but without the recursion. */
LISTBASE_FOREACH (IDProperty *, id_property, &group->data.group) { LISTBASE_FOREACH (IDProperty *, id_property, &group->data.group) {
if (STREQ(id_property->name, "_RNA_UI")) {
continue;
}
write(id_property); write(id_property);
} }
} }

@ -59,6 +59,58 @@ typedef struct DrawDataList {
struct DrawData *first, *last; struct DrawData *first, *last;
} DrawDataList; } 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 { typedef struct IDPropertyData {
void *pointer; void *pointer;
ListBase group; ListBase group;
@ -87,6 +139,8 @@ typedef struct IDProperty {
/* totallen is total length of allocated array/string, including a buffer. /* 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. */ * Note that the buffering is mild; the code comes from python's list implementation. */
int totallen; int totallen;
IDPropertyUIData *ui_data;
} IDProperty; } IDProperty;
#define MAX_IDPROP_NAME 64 #define MAX_IDPROP_NAME 64

@ -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_array(PointerRNA *ptr, PropertyRNA *prop, const int *values);
void RNA_property_int_set_index(PointerRNA *ptr, PropertyRNA *prop, int index, int value); void RNA_property_int_set_index(PointerRNA *ptr, PropertyRNA *prop, int index, int value);
int RNA_property_int_get_default(PointerRNA *ptr, PropertyRNA *prop); 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); 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); 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_array(PointerRNA *ptr, PropertyRNA *prop, const float *values);
void RNA_property_float_set_index(PointerRNA *ptr, PropertyRNA *prop, int index, float value); void RNA_property_float_set_index(PointerRNA *ptr, PropertyRNA *prop, int index, float value);
float RNA_property_float_get_default(PointerRNA *ptr, PropertyRNA *prop); 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); 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); 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(PointerRNA *ptr, PropertyRNA *prop, const char *value);
void RNA_property_string_set_bytes(PointerRNA *ptr, PropertyRNA *prop, const char *value, int len); void RNA_property_string_set_bytes(PointerRNA *ptr, PropertyRNA *prop, const char *value, int len);
int RNA_property_string_length(PointerRNA *ptr, PropertyRNA *prop); 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( char *RNA_property_string_get_default_alloc(
PointerRNA *ptr, PropertyRNA *prop, char *fixedbuf, int fixedlen, int *r_len); PointerRNA *ptr, PropertyRNA *prop, char *fixedbuf, int fixedlen, int *r_len);
int RNA_property_string_default_length(PointerRNA *ptr, PropertyRNA *prop); int RNA_property_string_default_length(PointerRNA *ptr, PropertyRNA *prop);

@ -246,130 +246,6 @@ void rna_idproperty_touch(IDProperty *idprop)
idprop->flag &= ~IDP_FLAG_GHOST; 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) IDProperty **RNA_struct_idprops_p(PointerRNA *ptr)
{ {
StructRNA *type = ptr->type; 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) static const char *rna_ensure_property_description(const PropertyRNA *prop)
{ {
const char *description = NULL;
if (prop->magic == RNA_MAGIC) { if (prop->magic == RNA_MAGIC) {
description = prop->description; return 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 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) 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) { if (prop->magic != RNA_MAGIC) {
IDProperty *idprop = (IDProperty *)prop; IDProperty *idprop = (IDProperty *)prop;
if (ELEM(idprop->type, IDP_INT, IDP_FLOAT, IDP_DOUBLE) || if (idprop->ui_data) {
((idprop->type == IDP_ARRAY) && ELEM(idprop->subtype, IDP_INT, IDP_FLOAT, IDP_DOUBLE))) { IDPropertyUIData *ui_data = idprop->ui_data;
const IDProperty *idp_ui = rna_idproperty_ui(prop); return (PropertySubType)ui_data->rna_subtype;
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;
}
}
} }
} }
@ -1387,20 +1242,17 @@ void RNA_property_int_range(PointerRNA *ptr, PropertyRNA *prop, int *hardmin, in
int softmin, softmax; int softmin, softmax;
if (prop->magic != RNA_MAGIC) { if (prop->magic != RNA_MAGIC) {
/* attempt to get the local ID values */ const IDProperty *idprop = (IDProperty *)prop;
const IDProperty *idp_ui = rna_idproperty_ui(prop); if (idprop->ui_data) {
IDPropertyUIDataInt *ui_data = (IDPropertyUIDataInt *)idprop->ui_data;
if (idp_ui) { *hardmin = ui_data->min;
IDProperty *item; *hardmax = ui_data->max;
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;
} }
else {
*hardmin = INT_MIN;
*hardmax = INT_MAX;
}
return;
} }
if (iprop->range) { if (iprop->range) {
@ -1428,23 +1280,19 @@ void RNA_property_int_ui_range(
int hardmin, hardmax; int hardmin, hardmax;
if (prop->magic != RNA_MAGIC) { if (prop->magic != RNA_MAGIC) {
/* attempt to get the local ID values */ const IDProperty *idprop = (IDProperty *)prop;
const IDProperty *idp_ui = rna_idproperty_ui(prop); if (idprop->ui_data) {
IDPropertyUIDataInt *ui_data_int = (IDPropertyUIDataInt *)idprop->ui_data;
if (idp_ui) { *softmin = ui_data_int->soft_min;
IDProperty *item; *softmax = ui_data_int->soft_max;
*step = ui_data_int->step;
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;
} }
else {
*softmin = INT_MIN;
*softmax = INT_MAX;
*step = 1;
}
return;
} }
*softmin = iprop->softmin; *softmin = iprop->softmin;
@ -1478,20 +1326,17 @@ void RNA_property_float_range(PointerRNA *ptr, PropertyRNA *prop, float *hardmin
float softmin, softmax; float softmin, softmax;
if (prop->magic != RNA_MAGIC) { if (prop->magic != RNA_MAGIC) {
/* attempt to get the local ID values */ const IDProperty *idprop = (IDProperty *)prop;
const IDProperty *idp_ui = rna_idproperty_ui(prop); if (idprop->ui_data) {
IDPropertyUIDataFloat *ui_data = (IDPropertyUIDataFloat *)idprop->ui_data;
if (idp_ui) { *hardmin = (float)ui_data->min;
IDProperty *item; *hardmax = (float)ui_data->max;
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;
} }
else {
*hardmin = FLT_MIN;
*hardmax = FLT_MAX;
}
return;
} }
if (fprop->range) { if (fprop->range) {
@ -1523,26 +1368,21 @@ void RNA_property_float_ui_range(PointerRNA *ptr,
float hardmin, hardmax; float hardmin, hardmax;
if (prop->magic != RNA_MAGIC) { if (prop->magic != RNA_MAGIC) {
/* attempt to get the local ID values */ const IDProperty *idprop = (IDProperty *)prop;
const IDProperty *idp_ui = rna_idproperty_ui(prop); if (idprop->ui_data) {
IDPropertyUIDataFloat *ui_data = (IDPropertyUIDataFloat *)idprop->ui_data;
if (idp_ui) { *softmin = (float)ui_data->soft_min;
IDProperty *item; *softmax = (float)ui_data->soft_max;
*step = ui_data->step;
item = IDP_GetPropertyTypeFromGroup(idp_ui, "soft_min", IDP_DOUBLE); *precision = (float)ui_data->precision;
*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;
} }
else {
*softmin = FLT_MIN;
*softmax = FLT_MAX;
*step = 1.0f;
*precision = 3.0f;
}
return;
} }
*softmin = fprop->softmin; *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); IntPropertyRNA *iprop = (IntPropertyRNA *)rna_ensure_property(prop);
if (prop->magic != RNA_MAGIC) { if (prop->magic != RNA_MAGIC) {
/* attempt to get the local ID values */ const IDProperty *idprop = (const IDProperty *)prop;
const IDProperty *idp_ui = rna_idproperty_ui(prop); if (idprop->ui_data) {
const IDPropertyUIDataInt *ui_data = (const IDPropertyUIDataInt *)idprop->ui_data;
if (idp_ui) { return ui_data->default_value;
IDProperty *item;
item = IDP_GetPropertyTypeFromGroup(idp_ui, "default", IDP_INT);
return item ? IDP_Int(item) : iprop->defaultvalue;
} }
} }
return iprop->defaultvalue; 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) { if (prop->magic == RNA_MAGIC) {
IDPropertyTemplate val = { return false;
.i = value,
};
return rna_idproperty_ui_set_default(ptr, prop, IDP_INT, &val);
} }
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) 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) { if (prop->magic != RNA_MAGIC) {
int length = rna_ensure_property_array_length(ptr, prop); int length = rna_ensure_property_array_length(ptr, prop);
const IDProperty *idp_ui = rna_idproperty_ui(prop); const IDProperty *idprop = (const IDProperty *)prop;
IDProperty *item = idp_ui ? IDP_GetPropertyFromGroup(idp_ui, "default") : NULL; if (idprop->ui_data) {
BLI_assert(idprop->type == IDP_ARRAY);
int defval = (item && item->type == IDP_INT) ? IDP_Int(item) : iprop->defaultvalue; BLI_assert(idprop->subtype == IDP_INT);
const IDPropertyUIDataInt *ui_data = (const IDPropertyUIDataInt *)idprop->ui_data;
if (item && item->type == IDP_ARRAY && item->subtype == IDP_INT) { if (ui_data->default_array) {
rna_property_int_fill_default_array_values( rna_property_int_fill_default_array_values(ui_data->default_array,
IDP_Array(item), item->len, defval, length, values); ui_data->default_array_len,
} ui_data->default_value,
else { length,
rna_property_int_fill_default_array_values(NULL, 0, defval, length, values); values);
}
else {
rna_property_int_fill_default_array_values(
NULL, 0, ui_data->default_value, length, values);
}
} }
} }
else if (prop->arraydimension == 0) { 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, static void rna_property_float_get_default_array_values(PointerRNA *ptr,
FloatPropertyRNA *fprop, FloatPropertyRNA *fprop,
float *r_values) 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); BLI_assert(RNA_property_array_check(prop) == false);
if (prop->magic != RNA_MAGIC) { if (prop->magic != RNA_MAGIC) {
/* attempt to get the local ID values */ const IDProperty *idprop = (const IDProperty *)prop;
const IDProperty *idp_ui = rna_idproperty_ui(prop); if (idprop->ui_data) {
BLI_assert(ELEM(idprop->type, IDP_FLOAT, IDP_DOUBLE));
if (idp_ui) { const IDPropertyUIDataFloat *ui_data = (const IDPropertyUIDataFloat *)idprop->ui_data;
IDProperty *item; return (float)ui_data->default_value;
item = IDP_GetPropertyTypeFromGroup(idp_ui, "default", IDP_DOUBLE);
return item ? IDP_Double(item) : fprop->defaultvalue;
} }
} }
return fprop->defaultvalue; 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) { if (prop->magic == RNA_MAGIC) {
IDPropertyTemplate val = { return false;
.d = value,
};
return rna_idproperty_ui_set_default(ptr, prop, IDP_DOUBLE, &val);
} }
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) 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) { if (prop->magic != RNA_MAGIC) {
int length = rna_ensure_property_array_length(ptr, prop); int length = rna_ensure_property_array_length(ptr, prop);
const IDProperty *idp_ui = rna_idproperty_ui(prop); const IDProperty *idprop = (const IDProperty *)prop;
IDProperty *item = idp_ui ? IDP_GetPropertyFromGroup(idp_ui, "default") : NULL; if (idprop->ui_data) {
BLI_assert(idprop->type == IDP_ARRAY);
float defval = (item && item->type == IDP_DOUBLE) ? IDP_Double(item) : fprop->defaultvalue; BLI_assert(ELEM(idprop->subtype, IDP_FLOAT, IDP_DOUBLE));
const IDPropertyUIDataFloat *ui_data = (const IDPropertyUIDataFloat *)idprop->ui_data;
if (item && item->type == IDP_ARRAY && item->subtype == IDP_DOUBLE) { rna_property_float_fill_default_array_values_double(ui_data->default_array,
double *defarr = IDP_Array(item); ui_data->default_array_len,
for (int i = 0; i < length; i++) { ui_data->default_value,
values[i] = (i < item->len) ? (float)defarr[i] : defval; length,
} values);
}
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);
} }
} }
else if (prop->arraydimension == 0) { 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); StringPropertyRNA *sprop = (StringPropertyRNA *)rna_ensure_property(prop);
if (prop->magic != RNA_MAGIC) { if (prop->magic != RNA_MAGIC) {
/* attempt to get the local ID values */ const IDProperty *idprop = (const IDProperty *)prop;
const IDProperty *idp_ui = rna_idproperty_ui(prop); if (idprop->ui_data) {
BLI_assert(idprop->type == IDP_STRING);
if (idp_ui) { const IDPropertyUIDataString *ui_data = (const IDPropertyUIDataString *)idprop->ui_data;
IDProperty *item; BLI_strncpy(value, ui_data->default_value, max_len);
return;
item = IDP_GetPropertyTypeFromGroup(idp_ui, "default", IDP_STRING);
if (item) {
strcpy(value, IDP_String(item));
return;
}
} }
strcpy(value, ""); strcpy(value, "");
@ -3554,7 +3406,7 @@ char *RNA_property_string_get_default_alloc(
buf = MEM_callocN(sizeof(char) * (length + 1), __func__); 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) { if (r_len) {
*r_len = length; *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); StringPropertyRNA *sprop = (StringPropertyRNA *)rna_ensure_property(prop);
if (prop->magic != RNA_MAGIC) { if (prop->magic != RNA_MAGIC) {
/* attempt to get the local ID values */ const IDProperty *idprop = (const IDProperty *)prop;
const IDProperty *idp_ui = rna_idproperty_ui(prop); if (idprop->ui_data) {
BLI_assert(idprop->type == IDP_STRING);
if (idp_ui) { const IDPropertyUIDataString *ui_data = (const IDPropertyUIDataString *)idprop->ui_data;
IDProperty *item; if (ui_data->default_value != NULL) {
return strlen(ui_data->default_value);
item = IDP_GetPropertyTypeFromGroup(idp_ui, "default", IDP_STRING);
if (item) {
return strlen(IDP_String(item));
} }
} }
@ -8200,12 +8049,12 @@ bool RNA_property_assign_default(PointerRNA *ptr, PropertyRNA *prop)
switch (RNA_property_type(prop)) { switch (RNA_property_type(prop)) {
case PROP_INT: { case PROP_INT: {
int value = RNA_property_int_get(ptr, prop); 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: { case PROP_FLOAT: {
float value = RNA_property_float_get(ptr, prop); 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: default:

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

@ -289,348 +289,178 @@ static bool logging_enabled(const ModifierEvalContext *ctx)
return true; return true;
} }
/** static IDProperty *id_property_create_from_socket(const bNodeSocket &socket)
* 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)
{ {
const char *new_prop_name = socket.identifier; switch (socket.type) {
/* 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) {
case SOCK_FLOAT: { case SOCK_FLOAT: {
static const SocketPropertyType float_type = { bNodeSocketValueFloat *value = (bNodeSocketValueFloat *)socket.default_value;
[](const bNodeSocket &socket, const char *name) { IDPropertyTemplate idprop = {0};
bNodeSocketValueFloat *value = (bNodeSocketValueFloat *)socket.default_value; idprop.f = value->value;
IDPropertyTemplate idprop = {0}; IDProperty *property = IDP_New(IDP_FLOAT, &idprop, socket.identifier);
idprop.f = value->value; IDPropertyUIDataFloat *ui_data = (IDPropertyUIDataFloat *)IDP_ui_data_ensure(property);
return IDP_New(IDP_FLOAT, &idprop, name); ui_data->base.rna_subtype = value->subtype;
}, ui_data->min = ui_data->soft_min = (double)value->min;
[](const bNodeSocket &socket, const char *name) { ui_data->max = ui_data->soft_max = (double)value->max;
bNodeSocketValueFloat *value = (bNodeSocketValueFloat *)socket.default_value; ui_data->default_value = value->value;
IDPropertyTemplate idprop = {0}; return property;
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;
} }
case SOCK_INT: { case SOCK_INT: {
static const SocketPropertyType int_type = { bNodeSocketValueInt *value = (bNodeSocketValueInt *)socket.default_value;
[](const bNodeSocket &socket, const char *name) { IDPropertyTemplate idprop = {0};
bNodeSocketValueInt *value = (bNodeSocketValueInt *)socket.default_value; idprop.i = value->value;
IDPropertyTemplate idprop = {0}; IDProperty *property = IDP_New(IDP_INT, &idprop, socket.identifier);
idprop.i = value->value; IDPropertyUIDataInt *ui_data = (IDPropertyUIDataInt *)IDP_ui_data_ensure(property);
return IDP_New(IDP_INT, &idprop, name); ui_data->base.rna_subtype = value->subtype;
}, ui_data->min = ui_data->soft_min = value->min;
[](const bNodeSocket &socket, const char *name) { ui_data->max = ui_data->soft_max = value->max;
bNodeSocketValueInt *value = (bNodeSocketValueInt *)socket.default_value; ui_data->default_value = value->value;
IDPropertyTemplate idprop = {0}; return property;
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;
} }
case SOCK_VECTOR: { case SOCK_VECTOR: {
static const SocketPropertyType vector_type = { bNodeSocketValueVector *value = (bNodeSocketValueVector *)socket.default_value;
[](const bNodeSocket &socket, const char *name) { IDPropertyTemplate idprop = {0};
bNodeSocketValueVector *value = (bNodeSocketValueVector *)socket.default_value; idprop.array.len = 3;
IDPropertyTemplate idprop = {0}; idprop.array.type = IDP_FLOAT;
idprop.array.len = 3; IDProperty *property = IDP_New(IDP_ARRAY, &idprop, socket.identifier);
idprop.array.type = IDP_FLOAT; copy_v3_v3((float *)IDP_Array(property), value->value);
IDProperty *property = IDP_New(IDP_ARRAY, &idprop, name); IDPropertyUIDataFloat *ui_data = (IDPropertyUIDataFloat *)IDP_ui_data_ensure(property);
copy_v3_v3((float *)IDP_Array(property), value->value); ui_data->base.rna_subtype = value->subtype;
return property; ui_data->min = ui_data->soft_min = (double)value->min;
}, ui_data->max = ui_data->soft_max = (double)value->max;
[](const bNodeSocket &socket, const char *name) { ui_data->default_array = (double *)MEM_mallocN(sizeof(double[3]), "mod_prop_default");
bNodeSocketValueVector *value = (bNodeSocketValueVector *)socket.default_value; ui_data->default_array_len = 3;
IDPropertyTemplate idprop = {0}; for (int i = 3; i < 3; i++) {
idprop.d = value->min; ui_data->default_array[i] = (double)value->value[i];
return IDP_New(IDP_DOUBLE, &idprop, name); }
}, return property;
[](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;
} }
case SOCK_BOOLEAN: { case SOCK_BOOLEAN: {
static const SocketPropertyType boolean_type = { bNodeSocketValueBoolean *value = (bNodeSocketValueBoolean *)socket.default_value;
[](const bNodeSocket &socket, const char *name) { IDPropertyTemplate idprop = {0};
bNodeSocketValueBoolean *value = (bNodeSocketValueBoolean *)socket.default_value; idprop.i = value->value != 0;
IDPropertyTemplate idprop = {0}; IDProperty *property = IDP_New(IDP_INT, &idprop, socket.identifier);
idprop.i = value->value != 0; IDPropertyUIDataInt *ui_data = (IDPropertyUIDataInt *)IDP_ui_data_ensure(property);
return IDP_New(IDP_INT, &idprop, name); ui_data->min = ui_data->soft_min = 0;
}, ui_data->max = ui_data->soft_max = 1;
[](const bNodeSocket &UNUSED(socket), const char *name) { ui_data->default_value = value->value != 0;
IDPropertyTemplate idprop = {0}; return property;
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;
} }
case SOCK_STRING: { case SOCK_STRING: {
static const SocketPropertyType string_type = { bNodeSocketValueString *value = (bNodeSocketValueString *)socket.default_value;
[](const bNodeSocket &socket, const char *name) { IDProperty *property = IDP_NewString(
bNodeSocketValueString *value = (bNodeSocketValueString *)socket.default_value; value->value, socket.identifier, BLI_strnlen(value->value, sizeof(value->value)) + 1);
return IDP_NewString( IDPropertyUIDataString *ui_data = (IDPropertyUIDataString *)IDP_ui_data_ensure(property);
value->value, name, BLI_strnlen(value->value, sizeof(value->value)) + 1); ui_data->default_value = BLI_strdup(value->value);
}, return property;
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;
} }
case SOCK_OBJECT: { case SOCK_OBJECT: {
static const SocketPropertyType object_type = { bNodeSocketValueObject *value = (bNodeSocketValueObject *)socket.default_value;
[](const bNodeSocket &socket, const char *name) { IDPropertyTemplate idprop = {0};
bNodeSocketValueObject *value = (bNodeSocketValueObject *)socket.default_value; idprop.id = (ID *)value->value;
IDPropertyTemplate idprop = {0}; return IDP_New(IDP_ID, &idprop, socket.identifier);
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;
} }
case SOCK_COLLECTION: { case SOCK_COLLECTION: {
static const SocketPropertyType collection_type = { bNodeSocketValueCollection *value = (bNodeSocketValueCollection *)socket.default_value;
[](const bNodeSocket &socket, const char *name) { IDPropertyTemplate idprop = {0};
bNodeSocketValueCollection *value = (bNodeSocketValueCollection *)socket.default_value; idprop.id = (ID *)value->value;
IDPropertyTemplate idprop = {0}; return IDP_New(IDP_ID, &idprop, socket.identifier);
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;
} }
case SOCK_TEXTURE: { case SOCK_TEXTURE: {
static const SocketPropertyType texture_type = { bNodeSocketValueTexture *value = (bNodeSocketValueTexture *)socket.default_value;
[](const bNodeSocket &socket, const char *name) { IDPropertyTemplate idprop = {0};
bNodeSocketValueTexture *value = (bNodeSocketValueTexture *)socket.default_value; idprop.id = (ID *)value->value;
IDPropertyTemplate idprop = {0}; return IDP_New(IDP_ID, &idprop, socket.identifier);
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;
} }
case SOCK_MATERIAL: { case SOCK_MATERIAL: {
static const SocketPropertyType material_type = { bNodeSocketValueMaterial *value = (bNodeSocketValueMaterial *)socket.default_value;
[](const bNodeSocket &socket, const char *name) { IDPropertyTemplate idprop = {0};
bNodeSocketValueMaterial *value = (bNodeSocketValueMaterial *)socket.default_value; idprop.id = (ID *)value->value;
IDPropertyTemplate idprop = {0}; return IDP_New(IDP_ID, &idprop, socket.identifier);
idprop.id = (ID *)value->value; }
return IDP_New(IDP_ID, &idprop, name); }
}, return nullptr;
nullptr, }
nullptr,
nullptr, static bool id_property_type_matches_socket(const bNodeSocket &socket, const IDProperty &property)
nullptr, {
[](const IDProperty &property) { return property.type == IDP_ID; }, switch (socket.type) {
[](const IDProperty &property, void *r_value) { case SOCK_FLOAT:
ID *id = IDP_Id(&property); return ELEM(property.type, IDP_FLOAT, IDP_DOUBLE);
Material *material = (id && GS(id->name) == ID_MA) ? (Material *)id : nullptr; case SOCK_INT:
*(Material **)r_value = material; return property.type == IDP_INT;
}, case SOCK_VECTOR:
}; return property.type == IDP_ARRAY && property.subtype == IDP_FLOAT && property.len == 3;
return &material_type; 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: { 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; IDProperty *old_properties = nmd->settings.properties;
{ {
IDPropertyTemplate idprop = {0}; IDPropertyTemplate idprop = {0};
nmd->settings.properties = IDP_New(IDP_GROUP, &idprop, "Nodes Modifier Settings"); 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) { LISTBASE_FOREACH (bNodeSocket *, socket, &nmd->node_group->inputs) {
const SocketPropertyType *property_type = get_socket_property_type(*socket); IDProperty *new_prop = id_property_create_from_socket(*socket);
if (property_type == nullptr) { 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; continue;
} }
IDProperty *new_prop = socket_add_property( new_prop->flag |= IDP_FLAG_OVERRIDABLE_LIBRARY;
nmd->settings.properties, ui_container_group, *property_type, *socket); 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) { if (old_properties != nullptr) {
IDProperty *old_prop = IDP_GetPropertyFromGroup(old_properties, socket->identifier); 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); 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, static void initialize_group_input(NodesModifierData &nmd,
const bNodeSocket &socket, const bNodeSocket &socket,
const CPPType &cpp_type,
void *r_value) 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) { if (nmd.settings.properties == nullptr) {
socket.typeinfo->get_geometry_nodes_cpp_value(socket, r_value); socket.typeinfo->get_geometry_nodes_cpp_value(socket, r_value);
return; return;
@ -731,11 +562,13 @@ static void initialize_group_input(NodesModifierData &nmd,
socket.typeinfo->get_geometry_nodes_cpp_value(socket, r_value); socket.typeinfo->get_geometry_nodes_cpp_value(socket, r_value);
return; 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); socket.typeinfo->get_geometry_nodes_cpp_value(socket, r_value);
return; 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) 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) { for (const OutputSocketRef *socket : remaining_input_sockets) {
const CPPType &cpp_type = *socket->typeinfo()->get_geometry_nodes_cpp_type(); const CPPType &cpp_type = *socket->typeinfo()->get_geometry_nodes_cpp_type();
void *value_in = allocator.allocate(cpp_type.size(), cpp_type.alignment()); 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}); 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; continue;
} }
const SocketPropertyType *property_type = get_socket_property_type(*socket); if (!id_property_type_matches_socket(*socket, *property)) {
if (!property_type->is_correct_type(*property)) {
BKE_modifier_set_error( BKE_modifier_set_error(
ob, md, "Property type does not match input socket \"(%s)\"", socket->name); ob, md, "Property type does not match input socket \"(%s)\"", socket->name);
continue; continue;
@ -1051,17 +883,12 @@ static void draw_property_for_socket(uiLayout *layout,
const IDProperty *modifier_props, const IDProperty *modifier_props,
const bNodeSocket &socket) 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. */ /* The property should be created in #MOD_nodes_update_interface with the correct type. */
IDProperty *property = IDP_GetPropertyFromGroup(modifier_props, socket.identifier); IDProperty *property = IDP_GetPropertyFromGroup(modifier_props, socket.identifier);
/* IDProperties can be removed with python, so there could be a situation where /* 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. */ * 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; return;
} }

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

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

@ -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);
}
/** \} */

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

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

@ -73,6 +73,7 @@
#include "DEG_depsgraph_query.h" #include "DEG_depsgraph_query.h"
#include "../generic/idprop_py_api.h" /* For IDprop lookups. */ #include "../generic/idprop_py_api.h" /* For IDprop lookups. */
#include "../generic/idprop_py_ui_api.h"
#include "../generic/py_capi_utils.h" #include "../generic/py_capi_utils.h"
#include "../generic/python_utildefines.h" #include "../generic/python_utildefines.h"
@ -4309,6 +4310,51 @@ static PyObject *pyrna_struct_id_properties_ensure(BPy_StructRNA *self)
return (PyObject *)group; 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, PyDoc_STRVAR(pyrna_struct_id_properties_clear_doc,
".. method:: id_properties_clear()\n\n" ".. method:: id_properties_clear()\n\n"
" :return: Remove the parent group for an RNA struct's custom IDProperties.\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, (PyCFunction)pyrna_struct_id_properties_clear,
METH_NOARGS, METH_NOARGS,
pyrna_struct_id_properties_clear_doc}, pyrna_struct_id_properties_clear_doc},
{"id_properties_ui",
(PyCFunction)pyrna_struct_id_properties_ui,
METH_VARARGS,
pyrna_struct_id_properties_ui_doc},
/* experimental */ /* experimental */
/* unused for now */ /* unused for now */

@ -246,6 +246,69 @@ class TestBufferProtocol(TestHelper, unittest.TestCase):
self.assertEqual(list(view1), list(view2)) self.assertEqual(list(view1), list(view2))
self.assertEqual(view1.tobytes(), view2.tobytes()) 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__': if __name__ == '__main__':
import sys import sys