Implement an Armature constraint that mimics the modifier.
authorAlexander Gavrilov <angavrilov@gmail.com>
Sun, 15 Jul 2018 17:39:02 +0000 (20:39 +0300)
committerAlexander Gavrilov <angavrilov@gmail.com>
Tue, 6 Nov 2018 07:56:08 +0000 (10:56 +0300)
The main use one can imagine for this is adding tweak controls to
parts of a model that are already deformed by multiple other major
bones. It is natural to expect such locations to deform as if the
tweaks aren't there by default; however currently there is no easy
way to make a bone follow multiple other bones.

This adds a new constraint that implements the math behind the Armature
modifier, with support for explicit weights, bone envelopes, and dual
quaternion blending. It can also access bones from multiple armatures
at the same time (mainly because it's easier to code it that way.)

This also fixes dquat_to_mat4, which wasn't used anywhere before.

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

14 files changed:
release/scripts/startup/bl_operators/__init__.py
release/scripts/startup/bl_operators/constraint.py [new file with mode: 0644]
release/scripts/startup/bl_ui/properties_constraint.py
source/blender/blenkernel/BKE_constraint.h
source/blender/blenkernel/intern/constraint.c
source/blender/blenlib/intern/math_rotation.c
source/blender/blenloader/intern/readfile.c
source/blender/blenloader/intern/writefile.c
source/blender/depsgraph/intern/builder/deg_builder_relations.cc
source/blender/editors/animation/keyframing.c
source/blender/editors/object/object_constraint.c
source/blender/editors/transform/transform_conversions.c
source/blender/makesdna/DNA_constraint_types.h
source/blender/makesrna/intern/rna_constraint.c

index de538634595b699624b3eee65aed88fd5c710e99..4d9038684d1fbdc8be38a8d9dca2abf7628b3eb3 100644 (file)
@@ -29,6 +29,7 @@ _modules = [
     "anim",
     "clip",
     "console",
+    "constraint",
     "file",
     "image",
     "mask",
diff --git a/release/scripts/startup/bl_operators/constraint.py b/release/scripts/startup/bl_operators/constraint.py
new file mode 100644 (file)
index 0000000..cf70022
--- /dev/null
@@ -0,0 +1,76 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8-80 compliant>
+
+import bpy
+from bpy.types import (
+    Operator,
+)
+from bpy.props import (
+    IntProperty,
+)
+
+
+class CONSTRAINT_OT_add_target(Operator):
+    """Add a target to the constraint"""
+    bl_idname = "constraint.add_target"
+    bl_label = "Add Target"
+    bl_options = {'UNDO', 'INTERNAL'}
+
+    def execute(self, context):
+        context.constraint.targets.new()
+        return {'FINISHED'}
+
+
+class CONSTRAINT_OT_remove_target(Operator):
+    """Remove the target from the constraint"""
+    bl_idname = "constraint.remove_target"
+    bl_label = "Remove Target"
+    bl_options = {'UNDO', 'INTERNAL'}
+
+    index = IntProperty()
+
+    def execute(self, context):
+        tgts = context.constraint.targets
+        tgts.remove(tgts[self.index])
+        return {'FINISHED'}
+
+
+class CONSTRAINT_OT_normalize_target_weights(Operator):
+    """Normalize weights of all target bones"""
+    bl_idname = "constraint.normalize_target_weights"
+    bl_label = "Normalize Weights"
+    bl_options = {'UNDO', 'INTERNAL'}
+
+    def execute(self, context):
+        tgts = context.constraint.targets
+        total = sum(t.weight for t in tgts)
+
+        if total > 0:
+            for t in tgts:
+                t.weight = t.weight / total
+
+        return {'FINISHED'}
+
+
+classes = (
+    CONSTRAINT_OT_add_target,
+    CONSTRAINT_OT_remove_target,
+    CONSTRAINT_OT_normalize_target_weights,
+)
index 30822fbe9a125e96c6f5acb6c45693ec3450374c..b1c0217f9c9571ac4e7481fc67d28f93fc05ac98 100644 (file)
@@ -919,6 +919,46 @@ class ConstraintButtonsPanel:
     def SCRIPT(self, context, layout, con):
         layout.label(text="Blender 2.6 doesn't support python constraints yet")
 
+    def ARMATURE(self, context, layout, con):
+        topcol = layout.column()
+        topcol.use_property_split = True
+        topcol.operator("constraint.add_target", text="Add Target Bone")
+
+        if not con.targets:
+            box = topcol.box()
+            box.label(text="No target bones were added", icon="ERROR")
+
+        for i, tgt in enumerate(con.targets):
+            box = topcol.box()
+
+            has_target = tgt.target is not None
+
+            header = box.row()
+            header.use_property_split = False
+
+            split = header.split(factor=0.45, align=True)
+            split.prop(tgt, "target", text="")
+
+            row = split.row(align=True)
+            row.active = has_target
+            if has_target:
+                row.prop_search(tgt, "subtarget", tgt.target.data, "bones", text="")
+            else:
+                row.prop(tgt, "subtarget", text="", icon="BONE_DATA")
+
+            header.operator("constraint.remove_target", icon="REMOVE", text="").index = i
+
+            col = box.column()
+            col.active = has_target and tgt.subtarget != ""
+            col.prop(tgt, "weight", slider=True)
+
+        topcol.operator("constraint.normalize_target_weights")
+        topcol.prop(con, "use_deform_preserve_volume")
+        topcol.prop(con, "use_bone_envelopes")
+
+        if context.pose_bone:
+            topcol.prop(con, "use_current_location")
+
 
 class OBJECT_PT_constraints(ConstraintButtonsPanel, Panel):
     bl_label = "Object Constraints"
index cfc7d8e6065fad1570b4a1248b9c610eee5c1974..e7672001a15f97abb2100e31733b4e97ed5c6d40 100644 (file)
@@ -131,11 +131,15 @@ void BKE_constraints_id_loop(struct ListBase *list, ConstraintIDFunc func, void
 void BKE_constraint_free_data(struct bConstraint *con);
 void BKE_constraint_free_data_ex(struct bConstraint *con, bool do_id_user);
 
+bool BKE_constraint_target_uses_bbone(struct bConstraint *con, struct bConstraintTarget *ct);
+
 /* Constraint API function prototypes */
 struct bConstraint *BKE_constraints_active_get(struct ListBase *list);
 void                BKE_constraints_active_set(ListBase *list, struct bConstraint *con);
 struct bConstraint *BKE_constraints_find_name(struct ListBase *list, const char *name);
 
+struct bConstraint *BKE_constraint_find_from_target(struct Object *ob, struct bConstraintTarget *tgt);
+
 struct bConstraint *BKE_constraint_add_for_object(struct Object *ob, const char *name, short type);
 struct bConstraint *BKE_constraint_add_for_pose(struct Object *ob, struct bPoseChannel *pchan, const char *name, short type);
 
index 2ba19b8c7de42f7877ab97f51ca16d5bfccf43eb..41b07d73dc972763cd4590281c149bad10109456 100644 (file)
@@ -2098,6 +2098,206 @@ static bConstraintTypeInfo CTI_PYTHON = {
        pycon_evaluate /* evaluate */
 };
 
+/* ----------- Armature Constraint -------------- */
+
+static void armdef_free(bConstraint *con)
+{
+       bArmatureConstraint *data = con->data;
+
+       /* Target list. */
+       BLI_freelistN(&data->targets);
+}
+
+static void armdef_copy(bConstraint *con, bConstraint *srccon)
+{
+       bArmatureConstraint *pcon = (bArmatureConstraint *)con->data;
+       bArmatureConstraint *opcon = (bArmatureConstraint *)srccon->data;
+
+       BLI_duplicatelist(&pcon->targets, &opcon->targets);
+}
+
+static int armdef_get_tars(bConstraint *con, ListBase *list)
+{
+       if (con && list) {
+               bArmatureConstraint *data = con->data;
+
+               *list = data->targets;
+
+               return BLI_listbase_count(&data->targets);
+       }
+
+       return 0;
+}
+
+static void armdef_id_looper(bConstraint *con, ConstraintIDFunc func, void *userdata)
+{
+       bArmatureConstraint *data = con->data;
+       bConstraintTarget *ct;
+
+       /* Target list. */
+       for (ct = data->targets.first; ct; ct = ct->next) {
+               func(con, (ID **)&ct->tar, false, userdata);
+       }
+}
+
+/* Compute the world space pose matrix of the target bone. */
+static void armdef_get_tarmat(struct Depsgraph *UNUSED(depsgraph),
+                              bConstraint *UNUSED(con), bConstraintOb *UNUSED(cob),
+                              bConstraintTarget *ct, float UNUSED(ctime))
+{
+       if (ct != NULL) {
+               if (ct->tar && ct->tar->type == OB_ARMATURE) {
+                       bPoseChannel *pchan = BKE_pose_channel_find_name(ct->tar->pose, ct->subtarget);
+
+                       if (pchan != NULL) {
+                               mul_m4_m4m4(ct->matrix, ct->tar->obmat, pchan->pose_mat);
+                               return;
+                       }
+               }
+
+               unit_m4(ct->matrix);
+       }
+}
+
+/* Compute and accumulate transformation for a single target bone. */
+static void armdef_accumulate_bone(bConstraintTarget *ct, bPoseChannel *pchan, const float wco[3], bool force_envelope, float *r_totweight, float r_sum_mat[4][4], DualQuat *r_sum_dq)
+{
+       float mat[4][4], iobmat[4][4], iamat[4][4], basemat[4][4], co[3];
+       Bone *bone = pchan->bone;
+       float weight = ct->weight;
+
+       /* Our object's location in target pose space. */
+       invert_m4_m4(iobmat, ct->tar->obmat);
+       mul_v3_m4v3(co, iobmat, wco);
+
+       /* Inverted rest pose matrix: bone->chan_mat may not be final yet. */
+       invert_m4_m4(iamat, bone->arm_mat);
+
+       /* Multiply by the envelope weight when appropriate. */
+       if (force_envelope || (bone->flag & BONE_MULT_VG_ENV)) {
+               weight *= distfactor_to_bone(co, bone->arm_head, bone->arm_tail,
+                                            bone->rad_head, bone->rad_tail, bone->dist);
+       }
+
+       /* Find the correct bone transform matrix in world space. */
+       if (bone->segments > 1) {
+               /* The target is a B-Bone:
+                * FIRST: find the segment (see b_bone_deform in armature.c)
+                * Need to transform co back to bonespace, only need y. */
+               float y = iamat[0][1] * co[0] + iamat[1][1] * co[1] + iamat[2][1] * co[2] + iamat[3][1];
+
+               float segment = bone->length / ((float)bone->segments);
+               int a = (int)(y / segment);
+
+               CLAMP(a, 0, bone->segments - 1);
+
+               /* SECOND: compute the matrix (see pchan_b_bone_defmats in armature.c) */
+               Mat4 b_bone[MAX_BBONE_SUBDIV], b_bone_rest[MAX_BBONE_SUBDIV];
+               float irmat[4][4];
+
+               b_bone_spline_setup(pchan, false, b_bone);
+               b_bone_spline_setup(pchan, true, b_bone_rest);
+
+               invert_m4_m4(irmat, b_bone_rest[a].mat);
+               mul_m4_series(mat, ct->matrix, b_bone[a].mat, irmat, iamat, iobmat);
+       }
+       else {
+               /* Simple bone. */
+               mul_m4_series(mat, ct->matrix, iamat, iobmat);
+       }
+
+       /* Accumulate the transformation. */
+       *r_totweight += weight;
+
+       if (r_sum_dq != NULL) {
+               DualQuat tmpdq;
+
+               mul_m4_series(basemat, ct->tar->obmat, bone->arm_mat, iobmat);
+
+               mat4_to_dquat(&tmpdq, basemat, mat);
+               add_weighted_dq_dq(r_sum_dq, &tmpdq, weight);
+       }
+       else {
+               mul_m4_fl(mat, weight);
+               add_m4_m4m4(r_sum_mat, r_sum_mat, mat);
+       }
+}
+
+static void armdef_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *targets)
+{
+       bArmatureConstraint *data = con->data;
+
+       float sum_mat[4][4], input_co[3];
+       DualQuat sum_dq;
+       float weight = 0.0f;
+
+       /* Prepare for blending. */
+       zero_m4(sum_mat);
+       memset(&sum_dq, 0, sizeof(sum_dq));
+
+       DualQuat *pdq = (data->flag & CONSTRAINT_ARMATURE_QUATERNION) ? &sum_dq : NULL;
+       bool use_envelopes = (data->flag & CONSTRAINT_ARMATURE_ENVELOPE) != 0;
+
+       if (cob->pchan && cob->pchan->bone && !(data->flag & CONSTRAINT_ARMATURE_CUR_LOCATION)) {
+               /* For constraints on bones, use the rest position to bind b-bone segments
+                * and envelopes, to allow safely changing the bone location as if parented. */
+               copy_v3_v3(input_co, cob->pchan->bone->arm_head);
+               mul_m4_v3(cob->ob->obmat, input_co);
+       }
+       else {
+               copy_v3_v3(input_co, cob->matrix[3]);
+       }
+
+       /* Process all targets. */
+       for (bConstraintTarget *ct = targets->first; ct; ct = ct->next) {
+               if (ct->weight <= 0.0f) {
+                       continue;
+               }
+
+               /* Lookup the bone and abort if failed. */
+               if (!VALID_CONS_TARGET(ct) || ct->tar->type != OB_ARMATURE) {
+                       return;
+               }
+
+               bPoseChannel *pchan = BKE_pose_channel_find_name(ct->tar->pose, ct->subtarget);
+
+               if (pchan == NULL || pchan->bone == NULL) {
+                       return;
+               }
+
+               armdef_accumulate_bone(ct, pchan, input_co, use_envelopes, &weight, sum_mat, pdq);
+       }
+
+       /* Compute the final transform. */
+       if (weight > 0.0f) {
+               if (pdq != NULL) {
+                       normalize_dq(pdq, weight);
+                       dquat_to_mat4(sum_mat, pdq);
+               }
+               else {
+                       mul_m4_fl(sum_mat, 1.0f / weight);
+               }
+
+               /* Apply the transform to the result matrix. */
+               mul_m4_m4m4(cob->matrix, sum_mat, cob->matrix);
+       }
+}
+
+static bConstraintTypeInfo CTI_ARMATURE = {
+       CONSTRAINT_TYPE_ARMATURE, /* type */
+       sizeof(bArmatureConstraint), /* size */
+       "Armature", /* name */
+       "bArmatureConstraint", /* struct name */
+       armdef_free, /* free data */
+       armdef_id_looper, /* id looper */
+       armdef_copy, /* copy data */
+       NULL, /* new data */
+       armdef_get_tars, /* get constraint targets */
+       NULL, /* flush constraint targets */
+       armdef_get_tarmat, /* get target matrix */
+       armdef_evaluate /* evaluate */
+};
+
 /* -------- Action Constraint ----------- */
 
 static void actcon_new_data(void *cdata)
@@ -4469,6 +4669,7 @@ static void constraints_init_typeinfo(void)
        constraintsTypeInfo[27] = &CTI_CAMERASOLVER;     /* Camera Solver Constraint */
        constraintsTypeInfo[28] = &CTI_OBJECTSOLVER;     /* Object Solver Constraint */
        constraintsTypeInfo[29] = &CTI_TRANSFORM_CACHE;  /* Transform Cache Constraint */
+       constraintsTypeInfo[30] = &CTI_ARMATURE;         /* Armature Constraint */
 }
 
 /* This function should be used for getting the appropriate type-info when only
@@ -4684,6 +4885,11 @@ static bConstraint *add_new_constraint(Object *ob, bPoseChannel *pchan, const ch
        return con;
 }
 
+bool BKE_constraint_target_uses_bbone(struct bConstraint *con, struct bConstraintTarget *UNUSED(ct))
+{
+       return (con->flag & CONSTRAINT_BBONE_SHAPE) || (con->type == CONSTRAINT_TYPE_ARMATURE);
+}
+
 /* ......... */
 
 /* Add new constraint for the given bone */
@@ -4829,6 +5035,48 @@ void BKE_constraints_active_set(ListBase *list, bConstraint *con)
        }
 }
 
