Export_3ds: Improved distance cue nodes setup
[blender-addons.git] / io_anim_bvh / import_bvh.py
blob5d1b5a1f67da85d455e8614907f45cd140a070e4
1 # SPDX-FileCopyrightText: 2011 Campbell Barton
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 from math import radians, ceil
7 import bpy
8 from bpy.app.translations import pgettext_tip as tip_
9 from mathutils import Vector, Euler, Matrix
12 class BVH_Node:
13 __slots__ = (
14 # Bvh joint name.
15 'name',
16 # BVH_Node type or None for no parent.
17 'parent',
18 # A list of children of this type..
19 'children',
20 # Worldspace rest location for the head of this node.
21 'rest_head_world',
22 # Localspace rest location for the head of this node.
23 'rest_head_local',
24 # Worldspace rest location for the tail of this node.
25 'rest_tail_world',
26 # Worldspace rest location for the tail of this node.
27 'rest_tail_local',
28 # List of 6 ints, -1 for an unused channel,
29 # otherwise an index for the BVH motion data lines,
30 # loc triple then rot triple.
31 'channels',
32 # A triple of indices as to the order rotation is applied.
33 # [0,1,2] is x/y/z - [None, None, None] if no rotation..
34 'rot_order',
35 # Same as above but a string 'XYZ' format..
36 'rot_order_str',
37 # A list one tuple's one for each frame: (locx, locy, locz, rotx, roty, rotz),
38 # euler rotation ALWAYS stored xyz order, even when native used.
39 'anim_data',
40 # Convenience function, bool, same as: (channels[0] != -1 or channels[1] != -1 or channels[2] != -1).
41 'has_loc',
42 # Convenience function, bool, same as: (channels[3] != -1 or channels[4] != -1 or channels[5] != -1).
43 'has_rot',
44 # Index from the file, not strictly needed but nice to maintain order.
45 'index',
46 # Use this for whatever you want.
47 'temp',
50 _eul_order_lookup = {
51 (None, None, None): 'XYZ', # XXX Dummy one, no rotation anyway!
52 (0, 1, 2): 'XYZ',
53 (0, 2, 1): 'XZY',
54 (1, 0, 2): 'YXZ',
55 (1, 2, 0): 'YZX',
56 (2, 0, 1): 'ZXY',
57 (2, 1, 0): 'ZYX',
60 def __init__(self, name, rest_head_world, rest_head_local, parent, channels, rot_order, index):
61 self.name = name
62 self.rest_head_world = rest_head_world
63 self.rest_head_local = rest_head_local
64 self.rest_tail_world = None
65 self.rest_tail_local = None
66 self.parent = parent
67 self.channels = channels
68 self.rot_order = tuple(rot_order)
69 self.rot_order_str = BVH_Node._eul_order_lookup[self.rot_order]
70 self.index = index
72 # convenience functions
73 self.has_loc = channels[0] != -1 or channels[1] != -1 or channels[2] != -1
74 self.has_rot = channels[3] != -1 or channels[4] != -1 or channels[5] != -1
76 self.children = []
78 # List of 6 length tuples: (lx, ly, lz, rx, ry, rz)
79 # even if the channels aren't used they will just be zero.
80 self.anim_data = [(0, 0, 0, 0, 0, 0)]
82 def __repr__(self):
83 return (
84 "BVH name: '%s', rest_loc:(%.3f,%.3f,%.3f), rest_tail:(%.3f,%.3f,%.3f)" % (
85 self.name,
86 *self.rest_head_world,
87 *self.rest_head_world,
92 def sorted_nodes(bvh_nodes):
93 bvh_nodes_list = list(bvh_nodes.values())
94 bvh_nodes_list.sort(key=lambda bvh_node: bvh_node.index)
95 return bvh_nodes_list
98 def read_bvh(context, file_path, rotate_mode='XYZ', global_scale=1.0):
99 # File loading stuff
100 # Open the file for importing
101 file = open(file_path, 'r')
103 # Separate into a list of lists, each line a list of words.
104 file_lines = file.readlines()
105 # Non standard carriage returns?
106 if len(file_lines) == 1:
107 file_lines = file_lines[0].split('\r')
109 # Split by whitespace.
110 file_lines = [ll for ll in [l.split() for l in file_lines] if ll]
112 # Create hierarchy as empties
113 if file_lines[0][0].lower() == 'hierarchy':
114 # print 'Importing the BVH Hierarchy for:', file_path
115 pass
116 else:
117 raise Exception("This is not a BVH file")
119 bvh_nodes = {None: None}
120 bvh_nodes_serial = [None]
121 bvh_frame_count = None
122 bvh_frame_time = None
124 channelIndex = -1
126 lineIdx = 0 # An index for the file.
127 while lineIdx < len(file_lines) - 1:
128 if file_lines[lineIdx][0].lower() in {'root', 'joint'}:
130 # Join spaces into 1 word with underscores joining it.
131 if len(file_lines[lineIdx]) > 2:
132 file_lines[lineIdx][1] = '_'.join(file_lines[lineIdx][1:])
133 file_lines[lineIdx] = file_lines[lineIdx][:2]
135 # MAY NEED TO SUPPORT MULTIPLE ROOTS HERE! Still unsure weather multiple roots are possible?
137 # Make sure the names are unique - Object names will match joint names exactly and both will be unique.
138 name = file_lines[lineIdx][1]
140 # While unlikely, there exists a user report of duplicate joint names, see: #109399.
141 if name in bvh_nodes:
142 name_orig = name
143 name_index = 1
144 while (name := "%s.%03d" % (name_orig, name_index)) in bvh_nodes:
145 name_index += 1
146 del name_orig, name_index
148 # print '%snode: %s, parent: %s' % (len(bvh_nodes_serial) * ' ', name, bvh_nodes_serial[-1])
150 lineIdx += 2 # Increment to the next line (Offset)
151 rest_head_local = global_scale * Vector((
152 float(file_lines[lineIdx][1]),
153 float(file_lines[lineIdx][2]),
154 float(file_lines[lineIdx][3]),
156 lineIdx += 1 # Increment to the next line (Channels)
158 # newChannel[Xposition, Yposition, Zposition, Xrotation, Yrotation, Zrotation]
159 # newChannel references indices to the motiondata,
160 # if not assigned then -1 refers to the last value that will be added on loading at a value of zero, this is appended
161 # We'll add a zero value onto the end of the MotionDATA so this always refers to a value.
162 my_channel = [-1, -1, -1, -1, -1, -1]
163 my_rot_order = [None, None, None]
164 rot_count = 0
165 for channel in file_lines[lineIdx][2:]:
166 channel = channel.lower()
167 channelIndex += 1 # So the index points to the right channel
168 if channel == 'xposition':
169 my_channel[0] = channelIndex
170 elif channel == 'yposition':
171 my_channel[1] = channelIndex
172 elif channel == 'zposition':
173 my_channel[2] = channelIndex
175 elif channel == 'xrotation':
176 my_channel[3] = channelIndex
177 my_rot_order[rot_count] = 0
178 rot_count += 1
179 elif channel == 'yrotation':
180 my_channel[4] = channelIndex
181 my_rot_order[rot_count] = 1
182 rot_count += 1
183 elif channel == 'zrotation':
184 my_channel[5] = channelIndex
185 my_rot_order[rot_count] = 2
186 rot_count += 1
188 channels = file_lines[lineIdx][2:]
190 my_parent = bvh_nodes_serial[-1] # account for none
192 # Apply the parents offset accumulatively
193 if my_parent is None:
194 rest_head_world = Vector(rest_head_local)
195 else:
196 rest_head_world = my_parent.rest_head_world + rest_head_local
198 bvh_node = bvh_nodes[name] = BVH_Node(
199 name,
200 rest_head_world,
201 rest_head_local,
202 my_parent,
203 my_channel,
204 my_rot_order,
205 len(bvh_nodes) - 1,
208 # If we have another child then we can call ourselves a parent, else
209 bvh_nodes_serial.append(bvh_node)
211 # Account for an end node.
212 # There is sometimes a name after 'End Site' but we will ignore it.
213 if file_lines[lineIdx][0].lower() == 'end' and file_lines[lineIdx][1].lower() == 'site':
214 # Increment to the next line (Offset)
215 lineIdx += 2
216 rest_tail = global_scale * Vector((
217 float(file_lines[lineIdx][1]),
218 float(file_lines[lineIdx][2]),
219 float(file_lines[lineIdx][3]),
222 bvh_nodes_serial[-1].rest_tail_world = bvh_nodes_serial[-1].rest_head_world + rest_tail
223 bvh_nodes_serial[-1].rest_tail_local = bvh_nodes_serial[-1].rest_head_local + rest_tail
225 # Just so we can remove the parents in a uniform way,
226 # the end has kids so this is a placeholder.
227 bvh_nodes_serial.append(None)
229 if len(file_lines[lineIdx]) == 1 and file_lines[lineIdx][0] == '}': # == ['}']
230 bvh_nodes_serial.pop() # Remove the last item
232 # End of the hierarchy. Begin the animation section of the file with
233 # the following header.
234 # MOTION
235 # Frames: n
236 # Frame Time: dt
237 if len(file_lines[lineIdx]) == 1 and file_lines[lineIdx][0].lower() == 'motion':
238 lineIdx += 1 # Read frame count.
239 if (
240 len(file_lines[lineIdx]) == 2 and
241 file_lines[lineIdx][0].lower() == 'frames:'
243 bvh_frame_count = int(file_lines[lineIdx][1])
245 lineIdx += 1 # Read frame rate.
246 if (
247 len(file_lines[lineIdx]) == 3 and
248 file_lines[lineIdx][0].lower() == 'frame' and
249 file_lines[lineIdx][1].lower() == 'time:'
251 bvh_frame_time = float(file_lines[lineIdx][2])
253 lineIdx += 1 # Set the cursor to the first frame
255 break
257 lineIdx += 1
259 # Remove the None value used for easy parent reference
260 del bvh_nodes[None]
261 # Don't use anymore
262 del bvh_nodes_serial
264 # importing world with any order but nicer to maintain order
265 # second life expects it, which isn't to spec.
266 bvh_nodes_list = sorted_nodes(bvh_nodes)
268 while lineIdx < len(file_lines):
269 line = file_lines[lineIdx]
270 for bvh_node in bvh_nodes_list:
271 # for bvh_node in bvh_nodes_serial:
272 lx = ly = lz = rx = ry = rz = 0.0
273 channels = bvh_node.channels
274 anim_data = bvh_node.anim_data
275 if channels[0] != -1:
276 lx = global_scale * float(line[channels[0]])
278 if channels[1] != -1:
279 ly = global_scale * float(line[channels[1]])
281 if channels[2] != -1:
282 lz = global_scale * float(line[channels[2]])
284 if channels[3] != -1 or channels[4] != -1 or channels[5] != -1:
286 rx = radians(float(line[channels[3]]))
287 ry = radians(float(line[channels[4]]))
288 rz = radians(float(line[channels[5]]))
290 # Done importing motion data #
291 anim_data.append((lx, ly, lz, rx, ry, rz))
292 lineIdx += 1
294 # Assign children
295 for bvh_node in bvh_nodes_list:
296 bvh_node_parent = bvh_node.parent
297 if bvh_node_parent:
298 bvh_node_parent.children.append(bvh_node)
300 # Now set the tip of each bvh_node
301 for bvh_node in bvh_nodes_list:
303 if not bvh_node.rest_tail_world:
304 if len(bvh_node.children) == 0:
305 # could just fail here, but rare BVH files have childless nodes
306 bvh_node.rest_tail_world = Vector(bvh_node.rest_head_world)
307 bvh_node.rest_tail_local = Vector(bvh_node.rest_head_local)
308 elif len(bvh_node.children) == 1:
309 bvh_node.rest_tail_world = Vector(bvh_node.children[0].rest_head_world)
310 bvh_node.rest_tail_local = bvh_node.rest_head_local + bvh_node.children[0].rest_head_local
311 else:
312 # allow this, see above
313 # if not bvh_node.children:
314 # raise Exception("bvh node has no end and no children. bad file")
316 # Removed temp for now
317 rest_tail_world = Vector((0.0, 0.0, 0.0))
318 rest_tail_local = Vector((0.0, 0.0, 0.0))
319 for bvh_node_child in bvh_node.children:
320 rest_tail_world += bvh_node_child.rest_head_world
321 rest_tail_local += bvh_node_child.rest_head_local
323 bvh_node.rest_tail_world = rest_tail_world * (1.0 / len(bvh_node.children))
324 bvh_node.rest_tail_local = rest_tail_local * (1.0 / len(bvh_node.children))
326 # Make sure tail isn't the same location as the head.
327 if (bvh_node.rest_tail_local - bvh_node.rest_head_local).length <= 0.001 * global_scale:
328 print("\tzero length node found:", bvh_node.name)
329 bvh_node.rest_tail_local.y = bvh_node.rest_tail_local.y + global_scale / 10
330 bvh_node.rest_tail_world.y = bvh_node.rest_tail_world.y + global_scale / 10
332 return bvh_nodes, bvh_frame_time, bvh_frame_count
335 def bvh_node_dict2objects(context, bvh_name, bvh_nodes, rotate_mode='NATIVE', frame_start=1, IMPORT_LOOP=False):
337 if frame_start < 1:
338 frame_start = 1
340 scene = context.scene
341 for obj in scene.objects:
342 obj.select_set(False)
344 objects = []
346 def add_ob(name):
347 obj = bpy.data.objects.new(name, None)
348 context.collection.objects.link(obj)
349 objects.append(obj)
350 obj.select_set(True)
352 # nicer drawing.
353 obj.empty_display_type = 'CUBE'
354 obj.empty_display_size = 0.1
356 return obj
358 # Add objects
359 for name, bvh_node in bvh_nodes.items():
360 bvh_node.temp = add_ob(name)
361 bvh_node.temp.rotation_mode = bvh_node.rot_order_str[::-1]
363 # Parent the objects
364 for bvh_node in bvh_nodes.values():
365 for bvh_node_child in bvh_node.children:
366 bvh_node_child.temp.parent = bvh_node.temp
368 # Offset
369 for bvh_node in bvh_nodes.values():
370 # Make relative to parents offset
371 bvh_node.temp.location = bvh_node.rest_head_local
373 # Add tail objects
374 for name, bvh_node in bvh_nodes.items():
375 if not bvh_node.children:
376 ob_end = add_ob(name + '_end')
377 ob_end.parent = bvh_node.temp
378 ob_end.location = bvh_node.rest_tail_world - bvh_node.rest_head_world
380 for name, bvh_node in bvh_nodes.items():
381 obj = bvh_node.temp
383 for frame_current in range(len(bvh_node.anim_data)):
385 lx, ly, lz, rx, ry, rz = bvh_node.anim_data[frame_current]
387 if bvh_node.has_loc:
388 obj.delta_location = Vector((lx, ly, lz)) - bvh_node.rest_head_world
389 obj.keyframe_insert("delta_location", index=-1, frame=frame_start + frame_current)
391 if bvh_node.has_rot:
392 obj.delta_rotation_euler = rx, ry, rz
393 obj.keyframe_insert("delta_rotation_euler", index=-1, frame=frame_start + frame_current)
395 return objects
398 def bvh_node_dict2armature(
399 context,
400 bvh_name,
401 bvh_nodes,
402 bvh_frame_time,
403 rotate_mode='XYZ',
404 frame_start=1,
405 IMPORT_LOOP=False,
406 global_matrix=None,
407 use_fps_scale=False,
409 from bpy.utils import escape_identifier
411 if frame_start < 1:
412 frame_start = 1
414 # Add the new armature,
415 scene = context.scene
416 for obj in scene.objects:
417 obj.select_set(False)
419 arm_data = bpy.data.armatures.new(bvh_name)
420 arm_ob = bpy.data.objects.new(bvh_name, arm_data)
422 context.collection.objects.link(arm_ob)
424 arm_ob.select_set(True)
425 context.view_layer.objects.active = arm_ob
427 bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
428 bpy.ops.object.mode_set(mode='EDIT', toggle=False)
430 bvh_nodes_list = sorted_nodes(bvh_nodes)
432 # Get the average bone length for zero length bones, we may not use this.
433 average_bone_length = 0.0
434 nonzero_count = 0
435 for bvh_node in bvh_nodes_list:
436 l = (bvh_node.rest_head_local - bvh_node.rest_tail_local).length
437 if l:
438 average_bone_length += l
439 nonzero_count += 1
441 # Very rare cases all bones could be zero length???
442 if not average_bone_length:
443 average_bone_length = 0.1
444 else:
445 # Normal operation
446 average_bone_length = average_bone_length / nonzero_count
448 # XXX, annoying, remove bone.
449 while arm_data.edit_bones:
450 arm_ob.edit_bones.remove(arm_data.edit_bones[-1])
452 ZERO_AREA_BONES = []
453 for bvh_node in bvh_nodes_list:
455 # New editbone
456 bone = bvh_node.temp = arm_data.edit_bones.new(bvh_node.name)
458 bone.head = bvh_node.rest_head_world
459 bone.tail = bvh_node.rest_tail_world
461 # Zero Length Bones! (an exceptional case)
462 if (bone.head - bone.tail).length < 0.001:
463 print("\tzero length bone found:", bone.name)
464 if bvh_node.parent:
465 ofs = bvh_node.parent.rest_head_local - bvh_node.parent.rest_tail_local
466 if ofs.length: # is our parent zero length also?? unlikely
467 bone.tail = bone.tail - ofs
468 else:
469 bone.tail.y = bone.tail.y + average_bone_length
470 else:
471 bone.tail.y = bone.tail.y + average_bone_length
473 ZERO_AREA_BONES.append(bone.name)
475 for bvh_node in bvh_nodes_list:
476 if bvh_node.parent:
477 # bvh_node.temp is the Editbone
479 # Set the bone parent
480 bvh_node.temp.parent = bvh_node.parent.temp
482 # Set the connection state
483 if (
484 (not bvh_node.has_loc) and
485 (bvh_node.parent.temp.name not in ZERO_AREA_BONES) and
486 (bvh_node.parent.rest_tail_local == bvh_node.rest_head_local)
488 bvh_node.temp.use_connect = True
490 # Replace the editbone with the editbone name,
491 # to avoid memory errors accessing the editbone outside editmode
492 for bvh_node in bvh_nodes_list:
493 bvh_node.temp = bvh_node.temp.name
495 # Now Apply the animation to the armature
497 # Get armature animation data
498 bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
500 pose = arm_ob.pose
501 pose_bones = pose.bones
503 if rotate_mode == 'NATIVE':
504 for bvh_node in bvh_nodes_list:
505 bone_name = bvh_node.temp # may not be the same name as the bvh_node, could have been shortened.
506 pose_bone = pose_bones[bone_name]
507 pose_bone.rotation_mode = bvh_node.rot_order_str
509 elif rotate_mode != 'QUATERNION':
510 for pose_bone in pose_bones:
511 pose_bone.rotation_mode = rotate_mode
512 else:
513 # Quats default
514 pass
516 context.view_layer.update()
518 arm_ob.animation_data_create()
519 action = bpy.data.actions.new(name=bvh_name)
520 arm_ob.animation_data.action = action
522 # Replace the bvh_node.temp (currently an editbone)
523 # With a tuple (pose_bone, armature_bone, bone_rest_matrix, bone_rest_matrix_inv)
524 num_frame = 0
525 for bvh_node in bvh_nodes_list:
526 bone_name = bvh_node.temp # may not be the same name as the bvh_node, could have been shortened.
527 pose_bone = pose_bones[bone_name]
528 rest_bone = arm_data.bones[bone_name]
529 bone_rest_matrix = rest_bone.matrix_local.to_3x3()
531 bone_rest_matrix_inv = Matrix(bone_rest_matrix)
532 bone_rest_matrix_inv.invert()
534 bone_rest_matrix_inv.resize_4x4()
535 bone_rest_matrix.resize_4x4()
536 bvh_node.temp = (pose_bone, bone, bone_rest_matrix, bone_rest_matrix_inv)
538 if 0 == num_frame:
539 num_frame = len(bvh_node.anim_data)
541 # Choose to skip some frames at the beginning. Frame 0 is the rest pose
542 # used internally by this importer. Frame 1, by convention, is also often
543 # the rest pose of the skeleton exported by the motion capture system.
544 skip_frame = 1
545 if num_frame > skip_frame:
546 num_frame = num_frame - skip_frame
548 # Create a shared time axis for all animation curves.
549 time = [float(frame_start)] * num_frame
550 if use_fps_scale:
551 dt = scene.render.fps * bvh_frame_time
552 for frame_i in range(1, num_frame):
553 time[frame_i] += float(frame_i) * dt
554 else:
555 for frame_i in range(1, num_frame):
556 time[frame_i] += float(frame_i)
558 # print("bvh_frame_time = %f, dt = %f, num_frame = %d"
559 # % (bvh_frame_time, dt, num_frame]))
561 for i, bvh_node in enumerate(bvh_nodes_list):
562 pose_bone, bone, bone_rest_matrix, bone_rest_matrix_inv = bvh_node.temp
564 if bvh_node.has_loc:
565 # Not sure if there is a way to query this or access it in the
566 # PoseBone structure.
567 data_path = 'pose.bones["%s"].location' % escape_identifier(pose_bone.name)
569 location = [(0.0, 0.0, 0.0)] * num_frame
570 for frame_i in range(num_frame):
571 bvh_loc = bvh_node.anim_data[frame_i + skip_frame][:3]
573 bone_translate_matrix = Matrix.Translation(
574 Vector(bvh_loc) - bvh_node.rest_head_local)
575 location[frame_i] = (bone_rest_matrix_inv @
576 bone_translate_matrix).to_translation()
578 # For each location x, y, z.
579 for axis_i in range(3):
580 curve = action.fcurves.new(data_path=data_path, index=axis_i, action_group=bvh_node.name)
581 keyframe_points = curve.keyframe_points
582 keyframe_points.add(num_frame)
584 for frame_i in range(num_frame):
585 keyframe_points[frame_i].co = (
586 time[frame_i],
587 location[frame_i][axis_i],
590 if bvh_node.has_rot:
591 data_path = None
592 rotate = None
594 if 'QUATERNION' == rotate_mode:
595 rotate = [(1.0, 0.0, 0.0, 0.0)] * num_frame
596 data_path = ('pose.bones["%s"].rotation_quaternion' % escape_identifier(pose_bone.name))
597 else:
598 rotate = [(0.0, 0.0, 0.0)] * num_frame
599 data_path = ('pose.bones["%s"].rotation_euler' % escape_identifier(pose_bone.name))
601 prev_euler = Euler((0.0, 0.0, 0.0))
602 for frame_i in range(num_frame):
603 bvh_rot = bvh_node.anim_data[frame_i + skip_frame][3:]
605 # apply rotation order and convert to XYZ
606 # note that the rot_order_str is reversed.
607 euler = Euler(bvh_rot, bvh_node.rot_order_str[::-1])
608 bone_rotation_matrix = euler.to_matrix().to_4x4()
609 bone_rotation_matrix = (
610 bone_rest_matrix_inv @
611 bone_rotation_matrix @
612 bone_rest_matrix
615 if len(rotate[frame_i]) == 4:
616 rotate[frame_i] = bone_rotation_matrix.to_quaternion()
617 else:
618 rotate[frame_i] = bone_rotation_matrix.to_euler(
619 pose_bone.rotation_mode, prev_euler)
620 prev_euler = rotate[frame_i]
622 # For each euler angle x, y, z (or quaternion w, x, y, z).
623 for axis_i in range(len(rotate[0])):
624 curve = action.fcurves.new(data_path=data_path, index=axis_i, action_group=bvh_node.name)
625 keyframe_points = curve.keyframe_points
626 keyframe_points.add(num_frame)
628 for frame_i in range(num_frame):
629 keyframe_points[frame_i].co = (
630 time[frame_i],
631 rotate[frame_i][axis_i],
634 for cu in action.fcurves:
635 if IMPORT_LOOP:
636 pass # 2.5 doenst have cyclic now?
638 for bez in cu.keyframe_points:
639 bez.interpolation = 'LINEAR'
641 # finally apply matrix
642 arm_ob.matrix_world = global_matrix
643 bpy.ops.object.transform_apply(location=False, rotation=True, scale=False)
645 return arm_ob
648 def load(
649 context,
650 filepath,
652 target='ARMATURE',
653 rotate_mode='NATIVE',
654 global_scale=1.0,
655 use_cyclic=False,
656 frame_start=1,
657 global_matrix=None,
658 use_fps_scale=False,
659 update_scene_fps=False,
660 update_scene_duration=False,
661 report=print,
663 import time
664 t1 = time.time()
665 print("\tparsing bvh %r..." % filepath, end="")
667 bvh_nodes, bvh_frame_time, bvh_frame_count = read_bvh(
668 context, filepath,
669 rotate_mode=rotate_mode,
670 global_scale=global_scale,
673 print("%.4f" % (time.time() - t1))
675 scene = context.scene
676 frame_orig = scene.frame_current
678 # Broken BVH handling: guess frame rate when it is not contained in the file.
679 if bvh_frame_time is None:
680 report(
681 {'WARNING'},
682 "The BVH file does not contain frame duration in its MOTION "
683 "section, assuming the BVH and Blender scene have the same "
684 "frame rate"
686 bvh_frame_time = scene.render.fps_base / scene.render.fps
687 # No need to scale the frame rate, as they're equal now anyway.
688 use_fps_scale = False
690 if update_scene_fps:
691 _update_scene_fps(context, report, bvh_frame_time)
693 # Now that we have a 1-to-1 mapping of Blender frames and BVH frames, there is no need
694 # to scale the FPS any more. It's even better not to, to prevent roundoff errors.
695 use_fps_scale = False
697 if update_scene_duration:
698 _update_scene_duration(context, report, bvh_frame_count, bvh_frame_time, frame_start, use_fps_scale)
700 t1 = time.time()
701 print("\timporting to blender...", end="")
703 bvh_name = bpy.path.display_name_from_filepath(filepath)
705 if target == 'ARMATURE':
706 bvh_node_dict2armature(
707 context, bvh_name, bvh_nodes, bvh_frame_time,
708 rotate_mode=rotate_mode,
709 frame_start=frame_start,
710 IMPORT_LOOP=use_cyclic,
711 global_matrix=global_matrix,
712 use_fps_scale=use_fps_scale,
715 elif target == 'OBJECT':
716 bvh_node_dict2objects(
717 context, bvh_name, bvh_nodes,
718 rotate_mode=rotate_mode,
719 frame_start=frame_start,
720 IMPORT_LOOP=use_cyclic,
721 # global_matrix=global_matrix, # TODO
724 else:
725 report({'ERROR'}, tip_("Invalid target %r (must be 'ARMATURE' or 'OBJECT')") % target)
726 return {'CANCELLED'}
728 print('Done in %.4f\n' % (time.time() - t1))
730 context.scene.frame_set(frame_orig)
732 return {'FINISHED'}
735 def _update_scene_fps(context, report, bvh_frame_time):
736 """Update the scene's FPS settings from the BVH, but only if the BVH contains enough info."""
738 # Broken BVH handling: prevent division by zero.
739 if bvh_frame_time == 0.0:
740 report(
741 {'WARNING'},
742 "Unable to update scene frame rate, as the BVH file "
743 "contains a zero frame duration in its MOTION section",
745 return
747 scene = context.scene
748 scene_fps = scene.render.fps / scene.render.fps_base
749 new_fps = 1.0 / bvh_frame_time
751 if scene.render.fps != new_fps or scene.render.fps_base != 1.0:
752 print("\tupdating scene FPS (was %f) to BVH FPS (%f)" % (scene_fps, new_fps))
753 scene.render.fps = int(round(new_fps))
754 scene.render.fps_base = scene.render.fps / new_fps
757 def _update_scene_duration(
758 context, report, bvh_frame_count, bvh_frame_time, frame_start,
759 use_fps_scale):
760 """Extend the scene's duration so that the BVH file fits in its entirety."""
762 if bvh_frame_count is None:
763 report(
764 {'WARNING'},
765 "Unable to extend the scene duration, as the BVH file does not "
766 "contain the number of frames in its MOTION section",
768 return
770 # Not likely, but it can happen when a BVH is just used to store an armature.
771 if bvh_frame_count == 0:
772 return
774 if use_fps_scale:
775 scene_fps = context.scene.render.fps / context.scene.render.fps_base
776 scaled_frame_count = int(ceil(bvh_frame_count * bvh_frame_time * scene_fps))
777 bvh_last_frame = frame_start + scaled_frame_count
778 else:
779 bvh_last_frame = frame_start + bvh_frame_count
781 # Only extend the scene, never shorten it.
782 if context.scene.frame_end < bvh_last_frame:
783 context.scene.frame_end = bvh_last_frame