Rigging Goodies: Spline IK Constraint

At last, this commit introduces the Spline IK Constraint to Blender. Spline IK is a constraint that makes n bones follow the shape of a specified curve. 

Simply add a chain of bones, add a curve, add a Spline IK Constraint to the tip bone and set the number of bones in the chain to make it work. Or, try the following test file:
http://download.blender.org/ftp/incoming/250_splineik_spine01.blend

Screenshots of this in action (as proof):
http://download.blender.org/ftp/incoming/b250_splineik_001_before.png
http://download.blender.org/ftp/incoming/b250_splineik_001_after.png

I've implemented this in a similar way to how standard IK solvers are done. However, this code is currently not an IK plugin, since I imagine that it would be useful to be able to combine the 2 types of IK. This can be easily changed though :)

Finally, a few notes on what to expect still:
* Constraint blending currently doesn't affect this. Getting that to work correctly will take a bit more work still.
* Options for not affecting the root joint (to make it easier to attach the chain to a stump or whatever), and non-uniform scaling options have yet to be added. I've marked the places where they can be added though
* Control over the twisting of the chain still needs investigation. 

Have fun!
This commit is contained in:
Joshua Leung 2009-11-01 11:29:40 +00:00
parent cb45db0336
commit 2068eaf1b7
16 changed files with 590 additions and 72 deletions

@ -582,6 +582,13 @@ class ConstraintButtonsPanel(bpy.types.Panel):
row.itemL(text="To:") row.itemL(text="To:")
row.itemR(con, "track", expand=True) row.itemR(con, "track", expand=True)
def SPLINE_IK(self, context, layout, con):
self.target_template(layout, con)
row = layout.row()
row.itemR(con, "chain_length")
# TODO: add the various options this constraint has...
class OBJECT_PT_constraints(ConstraintButtonsPanel): class OBJECT_PT_constraints(ConstraintButtonsPanel):
bl_label = "Constraints" bl_label = "Constraints"

@ -56,10 +56,13 @@ typedef struct PoseTree
{ {
struct PoseTree *next, *prev; struct PoseTree *next, *prev;
int type; /* type of IK that this serves (CONSTRAINT_TYPE_KINEMATIC or ..._SPLINEIK) */
int totchannel; /* number of pose channels */
struct ListBase targets; /* list of targets of the tree */ struct ListBase targets; /* list of targets of the tree */
struct bPoseChannel **pchan; /* array of pose channels */ struct bPoseChannel **pchan; /* array of pose channels */
int *parent; /* and their parents */ int *parent; /* and their parents */
int totchannel; /* number of pose channels */
float (*basis_change)[3][3]; /* basis change result from solver */ float (*basis_change)[3][3]; /* basis change result from solver */
int iterations; /* iterations from the constraint */ int iterations; /* iterations from the constraint */
int stretch; /* disable stretching */ int stretch; /* disable stretching */

@ -622,7 +622,7 @@ static void copy_pose_channel_data(bPoseChannel *pchan, const bPoseChannel *chan
} }
} }
/* checks for IK constraint, and also for Follow-Path constraint. /* checks for IK constraint, Spline IK, and also for Follow-Path constraint.
* can do more constraints flags later * can do more constraints flags later
*/ */
/* pose should be entirely OK */ /* pose should be entirely OK */
@ -675,6 +675,8 @@ void update_pose_constraint_flags(bPose *pose)
if ((data->tar) && (data->tar->type==OB_CURVE)) if ((data->tar) && (data->tar->type==OB_CURVE))
pose->flag |= POSE_CONSTRAINTS_TIMEDEPEND; pose->flag |= POSE_CONSTRAINTS_TIMEDEPEND;
} }
else if (con->type == CONSTRAINT_TYPE_SPLINEIK)
pchan->constflag |= PCHAN_HAS_SPLINEIK;
else else
pchan->constflag |= PCHAN_HAS_CONST; pchan->constflag |= PCHAN_HAS_CONST;
} }

