merge with trunk at r31523
[blender.git] / source / gameengine / Converter / BL_ArmatureObject.cpp
index bf774bf75683b286c5deb8e25ddb1a412ca803da..5d05dfcff521dace8ca63a16696d81f8d1fad52c 100644 (file)
@@ -15,7 +15,7 @@
  *
  * 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.
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  *
  * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
  * All rights reserved.
 
 #include "BL_ArmatureObject.h"
 #include "BL_ActionActuator.h"
+#include "KX_BlenderSceneConverter.h"
+#include "MEM_guardedalloc.h"
 #include "BLI_blenlib.h"
+#include "BLI_ghash.h"
+#include "BLI_math.h"
+#include "BIK_api.h"
 #include "BKE_action.h"
 #include "BKE_armature.h"
+#include "BKE_utildefines.h"
+#include "BKE_constraint.h"
 #include "GEN_Map.h"
 #include "GEN_HashedPtr.h"
 #include "MEM_guardedalloc.h"
 #include "DNA_action_types.h"
 #include "DNA_armature_types.h"
 #include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_nla_types.h"
+#include "DNA_constraint_types.h"
+#include "KX_PythonSeq.h"
+#include "KX_PythonInit.h"
+#include "KX_KetsjiEngine.h"
 
 #include "MT_Matrix4x4.h"
 
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
+/** 
+ * Move here pose function for game engine so that we can mix with GE objects
+ * Principle is as follow:
+ * Use Blender structures so that where_is_pose can be used unchanged
+ * Copy the constraint so that they can be enabled/disabled/added/removed at runtime
+ * Don't copy the constraints for the pose used by the Action actuator, it does not need them.
+ * Scan the constraint structures so that the KX equivalent of target objects are identified and 
+ * stored in separate list.
+ * When it is about to evaluate the pose, set the KX object position in the obmat of the corresponding
+ * Blender objects and restore after the evaluation.
+ */
+void game_copy_pose(bPose **dst, bPose *src, int copy_constraint) {
+       bPose *out;
+       bPoseChannel *pchan, *outpchan;
+       GHash *ghash;
+       
+       /* the game engine copies the current armature pose and then swaps
+        * the object pose pointer. this makes it possible to change poses
+        * without affecting the original blender data. */
+
+       if (!src) {
+               *dst=NULL;
+               return;
+       }
+       else if (*dst==src) {
+               printf("copy_pose source and target are the same\n");
+               *dst=NULL;
+               return;
+       }
+       
+       out= (bPose*)MEM_dupallocN(src);
+       out->chanhash = NULL;
+       out->agroups.first= out->agroups.last= NULL;
+       out->ikdata = NULL;
+       out->ikparam = MEM_dupallocN(out->ikparam);
+       out->flag |= POSE_GAME_ENGINE;
+       BLI_duplicatelist(&out->chanbase, &src->chanbase);
+
+       /* remap pointers */
+       ghash= BLI_ghash_new(BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, "game_copy_pose gh");
+
+       pchan= (bPoseChannel*)src->chanbase.first;
+       outpchan= (bPoseChannel*)out->chanbase.first;
+       for (; pchan; pchan=pchan->next, outpchan=outpchan->next)
+               BLI_ghash_insert(ghash, pchan, outpchan);
+
+       for (pchan=(bPoseChannel*)out->chanbase.first; pchan; pchan=(bPoseChannel*)pchan->next) {
+               pchan->parent= (bPoseChannel*)BLI_ghash_lookup(ghash, pchan->parent);
+               pchan->child= (bPoseChannel*)BLI_ghash_lookup(ghash, pchan->child);
+               pchan->path= NULL;
+
+               if (copy_constraint) {
+                       ListBase listb;
+                       // copy all constraint for backward compatibility
+                       copy_constraints(&listb, &pchan->constraints, FALSE);  // copy_constraints NULLs listb, no need to make extern for this operation.
+                       pchan->constraints= listb;
+               } else {
+                       pchan->constraints.first = NULL;
+                       pchan->constraints.last = NULL;
+               }
+
+               // fails to link, props are not used in the BGE yet.
+               /* if(pchan->prop)
+                       pchan->prop= IDP_CopyProperty(pchan->prop); */
+               pchan->prop= NULL;
+       }
+
+       BLI_ghash_free(ghash, NULL, NULL);
+       // set acceleration structure for channel lookup
+       make_pose_channels_hash(out);
+       *dst=out;
+}
+
 
 
+/* Only allowed for Poses with identical channels */
+void game_blend_poses(bPose *dst, bPose *src, float srcweight/*, short mode*/)
+{
+       short mode= ACTSTRIPMODE_BLEND;
+       
+       bPoseChannel *dchan;
+       const bPoseChannel *schan;
+       bConstraint *dcon, *scon;
+       float dstweight;
+       int i;
+
+       switch (mode){
+       case ACTSTRIPMODE_BLEND:
+               dstweight = 1.0F - srcweight;
+               break;
+       case ACTSTRIPMODE_ADD:
+               dstweight = 1.0F;
+               break;
+       default :
+               dstweight = 1.0F;
+       }
+       
+       schan= (bPoseChannel*)src->chanbase.first;
+       for (dchan = (bPoseChannel*)dst->chanbase.first; dchan; dchan=(bPoseChannel*)dchan->next, schan= (bPoseChannel*)schan->next){
+               // always blend on all channels since we don't know which one has been set
+               /* quat interpolation done separate */
+               if (schan->rotmode == ROT_MODE_QUAT) {
+                       float dquat[4], squat[4];
+                       
+                       QUATCOPY(dquat, dchan->quat);
+                       QUATCOPY(squat, schan->quat);
+                       if (mode==ACTSTRIPMODE_BLEND)
+                               interp_qt_qtqt(dchan->quat, dquat, squat, srcweight);
+                       else {
+                               mul_fac_qt_fl(squat, srcweight);
+                               mul_qt_qtqt(dchan->quat, dquat, squat);
+                       }
+                       
+                       normalize_qt(dchan->quat);
+               }
+
+               for (i=0; i<3; i++) {
+                       /* blending for loc and scale are pretty self-explanatory... */
+                       dchan->loc[i] = (dchan->loc[i]*dstweight) + (schan->loc[i]*srcweight);
+                       dchan->size[i] = 1.0f + ((dchan->size[i]-1.0f)*dstweight) + ((schan->size[i]-1.0f)*srcweight);
+                       
+                       /* euler-rotation interpolation done here instead... */
+                       // FIXME: are these results decent?
+                       if (schan->rotmode)
+                               dchan->eul[i] = (dchan->eul[i]*dstweight) + (schan->eul[i]*srcweight);
+               }
+               for(dcon= (bConstraint*)dchan->constraints.first, scon= (bConstraint*)schan->constraints.first; dcon && scon; dcon= (bConstraint*)dcon->next, scon= (bConstraint*)scon->next) {
+                       /* no 'add' option for constraint blending */
+                       dcon->enforce= dcon->enforce*(1.0f-srcweight) + scon->enforce*srcweight;
+               }
+       }
+       
+       /* this pose is now in src time */
+       dst->ctime= src->ctime;
+}
+
+void game_free_pose(bPose *pose)
+{
+       if (pose) {
+               /* free pose-channels and constraints */
+               free_pose_channels(pose);
+               
+               /* free IK solver state */
+               BIK_clear_data(pose);
+
+               /* free IK solver param */
+               if (pose->ikparam)
+                       MEM_freeN(pose->ikparam);
+
+               MEM_freeN(pose);
+       }
+}
+
 BL_ArmatureObject::BL_ArmatureObject(
                                void* sgReplicationInfo, 
                                SG_Callbacks callbacks, 
-                               Object *armature )
+                               Object *armature,
+                               Scene *scene)
 
 :      KX_GameObject(sgReplicationInfo,callbacks),
