1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # Script copyright (C) Campbell Barton
5 from math
import radians
, ceil
8 from bpy
.app
.translations
import pgettext_tip
as tip_
9 from mathutils
import Vector
, Euler
, Matrix
16 # BVH_Node type or None for no parent.
18 # A list of children of this type..
20 # Worldspace rest location for the head of this node.
22 # Localspace rest location for the head of this node.
24 # Worldspace rest location for the tail of this node.
26 # Worldspace rest location for the tail of this node.
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.
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..
35 # Same as above but a string 'XYZ' format..
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.
40 # Convenience function, bool, same as: (channels[0] != -1 or channels[1] != -1 or channels[2] != -1).
42 # Convenience function, bool, same as: (channels[3] != -1 or channels[4] != -1 or channels[5] != -1).
44 # Index from the file, not strictly needed but nice to maintain order.
46 # Use this for whatever you want.
51 (None, None, None): 'XYZ', # XXX Dummy one, no rotation anyway!
60 def __init__(self
, name
, rest_head_world
, rest_head_local
, parent
, channels
, rot_order
, index
):
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
67 self
.channels
= channels
68 self
.rot_order
= tuple(rot_order
)
69 self
.rot_order_str
= BVH_Node
._eul
_order
_lookup
[self
.rot_order
]
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
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)]
84 "BVH name: '%s', rest_loc:(%.3f,%.3f,%.3f), rest_tail:(%.3f,%.3f,%.3f)" % (
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
)
98 def read_bvh(context
, file_path
, rotate_mode
='XYZ', global_scale
=1.0):
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
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
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 # print '%snode: %s, parent: %s' % (len(bvh_nodes_serial) * ' ', name, bvh_nodes_serial[-1])
142 lineIdx
+= 2 # Increment to the next line (Offset)
143 rest_head_local
= global_scale
* Vector((
144 float(file_lines
[lineIdx
][1]),
145 float(file_lines
[lineIdx
][2]),
146 float(file_lines
[lineIdx
][3]),
148 lineIdx
+= 1 # Increment to the next line (Channels)
150 # newChannel[Xposition, Yposition, Zposition, Xrotation, Yrotation, Zrotation]
151 # newChannel references indices to the motiondata,
152 # if not assigned then -1 refers to the last value that will be added on loading at a value of zero, this is appended
153 # We'll add a zero value onto the end of the MotionDATA so this always refers to a value.
154 my_channel
= [-1, -1, -1, -1, -1, -1]
155 my_rot_order
= [None, None, None]
157 for channel
in file_lines
[lineIdx
][2:]:
158 channel
= channel
.lower()
159 channelIndex
+= 1 # So the index points to the right channel
160 if channel
== 'xposition':
161 my_channel
[0] = channelIndex
162 elif channel
== 'yposition':
163 my_channel
[1] = channelIndex
164 elif channel
== 'zposition':
165 my_channel
[2] = channelIndex
167 elif channel
== 'xrotation':
168 my_channel
[3] = channelIndex
169 my_rot_order
[rot_count
] = 0
171 elif channel
== 'yrotation':
172 my_channel
[4] = channelIndex
173 my_rot_order
[rot_count
] = 1
175 elif channel
== 'zrotation':
176 my_channel
[5] = channelIndex
177 my_rot_order
[rot_count
] = 2
180 channels
= file_lines
[lineIdx
][2:]
182 my_parent
= bvh_nodes_serial
[-1] # account for none
184 # Apply the parents offset accumulatively
185 if my_parent
is None:
186 rest_head_world
= Vector(rest_head_local
)
188 rest_head_world
= my_parent
.rest_head_world
+ rest_head_local
190 bvh_node
= bvh_nodes
[name
] = BVH_Node(
200 # If we have another child then we can call ourselves a parent, else
201 bvh_nodes_serial
.append(bvh_node
)
203 # Account for an end node.
204 # There is sometimes a name after 'End Site' but we will ignore it.
205 if file_lines
[lineIdx
][0].lower() == 'end' and file_lines
[lineIdx
][1].lower() == 'site':
206 # Increment to the next line (Offset)
208 rest_tail
= global_scale
* Vector((
209 float(file_lines
[lineIdx
][1]),
210 float(file_lines
[lineIdx
][2]),
211 float(file_lines
[lineIdx
][3]),
214 bvh_nodes_serial
[-1].rest_tail_world
= bvh_nodes_serial
[-1].rest_head_world
+ rest_tail
215 bvh_nodes_serial
[-1].rest_tail_local
= bvh_nodes_serial
[-1].rest_head_local
+ rest_tail
217 # Just so we can remove the parents in a uniform way,
218 # the end has kids so this is a placeholder.
219 bvh_nodes_serial
.append(None)
221 if len(file_lines
[lineIdx
]) == 1 and file_lines
[lineIdx
][0] == '}': # == ['}']
222 bvh_nodes_serial
.pop() # Remove the last item
224 # End of the hierarchy. Begin the animation section of the file with
225 # the following header.
229 if len(file_lines
[lineIdx
]) == 1 and file_lines
[lineIdx
][0].lower() == 'motion':
230 lineIdx
+= 1 # Read frame count.
232 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.
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
251 # Remove the None value used for easy parent reference
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
))
287 for bvh_node
in bvh_nodes_list
:
288 bvh_node_parent
= 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
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):
332 scene
= context
.scene
333 for obj
in scene
.objects
:
334 obj
.select_set(False)
339 obj
= bpy
.data
.objects
.new(name
, None)
340 context
.collection
.objects
.link(obj
)
345 obj
.empty_display_type
= 'CUBE'
346 obj
.empty_display_size
= 0.1
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]
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
361 for bvh_node
in bvh_nodes
.values():
362 # Make relative to parents offset
363 bvh_node
.temp
.location
= bvh_node
.rest_head_local
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():
375 for frame_current
in range(len(bvh_node
.anim_data
)):
377 lx
, ly
, lz
, rx
, ry
, rz
= bvh_node
.anim_data
[frame_current
]
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
)
384 obj
.delta_rotation_euler
= rx
, ry
, rz
385 obj
.keyframe_insert("delta_rotation_euler", index
=-1, frame
=frame_start
+ frame_current
)
390 def bvh_node_dict2armature(
405 # Add the new armature,
406 scene
= context
.scene
407 for obj
in scene
.objects
:
408 obj
.select_set(False)
410 arm_data
= bpy
.data
.armatures
.new(bvh_name
)
411 arm_ob
= bpy
.data
.objects
.new(bvh_name
, arm_data
)
413 context
.collection
.objects
.link(arm_ob
)
415 arm_ob
.select_set(True)
416 context
.view_layer
.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
426 for bvh_node
in bvh_nodes_list
:
427 l
= (bvh_node
.rest_head_local
- bvh_node
.rest_tail_local
).length
429 average_bone_length
+= l
432 # Very rare cases all bones could be zero length???
433 if not average_bone_length
:
434 average_bone_length
= 0.1
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])
444 for bvh_node
in bvh_nodes_list
:
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
)
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
460 bone
.tail
.y
= bone
.tail
.y
+ average_bone_length
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
:
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
475 (not bvh_node
.has_loc
) and
476 (bvh_node
.parent
.temp
.name
not in ZERO_AREA_BONES
) and
477 (bvh_node
.parent
.rest_tail_local
== bvh_node
.rest_head_local
)
479 bvh_node
.temp
.use_connect
= True
481 # Replace the editbone with the editbone name,
482 # to avoid memory errors accessing the editbone outside editmode
483 for bvh_node
in bvh_nodes_list
:
484 bvh_node
.temp
= bvh_node
.temp
.name
486 # Now Apply the animation to the armature
488 # Get armature animation data
489 bpy
.ops
.object.mode_set(mode
='OBJECT', toggle
=False)
492 pose_bones
= pose
.bones
494 if rotate_mode
== 'NATIVE':
495 for bvh_node
in bvh_nodes_list
:
496 bone_name
= bvh_node
.temp
# may not be the same name as the bvh_node, could have been shortened.
497 pose_bone
= pose_bones
[bone_name
]
498 pose_bone
.rotation_mode
= bvh_node
.rot_order_str
500 elif rotate_mode
!= 'QUATERNION':
501 for pose_bone
in pose_bones
:
502 pose_bone
.rotation_mode
= rotate_mode
507 context
.view_layer
.update()
509 arm_ob
.animation_data_create()
510 action
= bpy
.data
.actions
.new(name
=bvh_name
)
511 arm_ob
.animation_data
.action
= action
513 # Replace the bvh_node.temp (currently an editbone)
514 # With a tuple (pose_bone, armature_bone, bone_rest_matrix, bone_rest_matrix_inv)
516 for bvh_node
in bvh_nodes_list
:
517 bone_name
= bvh_node
.temp
# may not be the same name as the bvh_node, could have been shortened.
518 pose_bone
= pose_bones
[bone_name
]
519 rest_bone
= arm_data
.bones
[bone_name
]
520 bone_rest_matrix
= rest_bone
.matrix_local
.to_3x3()
522 bone_rest_matrix_inv
= Matrix(bone_rest_matrix
)
523 bone_rest_matrix_inv
.invert()
525 bone_rest_matrix_inv
.resize_4x4()
526 bone_rest_matrix
.resize_4x4()
527 bvh_node
.temp
= (pose_bone
, bone
, bone_rest_matrix
, bone_rest_matrix_inv
)
530 num_frame
= len(bvh_node
.anim_data
)
532 # Choose to skip some frames at the beginning. Frame 0 is the rest pose
533 # used internally by this importer. Frame 1, by convention, is also often
534 # the rest pose of the skeleton exported by the motion capture system.
536 if num_frame
> skip_frame
:
537 num_frame
= num_frame
- skip_frame
539 # Create a shared time axis for all animation curves.
540 time
= [float(frame_start
)] * num_frame
542 dt
= scene
.render
.fps
* bvh_frame_time
543 for frame_i
in range(1, num_frame
):
544 time
[frame_i
] += float(frame_i
) * dt
546 for frame_i
in range(1, num_frame
):
547 time
[frame_i
] += float(frame_i
)
549 # print("bvh_frame_time = %f, dt = %f, num_frame = %d"
550 # % (bvh_frame_time, dt, num_frame]))
552 for i
, bvh_node
in enumerate(bvh_nodes_list
):
553 pose_bone
, bone
, bone_rest_matrix
, bone_rest_matrix_inv
= bvh_node
.temp
556 # Not sure if there is a way to query this or access it in the
557 # PoseBone structure.
558 data_path
= 'pose.bones["%s"].location' % pose_bone
.name
560 location
= [(0.0, 0.0, 0.0)] * num_frame
561 for frame_i
in range(num_frame
):
562 bvh_loc
= bvh_node
.anim_data
[frame_i
+ skip_frame
][:3]
564 bone_translate_matrix
= Matrix
.Translation(
565 Vector(bvh_loc
) - bvh_node
.rest_head_local
)
566 location
[frame_i
] = (bone_rest_matrix_inv
@
567 bone_translate_matrix
).to_translation()
569 # For each location x, y, z.
570 for axis_i
in range(3):
571 curve
= action
.fcurves
.new(data_path
=data_path
, index
=axis_i
, action_group
=bvh_node
.name
)
572 keyframe_points
= curve
.keyframe_points
573 keyframe_points
.add(num_frame
)
575 for frame_i
in range(num_frame
):
576 keyframe_points
[frame_i
].co
= (
578 location
[frame_i
][axis_i
],
585 if 'QUATERNION' == rotate_mode
:
586 rotate
= [(1.0, 0.0, 0.0, 0.0)] * num_frame
587 data_path
= ('pose.bones["%s"].rotation_quaternion'
590 rotate
= [(0.0, 0.0, 0.0)] * num_frame
591 data_path
= ('pose.bones["%s"].rotation_euler' %
594 prev_euler
= Euler((0.0, 0.0, 0.0))
595 for frame_i
in range(num_frame
):
596 bvh_rot
= bvh_node
.anim_data
[frame_i
+ skip_frame
][3:]
598 # apply rotation order and convert to XYZ
599 # note that the rot_order_str is reversed.
600 euler
= Euler(bvh_rot
, bvh_node
.rot_order_str
[::-1])
601 bone_rotation_matrix
= euler
.to_matrix().to_4x4()
602 bone_rotation_matrix
= (
603 bone_rest_matrix_inv
@
604 bone_rotation_matrix
@
608 if len(rotate
[frame_i
]) == 4:
609 rotate
[frame_i
] = bone_rotation_matrix
.to_quaternion()
611 rotate
[frame_i
] = bone_rotation_matrix
.to_euler(
612 pose_bone
.rotation_mode
, prev_euler
)
613 prev_euler
= rotate
[frame_i
]
615 # For each euler angle x, y, z (or quaternion w, x, y, z).
616 for axis_i
in range(len(rotate
[0])):
617 curve
= action
.fcurves
.new(data_path
=data_path
, index
=axis_i
, action_group
=bvh_node
.name
)
618 keyframe_points
= curve
.keyframe_points
619 keyframe_points
.add(num_frame
)
621 for frame_i
in range(num_frame
):
622 keyframe_points
[frame_i
].co
= (
624 rotate
[frame_i
][axis_i
],
627 for cu
in action
.fcurves
:
629 pass # 2.5 doenst have cyclic now?
631 for bez
in cu
.keyframe_points
:
632 bez
.interpolation
= 'LINEAR'
634 # finally apply matrix
635 arm_ob
.matrix_world
= global_matrix
636 bpy
.ops
.object.transform_apply(location
=False, rotation
=True, scale
=False)
646 rotate_mode
='NATIVE',
652 update_scene_fps
=False,
653 update_scene_duration
=False,
658 print("\tparsing bvh %r..." % filepath
, end
="")
660 bvh_nodes
, bvh_frame_time
, bvh_frame_count
= read_bvh(
662 rotate_mode
=rotate_mode
,
663 global_scale
=global_scale
,
666 print("%.4f" % (time
.time() - t1
))
668 scene
= context
.scene
669 frame_orig
= scene
.frame_current
671 # Broken BVH handling: guess frame rate when it is not contained in the file.
672 if bvh_frame_time
is None:
675 "The BVH file does not contain frame duration in its MOTION "
676 "section, assuming the BVH and Blender scene have the same "
679 bvh_frame_time
= scene
.render
.fps_base
/ scene
.render
.fps
680 # No need to scale the frame rate, as they're equal now anyway.
681 use_fps_scale
= False
684 _update_scene_fps(context
, report
, bvh_frame_time
)
686 # Now that we have a 1-to-1 mapping of Blender frames and BVH frames, there is no need
687 # to scale the FPS any more. It's even better not to, to prevent roundoff errors.
688 use_fps_scale
= False
690 if update_scene_duration
:
691 _update_scene_duration(context
, report
, bvh_frame_count
, bvh_frame_time
, frame_start
, use_fps_scale
)
694 print("\timporting to blender...", end
="")
696 bvh_name
= bpy
.path
.display_name_from_filepath(filepath
)
698 if target
== 'ARMATURE':
699 bvh_node_dict2armature(
700 context
, bvh_name
, bvh_nodes
, bvh_frame_time
,
701 rotate_mode
=rotate_mode
,
702 frame_start
=frame_start
,
703 IMPORT_LOOP
=use_cyclic
,
704 global_matrix
=global_matrix
,
705 use_fps_scale
=use_fps_scale
,
708 elif target
== 'OBJECT':
709 bvh_node_dict2objects(
710 context
, bvh_name
, bvh_nodes
,
711 rotate_mode
=rotate_mode
,
712 frame_start
=frame_start
,
713 IMPORT_LOOP
=use_cyclic
,
714 # global_matrix=global_matrix, # TODO
718 report({'ERROR'}, tip_("Invalid target %r (must be 'ARMATURE' or 'OBJECT')") % target
)
721 print('Done in %.4f\n' % (time
.time() - t1
))
723 context
.scene
.frame_set(frame_orig
)
728 def _update_scene_fps(context
, report
, bvh_frame_time
):
729 """Update the scene's FPS settings from the BVH, but only if the BVH contains enough info."""
731 # Broken BVH handling: prevent division by zero.
732 if bvh_frame_time
== 0.0:
735 "Unable to update scene frame rate, as the BVH file "
736 "contains a zero frame duration in its MOTION section",
740 scene
= context
.scene
741 scene_fps
= scene
.render
.fps
/ scene
.render
.fps_base
742 new_fps
= 1.0 / bvh_frame_time
744 if scene
.render
.fps
!= new_fps
or scene
.render
.fps_base
!= 1.0:
745 print("\tupdating scene FPS (was %f) to BVH FPS (%f)" % (scene_fps
, new_fps
))
746 scene
.render
.fps
= int(round(new_fps
))
747 scene
.render
.fps_base
= scene
.render
.fps
/ new_fps
750 def _update_scene_duration(
751 context
, report
, bvh_frame_count
, bvh_frame_time
, frame_start
,
753 """Extend the scene's duration so that the BVH file fits in its entirety."""
755 if bvh_frame_count
is None:
758 "Unable to extend the scene duration, as the BVH file does not "
759 "contain the number of frames in its MOTION section",
763 # Not likely, but it can happen when a BVH is just used to store an armature.
764 if bvh_frame_count
== 0:
768 scene_fps
= context
.scene
.render
.fps
/ context
.scene
.render
.fps_base
769 scaled_frame_count
= int(ceil(bvh_frame_count
* bvh_frame_time
* scene_fps
))
770 bvh_last_frame
= frame_start
+ scaled_frame_count
772 bvh_last_frame
= frame_start
+ bvh_frame_count
774 # Only extend the scene, never shorten it.
775 if context
.scene
.frame_end
< bvh_last_frame
:
776 context
.scene
.frame_end
= bvh_last_frame