diff --git a/source/blender/editors/gizmo_library/gizmo_types/primitive3d_gizmo.cc b/source/blender/editors/gizmo_library/gizmo_types/primitive3d_gizmo.cc index ed9cde25892..8110f697639 100644 --- a/source/blender/editors/gizmo_library/gizmo_types/primitive3d_gizmo.cc +++ b/source/blender/editors/gizmo_library/gizmo_types/primitive3d_gizmo.cc @@ -280,6 +280,8 @@ static void GIZMO_GT_primitive_3d(wmGizmoType *gzt) RNA_def_property_enum_funcs_runtime(prop, gizmo_primitive_rna__draw_style_get_fn, gizmo_primitive_rna__draw_style_set_fn, + nullptr, + nullptr, nullptr); prop = RNA_def_float_factor( @@ -287,11 +289,16 @@ static void GIZMO_GT_primitive_3d(wmGizmoType *gzt) RNA_def_property_float_funcs_runtime(prop, gizmo_primitive_rna__arc_inner_factor_get_fn, gizmo_primitive_rna__arc_inner_factor_set_fn, + nullptr, + nullptr, nullptr); prop = RNA_def_boolean(gzt->srna, "draw_inner", true, "Draw Inner", ""); - RNA_def_property_boolean_funcs_runtime( - prop, gizmo_primitive_rna__draw_inner_get_fn, gizmo_primitive_rna__draw_inner_set_fn); + RNA_def_property_boolean_funcs_runtime(prop, + gizmo_primitive_rna__draw_inner_get_fn, + gizmo_primitive_rna__draw_inner_set_fn, + nullptr, + nullptr); } void ED_gizmotypes_primitive_3d() diff --git a/source/blender/editors/gizmo_library/gizmo_types/snap3d_gizmo.cc b/source/blender/editors/gizmo_library/gizmo_types/snap3d_gizmo.cc index 0fc1cec5be7..efe0e235b52 100644 --- a/source/blender/editors/gizmo_library/gizmo_types/snap3d_gizmo.cc +++ b/source/blender/editors/gizmo_library/gizmo_types/snap3d_gizmo.cc @@ -339,8 +339,12 @@ static void GIZMO_GT_snap_3d(wmGizmoType *gzt) "Point that defines the location of the perpendicular snap", FLT_MIN, FLT_MAX); - RNA_def_property_float_array_funcs_runtime( - prop, gizmo_snap_rna_prevpoint_get_fn, gizmo_snap_rna_prevpoint_set_fn, nullptr); + RNA_def_property_float_array_funcs_runtime(prop, + gizmo_snap_rna_prevpoint_get_fn, + gizmo_snap_rna_prevpoint_set_fn, + nullptr, + nullptr, + nullptr); /* Returns. */ prop = RNA_def_float_translation(gzt->srna, @@ -353,8 +357,12 @@ static void GIZMO_GT_snap_3d(wmGizmoType *gzt) "Snap Point Location", FLT_MIN, FLT_MAX); - RNA_def_property_float_array_funcs_runtime( - prop, gizmo_snap_rna_location_get_fn, gizmo_snap_rna_location_set_fn, nullptr); + RNA_def_property_float_array_funcs_runtime(prop, + gizmo_snap_rna_location_get_fn, + gizmo_snap_rna_location_set_fn, + nullptr, + nullptr, + nullptr); prop = RNA_def_float_vector_xyz(gzt->srna, "normal", @@ -366,7 +374,8 @@ static void GIZMO_GT_snap_3d(wmGizmoType *gzt) "Snap Point Normal", FLT_MIN, FLT_MAX); - RNA_def_property_float_array_funcs_runtime(prop, gizmo_snap_rna_normal_get_fn, nullptr, nullptr); + RNA_def_property_float_array_funcs_runtime( + prop, gizmo_snap_rna_normal_get_fn, nullptr, nullptr, nullptr, nullptr); prop = RNA_def_int_vector(gzt->srna, "snap_elem_index", @@ -379,7 +388,7 @@ static void GIZMO_GT_snap_3d(wmGizmoType *gzt) INT_MIN, INT_MAX); RNA_def_property_int_array_funcs_runtime( - prop, gizmo_snap_rna_snap_elem_index_get_fn, nullptr, nullptr); + prop, gizmo_snap_rna_snap_elem_index_get_fn, nullptr, nullptr, nullptr, nullptr); prop = RNA_def_enum(gzt->srna, "snap_source_type", @@ -390,6 +399,8 @@ static void GIZMO_GT_snap_3d(wmGizmoType *gzt) RNA_def_property_enum_funcs_runtime(prop, gizmo_snap_rna_snap_srouce_type_get_fn, gizmo_snap_rna_snap_srouce_type_set_fn, + nullptr, + nullptr, nullptr); } diff --git a/source/blender/editors/object/object_data_transfer.cc b/source/blender/editors/object/object_data_transfer.cc index b66eb5aa412..1ff68ad3390 100644 --- a/source/blender/editors/object/object_data_transfer.cc +++ b/source/blender/editors/object/object_data_transfer.cc @@ -783,7 +783,8 @@ void OBJECT_OT_data_transfer(wmOperatorType *ot) DT_LAYERS_ACTIVE_SRC, "Source Layers Selection", "Which layers to transfer, in case of multi-layers types"); - RNA_def_property_enum_funcs_runtime(prop, nullptr, nullptr, dt_layers_select_itemf); + RNA_def_property_enum_funcs_runtime( + prop, nullptr, nullptr, dt_layers_select_itemf, nullptr, nullptr); prop = RNA_def_enum(ot->srna, "layers_select_dst", @@ -791,7 +792,8 @@ void OBJECT_OT_data_transfer(wmOperatorType *ot) DT_LAYERS_ACTIVE_DST, "Destination Layers Matching", "How to match source and destination layers"); - RNA_def_property_enum_funcs_runtime(prop, nullptr, nullptr, dt_layers_select_itemf); + RNA_def_property_enum_funcs_runtime( + prop, nullptr, nullptr, dt_layers_select_itemf, nullptr, nullptr); prop = RNA_def_enum(ot->srna, "mix_mode", @@ -799,7 +801,7 @@ void OBJECT_OT_data_transfer(wmOperatorType *ot) CDT_MIX_TRANSFER, "Mix Mode", "How to affect destination elements with source values"); - RNA_def_property_enum_funcs_runtime(prop, nullptr, nullptr, dt_mix_mode_itemf); + RNA_def_property_enum_funcs_runtime(prop, nullptr, nullptr, dt_mix_mode_itemf, nullptr, nullptr); RNA_def_float( ot->srna, "mix_factor", @@ -948,7 +950,8 @@ void OBJECT_OT_datalayout_transfer(wmOperatorType *ot) DT_LAYERS_ACTIVE_SRC, "Source Layers Selection", "Which layers to transfer, in case of multi-layers types"); - RNA_def_property_enum_funcs_runtime(prop, nullptr, nullptr, dt_layers_select_src_itemf); + RNA_def_property_enum_funcs_runtime( + prop, nullptr, nullptr, dt_layers_select_src_itemf, nullptr, nullptr); prop = RNA_def_enum(ot->srna, "layers_select_dst", @@ -956,7 +959,8 @@ void OBJECT_OT_datalayout_transfer(wmOperatorType *ot) DT_LAYERS_ACTIVE_DST, "Destination Layers Matching", "How to match source and destination layers"); - RNA_def_property_enum_funcs_runtime(prop, nullptr, nullptr, dt_layers_select_dst_itemf); + RNA_def_property_enum_funcs_runtime( + prop, nullptr, nullptr, dt_layers_select_dst_itemf, nullptr, nullptr); } } // namespace blender::ed::object diff --git a/source/blender/editors/space_outliner/outliner_edit.cc b/source/blender/editors/space_outliner/outliner_edit.cc index 48d98547850..737e4ef050f 100644 --- a/source/blender/editors/space_outliner/outliner_edit.cc +++ b/source/blender/editors/space_outliner/outliner_edit.cc @@ -762,7 +762,7 @@ void OUTLINER_OT_id_remap(wmOperatorType *ot) prop = RNA_def_enum( ot->srna, "old_id", rna_enum_dummy_NULL_items, 0, "Old ID", "Old ID to replace"); - RNA_def_property_enum_funcs_runtime(prop, nullptr, nullptr, outliner_id_itemf); + RNA_def_property_enum_funcs_runtime(prop, nullptr, nullptr, outliner_id_itemf, nullptr, nullptr); RNA_def_property_flag(prop, PROP_ENUM_NO_TRANSLATE | PROP_HIDDEN); ot->prop = RNA_def_enum(ot->srna, @@ -771,7 +771,8 @@ void OUTLINER_OT_id_remap(wmOperatorType *ot) 0, "New ID", "New ID to remap all selected IDs' users to"); - RNA_def_property_enum_funcs_runtime(ot->prop, nullptr, nullptr, outliner_id_itemf); + RNA_def_property_enum_funcs_runtime( + ot->prop, nullptr, nullptr, outliner_id_itemf, nullptr, nullptr); RNA_def_property_flag(ot->prop, PROP_ENUM_NO_TRANSLATE); } diff --git a/source/blender/makesrna/RNA_access.hh b/source/blender/makesrna/RNA_access.hh index a10db49df66..39dffda2bfb 100644 --- a/source/blender/makesrna/RNA_access.hh +++ b/source/blender/makesrna/RNA_access.hh @@ -541,7 +541,8 @@ std::optional RNA_property_string_path_filter(const bContext *C, PropertyRNA *prop); /** - * \return the length without `\0` terminator. + * \return The final length without `\0` terminator (might differ from the length of the stored + * string, when a `get_transform` callback is defined). */ int RNA_property_string_length(PointerRNA *ptr, PropertyRNA *prop); void RNA_property_string_get_default(PropertyRNA *prop, char *value, int value_maxncpy); diff --git a/source/blender/makesrna/RNA_define.hh b/source/blender/makesrna/RNA_define.hh index dd646437eac..ffa98d18b35 100644 --- a/source/blender/makesrna/RNA_define.hh +++ b/source/blender/makesrna/RNA_define.hh @@ -573,36 +573,64 @@ void RNA_def_property_enum_default_func(PropertyRNA *prop, const char *get_defau void RNA_def_property_srna(PropertyRNA *prop, const char *type); void RNA_def_py_data(PropertyRNA *prop, void *py_data); +/* API to define callbacks for runtime-defined properties (mainly for Operators, and from the + * Python `bpy.props` API). + * + * These expect 'extended' versions of the callbacks, with both the StructRNA owner and the + * PropertyRNA as first arguments. + * + * The 'Transform' ones allow to add a transform step (applied after getting, or before setting the + * value), which only modifies the value, but does not handle actual storage. Currently only used + * by `bpy`, more details in the documentation of #BPyPropStore. + */ void RNA_def_property_boolean_funcs_runtime(PropertyRNA *prop, BooleanPropertyGetFunc getfunc, - BooleanPropertySetFunc setfunc); -void RNA_def_property_boolean_array_funcs_runtime(PropertyRNA *prop, - BooleanArrayPropertyGetFunc getfunc, - BooleanArrayPropertySetFunc setfunc); + BooleanPropertySetFunc setfunc, + BooleanPropertyGetTransformFunc get_transform_fn, + BooleanPropertySetTransformFunc set_transform_fn); +void RNA_def_property_boolean_array_funcs_runtime( + PropertyRNA *prop, + BooleanArrayPropertyGetFunc getfunc, + BooleanArrayPropertySetFunc setfunc, + BooleanArrayPropertyGetTransformFunc get_transform_fn, + BooleanArrayPropertySetTransformFunc set_transform_fn); void RNA_def_property_int_funcs_runtime(PropertyRNA *prop, IntPropertyGetFunc getfunc, IntPropertySetFunc setfunc, - IntPropertyRangeFunc rangefunc); + IntPropertyRangeFunc rangefunc, + IntPropertyGetTransformFunc get_transform_fn, + IntPropertySetTransformFunc set_transform_fn); void RNA_def_property_int_array_funcs_runtime(PropertyRNA *prop, IntArrayPropertyGetFunc getfunc, IntArrayPropertySetFunc setfunc, - IntPropertyRangeFunc rangefunc); + IntPropertyRangeFunc rangefunc, + IntArrayPropertyGetTransformFunc get_transform_fn, + IntArrayPropertySetTransformFunc set_transform_fn); void RNA_def_property_float_funcs_runtime(PropertyRNA *prop, FloatPropertyGetFunc getfunc, FloatPropertySetFunc setfunc, - FloatPropertyRangeFunc rangefunc); -void RNA_def_property_float_array_funcs_runtime(PropertyRNA *prop, - FloatArrayPropertyGetFunc getfunc, - FloatArrayPropertySetFunc setfunc, - FloatPropertyRangeFunc rangefunc); + FloatPropertyRangeFunc rangefunc, + FloatPropertyGetTransformFunc get_transform_fn, + FloatPropertySetTransformFunc set_transform_fn); +void RNA_def_property_float_array_funcs_runtime( + PropertyRNA *prop, + FloatArrayPropertyGetFunc getfunc, + FloatArrayPropertySetFunc setfunc, + FloatPropertyRangeFunc rangefunc, + FloatArrayPropertyGetTransformFunc get_transform_fn, + FloatArrayPropertySetTransformFunc set_transform_fn); void RNA_def_property_enum_funcs_runtime(PropertyRNA *prop, EnumPropertyGetFunc getfunc, EnumPropertySetFunc setfunc, - EnumPropertyItemFunc itemfunc); + EnumPropertyItemFunc itemfunc, + EnumPropertyGetTransformFunc get_transform_fn, + EnumPropertySetTransformFunc set_transform_fn); void RNA_def_property_string_funcs_runtime(PropertyRNA *prop, StringPropertyGetFunc getfunc, StringPropertyLengthFunc lengthfunc, - StringPropertySetFunc setfunc); + StringPropertySetFunc setfunc, + StringPropertyGetTransformFunc get_transform_fn, + StringPropertySetTransformFunc set_transform_fn); void RNA_def_property_string_search_func_runtime(PropertyRNA *prop, StringPropertySearchFunc search_fn, eStringPropertySearchFlag search_flag); diff --git a/source/blender/makesrna/RNA_types.hh b/source/blender/makesrna/RNA_types.hh index ca82f640e4a..ab65dd6c22b 100644 --- a/source/blender/makesrna/RNA_types.hh +++ b/source/blender/makesrna/RNA_types.hh @@ -684,30 +684,93 @@ struct EnumPropertyItem { /** Separator for RNA enum that begins a new column in menus (shown in the UI). */ #define RNA_ENUM_ITEM_SEPR_COLUMN RNA_ENUM_ITEM_HEADING("", NULL) -/* extended versions with PropertyRNA argument */ +/* Extended versions with PropertyRNA argument. Used in particular by the bpy code to wrap all the + * py-defined callbacks when defining a property using `bpy.props` module. + * + * The 'Transform' ones allow to add a transform step (applied after getting, or before setting the + * value), which only modifies the value, but does not handle actual storage. Currently only used + * by `bpy`, more details in the documentation of #BPyPropStore. + */ using BooleanPropertyGetFunc = bool (*)(PointerRNA *ptr, PropertyRNA *prop); using BooleanPropertySetFunc = void (*)(PointerRNA *ptr, PropertyRNA *prop, bool value); -using BooleanArrayPropertyGetFunc = void (*)(PointerRNA *ptr, PropertyRNA *prop, bool *values); +using BooleanPropertyGetTransformFunc = bool (*)(PointerRNA *ptr, + PropertyRNA *prop, + bool value, + bool is_set); +using BooleanPropertySetTransformFunc = + bool (*)(PointerRNA *ptr, PropertyRNA *prop, bool new_value, bool curr_value, bool is_set); + +using BooleanArrayPropertyGetFunc = void (*)(PointerRNA *ptr, PropertyRNA *prop, bool *r_values); using BooleanArrayPropertySetFunc = void (*)(PointerRNA *ptr, PropertyRNA *prop, - const bool *values); + const bool *r_values); +using BooleanArrayPropertyGetTransformFunc = void (*)( + PointerRNA *ptr, PropertyRNA *prop, const bool *curr_values, bool is_set, bool *r_values); +using BooleanArrayPropertySetTransformFunc = void (*)(PointerRNA *ptr, + PropertyRNA *prop, + const bool *new_values, + const bool *curr_values, + bool is_set, + bool *r_values); + using IntPropertyGetFunc = int (*)(PointerRNA *ptr, PropertyRNA *prop); using IntPropertySetFunc = void (*)(PointerRNA *ptr, PropertyRNA *prop, int value); +using IntPropertyGetTransformFunc = int (*)(PointerRNA *ptr, + PropertyRNA *prop, + int value, + bool is_set); +using IntPropertySetTransformFunc = + int (*)(PointerRNA *ptr, PropertyRNA *prop, int new_value, int curr_value, bool is_set); using IntArrayPropertyGetFunc = void (*)(PointerRNA *ptr, PropertyRNA *prop, int *values); using IntArrayPropertySetFunc = void (*)(PointerRNA *ptr, PropertyRNA *prop, const int *values); +using IntArrayPropertyGetTransformFunc = void (*)( + PointerRNA *ptr, PropertyRNA *prop, const int *curr_values, bool is_set, int *r_values); +using IntArrayPropertySetTransformFunc = void (*)(PointerRNA *ptr, + PropertyRNA *prop, + const int *new_values, + const int *curr_values, + bool is_set, + int *r_values); using IntPropertyRangeFunc = void (*)(PointerRNA *ptr, PropertyRNA *prop, int *min, int *max, int *softmin, int *softmax); + using FloatPropertyGetFunc = float (*)(PointerRNA *ptr, PropertyRNA *prop); using FloatPropertySetFunc = void (*)(PointerRNA *ptr, PropertyRNA *prop, float value); +using FloatPropertyGetTransformFunc = float (*)(PointerRNA *ptr, + PropertyRNA *prop, + float value, + bool is_set); +using FloatPropertySetTransformFunc = + float (*)(PointerRNA *ptr, PropertyRNA *prop, float new_value, float curr_value, bool is_set); using FloatArrayPropertyGetFunc = void (*)(PointerRNA *ptr, PropertyRNA *prop, float *values); using FloatArrayPropertySetFunc = void (*)(PointerRNA *ptr, PropertyRNA *prop, const float *values); +using FloatArrayPropertyGetTransformFunc = void (*)( + PointerRNA *ptr, PropertyRNA *prop, const float *curr_values, bool is_set, float *r_values); +using FloatArrayPropertySetTransformFunc = void (*)(PointerRNA *ptr, + PropertyRNA *prop, + const float *new_values, + const float *curr_values, + bool is_set, + float *r_values); using FloatPropertyRangeFunc = void (*)( PointerRNA *ptr, PropertyRNA *prop, float *min, float *max, float *softmin, float *softmax); -using StringPropertyGetFunc = void (*)(PointerRNA *ptr, PropertyRNA *prop, char *value); + +using StringPropertyGetFunc = std::string (*)(PointerRNA *ptr, PropertyRNA *prop); using StringPropertyLengthFunc = int (*)(PointerRNA *ptr, PropertyRNA *prop); -using StringPropertySetFunc = void (*)(PointerRNA *ptr, PropertyRNA *prop, const char *value); +using StringPropertySetFunc = void (*)(PointerRNA *ptr, + PropertyRNA *prop, + const std::string &value); +using StringPropertyGetTransformFunc = std::string (*)(PointerRNA *ptr, + PropertyRNA *prop, + const std::string &value, + bool is_set); +using StringPropertySetTransformFunc = std::string (*)(PointerRNA *ptr, + PropertyRNA *prop, + const std::string &new_value, + const std::string &curr_value, + bool is_set); struct StringPropertySearchVisitParams { /** Text being searched for. */ @@ -763,6 +826,12 @@ using StringPropertyPathFilterFunc = std::optional (*)(const bConte using EnumPropertyGetFunc = int (*)(PointerRNA *ptr, PropertyRNA *prop); using EnumPropertySetFunc = void (*)(PointerRNA *ptr, PropertyRNA *prop, int value); +using EnumPropertyGetTransformFunc = int (*)(PointerRNA *ptr, + PropertyRNA *prop, + int value, + bool is_set); +using EnumPropertySetTransformFunc = + int (*)(PointerRNA *ptr, PropertyRNA *prop, int new_value, int curr_value, bool is_set); /* same as PropEnumItemFunc */ using EnumPropertyItemFunc = const EnumPropertyItem *(*)(bContext *C, PointerRNA *ptr, diff --git a/source/blender/makesrna/intern/makesrna.cc b/source/blender/makesrna/intern/makesrna.cc index 3fbf6aff2fb..559c07f07b6 100644 --- a/source/blender/makesrna/intern/makesrna.cc +++ b/source/blender/makesrna/intern/makesrna.cc @@ -2050,7 +2050,8 @@ static void rna_def_property_funcs(FILE *f, StructRNA *srna, PropertyDefRNA *dp) BoolPropertyRNA *bprop = (BoolPropertyRNA *)prop; if (!(prop->flag & PROP_EDITABLE) && - (bprop->set || bprop->set_ex || bprop->setarray || bprop->setarray_ex)) + (bprop->set || bprop->set_ex || bprop->set_transform || bprop->setarray || + bprop->setarray_ex || bprop->setarray_transform)) { CLOG_ERROR(&LOG, "%s.%s, is read-only but has defines a \"set\" callback.", @@ -2060,7 +2061,8 @@ static void rna_def_property_funcs(FILE *f, StructRNA *srna, PropertyDefRNA *dp) } if (!prop->arraydimension && - (bprop->getarray || bprop->getarray_ex || bprop->setarray || bprop->setarray_ex)) + (bprop->getarray || bprop->getarray_ex || bprop->getarray_transform || bprop->setarray || + bprop->setarray_ex || bprop->setarray_transform)) { CLOG_ERROR(&LOG, "%s.%s, is not an array but defines an array callback.", @@ -2091,7 +2093,8 @@ static void rna_def_property_funcs(FILE *f, StructRNA *srna, PropertyDefRNA *dp) IntPropertyRNA *iprop = (IntPropertyRNA *)prop; if (!(prop->flag & PROP_EDITABLE) && - (iprop->set || iprop->set_ex || iprop->setarray || iprop->setarray_ex)) + (iprop->set || iprop->set_ex || iprop->set_transform || iprop->setarray || + iprop->setarray_ex || iprop->setarray_transform)) { CLOG_ERROR(&LOG, "%s.%s, is read-only but has defines a \"set\" callback.", @@ -2101,7 +2104,8 @@ static void rna_def_property_funcs(FILE *f, StructRNA *srna, PropertyDefRNA *dp) } if (!prop->arraydimension && - (iprop->getarray || iprop->getarray_ex || iprop->setarray || iprop->setarray_ex)) + (iprop->getarray || iprop->getarray_ex || iprop->getarray_transform || iprop->setarray || + iprop->setarray_ex || iprop->setarray_transform)) { CLOG_ERROR(&LOG, "%s.%s, is not an array but defines an array callback.", @@ -2136,7 +2140,8 @@ static void rna_def_property_funcs(FILE *f, StructRNA *srna, PropertyDefRNA *dp) FloatPropertyRNA *fprop = (FloatPropertyRNA *)prop; if (!(prop->flag & PROP_EDITABLE) && - (fprop->set || fprop->set_ex || fprop->setarray || fprop->setarray_ex)) + (fprop->set || fprop->set_ex || fprop->set_transform || fprop->setarray || + fprop->setarray_ex || fprop->setarray_transform)) { CLOG_ERROR(&LOG, "%s.%s, is read-only but has defines a \"set\" callback.", @@ -2146,7 +2151,8 @@ static void rna_def_property_funcs(FILE *f, StructRNA *srna, PropertyDefRNA *dp) } if (!prop->arraydimension && - (fprop->getarray || fprop->getarray_ex || fprop->setarray || fprop->setarray_ex)) + (fprop->getarray || fprop->getarray_ex || fprop->getarray_transform || fprop->setarray || + fprop->setarray_ex || fprop->setarray_transform)) { CLOG_ERROR(&LOG, "%s.%s, is not an array but defines an array callback.", @@ -2181,7 +2187,8 @@ static void rna_def_property_funcs(FILE *f, StructRNA *srna, PropertyDefRNA *dp) EnumPropertyRNA *eprop = (EnumPropertyRNA *)prop; if (dp->enumbitflags && eprop->item_fn && - !(eprop->item != rna_enum_dummy_NULL_items || eprop->set || eprop->set_ex)) + !(eprop->item != rna_enum_dummy_NULL_items || eprop->set || eprop->set_ex || + eprop->set_transform)) { CLOG_ERROR(&LOG, "%s.%s, bitflag enum should not define an `item` callback function, unless " @@ -2191,7 +2198,7 @@ static void rna_def_property_funcs(FILE *f, StructRNA *srna, PropertyDefRNA *dp) DefRNA.error = true; } - if (!(prop->flag & PROP_EDITABLE) && (eprop->set || eprop->set_ex)) { + if (!(prop->flag & PROP_EDITABLE) && (eprop->set || eprop->set_ex || eprop->set_transform)) { CLOG_ERROR(&LOG, "%s.%s, is read-only but has defines a \"set\" callback.", srna->identifier, @@ -2212,7 +2219,7 @@ static void rna_def_property_funcs(FILE *f, StructRNA *srna, PropertyDefRNA *dp) case PROP_STRING: { StringPropertyRNA *sprop = (StringPropertyRNA *)prop; - if (!(prop->flag & PROP_EDITABLE) && (sprop->set || sprop->set_ex)) { + if (!(prop->flag & PROP_EDITABLE) && (sprop->set || sprop->set_ex || sprop->set_transform)) { CLOG_ERROR(&LOG, "%s.%s, is read-only but has defines a \"set\" callback.", srna->identifier, @@ -4453,7 +4460,7 @@ static void rna_generate_property(FILE *f, StructRNA *srna, const char *nest, Pr case PROP_BOOLEAN: { BoolPropertyRNA *bprop = (BoolPropertyRNA *)prop; fprintf(f, - "\t%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %d, ", + "\t%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %d, ", rna_function_string(bprop->get), rna_function_string(bprop->set), rna_function_string(bprop->getarray), @@ -4462,6 +4469,10 @@ static void rna_generate_property(FILE *f, StructRNA *srna, const char *nest, Pr rna_function_string(bprop->set_ex), rna_function_string(bprop->getarray_ex), rna_function_string(bprop->setarray_ex), + rna_function_string(bprop->get_transform), + rna_function_string(bprop->set_transform), + rna_function_string(bprop->getarray_transform), + rna_function_string(bprop->setarray_transform), rna_function_string(bprop->get_default), rna_function_string(bprop->get_default_array), bprop->defaultvalue); @@ -4476,7 +4487,7 @@ static void rna_generate_property(FILE *f, StructRNA *srna, const char *nest, Pr case PROP_INT: { IntPropertyRNA *iprop = (IntPropertyRNA *)prop; fprintf(f, - "\t%s, %s, %s, %s, %s, %s, %s, %s, %s, %s,\n\t", + "\t%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,\n\t", rna_function_string(iprop->get), rna_function_string(iprop->set), rna_function_string(iprop->getarray), @@ -4486,7 +4497,11 @@ static void rna_generate_property(FILE *f, StructRNA *srna, const char *nest, Pr rna_function_string(iprop->set_ex), rna_function_string(iprop->getarray_ex), rna_function_string(iprop->setarray_ex), - rna_function_string(iprop->range_ex)); + rna_function_string(iprop->range_ex), + rna_function_string(iprop->get_transform), + rna_function_string(iprop->set_transform), + rna_function_string(iprop->getarray_transform), + rna_function_string(iprop->setarray_transform)); fprintf(f, "%s", rna_ui_scale_type_string(iprop->ui_scale_type)); fprintf(f, ", "); rna_int_print(f, iprop->softmin); @@ -4517,7 +4532,7 @@ static void rna_generate_property(FILE *f, StructRNA *srna, const char *nest, Pr case PROP_FLOAT: { FloatPropertyRNA *fprop = (FloatPropertyRNA *)prop; fprintf(f, - "\t%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, ", + "\t%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, ", rna_function_string(fprop->get), rna_function_string(fprop->set), rna_function_string(fprop->getarray), @@ -4527,7 +4542,11 @@ static void rna_generate_property(FILE *f, StructRNA *srna, const char *nest, Pr rna_function_string(fprop->set_ex), rna_function_string(fprop->getarray_ex), rna_function_string(fprop->setarray_ex), - rna_function_string(fprop->range_ex)); + rna_function_string(fprop->range_ex), + rna_function_string(fprop->get_transform), + rna_function_string(fprop->set_transform), + rna_function_string(fprop->getarray_transform), + rna_function_string(fprop->setarray_transform)); fprintf(f, "%s, ", rna_ui_scale_type_string(fprop->ui_scale_type)); rna_float_print(f, fprop->softmin); fprintf(f, ", "); @@ -4559,13 +4578,15 @@ static void rna_generate_property(FILE *f, StructRNA *srna, const char *nest, Pr case PROP_STRING: { StringPropertyRNA *sprop = (StringPropertyRNA *)prop; fprintf(f, - "\t%s, %s, %s, %s, %s, %s, %s, eStringPropertySearchFlag(%d), %s, %d, ", + "\t%s, %s, %s, %s, %s, %s, %s, %s, %s, eStringPropertySearchFlag(%d), %s, %d, ", rna_function_string(sprop->get), rna_function_string(sprop->length), rna_function_string(sprop->set), rna_function_string(sprop->get_ex), rna_function_string(sprop->length_ex), rna_function_string(sprop->set_ex), + rna_function_string(sprop->get_transform), + rna_function_string(sprop->set_transform), rna_function_string(sprop->search), int(sprop->search_flag), rna_function_string(sprop->path_filter), @@ -4577,12 +4598,14 @@ static void rna_generate_property(FILE *f, StructRNA *srna, const char *nest, Pr case PROP_ENUM: { EnumPropertyRNA *eprop = (EnumPropertyRNA *)prop; fprintf(f, - "\t%s, %s, %s, %s, %s, %s, ", + "\t%s, %s, %s, %s, %s, %s, %s, %s, ", rna_function_string(eprop->get), rna_function_string(eprop->set), rna_function_string(eprop->item_fn), rna_function_string(eprop->get_ex), rna_function_string(eprop->set_ex), + rna_function_string(eprop->get_transform), + rna_function_string(eprop->set_transform), rna_function_string(eprop->get_default)); if (eprop->item) { const char *item_global_id = rna_enum_id_from_pointer(eprop->item); diff --git a/source/blender/makesrna/intern/rna_access.cc b/source/blender/makesrna/intern/rna_access.cc index 10afb8195f8..2e57e4cc14f 100644 --- a/source/blender/makesrna/intern/rna_access.cc +++ b/source/blender/makesrna/intern/rna_access.cc @@ -2538,29 +2538,36 @@ void RNA_property_update_main(Main *bmain, Scene *scene, PointerRNA *ptr, Proper /* Property Data */ +static bool property_boolean_get(PointerRNA *ptr, PropertyRNAOrID &prop_rna_or_id) +{ + if (prop_rna_or_id.idprop) { + return IDP_Bool(prop_rna_or_id.idprop); + } + BoolPropertyRNA *bprop = reinterpret_cast(prop_rna_or_id.rnaprop); + if (bprop->get) { + return bprop->get(ptr); + } + if (bprop->get_ex) { + return bprop->get_ex(ptr, &bprop->property); + } + return bprop->defaultvalue; +} + bool RNA_property_boolean_get(PointerRNA *ptr, PropertyRNA *prop) { - BoolPropertyRNA *bprop = (BoolPropertyRNA *)prop; - IDProperty *idprop; - bool value; - BLI_assert(RNA_property_type(prop) == PROP_BOOLEAN); - BLI_assert(RNA_property_array_check(prop) == false); - if ((idprop = rna_idproperty_check(&prop, ptr))) { - value = IDP_Bool(idprop); - } - else if (bprop->get) { - value = bprop->get(ptr); - } - else if (bprop->get_ex) { - value = bprop->get_ex(ptr, prop); - } - else { - value = bprop->defaultvalue; - } + PropertyRNAOrID prop_rna_or_id; + rna_property_rna_or_id_get(prop, ptr, &prop_rna_or_id); + BLI_assert(!prop_rna_or_id.is_array); + /* Make initial `prop` pointer invalid, to ensure that it is not used anywhere below. */ + prop = nullptr; + BoolPropertyRNA *bprop = reinterpret_cast(prop_rna_or_id.rnaprop); - BLI_assert(ELEM(value, false, true)); + bool value = property_boolean_get(ptr, prop_rna_or_id); + if (bprop->get_transform) { + value = bprop->get_transform(ptr, &bprop->property, value, prop_rna_or_id.is_set); + } return value; } @@ -2574,17 +2581,23 @@ bool RNA_property_boolean_get(PointerRNA *ptr, PropertyRNA *prop) void RNA_property_boolean_set(PointerRNA *ptr, PropertyRNA *prop, bool value) { - BoolPropertyRNA *bprop = (BoolPropertyRNA *)prop; - IDProperty *idprop; - BLI_assert(RNA_property_type(prop) == PROP_BOOLEAN); - BLI_assert(RNA_property_array_check(prop) == false); - BLI_assert(ELEM(value, false, true)); - /* just in case other values are passed */ - BLI_assert(ELEM(value, true, false)); + PropertyRNAOrID prop_rna_or_id; + rna_property_rna_or_id_get(prop, ptr, &prop_rna_or_id); + BLI_assert(!prop_rna_or_id.is_array); + /* Make initial `prop` pointer invalid, to ensure that it is not used anywhere below. */ + prop = nullptr; + IDProperty *idprop = prop_rna_or_id.idprop; + BoolPropertyRNA *bprop = reinterpret_cast(prop_rna_or_id.rnaprop); - if ((idprop = rna_idproperty_check(&prop, ptr))) { + if (bprop->set_transform) { + /* Get raw, untransformed (aka 'storage') value. */ + const bool curr_value = property_boolean_get(ptr, prop_rna_or_id); + value = bprop->set_transform(ptr, &bprop->property, value, curr_value, prop_rna_or_id.is_set); + } + + if (idprop) { IDP_Bool(idprop) = value; rna_idproperty_touch(idprop); } @@ -2592,19 +2605,19 @@ void RNA_property_boolean_set(PointerRNA *ptr, PropertyRNA *prop, bool value) bprop->set(ptr, value); } else if (bprop->set_ex) { - bprop->set_ex(ptr, prop, value); + bprop->set_ex(ptr, &bprop->property, value); } - else if (prop->flag & PROP_EDITABLE) { + else if (bprop->property.flag & PROP_EDITABLE) { if (IDProperty *group = RNA_struct_system_idprops(ptr, true)) { #ifdef USE_INT_IDPROPS_FOR_BOOLEAN_RNA_PROP IDP_AddToGroup( group, - blender::bke::idprop::create(prop->identifier, int(value), IDP_FLAG_STATIC_TYPE) + blender::bke::idprop::create(prop_rna_or_id.identifier, int(value), IDP_FLAG_STATIC_TYPE) .release()); #else IDP_AddToGroup( group, - blender::bke::idprop::create_bool(prop->identifier, value, IDP_FLAG_STATIC_TYPE) + blender::bke::idprop::create_bool(prop_rna_or_id.identifier, value, IDP_FLAG_STATIC_TYPE) .release()); #endif } @@ -2661,46 +2674,73 @@ static void rna_property_boolean_get_default_array_values(PointerRNA *ptr, bprop->defaultarray, length, bprop->defaultvalue, out_length, r_values); } -void RNA_property_boolean_get_array(PointerRNA *ptr, PropertyRNA *prop, bool *values) +static void property_boolean_get_array(PointerRNA *ptr, + PropertyRNAOrID &prop_rna_or_id, + blender::MutableSpan r_values) { - BoolPropertyRNA *bprop = (BoolPropertyRNA *)prop; - IDProperty *idprop; - - BLI_assert(RNA_property_type(prop) == PROP_BOOLEAN); - BLI_assert(RNA_property_array_check(prop) != false); - - if ((idprop = rna_idproperty_check(&prop, ptr))) { - if (prop->arraydimension == 0) { - values[0] = RNA_property_boolean_get(ptr, prop); + PropertyRNA *rna_prop = prop_rna_or_id.rnaprop; + BoolPropertyRNA *bprop = reinterpret_cast(rna_prop); + if (prop_rna_or_id.idprop) { + IDProperty *idprop = prop_rna_or_id.idprop; + BLI_assert(idprop->len == RNA_property_array_length(ptr, rna_prop) || + (rna_prop->flag & PROP_IDPROPERTY)); + if (rna_prop->arraydimension == 0) { + r_values[0] = RNA_property_boolean_get(ptr, rna_prop); } else if (idprop->subtype == IDP_INT) { /* Some boolean IDProperty arrays might be saved in files as an integer * array property, since the boolean IDProperty type was added later. */ - int *values_src = static_cast(IDP_Array(idprop)); - for (uint i = 0; i < idprop->len; i++) { - values[i] = bool(values_src[i]); + const int *values_src = static_cast(IDP_Array(idprop)); + for (int i = 0; i < idprop->len; i++) { + r_values[i] = bool(values_src[i]); } } else if (idprop->subtype == IDP_BOOLEAN) { bool *values_src = static_cast(IDP_Array(idprop)); for (int i = 0; i < idprop->len; i++) { - values[i] = values_src[i]; + r_values[i] = values_src[i]; } } } - else if (prop->arraydimension == 0) { - values[0] = RNA_property_boolean_get(ptr, prop); + else if (rna_prop->arraydimension == 0) { + r_values[0] = RNA_property_boolean_get(ptr, rna_prop); } else if (bprop->getarray) { - bprop->getarray(ptr, values); + bprop->getarray(ptr, r_values.data()); } else if (bprop->getarray_ex) { - bprop->getarray_ex(ptr, prop, values); + bprop->getarray_ex(ptr, rna_prop, r_values.data()); } else { - rna_property_boolean_get_default_array_values(ptr, bprop, values); + rna_property_boolean_get_default_array_values(ptr, bprop, r_values.data()); } } + +void RNA_property_boolean_get_array(PointerRNA *ptr, PropertyRNA *prop, bool *values) +{ + BLI_assert(RNA_property_type(prop) == PROP_BOOLEAN); + + PropertyRNAOrID prop_rna_or_id; + rna_property_rna_or_id_get(prop, ptr, &prop_rna_or_id); + BLI_assert(prop_rna_or_id.is_array); + /* Make initial `prop` pointer invalid, to ensure that it is not used anywhere below. */ + prop = nullptr; + BoolPropertyRNA *bprop = reinterpret_cast(prop_rna_or_id.rnaprop); + + blender::MutableSpan r_values(values, int64_t(prop_rna_or_id.array_len)); + values = nullptr; /* Do not access this 'raw' pointer anymore in code below. */ + property_boolean_get_array(ptr, prop_rna_or_id, r_values); + if (bprop->getarray_transform) { + /* NOTE: Given current implementation, it would _probably_ be safe to use `values` for both + * input 'current values' and output 'final values', since python will make a copy of the input + * anyways. Think it's better to keep it clean and make a copy here to avoid any potential + * issues in the future though. */ + blender::Array curr_values(r_values.as_span()); + bprop->getarray_transform( + ptr, &bprop->property, curr_values.data(), prop_rna_or_id.is_set, r_values.data()); + } +} + void RNA_property_boolean_get_array_at_most(PointerRNA *ptr, PropertyRNA *prop, bool *values, @@ -2749,61 +2789,104 @@ bool RNA_property_boolean_get_index(PointerRNA *ptr, PropertyRNA *prop, int inde void RNA_property_boolean_set_array(PointerRNA *ptr, PropertyRNA *prop, const bool *values) { - BoolPropertyRNA *bprop = (BoolPropertyRNA *)prop; - IDProperty *idprop; - BLI_assert(RNA_property_type(prop) == PROP_BOOLEAN); - BLI_assert(RNA_property_array_check(prop) != false); - if ((idprop = rna_idproperty_check(&prop, ptr))) { - if (prop->arraydimension == 0) { - IDP_Int(idprop) = values[0]; + PropertyRNAOrID prop_rna_or_id; + rna_property_rna_or_id_get(prop, ptr, &prop_rna_or_id); + BLI_assert(prop_rna_or_id.is_array); + /* Make initial `prop` pointer invalid, to ensure that it is not used anywhere below. */ + prop = nullptr; + IDProperty *idprop = prop_rna_or_id.idprop; + PropertyRNA *rna_prop = prop_rna_or_id.rnaprop; + BoolPropertyRNA *bprop = reinterpret_cast(rna_prop); + + const int64_t values_num = int64_t(prop_rna_or_id.array_len); + blender::Span final_values(values, values_num); + values = nullptr; /* Do not access this 'raw' pointer anymore in code below. */ + /* Default init does not allocate anything, so it's cheap. This is only reinitialized with actual + * `values_num` items if `setarray_transform` is called. */ + blender::Array final_values_storage{}; + if (bprop->setarray_transform) { + /* Get raw, untransformed (aka 'storage') value. */ + blender::Array curr_values(values_num); + property_boolean_get_array(ptr, prop_rna_or_id, curr_values.as_mutable_span()); + + final_values_storage.reinitialize(values_num); + bprop->setarray_transform(ptr, + rna_prop, + final_values.data(), + curr_values.data(), + prop_rna_or_id.is_set, + final_values_storage.data()); + final_values = final_values_storage.as_span(); + } + + if (idprop) { + /* Support writing to integer and boolean IDProperties, since boolean + * RNA properties used to be stored with integer IDProperties. */ + if (rna_prop->arraydimension == 0) { + if (idprop->subtype == IDP_BOOLEAN) { + IDP_Bool(idprop) = final_values[0]; + } + else { + BLI_assert(idprop->subtype == IDP_INT); + IDP_Int(idprop) = final_values[0]; + } } else { BLI_assert(idprop->type = IDP_ARRAY); + BLI_assert(idprop->len == values_num); if (idprop->subtype == IDP_BOOLEAN) { - memcpy(IDP_Array(idprop), values, sizeof(int8_t) * idprop->len); + memcpy(IDP_Array(idprop), + final_values.data(), + sizeof(decltype(final_values)::value_type) * idprop->len); } - else if (idprop->subtype == IDP_INT) { - /* Support writing to integer and boolean IDProperties, since boolean - * RNA properties used to be stored with integer IDProperties. */ + else { + BLI_assert(idprop->subtype == IDP_INT); int *values_dst = static_cast(IDP_Array(idprop)); - for (uint i = 0; i < idprop->len; i++) { - values_dst[i] = int(values[i]); + for (int i = 0; i < idprop->len; i++) { + values_dst[i] = int(final_values[i]); } } } rna_idproperty_touch(idprop); } - else if (prop->arraydimension == 0) { - RNA_property_boolean_set(ptr, prop, values[0]); + else if (rna_prop->arraydimension == 0) { + RNA_property_boolean_set(ptr, rna_prop, final_values[0]); } else if (bprop->setarray) { - bprop->setarray(ptr, values); + bprop->setarray(ptr, final_values.data()); } else if (bprop->setarray_ex) { - bprop->setarray_ex(ptr, prop, values); + bprop->setarray_ex(ptr, rna_prop, final_values.data()); } - else if (prop->flag & PROP_EDITABLE) { - IDPropertyTemplate val = {0}; - - val.array.len = prop->totarraylength; + else if (rna_prop->flag & PROP_EDITABLE) { + if (IDProperty *group = RNA_struct_system_idprops(ptr, true)) { + IDPropertyTemplate val = {0}; + val.array.len = rna_prop->totarraylength; #ifdef USE_INT_IDPROPS_FOR_BOOLEAN_RNA_PROP - val.array.type = IDP_INT; + val.array.type = IDP_INT; #else - val.array.type = IDP_BOOLEAN; + val.array.type = IDP_BOOLEAN; #endif - if (IDProperty *group = RNA_struct_system_idprops(ptr, true)) { - idprop = IDP_New(IDP_ARRAY, &val, prop->identifier, IDP_FLAG_STATIC_TYPE); + idprop = IDP_New(IDP_ARRAY, &val, prop_rna_or_id.identifier, IDP_FLAG_STATIC_TYPE); IDP_AddToGroup(group, idprop); +#ifdef USE_INT_IDPROPS_FOR_BOOLEAN_RNA_PROP int *values_dst = static_cast(IDP_Array(idprop)); - for (uint i = 0; i < idprop->len; i++) { - values_dst[i] = values[i]; + for (int i = 0; i < idprop->len; i++) { + values_dst[i] = int(final_values[i]); } +#else + bool *values_dst = static_cast(IDP_Array(idprop)); + for (int i = 0; i < idprop->len; i++) { + values_dst[i] = final_values[i]; + } +#endif } } } + void RNA_property_boolean_set_array_at_most(PointerRNA *ptr, PropertyRNA *prop, const bool *values, @@ -2963,38 +3046,59 @@ bool RNA_property_boolean_get_default_index(PointerRNA *ptr, PropertyRNA *prop, return value; } -int RNA_property_int_get(PointerRNA *ptr, PropertyRNA *prop) +static int property_int_get(PointerRNA *ptr, PropertyRNAOrID &prop_rna_or_id) { - IntPropertyRNA *iprop = (IntPropertyRNA *)prop; - IDProperty *idprop; - - BLI_assert(RNA_property_type(prop) == PROP_INT); - BLI_assert(RNA_property_array_check(prop) == false); - - if ((idprop = rna_idproperty_check(&prop, ptr))) { - return IDP_Int(idprop); + if (prop_rna_or_id.idprop) { + return IDP_Int(prop_rna_or_id.idprop); } + IntPropertyRNA *iprop = reinterpret_cast(prop_rna_or_id.rnaprop); if (iprop->get) { return iprop->get(ptr); } if (iprop->get_ex) { - return iprop->get_ex(ptr, prop); + return iprop->get_ex(ptr, &iprop->property); } return iprop->defaultvalue; } +int RNA_property_int_get(PointerRNA *ptr, PropertyRNA *prop) +{ + BLI_assert(RNA_property_type(prop) == PROP_INT); + + PropertyRNAOrID prop_rna_or_id; + rna_property_rna_or_id_get(prop, ptr, &prop_rna_or_id); + BLI_assert(!prop_rna_or_id.is_array); + /* Make initial `prop` pointer invalid, to ensure that it is not used anywhere below. */ + prop = nullptr; + IntPropertyRNA *iprop = reinterpret_cast(prop_rna_or_id.rnaprop); + + int value = property_int_get(ptr, prop_rna_or_id); + if (iprop->get_transform) { + value = iprop->get_transform(ptr, &iprop->property, value, prop_rna_or_id.is_set); + } + + return value; +} + void RNA_property_int_set(PointerRNA *ptr, PropertyRNA *prop, int value) { - IntPropertyRNA *iprop = (IntPropertyRNA *)prop; - IDProperty *idprop; - BLI_assert(RNA_property_type(prop) == PROP_INT); - BLI_assert(RNA_property_array_check(prop) == false); - /* useful to check on bad values but set function should clamp */ - // BLI_assert(RNA_property_int_clamp(ptr, prop, &value) == 0); - if ((idprop = rna_idproperty_check(&prop, ptr))) { - RNA_property_int_clamp(ptr, prop, &value); + PropertyRNAOrID prop_rna_or_id; + rna_property_rna_or_id_get(prop, ptr, &prop_rna_or_id); + BLI_assert(!prop_rna_or_id.is_array); + /* Make initial `prop` pointer invalid, to ensure that it is not used anywhere below. */ + prop = nullptr; + IDProperty *idprop = prop_rna_or_id.idprop; + IntPropertyRNA *iprop = reinterpret_cast(prop_rna_or_id.rnaprop); + + if (iprop->set_transform) { + /* Get raw, untransformed (aka 'storage') value. */ + const int curr_value = property_int_get(ptr, prop_rna_or_id); + value = iprop->set_transform(ptr, &iprop->property, value, curr_value, prop_rna_or_id.is_set); + } + + if (idprop) { IDP_Int(idprop) = value; rna_idproperty_touch(idprop); } @@ -3002,14 +3106,14 @@ void RNA_property_int_set(PointerRNA *ptr, PropertyRNA *prop, int value) iprop->set(ptr, value); } else if (iprop->set_ex) { - iprop->set_ex(ptr, prop, value); + iprop->set_ex(ptr, &iprop->property, value); } - else if (prop->flag & PROP_EDITABLE) { - RNA_property_int_clamp(ptr, prop, &value); + else if (iprop->property.flag & PROP_EDITABLE) { if (IDProperty *group = RNA_struct_system_idprops(ptr, true)) { IDP_AddToGroup( group, - blender::bke::idprop::create(prop->identifier, value, IDP_FLAG_STATIC_TYPE).release()); + blender::bke::idprop::create(prop_rna_or_id.identifier, value, IDP_FLAG_STATIC_TYPE) + .release()); } } } @@ -3046,37 +3150,64 @@ static void rna_property_int_get_default_array_values(PointerRNA *ptr, iprop->defaultarray, length, iprop->defaultvalue, out_length, r_values); } -void RNA_property_int_get_array(PointerRNA *ptr, PropertyRNA *prop, int *values) +static void property_int_get_array(PointerRNA *ptr, + PropertyRNAOrID &prop_rna_or_id, + blender::MutableSpan r_values) { - IntPropertyRNA *iprop = (IntPropertyRNA *)prop; - IDProperty *idprop; - - BLI_assert(RNA_property_type(prop) == PROP_INT); - BLI_assert(RNA_property_array_check(prop) != false); - - if ((idprop = rna_idproperty_check(&prop, ptr))) { - BLI_assert(idprop->len == RNA_property_array_length(ptr, prop) || - (prop->flag & PROP_IDPROPERTY)); - if (prop->arraydimension == 0) { - values[0] = RNA_property_int_get(ptr, prop); + PropertyRNA *rna_prop = prop_rna_or_id.rnaprop; + IntPropertyRNA *iprop = reinterpret_cast(rna_prop); + if (prop_rna_or_id.idprop) { + IDProperty *idprop = prop_rna_or_id.idprop; + BLI_assert(idprop->len == RNA_property_array_length(ptr, rna_prop) || + (rna_prop->flag & PROP_IDPROPERTY)); + if (rna_prop->arraydimension == 0) { + r_values[0] = RNA_property_int_get(ptr, rna_prop); } else { - memcpy(values, IDP_Array(idprop), sizeof(int) * idprop->len); + memcpy(r_values.data(), + IDP_Array(idprop), + sizeof(decltype(r_values)::value_type) * idprop->len); } } - else if (prop->arraydimension == 0) { - values[0] = RNA_property_int_get(ptr, prop); + else if (rna_prop->arraydimension == 0) { + r_values[0] = RNA_property_int_get(ptr, rna_prop); } else if (iprop->getarray) { - iprop->getarray(ptr, values); + iprop->getarray(ptr, r_values.data()); } else if (iprop->getarray_ex) { - iprop->getarray_ex(ptr, prop, values); + iprop->getarray_ex(ptr, rna_prop, r_values.data()); } else { - rna_property_int_get_default_array_values(ptr, iprop, values); + rna_property_int_get_default_array_values(ptr, iprop, r_values.data()); } } + +void RNA_property_int_get_array(PointerRNA *ptr, PropertyRNA *prop, int *values) +{ + BLI_assert(RNA_property_type(prop) == PROP_INT); + + PropertyRNAOrID prop_rna_or_id; + rna_property_rna_or_id_get(prop, ptr, &prop_rna_or_id); + BLI_assert(prop_rna_or_id.is_array); + /* Make initial `prop` pointer invalid, to ensure that it is not used anywhere below. */ + prop = nullptr; + IntPropertyRNA *iprop = reinterpret_cast(prop_rna_or_id.rnaprop); + + blender::MutableSpan r_values(values, int64_t(prop_rna_or_id.array_len)); + values = nullptr; /* Do not access this 'raw' pointer anymore in code below. */ + property_int_get_array(ptr, prop_rna_or_id, r_values); + if (iprop->getarray_transform) { + /* NOTE: Given current implementation, it would _probably_ be safe to use `values` for both + * input 'current values' and output 'final values', since python will make a copy of the input + * anyways. Think it's better to keep it clean and make a copy here to avoid any potential + * issues in the future though. */ + blender::Array curr_values(r_values.as_span()); + iprop->getarray_transform( + ptr, &iprop->property, curr_values.data(), prop_rna_or_id.is_set, r_values.data()); + } +} + void RNA_property_int_get_array_at_most(PointerRNA *ptr, PropertyRNA *prop, int *values, @@ -3157,45 +3288,71 @@ 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) { - using namespace blender; - IntPropertyRNA *iprop = (IntPropertyRNA *)prop; - IDProperty *idprop; - BLI_assert(RNA_property_type(prop) == PROP_INT); - BLI_assert(RNA_property_array_check(prop) != false); - if ((idprop = rna_idproperty_check(&prop, ptr))) { - BLI_assert(idprop->len == RNA_property_array_length(ptr, prop) || - (prop->flag & PROP_IDPROPERTY)); - if (prop->arraydimension == 0) { - IDP_Int(idprop) = values[0]; + PropertyRNAOrID prop_rna_or_id; + rna_property_rna_or_id_get(prop, ptr, &prop_rna_or_id); + BLI_assert(prop_rna_or_id.is_array); + /* Make initial `prop` pointer invalid, to ensure that it is not used anywhere below. */ + prop = nullptr; + IDProperty *idprop = prop_rna_or_id.idprop; + PropertyRNA *rna_prop = prop_rna_or_id.rnaprop; + IntPropertyRNA *iprop = reinterpret_cast(rna_prop); + + const int64_t values_num = int64_t(prop_rna_or_id.array_len); + blender::Span final_values(values, values_num); + values = nullptr; /* Do not access this 'raw' pointer anymore in code below. */ + /* Default init does not allocate anything, so it's cheap. This is only reinitialized with actual + * `values_num` items if `setarray_transform` is called. */ + blender::Array final_values_storage{}; + if (iprop->setarray_transform) { + /* Get raw, untransformed (aka 'storage') value. */ + blender::Array curr_values(values_num); + property_int_get_array(ptr, prop_rna_or_id, curr_values.as_mutable_span()); + + final_values_storage.reinitialize(values_num); + iprop->setarray_transform(ptr, + rna_prop, + final_values.data(), + curr_values.data(), + prop_rna_or_id.is_set, + final_values_storage.data()); + final_values = final_values_storage.as_span(); + } + + if (idprop) { + BLI_assert(idprop->len == values_num); + if (rna_prop->arraydimension == 0) { + IDP_Int(idprop) = final_values[0]; } else { - memcpy(IDP_Array(idprop), values, sizeof(int) * idprop->len); + memcpy(IDP_Array(idprop), + final_values.data(), + sizeof(decltype(final_values)::value_type) * idprop->len); } rna_idproperty_touch(idprop); } - else if (prop->arraydimension == 0) { - RNA_property_int_set(ptr, prop, values[0]); + else if (rna_prop->arraydimension == 0) { + RNA_property_int_set(ptr, rna_prop, final_values[0]); } else if (iprop->setarray) { - iprop->setarray(ptr, values); + iprop->setarray(ptr, final_values.data()); } else if (iprop->setarray_ex) { - iprop->setarray_ex(ptr, prop, values); + iprop->setarray_ex(ptr, rna_prop, final_values.data()); } - else if (prop->flag & PROP_EDITABLE) { + else if (rna_prop->flag & PROP_EDITABLE) { // RNA_property_int_clamp_array(ptr, prop, &value); /* TODO. */ if (IDProperty *group = RNA_struct_system_idprops(ptr, true)) { IDP_AddToGroup(group, - bke::idprop::create(prop->identifier, - Span(values, prop->totarraylength), - IDP_FLAG_STATIC_TYPE) + blender::bke::idprop::create( + prop_rna_or_id.identifier, final_values, IDP_FLAG_STATIC_TYPE) .release()); } } } + void RNA_property_int_set_array_at_most(PointerRNA *ptr, PropertyRNA *prop, const int *values, @@ -3332,62 +3489,86 @@ int RNA_property_int_get_default_index(PointerRNA *ptr, PropertyRNA *prop, int i return value; } -float RNA_property_float_get(PointerRNA *ptr, PropertyRNA *prop) +static float property_float_get(PointerRNA *ptr, PropertyRNAOrID &prop_rna_or_id) { - FloatPropertyRNA *fprop = (FloatPropertyRNA *)prop; - IDProperty *idprop; - - BLI_assert(RNA_property_type(prop) == PROP_FLOAT); - BLI_assert(RNA_property_array_check(prop) == false); - - if ((idprop = rna_idproperty_check(&prop, ptr))) { + if (prop_rna_or_id.idprop) { + IDProperty *idprop = prop_rna_or_id.idprop; if (idprop->type == IDP_FLOAT) { return IDP_Float(idprop); } return float(IDP_Double(idprop)); } + FloatPropertyRNA *fprop = reinterpret_cast(prop_rna_or_id.rnaprop); if (fprop->get) { return fprop->get(ptr); } if (fprop->get_ex) { - return fprop->get_ex(ptr, prop); + return fprop->get_ex(ptr, &fprop->property); } return fprop->defaultvalue; } +float RNA_property_float_get(PointerRNA *ptr, PropertyRNA *prop) +{ + BLI_assert(RNA_property_type(prop) == PROP_FLOAT); + + PropertyRNAOrID prop_rna_or_id; + rna_property_rna_or_id_get(prop, ptr, &prop_rna_or_id); + BLI_assert(!prop_rna_or_id.is_array); + /* Make initial `prop` pointer invalid, to ensure that it is not used anywhere below. */ + prop = nullptr; + FloatPropertyRNA *fprop = reinterpret_cast(prop_rna_or_id.rnaprop); + + float value = property_float_get(ptr, prop_rna_or_id); + if (fprop->get_transform) { + value = fprop->get_transform(ptr, &fprop->property, value, prop_rna_or_id.is_set); + } + + return value; +} + void RNA_property_float_set(PointerRNA *ptr, PropertyRNA *prop, float value) { - FloatPropertyRNA *fprop = (FloatPropertyRNA *)prop; - IDProperty *idprop; - BLI_assert(RNA_property_type(prop) == PROP_FLOAT); - BLI_assert(RNA_property_array_check(prop) == false); - /* useful to check on bad values but set function should clamp */ - // BLI_assert(RNA_property_float_clamp(ptr, prop, &value) == 0); - if ((idprop = rna_idproperty_check(&prop, ptr))) { - RNA_property_float_clamp(ptr, prop, &value); + PropertyRNAOrID prop_rna_or_id; + rna_property_rna_or_id_get(prop, ptr, &prop_rna_or_id); + BLI_assert(!prop_rna_or_id.is_array); + /* Make initial `prop` pointer invalid, to ensure that it is not used anywhere below. */ + prop = nullptr; + IDProperty *idprop = prop_rna_or_id.idprop; + FloatPropertyRNA *fprop = reinterpret_cast(prop_rna_or_id.rnaprop); + + if (fprop->set_transform) { + /* Get raw, untransformed (aka 'storage') value. */ + const float curr_value = property_float_get(ptr, prop_rna_or_id); + value = fprop->set_transform(ptr, &fprop->property, value, curr_value, prop_rna_or_id.is_set); + } + + if (idprop) { if (idprop->type == IDP_FLOAT) { IDP_Float(idprop) = value; } else { - IDP_Double(idprop) = value; + IDP_Double(idprop) = double(value); } - rna_idproperty_touch(idprop); } else if (fprop->set) { fprop->set(ptr, value); } else if (fprop->set_ex) { - fprop->set_ex(ptr, prop, value); + fprop->set_ex(ptr, &fprop->property, value); } - else if (prop->flag & PROP_EDITABLE) { - RNA_property_float_clamp(ptr, prop, &value); + else if (fprop->property.flag & PROP_EDITABLE) { + /* FIXME: This is only called here? What about already existing IDProps (see above)? And + * similar code for Int properties? */ + RNA_property_float_clamp(ptr, &fprop->property, &value); if (IDProperty *group = RNA_struct_system_idprops(ptr, true)) { IDP_AddToGroup( group, - blender::bke::idprop::create(prop->identifier, value, IDP_FLAG_STATIC_TYPE).release()); + blender::bke::idprop::create(prop_rna_or_id.identifier, value, IDP_FLAG_STATIC_TYPE) + .release()); } } } @@ -3438,49 +3619,77 @@ static void rna_property_float_get_default_array_values(PointerRNA *ptr, } int length = fprop->property.totarraylength; - int out_length = RNA_property_array_length(ptr, (PropertyRNA *)fprop); + int out_length = RNA_property_array_length(ptr, &fprop->property); rna_property_float_fill_default_array_values( fprop->defaultarray, length, fprop->defaultvalue, out_length, r_values); } -void RNA_property_float_get_array(PointerRNA *ptr, PropertyRNA *prop, float *values) +static void property_float_get_array(PointerRNA *ptr, + PropertyRNAOrID &prop_rna_or_id, + blender::MutableSpan r_values) { - FloatPropertyRNA *fprop = (FloatPropertyRNA *)prop; - IDProperty *idprop; - int i; - - BLI_assert(RNA_property_type(prop) == PROP_FLOAT); - BLI_assert(RNA_property_array_check(prop) != false); - - if ((idprop = rna_idproperty_check(&prop, ptr))) { - BLI_assert(idprop->len == RNA_property_array_length(ptr, prop) || - (prop->flag & PROP_IDPROPERTY)); - if (prop->arraydimension == 0) { - values[0] = RNA_property_float_get(ptr, prop); + PropertyRNA *rna_prop = prop_rna_or_id.rnaprop; + FloatPropertyRNA *fprop = reinterpret_cast(rna_prop); + if (prop_rna_or_id.idprop) { + IDProperty *idprop = prop_rna_or_id.idprop; + BLI_assert(idprop->len == RNA_property_array_length(ptr, rna_prop) || + (rna_prop->flag & PROP_IDPROPERTY)); + if (rna_prop->arraydimension == 0) { + r_values[0] = RNA_property_float_get(ptr, rna_prop); } else if (idprop->subtype == IDP_FLOAT) { - memcpy(values, IDP_Array(idprop), sizeof(float) * idprop->len); + memcpy(r_values.data(), + IDP_Array(idprop), + sizeof(decltype(r_values)::value_type) * idprop->len); } else { - for (i = 0; i < idprop->len; i++) { - values[i] = float(((double *)IDP_Array(idprop))[i]); + double *src_values = static_cast(IDP_Array(idprop)); + for (int i = 0; i < idprop->len; i++) { + r_values[i] = float(src_values[i]); } } } - else if (prop->arraydimension == 0) { - values[0] = RNA_property_float_get(ptr, prop); + else if (rna_prop->arraydimension == 0) { + r_values[0] = RNA_property_float_get(ptr, rna_prop); } else if (fprop->getarray) { - fprop->getarray(ptr, values); + fprop->getarray(ptr, r_values.data()); } else if (fprop->getarray_ex) { - fprop->getarray_ex(ptr, prop, values); + fprop->getarray_ex(ptr, rna_prop, r_values.data()); } else { - rna_property_float_get_default_array_values(ptr, fprop, values); + rna_property_float_get_default_array_values(ptr, fprop, r_values.data()); } } + +void RNA_property_float_get_array(PointerRNA *ptr, PropertyRNA *prop, float *values) +{ + BLI_assert(RNA_property_type(prop) == PROP_FLOAT); + BLI_assert(RNA_property_array_check(prop) != false); + + PropertyRNAOrID prop_rna_or_id; + rna_property_rna_or_id_get(prop, ptr, &prop_rna_or_id); + BLI_assert(prop_rna_or_id.is_array); + /* Make initial `prop` pointer invalid, to ensure that it is not used anywhere below. */ + prop = nullptr; + FloatPropertyRNA *fprop = reinterpret_cast(prop_rna_or_id.rnaprop); + + blender::MutableSpan r_values(values, int64_t(prop_rna_or_id.array_len)); + values = nullptr; /* Do not access this 'raw' pointer anymore in code below. */ + property_float_get_array(ptr, prop_rna_or_id, r_values); + if (fprop->getarray_transform) { + /* NOTE: Given current implementation, it would _probably_ be safe to use `values` for both + * input 'current values' and output 'final values', since python will make a copy of the input + * anyways. Think it's better to keep it clean and make a copy here to avoid any potential + * issues in the future though. */ + blender::Array curr_values(r_values.as_span()); + fprop->getarray_transform( + ptr, &fprop->property, curr_values.data(), prop_rna_or_id.is_set, r_values.data()); + } +} + void RNA_property_float_get_array_at_most(PointerRNA *ptr, PropertyRNA *prop, float *values, @@ -3561,55 +3770,82 @@ 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) { - FloatPropertyRNA *fprop = (FloatPropertyRNA *)prop; - IDProperty *idprop; - int i; - BLI_assert(RNA_property_type(prop) == PROP_FLOAT); - BLI_assert(RNA_property_array_check(prop) != false); - if ((idprop = rna_idproperty_check(&prop, ptr))) { - BLI_assert(idprop->len == RNA_property_array_length(ptr, prop) || - (prop->flag & PROP_IDPROPERTY)); - if (prop->arraydimension == 0) { + PropertyRNAOrID prop_rna_or_id; + rna_property_rna_or_id_get(prop, ptr, &prop_rna_or_id); + BLI_assert(prop_rna_or_id.is_array); + /* Make initial `prop` pointer invalid, to ensure that it is not used anywhere below. */ + prop = nullptr; + IDProperty *idprop = prop_rna_or_id.idprop; + PropertyRNA *rna_prop = prop_rna_or_id.rnaprop; + FloatPropertyRNA *fprop = reinterpret_cast(rna_prop); + + const int64_t values_num = int64_t(prop_rna_or_id.array_len); + blender::Span final_values(values, values_num); + values = nullptr; /* Do not access this 'raw' pointer anymore in code below. */ + /* Default init does not allocate anything, so it's cheap. This is only reinitialized with actual + * `values_num` items if `setarray_transform` is called. */ + blender::Array final_values_storage{}; + if (fprop->setarray_transform) { + /* Get raw, untransformed (aka 'storage') value. */ + blender::Array curr_values(values_num); + property_float_get_array(ptr, prop_rna_or_id, curr_values.as_mutable_span()); + + final_values_storage.reinitialize(values_num); + fprop->setarray_transform(ptr, + rna_prop, + final_values.data(), + curr_values.data(), + prop_rna_or_id.is_set, + final_values_storage.data()); + final_values = final_values_storage.as_span(); + } + + if (idprop) { + BLI_assert(idprop->len == values_num); + if (rna_prop->arraydimension == 0) { if (idprop->type == IDP_FLOAT) { - IDP_Float(idprop) = values[0]; + IDP_Float(idprop) = final_values[0]; } else { - IDP_Double(idprop) = values[0]; + IDP_Double(idprop) = double(final_values[0]); } } else if (idprop->subtype == IDP_FLOAT) { - memcpy(IDP_Array(idprop), values, sizeof(float) * idprop->len); + memcpy(IDP_Array(idprop), + final_values.data(), + sizeof(decltype(final_values)::value_type) * idprop->len); } else { - for (i = 0; i < idprop->len; i++) { - ((double *)IDP_Array(idprop))[i] = values[i]; + double *dst_values = static_cast(IDP_Array(idprop)); + for (int i = 0; i < idprop->len; i++) { + dst_values[i] = double(final_values[i]); } } rna_idproperty_touch(idprop); } - else if (prop->arraydimension == 0) { - RNA_property_float_set(ptr, prop, values[0]); + else if (rna_prop->arraydimension == 0) { + RNA_property_float_set(ptr, rna_prop, final_values[0]); } else if (fprop->setarray) { - fprop->setarray(ptr, values); + fprop->setarray(ptr, final_values.data()); } else if (fprop->setarray_ex) { - fprop->setarray_ex(ptr, prop, values); + fprop->setarray_ex(ptr, rna_prop, final_values.data()); } - else if (prop->flag & PROP_EDITABLE) { + else if (rna_prop->flag & PROP_EDITABLE) { // RNA_property_float_clamp_array(ptr, prop, &value); /* TODO. */ if (IDProperty *group = RNA_struct_system_idprops(ptr, true)) { IDP_AddToGroup(group, - blender::bke::idprop::create(prop->identifier, - blender::Span(values, prop->totarraylength), - IDP_FLAG_STATIC_TYPE) + blender::bke::idprop::create( + prop_rna_or_id.identifier, final_values, IDP_FLAG_STATIC_TYPE) .release()); } } } + void RNA_property_float_set_array_at_most(PointerRNA *ptr, PropertyRNA *prop, const float *values, @@ -3744,67 +3980,80 @@ float RNA_property_float_get_default_index(PointerRNA *ptr, PropertyRNA *prop, i return value; } +static size_t property_string_length_storage(PointerRNA *ptr, PropertyRNAOrID &prop_rna_or_id) +{ + if (prop_rna_or_id.idprop) { + IDProperty *idprop = prop_rna_or_id.idprop; + if (idprop->subtype == IDP_STRING_SUB_BYTE) { + return size_t(idprop->len); + } + /* these _must_ stay in sync */ + if (strlen(IDP_String(idprop)) != idprop->len - 1) { + printf("%zu vs. %d\n", strlen(IDP_String(idprop)), idprop->len - 1); + } + BLI_assert(strlen(IDP_String(idprop)) == idprop->len - 1); + return size_t(idprop->len - 1); + } + + StringPropertyRNA *sprop = reinterpret_cast(prop_rna_or_id.rnaprop); + if (sprop->length) { + return sprop->length(ptr); + } + if (sprop->length_ex) { + return size_t(sprop->length_ex(ptr, &sprop->property)); + } + return strlen(sprop->defaultvalue); +} + +static std::string property_string_get(PointerRNA *ptr, PropertyRNAOrID &prop_rna_or_id) +{ + if (prop_rna_or_id.idprop) { + const size_t length = property_string_length_storage(ptr, prop_rna_or_id); + return std::string{IDP_String(prop_rna_or_id.idprop), length}; + } + StringPropertyRNA *sprop = reinterpret_cast(prop_rna_or_id.rnaprop); + if (sprop->get) { + const size_t length = property_string_length_storage(ptr, prop_rna_or_id); + /* Note: after `resize()` the underlying buffer is actually at least + * `length + 1` bytes long, because (since C++11) `std::string` guarantees + * a terminating null byte, but that is not considered part of the length. */ + std::string string_ret; + string_ret.resize(length); + + sprop->get(ptr, string_ret.data()); + return string_ret; + } + if (sprop->get_ex) { + return sprop->get_ex(ptr, &sprop->property); + } + return sprop->defaultvalue; +} + std::string RNA_property_string_get(PointerRNA *ptr, PropertyRNA *prop) { - StringPropertyRNA *sprop = reinterpret_cast(prop); - IDProperty *idprop; - BLI_assert(RNA_property_type(prop) == PROP_STRING); - const size_t length = size_t(RNA_property_string_length(ptr, prop)); + PropertyRNAOrID prop_rna_or_id; + rna_property_rna_or_id_get(prop, ptr, &prop_rna_or_id); + /* Make initial `prop` pointer invalid, to ensure that it is not used anywhere below. */ + prop = nullptr; + StringPropertyRNA *sprop = reinterpret_cast(prop_rna_or_id.rnaprop); - if ((idprop = rna_idproperty_check(&prop, ptr))) { - /* For normal strings, the length does not contain the null terminator. But for - * #IDP_STRING_SUB_BYTE, it contains the full string including any terminating null. */ - return std::string{IDP_String(idprop), length}; + std::string string_ret = property_string_get(ptr, prop_rna_or_id); + if (sprop->get_transform) { + string_ret = sprop->get_transform(ptr, &sprop->property, string_ret, prop_rna_or_id.is_set); } - if (!sprop->get && !sprop->get_ex) { - return std::string{sprop->defaultvalue}; - } - - std::string string_ret{}; - /* Note: after `resize()` the underlying buffer is actually at least - * `length + 1` bytes long, because (since C++11) `std::string` guarantees - * a terminating null byte, but that is not considered part of the length. */ - string_ret.resize(length); - - if (sprop->get) { - sprop->get(ptr, string_ret.data()); - } - else { /* if (sprop->get_ex) */ - sprop->get_ex(ptr, prop, string_ret.data()); - } + BLI_assert_msg(!sprop->maxlength || string_ret.size() < sprop->maxlength, + "Returned string exceeds the property's max length"); return string_ret; } void RNA_property_string_get(PointerRNA *ptr, PropertyRNA *prop, char *value) { - StringPropertyRNA *sprop = (StringPropertyRNA *)prop; - IDProperty *idprop; + const std::string string_ret = RNA_property_string_get(ptr, prop); - BLI_assert(RNA_property_type(prop) == PROP_STRING); - - if ((idprop = rna_idproperty_check(&prop, ptr))) { - /* editing bytes is not 100% supported - * since they can contain NIL chars */ - if (idprop->subtype == IDP_STRING_SUB_BYTE) { - memcpy(value, IDP_String(idprop), idprop->len); - value[idprop->len] = '\0'; - } - else { - memcpy(value, IDP_String(idprop), idprop->len); - } - } - else if (sprop->get) { - sprop->get(ptr, value); - } - else if (sprop->get_ex) { - sprop->get_ex(ptr, prop, value); - } - else { - strcpy(value, sprop->defaultvalue); - } + memcpy(value, string_ret.c_str(), string_ret.size() + 1); } char *RNA_property_string_get_alloc( @@ -3814,33 +4063,19 @@ char *RNA_property_string_get_alloc( BLI_string_debug_size(fixedbuf, fixedlen); } + const std::string string_ret = RNA_property_string_get(ptr, prop); + char *buf; - int length; - - BLI_assert(RNA_property_type(prop) == PROP_STRING); - - length = RNA_property_string_length(ptr, prop); - - if (length + 1 < fixedlen) { + if (string_ret.size() < fixedlen) { buf = fixedbuf; } else { - buf = MEM_malloc_arrayN(size_t(length) + 1, __func__); + buf = MEM_malloc_arrayN(string_ret.size() + 1, __func__); } -#ifndef NDEBUG - /* safety check to ensure the string is actually set */ - buf[length] = 255; -#endif - - RNA_property_string_get(ptr, prop, buf); - -#ifndef NDEBUG - BLI_assert(buf[length] == '\0'); -#endif - + memcpy(buf, string_ret.c_str(), string_ret.size() + 1); if (r_len) { - *r_len = length; + *r_len = int(string_ret.size()); } return buf; @@ -3848,93 +4083,104 @@ char *RNA_property_string_get_alloc( int RNA_property_string_length(PointerRNA *ptr, PropertyRNA *prop) { - StringPropertyRNA *sprop = (StringPropertyRNA *)prop; - IDProperty *idprop; - BLI_assert(RNA_property_type(prop) == PROP_STRING); + PropertyRNAOrID prop_rna_or_id; + rna_property_rna_or_id_get(prop, ptr, &prop_rna_or_id); + prop = nullptr; + /* NOTE: `prop` is kept unchanged, to allow e.g. call to `RNA_property_string_get` without + * further complications. + * `sprop->property` should be used when access to an actual RNA property is required. + */ + StringPropertyRNA *sprop = reinterpret_cast(prop_rna_or_id.rnaprop); - if ((idprop = rna_idproperty_check(&prop, ptr))) { - if (idprop->subtype == IDP_STRING_SUB_BYTE) { - return idprop->len; - } -#ifndef NDEBUG - /* these _must_ stay in sync */ - BLI_assert(strlen(IDP_String(idprop)) == idprop->len - 1); -#endif - return idprop->len - 1; + /* If there is a `get_transform` callback, no choice but get that final string to find out its + * length. Otherwise, get the 'storage length', whcih is typically more efficient to compute. */ + if (sprop->get_transform) { + std::string string_final = property_string_get(ptr, prop_rna_or_id); + return int(string_final.size()); } - if (sprop->length) { - return sprop->length(ptr); - } - if (sprop->length_ex) { - return sprop->length_ex(ptr, prop); - } - return strlen(sprop->defaultvalue); + return int(property_string_length_storage(ptr, prop_rna_or_id)); } void RNA_property_string_set(PointerRNA *ptr, PropertyRNA *prop, const char *value) { - StringPropertyRNA *sprop = (StringPropertyRNA *)prop; - IDProperty *idprop; - BLI_assert(RNA_property_type(prop) == PROP_STRING); + PropertyRNAOrID prop_rna_or_id; + rna_property_rna_or_id_get(prop, ptr, &prop_rna_or_id); + /* Make initial `prop` pointer invalid, to ensure that it is not used anywhere below. */ + prop = nullptr; + IDProperty *idprop = prop_rna_or_id.idprop; + StringPropertyRNA *sprop = reinterpret_cast(prop_rna_or_id.rnaprop); - if ((idprop = rna_idproperty_check(&prop, ptr))) { + std::string value_set = value; + if (sprop->set_transform) { + /* Get raw, untransformed (aka 'storage') value. */ + const std::string curr_value = property_string_get(ptr, prop_rna_or_id); + value_set = sprop->set_transform( + ptr, &sprop->property, value_set, curr_value, prop_rna_or_id.is_set); + } + + if (idprop) { /* both IDP_STRING_SUB_BYTE / IDP_STRING_SUB_UTF8 */ - IDP_AssignStringMaxSize(idprop, value, RNA_property_string_maxlength(prop)); + IDP_AssignStringMaxSize( + idprop, value_set.c_str(), RNA_property_string_maxlength(&sprop->property)); rna_idproperty_touch(idprop); } else if (sprop->set) { - sprop->set(ptr, value); /* set function needs to clamp itself */ + sprop->set(ptr, value_set.c_str()); /* set function needs to clamp itself */ } else if (sprop->set_ex) { - sprop->set_ex(ptr, prop, value); /* set function needs to clamp itself */ + sprop->set_ex(ptr, &sprop->property, value_set); /* set function needs to clamp itself */ } - else if (prop->flag & PROP_EDITABLE) { - IDProperty *group; - - group = RNA_struct_system_idprops(ptr, true); - if (group) { - IDP_AddToGroup( - group, - IDP_NewStringMaxSize( - value, RNA_property_string_maxlength(prop), prop->identifier, IDP_FLAG_STATIC_TYPE)); + else if (sprop->property.flag & PROP_EDITABLE) { + if (IDProperty *group = RNA_struct_system_idprops(ptr, true)) { + IDP_AddToGroup(group, + IDP_NewStringMaxSize(value_set.c_str(), + RNA_property_string_maxlength(&sprop->property), + prop_rna_or_id.identifier, + IDP_FLAG_STATIC_TYPE)); } } } void RNA_property_string_set_bytes(PointerRNA *ptr, PropertyRNA *prop, const char *value, int len) { - StringPropertyRNA *sprop = (StringPropertyRNA *)prop; - IDProperty *idprop; - BLI_assert(RNA_property_type(prop) == PROP_STRING); BLI_assert(RNA_property_subtype(prop) == PROP_BYTESTRING); + PropertyRNAOrID prop_rna_or_id; + rna_property_rna_or_id_get(prop, ptr, &prop_rna_or_id); + /* Make initial `prop` pointer invalid, to ensure that it is not used anywhere below. */ + prop = nullptr; + IDProperty *idprop = prop_rna_or_id.idprop; + StringPropertyRNA *sprop = reinterpret_cast(prop_rna_or_id.rnaprop); - if ((idprop = rna_idproperty_check(&prop, ptr))) { - IDP_ResizeArray(idprop, len); - memcpy(idprop->data.pointer, value, size_t(len)); + std::string value_set = {value, size_t(len)}; + if (sprop->set_transform) { + /* Get raw, untransformed (aka 'storage') value. */ + const std::string curr_value = property_string_get(ptr, prop_rna_or_id); + value_set = sprop->set_transform( + ptr, &sprop->property, value_set, curr_value, prop_rna_or_id.is_set); + } + if (idprop) { + IDP_ResizeArray(idprop, value_set.size() + 1); + memcpy(idprop->data.pointer, value, value_set.size() + 1); rna_idproperty_touch(idprop); } else if (sprop->set) { - /* XXX, should take length argument (currently not used). */ - sprop->set(ptr, value); /* set function needs to clamp itself */ + sprop->set(ptr, value_set.c_str()); /* set function needs to clamp itself */ } else if (sprop->set_ex) { - /* XXX, should take length argument (currently not used). */ - sprop->set_ex(ptr, prop, value); /* set function needs to clamp itself */ + sprop->set_ex(ptr, &sprop->property, value_set); /* set function needs to clamp itself */ } - else if (prop->flag & PROP_EDITABLE) { - IDProperty *group; - - group = RNA_struct_system_idprops(ptr, true); - if (group) { + else if (sprop->property.flag & PROP_EDITABLE) { + if (IDProperty *group = RNA_struct_system_idprops(ptr, true)) { IDPropertyTemplate val = {0}; - val.string.str = value; - val.string.len = len; + val.string.str = value_set.c_str(); + val.string.len = value_set.size() + 1; val.string.subtype = IDP_STRING_SUB_BYTE; - IDP_AddToGroup(group, IDP_New(IDP_STRING, &val, prop->identifier, IDP_FLAG_STATIC_TYPE)); + IDP_AddToGroup(group, + IDP_New(IDP_STRING, &val, prop_rna_or_id.identifier, IDP_FLAG_STATIC_TYPE)); } } } @@ -4050,33 +4296,59 @@ std::optional RNA_property_string_path_filter(const bContext *C, return sprop->path_filter(C, ptr, rna_prop); } -int RNA_property_enum_get(PointerRNA *ptr, PropertyRNA *prop) +static int property_enum_get(PointerRNA *ptr, PropertyRNAOrID &prop_rna_or_id) { - EnumPropertyRNA *eprop = (EnumPropertyRNA *)prop; - IDProperty *idprop; - - BLI_assert(RNA_property_type(prop) == PROP_ENUM); - - if ((idprop = rna_idproperty_check(&prop, ptr))) { - return IDP_Int(idprop); + if (prop_rna_or_id.idprop) { + return IDP_Int(prop_rna_or_id.idprop); } + EnumPropertyRNA *eprop = reinterpret_cast(prop_rna_or_id.rnaprop); if (eprop->get) { return eprop->get(ptr); } if (eprop->get_ex) { - return eprop->get_ex(ptr, prop); + return eprop->get_ex(ptr, &eprop->property); } return eprop->defaultvalue; } -void RNA_property_enum_set(PointerRNA *ptr, PropertyRNA *prop, int value) +int RNA_property_enum_get(PointerRNA *ptr, PropertyRNA *prop) { - EnumPropertyRNA *eprop = (EnumPropertyRNA *)prop; - IDProperty *idprop; - BLI_assert(RNA_property_type(prop) == PROP_ENUM); - if ((idprop = rna_idproperty_check(&prop, ptr))) { + PropertyRNAOrID prop_rna_or_id; + rna_property_rna_or_id_get(prop, ptr, &prop_rna_or_id); + BLI_assert(!prop_rna_or_id.is_array); + /* Make initial `prop` pointer invalid, to ensure that it is not used anywhere below. */ + prop = nullptr; + EnumPropertyRNA *eprop = reinterpret_cast(prop_rna_or_id.rnaprop); + + int value = property_enum_get(ptr, prop_rna_or_id); + if (eprop->get_transform) { + value = eprop->get_transform(ptr, &eprop->property, value, prop_rna_or_id.is_set); + } + + return value; +} + +void RNA_property_enum_set(PointerRNA *ptr, PropertyRNA *prop, int value) +{ + BLI_assert(RNA_property_type(prop) == PROP_ENUM); + + PropertyRNAOrID prop_rna_or_id; + rna_property_rna_or_id_get(prop, ptr, &prop_rna_or_id); + BLI_assert(!prop_rna_or_id.is_array); + /* Make initial `prop` pointer invalid, to ensure that it is not used anywhere below. */ + prop = nullptr; + IDProperty *idprop = prop_rna_or_id.idprop; + EnumPropertyRNA *eprop = reinterpret_cast(prop_rna_or_id.rnaprop); + + if (eprop->set_transform) { + /* Get raw, untransformed (aka 'storage') value. */ + const int curr_value = property_enum_get(ptr, prop_rna_or_id); + value = eprop->set_transform(ptr, &eprop->property, value, curr_value, prop_rna_or_id.is_set); + } + + if (idprop) { IDP_Int(idprop) = value; rna_idproperty_touch(idprop); } @@ -4084,17 +4356,14 @@ void RNA_property_enum_set(PointerRNA *ptr, PropertyRNA *prop, int value) eprop->set(ptr, value); } else if (eprop->set_ex) { - eprop->set_ex(ptr, prop, value); + eprop->set_ex(ptr, &eprop->property, value); } - else if (prop->flag & PROP_EDITABLE) { - IDPropertyTemplate val = {0}; - IDProperty *group; - - val.i = value; - - group = RNA_struct_system_idprops(ptr, true); - if (group) { - IDP_AddToGroup(group, IDP_New(IDP_INT, &val, prop->identifier, IDP_FLAG_STATIC_TYPE)); + else if (eprop->property.flag & PROP_EDITABLE) { + if (IDProperty *group = RNA_struct_system_idprops(ptr, true)) { + IDP_AddToGroup( + group, + blender::bke::idprop::create(prop_rna_or_id.identifier, value, IDP_FLAG_STATIC_TYPE) + .release()); } } } diff --git a/source/blender/makesrna/intern/rna_define.cc b/source/blender/makesrna/intern/rna_define.cc index 2de7d3998c1..c61400211ea 100644 --- a/source/blender/makesrna/intern/rna_define.cc +++ b/source/blender/makesrna/intern/rna_define.cc @@ -3312,7 +3312,9 @@ void RNA_def_property_boolean_funcs(PropertyRNA *prop, const char *get, const ch void RNA_def_property_boolean_funcs_runtime(PropertyRNA *prop, BooleanPropertyGetFunc getfunc, - BooleanPropertySetFunc setfunc) + BooleanPropertySetFunc setfunc, + BooleanPropertyGetTransformFunc get_transform_fn, + BooleanPropertySetTransformFunc set_transform_fn) { BoolPropertyRNA *bprop = (BoolPropertyRNA *)prop; @@ -3331,11 +3333,21 @@ void RNA_def_property_boolean_funcs_runtime(PropertyRNA *prop, RNA_def_property_clear_flag(prop, PROP_EDITABLE); } } + + if (get_transform_fn) { + bprop->get_transform = get_transform_fn; + } + if (set_transform_fn) { + bprop->set_transform = set_transform_fn; + } } -void RNA_def_property_boolean_array_funcs_runtime(PropertyRNA *prop, - BooleanArrayPropertyGetFunc getfunc, - BooleanArrayPropertySetFunc setfunc) +void RNA_def_property_boolean_array_funcs_runtime( + PropertyRNA *prop, + BooleanArrayPropertyGetFunc getfunc, + BooleanArrayPropertySetFunc setfunc, + BooleanArrayPropertyGetTransformFunc get_transform_fn, + BooleanArrayPropertySetTransformFunc set_transform_fn) { BoolPropertyRNA *bprop = (BoolPropertyRNA *)prop; @@ -3354,6 +3366,13 @@ void RNA_def_property_boolean_array_funcs_runtime(PropertyRNA *prop, RNA_def_property_clear_flag(prop, PROP_EDITABLE); } } + + if (get_transform_fn) { + bprop->getarray_transform = get_transform_fn; + } + if (set_transform_fn) { + bprop->setarray_transform = set_transform_fn; + } } void RNA_def_property_int_funcs(PropertyRNA *prop, @@ -3403,7 +3422,9 @@ void RNA_def_property_int_funcs(PropertyRNA *prop, void RNA_def_property_int_funcs_runtime(PropertyRNA *prop, IntPropertyGetFunc getfunc, IntPropertySetFunc setfunc, - IntPropertyRangeFunc rangefunc) + IntPropertyRangeFunc rangefunc, + IntPropertyGetTransformFunc get_transform_fn, + IntPropertySetTransformFunc set_transform_fn) { IntPropertyRNA *iprop = (IntPropertyRNA *)prop; @@ -3425,12 +3446,21 @@ void RNA_def_property_int_funcs_runtime(PropertyRNA *prop, RNA_def_property_clear_flag(prop, PROP_EDITABLE); } } + + if (get_transform_fn) { + iprop->get_transform = get_transform_fn; + } + if (set_transform_fn) { + iprop->set_transform = set_transform_fn; + } } void RNA_def_property_int_array_funcs_runtime(PropertyRNA *prop, IntArrayPropertyGetFunc getfunc, IntArrayPropertySetFunc setfunc, - IntPropertyRangeFunc rangefunc) + IntPropertyRangeFunc rangefunc, + IntArrayPropertyGetTransformFunc get_transform_fn, + IntArrayPropertySetTransformFunc set_transform_fn) { IntPropertyRNA *iprop = (IntPropertyRNA *)prop; @@ -3452,6 +3482,13 @@ void RNA_def_property_int_array_funcs_runtime(PropertyRNA *prop, RNA_def_property_clear_flag(prop, PROP_EDITABLE); } } + + if (get_transform_fn) { + iprop->getarray_transform = get_transform_fn; + } + if (set_transform_fn) { + iprop->setarray_transform = set_transform_fn; + } } void RNA_def_property_float_funcs(PropertyRNA *prop, @@ -3501,7 +3538,9 @@ void RNA_def_property_float_funcs(PropertyRNA *prop, void RNA_def_property_float_funcs_runtime(PropertyRNA *prop, FloatPropertyGetFunc getfunc, FloatPropertySetFunc setfunc, - FloatPropertyRangeFunc rangefunc) + FloatPropertyRangeFunc rangefunc, + FloatPropertyGetTransformFunc get_transform_fn, + FloatPropertySetTransformFunc set_transform_fn) { FloatPropertyRNA *fprop = (FloatPropertyRNA *)prop; @@ -3523,12 +3562,22 @@ void RNA_def_property_float_funcs_runtime(PropertyRNA *prop, RNA_def_property_clear_flag(prop, PROP_EDITABLE); } } + + if (get_transform_fn) { + fprop->get_transform = get_transform_fn; + } + if (set_transform_fn) { + fprop->set_transform = set_transform_fn; + } } -void RNA_def_property_float_array_funcs_runtime(PropertyRNA *prop, - FloatArrayPropertyGetFunc getfunc, - FloatArrayPropertySetFunc setfunc, - FloatPropertyRangeFunc rangefunc) +void RNA_def_property_float_array_funcs_runtime( + PropertyRNA *prop, + FloatArrayPropertyGetFunc getfunc, + FloatArrayPropertySetFunc setfunc, + FloatPropertyRangeFunc rangefunc, + FloatArrayPropertyGetTransformFunc get_transform_fn, + FloatArrayPropertySetTransformFunc set_transform_fn) { FloatPropertyRNA *fprop = (FloatPropertyRNA *)prop; @@ -3550,6 +3599,13 @@ void RNA_def_property_float_array_funcs_runtime(PropertyRNA *prop, RNA_def_property_clear_flag(prop, PROP_EDITABLE); } } + + if (get_transform_fn) { + fprop->getarray_transform = get_transform_fn; + } + if (set_transform_fn) { + fprop->setarray_transform = set_transform_fn; + } } void RNA_def_property_enum_funcs(PropertyRNA *prop, @@ -3589,7 +3645,9 @@ void RNA_def_property_enum_funcs(PropertyRNA *prop, void RNA_def_property_enum_funcs_runtime(PropertyRNA *prop, EnumPropertyGetFunc getfunc, EnumPropertySetFunc setfunc, - EnumPropertyItemFunc itemfunc) + EnumPropertyItemFunc itemfunc, + EnumPropertyGetTransformFunc get_transform_fn, + EnumPropertySetTransformFunc set_transform_fn) { EnumPropertyRNA *eprop = (EnumPropertyRNA *)prop; @@ -3611,6 +3669,13 @@ void RNA_def_property_enum_funcs_runtime(PropertyRNA *prop, RNA_def_property_clear_flag(prop, PROP_EDITABLE); } } + + if (get_transform_fn) { + eprop->get_transform = get_transform_fn; + } + if (set_transform_fn) { + eprop->set_transform = set_transform_fn; + } } void RNA_def_property_string_funcs(PropertyRNA *prop, @@ -3699,7 +3764,9 @@ void RNA_def_property_string_filepath_filter_func(PropertyRNA *prop, const char void RNA_def_property_string_funcs_runtime(PropertyRNA *prop, StringPropertyGetFunc getfunc, StringPropertyLengthFunc lengthfunc, - StringPropertySetFunc setfunc) + StringPropertySetFunc setfunc, + StringPropertyGetTransformFunc get_transform_fn, + StringPropertySetTransformFunc set_transform_fn) { StringPropertyRNA *sprop = (StringPropertyRNA *)prop; @@ -3721,6 +3788,13 @@ void RNA_def_property_string_funcs_runtime(PropertyRNA *prop, RNA_def_property_clear_flag(prop, PROP_EDITABLE); } } + + if (get_transform_fn) { + sprop->get_transform = get_transform_fn; + } + if (set_transform_fn) { + sprop->set_transform = set_transform_fn; + } } void RNA_def_property_string_search_func_runtime(PropertyRNA *prop, diff --git a/source/blender/makesrna/intern/rna_internal_types.hh b/source/blender/makesrna/intern/rna_internal_types.hh index d07f91cd03f..af0c0eab0c6 100644 --- a/source/blender/makesrna/intern/rna_internal_types.hh +++ b/source/blender/makesrna/intern/rna_internal_types.hh @@ -103,6 +103,9 @@ using PropCollectionAssignIntFunc = bool (*)(PointerRNA *ptr, const PointerRNA *assign_ptr); /* Extended versions with #PropertyRNA argument. */ +/* NOTE: All extended get/set callbacks will always get a 'real' PropertyRNA `prop` pointer, never + * an 'IDProperty as PropertyRNA' one (i.e. when called, the given `prop` is the RNA result of a + * call to `rna_property_rna_or_id_get` or one of its wrappers). */ using PropBooleanGetFuncEx = bool (*)(PointerRNA *ptr, PropertyRNA *prop); using PropBooleanSetFuncEx = void (*)(PointerRNA *ptr, PropertyRNA *prop, bool value); @@ -120,12 +123,35 @@ using PropFloatArrayGetFuncEx = void (*)(PointerRNA *ptr, PropertyRNA *prop, flo using PropFloatArraySetFuncEx = void (*)(PointerRNA *ptr, PropertyRNA *prop, const float *values); using PropFloatRangeFuncEx = void (*)( PointerRNA *ptr, PropertyRNA *prop, float *min, float *max, float *softmin, float *softmax); -using PropStringGetFuncEx = void (*)(PointerRNA *ptr, PropertyRNA *prop, char *value); +using PropStringGetFuncEx = std::string (*)(PointerRNA *ptr, PropertyRNA *prop); using PropStringLengthFuncEx = int (*)(PointerRNA *ptr, PropertyRNA *prop); -using PropStringSetFuncEx = void (*)(PointerRNA *ptr, PropertyRNA *prop, const char *value); +using PropStringSetFuncEx = void (*)(PointerRNA *ptr, PropertyRNA *prop, const std::string &value); using PropEnumGetFuncEx = int (*)(PointerRNA *ptr, PropertyRNA *prop); using PropEnumSetFuncEx = void (*)(PointerRNA *ptr, PropertyRNA *prop, int value); +/* Transform step (applied after getting, or before setting the value). Currently only used by + * `bpy`, more details in the documentation of #BPyPropStore. */ +/* NOTE: All transform get/set callbacks will always get a 'real' PropertyRNA `prop` pointer, never + * an 'IDProperty as PropertyRNA' one (i.e. when called, the given `prop` is the RNA result of a + * call to `rna_property_rna_or_id_get` or one of its wrappers). */ + +using PropBooleanGetTransformFunc = BooleanPropertyGetTransformFunc; +using PropBooleanSetTransformFunc = BooleanPropertySetTransformFunc; +using PropBooleanArrayGetTransformFunc = BooleanArrayPropertyGetTransformFunc; +using PropBooleanArraySetTransformFunc = BooleanArrayPropertySetTransformFunc; +using PropIntGetTransformFunc = IntPropertyGetTransformFunc; +using PropIntSetTransformFunc = IntPropertySetTransformFunc; +using PropIntArrayGetTransformFunc = IntArrayPropertyGetTransformFunc; +using PropIntArraySetTransformFunc = IntArrayPropertySetTransformFunc; +using PropFloatGetTransformFunc = FloatPropertyGetTransformFunc; +using PropFloatSetTransformFunc = FloatPropertySetTransformFunc; +using PropFloatArrayGetTransformFunc = FloatArrayPropertyGetTransformFunc; +using PropFloatArraySetTransformFunc = FloatArrayPropertySetTransformFunc; +using PropStringGetTransformFunc = StringPropertyGetTransformFunc; +using PropStringSetTransformFunc = StringPropertySetTransformFunc; +using PropEnumGetTransformFunc = EnumPropertyGetTransformFunc; +using PropEnumSetTransformFunc = EnumPropertySetTransformFunc; + /* Handling override operations, and also comparison. */ /** Structure storing all needed data to process all three kinds of RNA properties. */ @@ -463,6 +489,11 @@ struct BoolPropertyRNA { PropBooleanArrayGetFuncEx getarray_ex; PropBooleanArraySetFuncEx setarray_ex; + PropBooleanGetTransformFunc get_transform; + PropBooleanSetTransformFunc set_transform; + PropBooleanArrayGetTransformFunc getarray_transform; + PropBooleanArraySetTransformFunc setarray_transform; + PropBooleanGetFuncEx get_default; PropBooleanArrayGetFuncEx get_default_array; bool defaultvalue; @@ -484,6 +515,11 @@ struct IntPropertyRNA { PropIntArraySetFuncEx setarray_ex; PropIntRangeFuncEx range_ex; + PropIntGetTransformFunc get_transform; + PropIntSetTransformFunc set_transform; + PropIntArrayGetTransformFunc getarray_transform; + PropIntArraySetTransformFunc setarray_transform; + PropertyScaleType ui_scale_type; int softmin, softmax; int hardmin, hardmax; @@ -510,6 +546,11 @@ struct FloatPropertyRNA { PropFloatArraySetFuncEx setarray_ex; PropFloatRangeFuncEx range_ex; + PropFloatGetTransformFunc get_transform; + PropFloatSetTransformFunc set_transform; + PropFloatArrayGetTransformFunc getarray_transform; + PropFloatArraySetTransformFunc setarray_transform; + PropertyScaleType ui_scale_type; float softmin, softmax; float hardmin, hardmax; @@ -531,9 +572,14 @@ struct StringPropertyRNA { PropStringSetFunc set; PropStringGetFuncEx get_ex; + /* This callback only returns the 'storage' length (i.e. length of string returned by `get_ex`), + * _not_ the final length (potentially modified by the `get_transform` callback). */ PropStringLengthFuncEx length_ex; PropStringSetFuncEx set_ex; + PropStringGetTransformFunc get_transform; + PropStringSetTransformFunc set_transform; + /** * Optional callback to list candidates for a string. * This is only for use as suggestions in UI, other values may be assigned. @@ -565,6 +611,9 @@ struct EnumPropertyRNA { PropEnumGetFuncEx get_ex; PropEnumSetFuncEx set_ex; + PropEnumGetTransformFunc get_transform; + PropEnumSetTransformFunc set_transform; + PropEnumGetFuncEx get_default; const EnumPropertyItem *item; diff --git a/source/blender/makesrna/intern/rna_rna.cc b/source/blender/makesrna/intern/rna_rna.cc index af8c64c99c5..33794100c8f 100644 --- a/source/blender/makesrna/intern/rna_rna.cc +++ b/source/blender/makesrna/intern/rna_rna.cc @@ -159,6 +159,7 @@ const EnumPropertyItem rna_enum_property_unit_items[] = { }; /* Descriptions for rna_enum_property_flag_items and rna_enum_property_flag_enum_items. */ +static constexpr auto PROP_READ_ONLY_DESCR = "When set, the property cannot be edited"; static constexpr auto PROP_HIDDEN_DESCR = "For operators: hide from places in the user interface where Blender would add the property " "automatically, like Adjust Last Operation. Also this property is not written to presets."; @@ -183,6 +184,19 @@ static constexpr auto PROP_PATH_SUPPORTS_TEMPLATES_DESCR = static constexpr auto PROP_ENUM_FLAG_DESCR = ""; const EnumPropertyItem rna_enum_property_flag_items[] = { + /* NOTE: This is used only in the `bpy.props` module to define runtime RNA properties. + * The value of this 'READ_ONLY' enum item is logically inverted compared to the + * `PROP_EDITABLE` used everywhere else in RNA-related code. + * + * This inversion logic is handled by the bpy property definition code (see + * #bpy_prop_assign_flag), and does not affect any other code area. + * + * This special handling is done to allow python property definitions to create by default + * editable properties, without having to specify the `options={'EDITABLE', ...}` parameter all + * the time. + */ + {PROP_EDITABLE, "READ_ONLY", 0, "Read Only", PROP_READ_ONLY_DESCR}, + {PROP_HIDDEN, "HIDDEN", 0, "Hidden", PROP_HIDDEN_DESCR}, {PROP_SKIP_SAVE, "SKIP_SAVE", 0, "Skip Save", PROP_SKIP_SAVE_DESCR}, {PROP_SKIP_PRESET, "SKIP_PRESET", 0, "Skip Preset", PROP_SKIP_PRESET_DESCR}, @@ -214,6 +228,19 @@ const EnumPropertyItem rna_enum_property_flag_items[] = { /** Only for enum type properties. */ const EnumPropertyItem rna_enum_property_flag_enum_items[] = { + /* NOTE: This is used only in the `bpy.props` module to define runtime RNA properties. + * The value of this 'READ_ONLY' enum item is logically inverted compared to the + * `PROP_EDITABLE` used everywhere else in RNA-related code. + * + * This inversion logic is handled by the bpy property definition code (see + * #bpy_prop_assign_flag), and does not affect any other code area. + * + * This special handling is done to allow python property definitions to create by default + * editable properties, without having to specify the `options={'EDITABLE', ...}` parameter all + * the time. + */ + {PROP_EDITABLE, "READ_ONLY", 0, "Read Only", PROP_READ_ONLY_DESCR}, + {PROP_HIDDEN, "HIDDEN", 0, "Hidden", PROP_HIDDEN_DESCR}, {PROP_SKIP_SAVE, "SKIP_SAVE", 0, "Skip Save", PROP_SKIP_SAVE_DESCR}, {PROP_ANIMATABLE, "ANIMATABLE", 0, "Animatable", PROP_ANIMATABLE_DESCR}, diff --git a/source/blender/nodes/geometry/nodes/node_geo_viewer.cc b/source/blender/nodes/geometry/nodes/node_geo_viewer.cc index a7013de4776..0a07f884bc2 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_viewer.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_viewer.cc @@ -141,8 +141,12 @@ static void node_rna(StructRNA *srna) PropertyRNA *prop; prop = RNA_def_property(srna, "ui_shortcut", PROP_INT, PROP_NONE); - RNA_def_property_int_funcs_runtime( - prop, rna_Node_Viewer_shortcut_node_get, rna_Node_Viewer_shortcut_node_set, nullptr); + RNA_def_property_int_funcs_runtime(prop, + rna_Node_Viewer_shortcut_node_get, + rna_Node_Viewer_shortcut_node_set, + nullptr, + nullptr, + nullptr); RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); RNA_def_property_override_flag(prop, PROPOVERRIDE_IGNORE); RNA_def_property_int_default(prop, NODE_VIEWER_SHORTCUT_NONE); diff --git a/source/blender/nodes/intern/node_rna_define.cc b/source/blender/nodes/intern/node_rna_define.cc index 14c919ce117..5f04e93c390 100644 --- a/source/blender/nodes/intern/node_rna_define.cc +++ b/source/blender/nodes/intern/node_rna_define.cc @@ -33,7 +33,8 @@ PropertyRNA *RNA_def_node_enum(StructRNA *srna, const bool allow_animation) { PropertyRNA *prop = RNA_def_property(srna, identifier, PROP_ENUM, PROP_NONE); - RNA_def_property_enum_funcs_runtime(prop, accessors.getter, accessors.setter, item_func); + RNA_def_property_enum_funcs_runtime( + prop, accessors.getter, accessors.setter, item_func, nullptr, nullptr); RNA_def_property_enum_items(prop, static_items); if (default_value.has_value()) { RNA_def_property_enum_default(prop, *default_value); @@ -59,7 +60,8 @@ PropertyRNA *RNA_def_node_boolean(StructRNA *srna, bool allow_animation) { PropertyRNA *prop = RNA_def_property(srna, identifier, PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_funcs_runtime(prop, accessors.getter, accessors.setter); + RNA_def_property_boolean_funcs_runtime( + prop, accessors.getter, accessors.setter, nullptr, nullptr); if (default_value.has_value()) { RNA_def_property_boolean_default(prop, *default_value); } diff --git a/source/blender/python/intern/bpy_props.cc b/source/blender/python/intern/bpy_props.cc index 4c7664e9fcc..abdd2cd7734 100644 --- a/source/blender/python/intern/bpy_props.cc +++ b/source/blender/python/intern/bpy_props.cc @@ -14,6 +14,7 @@ #define PY_SSIZE_T_CLEAN #include +#include #include @@ -128,9 +129,29 @@ struct BPyPropStore { * nullptr members are skipped. */ struct { - /** Wrap: `RNA_def_property_*_funcs` (depending on type). */ + /** + * Wrap: `RNA_def_property_*_funcs` (depending on type). + * + * - `get`/`set` are used to provide access to a non-standard storage for the value (i.e. not + * in the default 'system-defined' IDProperties storage). + * - Their fallback implementation uses the system IDProperties storage system. + * - If `get` is specified, but not `set`, the property is considered read-only. + * - If `set` is specified, but not `get`, this is an error. + * - `get_transform`/`set_transform` are used to perform some additional transformation of the + * data, after `get` is called / before `set` is called (or their matching default get/set + * implementations). + * - Their fallback implementation is 'pass-through'. + * + * Conceptually, the flow of callings is: + * - getter: + * `return get_transform(self, get(self), is_property_set(self, "prop"))` + * - setter: + * `set(self, set_transform(self, new_value, get(self), is_property_set(self, "prop")))` + */ PyObject *get_fn; PyObject *set_fn; + PyObject *get_transform_fn; + PyObject *set_transform_fn; /** Wrap: #RNA_def_property_update_runtime */ PyObject *update_fn; @@ -396,16 +417,21 @@ static PyObject *pyrna_struct_as_instance(PointerRNA *ptr) return self; } -static void bpy_prop_assign_flag(PropertyRNA *prop, const int flag) +static void bpy_prop_assign_flag(PropertyRNA *prop, int flag) { - const int flag_mask = ((PROP_ANIMATABLE) & ~flag); + /* Map `READ_ONLY` to `EDITABLE`. */ + flag ^= PROP_EDITABLE; - if (flag) { - RNA_def_property_flag(prop, PropertyFlag(flag)); + /* The default is editable. */ + const int flag_mask_set = (flag & ~PROP_EDITABLE); + const int flag_mask_clear = ((PROP_ANIMATABLE | PROP_EDITABLE) & ~flag); + + if (flag_mask_set) { + RNA_def_property_flag(prop, PropertyFlag(flag_mask_set)); } - if (flag_mask) { - RNA_def_property_clear_flag(prop, PropertyFlag(flag_mask)); + if (flag_mask_clear) { + RNA_def_property_clear_flag(prop, PropertyFlag(flag_mask_clear)); } } @@ -448,10 +474,30 @@ static void bpy_prop_gil_rna_writable_end(const BPyPropGIL_RNAWritable_State &pr * \{ */ struct BPyPropArrayLength { - int len_total; + int len_total = 0; /** Ignore `dims` when `dims_len == 0`. */ - int dims[RNA_MAX_ARRAY_DIMENSION]; - int dims_len; + int dims[RNA_MAX_ARRAY_DIMENSION] = {}; + int dims_len = 0; + + BPyPropArrayLength() = default; + BPyPropArrayLength(PointerRNA *ptr, PropertyRNA *prop) + { + this->len_total = RNA_property_array_length(ptr, prop); + this->dims_len = RNA_property_array_dimension(ptr, prop, this->dims); + } + + bool operator==(const BPyPropArrayLength &other) const + { + if ((this->len_total != other.len_total) || (this->dims_len != other.dims_len)) { + return false; + } + for (int i = 0; i < this->dims_len; i++) { + if (this->dims[i] != other.dims[i]) { + return false; + } + } + return true; + } }; /** @@ -541,6 +587,51 @@ static int bpy_prop_array_from_py_with_dims(void *values, return PyC_AsArray_Multi(values, values_elem_size, py_values, dims, dims_len, type, error_str); } +/* NOTE: Always increases refcount of the returned value. */ +static PyObject *bpy_py_object_from_prop_array_with_dims(const void *values, + const BPyPropArrayLength &array_len_info, + const PyTypeObject &type) +{ + PyObject *py_values = nullptr; + + if (&type == &PyBool_Type) { + if (array_len_info.dims_len == 0) { + py_values = PyC_Tuple_PackArray_Bool(static_cast(values), + uint(array_len_info.len_total)); + } + else { + py_values = PyC_Tuple_PackArray_Multi_Bool( + static_cast(values), array_len_info.dims, array_len_info.dims_len); + } + } + else if (&type == &PyLong_Type) { + if (array_len_info.dims_len == 0) { + py_values = PyC_Tuple_PackArray_I32(static_cast(values), + uint(array_len_info.len_total)); + } + else { + py_values = PyC_Tuple_PackArray_Multi_I32( + static_cast(values), array_len_info.dims, array_len_info.dims_len); + } + } + else if (&type == &PyFloat_Type) { + if (array_len_info.dims_len == 0) { + py_values = PyC_Tuple_PackArray_F32(static_cast(values), + uint(array_len_info.len_total)); + } + else { + /* No need for matrix column/row swapping here unless the matrix data is read directly. */ + py_values = PyC_Tuple_PackArray_Multi_F32( + static_cast(values), array_len_info.dims, array_len_info.dims_len); + } + } + else { + BLI_assert_unreachable(); + } + + return py_values; +} + static bool bpy_prop_array_is_matrix_compatible_ex(int subtype, const BPyPropArrayLength *array_len_info) { @@ -691,6 +782,53 @@ static bool bpy_prop_boolean_get_fn(PointerRNA *ptr, PropertyRNA *prop) return value; } +static bool bpy_prop_boolean_get_transform_fn(PointerRNA *ptr, + PropertyRNA *prop, + bool curr_value, + bool is_set) +{ + const BPyPropGIL_RNAWritable_State bpy_state = bpy_prop_gil_rna_writable_begin(); + + BPyPropStore *prop_store = static_cast(RNA_property_py_data_get(prop)); + PyObject *py_func; + PyObject *ret; + + BLI_assert(prop_store != nullptr); + + py_func = prop_store->py_data.get_transform_fn; + + { + PyObject *args = PyTuple_New(3); + PyObject *self = pyrna_struct_as_instance(ptr); + PyTuple_SET_ITEMS(args, self, PyBool_FromLong(curr_value), PyBool_FromLong(is_set)); + + ret = PyObject_CallObject(py_func, args); + + Py_DECREF(args); + } + + bool ret_value = curr_value; + if (ret == nullptr) { + PyC_Err_PrintWithFunc(py_func); + } + else { + const int value_i = PyC_Long_AsBool(ret); + + if (value_i == -1 && PyErr_Occurred()) { + PyC_Err_PrintWithFunc(py_func); + } + else { + ret_value = bool(value_i); + } + + Py_DECREF(ret); + } + + bpy_prop_gil_rna_writable_end(bpy_state); + + return ret_value; +} + static void bpy_prop_boolean_set_fn(PointerRNA *ptr, PropertyRNA *prop, bool value) { const BPyPropGIL_RNAWritable_State bpy_state = bpy_prop_gil_rna_writable_begin(); @@ -728,7 +866,8 @@ static void bpy_prop_boolean_set_fn(PointerRNA *ptr, PropertyRNA *prop, bool val bpy_prop_gil_rna_writable_end(bpy_state); } -static void bpy_prop_boolean_array_get_fn(PointerRNA *ptr, PropertyRNA *prop, bool *values) +static bool bpy_prop_boolean_set_transform_fn( + PointerRNA *ptr, PropertyRNA *prop, bool new_value, bool curr_value, bool is_set) { const BPyPropGIL_RNAWritable_State bpy_state = bpy_prop_gil_rna_writable_begin(); @@ -736,11 +875,86 @@ static void bpy_prop_boolean_array_get_fn(PointerRNA *ptr, PropertyRNA *prop, bo PyObject *py_func; PyObject *ret; + BLI_assert(prop_store != nullptr); + + py_func = prop_store->py_data.set_transform_fn; + + { + PyObject *args = PyTuple_New(4); + PyObject *self = pyrna_struct_as_instance(ptr); + PyTuple_SET_ITEMS(args, + self, + PyBool_FromLong(new_value), + PyBool_FromLong(curr_value), + PyBool_FromLong(is_set)); + + ret = PyObject_CallObject(py_func, args); + + Py_DECREF(args); + } + + bool ret_value = curr_value; + if (ret == nullptr) { + PyC_Err_PrintWithFunc(py_func); + } + else { + const int value_i = PyC_Long_AsBool(ret); + + if (value_i == -1 && PyErr_Occurred()) { + PyC_Err_PrintWithFunc(py_func); + } + else { + ret_value = bool(value_i); + } + + Py_DECREF(ret); + } + + bpy_prop_gil_rna_writable_end(bpy_state); + + return ret_value; +} + +static void bpy_prop_boolean_array_from_callback_or_error(PyObject *bool_array_obj, + const BPyPropArrayLength &array_len_info, + PyObject *py_func, + bool *r_values) +{ bool is_values_set = false; - int i, len = RNA_property_array_length(ptr, prop); - BPyPropArrayLength array_len_info{}; - array_len_info.len_total = len; - array_len_info.dims_len = RNA_property_array_dimension(ptr, prop, array_len_info.dims); + + if (bool_array_obj != nullptr) { + if (bpy_prop_array_from_py_with_dims(r_values, + sizeof(*r_values), + bool_array_obj, + &array_len_info, + &PyBool_Type, + "BoolVectorProperty get callback") == -1) + { + PyC_Err_PrintWithFunc(py_func); + } + else { + is_values_set = true; + } + } + + if (is_values_set == false) { + /* This is the flattened length for multi-dimensional arrays. */ + for (int i = 0; i < array_len_info.len_total; i++) { + r_values[i] = false; + } + } + + Py_XDECREF(bool_array_obj); +} + +static void bpy_prop_boolean_array_get_fn(PointerRNA *ptr, PropertyRNA *prop, bool *values) +{ + const BPyPropGIL_RNAWritable_State bpy_state = bpy_prop_gil_rna_writable_begin(); + + const BPyPropArrayLength array_len_info{ptr, prop}; + BPyPropStore *prop_store = static_cast(RNA_property_py_data_get(prop)); + PyObject *py_func; + PyObject *ret; BLI_assert(prop_store != nullptr); @@ -756,28 +970,40 @@ static void bpy_prop_boolean_array_get_fn(PointerRNA *ptr, PropertyRNA *prop, bo Py_DECREF(args); } - if (ret != nullptr) { - if (bpy_prop_array_from_py_with_dims(values, - sizeof(*values), - ret, - &array_len_info, - &PyBool_Type, - "BoolVectorProperty get callback") == -1) - { - PyC_Err_PrintWithFunc(py_func); - } - else { - is_values_set = true; - } - Py_DECREF(ret); + bpy_prop_boolean_array_from_callback_or_error(ret, array_len_info, py_func, values); + + bpy_prop_gil_rna_writable_end(bpy_state); +} + +static void bpy_prop_boolean_array_get_transform_fn( + PointerRNA *ptr, PropertyRNA *prop, const bool *curr_values, bool is_set, bool *r_values) +{ + const BPyPropGIL_RNAWritable_State bpy_state = bpy_prop_gil_rna_writable_begin(); + + const BPyPropArrayLength array_len_info{ptr, prop}; + BPyPropStore *prop_store = static_cast(RNA_property_py_data_get(prop)); + PyObject *py_func; + PyObject *ret; + + BLI_assert(prop_store != nullptr); + + py_func = prop_store->py_data.get_transform_fn; + + { + PyObject *args = PyTuple_New(3); + PyObject *self = pyrna_struct_as_instance(ptr); + PyTuple_SET_ITEMS( + args, + self, + bpy_py_object_from_prop_array_with_dims(curr_values, array_len_info, PyBool_Type), + PyBool_FromLong(is_set)); + + ret = PyObject_CallObject(py_func, args); + + Py_DECREF(args); } - if (is_values_set == false) { - /* This is the flattened length for multi-dimensional arrays. */ - for (i = 0; i < len; i++) { - values[i] = false; - } - } + bpy_prop_boolean_array_from_callback_or_error(ret, array_len_info, py_func, r_values); bpy_prop_gil_rna_writable_end(bpy_state); } @@ -790,10 +1016,7 @@ static void bpy_prop_boolean_array_set_fn(PointerRNA *ptr, PropertyRNA *prop, co PyObject *py_func; PyObject *ret; - const int len = RNA_property_array_length(ptr, prop); - BPyPropArrayLength array_len_info{}; - array_len_info.len_total = len; - array_len_info.dims_len = RNA_property_array_dimension(ptr, prop, array_len_info.dims); + const BPyPropArrayLength array_len_info{ptr, prop}; BLI_assert(prop_store != nullptr); @@ -802,16 +1025,8 @@ static void bpy_prop_boolean_array_set_fn(PointerRNA *ptr, PropertyRNA *prop, co { PyObject *args = PyTuple_New(2); PyObject *self = pyrna_struct_as_instance(ptr); - - PyObject *py_values; - if (array_len_info.dims_len == 0) { - py_values = PyC_Tuple_PackArray_Bool(values, len); - } - else { - py_values = PyC_Tuple_PackArray_Multi_Bool( - values, array_len_info.dims, array_len_info.dims_len); - } - PyTuple_SET_ITEMS(args, self, py_values); + PyTuple_SET_ITEMS( + args, self, bpy_py_object_from_prop_array_with_dims(values, array_len_info, PyBool_Type)); ret = PyObject_CallObject(py_func, args); @@ -833,6 +1048,45 @@ static void bpy_prop_boolean_array_set_fn(PointerRNA *ptr, PropertyRNA *prop, co bpy_prop_gil_rna_writable_end(bpy_state); } +static void bpy_prop_boolean_array_set_transform_fn(PointerRNA *ptr, + PropertyRNA *prop, + const bool *new_values, + const bool *curr_values, + bool is_set, + bool *r_final_values) +{ + const BPyPropGIL_RNAWritable_State bpy_state = bpy_prop_gil_rna_writable_begin(); + + BPyPropStore *prop_store = static_cast(RNA_property_py_data_get(prop)); + PyObject *py_func; + PyObject *ret; + + const BPyPropArrayLength array_len_info{ptr, prop}; + + BLI_assert(prop_store != nullptr); + + py_func = prop_store->py_data.set_transform_fn; + + { + PyObject *args = PyTuple_New(4); + PyObject *self = pyrna_struct_as_instance(ptr); + PyTuple_SET_ITEMS( + args, + self, + bpy_py_object_from_prop_array_with_dims(new_values, array_len_info, PyBool_Type), + bpy_py_object_from_prop_array_with_dims(curr_values, array_len_info, PyBool_Type), + PyBool_FromLong(is_set)); + + ret = PyObject_CallObject(py_func, args); + + Py_DECREF(args); + } + + bpy_prop_boolean_array_from_callback_or_error(ret, array_len_info, py_func, r_final_values); + + bpy_prop_gil_rna_writable_end(bpy_state); +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -846,7 +1100,6 @@ static int bpy_prop_int_get_fn(PointerRNA *ptr, PropertyRNA *prop) BPyPropStore *prop_store = static_cast(RNA_property_py_data_get(prop)); PyObject *py_func; PyObject *ret; - int value; BLI_assert(prop_store != nullptr); @@ -865,7 +1118,7 @@ static int bpy_prop_int_get_fn(PointerRNA *ptr, PropertyRNA *prop) if (ret == nullptr) { PyC_Err_PrintWithFunc(py_func); - value = 0.0f; + value = 0; } else { value = PyC_Long_AsI32(ret); @@ -883,6 +1136,51 @@ static int bpy_prop_int_get_fn(PointerRNA *ptr, PropertyRNA *prop) return value; } +static int bpy_prop_int_get_transform_fn(PointerRNA *ptr, + PropertyRNA *prop, + int curr_value, + bool is_set) +{ + const BPyPropGIL_RNAWritable_State bpy_state = bpy_prop_gil_rna_writable_begin(); + + BPyPropStore *prop_store = static_cast(RNA_property_py_data_get(prop)); + PyObject *py_func; + PyObject *ret; + + BLI_assert(prop_store != nullptr); + + py_func = prop_store->py_data.get_transform_fn; + + { + PyObject *args = PyTuple_New(3); + PyObject *self = pyrna_struct_as_instance(ptr); + PyTuple_SET_ITEMS(args, self, PyLong_FromLong(curr_value), PyBool_FromLong(is_set)); + + ret = PyObject_CallObject(py_func, args); + + Py_DECREF(args); + } + + int ret_value = curr_value; + if (ret == nullptr) { + PyC_Err_PrintWithFunc(py_func); + } + else { + ret_value = PyC_Long_AsI32(ret); + + if (ret_value == -1 && PyErr_Occurred()) { + PyC_Err_PrintWithFunc(py_func); + ret_value = curr_value; + } + + Py_DECREF(ret); + } + + bpy_prop_gil_rna_writable_end(bpy_state); + + return ret_value; +} + static void bpy_prop_int_set_fn(PointerRNA *ptr, PropertyRNA *prop, int value) { const BPyPropGIL_RNAWritable_State bpy_state = bpy_prop_gil_rna_writable_begin(); @@ -920,7 +1218,8 @@ static void bpy_prop_int_set_fn(PointerRNA *ptr, PropertyRNA *prop, int value) bpy_prop_gil_rna_writable_end(bpy_state); } -static void bpy_prop_int_array_get_fn(PointerRNA *ptr, PropertyRNA *prop, int *values) +static int bpy_prop_int_set_transform_fn( + PointerRNA *ptr, PropertyRNA *prop, int new_value, int curr_value, bool is_set) { const BPyPropGIL_RNAWritable_State bpy_state = bpy_prop_gil_rna_writable_begin(); @@ -928,11 +1227,84 @@ static void bpy_prop_int_array_get_fn(PointerRNA *ptr, PropertyRNA *prop, int *v PyObject *py_func; PyObject *ret; + BLI_assert(prop_store != nullptr); + + py_func = prop_store->py_data.set_transform_fn; + + { + PyObject *args = PyTuple_New(4); + PyObject *self = pyrna_struct_as_instance(ptr); + PyTuple_SET_ITEMS(args, + self, + PyLong_FromLong(new_value), + PyLong_FromLong(curr_value), + PyBool_FromLong(is_set)); + + ret = PyObject_CallObject(py_func, args); + + Py_DECREF(args); + } + + int ret_value = curr_value; + if (ret == nullptr) { + PyC_Err_PrintWithFunc(py_func); + } + else { + ret_value = PyC_Long_AsI32(ret); + + if (ret_value == -1 && PyErr_Occurred()) { + PyC_Err_PrintWithFunc(py_func); + ret_value = curr_value; + } + + Py_DECREF(ret); + } + + bpy_prop_gil_rna_writable_end(bpy_state); + + return ret_value; +} + +static void bpy_prop_int_array_from_callback_or_error(PyObject *int_array_obj, + const BPyPropArrayLength &array_len_info, + PyObject *py_func, + int *r_values) +{ bool is_values_set = false; - int i, len = RNA_property_array_length(ptr, prop); - BPyPropArrayLength array_len_info{}; - array_len_info.len_total = len; - array_len_info.dims_len = RNA_property_array_dimension(ptr, prop, array_len_info.dims); + + if (int_array_obj != nullptr) { + if (bpy_prop_array_from_py_with_dims(r_values, + sizeof(*r_values), + int_array_obj, + &array_len_info, + &PyLong_Type, + "IntVectorProperty get callback") == -1) + { + PyC_Err_PrintWithFunc(py_func); + } + else { + is_values_set = true; + } + } + + if (is_values_set == false) { + /* This is the flattened length for multi-dimensional arrays. */ + for (int i = 0; i < array_len_info.len_total; i++) { + r_values[i] = 0; + } + } + + Py_XDECREF(int_array_obj); +} + +static void bpy_prop_int_array_get_fn(PointerRNA *ptr, PropertyRNA *prop, int *values) +{ + const BPyPropGIL_RNAWritable_State bpy_state = bpy_prop_gil_rna_writable_begin(); + + const BPyPropArrayLength array_len_info{ptr, prop}; + BPyPropStore *prop_store = static_cast(RNA_property_py_data_get(prop)); + PyObject *py_func; + PyObject *ret; BLI_assert(prop_store != nullptr); @@ -948,28 +1320,40 @@ static void bpy_prop_int_array_get_fn(PointerRNA *ptr, PropertyRNA *prop, int *v Py_DECREF(args); } - if (ret != nullptr) { - if (bpy_prop_array_from_py_with_dims(values, - sizeof(*values), - ret, - &array_len_info, - &PyLong_Type, - "IntVectorProperty get callback") == -1) - { - PyC_Err_PrintWithFunc(py_func); - } - else { - is_values_set = true; - } - Py_DECREF(ret); + bpy_prop_int_array_from_callback_or_error(ret, array_len_info, py_func, values); + + bpy_prop_gil_rna_writable_end(bpy_state); +} + +static void bpy_prop_int_array_get_transform_fn( + PointerRNA *ptr, PropertyRNA *prop, const int *curr_values, bool is_set, int *r_values) +{ + const BPyPropGIL_RNAWritable_State bpy_state = bpy_prop_gil_rna_writable_begin(); + + const BPyPropArrayLength array_len_info{ptr, prop}; + BPyPropStore *prop_store = static_cast(RNA_property_py_data_get(prop)); + PyObject *py_func; + PyObject *ret; + + BLI_assert(prop_store != nullptr); + + py_func = prop_store->py_data.get_transform_fn; + + { + PyObject *args = PyTuple_New(3); + PyObject *self = pyrna_struct_as_instance(ptr); + PyTuple_SET_ITEMS( + args, + self, + bpy_py_object_from_prop_array_with_dims(curr_values, array_len_info, PyLong_Type), + PyBool_FromLong(is_set)); + + ret = PyObject_CallObject(py_func, args); + + Py_DECREF(args); } - if (is_values_set == false) { - /* This is the flattened length for multi-dimensional arrays. */ - for (i = 0; i < len; i++) { - values[i] = 0; - } - } + bpy_prop_int_array_from_callback_or_error(ret, array_len_info, py_func, r_values); bpy_prop_gil_rna_writable_end(bpy_state); } @@ -982,10 +1366,7 @@ static void bpy_prop_int_array_set_fn(PointerRNA *ptr, PropertyRNA *prop, const PyObject *py_func; PyObject *ret; - const int len = RNA_property_array_length(ptr, prop); - BPyPropArrayLength array_len_info{}; - array_len_info.len_total = len; - array_len_info.dims_len = RNA_property_array_dimension(ptr, prop, array_len_info.dims); + const BPyPropArrayLength array_len_info{ptr, prop}; BLI_assert(prop_store != nullptr); @@ -994,16 +1375,8 @@ static void bpy_prop_int_array_set_fn(PointerRNA *ptr, PropertyRNA *prop, const { PyObject *args = PyTuple_New(2); PyObject *self = pyrna_struct_as_instance(ptr); - - PyObject *py_values; - if (array_len_info.dims_len == 0) { - py_values = PyC_Tuple_PackArray_I32(values, len); - } - else { - py_values = PyC_Tuple_PackArray_Multi_I32( - values, array_len_info.dims, array_len_info.dims_len); - } - PyTuple_SET_ITEMS(args, self, py_values); + PyTuple_SET_ITEMS( + args, self, bpy_py_object_from_prop_array_with_dims(values, array_len_info, PyLong_Type)); ret = PyObject_CallObject(py_func, args); @@ -1025,6 +1398,45 @@ static void bpy_prop_int_array_set_fn(PointerRNA *ptr, PropertyRNA *prop, const bpy_prop_gil_rna_writable_end(bpy_state); } +static void bpy_prop_int_array_set_transform_fn(PointerRNA *ptr, + PropertyRNA *prop, + const int *new_values, + const int *curr_values, + bool is_set, + int *r_final_values) +{ + const BPyPropGIL_RNAWritable_State bpy_state = bpy_prop_gil_rna_writable_begin(); + + BPyPropStore *prop_store = static_cast(RNA_property_py_data_get(prop)); + PyObject *py_func; + PyObject *ret; + + const BPyPropArrayLength array_len_info{ptr, prop}; + + BLI_assert(prop_store != nullptr); + + py_func = prop_store->py_data.set_transform_fn; + + { + PyObject *args = PyTuple_New(4); + PyObject *self = pyrna_struct_as_instance(ptr); + PyTuple_SET_ITEMS( + args, + self, + bpy_py_object_from_prop_array_with_dims(new_values, array_len_info, PyLong_Type), + bpy_py_object_from_prop_array_with_dims(curr_values, array_len_info, PyLong_Type), + PyBool_FromLong(is_set)); + + ret = PyObject_CallObject(py_func, args); + + Py_DECREF(args); + } + + bpy_prop_int_array_from_callback_or_error(ret, array_len_info, py_func, r_final_values); + + bpy_prop_gil_rna_writable_end(bpy_state); +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -1038,7 +1450,6 @@ static float bpy_prop_float_get_fn(PointerRNA *ptr, PropertyRNA *prop) BPyPropStore *prop_store = static_cast(RNA_property_py_data_get(prop)); PyObject *py_func; PyObject *ret; - float value; BLI_assert(prop_store != nullptr); @@ -1075,6 +1486,51 @@ static float bpy_prop_float_get_fn(PointerRNA *ptr, PropertyRNA *prop) return value; } +static float bpy_prop_float_get_transform_fn(PointerRNA *ptr, + PropertyRNA *prop, + float curr_value, + bool is_set) +{ + const BPyPropGIL_RNAWritable_State bpy_state = bpy_prop_gil_rna_writable_begin(); + + BPyPropStore *prop_store = static_cast(RNA_property_py_data_get(prop)); + PyObject *py_func; + PyObject *ret; + + BLI_assert(prop_store != nullptr); + + py_func = prop_store->py_data.get_transform_fn; + + { + PyObject *args = PyTuple_New(3); + PyObject *self = pyrna_struct_as_instance(ptr); + PyTuple_SET_ITEMS(args, self, PyFloat_FromDouble(curr_value), PyBool_FromLong(is_set)); + + ret = PyObject_CallObject(py_func, args); + + Py_DECREF(args); + } + + float ret_value = curr_value; + if (ret == nullptr) { + PyC_Err_PrintWithFunc(py_func); + } + else { + ret_value = PyFloat_AsDouble(ret); + + if (ret_value == -1.0f && PyErr_Occurred()) { + PyC_Err_PrintWithFunc(py_func); + ret_value = curr_value; + } + + Py_DECREF(ret); + } + + bpy_prop_gil_rna_writable_end(bpy_state); + + return ret_value; +} + static void bpy_prop_float_set_fn(PointerRNA *ptr, PropertyRNA *prop, float value) { const BPyPropGIL_RNAWritable_State bpy_state = bpy_prop_gil_rna_writable_begin(); @@ -1112,7 +1568,8 @@ static void bpy_prop_float_set_fn(PointerRNA *ptr, PropertyRNA *prop, float valu bpy_prop_gil_rna_writable_end(bpy_state); } -static void bpy_prop_float_array_get_fn(PointerRNA *ptr, PropertyRNA *prop, float *values) +static float bpy_prop_float_set_transform_fn( + PointerRNA *ptr, PropertyRNA *prop, float new_value, float curr_value, bool is_set) { const BPyPropGIL_RNAWritable_State bpy_state = bpy_prop_gil_rna_writable_begin(); @@ -1120,11 +1577,91 @@ static void bpy_prop_float_array_get_fn(PointerRNA *ptr, PropertyRNA *prop, floa PyObject *py_func; PyObject *ret; + BLI_assert(prop_store != nullptr); + + py_func = prop_store->py_data.set_transform_fn; + + { + PyObject *args = PyTuple_New(4); + PyObject *self = pyrna_struct_as_instance(ptr); + PyTuple_SET_ITEMS(args, + self, + PyFloat_FromDouble(new_value), + PyFloat_FromDouble(curr_value), + PyBool_FromLong(is_set)); + + ret = PyObject_CallObject(py_func, args); + + Py_DECREF(args); + } + + float ret_value = curr_value; + if (ret == nullptr) { + PyC_Err_PrintWithFunc(py_func); + } + else { + ret_value = PyFloat_AsDouble(ret); + + if (ret_value == -1.0f && PyErr_Occurred()) { + PyC_Err_PrintWithFunc(py_func); + ret_value = curr_value; + } + + Py_DECREF(ret); + } + + bpy_prop_gil_rna_writable_end(bpy_state); + + return ret_value; +} + +static void bpy_prop_float_array_from_callback_or_error(PropertyRNA *prop, + PyObject *float_array_obj, + const BPyPropArrayLength &array_len_info, + PyObject *py_func, + const bool do_matrix_row_col_swap, + float *r_values) +{ bool is_values_set = false; - int i, len = RNA_property_array_length(ptr, prop); - BPyPropArrayLength array_len_info{}; - array_len_info.len_total = len; - array_len_info.dims_len = RNA_property_array_dimension(ptr, prop, array_len_info.dims); + + if (float_array_obj != nullptr) { + if (bpy_prop_array_from_py_with_dims(r_values, + sizeof(*r_values), + float_array_obj, + &array_len_info, + &PyFloat_Type, + "FloatVectorProperty get callback") == -1) + { + PyC_Err_PrintWithFunc(py_func); + } + else { + /* Only for float types. */ + /* TODO: Clear and comnplete explanations about this matrix swap? */ + if (do_matrix_row_col_swap && bpy_prop_array_is_matrix_compatible(prop, &array_len_info)) { + bpy_prop_array_matrix_swap_row_column_vn(r_values, &array_len_info); + } + is_values_set = true; + } + } + + if (is_values_set == false) { + /* This is the flattened length for multi-dimensional arrays. */ + for (int i = 0; i < array_len_info.len_total; i++) { + r_values[i] = 0.0f; + } + } + + Py_XDECREF(float_array_obj); +} + +static void bpy_prop_float_array_get_fn(PointerRNA *ptr, PropertyRNA *prop, float *values) +{ + const BPyPropGIL_RNAWritable_State bpy_state = bpy_prop_gil_rna_writable_begin(); + + const BPyPropArrayLength array_len_info{ptr, prop}; + BPyPropStore *prop_store = static_cast(RNA_property_py_data_get(prop)); + PyObject *py_func; + PyObject *ret; BLI_assert(prop_store != nullptr); @@ -1140,32 +1677,45 @@ static void bpy_prop_float_array_get_fn(PointerRNA *ptr, PropertyRNA *prop, floa Py_DECREF(args); } - if (ret != nullptr) { - if (bpy_prop_array_from_py_with_dims(values, - sizeof(*values), - ret, - &array_len_info, - &PyFloat_Type, - "FloatVectorProperty get callback") == -1) - { - PyC_Err_PrintWithFunc(py_func); - } - else { - /* Only for float types. */ - if (bpy_prop_array_is_matrix_compatible(prop, &array_len_info)) { - bpy_prop_array_matrix_swap_row_column_vn(values, &array_len_info); - } - is_values_set = true; - } - Py_DECREF(ret); + /* Custom getter always needs to perform the matrix row/col swap. */ + bpy_prop_float_array_from_callback_or_error(prop, ret, array_len_info, py_func, true, values); + + bpy_prop_gil_rna_writable_end(bpy_state); +} + +static void bpy_prop_float_array_get_transform_fn( + PointerRNA *ptr, PropertyRNA *prop, const float *curr_values, bool is_set, float *r_values) +{ + const BPyPropGIL_RNAWritable_State bpy_state = bpy_prop_gil_rna_writable_begin(); + + const BPyPropArrayLength array_len_info{ptr, prop}; + BPyPropStore *prop_store = static_cast(RNA_property_py_data_get(prop)); + PyObject *py_func; + PyObject *ret; + + BLI_assert(prop_store != nullptr); + + py_func = prop_store->py_data.get_transform_fn; + + { + PyObject *args = PyTuple_New(3); + PyObject *self = pyrna_struct_as_instance(ptr); + PyTuple_SET_ITEMS( + args, + self, + bpy_py_object_from_prop_array_with_dims(curr_values, array_len_info, PyFloat_Type), + PyBool_FromLong(is_set)); + + ret = PyObject_CallObject(py_func, args); + + Py_DECREF(args); } - if (is_values_set == false) { - /* This is the flattened length for multi-dimensional arrays. */ - for (i = 0; i < len; i++) { - values[i] = 0.0f; - } - } + /* If there is a custom py-defined 'get' callback, the row/col matrix swap has already been + * performed, otherwise it needs to be done here. */ + const bool do_matrix_row_col_swap = prop_store->py_data.get_fn == nullptr; + bpy_prop_float_array_from_callback_or_error( + prop, ret, array_len_info, py_func, do_matrix_row_col_swap, r_values); bpy_prop_gil_rna_writable_end(bpy_state); } @@ -1178,10 +1728,7 @@ static void bpy_prop_float_array_set_fn(PointerRNA *ptr, PropertyRNA *prop, cons PyObject *py_func; PyObject *ret; - const int len = RNA_property_array_length(ptr, prop); - BPyPropArrayLength array_len_info{}; - array_len_info.len_total = len; - array_len_info.dims_len = RNA_property_array_dimension(ptr, prop, array_len_info.dims); + const BPyPropArrayLength array_len_info{ptr, prop}; BLI_assert(prop_store != nullptr); @@ -1190,17 +1737,8 @@ static void bpy_prop_float_array_set_fn(PointerRNA *ptr, PropertyRNA *prop, cons { PyObject *args = PyTuple_New(2); PyObject *self = pyrna_struct_as_instance(ptr); - - PyObject *py_values; - if (array_len_info.dims_len == 0) { - py_values = PyC_Tuple_PackArray_F32(values, len); - } - else { - /* No need for matrix column/row swapping here unless the matrix data is read directly. */ - py_values = PyC_Tuple_PackArray_Multi_F32( - values, array_len_info.dims, array_len_info.dims_len); - } - PyTuple_SET_ITEMS(args, self, py_values); + PyTuple_SET_ITEMS( + args, self, bpy_py_object_from_prop_array_with_dims(values, array_len_info, PyFloat_Type)); ret = PyObject_CallObject(py_func, args); @@ -1222,16 +1760,90 @@ static void bpy_prop_float_array_set_fn(PointerRNA *ptr, PropertyRNA *prop, cons bpy_prop_gil_rna_writable_end(bpy_state); } +static void bpy_prop_float_array_set_transform_fn(PointerRNA *ptr, + PropertyRNA *prop, + const float *new_values, + const float *curr_values, + bool is_set, + float *r_final_values) +{ + const BPyPropGIL_RNAWritable_State bpy_state = bpy_prop_gil_rna_writable_begin(); + + BPyPropStore *prop_store = static_cast(RNA_property_py_data_get(prop)); + PyObject *py_func; + PyObject *ret; + + const BPyPropArrayLength array_len_info{ptr, prop}; + + BLI_assert(prop_store != nullptr); + + py_func = prop_store->py_data.set_transform_fn; + + { + PyObject *args = PyTuple_New(4); + PyObject *self = pyrna_struct_as_instance(ptr); + PyTuple_SET_ITEMS( + args, + self, + bpy_py_object_from_prop_array_with_dims(new_values, array_len_info, PyFloat_Type), + bpy_py_object_from_prop_array_with_dims(curr_values, array_len_info, PyFloat_Type), + PyBool_FromLong(is_set)); + + ret = PyObject_CallObject(py_func, args); + + Py_DECREF(args); + } + + /* No need for matrix column/row swapping here unless the matrix data is read directly. */ + bpy_prop_float_array_from_callback_or_error( + prop, ret, array_len_info, py_func, false, r_final_values); + + bpy_prop_gil_rna_writable_end(bpy_state); +} + /** \} */ /* -------------------------------------------------------------------- */ /** \name String Property Callbacks * \{ */ -static void bpy_prop_string_get_fn(PointerRNA *ptr, PropertyRNA *prop, char *value) +static std::optional bpy_prop_string_from_callback_or_error(PyObject *str_obj, + const size_t max_length, + PyObject *py_func) { - const BPyPropGIL_RNAWritable_State bpy_state = bpy_prop_gil_rna_writable_begin(); + std::optional ret_value{}; + /* TODO: handle bytes strings. */ + if (str_obj == nullptr) { + PyC_Err_PrintWithFunc(py_func); + } + else if (!PyUnicode_Check(str_obj)) { + PyErr_Format( + PyExc_TypeError, "return value must be a string, not %.200s", Py_TYPE(str_obj)->tp_name); + PyC_Err_PrintWithFunc(py_func); + } + else { + /* NOTE: Python returns the length _without_ the `\0` terminator. */ + Py_ssize_t length; + const char *ret_cstr = PyUnicode_AsUTF8AndSize(str_obj, &length); + if (max_length && size_t(length) + 1 > max_length) { + PyErr_Format(PyExc_ValueError, + "return string must be of max length %zu, not %d", + max_length - 1, + length); + PyC_Err_PrintWithFunc(py_func); + } + else { + ret_value = {ret_cstr, size_t(length)}; + } + } + + Py_XDECREF(str_obj); + return ret_value; +} + +static std::string bpy_prop_string_get_locked_fn(PointerRNA *ptr, PropertyRNA *prop) +{ BPyPropStore *prop_store = static_cast(RNA_property_py_data_get(prop)); PyObject *py_func; PyObject *ret; @@ -1250,75 +1862,82 @@ static void bpy_prop_string_get_fn(PointerRNA *ptr, PropertyRNA *prop, char *val Py_DECREF(args); } - if (ret == nullptr) { - PyC_Err_PrintWithFunc(py_func); - value[0] = '\0'; - } - else if (!PyUnicode_Check(ret)) { - PyErr_Format( - PyExc_TypeError, "return value must be a string, not %.200s", Py_TYPE(ret)->tp_name); - PyC_Err_PrintWithFunc(py_func); - value[0] = '\0'; - Py_DECREF(ret); - } - else { - Py_ssize_t length; - const char *buffer = PyUnicode_AsUTF8AndSize(ret, &length); - memcpy(value, buffer, length + 1); - Py_DECREF(ret); - } + return bpy_prop_string_from_callback_or_error(ret, RNA_property_string_maxlength(prop), py_func) + .value_or(""); +} + +static std::string bpy_prop_string_get_fn(PointerRNA *ptr, PropertyRNA *prop) +{ + const BPyPropGIL_RNAWritable_State bpy_state = bpy_prop_gil_rna_writable_begin(); + + std::string ret_value = bpy_prop_string_get_locked_fn(ptr, prop); bpy_prop_gil_rna_writable_end(bpy_state); + + return ret_value; +} + +static std::string bpy_prop_string_get_transform_locked_fn(PointerRNA *ptr, + PropertyRNA *prop, + const std::string &curr_value, + bool is_set) +{ + BPyPropStore *prop_store = static_cast(RNA_property_py_data_get(prop)); + PyObject *py_func; + PyObject *ret; + + BLI_assert(prop_store != nullptr); + + /* TODO: handle bytes strings. */ + py_func = prop_store->py_data.get_transform_fn; + + { + PyObject *args = PyTuple_New(3); + PyObject *self = pyrna_struct_as_instance(ptr); + PyTuple_SET_ITEMS( + args, + self, + PyUnicode_FromStringAndSize(curr_value.c_str(), Py_ssize_t(curr_value.size())), + PyBool_FromLong(is_set)); + + ret = PyObject_CallObject(py_func, args); + + Py_DECREF(args); + } + + return bpy_prop_string_from_callback_or_error(ret, RNA_property_string_maxlength(prop), py_func) + .value_or(curr_value); +} + +static std::string bpy_prop_string_get_transform_fn(PointerRNA *ptr, + PropertyRNA *prop, + const std::string &curr_value, + bool is_set) +{ + const BPyPropGIL_RNAWritable_State bpy_state = bpy_prop_gil_rna_writable_begin(); + + std::string ret_value = bpy_prop_string_get_transform_locked_fn(ptr, prop, curr_value, is_set); + + bpy_prop_gil_rna_writable_end(bpy_state); + + return ret_value; } static int bpy_prop_string_length_fn(PointerRNA *ptr, PropertyRNA *prop) { const BPyPropGIL_RNAWritable_State bpy_state = bpy_prop_gil_rna_writable_begin(); - BPyPropStore *prop_store = static_cast(RNA_property_py_data_get(prop)); - PyObject *py_func; - PyObject *ret; - - int length; - - BLI_assert(prop_store != nullptr); - - py_func = prop_store->py_data.get_fn; - - { - PyObject *args = PyTuple_New(1); - PyObject *self = pyrna_struct_as_instance(ptr); - PyTuple_SET_ITEMS(args, self); - - ret = PyObject_CallObject(py_func, args); - - Py_DECREF(args); - } - - if (ret == nullptr) { - PyC_Err_PrintWithFunc(py_func); - length = 0; - } - else if (!PyUnicode_Check(ret)) { - PyErr_Format( - PyExc_TypeError, "return value must be a string, not %.200s", Py_TYPE(ret)->tp_name); - PyC_Err_PrintWithFunc(py_func); - length = 0; - Py_DECREF(ret); - } - else { - Py_ssize_t length_ssize = 0; - PyUnicode_AsUTF8AndSize(ret, &length_ssize); - length = length_ssize; - Py_DECREF(ret); - } + /* This bpyprops-specific length callback is only called when there is a custom `get` function. + */ + std::string ret = bpy_prop_string_get_locked_fn(ptr, prop); + const int length = int(ret.size()); bpy_prop_gil_rna_writable_end(bpy_state); return length; } -static void bpy_prop_string_set_fn(PointerRNA *ptr, PropertyRNA *prop, const char *value) +static void bpy_prop_string_set_fn(PointerRNA *ptr, PropertyRNA *prop, const std::string &value) { const BPyPropGIL_RNAWritable_State bpy_state = bpy_prop_gil_rna_writable_begin(); @@ -1334,9 +1953,19 @@ static void bpy_prop_string_set_fn(PointerRNA *ptr, PropertyRNA *prop, const cha PyObject *args = PyTuple_New(2); PyObject *self = pyrna_struct_as_instance(ptr); - PyObject *py_value = PyUnicode_FromString(value); + /* TODO: handle bytes strings. */ + const size_t max_length = RNA_property_string_maxlength(prop); + if (max_length && value.size() >= max_length) { + PyErr_Format(PyExc_ValueError, + "the given string must be of max length %zu, not %zu", + max_length - 1, + value.size()); + PyC_Err_PrintWithFunc(py_func); + } + PyObject *py_value = PyUnicode_FromStringAndSize(value.c_str(), value.size()); if (!py_value) { - PyErr_SetString(PyExc_ValueError, "the set value must be a valid string"); + PyErr_SetString(PyExc_ValueError, + "the given string value cannot be converted into a python string"); PyC_Err_PrintWithFunc(py_func); py_value = Py_None; Py_INCREF(py_value); @@ -1363,6 +1992,46 @@ static void bpy_prop_string_set_fn(PointerRNA *ptr, PropertyRNA *prop, const cha bpy_prop_gil_rna_writable_end(bpy_state); } +static std::string bpy_prop_string_set_transform_fn(PointerRNA *ptr, + PropertyRNA *prop, + const std::string &new_value, + const std::string &curr_value, + bool is_set) +{ + const BPyPropGIL_RNAWritable_State bpy_state = bpy_prop_gil_rna_writable_begin(); + + BPyPropStore *prop_store = static_cast(RNA_property_py_data_get(prop)); + PyObject *py_func; + PyObject *ret; + + BLI_assert(prop_store != nullptr); + + py_func = prop_store->py_data.set_transform_fn; + + { + PyObject *args = PyTuple_New(4); + PyObject *self = pyrna_struct_as_instance(ptr); + PyTuple_SET_ITEMS( + args, + self, + PyUnicode_FromStringAndSize(new_value.c_str(), Py_ssize_t(new_value.size())), + PyUnicode_FromStringAndSize(curr_value.c_str(), Py_ssize_t(curr_value.size())), + PyBool_FromLong(is_set)); + + ret = PyObject_CallObject(py_func, args); + + Py_DECREF(args); + } + + std::string ret_value = bpy_prop_string_from_callback_or_error( + ret, RNA_property_string_maxlength(prop), py_func) + .value_or(curr_value); + + bpy_prop_gil_rna_writable_end(bpy_state); + + return ret_value; +} + static bool bpy_prop_string_visit_fn_call( PyObject *py_func, PyObject *item, @@ -1572,7 +2241,6 @@ static int bpy_prop_enum_get_fn(PointerRNA *ptr, PropertyRNA *prop) BPyPropStore *prop_store = static_cast(RNA_property_py_data_get(prop)); PyObject *py_func; PyObject *ret; - int value; BLI_assert(prop_store != nullptr); @@ -1609,6 +2277,51 @@ static int bpy_prop_enum_get_fn(PointerRNA *ptr, PropertyRNA *prop) return value; } +static int bpy_prop_enum_get_transform_fn(PointerRNA *ptr, + PropertyRNA *prop, + int curr_value, + bool is_set) +{ + const BPyPropGIL_RNAWritable_State bpy_state = bpy_prop_gil_rna_writable_begin(); + + BPyPropStore *prop_store = static_cast(RNA_property_py_data_get(prop)); + PyObject *py_func; + PyObject *ret; + + BLI_assert(prop_store != nullptr); + + py_func = prop_store->py_data.get_transform_fn; + + { + PyObject *args = PyTuple_New(3); + PyObject *self = pyrna_struct_as_instance(ptr); + PyTuple_SET_ITEMS(args, self, PyLong_FromLong(curr_value), PyBool_FromLong(is_set)); + + ret = PyObject_CallObject(py_func, args); + + Py_DECREF(args); + } + + int ret_value = curr_value; + if (ret == nullptr) { + PyC_Err_PrintWithFunc(py_func); + } + else { + ret_value = PyC_Long_AsI32(ret); + + if (ret_value == -1 && PyErr_Occurred()) { + PyC_Err_PrintWithFunc(py_func); + ret_value = curr_value; + } + + Py_DECREF(ret); + } + + bpy_prop_gil_rna_writable_end(bpy_state); + + return ret_value; +} + static void bpy_prop_enum_set_fn(PointerRNA *ptr, PropertyRNA *prop, int value) { const BPyPropGIL_RNAWritable_State bpy_state = bpy_prop_gil_rna_writable_begin(); @@ -1646,6 +2359,53 @@ static void bpy_prop_enum_set_fn(PointerRNA *ptr, PropertyRNA *prop, int value) bpy_prop_gil_rna_writable_end(bpy_state); } +static int bpy_prop_enum_set_transform_fn( + PointerRNA *ptr, PropertyRNA *prop, int new_value, int curr_value, bool is_set) +{ + const BPyPropGIL_RNAWritable_State bpy_state = bpy_prop_gil_rna_writable_begin(); + + BPyPropStore *prop_store = static_cast(RNA_property_py_data_get(prop)); + PyObject *py_func; + PyObject *ret; + + BLI_assert(prop_store != nullptr); + + py_func = prop_store->py_data.set_transform_fn; + + { + PyObject *args = PyTuple_New(4); + PyObject *self = pyrna_struct_as_instance(ptr); + PyTuple_SET_ITEMS(args, + self, + PyLong_FromLong(new_value), + PyLong_FromLong(curr_value), + PyBool_FromLong(is_set)); + + ret = PyObject_CallObject(py_func, args); + + Py_DECREF(args); + } + + int ret_value = curr_value; + if (ret == nullptr) { + PyC_Err_PrintWithFunc(py_func); + } + else { + ret_value = PyC_Long_AsI32(ret); + + if (ret_value == -1 && PyErr_Occurred()) { + PyC_Err_PrintWithFunc(py_func); + ret_value = curr_value; + } + + Py_DECREF(ret); + } + + bpy_prop_gil_rna_writable_end(bpy_state); + + return ret_value; +} + /* utility function we need for parsing int's in an if statement */ static bool py_long_as_int(PyObject *py_long, int *r_int) { @@ -1998,10 +2758,16 @@ static void bpy_prop_callback_assign_pointer(PropertyRNA *prop, PyObject *poll_f } } -static void bpy_prop_callback_assign_boolean(PropertyRNA *prop, PyObject *get_fn, PyObject *set_fn) +static bool bpy_prop_callback_assign_boolean(PropertyRNA *prop, + PyObject *get_fn, + PyObject *set_fn, + PyObject *get_transform_fn, + PyObject *set_transform_fn) { BooleanPropertyGetFunc rna_get_fn = nullptr; BooleanPropertySetFunc rna_set_fn = nullptr; + BooleanPropertyGetTransformFunc rna_get_transform_fn = nullptr; + BooleanPropertySetTransformFunc rna_set_transform_fn = nullptr; if (get_fn && get_fn != Py_None) { BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); @@ -2011,21 +2777,49 @@ static void bpy_prop_callback_assign_boolean(PropertyRNA *prop, PyObject *get_fn } if (set_fn && set_fn != Py_None) { + if (!rna_get_fn) { + PyErr_SetString(PyExc_ValueError, + "The `set` callback is defined without a matching `get` function, this is " + "not supported. `set_transform` should probably be used instead?"); + return false; + } + BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); rna_set_fn = bpy_prop_boolean_set_fn; ASSIGN_PYOBJECT_INCREF(prop_store->py_data.set_fn, set_fn); } - RNA_def_property_boolean_funcs_runtime(prop, rna_get_fn, rna_set_fn); + if (get_transform_fn && get_transform_fn != Py_None) { + BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); + + rna_get_transform_fn = bpy_prop_boolean_get_transform_fn; + ASSIGN_PYOBJECT_INCREF(prop_store->py_data.get_transform_fn, get_transform_fn); + } + + if (set_transform_fn && set_transform_fn != Py_None) { + BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); + + rna_set_transform_fn = bpy_prop_boolean_set_transform_fn; + ASSIGN_PYOBJECT_INCREF(prop_store->py_data.set_transform_fn, set_transform_fn); + } + + RNA_def_property_boolean_funcs_runtime( + prop, rna_get_fn, rna_set_fn, rna_get_transform_fn, rna_set_transform_fn); + + return true; } -static void bpy_prop_callback_assign_boolean_array(PropertyRNA *prop, +static bool bpy_prop_callback_assign_boolean_array(PropertyRNA *prop, PyObject *get_fn, - PyObject *set_fn) + PyObject *set_fn, + PyObject *get_transform_fn, + PyObject *set_transform_fn) { BooleanArrayPropertyGetFunc rna_get_fn = nullptr; BooleanArrayPropertySetFunc rna_set_fn = nullptr; + BooleanArrayPropertyGetTransformFunc rna_get_transform_fn = nullptr; + BooleanArrayPropertySetTransformFunc rna_set_transform_fn = nullptr; if (get_fn && get_fn != Py_None) { BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); @@ -2035,19 +2829,49 @@ static void bpy_prop_callback_assign_boolean_array(PropertyRNA *prop, } if (set_fn && set_fn != Py_None) { + if (!rna_get_fn) { + PyErr_SetString(PyExc_ValueError, + "The `set` callback is defined without a matching `get` function, this is " + "not supported. `set_transform` should probably be used instead?"); + return false; + } + BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); rna_set_fn = bpy_prop_boolean_array_set_fn; ASSIGN_PYOBJECT_INCREF(prop_store->py_data.set_fn, set_fn); } - RNA_def_property_boolean_array_funcs_runtime(prop, rna_get_fn, rna_set_fn); + if (get_transform_fn && get_transform_fn != Py_None) { + BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); + + rna_get_transform_fn = bpy_prop_boolean_array_get_transform_fn; + ASSIGN_PYOBJECT_INCREF(prop_store->py_data.get_transform_fn, get_transform_fn); + } + + if (set_transform_fn && set_transform_fn != Py_None) { + BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); + + rna_set_transform_fn = bpy_prop_boolean_array_set_transform_fn; + ASSIGN_PYOBJECT_INCREF(prop_store->py_data.set_transform_fn, set_transform_fn); + } + + RNA_def_property_boolean_array_funcs_runtime( + prop, rna_get_fn, rna_set_fn, rna_get_transform_fn, rna_set_transform_fn); + + return true; } -static void bpy_prop_callback_assign_int(PropertyRNA *prop, PyObject *get_fn, PyObject *set_fn) +static bool bpy_prop_callback_assign_int(PropertyRNA *prop, + PyObject *get_fn, + PyObject *set_fn, + PyObject *get_transform_fn, + PyObject *set_transform_fn) { IntPropertyGetFunc rna_get_fn = nullptr; IntPropertySetFunc rna_set_fn = nullptr; + IntPropertyGetTransformFunc rna_get_transform_fn = nullptr; + IntPropertySetTransformFunc rna_set_transform_fn = nullptr; if (get_fn && get_fn != Py_None) { BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); @@ -2057,21 +2881,49 @@ static void bpy_prop_callback_assign_int(PropertyRNA *prop, PyObject *get_fn, Py } if (set_fn && set_fn != Py_None) { + if (!rna_get_fn) { + PyErr_SetString(PyExc_ValueError, + "The `set` callback is defined without a matching `get` function, this is " + "not supported. `set_transform` should probably be used instead?"); + return false; + } + BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); rna_set_fn = bpy_prop_int_set_fn; ASSIGN_PYOBJECT_INCREF(prop_store->py_data.set_fn, set_fn); } - RNA_def_property_int_funcs_runtime(prop, rna_get_fn, rna_set_fn, nullptr); + if (get_transform_fn && get_transform_fn != Py_None) { + BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); + + rna_get_transform_fn = bpy_prop_int_get_transform_fn; + ASSIGN_PYOBJECT_INCREF(prop_store->py_data.get_transform_fn, get_transform_fn); + } + + if (set_transform_fn && set_transform_fn != Py_None) { + BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); + + rna_set_transform_fn = bpy_prop_int_set_transform_fn; + ASSIGN_PYOBJECT_INCREF(prop_store->py_data.set_transform_fn, set_transform_fn); + } + + RNA_def_property_int_funcs_runtime( + prop, rna_get_fn, rna_set_fn, nullptr, rna_get_transform_fn, rna_set_transform_fn); + + return true; } static void bpy_prop_callback_assign_int_array(PropertyRNA *prop, PyObject *get_fn, - PyObject *set_fn) + PyObject *set_fn, + PyObject *get_transform_fn, + PyObject *set_transform_fn) { IntArrayPropertyGetFunc rna_get_fn = nullptr; IntArrayPropertySetFunc rna_set_fn = nullptr; + IntArrayPropertyGetTransformFunc rna_get_transform_fn = nullptr; + IntArrayPropertySetTransformFunc rna_set_transform_fn = nullptr; if (get_fn && get_fn != Py_None) { BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); @@ -2081,19 +2933,46 @@ static void bpy_prop_callback_assign_int_array(PropertyRNA *prop, } if (set_fn && set_fn != Py_None) { + if (!rna_get_fn) { + PyErr_SetString(PyExc_ValueError, + "The `set` callback is defined without a matching `get` function, this is " + "not supported. `set_transform` should probably be used instead?"); + } + BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); rna_set_fn = bpy_prop_int_array_set_fn; ASSIGN_PYOBJECT_INCREF(prop_store->py_data.set_fn, set_fn); } - RNA_def_property_int_array_funcs_runtime(prop, rna_get_fn, rna_set_fn, nullptr); + if (get_transform_fn && get_transform_fn != Py_None) { + BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); + + rna_get_transform_fn = bpy_prop_int_array_get_transform_fn; + ASSIGN_PYOBJECT_INCREF(prop_store->py_data.get_transform_fn, get_transform_fn); + } + + if (set_transform_fn && set_transform_fn != Py_None) { + BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); + + rna_set_transform_fn = bpy_prop_int_array_set_transform_fn; + ASSIGN_PYOBJECT_INCREF(prop_store->py_data.set_transform_fn, set_transform_fn); + } + + RNA_def_property_int_array_funcs_runtime( + prop, rna_get_fn, rna_set_fn, nullptr, rna_get_transform_fn, rna_set_transform_fn); } -static void bpy_prop_callback_assign_float(PropertyRNA *prop, PyObject *get_fn, PyObject *set_fn) +static bool bpy_prop_callback_assign_float(PropertyRNA *prop, + PyObject *get_fn, + PyObject *set_fn, + PyObject *get_transform_fn, + PyObject *set_transform_fn) { FloatPropertyGetFunc rna_get_fn = nullptr; FloatPropertySetFunc rna_set_fn = nullptr; + FloatPropertyGetTransformFunc rna_get_transform_fn = nullptr; + FloatPropertySetTransformFunc rna_set_transform_fn = nullptr; if (get_fn && get_fn != Py_None) { BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); @@ -2103,21 +2982,49 @@ static void bpy_prop_callback_assign_float(PropertyRNA *prop, PyObject *get_fn, } if (set_fn && set_fn != Py_None) { + if (!rna_get_fn) { + PyErr_SetString(PyExc_ValueError, + "The `set` callback is defined without a matching `get` function, this is " + "not supported. `set_transform` should probably be used instead?"); + return false; + } + BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); rna_set_fn = bpy_prop_float_set_fn; ASSIGN_PYOBJECT_INCREF(prop_store->py_data.set_fn, set_fn); } - RNA_def_property_float_funcs_runtime(prop, rna_get_fn, rna_set_fn, nullptr); + if (get_transform_fn && get_transform_fn != Py_None) { + BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); + + rna_get_transform_fn = bpy_prop_float_get_transform_fn; + ASSIGN_PYOBJECT_INCREF(prop_store->py_data.get_transform_fn, get_transform_fn); + } + + if (set_transform_fn && set_transform_fn != Py_None) { + BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); + + rna_set_transform_fn = bpy_prop_float_set_transform_fn; + ASSIGN_PYOBJECT_INCREF(prop_store->py_data.set_transform_fn, set_transform_fn); + } + + RNA_def_property_float_funcs_runtime( + prop, rna_get_fn, rna_set_fn, nullptr, rna_get_transform_fn, rna_set_transform_fn); + + return true; } static void bpy_prop_callback_assign_float_array(PropertyRNA *prop, PyObject *get_fn, - PyObject *set_fn) + PyObject *set_fn, + PyObject *get_transform_fn, + PyObject *set_transform_fn) { FloatArrayPropertyGetFunc rna_get_fn = nullptr; FloatArrayPropertySetFunc rna_set_fn = nullptr; + FloatArrayPropertyGetTransformFunc rna_get_transform_fn = nullptr; + FloatArrayPropertySetTransformFunc rna_set_transform_fn = nullptr; if (get_fn && get_fn != Py_None) { BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); @@ -2127,18 +3034,41 @@ static void bpy_prop_callback_assign_float_array(PropertyRNA *prop, } if (set_fn && set_fn != Py_None) { + if (!rna_get_fn) { + PyErr_SetString(PyExc_ValueError, + "The `set` callback is defined without a matching `get` function, this is " + "not supported. `set_transform` should probably be used instead?"); + } + BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); rna_set_fn = bpy_prop_float_array_set_fn; ASSIGN_PYOBJECT_INCREF(prop_store->py_data.set_fn, set_fn); } - RNA_def_property_float_array_funcs_runtime(prop, rna_get_fn, rna_set_fn, nullptr); + if (get_transform_fn && get_transform_fn != Py_None) { + BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); + + rna_get_transform_fn = bpy_prop_float_array_get_transform_fn; + ASSIGN_PYOBJECT_INCREF(prop_store->py_data.get_transform_fn, get_transform_fn); + } + + if (set_transform_fn && set_transform_fn != Py_None) { + BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); + + rna_set_transform_fn = bpy_prop_float_array_set_transform_fn; + ASSIGN_PYOBJECT_INCREF(prop_store->py_data.set_transform_fn, set_transform_fn); + } + + RNA_def_property_float_array_funcs_runtime( + prop, rna_get_fn, rna_set_fn, nullptr, rna_get_transform_fn, rna_set_transform_fn); } -static void bpy_prop_callback_assign_string(PropertyRNA *prop, +static bool bpy_prop_callback_assign_string(PropertyRNA *prop, PyObject *get_fn, PyObject *set_fn, + PyObject *get_transform_fn, + PyObject *set_transform_fn, PyObject *search_fn, const eStringPropertySearchFlag search_flag) { @@ -2146,6 +3076,8 @@ static void bpy_prop_callback_assign_string(PropertyRNA *prop, StringPropertyLengthFunc rna_length_fn = nullptr; StringPropertySetFunc rna_set_fn = nullptr; StringPropertySearchFunc rna_search_fn = nullptr; + StringPropertyGetTransformFunc rna_get_transform_fn = nullptr; + StringPropertySetTransformFunc rna_set_transform_fn = nullptr; if (get_fn && get_fn != Py_None) { BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); @@ -2156,11 +3088,33 @@ static void bpy_prop_callback_assign_string(PropertyRNA *prop, } if (set_fn && set_fn != Py_None) { + if (!rna_get_fn) { + PyErr_SetString(PyExc_ValueError, + "The `set` callback is defined without a matching `get` function, this is " + "not supported. `set_transform` should probably be used instead?"); + return false; + } + BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); rna_set_fn = bpy_prop_string_set_fn; ASSIGN_PYOBJECT_INCREF(prop_store->py_data.set_fn, set_fn); } + + if (get_transform_fn && get_transform_fn != Py_None) { + BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); + + rna_get_transform_fn = bpy_prop_string_get_transform_fn; + ASSIGN_PYOBJECT_INCREF(prop_store->py_data.get_transform_fn, get_transform_fn); + } + + if (set_transform_fn && set_transform_fn != Py_None) { + BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); + + rna_set_transform_fn = bpy_prop_string_set_transform_fn; + ASSIGN_PYOBJECT_INCREF(prop_store->py_data.set_transform_fn, set_transform_fn); + } + if (search_fn) { BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); @@ -2168,20 +3122,27 @@ static void bpy_prop_callback_assign_string(PropertyRNA *prop, ASSIGN_PYOBJECT_INCREF(prop_store->py_data.string_data.search_fn, search_fn); } - RNA_def_property_string_funcs_runtime(prop, rna_get_fn, rna_length_fn, rna_set_fn); + RNA_def_property_string_funcs_runtime( + prop, rna_get_fn, rna_length_fn, rna_set_fn, rna_get_transform_fn, rna_set_transform_fn); if (rna_search_fn) { RNA_def_property_string_search_func_runtime(prop, rna_search_fn, search_flag); } + + return true; } -static void bpy_prop_callback_assign_enum(PropertyRNA *prop, +static bool bpy_prop_callback_assign_enum(PropertyRNA *prop, PyObject *get_fn, PyObject *set_fn, - PyObject *itemf_fn) + PyObject *itemf_fn, + PyObject *get_transform_fn, + PyObject *set_transform_fn) { EnumPropertyGetFunc rna_get_fn = nullptr; - EnumPropertyItemFunc rna_itemf_fn = nullptr; EnumPropertySetFunc rna_set_fn = nullptr; + EnumPropertyGetTransformFunc rna_get_transform_fn = nullptr; + EnumPropertySetTransformFunc rna_set_transform_fn = nullptr; + EnumPropertyItemFunc rna_itemf_fn = nullptr; if (get_fn && get_fn != Py_None) { BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); @@ -2191,6 +3152,13 @@ static void bpy_prop_callback_assign_enum(PropertyRNA *prop, } if (set_fn && set_fn != Py_None) { + if (!rna_get_fn) { + PyErr_SetString(PyExc_ValueError, + "The `set` callback is defined without a matching `get` function, this is " + "not supported. `set_transform` should probably be used instead?"); + return false; + } + BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); rna_set_fn = bpy_prop_enum_set_fn; @@ -2203,7 +3171,24 @@ static void bpy_prop_callback_assign_enum(PropertyRNA *prop, ASSIGN_PYOBJECT_INCREF(prop_store->py_data.enum_data.itemf_fn, itemf_fn); } - RNA_def_property_enum_funcs_runtime(prop, rna_get_fn, rna_set_fn, rna_itemf_fn); + if (get_transform_fn && get_transform_fn != Py_None) { + BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); + + rna_get_transform_fn = bpy_prop_enum_get_transform_fn; + ASSIGN_PYOBJECT_INCREF(prop_store->py_data.get_transform_fn, get_transform_fn); + } + + if (set_transform_fn && set_transform_fn != Py_None) { + BPyPropStore *prop_store = bpy_prop_py_data_ensure(prop); + + rna_set_transform_fn = bpy_prop_enum_set_transform_fn; + ASSIGN_PYOBJECT_INCREF(prop_store->py_data.set_transform_fn, set_transform_fn); + } + + RNA_def_property_enum_funcs_runtime( + prop, rna_get_fn, rna_set_fn, rna_itemf_fn, rna_get_transform_fn, rna_set_transform_fn); + + return true; } /** \} */ @@ -2435,15 +3420,51 @@ static int bpy_prop_arg_parse_tag_defines(PyObject *o, void *p) "bool]\n" #define BPY_PROPDEF_GET_DOC(ty) \ - " :arg get: Function to be called when this value is 'read',\n" \ + " :arg get: Function to be called when this value is 'read', and the default,\n" \ + " system-defined storage is not used for this property.\n" \ " This function must take 1 value (self) and return the value of the property.\n" \ + "\n" \ + " .. note:: Defining this callback without a matching ``set`` one will make " \ + "the property read-only (even if ``READ_ONLY`` option is not set)." \ + "\n" \ " :type get: Callable[[:class:`bpy.types.bpy_struct`], " ty "]\n" #define BPY_PROPDEF_SET_DOC(ty) \ - " :arg set: Function to be called when this value is 'written',\n" \ + " :arg set: Function to be called when this value is 'written', and the default,\n" \ + " system-defined storage is not used for this property.\n" \ " This function must take 2 values (self, value) and return None.\n" \ + "\n" \ + " .. note:: Defining this callback without a matching ``get`` one is invalid." \ + "\n" \ " :type set: Callable[[:class:`bpy.types.bpy_struct`, " ty "], None]\n" +#define BPY_PROPDEF_GET_TRANSFORM_DOC(ty) \ + " :arg get_transform: Function to be called when this value is 'read',\n" \ + " if some additional processing must be performed on the stored value.\n" \ + " This function must take three arguments (self, the stored value,\n" \ + " and a boolean indicating if the property is currently set),\n" \ + " and return the final, transformed value of the property.\n" \ + "\n" \ + " .. note:: The callback is responsible to ensure that value limits of the property " \ + "(min/max, length...) are respected. Otherwise a ValueError exception is raised.\n" \ + "\n" \ + " :type get_transform: Callable[[:class:`bpy.types.bpy_struct`, " ty ", bool], " ty "]\n" + +#define BPY_PROPDEF_SET_TRANSFORM_DOC(ty) \ + " :arg set_transform: Function to be called when this value is 'written',\n" \ + " if some additional processing must be performed on the given value before storing it.\n" \ + " This function must take four arguments (self, the given value to store,\n" \ + " the currently stored value ('raw' value, without any ``get_transform`` applied to " \ + "it),\n" \ + " and a boolean indicating if the property is currently set),\n" \ + " and return the final, transformed value of the property.\n" \ + "\n" \ + " .. note:: The callback is responsible to ensure that value limits (min/max, " \ + "length...) are respected. Otherwise a ValueError exception is raised.\n" \ + "\n" \ + " :type set_transform: " \ + "Callable[[:class:`bpy.types.bpy_struct`, " ty ", " ty ", bool], " ty "]\n" + #define BPY_PROPDEF_SEARCH_DOC \ " :arg search: Function to be called to show candidates for this string (shown in the UI).\n" \ " This function must take 3 values (self, context, edit_text)\n" \ @@ -2512,12 +3533,15 @@ PyDoc_STRVAR( "subtype='NONE', " "update=None, " "get=None, " - "set=None)\n" + "set=None, " + "get_transform=None, " + "set_transform=None)\n" "\n" " Returns a new boolean property definition.\n" "\n" BPY_PROPDEF_NAME_DOC BPY_PROPDEF_DESC_DOC BPY_PROPDEF_CTXT_DOC BPY_PROPDEF_OPTIONS_DOC BPY_PROPDEF_OPTIONS_OVERRIDE_DOC BPY_PROPDEF_TAGS_DOC BPY_PROPDEF_SUBTYPE_NUMBER_DOC - BPY_PROPDEF_UPDATE_DOC BPY_PROPDEF_GET_DOC("bool") BPY_PROPDEF_SET_DOC("bool")); + BPY_PROPDEF_UPDATE_DOC BPY_PROPDEF_GET_DOC("bool") BPY_PROPDEF_SET_DOC("bool") + BPY_PROPDEF_GET_TRANSFORM_DOC("bool") BPY_PROPDEF_SET_TRANSFORM_DOC("bool")); static PyObject *BPy_BoolProperty(PyObject *self, PyObject *args, PyObject *kw) { StructRNA *srna; @@ -2555,6 +3579,9 @@ static PyObject *BPy_BoolProperty(PyObject *self, PyObject *args, PyObject *kw) PyObject *get_fn = nullptr; PyObject *set_fn = nullptr; + PyObject *get_transform_fn = nullptr; + PyObject *set_transform_fn = nullptr; + static const char *_keywords[] = { "attr", "name", @@ -2568,6 +3595,8 @@ static PyObject *BPy_BoolProperty(PyObject *self, PyObject *args, PyObject *kw) "update", "get", "set", + "get_transform", + "set_transform", nullptr, }; static _PyArg_Parser _parser = { @@ -2585,6 +3614,8 @@ static PyObject *BPy_BoolProperty(PyObject *self, PyObject *args, PyObject *kw) "O" /* `update` */ "O" /* `get` */ "O" /* `set` */ + "O" /* `get_transform` */ + "O" /* `set_transform` */ ":BoolProperty", _keywords, nullptr, @@ -2609,7 +3640,9 @@ static PyObject *BPy_BoolProperty(PyObject *self, PyObject *args, PyObject *kw) &subtype_enum, &update_fn, &get_fn, - &set_fn)) + &set_fn, + &get_transform_fn, + &set_transform_fn)) { return nullptr; } @@ -2623,6 +3656,12 @@ static PyObject *BPy_BoolProperty(PyObject *self, PyObject *args, PyObject *kw) if (bpy_prop_callback_check(set_fn, "set", 2) == -1) { return nullptr; } + if (bpy_prop_callback_check(get_transform_fn, "get_transform", 3) == -1) { + return nullptr; + } + if (bpy_prop_callback_check(set_transform_fn, "set_transform", 4) == -1) { + return nullptr; + } if (id_data.prop_free_handle != nullptr) { RNA_def_property_free_identifier_deferred_finish(srna, id_data.prop_free_handle); @@ -2645,7 +3684,10 @@ static PyObject *BPy_BoolProperty(PyObject *self, PyObject *args, PyObject *kw) bpy_prop_assign_flag_override(prop, override_enum.value); } bpy_prop_callback_assign_update(prop, update_fn); - bpy_prop_callback_assign_boolean(prop, get_fn, set_fn); + if (!bpy_prop_callback_assign_boolean(prop, get_fn, set_fn, get_transform_fn, set_transform_fn)) + { + return nullptr; + } RNA_def_property_duplicate_pointers(srna, prop); Py_RETURN_NONE; @@ -2667,7 +3709,9 @@ PyDoc_STRVAR( "size=3, " "update=None, " "get=None, " - "set=None)\n" + "set=None, " + "get_transform=None, " + "set_transform=None)\n" "\n" " Returns a new vector boolean property definition.\n" "\n" BPY_PROPDEF_NAME_DOC BPY_PROPDEF_DESC_DOC BPY_PROPDEF_CTXT_DOC @@ -2675,7 +3719,9 @@ PyDoc_STRVAR( " :type default: Sequence[bool]\n" BPY_PROPDEF_OPTIONS_DOC BPY_PROPDEF_OPTIONS_OVERRIDE_DOC BPY_PROPDEF_TAGS_DOC BPY_PROPDEF_SUBTYPE_NUMBER_ARRAY_DOC BPY_PROPDEF_VECSIZE_DOC BPY_PROPDEF_UPDATE_DOC BPY_PROPDEF_GET_DOC("Sequence[bool]") - BPY_PROPDEF_SET_DOC("tuple[bool, ...]")); + BPY_PROPDEF_SET_DOC("tuple[bool, ...]") + BPY_PROPDEF_GET_TRANSFORM_DOC("Sequence[bool]") + BPY_PROPDEF_SET_TRANSFORM_DOC("Sequence[bool]")); static PyObject *BPy_BoolVectorProperty(PyObject *self, PyObject *args, PyObject *kw) { StructRNA *srna; @@ -2717,6 +3763,8 @@ static PyObject *BPy_BoolVectorProperty(PyObject *self, PyObject *args, PyObject PyObject *update_fn = nullptr; PyObject *get_fn = nullptr; PyObject *set_fn = nullptr; + PyObject *get_transform_fn = nullptr; + PyObject *set_transform_fn = nullptr; static const char *_keywords[] = { "attr", @@ -2732,6 +3780,8 @@ static PyObject *BPy_BoolVectorProperty(PyObject *self, PyObject *args, PyObject "update", "get", "set", + "get_transform", + "set_transform", nullptr, }; static _PyArg_Parser _parser = { @@ -2750,6 +3800,8 @@ static PyObject *BPy_BoolVectorProperty(PyObject *self, PyObject *args, PyObject "O" /* `update` */ "O" /* `get` */ "O" /* `set` */ + "O" /* `get_transform` */ + "O" /* `set_transform` */ ":BoolVectorProperty", _keywords, nullptr, @@ -2775,7 +3827,9 @@ static PyObject *BPy_BoolVectorProperty(PyObject *self, PyObject *args, PyObject &array_len_info, &update_fn, &get_fn, - &set_fn)) + &set_fn, + &get_transform_fn, + &set_transform_fn)) { return nullptr; } @@ -2802,6 +3856,12 @@ static PyObject *BPy_BoolVectorProperty(PyObject *self, PyObject *args, PyObject if (bpy_prop_callback_check(set_fn, "set", 2) == -1) { return nullptr; } + if (bpy_prop_callback_check(get_transform_fn, "get_transform", 3) == -1) { + return nullptr; + } + if (bpy_prop_callback_check(set_transform_fn, "set_transform", 4) == -1) { + return nullptr; + } if (id_data.prop_free_handle != nullptr) { RNA_def_property_free_identifier_deferred_finish(srna, id_data.prop_free_handle); @@ -2836,7 +3896,7 @@ static PyObject *BPy_BoolVectorProperty(PyObject *self, PyObject *args, PyObject bpy_prop_assign_flag_override(prop, override_enum.value); } bpy_prop_callback_assign_update(prop, update_fn); - bpy_prop_callback_assign_boolean_array(prop, get_fn, set_fn); + bpy_prop_callback_assign_boolean_array(prop, get_fn, set_fn, get_transform_fn, set_transform_fn); RNA_def_property_duplicate_pointers(srna, prop); Py_RETURN_NONE; @@ -2860,14 +3920,17 @@ PyDoc_STRVAR( "subtype='NONE', " "update=None, " "get=None, " - "set=None)\n" + "set=None, " + "get_transform=None, " + "set_transform=None)\n" "\n" " Returns a new int property definition.\n" "\n" BPY_PROPDEF_NAME_DOC BPY_PROPDEF_DESC_DOC BPY_PROPDEF_CTXT_DOC BPY_PROPDEF_NUM_MINMAX_DOC("int") BPY_PROPDEF_NUM_SOFT_MINMAX_DOC("int") BPY_PROPDEF_INT_STEP_DOC BPY_PROPDEF_OPTIONS_DOC BPY_PROPDEF_OPTIONS_OVERRIDE_DOC BPY_PROPDEF_TAGS_DOC BPY_PROPDEF_SUBTYPE_NUMBER_DOC BPY_PROPDEF_UPDATE_DOC - BPY_PROPDEF_GET_DOC("int") BPY_PROPDEF_SET_DOC("int")); + BPY_PROPDEF_GET_DOC("int") BPY_PROPDEF_SET_DOC("int") + BPY_PROPDEF_GET_TRANSFORM_DOC("int") BPY_PROPDEF_SET_TRANSFORM_DOC("int")); static PyObject *BPy_IntProperty(PyObject *self, PyObject *args, PyObject *kw) { StructRNA *srna; @@ -2907,26 +3970,15 @@ static PyObject *BPy_IntProperty(PyObject *self, PyObject *args, PyObject *kw) PyObject *update_fn = nullptr; PyObject *get_fn = nullptr; PyObject *set_fn = nullptr; + PyObject *get_transform_fn = nullptr; + PyObject *set_transform_fn = nullptr; static const char *_keywords[] = { - "attr", - "name", - "description", - "translation_context", - "default", - "min", - "max", - "soft_min", - "soft_max", - "step", - "options", - "override", - "tags", - "subtype", - "update", - "get", - "set", - nullptr, + "attr", "name", "description", "translation_context", + "default", "min", "max", "soft_min", + "soft_max", "step", "options", "override", + "tags", "subtype", "update", "get", + "set", "get_transform", "set_transform", nullptr, }; static _PyArg_Parser _parser = { PY_ARG_PARSER_HEAD_COMPAT() @@ -2948,6 +4000,8 @@ static PyObject *BPy_IntProperty(PyObject *self, PyObject *args, PyObject *kw) "O" /* `update` */ "O" /* `get` */ "O" /* `set` */ + "O" /* `get_transform` */ + "O" /* `set_transform` */ ":IntProperty", _keywords, nullptr, @@ -2976,7 +4030,9 @@ static PyObject *BPy_IntProperty(PyObject *self, PyObject *args, PyObject *kw) &subtype_enum, &update_fn, &get_fn, - &set_fn)) + &set_fn, + &get_transform_fn, + &set_transform_fn)) { return nullptr; } @@ -2990,6 +4046,12 @@ static PyObject *BPy_IntProperty(PyObject *self, PyObject *args, PyObject *kw) if (bpy_prop_callback_check(set_fn, "set", 2) == -1) { return nullptr; } + if (bpy_prop_callback_check(get_transform_fn, "get_transform", 3) == -1) { + return nullptr; + } + if (bpy_prop_callback_check(set_transform_fn, "set_transform", 4) == -1) { + return nullptr; + } if (id_data.prop_free_handle != nullptr) { RNA_def_property_free_identifier_deferred_finish(srna, id_data.prop_free_handle); @@ -3014,7 +4076,7 @@ static PyObject *BPy_IntProperty(PyObject *self, PyObject *args, PyObject *kw) bpy_prop_assign_flag_override(prop, override_enum.value); } bpy_prop_callback_assign_update(prop, update_fn); - bpy_prop_callback_assign_int(prop, get_fn, set_fn); + bpy_prop_callback_assign_int(prop, get_fn, set_fn, get_transform_fn, set_transform_fn); RNA_def_property_duplicate_pointers(srna, prop); Py_RETURN_NONE; @@ -3039,7 +4101,9 @@ PyDoc_STRVAR( "size=3, " "update=None, " "get=None, " - "set=None)\n" + "set=None, " + "get_transform=None, " + "set_transform=None)\n" "\n" " Returns a new vector int property definition.\n" "\n" BPY_PROPDEF_NAME_DOC BPY_PROPDEF_DESC_DOC BPY_PROPDEF_CTXT_DOC @@ -3049,7 +4113,9 @@ PyDoc_STRVAR( BPY_PROPDEF_INT_STEP_DOC BPY_PROPDEF_OPTIONS_DOC BPY_PROPDEF_OPTIONS_OVERRIDE_DOC BPY_PROPDEF_TAGS_DOC BPY_PROPDEF_SUBTYPE_NUMBER_ARRAY_DOC BPY_PROPDEF_VECSIZE_DOC BPY_PROPDEF_UPDATE_DOC BPY_PROPDEF_GET_DOC("Sequence[int]") - BPY_PROPDEF_SET_DOC("tuple[int, ...]")); + BPY_PROPDEF_SET_DOC("tuple[int, ...]") + BPY_PROPDEF_GET_TRANSFORM_DOC("Sequence[int]") + BPY_PROPDEF_SET_TRANSFORM_DOC("Sequence[int]")); static PyObject *BPy_IntVectorProperty(PyObject *self, PyObject *args, PyObject *kw) { StructRNA *srna; @@ -3093,13 +4159,16 @@ static PyObject *BPy_IntVectorProperty(PyObject *self, PyObject *args, PyObject PyObject *update_fn = nullptr; PyObject *get_fn = nullptr; PyObject *set_fn = nullptr; + PyObject *get_transform_fn = nullptr; + PyObject *set_transform_fn = nullptr; static const char *_keywords[] = { - "attr", "name", "description", "translation_context", - "default", "min", "max", "soft_min", - "soft_max", "step", "options", "override", - "tags", "subtype", "size", "update", - "get", "set", nullptr, + "attr", "name", "description", "translation_context", + "default", "min", "max", "soft_min", + "soft_max", "step", "options", "override", + "tags", "subtype", "size", "update", + "get", "set", "get_transform", "set_transform", + nullptr, }; static _PyArg_Parser _parser = { PY_ARG_PARSER_HEAD_COMPAT() @@ -3122,6 +4191,8 @@ static PyObject *BPy_IntVectorProperty(PyObject *self, PyObject *args, PyObject "O" /* `update` */ "O" /* `get` */ "O" /* `set` */ + "O" /* `get_transform` */ + "O" /* `set_transform` */ ":IntVectorProperty", _keywords, nullptr, @@ -3152,7 +4223,9 @@ static PyObject *BPy_IntVectorProperty(PyObject *self, PyObject *args, PyObject &array_len_info, &update_fn, &get_fn, - &set_fn)) + &set_fn, + &get_transform_fn, + &set_transform_fn)) { return nullptr; } @@ -3179,6 +4252,12 @@ static PyObject *BPy_IntVectorProperty(PyObject *self, PyObject *args, PyObject if (bpy_prop_callback_check(set_fn, "set", 2) == -1) { return nullptr; } + if (bpy_prop_callback_check(get_transform_fn, "get_transform", 3) == -1) { + return nullptr; + } + if (bpy_prop_callback_check(set_transform_fn, "set_transform", 4) == -1) { + return nullptr; + } if (id_data.prop_free_handle != nullptr) { RNA_def_property_free_identifier_deferred_finish(srna, id_data.prop_free_handle); @@ -3215,7 +4294,7 @@ static PyObject *BPy_IntVectorProperty(PyObject *self, PyObject *args, PyObject bpy_prop_assign_flag_override(prop, override_enum.value); } bpy_prop_callback_assign_update(prop, update_fn); - bpy_prop_callback_assign_int_array(prop, get_fn, set_fn); + bpy_prop_callback_assign_int_array(prop, get_fn, set_fn, get_transform_fn, set_transform_fn); RNA_def_property_duplicate_pointers(srna, prop); Py_RETURN_NONE; @@ -3241,7 +4320,9 @@ PyDoc_STRVAR( "unit='NONE', " "update=None, " "get=None, " - "set=None)\n" + "set=None, " + "get_transform=None, " + "set_transform=None)\n" "\n" " Returns a new float (single precision) property definition.\n" "\n" BPY_PROPDEF_NAME_DOC BPY_PROPDEF_DESC_DOC BPY_PROPDEF_CTXT_DOC BPY_PROPDEF_NUM_MINMAX_DOC( @@ -3249,7 +4330,8 @@ PyDoc_STRVAR( BPY_PROPDEF_FLOAT_STEP_DOC BPY_PROPDEF_FLOAT_PREC_DOC BPY_PROPDEF_OPTIONS_DOC BPY_PROPDEF_OPTIONS_OVERRIDE_DOC BPY_PROPDEF_TAGS_DOC BPY_PROPDEF_SUBTYPE_NUMBER_DOC BPY_PROPDEF_UNIT_DOC BPY_PROPDEF_UPDATE_DOC BPY_PROPDEF_GET_DOC("float") - BPY_PROPDEF_SET_DOC("float")); + BPY_PROPDEF_SET_DOC("float") BPY_PROPDEF_GET_TRANSFORM_DOC("float") + BPY_PROPDEF_SET_TRANSFORM_DOC("float")); static PyObject *BPy_FloatProperty(PyObject *self, PyObject *args, PyObject *kw) { StructRNA *srna; @@ -3294,13 +4376,16 @@ static PyObject *BPy_FloatProperty(PyObject *self, PyObject *args, PyObject *kw) PyObject *update_fn = nullptr; PyObject *get_fn = nullptr; PyObject *set_fn = nullptr; + PyObject *get_transform_fn = nullptr; + PyObject *set_transform_fn = nullptr; static const char *_keywords[] = { - "attr", "name", "description", "translation_context", - "default", "min", "max", "soft_min", - "soft_max", "step", "precision", "options", - "override", "tags", "subtype", "unit", - "update", "get", "set", nullptr, + "attr", "name", "description", "translation_context", + "default", "min", "max", "soft_min", + "soft_max", "step", "precision", "options", + "override", "tags", "subtype", "unit", + "update", "get", "set", "get_transform", + "set_transform", nullptr, }; static _PyArg_Parser _parser = { PY_ARG_PARSER_HEAD_COMPAT() @@ -3324,6 +4409,8 @@ static PyObject *BPy_FloatProperty(PyObject *self, PyObject *args, PyObject *kw) "O" /* `update` */ "O" /* `get` */ "O" /* `set` */ + "O" /* `get_transform` */ + "O" /* `set_transform` */ ":FloatProperty", _keywords, nullptr, @@ -3355,7 +4442,9 @@ static PyObject *BPy_FloatProperty(PyObject *self, PyObject *args, PyObject *kw) &unit_enum, &update_fn, &get_fn, - &set_fn)) + &set_fn, + &get_transform_fn, + &set_transform_fn)) { return nullptr; } @@ -3369,6 +4458,12 @@ static PyObject *BPy_FloatProperty(PyObject *self, PyObject *args, PyObject *kw) if (bpy_prop_callback_check(set_fn, "set", 2) == -1) { return nullptr; } + if (bpy_prop_callback_check(get_transform_fn, "get_transform", 3) == -1) { + return nullptr; + } + if (bpy_prop_callback_check(set_transform_fn, "set_transform", 4) == -1) { + return nullptr; + } if (id_data.prop_free_handle != nullptr) { RNA_def_property_free_identifier_deferred_finish(srna, id_data.prop_free_handle); @@ -3394,7 +4489,7 @@ static PyObject *BPy_FloatProperty(PyObject *self, PyObject *args, PyObject *kw) bpy_prop_assign_flag_override(prop, override_enum.value); } bpy_prop_callback_assign_update(prop, update_fn); - bpy_prop_callback_assign_float(prop, get_fn, set_fn); + bpy_prop_callback_assign_float(prop, get_fn, set_fn, get_transform_fn, set_transform_fn); RNA_def_property_duplicate_pointers(srna, prop); Py_RETURN_NONE; @@ -3432,7 +4527,9 @@ PyDoc_STRVAR( BPY_PROPDEF_FLOAT_STEP_DOC BPY_PROPDEF_FLOAT_PREC_DOC BPY_PROPDEF_SUBTYPE_NUMBER_ARRAY_DOC BPY_PROPDEF_UNIT_DOC BPY_PROPDEF_VECSIZE_DOC BPY_PROPDEF_UPDATE_DOC BPY_PROPDEF_GET_DOC("Sequence[float]") - BPY_PROPDEF_SET_DOC("tuple[float, ...]")); + BPY_PROPDEF_SET_DOC("tuple[float, ...]") + BPY_PROPDEF_GET_TRANSFORM_DOC("Sequence[float]") + BPY_PROPDEF_SET_TRANSFORM_DOC("Sequence[float]")); static PyObject *BPy_FloatVectorProperty(PyObject *self, PyObject *args, PyObject *kw) { StructRNA *srna; @@ -3481,14 +4578,16 @@ static PyObject *BPy_FloatVectorProperty(PyObject *self, PyObject *args, PyObjec PyObject *update_fn = nullptr; PyObject *get_fn = nullptr; PyObject *set_fn = nullptr; + PyObject *get_transform_fn = nullptr; + PyObject *set_transform_fn = nullptr; static const char *_keywords[] = { - "attr", "name", "description", "translation_context", - "default", "min", "max", "soft_min", - "soft_max", "step", "precision", "options", - "override", "tags", "subtype", "unit", - "size", "update", "get", "set", - nullptr, + "attr", "name", "description", "translation_context", + "default", "min", "max", "soft_min", + "soft_max", "step", "precision", "options", + "override", "tags", "subtype", "unit", + "size", "update", "get", "set", + "get_transform", "set_transform", nullptr, }; static _PyArg_Parser _parser = { PY_ARG_PARSER_HEAD_COMPAT() @@ -3513,6 +4612,8 @@ static PyObject *BPy_FloatVectorProperty(PyObject *self, PyObject *args, PyObjec "O" /* `update` */ "O" /* `get` */ "O" /* `set` */ + "O" /* `get_transform` */ + "O" /* `set_transform` */ ":FloatVectorProperty", _keywords, nullptr, @@ -3546,7 +4647,9 @@ static PyObject *BPy_FloatVectorProperty(PyObject *self, PyObject *args, PyObjec &array_len_info, &update_fn, &get_fn, - &set_fn)) + &set_fn, + &get_transform_fn, + &set_transform_fn)) { return nullptr; } @@ -3576,6 +4679,12 @@ static PyObject *BPy_FloatVectorProperty(PyObject *self, PyObject *args, PyObjec if (bpy_prop_callback_check(set_fn, "set", 2) == -1) { return nullptr; } + if (bpy_prop_callback_check(get_transform_fn, "get_transform", 3) == -1) { + return nullptr; + } + if (bpy_prop_callback_check(set_transform_fn, "set_transform", 4) == -1) { + return nullptr; + } if (id_data.prop_free_handle != nullptr) { RNA_def_property_free_identifier_deferred_finish(srna, id_data.prop_free_handle); @@ -3613,7 +4722,7 @@ static PyObject *BPy_FloatVectorProperty(PyObject *self, PyObject *args, PyObjec bpy_prop_assign_flag_override(prop, override_enum.value); } bpy_prop_callback_assign_update(prop, update_fn); - bpy_prop_callback_assign_float_array(prop, get_fn, set_fn); + bpy_prop_callback_assign_float_array(prop, get_fn, set_fn, get_transform_fn, set_transform_fn); RNA_def_property_duplicate_pointers(srna, prop); Py_RETURN_NONE; @@ -3636,6 +4745,8 @@ PyDoc_STRVAR( "update=None, " "get=None, " "set=None, " + "get_transform=None, " + "set_transform=None, " "search=None, " "search_options={'SUGGESTION'})\n" "\n" @@ -3646,7 +4757,9 @@ PyDoc_STRVAR( " :arg maxlen: maximum length of the string.\n" " :type maxlen: int\n" BPY_PROPDEF_OPTIONS_DOC BPY_PROPDEF_OPTIONS_OVERRIDE_DOC BPY_PROPDEF_TAGS_DOC BPY_PROPDEF_SUBTYPE_STRING_DOC BPY_PROPDEF_UPDATE_DOC - BPY_PROPDEF_GET_DOC("str") BPY_PROPDEF_SET_DOC("str") BPY_PROPDEF_SEARCH_DOC); + BPY_PROPDEF_GET_DOC("str") BPY_PROPDEF_SET_DOC("str") + BPY_PROPDEF_GET_TRANSFORM_DOC("str") BPY_PROPDEF_SET_TRANSFORM_DOC("str") + BPY_PROPDEF_SEARCH_DOC); static PyObject *BPy_StringProperty(PyObject *self, PyObject *args, PyObject *kw) { StructRNA *srna; @@ -3684,6 +4797,8 @@ static PyObject *BPy_StringProperty(PyObject *self, PyObject *args, PyObject *kw PyObject *update_fn = nullptr; PyObject *get_fn = nullptr; PyObject *set_fn = nullptr; + PyObject *get_transform_fn = nullptr; + PyObject *set_transform_fn = nullptr; PyObject *search_fn = nullptr; BPy_EnumProperty_Parse search_options_enum{}; search_options_enum.items = rna_enum_property_string_search_flag_items; @@ -3703,6 +4818,8 @@ static PyObject *BPy_StringProperty(PyObject *self, PyObject *args, PyObject *kw "update", "get", "set", + "get_transform", + "set_transform", "search", "search_options", nullptr, @@ -3723,6 +4840,8 @@ static PyObject *BPy_StringProperty(PyObject *self, PyObject *args, PyObject *kw "O" /* `update` */ "O" /* `get` */ "O" /* `set` */ + "O" /* `get_transform` */ + "O" /* `set_transform` */ "O" /* `search` */ "O&" /* `search_options` */ ":StringProperty", @@ -3750,6 +4869,8 @@ static PyObject *BPy_StringProperty(PyObject *self, PyObject *args, PyObject *kw &update_fn, &get_fn, &set_fn, + &get_transform_fn, + &set_transform_fn, &search_fn, pyrna_enum_bitfield_parse_set, &search_options_enum)) @@ -3766,6 +4887,12 @@ static PyObject *BPy_StringProperty(PyObject *self, PyObject *args, PyObject *kw if (bpy_prop_callback_check(set_fn, "set", 2) == -1) { return nullptr; } + if (bpy_prop_callback_check(get_transform_fn, "get_transform", 3) == -1) { + return nullptr; + } + if (bpy_prop_callback_check(set_transform_fn, "set_transform", 4) == -1) { + return nullptr; + } if (bpy_prop_callback_check(search_fn, "search", 3) == -1) { return nullptr; } @@ -3797,8 +4924,13 @@ static PyObject *BPy_StringProperty(PyObject *self, PyObject *args, PyObject *kw bpy_prop_assign_flag_override(prop, override_enum.value); } bpy_prop_callback_assign_update(prop, update_fn); - bpy_prop_callback_assign_string( - prop, get_fn, set_fn, search_fn, eStringPropertySearchFlag(search_options_enum.value)); + bpy_prop_callback_assign_string(prop, + get_fn, + set_fn, + get_transform_fn, + set_transform_fn, + search_fn, + eStringPropertySearchFlag(search_options_enum.value)); RNA_def_property_duplicate_pointers(srna, prop); Py_RETURN_NONE; @@ -3819,7 +4951,9 @@ PyDoc_STRVAR( "tags=set(), " "update=None, " "get=None, " - "set=None)\n" + "set=None, " + "get_transform=None, " + "set_transform=None)\n" "\n" " Returns a new enumerator property definition.\n" "\n" @@ -3876,7 +5010,8 @@ PyDoc_STRVAR( " (i.e. if a callback function is given as *items* parameter).\n" " :type default: str | int | set[str]\n" BPY_PROPDEF_OPTIONS_ENUM_DOC BPY_PROPDEF_OPTIONS_OVERRIDE_DOC BPY_PROPDEF_TAGS_DOC BPY_PROPDEF_UPDATE_DOC - BPY_PROPDEF_GET_DOC("int") BPY_PROPDEF_SET_DOC("int")); + BPY_PROPDEF_GET_DOC("int") BPY_PROPDEF_SET_DOC("int") + BPY_PROPDEF_GET_TRANSFORM_DOC("int") BPY_PROPDEF_SET_TRANSFORM_DOC("int")); static PyObject *BPy_EnumProperty(PyObject *self, PyObject *args, PyObject *kw) { StructRNA *srna; @@ -3914,6 +5049,8 @@ static PyObject *BPy_EnumProperty(PyObject *self, PyObject *args, PyObject *kw) PyObject *update_fn = nullptr; PyObject *get_fn = nullptr; PyObject *set_fn = nullptr; + PyObject *get_transform_fn = nullptr; + PyObject *set_transform_fn = nullptr; static const char *_keywords[] = { "attr", @@ -3928,6 +5065,8 @@ static PyObject *BPy_EnumProperty(PyObject *self, PyObject *args, PyObject *kw) "update", "get", "set", + "get_transform", + "set_transform", nullptr, }; static _PyArg_Parser _parser = { @@ -3945,6 +5084,8 @@ static PyObject *BPy_EnumProperty(PyObject *self, PyObject *args, PyObject *kw) "O" /* `update` */ "O" /* `get` */ "O" /* `set` */ + "O" /* `get_transform` */ + "O" /* `set_transform` */ ":EnumProperty", _keywords, nullptr, @@ -3967,7 +5108,9 @@ static PyObject *BPy_EnumProperty(PyObject *self, PyObject *args, PyObject *kw) &tags_enum, &update_fn, &get_fn, - &set_fn)) + &set_fn, + &get_transform_fn, + &set_transform_fn)) { return nullptr; } @@ -3981,6 +5124,12 @@ static PyObject *BPy_EnumProperty(PyObject *self, PyObject *args, PyObject *kw) if (bpy_prop_callback_check(set_fn, "set", 2) == -1) { return nullptr; } + if (bpy_prop_callback_check(get_transform_fn, "get_transform", 3) == -1) { + return nullptr; + } + if (bpy_prop_callback_check(set_transform_fn, "set_transform", 4) == -1) { + return nullptr; + } if (default_py == Py_None) { /* This allows to get same behavior when explicitly passing None as default value, @@ -4056,7 +5205,8 @@ static PyObject *BPy_EnumProperty(PyObject *self, PyObject *args, PyObject *kw) bpy_prop_assign_flag_override(prop, override_enum.value); } bpy_prop_callback_assign_update(prop, update_fn); - bpy_prop_callback_assign_enum(prop, get_fn, set_fn, (is_itemf ? items : nullptr)); + bpy_prop_callback_assign_enum( + prop, get_fn, set_fn, (is_itemf ? items : nullptr), get_transform_fn, set_transform_fn); RNA_def_property_duplicate_pointers(srna, prop); if (is_itemf == false) { diff --git a/tests/performance/tests/bpy_rna.py b/tests/performance/tests/bpy_rna.py index cc6184736e4..fe5ca185077 100644 --- a/tests/performance/tests/bpy_rna.py +++ b/tests/performance/tests/bpy_rna.py @@ -69,18 +69,28 @@ def _run_runtime_group_register_access(args): do_register = args.get("do_register", False) do_access = args.get("do_access", False) do_get_set = args.get("do_get_set", False) + do_transform = args.get("do_transform", False) property_type = args.get("property_type", 'IntProperty') property_definition_cb = getattr(bpy.props, property_type) + assert (not (do_get_set and do_transform)) + # Define basic 'transform' callbacks to test setting value, # default to just setting untransformed value for 'unknown'/undefined property types. - property_transform_cb = { + property_transform_set_cb = { 'BoolProperty': lambda v: not v, 'IntProperty': lambda v: v + 1, 'FloatVectorProperty': lambda v: [v[2] + 1.0, v[0], v[1]], 'StringProperty': lambda v: ("B" if (v and v[0] == "A") else "A") + v[1:], }.get(property_type, lambda v: v) + property_transform_get_cb = { + 'BoolProperty': lambda v: not v, + 'IntProperty': lambda v: v - 1, + 'FloatVectorProperty': lambda v: [v[0], v[1], v[2]], + 'StringProperty': lambda v: ("B" if (v and v[0] == "A") else "A") + v[1:], + }.get(property_type, lambda v: v) + if do_get_set: class DummyGroup(bpy.types.PropertyGroup): dummy_prop: property_definition_cb( @@ -95,6 +105,12 @@ def _run_runtime_group_register_access(args): "dummy_prop", val), ) + elif do_transform: + class DummyGroup(bpy.types.PropertyGroup): + dummy_prop: property_definition_cb( + get_transform=lambda self, curr_v, is_set: property_transform_get_cb(curr_v), + set_transform=lambda self, curr_v, new_v, is_set: property_transform_set_cb(curr_v), + ) else: class DummyGroup(bpy.types.PropertyGroup): dummy_prop: property_definition_cb() @@ -115,9 +131,14 @@ def _run_runtime_group_register_access(args): bpy.utils.register_class(DummyGroup) bpy.types.Scene.dummy_group = bpy.props.PointerProperty(type=DummyGroup) - for i in range(iterations): - v = sce.dummy_group.dummy_prop - sce.dummy_group.dummy_prop = property_transform_cb(v) + if do_transform: + for i in range(iterations): + v = sce.dummy_group.dummy_prop + sce.dummy_group.dummy_prop = v + else: + for i in range(iterations): + v = sce.dummy_group.dummy_prop + sce.dummy_group.dummy_prop = property_transform_set_cb(v) del bpy.types.Scene.dummy_group bpy.utils.unregister_class(DummyGroup) @@ -165,4 +186,10 @@ def generate(env): {"do_access": True, "do_get_set": True, "property_type": 'FloatVectorProperty'}), BPYRNATest("Py-Defined StringProperty Custom Get/Set Access", _run_runtime_group_register_access, 10 * 1000, {"do_access": True, "do_get_set": True, "property_type": 'StringProperty'}), + BPYRNATest("Py-Defined BoolProperty Custom Transform Access", _run_runtime_group_register_access, 1000 * 1000, + {"do_access": True, "do_transform": True, "property_type": 'BoolProperty'}), + BPYRNATest("Py-Defined FloatVectorProperty Custom Transform Access", _run_runtime_group_register_access, 1000 * 1000, + {"do_access": True, "do_transform": True, "property_type": 'FloatVectorProperty'}), + BPYRNATest("Py-Defined StringProperty Custom Transform Access", _run_runtime_group_register_access, 1000 * 1000, + {"do_access": True, "do_transform": True, "property_type": 'StringProperty'}), ] diff --git a/tests/python/bl_pyapi_idprop.py b/tests/python/bl_pyapi_idprop.py index c14a4f001b9..b6d876cc883 100644 --- a/tests/python/bl_pyapi_idprop.py +++ b/tests/python/bl_pyapi_idprop.py @@ -7,6 +7,8 @@ import bpy import rna_prop_ui import idprop +import io +import sys import unittest from array import array @@ -266,6 +268,12 @@ class TestIdPropertyDynamicRNA(TestHelper, unittest.TestCase): string_prop: bpy.props.StringProperty() int_prop: bpy.props.IntProperty() float_array_prop: bpy.props.FloatVectorProperty(size=[3]) + # Basic get/set transform. + string_prop_transform: bpy.props.StringProperty( + default="test", + maxlen=10, + get_transform=lambda self, storage_val, is_set: storage_val + "!!", + set_transform=lambda self, new_val, storage_val, is_set: storage_val + "!!" + new_val) def setUp(self): super().setUp() @@ -348,6 +356,40 @@ class TestIdPropertyDynamicRNA(TestHelper, unittest.TestCase): with self.assertRaises(TypeError): self.id.bl_system_properties_get()['dynrna_prop']['float_array_prop'] = [1.0, 10.0, 100.0, 0.1] + def test_get_set_transform(self): + self.assertEqual(len(self.id.dynrna_prop.string_prop_transform), 6) + self.assertEqual(self.id.dynrna_prop.string_prop_transform, "test!!") + # Default value only, was not yet set. + self.assertFalse('string_prop_transform' in self.id.bl_system_properties_get()['dynrna_prop']) + + self.id.dynrna_prop.string_prop_transform = "-" + self.assertEqual(self.id.dynrna_prop.string_prop_transform, "test!!-!!") + self.assertEqual(self.id.bl_system_properties_get()['dynrna_prop']['string_prop_transform'], "test!!-") + + # Raw-set exactly maxlen - 1 char. + self.id.bl_system_properties_get()['dynrna_prop']['string_prop_transform'] = "test!!tes" + # get_transform will produce an 11-char results, which should trigger an error. + # These asserts are not raised currently, but only print in `stderr`... + # But the returned string is the 'storage' one, not the result from get_transform. + stderr, sys.stderr = sys.stderr, io.StringIO() + self.assertEqual(self.id.dynrna_prop.string_prop_transform, "test!!tes") + self.assertTrue("ValueError" in sys.stderr.getvalue() and + "10" in sys.stderr.getvalue() and "11" in sys.stderr.getvalue()) + sys.stderr.close() + sys.stderr = stderr + + # Raw-set back to default value. + self.id.bl_system_properties_get()['dynrna_prop']['string_prop_transform'] = "test" + # Now set_transform will return 12-char string, wich is also invalid and discarded. + stderr, sys.stderr = sys.stderr, io.StringIO() + self.id.dynrna_prop.string_prop_transform = "test!!" + self.assertTrue("ValueError" in sys.stderr.getvalue() and + "10" in sys.stderr.getvalue() and "12" in sys.stderr.getvalue()) + sys.stderr.close() + sys.stderr = stderr + self.assertEqual(self.id.bl_system_properties_get()['dynrna_prop']['string_prop_transform'], "test") + self.assertEqual(self.id.dynrna_prop.string_prop_transform, "test!!") + class TestIdPropertyGroupView(TestHelper, unittest.TestCase): diff --git a/tests/python/bl_pyapi_prop.py b/tests/python/bl_pyapi_prop.py index 2e4d040db59..3e8973cd00a 100644 --- a/tests/python/bl_pyapi_prop.py +++ b/tests/python/bl_pyapi_prop.py @@ -45,33 +45,70 @@ class TestPropNumerical(unittest.TestCase): id_type.test_float = FloatProperty(default=float(self.default_value)) self.test_bool_storage = bool(self.custom_value) + self.test_int_storage = int(self.custom_value) + self.test_float_storage = float(self.custom_value) - def set_(s, v): + def bool_set_(s, v): self.test_bool_storage = v + + def int_set_(s, v): + self.test_int_storage = v + + def float_set_(s, v): + self.test_float_storage = v + id_type.test_bool_getset = BoolProperty( default=bool(self.default_value), get=lambda s: self.test_bool_storage, - set=set_, + set=bool_set_, ) - - self.test_int_storage = int(self.custom_value) - - def set_(s, v): - self.test_int_storage = v id_type.test_int_getset = IntProperty( default=int(self.default_value), get=lambda s: self.test_int_storage, - set=set_, + set=int_set_, ) - - self.test_float_storage = float(self.custom_value) - - def set_(s, v): - self.test_float_storage = v id_type.test_float_getset = FloatProperty( default=float(self.default_value), get=lambda s: self.test_float_storage, - set=set_, + set=float_set_, + ) + + id_type.test_bool_transform = BoolProperty( + default=bool(self.default_value), + get_transform=lambda s, c_v, isset: c_v, + set_transform=lambda s, n_v, c_v, isset: n_v, + ) + id_type.test_int_transform = IntProperty( + default=int(self.default_value), + get_transform=lambda s, c_v, isset: c_v, + set_transform=lambda s, n_v, c_v, isset: n_v, + ) + id_type.test_float_transform = FloatProperty( + default=float(self.default_value), + get_transform=lambda s, c_v, isset: c_v, + set_transform=lambda s, n_v, c_v, isset: n_v, + ) + + id_type.test_bool_getset_transform = BoolProperty( + default=bool(self.default_value), + get=lambda s: self.test_bool_storage, + set=bool_set_, + get_transform=lambda s, c_v, isset: c_v, + set_transform=lambda s, n_v, c_v, isset: n_v, + ) + id_type.test_int_getset_transform = IntProperty( + default=int(self.default_value), + get=lambda s: self.test_int_storage, + set=int_set_, + get_transform=lambda s, c_v, isset: c_v, + set_transform=lambda s, n_v, c_v, isset: n_v, + ) + id_type.test_float_getset_transform = FloatProperty( + default=float(self.default_value), + get=lambda s: self.test_float_storage, + set=float_set_, + get_transform=lambda s, c_v, isset: c_v, + set_transform=lambda s, n_v, c_v, isset: n_v, ) def tearDown(self): @@ -83,6 +120,14 @@ class TestPropNumerical(unittest.TestCase): del id_type.test_int_getset del id_type.test_bool_getset + del id_type.test_float_transform + del id_type.test_int_transform + del id_type.test_bool_transform + + del id_type.test_float_getset_transform + del id_type.test_int_getset_transform + del id_type.test_bool_getset_transform + def do_test_access(self, prop_name, py_type, expected_value): v = getattr(id_inst, prop_name) self.assertIsInstance(v, py_type) @@ -110,6 +155,24 @@ class TestPropNumerical(unittest.TestCase): def test_access_float_getset(self): self.do_test_access("test_float_getset", float, float(self.custom_value)) + def test_access_bool_transform(self): + self.do_test_access("test_bool_transform", bool, bool(self.default_value)) + + def test_access_int_transform(self): + self.do_test_access("test_int_transform", int, int(self.default_value)) + + def test_access_float_transform(self): + self.do_test_access("test_float_transform", float, float(self.default_value)) + + def test_access_bool_getset_transform(self): + self.do_test_access("test_bool_getset_transform", bool, bool(self.custom_value)) + + def test_access_int_getset_transform(self): + self.do_test_access("test_int_getset_transform", int, int(self.custom_value)) + + def test_access_float_getset_transform(self): + self.do_test_access("test_float_getset_transform", float, float(self.custom_value)) + # TODO: Add expected failure cases (e.g. handling of out-of range values). @@ -171,28 +234,63 @@ class TestPropEnum(unittest.TestCase): ) self.test_enum_storage = self.enum_expected_values[self.custom_value] - - def set_(s, v): - self.test_enum_storage = v - id_type.test_enum_getset = EnumProperty( - items=self.enum_items, - default=self.default_value, - get=lambda s: self.test_enum_storage, - set=set_, - ) - self.test_enum_bitflag_storage = functools.reduce( lambda a, b: a | b, (self.enum_expected_bitflag_values[bf] for bf in self.custom_bitflag_value)) - def set_(s, v): + def enum_set_(s, v): + self.test_enum_storage = v + + def bitflag_set_(s, v): self.test_enum_bitflag_storage = v + + id_type.test_enum_getset = EnumProperty( + items=self.enum_items, + default=self.default_value, + get=lambda s: self.test_enum_storage, + set=enum_set_, + ) + id_type.test_enum_bitflag_getset = EnumProperty( items=self.enum_items, default=self.default_bitflag_value, options={"ENUM_FLAG"}, get=lambda s: self.test_enum_bitflag_storage, - set=set_, + set=bitflag_set_, + ) + + id_type.test_enum_transform = EnumProperty( + items=self.enum_items, + default=self.default_value, + get_transform=lambda s, c_v, isset: c_v, + set_transform=lambda s, n_v, c_v, isset: n_v, + ) + + id_type.test_enum_bitflag_transform = EnumProperty( + items=self.enum_items, + default=self.default_bitflag_value, + options={"ENUM_FLAG"}, + get_transform=lambda s, c_v, isset: c_v, + set_transform=lambda s, n_v, c_v, isset: n_v, + ) + + id_type.test_enum_getset_transform = EnumProperty( + items=self.enum_items, + default=self.default_value, + get=lambda s: self.test_enum_storage, + set=enum_set_, + get_transform=lambda s, c_v, isset: c_v, + set_transform=lambda s, n_v, c_v, isset: n_v, + ) + + id_type.test_enum_bitflag_getset_transform = EnumProperty( + items=self.enum_items, + default=self.default_bitflag_value, + options={"ENUM_FLAG"}, + get_transform=lambda s, c_v, isset: c_v, + set_transform=lambda s, n_v, c_v, isset: n_v, + get=lambda s: self.test_enum_bitflag_storage, + set=bitflag_set_, ) def tearDown(self): @@ -200,6 +298,10 @@ class TestPropEnum(unittest.TestCase): del id_type.test_enum_bitflag del id_type.test_enum_getset del id_type.test_enum_bitflag_getset + del id_type.test_enum_transform + del id_type.test_enum_bitflag_transform + del id_type.test_enum_getset_transform + del id_type.test_enum_bitflag_getset_transform # Test expected generated values for enum items. def do_test_enum_values(self, prop_name, expected_item_values): @@ -215,6 +317,24 @@ class TestPropEnum(unittest.TestCase): def test_enum_bitflag_item_values(self): self.do_test_enum_values("test_enum_bitflag", self.enum_expected_bitflag_values) + def test_enum_getset_item_values(self): + self.do_test_enum_values("test_enum_getset", self.enum_expected_values) + + def test_enum_bitflag_getset_item_values(self): + self.do_test_enum_values("test_enum_bitflag_getset", self.enum_expected_bitflag_values) + + def test_enum_transform_item_values(self): + self.do_test_enum_values("test_enum_transform", self.enum_expected_values) + + def test_enum_bitflag_transform_item_values(self): + self.do_test_enum_values("test_enum_bitflag_transform", self.enum_expected_bitflag_values) + + def test_enum_getset_transform_item_values(self): + self.do_test_enum_values("test_enum_getset_transform", self.enum_expected_values) + + def test_enum_bitflag_getset_transform_item_values(self): + self.do_test_enum_values("test_enum_bitflag_getset_transform", self.enum_expected_bitflag_values) + # Test basic access to enum values. def do_test_access(self, prop_name, py_type, expected_value): v = getattr(id_inst, prop_name) @@ -234,6 +354,18 @@ class TestPropEnum(unittest.TestCase): def test_access_enum_bitflag_getset(self): self.do_test_access("test_enum_bitflag_getset", set, self.custom_bitflag_value) + def test_access_enum_transform(self): + self.do_test_access("test_enum_transform", str, self.default_value) + + def test_access_enum_bitflag_transform(self): + self.do_test_access("test_enum_bitflag_transform", set, self.default_bitflag_value) + + def test_access_enum_getset_transform(self): + self.do_test_access("test_enum_getset_transform", str, self.custom_value) + + def test_access_enum_bitflag_getset_transform(self): + self.do_test_access("test_enum_bitflag_getset_transform", set, self.custom_bitflag_value) + # TODO: Add expected failure cases (e.g. handling of invalid items identifiers). diff --git a/tests/python/bl_pyapi_prop_array.py b/tests/python/bl_pyapi_prop_array.py index 7a5a71aff8a..59db79b57e7 100644 --- a/tests/python/bl_pyapi_prop_array.py +++ b/tests/python/bl_pyapi_prop_array.py @@ -147,32 +147,71 @@ class TestPropArrayIndex(unittest.TestCase): self.test_array_b_2d_storage = [[bool(v) for v in range(self.size_2d[1])] for i in range(self.size_2d[0])] - def set_(s, v): + def bool_set_(s, v): self.test_array_b_2d_storage = v - id_type.test_array_b_2d_getset = BoolVectorProperty( - size=self.size_2d, - get=lambda s: self.test_array_b_2d_storage, - set=set_, - ) self.test_array_i_2d_storage = [[int(v) for v in range(self.size_2d[1])] for i in range(self.size_2d[0])] - def set_(s, v): + def int_set_(s, v): self.test_array_i_2d_storage = v - id_type.test_array_i_2d_getset = IntVectorProperty( - size=self.size_2d, - get=lambda s: self.test_array_i_2d_storage, - set=set_, - ) self.test_array_f_2d_storage = [[float(v) for v in range(self.size_2d[1])] for i in range(self.size_2d[0])] - def set_(s, v): + def float_set_(s, v): self.test_array_f_2d_storage = v + + id_type.test_array_b_2d_getset = BoolVectorProperty( + size=self.size_2d, + get=lambda s: self.test_array_b_2d_storage, + set=bool_set_, + ) + id_type.test_array_i_2d_getset = IntVectorProperty( + size=self.size_2d, + get=lambda s: self.test_array_i_2d_storage, + set=int_set_, + ) id_type.test_array_f_2d_getset = FloatVectorProperty( size=self.size_2d, get=lambda s: self.test_array_f_2d_storage, - set=set_, + set=float_set_, + ) + + id_type.test_array_b_3d_transform = BoolVectorProperty( + size=self.size_3d, + get_transform=lambda s, c_v, isset: seq_items_xform(c_v, lambda v: not v), + set_transform=lambda s, n_v, c_v, isset: seq_items_xform(n_v, lambda v: not v), + ) + id_type.test_array_i_3d_transform = IntVectorProperty( + size=self.size_3d, + get_transform=lambda s, c_v, isset: seq_items_xform(c_v, lambda v: v + 1), + set_transform=lambda s, n_v, c_v, isset: seq_items_xform(n_v, lambda v: v - 1), + ) + id_type.test_array_f_3d_transform = FloatVectorProperty( + size=self.size_3d, + get_transform=lambda s, c_v, isset: seq_items_xform(c_v, lambda v: v * 2.0), + set_transform=lambda s, n_v, c_v, isset: seq_items_xform(n_v, lambda v: v / 2.0), + ) + + id_type.test_array_b_2d_getset_transform = BoolVectorProperty( + size=self.size_2d, + get=lambda s: self.test_array_b_2d_storage, + set=bool_set_, + get_transform=lambda s, c_v, isset: seq_items_xform(c_v, lambda v: not v), + set_transform=lambda s, n_v, c_v, isset: seq_items_xform(n_v, lambda v: not v), + ) + id_type.test_array_i_2d_getset_transform = IntVectorProperty( + size=self.size_2d, + get=lambda s: self.test_array_i_2d_storage, + set=int_set_, + get_transform=lambda s, c_v, isset: seq_items_xform(c_v, lambda v: v + 1), + set_transform=lambda s, n_v, c_v, isset: seq_items_xform(n_v, lambda v: v - 1), + ) + id_type.test_array_f_2d_getset_transform = FloatVectorProperty( + size=self.size_2d, + get=lambda s: self.test_array_f_2d_storage, + set=float_set_, + get_transform=lambda s, c_v, isset: seq_items_xform(c_v, lambda v: v * 2.0), + set_transform=lambda s, n_v, c_v, isset: seq_items_xform(n_v, lambda v: v / 2.0), ) def tearDown(self): @@ -190,6 +229,14 @@ class TestPropArrayIndex(unittest.TestCase): del id_type.test_array_i_2d_getset del id_type.test_array_b_2d_getset + del id_type.test_array_f_3d_transform + del id_type.test_array_i_3d_transform + del id_type.test_array_b_3d_transform + + del id_type.test_array_f_2d_getset_transform + del id_type.test_array_i_2d_getset_transform + del id_type.test_array_b_2d_getset_transform + @staticmethod def compute_slice_len(s): if not isinstance(s, slice): @@ -297,6 +344,36 @@ class TestPropArrayIndex(unittest.TestCase): id_inst.test_array_f_2d_getset, self.size_2d, self.valid_indices_2d, self.invalid_indices_2d ) + def test_indices_access_b_3d_transform(self): + self.do_test_indices_access( + id_inst.test_array_b_3d_transform, self.size_3d, self.valid_indices_3d, self.invalid_indices_3d + ) + + def test_indices_access_i_3d_transform(self): + self.do_test_indices_access( + id_inst.test_array_i_3d_transform, self.size_3d, self.valid_indices_3d, self.invalid_indices_3d + ) + + def test_indices_access_f_3d_transform(self): + self.do_test_indices_access( + id_inst.test_array_f_3d_transform, self.size_3d, self.valid_indices_3d, self.invalid_indices_3d + ) + + def test_indices_access_b_2d_getset_transform(self): + self.do_test_indices_access( + id_inst.test_array_b_2d_getset_transform, self.size_2d, self.valid_indices_2d, self.invalid_indices_2d + ) + + def test_indices_access_i_2d_getset_transform(self): + self.do_test_indices_access( + id_inst.test_array_i_2d_getset_transform, self.size_2d, self.valid_indices_2d, self.invalid_indices_2d + ) + + def test_indices_access_f_2d_getset_transform(self): + self.do_test_indices_access( + id_inst.test_array_f_2d_getset_transform, self.size_2d, self.valid_indices_2d, self.invalid_indices_2d + ) + class TestPropArrayForeach(unittest.TestCase): # Test foreach_get/_set access of Int and Float vector properties (bool ones do not support this). @@ -499,12 +576,37 @@ class TestPropArrayMultiDimensional(unittest.TestCase): def set_fn(id_arg, value): local_data["array"] = value + def get_tx_fn(id_arg, curr_value, is_set): + return seq_items_xform(curr_value, lambda v: v + 1.0) + + def set_tx_fn(id_arg, new_value, curr_value, is_set): + return seq_items_xform(new_value, lambda v: v - 1.0) + id_type.temp = FloatVectorProperty(size=(dim_x, dim_y), subtype='MATRIX', get=get_fn, set=set_fn) id_inst.temp = data_native data_as_tuple = seq_items_as_tuple(id_inst.temp) self.assertEqual(data_as_tuple, data_native) del id_type.temp + id_type.temp = FloatVectorProperty( + size=(dim_x, dim_y), subtype='MATRIX', get_transform=get_tx_fn, set_transform=set_tx_fn) + id_inst.temp = data_native + data_as_tuple = seq_items_as_tuple(id_inst.temp) + self.assertEqual(data_as_tuple, data_native) + del id_type.temp + + id_type.temp = FloatVectorProperty( + size=(dim_x, dim_y), + subtype='MATRIX', + get=get_fn, + set=set_fn, + get_transform=get_tx_fn, + set_transform=set_tx_fn) + id_inst.temp = data_native + data_as_tuple = seq_items_as_tuple(id_inst.temp) + self.assertEqual(data_as_tuple, data_native) + del id_type.temp + def test_matrix_3x3(self): self._test_matrix(3, 3)