+       m_controlledConstraints(),
+       m_poseChannels(),
        m_objArma(armature),
        m_framePose(NULL),
+       m_scene(scene), // maybe remove later. needed for where_is_pose
        m_lastframe(0.0),
+       m_timestep(0.040),
        m_activeAct(NULL),
        m_activePriority(999),
+       m_constraintNumber(0),
+       m_channelNumber(0),
        m_lastapplyframe(0.0)
 {
-       m_armature = get_armature(m_objArma);
+       m_armature = (bArmature *)armature->data;
 
        /* we make a copy of blender object's pose, and then always swap it with
         * the original pose before calling into blender functions, to deal with
         * replica's or other objects using the same blender object */
        m_pose = NULL;
-       copy_pose(&m_pose, m_objArma->pose, 1 /* copy_constraint_channels_hack */);
+       game_copy_pose(&m_pose, m_objArma->pose, 1);
+       // store the original armature object matrix
+       memcpy(m_obmat, m_objArma->obmat, sizeof(m_obmat));
 }
 
-CValue* BL_ArmatureObject::GetReplica()
+BL_ArmatureObject::~BL_ArmatureObject()
 {
-       BL_ArmatureObject* replica = new BL_ArmatureObject(*this);
+       BL_ArmatureConstraint* constraint;
+       while ((constraint = m_controlledConstraints.Remove()) != NULL) {
+               delete constraint;
+       }
+       BL_ArmatureChannel* channel;
+       while ((channel = static_cast<BL_ArmatureChannel*>(m_poseChannels.Remove())) != NULL) {
+               delete channel;
+       }
+       if (m_pose)
+               game_free_pose(m_pose);
+       if (m_framePose)
+               game_free_pose(m_framePose);
+}
+
+
+void BL_ArmatureObject::LoadConstraints(KX_BlenderSceneConverter* converter)
+{
+       // first delete any existing constraint (should not have any)
+       while (!m_controlledConstraints.Empty()) {
+               BL_ArmatureConstraint* constraint = m_controlledConstraints.Remove();
+               delete constraint;
+       }
+       m_constraintNumber = 0;
+
+       // list all the constraint and convert them to BL_ArmatureConstraint
+       // get the persistent pose structure
+       bPoseChannel* pchan;
+       bConstraint* pcon;
+       bConstraintTypeInfo* cti;
+       Object* blendtarget;
+       KX_GameObject* gametarget;
+       KX_GameObject* gamesubtarget;
+
+       // and locate the constraint
+       for (pchan = (bPoseChannel*)m_pose->chanbase.first; pchan; pchan=(bPoseChannel*)pchan->next) {
+               for (pcon = (bConstraint*)pchan->constraints.first; pcon; pcon=(bConstraint*)pcon->next) {
+                       if (pcon->flag & CONSTRAINT_DISABLE)
+                               continue;
+                       // which constraint should we support?
+                       switch (pcon->type) {
+                       case CONSTRAINT_TYPE_TRACKTO:
+                       case CONSTRAINT_TYPE_KINEMATIC:
+                       case CONSTRAINT_TYPE_ROTLIKE:
+                       case CONSTRAINT_TYPE_LOCLIKE:
+                       case CONSTRAINT_TYPE_MINMAX:
+                       case CONSTRAINT_TYPE_SIZELIKE:
+                       case CONSTRAINT_TYPE_LOCKTRACK:
+                       case CONSTRAINT_TYPE_STRETCHTO:
+                       case CONSTRAINT_TYPE_CLAMPTO:
+                       case CONSTRAINT_TYPE_TRANSFORM:
+                       case CONSTRAINT_TYPE_DISTLIMIT:
+                               cti = constraint_get_typeinfo(pcon);
+                               gametarget = gamesubtarget = NULL;
+                               if (cti && cti->get_constraint_targets) {
+                                       ListBase listb = { NULL, NULL };
+                                       cti->get_constraint_targets(pcon, &listb);
+                                       if (listb.first) {
+                                               bConstraintTarget* target = (bConstraintTarget*)listb.first;
+                                               if (target->tar && target->tar != m_objArma) {
+                                                       // only remember external objects, self target is handled automatically
+                                                       blendtarget = target->tar;
+                                                       gametarget = converter->FindGameObject(blendtarget);
+                                               }
+                                               if (target->next != NULL) {
+                                                       // secondary target
+                                                       target = (bConstraintTarget*)target->next;
+                                                       if (target->tar && target->tar != m_objArma) {
+                                                               // only track external object
+                                                               blendtarget = target->tar;
+                                                               gamesubtarget = converter->FindGameObject(blendtarget);
+                                                       }
+                                               }
+                                       }
+                                       if (cti->flush_constraint_targets)
+                                               cti->flush_constraint_targets(pcon, &listb, 1);
+                               }
+                               BL_ArmatureConstraint* constraint = new BL_ArmatureConstraint(this, pchan, pcon, gametarget, gamesubtarget);
+                               m_controlledConstraints.AddBack(constraint);
+                               m_constraintNumber++;
+                       }
+               }
+       }
+}
+
+BL_ArmatureConstraint* BL_ArmatureObject::GetConstraint(const char* posechannel, const char* constraintname)
+{
+       SG_DList::iterator<BL_ArmatureConstraint> cit(m_controlledConstraints);
+       for (cit.begin(); !cit.end(); ++cit) {
+               BL_ArmatureConstraint* constraint = *cit;
+               if (constraint->Match(posechannel, constraintname))
+                       return constraint;
+       }
+       return NULL;
+}
+
+BL_ArmatureConstraint* BL_ArmatureObject::GetConstraint(const char* posechannelconstraint)
+{
+       // performance: use hash string instead of plain string compare
+       SG_DList::iterator<BL_ArmatureConstraint> cit(m_controlledConstraints);
+       for (cit.begin(); !cit.end(); ++cit) {
+               BL_ArmatureConstraint* constraint = *cit;
+               if (!strcmp(constraint->GetName(), posechannelconstraint))
+                       return constraint;
+       }
+       return NULL;
+}
+
+BL_ArmatureConstraint* BL_ArmatureObject::GetConstraint(int index)
+{
+       SG_DList::iterator<BL_ArmatureConstraint> cit(m_controlledConstraints);
+       for (cit.begin(); !cit.end() && index; ++cit, --index);
+       return (cit.end()) ? NULL : *cit;
+}
+
+/* this function is called to populate the m_poseChannels list */
+void BL_ArmatureObject::LoadChannels()
+{
+       if (m_poseChannels.Empty()) {
+               bPoseChannel* pchan;
+               BL_ArmatureChannel* proxy;
        
-       // this will copy properties and so on...
-       CValue::AddDataToReplica(replica);
+               m_channelNumber = 0;
+               for (pchan = (bPoseChannel*)m_pose->chanbase.first; pchan; pchan=(bPoseChannel*)pchan->next) {
+                       proxy = new BL_ArmatureChannel(this, pchan);
+                       m_poseChannels.AddBack(proxy);
+                       m_channelNumber++;
+               }
+       }
+}
 
