1 # SPDX-FileCopyrightText: 2011-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
6 "name": "BioVision Motion Capture (BVH) format",
7 "author": "Campbell Barton",
10 "location": "File > Import-Export",
11 "description": "Import-Export BVH from armature objects",
13 "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/anim_bvh.html",
14 "support": 'OFFICIAL',
15 "category": "Import-Export",
20 if "import_bvh" in locals():
21 importlib
.reload(import_bvh
)
22 if "export_bvh" in locals():
23 importlib
.reload(export_bvh
)
26 from bpy
.props
import (
33 from bpy_extras
.io_utils
import (
41 @orientation_helper(axis_forward
='-Z', axis_up
='Y')
42 class ImportBVH(bpy
.types
.Operator
, ImportHelper
):
43 """Load a BVH motion capture file"""
44 bl_idname
= "import_anim.bvh"
45 bl_label
= "Import BVH"
46 bl_options
= {'REGISTER', 'UNDO'}
49 filter_glob
: StringProperty(default
="*.bvh", options
={'HIDDEN'})
53 ('ARMATURE', "Armature", ""),
54 ('OBJECT', "Object", ""),
57 description
="Import target type",
60 global_scale
: FloatProperty(
62 description
="Scale the BVH by this value",
63 min=0.0001, max=1000000.0,
64 soft_min
=0.001, soft_max
=100.0,
67 frame_start
: IntProperty(
69 description
="Starting frame for the animation",
72 use_fps_scale
: BoolProperty(
75 "Scale the framerate from the BVH to the current scenes, "
76 "otherwise each BVH frame maps directly to a Blender frame"
80 update_scene_fps
: BoolProperty(
81 name
="Update Scene FPS",
83 "Set the scene framerate to that of the BVH file (note that this "
84 "nullifies the 'Scale FPS' option, as the scale will be 1:1)"
88 update_scene_duration
: BoolProperty(
89 name
="Update Scene Duration",
90 description
="Extend the scene's duration to the BVH duration (never shortens the scene)",
93 use_cyclic
: BoolProperty(
95 description
="Loop the animation playback",
98 rotate_mode
: EnumProperty(
100 description
="Rotation conversion",
102 ('QUATERNION', "Quaternion",
103 "Convert rotations to quaternions"),
104 ('NATIVE', "Euler (Native)",
105 "Use the rotation order defined in the BVH file"),
106 ('XYZ', "Euler (XYZ)", "Convert rotations to euler XYZ"),
107 ('XZY', "Euler (XZY)", "Convert rotations to euler XZY"),
108 ('YXZ', "Euler (YXZ)", "Convert rotations to euler YXZ"),
109 ('YZX', "Euler (YZX)", "Convert rotations to euler YZX"),
110 ('ZXY', "Euler (ZXY)", "Convert rotations to euler ZXY"),
111 ('ZYX', "Euler (ZYX)", "Convert rotations to euler ZYX"),
116 def execute(self
, context
):
117 keywords
= self
.as_keywords(
124 global_matrix
= axis_conversion(
125 from_forward
=self
.axis_forward
,
126 from_up
=self
.axis_up
,
129 keywords
["global_matrix"] = global_matrix
131 from . import import_bvh
132 return import_bvh
.load(context
, report
=self
.report
, **keywords
)
134 def draw(self
, context
):
138 class BVH_PT_import_main(bpy
.types
.Panel
):
139 bl_space_type
= 'FILE_BROWSER'
140 bl_region_type
= 'TOOL_PROPS'
142 bl_parent_id
= "FILE_PT_operator"
143 bl_options
= {'HIDE_HEADER'}
146 def poll(cls
, context
):
147 sfile
= context
.space_data
148 operator
= sfile
.active_operator
150 return operator
.bl_idname
== "IMPORT_ANIM_OT_bvh"
152 def draw(self
, context
):
154 layout
.use_property_split
= True
155 layout
.use_property_decorate
= False # No animation.
157 sfile
= context
.space_data
158 operator
= sfile
.active_operator
160 layout
.prop(operator
, "target")
163 class BVH_PT_import_transform(bpy
.types
.Panel
):
164 bl_space_type
= 'FILE_BROWSER'
165 bl_region_type
= 'TOOL_PROPS'
166 bl_label
= "Transform"
167 bl_parent_id
= "FILE_PT_operator"
170 def poll(cls
, context
):
171 sfile
= context
.space_data
172 operator
= sfile
.active_operator
174 return operator
.bl_idname
== "IMPORT_ANIM_OT_bvh"
176 def draw(self
, context
):
178 layout
.use_property_split
= True
179 layout
.use_property_decorate
= False # No animation.
181 sfile
= context
.space_data
182 operator
= sfile
.active_operator
184 layout
.prop(operator
, "global_scale")
185 layout
.prop(operator
, "rotate_mode")
186 layout
.prop(operator
, "axis_forward")
187 layout
.prop(operator
, "axis_up")
190 class BVH_PT_import_animation(bpy
.types
.Panel
):
191 bl_space_type
= 'FILE_BROWSER'
192 bl_region_type
= 'TOOL_PROPS'
193 bl_label
= "Animation"
194 bl_parent_id
= "FILE_PT_operator"
197 def poll(cls
, context
):
198 sfile
= context
.space_data
199 operator
= sfile
.active_operator
201 return operator
.bl_idname
== "IMPORT_ANIM_OT_bvh"
203 def draw(self
, context
):
205 layout
.use_property_split
= True
206 layout
.use_property_decorate
= False # No animation.
208 sfile
= context
.space_data
209 operator
= sfile
.active_operator
211 layout
.prop(operator
, "frame_start")
212 layout
.prop(operator
, "use_fps_scale")
213 layout
.prop(operator
, "use_cyclic")
215 layout
.prop(operator
, "update_scene_fps")
216 layout
.prop(operator
, "update_scene_duration")
219 class ExportBVH(bpy
.types
.Operator
, ExportHelper
):
220 """Save a BVH motion capture file from an armature"""
221 bl_idname
= "export_anim.bvh"
222 bl_label
= "Export BVH"
224 filename_ext
= ".bvh"
225 filter_glob
: StringProperty(
230 global_scale
: FloatProperty(
232 description
="Scale the BVH by this value",
233 min=0.0001, max=1000000.0,
234 soft_min
=0.001, soft_max
=100.0,
237 frame_start
: IntProperty(
239 description
="Starting frame to export",
242 frame_end
: IntProperty(
244 description
="End frame to export",
247 rotate_mode
: EnumProperty(
249 description
="Rotation conversion",
251 ('NATIVE', "Euler (Native)",
252 "Use the rotation order defined in the BVH file"),
253 ('XYZ', "Euler (XYZ)", "Convert rotations to euler XYZ"),
254 ('XZY', "Euler (XZY)", "Convert rotations to euler XZY"),
255 ('YXZ', "Euler (YXZ)", "Convert rotations to euler YXZ"),
256 ('YZX', "Euler (YZX)", "Convert rotations to euler YZX"),
257 ('ZXY', "Euler (ZXY)", "Convert rotations to euler ZXY"),
258 ('ZYX', "Euler (ZYX)", "Convert rotations to euler ZYX"),
262 root_transform_only
: BoolProperty(
263 name
="Root Translation Only",
264 description
="Only write out translation channels for the root bone",
269 def poll(cls
, context
):
271 return obj
and obj
.type == 'ARMATURE'
273 def invoke(self
, context
, event
):
274 self
.frame_start
= context
.scene
.frame_start
275 self
.frame_end
= context
.scene
.frame_end
277 return super().invoke(context
, event
)
279 def execute(self
, context
):
280 if self
.frame_start
== 0 and self
.frame_end
== 0:
281 self
.frame_start
= context
.scene
.frame_start
282 self
.frame_end
= context
.scene
.frame_end
284 keywords
= self
.as_keywords(
293 from . import export_bvh
294 return export_bvh
.save(context
, **keywords
)
296 def draw(self
, context
):
300 class BVH_PT_export_transform(bpy
.types
.Panel
):
301 bl_space_type
= 'FILE_BROWSER'
302 bl_region_type
= 'TOOL_PROPS'
303 bl_label
= "Transform"
304 bl_parent_id
= "FILE_PT_operator"
307 def poll(cls
, context
):
308 sfile
= context
.space_data
309 operator
= sfile
.active_operator
311 return operator
.bl_idname
== "EXPORT_ANIM_OT_bvh"
313 def draw(self
, context
):
315 layout
.use_property_split
= True
316 layout
.use_property_decorate
= False # No animation.
318 sfile
= context
.space_data
319 operator
= sfile
.active_operator
321 layout
.prop(operator
, "global_scale")
322 layout
.prop(operator
, "rotate_mode")
323 layout
.prop(operator
, "root_transform_only")
326 class BVH_PT_export_animation(bpy
.types
.Panel
):
327 bl_space_type
= 'FILE_BROWSER'
328 bl_region_type
= 'TOOL_PROPS'
329 bl_label
= "Animation"
330 bl_parent_id
= "FILE_PT_operator"
333 def poll(cls
, context
):
334 sfile
= context
.space_data
335 operator
= sfile
.active_operator
337 return operator
.bl_idname
== "EXPORT_ANIM_OT_bvh"
339 def draw(self
, context
):
341 layout
.use_property_split
= True
342 layout
.use_property_decorate
= False # No animation.
344 sfile
= context
.space_data
345 operator
= sfile
.active_operator
347 col
= layout
.column(align
=True)
348 col
.prop(operator
, "frame_start", text
="Frame Start")
349 col
.prop(operator
, "frame_end", text
="End")
352 def menu_func_import(self
, context
):
353 self
.layout
.operator(ImportBVH
.bl_idname
, text
="Motion Capture (.bvh)")
356 def menu_func_export(self
, context
):
357 self
.layout
.operator(ExportBVH
.bl_idname
, text
="Motion Capture (.bvh)")
363 BVH_PT_import_transform
,
364 BVH_PT_import_animation
,
366 BVH_PT_export_transform
,
367 BVH_PT_export_animation
,
373 bpy
.utils
.register_class(cls
)
375 bpy
.types
.TOPBAR_MT_file_import
.append(menu_func_import
)
376 bpy
.types
.TOPBAR_MT_file_export
.append(menu_func_export
)
381 bpy
.utils
.unregister_class(cls
)
383 bpy
.types
.TOPBAR_MT_file_import
.remove(menu_func_import
)
384 bpy
.types
.TOPBAR_MT_file_export
.remove(menu_func_export
)
387 if __name__
== "__main__":