RNA: support setting default values for custom properties.

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
This commit is contained in:
Alexander Gavrilov 2018-12-15 22:37:12 +03:00
parent 908a274240
commit 61c941f040
8 changed files with 350 additions and 9 deletions

View 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):

View 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,
)

View 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")

View 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"

View 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"),

View 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);

View 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
*

View 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,