bab692f9daf75fec08c574ee5dcb171fa5df19ef
[blender-staging.git] / release / scripts / io / import_anim_bvh.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8 compliant>
20
21 import math
22 from math import radians
23
24 import bpy
25 import mathutils
26 from mathutils import Vector, Euler, Matrix, RotationMatrix, TranslationMatrix
27
28
29 class bvh_node_class(object):
30     __slots__ = (
31     'name',# bvh joint name
32     'parent',# bvh_node_class type or None for no parent
33     'children',# a list of children of this type.
34     'rest_head_world',# worldspace rest location for the head of this node
35     'rest_head_local',# localspace rest location for the head of this node
36     'rest_tail_world',# # worldspace rest location for the tail of this node
37     'rest_tail_local',# # worldspace rest location for the tail of this node
38     'channels',# list of 6 ints, -1 for an unused channel, otherwise an index for the BVH motion data lines, lock triple then rot triple
39     'rot_order',# a triple of indicies as to the order rotation is applied. [0,1,2] is x/y/z - [None, None, None] if no rotation.
40     'anim_data',# a list one tuple's one for each frame. (locx, locy, locz, rotx, roty, rotz)
41     'has_loc',# Conveinience function, bool, same as (channels[0]!=-1 or channels[1]!=-1 channels[2]!=-1)
42     'has_rot',# Conveinience function, bool, same as (channels[3]!=-1 or channels[4]!=-1 channels[5]!=-1)
43     'temp')# use this for whatever you want
44
45     def __init__(self, name, rest_head_world, rest_head_local, parent, channels, rot_order):
46         self.name = name
47         self.rest_head_world = rest_head_world
48         self.rest_head_local = rest_head_local
49         self.rest_tail_world = None
50         self.rest_tail_local = None
51         self.parent = parent
52         self.channels = channels
53         self.rot_order = rot_order
54
55         # convenience functions
56         self.has_loc = channels[0] != -1 or channels[1] != -1 or channels[2] != -1
57         self.has_rot = channels[3] != -1 or channels[4] != -1 or channels[5] != -1
58
59
60         self.children = []
61
62         # list of 6 length tuples: (lx,ly,lz, rx,ry,rz)
63         # even if the channels arnt used they will just be zero
64         #
65         self.anim_data = [(0, 0, 0, 0, 0, 0)]
66
67     def __repr__(self):
68         return 'BVH name:"%s", rest_loc:(%.3f,%.3f,%.3f), rest_tail:(%.3f,%.3f,%.3f)' %\
69         (self.name,\
70         self.rest_head_world.x, self.rest_head_world.y, self.rest_head_world.z,\
71         self.rest_head_world.x, self.rest_head_world.y, self.rest_head_world.z)
72
73
74 # Change the order rotation is applied.
75 MATRIX_IDENTITY_3x3 = Matrix([1, 0, 0], [0, 1, 0], [0, 0, 1])
76 MATRIX_IDENTITY_4x4 = Matrix([1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1])
77
78
79 def eulerRotate(x, y, z, rot_order):
80     # Clamp all values between 0 and 360, values outside this raise an error.
81     mats = [RotationMatrix(x, 3, 'X'), RotationMatrix(y, 3, 'Y'), RotationMatrix(z, 3, 'Z')]
82     return (MATRIX_IDENTITY_3x3 * mats[rot_order[0]] * (mats[rot_order[1]] * (mats[rot_order[2]]))).to_euler()
83
84     # Should work but doesnt!
85     '''
86     eul = Euler((x, y, z))
87     eul.order = "XYZ"[rot_order[0]] + "XYZ"[rot_order[1]] + "XYZ"[rot_order[2]]
88     return tuple(eul.to_matrix().to_euler())
89     '''
90
91
92 def read_bvh(context, file_path, ROT_MODE='XYZ', GLOBAL_SCALE=1.0):
93     # File loading stuff
94     # Open the file for importing
95     file = open(file_path, 'rU')
96
97     # Seperate into a list of lists, each line a list of words.
98     file_lines = file.readlines()
99     # Non standard carrage returns?
100     if len(file_lines) == 1:
101         file_lines = file_lines[0].split('\r')
102
103     # Split by whitespace.
104     file_lines = [ll for ll in [l.split() for l in file_lines] if ll]
105
106
107     # Create Hirachy as empties
108
109     if file_lines[0][0].lower() == 'hierarchy':
110         #print 'Importing the BVH Hierarchy for:', file_path
111         pass
112     else:
113         raise 'ERROR: This is not a BVH file'
114
115     bvh_nodes = {None: None}
116     bvh_nodes_serial = [None]
117
118     channelIndex = -1
119
120
121     lineIdx = 0 # An index for the file.
122     while lineIdx < len(file_lines) -1:
123         #...
124         if file_lines[lineIdx][0].lower() == 'root' or file_lines[lineIdx][0].lower() == 'joint':
125
126             # Join spaces into 1 word with underscores joining it.
127             if len(file_lines[lineIdx]) > 2:
128                 file_lines[lineIdx][1] = '_'.join(file_lines[lineIdx][1:])
129                 file_lines[lineIdx] = file_lines[lineIdx][:2]
130
131             # MAY NEED TO SUPPORT MULTIPLE ROOT's HERE!!!, Still unsure weather multiple roots are possible.??
132
133             # Make sure the names are unique- Object names will match joint names exactly and both will be unique.
134             name = file_lines[lineIdx][1]
135
136             #print '%snode: %s, parent: %s' % (len(bvh_nodes_serial) * '  ', name,  bvh_nodes_serial[-1])
137
138             lineIdx += 2 # Incriment to the next line (Offset)
139             rest_head_local = Vector((float(file_lines[lineIdx][1]), float(file_lines[lineIdx][2]), float(file_lines[lineIdx][3]))) * GLOBAL_SCALE
140             lineIdx += 1 # Incriment to the next line (Channels)
141
142             # newChannel[Xposition, Yposition, Zposition, Xrotation, Yrotation, Zrotation]
143             # newChannel references indecies to the motiondata,
144             # if not assigned then -1 refers to the last value that will be added on loading at a value of zero, this is appended
145             # We'll add a zero value onto the end of the MotionDATA so this is always refers to a value.
146             my_channel = [-1, -1, -1, -1, -1, -1]
147             my_rot_order = [None, None, None]
148             rot_count = 0
149             for channel in file_lines[lineIdx][2:]:
150                 channel = channel.lower()
151                 channelIndex += 1 # So the index points to the right channel
152                 if channel == 'xposition':
153                     my_channel[0] = channelIndex
154                 elif channel == 'yposition':
155                     my_channel[1] = channelIndex
156                 elif channel == 'zposition':
157                     my_channel[2] = channelIndex
158
159                 elif channel == 'xrotation':
160                     my_channel[3] = channelIndex
161                     my_rot_order[rot_count] = 0
162                     rot_count += 1
163                 elif channel == 'yrotation':
164                     my_channel[4] = channelIndex
165                     my_rot_order[rot_count] = 1
166                     rot_count += 1
167                 elif channel == 'zrotation':
168                     my_channel[5] = channelIndex
169                     my_rot_order[rot_count] = 2
170                     rot_count += 1
171
172             channels = file_lines[lineIdx][2:]
173
174             my_parent = bvh_nodes_serial[-1] # account for none
175
176
177             # Apply the parents offset accumletivly
178             if my_parent == None:
179                 rest_head_world = Vector(rest_head_local)
180             else:
181                 rest_head_world = my_parent.rest_head_world + rest_head_local
182
183             bvh_node = bvh_nodes[name] = bvh_node_class(name, rest_head_world, rest_head_local, my_parent, my_channel, my_rot_order)
184
185             # If we have another child then we can call ourselves a parent, else
186             bvh_nodes_serial.append(bvh_node)
187
188         # Account for an end node
189         if file_lines[lineIdx][0].lower() == 'end' and file_lines[lineIdx][1].lower() == 'site': # There is somtimes a name after 'End Site' but we will ignore it.
190             lineIdx += 2 # Incriment to the next line (Offset)
191             rest_tail = Vector((float(file_lines[lineIdx][1]), float(file_lines[lineIdx][2]), float(file_lines[lineIdx][3]))) * GLOBAL_SCALE
192
193             bvh_nodes_serial[-1].rest_tail_world = bvh_nodes_serial[-1].rest_head_world + rest_tail
194             bvh_nodes_serial[-1].rest_tail_local = bvh_nodes_serial[-1].rest_head_local + rest_tail
195
196
197             # Just so we can remove the Parents in a uniform way- End end never has kids
198             # so this is a placeholder
199             bvh_nodes_serial.append(None)
200
201         if len(file_lines[lineIdx]) == 1 and file_lines[lineIdx][0] == '}': # == ['}']
202             bvh_nodes_serial.pop() # Remove the last item
203
204         if len(file_lines[lineIdx]) == 1 and file_lines[lineIdx][0].lower() == 'motion':
205             #print '\nImporting motion data'
206             lineIdx += 3 # Set the cursor to the first frame
207             break
208
209         lineIdx += 1
210
211
212     # Remove the None value used for easy parent reference
213     del bvh_nodes[None]
214     # Dont use anymore
215     del bvh_nodes_serial
216
217     bvh_nodes_list = bvh_nodes.values()
218
219     while lineIdx < len(file_lines):
220         line = file_lines[lineIdx]
221         for bvh_node in bvh_nodes_list:
222             #for bvh_node in bvh_nodes_serial:
223             lx = ly = lz = rx = ry = rz = 0.0
224             channels = bvh_node.channels
225             anim_data = bvh_node.anim_data
226             if channels[0] != -1:
227                 lx = GLOBAL_SCALE * float(line[channels[0]])
228
229             if channels[1] != -1:
230                 ly = GLOBAL_SCALE * float(line[channels[1]])
231
232             if channels[2] != -1:
233                 lz = GLOBAL_SCALE * float(line[channels[2]])
234
235             if channels[3] != -1 or channels[4] != -1 or channels[5] != -1:
236                 rx, ry, rz = float(line[channels[3]]), float(line[channels[4]]), float(line[channels[5]])
237
238                 if ROT_MODE != 'NATIVE':
239                     rx, ry, rz = eulerRotate(radians(rx), radians(ry), radians(rz), bvh_node.rot_order)
240                 else:
241                     rx, ry, rz = radians(rx), radians(ry), radians(rz)
242
243             # Done importing motion data #
244             anim_data.append((lx, ly, lz, rx, ry, rz))
245         lineIdx += 1
246
247     # Assign children
248     for bvh_node in bvh_nodes.values():
249         bvh_node_parent = bvh_node.parent
250         if bvh_node_parent:
251             bvh_node_parent.children.append(bvh_node)
252
253     # Now set the tip of each bvh_node
254     for bvh_node in bvh_nodes.values():
255
256         if not bvh_node.rest_tail_world:
257             if len(bvh_node.children) == 0:
258                 # could just fail here, but rare BVH files have childless nodes
259                 bvh_node.rest_tail_world = Vector(bvh_node.rest_head_world)
260                 bvh_node.rest_tail_local = Vector(bvh_node.rest_head_local)
261             elif len(bvh_node.children) == 1:
262                 bvh_node.rest_tail_world = Vector(bvh_node.children[0].rest_head_world)
263                 bvh_node.rest_tail_local = bvh_node.rest_head_local + bvh_node.children[0].rest_head_local
264             else:
265                 # allow this, see above
266                 #if not bvh_node.children:
267                 #       raise 'error, bvh node has no end and no children. bad file'
268
269                 # Removed temp for now
270                 rest_tail_world = Vector((0.0, 0.0, 0.0))
271                 rest_tail_local = Vector((0.0, 0.0, 0.0))
272                 for bvh_node_child in bvh_node.children:
273                     rest_tail_world += bvh_node_child.rest_head_world
274                     rest_tail_local += bvh_node_child.rest_head_local
275
276                 bvh_node.rest_tail_world = rest_tail_world * (1.0 / len(bvh_node.children))
277                 bvh_node.rest_tail_local = rest_tail_local * (1.0 / len(bvh_node.children))
278
279         # Make sure tail isnt the same location as the head.
280         if (bvh_node.rest_tail_local - bvh_node.rest_head_local).length <= 0.001 * GLOBAL_SCALE:
281             bvh_node.rest_tail_local.y = bvh_node.rest_tail_local.y + GLOBAL_SCALE / 10
282             bvh_node.rest_tail_world.y = bvh_node.rest_tail_world.y + GLOBAL_SCALE / 10
283
284     return bvh_nodes
285
286
287 def bvh_node_dict2objects(context, bvh_nodes, IMPORT_START_FRAME=1, IMPORT_LOOP=False):
288
289     if IMPORT_START_FRAME < 1:
290         IMPORT_START_FRAME = 1
291
292     scn = context.scene
293     scn.objects.selected = []
294
295     objects = []
296
297     def add_ob(name):
298         ob = scn.objects.new('Empty', None)
299         objects.append(ob)
300         return ob
301
302     # Add objects
303     for name, bvh_node in bvh_nodes.items():
304         bvh_node.temp = add_ob(name)
305
306     # Parent the objects
307     for bvh_node in bvh_nodes.values():
308         bvh_node.temp.makeParent([bvh_node_child.temp for bvh_node_child in bvh_node.children], 1, 0) # ojbs, noninverse, 1 = not fast.
309
310     # Offset
311     for bvh_node in bvh_nodes.values():
312         # Make relative to parents offset
313         bvh_node.temp.loc = bvh_node.rest_head_local
314
315     # Add tail objects
316     for name, bvh_node in bvh_nodes.items():
317         if not bvh_node.children:
318             ob_end = add_ob(name + '_end')
319             bvh_node.temp.makeParent([ob_end], 1, 0) # ojbs, noninverse, 1 = not fast.
320             ob_end.loc = bvh_node.rest_tail_local
321
322
323     # Animate the data, the last used bvh_node will do since they all have the same number of frames
324     for frame_current in range(len(bvh_node.anim_data)):
325         Blender.Set('curframe', frame_current + IMPORT_START_FRAME)
326
327         for bvh_node in bvh_nodes.values():
328             lx, ly, lz, rx, ry, rz = bvh_node.anim_data[frame_current]
329
330             rest_head_local = bvh_node.rest_head_local
331             bvh_node.temp.loc = rest_head_local + Vector((lx, ly, lz))
332
333             bvh_node.temp.rot = rx, ry, rz
334
335             bvh_node.temp.insertIpoKey(Blender.Object.IpoKeyTypes.LOCROT) # XXX invalid
336
337     scn.update(1)
338     return objects
339
340
341 def bvh_node_dict2armature(context, bvh_nodes, ROT_MODE='XYZ', IMPORT_START_FRAME=1, IMPORT_LOOP=False):
342
343     if IMPORT_START_FRAME < 1:
344         IMPORT_START_FRAME = 1
345
346     # Add the new armature,
347     scn = context.scene
348 #XXX    scn.objects.selected = []
349     for ob in scn.objects:
350         ob.selected = False
351
352     scn.set_frame(IMPORT_START_FRAME)
353
354     arm_data = bpy.data.armatures.new("MyBVH")
355     arm_ob = bpy.data.objects.new("MyBVH", arm_data)
356
357     scn.objects.link(arm_ob)
358
359     arm_ob.selected = True
360     scn.objects.active = arm_ob
361     print(scn.objects.active)
362
363     bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
364     bpy.ops.object.mode_set(mode='EDIT', toggle=False)
365
366
367     # Get the average bone length for zero length bones, we may not use this.
368     average_bone_length = 0.0
369     nonzero_count = 0
370     for bvh_node in bvh_nodes.values():
371         l = (bvh_node.rest_head_local - bvh_node.rest_tail_local).length
372         if l:
373             average_bone_length += l
374             nonzero_count += 1
375
376     # Very rare cases all bones couldbe zero length???
377     if not average_bone_length:
378         average_bone_length = 0.1
379     else:
380         # Normal operation
381         average_bone_length = average_bone_length / nonzero_count
382
383
384 #XXX - sloppy operator code
385
386     bpy.ops.armature.delete()
387     bpy.ops.armature.select_all()
388     bpy.ops.armature.delete()
389
390     ZERO_AREA_BONES = []
391     for name, bvh_node in bvh_nodes.items():
392         # New editbone
393         bpy.ops.armature.bone_primitive_add(name="Bone")
394
395         bone = bvh_node.temp = arm_data.edit_bones[-1]
396
397         bone.name = name
398 #               arm_data.bones[name]= bone
399
400         bone.head = bvh_node.rest_head_world
401         bone.tail = bvh_node.rest_tail_world
402
403         # ZERO AREA BONES.
404         if (bone.head - bone.tail).length < 0.001:
405             if bvh_node.parent:
406                 ofs = bvh_node.parent.rest_head_local - bvh_node.parent.rest_tail_local
407                 if ofs.length: # is our parent zero length also?? unlikely
408                     bone.tail = bone.tail + ofs
409                 else:
410                     bone.tail.y = bone.tail.y + average_bone_length
411             else:
412                 bone.tail.y = bone.tail.y + average_bone_length
413
414             ZERO_AREA_BONES.append(bone.name)
415
416
417     for bvh_node in bvh_nodes.values():
418         if bvh_node.parent:
419             # bvh_node.temp is the Editbone
420
421             # Set the bone parent
422             bvh_node.temp.parent = bvh_node.parent.temp
423
424             # Set the connection state
425             if not bvh_node.has_loc and\
426             bvh_node.parent and\
427             bvh_node.parent.temp.name not in ZERO_AREA_BONES and\
428             bvh_node.parent.rest_tail_local == bvh_node.rest_head_local:
429                 bvh_node.temp.connected = True
430
431     # Replace the editbone with the editbone name,
432     # to avoid memory errors accessing the editbone outside editmode
433     for bvh_node in bvh_nodes.values():
434         bvh_node.temp = bvh_node.temp.name
435
436 #XXX    arm_data.update()
437
438     # Now Apply the animation to the armature
439
440     # Get armature animation data
441     bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
442     bpy.ops.object.mode_set(mode='POSE', toggle=False)
443
444     pose = arm_ob.pose
445     pose_bones = pose.bones
446
447     if ROT_MODE == 'NATIVE':
448         eul_order_lookup = {\
449             (0, 1, 2): 'XYZ',
450             (0, 2, 1): 'XZY',
451             (1, 0, 2): 'YXZ',
452             (1, 2, 0): 'YZX',
453             (2, 0, 1): 'ZXY',
454             (2, 1, 0): 'ZYX'}
455
456         for bvh_node in bvh_nodes.values():
457             bone_name = bvh_node.temp # may not be the same name as the bvh_node, could have been shortened.
458             pose_bone = pose_bones[bone_name]
459             pose_bone.rotation_mode = eul_order_lookup[tuple(bvh_node.rot_order)]
460
461     elif ROT_MODE == 'XYZ':
462         print(2)
463         for pose_bone in pose_bones:
464             pose_bone.rotation_mode = 'XYZ'
465     else:
466         # Quats default
467         print(3)
468         pass
469
470     context.scene.update()
471
472     bpy.ops.pose.select_all() # set
473     bpy.ops.anim.keyframe_insert_menu(type=-4) # XXX -     -4 ???
474
475
476 #XXX    action = Blender.Armature.NLA.NewAction("Action")
477 #XXX    action.setActive(arm_ob)
478
479     #bpy.ops.action.new()
480     #action = bpy.data.actions[-1]
481
482     # arm_ob.animation_data.action = action
483     action = arm_ob.animation_data.action
484
485     # Replace the bvh_node.temp (currently an editbone)
486     # With a tuple  (pose_bone, armature_bone, bone_rest_matrix, bone_rest_matrix_inv)
487     for bvh_node in bvh_nodes.values():
488         bone_name = bvh_node.temp # may not be the same name as the bvh_node, could have been shortened.
489         pose_bone = pose_bones[bone_name]
490         rest_bone = arm_data.bones[bone_name]
491         bone_rest_matrix = rest_bone.matrix_local.rotation_part()
492
493
494         bone_rest_matrix_inv = Matrix(bone_rest_matrix)
495         bone_rest_matrix_inv.invert()
496
497         bone_rest_matrix_inv.resize4x4()
498         bone_rest_matrix.resize4x4()
499         bvh_node.temp = (pose_bone, bone, bone_rest_matrix, bone_rest_matrix_inv)
500
501
502     # Make a dict for fast access without rebuilding a list all the time.
503
504     # KEYFRAME METHOD, SLOW, USE IPOS DIRECT
505     # TODO: use f-point samples instead (Aligorith)
506
507     if ROT_MODE != 'QUATERNION':
508         prev_euler = [Euler() for i in range(len(bvh_nodes))]
509
510     # Animate the data, the last used bvh_node will do since they all have the same number of frames
511     for frame_current in range(len(bvh_node.anim_data)-1): # skip the first frame (rest frame)
512         # print frame_current
513
514         # if frame_current==40: # debugging
515         #       break
516
517         # Dont neet to set the current frame
518         for i, bvh_node in enumerate(bvh_nodes.values()):
519             pose_bone, bone, bone_rest_matrix, bone_rest_matrix_inv = bvh_node.temp
520             lx, ly, lz, rx, ry, rz = bvh_node.anim_data[frame_current + 1]
521
522             if bvh_node.has_rot:
523                 bone_rotation_matrix = Euler(rx, ry, rz).to_matrix().resize4x4()
524                 bone_rotation_matrix = bone_rest_matrix_inv * bone_rotation_matrix * bone_rest_matrix
525
526                 if ROT_MODE == 'QUATERNION':
527                     pose_bone.rotation_quaternion = bone_rotation_matrix.to_quat()
528                 else:
529                     euler = bone_rotation_matrix.to_euler('XYZ', prev_euler[i]) # pose_bone.rotation_mode # TODO, XYZ default for now
530                     pose_bone.rotation_euler = euler
531                     prev_euler[i] = euler
532
533             if bvh_node.has_loc:
534                 pose_bone.location = (bone_rest_matrix_inv * TranslationMatrix(Vector((lx, ly, lz)) - bvh_node.rest_head_local)).translation_part()
535
536             if bvh_node.has_loc:
537                 pose_bone.keyframe_insert("location")
538             if bvh_node.has_rot:
539                 if ROT_MODE == 'QUATERNION':
540                     pose_bone.keyframe_insert("rotation_quaternion")
541                 else:
542                     pose_bone.keyframe_insert("rotation_euler")
543
544
545         # bpy.ops.anim.keyframe_insert_menu(type=-4) # XXX -     -4 ???
546         bpy.ops.screen.frame_offset(delta=1)
547
548     for cu in action.fcurves:
549         if IMPORT_LOOP:
550             pass # 2.5 doenst have cyclic now?
551
552         for bez in cu.keyframe_points:
553             bez.interpolation = 'LINEAR'
554
555     return arm_ob
556
557
558 from bpy.props import *
559
560
561 class BvhImporter(bpy.types.Operator):
562     '''Load a OBJ Motion Capture File'''
563     bl_idname = "import_anim.bvh"
564     bl_label = "Import BVH"
565
566     path = StringProperty(name="File Path", description="File path used for importing the OBJ file", maxlen=1024, default="")
567     scale = FloatProperty(name="Scale", description="Scale the BVH by this value", min=0.0001, max=1000000.0, soft_min=0.001, soft_max=100.0, default=0.1)
568     frame_start = IntProperty(name="Start Frame", description="Starting frame for the animation", default=1)
569     loop = BoolProperty(name="Loop", description="Loop the animation playback", default=False)
570     rotate_mode = EnumProperty(items=(
571             ('QUATERNION', "Quaternion", "Convert rotations to quaternions"),
572             # ('NATIVE', "Euler (Native)", "Use the rotation order defined in the BVH file"),
573             ('XYZ', "Euler (XYZ)", "Convert rotations to euler XYZ"),
574             # ('XZY', "Euler (XZY)", "Convert rotations to euler XZY"),
575             # ('YXZ', "Euler (YXZ)", "Convert rotations to euler YXZ"),
576             # ('YZX', "Euler (YZX)", "Convert rotations to euler YZX"),
577             # ('ZXY', "Euler (ZXY)", "Convert rotations to euler ZXY"),
578             # ('ZYX', "Euler (ZYX)", "Convert rotations to euler ZYX")),
579             ),
580                 name="Rotation",
581                 description="Rotation conversion.",
582                 default='QUATERNION')
583
584     def execute(self, context):
585         # print("Selected: " + context.active_object.name)
586         import time
587         t1 = time.time()
588         print('\tparsing bvh...', end="")
589
590         bvh_nodes = read_bvh(context, self.properties.path,
591                 ROT_MODE=self.properties.rotate_mode,
592                 GLOBAL_SCALE=self.properties.scale)
593
594         print('%.4f' % (time.time() - t1))
595         t1 = time.time()
596         print('\timporting to blender...', end="")
597
598         bvh_node_dict2armature(context, bvh_nodes,
599                 ROT_MODE=self.properties.rotate_mode,
600                 IMPORT_START_FRAME=self.properties.frame_start,
601                 IMPORT_LOOP=self.properties.loop)
602
603         print('Done in %.4f\n' % (time.time() - t1))
604         return {'FINISHED'}
605
606     def invoke(self, context, event):
607         wm = context.manager
608         wm.add_fileselect(self)
609         return {'RUNNING_MODAL'}
610
611
612 def menu_func(self, context):
613     self.layout.operator(BvhImporter.bl_idname, text="Motion Capture (.bvh)")
614
615
616 def register():
617     bpy.types.register(BvhImporter)
618     bpy.types.INFO_MT_file_import.append(menu_func)
619
620
621 def unregister():
622     bpy.types.unregister(BvhImporter)
623     bpy.types.INFO_MT_file_import.remove(menu_func)
624
625 if __name__ == "__main__":
626     register()