1 # SPDX-FileCopyrightText: 2011 Campbell Barton
3 # SPDX-License-Identifier: GPL-2.0-or-later
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 # While unlikely, there exists a user report of duplicate joint names, see: #109399.
141 if name
in bvh_nodes
:
144 while (name
:= "%s.%03d" % (name_orig
, name_index
)) in bvh_nodes
:
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]
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
179 elif channel
== 'yrotation':
180 my_channel
[4] = channelIndex
181 my_rot_order
[rot_count
] = 1
183 elif channel
== 'zrotation':
184 my_channel
[5] = channelIndex
185 my_rot_order
[rot_count
] = 2
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
)
196 rest_head_world
= my_parent
.rest_head_world
+ rest_head_local
198 bvh_node
= bvh_nodes
[name
] = BVH_Node(
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)
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.
237 if len(file_lines
[lineIdx
]) == 1 and file_lines
[lineIdx
][0].lower() == 'motion':
238 lineIdx
+= 1 # Read frame count.
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.
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
259 # Remove the None value used for easy parent reference
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
))
295 for bvh_node
in bvh_nodes_list
:
296 bvh_node_parent
= 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
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):
340 scene
= context
.scene
341 for obj
in scene
.objects
:
342 obj
.select_set(False)
347 obj
= bpy
.data
.objects
.new(name
, None)
348 context
.collection
.objects
.link(obj
)
353 obj
.empty_display_type
= 'CUBE'
354 obj
.empty_display_size
= 0.1
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]
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
369 for bvh_node
in bvh_nodes
.values():
370 # Make relative to parents offset
371 bvh_node
.temp
.location
= bvh_node
.rest_head_local
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():
383 for frame_current
in range(len(bvh_node
.anim_data
)):
385 lx
, ly
, lz
, rx
, ry
, rz
= bvh_node
.anim_data
[frame_current
]
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
)
392 obj
.delta_rotation_euler
= rx
, ry
, rz
393 obj
.keyframe_insert("delta_rotation_euler", index
=-1, frame
=frame_start
+ frame_current
)
398 def bvh_node_dict2armature(
409 from bpy
.utils
import escape_identifier
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
435 for bvh_node
in bvh_nodes_list
:
436 l
= (bvh_node
.rest_head_local
- bvh_node
.rest_tail_local
).length
438 average_bone_length
+= l
441 # Very rare cases all bones could be zero length???
442 if not average_bone_length
:
443 average_bone_length
= 0.1
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])
453 for bvh_node
in bvh_nodes_list
:
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
)
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
469 bone
.tail
.y
= bone
.tail
.y
+ average_bone_length
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
:
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
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)
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
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)
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
)
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.
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
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
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
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
= (
587 location
[frame_i
][axis_i
],
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
))
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
@
615 if len(rotate
[frame_i
]) == 4:
616 rotate
[frame_i
] = bone_rotation_matrix
.to_quaternion()
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
= (
631 rotate
[frame_i
][axis_i
],
634 for cu
in action
.fcurves
:
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)
653 rotate_mode
='NATIVE',
659 update_scene_fps
=False,
660 update_scene_duration
=False,
665 print("\tparsing bvh %r..." % filepath
, end
="")
667 bvh_nodes
, bvh_frame_time
, bvh_frame_count
= read_bvh(
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:
682 "The BVH file does not contain frame duration in its MOTION "
683 "section, assuming the BVH and Blender scene have the same "
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
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
)
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
725 report({'ERROR'}, tip_("Invalid target %r (must be 'ARMATURE' or 'OBJECT')") % target
)
728 print('Done in %.4f\n' % (time
.time() - t1
))
730 context
.scene
.frame_set(frame_orig
)
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:
742 "Unable to update scene frame rate, as the BVH file "
743 "contains a zero frame duration in its MOTION section",
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
,
760 """Extend the scene's duration so that the BVH file fits in its entirety."""
762 if bvh_frame_count
is None:
765 "Unable to extend the scene duration, as the BVH file does not "
766 "contain the number of frames in its MOTION section",
770 # Not likely, but it can happen when a BVH is just used to store an armature.
771 if bvh_frame_count
== 0:
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
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