+static bConstraint *constraint_list_find_from_target(ListBase *constraints, bConstraintTarget *tgt)
+{
+       for (bConstraint *con = constraints->first; con; con = con->next) {
+               ListBase *targets = NULL;
+
+               if (con->type == CONSTRAINT_TYPE_PYTHON) {
+                       targets = &((bPythonConstraint*)con->data)->targets;
+               }
+               else if (con->type == CONSTRAINT_TYPE_ARMATURE) {
+                       targets = &((bArmatureConstraint*)con->data)->targets;
+               }
+
+               if (targets && BLI_findindex(targets, tgt) != -1) {
+                       return con;
+               }
+       }
+
+       return NULL;
+}
+
+/* Finds the constraint that owns the given target within the object. */
+bConstraint *BKE_constraint_find_from_target(Object *ob, bConstraintTarget *tgt)
+{
+       bConstraint *result = constraint_list_find_from_target(&ob->constraints, tgt);
+
+       if (result != NULL) {
+               return result;
+       }
+
+       if (ob->pose != NULL) {
+               for (bPoseChannel *pchan = ob->pose->chanbase.first; pchan; pchan = pchan->next) {
+                       result = constraint_list_find_from_target(&pchan->constraints, tgt);
+
+                       if (result != NULL) {
+                               return result;
+                       }
+               }
+       }
+
+       return NULL;
+}
+
 /* -------- Constraints and Proxies ------- */
 
 /* Rescue all constraints tagged as being CONSTRAINT_PROXY_LOCAL (i.e. added to bone that's proxy-synced in this file) */
