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
34 root_transform_only
=False,
37 def ensure_rot_order(rot_order_str
):
38 if set(rot_order_str
) != {'X', 'Y', 'Z'}:
42 from mathutils
import Matrix
, Euler
43 from math
import degrees
45 file = open(filepath
, "w", encoding
="utf8", newline
="\n")
50 # Build a dictionary of children.
54 # initialize with blank lists
55 for bone
in arm
.bones
:
56 children
[bone
.name
] = []
58 # keep bone order from armature, no sorting, not esspential but means
59 # we can maintain order from import -> export which secondlife incorrectly expects.
60 for bone
in arm
.bones
:
61 children
[getattr(bone
.parent
, "name", None)].append(bone
.name
)
63 # bone name list in the order that the bones are written
68 file.write("HIERARCHY\n")
70 def write_recursive_nodes(bone_name
, indent
):
71 my_children
= children
[bone_name
]
73 indent_str
= "\t" * indent
75 bone
= arm
.bones
[bone_name
]
76 pose_bone
= obj
.pose
.bones
[bone_name
]
78 node_locations
[bone_name
] = loc
80 if rotate_mode
== "NATIVE":
81 rot_order_str
= ensure_rot_order(pose_bone
.rotation_mode
)
83 rot_order_str
= rotate_mode
85 # make relative if we can
87 loc
= loc
- node_locations
[bone
.parent
.name
]
90 file.write("%sJOINT %s\n" % (indent_str
, bone_name
))
92 file.write("%sROOT %s\n" % (indent_str
, bone_name
))
94 file.write("%s{\n" % indent_str
)
95 file.write("%s\tOFFSET %.6f %.6f %.6f\n" % (indent_str
, *(loc
* global_scale
)))
96 if (bone
.use_connect
or root_transform_only
) and bone
.parent
:
97 file.write("%s\tCHANNELS 3 %srotation %srotation %srotation\n" % (indent_str
, *rot_order_str
))
99 file.write("%s\tCHANNELS 6 Xposition Yposition Zposition %srotation %srotation %srotation\n" % (indent_str
, *rot_order_str
))
102 # store the location for the children
103 # to get their relative offset
106 for child_bone
in my_children
:
107 serialized_names
.append(child_bone
)
108 write_recursive_nodes(child_bone
, indent
+ 1)
111 # Write the bone end.
112 file.write("%s\tEnd Site\n" % indent_str
)
113 file.write("%s\t{\n" % indent_str
)
114 loc
= bone
.tail_local
- node_locations
[bone_name
]
115 file.write("%s\t\tOFFSET %.6f %.6f %.6f\n" % (indent_str
, *(loc
* global_scale
)))
116 file.write("%s\t}\n" % indent_str
)
118 file.write("%s}\n" % indent_str
)
120 if len(children
[None]) == 1:
121 key
= children
[None][0]
122 serialized_names
.append(key
)
125 write_recursive_nodes(key
, indent
)
128 # Write a dummy parent node, with a dummy key name
129 # Just be sure it's not used by another bone!
132 while key
in children
:
135 file.write("ROOT %s\n" % key
)
137 file.write("\tOFFSET 0.0 0.0 0.0\n")
138 file.write("\tCHANNELS 0\n") # Xposition Yposition Zposition Xrotation Yrotation Zrotation
142 for child_bone
in children
[None]:
143 serialized_names
.append(child_bone
)
144 write_recursive_nodes(child_bone
, indent
)
148 # redefine bones as sorted by serialized_names
149 # so we can write motion
153 # Bone name, used as key in many places.
155 "parent", # decorated bone parent, set in a later loop
156 # Blender armature bone.
160 # Blender pose matrix.
162 # Blender rest matrix (armature space).
164 # Blender rest batrix (local space).
168 # Rest_arm_mat inverted.
170 # Rest_local_mat inverted.
172 # Last used euler to preserve euler compability in between keyframes.
174 # Is the bone disconnected to the parent bone?
178 # Needed for the euler order when converting from a matrix.
179 "rot_order_str_reverse",
182 _eul_order_lookup
= {
191 def __init__(self
, bone_name
):
192 self
.name
= bone_name
193 self
.rest_bone
= arm
.bones
[bone_name
]
194 self
.pose_bone
= obj
.pose
.bones
[bone_name
]
196 if rotate_mode
== "NATIVE":
197 self
.rot_order_str
= ensure_rot_order(self
.pose_bone
.rotation_mode
)
199 self
.rot_order_str
= rotate_mode
200 self
.rot_order_str_reverse
= self
.rot_order_str
[::-1]
202 self
.rot_order
= DecoratedBone
._eul
_order
_lookup
[self
.rot_order_str
]
204 self
.pose_mat
= self
.pose_bone
.matrix
206 # mat = self.rest_bone.matrix # UNUSED
207 self
.rest_arm_mat
= self
.rest_bone
.matrix_local
208 self
.rest_local_mat
= self
.rest_bone
.matrix
211 self
.pose_imat
= self
.pose_mat
.inverted()
212 self
.rest_arm_imat
= self
.rest_arm_mat
.inverted()
213 self
.rest_local_imat
= self
.rest_local_mat
.inverted()
216 self
.prev_euler
= Euler((0.0, 0.0, 0.0), self
.rot_order_str_reverse
)
217 self
.skip_position
= ((self
.rest_bone
.use_connect
or root_transform_only
) and self
.rest_bone
.parent
)
219 def update_posedata(self
):
220 self
.pose_mat
= self
.pose_bone
.matrix
221 self
.pose_imat
= self
.pose_mat
.inverted()
225 return "[\"%s\" child on \"%s\"]\n" % (self
.name
, self
.parent
.name
)
227 return "[\"%s\" root bone]\n" % (self
.name
)
229 bones_decorated
= [DecoratedBone(bone_name
) for bone_name
in serialized_names
]
232 bones_decorated_dict
= {dbone
.name
: dbone
for dbone
in bones_decorated
}
233 for dbone
in bones_decorated
:
234 parent
= dbone
.rest_bone
.parent
236 dbone
.parent
= bones_decorated_dict
[parent
.name
]
237 del bones_decorated_dict
238 # finish assigning parents
240 scene
= context
.scene
241 frame_current
= scene
.frame_current
243 file.write("MOTION\n")
244 file.write("Frames: %d\n" % (frame_end
- frame_start
+ 1))
245 file.write("Frame Time: %.6f\n" % (1.0 / (scene
.render
.fps
/ scene
.render
.fps_base
)))
247 for frame
in range(frame_start
, frame_end
+ 1):
248 scene
.frame_set(frame
)
250 for dbone
in bones_decorated
:
251 dbone
.update_posedata()
253 for dbone
in bones_decorated
:
254 trans
= Matrix
.Translation(dbone
.rest_bone
.head_local
)
255 itrans
= Matrix
.Translation(-dbone
.rest_bone
.head_local
)
258 mat_final
= dbone
.parent
.rest_arm_mat
* dbone
.parent
.pose_imat
* dbone
.pose_mat
* dbone
.rest_arm_imat
259 mat_final
= itrans
* mat_final
* trans
260 loc
= mat_final
.to_translation() + (dbone
.rest_bone
.head_local
- dbone
.parent
.rest_bone
.head_local
)
262 mat_final
= dbone
.pose_mat
* dbone
.rest_arm_imat
263 mat_final
= itrans
* mat_final
* trans
264 loc
= mat_final
.to_translation() + dbone
.rest_bone
.head
266 # keep eulers compatible, no jumping on interpolation.
267 rot
= mat_final
.to_euler(dbone
.rot_order_str_reverse
, dbone
.prev_euler
)
269 if not dbone
.skip_position
:
270 file.write("%.6f %.6f %.6f " % (loc
* global_scale
)[:])
272 file.write("%.6f %.6f %.6f " % (degrees(rot
[dbone
.rot_order
[0]]), degrees(rot
[dbone
.rot_order
[1]]), degrees(rot
[dbone
.rot_order
[2]])))
274 dbone
.prev_euler
= rot
280 scene
.frame_set(frame_current
)
282 print("BVH Exported: %s frames:%d\n" % (filepath
, frame_end
- frame_start
+ 1))
286 context
, filepath
="",
290 rotate_mode
="NATIVE",
291 root_transform_only
=False,
295 frame_start
=frame_start
,
297 global_scale
=global_scale
,
298 rotate_mode
=rotate_mode
,
299 root_transform_only
=root_transform_only
,