DNA_constraint_types.h, readfile.c, writefile.c, editconstraint.c,
constraint.c, buttons_obj.c, butspace.h
First we have to create the constraints data structure. For this we have to edit
the DNA_constraint_types.h file. Locate the section containing Struct
Definitions for the constraints. They will be near the top of the file. At the
bottom of this list create a new struct for your constraint. Some of the
Required/standard data members are:
<ccode>Object *tar; /*for the constraints target object if there is one*/</ccode>
<ccode>char subtarget[32]; /*for the subtarget of course if there is one*/</ccode>
Besides creating the Struct we also have to #define our bconstraint.type constant. These are listed under the /* constraint.type */ comment. Simply add yours to the bottom of the list and take the next available integer.
We want to be able to write our constraint to a file too. After all if we can't do
that then we the constraint won't be very useful will it?
We need to edit writefile.c there is only one function to worry about here, write_constraints. Look again for the switch block and add a case for your
constraint. in our case we need to add the following line:
<ccode>writestruct(wd, DATA, "bMyConstraintConstraint", 1, con->data);</ccode>
That line must be there for every constraint regardless of type. This block can never be skipped. The portion in quotes is the name of your constraints struct.
We want to be able to read our constraint from a file once it has been saved. To do this we have to edit two functions in readfile.c
The first is expand_constraints(). Look for the switch block in this function
and and add a case for your constraint. You will need a minimum of two lines
in your constraint's case.
<ccode>bMyConstraintConstraint *data = (bMyConstraintConstraint*)curcon->data; </ccode>
Retrieves your constraints data from the struct we created earlier.
<ccode>expand_doit(fd, mainvar, data->tar);</ccode>
Is only used for the constraints DNA data members. you must add expand_doit() lines for every pointer to a DNA member that is in your struct.
This function can be skipped if your constraint has no pointers to other DNA structs in its data members for instance: zero target constraints.
Next we edit lib_link_constraints in this function we do much the
same thing as before. If we have no data members which are pointers to
other DNA then we can again skip this function. For those Constraints
which do ,however, we must add a case to the switch block in this function
for our constraint like so:
<ccode>case CONSTRAINT_TYPE_MYCONSTRAINT:
{
bMyConstraintConstraint *data;
data= ((bMyConstraintConstraint*)con->data);
data->tar = newlibadr(fd, id->lib, data->tar);
};
break;</ccode>
as before you add a newlibadr call for every pointer to a DNA member.
We have to be able to handle our constraints presence too. We have to edit
two files to handle this.
<tab>
The new_constraint_data Function initializes our constraint with any
default values and ceates the constraint. Locate the Switch block and add a
case for our constraint. In our constraints case we need to first declare our
data variable like so:
<ccode>bMyConstraint *data; </ccode>
Then we need to allocate memory for our constraint.
<ccode>data = MEM_callocN(sizeof(bMyConstraint ), "myConstraint"); </ccode>
and finally we add the default values for our struct.
<ccode>data->myvalue = 1; </ccode>
The get_constraint_target function can be skipped if our constraint has no
target. If it does have a target then we need to add a case for our constraint
in the switch block like so:
<ccode>case CONSTRAINT_TYPE_MYCONSTRAINT:
{
bMyConstraintConstraint *data;
data = (bMyConstraintConstraint*)con->data;
if (data->tar){
constraint_target_to_mat4(data->tar, data->subtarget,
mat, size, ctime);
valid=1;
}
else
Mat4One (mat);
}
break;</ccode>
just change the constraint and struct names to yours like before.
Next we need to find the relink_constraints function. This function
handles linking constraints to their targets. If your constraint has a a target
then modify the switch block to include a case for your constraint like so:
<ccode>case CONSTRAINT_TYPE_MYCONSTRAINT:
{
bMyConstraintConstraint *data;
data = con->data;
ID_NEW(data->tar);
}
break;
</ccode>
Next we need to find the copy_constraints function. This function
behaves exactly like it sounds. It copies constraints. Every constraint
needs to be in this function. In the switch block add a case for your
constraint like so:
<ccode>CONSTRAINT_TYPE_MYCONSTRAINT:
{
bMyConstraintConstraint *data;
con->data = MEM_dupallocN (con->data);
data = (bMyConstraintConstraint*) con->data;
}
break;</ccode>
As before just modify the struct name and constants to match your struct.
Now we finally get to the meat of our constraint. The function:
evaluate_constraint is as you might expect where the constraint actually
does whatever it is that it does. Add a case for your Constraint in the
switch block and put your nifty Constraint handling code there.
I highly recommend creating a function which gets called for the
constraint. Trying to write it all in that case statement would make for
messy code.</tab>
<tab>
editconstraint.c This file has one function to edit: detect_constraint_loop
This function detects looping constraints. Such a condition occurs when
The target (data->tar) has a constraint which targets this constraints object.
This is a bad thing so we want to catch them in the interest of good code.
However this can't occur if your constraint has zero targets. You can omit this
step if your constraint has no targets. If it does have a target though then
you will need a case for your constraint here:
<ccode>case CONSTRAINT_TYPE_MYCONSTRAINT:
{
bMyConstraintConstraint *data = curcon->data;
if (!exist_object(data->tar)) data->tar = NULL;
if (add_constraint_element (data->tar, data->subtarget, owner, substring))
{
curcon->flag |= CONSTRAINT_DISABLE;
result = 1;
break;
}
if (detect_constraint_loop (data->tar, data->subtarget, disable))
{
curcon->flag |= CONSTRAINT_DISABLE;
result = 1;
break;
}
}
break;</ccode>
as before just change the code with your constraint and struct names. </tab>
buttons_object.c is the file which will contain our GUI code for the constraint. There are several function we have to modify here.
get_constraint_typestring sets our display string for the constraint. Just copy a case and modify the text to reflect your case.
get_constraint_col sets the color of our constraints block in the GUI. Again
copy a case and modify it to reflect your case.
draw_constraint is where all the action is for our GUI Drawing.
We need to locate the switch block containing all the constraint type cases,
and add a case for our constraint. Here are the minimum contents of our
constraints case:
<ccode>case CONSTRAINT_TYPE_MYCONSTRAINT:
{
bMyConstraintConstraint *data = con->data;
bArmature *arm; /* needed if our constraint has a target. */
height = 46;
BIF_set_color(curCol, COLORSHADE_GREY);
glRects(*xco, *yco-height, *xco+width, *yco);
uiEmboss((float)*xco, (float)*yco-height,
(float)*xco+width, (float)*yco, 1);
/* needed if our constraint has a target. */
uiDefIDPoinBut(block, test_obpoin_but,
B_CONSTRAINT_CHANGETARGET, "OB:",
*xco+((width/2)-48), *yco-20, 96, 18,
&data->tar, "Target Object");
/* needed if our constraint has a target. */
arm = get_armature(data->tar);
if (arm)
{ /* needed if our constraint has a target. */
but=uiDefBut(block, TEX, B_CONSTRAINT_CHANGETARGET,
"BO:", *xco+((width/2)-48), *yco-40,96,18,
&data->subtarget, 0, 24, 0, 0, "Bone");
}
else
{
strcpy (data->subtarget, "");
}
break;</ccode>
thats all there is to it. We now have a minimal GUI for our Constraint. Of
course you will want to add more to it depending on what your Constraints
modifiable parameters are but you should be able to take it from here.
There is just one final detail to take care of. We need to be able to add our
constraint to an object before we can use our nifty new GUI. We have to add
our constraint to the add_constraintmenu function. We do this by adding the
following line in the appropriate position.
<ccode>uiDefBut(block, BUTM, B_CONSTRAINT_ADD_MyConstraint,
"Limit Rotation", 0, yco-=20, 160, 19, NULL, 0.0, 0.0, 1, 0,
"tooltip text here"); </ccode>
We also have to add the B_CONSTRAINT_ADD_MyConstraint to the
appropriate Enum in butspace.h
Then we have to edit do_constraintbuts to add a case in the switch
block for our new option like so:
<ccode>case B_CONSTRAINT_ADD_MyConstraint:
{
bConstraint *con;
con = add_new_constraint(CONSTRAINT_TYPE_MYCONSTRAINT);
add_constraint_to_client(con);
test_scene_constraints();
allqueue (REDRAWVIEW3D, 0);
allqueue (REDRAWBUTSOBJECT, 0);
}
break;</ccode>