@ -52,6 +52,7 @@
#include "BKE_armature.h" #include "BKE_armature.h"
#include "BKE_action.h" #include "BKE_action.h"
#include "BKE_anim.h"
#include "BKE_blender.h" #include "BKE_blender.h"
#include "BKE_constraint.h" #include "BKE_constraint.h"
#include "BKE_curve.h" #include "BKE_curve.h"
@ -1604,6 +1605,260 @@ void armature_rebuild_pose(Object *ob, bArmature *arm)
} }
/* ********************** SPLINE IK SOLVER ******************* */
/* Temporary evaluation tree data used for Spline IK */
typedef struct tSplineIK_Tree {
struct tSplineIK_Tree *next, *prev;
int type; /* type of IK that this serves (CONSTRAINT_TYPE_KINEMATIC or ..._SPLINEIK) */
int chainlen; /* number of bones in the chain */
bPoseChannel **chain; /* chain of bones to affect using Spline IK (ordered from the tip) */
bPoseChannel *root; /* bone that is the root node of the chain */
bConstraint *con; /* constraint for this chain */
bSplineIKConstraint *ikData; /* constraint settings for this chain */
} tSplineIK_Tree;
/* ----------- */
/* Tag the bones in the chain formed by the given bone for IK */
static void splineik_init_tree_from_pchan(Object *ob, bPoseChannel *pchan_tip)
{
bPoseChannel *pchan, *pchanRoot=NULL;
bPoseChannel *pchanChain[255];
bConstraint *con = NULL;
bSplineIKConstraint *ikData = NULL;
float boneLengths[255];
float totLength = 0.0f;
int segcount = 0;
/* find the SplineIK constraint */
for (con= pchan_tip->constraints.first; con; con= con->next) {
if (con->type == CONSTRAINT_TYPE_SPLINEIK) {
ikData= con->data;
/* target can only be curve */
if ((ikData->tar == NULL) || (ikData->tar->type != OB_CURVE))
continue;
/* skip if disabled */
if ( (con->enforce == 0.0f) || (con->flag & (CONSTRAINT_DISABLE|CONSTRAINT_OFF)) )
continue;
/* otherwise, constraint is ok... */
break;
}
}
if (con == NULL)
return;
/* find the root bone and the chain of bones from the root to the tip
* NOTE: this assumes that the bones are connected, but that may not be true...
*/
for (pchan= pchan_tip; pchan; pchan= pchan->parent) {
/* store this segment in the chain */
pchanChain[segcount]= pchan;
/* if performing rebinding, calculate the length of the bone */
if ((ikData->flag & CONSTRAINT_SPLINEIK_BOUND) == 0) {
boneLengths[segcount]= pchan->bone->length;
totLength += boneLengths[segcount];
}
/* check if we've gotten the number of bones required yet (after incrementing the count first)
* NOTE: the 255 limit here is rather ugly, but the standard IK does this too!
*/
segcount++;
if ((segcount == ikData->chainlen) || (segcount > 255))
break;
}
if (segcount == 0)
return;
else
pchanRoot= pchanChain[segcount-1];
/* perform binding step if required */
if ((ikData->flag & CONSTRAINT_SPLINEIK_BOUND) == 0) {
int i;
/* setup new empty array for the points list */
if (ikData->points)
MEM_freeN(ikData->points);
ikData->numpoints= (ikData->flag & CONSTRAINT_SPLINEIK_NO_ROOT)? ikData->chainlen : ikData->chainlen+1;
ikData->points= MEM_callocN(sizeof(float)*ikData->numpoints, "Spline IK Binding");
/* perform binding of the joints to parametric positions along the curve based
* proportion of the total length that each bone occupies
*/
for (i = 0; i < segcount; i++) {
if (i != 0) {
/* 'head' joints
* - 2 methods; the one chosen depends on whether we've got usable lengths
*/
if (totLength == 0.0f) {
/* 1) equi-spaced joints */
// TODO: maybe this should become an option too, in case we want this option by default
ikData->points[i]= (1.0f / (float)segcount); // TODO: optimize by puttig this outside the loop!
}
else {
/* 2) to find this point on the curve, we take a step from the previous joint
* a distance given by the proportion that this bone takes
*/
ikData->points[i]= ikData->points[i-1] - (boneLengths[i] / totLength);
}
}
else {
/* 'tip' of chain, special exception for the first joint */
ikData->points[0]= 1.0f;
}
}
/* spline has now been bound */
ikData->flag |= CONSTRAINT_SPLINEIK_BOUND;
}
/* make a new Spline-IK chain, and store it in the IK chains */
// TODO: we should check if there is already an IK chain on this, since that would take presidence...
{
/* make new tree */
tSplineIK_Tree *tree= MEM_callocN(sizeof(tSplineIK_Tree), "SplineIK Tree");
tree->type= CONSTRAINT_TYPE_SPLINEIK;
tree->chainlen= segcount;
/* copy over the array of links to bones in the chain (from tip to root) */
tree->chain= MEM_callocN(sizeof(bPoseChannel*)*segcount, "SplineIK Chain");
memcpy(tree->chain, pchanChain, sizeof(bPoseChannel*)*segcount);
tree->root= pchanRoot;
tree->con= con;
tree->ikData= ikData;
/* AND! link the tree to the root */
BLI_addtail(&pchanRoot->iktree, tree);
}
/* mark root channel having an IK tree */
pchanRoot->flag |= POSE_IKSPLINE;
}
/* Tag which bones are members of Spline IK chains */
static void splineik_init_tree(Scene *scene, Object *ob, float ctime)
{
bPoseChannel *pchan;
/* find the tips of Spline IK chains, which are simply the bones which have been tagged as such */
for (pchan= ob->pose->chanbase.first; pchan; pchan= pchan->next) {
if (pchan->constflag & PCHAN_HAS_SPLINEIK)
splineik_init_tree_from_pchan(ob, pchan);
}
}
/* ----------- */
/* Evaluate spline IK for a given bone */
// TODO: this method doesn't allow for non-strechiness...
// TODO: include code for dealing with constraint blending
static void splineik_evaluate_bone(tSplineIK_Tree *tree, Object *ob, bPoseChannel *pchan, int index)
{
bSplineIKConstraint *ikData= tree->ikData;
float dirX[3]={1,0,0}, dirZ[3]={0,0,1};
float axis1[3], axis2[3], tmpVec[3];
float splineVec[3], scaleFac;
float vec[4], dir[3];
/* step 1: get xyz positions for the endpoints of the bone */
/* tail */
if ( where_on_path(ikData->tar, ikData->points[index], vec, dir, NULL, NULL) ) {
/* convert the position to pose-space, then store it */
Mat4MulVecfl(ob->imat, vec);
VECCOPY(pchan->pose_tail, vec);
}
/* head */ // TODO: only calculate here when we're
if ( where_on_path(ikData->tar, ikData->points[index+1], vec, dir, NULL, NULL) ) {
/* store the position, and convert it to pose space */
Mat4MulVecfl(ob->imat, vec);
VECCOPY(pchan->pose_head, vec);
}
/* step 2a: determine the implied transform from these endpoints
* - splineVec: the vector direction that the spline applies on the bone
* - scaleFac: the factor that the bone length is scaled by to get the desired amount
*/
VecSubf(splineVec, pchan->pose_tail, pchan->pose_head);
scaleFac= VecLength(splineVec) / pchan->bone->length; // TODO: this will need to be modified by blending factor
/* step 2b: the spline vector now becomes the y-axis of the bone
* - we need to normalise the splineVec first, so that it's just a unit direction vector
*/
Mat4One(pchan->pose_mat);
Normalize(splineVec);
VECCOPY(pchan->pose_mat[1], splineVec);
/* step 3: determine two vectors which will both be at right angles to the bone vector
* based on the method described at
* http://ltcconline.net/greenl/courses/203/Vectors/orthonormalBases.htm
* and normalise them to make sure they they don't act strangely
*/
/* x-axis = dirX - projection(dirX onto splineVec) */
Projf(axis1, dirX, splineVec); /* project dirX onto splineVec */
VecSubf(pchan->pose_mat[0], dirX, axis1);
Normalize(pchan->pose_mat[0]);
/* z-axis = dirZ - projection(dirZ onto splineVec) - projection(dirZ onto dirX) */
Projf(axis1, dirZ, splineVec); /* project dirZ onto Y-Axis */
Projf(axis2, dirZ, pchan->pose_mat[0]); /* project dirZ onto X-Axis */
VecSubf(tmpVec, dirZ, axis1); /* dirZ - proj(dirZ->YAxis) */
VecSubf(pchan->pose_mat[2], tmpVec, axis2); /* (dirZ - proj(dirZ->YAxis)) - proj(dirZ->XAxis) */
Normalize(pchan->pose_mat[2]);
/* step 4a: multiply all the axes of the bone by the scaling factor to get uniform scaling */
// TODO: maybe this can be extended to give non-uniform scaling?
//VecMulf(pchan->pose_mat[0], scaleFac);
VecMulf(pchan->pose_mat[1], scaleFac);
//VecMulf(pchan->pose_mat[2], scaleFac);
/* step 5: set the location of the bone in the matrix */
VECCOPY(pchan->pose_mat[3], pchan->pose_head);
/* done! */
pchan->flag |= POSE_DONE;
}
/* Evaluate the chain starting from the nominated bone */
static void splineik_execute_tree(Scene *scene, Object *ob, bPoseChannel *pchan_root, float ctime)
{
tSplineIK_Tree *tree;
/* for each pose-tree, execute it if it is spline, otherwise just free it */
for (tree= pchan_root->iktree.first; tree; tree= pchan_root->iktree.first) {
/* only evaluate if tagged for Spline IK */
if (tree->type == CONSTRAINT_TYPE_SPLINEIK) {
int i;
/* walk over each bone in the chain, calculating the effects of spline IK */
for (i= 0; i < tree->chainlen; i++) {
bPoseChannel *pchan= tree->chain[i];
splineik_evaluate_bone(tree, ob, pchan, i);
}
}
/* free the tree info now */
if (tree->chain) MEM_freeN(tree->chain);
BLI_freelinkN(&pchan_root->iktree, tree);
}
}
/* ********************** THE POSE SOLVER ******************* */ /* ********************** THE POSE SOLVER ******************* */
@ -1629,7 +1884,7 @@ void chan_calc_mat(bPoseChannel *chan)
} }
else { else {
/* quats are normalised before use to eliminate scaling issues */ /* quats are normalised before use to eliminate scaling issues */
NormalQuat(chan->quat); NormalQuat(chan->quat); // TODO: do this with local vars only!
QuatToMat3(chan->quat, rmat); QuatToMat3(chan->quat, rmat);
} }
@ -1913,16 +2168,26 @@ void where_is_pose (Scene *scene, Object *ob)
for(pchan= ob->pose->chanbase.first; pchan; pchan= pchan->next) { for(pchan= ob->pose->chanbase.first; pchan; pchan= pchan->next) {
pchan->flag &= ~(POSE_DONE|POSE_CHAIN|POSE_IKTREE); pchan->flag &= ~(POSE_DONE|POSE_CHAIN|POSE_IKTREE);
} }
/* 2. construct the IK tree */
/* 2a. construct the IK tree (standard IK) */
BIK_initialize_tree(scene, ob, ctime); BIK_initialize_tree(scene, ob, ctime);
/* 2b. construct the Spline IK trees
* - this is not integrated as an IK plugin, since it should be able
* to function in conjunction with standard IK
*/
splineik_init_tree(scene, ob, ctime);
/* 3. the main loop, channels are already hierarchical sorted from root to children */ /* 3. the main loop, channels are already hierarchical sorted from root to children */
for(pchan= ob->pose->chanbase.first; pchan; pchan= pchan->next) { for(pchan= ob->pose->chanbase.first; pchan; pchan= pchan->next) {
/* 4a. if we find an IK root, we handle it separated */
/* 4. if we find an IK root, we handle it separated */
if(pchan->flag & POSE_IKTREE) { if(pchan->flag & POSE_IKTREE) {
BIK_execute_tree(scene, ob, pchan, ctime); BIK_execute_tree(scene, ob, pchan, ctime);
} }
/* 4b. if we find a Spline IK root, we handle it separated too */
else if(pchan->flag & POSE_IKSPLINE) {
splineik_execute_tree(scene, ob, pchan, ctime);
}
/* 5. otherwise just call the normal solver */ /* 5. otherwise just call the normal solver */
else if(!(pchan->flag & POSE_DONE)) { else if(!(pchan->flag & POSE_DONE)) {
where_is_pose_bone(scene, ob, pchan, ctime); where_is_pose_bone(scene, ob, pchan, ctime);

@ -3446,13 +3446,95 @@ static bConstraintTypeInfo CTI_DAMPTRACK = {
damptrack_evaluate /* evaluate */ damptrack_evaluate /* evaluate */
}; };
/* ----------- Spline IK ------------ */
static void splineik_free (bConstraint *con)
{
bSplineIKConstraint *data= con->data;
/* binding array */
if (data->points)
MEM_freeN(data->points);
}
static void splineik_copy (bConstraint *con, bConstraint *srccon)
{
bSplineIKConstraint *src= srccon->data;
bSplineIKConstraint *dst= con->data;
/* copy the binding array */
dst->points= MEM_dupallocN(src->points);
}
static int splineik_get_tars (bConstraint *con, ListBase *list)
{
if (con && list) {
bSplineIKConstraint *data= con->data;
bConstraintTarget *ct;
/* standard target-getting macro for single-target constraints without subtargets */
SINGLETARGETNS_GET_TARS(con, data->tar, ct, list)
return 1;
}
return 0;
}
static void splineik_flush_tars (bConstraint *con, ListBase *list, short nocopy)
{
if (con && list) {
bSplineIKConstraint *data= con->data;
bConstraintTarget *ct= list->first;
/* the following macro is used for all standard single-target constraints */
SINGLETARGETNS_FLUSH_TARS(con, data->tar, ct, list, nocopy)
}
}
static void splineik_get_tarmat (bConstraint *con, bConstraintOb *cob, bConstraintTarget *ct, float ctime)
{
if (VALID_CONS_TARGET(ct)) {
Curve *cu= ct->tar->data;
/* note: when creating constraints that follow path, the curve gets the CU_PATH set now,
* currently for paths to work it needs to go through the bevlist/displist system (ton)
*/
/* only happens on reload file, but violates depsgraph still... fix! */
if (cu->path==NULL || cu->path->data==NULL)
makeDispListCurveTypes(cob->scene, ct->tar, 0);
}
/* technically, this isn't really needed for evaluation, but we don't know what else
* might end up calling this...
*/
if (ct)
Mat4One(ct->matrix);
}
static bConstraintTypeInfo CTI_SPLINEIK = {
CONSTRAINT_TYPE_SPLINEIK, /* type */
sizeof(bSplineIKConstraint), /* size */
"Spline IK", /* name */
"bSplineIKConstraint", /* struct name */
splineik_free, /* free data */
NULL, /* relink data */
splineik_copy, /* copy data */
NULL, /* new data */
splineik_get_tars, /* get constraint targets */
splineik_flush_tars, /* flush constraint targets */
splineik_get_tarmat, /* get target matrix */
NULL /* evaluate - solved as separate loop */
};
/* ************************* Constraints Type-Info *************************** */ /* ************************* Constraints Type-Info *************************** */
/* All of the constraints api functions use bConstraintTypeInfo structs to carry out /* All of the constraints api functions use bConstraintTypeInfo structs to carry out
* and operations that involve constraint specific code. * and operations that involve constraint specific code.
*/ */
/* These globals only ever get directly accessed in this file */ /* These globals only ever get directly accessed in this file */
static bConstraintTypeInfo *constraintsTypeInfo[NUM_CONSTRAINT_TYPES+1]; static bConstraintTypeInfo *constraintsTypeInfo[NUM_CONSTRAINT_TYPES];
static short CTI_INIT= 1; /* when non-zero, the list needs to be updated */ static short CTI_INIT= 1; /* when non-zero, the list needs to be updated */
/* This function only gets called when CTI_INIT is non-zero */ /* This function only gets called when CTI_INIT is non-zero */
@ -3479,6 +3561,7 @@ static void constraints_init_typeinfo () {
constraintsTypeInfo[19]= &CTI_TRANSFORM; /* Transformation Constraint */ constraintsTypeInfo[19]= &CTI_TRANSFORM; /* Transformation Constraint */
constraintsTypeInfo[20]= &CTI_SHRINKWRAP; /* Shrinkwrap Constraint */ constraintsTypeInfo[20]= &CTI_SHRINKWRAP; /* Shrinkwrap Constraint */
constraintsTypeInfo[21]= &CTI_DAMPTRACK; /* Damped TrackTo Constraint */ constraintsTypeInfo[21]= &CTI_DAMPTRACK; /* Damped TrackTo Constraint */
constraintsTypeInfo[22]= &CTI_SPLINEIK; /* Spline IK Constraint */
} }
/* This function should be used for getting the appropriate type-info when only /* This function should be used for getting the appropriate type-info when only
@ -3494,7 +3577,7 @@ bConstraintTypeInfo *get_constraint_typeinfo (int type)
/* only return for valid types */ /* only return for valid types */
if ( (type >= CONSTRAINT_TYPE_NULL) && if ( (type >= CONSTRAINT_TYPE_NULL) &&
(type <= NUM_CONSTRAINT_TYPES ) ) (type < NUM_CONSTRAINT_TYPES ) )
{ {
/* there shouldn't be any segfaults here... */ /* there shouldn't be any segfaults here... */
return constraintsTypeInfo[type]; return constraintsTypeInfo[type];

@ -399,7 +399,7 @@ static void build_dag_object(DagForest *dag, DagNode *scenenode, Scene *scene, O
if (ct->subtarget[0]) if (ct->subtarget[0])
dag_add_relation(dag,node3,node, DAG_RL_OB_DATA|DAG_RL_DATA_DATA, cti->name); dag_add_relation(dag,node3,node, DAG_RL_OB_DATA|DAG_RL_DATA_DATA, cti->name);
else if(ELEM(con->type, CONSTRAINT_TYPE_FOLLOWPATH, CONSTRAINT_TYPE_CLAMPTO)) else if(ELEM3(con->type, CONSTRAINT_TYPE_FOLLOWPATH, CONSTRAINT_TYPE_CLAMPTO, CONSTRAINT_TYPE_SPLINEIK))
dag_add_relation(dag,node3,node, DAG_RL_DATA_DATA|DAG_RL_OB_DATA, cti->name); dag_add_relation(dag,node3,node, DAG_RL_DATA_DATA|DAG_RL_OB_DATA, cti->name);
else else
dag_add_relation(dag,node3,node, DAG_RL_OB_DATA, cti->name); dag_add_relation(dag,node3,node, DAG_RL_OB_DATA, cti->name);
@ -2407,7 +2407,7 @@ void DAG_pose_sort(Object *ob)
bPoseChannel *target= get_pose_channel(ob->pose, ct->subtarget); bPoseChannel *target= get_pose_channel(ob->pose, ct->subtarget);
if (target) { if (target) {
node2= dag_get_node(dag, target); node2= dag_get_node(dag, target);
dag_add_relation(dag, node2, node, 0, "IK Constraint"); dag_add_relation(dag, node2, node, 0, "Pose Constraint");
if (con->type==CONSTRAINT_TYPE_KINEMATIC) { if (con->type==CONSTRAINT_TYPE_KINEMATIC) {
bKinematicConstraint *data = (bKinematicConstraint *)con->data; bKinematicConstraint *data = (bKinematicConstraint *)con->data;

@ -2288,6 +2288,11 @@ static void direct_link_constraints(FileData *fd, ListBase *lb)
if (data->prop) if (data->prop)
IDP_DirectLinkProperty(data->prop, (fd->flags & FD_FLAGS_SWITCH_ENDIAN), fd); IDP_DirectLinkProperty(data->prop, (fd->flags & FD_FLAGS_SWITCH_ENDIAN), fd);
} }
else if (cons->type == CONSTRAINT_TYPE_SPLINEIK) {
bSplineIKConstraint *data= cons->data;
data->points= newdataadr(fd, data->points);
}
} }
} }

@ -1078,6 +1078,14 @@ static void write_constraints(WriteData *wd, ListBase *conlist)
IDP_WriteProperty(data->prop, wd); IDP_WriteProperty(data->prop, wd);
} }
break; break;
case CONSTRAINT_TYPE_SPLINEIK:
{
bSplineIKConstraint *data= (bSplineIKConstraint*)con->data;
/* write points array */
writedata(wd, DATA, sizeof(float)*(data->numpoints), data->points);
}
break;
} }
} }

