This repository has been archived by the owner on Jul 8, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 30
/
import_seanim.py
285 lines (234 loc) · 11.4 KB
/
import_seanim.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
import bpy
from mathutils import *
from bpy_extras.wm_utils.progress_report import ProgressReport, ProgressReportSubstep
import os
from . import seanim as SEAnim
# <pep8 compliant>
# This is the scale multiplier for exported anims
g_scale = 1 # TODO - Proper scaling
# A list (in order of priority) of bone names to automatically search for
# when determining which bone to use as the root for delta anims
# All entries in this list should be lowercase
DeltaRootBones = ["tag_origin"]
def first(a, b):
"""
Compare two iterable objects
compares each element in 'a' with every element in 'b'
(Elements in 'a' are prioritized)
Returns None if there is no match
"""
for elem in a:
if elem in b:
return a
return None
def ResolvePotentialAnimTypeOverride(bone, boneAnimModifiers):
"""
Attempt to resolve the animType for a bone based on
a given list of modifier bones
Returns None if no override is needed
"""
# + [bone] # Add [bone] to the parent list if the modifier affects
# a bone and its children instead of ONLY the children
parents = bone.parent_recursive
if len(parents) == 0 or len(boneAnimModifiers) == 0:
return None
for parent in parents:
for modBone in boneAnimModifiers:
if parent.name == modBone.name:
# print("'%s' is (indirect) child of '%s'" %
# (bone.name, modBone.name))
return modBone.modifier
# print("'%s' ~default" % (bone.name))
return None
def generate_fcurves(action_fcurves, tag_name, _type, count):
'''
'tag_name': The name of the pose bone to generate fcurves for
'_type': The type of fcurve to add
ex: 'location', 'rotation_quaternion', 'scale'
'count': Number of fcurves to generate (should match up with the
number of channels for a given fcurve type)
Returns a list of the generated fcurves
'''
return [action_fcurves.new(data_path='pose.bones["%s"].%s' %
(tag_name, _type),
index=index,
action_group=tag_name)
for index in range(count)]
def load(self, context, filepath=""):
ob = bpy.context.object
if ob.type != 'ARMATURE':
return "An armature must be selected!"
path = os.path.dirname(filepath)
path = os.path.normpath(path)
try:
ob.animation_data.action
except:
ob.animation_data_create()
with ProgressReport(context.window_manager) as progress:
# Begin the progress counter with 1 step for each file
progress.enter_substeps(len(self.files))
# Force all bones to use quaternion rotation
# (Must be included or bone.rotation_quaternion won't update
# properly when setting the matrix directly)
for bone in ob.pose.bones.data.bones:
bone.rotation_mode = 'QUATERNION'
for f in self.files:
progress.enter_substeps(1, f.name)
try:
anim_path = os.path.normpath(os.path.join(path, f.name))
load_seanim(self, context, progress, anim_path)
except Exception as e:
progress.leave_substeps("ERROR: " + repr(e))
else:
progress.leave_substeps()
# Print when all files have been imported
progress.leave_substeps("Finished!")
def load_seanim(self, context, progress, filepath=""):
anim = SEAnim.Anim(filepath)
# Import the animation data
ob = bpy.context.object
bpy.ops.object.mode_set(mode='POSE')
actionName = os.path.basename(os.path.splitext(filepath)[0])
action = bpy.data.actions.new(actionName)
ob.animation_data.action = action
ob.animation_data.action.use_fake_user = True
scene = bpy.context.scene
scene.render.fps = int(anim.header.framerate)
scene.frame_start = 0 # bpy.context.scene.frame_current
scene.frame_end = scene.frame_start + anim.header.frameCount - 1
# Import the actual keyframes
progress.enter_substeps(anim.header.boneCount)
# Look up table that we use to get a given bone by name
# without having to worry about casing
bone_map = {}
for bone in ob.pose.bones:
name = bone.name.lower()
if name in bone_map:
print("Warning: Bone name conflict for '%s'\n" % name)
bone_map[bone.name.lower()] = bone
for i, tag in enumerate(anim.bones):
try:
# Attempt to resolve the root bone name (if it doesn't have one)
# based on the prioritized DeltaRootBones array
if(len(tag.name) == 0 and
anim.header.animType == SEAnim.SEANIM_TYPE.SEANIM_TYPE_DELTA):
root = first(DeltaRootBones,
[bone.name.lower() for bone in ob.pose.bones])
if root is not None:
tag.name = root
bone = bone_map[tag.name.lower()]
except:
pass
else:
animType = ResolvePotentialAnimTypeOverride(
bone, anim.boneAnimModifiers)
if animType is None:
animType = anim.header.animType
# Import the position keyframes
if len(tag.posKeys):
bone.matrix_basis.identity()
fcurves = generate_fcurves(action.fcurves, bone.name,
'location', 3)
keyCount = len(tag.posKeys)
for axis, fcurve in enumerate(fcurves):
fcurve.color_mode = 'AUTO_RGB'
# Add an extra keyframe for the control keyframe
fcurve.keyframe_points.add(keyCount + 1)
# Add the control keyframe # Can be changed to Vector((-1,
# 0)) because Location 0,0,0 is rest pos
fcurve.keyframe_points[0].co = Vector(
(-1, bone.location[axis]))
for k, key in enumerate(tag.posKeys):
# Currently the conversion is only here because I never
# added scaling options for Blender-CoD
offset = Vector(key.data) * g_scale
# Viewanims are SEANIM_TYPE_ABSOLUTE - But all children of
# j_gun has a SEANIM_TYPE_RELATIVE override
if (animType == SEAnim.SEANIM_TYPE.SEANIM_TYPE_ABSOLUTE and
bone.parent is not None):
bone.matrix.translation = bone.parent.matrix @ offset
# SEANIM_TYPE_RELATIVE
elif animType == SEAnim.SEANIM_TYPE.SEANIM_TYPE_RELATIVE:
bone.matrix_basis.translation = bone.bone.matrix.inverted() @ offset
# Use DELTA / RELATIVE results (ADDITIVE is unknown)
else:
bone.matrix_basis.translation = offset
# bone.keyframe_insert("location", index=-1, frame=key.frame, group=tag.name) # nopep8
for axis, fcurve in enumerate(fcurves):
fcurve.keyframe_points[k + 1].co = Vector((key.frame, bone.location[axis])) # nopep8
fcurve.keyframe_points[k + 1].interpolation = 'LINEAR'
# Update the FCurves
for fc in fcurves:
fc.update()
# Import the rotation keyframes
if len(tag.rotKeys):
bone.matrix_basis.identity()
fcurves = generate_fcurves(action.fcurves, bone.name,
'rotation_quaternion', 4)
keyCount = len(tag.rotKeys)
for axis, fcurve in enumerate(fcurves):
fcurve.color_mode = 'AUTO_YRGB'
# Add an extra keyframe for the control keyframe
fcurve.keyframe_points.add(keyCount + 1)
fcurve.keyframe_points[0].co = Vector(
(-1, [1, 0, 0, 0][axis])) # Add the control keyframe
for k, key in enumerate(tag.rotKeys):
# Convert the Quaternion to WXYZ
quat = Quaternion(
(key.data[3], key.data[0], key.data[1], key.data[2]))
angle = quat.to_matrix().to_3x3()
bone.matrix_basis.identity()
if bone.parent is None:
# I don't actually remember why this is here - probably
# to set the root bone(s) to its rest pos / angle
bone.matrix_basis.identity()
mat = angle.to_4x4()
else:
mat = (bone.parent.matrix.to_3x3() @ angle).to_4x4()
bone.matrix = mat
quat = bone.rotation_quaternion
for axis, fcurve in enumerate(fcurves):
# bone.rotation_quaternion[axis]
fcurve.keyframe_points[k + 1].co = Vector((key.frame, quat[axis])) # nopep8
fcurve.keyframe_points[k + 1].interpolation = 'LINEAR'
# Update the FCurves
for fc in fcurves:
fc.update()
# Import the scale keyframes
if len(tag.scaleKeys):
bone.matrix_basis.identity()
fcurves = generate_fcurves(action.fcurves, bone.name,
'scale', 3)
keyCount = len(tag.scaleKeys)
for axis, fcurve in enumerate(fcurves):
fcurve.color_mode = 'AUTO_RGB'
# Add an extra keyframe for the control keyframe
fcurve.keyframe_points.add(keyCount + 1)
fcurve.keyframe_points[0].co = Vector(
(-1, bone.scale[axis])) # Add the control keyframe
for k, key in enumerate(tag.scaleKeys):
scale = Vector(key.data)
for axis, fcurve in enumerate(fcurves):
fcurve.keyframe_points[
k + 1].co = Vector((key.frame, scale[axis]))
fcurve.keyframe_points[k + 1].interpolation = 'LINEAR'
# Update the FCurves
for fc in fcurves:
fc.update()
frame = scene.frame_start - 1
bone.keyframe_delete(data_path="location",
frame=frame, group=tag.name)
bone.keyframe_delete(data_path="rotation_quaternion",
frame=frame, group=tag.name)
bone.keyframe_delete(data_path="scale",
frame=frame, group=tag.name)
# Remove any leftover temporary transformations for this bone
bone.matrix_basis.identity()
progress.step()
progress.leave_substeps()
# Notetracks
for note in anim.notes:
notetrack = ob.animation_data.action.pose_markers.new(note.name)
notetrack.frame = note.frame
bpy.context.evaluated_depsgraph_get().update()
bpy.ops.object.mode_set(mode='POSE')