-       ProcessReplica(replica);
+BL_ArmatureChannel* BL_ArmatureObject::GetChannel(bPoseChannel* pchan)
+{
+       LoadChannels();
+       SG_DList::iterator<BL_ArmatureChannel> cit(m_poseChannels);
+       for (cit.begin(); !cit.end(); ++cit) 
+       {
+               BL_ArmatureChannel* channel = *cit;
+               if (channel->m_posechannel == pchan)
+                       return channel;
+       }
+       return NULL;
+}
+
+BL_ArmatureChannel* BL_ArmatureObject::GetChannel(const char* str)
+{
+       LoadChannels();
+       SG_DList::iterator<BL_ArmatureChannel> cit(m_poseChannels);
+       for (cit.begin(); !cit.end(); ++cit) 
+       {
+               BL_ArmatureChannel* channel = *cit;
+               if (!strcmp(channel->m_posechannel->name, str))
+                       return channel;
+       }
+       return NULL;
+}
+
+BL_ArmatureChannel* BL_ArmatureObject::GetChannel(int index)
+{
+       LoadChannels();
+       if (index < 0 || index >= m_channelNumber)
+               return NULL;
+       SG_DList::iterator<BL_ArmatureChannel> cit(m_poseChannels);
+       for (cit.begin(); !cit.end() && index; ++cit, --index);
+       return (cit.end()) ? NULL : *cit;
+}
+
+CValue* BL_ArmatureObject::GetReplica()
+{
+       BL_ArmatureObject* replica = new BL_ArmatureObject(*this);
+       replica->ProcessReplica();
        return replica;
 }
 
