1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
22 "name": "BioVision Motion Capture (BVH) format",
23 "author": "Campbell Barton",
25 "blender": (2, 81, 6),
26 "location": "File > Import-Export",
27 "description": "Import-Export BVH from armature objects",
29 "wiki_url": "https://docs.blender.org/manual/en/dev/addons/"
30 "import_export/io_anim_bvh.html",
31 "support": 'OFFICIAL',
32 "category": "Import-Export",
37 if "import_bvh" in locals():
38 importlib
.reload(import_bvh
)
39 if "export_bvh" in locals():
40 importlib
.reload(export_bvh
)
43 from bpy
.props
import (
50 from bpy_extras
.io_utils
import (
58 @orientation_helper(axis_forward
='-Z', axis_up
='Y')
59 class ImportBVH(bpy
.types
.Operator
, ImportHelper
):
60 """Load a BVH motion capture file"""
61 bl_idname
= "import_anim.bvh"
62 bl_label
= "Import BVH"
63 bl_options
= {'REGISTER', 'UNDO'}
66 filter_glob
: StringProperty(default
="*.bvh", options
={'HIDDEN'})
70 ('ARMATURE', "Armature", ""),
71 ('OBJECT', "Object", ""),
74 description
="Import target type",
77 global_scale
: FloatProperty(
79 description
="Scale the BVH by this value",
80 min=0.0001, max=1000000.0,
81 soft_min
=0.001, soft_max
=100.0,
84 frame_start
: IntProperty(
86 description
="Starting frame for the animation",
89 use_fps_scale
: BoolProperty(
92 "Scale the framerate from the BVH to the current scenes, "
93 "otherwise each BVH frame maps directly to a Blender frame"
97 update_scene_fps
: BoolProperty(
98 name
="Update Scene FPS",
100 "Set the scene framerate to that of the BVH file (note that this "
101 "nullifies the 'Scale FPS' option, as the scale will be 1:1)"
105 update_scene_duration
: BoolProperty(
106 name
="Update Scene Duration",
107 description
="Extend the scene's duration to the BVH duration (never shortens the scene)",
110 use_cyclic
: BoolProperty(
112 description
="Loop the animation playback",
115 rotate_mode
: EnumProperty(
117 description
="Rotation conversion",
119 ('QUATERNION', "Quaternion",
120 "Convert rotations to quaternions"),
121 ('NATIVE', "Euler (Native)",
122 "Use the rotation order defined in the BVH file"),
123 ('XYZ', "Euler (XYZ)", "Convert rotations to euler XYZ"),
124 ('XZY', "Euler (XZY)", "Convert rotations to euler XZY"),
125 ('YXZ', "Euler (YXZ)", "Convert rotations to euler YXZ"),
126 ('YZX', "Euler (YZX)", "Convert rotations to euler YZX"),
127 ('ZXY', "Euler (ZXY)", "Convert rotations to euler ZXY"),
128 ('ZYX', "Euler (ZYX)", "Convert rotations to euler ZYX"),
133 def execute(self
, context
):
134 keywords
= self
.as_keywords(
141 global_matrix
= axis_conversion(
142 from_forward
=self
.axis_forward
,
143 from_up
=self
.axis_up
,
146 keywords
["global_matrix"] = global_matrix
148 from . import import_bvh
149 return import_bvh
.load(context
, report
=self
.report
, **keywords
)
151 def draw(self
, context
):
155 class BVH_PT_import_main(bpy
.types
.Panel
):
156 bl_space_type
= 'FILE_BROWSER'
157 bl_region_type
= 'TOOL_PROPS'
159 bl_parent_id
= "FILE_PT_operator"
160 bl_options
= {'HIDE_HEADER'}
164 def poll(cls
, context
):
165 sfile
= context
.space_data
166 operator
= sfile
.active_operator
168 return operator
.bl_idname
== "IMPORT_ANIM_OT_bvh"
170 def draw(self
, context
):
172 layout
.use_property_split
= True
173 layout
.use_property_decorate
= False # No animation.
175 sfile
= context
.space_data
176 operator
= sfile
.active_operator
178 layout
.prop(operator
, "target")
181 class BVH_PT_import_transform(bpy
.types
.Panel
):
182 bl_space_type
= 'FILE_BROWSER'
183 bl_region_type
= 'TOOL_PROPS'
184 bl_label
= "Transform"
185 bl_parent_id
= "FILE_PT_operator"
188 def poll(cls
, context
):
189 sfile
= context
.space_data
190 operator
= sfile
.active_operator
192 return operator
.bl_idname
== "IMPORT_ANIM_OT_bvh"
194 def draw(self
, context
):
196 layout
.use_property_split
= True
197 layout
.use_property_decorate
= False # No animation.
199 sfile
= context
.space_data
200 operator
= sfile
.active_operator
202 layout
.prop(operator
, "global_scale")
203 layout
.prop(operator
, "rotate_mode")
204 layout
.prop(operator
, "axis_forward")
205 layout
.prop(operator
, "axis_up")
208 class BVH_PT_import_animation(bpy
.types
.Panel
):
209 bl_space_type
= 'FILE_BROWSER'
210 bl_region_type
= 'TOOL_PROPS'
211 bl_label
= "Animation"
212 bl_parent_id
= "FILE_PT_operator"
215 def poll(cls
, context
):
216 sfile
= context
.space_data
217 operator
= sfile
.active_operator
219 return operator
.bl_idname
== "IMPORT_ANIM_OT_bvh"
221 def draw(self
, context
):
223 layout
.use_property_split
= True
224 layout
.use_property_decorate
= False # No animation.
226 sfile
= context
.space_data
227 operator
= sfile
.active_operator
229 layout
.prop(operator
, "frame_start")
230 layout
.prop(operator
, "use_fps_scale")
231 layout
.prop(operator
, "use_cyclic")
233 layout
.prop(operator
, "update_scene_fps")
234 layout
.prop(operator
, "update_scene_duration")
237 class ExportBVH(bpy
.types
.Operator
, ExportHelper
):
238 """Save a BVH motion capture file from an armature"""
239 bl_idname
= "export_anim.bvh"
240 bl_label
= "Export BVH"
242 filename_ext
= ".bvh"
243 filter_glob
: StringProperty(
248 global_scale
: FloatProperty(
250 description
="Scale the BVH by this value",
251 min=0.0001, max=1000000.0,
252 soft_min
=0.001, soft_max
=100.0,
255 frame_start
: IntProperty(
257 description
="Starting frame to export",
260 frame_end
: IntProperty(
262 description
="End frame to export",
265 rotate_mode
: EnumProperty(
267 description
="Rotation conversion",
269 ('NATIVE', "Euler (Native)",
270 "Use the rotation order defined in the BVH file"),
271 ('XYZ', "Euler (XYZ)", "Convert rotations to euler XYZ"),
272 ('XZY', "Euler (XZY)", "Convert rotations to euler XZY"),
273 ('YXZ', "Euler (YXZ)", "Convert rotations to euler YXZ"),
274 ('YZX', "Euler (YZX)", "Convert rotations to euler YZX"),
275 ('ZXY', "Euler (ZXY)", "Convert rotations to euler ZXY"),
276 ('ZYX', "Euler (ZYX)", "Convert rotations to euler ZYX"),
280 root_transform_only
: BoolProperty(
281 name
="Root Translation Only",
282 description
="Only write out translation channels for the root bone",
287 def poll(cls
, context
):
289 return obj
and obj
.type == 'ARMATURE'
291 def invoke(self
, context
, event
):
292 self
.frame_start
= context
.scene
.frame_start
293 self
.frame_end
= context
.scene
.frame_end
295 return super().invoke(context
, event
)
297 def execute(self
, context
):
298 if self
.frame_start
== 0 and self
.frame_end
== 0:
299 self
.frame_start
= context
.scene
.frame_start
300 self
.frame_end
= context
.scene
.frame_end
302 keywords
= self
.as_keywords(
311 from . import export_bvh
312 return export_bvh
.save(context
, **keywords
)
314 def draw(self
, context
):
318 class BVH_PT_export_transform(bpy
.types
.Panel
):
319 bl_space_type
= 'FILE_BROWSER'
320 bl_region_type
= 'TOOL_PROPS'
321 bl_label
= "Transform"
322 bl_parent_id
= "FILE_PT_operator"
325 def poll(cls
, context
):
326 sfile
= context
.space_data
327 operator
= sfile
.active_operator
329 return operator
.bl_idname
== "EXPORT_ANIM_OT_bvh"
331 def draw(self
, context
):
333 layout
.use_property_split
= True
334 layout
.use_property_decorate
= False # No animation.
336 sfile
= context
.space_data
337 operator
= sfile
.active_operator
339 layout
.prop(operator
, "global_scale")
340 layout
.prop(operator
, "rotate_mode")
341 layout
.prop(operator
, "root_transform_only")
344 class BVH_PT_export_animation(bpy
.types
.Panel
):
345 bl_space_type
= 'FILE_BROWSER'
346 bl_region_type
= 'TOOL_PROPS'
347 bl_label
= "Animation"
348 bl_parent_id
= "FILE_PT_operator"
351 def poll(cls
, context
):
352 sfile
= context
.space_data
353 operator
= sfile
.active_operator
355 return operator
.bl_idname
== "EXPORT_ANIM_OT_bvh"
357 def draw(self
, context
):
359 layout
.use_property_split
= True
360 layout
.use_property_decorate
= False # No animation.
362 sfile
= context
.space_data
363 operator
= sfile
.active_operator
365 col
= layout
.column(align
=True)
366 col
.prop(operator
, "frame_start", text
="Frame Start")
367 col
.prop(operator
, "frame_end", text
="End")
370 def menu_func_import(self
, context
):
371 self
.layout
.operator(ImportBVH
.bl_idname
, text
="Motion Capture (.bvh)")
374 def menu_func_export(self
, context
):
375 self
.layout
.operator(ExportBVH
.bl_idname
, text
="Motion Capture (.bvh)")
381 BVH_PT_import_transform
,
382 BVH_PT_import_animation
,
384 BVH_PT_export_transform
,
385 BVH_PT_export_animation
,
390 bpy
.utils
.register_class(cls
)
392 bpy
.types
.TOPBAR_MT_file_import
.append(menu_func_import
)
393 bpy
.types
.TOPBAR_MT_file_export
.append(menu_func_export
)
398 bpy
.utils
.unregister_class(cls
)
400 bpy
.types
.TOPBAR_MT_file_import
.remove(menu_func_import
)
401 bpy
.types
.TOPBAR_MT_file_export
.remove(menu_func_export
)
403 if __name__
== "__main__":