1 # SPDX-License-Identifier: GPL-2.0-or-later
5 # Script copyright (C) Campbell Barton
7 from math
import radians
, ceil
10 from mathutils
import Vector
, Euler
, Matrix
17 # BVH_Node type or None for no parent.
19 # A list of children of this type..
21 # Worldspace rest location for the head of this node.
23 # Localspace rest location for the head of this node.
25 # Worldspace rest location for the tail of this node.
27 # Worldspace rest location for the tail of this node.
29 # List of 6 ints, -1 for an unused channel,
30 # otherwise an index for the BVH motion data lines,
31 # loc triple then rot triple.
33 # A triple of indices as to the order rotation is applied.
34 # [0,1,2] is x/y/z - [None, None, None] if no rotation..
36 # Same as above but a string 'XYZ' format..
38 # A list one tuple's one for each frame: (locx, locy, locz, rotx, roty, rotz),
39 # euler rotation ALWAYS stored xyz order, even when native used.
41 # Convenience function, bool, same as: (channels[0] != -1 or channels[1] != -1 or channels[2] != -1).
43 # Convenience function, bool, same as: (channels[3] != -1 or channels[4] != -1 or channels[5] != -1).
45 # Index from the file, not strictly needed but nice to maintain order.
47 # Use this for whatever you want.
52 (None, None, None): 'XYZ', # XXX Dummy one, no rotation anyway!
61 def __init__(self
, name
, rest_head_world
, rest_head_local
, parent
, channels
, rot_order
, index
):
63 self
.rest_head_world
= rest_head_world
64 self
.rest_head_local
= rest_head_local
65 self
.rest_tail_world
= None
66 self
.rest_tail_local
= None
68 self
.channels
= channels
69 self
.rot_order
= tuple(rot_order
)
70 self
.rot_order_str
= BVH_Node
._eul
_order
_lookup
[self
.rot_order
]
73 # convenience functions
74 self
.has_loc
= channels
[0] != -1 or channels
[1] != -1 or channels
[2] != -1
75 self
.has_rot
= channels
[3] != -1 or channels
[4] != -1 or channels
[5] != -1
79 # List of 6 length tuples: (lx, ly, lz, rx, ry, rz)
80 # even if the channels aren't used they will just be zero.
81 self
.anim_data
= [(0, 0, 0, 0, 0, 0)]
85 "BVH name: '%s', rest_loc:(%.3f,%.3f,%.3f), rest_tail:(%.3f,%.3f,%.3f)" % (
87 *self
.rest_head_world
,
88 *self
.rest_head_world
,
93 def sorted_nodes(bvh_nodes
):
94 bvh_nodes_list
= list(bvh_nodes
.values())
95 bvh_nodes_list
.sort(key
=lambda bvh_node
: bvh_node
.index
)
99 def read_bvh(context
, file_path
, rotate_mode
='XYZ', global_scale
=1.0):
101 # Open the file for importing
102 file = open(file_path
, 'rU')
104 # Separate into a list of lists, each line a list of words.
105 file_lines
= file.readlines()
106 # Non standard carrage returns?
107 if len(file_lines
) == 1:
108 file_lines
= file_lines
[0].split('\r')
110 # Split by whitespace.
111 file_lines
= [ll
for ll
in [l
.split() for l
in file_lines
] if ll
]
113 # Create hierarchy as empties
114 if file_lines
[0][0].lower() == 'hierarchy':
115 # print 'Importing the BVH Hierarchy for:', file_path
118 raise Exception("This is not a BVH file")
120 bvh_nodes
= {None: None}
121 bvh_nodes_serial
= [None]
122 bvh_frame_count
= None
123 bvh_frame_time
= None
127 lineIdx
= 0 # An index for the file.
128 while lineIdx
< len(file_lines
) - 1:
129 if file_lines
[lineIdx
][0].lower() in {'root', 'joint'}:
131 # Join spaces into 1 word with underscores joining it.
132 if len(file_lines
[lineIdx
]) > 2:
133 file_lines
[lineIdx
][1] = '_'.join(file_lines
[lineIdx
][1:])
134 file_lines
[lineIdx
] = file_lines
[lineIdx
][:2]
136 # MAY NEED TO SUPPORT MULTIPLE ROOTS HERE! Still unsure weather multiple roots are possible?
138 # Make sure the names are unique - Object names will match joint names exactly and both will be unique.
139 name
= file_lines
[lineIdx
][1]
141 # print '%snode: %s, parent: %s' % (len(bvh_nodes_serial) * ' ', name, bvh_nodes_serial[-1])
143 lineIdx
+= 2 # Increment to the next line (Offset)
144 rest_head_local
= global_scale
* Vector((
145 float(file_lines
[lineIdx
][1]),
146 float(file_lines
[lineIdx
][2]),
147 float(file_lines
[lineIdx
][3]),
149 lineIdx
+= 1 # Increment to the next line (Channels)
151 # newChannel[Xposition, Yposition, Zposition, Xrotation, Yrotation, Zrotation]
152 # newChannel references indices to the motiondata,
153 # if not assigned then -1 refers to the last value that will be added on loading at a value of zero, this is appended
154 # We'll add a zero value onto the end of the MotionDATA so this always refers to a value.
155 my_channel
= [-1, -1, -1, -1, -1, -1]
156 my_rot_order
= [None, None, None]
158 for channel
in file_lines
[lineIdx
][2:]:
159 channel
= channel
.lower()
160 channelIndex
+= 1 # So the index points to the right channel
161 if channel
== 'xposition':
162 my_channel
[0] = channelIndex
163 elif channel
== 'yposition':
164 my_channel
[1] = channelIndex
165 elif channel
== 'zposition':
166 my_channel
[2] = channelIndex
168 elif channel
== 'xrotation':
169 my_channel
[3] = channelIndex
170 my_rot_order
[rot_count
] = 0
172 elif channel
== 'yrotation':
173 my_channel
[4] = channelIndex
174 my_rot_order
[rot_count
] = 1
176 elif channel
== 'zrotation':
177 my_channel
[5] = channelIndex
178 my_rot_order
[rot_count
] = 2
181 channels
= file_lines
[lineIdx
][2:]
183 my_parent
= bvh_nodes_serial
[-1] # account for none
185 # Apply the parents offset accumulatively
186 if my_parent
is None:
187 rest_head_world
= Vector(rest_head_local
)
189 rest_head_world
= my_parent
.rest_head_world
+ rest_head_local
191 bvh_node
= bvh_nodes
[name
] = BVH_Node(
201 # If we have another child then we can call ourselves a parent, else
202 bvh_nodes_serial
.append(bvh_node
)
204 # Account for an end node.
205 # There is sometimes a name after 'End Site' but we will ignore it.
206 if file_lines
[lineIdx
][0].lower() == 'end' and file_lines
[lineIdx
][1].lower() == 'site':
207 # Increment to the next line (Offset)
209 rest_tail
= global_scale
* Vector((
210 float(file_lines
[lineIdx
][1]),
211 float(file_lines
[lineIdx
][2]),
212 float(file_lines
[lineIdx
][3]),
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.
230 if len(file_lines
[lineIdx
]) == 1 and file_lines
[lineIdx
][0].lower() == 'motion':
231 lineIdx
+= 1 # Read frame count.
233 len(file_lines
[lineIdx
]) == 2 and
234 file_lines
[lineIdx
][0].lower() == 'frames:'
236 bvh_frame_count
= int(file_lines
[lineIdx
][1])
238 lineIdx
+= 1 # Read frame rate.
240 len(file_lines
[lineIdx
]) == 3 and
241 file_lines
[lineIdx
][0].lower() == 'frame' and
242 file_lines
[lineIdx
][1].lower() == 'time:'
244 bvh_frame_time
= float(file_lines
[lineIdx
][2])
246 lineIdx
+= 1 # Set the cursor to the first frame
252 # Remove the None value used for easy parent reference
257 # importing world with any order but nicer to maintain order
258 # second life expects it, which isn't to spec.
259 bvh_nodes_list
= sorted_nodes(bvh_nodes
)
261 while lineIdx
< len(file_lines
):
262 line
= file_lines
[lineIdx
]
263 for bvh_node
in bvh_nodes_list
:
264 # for bvh_node in bvh_nodes_serial:
265 lx
= ly
= lz
= rx
= ry
= rz
= 0.0
266 channels
= bvh_node
.channels
267 anim_data
= bvh_node
.anim_data
268 if channels
[0] != -1:
269 lx
= global_scale
* float(line
[channels
[0]])
271 if channels
[1] != -1:
272 ly
= global_scale
* float(line
[channels
[1]])
274 if channels
[2] != -1:
275 lz
= global_scale
* float(line
[channels
[2]])
277 if channels
[3] != -1 or channels
[4] != -1 or channels
[5] != -1:
279 rx
= radians(float(line
[channels
[3]]))
280 ry
= radians(float(line
[channels
[4]]))
281 rz
= radians(float(line
[channels
[5]]))
283 # Done importing motion data #
284 anim_data
.append((lx
, ly
, lz
, rx
, ry
, rz
))
288 for bvh_node
in bvh_nodes_list
:
289 bvh_node_parent
= bvh_node
.parent
291 bvh_node_parent
.children
.append(bvh_node
)
293 # Now set the tip of each bvh_node
294 for bvh_node
in bvh_nodes_list
:
296 if not bvh_node
.rest_tail_world
:
297 if len(bvh_node
.children
) == 0:
298 # could just fail here, but rare BVH files have childless nodes
299 bvh_node
.rest_tail_world
= Vector(bvh_node
.rest_head_world
)
300 bvh_node
.rest_tail_local
= Vector(bvh_node
.rest_head_local
)
301 elif len(bvh_node
.children
) == 1:
302 bvh_node
.rest_tail_world
= Vector(bvh_node
.children
[0].rest_head_world
)
303 bvh_node
.rest_tail_local
= bvh_node
.rest_head_local
+ bvh_node
.children
[0].rest_head_local
305 # allow this, see above
306 # if not bvh_node.children:
307 # raise Exception("bvh node has no end and no children. bad file")
309 # Removed temp for now
310 rest_tail_world
= Vector((0.0, 0.0, 0.0))
311 rest_tail_local
= Vector((0.0, 0.0, 0.0))
312 for bvh_node_child
in bvh_node
.children
:
313 rest_tail_world
+= bvh_node_child
.rest_head_world
314 rest_tail_local
+= bvh_node_child
.rest_head_local
316 bvh_node
.rest_tail_world
= rest_tail_world
* (1.0 / len(bvh_node
.children
))
317 bvh_node
.rest_tail_local
= rest_tail_local
* (1.0 / len(bvh_node
.children
))
319 # Make sure tail isn't the same location as the head.
320 if (bvh_node
.rest_tail_local
- bvh_node
.rest_head_local
).length
<= 0.001 * global_scale
:
321 print("\tzero length node found:", bvh_node
.name
)
322 bvh_node
.rest_tail_local
.y
= bvh_node
.rest_tail_local
.y
+ global_scale
/ 10
323 bvh_node
.rest_tail_world
.y
= bvh_node
.rest_tail_world
.y
+ global_scale
/ 10
325 return bvh_nodes
, bvh_frame_time
, bvh_frame_count
328 def bvh_node_dict2objects(context
, bvh_name
, bvh_nodes
, rotate_mode
='NATIVE', frame_start
=1, IMPORT_LOOP
=False):
333 scene
= context
.scene
334 for obj
in scene
.objects
:
335 obj
.select_set(False)
340 obj
= bpy
.data
.objects
.new(name
, None)
341 context
.collection
.objects
.link(obj
)
346 obj
.empty_display_type
= 'CUBE'
347 obj
.empty_display_size
= 0.1
352 for name
, bvh_node
in bvh_nodes
.items():
353 bvh_node
.temp
= add_ob(name
)
354 bvh_node
.temp
.rotation_mode
= bvh_node
.rot_order_str
[::-1]
357 for bvh_node
in bvh_nodes
.values():
358 for bvh_node_child
in bvh_node
.children
:
359 bvh_node_child
.temp
.parent
= bvh_node
.temp
362 for bvh_node
in bvh_nodes
.values():
363 # Make relative to parents offset
364 bvh_node
.temp
.location
= bvh_node
.rest_head_local
367 for name
, bvh_node
in bvh_nodes
.items():
368 if not bvh_node
.children
:
369 ob_end
= add_ob(name
+ '_end')
370 ob_end
.parent
= bvh_node
.temp
371 ob_end
.location
= bvh_node
.rest_tail_world
- bvh_node
.rest_head_world
373 for name
, bvh_node
in bvh_nodes
.items():
376 for frame_current
in range(len(bvh_node
.anim_data
)):
378 lx
, ly
, lz
, rx
, ry
, rz
= bvh_node
.anim_data
[frame_current
]
381 obj
.delta_location
= Vector((lx
, ly
, lz
)) - bvh_node
.rest_head_world
382 obj
.keyframe_insert("delta_location", index
=-1, frame
=frame_start
+ frame_current
)
385 obj
.delta_rotation_euler
= rx
, ry
, rz
386 obj
.keyframe_insert("delta_rotation_euler", index
=-1, frame
=frame_start
+ frame_current
)
391 def bvh_node_dict2armature(
406 # Add the new armature,
407 scene
= context
.scene
408 for obj
in scene
.objects
:
409 obj
.select_set(False)
411 arm_data
= bpy
.data
.armatures
.new(bvh_name
)
412 arm_ob
= bpy
.data
.objects
.new(bvh_name
, arm_data
)
414 context
.collection
.objects
.link(arm_ob
)
416 arm_ob
.select_set(True)
417 context
.view_layer
.objects
.active
= arm_ob
419 bpy
.ops
.object.mode_set(mode
='OBJECT', toggle
=False)
420 bpy
.ops
.object.mode_set(mode
='EDIT', toggle
=False)
422 bvh_nodes_list
= sorted_nodes(bvh_nodes
)
424 # Get the average bone length for zero length bones, we may not use this.
425 average_bone_length
= 0.0
427 for bvh_node
in bvh_nodes_list
:
428 l
= (bvh_node
.rest_head_local
- bvh_node
.rest_tail_local
).length
430 average_bone_length
+= l
433 # Very rare cases all bones could be zero length???
434 if not average_bone_length
:
435 average_bone_length
= 0.1
438 average_bone_length
= average_bone_length
/ nonzero_count
440 # XXX, annoying, remove bone.
441 while arm_data
.edit_bones
:
442 arm_ob
.edit_bones
.remove(arm_data
.edit_bones
[-1])
445 for bvh_node
in bvh_nodes_list
:
448 bone
= bvh_node
.temp
= arm_data
.edit_bones
.new(bvh_node
.name
)
450 bone
.head
= bvh_node
.rest_head_world
451 bone
.tail
= bvh_node
.rest_tail_world
453 # Zero Length Bones! (an exceptional case)
454 if (bone
.head
- bone
.tail
).length
< 0.001:
455 print("\tzero length bone found:", bone
.name
)
457 ofs
= bvh_node
.parent
.rest_head_local
- bvh_node
.parent
.rest_tail_local
458 if ofs
.length
: # is our parent zero length also?? unlikely
459 bone
.tail
= bone
.tail
- ofs
461 bone
.tail
.y
= bone
.tail
.y
+ average_bone_length
463 bone
.tail
.y
= bone
.tail
.y
+ average_bone_length
465 ZERO_AREA_BONES
.append(bone
.name
)
467 for bvh_node
in bvh_nodes_list
:
469 # bvh_node.temp is the Editbone
471 # Set the bone parent
472 bvh_node
.temp
.parent
= bvh_node
.parent
.temp
474 # Set the connection state
476 (not bvh_node
.has_loc
) and
477 (bvh_node
.parent
.temp
.name
not in ZERO_AREA_BONES
) and
478 (bvh_node
.parent
.rest_tail_local
== bvh_node
.rest_head_local
)
480 bvh_node
.temp
.use_connect
= True
482 # Replace the editbone with the editbone name,
483 # to avoid memory errors accessing the editbone outside editmode
484 for bvh_node
in bvh_nodes_list
:
485 bvh_node
.temp
= bvh_node
.temp
.name
487 # Now Apply the animation to the armature
489 # Get armature animation data
490 bpy
.ops
.object.mode_set(mode
='OBJECT', toggle
=False)
493 pose_bones
= pose
.bones
495 if rotate_mode
== 'NATIVE':
496 for bvh_node
in bvh_nodes_list
:
497 bone_name
= bvh_node
.temp
# may not be the same name as the bvh_node, could have been shortened.
498 pose_bone
= pose_bones
[bone_name
]
499 pose_bone
.rotation_mode
= bvh_node
.rot_order_str
501 elif rotate_mode
!= 'QUATERNION':
502 for pose_bone
in pose_bones
:
503 pose_bone
.rotation_mode
= rotate_mode
508 context
.view_layer
.update()
510 arm_ob
.animation_data_create()
511 action
= bpy
.data
.actions
.new(name
=bvh_name
)
512 arm_ob
.animation_data
.action
= action
514 # Replace the bvh_node.temp (currently an editbone)
515 # With a tuple (pose_bone, armature_bone, bone_rest_matrix, bone_rest_matrix_inv)
517 for bvh_node
in bvh_nodes_list
:
518 bone_name
= bvh_node
.temp
# may not be the same name as the bvh_node, could have been shortened.
519 pose_bone
= pose_bones
[bone_name
]
520 rest_bone
= arm_data
.bones
[bone_name
]
521 bone_rest_matrix
= rest_bone
.matrix_local
.to_3x3()
523 bone_rest_matrix_inv
= Matrix(bone_rest_matrix
)
524 bone_rest_matrix_inv
.invert()
526 bone_rest_matrix_inv
.resize_4x4()
527 bone_rest_matrix
.resize_4x4()
528 bvh_node
.temp
= (pose_bone
, bone
, bone_rest_matrix
, bone_rest_matrix_inv
)
531 num_frame
= len(bvh_node
.anim_data
)
533 # Choose to skip some frames at the beginning. Frame 0 is the rest pose
534 # used internally by this importer. Frame 1, by convention, is also often
535 # the rest pose of the skeleton exported by the motion capture system.
537 if num_frame
> skip_frame
:
538 num_frame
= num_frame
- skip_frame
540 # Create a shared time axis for all animation curves.
541 time
= [float(frame_start
)] * num_frame
543 dt
= scene
.render
.fps
* bvh_frame_time
544 for frame_i
in range(1, num_frame
):
545 time
[frame_i
] += float(frame_i
) * dt
547 for frame_i
in range(1, num_frame
):
548 time
[frame_i
] += float(frame_i
)
550 # print("bvh_frame_time = %f, dt = %f, num_frame = %d"
551 # % (bvh_frame_time, dt, num_frame]))
553 for i
, bvh_node
in enumerate(bvh_nodes_list
):
554 pose_bone
, bone
, bone_rest_matrix
, bone_rest_matrix_inv
= bvh_node
.temp
557 # Not sure if there is a way to query this or access it in the
558 # PoseBone structure.
559 data_path
= 'pose.bones["%s"].location' % pose_bone
.name
561 location
= [(0.0, 0.0, 0.0)] * num_frame
562 for frame_i
in range(num_frame
):
563 bvh_loc
= bvh_node
.anim_data
[frame_i
+ skip_frame
][:3]
565 bone_translate_matrix
= Matrix
.Translation(
566 Vector(bvh_loc
) - bvh_node
.rest_head_local
)
567 location
[frame_i
] = (bone_rest_matrix_inv
@
568 bone_translate_matrix
).to_translation()
570 # For each location x, y, z.
571 for axis_i
in range(3):
572 curve
= action
.fcurves
.new(data_path
=data_path
, index
=axis_i
, action_group
=bvh_node
.name
)
573 keyframe_points
= curve
.keyframe_points
574 keyframe_points
.add(num_frame
)
576 for frame_i
in range(num_frame
):
577 keyframe_points
[frame_i
].co
= (
579 location
[frame_i
][axis_i
],
586 if 'QUATERNION' == rotate_mode
:
587 rotate
= [(1.0, 0.0, 0.0, 0.0)] * num_frame
588 data_path
= ('pose.bones["%s"].rotation_quaternion'
591 rotate
= [(0.0, 0.0, 0.0)] * num_frame
592 data_path
= ('pose.bones["%s"].rotation_euler' %
595 prev_euler
= Euler((0.0, 0.0, 0.0))
596 for frame_i
in range(num_frame
):
597 bvh_rot
= bvh_node
.anim_data
[frame_i
+ skip_frame
][3:]
599 # apply rotation order and convert to XYZ
600 # note that the rot_order_str is reversed.
601 euler
= Euler(bvh_rot
, bvh_node
.rot_order_str
[::-1])
602 bone_rotation_matrix
= euler
.to_matrix().to_4x4()
603 bone_rotation_matrix
= (
604 bone_rest_matrix_inv
@
605 bone_rotation_matrix
@
609 if len(rotate
[frame_i
]) == 4:
610 rotate
[frame_i
] = bone_rotation_matrix
.to_quaternion()
612 rotate
[frame_i
] = bone_rotation_matrix
.to_euler(
613 pose_bone
.rotation_mode
, prev_euler
)
614 prev_euler
= rotate
[frame_i
]
616 # For each euler angle x, y, z (or quaternion w, x, y, z).
617 for axis_i
in range(len(rotate
[0])):
618 curve
= action
.fcurves
.new(data_path
=data_path
, index
=axis_i
, action_group
=bvh_node
.name
)
619 keyframe_points
= curve
.keyframe_points
620 keyframe_points
.add(num_frame
)
622 for frame_i
in range(num_frame
):
623 keyframe_points
[frame_i
].co
= (
625 rotate
[frame_i
][axis_i
],
628 for cu
in action
.fcurves
:
630 pass # 2.5 doenst have cyclic now?
632 for bez
in cu
.keyframe_points
:
633 bez
.interpolation
= 'LINEAR'
635 # finally apply matrix
636 arm_ob
.matrix_world
= global_matrix
637 bpy
.ops
.object.transform_apply(location
=False, rotation
=True, scale
=False)
647 rotate_mode
='NATIVE',
653 update_scene_fps
=False,
654 update_scene_duration
=False,
659 print("\tparsing bvh %r..." % filepath
, end
="")
661 bvh_nodes
, bvh_frame_time
, bvh_frame_count
= read_bvh(
663 rotate_mode
=rotate_mode
,
664 global_scale
=global_scale
,
667 print("%.4f" % (time
.time() - t1
))
669 scene
= context
.scene
670 frame_orig
= scene
.frame_current
672 # Broken BVH handling: guess frame rate when it is not contained in the file.
673 if bvh_frame_time
is None:
676 "The BVH file does not contain frame duration in its MOTION "
677 "section, assuming the BVH and Blender scene have the same "
680 bvh_frame_time
= scene
.render
.fps_base
/ scene
.render
.fps
681 # No need to scale the frame rate, as they're equal now anyway.
682 use_fps_scale
= False
685 _update_scene_fps(context
, report
, bvh_frame_time
)
687 # Now that we have a 1-to-1 mapping of Blender frames and BVH frames, there is no need
688 # to scale the FPS any more. It's even better not to, to prevent roundoff errors.
689 use_fps_scale
= False
691 if update_scene_duration
:
692 _update_scene_duration(context
, report
, bvh_frame_count
, bvh_frame_time
, frame_start
, use_fps_scale
)
695 print("\timporting to blender...", end
="")
697 bvh_name
= bpy
.path
.display_name_from_filepath(filepath
)
699 if target
== 'ARMATURE':
700 bvh_node_dict2armature(
701 context
, bvh_name
, bvh_nodes
, bvh_frame_time
,
702 rotate_mode
=rotate_mode
,
703 frame_start
=frame_start
,
704 IMPORT_LOOP
=use_cyclic
,
705 global_matrix
=global_matrix
,
706 use_fps_scale
=use_fps_scale
,
709 elif target
== 'OBJECT':
710 bvh_node_dict2objects(
711 context
, bvh_name
, bvh_nodes
,
712 rotate_mode
=rotate_mode
,
713 frame_start
=frame_start
,
714 IMPORT_LOOP
=use_cyclic
,
715 # global_matrix=global_matrix, # TODO
719 report({'ERROR'}, "Invalid target %r (must be 'ARMATURE' or 'OBJECT')" % target
)
722 print('Done in %.4f\n' % (time
.time() - t1
))
724 context
.scene
.frame_set(frame_orig
)
729 def _update_scene_fps(context
, report
, bvh_frame_time
):
730 """Update the scene's FPS settings from the BVH, but only if the BVH contains enough info."""
732 # Broken BVH handling: prevent division by zero.
733 if bvh_frame_time
== 0.0:
736 "Unable to update scene frame rate, as the BVH file "
737 "contains a zero frame duration in its MOTION section",
741 scene
= context
.scene
742 scene_fps
= scene
.render
.fps
/ scene
.render
.fps_base
743 new_fps
= 1.0 / bvh_frame_time
745 if scene
.render
.fps
!= new_fps
or scene
.render
.fps_base
!= 1.0:
746 print("\tupdating scene FPS (was %f) to BVH FPS (%f)" % (scene_fps
, new_fps
))
747 scene
.render
.fps
= new_fps
748 scene
.render
.fps_base
= 1.0
751 def _update_scene_duration(
752 context
, report
, bvh_frame_count
, bvh_frame_time
, frame_start
,
754 """Extend the scene's duration so that the BVH file fits in its entirety."""
756 if bvh_frame_count
is None:
759 "Unable to extend the scene duration, as the BVH file does not "
760 "contain the number of frames in its MOTION section",
764 # Not likely, but it can happen when a BVH is just used to store an armature.
765 if bvh_frame_count
== 0:
769 scene_fps
= context
.scene
.render
.fps
/ context
.scene
.render
.fps_base
770 scaled_frame_count
= int(ceil(bvh_frame_count
* bvh_frame_time
* scene_fps
))
771 bvh_last_frame
= frame_start
+ scaled_frame_count
773 bvh_last_frame
= frame_start
+ bvh_frame_count
775 # Only extend the scene, never shorten it.
776 if context
.scene
.frame_end
< bvh_last_frame
:
777 context
.scene
.frame_end
= bvh_last_frame