diff --git a/source/blender/makesdna/DNA_customdata_types.h b/source/blender/makesdna/DNA_customdata_types.h index d45abd3bcc6..3c0618441a4 100644 --- a/source/blender/makesdna/DNA_customdata_types.h +++ b/source/blender/makesdna/DNA_customdata_types.h @@ -42,9 +42,10 @@ typedef struct CustomDataLayer { int active_mask; /** Shape key-block unique id reference. */ int uid; + /** Only for use in RNA and #bpy_rna: number of items in the layer data. */ + int length; /** Layer name, MAX_CUSTOMDATA_LAYER_NAME. */ char name[68]; - char _pad1[4]; /** Layer data. */ void *data; /** diff --git a/source/blender/makesrna/RNA_access.hh b/source/blender/makesrna/RNA_access.hh index d14da1e7929..57adee1f027 100644 --- a/source/blender/makesrna/RNA_access.hh +++ b/source/blender/makesrna/RNA_access.hh @@ -84,6 +84,7 @@ const StructRNA *RNA_struct_base_child_of(const StructRNA *type, const StructRNA bool RNA_struct_is_ID(const StructRNA *type); bool RNA_struct_is_a(const StructRNA *type, const StructRNA *srna); +bool RNA_struct_is_attribute_array(const StructRNA *type); bool RNA_struct_undo_check(const StructRNA *type); diff --git a/source/blender/makesrna/RNA_types.hh b/source/blender/makesrna/RNA_types.hh index 290faaad412..cd59dee0902 100644 --- a/source/blender/makesrna/RNA_types.hh +++ b/source/blender/makesrna/RNA_types.hh @@ -723,6 +723,8 @@ enum StructFlag { * So accessing the property should not read from the current context to derive values/limits. */ STRUCT_NO_CONTEXT_WITHOUT_OWNER_ID = (1 << 11), + /** Indicates that this struct is a geometry attribute array. */ + STRUCT_ATTRIBUTE_ARRAY = (1 << 12), }; using StructValidateFunc = int (*)(PointerRNA *ptr, void *data, bool *have_function); diff --git a/source/blender/makesrna/intern/rna_access.cc b/source/blender/makesrna/intern/rna_access.cc index 2ed5acb5d5f..05395780f4b 100644 --- a/source/blender/makesrna/intern/rna_access.cc +++ b/source/blender/makesrna/intern/rna_access.cc @@ -744,6 +744,11 @@ bool RNA_struct_idprops_unset(PointerRNA *ptr, const char *identifier) return false; } +bool RNA_struct_is_attribute_array(const StructRNA *type) +{ + return (type->flag & STRUCT_ATTRIBUTE_ARRAY) != 0; +} + bool RNA_struct_is_a(const StructRNA *type, const StructRNA *srna) { const StructRNA *base; diff --git a/source/blender/makesrna/intern/rna_attribute.cc b/source/blender/makesrna/intern/rna_attribute.cc index 36025e11f7c..58601b8131a 100644 --- a/source/blender/makesrna/intern/rna_attribute.cc +++ b/source/blender/makesrna/intern/rna_attribute.cc @@ -351,6 +351,13 @@ static void rna_Attribute_update_data(Main * /*bmain*/, Scene * /*scene*/, Point } } +static PointerRNA rna_Attribute_attribute_array_get(PointerRNA *ptr) +{ + CustomDataLayer *layer = static_cast(ptr->data); + layer->length = rna_Attribute_data_length(ptr); + return rna_pointer_inherit_refine(ptr, &RNA_AttributeArray, ptr->data); +} + /* Color Attribute */ static void rna_ByteColorAttributeValue_color_get(PointerRNA *ptr, float *values) @@ -1175,6 +1182,17 @@ static void rna_def_attribute_float2(BlenderRNA *brna) RNA_def_property_update(prop, 0, "rna_Attribute_update_data"); } +static void rna_def_attribute_array(BlenderRNA *brna) +{ + /* The #AttributeArray struct grants access to attribute arrays in Blender python. The + * #STRUCT_ATTRIBUTE_ARRAY flag is linked to the #BPy_AttributeArray in #bpy_rna.cc. See + * #python/generic/attribute_array_py_api.cc for all Python methods available in this class. */ + StructRNA *srna = RNA_def_struct(brna, "AttributeArray", nullptr); + RNA_def_struct_flag(srna, STRUCT_ATTRIBUTE_ARRAY); + RNA_def_struct_sdna(srna, "CustomDataLayer"); + RNA_def_struct_ui_text(srna, "Attribute Array", "A geometry attribute data array"); +} + static void rna_def_attribute(BlenderRNA *brna) { PropertyRNA *prop; @@ -1217,6 +1235,12 @@ static void rna_def_attribute(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Is Required", "Whether the attribute can be removed or renamed"); RNA_def_property_clear_flag(prop, PROP_EDITABLE); + /* Direct Python access to the attribute array. */ + prop = RNA_def_property(srna, "data_array", PROP_POINTER, PROP_NONE); + RNA_def_property_struct_type(prop, "AttributeArray"); + RNA_def_property_pointer_funcs( + prop, "rna_Attribute_attribute_array_get", nullptr, nullptr, nullptr); + /* types */ rna_def_attribute_float(brna); rna_def_attribute_float_vector(brna); @@ -1230,6 +1254,8 @@ static void rna_def_attribute(BlenderRNA *brna) rna_def_attribute_bool(brna); rna_def_attribute_float2(brna); rna_def_attribute_int8(brna); + + rna_def_attribute_array(brna); } /* Mesh/PointCloud/Curves.attributes */ diff --git a/source/blender/python/generic/CMakeLists.txt b/source/blender/python/generic/CMakeLists.txt index 15126d56968..f32118347a7 100644 --- a/source/blender/python/generic/CMakeLists.txt +++ b/source/blender/python/generic/CMakeLists.txt @@ -14,6 +14,7 @@ set(INC_SYS ) set(SRC + attribute_array_py_api.cc bgl.cc bl_math_py_api.cc blf_py_api.cc @@ -24,6 +25,8 @@ set(SRC py_capi_rna.cc py_capi_utils.cc + attribute_array_py_api.h + attribute_array_py_types.hh bgl.h bl_math_py_api.h blf_py_api.h diff --git a/source/blender/python/generic/attribute_array_py_api.cc b/source/blender/python/generic/attribute_array_py_api.cc new file mode 100644 index 00000000000..ce8f397f959 --- /dev/null +++ b/source/blender/python/generic/attribute_array_py_api.cc @@ -0,0 +1,659 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup pygen + */ + +#include "attribute_array_py_types.hh" + +#include "RNA_access.hh" + +namespace blender::python::attributearray { + +/* -------------------------------------------------------------------- */ +/** \name BPy_AttributeArray Mapping + * \{ */ + +static int BPy_AttributeArray_assign_subscript_int(BPy_AttributeArray *self, + Py_ssize_t index, + PyObject *value_to_assign) +{ + Py_ssize_t index_abs = index >= 0 ? index : index + self->data_layer->length; + if (index_abs < 0 || index_abs >= self->data_layer->length) { + PyErr_Format(PyExc_IndexError, + "AttributeArray[index] = value: index %d out of range, size %d", + index, + self->data_layer->length); + return -1; + } + + if (!check_attribute_type(self)) { + return -1; + } + + if (!set_attribute_by_type(self, int(index), value_to_assign)) { + StringRef type_description = get_attribute_type_description(self); + PyErr_Format(PyExc_ValueError, + "AttributeArray[index] = value: invalid value type, expected a %.200s", + type_description.data()); + return -1; + } + return 0; +} + +static int BPy_AttributeArray_assign_subscript_slice(BPy_AttributeArray *self, + Py_ssize_t start, + Py_ssize_t stop, + PyObject *values_to_assign) +{ + if (!check_attribute_type(self)) { + return -1; + } + + if (!PySequence_Check(values_to_assign)) { + StringRef type_description = get_attribute_type_description(self); + PyErr_Format(PyExc_TypeError, + "AttributeArray[:] = value: invalid value type, expected a sequence of %.200s", + type_description.data()); + return -1; + } + + const Py_ssize_t count = PySequence_Fast_GET_SIZE(values_to_assign); + if (count != stop - start) { + PyErr_Format(PyExc_ValueError, + "AttributeArray[:] = value: size mismatch in assignment (expected %d, got %d)", + stop - start, + count); + return -1; + } + + /* Assign sequence to attribute array. */ + PyObject **values_fast = PySequence_Fast_ITEMS(values_to_assign); + for (int index = start; index < stop; index++) { + PyObject *item = values_fast[index - start]; + if (item) { + if (!set_attribute_by_type(self, index, item)) { + StringRef type_description = get_attribute_type_description(self); + PyErr_Format(PyExc_ValueError, + "AttributeArray[index] = value: invalid value type, expected a %.200s", + type_description.data()); + return -1; + } + } + else { + return -1; + } + } + return 0; +} + +static int BPy_AttributeArray_assign_subscript(BPy_AttributeArray *self, + PyObject *key, + PyObject *value) +{ + if (value == nullptr) { + PyErr_SetString(PyExc_ValueError, "del AttributeArray[key]: not supported"); + return -1; + } + + if (PyIndex_Check(key)) { + const Py_ssize_t index = PyNumber_AsSsize_t(key, PyExc_IndexError); + if (index == -1 && PyErr_Occurred()) { + return -1; + } + + return BPy_AttributeArray_assign_subscript_int(self, index, value); + } + else if (PySlice_Check(key)) { + Py_ssize_t step = 1; + PySliceObject *key_slice = (PySliceObject *)key; + + if (key_slice->step != Py_None && !_PyEval_SliceIndex(key, &step)) { + return -1; + } + if (step != 1) { + PyErr_SetString(PyExc_IndexError, "AttributeArray[slice]: slice steps not supported"); + return -1; + } + + Py_ssize_t start, stop, slicelength; + if (PySlice_GetIndicesEx(key, self->data_layer->length, &start, &stop, &step, &slicelength) < + 0) + { + return -1; + } + if (slicelength <= 0) { + return 0; + } + + return BPy_AttributeArray_assign_subscript_slice(self, start, stop, value); + } + + PyErr_Format(PyExc_IndexError, + "AttributeArray[key] = value: invalid key, must be an int or slice, not %.200s", + Py_TYPE(key)->tp_name); + return -1; +} + +static PyObject *BPy_AttributeArray_subscript_int(BPy_AttributeArray *self, Py_ssize_t index) +{ + Py_ssize_t index_abs = index >= 0 ? index : index + self->data_layer->length; + if (index_abs < 0 || index_abs >= self->data_layer->length) { + PyErr_Format(PyExc_IndexError, + "AttributeArray[index]: index %d out of range, size %d", + index, + self->data_layer->length); + return nullptr; + } + + if (!check_attribute_type(self)) { + return nullptr; + } + + return get_attribute_by_type(self, int(index)); +} + +static PyObject *BPy_AttributeArray_subscript_slice(BPy_AttributeArray *self, + Py_ssize_t start, + Py_ssize_t stop) +{ + PyObject *list = PyList_New(stop - start); + for (int i = 0; i < stop - start; i++) { + PyObject *item = BPy_AttributeArray_subscript_int(self, start + i); + PyList_SET_ITEM(list, i, item); + } + return list; +} + +static PyObject *BPy_AttributeArray_subscript(BPy_AttributeArray *self, PyObject *key) +{ + if (PyIndex_Check(key)) { + const Py_ssize_t keynum = PyNumber_AsSsize_t(key, PyExc_IndexError); + if (keynum == -1 && PyErr_Occurred()) { + return nullptr; + } + + return BPy_AttributeArray_subscript_int(self, keynum); + } + else if (PySlice_Check(key)) { + Py_ssize_t step = 1; + PySliceObject *key_slice = (PySliceObject *)key; + + if (key_slice->step != Py_None && !_PyEval_SliceIndex(key, &step)) { + return nullptr; + } + if (step != 1) { + PyErr_SetString(PyExc_IndexError, "AttributeArray[slice]: slice steps not supported"); + return nullptr; + } + + Py_ssize_t start, stop, slicelength; + if (PySlice_GetIndicesEx(key, self->data_layer->length, &start, &stop, &step, &slicelength) < + 0) + { + return nullptr; + } + if (slicelength <= 0) { + return PyList_New(0); + } + + return BPy_AttributeArray_subscript_slice(self, start, stop); + } + + PyErr_Format(PyExc_IndexError, + "AttributeArray[key]: invalid key, must be an int or slice, not %.200s", + Py_TYPE(key)->tp_name); + return nullptr; +} + +static Py_ssize_t BPy_AttributeArray_length(BPy_AttributeArray *self) +{ + return self->data_layer->length; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name BPy_AttributeArray Foreach get/set + * \{ */ + +static PyObject *foreach_getset(BPy_AttributeArray *self, + PyObject *args, + const char *function_name, + const bool use_get) +{ + PyObject *buffer_obj; + if (!PyArg_ParseTuple(args, "O:foreach_get/set", &buffer_obj)) { + return nullptr; + } + + if (!PyObject_CheckBuffer(buffer_obj)) { + PyErr_Format( + PyExc_TypeError, + "%s(array) expected the argument to be a contiguous array (like a Python or numpy array), " + "not a %.200s", + function_name, + Py_TYPE(buffer_obj)->tp_name); + return nullptr; + } + + /* Compare buffer with attribute array size and type. */ + Py_buffer buffer; + if (PyObject_GetBuffer(buffer_obj, &buffer, PyBUF_ND | PyBUF_FORMAT) == -1) { + PyErr_SetString(PyExc_BufferError, "%s(array): couldn't access the buffer"); + return nullptr; + } + + if (!check_attribute_type(self)) { + return nullptr; + } + + const bool type_is_compatible = check_buffer_format(self, buffer.format); + const int item_size = get_attribute_item_size(self); + const bool size_matches = (buffer.len == int64_t(self->data_layer->length) * item_size); + + if (!type_is_compatible || !size_matches) { + PyBuffer_Release(&buffer); + } + + if (!type_is_compatible) { + const char expected_buffer_format = get_buffer_format_by_type(self); + PyErr_Format(PyExc_TypeError, + "%s(array): array type mismatch (expected type '%.1s', got '%.1s')", + function_name, + &expected_buffer_format, + buffer.format); + return nullptr; + } + + if (!size_matches) { + PyErr_Format(PyExc_ValueError, + "%s(array): array length mismatch (expected %lld, got %lld)", + function_name, + int64_t(self->data_layer->length) * item_size, + buffer.len); + return nullptr; + } + + /* Source and destination arrays are contiguous, copy the data at once. */ + if (use_get) { + memcpy(buffer.buf, self->data_layer->data, buffer.len); + } + else { + memcpy(self->data_layer->data, buffer.buf, buffer.len); + } + + PyBuffer_Release(&buffer); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR( + /* Wrap. */ + BPy_AttributeArray_foreach_get_doc, + ".. method:: foreach_get(array)\n" + "\n" + " This is a function to give fast access to the values in an geometry attribute array.\n" + "\n" + " :arg array: An contiguous array of the same size as the attribute array,\n" + " e.g. a Python array.array() or a numpy array.\n" + " :type array: The same type as the attribute array.\n"); +static PyObject *BPy_AttributeArray_foreach_get(BPy_AttributeArray *self, PyObject *args) +{ + return foreach_getset(self, args, "foreach_get", true); +} + +PyDoc_STRVAR( + /* Wrap. */ + BPy_AttributeArray_foreach_set_doc, + ".. method:: foreach_set(array)\n" + "\n" + " This is a function to give fast access to the values in an geometry attribute array.\n" + "\n" + " :arg array: An contiguous array of the same size as the attribute array,\n" + " e.g. a Python array.array() or a numpy array.\n" + " :type array: The same type as the attribute array.\n"); +static PyObject *BPy_AttributeArray_foreach_set(BPy_AttributeArray *self, PyObject *args) +{ + return foreach_getset(self, args, "foreach_set", false); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name BPy_AttributeArray Fill + * \{ */ + +static std::optional get_index_int(PyObject *py_index) +{ + const int index = PyLong_AsLong(py_index); + if (index == -1 && PyErr_Occurred()) { + return std::nullopt; + } + return index; +} + +PyDoc_STRVAR( + /* Wrap. */ + BPy_AttributeArray_fill_doc, + ".. method:: fill(value, indices=None)\n" + "\n" + " Fill the geometry attribute array with a value.\n" + "\n" + " :arg value: Value to fill the array with.\n" + " :arg indices: Only fill the given indices (optional argument).\n" + " :type indices: List, array or numpy array of ints.\n"); +static PyObject *BPy_AttributeArray_fill(BPy_AttributeArray *self, PyObject *args) +{ + /* Get fill value and (optionally) a list of indices. */ + PyObject *py_value, *py_indices = nullptr; + if (!PyArg_ParseTuple(args, "O|O:AttributeArray.fill(value, indices)", &py_value, &py_indices)) { + return nullptr; + } + if (!check_attribute_type(self)) { + return nullptr; + } + + /* Verify the indices object. */ + if (py_indices == Py_None) { + py_indices = nullptr; + } + if (py_indices && !PySequence_Check(py_indices)) { + PyErr_SetString( + PyExc_TypeError, + "AttributeArray.fill[value, indices]: invalid indices type, expected a list of ints"); + return nullptr; + } + + Py_ssize_t indices_length; + if (py_indices) { + indices_length = PySequence_Size(py_indices); + if (indices_length == 0) { + Py_RETURN_NONE; + } + } + + /* Get the index of first value in the array. */ + int first_index = 0; + if (py_indices) { + PyObject *py_index = PySequence_GetItem(py_indices, 0); + std::optional maybe_first_index = get_index_int(py_index); + if (!maybe_first_index.has_value()) { + PyErr_Format( + PyExc_IndexError, + "AttributeArray.fill(value, indices): invalid index type (expected an int, got %.200s)", + Py_TYPE(py_index)->tp_name); + Py_DECREF(py_index); + return nullptr; + } + + Py_DECREF(py_index); + first_index = maybe_first_index.value(); + if (first_index < 0 || first_index >= self->data_layer->length) { + PyErr_Format(PyExc_IndexError, + "AttributeArray.fill(value, indices): index %d out of range, size %d", + first_index, + self->data_layer->length); + return nullptr; + } + } + + /* Set the first value. */ + if (!set_attribute_by_type(self, first_index, py_value)) { + StringRef type_description = get_attribute_type_description(self); + PyErr_Format(PyExc_ValueError, + "AttributeArray.fill(value, ...): invalid value type, expected a %.200s", + type_description.data()); + return nullptr; + } + + /* Fill the entire array. */ + void *data = self->data_layer->data; + const int item_size = get_attribute_item_size(self); + if (py_indices == nullptr) { + for (int index = 1; index < self->data_layer->length; index++) { + memcpy(static_cast(data) + index * item_size, data, item_size); + } + } + /* Fill the array using indices. */ + else { + for (int i = 1; i < indices_length; i++) { + PyObject *py_index = PySequence_GetItem(py_indices, i); + std::optional maybe_index = get_index_int(py_index); + if (!maybe_index.has_value()) { + PyErr_Format(PyExc_IndexError, + "AttributeArray.fill(value, indices): invalid index type (expected an int, " + "got %.200s)", + Py_TYPE(py_index)->tp_name); + Py_DECREF(py_index); + return nullptr; + } + + Py_DECREF(py_index); + const int index = maybe_index.value(); + if (index < 0 || index >= self->data_layer->length) { + PyErr_Format(PyExc_IndexError, + "AttributeArray.fill(value, indices): index %d out of range, size %d", + index, + self->data_layer->length); + return nullptr; + } + + memcpy(static_cast(data) + index * item_size, + static_cast(data) + first_index * item_size, + item_size); + } + } + + Py_RETURN_NONE; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name BPy_AttributeArray Miscellaneous + * \{ */ + +static PyObject *BPy_AttributeArray_repr(BPy_AttributeArray *self) +{ + const CustomDataLayer &layer = *self->data_layer; + + return PyUnicode_FromFormat("", layer.name, layer.data); +} + +static Py_hash_t BPy_AttributeArray_hash(BPy_AttributeArray *self) +{ + return _Py_HashPointer(self->data_layer); +} + +/** \} */ + +} // namespace blender::python::attributearray + +/* -------------------------------------------------------------------- */ +/** \name BPy_AttributeArray Create object + * \{ */ + +PyObject *BPy_AttributeArray_CreatePyObject(PointerRNA *ptr) +{ + BPy_AttributeArray *self = PyObject_New(BPy_AttributeArray, &BPy_AttributeArray_Type); + self->data_layer = static_cast(ptr->data); + return (PyObject *)self; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name BPy_AttributeArray Type + * \{ */ + +using namespace blender::python::attributearray; + +static PyMethodDef BPy_AttributeArray_methods[] = { + {"foreach_get", + (PyCFunction)BPy_AttributeArray_foreach_get, + METH_VARARGS, + BPy_AttributeArray_foreach_get_doc}, + {"foreach_set", + (PyCFunction)BPy_AttributeArray_foreach_set, + METH_VARARGS, + BPy_AttributeArray_foreach_set_doc}, + {"fill", (PyCFunction)BPy_AttributeArray_fill, METH_VARARGS, BPy_AttributeArray_fill_doc}, + {nullptr, nullptr, 0, nullptr}, +}; + +static PySequenceMethods BPy_AttributeArray_as_sequence = { + /*sq_length*/ (lenfunc)BPy_AttributeArray_length, + /*sq_concat*/ nullptr, + /*sq_repeat*/ nullptr, + /*sq_item*/ (ssizeargfunc)BPy_AttributeArray_subscript_int, + /*was_sq_slice*/ nullptr, + /*sq_ass_item*/ + (ssizeobjargproc)BPy_AttributeArray_assign_subscript_int, + /*was_sq_ass_slice*/ nullptr, + /*sq_contains*/ nullptr, + /*sq_inplace_concat*/ nullptr, + /*sq_inplace_repeat*/ nullptr, +}; + +static PyMappingMethods BPy_AttributeArray_as_mapping = { + /*mp_length*/ (lenfunc)BPy_AttributeArray_length, + /*mp_subscript*/ (binaryfunc)BPy_AttributeArray_subscript, + /*mp_ass_subscript*/ + (objobjargproc)BPy_AttributeArray_assign_subscript, +}; + +PyTypeObject BPy_AttributeArray_Type = { + /*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0) + /*tp_name*/ "AttributeArray", + /*tp_basicsize*/ sizeof(BPy_AttributeArray), + /*tp_itemsize*/ 0, + /*tp_dealloc*/ nullptr, + /*tp_vectorcall_offset */ 0, + /*tp_getattr*/ nullptr, + /*tp_setattr*/ nullptr, + /*tp_as_async*/ nullptr, + /*tp_repr*/ (reprfunc)BPy_AttributeArray_repr, + /*tp_as_number*/ nullptr, + /*tp_as_sequence*/ &BPy_AttributeArray_as_sequence, + /*tp_as_mapping*/ &BPy_AttributeArray_as_mapping, + /*tp_hash*/ (hashfunc)BPy_AttributeArray_hash, + /*tp_call*/ nullptr, + /*tp_str*/ nullptr, + /*tp_getattro*/ nullptr, + /*tp_setattro*/ nullptr, + /*tp_as_buffer*/ nullptr, + /*tp_flags*/ Py_TPFLAGS_DEFAULT, + /*tp_doc*/ nullptr, + /*tp_traverse*/ nullptr, + /*tp_clear*/ nullptr, + /*tp_richcompare*/ nullptr, + /*tp_weaklistoffset*/ 0, + /*tp_iter*/ nullptr, + /*tp_iternext*/ nullptr, + /*tp_methods*/ BPy_AttributeArray_methods, + /*tp_members*/ nullptr, + /*tp_getset*/ nullptr, + /*tp_base*/ nullptr, + /*tp_dict*/ nullptr, + /*tp_descr_get*/ nullptr, + /*tp_descr_set*/ nullptr, + /*tp_dictoffset*/ 0, + /*tp_init*/ nullptr, + /*tp_alloc*/ nullptr, + /*tp_new*/ nullptr, + /*tp_free*/ nullptr, + /*tp_is_gc*/ nullptr, + /*tp_bases*/ nullptr, + /*tp_mro*/ nullptr, + /*tp_cache*/ nullptr, + /*tp_subclasses*/ nullptr, + /*tp_weaklist*/ nullptr, + /*tp_del*/ nullptr, + /*tp_version_tag*/ 0, + /*tp_finalize*/ nullptr, + /*tp_vectorcall*/ nullptr, +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Initialize Types + * \{ */ + +void AttributeArray_Init_Types() +{ + PyType_Ready(&BPy_AttributeArray_Type); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Public Module 'attribute_array.types' + * \{ */ + +static PyModuleDef AttributeArray_types_module_def = { + /*m_base*/ PyModuleDef_HEAD_INIT, + /*m_name*/ "attribute_array.types", + /*m_doc*/ nullptr, + /*m_size*/ 0, + /*m_methods*/ nullptr, + /*m_slots*/ nullptr, + /*m_traverse*/ nullptr, + /*m_clear*/ nullptr, + /*m_free*/ nullptr, +}; + +static PyObject *BPyInit_attribute_array_types() +{ + PyObject *submodule; + + submodule = PyModule_Create(&AttributeArray_types_module_def); + + AttributeArray_Init_Types(); + + PyModule_AddType(submodule, &BPy_AttributeArray_Type); + + return submodule; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Public Module 'attribute_array' + * \{ */ + +PyDoc_STRVAR( + /* Wrap. */ + AttributeArray_module_doc, + "This module provides access to geometry attribute arrays."); +static PyModuleDef AttributeArray_module_def = { + /*m_base*/ PyModuleDef_HEAD_INIT, + /*m_name*/ "attribute_array", + /*m_doc*/ AttributeArray_module_doc, + /*m_size*/ 0, + /*m_methods*/ nullptr, + /*m_slots*/ nullptr, + /*m_traverse*/ nullptr, + /*m_clear*/ nullptr, + /*m_free*/ nullptr, +}; + +PyObject *BPyInit_attribute_array() +{ + PyObject *mod; + PyObject *submodule; + PyObject *sys_modules = PyImport_GetModuleDict(); + + mod = PyModule_Create(&AttributeArray_module_def); + + /* attribute_array.types */ + PyModule_AddObject(mod, "types", (submodule = BPyInit_attribute_array_types())); + PyDict_SetItem(sys_modules, PyModule_GetNameObject(submodule), submodule); + + return mod; +} + +/** \} */ diff --git a/source/blender/python/generic/attribute_array_py_api.h b/source/blender/python/generic/attribute_array_py_api.h new file mode 100644 index 00000000000..c77bcb8d1f9 --- /dev/null +++ b/source/blender/python/generic/attribute_array_py_api.h @@ -0,0 +1,33 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup pygen + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +struct PointerRNA; +struct CustomDataLayer; + +extern PyTypeObject BPy_AttributeArray_Type; + +typedef struct BPy_AttributeArray { + PyObject_VAR_HEAD + CustomDataLayer *data_layer; +} BPy_AttributeArray; + +PyObject *BPy_AttributeArray_CreatePyObject(PointerRNA *ptr); + +void AttributeArray_Init_Types(void); + +PyObject *BPyInit_attribute_array(void); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/python/generic/attribute_array_py_types.hh b/source/blender/python/generic/attribute_array_py_types.hh new file mode 100644 index 00000000000..6dba23bab0b --- /dev/null +++ b/source/blender/python/generic/attribute_array_py_types.hh @@ -0,0 +1,637 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup pygen + */ + +#pragma once + +#include +#include + +#include + +#include "attribute_array_py_api.h" +#include "py_capi_utils.h" + +#include "../mathutils/mathutils.h" + +#include "DNA_meshdata_types.h" + +#include "BKE_customdata.hh" + +#include "BLI_color.hh" +#include "BLI_math_matrix_types.hh" +#include "BLI_math_quaternion_types.hh" +#include "BLI_math_vector_types.hh" + +namespace blender::python::attributearray { + +/* -------------------------------------------------------------------- */ +/** \name BPy_AttributeArray Set attribute + * \{ */ + +static bool set_attribute_bool(void *data, int index, PyObject *py_value) +{ + const int value = PyC_Long_AsBool(py_value); + if (value == -1) { + return false; + } + static_cast(data)[index] = value; + return true; +} + +static bool set_attribute_float(void *data, int index, PyObject *py_value) +{ + if (PyLong_Check(py_value)) { + /* Allow int values (like 0), convert them to float. */ + const long value = PyLong_AsLong(py_value); + static_cast(data)[index] = float(value); + return true; + } + if (!PyFloat_Check(py_value)) { + return false; + } + static_cast(data)[index] = PyFloat_AsDouble(py_value); + return true; +} + +static bool set_attribute_int8(void *data, int index, PyObject *py_value) +{ + const int value = _PyLong_AsInt(py_value); + if (UNLIKELY(value == -1 && PyErr_Occurred())) { + return false; + } + if (UNLIKELY(value < INT8_MIN || value > INT8_MAX)) { + return false; + } + static_cast(data)[index] = int8_t(value); + return true; +} + +static bool set_attribute_uint8(void *data, int index, PyObject *py_value) +{ + const int value = _PyLong_AsInt(py_value); + if (UNLIKELY(value == -1 && PyErr_Occurred())) { + return false; + } + if (UNLIKELY(value < 0 || value > UINT8_MAX)) { + return false; + } + static_cast(data)[index] = uint8_t(value); + return true; +} + +static bool set_attribute_int32(void *data, int index, PyObject *py_value) +{ + if (!PyLong_Check(py_value)) { + return false; + } + const long value = PyLong_AsLong(py_value); + if (UNLIKELY(value < INT32_MIN || value > INT32_MAX)) { + return false; + } + static_cast(data)[index] = int32_t(value); + return true; +} + +static bool set_attribute_int2(void *data, int index, PyObject *py_value) +{ + if (PyTuple_Check(py_value)) { + const Py_ssize_t size = PyTuple_GET_SIZE(py_value); + if (size != 2) { + return false; + } + int2 &set_value = static_cast(data)[index]; + for (int i = 0; i < 2; i++) { + if (!set_attribute_int32(set_value, i, PyTuple_GET_ITEM(py_value, i))) { + return false; + } + } + return true; + } + if (PyList_Check(py_value)) { + const Py_ssize_t size = PyList_GET_SIZE(py_value); + if (size != 2) { + return false; + } + int2 &set_value = static_cast(data)[index]; + for (int i = 0; i < 2; i++) { + if (!set_attribute_int32(set_value, i, PyList_GET_ITEM(py_value, i))) { + return false; + } + } + return true; + } + return false; +} + +static bool set_attribute_string(void *data, int index, PyObject *py_value) +{ + if (!PyUnicode_Check(py_value)) { + return false; + } + const std::string value = std::string(PyUnicode_AsUTF8(py_value)); + if (value.length() > 254) { + return false; + } + strcpy(static_cast(data)[index].s, value.c_str()); + static_cast(data)[index].s_len = value.length(); + return true; +} + +static bool set_attribute_float2(void *data, int index, PyObject *py_value) +{ + if (VectorObject_Check(py_value)) { + const VectorObject *value = reinterpret_cast(py_value); + if (value->vec_num != 2) { + return false; + } + static_cast(data)[index] = {value->vec[0], value->vec[1]}; + return true; + } + if (PyTuple_Check(py_value)) { + const Py_ssize_t size = PyTuple_GET_SIZE(py_value); + if (size != 2) { + return false; + } + float2 &set_value = static_cast(data)[index]; + for (int i = 0; i < 2; i++) { + if (!set_attribute_float(set_value, i, PyTuple_GET_ITEM(py_value, i))) { + return false; + } + } + return true; + } + if (PyList_Check(py_value)) { + const Py_ssize_t size = PyList_GET_SIZE(py_value); + if (size != 2) { + return false; + } + float2 &set_value = static_cast(data)[index]; + for (int i = 0; i < 2; i++) { + if (!set_attribute_float(set_value, i, PyList_GET_ITEM(py_value, i))) { + return false; + } + } + return true; + } + return false; +} + +static bool set_attribute_float3(void *data, int index, PyObject *py_value) +{ + if (VectorObject_Check(py_value)) { + const VectorObject *value = reinterpret_cast(py_value); + if (value->vec_num != 3) { + return false; + } + static_cast(data)[index] = {value->vec[0], value->vec[1], value->vec[2]}; + return true; + } + if (PyTuple_Check(py_value)) { + const Py_ssize_t size = PyTuple_GET_SIZE(py_value); + if (size != 3) { + return false; + } + float3 &set_value = static_cast(data)[index]; + for (int i = 0; i < 3; i++) { + if (!set_attribute_float(set_value, i, PyTuple_GET_ITEM(py_value, i))) { + return false; + } + } + return true; + } + if (PyList_Check(py_value)) { + const Py_ssize_t size = PyList_GET_SIZE(py_value); + if (size != 3) { + return false; + } + float3 &set_value = static_cast(data)[index]; + for (int i = 0; i < 3; i++) { + if (!set_attribute_float(set_value, i, PyList_GET_ITEM(py_value, i))) { + return false; + } + } + return true; + } + return false; +} + +static bool set_attribute_quaternion(void *data, int index, PyObject *py_value) +{ + if (QuaternionObject_Check(py_value)) { + const QuaternionObject *value = reinterpret_cast(py_value); + static_cast(data)[index] = { + value->quat[0], value->quat[1], value->quat[2], value->quat[3]}; + return true; + } + if (PyTuple_Check(py_value)) { + const Py_ssize_t size = PyTuple_GET_SIZE(py_value); + if (size != 4) { + return false; + } + float4 &set_value = static_cast(data)[index]; + for (int i = 0; i < 4; i++) { + if (!set_attribute_float(set_value, i, PyTuple_GET_ITEM(py_value, i))) { + return false; + } + } + return true; + } + if (PyList_Check(py_value)) { + const Py_ssize_t size = PyList_GET_SIZE(py_value); + if (size != 4) { + return false; + } + float4 &set_value = static_cast(data)[index]; + for (int i = 0; i < 4; i++) { + if (!set_attribute_float(set_value, i, PyList_GET_ITEM(py_value, i))) { + return false; + } + } + return true; + } + return false; +} + +static bool set_attribute_float4x4(void *data, int index, PyObject *py_value) +{ + if (!MatrixObject_Check(py_value)) { + return false; + } + MatrixObject &value = *reinterpret_cast(py_value); + if (value.col_num != 4 || value.row_num != 4) { + return false; + } + std::copy_n(value.matrix, 16, static_cast(data)[index].base_ptr()); + return true; +} + +static bool set_attribute_color(void *data, int index, PyObject *py_value) +{ + if (PyTuple_Check(py_value)) { + const Py_ssize_t size = PyTuple_GET_SIZE(py_value); + if (size != 4) { + return false; + } + ColorGeometry4f &set_value = static_cast(data)[index]; + for (int i = 0; i < 4; i++) { + if (!set_attribute_float(set_value, i, PyTuple_GET_ITEM(py_value, i))) { + return false; + } + } + return true; + } + if (PyList_Check(py_value)) { + const Py_ssize_t size = PyList_GET_SIZE(py_value); + if (size != 4) { + return false; + } + ColorGeometry4f &set_value = static_cast(data)[index]; + for (int i = 0; i < 4; i++) { + if (!set_attribute_float(set_value, i, PyList_GET_ITEM(py_value, i))) { + return false; + } + } + return true; + } + return false; +} + +static bool set_attribute_byte_color(void *data, int index, PyObject *py_value) +{ + if (PyTuple_Check(py_value)) { + const Py_ssize_t size = PyTuple_GET_SIZE(py_value); + if (size != 4) { + return false; + } + ColorGeometry4b &set_value = static_cast(data)[index]; + for (int i = 0; i < 4; i++) { + if (!set_attribute_uint8(set_value, i, PyTuple_GET_ITEM(py_value, i))) { + return false; + } + } + return true; + } + if (PyList_Check(py_value)) { + const Py_ssize_t size = PyList_GET_SIZE(py_value); + if (size != 4) { + return false; + } + ColorGeometry4b &set_value = static_cast(data)[index]; + for (int i = 0; i < 4; i++) { + if (!set_attribute_uint8(set_value, i, PyList_GET_ITEM(py_value, i))) { + return false; + } + } + return true; + } + + return false; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name BPy_AttributeArray Get attribute + * \{ */ + +PyObject *get_attribute_bool(void *data, int index) +{ + return PyBool_FromLong(static_cast(data)[index]); +} + +PyObject *get_attribute_float(void *data, int index) +{ + return PyFloat_FromDouble(static_cast(data)[index]); +} + +PyObject *get_attribute_int8(void *data, int index) +{ + return PyLong_FromLong(static_cast(data)[index]); +} + +PyObject *get_attribute_int32(void *data, int index) +{ + return PyLong_FromLong(static_cast(data)[index]); +} + +PyObject *get_attribute_int2(void *data, int index) +{ + PyObject *result = PyTuple_New(2); + for (int i = 0; i < 2; i++) { + PyTuple_SET_ITEM(result, i, PyLong_FromLong((static_cast(data)[index])[i])); + } + return result; +} + +PyObject *get_attribute_string(void *data, int index) +{ + return PyUnicode_FromStringAndSize(static_cast(data)[index].s, + static_cast(data)[index].s_len); +} + +PyObject *get_attribute_float2(void *data, int index) +{ + return Vector_CreatePyObject_wrap(static_cast(data)[index], 2, nullptr); +} + +PyObject *get_attribute_float3(void *data, int index) +{ + return Vector_CreatePyObject_wrap(static_cast(data)[index], 3, nullptr); +} + +PyObject *get_attribute_float4x4(void *data, int index) +{ + return Matrix_CreatePyObject_wrap( + static_cast((static_cast(data)[index]).base_ptr()), 4, 4, nullptr); +} + +PyObject *get_attribute_quaternion(void *data, int index) +{ + return Quaternion_CreatePyObject_wrap(static_cast(data)[index], nullptr); +} + +PyObject *get_attribute_color(void *data, int index) +{ + PyObject *result = PyTuple_New(4); + for (int i = 0; i < 4; i++) { + PyTuple_SET_ITEM( + result, i, PyFloat_FromDouble((static_cast(data)[index])[i])); + } + return result; +} + +PyObject *get_attribute_byte_color(void *data, int index) +{ + PyObject *result = PyTuple_New(4); + for (int i = 0; i < 4; i++) { + PyTuple_SET_ITEM(result, i, PyLong_FromLong((static_cast(data)[index])[i])); + } + return result; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name BPy_AttributeArray Map attribute types to functions + * \{ */ + +bool set_attribute_by_type(BPy_AttributeArray *self, int index, PyObject *value) +{ + void *data = self->data_layer->data; + switch (self->data_layer->type) { + case CD_PROP_BOOL: + return set_attribute_bool(data, index, value); + case CD_PROP_FLOAT: + return set_attribute_float(data, index, value); + case CD_PROP_INT8: + return set_attribute_int8(data, index, value); + case CD_PROP_INT32: + return set_attribute_int32(data, index, value); + case CD_PROP_INT32_2D: + return set_attribute_int2(data, index, value); + case CD_PROP_STRING: + return set_attribute_string(data, index, value); + case CD_PROP_FLOAT2: + return set_attribute_float2(data, index, value); + case CD_PROP_FLOAT3: + return set_attribute_float3(data, index, value); + case CD_PROP_FLOAT4X4: + return set_attribute_float4x4(data, index, value); + case CD_PROP_QUATERNION: + return set_attribute_quaternion(data, index, value); + case CD_PROP_COLOR: + return set_attribute_color(data, index, value); + case CD_PROP_BYTE_COLOR: + return set_attribute_byte_color(data, index, value); + } + return false; +} + +PyObject *get_attribute_by_type(const BPy_AttributeArray *self, int index) +{ + void *data = self->data_layer->data; + switch (self->data_layer->type) { + case CD_PROP_BOOL: + return get_attribute_bool(data, index); + case CD_PROP_FLOAT: + return get_attribute_float(data, index); + case CD_PROP_INT8: + return get_attribute_int8(data, index); + case CD_PROP_INT32: + return get_attribute_int32(data, index); + case CD_PROP_INT32_2D: + return get_attribute_int2(data, index); + case CD_PROP_STRING: + return get_attribute_string(data, index); + case CD_PROP_FLOAT2: + return get_attribute_float2(data, index); + case CD_PROP_FLOAT3: + return get_attribute_float3(data, index); + case CD_PROP_FLOAT4X4: + return get_attribute_float4x4(data, index); + case CD_PROP_QUATERNION: + return get_attribute_quaternion(data, index); + case CD_PROP_COLOR: + return get_attribute_color(data, index); + case CD_PROP_BYTE_COLOR: + return get_attribute_byte_color(data, index); + } + return nullptr; +} + +int get_attribute_item_size(BPy_AttributeArray *self) +{ + switch (self->data_layer->type) { + case CD_PROP_BOOL: + return sizeof(bool); + case CD_PROP_FLOAT: + return sizeof(float); + case CD_PROP_INT8: + return sizeof(int8_t); + case CD_PROP_INT32: + return sizeof(int32_t); + case CD_PROP_INT32_2D: + return sizeof(int2); + case CD_PROP_STRING: + return sizeof(MStringProperty); + case CD_PROP_FLOAT2: + return sizeof(float2); + case CD_PROP_FLOAT3: + return sizeof(float3); + case CD_PROP_FLOAT4X4: + return sizeof(float4x4); + case CD_PROP_QUATERNION: + return sizeof(math::Quaternion); + case CD_PROP_COLOR: + return sizeof(ColorGeometry4f); + case CD_PROP_BYTE_COLOR: + return sizeof(ColorGeometry4b); + } + return 0; +} + +StringRef get_attribute_type_description(const BPy_AttributeArray *self) +{ + switch (self->data_layer->type) { + case CD_PROP_BOOL: + return "bool value True/False or 0/1"; + case CD_PROP_FLOAT: + return "float"; + case CD_PROP_INT8: + return "int8 between -128 and 127"; + case CD_PROP_INT32: + return "int"; + case CD_PROP_INT32_2D: + return "tuple (x, y) or list [x, y] of ints"; + case CD_PROP_STRING: + return "string with a maximum of 254 characters"; + case CD_PROP_FLOAT2: + return "mathutils.Vector((x, y)), tuple (x, y) or list [x, y]"; + case CD_PROP_FLOAT3: + return "mathutils.Vector((x, y, z)), tuple (x, y, z) or list [x, y, z]"; + case CD_PROP_FLOAT4X4: + return "mathutils.Matrix() with 4x4 dimensions"; + case CD_PROP_QUATERNION: + return "mathutils.Quaternion((w, x, y, z)), tuple (w, x, y, z) or list [w, x, y, z]"; + case CD_PROP_COLOR: + return "tuple (r, g, b, a) or list [r, g, b, a] of floats"; + case CD_PROP_BYTE_COLOR: + return "tuple (r, g, b, a) or list [r, g, b, a] of int in the range 0-255"; + } + return ""; +} + +char get_buffer_format_by_type(BPy_AttributeArray *self) +{ + switch (self->data_layer->type) { + case CD_PROP_BOOL: + return '?'; + case CD_PROP_FLOAT: + return 'f'; + case CD_PROP_INT8: + return 'b'; + case CD_PROP_INT32: + return 'i'; + case CD_PROP_INT32_2D: + return 'i'; + case CD_PROP_STRING: + return 'B'; + case CD_PROP_FLOAT2: + return 'f'; + case CD_PROP_FLOAT3: + return 'f'; + case CD_PROP_FLOAT4X4: + return 'f'; + case CD_PROP_QUATERNION: + return 'f'; + case CD_PROP_COLOR: + return 'f'; + case CD_PROP_BYTE_COLOR: + return 'B'; + } + return ' '; +} + +bool check_buffer_format(const BPy_AttributeArray *self, const char *buffer_format) +{ + const char f = buffer_format ? *buffer_format : 'B'; + switch (self->data_layer->type) { + case CD_PROP_BOOL: + return f == '?'; + case CD_PROP_FLOAT: + return f == 'f'; + case CD_PROP_INT8: + return f == 'b'; + case CD_PROP_INT32: + return f == 'i' || f == 'l'; + case CD_PROP_INT32_2D: + return f == 'i' || f == 'l'; + case CD_PROP_STRING: + return f == 'B'; + case CD_PROP_FLOAT2: + return f == 'f'; + case CD_PROP_FLOAT3: + return f == 'f'; + case CD_PROP_FLOAT4X4: + return f == 'f'; + case CD_PROP_QUATERNION: + return f == 'f'; + case CD_PROP_COLOR: + return f == 'f'; + case CD_PROP_BYTE_COLOR: + return f == 'B'; + } + return false; +} + +bool check_attribute_type(const BPy_AttributeArray *self) +{ + switch (self->data_layer->type) { + case CD_PROP_BOOL: + case CD_PROP_FLOAT: + case CD_PROP_INT8: + case CD_PROP_INT32: + case CD_PROP_INT32_2D: + case CD_PROP_STRING: + case CD_PROP_FLOAT2: + case CD_PROP_FLOAT3: + case CD_PROP_FLOAT4X4: + case CD_PROP_QUATERNION: + case CD_PROP_COLOR: + case CD_PROP_BYTE_COLOR: + return true; + } + + PyErr_Format( + PyExc_TypeError, + "AttributeArray[index]: unexpected error, data type for attribute '%.200s' not found", + self->data_layer->name); + return false; +} + +} // namespace blender::python::attributearray diff --git a/source/blender/python/intern/bpy.cc b/source/blender/python/intern/bpy.cc index a2affd0f800..fa9b335afea 100644 --- a/source/blender/python/intern/bpy.cc +++ b/source/blender/python/intern/bpy.cc @@ -51,6 +51,7 @@ #include "../generic/python_utildefines.h" /* external util modules */ +#include "../generic/attribute_array_py_api.h" #include "../generic/idprop_py_api.h" #include "../generic/idprop_py_ui_api.h" #include "bpy_msgbus.h" @@ -728,6 +729,7 @@ void BPy_init_modules(bContext *C) /* stand alone utility modules not related to blender directly */ IDProp_Init_Types(); /* not actually a submodule, just types */ IDPropertyUIData_Init_Types(); + AttributeArray_Init_Types(); /* not actually a submodule, just types */ #ifdef WITH_FREESTYLE Freestyle_Init(); #endif diff --git a/source/blender/python/intern/bpy_interface.cc b/source/blender/python/intern/bpy_interface.cc index e14f4ce4e59..14a996ffeec 100644 --- a/source/blender/python/intern/bpy_interface.cc +++ b/source/blender/python/intern/bpy_interface.cc @@ -63,6 +63,7 @@ /* `inittab` initialization functions. */ #include "../bmesh/bmesh_py_api.h" +#include "../generic/attribute_array_py_api.h" #include "../generic/bgl.h" #include "../generic/bl_math_py_api.h" #include "../generic/blf_py_api.h" @@ -300,6 +301,7 @@ static _inittab bpy_internal_modules[] = { #endif {"gpu", BPyInit_gpu}, {"idprop", BPyInit_idprop}, + {"attribute_array", BPyInit_attribute_array}, #ifdef WITH_HYDRA {"_bpy_hydra", BPyInit_hydra}, #endif diff --git a/source/blender/python/intern/bpy_rna.cc b/source/blender/python/intern/bpy_rna.cc index 1464ab24a94..3593ca66806 100644 --- a/source/blender/python/intern/bpy_rna.cc +++ b/source/blender/python/intern/bpy_rna.cc @@ -62,6 +62,7 @@ #include "DEG_depsgraph_query.hh" +#include "../generic/attribute_array_py_api.h" #include "../generic/idprop_py_api.h" /* For IDprop lookups. */ #include "../generic/idprop_py_ui_api.h" #include "../generic/py_capi_rna.h" @@ -1454,7 +1455,12 @@ PyObject *pyrna_prop_to_py(PointerRNA *ptr, PropertyRNA *prop) PointerRNA newptr; newptr = RNA_property_pointer_get(ptr, prop); if (newptr.data) { - ret = pyrna_struct_CreatePyObject(&newptr); + if (RNA_struct_is_attribute_array(newptr.type)) { + ret = BPy_AttributeArray_CreatePyObject(&newptr); + } + else { + ret = pyrna_struct_CreatePyObject(&newptr); + } } else { ret = Py_None;