diff --git a/fast64_internal/sm64/animation/classes.py b/fast64_internal/sm64/animation/classes.py index 3aacbf0b2..542ade74b 100644 --- a/fast64_internal/sm64/animation/classes.py +++ b/fast64_internal/sm64/animation/classes.py @@ -15,6 +15,7 @@ from ...utility import PluginError, cast_integer, encodeSegmentedAddr, intToHex from ..sm64_constants import MAX_U16, SegmentData +from ..sm64_utility import CommentMatch, adjust_start_end from ..sm64_classes import RomReader, DMATable, DMATableElement, IntArray from .constants import HEADER_STRUCT, HEADER_SIZE, TABLE_ELEMENT_PATTERN @@ -871,12 +872,18 @@ def read_dma_binary( def read_c( self, c_data: str, + start: int, + end: int, + comment_map: list[CommentMatch], read_headers: dict[str, SM64_AnimHeader], header_decls: list[CArrayDeclaration], values_decls: list[CArrayDeclaration], indices_decls: list[CArrayDeclaration], ): - for i, element_match in enumerate(re.finditer(TABLE_ELEMENT_PATTERN, c_data)): + table_start, table_end = adjust_start_end(start, end, comment_map) + self.start, self.end = table_start, table_end + + for i, element_match in enumerate(re.finditer(TABLE_ELEMENT_PATTERN, c_data[start:end])): enum, element, null = ( element_match.group("enum"), element_match.group("element"), @@ -895,12 +902,15 @@ def read_c( read_headers, i, ) + element_start, element_end = adjust_start_end( + table_start + element_match.start(), table_start + element_match.end(), comment_map + ) self.elements.append( SM64_AnimTableElement( element, enum_name=enum, - reference_start=element_match.start(), - reference_end=element_match.end(), + reference_start=element_start - table_start, + reference_end=element_end - table_start, header=header, ) ) diff --git a/fast64_internal/sm64/animation/constants.py b/fast64_internal/sm64/animation/constants.py index d1cde79bb..7ea4bbdc4 100644 --- a/fast64_internal/sm64/animation/constants.py +++ b/fast64_internal/sm64/animation/constants.py @@ -1,7 +1,7 @@ import struct import re -from ...utility import intToHex, COMMENT_PATTERN +from ...utility import intToHex from ..sm64_constants import ACTOR_PRESET_INFO, ActorPresetInfo HEADER_STRUCT = struct.Struct(">h h h h h h I I I") @@ -9,54 +9,42 @@ TABLE_ELEMENT_PATTERN = re.compile( # strict but only in the sense that it requires valid c code r""" - (?:COMMENT_PATTERN)| (?:\[\s*(?P\w+)\s*\]\s*=\s*)? # Don´t capture brackets or equal, works with nums (?:(?:&\s*(?P\w+))|(?PNULL)) # Capture element or null, element requires & (?:\s*,|) # allow no comma, techinically not correct but no other method works - """.replace( - "COMMENT_PATTERN", COMMENT_PATTERN.pattern - ), + """, re.DOTALL | re.VERBOSE | re.MULTILINE, ) TABLE_PATTERN = re.compile( r""" - (?:COMMENT_PATTERN)| const\s+struct\s*Animation\s*\*const\s*(?P\w+)\s* (?:\[.*?\])? # Optional size, don´t capture \s*=\s*\{ - (?P(?:COMMENT_PATTERN|[\s\S])*) # Capture any character including new lines + (?P[\s\S]*) # Capture any character including new lines (?=\}\s*;) # Look ahead for the end - """.replace( - "COMMENT_PATTERN", COMMENT_PATTERN.pattern - ), + """, re.DOTALL | re.VERBOSE | re.MULTILINE, ) TABLE_ENUM_PATTERN = re.compile( # strict but only in the sense that it requires valid c code r""" - (?:COMMENT_PATTERN)| (?P\w+)\s* (?:\s*=\s*(?P\w+)\s*)? (?=,|) # lookahead, allow no comma, techinically not correct but no other method works - """.replace( - "COMMENT_PATTERN", COMMENT_PATTERN.pattern - ), + """, re.DOTALL | re.VERBOSE | re.MULTILINE, ) TABLE_ENUM_LIST_PATTERN = re.compile( r""" - (?:COMMENT_PATTERN)| enum\s*(?P\w+)\s*\{ - (?P(?:COMMENT_PATTERN|[\s\S])*) # Capture any character including new lines, lazy + (?P[\s\S]*) # Capture any character including new lines, lazy (?=\}\s*;) - """.replace( - "COMMENT_PATTERN", COMMENT_PATTERN.pattern - ), + """, re.DOTALL | re.VERBOSE | re.MULTILINE, ) diff --git a/fast64_internal/sm64/animation/exporting.py b/fast64_internal/sm64/animation/exporting.py index e904f11b9..95eb4152c 100644 --- a/fast64_internal/sm64/animation/exporting.py +++ b/fast64_internal/sm64/animation/exporting.py @@ -27,7 +27,8 @@ from ..sm64_constants import BEHAVIOR_COMMANDS, BEHAVIOR_EXITS, defaultExtendSegment4, level_pointers from ..sm64_utility import ( ModifyFoundDescriptor, - find_descriptors, + find_descriptor_in_text, + get_comment_map, to_include_descriptor, write_includes, update_actor_includes, @@ -467,11 +468,10 @@ def update_anim_header(path: Path, table_name: str, gen_enums: bool, override_fi def update_enum_file(path: Path, override_files: bool, table: SM64_AnimTable): + text, comment_map = "", [] existing_file = path.exists() and not override_files if existing_file: - text = path.read_text() - else: - text = "" + text, comment_map = get_comment_map(path.read_text()) if table.enum_list_start == -1 and table.enum_list_end == -1: # create new enum list if text and text[-1] not in {"\n", "\r"}: @@ -515,26 +515,31 @@ def update_table_file( designated: bool, enum_list_path: Path, ): - text, enum_text = "", "" + assert isinstance(table.reference, str) and table.reference, "Invalid table reference" + + text, comment_less, enum_text, comment_map = "", "", "", [] existing_file = table_path.exists() and not override_files if existing_file: text = table_path.read_text() + comment_less, comment_map = get_comment_map(text) # add include if not already there descriptor = to_include_descriptor(Path("table_enum.h")) - if gen_enums and descriptor not in find_descriptors(text, [descriptor])[0]: + if gen_enums and len(find_descriptor_in_text(descriptor, comment_less, comment_map)) == 0: text = '#include "table_enum.h"\n' + text # First, find existing tables - tables = import_tables(text, table_path, table.reference) + tables = import_tables(comment_less, table_path, comment_map, table.reference) + enum_tables = [] if gen_enums: + assert isinstance(table.enum_list_reference, str) and table.enum_list_reference + enum_text, enum_comment_less, enum_comment_map = "", "", [] if enum_list_path.exists() and not override_files: enum_text = enum_list_path.read_text() - enum_tables = import_enums(enum_text, enum_list_path, table.enum_list_reference) + enum_comment_less, enum_comment_map = get_comment_map(enum_text) + enum_tables = import_enums(enum_comment_less, enum_list_path, enum_comment_map, table.enum_list_reference) if len(enum_tables) > 1: raise PluginError(f'Duplicate enum list "{table.enum_list_reference}"') - else: - enum_tables = [] if len(tables) > 1: raise PluginError(f'Duplicate animation table "{table.reference}"') diff --git a/fast64_internal/sm64/animation/importing.py b/fast64_internal/sm64/animation/importing.py index b467d83e0..21237a57d 100644 --- a/fast64_internal/sm64/animation/importing.py +++ b/fast64_internal/sm64/animation/importing.py @@ -12,12 +12,12 @@ from mathutils import Quaternion from ...f3d.f3d_parser import math_eval -from ...utility import PluginError, decodeSegmentedAddr, filepath_checks, path_checks, intToHex, removeComments +from ...utility import PluginError, decodeSegmentedAddr, filepath_checks, path_checks, intToHex from ...utility_anim import create_basic_action from ..sm64_constants import AnimInfo, level_pointers from ..sm64_level_parser import parseLevelAtPointer -from ..sm64_utility import import_rom_checks +from ..sm64_utility import CommentMatch, get_comment_map, adjust_start_end, import_rom_checks from ..sm64_classes import RomReader from .utility import ( @@ -27,6 +27,7 @@ get_scene_anim_props, get_anim_actor_name, anim_name_to_enum_name, + table_name_to_enum, ) from .classes import ( SM64_Anim, @@ -353,7 +354,7 @@ def from_anim_table_class( anim_props.write_data_seperately = True anim_props.data_address = intToHex(min(start_addresses)) anim_props.data_end_address = intToHex(max(end_addresses)) - elif isinstance(table.reference, str): # C + elif isinstance(table.reference, str) and table.reference: # C if use_custom_name: anim_props.custom_table_name = table.reference if anim_props.get_table_name(actor_name) != anim_props.custom_table_name: @@ -416,7 +417,7 @@ def update_table_with_table_enum(table: SM64_AnimTable, enum_table: SM64_AnimTab table.enum_list_end = enum_table.enum_list_end -def import_enums(c_data: str, path: Path, specific_name=""): +def import_enums(c_data: str, path: Path, comment_map: list[CommentMatch], specific_name=""): tables = [] for list_match in re.finditer(TABLE_ENUM_LIST_PATTERN, c_data): name, content = list_match.group("name"), list_match.group("content") @@ -424,7 +425,7 @@ def import_enums(c_data: str, path: Path, specific_name=""): continue if specific_name and name != specific_name: continue - list_start, list_end = c_data.find(content, list_match.start()), list_match.end() + list_start, list_end = adjust_start_end(c_data.find(content, list_match.start()), list_match.end(), comment_map) content = c_data[list_start:list_end] table = SM64_AnimTable( file_name=path.name, @@ -436,9 +437,12 @@ def import_enums(c_data: str, path: Path, specific_name=""): name, num = (element_match.group("name"), element_match.group("num")) if name is None and num is None: # comment continue + enum_start, enum_end = adjust_start_end( + list_start + element_match.start(), list_start + element_match.end(), comment_map + ) table.elements.append( SM64_AnimTableElement( - enum_name=name, enum_val=num, enum_start=element_match.start(), enum_end=element_match.end() + enum_name=name, enum_val=num, enum_start=enum_start - list_start, enum_end=enum_end - list_start ) ) tables.append(table) @@ -448,10 +452,11 @@ def import_enums(c_data: str, path: Path, specific_name=""): def import_tables( c_data: str, path: Path, + comment_map: list[CommentMatch], specific_name="", - header_decls: list[CArrayDeclaration] = None, - values_decls: list[CArrayDeclaration] = None, - indices_decls: list[CArrayDeclaration] = None, + header_decls: Optional[list[CArrayDeclaration]] = None, + values_decls: Optional[list[CArrayDeclaration]] = None, + indices_decls: Optional[list[CArrayDeclaration]] = None, ): read_headers = {} header_decls, values_decls, indices_decls = ( @@ -468,16 +473,24 @@ def import_tables( if specific_name and name != specific_name: continue - table_start, table_end = c_data.find(content, table_match.start()), table_match.end() - table = SM64_AnimTable(name, file_name=path.name, elements=table_elements, start=table_start, end=table_end) - table.read_c(c_data[table_start:table_end], read_headers, header_decls, values_decls, indices_decls) + table = SM64_AnimTable(name, file_name=path.name, elements=table_elements) + table.read_c( + c_data, + c_data.find(content, table_match.start()), + table_match.end(), + comment_map, + read_headers, + header_decls, + values_decls, + indices_decls, + ) tables.append(table) return tables DECL_PATTERN = re.compile( r"(static\s+const\s+struct\s+Animation|static\s+const\s+u16|static\s+const\s+s16)\s+" - r"(\w+)\s*?(?:\[.*?\])?\s*?=\s*?\{(.*?)\};", + r"(\w+)\s*?(?:\[.*?\])?\s*?=\s*?\{(.*?)\s*?\};", re.DOTALL, ) VALUE_SPLIT_PATTERN = re.compile(r"\s*(?:(?:\.(?P\w+)|\[\s*(?P.*?)\s*\])\s*=\s*)?(?P.+?)(?:,|\Z)") @@ -527,7 +540,7 @@ def import_c_animations(path: Path) -> tuple[SM64_AnimTable | None, dict[str, SM raise PluginError("Path is neither a file or a folder but it exists, somehow.") print("Reading from:\n" + "\n".join([f.name for f in file_paths])) - c_files = {file_path: removeComments(file_path.read_text()) for file_path in file_paths} + c_files = {file_path: get_comment_map(file_path.read_text()) for file_path in file_paths} decl_lists = {"static const struct Animation": [], "static const u16": [], "static const s16": []} header_decls, indices_decls, value_decls = ( @@ -536,15 +549,27 @@ def import_c_animations(path: Path) -> tuple[SM64_AnimTable | None, dict[str, SM decl_lists["static const s16"], ) tables: list[SM64_AnimTable] = [] - for file_path, c_data in c_files.items(): - find_decls(c_data, file_path, decl_lists) - for file_path, c_data in c_files.items(): - tables.extend(import_tables(c_data, file_path, "", header_decls, value_decls, indices_decls)) + enum_lists: list[SM64_AnimTable] = [] + for file_path, (comment_less, _comment_map) in c_files.items(): + find_decls(comment_less, file_path, decl_lists) + for file_path, (comment_less, comment_map) in c_files.items(): + tables.extend(import_tables(comment_less, file_path, comment_map, "", header_decls, value_decls, indices_decls)) + enum_lists.extend(import_enums(comment_less, file_path, comment_map)) if len(tables) > 1: raise ValueError("More than 1 table declaration") elif len(tables) == 1: table: SM64_AnimTable = tables[0] + if enum_lists: + enum_table = next( # find enum with the same name or use the first + ( + enum_table + for enum_table in enum_lists + if enum_table.reference == table_name_to_enum(table.reference) + ), + enum_lists[0], + ) + update_table_with_table_enum(table, enum_table) read_headers = {header.reference: header for header in table.header_set} return table, read_headers else: diff --git a/fast64_internal/sm64/animation/properties.py b/fast64_internal/sm64/animation/properties.py index 84b3c0515..060d626af 100644 --- a/fast64_internal/sm64/animation/properties.py +++ b/fast64_internal/sm64/animation/properties.py @@ -1,4 +1,3 @@ -from pathlib import Path import os import bpy @@ -53,6 +52,7 @@ anim_name_to_enum_name, action_name_to_enum_name, duplicate_name, + table_name_to_enum, ) from .importing import get_enum_from_import_preset, update_table_preset @@ -1005,8 +1005,7 @@ def get_table_name(self, actor_name: str) -> str: return f"{actor_name}_anims" def get_enum_name(self, actor_name: str): - table_name = self.get_table_name(actor_name) - return table_name.title().replace("_", "") + return table_name_to_enum(self.get_table_name(actor_name)) def get_enum_end(self, actor_name: str): table_name = self.get_table_name(actor_name) diff --git a/fast64_internal/sm64/animation/utility.py b/fast64_internal/sm64/animation/utility.py index 0a7ffd2af..b8f18efb9 100644 --- a/fast64_internal/sm64/animation/utility.py +++ b/fast64_internal/sm64/animation/utility.py @@ -131,6 +131,10 @@ def duplicate_name(name: str, existing_names: dict[str, int]) -> str: return name +def table_name_to_enum(name: str): + return name.title().replace("_", "") + + def get_action_props(action: Action) -> "SM64_ActionAnimProperty": return action.fast64.sm64.animation diff --git a/fast64_internal/sm64/sm64_utility.py b/fast64_internal/sm64/sm64_utility.py index 678988dfb..97ab4e563 100644 --- a/fast64_internal/sm64/sm64_utility.py +++ b/fast64_internal/sm64/sm64_utility.py @@ -166,18 +166,23 @@ class CommentMatch(NamedTuple): size: int -def find_descriptor_in_text(value: ModifyFoundDescriptor, commentless: str, comment_map: list[CommentMatch]): +def adjust_start_end(start: int, end: int, comment_map: list[CommentMatch]): + for commentless_pos, comment_size in comment_map: + if start >= commentless_pos: + start += comment_size + if end >= commentless_pos: + end += comment_size + return start, end + + +def find_descriptor_in_text( + value: ModifyFoundDescriptor, commentless: str, comment_map: list[CommentMatch], start=0, end=-1 +): matches: list[DescriptorMatch] = [] - for match in re.finditer(value.regex, commentless): - found_start, found_end = match.start(), match.end() - start, end = match.start(), match.end() - for commentless_pos, comment_size in comment_map: # only remove parts outside comments - if commentless_pos >= found_start and commentless_pos <= found_end: # comment inside descriptor - end += comment_size - elif found_end >= commentless_pos: - start += comment_size - end += comment_size - matches.append(DescriptorMatch(match.group(0), start, end)) + for match in re.finditer(value.regex, commentless[start:end]): + matches.append( + DescriptorMatch(match.group(0), *adjust_start_end(start + match.start(), start + match.end(), comment_map)) + ) return matches @@ -225,7 +230,7 @@ def find_descriptors( # find first footer after the header if footer_matches: if header_matches: - footer_pos = next((pos for _, pos, _ in footer_matches if pos >= header_pos), footer_matches[-1]) + footer_pos = next((pos for _, pos, _ in footer_matches if pos >= header_pos), footer_matches[-1].start) else: _, footer_pos, _ = footer_matches[-1] else: @@ -235,7 +240,7 @@ def find_descriptors( found_matches: dict[ModifyFoundDescriptor, list[DescriptorMatch]] = {} for descriptor in descriptors: - matches = find_descriptor_in_text(descriptor, commentless[header_pos:footer_pos], comment_map) + matches = find_descriptor_in_text(descriptor, commentless, comment_map, header_pos, footer_pos) if matches: found_matches.setdefault(descriptor, []).extend(matches) return found_matches, footer_pos