Fix T58391: correctly handle Curve Shape Key layout in Python API.
authorAlexander Gavrilov <angavrilov@gmail.com>
Wed, 5 Dec 2018 15:30:42 +0000 (18:30 +0300)
committerAlexander Gavrilov <angavrilov@gmail.com>
Wed, 5 Dec 2018 15:36:52 +0000 (18:36 +0300)
The layout changed when the radius property was added to shape
keys in 2.8, but the RNA code wasn't updated.

Also, even before that, the code didn't do anything to correctly
handle mixing sub-curves of different type (nurbs vs bezier) in
the same Curve object. Now that case is handled correctly but not
very efficiently by allocating a mapping table when necessary. To
recover some performance, a custom index lookup function is added.

source/blender/makesrna/intern/rna_key.c

index ed1e0d2690e95296b95c15eb6b3a690cc1b5ea99..684756c0b12a6b6dc3194007a45121a84e795810 100644 (file)
@@ -35,6 +35,7 @@
 
 #include "BLI_string_utils.h"
 #include "BLI_utildefines.h"
+#include "BLI_math.h"
 
 #include "BLT_translation.h"
 
@@ -42,6 +43,8 @@
 #include "RNA_define.h"
 #include "RNA_enum_types.h"
 
+#include "MEM_guardedalloc.h"
+
 #include "rna_internal.h"
 
 #ifdef RNA_RUNTIME
@@ -336,6 +339,19 @@ static void rna_ShapeKeyCurvePoint_tilt_set(PointerRNA *ptr, float value)
        vec[3] = value;
 }
 
