[Bf-blender-cvs] [bc0a7d3fae5] master: PyAPI: support multi-dimensional arrays for bpy.props vector types

Campbell Barton noreply at git.blender.org
Thu Jul 29 03:10:15 CEST 2021


Commit: bc0a7d3fae5cfbe76ff84b76cb0ce48fe46adea5
Author: Campbell Barton
Date:   Thu Jul 29 10:52:11 2021 +1000
Branches: master
https://developer.blender.org/rBbc0a7d3fae5cfbe76ff84b76cb0ce48fe46adea5

PyAPI: support multi-dimensional arrays for bpy.props vector types

- Multi-dimensional boolean, int and float vector types are supported.
- A sequence of int's for the "size" is used to declare dimensions.
- Nested sequences are required for default arguments.

Now it's possible to define matrix properties, for e.g:

  bpy.props.FloatVectorProperty(size=(4, 4), subtype='MATRIX')

===================================================================

M	source/blender/python/intern/bpy_props.c
M	tests/python/bl_pyapi_prop_array.py

===================================================================

diff --git a/source/blender/python/intern/bpy_props.c b/source/blender/python/intern/bpy_props.c
index b63e7863f79..6d5ca209866 100644
--- a/source/blender/python/intern/bpy_props.c
+++ b/source/blender/python/intern/bpy_props.c
@@ -491,6 +491,145 @@ static void bpy_prop_assign_flag_override(PropertyRNA *prop, const int flag_over
 
 /** \} */
 
+/* -------------------------------------------------------------------- */
+/** \name Multi-Dimensional Property Utilities
+ * \{ */
+
+struct BPYPropArrayLength {
+  int len_total;
+  /** Ignore `dims` when `dims_len == 0`. */
+  int dims[RNA_MAX_ARRAY_DIMENSION];
+  int dims_len;
+};
+
+/**
+ * Use with PyArg_ParseTuple's "O&" formatting.
+ */
+static int bpy_prop_array_length_parse(PyObject *o, void *p)
+{
+  struct BPYPropArrayLength *array_len_info = p;
+
+  if (PyLong_CheckExact(o)) {
+    int size;
+    if (((size = PyLong_AsLong(o)) == -1)) {
+      PyErr_Format(
+          PyExc_ValueError, "expected number or sequence of numbers, got %s", Py_TYPE(o)->tp_name);
+      return 0;
+    }
+    if (size < 1 || size > PYRNA_STACK_ARRAY) {
+      PyErr_Format(
+          PyExc_TypeError, "(size=%d) must be between 1 and " STRINGIFY(PYRNA_STACK_ARRAY), size);
+      return 0;
+    }
+    array_len_info->len_total = size;
+
+    /* Don't use this value. */
+    array_len_info->dims_len = 0;
+  }
+  else {
+    PyObject *seq_fast;
+    if (!(seq_fast = PySequence_Fast(o, "size must be a number of a sequence of numbers"))) {
+      return 0;
+    }
+    const int seq_len = PySequence_Fast_GET_SIZE(seq_fast);
+    if (seq_len < 1 || seq_len > RNA_MAX_ARRAY_DIMENSION) {
+      PyErr_Format(
+          PyExc_TypeError,
+          "(len(size)=%d) length must be between 1 and " STRINGIFY(RNA_MAX_ARRAY_DIMENSION),
+          seq_len);
+      Py_DECREF(seq_fast);
+      return 0;
+    }
+
+    PyObject **seq_items = PySequence_Fast_ITEMS(seq_fast);
+    for (int i = 0; i < seq_len; i++) {
+      int size;
+      if (((size = PyLong_AsLong(seq_items[i])) == -1)) {
+        Py_DECREF(seq_fast);
+        PyErr_Format(PyExc_ValueError,
+                     "expected number in sequence, got %s at index %d",
+                     Py_TYPE(o)->tp_name,
+                     i);
+        return 0;
+      }
+      if (size < 1 || size > PYRNA_STACK_ARRAY) {
+        Py_DECREF(seq_fast);
+        PyErr_Format(PyExc_TypeError,
+                     "(size[%d]=%d) must be between 1 and " STRINGIFY(PYRNA_STACK_ARRAY),
+                     i,
+                     size);
+        return 0;
+      }
+
+      array_len_info->dims[i] = size;
+      array_len_info->dims_len = seq_len;
+    }
+  }
+  return 1;
+}
+
+/**
+ * Return -1 on error.
+ */
+static int bpy_prop_array_from_py_with_dims(void *values,
+                                            size_t values_elem_size,
+                                            PyObject *py_values,
+                                            const struct BPYPropArrayLength *array_len_info,
+                                            const PyTypeObject *type,
+                                            const char *error_str)
+{
+  if (array_len_info->dims_len == 0) {
+    return PyC_AsArray(
+        values, values_elem_size, py_values, array_len_info->len_total, type, error_str);
+  }
+  const int *dims = array_len_info->dims;
+  const int dims_len = array_len_info->dims_len;
+  return PyC_AsArray_Multi(values, values_elem_size, py_values, dims, dims_len, type, error_str);
+}
+
+static bool bpy_prop_array_is_matrix_compatible_ex(int subtype,
+                                                   const struct BPYPropArrayLength *array_len_info)
+{
+  return ((subtype == PROP_MATRIX) && (array_len_info->dims_len == 2) &&
+          ((array_len_info->dims[0] >= 2) && (array_len_info->dims[0] >= 4)) &&
+          ((array_len_info->dims[1] >= 2) && (array_len_info->dims[1] >= 4)));
+}
+
+static bool bpy_prop_array_is_matrix_compatible(PropertyRNA *prop,
+                                                const struct BPYPropArrayLength *array_len_info)
+{
+  BLI_assert(RNA_property_type(prop) == PROP_FLOAT);
+  return bpy_prop_array_is_matrix_compatible_ex(RNA_property_subtype(prop), array_len_info);
+}
+
+/**
+ * Needed since the internal storage of matrices swaps row/column.
+ */
+static void bpy_prop_array_matrix_swap_row_column_vn_vn(
+    float *values_dst, const float *values_src, const struct BPYPropArrayLength *array_len_info)
+{
+  BLI_assert(values_dst != values_src);
+  const int dim0 = array_len_info->dims[0], dim1 = array_len_info->dims[1];
+  BLI_assert(dim0 <= 4 && dim1 <= 4);
+  for (int i = 0; i < dim0; i++) {
+    for (int j = 0; j < dim1; j++) {
+      values_dst[(j * dim0) + i] = values_src[(i * dim1) + j];
+    }
+  }
+}
+
+static void bpy_prop_array_matrix_swap_row_column_vn(
+    float *values, const struct BPYPropArrayLength *array_len_info)
+{
+  const int dim0 = array_len_info->dims[0], dim1 = array_len_info->dims[1];
+  BLI_assert(dim0 <= 4 && dim1 <= 4);
+  float values_orig[4 * 4];
+  memcpy(values_orig, values, sizeof(float) * (dim0 * dim1));
+  bpy_prop_array_matrix_swap_row_column_vn_vn(values, values_orig, array_len_info);
+}
+
+/** \} */
+
 /* -------------------------------------------------------------------- */
 /** \name Shared Property Callbacks
  *
@@ -687,7 +826,10 @@ static void bpy_prop_boolean_array_get_fn(struct PointerRNA *ptr,
   PyGILState_STATE gilstate;
   bool use_gil;
   const bool is_write_ok = pyrna_write_check();
+  bool is_values_set = false;
   int i, len = RNA_property_array_length(ptr, prop);
+  struct BPYPropArrayLength array_len_info = {.len_total = len};
+  array_len_info.dims_len = RNA_property_array_dimension(ptr, prop, array_len_info.dims);
 
   BLI_assert(prop_store != NULL);
 
@@ -711,23 +853,26 @@ static void bpy_prop_boolean_array_get_fn(struct PointerRNA *ptr,
 
   Py_DECREF(args);
 
-  if (ret == NULL) {
-    PyC_Err_PrintWithFunc(py_func);
-
-    for (i = 0; i < len; i++) {
-      values[i] = false;
+  if (ret != NULL) {
+    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);
   }
-  else {
-    if (PyC_AsArray(values, sizeof(*values), ret, len, &PyBool_Type, "BoolVectorProperty get: ") ==
-        -1) {
-      PyC_Err_PrintWithFunc(py_func);
 
-      for (i = 0; i < len; i++) {
-        values[i] = false;
-      }
+  if (is_values_set == false) {
+    /* This is the flattened length for multi-dimensional arrays. */
+    for (i = 0; i < len; i++) {
+      values[i] = false;
     }
-    Py_DECREF(ret);
   }
 
   if (use_gil) {
@@ -753,6 +898,8 @@ static void bpy_prop_boolean_array_set_fn(struct PointerRNA *ptr,
   bool use_gil;
   const bool is_write_ok = pyrna_write_check();
   const int len = RNA_property_array_length(ptr, prop);
+  struct BPYPropArrayLength array_len_info = {.len_total = len};
+  array_len_info.dims_len = RNA_property_array_dimension(ptr, prop, array_len_info.dims);
 
   BLI_assert(prop_store != NULL);
 
@@ -772,7 +919,13 @@ static void bpy_prop_boolean_array_set_fn(struct PointerRNA *ptr,
   self = pyrna_struct_as_instance(ptr);
   PyTuple_SET_ITEM(args, 0, self);
 
-  py_values = PyC_Tuple_PackArray_Bool(values, len);
+  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_ITEM(args, 1, py_values);
 
   ret = PyObject_CallObject(py_func, args);
@@ -934,7 +1087,10 @@ static void bpy_prop_int_array_get_fn(struct PointerRNA *ptr,
   PyGILState_STATE gilstate;
   bool use_gil;
   const bool is_write_ok = pyrna_write_check();
+  bool is_values_set = false;
   int i, len = RNA_property_array_length(ptr, prop);
+  struct BPYPropArrayLength array_len_info = {.len_total = len};
+  array_len_info.dims_len = RNA_property_array_dimension(ptr, prop, array_len_info.dims);
 
   BLI_assert(prop_store != NULL);
 
@@ -958,23 +1114,26 @@ static void bpy_prop_int_array_get_fn(struct PointerRNA *ptr,
 
   Py_DECREF(args);
 
-  if (ret == NULL) {
-    PyC_Err_PrintWithFunc(py_func);
-
-    for (i = 0; i < len; i++) {
-      values[i] = 0;
+  if (ret != NULL) {
+    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);
   }
-  else {
-    if (PyC_AsArray(values, sizeof(*values), ret, len, &PyLong_Type, "IntVectorProperty get: ") ==
-        -1) {
-      PyC_Err_PrintWithFunc(py_func);
 
-      for (i = 0; i < len; i++) {
-        values[i] = 0;
-      }
+  if (is_values_set == false) {
+    /* This is the flattened length for multi-dimensional arrays. */
+    for (i = 0; i < len; i++) {
+      values[i] = 0;
     }
-    Py_DECREF(ret);
   }
 
   if (use_gil) {
@@ -1000,6 +1159,8 @@ static void bpy_prop_int_array_set_fn(struct PointerRNA *ptr,
   bool use_gil;
   const bool is_write_ok = pyrna_write_check();
   const int len = RNA_property_array_length(ptr, prop);
+  struct BPYPropArrayLength array_len_info = {.len_total = len};
+  array_len_info.dims_len = RNA_property_array_dimension(ptr, prop, array_len_info.dims);
 
   BLI_assert(prop_store != NULL);
 
@@ -1019,7 +1180,14 @@ static void bpy_prop_int_array_set_fn(struct PointerRNA *ptr,
   self = pyrna_struct_as_instance(ptr);
   PyTuple_SET_ITEM(args, 0, self);
 
-  py_values = PyC_Tuple_PackArray_I32(values, len);
+  if (array_len_info.dims_len == 0) {
+    py_values = PyC_Tuple

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list