CM3D2 Converter.anm_import

  1import re
  2import struct
  3import math
  4import unicodedata
  5import time
  6import bpy
  7import bmesh
  8import mathutils
  9import os
 10from . import common
 11from . import compat
 12from .translations.pgettext_functions import *
 13
 14
 15# メインオペレーター
 16@compat.BlRegister()
 17class CNV_OT_import_cm3d2_anm(bpy.types.Operator):
 18    bl_idname = 'import_anim.import_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=5, min=0.1, max=100, soft_min=0.1, soft_max=100, step=100, precision=1, description="インポート時のメッシュ等の拡大率です")
 28    set_frame_rate = bpy.props.BoolProperty(name="Set Framerate", default=True, description="Change the scene's render settings to 60 fps")                                     
 29    is_loop = bpy.props.BoolProperty(name="Loop", default=True)
 30
 31    is_anm_data_text = bpy.props.BoolProperty(name="Anm Text", default=True, description="Output Data to a JSON file")
 32    
 33    remove_pre_animation = bpy.props.BoolProperty(name="既にあるアニメーションを削除", default=True)
 34    set_frame = bpy.props.BoolProperty(name="フレーム開始・終了位置を調整", default=True)
 35    ignore_automatic_bone = bpy.props.BoolProperty(name="Twisterボーンを除外", default=True)
 36
 37    is_location = bpy.props.BoolProperty(name="位置", default=True)
 38    is_rotation = bpy.props.BoolProperty(name="回転", default=True)
 39    is_scale = bpy.props.BoolProperty(name="拡縮", default=False)
 40    is_tangents = bpy.props.BoolProperty(name="Tangents"      , default=False)
 41
 42    @classmethod
 43    def poll(cls, context):
 44        ob = context.active_object
 45        if ob and ob.type == 'ARMATURE':
 46            return True
 47        return False
 48
 49    def invoke(self, context, event):
 50        prefs = common.preferences()
 51        if prefs.anm_default_path:
 52            self.filepath = common.default_cm3d2_dir(prefs.anm_default_path, None, "anm")
 53        else:
 54            self.filepath = common.default_cm3d2_dir(prefs.anm_import_path, None, "anm")
 55        self.scale = prefs.scale
 56        context.window_manager.fileselect_add(self)
 57        return {'RUNNING_MODAL'}
 58
 59    def draw(self, context):
 60        self.layout.prop(self, 'scale')
 61        self.layout.prop(self, 'set_frame_rate'  , icon=compat.icon('RENDER_ANIMATION'))
 62        self.layout.prop(self, 'is_loop'         , icon=compat.icon('LOOP_BACK'       ))
 63        self.layout.prop(self, 'is_anm_data_text', icon=compat.icon('TEXT'            ))
 64
 65        box = self.layout.box()
 66        box.prop(self, 'remove_pre_animation', icon='DISCLOSURE_TRI_DOWN')
 67        box.prop(self, 'set_frame', icon='NEXT_KEYFRAME')
 68        box.prop(self, 'ignore_automatic_bone', icon='X')
 69
 70        box = self.layout.box()
 71        box.label(text="読み込むアニメーション情報")
 72        column = box.column(align=True)
 73        column.prop(self, 'is_location', icon=compat.icon('CON_LOCLIKE'))
 74        column.prop(self, 'is_rotation', icon=compat.icon('CON_ROTLIKE'))
 75        row = column.row()
 76        row.prop(self, 'is_scale', icon=compat.icon('CON_SIZELIKE'))
 77        row.enabled = False
 78        column.prop(self, 'is_tangents', icon=compat.icon('IPO_BEZIER' ))
 79
 80    def execute(self, context):
 81        prefs = common.preferences()
 82        prefs.anm_import_path = self.filepath
 83        prefs.scale = self.scale
 84
 85        try:
 86            file = open(self.filepath, 'rb')
 87        except:
 88            self.report(type={'ERROR'}, message=f_tip_("ファイルを開くのに失敗しました、アクセス不可かファイルが存在しません。file={}", self.filepath))
 89            return {'CANCELLED'}
 90
 91        # ヘッダー
 92        ext = common.read_str(file)
 93        if ext != 'CM3D2_ANIM':
 94            self.report(type={'ERROR'}, message="これはカスタムメイド3D2のモーションファイルではありません")
 95            return {'CANCELLED'}
 96        anm_version = struct.unpack('<i', file.read(4))[0]
 97        first_channel_id = struct.unpack('<B', file.read(1))[0]
 98        if first_channel_id != 1:
 99            self.report(type={'ERROR'}, message=f_tip_("Unexpected first channel id = {id} (should be 1).", id=first_channel_id))
