CM3D2 Converter.anm_export
1import re 2import struct 3import math 4import unicodedata 5import time 6import bpy 7import bmesh 8import mathutils 9from . import common 10from . import compat 11from .translations.pgettext_functions import * 12from . import misc_DOPESHEET_MT_editor_menus 13 14 15# メインオペレーター 16@compat.BlRegister() 17class CNV_OT_export_cm3d2_anm(bpy.types.Operator): 18 bl_idname = 'export_anim.export_cm3d2_anm' 19 bl_label = "CM3D2モーション (.anm)" 20 bl_description = "カスタムメイド3D2のanmファイルを保存します" 21 bl_options = {'REGISTER'} 22 23 filepath = bpy.props.StringProperty(subtype='FILE_PATH') 24 filename_ext = ".anm" 25 filter_glob = bpy.props.StringProperty(default="*.anm", options={'HIDDEN'}) 26 27 scale = bpy.props.FloatProperty(name="倍率", default=0.2, min=0.1, max=100, soft_min=0.1, soft_max=100, step=100, precision=1, description="エクスポート時のメッシュ等の拡大率です") 28 is_backup = bpy.props.BoolProperty(name="ファイルをバックアップ", default=True, description="ファイルに上書きする場合にバックアップファイルを複製します") 29 version = bpy.props.IntProperty(name="ファイルバージョン", default=1000, min=1000, max=1111, soft_min=1000, soft_max=1111, step=1) 30 31 #is_anm_data_text = bpy.props.BoolProperty(name="From Anm Text", default=False, description="Input data from JSON file") 32 items = [ 33 ('ALL' , "Bake All Frames" , "Export every frame as a keyframe (legacy behavior, large file sizes)", 'SEQUENCE' , 1), 34 ('KEYED', "Only Export Keyframes", "Only export keyframes and their tangents (for more advance users)" , 'KEYINGSET', 2), 35 ('TEXT' , "From Anm Text JSON" , "Export data from the JSON in the 'AnmData' text file" , 'TEXT' , 3) 36 ] 37 export_method = bpy.props.EnumProperty(items=items, name="Export Method", default='ALL') 38 39 40 frame_start = bpy.props.IntProperty(name="開始フレーム", default=0, min=0, max=99999, soft_min=0, soft_max=99999, step=1) 41 frame_end = bpy.props.IntProperty(name="最終フレーム", default=0, min=0, max=99999, soft_min=0, soft_max=99999, step=1) 42 key_frame_count = bpy.props.IntProperty(name="キーフレーム数", default=1, min=1, max=99999, soft_min=1, soft_max=99999, step=1) 43 time_scale = bpy.props.FloatProperty(name="再生速度", default=1.0, min=0.1, max=10.0, soft_min=0.1, soft_max=10.0, step=10, precision=1) 44 is_keyframe_clean = bpy.props.BoolProperty(name="同じ変形のキーフレームを掃除", default=True) 45 is_visual_transform = bpy.props.BoolProperty(name="Use Visual Transforms", default=True ) 46 is_smooth_handle = bpy.props.BoolProperty(name="キーフレーム間の変形をスムーズに", default=True) 47 48 items = [ 49 ('ARMATURE', "アーマチュア", "", 'OUTLINER_OB_ARMATURE', 1), 50 ('ARMATURE_PROPERTY', "アーマチュア内プロパティ", "", 'ARMATURE_DATA', 2), 51 ] 52 bone_parent_from = bpy.props.EnumProperty(items=items, name="ボーン親情報の参照先", default='ARMATURE_PROPERTY') 53 54 is_remove_unkeyed_bone = bpy.props.BoolProperty(name="Remove Unkeyed Bones", default=False) 55 is_remove_alone_bone = bpy.props.BoolProperty(name="親も子も存在しない", default=True) 56 is_remove_ik_bone = bpy.props.BoolProperty(name="名前がIK/Nubっぽい", default=True) 57 is_remove_serial_number_bone = bpy.props.BoolProperty(name="名前が連番付き", default=True) 58 is_remove_japanese_bone = bpy.props.BoolProperty(name="名前に日本語が含まれる", default=True) 59 60 @classmethod 61 def poll(cls, context): 62 ob = context.active_object 63 if ob and ob.type == 'ARMATURE': 64 return True 65 return False 66 67 def invoke(self, context, event): 68 prefs = common.preferences() 69 70 ob = context.active_object 71 arm = ob.data 72 action_name = None 73 if ob.animation_data and ob.animation_data.action: 74 action_name = common.remove_serial_number(ob.animation_data.action.name) 75 76 if prefs.anm_default_path: 77 self.filepath = common.default_cm3d2_dir(prefs.anm_default_path, action_name, "anm") 78 else: 79 self.filepath = common.default_cm3d2_dir(prefs.anm_export_path, action_name, "anm") 80 self.frame_start = context.scene.frame_start 81 self.frame_end = context.scene.frame_end 82 self.scale = 1.0 / prefs.scale 83 self.is_backup = bool(prefs.backup_ext) 84 self.key_frame_count = (context.scene.frame_end - context.scene.frame_start) + 1 85 86 if "BoneData:0" in arm: 87 self.bone_parent_from = 'ARMATURE_PROPERTY' 88 else: 89 self.bone_parent_from = 'ARMATURE' 90 91 context.window_manager.fileselect_add(self) 92 return {'RUNNING_MODAL'} 93 94 def draw(self, context): 95 self.layout.prop(self, 'scale') 96 97 box = self.layout.box() 98 box.prop(self, 'is_backup', icon='FILE_BACKUP') 99 box.prop(self, 'version') 100 101 #self.layout.prop(self, 'is_anm_data_text', icon='TEXT') 102 box = self.layout.box() 103 box.label(text="Export Method") 104 box.prop(self, 'export_method', expand=True) 105 106 box = self.layout.box() 107 box.enabled = not (self.export_method == 'TEXT') 108 box.prop(self, 'time_scale') 109 sub_box = box.box() 110 sub_box.enabled = (self.export_method == 'ALL') 111 row = sub_box.row() 112 row.prop(self, 'frame_start') 113 row.prop(self, 'frame_end') 114 sub_box.prop(self, 'key_frame_count') 115 sub_box.prop(self, 'is_keyframe_clean', icon='DISCLOSURE_TRI_DOWN') 116 sub_box.prop(self, 'is_smooth_handle', icon='SMOOTHCURVE') 117 118 sub_box = box.box() 119 sub_box.label(text="ボーン親情報の参照先", icon='FILE_PARENT') 120 sub_box.prop(self, 'bone_parent_from', icon='FILE_PARENT', expand=True) 121 122 sub_box = box.box() 123 sub_box.label(text="除外するボーン", icon='X') 124 column = sub_box.column(align=True) 125 column.prop(self, 'is_remove_unkeyed_bone' , icon='KEY_DEHLT' ) 126 column.prop(self, 'is_remove_alone_bone' , icon='UNLINKED' ) 127 column.prop(self, 'is_remove_ik_bone' , icon='CONSTRAINT_BONE' ) 128 column.prop(self, 'is_remove_serial_number_bone', icon='SEQUENCE' ) 129 column.prop(self, 'is_remove_japanese_bone' , icon=compat.icon('HOLDOUT_ON')) 130 131 def execute(self, context): 132 common.preferences().anm_export_path = self.filepath 133 134 try: 135 file = common.open_temporary(self.filepath, 'wb', is_backup=self.is_backup) 136 except: 137 self.report(type={'ERROR'}, message=f_tip_("ファイルを開くのに失敗しました、アクセス不可かファイルが存在しません。file={}", self.filepath)) 138 return {'CANCELLED'} 139 140 try: 141 with file: 142 if self.export_method == 'TEXT': 143 self.write_animation_from_text(context, file) 144 else: 145 self.write_animation(context, file) 146 except common.CM3D2ExportException as e: 147 self.report(type={'ERROR'}, message=str(e)) 148 return {'CANCELLED'} 149 150 return {'FINISHED'} 151 152 def get_animation_frames(self, context, fps, pose, bones, bone_parents): 153 anm_data_raw = {} 154 class KeyFrame: 155 def __init__(self, time, value, slope=None): 156 self.time = time 157 self.value = value 158 if slope: 159 self.slope = slope 160 elif type(value) == mathutils.Vector: 161 self.slope = mathutils.Vector.Fill(len(value)) 162 elif type(value) == mathutils.Quaternion: 163 self.slope = mathutils.Quaternion((0,0,0,0)) 164 else: 165 self.slope = 0 166 167 same_locs = {} 168 same_rots = {} 169 pre_rots = {} 170 for key_frame_index in range(self.key_frame_count): 171 if self.key_frame_count == 1: 172 frame = 0.0 173 else: 174 frame = (self.frame_end - self.frame_start) / (self.key_frame_count - 1) * key_frame_index + self.frame_start 175 context.scene.frame_set(frame=int(frame), subframe=frame - int(frame)) 176 if compat.IS_LEGACY: 177 context.scene.update() 178 else: 179 layer = context.view_layer 180 layer.update() 181 182 time = frame / fps * (1.0 / self.time_scale) 183 184 for bone in bones: 185 if bone.name not in anm_data_raw: 186 anm_data_raw[bone.name] = {"LOC": {}, "ROT": {}} 187 same_locs[bone.name] = [] 188 same_rots[bone.name] = [] 189 190 pose_bone = pose.bones[bone.name] 191 pose_mat = pose_bone.matrix.copy() #ob.convert_space(pose_bone=pose_bone, matrix=pose_bone.matrix, from_space='POSE', to_space='WORLD') 192 parent = bone_parents[bone.name] 193 if parent: 194 pose_mat = compat.convert_bl_to_cm_bone_rotation(pose_mat) 195 pose_mat = compat.mul(pose.bones[parent.name].matrix.inverted(), pose_mat) 196 pose_mat = compat.convert_bl_to_cm_bone_space(pose_mat) 197 else: 198 pose_mat = compat.convert_bl_to_cm_bone_rotation(pose_mat) 199 pose_mat = compat.convert_bl_to_cm_space(pose_mat) 200 201 loc = pose_mat.to_translation() * self.scale 202 rot = pose_mat.to_quaternion() 203 204 # This fixes rotations that jump to alternate representations. 205 if bone.name in pre_rots: 206 if 5.0 < pre_rots[bone.name].rotation_difference(rot).angle: 207 rot.w, rot.x, rot.y, rot.z = -rot.w, -rot.x, -rot.y, -rot.z 208 pre_rots[bone.name] = rot.copy() 209 210 #if parent: 211 # #loc.x, loc.y, loc.z = -loc.y, -loc.x, loc.z 212 # loc = compat.convert_bl_to_cm_bone_space(loc) 213 # 214 # # quat.w, quat.x, quat.y, quat.z = quat.w, -quat.z, quat.x, -quat.y 215 # #rot.w, rot.x, rot.y, rot.z = rot.w, rot.y, rot.x, -rot.z 216 # rot.w, rot.x, rot.y, rot.z = rot.w, rot.y, -rot.z, -rot.x 217 # 218 #else: 219 # loc.x, loc.y, loc.z = -loc.x, loc.z, -loc.y 220 # 221 # fix_quat = mathutils.Euler((0, 0, math.radians(-90)), 'XYZ').to_quaternion() 222 # fix_quat2 = mathutils.Euler((math.radians(-90), 0, 0), 'XYZ').to_quaternion() 223 # rot = compat.mul3(rot, fix_quat, fix_quat2) 224 # 225 # rot.w, rot.x, rot.y, rot.z = -rot.y, -rot.z, -rot.x, rot.w 226 227 if not self.is_keyframe_clean or key_frame_index == 0 or key_frame_index == self.key_frame_count - 1: 228 anm_data_raw[bone.name]["LOC"][time] = loc.copy() 229 anm_data_raw[bone.name]["ROT"][time] = rot.copy() 230 231 if self.is_keyframe_clean: 232 same_locs[bone.name].append(KeyFrame(time, loc.copy())) 233 same_rots[bone.name].append(KeyFrame(time, rot.copy())) 234 else: 235 def is_mismatch(a, b): 236 return 0.000001 < abs(a - b) 237 238 a = same_locs[bone.name][-1].value - loc 239 b = same_locs[bone.name][-1].slope 240 if is_mismatch(a.x, b.x) or is_mismatch(a.y, b.y) or is_mismatch(a.z, b.z): 241 if 2 <= len(same_locs[bone.name]): 242 anm_data_raw[bone.name]["LOC"][same_locs[bone.name][-1].time] = same_locs[bone.name][-1].value.copy() 243 anm_data_raw[bone.name]["LOC"][time] = loc.copy() 244 same_locs[bone.name] = [KeyFrame(time, loc.copy(), a.copy())] # update last position and slope 245 else: 246 same_locs[bone.name].append(KeyFrame(time, loc.copy(), b.copy())) # update last position, but not last slope 247 248 a = same_rots[bone.name][-1].value - rot 249 b = same_rots[bone.name][-1].slope 250 if is_mismatch(a.w, b.w) or is_mismatch(a.x, b.x) or is_mismatch(a.y, b.y) or is_mismatch(a.z, b.z): 251 if 2 <= len(same_rots[bone.name]): 252 anm_data_raw[bone.name]["ROT"][same_rots[bone.name][-1].time] = same_rots[bone.name][-1].value.copy() 253 anm_data_raw[bone.name]["ROT"][time] = rot.copy() 254 same_rots[bone.name] = [KeyFrame(time, rot.copy(), a.copy())] # update last position and slope 255 else: 256 same_rots[bone.name].append(KeyFrame(time, rot.copy(), b.copy())) # update last position, but not last slope 257 258 return anm_data_raw 259 260 def get_animation_keyframes(self, context, fps, pose, keyed_bones, fcurves): 261 anm_data_raw = {} 262 263 prop_sizes = {'location': 3, 'rotation_quaternion': 4, 'rotation_euler': 3} 264 265 #class KeyFrame: 266 # def __init__(self, time, value): 267 # self.time = time 268 # self.value = value 269 #same_locs = {} 270 #same_rots = {} 271 #pre_rots = {} 272 273 def _convert_loc(pose_bone, loc): 274 loc = mathutils.Vector(loc) 275 loc = compat.mul(pose_bone.bone.matrix_local, loc) 276 if pose_bone.parent: 277 loc = compat.mul(pose_bone.parent.bone.matrix_local.inverted(), loc) 278 loc = compat.convert_bl_to_cm_bone_space(loc) 279 else: 280 loc = compat.convert_bl_to_cm_space(loc) 281 return loc * self.scale 282 """ 283 def _convert_quat(pose_bone, quat): 284 #quat = mathutils.Quaternion(quat) 285 #'''Can't use matrix transforms here as they would mess up interpolation.''' 286 #quat = compat.mul(pose_bone.bone.matrix_local.to_quaternion(), quat) 287 288 quat_mat = mathutils.Quaternion(quat).to_matrix().to_4x4() 289 quat_mat = compat.mul(pose_bone.bone.matrix_local, quat_mat) 290 #quat = quat_mat.to_quaternion() 291 if pose_bone.parent: 292 ## inverse of quat.w, quat.x, quat.y, quat.z = quat.w, -quat.z, quat.x, -quat.y 293 #quat.w, quat.x, quat.y, quat.z = quat.w, quat.y, -quat.z, -quat.x 294 #quat = compat.mul(pose_bone.parent.bone.matrix_local.to_quaternion().inverted(), quat) 295 ##quat = compat.mul(pose_bone.parent.bone.matrix_local.inverted().to_quaternion(), quat)\ 296 quat_mat = compat.convert_bl_to_cm_bone_rotation(quat_mat) 297 quat_mat = compat.mul(pose_bone.parent.bone.matrix_local.inverted(), quat_mat) 298 quat_mat = compat.convert_bl_to_cm_bone_space(quat_mat) 299 quat = quat_mat.to_quaternion() 300 else: 301 #fix_quat = mathutils.Euler((0, 0, math.radians(-90)), 'XYZ').to_quaternion() 302 #fix_quat2 = mathutils.Euler((math.radians(-90), 0, 0), 'XYZ').to_quaternion() 303 #quat = compat.mul3(quat, fix_quat, fix_quat2) 304 # 305 #quat.w, quat.x, quat.y, quat.z = -quat.y, -quat.z, -quat.x, quat.w 306 307 #quat.w, quat.x, quat.y, quat.z = quat.w, quat.y, -quat.z, -quat.x 308 #quat = compat.mul(mathutils.Matrix.Rotation(math.radians(90.0), 4, 'Z').to_quaternion(), quat) 309 310 quat_mat = compat.convert_bl_to_cm_bone_rotation(quat_mat) 311 quat_mat = compat.convert_bl_to_cm_space(quat_mat) 312 quat = quat_mat.to_quaternion() 313 return quat 314 """ 315 316 def _convert_quat(pose_bone, quat): 317 bone_quat = pose_bone.bone.matrix.to_quaternion() 318 quat = mathutils.Quaternion(quat) 319 320 '''Can't use matrix transforms here as they would mess up interpolation.''' 321 quat = compat.mul(bone_quat, quat) 322 323 if pose_bone.bone.parent: 324 #quat.w, quat.x, quat.y, quat.z = quat.w, -quat.z, quat.x, -quat.y 325 quat.w, quat.y, quat.x, quat.z = quat.w, -quat.z, quat.y, -quat.x 326 else: 327 quat = compat.mul(mathutils.Matrix.Rotation(math.radians(90.0), 4, 'Z').to_quaternion(), quat) 328 quat.w, quat.y, quat.x, quat.z = quat.w, -quat.z, quat.y, -quat.x 329 return quat 330 331 for prop, prop_keyed_bones in keyed_bones.items(): 332 #self.report(type={'INFO'}, message=f_tip_("{prop} {list}", prop=prop, list=prop_keyed_bones)) 333 for bone_name in prop_keyed_bones: 334 if bone_name not in anm_data_raw: 335 anm_data_raw[bone_name] = {} 336 #same_locs[bone_name] = [] 337 #same_rots[bone_name] = [] 338 339 pose_bone = pose.bones[bone_name] 340 rna_data_path = 'pose.bones["{bone_name}"].{property}'.format(bone_name=bone_name, property=prop) 341 prop_fcurves = [ fcurves.find(rna_data_path, index=axis_index) for axis_index in range(prop_sizes[prop]) ] 342 343 # Create missing fcurves, and make existing fcurves CM3D2 compatible. 344 for axis_index, fcurve in enumerate(prop_fcurves): 345 if not fcurve: 346 fcurve = fcurves.new(rna_data_path, index=axis_index, action_group=pose_bone.name) 347 prop_fcurves[axis_index] = fcurve 348 self.report(type={'WARNING'}, message=f_tip_("Creating missing FCurve for {path}[{index}]", path=rna_data_path, index=axis_index)) 349 else: 350 override = context.copy() 351 override['active_editable_fcurve'] = fcurve 352 bpy.ops.fcurve.convert_to_cm3d2_interpolation(override, only_selected=False, keep_reports=True) 353 for kwargs in misc_DOPESHEET_MT_editor_menus.REPORTS: 354 self.report(**kwargs) 355 misc_DOPESHEET_MT_editor_menus.REPORTS.clear() 356 357 358 # Create a list by frame, indicating wether or not there is a keyframe at that time for each fcurve 359 is_keyframes = {} 360 for fcurve in prop_fcurves: 361 for keyframe in fcurve.keyframe_points: 362 frame = keyframe.co[0] 363 if frame not in is_keyframes: 364 is_keyframes[frame] = [False] * prop_sizes[prop] 365 is_keyframes[frame][fcurve.array_index] = True 366 367 # Make sure that no keyframe times are missing any components 368 for frame, is_axes in is_keyframes.items(): 369 for axis_index, is_axis in enumerate(is_axes): 370 if not is_axis: 371 fcurve = prop_fcurves[axis_index] 372 keyframe = fcurve.keyframe_points.insert( 373 frame = frame , 374 value = fcurve.evaluate(frame), 375 options = {'NEEDED', 'FAST'} 376 ) 377 self.report(type={'WARNING'}, message=f_tip_("Creating missing keyframe @ frame {frame} for {path}[{index}]", path=rna_data_path, index=axis_index, frame=frame)) 378 379 for fcurve in prop_fcurves: 380 fcurve.update() 381 382 for keyframe_index, frame in enumerate(is_keyframes.keys()): 383 time = frame / fps * (1.0 / self.time_scale) 384 385 _kf = lambda fcurve: fcurve.keyframe_points[keyframe_index] 386 raw_keyframe = [ _kf(fc).co[1] for fc in prop_fcurves ] 387 tangent_in = [ ( _kf(fc).handle_left [1] - _kf(fc).co[1] ) / ( _kf(fc).handle_left [0] - _kf(fc).co[0] ) * fps for fc in prop_fcurves ] 388 tangent_out = [ ( _kf(fc).handle_right[1] - _kf(fc).co[1] ) / ( _kf(fc).handle_right[0] - _kf(fc).co[0] ) * fps for fc in prop_fcurves ] 389 390 if prop == 'location': 391 if 'LOC' not in anm_data_raw[bone_name]: 392 anm_data_raw[bone_name]['LOC' ] = {} 393 anm_data_raw[bone_name]['LOC_IN' ] = {} 394 anm_data_raw[bone_name]['LOC_OUT'] = {} 395 anm_data_raw[bone_name]['LOC' ][time] = _convert_loc(pose_bone, raw_keyframe).copy() 396 anm_data_raw[bone_name]['LOC_IN' ][time] = _convert_loc(pose_bone, tangent_in ).copy() 397 anm_data_raw[bone_name]['LOC_OUT'][time] = _convert_loc(pose_bone, tangent_out ).copy() 398 elif prop == 'rotation_quaternion': 399 if 'ROT' not in anm_data_raw[bone_name]: 400 anm_data_raw[bone_name]['ROT' ] = {} 401 anm_data_raw[bone_name]['ROT_IN' ] = {} 402 anm_data_raw[bone_name]['ROT_OUT'] = {} 403 anm_data_raw[bone_name]['ROT' ][time] = _convert_quat(pose_bone, raw_keyframe).copy() 404 anm_data_raw[bone_name]['ROT_OUT'][time] = _convert_quat(pose_bone, tangent_out ).copy() 405 anm_data_raw[bone_name]['ROT_IN' ][time] = _convert_quat(pose_bone, tangent_in ).copy() 406 # - - - Alternative Method - - - 407 #raw_keyframe = mathutils.Quaternion(raw_keyframe) 408 #tangent_in = mathutils.Quaternion(tangent_in) 409 #tangent_out = mathutils.Quaternion(tangent_out) 410 #converted_quat = _convert_quat(pose_bone, raw_keyframe).copy() 411 #anm_data_raw[bone_name]['ROT' ][time] = converted_quat.copy() 412 #anm_data_raw[bone_name]['ROT_IN' ][time] = converted_quat.inverted() @ _convert_quat(pose_bone, raw_keyframe @ tangent_in ) 413 #anm_data_raw[bone_name]['ROT_OUT'][time] = converted_quat.inverted() @ _convert_quat(pose_bone, raw_keyframe @ tangent_out ) 414 415 return anm_data_raw 416 417 def write_animation(self, context, file): 418 ob = context.active_object 419 arm = ob.data 420 pose = ob.pose 421 fps = context.scene.render.fps 422 423 424 bone_parents = {} 425 if self.bone_parent_from == 'ARMATURE_PROPERTY': 426 for i in range(9999): 427 name = "BoneData:" + str(i) 428 if name not in arm: 429 continue 430 elems = arm[name].split(",") 431 if len(elems) != 5: 432 continue 433 if elems[0] in arm.bones: 434 if elems[2] in arm.bones: 435 bone_parents[elems[0]] = arm.bones[elems[2]] 436 else: 437 bone_parents[elems[0]] = None 438 for bone in arm.bones: 439 if bone.name in bone_parents: 440 continue 441 bone_parents[bone.name] = bone.parent 442 else: 443 for bone in arm.bones: 444 bone_parents[bone.name] = bone.parent 445 446 copied_action = None 447 if ob.animation_data and ob.animation_data.action: 448 copied_action = ob.animation_data.action.copy() 449 copied_action.name = ob.animation_data.action.name + "__anm_export" 450 fcurves = copied_action.fcurves 451 keyed_bones = {'location': [], 'rotation_quaternion': [], 'rotation_euler': []} 452 for bone in arm.bones: 453 rna_data_stub = 'pose.bones["{bone_name}"]'.format(bone_name=bone.name) 454 for prop, axes in [('location', 3), ('rotation_quaternion', 4), ('rotation_euler', 3)]: 455 found_fcurve = False 456 for axis_index in range(0, axes): 457 if fcurves.find(rna_data_stub + '.' + prop, index=axis_index): 458 found_fcurve = True 459 break 460 if found_fcurve: 461 keyed_bones[prop].append(bone.name) 462 463 elif self.export_method == 'KEYED' or self.is_remove_unkeyed_bone: 464 raise common.CM3D2ExportException( 465 "Active armature has no animation data / action. Please use \"{method}\" with \"{option}\" disabled, or bake keyframes before exporting.".format( 466 method = "Bake All Frames", 467 option = "Remove Unkeyed Bones" 468 ) 469 ) 470 471 472 473 474 def is_japanese(string): 475 for ch in string: 476 name = unicodedata.name(ch) 477 if 'CJK UNIFIED' in name or 'HIRAGANA' in name or 'KATAKANA' in name: 478 return True 479 return False 480 bones = [] 481 already_bone_names = [] 482 bones_queue = arm.bones[:] 483 while len(bones_queue): 484 bone = bones_queue.pop(0) 485 486 if not bone_parents[bone.name]: 487 already_bone_names.append(bone.name) 488 if self.is_remove_serial_number_bone: 489 if common.has_serial_number(bone.name): 490 continue 491 if self.is_remove_japanese_bone: 492 if is_japanese(bone.name): 493 continue 494 if self.is_remove_alone_bone and len(bone.children) == 0: 495 continue 496 if self.is_remove_unkeyed_bone: 497 is_keyed = False 498 for prop in keyed_bones: 499 if bone.name in keyed_bones[prop]: 500 is_keyed = True 501 break 502 if not is_keyed: 503 continue 504 bones.append(bone) 505 continue 506 elif bone_parents[bone.name].name in already_bone_names: 507 already_bone_names.append(bone.name) 508 if self.is_remove_serial_number_bone: 509 if common.has_serial_number(bone.name): 510 continue 511 if self.is_remove_japanese_bone: 512 if is_japanese(bone.name): 513 continue 514 if self.is_remove_ik_bone: 515 bone_name_low = bone.name.lower() 516 if '_ik_' in bone_name_low or bone_name_low.endswith('_nub') or bone.name.endswith('Nub'): 517 continue 518 if self.is_remove_unkeyed_bone: 519 is_keyed = False 520 for prop in keyed_bones: 521 if bone.name in keyed_bones[prop]: 522 is_keyed = True 523 break 524 if not is_keyed: 525 continue 526 bones.append(bone) 527 continue 528 529 bones_queue.append(bone) 530 531 if self.export_method == 'ALL': 532 anm_data_raw = self.get_animation_frames(context, fps, pose, bones, bone_parents) 533 elif self.export_method == 'KEYED': 534 anm_data_raw = self.get_animation_keyframes(context, fps, pose, keyed_bones, fcurves) 535 536 if copied_action: 537 context.blend_data.actions.remove(copied_action, do_unlink=True, do_id_user=True, do_ui_user=True) 538 539 anm_data = {} 540 for bone_name, channels in anm_data_raw.items(): 541 anm_data[bone_name] = {100: {}, 101: {}, 102: {}, 103: {}, 104: {}, 105: {}, 106: {}} 542 if channels.get('LOC'): 543 has_tangents = bool(channels.get('LOC_IN') and channels.get('LOC_OUT')) 544 for time, loc in channels["LOC"].items(): 545 tangent_in = channels['LOC_IN' ][time] if has_tangents else mathutils.Vector() 546 tangent_out = channels['LOC_OUT'][time] if has_tangents else mathutils.Vector() 547 anm_data[bone_name][104][time] = (loc.x, tangent_in.x, tangent_out.x) 548 anm_data[bone_name][105][time] = (loc.y, tangent_in.y, tangent_out.y) 549 anm_data[bone_name][106][time] = (loc.z, tangent_in.z, tangent_out.z) 550 if channels.get('ROT'): 551 has_tangents = bool(channels.get('ROT_IN') and channels.get('ROT_OUT')) 552 for time, rot in channels["ROT"].items(): 553 tangent_in = channels['ROT_IN' ][time] if has_tangents else mathutils.Quaternion((0,0,0,0)) 554 tangent_out = channels['ROT_OUT'][time] if has_tangents else mathutils.Quaternion((0,0,0,0)) 555 anm_data[bone_name][100][time] = (rot.x, tangent_in.x, tangent_out.x) 556 anm_data[bone_name][101][time] = (rot.y, tangent_in.y, tangent_out.y) 557 anm_data[bone_name][102][time] = (rot.z, tangent_in.z, tangent_out.z) 558 anm_data[bone_name][103][time] = (rot.w, tangent_in.w, tangent_out.w) 559 560 time_step = 1 / fps * (1.0 / self.time_scale) 561 562 563 ''' Write data to the file ''' 564 565 common.write_str(file, 'CM3D2_ANIM') 566 file.write(struct.pack('<i', self.version)) 567 568 for bone in bones: 569 if not anm_data.get(bone.name): 570 continue 571 572 file.write(struct.pack('<?', True)) 573 574 bone_names = [bone.name] 575 current_bone = bone 576 while bone_parents[current_bone.name]: 577 bone_names.append(bone_parents[current_bone.name].name) 578 current_bone = bone_parents[current_bone.name] 579 580 bone_names.reverse() 581 common.write_str(file, "/".join(bone_names)) 582 583 for channel_id, keyframes in sorted(anm_data[bone.name].items(), key=lambda x: x[0]): 584 file.write(struct.pack('<B', channel_id)) 585 file.write(struct.pack('<i', len(keyframes))) 586 587 keyframes_list = sorted(keyframes.items(), key=lambda x: x[0]) 588 for i in range(len(keyframes_list)): 589 x = keyframes_list[i][0] 590 y, dydx_in, dydx_out = keyframes_list[i][1] 591 592 if len(keyframes_list) <= 1: 593 file.write(struct.pack('<f', x)) 594 file.write(struct.pack('<f', y)) 595 file.write(struct.pack('<2f', 0.0, 0.0)) 596 continue 597 598 file.write(struct.pack('<f', x)) 599 file.write(struct.pack('<f', y)) 600 601 if self.is_smooth_handle and self.export_method == 'ALL': 602 if i == 0: 603 prev_x = x - (keyframes_list[i + 1][0] - x) 604 prev_y = y - (keyframes_list[i + 1][1][0] - y) 605 next_x = keyframes_list[i + 1][0] 606 next_y = keyframes_list[i + 1][1][0] 607 elif i == len(keyframes_list) - 1: 608 prev_x = keyframes_list[i - 1][0] 609 prev_y = keyframes_list[i - 1][1][0] 610 next_x = x + (x - keyframes_list[i - 1][0]) 611 next_y = y + (y - keyframes_list[i - 1][1][0]) 612 else: 613 prev_x = keyframes_list[i - 1][0] 614 prev_y = keyframes_list[i - 1][1][0] 615 next_x = keyframes_list[i + 1][0] 616 next_y = keyframes_list[i + 1][1][0] 617 618 prev_rad = (prev_y - y) / (prev_x - x) 619 next_rad = (next_y - y) / (next_x - x) 620 join_rad = (prev_rad + next_rad) / 2 621 622 tan_in = join_rad if x - prev_x <= time_step * 1.5 else prev_rad 623 tan_out = join_rad if next_x - x <= time_step * 1.5 else next_rad 624 625 file.write(struct.pack('<2f', tan_in, tan_out)) 626 #file.write(struct.pack('<2f', join_rad, join_rad)) 627 #file.write(struct.pack('<2f', prev_rad, next_rad)) 628 else: 629 file.write(struct.pack('<2f', dydx_in, dydx_out)) 630 631 file.write(struct.pack('<?', False)) 632 633 def write_animation_from_text(self, context, file): 634 txt = context.blend_data.texts.get("AnmData") 635 if not txt: 636 raise common.CM3D2ExportException("There is no 'AnmData' text file.") 637 638 import json 639 anm_data = json.loads(txt.as_string()) 640 641 common.write_str(file, 'CM3D2_ANIM') 642 file.write(struct.pack('<i', self.version)) 643 644 for base_bone_name, bone_data in anm_data.items(): 645 path = bone_data['path'] 646 file.write(struct.pack('<?', True)) 647 common.write_str(file, path) 648 649 for channel_id, channel in bone_data['channels'].items(): 650 file.write(struct.pack('<B', int(channel_id))) 651 channel_data_count = len(channel) 652 file.write(struct.pack('<i', channel_data_count)) 653 for channel_data in channel: 654 frame = channel_data['frame'] 655 data = ( channel_data['f0'], channel_data['f1'], channel_data['f2'] ) 656 file.write(struct.pack('<f' , frame)) 657 file.write(struct.pack('<3f', *data )) 658 659 file.write(struct.pack('<?', False)) 660 661 662 663 664 665# メニューに登録する関数 666def menu_func(self, context): 667 self.layout.operator(CNV_OT_export_cm3d2_anm.bl_idname, icon_value=common.kiss_icon())
@compat.BlRegister()
class
CNV_OT_export_cm3d2_anm17@compat.BlRegister() 18class CNV_OT_export_cm3d2_anm(bpy.types.Operator): 19 bl_idname = 'export_anim.export_cm3d2_anm' 20 bl_label = "CM3D2モーション (.anm)" 21 bl_description = "カスタムメイド3D2のanmファイルを保存します" 22 bl_options = {'REGISTER'} 23 24 filepath = bpy.props.StringProperty(subtype='FILE_PATH') 25 filename_ext = ".anm" 26 filter_glob = bpy.props.StringProperty(default="*.anm", options={'HIDDEN'}) 27 28 scale = bpy.props.FloatProperty(name="倍率", default=0.2, min=0.1, max=100, soft_min=0.1, soft_max=100, step=100, precision=1, description="エクスポート時のメッシュ等の拡大率です") 29 is_backup = bpy.props.BoolProperty(name="ファイルをバックアップ", default=True, description="ファイルに上書きする場合にバックアップファイルを複製します") 30 version = bpy.props.IntProperty(name="ファイルバージョン", default=1000, min=1000, max=1111, soft_min=1000, soft_max=1111, step=1) 31 32 #is_anm_data_text = bpy.props.BoolProperty(name="From Anm Text", default=False, description="Input data from JSON file") 33 items = [ 34 ('ALL' , "Bake All Frames" , "Export every frame as a keyframe (legacy behavior, large file sizes)", 'SEQUENCE' , 1), 35 ('KEYED', "Only Export Keyframes", "Only export keyframes and their tangents (for more advance users)" , 'KEYINGSET', 2), 36 ('TEXT' , "From Anm Text JSON" , "Export data from the JSON in the 'AnmData' text file" , 'TEXT' , 3) 37 ] 38 export_method = bpy.props.EnumProperty(items=items, name="Export Method", default='ALL') 39 40 41 frame_start = bpy.props.IntProperty(name="開始フレーム", default=0, min=0, max=99999, soft_min=0, soft_max=99999, step=1) 42 frame_end = bpy.props.IntProperty(name="最終フレーム", default=0, min=0, max=99999, soft_min=0, soft_max=99999, step=1) 43 key_frame_count = bpy.props.IntProperty(name="キーフレーム数", default=1, min=1, max=99999, soft_min=1, soft_max=99999, step=1) 44 time_scale = bpy.props.FloatProperty(name="再生速度", default=1.0, min=0.1, max=10.0, soft_min=0.1, soft_max=10.0, step=10, precision=1) 45 is_keyframe_clean = bpy.props.BoolProperty(name="同じ変形のキーフレームを掃除", default=True) 46 is_visual_transform = bpy.props.BoolProperty(name="Use Visual Transforms", default=True ) 47 is_smooth_handle = bpy.props.BoolProperty(name="キーフレーム間の変形をスムーズに", default=True) 48 49 items = [ 50 ('ARMATURE', "アーマチュア", "", 'OUTLINER_OB_ARMATURE', 1), 51 ('ARMATURE_PROPERTY', "アーマチュア内プロパティ", "", 'ARMATURE_DATA', 2), 52 ] 53 bone_parent_from = bpy.props.EnumProperty(items=items, name="ボーン親情報の参照先", default='ARMATURE_PROPERTY') 54 55 is_remove_unkeyed_bone = bpy.props.BoolProperty(name="Remove Unkeyed Bones", default=False) 56 is_remove_alone_bone = bpy.props.BoolProperty(name="親も子も存在しない", default=True) 57 is_remove_ik_bone = bpy.props.BoolProperty(name="名前がIK/Nubっぽい", default=True) 58 is_remove_serial_number_bone = bpy.props.BoolProperty(name="名前が連番付き", default=True) 59 is_remove_japanese_bone = bpy.props.BoolProperty(name="名前に日本語が含まれる", default=True) 60 61 @classmethod 62 def poll(cls, context): 63 ob = context.active_object 64 if ob and ob.type == 'ARMATURE': 65 return True 66 return False 67 68 def invoke(self, context, event): 69 prefs = common.preferences() 70 71 ob = context.active_object 72 arm = ob.data 73 action_name = None 74 if ob.animation_data and ob.animation_data.action: 75 action_name = common.remove_serial_number(ob.animation_data.action.name) 76 77 if prefs.anm_default_path: 78 self.filepath = common.default_cm3d2_dir(prefs.anm_default_path, action_name, "anm") 79 else: 80 self.filepath = common.default_cm3d2_dir(prefs.anm_export_path, action_name, "anm") 81 self.frame_start = context.scene.frame_start 82 self.frame_end = context.scene.frame_end 83 self.scale = 1.0 / prefs.scale 84 self.is_backup = bool(prefs.backup_ext) 85 self.key_frame_count = (context.scene.frame_end - context.scene.frame_start) + 1 86 87 if "BoneData:0" in arm: 88 self.bone_parent_from = 'ARMATURE_PROPERTY' 89 else: 90 self.bone_parent_from = 'ARMATURE' 91 92 context.window_manager.fileselect_add(self) 93 return {'RUNNING_MODAL'} 94 95 def draw(self, context): 96 self.layout.prop(self, 'scale') 97 98 box = self.layout.box() 99 box.prop(self, 'is_backup', icon='FILE_BACKUP') 100 box.prop(self, 'version') 101 102 #self.layout.prop(self, 'is_anm_data_text', icon='TEXT') 103 box = self.layout.box() 104 box.label(text="Export Method") 105 box.prop(self, 'export_method', expand=True) 106 107 box = self.layout.box() 108 box.enabled = not (self.export_method == 'TEXT') 109 box.prop(self, 'time_scale') 110 sub_box = box.box() 111 sub_box.enabled = (self.export_method == 'ALL') 112 row = sub_box.row() 113 row.prop(self, 'frame_start') 114 row.prop(self, 'frame_end') 115 sub_box.prop(self, 'key_frame_count') 116 sub_box.prop(self, 'is_keyframe_clean', icon='DISCLOSURE_TRI_DOWN') 117 sub_box.prop(self, 'is_smooth_handle', icon='SMOOTHCURVE') 118 119 sub_box = box.box() 120 sub_box.label(text="ボーン親情報の参照先", icon='FILE_PARENT') 121 sub_box.prop(self, 'bone_parent_from', icon='FILE_PARENT', expand=True) 122 123 sub_box = box.box() 124 sub_box.label(text="除外するボーン", icon='X') 125 column = sub_box.column(align=True) 126 column.prop(self, 'is_remove_unkeyed_bone' , icon='KEY_DEHLT' ) 127 column.prop(self, 'is_remove_alone_bone' , icon='UNLINKED' ) 128 column.prop(self, 'is_remove_ik_bone' , icon='CONSTRAINT_BONE' ) 129 column.prop(self, 'is_remove_serial_number_bone', icon='SEQUENCE' ) 130 column.prop(self, 'is_remove_japanese_bone' , icon=compat.icon('HOLDOUT_ON')) 131 132 def execute(self, context): 133 common.preferences().anm_export_path = self.filepath 134 135 try: 136 file = common.open_temporary(self.filepath, 'wb', is_backup=self.is_backup) 137 except: 138 self.report(type={'ERROR'}, message=f_tip_("ファイルを開くのに失敗しました、アクセス不可かファイルが存在しません。file={}", self.filepath)) 139 return {'CANCELLED'} 140 141 try: 142 with file: 143 if self.export_method == 'TEXT': 144 self.write_animation_from_text(context, file) 145 else: 146 self.write_animation(context, file) 147 except common.CM3D2ExportException as e: 148 self.report(type={'ERROR'}, message=str(e)) 149 return {'CANCELLED'} 150 151 return {'FINISHED'} 152 153 def get_animation_frames(self, context, fps, pose, bones, bone_parents): 154 anm_data_raw = {} 155 class KeyFrame: 156 def __init__(self, time, value, slope=None): 157 self.time = time 158 self.value = value 159 if slope: 160 self.slope = slope 161 elif type(value) == mathutils.Vector: 162 self.slope = mathutils.Vector.Fill(len(value)) 163 elif type(value) == mathutils.Quaternion: 164 self.slope = mathutils.Quaternion((0,0,0,0)) 165 else: 166 self.slope = 0 167 168 same_locs = {} 169 same_rots = {} 170 pre_rots = {} 171 for key_frame_index in range(self.key_frame_count): 172 if self.key_frame_count == 1: 173 frame = 0.0 174 else: 175 frame = (self.frame_end - self.frame_start) / (self.key_frame_count - 1) * key_frame_index + self.frame_start 176 context.scene.frame_set(frame=int(frame), subframe=frame - int(frame)) 177 if compat.IS_LEGACY: 178 context.scene.update() 179 else: 180 layer = context.view_layer 181 layer.update() 182 183 time = frame / fps * (1.0 / self.time_scale) 184 185 for bone in bones: 186 if bone.name not in anm_data_raw: 187 anm_data_raw[bone.name] = {"LOC": {}, "ROT": {}} 188 same_locs[bone.name] = [] 189 same_rots[bone.name] = [] 190 191 pose_bone = pose.bones[bone.name] 192 pose_mat = pose_bone.matrix.copy() #ob.convert_space(pose_bone=pose_bone, matrix=pose_bone.matrix, from_space='POSE', to_space='WORLD') 193 parent = bone_parents[bone.name] 194 if parent: 195 pose_mat = compat.convert_bl_to_cm_bone_rotation(pose_mat) 196 pose_mat = compat.mul(pose.bones[parent.name].matrix.inverted(), pose_mat) 197 pose_mat = compat.convert_bl_to_cm_bone_space(pose_mat) 198 else: 199 pose_mat = compat.convert_bl_to_cm_bone_rotation(pose_mat) 200 pose_mat = compat.convert_bl_to_cm_space(pose_mat) 201 202 loc = pose_mat.to_translation() * self.scale 203 rot = pose_mat.to_quaternion() 204 205 # This fixes rotations that jump to alternate representations. 206 if bone.name in pre_rots: 207 if 5.0 < pre_rots[bone.name].rotation_difference(rot).angle: 208 rot.w, rot.x, rot.y, rot.z = -rot.w, -rot.x, -rot.y, -rot.z 209 pre_rots[bone.name] = rot.copy() 210 211 #if parent: 212 # #loc.x, loc.y, loc.z = -loc.y, -loc.x, loc.z 213 # loc = compat.convert_bl_to_cm_bone_space(loc) 214 # 215 # # quat.w, quat.x, quat.y, quat.z = quat.w, -quat.z, quat.x, -quat.y 216 # #rot.w, rot.x, rot.y, rot.z = rot.w, rot.y, rot.x, -rot.z 217 # rot.w, rot.x, rot.y, rot.z = rot.w, rot.y, -rot.z, -rot.x 218 # 219 #else: 220 # loc.x, loc.y, loc.z = -loc.x, loc.z, -loc.y 221 # 222 # fix_quat = mathutils.Euler((0, 0, math.radians(-90)), 'XYZ').to_quaternion() 223 # fix_quat2 = mathutils.Euler((math.radians(-90), 0, 0), 'XYZ').to_quaternion() 224 # rot = compat.mul3(rot, fix_quat, fix_quat2) 225 # 226 # rot.w, rot.x, rot.y, rot.z = -rot.y, -rot.z, -rot.x, rot.w 227 228 if not self.is_keyframe_clean or key_frame_index == 0 or key_frame_index == self.key_frame_count - 1: 229 anm_data_raw[bone.name]["LOC"][time] = loc.copy() 230 anm_data_raw[bone.name]["ROT"][time] = rot.copy() 231 232 if self.is_keyframe_clean: 233 same_locs[bone.name].append(KeyFrame(time, loc.copy())) 234 same_rots[bone.name].append(KeyFrame(time, rot.copy())) 235 else: 236 def is_mismatch(a, b): 237 return 0.000001 < abs(a - b) 238 239 a = same_locs[bone.name][-1].value - loc 240 b = same_locs[bone.name][-1].slope 241 if is_mismatch(a.x, b.x) or is_mismatch(a.y, b.y) or is_mismatch(a.z, b.z): 242 if 2 <= len(same_locs[bone.name]): 243 anm_data_raw[bone.name]["LOC"][same_locs[bone.name][-1].time] = same_locs[bone.name][-1].value.copy() 244 anm_data_raw[bone.name]["LOC"][time] = loc.copy() 245 same_locs[bone.name] = [KeyFrame(time, loc.copy(), a.copy())] # update last position and slope 246 else: 247 same_locs[bone.name].append(KeyFrame(time, loc.copy(), b.copy())) # update last position, but not last slope 248 249 a = same_rots[bone.name][-1].value - rot 250 b = same_rots[bone.name][-1].slope 251 if is_mismatch(a.w, b.w) or is_mismatch(a.x, b.x) or is_mismatch(a.y, b.y) or is_mismatch(a.z, b.z): 252 if 2 <= len(same_rots[bone.name]): 253 anm_data_raw[bone.name]["ROT"][same_rots[bone.name][-1].time] = same_rots[bone.name][-1].value.copy() 254 anm_data_raw[bone.name]["ROT"][time] = rot.copy() 255 same_rots[bone.name] = [KeyFrame(time, rot.copy(), a.copy())] # update last position and slope 256 else: 257 same_rots[bone.name].append(KeyFrame(time, rot.copy(), b.copy())) # update last position, but not last slope 258 259 return anm_data_raw 260 261 def get_animation_keyframes(self, context, fps, pose, keyed_bones, fcurves): 262 anm_data_raw = {} 263 264 prop_sizes = {'location': 3, 'rotation_quaternion': 4, 'rotation_euler': 3} 265 266 #class KeyFrame: 267 # def __init__(self, time, value): 268 # self.time = time 269 # self.value = value 270 #same_locs = {} 271 #same_rots = {} 272 #pre_rots = {} 273 274 def _convert_loc(pose_bone, loc): 275 loc = mathutils.Vector(loc) 276 loc = compat.mul(pose_bone.bone.matrix_local, loc) 277 if pose_bone.parent: 278 loc = compat.mul(pose_bone.parent.bone.matrix_local.inverted(), loc) 279 loc = compat.convert_bl_to_cm_bone_space(loc) 280 else: 281 loc = compat.convert_bl_to_cm_space(loc) 282 return loc * self.scale 283 """ 284 def _convert_quat(pose_bone, quat): 285 #quat = mathutils.Quaternion(quat) 286 #'''Can't use matrix transforms here as they would mess up interpolation.''' 287 #quat = compat.mul(pose_bone.bone.matrix_local.to_quaternion(), quat) 288 289 quat_mat = mathutils.Quaternion(quat).to_matrix().to_4x4() 290 quat_mat = compat.mul(pose_bone.bone.matrix_local, quat_mat) 291 #quat = quat_mat.to_quaternion() 292 if pose_bone.parent: 293 ## inverse of quat.w, quat.x, quat.y, quat.z = quat.w, -quat.z, quat.x, -quat.y 294 #quat.w, quat.x, quat.y, quat.z = quat.w, quat.y, -quat.z, -quat.x 295 #quat = compat.mul(pose_bone.parent.bone.matrix_local.to_quaternion().inverted(), quat) 296 ##quat = compat.mul(pose_bone.parent.bone.matrix_local.inverted().to_quaternion(), quat)\ 297 quat_mat = compat.convert_bl_to_cm_bone_rotation(quat_mat) 298 quat_mat = compat.mul(pose_bone.parent.bone.matrix_local.inverted(), quat_mat) 299 quat_mat = compat.convert_bl_to_cm_bone_space(quat_mat) 300 quat = quat_mat.to_quaternion() 301 else: 302 #fix_quat = mathutils.Euler((0, 0, math.radians(-90)), 'XYZ').to_quaternion() 303 #fix_quat2 = mathutils.Euler((math.radians(-90), 0, 0), 'XYZ').to_quaternion() 304 #quat = compat.mul3(quat, fix_quat, fix_quat2) 305 # 306 #quat.w, quat.x, quat.y, quat.z = -quat.y, -quat.z, -quat.x, quat.w 307 308 #quat.w, quat.x, quat.y, quat.z = quat.w, quat.y, -quat.z, -quat.x 309 #quat = compat.mul(mathutils.Matrix.Rotation(math.radians(90.0), 4, 'Z').to_quaternion(), quat) 310 311 quat_mat = compat.convert_bl_to_cm_bone_rotation(quat_mat) 312 quat_mat = compat.convert_bl_to_cm_space(quat_mat) 313 quat = quat_mat.to_quaternion() 314 return quat 315 """ 316 317 def _convert_quat(pose_bone, quat): 318 bone_quat = pose_bone.bone.matrix.to_quaternion() 319 quat = mathutils.Quaternion(quat) 320 321 '''Can't use matrix transforms here as they would mess up interpolation.''' 322 quat = compat.mul(bone_quat, quat) 323 324 if pose_bone.bone.parent: 325 #quat.w, quat.x, quat.y, quat.z = quat.w, -quat.z, quat.x, -quat.y 326 quat.w, quat.y, quat.x, quat.z = quat.w, -quat.z, quat.y, -quat.x 327 else: 328 quat = compat.mul(mathutils.Matrix.Rotation(math.radians(90.0), 4, 'Z').to_quaternion(), quat) 329 quat.w, quat.y, quat.x, quat.z = quat.w, -quat.z, quat.y, -quat.x 330 return quat 331 332 for prop, prop_keyed_bones in keyed_bones.items(): 333 #self.report(type={'INFO'}, message=f_tip_("{prop} {list}", prop=prop, list=prop_keyed_bones)) 334 for bone_name in prop_keyed_bones: 335 if bone_name not in anm_data_raw: 336 anm_data_raw[bone_name] = {} 337 #same_locs[bone_name] = [] 338 #same_rots[bone_name] = [] 339 340 pose_bone = pose.bones[bone_name] 341 rna_data_path = 'pose.bones["{bone_name}"].{property}'.format(bone_name=bone_name, property=prop) 342 prop_fcurves = [ fcurves.find(rna_data_path, index=axis_index) for axis_index in range(prop_sizes[prop]) ] 343 344 # Create missing fcurves, and make existing fcurves CM3D2 compatible. 345 for axis_index, fcurve in enumerate(prop_fcurves): 346 if not fcurve: 347 fcurve = fcurves.new(rna_data_path, index=axis_index, action_group=pose_bone.name) 348 prop_fcurves[axis_index] = fcurve 349 self.report(type={'WARNING'}, message=f_tip_("Creating missing FCurve for {path}[{index}]", path=rna_data_path, index=axis_index)) 350 else: 351 override = context.copy() 352 override['active_editable_fcurve'] = fcurve 353 bpy.ops.fcurve.convert_to_cm3d2_interpolation(override, only_selected=False, keep_reports=True) 354 for kwargs in misc_DOPESHEET_MT_editor_menus.REPORTS: 355 self.report(**kwargs) 356 misc_DOPESHEET_MT_editor_menus.REPORTS.clear() 357 358 359 # Create a list by frame, indicating wether or not there is a keyframe at that time for each fcurve 360 is_keyframes = {} 361 for fcurve in prop_fcurves: 362 for keyframe in fcurve.keyframe_points: 363 frame = keyframe.co[0] 364 if frame not in is_keyframes: 365 is_keyframes[frame] = [False] * prop_sizes[prop] 366 is_keyframes[frame][fcurve.array_index] = True 367 368 # Make sure that no keyframe times are missing any components 369 for frame, is_axes in is_keyframes.items(): 370 for axis_index, is_axis in enumerate(is_axes): 371 if not is_axis: 372 fcurve = prop_fcurves[axis_index] 373 keyframe = fcurve.keyframe_points.insert( 374 frame = frame , 375 value = fcurve.evaluate(frame), 376 options = {'NEEDED', 'FAST'} 377 ) 378 self.report(type={'WARNING'}, message=f_tip_("Creating missing keyframe @ frame {frame} for {path}[{index}]", path=rna_data_path, index=axis_index, frame=frame)) 379 380 for fcurve in prop_fcurves: 381 fcurve.update() 382 383 for keyframe_index, frame in enumerate(is_keyframes.keys()): 384 time = frame / fps * (1.0 / self.time_scale) 385 386 _kf = lambda fcurve: fcurve.keyframe_points[keyframe_index] 387 raw_keyframe = [ _kf(fc).co[1] for fc in prop_fcurves ] 388 tangent_in = [ ( _kf(fc).handle_left [1] - _kf(fc).co[1] ) / ( _kf(fc).handle_left [0] - _kf(fc).co[0] ) * fps for fc in prop_fcurves ] 389 tangent_out = [ ( _kf(fc).handle_right[1] - _kf(fc).co[1] ) / ( _kf(fc).handle_right[0] - _kf(fc).co[0] ) * fps for fc in prop_fcurves ] 390 391 if prop == 'location': 392 if 'LOC' not in anm_data_raw[bone_name]: 393 anm_data_raw[bone_name]['LOC' ] = {} 394 anm_data_raw[bone_name]['LOC_IN' ] = {} 395 anm_data_raw[bone_name]['LOC_OUT'] = {} 396 anm_data_raw[bone_name]['LOC' ][time] = _convert_loc(pose_bone, raw_keyframe).copy() 397 anm_data_raw[bone_name]['LOC_IN' ][time] = _convert_loc(pose_bone, tangent_in ).copy() 398 anm_data_raw[bone_name]['LOC_OUT'][time] = _convert_loc(pose_bone, tangent_out ).copy() 399 elif prop == 'rotation_quaternion': 400 if 'ROT' not in anm_data_raw[bone_name]: 401 anm_data_raw[bone_name]['ROT' ] = {} 402 anm_data_raw[bone_name]['ROT_IN' ] = {} 403 anm_data_raw[bone_name]['ROT_OUT'] = {} 404 anm_data_raw[bone_name]['ROT' ][time] = _convert_quat(pose_bone, raw_keyframe).copy() 405 anm_data_raw[bone_name]['ROT_OUT'][time] = _convert_quat(pose_bone, tangent_out ).copy() 406 anm_data_raw[bone_name]['ROT_IN' ][time] = _convert_quat(pose_bone, tangent_in ).copy() 407 # - - - Alternative Method - - - 408 #raw_keyframe = mathutils.Quaternion(raw_keyframe) 409 #tangent_in = mathutils.Quaternion(tangent_in) 410 #tangent_out = mathutils.Quaternion(tangent_out) 411 #converted_quat = _convert_quat(pose_bone, raw_keyframe).copy() 412 #anm_data_raw[bone_name]['ROT' ][time] = converted_quat.copy() 413 #anm_data_raw[bone_name]['ROT_IN' ][time] = converted_quat.inverted() @ _convert_quat(pose_bone, raw_keyframe @ tangent_in ) 414 #anm_data_raw[bone_name]['ROT_OUT'][time] = converted_quat.inverted() @ _convert_quat(pose_bone, raw_keyframe @ tangent_out ) 415 416 return anm_data_raw 417 418 def write_animation(self, context, file): 419 ob = context.active_object 420 arm = ob.data 421 pose = ob.pose 422 fps = context.scene.render.fps 423 424 425 bone_parents = {} 426 if self.bone_parent_from == 'ARMATURE_PROPERTY': 427 for i in range(9999): 428 name = "BoneData:" + str(i) 429 if name not in arm: 430 continue 431 elems = arm[name].split(",") 432 if len(elems) != 5: 433 continue 434 if elems[0] in arm.bones: 435 if elems[2] in arm.bones: 436 bone_parents[elems[0]] = arm.bones[elems[2]] 437 else: 438 bone_parents[elems[0]] = None 439 for bone in arm.bones: 440 if bone.name in bone_parents: 441 continue 442 bone_parents[bone.name] = bone.parent 443 else: 444 for bone in arm.bones: 445 bone_parents[bone.name] = bone.parent 446 447 copied_action = None 448 if ob.animation_data and ob.animation_data.action: 449 copied_action = ob.animation_data.action.copy() 450 copied_action.name = ob.animation_data.action.name + "__anm_export" 451 fcurves = copied_action.fcurves 452 keyed_bones = {'location': [], 'rotation_quaternion': [], 'rotation_euler': []} 453 for bone in arm.bones: 454 rna_data_stub = 'pose.bones["{bone_name}"]'.format(bone_name=bone.name) 455 for prop, axes in [('location', 3), ('rotation_quaternion', 4), ('rotation_euler', 3)]: 456 found_fcurve = False 457 for axis_index in range(0, axes): 458 if fcurves.find(rna_data_stub + '.' + prop, index=axis_index): 459 found_fcurve = True 460 break 461 if found_fcurve: 462 keyed_bones[prop].append(bone.name) 463 464 elif self.export_method == 'KEYED' or self.is_remove_unkeyed_bone: 465 raise common.CM3D2ExportException( 466 "Active armature has no animation data / action. Please use \"{method}\" with \"{option}\" disabled, or bake keyframes before exporting.".format( 467 method = "Bake All Frames", 468 option = "Remove Unkeyed Bones" 469 ) 470 ) 471 472 473 474 475 def is_japanese(string): 476 for ch in string: 477 name = unicodedata.name(ch) 478 if 'CJK UNIFIED' in name or 'HIRAGANA' in name or 'KATAKANA' in name: 479 return True 480 return False 481 bones = [] 482 already_bone_names = [] 483 bones_queue = arm.bones[:] 484 while len(bones_queue): 485 bone = bones_queue.pop(0) 486 487 if not bone_parents[bone.name]: 488 already_bone_names.append(bone.name) 489 if self.is_remove_serial_number_bone: 490 if common.has_serial_number(bone.name): 491 continue 492 if self.is_remove_japanese_bone: 493 if is_japanese(bone.name): 494 continue 495 if self.is_remove_alone_bone and len(bone.children) == 0: 496 continue 497 if self.is_remove_unkeyed_bone: 498 is_keyed = False 499 for prop in keyed_bones: 500 if bone.name in keyed_bones[prop]: 501 is_keyed = True 502 break 503 if not is_keyed: 504 continue 505 bones.append(bone) 506 continue 507 elif bone_parents[bone.name].name in already_bone_names: 508 already_bone_names.append(bone.name) 509 if self.is_remove_serial_number_bone: 510 if common.has_serial_number(bone.name): 511 continue 512 if self.is_remove_japanese_bone: 513 if is_japanese(bone.name): 514 continue 515 if self.is_remove_ik_bone: 516 bone_name_low = bone.name.lower() 517 if '_ik_' in bone_name_low or bone_name_low.endswith('_nub') or bone.name.endswith('Nub'): 518 continue 519 if self.is_remove_unkeyed_bone: 520 is_keyed = False 521 for prop in keyed_bones: 522 if bone.name in keyed_bones[prop]: 523 is_keyed = True 524 break 525 if not is_keyed: 526 continue 527 bones.append(bone) 528 continue 529 530 bones_queue.append(bone) 531 532 if self.export_method == 'ALL': 533 anm_data_raw = self.get_animation_frames(context, fps, pose, bones, bone_parents) 534 elif self.export_method == 'KEYED': 535 anm_data_raw = self.get_animation_keyframes(context, fps, pose, keyed_bones, fcurves) 536 537 if copied_action: 538 context.blend_data.actions.remove(copied_action, do_unlink=True, do_id_user=True, do_ui_user=True) 539 540 anm_data = {} 541 for bone_name, channels in anm_data_raw.items(): 542 anm_data[bone_name] = {100: {}, 101: {}, 102: {}, 103: {}, 104: {}, 105: {}, 106: {}} 543 if channels.get('LOC'): 544 has_tangents = bool(channels.get('LOC_IN') and channels.get('LOC_OUT')) 545 for time, loc in channels["LOC"].items(): 546 tangent_in = channels['LOC_IN' ][time] if has_tangents else mathutils.Vector() 547 tangent_out = channels['LOC_OUT'][time] if has_tangents else mathutils.Vector() 548 anm_data[bone_name][104][time] = (loc.x, tangent_in.x, tangent_out.x) 549 anm_data[bone_name][105][time] = (loc.y, tangent_in.y, tangent_out.y) 550 anm_data[bone_name][106][time] = (loc.z, tangent_in.z, tangent_out.z) 551 if channels.get('ROT'): 552 has_tangents = bool(channels.get('ROT_IN') and channels.get('ROT_OUT')) 553 for time, rot in channels["ROT"].items(): 554 tangent_in = channels['ROT_IN' ][time] if has_tangents else mathutils.Quaternion((0,0,0,0)) 555 tangent_out = channels['ROT_OUT'][time] if has_tangents else mathutils.Quaternion((0,0,0,0)) 556 anm_data[bone_name][100][time] = (rot.x, tangent_in.x, tangent_out.x) 557 anm_data[bone_name][101][time] = (rot.y, tangent_in.y, tangent_out.y) 558 anm_data[bone_name][102][time] = (rot.z, tangent_in.z, tangent_out.z) 559 anm_data[bone_name][103][time] = (rot.w, tangent_in.w, tangent_out.w) 560 561 time_step = 1 / fps * (1.0 / self.time_scale) 562 563 564 ''' Write data to the file ''' 565 566 common.write_str(file, 'CM3D2_ANIM') 567 file.write(struct.pack('<i', self.version)) 568 569 for bone in bones: 570 if not anm_data.get(bone.name): 571 continue 572 573 file.write(struct.pack('<?', True)) 574 575 bone_names = [bone.name] 576 current_bone = bone 577 while bone_parents[current_bone.name]: 578 bone_names.append(bone_parents[current_bone.name].name) 579 current_bone = bone_parents[current_bone.name] 580 581 bone_names.reverse() 582 common.write_str(file, "/".join(bone_names)) 583 584 for channel_id, keyframes in sorted(anm_data[bone.name].items(), key=lambda x: x[0]): 585 file.write(struct.pack('<B', channel_id)) 586 file.write(struct.pack('<i', len(keyframes))) 587 588 keyframes_list = sorted(keyframes.items(), key=lambda x: x[0]) 589 for i in range(len(keyframes_list)): 590 x = keyframes_list[i][0] 591 y, dydx_in, dydx_out = keyframes_list[i][1] 592 593 if len(keyframes_list) <= 1: 594 file.write(struct.pack('<f', x)) 595 file.write(struct.pack('<f', y)) 596 file.write(struct.pack('<2f', 0.0, 0.0)) 597 continue 598 599 file.write(struct.pack('<f', x)) 600 file.write(struct.pack('<f', y)) 601 602 if self.is_smooth_handle and self.export_method == 'ALL': 603 if i == 0: 604 prev_x = x - (keyframes_list[i + 1][0] - x) 605 prev_y = y - (keyframes_list[i + 1][1][0] - y) 606 next_x = keyframes_list[i + 1][0] 607 next_y = keyframes_list[i + 1][1][0] 608 elif i == len(keyframes_list) - 1: 609 prev_x = keyframes_list[i - 1][0] 610 prev_y = keyframes_list[i - 1][1][0] 611 next_x = x + (x - keyframes_list[i - 1][0]) 612 next_y = y + (y - keyframes_list[i - 1][1][0]) 613 else: 614 prev_x = keyframes_list[i - 1][0] 615 prev_y = keyframes_list[i - 1][1][0] 616 next_x = keyframes_list[i + 1][0] 617 next_y = keyframes_list[i + 1][1][0] 618 619 prev_rad = (prev_y - y) / (prev_x - x) 620 next_rad = (next_y - y) / (next_x - x) 621 join_rad = (prev_rad + next_rad) / 2 622 623 tan_in = join_rad if x - prev_x <= time_step * 1.5 else prev_rad 624 tan_out = join_rad if next_x - x <= time_step * 1.5 else next_rad 625 626 file.write(struct.pack('<2f', tan_in, tan_out)) 627 #file.write(struct.pack('<2f', join_rad, join_rad)) 628 #file.write(struct.pack('<2f', prev_rad, next_rad)) 629 else: 630 file.write(struct.pack('<2f', dydx_in, dydx_out)) 631 632 file.write(struct.pack('<?', False)) 633 634 def write_animation_from_text(self, context, file): 635 txt = context.blend_data.texts.get("AnmData") 636 if not txt: 637 raise common.CM3D2ExportException("There is no 'AnmData' text file.") 638 639 import json 640 anm_data = json.loads(txt.as_string()) 641 642 common.write_str(file, 'CM3D2_ANIM') 643 file.write(struct.pack('<i', self.version)) 644 645 for base_bone_name, bone_data in anm_data.items(): 646 path = bone_data['path'] 647 file.write(struct.pack('<?', True)) 648 common.write_str(file, path) 649 650 for channel_id, channel in bone_data['channels'].items(): 651 file.write(struct.pack('<B', int(channel_id))) 652 channel_data_count = len(channel) 653 file.write(struct.pack('<i', channel_data_count)) 654 for channel_data in channel: 655 frame = channel_data['frame'] 656 data = ( channel_data['f0'], channel_data['f1'], channel_data['f2'] ) 657 file.write(struct.pack('<f' , frame)) 658 file.write(struct.pack('<3f', *data )) 659 660 file.write(struct.pack('<?', False))
filepath: <_PropertyDeferred, <built-in function StringProperty>, {'subtype': 'FILE_PATH', 'attr': 'filepath'}> =
<_PropertyDeferred, <built-in function StringProperty>, {'subtype': 'FILE_PATH', 'attr': 'filepath'}>
filter_glob: <_PropertyDeferred, <built-in function StringProperty>, {'default': '*.anm', 'options': {'HIDDEN'}, 'attr': 'filter_glob'}> =
<_PropertyDeferred, <built-in function StringProperty>, {'default': '*.anm', 'options': {'HIDDEN'}, 'attr': 'filter_glob'}>
scale: <_PropertyDeferred, <built-in function FloatProperty>, {'name': '倍率', 'default': 0.2, 'min': 0.1, 'max': 100, 'soft_min': 0.1, 'soft_max': 100, 'step': 100, 'precision': 1, 'description': 'エクスポート時のメッシュ等の拡大率です', 'attr': 'scale'}> =
<_PropertyDeferred, <built-in function FloatProperty>, {'name': '倍率', 'default': 0.2, 'min': 0.1, 'max': 100, 'soft_min': 0.1, 'soft_max': 100, 'step': 100, 'precision': 1, 'description': 'エクスポート時のメッシュ等の拡大率です', 'attr': 'scale'}>
is_backup: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'ファイルをバックアップ', 'default': True, 'description': 'ファイルに上書きする場合にバックアップファイルを複製します', 'attr': 'is_backup'}> =
<_PropertyDeferred, <built-in function BoolProperty>, {'name': 'ファイルをバックアップ', 'default': True, 'description': 'ファイルに上書きする場合にバックアップファイルを複製します', 'attr': 'is_backup'}>
version: <_PropertyDeferred, <built-in function IntProperty>, {'name': 'ファイルバージョン', 'default': 1000, 'min': 1000, 'max': 1111, 'soft_min': 1000, 'soft_max': 1111, 'step': 1, 'attr': 'version'}> =
<_PropertyDeferred, <built-in function IntProperty>, {'name': 'ファイルバージョン', 'default': 1000, 'min': 1000, 'max': 1111, 'soft_min': 1000, 'soft_max': 1111, 'step': 1, 'attr': 'version'}>
items =
[('ARMATURE', 'アーマチュア', '', 'OUTLINER_OB_ARMATURE', 1), ('ARMATURE_PROPERTY', 'アーマチュア内プロパティ', '', 'ARMATURE_DATA', 2)]
export_method: <_PropertyDeferred, <built-in function EnumProperty>, {'items': [('ALL', 'Bake All Frames', 'Export every frame as a keyframe (legacy behavior, large file sizes)', 'SEQUENCE', 1), ('KEYED', 'Only Export Keyframes', 'Only export keyframes and their tangents (for more advance users)', 'KEYINGSET', 2), ('TEXT', 'From Anm Text JSON', "Export data from the JSON in the 'AnmData' text file", 'TEXT', 3)], 'name': 'Export Method', 'default': 'ALL', 'attr': 'export_method'}> =
<_PropertyDeferred, <built-in function EnumProperty>, {'items': [('ALL', 'Bake All Frames', 'Export every frame as a keyframe (legacy behavior, large file sizes)', 'SEQUENCE', 1), ('KEYED', 'Only Export Keyframes', 'Only export keyframes and their tangents (for more advance users)', 'KEYINGSET', 2), ('TEXT', 'From Anm Text JSON', "Export data from the JSON in the 'AnmData' text file", 'TEXT', 3)], 'name': 'Export Method', 'default': 'ALL', 'attr': 'export_method'}>
frame_start: <_PropertyDeferred, <built-in function IntProperty>, {'name': '開始フレーム', 'default': 0, 'min': 0, 'max': 99999, 'soft_min': 0, 'soft_max': 99999, 'step': 1, 'attr': 'frame_start'}> =
<_PropertyDeferred, <built-in function IntProperty>, {'name': '開始フレーム', 'default': 0, 'min': 0, 'max': 99999, 'soft_min': 0, 'soft_max': 99999, 'step': 1, 'attr': 'frame_start'}>
frame_end: <_PropertyDeferred, <built-in function IntProperty>, {'name': '最終フレーム', 'default': 0, 'min': 0, 'max': 99999, 'soft_min': 0, 'soft_max': 99999, 'step': 1, 'attr': 'frame_end'}> =
<_PropertyDeferred, <built-in function IntProperty>, {'name': '最終フレーム', 'default': 0, 'min': 0, 'max': 99999, 'soft_min': 0, 'soft_max': 99999, 'step': 1, 'attr': 'frame_end'}>
key_frame_count: <_PropertyDeferred, <built-in function IntProperty>, {'name': 'キーフレーム数', 'default': 1, 'min': 1, 'max': 99999, 'soft_min': 1, 'soft_max': 99999, 'step': 1, 'attr': 'key_frame_count'}> =
<_PropertyDeferred, <built-in function IntProperty>, {'name': 'キーフレーム数', 'default': 1, 'min': 1, 'max': 99999, 'soft_min': 1, 'soft_max': 99999, 'step': 1, 'attr': 'key_frame_count'}>
time_scale: <_PropertyDeferred, <built-in function FloatProperty>, {'name': '再生速度', 'default': 1.0, 'min': 0.1, 'max': 10.0, 'soft_min': 0.1, 'soft_max': 10.0, 'step': 10, 'precision': 1, 'attr': 'time_scale'}> =
<_PropertyDeferred, <built-in function FloatProperty>, {'name': '再生速度', 'default': 1.0, 'min': 0.1, 'max': 10.0, 'soft_min': 0.1, 'soft_max': 10.0, 'step': 10, 'precision': 1, 'attr': 'time_scale'}>
is_keyframe_clean: <_PropertyDeferred, <built-in function BoolProperty>, {'name': '同じ変形のキーフレームを掃除', 'default': True, 'attr': 'is_keyframe_clean'}> =
<_PropertyDeferred, <built-in function BoolProperty>, {'name': '同じ変形のキーフレームを掃除', 'default': True, 'attr': 'is_keyframe_clean'}>
is_visual_transform: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Use Visual Transforms', 'default': True, 'attr': 'is_visual_transform'}> =
<_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Use Visual Transforms', 'default': True, 'attr': 'is_visual_transform'}>
is_smooth_handle: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'キーフレーム間の変形をスムーズに', 'default': True, 'attr': 'is_smooth_handle'}> =
<_PropertyDeferred, <built-in function BoolProperty>, {'name': 'キーフレーム間の変形をスムーズに', 'default': True, 'attr': 'is_smooth_handle'}>
bone_parent_from: <_PropertyDeferred, <built-in function EnumProperty>, {'items': [('ARMATURE', 'アーマチュア', '', 'OUTLINER_OB_ARMATURE', 1), ('ARMATURE_PROPERTY', 'アーマチュア内プロパティ', '', 'ARMATURE_DATA', 2)], 'name': 'ボーン親情報の参照先', 'default': 'ARMATURE_PROPERTY', 'attr': 'bone_parent_from'}> =
<_PropertyDeferred, <built-in function EnumProperty>, {'items': [('ARMATURE', 'アーマチュア', '', 'OUTLINER_OB_ARMATURE', 1), ('ARMATURE_PROPERTY', 'アーマチュア内プロパティ', '', 'ARMATURE_DATA', 2)], 'name': 'ボーン親情報の参照先', 'default': 'ARMATURE_PROPERTY', 'attr': 'bone_parent_from'}>
is_remove_unkeyed_bone: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Remove Unkeyed Bones', 'default': False, 'attr': 'is_remove_unkeyed_bone'}> =
<_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Remove Unkeyed Bones', 'default': False, 'attr': 'is_remove_unkeyed_bone'}>
is_remove_alone_bone: <_PropertyDeferred, <built-in function BoolProperty>, {'name': '親も子も存在しない', 'default': True, 'attr': 'is_remove_alone_bone'}> =
<_PropertyDeferred, <built-in function BoolProperty>, {'name': '親も子も存在しない', 'default': True, 'attr': 'is_remove_alone_bone'}>
is_remove_ik_bone: <_PropertyDeferred, <built-in function BoolProperty>, {'name': '名前がIK/Nubっぽい', 'default': True, 'attr': 'is_remove_ik_bone'}> =
<_PropertyDeferred, <built-in function BoolProperty>, {'name': '名前がIK/Nubっぽい', 'default': True, 'attr': 'is_remove_ik_bone'}>
is_remove_serial_number_bone: <_PropertyDeferred, <built-in function BoolProperty>, {'name': '名前が連番付き', 'default': True, 'attr': 'is_remove_serial_number_bone'}> =
<_PropertyDeferred, <built-in function BoolProperty>, {'name': '名前が連番付き', 'default': True, 'attr': 'is_remove_serial_number_bone'}>
is_remove_japanese_bone: <_PropertyDeferred, <built-in function BoolProperty>, {'name': '名前に日本語が含まれる', 'default': True, 'attr': 'is_remove_japanese_bone'}> =
<_PropertyDeferred, <built-in function BoolProperty>, {'name': '名前に日本語が含まれる', 'default': True, 'attr': 'is_remove_japanese_bone'}>
def
invoke(self, context, event):
68 def invoke(self, context, event): 69 prefs = common.preferences() 70 71 ob = context.active_object 72 arm = ob.data 73 action_name = None 74 if ob.animation_data and ob.animation_data.action: 75 action_name = common.remove_serial_number(ob.animation_data.action.name) 76 77 if prefs.anm_default_path: 78 self.filepath = common.default_cm3d2_dir(prefs.anm_default_path, action_name, "anm") 79 else: 80 self.filepath = common.default_cm3d2_dir(prefs.anm_export_path, action_name, "anm") 81 self.frame_start = context.scene.frame_start 82 self.frame_end = context.scene.frame_end 83 self.scale = 1.0 / prefs.scale 84 self.is_backup = bool(prefs.backup_ext) 85 self.key_frame_count = (context.scene.frame_end - context.scene.frame_start) + 1 86 87 if "BoneData:0" in arm: 88 self.bone_parent_from = 'ARMATURE_PROPERTY' 89 else: 90 self.bone_parent_from = 'ARMATURE' 91 92 context.window_manager.fileselect_add(self) 93 return {'RUNNING_MODAL'}
def
draw(self, context):
95 def draw(self, context): 96 self.layout.prop(self, 'scale') 97 98 box = self.layout.box() 99 box.prop(self, 'is_backup', icon='FILE_BACKUP') 100 box.prop(self, 'version') 101 102 #self.layout.prop(self, 'is_anm_data_text', icon='TEXT') 103 box = self.layout.box() 104 box.label(text="Export Method") 105 box.prop(self, 'export_method', expand=True) 106 107 box = self.layout.box() 108 box.enabled = not (self.export_method == 'TEXT') 109 box.prop(self, 'time_scale') 110 sub_box = box.box() 111 sub_box.enabled = (self.export_method == 'ALL') 112 row = sub_box.row() 113 row.prop(self, 'frame_start') 114 row.prop(self, 'frame_end') 115 sub_box.prop(self, 'key_frame_count') 116 sub_box.prop(self, 'is_keyframe_clean', icon='DISCLOSURE_TRI_DOWN') 117 sub_box.prop(self, 'is_smooth_handle', icon='SMOOTHCURVE') 118 119 sub_box = box.box() 120 sub_box.label(text="ボーン親情報の参照先", icon='FILE_PARENT') 121 sub_box.prop(self, 'bone_parent_from', icon='FILE_PARENT', expand=True) 122 123 sub_box = box.box() 124 sub_box.label(text="除外するボーン", icon='X') 125 column = sub_box.column(align=True) 126 column.prop(self, 'is_remove_unkeyed_bone' , icon='KEY_DEHLT' ) 127 column.prop(self, 'is_remove_alone_bone' , icon='UNLINKED' ) 128 column.prop(self, 'is_remove_ik_bone' , icon='CONSTRAINT_BONE' ) 129 column.prop(self, 'is_remove_serial_number_bone', icon='SEQUENCE' ) 130 column.prop(self, 'is_remove_japanese_bone' , icon=compat.icon('HOLDOUT_ON'))
def
execute(self, context):
132 def execute(self, context): 133 common.preferences().anm_export_path = self.filepath 134 135 try: 136 file = common.open_temporary(self.filepath, 'wb', is_backup=self.is_backup) 137 except: 138 self.report(type={'ERROR'}, message=f_tip_("ファイルを開くのに失敗しました、アクセス不可かファイルが存在しません。file={}", self.filepath)) 139 return {'CANCELLED'} 140 141 try: 142 with file: 143 if self.export_method == 'TEXT': 144 self.write_animation_from_text(context, file) 145 else: 146 self.write_animation(context, file) 147 except common.CM3D2ExportException as e: 148 self.report(type={'ERROR'}, message=str(e)) 149 return {'CANCELLED'} 150 151 return {'FINISHED'}
def
get_animation_frames(self, context, fps, pose, bones, bone_parents):
153 def get_animation_frames(self, context, fps, pose, bones, bone_parents): 154 anm_data_raw = {} 155 class KeyFrame: 156 def __init__(self, time, value, slope=None): 157 self.time = time 158 self.value = value 159 if slope: 160 self.slope = slope 161 elif type(value) == mathutils.Vector: 162 self.slope = mathutils.Vector.Fill(len(value)) 163 elif type(value) == mathutils.Quaternion: 164 self.slope = mathutils.Quaternion((0,0,0,0)) 165 else: 166 self.slope = 0 167 168 same_locs = {} 169 same_rots = {} 170 pre_rots = {} 171 for key_frame_index in range(self.key_frame_count): 172 if self.key_frame_count == 1: 173 frame = 0.0 174 else: 175 frame = (self.frame_end - self.frame_start) / (self.key_frame_count - 1) * key_frame_index + self.frame_start 176 context.scene.frame_set(frame=int(frame), subframe=frame - int(frame)) 177 if compat.IS_LEGACY: 178 context.scene.update() 179 else: 180 layer = context.view_layer 181 layer.update() 182 183 time = frame / fps * (1.0 / self.time_scale) 184 185 for bone in bones: 186 if bone.name not in anm_data_raw: 187 anm_data_raw[bone.name] = {"LOC": {}, "ROT": {}} 188 same_locs[bone.name] = [] 189 same_rots[bone.name] = [] 190 191 pose_bone = pose.bones[bone.name] 192 pose_mat = pose_bone.matrix.copy() #ob.convert_space(pose_bone=pose_bone, matrix=pose_bone.matrix, from_space='POSE', to_space='WORLD') 193 parent = bone_parents[bone.name] 194 if parent: 195 pose_mat = compat.convert_bl_to_cm_bone_rotation(pose_mat) 196 pose_mat = compat.mul(pose.bones[parent.name].matrix.inverted(), pose_mat) 197 pose_mat = compat.convert_bl_to_cm_bone_space(pose_mat) 198 else: 199 pose_mat = compat.convert_bl_to_cm_bone_rotation(pose_mat) 200 pose_mat = compat.convert_bl_to_cm_space(pose_mat) 201 202 loc = pose_mat.to_translation() * self.scale 203 rot = pose_mat.to_quaternion() 204 205 # This fixes rotations that jump to alternate representations. 206 if bone.name in pre_rots: 207 if 5.0 < pre_rots[bone.name].rotation_difference(rot).angle: 208 rot.w, rot.x, rot.y, rot.z = -rot.w, -rot.x, -rot.y, -rot.z 209 pre_rots[bone.name] = rot.copy() 210 211 #if parent: 212 # #loc.x, loc.y, loc.z = -loc.y, -loc.x, loc.z 213 # loc = compat.convert_bl_to_cm_bone_space(loc) 214 # 215 # # quat.w, quat.x, quat.y, quat.z = quat.w, -quat.z, quat.x, -quat.y 216 # #rot.w, rot.x, rot.y, rot.z = rot.w, rot.y, rot.x, -rot.z 217 # rot.w, rot.x, rot.y, rot.z = rot.w, rot.y, -rot.z, -rot.x 218 # 219 #else: 220 # loc.x, loc.y, loc.z = -loc.x, loc.z, -loc.y 221 # 222 # fix_quat = mathutils.Euler((0, 0, math.radians(-90)), 'XYZ').to_quaternion() 223 # fix_quat2 = mathutils.Euler((math.radians(-90), 0, 0), 'XYZ').to_quaternion() 224 # rot = compat.mul3(rot, fix_quat, fix_quat2) 225 # 226 # rot.w, rot.x, rot.y, rot.z = -rot.y, -rot.z, -rot.x, rot.w 227 228 if not self.is_keyframe_clean or key_frame_index == 0 or key_frame_index == self.key_frame_count - 1: 229 anm_data_raw[bone.name]["LOC"][time] = loc.copy() 230 anm_data_raw[bone.name]["ROT"][time] = rot.copy() 231 232 if self.is_keyframe_clean: 233 same_locs[bone.name].append(KeyFrame(time, loc.copy())) 234 same_rots[bone.name].append(KeyFrame(time, rot.copy())) 235 else: 236 def is_mismatch(a, b): 237 return 0.000001 < abs(a - b) 238 239 a = same_locs[bone.name][-1].value - loc 240 b = same_locs[bone.name][-1].slope 241 if is_mismatch(a.x, b.x) or is_mismatch(a.y, b.y) or is_mismatch(a.z, b.z): 242 if 2 <= len(same_locs[bone.name]): 243 anm_data_raw[bone.name]["LOC"][same_locs[bone.name][-1].time] = same_locs[bone.name][-1].value.copy() 244 anm_data_raw[bone.name]["LOC"][time] = loc.copy() 245 same_locs[bone.name] = [KeyFrame(time, loc.copy(), a.copy())] # update last position and slope 246 else: 247 same_locs[bone.name].append(KeyFrame(time, loc.copy(), b.copy())) # update last position, but not last slope 248 249 a = same_rots[bone.name][-1].value - rot 250 b = same_rots[bone.name][-1].slope 251 if is_mismatch(a.w, b.w) or is_mismatch(a.x, b.x) or is_mismatch(a.y, b.y) or is_mismatch(a.z, b.z): 252 if 2 <= len(same_rots[bone.name]): 253 anm_data_raw[bone.name]["ROT"][same_rots[bone.name][-1].time] = same_rots[bone.name][-1].value.copy() 254 anm_data_raw[bone.name]["ROT"][time] = rot.copy() 255 same_rots[bone.name] = [KeyFrame(time, rot.copy(), a.copy())] # update last position and slope 256 else: 257 same_rots[bone.name].append(KeyFrame(time, rot.copy(), b.copy())) # update last position, but not last slope 258 259 return anm_data_raw
def
get_animation_keyframes(self, context, fps, pose, keyed_bones, fcurves):
261 def get_animation_keyframes(self, context, fps, pose, keyed_bones, fcurves): 262 anm_data_raw = {} 263 264 prop_sizes = {'location': 3, 'rotation_quaternion': 4, 'rotation_euler': 3} 265 266 #class KeyFrame: 267 # def __init__(self, time, value): 268 # self.time = time 269 # self.value = value 270 #same_locs = {} 271 #same_rots = {} 272 #pre_rots = {} 273 274 def _convert_loc(pose_bone, loc): 275 loc = mathutils.Vector(loc) 276 loc = compat.mul(pose_bone.bone.matrix_local, loc) 277 if pose_bone.parent: 278 loc = compat.mul(pose_bone.parent.bone.matrix_local.inverted(), loc) 279 loc = compat.convert_bl_to_cm_bone_space(loc) 280 else: 281 loc = compat.convert_bl_to_cm_space(loc) 282 return loc * self.scale 283 """ 284 def _convert_quat(pose_bone, quat): 285 #quat = mathutils.Quaternion(quat) 286 #'''Can't use matrix transforms here as they would mess up interpolation.''' 287 #quat = compat.mul(pose_bone.bone.matrix_local.to_quaternion(), quat) 288 289 quat_mat = mathutils.Quaternion(quat).to_matrix().to_4x4() 290 quat_mat = compat.mul(pose_bone.bone.matrix_local, quat_mat) 291 #quat = quat_mat.to_quaternion() 292 if pose_bone.parent: 293 ## inverse of quat.w, quat.x, quat.y, quat.z = quat.w, -quat.z, quat.x, -quat.y 294 #quat.w, quat.x, quat.y, quat.z = quat.w, quat.y, -quat.z, -quat.x 295 #quat = compat.mul(pose_bone.parent.bone.matrix_local.to_quaternion().inverted(), quat) 296 ##quat = compat.mul(pose_bone.parent.bone.matrix_local.inverted().to_quaternion(), quat)\ 297 quat_mat = compat.convert_bl_to_cm_bone_rotation(quat_mat) 298 quat_mat = compat.mul(pose_bone.parent.bone.matrix_local.inverted(), quat_mat) 299 quat_mat = compat.convert_bl_to_cm_bone_space(quat_mat) 300 quat = quat_mat.to_quaternion() 301 else: 302 #fix_quat = mathutils.Euler((0, 0, math.radians(-90)), 'XYZ').to_quaternion() 303 #fix_quat2 = mathutils.Euler((math.radians(-90), 0, 0), 'XYZ').to_quaternion() 304 #quat = compat.mul3(quat, fix_quat, fix_quat2) 305 # 306 #quat.w, quat.x, quat.y, quat.z = -quat.y, -quat.z, -quat.x, quat.w 307 308 #quat.w, quat.x, quat.y, quat.z = quat.w, quat.y, -quat.z, -quat.x 309 #quat = compat.mul(mathutils.Matrix.Rotation(math.radians(90.0), 4, 'Z').to_quaternion(), quat) 310 311 quat_mat = compat.convert_bl_to_cm_bone_rotation(quat_mat) 312 quat_mat = compat.convert_bl_to_cm_space(quat_mat) 313 quat = quat_mat.to_quaternion() 314 return quat 315 """ 316 317 def _convert_quat(pose_bone, quat): 318 bone_quat = pose_bone.bone.matrix.to_quaternion() 319 quat = mathutils.Quaternion(quat) 320 321 '''Can't use matrix transforms here as they would mess up interpolation.''' 322 quat = compat.mul(bone_quat, quat) 323 324 if pose_bone.bone.parent: 325 #quat.w, quat.x, quat.y, quat.z = quat.w, -quat.z, quat.x, -quat.y 326 quat.w, quat.y, quat.x, quat.z = quat.w, -quat.z, quat.y, -quat.x 327 else: 328 quat = compat.mul(mathutils.Matrix.Rotation(math.radians(90.0), 4, 'Z').to_quaternion(), quat) 329 quat.w, quat.y, quat.x, quat.z = quat.w, -quat.z, quat.y, -quat.x 330 return quat 331 332 for prop, prop_keyed_bones in keyed_bones.items(): 333 #self.report(type={'INFO'}, message=f_tip_("{prop} {list}", prop=prop, list=prop_keyed_bones)) 334 for bone_name in prop_keyed_bones: 335 if bone_name not in anm_data_raw: 336 anm_data_raw[bone_name] = {} 337 #same_locs[bone_name] = [] 338 #same_rots[bone_name] = [] 339 340 pose_bone = pose.bones[bone_name] 341 rna_data_path = 'pose.bones["{bone_name}"].{property}'.format(bone_name=bone_name, property=prop) 342 prop_fcurves = [ fcurves.find(rna_data_path, index=axis_index) for axis_index in range(prop_sizes[prop]) ] 343 344 # Create missing fcurves, and make existing fcurves CM3D2 compatible. 345 for axis_index, fcurve in enumerate(prop_fcurves): 346 if not fcurve: 347 fcurve = fcurves.new(rna_data_path, index=axis_index, action_group=pose_bone.name) 348 prop_fcurves[axis_index] = fcurve 349 self.report(type={'WARNING'}, message=f_tip_("Creating missing FCurve for {path}[{index}]", path=rna_data_path, index=axis_index)) 350 else: 351 override = context.copy() 352 override['active_editable_fcurve'] = fcurve 353 bpy.ops.fcurve.convert_to_cm3d2_interpolation(override, only_selected=False, keep_reports=True) 354 for kwargs in misc_DOPESHEET_MT_editor_menus.REPORTS: 355 self.report(**kwargs) 356 misc_DOPESHEET_MT_editor_menus.REPORTS.clear() 357 358 359 # Create a list by frame, indicating wether or not there is a keyframe at that time for each fcurve 360 is_keyframes = {} 361 for fcurve in prop_fcurves: 362 for keyframe in fcurve.keyframe_points: 363 frame = keyframe.co[0] 364 if frame not in is_keyframes: 365 is_keyframes[frame] = [False] * prop_sizes[prop] 366 is_keyframes[frame][fcurve.array_index] = True 367 368 # Make sure that no keyframe times are missing any components 369 for frame, is_axes in is_keyframes.items(): 370 for axis_index, is_axis in enumerate(is_axes): 371 if not is_axis: 372 fcurve = prop_fcurves[axis_index] 373 keyframe = fcurve.keyframe_points.insert( 374 frame = frame , 375 value = fcurve.evaluate(frame), 376 options = {'NEEDED', 'FAST'} 377 ) 378 self.report(type={'WARNING'}, message=f_tip_("Creating missing keyframe @ frame {frame} for {path}[{index}]", path=rna_data_path, index=axis_index, frame=frame)) 379 380 for fcurve in prop_fcurves: 381 fcurve.update() 382 383 for keyframe_index, frame in enumerate(is_keyframes.keys()): 384 time = frame / fps * (1.0 / self.time_scale) 385 386 _kf = lambda fcurve: fcurve.keyframe_points[keyframe_index] 387 raw_keyframe = [ _kf(fc).co[1] for fc in prop_fcurves ] 388 tangent_in = [ ( _kf(fc).handle_left [1] - _kf(fc).co[1] ) / ( _kf(fc).handle_left [0] - _kf(fc).co[0] ) * fps for fc in prop_fcurves ] 389 tangent_out = [ ( _kf(fc).handle_right[1] - _kf(fc).co[1] ) / ( _kf(fc).handle_right[0] - _kf(fc).co[0] ) * fps for fc in prop_fcurves ] 390 391 if prop == 'location': 392 if 'LOC' not in anm_data_raw[bone_name]: 393 anm_data_raw[bone_name]['LOC' ] = {} 394 anm_data_raw[bone_name]['LOC_IN' ] = {} 395 anm_data_raw[bone_name]['LOC_OUT'] = {} 396 anm_data_raw[bone_name]['LOC' ][time] = _convert_loc(pose_bone, raw_keyframe).copy() 397 anm_data_raw[bone_name]['LOC_IN' ][time] = _convert_loc(pose_bone, tangent_in ).copy() 398 anm_data_raw[bone_name]['LOC_OUT'][time] = _convert_loc(pose_bone, tangent_out ).copy() 399 elif prop == 'rotation_quaternion': 400 if 'ROT' not in anm_data_raw[bone_name]: 401 anm_data_raw[bone_name]['ROT' ] = {} 402 anm_data_raw[bone_name]['ROT_IN' ] = {} 403 anm_data_raw[bone_name]['ROT_OUT'] = {} 404 anm_data_raw[bone_name]['ROT' ][time] = _convert_quat(pose_bone, raw_keyframe).copy() 405 anm_data_raw[bone_name]['ROT_OUT'][time] = _convert_quat(pose_bone, tangent_out ).copy() 406 anm_data_raw[bone_name]['ROT_IN' ][time] = _convert_quat(pose_bone, tangent_in ).copy() 407 # - - - Alternative Method - - - 408 #raw_keyframe = mathutils.Quaternion(raw_keyframe) 409 #tangent_in = mathutils.Quaternion(tangent_in) 410 #tangent_out = mathutils.Quaternion(tangent_out) 411 #converted_quat = _convert_quat(pose_bone, raw_keyframe).copy() 412 #anm_data_raw[bone_name]['ROT' ][time] = converted_quat.copy() 413 #anm_data_raw[bone_name]['ROT_IN' ][time] = converted_quat.inverted() @ _convert_quat(pose_bone, raw_keyframe @ tangent_in ) 414 #anm_data_raw[bone_name]['ROT_OUT'][time] = converted_quat.inverted() @ _convert_quat(pose_bone, raw_keyframe @ tangent_out ) 415 416 return anm_data_raw
def
write_animation(self, context, file):
418 def write_animation(self, context, file): 419 ob = context.active_object 420 arm = ob.data 421 pose = ob.pose 422 fps = context.scene.render.fps 423 424 425 bone_parents = {} 426 if self.bone_parent_from == 'ARMATURE_PROPERTY': 427 for i in range(9999): 428 name = "BoneData:" + str(i) 429 if name not in arm: 430 continue 431 elems = arm[name].split(",") 432 if len(elems) != 5: 433 continue 434 if elems[0] in arm.bones: 435 if elems[2] in arm.bones: 436 bone_parents[elems[0]] = arm.bones[elems[2]] 437 else: 438 bone_parents[elems[0]] = None 439 for bone in arm.bones: 440 if bone.name in bone_parents: 441 continue 442 bone_parents[bone.name] = bone.parent 443 else: 444 for bone in arm.bones: 445 bone_parents[bone.name] = bone.parent 446 447 copied_action = None 448 if ob.animation_data and ob.animation_data.action: 449 copied_action = ob.animation_data.action.copy() 450 copied_action.name = ob.animation_data.action.name + "__anm_export" 451 fcurves = copied_action.fcurves 452 keyed_bones = {'location': [], 'rotation_quaternion': [], 'rotation_euler': []} 453 for bone in arm.bones: 454 rna_data_stub = 'pose.bones["{bone_name}"]'.format(bone_name=bone.name) 455 for prop, axes in [('location', 3), ('rotation_quaternion', 4), ('rotation_euler', 3)]: 456 found_fcurve = False 457 for axis_index in range(0, axes): 458 if fcurves.find(rna_data_stub + '.' + prop, index=axis_index): 459 found_fcurve = True 460 break 461 if found_fcurve: 462 keyed_bones[prop].append(bone.name) 463 464 elif self.export_method == 'KEYED' or self.is_remove_unkeyed_bone: 465 raise common.CM3D2ExportException( 466 "Active armature has no animation data / action. Please use \"{method}\" with \"{option}\" disabled, or bake keyframes before exporting.".format( 467 method = "Bake All Frames", 468 option = "Remove Unkeyed Bones" 469 ) 470 ) 471 472 473 474 475 def is_japanese(string): 476 for ch in string: 477 name = unicodedata.name(ch) 478 if 'CJK UNIFIED' in name or 'HIRAGANA' in name or 'KATAKANA' in name: 479 return True 480 return False 481 bones = [] 482 already_bone_names = [] 483 bones_queue = arm.bones[:] 484 while len(bones_queue): 485 bone = bones_queue.pop(0) 486 487 if not bone_parents[bone.name]: 488 already_bone_names.append(bone.name) 489 if self.is_remove_serial_number_bone: 490 if common.has_serial_number(bone.name): 491 continue 492 if self.is_remove_japanese_bone: 493 if is_japanese(bone.name): 494 continue 495 if self.is_remove_alone_bone and len(bone.children) == 0: 496 continue 497 if self.is_remove_unkeyed_bone: 498 is_keyed = False 499 for prop in keyed_bones: 500 if bone.name in keyed_bones[prop]: 501 is_keyed = True 502 break 503 if not is_keyed: 504 continue 505 bones.append(bone) 506 continue 507 elif bone_parents[bone.name].name in already_bone_names: 508 already_bone_names.append(bone.name) 509 if self.is_remove_serial_number_bone: 510 if common.has_serial_number(bone.name): 511 continue 512 if self.is_remove_japanese_bone: 513 if is_japanese(bone.name): 514 continue 515 if self.is_remove_ik_bone: 516 bone_name_low = bone.name.lower() 517 if '_ik_' in bone_name_low or bone_name_low.endswith('_nub') or bone.name.endswith('Nub'): 518 continue 519 if self.is_remove_unkeyed_bone: 520 is_keyed = False 521 for prop in keyed_bones: 522 if bone.name in keyed_bones[prop]: 523 is_keyed = True 524 break 525 if not is_keyed: 526 continue 527 bones.append(bone) 528 continue 529 530 bones_queue.append(bone) 531 532 if self.export_method == 'ALL': 533 anm_data_raw = self.get_animation_frames(context, fps, pose, bones, bone_parents) 534 elif self.export_method == 'KEYED': 535 anm_data_raw = self.get_animation_keyframes(context, fps, pose, keyed_bones, fcurves) 536 537 if copied_action: 538 context.blend_data.actions.remove(copied_action, do_unlink=True, do_id_user=True, do_ui_user=True) 539 540 anm_data = {} 541 for bone_name, channels in anm_data_raw.items(): 542 anm_data[bone_name] = {100: {}, 101: {}, 102: {}, 103: {}, 104: {}, 105: {}, 106: {}} 543 if channels.get('LOC'): 544 has_tangents = bool(channels.get('LOC_IN') and channels.get('LOC_OUT')) 545 for time, loc in channels["LOC"].items(): 546 tangent_in = channels['LOC_IN' ][time] if has_tangents else mathutils.Vector() 547 tangent_out = channels['LOC_OUT'][time] if has_tangents else mathutils.Vector() 548 anm_data[bone_name][104][time] = (loc.x, tangent_in.x, tangent_out.x) 549 anm_data[bone_name][105][time] = (loc.y, tangent_in.y, tangent_out.y) 550 anm_data[bone_name][106][time] = (loc.z, tangent_in.z, tangent_out.z) 551 if channels.get('ROT'): 552 has_tangents = bool(channels.get('ROT_IN') and channels.get('ROT_OUT')) 553 for time, rot in channels["ROT"].items(): 554 tangent_in = channels['ROT_IN' ][time] if has_tangents else mathutils.Quaternion((0,0,0,0)) 555 tangent_out = channels['ROT_OUT'][time] if has_tangents else mathutils.Quaternion((0,0,0,0)) 556 anm_data[bone_name][100][time] = (rot.x, tangent_in.x, tangent_out.x) 557 anm_data[bone_name][101][time] = (rot.y, tangent_in.y, tangent_out.y) 558 anm_data[bone_name][102][time] = (rot.z, tangent_in.z, tangent_out.z) 559 anm_data[bone_name][103][time] = (rot.w, tangent_in.w, tangent_out.w) 560 561 time_step = 1 / fps * (1.0 / self.time_scale) 562 563 564 ''' Write data to the file ''' 565 566 common.write_str(file, 'CM3D2_ANIM') 567 file.write(struct.pack('<i', self.version)) 568 569 for bone in bones: 570 if not anm_data.get(bone.name): 571 continue 572 573 file.write(struct.pack('<?', True)) 574 575 bone_names = [bone.name] 576 current_bone = bone 577 while bone_parents[current_bone.name]: 578 bone_names.append(bone_parents[current_bone.name].name) 579 current_bone = bone_parents[current_bone.name] 580 581 bone_names.reverse() 582 common.write_str(file, "/".join(bone_names)) 583 584 for channel_id, keyframes in sorted(anm_data[bone.name].items(), key=lambda x: x[0]): 585 file.write(struct.pack('<B', channel_id)) 586 file.write(struct.pack('<i', len(keyframes))) 587 588 keyframes_list = sorted(keyframes.items(), key=lambda x: x[0]) 589 for i in range(len(keyframes_list)): 590 x = keyframes_list[i][0] 591 y, dydx_in, dydx_out = keyframes_list[i][1] 592 593 if len(keyframes_list) <= 1: 594 file.write(struct.pack('<f', x)) 595 file.write(struct.pack('<f', y)) 596 file.write(struct.pack('<2f', 0.0, 0.0)) 597 continue 598 599 file.write(struct.pack('<f', x)) 600 file.write(struct.pack('<f', y)) 601 602 if self.is_smooth_handle and self.export_method == 'ALL': 603 if i == 0: 604 prev_x = x - (keyframes_list[i + 1][0] - x) 605 prev_y = y - (keyframes_list[i + 1][1][0] - y) 606 next_x = keyframes_list[i + 1][0] 607 next_y = keyframes_list[i + 1][1][0] 608 elif i == len(keyframes_list) - 1: 609 prev_x = keyframes_list[i - 1][0] 610 prev_y = keyframes_list[i - 1][1][0] 611 next_x = x + (x - keyframes_list[i - 1][0]) 612 next_y = y + (y - keyframes_list[i - 1][1][0]) 613 else: 614 prev_x = keyframes_list[i - 1][0] 615 prev_y = keyframes_list[i - 1][1][0] 616 next_x = keyframes_list[i + 1][0] 617 next_y = keyframes_list[i + 1][1][0] 618 619 prev_rad = (prev_y - y) / (prev_x - x) 620 next_rad = (next_y - y) / (next_x - x) 621 join_rad = (prev_rad + next_rad) / 2 622 623 tan_in = join_rad if x - prev_x <= time_step * 1.5 else prev_rad 624 tan_out = join_rad if next_x - x <= time_step * 1.5 else next_rad 625 626 file.write(struct.pack('<2f', tan_in, tan_out)) 627 #file.write(struct.pack('<2f', join_rad, join_rad)) 628 #file.write(struct.pack('<2f', prev_rad, next_rad)) 629 else: 630 file.write(struct.pack('<2f', dydx_in, dydx_out)) 631 632 file.write(struct.pack('<?', False))
def
write_animation_from_text(self, context, file):
634 def write_animation_from_text(self, context, file): 635 txt = context.blend_data.texts.get("AnmData") 636 if not txt: 637 raise common.CM3D2ExportException("There is no 'AnmData' text file.") 638 639 import json 640 anm_data = json.loads(txt.as_string()) 641 642 common.write_str(file, 'CM3D2_ANIM') 643 file.write(struct.pack('<i', self.version)) 644 645 for base_bone_name, bone_data in anm_data.items(): 646 path = bone_data['path'] 647 file.write(struct.pack('<?', True)) 648 common.write_str(file, path) 649 650 for channel_id, channel in bone_data['channels'].items(): 651 file.write(struct.pack('<B', int(channel_id))) 652 channel_data_count = len(channel) 653 file.write(struct.pack('<i', channel_data_count)) 654 for channel_data in channel: 655 frame = channel_data['frame'] 656 data = ( channel_data['f0'], channel_data['f1'], channel_data['f2'] ) 657 file.write(struct.pack('<f' , frame)) 658 file.write(struct.pack('<3f', *data )) 659 660 file.write(struct.pack('<?', False))
Inherited Members
- bpy_types.Operator
- as_keywords
- poll_message_set
- builtins.bpy_struct
- keys
- values
- get
- pop
- as_pointer
- keyframe_insert
- keyframe_delete
- driver_add
- driver_remove
- is_property_set
- property_unset
- is_property_readonly
- is_property_overridable_library
- property_overridable_library_set
- path_resolve
- path_from_id
- type_recast
- bl_rna_get_subclass_py
- bl_rna_get_subclass
- id_properties_ensure
- id_properties_clear
- id_properties_ui
- id_data