提供: wiki
移動先: 案内検索

Adding an iTaSC Constraint to Blender

This page describes the step by step instructions to add a new constraint to iTaSC. It assumes that you are familiar with the theory behind iTaSC (introduction to iTaSC), with KDL (orocos project), and that you have already used iTaSC in Blender (iTaSC Users' guide).

Table of content

1. Implement the constraint in iTaSC
1.1. Usage of ConstraintValues structure in iTaSC
1.2. The cache system
1.3. What data member must be defined in the constraint class?
1.4. The Timestamp structure
1.5. Member functions to implement
1.5.1 Mandatory functions
1.5.2 Optional functions
2. Add the constraint to Blender UI
2.1 Add constraint parameters to DNA
2.2 Update initialization and conversion code
2.3 Update RNA
2.4 Update UI script
3. Update the plugin
3.1 Add the constraint
3.2 Update the target callback
4. Update the BGE
5. Conclusion

1. Implement the constraint in iTaSC

The first step is to create a new class derived from iTaSC::ConstraintSet and implement all the required functions. Two constraints, Distance and CopyPose, are already fully implemented in iTaSC. Use them as examples when implementing the new constraint. The corresponding files are:


CopyPose is an example of a contraint that the does not implement an armature explicitely: the constraint is only defined at velocity level. Distance is an example of a constraint with explicit armature.

1.1. Usage of ConstraintValues structure in iTaSC

This structure is used to exchange data between the constraint and the application. When passed to the application (via the callback function or getControlParameters()), it contains the current state of the constraint: output values and velocities, desired output values and velocities, feedback coefficient and weight. When passed to the constraint (on return of the callback or via setControlParameters()), it contains the modified values that the application would like to set as indicated by the action and id fields.

ConstraintValues is designed to allow quick access to any particular value while being sufficiently general to adapt to any constraint. The data exchanged is an array of ConstraintValues, each pointing to an array of ConstraintSingleValue. Each ConstraintValues groups several output values (stored in ConstraintSingleValue) that use the same weight and feedback coefficients. The application can change selectively the weight and feedback by setting the new value in the corresponding fields and setting action to a combination of ACT_FEEDBACK and ACT_ALPHA depending on which value(s) it changed. The desired output value and velocity is set similarly in ConstraintSingleValue: the ACT_VALUE and ACT_VELOCITY flags in action indicates that the application has changed the desired value and desired velocity respectively.

When you create a new constraint, you must think on a particular arangement of output values and stick to it. You must also give a unique ID to each output value and define this in an enum ID in your class. If your constraint allows a variable set of output values (see CopyPose), you must stick to a particular order. The idea behind this requirement is that the callback function can rely on a certain data organization and directly get to the data of interested without going through tedious data scan loop. However, you should always set the id and number fields so that a data scan loop is possible.

This structure is primarly used for data exchange, it is not the best place to store the data that you will use in the control equation:

  1. You don't want the application to corrupt important data; you always want to check the values before updating the internal data.
  2. You cannot used the output values in ConstraintSingleValue directly in case you implement an interpolation formula (see updateKinematics() below). Interpolation is important if you plan to have variable desired output value and if you want to have better precision by tracking the velocity and not just the error.

However, you may decide that the output value will essentially be constant and assume that the application (the itasc plugin) will not corrupt the data. In that case, you can use the same ConstraintValues array for internal storage and for data exchange.

1.2. The cache system

iTaSC in Simulation mode uses a cache to store the state of all objects and constraints so that the state of the system can be retrieved and the velocity correctly evaluated even if the time is going backward. The cache memory is organized in devices, channels, buffers and elements. It is the responsability of each constraint to create the channels that it needs and to push and pop elements when appropriate (device and buffers are manages transparently). A constraint need only to create one channel; there is no reason to create multiple channels, except if you want to store other things that just state information.

For the cache system, elements are unstructured binary arrays of variable size but with a maximum size that must be specified when creating the channel. Elements are ordered by timestamp in the channel. It is not possible to insert an element in the middle of a channel without deleting all the elements with equal or higher timestamp. It is also not possible to delete an element in the middle of channel: you can only delete all elements at or after a given timestamp. These restrictions are justified by the fact that modifying the state at a particular time automatically invalidates the state at any future time.

To avoid unnecessary cache truncation, the cache system provides the helper function addCacheVectorIfDifferent() that leaves the cache unchanged if the new element is identical (within a threshold) to the element that is already in the cache for the same timestamp. This function assumes that the element is an array of doubles, which is the case for all elements currently defined in iTaSC.

The cache system is 'pointer friendly': elements are aligned in memory so that you can cast an element address to a C structure and read/write directly from it.

What data do you need to store in the cache?
You need to store what is necessary to evaluates the constraint pose and output value and velocities:

  • the Xf array of joint values: if your constraint has a virtual armature
  • the desired output value: always needed unless you decide that the desired output value has a predefined fixed value.
  • the desired output velocity: not needed if you decide that the application cannot specify directly a desired output velocity (the data interface allows it).
  • the feedback and weight coefficients: not needed if you decide that the callback function must always update them.

1.3. What data member must be defined in the constraint class?

At a minimum a constraint class must have the following data:

  • A fixed array of ConstraintValues and ConstraintSingleValue that you will setup once in the constructor and use it to pass constraint state to the application. Using a fixed array eliminates the need to allocate memory each time you communicate with the application.
  • internal output value, velocity, weight and feedback if you don't use ConstraintValues for that.
  • Information on cache: pointer to Cache object, identifier of channel, element size and any other information needed to assemble and disassemble elements, timestamp of last element stored in cache.
  • KDL Kinematic chain and Jacobian if the constraint has a virtual armature.

1.4. The Timestamp structure

The Timestamp structure carries a lot of information about the simulation step that is being executed. Many constraint member functions receive this structure as parameter and must comply to the instructions that are implicitely given by the field values.

Here is a detailed description of the fields and actions to take;

double realTimestamp

The time in second for the current simulation step. In Blender, it is computed as 2147483.648+frame_number/frame_rate. The offset ensures that the timestamp is always positive event for negative frame. In the BGE, it is simply the number of second since the start of the simulation. This value stays the same during substeps. It is normally not used by constraints.

double realTimestep

The duration of the simulation interval in second. In Blender, it is the interval between the current frame and the previous frame in the cache. If there is no previous frame in the cache, it defaults to the frame duration (1/frame_rate). This value is affected by substeps:
  • if substep=0 (i.e. for the first substep), it is the entire simulation interval. Constraint can use it to estimate the desired output velocity that will be used for the subsequent substeps.
  • if substep=1 (i.e. for subsequent substeps), it is the duration of the current substep. Constraint can used it to integrate the desired output value based on the current value and the desired output velocity computed in the first substep.

CacheTS cacheTimestamp

Same as realTimestamp but in milliseconds and in 32 bit unsigned integer format. This timestamp is used to store elements in the cache. This format allows compact storage of elements in cache while having a good precision on the time. The drawback is that it limits the animations to 2147483 seconds. However, this time is long enough for Blender. It is not a limiting factor in the BGE since cache is not used.

unsigned int numstep:8

8 bits field representing the total number of substeps requested by the application. A value of 0 is legal and indicates that the application requested the auto-step algorithm. This value stays the same during substeps. This field is normally not used by constraints.

unsigned int substep:1

boolean value indicating the substep state:
  • 0 = first and last substep of the simulation step. For the first substep, the constraint must pop the state from the cache and call back the application to get new control values. For the last substep, the constraint must push the new state on the cache. The fact that the same value is used for 2 different things is not a problem because it is used in two different functions.
  • 1 = intermediate substeps. The constraint may decide to call back the application if fine control is required (then iTaSC::ConstraintSet::m_substep must be turned on to tell iTaSC::Scene to update the matrices on each substep).

unsigned int reiterate:1

boolean value indicating if the simulation step is a reiteration, i.e. a simulation step immediately following a previous simulation step with the same timestamp. The purpose of reiteration is to improve the precision of the constraint by extending the convergence time.
  • 0 = first simulation step. Constraint state is poped from cache.
  • 1 = reiteration steps. Don't pop state from cache. Don't call back the application if iTaSC::ConstraintSet::m_substep is off (the application was already called in first simulation step)

unsigned int cache:1

boolean indicating if state should be pushed on cache at the end of the simulation step.
  • 0 = don't push state on cache. This is used in case of reiteration: the cache should only be pushed on the last reiteration.
  • 1 = push state on cache on the last substep

unsigned int update:1

not used, always 1.

unsigned int interpolate:1

boolean indicating if desired output value should be interpolated during substep.
  • 0 = don't interpolate (desired output take immediately the value given by the application in the callback). This is used in Animation mode where the solver does not work in true time context.
  • 1 = interpolate (desired output is integrated during substeps). This is the normal Simulation mode.

1.5. Member functions to implement

1.5.1 Mandatory functions


The parameters of the constructor should specify the outputs that will be controlled. iTaSC does not allow redefinition of the output vector after the iTaSC::Scene is initialized. The scene must be deleted and entirely rebuilt if something change that affects the size of the matrices.
The constructor should initialize the ConstraintValues array and the internal data.
A typical parameter is the 'arm_length' which gives an estimate of the bone size of the armature object for which the iTaSC::Scene is being prepared (the plugin computes this estimate). It is also the upper bound for the cartesian error that the constraint should use in the feedback correction term of the control equation (the K*(yd-y) term). If the cartesian error is larger that this value, it should be clamped. The goal is to avoid excessive velocity in case the target is far away out of reach.

virtual void initCache(Cache *_cache)

This function is called once to initialize the cache system; it should create the cache channel. The maxmimum size of the elements must be known. It is usually computed in the constructor based on the number of output values.
_cache is the pointer to the cache system, it must be saved to later push and pop elements during the simulation.
_cache can be NULL when cache is disabled (Animation mode and in the BGE).

virtual void updateKinematics(const Timestamp& timestamp)

update the desired output and push state to cache depending on Timestamp fields.
Typical pseudo code:
 	void MyConstraint::updateKinematics(const Timestamp& timestamp)
 		if (timestamp.interpolate) {
 			if (timestamp.substep) {
 				yd += yddot*timestamp.realTimestep;
 			} else {
 				yd = nextyd;
 				yddot = nextyddot;

virtual void pushCache(const Timestamp& timestamp)

Explicit push state in cache. This function is used in case of reiteration when the target precision has been obtained. During all reiteration steps Timestamp.cache is false so that updateKinematics does not update the cache. iTaSC will request explicit push cache with this function outside a simulation step.
Typical pseudo code:
 	void MyConstraint::pushCache(const Timestamp& timestamp)
 		if (!timestamp.substep && timestamp.cache)
myPushCache is a private function that performs the actual state serialilzation and push in cache channel. Each constraint must implement a function like that.

virtual void updateJacobian()

Computes the Jacobian for the current constraint armature joints and update iTaSC::ConstraintSet::m_Jf. This function is not called directly by iTaSC. It should be used whenever the joint armature have changed, for example after poping the state from the cache. The Jacobian reference point must be the base of the constraint armature.

virtual void updateControlOutput(const Timestamp& timestamp)

Compute output velocity with the control equation ydot = yddot+K(yd-y) (in plain english: output velocity equal the desired output velocity plus the difference between the desired output value and the actual output value (=error) multiplied by the feedback coefficient).
In this function you optionally pop state from cache and callback the application to get new control values. It is called at the start of the substep.
Pseudo code:
 	void MyConstraint::updateControlOutput(const Timestamp& timestamp)
 	    bool cacheAvail = true;
 	    if (!timestamp.substep) {
 	        if (!timestamp.reiterate)
 	            cacheAvail = myPopCache(timestamp.cacheTimestamp);
 	    if (m_constraintCallback && 
 	        (m_substep || (!timestamp.reiterate && !timestamp.substep))) {
 	        update ConstraintValues with current state
 	        if ((*m_constraintCallback)(timestamp, &myValues, myNb, myParam)) {
 	            setControlParameters(&myValues, myNb, timestamp.realTimestep);
 	    if (!cacheAvail || !timestamp.interpolate) {
 	        // no interpolation possible
 	        // take directly the value passed by application
 	        yd = nextyd;
 	        yddot = nextyddot;
 	    double error = yd-y;
 	    if (error > myMax)
 	        clamp error;

virtual const ConstraintValues* getControlParameters(unsigned int* _nvalues)

return current constraint state values
pseudo code:
 	const ConstraintValues* MyConstraint::getControlParameters(unsigned int* _nvalues)
 	    update ConstraintValues with current state
 	    if (_nvalues) 
 	    return &myValues; 

virtual bool setControlParameters(struct ConstraintValues* _values, unsigned int _nvalues, double timestep)

Updates the constraint internal control parameters. You must scan the _values array, and for each group of values, scan the _values.values array. For each matching id, you must perform the action specified in the action field: update weight, feedback, desired output and/or desired output velocity. If the application does not specify directly a desired output velocity, you must compute it from the variation of the desired output value over the simulation step.
timestep gives the length of the step. It can be 0 if the function is called outside the simulation loop. In that case, and if the application specifies the desired output, the internal desired output value can be updated directly and the desired velocity set to 0: you cannot estimate a velocity nor integrate it anyway.
This function is normally called during the simulation loop (see updateControlOutput) with timestep set to the duration of the simulation step. If the application specifies the desired output, it must be considered the desired output at the end of the step. As this function is called at the start of the step, the internal desired output value cannot be changed instantly: the velocity must be computed assuming linear interpolation and the internal desired output will be integrated on each substep (see updateKinematics). This is however optional and always assume desired output velocity=0. In that case the output velocity is only computed from the error and feedback coefficient.

1.5.2 Optional functions

virtual bool initialise(Frame& init_pose)

Initialize the constraint virtual armature to match init_pose and update the Jacobian.
This function is called when the simulation scene is initialized but also whenever the position of the objects on which the constraint is defined has changed unexpectedly.
This function is implemented in iTaSC::ConstraintSet by using the closeLoop function. It is usually more efficient to reimplement it directly if you have established a formula that computes the armature joints directly from the pose.
Pseudo code:
 	bool MyConstraint::initialise(Frame& init_pose)
 	    return true;

virtual void modelUpdate(Frame& _external_pose,const Timestamp& timestamp)

Updates the constraint virtual armature based on Xf velocity that iTaSC computed during the simulation scene.
This function is implemented in iTaSC::ConstraintSet as an IK solver for the constraint armature, assuming that you cannot compute the armature directly. If you have established a formula that gives the armature joints directly from the pose, you should overwrite this function and implement it just like initialise

virtual bool closeLoop()

Compute a new estimate of the constraint armature so that the constraint pose matches m_externalPose. Returns true is the new armature pose is sufficiently close to the external pose, false if not.
iTaSC only calls this function within iTaSC::ConstraintSet::initialise() and iTaSC::ConstraintSet::modelUpdate(). If you have overwritten these 2 functions, you don't need to overwrite this one.

virtual double getMaxTimestep(double& timestep)

Checks if the constraint armature joint velocity computed by the iTaSC will remain valid over the time interval specified by timestep and reduce it if not (timestep parameter is an input/output parameter).
This function is called as part of the auto-step algorithm of iTaSC. After solving the Jacobian equation, iTaSC updates the joint velocities, sets timestep to the Max parameter of the auto-step algorithm and calls this function on each object and constraint to let them check if this timestep is short enough for velocity integration. Each of them can reduce the timestep based on own criteria.
This function is implemented in iTaSC::ConstraintSet based on the simple rule that any joint should not change by more than a given max (usually 0.5 radiant) over the timestep. If the velocity is such that this max is violated, the timestep is reduced accordingly.
You may need to overwrite this function if you want to implement a more complex rule or if your constraint does not use a virtual armature.

2. Add the constraint to Blender UI

This chapter explains what you need to change in Blender to give access to your new constraint in the UI.

2.1 Add constraint parameters to DNA

If the constraint has parameters that you want to expose to Blender, you must add corresponding fields to the bKinematicConstraint structure (found in DNA_constraint_types.h). You can also reuse fields if best fit.

The bKinematicConstraint structure corresponds to the "IK" constraint in the Add Constraint menu (you find this menu on the Contraint panel in Contraint context of bones in pose mode). All iTaSC constraints are grouped under this constraint and therefore, must use fields of bKinematicConstraint for their parameters.

This constraint supports a variety of mode determine by the type field according to B_CONSTRAINT_IK_TYPE. Some fields are used by all types, some are specific to some types, this is indicated in the comment:

 typedef struct bKinematicConstraint {
   Object *tar;         // All: target object in case constraint needs a target
   short  iterations;   // All: Maximum number of iterations to try
   short  flag;         // All & CopyPose: some options Like CONSTRAINT_IK_TIP
   short  rootbone;     // All: index to rootbone, if zero go all the way to base
   short  max_rootbone;      // CopyPose: for auto-ik, maximum length of chain
   char   subtarget[32];     // All: String to specify sub-object target
   Object *poletar;          // All: Pole vector target
   char   polesubtarget[32]; // All: Pole vector sub-object target
   float  poleangle;         // All: Pole vector rest angle
   float  weight;            // All: Weight of constraint in IK tree
   float  orientweight; // CopyPose: weight of rotation constraint in IK tree
   float  grabtarget[3];// CopyPose: for target-less IK
   short  type;         // subtype of IK constraint: B_CONSTRAINT_IK_TYPE
   short  mode;         // Distance: how to limit to clamping sphere: LIMITDIST_..
   float  dist;         // Distance: radius of clamping sphere from target
 } bKinematicConstraint;

You will need to add at the very least a new constraint type at the end of this list:

 typedef enum B_CONSTRAINT_IK_TYPE {
   CONSTRAINT_IK_COPYPOSE = 0, // match position and/or orientation of target
   CONSTRAINT_IK_DISTANCE      // maintain distance with target */

If your constraint has parameters that fit existing fields of bKinematicConstraint, just reuse them. The fit must be good to be acceptable: the values you allow for a field should not be invalid for any of the constraints that use the same field and similarly, all values allowed by the other constraint should be acceptable to your constraint. If the field is used like an enum, you should use the same enum.

Strictly speaking you can reuse field in an incompatible way provided that you add the adaptation code in RNA when the user changes the constraint type. But this is not recommended.

If you have parameters that don't fit any of the existing fields, add more fields to the structure by respecting the DNA rules:

  1. align all new fields (the structure must not have 'holes')
  2. new fields must be added by group of 64bits. So if you just need a short, you will have to add 3 more shorts for padding. Padding fields should be called pad1, pad2, etc. These padding fields can be renamed later when more fields are necessary.
  3. once you have chosen a name for a field, you must never change it: DNA is intolerant to name change.
  4. don't use double or long, only char, short, int, float.
  5. use smallest type possible to limit structure size.

2.2 Update initialization and conversion code

DNA structures are allocated, loaded and initialized at different place in Blender. Adding new fields requires that you visit those places and add the code for the new field. The best way to make sure that you don't forget any place is to do a blender wide search in the source code for other fields in this structure.

For example, here is the result of the search on orienweight (I have removed lines corresponding to RNA and plugin that will be explained later):

source\blender\blenkernel\intern\constraint.c(1056): data->orientweight=1.0f;
source\blender\blenloader\intern\readfile.c(7724):   data->orientweight=1.0f;

The line in constraint.c corresponds to the initialization of the structure. If your field has a default value that is different from 0, you must add a line in this file. It is recommend to add a line even if the default value is 0.

The line in readfile.c corresponds to the upgrade code that you must add when you define fields that don't have 0 as default value. This is to deal with old blend files that don't have the new fields. DNA is capable of mapping old structures to new structures but the new fields are initialized to 0 by default. If your field has a non-zero default value you must take care of initializing it in readfile.c. I won't explain how to do it as it is a common modification in Blender, just look carefully at that file.

2.3 Update RNA

The RNA system is another layer of meta data above DNA. It is used to generate UI buttons automatically. It is fairly easy to add new fields to RNA, just follow the examples of the existing fields in source/blender/makesrna/intern/rna_constraint.c, in function rna_def_constraint_kinematic. Here is the code corresponding to orientweight field:

 prop= RNA_def_property(srna, "orient_weight", PROP_FLOAT, PROP_NONE);
 RNA_def_property_float_sdna(prop, NULL, "orientweight");
 RNA_def_property_range(prop, 0.01, 1.f);
 RNA_def_property_ui_text(prop, "Orientation Weight", 
                "For Tree-IK: Weight of orientation control for this target.");
 RNA_def_property_update(prop, NC_OBJECT|ND_CONSTRAINT, "rna_Constraint_update");


  • The RNA field name can be different than the DNA field name. There are rules to choose RNA names: Naming Conventions
  • The RNA_def_property_update function allows you to define a function that will be called whenever the field is changed via RNA (i.e. via the UI or Python). If changing the field value invalidates the iTaSC scene (example: it changes the number of output values), you must flush the entire iTaSC scene: use the rna_Constraint_dependency_update function for that. If the change just requires to clear the cache, use rna_Constraint_update. If you must do special processing (such as correlating two fields value), create your own function and define it in the first part of the file.

Instructions if you reuse fields in an incompatible way:
If for some reason you decide to reuse fields in an incompatible way you must take the following additional steps:

  • define different RNA fields anyway. They will point to the same DNA field but will have different name, min/max values, helper text, enum values, etc.
  • add code to set appropriate default value in rna_Constraint_ik_type_set: this function is called before the constraint type is changed and gives you the possible to initialize certain fields according to the new constraint type.

Don't forget to add another value to the constraint_ik_type_items that correspond to the new constraint type:

 EnumPropertyItem constraint_ik_type_items[] ={
   {CONSTRAINT_IK_COPYPOSE, "COPY_POSE", 0, "Copy Pose", ""},
   {CONSTRAINT_IK_DISTANCE, "DISTANCE", 0, "Distance", ""},
   {0, NULL, 0, NULL, NULL},

The first string in capital letters is the value of the item, the second string is how it shows up in menu.

2.4 Update UI script

The script that displays the IK constraint is in release/script/ui/ The code is splitted in different functions. The function that displays the IK constraint is:

 def IK(self, context, layout, con):
     if context.object.pose.ik_solver == "ITASC":
         layout.itemR(con, "ik_type")
         getattr(self, "IK_"+con.ik_type)(context, layout, con)
         self.IK_COPY_POSE(context, layout, con)

You will notice that if the solver is iTaSC, it delegates the job of displaying the fields to a function that is called "IK_"+con.ik_type where con.ik_type takes the value that you have defined in constraint_ik_type_items. For the Copy Pose and Distance constraints, the functions are "IK_COPY_POSE" and "IK_DISTANCE" respectively.

I won't explain how to define and customize buttons, there are guidelines for that elsewhere in BlenderWiki.

3. Update the plugin

3.1 Add the constraint

The plugin is located in source/blender/ikplugin/intern/itasc_plugin.cpp. It is the bridge between Blender and iTasc: it converts a Blender scene into an iTaSC scene, executes the solver and extracts the armature pose from the iTaSC scene. It is a complex piece of code but fortunately, adding a constraint to it is easy.

If you search for CONSTRAINT_IK_COPYPOSE, you will see how the CopyPose is handled:

 itasc_plugin.cpp(1402): iktarget->eeBlend = 
          (!ikscene->polarConstraint && condata->type==CONSTRAINT_IK_COPYPOSE) ? true : false;
 itasc_plugin.cpp(1412): case CONSTRAINT_IK_COPYPOSE:
 itasc_plugin.cpp(1771): case CONSTRAINT_IK_COPYPOSE:

The first occurence corresponds to the handling of the enforce parameter. The enforce parameter exists for all constraints. It is supposed to control how much a constraint affects the armature. However, it is not implemented as a weight in the IK algorithm but as a change of the target position. The two extreme positions are:

  1. the actual target position (enforce=1.0)
  2. the position the end effector would take if the armature was in rest pose (enforce=0.0).

The target position is interpolated between these two positions according to the enforce parameter (see the interpolation code in target_callback(). The idea is that if the target gets closer to the rest position of the end effector, the armature will have to move less to match it. However, this method only makes sense for the CopyPose constraint, hence the check on CONSTRAINT_IK_COPYPOSE to set the eeBlend flag (this flag is used when computing the target position).
You may find a certain way to implement the enforce parameter for your constraint and implement it. You may also just drop it; the natural way to deal with constraint enforcing in iTaSC is by the weight parameter.

The second occurence corresponds to the conversion code. You must add another case for your constraint. In the conversion code, you must at least:

  1. create the iTaSC constraint object
  2. initialize the control parameters. Not needed for the parameters that you decide to update systematically in the callback
  3. register the control callback function
  4. register the error callback function. This function is not a iTaSC function. It is used by the plugin to update the constraint error at the end of the simulation step.
  5. add the constraint to iTaSC. You specify the two objects between which the constraint is define. The first one is normally the armature, the second one is normally a MovingFrame object that was created for the constraint. This order is not mandatory, the CopyPose constraint allows to select the order for instance.

The third occurence is for the test_constraint() plugin function. Blender calls this function after any user modification in IK constraint panel. This way, the plugin has the possibility to check if the parameters specified by the user are consistent and to correct them if needed. Note that Blender automatically checks that target objects exist and RNA ensures that each field is valid according to min/max and enum list. You only need to add check code if certain combinations of values are illegal. You can change the values automatically or set the CONSTRAINT_DISABLE flag to prevent the conversion of the constraint.

Finally, you must implement the control callback function and the error callback function. In the control callback function, you receive a ConstraintValues array that you will update with new control parameters: desired values, weight, feedback. You get those values from the Blender bKinematicConstraint structure that you retrieve via the _param parameter. You must also check the CONSTRAINT_OFF flag since Blender and the GE uses this flag to turn off a constraint temporarily. You will implement this flag by turning the weight of the constraint to 0.

The error callback function is called after the simulation step. You receive the same ConstraintValues array as in the control callback function. You must extract from it a numerical value that represents the amount of error on the realization of the constraint and store it in the lin_error field of the bKinematicConstraint structure, or the rot_error field, or both (rot_error is for angular error). This field is used in the GE to implement high level logic based on the realization/non-realization of constraints.

3.2 Update the target callback

The target of an IK constraint is stored in the tar field of the bKinematicConstraint structure. The plugin converts the target into a iTaSC::MovingFrame object (i.e. an uncontrolled object) and pass the Blender object position to iTaSC in the target_callback() function.

Note: a target is always a MovingFrame; even if the target object is an armature. This is because the plugin builds one simulation scene for each armature (or portion of armature) controlled by IK and stores the scene in the Pose structure attached to the Blender object. Creating a simulation scene that is common to two armatures would be a major complication in Blender data handling and is not supported at present, although iTaSC would support it.

If your constraint makes use of the target, you MUST use the tar field.

If you are happy with the target being the position of a Blender object (i.e. its center), don't change anything to the target conversion/callback code. The target conversion code is just above the constraint conversion code and the callback function is target_callback().

If you don't use a target (i.e. the target is the world reference), you must tweak the target conversion code and update itasc_test_constraint() since the absence of target will invalidate the constraint (see test_constraints() in source/blender/editors/object/object_constraint.c).

If you have a more complicated method of computing the target, you must tweak the target conversion code and define another target callback function.

I cannot give more explanation at this stage as it depends on the constraint.

4. Update the BGE

If you have added new fields in the bKinematicConstraint structure, you probably want to give access to them in the BGE. This is done in the source/gameengine/Converter/BL_ArmatureConstraint.cpp file:

  1. Add a BCA_ constant at the end of the list, give it the next value in the list.
  2. Add a KX_PYATTRIBUTE_RO_FUNCTION (read-only) or KX_PYATTRIBUTE_RW_FUNCTION (read-write) macro at the end of the BL_ArmatureConstraint::Attributes[] array. Use the read-only version if changing the field would require rebuilding the iTaSC scene, which is not supported at present. Use the read-write version for fields that are used in the callback function (desired output value, flag, etc.).
  3. Add a case in BL_ArmatureConstraint::py_attr_getattr() to return the field value to Python.
  4. For read-write attributes, add a case in BL_ArmatureConstraint::py_attr_setattr() to set the value from Python. Note that you must check the value before updating the structure.

If you want to access the field via Logic Bricks (the GUI for logic programmation), you need to update a lot more stuff. Search for ACT_ARM_SETWEIGHT in Blender source code to see all the places that needs to be updated. I won't explain that part since Python is better suited for constraint control.

You can also add Python constants in initGameLogic() function in source/gameengine/Ketsji/KX_PythonInit.cpp. At the very least you should add another CONSTRAINT_IK_.. constant for the new constraint type (see CONSTRAINT_IK_COPYPOSE). If your fields use flags and enum values, using constants is preferable for code readability.

Any new field/constant must be documented in source/gameengine/PyDoc/ Search for BL_ArmatureConstraint and add your fields/constant there. This file is in epydoc format (similar to Python code). I won't explain the format here, it's easy to add stuff by following the examples. To see the result of your change, you must compile the doc with script in the same directory (epydoc must installed and configured). If you have problems with epydoc, you can easily find help on the net.

5. Conclusion

I hope I didn't forget anything fundamental. In any case, I advise you to search the code for field name, function name, constant, etc. that are specific to the existing constraints and visit all the places where they are used; these are the places where you will have to make your changes.

If everything went well, the miracle of iTaSC is that armature will perform the new task 'intelligently' although you didn't have to implement the 'intelligence' explicitely.