RNA: support setting default values for custom properties.
authorAlexander Gavrilov <angavrilov@gmail.com>
Sat, 15 Dec 2018 19:37:12 +0000 (22:37 +0300)
committerAlexander Gavrilov <angavrilov@gmail.com>
Wed, 19 Dec 2018 11:20:35 +0000 (14:20 +0300)
NLA requires a usable default value for all properties that
are to be animated via it, without any exceptions. This is
the real cause of T36496: using the default of 0 for a scale
related custom property obviously doesn't work.

Thus, to really fix this it is necessary to support configurable
default values for custom properties, which are very frequently
used in rigs for auxiliary settings. For common use it is enough
to support this for scalar float and integer properties.

The default can be set via the custom property configuration
popup, or a right click menu option. In addition, to help in
updating old rigs, an operator that saves current values as
defaults for all object and bone properties is added.

Reviewers: campbellbarton, brecht

Differential Revision: https://developer.blender.org/D4084

release/scripts/modules/rna_prop_ui.py
release/scripts/startup/bl_operators/object.py
release/scripts/startup/bl_operators/wm.py
release/scripts/startup/bl_ui/space_view3d.py
source/blender/editors/interface/interface_context_menu.c
source/blender/editors/interface/interface_ops.c
source/blender/makesrna/RNA_access.h
source/blender/makesrna/intern/rna_access.c

index fc17cc60c6c63e74a1e994ce696f7fade2b31871..f08390cfd6d64f534457a5ec3ed8c507de6fdca6 100644 (file)
@@ -96,6 +96,25 @@ def rna_idprop_has_properties(rna_item):
     return (nbr_props > 1) or (nbr_props and '_RNA_UI' not in keys)
 
 
+def rna_idprop_ui_prop_default_set(item, prop, value):
+    defvalue = None
+    try:
+        prop_type = type(item[prop])
+
+        if prop_type in {int, float}:
+            defvalue = prop_type(value)
+    except KeyError:
+        pass
+
+    if defvalue:
+        rna_ui = rna_idprop_ui_prop_get(item, prop, True)
+        rna_ui["default"] = defvalue
+    else:
+        rna_ui = rna_idprop_ui_prop_get(item, prop)
+        if rna_ui and "default" in rna_ui:
+            del rna_ui["default"]
+
+
 def draw(layout, context, context_member, property_type, use_edit=True):
 
     def assign_props(prop, val, key):
index 949e2baff03193a4e3ec01f8b1c52a24440778a0..660f87aea0dfe212f57b9a2eb86abea531f13562 100644 (file)
@@ -943,6 +943,49 @@ class LoadReferenceImage(LoadImageAsEmpty, Operator):
         pass
 
 