-void BL_ArmatureObject::ProcessReplica(BL_ArmatureObject *replica)
+void BL_ArmatureObject::ProcessReplica()
 {
-       KX_GameObject::ProcessReplica(replica);
+       bPose *pose= m_pose;
+       KX_GameObject::ProcessReplica();
 
-       replica->m_pose = NULL;
-       copy_pose(&replica->m_pose, m_pose, 1 /* copy_constraint_channels_hack */);
+       m_pose = NULL;
+       m_framePose = NULL;
+       game_copy_pose(&m_pose, pose, 1);       
 }
 
-BL_ArmatureObject::~BL_ArmatureObject()
+void BL_ArmatureObject::ReParentLogic()
 {
-       if (m_pose)
-               free_pose(m_pose);
+       SG_DList::iterator<BL_ArmatureConstraint> cit(m_controlledConstraints);
+       for (cit.begin(); !cit.end(); ++cit) {
+               (*cit)->ReParent(this);
+       }
+       KX_GameObject::ReParentLogic();
+}
+
+void BL_ArmatureObject::Relink(GEN_Map<GEN_HashedPtr, void*> *obj_map)
+{
+       SG_DList::iterator<BL_ArmatureConstraint> cit(m_controlledConstraints);
+       for (cit.begin(); !cit.end(); ++cit) {
+               (*cit)->Relink(obj_map);
+       }
+       KX_GameObject::Relink(obj_map);
 }
 
