Cleanup: BVH import/export
[blender-addons.git] / io_anim_bvh / import_bvh.py
bloba12ef0cc1d590de93466141ddce70bb38e7075e9
1 # ##### BEGIN GPL LICENSE BLOCK #####
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.
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.
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.
17 # ##### END GPL LICENSE BLOCK #####
19 # <pep8 compliant>
21 # Script copyright (C) Campbell Barton
23 from math import radians, ceil
25 import bpy
26 from mathutils import Vector, Euler, Matrix
29 class BVH_Node:
30 __slots__ = (
31 # Bvh joint name.
32 'name',
33 # BVH_Node type or None for no parent.
34 'parent',
35 # A list of children of this type..
36 'children',
37 # Worldspace rest location for the head of this node.
38 'rest_head_world',
39 # Localspace rest location for the head of this node.
40 'rest_head_local',
41 # Worldspace rest location for the tail of this node.
42 'rest_tail_world',
43 # Worldspace rest location for the tail of this node.
44 'rest_tail_local',
45 # List of 6 ints, -1 for an unused channel,
46 # otherwise an index for the BVH motion data lines,
47 # loc triple then rot triple.
48 'channels',
49 # A triple of indices as to the order rotation is applied.
50 # [0,1,2] is x/y/z - [None, None, None] if no rotation..
51 'rot_order',
52 # Same as above but a string 'XYZ' format..
53 'rot_order_str',
54 # A list one tuple's one for each frame: (locx, locy, locz, rotx, roty, rotz),
55 # euler rotation ALWAYS stored xyz order, even when native used.
56 'anim_data',
57 # Convenience function, bool, same as: (channels[0] != -1 or channels[1] != -1 or channels[2] != -1).
58 'has_loc',
59 # Convenience function, bool, same as: (channels[3] != -1 or channels[4] != -1 or channels[5] != -1).
60 'has_rot',
61 # Index from the file, not strictly needed but nice to maintain order.
62 'index',
63 # Use this for whatever you want.
64 'temp',
67 _eul_order_lookup = {
68 (None, None, None): 'XYZ', # XXX Dummy one, no rotation anyway!
69 (0, 1, 2): 'XYZ',
70 (0, 2, 1): 'XZY',
71 (1, 0, 2): 'YXZ',
72 (1, 2, 0): 'YZX',
73 (2, 0, 1): 'ZXY',
74 (2, 1, 0): 'ZYX',
77 def __init__(self, name, rest_head_world, rest_head_local, parent, channels, rot_order, index):
78 self.name = name
79 self.rest_head_world = rest_head_world
80 self.rest_head_local = rest_head_local
81 self.rest_tail_world = None
82 self.rest_tail_local = None
83 self.parent = parent
84 self.channels = channels
85 self.rot_order = tuple(rot_order)
86 self.rot_order_str = BVH_Node._eul_order_lookup[self.rot_order]
87 self.index = index
89 # convenience functions
90 self.has_loc = channels[0] != -1 or channels[1] != -1 or channels[2] != -1
91 self.has_rot = channels[3] != -1 or channels[4] != -1 or channels[5] != -1
93 self.children = []
95 # List of 6 length tuples: (lx, ly, lz, rx, ry, rz)
96 # even if the channels aren't used they will just be zero.
97 self.anim_data = [(0, 0, 0, 0, 0, 0)]
99 def __repr__(self):
100 return (
101 "BVH name: '%s', rest_loc:(%.3f,%.3f,%.3f), rest_tail:(%.3f,%.3f,%.3f)" % (
102 self.name,
103 *self.rest_head_world,
104 *self.rest_head_world,
109 def sorted_nodes(bvh_nodes):
110 bvh_nodes_list = list(bvh_nodes.values())
111 bvh_nodes_list.sort(key=lambda bvh_node: bvh_node.index)
112 return bvh_nodes_list
115 def read_bvh(context, file_path, rotate_mode='XYZ', global_scale=1.0):
116 # File loading stuff
117 # Open the file for importing
118 file = open(file_path, 'rU')
120 # Seperate into a list of lists, each line a list of words.
121 file_lines = file.readlines()
122 # Non standard carrage returns?
123 if len(file_lines) == 1:
124 file_lines = file_lines[0].split('\r')
126 # Split by whitespace.
127 file_lines = [ll for ll in [l.split() for l in file_lines] if ll]
129 # Create hierarchy as empties
130 if file_lines[0][0].lower() == 'hierarchy':
131 # print 'Importing the BVH Hierarchy for:', file_path
132 pass
133 else:
134 raise Exception("This is not a BVH file")
136 bvh_nodes = {None: None}
137 bvh_nodes_serial = [None]
138 bvh_frame_count = None
139 bvh_frame_time = None
141 channelIndex = -1
143 lineIdx = 0 # An index for the file.
144 while lineIdx < len(file_lines) - 1:
145 if file_lines[lineIdx][0].lower() in {'root', 'joint'}:
147 # Join spaces into 1 word with underscores joining it.
148 if len(file_lines[lineIdx]) > 2:
149 file_lines[lineIdx][1] = '_'.join(file_lines[lineIdx][1:])
150 file_lines[lineIdx] = file_lines[lineIdx][:2]
152 # MAY NEED TO SUPPORT MULTIPLE ROOTS HERE! Still unsure weather multiple roots are possible?
154 # Make sure the names are unique - Object names will match joint names exactly and both will be unique.
155 name = file_lines[lineIdx][1]
157 # print '%snode: %s, parent: %s' % (len(bvh_nodes_serial) * ' ', name, bvh_nodes_serial[-1])
159 lineIdx += 2 # Increment to the next line (Offset)
160 rest_head_local = Vector((float(file_lines[lineIdx][1]), float(file_lines[lineIdx][2]), float(file_lines[lineIdx][3]))) * global_scale
161 lineIdx += 1 # Increment to the next line (Channels)
163 # newChannel[Xposition, Yposition, Zposition, Xrotation, Yrotation, Zrotation]
164 # newChannel references indices to the motiondata,
165 # if not assigned then -1 refers to the last value that will be added on loading at a value of zero, this is appended
166 # We'll add a zero value onto the end of the MotionDATA so this always refers to a value.
167 my_channel = [-1, -1, -1, -1, -1, -1]
168 my_rot_order = [None, None, None]
169 rot_count = 0
170 for channel in file_lines[lineIdx][2:]:
171 channel = channel.lower()
172 channelIndex += 1 # So the index points to the right channel
173 if channel == 'xposition':
174 my_channel[0] = channelIndex
175 elif channel == 'yposition':
176 my_channel[1] = channelIndex
177 elif channel == 'zposition':
178 my_channel[2] = channelIndex
180 elif channel == 'xrotation':
181 my_channel[3] = channelIndex
182 my_rot_order[rot_count] = 0
183 rot_count += 1
184 elif channel == 'yrotation':
185 my_channel[4] = channelIndex
186 my_rot_order[rot_count] = 1
187 rot_count += 1
188 elif channel == 'zrotation':
189 my_channel[5] = channelIndex
190 my_rot_order[rot_count] = 2
191 rot_count += 1
193 channels = file_lines[lineIdx][2:]
195 my_parent = bvh_nodes_serial[-1] # account for none
197 # Apply the parents offset accumulatively
198 if my_parent is None:
199 rest_head_world = Vector(rest_head_local)
200 else:
201 rest_head_world = my_parent.rest_head_world + rest_head_local
203 bvh_node = bvh_nodes[name] = BVH_Node(name, rest_head_world, rest_head_local, my_parent, my_channel, my_rot_order, len(bvh_nodes) - 1)
205 # If we have another child then we can call ourselves a parent, else
206 bvh_nodes_serial.append(bvh_node)
208 # Account for an end node.
209 # There is sometimes a name after 'End Site' but we will ignore it.
210 if file_lines[lineIdx][0].lower() == 'end' and file_lines[lineIdx][1].lower() == 'site':
211 # Increment to the next line (Offset)
212 lineIdx += 2
213 rest_tail = Vector((float(file_lines[lineIdx][1]), float(file_lines[lineIdx][2]), float(file_lines[lineIdx][3]))) * global_scale
215 bvh_nodes_serial[-1].rest_tail_world = bvh_nodes_serial[-1].rest_head_world + rest_tail
216 bvh_nodes_serial[-1].rest_tail_local = bvh_nodes_serial[-1].rest_head_local + rest_tail
218 # Just so we can remove the parents in a uniform way,
219 # the end has kids so this is a placeholder.
220 bvh_nodes_serial.append(None)
222 if len(file_lines[lineIdx]) == 1 and file_lines[lineIdx][0] == '}': # == ['}']
223 bvh_nodes_serial.pop() # Remove the last item
225 # End of the hierarchy. Begin the animation section of the file with
226 # the following header.
227 # MOTION
228 # Frames: n
229 # Frame Time: dt
230 if len(file_lines[lineIdx]) == 1 and file_lines[lineIdx][0].lower() == 'motion':
231 lineIdx += 1 # Read frame count.
232 if (len(file_lines[lineIdx]) == 2 and
233 file_lines[lineIdx][0].lower() == 'frames:'):
235 bvh_frame_count = int(file_lines[lineIdx][1])
237 lineIdx += 1 # Read frame rate.
238 if (
239 len(file_lines[lineIdx]) == 3 and
240 file_lines[lineIdx][0].lower() == 'frame' and
241 file_lines[lineIdx][1].lower() == 'time:'
243 bvh_frame_time = float(file_lines[lineIdx][2])
245 lineIdx += 1 # Set the cursor to the first frame
247 break
249 lineIdx += 1
251 # Remove the None value used for easy parent reference
252 del bvh_nodes[None]
253 # Don't use anymore
254 del bvh_nodes_serial
256 # importing world with any order but nicer to maintain order
257 # second life expects it, which isn't to spec.
258 bvh_nodes_list = sorted_nodes(bvh_nodes)
260 while lineIdx < len(file_lines):
261 line = file_lines[lineIdx]
262 for bvh_node in bvh_nodes_list:
263 # for bvh_node in bvh_nodes_serial:
264 lx = ly = lz = rx = ry = rz = 0.0
265 channels = bvh_node.channels
266 anim_data = bvh_node.anim_data
267 if channels[0] != -1:
268 lx = global_scale * float(line[channels[0]])
270 if channels[1] != -1:
271 ly = global_scale * float(line[channels[1]])
273 if channels[2] != -1:
274 lz = global_scale * float(line[channels[2]])
276 if channels[3] != -1 or channels[4] != -1 or channels[5] != -1:
278 rx = radians(float(line[channels[3]]))
279 ry = radians(float(line[channels[4]]))
280 rz = radians(float(line[channels[5]]))
282 # Done importing motion data #
283 anim_data.append((lx, ly, lz, rx, ry, rz))
284 lineIdx += 1
286 # Assign children
287 for bvh_node in bvh_nodes_list:
288 bvh_node_parent = bvh_node.parent
289 if bvh_node_parent:
290 bvh_node_parent.children.append(bvh_node)
292 # Now set the tip of each bvh_node
293 for bvh_node in bvh_nodes_list:
295 if not bvh_node.rest_tail_world:
296 if len(bvh_node.children) == 0:
297 # could just fail here, but rare BVH files have childless nodes
298 bvh_node.rest_tail_world = Vector(bvh_node.rest_head_world)
299 bvh_node.rest_tail_local = Vector(bvh_node.rest_head_local)
300 elif len(bvh_node.children) == 1:
301 bvh_node.rest_tail_world = Vector(bvh_node.children[0].rest_head_world)
302 bvh_node.rest_tail_local = bvh_node.rest_head_local + bvh_node.children[0].rest_head_local
303 else:
304 # allow this, see above
305 # if not bvh_node.children:
306 # raise Exception("bvh node has no end and no children. bad file")
308 # Removed temp for now
309 rest_tail_world = Vector((0.0, 0.0, 0.0))
310 rest_tail_local = Vector((0.0, 0.0, 0.0))
311 for bvh_node_child in bvh_node.children:
312 rest_tail_world += bvh_node_child.rest_head_world
313 rest_tail_local += bvh_node_child.rest_head_local
315 bvh_node.rest_tail_world = rest_tail_world * (1.0 / len(bvh_node.children))
316 bvh_node.rest_tail_local = rest_tail_local * (1.0 / len(bvh_node.children))
318 # Make sure tail isn't the same location as the head.
319 if (bvh_node.rest_tail_local - bvh_node.rest_head_local).length <= 0.001 * global_scale:
320 print("\tzero length node found:", bvh_node.name)
321 bvh_node.rest_tail_local.y = bvh_node.rest_tail_local.y + global_scale / 10
322 bvh_node.rest_tail_world.y = bvh_node.rest_tail_world.y + global_scale / 10
324 return bvh_nodes, bvh_frame_time, bvh_frame_count
327 def bvh_node_dict2objects(context, bvh_name, bvh_nodes, rotate_mode='NATIVE', frame_start=1, IMPORT_LOOP=False):
329 if frame_start < 1:
330 frame_start = 1
332 scene = context.scene
333 for obj in scene.objects:
334 obj.select = False
336 objects = []
338 def add_ob(name):
339 obj = bpy.data.objects.new(name, None)
340 scene.objects.link(obj)
341 objects.append(obj)
342 obj.select = True
344 # nicer drawing.
345 obj.empty_draw_type = 'CUBE'
346 obj.empty_draw_size = 0.1
348 return obj
350 # Add objects
351 for name, bvh_node in bvh_nodes.items():
352 bvh_node.temp = add_ob(name)
353 bvh_node.temp.rotation_mode = bvh_node.rot_order_str[::-1]
355 # Parent the objects
356 for bvh_node in bvh_nodes.values():
357 for bvh_node_child in bvh_node.children:
358 bvh_node_child.temp.parent = bvh_node.temp
360 # Offset
361 for bvh_node in bvh_nodes.values():
362 # Make relative to parents offset
363 bvh_node.temp.location = bvh_node.rest_head_local
365 # Add tail objects
366 for name, bvh_node in bvh_nodes.items():
367 if not bvh_node.children:
368 ob_end = add_ob(name + '_end')
369 ob_end.parent = bvh_node.temp
370 ob_end.location = bvh_node.rest_tail_world - bvh_node.rest_head_world
372 for name, bvh_node in bvh_nodes.items():
373 obj = bvh_node.temp
375 for frame_current in range(len(bvh_node.anim_data)):
377 lx, ly, lz, rx, ry, rz = bvh_node.anim_data[frame_current]
379 if bvh_node.has_loc:
380 obj.delta_location = Vector((lx, ly, lz)) - bvh_node.rest_head_world
381 obj.keyframe_insert("delta_location", index=-1, frame=frame_start + frame_current)
383 if bvh_node.has_rot:
384 obj.delta_rotation_euler = rx, ry, rz
385 obj.keyframe_insert("delta_rotation_euler", index=-1, frame=frame_start + frame_current)
387 return objects
390 def bvh_node_dict2armature(
391 context,
392 bvh_name,
393 bvh_nodes,
394 bvh_frame_time,
395 rotate_mode='XYZ',
396 frame_start=1,
397 IMPORT_LOOP=False,
398 global_matrix=None,
399 use_fps_scale=False,
402 if frame_start < 1:
403 frame_start = 1
405 # Add the new armature,
406 scene = context.scene
407 for obj in scene.objects:
408 obj.select = False
410 arm_data = bpy.data.armatures.new(bvh_name)
411 arm_ob = bpy.data.objects.new(bvh_name, arm_data)
413 scene.objects.link(arm_ob)
415 arm_ob.select = True
416 scene.objects.active = arm_ob
418 bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
419 bpy.ops.object.mode_set(mode='EDIT', toggle=False)
421 bvh_nodes_list = sorted_nodes(bvh_nodes)
423 # Get the average bone length for zero length bones, we may not use this.
424 average_bone_length = 0.0
425 nonzero_count = 0
426 for bvh_node in bvh_nodes_list:
427 l = (bvh_node.rest_head_local - bvh_node.rest_tail_local).length
428 if l:
429 average_bone_length += l
430 nonzero_count += 1
432 # Very rare cases all bones could be zero length???
433 if not average_bone_length:
434 average_bone_length = 0.1
435 else:
436 # Normal operation
437 average_bone_length = average_bone_length / nonzero_count
439 # XXX, annoying, remove bone.
440 while arm_data.edit_bones:
441 arm_ob.edit_bones.remove(arm_data.edit_bones[-1])
443 ZERO_AREA_BONES = []
444 for bvh_node in bvh_nodes_list:
446 # New editbone
447 bone = bvh_node.temp = arm_data.edit_bones.new(bvh_node.name)
449 bone.head = bvh_node.rest_head_world
450 bone.tail = bvh_node.rest_tail_world
452 # Zero Length Bones! (an exceptional case)
453 if (bone.head - bone.tail).length < 0.001:
454 print("\tzero length bone found:", bone.name)
455 if bvh_node.parent:
456 ofs = bvh_node.parent.rest_head_local - bvh_node.parent.rest_tail_local
457 if ofs.length: # is our parent zero length also?? unlikely
458 bone.tail = bone.tail - ofs
459 else:
460 bone.tail.y = bone.tail.y + average_bone_length
461 else:
462 bone.tail.y = bone.tail.y + average_bone_length
464 ZERO_AREA_BONES.append(bone.name)
466 for bvh_node in bvh_nodes_list:
467 if bvh_node.parent:
468 # bvh_node.temp is the Editbone
470 # Set the bone parent
471 bvh_node.temp.parent = bvh_node.parent.temp
473 # Set the connection state
474 if((not bvh_node.has_loc) and
475 (bvh_node.parent.temp.name not in ZERO_AREA_BONES) and
476 (bvh_node.parent.rest_tail_local == bvh_node.rest_head_local)):
478 bvh_node.temp.use_connect = True
480 # Replace the editbone with the editbone name,
481 # to avoid memory errors accessing the editbone outside editmode
482 for bvh_node in bvh_nodes_list:
483 bvh_node.temp = bvh_node.temp.name
485 # Now Apply the animation to the armature
487 # Get armature animation data
488 bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
490 pose = arm_ob.pose
491 pose_bones = pose.bones
493 if rotate_mode == 'NATIVE':
494 for bvh_node in bvh_nodes_list:
495 bone_name = bvh_node.temp # may not be the same name as the bvh_node, could have been shortened.
496 pose_bone = pose_bones[bone_name]
497 pose_bone.rotation_mode = bvh_node.rot_order_str
499 elif rotate_mode != 'QUATERNION':
500 for pose_bone in pose_bones:
501 pose_bone.rotation_mode = rotate_mode
502 else:
503 # Quats default
504 pass
506 context.scene.update()
508 arm_ob.animation_data_create()
509 action = bpy.data.actions.new(name=bvh_name)
510 arm_ob.animation_data.action = action
512 # Replace the bvh_node.temp (currently an editbone)
513 # With a tuple (pose_bone, armature_bone, bone_rest_matrix, bone_rest_matrix_inv)
514 num_frame = 0
515 for bvh_node in bvh_nodes_list:
516 bone_name = bvh_node.temp # may not be the same name as the bvh_node, could have been shortened.
517 pose_bone = pose_bones[bone_name]
518 rest_bone = arm_data.bones[bone_name]
519 bone_rest_matrix = rest_bone.matrix_local.to_3x3()
521 bone_rest_matrix_inv = Matrix(bone_rest_matrix)
522 bone_rest_matrix_inv.invert()
524 bone_rest_matrix_inv.resize_4x4()
525 bone_rest_matrix.resize_4x4()
526 bvh_node.temp = (pose_bone, bone, bone_rest_matrix, bone_rest_matrix_inv)
528 if 0 == num_frame:
529 num_frame = len(bvh_node.anim_data)
531 # Choose to skip some frames at the beginning. Frame 0 is the rest pose
532 # used internally by this importer. Frame 1, by convention, is also often
533 # the rest pose of the skeleton exported by the motion capture system.
534 skip_frame = 1
535 if num_frame > skip_frame:
536 num_frame = num_frame - skip_frame
538 # Create a shared time axis for all animation curves.
539 time = [float(frame_start)] * num_frame
540 if use_fps_scale:
541 dt = scene.render.fps * bvh_frame_time
542 for frame_i in range(1, num_frame):
543 time[frame_i] += float(frame_i) * dt
544 else:
545 for frame_i in range(1, num_frame):
546 time[frame_i] += float(frame_i)
548 # print("bvh_frame_time = %f, dt = %f, num_frame = %d"
549 # % (bvh_frame_time, dt, num_frame]))
551 for i, bvh_node in enumerate(bvh_nodes_list):
552 pose_bone, bone, bone_rest_matrix, bone_rest_matrix_inv = bvh_node.temp
554 if bvh_node.has_loc:
555 # Not sure if there is a way to query this or access it in the
556 # PoseBone structure.
557 data_path = 'pose.bones["%s"].location' % pose_bone.name
559 location = [(0.0, 0.0, 0.0)] * num_frame
560 for frame_i in range(num_frame):
561 bvh_loc = bvh_node.anim_data[frame_i + skip_frame][:3]
563 bone_translate_matrix = Matrix.Translation(
564 Vector(bvh_loc) - bvh_node.rest_head_local)
565 location[frame_i] = (bone_rest_matrix_inv *
566 bone_translate_matrix).to_translation()
568 # For each location x, y, z.
569 for axis_i in range(3):
570 curve = action.fcurves.new(data_path=data_path, index=axis_i)
571 keyframe_points = curve.keyframe_points
572 keyframe_points.add(num_frame)
574 for frame_i in range(num_frame):
575 keyframe_points[frame_i].co = \
576 (time[frame_i], location[frame_i][axis_i])
578 if bvh_node.has_rot:
579 data_path = None
580 rotate = None
582 if 'QUATERNION' == rotate_mode:
583 rotate = [(1.0, 0.0, 0.0, 0.0)] * num_frame
584 data_path = ('pose.bones["%s"].rotation_quaternion'
585 % pose_bone.name)
586 else:
587 rotate = [(0.0, 0.0, 0.0)] * num_frame
588 data_path = ('pose.bones["%s"].rotation_euler' %
589 pose_bone.name)
591 prev_euler = Euler((0.0, 0.0, 0.0))
592 for frame_i in range(num_frame):
593 bvh_rot = bvh_node.anim_data[frame_i + skip_frame][3:]
595 # apply rotation order and convert to XYZ
596 # note that the rot_order_str is reversed.
597 euler = Euler(bvh_rot, bvh_node.rot_order_str[::-1])
598 bone_rotation_matrix = euler.to_matrix().to_4x4()
599 bone_rotation_matrix = (bone_rest_matrix_inv *
600 bone_rotation_matrix *
601 bone_rest_matrix)
603 if 4 == len(rotate[frame_i]):
604 rotate[frame_i] = bone_rotation_matrix.to_quaternion()
605 else:
606 rotate[frame_i] = bone_rotation_matrix.to_euler(
607 pose_bone.rotation_mode, prev_euler)
608 prev_euler = rotate[frame_i]
610 # For each Euler angle x, y, z (or Quaternion w, x, y, z).
611 for axis_i in range(len(rotate[0])):
612 curve = action.fcurves.new(data_path=data_path, index=axis_i)
613 keyframe_points = curve.keyframe_points
614 curve.keyframe_points.add(num_frame)
616 for frame_i in range(0, num_frame):
617 keyframe_points[frame_i].co = \
618 (time[frame_i], rotate[frame_i][axis_i])
620 for cu in action.fcurves:
621 if IMPORT_LOOP:
622 pass # 2.5 doenst have cyclic now?
624 for bez in cu.keyframe_points:
625 bez.interpolation = 'LINEAR'
627 # finally apply matrix
628 arm_ob.matrix_world = global_matrix
629 bpy.ops.object.transform_apply(rotation=True)
631 return arm_ob
634 def load(
635 context,
636 filepath,
638 target='ARMATURE',
639 rotate_mode='NATIVE',
640 global_scale=1.0,
641 use_cyclic=False,
642 frame_start=1,
643 global_matrix=None,
644 use_fps_scale=False,
645 update_scene_fps=False,
646 update_scene_duration=False,
647 report=print
649 import time
650 t1 = time.time()
651 print("\tparsing bvh %r..." % filepath, end="")
653 bvh_nodes, bvh_frame_time, bvh_frame_count = read_bvh(
654 context, filepath,
655 rotate_mode=rotate_mode,
656 global_scale=global_scale,
659 print("%.4f" % (time.time() - t1))
661 scene = context.scene
662 frame_orig = scene.frame_current
664 # Broken BVH handling: guess frame rate when it is not contained in the file.
665 if bvh_frame_time is None:
666 report(
667 {'WARNING'},
668 "The BVH file does not contain frame duration in its MOTION "
669 "section, assuming the BVH and Blender scene have the same "
670 "frame rate"
672 bvh_frame_time = scene.render.fps_base / scene.render.fps
673 # No need to scale the frame rate, as they're equal now anyway.
674 use_fps_scale = False
676 if update_scene_fps:
677 _update_scene_fps(context, report, bvh_frame_time)
679 # Now that we have a 1-to-1 mapping of Blender frames and BVH frames, there is no need
680 # to scale the FPS any more. It's even better not to, to prevent roundoff errors.
681 use_fps_scale = False
683 if update_scene_duration:
684 _update_scene_duration(context, report, bvh_frame_count, bvh_frame_time, frame_start, use_fps_scale)
686 t1 = time.time()
687 print("\timporting to blender...", end="")
689 bvh_name = bpy.path.display_name_from_filepath(filepath)
691 if target == 'ARMATURE':
692 bvh_node_dict2armature(
693 context, bvh_name, bvh_nodes, bvh_frame_time,
694 rotate_mode=rotate_mode,
695 frame_start=frame_start,
696 IMPORT_LOOP=use_cyclic,
697 global_matrix=global_matrix,
698 use_fps_scale=use_fps_scale,
701 elif target == 'OBJECT':
702 bvh_node_dict2objects(
703 context, bvh_name, bvh_nodes,
704 rotate_mode=rotate_mode,
705 frame_start=frame_start,
706 IMPORT_LOOP=use_cyclic,
707 # global_matrix=global_matrix, # TODO
710 else:
711 report({'ERROR'}, "Invalid target %r (must be 'ARMATURE' or 'OBJECT')" % target)
712 return {'CANCELLED'}
714 print('Done in %.4f\n' % (time.time() - t1))
716 context.scene.frame_set(frame_orig)
718 return {'FINISHED'}
721 def _update_scene_fps(context, report, bvh_frame_time):
722 """Update the scene's FPS settings from the BVH, but only if the BVH contains enough info."""
724 # Broken BVH handling: prevent division by zero.
725 if bvh_frame_time == 0.0:
726 report(
727 {'WARNING'},
728 "Unable to update scene frame rate, as the BVH file "
729 "contains a zero frame duration in its MOTION section",
731 return
733 scene = context.scene
734 scene_fps = scene.render.fps / scene.render.fps_base
735 new_fps = 1.0 / bvh_frame_time
737 if scene.render.fps != new_fps or scene.render.fps_base != 1.0:
738 print("\tupdating scene FPS (was %f) to BVH FPS (%f)" % (scene_fps, new_fps))
739 scene.render.fps = new_fps
740 scene.render.fps_base = 1.0
743 def _update_scene_duration(
744 context, report, bvh_frame_count, bvh_frame_time, frame_start,
745 use_fps_scale):
746 """Extend the scene's duration so that the BVH file fits in its entirety."""
748 if bvh_frame_count is None:
749 report(
750 {'WARNING'},
751 "Unable to extend the scene duration, as the BVH file does not "
752 "contain the number of frames in its MOTION section",
754 return
756 # Not likely, but it can happen when a BVH is just used to store an armature.
757 if bvh_frame_count == 0:
758 return
760 if use_fps_scale:
761 scene_fps = context.scene.render.fps / context.scene.render.fps_base
762 scaled_frame_count = int(ceil(bvh_frame_count * bvh_frame_time * scene_fps))
763 bvh_last_frame = frame_start + scaled_frame_count
764 else:
765 bvh_last_frame = frame_start + bvh_frame_count
767 # Only extend the scene, never shorten it.
768 if context.scene.frame_end < bvh_last_frame:
769 context.scene.frame_end = bvh_last_frame