Python part of multidim. array support for RNA complete.

Multidim. arrays can now be modified at any level, for example:

struc.arrayprop = x
struc.arrayprop[i] = x
struc.arrayprop[i][j] = x
struc.arrayprop[i][j][k] = x
etc...

Approriate rvalue type/length checking is done. 

To ensure all works correctly, I wrote automated tests in release/test/rna_array.py.

These tests cover: array/item access, assignment on different levels, tests that proper exceptions are thrown on invalid item access/assignment.

The tests use properties of the RNA Test struct defined in rna_test.c. This struct is only compiled when building with BF_UNIT_TEST=1 scons arg.

Currently unit tests are run manually by loading the script in the Text Editor.
Here's the output I have: http://www.pasteall.org/7644

Things to improve here:
- better exception messages when multidim. array assignment fails. Those we have currently are not very useful for multidim.
- add tests for slice assignment
This commit is contained in:
2009-09-06 15:13:57 +00:00
parent 3d64d65ad9
commit 62138aaa5a
17 changed files with 1041 additions and 213 deletions

View File

@@ -30,33 +30,65 @@
#include "BLI_string.h"
#include "BKE_global.h"
#include "MEM_guardedalloc.h"
typedef void (*ItemConvertFunc)(PyObject *, char *);
typedef int (*ItemTypeCheckFunc)(PyObject *);
typedef void (*RNA_SetArrayFunc)(PointerRNA *, PropertyRNA *, const char *);
#define MAX_ARRAY_DIMENSION 10
/* Ensures that a python sequence has an expected number of items/sub-items and items are of expected type. */
static int pyrna_validate_array(PyObject *seq, unsigned short dim, unsigned short totdim, unsigned short dim_size[],
ItemTypeCheckFunc check_item_type, const char *item_type_str, char *error_str, int error_str_size)
/* convenient way to access array dimension size */
#define DIMSIZE(a) (dimsize[a - 1])
typedef void (*ItemConvertFunc)(PyObject *, char *);
typedef int (*ItemTypeCheckFunc)(PyObject *);
typedef void (*RNA_SetArrayFunc)(PointerRNA *, PropertyRNA *, const char *);
typedef void (*RNA_SetIndexFunc)(PointerRNA *, PropertyRNA *, int index, void *);
/*
arr[3][4][5]
0 1 2 <- dimension index
*/
/*
arr[2] = x
py_to_array_index(arraydim=0, arrayoffset=0, index=2)
validate_array(lvalue_dim=0)
... make real index ...
*/
/* arr[3]=x, self->arraydim is 0, lvalue_dim is 1 */
/* Ensures that a python sequence has expected number of items/sub-items and items are of desired type. */
static int validate_array_type(PyObject *seq, unsigned short dim, unsigned short totdim, unsigned short dimsize[],
ItemTypeCheckFunc check_item_type, const char *item_type_str, const char *error_prefix)
{
int i;
if (dim < totdim) {
/* not the last dimension */
if (dim + 1 < totdim) {
/* check that a sequence contains dimsize[dim] items */
for (i= 0; i < PySequence_Length(seq); i++) {
PyObject *item;
int ok= 1;
item= PySequence_GetItem(seq, i);
if (!PySequence_Check(item)) {
BLI_snprintf(error_str, error_str_size, "expected a %d-dimensional sequence of %s", (int)totdim, item_type_str);
/* BLI_snprintf(error_str, error_str_size, "expected a sequence of %s", item_type_str); */
PyErr_Format(PyExc_TypeError, "%s expected a sequence of %s", error_prefix, item_type_str);
ok= 0;
}
else if (PySequence_Length(item) != dim_size[dim - 1]) {
BLI_snprintf(error_str, error_str_size, "dimension %d should contain %d items", (int)dim, (int)dim_size[dim - 1]);
/* arr[3][4][5]
DIMSIZE(1)=4
DIMSIZE(2)=5
dim=0 */
else if (PySequence_Length(item) != DIMSIZE(dim + 1)) {
/* BLI_snprintf(error_str, error_str_size, "sequences of dimension %d should contain %d items", (int)dim + 1, (int)DIMSIZE(dim + 1)); */
PyErr_Format(PyExc_ValueError, "%s sequences of dimension %d should contain %d items", error_prefix, (int)dim + 1, (int)DIMSIZE(dim + 1));
ok= 0;
}
if (!pyrna_validate_array(item, dim + 1, totdim, dim_size, check_item_type, item_type_str, error_str, error_str_size)) {
else if (!validate_array_type(item, dim + 1, totdim, dimsize, check_item_type, item_type_str, error_prefix)) {
ok= 0;
}
@@ -67,13 +99,15 @@ static int pyrna_validate_array(PyObject *seq, unsigned short dim, unsigned shor
}
}
else {
/* check that items are of correct type */
for (i= 0; i < PySequence_Length(seq); i++) {
PyObject *item= PySequence_GetItem(seq, i);
if (!check_item_type(item)) {
Py_DECREF(item);
BLI_snprintf(error_str, error_str_size, "sequence items should be of type %s", item_type_str);
/* BLI_snprintf(error_str, error_str_size, "sequence items should be of type %s", item_type_str); */
PyErr_Format(PyExc_TypeError, "sequence items should be of type %s", item_type_str);
return 0;
}
@@ -85,7 +119,7 @@ static int pyrna_validate_array(PyObject *seq, unsigned short dim, unsigned shor
}
/* Returns the number of items in a single- or multi-dimensional sequence. */
static int pyrna_count_items(PyObject *seq)
static int count_items(PyObject *seq)
{
int totitem= 0;
@@ -93,7 +127,7 @@ static int pyrna_count_items(PyObject *seq)
int i;
for (i= 0; i < PySequence_Length(seq); i++) {
PyObject *item= PySequence_GetItem(seq, i);
totitem += pyrna_count_items(item);
totitem += count_items(item);
Py_DECREF(item);
}
}
@@ -103,40 +137,103 @@ static int pyrna_count_items(PyObject *seq)
return totitem;
}
static int pyrna_apply_array_length(PointerRNA *ptr, PropertyRNA *prop, int totitem, char *error_str, int error_str_size)
/* Modifies property array length if needed and PROP_DYNAMIC flag is set. */
static int validate_array_length(PyObject *rvalue, PointerRNA *ptr, PropertyRNA *prop, int lvalue_dim, int *totitem, const char *error_prefix)
{
if (RNA_property_flag(prop) & PROP_DYNAMIC) {
/* length can be flexible */
if (RNA_property_array_length(ptr, prop) != totitem) {
if (!RNA_property_dynamic_array_set_length(ptr, prop, totitem)) {
BLI_snprintf(error_str, error_str_size, "%s.%s: array length cannot be changed to %d", RNA_struct_identifier(ptr->type), RNA_property_identifier(prop), totitem);
unsigned short dimsize[MAX_ARRAY_DIMENSION];
int tot, totdim, len;
tot= count_items(rvalue);
totdim= RNA_property_array_dimension(prop, dimsize);
if ((RNA_property_flag(prop) & PROP_DYNAMIC) && lvalue_dim == 0) {
/* length is flexible */
if (RNA_property_array_length(ptr, prop) != tot) {
if (!RNA_property_dynamic_array_set_length(ptr, prop, tot)) {
/* BLI_snprintf(error_str, error_str_size, "%s.%s: array length cannot be changed to %d", RNA_struct_identifier(ptr->type), RNA_property_identifier(prop), tot); */
PyErr_Format(PyExc_ValueError, "%s %s.%s: array length cannot be changed to %d", error_prefix, RNA_struct_identifier(ptr->type), RNA_property_identifier(prop), tot);
return 0;
}
len= tot;
}
}
else {
/* length is a constraint */
int len= RNA_property_array_length(ptr, prop);
if (totitem != len) {
BLI_snprintf(error_str, error_str_size, "sequence must have length of %d", len);
if (!lvalue_dim) {
len= RNA_property_array_length(ptr, prop);
}
/* array item assignment */
else {
int i;
len= 1;
/* arr[3][4][5]
arr[2] = x
dimsize={4, 5}
DIMSIZE(1) = 4
DIMSIZE(2) = 5
lvalue_dim=0, totdim=3
arr[2][3] = x
lvalue_dim=1
arr[2][3][4] = x
lvalue_dim=2 */
for (i= lvalue_dim; i < totdim; i++)
len *= DIMSIZE(i);
}
if (tot != len) {
/* BLI_snprintf(error_str, error_str_size, "sequence must have length of %d", len); */
PyErr_Format(PyExc_ValueError, "%s sequence must have %d items total", error_prefix, len);
return 0;
}
}
*totitem= len;
return 1;
}
static char *pyrna_py_to_array(PyObject *seq, unsigned short dim, unsigned short totdim, char *data, unsigned int item_size, ItemConvertFunc convert_item)
static int validate_array(PyObject *rvalue, PointerRNA *ptr, PropertyRNA *prop, int lvalue_dim, ItemTypeCheckFunc check_item_type, const char *item_type_str, int *totitem, const char *error_prefix)
{
unsigned short dimsize[MAX_ARRAY_DIMENSION];
int totdim= RNA_property_array_dimension(prop, dimsize);
/* validate type first because length validation may modify property array length */
if (!validate_array_type(rvalue, lvalue_dim, totdim, dimsize, check_item_type, item_type_str, error_prefix))
return 0;
return validate_array_length(rvalue, ptr, prop, lvalue_dim, totitem, error_prefix);
}
static char *copy_values(PyObject *seq, PointerRNA *ptr, PropertyRNA *prop, unsigned short dim, char *data, unsigned int item_size, int *index, ItemConvertFunc convert_item, RNA_SetIndexFunc rna_set_index)
{
unsigned int i;
int totdim= RNA_property_array_dimension(prop, NULL);
for (i= 0; i < PySequence_Length(seq); i++) {
PyObject *item= PySequence_GetItem(seq, i);
if (dim < totdim) {
data= pyrna_py_to_array(item, dim + 1, totdim, data, item_size, convert_item);
if (dim + 1 < totdim) {
data= copy_values(item, ptr, prop, dim + 1, data, item_size, index, convert_item, rna_set_index);
}
else {
convert_item(item, data);
data += item_size;
if (!data) {
char value[sizeof(int)];
convert_item(item, value);
rna_set_index(ptr, prop, *index, value);
*index = *index + 1;
}
else {
convert_item(item, data);
data += item_size;
}
}
Py_DECREF(item);
@@ -145,21 +242,15 @@ static char *pyrna_py_to_array(PyObject *seq, unsigned short dim, unsigned short
return data;
}
static int pyrna_py_to_array_generic(PyObject *py, PointerRNA *ptr, PropertyRNA *prop, char *param_data, char *error_str, int error_str_size,
ItemTypeCheckFunc check_item_type, const char *item_type_str, int item_size, ItemConvertFunc convert_item, RNA_SetArrayFunc rna_set_array)
static int py_to_array(PyObject *py, PointerRNA *ptr, PropertyRNA *prop, char *param_data, ItemTypeCheckFunc check_item_type, const char *item_type_str, int item_size, ItemConvertFunc convert_item, RNA_SetArrayFunc rna_set_array, const char *error_prefix)
{
unsigned short totdim, dim_size[100];
unsigned short totdim, dim_size[MAX_ARRAY_DIMENSION];
int totitem;
char *data= NULL;
totdim= RNA_property_array_dimension(prop, dim_size);
if (!pyrna_validate_array(py, 1, totdim, dim_size, check_item_type, item_type_str, error_str, error_str_size))
return 0;
totitem= pyrna_count_items(py);
if (!pyrna_apply_array_length(ptr, prop, totitem, error_str, error_str_size))
if (!validate_array(py, ptr, prop, 0, check_item_type, item_type_str, &totitem, error_prefix))
return 0;
if (totitem) {
@@ -168,7 +259,7 @@ static int pyrna_py_to_array_generic(PyObject *py, PointerRNA *ptr, PropertyRNA
else
data= param_data;
pyrna_py_to_array(py, 1, totdim, data, item_size, convert_item);
copy_values(py, ptr, prop, 0, data, item_size, NULL, convert_item, NULL);
if (param_data) {
if (RNA_property_flag(prop) & PROP_DYNAMIC) {
@@ -186,50 +277,236 @@ static int pyrna_py_to_array_generic(PyObject *py, PointerRNA *ptr, PropertyRNA
return 1;
}
static void pyrna_py_to_float(PyObject *py, char *data)
static int py_to_array_index(PyObject *py, PointerRNA *ptr, PropertyRNA *prop, int lvalue_dim, int arrayoffset, int index, ItemTypeCheckFunc check_item_type, const char *item_type_str, ItemConvertFunc convert_item, RNA_SetIndexFunc rna_set_index, const char *error_prefix)
{
unsigned short totdim, dimsize[MAX_ARRAY_DIMENSION];
int totitem, i;
totdim= RNA_property_array_dimension(prop, dimsize);
/* convert index */
/* arr[3][4][5]
arr[2] = x
lvalue_dim=0, index = 0 + 2 * 4 * 5
arr[2][3] = x
lvalue_dim=1, index = 40 + 3 * 5 */
lvalue_dim++;
for (i= lvalue_dim; i < totdim; i++)
index *= DIMSIZE(i);
index += arrayoffset;
if (!validate_array(py, ptr, prop, lvalue_dim, check_item_type, item_type_str, &totitem, error_prefix))
return 0;
if (totitem)
copy_values(py, ptr, prop, lvalue_dim, NULL, 0, &index, convert_item, rna_set_index);
return 1;
}
static void py_to_float(PyObject *py, char *data)
{
*(float*)data= (float)PyFloat_AsDouble(py);
}
static void pyrna_py_to_int(PyObject *py, char *data)
static void py_to_int(PyObject *py, char *data)
{
*(int*)data= (int)PyLong_AsSsize_t(py);
}
static void pyrna_py_to_boolean(PyObject *py, char *data)
static void py_to_bool(PyObject *py, char *data)
{
*(int*)data= (int)PyObject_IsTrue(py);
}
static int py_float_check(PyObject *py)
{
return PyFloat_Check(py) || (PyIndex_Check(py));
/* accept both floats and integers */
return PyFloat_Check(py) || PyLong_Check(py);
}
static int py_int_check(PyObject *py)
{
return PyLong_Check(py) || (PyIndex_Check(py));
/* accept only integers */
return PyLong_Check(py);
}
static int py_bool_check(PyObject *py)
{
return PyBool_Check(py) || (PyIndex_Check(py));
return PyBool_Check(py);
}
int pyrna_py_to_float_array(PyObject *py, PointerRNA *ptr, PropertyRNA *prop, char *param_data, char *error_str, int error_str_size)
static void float_set_index(PointerRNA *ptr, PropertyRNA *prop, int index, void *value)
{
return pyrna_py_to_array_generic(py, ptr, prop, param_data, error_str, error_str_size,
py_float_check, "float", sizeof(float), pyrna_py_to_float, (RNA_SetArrayFunc)RNA_property_float_set_array);
RNA_property_float_set_index(ptr, prop, index, *(float*)value);
}
int pyrna_py_to_int_array(PyObject *py, PointerRNA *ptr, PropertyRNA *prop, char *param_data, char *error_str, int error_str_size)
static void int_set_index(PointerRNA *ptr, PropertyRNA *prop, int index, void *value)
{
return pyrna_py_to_array_generic(py, ptr, prop, param_data, error_str, error_str_size,
py_int_check, "int", sizeof(int), pyrna_py_to_int, (RNA_SetArrayFunc)RNA_property_int_set_array);
RNA_property_int_set_index(ptr, prop, index, *(int*)value);
}
int pyrna_py_to_boolean_array(PyObject *py, PointerRNA *ptr, PropertyRNA *prop, char *param_data, char *error_str, int error_str_size)
static void bool_set_index(PointerRNA *ptr, PropertyRNA *prop, int index, void *value)
{
return pyrna_py_to_array_generic(py, ptr, prop, param_data, error_str, error_str_size,
py_bool_check, "boolean", sizeof(int), pyrna_py_to_boolean, (RNA_SetArrayFunc)RNA_property_boolean_set_array);
RNA_property_boolean_set_index(ptr, prop, index, *(int*)value);
}
int pyrna_py_to_array(PointerRNA *ptr, PropertyRNA *prop, char *param_data, PyObject *py, const char *error_prefix)
{
int ret;
switch (RNA_property_type(prop)) {
case PROP_FLOAT:
ret= py_to_array(py, ptr, prop, param_data, py_float_check, "float", sizeof(float), py_to_float, (RNA_SetArrayFunc)RNA_property_float_set_array, error_prefix);
break;
case PROP_INT:
ret= py_to_array(py, ptr, prop, param_data, py_int_check, "int", sizeof(int), py_to_int, (RNA_SetArrayFunc)RNA_property_int_set_array, error_prefix);
break;
case PROP_BOOLEAN:
ret= py_to_array(py, ptr, prop, param_data, py_bool_check, "boolean", sizeof(int), py_to_bool, (RNA_SetArrayFunc)RNA_property_boolean_set_array, error_prefix);
break;
default:
PyErr_SetString(PyExc_TypeError, "not an array type");
ret= 0;
}
return ret;
}
int pyrna_py_to_array_index(PointerRNA *ptr, PropertyRNA *prop, int arraydim, int arrayoffset, int index, PyObject *py, const char *error_prefix)
{
int ret;
switch (RNA_property_type(prop)) {
case PROP_FLOAT:
ret= py_to_array_index(py, ptr, prop, arraydim, arrayoffset, index, py_float_check, "float", py_to_float, float_set_index, error_prefix);
break;
case PROP_INT:
ret= py_to_array_index(py, ptr, prop, arraydim, arrayoffset, index, py_int_check, "int", py_to_int, int_set_index, error_prefix);
break;
case PROP_BOOLEAN:
ret= py_to_array_index(py, ptr, prop, arraydim, arrayoffset, index, py_bool_check, "boolean", py_to_bool, bool_set_index, error_prefix);
break;
default:
PyErr_SetString(PyExc_TypeError, "not an array type");
ret= 0;
}
return ret;
}
static PyObject *pyrna_array_item(PointerRNA *ptr, PropertyRNA *prop, int index)
{
PyObject *item;
switch (RNA_property_type(prop)) {
case PROP_FLOAT:
item= PyFloat_FromDouble(RNA_property_float_get_index(ptr, prop, index));
break;
case PROP_BOOLEAN:
item= PyBool_FromLong(RNA_property_boolean_get_index(ptr, prop, index));
break;
case PROP_INT:
item= PyLong_FromSsize_t(RNA_property_int_get_index(ptr, prop, index));
break;
default:
PyErr_SetString(PyExc_TypeError, "not an array type");
item= NULL;
}
return item;
}
#if 0
/* XXX this is not used (and never will?) */
/* Given an array property, creates an N-dimensional tuple of values. */
static PyObject *pyrna_py_from_array_internal(PointerRNA *ptr, PropertyRNA *prop, int dim, int *index)
{
PyObject *tuple;
int i, len;
int totdim= RNA_property_array_dimension(prop, NULL);
len= RNA_property_multidimensional_array_length(ptr, prop, dim);
tuple= PyTuple_New(len);
for (i= 0; i < len; i++) {
PyObject *item;
if (dim + 1 < totdim)
item= pyrna_py_from_array_internal(ptr, prop, dim + 1, index);
else {
item= pyrna_array_item(ptr, prop, *index);
*index= *index + 1;
}
if (!item) {
Py_DECREF(tuple);
return NULL;
}
PyTuple_SetItem(tuple, i, item);
}
return tuple;
}
#endif
PyObject *pyrna_py_from_array_index(BPy_PropertyRNA *self, int index)
{
int totdim, i, len;
unsigned short dimsize[MAX_ARRAY_DIMENSION];
BPy_PropertyRNA *ret= NULL;
/* just in case check */
len= RNA_property_multidimensional_array_length(&self->ptr, self->prop, self->arraydim);
if (index >= len || index < 0) {
/* this shouldn't happen because higher level funcs must check for invalid index */
if (G.f & G_DEBUG) printf("pyrna_py_from_array_index: invalid index %d for array with length=%d\n", index, len);
PyErr_SetString(PyExc_IndexError, "out of range");
return NULL;
}
totdim= RNA_property_array_dimension(self->prop, dimsize);
if (self->arraydim + 1 < totdim) {
ret= (BPy_PropertyRNA*)pyrna_prop_CreatePyObject(&self->ptr, self->prop);
ret->arraydim= self->arraydim + 1;
/* arr[3][4][5]
x = arr[2]
index = 0 + 2 * 4 * 5
x = arr[2][3]
index = offset + 3 * 5 */
for (i= self->arraydim + 1; i < totdim; i++)
index *= DIMSIZE(i);
ret->arrayoffset= self->arrayoffset + index;
}
else {
index = self->arrayoffset + index;
ret= (BPy_PropertyRNA*)pyrna_array_item(&self->ptr, self->prop, index);
}
return (PyObject*)ret;
}
PyObject *pyrna_py_from_array(PointerRNA *ptr, PropertyRNA *prop)
{
PyObject *ret;
ret= pyrna_math_object_from_array(ptr, prop);
/* is this a maths object? */
if (ret) return ret;
return pyrna_prop_CreatePyObject(ptr, prop);
}