From 152b22feb942e3b2736a88d29598f4ae354b5c26 Mon Sep 17 00:00:00 2001 From: William Herald Snyder Date: Tue, 20 Oct 2020 14:28:53 -0400 Subject: [PATCH] MSH scene reading draft, no blender conversion yet --- addons/io_scene_swbf_msh/__init__.py | 37 ++- addons/io_scene_swbf_msh/msh_reader.py | 118 ++++++++ addons/io_scene_swbf_msh/msh_scene_read.py | 308 +++++++++++++++++++++ addons/io_scene_swbf_msh/msh_utilities.py | 12 + 4 files changed, 474 insertions(+), 1 deletion(-) create mode 100644 addons/io_scene_swbf_msh/msh_reader.py create mode 100644 addons/io_scene_swbf_msh/msh_scene_read.py diff --git a/addons/io_scene_swbf_msh/__init__.py b/addons/io_scene_swbf_msh/__init__.py index 200b48f..6165685 100644 --- a/addons/io_scene_swbf_msh/__init__.py +++ b/addons/io_scene_swbf_msh/__init__.py @@ -53,11 +53,12 @@ if "bpy" in locals(): # End of stuff taken from glTF import bpy -from bpy_extras.io_utils import ExportHelper +from bpy_extras.io_utils import ExportHelper, ImportHelper from bpy.props import BoolProperty, EnumProperty from bpy.types import Operator from .msh_scene import create_scene from .msh_scene_save import save_scene +from .msh_scene_read import read_scene from .msh_material_properties import * class ExportMSH(Operator, ExportHelper): @@ -108,16 +109,47 @@ class ExportMSH(Operator, ExportHelper): return {'FINISHED'} + # Only needed if you want to add into a dynamic menu def menu_func_export(self, context): self.layout.operator(ExportMSH.bl_idname, text="SWBF msh (.msh)") + + + + +class ImportMSH(Operator, ImportHelper): + """ Import an SWBF .msh file. """ + + bl_idname = "swbf_msh.import" + bl_label = "Import SWBF .msh File" + filename_ext = ".msh" + + filter_glob: StringProperty( + default="*.msh", + options={'HIDDEN'}, + maxlen=255, # Max internal buffer length, longer would be clamped. + ) + + def execute(self, context): + with open(self.filepath, 'rb') as input_file: + read_scene(input_file) + return {'FINISHED'} + +def menu_func_import(self, context): + self.layout.operator(ImportMSH.bl_idname, text="SWBF msh (.msh)") + + + + def register(): bpy.utils.register_class(MaterialProperties) bpy.utils.register_class(MaterialPropertiesPanel) bpy.utils.register_class(ExportMSH) + bpy.utils.register_class(ImportMSH) bpy.types.TOPBAR_MT_file_export.append(menu_func_export) + bpy.types.TOPBAR_MT_file_import.append(menu_func_import) bpy.types.Material.swbf_msh = bpy.props.PointerProperty(type=MaterialProperties) @@ -125,7 +157,10 @@ def unregister(): bpy.utils.unregister_class(MaterialProperties) bpy.utils.unregister_class(MaterialPropertiesPanel) bpy.utils.unregister_class(ExportMSH) + bpy.utils.unregister_class(ImportMSH) bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) + bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) + if __name__ == "__main__": register() diff --git a/addons/io_scene_swbf_msh/msh_reader.py b/addons/io_scene_swbf_msh/msh_reader.py new file mode 100644 index 0000000..c652683 --- /dev/null +++ b/addons/io_scene_swbf_msh/msh_reader.py @@ -0,0 +1,118 @@ + +import io +import struct + +class Reader: + def __init__(self, file, chunk_id: str, parent=None, indent=0): + self.file = file + self.size: int = 0 + self.size_pos = None + self.parent = parent + self.header = chunk_id + self.indent = " " * indent + + + def __enter__(self): + self.size_pos = self.file.tell() + self.file.seek(4,1) #skip header, will add check later + self.size = self.read_u32() + + padding_length = 4 - (self.size % 4) if self.size % 4 > 0 else 0 + self.end_pos = self.size_pos + padding_length + self.size + 8 + + print(self.indent + "Begin " + self.header + ", Size: " + str(self.size) + ", Pos: " + str(self.size_pos)) + + return self + + + def __exit__(self, exc_type, exc_value, traceback): + if self.size > self.MAX_SIZE: + raise OverflowError(f".msh file overflowed max size. size = {self.size} MAX_SIZE = {self.MAX_SIZE}") + + print(self.indent + "End " + self.header) + self.file.seek(self.end_pos) + + + + def read_bytes(self,num_bytes): + return self.file.read(num_bytes) + + + def read_string(self): + last_byte = self.read_bytes(1) + result = b'' + while last_byte[0] != 0x0: + result += last_byte + last_byte = self.read_bytes(1) + + return result.decode("utf-8") + + def read_i8(self, num=1): + buf = self.read_bytes(num) + result = struct.unpack(f"<{num}b", buf) + return result[0] if num == 1 else result + + def read_u8(self, num=1): + buf = self.read_bytes(num) + result = struct.unpack(f"<{num}B", buf) + return result[0] if num == 1 else result + + def read_i16(self, num=1): + buf = self.read_bytes(num * 2) + result = struct.unpack(f"<{num}h", buf) + return result[0] if num == 1 else result + + def read_u16(self, num=1): + buf = self.read_bytes(num * 2) + result = struct.unpack(f"<{num}H", buf) + return result[0] if num == 1 else result + + def read_i32(self, num=1): + buf = self.read_bytes(num * 4) + result = struct.unpack(f"<{num}i", buf) + return result[0] if num == 1 else result + + def read_u32(self, num=1): + buf = self.read_bytes(num * 4) + result = struct.unpack(f"<{num}I", buf) + return result[0] if num == 1 else result + + def read_f32(self, num=1): + buf = self.read_bytes(num * 4) + result = struct.unpack(f"<{num}f", buf) + return result[0] if num == 1 else result + + + + def read_child(self, child_id: str): + child = Reader(self.file, chunk_id=child_id, parent=self, indent=int(len(self.indent) / 2) + 1) + return child + + + def skip_bytes(self,num): + self.file.seek(num,1) + + + def peak_next_header(self): + buf = self.read_bytes(4); + self.file.seek(-4,1) + return buf.decode("utf-8") + + + def could_have_child(self): + return self.end_pos - self.file.tell() >= 8 + + + + MAX_SIZE: int = 2147483647 - 8 + +''' +with open("/Users/will/Desktop/spacedoortest/spa1_prop_impdoor.msh", "rb") as tst_stream: + with Reader(tst_stream, "HEDR") as hedr: + print(hedr.peak_next_header()) +''' + + + + + diff --git a/addons/io_scene_swbf_msh/msh_scene_read.py b/addons/io_scene_swbf_msh/msh_scene_read.py new file mode 100644 index 0000000..cae2d15 --- /dev/null +++ b/addons/io_scene_swbf_msh/msh_scene_read.py @@ -0,0 +1,308 @@ +""" Contains functions for saving a Scene to a .msh file. """ + +from itertools import islice +from typing import Dict +from .msh_scene import Scene +from .msh_model import * +from .msh_material import * +from .msh_reader import Reader +from .msh_utilities import * + +from .crc import * + +def read_scene(input_file) -> Scene: + + scene = Scene() + scene.models = [] + + with Reader(file=input_file, chunk_id="HEDR") as hedr: + with hedr.read_child("MSH2") as msh2: + + with msh2.read_child("SINF") as sinf: + pass + + materials_list: List[str] = [] + + with msh2.read_child("MATL") as matl: + materials_list = _read_matl_and_get_materials_list(matl) + + while ("MODL" in msh2.peak_next_header()): + with msh2.read_child("MODL") as modl: + scene.models.append(_read_modl(modl, materials_list)) + + mats_dict = {} + for i,mat in enumerate(materials_list): + mats_dict["Material" + str(i)] = mat + + scene.materials = mats_dict + + #with hedr.read_child("ANM2") as anm2: #simple for now + # for anim in scene.anims: + # _write_anm2(anm2, anim) + + #with hedr.read_child("CL1L"): + # pass + + + + return scene + + +def _read_matl_and_get_materials_list(matl: Reader) -> List[str]: + materials_list: List[str] = [] + + num_mats = matl.read_u32() + + for _ in range(num_mats): + with matl.read_child("MATD") as matd: + materials_list.append(_read_matd(matd)) + + return materials_list + + + +def _read_matd(matd: Reader) -> Material: + + mat = Material() + + while matd.could_have_child(): + + next_header = matd.peak_next_header() + + if "NAME" in next_header: + with matd.read_child("NAME") as name: + mat.name = name.read_string() + print(matd.indent + "Got a new material: " + mat.name) + + elif "DATA" in next_header: + with matd.read_child("DATA") as data: + data.read_f32(4) # Diffuse Color (Seams to get ignored by modelmunge) + mat.specular_color = data.read_f32(4) + data.read_f32(4) # Ambient Color (Seams to get ignored by modelmunge and Zero(?)) + data.read_f32() # Specular Exponent/Decay (Gets ignored by RedEngine in SWBFII for all known materials) + + elif "ATRB" in next_header: + with matd.read_child("ATRB") as atrb: + mat.flags = atrb.read_u8() + mat.rendertype = atrb.read_u8() + mat.data = atrb.read_u8(2) + + elif "TX0D" in next_header: + with matd.read_child("TX0D") as tx0d: + mat.texture0 = tx0d.read_string() + + elif "TX1D" in next_header: + with matd.read_child("TX1D") as tx1d: + mat.texture1 = tx1d.read_string() + + elif "TX2D" in next_header: + with matd.read_child("TX2D") as tx2d: + mat.texture2 = tx2d.read_string() + + elif "TX3D" in next_header: + with matd.read_child("TX3D") as tx3d: + mat.texture3 = tx3d.read_string() + + else: + matd.skip_bytes(4) + + return mat + + +def _read_modl(modl: Reader, materials_list: List[str]) -> Model: + + model = Model() + + while modl.could_have_child(): + + next_header = modl.peak_next_header() + + if "MTYP" in next_header: + with modl.read_child("MTYP") as mtyp: + model.model_type = mtyp.read_u32() + + elif "MNDX" in next_header: + with modl.read_child("MNDX") as mndx: + pass + + elif "NAME" in next_header: + with modl.read_child("NAME") as name: + model.name = name.read_string() + print(modl.indent + "New model: " + model.name) + + elif "PRNT" in next_header: + with modl.read_child("PRNT") as prnt: + model.parent = prnt.read_string() + + elif "FLGS" in next_header: + with modl.read_child("FLGS") as flgs: + model.hidden = flgs.read_u32() + + elif "TRAN" in next_header: + with modl.read_child("TRAN") as tran: + model.transform = _read_tran(tran) + + elif "GEOM" in next_header: + model.geometry = [] + with modl.read_child("GEOM") as geom: + + next_header_modl = geom.peak_next_header() + + if "SEGM" in next_header_modl: + with geom.read_child("SEGM") as segm: + model.geometry.append(_read_segm(segm, materials_list)) + ''' + if model.model_type == ModelType.SKIN: + with modl.read_child("ENVL") as envl: + envl.write_u32(len(scene.models)) + for i in range(len(scene.models)): + envl.write_u32(i) + ''' + elif "SWCI" in next_header: + prim = CollisionPrimitive() + with modl.read_child("SWCI") as swci: + prim.shape.value = swci.read_u32() + prim.radius = swci.read_f32() + prim.height = swci.read_f32() + prim.length = swci.read_f32() + model.collisionprimitive = prim + + else: + with modl.read_child("NULL") as unknown: + pass + + + +def _read_tran(tran: Reader) -> ModelTransform: + + xform = ModelTransform() + + tran.skip_bytes(4 * 3) #ignore scale + xform.rotation = Quaternion(tran.read_f32(4)) + xform.position = Vector(tran.read_f32(3)) + + return xform + + + +def _read_segm(segm: Reader, materials_list: List[str]) -> GeometrySegment: + + geometry_seg = GeometrySegment() + + while segm.could_have_child(): + + next_header = segm.peak_next_header() + + if "MATI" in next_header: + with segm.read_child("MATI") as mati: + geometry_seg.material_name = materials_list[mati.read_u32()] + + elif "POSL" in next_header: + with segm.read_child("POSL") as posl: + num_positions = posl.read_u32() + + for _ in range(num_positions): + geometry_seg.positions.append(Vector(posl.read_f32(3))) + + elif "NRML" in next_header: + with segm.read_child("NRML") as nrml: + num_normals = nrml.read_u32() + + for _ in range(num_positions): + geometry_seg.normals.append(Vector(nrml.read_f32(3))) + + elif "WGHT" in next_header: + geometry_seg.weights = [] + + with segm.read_child("WGHT") as wght: + num_boneweights = wght.read_u32() + + for _ in range(num_boneweights): + geometry_seg.weights.append((wght.read_u32(), wght.read_f32())) + + elif "CLRL" in next_header: + geometry_seg.colors = [] + + with segm.read_child("CLRL") as clrl: + num_colors = clrl.read_u32() + + for _ in range(num_colors): + geometry_seg.colors += unpack_color(clrl.read_u32()) + + elif "UV0L" in next_header: + with segm.read_child("UV0L") as uv0l: + num_texcoords = uv0l.read_u32() + + for _ in range(num_texcoords): + geometry_seg.texcoords.append(Vector(uv0l.read_f32(2))) + + elif "NDXL" in next_header: + with segm.read_child("NDXL") as ndxl: + num_polygons = ndxl.read_u32() + + for _ in range(num_polygons): + polygon = ndxl.read_u16(ndxl.read_u16()) + geometry_seg.polygons.append(polygon) + + elif "NDXT" in next_header: + with segm.read_child("NDXT") as ndxt: + num_tris = ndxt.read_u32() + + for _ in range(num_tris): + geometry_seg.triangles.append(ndxt.read_u16(3)) + + elif "STRP" in next_header: + with segm.read_child("STRP") as strp: + pass + + if segm.read_u16 != 0: #trailing 0 bug + segm.skip_bytes(-2) + + else: + with segm.read_child("NULL") as unknown: + pass + + return geometry_seg + + + +''' + + +def _write_anm2(anm2: Writer, anim: Animation): + + with anm2.read_child("CYCL") as cycl: + + cycl.write_u32(1) + cycl.write_string(anim.name) + + for _ in range(63 - len(anim.name)): + cycl.write_u8(0) + + cycl.write_f32(10.0) #test framerate + cycl.write_u32(0) #what does play style refer to? + cycl.write_u32(0, 20) #first frame indices + + + with anm2.read_child("KFR3") as kfr3: + + kfr3.write_u32(len(anim.bone_transforms.keys())) + + for boneName in anim.bone_transforms.keys(): + kfr3.write_u32(crc(boneName)) + kfr3.write_u32(0) #what is keyframe type? + + kfr3.write_u32(21, 21) #basic testing + + for i, xform in enumerate(anim.bone_transforms[boneName]): + kfr3.write_u32(i) + kfr3.write_f32(xform.translation.x, xform.translation.y, xform.translation.z) + + for i, xform in enumerate(anim.bone_transforms[boneName]): + kfr3.write_u32(i) + kfr3.write_f32(xform.rotation.x, xform.rotation.y, xform.rotation.z, xform.rotation.w) + + +''' + + diff --git a/addons/io_scene_swbf_msh/msh_utilities.py b/addons/io_scene_swbf_msh/msh_utilities.py index 7715383..701933f 100644 --- a/addons/io_scene_swbf_msh/msh_utilities.py +++ b/addons/io_scene_swbf_msh/msh_utilities.py @@ -1,6 +1,7 @@ """ Misc utilities. """ from mathutils import Vector +from typing import List def add_vec(l: Vector, r: Vector) -> Vector: return Vector(v0 + v1 for v0, v1 in zip(l, r)) @@ -29,3 +30,14 @@ def pack_color(color) -> int: packed |= (int(color[3] * 255.0 + 0.5) << 24) return packed + +def unpack_color(color: int) -> List[float]: + + mask = int(0x000000ff) + + r = (color & (mask << 16)) / 255.0 + g = (color & (mask << 8)) / 255.0 + b = (color & mask) / 255.0 + a = (color & (mask << 24)) / 255.0 + + return [r,g,b,a]