summaryrefslogtreecommitdiff
path: root/bck_import.py
blob: 398f18d7e8ef641d5284bf35cd718eed0573035b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
# "simple" importer for BCK anim files from SMG
# file format information --> https://humming-owl.neocities.org/smg-stuff/pages/tutorials/bck

import bpy, math
from . import blender_funcs
from . import bck_funcs
from . import file_ops
from . import math_funcs
import mathutils

# import BCK animation into the selected armature object
# creates a new "action" and writes the data into that action slot
def import_bck_func(context, options):   
#  
  # this thing is always needed for stuff
  scene = bpy.context.scene
  
  # if no armature is selected end the importer
  if (scene.objects.active == None or scene.objects.active.type != 'ARMATURE'):
    if (scene.objects.active == None):
      blender_funcs.disp_msg("No Armature selected. Select one and try again.")
    else:
      blender_funcs.disp_msg("No Armature selected. Currently selecting: \"%s\""
                             % (scene.objects.active.name))
    return {"FINISHED"}
  
  # open the binary file and read its data
  anim = bck_funcs.read_bck_file(options.filepath)
  print(anim)
  if (anim == None):
    blender_funcs.disp_msg("Animation file: \"%s\" is malformed."
                           % (file_ops.get_file_name(options.filepath)))
    return {"FINISHED"}
  
  # select the armature object
  armature = scene.objects.active
  # change to object mode
  blender_funcs.select_obj(armature, False, "OBJECT")
  
  # check if the bone count matches (the only check it can be done)
  if (len(armature.data.bones) != anim.bone_count):
    blender_funcs.disp_msg("Animation file \"%s\" contains incorrect number of bones"
                           % (file_ops.get_file_name(options.filepath)))
    return {"FINISHED"}
  
  # check import_type to know what to do
  file_name = file_ops.get_file_name(options.filepath)
  
  # change the bones's rotation mode to match the rotation mode going to be imported?
  # (thing to think later)
  
  # clear rest pose and import the animation data directly
  if (options.import_type == "OPT_A"):
  #
    # select the armature object and its children meshes and duplicate it
    old_armature = armature
    blender_funcs.duplicate_obj(scene, armature)
    armature = scene.objects.active
    armature.name = old_armature.name + "_cp"
    
    # clear the rest pose of the object (pose mode)
    blender_funcs.select_obj(armature, False, "POSE")
    # unlink any actions in the armature and clear the pose
    armature.animation_data.action = None
    bpy.ops.pose.select_all(action = "SELECT")
    bpy.ops.pose.transforms_clear()
    for bone in armature.pose.bones:
      if (bone.parent != None):
        # current bone mat = (first parent mat) * (current bone rest mat)
        # first parent mat = (second parent mat) * (first parent bone rest mat)
        # second parent mat = (third parent mat) * (second parent bone rest mat)
        # ...
        rest_mat = bone.parent.matrix.copy().inverted() * bone.matrix.copy()
        bone.matrix = bone.matrix.copy() * rest_mat.copy().inverted()
      else: # Y up consideration on the very first bone (has no parents)
        bone.matrix = math_funcs.calc_rotation_matrix(math.pi / 2, 0, 0) * mathutils.Matrix.Identity(4)
    
    # apply visual transform to all meshes
    # (could the error this makes be fixed? question for future me)
    for child in armature.children:
      if (child.type == "MESH"): # only to meshes
        blender_funcs.select_obj(child, False, "OBJECT")
        bpy.ops.object.convert(target = "MESH")
    
    # apply pose as rest pose
    blender_funcs.select_obj(armature, False, "POSE")
    bpy.ops.pose.armature_apply()
    
    # reassign the armature modifiers to all meshes
    for mesh in armature.children:
      blender_funcs.select_obj(mesh, False, "OBJECT")
      bpy.ops.object.modifier_add(type = "ARMATURE")
      mesh.modifiers["Armature"].object = armature
      mesh.modifiers["Armature"].use_vertex_groups = True
    
    # select the armature again
    blender_funcs.select_obj(armature, False, "OBJECT")
    
    # create a new animation action for the skeleton
    
    # if it already exists, eliminate it and create a new one with the same name
    for action in bpy.data.actions:
      if (action.name == file_name):
        bpy.data.actions.remove(action)
        break
    action = bpy.data.actions.new(name = file_name)
    # armature has no animations, create new one, assign created action to it
    if (armature.animation_data == None):
      armature.animation_data_create()
    armature.animation_data.action = action
    
    # find the lowest and greatest value for the time in the bck animation
    lowest_anim_frame = 0
    for i in range(anim.bone_count):
      for j in range(9):
        if (anim.anim_data[i].comp[j].time[0] != None):
          if (anim.anim_data[i].comp[j].time[0] < lowest_anim_frame):
            lowest_anim_frame = anim.anim_data[i].comp[j].time[0]
    greatest_anim_frame = 0
    for i in range(anim.bone_count):
      for j in range(9):
        for k in range(anim.anim_data[i].comp[j].kf_count):
          if (anim.anim_data[i].comp[j].time[k] != None
              and anim.anim_data[i].comp[j].time[k] > greatest_anim_frame):
            greatest_anim_frame = anim.anim_data[i].comp[j].time[k]
    
    # assign the frame_start and frame_end blender scene variables
    scene.frame_preview_start = lowest_anim_frame
    scene.frame_current = lowest_anim_frame
    scene.frame_preview_end = greatest_anim_frame
    
    # change the rotation mode of the pose bones
    for bone in armature.pose.bones:
      bone.rotation_mode = "XYZ"
    
    # get the animation data into all the bones
    for i in range(anim.bone_count): 
      # the bone and its animation data
      bone = armature.data.bones[i] # "data.bones" contains the right index order
      bone_anim = anim.anim_data[i]
      for j in range(9): # scale/rot/transl x/y/z
        comp_string = None
        comp_index = None
        if (j == 0 or j == 3 or j == 6): # scale
          comp_string = "scale"
          comp_index = int(j / 3)
        elif (j == 1 or j == 4 or j == 7): # rot
          comp_string = "rotation_euler"
          comp_index = int((j - 1) / 3)
        elif (j == 2 or j == 5 or j == 8): # transl
          comp_string = "location"
          comp_index = int((j - 2) / 3)
        
        # create the component fcurve
        data_path = "pose.bones[\"%s\"].%s" % (bone.name, comp_string)
        fcurve = action.fcurves.new(data_path, comp_index, bone.name)
        
        # assign the keyframes
        for k in range(bone_anim.comp[j].kf_count):
          # convert translation values to blenxy's coordinate space
          comp_value = bone_anim.comp[j].value[k]
          if (j == 2 or j == 5 or j == 8):
            comp_value /= 100
          # create the keyframe
          if (bone_anim.comp[j].time[k] == None): # consider 1 keyframe animation tracks
            fcurve.keyframe_points.insert(lowest_anim_frame, comp_value)
          else:
            fcurve.keyframe_points.insert(bone_anim.comp[j].time[k], comp_value)
            
        # adjust slopes (in one keyframe point look left and right)
        for k in range(bone_anim.comp[j].kf_count):
          
          # change the keyframe point handles type
          fcurve.keyframe_points[k].handle_left_type = "FREE"
          fcurve.keyframe_points[k].handle_right_type = "FREE" 
          
          # transform the translation into the right system
          comp_in_slope = bone_anim.comp[j].in_slope[k]
          comp_out_slope = bone_anim.comp[j].out_slope[k]
          if (j == 2 or j == 5 or j == 8):
            if (bone_anim.comp[j].kf_count > 1):
              comp_in_slope = comp_in_slope / 100
              comp_out_slope = comp_out_slope / 100
          
          # in slope, assign in-slopes only to keyframes after the first one
          if (k != 0):
            # get the time difference between this frame and the previous one
            time_dif = fcurve.keyframe_points[k].co[0] - fcurve.keyframe_points[k - 1].co[0]
            # compute the in slope vector angle
            x_comp = -(time_dif / 3)
            y_comp = x_comp * comp_in_slope
            vec_slope = fcurve.keyframe_points[k].co.copy() + mathutils.Vector((x_comp, y_comp))
            fcurve.keyframe_points[k].handle_left = vec_slope
          
          # out slope, assign out-slopes only to keyframes before the last one
          if (k != bone_anim.comp[j].kf_count - 1):
            # get the time difference between this frame and the next one
            time_dif = fcurve.keyframe_points[k + 1].co[0] - fcurve.keyframe_points[k].co[0]
            # compute the out slope vector angle
            x_comp = time_dif / 3
            y_comp = x_comp * comp_out_slope
            vec_slope = fcurve.keyframe_points[k].co.copy() + mathutils.Vector((x_comp, y_comp))
            fcurve.keyframe_points[k].handle_right = vec_slope
  
  # mantain rest pose and import the animation data respect to rest pose
  # "OPT_B" --> sample everything
  # "OPT_C" --> sample everything and find "best" interpolator fit
  elif (options.import_type == "OPT_B" or options.import_type == "OPT_C"):
    
    # its matrix time
    
    # create a new animation action for the skeleton
    # if it already exists, eliminate it and create a new one with the same name
    for action in bpy.data.actions:
      if (action.name == file_name):
        bpy.data.actions.remove(action)
        break
    action = bpy.data.actions.new(name = file_name)
    # armature has no animations, create new one, assign created action to it
    if (armature.animation_data == None):
      armature.animation_data_create()
    armature.animation_data.action = action
    
    # find the lowest and greatest value for the time in the bck animation
    lowest_anim_frame = 0
    greatest_anim_frame = 0
    for i in range(anim.bone_count):
      for j in range(9):
        if (anim.anim_data[i].comp[j].kf_count > 1):
          if (anim.anim_data[i].comp[j].time[0] < lowest_anim_frame):
            lowest_anim_frame = anim.anim_data[i].comp[j].time[0]
          if (anim.anim_data[i].comp[j].time[-1] > greatest_anim_frame):
            greatest_anim_frame = anim.anim_data[i].comp[j].time[-1]
    
    # round with ceil (hopefully the best choice? might think about this later)
    lowest_anim_frame = math.ceil(lowest_anim_frame)
    greatest_anim_frame = math.ceil(greatest_anim_frame)
    
    # assign the frame_start and frame_end blender scene variables
    scene.frame_preview_start = lowest_anim_frame
    scene.frame_current = lowest_anim_frame
    scene.frame_preview_end = greatest_anim_frame
    
    # change the rotation mode of the pose bones
    for pose_bone in armature.pose.bones:
      pose_bone.rotation_mode = "XYZ"
    
    # get the animation data into all the bones
    for i in range(anim.bone_count): 
      # the bone and its animation data
      data_bone = armature.data.bones[i] # "data.bones" contains the right index order
      pose_bone = armature.pose.bones[data_bone.name] # pose bones contain the data from the rest pose
      bone_anim = anim.anim_data[i]
      
      # get the transformation matrix from parent to child (rest mat)
      # do not use the matrix from pose bones because that is the one that will be animated
      rest_mat = None
      if (pose_bone.parent != None):
        # current bone mat = (first parent mat) * (current bone rest mat)
        # first parent mat = (second parent mat) * (first parent bone rest mat)
        # second parent mat = (third parent mat) * (second parent bone rest mat)
        # ...
        rest_mat = data_bone.parent.matrix_local.copy().inverted() * data_bone.matrix_local.copy()
      else: # Y up consideration on the very first bone (has no parents)
        rest_mat = mathutils.Matrix.Identity(4)
      
      # need to include keyframes not for each component but rather for each of the XYZ groups
      # the coordinate system change can turn a simple X translation into a XYZ translation in the other system
      
      # get the last animation frame in which values actually change
      first_anim_frame = 0
      last_anim_frame = 0
      for j in range(9):
        # one keyframe animations skipped
        if (bone_anim.comp[j].kf_count > 1):
          if (bone_anim.comp[j].time[0] < first_anim_frame):
            first_anim_frame = bone_anim.comp[j].time[0]
          if (bone_anim.comp[j].time[-1] > last_anim_frame):
            last_anim_frame = bone_anim.comp[j].time[-1]
      # will adjust the keyframe times to be integers
      # the method (for now) is to round to the nearest integer with math.ceil
      last_anim_frame = math.ceil(last_anim_frame)
      print("first and last keyframe frames: %s %s" % (first_anim_frame, last_anim_frame))
      
      # iterate over the actual animation frames
      # get the initial animation values
      scale  = [bone_anim.comp[0].value[0], bone_anim.comp[3].value[0], bone_anim.comp[6].value[0]]
      rot    = [bone_anim.comp[1].value[0], bone_anim.comp[4].value[0], bone_anim.comp[7].value[0]]
      transl = [bone_anim.comp[2].value[0] / 100,
                bone_anim.comp[5].value[0] / 100,
                bone_anim.comp[8].value[0] / 100]
      # declare the current bone's fcurves list
      fcurves = [None, None, None, # scale XYZ
                 None, None, None, # rot XYZ
                 None, None, None] # transl XYZ
      
      # iterate over the frames of the animation      
      for j in range(lowest_anim_frame, last_anim_frame + 1):
        
        # check which keyframe components need to be added
        is_scale_anim = False
        is_rot_anim = False
        is_transl_anim = False
        new_scale = None
        new_rot = None
        new_transl = None
        
        # get the animation data for the current bone
        
        # get the animation transformation matrix
        if (j == lowest_anim_frame): # all the animation components on the first frame are set
          is_scale_anim = True
          is_rot_anim = True
          is_transl_anim = True
          # create the components fcurves (all of them)
          data_path = "pose.bones[\"%s\"].scale" % (data_bone.name)
          fcurves[0] = action.fcurves.new(data_path, 0, data_bone.name)
          fcurves[1] = action.fcurves.new(data_path, 1, data_bone.name)
          fcurves[2] = action.fcurves.new(data_path, 2, data_bone.name)
          data_path = "pose.bones[\"%s\"].rotation_euler" % (data_bone.name)
          fcurves[3] = action.fcurves.new(data_path, 0, data_bone.name)
          fcurves[4] = action.fcurves.new(data_path, 1, data_bone.name)
          fcurves[5] = action.fcurves.new(data_path, 2, data_bone.name)
          data_path = "pose.bones[\"%s\"].location" % (data_bone.name)
          fcurves[6] = action.fcurves.new(data_path, 0, data_bone.name)
          fcurves[7] = action.fcurves.new(data_path, 1, data_bone.name)
          fcurves[8] = action.fcurves.new(data_path, 2, data_bone.name)
        
        # for any other frame
        else:          
          # check if the bone has more frames to animate in the 9 components
          for k in range(9):            
            # interpolation result
            result = 0            
            # 1 keyframe animations are skipped
            if (bone_anim.comp[k].kf_count == 1):
              continue            
            # iterate over the keyframes on the component
            for l in range(bone_anim.comp[k].kf_count):
              # frame is exactly the last keyframe of the component
              if (l == bone_anim.comp[k].kf_count - 1):
                result = bone_anim.comp[k].value[-1]
                break
              # frame to convert is in between the keyframes
              elif (j >= bone_anim.comp[k].time[l]
                    and j < bone_anim.comp[k].time[l + 1]):
                # interpolate to find the result and add it as a new keyframe
                p0 = bone_anim.comp[k].value[l]
                m0 = bone_anim.comp[k].out_slope[l]
                m1 = bone_anim.comp[k].in_slope[l + 1]
                p1 = bone_anim.comp[k].value[l + 1]
                t0 = bone_anim.comp[k].time[l]
                tf = bone_anim.comp[k].time[l + 1]
                t  = j
                result = math_funcs.cubic_hermite_spline_time_general(p0, m0, m1, p1, t0, tf, t)
                break
              # animation for this particular component has ended
              elif (j > bone_anim.comp[k].time[-1]):
                result = None
                break
            
            # ~ print("interpolation result: %s" % (result))                
            # assign to the respective component
            if (result != None):
              if (k == 0 or k == 3 or k == 6): # scale
                is_scale_anim = True
                scale[int((k - 0) / 3)] = result
              elif (k == 1 or k == 4 or k == 7): # rot
                is_rot_anim = True
                rot[int((k - 1) / 3)] = result
              elif (k == 2 or k == 5 or k == 8): # transl
                is_transl_anim = True
                transl[int((k - 2) / 3)] = result / 100
          
        # ~ print("frame S R T: %d %s %s %s" %(j, is_scale_anim, is_rot_anim, is_transl_anim))
        # ~ print(scale)
        # ~ print(rot)
        # ~ print(transl)
        # convert the just got frame anim values to be rest pose relative
        mat = rest_mat.inverted() * math_funcs.calc_transf_mat(scale, rot, transl,
                                                               options.euler_mode,
                                                               options.mult_order)
        # extract the new animation data 
        new_scale = mat.to_scale()
        new_rot = mat.to_euler(options.euler_mode)
        new_transl = mat.to_translation()
                                
        # check if new data must be added
        if (is_scale_anim == False
            and is_rot_anim == False
            and is_transl_anim == False):
          break
        
        # assign the keyframes were needed and set the bezier handles to 0            
        if (is_scale_anim == True):
          for k in range(3):
            tmp = fcurves[k].keyframe_points.insert(j, new_scale[k])
            tmp.handle_left_type = "FREE"
            tmp.handle_right_type = "FREE"
            tmp.handle_left = tmp.co
            tmp.handle_right = tmp.co          
        if (is_rot_anim == True):
          for k in range(3):
            tmp = fcurves[3 + k].keyframe_points.insert(j, new_rot[k])
            tmp.handle_left_type = "FREE"
            tmp.handle_right_type = "FREE"
            tmp.handle_left = tmp.co
            tmp.handle_right = tmp.co  
        if (is_transl_anim == True):
          for k in range(3):
            tmp = fcurves[6 + k].keyframe_points.insert(j, new_transl[k])
            tmp.handle_left_type = "FREE"
            tmp.handle_right_type = "FREE"
            tmp.handle_left = tmp.co
            tmp.handle_right = tmp.co  
            
        # reset the just checked variables for the next frame
        is_scale_anim = False
        is_rot_anim = False
        is_transl_anim = False
    
      # remove the fcurve keyframe points that are constant (some cleanup by yours truly)
      # don't want to use blender's built-in action.clean() method cuz it resets the bezier handles
      for fcurve in action.fcurves:
        if (len(fcurve.keyframe_points) == 1): # nothing to clean (are you sure about that?)
          continue
        else:
          # check if a keyframe has the same value as its 2 closest keyframes
          old_value = None
          new_value = None
          j = 0
          while (j < len(fcurve.keyframe_points)):
            if (fcurve.keyframe_points[j].co[0] == lowest_anim_frame):
              old_value = fcurve.keyframe_points[j].co[1]
            else:
              cur_value = fcurve.keyframe_points[j].co[1]
              # remove the current keyframe if the next one is the same
              # or if it is the last keyframe on the fcurve
              if (cur_value == old_value):
                if ((j < len(fcurve.keyframe_points) - 1 and cur_value == fcurve.keyframe_points[j + 1].co[1])
                    or (j == len(fcurve.keyframe_points) - 1)):
                  fcurve.keyframe_points.remove(fcurve.keyframe_points[j])
                  j -= 1
              old_value = cur_value
            j += 1
    
    # now, if this has not been hard enough...
    # find the "best" interpolator fits to be able to simplify the keyframe count
    if (options.import_type == "OPT_C"):      
      # lets start
      for fcurve in action.fcurves:
        # skip 1 frame animations
        if (len(fcurve.keyframe_points) == 1):
          continue
        
        # get all the sampled points data into a list and then pass it into the "find best fit" function
        values = []
        for i in range(lowest_anim_frame, greatest_anim_frame + 1):
          values.append(fcurve.evaluate(i))
        print(fcurve.data_path)
        new_kfs = math_funcs.find_best_cubic_hermite_spline_fit(lowest_anim_frame, values,
                                                                options.angle_limit,
                                                                options.max_concavity_changes)
        print(new_kfs)
        
        # remove all keyframe points and add the new ones
        while (len(fcurve.keyframe_points) != 0):
          fcurve.keyframe_points.remove(fcurve.keyframe_points[0])
        
        # add the new keyframe points
        for i in range(new_kfs.kf_count):
          fcurve.keyframe_points.insert(new_kfs.time[i], new_kfs.value[i])
        
        # manage the handles
        for i in range(len(fcurve.keyframe_points)):
          kf_point = fcurve.keyframe_points[i]
          kf_point.handle_left_type = "FREE"
          kf_point.handle_right_type = "FREE"
          # assign the handles
          left = kf_point.co.copy()
          right = kf_point.co.copy()
          # left handle
          if (i > 0):
            time_dif = new_kfs.time[i] - new_kfs.time[i - 1]
            x_comp = -(time_dif / 3)
            y_comp = x_comp * new_kfs.in_slope[i]
            left = kf_point.co.copy() + mathutils.Vector((x_comp, y_comp))
          # right handle
          if (i < new_kfs.kf_count - 1):
            time_dif = new_kfs.time[i + 1] - new_kfs.time[i]
            x_comp = (time_dif / 3)
            y_comp = x_comp * new_kfs.out_slope[i]
            right = kf_point.co.copy() + mathutils.Vector((x_comp, y_comp))
          
          # assign the handles
          kf_point.handle_left = left
          kf_point.handle_right = right      
  
  # return to object mode      
  bpy.ops.object.mode_set(mode = "OBJECT")  
  # store the loop mode in a custom property of the armature object
  if ("loop_mode" in armature.data):
    # check if nothing touched the type of this property
    import idprop
    if (type(armature.data["loop_mode"]) != idprop.types.IDPropertyGroup):
      armature.data["loop_mode"] = {file_ops.get_file_name(options.filepath) : anim.loop_mode}
    else:
      armature.data["loop_mode"][file_ops.get_file_name(options.filepath)] = anim.loop_mode
  else:
    armature.data["loop_mode"] = {file_ops.get_file_name(options.filepath) : anim.loop_mode}  
  # display some message
  blender_funcs.disp_msg("Animation file \"%s\" imported." % (file_ops.get_file_name(options.filepath)))                 
  # done!
  return {"FINISHED"}