-bool BL_ArmatureObject::VerifyPose()
+bool BL_ArmatureObject::UnlinkObject(SCA_IObject* clientobj)
 {
+       // clientobj is being deleted, make sure we don't hold any reference to it
+       bool res = false;
+       SG_DList::iterator<BL_ArmatureConstraint> cit(m_controlledConstraints);
+       for (cit.begin(); !cit.end(); ++cit) {
+               res |= (*cit)->UnlinkObject(clientobj);
+       }
+       return res;
+}
+
+void BL_ArmatureObject::ApplyPose()
+{
+       m_armpose = m_objArma->pose;
+       m_objArma->pose = m_pose;
+       // in the GE, we use ctime to store the timestep
+       m_pose->ctime = (float)m_timestep;
+       //m_scene->r.cfra++;
        if(m_lastapplyframe != m_lastframe) {
-               extract_pose_from_pose(m_objArma->pose, m_pose);
-               where_is_pose(m_objArma);
+               // update the constraint if any, first put them all off so that only the active ones will be updated
+               SG_DList::iterator<BL_ArmatureConstraint> cit(m_controlledConstraints);
+               for (cit.begin(); !cit.end(); ++cit) {
+                       (*cit)->UpdateTarget();
+               }
+               // update ourself
+               UpdateBlenderObjectMatrix(m_objArma);
+               where_is_pose(m_scene, m_objArma); // XXX
+               // restore ourself
+               memcpy(m_objArma->obmat, m_obmat, sizeof(m_obmat));
+               // restore active targets
+               for (cit.begin(); !cit.end(); ++cit) {
+                       (*cit)->RestoreTarget();
+               }
                m_lastapplyframe = m_lastframe;
-               extract_pose_from_pose(m_pose, m_objArma->pose);
-               return false;
        }
-       else
-               return true;
 }
 
-void BL_ArmatureObject::ApplyPose()
+void BL_ArmatureObject::RestorePose()
 {
-       if(VerifyPose())
-               extract_pose_from_pose(m_objArma->pose, m_pose);
+       m_objArma->pose = m_armpose;
+       m_armpose = NULL;
 }
 
 void BL_ArmatureObject::SetPose(bPose *pose)
@@ -122,30 +494,37 @@ bool BL_ArmatureObject::SetActiveAction(BL_ActionActuator *act, short priority,
 {
        if (curtime != m_lastframe){
                m_activePriority = 9999;
+               // compute the timestep for the underlying IK algorithm
+               m_timestep = curtime-m_lastframe;
                m_lastframe= curtime;
                m_activeAct = NULL;
                // remember the pose at the start of the frame
-               m_framePose = m_pose;
+               GetPose(&m_framePose);
        }
 
-       if (priority<=m_activePriority)
+       if (act) 
        {
-               if (priority<m_activePriority)
-                       // this action overwrites the previous ones, start from initial pose to cancel their effects
-                       m_pose = m_framePose;
-               if (m_activeAct && (m_activeAct!=act))
-                       m_activeAct->SetBlendTime(0.0); /* Reset the blend timer */
-               m_activeAct = act;
-               m_activePriority = priority;
-               m_lastframe = curtime;
-       
-               return true;
-       }
-       else{
-               act->SetBlendTime(0.0);
-               return false;
+               if (priority<=m_activePriority)
+               {
+                       if (priority<m_activePriority) {
+                               // this action overwrites the previous ones, start from initial pose to cancel their effects
+                               SetPose(m_framePose);
+                               if (m_activeAct && (m_activeAct!=act))
+                                       /* Reset the blend timer since this new action cancels the old one */
+                                       m_activeAct->SetBlendTime(0.0); 
+                       }
+                       m_activeAct = act;
+                       m_activePriority = priority;
+                       m_lastframe = curtime;
+               
+                       return true;
+               }
+               else{
+                       act->SetBlendTime(0.0);
+                       return false;
+               }
        }
-       
+       return false;
 }
 
 BL_ActionActuator * BL_ArmatureObject::GetActiveAction()
@@ -164,8 +543,7 @@ void BL_ArmatureObject::GetPose(bPose **pose)
                        a crash and memory leakage when 
                        &BL_ActionActuator::m_pose is freed
                */
-               int copy_constraint_channels_hack = 1;
-               copy_pose(pose, m_pose, copy_constraint_channels_hack);
+               game_copy_pose(pose, m_pose, 0);
        }
        else {
                if (*pose == m_pose)
@@ -181,17 +559,10 @@ void BL_ArmatureObject::GetMRDPose(bPose **pose)
        /* If the caller supplies a null pose, create a new one. */
        /* Otherwise, copy the armature's pose channels into the caller-supplied pose */
 
-       if (!*pose) {
-               // must duplicate the constraints too otherwise we have corruption in free_pose_channels()
-               // because it will free the blender constraints. 
-               // Ideally, blender should rememeber that the constraints were not copied so that
-               // free_pose_channels() would not free them.
-               copy_pose(pose, m_pose, 1);
-       }
-       else {
+       if (!*pose)
+               game_copy_pose(pose, m_pose, 0);
+       else
                extract_pose_from_pose(*pose, m_pose);
-       }
-
 }
 
 short BL_ArmatureObject::GetActivePriority()