@ -395,6 +395,26 @@ static void test_constraints (Object *owner, const char substring[])
if (data->lockflag+3==data->trackflag) if (data->lockflag+3==data->trackflag)
curcon->flag |= CONSTRAINT_DISABLE; curcon->flag |= CONSTRAINT_DISABLE;
} }
else if (curcon->type == CONSTRAINT_TYPE_SPLINEIK) {
bSplineIKConstraint *data = curcon->data;
/* if the number of points does not match the amount required by the chain length,
* free the points array and request a rebind...
*/
if ( (data->points == NULL) ||
(!(data->flag & CONSTRAINT_SPLINEIK_NO_ROOT) && (data->numpoints != data->chainlen+1)) ||
( (data->flag & CONSTRAINT_SPLINEIK_NO_ROOT) && (data->numpoints != data->chainlen)) )
{
/* free the points array */
if (data->points) {
MEM_freeN(data->points);
data->points = NULL;
}
/* clear the bound flag, forcing a rebind next time this is evaluated */
data->flag &= ~CONSTRAINT_SPLINEIK_BOUND;
}
}
/* Check targets for constraints */ /* Check targets for constraints */
if (cti && cti->get_constraint_targets) { if (cti && cti->get_constraint_targets) {
@ -414,7 +434,7 @@ static void test_constraints (Object *owner, const char substring[])
} }
/* target checks for specific constraints */ /* target checks for specific constraints */
if (ELEM(curcon->type, CONSTRAINT_TYPE_FOLLOWPATH, CONSTRAINT_TYPE_CLAMPTO)) { if (ELEM3(curcon->type, CONSTRAINT_TYPE_FOLLOWPATH, CONSTRAINT_TYPE_CLAMPTO, CONSTRAINT_TYPE_SPLINEIK)) {
if (ct->tar) { if (ct->tar) {
if (ct->tar->type != OB_CURVE) { if (ct->tar->type != OB_CURVE) {
ct->tar= NULL; ct->tar= NULL;
@ -855,7 +875,7 @@ static int pose_constraints_clear_exec(bContext *C, wmOperator *op)
CTX_DATA_BEGIN(C, bPoseChannel*, pchan, selected_pchans) CTX_DATA_BEGIN(C, bPoseChannel*, pchan, selected_pchans)
{ {
free_constraints(&pchan->constraints); free_constraints(&pchan->constraints);
pchan->constflag &= ~(PCHAN_HAS_IK|PCHAN_HAS_CONST); pchan->constflag &= ~(PCHAN_HAS_IK|PCHAN_HAS_SPLINEIK|PCHAN_HAS_CONST);
} }
CTX_DATA_END; CTX_DATA_END;
@ -947,6 +967,7 @@ static short get_new_constraint_target(bContext *C, int con_type, Object **tar_o
/* curve-based constraints - set the only_curve and only_ob flags */ /* curve-based constraints - set the only_curve and only_ob flags */
case CONSTRAINT_TYPE_CLAMPTO: case CONSTRAINT_TYPE_CLAMPTO:
case CONSTRAINT_TYPE_FOLLOWPATH: case CONSTRAINT_TYPE_FOLLOWPATH:
case CONSTRAINT_TYPE_SPLINEIK:
only_curve= 1; only_curve= 1;
only_ob= 1; only_ob= 1;
add= 0; add= 0;
@ -1070,6 +1091,10 @@ static int constraint_add_exec(bContext *C, wmOperator *op, Object *ob, ListBase
BKE_report(op->reports, RPT_ERROR, "IK Constraint can only be added to Bones."); BKE_report(op->reports, RPT_ERROR, "IK Constraint can only be added to Bones.");
return OPERATOR_CANCELLED; return OPERATOR_CANCELLED;
} }
if ( (type == CONSTRAINT_TYPE_SPLINEIK) && ((!pchan) || (list != &pchan->constraints)) ) {
BKE_report(op->reports, RPT_ERROR, "Spline IK Constraint can only be added to Bones.");
return OPERATOR_CANCELLED;
}
/* create a new constraint of the type requried, and add it to the active/given constraints list */ /* create a new constraint of the type requried, and add it to the active/given constraints list */
con = add_new_constraint(type); con = add_new_constraint(type);
@ -1119,6 +1144,7 @@ static int constraint_add_exec(bContext *C, wmOperator *op, Object *ob, ListBase
} }
/* do type-specific tweaking to the constraint settings */ /* do type-specific tweaking to the constraint settings */
// TODO: does action constraint need anything here - i.e. spaceonce?
switch (type) { switch (type) {
case CONSTRAINT_TYPE_CHILDOF: case CONSTRAINT_TYPE_CHILDOF:
{ {

@ -209,6 +209,7 @@ static short set_pchan_glColor (short colCode, int armflag, int boneflag, int co
if (constflag & PCHAN_HAS_STRIDE) glColor4ub(0, 0, 200, 80); if (constflag & PCHAN_HAS_STRIDE) glColor4ub(0, 0, 200, 80);
else if (constflag & PCHAN_HAS_TARGET) glColor4ub(255, 150, 0, 80); else if (constflag & PCHAN_HAS_TARGET) glColor4ub(255, 150, 0, 80);
else if (constflag & PCHAN_HAS_IK) glColor4ub(255, 255, 0, 80); else if (constflag & PCHAN_HAS_IK) glColor4ub(255, 255, 0, 80);
else if (constflag & PCHAN_HAS_SPLINEIK) glColor4ub(200, 255, 0, 80);
else if (constflag & PCHAN_HAS_CONST) glColor4ub(0, 255, 120, 80); else if (constflag & PCHAN_HAS_CONST) glColor4ub(0, 255, 120, 80);
else if (constflag) UI_ThemeColor4(TH_BONE_POSE); // PCHAN_HAS_ACTION else if (constflag) UI_ThemeColor4(TH_BONE_POSE); // PCHAN_HAS_ACTION
@ -280,6 +281,7 @@ static short set_pchan_glColor (short colCode, int armflag, int boneflag, int co
if (constflag & PCHAN_HAS_STRIDE) glColor3ub(0, 0, 200); if (constflag & PCHAN_HAS_STRIDE) glColor3ub(0, 0, 200);
else if (constflag & PCHAN_HAS_TARGET) glColor3ub(255, 150, 0); else if (constflag & PCHAN_HAS_TARGET) glColor3ub(255, 150, 0);
else if (constflag & PCHAN_HAS_IK) glColor3ub(255, 255, 0); else if (constflag & PCHAN_HAS_IK) glColor3ub(255, 255, 0);
else if (constflag & PCHAN_HAS_SPLINEIK) glColor3ub(200, 255, 0);
else if (constflag & PCHAN_HAS_CONST) glColor3ub(0, 255, 120); else if (constflag & PCHAN_HAS_CONST) glColor3ub(0, 255, 120);
else if (constflag) UI_ThemeColor(TH_BONE_POSE); /* PCHAN_HAS_ACTION */ else if (constflag) UI_ThemeColor(TH_BONE_POSE); /* PCHAN_HAS_ACTION */
} }
@ -1296,7 +1298,12 @@ static void pchan_draw_IK_root_lines(bPoseChannel *pchan, short only_temp)
bPoseChannel *parchan; bPoseChannel *parchan;
for (con= pchan->constraints.first; con; con= con->next) { for (con= pchan->constraints.first; con; con= con->next) {
if (con->type == CONSTRAINT_TYPE_KINEMATIC && (con->enforce!=0.0)) { if (con->enforce == 0.0f)
continue;
switch (con->type) {
case CONSTRAINT_TYPE_KINEMATIC:
{
bKinematicConstraint *data = (bKinematicConstraint*)con->data; bKinematicConstraint *data = (bKinematicConstraint*)con->data;
int segcount= 0; int segcount= 0;
@ -1327,6 +1334,33 @@ static void pchan_draw_IK_root_lines(bPoseChannel *pchan, short only_temp)
glEnd(); glEnd();
setlinestyle(0); setlinestyle(0);
} }
break;
case CONSTRAINT_TYPE_SPLINEIK:
{
bSplineIKConstraint *data = (bSplineIKConstraint*)con->data;
int segcount= 0;
setlinestyle(3);
glBegin(GL_LINES);
parchan= pchan;
glVertex3fv(parchan->pose_tail);
/* Find the chain's root */
while (parchan->parent) {
segcount++;
// FIXME: revise the breaking conditions
if(segcount==data->chainlen || segcount>255) break; // 255 is weak
parchan= parchan->parent;
}
if (parchan) // XXX revise the breaking conditions to only stop at the tail?
glVertex3fv(parchan->pose_head);
glEnd();
setlinestyle(0);
}
break;
}
} }
} }
@ -1739,6 +1773,14 @@ static void draw_pose_channels(Scene *scene, View3D *v3d, ARegion *ar, Base *bas
if (pchan->constflag & PCHAN_HAS_TARGET) glColor3ub(200, 120, 0); if (pchan->constflag & PCHAN_HAS_TARGET) glColor3ub(200, 120, 0);
else glColor3ub(200, 200, 50); // add theme! else glColor3ub(200, 200, 50); // add theme!
glLoadName(index & 0xFFFF);
pchan_draw_IK_root_lines(pchan, !(do_dashed & 2));
}
}
else if (pchan->constflag & PCHAN_HAS_SPLINEIK) {
if (bone->flag & BONE_SELECTED) {
glColor3ub(150, 200, 50); // add theme!
glLoadName(index & 0xFFFF); glLoadName(index & 0xFFFF);
pchan_draw_IK_root_lines(pchan, !(do_dashed & 2)); pchan_draw_IK_root_lines(pchan, !(do_dashed & 2));
} }

@ -110,6 +110,8 @@ static void initialize_posetree(struct Object *ob, bPoseChannel *pchan_tip)
/* make new tree */ /* make new tree */
tree= MEM_callocN(sizeof(PoseTree), "posetree"); tree= MEM_callocN(sizeof(PoseTree), "posetree");
tree->type= CONSTRAINT_TYPE_KINEMATIC;
tree->iterations= data->iterations; tree->iterations= data->iterations;
tree->totchannel= segcount; tree->totchannel= segcount;
tree->stretch = (data->flag & CONSTRAINT_IK_STRETCH); tree->stretch = (data->flag & CONSTRAINT_IK_STRETCH);
@ -503,6 +505,10 @@ void iksolver_execute_tree(struct Scene *scene, struct Object *ob, struct bPose
PoseTree *tree= pchan->iktree.first; PoseTree *tree= pchan->iktree.first;
int a; int a;
/* stop on the first tree that isn't a standard IK chain */
if (tree->type != CONSTRAINT_TYPE_KINEMATIC)
return;
/* 4. walk over the tree for regular solving */ /* 4. walk over the tree for regular solving */
for(a=0; a<tree->totchannel; a++) { for(a=0; a<tree->totchannel; a++) {
if(!(tree->pchan[a]->flag & POSE_DONE)) // successive trees can set the flag if(!(tree->pchan[a]->flag & POSE_DONE)) // successive trees can set the flag

@ -159,20 +159,30 @@ typedef struct bPoseChannel {
/* PoseChannel (transform) flags */ /* PoseChannel (transform) flags */
typedef enum ePchan_Flag { typedef enum ePchan_Flag {
POSE_LOC = 0x0001, /* has transforms */
POSE_ROT = 0x0002, POSE_LOC = (1<<0),
POSE_SIZE = 0x0004, POSE_ROT = (1<<1),
POSE_IK_MAT = 0x0008, POSE_SIZE = (1<<2),
POSE_UNUSED2 = 0x0010, /* old IK/cache stuff... */
POSE_UNUSED3 = 0x0020, POSE_IK_MAT = (1<<3),
POSE_UNUSED4 = 0x0040, POSE_UNUSED2 = (1<<4),
POSE_UNUSED5 = 0x0080, POSE_UNUSED3 = (1<<5),
POSE_HAS_IK = 0x0100, POSE_UNUSED4 = (1<<6),
POSE_CHAIN = 0x0200, POSE_UNUSED5 = (1<<7),
POSE_DONE = 0x0400, /* has Standard IK */
POSE_KEY = 0x1000, POSE_HAS_IK = (1<<8),
POSE_STRIDE = 0x2000, /* IK/Pose solving*/
POSE_IKTREE = 0x4000, POSE_CHAIN = (1<<9),
POSE_DONE = (1<<10),
/* visualisation */
POSE_KEY = (1<<11),
POSE_STRIDE = (1<<12),
/* standard IK solving */
POSE_IKTREE = (1<<13),
/* has Spline IK */
POSE_HAS_IKS = (1<<14),
/* spline IK solving */
POSE_IKSPLINE = (1<<15),
} ePchan_Flag; } ePchan_Flag;
/* PoseChannel constflag (constraint detection) */ /* PoseChannel constflag (constraint detection) */
@ -183,7 +193,9 @@ typedef enum ePchan_ConstFlag {
PCHAN_HAS_ACTION = (1<<2), PCHAN_HAS_ACTION = (1<<2),
PCHAN_HAS_TARGET = (1<<3), PCHAN_HAS_TARGET = (1<<3),
/* only for drawing Posemode too */ /* only for drawing Posemode too */
PCHAN_HAS_STRIDE = (1<<4) PCHAN_HAS_STRIDE = (1<<4),
/* spline IK */
PCHAN_HAS_SPLINEIK = (1<<5),
} ePchan_ConstFlag; } ePchan_ConstFlag;
/* PoseChannel->ikflag */ /* PoseChannel->ikflag */
@ -202,7 +214,6 @@ typedef enum ePchan_IkFlag {
BONE_IK_NO_XDOF_TEMP = (1<<10), BONE_IK_NO_XDOF_TEMP = (1<<10),
BONE_IK_NO_YDOF_TEMP = (1<<11), BONE_IK_NO_YDOF_TEMP = (1<<11),
BONE_IK_NO_ZDOF_TEMP = (1<<12), BONE_IK_NO_ZDOF_TEMP = (1<<12),
} ePchan_IkFlag; } ePchan_IkFlag;
/* PoseChannel->rotmode and Object->rotmode */ /* PoseChannel->rotmode and Object->rotmode */

@ -73,6 +73,7 @@ typedef struct Bone {
typedef struct bArmature { typedef struct bArmature {
ID id; ID id;
struct AnimData *adt; struct AnimData *adt;
ListBase bonebase; ListBase bonebase;
ListBase chainbase; ListBase chainbase;
ListBase *edbo; /* editbone listbase, we use pointer so we can check state */ ListBase *edbo; /* editbone listbase, we use pointer so we can check state */

@ -32,7 +32,6 @@
#define DNA_CONSTRAINT_TYPES_H #define DNA_CONSTRAINT_TYPES_H
#include "DNA_ID.h" #include "DNA_ID.h"
#include "DNA_ipo_types.h"
#include "DNA_listBase.h" #include "DNA_listBase.h"
#include "DNA_object_types.h" #include "DNA_object_types.h"
@ -41,9 +40,10 @@ struct Text;
struct Ipo; struct Ipo;
/* channels reside in Object or Action (ListBase) constraintChannels */ /* channels reside in Object or Action (ListBase) constraintChannels */
// XXX depreceated... old AnimSys
typedef struct bConstraintChannel { typedef struct bConstraintChannel {
struct bConstraintChannel *next, *prev; struct bConstraintChannel *next, *prev;
Ipo *ipo; struct Ipo *ipo;
short flag; short flag;
char name[30]; char name[30];
} bConstraintChannel; } bConstraintChannel;
@ -66,6 +66,7 @@ typedef struct bConstraint {
int pad; int pad;
struct Ipo *ipo; /* local influence ipo or driver */ // XXX depreceated for 2.5... old animation system hack struct Ipo *ipo; /* local influence ipo or driver */ // XXX depreceated for 2.5... old animation system hack
/* below are readonly fields that are set at runtime by the solver for use in the GE (only IK atm) */ /* below are readonly fields that are set at runtime by the solver for use in the GE (only IK atm) */
float lin_error; /* residual error on constraint expressed in blender unit*/ float lin_error; /* residual error on constraint expressed in blender unit*/
float rot_error; /* residual error on constraint expressed in radiant */ float rot_error; /* residual error on constraint expressed in radiant */
@ -122,7 +123,7 @@ typedef struct bPythonConstraint {
} bPythonConstraint; } bPythonConstraint;
/* inverse-Kinematics (IK) constraint /* Inverse-Kinematics (IK) constraint
This constraint supports a variety of mode determine by the type field This constraint supports a variety of mode determine by the type field
according to B_CONSTRAINT_IK_TYPE. according to B_CONSTRAINT_IK_TYPE.
Some fields are used by all types, some are specific to some types Some fields are used by all types, some are specific to some types
@ -151,6 +152,27 @@ typedef enum B_CONSTRAINT_IK_TYPE {
CONSTRAINT_IK_DISTANCE /* maintain distance with target */ CONSTRAINT_IK_DISTANCE /* maintain distance with target */
} B_CONSTRAINT_IK_TYPE; } B_CONSTRAINT_IK_TYPE;
/* Spline IK Constraint
* Aligns 'n' bones to the curvature defined by the curve,
* with the chain ending on the bone that owns this constraint,
* and starting on the nth parent.
*/
typedef struct bSplineIKConstraint {
/* target(s) */
Object *tar; /* curve object (with follow path enabled) which drives the bone chain */
/* binding details */
float *points; /* array of numpoints items, denoting parametric positions along curve that joints should follow */
short numpoints; /* number of points to bound in points array */
short chainlen; /* number of bones ('n') that are in the chain */
/* settings */
short flag; /* general settings for constraint */
short upflag; /* axis of bone that points up */
} bSplineIKConstraint;
/* Single-target subobject constraints --------------------- */ /* Single-target subobject constraints --------------------- */
/* Track To Constraint */ /* Track To Constraint */
typedef struct bTrackToConstraint { typedef struct bTrackToConstraint {
@ -378,9 +400,10 @@ typedef enum B_CONSTAINT_TYPES {
CONSTRAINT_TYPE_TRANSFORM, /* transformation (loc/rot/size -> loc/rot/size) constraint */ CONSTRAINT_TYPE_TRANSFORM, /* transformation (loc/rot/size -> loc/rot/size) constraint */
CONSTRAINT_TYPE_SHRINKWRAP, /* shrinkwrap (loc/rot) constraint */ CONSTRAINT_TYPE_SHRINKWRAP, /* shrinkwrap (loc/rot) constraint */
CONSTRAINT_TYPE_DAMPTRACK, /* New Tracking constraint that minimises twisting */ CONSTRAINT_TYPE_DAMPTRACK, /* New Tracking constraint that minimises twisting */
CONSTRAINT_TYPE_SPLINEIK, /* Spline-IK - Align 'n' bones to a curve */
/* NOTE: everytime a new constraint is added, update this */ /* NOTE: no constraints are allowed to be added after this */
NUM_CONSTRAINT_TYPES= CONSTRAINT_TYPE_DAMPTRACK NUM_CONSTRAINT_TYPES
} B_CONSTRAINT_TYPES; } B_CONSTRAINT_TYPES;
/* bConstraint->flag */ /* bConstraint->flag */
@ -521,6 +544,13 @@ typedef enum B_CONSTRAINTCHANNEL_FLAG {
/* axis relative to target */ /* axis relative to target */
#define CONSTRAINT_IK_TARGETAXIS 16384 #define CONSTRAINT_IK_TARGETAXIS 16384
/* bSplineIKConstraint->flag */
/* chain has been attached to spline */
#define CONSTRAINT_SPLINEIK_BOUND (1<<0)
/* roll on chains is not determined by the constraint */
#define CONSTRAINT_SPLINEIK_NO_TWIST (1<<1)
/* root of chain is not influence by the constraint */
#define CONSTRAINT_SPLINEIK_NO_ROOT (1<<2)
/* MinMax (floor) flags */ /* MinMax (floor) flags */
#define MINMAX_STICKY 0x01 #define MINMAX_STICKY 0x01

@ -413,6 +413,7 @@ extern StructRNA RNA_SoftBodyModifier;
extern StructRNA RNA_SoftBodySettings; extern StructRNA RNA_SoftBodySettings;
extern StructRNA RNA_Sound; extern StructRNA RNA_Sound;
extern StructRNA RNA_SoundSequence; extern StructRNA RNA_SoundSequence;
extern StructRNA RNA_SplineIKConstraint;
extern StructRNA RNA_Space; extern StructRNA RNA_Space;
extern StructRNA RNA_Space3DView; extern StructRNA RNA_Space3DView;
extern StructRNA RNA_SpaceConsole; extern StructRNA RNA_SpaceConsole;

@ -55,6 +55,7 @@ EnumPropertyItem constraint_type_items[] ={
{CONSTRAINT_TYPE_CLAMPTO, "CLAMP_TO", ICON_CONSTRAINT_DATA, "Clamp To", ""}, {CONSTRAINT_TYPE_CLAMPTO, "CLAMP_TO", ICON_CONSTRAINT_DATA, "Clamp To", ""},
{CONSTRAINT_TYPE_STRETCHTO, "STRETCH_TO",ICON_CONSTRAINT_DATA, "Stretch To", ""}, {CONSTRAINT_TYPE_STRETCHTO, "STRETCH_TO",ICON_CONSTRAINT_DATA, "Stretch To", ""},
{CONSTRAINT_TYPE_KINEMATIC, "IK", ICON_CONSTRAINT_DATA, "Inverse Kinematics", ""}, {CONSTRAINT_TYPE_KINEMATIC, "IK", ICON_CONSTRAINT_DATA, "Inverse Kinematics", ""},
{CONSTRAINT_TYPE_SPLINEIK, "SPLINE_IK", ICON_CONSTRAINT_DATA, "Spline IK", ""},
{0, "", 0, "Relationship", ""}, {0, "", 0, "Relationship", ""},
{CONSTRAINT_TYPE_CHILDOF, "CHILD_OF", ICON_CONSTRAINT_DATA, "Child Of", ""}, {CONSTRAINT_TYPE_CHILDOF, "CHILD_OF", ICON_CONSTRAINT_DATA, "Child Of", ""},
{CONSTRAINT_TYPE_MINMAX, "FLOOR", ICON_CONSTRAINT_DATA, "Floor", ""}, {CONSTRAINT_TYPE_MINMAX, "FLOOR", ICON_CONSTRAINT_DATA, "Floor", ""},
@ -147,6 +148,8 @@ static StructRNA *rna_ConstraintType_refine(struct PointerRNA *ptr)
return &RNA_ShrinkwrapConstraint; return &RNA_ShrinkwrapConstraint;
case CONSTRAINT_TYPE_DAMPTRACK: case CONSTRAINT_TYPE_DAMPTRACK:
return &RNA_DampedTrackConstraint; return &RNA_DampedTrackConstraint;
case CONSTRAINT_TYPE_SPLINEIK:
return &RNA_SplineIKConstraint;
default: default:
return &RNA_UnknownType; return &RNA_UnknownType;
} }
@ -1169,7 +1172,7 @@ static void rna_def_constraint_clamp_to(BlenderRNA *brna)
RNA_def_struct_sdna_from(srna, "bClampToConstraint", "data"); RNA_def_struct_sdna_from(srna, "bClampToConstraint", "data");
prop= RNA_def_property(srna, "target", PROP_POINTER, PROP_NONE); prop= RNA_def_property(srna, "target", PROP_POINTER, PROP_NONE);
RNA_def_property_pointer_sdna(prop, NULL, "tar"); RNA_def_property_pointer_sdna(prop, NULL, "tar"); // TODO: curve only!
RNA_def_property_ui_text(prop, "Target", "Target Object"); RNA_def_property_ui_text(prop, "Target", "Target Object");
RNA_def_property_flag(prop, PROP_EDITABLE); RNA_def_property_flag(prop, PROP_EDITABLE);
RNA_def_property_update(prop, NC_OBJECT|ND_CONSTRAINT, "rna_Constraint_dependency_update"); RNA_def_property_update(prop, NC_OBJECT|ND_CONSTRAINT, "rna_Constraint_dependency_update");
@ -1672,6 +1675,30 @@ static void rna_def_constraint_damped_track(BlenderRNA *brna)
RNA_def_property_update(prop, NC_OBJECT|ND_CONSTRAINT, "rna_Constraint_update"); RNA_def_property_update(prop, NC_OBJECT|ND_CONSTRAINT, "rna_Constraint_update");
} }
static void rna_def_constraint_spline_ik(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
srna= RNA_def_struct(brna, "SplineIKConstraint", "Constraint");
RNA_def_struct_ui_text(srna, "Spline IK Constraint", "Align 'n' bones along a curve.");
RNA_def_struct_sdna_from(srna, "bSplineIKConstraint", "data");
prop= RNA_def_property(srna, "target", PROP_POINTER, PROP_NONE);
RNA_def_property_pointer_sdna(prop, NULL, "tar"); // TODO: curve only
RNA_def_property_ui_text(prop, "Target", "Target Object");
RNA_def_property_flag(prop, PROP_EDITABLE);
RNA_def_property_update(prop, NC_OBJECT|ND_CONSTRAINT, "rna_Constraint_dependency_update");
prop= RNA_def_property(srna, "chain_length", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, NULL, "chainlen");
RNA_def_property_range(prop, 1, 255); // TODO: this should really check the max length of the chain the constraint is attached to
RNA_def_property_ui_text(prop, "Chain Length", "How many bones are included in the chain");
RNA_def_property_update(prop, NC_OBJECT|ND_CONSTRAINT, "rna_Constraint_dependency_update");
// TODO: add access to the positions array to allow more flexible aligning?
}
/* base struct for constraints */ /* base struct for constraints */
void RNA_def_constraint(BlenderRNA *brna) void RNA_def_constraint(BlenderRNA *brna)
{ {
@ -1773,6 +1800,7 @@ void RNA_def_constraint(BlenderRNA *brna)
rna_def_constraint_transform(brna); rna_def_constraint_transform(brna);
rna_def_constraint_shrinkwrap(brna); rna_def_constraint_shrinkwrap(brna);
rna_def_constraint_damped_track(brna); rna_def_constraint_damped_track(brna);
rna_def_constraint_spline_ik(brna);
} }
#endif #endif