From a622a91e283ea4c1d0bbcca511c11ad776cfdcd4 Mon Sep 17 00:00:00 2001 From: Lila <87947656+Lilaa3@users.noreply.github.com> Date: Tue, 16 Apr 2024 22:30:36 +0100 Subject: [PATCH] some drafting --- fast64_internal/sm64/animation/classes.py | 554 ++++++++++--------- fast64_internal/sm64/animation/exporting.py | 37 +- fast64_internal/sm64/animation/importing.py | 62 ++- fast64_internal/sm64/animation/operators.py | 36 +- fast64_internal/sm64/animation/properties.py | 132 +++-- fast64_internal/sm64/animation/utility.py | 14 +- fast64_internal/sm64/sm64_constants.py | 8 +- 7 files changed, 493 insertions(+), 350 deletions(-) diff --git a/fast64_internal/sm64/animation/classes.py b/fast64_internal/sm64/animation/classes.py index dfc25d602..f6c6a08cc 100644 --- a/fast64_internal/sm64/animation/classes.py +++ b/fast64_internal/sm64/animation/classes.py @@ -7,8 +7,21 @@ from ...utility import PluginError, decodeSegmentedAddr, is_bit_active from ..sm64_constants import MAX_U16 from ..sm64_utility import SM64_ShortArray + from .utility import RomReading, eval_num_from_str -from .c_parser import CParser, Initialization +from .c_parser import Initialization, ParsedValue + +HEADER_SIZE = 0x18 +int_to_c_flags = { + 0: "ANIM_FLAG_NOLOOP", + 1: "ANIM_FLAG_FORWARD", + 2: "ANIM_FLAG_NO_ACCEL", + 3: "ANIM_FLAG_HOR_TRANS", + 4: "ANIM_FLAG_VERT_TRANS", + 5: "ANIM_FLAG_DISABLED", + 6: "ANIM_FLAG_NO_TRANS", + 7: "ANIM_FLAG_UNUSED", +} @dataclasses.dataclass @@ -52,27 +65,77 @@ def read_c(self, maxFrame, offset, values: list[int]): self.values.append(value) -HEADER_SIZE = 0x18 -int_to_c_flags = { - 0: "ANIM_FLAG_NOLOOP", - 1: "ANIM_FLAG_FORWARD", - 2: "ANIM_FLAG_NO_ACCEL", - 3: "ANIM_FLAG_HOR_TRANS", - 4: "ANIM_FLAG_VERT_TRANS", - 5: "ANIM_FLAG_DISABLED", - 6: "ANIM_FLAG_NO_TRANS", - 7: "ANIM_FLAG_UNUSED", -} +@dataclasses.dataclass +class SM64_AnimData: + pairs: list[SM64_AnimPair] = dataclasses.field(default_factory=list) + indice_reference: str = "" + values_reference: str = "" + # Import + indices_file_name: str = "" + values_file_name: str = "" + + def to_c(self, is_dma_structure: bool): + data = StringIO() + + value_table, indice_tables = create_tables([self]) + indice_table = indice_tables[0] + + if is_dma_structure: + data.write(indice_table.to_c()) + data.write("\n\n") + data.write(value_table.to_c()) + else: + data.write(value_table.to_c()) + data.write("\n\n") + data.write(indice_table.to_c()) + + return data.getvalue() + + def to_binary(self, start_address: int) -> bytearray: + data: bytearray = bytearray() + + value_table, indice_tables = create_tables([self]) + indice_table = indice_tables[0] + + indices_size = len(indice_table.data) * 2 + values_offset = start_address + indices_size + values_address = start_address, values_offset + + data.extend(indice_table.to_binary()) + data.extend(value_table.to_binary()) + + return data, values_address + + def read_binary(self, data: bytes, indices_address, values_address, bone_count: int): + self.indice_reference = indices_address + self.values_reference = values_address + indices_reader = RomReading(data, indices_address) + for _ in range((bone_count + 1) * 3): + pair = SM64_AnimPair() + pair.read_binary(indices_reader, data, values_address) + pair.clean_frames() + self.pairs.append(pair) + + def read_c(self, indices_array: Initialization, values_array: Initialization): + self.indices_file_name = os.path.basename(indices_array.origin_path) + self.values_file_name = os.path.basename(values_array.origin_path) + + self.indice_reference = indices_array.name + self.values_reference = values_array.name + + indices = indices_array.value.value + values = values_array.value.value + for i in range(0, len(indices), 2): + max_frame, offset = indices[i].value, indices[i + 1].value + pair = SM64_AnimPair() + pair.read_c(max_frame, offset, values) + pair.clean_frames() + self.pairs.append(pair) @dataclasses.dataclass class SM64_AnimHeader: - data: "SM64_Anim" = None - - name: str = None - address: int = None - origin_path: str = "" - header_variant: int = 0 + reference: str | int = None flags: int = 0 custom_flags: str = None @@ -80,9 +143,14 @@ class SM64_AnimHeader: start_frame: int = 0 loop_start: int = 0 loop_end: int = 1 - bone_count: int = None - values: str = "" - indices: str = "" + bone_count: int = 0 + + indice_reference: str | int = None + values_reference: str | int = None + + # Imports + header_variant: int = 0 + file_name: str = "" def get_c_flags(self, is_dma_structure: bool, refresh_version: str): if is_dma_structure: @@ -111,79 +179,52 @@ def get_binary_flags(self): return eval_num_from_str(self.custom_flags) return self.flags - def to_c(self, indices_reference: str, values_reference: str, is_dma_structure: bool, refresh_version: str) -> str: + def to_c(self, is_dma_structure: bool, refresh_version: str) -> str: return ( - f"static const struct Animation {self.name}{'[]' if is_dma_structure else ''} = {{\n" + f"static const struct Animation {self.reference}{'[]' if is_dma_structure else ''} = {{\n" f"\t{self.get_c_flags(is_dma_structure, refresh_version)}, // flags\n" f"\t{self.trans_divisor}, // animYTransDivisor\n" f"\t{self.start_frame}, // startFrame\n" f"\t{self.loop_start}, // loopStart\n" f"\t{self.loop_end}, // loopEnd\n" - f"\tANIMINDEX_NUMPARTS({indices_reference}), // unusedBoneCount\n" - f"\t{values_reference}, // values\n" - f"\t{indices_reference}, // index\n" + f"\tANIMINDEX_NUMPARTS({self.indice_reference}), // unusedBoneCount\n" + f"\t{self.values_reference}, // values\n" + f"\t{self.indice_reference}, // index\n" "\t0\n" "};\n" ) - def to_binary(self, indices_reference: str, values_reference: str, bone_count: int): + def to_binary(self, values_reference: int | None = None, indice_reference: int | None = None): data = bytearray() data.extend(self.flags.to_bytes(2, byteorder="big", signed=False)) # 0x00 data.extend(self.trans_divisor.to_bytes(2, byteorder="big", signed=True)) # 0x02 data.extend(self.start_frame.to_bytes(2, byteorder="big", signed=True)) # 0x04 data.extend(self.loop_start.to_bytes(2, byteorder="big", signed=True)) # 0x06 data.extend(self.loop_end.to_bytes(2, byteorder="big", signed=True)) # 0x08 - data.extend(bone_count.to_bytes(2, byteorder="big", signed=True)) # 0x0A + data.extend(self.bone_count.to_bytes(2, byteorder="big", signed=True)) # 0x0A + + if not values_reference: + values_reference = self.values_reference + if not indice_reference: + indice_reference = self.indice_reference + if isinstance(values_reference, str): + values_reference = eval_num_from_str(values_reference) + if isinstance(indice_reference, str): + indice_reference = eval_num_from_str(indice_reference) + data.extend(values_reference.to_bytes(4, byteorder="big", signed=False)) # 0x0C - data.extend(indices_reference.to_bytes(4, byteorder="big", signed=False)) # 0x10 + data.extend(indice_reference.to_bytes(4, byteorder="big", signed=False)) # 0x10 data.extend(bytearray([0x00] * 4)) # 0x14 # Unused with no porpuse # 0x18 return data # Importing - def toHeaderProps(self, action, header): - flagsToProps = { # TODO: Fix this whole thing its so cringe help me oh god help me - "ANIM_FLAG_NOLOOP": "no_loop", - "ANIM_FLAG_BACKWARD": "backwards", - "ANIM_FLAG_NO_ACCEL": "no_acceleration", - "ANIM_FLAG_DISABLED": "disabled", - "ANIM_FLAG_HOR_TRANS": "only_horizontal_trans", - "ANIM_FLAG_VERT_TRANS": "only_vertical_trans", - "ANIM_FLAG_NO_TRANS": "no_trans", - } - - header.action = action - - if self.name: - header.custom_name = self.name - header.override_name = True - - correctFrameRange = self.start_frame, self.loop_start, self.loop_end - header.start_frame, header.loop_start, header.loop_end = correctFrameRange - if correctFrameRange != header.get_frame_range(action): # If auto frame range is wrong - header.manual_frame_range = True - - header.trans_divisor = self.trans_divisor - - if isinstance(self.flags, int): - header.customIntFlags = hex(self.flags) - cFlags = [flag for bit, flag in int_to_c_flags.items() if is_bit_active(self.flags, bit)] - header.customFlags = " | ".join(cFlags) - for cFlag in cFlags: - if cFlag in flagsToProps: - setattr(header, flagsToProps[cFlag], True) - else: - header.setCustomFlags = True - else: - header.setCustomFlags = True - header.customFlags = self.flags - def read_binary( - self, data: bytes, address: int, segment_data: Optional[dict[int, tuple[int, int]]], isDMA: bool = False + self, data: bytes, address: int, segment_data: Optional[dict[int, tuple[int, int]]], is_dma: bool = False ): reader = RomReading(data, address) - self.address = address + self.reference = address self.flags = reader.read_value(2, signed=False) # /*0x00*/ s16 flags; self.trans_divisor = reader.read_value(2) # /*0x02*/ s16 animYTransDivisor; self.start_frame = reader.read_value(2) # /*0x04*/ s16 startFrame; @@ -195,18 +236,19 @@ def read_binary( values_offfset = reader.read_value(4) # /*0x0C*/ const s16 *values; indices_offset = reader.read_value(4) # /*0x10*/ const u16 *index; - if isDMA: - self.values = address + values_offfset - self.indices = address + indices_offset + if is_dma: + self.values_reference = address + values_offfset + self.indice_reference = address + indices_offset elif segment_data: - self.values = decodeSegmentedAddr(values_offfset.to_bytes(4, byteorder="big"), segment_data) - self.indices = decodeSegmentedAddr(indices_offset.to_bytes(4, byteorder="big"), segment_data) + self.values_reference = decodeSegmentedAddr(values_offfset.to_bytes(4, byteorder="big"), segment_data) + self.indice_reference = decodeSegmentedAddr(indices_offset.to_bytes(4, byteorder="big"), segment_data) else: - self.values = values_offfset - self.indices = indices_offset + self.values_reference = values_offfset + self.indice_reference = indices_offset def read_c(self, value: Initialization): - self.name = value.name + self.file_name = os.path.basename(value.origin_path) + self.reference = value.name value.set_attributes_from_struct( self, @@ -218,100 +260,78 @@ def read_c(self, value: Initialization): "loopStart": "loop_start", "loopEnd": "loop_end", "unusedBoneCount": "bone_count", - "values": "values", - "index": "indices", + "values": "values_reference", + "index": "indice_reference", "length": "length", }, ), ) - self.origin_path = value.origin_path + def to_props(self, action, header): + flagsToProps = { # TODO: Fix this whole thing its so cringe help me oh god help me + "ANIM_FLAG_NOLOOP": "no_loop", + "ANIM_FLAG_BACKWARD": "backwards", + "ANIM_FLAG_NO_ACCEL": "no_acceleration", + "ANIM_FLAG_DISABLED": "disabled", + "ANIM_FLAG_HOR_TRANS": "only_horizontal_trans", + "ANIM_FLAG_VERT_TRANS": "only_vertical_trans", + "ANIM_FLAG_NO_TRANS": "no_trans", + } + header.action = action -@dataclasses.dataclass -class SM64_Anim: - indices_reference: str = "" - values_reference: str = "" - reference: bool = False - headers: list[SM64_AnimHeader] = dataclasses.field(default_factory=list) - pairs: list[SM64_AnimPair] = dataclasses.field(default_factory=list) - action_name: str = None - file_name: str = None - - def create_tables(self): - - def index_sub_seq_in_seq(sub_seq, seq): - i, seq_length, sub_length = -1, len(seq), len(seq) - try: - while True: - i = seq.index(sub_seq[0], i + 1, seq_length - sub_length + 1) - if sub_seq == seq[i : i + sub_length]: - return i - except ValueError: - return -1 - - value_table, indices_table = ( - SM64_ShortArray(self.values_reference, True), - SM64_ShortArray(self.indices_reference, False), - ) + if isinstance(self.reference, str): + header.custom_name = self.reference + header.override_name = True - # Generate compressed value table and offsets - value_table_parts: list[(list[SM64_AnimPair], list[int])] = [] - for pair in self.pairs: - values = pair.values - max_frame = len(values) - if max_frame > MAX_U16: - raise PluginError("Pair frame count is higher than the 16 bit max") - - for value_table_part_pairs, value_table_part in value_table_parts: - index = index_sub_seq_in_seq(values, value_table_part) - if index != -1: - pair.offset = index - value_table_part_pairs.append(pair) - break - # TODO: Add more extensive compression in the future - else: - value_table_parts.append(([pair], values)) - pair.offset = 0 + correctFrameRange = self.start_frame, self.loop_start, self.loop_end + header.start_frame, header.loop_start, header.loop_end = correctFrameRange + if correctFrameRange != header.get_frame_range(action): # If auto frame range is wrong + header.manual_frame_range = True - for value_table_part_pairs, value_table_part in value_table_parts: - for pair in value_table_part_pairs: - pair.offset += len(value_table.data) - if pair.offset > MAX_U16: - raise PluginError("Pair offset is higher than the 16 bit max.") - value_table.data.extend(value_table_part) + header.trans_divisor = self.trans_divisor + + if isinstance(self.flags, int): + header.customIntFlags = hex(self.flags) + cFlags = [flag for bit, flag in int_to_c_flags.items() if is_bit_active(self.flags, bit)] + header.customFlags = " | ".join(cFlags) + for cFlag in cFlags: + if cFlag in flagsToProps: + setattr(header, flagsToProps[cFlag], True) + else: + header.setCustomFlags = True + else: + header.setCustomFlags = True + header.customFlags = self.flags - # Use calculated offsets to generate the indices table - for pair in self.pairs: - indices_table.data.extend([len(pair.values), pair.offset]) - return value_table, indices_table +@dataclasses.dataclass +class SM64_Anim: + data: SM64_AnimData = None + headers: list[SM64_AnimHeader] = dataclasses.field(default_factory=list) + action_name: str = None def to_binary(self, is_dma: bool, start_address: int) -> bytearray: data: bytearray = bytearray() ptrs: list[int] = [] - if self.reference: - indices_reference, values_reference = self.indices_reference, self.values_reference - bone_count = 0 - else: - values_table, indices_table = self.create_tables() - indices_offset = HEADER_SIZE * len(self.headers) - indices_size = len(indices_table.data) * 2 - values_offset = indices_offset + indices_size - bone_count = (len(self.pairs) // 3) - 1 - indices_reference, values_reference = start_address + indices_offset, start_address + values_offset + if self.data: + indice_offset = HEADER_SIZE * len(self.headers) + indice_reference = start_address + indice_offset + anim_data, values_reference = self.data.to_binary(indice_reference) for header in self.headers: ptrs.extend([start_address + len(data) + 12, start_address + len(data) + 16]) - header_data = header.to_binary(indices_reference, values_reference, bone_count) + header_data = header.to_binary( + indice_reference if self.data else None, + values_reference if self.data else None, + ) data.extend(header_data) - if not self.reference: - data.extend(indices_table.to_binary()) - data.extend(values_table.to_binary()) + if self.data: + data.extend(anim_data) - if is_dma or self.reference: + if is_dma or not self.data: return data, [] else: return data, ptrs @@ -319,156 +339,176 @@ def to_binary(self, is_dma: bool, start_address: int) -> bytearray: def headers_to_c(self, is_dma_structure: bool, refresh_version: str) -> str: data = StringIO() for header in self.headers: - data.write(header.to_c(self.indices_reference, self.values_reference, is_dma_structure, refresh_version)) + data.write(header.to_c(is_dma_structure, refresh_version)) data.write("\n") return data.getvalue() - def data_to_c(self, is_dma_structure: bool): - if self.reference: - return - - values_table, indices_table = self.create_tables() - + def to_c(self, is_dma_structure: bool, refresh_version: str): data = StringIO() - if is_dma_structure: - data.write(indices_table.to_c()) - data.write("\n\n") - data.write(values_table.to_c()) - else: - data.write(values_table.to_c()) - data.write("\n\n") - data.write(indices_table.to_c()) - - return data.getvalue() + table_data = None + if self.data: + table_data = self.data.to_c(is_dma_structure) - def to_c(self, is_dma_structure: bool, refresh_version: str): - data = StringIO() - c_data = self.data_to_c(is_dma_structure) c_headers = self.headers_to_c(is_dma_structure, refresh_version) if is_dma_structure: data.write(c_headers) - if c_data: + if table_data: data.write("\n") - data.write(c_data) + data.write(table_data) else: - if c_data: - data.write(c_data) + if table_data: + data.write(table_data) data.write("\n") data.write(c_headers) return data.getvalue() # Importing - def to_action(self, action, remove_name_footer: bool = True): + def to_props(self, action, remove_name_footer: bool = True): action_props = action.fast64.sm64 + main_header = self.headers[0] + if not self.action_name: - if self.headers[0].name: - if remove_name_footer and self.headers[0].name.startswith("anim_"): - self.action_name = self.headers[0].name.replace("anim_", "", 1) + if main_header.reference: + if remove_name_footer and main_header.reference.startswith("anim_"): + self.action_name = main_header.reference.replace("anim_", "", 1) else: - self.action_name = self.headers[0].name - else: - self.action_name = hex(self.headers[0].address) - action.name = self.action_name - - if self.file_name: - action_props.custom_file_name = self.file_name - if action_props.get_anim_file_name(action) != self.file_name: - action_props.override_file_name = True + self.action_name = main_header.reference + if self.action_name: + action.name = self.action_name - action_props.indices_table, action_props.indices_address = self.indices_reference, self.indices_reference - action_props.values_table, action_props.values_address = self.values_reference, self.values_reference + action_props.indices_table, action_props.indices_address = ( + str(main_header.indice_reference), + str(main_header.indice_reference), + ) + action_props.values_table, action_props.values_address = ( + str(main_header.values_reference), + str(main_header.values_reference), + ) - self.reference_tables = self.reference + if self.data: + action_props.custom_file_name = self.data.indices_file_name + action_props.custom_max_frame = max([1] + [len(x.values) for x in self.data.pairs]) + else: + action_props.custom_file_name = main_header.file_name + action_props.reference_tables = True - action_props.custom_max_frame = max([1] + [len(x.values) for x in self.pairs]) + if action_props.get_anim_file_name(action) != action_props.custom_file_name: + action_props.override_file_name = True for i in range(len(self.headers) - 1): action_props.header_variants.add() for header, header_props in zip(self.headers, action_props.headers): - header.toHeaderProps(action, header_props) + header.to_props(action, header_props) action_props.update_header_variant_numbers() - def read_binary(self, data: bytes, header: SM64_AnimHeader): - self.indices_reference = hex(header.indices) - self.values_reference = hex(header.values) - - indices_reader = RomReading(data, header.indices) - for _ in range((header.bone_count + 1) * 3): - pair = SM64_AnimPair() - pair.read_binary(indices_reader, data, header.values) - pair.clean_frames() - self.pairs.append(pair) - - def read_c(self, header: SM64_AnimHeader, c_parser: CParser): - self.indices_reference = header.indices - self.values_reference = header.values - - if self.indices_reference in c_parser.values_by_name and self.values_reference in c_parser.values_by_name: - indicesArray = c_parser.values_by_name[self.indices_reference] - valuesArray = c_parser.values_by_name[self.values_reference] - self.file_name = os.path.basename(indicesArray.origin_path) - else: - self.file_name = os.path.basename(header.origin_path) - self.reference = True - return - - indices = indicesArray.value.value - values = valuesArray.value.value - for i in range(0, len(indices), 2): - maxFrame, offset = indices[i].value, indices[i + 1].value - pair = SM64_AnimPair() - pair.read_c(maxFrame, offset, values) - pair.clean_frames() - self.pairs.append(pair) - @dataclasses.dataclass class SM64_AnimTable: name: str = "" - elements: list[SM64_AnimHeader] = dataclasses.field(default_factory=list) - enum_list_name: str = "" - enum_indexed: bool = True - enum_list: set[str] = dataclasses.field(default_factory=set) - enum_indexed_elements: list[tuple[str, SM64_AnimHeader]] = dataclasses.field(default_factory=list) - - def to_binary(self): - pass + elements: list[SM64_AnimHeader, SM64_AnimData] = dataclasses.field(default_factory=list) - def enum_list_to_c(self): - data = StringIO() - data.write(f"enum {self.enum_list_name} = {{") - for enum in self.enum_list: - data.write(f"\t{enum},\n") - data.write("};\n") - return data.getvalue() + def to_binary(self, is_dma: bool, start_address: int): + data: bytearray = bytearray() + ptrs: list[int] = [] - def to_c(self): - data = StringIO() - if self.enum_indexed: - data.write(self.enum_list_to_c()) + anim_data_set = [] + headers_set = [] + for anim_header, anim_data in self.elements: + if anim_data and not anim_data in anim_data_set: + anim_data_set.append(anim_data) + if not anim_header in headers_set: + headers_set.append(anim_header) + value_table, indice_tables = create_tables(anim_data_set, "values") + + table_offset = start_address + table_length = len(self.elements) * 4 + headers_offset = table_offset + table_length + headers_length = len(headers_set) * HEADER_SIZE + indice_tables_offset = start_address + headers_length + indice_tables_length = sum([len(indice_table.data) for indice_table in indice_tables]) + values_table_offset = indice_tables_length + + for anim_header, anim_data in self.elements: + ptrs.append(len(data)) + header_offset = headers_offset + (headers_set.index(anim_header) * HEADER_SIZE) + data.extend(header_offset.to_bytes(4, byteorder="big", signed=False)) + + for anim_header, anim_data in self.elements: + if anim_data: + indice_offset = sum( + [len(indice_table.data) for indice_table in indice_tables[: anim_data_set.index(anim_data)]] + ) + data.extend( + anim_header.to_binary( + indice_tables_offset + indice_offset, + values_table_offset, + ) + ) + else: + ptrs.append(len(data)) + data.extend(anim_header.to_binary()) - data.write("const struct Animation *const = {") - if self.enum_indexed: - for enum, element in self.enum_indexed_elements: - data.write(f"\t[{enum}] = {element},\n") - else: - for enum, element in self.elements: - data.write(f"\t{element},\n") - data.write("};\n") + for indice_table in indice_tables: + data.extend(indice_table.to_binary()) + data.extend(value_table.to_binary()) - def read_binary(self): + def to_c(self, is_dma: bool, start_address: int): pass - def read_c_enum_list(self): - pass - def read_c_table(self): - pass +def create_tables(anims_data: list[SM64_AnimData], values_name: str = None): + """Can generate multiple indices table with only one value table, which improves compression""" + """This feature is used in table exports""" - def read_c(self): - pass + def index_sub_seq_in_seq(sub_seq, seq): + i, seq_length, sub_length = -1, len(seq), len(seq) + try: + while True: + i = seq.index(sub_seq[0], i + 1, seq_length - sub_length + 1) + if sub_seq == seq[i : i + sub_length]: + return i + except ValueError: + return -1 + + value_table = SM64_ShortArray(values_name if values_name else anims_data[0].values_reference, True) + + all_pairs = [pair for anim_data in anims_data for pair in anim_data.pairs] + # Generate compressed value table and offsets + value_table_parts: list[(list[SM64_AnimPair], list[int])] = [] + for pair in all_pairs: + values = pair.values + max_frame = len(values) + if max_frame > MAX_U16: + raise PluginError("Pair frame count is higher than the 16 bit max") + + for value_table_part_pairs, value_table_part in value_table_parts: + index = index_sub_seq_in_seq(values, value_table_part) + if index != -1: + pair.offset = index + value_table_part_pairs.append(pair) + break + # TODO: Add more extensive compression in the future + else: + value_table_parts.append(([pair], values)) + pair.offset = 0 + + for value_table_part_pairs, value_table_part in value_table_parts: + for pair in value_table_part_pairs: + pair.offset += len(value_table.data) + if pair.offset > MAX_U16: + raise PluginError("Pair offset is higher than the 16 bit max.") + value_table.data.extend(value_table_part) + + indice_tables: list[SM64_ShortArray] = [] + # Use calculated offsets to generate the indices table + for anim_data in anims_data: + indice_table = SM64_ShortArray(anim_data.indice_reference, False) + for pair in anim_data.pairs: + indice_table.data.extend([len(pair.values), pair.offset]) + indice_tables.append(indice_table) + return value_table, indice_tables diff --git a/fast64_internal/sm64/animation/exporting.py b/fast64_internal/sm64/animation/exporting.py index 31f5677e5..6b9b75299 100644 --- a/fast64_internal/sm64/animation/exporting.py +++ b/fast64_internal/sm64/animation/exporting.py @@ -2,15 +2,17 @@ import bpy import mathutils -from bpy.types import Context, Object, Scene, Action +from bpy.types import Object, Scene, Action from bpy.path import abspath from ...utility import ( + PluginError, radians_to_s16, writeIfNotFound, writeInsertableFile, ) -from ..sm64_constants import NULL +from ..sm64_utility import SM64_ShortArray +from ..sm64_constants import MAX_U16, NULL from .classes import SM64_Anim, SM64_AnimPair from .utility import anim_name_to_enum, get_anim_pose_bones @@ -147,8 +149,9 @@ def update_table_file( include_text = '#include "table_enum.h"\n' include_index = text.find(include_text) if include_index == -1: # If there is no table, add one and find again - text += include_text + text = include_text + text include_index = text.find(include_text) + # Table table_index = text.find(table_name) if table_index == -1: # If there is no table, add one and find again @@ -157,20 +160,20 @@ def update_table_file( for header_name in header_names: header_reference = f"&{header_name}" - if text.find(header_reference, table_index) == -1: - table_end = text.find("\tNULL", table_index) - if table_end == -1: # Just in case the NULL has no tab - table_end = text.find("NULL", table_index) - if table_end == -1: - table_end = text.find("}", table_index) - if generate_enums: - text = ( - text[:table_end] - + f"\t[{anim_name_to_enum(header_name)}] = {header_reference},\n" - + text[table_end:] - ) - else: - text = text[:table_end] + f"\t{header_reference},\n" + text[table_end:] + header_by_enum_reference = f"[{anim_name_to_enum(header_name)}] = &{header_name}" + if text.find(header_reference, table_index) != -1 and ( + not generate_enums or text.find(header_by_enum_reference, table_index) != -1 + ): + continue + + table_end = text.find("NULL", table_index) + if table_end == -1: + table_end = text.find("}", table_index) + + if generate_enums: + text = text[:table_end] + f"\t[{anim_name_to_enum(header_name)}] = {header_reference},\n" + text[table_end:] + else: + text = text[:table_end] + f"\t{header_reference},\n" + text[table_end:] with open(table_path, "w", newline="\n") as file: file.write(text) diff --git a/fast64_internal/sm64/animation/importing.py b/fast64_internal/sm64/animation/importing.py index 38613a0d2..c72856a30 100644 --- a/fast64_internal/sm64/animation/importing.py +++ b/fast64_internal/sm64/animation/importing.py @@ -14,8 +14,8 @@ from ..sm64_constants import insertableBinaryTypes from .utility import RomReading, get_anim_pose_bones, sm64_to_radian -from .classes import SM64_Anim, SM64_AnimHeader, SM64_AnimPair, SM64_AnimTable -from .c_parser import CParser, Initialization +from .classes import SM64_Anim, SM64_AnimData, SM64_AnimHeader, SM64_AnimPair, SM64_AnimTable +from .c_parser import CParser, EnumIndexedValue, Initialization def value_distance(e1: Euler, e2: Euler) -> float: @@ -95,7 +95,7 @@ def animation_data_to_blender( pose_bone.rotation_mode = "QUATERNION" action = bpy.data.actions.new("") - anim_import.to_action(action, remove_name_footer) + anim_import.to_props(action, remove_name_footer) if armature_obj.animation_data is None: armature_obj.animation_data_create() @@ -103,10 +103,13 @@ def animation_data_to_blender( stashActionInArmature(armature_obj, action) armature_obj.animation_data.action = action + if not anim_import.data: + return + bone_anim_data: list[SM64_AnimBone] = [] # TODO: Duplicate keyframe filter - pairs = anim_import.pairs + pairs = anim_import.data.pairs for pair_num in range(3, len(pairs), 3): bone = SM64_AnimBone() if pair_num == 3: @@ -145,18 +148,23 @@ def import_animation_from_c_header( header = SM64_AnimHeader() header.read_c(header_initialization) - data_key = f"{header.indices}-{header.values}" + data_key = f"{header.indice_reference}-{header.values_reference}" if data_key in animations: - data = animations[data_key] + anim = animations[data_key] else: - data = SM64_Anim() - data.read_c(header, c_parser) - animations[data_key] = data + anim = SM64_Anim() + if header.indice_reference in c_parser.values_by_name and header.values_reference in c_parser.values_by_name: + indices_array = c_parser.values_by_name[header.indice_reference] + values_array = c_parser.values_by_name[header.values_reference] + anim.data = SM64_AnimData() + anim.data.read_c(indices_array, values_array) + + animations[data_key] = anim - header.header_variant = len(data.headers) - header.data = data - data.headers.append(header) + header.header_variant = len(anim.headers) + header.data = anim + anim.headers.append(header) return header @@ -194,12 +202,14 @@ def import_c_animations(path: str, animations: dict[str, SM64_Anim], table: SM64 table_initialization = value else: header = import_animation_from_c_header(animations, value, c_parser) - all_headers[header.name] = header + all_headers[header.reference] = header if table_initialization: # If a table was found for element in table_initialization.value.value: - # TODO: Add suport for enum indexed tables - name = element.value[1:] + if isinstance(element, EnumIndexedValue): + name = element.value.value[1:] + else: + name = element.value[1:] if name in all_headers: table.elements.append(all_headers[name]) table.name = table_initialization.name @@ -217,17 +227,21 @@ def import_binary_header( header = SM64_AnimHeader() header.read_binary(data, address, segment_data, is_dma) - data_key = f"{header.indices}-{header.values}" + data_key = f"{header.indice_reference}-{header.values_reference}" if data_key in animations: - anim_data = animations[data_key] + anim = animations[data_key] else: - anim_data = SM64_Anim() - anim_data.read_binary(data, header) - animations[data_key] = anim_data - - header.header_variant = len(anim_data.headers) - header.data = anim_data - anim_data.headers.append(header) + anim = SM64_Anim() + if header.indice_reference < len(data) and header.values_reference < len(data): + anim.data = SM64_AnimData() + anim.data.read_binary( + data, header.indice_reference, header.values_reference, header.bone_count + ) # Add suport for using the bone ocunt in the selected armature + animations[data_key] = anim + + header.header_variant = len(anim.headers) + header.data = anim + anim.headers.append(header) return header diff --git a/fast64_internal/sm64/animation/operators.py b/fast64_internal/sm64/animation/operators.py index ee5582e99..eba634690 100644 --- a/fast64_internal/sm64/animation/operators.py +++ b/fast64_internal/sm64/animation/operators.py @@ -1,8 +1,3 @@ -from cProfile import Profile -import math -import os -from pstats import SortKey, Stats - import bpy from bpy.utils import register_class, unregister_class from bpy.types import Context, Object, Scene, Operator @@ -13,6 +8,8 @@ IntProperty, ) +import os + from ...utility import ( PluginError, applyBasicTweaks, @@ -276,12 +273,18 @@ def execute_operator(self, context: Context): action, armature_obj, sm64_props.blender_to_sm64_scale, + sm64_props.export_type == "C" or not anim_export_props.binary_is_dma, actor_name, ) anim_file_name = action_props.get_anim_file_name(action) anim_path = os.path.join(anim_dir_path, anim_file_name) with open(anim_path, "w", newline="\n") as file: - file.write(animation.to_c(anim_export_props.is_c_dma_structure, sm64_props.refresh_version)) + file.write( + animation.to_c( + anim_export_props.is_c_dma_structure, + sm64_props.refresh_version, + ) + ) if header_type != "DMA": table_name = table_props.get_anim_table_name(actor_name) @@ -348,7 +351,11 @@ def execute_operator(self, context: Context): actor_name = anim_export_props.actor_name animation: SM64_Anim = action_props.to_animation_class( - action, armature_obj, sm64_props.blender_to_sm64_scale, actor_name + action, + armature_obj, + sm64_props.blender_to_sm64_scale, + sm64_props.export_type == "C" or not anim_export_props.binary_is_dma, + actor_name, ) if sm64_props.export_type == "C": header_type = anim_export_props.header_type @@ -363,19 +370,28 @@ def execute_operator(self, context: Context): applyBasicTweaks(abspath(sm64_props.decomp_path)) with open(anim_path, "w", newline="\n") as file: - file.write(animation.to_c(anim_export_props.is_c_dma_structure, sm64_props.refresh_version)) + file.write( + animation.to_c( + anim_export_props.is_c_dma_structure, + sm64_props.refresh_version, + ) + ) if header_type != "DMA": table_name = table_props.get_anim_table_name(actor_name) enum_list_name = table_props.get_enum_list_name(actor_name) - write_anim_header(os.path.join(geo_dir_path, "anim_header.h"), table_name, table_props.generate_enums) + write_anim_header( + os.path.join(geo_dir_path, "anim_header.h"), + table_name, + table_props.generate_enums, + ) update_table_file( os.path.join(anim_dir_path, "table.inc.c"), [header.get_anim_name(actor_name, action) for header in action_props.headers], table_name, table_props.generate_enums, - table_props.override_files, + False, os.path.join(anim_dir_path, "table_enum.h"), enum_list_name, ) diff --git a/fast64_internal/sm64/animation/properties.py b/fast64_internal/sm64/animation/properties.py index b8d5a3946..ffe250406 100644 --- a/fast64_internal/sm64/animation/properties.py +++ b/fast64_internal/sm64/animation/properties.py @@ -44,14 +44,14 @@ SM64_AnimVariantOperations, SM64_PreviewAnimOperator, ) -from .classes import SM64_Anim, SM64_AnimHeader, SM64_AnimTable +from .classes import SM64_Anim, SM64_AnimData, SM64_AnimHeader, SM64_AnimTable from .constants import ( enumAnimImportTypes, enumAnimBinaryImportTypes, marioAnimationNames, enumAnimExportTypes, ) -from .utility import anim_name_to_enum +from .utility import anim_name_to_enum, get_anim_pose_bones from .exporting import get_animation_pairs @@ -175,11 +175,19 @@ def get_int_flags(self): return flags - def to_header_class(self, animation: SM64_Anim, action: Action, actor_name: str = ""): + def to_header_class( + self, + animation: SM64_Anim, + action: Action, + values_reference: int | str, + indice_reference: int | str, + bone_count: int, + actor_name: str = "", + ): header = SM64_AnimHeader() header.data = animation - header.name = self.get_anim_name(actor_name, action) + header.reference = self.get_anim_name(actor_name, action) if self.set_custom_flags: header.custom_flags = self.custom_flags @@ -192,7 +200,9 @@ def to_header_class(self, animation: SM64_Anim, action: Action, actor_name: str header.start_frame = start_frame header.loop_start = loop_start header.loop_end = loop_end - header.bone_count = 0 # TODO: Pass bone count here + header.values_reference = values_reference + header.indice_reference = indice_reference + header.bone_count = bone_count return header @@ -351,26 +361,53 @@ def get_max_frame(self, action: Action) -> int: return max(loop_ends) + def to_data_class( + self, + action: Action, + armature_obj: Object, + blender_to_sm64_scale: float, + ): + data = SM64_AnimData() + pairs = get_animation_pairs(blender_to_sm64_scale, action, armature_obj) + data_name: str = toAlnum(f"anim_{action.name}") + values_reference = f"{data_name}_values" + indice_reference = f"{data_name}_indices" + data.pairs = pairs + data.values_reference, data.indice_reference = values_reference, indice_reference + return data + def to_animation_class( - self, action: Action, armature_obj: Object, blender_to_sm64_scale: float, actor_name: str - ) -> SM64_Anim: + self, + action: Action, + armature_obj: Object, + blender_to_sm64_scale: float, + can_use_references: bool, + actor_name: str, + ): animation = SM64_Anim() - if self.reference_tables: - animation.reference = True - animation.values_reference = self.values_table - animation.indices_reference = self.indices_table + if can_use_references and self.reference_tables: + values_reference = self.values_table + indice_reference = self.indices_table else: - data_name: str = toAlnum(f"anim_{action.name}") - animation.values_reference = f"{data_name}_values" - animation.indices_reference = f"{data_name}_indices" - animation.pairs = get_animation_pairs(blender_to_sm64_scale, action, armature_obj) - + animation.data = self.to_data_class(action, armature_obj, blender_to_sm64_scale) + values_reference = animation.data.values_reference + indice_reference = animation.data.indice_reference + bone_count = len(get_anim_pose_bones(armature_obj)) for header_props in self.headers: - animation.headers.append(header_props.to_header_class(animation, action, actor_name)) + animation.headers.append( + header_props.to_header_class( + animation, + action, + values_reference, + indice_reference, + bone_count, + actor_name, + ) + ) return animation - def drawHeaderVariant( + def draw_variant( self, action: Action, layout: UILayout, @@ -385,7 +422,11 @@ def drawHeaderVariant( op_row = col.row() remove_op = op_row.operator(SM64_AnimVariantOperations.bl_idname, icon="REMOVE") - remove_op.array_index, remove_op.type, remove_op.action_name = array_index, "REMOVE", action.name + remove_op.array_index, remove_op.type, remove_op.action_name = ( + array_index, + "REMOVE", + action.name, + ) add_op = op_row.operator(SM64_AnimVariantOperations.bl_idname, icon="ADD") add_op.array_index, add_op.type, add_op.action_name = array_index, "ADD", action.name @@ -393,12 +434,23 @@ def drawHeaderVariant( move_up_col = op_row.column() move_up_col.enabled = array_index != 0 move_up_op = move_up_col.operator(SM64_AnimVariantOperations.bl_idname, icon="TRIA_UP") - move_up_op.array_index, move_up_op.type, move_up_op.action_name = array_index, "MOVE_UP", action.name + move_up_op.array_index, move_up_op.type, move_up_op.action_name = ( + array_index, + "MOVE_UP", + action.name, + ) move_down_col = op_row.column() move_down_col.enabled = array_index != len(self.header_variants) - 1 - moveDown = move_down_col.operator(SM64_AnimVariantOperations.bl_idname, icon="TRIA_DOWN") - moveDown.array_index, moveDown.type, moveDown.action_name = array_index, "MOVE_DOWN", action.name + move_down_op = move_down_col.operator( + SM64_AnimVariantOperations.bl_idname, + icon="TRIA_DOWN", + ) + move_down_op.array_index, move_down_op.type, move_down_op.action_name = ( + array_index, + "MOVE_DOWN", + action.name, + ) col.prop( header, @@ -430,12 +482,12 @@ def draw_variants( if not self.expandVariantsTab: return - opRow = col.row() - add_op = opRow.operator(SM64_AnimVariantOperations.bl_idname, icon="ADD") + op_row = col.row() + add_op = op_row.operator(SM64_AnimVariantOperations.bl_idname, icon="ADD") add_op.array_index, add_op.type, add_op.action_name = -1, "ADD", action.name if self.header_variants: - clear_op = opRow.operator(SM64_AnimVariantOperations.bl_idname, icon="TRASH") + clear_op = op_row.operator(SM64_AnimVariantOperations.bl_idname, icon="TRASH") clear_op.type, clear_op.action_name = "CLEAR", action.name box = col.box().column() @@ -443,7 +495,7 @@ def draw_variants( for i, variant in enumerate(self.header_variants): if i != 0: box.separator(factor=2.0) - self.drawHeaderVariant(action, box, variant, i, actor_name, generate_enums, is_binary_dma, export_type) + self.draw_variant(action, box, variant, i, actor_name, generate_enums, is_binary_dma, export_type) def draw_references(self, layout: UILayout): col = layout.column() @@ -566,16 +618,10 @@ def from_anim_table_class(self, table: SM64_AnimTable): def to_anim_table_class(self, actor_name: str): table = SM64_AnimTable() table.name = self.get_anim_table_name(actor_name) - table.elements = [element.header for element in self.elements] - table.enum_list_name = self.get_enum_list_name(actor_name) - table.enum_indexed = self.generate_enums - table.enum_list = [element.header.get_anim_enum(actor_name, element.action) for element in self.elements] - table.enum_indexed_elements = [ - (element.header.get_anim_enum(actor_name, element.action), element.header) for element in self.elements - ] + # table.elements = [element.header. for element in self.elements] return table - def drawTableElement(self, layout: UILayout, table_index: int, table_element, actor_name: str, export_type: str): + def draw_element(self, layout: UILayout, table_index: int, table_element, actor_name: str, export_type: str): row = layout.box().row() left_row = row.row() @@ -648,12 +694,13 @@ def draw_props(self, layout: UILayout, actor_name: str, export_type: str, header if self.elements: col.separator() + if export_type == "C" and header_type != "DMA": col.prop(self, "override_files") col.operator(SM64_ExportAnimTable.bl_idname, icon="EXPORT") + col.separator() - if self.elements: col.prop( self, "expand_headers_tab", @@ -665,9 +712,9 @@ def draw_props(self, layout: UILayout, actor_name: str, export_type: str, header clear_op.type = "CLEAR" for table_index, table_element in enumerate(self.elements): - self.drawTableElement(col, table_index, table_element, actor_name, export_type) + self.draw_element(col, table_index, table_element, actor_name, export_type) else: - col.label(text="Empty table, add headers from actions.") + col.label(icon="INFO", text="Empty table, add headers to do a table export.") class SM64_AnimExportProps(PropertyGroup): @@ -696,7 +743,7 @@ class SM64_AnimExportProps(PropertyGroup): group_name: StringProperty( name="Group Name", default="group0", - ) # Ideally, this pr will be merged after combined exports, so this should be updated to use the group enum there + ) # TODO: Ideally, this pr will be merged after combined exports, so this should be updated to use the group enum there header_type: EnumProperty(items=enumAnimExportTypes, name="Header Export", default="Actor") level_name: StringProperty(name="Level", default="bob") level_option: EnumProperty(items=enumLevelNames, name="Level", default="bob") @@ -866,7 +913,7 @@ class SM64_AnimImportProps(PropertyGroup): ignore_null: BoolProperty(name="Ignore NULL Delimiter") dma_table_address: StringProperty(name="DMA Table Address", default="0x4EC000") - mario_animation: IntProperty(name="Selected Preset Mario Animation", default=0) + mario_animation: IntProperty(name="Selected Preset Mario Animation") c_path: StringProperty( name="Path", @@ -879,7 +926,11 @@ class SM64_AnimImportProps(PropertyGroup): default=True, ) - insertable_path: StringProperty(name="Path", subtype="FILE_PATH", default="") + insertable_path: StringProperty(name="Path", subtype="FILE_PATH") + insertable_read_from_rom: BoolProperty( + name="Read From Import ROM", + description="When enabled, the importer will read from the import ROM given a non defined address", + ) @property def mario_or_table_index(self): @@ -926,6 +977,7 @@ def draw_insertable_binary(self, layout: UILayout): col = layout.column() prop_split(col, self, "insertable_path", "Path") path_ui_warnings(col, abspath(self.insertable_path)) + col.prop(self, "insertable_read_from_rom") def draw_props(self, layout: UILayout, binary_col_enabled: bool = True): col = layout.column() diff --git a/fast64_internal/sm64/animation/utility.py b/fast64_internal/sm64/animation/utility.py index 9f874c206..5802feb90 100644 --- a/fast64_internal/sm64/animation/utility.py +++ b/fast64_internal/sm64/animation/utility.py @@ -91,4 +91,16 @@ def eval_num_from_str(string: str): # TODO: Reconsider this, if a good idea, pu try: return ast.literal_eval(string) except SyntaxError as exc: - raise SyntaxError(f"{str(exc)}.\nIf value is in hexadecimal, use 0x before it.") from exc + raise SyntaxError( + f"Exception occured while evaluating number from text, if value is in hexadecimal, " + f"use 0x before it.\n({string}\n{str(exc)}" + ) from exc + except ValueError as exc: + raise ValueError( + f"Exception occured while evaluating number from text, the value must be a number." + f"\n({string})\n{str(exc)}" + ) from exc + except Exception as exc: + raise Exception( + f"Exception occured while evaluating number from text.\n({string})\n{str(exc)}", + ) from exc diff --git a/fast64_internal/sm64/sm64_constants.py b/fast64_internal/sm64/sm64_constants.py index e341a58f1..5cd6e5963 100644 --- a/fast64_internal/sm64/sm64_constants.py +++ b/fast64_internal/sm64/sm64_constants.py @@ -354,7 +354,13 @@ def __init__(self, geoAddr, level, switchDict): "TTM": 0x2AC2EC, } -insertableBinaryTypes = {"Display List": 0, "Geolayout": 1, "Animation": 2, "Collision": 3} +insertableBinaryTypes = { + "Display List": 0, + "Geolayout": 1, + "Animation": 2, + "Collision": 3, + "Animation Table": 4, +} enumBehaviourPresets = [ ("Custom", "Custom", "Custom"), ("1300407c", "1 Up", "1 Up"),