+class OBJECT_OT_assign_property_defaults(Operator):
+    """Assign the current values of custom properties as their defaults, for use as part of the rest pose state in NLA track mixing"""
+    bl_idname = "object.assign_property_defaults"
+    bl_label = "Assign Custom Property Values as Default"
+    bl_options = {'UNDO', 'REGISTER'}
+
+    process_data: BoolProperty(name="Process data properties", default=True)
+    process_bones: BoolProperty(name="Process bone properties", default=True)
+
+    @classmethod
+    def poll(cls, context):
+        obj = context.active_object
+        return obj is not None and obj.library is None and obj.mode in {'POSE', 'OBJECT'}
+
+    @staticmethod
+    def assign_defaults(obj):
+        from rna_prop_ui import rna_idprop_ui_prop_default_set
+
+        rna_properties = {'_RNA_UI'} | {prop.identifier for prop in obj.bl_rna.properties if prop.is_runtime}
+
+        for prop, value in obj.items():
+            if prop not in rna_properties:
+                rna_idprop_ui_prop_default_set(obj, prop, value)
+
+    def execute(self, context):
+        obj = context.active_object
+
+        self.assign_defaults(obj)
+
+        if self.process_bones and obj.pose:
+            for pbone in obj.pose.bones:
+                self.assign_defaults(pbone)
+
+        if self.process_data and obj.data and obj.data.library is None:
+            self.assign_defaults(obj.data)
+
+            if self.process_bones and isinstance(obj.data, bpy.types.Armature):
+                for bone in obj.data.bones:
+                    self.assign_defaults(bone)
+
+        return {'FINISHED'}
+
+
 classes = (
     ClearAllRestrictRender,
     DupliOffsetFromCursor,
@@ -958,4 +1001,5 @@ classes = (
     SubdivisionSet,
     TransformsToDeltas,
     TransformsToDeltasAnim,
+    OBJECT_OT_assign_property_defaults,
 )
index e26e2ddf214a82f1eb25f98edea11a19dbf5f7eb..bf968de864183982cf59e9e64a3746a368875c48 100644 (file)
@@ -1052,6 +1052,12 @@ rna_value = StringProperty(
     maxlen=1024,
 )
 
+rna_default = StringProperty(
+    name="Default Value",
+    description="Default value of the property. Important for NLA mixing",
+    maxlen=1024,
+)
+
 rna_property = StringProperty(
     name="Property Name",
     description="Property name edit",
@@ -1089,6 +1095,7 @@ class WM_OT_properties_edit(Operator):
     data_path: rna_path
     property: rna_property
     value: rna_value
+    default: rna_default
     min: rna_min
     max: rna_max
     use_soft_limits: rna_use_soft_limits
@@ -1107,6 +1114,28 @@ class WM_OT_properties_edit(Operator):
             "hard_range": (self.min, self.max),
         }
 
+    def get_value_eval(self):
+        try:
+            value_eval = eval(self.value)
+            # assert else None -> None, not "None", see [#33431]
+            assert(type(value_eval) in {str, float, int, bool, tuple, list})
+        except:
+            value_eval = self.value
+
+        return value_eval
+
+
+    def get_default_eval(self):
+        try:
+            default_eval = eval(self.default)
+            # assert else None -> None, not "None", see [#33431]
+            assert(type(default_eval) in {str, float, int, bool, tuple, list})
+        except:
+            default_eval = self.default
+
+        return default_eval
+
+
     def execute(self, context):
         from rna_prop_ui import (
             rna_idprop_ui_prop_get,
@@ -1124,12 +1153,8 @@ class WM_OT_properties_edit(Operator):
             self.report({'ERROR'}, "Direct execution not supported")
             return {'CANCELLED'}
 
-        try:
-            value_eval = eval(value)
-            # assert else None -> None, not "None", see [#33431]
-            assert(type(value_eval) in {str, float, int, bool, tuple, list})
-        except:
-            value_eval = value
+        value_eval = self.get_value_eval()
+        default_eval = self.get_default_eval()
 
         # First remove
         item = eval("context.%s" % data_path)
@@ -1159,6 +1184,8 @@ class WM_OT_properties_edit(Operator):
         if prop_type in {float, int}:
             prop_ui["min"] = prop_type(self.min)
             prop_ui["max"] = prop_type(self.max)
+            if type(default_eval) in {float, int} and default_eval != 0:
+                prop_ui["default"] = prop_type(default_eval)
 
             if self.use_soft_limits:
                 prop_ui["soft_min"] = prop_type(self.soft_min)
@@ -1223,6 +1250,13 @@ class WM_OT_properties_edit(Operator):
         exec_str = "item.is_property_overridable_static('[\"%s\"]')" % (self.property)
         self.is_overridable_static = bool(eval(exec_str))
 
+        # default default value
+        prop_type = type(self.get_value_eval())
+        if prop_type in {int,float}:
+            self.default = str(prop_type(0))
+        else:
+            self.default = ""
+
         # setup defaults
         prop_ui = rna_idprop_ui_prop_get(item, self.property, False)  # don't create
         if prop_ui:
@@ -1230,6 +1264,10 @@ class WM_OT_properties_edit(Operator):
             self.max = prop_ui.get("max", 1000000000)
             self.description = prop_ui.get("description", "")
 
+            defval = prop_ui.get("default", None)
+            if defval is not None:
+                self.default = str(defval)
+
             self.soft_min = prop_ui.get("soft_min", self.min)
             self.soft_max = prop_ui.get("soft_max", self.max)
             self.use_soft_limits = (
@@ -1275,6 +1313,11 @@ class WM_OT_properties_edit(Operator):
         layout = self.layout
         layout.prop(self, "property")
         layout.prop(self, "value")
+
+        row = layout.row()
+        row.enabled = type(self.get_value_eval()) in {int,float}
+        row.prop(self, "default")
+
         row = layout.row(align=True)
         row.prop(self, "min")
         row.prop(self, "max")
index fa58fed61027b15a63f8b0e134758bf503b799fe..7922cdb90ec96ac4be99eee366473b5ff6d0b7ba 100644 (file)
@@ -2662,6 +2662,11 @@ class VIEW3D_MT_pose_apply(Menu):
         layout.operator("pose.armature_apply")
         layout.operator("pose.visual_transform_apply")
 
+        layout.separator()
+
+        props = layout.operator("object.assign_property_defaults")
+        props.process_bones = True
+
 
 class VIEW3D_MT_pose_specials(Menu):
     bl_label = "Pose Context Menu"
index 84460f9f149364b382a43807db102e96da733ba6..d9967625199a5f5833c8ddf4aa4096bc4d18aaf2 100644 (file)
@@ -404,7 +404,7 @@ bool ui_popup_context_menu_for_button(bContext *C, uiBut *but)
                const PropertySubType subtype = RNA_property_subtype(prop);
                bool is_anim = RNA_property_animateable(ptr, prop);
                bool is_editable = RNA_property_editable(ptr, prop);
-               /*bool is_idprop = RNA_property_is_idprop(prop);*/ /* XXX does not work as expected, not strictly needed */
+               bool is_idprop = RNA_property_is_idprop(prop);
                bool is_set = RNA_property_is_set(ptr, prop);
 
                /* second slower test, saved people finding keyframe items in menus when its not possible */
@@ -643,6 +643,13 @@ bool ui_popup_context_menu_for_button(bContext *C, uiBut *but)
                                ICON_NONE, "UI_OT_unset_property_button");
                }
 
+               if (is_idprop && !is_array_component && ELEM(type, PROP_INT, PROP_FLOAT)) {
+                       uiItemO(layout, CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Assign Value as Default"),
+                               ICON_NONE, "UI_OT_assign_default_button");
+
+                       uiItemS(layout);
+               }
+
                if (is_array_component) {
                        uiItemBooleanO(
                                layout, CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy All To Selected"),
index b4b59cae75bb62be22223db9df73693b34331455..1cb9f156eeb2c7b5b9ccd598ea8742f6367b8c5d 100644 (file)
@@ -301,6 +301,58 @@ static void UI_OT_reset_default_button(wmOperatorType *ot)
        RNA_def_boolean(ot->srna, "all", 1, "All", "Reset to default values all elements of the array");
 }
 
+/* Assign Value as Default Button Operator ------------------------ */
+
+static bool assign_default_button_poll(bContext *C)
+{
+       PointerRNA ptr;
+       PropertyRNA *prop;
+       int index;
+
+       UI_context_active_but_prop_get(C, &ptr, &prop, &index);
+
+       if (ptr.data && prop && RNA_property_editable(&ptr, prop)) {
+               PropertyType type = RNA_property_type(prop);
+
+               return RNA_property_is_idprop(prop) && !RNA_property_array_check(prop) && ELEM(type, PROP_INT, PROP_FLOAT);
+       }
+
+       return false;
+}
+
+static int assign_default_button_exec(bContext *C, wmOperator *UNUSED(op))
+{
+       PointerRNA ptr;
+       PropertyRNA *prop;
+       int index;
+
+       /* try to reset the nominated setting to its default value */
+       UI_context_active_but_prop_get(C, &ptr, &prop, &index);
+
+       /* if there is a valid property that is editable... */
+       if (ptr.data && prop && RNA_property_editable(&ptr, prop)) {
+               if (RNA_property_assign_default(&ptr, prop))
+                       return operator_button_property_finish(C, &ptr, prop);
+       }
+
+       return OPERATOR_CANCELLED;
+}
+
+static void UI_OT_assign_default_button(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Assign Value as Default";
+       ot->idname = "UI_OT_assign_default_button";
+       ot->description = "Set this property's current value as the new default";
+
+       /* callbacks */
+       ot->poll = assign_default_button_poll;
+       ot->exec = assign_default_button_exec;
+
+       /* flags */
+       ot->flag = OPTYPE_UNDO;
+}
+
 /* Unset Property Button Operator ------------------------ */
 
 static int unset_property_button_exec(bContext *C, wmOperator *UNUSED(op))
@@ -1557,6 +1609,7 @@ void ED_operatortypes_ui(void)
        WM_operatortype_append(UI_OT_copy_data_path_button);
        WM_operatortype_append(UI_OT_copy_python_command_button);
        WM_operatortype_append(UI_OT_reset_default_button);
+       WM_operatortype_append(UI_OT_assign_default_button);
        WM_operatortype_append(UI_OT_unset_property_button);
        WM_operatortype_append(UI_OT_override_type_set_button);
        WM_operatortype_append(UI_OT_override_remove_button);
index f6bfcb2bb3275c5e3ba2fcbbf9d456a103ac6b3c..03a879c9cf7159a504c727a19e77061cf8045115 100644 (file)
@@ -952,6 +952,7 @@ 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);
 void RNA_property_int_set_index(PointerRNA *ptr, PropertyRNA *prop, int index, int value);
 int RNA_property_int_get_default(PointerRNA *ptr, PropertyRNA *prop);
+bool RNA_property_int_set_default(PointerRNA *ptr, PropertyRNA *prop, int value);
 void RNA_property_int_get_default_array(PointerRNA *ptr, PropertyRNA *prop, int *values);
 int RNA_property_int_get_default_index(PointerRNA *ptr, PropertyRNA *prop, int index);
 
@@ -963,6 +964,7 @@ 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);
 void RNA_property_float_set_index(PointerRNA *ptr, PropertyRNA *prop, int index, float value);
 float RNA_property_float_get_default(PointerRNA *ptr, PropertyRNA *prop);