index 29e7cf32ddcf029ec300ecea664b7c5a415c3d5f..17b395036a3ad055bdd6c02501bb8d40edae3021 100644 (file)
@@ -1872,19 +1872,24 @@ void dquat_to_mat4(float mat[4][4], const DualQuat *dq)
 
        /* normalize */
        len = sqrtf(dot_qtqt(q0, q0));
-       if (len != 0.0f)
-               mul_qt_fl(q0, 1.0f / len);
+       if (len != 0.0f) {
+               len = 1.0f / len;
+       }
+       mul_qt_fl(q0, len);
 
        /* rotation */
        quat_to_mat4(mat, q0);
 
        /* translation */
        t = dq->trans;
-       mat[3][0] = 2.0f * (-t[0] * q0[1] + t[1] * q0[0] - t[2] * q0[3] + t[3] * q0[2]);
-       mat[3][1] = 2.0f * (-t[0] * q0[2] + t[1] * q0[3] + t[2] * q0[0] - t[3] * q0[1]);
-       mat[3][2] = 2.0f * (-t[0] * q0[3] - t[1] * q0[2] + t[2] * q0[1] + t[3] * q0[0]);
+       mat[3][0] = 2.0f * (-t[0] * q0[1] + t[1] * q0[0] - t[2] * q0[3] + t[3] * q0[2]) * len;
+       mat[3][1] = 2.0f * (-t[0] * q0[2] + t[1] * q0[3] + t[2] * q0[0] - t[3] * q0[1]) * len;
+       mat[3][2] = 2.0f * (-t[0] * q0[3] - t[1] * q0[2] + t[2] * q0[1] + t[3] * q0[0]) * len;
 
