From 82f7e22cacaf53c4f5a24244ddbb97b0673cfb4b Mon Sep 17 00:00:00 2001 From: Lila Date: Fri, 27 Sep 2024 17:21:48 +0100 Subject: [PATCH] New versatile write_or_delete_if_found function Rewrite --- fast64_internal/sm64/sm64_anim.py | 3 +- fast64_internal/sm64/sm64_collision.py | 23 +- fast64_internal/sm64/sm64_f3d_writer.py | 31 +- fast64_internal/sm64/sm64_geolayout_writer.py | 13 +- fast64_internal/sm64/sm64_level_writer.py | 35 ++- fast64_internal/sm64/sm64_texscroll.py | 9 +- fast64_internal/sm64/sm64_utility.py | 266 +++++++++++------- fast64_internal/utility.py | 37 +-- 8 files changed, 248 insertions(+), 169 deletions(-) diff --git a/fast64_internal/sm64/sm64_anim.py b/fast64_internal/sm64/sm64_anim.py index f9edaffdb..263e6b29a 100644 --- a/fast64_internal/sm64/sm64_anim.py +++ b/fast64_internal/sm64/sm64_anim.py @@ -234,7 +234,7 @@ def exportAnimationC(armatureObj, loopAnim, dirPath, dirName, groupName, customE headerFile.write("extern const struct Animation *const " + animsName + "[];\n") headerFile.close() - write_includes(Path(animDirPath) / "data.inc.c", [f'"{animFileName}"']) + write_includes(Path(animDirPath) / "data.inc.c", [Path(animFileName)]) # write to table.inc.c tableFilePath = os.path.join(animDirPath, "table.inc.c") @@ -277,6 +277,7 @@ def exportAnimationC(armatureObj, loopAnim, dirPath, dirName, groupName, customE groupName, Path(dirPath), dirName, + levelName, [Path("anims/data.inc.c"), Path("anims/table.inc.c")], [Path("anim_header.h")], ) diff --git a/fast64_internal/sm64/sm64_collision.py b/fast64_internal/sm64/sm64_collision.py index 74f7f2a1f..5f342bd27 100644 --- a/fast64_internal/sm64/sm64_collision.py +++ b/fast64_internal/sm64/sm64_collision.py @@ -9,7 +9,7 @@ insertableBinaryTypes, defaultExtendSegment4, ) -from .sm64_utility import export_rom_checks, update_actor_includes +from .sm64_utility import export_rom_checks, to_include_descriptor, update_actor_includes, write_or_delete_if_found from .sm64_objects import SM64_Area, start_process_sm64_objects from .sm64_level_parser import parseLevelAtPointer from .sm64_rom_tweaks import ExtendBank0x04 @@ -24,7 +24,6 @@ get64bitAlignedAddr, prop_split, getExportDir, - deleteIfFound, duplicateHierarchy, cleanupDuplicatedObjects, writeInsertableFile, @@ -334,15 +333,23 @@ def exportCollisionC( data_includes = [Path("collision.inc.c")] if writeRoomsFile: data_includes.append(Path("rooms.inc.c")) - update_actor_includes(headerType, groupName, Path(dirPath), name, data_includes, [Path("collision_header.h")]) + update_actor_includes( + headerType, groupName, Path(dirPath), name, levelName, data_includes, [Path("collision_header.h")] + ) if not writeRoomsFile: # TODO: Could be done better if headerType == "Actor": - groupPathC = os.path.join(dirPath, groupName + ".c") - deleteIfFound(groupPathC, '\n#include "' + name + '/rooms.inc.c"') + group_path_c = Path(dirPath) / f"{groupName}.c" + write_or_delete_if_found(group_path_c, to_remove=[to_include_descriptor(Path(name) / "rooms.inc.c")]) elif headerType == "Level": - groupPathC = os.path.join(dirPath, "leveldata.c") - deleteIfFound(groupPathC, '#include "levels/' + levelName + "/" + name + '/rooms.inc.c"') - deleteIfFound(groupPathC, '#include "rooms.inc.c"') + group_path_c = Path(dirPath) / "leveldata.c" + write_or_delete_if_found( + group_path_c, + to_remove=[ + to_include_descriptor( + Path(name) / "rooms.inc.c", Path("levels") / levelName / name / "rooms.inc.c" + ), + ], + ) return cDefine diff --git a/fast64_internal/sm64/sm64_f3d_writer.py b/fast64_internal/sm64/sm64_f3d_writer.py index 38b5dd8a6..e93b2231f 100644 --- a/fast64_internal/sm64/sm64_f3d_writer.py +++ b/fast64_internal/sm64/sm64_f3d_writer.py @@ -15,7 +15,15 @@ update_world_default_rendermode, ) from .sm64_texscroll import modifyTexScrollFiles, modifyTexScrollHeadersGroup -from .sm64_utility import export_rom_checks, starSelectWarning, update_actor_includes, writeMaterialHeaders +from .sm64_utility import ( + END_IF_FOOTER, + ModifyFoundDescriptor, + export_rom_checks, + starSelectWarning, + update_actor_includes, + write_or_delete_if_found, + write_material_headers, +) from .sm64_level_parser import parseLevelAtPointer from .sm64_rom_tweaks import ExtendBank0x04 from typing import Tuple, Union, Iterable @@ -62,7 +70,6 @@ applyRotation, toAlnum, checkIfPathExists, - writeIfNotFound, overwriteData, getExportDir, writeMaterialFiles, @@ -196,7 +203,9 @@ def exportTexRectToC(dirPath, texProp, texDir, savePNG, name, exportToProject, p overwriteData("const\s*u8\s*", textures[0].name, data, seg2CPath, None, False) # Append texture declaration to segment2.h - writeIfNotFound(seg2HPath, declaration, "#endif") + write_or_delete_if_found( + Path(seg2HPath), ModifyFoundDescriptor(declaration), path_must_exist=True, footer=END_IF_FOOTER + ) # Write/Overwrite function to hud.c overwriteData("void\s*", fTexRect.name, code, hudPath, projectExportData[1], True) @@ -425,15 +434,15 @@ def sm64ExportF3DtoC( cDefFile.write(staticData.header) cDefFile.close() - update_actor_includes(headerType, groupName, Path(dirPath), name, ["model.inc.c"], ["header.h"]) + update_actor_includes(headerType, groupName, Path(dirPath), name, levelName, ["model.inc.c"], ["header.h"]) fileStatus = None if not customExport: if headerType == "Actor": if DLFormat != DLFormat.Static: # Change this - writeMaterialHeaders( - basePath, - '"actors/' + toAlnum(name) + '/material.inc.c"', - '"actors/' + toAlnum(name) + '/material.inc.h"', + write_material_headers( + Path(basePath), + Path("actors") / toAlnum(name) / "material.inc.c", + Path("actors") / toAlnum(name) / "material.inc.h", ) texscrollIncludeC = '#include "actors/' + name + '/texscroll.inc.c"' @@ -443,10 +452,10 @@ def sm64ExportF3DtoC( elif headerType == "Level": if DLFormat != DLFormat.Static: # Change this - writeMaterialHeaders( + write_material_headers( basePath, - '"levels/' + levelName + "/" + toAlnum(name) + '/material.inc.c"', - '"levels/' + levelName + "/" + toAlnum(name) + '/material.inc.h"', + Path("levels") / levelName / toAlnum(name) / "material.inc.c", + Path("levels") / levelName / toAlnum(name) / "material.inc.h", ) texscrollIncludeC = '#include "levels/' + levelName + "/" + name + '/texscroll.inc.c"' diff --git a/fast64_internal/sm64/sm64_geolayout_writer.py b/fast64_internal/sm64/sm64_geolayout_writer.py index bda5bb0fa..3c8038612 100644 --- a/fast64_internal/sm64/sm64_geolayout_writer.py +++ b/fast64_internal/sm64/sm64_geolayout_writer.py @@ -14,7 +14,7 @@ from .sm64_texscroll import modifyTexScrollFiles, modifyTexScrollHeadersGroup from .sm64_level_parser import parseLevelAtPointer from .sm64_rom_tweaks import ExtendBank0x04 -from .sm64_utility import export_rom_checks, starSelectWarning, update_actor_includes, writeMaterialHeaders +from .sm64_utility import export_rom_checks, starSelectWarning, update_actor_includes, write_material_headers from ..utility import ( PluginError, @@ -658,12 +658,12 @@ def saveGeolayoutC( geoData = geolayoutGraph.to_c() if headerType == "Actor": - matCInclude = '"actors/' + dirName + '/material.inc.c"' - matHInclude = '"actors/' + dirName + '/material.inc.h"' + matCInclude = Path("actors") / dirName / "material.inc.c" + matHInclude = Path("actors") / dirName / "material.inc.h" headerInclude = '#include "actors/' + dirName + '/geo_header.h"' else: - matCInclude = '"levels/' + levelName + "/" + dirName + '/material.inc.c"' - matHInclude = '"levels/' + levelName + "/" + dirName + '/material.inc.h"' + matCInclude = Path("levels") / levelName / dirName / "material.inc.c" + matHInclude = Path("levels") / levelName / dirName / "material.inc.h" headerInclude = '#include "levels/' + levelName + "/" + dirName + '/geo_header.h"' modifyTexScrollFiles(exportDir, geoDirPath, scrollData) @@ -714,6 +714,7 @@ def saveGeolayoutC( groupName, Path(dirPath), dirName, + levelName, [Path("model.inc.c")], [Path("geo_header.h")], [Path("geo.inc.c")], @@ -791,7 +792,7 @@ def saveGeolayoutC( ) if DLFormat != DLFormat.Static: # Change this - writeMaterialHeaders(exportDir, matCInclude, matHInclude) + write_material_headers(Path(exportDir), matCInclude, matHInclude) return staticData.header, fileStatus diff --git a/fast64_internal/sm64/sm64_level_writer.py b/fast64_internal/sm64/sm64_level_writer.py index 267d4d953..b671896fb 100644 --- a/fast64_internal/sm64/sm64_level_writer.py +++ b/fast64_internal/sm64/sm64_level_writer.py @@ -12,7 +12,14 @@ from .sm64_f3d_writer import SM64Model, SM64GfxFormatter from .sm64_geolayout_writer import setRooms, convertObjectToGeolayout from .sm64_f3d_writer import modifyTexScrollFiles, modifyTexScrollHeadersGroup -from .sm64_utility import cameraWarning, starSelectWarning, write_includes, writeMaterialHeaders +from .sm64_utility import ( + cameraWarning, + starSelectWarning, + to_include_descriptor, + write_includes, + write_or_delete_if_found, + write_material_headers, +) from ..utility import ( PluginError, @@ -22,7 +29,6 @@ restoreHiddenState, overwriteData, selectSingleObject, - deleteIfFound, applyBasicTweaks, applyRotation, raisePluginError, @@ -999,10 +1005,10 @@ def include_proto(file_name, new_line_first=False): if not customExport: if DLFormat != DLFormat.Static: # Write material headers - writeMaterialHeaders( - exportDir, - f'"levels/{level_name}/material.inc.c"', - f'"levels/{level_name}/material.inc.h"', + write_material_headers( + Path(exportDir), + Path("levels") / level_name / "material.inc.c", + Path("levels") / level_name / "material.inc.c", ) # Export camera triggers @@ -1073,19 +1079,26 @@ def include_proto(file_name, new_line_first=False): createHeaderFile(level_name, headerPath) # Write level data - write_includes(Path(geoPath), ['"geo.inc.c"']) - write_includes(Path(levelDataPath), ['"leveldata.inc.c"']) - write_includes(Path(headerPath), ['"header.inc.h"'], before_endif=True) + write_includes(Path(geoPath), [Path("geo.inc.c")]) + write_includes(Path(levelDataPath), [Path("leveldata.inc.c")]) + write_includes(Path(headerPath), [Path("header.inc.h")], before_endif=True) + old_include = to_include_descriptor(Path("levels") / level_name / "texture_include.inc.c") if fModel.texturesSavedLastExport == 0: textureIncludePath = os.path.join(level_dir, "texture_include.inc.c") if os.path.exists(textureIncludePath): os.remove(textureIncludePath) # This one is for backwards compatibility purposes - deleteIfFound(os.path.join(level_dir, "texture.inc.c"), include_proto("texture_include.inc.c")) + write_or_delete_if_found( + Path(level_dir) / "texture.inc.c", + to_remove=[old_include], + ) # This one is for backwards compatibility purposes - deleteIfFound(levelDataPath, include_proto("texture_include.inc.c")) + write_or_delete_if_found( + Path(levelDataPath), + to_remove=[old_include], + ) texscrollIncludeC = include_proto("texscroll.inc.c") texscrollIncludeH = include_proto("texscroll.inc.h") diff --git a/fast64_internal/sm64/sm64_texscroll.py b/fast64_internal/sm64/sm64_texscroll.py index 70734b247..ad7836de5 100644 --- a/fast64_internal/sm64/sm64_texscroll.py +++ b/fast64_internal/sm64/sm64_texscroll.py @@ -2,7 +2,7 @@ import os, re, bpy from ..utility import PluginError, getDataFromFile, saveDataToFile, CScrollData, CData from .c_templates.tile_scroll import tile_scroll_c, tile_scroll_h -from .sm64_utility import getMemoryCFilePath, write_includes +from .sm64_utility import END_IF_FOOTER, ModifyFoundDescriptor, getMemoryCFilePath, write_or_delete_if_found # This is for writing framework for scroll code. # Actual scroll code found in f3d_gbi.py (FVertexScrollData) @@ -79,7 +79,12 @@ def writeSegmentROMTable(baseDir): memFile.close() # Add extern definition of segment table - write_includes(Path(baseDir) / "src/game/memory.h", externs=["uintptr_t sSegmentROMTable[32]"], before_endif=True) + write_or_delete_if_found( + Path(baseDir) / "src/game/memory.h", + [ModifyFoundDescriptor("uintptr_t sSegmentROMTable[32];", r"uintptr_t\h*sSegmentROMTable\[.*?\]\h?;")], + path_must_exist=True, + footer=END_IF_FOOTER, + ) def writeScrollTextureCall(path, include, callString): diff --git a/fast64_internal/sm64/sm64_utility.py b/fast64_internal/sm64/sm64_utility.py index 2911417dd..58cea3bc0 100644 --- a/fast64_internal/sm64/sm64_utility.py +++ b/fast64_internal/sm64/sm64_utility.py @@ -1,3 +1,4 @@ +from typing import NamedTuple from pathlib import Path import os import re @@ -5,15 +6,7 @@ import bpy from bpy.types import UILayout -from ..utility import ( - PluginError, - filepath_checks, - removeComments, - run_and_draw_errors, - multilineLabel, - prop_split, - COMMENT_PATTERN, -) +from ..utility import PluginError, filepath_checks, run_and_draw_errors, multilineLabel, prop_split, COMMENT_PATTERN from .sm64_function_map import func_map @@ -135,92 +128,166 @@ def convert_addr_to_func(addr: str): return addr -# \h is invalid because the python devs don´t believe in god -INCLUDE_EXTERN_PATTERN = re.compile( - r""" - # expects comments to be removed before running, we don´t need index - ^\h*\#\h*include\h*(.*?)\h*$| # catch includes - extern\s*(.*?)\s*; # catch externs - """.replace( - r"\h", r"[^\v\S]" - ).replace( - "COMMENT_PATTERN", COMMENT_PATTERN.pattern - ), - re.MULTILINE | re.VERBOSE, -) -ENDIF_PATTERN = re.compile( - r""" - (?:COMMENT_PATTERN)*?| # can't be a negative lookbehind, size is unknown - (?P^\h*\#\h*endif) # catch endif - """.replace( - r"\h", r"[^\v\S]" - ).replace( - "COMMENT_PATTERN", COMMENT_PATTERN.pattern - ), - re.MULTILINE | re.VERBOSE, -) - - -def find_includes_and_externs(text: str) -> tuple[set[str], set[str]]: - text = removeComments(text) - matches = re.findall(INCLUDE_EXTERN_PATTERN, text) - if matches: - existing_includes, existing_externs = zip(*re.findall(INCLUDE_EXTERN_PATTERN, text)) - return set(existing_includes), set(existing_externs) - return {}, {} +class ModifyFoundDescriptor: + string: str + regex: str + def __init__(self, string: str, regex: str = ""): + self.string = string + if regex: + self.regex = regex.replace(r"\h", r"[^\v\S]") # /h is invalid... for some reason + else: + self.regex = re.escape(string) + r"\n?" -def write_includes( + +def write_or_delete_if_found( path: Path, - includes: list[str] = None, - externs: list[str] = None, + to_add: list[ModifyFoundDescriptor] | None = None, + to_remove: list[ModifyFoundDescriptor] | None = None, path_must_exist=False, create_new=False, - before_endif=False, -) -> bool: - """Smarter version of writeIfNotFound, handles comments and all kinds of weird formatting - but most importantly files without a trailing newline. - Returns true if something was added. - Example arguments: includes=['"mario/anims/data.inc.c"'], - externs=["const struct Animation *const mario_anims[]"] - """ + error_if_no_header=False, + header: ModifyFoundDescriptor | None = None, + error_if_no_footer=False, + footer: ModifyFoundDescriptor | None = None, + ignore_comments=True, +): + class DescriptorMatch(NamedTuple): + string: str + start: int + end: int + + def find_descriptor_in_text(value: ModifyFoundDescriptor, commentless: str, comment_map: dict): + matches: list[DescriptorMatch] = list() + for match in re.finditer(value.regex, commentless): + found_start, found_end = match.start(), match.end() + start, end = match.start(), match.end() + for commentless_start, comment_size in comment_map: # only remove parts outside comments + if commentless_start >= found_start and commentless_start <= found_end: # comment inside descriptor + end += comment_size + elif found_end >= commentless_start: + start += comment_size + end += comment_size + matches.append(DescriptorMatch(match.group(0), start, end)) + return matches + + comment_map = [] + commentless = "" + found_matches: dict[ModifyFoundDescriptor, list[DescriptorMatch]] = {} + changed = False + text = "" + header_pos = footer_pos = 0 + to_add, to_remove = to_add or [], to_remove or [] + assert not (path_must_exist and create_new), "path_must_exist and create_new" if path_must_exist: filepath_checks(path) - if not create_new and not includes and not externs: + if not create_new and not to_add and not to_remove: return False - includes, externs = includes or [], externs or [] if os.path.exists(path) and not create_new: text = path.read_text() - commentless = removeComments(text) + + if ignore_comments: + commentless = "" + last_pos = 0 + + for match in re.finditer(COMMENT_PATTERN, text): + commentless += text[last_pos : match.start()] # add text before comment + string = match.group(0) + if string.startswith("/"): + comment_map.append((len(commentless), len(string) - 1)) + commentless += " " + else: + commentless += string + last_pos = match.end() + + commentless += text[last_pos:] # add any remaining text after the last match + else: + commentless = text if commentless and commentless[-1] not in {"\n", "\r"}: # add end new line if not there text += "\n" - changed = False - else: - text, commentless, changed = "", "", True - existing_includes, existing_externs = find_includes_and_externs(commentless) - new_text = "" - for include in includes: - if include not in existing_includes: - new_text += f"#include {include}\n" - changed = True - for extern in externs: - if extern not in existing_externs: - new_text += f"extern {extern};\n" + header_matches = find_descriptor_in_text(header, commentless, comment_map) if header is not None else [] + footer_matches = find_descriptor_in_text(footer, commentless, comment_map) if footer is not None else [] + + header_pos = 0 + if len(header_matches) > 0: + _, header_pos, _ = header_matches[0] + elif error_if_no_header: + raise PluginError(f"Header {header.string} does not exist.") + + # 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]) + elif len(footer_matches) > 0: + _, footer_pos, _ = footer_matches[-1] + else: + if error_if_no_footer: + raise PluginError(f"Footer {footer.string} does not exist.") + footer_pos = len(text) + + for descriptor in to_add + to_remove: + matches = find_descriptor_in_text(descriptor, commentless[header_pos:footer_pos], comment_map) + if matches: + found_matches.setdefault(descriptor, []).extend(matches) + + for descriptor in to_remove: + matches = found_matches.get(descriptor) + if matches is None: + continue + print(f"Removing {descriptor.string} in {str(path)}") + for match in matches: changed = True - if not changed: - return False + text = text[: match.start] + text[match.end :] # Remove match + diff = match.end - match.start + for other_match in (other_match for matches in found_matches.values() for other_match in matches): + if other_match.start > match.start: + other_match.start -= diff + other_match.end -= diff + if footer_pos > match.start: + footer_pos -= diff + + additions = "" + for descriptor in to_add: + if descriptor in found_matches: + continue + print(f"Adding {descriptor.string} in {str(path)}") + additions += f"{descriptor.string}\n" + changed = True + text = text[:footer_pos] + additions + text[footer_pos:] + + if changed or create_new: + path.write_text(text) + return True + return False - pos = len(text) - if before_endif: # don't error if there is no endif as the user may just be using #pragma once - for match in re.finditer(ENDIF_PATTERN, text): - if match.group("endif"): - pos = match.start() - text = text[:pos] + new_text + text[pos:] - path.write_text(text) - return True + +def to_include_descriptor(include: Path, *alternatives: Path): + base_regex = r'\n?#\h*?include\h*?"{0}"' + regex = base_regex.format(include.as_posix()) + for alternative in alternatives: + regex += f"|{base_regex.format(alternative.as_posix())}" + return ModifyFoundDescriptor(f'#include "{include.as_posix()}"', regex) + + +END_IF_FOOTER = ModifyFoundDescriptor("#endif", r"#\h*?endif") + + +def write_includes( + path: Path, includes: list[Path] = None, path_must_exist=False, create_new=False, before_endif=False +) -> bool: + to_add = [] + for include in includes or []: + to_add.append(to_include_descriptor(include)) + write_or_delete_if_found( + path, + to_add, + path_must_exist=path_must_exist, + create_new=create_new, + footer=END_IF_FOOTER if before_endif else None, + ) def update_actor_includes( @@ -228,11 +295,11 @@ def update_actor_includes( group_name: str, header_dir: Path, dir_name: str, + level_name: str | None = None, # for backwards compatibility data_includes: list[Path] = None, header_includes: list[Path] = None, geo_includes: list[Path] = None, ): - data_includes, header_includes, geo_includes = data_includes or [], header_includes or [], geo_includes or [] if header_type == "Actor": if not group_name: raise PluginError("Empty group name") @@ -248,23 +315,34 @@ def update_actor_includes( else: raise PluginError(f'Unknown header type "{header_type}"') - if write_includes( - data_path, [f'"{(Path(dir_name) / include).as_posix()}"' for include in data_includes], path_must_exist=True - ): + def write_includes_with_alternate(path: Path, includes: list[Path] | None, before_endif=False): + if includes is None: + return False + if header_type == "Level": + path_and_alternates = [ + [ + Path(dir_name) / include, + Path("levels") / level_name / (dir_name) / include, # backwards compatability + ] + for include in includes + ] + else: + path_and_alternates = [[Path(dir_name) / include] for include in includes] + return write_or_delete_if_found( + path, + [to_include_descriptor(*paths) for paths in path_and_alternates], + path_must_exist=True, + footer=END_IF_FOOTER if before_endif else None, + ) + + if write_includes_with_alternate(data_path, data_includes): print(f"Updated data includes at {header_path}.") - if write_includes( - header_path, - [f'"{(Path(dir_name) / include).as_posix()}"' for include in header_includes], - path_must_exist=True, - before_endif=True, - ): + if write_includes_with_alternate(header_path, header_includes, before_endif=True): print(f"Updated header includes at {header_path}.") - if write_includes( - geo_path, [f'"{(Path(dir_name) / include).as_posix()}"' for include in geo_includes], path_must_exist=True - ): + if write_includes_with_alternate(geo_path, geo_includes): print(f"Updated geo data at {geo_path}.") -def writeMaterialHeaders(exportDir, matCInclude, matHInclude): - write_includes(Path(exportDir) / "src/game/materials.c", [matCInclude]) - write_includes(Path(exportDir) / "src/game/materials.h", [matHInclude], before_endif=True) +def write_material_headers(decomp: Path, c_include: Path, h_include: Path): + write_includes(decomp / "src/game/materials.c", [c_include]) + write_includes(decomp / "src/game/materials.h", [h_include], before_endif=True) diff --git a/fast64_internal/utility.py b/fast64_internal/utility.py index 916f8947b..3da649478 100644 --- a/fast64_internal/utility.py +++ b/fast64_internal/utility.py @@ -737,40 +737,6 @@ def overwriteData(headerRegex, name, value, filePath, writeNewBeforeString, isFu raise PluginError(filePath + " does not exist.") -def writeIfNotFound(filePath, stringValue, footer): - if os.path.exists(filePath): - fileData = open(filePath, "r") - fileData.seek(0) - stringData = fileData.read() - fileData.close() - if stringValue not in stringData: - if len(footer) > 0: - footerIndex = stringData.rfind(footer) - if footerIndex == -1: - raise PluginError("Footer " + footer + " does not exist.") - stringData = stringData[:footerIndex] + stringValue + "\n" + stringData[footerIndex:] - else: - stringData += stringValue - fileData = open(filePath, "w", newline="\n") - fileData.write(stringData) - fileData.close() - else: - raise PluginError(filePath + " does not exist.") - - -def deleteIfFound(filePath, stringValue): - if os.path.exists(filePath): - fileData = open(filePath, "r") - fileData.seek(0) - stringData = fileData.read() - fileData.close() - if stringValue in stringData: - stringData = stringData.replace(stringValue, "") - fileData = open(filePath, "w", newline="\n") - fileData.write(stringData) - fileData.close() - - def yield_children(obj: bpy.types.Object): yield obj if obj.children: @@ -1711,12 +1677,11 @@ def getTextureSuffixFromFormat(texFmt): return texFmt.lower() +# https://stackoverflow.com/a/241506 COMMENT_PATTERN = re.compile(r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', re.DOTALL | re.MULTILINE) def removeComments(text: str): - # https://stackoverflow.com/a/241506 - def replacer(match: re.Match[str]): s = match.group(0) if s.startswith("/"):