Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/editors/object/object_constraint.c')
-rw-r--r--source/blender/editors/object/object_constraint.c1416
1 files changed, 1416 insertions, 0 deletions
diff --git a/source/blender/editors/object/object_constraint.c b/source/blender/editors/object/object_constraint.c
new file mode 100644
index 00000000000..9b073ed5878
--- /dev/null
+++ b/source/blender/editors/object/object_constraint.c
@@ -0,0 +1,1416 @@
+/**
+ * $Id$
+ *
+ * ***** 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): Joshua Leung, Blender Foundation
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_blenlib.h"
+#include "BLI_arithb.h"
+#include "BLI_dynstr.h"
+
+#include "DNA_action_types.h"
+#include "DNA_armature_types.h"
+#include "DNA_constraint_types.h"
+#include "DNA_curve_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_screen_types.h"
+#include "DNA_text_types.h"
+#include "DNA_view3d_types.h"
+
+#include "BKE_action.h"
+#include "BKE_armature.h"
+#include "BKE_constraint.h"
+#include "BKE_context.h"
+#include "BKE_depsgraph.h"
+#include "BKE_global.h"
+#include "BKE_main.h"
+#include "BKE_object.h"
+#include "BKE_report.h"
+#include "BKE_utildefines.h"
+
+#ifndef DISABLE_PYTHON
+#include "BPY_extern.h"
+#endif
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+#include "RNA_enum_types.h"
+#include "RNA_types.h"
+
+#include "ED_object.h"
+#include "ED_screen.h"
+
+#include "UI_interface.h"
+
+#include "object_intern.h"
+
+/* -------------- Get Active Constraint Data ---------------------- */
+
+/* if object in posemode, active bone constraints, else object constraints */
+ListBase *get_active_constraints (Object *ob)
+{
+ if (ob == NULL)
+ return NULL;
+
+ if (ob->mode & OB_MODE_POSE) {
+ bPoseChannel *pchan;
+
+ pchan = get_active_posechannel(ob);
+ if (pchan)
+ return &pchan->constraints;
+ }
+ else
+ return &ob->constraints;
+
+ return NULL;
+}
+
+/* single constraint */
+bConstraint *get_active_constraint (Object *ob)
+{
+ ListBase *lb= get_active_constraints(ob);
+
+ if (lb) {
+ bConstraint *con;
+
+ for (con= lb->first; con; con=con->next) {
+ if (con->flag & CONSTRAINT_ACTIVE)
+ return con;
+ }
+ }
+
+ return NULL;
+}
+/* -------------- Constraint Management (Add New, Remove, Rename) -------------------- */
+/* ------------- PyConstraints ------------------ */
+
+/* this callback sets the text-file to be used for selected menu item */
+void validate_pyconstraint_cb (void *arg1, void *arg2)
+{
+ bPythonConstraint *data = arg1;
+ Text *text= NULL;
+ int index = *((int *)arg2);
+ int i;
+
+ /* exception for no script */
+ if (index) {
+ /* innovative use of a for...loop to search */
+ for (text=G.main->text.first, i=1; text && index!=i; i++, text=text->id.next);
+ }
+ data->text = text;
+}
+
+#ifndef DISABLE_PYTHON
+/* this returns a string for the list of usable pyconstraint script names */
+char *buildmenu_pyconstraints (Text *con_text, int *pyconindex)
+{
+ DynStr *pupds= BLI_dynstr_new();
+ Text *text;
+ char *str;
+ char buf[64];
+ int i;
+
+ /* add title first */
+ sprintf(buf, "Scripts: %%t|[None]%%x0|");
+ BLI_dynstr_append(pupds, buf);
+
+ /* init active-index first */
+ if (con_text == NULL)
+ *pyconindex= 0;
+
+ /* loop through markers, adding them */
+ for (text=G.main->text.first, i=1; text; i++, text=text->id.next) {
+ /* this is important to ensure that right script is shown as active */
+ if (text == con_text) *pyconindex = i;
+
+ /* only include valid pyconstraint scripts */
+ if (BPY_is_pyconstraint(text)) {
+ BLI_dynstr_append(pupds, text->id.name+2);
+
+ sprintf(buf, "%%x%d", i);
+ BLI_dynstr_append(pupds, buf);
+
+ if (text->id.next)
+ BLI_dynstr_append(pupds, "|");
+ }
+ }
+
+ /* convert to normal MEM_malloc'd string */
+ str= BLI_dynstr_get_cstring(pupds);
+ BLI_dynstr_free(pupds);
+
+ return str;
+}
+#endif /* DISABLE_PYTHON */
+
+/* this callback gets called when the 'refresh' button of a pyconstraint gets pressed */
+void update_pyconstraint_cb (void *arg1, void *arg2)
+{
+ Object *owner= (Object *)arg1;
+ bConstraint *con= (bConstraint *)arg2;
+#ifndef DISABLE_PYTHON
+ if (owner && con)
+ BPY_pyconstraint_update(owner, con);
+#endif
+}
+
+/* Creates a new constraint, initialises its data, and returns it */
+bConstraint *add_new_constraint (short type)
+{
+ bConstraint *con;
+ bConstraintTypeInfo *cti;
+
+ con = MEM_callocN(sizeof(bConstraint), "Constraint");
+
+ /* Set up a generic constraint datablock */
+ con->type = type;
+ con->flag |= CONSTRAINT_EXPAND;
+ con->enforce = 1.0f;
+
+ /* Load the data for it */
+ cti = constraint_get_typeinfo(con);
+ if (cti) {
+ con->data = MEM_callocN(cti->size, cti->structName);
+
+ /* only constraints that change any settings need this */
+ if (cti->new_data)
+ cti->new_data(con->data);
+
+ /* set the name based on the type of constraint */
+ strcpy(con->name, cti->name);
+ }
+ else
+ strcpy(con->name, "Const");
+
+ return con;
+}
+
+/* Adds the given constraint to the Object-level set of constraints for the given Object */
+void add_constraint_to_object (bConstraint *con, Object *ob)
+{
+ ListBase *list;
+ list = &ob->constraints;
+
+ if (list) {
+ unique_constraint_name(con, list);
+ BLI_addtail(list, con);
+
+ if (proxylocked_constraints_owner(ob, NULL))
+ con->flag |= CONSTRAINT_PROXY_LOCAL;
+
+ con->flag |= CONSTRAINT_ACTIVE;
+ for (con= con->prev; con; con= con->prev)
+ con->flag &= ~CONSTRAINT_ACTIVE;
+ }
+}
+
+/* helper function for add_constriant - sets the last target for the active constraint */
+static void set_constraint_nth_target (bConstraint *con, Object *target, char subtarget[], int index)
+{
+ bConstraintTypeInfo *cti= constraint_get_typeinfo(con);
+ ListBase targets = {NULL, NULL};
+ bConstraintTarget *ct;
+ int num_targets, i;
+
+ if (cti && cti->get_constraint_targets) {
+ cti->get_constraint_targets(con, &targets);
+ num_targets= BLI_countlist(&targets);
+
+ if (index < 0) {
+ if (abs(index) < num_targets)
+ index= num_targets - abs(index);
+ else
+ index= num_targets - 1;
+ }
+ else if (index >= num_targets) {
+ index= num_targets - 1;
+ }
+
+ for (ct=targets.first, i=0; ct; ct= ct->next, i++) {
+ if (i == index) {
+ ct->tar= target;
+ strcpy(ct->subtarget, subtarget);
+ break;
+ }
+ }
+
+ if (cti->flush_constraint_targets)
+ cti->flush_constraint_targets(con, &targets, 0);
+ }
+}
+
+/* ------------- Constraint Sanity Testing ------------------- */
+
+/* checks validity of object pointers, and NULLs,
+ * if Bone doesnt exist it sets the CONSTRAINT_DISABLE flag
+ */
+static void test_constraints (Object *owner, const char substring[])
+{
+ bConstraint *curcon;
+ ListBase *conlist= NULL;
+ int type;
+
+ if (owner==NULL) return;
+
+ /* Check parents */
+ if (strlen(substring)) {
+ switch (owner->type) {
+ case OB_ARMATURE:
+ type = CONSTRAINT_OBTYPE_BONE;
+ break;
+ default:
+ type = CONSTRAINT_OBTYPE_OBJECT;
+ break;
+ }
+ }
+ else
+ type = CONSTRAINT_OBTYPE_OBJECT;
+
+ /* Get the constraint list for this object */
+ switch (type) {
+ case CONSTRAINT_OBTYPE_OBJECT:
+ conlist = &owner->constraints;
+ break;
+ case CONSTRAINT_OBTYPE_BONE:
+ {
+ Bone *bone;
+ bPoseChannel *chan;
+
+ bone = get_named_bone( ((bArmature *)owner->data), substring );
+ chan = get_pose_channel(owner->pose, substring);
+ if (bone && chan) {
+ conlist = &chan->constraints;
+ }
+ }
+ break;
+ }
+
+ /* Check all constraints - is constraint valid? */
+ if (conlist) {
+ for (curcon = conlist->first; curcon; curcon=curcon->next) {
+ bConstraintTypeInfo *cti= constraint_get_typeinfo(curcon);
+ ListBase targets = {NULL, NULL};
+ bConstraintTarget *ct;
+
+ /* clear disabled-flag first */
+ curcon->flag &= ~CONSTRAINT_DISABLE;
+
+ if (curcon->type == CONSTRAINT_TYPE_KINEMATIC) {
+ bKinematicConstraint *data = curcon->data;
+
+ /* bad: we need a separate set of checks here as poletarget is
+ * optional... otherwise poletarget must exist too or else
+ * the constraint is deemed invalid
+ */
+ if (exist_object(data->tar) == 0) {
+ data->tar = NULL;
+ curcon->flag |= CONSTRAINT_DISABLE;
+ }
+ else if (data->tar == owner) {
+ if (!get_named_bone(get_armature(owner), data->subtarget)) {
+ curcon->flag |= CONSTRAINT_DISABLE;
+ }
+ }
+
+ if (data->poletar) {
+ if (exist_object(data->poletar) == 0) {
+ data->poletar = NULL;
+ curcon->flag |= CONSTRAINT_DISABLE;
+ }
+ else if (data->poletar == owner) {
+ if (!get_named_bone(get_armature(owner), data->polesubtarget)) {
+ curcon->flag |= CONSTRAINT_DISABLE;
+ }
+ }
+ }
+
+ /* targets have already been checked for this */
+ continue;
+ }
+ else if (curcon->type == CONSTRAINT_TYPE_ACTION) {
+ bActionConstraint *data = curcon->data;
+
+ /* validate action */
+ if (data->act == NULL)
+ curcon->flag |= CONSTRAINT_DISABLE;
+ }
+ else if (curcon->type == CONSTRAINT_TYPE_FOLLOWPATH) {
+ bFollowPathConstraint *data = curcon->data;
+
+ /* don't allow track/up axes to be the same */
+ if (data->upflag==data->trackflag)
+ curcon->flag |= CONSTRAINT_DISABLE;
+ if (data->upflag+3==data->trackflag)
+ curcon->flag |= CONSTRAINT_DISABLE;
+ }
+ else if (curcon->type == CONSTRAINT_TYPE_TRACKTO) {
+ bTrackToConstraint *data = curcon->data;
+
+ /* don't allow track/up axes to be the same */
+ if (data->reserved2==data->reserved1)
+ curcon->flag |= CONSTRAINT_DISABLE;
+ if (data->reserved2+3==data->reserved1)
+ curcon->flag |= CONSTRAINT_DISABLE;
+ }
+ else if (curcon->type == CONSTRAINT_TYPE_LOCKTRACK) {
+ bLockTrackConstraint *data = curcon->data;
+
+ if (data->lockflag==data->trackflag)
+ curcon->flag |= CONSTRAINT_DISABLE;
+ if (data->lockflag+3==data->trackflag)
+ curcon->flag |= CONSTRAINT_DISABLE;
+ }
+
+ /* Check targets for constraints */
+ if (cti && cti->get_constraint_targets) {
+ cti->get_constraint_targets(curcon, &targets);
+
+ /* 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) */
+ if (exist_object(ct->tar) == 0) {
+ ct->tar = NULL;
+ curcon->flag |= CONSTRAINT_DISABLE;
+ }
+ else if (ct->tar == owner) {
+ if (!get_named_bone(get_armature(owner), ct->subtarget)) {
+ curcon->flag |= CONSTRAINT_DISABLE;
+ }
+ }
+
+ /* target checks for specific constraints */
+ if (ELEM(curcon->type, CONSTRAINT_TYPE_FOLLOWPATH, CONSTRAINT_TYPE_CLAMPTO)) {
+ if (ct->tar) {
+ if (ct->tar->type != OB_CURVE) {
+ ct->tar= NULL;
+ curcon->flag |= CONSTRAINT_DISABLE;
+ }
+ else {
+ Curve *cu= ct->tar->data;
+
+ /* auto-set 'Path' setting on curve so this works */
+ cu->flag |= CU_PATH;
+ }
+ }
+ }
+ }
+
+ /* free any temporary targets */
+ if (cti->flush_constraint_targets)
+ cti->flush_constraint_targets(curcon, &targets, 0);
+ }
+ }
+ }
+}
+
+static void test_bonelist_constraints (Object *owner, ListBase *list)
+{
+ Bone *bone;
+
+ for (bone = list->first; bone; bone = bone->next) {
+ test_constraints(owner, bone->name);
+ test_bonelist_constraints(owner, &bone->childbase);
+ }
+}
+
+void object_test_constraints (Object *owner)
+{
+ test_constraints(owner, "");
+
+ if (owner->type==OB_ARMATURE) {
+ bArmature *arm= get_armature(owner);
+
+ if (arm)
+ test_bonelist_constraints(owner, &arm->bonebase);
+ }
+}
+
+/* ********************** CONSTRAINT-SPECIFIC STUFF ********************* */
+
+/* ---------- Distance-Dependent Constraints ---------- */
+/* StretchTo, Limit Distance */
+
+static int stretchto_poll(bContext *C)
+{
+ PointerRNA ptr= CTX_data_pointer_get_type(C, "constraint", &RNA_StretchToConstraint);
+ return (ptr.id.data && ptr.data);
+}
+
+static int stretchto_reset_exec (bContext *C, wmOperator *op)
+{
+ PointerRNA ptr= CTX_data_pointer_get_type(C, "constraint", &RNA_StretchToConstraint);
+
+ /* just set original length to 0.0, which will cause a reset on next recalc */
+ RNA_float_set(&ptr, "original_length", 0.0f);
+
+ WM_event_add_notifier(C, NC_OBJECT|ND_CONSTRAINT, NULL);
+ return OPERATOR_FINISHED;
+}
+
+void CONSTRAINT_OT_stretchto_reset (wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name= "Reset Original Length";
+ ot->idname= "CONSTRAINT_OT_stretchto_reset";
+ ot->description= "Reset original length of bone for Stretch To Constraint.";
+
+ ot->exec= stretchto_reset_exec;
+ ot->poll= stretchto_poll;
+
+ /* flags */
+ ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+
+static int limitdistance_reset_exec (bContext *C, wmOperator *op)
+{
+ PointerRNA ptr= CTX_data_pointer_get_type(C, "constraint", &RNA_LimitDistanceConstraint);
+
+ /* just set distance to 0.0, which will cause a reset on next recalc */
+ RNA_float_set(&ptr, "distance", 0.0f);
+
+ WM_event_add_notifier(C, NC_OBJECT|ND_CONSTRAINT, NULL);
+ return OPERATOR_FINISHED;
+}
+
+static int limitdistance_poll(bContext *C)
+{
+ PointerRNA ptr= CTX_data_pointer_get_type(C, "constraint", &RNA_LimitDistanceConstraint);
+ return (ptr.id.data && ptr.data);
+}
+
+void CONSTRAINT_OT_limitdistance_reset (wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name= "Reset Distance";
+ ot->idname= "CONSTRAINT_OT_limitdistance_reset";
+ ot->description= "Reset limiting distance for Limit Distance Constraint.";
+
+ ot->exec= limitdistance_reset_exec;
+ ot->poll= limitdistance_poll;
+
+ /* flags */
+ ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+/* ------------- Child-Of Constraint ------------------ */
+
+static int childof_poll(bContext *C)
+{
+ PointerRNA ptr= CTX_data_pointer_get_type(C, "constraint", &RNA_ChildOfConstraint);
+ return (ptr.id.data && ptr.data);
+}
+
+/* ChildOf Constraint - set inverse callback */
+static int childof_set_inverse_exec (bContext *C, wmOperator *op)
+{
+ PointerRNA ptr= CTX_data_pointer_get_type(C, "constraint", &RNA_ChildOfConstraint);
+ Scene *scene= CTX_data_scene(C);
+ Object *ob= ptr.id.data;
+ bConstraint *con= ptr.data;
+ bChildOfConstraint *data= (bChildOfConstraint *)con->data;
+ bPoseChannel *pchan= NULL;
+
+ /* try to find a pose channel */
+ // TODO: get from context instead?
+ if (ob && ob->pose)
+ pchan= get_active_posechannel(ob);
+
+ /* calculate/set inverse matrix */
+ if (pchan) {
+ float pmat[4][4], cinf;
+ float imat[4][4], tmat[4][4];
+
+ /* make copy of pchan's original pose-mat (for use later) */
+ Mat4CpyMat4(pmat, pchan->pose_mat);
+
+ /* disable constraint for pose to be solved without it */
+ cinf= con->enforce;
+ con->enforce= 0.0f;
+
+ /* solve pose without constraint */
+ where_is_pose(scene, ob);
+
+ /* determine effect of constraint by removing the newly calculated
+ * pchan->pose_mat from the original pchan->pose_mat, thus determining
+ * the effect of the constraint
+ */
+ Mat4Invert(imat, pchan->pose_mat);
+ Mat4MulMat4(tmat, imat, pmat);
+ Mat4Invert(data->invmat, tmat);
+
+ /* recalculate pose with new inv-mat */
+ con->enforce= cinf;
+ where_is_pose(scene, ob);
+ }
+ else if (ob) {
+ Object workob;
+ /* use what_does_parent to find inverse - just like for normal parenting.
+ * NOTE: what_does_parent uses a static workob defined in object.c
+ */
+ what_does_parent(scene, ob, &workob);
+ Mat4Invert(data->invmat, workob.obmat);
+ }
+ else
+ Mat4One(data->invmat);
+
+ WM_event_add_notifier(C, NC_OBJECT|ND_CONSTRAINT, ob);
+
+ return OPERATOR_FINISHED;
+}
+
+void CONSTRAINT_OT_childof_set_inverse (wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name= "Set Inverse";
+ ot->idname= "CONSTRAINT_OT_childof_set_inverse";
+ ot->description= "Set inverse correction for ChildOf constraint.";
+
+ ot->exec= childof_set_inverse_exec;
+ ot->poll= childof_poll;
+
+ /* flags */
+ ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+/* ChildOf Constraint - clear inverse callback */
+static int childof_clear_inverse_exec (bContext *C, wmOperator *op)
+{
+ PointerRNA ptr= CTX_data_pointer_get_type(C, "constraint", &RNA_ChildOfConstraint);
+ Object *ob= ptr.id.data;
+ bConstraint *con= ptr.data;
+ bChildOfConstraint *data= (bChildOfConstraint *)con->data;
+
+ /* simply clear the matrix */
+ Mat4One(data->invmat);
+
+ WM_event_add_notifier(C, NC_OBJECT|ND_CONSTRAINT, ob);
+
+ return OPERATOR_FINISHED;
+}
+
+void CONSTRAINT_OT_childof_clear_inverse (wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name= "Clear Inverse";
+ ot->idname= "CONSTRAINT_OT_childof_clear_inverse";
+ ot->description= "Clear inverse correction for ChildOf constraint.";
+
+ ot->exec= childof_clear_inverse_exec;
+ ot->poll= childof_poll;
+
+ /* flags */
+ ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+/***************************** BUTTONS ****************************/
+
+/* Rename the given constraint, con already has the new name */
+void ED_object_constraint_rename(Object *ob, bConstraint *con, char *oldname)
+{
+ bConstraint *tcon;
+ ListBase *conlist= NULL;
+ int from_object= 0;
+
+ /* get context by searching for con (primitive...) */
+ for (tcon= ob->constraints.first; tcon; tcon= tcon->next) {
+ if (tcon==con)
+ break;
+ }
+
+ if (tcon) {
+ conlist= &ob->constraints;
+ from_object= 1;
+ }
+ else if (ob->pose) {
+ bPoseChannel *pchan;
+
+ for (pchan=ob->pose->chanbase.first; pchan; pchan=pchan->next) {
+ for (tcon= pchan->constraints.first; tcon; tcon= tcon->next) {
+ if (tcon==con)
+ break;
+ }
+ if (tcon)
+ break;
+ }
+
+ if (tcon) {
+ conlist= &pchan->constraints;
+ }
+ }
+
+ if (conlist==NULL) {
+ printf("rename constraint failed\n"); /* should not happen in UI */
+ return;
+ }
+
+ /* first make sure it's a unique name within context */
+ unique_constraint_name(con, conlist);
+}
+
+
+
+
+void ED_object_constraint_set_active(Object *ob, bConstraint *con)
+{
+ ListBase *lb;
+ bConstraint *origcon= con;
+
+ /* lets be nice and escape if its active already */
+ if(con && (con->flag & CONSTRAINT_ACTIVE))
+ return ;
+
+ lb= get_active_constraints(ob);
+ if(lb == NULL)
+ return;
+
+ for(con= lb->first; con; con= con->next) {
+ if(con==origcon) con->flag |= CONSTRAINT_ACTIVE;
+ else con->flag &= ~CONSTRAINT_ACTIVE;
+ }
+}
+
+static int constraint_poll(bContext *C)
+{
+ PointerRNA ptr= CTX_data_pointer_get_type(C, "constraint", &RNA_Constraint);
+ return (ptr.id.data && ptr.data);
+}
+
+static int constraint_delete_exec (bContext *C, wmOperator *op)
+{
+ PointerRNA ptr= CTX_data_pointer_get_type(C, "constraint", &RNA_Constraint);
+ Object *ob= ptr.id.data;
+ bConstraint *con= ptr.data;
+ ListBase *lb;
+
+ /* remove constraint itself */
+ lb= get_active_constraints(ob);
+ free_constraint_data(con);
+ BLI_freelinkN(lb, con);
+
+ ED_object_constraint_set_active(ob, NULL);
+ WM_event_add_notifier(C, NC_OBJECT|ND_CONSTRAINT, ob);
+
+ return OPERATOR_FINISHED;
+}
+
+void CONSTRAINT_OT_delete (wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name= "Delete Constraint";
+ ot->idname= "CONSTRAINT_OT_delete";
+ ot->description= "Remove constraitn from constraint stack.";
+
+ /* callbacks */
+ ot->exec= constraint_delete_exec;
+ ot->poll= constraint_poll;
+
+ /* flags */
+ ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+static int constraint_move_down_exec (bContext *C, wmOperator *op)
+{
+ PointerRNA ptr= CTX_data_pointer_get_type(C, "constraint", &RNA_Constraint);
+ Object *ob= ptr.id.data;
+ bConstraint *con= ptr.data;
+
+ if (con->next) {
+ ListBase *conlist= get_active_constraints(ob);
+ bConstraint *nextCon= con->next;
+
+ /* insert the nominated constraint after the one that used to be after it */
+ BLI_remlink(conlist, con);
+ BLI_insertlinkafter(conlist, nextCon, con);
+
+ WM_event_add_notifier(C, NC_OBJECT|ND_CONSTRAINT, ob);
+
+ return OPERATOR_FINISHED;
+ }
+
+ return OPERATOR_CANCELLED;
+}
+
+void CONSTRAINT_OT_move_down (wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name= "Move Constraint Down";
+ ot->idname= "CONSTRAINT_OT_move_down";
+ ot->description= "Move constraint down constraint stack.";
+
+ /* callbacks */
+ ot->exec= constraint_move_down_exec;
+ ot->poll= constraint_poll;
+
+ /* flags */
+ ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+
+static int constraint_move_up_exec (bContext *C, wmOperator *op)
+{
+ PointerRNA ptr= CTX_data_pointer_get_type(C, "constraint", &RNA_Constraint);
+ Object *ob= ptr.id.data;
+ bConstraint *con= ptr.data;
+
+ if (con->prev) {
+ ListBase *conlist= get_active_constraints(ob);
+ bConstraint *prevCon= con->prev;
+
+ /* insert the nominated constraint before the one that used to be before it */
+ BLI_remlink(conlist, con);
+ BLI_insertlinkbefore(conlist, prevCon, con);
+
+ WM_event_add_notifier(C, NC_OBJECT|ND_CONSTRAINT, ob);
+
+ return OPERATOR_FINISHED;
+ }
+
+ return OPERATOR_CANCELLED;
+}
+
+void CONSTRAINT_OT_move_up (wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name= "Move Constraint Up";
+ ot->idname= "CONSTRAINT_OT_move_up";
+ ot->description= "Move constraint up constraint stack.";
+
+ /* callbacks */
+ ot->exec= constraint_move_up_exec;
+ ot->poll= constraint_poll;
+
+ /* flags */
+ ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+/***************************** OPERATORS ****************************/
+
+/************************ remove constraint operators *********************/
+
+static int pose_constraints_clear_exec(bContext *C, wmOperator *op)
+{
+ Object *ob= CTX_data_active_object(C);
+
+ /* free constraints for all selected bones */
+ CTX_DATA_BEGIN(C, bPoseChannel*, pchan, selected_pchans)
+ {
+ free_constraints(&pchan->constraints);
+ }
+ CTX_DATA_END;
+
+ /* do updates */
+ DAG_id_flush_update(&ob->id, OB_RECALC_OB);
+ WM_event_add_notifier(C, NC_OBJECT|ND_POSE|ND_CONSTRAINT|NA_REMOVED, ob);
+
+ return OPERATOR_FINISHED;
+}
+
+void POSE_OT_constraints_clear(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Clear Constraints";
+ ot->idname= "POSE_OT_constraints_clear";
+ ot->description= "Clear all the constraints for the selected bones.";
+
+ /* callbacks */
+ ot->exec= pose_constraints_clear_exec;
+ ot->poll= ED_operator_posemode; // XXX - do we want to ensure there are selected bones too?
+}
+
+
+static int object_constraints_clear_exec(bContext *C, wmOperator *op)
+{
+ Object *ob= CTX_data_active_object(C);
+
+ /* do freeing */
+ // TODO: we should free constraints for all selected objects instead (to be more consistent with bones)
+ free_constraints(&ob->constraints);
+
+ /* do updates */
+ DAG_id_flush_update(&ob->id, OB_RECALC_OB);
+ WM_event_add_notifier(C, NC_OBJECT|ND_CONSTRAINT|NA_REMOVED, ob);
+
+ return OPERATOR_FINISHED;
+}
+
+void OBJECT_OT_constraints_clear(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Clear Constraints";
+ ot->idname= "OBJECT_OT_constraints_clear";
+ ot->description= "Clear all the constraints for the active Object only.";
+
+ /* callbacks */
+ ot->exec= object_constraints_clear_exec;
+ ot->poll= ED_operator_object_active;
+}
+
+/************************ add constraint operators *********************/
+
+/* get the Object and/or PoseChannel to use as target */
+static short get_new_constraint_target(bContext *C, int con_type, Object **tar_ob, bPoseChannel **tar_pchan, short add)
+{
+ Object *obact= CTX_data_active_object(C);
+ bPoseChannel *pchanact= get_active_posechannel(obact);
+ short only_curve= 0, only_mesh= 0, only_ob= 0;
+ short found= 0;
+
+ /* clear tar_ob and tar_pchan fields before use
+ * - assume for now that both always exist...
+ */
+ *tar_ob= NULL;
+ *tar_pchan= NULL;
+
+ /* check if constraint type doesn't requires a target
+ * - if so, no need to get any targets
+ */
+ switch (con_type) {
+ /* no-target constraints --------------------------- */
+ /* null constraint - shouldn't even be added! */
+ case CONSTRAINT_TYPE_NULL:
+ /* limit constraints - no targets needed */
+ case CONSTRAINT_TYPE_LOCLIMIT:
+ case CONSTRAINT_TYPE_ROTLIMIT:
+ case CONSTRAINT_TYPE_SIZELIMIT:
+ return 0;
+
+ /* restricted target-type constraints -------------- */
+ /* NOTE: for these, we cannot try to add a target object if no valid ones are found, since that doesn't work */
+ /* curve-based constraints - set the only_curve and only_ob flags */
+ case CONSTRAINT_TYPE_TRACKTO:
+ case CONSTRAINT_TYPE_CLAMPTO:
+ case CONSTRAINT_TYPE_FOLLOWPATH:
+ only_curve= 1;
+ only_ob= 1;
+ add= 0;
+ break;
+
+ /* mesh only? */
+ case CONSTRAINT_TYPE_SHRINKWRAP:
+ only_mesh= 1;
+ only_ob= 1;
+ add= 0;
+ break;
+
+ /* object only - add here is ok? */
+ case CONSTRAINT_TYPE_RIGIDBODYJOINT:
+ only_ob= 1;
+ break;
+ }
+
+ /* if the active Object is Armature, and we can search for bones, do so... */
+ if ((obact->type == OB_ARMATURE) && (only_ob == 0)) {
+ /* search in list of selected Pose-Channels for target */
+ CTX_DATA_BEGIN(C, bPoseChannel*, pchan, selected_pchans)
+ {
+ /* just use the first one that we encounter, as long as it is not the active one */
+ if (pchan != pchanact) {
+ *tar_ob= obact;
+ *tar_pchan= pchan;
+ found= 1;
+
+ break;
+ }
+ }
+ CTX_DATA_END;
+ }
+
+ /* if not yet found, try selected Objects... */
+ if (found == 0) {
+ /* search in selected objects context */
+ CTX_DATA_BEGIN(C, Object*, ob, selected_objects)
+ {
+ /* just use the first object we encounter (that isn't the active object)
+ * and which fulfills the criteria for the object-target that we've got
+ */
+ if ( (ob != obact) &&
+ ((!only_curve) || (ob->type == OB_CURVE)) &&
+ ((!only_mesh) || (ob->type == OB_MESH)) )
+ {
+ /* set target */
+ *tar_ob= ob;
+ found= 1;
+
+ /* perform some special operations on the target */
+ if (only_curve) {
+ /* Curve-Path option must be enabled for follow-path constraints to be able to work */
+ Curve *cu= (Curve *)ob->data;
+ cu->flag |= CU_PATH;
+ }
+
+ break;
+ }
+ }
+ CTX_DATA_END;
+ }
+
+ /* if still not found, add a new empty to act as a target (if allowed) */
+ if ((found == 0) && (add)) {
+ Scene *scene= CTX_data_scene(C);
+ Base *base= BASACT, *newbase=NULL;
+ Object *obt;
+
+ /* add new target object */
+ obt= add_object(scene, OB_EMPTY);
+
+ /* set layers OK */
+ newbase= BASACT;
+ newbase->lay= base->lay;
+ obt->lay= newbase->lay;
+
+ /* transform cent to global coords for loc */
+ if (pchanact) {
+ /* since by default, IK targets the tip of the last bone, use the tip of the active PoseChannel
+ * if adding a target for an IK Constraint
+ */
+ if (con_type == CONSTRAINT_TYPE_KINEMATIC)
+ VecMat4MulVecfl(obt->loc, obact->obmat, pchanact->pose_tail);
+ else
+ VecMat4MulVecfl(obt->loc, obact->obmat, pchanact->pose_head);
+ }
+ else
+ VECCOPY(obt->loc, obact->obmat[3]);
+
+ /* restore, add_object sets active */
+ BASACT= base;
+ base->flag |= SELECT;
+
+ /* make our new target the new object */
+ *tar_ob= obt;
+ found= 1;
+ }
+
+ /* return whether there's any target */
+ return found;
+}
+
+/* used by add constraint operators to add the constraint required */
+static int constraint_add_exec(bContext *C, wmOperator *op, Object *ob, ListBase *list, int type, short setTarget)
+{
+ Scene *scene= CTX_data_scene(C);
+ bPoseChannel *pchan= get_active_posechannel(ob);
+ bConstraint *con;
+
+ /* check if constraint to be added is valid for the given constraints stack */
+ if (type == CONSTRAINT_TYPE_NULL) {
+ return OPERATOR_CANCELLED;
+ }
+ if ( (type == CONSTRAINT_TYPE_RIGIDBODYJOINT) && (list != &ob->constraints) ) {
+ BKE_report(op->reports, RPT_ERROR, "Rigid Body Joint Constraint can only be added to Objects.");
+ return OPERATOR_CANCELLED;
+ }
+ if ( (type == CONSTRAINT_TYPE_KINEMATIC) && ((!pchan) || (list != &pchan->constraints)) ) {
+ BKE_report(op->reports, RPT_ERROR, "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 */
+ con = add_new_constraint(type);
+
+ if (list) {
+ bConstraint *coniter;
+
+ /* add new constraint to end of list of constraints before ensuring that it has a unique name
+ * (otherwise unique-naming code will fail, since it assumes element exists in list)
+ */
+ BLI_addtail(list, con);
+ unique_constraint_name(con, list);
+
+ /* if the target list is a list on some PoseChannel belonging to a proxy-protected
+ * Armature layer, we must tag newly added constraints with a flag which allows them
+ * to persist after proxy syncing has been done
+ */
+ if (proxylocked_constraints_owner(ob, pchan))
+ con->flag |= CONSTRAINT_PROXY_LOCAL;
+
+ /* make this constraint the active one
+ * - since constraint was added at end of stack, we can just go
+ * through deactivating all previous ones
+ */
+ con->flag |= CONSTRAINT_ACTIVE;
+ for (coniter= con->prev; coniter; coniter= coniter->prev)
+ coniter->flag &= ~CONSTRAINT_ACTIVE;
+ }
+
+ /* get the first selected object/bone, and make that the target
+ * - apart from the buttons-window add buttons, we shouldn't add in this way
+ */
+ if (setTarget) {
+ Object *tar_ob= NULL;
+ bPoseChannel *tar_pchan= NULL;
+
+ /* get the target objects, adding them as need be */
+ if (get_new_constraint_target(C, type, &tar_ob, &tar_pchan, 1)) {
+ /* method of setting target depends on the type of target we've got
+ * - by default, just set the first target (distinction here is only for multiple-targetted constraints)
+ */
+ if (tar_pchan)
+ set_constraint_nth_target(con, tar_ob, tar_pchan->name, 0);
+ else
+ set_constraint_nth_target(con, tar_ob, "", 0);
+ }
+ }
+
+ /* do type-specific tweaking to the constraint settings */
+ switch (type) {
+ case CONSTRAINT_TYPE_CHILDOF:
+ {
+ /* if this constraint is being added to a posechannel, make sure
+ * the constraint gets evaluated in pose-space */
+ if (ob->mode & OB_MODE_POSE) {
+ con->ownspace = CONSTRAINT_SPACE_POSE;
+ con->flag |= CONSTRAINT_SPACEONCE;
+ }
+ }
+ break;
+
+ case CONSTRAINT_TYPE_PYTHON: // FIXME: this code is not really valid anymore
+ {
+ char *menustr;
+ int scriptint= 0;
+#ifndef DISABLE_PYTHON
+ /* popup a list of usable scripts */
+ menustr = buildmenu_pyconstraints(NULL, &scriptint);
+ // XXX scriptint = pupmenu(menustr);
+ MEM_freeN(menustr);
+
+ /* only add constraint if a script was chosen */
+ if (scriptint) {
+ /* add constraint */
+ validate_pyconstraint_cb(con->data, &scriptint);
+
+ /* make sure target allowance is set correctly */
+ BPY_pyconstraint_update(ob, con);
+ }
+#endif
+ }
+ default:
+ break;
+ }
+
+ /* make sure all settings are valid - similar to above checks, but sometimes can be wrong */
+ object_test_constraints(ob);
+
+ if (ob->pose)
+ update_pose_constraint_flags(ob->pose);
+
+
+ /* force depsgraph to get recalculated since new relationships added */
+ DAG_scene_sort(scene); /* sort order of objects */
+
+ if ((ob->type==OB_ARMATURE) && (pchan)) {
+ ob->pose->flag |= POSE_RECALC; /* sort pose channels */
+ DAG_id_flush_update(&ob->id, OB_RECALC_DATA|OB_RECALC_OB);
+ }
+ else
+ DAG_id_flush_update(&ob->id, OB_RECALC_DATA);
+
+ /* notifiers for updates */
+ WM_event_add_notifier(C, NC_OBJECT|ND_CONSTRAINT|NA_ADDED, ob);
+
+ return OPERATOR_FINISHED;
+}
+
+/* ------------------ */
+
+/* dummy operator callback */
+static int object_constraint_add_exec(bContext *C, wmOperator *op)
+{
+ ScrArea *sa= CTX_wm_area(C);
+ Object *ob;
+ int type= RNA_enum_get(op->ptr, "type");
+ short with_targets= 0;
+
+ /* get active object from context */
+ if (sa->spacetype == SPACE_BUTS)
+ ob= CTX_data_pointer_get_type(C, "object", &RNA_Object).data;
+ else
+ ob= CTX_data_active_object(C);
+
+ if (!ob) {
+ BKE_report(op->reports, RPT_ERROR, "No active object to add constraint to.");
+ return OPERATOR_CANCELLED;
+ }
+
+ /* hack: set constraint targets from selected objects in context is allowed when
+ * operator name included 'with_targets', since the menu doesn't allow multiple properties
+ */
+ if (strstr(op->idname, "with_targets"))
+ with_targets= 1;
+
+ return constraint_add_exec(C, op, ob, &ob->constraints, type, with_targets);
+}
+
+/* dummy operator callback */
+static int pose_constraint_add_exec(bContext *C, wmOperator *op)
+{
+ ScrArea *sa= CTX_wm_area(C);
+ Object *ob;
+ int type= RNA_enum_get(op->ptr, "type");
+ short with_targets= 0;
+
+ /* get active object from context */
+ if (sa->spacetype == SPACE_BUTS)
+ ob= CTX_data_pointer_get_type(C, "object", &RNA_Object).data;
+ else
+ ob= CTX_data_active_object(C);
+
+ if (!ob) {
+ BKE_report(op->reports, RPT_ERROR, "No active object to add constraint to.");
+ return OPERATOR_CANCELLED;
+ }
+
+ /* hack: set constraint targets from selected objects in context is allowed when
+ * operator name included 'with_targets', since the menu doesn't allow multiple properties
+ */
+ if (strstr(op->idname, "with_targets"))
+ with_targets= 1;
+
+ return constraint_add_exec(C, op, ob, get_active_constraints(ob), type, with_targets);
+}
+
+/* ------------------ */
+
+void OBJECT_OT_constraint_add(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name= "Add Constraint";
+ ot->description = "Add a constraint to the active object.";
+ ot->idname= "OBJECT_OT_constraint_add";
+
+ /* api callbacks */
+ ot->invoke= WM_menu_invoke;
+ ot->exec= object_constraint_add_exec;
+ ot->poll= ED_operator_object_active;
+
+ /* flags */
+ ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
+
+ /* properties */
+ RNA_def_enum(ot->srna, "type", constraint_type_items, 0, "Type", "");
+}
+
+void OBJECT_OT_constraint_add_with_targets(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name= "Add Constraint (with Targets)";
+ ot->description = "Add a constraint to the active object, with target (where applicable) set to the selected Objects/Bones.";
+ ot->idname= "OBJECT_OT_constraint_add_with_targets";
+
+ /* api callbacks */
+ ot->invoke= WM_menu_invoke;
+ ot->exec= object_constraint_add_exec;
+ ot->poll= ED_operator_object_active;
+
+ /* flags */
+ ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
+
+ /* properties */
+ RNA_def_enum(ot->srna, "type", constraint_type_items, 0, "Type", "");
+}
+
+void POSE_OT_constraint_add(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name= "Add Constraint";
+ ot->description = "Add a constraint to the active bone.";
+ ot->idname= "POSE_OT_constraint_add";
+
+ /* api callbacks */
+ ot->invoke= WM_menu_invoke;
+ ot->exec= pose_constraint_add_exec;
+ ot->poll= ED_operator_posemode;
+
+ /* flags */
+ ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
+
+ /* properties */
+ RNA_def_enum(ot->srna, "type", constraint_type_items, 0, "Type", "");
+}
+
+void POSE_OT_constraint_add_with_targets(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name= "Add Constraint (with Targets)";
+ ot->description = "Add a constraint to the active bone, with target (where applicable) set to the selected Objects/Bones.";
+ ot->idname= "POSE_OT_constraint_add_with_targets";
+
+ /* api callbacks */
+ ot->invoke= WM_menu_invoke;
+ ot->exec= pose_constraint_add_exec;
+ ot->poll= ED_operator_posemode;
+
+ /* flags */
+ ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
+
+ /* properties */
+ RNA_def_enum(ot->srna, "type", constraint_type_items, 0, "Type", "");
+}
+
+/************************ IK Constraint operators *********************/
+/* NOTE: only for Pose-Channels */
+// TODO: should these be here, or back in editors/armature/poseobject.c again?
+
+/* present menu with options + validation for targets to use */
+static int pose_ik_add_invoke(bContext *C, wmOperator *op, wmEvent *evt)
+{
+ Object *ob= CTX_data_active_object(C);
+ bPoseChannel *pchan= get_active_posechannel(ob);
+ bConstraint *con= NULL;
+
+ uiPopupMenu *pup;
+ uiLayout *layout;
+ Object *tar_ob= NULL;
+ bPoseChannel *tar_pchan= NULL;
+
+ /* must have active bone */
+ if (ELEM(NULL, ob, pchan)) {
+ BKE_report(op->reports, RPT_ERROR, "Must have active bone to add IK Constraint to.");
+ return OPERATOR_CANCELLED;
+ }
+
+ /* bone must not have any constraints already */
+ for (con= pchan->constraints.first; con; con= con->next) {
+ if (con->type==CONSTRAINT_TYPE_KINEMATIC) break;
+ }
+ if (con) {
+ BKE_report(op->reports, RPT_ERROR, "Bone already has IK Constraint.");
+ return OPERATOR_CANCELLED;
+ }
+
+ /* prepare popup menu to choose targetting options */
+ pup= uiPupMenuBegin(C, "Add IK", 0);
+ layout= uiPupMenuLayout(pup);
+
+ /* the type of targets we'll set determines the menu entries to show... */
+ if (get_new_constraint_target(C, CONSTRAINT_TYPE_KINEMATIC, &tar_ob, &tar_pchan, 0)) {
+ /* bone target, or object target?
+ * - the only thing that matters is that we want a target...
+ */
+ if (tar_pchan)
+ uiItemBooleanO(layout, "To Active Bone", 0, "POSE_OT_ik_add", "with_targets", 1);
+ else
+ uiItemBooleanO(layout, "To Active Object", 0, "POSE_OT_ik_add", "with_targets", 1);
+ }
+ else {
+ /* we have a choice of adding to a new empty, or not setting any target (targetless IK) */
+ uiItemBooleanO(layout, "To New Empty Object", 0, "POSE_OT_ik_add", "with_targets", 1);
+ uiItemBooleanO(layout, "Without Targets", 0, "POSE_OT_ik_add", "with_targets", 0);
+ }
+
+ /* finish building the menu, and process it (should result in calling self again) */
+ uiPupMenuEnd(C, pup);
+
+ return OPERATOR_CANCELLED;
+}
+
+/* call constraint_add_exec() to add the IK constraint */
+static int pose_ik_add_exec(bContext *C, wmOperator *op)
+{
+ Object *ob= CTX_data_active_object(C);
+ int with_targets= RNA_boolean_get(op->ptr, "with_targets");
+
+ /* add the constraint - all necessary checks should have been done by the invoke() callback already... */
+ return constraint_add_exec(C, op, ob, get_active_constraints(ob), CONSTRAINT_TYPE_KINEMATIC, with_targets);
+}
+
+void POSE_OT_ik_add(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name= "Add IK to Bone";
+ ot->description= "Add IK Constraint to the active Bone.";
+ ot->idname= "POSE_OT_ik_add";
+
+ /* api callbacks */
+ ot->invoke= pose_ik_add_invoke;
+ ot->exec= pose_ik_add_exec;
+ ot->poll= ED_operator_posemode;
+
+ /* flags */
+ ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
+
+ /* properties */
+ RNA_def_boolean(ot->srna, "with_targets", 1, "With Targets", "Assign IK Constraint with targets derived from the select bones/objects");
+}
+
+/* ------------------ */
+
+/* remove IK constraints from selected bones */
+static int pose_ik_clear_exec(bContext *C, wmOperator *op)
+{
+ Object *ob= CTX_data_active_object(C);
+
+ /* only remove IK Constraints */
+ CTX_DATA_BEGIN(C, bPoseChannel*, pchan, selected_pchans)
+ {
+ bConstraint *con, *next;
+
+ // TODO: should we be checking if these contraints were local before we try and remove them?
+ for (con= pchan->constraints.first; con; con= next) {
+ next= con->next;
+ if (con->type==CONSTRAINT_TYPE_KINEMATIC) {
+ free_constraint_data(con);
+ BLI_freelinkN(&pchan->constraints, con);
+ }
+ }
+ pchan->constflag &= ~(PCHAN_HAS_IK|PCHAN_HAS_TARGET);
+ }
+ CTX_DATA_END;
+
+ /* */
+ DAG_id_flush_update(&ob->id, OB_RECALC_DATA);
+
+ /* note, notifier might evolve */
+ WM_event_add_notifier(C, NC_OBJECT|ND_CONSTRAINT|NA_REMOVED, ob);
+
+ return OPERATOR_FINISHED;
+}
+
+void POSE_OT_ik_clear(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name= "Remove IK";
+ ot->description= "Remove all IK Constraints from selected bones.";
+ ot->idname= "POSE_OT_ik_clear";
+
+ /* api callbacks */
+ ot->exec= pose_ik_clear_exec;
+ ot->poll= ED_operator_posemode;
+
+ /* flags */
+ ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+