diff --git a/fast64_internal/sm64/animation/classes.py b/fast64_internal/sm64/animation/classes.py index 95badf106..79b9fc15c 100644 --- a/fast64_internal/sm64/animation/classes.py +++ b/fast64_internal/sm64/animation/classes.py @@ -17,11 +17,15 @@ @dataclasses.dataclass class SM64_AnimHeader: + data: "SM64_Anim" = None + name: str = None address: int = None origin_path: str = "" header_variant: int = 0 - flags: str | int = None + + flags: int = 0 + custom_flags: int = "" trans_divisor: int = 0 start_frame: int = 0 loop_start: int = 0 @@ -29,7 +33,6 @@ class SM64_AnimHeader: bone_count: int = None values: str = "" indices: str = "" - data: "SM64_Anim" = None def toC(self, designated: bool, asArray: bool) -> str: headerData: list[tuple[str, object]] = [ @@ -189,17 +192,16 @@ def readC(self): @dataclasses.dataclass class SM64_AnimPair: - maxFrame: int = 1 values: list[int] = dataclasses.field(default_factory=list) - def appendFrame(self, value: int): - if len(self.values) >= 1: - lastValue = self.values[-1] + def clean_frames(self): + last_value = self.values[-1] - if abs(value - lastValue) > 1: - self.maxFrame = len(self.values) + 1 - - self.values.append(value) + i = 1 + for i, value in enumerate(reversed(self.values)): + if value != last_value: + break + self.values = self.values[:-i] def getFrame(self, frame: int): if frame < len(self.values): @@ -214,7 +216,7 @@ def read_binary(self, indicesReader: RomReading, data: bytes, valuesAdress: int) valueReader = RomReading(data, valuesAdress + valueOffset) for _ in range(maxFrame): value = valueReader.read_value(2, signed=True) - self.appendFrame(value) + self.values.append(value) def readC(self, maxFrame, offset, values: list[int]): for frame in range(maxFrame): @@ -234,52 +236,52 @@ class SM64_Anim: indicesReference: str = "" valuesReference: str = "" reference: bool = False - isDmaStructure: bool = False headers: list[SM64_AnimHeader] = dataclasses.field(default_factory=list) pairs: list[SM64_AnimPair] = dataclasses.field(default_factory=list) actionName: str = None fileName: str = None - def createTables(self, merge: bool) -> tuple[list[int], list[int]]: - def findOffset(addedIndexes: list, pairValues) -> int | None: + def create_tables(self, pairs: list[SM64_AnimPair]): + def find_offset(added_indices: list, pairValues) -> int | None: offset: int | None = None - for addedIndex in addedIndexes: - for i, j in zip(pairValues[0 : len(addedIndex.values)], addedIndex.values[0 : len(pairValues)]): - offset = addedIndex.offset + for added_index in added_indices: + for i, j in zip(pairValues[0 : len(added_index.values)], added_index.values[0 : len(pairValues)]): + offset = added_index.offset if abs(i - j) > 1: offset = None break - if len(addedIndex.values) < len(pairValues): - addedIndex.extend(pairValues[len(pairValues) - 1 :]) + if len(added_index.values) < len(pairValues): + added_index.values.extend(pairValues[len(pairValues) - 1 :]) return offset print("Merging values and creating tables.") - valueTable, indicesTable, addedIndexes = [], [], [] + value_table, indices_table, added_indices = [], [], [] + + for pair in pairs: + max_frame: int = len(pair.values) + if max_frame > MAX_U16: + raise PluginError("Index pair´s max frame is too high. Too many frames.") - for pair in self.pairs: - maxFrame: int = pair.maxFrame - pairValues: list[int] = pair.values[0:maxFrame] + pairValues: list[int] = pair.values[0:max_frame] - existingOffset: int | None = None - if merge: - existingOffset = findOffset(addedIndexes, pairValues) + existing_offset = find_offset(added_indices, pairValues) - if existingOffset is None: - offset: int = len(valueTable) + if existing_offset is None: + offset: int = len(value_table) pair.offset = offset - valueTable.extend(pairValues) + value_table.extend(pairValues) - addedIndexes.append(pair) + added_indices.append(pair) else: - offset: int = existingOffset + offset: int = existing_offset if offset > MAX_U16: raise PluginError("Index pair´s value offset is too high. Value table might be too long.") - indicesTable.extend([maxFrame, offset]) + indices_table.extend([max_frame, offset]) - return valueTable, indicesTable + return value_table, indices_table def headersToC(self, designated: bool, asArray: bool) -> str: cData = StringIO() @@ -389,6 +391,7 @@ def read_binary(self, data: bytes, header: SM64_AnimHeader): for _ in range((header.bone_count + 1) * 3): pair = SM64_AnimPair() pair.read_binary(indicesReader, data, header.values) + pair.clean_frames() self.pairs.append(pair) def readC(self, header: SM64_AnimHeader, c_parser: CParser): @@ -410,4 +413,5 @@ def readC(self, header: SM64_AnimHeader, c_parser: CParser): maxFrame, offset = indices[i].value, indices[i + 1].value pair = SM64_AnimPair() pair.readC(maxFrame, offset, values) + pair.clean_frames() self.pairs.append(pair) diff --git a/fast64_internal/sm64/animation/exporting.py b/fast64_internal/sm64/animation/exporting.py index 6536a0cc5..8750af433 100644 --- a/fast64_internal/sm64/animation/exporting.py +++ b/fast64_internal/sm64/animation/exporting.py @@ -35,7 +35,7 @@ def get_animation_pairs( transXPair, transYPair, transZPair = pairs rotationPairs: list[tuple[SM64_AnimPair]] = [] - for boneInfo in animBonesInfo: + for _ in animBonesInfo: xyzPairs = ( SM64_AnimPair(), SM64_AnimPair(), @@ -58,12 +58,15 @@ def get_animation_pairs( for boneIndex, poseBone in enumerate(animBonesInfo): if boneIndex == 0: # Only first bone has translation. translation: mathutils.Vector = poseBone.location * scale - transXPair.appendFrame(int(translation.x)) - transYPair.appendFrame(int(translation.y)) - transZPair.appendFrame(int(translation.z)) + transXPair.values.append(int(translation.x)) + transYPair.values.append(int(translation.y)) + transZPair.values.append(int(translation.z)) for angle, pair in zip(poseBone.matrix_basis.to_euler(), rotationPairs[boneIndex]): - pair.appendFrame(radian_to_sm64_degree(angle)) + pair.values.append(radian_to_sm64_degree(angle)) + + for pair in pairs: + pair.clean_frames() armature_obj.animation_data.action = pre_export_action bpy.context.scene.frame_current = pre_export_frame @@ -340,7 +343,11 @@ def getAnimationPaths(anim_export_props): def get_animation_data( - armature_obj: Object, action: Action, blender_to_sm64_scale: float, is_dma_structure: bool, headers + armature_obj: Object, + action: Action, + blender_to_sm64_scale: float, + headers, + actor_name: str = "", ): action_props = action.fast64.sm64 @@ -358,10 +365,8 @@ def get_animation_data( else: animation.pairs = get_animation_pairs(blender_to_sm64_scale, action, armature_obj, anim_bones) - animation.isDmaStructure = is_dma_structure - for header_props in headers: - animation.headers.append(header_props.to_header_class()) + animation.headers.append(header_props.to_header_class(animation, action, actor_name)) return animation diff --git a/fast64_internal/sm64/animation/operators.py b/fast64_internal/sm64/animation/operators.py index 4341f6849..03fdc86ea 100644 --- a/fast64_internal/sm64/animation/operators.py +++ b/fast64_internal/sm64/animation/operators.py @@ -26,6 +26,7 @@ from .exporting import ( exportAnimation, exportAnimationTable, + get_animation_data, ) from .utility import ( animation_operator_checks, @@ -272,9 +273,17 @@ def execute_operator(self, context): armature_obj: Object = context.selected_objects[0] - exportAnimation(armature_obj, anim_export_props.selected_action, sm64_props.blender_to_sm64_scale) + action = anim_export_props.selected_action + sm64Anim = get_animation_data( + armature_obj, + action, + sm64_props.blender_to_sm64_scale, + action.fast64.sm64.get_headers(), + anim_export_props.actor_name, + ) self.report({"INFO"}, "Animation exported successfully") + return {"FINISHED"} def execute(self, context: Context): starting_context_mode = context.mode @@ -292,7 +301,7 @@ def execute(self, context: Context): applyRotation([armatureObj], math.radians(90), "X") try: - self.execute_operator(context) + return self.execute_operator(context) except Exception as e: raisePluginError(self, e) return {"CANCELLED"} diff --git a/fast64_internal/sm64/animation/properties.py b/fast64_internal/sm64/animation/properties.py index 1e05958d3..f4b118557 100644 --- a/fast64_internal/sm64/animation/properties.py +++ b/fast64_internal/sm64/animation/properties.py @@ -104,7 +104,7 @@ class SM64_AnimHeaderProps(PropertyGroup): description="ANIM_FLAG_NO_TRANS\nSkips animation translation", ) set_custom_flags: BoolProperty(name="Set Custom Flags") - custom_flags: StringProperty(name="Flags", default="ANIM_FLAG_UNUSED") + custom_flags: StringProperty(name="Flags", default="") # Binary dma_entry: IntProperty(name="DMA Entrie") @@ -144,24 +144,45 @@ def copyHeader(self, actor_name, action, header: "SM64_AnimHeaderProps"): self.custom_name = f"{header.get_anim_name(actor_name, action)}_variant{self.header_variant}" - def to_header_class(self, animation: SM64_Anim, actor_name: str, action: Action) -> SM64_AnimHeader: + def get_int_flags(header): + flags: int = 0 + if header.no_loop: + flags |= 1 << 0 + if header.backwards: + flags |= 1 << 1 + if header.no_acceleration: + flags |= 1 << 2 + if header.only_horizontal_trans: + flags |= 1 << 3 + if header.only_vertical_trans: + flags |= 1 << 4 + if header.disabled: + flags |= 1 << 5 + if header.no_trans: + flags |= 1 << 6 + + return flags + + def to_header_class(self, animation: SM64_Anim, action: Action, actor_name: str = "") -> SM64_AnimHeader: header = SM64_AnimHeader() header.data = animation - animation.headers.append(header) header.name = self.get_anim_name(actor_name, action) - if animation.isDmaStructure or sm64Props.is_binary_export(): - header.flags = getIntFlags(header_props) + + if self.set_custom_flags: + header.custom_flags = self.custom_flags else: - header.flags = getCFlags(header_props) + header.flags = self.get_int_flags() - startFrame, loopStart, loop_end = header_props.get_frame_range(action) + startFrame, loopStart, loop_end = self.get_frame_range(action) - header.trans_divisor = header_props.trans_divisor + header.trans_divisor = self.trans_divisor header.startFrame = startFrame header.loopStart = loopStart header.loop_end = loop_end - header.bone_count = len(anim_bones) + header.bone_count = 0 # TODO: Pass bone count here + + return header def draw_binary(self, layout: UILayout, is_binary_dma: bool): col = layout.column() @@ -183,12 +204,11 @@ def draw_binary(self, layout: UILayout, is_binary_dma: bool): def draw_flag_props(self, layout: UILayout, export_type: str): col = layout.column() - if export_type == "C": - col.prop(self, "set_custom_flags") + col.prop(self, "set_custom_flags") - if self.set_custom_flags: - col.prop(self, "custom_flags") - return + if self.set_custom_flags: + col.prop(self, "custom_flags") + return row = col.row() row.prop(self, "no_acceleration")