-       /* note: this does not handle scaling */
+       /* scaling */
+       if (dq->scale_weight) {
+               mul_m4_m4m4(mat, mat, dq->scale);
+       }
 }
 
 void add_weighted_dq_dq(DualQuat *dqsum, const DualQuat *dq, float weight)
index 4a2a0b3efe2464c7b651e962eff01517a33e38a8..530a4c8dfb8f47a6066f6c106a2a1e2efcaf42e5 100644 (file)
@@ -3501,6 +3501,14 @@ static void direct_link_constraints(FileData *fd, ListBase *lb)
                                IDP_DirectLinkGroup_OrFree(&data->prop, (fd->flags & FD_FLAGS_SWITCH_ENDIAN), fd);
                                break;
                        }
+                       case CONSTRAINT_TYPE_ARMATURE:
+                       {
+                               bArmatureConstraint *data= con->data;
+
+                               link_list(fd, &data->targets);
+
+                               break;
+                       }
                        case CONSTRAINT_TYPE_SPLINEIK:
                        {
                                bSplineIKConstraint *data= con->data;
index c3693de48667d765e6ee9db76db96ff03f3803dd..919c6d057406c0dab9c014ae869024d9d934b685 100644 (file)
@@ -1536,6 +1536,18 @@ static void write_constraints(WriteData *wd, ListBase *conlist)
 
                                        break;
                                }