100            return {'CANCELLED'}
101
102
103        anm_data = {}
104        for anim_data_index in range(9**9):
105            path = common.read_str(file)
106            
107            base_bone_name = path.split('/')[-1]
108            if base_bone_name not in anm_data:
109                anm_data[base_bone_name] = {'path': path}
110                anm_data[base_bone_name]['channels'] = {}
111
112            for channel_index in range(9**9):
113                channel_id = struct.unpack('<B', file.read(1))[0]
114                channel_id_str = channel_id
115                if channel_id <= 1:
116                    break
117                anm_data[base_bone_name]['channels'][channel_id_str] = []
118                channel_data_count = struct.unpack('<i', file.read(4))[0]
119                for channel_data_index in range(channel_data_count):
120                    frame = struct.unpack('<f', file.read(4))[0]
121                    data = struct.unpack('<3f', file.read(4 * 3))
122
123                    anm_data[base_bone_name]['channels'][channel_id_str].append({'frame': frame, 'f0': data[0], 'f1': data[1], 'f2': data[2]})
124
125            if channel_id == 0:
126                break
127        
128        if self.is_anm_data_text:
129            if "AnmData" in context.blend_data.texts:
130                txt = context.blend_data.texts["AnmData"]
131                txt.clear()
132            else:
133                txt = context.blend_data.texts.new("AnmData")
134            import json
135            txt.write( json.dumps(anm_data, ensure_ascii=False, indent=2) )
136
137        if self.set_frame_rate:
138            context.scene.render.fps = 60
139        fps = context.scene.render.fps
140
141        ob = context.active_object
142        arm = ob.data
143        pose = ob.pose
144        base_bone = arm.get('BaseBone')
145        if base_bone:
146            base_bone = arm.bones.get(base_bone)
147
148        anim = ob.animation_data
149        if not anim:
150            anim = ob.animation_data_create()
151        action = anim.action
152        if not action:
153            action = context.blend_data.actions.new(os.path.basename(self.filepath))
154            anim.action = action
155            fcurves = action.fcurves
156        else:
157            action.name = os.path.basename(self.filepath)
158            fcurves = action.fcurves
159            if self.remove_pre_animation:
160                for fcurve in fcurves:
161                    fcurves.remove(fcurve)
162
163        max_frame = 0
164        bpy.ops.object.mode_set(mode='OBJECT')
165        found_unknown = []
166        found_tangents = []
167        for bone_name, bone_data in anm_data.items():
168            if self.ignore_automatic_bone:
169                if re.match(r"Kata_[RL]", bone_name):
170                    continue
171                if re.match(r"Uppertwist1_[RL]", bone_name):
172                    continue
173                if re.match(r"momoniku_[RL]", bone_name):
174                    continue
175
176            if bone_name not in pose.bones:
177                bone_name = common.decode_bone_name(bone_name)
178                if bone_name not in pose.bones:
179                    continue
180            bone = arm.bones[bone_name]
181            pose_bone = pose.bones[bone_name]
182
183            loc_fcurves  = None
184
185            locs = {}
186            loc_tangents = {}
187            quats = {}
188            quat_tangents = {}
189            for channel_id, channel_data in bone_data['channels'].items():
190
191                if channel_id in [100, 101, 102, 103]:
192                    for data in channel_data:
193                        frame = data['frame']
194                        if frame not in quats:
195                            quats[frame] = [None, None, None, None]
196
197                        if channel_id == 103:
198                            quats[frame][0] = data['f0']
199                        elif channel_id == 100:
200                            quats[frame][1] = data['f0']
201                        elif channel_id == 101:
202                            quats[frame][2] = data['f0']
203                        elif channel_id == 102:
204                            quats[frame][3] = data['f0']
205
206                        #tangents = (data['f1'], data['f2'])
207                        #if (data['f1']**2 + data['f2']**2) ** .5 > 0.01:
208                        #    found_tangents.append(tangents)
209                        if frame not in quat_tangents:
210                            quat_tangents[frame] = {'in': [None, None, None, None], 'out': [None, None, None, None]}
211
212                        if channel_id == 103:
213                            quat_tangents[frame]['in' ][0] = data['f1']
214                            quat_tangents[frame]['out'][0] = data['f2']
215                        elif channel_id == 100:                        
216                            quat_tangents[frame]['in' ][1] = data['f1']
217                            quat_tangents[frame]['out'][1] = data['f2']
218                        elif channel_id == 101:                        
219                            quat_tangents[frame]['in' ][2] = data['f1']
220                            quat_tangents[frame]['out'][2] = data['f2']
221                        elif channel_id == 102:              
222                            quat_tangents[frame]['in' ][3] = data['f1']
223                            quat_tangents[frame]['out'][3] = data['f2']
224
225                elif channel_id in [104, 105, 106]:
226                    for data in channel_data:
227                        frame = data['frame']
228                        if frame not in locs:
229                            locs[frame] = [None, None, None]
230
231                        if channel_id == 104:
232                            locs[frame][0] = data['f0']
233                        elif channel_id == 105:
234                            locs[frame][1] = data['f0']
235                        elif channel_id == 106:
236                            locs[frame][2] = data['f0']
237                        
238                        #tangents = (data['f1'], data['f2'])
239                        #if (data['f1']**2 + data['f2']**2) ** .5 > 0.05:
240                        #    found_tangents.append(tangents)
241                        if frame not in loc_tangents:
242                            loc_tangents[frame] = {'in': [None, None, None], 'out': [None, None, None]}
243
244                        if channel_id == 104:
245                            loc_tangents[frame]['in' ][0] = data['f1']
246                            loc_tangents[frame]['out'][0] = data['f2']
247                        elif channel_id == 105:                       
248                            loc_tangents[frame]['in' ][1] = data['f1']
249                            loc_tangents[frame]['out'][1] = data['f2']
250                        elif channel_id == 106:                       
251                            loc_tangents[frame]['in' ][2] = data['f1']
252                            loc_tangents[frame]['out'][2] = data['f2']
253
254                elif channel_id not in found_unknown:
255                    found_unknown.append(channel_id)
256                    self.report(type={'INFO'}, message=f_tip_("Unknown channel id {num}", num=channel_id))
257
258            '''
259            for frame, (loc, quat) in enumerate(zip(locs.values(), quats.values())):
260                loc  = mathutils.Vector(loc) * self.scale
261                quat = mathutils.Quaternion(quat)
262            
263                loc_mat = mathutils.Matrix.Translation(loc).to_4x4()
264                rot_mat = quat.to_matrix().to_4x4()
265                mat     = compat.mul(loc_mat, rot_mat)
266                
267                bone_loc  = bone.head_local.copy()
268                bone_quat = bone.matrix.to_quaternion()
269            
270                if bone.parent:
271                    parent = bone.parent
272                else:
273                    parent = base_bone
274                    
275                if parent:
276                    mat = compat.convert_cm_to_bl_bone_space(mat)
277                    mat = compat.mul(parent.matrix_local, mat)
278                    mat = compat.convert_cm_to_bl_bone_rotation(mat)
279                    pose_mat = bone.convert_local_to_pose(
280                        matrix              = mat, 
281                        matrix_local        = bone.matrix_local,
282                        parent_matrix       = mathutils.Matrix.Identity(4),
283                        parent_matrix_local = parent.matrix_local
284                    )
285                else:
286                    mat = compat.convert_cm_to_bl_bone_rotation(mat)
287                    mat = compat.convert_cm_to_bl_space(mat)
288                    pose_mat = bone.convert_local_to_pose(
289                        matrix       = mat, 
290                        matrix_local = bone.matrix_local
291                    )
292            
293                if self.is_location:
294                    pose_bone.location = pose_mat.to_translation()
295                    pose_bone.keyframe_insert('location'           , frame=frame * fps, group=pose_bone.name)
296                if self.is_rotation:
297                    pose_bone.rotation_quaternion = pose_mat.to_quaternion()
298                    pose_bone.keyframe_insert('rotation_quaternion', frame=frame * fps, group=pose_bone.name)
299                if max_frame < frame * fps:
300                    max_frame = frame * fps
301            '''            
302            
303            def _apply_tangents(fcurves, keyframes, tangents):
304                for axis_index, axis_keyframes in enumerate(keyframes):
305                    fcurve = fcurves[axis_index]
306                    fcurve.update() # make sure automatic handles are calculated
307                    axis_keyframes.sort() # make sure list is in order
308                    for keyframe_index, frame in enumerate(axis_keyframes):
309                        tangent_in  = tangents[frame]['in' ][axis_index]
310                        tangent_out = tangents[frame]['out'][axis_index]
311
312                        vec_in   = mathutils.Vector((1, tangent_in  / fps))   
313                        vec_out  = mathutils.Vector((1, tangent_out / fps))
314
315                        this_keyframe = fcurve.keyframe_points[keyframe_index  ]
316                        next_keyframe = fcurve.keyframe_points[keyframe_index+1] if keyframe_index+1 < len(axis_keyframes) else None
317                        last_keyframe = fcurve.keyframe_points[keyframe_index-1] if keyframe_index-1 >= 0                  else None
318                        
319                        if vec_in.y != vec_out.y:
320                            this_keyframe.handle_left_type  = 'FREE'
321                            this_keyframe.handle_right_type = 'FREE'
322                        else:
323                            this_keyframe.handle_left_type  = 'ALIGNED'
324                            this_keyframe.handle_right_type = 'ALIGNED'
325
326                        this_co = mathutils.Vector(this_keyframe.co)
327                        next_co = mathutils.Vector(next_keyframe.co) if next_keyframe else None
328                        last_co = mathutils.Vector(last_keyframe.co) if last_keyframe else None
329                        if not next_keyframe:
330                            next_keyframe = fcurve.keyframe_points[0]
331                            if next_keyframe and next_keyframe != this_keyframe:
332                                next_co = mathutils.Vector(next_keyframe.co)
333                                next_co.x += max_frame
334                        if not last_keyframe:
335                            last_keyframe = fcurve.keyframe_points[len(axis_keyframes)-1]
336                            if last_keyframe and last_keyframe != this_keyframe:
337                                last_co = mathutils.Vector(last_keyframe.co)
338                                last_co.x -= max_frame
339
340                        factor = 3
341                        dist_in  = (last_co.x - this_co.x) / factor if factor and last_co else None
342                        dist_out = (next_co.x - this_co.x) / factor if factor and next_co else None
343                        if not dist_in and not dist_out:
344                            dist_in  = this_keyframe.handle_left[0]  - this_co.x
345                            dist_out = this_keyframe.handle_right[0] - this_co.x
346                        elif not dist_in:
347                            dist_in  = -dist_out
348                        elif not dist_out:
349                            dist_out = -dist_in
350
351                        this_keyframe.handle_left  = vec_in  * dist_in  + this_co
352                        this_keyframe.handle_right = vec_out * dist_out + this_co
353
354
355            if self.is_location:
356                loc_fcurves = [None, None, None]
357                loc_keyframes = [[],[],[]]
358                rna_data_path = 'pose.bones["{bone_name}"].location'.format(bone_name=bone.name)
359                for axis_index in range(0, 3):
360                    new_fcurve = fcurves.find(rna_data_path, index=axis_index)
361                    if not new_fcurve:
362                        new_fcurve = fcurves.new(rna_data_path, index=axis_index, action_group=pose_bone.name)
363                    loc_fcurves[axis_index] = new_fcurve
364                
365                def _convert_loc(loc) -> mathutils.Vector:
366                    loc = mathutils.Vector(loc) * self.scale
367                    #bone_loc = bone.head_local.copy()
368                    #
369                    #if bone.parent:
370                    #    #loc.x, loc.y, loc.z = -loc.y, -loc.x, loc.z
371                    #
372                    #    #co.x, co.y, co.z = -co.y, co.z, co.x
373                    #    #loc.x, loc.y, loc.z = loc.z, -loc.x, loc.y
374                    #    #mat = mathutils.Matrix(
375                    #    #    [( 0,  0,  1,  0), 
376                    #    #     (-1,  0,  0,  0), 
377                    #    #     ( 0,  1,  0,  0),
378                    #    #     ( 0,  0,  0,  1)]
379                    #    #)
380                    #    #loc = compat.mul(mat, loc)
381                    #
382                    #    loc = compat.convert_cm_to_bl_bone_space(loc)
383                    #
384                    #    bone_loc = bone_loc - bone.parent.head_local
385                    #    bone_loc.rotate(bone.parent.matrix_local.to_quaternion().inverted())
386                    #else:
387                    #    #loc.x, loc.y, loc.z = loc.x, loc.z, loc.y
388                    #    loc = compat.convert_cm_to_bl_space(loc)
389                    #
390                    #result_loc = loc - bone_loc
391                    if bone.parent:
392                        loc = compat.convert_cm_to_bl_bone_space(loc)
393                        loc = compat.mul(bone.parent.matrix_local, loc)
394                    else:
395                        loc = compat.convert_cm_to_bl_space(loc)
396                    return compat.mul(bone.matrix_local.inverted(), loc)
397
398                for frame, loc in locs.items():
399                    result_loc = _convert_loc(loc)
400                    #pose_bone.location = result_loc
401
402                    #pose_bone.keyframe_insert('location', frame=frame * fps, group=pose_bone.name)
403                    if max_frame < frame * fps:
404                        max_frame = frame * fps
405     
406                    for fcurve in loc_fcurves:
407                        keyframe_type = 'KEYFRAME'
408                        tangents = loc_tangents[frame]
409                        if tangents:
410                            tangents = mathutils.Vector((tangents['in'][fcurve.array_index], tangents['out'][fcurve.array_index]))
411                            if tangents.magnitude < 1e-6:
412                                keyframe_type = 'JITTER'
413                            elif tangents.magnitude > 0.1:
414                                keyframe_type = 'EXTREME'
415
416                        keyframe = fcurve.keyframe_points.insert(
417                            frame         = frame * fps                   , 
418                            value         = result_loc[fcurve.array_index], 
419                            options       = {'FAST'}                      , 
420                            keyframe_type = keyframe_type
421                        )
422                        keyframe.type = keyframe_type
423                        loc_keyframes[fcurve.array_index].append(frame)
424
425                if self.is_loop:
426                    for fcurve in loc_fcurves:
427                        new_modifier = fcurve.modifiers.new('CYCLES')
428
429                if self.is_tangents:
430                    for frame, tangents in loc_tangents.items():
431                        tangent_in  = mathutils.Vector(tangents['in' ]) * self.scale
432                        tangent_out = mathutils.Vector(tangents['out']) * self.scale
433                        if bone.parent:
434                            tangent_in  = compat.convert_cm_to_bl_bone_space(tangent_in )
435                            tangent_out = compat.convert_cm_to_bl_bone_space(tangent_out)
436                        else:
437                            tangent_in  = compat.convert_cm_to_bl_space(tangent_in )
438                            tangent_out = compat.convert_cm_to_bl_space(tangent_out)
439                        tangents['in' ][:] = tangent_in [:]
440                        tangents['out'][:] = tangent_out[:]
441
442                    _apply_tangents(loc_fcurves, loc_keyframes, loc_tangents)
443                        
444            
445            
446            if self.is_rotation:
447                quat_fcurves = [None, None, None, None]
448                quat_keyframes = [[],[],[],[]]
449                rna_data_path = 'pose.bones["{bone_name}"].rotation_quaternion'.format(bone_name=pose_bone.name)
450                for axis_index in range(0, 4):
451                    new_fcurve = fcurves.find(rna_data_path, index=axis_index)
452                    if not new_fcurve:
453                        new_fcurve = fcurves.new(rna_data_path, index=axis_index, action_group=pose_bone.name)
454                    quat_fcurves[axis_index] = new_fcurve
455
456
457                bone_quat = bone.matrix.to_quaternion()
458                def _convert_quat(quat) -> mathutils.Quaternion:
459                    quat = mathutils.Quaternion(quat)
460                    #orig_quat = quat.copy()
461                    '''Can't use matrix transforms here as they would mess up interpolation.'''
462                    if bone.parent:
463                        quat.w, quat.x, quat.y, quat.z = quat.w, -quat.z, quat.x, -quat.y
464                        #quat_mat = compat.convert_cm_to_bl_bone_space(quat.to_matrix().to_4x4())
465                        #quat_mat = compat.convert_cm_to_bl_bone_rotation(quat_mat)
466                    else:
467                        quat.w, quat.x, quat.y, quat.z = quat.w, -quat.z, quat.x, -quat.y
468                        quat = compat.mul(mathutils.Matrix.Rotation(math.radians(-90.0), 4, 'Z').to_quaternion(), quat)
469                        #quat_mat = compat.convert_cm_to_bl_space(quat.to_matrix().to_4x4())
470                        #quat = compat.convert_cm_to_bl_bone_rotation(quat_mat).to_quaternion()
471                    quat = compat.mul(bone_quat.inverted(), quat)
472                    #quat.make_compatible(orig_quat)
473                    return quat
474                        
475                for frame, quat in quats.items():
476                    result_quat = _convert_quat(quat)
477                    #pose_bone.rotation_quaternion = result_quat.copy()
478            
479                    #pose_bone.keyframe_insert('rotation_quaternion', frame=frame * fps, group=pose_bone.name)
480                    if max_frame < frame * fps:
481                        max_frame = frame * fps
482                    
483                    for fcurve in quat_fcurves:
484                        keyframe_type = 'KEYFRAME'
485                        tangents = quat_tangents[frame]
486                        if tangents:
487                            tangents = mathutils.Vector((tangents['in'][fcurve.array_index], tangents['out'][fcurve.array_index]))
488                            if tangents.magnitude < 1e-6:
489                                keyframe_type = 'JITTER'
490                            elif tangents.magnitude > 0.1:
491                                keyframe_type = 'EXTREME'
492                        
493                        keyframe = fcurve.keyframe_points.insert(
494                            frame         = frame * fps                     , 
495                            value         = result_quat[fcurve.array_index] , 
496                            options       = {'FAST'}                        , 
497                            keyframe_type = keyframe_type
498                        )
499                        keyframe.type = keyframe_type
500                        quat_keyframes[fcurve.array_index].append(frame)
501
502                if self.is_loop:
503                    for fcurve in quat_fcurves:
504                        new_modifier = fcurve.modifiers.new('CYCLES')
505                
506                if self.is_tangents:
507                    for frame, tangents in quat_tangents.items():
508                        tangents['in' ][:] = _convert_quat(tangents['in' ])[:]
509                        tangents['out'][:] = _convert_quat(tangents['out'])[:]
510
511                    _apply_tangents(quat_fcurves, quat_keyframes, quat_tangents)
512                            
513
514
515        if found_tangents:
516            self.report(type={'INFO'}, message="Found the following tangent values:")
517            for f1, f2 in found_tangents:
518                self.report(type={'INFO'}, message=f_tip_("f1 = {float1}, f2 = {float2}", float1=f1, float2=f2))
519            self.report(type={'INFO'}, message="Found the above tangent values.")  
520            self.report(type={'WARNING'}, message=f_tip_("Found {count} large tangents. Blender animation may not interpolate properly. See log for more info.", count=len(found_tangents)))  
521        if found_unknown:
522            self.report(type={'INFO'}, message="Found the following unknown channel IDs:")
523            for channel_id in found_unknown:
524                self.report(type={'INFO'}, message=f_tip_("id = {id}", id=channel_id))
525            self.report(type={'INFO'}, message="Found the above unknown channel IDs.")  
526            self.report(type={'WARNING'}, message=f_tip_("Found {count} unknown channel IDs. Blender animation may be missing some keyframes. See log for more info.", count=len(found_unknown)))
527
528        if self.set_frame:
529            context.scene.frame_start = 0
530            context.scene.frame_end = max_frame
531            context.scene.frame_set(0)
532
533        return {'FINISHED'}
534
535
536# メニューに登録する関数
537def menu_func(self, context):
538    self.layout.operator(CNV_OT_import_cm3d2_anm.bl_idname, icon_value=common.kiss_icon())
@compat.BlRegister()
class CNV_OT_import_cm3d2_anm(bpy_types.Operator):
 17@compat.BlRegister()
 18class CNV_OT_import_cm3d2_anm(bpy.types.Operator):
 19    bl_idname = 'import_anim.import_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=5, min=0.1, max=100, soft_min=0.1, soft_max=100, step=100, precision=1, description="インポート時のメッシュ等の拡大率です")
 29    set_frame_rate = bpy.props.BoolProperty(name="Set Framerate", default=True, description="Change the scene's render settings to 60 fps")                                     
 30    is_loop = bpy.props.BoolProperty(name="Loop", default=True)
 31
 32    is_anm_data_text = bpy.props.BoolProperty(name="Anm Text", default=True, description="Output Data to a JSON file")
 33    
 34    remove_pre_animation = bpy.props.BoolProperty(name="既にあるアニメーションを削除", default=True)
 35    set_frame = bpy.props.BoolProperty(name="フレーム開始・終了位置を調整", default=True)
 36    ignore_automatic_bone = bpy.props.BoolProperty(name="Twisterボーンを除外", default=True)
 37
 38    is_location = bpy.props.BoolProperty(name="位置", default=True)
 39    is_rotation = bpy.props.BoolProperty(name="回転", default=True)
 40    is_scale = bpy.props.BoolProperty(name="拡縮", default=False)
 41    is_tangents = bpy.props.BoolProperty(name="Tangents"      , default=False)
 42
 43    @classmethod
 44    def poll(cls, context):
 45        ob = context.active_object
 46        if ob and ob.type == 'ARMATURE':
 47            return True
 48        return False
 49
 50    def invoke(self, context, event):
 51        prefs = common.preferences()
 52        if prefs.anm_default_path:
 53            self.filepath = common.default_cm3d2_dir(prefs.anm_default_path, None, "anm")
 54        else:
 55            self.filepath = common.default_cm3d2_dir(prefs.anm_import_path, None, "anm")
 56        self.scale = prefs.scale
 57        context.window_manager.fileselect_add(self)
 58        return {'RUNNING_MODAL'}
 59
 60    def draw(self, context):
 61        self.layout.prop(self, 'scale')
 62        self.layout.prop(self, 'set_frame_rate'  , icon=compat.icon('RENDER_ANIMATION'))
 63        self.layout.prop(self, 'is_loop'         , icon=compat.icon('LOOP_BACK'       ))
 64        self.layout.prop(self, 'is_anm_data_text', icon=compat.icon('TEXT'            ))
 65
 66        box = self.layout.box()
 67        box.prop(self, 'remove_pre_animation', icon='DISCLOSURE_TRI_DOWN')
 68        box.prop(self, 'set_frame', icon='NEXT_KEYFRAME')
 69        box.prop(self, 'ignore_automatic_bone', icon='X')
 70
 71        box = self.layout.box()
 72        box.label(text="読み込むアニメーション情報")
 73        column = box.column(align=True)
 74        column.prop(self, 'is_location', icon=compat.icon('CON_LOCLIKE'))
 75        column.prop(self, 'is_rotation', icon=compat.icon('CON_ROTLIKE'))
 76        row = column.row()
 77        row.prop(self, 'is_scale', icon=compat.icon('CON_SIZELIKE'))
 78        row.enabled = False
 79        column.prop(self, 'is_tangents', icon=compat.icon('IPO_BEZIER' ))
 80
 81    def execute(self, context):
 82        prefs = common.preferences()
 83        prefs.anm_import_path = self.filepath
 84        prefs.scale = self.scale
 85
 86        try:
 87            file = open(self.filepath, 'rb')
 88        except:
 89            self.report(type={'ERROR'}, message=f_tip_("ファイルを開くのに失敗しました、アクセス不可かファイルが存在しません。file={}", self.filepath))
 90            return {'CANCELLED'}
 91
 92        # ヘッダー
 93        ext = common.read_str(file)
 94        if ext != 'CM3D2_ANIM':
 95            self.report(type={'ERROR'}, message="これはカスタムメイド3D2のモーションファイルではありません")
 96            return {'CANCELLED'}
 97        anm_version = struct.unpack('<i', file.read(4))[0]
 98        first_channel_id = struct.unpack('<B', file.read(1))[0]
 99        if first_channel_id != 1:
