diff options
author | Owl <isaclien9752@gmail.com> | 2025-08-23 00:29:45 -0400 |
---|---|---|
committer | Owl <isaclien9752@gmail.com> | 2025-08-23 00:29:45 -0400 |
commit | a5bdfdab2ec68ebfe99535a54b3b0cd8b95a87d6 (patch) | |
tree | 3cbc818cc7a9e221697fe0be05b9552fd6d33f09 | |
parent | 51077c2fe8c160743a67303fb516126bb98afff7 (diff) | |
download | blenxy-a5bdfdab2ec68ebfe99535a54b3b0cd8b95a87d6.tar.gz blenxy-a5bdfdab2ec68ebfe99535a54b3b0cd8b95a87d6.zip |
bck exporter seems to be working
-rw-r--r-- | bck_export.py | 133 | ||||
-rw-r--r-- | bck_funcs.py | 227 | ||||
-rw-r--r-- | collada_superbmd_import.py | 2 | ||||
-rw-r--r-- | math_funcs.py | 7 | ||||
-rw-r--r-- | smg_common.py | 8 | ||||
-rw-r--r-- | test.py | 39 |
6 files changed, 284 insertions, 132 deletions
diff --git a/bck_export.py b/bck_export.py index 9d8576c..9ba4427 100644 --- a/bck_export.py +++ b/bck_export.py @@ -62,20 +62,22 @@ def export_bck_func(options, context): bone_fcurves = [None, None, None, None, None, None, None, None, None] # ^ sx, rx, tx, sy (24!), ry, ty, sz, rz and tz in that order (bck order) bone_data_path_str = "pose.bones[\"%s\"]." % (data_bone.name) - for fcurve in armature.animation_data.action: + for fcurve in armature.animation_data.action.fcurves: if (fcurve.data_path == bone_data_path_str + "scale"): - bone_fcurves[0 + fcurve.array_index] = fcurve + bone_fcurves[int((3 * fcurve.array_index) + 0)] = fcurve elif (fcurve.data_path == bone_data_path_str + "rotation_euler"): - bone_fcurves[3 + fcurve.array_index] = fcurve + bone_fcurves[int((3 * fcurve.array_index) + 1)] = fcurve elif (fcurve.data_path == bone_data_path_str + "location"): - bone_fcurves[6 + fcurve.array_index] = fcurve + bone_fcurves[int((3 * fcurve.array_index) + 2)] = fcurve # generate all the animation points, interpolation stuff will be done later # get the rest pose matrix rest_mat = data_bone.matrix_local.copy() if (pose_bone.parent != None): - rest_mat = data_bone.parent.matrix_local.copy().inverted() * mat.copy() + rest_mat = data_bone.parent.matrix_local.copy().inverted() * rest_mat.copy() + else: + rest_mat = mathutils.Matrix.Identity(4) # get the points on all frames, only the points for j in range(bck_anim.anim_length): @@ -91,67 +93,142 @@ def export_bck_func(options, context): value = bone_fcurves[k].evaluate(options.first_frame + j) # check which is the component to get if (k == 0 or k == 3 or k == 6): - scale[k / 3] = value + scale[int((k - 0) / 3)] = value elif (k == 1 or k == 4 or k == 7): - rot[(k - 1) / 3] = value + rot[int((k - 1) / 3)] = value elif (k == 2 or k == 5 or k == 8): - trans[(k - 2) / 3] = value + transl[int((k - 2) / 3)] = value # convert the values to be respect to parent - new_mat = math_funcs.calc_transf_mat(scale, rot, transl).copy() * rest_mat.copy() + new_mat = rest_mat.copy() * math_funcs.calc_transf_mat(scale, rot, transl).copy() for k in range(9): value = None # check which is the component to get if (k == 0 or k == 3 or k == 6): - value = new_mat.to_scale()[k / 3] + value = new_mat.to_scale()[int((k - 0) / 3)] elif (k == 1 or k == 4 or k == 7): - value = new_mat.to_euler("XYZ")[(k - 1) / 3] + value = new_mat.to_euler("XYZ")[int((k - 1) / 3)] elif (k == 2 or k == 5 or k == 8): - value = new_mat.to_translation()[(k - 2) / 3] - bck_anim.anim_data[i].comp[k].values.append(value) + value = 100 * new_mat.to_translation()[int((k - 2) / 3)] # conversion from blenxy's coordinates + bck_anim.anim_data[i].comp[k].value.append(value) - # got all the animation points, now to decide what to do with them + # got all the animation points + # delete constant value animation tracks + for i in range(bck_anim.bone_count): + for j in range(9): + anim_track_constant = True + for k in range(bck_anim.anim_length): + if (k == 0): + continue + # check if the whole animation track is the same + comp_min_dif = options.min_dif # min difference (radians) + if (j == 0 or j == 3 or j == 6): # scale + comp_min_dif = options.min_dif / 3 + elif (j == 2 or j == 5 or j == 8): # translation + comp_min_dif = options.min_dif * 1000 + if (abs(bck_anim.anim_data[i].comp[j].value[k - 1] + - bck_anim.anim_data[i].comp[j].value[k]) > comp_min_dif): + anim_track_constant = False + break + if (anim_track_constant == True): + bck_anim.anim_data[i].comp[j].kf_count = 1 + bck_anim.anim_data[i].comp[j].interp_mode = 0 + bck_anim.anim_data[i].comp[j].time = [None] + bck_anim.anim_data[i].comp[j].value = [bck_anim.anim_data[i].comp[j].value[0]] + bck_anim.anim_data[i].comp[j].in_slope = [None] + bck_anim.anim_data[i].comp[j].out_slope = [None] + + print(bck_anim) # keep all the samples intact and calculate the slopes # using linear interpolation between consecutive frames if (options.export_type == "OPT_A"): + # assign the rest of the variables for i in range(bck_anim.bone_count): for j in range(9): - bck_anim.anim_data[i].comp[j].kf_count = len(bck_anim.anim_length) + # skip 1 keyframe animations + if (bck_anim.anim_data[i].comp[j].kf_count == 1): + continue + bck_anim.anim_data[i].comp[j].kf_count = bck_anim.anim_length bck_anim.anim_data[i].comp[j].interp_mode = 1 # has to be like this for k in range(bck_anim.anim_length): bck_anim.anim_data[i].comp[j].time.append(k) in_slope = 0 out_slope = 0 if (k > 0): - in_slope = bck_anim.anim_data[i].comp[j].values[k] - bck_anim.anim_data[i].comp[j].values[k - 1] + in_slope = bck_anim.anim_data[i].comp[j].value[k] - bck_anim.anim_data[i].comp[j].value[k - 1] if (k < bck_anim.anim_length - 1): - out_slope = bck_anim.anim_data[i].comp[j].values[k + 1] - bck_anim.anim_data[i].comp[j].values[k] + out_slope = bck_anim.anim_data[i].comp[j].value[k + 1] - bck_anim.anim_data[i].comp[j].value[k] bck_anim.anim_data[i].comp[j].in_slope.append(in_slope) bck_anim.anim_data[i].comp[j].out_slope.append(out_slope) # find "best" interpolator fits for the samples elif (options.export_type == "OPT_B"): - print() - + + # assign the rest of the variables + for i in range(bck_anim.bone_count): + # assign the best fit for each animation component + for j in range(9): + # skip 1 keyframe animations + if (bck_anim.anim_data[i].comp[j].kf_count == 1): + continue + # get the best fit interpolation result + interp_result = math_funcs.find_best_cubic_hermite_spline_fit(options.first_frame, + bck_anim.anim_data[i].comp[j].value, + options.angle_limit) + # check if the fit can be made in interpolation mode == 0 (in_slope = out_slope) + # assign the best fit for each animation component + can_use_smooth_interp = True + comp_min_dif = options.min_dif # min difference (radians) + if (j == 0 or j == 3 or j == 6): # scale + comp_min_dif = options.min_dif / 3 + elif (j == 2 or j == 5 or j == 8): # translation + comp_min_dif = options.min_dif * 1000 + for k in range(interp_result.kf_count): + if (k == 0 or k == interp_result.kf_count - 1): + continue + if (abs(interp_result.in_slope[k] - interp_result.out_slope[k]) > comp_min_dif): + can_use_smooth_interp = False + break + + # nice, adjust in_slope[0] and out_slope[-1] + if (can_use_smooth_interp == True): + interp_result.in_slope[0] = interp_result.out_slope[0] + interp_result.out_slope[-1] = interp_result.in_slope[-1] + else: + interp_result.in_slope[0] = 0 + interp_result.out_slope[-1] = 0 + + # overwrite the old animation track + bck_anim.anim_data[i].comp[j].kf_count = interp_result.kf_count + bck_anim.anim_data[i].comp[j].interp_mode = 1 + if (can_use_smooth_interp == True): + bck_anim.anim_data[i].comp[j].interp_mode = 0 + bck_anim.anim_data[i].comp[j].time = interp_result.time + bck_anim.anim_data[i].comp[j].value = interp_result.value + bck_anim.anim_data[i].comp[j].in_slope = interp_result.in_slope + bck_anim.anim_data[i].comp[j].out_slope = interp_result.out_slope + + # hopefully everything went okay + print(bck_anim) + # create a raw bck struct and write the BCK file raw = bck_funcs.create_smg_bck_raw(bck_anim) - print(raw) + print(raw) endian_ch = ">" # big endian character for struct.unpack() if (options.endian == "OPT_B"): # little character endian_ch = "<" - bck_funcs.write_smg_bck_raw(raw, filepath, endian_ch) + bck_funcs.write_smg_bck_raw(raw, options.filepath, endian_ch) # done! - blender_funcs.disp_msg("BCK animation \"%s\" written" % (file_ops.get_filename(filepath))) + blender_funcs.disp_msg("BCK animation \"%s\" written" % (file_ops.get_file_name(options.filepath))) 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 @@ -177,7 +254,7 @@ class export_bck(Operator, ExportHelper): ) 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", + 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 export option \"Find Best Interpolator\"", default = 45, min = 0, max = 180, @@ -213,6 +290,12 @@ class export_bck(Operator, ExportHelper): ("OPT_B", "Little", "Write data in the little endian byte ordering") ) ) + min_dif = FloatProperty( + name = "Minimum difference", + description = "Minimum difference at which 2 numbers can be considered the same. For reference, this value is linked to randians magnitude", + default = 0.001, + min = 1e-9, + ) # what the importer actually does def execute(self, context): return export_bck_func(self, context) @@ -225,6 +308,6 @@ bpy.utils.register_class(export_bck) bpy.types.INFO_MT_file_export.append(menu_export_bck) # test call -bpy.ops.export_scene.bck('INVOKE_DEFAULT') +bpy.ops.export_scene.bck("INVOKE_DEFAULT") diff --git a/bck_funcs.py b/bck_funcs.py index d8c1162..8e2d6bb 100644 --- a/bck_funcs.py +++ b/bck_funcs.py @@ -199,7 +199,7 @@ class smg_bck_anim: # create a global variable to hold temporal information bck_raw_info = None -bck_error_str = "bck-error: " +bck_raw_error_str = "bck-raw-error: " bck_anim_error_str = "bck-anim-error: " pad_str = "hoot" f = None @@ -217,7 +217,7 @@ def read_bck_file(filepath): # all good bck_anim_info = None - if (result_str == bck_error_str + "all good"): + if (result_str == bck_raw_error_str + "all good"): # construct the data structure that is easier to deal with print(bck_raw_info) bck_anim_info = smg_bck_anim() @@ -287,7 +287,7 @@ def pre_read_bck_file(filepath): # check its size first if (os.path.getsize(filepath) <= 32): - return bck_error_str + "file size" + return bck_raw_error_str + "file size" # make global variables editable global f @@ -309,7 +309,7 @@ def pre_read_bck_file(filepath): elif (bck_raw_info.header.magic == "1D3J"): bck_raw_info.endian = "LITTLE" else: - return bck_error_str + "magic" + return bck_raw_error_str + "magic" bck_raw_info.header.magic = "J3D1" # variable to set for struct.unpack byte order reading @@ -321,24 +321,24 @@ def pre_read_bck_file(filepath): bck_raw_info.header.ftype = f.read(4).decode("ascii") if ((bck_raw_info.header.ftype == "bck1" and bck_raw_info.endian != "BIG") and (bck_raw_info.header.ftype == "1kcb" and bck_raw_info.endian != "LITTLE")): - return bck_error_str + "ftype" + return bck_raw_error_str + "ftype" bck_raw_info.header.ftype = "bck1" # file size bck_raw_info.header.file_size = struct.unpack(endian_ch + "I", f.read(4))[0] if (bck_raw_info.header.file_size != os.path.getsize(filepath)): - return bck_error_str + "file size" + return bck_raw_error_str + "file size" # section count bck_raw_info.header.section_count = struct.unpack(endian_ch + "I", f.read(4))[0] if (bck_raw_info.header.section_count != 1): - return bck_error_str + "section count" + return bck_raw_error_str + "section count" # unknown 1 bck_raw_info.header.unknown1 = list(f.read(16)) for i in range(16): if (bck_raw_info.header.unknown1[i] != 0xFF): - return bck_error_str + "unknown 1" + return bck_raw_error_str + "unknown 1" ############## # ank1 section @@ -347,18 +347,18 @@ def pre_read_bck_file(filepath): bck_raw_info.ank1.magic = f.read(4).decode("ascii") if ((bck_raw_info.ank1.magic == "ANK1" and bck_raw_info.endian != "BIG") and (bck_raw_info.ank1.magic == "1KNA" and bck_raw_info.endian != "LITTLE")): - return bck_error_str + "ank1 magic" + return bck_raw_error_str + "ank1 magic" bck_raw_info.ank1.magic = "ANK1" # size bck_raw_info.ank1.size = struct.unpack(endian_ch + "I", f.read(4))[0] if (bck_raw_info.ank1.size != bck_raw_info.header.file_size - 32): - return bck_error_str + "ank1 size" + return bck_raw_error_str + "ank1 size" # loop mode bck_raw_info.ank1.loop_mode = struct.unpack(endian_ch + "B", f.read(1))[0] if (bck_raw_info.ank1.loop_mode > 0x04): - return bck_error_str + "ank1 loop mode" + return bck_raw_error_str + "ank1 loop mode" # rotation left shift bck_raw_info.ank1.rot_lshift = struct.unpack(endian_ch + "B", f.read(1))[0] @@ -379,22 +379,22 @@ def pre_read_bck_file(filepath): bck_raw_info.ank1.anim_data_offset = struct.unpack(endian_ch + "I", f.read(4))[0] if (bck_raw_info.ank1.anim_data_offset + (bck_raw_info.ank1.bone_count * 9 * 6) > bck_raw_info.ank1.size): - return bck_error_str + "ank1 bone animation data offset" + return bck_raw_error_str + "ank1 bone animation data offset" # scale array offset bck_raw_info.ank1.scale_arr_offset = struct.unpack(endian_ch + "I", f.read(4))[0] if (bck_raw_info.ank1.scale_arr_offset + (bck_raw_info.ank1.scale_arr_length * 4) > bck_raw_info.ank1.size): - return bck_error_str + "ank1 scale array offset" + return bck_raw_error_str + "ank1 scale array offset" # rotation array offset bck_raw_info.ank1.rot_arr_offset = struct.unpack(endian_ch + "I", f.read(4))[0] if (bck_raw_info.ank1.rot_arr_offset + (bck_raw_info.ank1.rot_arr_length * 2) > bck_raw_info.ank1.size): - return bck_error_str + "ank1 rotation array offset" + return bck_raw_error_str + "ank1 rotation array offset" # translation array offset bck_raw_info.ank1.transl_arr_offset = struct.unpack(endian_ch + "I", f.read(4))[0] if (bck_raw_info.ank1.transl_arr_offset + (bck_raw_info.ank1.transl_arr_length * 4) > bck_raw_info.ank1.size): - return bck_error_str + "ank1 translation array offset" + return bck_raw_error_str + "ank1 translation array offset" ######################################################################## # refer to the offsets to read the animation data always (SMG does this) @@ -421,7 +421,7 @@ def pre_read_bck_file(filepath): # check the interpolation mode and if nothing overflows if (interp_mode > 1): - return bck_error_str + "ank1 interpolation mode" + return bck_raw_error_str + "ank1 interpolation mode" # variables to be used later item_read_size = None @@ -454,7 +454,7 @@ def pre_read_bck_file(filepath): # check overflow if (arr_offset + (item_read_size * data_index) + (item_read_size * kf_count * number_of_items_per_kf) > bck_raw_info.ank1.size): - return bck_error_str + "ank1 anim data overflow" + return bck_raw_error_str + "ank1 anim data overflow" # read the respective arrays to check time consistency old_time = None @@ -476,7 +476,7 @@ def pre_read_bck_file(filepath): cur_time = struct.unpack(endian_ch + item_read_type, f.read(item_read_size))[0] if (old_time != None): if (old_time >= cur_time): - return bck_error_str + "ank1 keyframe time" + return bck_raw_error_str + "ank1 keyframe time" old_time = cur_time # value value = struct.unpack(endian_ch + item_read_type, f.read(item_read_size))[0] @@ -500,7 +500,7 @@ def pre_read_bck_file(filepath): bck_raw_info.ank1.transl_arr.append(struct.unpack(endian_ch + "f", f.read(4))[0]) # finally done bruh - return bck_error_str + "all good" + return bck_raw_error_str + "all good" # check if a smg_bck_anim structure is good def check_smg_bck_anim(anim): @@ -523,16 +523,17 @@ def check_smg_bck_anim(anim): for comp in bone.comp: # check object types and integer data - if (type(comp) != smg_bck_anim.anim_data): + if (type(comp) != smg_bck_anim.anim_data.comp): return bck_anim_error_str + "anim_data struct" if (type(comp.kf_count) != int or comp.kf_count <= 0): + print(comp.kf_count) return bck_anim_error_str + "keyframe count" - if (type(comp.interp_mode) != int or (comp.kf_count != 0 and comp.kf_count != 1)): + if (type(comp.interp_mode) != int or (comp.interp_mode != 0 and comp.interp_mode != 1)): return bck_anim_error_str + "interpolation mode" if (type(comp.time) != list or len(comp.time) != comp.kf_count): return bck_anim_error_str + "time list" - if (type(comp.values) != list or len(comp.values) != comp.kf_count): - return bck_anim_error_str + "values list" + if (type(comp.value) != list or len(comp.value) != comp.kf_count): + return bck_anim_error_str + "value list" if (type(comp.in_slope) != list or len(comp.in_slope) != comp.kf_count): return bck_anim_error_str + "in_slope list" if (type(comp.out_slope) != list or len(comp.out_slope) != comp.kf_count): @@ -549,6 +550,9 @@ def check_smg_bck_anim(anim): return bck_anim_error_str + "all good" # create smg_bck_raw from smg_bck_anim +# assumes angles are in radians +# and that the timings between the keyframes are t0 = 0 and tf = 1 +# (cubic hermite spline) def create_smg_bck_raw(anim): # calls check_smg_bck_anim() @@ -563,7 +567,7 @@ def create_smg_bck_raw(anim): # header raw.header.magic = "J3D1" - raw.header.ftype = "btp1" + raw.header.ftype = "bck1" raw.header.file_size = 0 # update later raw.header.section_count = 1 raw.header.unknown1 = [0xFF, 0xFF, 0xFF, 0xFF, @@ -583,33 +587,38 @@ def create_smg_bck_raw(anim): raw.ank1.anim_length = anim.anim_length raw.ank1.bone_count = anim.bone_count - # check all the rotation lists and get the average value - avg_angle_mag = 0 - angle_count = 0 + # check all the rotation lists and get the largest value/slope + max_angle_value_slope_mag = 0 for i in range(anim.bone_count): for j in range(3): - for k in range(len(anim.anim_data[i].comp[(j * 3) + 1].kf_count)): - max_angle_mag += abs(anim.anim_data[i].comp[(j * 3) + 1].values[k]) - angle_count += 1 - # calculate rot_lshift so that this value can be represented - # (shit can go crazy if this value is very large) - avg_angle_mag = avg_angle_mag / angle_count - raw.ank1.rot_lshift = int(math.ceil(math.log2(avg_angle_mag / math.pi))) - # ceil of it because I am forcing the average to be represented - # as 0x7FFF which cannot be done as rot_lshift needs to be an integer + for k in range(anim.anim_data[i].comp[(j * 3) + 1].kf_count): + if (abs(anim.anim_data[i].comp[(j * 3) + 1].value[k]) > max_angle_value_slope_mag): + max_angle_value_slope_mag = abs(anim.anim_data[i].comp[(j * 3) + 1].value[k]) + # ~ if (k > 0): + # ~ if (abs(anim.anim_data[i].comp[(j * 3) + 1].in_slope[k]) > max_angle_value_slope_mag): + # ~ max_angle_value_slope_mag = abs(anim.anim_data[i].comp[(j * 3) + 1].in_slope[k]) + # ~ if (k < anim.anim_data[i].comp[(j * 3) + 1].kf_count - 1): + # ~ if (abs(anim.anim_data[i].comp[(j * 3) + 1].out_slope[k]) > max_angle_value_slope_mag): + # ~ max_angle_value_slope_mag = abs(anim.anim_data[i].comp[(j * 3) + 1].out_slope[k]) + # calculate rot_lshift so that the largest value can be represented + if (max_angle_value_slope_mag > math.pi): + raw.ank1.rot_lshift = int(math.ceil(math.log2(max_angle_value_slope_mag / math.pi))) + # ^ ceil of it because I am forcing the average to be represented + # as 0x7FFF which cannot be done as rot_lshift needs to be an integer + else: # angles can be represented between -180 and 180 + raw.ank1.rot_lshift = 0 # start writing the animation data # iterate over the bones for i in range(anim.bone_count): # add a section for a bone - raw.ank1.anim_data.append(smg_bck_raw.anim_data()) + raw.ank1.anim_data.append(smg_bck_raw.ank1.anim_data()) # iterate over the animation components for j in range(9): - # in case the same dataset can be found already - # written in one of the animation data arrays - match_found = True + + # get the animation component array arr = None # scale if (j == 0 or j == 3 or j == 6): @@ -620,6 +629,7 @@ def create_smg_bck_raw(anim): # translate elif (j == 2 or j == 5 or j == 8): arr = raw.ank1.transl_arr + # get the last index to write in the array cur_index = len(arr) # keyframe_count, interpolation_mode @@ -633,64 +643,69 @@ def create_smg_bck_raw(anim): elif (raw.ank1.anim_data[i].comp[j].interpolation_mode == 1): number_of_items = 4 - # check if it is a single keyframe value + # variable to check if the animation track data + # can be found already in the component array + match_found = False + + # rotation consideration + rot_mult = 1 + if (j == 1 or j == 4 or j == 7): + rot_mult = (0x7FFF / (math.pi * math.pow(2, raw.ank1.rot_lshift))) + + # check if it is a single keyframe value if (number_of_items == 1): # check if there is an equivalent value around the already written data - match_found = False - k = 0 - while (k < len(arr)): - if (anim.anim_data[i].comp[j].values[0] == arr[k]): - raw.ank1.anim_data[i].comp[j].anim_data_index = k - match_found = True - break - k += 1 + if (anim.anim_data[i].comp[j].value[0] in arr): + match_found = True + for k in range(len(arr)): + if (arr[k] == anim.anim_data[i].comp[j].value[0] * rot_mult): + raw.ank1.anim_data[i].comp[j].anim_data_index = k + break # something was found, index was already assigned if (match_found == True): continue - # otherwise, update the array - arr.append(anim.anim_data[i].comp[j].values[0]) + # otherwise, update the array and assign the new index + raw.ank1.anim_data[i].comp[j].anim_data_index = cur_index + arr.append(anim.anim_data[i].comp[j].value[0] * rot_mult) # or if it is more else: # iterate over all the other values in the scale array to see if a match is found - k = 0 - while ((k + (anim.anim_data[i].comp[j].kf_count * number_of_items)) < len(arr)): - # check coincidence - l = 0 + for k in range(len(arr)): + # check if there is enough space left in the array + if (k + (anim.anim_data[i].comp[j].kf_count * number_of_items) > len(arr)): + break + # check if the section from the array is the same as the data to match + tmp = arr[k : k + (anim.anim_data[i].comp[j].kf_count * number_of_items)] match_found = True - while (l < anim.anim_data[i].comp[j].kf_count): - # check value equality - if ((anim.anim_data[i].comp[j].time[l] != arr[k + l + 0]) - or (anim.anim_data[i].comp[j].value[l] != arr[k + l + 1]) - or (anim.anim_data[i].comp[j].in_slope[l] != arr[k + l + 2])): + for l in range((anim.anim_data[i].comp[j].kf_count)): + # 3 items to check + if ((tmp[int((l * number_of_items) + 0)] != anim.anim_data[i].comp[j].time[l]) + or (tmp[int((l * number_of_items) + 1)] != anim.anim_data[i].comp[j].value[l] * rot_mult) + or (tmp[int((l * number_of_items) + 2)] != anim.anim_data[i].comp[j].in_slope[l] * rot_mult)): match_found = False break - # interpolation mode == 1 + # 4 items to check if ((anim.anim_data[i].comp[j].interp_mode == 1) - and (anim.anim_data[i].comp[j].out_slope[l] != arr[k + l + 3])): + and (tmp[int((l * number_of_items) + 3)] != anim.anim_data[i].comp[j].out_slope[l] * rot_mult)): match_found = False break - l += number_of_items - # something was found + # something was found, assign the index and break out of the loop if (match_found == True): raw.ank1.anim_data[i].comp[j].anim_data_index = k break - # continue to next loop - k += 1 # something was found, index was already assigned if (match_found == True): continue - # else append the new data + # otherwise, update the array and assign the new index raw.ank1.anim_data[i].comp[j].anim_data_index = cur_index - # iterate over the frames and assign the scale values - k = 0 - while (k < anim.anim_data[i].comp[j].kf_count): + # iterate over the frames and assign the values + for k in range(anim.anim_data[i].comp[j].kf_count): arr.append(anim.anim_data[i].comp[j].time[k]) - arr.append(anim.anim_data[i].comp[j].value[k]) - arr.append(anim.anim_data[i].comp[j].in_slope[k]) + arr.append(anim.anim_data[i].comp[j].value[k] * rot_mult) + arr.append(anim.anim_data[i].comp[j].in_slope[k] * rot_mult) if (anim.anim_data[i].comp[j].interp_mode == 1): - arr.append(anim.anim_data[i].comp[j].out_slope[k]) - k += 1 + arr.append(anim.anim_data[i].comp[j].out_slope[k] * rot_mult) # update the animation arrays # scale @@ -703,7 +718,8 @@ def create_smg_bck_raw(anim): elif (j == 2 or j == 5 or j == 8): raw.ank1.transl_arr = arr - # assign these variables now + # assign these variables now + # dont be crazy with it an assign the data tables to the "standard offsets" raw.ank1.scale_arr_length = len(raw.ank1.scale_arr) raw.ank1.rot_arr_length = len(raw.ank1.rot_arr) raw.ank1.transl_arr_length = len(raw.ank1.transl_arr) @@ -717,7 +733,7 @@ def create_smg_bck_raw(anim): raw.ank1.transl_arr_offset += len(pad_str.string_fill(32, raw.ank1.transl_arr_offset)) # section size and file size raw.ank1.size = raw.ank1.transl_arr_offset + (raw.ank1.transl_arr_length * 4) - raw.ank1.size += len(pad_str.string_fill(32, raw.ank1.size)) + 1 + raw.ank1.size += len(pad_str.string_fill(32, raw.ank1.size)) raw.header.file_size = 32 + raw.ank1.size # done! @@ -726,11 +742,60 @@ def create_smg_bck_raw(anim): # write smg_bck_raw def write_smg_bck_raw(raw, filepath, endian_ch): - # assumes smg_bck_raw struct is correct so don't even - # attempt in making one yourself, use create_smg_bck_raw() + # assumes smg_bck_raw struct is correct so don't even attempt in making one yourself + # use create_smg_bck_raw() to get a raw struct from an anim struct # use struct.pack() to write in different endian orders - # dont be crazy with it an assign the data tables to the "standard offsets" - - global f f = open(filepath, "wb") + + # header + f.write(struct.pack(endian_ch + "I", struct.unpack(">I", raw.header.magic.encode("ascii"))[0])) + f.write(struct.pack(endian_ch + "I", struct.unpack(">I", raw.header.ftype.encode("ascii"))[0])) + f.write(struct.pack(endian_ch + "I", raw.header.file_size)) + f.write(struct.pack(endian_ch + "I", raw.header.section_count)) + f.write(bytes(raw.header.unknown1)) + + # ank1 + f.write(struct.pack(endian_ch + "I", struct.unpack(">I", raw.ank1.magic.encode("ascii"))[0])) + f.write(struct.pack(endian_ch + "I", raw.ank1.size)) + f.write(struct.pack(endian_ch + "B", raw.ank1.loop_mode)) + f.write(struct.pack(endian_ch + "B", raw.ank1.rot_lshift)) + f.write(struct.pack(endian_ch + "H", raw.ank1.anim_length)) + f.write(struct.pack(endian_ch + "H", raw.ank1.bone_count)) + f.write(struct.pack(endian_ch + "H", raw.ank1.scale_arr_length)) + f.write(struct.pack(endian_ch + "H", raw.ank1.rot_arr_length)) + f.write(struct.pack(endian_ch + "H", raw.ank1.transl_arr_length)) + f.write(struct.pack(endian_ch + "I", raw.ank1.anim_data_offset)) + f.write(struct.pack(endian_ch + "I", raw.ank1.scale_arr_offset)) + f.write(struct.pack(endian_ch + "I", raw.ank1.rot_arr_offset)) + f.write(struct.pack(endian_ch + "I", raw.ank1.transl_arr_offset)) + pad = smg_common.padding() + f.write(pad.string_fill(32, 0x24)) + # anim data + for i in range(raw.ank1.bone_count): + for j in range(9): + f.write(struct.pack(endian_ch + "H", raw.ank1.anim_data[i].comp[j].keyframe_count)) + f.write(struct.pack(endian_ch + "H", raw.ank1.anim_data[i].comp[j].anim_data_index)) + f.write(struct.pack(endian_ch + "H", raw.ank1.anim_data[i].comp[j].interpolation_mode)) + f.write(pad.string_fill(32, raw.ank1.anim_data_offset + (raw.ank1.bone_count * 9 * 6))) + # scale array + for i in range(raw.ank1.scale_arr_length): + f.write(struct.pack(endian_ch + "f", raw.ank1.scale_arr[i])) + f.write(pad.string_fill(32, raw.ank1.scale_arr_offset + (raw.ank1.scale_arr_length * 4))) + # rotation array + for i in range(raw.ank1.rot_arr_length): + # check if the slope surpaces max representation container + # (truncate the value to the max representation) + value = int(raw.ank1.rot_arr[i]) + if (value > 0x7FFF): + value = 0x7FFF + elif (value < -0x7FFF): + value = -0x7FFF + f.write(struct.pack(endian_ch + "h", value)) + f.write(pad.string_fill(32, raw.ank1.rot_arr_offset + (raw.ank1.rot_arr_length * 2))) + # translation array + for i in range(raw.ank1.transl_arr_length): + f.write(struct.pack(endian_ch + "f", raw.ank1.transl_arr[i])) + f.write(pad.string_fill(32, raw.ank1.transl_arr_offset + (raw.ank1.transl_arr_length * 4))) + + # done! f.close() diff --git a/collada_superbmd_import.py b/collada_superbmd_import.py index 2fa7ef0..c19a91a 100644 --- a/collada_superbmd_import.py +++ b/collada_superbmd_import.py @@ -38,7 +38,7 @@ def import_collada_superbmd(context, filepath): # get asset's unit element root = xml.getroot() - + # texture files path if (os.name == "posix" and root.find("library_images") != None): for image in root.find("library_images"): diff --git a/math_funcs.py b/math_funcs.py index ec7f7de..658797a 100644 --- a/math_funcs.py +++ b/math_funcs.py @@ -114,7 +114,7 @@ class best_chs_fits: # for a given a set of points. The set of points is expected to be at # each frame of the animation (start_frame indicates the start frame, integer) # the function will return the above structure -# it will be the generic cubic hermite spline form +# it will be in the general cubic hermite spline form (t0 < tf) def find_best_cubic_hermite_spline_fit(start_frame, values, angle_limit): # check @@ -157,10 +157,11 @@ def find_best_cubic_hermite_spline_fit(start_frame, values, angle_limit): avg_value = 0 for i in range(len(values)): if (abs(values[i]) < lowest_value): - lowest_value = values[i] + lowest_value = abs(values[i]) if (abs(values[i]) > highest_value): - highest_value = values[i] + highest_value = abs(values[i]) avg_value += abs(values[i]) + print(highest_value) scale_factor = (1 / highest_value) * (len(values) - start_frame) avg_value /= len(values) diff --git a/smg_common.py b/smg_common.py index 3c7bb0f..4fa4017 100644 --- a/smg_common.py +++ b/smg_common.py @@ -1,7 +1,7 @@ # padding stuff # padding string -class padding(): +class padding: # the fill string def __init__(self): @@ -14,16 +14,16 @@ class padding(): if ((type(start_index) != int) or (type(byte_alignment) != int) or (start_index < 0) - or (byte_alignment != 4 and byte_alignment != 32): + or (byte_alignment != 4 and byte_alignment != 32)): return None # return the fill string i = 0 rtn = "" while (start_index % byte_alignment != 0): - rtn += self.padding[i] + rtn += self.string[i] i += 1 start_index += 1 - return rtn + return bytes(rtn.encode("ascii")) # name tables @@ -1,26 +1,29 @@ -import smg_name_table_funcs +from . import bck_funcs + +print(bck_funcs.smg_bck_raw.anim_data) +print(bck_funcs.smg_bck_anim.anim_data) # I think I am making my own way to handle bytes # ~ class owl_byte(int): # ~ def __str__(self): # ~ return "%0X" % (self) -# I think I am making my own way to print bytes -# the default printing is awful to look at and I need rapid inspection -class owl_bytes(bytearray): - def __str__(self): - rtn = "" - for i in range(len(self)): - rtn += "%02X" % self[i] - rtn += " " - return rtn +# ~ # I think I am making my own way to print bytes +# ~ # the default printing is awful to look at and I need rapid inspection +# ~ class owl_bytes(bytearray): + # ~ def __str__(self): + # ~ rtn = "" + # ~ for i in range(len(self)): + # ~ rtn += "%02X" % self[i] + # ~ rtn += " " + # ~ return rtn -f = open("test.bin", "rb") -byte_arr = owl_bytes(b"") -byte = f.read(1) -while (byte != b""): - byte_arr += byte - byte = f.read(1) -f.close() +# ~ f = open("test.bin", "rb") +# ~ byte_arr = owl_bytes(b"") +# ~ byte = f.read(1) +# ~ while (byte != b""): + # ~ byte_arr += byte + # ~ byte = f.read(1) +# ~ f.close() -print(byte_arr) +# ~ print(byte_arr) |