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