style cleanup
[blender.git] / source / gameengine / Converter / BL_ArmatureObject.cpp
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
19  * All rights reserved.
20  *
21  * The Original Code is: all of this file.
22  *
23  * Contributor(s): none yet.
24  *
25  * ***** END GPL LICENSE BLOCK *****
26  */
27
28 /** \file gameengine/Converter/BL_ArmatureObject.cpp
29  *  \ingroup bgeconv
30  */
31
32
33 #include "BL_ArmatureObject.h"
34 #include "BL_ActionActuator.h"
35 #include "KX_BlenderSceneConverter.h"
36 #include "MEM_guardedalloc.h"
37 #include "BLI_blenlib.h"
38 #include "BLI_math.h"
39 #include "BLI_utildefines.h"
40 #include "BLI_ghash.h"
41 #include "BIK_api.h"
42 #include "BKE_action.h"
43 #include "BKE_armature.h"
44
45 #include "BKE_constraint.h"
46 #include "CTR_Map.h"
47 #include "CTR_HashedPtr.h"
48 #include "MEM_guardedalloc.h"
49 #include "DNA_action_types.h"
50 #include "DNA_armature_types.h"
51 #include "DNA_object_types.h"
52 #include "DNA_scene_types.h"
53 #include "DNA_nla_types.h"
54 #include "DNA_constraint_types.h"
55 #include "KX_PythonSeq.h"
56 #include "KX_PythonInit.h"
57 #include "KX_KetsjiEngine.h"
58
59 #include "MT_Matrix4x4.h"
60
61 /** 
62  * Move here pose function for game engine so that we can mix with GE objects
63  * Principle is as follow:
64  * Use Blender structures so that BKE_pose_where_is can be used unchanged
65  * Copy the constraint so that they can be enabled/disabled/added/removed at runtime
66  * Don't copy the constraints for the pose used by the Action actuator, it does not need them.
67  * Scan the constraint structures so that the KX equivalent of target objects are identified and 
68  * stored in separate list.
69  * When it is about to evaluate the pose, set the KX object position in the obmat of the corresponding
70  * Blender objects and restore after the evaluation.
71  */
72 void game_copy_pose(bPose **dst, bPose *src, int copy_constraint)
73 {
74         bPose *out;
75         bPoseChannel *pchan, *outpchan;
76         GHash *ghash;
77         
78         /* the game engine copies the current armature pose and then swaps
79          * the object pose pointer. this makes it possible to change poses
80          * without affecting the original blender data. */
81
82         if (!src) {
83                 *dst=NULL;
84                 return;
85         }
86         else if (*dst==src) {
87                 printf("BKE_pose_copy_data source and target are the same\n");
88                 *dst=NULL;
89                 return;
90         }
91         
92         out= (bPose*)MEM_dupallocN(src);
93         out->chanhash = NULL;
94         out->agroups.first= out->agroups.last= NULL;
95         out->ikdata = NULL;
96         out->ikparam = MEM_dupallocN(out->ikparam);
97         out->flag |= POSE_GAME_ENGINE;
98         BLI_duplicatelist(&out->chanbase, &src->chanbase);
99
100         /* remap pointers */
101         ghash= BLI_ghash_new(BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, "game_copy_pose gh");
102
103         pchan= (bPoseChannel *)src->chanbase.first;
104         outpchan= (bPoseChannel *)out->chanbase.first;
105         for (; pchan; pchan=pchan->next, outpchan=outpchan->next)
106                 BLI_ghash_insert(ghash, pchan, outpchan);
107
108         for (pchan = (bPoseChannel *)out->chanbase.first; pchan; pchan = pchan->next) {
109                 pchan->parent= (bPoseChannel *)BLI_ghash_lookup(ghash, pchan->parent);
110                 pchan->child= (bPoseChannel *)BLI_ghash_lookup(ghash, pchan->child);
111
112                 if (copy_constraint) {
113                         ListBase listb;
114                         // copy all constraint for backward compatibility
115                         // BKE_copy_constraints NULLs listb, no need to make extern for this operation.
116                         BKE_copy_constraints(&listb, &pchan->constraints, FALSE);
117                         pchan->constraints= listb;
118                 } else {
119                         pchan->constraints.first = NULL;
120                         pchan->constraints.last = NULL;
121                 }
122
123                 // fails to link, props are not used in the BGE yet.
124 #if 0
125                 if (pchan->prop)
126                         pchan->prop= IDP_CopyProperty(pchan->prop);
127 #endif
128                 pchan->prop= NULL;
129         }
130
131         BLI_ghash_free(ghash, NULL, NULL);
132         // set acceleration structure for channel lookup
133         BKE_pose_channels_hash_make(out);
134         *dst=out;
135 }
136
137
138
139 /* Only allowed for Poses with identical channels */
140 void game_blend_poses(bPose *dst, bPose *src, float srcweight/*, short mode*/)
141 {
142         short mode= ACTSTRIPMODE_BLEND;
143         
144         bPoseChannel *dchan;
145         const bPoseChannel *schan;
146         bConstraint *dcon, *scon;
147         float dstweight;
148         int i;
149
150         switch (mode) {
151         case ACTSTRIPMODE_BLEND:
152                 dstweight = 1.0F - srcweight;
153                 break;
154         case ACTSTRIPMODE_ADD:
155                 dstweight = 1.0F;
156                 break;
157         default :
158                 dstweight = 1.0F;
159         }
160         
161         schan= (bPoseChannel *)src->chanbase.first;
162         for (dchan = (bPoseChannel *)dst->chanbase.first; dchan; dchan=(bPoseChannel *)dchan->next, schan= (bPoseChannel *)schan->next) {
163                 // always blend on all channels since we don't know which one has been set
164                 /* quat interpolation done separate */
165                 if (schan->rotmode == ROT_MODE_QUAT) {
166                         float dquat[4], squat[4];
167                         
168                         copy_qt_qt(dquat, dchan->quat);
169                         copy_qt_qt(squat, schan->quat);
170                         if (mode==ACTSTRIPMODE_BLEND)
171                                 interp_qt_qtqt(dchan->quat, dquat, squat, srcweight);
172                         else {
173                                 mul_fac_qt_fl(squat, srcweight);
174                                 mul_qt_qtqt(dchan->quat, dquat, squat);
175                         }
176                         
177                         normalize_qt(dchan->quat);
178                 }
179
180                 for (i=0; i<3; i++) {
181                         /* blending for loc and scale are pretty self-explanatory... */
182                         dchan->loc[i] = (dchan->loc[i]*dstweight) + (schan->loc[i]*srcweight);
183                         dchan->size[i] = 1.0f + ((dchan->size[i]-1.0f)*dstweight) + ((schan->size[i]-1.0f)*srcweight);
184                         
185                         /* euler-rotation interpolation done here instead... */
186                         // FIXME: are these results decent?
187                         if (schan->rotmode)
188                                 dchan->eul[i] = (dchan->eul[i]*dstweight) + (schan->eul[i]*srcweight);
189                 }
190                 for (dcon= (bConstraint *)dchan->constraints.first, scon= (bConstraint *)schan->constraints.first;
191                      dcon && scon;
192                      dcon = dcon->next, scon = scon->next)
193                 {
194                         /* no 'add' option for constraint blending */
195                         dcon->enforce= dcon->enforce*(1.0f-srcweight) + scon->enforce*srcweight;
196                 }
197         }
198         
199         /* this pose is now in src time */
200         dst->ctime= src->ctime;
201 }
202
203 void game_free_pose(bPose *pose)
204 {
205         if (pose) {
206                 /* free pose-channels and constraints */
207                 BKE_pose_channels_free(pose);
208                 
209                 /* free IK solver state */
210                 BIK_clear_data(pose);
211
212                 /* free IK solver param */
213                 if (pose->ikparam)
214                         MEM_freeN(pose->ikparam);
215
216                 MEM_freeN(pose);
217         }
218 }
219
220 BL_ArmatureObject::BL_ArmatureObject(
221                                 void* sgReplicationInfo, 
222                                 SG_Callbacks callbacks, 
223                                 Object *armature,
224                                 Scene *scene,
225                                 int vert_deform_type)
226
227 :       KX_GameObject(sgReplicationInfo,callbacks),
228         m_controlledConstraints(),
229         m_poseChannels(),
230         m_objArma(armature),
231         m_framePose(NULL),
232         m_scene(scene), // maybe remove later. needed for BKE_pose_where_is
233         m_lastframe(0.0),
234         m_timestep(0.040),
235         m_activeAct(NULL),
236         m_activePriority(999),
237         m_vert_deform_type(vert_deform_type),
238         m_constraintNumber(0),
239         m_channelNumber(0),
240         m_lastapplyframe(0.0)
241 {
242         m_armature = (bArmature *)armature->data;
243
244         /* we make a copy of blender object's pose, and then always swap it with
245          * the original pose before calling into blender functions, to deal with
246          * replica's or other objects using the same blender object */
247         m_pose = NULL;
248         game_copy_pose(&m_pose, m_objArma->pose, 1);
249         // store the original armature object matrix
250         memcpy(m_obmat, m_objArma->obmat, sizeof(m_obmat));
251 }
252
253 BL_ArmatureObject::~BL_ArmatureObject()
254 {
255         BL_ArmatureConstraint* constraint;
256         while ((constraint = m_controlledConstraints.Remove()) != NULL) {
257                 delete constraint;
258         }
259         BL_ArmatureChannel* channel;
260         while ((channel = static_cast<BL_ArmatureChannel*>(m_poseChannels.Remove())) != NULL) {
261                 delete channel;
262         }
263         if (m_pose)
264                 game_free_pose(m_pose);
265         if (m_framePose)
266                 game_free_pose(m_framePose);
267 }
268
269
270 void BL_ArmatureObject::LoadConstraints(KX_BlenderSceneConverter* converter)
271 {
272         // first delete any existing constraint (should not have any)
273         while (!m_controlledConstraints.Empty()) {
274                 BL_ArmatureConstraint* constraint = m_controlledConstraints.Remove();
275                 delete constraint;
276         }
277         m_constraintNumber = 0;
278
279         // list all the constraint and convert them to BL_ArmatureConstraint
280         // get the persistent pose structure
281         bPoseChannel* pchan;
282         bConstraint* pcon;
283         bConstraintTypeInfo* cti;
284         Object* blendtarget;
285         KX_GameObject* gametarget;
286         KX_GameObject* gamesubtarget;
287
288         // and locate the constraint
289         for (pchan = (bPoseChannel *)m_pose->chanbase.first; pchan; pchan = pchan->next) {
290                 for (pcon = (bConstraint *)pchan->constraints.first; pcon; pcon = pcon->next) {
291                         if (pcon->flag & CONSTRAINT_DISABLE)
292                                 continue;
293                         // which constraint should we support?
294                         switch (pcon->type) {
295                         case CONSTRAINT_TYPE_TRACKTO:
296                         case CONSTRAINT_TYPE_DAMPTRACK:
297                         case CONSTRAINT_TYPE_KINEMATIC:
298                         case CONSTRAINT_TYPE_ROTLIKE:
299                         case CONSTRAINT_TYPE_LOCLIKE:
300                         case CONSTRAINT_TYPE_MINMAX:
301                         case CONSTRAINT_TYPE_SIZELIKE:
302                         case CONSTRAINT_TYPE_LOCKTRACK:
303                         case CONSTRAINT_TYPE_STRETCHTO:
304                         case CONSTRAINT_TYPE_CLAMPTO:
305                         case CONSTRAINT_TYPE_TRANSFORM:
306                         case CONSTRAINT_TYPE_DISTLIMIT:
307                         case CONSTRAINT_TYPE_TRANSLIKE:
308                                 cti = BKE_constraint_get_typeinfo(pcon);
309                                 gametarget = gamesubtarget = NULL;
310                                 if (cti && cti->get_constraint_targets) {
311                                         ListBase listb = { NULL, NULL };
312                                         cti->get_constraint_targets(pcon, &listb);
313                                         if (listb.first) {
314                                                 bConstraintTarget* target = (bConstraintTarget*)listb.first;
315                                                 if (target->tar && target->tar != m_objArma) {
316                                                         // only remember external objects, self target is handled automatically
317                                                         blendtarget = target->tar;
318                                                         gametarget = converter->FindGameObject(blendtarget);
319                                                 }
320                                                 if (target->next != NULL) {
321                                                         // secondary target
322                                                         target = target->next;
323                                                         if (target->tar && target->tar != m_objArma) {
324                                                                 // only track external object
325                                                                 blendtarget = target->tar;
326                                                                 gamesubtarget = converter->FindGameObject(blendtarget);
327                                                         }
328                                                 }
329                                         }
330                                         if (cti->flush_constraint_targets)
331                                                 cti->flush_constraint_targets(pcon, &listb, 1);
332                                 }
333                                 BL_ArmatureConstraint* constraint = new BL_ArmatureConstraint(this, pchan, pcon, gametarget, gamesubtarget);
334                                 m_controlledConstraints.AddBack(constraint);
335                                 m_constraintNumber++;
336                         }
337                 }
338         }
339 }
340
341 BL_ArmatureConstraint* BL_ArmatureObject::GetConstraint(const char* posechannel, const char* constraintname)
342 {
343         SG_DList::iterator<BL_ArmatureConstraint> cit(m_controlledConstraints);
344         for (cit.begin(); !cit.end(); ++cit) {
345                 BL_ArmatureConstraint* constraint = *cit;
346                 if (constraint->Match(posechannel, constraintname))
347                         return constraint;
348         }
349         return NULL;
350 }
351
352 BL_ArmatureConstraint* BL_ArmatureObject::GetConstraint(const char* posechannelconstraint)
353 {
354         // performance: use hash string instead of plain string compare
355         SG_DList::iterator<BL_ArmatureConstraint> cit(m_controlledConstraints);
356         for (cit.begin(); !cit.end(); ++cit) {
357                 BL_ArmatureConstraint* constraint = *cit;
358                 if (!strcmp(constraint->GetName(), posechannelconstraint))
359                         return constraint;
360         }
361         return NULL;
362 }
363
364 BL_ArmatureConstraint* BL_ArmatureObject::GetConstraint(int index)
365 {
366         SG_DList::iterator<BL_ArmatureConstraint> cit(m_controlledConstraints);
367         for (cit.begin(); !cit.end() && index; ++cit, --index);
368         return (cit.end()) ? NULL : *cit;
369 }
370
371 /* this function is called to populate the m_poseChannels list */
372 void BL_ArmatureObject::LoadChannels()
373 {
374         if (m_poseChannels.Empty()) {
375                 bPoseChannel* pchan;
376                 BL_ArmatureChannel* proxy;
377         
378                 m_channelNumber = 0;
379                 for (pchan = (bPoseChannel *)m_pose->chanbase.first; pchan; pchan=(bPoseChannel *)pchan->next) {
380                         proxy = new BL_ArmatureChannel(this, pchan);
381                         m_poseChannels.AddBack(proxy);
382                         m_channelNumber++;
383                 }
384         }
385 }
386
387 BL_ArmatureChannel* BL_ArmatureObject::GetChannel(bPoseChannel* pchan)
388 {
389         LoadChannels();
390         SG_DList::iterator<BL_ArmatureChannel> cit(m_poseChannels);
391         for (cit.begin(); !cit.end(); ++cit) 
392         {
393                 BL_ArmatureChannel* channel = *cit;
394                 if (channel->m_posechannel == pchan)
395                         return channel;
396         }
397         return NULL;
398 }
399
400 BL_ArmatureChannel* BL_ArmatureObject::GetChannel(const char* str)
401 {
402         LoadChannels();
403         SG_DList::iterator<BL_ArmatureChannel> cit(m_poseChannels);
404         for (cit.begin(); !cit.end(); ++cit) 
405         {
406                 BL_ArmatureChannel* channel = *cit;
407                 if (!strcmp(channel->m_posechannel->name, str))
408                         return channel;
409         }
410         return NULL;
411 }
412
413 BL_ArmatureChannel* BL_ArmatureObject::GetChannel(int index)
414 {
415         LoadChannels();
416         if (index < 0 || index >= m_channelNumber)
417                 return NULL;
418         SG_DList::iterator<BL_ArmatureChannel> cit(m_poseChannels);
419         for (cit.begin(); !cit.end() && index; ++cit, --index);
420         return (cit.end()) ? NULL : *cit;
421 }
422
423 CValue* BL_ArmatureObject::GetReplica()
424 {
425         BL_ArmatureObject* replica = new BL_ArmatureObject(*this);
426         replica->ProcessReplica();
427         return replica;
428 }
429
430 void BL_ArmatureObject::ProcessReplica()
431 {
432         bPose *pose= m_pose;
433         KX_GameObject::ProcessReplica();
434
435         m_pose = NULL;
436         m_framePose = NULL;
437         game_copy_pose(&m_pose, pose, 1);
438 }
439
440 void BL_ArmatureObject::ReParentLogic()
441 {
442         SG_DList::iterator<BL_ArmatureConstraint> cit(m_controlledConstraints);
443         for (cit.begin(); !cit.end(); ++cit) {
444                 (*cit)->ReParent(this);
445         }
446         KX_GameObject::ReParentLogic();
447 }
448
449 void BL_ArmatureObject::Relink(CTR_Map<CTR_HashedPtr, void*> *obj_map)
450 {
451         SG_DList::iterator<BL_ArmatureConstraint> cit(m_controlledConstraints);
452         for (cit.begin(); !cit.end(); ++cit) {
453                 (*cit)->Relink(obj_map);
454         }
455         KX_GameObject::Relink(obj_map);
456 }
457
458 bool BL_ArmatureObject::UnlinkObject(SCA_IObject* clientobj)
459 {
460         // clientobj is being deleted, make sure we don't hold any reference to it
461         bool res = false;
462         SG_DList::iterator<BL_ArmatureConstraint> cit(m_controlledConstraints);
463         for (cit.begin(); !cit.end(); ++cit) {
464                 res |= (*cit)->UnlinkObject(clientobj);
465         }
466         return res;
467 }
468
469 void BL_ArmatureObject::ApplyPose()
470 {
471         m_armpose = m_objArma->pose;
472         m_objArma->pose = m_pose;
473         // in the GE, we use ctime to store the timestep
474         m_pose->ctime = (float)m_timestep;
475         //m_scene->r.cfra++;
476         if (m_lastapplyframe != m_lastframe) {
477                 // update the constraint if any, first put them all off so that only the active ones will be updated
478                 SG_DList::iterator<BL_ArmatureConstraint> cit(m_controlledConstraints);
479                 for (cit.begin(); !cit.end(); ++cit) {
480                         (*cit)->UpdateTarget();
481                 }
482                 // update ourself
483                 UpdateBlenderObjectMatrix(m_objArma);
484                 BKE_pose_where_is(m_scene, m_objArma); // XXX
485                 // restore ourself
486                 memcpy(m_objArma->obmat, m_obmat, sizeof(m_obmat));
487                 // restore active targets
488                 for (cit.begin(); !cit.end(); ++cit) {
489                         (*cit)->RestoreTarget();
490                 }
491                 m_lastapplyframe = m_lastframe;
492         }
493 }
494
495 void BL_ArmatureObject::RestorePose()
496 {
497         m_objArma->pose = m_armpose;
498         m_armpose = NULL;
499 }
500
501 void BL_ArmatureObject::SetPose(bPose *pose)
502 {
503         extract_pose_from_pose(m_pose, pose);
504         m_lastapplyframe = -1.0;
505 }
506
507 bool BL_ArmatureObject::SetActiveAction(BL_ActionActuator *act, short priority, double curtime)
508 {
509         if (curtime != m_lastframe) {
510                 m_activePriority = 9999;
511                 // compute the timestep for the underlying IK algorithm
512                 m_timestep = curtime-m_lastframe;
513                 m_lastframe= curtime;
514                 m_activeAct = NULL;
515                 // remember the pose at the start of the frame
516                 GetPose(&m_framePose);
517         }
518
519         if (act) 
520         {
521                 if (priority<=m_activePriority)
522                 {
523                         if (priority<m_activePriority) {
524                                 // this action overwrites the previous ones, start from initial pose to cancel their effects
525                                 SetPose(m_framePose);
526                                 if (m_activeAct && (m_activeAct!=act))
527                                         /* Reset the blend timer since this new action cancels the old one */
528                                         m_activeAct->SetBlendTime(0.0);
529                         }
530                         m_activeAct = act;
531                         m_activePriority = priority;
532                         m_lastframe = curtime;
533                 
534                         return true;
535                 }
536                 else {
537                         act->SetBlendTime(0.0);
538                         return false;
539                 }
540         }
541         return false;
542 }
543
544 BL_ActionActuator * BL_ArmatureObject::GetActiveAction()
545 {
546         return m_activeAct;
547 }
548
549 void BL_ArmatureObject::GetPose(bPose **pose)
550 {
551         /* If the caller supplies a null pose, create a new one. */
552         /* Otherwise, copy the armature's pose channels into the caller-supplied pose */
553                 
554         if (!*pose) {
555                 /* probably not to good of an idea to
556                  * duplicate everything, but it clears up 
557                  * a crash and memory leakage when 
558                  * &BL_ActionActuator::m_pose is freed
559                  */
560                 game_copy_pose(pose, m_pose, 0);
561         }
562         else {
563                 if (*pose == m_pose)
564                         // no need to copy if the pointers are the same
565                         return;
566
567                 extract_pose_from_pose(*pose, m_pose);
568         }
569 }
570
571 void BL_ArmatureObject::GetMRDPose(bPose **pose)
572 {
573         /* If the caller supplies a null pose, create a new one. */
574         /* Otherwise, copy the armature's pose channels into the caller-supplied pose */
575
576         if (!*pose)
577                 game_copy_pose(pose, m_pose, 0);
578         else
579                 extract_pose_from_pose(*pose, m_pose);
580 }
581
582 short BL_ArmatureObject::GetActivePriority()
583 {
584         return m_activePriority;
585 }
586
587 double BL_ArmatureObject::GetLastFrame()
588 {
589         return m_lastframe;
590 }
591
592 bool BL_ArmatureObject::GetBoneMatrix(Bone* bone, MT_Matrix4x4& matrix)
593 {
594         bPoseChannel *pchan;
595
596         ApplyPose();
597         pchan = BKE_pose_channel_find_name(m_objArma->pose, bone->name);
598         if (pchan)
599                 matrix.setValue(&pchan->pose_mat[0][0]);
600         RestorePose();
601
602         return (pchan != NULL);
603 }
604
605 float BL_ArmatureObject::GetBoneLength(Bone* bone) const
606 {
607         return (float)(MT_Point3(bone->head) - MT_Point3(bone->tail)).length();
608 }
609
610 #ifdef WITH_PYTHON
611
612 // PYTHON
613
614 PyTypeObject BL_ArmatureObject::Type = {
615         PyVarObject_HEAD_INIT(NULL, 0)
616         "BL_ArmatureObject",
617         sizeof(PyObjectPlus_Proxy),
618         0,
619         py_base_dealloc,
620         0,
621         0,
622         0,
623         0,
624         py_base_repr,
625         0,
626         &KX_GameObject::Sequence,
627         &KX_GameObject::Mapping,
628         0,0,0,
629         NULL,
630         NULL,
631         0,
632         Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
633         0,0,0,0,0,0,0,
634         Methods,
635         0,
636         0,
637         &KX_GameObject::Type,
638         0,0,0,0,0,0,
639         py_base_new
640 };
641
642 PyMethodDef BL_ArmatureObject::Methods[] = {
643         KX_PYMETHODTABLE_NOARGS(BL_ArmatureObject, update),
644         {NULL,NULL} //Sentinel
645 };
646
647 PyAttributeDef BL_ArmatureObject::Attributes[] = {
648
649         KX_PYATTRIBUTE_RO_FUNCTION("constraints",               BL_ArmatureObject, pyattr_get_constraints),
650         KX_PYATTRIBUTE_RO_FUNCTION("channels",          BL_ArmatureObject, pyattr_get_channels),
651         {NULL} //Sentinel
652 };
653
654 PyObject *BL_ArmatureObject::pyattr_get_constraints(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef)
655 {
656         return KX_PythonSeq_CreatePyObject((static_cast<BL_ArmatureObject*>(self_v))->m_proxy, KX_PYGENSEQ_OB_TYPE_CONSTRAINTS);
657 }
658
659 PyObject *BL_ArmatureObject::pyattr_get_channels(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef)
660 {
661         BL_ArmatureObject* self = static_cast<BL_ArmatureObject*>(self_v);
662         self->LoadChannels(); // make sure we have the channels
663         return KX_PythonSeq_CreatePyObject((static_cast<BL_ArmatureObject*>(self_v))->m_proxy, KX_PYGENSEQ_OB_TYPE_CHANNELS);
664 }
665
666 KX_PYMETHODDEF_DOC_NOARGS(BL_ArmatureObject, update, 
667                                                   "update()\n"
668                                                   "Make sure that the armature will be updated on next graphic frame.\n"
669                                                   "This is automatically done if a KX_ArmatureActuator with mode run is active\n"
670                                                   "or if an action is playing. This function is useful in other cases.\n")
671 {
672         SetActiveAction(NULL, 0, KX_GetActiveEngine()->GetFrameTime());
673         Py_RETURN_NONE;
674 }
675
676 #endif // WITH_PYTHON