1 # SPDX-License-Identifier: GPL-2.0-or-later
4 "name": "BioVision Motion Capture (BVH) format",
5 "author": "Campbell Barton",
8 "location": "File > Import-Export",
9 "description": "Import-Export BVH from armature objects",
11 "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/anim_bvh.html",
12 "support": 'OFFICIAL',
13 "category": "Import-Export",
18 if "import_bvh" in locals():
19 importlib
.reload(import_bvh
)
20 if "export_bvh" in locals():
21 importlib
.reload(export_bvh
)
24 from bpy
.props
import (
31 from bpy_extras
.io_utils
import (
39 @orientation_helper(axis_forward
='-Z', axis_up
='Y')
40 class ImportBVH(bpy
.types
.Operator
, ImportHelper
):
41 """Load a BVH motion capture file"""
42 bl_idname
= "import_anim.bvh"
43 bl_label
= "Import BVH"
44 bl_options
= {'REGISTER', 'UNDO'}
47 filter_glob
: StringProperty(default
="*.bvh", options
={'HIDDEN'})
51 ('ARMATURE', "Armature", ""),
52 ('OBJECT', "Object", ""),
55 description
="Import target type",
58 global_scale
: FloatProperty(
60 description
="Scale the BVH by this value",
61 min=0.0001, max=1000000.0,
62 soft_min
=0.001, soft_max
=100.0,
65 frame_start
: IntProperty(
67 description
="Starting frame for the animation",
70 use_fps_scale
: BoolProperty(
73 "Scale the framerate from the BVH to the current scenes, "
74 "otherwise each BVH frame maps directly to a Blender frame"
78 update_scene_fps
: BoolProperty(
79 name
="Update Scene FPS",
81 "Set the scene framerate to that of the BVH file (note that this "
82 "nullifies the 'Scale FPS' option, as the scale will be 1:1)"
86 update_scene_duration
: BoolProperty(
87 name
="Update Scene Duration",
88 description
="Extend the scene's duration to the BVH duration (never shortens the scene)",
91 use_cyclic
: BoolProperty(
93 description
="Loop the animation playback",
96 rotate_mode
: EnumProperty(
98 description
="Rotation conversion",
100 ('QUATERNION', "Quaternion",
101 "Convert rotations to quaternions"),
102 ('NATIVE', "Euler (Native)",
103 "Use the rotation order defined in the BVH file"),
104 ('XYZ', "Euler (XYZ)", "Convert rotations to euler XYZ"),
105 ('XZY', "Euler (XZY)", "Convert rotations to euler XZY"),
106 ('YXZ', "Euler (YXZ)", "Convert rotations to euler YXZ"),
107 ('YZX', "Euler (YZX)", "Convert rotations to euler YZX"),
108 ('ZXY', "Euler (ZXY)", "Convert rotations to euler ZXY"),
109 ('ZYX', "Euler (ZYX)", "Convert rotations to euler ZYX"),
114 def execute(self
, context
):
115 keywords
= self
.as_keywords(
122 global_matrix
= axis_conversion(
123 from_forward
=self
.axis_forward
,
124 from_up
=self
.axis_up
,
127 keywords
["global_matrix"] = global_matrix
129 from . import import_bvh
130 return import_bvh
.load(context
, report
=self
.report
, **keywords
)
132 def draw(self
, context
):
136 class BVH_PT_import_main(bpy
.types
.Panel
):
137 bl_space_type
= 'FILE_BROWSER'
138 bl_region_type
= 'TOOL_PROPS'
140 bl_parent_id
= "FILE_PT_operator"
141 bl_options
= {'HIDE_HEADER'}
145 def poll(cls
, context
):
146 sfile
= context
.space_data
147 operator
= sfile
.active_operator
149 return operator
.bl_idname
== "IMPORT_ANIM_OT_bvh"
151 def draw(self
, context
):
153 layout
.use_property_split
= True
154 layout
.use_property_decorate
= False # No animation.
156 sfile
= context
.space_data
157 operator
= sfile
.active_operator
159 layout
.prop(operator
, "target")
162 class BVH_PT_import_transform(bpy
.types
.Panel
):
163 bl_space_type
= 'FILE_BROWSER'
164 bl_region_type
= 'TOOL_PROPS'
165 bl_label
= "Transform"
166 bl_parent_id
= "FILE_PT_operator"
169 def poll(cls
, context
):
170 sfile
= context
.space_data
171 operator
= sfile
.active_operator
173 return operator
.bl_idname
== "IMPORT_ANIM_OT_bvh"
175 def draw(self
, context
):
177 layout
.use_property_split
= True
178 layout
.use_property_decorate
= False # No animation.
180 sfile
= context
.space_data
181 operator
= sfile
.active_operator
183 layout
.prop(operator
, "global_scale")
184 layout
.prop(operator
, "rotate_mode")
185 layout
.prop(operator
, "axis_forward")
186 layout
.prop(operator
, "axis_up")
189 class BVH_PT_import_animation(bpy
.types
.Panel
):
190 bl_space_type
= 'FILE_BROWSER'
191 bl_region_type
= 'TOOL_PROPS'
192 bl_label
= "Animation"
193 bl_parent_id
= "FILE_PT_operator"
196 def poll(cls
, context
):
197 sfile
= context
.space_data
198 operator
= sfile
.active_operator
200 return operator
.bl_idname
== "IMPORT_ANIM_OT_bvh"
202 def draw(self
, context
):
204 layout
.use_property_split
= True
205 layout
.use_property_decorate
= False # No animation.
207 sfile
= context
.space_data
208 operator
= sfile
.active_operator
210 layout
.prop(operator
, "frame_start")
211 layout
.prop(operator
, "use_fps_scale")
212 layout
.prop(operator
, "use_cyclic")
214 layout
.prop(operator
, "update_scene_fps")
215 layout
.prop(operator
, "update_scene_duration")
218 class ExportBVH(bpy
.types
.Operator
, ExportHelper
):
219 """Save a BVH motion capture file from an armature"""
220 bl_idname
= "export_anim.bvh"
221 bl_label
= "Export BVH"
223 filename_ext
= ".bvh"
224 filter_glob
: StringProperty(
229 global_scale
: FloatProperty(
231 description
="Scale the BVH by this value",
232 min=0.0001, max=1000000.0,
233 soft_min
=0.001, soft_max
=100.0,
236 frame_start
: IntProperty(
238 description
="Starting frame to export",
241 frame_end
: IntProperty(
243 description
="End frame to export",
246 rotate_mode
: EnumProperty(
248 description
="Rotation conversion",
250 ('NATIVE', "Euler (Native)",
251 "Use the rotation order defined in the BVH file"),
252 ('XYZ', "Euler (XYZ)", "Convert rotations to euler XYZ"),
253 ('XZY', "Euler (XZY)", "Convert rotations to euler XZY"),
254 ('YXZ', "Euler (YXZ)", "Convert rotations to euler YXZ"),
255 ('YZX', "Euler (YZX)", "Convert rotations to euler YZX"),
256 ('ZXY', "Euler (ZXY)", "Convert rotations to euler ZXY"),
257 ('ZYX', "Euler (ZYX)", "Convert rotations to euler ZYX"),
261 root_transform_only
: BoolProperty(
262 name
="Root Translation Only",
263 description
="Only write out translation channels for the root bone",
268 def poll(cls
, context
):
270 return obj
and obj
.type == 'ARMATURE'
272 def invoke(self
, context
, event
):
273 self
.frame_start
= context
.scene
.frame_start
274 self
.frame_end
= context
.scene
.frame_end
276 return super().invoke(context
, event
)
278 def execute(self
, context
):
279 if self
.frame_start
== 0 and self
.frame_end
== 0:
280 self
.frame_start
= context
.scene
.frame_start
281 self
.frame_end
= context
.scene
.frame_end
283 keywords
= self
.as_keywords(
292 from . import export_bvh
293 return export_bvh
.save(context
, **keywords
)
295 def draw(self
, context
):
299 class BVH_PT_export_transform(bpy
.types
.Panel
):
300 bl_space_type
= 'FILE_BROWSER'
301 bl_region_type
= 'TOOL_PROPS'
302 bl_label
= "Transform"
303 bl_parent_id
= "FILE_PT_operator"
306 def poll(cls
, context
):
307 sfile
= context
.space_data
308 operator
= sfile
.active_operator
310 return operator
.bl_idname
== "EXPORT_ANIM_OT_bvh"
312 def draw(self
, context
):
314 layout
.use_property_split
= True
315 layout
.use_property_decorate
= False # No animation.
317 sfile
= context
.space_data
318 operator
= sfile
.active_operator
320 layout
.prop(operator
, "global_scale")
321 layout
.prop(operator
, "rotate_mode")
322 layout
.prop(operator
, "root_transform_only")
325 class BVH_PT_export_animation(bpy
.types
.Panel
):
326 bl_space_type
= 'FILE_BROWSER'
327 bl_region_type
= 'TOOL_PROPS'
328 bl_label
= "Animation"
329 bl_parent_id
= "FILE_PT_operator"
332 def poll(cls
, context
):
333 sfile
= context
.space_data
334 operator
= sfile
.active_operator
336 return operator
.bl_idname
== "EXPORT_ANIM_OT_bvh"
338 def draw(self
, context
):
340 layout
.use_property_split
= True
341 layout
.use_property_decorate
= False # No animation.
343 sfile
= context
.space_data
344 operator
= sfile
.active_operator
346 col
= layout
.column(align
=True)
347 col
.prop(operator
, "frame_start", text
="Frame Start")
348 col
.prop(operator
, "frame_end", text
="End")
351 def menu_func_import(self
, context
):
352 self
.layout
.operator(ImportBVH
.bl_idname
, text
="Motion Capture (.bvh)")
355 def menu_func_export(self
, context
):
356 self
.layout
.operator(ExportBVH
.bl_idname
, text
="Motion Capture (.bvh)")
362 BVH_PT_import_transform
,
363 BVH_PT_import_animation
,
365 BVH_PT_export_transform
,
366 BVH_PT_export_animation
,
371 bpy
.utils
.register_class(cls
)
373 bpy
.types
.TOPBAR_MT_file_import
.append(menu_func_import
)
374 bpy
.types
.TOPBAR_MT_file_export
.append(menu_func_export
)
379 bpy
.utils
.unregister_class(cls
)
381 bpy
.types
.TOPBAR_MT_file_import
.remove(menu_func_import
)
382 bpy
.types
.TOPBAR_MT_file_export
.remove(menu_func_export
)
384 if __name__
== "__main__":