+static float rna_ShapeKeyCurvePoint_radius_get(PointerRNA *ptr)
+{
+       float *vec = (float *)ptr->data;
+       return vec[4];
+}
+
+static void rna_ShapeKeyCurvePoint_radius_set(PointerRNA *ptr, float value)
+{
+       float *vec = (float *)ptr->data;
+       CLAMP_MIN(value, 0.0f);
+       vec[4] = value;
+}
+
 static void rna_ShapeKeyBezierPoint_co_get(PointerRNA *ptr, float *values)
 {
        float *vec = (float *)ptr->data;
@@ -390,35 +406,166 @@ static void rna_ShapeKeyBezierPoint_handle_2_co_set(PointerRNA *ptr, const float
        vec[6 + 2] = values[2];
 }
 
-#if 0
 static float rna_ShapeKeyBezierPoint_tilt_get(PointerRNA *ptr)
 {
        float *vec = (float *)ptr->data;
-       return vec[10];
+       return vec[9];
 }
 
 static void rna_ShapeKeyBezierPoint_tilt_set(PointerRNA *ptr, float value)
 {
        float *vec = (float *)ptr->data;
+       vec[9] = value;
+}
+
+static float rna_ShapeKeyBezierPoint_radius_get(PointerRNA *ptr)
+{
+       float *vec = (float *)ptr->data;
+       return vec[10];
+}
+
+static void rna_ShapeKeyBezierPoint_radius_set(PointerRNA *ptr, float value)
+{
+       float *vec = (float *)ptr->data;
+       CLAMP_MIN(value, 0.0f);
        vec[10] = value;
 }
-#endif
+
+/* Indexing and iteration of Curve points through sub-curves. */
+typedef struct NurbInfo {
+       Nurb *nu;
+       int nurb_size, nurb_elem_step;
+
+       /* Current index in the Nurb */
+       int nurb_index;
+
+       /* Total index as item and element. */
+       int item_index, elem_index;
+} NurbInfo;
+
+StructRNA *rna_ShapeKey_curve_point_type(Nurb *nu)
+{
+       if (nu->bezt) {
+               return &RNA_ShapeKeyBezierPoint;
+       }
+       else {
+               return &RNA_ShapeKeyCurvePoint;
+       }
+}
+
+static void rna_ShapeKey_NurbInfo_init(NurbInfo *r_info, Nurb *nu)
+{
+       r_info->nu = nu;
+
+       if (nu->bezt) {
+               r_info->nurb_size = nu->pntsu;
+               r_info->nurb_elem_step = KEYELEM_ELEM_LEN_BEZTRIPLE;
+       }
+       else {
+               r_info->nurb_size = nu->pntsu * nu->pntsv;
+               r_info->nurb_elem_step = KEYELEM_ELEM_LEN_BPOINT;
+       }
+}
+
+static void rna_ShapeKey_NurbInfo_step(NurbInfo *r_info, Nurb *nu, int *p_raw_index, bool input_elem)
+{
+       rna_ShapeKey_NurbInfo_init(r_info, nu);
+
+       if (input_elem) {
+               r_info->nurb_index = MIN2(r_info->nurb_size, *p_raw_index / r_info->nurb_elem_step);
+               *p_raw_index -= r_info->nurb_size * r_info->nurb_elem_step;
+       }
+       else {
+               r_info->nurb_index = MIN2(r_info->nurb_size, *p_raw_index);
+               *p_raw_index -= r_info->nurb_size;
+       }
+
+       r_info->item_index += r_info->nurb_index;
+       r_info->elem_index += r_info->nurb_index * r_info->nurb_elem_step;
+}
+
+static void rna_ShapeKey_NurbInfo_find_index(Key *key, int raw_index, bool input_elem, NurbInfo *r_info)
+{
+       Curve *cu = (Curve *)key->from;
+
+       memset(r_info, 0, sizeof(*r_info));
+
+       for (Nurb *nu = cu->nurb.first; nu && raw_index >= 0; nu = nu->next) {
+               rna_ShapeKey_NurbInfo_step(r_info, nu, &raw_index, input_elem);
+       }
+}
+
+static int rna_ShapeKey_curve_find_index(Key *key, int elem_index)
+{
+       NurbInfo info;
+       rna_ShapeKey_NurbInfo_find_index(key, elem_index, true, &info);
+       return info.item_index;
+}
+
+typedef struct ShapeKeyCurvePoint {
+       StructRNA *type;
+       void *data;
+} ShapeKeyCurvePoint;
+
+/* Build a mapping array for Curve objects with mixed sub-curve types. */
+static void rna_ShapeKey_data_begin_mixed(CollectionPropertyIterator *iter, Key *key, KeyBlock *kb, Curve *cu)
+{
+       int point_count = rna_ShapeKey_curve_find_index(key, kb->totelem);
+
+       ShapeKeyCurvePoint *points = MEM_malloc_arrayN(sizeof(ShapeKeyCurvePoint), point_count, __func__);
+
+       char *databuf = kb->data;
+       int items_left = point_count;
+       NurbInfo info = { NULL };
+
+       for (Nurb *nu = cu->nurb.first; nu && items_left > 0; nu = nu->next) {
+               ShapeKeyCurvePoint *nurb_points = points + info.item_index;
+               char *nurb_data = databuf + info.elem_index * key->elemsize;
+
+               rna_ShapeKey_NurbInfo_step(&info, nu, &items_left, false);
+
+               StructRNA *type = rna_ShapeKey_curve_point_type(nu);
+
+               for (int i = 0; i < info.nurb_index; i++) {
+                       nurb_points[i].type = type;
+                       nurb_points[i].data = nurb_data + i * info.nurb_elem_step * key->elemsize;
+               }
+       }
+
+       rna_iterator_array_begin(iter, points, sizeof(*points), point_count, true, NULL);
+}
 
 static void rna_ShapeKey_data_begin(CollectionPropertyIterator *iter, PointerRNA *ptr)
 {
        Key *key = rna_ShapeKey_find_key(ptr->id.data);
        KeyBlock *kb = (KeyBlock *)ptr->data;
-       Curve *cu;
-       Nurb *nu;
        int tot = kb->totelem, size = key->elemsize;
 
-       if (GS(key->from->name) == ID_CU) {
-               cu = (Curve *)key->from;
-               nu = cu->nurb.first;
+       if (GS(key->from->name) == ID_CU && tot > 0) {
+               Curve *cu = (Curve *)key->from;
+               StructRNA *type = NULL;
+               NurbInfo info = { 0 };
+
+               /* Check if all sub-curves have the same type. */
+               for (Nurb *nu = cu->nurb.first; nu; nu = nu->next) {
+                       if (type == NULL) {
+                               type = rna_ShapeKey_curve_point_type(nu);
+                               rna_ShapeKey_NurbInfo_init(&info, nu);
+                       }
+                       else if (type != rna_ShapeKey_curve_point_type(nu)) {
+                               type = NULL;
+                               break;
+                       }
+               }
 
-               if (nu->bezt) {
-                       tot /= 3;
-                       size *= 3;
+               /* If types are mixed, build a mapping array. */
+               if (type == NULL) {
+                       rna_ShapeKey_data_begin_mixed(iter, key, kb, cu);
+                       return;
+               }
+               else {
+                       tot /= info.nurb_elem_step;
+                       size *= info.nurb_elem_step;
                }
        }
 
@@ -429,16 +576,10 @@ static int rna_ShapeKey_data_length(PointerRNA *ptr)
 {
        Key *key = rna_ShapeKey_find_key(ptr->id.data);
        KeyBlock *kb = (KeyBlock *)ptr->data;
-       Curve *cu;
-       Nurb *nu;
        int tot = kb->totelem;
 
        if (GS(key->from->name) == ID_CU) {
-               cu = (Curve *)key->from;
-               nu = cu->nurb.first;
-
-               if (nu->bezt)
-                       tot /= 3;
+               tot = rna_ShapeKey_curve_find_index(key, tot);
        }
 
        return tot;
@@ -447,23 +588,57 @@ static int rna_ShapeKey_data_length(PointerRNA *ptr)
 static PointerRNA rna_ShapeKey_data_get(CollectionPropertyIterator *iter)
 {
        Key *key = rna_ShapeKey_find_key(iter->parent.id.data);
-       StructRNA *type;
-       Curve *cu;
-       Nurb *nu;
+       void *ptr = rna_iterator_array_get(iter);
+       StructRNA *type = &RNA_ShapeKeyPoint;
+
+       /* If data_begin allocated a mapping array, access it. */
+       if (iter->internal.array.free_ptr) {
+               ShapeKeyCurvePoint *point = ptr;
+
+               return rna_pointer_inherit_refine(&iter->parent, point->type, point->data);
+       }
 
        if (GS(key->from->name) == ID_CU) {
-               cu = (Curve *)key->from;
-               nu = cu->nurb.first;
+               Curve *cu = (Curve *)key->from;
 
-               if (nu->bezt)
-                       type = &RNA_ShapeKeyBezierPoint;
-               else
-                       type = &RNA_ShapeKeyCurvePoint;
+               type = rna_ShapeKey_curve_point_type(cu->nurb.first);
+       }
+
+       return rna_pointer_inherit_refine(&iter->parent, type, ptr);
+}
+
+int rna_ShapeKey_data_lookup_int(PointerRNA *ptr, int index, PointerRNA *r_ptr)
+{
+       Key *key = rna_ShapeKey_find_key(ptr->id.data);
+       KeyBlock *kb = (KeyBlock *)ptr->data;
+       int elemsize = key->elemsize;
+       char *databuf = kb->data;
+
+       memset(r_ptr, 0, sizeof(*r_ptr));
+
+       if (index < 0) {
+               return false;
+       }
+
+       if (GS(key->from->name) == ID_CU) {
+               NurbInfo info;
+               rna_ShapeKey_NurbInfo_find_index(key, index, false, &info);
+
+               if (info.nu && info.nurb_index < info.nurb_size) {
+                       StructRNA *type = rna_ShapeKey_curve_point_type(info.nu);
+
+                       *r_ptr = rna_pointer_inherit_refine(ptr, type, databuf + elemsize * info.elem_index);
+                       return true;
+               }
+       }
+       else {
+               if (index < kb->totelem) {
+                       *r_ptr = rna_pointer_inherit_refine(ptr, &RNA_ShapeKeyPoint, databuf + elemsize * index);
+                       return true;
+               }
        }
-       else
-               type = &RNA_ShapeKeyPoint;
 
-       return rna_pointer_inherit_refine(&iter->parent, type, rna_iterator_array_get(iter));
+       return false;
 }
 
 static char *rna_ShapeKey_path(PointerRNA *ptr)
@@ -547,19 +722,6 @@ static int rna_ShapeKeyPoint_get_index(Key *key, KeyBlock *kb, float *point)
        return (int)(pt - start) / key->elemsize;
 }
 
-static int rna_ShapeKeyBezierPoint_get_index(KeyBlock *kb, float *point)
-{
-       float *start = (float *)kb->data;
-
-       /* Unlike with rna_ShapeKeyPoint_get_index(), we cannot use key->elemsize here
-        * since the default value for curves (16) is actually designed for BPoints
-        * (i.e. NURBS Surfaces). The magic number "12" here was found by empirical
-        * testing on a 64-bit system, and is similar to what's used for meshes and
-        * lattices. For more details, see T38013
-        */
-       return (int)(point - start) / 12;
-}
-
 static char *rna_ShapeKeyPoint_path(PointerRNA *ptr)
 {
        ID *id = (ID *)ptr->id.data;
@@ -574,10 +736,11 @@ static char *rna_ShapeKeyPoint_path(PointerRNA *ptr)
                char name_esc_kb[sizeof(kb->name) * 2];
                int index;
 
-               if (ptr->type == &RNA_ShapeKeyBezierPoint)
-                       index = rna_ShapeKeyBezierPoint_get_index(kb, point);
-               else
-                       index = rna_ShapeKeyPoint_get_index(key, kb, point);
+               index = rna_ShapeKeyPoint_get_index(key, kb, point);
+
+               if (ELEM(ptr->type, &RNA_ShapeKeyBezierPoint, &RNA_ShapeKeyCurvePoint)) {
+                       index = rna_ShapeKey_curve_find_index(key, index);
+               }
 
                BLI_strescape(name_esc_kb, kb->name, sizeof(name_esc_kb));
 
@@ -600,6 +763,8 @@ const EnumPropertyItem rna_enum_keyblock_type_items[] = {
        {0, NULL, 0, NULL, NULL}
 };
 
+static const float tilt_limit = DEG2RADF(21600.0f);
+
 static void rna_def_keydata(BlenderRNA *brna)
 {
        StructRNA *srna;
@@ -626,9 +791,17 @@ static void rna_def_keydata(BlenderRNA *brna)
        RNA_def_property_ui_text(prop, "Location", "");
        RNA_def_property_update(prop, 0, "rna_Key_update_data");
 
-       prop = RNA_def_property(srna, "tilt", PROP_FLOAT, PROP_NONE);
+       prop = RNA_def_property(srna, "tilt", PROP_FLOAT, PROP_ANGLE);
        RNA_def_property_float_funcs(prop, "rna_ShapeKeyCurvePoint_tilt_get", "rna_ShapeKeyCurvePoint_tilt_set", NULL);
-       RNA_def_property_ui_text(prop, "Tilt", "");
+       RNA_def_property_range(prop, -tilt_limit, tilt_limit);
+       RNA_def_property_ui_range(prop, -tilt_limit, tilt_limit, 10, 3);
+       RNA_def_property_ui_text(prop, "Tilt", "Tilt in 3D View");
+       RNA_def_property_update(prop, 0, "rna_Key_update_data");
+
+       prop = RNA_def_property(srna, "radius", PROP_FLOAT, PROP_NONE);
+       RNA_def_property_float_funcs(prop, "rna_ShapeKeyCurvePoint_radius_get", "rna_ShapeKeyCurvePoint_radius_set", NULL);
+       RNA_def_property_range(prop, 0.0f, FLT_MAX);
+       RNA_def_property_ui_text(prop, "Radius", "Radius for beveling");
        RNA_def_property_update(prop, 0, "rna_Key_update_data");
 
        srna = RNA_def_struct(brna, "ShapeKeyBezierPoint", NULL);
@@ -656,13 +829,18 @@ static void rna_def_keydata(BlenderRNA *brna)
        RNA_def_property_ui_text(prop, "Handle 2 Location", "");
        RNA_def_property_update(prop, 0, "rna_Key_update_data");
 
-       /* appears to be unused currently */
-#if 0
-       prop = RNA_def_property(srna, "tilt", PROP_FLOAT, PROP_NONE);
+       prop = RNA_def_property(srna, "tilt", PROP_FLOAT, PROP_ANGLE);
        RNA_def_property_float_funcs(prop, "rna_ShapeKeyBezierPoint_tilt_get", "rna_ShapeKeyBezierPoint_tilt_set", NULL);
-       RNA_def_property_ui_text(prop, "Tilt", "");
+       RNA_def_property_range(prop, -tilt_limit, tilt_limit);
+       RNA_def_property_ui_range(prop, -tilt_limit, tilt_limit, 10, 3);
+       RNA_def_property_ui_text(prop, "Tilt", "Tilt in 3D View");
+       RNA_def_property_update(prop, 0, "rna_Key_update_data");
+
+       prop = RNA_def_property(srna, "radius", PROP_FLOAT, PROP_NONE);
+       RNA_def_property_float_funcs(prop, "rna_ShapeKeyBezierPoint_radius_get", "rna_ShapeKeyBezierPoint_radius_set", NULL);
+       RNA_def_property_range(prop, 0.0f, FLT_MAX);
+       RNA_def_property_ui_text(prop, "Radius", "Radius for beveling");
        RNA_def_property_update(prop, 0, "rna_Key_update_data");
-#endif
 }
 
 static void rna_def_keyblock(BlenderRNA *brna)
@@ -743,7 +921,7 @@ static void rna_def_keyblock(BlenderRNA *brna)
        RNA_def_property_struct_type(prop, "UnknownType");
        RNA_def_property_ui_text(prop, "Data", "");
        RNA_def_property_collection_funcs(prop, "rna_ShapeKey_data_begin", NULL, NULL, "rna_ShapeKey_data_get",
-                                         "rna_ShapeKey_data_length", NULL, NULL, NULL);
+                                         "rna_ShapeKey_data_length", "rna_ShapeKey_data_lookup_int", NULL, NULL);
 
        /* XXX multi-dim dynamic arrays are very badly supported by (py)rna currently, those are defined for the day
         *     it works better, for now user will get a 1D tuple...