+                               case CONSTRAINT_TYPE_ARMATURE:
+                               {
+                                       bArmatureConstraint *data = con->data;
+                                       bConstraintTarget *ct;
+
+                                       /* write targets */
+                                       for (ct = data->targets.first; ct; ct = ct->next) {
+                                               writestruct(wd, DATA, bConstraintTarget, 1, ct);
+                                       }
+
+                                       break;
+                               }
                                case CONSTRAINT_TYPE_SPLINEIK:
                                {
                                        bSplineIKConstraint *data = con->data;
index 960e49416c1cc1857c853fcee2181bb0036005d4..122f4f7d317aff4c5c30a81c23e8499bc318a582 100644 (file)
@@ -965,7 +965,7 @@ void DepsgraphRelationBuilder::build_constraints(ID *id,
                                                                opcode);
                                        add_relation(target_key, constraint_op_key, cti->name);
                                        /* if needs bbone shape, also reference handles */
-                                       if (con->flag & CONSTRAINT_BBONE_SHAPE) {
+                                       if (BKE_constraint_target_uses_bbone(con, ct)) {
                                                bPoseChannel *pchan = BKE_pose_channel_find_name(ct->tar->pose, ct->subtarget);
                                                /* actually a bbone */
                                                if (pchan && pchan->bone && pchan->bone->segments > 1) {
index 39b1bed3ac1a1b55a43d1d93af85709800db80a6..863dd4efa82466ffe574d60bd8c79f2200520d3a 100644 (file)
@@ -810,6 +810,7 @@ static bool visualkey_can_use(PointerRNA *ptr, PropertyRNA *prop)
                        switch (con->type) {
                                /* multi-transform constraints */
                                case CONSTRAINT_TYPE_CHILDOF:
+                               case CONSTRAINT_TYPE_ARMATURE:
                                        return true;
                                case CONSTRAINT_TYPE_TRANSFORM:
                                case CONSTRAINT_TYPE_TRANSLIKE:
index 4081c0051324cf5b08d3e11851a1974ce3bd8d85..212bf0ba018b7e3cb5c99c37859c554805b32cf4 100644 (file)
@@ -430,6 +430,11 @@ static void test_constraint(Main *bmain, Object *owner, bPoseChannel *pchan, bCo
        if (check_targets && cti && cti->get_constraint_targets) {
                cti->get_constraint_targets(con, &targets);
 
+               /* constraints with empty target list that actually require targets */
+               if (!targets.first && ELEM(con->type, CONSTRAINT_TYPE_ARMATURE)) {
+                       con->flag |= CONSTRAINT_DISABLE;
+               }
+
                /* disable and clear constraints targets that are incorrect */
                for (ct = targets.first; ct; ct = ct->next) {
                        /* general validity checks (for those constraints that need this) */
@@ -473,6 +478,18 @@ static void test_constraint(Main *bmain, Object *owner, bPoseChannel *pchan, bCo
                                        }
                                }
                        }
+                       else if (con->type == CONSTRAINT_TYPE_ARMATURE) {
+                               if (ct->tar) {
+                                       if (ct->tar->type != OB_ARMATURE) {
+                                               ct->tar = NULL;
+                                               con->flag |= CONSTRAINT_DISABLE;
+                                       }
+                                       else if (!BKE_armature_find_bone_name(BKE_armature_from_object(ct->tar), ct->subtarget)) {
+                                               /* bone must exist in armature... */
+                                               con->flag |= CONSTRAINT_DISABLE;
+                                       }
+                               }
+                       }
                }
 
                /* free any temporary targets */
@@ -1245,7 +1262,9 @@ void ED_object_constraint_tag_update(Main *bmain, Object *ob, bConstraint *con)
                BKE_pose_tag_update_constraint_flags(ob->pose);
        }
 
-       object_test_constraint(bmain, ob, con);
+       if (con) {
+               object_test_constraint(bmain, ob, con);
+       }
 
        if (ob->type == OB_ARMATURE)
                DEG_id_tag_update(&ob->id, OB_RECALC_DATA | OB_RECALC_OB);
index d9857b50ec04e9954c80a625a55c2be6176719e1..c0d4db0cc9740a595b71ae25cec07f9bf9247709 100644 (file)
@@ -5599,6 +5599,7 @@ static bool constraints_list_needinv(TransInfo *t, ListBase *list)
                                if (ELEM(con->type,
                                         CONSTRAINT_TYPE_FOLLOWPATH,
                                         CONSTRAINT_TYPE_CLAMPTO,
+                                        CONSTRAINT_TYPE_ARMATURE,
                                         CONSTRAINT_TYPE_OBJECTSOLVER,
                                         CONSTRAINT_TYPE_FOLLOWTRACK))
                                {
index fd98774e948baa92164c3f8e0db6ae8dfb6b9efd..15555a2bd50a2e9041bdf280de64d640838887ac 100644 (file)
@@ -94,6 +94,8 @@ typedef struct bConstraintTarget {
        short flag;                             /* runtime settings (for editor, etc.) */
        short type;                             /* type of target (eConstraintObType) */
        short rotOrder;                 /* rotation order for target (as defined in BLI_math.h) */
+       float weight;                   /* weight for armature deform */
+       char pad[4];
 } bConstraintTarget;
 
 /* bConstraintTarget -> flag */
@@ -180,6 +182,13 @@ typedef struct bSplineIKConstraint {
        float           bulge_smooth;
 } bSplineIKConstraint;
 
+/* Armature Constraint */
+typedef struct bArmatureConstraint {
+       int flag;                       /* general settings/state indicators accessed by bitmapping */
+       char pad[4];
+
+       ListBase targets;               /* a list of targets that this constraint has (bConstraintTarget-s) */
+} bArmatureConstraint;
 
 /* Single-target subobject constraints ---------------------  */
 
@@ -504,6 +513,7 @@ typedef enum eBConstraint_Types {
        CONSTRAINT_TYPE_CAMERASOLVER = 27,              /* Camera Solver Constraint */
        CONSTRAINT_TYPE_OBJECTSOLVER = 28,              /* Object Solver Constraint */
        CONSTRAINT_TYPE_TRANSFORM_CACHE = 29,   /* Transform Cache Constraint */
+       CONSTRAINT_TYPE_ARMATURE = 30,                  /* Armature Deform Constraint */
 
        /* NOTE: no constraints are allowed to be added after this */
        NUM_CONSTRAINT_TYPES
@@ -747,6 +757,13 @@ typedef enum eSplineIK_XZScaleModes {
        CONSTRAINT_SPLINEIK_XZS_VOLUMETRIC              = 3
 } eSplineIK_XZScaleModes;
 
+/* bArmatureConstraint -> flag */
+typedef enum eArmature_Flags {
+       CONSTRAINT_ARMATURE_QUATERNION          = (1<<0),       /* use dual quaternion blending */
+       CONSTRAINT_ARMATURE_ENVELOPE            = (1<<1),       /* use envelopes */
+       CONSTRAINT_ARMATURE_CUR_LOCATION        = (1<<2),       /* use current bone location */
+} eArmature_Flags;
+
 /* MinMax (floor) flags */
 typedef enum eFloor_Flags {
        MINMAX_STICKY   = (1<<0),
index 51d4d93586cd186af4f44fc7b5759796d4470d21..e2d4093b753b9c143a6c83ce831c86b2710e821e 100644 (file)
@@ -27,6 +27,9 @@
 #include <stdlib.h>
 
 #include "BLI_math.h"
+#include "BLI_listbase.h"
+
+#include "MEM_guardedalloc.h"
 
 #include "BLT_translation.h"
 
@@ -106,6 +109,8 @@ const EnumPropertyItem rna_enum_constraint_type_items[] = {
                                     "Custom constraint(s) written in Python (Not yet implemented)"}, */
        {CONSTRAINT_TYPE_SHRINKWRAP, "SHRINKWRAP", ICON_CONSTRAINT, "Shrinkwrap",
                                     "Restrict movements to surface of target mesh"},
+       {CONSTRAINT_TYPE_ARMATURE, "ARMATURE", ICON_CONSTRAINT, "Armature",
+                                  "Apply weight-blended transformation from multiple bones like the Armature modifier"},
        {0, NULL, 0, NULL, NULL}
 };
 
@@ -192,6 +197,8 @@ static StructRNA *rna_ConstraintType_refine(struct PointerRNA *ptr)
                        return &RNA_MaintainVolumeConstraint;
                case CONSTRAINT_TYPE_PYTHON:
                        return &RNA_PythonConstraint;
+               case CONSTRAINT_TYPE_ARMATURE:
+                       return &RNA_ArmatureConstraint;
                case CONSTRAINT_TYPE_ACTION:
                        return &RNA_ActionConstraint;
                case CONSTRAINT_TYPE_LOCKTRACK:
@@ -235,6 +242,17 @@ static StructRNA *rna_ConstraintType_refine(struct PointerRNA *ptr)
        }
 }
 
+static void rna_ConstraintTargetBone_target_set(PointerRNA *ptr, PointerRNA value)
+{
+       bConstraintTarget *tgt = (bConstraintTarget *)ptr->data;
+       Object *ob = value.data;
+
+       if (!ob || ob->type == OB_ARMATURE) {
+               id_lib_extern((ID *)ob);
+               tgt->tar = ob;
+       }
+}
+
 static void rna_Constraint_name_set(PointerRNA *ptr, const char *value)
 {
        bConstraint *con = ptr->data;
@@ -260,10 +278,8 @@ static void rna_Constraint_name_set(PointerRNA *ptr, const char *value)
        BKE_animdata_fix_paths_rename_all(NULL, "constraints", oldname, con->name);
 }
 
-static char *rna_Constraint_path(PointerRNA *ptr)
+static char *rna_Constraint_do_compute_path(Object *ob, bConstraint *con)
 {
-       Object *ob = ptr->id.data;
-       bConstraint *con = ptr->data;
        bPoseChannel *pchan;
        ListBase *lb = get_constraint_lb(ob, con, &pchan);
 
@@ -285,6 +301,55 @@ static char *rna_Constraint_path(PointerRNA *ptr)
        }
 }
 
+static char *rna_Constraint_path(PointerRNA *ptr)
+{
+       Object *ob = ptr->id.data;
+       bConstraint *con = ptr->data;
+
+       return rna_Constraint_do_compute_path(ob, con);
+}
+
+static bConstraint* rna_constraint_from_target(PointerRNA *ptr)
+{
+       Object *ob = ptr->id.data;
+       bConstraintTarget *tgt = ptr->data;
+
+       return BKE_constraint_find_from_target(ob, tgt);
+}
+
+static char *rna_ConstraintTarget_path(PointerRNA *ptr)
+{
+       Object *ob = ptr->id.data;
+       bConstraintTarget *tgt = ptr->data;
+       bConstraint *con = rna_constraint_from_target(ptr);
+       int index = -1;
+
+       if (con != NULL) {
+               if (con->type == CONSTRAINT_TYPE_ARMATURE) {
+                       bArmatureConstraint *acon = (bArmatureConstraint*)con->data;
+                       index = BLI_findindex(&acon->targets, tgt);
+               }
+               else if (con->type == CONSTRAINT_TYPE_PYTHON) {
+                       bPythonConstraint *pcon = (bPythonConstraint*)con->data;
+                       index = BLI_findindex(&pcon->targets, tgt);
+               }
+       }
+
+       if (index >= 0) {
+               char *con_path = rna_Constraint_do_compute_path(ob, con);
+               char *result = BLI_sprintfN("%s.targets[%d]", con_path, index);
+
+               MEM_freeN(con_path);
+               return result;
+       }
+       else {
+               printf("%s: internal error, constraint '%s' of object '%s' does not contain the target\n",
+                      __func__, con->name, ob->id.name);
+       }
+
+       return NULL;
+}
+
 static void rna_Constraint_update(Main *bmain, Scene *UNUSED(scene), PointerRNA *ptr)
 {
        ED_object_constraint_tag_update(bmain, ptr->id.data, ptr->data);
@@ -295,6 +360,16 @@ static void rna_Constraint_dependency_update(Main *bmain, Scene *UNUSED(scene),
        ED_object_constraint_dependency_tag_update(bmain, ptr->id.data, ptr->data);
 }
 
+static void rna_ConstraintTarget_update(Main *bmain, Scene *UNUSED(scene), PointerRNA *ptr)
+{
+       ED_object_constraint_tag_update(bmain, ptr->id.data, rna_constraint_from_target(ptr));
+}
+
+static void rna_ConstraintTarget_dependency_update(Main *bmain, Scene *UNUSED(scene), PointerRNA *ptr)
+{
+       ED_object_constraint_dependency_tag_update(bmain, ptr->id.data, rna_constraint_from_target(ptr));
+}
+
 static void rna_Constraint_influence_update(Main *bmain, Scene *scene, PointerRNA *ptr)
 {
        Object *ob = ptr->id.data;
@@ -360,6 +435,42 @@ static const EnumPropertyItem *rna_Constraint_target_space_itemf(bContext *UNUSE
        return space_object_items;
 }
 
+static bConstraintTarget *rna_ArmatureConstraint_target_new(ID *id, bConstraint *con, Main *bmain)
+{
+       bArmatureConstraint *acon = (bArmatureConstraint*)con->data;
+       bConstraintTarget *tgt = MEM_callocN(sizeof(bConstraintTarget), "Constraint Target");
+
+       tgt->weight = 1.0f;
+       BLI_addtail(&acon->targets, tgt);
+
+       ED_object_constraint_dependency_tag_update(bmain, (Object*)id, con);
+       return tgt;
+}
+
+static void rna_ArmatureConstraint_target_remove(ID *id, bConstraint *con, Main *bmain, ReportList *reports, PointerRNA *target_ptr)
+{
+       bArmatureConstraint *acon = (bArmatureConstraint*)con->data;
+       bConstraintTarget *tgt = target_ptr->data;
+
+       if (BLI_findindex(&acon->targets, tgt) < 0) {
+               BKE_reportf(reports, RPT_ERROR, "Target is not in the constraint target list");
+               return;
+       }
+
+       BLI_freelinkN(&acon->targets, tgt);
+
+       ED_object_constraint_dependency_tag_update(bmain, (Object*)id, con);
+}
+
+static void rna_ArmatureConstraint_target_clear(ID *id, bConstraint *con, Main *bmain)
+{
+       bArmatureConstraint *acon = (bArmatureConstraint*)con->data;
+
+       BLI_freelistN(&acon->targets);
+
+       ED_object_constraint_dependency_tag_update(bmain, (Object*)id, con);
+}
+
 static void rna_ActionConstraint_minmax_range(PointerRNA *ptr, float *min, float *max,
                                               float *UNUSED(softmin), float *UNUSED(softmax))
 {
@@ -564,16 +675,58 @@ static void rna_def_constraint_target_common(StructRNA *srna)
 static void rna_def_constrainttarget(BlenderRNA *brna)
 {
        StructRNA *srna;
+       PropertyRNA *prop;
 
        srna = RNA_def_struct(brna, "ConstraintTarget", NULL);
        RNA_def_struct_ui_text(srna, "Constraint Target", "Target object for multi-target constraints");
+       RNA_def_struct_path_func(srna, "rna_ConstraintTarget_path");
        RNA_def_struct_sdna(srna, "bConstraintTarget");
 
-       rna_def_constraint_target_common(srna);
+       prop = RNA_def_property(srna, "target", PROP_POINTER, PROP_NONE);
+       RNA_def_property_pointer_sdna(prop, NULL, "tar");
+       RNA_def_property_ui_text(prop, "Target", "Target object");
+       RNA_def_property_flag(prop, PROP_EDITABLE);
+       RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_STATIC);
+       RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_ConstraintTarget_dependency_update");
+
+       prop = RNA_def_property(srna, "subtarget", PROP_STRING, PROP_NONE);
+       RNA_def_property_string_sdna(prop, NULL, "subtarget");
+       RNA_def_property_ui_text(prop, "Sub-Target", "Armature bone, mesh or lattice vertex group, ...");
+       RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_ConstraintTarget_dependency_update");
 
        /* space, flag and type still to do  */
 }
 
+static void rna_def_constrainttarget_bone(BlenderRNA *brna)
+{
+       StructRNA *srna;
+       PropertyRNA *prop;
+
+       srna = RNA_def_struct(brna, "ConstraintTargetBone", NULL);
+       RNA_def_struct_ui_text(srna, "Constraint Target Bone", "Target bone for multi-target constraints");
+       RNA_def_struct_path_func(srna, "rna_ConstraintTarget_path");
+       RNA_def_struct_sdna(srna, "bConstraintTarget");
+
+       prop = RNA_def_property(srna, "target", PROP_POINTER, PROP_NONE);
+       RNA_def_property_pointer_sdna(prop, NULL, "tar");
+       RNA_def_property_ui_text(prop, "Target", "Target armature");
+       RNA_def_property_pointer_funcs(prop, NULL, "rna_ConstraintTargetBone_target_set", NULL, "rna_Armature_object_poll");
+       RNA_def_property_flag(prop, PROP_EDITABLE);
+       RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_STATIC);
+       RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_ConstraintTarget_dependency_update");
+
+       prop = RNA_def_property(srna, "subtarget", PROP_STRING, PROP_NONE);
+       RNA_def_property_string_sdna(prop, NULL, "subtarget");
+       RNA_def_property_ui_text(prop, "Sub-Target", "Target armature bone");
+       RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_ConstraintTarget_dependency_update");
+
+       prop = RNA_def_property(srna, "weight", PROP_FLOAT, PROP_NONE);
+       RNA_def_property_float_sdna(prop, NULL, "weight");
+       RNA_def_property_range(prop, 0.0f, 1.0f);
+       RNA_def_property_ui_text(prop, "Blend Weight", "Blending weight of this bone");
+       RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_ConstraintTarget_update");
+}
+
 static void rna_def_constraint_childof(BlenderRNA *brna)
 {
        StructRNA *srna;
@@ -673,6 +826,70 @@ static void rna_def_constraint_python(BlenderRNA *brna)
        RNA_def_property_ui_text(prop, "Script Error", "The linked Python script has thrown an error");
 }
 
+
+static void rna_def_constraint_armature_deform_targets(BlenderRNA *brna, PropertyRNA *cprop)
+{
+       StructRNA *srna;
+       FunctionRNA *func;
+       PropertyRNA *parm;
+
+       RNA_def_property_srna(cprop, "ArmatureConstraintTargets");
+       srna = RNA_def_struct(brna, "ArmatureConstraintTargets", NULL);
+       RNA_def_struct_sdna(srna, "bConstraint");
+       RNA_def_struct_ui_text(srna, "Armature Deform Constraint Targets", "Collection of target bones and weights");
+
+       func = RNA_def_function(srna, "new", "rna_ArmatureConstraint_target_new");
+       RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN);
+       RNA_def_function_ui_description(func, "Add a new target to the constraint");
+       parm = RNA_def_pointer(func, "target", "ConstraintTargetBone", "", "New target bone");
+       RNA_def_function_return(func, parm);
+
+       func = RNA_def_function(srna, "remove", "rna_ArmatureConstraint_target_remove");
+       RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN | FUNC_USE_REPORTS);
+       RNA_def_function_ui_description(func, "Delete target from the constraint");
+       parm = RNA_def_pointer(func, "target", "ConstraintTargetBone", "", "Target to remove");
+       RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR);
+       RNA_def_parameter_clear_flags(parm, PROP_THICK_WRAP, 0);
+
+       func = RNA_def_function(srna, "clear", "rna_ArmatureConstraint_target_clear");
+       RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN);
+       RNA_def_function_ui_description(func, "Delete all targets from object");
+}
+
+static void rna_def_constraint_armature_deform(BlenderRNA *brna)
+{
+       StructRNA *srna;
+       PropertyRNA *prop;
+
+       srna = RNA_def_struct(brna, "ArmatureConstraint", "Constraint");
+       RNA_def_struct_ui_text(srna, "Armature Constraint", "Applies transformations done by the Armature modifier");
+       RNA_def_struct_sdna_from(srna, "bArmatureConstraint", "data");
+
+       prop = RNA_def_property(srna, "targets", PROP_COLLECTION, PROP_NONE);
+       RNA_def_property_collection_sdna(prop, NULL, "targets", NULL);
+       RNA_def_property_struct_type(prop, "ConstraintTargetBone");
+       RNA_def_property_ui_text(prop, "Targets", "Target Bones");
+       rna_def_constraint_armature_deform_targets(brna, prop);
+
+       prop = RNA_def_property(srna, "use_deform_preserve_volume", PROP_BOOLEAN, PROP_NONE);
+       RNA_def_property_boolean_sdna(prop, NULL, "flag", CONSTRAINT_ARMATURE_QUATERNION);
+       RNA_def_property_ui_text(prop, "Preserve Volume", "Deform rotation interpolation with quaternions");
+       RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update");
+
+       prop = RNA_def_property(srna, "use_bone_envelopes", PROP_BOOLEAN, PROP_NONE);
+       RNA_def_property_boolean_sdna(prop, NULL, "flag", CONSTRAINT_ARMATURE_ENVELOPE);
+       RNA_def_property_ui_text(prop, "Use Envelopes",
+                                "Multiply weights by envelope for all bones, instead of acting like Vertex Group based blending. "
+                                "The specified weights are still used, and only the listed bones are considered");
+       RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update");
+
+       prop = RNA_def_property(srna, "use_current_location", PROP_BOOLEAN, PROP_NONE);
+       RNA_def_property_boolean_sdna(prop, NULL, "flag", CONSTRAINT_ARMATURE_CUR_LOCATION);
+       RNA_def_property_ui_text(prop, "Use Current Location",
+                                "Use the current bone location for envelopes and choosing B-Bone segments instead of rest position");
+       RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update");
+}
+
 static void rna_def_constraint_kinematic(BlenderRNA *brna)
 {
        StructRNA *srna;
@@ -2465,9 +2682,11 @@ void RNA_def_constraint(BlenderRNA *brna)
 
        /* pointers */
        rna_def_constrainttarget(brna);
+       rna_def_constrainttarget_bone(brna);
 
        rna_def_constraint_childof(brna);
        rna_def_constraint_python(brna);
+       rna_def_constraint_armature_deform(brna);
        rna_def_constraint_stretch_to(brna);
        rna_def_constraint_follow_path(brna);
        rna_def_constraint_locked_track(brna);