100            self.report(type={'ERROR'}, message=f_tip_("Unexpected first channel id = {id} (should be 1).", id=first_channel_id))
101            return {'CANCELLED'}
102
103
104        anm_data = {}
105        for anim_data_index in range(9**9):
106            path = common.read_str(file)
107            
108            base_bone_name = path.split('/')[-1]
109            if base_bone_name not in anm_data:
110                anm_data[base_bone_name] = {'path': path}
111                anm_data[base_bone_name]['channels'] = {}
112
113            for channel_index in range(9**9):
114                channel_id = struct.unpack('<B', file.read(1))[0]
115                channel_id_str = channel_id
116                if channel_id <= 1:
117                    break
118                anm_data[base_bone_name]['channels'][channel_id_str] = []
119                channel_data_count = struct.unpack('<i', file.read(4))[0]
120                for channel_data_index in range(channel_data_count):
121                    frame = struct.unpack('<f', file.read(4))[0]
122                    data = struct.unpack('<3f', file.read(4 * 3))
123
124                    anm_data[base_bone_name]['channels'][channel_id_str].append({'frame': frame, 'f0': data[0], 'f1': data[1], 'f2': data[2]})
125
126            if channel_id == 0:
127                break
128        
129        if self.is_anm_data_text:
130            if "AnmData" in context.blend_data.texts:
131                txt = context.blend_data.texts["AnmData"]
132                txt.clear()
133            else:
134                txt = context.blend_data.texts.new("AnmData")
135            import json
136            txt.write( json.dumps(anm_data, ensure_ascii=False, indent=2) )
137
138        if self.set_frame_rate:
139            context.scene.render.fps = 60
140        fps = context.scene.render.fps
141
142        ob = context.active_object
143        arm = ob.data
144        pose = ob.pose
145        base_bone = arm.get('BaseBone')
146        if base_bone:
147            base_bone = arm.bones.get(base_bone)
148
149        anim = ob.animation_data
150        if not anim:
151            anim = ob.animation_data_create()
152        action = anim.action
153        if not action:
154            action = context.blend_data.actions.new(os.path.basename(self.filepath))
155            anim.action = action
156            fcurves = action.fcurves
157        else:
158            action.name = os.path.basename(self.filepath)
159            fcurves = action.fcurves
160            if self.remove_pre_animation:
161                for fcurve in fcurves:
162                    fcurves.remove(fcurve)
163
164        max_frame = 0
165        bpy.ops.object.mode_set(mode='OBJECT')
166        found_unknown = []
167        found_tangents = []
168        for bone_name, bone_data in anm_data.items():
169            if self.ignore_automatic_bone:
170                if re.match(r"Kata_[RL]", bone_name):
171                    continue
172                if re.match(r"Uppertwist1_[RL]", bone_name):
173                    continue
174                if re.match(r"momoniku_[RL]", bone_name):
175                    continue
176
177            if bone_name not in pose.bones:
178                bone_name = common.decode_bone_name(bone_name)
179                if bone_name not in pose.bones:
180                    continue
181            bone = arm.bones[bone_name]
182            pose_bone = pose.bones[bone_name]
183
184            loc_fcurves  = None
185
186            locs = {}
187            loc_tangents = {}
188            quats = {}
189            quat_tangents = {}
190            for channel_id, channel_data in bone_data['channels'].items():
191
192                if channel_id in [100, 101, 102, 103]:
193                    for data in channel_data:
194                        frame = data['frame']
195                        if frame not in quats:
196                            quats[frame] = [None, None, None, None]
197
198                        if channel_id == 103:
199                            quats[frame][0] = data['f0']
200                        elif channel_id == 100:
201                            quats[frame][1] = data['f0']
202                        elif channel_id == 101:
203                            quats[frame][2] = data['f0']
204                        elif channel_id == 102:
205                            quats[frame][3] = data['f0']
206
207                        #tangents = (data['f1'], data['f2'])
208                        #if (data['f1']**2 + data['f2']**2) ** .5 > 0.01:
209                        #    found_tangents.append(tangents)
210                        if frame not in quat_tangents:
211                            quat_tangents[frame] = {'in': [None, None, None, None], 'out': [None, None, None, None]}
212
213                        if channel_id == 103:
214                            quat_tangents[frame]['in' ][0] = data['f1']
215                            quat_tangents[frame]['out'][0] = data['f2']
216                        elif channel_id == 100:                        
217                            quat_tangents[frame]['in' ][1] = data['f1']
218                            quat_tangents[frame]['out'][1] = data['f2']
219                        elif channel_id == 101:                        
220                            quat_tangents[frame]['in' ][2] = data['f1']
221                            quat_tangents[frame]['out'][2] = data['f2']
222                        elif channel_id == 102:              
223                            quat_tangents[frame]['in' ][3] = data['f1']
224                            quat_tangents[frame]['out'][3] = data['f2']
225
226                elif channel_id in [104, 105, 106]:
227                    for data in channel_data:
228                        frame = data['frame']
229                        if frame not in locs:
230                            locs[frame] = [None, None, None]
231
232                        if channel_id == 104:
233                            locs[frame][0] = data['f0']
234                        elif channel_id == 105:
235                            locs[frame][1] = data['f0']
236                        elif channel_id == 106:
237                            locs[frame][2] = data['f0']
238                        
239                        #tangents = (data['f1'], data['f2'])
240                        #if (data['f1']**2 + data['f2']**2) ** .5 > 0.05:
241                        #    found_tangents.append(tangents)
242                        if frame not in loc_tangents:
243                            loc_tangents[frame] = {'in': [None, None, None], 'out': [None, None, None]}
244
245                        if channel_id == 104:
246                            loc_tangents[frame]['in' ][0] = data['f1']
247                            loc_tangents[frame]['out'][0] = data['f2']
248                        elif channel_id == 105:                       
249                            loc_tangents[frame]['in' ][1] = data['f1']
250                            loc_tangents[frame]['out'][1] = data['f2']
251                        elif channel_id == 106:                       
252                            loc_tangents[frame]['in' ][2] = data['f1']
253                            loc_tangents[frame]['out'][2] = data['f2']
254
255                elif channel_id not in found_unknown:
256                    found_unknown.append(channel_id)
257                    self.report(type={'INFO'}, message=f_tip_("Unknown channel id {num}", num=channel_id))
258
259            '''
260            for frame, (loc, quat) in enumerate(zip(locs.values(), quats.values())):
261                loc  = mathutils.Vector(loc) * self.scale
262                quat = mathutils.Quaternion(quat)
263            
264                loc_mat = mathutils.Matrix.Translation(loc).to_4x4()
265                rot_mat = quat.to_matrix().to_4x4()
266                mat     = compat.mul(loc_mat, rot_mat)
267                
268                bone_loc  = bone.head_local.copy()
269                bone_quat = bone.matrix.to_quaternion()
270            
271                if bone.parent:
272                    parent = bone.parent
273                else:
274                    parent = base_bone
275                    
276                if parent:
277                    mat = compat.convert_cm_to_bl_bone_space(mat)
278                    mat = compat.mul(parent.matrix_local, mat)
279                    mat = compat.convert_cm_to_bl_bone_rotation(mat)
280                    pose_mat = bone.convert_local_to_pose(
281                        matrix              = mat, 
282                        matrix_local        = bone.matrix_local,
283                        parent_matrix       = mathutils.Matrix.Identity(4),
284                        parent_matrix_local = parent.matrix_local
285                    )
286                else:
287                    mat = compat.convert_cm_to_bl_bone_rotation(mat)
288                    mat = compat.convert_cm_to_bl_space(mat)
289                    pose_mat = bone.convert_local_to_pose(
290                        matrix       = mat, 
291                        matrix_local = bone.matrix_local
292                    )
293            
294                if self.is_location:
295                    pose_bone.location = pose_mat.to_translation()
296                    pose_bone.keyframe_insert('location'           , frame=frame * fps, group=pose_bone.name)
297                if self.is_rotation:
298                    pose_bone.rotation_quaternion = pose_mat.to_quaternion()
299                    pose_bone.keyframe_insert('rotation_quaternion', frame=frame * fps, group=pose_bone.name)
300                if max_frame < frame * fps:
301                    max_frame = frame * fps
302            '''            
303            
304            def _apply_tangents(fcurves, keyframes, tangents):
305                for axis_index, axis_keyframes in enumerate(keyframes):
306                    fcurve = fcurves[axis_index]
307                    fcurve.update() # make sure automatic handles are calculated
308                    axis_keyframes.sort() # make sure list is in order
309                    for keyframe_index, frame in enumerate(axis_keyframes):
310                        tangent_in  = tangents[frame]['in' ][axis_index]
311                        tangent_out = tangents[frame]['out'][axis_index]
312
313                        vec_in   = mathutils.Vector((1, tangent_in  / fps))   
314                        vec_out  = mathutils.Vector((1, tangent_out / fps))
315
316                        this_keyframe = fcurve.keyframe_points[keyframe_index  ]
317                        next_keyframe = fcurve.keyframe_points[keyframe_index+1] if keyframe_index+1 < len(axis_keyframes) else None
318                        last_keyframe = fcurve.keyframe_points[keyframe_index-1] if keyframe_index-1 >= 0                  else None
319                        
320                        if vec_in.y != vec_out.y:
321                            this_keyframe.handle_left_type  = 'FREE'
322                            this_keyframe.handle_right_type = 'FREE'
323                        else:
324                            this_keyframe.handle_left_type  = 'ALIGNED'
325                            this_keyframe.handle_right_type = 'ALIGNED'
326
327                        this_co = mathutils.Vector(this_keyframe.co)
328                        next_co = mathutils.Vector(next_keyframe.co) if next_keyframe else None
329                        last_co = mathutils.Vector(last_keyframe.co) if last_keyframe else None
330                        if not next_keyframe:
331                            next_keyframe = fcurve.keyframe_points[0]
332                            if next_keyframe and next_keyframe != this_keyframe:
333                                next_co = mathutils.Vector(next_keyframe.co)
334                                next_co.x += max_frame
335                        if not last_keyframe:
336                            last_keyframe = fcurve.keyframe_points[len(axis_keyframes)-1]
337                            if last_keyframe and last_keyframe != this_keyframe:
338                                last_co = mathutils.Vector(last_keyframe.co)
339                                last_co.x -= max_frame
340
341                        factor = 3
342                        dist_in  = (last_co.x - this_co.x) / factor if factor and last_co else None
343                        dist_out = (next_co.x - this_co.x) / factor if factor and next_co else None
344                        if not dist_in and not dist_out:
345                            dist_in  = this_keyframe.handle_left[0]  - this_co.x
346                            dist_out = this_keyframe.handle_right[0] - this_co.x
347                        elif not dist_in:
348                            dist_in  = -dist_out
349                        elif not dist_out:
350                            dist_out = -dist_in
351
352                        this_keyframe.handle_left  = vec_in  * dist_in  + this_co
353                        this_keyframe.handle_right = vec_out * dist_out + this_co
354
355
356            if self.is_location:
357                loc_fcurves = [None, None, None]
358                loc_keyframes = [[],[],[]]
359                rna_data_path = 'pose.bones["{bone_name}"].location'.format(bone_name=bone.name)
360                for axis_index in range(0, 3):
361                    new_fcurve = fcurves.find(rna_data_path, index=axis_index)
362                    if not new_fcurve:
363                        new_fcurve = fcurves.new(rna_data_path, index=axis_index, action_group=pose_bone.name)
364                    loc_fcurves[axis_index] = new_fcurve
365                
366                def _convert_loc(loc) -> mathutils.Vector:
367                    loc = mathutils.Vector(loc) * self.scale
368                    #bone_loc = bone.head_local.copy()
369                    #
370                    #if bone.parent:
371                    #    #loc.x, loc.y, loc.z = -loc.y, -loc.x, loc.z
372                    #
373                    #    #co.x, co.y, co.z = -co.y, co.z, co.x
374                    #    #loc.x, loc.y, loc.z = loc.z, -loc.x, loc.y
375                    #    #mat = mathutils.Matrix(
376                    #    #    [( 0,  0,  1,  0), 
377                    #    #     (-1,  0,  0,  0), 
378                    #    #     ( 0,  1,  0,  0),
379                    #    #     ( 0,  0,  0,  1)]
380                    #    #)
381                    #    #loc = compat.mul(mat, loc)
382                    #
383                    #    loc = compat.convert_cm_to_bl_bone_space(loc)
384                    #
385                    #    bone_loc = bone_loc - bone.parent.head_local
386                    #    bone_loc.rotate(bone.parent.matrix_local.to_quaternion().inverted())
387                    #else:
388                    #    #loc.x, loc.y, loc.z = loc.x, loc.z, loc.y
389                    #    loc = compat.convert_cm_to_bl_space(loc)
390                    #
391                    #result_loc = loc - bone_loc
392                    if bone.parent:
393                        loc = compat.convert_cm_to_bl_bone_space(loc)
394                        loc = compat.mul(bone.parent.matrix_local, loc)
395                    else:
396                        loc = compat.convert_cm_to_bl_space(loc)
397                    return compat.mul(bone.matrix_local.inverted(), loc)
398
399                for frame, loc in locs.items():
400                    result_loc = _convert_loc(loc)
401                    #pose_bone.location = result_loc
402
403                    #pose_bone.keyframe_insert('location', frame=frame * fps, group=pose_bone.name)
404                    if max_frame < frame * fps:
405                        max_frame = frame * fps
406     
407                    for fcurve in loc_fcurves:
408                        keyframe_type = 'KEYFRAME'
409                        tangents = loc_tangents[frame]
410                        if tangents:
411                            tangents = mathutils.Vector((tangents['in'][fcurve.array_index], tangents['out'][fcurve.array_index]))
412                            if tangents.magnitude < 1e-6:
413                                keyframe_type = 'JITTER'
414                            elif tangents.magnitude > 0.1:
415                                keyframe_type = 'EXTREME'
416
417                        keyframe = fcurve.keyframe_points.insert(
418                            frame         = frame * fps                   , 
419                            value         = result_loc[fcurve.array_index], 
420                            options       = {'FAST'}                      , 
421                            keyframe_type = keyframe_type
422                        )
423                        keyframe.type = keyframe_type
424                        loc_keyframes[fcurve.array_index].append(frame)
425
426                if self.is_loop:
427                    for fcurve in loc_fcurves:
428                        new_modifier = fcurve.modifiers.new('CYCLES')
429
430                if self.is_tangents:
431                    for frame, tangents in loc_tangents.items():
432                        tangent_in  = mathutils.Vector(tangents['in' ]) * self.scale
433                        tangent_out = mathutils.Vector(tangents['out']) * self.scale
434                        if bone.parent:
435                            tangent_in  = compat.convert_cm_to_bl_bone_space(tangent_in )
436                            tangent_out = compat.convert_cm_to_bl_bone_space(tangent_out)
437                        else:
438                            tangent_in  = compat.convert_cm_to_bl_space(tangent_in )
439                            tangent_out = compat.convert_cm_to_bl_space(tangent_out)
440                        tangents['in' ][:] = tangent_in [:]
441                        tangents['out'][:] = tangent_out[:]
442
443                    _apply_tangents(loc_fcurves, loc_keyframes, loc_tangents)
444                        
445            
446            
447            if self.is_rotation:
448                quat_fcurves = [None, None, None, None]
449                quat_keyframes = [[],[],[],[]]
450                rna_data_path = 'pose.bones["{bone_name}"].rotation_quaternion'.format(bone_name=pose_bone.name)
451                for axis_index in range(0, 4):
452                    new_fcurve = fcurves.find(rna_data_path, index=axis_index)
453                    if not new_fcurve:
454                        new_fcurve = fcurves.new(rna_data_path, index=axis_index, action_group=pose_bone.name)
455                    quat_fcurves[axis_index] = new_fcurve
456
457
458                bone_quat = bone.matrix.to_quaternion()
459                def _convert_quat(quat) -> mathutils.Quaternion:
460                    quat = mathutils.Quaternion(quat)
461                    #orig_quat = quat.copy()
462                    '''Can't use matrix transforms here as they would mess up interpolation.'''
463                    if bone.parent:
464                        quat.w, quat.x, quat.y, quat.z = quat.w, -quat.z, quat.x, -quat.y
465                        #quat_mat = compat.convert_cm_to_bl_bone_space(quat.to_matrix().to_4x4())
466                        #quat_mat = compat.convert_cm_to_bl_bone_rotation(quat_mat)
467                    else:
468                        quat.w, quat.x, quat.y, quat.z = quat.w, -quat.z, quat.x, -quat.y
469                        quat = compat.mul(mathutils.Matrix.Rotation(math.radians(-90.0), 4, 'Z').to_quaternion(), quat)
470                        #quat_mat = compat.convert_cm_to_bl_space(quat.to_matrix().to_4x4())
471                        #quat = compat.convert_cm_to_bl_bone_rotation(quat_mat).to_quaternion()
472                    quat = compat.mul(bone_quat.inverted(), quat)
473                    #quat.make_compatible(orig_quat)
474                    return quat
475                        
476                for frame, quat in quats.items():
477                    result_quat = _convert_quat(quat)
478                    #pose_bone.rotation_quaternion = result_quat.copy()
479            
480                    #pose_bone.keyframe_insert('rotation_quaternion', frame=frame * fps, group=pose_bone.name)
481                    if max_frame < frame * fps:
482                        max_frame = frame * fps
483                    
484                    for fcurve in quat_fcurves:
485                        keyframe_type = 'KEYFRAME'
486                        tangents = quat_tangents[frame]
487                        if tangents:
488                            tangents = mathutils.Vector((tangents['in'][fcurve.array_index], tangents['out'][fcurve.array_index]))
489                            if tangents.magnitude < 1e-6:
490                                keyframe_type = 'JITTER'
491                            elif tangents.magnitude > 0.1:
492                                keyframe_type = 'EXTREME'
493                        
494                        keyframe = fcurve.keyframe_points.insert(
495                            frame         = frame * fps                     , 
496                            value         = result_quat[fcurve.array_index] , 
497                            options       = {'FAST'}                        , 
498                            keyframe_type = keyframe_type
499                        )
500                        keyframe.type = keyframe_type
501                        quat_keyframes[fcurve.array_index].append(frame)
502
503                if self.is_loop:
504                    for fcurve in quat_fcurves:
505                        new_modifier = fcurve.modifiers.new('CYCLES')
506                
507                if self.is_tangents:
508                    for frame, tangents in quat_tangents.items():
509                        tangents['in' ][:] = _convert_quat(tangents['in' ])[:]
510                        tangents['out'][:] = _convert_quat(tangents['out'])[:]
511
512                    _apply_tangents(quat_fcurves, quat_keyframes, quat_tangents)
513                            
514
515
516        if found_tangents:
517            self.report(type={'INFO'}, message="Found the following tangent values:")
518            for f1, f2 in found_tangents:
519                self.report(type={'INFO'}, message=f_tip_("f1 = {float1}, f2 = {float2}", float1=f1, float2=f2))
520            self.report(type={'INFO'}, message="Found the above tangent values.")  
521            self.report(type={'WARNING'}, message=f_tip_("Found {count} large tangents. Blender animation may not interpolate properly. See log for more info.", count=len(found_tangents)))  
522        if found_unknown:
523            self.report(type={'INFO'}, message="Found the following unknown channel IDs:")
524            for channel_id in found_unknown:
525                self.report(type={'INFO'}, message=f_tip_("id = {id}", id=channel_id))
526            self.report(type={'INFO'}, message="Found the above unknown channel IDs.")  
527            self.report(type={'WARNING'}, message=f_tip_("Found {count} unknown channel IDs. Blender animation may be missing some keyframes. See log for more info.", count=len(found_unknown)))
528
529        if self.set_frame:
530            context.scene.frame_start = 0
531            context.scene.frame_end = max_frame
532            context.scene.frame_set(0)
533
534        return {'FINISHED'}
bl_idname = 'import_anim.import_cm3d2_anm'
bl_label = 'CM3D2モーション (.anm)'
bl_description = 'カスタムメイド3D2のanmファイルを読み込みます'
bl_options = {'REGISTER'}
filepath: <_PropertyDeferred, <built-in function StringProperty>, {'subtype': 'FILE_PATH', 'attr': 'filepath'}> = <_PropertyDeferred, <built-in function StringProperty>, {'subtype': 'FILE_PATH', 'attr': 'filepath'}>
filename_ext = '.anm'
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': 5, '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': 5, 'min': 0.1, 'max': 100, 'soft_min': 0.1, 'soft_max': 100, 'step': 100, 'precision': 1, 'description': 'インポート時のメッシュ等の拡大率です', 'attr': 'scale'}>
set_frame_rate: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Set Framerate', 'default': True, 'description': "Change the scene's render settings to 60 fps", 'attr': 'set_frame_rate'}> = <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Set Framerate', 'default': True, 'description': "Change the scene's render settings to 60 fps", 'attr': 'set_frame_rate'}>
is_loop: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Loop', 'default': True, 'attr': 'is_loop'}> = <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Loop', 'default': True, 'attr': 'is_loop'}>
is_anm_data_text: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Anm Text', 'default': True, 'description': 'Output Data to a JSON file', 'attr': 'is_anm_data_text'}> = <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Anm Text', 'default': True, 'description': 'Output Data to a JSON file', 'attr': 'is_anm_data_text'}>
remove_pre_animation: <_PropertyDeferred, <built-in function BoolProperty>, {'name': '既にあるアニメーションを削除', 'default': True, 'attr': 'remove_pre_animation'}> = <_PropertyDeferred, <built-in function BoolProperty>, {'name': '既にあるアニメーションを削除', 'default': True, 'attr': 'remove_pre_animation'}>
set_frame: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'フレーム開始・終了位置を調整', 'default': True, 'attr': 'set_frame'}> = <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'フレーム開始・終了位置を調整', 'default': True, 'attr': 'set_frame'}>
ignore_automatic_bone: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Twisterボーンを除外', 'default': True, 'attr': 'ignore_automatic_bone'}> = <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Twisterボーンを除外', 'default': True, 'attr': 'ignore_automatic_bone'}>
is_location: <_PropertyDeferred, <built-in function BoolProperty>, {'name': '位置', 'default': True, 'attr': 'is_location'}> = <_PropertyDeferred, <built-in function BoolProperty>, {'name': '位置', 'default': True, 'attr': 'is_location'}>
is_rotation: <_PropertyDeferred, <built-in function BoolProperty>, {'name': '回転', 'default': True, 'attr': 'is_rotation'}> = <_PropertyDeferred, <built-in function BoolProperty>, {'name': '回転', 'default': True, 'attr': 'is_rotation'}>
is_scale: <_PropertyDeferred, <built-in function BoolProperty>, {'name': '拡縮', 'default': False, 'attr': 'is_scale'}> = <_PropertyDeferred, <built-in function BoolProperty>, {'name': '拡縮', 'default': False, 'attr': 'is_scale'}>
is_tangents: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Tangents', 'default': False, 'attr': 'is_tangents'}> = <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Tangents', 'default': False, 'attr': 'is_tangents'}>
@classmethod
def poll(cls, context):
43    @classmethod
44    def poll(cls, context):
45        ob = context.active_object
46        if ob and ob.type == 'ARMATURE':
47            return True
48        return False
def invoke(self, context, event):
50    def invoke(self, context, event):
51        prefs = common.preferences()
52        if prefs.anm_default_path:
53            self.filepath = common.default_cm3d2_dir(prefs.anm_default_path, None, "anm")
54        else:
55            self.filepath = common.default_cm3d2_dir(prefs.anm_import_path, None, "anm")
56        self.scale = prefs.scale
57        context.window_manager.fileselect_add(self)
58        return {'RUNNING_MODAL'}
def draw(self, context):
60    def draw(self, context):
61        self.layout.prop(self, 'scale')
62        self.layout.prop(self, 'set_frame_rate'  , icon=compat.icon('RENDER_ANIMATION'))
63        self.layout.prop(self, 'is_loop'         , icon=compat.icon('LOOP_BACK'       ))
64        self.layout.prop(self, 'is_anm_data_text', icon=compat.icon('TEXT'            ))
65
66        box = self.layout.box()
67        box.prop(self, 'remove_pre_animation', icon='DISCLOSURE_TRI_DOWN')
68        box.prop(self, 'set_frame', icon='NEXT_KEYFRAME')
69        box.prop(self, 'ignore_automatic_bone', icon='X')
70
71        box = self.layout.box()
72        box.label(text="読み込むアニメーション情報")
73        column = box.column(align=True)
74        column.prop(self, 'is_location', icon=compat.icon('CON_LOCLIKE'))
75        column.prop(self, 'is_rotation', icon=compat.icon('CON_ROTLIKE'))
76        row = column.row()
77        row.prop(self, 'is_scale', icon=compat.icon('CON_SIZELIKE'))
78        row.enabled = False
79        column.prop(self, 'is_tangents', icon=compat.icon('IPO_BEZIER' ))
def execute(self, context):
 81    def execute(self, context):
 82        prefs = common.preferences()
 83        prefs.anm_import_path = self.filepath
 84        prefs.scale = self.scale
 85
 86        try:
 87            file = open(self.filepath, 'rb')
 88        except:
 89            self.report(type={'ERROR'}, message=f_tip_("ファイルを開くのに失敗しました、アクセス不可かファイルが存在しません。file={}", self.filepath))
 90            return {'CANCELLED'}
 91
 92        # ヘッダー
 93        ext = common.read_str(file)
 94        if ext != 'CM3D2_ANIM':
 95            self.report(type={'ERROR'}, message="これはカスタムメイド3D2のモーションファイルではありません")
 96            return {'CANCELLED'}
 97        anm_version = struct.unpack('<i', file.read(4))[0]
 98        first_channel_id = struct.unpack('<B', file.read(1))[0]
 99        if first_channel_id != 1:
100            self.report(type={'ERROR'}, message=f_tip_("Unexpected first channel id = {id} (should be 1).", id=first_channel_id))
101            return {'CANCELLED'}
102
103
104        anm_data = {}
105        for anim_data_index in range(9**9):
106            path = common.read_str(file)
107            
108            base_bone_name = path.split('/')[-1]
109            if base_bone_name not in anm_data:
110                anm_data[base_bone_name] = {'path': path}
111                anm_data[base_bone_name]['channels'] = {}
112
113            for channel_index in range(9**9):
114                channel_id = struct.unpack('<B', file.read(1))[0]
115                channel_id_str = channel_id
116                if channel_id <= 1:
117                    break
118                anm_data[base_bone_name]['channels'][channel_id_str] = []
119                channel_data_count = struct.unpack('<i', file.read(4))[0]
120                for channel_data_index in range(channel_data_count):
121                    frame = struct.unpack('<f', file.read(4))[0]
122                    data = struct.unpack('<3f', file.read(4 * 3))
123
124                    anm_data[base_bone_name]['channels'][channel_id_str].append({'frame': frame, 'f0': data[0], 'f1': data[1], 'f2': data[2]})
125
126            if channel_id == 0:
127                break
128        
129        if self.is_anm_data_text:
130            if "AnmData" in context.blend_data.texts:
131                txt = context.blend_data.texts["AnmData"]
132                txt.clear()
133            else:
134                txt = context.blend_data.texts.new("AnmData")
135            import json
136            txt.write( json.dumps(anm_data, ensure_ascii=False, indent=2) )
137
138        if self.set_frame_rate:
139            context.scene.render.fps = 60
140        fps = context.scene.render.fps
141
142        ob = context.active_object
143        arm = ob.data
144        pose = ob.pose
145        base_bone = arm.get('BaseBone')
146        if base_bone:
147            base_bone = arm.bones.get(base_bone)
148
149        anim = ob.animation_data
150        if not anim:
151            anim = ob.animation_data_create()
152        action = anim.action
153        if not action:
154            action = context.blend_data.actions.new(os.path.basename(self.filepath))
155            anim.action = action
156            fcurves = action.fcurves
157        else:
158            action.name = os.path.basename(self.filepath)
159            fcurves = action.fcurves
160            if self.remove_pre_animation:
161                for fcurve in fcurves:
162                    fcurves.remove(fcurve)
163
164        max_frame = 0
165        bpy.ops.object.mode_set(mode='OBJECT')
166        found_unknown = []
167        found_tangents = []
168        for bone_name, bone_data in anm_data.items():
169            if self.ignore_automatic_bone:
170                if re.match(r"Kata_[RL]", bone_name):
171                    continue
172                if re.match(r"Uppertwist1_[RL]", bone_name):
173                    continue
174                if re.match(r"momoniku_[RL]", bone_name):
175                    continue
176
177            if bone_name not in pose.bones:
178                bone_name = common.decode_bone_name(bone_name)
179                if bone_name not in pose.bones:
180                    continue
181            bone = arm.bones[bone_name]
182            pose_bone = pose.bones[bone_name]
183
184            loc_fcurves  = None
185
186            locs = {}
187            loc_tangents = {}
188            quats = {}
189            quat_tangents = {}
190            for channel_id, channel_data in bone_data['channels'].items():
191
192                if channel_id in [100, 101, 102, 103]:
193                    for data in channel_data:
194                        frame = data['frame']
195                        if frame not in quats:
196                            quats[frame] = [None, None, None, None]
197
198                        if channel_id == 103:
199                            quats[frame][0] = data['f0']
200                        elif channel_id == 100:
201                            quats[frame][1] = data['f0']
202                        elif channel_id == 101:
203                            quats[frame][2] = data['f0']
204                        elif channel_id == 102:
205                            quats[frame][3] = data['f0']
206
207                        #tangents = (data['f1'], data['f2'])
208                        #if (data['f1']**2 + data['f2']**2) ** .5 > 0.01:
209                        #    found_tangents.append(tangents)
210                        if frame not in quat_tangents:
211                            quat_tangents[frame] = {'in': [None, None, None, None], 'out': [None, None, None, None]}
212
213                        if channel_id == 103:
214                            quat_tangents[frame]['in' ][0] = data['f1']
215                            quat_tangents[frame]['out'][0] = data['f2']
216                        elif channel_id == 100:                        
217                            quat_tangents[frame]['in' ][1] = data['f1']
218                            quat_tangents[frame]['out'][1] = data['f2']
219                        elif channel_id == 101:                        
220                            quat_tangents[frame]['in' ][2] = data['f1']
221                            quat_tangents[frame]['out'][2] = data['f2']
222                        elif channel_id == 102:              
223                            quat_tangents[frame]['in' ][3] = data['f1']
224                            quat_tangents[frame]['out'][3] = data['f2']
225
226                elif channel_id in [104, 105, 106]:
227                    for data in channel_data:
228                        frame = data['frame']
229                        if frame not in locs:
230                            locs[frame] = [None, None, None]
231
232                        if channel_id == 104:
233                            locs[frame][0] = data['f0']
234                        elif channel_id == 105:
235                            locs[frame][1] = data['f0']
236                        elif channel_id == 106:
237                            locs[frame][2] = data['f0']
238                        
239                        #tangents = (data['f1'], data['f2'])
240                        #if (data['f1']**2 + data['f2']**2) ** .5 > 0.05:
241                        #    found_tangents.append(tangents)
242                        if frame not in loc_tangents:
243                            loc_tangents[frame] = {'in': [None, None, None], 'out': [None, None, None]}
244
245                        if channel_id == 104:
246                            loc_tangents[frame]['in' ][0] = data['f1']
247                            loc_tangents[frame]['out'][0] = data['f2']
248                        elif channel_id == 105:                       
249                            loc_tangents[frame]['in' ][1] = data['f1']
250                            loc_tangents[frame]['out'][1] = data['f2']
251                        elif channel_id == 106:                       
252                            loc_tangents[frame]['in' ][2] = data['f1']
253                            loc_tangents[frame]['out'][2] = data['f2']
254
255                elif channel_id not in found_unknown:
256                    found_unknown.append(channel_id)
257                    self.report(type={'INFO'}, message=f_tip_("Unknown channel id {num}", num=channel_id))
258
259            '''
260            for frame, (loc, quat) in enumerate(zip(locs.values(), quats.values())):
261                loc  = mathutils.Vector(loc) * self.scale
262                quat = mathutils.Quaternion(quat)
263            
264                loc_mat = mathutils.Matrix.Translation(loc).to_4x4()
265                rot_mat = quat.to_matrix().to_4x4()
266                mat     = compat.mul(loc_mat, rot_mat)
267                
268                bone_loc  = bone.head_local.copy()
269                bone_quat = bone.matrix.to_quaternion()
270            
271                if bone.parent:
272                    parent = bone.parent
273                else:
274                    parent = base_bone
275                    
276                if parent:
277                    mat = compat.convert_cm_to_bl_bone_space(mat)
278                    mat = compat.mul(parent.matrix_local, mat)
279                    mat = compat.convert_cm_to_bl_bone_rotation(mat)
280                    pose_mat = bone.convert_local_to_pose(
281                        matrix              = mat, 
282                        matrix_local        = bone.matrix_local,
283                        parent_matrix       = mathutils.Matrix.Identity(4),
284                        parent_matrix_local = parent.matrix_local
285                    )
286                else:
287                    mat = compat.convert_cm_to_bl_bone_rotation(mat)
288                    mat = compat.convert_cm_to_bl_space(mat)
289                    pose_mat = bone.convert_local_to_pose(
290                        matrix       = mat, 
291                        matrix_local = bone.matrix_local
292                    )
293            
294                if self.is_location:
295                    pose_bone.location = pose_mat.to_translation()
296                    pose_bone.keyframe_insert('location'           , frame=frame * fps, group=pose_bone.name)
297                if self.is_rotation:
298                    pose_bone.rotation_quaternion = pose_mat.to_quaternion()
299                    pose_bone.keyframe_insert('rotation_quaternion', frame=frame * fps, group=pose_bone.name)
300                if max_frame < frame * fps:
301                    max_frame = frame * fps
302            '''            
303            
304            def _apply_tangents(fcurves, keyframes, tangents):
305                for axis_index, axis_keyframes in enumerate(keyframes):
306                    fcurve = fcurves[axis_index]
307                    fcurve.update() # make sure automatic handles are calculated
308                    axis_keyframes.sort() # make sure list is in order
309                    for keyframe_index, frame in enumerate(axis_keyframes):
310                        tangent_in  = tangents[frame]['in' ][axis_index]
311                        tangent_out = tangents[frame]['out'][axis_index]
312
313                        vec_in   = mathutils.Vector((1, tangent_in  / fps))   
314                        vec_out  = mathutils.Vector((1, tangent_out / fps))
315
316                        this_keyframe = fcurve.keyframe_points[keyframe_index  ]
317                        next_keyframe = fcurve.keyframe_points[keyframe_index+1] if keyframe_index+1 < len(axis_keyframes) else None
318                        last_keyframe = fcurve.keyframe_points[keyframe_index-1] if keyframe_index-1 >= 0                  else None
319                        
320                        if vec_in.y != vec_out.y:
321                            this_keyframe.handle_left_type  = 'FREE'
322                            this_keyframe.handle_right_type = 'FREE'
323                        else:
324                            this_keyframe.handle_left_type  = 'ALIGNED'
325                            this_keyframe.handle_right_type = 'ALIGNED'
326
327                        this_co = mathutils.Vector(this_keyframe.co)
328                        next_co = mathutils.Vector(next_keyframe.co) if next_keyframe else None
329                        last_co = mathutils.Vector(last_keyframe.co) if last_keyframe else None
330                        if not next_keyframe:
331                            next_keyframe = fcurve.keyframe_points[0]
332                            if next_keyframe and next_keyframe != this_keyframe:
333                                next_co = mathutils.Vector(next_keyframe.co)
334                                next_co.x += max_frame
335                        if not last_keyframe:
336                            last_keyframe = fcurve.keyframe_points[len(axis_keyframes)-1]
337                            if last_keyframe and last_keyframe != this_keyframe:
338                                last_co = mathutils.Vector(last_keyframe.co)
339                                last_co.x -= max_frame
340
341                        factor = 3
342                        dist_in  = (last_co.x - this_co.x) / factor if factor and last_co else None
343                        dist_out = (next_co.x - this_co.x) / factor if factor and next_co else None
344                        if not dist_in and not dist_out:
345                            dist_in  = this_keyframe.handle_left[0]  - this_co.x
346                            dist_out = this_keyframe.handle_right[0] - this_co.x
347                        elif not dist_in:
348                            dist_in  = -dist_out
349                        elif not dist_out:
350                            dist_out = -dist_in
351
352                        this_keyframe.handle_left  = vec_in  * dist_in  + this_co
353                        this_keyframe.handle_right = vec_out * dist_out + this_co
354
355
356            if self.is_location:
357                loc_fcurves = [None, None, None]
358                loc_keyframes = [[],[],[]]
359                rna_data_path = 'pose.bones["{bone_name}"].location'.format(bone_name=bone.name)
360                for axis_index in range(0, 3):
361                    new_fcurve = fcurves.find(rna_data_path, index=axis_index)
362                    if not new_fcurve:
363                        new_fcurve = fcurves.new(rna_data_path, index=axis_index, action_group=pose_bone.name)
364                    loc_fcurves[axis_index] = new_fcurve
365                
366                def _convert_loc(loc) -> mathutils.Vector:
367                    loc = mathutils.Vector(loc) * self.scale
368                    #bone_loc = bone.head_local.copy()
369                    #
370                    #if bone.parent:
371                    #    #loc.x, loc.y, loc.z = -loc.y, -loc.x, loc.z
372                    #
373                    #    #co.x, co.y, co.z = -co.y, co.z, co.x
374                    #    #loc.x, loc.y, loc.z = loc.z, -loc.x, loc.y
375                    #    #mat = mathutils.Matrix(
376                    #    #    [( 0,  0,  1,  0), 
377                    #    #     (-1,  0,  0,  0), 
378                    #    #     ( 0,  1,  0,  0),
379                    #    #     ( 0,  0,  0,  1)]
380                    #    #)
381                    #    #loc = compat.mul(mat, loc)
382                    #
383                    #    loc = compat.convert_cm_to_bl_bone_space(loc)
384                    #
385                    #    bone_loc = bone_loc - bone.parent.head_local
386                    #    bone_loc.rotate(bone.parent.matrix_local.to_quaternion().inverted())
387                    #else:
388                    #    #loc.x, loc.y, loc.z = loc.x, loc.z, loc.y
389                    #    loc = compat.convert_cm_to_bl_space(loc)
390                    #
391                    #result_loc = loc - bone_loc
392                    if bone.parent:
393                        loc = compat.convert_cm_to_bl_bone_space(loc)
394                        loc = compat.mul(bone.parent.matrix_local, loc)
395                    else:
396                        loc = compat.convert_cm_to_bl_space(loc)
397                    return compat.mul(bone.matrix_local.inverted(), loc)
398
399                for frame, loc in locs.items():
400                    result_loc = _convert_loc(loc)
401                    #pose_bone.location = result_loc
402
403                    #pose_bone.keyframe_insert('location', frame=frame * fps, group=pose_bone.name)
404                    if max_frame < frame * fps:
405                        max_frame = frame * fps
406     
407                    for fcurve in loc_fcurves:
408                        keyframe_type = 'KEYFRAME'
409                        tangents = loc_tangents[frame]
410                        if tangents:
411                            tangents = mathutils.Vector((tangents['in'][fcurve.array_index], tangents['out'][fcurve.array_index]))
412                            if tangents.magnitude < 1e-6:
413                                keyframe_type = 'JITTER'
414                            elif tangents.magnitude > 0.1:
415                                keyframe_type = 'EXTREME'
416
417                        keyframe = fcurve.keyframe_points.insert(
418                            frame         = frame * fps                   , 
419                            value         = result_loc[fcurve.array_index], 
420                            options       = {'FAST'}                      , 
421                            keyframe_type = keyframe_type
422                        )
423                        keyframe.type = keyframe_type
424                        loc_keyframes[fcurve.array_index].append(frame)
425
426                if self.is_loop:
427                    for fcurve in loc_fcurves:
428                        new_modifier = fcurve.modifiers.new('CYCLES')
429
430                if self.is_tangents:
431                    for frame, tangents in loc_tangents.items():
432                        tangent_in  = mathutils.Vector(tangents['in' ]) * self.scale
433                        tangent_out = mathutils.Vector(tangents['out']) * self.scale
434                        if bone.parent:
435                            tangent_in  = compat.convert_cm_to_bl_bone_space(tangent_in )
436                            tangent_out = compat.convert_cm_to_bl_bone_space(tangent_out)
437                        else:
438                            tangent_in  = compat.convert_cm_to_bl_space(tangent_in )
439                            tangent_out = compat.convert_cm_to_bl_space(tangent_out)
440                        tangents['in' ][:] = tangent_in [:]
441                        tangents['out'][:] = tangent_out[:]
442
443                    _apply_tangents(loc_fcurves, loc_keyframes, loc_tangents)
444                        
445            
446            
447            if self.is_rotation:
448                quat_fcurves = [None, None, None, None]
449                quat_keyframes = [[],[],[],[]]
450                rna_data_path = 'pose.bones["{bone_name}"].rotation_quaternion'.format(bone_name=pose_bone.name)
451                for axis_index in range(0, 4):
452                    new_fcurve = fcurves.find(rna_data_path, index=axis_index)
453                    if not new_fcurve:
454                        new_fcurve = fcurves.new(rna_data_path, index=axis_index, action_group=pose_bone.name)
455                    quat_fcurves[axis_index] = new_fcurve
456
457
458                bone_quat = bone.matrix.to_quaternion()
459                def _convert_quat(quat) -> mathutils.Quaternion:
460                    quat = mathutils.Quaternion(quat)
461                    #orig_quat = quat.copy()
462                    '''Can't use matrix transforms here as they would mess up interpolation.'''
463                    if bone.parent:
464                        quat.w, quat.x, quat.y, quat.z = quat.w, -quat.z, quat.x, -quat.y
465                        #quat_mat = compat.convert_cm_to_bl_bone_space(quat.to_matrix().to_4x4())
466                        #quat_mat = compat.convert_cm_to_bl_bone_rotation(quat_mat)
467                    else:
468                        quat.w, quat.x, quat.y, quat.z = quat.w, -quat.z, quat.x, -quat.y
469                        quat = compat.mul(mathutils.Matrix.Rotation(math.radians(-90.0), 4, 'Z').to_quaternion(), quat)
470                        #quat_mat = compat.convert_cm_to_bl_space(quat.to_matrix().to_4x4())
471                        #quat = compat.convert_cm_to_bl_bone_rotation(quat_mat).to_quaternion()
472                    quat = compat.mul(bone_quat.inverted(), quat)
473                    #quat.make_compatible(orig_quat)
474                    return quat
475                        
476                for frame, quat in quats.items():
477                    result_quat = _convert_quat(quat)
478                    #pose_bone.rotation_quaternion = result_quat.copy()
479            
480                    #pose_bone.keyframe_insert('rotation_quaternion', frame=frame * fps, group=pose_bone.name)
481                    if max_frame < frame * fps:
482                        max_frame = frame * fps
483                    
484                    for fcurve in quat_fcurves:
485                        keyframe_type = 'KEYFRAME'
486                        tangents = quat_tangents[frame]
487                        if tangents:
488                            tangents = mathutils.Vector((tangents['in'][fcurve.array_index], tangents['out'][fcurve.array_index]))
489                            if tangents.magnitude < 1e-6:
490                                keyframe_type = 'JITTER'
491                            elif tangents.magnitude > 0.1:
492                                keyframe_type = 'EXTREME'
493                        
494                        keyframe = fcurve.keyframe_points.insert(
495                            frame         = frame * fps                     , 
496                            value         = result_quat[fcurve.array_index] , 
497                            options       = {'FAST'}                        , 
498                            keyframe_type = keyframe_type
499                        )
500                        keyframe.type = keyframe_type
501                        quat_keyframes[fcurve.array_index].append(frame)
502
503                if self.is_loop:
504                    for fcurve in quat_fcurves:
505                        new_modifier = fcurve.modifiers.new('CYCLES')
506                
507                if self.is_tangents:
508                    for frame, tangents in quat_tangents.items():
509                        tangents['in' ][:] = _convert_quat(tangents['in' ])[:]
510                        tangents['out'][:] = _convert_quat(tangents['out'])[:]
511
512                    _apply_tangents(quat_fcurves, quat_keyframes, quat_tangents)
513                            
514
515
516        if found_tangents:
517            self.report(type={'INFO'}, message="Found the following tangent values:")
518            for f1, f2 in found_tangents:
519                self.report(type={'INFO'}, message=f_tip_("f1 = {float1}, f2 = {float2}", float1=f1, float2=f2))
520            self.report(type={'INFO'}, message="Found the above tangent values.")  
521            self.report(type={'WARNING'}, message=f_tip_("Found {count} large tangents. Blender animation may not interpolate properly. See log for more info.", count=len(found_tangents)))  
522        if found_unknown:
523            self.report(type={'INFO'}, message="Found the following unknown channel IDs:")
524            for channel_id in found_unknown:
525                self.report(type={'INFO'}, message=f_tip_("id = {id}", id=channel_id))
526            self.report(type={'INFO'}, message="Found the above unknown channel IDs.")  
527            self.report(type={'WARNING'}, message=f_tip_("Found {count} unknown channel IDs. Blender animation may be missing some keyframes. See log for more info.", count=len(found_unknown)))
528
529        if self.set_frame:
530            context.scene.frame_start = 0
531            context.scene.frame_end = max_frame
532            context.scene.frame_set(0)
533
534        return {'FINISHED'}
bl_rna = <bpy_struct, Struct("IMPORT_ANIM_OT_import_cm3d2_anm")>
Inherited Members
bpy_types.Operator
as_keywords
poll_message_set
builtins.bpy_struct
keys
values
items
get
pop
as_pointer
keyframe_insert
keyframe_delete
driver_add
driver_remove
is_property_set
property_unset
is_property_hidden
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