+bool RNA_property_float_set_default(PointerRNA *ptr, PropertyRNA *prop, float value);
 void RNA_property_float_get_default_array(PointerRNA *ptr, PropertyRNA *prop, float *values);
 float RNA_property_float_get_default_index(PointerRNA *ptr, PropertyRNA *prop, int index);
 
@@ -1015,6 +1017,7 @@ bool RNA_property_collection_move(PointerRNA *ptr, PropertyRNA *prop, int key, i
 /* copy/reset */
 bool RNA_property_copy(struct Main *bmain, PointerRNA *ptr, PointerRNA *fromptr, PropertyRNA *prop, int index);
 bool RNA_property_reset(PointerRNA *ptr, PropertyRNA *prop, int index);
+bool RNA_property_assign_default(PointerRNA *ptr, PropertyRNA *prop);
 
 /* Path
  *
index 2f23bc5eea9748ed683a7825a93b550c1c1514c4..1a212097c315932d44433279157cff95e5523509 100644 (file)
@@ -257,8 +257,7 @@ static void rna_idproperty_touch(IDProperty *idprop)
        idprop->flag &= ~IDP_FLAG_GHOST;
 }
 
-/* return a UI local ID prop definition for this prop */
-static IDProperty *rna_idproperty_ui(PropertyRNA *prop)
+static IDProperty *rna_idproperty_ui_container(PropertyRNA *prop)
 {
        IDProperty *idprop;
 
@@ -274,6 +273,14 @@ static IDProperty *rna_idproperty_ui(PropertyRNA *prop)
                }
        }
 
