1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # Script copyright (C) Campbell Barton
5 from math
import radians
, ceil
8 from mathutils
import Vector
, Euler
, Matrix
15 # BVH_Node type or None for no parent.
17 # A list of children of this type..
19 # Worldspace rest location for the head of this node.
21 # Localspace rest location for the head of this node.
23 # Worldspace rest location for the tail of this node.
25 # Worldspace rest location for the tail of this node.
27 # List of 6 ints, -1 for an unused channel,
28 # otherwise an index for the BVH motion data lines,
29 # loc triple then rot triple.
31 # A triple of indices as to the order rotation is applied.
32 # [0,1,2] is x/y/z - [None, None, None] if no rotation..
34 # Same as above but a string 'XYZ' format..
36 # A list one tuple's one for each frame: (locx, locy, locz, rotx, roty, rotz),
37 # euler rotation ALWAYS stored xyz order, even when native used.
39 # Convenience function, bool, same as: (channels[0] != -1 or channels[1] != -1 or channels[2] != -1).
41 # Convenience function, bool, same as: (channels[3] != -1 or channels[4] != -1 or channels[5] != -1).
43 # Index from the file, not strictly needed but nice to maintain order.
45 # Use this for whatever you want.
50 (None, None, None): 'XYZ', # XXX Dummy one, no rotation anyway!
59 def __init__(self
, name
, rest_head_world
, rest_head_local
, parent
, channels
, rot_order
, index
):
61 self
.rest_head_world
= rest_head_world
62 self
.rest_head_local
= rest_head_local
63 self
.rest_tail_world
= None
64 self
.rest_tail_local
= None
66 self
.channels
= channels
67 self
.rot_order
= tuple(rot_order
)
68 self
.rot_order_str
= BVH_Node
._eul
_order
_lookup
[self
.rot_order
]
71 # convenience functions
72 self
.has_loc
= channels
[0] != -1 or channels
[1] != -1 or channels
[2] != -1
73 self
.has_rot
= channels
[3] != -1 or channels
[4] != -1 or channels
[5] != -1
77 # List of 6 length tuples: (lx, ly, lz, rx, ry, rz)
78 # even if the channels aren't used they will just be zero.
79 self
.anim_data
= [(0, 0, 0, 0, 0, 0)]
83 "BVH name: '%s', rest_loc:(%.3f,%.3f,%.3f), rest_tail:(%.3f,%.3f,%.3f)" % (
85 *self
.rest_head_world
,
86 *self
.rest_head_world
,
91 def sorted_nodes(bvh_nodes
):
92 bvh_nodes_list
= list(bvh_nodes
.values())
93 bvh_nodes_list
.sort(key
=lambda bvh_node
: bvh_node
.index
)
97 def read_bvh(context
, file_path
, rotate_mode
='XYZ', global_scale
=1.0):
99 # Open the file for importing
100 file = open(file_path
, 'rU')
102 # Separate into a list of lists, each line a list of words.
103 file_lines
= file.readlines()
104 # Non standard carrage returns?
105 if len(file_lines
) == 1:
106 file_lines
= file_lines
[0].split('\r')
108 # Split by whitespace.
109 file_lines
= [ll
for ll
in [l
.split() for l
in file_lines
] if ll
]
111 # Create hierarchy as empties
112 if file_lines
[0][0].lower() == 'hierarchy':
113 # print 'Importing the BVH Hierarchy for:', file_path
116 raise Exception("This is not a BVH file")
118 bvh_nodes
= {None: None}
119 bvh_nodes_serial
= [None]
120 bvh_frame_count
= None
121 bvh_frame_time
= None
125 lineIdx
= 0 # An index for the file.
126 while lineIdx
< len(file_lines
) - 1:
127 if file_lines
[lineIdx
][0].lower() in {'root', 'joint'}:
129 # Join spaces into 1 word with underscores joining it.
130 if len(file_lines
[lineIdx
]) > 2:
131 file_lines
[lineIdx
][1] = '_'.join(file_lines
[lineIdx
][1:])
132 file_lines
[lineIdx
] = file_lines
[lineIdx
][:2]
134 # MAY NEED TO SUPPORT MULTIPLE ROOTS HERE! Still unsure weather multiple roots are possible?
136 # Make sure the names are unique - Object names will match joint names exactly and both will be unique.
137 name
= file_lines
[lineIdx
][1]
139 # print '%snode: %s, parent: %s' % (len(bvh_nodes_serial) * ' ', name, bvh_nodes_serial[-1])
141 lineIdx
+= 2 # Increment to the next line (Offset)
142 rest_head_local
= global_scale
* Vector((
143 float(file_lines
[lineIdx
][1]),
144 float(file_lines
[lineIdx
][2]),
145 float(file_lines
[lineIdx
][3]),
147 lineIdx
+= 1 # Increment to the next line (Channels)
149 # newChannel[Xposition, Yposition, Zposition, Xrotation, Yrotation, Zrotation]
150 # newChannel references indices to the motiondata,
151 # if not assigned then -1 refers to the last value that will be added on loading at a value of zero, this is appended
152 # We'll add a zero value onto the end of the MotionDATA so this always refers to a value.
153 my_channel
= [-1, -1, -1, -1, -1, -1]
154 my_rot_order
= [None, None, None]
156 for channel
in file_lines
[lineIdx
][2:]:
157 channel
= channel
.lower()
158 channelIndex
+= 1 # So the index points to the right channel
159 if channel
== 'xposition':
160 my_channel
[0] = channelIndex
161 elif channel
== 'yposition':
162 my_channel
[1] = channelIndex
163 elif channel
== 'zposition':
164 my_channel
[2] = channelIndex
166 elif channel
== 'xrotation':
167 my_channel
[3] = channelIndex
168 my_rot_order
[rot_count
] = 0
170 elif channel
== 'yrotation':
171 my_channel
[4] = channelIndex
172 my_rot_order
[rot_count
] = 1
174 elif channel
== 'zrotation':
175 my_channel
[5] = channelIndex
176 my_rot_order
[rot_count
] = 2
179 channels
= file_lines
[lineIdx
][2:]
181 my_parent
= bvh_nodes_serial
[-1] # account for none
183 # Apply the parents offset accumulatively
184 if my_parent
is None:
185 rest_head_world
= Vector(rest_head_local
)
187 rest_head_world
= my_parent
.rest_head_world
+ rest_head_local
189 bvh_node
= bvh_nodes
[name
] = BVH_Node(
199 # If we have another child then we can call ourselves a parent, else
200 bvh_nodes_serial
.append(bvh_node
)
202 # Account for an end node.
203 # There is sometimes a name after 'End Site' but we will ignore it.
204 if file_lines
[lineIdx
][0].lower() == 'end' and file_lines
[lineIdx
][1].lower() == 'site':
205 # Increment to the next line (Offset)
207 rest_tail
= global_scale
* Vector((
208 float(file_lines
[lineIdx
][1]),
209 float(file_lines
[lineIdx
][2]),
210 float(file_lines
[lineIdx
][3]),
213 bvh_nodes_serial
[-1].rest_tail_world
= bvh_nodes_serial
[-1].rest_head_world
+ rest_tail
214 bvh_nodes_serial
[-1].rest_tail_local
= bvh_nodes_serial
[-1].rest_head_local
+ rest_tail
216 # Just so we can remove the parents in a uniform way,
217 # the end has kids so this is a placeholder.
218 bvh_nodes_serial
.append(None)
220 if len(file_lines
[lineIdx
]) == 1 and file_lines
[lineIdx
][0] == '}': # == ['}']
221 bvh_nodes_serial
.pop() # Remove the last item
223 # End of the hierarchy. Begin the animation section of the file with
224 # the following header.
228 if len(file_lines
[lineIdx
]) == 1 and file_lines
[lineIdx
][0].lower() == 'motion':
229 lineIdx
+= 1 # Read frame count.
231 len(file_lines
[lineIdx
]) == 2 and
232 file_lines
[lineIdx
][0].lower() == 'frames:'
234 bvh_frame_count
= int(file_lines
[lineIdx
][1])
236 lineIdx
+= 1 # Read frame rate.
238 len(file_lines
[lineIdx
]) == 3 and
239 file_lines
[lineIdx
][0].lower() == 'frame' and
240 file_lines
[lineIdx
][1].lower() == 'time:'
242 bvh_frame_time
= float(file_lines
[lineIdx
][2])
244 lineIdx
+= 1 # Set the cursor to the first frame
250 # Remove the None value used for easy parent reference
255 # importing world with any order but nicer to maintain order
256 # second life expects it, which isn't to spec.
257 bvh_nodes_list
= sorted_nodes(bvh_nodes
)
259 while lineIdx
< len(file_lines
):
260 line
= file_lines
[lineIdx
]
261 for bvh_node
in bvh_nodes_list
:
262 # for bvh_node in bvh_nodes_serial:
263 lx
= ly
= lz
= rx
= ry
= rz
= 0.0
264 channels
= bvh_node
.channels
265 anim_data
= bvh_node
.anim_data
266 if channels
[0] != -1:
267 lx
= global_scale
* float(line
[channels
[0]])
269 if channels
[1] != -1:
270 ly
= global_scale
* float(line
[channels
[1]])
272 if channels
[2] != -1:
273 lz
= global_scale
* float(line
[channels
[2]])
275 if channels
[3] != -1 or channels
[4] != -1 or channels
[5] != -1:
277 rx
= radians(float(line
[channels
[3]]))
278 ry
= radians(float(line
[channels
[4]]))
279 rz
= radians(float(line
[channels
[5]]))
281 # Done importing motion data #
282 anim_data
.append((lx
, ly
, lz
, rx
, ry
, rz
))
286 for bvh_node
in bvh_nodes_list
:
287 bvh_node_parent
= bvh_node
.parent
289 bvh_node_parent
.children
.append(bvh_node
)
291 # Now set the tip of each bvh_node
292 for bvh_node
in bvh_nodes_list
:
294 if not bvh_node
.rest_tail_world
:
295 if len(bvh_node
.children
) == 0:
296 # could just fail here, but rare BVH files have childless nodes
297 bvh_node
.rest_tail_world
= Vector(bvh_node
.rest_head_world
)
298 bvh_node
.rest_tail_local
= Vector(bvh_node
.rest_head_local
)
299 elif len(bvh_node
.children
) == 1:
300 bvh_node
.rest_tail_world
= Vector(bvh_node
.children
[0].rest_head_world
)
301 bvh_node
.rest_tail_local
= bvh_node
.rest_head_local
+ bvh_node
.children
[0].rest_head_local
303 # allow this, see above
304 # if not bvh_node.children:
305 # raise Exception("bvh node has no end and no children. bad file")
307 # Removed temp for now
308 rest_tail_world
= Vector((0.0, 0.0, 0.0))
309 rest_tail_local
= Vector((0.0, 0.0, 0.0))
310 for bvh_node_child
in bvh_node
.children
:
311 rest_tail_world
+= bvh_node_child
.rest_head_world
312 rest_tail_local
+= bvh_node_child
.rest_head_local
314 bvh_node
.rest_tail_world
= rest_tail_world
* (1.0 / len(bvh_node
.children
))
315 bvh_node
.rest_tail_local
= rest_tail_local
* (1.0 / len(bvh_node
.children
))
317 # Make sure tail isn't the same location as the head.
318 if (bvh_node
.rest_tail_local
- bvh_node
.rest_head_local
).length
<= 0.001 * global_scale
:
319 print("\tzero length node found:", bvh_node
.name
)
320 bvh_node
.rest_tail_local
.y
= bvh_node
.rest_tail_local
.y
+ global_scale
/ 10
321 bvh_node
.rest_tail_world
.y
= bvh_node
.rest_tail_world
.y
+ global_scale
/ 10
323 return bvh_nodes
, bvh_frame_time
, bvh_frame_count
326 def bvh_node_dict2objects(context
, bvh_name
, bvh_nodes
, rotate_mode
='NATIVE', frame_start
=1, IMPORT_LOOP
=False):
331 scene
= context
.scene
332 for obj
in scene
.objects
:
333 obj
.select_set(False)
338 obj
= bpy
.data
.objects
.new(name
, None)
339 context
.collection
.objects
.link(obj
)
344 obj
.empty_display_type
= 'CUBE'
345 obj
.empty_display_size
= 0.1
350 for name
, bvh_node
in bvh_nodes
.items():
351 bvh_node
.temp
= add_ob(name
)
352 bvh_node
.temp
.rotation_mode
= bvh_node
.rot_order_str
[::-1]
355 for bvh_node
in bvh_nodes
.values():
356 for bvh_node_child
in bvh_node
.children
:
357 bvh_node_child
.temp
.parent
= bvh_node
.temp
360 for bvh_node
in bvh_nodes
.values():
361 # Make relative to parents offset
362 bvh_node
.temp
.location
= bvh_node
.rest_head_local
365 for name
, bvh_node
in bvh_nodes
.items():
366 if not bvh_node
.children
:
367 ob_end
= add_ob(name
+ '_end')
368 ob_end
.parent
= bvh_node
.temp
369 ob_end
.location
= bvh_node
.rest_tail_world
- bvh_node
.rest_head_world
371 for name
, bvh_node
in bvh_nodes
.items():
374 for frame_current
in range(len(bvh_node
.anim_data
)):
376 lx
, ly
, lz
, rx
, ry
, rz
= bvh_node
.anim_data
[frame_current
]
379 obj
.delta_location
= Vector((lx
, ly
, lz
)) - bvh_node
.rest_head_world
380 obj
.keyframe_insert("delta_location", index
=-1, frame
=frame_start
+ frame_current
)
383 obj
.delta_rotation_euler
= rx
, ry
, rz
384 obj
.keyframe_insert("delta_rotation_euler", index
=-1, frame
=frame_start
+ frame_current
)
389 def bvh_node_dict2armature(
404 # Add the new armature,
405 scene
= context
.scene
406 for obj
in scene
.objects
:
407 obj
.select_set(False)
409 arm_data
= bpy
.data
.armatures
.new(bvh_name
)
410 arm_ob
= bpy
.data
.objects
.new(bvh_name
, arm_data
)
412 context
.collection
.objects
.link(arm_ob
)
414 arm_ob
.select_set(True)
415 context
.view_layer
.objects
.active
= arm_ob
417 bpy
.ops
.object.mode_set(mode
='OBJECT', toggle
=False)
418 bpy
.ops
.object.mode_set(mode
='EDIT', toggle
=False)
420 bvh_nodes_list
= sorted_nodes(bvh_nodes
)
422 # Get the average bone length for zero length bones, we may not use this.
423 average_bone_length
= 0.0
425 for bvh_node
in bvh_nodes_list
:
426 l
= (bvh_node
.rest_head_local
- bvh_node
.rest_tail_local
).length
428 average_bone_length
+= l
431 # Very rare cases all bones could be zero length???
432 if not average_bone_length
:
433 average_bone_length
= 0.1
436 average_bone_length
= average_bone_length
/ nonzero_count
438 # XXX, annoying, remove bone.
439 while arm_data
.edit_bones
:
440 arm_ob
.edit_bones
.remove(arm_data
.edit_bones
[-1])
443 for bvh_node
in bvh_nodes_list
:
446 bone
= bvh_node
.temp
= arm_data
.edit_bones
.new(bvh_node
.name
)
448 bone
.head
= bvh_node
.rest_head_world
449 bone
.tail
= bvh_node
.rest_tail_world
451 # Zero Length Bones! (an exceptional case)
452 if (bone
.head
- bone
.tail
).length
< 0.001:
453 print("\tzero length bone found:", bone
.name
)
455 ofs
= bvh_node
.parent
.rest_head_local
- bvh_node
.parent
.rest_tail_local
456 if ofs
.length
: # is our parent zero length also?? unlikely
457 bone
.tail
= bone
.tail
- ofs
459 bone
.tail
.y
= bone
.tail
.y
+ average_bone_length
461 bone
.tail
.y
= bone
.tail
.y
+ average_bone_length
463 ZERO_AREA_BONES
.append(bone
.name
)
465 for bvh_node
in bvh_nodes_list
:
467 # bvh_node.temp is the Editbone
469 # Set the bone parent
470 bvh_node
.temp
.parent
= bvh_node
.parent
.temp
472 # Set the connection state
474 (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)
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
506 context
.view_layer
.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)
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
)
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.
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
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
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
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
, action_group
=bvh_node
.name
)
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
= (
577 location
[frame_i
][axis_i
],
584 if 'QUATERNION' == rotate_mode
:
585 rotate
= [(1.0, 0.0, 0.0, 0.0)] * num_frame
586 data_path
= ('pose.bones["%s"].rotation_quaternion'
589 rotate
= [(0.0, 0.0, 0.0)] * num_frame
590 data_path
= ('pose.bones["%s"].rotation_euler' %
593 prev_euler
= Euler((0.0, 0.0, 0.0))
594 for frame_i
in range(num_frame
):
595 bvh_rot
= bvh_node
.anim_data
[frame_i
+ skip_frame
][3:]
597 # apply rotation order and convert to XYZ
598 # note that the rot_order_str is reversed.
599 euler
= Euler(bvh_rot
, bvh_node
.rot_order_str
[::-1])
600 bone_rotation_matrix
= euler
.to_matrix().to_4x4()
601 bone_rotation_matrix
= (
602 bone_rest_matrix_inv
@
603 bone_rotation_matrix
@
607 if len(rotate
[frame_i
]) == 4:
608 rotate
[frame_i
] = bone_rotation_matrix
.to_quaternion()
610 rotate
[frame_i
] = bone_rotation_matrix
.to_euler(
611 pose_bone
.rotation_mode
, prev_euler
)
612 prev_euler
= rotate
[frame_i
]
614 # For each euler angle x, y, z (or quaternion w, x, y, z).
615 for axis_i
in range(len(rotate
[0])):
616 curve
= action
.fcurves
.new(data_path
=data_path
, index
=axis_i
, action_group
=bvh_node
.name
)
617 keyframe_points
= curve
.keyframe_points
618 keyframe_points
.add(num_frame
)
620 for frame_i
in range(num_frame
):
621 keyframe_points
[frame_i
].co
= (
623 rotate
[frame_i
][axis_i
],
626 for cu
in action
.fcurves
:
628 pass # 2.5 doenst have cyclic now?
630 for bez
in cu
.keyframe_points
:
631 bez
.interpolation
= 'LINEAR'
633 # finally apply matrix
634 arm_ob
.matrix_world
= global_matrix
635 bpy
.ops
.object.transform_apply(location
=False, rotation
=True, scale
=False)
645 rotate_mode
='NATIVE',
651 update_scene_fps
=False,
652 update_scene_duration
=False,
657 print("\tparsing bvh %r..." % filepath
, end
="")
659 bvh_nodes
, bvh_frame_time
, bvh_frame_count
= read_bvh(
661 rotate_mode
=rotate_mode
,
662 global_scale
=global_scale
,
665 print("%.4f" % (time
.time() - t1
))
667 scene
= context
.scene
668 frame_orig
= scene
.frame_current
670 # Broken BVH handling: guess frame rate when it is not contained in the file.
671 if bvh_frame_time
is None:
674 "The BVH file does not contain frame duration in its MOTION "
675 "section, assuming the BVH and Blender scene have the same "
678 bvh_frame_time
= scene
.render
.fps_base
/ scene
.render
.fps
679 # No need to scale the frame rate, as they're equal now anyway.
680 use_fps_scale
= False
683 _update_scene_fps(context
, report
, bvh_frame_time
)
685 # Now that we have a 1-to-1 mapping of Blender frames and BVH frames, there is no need
686 # to scale the FPS any more. It's even better not to, to prevent roundoff errors.
687 use_fps_scale
= False
689 if update_scene_duration
:
690 _update_scene_duration(context
, report
, bvh_frame_count
, bvh_frame_time
, frame_start
, use_fps_scale
)
693 print("\timporting to blender...", end
="")
695 bvh_name
= bpy
.path
.display_name_from_filepath(filepath
)
697 if target
== 'ARMATURE':
698 bvh_node_dict2armature(
699 context
, bvh_name
, bvh_nodes
, bvh_frame_time
,
700 rotate_mode
=rotate_mode
,
701 frame_start
=frame_start
,
702 IMPORT_LOOP
=use_cyclic
,
703 global_matrix
=global_matrix
,
704 use_fps_scale
=use_fps_scale
,
707 elif target
== 'OBJECT':
708 bvh_node_dict2objects(
709 context
, bvh_name
, bvh_nodes
,
710 rotate_mode
=rotate_mode
,
711 frame_start
=frame_start
,
712 IMPORT_LOOP
=use_cyclic
,
713 # global_matrix=global_matrix, # TODO
717 report({'ERROR'}, "Invalid target %r (must be 'ARMATURE' or 'OBJECT')" % target
)
720 print('Done in %.4f\n' % (time
.time() - t1
))
722 context
.scene
.frame_set(frame_orig
)
727 def _update_scene_fps(context
, report
, bvh_frame_time
):
728 """Update the scene's FPS settings from the BVH, but only if the BVH contains enough info."""
730 # Broken BVH handling: prevent division by zero.
731 if bvh_frame_time
== 0.0:
734 "Unable to update scene frame rate, as the BVH file "
735 "contains a zero frame duration in its MOTION section",
739 scene
= context
.scene
740 scene_fps
= scene
.render
.fps
/ scene
.render
.fps_base
741 new_fps
= 1.0 / bvh_frame_time
743 if scene
.render
.fps
!= new_fps
or scene
.render
.fps_base
!= 1.0:
744 print("\tupdating scene FPS (was %f) to BVH FPS (%f)" % (scene_fps
, new_fps
))
745 scene
.render
.fps
= new_fps
746 scene
.render
.fps_base
= 1.0
749 def _update_scene_duration(
750 context
, report
, bvh_frame_count
, bvh_frame_time
, frame_start
,
752 """Extend the scene's duration so that the BVH file fits in its entirety."""
754 if bvh_frame_count
is None:
757 "Unable to extend the scene duration, as the BVH file does not "
758 "contain the number of frames in its MOTION section",
762 # Not likely, but it can happen when a BVH is just used to store an armature.
763 if bvh_frame_count
== 0:
767 scene_fps
= context
.scene
.render
.fps
/ context
.scene
.render
.fps_base
768 scaled_frame_count
= int(ceil(bvh_frame_count
* bvh_frame_time
* scene_fps
))
769 bvh_last_frame
= frame_start
+ scaled_frame_count
771 bvh_last_frame
= frame_start
+ bvh_frame_count
773 # Only extend the scene, never shorten it.
774 if context
.scene
.frame_end
< bvh_last_frame
:
775 context
.scene
.frame_end
= bvh_last_frame