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 #####
21 # Script copyright (C) Campbell Barton
22 # fixes from Andrea Rugliancich
27 def write_armature(context
,
33 root_transform_only
=False,
36 def ensure_rot_order(rot_order_str
):
37 if set(rot_order_str
) != {'X', 'Y', 'Z'}:
41 from mathutils
import Matrix
, Euler
42 from math
import degrees
44 file = open(filepath
, "w", encoding
="utf8", newline
="\n")
49 # Build a dictionary of children.
53 # initialize with blank lists
54 for bone
in arm
.bones
:
55 children
[bone
.name
] = []
57 # keep bone order from armature, no sorting, not esspential but means
58 # we can maintain order from import -> export which secondlife incorrectly expects.
59 for bone
in arm
.bones
:
60 children
[getattr(bone
.parent
, "name", None)].append(bone
.name
)
62 # bone name list in the order that the bones are written
67 file.write("HIERARCHY\n")
69 def write_recursive_nodes(bone_name
, indent
):
70 my_children
= children
[bone_name
]
72 indent_str
= "\t" * indent
74 bone
= arm
.bones
[bone_name
]
75 pose_bone
= obj
.pose
.bones
[bone_name
]
77 node_locations
[bone_name
] = loc
79 if rotate_mode
== "NATIVE":
80 rot_order_str
= ensure_rot_order(pose_bone
.rotation_mode
)
82 rot_order_str
= rotate_mode
84 # make relative if we can
86 loc
= loc
- node_locations
[bone
.parent
.name
]
89 file.write("%sJOINT %s\n" % (indent_str
, bone_name
))
91 file.write("%sROOT %s\n" % (indent_str
, bone_name
))
93 file.write("%s{\n" % indent_str
)
94 file.write("%s\tOFFSET %.6f %.6f %.6f\n" % (indent_str
, loc
.x
* global_scale
, loc
.y
* global_scale
, loc
.z
* global_scale
))
95 if (bone
.use_connect
or root_transform_only
) and bone
.parent
:
96 file.write("%s\tCHANNELS 3 %srotation %srotation %srotation\n" % (indent_str
, rot_order_str
[0], rot_order_str
[1], rot_order_str
[2]))
98 file.write("%s\tCHANNELS 6 Xposition Yposition Zposition %srotation %srotation %srotation\n" % (indent_str
, rot_order_str
[0], rot_order_str
[1], rot_order_str
[2]))
101 # store the location for the children
102 # to get their relative offset
105 for child_bone
in my_children
:
106 serialized_names
.append(child_bone
)
107 write_recursive_nodes(child_bone
, indent
+ 1)
110 # Write the bone end.
111 file.write("%s\tEnd Site\n" % indent_str
)
112 file.write("%s\t{\n" % indent_str
)
113 loc
= bone
.tail_local
- node_locations
[bone_name
]
114 file.write("%s\t\tOFFSET %.6f %.6f %.6f\n" % (indent_str
, loc
.x
* global_scale
, loc
.y
* global_scale
, loc
.z
* global_scale
))
115 file.write("%s\t}\n" % indent_str
)
117 file.write("%s}\n" % indent_str
)
119 if len(children
[None]) == 1:
120 key
= children
[None][0]
121 serialized_names
.append(key
)
124 write_recursive_nodes(key
, indent
)
127 # Write a dummy parent node, with a dummy key name
128 # Just be sure it's not used by another bone!
131 while key
in children
:
134 file.write("ROOT %s\n" % key
)
136 file.write("\tOFFSET 0.0 0.0 0.0\n")
137 file.write("\tCHANNELS 0\n") # Xposition Yposition Zposition Xrotation Yrotation Zrotation
141 for child_bone
in children
[None]:
142 serialized_names
.append(child_bone
)
143 write_recursive_nodes(child_bone
, indent
)
147 # redefine bones as sorted by serialized_names
148 # so we can write motion
152 "name", # bone name, used as key in many places
153 "parent", # decorated bone parent, set in a later loop
154 "rest_bone", # blender armature bone
155 "pose_bone", # blender pose bone
156 "pose_mat", # blender pose matrix
157 "rest_arm_mat", # blender rest matrix (armature space)
158 "rest_local_mat", # blender rest batrix (local space)
159 "pose_imat", # pose_mat inverted
160 "rest_arm_imat", # rest_arm_mat inverted
161 "rest_local_imat", # rest_local_mat inverted
162 "prev_euler", # last used euler to preserve euler compability in between keyframes
163 "skip_position", # is the bone disconnected to the parent bone?
166 "rot_order_str_reverse", # needed for the euler order when converting from a matrix
169 _eul_order_lookup
= {
178 def __init__(self
, bone_name
):
179 self
.name
= bone_name
180 self
.rest_bone
= arm
.bones
[bone_name
]
181 self
.pose_bone
= obj
.pose
.bones
[bone_name
]
183 if rotate_mode
== "NATIVE":
184 self
.rot_order_str
= ensure_rot_order(self
.pose_bone
.rotation_mode
)
186 self
.rot_order_str
= rotate_mode
187 self
.rot_order_str_reverse
= self
.rot_order_str
[::-1]
189 self
.rot_order
= DecoratedBone
._eul
_order
_lookup
[self
.rot_order_str
]
191 self
.pose_mat
= self
.pose_bone
.matrix
193 # mat = self.rest_bone.matrix # UNUSED
194 self
.rest_arm_mat
= self
.rest_bone
.matrix_local
195 self
.rest_local_mat
= self
.rest_bone
.matrix
198 self
.pose_imat
= self
.pose_mat
.inverted()
199 self
.rest_arm_imat
= self
.rest_arm_mat
.inverted()
200 self
.rest_local_imat
= self
.rest_local_mat
.inverted()
203 self
.prev_euler
= Euler((0.0, 0.0, 0.0), self
.rot_order_str_reverse
)
204 self
.skip_position
= ((self
.rest_bone
.use_connect
or root_transform_only
) and self
.rest_bone
.parent
)
206 def update_posedata(self
):
207 self
.pose_mat
= self
.pose_bone
.matrix
208 self
.pose_imat
= self
.pose_mat
.inverted()
212 return "[\"%s\" child on \"%s\"]\n" % (self
.name
, self
.parent
.name
)
214 return "[\"%s\" root bone]\n" % (self
.name
)
216 bones_decorated
= [DecoratedBone(bone_name
) for bone_name
in serialized_names
]
219 bones_decorated_dict
= {}
220 for dbone
in bones_decorated
:
221 bones_decorated_dict
[dbone
.name
] = dbone
223 for dbone
in bones_decorated
:
224 parent
= dbone
.rest_bone
.parent
226 dbone
.parent
= bones_decorated_dict
[parent
.name
]
227 del bones_decorated_dict
228 # finish assigning parents
230 scene
= bpy
.context
.scene
231 frame_current
= scene
.frame_current
233 file.write("MOTION\n")
234 file.write("Frames: %d\n" % (frame_end
- frame_start
+ 1))
235 file.write("Frame Time: %.6f\n" % (1.0 / (scene
.render
.fps
/ scene
.render
.fps_base
)))
237 for frame
in range(frame_start
, frame_end
+ 1):
238 scene
.frame_set(frame
)
240 for dbone
in bones_decorated
:
241 dbone
.update_posedata()
243 for dbone
in bones_decorated
:
244 trans
= Matrix
.Translation(dbone
.rest_bone
.head_local
)
245 itrans
= Matrix
.Translation(-dbone
.rest_bone
.head_local
)
248 mat_final
= dbone
.parent
.rest_arm_mat
* dbone
.parent
.pose_imat
* dbone
.pose_mat
* dbone
.rest_arm_imat
249 mat_final
= itrans
* mat_final
* trans
250 loc
= mat_final
.to_translation() + (dbone
.rest_bone
.head_local
- dbone
.parent
.rest_bone
.head_local
)
252 mat_final
= dbone
.pose_mat
* dbone
.rest_arm_imat
253 mat_final
= itrans
* mat_final
* trans
254 loc
= mat_final
.to_translation() + dbone
.rest_bone
.head
256 # keep eulers compatible, no jumping on interpolation.
257 rot
= mat_final
.to_euler(dbone
.rot_order_str_reverse
, dbone
.prev_euler
)
259 if not dbone
.skip_position
:
260 file.write("%.6f %.6f %.6f " % (loc
* global_scale
)[:])
262 file.write("%.6f %.6f %.6f " % (degrees(rot
[dbone
.rot_order
[0]]), degrees(rot
[dbone
.rot_order
[1]]), degrees(rot
[dbone
.rot_order
[2]])))
264 dbone
.prev_euler
= rot
270 scene
.frame_set(frame_current
)
272 print("BVH Exported: %s frames:%d\n" % (filepath
, frame_end
- frame_start
+ 1))
275 def save(operator
, context
, filepath
="",
279 rotate_mode
="NATIVE",
280 root_transform_only
=False,
283 write_armature(context
, filepath
,
284 frame_start
=frame_start
,
286 global_scale
=global_scale
,
287 rotate_mode
=rotate_mode
,
288 root_transform_only
=root_transform_only
,