+       return idprop;
+}
+
+/* return a UI local ID prop definition for this prop */
+static IDProperty *rna_idproperty_ui(PropertyRNA *prop)
+{
+       IDProperty *idprop = rna_idproperty_ui_container(prop);
+
        if (idprop) {
                return IDP_GetPropertyTypeFromGroup(idprop, ((IDProperty *)prop)->name, IDP_GROUP);
        }
@@ -281,6 +288,94 @@ static IDProperty *rna_idproperty_ui(PropertyRNA *prop)
        return NULL;
 }
 
+/* return or create a UI local ID prop definition for this prop */
+static IDProperty *rna_idproperty_ui_ensure(PointerRNA *ptr, PropertyRNA *prop, bool create)
+{
+       IDProperty *idprop = rna_idproperty_ui_container(prop);
+       IDPropertyTemplate dummy = { 0 };
+
+       if (idprop == NULL && create) {
+               IDProperty *props = RNA_struct_idprops(ptr, false);
+
+               /* Sanity check: props is the actual container of this property. */
+               if (props != NULL && BLI_findindex(&props->data.group, prop) >= 0) {
+                       idprop = IDP_New(IDP_GROUP, &dummy, RNA_IDP_UI);
+
+                       if (!IDP_AddToGroup(props, idprop)) {
+                               IDP_FreeProperty(idprop);
+                               return NULL;
+                       }
+               }
+       }
+
+       if (idprop) {
+               const char *name = ((IDProperty *)prop)->name;
+               IDProperty *rv = IDP_GetPropertyTypeFromGroup(idprop, name, IDP_GROUP);
+
+               if (rv == NULL && create) {
+                       rv = IDP_New(IDP_GROUP, &dummy, name);
+
+                       if (!IDP_AddToGroup(idprop, rv)) {
+                               IDP_FreeProperty(rv);
+                               return NULL;
+                       }
+               }
+
+               return rv;
+       }
+
+       return NULL;
+}
+
+static bool rna_idproperty_ui_set_default(PointerRNA *ptr, PropertyRNA *prop, const char type, IDPropertyTemplate *value)
+{
+       BLI_assert(ELEM(type, IDP_INT, IDP_DOUBLE));
+
+       if (prop->magic == RNA_MAGIC) {
+               return false;
+       }
+
+       /* attempt to get the local ID values */
+       IDProperty *idp_ui = rna_idproperty_ui_ensure(ptr, prop, value != NULL);
+
+       if (idp_ui == NULL) {
+               return (value == NULL);
+       }
+
+       IDProperty *item = IDP_GetPropertyTypeFromGroup(idp_ui, "default", type);
+
+       if (value == NULL) {
+               if (item != NULL) {
+                       IDP_RemoveFromGroup(idp_ui, item);
+               }
+       }
+       else {
+               if (item != NULL) {
+                       switch (type) {
+                               case IDP_INT:
+                                       IDP_Int(item) = value->i;
+                                       break;
+                               case IDP_DOUBLE:
+                                       IDP_Double(item) = value->d;
+                                       break;
+                               default:
+                                       BLI_assert(false);
+                                       return false;
+                       }
+               }
+               else {
+                       item = IDP_New(type, value, "default");
+
+                       if (!IDP_AddToGroup(idp_ui, item)) {
+                               IDP_FreeProperty(item);
+                               return false;
+                       }
+               }
+       }
+
+       return true;
+}
+
 IDProperty *RNA_struct_idprops(PointerRNA *ptr, bool create)
 {
        StructRNA *type = ptr->type;
@@ -2726,9 +2821,33 @@ void RNA_property_int_set_index(PointerRNA *ptr, PropertyRNA *prop, int index, i
 int RNA_property_int_get_default(PointerRNA *UNUSED(ptr), PropertyRNA *prop)
 {
        IntPropertyRNA *iprop = (IntPropertyRNA *)rna_ensure_property(prop);
+
+       if (prop->magic != RNA_MAGIC) {
+               /* attempt to get the local ID values */
+               IDProperty *idp_ui = rna_idproperty_ui(prop);
+
+               if (idp_ui) {
+                       IDProperty *item;
+
+                       item = IDP_GetPropertyTypeFromGroup(idp_ui, "default", IDP_INT);
+                       return item ? IDP_Int(item) : iprop->defaultvalue;
+               }
+       }
+
        return iprop->defaultvalue;
 }
 
+bool RNA_property_int_set_default(PointerRNA *ptr, PropertyRNA *prop, int value)
+{
+       if (value != 0) {
+               IDPropertyTemplate val = { .i = value };
+               return rna_idproperty_ui_set_default(ptr, prop, IDP_INT, &val);
+       }
+       else {
+               return rna_idproperty_ui_set_default(ptr, prop, IDP_INT, NULL);
+       }
+}
+
 void RNA_property_int_get_default_array(PointerRNA *UNUSED(ptr), PropertyRNA *prop, int *values)
 {
        IntPropertyRNA *iprop = (IntPropertyRNA *)rna_ensure_property(prop);
@@ -3023,9 +3142,32 @@ float RNA_property_float_get_default(PointerRNA *UNUSED(ptr), PropertyRNA *prop)
        BLI_assert(RNA_property_type(prop) == PROP_FLOAT);
        BLI_assert(RNA_property_array_check(prop) == false);
 
+       if (prop->magic != RNA_MAGIC) {
+               /* attempt to get the local ID values */
+               IDProperty *idp_ui = rna_idproperty_ui(prop);
+
+               if (idp_ui) {
+                       IDProperty *item;
+
+                       item = IDP_GetPropertyTypeFromGroup(idp_ui, "default", IDP_DOUBLE);
+                       return item ? IDP_Double(item) : fprop->defaultvalue;
+               }
+       }
+
        return fprop->defaultvalue;
 }
 
+bool RNA_property_float_set_default(PointerRNA *ptr, PropertyRNA *prop, float value)
+{
+       if (value != 0) {
+               IDPropertyTemplate val = { .d = value };
+               return rna_idproperty_ui_set_default(ptr, prop, IDP_DOUBLE, &val);
+       }
+       else {
+               return rna_idproperty_ui_set_default(ptr, prop, IDP_DOUBLE, NULL);
+       }
+}
+
 void RNA_property_float_get_default_array(PointerRNA *UNUSED(ptr), PropertyRNA *prop, float *values)
 {
        FloatPropertyRNA *fprop = (FloatPropertyRNA *)rna_ensure_property(prop);
@@ -7257,6 +7399,31 @@ bool RNA_property_reset(PointerRNA *ptr, PropertyRNA *prop, int index)
        }
 }
 
+bool RNA_property_assign_default(PointerRNA *ptr, PropertyRNA *prop)
+{
+       if (!RNA_property_is_idprop(prop) || RNA_property_array_check(prop)) {
+               return false;
+       }
+
+       /* get and set the default values as appropriate for the various types */
+       switch (RNA_property_type(prop)) {
+               case PROP_INT:
+               {
+                       int value = RNA_property_int_get(ptr, prop);
+                       return RNA_property_int_set_default(ptr, prop, value);
+               }
+
+               case PROP_FLOAT:
+               {
+                       float value = RNA_property_float_get(ptr, prop);
+                       return RNA_property_float_set_default(ptr, prop, value);
+               }
+
+               default:
+                       return false;
+       }
+}
+
 static bool rna_property_override_operation_apply(
         Main *bmain,
         PointerRNA *ptr_local, PointerRNA *ptr_override, PointerRNA *ptr_storage,