1 # 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'}
147 def poll(cls
, context
):
148 sfile
= context
.space_data
149 operator
= sfile
.active_operator
151 return operator
.bl_idname
== "IMPORT_ANIM_OT_bvh"
153 def draw(self
, context
):
155 layout
.use_property_split
= True
156 layout
.use_property_decorate
= False # No animation.
158 sfile
= context
.space_data
159 operator
= sfile
.active_operator
161 layout
.prop(operator
, "target")
164 class BVH_PT_import_transform(bpy
.types
.Panel
):
165 bl_space_type
= 'FILE_BROWSER'
166 bl_region_type
= 'TOOL_PROPS'
167 bl_label
= "Transform"
168 bl_parent_id
= "FILE_PT_operator"
171 def poll(cls
, context
):
172 sfile
= context
.space_data
173 operator
= sfile
.active_operator
175 return operator
.bl_idname
== "IMPORT_ANIM_OT_bvh"
177 def draw(self
, context
):
179 layout
.use_property_split
= True
180 layout
.use_property_decorate
= False # No animation.
182 sfile
= context
.space_data
183 operator
= sfile
.active_operator
185 layout
.prop(operator
, "global_scale")
186 layout
.prop(operator
, "rotate_mode")
187 layout
.prop(operator
, "axis_forward")
188 layout
.prop(operator
, "axis_up")
191 class BVH_PT_import_animation(bpy
.types
.Panel
):
192 bl_space_type
= 'FILE_BROWSER'
193 bl_region_type
= 'TOOL_PROPS'
194 bl_label
= "Animation"
195 bl_parent_id
= "FILE_PT_operator"
198 def poll(cls
, context
):
199 sfile
= context
.space_data
200 operator
= sfile
.active_operator
202 return operator
.bl_idname
== "IMPORT_ANIM_OT_bvh"
204 def draw(self
, context
):
206 layout
.use_property_split
= True
207 layout
.use_property_decorate
= False # No animation.
209 sfile
= context
.space_data
210 operator
= sfile
.active_operator
212 layout
.prop(operator
, "frame_start")
213 layout
.prop(operator
, "use_fps_scale")
214 layout
.prop(operator
, "use_cyclic")
216 layout
.prop(operator
, "update_scene_fps")
217 layout
.prop(operator
, "update_scene_duration")
220 class ExportBVH(bpy
.types
.Operator
, ExportHelper
):
221 """Save a BVH motion capture file from an armature"""
222 bl_idname
= "export_anim.bvh"
223 bl_label
= "Export BVH"
225 filename_ext
= ".bvh"
226 filter_glob
: StringProperty(
231 global_scale
: FloatProperty(
233 description
="Scale the BVH by this value",
234 min=0.0001, max=1000000.0,
235 soft_min
=0.001, soft_max
=100.0,
238 frame_start
: IntProperty(
240 description
="Starting frame to export",
243 frame_end
: IntProperty(
245 description
="End frame to export",
248 rotate_mode
: EnumProperty(
250 description
="Rotation conversion",
252 ('NATIVE', "Euler (Native)",
253 "Use the rotation order defined in the BVH file"),
254 ('XYZ', "Euler (XYZ)", "Convert rotations to euler XYZ"),
255 ('XZY', "Euler (XZY)", "Convert rotations to euler XZY"),
256 ('YXZ', "Euler (YXZ)", "Convert rotations to euler YXZ"),
257 ('YZX', "Euler (YZX)", "Convert rotations to euler YZX"),
258 ('ZXY', "Euler (ZXY)", "Convert rotations to euler ZXY"),
259 ('ZYX', "Euler (ZYX)", "Convert rotations to euler ZYX"),
263 root_transform_only
: BoolProperty(
264 name
="Root Translation Only",
265 description
="Only write out translation channels for the root bone",
270 def poll(cls
, context
):
272 return obj
and obj
.type == 'ARMATURE'
274 def invoke(self
, context
, event
):
275 self
.frame_start
= context
.scene
.frame_start
276 self
.frame_end
= context
.scene
.frame_end
278 return super().invoke(context
, event
)
280 def execute(self
, context
):
281 if self
.frame_start
== 0 and self
.frame_end
== 0:
282 self
.frame_start
= context
.scene
.frame_start
283 self
.frame_end
= context
.scene
.frame_end
285 keywords
= self
.as_keywords(
294 from . import export_bvh
295 return export_bvh
.save(context
, **keywords
)
297 def draw(self
, context
):
301 class BVH_PT_export_transform(bpy
.types
.Panel
):
302 bl_space_type
= 'FILE_BROWSER'
303 bl_region_type
= 'TOOL_PROPS'
304 bl_label
= "Transform"
305 bl_parent_id
= "FILE_PT_operator"
308 def poll(cls
, context
):
309 sfile
= context
.space_data
310 operator
= sfile
.active_operator
312 return operator
.bl_idname
== "EXPORT_ANIM_OT_bvh"
314 def draw(self
, context
):
316 layout
.use_property_split
= True
317 layout
.use_property_decorate
= False # No animation.
319 sfile
= context
.space_data
320 operator
= sfile
.active_operator
322 layout
.prop(operator
, "global_scale")
323 layout
.prop(operator
, "rotate_mode")
324 layout
.prop(operator
, "root_transform_only")
327 class BVH_PT_export_animation(bpy
.types
.Panel
):
328 bl_space_type
= 'FILE_BROWSER'
329 bl_region_type
= 'TOOL_PROPS'
330 bl_label
= "Animation"
331 bl_parent_id
= "FILE_PT_operator"
334 def poll(cls
, context
):
335 sfile
= context
.space_data
336 operator
= sfile
.active_operator
338 return operator
.bl_idname
== "EXPORT_ANIM_OT_bvh"
340 def draw(self
, context
):
342 layout
.use_property_split
= True
343 layout
.use_property_decorate
= False # No animation.
345 sfile
= context
.space_data
346 operator
= sfile
.active_operator
348 col
= layout
.column(align
=True)
349 col
.prop(operator
, "frame_start", text
="Frame Start")
350 col
.prop(operator
, "frame_end", text
="End")
353 def menu_func_import(self
, context
):
354 self
.layout
.operator(ImportBVH
.bl_idname
, text
="Motion Capture (.bvh)")
357 def menu_func_export(self
, context
):
358 self
.layout
.operator(ExportBVH
.bl_idname
, text
="Motion Capture (.bvh)")
364 BVH_PT_import_transform
,
365 BVH_PT_import_animation
,
367 BVH_PT_export_transform
,
368 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
)
386 if __name__
== "__main__":