diff --git a/README.md b/README.md index c2ebc83..525991d 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ abmatt [command_line][--interactive -f -b -d - | -i | --interactive | Interactive shell mode. | | -l | --loudness | Sets the verbosity level. (0-5) | -o | --overwrite | Overwrite existing files. | +| | --moonview | Treat the Brres as Moonview course, adjusting material names. | ### Command Line Examples This command would open *course_model.brres* in overwrite mode and run the commands stored in *my_commands.txt* @@ -89,7 +90,7 @@ line = begin_preset | command_line; begin_preset = '[' ']' EOL; command_line = cmd-prefix ['for' selection] EOL; -cmd-prefix = set | info | add | remove | select | preset | save | copy | paste | convert; +cmd-prefix = set | info | add | remove | select | preset | save | copy | paste | convert | load; set = 'set' type setting; info = 'info' type [key | 'keys']; add = 'add' type; @@ -100,6 +101,7 @@ save = 'save' [filename] ['as' destination] ['overwrite'] copy = 'copy' type; paste = 'paste' type; convert = 'convert' filename ['to' destination] ['--no-colors'] ['--no-normals'] ['--single-bone'] ['--no-uvs'] +load = 'load' command-file selection = name ['in' container] container = ['brres' filename] ['model' name]; @@ -241,10 +243,10 @@ tex0-format = 'cmpr' | 'c14x2' | 'c8' | 'c4' | 'rgba32' | 'rgb5a3' | 'rgb565' | 'ia8' | 'ia4' | 'i8' | 'i4'; ``` -### Presets +### Presets and Command Files Presets are a way of grouping commands together. They can be defined in `presets.txt` or in command files. Presets begin with `[]` and include all commands until another preset is encountered or end of file. -An empty preset `[]` can be used to stop preset parsing. +An empty preset `[]` can be used to stop preset parsing (and begin command parsing). ``` [my_preset] set material xlu:True @@ -254,5 +256,9 @@ set layer mapmode:linear_mipmap_linear To call the preset: `preset my_preset for my_material_name` +The `load` command can be used to load additional commands and presets. +As with all recursive things, be careful not to create an infinite loop! + ## Known Limitations and Bugs -* Windows installer sometimes hangs in the background until the process is terminated. \ No newline at end of file +* Windows installer sometimes hangs in the background until the process is terminated. +* Non-standard files in Brres are not supported. \ No newline at end of file diff --git a/abmatt/__main__.py b/abmatt/__main__.py index cd9d082..904c75e 100755 --- a/abmatt/__main__.py +++ b/abmatt/__main__.py @@ -21,7 +21,7 @@ def main(): base_path = os.path.abspath(__file__) files = load_config.parse_args(argv, base_path) # cleanup - for file in files.values(): + for file in files: file.close() diff --git a/abmatt/autofix.py b/abmatt/autofix.py index 07d560b..5071c5f 100644 --- a/abmatt/autofix.py +++ b/abmatt/autofix.py @@ -114,6 +114,7 @@ def __init__(self, fix_level=3, loudness=3): if self.__AUTO_FIXER: raise RuntimeError('Autofixer already initialized') self.loudness = loudness self.fix_level = fix_level + self.zero_level_func = None self.queue = [] self.is_running = False self.pipe = None # if set, output is sent to the pipe, must implement info warn and error. @@ -151,15 +152,22 @@ def get(fixe_level=3, loudness=3): def set_pipe(self, obj): self.pipe = obj - def set_fix_level(self, fix_level): - try: - self.fix_level = int(fix_level) - except ValueError: - fix_level = fix_level.upper() - if fix_level == 'ALL': - self.fix_level = 4 - else: - self.fix_level = self.ERROR_LEVELS.index(fix_level) + def set_fix_level(self, fix_level, zero_level_func=None): + if zero_level_func: + self.zero_level_func = zero_level_func + if type(fix_level) is int: + self.fix_level = fix_level + elif fix_level is not None: + try: + self.fix_level = int(fix_level) + except ValueError: + fix_level = fix_level.upper() + if fix_level == 'ALL': + self.fix_level = 4 + elif fix_level in self.ERROR_LEVELS: + self.fix_level = self.ERROR_LEVELS.index(fix_level) + if fix_level == 0 and self.zero_level_func: + self.zero_level_func() def can_prompt(self): return self.fix_level == self.FIX_PROMPT diff --git a/abmatt/brres/brres.py b/abmatt/brres/brres.py index 9781ccb..14cdd3c 100644 --- a/abmatt/brres/brres.py +++ b/abmatt/brres/brres.py @@ -3,6 +3,7 @@ # Brres Class # -------------------------------------------------------- import os +import string from abmatt.autofix import AutoFix, Bug from abmatt.brres.lib.binfile import BinFile @@ -10,18 +11,19 @@ from abmatt.brres.lib.node import Clipable, Packable from abmatt.brres.lib.packing.pack_brres import PackBrres from abmatt.brres.lib.unpacking.unpack_brres import UnpackBrres +from abmatt.brres.mdl0.material.material import Material from abmatt.brres.tex0 import Tex0 from abmatt.image_converter import ImgConverter class Brres(Clipable, Packable): - SETTINGS = ('name',) MAGIC = 'bres' OVERWRITE = False DESTINATION = None OPEN_FILES = [] # reference to active files REMOVE_UNUSED_TEXTURES = False + MOONVIEW = False # if true, treat brres as moonview def __init__(self, name, parent=None, read_file=True): """ @@ -37,12 +39,13 @@ def __init__(self, name, parent=None, read_file=True): self.models = [] self.texture_map = {} self.textures = [] - self.srt0 = [] - self.pat0 = [] + self.unused_srt0 = None + self.unused_pat0 = None self.chr0 = [] self.scn0 = [] self.shp0 = [] self.clr0 = [] + self.unknown = [] binfile = BinFile(name) if read_file else None super(Brres, self).__init__(name, parent, binfile) self.add_open_file(self) @@ -76,6 +79,20 @@ def get_brres(filename, create_if_not_exists=False): def begin(self): self.is_modified = True + def respect_model_names(self): + names = {x.name.rstrip(string.digits) for x in self.models} + return len(names) != len(self.models) + + def __hash__(self): + return hash(self.name) + + def __eq__(self, other): + return other is not None and type(other) == Brres and self.models == other.models \ + and self.texture_map == other.texture_map \ + and self.unused_srt0 == other.unused_srt0 and self.unused_pat0 == other.unused_pat0 \ + and self.chr0 == other.chr0 and self.scn0 == other.scn0 and self.shp0 == other.shp0 \ + and self.clr0 == other.clr0 and self.unknown == other.unknown + def get_str(self, key): if key == 'name': return self.name @@ -107,10 +124,6 @@ def remove_mdl0(self, name): for x in self.models: if x.name == name: self.models.remove(x) - if x.srt0_collection: - self.srt0.remove(x.srt0_collection) - if x.pat0_collection: - self.pat0.remove(x.pat0_collection) self.mark_modified() break @@ -180,7 +193,7 @@ def get_trace(self): def info(self, key=None, indentation_level=0): AutoFix.info('{}{}:\t{} model(s)\t{} texture(s)'.format(' ' * indentation_level + '>', - self.name, len(self.models), len(self.textures)), 1) + self.name, len(self.models), len(self.textures)), 1) indentation_level += 2 self.sub_info('MDL0', self.models, key, indentation_level) self.sub_info('TEX0', self.textures, key, indentation_level) @@ -223,7 +236,7 @@ def get_expected_brres_fname(filename): return os.path.join(w_dir, name + '.brres') def get_expected_mdl_name(self): - filename = self.name + filename = os.path.basename(self.name) for item in ('course', 'map', 'vrcorn'): if item in filename: return item @@ -264,7 +277,7 @@ def add_tex0(self, tex0, replace=True, mark_modified=True): tex0 = t self.textures.append(tex0) self.texture_map[tex0.name] = tex0 - tex0.parent = self # this may be redundant + tex0.parent = self # this may be redundant if mark_modified: self.mark_modified() return True @@ -353,6 +366,9 @@ def pack(self, binfile): def check(self): AutoFix.info('checking file {}'.format(self.name), 4) expected = self.get_expected_mdl_name() + if self.MOONVIEW or 'ridgehighway_course' in self.name: + self.check_moonview() + Brres.MOONVIEW = False for mdl in self.models: mdl.check(expected) expected = None @@ -369,6 +385,33 @@ def check(self): for tex in all_tex: tex.check() + def check_moonview(self): + if not self.models: + return True + mat_names = ['Goal_Merg', 'Iwa', 'Iwa_alfa', 'Nuki_Ryoumen', 'WallMerg00', + 'moon_kabe0000', 'moon_road00', 'road', 'road01', 'road02', 'road03', + 'siba00'] + materials = self.models[0].materials + # First check if there's any modification needed + j = 0 + for i in range(len(materials)): + if materials[i].name == mat_names[j]: + j += 1 + # Now rename + if j != len(mat_names): + b = Bug(3, 3, 'Incorrect material names for ridgehighway_course', 'Renaming materials') + for i in range(len(mat_names)): + if i < len(materials): + if materials[i].name != mat_names[i]: + material = self.models[0].get_material_by_name(mat_names[i]) + if material: + material.rename(Material.get_unique_material('material', self.models[0], get_name_only=True)) + materials[i].rename(mat_names[i]) + else: + self.models[0].add_material(Material.get_unique_material(mat_names[i], self.models[0])) + b.resolve() + return j != len(mat_names) + def remove_unused_textures(self, unused_textures): tex = self.textures tex_map = self.texture_map diff --git a/abmatt/brres/chr0.py b/abmatt/brres/chr0.py index 0516665..4967b61 100644 --- a/abmatt/brres/chr0.py +++ b/abmatt/brres/chr0.py @@ -42,6 +42,23 @@ def __init__(self, name, parent, binfile=None, framecount=1, loop=True): # self.offset = offset # since we don't parse data... store name offsetg super().__init__(name, parent, binfile) + def __eq__(self, other): + return super().__eq__(other) and self.framecount == other.framecount and self.loop == other.loop \ + and self.x_translation == other.x_translation and self.y_translation == other.y_translation \ + and self.z_translation == other.z_translation and self.x_rotation == other.x_rotation \ + and self.y_rotation == other.y_rotation and self.z_rotation == other.z_rotation \ + and self.x_scale == other.x_scale and self.y_scale == other.y_scale and self.z_scale == other.z_scale + + def __iter__(self): + return iter(self.animations) + + def __next__(self): + return next(self.animations) + + def __eq__(self, other): + return super().__eq__(other) and self.framecount == other.framecount and self.loop == other.loop \ + and self.scaling_rule == other.scaling_rule and self.animations == other.animations + def set_str(self, key, value): return set_anim_str(self, key, value) diff --git a/abmatt/brres/clr0/clr0.py b/abmatt/brres/clr0/clr0.py index 2c31eab..6c3f673 100644 --- a/abmatt/brres/clr0/clr0.py +++ b/abmatt/brres/clr0/clr0.py @@ -18,6 +18,9 @@ def __init__(self, name, parent, binfile=None): self.animations = [] super(Clr0, self).__init__(name, parent, binfile) + def __eq__(self, other): + return super().__eq__(other) and self.framecount == other.framecount and self.animations == other.animations + def begin(self, initial_values=None): self.loop = True self.framecount = 1 diff --git a/abmatt/brres/clr0/clr0_animation.py b/abmatt/brres/clr0/clr0_animation.py index cf6127b..21643fe 100644 --- a/abmatt/brres/clr0/clr0_animation.py +++ b/abmatt/brres/clr0/clr0_animation.py @@ -1,10 +1,19 @@ -class Clr0Animation: +from abmatt.brres.lib.node import Node - def __init__(self, name, framecount=1, loop=True): + +class Clr0Animation(Node): + + def __init__(self, name, parent): self.name = name - self.framecount = framecount - self.loop=loop + self.framecount = parent.framecount + self.loop = parent.loop self.flags = [False] * 16 self.is_constant = [False] * 16 self.entry_masks = [] - self.entries = [] # can be fixed (if constant) otherwise key frame list + self.entries = [] # can be fixed (if constant) otherwise key frame list + super().__init__(name, parent) + + def __eq__(self, other): + return super().__eq__(other) and self.framecount == other.framecount and self.loop == other.loop \ + and self.flags == other.flags and self.is_constant == other.is_constant \ + and self.entry_masks == other.entry_masks and self.entries == other.entries diff --git a/abmatt/brres/lib/binfile.py b/abmatt/brres/lib/binfile.py index 7bc947e..3b4750e 100644 --- a/abmatt/brres/lib/binfile.py +++ b/abmatt/brres/lib/binfile.py @@ -28,6 +28,7 @@ def __init__(self, filename, mode='r', bom='>'): """ self.beginOffset = self.offset = 0 self.filename = filename + self.names_offset = float('inf') self.stack = [] # used for tracking depth in files self.references = {} # used for forward references in relation to start self.bom = bom # byte order mark > | < @@ -388,6 +389,8 @@ def unpack_name(self, advance=True): if name: return name try: + if offset - 4 < self.names_offset: + self.names_offset = offset - 4 [name_lens] = self.readOffset("I", offset - 4) except struct.error: raise UnpackingError(self, 'Incorrect name offset') @@ -428,7 +431,7 @@ def packNames(self): # with open('names.txt', 'w') as f: # f.write(str(out)) #- end debug - + self.names_offset = self.offset for key in sorted(names): if key is not None and key != b'': self.align(4) @@ -594,8 +597,7 @@ def addEntry(self, name, dataPtr=0): def unpack(self, binfile): """ Unpacks folder """ # print('Folder {} offset {}'.format(self.name, binfile.offset)) - binfile.start() - self.offset = binfile.offset + self.offset = binfile.start() len, num_entries = binfile.read("2I", 8) binfile.advance(16) # skip first entry # first = FolderEntry(self, 0) @@ -609,8 +611,7 @@ def unpack(self, binfile): def pack(self, binfile): """ packs folder """ - binfile.start() - self.offset = binfile.offset + self.offset = binfile.start() entries = self.calcEntries() binfile.write("2I", self.byteSize(), len(entries) - 1) # -1 to ignore reference entry for x in entries: @@ -661,11 +662,11 @@ def createEntryRef(self, name): return self.createEntryRefI(i) raise PackingError(self.binfile, "Entry name {} not in folder {}".format(name, self.name)) - def createEntryRefI(self, index=0): - """ creates reference in folder to section at entry[index] (once only, pops)""" - entry = self.entries.pop(index) - # print('{} {}'.format(self.binfile.offset, entry.name)) - return self.binfile.createRefFrom(self.offset, index + 1) # index + 1 ignoring the first ref entry + def createEntryRefI(self, index=0, pop=True): + """ creates reference in folder to section at entry[index]""" + if pop: + entry = self.entries.pop(index) + return self.binfile.createRefFrom(self.offset, index + 1, pop=pop) # index + 1 ignoring the first ref entry def printCollectionHex(collection): diff --git a/abmatt/brres/lib/decoder.py b/abmatt/brres/lib/decoder.py index 6b69cea..a82d365 100644 --- a/abmatt/brres/lib/decoder.py +++ b/abmatt/brres/lib/decoder.py @@ -1,3 +1,4 @@ +from copy import deepcopy from struct import unpack_from, unpack import numpy as np @@ -9,10 +10,14 @@ from abmatt.converters.points import PointCollection -def decode_geometry_group(geometry): +def decode_geometry_group(geometry, n_columns, flip_points=False): arr = np.array(geometry.data, np.float) if geometry.divisor: arr = arr / (2 ** geometry.divisor) + if arr.shape[1] < n_columns: + arr = np.append(arr, np.zeros((arr.shape[0], 1), np.float), axis=1) + if flip_points: + arr[:, -1] = arr[:, -1] * -1 + 1 # convert st to xy return arr @@ -135,8 +140,7 @@ def decode_polygon(polygon, influences=None): face_point_indices, weights = decode_indices(polygon, polygon.encode_str) face_point_indices = np.array(face_point_indices, dtype=np.uint) face_point_indices[:, [0, 1]] = face_point_indices[:, [1, 0]] - # decoded_verts = - g_verts = PointCollection(decode_geometry_group(vertices), face_point_indices[:, :, vertex_index]) + g_verts = PointCollection(vertices.get_decoded(), face_point_indices[:, :, vertex_index]) linked_bone = polygon.get_linked_bone() if pos_matrix_index >= 0: # apply influences to vertices influence_collection = decode_pos_mtx_indices(influences, weights, g_verts, @@ -145,26 +149,18 @@ def decode_polygon(polygon, influences=None): influence = influences[linked_bone.weight_id] g_verts.apply_affine_matrix(np.array(linked_bone.get_transform_matrix()), apply=True) influence_collection = InfluenceCollection({0: influence}) - # for x in polygon.uv_mtx_indices: - # if x >= 0: - # AutoFix.warn('{} uv matrices data lost in export.'.format(polygon.name)) - # indices = face_point_indices[:, :, x] // 3 - # if (indices < 10).any(): - # print('Less than 10!') from abmatt.converters.geometry import Geometry geometry = Geometry(polygon.name, polygon.get_material().name, g_verts, triangles=None, influences=influence_collection, linked_bone=linked_bone) # create the point collections if normals: - geometry.normals = PointCollection(decode_geometry_group(normals), face_point_indices[:, :, normal_index]) + geometry.normals = PointCollection(normals.get_decoded(), face_point_indices[:, :, normal_index]) if colors: - geometry.colors = ColorCollection(ColorDecoder.decode_data(colors), face_point_indices[:, :, color_index]) + geometry.colors = ColorCollection(colors.get_decoded(), face_point_indices[:, :, color_index]) for tex in texcoords: - x = decode_geometry_group(tex) - pc = PointCollection(x, face_point_indices[:, :, texcoord_index], + pc = PointCollection(tex.get_decoded(), face_point_indices[:, :, texcoord_index], tex.minimum, tex.maximum) - pc.flip_points() geometry.texcoords.append(pc) texcoord_index += 1 return geometry @@ -276,27 +272,41 @@ def decode_pos_mtx_indices(all_influences, weight_groups, vertices, pos_mtx_indi slicer = sorted(weight_groups.keys()) slicer.append(len(vert_indices)) # add the max onto the end for slicing + remapper = {} + max_vert = np.max(vert_indices) + new_points = [] + new_face_indices = deepcopy(vert_indices) # Each weighting slice group for i in range(len(slicer) - 1): start = slicer[i] end = slicer[i + 1] weights = np.array(weight_groups[start]) - vertex_slice = vert_indices[start:end].flatten() - pos_mtx_slice = pos_mtx_indices[start:end].flatten() - # get the ordered indices corresponding to vertices - vertex_indices, indices = np.unique(vertex_slice, return_index=True) - weight_indices = weights[ - pos_mtx_slice[indices]] # get the matrix corresponding to vert index, resolve to weight_id + vertex_slice = vert_indices[start:end] + pos_mtx_slice = pos_mtx_indices[start:end] - # map each vertex id to an influence and apply it - for i in range(len(vertex_indices)): - vertex_index = vertex_indices[i] - if vertex_index not in influences: - influences[vertex_index] = influence = all_influences[weight_indices[i]] - points[vertex_index] = influence.apply_to(points[vertex_index], decode=True) - elif influences[vertex_index].influence_id != weight_indices[i]: - AutoFix.warn(f'vertex {vertex_index} has multiple different influences!') - influences[vertex_index] = all_influences[weight_indices[i]] - - # assert len(influences) == len(points) + # map each vertex to an influence + for i in range(len(vertex_slice)): + for j in range(3): + vert_id = vertex_slice[i, j] + inf = all_influences[weights[pos_mtx_slice[i, j]]] + weight_id = inf.influence_id + remappings = remapper.get(vert_id) + add_it = bool(remappings is None) + if not remappings: + remapper[vert_id] = remappings = [] + if not add_it: + add_it = True + for index, influence in remappings: + if influence.influence_id == weight_id: + add_it = False + new_face_indices[start + i, j] = index + break + if add_it: + remappings.append((len(new_points), inf)) + influences[len(new_points)] = inf + new_face_indices[start + i, j] = len(new_points) + new_points.append(inf.apply_to(points[vert_id], decode=True)) + vertices.points = np.array(new_points) + vertices.face_indices = new_face_indices + assert len(influences) == len(new_points) return InfluenceCollection(influences) diff --git a/abmatt/brres/lib/node.py b/abmatt/brres/lib/node.py index 71a34dd..18a721a 100644 --- a/abmatt/brres/lib/node.py +++ b/abmatt/brres/lib/node.py @@ -46,9 +46,18 @@ def begin(self): def __deepcopy__(self, memodict=None): raise NotImplementedError() + def __eq__(self, other): + return other is not None and type(self) == type(other) and self.name == other.name + def __str__(self): return self.name + def __hash__(self): + return hash(self.get_full_path()) + + def get_full_path(self): + return os.path.join(self.parent.get_full_path(), self.name) if self.parent else self.name + def link_parent(self, parent): self.parent = parent diff --git a/abmatt/brres/lib/packing/pack_brres.py b/abmatt/brres/lib/packing/pack_brres.py index 3f10d8b..53ab2bc 100644 --- a/abmatt/brres/lib/packing/pack_brres.py +++ b/abmatt/brres/lib/packing/pack_brres.py @@ -1,26 +1,57 @@ +import string + +from abmatt.brres.chr0 import Chr0 from abmatt.brres.lib.binfile import Folder from abmatt.brres.lib.packing.interface import Packer +from abmatt.brres.lib.packing.pack_unknown import UnknownPacker +from abmatt.brres.lib.unpacking.unpack_unknown import UnknownFolder +from abmatt.brres.pat0.pat0 import Pat0 +from abmatt.brres.srt0.srt0 import Srt0 class PackBrres(Packer): - @staticmethod - def get_anim_for_packing(anim_collection): - # srt animation processing - animations = [] - for x in anim_collection: - animations.extend(x.consolidate()) - return animations + + def add_or_create_anim(self, anim, anims, klass, name_map_count): + for x in anims: + if self.respect_naming: + if x.name == anim.get_anim_base_name() and x.add(anim): + return True + elif x.add(anim): + return True + count = name_map_count.get(anim.get_anim_base_name()) + name = anim.parent_base_name if not count else anim.parent_base_name + str(count) + k = klass(name, self.node, base_name=anim.parent_base_name) + name_map_count[anim.parent_base_name] = count + 1 if count else 1 + k.add(anim) + anims.append(k) + return False + + def get_anims(self, unused_anims, model_attr, anim_attr, klass): + name_map_count = {} + anims = [] + if unused_anims: + for x in unused_anims: + self.add_or_create_anim(x, anims, klass, name_map_count) + for model in self.node.models: + for item in getattr(model, model_attr): + anim = getattr(item, anim_attr) + if anim: + self.add_or_create_anim(anim, anims, klass, name_map_count) + return anims def pre_packing(self, brres): + self.respect_naming = brres.respect_model_names() + self.srt0 = self.get_anims(brres.unused_srt0, 'materials', 'srt0', Srt0) + self.pat0 = self.get_anims(brres.unused_pat0, 'materials', 'pat0', Pat0) ret = [] if brres.models: ret.append(('3DModels(NW4R)', brres.models)) if brres.textures: ret.append(('Textures(NW4R)', brres.textures)) - if brres.pat0: - ret.append(('AnmTexPat(NW4R)', self.get_anim_for_packing(brres.pat0))) - if brres.srt0: - ret.append(('AnmTexSrt(NW4R)', self.get_anim_for_packing(brres.srt0))) + if self.pat0: + ret.append(('AnmTexPat(NW4R)', self.pat0)) + if self.srt0: + ret.append(('AnmTexSrt(NW4R)', self.srt0)) if brres.chr0: ret.append(('AnmChr(NW4R)', brres.chr0)) if brres.scn0: @@ -31,17 +62,24 @@ def pre_packing(self, brres): ret.append(('AnmClr(NW4R)', brres.clr0)) return ret + def recursive_add_unknown(self, parent_dir, node): + parent_dir.addEntry(node.name) + if type(node) == UnknownFolder: + f = Folder(self.binfile, node.name) + node.folder = f + for x in node.subfiles: + self.recursive_add_unknown(f, x) + return f.byteSize() + return 0 + def generateRoot(self, subfiles): """ Generates the root folders - Does not hook up data pointers except the head group, + Does not hook up data pointers, returns (rootFolders, bytesize) """ rootFolders = [] # for storing Index Groups - byteSize = 0 # Create folder indexing folders - rootFolder = Folder(self.binfile, 'root') - rootFolders.append(rootFolder) - offsets = [] # for tracking offsets from first group to others + self.rootFolder = rootFolder = Folder(self.binfile, 'root') # Create folder for each section the brres has for i in range(len(subfiles)): folder_name, folder = subfiles[i] @@ -52,32 +90,37 @@ def generateRoot(self, subfiles): f.addEntry(folder[j].name) # might not have name? rootFolder.addEntry(f.name) rootFolders.append(f) - offsets.append(byteSize) - byteSize += f.byteSize() - # now update the dataptrs - rtsize = rootFolder.byteSize() - entries = rootFolder.entries - for i in range(len(offsets)): - entries[i].dataPtr = offsets[i] + rtsize + for uk in self.node.unknown: + self.recursive_add_unknown(rootFolder, uk) return rootFolders - @staticmethod - def packRoot(binfile, rt_folders): + def __pack_uk_folders_recurse(self, parent_folder, files, first=True): + if not first: + parent_folder.pack(self.binfile) + for f in files: + if type(f) == UnknownFolder: + parent_folder.createEntryRef(f.name) + self.__pack_uk_folders_recurse(f.folder, f.subfiles, False) + + def packRoot(self, binfile, rt_folders): """ Packs the root section, returns root folders that need data ptrs""" binfile.start() binfile.writeMagic("root") binfile.markLen() + self.rootFolder.pack(binfile) for f in rt_folders: + self.rootFolder.createEntryRefI() f.pack(binfile) + self.__pack_uk_folders_recurse(self.rootFolder, self.node.unknown) binfile.end() binfile.align() - return rt_folders[1:] + return rt_folders @staticmethod def getNumSections(folders): """ gets the number of sections, including root""" count = 1 # root - for x in folders[count:]: + for x in folders: if x: count += len(x) # print('Length of folder {} is {}'.format(x.name, len(x))) @@ -105,5 +148,16 @@ def pack(self, brres, binfile): index_group.createEntryRefI() # create the dataptr file.pack(binfile) folder_index += 1 + if brres.unknown: + self.__recursive_pack_uk(brres.unknown, self.rootFolder) binfile.packNames() binfile.end() + + def __recursive_pack_uk(self, unknown, parent_folder): + for i in range(len(unknown)): + x = unknown[i] + if type(x) != UnknownFolder: + parent_folder.createEntryRef(x.name) + UnknownPacker(x, self.binfile) + else: + self.__recursive_pack_uk(x.subfiles, x.folder) diff --git a/abmatt/brres/lib/packing/pack_mdl0/pack_polygon.py b/abmatt/brres/lib/packing/pack_mdl0/pack_polygon.py index 9014d32..8c98b9b 100644 --- a/abmatt/brres/lib/packing/pack_mdl0/pack_polygon.py +++ b/abmatt/brres/lib/packing/pack_mdl0/pack_polygon.py @@ -16,7 +16,7 @@ def pack(self, poly, binfile): binfile.writeOuterOffset() hi, lo = self.get_cp_vertex_format() xf_specs = self.get_xf_vertex_specs() - binfile.write('i3I', self.get_item_id(poly.linked_bone), lo, hi, xf_specs) + binfile.write('i3I', self.get_bone_weight_id(poly.linked_bone), lo, hi, xf_specs) binfile.write('3I', 0xe0, 0x80, 0) vt_dec_offset = binfile.offset - 4 data_len = len(poly.data) @@ -80,6 +80,11 @@ def get_cp_vertex_format(self): shifter += 2 return hi, lo + def get_bone_weight_id(self, bone): + if bone: + return bone.weight_id + return -1 + def get_item_id(self, point): if point: return point.index diff --git a/abmatt/brres/lib/packing/pack_pat0.py b/abmatt/brres/lib/packing/pack_pat0.py index 381b1c1..b19ab74 100644 --- a/abmatt/brres/lib/packing/pack_pat0.py +++ b/abmatt/brres/lib/packing/pack_pat0.py @@ -16,7 +16,7 @@ def pack(self, pat0, binfile): self.offset = binfile.start() binfile.storeNameRef(pat0.name) # self.fixedTexture = len(self.frames) <= 1 # todo, check fixed texture formatting/why? - flags = pat0.enabled | pat0.fixedTexture << 1 | pat0.hasTexture << 2 | pat0.hasPalette << 3 + flags = pat0.enabled | pat0.fixed_texture << 1 | pat0.has_texture << 2 | pat0.has_palette << 3 binfile.write('I', flags) binfile.mark() binfile.end() diff --git a/abmatt/brres/lib/packing/pack_scn0.py b/abmatt/brres/lib/packing/pack_scn0.py index 1562784..0ee3827 100644 --- a/abmatt/brres/lib/packing/pack_scn0.py +++ b/abmatt/brres/lib/packing/pack_scn0.py @@ -1,3 +1,4 @@ +from abmatt.autofix import AutoFix from abmatt.brres.lib.binfile import Folder from abmatt.brres.lib.packing.interface import Packer from abmatt.brres.lib.packing.pack_subfile import PackSubfile @@ -40,8 +41,10 @@ def pack(self, lightset, binfile): binfile.write('H', filler) binfile.write('B', len(lightset.light_names)) binfile.advance(1) + binfile.start() for x in lightset.light_names: binfile.storeNameRef(x) + binfile.end() binfile.advance((8 - len(lightset.light_names)) * 4) for i in range(8): binfile.write('H', filler) @@ -49,7 +52,7 @@ def pack(self, lightset, binfile): class PackFog(Packer): def pack(self, fog, binfile): - print('WARNING: packing scn0 fog is not supported.') + AutoFix.warn('packing scn0 fog is not supported.') pack_header(binfile, fog.name, fog.node_id, fog.real_id) binfile.write('4BI2f', fog.flags, 0, 0, 0, fog.type, fog.start, fog.end) binfile.write('4B', fog.color) diff --git a/abmatt/brres/lib/packing/pack_shp0.py b/abmatt/brres/lib/packing/pack_shp0.py index 2b054b9..9299ed9 100644 --- a/abmatt/brres/lib/packing/pack_shp0.py +++ b/abmatt/brres/lib/packing/pack_shp0.py @@ -20,10 +20,10 @@ def pack(self, anim, binfile): entries = anim.entries binfile.write('2Hi', anim.name_id, len(entries), anim.fixed_flags) binfile.mark() # indices offset + binfile.write('{}H'.format(len(entries)), *anim.indices) binfile.align(4) binfile.mark(len(entries)) binfile.createRef() # indices offset - binfile.write('{}H'.format(len(entries)), *anim.indices) for x in entries: self.PackFrames(x, binfile) binfile.end() diff --git a/abmatt/brres/lib/packing/pack_unknown.py b/abmatt/brres/lib/packing/pack_unknown.py new file mode 100644 index 0000000..217697f --- /dev/null +++ b/abmatt/brres/lib/packing/pack_unknown.py @@ -0,0 +1,7 @@ +from abmatt.brres.lib.binfile import Folder +from abmatt.brres.lib.packing.interface import Packer + + +class UnknownPacker(Packer): + def pack(self, node, binfile): + binfile.write('{}B'.format(len(node.data)), *node.data) diff --git a/abmatt/brres/lib/unpacking/unpack_brres.py b/abmatt/brres/lib/unpacking/unpack_brres.py index f859c38..039bf02 100644 --- a/abmatt/brres/lib/unpacking/unpack_brres.py +++ b/abmatt/brres/lib/unpacking/unpack_brres.py @@ -1,3 +1,4 @@ +import os import string from abmatt.autofix import AutoFix @@ -5,79 +6,76 @@ from abmatt.brres.clr0.clr0 import Clr0 from abmatt.brres.lib.binfile import Folder, UnpackingError from abmatt.brres.lib.unpacking.interface import Unpacker +from abmatt.brres.lib.unpacking.unpack_unknown import UnknownUnpacker from abmatt.brres.mdl0.mdl0 import Mdl0 -from abmatt.brres.pat0.pat0 import Pat0Collection, Pat0 +from abmatt.brres.pat0.pat0 import Pat0 from abmatt.brres.scn0.scn0 import Scn0 from abmatt.brres.shp0.shp0 import Shp0 -from abmatt.brres.srt0.srt0 import SRTCollection, Srt0 +from abmatt.brres.srt0.srt0 import Srt0 from abmatt.brres.tex0 import Tex0 class UnpackBrres(Unpacker): - @staticmethod - def create_model_animation_map(animations, model_names): - model_anim_map = {} # dictionary of model names to animations - if animations: - for x in animations: - name = x.name # first try just the name no change - if name not in model_names: - name = name.rstrip(string.digits) # strip digits - if not model_anim_map.get(name): - model_anim_map[name] = [x] - else: - model_anim_map[name].append(x) - return model_anim_map + def __init__(self, node, binfile): + self.chr0 = self.pat0 = self.srt0 = None + self.uk_offsets = [] + super().__init__(node, binfile) - def generate_srt_collections(self, srt0_anims): - # srt animation processing - brres = self.node - model_names = {x.name for x in brres.models} - model_anim_map = self.create_model_animation_map(srt0_anims, model_names) - # now create SRT Collection - anim_collections = [] - for key in model_anim_map: - collection = SRTCollection(key, brres, model_anim_map[key]) - anim_collections.append(collection) - mdl = brres.get_model(key) - if not mdl: - AutoFix.info('No model found matching srt0 animation {} in {}'.format(key, brres.name), 3) - else: - mdl.set_srt0(collection) - return anim_collections + def locate_anim_model(self, anim): + for x in self.node.models: + if anim.name == x.name: + return x - def generate_pat0_collections(self, pat0_anims): - brres = self.node - model_names = {x.name for x in brres.models} - model_anim_map = self.create_model_animation_map(pat0_anims, model_names) - # now create PAT0 Collection - anim_collections = [] - for key in model_anim_map: - collection = Pat0Collection(key, brres, model_anim_map[key]) - anim_collections.append(collection) - mdl = brres.get_model(key) - if not mdl: - AutoFix.info('No model found matching pat0 animation {} in {}'.format(key, brres.name), 3) - else: - mdl.set_pat0(collection) - return anim_collections + def post_unpack_anim(self, anims, model_attr, item_attr): + if anims is None: + return None + anim_map = {} + for anim in anims: + base_name = anim.name.rstrip(string.digits) if not self.locate_anim_model(anim) else anim.name + for x in anim: + if anim_map.get(x.name) is None: + anim_map[x.name] = [x] + else: + anim_map[x.name].append(x) + x.parent_base_name = base_name + unused = set(anim_map.keys()) + for model in self.node.models: + for item in getattr(model, model_attr): + anims = anim_map.get(item.name) + if anims: + if len(anims) > 1: + for x in anims: + if x.parent_base_name == model.name: + setattr(item, item_attr, x) + if item.name in unused: unused.remove(item.name) + break + else: + setattr(item, item_attr, anims[0]) + try: + unused.remove(item.name) + except KeyError: + pass + return [y for x in unused for y in anim_map[x]] def post_unpacking(self, brres): for x in brres.textures: brres.texture_map[x.name] = x - brres.pat0 = self.generate_pat0_collections(brres.pat0) if brres.pat0 else [] - brres.srt0 = self.generate_srt_collections(brres.srt0) if brres.srt0 else [] + brres.unused_pat0 = self.post_unpack_anim(self.pat0, 'materials', 'pat0') + brres.unused_srt0 = self.post_unpack_anim(self.srt0, 'materials', 'srt0') + if brres.unused_pat0 or brres.unused_srt0: + AutoFix.warn('Unused animations detected') def unpack(self, brres, binfile): """ Unpacks the brres """ - binfile.start() + self.offset = binfile.start() magic = binfile.readMagic() if magic != 'bres': raise UnpackingError(brres, '"{}" not a brres file'.format(brres.name)) bom = binfile.read("H", 2) binfile.bom = "<" if bom == 0xfffe else ">" binfile.advance(2) - binfile.readLen() + l = binfile.readLen() rootoffset, numSections = binfile.read("2h", 4) binfile.offset = rootoffset root = binfile.readMagic() @@ -85,34 +83,51 @@ def unpack(self, brres, binfile): section_length = binfile.read("I", 4) root = Folder(binfile, root) root.unpack(binfile) + self.section_offsets = [] # open all the folders for i in range(len(root)): self.unpack_folder(root.recallEntryI()) + self.section_offsets.append(binfile.names_offset) + brres.unknown = self.unpack_unknown() binfile.end() self.post_unpacking(brres) - def unpack_subfiles(self, klass): - subfolder = Folder(self.binfile, self.folder_name) - subfolder.unpack(self.binfile) - return [klass(subfolder.recallEntryI(), self.node, self.binfile) for i in range(len(subfolder))] + def unpack_unknown(self): + if self.uk_offsets: + AutoFix.warn('Unknown files {}, may be loosely supported'.format([x[0] for x in self.uk_offsets])) + uk = [] + for name, offset in self.uk_offsets: + self.binfile.offset = offset + uk.append(UnknownUnpacker(self.binfile, self.section_offsets, name).node) + return uk + + def unpack_subfiles(self, klass, binfile): + subfolder = Folder(binfile, self.folder_name) + subfolder.unpack(binfile) + r = [] + for i in range(len(subfolder)): + name = subfolder.recallEntryI() + self.section_offsets.append(binfile.offset) + r.append(klass(name, self.node, binfile)) + return r def unpack_folder(self, folder_name): self.folder_name = folder_name if folder_name == "3DModels(NW4R)": - self.node.models = self.unpack_subfiles(Mdl0) + self.node.models = self.unpack_subfiles(Mdl0, self.binfile) elif folder_name == "Textures(NW4R)": - self.node.textures = self.unpack_subfiles(Tex0) + self.node.textures = self.unpack_subfiles(Tex0, self.binfile) elif folder_name == "AnmTexPat(NW4R)": - self.node.pat0 = self.unpack_subfiles(Pat0) + self.pat0 = self.unpack_subfiles(Pat0, self.binfile) elif folder_name == "AnmTexSrt(NW4R)": - self.node.srt0 = self.unpack_subfiles(Srt0) + self.srt0 = self.unpack_subfiles(Srt0, self.binfile) elif folder_name == "AnmChr(NW4R)": - self.node.chr0 = self.unpack_subfiles(Chr0) + self.node.chr0 = self.unpack_subfiles(Chr0, self.binfile) elif folder_name == "AnmScn(NW4R)": - self.node.scn0 = self.unpack_subfiles(Scn0) + self.node.scn0 = self.unpack_subfiles(Scn0, self.binfile) elif folder_name == "AnmShp(NW4R)": - self.node.shp0 = self.unpack_subfiles(Shp0) + self.node.shp0 = self.unpack_subfiles(Shp0, self.binfile) elif folder_name == "AnmClr(NW4R)": - self.node.clr0 = self.unpack_subfiles(Clr0) + self.node.clr0 = self.unpack_subfiles(Clr0, self.binfile) else: - raise UnpackingError(self.binfile, 'Unkown folder {}'.format(folder_name)) \ No newline at end of file + self.uk_offsets.append((folder_name, self.binfile.offset)) diff --git a/abmatt/brres/lib/unpacking/unpack_clr0.py b/abmatt/brres/lib/unpacking/unpack_clr0.py index 0136b4a..ff645d1 100644 --- a/abmatt/brres/lib/unpacking/unpack_clr0.py +++ b/abmatt/brres/lib/unpacking/unpack_clr0.py @@ -48,7 +48,7 @@ def unpack(self, clr0, binfile): folder = Folder(binfile) folder.unpack(binfile) while len(folder): - anim = Clr0Animation(folder.recallEntryI(), clr0.framecount, clr0.loop) + anim = Clr0Animation(folder.recallEntryI(), clr0) self.UnpackSub(anim, binfile) clr0.animations.append(anim) binfile.end() diff --git a/abmatt/brres/lib/unpacking/unpack_mdl0/unpack_polygon.py b/abmatt/brres/lib/unpacking/unpack_mdl0/unpack_polygon.py index c8e7740..287e5ca 100644 --- a/abmatt/brres/lib/unpacking/unpack_mdl0/unpack_polygon.py +++ b/abmatt/brres/lib/unpacking/unpack_mdl0/unpack_polygon.py @@ -87,6 +87,9 @@ def post_unpack(self): poly.encode_str = self.encode_string if self.bone_id >= 0: poly.linked_bone = mdl0.bones[mdl0.bone_table[self.bone_id]] + elif poly.weight_index < 0: + poly.linked_bone = mdl0.bones[0] + AutoFix.warn('{} has incorrect bone reference, using {}'.format(poly.name, poly.linked_bone)) else: poly.linked_bone = None diff --git a/abmatt/brres/lib/unpacking/unpack_pat0.py b/abmatt/brres/lib/unpacking/unpack_pat0.py index 2a284ef..f993d95 100644 --- a/abmatt/brres/lib/unpacking/unpack_pat0.py +++ b/abmatt/brres/lib/unpacking/unpack_pat0.py @@ -30,11 +30,11 @@ def unpack_flags(self, pat0, binfile): binfile.advance(4) # already have name [flags] = binfile.read('I', 4) pat0.enabled = flags & 1 - pat0.fixedTexture = flags >> 1 & 1 + pat0.fixed_texture = flags >> 1 & 1 # if self.fixedTexture: # print('{} Fixed texture!'.format(self.name)) pat0.has_texture = flags >> 2 & 1 - pat0.hasPalette = flags >> 3 & 1 + pat0.has_palette = flags >> 3 & 1 def unpack(self, pat0, binfile): binfile.start() @@ -59,7 +59,7 @@ def unpack(self, pat0, binfile): unpacked = [] while len(folder): name = folder.recallEntryI() - anim = Pat0MatAnimation(name, pat0.parent, pat0.framecount, pat0.loop) + anim = Pat0MatAnimation(name, pat0, pat0.framecount, pat0.loop) unpacked.append(UnpackPat0Animation(anim, binfile)) pat0.mat_anims.append(anim) binfile.recall() # section 1 diff --git a/abmatt/brres/lib/unpacking/unpack_scn0.py b/abmatt/brres/lib/unpacking/unpack_scn0.py index 04f2709..bb2fbf4 100644 --- a/abmatt/brres/lib/unpacking/unpack_scn0.py +++ b/abmatt/brres/lib/unpacking/unpack_scn0.py @@ -69,7 +69,7 @@ def unpack(self, camera, binfile): def unpack_group(self, unpacker, klass, section_count): if self.binfile.recall(): - return [unpacker(klass(), self.binfile).node for i in range(section_count)] + return [unpacker(klass('', self.node), self.binfile).node for i in range(section_count)] return [] def unpack(self, scn0, binfile): @@ -77,8 +77,6 @@ def unpack(self, scn0, binfile): _, scn0.framecount, scn0.speclightcount, scn0.loop = binfile.read('i2Hi', 12) section_counts = binfile.read('5H', 12) # + pad binfile.recall() # section root - # folder = Folder(binfile).unpack(binfile) - # groups = [scn0.lightsets, scn0.ambient_lights, scn0.lights, scn0.fogs, scn0.cameras] scn0.lightsets = self.unpack_group(self.UnpackLightSet, light.LightSet, section_counts[0]) scn0.ambient_lights = self.unpack_group(self.UnpackAmbientLight, light.AmbientLight, section_counts[1]) scn0.lights = self.unpack_group(self.UnpackLight, light.Light, section_counts[2]) diff --git a/abmatt/brres/lib/unpacking/unpack_shp0.py b/abmatt/brres/lib/unpacking/unpack_shp0.py index bf0f0a7..8f26ad0 100644 --- a/abmatt/brres/lib/unpacking/unpack_shp0.py +++ b/abmatt/brres/lib/unpacking/unpack_shp0.py @@ -46,7 +46,7 @@ def unpack(self, shp0, binfile): folder = Folder(binfile) folder.unpack(binfile) while len(folder): - anim = Shp0Animation(folder.recallEntryI()) + anim = Shp0Animation(folder.recallEntryI(), shp0) self.UnpackSub(anim, binfile) shp0.animations.append(anim) binfile.end() diff --git a/abmatt/brres/lib/unpacking/unpack_srt0.py b/abmatt/brres/lib/unpacking/unpack_srt0.py index ff69a08..1ad62a5 100644 --- a/abmatt/brres/lib/unpacking/unpack_srt0.py +++ b/abmatt/brres/lib/unpacking/unpack_srt0.py @@ -128,7 +128,7 @@ def unpack(self, srt0, binfile): e = folder.openI() if not e: break - mat = SRTMatAnim(e, srt0.framecount) + mat = SRTMatAnim(e, srt0.framecount, parent=srt0) UnpackSrt0Material(mat, binfile) srt0.matAnimations.append(mat) # binfile.recall() # section 1 (unknown) diff --git a/abmatt/brres/lib/unpacking/unpack_unknown.py b/abmatt/brres/lib/unpacking/unpack_unknown.py new file mode 100644 index 0000000..a2380b4 --- /dev/null +++ b/abmatt/brres/lib/unpacking/unpack_unknown.py @@ -0,0 +1,75 @@ +import struct + +from abmatt.brres.lib.binfile import Folder, UnpackingError +from abmatt.brres.lib.unpacking.interface import Unpacker + + +class UnknownFile: + def __init__(self, name, data): + self.name = name + self.data = data + + def __eq__(self, other): + return other is not None and type(other) == UnknownFile and self.name == other.name and self.data == other.data + + +class UnknownFolder: + def __init__(self, name, subfiles): + self.name = name + self.subfiles = subfiles + + def __eq__(self, other): + return other is not None and type(other) == UnknownFolder and self.name == other.name \ + and self.subfiles == other.subfiles + +class UnknownUnpacker(Unpacker): + def __init__(self, binfile, boundary_offsets, node=None): + self.is_folder = self.check_for_folder(binfile) + self.boundary_offsets = boundary_offsets + self.end_offset = self.get_next_offset(binfile, boundary_offsets) + self.length = self.end_offset - binfile.offset + super().__init__(node, binfile) + + def check_for_folder(self, binfile): + """Detect if its a folder by reading the first entry""" + try: + id, u, left, right, name, dataptr = binfile.readOffset('4H2I', binfile.offset + 8) + return id == 0xffff and u == 0 and name == 0 and dataptr == 0 + except struct.error: + return False + + def get_next_offset(self, binfile, boundary_offsets): + current_offset = binfile.offset + next_offset = float('inf') + for x in boundary_offsets: + if current_offset < x < next_offset: + next_offset = x + if next_offset == float('inf'): + raise UnpackingError(binfile, 'Failed to detect end offset after {}'.format(current_offset)) + if not self.is_folder: + for i in range(current_offset, len(binfile.file)): + if binfile.file[i] == 0: + i += 1 + if next_offset < i: + return next_offset + return i + return next_offset + + def unpack(self, node, binfile): + """Returns tuple(name, data, is_folder)""" + if self.is_folder: + folder = Folder(binfile, node) + folder.unpack(binfile) + entries = [] + for i in range(len(folder)): + entries.append((folder.recallEntryI(), binfile.offset)) + self.boundary_offsets.append(binfile.offset) + entries = sorted(entries, key=lambda x: x[1]) + nodes = [] + for i in range(len(entries)): + x = entries[i] + binfile.offset = x[1] + nodes.append(UnknownUnpacker(binfile, self.boundary_offsets, x[0]).node) + self.node = UnknownFolder(node, nodes) + else: + self.node = UnknownFile(node, binfile.read('{}B'.format(self.length), 0)) diff --git a/abmatt/brres/mdl0/bone.py b/abmatt/brres/mdl0/bone.py index 62952f9..d87c5b3 100644 --- a/abmatt/brres/mdl0/bone.py +++ b/abmatt/brres/mdl0/bone.py @@ -40,7 +40,7 @@ def begin(self): self.inverse_matrix = [[y for y in x] for x in self.identity_matrix] def __eq__(self, other): - return other is not None and type(other) == Bone and np.allclose(self.scale, other.scale) and \ + result = super().__eq__(other) and np.allclose(self.scale, other.scale) and \ np.allclose(self.rotation, other.rotation) and np.allclose(self.translation, other.translation) and \ np.allclose(self.transform_matrix, other.transform_matrix) and self.no_transform == other.no_transform \ and self.fixed_translation == other.fixed_translation and self.fixed_scale == other.fixed_scale \ @@ -49,6 +49,7 @@ def __eq__(self, other): and self.seg_scale_comp_parent == other.seg_scale_comp_parent \ and self.classic_scale_off == other.classic_scale_off and self.visible == other.visible \ and self.has_geometry == other.has_geometry and self.has_billboard_parent == other.has_billboard_parent + return result def get_bone_parent(self): return self.b_parent diff --git a/abmatt/brres/mdl0/color.py b/abmatt/brres/mdl0/color.py index c9cb688..dc2b9c7 100644 --- a/abmatt/brres/mdl0/color.py +++ b/abmatt/brres/mdl0/color.py @@ -56,6 +56,11 @@ def __next__(self): def __getitem__(self, item): return self.get_decoded()[item] + def __eq__(self, other): + return super().__eq__(other) and self.flags == other.flags and self.stride == other.stride and \ + self.count == other.count and self.has_alpha == other.has_alpha and self.format == other.format and \ + self.data == other.data + def check(self): if not FMT_RGB565 <= self.format <= FMT_RGBA8: AutoFix.error(f'Color {self.name} has unknown color format.') diff --git a/abmatt/brres/mdl0/definition.py b/abmatt/brres/mdl0/definition.py index 6d7110e..7299831 100644 --- a/abmatt/brres/mdl0/definition.py +++ b/abmatt/brres/mdl0/definition.py @@ -21,6 +21,10 @@ def __init__(self, name, parent, binfile=None): self.fixed_weights = [] super().__init__(name, parent, binfile) + def __eq__(self, other): + return super().__eq__(other) and self.fixed_weights == other.fixed_weights and \ + self.mixed_weights == other.mixed_weights + def __bool__(self): return bool(self.mixed_weights) @@ -41,25 +45,6 @@ def add_mixed_weight(self, weight_id, weights): def add_fixed_weight(self, weight_id, bone_id): self.fixed_weights.append(self.FixedWeight(weight_id, bone_id)) - # def create_or_find_influence(self, influence): - # if len(influence) > 1: - # for x in self.mixed_weights: - # if x.inf_eq(influence): - # return x.weight_id - # # wasn't found! Let's create it - # weight = self.MixedWeight(len(self.mixed_weights) + len(self.fixed_weights)) - # for x in influence: - # weight.add_weight(*x) - # return weight.weight_id - # else: - # # search in fixed weights - # for x in self.fixed_weights: - # if x.bone_id == influence[0]: - # return x.weight_id - # # wasn't found! Let's create it - # weight = self.FixedWeight(len(self.mixed_weights) + len(self.fixed_weights), influence[0]) - # return weight.weight_id - class MixedWeight: def __init__(self, weight_id=None, binfile=None): if binfile: @@ -68,6 +53,9 @@ def __init__(self, weight_id=None, binfile=None): self.weight_id = weight_id self.weights = [] + def __eq__(self, other): + return self.weight_id == other.weight_id and self.weights == other.weights + def __iter__(self): return iter(self.weights) @@ -76,17 +64,6 @@ def __next__(self): def __str__(self): return str(self.weight_id) + ':' + str(self.weights) - # def inf_eq(self, influence): - # weights = self.weights - # if len(weights) != len(influence): - # return False - # for i in range(len(weights)): - # if weights[i] != influence[i]: - # return False - # return True - # - # def to_inf(self): - # return [x for x in self.weights] def add_weight(self, x): self.weights.append(x) @@ -108,6 +85,10 @@ def __init__(self, weight_id, bone_id): def __str__(self): return str(self.weight_id) + ':' + str(self.bone_id) + def __eq__(self, other): + return other is not None and type(self) == type(other) and \ + self.weight_id == other.weight_id and self.bone_id == other.bone_id + def to_inf(self): return [(self.bone_id, 1.0)] @@ -140,6 +121,9 @@ def __init__(self, name, parent, binfile=None): def add_entry(self, bone_index, parent_index): self.nodes.append((bone_index, parent_index)) + def __eq__(self, other): + return super().__eq__(other) and self.nodes == other.nodes + def __str__(self): return str(self.nodes) @@ -197,6 +181,9 @@ def __len__(self): def __bool__(self): return len(self.list) > 0 + def __eq__(self, other): + return super().__eq__(other) and self.list == other.list and self.is_xlu == other.is_xlu + def pop(self, draw_entry): li = self.list for i in range(len(li)): diff --git a/abmatt/brres/mdl0/material/layer.py b/abmatt/brres/mdl0/material/layer.py index 5ad2e2b..6475372 100644 --- a/abmatt/brres/mdl0/material/layer.py +++ b/abmatt/brres/mdl0/material/layer.py @@ -259,8 +259,8 @@ def set_camera_ref_str(self, value): def set_light_ref_str(self, value): i = int(value) - if i != -1: - raise ValueError("Expected -1 for light reference") + if i > 0: + AutoFix.warn('{} set unusual light ref {}, expected -1'.format(self.name, value)) if self.scn0_light_ref != i: self.scn0_light_ref = i self.mark_modified() @@ -295,7 +295,7 @@ def set_magfilter_str(self, value): if i > 1: raise ValueError("MagFilter out of range (0-1)") elif i >= 0: - self.minfilter = i + self.magfilter = i self.mark_modified() def set_lod_bias_str(self, value): diff --git a/abmatt/brres/mdl0/material/material.py b/abmatt/brres/mdl0/material/material.py index 0c199ed..39147ab 100644 --- a/abmatt/brres/mdl0/material/material.py +++ b/abmatt/brres/mdl0/material/material.py @@ -5,6 +5,7 @@ # --------------------------------------------------------------------- import json import re +import string from copy import deepcopy from abmatt.autofix import AutoFix @@ -74,9 +75,9 @@ class Material(Clipable): # CONSTANTS # ----------------------------------------------------------------------- EXT = 'matl' - SETTINGS = ("xlu", "ref0", "ref1", + SETTINGS = ("xlu", "ref0", "ref1", "logic", "comp0", "comp1", "comparebeforetexture", "blend", - "blendsrc", "blendlogic", "blenddest", "constantalpha", + "blendsrc", "enableblendlogic", "blendlogic", "blenddest", "constantalpha", "cullmode", "shadercolor", "lightchannel", "lightset", "fogset", "matrixmode", "enabledepthtest", "enabledepthupdate", "depthfunction", "drawpriority", @@ -150,12 +151,12 @@ def is_used(self): return len(self.polygons) > 0 @staticmethod - def get_unique_material(name, mdl): + def get_unique_material(name, mdl, get_name_only=False): is_digit = False while True: mat = mdl.get_material_by_name(name) if not mat: - return Material(name, mdl) + return Material(name, mdl) if not get_name_only else name if not is_digit and not name[-1].isdigit(): name = name + '1' is_digit = True @@ -238,7 +239,7 @@ def get_comp0(self): def get_comp1(self): return self.COMP_STRINGS[self.comp1] - def getLogic(self): + def get_logic(self): return self.LOGIC_STRINGS[self.logic] def get_compare_before_texture(self): @@ -253,6 +254,9 @@ def get_blend_src(self): def get_blend_logic(self): return self.BLLOGIC_STRINGS[self.blend_logic] + def get_blend_logic_enabled(self): + return self.blend_logic_enabled + def get_blend_dest(self): return self.BLFACTOR_STRINGS[self.blend_dest] @@ -353,8 +357,9 @@ def get_layer_count(self): return len(self.layers) # Get Functions - GET_SETTING = (get_xlu, get_ref0, get_ref1, get_comp0, get_comp1, get_compare_before_texture, - get_blend, get_blend_src, get_blend_logic, get_blend_dest, get_constant_alpha, get_cull_mode, + GET_SETTING = (get_xlu, get_ref0, get_ref1, get_logic, get_comp0, get_comp1, get_compare_before_texture, + get_blend, get_blend_src, get_blend_logic_enabled, get_blend_logic, get_blend_dest, + get_constant_alpha, get_cull_mode, get_shader_color, get_light_channel, get_lightset, get_fogset, get_matrix_mode, get_enable_depth_test, get_enable_depth_update, get_depth_function, get_draw_priority, get_ind_matrix_str, get_name, get_layer_count) @@ -462,7 +467,7 @@ def set_light_channel_str(self, lcStr): def set_lightset_str(self, str): val = int(str) if val > 0: - AutoFix.error("Invalid lightset " + str + ", expected -1") + AutoFix.warn("Unusual lightset " + str + ", expected -1") if self.lightset != val: self.lightset = val self.mark_modified() @@ -599,6 +604,9 @@ def set_blend_logic_str(self, str): self.blend_logic = i self.mark_modified() + def set_enable_blend_logic_str(self, s): + self.enable_blend_logic(validBool(s)) + def enable_blend_logic(self, enabled): if self.blend_logic_enabled != enabled: self.blend_logic_enabled = enabled @@ -733,9 +741,10 @@ def set_layer_count(self, val): self.mark_modified() # Set functions - SET_SETTING = (set_xlu_str, set_ref0_str, set_ref1_str, + SET_SETTING = (set_xlu_str, set_ref0_str, set_ref1_str, set_logic, set_comp0_str, set_comp1_str, set_compare_before_tex_str, set_blend_str, set_blend_src_str, - set_blend_logic_str, set_blend_dest_str, set_constant_alpha_str, set_cull_mode_str, + set_enable_blend_logic_str, set_blend_logic_str, set_blend_dest_str, set_constant_alpha_str, + set_cull_mode_str, set_shader_color_str, set_light_channel_str, set_lightset_str, set_fogset_str, set_matrix_mode_str, set_enable_depth_test_str, set_enable_depth_update_str, set_depth_function_str, set_draw_priority_str, @@ -798,8 +807,6 @@ def add_srt0(self): if self.srt0: return self.srt0 anim = SRTMatAnim(self.name) - if self.parent is not None: - self.parent.add_srt0(anim) self.set_srt0(anim) anim.add_layer() self.mark_modified() @@ -807,7 +814,7 @@ def add_srt0(self): def remove_srt0(self): if self.srt0: - self.parent.remove_srt0(self.srt0) + self.srt0.remove_material(self) self.srt0 = None self.mark_modified() @@ -833,20 +840,21 @@ def has_srt0(self): def has_pat0(self): return self.pat0 is not None + def get_anim_base_name(self): + if self.parent: + return self.parent.get_anim_base_name() + def add_pat0(self): """Adds a new pat0""" if self.pat0: return self.pat0 - anim = Pat0MatAnimation(self.name, self.parent) - if self.parent is not None: - self.parent.add_pat0(anim) + anim = Pat0MatAnimation(self.name, self) self.set_pat0(anim) self.mark_modified() return anim def remove_pat0(self): if self.pat0: - self.parent.remove_pat0(self.pat0) self.pat0 = None self.mark_modified() @@ -860,6 +868,12 @@ def set_pat0(self, anim): def get_pat0(self): return self.pat0 + def get_used_textures(self): + used = {x.name for x in self.layers} + if self.pat0: + used |= self.pat0.get_used_textures() + return used + def disable_blend(self): if self.is_blend_enabled(): self.blend_enabled = False @@ -1032,6 +1046,9 @@ def get_colors_used(self): ret.add('vertex') return ret + def __hash__(self): + return super().__hash__() + def __eq__(self, item): return self is item or (item is not None and type(self) == type(item) and \ self.name == item.name and self.xlu == item.xlu and \ @@ -1062,7 +1079,7 @@ def __eq__(self, item): it_eq(self.constant_colors, item.constant_colors) and \ it_eq(self.ras1_ss, item.ras1_ss) and \ self.indirect_matrices == item.indirect_matrices and \ - self.lightChannels == item.lightChannels and \ + self.lightChannels[0] == item.lightChannels[0] and \ self.srt0 == item.srt0 and \ self.pat0 == item.pat0 and \ self.shader == item.shader and \ diff --git a/abmatt/brres/mdl0/mdl0.py b/abmatt/brres/mdl0/mdl0.py index 9e761f1..7ba3b3a 100644 --- a/abmatt/brres/mdl0/mdl0.py +++ b/abmatt/brres/mdl0/mdl0.py @@ -1,17 +1,16 @@ """ MDL0 Models """ # ----------------- Model sub files -------------------------------------------- import math +import string from abmatt.autofix import AutoFix, Bug -from abmatt.brres.lib.matching import fuzzy_match, MATCHING +from abmatt.brres.lib.matching import fuzzy_match, MATCHING, it_eq from abmatt.brres.lib.node import Node, get_name_mapping from abmatt.brres.lib.packing.pack_mdl0.pack_mdl0 import PackMdl0 from abmatt.brres.lib.unpacking.unpack_mdl0.unpack_mdl0 import UnpackMdl0 from abmatt.brres.mdl0.bone import Bone from abmatt.brres.mdl0.definition import get_definition from abmatt.brres.mdl0.material.material import Material -from abmatt.brres.pat0.pat0 import Pat0Collection -from abmatt.brres.srt0.srt0 import SRTCollection from abmatt.brres.subfile import SubFile @@ -71,8 +70,6 @@ class Mdl0(SubFile): def __init__(self, name, parent, binfile=None): """ initialize model """ - self.srt0_collection = None - self.pat0_collection = None self.DrawOpa = self.DrawXlu = self.NodeTree = self.NodeMix = None self.weights_by_id = None self.rebuild_head = False @@ -92,7 +89,6 @@ def __init__(self, name, parent, binfile=None): # self.furVectors = [] # self.furLayers = [] self.materials = [] - # self.shaders = ShaderList() self.objects = [] self.influences = None # self.paletteLinks = [] @@ -101,6 +97,17 @@ def __init__(self, name, parent, binfile=None): self.is_map_model = True if 'map' in name else False super(Mdl0, self).__init__(name, parent, binfile) + def __hash__(self): + return super().__hash__() + + def __eq__(self, other): + return super().__eq__(other) and self.objects == other.objects and self.DrawOpa == other.DrawOpa \ + and self.DrawXlu == other.DrawXlu and self.NodeTree == other.NodeTree and self.NodeMix == other.NodeMix \ + and it_eq(self.minimum, other.minimum) and it_eq(self.maximum, other.maximum) \ + and self.facepoint_count == other.facepoint_count and self.face_count == other.face_count \ + and self.scaling_rule == other.scaling_rule and self.texture_matrix_mode == other.texture_matrix_mode \ + and self.bone_table == other.bone_table + def get_influences(self): return self.influences @@ -251,11 +258,6 @@ def add_material(self, material): if self.get_material_by_name(material.name): raise RuntimeError(f'Material with name {material.name} is already in model!') self.materials.append(material) - # animations - if material.srt0: - self.add_srt0(material.srt0) - if material.pat0: - self.add_pat0(material.pat0) self.mark_modified() return material @@ -264,11 +266,6 @@ def remove_material(self, material): if polys: raise RuntimeError('Unable to remove linked material, used by {}'.format(polys)) self.materials.remove(material) - # take care of animations - if material.srt0: - self.srt0_collection.remove(material.srt0) - if material.pat0: - self.pat0_collection.remove(material.pat0) self.mark_modified() def get_polys_using_material(self, material): @@ -299,74 +296,6 @@ def add_definition(self, material, polygon, visible_bone=None, priority=0): polygon.draw_priority = priority self.rebuild_head = True - # ---------------------------------- SRT0 ------------------------------------------ - def set_srt0(self, srt0_collection): - self.srt0_collection = srt0_collection - not_found = [] - for x in srt0_collection: - mat = self.get_material_by_name(x.name) - if not mat: - not_found.append(x) - else: - mat.set_srt0(x) - for x in not_found: - mat = fuzzy_match(x.name, self.materials) - desc = 'No material matching SRT0 {}'.format(x.name) - b = Bug(1, 1, desc, 'Rename material') - if self.RENAME_UNKNOWN_REFS and mat and not mat.srt0: - if mat.set_srt0(x): - x.rename(mat.name) - b.resolve() - self.mark_modified() - else: - b.fix_des = 'Remove SRT0' - if self.REMOVE_UNKNOWN_REFS: - srt0_collection.remove(x) - b.resolve() - - def add_srt0(self, anim): - if not self.srt0_collection: - self.srt0_collection = self.parent.add_srt_collection(SRTCollection(self.name, self.parent)) - self.srt0_collection.add(anim) - return anim - - def remove_srt0(self, animation): - return self.srt0_collection.remove(animation) - - # ------------------ Pat0 -------------------------------------- - def set_pat0(self, pat0_collection): - self.pat0_collection = pat0_collection - not_found = [] - for x in pat0_collection: - mat = self.get_material_by_name(x.name) - if not mat: - not_found.append(x) - else: - mat.set_pat0(x) - for x in not_found: - desc = 'No material matching PAT0 {}'.format(x.name) - mat = fuzzy_match(x.name, self.materials) - b = Bug(1, 1, desc, None) - if self.RENAME_UNKNOWN_REFS and mat and not mat.pat0: - b.fix_des = 'Rename to {}'.format(mat.name) - if mat.set_pat0(x): - x.rename(mat.name) - b.resolve() - else: - if self.REMOVE_UNKNOWN_REFS: - b.fix_des = 'remove pat0' - pat0_collection.remove(x) - b.resolve() - - def add_pat0(self, anim): - if not self.pat0_collection: - self.pat0_collection = self.parent.add_pat0_collection(Pat0Collection(self.name, self.parent)) - self.pat0_collection.add(anim) - return anim - - def remove_pat0(self, animation): - return self.pat0_collection.remove(animation) - # ------------------ Name -------------------------------------- def rename(self, name): for x in self.parent.models: @@ -437,7 +366,7 @@ def paste(self, item): self.paste_group(self.materials, item.materials) def __deepcopy__(self, memodict={}): - raise NotImplementedError() # hasn't been tested and it seems dangerous + raise NotImplementedError() # hasn't been tested and it seems dangerous copy = super().__deepcopy__(memodict) sections = [copy.definitions, copy.bones, copy.vertices, copy.normals, copy.colors, copy.uvs, @@ -451,26 +380,14 @@ def __deepcopy__(self, memodict={}): def link_parent(self, parent): super().link_parent(parent) - brres_textures = self.getTextureMap() - if self.pat0_collection: - for x in self.pat0_collection: - x.brres_textures = brres_textures for x in self.materials: x.on_brres_link(parent) def on_material_rename(self, material, new_name): if material.srt0: material.srt0.rename(new_name) - elif self.srt0_collection: - anim = self.srt0_collection[new_name] - if anim: - material.set_srt0(anim) if material.pat0: material.pat0.rename(new_name) - elif self.pat0_collection: - anim = self.pat0_collection[new_name] - if anim: - material.set_pat0(anim) return new_name def getTextureMap(self): @@ -566,10 +483,7 @@ def add_map_bones(self): def get_used_textures(self): textures = set() for x in self.materials: - for y in x.layers: - textures.add(y.name) - if self.pat0_collection is not None: - textures |= self.pat0_collection.get_used_textures() + textures |= x.get_used_textures() return textures # ---------------START PACKING STUFF ------------------------------------- diff --git a/abmatt/brres/mdl0/normal.py b/abmatt/brres/mdl0/normal.py index 0230eb6..090929b 100644 --- a/abmatt/brres/mdl0/normal.py +++ b/abmatt/brres/mdl0/normal.py @@ -16,6 +16,10 @@ def point_width(self): return 9 return 3 + @property + def default_point_width(self): + return 3 + @property def default_comp_count(self): return TYPE_NORMAL diff --git a/abmatt/brres/mdl0/point.py b/abmatt/brres/mdl0/point.py index 72d4eff..5857220 100644 --- a/abmatt/brres/mdl0/point.py +++ b/abmatt/brres/mdl0/point.py @@ -13,6 +13,8 @@ class Point(Node): + flip_on_decode = False + def __init__(self, name, parent, binfile=None): self.decoded = None super().__init__(name, parent, binfile) @@ -30,6 +32,10 @@ def paste(self, item): def default_comp_count(self): raise NotImplementedError() + @property + def default_point_width(self): + raise NotImplementedError() + @staticmethod def comp_count_from_width(width): """Gets comp count corresponding to width""" @@ -57,7 +63,7 @@ def begin(self): def get_decoded(self): if self.decoded is None: - self.decoded = decode_geometry_group(self) + self.decoded = decode_geometry_group(self, self.default_point_width, self.flip_on_decode) return self.decoded def __iter__(self): @@ -66,6 +72,11 @@ def __iter__(self): def __next__(self): return next(self.decoded) + def __eq__(self, other): + return super().__eq__(other) and self.count == other.count and self.stride == other.stride \ + and self.divisor == other.divisor and self.format == other.format and self.comp_count == other.comp_count \ + and self.data == other.data + def get_format(self): return self.format diff --git a/abmatt/brres/mdl0/polygon.py b/abmatt/brres/mdl0/polygon.py index 0ea8ed4..a2f0670 100644 --- a/abmatt/brres/mdl0/polygon.py +++ b/abmatt/brres/mdl0/polygon.py @@ -25,7 +25,6 @@ def paste(self, item): raise NotImplementedError() def __init__(self, name, parent, binfile=None): - self.tex_divisor = [0] * 8 self.tex_e = [1] * 8 self.material = None self.decoded = None @@ -33,6 +32,19 @@ def __init__(self, name, parent, binfile=None): self.visible_bone = None super(Polygon, self).__init__(name, parent, binfile) + def __eq__(self, other): + return super().__eq__(other) and self.encode_str == other.encode_str and self.face_count == other.face_count \ + and self.weight_index == other.weight_index and self.vertex_index == other.vertex_index \ + and self.normal_index == other.normal_index and self.color0_index == other.color0_index \ + and self.uv_indices == other.uv_indices and self.uv_mtx_indices == other.uv_mtx_indices \ + and self.facepoint_count == other.facepoint_count and self.vertices == other.vertices \ + and self.colors == other.colors and self.normals == other.normals and self.uvs == other.uvs \ + and self.flags == other.flags and self.linked_bone == other.linked_bone \ + and self.visible_bone == other.visible_bone and self.flags == other.flags \ + and self.vertex_e == other.vertex_e and self.normal_index3 == other.normal_index3 \ + and self.color0_e == other.color0_e and self.normal_e == other.normal_e \ + and self.priority == other.priority and self.material == other.material and self.data == other.data + def begin(self): # The face point indices, also indexes into the encode string self.vertex_index = -1 diff --git a/abmatt/brres/mdl0/shader.py b/abmatt/brres/mdl0/shader.py index 9f6ffe6..50c5823 100644 --- a/abmatt/brres/mdl0/shader.py +++ b/abmatt/brres/mdl0/shader.py @@ -293,8 +293,8 @@ def check(self): stage['colora'] = 'rastercolor' stage['alphaa'] = 'rasteralpha' else: - b.fix_des = 'Set solid color {}'.format(self.parent.getColor(0)) self.parent.set_default_color() + b.fix_des = 'Set solid color {}'.format(self.parent.getColor(0)) b.resolve() resolved_bug = True # indirect check diff --git a/abmatt/brres/mdl0/texcoord.py b/abmatt/brres/mdl0/texcoord.py index 12c8acc..f50b2cd 100644 --- a/abmatt/brres/mdl0/texcoord.py +++ b/abmatt/brres/mdl0/texcoord.py @@ -5,6 +5,8 @@ class TexCoord(Point): + flip_on_decode = True + def __deepcopy__(self, memodict=None): copy = TexCoord(self.name, None) return copy.paste(self) @@ -13,6 +15,10 @@ def __deepcopy__(self, memodict=None): def point_width(self): return self.comp_count + 1 + @property + def default_point_width(self): + return 2 + @staticmethod def comp_count_from_width(width): if width == 2: diff --git a/abmatt/brres/mdl0/vertex.py b/abmatt/brres/mdl0/vertex.py index 2661b4b..50ec335 100644 --- a/abmatt/brres/mdl0/vertex.py +++ b/abmatt/brres/mdl0/vertex.py @@ -20,21 +20,26 @@ def check_vertices(self, linked_bone): # Check Y value if self.parent.name == 'course' and self.minimum[1] + linked_bone.translation[1] < 0: AutoFix.warn('Vertex {} minimum y below axis {}'.format(self.name, self.minimum[1])) - for x in self.minimum: - if abs(x) > self.__MAX_COORD: - AutoFix.warn( - 'Vertex {} extreme coordinate {}, (ignore this warning for non-drivable surfaces)'.format( - self.name, x)) - for x in self.maximum: - if x > self.__MAX_COORD: - AutoFix.warn( - 'Vertex {} extreme coordinate {}, (ignore this warning for non-drivable surfaces)'.format( - self.name, x)) + if self.parent.name != 'vrcorn': + for x in self.minimum: + if abs(x) > self.__MAX_COORD: + AutoFix.warn( + 'Vertex {} extreme coordinate {}, (ignore this warning for non-drivable surfaces)'.format( + self.name, x)) + for x in self.maximum: + if x > self.__MAX_COORD: + AutoFix.warn( + 'Vertex {} extreme coordinate {}, (ignore this warning for non-drivable surfaces)'.format( + self.name, x)) @property def point_width(self): return self.comp_count + 2 + @property + def default_point_width(self): + return 3 + @property def default_comp_count(self): return XYZ_POSITION diff --git a/abmatt/brres/pat0/pat0.py b/abmatt/brres/pat0/pat0.py index a25406c..5dd9a15 100644 --- a/abmatt/brres/pat0/pat0.py +++ b/abmatt/brres/pat0/pat0.py @@ -7,70 +7,6 @@ from abmatt.brres.subfile import SubFile, set_anim_str, get_anim_str -class Pat0Collection(Node): - """A collection of pat0 mat animations for a model""" - - def __init__(self, name, parent, pats=None): - self.collection = [] - if pats: - for x in pats: - self.collection.extend(x.mat_anims) - super().__init__(name, parent) - - def __getitem__(self, material_name): - """Gets animation in collection matching material name""" - for x in self.collection: - if x.name == material_name: - return x - - def __len__(self): - return len(self.collection) - - def __iter__(self): - for x in self.collection: - yield x - - def get_used_textures(self): - used = set() - for anim in self.collection: - used |= anim.get_used_textures() - return used - - def add(self, mat_animation): - self.collection.append(mat_animation) - - def remove(self, animation): - self.collection.remove(animation) - - def rename(self, name): - self.name = name - - def info(self, key=None, indentation_level=0): - trace = ' ' * indentation_level + '>(PAT0)' + self.name if indentation_level else '>(PAT0)' + self.name - print('{}: {} animations'.format(trace, len(self.collection))) - indentation_level += 1 - for x in self.collection: - x.info(key, indentation_level) - - def consolidate(self): - """Combines the pats, returning list of pat0""" - n = 0 - pats = [] # for storing pat0s - for x in self.collection: - added = False - for pat in pats: - if pat.add(x): - added = True - break - if not added: # create new one - postfix = str(len(pats)) if len(pats) > 0 else '' - s = Pat0(self.name + postfix, self.parent) - if not s.add(x): - print('Error has occurred') - pats.append(s) - return pats - - class Pat0(SubFile): """ Pat0 animation class """ @@ -87,9 +23,10 @@ class Pat0(SubFile): VERSION_SECTIONCOUNT = {3: 5, 4: 6} EXPECTED_VERSION = 4 - def __init__(self, name, parent, binfile=None): + def __init__(self, name, parent, binfile=None, base_name=None): self.n_str = 1 self.version = 4 + self.base_name = base_name self.mat_anims = [] super(Pat0, self).__init__(name, parent, binfile) @@ -97,16 +34,29 @@ def begin(self): self.framecount = 100 self.loop = True + def __eq__(self, other): + return super().__eq__(other) and self.framecount == other.framecount and self.loop == other.loop \ + and self.mat_anims == other.mat_anims and self.version == other.version + + def __iter__(self): + return iter(self.mat_anims) + + def __next__(self): + return next(self.mat_anims) + def add(self, x): + if self.base_name != x.get_anim_base_name(): + return False if not self.mat_anims: self.framecount = x.framecount self.loop = x.loop self.mat_anims.append(x) return True - elif x.framecount == self.framecount and x.loop == self.loop: + elif x.framecount != self.framecount or x.loop != self.loop: + return False + elif x not in self.mat_anims: self.mat_anims.append(x) - return True - return False + return True def getTextures(self): textures = [] diff --git a/abmatt/brres/pat0/pat0_material.py b/abmatt/brres/pat0/pat0_material.py index 228be7b..7ee84ae 100644 --- a/abmatt/brres/pat0/pat0_material.py +++ b/abmatt/brres/pat0/pat0_material.py @@ -1,3 +1,4 @@ +import string from copy import deepcopy from abmatt.autofix import Bug, AutoFix @@ -15,20 +16,31 @@ class Pat0MatAnimation(Clipable): def __init__(self, name, parent, framecount=100, loop=True, binfile=None): self.enabled = True - self.fixedTexture = False - self.hasTexture = True - self.hasPalette = False + self.fixed_texture = False + self.has_texture = True + self.has_palette = False self.frames = [] self.framecount = framecount self.loop = loop - self.brres_textures = parent.get_texture_map() if parent else None + if parent: + self.parent_base_name = parent.get_anim_base_name() + self.brres_textures = parent.get_texture_map() + else: + self.brres_textures = None + self.parent_base_name = None super(Pat0MatAnimation, self).__init__(name, parent, binfile) + def get_anim_base_name(self): + if not self.parent_base_name: + if self.parent: + self.parent_base_name = self.parent.get_anim_base_name() + return self.parent_base_name def __eq__(self, other): - return other is not None and type(other) == Pat0MatAnimation \ + return super().__eq__(other) \ and self.enabled == other.enabled and self.framecount == other.framecount \ - and self.loop == other.loop and self.frames == other.frames + and self.loop == other.loop and self.frames == other.frames \ + and self.has_fixed_texture() == other.has_fixed_texture() def __deepcopy__(self, memodict={}): tex = self.brres_textures @@ -39,9 +51,9 @@ def __deepcopy__(self, memodict={}): def paste(self, item): self.enabled = item.enabled - self.fixedTexture = item.fixedTexture - self.hasTexture = item.hasTexture - self.hasPalette = item.hasPalette + self.fixed_texture = item.fixed_texture + self.has_texture = item.has_texture + self.has_palette = item.has_palette self.frames = deepcopy(item.frames) self.framecount = item.framecount self.loop = item.loop @@ -53,6 +65,9 @@ def create_brres_tex_ref(self, brres_textures): def get_used_textures(self): return {x.tex for x in self.frames} + def has_fixed_texture(self): + return self.fixed_texture or len(self.frames) <= 1 + def set_str(self, key, value): if key == 'loop': loop = validBool(value) diff --git a/abmatt/brres/scn0/camera.py b/abmatt/brres/scn0/camera.py index 9a30cd5..f38e66a 100644 --- a/abmatt/brres/scn0/camera.py +++ b/abmatt/brres/scn0/camera.py @@ -1,6 +1,27 @@ +from abmatt.brres.lib.node import Node -class Camera: - def __init__(self): - pass +class Camera(Node): + def begin(self): + self.node_id = 0 + self.real_id = 0 + self.projection_type = 0 + self.flags1 = 0 + self.flags2 = 0 + self.position = [0.0, 0.0, 0.0] + self.aspect = 0.0 + self.near_z = 0.0 + self.far_z = 0.0 + self.rotate = [0.0, 0.0, 0.0] + self.aim = [0.00, 0.0, 0.0] + self.twist = 0.0 + self.persp_fov_y = 0.0 + self.ortho_height = 0.0 + def __eq__(self, other): + return super().__eq__(other) and self.node_id == other.node_id and self.real_id == other.real_id \ + and self.projection_type == other.projection_type and self.flags1 == other.flags1 \ + and self.flags2 == other.flags2 and self.position == other.position and self.aspect == other.aspect \ + and self.near_z == other.near_z and self.far_z == other.far_z and self.rotate == other.rotate \ + and self.aim == other.aim and self.twist == other.twist and self.persp_fov_y == other.persp_fov_y \ + and self.ortho_height == other.ortho_height \ No newline at end of file diff --git a/abmatt/brres/scn0/fog.py b/abmatt/brres/scn0/fog.py index 658ec1b..1a18dc0 100644 --- a/abmatt/brres/scn0/fog.py +++ b/abmatt/brres/scn0/fog.py @@ -1,6 +1,17 @@ +from abmatt.brres.lib.node import Node -class Fog: - def __init__(self): - pass +class Fog(Node): + def begin(self): + self.node_id = 0 + self.real_id = 0 + self.flags = 0 + self.type = 0 + self.start = 0.0 + self.end = 0.0 + self.color = [0, 0, 0, 0] + def __eq__(self, other): + return super().__eq__(other) and self.node_id == other.node_id and self.real_id == other.real_id \ + and self.flags == other.flags and self.type == other.type and self.start == other.start \ + and self.end == other.end and self.color == other.color diff --git a/abmatt/brres/scn0/light.py b/abmatt/brres/scn0/light.py index c6eee09..7def248 100644 --- a/abmatt/brres/scn0/light.py +++ b/abmatt/brres/scn0/light.py @@ -1,15 +1,57 @@ +from abmatt.brres.lib.node import Node -class Light: - def __init__(self): - pass +class Light(Node): + def begin(self): + self.node_id = 0 + self.real_id = 0 + self.non_spec_light_id = 0 + self.fixed_flags = 0 + self.usage_flags = 0 + self.vis_offset = 0 + self.start_point = [0.0, 0.0, 0.0] + self.light_color = [0, 0, 0, 0] + self.end_point = [0.0, 0.0, 0.0] + self.dist_func = 0 + self.ref_distance = 0.0 + self.ref_brightness = 0.0 + self.spot_func = 0 + self.cutoff = 0.0 + self.specular_color = 0 + self.shinyness = 0.0 + def __eq__(self, other): + return super().__eq__(other) and self.node_id == other.node_id \ + and self.real_id == other.real_id and self.non_spec_light_id == other.non_spec_light_id \ + and self.fixed_flags == other.fixed_flags and self.usage_flags == other.usage_flags \ + and self.vis_offset == other.vis_offset and self.start_point == other.start_point \ + and self.end_point == other.end_point and self.light_color == other.light_color \ + and self.dist_func == other.dist_func and self.ref_distance == other.ref_distance \ + and self.ref_brightness == other.ref_brightness and self.spot_func == other.spot_func \ + and self.cutoff == other.cutoff and self.specular_color == other.specular_color \ + and self.shinyness == other.shinyness -class AmbientLight: - def __init__(self): - pass +class AmbientLight(Node): + def begin(self): + self.node_id = 0 + self.real_id = 0 + self.fixed_flags = 0 + self.flags = 0 + self.lighting = [0, 0, 0, 0] -class LightSet: - def __init__(self): + def __eq__(self, other): + return super().__eq__(other) and self.node_id == other.node_id and self.real_id == other.real_id \ + and self.fixed_flags == other.fixed_flags and self.flags == other.flags \ + and self.lighting == other.lighting + +class LightSet(Node): + def begin(self): + self.node_id = 0 + self.real_id = 0 + self.ambient_name = '' self.light_names = [] + + def __eq__(self, other): + return super().__eq__(other) and self.node_id == other.node_id and self.real_id == other.real_id \ + and self.ambient_name == other.ambient_name and self.light_names == other.light_names diff --git a/abmatt/brres/scn0/scn0.py b/abmatt/brres/scn0/scn0.py index 6b6201d..2021ff2 100644 --- a/abmatt/brres/scn0/scn0.py +++ b/abmatt/brres/scn0/scn0.py @@ -10,12 +10,18 @@ class Scn0KeyFrameList: def __init__(self): self.frames = [] + def __eq__(self, other): + return self.frames == other.frames + class KeyFrame: def __init__(self, value=0, index=0, delta=0): self.value = value self.index = index self.delta = delta + def __eq__(self, other): + return self.value == other.value and self.index == other.index and self.delta == other.delta + def unpack(self, binfile): self.delta, self.index, self.value = binfile.read('3f', 12) return self @@ -52,6 +58,11 @@ def __init__(self, name, parent, binfile=None): self.cameras = [] super(Scn0, self).__init__(name, parent, binfile) + def __eq__(self, other): + return super().__eq__(other) and self.animations == other.animations and self.lightsets == other.lightsets \ + and self.ambient_lights == other.ambient_lights and self.lights == other.lights \ + and self.fogs == other.fogs and self.cameras == other.cameras + def begin(self): self.framecount = 1 self.loop = True diff --git a/abmatt/brres/shp0/shp0.py b/abmatt/brres/shp0/shp0.py index 76c17c2..5508495 100644 --- a/abmatt/brres/shp0/shp0.py +++ b/abmatt/brres/shp0/shp0.py @@ -21,6 +21,10 @@ def __init__(self, name, parent, binfile=None): self.strings = [] super(Shp0, self).__init__(name, parent, binfile) + def __eq__(self, other): + return super().__eq__(other) and self.framecount == other.framecount and self.loop == other.loop \ + and self.animations == other.animations and self.strings == other.strings + def __getitem__(self, item): if item == self.SETTINGS[0]: return self.framecount diff --git a/abmatt/brres/shp0/shp0_animation.py b/abmatt/brres/shp0/shp0_animation.py index 54a7757..239d5c8 100644 --- a/abmatt/brres/shp0/shp0_animation.py +++ b/abmatt/brres/shp0/shp0_animation.py @@ -1,19 +1,30 @@ +from abmatt.brres.lib.node import Node + + class Shp0KeyFrameList: def __init__(self, id): self.frames = [] self.id = id + def __eq__(self, other): + return self.id == other.id and self.frames == other.frames + class Shp0KeyFrame: def __init__(self, frame_id, value, delta): self.frame_id = frame_id self.value = value self.delta = delta + def __eq__(self, other): + return self.frame_id == other.frame_id and self.value == other.value and self.delta == other.delta + -class Shp0Animation: +class Shp0Animation(Node): """A single animation entry in the file""" - def __init__(self, name): + def __init__(self, name, parent): # for modifying, need to add framecount / texture references .. etc - self.name = name self.entries = [] + super().__init__(name, parent) + def __eq__(self, other): + return super().__eq__(other) and self.entries == other.entries \ No newline at end of file diff --git a/abmatt/brres/srt0/srt0.py b/abmatt/brres/srt0/srt0.py index 0db7027..2b7750a 100644 --- a/abmatt/brres/srt0/srt0.py +++ b/abmatt/brres/srt0/srt0.py @@ -1,5 +1,6 @@ #!/usr/bin/python """ Srt0 Brres subfile """ +import string from abmatt.brres.lib.node import Clipable, Node # --------------------------------------------------------- @@ -8,61 +9,6 @@ from abmatt.brres.subfile import SubFile, set_anim_str, get_anim_str -class SRTCollection(Node): - """A collection of srt mat animations for a model""" - - def __init__(self, name, parent, srts=None): - self.collection = [] - if srts: - for x in srts: - self.collection.extend(x.matAnimations) - super().__init__(name, parent) - - def __getitem__(self, material_name): - """Gets animation in collection matching material name""" - for x in self.collection: - if x.name == material_name: - return x - - def __len__(self): - return len(self.collection) - - def __iter__(self): - for x in self.collection: - yield x - - def add(self, mat_animation): - self.collection.append(mat_animation) - - def remove(self, animation): - self.collection.remove(animation) - - def info(self, key=None, indentation_level=0): - trace = ' ' * indentation_level + '>(SRT0)' + self.name if indentation_level else '>(SRT0)' + self.name - print('{}: {} animations'.format(trace, len(self.collection))) - indentation_level += 1 - for x in self.collection: - x.info(key, indentation_level) - - def consolidate(self): - """Combines the srts, returning list of SRT0""" - n = 0 - srts = [] # for storing srt0s - for x in self.collection: - added = False - for srt in srts: - if srt.add(x): - added = True - break - if not added: # create new one - postfix = str(len(srts) + 1) if len(srts) > 0 else '' - s = Srt0(self.name + postfix, self.parent) - if not s.add(x): - print('Error has occurred') - srts.append(s) - return srts - - class Srt0(SubFile): """ Srt0 Animation """ SETTINGS = ('framecount', 'loop') @@ -71,8 +17,9 @@ class Srt0(SubFile): VERSION_SECTIONCOUNT = {4: 1, 5: 2} EXPECTED_VERSION = 5 - def __init__(self, name, parent, binfile=None): + def __init__(self, name, parent, binfile=None, base_name=None): self.matAnimations = [] + self.base_name = base_name super(Srt0, self).__init__(name, parent, binfile) def begin(self): @@ -80,6 +27,11 @@ def begin(self): self.loop = True self.matrixmode = 0 + def __iter__(self): + return iter(self.matAnimations) + + def __next__(self): + return next(self.matAnimations) def set_str(self, key, value): set_anim_str(self, key, value) @@ -112,12 +64,15 @@ def setFrameCount(self, count): def add(self, mat_animation): """ Adds a material animation """ + if self.base_name != mat_animation.get_anim_base_name(): + return False if not self.matAnimations: self.loop = mat_animation.loop self.framecount = mat_animation.framecount elif self.framecount != mat_animation.framecount or self.loop != mat_animation.loop: return False - self.matAnimations.append(mat_animation) + if mat_animation not in self.matAnimations: + self.matAnimations.append(mat_animation) return True def removeMatAnimation(self, material): diff --git a/abmatt/brres/srt0/srt0_animation.py b/abmatt/brres/srt0/srt0_animation.py index 0ff00e2..413b8b6 100644 --- a/abmatt/brres/srt0/srt0_animation.py +++ b/abmatt/brres/srt0/srt0_animation.py @@ -1,3 +1,4 @@ +import string from copy import deepcopy, copy from abmatt.autofix import Bug, AutoFix @@ -21,7 +22,7 @@ def __init__(self, name, framecount, parent, binfile=None): super(SRTTexAnim, self).__init__(name, parent, binfile) def __eq__(self, other): - return type(other) == SRTTexAnim and self.animations == other.animations + return super().__eq__(other) and self.animations == other.animations # ---------------------- CLIPABLE ------------------------------------------------------------- def paste(self, item): @@ -105,16 +106,37 @@ class SRTMatAnim(Clipable): def __init__(self, name, frame_count=1, looping=True, parent=None, binfile=None): self.parent = parent self.framecount = frame_count + self.parent_base_name = parent.get_anim_base_name() if parent else None self.tex_animations = [] self.texEnabled = [False] * 8 + self.materials = [] self.loop = looping super(SRTMatAnim, self).__init__(name, parent, binfile) + def __deepcopy__(self, memodict=None): + copy = SRTMatAnim(self.name, self.framecount, self.loop) + copy.parent_base_name = self.parent_base_name + copy.texEnabled = [x for x in self.texEnabled] + copy.tex_animations = deepcopy(self.tex_animations) + return copy + def __eq__(self, other): - return other is not None and type(other) == SRTMatAnim \ + return super().__eq__(other) \ and self.framecount == other.framecount and self.loop == other.loop \ and self.texEnabled == other.texEnabled and self.tex_animations == other.tex_animations + def __mat_with_max_layers(self): + v = [len(x.layers) for x in self.materials] + return self.materials[v.index(max(v))] + + def get_anim_base_name(self): + if not self.parent_base_name: + if self.parent: + self.parent_base_name = self.parent.get_anim_base_name() + elif self.materials: + self.parent_base_name = self.materials[0].get_anim_base_name() + return self.parent_base_name + def mark_unmodified(self): self.is_modified = False self._mark_unmodified_group(self.tex_animations) @@ -166,7 +188,7 @@ def paste(self, item): # setup parent for x in self.tex_animations: x.parent = self - self.update_layer_names(self.parent) + self.update_layer_names() self.mark_modified() def set_frame_count(self, count): @@ -175,15 +197,19 @@ def set_frame_count(self, count): x.set_frame_count(count) def set_material(self, material): - self.parent = material - self.update_layer_names(material) + self.materials.append(material) + self.update_layer_names() + + def remove_material(self, material): + if material in self.materials: + self.materials.remove(material) def tex_enable(self, i): if not self.texEnabled[i]: self.texEnabled[i] = True anim = SRTTexAnim(i, self.framecount, self) self.tex_animations.append(anim) - layer = self.parent.getLayerI(i) + layer = self.__mat_with_max_layers().getLayerI(i) if layer: anim.real_name = layer.name self.mark_modified() @@ -224,10 +250,12 @@ def add_layer(self): def add_layer_by_name(self, name): """Adds layer if found""" - i = self.parent.getLayerByName(name) - if i > 0: - self.tex_enable(i) - self.mark_modified() + for x in self.materials: + i = x.getLayerByName(name) + if i: + self.tex_enable(i) + self.mark_modified() + return raise ValueError('{} Unknown layer {}'.format(self.name, name)) # -------------------------------- Remove ------------------------------------------- @@ -269,30 +297,35 @@ def update_layer_name_i(self, i, name): if tex: tex.real_name = name - def update_layer_names(self, material): + def update_layer_names(self): """Updates the underlying reference names given material""" - layers = material.layers - j = 0 # tex indexer - for i in range(len(layers)): - if self.texEnabled[i]: - self.tex_animations[j].real_name = layers[i].name - j += 1 + for material in self.materials: + layers = material.layers + j = 0 # tex indexer + for i in range(len(layers)): + if self.texEnabled[i]: + self.tex_animations[j].real_name = layers[i].name + j += 1 + return def check(self): - if not self.parent: + if not self.materials: return - max = len(self.parent.layers) + maximum = max(len(x.layers) for x in self.materials) enabled = 0 for i in range(8): if self.texEnabled[i]: enabled += 1 - if i >= max: - b = Bug(1, 3, "{} SRT layer {} doesn't exist".format(self.parent.name, i), 'Remove srt0 layer') + if i >= maximum: + b = Bug(1, 3, "{} SRT layer {} doesn't exist".format(self.name, i), 'Remove srt0 layer') if self.REMOVE_UNKNOWN_REFS: self.tex_disable(i) b.resolve() if not enabled: - self.parent.remove_srt0() + mats = self.materials + self.materials = [] + for x in mats: + x.remove_srt0() def save(self, dest, overwrite): from abmatt.brres.srt0.srt0 import Srt0 diff --git a/abmatt/brres/subfile.py b/abmatt/brres/subfile.py index ba56efa..65728e3 100644 --- a/abmatt/brres/subfile.py +++ b/abmatt/brres/subfile.py @@ -5,6 +5,7 @@ # Most Brres Subfiles # -------------------------------------------------------- import os +import string from abmatt.autofix import Bug, AutoFix from abmatt.brres.lib.binfile import BinFile @@ -19,6 +20,9 @@ def set_anim_str(animation, key, value): elif key == 'loop': # loop val = validBool(value) animation.loop = val + else: + return False + return True def get_anim_str(animation, key): @@ -62,6 +66,11 @@ def __init__(self, name, parent, binfile): def _getNumSections(self): return self.VERSION_SECTIONCOUNT[self.version] + def get_anim_base_name(self): + if self.parent and self.parent.respect_model_names(): + return self.name + return self.name.rstrip(string.digits) + def check(self): if self.version != self.EXPECTED_VERSION: b = Bug(2, 3, '{} {} unusual version {}'.format(self.MAGIC, self.name, self.version), diff --git a/abmatt/brres/tex0.py b/abmatt/brres/tex0.py index 44b15e2..4e2c3cc 100644 --- a/abmatt/brres/tex0.py +++ b/abmatt/brres/tex0.py @@ -37,6 +37,10 @@ def begin(self): self.num_mips = 0 self.data = None + def __eq__(self, other): + return super().__eq__(other) and self.width == other.width and self.height == other.height \ + and self.format == other.format and self.num_mips == other.num_mips and self.data == other.data + def get_str(self, key): if key == 'dimensions': return self.width, self.height @@ -136,7 +140,6 @@ def set_dimensions(self, width, height): return False def check(self): - super(Tex0, self).check() if not self.is_power_of_two(self.width) or not self.is_power_of_two(self.height): b = Bug(2, 2, str(self) + ' not a power of 2', None) if self.should_resize_pow_two(): diff --git a/abmatt/command.py b/abmatt/command.py index 8972550..1f1a57a 100644 --- a/abmatt/command.py +++ b/abmatt/command.py @@ -70,7 +70,7 @@ def getBrresFromMaterials(mats): class Command: - COMMANDS = ["preset", "set", "add", "remove", "info", "select", "save", "copy", "paste", "convert"] + COMMANDS = ["preset", "set", "add", "remove", "info", "select", "save", "copy", "paste", "convert", "load"] SELECTED = [] # selection list SELECT_TYPE = None # current selection list type SELECT_ID = None # current selection id @@ -78,7 +78,6 @@ class Command: DESTINATION = None OVERWRITE = False ACTIVE_FILES = [] # currently being used in selection - OPEN_FILES = {} # currently open FILES_MARKED = set() # files marked as modified MODELS = [] MATERIALS = [] @@ -137,6 +136,9 @@ def __init__(self, text=None, arg_list=None): if cmd == 'convert': self.set_convert(x) return + elif cmd == 'load': + self.set_load(x) + return if self.setType(x[0]): x.pop(0) if cmd == 'select': @@ -187,6 +189,13 @@ def __init__(self, text=None, arg_list=None): raise ParsingException(self.txt, "Unknown Key {} for {}, possible keys:\n\t{}".format( self.key, self.type, self.TYPE_SETTING_MAP[self.type])) + def set_load(self, params): + if params: + filepath = params.pop(0) + self.file = filepath + else: + raise ParsingException('load requires target!') + def set_convert(self, params): flags = 0 self.name = self.destination = self.model = None @@ -375,7 +384,7 @@ def setCmd(self, val): @staticmethod def updateFile(filename): # check in opened files - files = MATCHING.findAll(filename, Command.OPEN_FILES.values()) + files = MATCHING.findAll(filename, Brres.OPEN_FILES) if files: Command.ACTIVE_FILES = files else: @@ -395,30 +404,33 @@ def updateFile(filename): return Command.ACTIVE_FILES @staticmethod - def closeFiles(file_names): - opened = Command.OPEN_FILES + def closeFiles(files): + opened = Brres.OPEN_FILES marked = Command.FILES_MARKED - for x in file_names: - file = opened.pop(x) - file.close() - marked.remove(file) + for x in files: + try: + x.close() + marked.remove(x) + opened.remove(x) + except (ValueError, KeyError): + pass @staticmethod def auto_close(amount, exclude=[]): can_close = [] # modified but can close to_close = [] # going to close if needs closing excluded = [] - opened = Command.OPEN_FILES + opened = Brres.OPEN_FILES # first pass, mark non-modified files for closing, and appending active for x in opened: - if x not in exclude: - if not opened[x].is_modified: + if x.name not in exclude: + if not x.name.is_modified: to_close.append(x) amount -= 1 else: can_close.append(x) else: - excluded.append(opened[x]) + excluded.append(x) if amount: for x in can_close: to_close.append(x) @@ -430,7 +442,7 @@ def auto_close(amount, exclude=[]): @staticmethod def openFiles(filenames): - opened = Command.OPEN_FILES + opened = Brres.OPEN_FILES max = Command.MAX_FILES_OPEN # max that can remain open if max - len(filenames) < 0: raise MaxFileLimit() @@ -444,17 +456,13 @@ def openFiles(filenames): active = [] # open any that aren't opened for f in to_open: - # try: brres = Brres.get_brres(f, True) - opened[f] = brres active.append(brres) - # except UnpackingError as e: - # AutoFix.error(e) return active @staticmethod def create_or_open(filename): - amount = len(Command.OPEN_FILES) - Command.MAX_FILES_OPEN + amount = len(Brres.OPEN_FILES) - Command.MAX_FILES_OPEN if amount > 0: Command.auto_close(amount, [filename]) if os.path.exists(filename): @@ -462,7 +470,6 @@ def create_or_open(filename): b = files[0] if len(files) else None else: b = Brres(filename, read_file=False) - Command.OPEN_FILES[filename] = b Command.ACTIVE_FILES = [b] Command.MODELS = [] return b @@ -678,14 +685,6 @@ def updateTypeSelection(self): for x in getBrresFromMaterials(self.MATERIALS): Command.SELECTED.extend(MATCHING.findAll(self.SELECT_ID, x.textures)) - @staticmethod - def markModified(): - marked = Command.FILES_MARKED - for f in Command.ACTIVE_FILES: - if f not in marked: - f.is_modified = True - Command.FILES_MARKED.add(f) - # ---------------------------------------------- RUN CMD --------------------------------------------------- @staticmethod def run_commands(commandlist): @@ -735,6 +734,10 @@ def import_texture(self, brres, file, tex_format=None, num_mips=-1): print(e) def run_cmd(self): + if self.cmd == 'load': + if not os.path.exists(self.file): + raise RuntimeError('load file not found {}'.format(self.file)) + return self.run_cmds(self.load_commandfile(self.file)) if self.hasSelection: if not self.updateSelection(self.file, self.model, self.name): AutoFix.warn('No selection found for "{}"'.format(self.txt)) @@ -764,7 +767,6 @@ def run_cmd(self): return True raise RuntimeError('No items found in selection! ({})'.format(self.txt)) if self.cmd == 'set': - self.markModified() for x in self.SELECTED: x.set_str(self.key, self.value) elif self.cmd == 'info': @@ -776,15 +778,12 @@ def run_cmd(self): # for y in self.ACTIVE_FILES: # print(y.name) elif self.cmd == 'add': - self.markModified() self.add(self.SELECT_TYPE, self.SELECT_ID) elif self.cmd == 'remove': - self.markModified() self.remove(self.SELECT_TYPE, self.SELECT_ID) elif self.cmd == 'copy': self.run_copy(self.SELECT_TYPE) elif self.cmd == 'paste': - self.markModified() self.run_paste(self.SELECT_TYPE) return True @@ -898,11 +897,18 @@ def run_save(self): if not x.save(self.destination, self.overwrite): raise SaveError('File already Exists!') + def run_cmds(self, cmds): + err = False + for x in cmds: + try: + err = err or not x.run_cmd() + except RuntimeError as e: + AutoFix.warn(e) + return not err + def runPreset(self, key): """Runs preset""" - cmds = self.PRESETS[key] - for x in cmds: - x.run_cmd() + return self.run_cmds(self.PRESETS[key]) def add(self, type, type_id): """Add command""" @@ -1263,7 +1269,7 @@ def do_preset(self, line): self.run('preset', line) def help_preset(self): - print('USAGE: preset [for ]') + print('PRESETS: {}\nUSAGE: preset [for ]'.format(list(Command.PRESETS.keys()))) def complete_preset(self, text, line, begid, endid): words = self.get_words(text, line) @@ -1293,7 +1299,7 @@ def complete_save(self, text, line, begid, endid): possible = [] words = self.get_words(text, line) if not words: - possible = [x for x in Command.OPEN_FILES if x.startswith(text)] + possible = [x for x in Brres.OPEN_FILES if x.name.startswith(text)] elif 'as' in words: file_words = words[words.index('as') + 1:] if file_words: @@ -1350,6 +1356,16 @@ def help_convert(self): print('Converts dae or obj model to/from brres.\n' 'Usage: convert [to ][--no-normals][--no-colors][--single-bone][--no-uvs]') + def do_load(self, line): + self.run('load', line) + + def complete_load(self, text, line, begid, endid): + words = self.get_words(text, line) + return self.find_files(self.construct_file_path(words), text) + + def help_load(self): + print('Loads and runs a file with commands and/or presets.') + def default(self, line): if line == 'x' or line == 'q': return self.do_quit(line) diff --git a/abmatt/config.py b/abmatt/config.py index c338260..f54f97e 100644 --- a/abmatt/config.py +++ b/abmatt/config.py @@ -11,7 +11,7 @@ def parse_line(line): line = line[:comment] split_line = line.split('=', 1) if len(split_line) > 1: - return [x.strip().lower() for x in split_line] + return [x.strip() for x in split_line] return None @@ -46,7 +46,8 @@ def __len__(self): return len(self.config) def __getitem__(self, item): - return self.config.get(item) + item = self.config.get(item) + return item.lower() if item else item def __setitem__(self, key, value): if value == self.config.get(key): diff --git a/abmatt/converters/convert_lib.py b/abmatt/converters/convert_lib.py index af346d6..614e60d 100644 --- a/abmatt/converters/convert_lib.py +++ b/abmatt/converters/convert_lib.py @@ -9,6 +9,7 @@ from abmatt.brres.material_library import MaterialLibrary from abmatt.brres.mdl0.material import material from abmatt.brres.mdl0.mdl0 import Mdl0 +from abmatt.command import Command from abmatt.converters import matrix from abmatt.converters.convert_mats_to_json import MatsToJsonConverter from abmatt.converters.matrix import matrix_to_srt @@ -23,6 +24,8 @@ class Converter: NO_UVS = 0x8 DETECT_FILE_UNITS = True OVERWRITE_IMAGES = False + ENCODE_PRESET = None + ENCODE_PRESET_ON_NEW = True def __init__(self, brres, mdl_file, flags=0, encode=True, mdl0=None, encoder=None): if not brres: @@ -38,15 +41,16 @@ def __init__(self, brres, mdl_file, flags=0, encode=True, mdl0=None, encoder=Non self.mdl0 = mdl0 if type(mdl0) == Mdl0 else brres.get_model(mdl0) self.flags = flags self.image_dir = None - self.image_library = set() - self.geometries = [] self.replacement_model = None self.encode = encode self.encoder = encoder def _start_saving(self, mdl0): - AutoFix.info('Exporting to {}...'.format(self.mdl_file)) + AutoFix.info('Exporting {} to {}...'.format(os.path.basename(self.brres.name), self.mdl_file)) + if 'sand_battle.d' in self.brres.name and 'map_model' in self.brres.name: + print('debug') self.start = time.time() + self.image_library = set() if mdl0 is None: mdl0 = self.mdl0 if mdl0 is None: @@ -77,10 +81,13 @@ def _end_saving(self, writer): os.chdir(self.cwd) writer.write(self.mdl_file) AutoFix.info('\t...finished in {} seconds.'.format(round(time.time() - self.start, 2))) + return self.mdl_file def _start_loading(self, model_name): AutoFix.info('Converting {}... '.format(self.mdl_file)) self.start = time.time() + self.image_library = set() + self.geometries = [] self.cwd = os.getcwd() self.import_textures_map = {} self.mdl_file = os.path.abspath(self.mdl_file) @@ -115,6 +122,9 @@ def _end_loading(self): if self.is_map: mdl0.add_map_bones() os.chdir(self.cwd) + if self.ENCODE_PRESET: + if not self.ENCODE_PRESET_ON_NEW or (self.replacement_model is None and self.json_polygon_encoding is None): + Command('preset ' + self.ENCODE_PRESET + ' for * in ' + self.brres.name + ' model ' + self.mdl0.name).run_cmd() AutoFix.info('\t... finished in {} secs'.format(round(time.time() - self.start, 2))) if self.encoder: self.encoder.after_encode(mdl0) @@ -173,10 +183,11 @@ def is_identity_matrix(mtx): def __add_pat0_images(self): """Adds the pat0 images to tex0 library""" - if self.mdl0.pat0_collection is not None: - for tex in self.mdl0.pat0_collection.get_used_textures(): - if tex not in self.tex0_map: - self.tex0_map[tex] = self.texture_library.get(tex) + for material in self.mdl0.materials: + if material.pat0: + for tex in material.pat0.get_used_textures(): + if tex not in self.tex0_map: + self.tex0_map[tex] = self.texture_library.get(tex) def _encode_material(self, generic_mat): m = None @@ -286,6 +297,7 @@ def convert(self): self.load_model() else: self.save_model() + return self def __eq__(self, other): return type(self) == type(other) and self.brres is other.brres and self.mdl0 is other.mdl0 \ diff --git a/abmatt/converters/convert_mats_to_json.py b/abmatt/converters/convert_mats_to_json.py index 6eceeae..9ddecdd 100644 --- a/abmatt/converters/convert_mats_to_json.py +++ b/abmatt/converters/convert_mats_to_json.py @@ -78,7 +78,11 @@ def __load_polygons(self, poly_data): self.polygons_by_name[x] = poly_data[x] def __load_srt0(self, srt0, data): - self.__load_settings(srt0, data.get('settings')) + settings = data.get('settings') + base_name = settings.get('base_name') + if base_name: + srt0.parent_base_name = base_name + self.__load_settings(srt0, settings) d = data.get('texture_animations') if d: if len(d) != len(srt0.tex_animations): @@ -114,21 +118,32 @@ def __load_pat0_settings(self, pat0, data): pat0.fixedTeture = data['fixed_texture'] pat0.framecount = data['frame_count'] pat0.loop = data['loop'] + pat0.parent_base_name = data.get('base_name') def __load_layers(self, layers, data): i = 0 - for layer_name in data: - layers[i].set_name(layer_name) - self.__load_settings(layers[i], data[layer_name]) + for x in data: + if type(data) is dict: + x = data[x] + self.__load_settings(layers[i], x) i += 1 def __load_shader(self, shader, data): self.__load_settings(shader, data.get('settings')) + swap_table = data.get('swap_table') + if swap_table: + try: + for i in range(len(shader.swap_table)): + shader.swap_table[i].data = swap_table[i] + except IndexError: + pass i = 0 stages_data = data.get('stages') if stages_data: for stage in stages_data: - self.__load_settings(shader.stages[i], stages_data[stage]) + if type(stages_data) is dict: + stage = stages_data[stage] + self.__load_settings(shader.stages[i], stage) i += 1 @staticmethod @@ -165,10 +180,13 @@ def __get_polygon_str(self, poly): def __get_shader_str(self, shader): return {'settings': self.__get_settings(shader), + 'swap_table': [x.data for x in shader.swap_table], 'stages': self.__get_items_str(shader.stages)} def __get_srt0_str(self, srt0): - return {'settings': self.__get_settings(srt0), + settings = self.__get_settings(srt0) + settings['base_name'] = srt0.parent_base_name + return {'settings': settings, 'texture_animations': [self.__get_srt0_tex_anim_str(x) for x in srt0.tex_animations]} def __get_srt0_tex_anim_str(self, tex_anim): @@ -201,16 +219,14 @@ def __get_pat0_frames(self, frames): def __get_pat0_settings(self, pat0): return { 'enabled': pat0.enabled, - 'fixed_texture': pat0.fixedTexture, + 'fixed_texture': pat0.fixed_texture, 'frame_count': pat0.framecount, 'loop': pat0.loop, + 'base_name': pat0.parent_base_name } def __get_items_str(self, items): - ret = {} - for x in items: - ret[x.name] = self.__get_settings(x) - return ret + return [self.__get_settings(x) for x in items] @staticmethod def __get_settings(item): diff --git a/abmatt/converters/convert_obj.py b/abmatt/converters/convert_obj.py index bc55862..b6968a2 100644 --- a/abmatt/converters/convert_obj.py +++ b/abmatt/converters/convert_obj.py @@ -56,10 +56,15 @@ def save_model(self, mdl0=None): obj_mat = self.__decode_material(mat) obj_materials[obj_mat.name] = obj_mat obj_geometries = obj.geometries + has_colors = False for x in polygons: geometry = x.get_decoded() material = geometry.material_name obj_geometries.append(self.__decode_geometry(geometry, material)) + if x.get_color_group(): + has_colors = True + if has_colors: + AutoFix.warn('Loss of color data exporting obj') self._end_saving(obj) @staticmethod @@ -86,8 +91,6 @@ def __decode_geometry(geometry, material_name): geo.vertices = geometry.vertices geo.normals = geometry.normals geo.has_normals = bool(geo.normals) - if geometry.colors: - AutoFix.warn('Loss of color data for {}'.format(geo.name)) texcoords = geometry.texcoords if len(texcoords) > 1: AutoFix.warn('Loss of UV data for {}.'.format(geo.name)) diff --git a/abmatt/converters/dae.py b/abmatt/converters/dae.py index 131d2fd..a762b33 100644 --- a/abmatt/converters/dae.py +++ b/abmatt/converters/dae.py @@ -726,11 +726,12 @@ def __try_get_texture(self, map_xml_node, profile_common): if not image_id: image_id = init_from[0].text image = self.get_element_by_id(image_id) - init_from = first(image, 'init_from') - image_path = init_from.text - if not image_path: - image_path = init_from[0].text - return image_path + if image is not None: + init_from = first(image, 'init_from') + image_path = init_from.text + if not image_path: + image_path = init_from[0].text + return image_path @staticmethod def __get_bound_materials(node): diff --git a/abmatt/converters/geometry.py b/abmatt/converters/geometry.py index 571ae87..5978bd9 100644 --- a/abmatt/converters/geometry.py +++ b/abmatt/converters/geometry.py @@ -2,6 +2,7 @@ import numpy as np +from abmatt.autofix import AutoFix from abmatt.brres.mdl0.color import Color from abmatt.brres.mdl0.normal import Normal from abmatt.brres.mdl0.polygon import Polygon @@ -12,6 +13,8 @@ class Geometry: + ENABLE_VERTEX_COLORS = True + def __init__(self, name, material_name, vertices, texcoords=None, normals=None, colors=None, triangles=None, influences=None, linked_bone=None): self.name = name @@ -66,7 +69,7 @@ def swap_y_z_axis(self): points[:, [1, 2]] = points[:, [2, 1]] def apply_linked_bone_bindings(self): - self.influences.apply_world_position(self.vertices) + self.influences.apply_world_position(np.array(self.vertices)) def apply_matrix(self, matrix): if matrix is not None: @@ -125,7 +128,8 @@ def encode(self, mdl, visible_bone=None, encoder=None, mdl.objects.append(p) material = mdl.get_material_by_name(self.material_name) mdl.add_definition(material, p, visible_bone, self.priority) - if self.colors: + if self.colors and self.ENABLE_VERTEX_COLORS: + AutoFix.info('{} has colors, enabled vertex color in light channel.'.format(self.name)) material.enable_vertex_color() if encoder: encoder.after_encode(p) @@ -196,7 +200,7 @@ def __encode_weighted_tris(self, tri_strips, tris): def __encode_tris(self, tris, is_weighted=False): tris[:, [0, 1]] = tris[:, [1, 0]] - triset = TriangleSet(tris) + triset = TriangleSet(tris, is_weighted) if not triset: return None, 0, 0 fmt_str = self.fmt_str if not is_weighted else None diff --git a/abmatt/converters/influence.py b/abmatt/converters/influence.py index ae073c8..083e940 100644 --- a/abmatt/converters/influence.py +++ b/abmatt/converters/influence.py @@ -21,7 +21,7 @@ def get_bone_parent(self): @staticmethod def get_world_position(bone): - matrix = bone.get_inv_transform_matrix() + matrix = np.array(bone.get_inv_transform_matrix()) parent = bone.get_bone_parent() if parent is not None: return np.dot(matrix, Joint.get_world_position(parent)) @@ -108,7 +108,7 @@ def __get_world_position_matrix(self): else: matrix = np.dot(matrix, bw_matrix) self.world_matrix = matrix - return self.world_matrix + return np.array(self.world_matrix) def apply_world_position(self, vertex): return apply_matrix_single(self.__get_world_position_matrix(), vertex) diff --git a/abmatt/converters/obj.py b/abmatt/converters/obj.py index 2d45d51..2ab687b 100644 --- a/abmatt/converters/obj.py +++ b/abmatt/converters/obj.py @@ -277,7 +277,7 @@ def parse_mat_lib(self, mat_lib_path): for line in data: if len(line) < 2 or line[0] == '#': continue - words = re.split("\s+", line.rstrip('\n').strip()) + words = re.split(r"\s+", line.rstrip('\n').strip()) new_mat = self.parse_mtl_words(words, material) if new_mat: material = ObjMaterial(new_mat) @@ -291,7 +291,7 @@ def parse_file(self, filename): for line in data: if len(line) < 2 or line[0] == '#': continue - words = re.split('\s+', line.rstrip('\n').strip()) + words = re.split(r'\s+', line.rstrip('\n').strip()) new_geo = self.parse_words(words, geometry) if new_geo: if not geometry or geometry.name != new_geo: diff --git a/abmatt/converters/triangle.py b/abmatt/converters/triangle.py index 0947b8b..03a0842 100644 --- a/abmatt/converters/triangle.py +++ b/abmatt/converters/triangle.py @@ -35,7 +35,7 @@ def get_weighted_tri_groups(tri_strips, tris): if not added: if len(new_infs) > 10: raise RuntimeError( - 'Too many influences in tri-strip') # Todo, split tristrip if it contains too many infs? + 'Too many influences in tri-strip') if best_group >= 0: g[best_group].append(tri_strip) group_matrices_sets[best_group] |= new_infs @@ -65,10 +65,12 @@ def encode_triangles(triangle_indices, fmt_str, byte_array): class TriangleSet: triangles_in_strips_count = 0 + max_infs = 10 - def __init__(self, np_tris): + def __init__(self, np_tris, has_infs=False): Triangle.edge_map = {} # reset tris = [] + self.has_infs = has_infs for tri in np_tris: verts = [tuple(x) for x in tri] tri_set = {x for x in verts} @@ -110,7 +112,7 @@ def get_tri_strips(self, fmt_str=None): disconnected.append(start) # now try to generate strip while start is not None: - strip = start.create_strip() + strip = start.create_strip(self.has_infs) if strip: if fmt_str: face_point_count += encode_triangle_strip(strip, fmt_str, tristrips) @@ -223,7 +225,7 @@ def reconnect(self): def nextI(currentI): return currentI + 1 if currentI < 2 else 0 - def create_strip(self): + def create_strip(self, inf_set): # self.disconnect() # disconnect edges = self.edges for i in range(3): @@ -235,11 +237,13 @@ def create_strip(self): adjacent.disconnect() right_vert = adjacent.get_opposite_vert(edge) vert_strip = deque((vertices[i - 1], vertices[i], vertices[self.nextI(i)], right_vert)) - return adjacent.extend_right(vert_strip, (vert_strip[2], vert_strip[3])) + if inf_set: + inf_set = {x[0] for x in vert_strip} + return adjacent.extend_right(vert_strip, (vert_strip[2], vert_strip[3]), inf_set) # return self.extend_left(vert_strip, (vert_strip[0], vert_strip[1])) # return vert_strip - def extend_right(self, strip, last_verts): + def extend_right(self, strip, last_verts, inf_set): edge = self.edge_map[last_verts] if not edge.tri_count(): return strip @@ -247,7 +251,11 @@ def extend_right(self, strip, last_verts): adj.disconnect() vert = adj.get_opposite_vert(edge) strip.append(vert) - return adj.extend_right(strip, (last_verts[1], vert)) + if inf_set: + inf_set.add(vert[0]) + if len(inf_set) >= TriangleSet.max_infs: + return strip + return adj.extend_right(strip, (last_verts[1], vert), inf_set) def extend_left(self, strip, last_verts, cull_wrong=False): edge = self.edge_map[last_verts] diff --git a/abmatt/load_config.py b/abmatt/load_config.py index cc1a174..1ba0001 100644 --- a/abmatt/load_config.py +++ b/abmatt/load_config.py @@ -16,6 +16,8 @@ from abmatt.brres.tex0 import Tex0 from abmatt.command import Command, NoSuchFile, Shell from abmatt.config import Config +from abmatt.converters.convert_lib import Converter +from abmatt.converters.geometry import Geometry from abmatt.image_converter import ImgConverterI, ImgConverter @@ -44,7 +46,6 @@ def set_remove_unused(val): def load_config(app_dir, loudness=None, autofix_level=None): conf = Config.get_instance(os.path.join(app_dir, 'config.conf')) tmp_dir = os.path.join(app_dir, 'temp_files') - MaterialLibrary.LIBRARY_PATH = os.path.join(app_dir, 'mat_lib.brres') converter = ImgConverter(tmp_dir) Tex0.converter = converter if not loudness: @@ -54,6 +55,7 @@ def load_config(app_dir, loudness=None, autofix_level=None): AutoFix.set_loudness(loudness) except ValueError: AutoFix.warn('Invalid loudness level {}'.format(loudness)) + AutoFix.set_fix_level(autofix_level, turn_off_fixes) if not len(conf): AutoFix.warn('No configuration detected (etc/abmatt/config.conf).') return @@ -102,9 +104,18 @@ def load_config(app_dir, loudness=None, autofix_level=None): Tex0.set_max_image_size(validInt(conf['max_image_size'], 0, 10000)) except (TypeError, ValueError): pass + try: + Geometry.ENABLE_VERTEX_COLORS = validBool(conf['enable_vertex_colors']) + except ValueError: + pass + Converter.ENCODE_PRESET = conf['encode_preset'] resample = conf['img_resample'] if resample is not None: ImgConverterI.set_resample(resample) + if conf['material_library']: + MaterialLibrary.LIBRARY_PATH = conf.config.get('material_library') + else: + MaterialLibrary.LIBRARY_PATH = os.path.join(app_dir, 'mat_lib.brres') return conf @@ -173,6 +184,19 @@ def hlp(cmd=None): print("{}".format(USAGE)) +def turn_off_fixes(): + SubFile.FORCE_VERSION = False + Brres.REMOVE_UNUSED_TEXTURES = False + Layer.MINFILTER_AUTO = False + Mdl0.DETECT_MODEL_NAME = False + Shader.MAP_ID_AUTO = False + Tex0.RESIZE_TO_POW_TWO = False + Geometry.ENABLE_VERTEX_COLORS = False + Mdl0.RENAME_UNKNOWN_REFS = False + Mdl0.REMOVE_UNKNOWN_REFS = False + Shader.REMOVE_UNUSED_LAYERS = False + + def parse_args(argv, app_dir): interactive = overwrite = debug = False type = "" @@ -180,7 +204,7 @@ def parse_args(argv, app_dir): command = destination = brres_file = command_file = model = value = key = "" autofix = loudness = None name = None - no_normals = no_colors = single_bone = no_uvs = False + no_normals = no_colors = single_bone = no_uvs = moonview = False do_help = False for i in range(len(argv)): if argv[i][0] == '-': @@ -195,7 +219,7 @@ def parse_args(argv, app_dir): "command=", "type=", "key=", "value=", "name=", "brres=", "model=", "file=", "interactive", "loudness=", "debug", - "single-bone", "no-colors", "no-normals", "no-uvs"]) + "single-bone", "no-colors", "no-normals", "no-uvs", "moonview"]) except getopt.GetoptError as e: print(e) print(USAGE) @@ -240,6 +264,8 @@ def parse_args(argv, app_dir): no_colors = True elif opt == '--no-uvs': no_uvs = True + elif opt == '--moonview': + moonview = True else: print("Unknown option '{}'".format(opt)) print(USAGE) @@ -261,6 +287,7 @@ def parse_args(argv, app_dir): config = load_config(app_dir, loudness, autofix) Command.APP_DIR = app_dir Command.DEBUG = debug + Brres.MOONVIEW = moonview cmds = [] if cmd_args: if cmd_args[0] == 'convert': @@ -322,4 +349,4 @@ def parse_args(argv, app_dir): sys.exit(1) if interactive: Shell().cmdloop('Interactive shell started...') - return Command.OPEN_FILES \ No newline at end of file + return Brres.OPEN_FILES \ No newline at end of file diff --git a/brres_files/castleflower1.brres b/brres_files/castleflower1.brres new file mode 100644 index 0000000..8225ad0 Binary files /dev/null and b/brres_files/castleflower1.brres differ diff --git a/brres_files/farm_course.brres b/brres_files/farm_course.brres new file mode 100644 index 0000000..7d82e73 Binary files /dev/null and b/brres_files/farm_course.brres differ diff --git a/brres_files/koopaFigure.brres b/brres_files/koopaFigure.brres new file mode 100644 index 0000000..d35793e Binary files /dev/null and b/brres_files/koopaFigure.brres differ diff --git a/brres_files/kuribo_with_txt.brres b/brres_files/kuribo_with_txt.brres new file mode 100644 index 0000000..22f41e8 Binary files /dev/null and b/brres_files/kuribo_with_txt.brres differ diff --git a/brres_files/pocha.brres b/brres_files/pocha.brres new file mode 100644 index 0000000..6c3f411 Binary files /dev/null and b/brres_files/pocha.brres differ diff --git a/brres_files/simple_multi_bone_single_bind.brres b/brres_files/simple_multi_bone_single_bind.brres index eb7d938..64073fe 100644 Binary files a/brres_files/simple_multi_bone_single_bind.brres and b/brres_files/simple_multi_bone_single_bind.brres differ diff --git a/debug/analyze.py b/debug/analyze.py index 927332e..0965a97 100644 --- a/debug/analyze.py +++ b/debug/analyze.py @@ -92,58 +92,86 @@ def analyze_material(mat): def perform_analysis(brres): - print('Analyzing {}'.format(brres.name)) + s = '' + has_count = 0 + if brres.chr0: + s += ' has chr0' + if brres.shp0: + s += ' has shp0' + has_count += 1 + if brres.clr0: + s += ' has clr0' + has_count += 1 + if brres.unused_pat0: + s += ' has unused pat0' + if brres.unused_srt0: + s += ' has unused srt0' + if brres.scn0: + s += ' has scn0' + has_count += 1 + if has_count: + AutoFix.info(f'{brres.name} {s}') + # AutoFix.info('Analyzing {}'.format(brres.name)) export = False - for model in brres.models: - if len(model.colors) > 0 and model.colors[0].count > 1: - export = True - # for mat in model.materials: - # if len(mat.polygons) > 1: - # print('Mat {} used more than once by {}'.format(mat.name, [x.name for x in mat.polygons])) - # for poly in model.objects: - # - # has_uv_mtx = False - # for x in poly.uv_mtx_indices: - # if x >= 0: - # has_uv_mtx = True - # break - # if has_uv_mtx: - # decode_polygon(poly, decode_mdl0_influences(model)) - if export: - DaeConverter(brres, os.path.join(os.getcwd(), 'tmp', 'tmp.dae'), encode=False).convert() + # for model in brres.models: + # for mat in model.materials: + # if len(mat.polygons) > 1: + # print(f'{brres.name}/{model.name} has multiple polys/mat') + # print('Mat {} used more than once by {}'.format(mat.name, [x.name for x in mat.polygons])) + # + # has_uv_mtx = False + # for x in poly.uv_mtx_indices: + # if x >= 0: + # has_uv_mtx = True + # break + # if has_uv_mtx: + # decode_polygon(poly, decode_mdl0_influences(model)) # for model in brres.models: # for material in model.materials: # analyze_material(material) + # brres.save(os.path.join(os.path.dirname(os.getcwd()), 'tmp', 'tmp.brres'), overwrite=True) -def gather_brres_files(root, brres_files, filter=None): +def gather_files(root, match_filter=None, filter='.brres', exclude=['tmp']): """ Recursively gathers brres files in root :param root: path - :param brres_files: list to gather files in + :param files: list to gather files in """ for file in os.listdir(root): path = os.path.join(root, file) if file.startswith('.'): continue - if os.path.isdir(path): - gather_brres_files(path, brres_files, filter) - elif file.endswith('.brres'): - if filter is None or fnmatch.fnmatch(file, filter): - brres_files.append(path) - return brres_files + if os.path.isdir(path) and file not in exclude: + yield from gather_files(path, match_filter, filter) + elif file.endswith(filter): + if match_filter is None or fnmatch.fnmatch(file, match_filter): + yield path + + +def get_project_root(): + directory = os.getcwd() + while True: + new_dir, name = os.path.split(directory) + if name == 'abmatt': + return directory + directory = new_dir + if not directory: + break if __name__ == '__main__': args = sys.argv[1:] - filter = 'water_course.d\\map_model.brres' + filter = None if not len(args): root = os.getcwd() + if os.path.basename(root) == 'debug': + root = os.path.dirname(root) + os.chdir(root) else: root = args.pop(0) if not os.path.exists(root) or not os.path.isdir(root): print('Invalid root path') sys.exit(1) - files = [] - for x in gather_brres_files(root, files, filter): + for x in gather_files(root, filter): perform_analysis(Brres(x)) diff --git a/debug/test_brres.py b/debug/test_brres.py new file mode 100644 index 0000000..ddfed4a --- /dev/null +++ b/debug/test_brres.py @@ -0,0 +1,91 @@ +import os +import unittest + +from abmatt.autofix import AutoFix +from abmatt.brres import Brres +from abmatt.converters.convert_dae import DaeConverter +from abmatt.converters.convert_obj import ObjConverter +from abmatt import load_config +from debug.analyze import gather_files, get_project_root +from tests.lib import CheckPositions, node_eq + + +class TestBrres(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.root = get_project_root() + AutoFix.set_loudness('0') + + def tearDown(self): + try: + os.remove(self.tmp) + except (AttributeError, FileNotFoundError): + pass + + def _get_tmp(self, ext='.brres'): + self.tmp = os.path.join(self.root, 'tmp' + ext) + return self.tmp + + def test_save_all(self): + tmp = self._get_tmp() + for x in gather_files(self.root): + try: + original = Brres(x) + original.save(tmp, overwrite=True) + new = Brres(tmp) + self.assertEqual(original, new) + except: + print(f'ERROR saving {x}') + raise + + def test_export_import_dae_eq(self): + AutoFix.set_fix_level(0, load_config.turn_off_fixes) + tmp = self._get_tmp('.dae') + tmp_brres = self._get_tmp('.brres') + for x in gather_files(self.root): + try: + converter = DaeConverter(Brres(x), tmp) + for model in converter.brres.models: + converter.save_model(model) + importer = DaeConverter(Brres(tmp_brres, read_file=False), tmp) + importer.load_model() + self.assertTrue(node_eq(model.materials, importer.mdl0.materials)) + except: + print(f'ERROR converting {x}') + raise + + def test_export_all_obj(self): + tmp = self._get_tmp('.obj') + for x in gather_files(self.root): + try: + converter = ObjConverter(Brres(x), tmp) + for model in converter.brres.models: + converter.save_model(model) + except: + print(f'ERROR exporting {x}') + raise + + def test_import_all_dae(self): + tmp = self._get_tmp() + for x in gather_files(self.root, filter='.dae'): + try: + converter = DaeConverter(tmp, x) + converter.load_model() + converter.brres.save(overwrite=True) + except: + print(f'ERROR importing {x}') + raise + + def test_import_all_obj(self): + tmp = self._get_tmp() + for x in gather_files(self.root, filter='.obj'): + try: + converter = ObjConverter(tmp, x) + converter.load_model() + converter.brres.save(overwrite=True) + except: + print(f'ERROR importing {x}') + raise + +if __name__ == '__main__': + unittest.main() diff --git a/etc/abmatt/config.conf b/etc/abmatt/config.conf index ce11277..27ebf3d 100644 --- a/etc/abmatt/config.conf +++ b/etc/abmatt/config.conf @@ -1,8 +1,19 @@ +# ---------------------------------------------------------------------------------------------- +# ABMatt Configuration file +# IMPORTANT: re-installing abmatt may overwrite your configurations. Remember to back them up! +# ---------------------------------------------------------------------------------------------- + # General -loudness=3 # verbosity between 0-5 +loudness=3 # verbosity between 0-5 max_brres_files=10 # maximum files open (command line only) +# Encoding +encode_preset= # preset to run upon loading a model +encode_preset_only_on_new=True # only run the preset if not replacing model and no previous json settings detected +enable_vertex_colors=True # enables vertex colors if colors are loaded in geometry + # Materials +material_library= # brres file path, materials with matching names are replaced when creating model default_material_color=200,200,200,255 # RGBA color used for materials with no map layers # Textures diff --git a/test_files/presets.txt b/test_files/presets.txt new file mode 100644 index 0000000..bbcc41f --- /dev/null +++ b/test_files/presets.txt @@ -0,0 +1,5 @@ + +[load_preset] +set cullmode:None for cull_none* +set blend:True for * + diff --git a/test_files/simple/Wax_02.jpg b/test_files/simple/Wax_02.jpg index 3a559d3..732486e 100644 Binary files a/test_files/simple/Wax_02.jpg and b/test_files/simple/Wax_02.jpg differ diff --git a/test_files/simple/Wax_02.png b/test_files/simple/Wax_02.png index 5cd6455..214212d 100644 Binary files a/test_files/simple/Wax_02.png and b/test_files/simple/Wax_02.png differ diff --git a/test_files/skp_simple_Textures/Wax_02.PNG b/test_files/skp_simple_Textures/Wax_02.PNG index b631b3a..3394fc2 100644 Binary files a/test_files/skp_simple_Textures/Wax_02.PNG and b/test_files/skp_simple_Textures/Wax_02.PNG differ diff --git a/tests/brres/mdl0/material/test_material.py b/tests/brres/mdl0/material/test_material.py index 8d21145..7e80d85 100644 --- a/tests/brres/mdl0/material/test_material.py +++ b/tests/brres/mdl0/material/test_material.py @@ -2,13 +2,14 @@ from copy import deepcopy from abmatt.brres.mdl0.material.material import Material from tests import lib +from tests.lib import node_eq class TestMaterial(lib.TestBeginner): def test_deepcopy(self): original_mats = self.brres.models[0].materials new_mats = deepcopy(original_mats) - self.assertEqual(original_mats, new_mats) + self.assertTrue(node_eq(original_mats, new_mats)) if __name__ == '__main__': diff --git a/tests/converters/test_convert_dae.py b/tests/converters/test_convert_dae.py index e4c5d8c..66058e8 100644 --- a/tests/converters/test_convert_dae.py +++ b/tests/converters/test_convert_dae.py @@ -1,6 +1,7 @@ import os from abmatt.brres import Brres +from abmatt.command import load_preset_file from abmatt.converters.convert_dae import DaeConverter from tests.lib import AbmattTest, CheckPositions @@ -113,7 +114,7 @@ def test_save_multi_bone_single_bind(self): original = converter.mdl0 converter = DaeConverter(self._get_tmp('.brres'), fname) converter.load_model() - # converter.brres.save(overwrite=True) + converter.brres.save(overwrite=True) self.assertTrue(CheckPositions().model_equal(original, converter.mdl0, 0.01, 0.001)) def test_save_multi_bone_as_single_bone(self): @@ -166,4 +167,17 @@ def test_save_and_load_uv_mtx(self): expected.extend([False] * 5) for i in range(8): self.assertEqual(expected[i], poly.has_uv_matrix(i)) + + def test_save_multi_influence_per_face_index(self): + DaeConverter(self._get_brres('koopaFigure.brres'), self._get_tmp('.dae')).save_model() + + def test_save_and_load_polygons_bound_to_single_material(self): + converter = DaeConverter(self._get_brres('castleflower1.brres'), self._get_tmp('.dae')) + converter.save_model() + original = converter.mdl0 + new = DaeConverter(self._get_tmp('.brres'), converter.mdl_file).load_model() + self.assertTrue(CheckPositions.positions_equal(original.vertices, new.vertices, rtol=0.1, atol=0.01)) + self.assertTrue(CheckPositions.bones_equal([x.linked_bone for x in original.objects], + [x.linked_bone for x in new.objects])) + # endregion save_model diff --git a/tests/integration/test_save_brres.py b/tests/integration/test_save_brres.py new file mode 100644 index 0000000..58786af --- /dev/null +++ b/tests/integration/test_save_brres.py @@ -0,0 +1,11 @@ +from abmatt.brres import Brres +from tests.integration.lib import IntegTest +from tests.lib import AbmattTest + + +class TestSaveBrres(IntegTest, AbmattTest): + def test_save_moonview(self): + self.assertTrue(self._abmatt('-b brres_files/simple.brres -d test_files/tmp.brres -o --moonview')) + b = Brres(self._get_tmp('.brres', False)) + self.assertFalse(b.check_moonview()) + self.assertFalse(b.MOONVIEW) \ No newline at end of file diff --git a/tests/lib.py b/tests/lib.py index 706b231..b6b6d87 100644 --- a/tests/lib.py +++ b/tests/lib.py @@ -7,6 +7,10 @@ from abmatt.brres import Brres from abmatt.brres.lib import matching from abmatt.brres.lib.matching import it_eq +from abmatt.brres.mdl0 import Mdl0 +from abmatt.brres.mdl0.material.material import Material +from abmatt.brres.mdl0.polygon import Polygon +from abmatt.brres.mdl0.shader import Shader def get_base_path(): @@ -132,10 +136,11 @@ def __pre_process(vertices1, vertices2, group_type): # Try to sort by name v1 = sorted(vertices1, key=lambda x: x.name) v2 = sorted(vertices2, key=lambda x: x.name) - if not matching.fuzzy_match(v1[0].name, v2): # no match! try matching shapes + if not matching.fuzzy_match(v1[0].name, v2): # no match! try matching shapes # print('Groups dont have matching names!') v1 = sorted(vertices1, key=lambda x: x.count) v2 = sorted(vertices2, key=lambda x: x.count) + return v1, v2, mismatched_len @staticmethod @@ -167,10 +172,10 @@ def colors_equal(colr1, colr2, rtol=1.e-2, atol=1.e-3): @staticmethod def model_equal(mdl1, mdl2, rtol=1.e-2, atol=1.e-3): return CheckPositions.bones_equal(mdl1.bones, mdl2.bones, rtol, atol) \ - and CheckPositions.positions_equal(mdl1.vertices, mdl2.vertices, rtol, atol) \ - and CheckPositions.positions_equal(mdl1.uvs, mdl2.uvs, rtol, atol) \ - and CheckPositions.positions_equal(mdl1.normals, mdl2.normals, rtol, atol) \ - and CheckPositions.colors_equal(mdl1.colors, mdl2.colors, rtol, atol) + and CheckPositions.positions_equal(mdl1.vertices, mdl2.vertices, rtol, atol) \ + and CheckPositions.positions_equal(mdl1.uvs, mdl2.uvs, rtol, atol) \ + and CheckPositions.positions_equal(mdl1.normals, mdl2.normals, rtol, atol) \ + and CheckPositions.colors_equal(mdl1.colors, mdl2.colors, rtol, atol) @staticmethod def bones_equal(bone_list1, bone_list2, rtol=1.e-2, atol=1.e-3): @@ -222,3 +227,204 @@ def positions_equal(vertices1, vertices2, rtol=1.e-2, atol=1.e-3): if current_err: print('{} and {} mismatch'.format(vertices1[k].name, vertices2[k].name)) return not err + + +class CheckAttr: + def __init__(self, attr, sub_check_attr=None): + """ + :param attr the attribute to check + :param sub_check_attr list of CheckAttr to use as a subcheck + """ + self.attr = attr + self.sub_check_attr = sub_check_attr + + +class CheckNodeEq: + + def __init__(self, node, other, check_attr=[], trace=True, parent_node=None): + self.node = node + self.other = other + self.result = it_eq(node, other) + self.my_item_diff = self.their_item_diff = self.attr = None + self.err_message = '' + self.check_attr = check_attr + self.parent_node = parent_node if parent_node else self + if trace: + self.err_trace = self.trace() + + def __str__(self): + if self.result: + return self.err_message + return self.get_err() + + def get_err(self): + mine, theirs = self.err_trace + return self.parent_node.err_message + f'\nMINE: {mine}\nTHEIRS: {theirs}\n' + + def trace(self, my_trace='', other_trace=''): + if self.result: + self.err_message = 'Items equal, errors found' + return None, None + my_trace = str(self.node) if not my_trace else my_trace + '->' + str(self.node) + other_trace = str(self.other) if not other_trace else other_trace + '->' + str(self.other) + if self.check_attr: + for check in self.check_attr: + if type(check) == str: + check = CheckAttr(check) + self.parent_node.attr = check.attr + mine = getattr(self.node, check.attr) + theirs = getattr(self.other, check.attr) + if mine is None or theirs is None: + if mine is not theirs: + self.parent_node.err_message = f'Null {check.attr}! ({mine} != {theirs})' + break + continue + if type(mine) not in (list, dict, set, tuple): + mine_sub, their_sub = CheckNodeEq(mine, theirs, + check.sub_check_attr, + False, + self.parent_node).trace(my_trace, other_trace) + if mine_sub or their_sub: + return mine_sub, their_sub + else: # iterable? + if len(mine) != len(theirs): + self.parent_node.err_message = f'{check.attr} has mismatching lengths!' + break + if type(mine) == dict: + for key in mine: + if key not in theirs: + self.parent_node.err_message = f'{check.attr} missing item {key} in theirs!' + break + mine_sub, their_sub = CheckNodeEq(mine[key], theirs[key], + check.sub_check_attr, + False, + self.parent_node).trace(my_trace, other_trace) + if mine_sub or their_sub: + return mine_sub, their_sub + else: + for i in range(len(mine)): + mine_sub, their_sub = CheckNodeEq(mine[i], theirs[i], + check.sub_check_attr, + False, + self.parent_node).trace(my_trace, other_trace) + if mine_sub or their_sub: + return mine_sub, their_sub + self.parent_node.attr = None + self.parent_node.my_item_diff = self.node + self.parent_node.their_item_diff = self.other + self.parent_node.err_message += f'\nERR checking {self.parent_node.attr}\n({self.node} != {self.other})' + return my_trace, other_trace + + +class CheckMaterialAttr(CheckAttr): + class CheckLayerAttr(CheckAttr): + def __init__(self): + sub_attrs = ['enable', 'scale', 'rotation', 'translation', 'scn0_light_ref', 'scn0_camera_ref', + 'map_mode', 'vwrap', 'uwrap', 'minfilter', 'magfilter', 'lod_bias', 'max_anisotrophy', + 'texel_interpolate', 'clamp_bias', 'normalize', 'projection', 'inputform', 'type', + 'coordinates', 'emboss_source', 'emboss_light'] + super().__init__('layers', sub_attrs) + + class CheckPat0Attr(CheckAttr): + def __init__(self): + super().__init__('pat0') + + class CheckSrt0Attr(CheckAttr): + def __init__(self): + super().__init__('srt0') + + class CheckShaderAttr(CheckAttr): + def __init__(self): + sub_attrs = ['swap_table', 'ind_tex_maps', 'ind_tex_coords', + CheckMaterialAttr.CheckStageAttr()] + super().__init__('shader', sub_attrs) + + class CheckStageAttr(CheckAttr): + def __init__(self): + sub_attrs = ['map_id', 'coord_id', 'texture_swap_sel', 'raster_color', 'raster_swap_sel', + 'constant', 'sel_a', 'sel_b', 'sel_c', 'sel_d', 'bias', 'oper', 'clamp', 'scale', 'dest', + 'constant_a', 'sel_a_a', 'sel_b_a', 'sel_c_a', 'sel_d_a', + 'bias_a', 'oper_a', 'clamp_a', 'scale_a', 'dest_a', + 'ind_stage', 'ind_format', 'ind_alpha', 'ind_bias', 'ind_matrix', + 'ind_s_wrap', 'ind_t_wrap', 'ind_use_prev', 'ind_unmodify_lod'] + super().__init__('stages', sub_attrs) + + def __init__(self): + sub_attrs = [self.CheckShaderAttr(), self.CheckLayerAttr(), self.CheckPat0Attr(), self.CheckSrt0Attr(), + CheckAttr('colors'), CheckAttr('constant_colors'), + CheckAttr('indirect_matrices'), CheckAttr('lightChannels'), + CheckAttr('blend_dest'), CheckAttr('blend_source'), CheckAttr('blend_logic'), + CheckAttr('blend_subtract'), CheckAttr('blend_update_alpha'), CheckAttr('blend_update_color'), + CheckAttr('blend_dither'), CheckAttr('blend_logic_enabled'), CheckAttr('blend_enabled'), + CheckAttr('depth_function'), CheckAttr('depth_update'), CheckAttr('depth_test'), + CheckAttr('logic'), CheckAttr('comp1'), CheckAttr('comp0'), CheckAttr('ref1'), CheckAttr('ref0'), + CheckAttr('constant_alpha_enabled'), CheckAttr('constant_alpha'), + CheckAttr('indirect_matrices')] + super().__init__('material', sub_attrs) + + +class CheckPolysAttr(CheckAttr): + def __init__(self): + sub_attrs = [CheckMaterialAttr()] + super().__init__('objects', sub_attrs) + + +class CheckModelsAttr(CheckAttr): + def __init__(self): + sub_attrs = [CheckPolysAttr()] + super().__init__('models', sub_attrs) + + +class CheckBrresAttr(CheckAttr): + def __init__(self): + sub_attrs = [CheckModelsAttr(), CheckTextureMap(), CheckAttr('unused_pat0'), + CheckAttr('unused_srt0'), + CheckAttr('chr0'), + CheckAttr('scn0'), + CheckAttr('shp0'), + CheckAttr('clr0')] + super().__init__('brres', sub_attrs) + + +class CheckTextureMap(CheckAttr): + def __init__(self): + super().__init__('texture_map') + + +CHECK_ATTR_MAP = { + Brres: CheckBrresAttr, + Mdl0: CheckModelsAttr, + Polygon: CheckPolysAttr, + Material: CheckMaterialAttr, +} + + +def brres_eq(mine, other): + check = CheckNodeEq(mine, other, [CheckModelsAttr(), + CheckTextureMap(), + CheckAttr('unused_pat0'), + CheckAttr('unused_srt0'), + CheckAttr('chr0'), + CheckAttr('scn0'), + CheckAttr('shp0'), + CheckAttr('clr0')]) + if not check.result: + print(check) + return check.result + + +def node_eq(mine, other): + if type(mine) in (list, tuple): + if len(mine) != len(other): + print('Mismatching lengths!') + else: + for i in range(len(mine)): + if not node_eq(mine[i], other[i]): + return False + else: + check_attr = CHECK_ATTR_MAP[type(mine)] + check = CheckNodeEq(mine, other, check_attr().sub_check_attr) + if not check.result: + print(check) + return check.result + return True \ No newline at end of file diff --git a/tests/mocks/__init__.py b/tests/mocks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/mocks/mock_autofix.py b/tests/mocks/mock_autofix.py new file mode 100644 index 0000000..fda5c72 --- /dev/null +++ b/tests/mocks/mock_autofix.py @@ -0,0 +1,17 @@ +from abmatt.autofix import MessageReceiver + + +class AutoFixMock(MessageReceiver): + def info(self, message): + self.infos.append(message) + + def warn(self, message): + self.warnings.append(message) + + def error(self, message): + self.errors.append(message) + + def __init__(self): + self.infos = [] + self.warnings = [] + self.errors = [] diff --git a/tests/test_brres.py b/tests/test_brres.py index 9259b6f..d4a0a60 100644 --- a/tests/test_brres.py +++ b/tests/test_brres.py @@ -3,7 +3,7 @@ import unittest from abmatt.brres import Brres -from tests.lib import AbmattTest +from tests.lib import AbmattTest, node_eq class TestBrresLoadsCorrectly(AbmattTest): @@ -23,8 +23,6 @@ def setUpClass(cls): def test_correct_amount_of_objects(self): self.assertEqual(len(self.brres.models), 1) self.assertEqual(len(self.brres.textures), 29) - self.assertEqual(len(self.brres.pat0), 1) - self.assertEqual(len(self.brres.srt0), 1) def test_materials_equal(self): self.assertTrue(len(self.original_model.materials) > 0) @@ -36,5 +34,33 @@ def test_polygons_equal(self): self.assertTrue(original_len > 0) self.assertTrue(original_len == len(self.test_model.objects)) + # Currently unsupported + # def test_load_and_save_brres_with_text_file(self): + # brres = self._get_brres('kuribo_with_txt.brres') + # brres.save(self._get_tmp('.brres'), overwrite=True) + + +class TestBrresSaveEqual(AbmattTest): + def _test_save_eq(self, brres): + tmp = self._get_tmp('.brres') + brres.save(tmp, overwrite=True) + return node_eq(Brres(tmp), brres) + + def test_save_beginner(self): # has pat0 and srt0 + self.assertTrue(self._test_save_eq(self._get_brres('beginner_course.brres'))) + + def test_save_farm(self): # has scn0 + self.assertTrue(self._test_save_eq(self._get_brres('farm_course.brres'))) + + def test_save_pocha(self): # has clr0 and chr0 + self.assertTrue(self._test_save_eq(self._get_brres('pocha.brres'))) + + def test_save_flagb2(self): # has shp0 + self.assertTrue(self._test_save_eq(self._get_brres('FlagB2.brres'))) + + def test_save_with_unknown_files(self): # various txt files + self.assertTrue(self._test_save_eq(self._get_brres('kuribo_with_txt.brres'))) + + if __name__ == '__main__': unittest.main() diff --git a/tests/test_command.py b/tests/test_command.py new file mode 100644 index 0000000..2334b03 --- /dev/null +++ b/tests/test_command.py @@ -0,0 +1,8 @@ +from abmatt.command import Command +from tests.lib import AbmattTest + + +class TestCommand(AbmattTest): + def test_load(self): + self.assertTrue(Command('load {}'.format(self._get_test_fname('presets.txt'))).run_cmd()) + self.assertTrue(Command.PRESETS.get('load_preset')) diff --git a/tests/test_preset.py b/tests/test_preset.py new file mode 100644 index 0000000..09cbf38 --- /dev/null +++ b/tests/test_preset.py @@ -0,0 +1,24 @@ +import os + +from abmatt.command import load_preset_file +from abmatt.converters.convert_dae import DaeConverter +from abmatt.converters.convert_lib import Converter +from tests.lib import AbmattTest + + +class TestPreset(AbmattTest): + @classmethod + def setUpClass(cls): + load_preset_file(os.path.join(cls.base_path, 'test_files')) + Converter.ENCODE_PRESET = 'load_preset' + + def test_convert_loads_presets(self): + converter = DaeConverter(self._get_tmp('.brres'), self._get_test_fname('3ds_simple.DAE'), encode=True).convert() + self.assertTrue(all([mat.blend_enabled for mat in converter.mdl0.materials])) + + def test_replace_doesnt_load_preset(self): + Converter.ENCODE_PRESET_ON_NEW = True + converter = DaeConverter(self._get_brres('simple.brres'), self._get_test_fname('3ds_simple.DAE'), + encode=True).convert() + converter.brres.save(self._get_tmp('.brres')) # save so we don't overwrite accidentally + self.assertFalse(all([x.blend_enabled for x in converter.mdl0.materials]))