diff --git a/release/scripts/modules/rna_prop_ui.py b/release/scripts/modules/rna_prop_ui.py index bafa2b28bbf..26a2f9ad89b 100644 --- a/release/scripts/modules/rna_prop_ui.py +++ b/release/scripts/modules/rna_prop_ui.py @@ -30,24 +30,6 @@ ARRAY_TYPES = (list, tuple, IDPropertyArray, Vector) MAX_DISPLAY_ROWS = 4 -def rna_idprop_ui_get(item, *, create=True): - try: - return item['_RNA_UI'] - except: - if create: - item['_RNA_UI'] = {} - return item['_RNA_UI'] - else: - return None - - -def rna_idprop_ui_del(item): - try: - del item['_RNA_UI'] - except KeyError: - pass - - def rna_idprop_quote_path(prop): return "[\"%s\"]" % bpy.utils.escape_identifier(prop) @@ -59,32 +41,9 @@ def rna_idprop_ui_prop_update(item, prop): prop_rna.update() -def rna_idprop_ui_prop_get(item, prop, *, create=True): - - rna_ui = rna_idprop_ui_get(item, create=create) - - if rna_ui is None: - return None - - try: - return rna_ui[prop] - except: - rna_ui[prop] = {} - return rna_ui[prop] - - -def rna_idprop_ui_prop_clear(item, prop, *, remove=True): - rna_ui = rna_idprop_ui_get(item, create=False) - - if rna_ui is None: - return - - try: - del rna_ui[prop] - except KeyError: - pass - if remove and len(item.keys()) == 1: - rna_idprop_ui_del(item) +def rna_idprop_ui_prop_clear(item, prop): + ui_data = item.id_properties_ui(prop) + ui_data.clear() def rna_idprop_context_value(context, context_member, property_type): @@ -106,8 +65,7 @@ def rna_idprop_context_value(context, context_member, property_type): def rna_idprop_has_properties(rna_item): keys = rna_item.keys() - nbr_props = len(keys) - return (nbr_props > 1) or (nbr_props and '_RNA_UI' not in keys) + return bool(keys) def rna_idprop_value_to_python(value): @@ -126,31 +84,8 @@ def rna_idprop_value_item_type(value): def rna_idprop_ui_prop_default_set(item, prop, value): - defvalue = None - try: - prop_type, is_array = rna_idprop_value_item_type(item[prop]) - - if prop_type in {int, float, str}: - if is_array and isinstance(value, ARRAY_TYPES): - value = [prop_type(item) for item in value] - if any(value): - defvalue = value - else: - defvalue = prop_type(value) - except KeyError: - pass - except ValueError: - pass - - if defvalue: - rna_ui = rna_idprop_ui_prop_get(item, prop, create=True) - rna_ui["default"] = defvalue - else: - rna_ui = rna_idprop_ui_prop_get(item, prop) - if rna_ui: - rna_ui.pop("default", None) - - return defvalue + ui_data = item.id_properties_ui(prop) + ui_data.update(default=value) def rna_idprop_ui_create( @@ -163,7 +98,7 @@ def rna_idprop_ui_create( ): """Create and initialize a custom property with limits, defaults and other settings.""" - proptype, is_array = rna_idprop_value_item_type(default) + proptype, _ = rna_idprop_value_item_type(default) # Sanitize limits if proptype is bool: @@ -180,35 +115,22 @@ def rna_idprop_ui_create( rna_idprop_ui_prop_update(item, prop) - # Clear the UI settings - rna_ui_group = rna_idprop_ui_get(item, create=True) - rna_ui_group[prop] = {} - rna_ui = rna_ui_group[prop] - - # Assign limits and default - if proptype in {int, float, bool}: - # The type must be exactly the same - rna_ui["min"] = proptype(min) - rna_ui["soft_min"] = proptype(soft_min) - rna_ui["max"] = proptype(max) - rna_ui["soft_max"] = proptype(soft_max) - - if default and (not is_array or any(default)): - rna_ui["default"] = default - - if is_array and subtype and subtype != 'NONE': - rna_ui["subtype"] = subtype - - # Assign other settings - if description is not None: - rna_ui["description"] = description + # Update the UI settings. + ui_data = item.id_properties_ui(prop) + ui_data.update( + subtype=subtype, + min=min, + max=max, + soft_min=soft_min, + soft_max=soft_max, + description=description, + default=default, + ) prop_path = rna_idprop_quote_path(prop) item.property_overridable_library_set(prop_path, overridable) - return rna_ui - def draw(layout, context, context_member, property_type, *, use_edit=True): @@ -254,10 +176,6 @@ def draw(layout, context, context_member, property_type, *, use_edit=True): flow = layout.grid_flow(row_major=False, columns=0, even_columns=True, even_rows=False, align=True) for key, val in items: - - if key == '_RNA_UI': - continue - is_rna = (key in rna_properties) # only show API defined for developers diff --git a/release/scripts/startup/bl_operators/object.py b/release/scripts/startup/bl_operators/object.py index d61bed71cab..df37db40102 100644 --- a/release/scripts/startup/bl_operators/object.py +++ b/release/scripts/startup/bl_operators/object.py @@ -970,7 +970,7 @@ class OBJECT_OT_assign_property_defaults(Operator): def assign_defaults(obj): from rna_prop_ui import rna_idprop_ui_prop_default_set - rna_properties = {'_RNA_UI'} | {prop.identifier for prop in obj.bl_rna.properties if prop.is_runtime} + rna_properties = {prop.identifier for prop in obj.bl_rna.properties if prop.is_runtime} for prop, value in obj.items(): if prop not in rna_properties: diff --git a/release/scripts/startup/bl_operators/wm.py b/release/scripts/startup/bl_operators/wm.py index 6a3830ad1e4..989c067efec 100644 --- a/release/scripts/startup/bl_operators/wm.py +++ b/release/scripts/startup/bl_operators/wm.py @@ -1388,10 +1388,8 @@ class WM_OT_properties_edit(Operator): def execute(self, context): from rna_prop_ui import ( - rna_idprop_ui_prop_get, rna_idprop_ui_prop_clear, rna_idprop_ui_prop_update, - rna_idprop_ui_prop_default_set, rna_idprop_value_item_type, ) @@ -1431,27 +1429,35 @@ class WM_OT_properties_edit(Operator): prop_type_new = type(prop_value) prop_type, is_array = rna_idprop_value_item_type(prop_value) - prop_ui = rna_idprop_ui_prop_get(item, prop) + ui_data = item.id_properties_ui(prop) + ui_data.update(subtype=self.subtype, description=self.description) - if prop_type in {float, int}: - prop_ui["min"] = prop_type(self.min) - prop_ui["max"] = prop_type(self.max) - - if self.use_soft_limits: - prop_ui["soft_min"] = prop_type(self.soft_min) - prop_ui["soft_max"] = prop_type(self.soft_max) - else: - prop_ui["soft_min"] = prop_type(self.min) - prop_ui["soft_max"] = prop_type(self.max) - - if prop_type == float and is_array and self.subtype != 'NONE': - prop_ui["subtype"] = self.subtype - else: - prop_ui.pop("subtype", None) - - prop_ui["description"] = self.description - - rna_idprop_ui_prop_default_set(item, prop, default_eval) + if prop_type == int: + if type(default_eval) == str: + self.report({'WARNING'}, "Could not evaluate number from default value") + default_eval = None + elif hasattr(default_eval, "__len__"): + default_eval = [int(round(value)) for value in default_eval] + ui_data.update( + min=int(round(self.min)), + max=int(round(self.max)), + soft_min=int(round(self.soft_min)), + soft_max=int(round(self.soft_max)), + default=default_eval, + ) + elif prop_type == float: + if type(default_eval) == str: + self.report({'WARNING'}, "Could not evaluate number from default value") + default_eval = None + ui_data.update( + min=self.min, + max=self.max, + soft_min=self.soft_min, + soft_max=self.soft_max, + default=default_eval, + ) + elif prop_type == str: + ui_data.update(default=self.default) # If we have changed the type of the property, update its potential anim curves! if prop_type_old != prop_type_new: @@ -1492,7 +1498,6 @@ class WM_OT_properties_edit(Operator): def invoke(self, context, _event): from rna_prop_ui import ( - rna_idprop_ui_prop_get, rna_idprop_value_to_python, rna_idprop_value_item_type ) @@ -1526,28 +1531,22 @@ class WM_OT_properties_edit(Operator): self.default = "" # setup defaults - prop_ui = rna_idprop_ui_prop_get(item, prop, create=False) - if prop_ui: - self.min = prop_ui.get("min", -1000000000) - self.max = prop_ui.get("max", 1000000000) - self.description = prop_ui.get("description", "") - - defval = prop_ui.get("default", None) - if defval is not None: - self.default = str(rna_idprop_value_to_python(defval)) - - self.soft_min = prop_ui.get("soft_min", self.min) - self.soft_max = prop_ui.get("soft_max", self.max) + ui_data = item.id_properties_ui(prop) + rna_data = ui_data.as_dict() + self.subtype = rna_data["subtype"] + if prop_type in {int, float}: + self.min = rna_data["min"] + self.max = rna_data["max"] + self.soft_min = rna_data["soft_min"] + self.soft_max = rna_data["soft_max"] self.use_soft_limits = ( self.min != self.soft_min or self.max != self.soft_max ) + if prop_type in {int, float, str}: + self.default = str(rna_data["default"]) - subtype = prop_ui.get("subtype", None) - else: - subtype = None - - self._init_subtype(prop_type, is_array, subtype) + self._init_subtype(prop_type, is_array, self.subtype) # store for comparison self._cmp_props = self._cmp_props_get() @@ -1688,7 +1687,6 @@ class WM_OT_properties_remove(Operator): def execute(self, context): from rna_prop_ui import ( - rna_idprop_ui_prop_clear, rna_idprop_ui_prop_update, ) data_path = self.data_path @@ -1701,7 +1699,6 @@ class WM_OT_properties_remove(Operator): prop = self.property rna_idprop_ui_prop_update(item, prop) del item[prop] - rna_idprop_ui_prop_clear(item, prop) return {'FINISHED'} diff --git a/release/scripts/startup/keyingsets_builtins.py b/release/scripts/startup/keyingsets_builtins.py index ceffeaaff6c..d9811b6c0ed 100644 --- a/release/scripts/startup/keyingsets_builtins.py +++ b/release/scripts/startup/keyingsets_builtins.py @@ -523,10 +523,6 @@ class WholeCharacterMixin: # go over all custom properties for bone for prop in bone.keys(): - # ignore special "_RNA_UI" used for UI editing - if prop == "_RNA_UI": - continue - # for now, just add all of 'em prop_rna = type(bone).bl_rna.properties.get(prop, None) if prop_rna is None: diff --git a/source/blender/blenkernel/BKE_idprop.h b/source/blender/blenkernel/BKE_idprop.h index a5cb6489194..c28ac63388b 100644 --- a/source/blender/blenkernel/BKE_idprop.h +++ b/source/blender/blenkernel/BKE_idprop.h @@ -32,6 +32,7 @@ struct BlendLibReader; struct BlendWriter; struct ID; struct IDProperty; +struct IDPropertyUIData; typedef union IDPropertyTemplate { int i; @@ -183,6 +184,10 @@ void IDP_Reset(struct IDProperty *prop, const struct IDProperty *reference); # define IDP_Id(prop) ((ID *)(prop)->data.pointer) #endif +int IDP_coerce_to_int_or_zero(const struct IDProperty *prop); +float IDP_coerce_to_float_or_zero(const struct IDProperty *prop); +double IDP_coerce_to_double_or_zero(const struct IDProperty *prop); + /** * Call a callback for each idproperty in the hierarchy under given root one (included). * @@ -209,6 +214,28 @@ void IDP_BlendReadData_impl(struct BlendDataReader *reader, void IDP_BlendReadLib(struct BlendLibReader *reader, struct IDProperty *prop); void IDP_BlendReadExpand(struct BlendExpander *expander, struct IDProperty *prop); +typedef enum eIDPropertyUIDataType { + /** Other properties types that don't support RNA UI data. */ + IDP_UI_DATA_TYPE_UNSUPPORTED = -1, + /** IDP_INT or IDP_ARRAY with subtype IDP_INT. */ + IDP_UI_DATA_TYPE_INT = 0, + /** IDP_FLOAT and IDP_DOUBLE or IDP_ARRAY properties with a float or double subtypes. */ + IDP_UI_DATA_TYPE_FLOAT = 1, + /** IDP_STRING properties. */ + IDP_UI_DATA_TYPE_STRING = 2, + /** IDP_ID. */ + IDP_UI_DATA_TYPE_ID = 3, +} eIDPropertyUIDataType; + +bool IDP_ui_data_supported(const struct IDProperty *prop); +eIDPropertyUIDataType IDP_ui_data_type(const struct IDProperty *prop); +void IDP_ui_data_free(struct IDProperty *prop); +void IDP_ui_data_free_unique_contents(struct IDPropertyUIData *ui_data, + eIDPropertyUIDataType type, + const struct IDPropertyUIData *other); +struct IDPropertyUIData *IDP_ui_data_ensure(struct IDProperty *prop); +struct IDPropertyUIData *IDP_ui_data_copy(const struct IDProperty *prop); + #ifdef __cplusplus } #endif diff --git a/source/blender/blenkernel/intern/idprop.c b/source/blender/blenkernel/intern/idprop.c index 7a88a00c44f..786df14756a 100644 --- a/source/blender/blenkernel/intern/idprop.c +++ b/source/blender/blenkernel/intern/idprop.c @@ -21,6 +21,7 @@ * \ingroup bke */ +#include #include #include #include @@ -274,6 +275,43 @@ void IDP_FreeArray(IDProperty *prop) } } +IDPropertyUIData *IDP_ui_data_copy(const IDProperty *prop) +{ + IDPropertyUIData *dst_ui_data = MEM_dupallocN(prop->ui_data); + + /* Copy extra type specific data. */ + switch (IDP_ui_data_type(prop)) { + case IDP_UI_DATA_TYPE_STRING: { + const IDPropertyUIDataString *src = (const IDPropertyUIDataString *)prop->ui_data; + IDPropertyUIDataString *dst = (IDPropertyUIDataString *)dst_ui_data; + dst->default_value = MEM_dupallocN(src->default_value); + break; + } + case IDP_UI_DATA_TYPE_ID: { + break; + } + case IDP_UI_DATA_TYPE_INT: { + const IDPropertyUIDataInt *src = (const IDPropertyUIDataInt *)prop->ui_data; + IDPropertyUIDataInt *dst = (IDPropertyUIDataInt *)dst_ui_data; + dst->default_array = MEM_dupallocN(src->default_array); + break; + } + case IDP_UI_DATA_TYPE_FLOAT: { + const IDPropertyUIDataFloat *src = (const IDPropertyUIDataFloat *)prop->ui_data; + IDPropertyUIDataFloat *dst = (IDPropertyUIDataFloat *)dst_ui_data; + dst->default_array = MEM_dupallocN(src->default_array); + break; + } + case IDP_UI_DATA_TYPE_UNSUPPORTED: { + break; + } + } + + dst_ui_data->description = MEM_dupallocN(prop->ui_data->description); + + return dst_ui_data; +} + static IDProperty *idp_generic_copy(const IDProperty *prop, const int UNUSED(flag)) { IDProperty *newp = MEM_callocN(sizeof(IDProperty), __func__); @@ -284,6 +322,10 @@ static IDProperty *idp_generic_copy(const IDProperty *prop, const int UNUSED(fla newp->data.val = prop->data.val; newp->data.val2 = prop->data.val2; + if (prop->ui_data != NULL) { + newp->ui_data = IDP_ui_data_copy(prop); + } + return newp; } @@ -679,6 +721,7 @@ bool IDP_InsertToGroup(IDProperty *group, IDProperty *previous, IDProperty *pnew void IDP_RemoveFromGroup(IDProperty *group, IDProperty *prop) { BLI_assert(group->type == IDP_GROUP); + BLI_assert(BLI_findindex(&group->data.group, prop) != -1); group->len--; BLI_remlink(&group->data.group, prop); @@ -725,6 +768,60 @@ static void IDP_FreeGroup(IDProperty *prop, const bool do_id_user) /** \name Main Functions (IDProperty Main API) * \{ */ +/** + * Return an int from an IDProperty with a compatible type. This should be avoided, but + * it's sometimes necessary, for example when legacy files have incorrect property types. + */ +int IDP_coerce_to_int_or_zero(const IDProperty *prop) +{ + switch (prop->type) { + case IDP_INT: + return IDP_Int(prop); + case IDP_DOUBLE: + return (int)IDP_Double(prop); + case IDP_FLOAT: + return (int)IDP_Float(prop); + default: + return 0; + } +} + +/** + * Return a double from an IDProperty with a compatible type. This should be avoided, but + * it's sometimes necessary, for example when legacy files have incorrect property types. + */ +double IDP_coerce_to_double_or_zero(const IDProperty *prop) +{ + switch (prop->type) { + case IDP_DOUBLE: + return IDP_Double(prop); + case IDP_FLOAT: + return (double)IDP_Float(prop); + case IDP_INT: + return (double)IDP_Int(prop); + default: + return 0.0; + } +} + +/** + * Return a float from an IDProperty with a compatible type. This should be avoided, but + * it's sometimes necessary, for example when legacy files have incorrect property types. + */ +float IDP_coerce_to_float_or_zero(const IDProperty *prop) +{ + switch (prop->type) { + case IDP_FLOAT: + return IDP_Float(prop); + case IDP_DOUBLE: + return (float)IDP_Double(prop); + case IDP_INT: + return (float)IDP_Int(prop); + default: + return 0.0f; + } +} + IDProperty *IDP_CopyProperty_ex(const IDProperty *prop, const int flag) { switch (prop->type) { @@ -999,6 +1096,85 @@ IDProperty *IDP_New(const char type, const IDPropertyTemplate *val, const char * return prop; } +/** + * Free allocated pointers in the UI data that isn't shared with the UI data in the #other + * argument. Useful for returning early on failure when updating UI data in place, or when + * replacing a subset of the UI data's allocated pointers. + */ +void IDP_ui_data_free_unique_contents(IDPropertyUIData *ui_data, + const eIDPropertyUIDataType type, + const IDPropertyUIData *other) +{ + if (ui_data->description != other->description) { + MEM_SAFE_FREE(ui_data->description); + } + + switch (type) { + case IDP_UI_DATA_TYPE_STRING: { + const IDPropertyUIDataString *other_string = (const IDPropertyUIDataString *)other; + IDPropertyUIDataString *ui_data_string = (IDPropertyUIDataString *)ui_data; + if (ui_data_string->default_value != other_string->default_value) { + MEM_SAFE_FREE(ui_data_string->default_value); + } + break; + } + case IDP_UI_DATA_TYPE_ID: { + break; + } + case IDP_UI_DATA_TYPE_INT: { + const IDPropertyUIDataInt *other_int = (const IDPropertyUIDataInt *)other; + IDPropertyUIDataInt *ui_data_int = (IDPropertyUIDataInt *)ui_data; + if (ui_data_int->default_array != other_int->default_array) { + MEM_SAFE_FREE(ui_data_int->default_array); + } + break; + } + case IDP_UI_DATA_TYPE_FLOAT: { + const IDPropertyUIDataFloat *other_float = (const IDPropertyUIDataFloat *)other; + IDPropertyUIDataFloat *ui_data_float = (IDPropertyUIDataFloat *)ui_data; + if (ui_data_float->default_array != other_float->default_array) { + MEM_SAFE_FREE(ui_data_float->default_array); + } + break; + } + case IDP_UI_DATA_TYPE_UNSUPPORTED: { + break; + } + } +} + +void IDP_ui_data_free(IDProperty *prop) +{ + switch (IDP_ui_data_type(prop)) { + case IDP_UI_DATA_TYPE_STRING: { + IDPropertyUIDataString *ui_data_string = (IDPropertyUIDataString *)prop->ui_data; + MEM_SAFE_FREE(ui_data_string->default_value); + break; + } + case IDP_UI_DATA_TYPE_ID: { + break; + } + case IDP_UI_DATA_TYPE_INT: { + IDPropertyUIDataInt *ui_data_int = (IDPropertyUIDataInt *)prop->ui_data; + MEM_SAFE_FREE(ui_data_int->default_array); + break; + } + case IDP_UI_DATA_TYPE_FLOAT: { + IDPropertyUIDataFloat *ui_data_float = (IDPropertyUIDataFloat *)prop->ui_data; + MEM_SAFE_FREE(ui_data_float->default_array); + break; + } + case IDP_UI_DATA_TYPE_UNSUPPORTED: { + break; + } + } + + MEM_SAFE_FREE(prop->ui_data->description); + + MEM_freeN(prop->ui_data); + prop->ui_data = NULL; +} + /** * \note This will free allocated data, all child properties of arrays and groups, and unlink IDs! * But it does not free the actual IDProperty struct itself. @@ -1024,6 +1200,10 @@ void IDP_FreePropertyContent_ex(IDProperty *prop, const bool do_id_user) } break; } + + if (prop->ui_data != NULL) { + IDP_ui_data_free(prop); + } } void IDP_FreePropertyContent(IDProperty *prop) @@ -1104,6 +1284,48 @@ void IDP_foreach_property(IDProperty *id_property_root, void IDP_WriteProperty_OnlyData(const IDProperty *prop, BlendWriter *writer); +static void write_ui_data(const IDProperty *prop, BlendWriter *writer) +{ + IDPropertyUIData *ui_data = prop->ui_data; + + BLO_write_string(writer, ui_data->description); + + switch (IDP_ui_data_type(prop)) { + case IDP_UI_DATA_TYPE_STRING: { + IDPropertyUIDataString *ui_data_string = (IDPropertyUIDataString *)ui_data; + BLO_write_string(writer, ui_data_string->default_value); + BLO_write_struct(writer, IDPropertyUIDataString, ui_data); + break; + } + case IDP_UI_DATA_TYPE_ID: { + BLO_write_struct(writer, IDPropertyUIDataID, ui_data); + break; + } + case IDP_UI_DATA_TYPE_INT: { + IDPropertyUIDataInt *ui_data_int = (IDPropertyUIDataInt *)ui_data; + if (prop->type == IDP_ARRAY) { + BLO_write_int32_array( + writer, (uint)ui_data_int->default_array_len, (int32_t *)ui_data_int->default_array); + } + BLO_write_struct(writer, IDPropertyUIDataInt, ui_data); + break; + } + case IDP_UI_DATA_TYPE_FLOAT: { + IDPropertyUIDataFloat *ui_data_float = (IDPropertyUIDataFloat *)ui_data; + if (prop->type == IDP_ARRAY) { + BLO_write_double_array( + writer, (uint)ui_data_float->default_array_len, ui_data_float->default_array); + } + BLO_write_struct(writer, IDPropertyUIDataFloat, ui_data); + break; + } + case IDP_UI_DATA_TYPE_UNSUPPORTED: { + BLI_assert_unreachable(); + break; + } + } +} + static void IDP_WriteArray(const IDProperty *prop, BlendWriter *writer) { /* Remember to set #IDProperty.totallen to len in the linking code! */ @@ -1165,6 +1387,9 @@ void IDP_WriteProperty_OnlyData(const IDProperty *prop, BlendWriter *writer) IDP_WriteIDPArray(prop, writer); break; } + if (prop->ui_data != NULL) { + write_ui_data(prop, writer); + } } void IDP_BlendWrite(BlendWriter *writer, const IDProperty *prop) @@ -1175,6 +1400,43 @@ void IDP_BlendWrite(BlendWriter *writer, const IDProperty *prop) static void IDP_DirectLinkProperty(IDProperty *prop, BlendDataReader *reader); +static void read_ui_data(IDProperty *prop, BlendDataReader *reader) +{ + BLO_read_data_address(reader, &prop->ui_data); + BLO_read_data_address(reader, &prop->ui_data->description); + + switch (IDP_ui_data_type(prop)) { + case IDP_UI_DATA_TYPE_STRING: { + IDPropertyUIDataString *ui_data_string = (IDPropertyUIDataString *)prop->ui_data; + BLO_read_data_address(reader, &ui_data_string->default_value); + break; + } + case IDP_UI_DATA_TYPE_ID: { + break; + } + case IDP_UI_DATA_TYPE_INT: { + IDPropertyUIDataInt *ui_data_int = (IDPropertyUIDataInt *)prop->ui_data; + if (prop->type == IDP_ARRAY) { + BLO_read_int32_array( + reader, ui_data_int->default_array_len, (int **)&ui_data_int->default_array); + } + break; + } + case IDP_UI_DATA_TYPE_FLOAT: { + IDPropertyUIDataFloat *ui_data_float = (IDPropertyUIDataFloat *)prop->ui_data; + if (prop->type == IDP_ARRAY) { + BLO_read_double_array( + reader, ui_data_float->default_array_len, (double **)&ui_data_float->default_array); + } + break; + } + case IDP_UI_DATA_TYPE_UNSUPPORTED: { + BLI_assert_unreachable(); + break; + } + } +} + static void IDP_DirectLinkIDPArray(IDProperty *prop, BlendDataReader *reader) { /* since we didn't save the extra buffer, set totallen to len */ @@ -1279,6 +1541,10 @@ static void IDP_DirectLinkProperty(IDProperty *prop, BlendDataReader *reader) prop->subtype = 0; IDP_Int(prop) = 0; } + + if (prop->ui_data != NULL) { + read_ui_data(prop, reader); + } } void IDP_BlendReadData_impl(BlendDataReader *reader, IDProperty **prop, const char *caller_func_id) @@ -1358,4 +1624,74 @@ void IDP_BlendReadExpand(struct BlendExpander *expander, IDProperty *prop) } } +eIDPropertyUIDataType IDP_ui_data_type(const IDProperty *prop) +{ + if (prop->type == IDP_STRING) { + return IDP_UI_DATA_TYPE_STRING; + } + if (prop->type == IDP_ID) { + return IDP_UI_DATA_TYPE_ID; + } + if (prop->type == IDP_INT || (prop->type == IDP_ARRAY && prop->subtype == IDP_INT)) { + return IDP_UI_DATA_TYPE_INT; + } + if (ELEM(prop->type, IDP_FLOAT, IDP_DOUBLE) || + (prop->type == IDP_ARRAY && ELEM(prop->subtype, IDP_FLOAT, IDP_DOUBLE))) { + return IDP_UI_DATA_TYPE_FLOAT; + } + return IDP_UI_DATA_TYPE_UNSUPPORTED; +} + +bool IDP_ui_data_supported(const IDProperty *prop) +{ + return IDP_ui_data_type(prop) != IDP_UI_DATA_TYPE_UNSUPPORTED; +} + +IDPropertyUIData *IDP_ui_data_ensure(IDProperty *prop) +{ + if (prop->ui_data != NULL) { + return prop->ui_data; + } + + switch (IDP_ui_data_type(prop)) { + case IDP_UI_DATA_TYPE_STRING: { + prop->ui_data = MEM_callocN(sizeof(IDPropertyUIDataString), __func__); + break; + } + case IDP_UI_DATA_TYPE_ID: { + IDPropertyUIDataID *ui_data = MEM_callocN(sizeof(IDPropertyUIDataID), __func__); + prop->ui_data = (IDPropertyUIData *)ui_data; + break; + } + case IDP_UI_DATA_TYPE_INT: { + IDPropertyUIDataInt *ui_data = MEM_callocN(sizeof(IDPropertyUIDataInt), __func__); + ui_data->min = -INT_MAX; + ui_data->max = INT_MAX; + ui_data->soft_min = -INT_MAX; + ui_data->soft_max = INT_MAX; + ui_data->step = 1; + prop->ui_data = (IDPropertyUIData *)ui_data; + break; + } + case IDP_UI_DATA_TYPE_FLOAT: { + IDPropertyUIDataFloat *ui_data = MEM_callocN(sizeof(IDPropertyUIDataFloat), __func__); + ui_data->min = -FLT_MAX; + ui_data->max = FLT_MAX; + ui_data->soft_min = -FLT_MAX; + ui_data->soft_max = FLT_MAX; + ui_data->step = 1.0f; + ui_data->precision = 3; + prop->ui_data = (IDPropertyUIData *)ui_data; + break; + } + case IDP_UI_DATA_TYPE_UNSUPPORTED: { + /* UI data not supported for remaining types, this shouldn't be called in those cases. */ + BLI_assert_unreachable(); + break; + } + } + + return prop->ui_data; +} + /** \} */ diff --git a/source/blender/blenloader/intern/versioning_300.c b/source/blender/blenloader/intern/versioning_300.c index aee6d798799..0253316dc3a 100644 --- a/source/blender/blenloader/intern/versioning_300.c +++ b/source/blender/blenloader/intern/versioning_300.c @@ -20,6 +20,10 @@ /* allow readfile to use deprecated functionality */ #define DNA_DEPRECATED_ALLOW +#include + +#include "MEM_guardedalloc.h" + #include "BLI_listbase.h" #include "BLI_math_vector.h" #include "BLI_path_util.h" @@ -46,10 +50,14 @@ #include "BKE_deform.h" #include "BKE_fcurve.h" #include "BKE_fcurve_driver.h" +#include "BKE_idprop.h" #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_node.h" +#include "RNA_access.h" +#include "RNA_enum_types.h" + #include "BLO_readfile.h" #include "MEM_guardedalloc.h" #include "readfile.h" @@ -60,6 +68,238 @@ #include "versioning_common.h" +static IDProperty *idproperty_find_ui_container(IDProperty *idprop_group) +{ + LISTBASE_FOREACH (IDProperty *, prop, &idprop_group->data.group) { + if (prop->type == IDP_GROUP && STREQ(prop->name, "_RNA_UI")) { + return prop; + } + } + return NULL; +} + +static void version_idproperty_move_data_int(IDPropertyUIDataInt *ui_data, + const IDProperty *prop_ui_data) +{ + IDProperty *min = IDP_GetPropertyFromGroup(prop_ui_data, "min"); + if (min != NULL) { + ui_data->min = ui_data->soft_min = IDP_coerce_to_int_or_zero(min); + } + IDProperty *max = IDP_GetPropertyFromGroup(prop_ui_data, "max"); + if (max != NULL) { + ui_data->max = ui_data->soft_max = IDP_coerce_to_int_or_zero(max); + } + IDProperty *soft_min = IDP_GetPropertyFromGroup(prop_ui_data, "soft_min"); + if (soft_min != NULL) { + ui_data->soft_min = IDP_coerce_to_int_or_zero(soft_min); + ui_data->soft_min = MIN2(ui_data->soft_min, ui_data->min); + } + IDProperty *soft_max = IDP_GetPropertyFromGroup(prop_ui_data, "soft_max"); + if (soft_max != NULL) { + ui_data->soft_max = IDP_coerce_to_int_or_zero(soft_max); + ui_data->soft_max = MAX2(ui_data->soft_max, ui_data->max); + } + IDProperty *step = IDP_GetPropertyFromGroup(prop_ui_data, "step"); + if (step != NULL) { + ui_data->step = IDP_coerce_to_int_or_zero(soft_max); + } + IDProperty *default_value = IDP_GetPropertyFromGroup(prop_ui_data, "default"); + if (default_value != NULL) { + if (default_value->type == IDP_ARRAY) { + if (default_value->subtype == IDP_INT) { + ui_data->default_array = MEM_dupallocN(IDP_Array(default_value)); + ui_data->default_array_len = default_value->len; + } + } + else if (default_value->type == IDP_INT) { + ui_data->default_value = IDP_coerce_to_int_or_zero(default_value); + } + } +} + +static void version_idproperty_move_data_float(IDPropertyUIDataFloat *ui_data, + const IDProperty *prop_ui_data) +{ + IDProperty *min = IDP_GetPropertyFromGroup(prop_ui_data, "min"); + if (min != NULL) { + ui_data->min = ui_data->soft_min = IDP_coerce_to_double_or_zero(min); + } + IDProperty *max = IDP_GetPropertyFromGroup(prop_ui_data, "max"); + if (max != NULL) { + ui_data->max = ui_data->soft_max = IDP_coerce_to_double_or_zero(min); + } + IDProperty *soft_min = IDP_GetPropertyFromGroup(prop_ui_data, "soft_min"); + if (soft_min != NULL) { + ui_data->soft_min = IDP_coerce_to_double_or_zero(soft_min); + ui_data->soft_min = MAX2(ui_data->soft_min, ui_data->min); + } + IDProperty *soft_max = IDP_GetPropertyFromGroup(prop_ui_data, "soft_max"); + if (soft_max != NULL) { + ui_data->soft_max = IDP_coerce_to_double_or_zero(soft_max); + ui_data->soft_max = MIN2(ui_data->soft_max, ui_data->max); + } + IDProperty *step = IDP_GetPropertyFromGroup(prop_ui_data, "step"); + if (step != NULL) { + ui_data->step = IDP_coerce_to_float_or_zero(step); + } + IDProperty *precision = IDP_GetPropertyFromGroup(prop_ui_data, "precision"); + if (precision != NULL) { + ui_data->precision = IDP_coerce_to_int_or_zero(precision); + } + IDProperty *default_value = IDP_GetPropertyFromGroup(prop_ui_data, "default"); + if (default_value != NULL) { + if (default_value->type == IDP_ARRAY) { + if (ELEM(default_value->subtype, IDP_FLOAT, IDP_DOUBLE)) { + ui_data->default_array = MEM_dupallocN(IDP_Array(default_value)); + ui_data->default_array_len = default_value->len; + } + } + else if (ELEM(default_value->type, IDP_DOUBLE, IDP_FLOAT)) { + ui_data->default_value = IDP_coerce_to_double_or_zero(default_value); + } + } +} + +static void version_idproperty_move_data_string(IDPropertyUIDataString *ui_data, + const IDProperty *prop_ui_data) +{ + IDProperty *default_value = IDP_GetPropertyFromGroup(prop_ui_data, "default"); + if (default_value != NULL && default_value->type == IDP_STRING) { + ui_data->default_value = BLI_strdup(IDP_String(default_value)); + } +} + +static void version_idproperty_ui_data(IDProperty *idprop_group) +{ + if (idprop_group == NULL) { /* NULL check here to reduce verbosity of calls to this function. */ + return; + } + + IDProperty *ui_container = idproperty_find_ui_container(idprop_group); + if (ui_container == NULL) { + return; + } + + LISTBASE_FOREACH (IDProperty *, prop, &idprop_group->data.group) { + IDProperty *prop_ui_data = IDP_GetPropertyFromGroup(ui_container, prop->name); + if (prop_ui_data == NULL) { + continue; + } + + if (!IDP_ui_data_supported(prop)) { + continue; + } + + IDPropertyUIData *ui_data = IDP_ui_data_ensure(prop); + + IDProperty *subtype = IDP_GetPropertyFromGroup(prop_ui_data, "subtype"); + if (subtype != NULL && subtype->type == IDP_STRING) { + const char *subtype_string = IDP_String(subtype); + int result = PROP_NONE; + RNA_enum_value_from_id(rna_enum_property_subtype_items, subtype_string, &result); + ui_data->rna_subtype = result; + } + + IDProperty *description = IDP_GetPropertyFromGroup(prop_ui_data, "description"); + if (description != NULL && description->type == IDP_STRING) { + ui_data->description = BLI_strdup(IDP_String(description)); + } + + /* Type specific data. */ + switch (IDP_ui_data_type(prop)) { + case IDP_UI_DATA_TYPE_STRING: + version_idproperty_move_data_string((IDPropertyUIDataString *)ui_data, prop_ui_data); + break; + case IDP_UI_DATA_TYPE_ID: + break; + case IDP_UI_DATA_TYPE_INT: + version_idproperty_move_data_int((IDPropertyUIDataInt *)ui_data, prop_ui_data); + break; + case IDP_UI_DATA_TYPE_FLOAT: + version_idproperty_move_data_float((IDPropertyUIDataFloat *)ui_data, prop_ui_data); + break; + case IDP_UI_DATA_TYPE_UNSUPPORTED: + BLI_assert_unreachable(); + break; + } + + IDP_FreeFromGroup(ui_container, prop_ui_data); + } + + IDP_FreeFromGroup(idprop_group, ui_container); +} + +static void do_versions_idproperty_bones_recursive(Bone *bone) +{ + version_idproperty_ui_data(bone->prop); + LISTBASE_FOREACH (Bone *, child_bone, &bone->childbase) { + do_versions_idproperty_bones_recursive(child_bone); + } +} + +/** + * For every data block that supports them, initialize the new IDProperty UI data struct based on + * the old more complicated storage. Assumes only the top level of IDProperties below the parent + * group had UI data in a "_RNA_UI" group. + * + * \note The following IDProperty groups in DNA aren't exposed in the UI or are runtime-only, so + * they don't have UI data: wmOperator, bAddon, bUserMenuItem_Op, wmKeyMapItem, wmKeyConfigPref, + * uiList, FFMpegCodecData, View3DShading, bToolRef, TimeMarker, ViewLayer, bPoseChannel. + */ +static void do_versions_idproperty_ui_data(Main *bmain) +{ + /* ID data. */ + ID *id; + FOREACH_MAIN_ID_BEGIN (bmain, id) { + IDProperty *idprop_group = IDP_GetProperties(id, false); + if (idprop_group == NULL) { + continue; + } + version_idproperty_ui_data(idprop_group); + } + FOREACH_MAIN_ID_END; + + /* Bones. */ + LISTBASE_FOREACH (bArmature *, armature, &bmain->armatures) { + LISTBASE_FOREACH (Bone *, bone, &armature->bonebase) { + do_versions_idproperty_bones_recursive(bone); + } + } + + /* Nodes and node sockets. */ + LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) { + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { + version_idproperty_ui_data(node->prop); + } + LISTBASE_FOREACH (bNodeSocket *, socket, &ntree->inputs) { + version_idproperty_ui_data(socket->prop); + } + LISTBASE_FOREACH (bNodeSocket *, socket, &ntree->outputs) { + version_idproperty_ui_data(socket->prop); + } + } + + /* The UI data from exposed node modifier properties is just copied from the corresponding node + * group, but the copying only runs when necessary, so we still need to version UI data here. */ + LISTBASE_FOREACH (Object *, ob, &bmain->objects) { + LISTBASE_FOREACH (ModifierData *, md, &ob->modifiers) { + if (md->type == eModifierType_Nodes) { + NodesModifierData *nmd = (NodesModifierData *)md; + version_idproperty_ui_data(nmd->settings.properties); + } + } + } + + /* Sequences. */ + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + if (scene->ed != NULL) { + LISTBASE_FOREACH (Sequence *, seq, &scene->ed->seqbase) { + version_idproperty_ui_data(seq->prop); + } + } + } +} + static void sort_linked_ids(Main *bmain) { ListBase *lb; @@ -252,6 +492,7 @@ void do_versions_after_linking_300(Main *bmain, ReportList *UNUSED(reports)) */ { /* Keep this block, even when empty. */ + do_versions_idproperty_ui_data(bmain); } } diff --git a/source/blender/io/alembic/exporter/abc_custom_props.cc b/source/blender/io/alembic/exporter/abc_custom_props.cc index 4ea2fd03fff..e27a9012dab 100644 --- a/source/blender/io/alembic/exporter/abc_custom_props.cc +++ b/source/blender/io/alembic/exporter/abc_custom_props.cc @@ -60,9 +60,6 @@ void CustomPropertiesExporter::write_all(const IDProperty *group) /* Loop over the properties, just like IDP_foreach_property() does, but without the recursion. */ LISTBASE_FOREACH (IDProperty *, id_property, &group->data.group) { - if (STREQ(id_property->name, "_RNA_UI")) { - continue; - } write(id_property); } } diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h index 10a5a0f1c47..5b7c99f2545 100644 --- a/source/blender/makesdna/DNA_ID.h +++ b/source/blender/makesdna/DNA_ID.h @@ -59,6 +59,58 @@ typedef struct DrawDataList { struct DrawData *first, *last; } DrawDataList; +typedef struct IDPropertyUIData { + /** Tooltip / property description pointer. Owned by the IDProperty. */ + char *description; + /** RNA subtype, used for every type except string properties (PropertySubType). */ + int rna_subtype; + + char _pad[4]; +} IDPropertyUIData; + +/* IDP_UI_DATA_TYPE_INT */ +typedef struct IDPropertyUIDataInt { + IDPropertyUIData base; + int *default_array; /* Only for array properties. */ + int default_array_len; + char _pad[4]; + + int min; + int max; + int soft_min; + int soft_max; + int step; + int default_value; +} IDPropertyUIDataInt; + +/* IDP_UI_DATA_TYPE_FLOAT */ +typedef struct IDPropertyUIDataFloat { + IDPropertyUIData base; + double *default_array; /* Only for array properties. */ + int default_array_len; + char _pad[4]; + + float step; + int precision; + + double min; + double max; + double soft_min; + double soft_max; + double default_value; +} IDPropertyUIDataFloat; + +/* IDP_UI_DATA_TYPE_STRING */ +typedef struct IDPropertyUIDataString { + IDPropertyUIData base; + char *default_value; +} IDPropertyUIDataString; + +/* IDP_UI_DATA_TYPE_ID */ +typedef struct IDPropertyUIDataID { + IDPropertyUIData base; +} IDPropertyUIDataID; + typedef struct IDPropertyData { void *pointer; ListBase group; @@ -87,6 +139,8 @@ typedef struct IDProperty { /* totallen is total length of allocated array/string, including a buffer. * Note that the buffering is mild; the code comes from python's list implementation. */ int totallen; + + IDPropertyUIData *ui_data; } IDProperty; #define MAX_IDPROP_NAME 64 diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h index b943a8fad5a..abbe609d0ef 100644 --- a/source/blender/makesrna/RNA_access.h +++ b/source/blender/makesrna/RNA_access.h @@ -1006,7 +1006,7 @@ int RNA_property_int_get_index(PointerRNA *ptr, PropertyRNA *prop, int index); void RNA_property_int_set_array(PointerRNA *ptr, PropertyRNA *prop, const int *values); void RNA_property_int_set_index(PointerRNA *ptr, PropertyRNA *prop, int index, int value); int RNA_property_int_get_default(PointerRNA *ptr, PropertyRNA *prop); -bool RNA_property_int_set_default(PointerRNA *ptr, PropertyRNA *prop, int value); +bool RNA_property_int_set_default(PropertyRNA *prop, int value); void RNA_property_int_get_default_array(PointerRNA *ptr, PropertyRNA *prop, int *values); int RNA_property_int_get_default_index(PointerRNA *ptr, PropertyRNA *prop, int index); @@ -1018,7 +1018,7 @@ float RNA_property_float_get_index(PointerRNA *ptr, PropertyRNA *prop, int index void RNA_property_float_set_array(PointerRNA *ptr, PropertyRNA *prop, const float *values); void RNA_property_float_set_index(PointerRNA *ptr, PropertyRNA *prop, int index, float value); float RNA_property_float_get_default(PointerRNA *ptr, PropertyRNA *prop); -bool RNA_property_float_set_default(PointerRNA *ptr, PropertyRNA *prop, float value); +bool RNA_property_float_set_default(PropertyRNA *prop, float value); void RNA_property_float_get_default_array(PointerRNA *ptr, PropertyRNA *prop, float *values); float RNA_property_float_get_default_index(PointerRNA *ptr, PropertyRNA *prop, int index); @@ -1028,7 +1028,7 @@ char *RNA_property_string_get_alloc( void RNA_property_string_set(PointerRNA *ptr, PropertyRNA *prop, const char *value); void RNA_property_string_set_bytes(PointerRNA *ptr, PropertyRNA *prop, const char *value, int len); int RNA_property_string_length(PointerRNA *ptr, PropertyRNA *prop); -void RNA_property_string_get_default(PointerRNA *ptr, PropertyRNA *prop, char *value); +void RNA_property_string_get_default(PropertyRNA *prop, char *value, int max_len); char *RNA_property_string_get_default_alloc( PointerRNA *ptr, PropertyRNA *prop, char *fixedbuf, int fixedlen, int *r_len); int RNA_property_string_default_length(PointerRNA *ptr, PropertyRNA *prop); diff --git a/source/blender/makesrna/intern/rna_access.c b/source/blender/makesrna/intern/rna_access.c index 8fa2d0ed849..2d6d5408aa0 100644 --- a/source/blender/makesrna/intern/rna_access.c +++ b/source/blender/makesrna/intern/rna_access.c @@ -246,130 +246,6 @@ void rna_idproperty_touch(IDProperty *idprop) idprop->flag &= ~IDP_FLAG_GHOST; } -static IDProperty *rna_idproperty_ui_container(PropertyRNA *prop) -{ - IDProperty *idprop; - - for (idprop = ((IDProperty *)prop)->prev; idprop; idprop = idprop->prev) { - if (STREQ(RNA_IDP_UI, idprop->name)) { - break; - } - } - - if (idprop == NULL) { - for (idprop = ((IDProperty *)prop)->next; idprop; idprop = idprop->next) { - if (STREQ(RNA_IDP_UI, idprop->name)) { - break; - } - } - } - - return idprop; -} - -/* return a UI local ID prop definition for this prop */ -static const IDProperty *rna_idproperty_ui(const PropertyRNA *prop) -{ - IDProperty *idprop = rna_idproperty_ui_container((PropertyRNA *)prop); - - if (idprop) { - return IDP_GetPropertyTypeFromGroup(idprop, ((IDProperty *)prop)->name, IDP_GROUP); - } - - return NULL; -} - -/* return or create a UI local ID prop definition for this prop */ -static IDProperty *rna_idproperty_ui_ensure(PointerRNA *ptr, PropertyRNA *prop, bool create) -{ - IDProperty *idprop = rna_idproperty_ui_container(prop); - IDPropertyTemplate dummy = {0}; - - if (idprop == NULL && create) { - IDProperty *props = RNA_struct_idprops(ptr, false); - - /* Sanity check: props is the actual container of this property. */ - if (props != NULL && BLI_findindex(&props->data.group, prop) >= 0) { - idprop = IDP_New(IDP_GROUP, &dummy, RNA_IDP_UI); - - if (!IDP_AddToGroup(props, idprop)) { - IDP_FreePropertyContent(idprop); - return NULL; - } - } - } - - if (idprop) { - const char *name = ((IDProperty *)prop)->name; - IDProperty *rv = IDP_GetPropertyTypeFromGroup(idprop, name, IDP_GROUP); - - if (rv == NULL && create) { - rv = IDP_New(IDP_GROUP, &dummy, name); - - if (!IDP_AddToGroup(idprop, rv)) { - IDP_FreePropertyContent(rv); - return NULL; - } - } - - return rv; - } - - return NULL; -} - -static bool rna_idproperty_ui_set_default(PointerRNA *ptr, - PropertyRNA *prop, - const char type, - IDPropertyTemplate *value) -{ - BLI_assert(ELEM(type, IDP_INT, IDP_DOUBLE)); - - if (prop->magic == RNA_MAGIC) { - return false; - } - - /* attempt to get the local ID values */ - IDProperty *idp_ui = rna_idproperty_ui_ensure(ptr, prop, value != NULL); - - if (idp_ui == NULL) { - return (value == NULL); - } - - IDProperty *item = IDP_GetPropertyTypeFromGroup(idp_ui, "default", type); - - if (value == NULL) { - if (item != NULL) { - IDP_RemoveFromGroup(idp_ui, item); - } - } - else { - if (item != NULL) { - switch (type) { - case IDP_INT: - IDP_Int(item) = value->i; - break; - case IDP_DOUBLE: - IDP_Double(item) = value->d; - break; - default: - BLI_assert(false); - return false; - } - } - else { - item = IDP_New(type, value, "default"); - - if (!IDP_AddToGroup(idp_ui, item)) { - IDP_FreePropertyContent(item); - return false; - } - } - } - - return true; -} - IDProperty **RNA_struct_idprops_p(PointerRNA *ptr) { StructRNA *type = ptr->type; @@ -693,28 +569,17 @@ static const char *rna_ensure_property_identifier(const PropertyRNA *prop) static const char *rna_ensure_property_description(const PropertyRNA *prop) { - const char *description = NULL; - if (prop->magic == RNA_MAGIC) { - description = prop->description; - } - else { - /* attempt to get the local ID values */ - const IDProperty *idp_ui = rna_idproperty_ui(prop); - - if (idp_ui) { - IDProperty *item = IDP_GetPropertyTypeFromGroup(idp_ui, "description", IDP_STRING); - if (item) { - description = IDP_String(item); - } - } - - if (description == NULL) { - description = ((IDProperty *)prop)->name; /* XXX: not correct. */ - } + return prop->description; } - return description; + const IDProperty *idprop = (const IDProperty *)prop; + if (idprop->ui_data) { + const IDPropertyUIData *ui_data = idprop->ui_data; + return ui_data->description; + } + + return ""; } static const char *rna_ensure_property_name(const PropertyRNA *prop) @@ -1196,19 +1061,9 @@ PropertySubType RNA_property_subtype(PropertyRNA *prop) if (prop->magic != RNA_MAGIC) { IDProperty *idprop = (IDProperty *)prop; - if (ELEM(idprop->type, IDP_INT, IDP_FLOAT, IDP_DOUBLE) || - ((idprop->type == IDP_ARRAY) && ELEM(idprop->subtype, IDP_INT, IDP_FLOAT, IDP_DOUBLE))) { - const IDProperty *idp_ui = rna_idproperty_ui(prop); - - if (idp_ui) { - IDProperty *item = IDP_GetPropertyTypeFromGroup(idp_ui, "subtype", IDP_STRING); - - if (item) { - int result = PROP_NONE; - RNA_enum_value_from_id(rna_enum_property_subtype_items, IDP_String(item), &result); - return (PropertySubType)result; - } - } + if (idprop->ui_data) { + IDPropertyUIData *ui_data = idprop->ui_data; + return (PropertySubType)ui_data->rna_subtype; } } @@ -1387,20 +1242,17 @@ void RNA_property_int_range(PointerRNA *ptr, PropertyRNA *prop, int *hardmin, in int softmin, softmax; if (prop->magic != RNA_MAGIC) { - /* attempt to get the local ID values */ - const IDProperty *idp_ui = rna_idproperty_ui(prop); - - if (idp_ui) { - IDProperty *item; - - item = IDP_GetPropertyTypeFromGroup(idp_ui, "min", IDP_INT); - *hardmin = item ? IDP_Int(item) : INT_MIN; - - item = IDP_GetPropertyTypeFromGroup(idp_ui, "max", IDP_INT); - *hardmax = item ? IDP_Int(item) : INT_MAX; - - return; + const IDProperty *idprop = (IDProperty *)prop; + if (idprop->ui_data) { + IDPropertyUIDataInt *ui_data = (IDPropertyUIDataInt *)idprop->ui_data; + *hardmin = ui_data->min; + *hardmax = ui_data->max; } + else { + *hardmin = INT_MIN; + *hardmax = INT_MAX; + } + return; } if (iprop->range) { @@ -1428,23 +1280,19 @@ void RNA_property_int_ui_range( int hardmin, hardmax; if (prop->magic != RNA_MAGIC) { - /* attempt to get the local ID values */ - const IDProperty *idp_ui = rna_idproperty_ui(prop); - - if (idp_ui) { - IDProperty *item; - - item = IDP_GetPropertyTypeFromGroup(idp_ui, "soft_min", IDP_INT); - *softmin = item ? IDP_Int(item) : INT_MIN; - - item = IDP_GetPropertyTypeFromGroup(idp_ui, "soft_max", IDP_INT); - *softmax = item ? IDP_Int(item) : INT_MAX; - - item = IDP_GetPropertyTypeFromGroup(idp_ui, "step", IDP_INT); - *step = item ? IDP_Int(item) : 1; - - return; + const IDProperty *idprop = (IDProperty *)prop; + if (idprop->ui_data) { + IDPropertyUIDataInt *ui_data_int = (IDPropertyUIDataInt *)idprop->ui_data; + *softmin = ui_data_int->soft_min; + *softmax = ui_data_int->soft_max; + *step = ui_data_int->step; } + else { + *softmin = INT_MIN; + *softmax = INT_MAX; + *step = 1; + } + return; } *softmin = iprop->softmin; @@ -1478,20 +1326,17 @@ void RNA_property_float_range(PointerRNA *ptr, PropertyRNA *prop, float *hardmin float softmin, softmax; if (prop->magic != RNA_MAGIC) { - /* attempt to get the local ID values */ - const IDProperty *idp_ui = rna_idproperty_ui(prop); - - if (idp_ui) { - IDProperty *item; - - item = IDP_GetPropertyTypeFromGroup(idp_ui, "min", IDP_DOUBLE); - *hardmin = item ? (float)IDP_Double(item) : -FLT_MAX; - - item = IDP_GetPropertyTypeFromGroup(idp_ui, "max", IDP_DOUBLE); - *hardmax = item ? (float)IDP_Double(item) : FLT_MAX; - - return; + const IDProperty *idprop = (IDProperty *)prop; + if (idprop->ui_data) { + IDPropertyUIDataFloat *ui_data = (IDPropertyUIDataFloat *)idprop->ui_data; + *hardmin = (float)ui_data->min; + *hardmax = (float)ui_data->max; } + else { + *hardmin = FLT_MIN; + *hardmax = FLT_MAX; + } + return; } if (fprop->range) { @@ -1523,26 +1368,21 @@ void RNA_property_float_ui_range(PointerRNA *ptr, float hardmin, hardmax; if (prop->magic != RNA_MAGIC) { - /* attempt to get the local ID values */ - const IDProperty *idp_ui = rna_idproperty_ui(prop); - - if (idp_ui) { - IDProperty *item; - - item = IDP_GetPropertyTypeFromGroup(idp_ui, "soft_min", IDP_DOUBLE); - *softmin = item ? (float)IDP_Double(item) : -FLT_MAX; - - item = IDP_GetPropertyTypeFromGroup(idp_ui, "soft_max", IDP_DOUBLE); - *softmax = item ? (float)IDP_Double(item) : FLT_MAX; - - item = IDP_GetPropertyTypeFromGroup(idp_ui, "step", IDP_DOUBLE); - *step = item ? (float)IDP_Double(item) : 1.0f; - - item = IDP_GetPropertyTypeFromGroup(idp_ui, "precision", IDP_DOUBLE); - *precision = item ? (float)IDP_Double(item) : 3.0f; - - return; + const IDProperty *idprop = (IDProperty *)prop; + if (idprop->ui_data) { + IDPropertyUIDataFloat *ui_data = (IDPropertyUIDataFloat *)idprop->ui_data; + *softmin = (float)ui_data->soft_min; + *softmax = (float)ui_data->soft_max; + *step = ui_data->step; + *precision = (float)ui_data->precision; } + else { + *softmin = FLT_MIN; + *softmax = FLT_MAX; + *step = 1.0f; + *precision = 3.0f; + } + return; } *softmin = fprop->softmin; @@ -2905,29 +2745,28 @@ int RNA_property_int_get_default(PointerRNA *UNUSED(ptr), PropertyRNA *prop) IntPropertyRNA *iprop = (IntPropertyRNA *)rna_ensure_property(prop); if (prop->magic != RNA_MAGIC) { - /* attempt to get the local ID values */ - const IDProperty *idp_ui = rna_idproperty_ui(prop); - - if (idp_ui) { - IDProperty *item; - - item = IDP_GetPropertyTypeFromGroup(idp_ui, "default", IDP_INT); - return item ? IDP_Int(item) : iprop->defaultvalue; + const IDProperty *idprop = (const IDProperty *)prop; + if (idprop->ui_data) { + const IDPropertyUIDataInt *ui_data = (const IDPropertyUIDataInt *)idprop->ui_data; + return ui_data->default_value; } } return iprop->defaultvalue; } -bool RNA_property_int_set_default(PointerRNA *ptr, PropertyRNA *prop, int value) +bool RNA_property_int_set_default(PropertyRNA *prop, int value) { - if (value != 0) { - IDPropertyTemplate val = { - .i = value, - }; - return rna_idproperty_ui_set_default(ptr, prop, IDP_INT, &val); + if (prop->magic == RNA_MAGIC) { + return false; } - return rna_idproperty_ui_set_default(ptr, prop, IDP_INT, NULL); + + IDProperty *idprop = (IDProperty *)prop; + BLI_assert(idprop->type == IDP_INT); + + IDPropertyUIDataInt *ui_data = (IDPropertyUIDataInt *)IDP_ui_data_ensure(idprop); + ui_data->default_value = value; + return true; } void RNA_property_int_get_default_array(PointerRNA *ptr, PropertyRNA *prop, int *values) @@ -2940,17 +2779,22 @@ void RNA_property_int_get_default_array(PointerRNA *ptr, PropertyRNA *prop, int if (prop->magic != RNA_MAGIC) { int length = rna_ensure_property_array_length(ptr, prop); - const IDProperty *idp_ui = rna_idproperty_ui(prop); - IDProperty *item = idp_ui ? IDP_GetPropertyFromGroup(idp_ui, "default") : NULL; - - int defval = (item && item->type == IDP_INT) ? IDP_Int(item) : iprop->defaultvalue; - - if (item && item->type == IDP_ARRAY && item->subtype == IDP_INT) { - rna_property_int_fill_default_array_values( - IDP_Array(item), item->len, defval, length, values); - } - else { - rna_property_int_fill_default_array_values(NULL, 0, defval, length, values); + const IDProperty *idprop = (const IDProperty *)prop; + if (idprop->ui_data) { + BLI_assert(idprop->type == IDP_ARRAY); + BLI_assert(idprop->subtype == IDP_INT); + const IDPropertyUIDataInt *ui_data = (const IDPropertyUIDataInt *)idprop->ui_data; + if (ui_data->default_array) { + rna_property_int_fill_default_array_values(ui_data->default_array, + ui_data->default_array_len, + ui_data->default_value, + length, + values); + } + else { + rna_property_int_fill_default_array_values( + NULL, 0, ui_data->default_value, length, values); + } } } else if (prop->arraydimension == 0) { @@ -3066,6 +2910,26 @@ static void rna_property_float_fill_default_array_values( } } +/** + * The same logic as #rna_property_float_fill_default_array_values for a double array. + */ +static void rna_property_float_fill_default_array_values_double(const double *default_array, + const int default_array_len, + const double default_value, + const int out_length, + float *r_values) +{ + const int array_copy_len = MIN2(out_length, default_array_len); + + for (int i = 0; i < array_copy_len; i++) { + r_values[i] = (float)default_array[i]; + } + + for (int i = array_copy_len; i < out_length; i++) { + r_values[i] = (float)default_value; + } +} + static void rna_property_float_get_default_array_values(PointerRNA *ptr, FloatPropertyRNA *fprop, float *r_values) @@ -3268,29 +3132,29 @@ float RNA_property_float_get_default(PointerRNA *UNUSED(ptr), PropertyRNA *prop) BLI_assert(RNA_property_array_check(prop) == false); if (prop->magic != RNA_MAGIC) { - /* attempt to get the local ID values */ - const IDProperty *idp_ui = rna_idproperty_ui(prop); - - if (idp_ui) { - IDProperty *item; - - item = IDP_GetPropertyTypeFromGroup(idp_ui, "default", IDP_DOUBLE); - return item ? IDP_Double(item) : fprop->defaultvalue; + const IDProperty *idprop = (const IDProperty *)prop; + if (idprop->ui_data) { + BLI_assert(ELEM(idprop->type, IDP_FLOAT, IDP_DOUBLE)); + const IDPropertyUIDataFloat *ui_data = (const IDPropertyUIDataFloat *)idprop->ui_data; + return (float)ui_data->default_value; } } return fprop->defaultvalue; } -bool RNA_property_float_set_default(PointerRNA *ptr, PropertyRNA *prop, float value) +bool RNA_property_float_set_default(PropertyRNA *prop, float value) { - if (value != 0) { - IDPropertyTemplate val = { - .d = value, - }; - return rna_idproperty_ui_set_default(ptr, prop, IDP_DOUBLE, &val); + if (prop->magic == RNA_MAGIC) { + return false; } - return rna_idproperty_ui_set_default(ptr, prop, IDP_DOUBLE, NULL); + + IDProperty *idprop = (IDProperty *)prop; + BLI_assert(idprop->type == IDP_FLOAT); + + IDPropertyUIDataFloat *ui_data = (IDPropertyUIDataFloat *)IDP_ui_data_ensure(idprop); + ui_data->default_value = (double)value; + return true; } void RNA_property_float_get_default_array(PointerRNA *ptr, PropertyRNA *prop, float *values) @@ -3303,23 +3167,16 @@ void RNA_property_float_get_default_array(PointerRNA *ptr, PropertyRNA *prop, fl if (prop->magic != RNA_MAGIC) { int length = rna_ensure_property_array_length(ptr, prop); - const IDProperty *idp_ui = rna_idproperty_ui(prop); - IDProperty *item = idp_ui ? IDP_GetPropertyFromGroup(idp_ui, "default") : NULL; - - float defval = (item && item->type == IDP_DOUBLE) ? IDP_Double(item) : fprop->defaultvalue; - - if (item && item->type == IDP_ARRAY && item->subtype == IDP_DOUBLE) { - double *defarr = IDP_Array(item); - for (int i = 0; i < length; i++) { - values[i] = (i < item->len) ? (float)defarr[i] : defval; - } - } - else if (item && item->type == IDP_ARRAY && item->subtype == IDP_FLOAT) { - rna_property_float_fill_default_array_values( - IDP_Array(item), item->len, defval, length, values); - } - else { - rna_property_float_fill_default_array_values(NULL, 0, defval, length, values); + const IDProperty *idprop = (const IDProperty *)prop; + if (idprop->ui_data) { + BLI_assert(idprop->type == IDP_ARRAY); + BLI_assert(ELEM(idprop->subtype, IDP_FLOAT, IDP_DOUBLE)); + const IDPropertyUIDataFloat *ui_data = (const IDPropertyUIDataFloat *)idprop->ui_data; + rna_property_float_fill_default_array_values_double(ui_data->default_array, + ui_data->default_array_len, + ui_data->default_value, + length, + values); } } else if (prop->arraydimension == 0) { @@ -3510,22 +3367,17 @@ void RNA_property_string_set_bytes(PointerRNA *ptr, PropertyRNA *prop, const cha } } -void RNA_property_string_get_default(PointerRNA *UNUSED(ptr), PropertyRNA *prop, char *value) +void RNA_property_string_get_default(PropertyRNA *prop, char *value, const int max_len) { StringPropertyRNA *sprop = (StringPropertyRNA *)rna_ensure_property(prop); if (prop->magic != RNA_MAGIC) { - /* attempt to get the local ID values */ - const IDProperty *idp_ui = rna_idproperty_ui(prop); - - if (idp_ui) { - IDProperty *item; - - item = IDP_GetPropertyTypeFromGroup(idp_ui, "default", IDP_STRING); - if (item) { - strcpy(value, IDP_String(item)); - return; - } + const IDProperty *idprop = (const IDProperty *)prop; + if (idprop->ui_data) { + BLI_assert(idprop->type == IDP_STRING); + const IDPropertyUIDataString *ui_data = (const IDPropertyUIDataString *)idprop->ui_data; + BLI_strncpy(value, ui_data->default_value, max_len); + return; } strcpy(value, ""); @@ -3554,7 +3406,7 @@ char *RNA_property_string_get_default_alloc( buf = MEM_callocN(sizeof(char) * (length + 1), __func__); } - RNA_property_string_get_default(ptr, prop, buf); + RNA_property_string_get_default(prop, buf, length + 1); if (r_len) { *r_len = length; @@ -3569,15 +3421,12 @@ int RNA_property_string_default_length(PointerRNA *UNUSED(ptr), PropertyRNA *pro StringPropertyRNA *sprop = (StringPropertyRNA *)rna_ensure_property(prop); if (prop->magic != RNA_MAGIC) { - /* attempt to get the local ID values */ - const IDProperty *idp_ui = rna_idproperty_ui(prop); - - if (idp_ui) { - IDProperty *item; - - item = IDP_GetPropertyTypeFromGroup(idp_ui, "default", IDP_STRING); - if (item) { - return strlen(IDP_String(item)); + const IDProperty *idprop = (const IDProperty *)prop; + if (idprop->ui_data) { + BLI_assert(idprop->type == IDP_STRING); + const IDPropertyUIDataString *ui_data = (const IDPropertyUIDataString *)idprop->ui_data; + if (ui_data->default_value != NULL) { + return strlen(ui_data->default_value); } } @@ -8200,12 +8049,12 @@ bool RNA_property_assign_default(PointerRNA *ptr, PropertyRNA *prop) switch (RNA_property_type(prop)) { case PROP_INT: { int value = RNA_property_int_get(ptr, prop); - return RNA_property_int_set_default(ptr, prop, value); + return RNA_property_int_set_default(prop, value); } case PROP_FLOAT: { float value = RNA_property_float_get(ptr, prop); - return RNA_property_float_set_default(ptr, prop, value); + return RNA_property_float_set_default(prop, value); } default: diff --git a/source/blender/makesrna/intern/rna_internal_types.h b/source/blender/makesrna/intern/rna_internal_types.h index 479306e8c06..22a9b9d930a 100644 --- a/source/blender/makesrna/intern/rna_internal_types.h +++ b/source/blender/makesrna/intern/rna_internal_types.h @@ -42,9 +42,6 @@ struct bContext; typedef struct IDProperty IDProperty; -/* store local properties here */ -#define RNA_IDP_UI "_RNA_UI" - /* Function Callbacks */ typedef void (*UpdateFunc)(struct Main *main, struct Scene *scene, struct PointerRNA *ptr); diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index 620c7ef438a..9d8630b21e7 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -289,348 +289,178 @@ static bool logging_enabled(const ModifierEvalContext *ctx) return true; } -/** - * This code is responsible for creating the new property and also creating the group of - * properties in the prop_ui_container group for the UI info, the mapping for which is - * scattered about in RNA_access.c. - * - * TODO(Hans): Codify this with some sort of table or refactor IDProperty use in RNA_access.c. - */ -struct SocketPropertyType { - /* Create the actual property used to store the data for the modifier. */ - IDProperty *(*create_prop)(const bNodeSocket &socket, const char *name); - /* Reused to build the "soft_min" property too. */ - IDProperty *(*create_min_ui_prop)(const bNodeSocket &socket, const char *name); - /* Reused to build the "soft_max" property too. */ - IDProperty *(*create_max_ui_prop)(const bNodeSocket &socket, const char *name); - /* This uses the same values as #create_prop, but sometimes the type is different, so it can't - * be the same function. */ - IDProperty *(*create_default_ui_prop)(const bNodeSocket &socket, const char *name); - PropertyType (*rna_subtype_get)(const bNodeSocket &socket); - bool (*is_correct_type)(const IDProperty &property); - void (*init_cpp_value)(const IDProperty &property, void *r_value); -}; - -static IDProperty *socket_add_property(IDProperty *settings_prop_group, - IDProperty *ui_container, - const SocketPropertyType &property_type, - const bNodeSocket &socket) +static IDProperty *id_property_create_from_socket(const bNodeSocket &socket) { - const char *new_prop_name = socket.identifier; - /* Add the property actually storing the data to the modifier's group. */ - IDProperty *prop = property_type.create_prop(socket, new_prop_name); - IDP_AddToGroup(settings_prop_group, prop); - - prop->flag |= IDP_FLAG_OVERRIDABLE_LIBRARY; - - /* Make the group in the UI container group to hold the property's UI settings. */ - IDProperty *prop_ui_group; - { - IDPropertyTemplate idprop = {0}; - prop_ui_group = IDP_New(IDP_GROUP, &idprop, new_prop_name); - IDP_AddToGroup(ui_container, prop_ui_group); - } - - /* Set property description (tooltip). */ - IDPropertyTemplate property_description_template; - property_description_template.string.str = socket.description; - property_description_template.string.len = BLI_strnlen(socket.description, MAX_NAME) + 1; - property_description_template.string.subtype = IDP_STRING_SUB_UTF8; - IDProperty *description = IDP_New(IDP_STRING, &property_description_template, "description"); - IDP_AddToGroup(prop_ui_group, description); - - /* Create the properties for the socket's UI settings. */ - if (property_type.create_min_ui_prop != nullptr) { - IDP_AddToGroup(prop_ui_group, property_type.create_min_ui_prop(socket, "min")); - IDP_AddToGroup(prop_ui_group, property_type.create_min_ui_prop(socket, "soft_min")); - } - if (property_type.create_max_ui_prop != nullptr) { - IDP_AddToGroup(prop_ui_group, property_type.create_max_ui_prop(socket, "max")); - IDP_AddToGroup(prop_ui_group, property_type.create_max_ui_prop(socket, "soft_max")); - } - if (property_type.create_default_ui_prop != nullptr) { - IDP_AddToGroup(prop_ui_group, property_type.create_default_ui_prop(socket, "default")); - } - if (property_type.rna_subtype_get != nullptr) { - const char *subtype_identifier = nullptr; - RNA_enum_identifier(rna_enum_property_subtype_items, - property_type.rna_subtype_get(socket), - &subtype_identifier); - - if (subtype_identifier != nullptr) { - IDPropertyTemplate idprop = {0}; - idprop.string.str = subtype_identifier; - idprop.string.len = BLI_strnlen(subtype_identifier, MAX_NAME) + 1; - IDP_AddToGroup(prop_ui_group, IDP_New(IDP_STRING, &idprop, "subtype")); - } - } - - return prop; -} - -static const SocketPropertyType *get_socket_property_type(const bNodeSocket &bsocket) -{ - switch (bsocket.type) { + switch (socket.type) { case SOCK_FLOAT: { - static const SocketPropertyType float_type = { - [](const bNodeSocket &socket, const char *name) { - bNodeSocketValueFloat *value = (bNodeSocketValueFloat *)socket.default_value; - IDPropertyTemplate idprop = {0}; - idprop.f = value->value; - return IDP_New(IDP_FLOAT, &idprop, name); - }, - [](const bNodeSocket &socket, const char *name) { - bNodeSocketValueFloat *value = (bNodeSocketValueFloat *)socket.default_value; - IDPropertyTemplate idprop = {0}; - idprop.d = value->min; - return IDP_New(IDP_DOUBLE, &idprop, name); - }, - [](const bNodeSocket &socket, const char *name) { - bNodeSocketValueFloat *value = (bNodeSocketValueFloat *)socket.default_value; - IDPropertyTemplate idprop = {0}; - idprop.d = value->max; - return IDP_New(IDP_DOUBLE, &idprop, name); - }, - [](const bNodeSocket &socket, const char *name) { - bNodeSocketValueFloat *value = (bNodeSocketValueFloat *)socket.default_value; - IDPropertyTemplate idprop = {0}; - idprop.d = value->value; - return IDP_New(IDP_DOUBLE, &idprop, name); - }, - [](const bNodeSocket &socket) { - return (PropertyType)((bNodeSocketValueFloat *)socket.default_value)->subtype; - }, - [](const IDProperty &property) { return ELEM(property.type, IDP_FLOAT, IDP_DOUBLE); }, - [](const IDProperty &property, void *r_value) { - if (property.type == IDP_FLOAT) { - *(float *)r_value = IDP_Float(&property); - } - else if (property.type == IDP_DOUBLE) { - *(float *)r_value = (float)IDP_Double(&property); - } - }, - }; - return &float_type; + bNodeSocketValueFloat *value = (bNodeSocketValueFloat *)socket.default_value; + IDPropertyTemplate idprop = {0}; + idprop.f = value->value; + IDProperty *property = IDP_New(IDP_FLOAT, &idprop, socket.identifier); + IDPropertyUIDataFloat *ui_data = (IDPropertyUIDataFloat *)IDP_ui_data_ensure(property); + ui_data->base.rna_subtype = value->subtype; + ui_data->min = ui_data->soft_min = (double)value->min; + ui_data->max = ui_data->soft_max = (double)value->max; + ui_data->default_value = value->value; + return property; } case SOCK_INT: { - static const SocketPropertyType int_type = { - [](const bNodeSocket &socket, const char *name) { - bNodeSocketValueInt *value = (bNodeSocketValueInt *)socket.default_value; - IDPropertyTemplate idprop = {0}; - idprop.i = value->value; - return IDP_New(IDP_INT, &idprop, name); - }, - [](const bNodeSocket &socket, const char *name) { - bNodeSocketValueInt *value = (bNodeSocketValueInt *)socket.default_value; - IDPropertyTemplate idprop = {0}; - idprop.i = value->min; - return IDP_New(IDP_INT, &idprop, name); - }, - [](const bNodeSocket &socket, const char *name) { - bNodeSocketValueInt *value = (bNodeSocketValueInt *)socket.default_value; - IDPropertyTemplate idprop = {0}; - idprop.i = value->max; - return IDP_New(IDP_INT, &idprop, name); - }, - [](const bNodeSocket &socket, const char *name) { - bNodeSocketValueInt *value = (bNodeSocketValueInt *)socket.default_value; - IDPropertyTemplate idprop = {0}; - idprop.i = value->value; - return IDP_New(IDP_INT, &idprop, name); - }, - [](const bNodeSocket &socket) { - return (PropertyType)((bNodeSocketValueInt *)socket.default_value)->subtype; - }, - [](const IDProperty &property) { return property.type == IDP_INT; }, - [](const IDProperty &property, void *r_value) { *(int *)r_value = IDP_Int(&property); }, - }; - return &int_type; + bNodeSocketValueInt *value = (bNodeSocketValueInt *)socket.default_value; + IDPropertyTemplate idprop = {0}; + idprop.i = value->value; + IDProperty *property = IDP_New(IDP_INT, &idprop, socket.identifier); + IDPropertyUIDataInt *ui_data = (IDPropertyUIDataInt *)IDP_ui_data_ensure(property); + ui_data->base.rna_subtype = value->subtype; + ui_data->min = ui_data->soft_min = value->min; + ui_data->max = ui_data->soft_max = value->max; + ui_data->default_value = value->value; + return property; } case SOCK_VECTOR: { - static const SocketPropertyType vector_type = { - [](const bNodeSocket &socket, const char *name) { - bNodeSocketValueVector *value = (bNodeSocketValueVector *)socket.default_value; - IDPropertyTemplate idprop = {0}; - idprop.array.len = 3; - idprop.array.type = IDP_FLOAT; - IDProperty *property = IDP_New(IDP_ARRAY, &idprop, name); - copy_v3_v3((float *)IDP_Array(property), value->value); - return property; - }, - [](const bNodeSocket &socket, const char *name) { - bNodeSocketValueVector *value = (bNodeSocketValueVector *)socket.default_value; - IDPropertyTemplate idprop = {0}; - idprop.d = value->min; - return IDP_New(IDP_DOUBLE, &idprop, name); - }, - [](const bNodeSocket &socket, const char *name) { - bNodeSocketValueVector *value = (bNodeSocketValueVector *)socket.default_value; - IDPropertyTemplate idprop = {0}; - idprop.d = value->max; - return IDP_New(IDP_DOUBLE, &idprop, name); - }, - [](const bNodeSocket &socket, const char *name) { - bNodeSocketValueVector *value = (bNodeSocketValueVector *)socket.default_value; - IDPropertyTemplate idprop = {0}; - idprop.array.len = 3; - idprop.array.type = IDP_FLOAT; - IDProperty *property = IDP_New(IDP_ARRAY, &idprop, name); - copy_v3_v3((float *)IDP_Array(property), value->value); - return property; - }, - [](const bNodeSocket &socket) { - return (PropertyType)((bNodeSocketValueVector *)socket.default_value)->subtype; - }, - [](const IDProperty &property) { - return property.type == IDP_ARRAY && property.subtype == IDP_FLOAT && - property.len == 3; - }, - [](const IDProperty &property, void *r_value) { - copy_v3_v3((float *)r_value, (const float *)IDP_Array(&property)); - }, - }; - return &vector_type; + bNodeSocketValueVector *value = (bNodeSocketValueVector *)socket.default_value; + IDPropertyTemplate idprop = {0}; + idprop.array.len = 3; + idprop.array.type = IDP_FLOAT; + IDProperty *property = IDP_New(IDP_ARRAY, &idprop, socket.identifier); + copy_v3_v3((float *)IDP_Array(property), value->value); + IDPropertyUIDataFloat *ui_data = (IDPropertyUIDataFloat *)IDP_ui_data_ensure(property); + ui_data->base.rna_subtype = value->subtype; + ui_data->min = ui_data->soft_min = (double)value->min; + ui_data->max = ui_data->soft_max = (double)value->max; + ui_data->default_array = (double *)MEM_mallocN(sizeof(double[3]), "mod_prop_default"); + ui_data->default_array_len = 3; + for (int i = 3; i < 3; i++) { + ui_data->default_array[i] = (double)value->value[i]; + } + return property; } case SOCK_BOOLEAN: { - static const SocketPropertyType boolean_type = { - [](const bNodeSocket &socket, const char *name) { - bNodeSocketValueBoolean *value = (bNodeSocketValueBoolean *)socket.default_value; - IDPropertyTemplate idprop = {0}; - idprop.i = value->value != 0; - return IDP_New(IDP_INT, &idprop, name); - }, - [](const bNodeSocket &UNUSED(socket), const char *name) { - IDPropertyTemplate idprop = {0}; - idprop.i = 0; - return IDP_New(IDP_INT, &idprop, name); - }, - [](const bNodeSocket &UNUSED(socket), const char *name) { - IDPropertyTemplate idprop = {0}; - idprop.i = 1; - return IDP_New(IDP_INT, &idprop, name); - }, - [](const bNodeSocket &socket, const char *name) { - bNodeSocketValueBoolean *value = (bNodeSocketValueBoolean *)socket.default_value; - IDPropertyTemplate idprop = {0}; - idprop.i = value->value != 0; - return IDP_New(IDP_INT, &idprop, name); - }, - nullptr, - [](const IDProperty &property) { return property.type == IDP_INT; }, - [](const IDProperty &property, void *r_value) { - *(bool *)r_value = IDP_Int(&property) != 0; - }, - }; - return &boolean_type; + bNodeSocketValueBoolean *value = (bNodeSocketValueBoolean *)socket.default_value; + IDPropertyTemplate idprop = {0}; + idprop.i = value->value != 0; + IDProperty *property = IDP_New(IDP_INT, &idprop, socket.identifier); + IDPropertyUIDataInt *ui_data = (IDPropertyUIDataInt *)IDP_ui_data_ensure(property); + ui_data->min = ui_data->soft_min = 0; + ui_data->max = ui_data->soft_max = 1; + ui_data->default_value = value->value != 0; + return property; } case SOCK_STRING: { - static const SocketPropertyType string_type = { - [](const bNodeSocket &socket, const char *name) { - bNodeSocketValueString *value = (bNodeSocketValueString *)socket.default_value; - return IDP_NewString( - value->value, name, BLI_strnlen(value->value, sizeof(value->value)) + 1); - }, - nullptr, - nullptr, - [](const bNodeSocket &socket, const char *name) { - bNodeSocketValueString *value = (bNodeSocketValueString *)socket.default_value; - return IDP_NewString( - value->value, name, BLI_strnlen(value->value, sizeof(value->value)) + 1); - }, - nullptr, - [](const IDProperty &property) { return property.type == IDP_STRING; }, - [](const IDProperty &property, void *r_value) { - new (r_value) std::string(IDP_String(&property)); - }, - }; - return &string_type; + bNodeSocketValueString *value = (bNodeSocketValueString *)socket.default_value; + IDProperty *property = IDP_NewString( + value->value, socket.identifier, BLI_strnlen(value->value, sizeof(value->value)) + 1); + IDPropertyUIDataString *ui_data = (IDPropertyUIDataString *)IDP_ui_data_ensure(property); + ui_data->default_value = BLI_strdup(value->value); + return property; } case SOCK_OBJECT: { - static const SocketPropertyType object_type = { - [](const bNodeSocket &socket, const char *name) { - bNodeSocketValueObject *value = (bNodeSocketValueObject *)socket.default_value; - IDPropertyTemplate idprop = {0}; - idprop.id = (ID *)value->value; - return IDP_New(IDP_ID, &idprop, name); - }, - nullptr, - nullptr, - nullptr, - nullptr, - [](const IDProperty &property) { return property.type == IDP_ID; }, - [](const IDProperty &property, void *r_value) { - ID *id = IDP_Id(&property); - Object *object = (id && GS(id->name) == ID_OB) ? (Object *)id : nullptr; - *(Object **)r_value = object; - }, - }; - return &object_type; + bNodeSocketValueObject *value = (bNodeSocketValueObject *)socket.default_value; + IDPropertyTemplate idprop = {0}; + idprop.id = (ID *)value->value; + return IDP_New(IDP_ID, &idprop, socket.identifier); } case SOCK_COLLECTION: { - static const SocketPropertyType collection_type = { - [](const bNodeSocket &socket, const char *name) { - bNodeSocketValueCollection *value = (bNodeSocketValueCollection *)socket.default_value; - IDPropertyTemplate idprop = {0}; - idprop.id = (ID *)value->value; - return IDP_New(IDP_ID, &idprop, name); - }, - nullptr, - nullptr, - nullptr, - nullptr, - [](const IDProperty &property) { return property.type == IDP_ID; }, - [](const IDProperty &property, void *r_value) { - ID *id = IDP_Id(&property); - Collection *collection = (id && GS(id->name) == ID_GR) ? (Collection *)id : nullptr; - *(Collection **)r_value = collection; - }, - }; - return &collection_type; + bNodeSocketValueCollection *value = (bNodeSocketValueCollection *)socket.default_value; + IDPropertyTemplate idprop = {0}; + idprop.id = (ID *)value->value; + return IDP_New(IDP_ID, &idprop, socket.identifier); } case SOCK_TEXTURE: { - static const SocketPropertyType texture_type = { - [](const bNodeSocket &socket, const char *name) { - bNodeSocketValueTexture *value = (bNodeSocketValueTexture *)socket.default_value; - IDPropertyTemplate idprop = {0}; - idprop.id = (ID *)value->value; - return IDP_New(IDP_ID, &idprop, name); - }, - nullptr, - nullptr, - nullptr, - nullptr, - [](const IDProperty &property) { return property.type == IDP_ID; }, - [](const IDProperty &property, void *r_value) { - ID *id = IDP_Id(&property); - Tex *texture = (id && GS(id->name) == ID_TE) ? (Tex *)id : nullptr; - *(Tex **)r_value = texture; - }, - }; - return &texture_type; + bNodeSocketValueTexture *value = (bNodeSocketValueTexture *)socket.default_value; + IDPropertyTemplate idprop = {0}; + idprop.id = (ID *)value->value; + return IDP_New(IDP_ID, &idprop, socket.identifier); } case SOCK_MATERIAL: { - static const SocketPropertyType material_type = { - [](const bNodeSocket &socket, const char *name) { - bNodeSocketValueMaterial *value = (bNodeSocketValueMaterial *)socket.default_value; - IDPropertyTemplate idprop = {0}; - idprop.id = (ID *)value->value; - return IDP_New(IDP_ID, &idprop, name); - }, - nullptr, - nullptr, - nullptr, - nullptr, - [](const IDProperty &property) { return property.type == IDP_ID; }, - [](const IDProperty &property, void *r_value) { - ID *id = IDP_Id(&property); - Material *material = (id && GS(id->name) == ID_MA) ? (Material *)id : nullptr; - *(Material **)r_value = material; - }, - }; - return &material_type; + bNodeSocketValueMaterial *value = (bNodeSocketValueMaterial *)socket.default_value; + IDPropertyTemplate idprop = {0}; + idprop.id = (ID *)value->value; + return IDP_New(IDP_ID, &idprop, socket.identifier); + } + } + return nullptr; +} + +static bool id_property_type_matches_socket(const bNodeSocket &socket, const IDProperty &property) +{ + switch (socket.type) { + case SOCK_FLOAT: + return ELEM(property.type, IDP_FLOAT, IDP_DOUBLE); + case SOCK_INT: + return property.type == IDP_INT; + case SOCK_VECTOR: + return property.type == IDP_ARRAY && property.subtype == IDP_FLOAT && property.len == 3; + case SOCK_BOOLEAN: + return property.type == IDP_INT; + case SOCK_STRING: + return property.type == IDP_STRING; + case SOCK_OBJECT: + case SOCK_COLLECTION: + case SOCK_TEXTURE: + case SOCK_MATERIAL: + return property.type == IDP_ID; + } + BLI_assert_unreachable(); + return false; +} + +static void init_socket_cpp_value_from_property(const IDProperty &property, + const eNodeSocketDatatype socket_value_type, + void *r_value) +{ + switch (socket_value_type) { + case SOCK_FLOAT: { + if (property.type == IDP_FLOAT) { + *(float *)r_value = IDP_Float(&property); + } + else if (property.type == IDP_DOUBLE) { + *(float *)r_value = (float)IDP_Double(&property); + } + break; + } + case SOCK_INT: { + *(int *)r_value = IDP_Int(&property); + break; + } + case SOCK_VECTOR: { + copy_v3_v3((float *)r_value, (const float *)IDP_Array(&property)); + break; + } + case SOCK_BOOLEAN: { + *(bool *)r_value = IDP_Int(&property) != 0; + break; + } + case SOCK_STRING: { + new (r_value) std::string(IDP_String(&property)); + break; + } + case SOCK_OBJECT: { + ID *id = IDP_Id(&property); + Object *object = (id && GS(id->name) == ID_OB) ? (Object *)id : nullptr; + *(Object **)r_value = object; + break; + } + case SOCK_COLLECTION: { + ID *id = IDP_Id(&property); + Collection *collection = (id && GS(id->name) == ID_GR) ? (Collection *)id : nullptr; + *(Collection **)r_value = collection; + break; + } + case SOCK_TEXTURE: { + ID *id = IDP_Id(&property); + Tex *texture = (id && GS(id->name) == ID_TE) ? (Tex *)id : nullptr; + *(Tex **)r_value = texture; + break; + } + case SOCK_MATERIAL: { + ID *id = IDP_Id(&property); + Material *material = (id && GS(id->name) == ID_MA) ? (Material *)id : nullptr; + *(Material **)r_value = material; + break; } default: { - return nullptr; + BLI_assert_unreachable(); + break; } } } @@ -647,32 +477,39 @@ void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd) } IDProperty *old_properties = nmd->settings.properties; - { IDPropertyTemplate idprop = {0}; nmd->settings.properties = IDP_New(IDP_GROUP, &idprop, "Nodes Modifier Settings"); } - IDProperty *ui_container_group; - { - IDPropertyTemplate idprop = {0}; - ui_container_group = IDP_New(IDP_GROUP, &idprop, "_RNA_UI"); - IDP_AddToGroup(nmd->settings.properties, ui_container_group); - } - LISTBASE_FOREACH (bNodeSocket *, socket, &nmd->node_group->inputs) { - const SocketPropertyType *property_type = get_socket_property_type(*socket); - if (property_type == nullptr) { + IDProperty *new_prop = id_property_create_from_socket(*socket); + if (new_prop == nullptr) { + /* Out of the set of supported input sockets, only + * geometry sockets aren't added to the modifier. */ + BLI_assert(socket->type == SOCK_GEOMETRY); continue; } - IDProperty *new_prop = socket_add_property( - nmd->settings.properties, ui_container_group, *property_type, *socket); + new_prop->flag |= IDP_FLAG_OVERRIDABLE_LIBRARY; + if (socket->description[0] != '\0') { + IDPropertyUIData *ui_data = IDP_ui_data_ensure(new_prop); + ui_data->description = BLI_strdup(socket->description); + } + IDP_AddToGroup(nmd->settings.properties, new_prop); if (old_properties != nullptr) { IDProperty *old_prop = IDP_GetPropertyFromGroup(old_properties, socket->identifier); - if (old_prop != nullptr && property_type->is_correct_type(*old_prop)) { + if (old_prop != nullptr && id_property_type_matches_socket(*socket, *old_prop)) { + /* #IDP_CopyPropertyContent replaces the UI data as well, which we don't (we only + * want to replace the values). So release it temporarily and replace it after. */ + IDPropertyUIData *ui_data = new_prop->ui_data; + new_prop->ui_data = nullptr; IDP_CopyPropertyContent(new_prop, old_prop); + if (new_prop->ui_data != nullptr) { + IDP_ui_data_free(new_prop); + } + new_prop->ui_data = ui_data; } } } @@ -713,14 +550,8 @@ void MOD_nodes_init(Main *bmain, NodesModifierData *nmd) static void initialize_group_input(NodesModifierData &nmd, const bNodeSocket &socket, - const CPPType &cpp_type, void *r_value) { - const SocketPropertyType *property_type = get_socket_property_type(socket); - if (property_type == nullptr) { - cpp_type.copy_construct(cpp_type.default_value(), r_value); - return; - } if (nmd.settings.properties == nullptr) { socket.typeinfo->get_geometry_nodes_cpp_value(socket, r_value); return; @@ -731,11 +562,13 @@ static void initialize_group_input(NodesModifierData &nmd, socket.typeinfo->get_geometry_nodes_cpp_value(socket, r_value); return; } - if (!property_type->is_correct_type(*property)) { + if (!id_property_type_matches_socket(socket, *property)) { socket.typeinfo->get_geometry_nodes_cpp_value(socket, r_value); return; } - property_type->init_cpp_value(*property, r_value); + + init_socket_cpp_value_from_property( + *property, static_cast(socket.type), r_value); } static Vector find_spreadsheet_editors(Main *bmain) @@ -886,7 +719,7 @@ static GeometrySet compute_geometry(const DerivedNodeTree &tree, for (const OutputSocketRef *socket : remaining_input_sockets) { const CPPType &cpp_type = *socket->typeinfo()->get_geometry_nodes_cpp_type(); void *value_in = allocator.allocate(cpp_type.size(), cpp_type.alignment()); - initialize_group_input(*nmd, *socket->bsocket(), cpp_type, value_in); + initialize_group_input(*nmd, *socket->bsocket(), value_in); group_inputs.add_new({root_context, socket}, {cpp_type, value_in}); } } @@ -954,8 +787,7 @@ static void check_property_socket_sync(const Object *ob, ModifierData *md) continue; } - const SocketPropertyType *property_type = get_socket_property_type(*socket); - if (!property_type->is_correct_type(*property)) { + if (!id_property_type_matches_socket(*socket, *property)) { BKE_modifier_set_error( ob, md, "Property type does not match input socket \"(%s)\"", socket->name); continue; @@ -1051,17 +883,12 @@ static void draw_property_for_socket(uiLayout *layout, const IDProperty *modifier_props, const bNodeSocket &socket) { - const SocketPropertyType *property_type = get_socket_property_type(socket); - if (property_type == nullptr) { - return; - } - /* The property should be created in #MOD_nodes_update_interface with the correct type. */ IDProperty *property = IDP_GetPropertyFromGroup(modifier_props, socket.identifier); /* IDProperties can be removed with python, so there could be a situation where * there isn't a property for a socket or it doesn't have the correct type. */ - if (property == nullptr || !property_type->is_correct_type(*property)) { + if (property == nullptr || !id_property_type_matches_socket(socket, *property)) { return; } diff --git a/source/blender/python/generic/CMakeLists.txt b/source/blender/python/generic/CMakeLists.txt index dce6a7e1c91..10cc1e8b113 100644 --- a/source/blender/python/generic/CMakeLists.txt +++ b/source/blender/python/generic/CMakeLists.txt @@ -21,6 +21,7 @@ set(INC ../../blenlib ../../gpu ../../makesdna + ../../makesrna ../../../../intern/glew-mx ../../../../intern/guardedalloc ) @@ -36,6 +37,7 @@ set(SRC blf_py_api.c bpy_threads.c idprop_py_api.c + idprop_py_ui_api.c imbuf_py_api.c py_capi_utils.c @@ -43,6 +45,7 @@ set(SRC bl_math_py_api.h blf_py_api.h idprop_py_api.h + idprop_py_ui_api.h imbuf_py_api.h py_capi_utils.h diff --git a/source/blender/python/generic/idprop_py_api.c b/source/blender/python/generic/idprop_py_api.c index 024900db691..58a947943b1 100644 --- a/source/blender/python/generic/idprop_py_api.c +++ b/source/blender/python/generic/idprop_py_api.c @@ -25,6 +25,7 @@ #include "BLI_utildefines.h" #include "idprop_py_api.h" +#include "idprop_py_ui_api.h" #include "BKE_idprop.h" @@ -2135,6 +2136,7 @@ static PyObject *BPyInit_idprop_types(void) submodule = PyModule_Create(&IDProp_types_module_def); IDProp_Init_Types(); + IDPropertyUIData_Init_Types(); /* bmesh_py_types.c */ PyModule_AddType(submodule, &BPy_IDGroup_Type); diff --git a/source/blender/python/generic/idprop_py_ui_api.c b/source/blender/python/generic/idprop_py_ui_api.c new file mode 100644 index 00000000000..92cc3b8e1dd --- /dev/null +++ b/source/blender/python/generic/idprop_py_ui_api.c @@ -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 + +#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( + "", 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 "." */ + "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); +} + +/** \} */ diff --git a/source/blender/python/generic/idprop_py_ui_api.h b/source/blender/python/generic/idprop_py_ui_api.h new file mode 100644 index 00000000000..f678e5ae9c1 --- /dev/null +++ b/source/blender/python/generic/idprop_py_ui_api.h @@ -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); diff --git a/source/blender/python/intern/bpy.c b/source/blender/python/intern/bpy.c index 0e0f431cb19..7646109c1b0 100644 --- a/source/blender/python/intern/bpy.c +++ b/source/blender/python/intern/bpy.c @@ -56,6 +56,7 @@ /* external util modules */ #include "../generic/idprop_py_api.h" +#include "../generic/idprop_py_ui_api.h" #include "bpy_msgbus.h" #ifdef WITH_FREESTYLE @@ -407,6 +408,7 @@ void BPy_init_modules(struct bContext *C) } /* stand alone utility modules not related to blender directly */ IDProp_Init_Types(); /* not actually a submodule, just types */ + IDPropertyUIData_Init_Types(); #ifdef WITH_FREESTYLE Freestyle_Init(); #endif diff --git a/source/blender/python/intern/bpy_rna.c b/source/blender/python/intern/bpy_rna.c index e9d5ae278bb..ac1fdeb2bad 100644 --- a/source/blender/python/intern/bpy_rna.c +++ b/source/blender/python/intern/bpy_rna.c @@ -73,6 +73,7 @@ #include "DEG_depsgraph_query.h" #include "../generic/idprop_py_api.h" /* For IDprop lookups. */ +#include "../generic/idprop_py_ui_api.h" #include "../generic/py_capi_utils.h" #include "../generic/python_utildefines.h" @@ -4309,6 +4310,51 @@ static PyObject *pyrna_struct_id_properties_ensure(BPy_StructRNA *self) return (PyObject *)group; } +PyDoc_STRVAR(pyrna_struct_id_properties_ui_doc, + ".. method:: id_properties_ui(key)\n" + "\n" + " :return: Return an object used to manage an IDProperty's UI data.\n" + " :arg key: String name of the property.\n" + " :rtype: :class:`bpy.types.IDPropertyUIManager`\n"); +static PyObject *pyrna_struct_id_properties_ui(BPy_StructRNA *self, PyObject *args) +{ + PYRNA_STRUCT_CHECK_OBJ(self); + + if (RNA_struct_idprops_check(self->ptr.type) == 0) { + PyErr_SetString(PyExc_TypeError, "This type doesn't support IDProperties"); + return NULL; + } + + const char *key; + if (!PyArg_ParseTuple(args, "s:ui_data", &key)) { + return NULL; + } + + IDProperty *parent_group = RNA_struct_idprops(&self->ptr, true); + + /* This is a paranoid check that theoretically might not be necessary. + * It allows the possibility that some structs can't ensure IDProperties. */ + if (parent_group == NULL) { + return Py_None; + } + + IDProperty *property = IDP_GetPropertyFromGroup(parent_group, key); + if (property == NULL) { + PyErr_SetString(PyExc_KeyError, "Property not found in IDProperty group"); + return NULL; + } + + if (!IDP_ui_data_supported(property)) { + PyErr_Format(PyExc_TypeError, "IDProperty \"%s\" does not support UI data", property->name); + return NULL; + } + + BPy_IDPropertyUIManager *ui_manager = PyObject_New(BPy_IDPropertyUIManager, + &BPy_IDPropertyUIManager_Type); + ui_manager->property = property; + return (PyObject *)ui_manager; +} + PyDoc_STRVAR(pyrna_struct_id_properties_clear_doc, ".. method:: id_properties_clear()\n\n" " :return: Remove the parent group for an RNA struct's custom IDProperties.\n"); @@ -5829,6 +5875,10 @@ static struct PyMethodDef pyrna_struct_methods[] = { (PyCFunction)pyrna_struct_id_properties_clear, METH_NOARGS, pyrna_struct_id_properties_clear_doc}, + {"id_properties_ui", + (PyCFunction)pyrna_struct_id_properties_ui, + METH_VARARGS, + pyrna_struct_id_properties_ui_doc}, /* experimental */ /* unused for now */ diff --git a/tests/python/bl_pyapi_idprop.py b/tests/python/bl_pyapi_idprop.py index 38cd9d04a6b..bdf625c1c2e 100644 --- a/tests/python/bl_pyapi_idprop.py +++ b/tests/python/bl_pyapi_idprop.py @@ -246,6 +246,69 @@ class TestBufferProtocol(TestHelper, unittest.TestCase): self.assertEqual(list(view1), list(view2)) self.assertEqual(view1.tobytes(), view2.tobytes()) +class TestRNAData(TestHelper, unittest.TestCase): + + def test_custom_properties_none(self): + bpy.data.objects.new("test", None) + test_object = bpy.data.objects["test"] + + # Access default RNA data values. + test_object.id_properties_clear() + test_object["test_prop"] = 0.5 + ui_data_test_prop = test_object.id_properties_ui("test_prop") + + rna_data = ui_data_test_prop.as_dict() + self.assertTrue("min" in rna_data) + self.assertLess(rna_data["min"], -10000.0) + self.assertEqual(rna_data["subtype"], "NONE") + self.assertGreater(rna_data["soft_max"], 10000.0) + + # Change RNA data values. + ui_data_test_prop.update(subtype="TEMPERATURE", min=0, soft_min=0.1) + rna_data = ui_data_test_prop.as_dict() + self.assertEqual(rna_data["min"], 0) + self.assertEqual(rna_data["soft_min"], 0.1) + self.assertEqual(rna_data["subtype"], "TEMPERATURE") + + # Copy RNA data values from one property to another. + test_object["test_prop_2"] = 11.7 + ui_data_test_prop_2 = test_object.id_properties_ui("test_prop_2") + ui_data_test_prop_2.update_from(ui_data_test_prop) + rna_data = ui_data_test_prop_2.as_dict() + self.assertEqual(rna_data["min"], 0) + self.assertEqual(rna_data["soft_min"], 0.1) + self.assertEqual(rna_data["subtype"], "TEMPERATURE") + self.assertGreater(rna_data["soft_max"], 10000.0) + + # Copy RNA data values to another object's property. + bpy.data.objects.new("test_2", None) + test_object_2 = bpy.data.objects["test_2"] + test_object_2["test_prop_3"] = 20.1 + ui_data_test_prop_3 = test_object_2.id_properties_ui("test_prop_3") + ui_data_test_prop_3.update_from(ui_data_test_prop_2) + rna_data = ui_data_test_prop_3.as_dict() + self.assertEqual(rna_data["min"], 0) + self.assertEqual(rna_data["soft_min"], 0.1) + self.assertEqual(rna_data["subtype"], "TEMPERATURE") + self.assertGreater(rna_data["soft_max"], 10000.0) + + # Test RNA data for string property. + test_object.id_properties_clear() + test_object["test_string_prop"] = "Hello there!" + ui_data_test_prop_string = test_object.id_properties_ui("test_string_prop") + ui_data_test_prop_string.update(default="Goodbye where?") + rna_data = ui_data_test_prop_string.as_dict() + self.assertEqual(rna_data["default"], "Goodbye where?") + + # Test RNA data for array property. + test_object.id_properties_clear() + test_object["test_array_prop"] = [1, 2, 3] + ui_data_test_prop_array = test_object.id_properties_ui("test_array_prop") + ui_data_test_prop_array.update(default=[1, 2]) + rna_data = ui_data_test_prop_array.as_dict() + self.assertEqual(rna_data["default"], [1, 2]) + + if __name__ == '__main__': import sys