summaryrefslogtreecommitdiff
path: root/csv_anim_bck_export.py
diff options
context:
space:
mode:
authorOwl <isaclien9752@gmail.com>2025-08-23 12:37:51 -0400
committerOwl <isaclien9752@gmail.com>2025-08-23 12:37:51 -0400
commit0ab9e972cb4a4128c2041f6da580a72515b3db64 (patch)
tree1a79d85db13b9714af806b54358fda15aca2ddae /csv_anim_bck_export.py
parenta5bdfdab2ec68ebfe99535a54b3b0cd8b95a87d6 (diff)
downloadblenxy-0ab9e972cb4a4128c2041f6da580a72515b3db64.tar.gz
blenxy-0ab9e972cb4a4128c2041f6da580a72515b3db64.zip
Some changes to the BCK exporter
Diffstat (limited to 'csv_anim_bck_export.py')
-rw-r--r--csv_anim_bck_export.py473
1 files changed, 0 insertions, 473 deletions
diff --git a/csv_anim_bck_export.py b/csv_anim_bck_export.py
deleted file mode 100644
index 9451507..0000000
--- a/csv_anim_bck_export.py
+++ /dev/null
@@ -1,473 +0,0 @@
-'''
-CSV exporter for the BCK animation type
-CSV to be used with the j3d animation editor program
-'''
-
-import bpy, math, re
-from mathutils import Matrix
-from .my_functions import *
-
-# Notes (AFAIK):
-
-# - position/rotation/scaling values of a bone in an animation
-# must be the ones that are relative to its parent bone
-# - Extrinsic Euler XYZ system is the one being used for rotation values.
-# - all animations must start in Frame 0 (starting frame).
-
-######################################
-# write_csv_bck (MAIN FUNCTION)
-# function to write a CSV file with
-# data for the BCK animation type
-# to be used with J3D Animation Editor
-######################################
-def write_csv_bck(context, filepath, loop_mode, export_mode):
-
- # always needed
- scene = bpy.context.scene
-
- # loop mode variable declaration
- loop_number = 0
-
- if (loop_mode == "OPT_A"):
- loop_number = 0
- elif (loop_mode == "OPT_B"):
- loop_number = 1
- elif (loop_mode == "OPT_C"):
- loop_number = 2
- elif (loop_mode == "OPT_D"):
- loop_number = 3
- elif (loop_mode == "OPT_E"):
- loop_number = 4
-
- # if nothing is selected end the exporter
- if (scene.objects.active == None
- or
- scene.objects.active.type != 'ARMATURE'):
- error_string = "No Armature object selected. Select one and try again."
- print("\n### ERROR ###\n" + error_string + "\n### ERROR ###\n")
- show_message(error_string, "Error exporting collada file", 'ERROR')
- return {'FINISHED'}
-
- # get armature object
- armature = scene.objects.active
- print()
- print("###############")
- print("Armature found: %s" % armature.name)
- print()
-
- print("Creating CSV file (for BCK)...")
-
- # open file to write
- f = open(filepath, 'w', encoding='utf-8')
-
- print("Writing CSV header...")
-
- ###############################################
- # write the "header" of the CSV animation table
- ###############################################
-
- animation_length = scene.frame_end + 1
- f.write("%d,%d,%d,%s" % (loop_number, animation_length - 1, 0, ".bck"))
- f.write("\n")
-
- f.write("Bone Name,Tangent Interpolation,Component")
-
- for frame in range(animation_length):
- f.write(",Frame %d" % (frame))
- f.write("\n")
-
- ####################################################
- # get a detailed list of the keyframes used in the
- # animation and the bones related to those keyframes
- #
- # bone_kf_data will hold the bone and keyframe position as follows:
- #
- # bone name --> name of the animated bone
- # anim property --> Scale/Rotation/Translation
- # axis --> 0/1/2 --> X/Y/Z
- #
- # a bone name, anim property, axis, kf 1 pos, kf 2 pos, ...
- # another bone name, anim property, axis, kf 3 pos, kf 4 pos, ...
- # ...
- #
- # Note that each animation property can have different keyframe positions in time
-
- bone_kf_data = []
-
- for i in range(len(armature.animation_data.action.fcurves)):
- #
- # get curve
- curve = armature.animation_data.action.fcurves[i]
- # add new row for given curve in bone_kf_data
- bone_kf_data.append([])
-
- ################################################################
- # append bone name, animation property and axis related to curve
-
- # bone name
- bone_kf_data[i].append(re.search('\["(.+?)"\]', curve.data_path).group(1))
-
- # anim property
- if (curve.data_path.find("scale") + 1):
- bone_kf_data[i].append("scale")
- elif (curve.data_path.find("rotation_euler") + 1):
- bone_kf_data[i].append("rotation")
- elif (curve.data_path.find("location") + 1):
- bone_kf_data[i].append("translation")
- # axis
- if (curve.array_index == 0):
- bone_kf_data[i].append("x")
- elif (curve.array_index == 1):
- bone_kf_data[i].append("y")
- elif (curve.array_index == 2):
- bone_kf_data[i].append("z")
-
- # store keyframe data
- for j in range(len(curve.keyframe_points)):
- keyframe = curve.keyframe_points[j]
- bone_kf_data[i].append(int(keyframe.co[0])) # keyframe pos is an integer (frame)
- #
-
- # print bone_kf_data to terminal
- print()
- for row in bone_kf_data:
- print(row)
-
- ############################################################
- # get the armature bones that contain 2 or more keyframes
- # defined (read bone_kf_data) and store at the side of
- # each bone name the last keyframe position of its animation
- #
- # bone_last_kf_pos will contain data as follows:
- #
- # bone1 name, last bone1 keyframe position, bone2 name, last bone2 keyframe position, ...
- #
- bone_last_kf_pos = []
- for i in range(len(bone_kf_data)):
- #
- if (len(bone_last_kf_pos) != 0):
- # if the last bone name on bone_last_kf_pos is the same as the
- # one on bone_kf_data[i][0] go to the column element
- if (bone_last_kf_pos[len(bone_last_kf_pos) - 2] == bone_kf_data[i][0]):
- # check if the keyframe position of the bone is larger and store the larger value
- if (bone_last_kf_pos[len(bone_last_kf_pos) - 1] < bone_kf_data[i][len(bone_kf_data[i]) - 1]):
- bone_last_kf_pos[len(bone_last_kf_pos) - 1] = bone_kf_data[i][len(bone_kf_data[i]) - 1]
- continue
-
- # bone animation row has more than 1 keyframe on an anim property
- # append bone name and last keyframe position in time
- if (len(bone_kf_data[i]) > 4):
- bone_last_kf_pos.append(bone_kf_data[i][0])
- bone_last_kf_pos.append(bone_kf_data[i][len(bone_kf_data[i]) - 1])
- #
-
- # print bones_with_kf to terminal
- print()
- print(bone_last_kf_pos)
- print()
-
- ###################################################################
- # read animation data for one bone then dump the animation data for
- # said bone on the CSV file
- ###################################################################
-
- ########################
- # loop through each bone
- for i in range(len(armature.pose.bones)):
-
- # get bone
- bone = armature.pose.bones[i]
- # print bone going to be processed on terminal
- print("Processing animation for bone: %s" % (bone.name))
-
- # store the animation data scale/rotation/translation X/Y/Z on
- # bone_anim_data which will have 9 rows and each row will be the
- # length of the animation + 1 (including Frame 0 values)
-
- # row 1 --> Scale X
- # row 2 --> Scale Y
- # row 3 --> Scale Z
- # row 4 --> Rotation X
- # row 5 --> Rotation Y
- # row 6 --> Rotation Z
- # row 7 --> Translation X
- # row 8 --> Translation Y
- # row 9 --> Translation Z
-
- bone_anim_data = [[], [], [], [], [], [], [], [], []]
-
- ###############################################################
- # fill bone_anim_data so the rows have the animation length + 1
- for j in range(len(bone_anim_data)):
- for k in range(animation_length):
- bone_anim_data[j].append("")
-
- ###########################################################################
- # check if the bone has 2 or more keyframes (bone has an animation)
- # and store the animation length of that bone (read bone_last_kf_pos,
- # first frame counts on the animation length!, to use later)
- bone_has_anim = False
- anim_length_for_bone = 0 + 1
- for j in range(int(len(bone_last_kf_pos) / 2)):
- #
- if (bone_last_kf_pos[2 * j] == bone.name):
- bone_has_anim = True
- anim_length_for_bone = bone_last_kf_pos[(2 * j) + 1] + 1
- break
- #
-
- print("Bone has animation? ", end = "")
- print(bone_has_anim)
- print("Bone animation length: %d" % (anim_length_for_bone))
-
- ###################################################################
- # if export mode is "only keyframes" define current_bone_kf_data
- # and get from it all the bone keyframes in each animation property
- # keyframes on bone_kfs won't necessarily be on order
- # (do it on bones that have animation)
- current_bone_kf_data = []
- bone_kfs = []
- if (export_mode == "OPT_B" and bone_has_anim == True):
- #
- ##########################################################
- # store the rows of bone_kf_data in which the current bone
- # appears in current_bone_kf_data (to use later)
- current_bone_kf_data = []
- for j in range(len(bone_kf_data)):
- if (bone_kf_data[j][0] == bone.name):
- current_bone_kf_data.append(bone_kf_data[j])
-
- #################################################
- # read current_bone_kf_data to get all the unique
- # keyframe positions of the bone animation
- for j in range(len(current_bone_kf_data)):
- #
- # store the keyframes found on the first row
- # of current_bone_kf_data in bone_kfs
- if (j == 0):
- for k in range(len(current_bone_kf_data[0])):
- # make k equal to 3
- if (k < 3):
- continue
- bone_kfs.append(current_bone_kf_data[j][k])
-
- # other rows
- for k in range(len(current_bone_kf_data[j])):
- #
- if (k < 3): # make k equal to 3
- continue
-
- # loop through bone_kfs to check for new keyframe positions
- keyframe_exists = False
- for l in range(len(bone_kfs)):
- if (current_bone_kf_data[j][k] == bone_kfs[l]):
- keyframe_exists = True
- break
-
- if (keyframe_exists == False):
- bone_kfs.append(current_bone_kf_data[j][k])
- #
- #
- # ~ print(current_bone_kf_data)
- print("Bone's keyframes position:")
- print(bone_kfs)
- #
-
- print()
-
- # if bone_has_anim equals False only store its first frame animation values
- # if bone_has_anim equals True store (depending on the export mode) its
- # keyframes/all frame animation values (until the last keyframe)
-
- ########################################
- # loop through the animation of the bone
- # k is used to go through bone_kfs if
- # export mode is "only keyframes"
- k = 0
- for j in range(anim_length_for_bone):
- #
- ##########################################
- # set scene frame depending on export mode
- if (export_mode == "OPT_B" and bone_has_anim == True and j != 0):
- #
- # check k in case bone_kfs end is reached
- if (k == len(bone_kfs)):
- break
-
- frame = bone_kfs[k]
- scene.frame_set(frame)
- k = k + 1
- #
- else:
- #
- frame = j
- scene.frame_set(frame)
- #
-
- # first bone must be the outermost bone and therefore it
- # has no parent (batman moment, sorry batman u epik >:])
- if (i == 0):
- # -90 degree rotation matrix
- rot_mat = calc_rotation_matrix(math.radians(-90), 0, 0)
- # the first bone has to be rotated -90 degrees
- # on X to get correct animation values for it
- bone_rel_to_parent_mat = rot_mat * bone.matrix
- else: # for any other bone
- bone_rel_to_parent_mat = bone.parent.matrix.inverted() * bone.matrix
-
- ##########################################
- # extract bone_rel_to_parent_mat anim data
-
- # bone scaling
- bone_scale = bone_rel_to_parent_mat.to_scale()
- # bone rotation (stored in radians, extrinsic XYZ Euler)
- bone_rotation = bone_rel_to_parent_mat.to_euler("XYZ")
- # bone translation (multiplied by 100 because 1 GU is 100 meters)
- bone_translation = 100 * bone_rel_to_parent_mat.to_translation()
-
- ##########################################################
- # store frame animation values of bone into bone_anim_data
-
- # scaling data
- bone_anim_data[0][frame] = round(bone_scale[0], 2)
- bone_anim_data[1][frame] = round(bone_scale[1], 2)
- bone_anim_data[2][frame] = round(bone_scale[2], 2)
-
- # rotation data (must be in degrees!)
- bone_anim_data[3][frame] = round(math.degrees(bone_rotation[0]), 2)
- bone_anim_data[4][frame] = round(math.degrees(bone_rotation[1]), 2)
- bone_anim_data[5][frame] = round(math.degrees(bone_rotation[2]), 2)
-
- # position data
- bone_anim_data[6][frame] = round(bone_translation[0], 2)
- bone_anim_data[7][frame] = round(bone_translation[1], 2)
- bone_anim_data[8][frame] = round(bone_translation[2], 2)
-
- # ^ a lot of values can be repeated in each row.
- # When writing the animation data to the CSV is when
- # an optimization method will be done
- #
-
- # ~ for row in bone_anim_data:
- # ~ print(row)
- # ~ print()
-
- ####################################
- # write bone animation data into CSV
- # read bone_anim_data
- ####################################
- for j in range(9):
- #
- # first 3 rows are scaling data
- # the next 3 rows are rotation data
- # the final 3 rows are translation data
- # the start of the first row for a bone
- # animation data (Scale X) contains the bone name
- if (j == 0):
- f.write("%s,Linear,Scale X:" % (bone.name))
- elif(j == 1):
- f.write(",Linear,Scale Y:")
- elif(j == 2):
- f.write(",Linear,Scale Z:")
- elif(j == 3):
- f.write(",Linear,Rotation X:")
- elif(j == 4):
- f.write(",Linear,Rotation Y:")
- elif(j == 5):
- f.write(",Linear,Rotation Z:")
- elif(j == 6):
- f.write(",Linear,Translation X:")
- elif(j == 7):
- f.write(",Linear,Translation Y:")
- elif(j == 8):
- f.write(",Linear,Translation Z:")
-
- ###################################
- # write animation row data
- # will print values with 2 decimals
- ###################################
- for k in range(animation_length):
- #
- # get current animation value
- current_value = bone_anim_data[j][k]
-
- # write the first value from row
- if (k == 0):
- f.write(",%.2f" % (current_value))
- # compare old_value with current_value. if equal leave blank the
- # animation frame value otherwise write said value. This is done
- # to avoid repeating the same number each time (to save file size)
- elif (old_value == current_value or current_value == ""):
- f.write(",")
- else:
- f.write(",%.2f" % (current_value))
-
- # if the end of a row is reached write a newline char to the line
- if (k == (animation_length - 1)):
- f.write("\n")
-
- # store old animation value for the next loop
- if (current_value != ""):
- old_value = current_value
- #
- #
- #
-
- # exporter end
- return {'FINISHED'}
-
-
-#################################################
-# Stuff down is for the menu appending
-# of the exporter 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
-from bpy.types import Operator
-
-class Export_CSV_BCK(Operator, ExportHelper):
-#
- """Save a CSV file for BCK conversion (to use with J3D Animation Editor)"""
- bl_idname = "export_scene.csv_bck"
- bl_label = "Export CSV (for BCK)"
- filename_ext = ".csv"
-
- filter_glob = StringProperty(
- default="*.csv",
- options={'HIDDEN'},
- maxlen=255,
- )
-
- loop_mode = EnumProperty(
- name="Loop Mode",
- description="Choose the loop mode for the animation",
- items=( ('OPT_A', "Once", "Play the animation once and stop at the last frame"),
- ('OPT_B', "Once and Reset", "Play the animation once and stop at the first frame"),
- ('OPT_C', "Loop", "Loop the animation infinitely"),
- ('OPT_D', "Mirrored Once", "Play the animation forwards and then backwards once. Stops at the first frame"),
- ('OPT_E', "Mirrored Loop", "Play the animation forwards and then backwards infinitely")
- ), default='OPT_A'
- )
- export_mode = EnumProperty(
- name="Export Mode",
- description="Choose the method used to export the model animation data",
- items=( ('OPT_A', "All Frames", "Export animation values for each frame of the animation. Slow, higher CSV file size, more accurate animation"),
- ('OPT_B', "Only Keyframes", "Only export the animation keyframe values of each bone. Fast, lower CSV file size, least accurate animation"),
- ), default='OPT_A'
- )
-
- def execute(self, context):
- return write_csv_bck(context, self.filepath, self.loop_mode, self.export_mode)
-#
-
-# Only needed if you want to add into a dynamic menu
-def menu_export_csv_bck(self, context):
- self.layout.operator(Export_CSV_BCK.bl_idname, text="CSV Animation Table (for BCK) (.csv)")
-
-bpy.utils.register_class(Export_CSV_BCK)
-bpy.types.INFO_MT_file_export.append(menu_export_csv_bck)
-
-# test call
-bpy.ops.export_scene.csv_bck('INVOKE_DEFAULT')