# Stuff down is for the menu appending
# of the importer to work plus some setting stuff
# comes from a Blender importer template

from bpy_extras.io_utils import ExportHelper
from bpy.props import StringProperty, BoolProperty, EnumProperty, FloatProperty, IntProperty
from bpy.types import Operator

# import_bck class 
class import_bck(Operator, ExportHelper):
  """Import the animation data from a SMG BCK file"""
  # stuff for blender
  bl_idname = "import_scene.bck"
  bl_label = "Import BCK (SMG)"
  filename_ext = ".bck"
  filter_glob = StringProperty(default = "*.bck",
                               options = {"HIDDEN"},
                               maxlen = 255)
  
  # importer options
  import_type = EnumProperty(
    name = "Import Mode",
    description = "Way in which the animation will be imported",
    default = "OPT_C",
    items = (
      ("OPT_A", "Ignore Rest Pose", "Selected armature will be duplicated and its rest pose cleared. Animation data will be applied to it exactly as described by the BCK. For some reason, some model meshes will be deformed weirdly (Floating point limitations? Mesh vertex deform in a non reversible way?)"),
      ("OPT_B", "Keep Rest Pose - Sample Everything", "Animation will be written into the selected armature respect to its rest pose and it will be sampled for each frame of the animation. Some cleanup will be done at the end"),
      ("OPT_C", "Keep Rest Pose - Find Best Interpolator", "Same as \"Keep Rest Pose and Sample everything\" but after sampling everything there will be attempts to find the best interpolator expressions for all animation tracks. This will take more time")
      )
    )
  angle_limit = FloatProperty(
    name = "Derivative angle limit",
    description = "Value used to specify a keyframe generation at curve points at which sudden slope changes occur. Useful to adjust several straight lines. The angle comes from scaling the vertical axis of the animation track so that the \"visual derivative changes\" become visible. For Import Mode \"Keep Rest Pose - Find Best Interpolator\"",
    default = 45,
    min = 0,
    max = 180,
  )
  max_concavity_changes = IntProperty(
    name = "Max concavity changes",
    description = "Number of concavity changes determined in an animation curve that when reached will trigger the generation of a keyframe. For Import Mode \"Keep Rest Pose - Find Best Interpolator\"",
    default = 1,
    min = 1,
  )
  euler_mode = EnumProperty(
    name = "Euler order",
    description = "Import rotation animations in the specified Euler angles order",
    default = "XYZ",
    items = (
      ("XYZ", "XYZ", "X rotation first, Y rotation second, Z rotation last"),
      ("XZY", "XZY", "X rotation first, Z rotation second, Y rotation last"),
      ("YXZ", "YXZ", "Y rotation first, X rotation second, Z rotation last"),
      ("YZX", "YZX", "Y rotation first, Z rotation second, X rotation last"),
      ("ZXY", "ZXY", "Z rotation first, X rotation second, Y rotation last"),
      ("ZYX", "ZYX", "Z rotation first, Y rotation second, X rotation last")
    )
  )
  mult_order = EnumProperty(
    name = "Scale/Rot/Transl mult order",
    description = "Import animations in the specified matrix multiplication order",
    default = "SRT",
    items = (
      ("TRS", "TRS", "Translation first, Rotation second, Scaling last"),
      ("TSR", "TSR", "Translation first, Scaling second, Rotation last"),
      ("RTS", "RTS", "Rotation first, Translation second, Scaling last"),
      ("RST", "RST", "Rotation first, Scaling second, Translation last"),
      ("STR", "STR", "Scaling first, Translation second, Rotation last"),
      ("SRT", "SRT", "Scaling first, Rotation second, Translation last")
    )
  )
  
  # what the importer actually does
  def execute(self, context):
    return import_bck_func(context, self)

# stuff to append the item to the File -> Import/Export menu
def menu_import_bck(self, context):
  self.layout.operator(import_bck.bl_idname, text = "BCK from SMG (.bck)")

# register func
@bpy.app.handlers.persistent
def register(dummy):
  try:
    bpy.utils.register_class(import_bck)
    bpy.types.INFO_MT_file_import.append(menu_import_bck)
  except:
    return
    
# unregister func
def unregister():
  try:
    bpy.utils.unregister_class(import_bck)
    bpy.types.INFO_MT_file_import.remove(menu_import_bck)
  except:
    return