From 2b79e7f483cb5a017d19b3b8844d9f6930241933 Mon Sep 17 00:00:00 2001 From: Lila <87947656+Lilaa3@users.noreply.github.com> Date: Fri, 19 Apr 2024 22:30:43 +0100 Subject: [PATCH] Quick read (missing ui) --- fast64_internal/sm64/animation/classes.py | 237 ++++++++++++------- fast64_internal/sm64/animation/exporting.py | 149 +++++++++--- fast64_internal/sm64/animation/importing.py | 9 +- fast64_internal/sm64/animation/operators.py | 58 +++-- fast64_internal/sm64/animation/properties.py | 80 ++++--- fast64_internal/utility.py | 6 + 6 files changed, 363 insertions(+), 176 deletions(-) diff --git a/fast64_internal/sm64/animation/classes.py b/fast64_internal/sm64/animation/classes.py index 8a1274393..6ecf0c4f5 100644 --- a/fast64_internal/sm64/animation/classes.py +++ b/fast64_internal/sm64/animation/classes.py @@ -75,21 +75,21 @@ class SM64_AnimData: values_file_name: str = "" def to_c(self, is_dma_structure: bool): - data = StringIO() + text_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()) + text_data.write(indice_table.to_c()) + text_data.write("\n\n") + text_data.write(value_table.to_c()) else: - data.write(value_table.to_c()) - data.write("\n\n") - data.write(indice_table.to_c()) + text_data.write(value_table.to_c()) + text_data.write("\n\n") + text_data.write(indice_table.to_c()) - return data.getvalue() + return text_data.getvalue() def to_binary(self, start_address: int) -> bytearray: data: bytearray = bytearray() @@ -183,7 +183,29 @@ def get_binary_flags(self): return eval_num_from_str(self.custom_flags) return self.flags - def to_c(self, is_dma_structure: bool, refresh_version: str) -> str: + def get_values_reference(self, override: Optional[str | int] = None): + if override: + return override + elif self.values_reference: + return self.values_reference + elif self.data and self.data.values_reference: + return self.data.values_reference + + def get_indice_reference(self, override: Optional[str | int] = None): + if override: + return override + elif self.indice_reference: + return self.indice_reference + elif self.data and self.data.indice_reference: + return self.data.indice_reference + + def to_c( + self, + values_override: Optional[str | int] = None, + indice_override: Optional[str | int] = None, + is_dma_structure: bool = False, + refresh_version: str = "Refresh 13", + ): return ( 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" @@ -191,14 +213,18 @@ def to_c(self, is_dma_structure: bool, refresh_version: str) -> str: f"\t{self.start_frame}, // startFrame\n" f"\t{self.loop_start}, // loopStart\n" f"\t{self.loop_end}, // loopEnd\n" - f"\tANIMINDEX_NUMPARTS({self.indice_reference}), // unusedBoneCount\n" - f"\t{self.values_reference}, // values\n" - f"\t{self.indice_reference}, // index\n" + f"\tANIMINDEX_NUMPARTS({self.get_indice_reference(indice_override)}), // unusedBoneCount\n" + f"\t{self.get_values_reference(values_override)}, // values\n" + f"\t{self.get_indice_reference(indice_override)}, // index\n" "\t0\n" "};\n" ) - def to_binary(self, values_reference: int | None = None, indice_reference: int | None = None): + def to_binary( + self, + values_override: Optional[str | int] = None, + indice_override: Optional[str | int] = 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 @@ -206,18 +232,8 @@ def to_binary(self, values_reference: int | None = None, indice_reference: int | 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(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(indice_reference.to_bytes(4, byteorder="big", signed=False)) # 0x10 + data.extend(self.get_values_reference(values_override).to_bytes(4, byteorder="big", signed=False)) # 0x0C + data.extend(self.get_indice_reference(indice_override).to_bytes(4, byteorder="big", signed=False)) # 0x10 data.extend(bytearray([0x00] * 4)) # 0x14 # Unused with no porpuse # 0x18 return data @@ -271,7 +287,7 @@ def read_c(self, value: Initialization): ), ) - def to_props(self, action, header): + def to_props(self, action, header, actor_name: str, use_custom_name: bool = True): flagsToProps = { # TODO: Fix this whole thing its so cringe help me oh god help me "ANIM_FLAG_NOLOOP": "no_loop", "ANIM_FLAG_BACKWARD": "backwards", @@ -284,8 +300,12 @@ def to_props(self, action, header): header.action = action - if isinstance(self.reference, str): - header.custom_name = self.reference + header.custom_name = self.reference + if ( + isinstance(self.reference, str) + and self.reference != header.get_anim_name(actor_name, action) + and use_custom_name + ): header.override_name = True correct_frame_range = self.start_frame, self.loop_start, self.loop_end @@ -342,14 +362,14 @@ def to_binary(self, is_dma: bool, start_address: int) -> bytearray: return data, ptrs def headers_to_c(self, is_dma_structure: bool, refresh_version: str) -> str: - data = StringIO() + text_data = StringIO() for header in self.headers: - data.write(header.to_c(is_dma_structure, refresh_version)) - data.write("\n") - return data.getvalue() + text_data.write(header.to_c(is_dma_structure=is_dma_structure, refresh_version=refresh_version)) + text_data.write("\n") + return text_data.getvalue() def to_c(self, is_dma_structure: bool, refresh_version: str): - data = StringIO() + text_data = StringIO() table_data = None if self.data: @@ -357,30 +377,26 @@ def to_c(self, is_dma_structure: bool, refresh_version: str): c_headers = self.headers_to_c(is_dma_structure, refresh_version) if is_dma_structure: - data.write(c_headers) + text_data.write(c_headers) if table_data: - data.write("\n") - data.write(table_data) + text_data.write("\n") + text_data.write(table_data) else: if table_data: - data.write(table_data) - data.write("\n") - data.write(c_headers) + text_data.write(table_data) + text_data.write("\n") + text_data.write(c_headers) - return data.getvalue() + return text_data.getvalue() # Importing - def to_props(self, action, remove_name_footer: bool = True): + def to_props(self, action, actor_name: str, remove_name_footer: bool = True, use_custom_name: bool = True): action_props = action.fast64.sm64 main_header = self.headers[0] - if not self.action_name: - 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 = main_header.reference + if not self.action_name and main_header.reference and remove_name_footer: + self.action_name = main_header.reference.lstrip("anim_") if self.action_name: action.name = self.action_name @@ -407,7 +423,7 @@ def to_props(self, action, remove_name_footer: bool = True): action_props.header_variants.add() for header, header_props in zip(self.headers, action_props.headers): - header.to_props(action, header_props) + header.to_props(action, header_props, actor_name, use_custom_name) action_props.update_header_variant_numbers() @@ -442,6 +458,11 @@ def read_binary(self, rom_data: bytes, address: int): self.data = rom_data[address : address + end_of_table] +def num_to_padded_hex(num: int): + hex_str = hex(num)[2:].upper() # remove the '0x' prefix + return hex_str.zfill(2) + + @dataclasses.dataclass class SM64_AnimTable: reference: str | int = None @@ -508,12 +529,54 @@ def to_binary(self, is_dma: bool, start_address: int): return data - def data_and_headers_to_c(self, is_dma_structure: bool, refresh_version: str) -> list[os.PathLike, str]: + def to_c_dma(self) -> list[os.PathLike, str]: + files_data: dict[str, str] = {} + + text_data = StringIO() + header_nums = [] + included_headers = [] + for i, anim_header in enumerate(self.elements): + header_nums.append(i) + included_headers.append(anim_header) + anim_header.reference = f"anim_{num_to_padded_hex(i)}" + # If at the end of the list, or the next element has different data + if (i >= len(self.elements) - 1) or self.elements[i + 1].data != anim_header.data: + # Create appropriate file name, store the final text and reset the data + name = "anim" + for num in header_nums: + name += f"_{num_to_padded_hex(num)}" + file_name = f"{name}.inc.c" + + # While normal names are possible (order goes by line and file) + # That would break convention, require extra code for duplicates, warnings, etc + anim_header.data.indice_reference = f"{name}_indices" + anim_header.data.values_reference = f"{name}_values" + + for included_header in included_headers: + text_data.write(included_header.to_c(is_dma_structure=True)) + text_data.write("\n") + + value_table, indice_tables = create_tables([anim_header.data]) + text_data.write(indice_tables[0].to_c()) + text_data.write("\n") + text_data.write(value_table.to_c()) + text_data.write("\n") + + files_data[file_name] = text_data.getvalue() + + text_data = StringIO() + header_nums.clear() + included_headers.clear() + + return files_data + + def data_and_headers_to_c(self, refresh_version: str) -> list[os.PathLike, str]: files_data: dict[str, str] = {} headers_set = self.get_sets()[0] + text_data = StringIO() for anim_header in headers_set: - data = StringIO() + text_data.truncate(0) same_reference_headers = [anim_header] for other_header in headers_set: if ( @@ -527,79 +590,79 @@ def data_and_headers_to_c(self, is_dma_structure: bool, refresh_version: str) -> for same_reference_header in same_reference_headers: if same_reference_header.data: value_table, indice_tables = create_tables([same_reference_header.data]) - data.write(value_table.to_c()) - data.write("\n") - for indice_table in indice_tables: - data.write(indice_table.to_c()) - data.write("\n") + text_data.write(value_table.to_c()) + text_data.write("\n") + text_data.write(indice_tables[0].to_c()) + text_data.write("\n") break for same_reference_header in same_reference_headers: - data.write(same_reference_header.to_c(is_dma_structure, refresh_version)) - data.write("\n") + text_data.write(same_reference_header.to_c(False, refresh_version)) + text_data.write("\n") - files_data[anim_header.file_name] = data.getvalue() + files_data[anim_header.file_name] = text_data.getvalue() return files_data - def combined_data_and_headers_to_c(self, is_dma_structure: bool, refresh_version: str): - data = StringIO() + def combined_data_and_headers_to_c(self, refresh_version: str): + text_data = StringIO() headers_set, data_set = self.get_sets() if data_set: value_table, indice_tables = create_tables(data_set, self.values_reference) - data.write(value_table.to_c()) - data.write("\n") + text_data.write(value_table.to_c()) + text_data.write("\n") for indice_table in indice_tables: - data.write(indice_table.to_c()) - data.write("\n") + text_data.write(indice_table.to_c()) + text_data.write("\n") for anim_header in headers_set: - data.write(anim_header.to_c(is_dma_structure, refresh_version)) - data.write("\n") + text_data.write(anim_header.to_c(False, refresh_version)) + text_data.write("\n") - return data.getvalue() + return text_data.getvalue() def enum_list_to_c(self): - data = StringIO() + text_data = StringIO() - data.write(f"enum {self.enum_list_reference} {{\n") + text_data.write(f"enum {self.enum_list_reference} {{\n") for anim_header in self.elements: - data.write(f"\t{anim_header.enum_reference},\n") - data.write("};\n") + text_data.write(f"\t{anim_header.enum_reference},\n") + text_data.write("};\n") return data.getvalue() def table_to_c(self, generate_enums: bool): - data = StringIO() + text_data = StringIO() if generate_enums: - data.write(f'#include "table_enum.h"\n') + text_data.write(f'#include "table_enum.h"\n') - data.write(f"const struct Animation *const {self.reference}[] = {{\n") + text_data.write(f"const struct Animation *const {self.reference}[] = {{\n") for anim_header in self.elements: if generate_enums: - data.write(f"\t[{anim_header.enum_reference}] = &{anim_header.reference},\n") + text_data.write(f"\t[{anim_header.enum_reference}] = &{anim_header.reference},\n") else: - data.write(f"\t&{anim_header.reference},\n") - data.write("\tNULL,\n};\n") + text_data.write(f"\t&{anim_header.reference},\n") + text_data.write("\tNULL,\n};\n") - return data.getvalue() + return text_data.getvalue() 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 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 + def index_sub_seq_in_seq(sub_seq: list[int], seq: list[int]): + i, sub_length = -1, len(sub_seq) + while True: + trunc_seq = seq[i + 1 :] + if sub_seq[0] not in trunc_seq: + return -1 + i = seq.index(sub_seq[0], i + 1) + if sub_seq == seq[i:sub_length]: + return i + return -1 value_table = SM64_ShortArray(values_name if values_name else anims_data[0].values_reference, True) diff --git a/fast64_internal/sm64/animation/exporting.py b/fast64_internal/sm64/animation/exporting.py index 4934d18a2..c38399911 100644 --- a/fast64_internal/sm64/animation/exporting.py +++ b/fast64_internal/sm64/animation/exporting.py @@ -2,10 +2,11 @@ import bpy import mathutils -from bpy.types import Object, Scene, Action +from bpy.types import Object, Scene, Action, PoseBone from bpy.path import abspath from ...utility import ( + PluginError, radians_to_s16, writeIfNotFound, writeInsertableFile, @@ -15,59 +16,133 @@ from .utility import get_anim_pose_bones -def get_animation_pairs( - blender_to_sm64_scale: float, - action: Action, - armature_obj: Object, -) -> tuple[list[int], list[int]]: - # applyRotation([armatureObj], math.radians(90), "X") - anim_bones = get_anim_pose_bones(armature_obj) +def get_entire_fcurve_data(action: Action, bone: PoseBone, path: str, max_frame: int, max_index: int = 0): + values = [] + for index in range(max_index): + values.append([]) + for fcurve in action.fcurves: + if fcurve.data_path != f'pose.bones["{bpy.utils.escape_identifier(bone.name)}"].{path}': + continue + for index in range(max_index): + if fcurve.array_index == index: + for frame in range(min(int(fcurve.range()[1]) + 1, max_frame)): + values[index].append(frame) + return values - pairs = [ + +def get_trans_data(action: Action, bone: PoseBone, max_frame: int, blender_to_sm64_scale: float): + trasnlation_pairs = ( SM64_AnimPair(), SM64_AnimPair(), SM64_AnimPair(), - ] - trans_x_pair, trans_y_pair, trans_z_pair = pairs + ) + data_path = f'pose.bones["{bpy.utils.escape_identifier(bone.name)}"].location' + for fcurve in action.fcurves: + if fcurve.data_path != data_path: + continue + for index in range(3): + if fcurve.array_index == index: + values = trasnlation_pairs[index].values + for frame in range(min(int(fcurve.range()[1]) + 1, max_frame)): + values.append(int(fcurve.evaluate(frame) * blender_to_sm64_scale)) + return trasnlation_pairs - rotation_pairs: list[tuple[SM64_AnimPair]] = [] - for _ in anim_bones: - rotation = ( - SM64_AnimPair(), - SM64_AnimPair(), - SM64_AnimPair(), + +def get_rotation_data(action: Action, bone: PoseBone, max_frame: int): + rotation_pairs = ( + SM64_AnimPair(), + SM64_AnimPair(), + SM64_AnimPair(), + ) + rotation = (rotation_pairs[0].values, rotation_pairs[1].values, rotation_pairs[2].values) + if bone.rotation_mode == "QUATERNION": + w_fcurve, x_fcurve, y_fcurve, z_fcurve = get_entire_fcurve_data( + action, bone, "rotation_quaternion", max_frame, 4 + ) + for w, x, y, z in zip(w_fcurve, x_fcurve, y_fcurve, z_fcurve): + euler = mathutils.Quaternion((w, x, y, z)).to_euler() + rotation[0].append(radians_to_s16(euler[0])) + rotation[1].append(radians_to_s16(euler[1])) + rotation[2].append(radians_to_s16(euler[2])) + elif bone.rotation_mode == "AXIS_ANGLE": + x_fcurve, y_fcurve, z_fcurve, w_fcurve = get_entire_fcurve_data( + action, bone, "rotation_axis_angle", max_frame, 4 ) - rotation_pairs.append(rotation) - pairs.extend(rotation) + for x, y, z, w in zip(x_fcurve, y_fcurve, z_fcurve, w_fcurve): + euler = mathutils.AxisAngle((x, y, z), w).to_euler() + rotation[0].append(radians_to_s16(euler[0])) + rotation[1].append(radians_to_s16(euler[1])) + rotation[2].append(radians_to_s16(euler[2])) + else: + x_fcurve, y_fcurve, z_fcurve = get_entire_fcurve_data(action, bone, "rotation_euler", max_frame, 3) + for x, y, z in zip(x_fcurve, y_fcurve, z_fcurve): + euler = mathutils.Euler(x, y, z, action, bone.rotation_mode).to_euler() + rotation[0].append(radians_to_s16(euler[0])) + rotation[1].append(radians_to_s16(euler[1])) + rotation[2].append(radians_to_s16(euler[2])) + return rotation_pairs + - scale: mathutils.Vector = armature_obj.matrix_world.to_scale() * blender_to_sm64_scale +def get_animation_pairs( + blender_to_sm64_scale: float, action: Action, armature_obj: Object, quick_read: bool = False +) -> tuple[list[int], list[int]]: + anim_bones = get_anim_pose_bones(armature_obj) + if len(anim_bones) < 1: + raise PluginError(f'No animation bones in armature "{armature_obj.name}"') + + max_frame = action.fast64.sm64.get_max_frame(action) + pairs = [] print(f"Reading animation pair values from action {action.name}.") - pre_export_frame = bpy.context.scene.frame_current - pre_export_action = armature_obj.animation_data.action + if quick_read: + root_bone = anim_bones[0] + pairs.extend(get_trans_data(action, root_bone, max_frame, blender_to_sm64_scale)) - armature_obj.animation_data.action = action - max_frame = action.fast64.sm64.get_max_frame(action) - for frame in range(max_frame): - bpy.context.scene.frame_set(frame) for i, pose_bone in enumerate(anim_bones): - matrix = pose_bone.matrix_basis - if i == 0: # Only first bone has translation. - translation: mathutils.Vector = matrix.to_translation() * scale - trans_x_pair.values.append(int(translation.x)) - trans_y_pair.values.append(int(translation.y)) - trans_z_pair.values.append(int(translation.z)) + pairs.extend(get_rotation_data(action, pose_bone, max_frame)) + else: + pre_export_frame = bpy.context.scene.frame_current + pre_export_action = armature_obj.animation_data.action + armature_obj.animation_data.action = action - for angle, pair in zip(matrix.to_euler(), rotation_pairs[i]): - pair.values.append(radians_to_s16(angle)) + pairs = [ + SM64_AnimPair(), + SM64_AnimPair(), + SM64_AnimPair(), + ] + trans_x_pair, trans_y_pair, trans_z_pair = pairs + + rotation_pairs: list[tuple[SM64_AnimPair]] = [] + for _ in anim_bones: + rotation = ( + SM64_AnimPair(), + SM64_AnimPair(), + SM64_AnimPair(), + ) + rotation_pairs.append(rotation) + pairs.extend(rotation) + + scale: mathutils.Vector = armature_obj.matrix_world.to_scale() * blender_to_sm64_scale + for frame in range(max_frame): + bpy.context.scene.frame_set(frame) + for i, pose_bone in enumerate(anim_bones): + matrix = pose_bone.matrix_basis + if i == 0: # Only first bone has translation. + translation: mathutils.Vector = matrix.to_translation() * scale + trans_x_pair.values.append(int(translation.x)) + trans_y_pair.values.append(int(translation.y)) + trans_z_pair.values.append(int(translation.z)) + + for angle, pair in zip(matrix.to_euler(), rotation_pairs[i]): + pair.values.append(radians_to_s16(angle)) + + armature_obj.animation_data.action = pre_export_action + bpy.context.scene.frame_current = pre_export_frame for pair in pairs: pair.clean_frames() - armature_obj.animation_data.action = pre_export_action - bpy.context.scene.frame_current = pre_export_frame - return pairs diff --git a/fast64_internal/sm64/animation/importing.py b/fast64_internal/sm64/animation/importing.py index 6f3c777a1..04498698f 100644 --- a/fast64_internal/sm64/animation/importing.py +++ b/fast64_internal/sm64/animation/importing.py @@ -88,7 +88,12 @@ def read_rotation(self, pairs: list[SM64_AnimPair]): def animation_data_to_blender( - armature_obj: Object, blender_to_sm64_scale: float, anim_import: SM64_Anim, remove_name_footer: bool = True + armature_obj: Object, + blender_to_sm64_scale: float, + anim_import: SM64_Anim, + actor_name: str, + remove_name_footer: bool = True, + use_custom_name: bool = True, ): anim_bones = get_anim_pose_bones(armature_obj) for pose_bone in anim_bones: @@ -138,7 +143,7 @@ def animation_data_to_blender( for frame, rotation in enumerate(bone_data.rotation): f_curve.keyframe_points.insert(frame, rotation[property_index]) - anim_import.to_props(action, remove_name_footer) + anim_import.to_props(action, actor_name, remove_name_footer, use_custom_name) def import_animation_from_c_header( diff --git a/fast64_internal/sm64/animation/operators.py b/fast64_internal/sm64/animation/operators.py index cc735d1a7..6f633cc30 100644 --- a/fast64_internal/sm64/animation/operators.py +++ b/fast64_internal/sm64/animation/operators.py @@ -255,6 +255,7 @@ class SM64_ExportAnimTable(Operator): bl_options = {"REGISTER", "UNDO", "PRESET"} def execute_operator(self, context: Context): + # TODO: This got a bit gross, revisit eventually bpy.ops.object.mode_set(mode="OBJECT") animation_operator_checks(context) @@ -266,11 +267,15 @@ def execute_operator(self, context: Context): actions = table_props.actions armature_obj: Object = context.selected_objects[0] + print("Stashing all actions in table") + for action in actions: stashActionInArmature(armature_obj, action) actor_name = anim_export_props.actor_name + print("Reading table data") + table: SM64_AnimTable = table_props.to_table_class( armature_obj, sm64_props.blender_to_sm64_scale, @@ -278,6 +283,8 @@ def execute_operator(self, context: Context): actor_name, ) + print("Exporting table data") + if sm64_props.export_type == "C": header_type = anim_export_props.header_type @@ -289,14 +296,18 @@ def execute_operator(self, context: Context): applyBasicTweaks(abspath(sm64_props.decomp_path)) if header_type == "DMA" or table_props.export_seperately: - files_data = table.data_and_headers_to_c( - anim_export_props.is_c_dma_structure, - sm64_props.refresh_version, - ) + if header_type == "DMA": + files_data = table.to_c_dma() + else: + files_data = table.data_and_headers_to_c( + anim_export_props.is_c_dma_structure, + sm64_props.refresh_version, + ) for file_name, file_data in files_data.items(): with open(os.path.join(anim_dir_path, file_name), "w", newline="\n") as file: file.write(file_data) - update_data_file(os.path.join(anim_dir_path, "data.inc.c"), [file_name]) + if header_type != "DMA": + update_data_file(os.path.join(anim_dir_path, "data.inc.c"), [file_name]) else: with open(os.path.join(anim_dir_path, "data.inc.c"), "w", newline="\n") as file: file.write( @@ -485,7 +496,7 @@ def execute_operator(self, context): entrie: DMATableEntrie = dma_table.entries[entrie_num] header = import_binary_header(rom_data, entrie.address, True, animations, None) table.elements.append(header) - # header.reference = toAlnum(name) + header.reference = toAlnum(name) header.data.action_name = name else: raise PluginError("Unimplemented import type.") @@ -519,33 +530,34 @@ def execute_operator(self, context): animation_operator_checks(context, False) sm64_props = context.scene.fast64.sm64 - anim_import_props = sm64_props.anim_import + export_props = sm64_props.anim_export + import_props = sm64_props.anim_import animations: dict[str, SM64_Anim] = {} table = SM64_AnimTable() - if anim_import_props.import_type == "Binary": + if import_props.import_type == "Binary": import_rom_path = abspath(sm64_props.import_rom) import_rom_checks(import_rom_path) with open(import_rom_path, "rb") as rom_data: import_binary_animations( rom_data.read(), - anim_import_props.binary_import_type, - anim_import_props.is_segmented_address, + import_props.binary_import_type, + import_props.is_segmented_address, eval_num_from_str( - anim_import_props.dma_table_address - if anim_import_props.binary_import_type == "DMA" - else anim_import_props.address + import_props.dma_table_address + if import_props.binary_import_type == "DMA" + else import_props.address ), - parseLevelAtPointer(rom_data, level_pointers[anim_import_props.level]).segmentData, + parseLevelAtPointer(rom_data, level_pointers[import_props.level]).segmentData, animations, - anim_import_props.read_entire_table, - anim_import_props.mario_or_table_index, - anim_import_props.ignore_null, + import_props.read_entire_table, + import_props.mario_or_table_index, + import_props.ignore_null, table, ) - elif anim_import_props.import_type == "Insertable Binary": - insertable_path = abspath(anim_import_props.insertable_path) + elif import_props.import_type == "Insertable Binary": + insertable_path = abspath(import_props.insertable_path) filepath_checks(insertable_path) with open(insertable_path, "rb") as rom_data: import_insertable_binary_animations( @@ -553,15 +565,17 @@ def execute_operator(self, context): animations, table, ) - elif anim_import_props.import_type == "C": - import_c_animations(abspath(anim_import_props.c_path), animations, table) + elif import_props.import_type == "C": + import_c_animations(abspath(import_props.c_path), animations, table) for data in animations.values(): animation_data_to_blender( context.selected_objects[0], sm64_props.blender_to_sm64_scale, data, - anim_import_props.remove_name_footer, + export_props.actor_name, + import_props.remove_name_footer, + import_props.use_custom_name, ) sm64_props.anim_export.table.from_anim_table_class(table) diff --git a/fast64_internal/sm64/animation/properties.py b/fast64_internal/sm64/animation/properties.py index 1f8d7bd11..8d935e877 100644 --- a/fast64_internal/sm64/animation/properties.py +++ b/fast64_internal/sm64/animation/properties.py @@ -1,5 +1,6 @@ import os import re +from typing import Optional import bpy from bpy.types import PropertyGroup, Action, UILayout, Object @@ -151,12 +152,14 @@ def get_frame_range(self, action: Action): def get_anim_name(self, actor_name: str, action: Action): if self.override_name: return self.custom_name - else: - if self.header_variant == 0: + if self.header_variant == 0: + if actor_name: name = f"{actor_name}_anim_{action.name}" else: - main_header_name = action.fast64.sm64.headers[0].get_anim_name(actor_name, action) - name = f"{main_header_name}_{self.header_variant}" + name = f"anim_{action.name}" + else: + main_header_name = action.fast64.sm64.headers[0].get_anim_name(actor_name, action) + name = f"{main_header_name}_{self.header_variant}" return toAlnum(name) @@ -191,13 +194,13 @@ def get_int_flags(self): def to_header_class( self, - values_reference: int | str, - indice_reference: int | str, bone_count: int, data: SM64_AnimData, - action: Action = None, - actor_name: str = "", - file_name: str = "", + action: Action, + values_reference: Optional[int | str] = None, + indice_reference: Optional[int | str] = None, + actor_name: Optional[str] = "", + file_name: Optional[str] = "", ): header = SM64_AnimHeader() header.reference = self.get_anim_name(actor_name, action) @@ -311,6 +314,7 @@ def draw_props( layout: UILayout, action: Action, draw_table_operations: bool = True, + draw_names: bool = True, export_type: str = "C", actor_name: str = "mario", generate_enums: bool = False, @@ -439,17 +443,17 @@ def to_animation_class( indice_reference = self.indices_table else: animation.data = self.to_data_class(action, armature_obj, blender_to_sm64_scale, animation.file_name) - values_reference = animation.data.values_reference - indice_reference = animation.data.indice_reference + values_reference = None + indice_reference = None bone_count = len(get_anim_pose_bones(armature_obj)) for header_props in self.headers: animation.headers.append( header_props.to_header_class( - values_reference, - indice_reference, bone_count, animation.data, action, + values_reference, + indice_reference, actor_name, animation.file_name, ) @@ -464,6 +468,7 @@ def draw_variant( header: SM64_AnimHeaderProps, array_index: int, draw_table_operations: bool = True, + draw_names: bool = True, export_type: str = "C", actor_name: str = "mario", generate_enums: bool = False, @@ -516,6 +521,7 @@ def draw_variant( layout=col, action=action, draw_table_operations=draw_table_operations, + draw_names=draw_names, export_type=export_type, actor_name=actor_name, generate_enums=generate_enums, @@ -527,6 +533,7 @@ def draw_variants( layout: UILayout, action: Action, draw_table_operations: bool = True, + draw_names: bool = True, export_type: str = "C", actor_name: str = "mario", generate_enums: bool = False, @@ -545,6 +552,7 @@ def draw_variants( layout=col, action=action, draw_table_operations=draw_table_operations, + draw_names=draw_names, export_type=export_type, actor_name=actor_name, generate_enums=generate_enums, @@ -578,6 +586,7 @@ def draw_variants( header=variant, array_index=i, draw_table_operations=draw_table_operations, + draw_names=draw_names, export_type=export_type, actor_name=actor_name, generate_enums=generate_enums, @@ -600,9 +609,11 @@ def draw_props( action: Action, header_scale_y: float = 0.8, specific_variant: int | None = None, - draw_file_name: bool = True, draw_export_operation: bool = True, draw_table_operations: bool = True, + draw_names: bool = True, + draw_references: bool = True, + draw_file_name: bool = True, export_type: str = "C", actor_name: str = "mario", generate_enums: bool = False, @@ -631,7 +642,7 @@ def draw_props( box.scale_y = 0.5 box.label(text=self.get_anim_file_name(action)) - if export_type == "C" or not is_dma: + if draw_references: self.draw_references(col) if not self.reference_tables: @@ -653,6 +664,7 @@ def draw_props( layout=col, action=action, draw_table_operations=draw_table_operations, + draw_names=draw_names, export_type=export_type, actor_name=actor_name, generate_enums=generate_enums, @@ -663,6 +675,7 @@ def draw_props( layout=col, action=action, draw_table_operations=draw_table_operations, + draw_names=draw_names, export_type=export_type, actor_name=actor_name, generate_enums=generate_enums, @@ -725,7 +738,7 @@ def get_anim_table_name(self, actor_name: str): return f"{actor_name}_anims" def get_enum_list_name(self, actor_name: str): - return f"{actor_name.title()}Anims" + return f"{actor_name}Anims".title() def get_header_names(self, actor_name: str): header_names = [] @@ -786,21 +799,20 @@ def to_table_class( indice_reference = self.indices_table data = None else: - data = existing_data.get( - action, - action_props.to_data_class(action, armature_obj, blender_to_sm64_scale), - ) - values_reference = table.values_reference - indice_reference = data.indice_reference + if not action in existing_data: + existing_data[action] = action_props.to_data_class(action, armature_obj, blender_to_sm64_scale) + data = existing_data[action] + values_reference = None + indice_reference = None header = existing_headers.get( header_props, header_props.to_header_class( - values_reference, - indice_reference, bone_count, data, action, + values_reference, + indice_reference, actor_name, action_props.get_anim_file_name(action), ), @@ -882,9 +894,11 @@ def draw_element( export_type=export_type, header_scale_y=1.0, specific_variant=header_variant, - draw_file_name=export_type == "C" and self.export_seperately, draw_export_operation=False, draw_table_operations=False, + draw_names=export_type == "C" and not is_dma, + draw_references=export_type == "C" and not is_dma, + draw_file_name=export_type == "C" and not is_dma and self.export_seperately, actor_name=actor_name, generate_enums=self.generate_enums, is_dma=is_dma, @@ -940,7 +954,7 @@ def draw_props( if is_dma and export_type == "C": multilineLabel( col, - "The exported files will follow the vanilla DMA naming\nconventions (anim_00.inc.c, anim_00_01.inc.c, etc).", + "The export will follow the vanilla DMA naming\nconventions (anim_xx.inc.c, anim_xx, anim_xx_values, etc).", icon="INFO", ) @@ -998,7 +1012,7 @@ class SM64_AnimExportProps(PropertyGroup): default=False, ) custom_path: StringProperty(name="Directory", subtype="FILE_PATH") - actor_name: StringProperty(name="Name", default="mario") + actor_name_prop: StringProperty(name="Name", default="mario") group_name: StringProperty( name="Group Name", default="group0", @@ -1014,6 +1028,10 @@ class SM64_AnimExportProps(PropertyGroup): insertable_path: StringProperty(name="Insertable Path", subtype="FILE_PATH") + @property + def actor_name(self): + return self.actor_name_prop if self.header_type != "DMA" else None + @property def is_c_dma_structure(self): if self.header_type == "DMA": @@ -1081,6 +1099,7 @@ def draw_action_properties(self, layout: UILayout, is_dma: bool, export_type: st self.selected_action.fast64.sm64.draw_props( layout=col, action=self.selected_action, + draw_references=export_type == "C" or not is_dma, export_type=export_type, actor_name=self.actor_name, generate_enums=self.table.generate_enums, @@ -1123,7 +1142,7 @@ def draw_c_settings(self, layout: UILayout): prop_split(col, self, "header_type", "Export Type") if self.header_type != "DMA": - prop_split(col, self, "actor_name", "Name") + prop_split(col, self, "actor_name_prop", "Name") if self.header_type == "Custom": col.prop(self, "use_dma_structure") @@ -1210,6 +1229,10 @@ class SM64_AnimImportProps(PropertyGroup): description='Remove "anim_" from imported animations', default=True, ) + use_custom_name: BoolProperty( + name="Use Custom Name", + default=False, + ) insertable_path: StringProperty(name="Path", subtype="FILE_PATH") insertable_read_from_rom: BoolProperty( @@ -1257,6 +1280,7 @@ def draw_c(self, layout: UILayout): prop_split(col, self, "c_path", "Path") path_ui_warnings(col, abspath(self.c_path)) col.prop(self, "remove_name_footer") + col.prop(self, "use_custom_name") def draw_insertable_binary(self, layout: UILayout): col = layout.column() diff --git a/fast64_internal/utility.py b/fast64_internal/utility.py index e7e82906e..ed0122a05 100644 --- a/fast64_internal/utility.py +++ b/fast64_internal/utility.py @@ -677,11 +677,17 @@ def makeWriteInfoBox(layout): def writeBoxExportType(writeBox, headerType, name, levelName, levelOption): + if not name: + writeBox.label(text="Empty actor name", icon="ERROR") + return if headerType == "Actor": writeBox.label(text="actors/" + toAlnum(name)) elif headerType == "Level": if levelOption != "custom": levelName = levelOption + if not name: + writeBox.label(text="Empty level name", icon="ERROR") + return writeBox.label(text="levels/" + toAlnum(levelName) + "/" + toAlnum(name))