summaryrefslogtreecommitdiff
path: root/bck_funcs.py
diff options
context:
space:
mode:
authorOwl <isaclien9752@gmail.com>2025-08-23 00:29:45 -0400
committerOwl <isaclien9752@gmail.com>2025-08-23 00:29:45 -0400
commita5bdfdab2ec68ebfe99535a54b3b0cd8b95a87d6 (patch)
tree3cbc818cc7a9e221697fe0be05b9552fd6d33f09 /bck_funcs.py
parent51077c2fe8c160743a67303fb516126bb98afff7 (diff)
downloadblenxy-a5bdfdab2ec68ebfe99535a54b3b0cd8b95a87d6.tar.gz
blenxy-a5bdfdab2ec68ebfe99535a54b3b0cd8b95a87d6.zip
bck exporter seems to be working
Diffstat (limited to 'bck_funcs.py')
-rw-r--r--bck_funcs.py227
1 files changed, 146 insertions, 81 deletions
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()