@@ -210,16 +581,83 @@ bool BL_ArmatureObject::GetBoneMatrix(Bone* bone, MT_Matrix4x4& matrix)
 
        ApplyPose();
        pchan = get_pose_channel(m_objArma->pose, bone->name);
-
-       if(pchan) {
+       if(pchan)
                matrix.setValue(&pchan->pose_mat[0][0]);
-               return true;
-       }
+       RestorePose();
 
-       return false;
+       return (pchan != NULL);
 }
 
 float BL_ArmatureObject::GetBoneLength(Bone* bone) const
 {
        return (float)(MT_Point3(bone->head) - MT_Point3(bone->tail)).length();
 }
+
+#ifndef DISABLE_PYTHON
+
+// PYTHON
+
+PyTypeObject BL_ArmatureObject::Type = {
+       PyVarObject_HEAD_INIT(NULL, 0)
+       "BL_ArmatureObject",
+       sizeof(PyObjectPlus_Proxy),
+       0,
+       py_base_dealloc,
+       0,
+       0,
+       0,
+       0,
+       py_base_repr,
+       0,
+       &KX_GameObject::Sequence,
+       &KX_GameObject::Mapping,
+       0,0,0,
+       NULL,
+       NULL,
+       0,
+       Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+       0,0,0,0,0,0,0,
+       Methods,
+       0,
+       0,
+       &KX_GameObject::Type,
+       0,0,0,0,0,0,
+       py_base_new
+};
+
+PyMethodDef BL_ArmatureObject::Methods[] = {
+
+       KX_PYMETHODTABLE_NOARGS(BL_ArmatureObject, update),
+       {NULL,NULL} //Sentinel
+};
+
+PyAttributeDef BL_ArmatureObject::Attributes[] = {
+
+       KX_PYATTRIBUTE_RO_FUNCTION("constraints",               BL_ArmatureObject, pyattr_get_constraints),
+       KX_PYATTRIBUTE_RO_FUNCTION("channels",          BL_ArmatureObject, pyattr_get_channels),
+       {NULL} //Sentinel
+};
+
+PyObject* BL_ArmatureObject::pyattr_get_constraints(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef)
+{
+       return KX_PythonSeq_CreatePyObject((static_cast<BL_ArmatureObject*>(self_v))->m_proxy, KX_PYGENSEQ_OB_TYPE_CONSTRAINTS);
+}
+
+PyObject* BL_ArmatureObject::pyattr_get_channels(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef)
+{
+       BL_ArmatureObject* self = static_cast<BL_ArmatureObject*>(self_v);
+       self->LoadChannels(); // make sure we have the channels
+       return KX_PythonSeq_CreatePyObject((static_cast<BL_ArmatureObject*>(self_v))->m_proxy, KX_PYGENSEQ_OB_TYPE_CHANNELS);
+}
+
+KX_PYMETHODDEF_DOC_NOARGS(BL_ArmatureObject, update, 
+                                                 "update()\n"
+                                                 "Make sure that the armature will be updated on next graphic frame.\n"
+                                                 "This is automatically done if a KX_ArmatureActuator with mode run is active\n"
+                                                 "or if an action is playing. This function is usefull in other cases.\n")
+{
+       SetActiveAction(NULL, 0, KX_GetActiveEngine()->GetFrameTime());
+       Py_RETURN_NONE;
+}
+
+#endif // DISABLE_PYTHON