diff options
author | Owl <isaclien9752@gmail.com> | 2025-08-23 12:37:51 -0400 |
---|---|---|
committer | Owl <isaclien9752@gmail.com> | 2025-08-23 12:37:51 -0400 |
commit | 0ab9e972cb4a4128c2041f6da580a72515b3db64 (patch) | |
tree | 1a79d85db13b9714af806b54358fda15aca2ddae /csv_anim_bck_export.py | |
parent | a5bdfdab2ec68ebfe99535a54b3b0cd8b95a87d6 (diff) | |
download | blenxy-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.py | 473 |
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') |