From 152b22feb942e3b2736a88d29598f4ae354b5c26 Mon Sep 17 00:00:00 2001 From: William Herald Snyder Date: Tue, 20 Oct 2020 14:28:53 -0400 Subject: [PATCH 1/7] 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] From 1cc6a8d08d04f8e0d808567380862627a7baf148 Mon Sep 17 00:00:00 2001 From: William Herald Snyder Date: Mon, 26 Oct 2020 10:20:33 -0400 Subject: [PATCH 2/7] Non-import changes removed --- addons/io_scene_swbf_msh/msh_anim_gather.py | 57 ---------------- addons/io_scene_swbf_msh/msh_model.py | 15 +---- addons/io_scene_swbf_msh/msh_model_gather.py | 64 ++---------------- addons/io_scene_swbf_msh/msh_scene.py | 21 +----- addons/io_scene_swbf_msh/msh_scene_save.py | 70 ++------------------ 5 files changed, 14 insertions(+), 213 deletions(-) delete mode 100644 addons/io_scene_swbf_msh/msh_anim_gather.py diff --git a/addons/io_scene_swbf_msh/msh_anim_gather.py b/addons/io_scene_swbf_msh/msh_anim_gather.py deleted file mode 100644 index 631839f..0000000 --- a/addons/io_scene_swbf_msh/msh_anim_gather.py +++ /dev/null @@ -1,57 +0,0 @@ -""" Gathers the Blender objects from the current scene and returns them as a list of - Model objects. """ - -import bpy -import math -from enum import Enum -from typing import List, Set, Dict, Tuple -from itertools import zip_longest -from .msh_model import * -from .msh_model_utilities import * -from .msh_utilities import * -from .msh_model_gather import * - - -def gather_animdata(armature: bpy.types.Armature) -> List[Animation]: - - anim_data = Animation(); - - action = armature.animation_data.action - - framerange = action.frame_range - increment = (framerange.y - framerange.x) / 20.0 - offset = framerange.x; - - anim_data.bone_transforms[armature.parent.name] = [] - for bone in armature.data.bones: - anim_data.bone_transforms[bone.name] = [] - - for frame in range(21): - frame_time = offset + frame * increment - bpy.context.scene.frame_set(frame_time) - - anim_data.bone_transforms[armature.parent.name].append(ModelTransform()) #for testing - - for bone in armature.pose.bones: - xform = ModelTransform() - - - vt = convert_vector_space(bone.location); - - xform.translation = Vector((vt.x * -1.0, vt.y, vt.z)) - xform.rotation = convert_rotation_space(bone.rotation_quaternion) - - ''' - xform.translation = bone.location - xform.rotation = bone.rotation_quaternion - - anim_data.bone_transforms[bone.name].append(xform) - ''' - - return [anim_data] - - - - - - diff --git a/addons/io_scene_swbf_msh/msh_model.py b/addons/io_scene_swbf_msh/msh_model.py index 31f04a0..589a274 100644 --- a/addons/io_scene_swbf_msh/msh_model.py +++ b/addons/io_scene_swbf_msh/msh_model.py @@ -2,7 +2,7 @@ saved to a .msh file. """ from dataclasses import dataclass, field -from typing import List, Dict, Tuple +from typing import List from enum import Enum from mathutils import Vector, Quaternion @@ -37,8 +37,7 @@ class GeometrySegment: normals: List[Vector] = field(default_factory=list) colors: List[List[float]] = None texcoords: List[Vector] = field(default_factory=list) - - weights: List[Tuple[int, float]] = None + # TODO: Skin support. polygons: List[List[int]] = field(default_factory=list) triangles: List[List[int]] = field(default_factory=list) @@ -66,13 +65,3 @@ class Model: geometry: List[GeometrySegment] = None collisionprimitive: CollisionPrimitive = None - - vgroups_to_modelnames_map : Dict[int, str] = None - -@dataclass -class Animation: - """ Class representing 'CYCL' + 'KFR3' sections in a .msh file """ - - name: str = "open" - bone_transforms: Dict[str, List[ModelTransform]] = field(default_factory=dict) - diff --git a/addons/io_scene_swbf_msh/msh_model_gather.py b/addons/io_scene_swbf_msh/msh_model_gather.py index 7b6e7fd..a55a5ef 100644 --- a/addons/io_scene_swbf_msh/msh_model_gather.py +++ b/addons/io_scene_swbf_msh/msh_model_gather.py @@ -4,13 +4,13 @@ import bpy import math from enum import Enum -from typing import List, Set, Dict, Tuple, Set +from typing import List, Set, Dict, Tuple from itertools import zip_longest from .msh_model import * from .msh_model_utilities import * from .msh_utilities import * -SKIPPED_OBJECT_TYPES = {"LATTICE", "CAMERA", "LIGHT", "SPEAKER", "LIGHT_PROBE", "ARMATURE"} +SKIPPED_OBJECT_TYPES = {"LATTICE", "CAMERA", "LIGHT", "SPEAKER", "LIGHT_PROBE"} MESH_OBJECT_TYPES = {"MESH", "CURVE", "SURFACE", "META", "FONT", "GPENCIL"} MAX_MSH_VERTEX_COUNT = 32767 @@ -22,7 +22,6 @@ def gather_models(apply_modifiers: bool, export_target: str) -> List[Model]: parents = create_parents_set() models_list: List[Model] = [] - skeleton: bpy.types.Armature = None for uneval_obj in select_objects(export_target): if uneval_obj.type in SKIPPED_OBJECT_TYPES and uneval_obj.name not in parents: @@ -45,24 +44,11 @@ def gather_models(apply_modifiers: bool, export_target: str) -> List[Model]: model.transform.translation = convert_vector_space(local_translation) if obj.parent is not None: - if obj.parent.type == "ARMATURE": - skeleton = obj.parent - - parent_bone_name = obj.parent_bone - if parent_bone_name == "": - model.parent = obj.parent.parent - else: - model.parent = parent_bone_name - - if model.model_type == ModelType.SKIN: - model.vgroups_to_modelnames_map = {} - for i, vgroup in enumerate(obj.vertex_groups): - vgroups_to_modelnames_map[i] = vgroup.name - + model.parent = obj.parent.name if obj.type in MESH_OBJECT_TYPES: - mesh = obj.to_mesh() - model.geometry = create_mesh_geometry(mesh, model.model_type == ModelType.SKIN) + mesh = obj.to_mesh() + model.geometry = create_mesh_geometry(mesh) obj.to_mesh_clear() _, _, world_scale = obj.matrix_world.decompose() @@ -80,30 +66,6 @@ def gather_models(apply_modifiers: bool, export_target: str) -> List[Model]: models_list.append(model) - - for bone in skeleton.data.bones: - - model = Model() - model.name = bone.name - model.model_type = ModelType.NULL - model.hidden = False - - local_translation, local_rotation, _ = bone.matrix_local.decompose() - model.transform.rotation = convert_rotation_space(local_rotation) - model.transform.translation = convert_vector_space(local_translation) - - parent_name = bone.parent - if parent_name is not None: - model.parent = parent_name - else: - if skeleton.parent is not None: - model.parent = skeleton.parent.name - else: - model.parent = None - - models_list.append(model) - - return models_list def create_parents_set() -> Set[str]: @@ -118,7 +80,7 @@ def create_parents_set() -> Set[str]: return parents -def create_mesh_geometry(mesh: bpy.types.Mesh, is_skinned : bool) -> List[GeometrySegment]: +def create_mesh_geometry(mesh: bpy.types.Mesh) -> List[GeometrySegment]: """ Creates a list of GeometrySegment objects from a Blender mesh. Does NOT create triangle strips in the GeometrySegment however. """ @@ -192,16 +154,6 @@ def create_mesh_geometry(mesh: bpy.types.Mesh, is_skinned : bool) -> List[Geomet segment.positions.append(convert_vector_space(mesh.vertices[vertex_index].co)) segment.normals.append(convert_vector_space(vertex_normal)) - if is_skinned: - for i,grp_el in mesh.vertices[vertex_index].groups: - segment.weights.append(tuple(grp_el.group, grp_el.weight)) - print("Adding weight to group {grp_el.group} of value {grp_el.weight}") - if i > 3: #will have to look into aramture/skin settings for limiting envolopes to 4 weights... - break - while i < 3: - segment.weights.append(tuple(0,0.0)) - i+=1 - if mesh.uv_layers.active is None: segment.texcoords.append(Vector((0.0, 0.0))) else: @@ -212,7 +164,6 @@ def create_mesh_geometry(mesh: bpy.types.Mesh, is_skinned : bool) -> List[Geomet return new_index - for tri in mesh.loop_triangles: polygons[tri.material_index].add(tri.polygon_index) segments[tri.material_index].triangles.append([ @@ -230,8 +181,7 @@ def create_mesh_geometry(mesh: bpy.types.Mesh, is_skinned : bool) -> List[Geomet def get_model_type(obj: bpy.types.Object) -> ModelType: """ Get the ModelType for a Blender object. """ - if obj.parent_type == "ARMATURE": - return ModelType.SKIN + # TODO: Skinning support, etc if obj.type in MESH_OBJECT_TYPES: return ModelType.STATIC diff --git a/addons/io_scene_swbf_msh/msh_scene.py b/addons/io_scene_swbf_msh/msh_scene.py index 7fa5930..6c6f7fd 100644 --- a/addons/io_scene_swbf_msh/msh_scene.py +++ b/addons/io_scene_swbf_msh/msh_scene.py @@ -6,7 +6,7 @@ from typing import List, Dict from copy import copy import bpy from mathutils import Vector -from .msh_model import Model, Animation +from .msh_model import Model from .msh_model_gather import gather_models from .msh_model_utilities import sort_by_parent, has_multiple_root_models, reparent_model_roots, get_model_world_matrix from .msh_model_triangle_strips import create_models_triangle_strips @@ -14,7 +14,6 @@ from .msh_material import * from .msh_material_gather import gather_materials from .msh_material_utilities import remove_unused_materials from .msh_utilities import * -from .msh_anim_gather import * @dataclass class SceneAABB: @@ -44,8 +43,6 @@ class Scene: name: str = "Scene" materials: Dict[str, Material] = field(default_factory=dict) models: List[Model] = field(default_factory=list) - anims: List[Animation] = field(default_factory=list) - def create_scene(generate_triangle_strips: bool, apply_modifiers: bool, export_target: str) -> Scene: """ Create a msh Scene from the active Blender scene. """ @@ -70,24 +67,8 @@ def create_scene(generate_triangle_strips: bool, apply_modifiers: bool, export_t if has_multiple_root_models(scene.models): scene.models = reparent_model_roots(scene.models) - - #now that we've collected all models, we should remap WGHT indices... - names_to_indices = {} - for i,model in enumerate(scene.models): - names_to_indices[model.name] = i; - - for model in scene.models: - if model.model_type == ModelType.SKIN: - for segment in model.geometry: - for i in range(len(segment.weights)): - vgroup_index = segment.weights[i][0] - segment.weights[i][0] = names_to_indices[model.vgroups_to_modelnames_map[vgroup_index]] - - scene.materials = remove_unused_materials(scene.materials, scene.models) - #scene.anims = gather_animdata(bpy.context.scene.objects["Armature"]) - return scene def create_scene_aabb(scene: Scene) -> SceneAABB: diff --git a/addons/io_scene_swbf_msh/msh_scene_save.py b/addons/io_scene_swbf_msh/msh_scene_save.py index 2fd0945..71914a0 100644 --- a/addons/io_scene_swbf_msh/msh_scene_save.py +++ b/addons/io_scene_swbf_msh/msh_scene_save.py @@ -8,8 +8,6 @@ from .msh_material import * from .msh_writer import Writer from .msh_utilities import * -from .crc import * - def save_scene(output_file, scene: Scene): """ Saves scene to the supplied file. """ @@ -26,11 +24,7 @@ def save_scene(output_file, scene: Scene): for index, model in enumerate(scene.models): with msh2.create_child("MODL") as modl: - _write_modl(modl, model, index, material_index, scene) - - #with hedr.create_child("ANM2") as anm2: #simple for now - # for anim in scene.anims: - # _write_anm2(anm2, anim) + _write_modl(modl, model, index, material_index) with hedr.create_child("CL1L"): pass @@ -40,8 +34,8 @@ def _write_sinf(sinf: Writer, scene: Scene): name.write_string(scene.name) with sinf.create_child("FRAM") as fram: - fram.write_i32(0, 20) #test values - fram.write_f32(10.0) #test values + fram.write_i32(0, 1) + fram.write_f32(29.97003) with sinf.create_child("BBOX") as bbox: aabb = create_scene_aabb(scene) @@ -103,7 +97,7 @@ def _write_matd(matd: Writer, material_name: str, material: Material): with matd.create_child("TX3D") as tx3d: tx3d.write_string(material.texture3) -def _write_modl(modl: Writer, model: Model, index: int, material_index: Dict[str, int], scene: Scene): +def _write_modl(modl: Writer, model: Model, index: int, material_index: Dict[str, int]): with modl.create_child("MTYP") as mtyp: mtyp.write_u32(model.model_type.value) @@ -129,12 +123,6 @@ def _write_modl(modl: Writer, model: Model, index: int, material_index: Dict[str for segment in model.geometry: with geom.create_child("SEGM") as segm: _write_segm(segm, segment, material_index) - - if model.model_type == ModelType.SKIN: - with modl.create_child("ENVL") as envl: - envl.write_u32(len(scene.models)) - for i in range(len(scene.models)): - envl.write_u32(i) if model.collisionprimitive is not None: with modl.create_child("SWCI") as swci: @@ -165,13 +153,6 @@ def _write_segm(segm: Writer, segment: GeometrySegment, material_index: Dict[str for normal in segment.normals: nrml.write_f32(normal.x, normal.y, normal.z) - if segment.weights is not None: - with segm.create_child("WGHT") as wght: - wght.write_u32(len(segment.weights) / 4) - for weight in segment.weights: - wght.write_u32(weight[0]) - wght.write_f32(weight[1]) - if segment.colors is not None: with segm.create_child("CLRL") as clrl: clrl.write_u32(len(segment.colors)) @@ -208,46 +189,3 @@ def _write_segm(segm: Writer, segment: GeometrySegment, material_index: Dict[str for index in islice(strip, 2, len(strip)): strp.write_u16(index) - - -def _write_anm2(anm2: Writer, anim: Animation): - - with anm2.create_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.create_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) - - - - - - - - - From b8afa1ed105fb29e721a934bad2ddd1bf9b6c4f9 Mon Sep 17 00:00:00 2001 From: William Herald Snyder Date: Fri, 30 Oct 2020 16:59:54 -0400 Subject: [PATCH 3/7] Models with basic materials importing correctly --- addons/io_scene_swbf_msh/__init__.py | 3 +- addons/io_scene_swbf_msh/msh_material.py | 5 +- addons/io_scene_swbf_msh/msh_scene_read.py | 130 +++++++-------------- addons/io_scene_swbf_msh/msh_to_blend.py | 124 ++++++++++++++++++++ 4 files changed, 171 insertions(+), 91 deletions(-) create mode 100644 addons/io_scene_swbf_msh/msh_to_blend.py diff --git a/addons/io_scene_swbf_msh/__init__.py b/addons/io_scene_swbf_msh/__init__.py index 6165685..e432118 100644 --- a/addons/io_scene_swbf_msh/__init__.py +++ b/addons/io_scene_swbf_msh/__init__.py @@ -60,6 +60,7 @@ 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 * +from .msh_to_blend import * class ExportMSH(Operator, ExportHelper): """ Export the current scene as a SWBF .msh file. """ @@ -133,7 +134,7 @@ class ImportMSH(Operator, ImportHelper): def execute(self, context): with open(self.filepath, 'rb') as input_file: - read_scene(input_file) + extract_scene(read_scene(input_file)) return {'FINISHED'} def menu_func_import(self, context): diff --git a/addons/io_scene_swbf_msh/msh_material.py b/addons/io_scene_swbf_msh/msh_material.py index ed7867b..560e3bb 100644 --- a/addons/io_scene_swbf_msh/msh_material.py +++ b/addons/io_scene_swbf_msh/msh_material.py @@ -32,8 +32,9 @@ class MaterialFlags(Flag): @dataclass class Material: - """ Data class representing a .msh material. - Intended to be stored in a dictionary so name is missing. """ + """ Data class representing a .msh material.""" + + name: str = "" specular_color: Color = Color((1.0, 1.0, 1.0)) rendertype: Rendertype = Rendertype.NORMAL diff --git a/addons/io_scene_swbf_msh/msh_scene_read.py b/addons/io_scene_swbf_msh/msh_scene_read.py index cae2d15..9faa78f 100644 --- a/addons/io_scene_swbf_msh/msh_scene_read.py +++ b/addons/io_scene_swbf_msh/msh_scene_read.py @@ -1,4 +1,4 @@ -""" Contains functions for saving a Scene to a .msh file. """ +""" Contains functions for extracting a scene from a .msh file""" from itertools import islice from typing import Dict @@ -14,42 +14,52 @@ def read_scene(input_file) -> Scene: scene = Scene() scene.models = [] + scene.materials = {} 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 + while hedr.could_have_child(): - materials_list: List[str] = [] + next_header = hedr.peak_next_header() - with msh2.read_child("MATL") as matl: - materials_list = _read_matl_and_get_materials_list(matl) + if "MSH2" in next_header: - while ("MODL" in msh2.peak_next_header()): - with msh2.read_child("MODL") as modl: - scene.models.append(_read_modl(modl, materials_list)) + with hedr.read_child("MSH2") as msh2: - mats_dict = {} - for i,mat in enumerate(materials_list): - mats_dict["Material" + str(i)] = mat + materials_list = [] - scene.materials = mats_dict + while (msh2.could_have_child()): - #with hedr.read_child("ANM2") as anm2: #simple for now - # for anim in scene.anims: - # _write_anm2(anm2, anim) + next_header = msh2.peak_next_header() - #with hedr.read_child("CL1L"): - # pass + if "SINF" in next_header: + with msh2.read_child("SINF") as sinf: + pass + elif "MATL" in next_header: + with msh2.read_child("MATL") as matl: + materials_list += _read_matl_and_get_materials_list(matl) + for i,mat in enumerate(materials_list): + scene.materials[mat.name] = mat + elif "MODL" in next_header: + while ("MODL" in msh2.peak_next_header()): + with msh2.read_child("MODL") as modl: + scene.models.append(_read_modl(modl, materials_list)) + + else: + with hedr.read_child("NULL") as unknown: + pass + + else: + with hedr.read_child("NULL") as unknown: + pass return scene -def _read_matl_and_get_materials_list(matl: Reader) -> List[str]: - materials_list: List[str] = [] +def _read_matl_and_get_materials_list(matl: Reader) -> List[Material]: + materials_list: List[Material] = [] num_mats = matl.read_u32() @@ -72,7 +82,6 @@ def _read_matd(matd: Reader) -> Material: 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: @@ -109,7 +118,7 @@ def _read_matd(matd: Reader) -> Material: return mat -def _read_modl(modl: Reader, materials_list: List[str]) -> Model: +def _read_modl(modl: Reader, materials_list: List[Material]) -> Model: model = Model() @@ -119,7 +128,7 @@ def _read_modl(modl: Reader, materials_list: List[str]) -> Model: if "MTYP" in next_header: with modl.read_child("MTYP") as mtyp: - model.model_type = mtyp.read_u32() + model.model_type = ModelType(mtyp.read_u32()) elif "MNDX" in next_header: with modl.read_child("MNDX") as mndx: @@ -128,7 +137,6 @@ def _read_modl(modl: Reader, materials_list: List[str]) -> Model: 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: @@ -151,13 +159,7 @@ def _read_modl(modl: Reader, materials_list: List[str]) -> Model: 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: @@ -171,6 +173,7 @@ def _read_modl(modl: Reader, materials_list: List[str]) -> Model: with modl.read_child("NULL") as unknown: pass + return model def _read_tran(tran: Reader) -> ModelTransform: @@ -178,14 +181,15 @@ 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)) + + rot = tran.read_f32(4) + xform.rotation = Quaternion((rot[3], rot[0], rot[1], rot[2])) + xform.translation = Vector(tran.read_f32(3)) return xform - -def _read_segm(segm: Reader, materials_list: List[str]) -> GeometrySegment: +def _read_segm(segm: Reader, materials_list: List[Material]) -> GeometrySegment: geometry_seg = GeometrySegment() @@ -195,7 +199,7 @@ def _read_segm(segm: Reader, materials_list: List[str]) -> GeometrySegment: if "MATI" in next_header: with segm.read_child("MATI") as mati: - geometry_seg.material_name = materials_list[mati.read_u32()] + geometry_seg.material_name = materials_list[mati.read_u32()].name elif "POSL" in next_header: with segm.read_child("POSL") as posl: @@ -211,15 +215,6 @@ def _read_segm(segm: Reader, materials_list: List[str]) -> GeometrySegment: 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 = [] @@ -255,7 +250,7 @@ def _read_segm(segm: Reader, materials_list: List[str]) -> GeometrySegment: with segm.read_child("STRP") as strp: pass - if segm.read_u16 != 0: #trailing 0 bug + if segm.read_u16 != 0: #trailing 0 bug https://schlechtwetterfront.github.io/ze_filetypes/msh.html#STRP segm.skip_bytes(-2) else: @@ -265,44 +260,3 @@ def _read_segm(segm: Reader, materials_list: List[str]) -> GeometrySegment: 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_to_blend.py b/addons/io_scene_swbf_msh/msh_to_blend.py new file mode 100644 index 0000000..a5c3495 --- /dev/null +++ b/addons/io_scene_swbf_msh/msh_to_blend.py @@ -0,0 +1,124 @@ +""" Gathers the Blender objects from the current scene and returns them as a list of + Model objects. """ + +import bpy +import bmesh +import math +from enum import Enum +from typing import List, Set, Dict, Tuple +from itertools import zip_longest +from .msh_scene import Scene +from .msh_model import * +from .msh_model_utilities import * +from .msh_utilities import * +from .msh_model_gather import * + + + +def extract_models(models, materials_map): + + model_map = {} + + for model in sort_by_parent(models): + + if model.model_type != ModelType.STATIC: + new_obj = bpy.data.objects.new(model.name, None) + new_obj.empty_display_size = 1 + new_obj.empty_display_type = 'PLAIN_AXES' + + else: + new_mesh = bpy.data.meshes.new(model.name) + verts = [] + faces = [] + offset = 0 + + mat_name = "" + + for i,seg in enumerate(model.geometry): + + if i == 0: + mat_name = seg.material_name + + verts += [tuple(convert_vector_space(v)) for v in seg.positions] + faces += [tuple([ind + offset for ind in tri]) for tri in seg.triangles] + + offset += len(seg.positions) + + new_mesh.from_pydata(verts, [], faces) + new_mesh.update() + new_mesh.validate() + + + edit_mesh = bmesh.new() + edit_mesh.from_mesh(new_mesh) + + uvlayer = edit_mesh.loops.layers.uv.verify() + + for edit_mesh_face in edit_mesh.faces: + mesh_face = faces[edit_mesh_face.index] + + for i,loop in enumerate(edit_mesh_face.loops): + texcoord = seg.texcoords[mesh_face[i]] + loop[uvlayer].uv = tuple([texcoord.x, texcoord.y]) + + edit_mesh.to_mesh(new_mesh) + edit_mesh.free() + + + new_obj = bpy.data.objects.new(new_mesh.name, new_mesh) + + ''' + Assign Materials - will do per segment later... + ''' + + if mat_name: + material = materials_map[mat_name] + + if new_obj.data.materials: + new_obj.data.materials[0] = material + else: + new_obj.data.materials.append(material) + + + + model_map[model.name] = new_obj + + if model.parent: + new_obj.parent = model_map[model.parent] + + new_obj.location = convert_vector_space(model.transform.translation) + new_obj.rotation_mode = "QUATERNION" + new_obj.rotation_quaternion = convert_rotation_space(model.transform.rotation) + + bpy.context.collection.objects.link(new_obj) + + + +def extract_materials(scene: Scene) -> Dict[str,bpy.types.Material]: + + extracted_materials = {} + + for material_name in scene.materials.keys(): + + new_mat = bpy.data.materials.new(name=material_name) + new_mat.use_nodes = True + bsdf = new_mat.node_tree.nodes["Principled BSDF"] + texImage = new_mat.node_tree.nodes.new('ShaderNodeTexImage') + texImage.image = bpy.data.images.load("/Users/will/Desktop/grad.png") + new_mat.node_tree.links.new(bsdf.inputs['Base Color'], texImage.outputs['Color']) + + extracted_materials[material_name] = new_mat + + return extracted_materials + + + +def extract_scene(scene: Scene): + + matmap = extract_materials(scene) + extract_models(scene.models, matmap) + + + + + From 706c32431d450bfebf42031dd826e496dca8032f Mon Sep 17 00:00:00 2001 From: William Herald Snyder Date: Sun, 1 Nov 2020 11:48:07 -0500 Subject: [PATCH 4/7] msh_reader refined, more chunks implemented --- addons/io_scene_swbf_msh/msh_model.py | 1 + addons/io_scene_swbf_msh/msh_reader.py | 23 ++---- addons/io_scene_swbf_msh/msh_scene.py | 2 + addons/io_scene_swbf_msh/msh_scene_read.py | 82 +++++++++++++--------- addons/io_scene_swbf_msh/msh_to_blend.py | 29 ++++---- 5 files changed, 71 insertions(+), 66 deletions(-) diff --git a/addons/io_scene_swbf_msh/msh_model.py b/addons/io_scene_swbf_msh/msh_model.py index 589a274..4f1e29b 100644 --- a/addons/io_scene_swbf_msh/msh_model.py +++ b/addons/io_scene_swbf_msh/msh_model.py @@ -12,6 +12,7 @@ class ModelType(Enum): CLOTH = 2 BONE = 3 STATIC = 4 + SHADOWVOLUME = 6 class CollisionPrimitiveShape(Enum): SPHERE = 0 diff --git a/addons/io_scene_swbf_msh/msh_reader.py b/addons/io_scene_swbf_msh/msh_reader.py index c652683..3203955 100644 --- a/addons/io_scene_swbf_msh/msh_reader.py +++ b/addons/io_scene_swbf_msh/msh_reader.py @@ -3,18 +3,17 @@ import io import struct class Reader: - def __init__(self, file, chunk_id: str, parent=None, indent=0): + def __init__(self, file, 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 + self.indent = " " * indent #for print debugging def __enter__(self): self.size_pos = self.file.tell() - self.file.seek(4,1) #skip header, will add check later + self.header = self.read_bytes(4).decode("utf-8") self.size = self.read_u32() padding_length = 4 - (self.size % 4) if self.size % 4 > 0 else 0 @@ -84,8 +83,8 @@ class Reader: - def read_child(self, child_id: str): - child = Reader(self.file, chunk_id=child_id, parent=self, indent=int(len(self.indent) / 2) + 1) + def read_child(self): + child = Reader(self.file, parent=self, indent=int(len(self.indent) / 2) + 1) return child @@ -103,16 +102,4 @@ class Reader: 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.py b/addons/io_scene_swbf_msh/msh_scene.py index 6c6f7fd..53266be 100644 --- a/addons/io_scene_swbf_msh/msh_scene.py +++ b/addons/io_scene_swbf_msh/msh_scene.py @@ -44,6 +44,8 @@ class Scene: materials: Dict[str, Material] = field(default_factory=dict) models: List[Model] = field(default_factory=list) + skeleton: List[int] = field(default_factory=list) + def create_scene(generate_triangle_strips: bool, apply_modifiers: bool, export_target: str) -> Scene: """ Create a msh Scene from the active Blender scene. """ diff --git a/addons/io_scene_swbf_msh/msh_scene_read.py b/addons/io_scene_swbf_msh/msh_scene_read.py index 9faa78f..36657f8 100644 --- a/addons/io_scene_swbf_msh/msh_scene_read.py +++ b/addons/io_scene_swbf_msh/msh_scene_read.py @@ -16,7 +16,7 @@ def read_scene(input_file) -> Scene: scene.models = [] scene.materials = {} - with Reader(file=input_file, chunk_id="HEDR") as hedr: + with Reader(file=input_file) as hedr: while hedr.could_have_child(): @@ -24,7 +24,7 @@ def read_scene(input_file) -> Scene: if "MSH2" in next_header: - with hedr.read_child("MSH2") as msh2: + with hedr.read_child() as msh2: materials_list = [] @@ -33,26 +33,37 @@ def read_scene(input_file) -> Scene: next_header = msh2.peak_next_header() if "SINF" in next_header: - with msh2.read_child("SINF") as sinf: + with msh2.read_child() as sinf: pass elif "MATL" in next_header: - with msh2.read_child("MATL") as matl: + with msh2.read_child() as matl: materials_list += _read_matl_and_get_materials_list(matl) for i,mat in enumerate(materials_list): scene.materials[mat.name] = mat elif "MODL" in next_header: while ("MODL" in msh2.peak_next_header()): - with msh2.read_child("MODL") as modl: + with msh2.read_child() as modl: scene.models.append(_read_modl(modl, materials_list)) else: - with hedr.read_child("NULL") as unknown: + with hedr.read_child() as unknown: pass + elif "SKL2" in next_header: + with hedr.read_child() as skl2: + num_bones = skl2.read_u32() + scene.skeleton = [skl2.read_u32(5)[0] for i in range(num_bones)] + print("Skeleton models: ") + for crc_hash in scene.skeleton: + for model in scene.models: + if crc_hash == crc(model.name): + print("\t" + model.name + " with type: " + str(model.model_type)) + + else: - with hedr.read_child("NULL") as unknown: + with hedr.read_child() as unknown: pass return scene @@ -64,7 +75,7 @@ def _read_matl_and_get_materials_list(matl: Reader) -> List[Material]: num_mats = matl.read_u32() for _ in range(num_mats): - with matl.read_child("MATD") as matd: + with matl.read_child() as matd: materials_list.append(_read_matd(matd)) return materials_list @@ -80,36 +91,36 @@ def _read_matd(matd: Reader) -> Material: next_header = matd.peak_next_header() if "NAME" in next_header: - with matd.read_child("NAME") as name: + with matd.read_child() as name: mat.name = name.read_string() elif "DATA" in next_header: - with matd.read_child("DATA") as data: + with matd.read_child() 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: + with matd.read_child() 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: + with matd.read_child() as tx0d: mat.texture0 = tx0d.read_string() elif "TX1D" in next_header: - with matd.read_child("TX1D") as tx1d: + with matd.read_child() as tx1d: mat.texture1 = tx1d.read_string() elif "TX2D" in next_header: - with matd.read_child("TX2D") as tx2d: + with matd.read_child() as tx2d: mat.texture2 = tx2d.read_string() elif "TX3D" in next_header: - with matd.read_child("TX3D") as tx3d: + with matd.read_child() as tx3d: mat.texture3 = tx3d.read_string() else: @@ -127,50 +138,50 @@ def _read_modl(modl: Reader, materials_list: List[Material]) -> Model: next_header = modl.peak_next_header() if "MTYP" in next_header: - with modl.read_child("MTYP") as mtyp: + with modl.read_child() as mtyp: model.model_type = ModelType(mtyp.read_u32()) elif "MNDX" in next_header: - with modl.read_child("MNDX") as mndx: + with modl.read_child() as mndx: pass elif "NAME" in next_header: - with modl.read_child("NAME") as name: + with modl.read_child() as name: model.name = name.read_string() elif "PRNT" in next_header: - with modl.read_child("PRNT") as prnt: + with modl.read_child() as prnt: model.parent = prnt.read_string() elif "FLGS" in next_header: - with modl.read_child("FLGS") as flgs: + with modl.read_child() as flgs: model.hidden = flgs.read_u32() elif "TRAN" in next_header: - with modl.read_child("TRAN") as tran: + with modl.read_child() as tran: model.transform = _read_tran(tran) elif "GEOM" in next_header: model.geometry = [] - with modl.read_child("GEOM") as geom: + with modl.read_child() as geom: next_header_modl = geom.peak_next_header() if "SEGM" in next_header_modl: - with geom.read_child("SEGM") as segm: + with geom.read_child() as segm: model.geometry.append(_read_segm(segm, materials_list)) elif "SWCI" in next_header: prim = CollisionPrimitive() - with modl.read_child("SWCI") as swci: - prim.shape.value = swci.read_u32() + with modl.read_child() as swci: + prim.shape = CollisionPrimitiveShape(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: + with modl.read_child() as unknown: pass return model @@ -198,18 +209,18 @@ def _read_segm(segm: Reader, materials_list: List[Material]) -> GeometrySegment: next_header = segm.peak_next_header() if "MATI" in next_header: - with segm.read_child("MATI") as mati: + with segm.read_child() as mati: geometry_seg.material_name = materials_list[mati.read_u32()].name elif "POSL" in next_header: - with segm.read_child("POSL") as posl: + with segm.read_child() 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: + with segm.read_child() as nrml: num_normals = nrml.read_u32() for _ in range(num_positions): @@ -218,21 +229,21 @@ def _read_segm(segm: Reader, materials_list: List[Material]) -> GeometrySegment: elif "CLRL" in next_header: geometry_seg.colors = [] - with segm.read_child("CLRL") as clrl: + with segm.read_child() 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: + with segm.read_child() 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: + with segm.read_child() as ndxl: num_polygons = ndxl.read_u32() for _ in range(num_polygons): @@ -240,23 +251,24 @@ def _read_segm(segm: Reader, materials_list: List[Material]) -> GeometrySegment: geometry_seg.polygons.append(polygon) elif "NDXT" in next_header: - with segm.read_child("NDXT") as ndxt: + with segm.read_child() 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: + with segm.read_child() as strp: pass if segm.read_u16 != 0: #trailing 0 bug https://schlechtwetterfront.github.io/ze_filetypes/msh.html#STRP segm.skip_bytes(-2) else: - with segm.read_child("NULL") as unknown: + with segm.read_child() as unknown: pass return geometry_seg + diff --git a/addons/io_scene_swbf_msh/msh_to_blend.py b/addons/io_scene_swbf_msh/msh_to_blend.py index a5c3495..ac3e933 100644 --- a/addons/io_scene_swbf_msh/msh_to_blend.py +++ b/addons/io_scene_swbf_msh/msh_to_blend.py @@ -12,21 +12,18 @@ from .msh_model import * from .msh_model_utilities import * from .msh_utilities import * from .msh_model_gather import * +from .crc import * -def extract_models(models, materials_map): +def extract_models(scene: Scene, materials_map): model_map = {} - for model in sort_by_parent(models): + for model in sort_by_parent(scene.models): - if model.model_type != ModelType.STATIC: - new_obj = bpy.data.objects.new(model.name, None) - new_obj.empty_display_size = 1 - new_obj.empty_display_type = 'PLAIN_AXES' - - else: + if model.model_type == ModelType.STATIC: + new_mesh = bpy.data.meshes.new(model.name) verts = [] faces = [] @@ -67,10 +64,10 @@ def extract_models(models, materials_map): new_obj = bpy.data.objects.new(new_mesh.name, new_mesh) + ''' Assign Materials - will do per segment later... ''' - if mat_name: material = materials_map[mat_name] @@ -78,6 +75,12 @@ def extract_models(models, materials_map): new_obj.data.materials[0] = material else: new_obj.data.materials.append(material) + + else: + + new_obj = bpy.data.objects.new(model.name, None) + new_obj.empty_display_size = 1 + new_obj.empty_display_type = 'PLAIN_AXES' @@ -104,7 +107,7 @@ def extract_materials(scene: Scene) -> Dict[str,bpy.types.Material]: new_mat.use_nodes = True bsdf = new_mat.node_tree.nodes["Principled BSDF"] texImage = new_mat.node_tree.nodes.new('ShaderNodeTexImage') - texImage.image = bpy.data.images.load("/Users/will/Desktop/grad.png") + texImage.image = bpy.data.images.load("/Users/will/Desktop/grad.jpg") new_mat.node_tree.links.new(bsdf.inputs['Base Color'], texImage.outputs['Color']) extracted_materials[material_name] = new_mat @@ -114,9 +117,9 @@ def extract_materials(scene: Scene) -> Dict[str,bpy.types.Material]: def extract_scene(scene: Scene): - - matmap = extract_materials(scene) - extract_models(scene.models, matmap) + return None + #matmap = extract_materials(scene) + #extract_models(scene, matmap) From 049803f750d96d802533663a12e5b673919b79c0 Mon Sep 17 00:00:00 2001 From: William Herald Snyder Date: Sun, 1 Nov 2020 18:11:26 -0500 Subject: [PATCH 5/7] Reader class simplified --- addons/io_scene_swbf_msh/__init__.py | 3 +- addons/io_scene_swbf_msh/msh_model.py | 4 +- addons/io_scene_swbf_msh/msh_scene_read.py | 84 ++++++++++++++++++---- addons/io_scene_swbf_msh/msh_to_blend.py | 44 ++++++++---- 4 files changed, 104 insertions(+), 31 deletions(-) diff --git a/addons/io_scene_swbf_msh/__init__.py b/addons/io_scene_swbf_msh/__init__.py index e432118..cab7f18 100644 --- a/addons/io_scene_swbf_msh/__init__.py +++ b/addons/io_scene_swbf_msh/__init__.py @@ -62,6 +62,7 @@ from .msh_scene_read import read_scene from .msh_material_properties import * from .msh_to_blend import * + class ExportMSH(Operator, ExportHelper): """ Export the current scene as a SWBF .msh file. """ @@ -134,7 +135,7 @@ class ImportMSH(Operator, ImportHelper): def execute(self, context): with open(self.filepath, 'rb') as input_file: - extract_scene(read_scene(input_file)) + extract_scene(self.filepath, read_scene(input_file)) return {'FINISHED'} def menu_func_import(self, context): diff --git a/addons/io_scene_swbf_msh/msh_model.py b/addons/io_scene_swbf_msh/msh_model.py index 4f1e29b..c23c5c5 100644 --- a/addons/io_scene_swbf_msh/msh_model.py +++ b/addons/io_scene_swbf_msh/msh_model.py @@ -16,9 +16,9 @@ class ModelType(Enum): class CollisionPrimitiveShape(Enum): SPHERE = 0 - # ELLIPSOID = 1 + ELLIPSOID = 1 CYLINDER = 2 - # MESH = 3 + MESH = 3 BOX = 4 @dataclass diff --git a/addons/io_scene_swbf_msh/msh_scene_read.py b/addons/io_scene_swbf_msh/msh_scene_read.py index 36657f8..b774d5a 100644 --- a/addons/io_scene_swbf_msh/msh_scene_read.py +++ b/addons/io_scene_swbf_msh/msh_scene_read.py @@ -10,6 +10,8 @@ from .msh_utilities import * from .crc import * +envls = [] + def read_scene(input_file) -> Scene: scene = Scene() @@ -52,20 +54,27 @@ def read_scene(input_file) -> Scene: pass elif "SKL2" in next_header: - with hedr.read_child() as skl2: - num_bones = skl2.read_u32() - scene.skeleton = [skl2.read_u32(5)[0] for i in range(num_bones)] - print("Skeleton models: ") - for crc_hash in scene.skeleton: - for model in scene.models: - if crc_hash == crc(model.name): - print("\t" + model.name + " with type: " + str(model.model_type)) + with hedr.read_child() as skl2: + num_bones = skl2.read_u32() + scene.skeleton = [skl2.read_u32(5)[0] for i in range(num_bones)] + print("Skeleton models: ") + for crc_hash in scene.skeleton: + for model in scene.models: + if crc_hash == crc(model.name): + print("\t" + model.name + " with type: " + str(model.model_type)) + elif "ANM2" in next_header: + with hedr.read_child() as anm2: + _read_anm2(anm2, scene.models) else: - with hedr.read_child() as unknown: + with hedr.read_child() as null: pass + print("Models indexed by ENVLs: ") + for envl_index in set(envls): + print("\t" + scene.models[envl_index].name) + return scene @@ -164,12 +173,23 @@ def _read_modl(modl: Reader, materials_list: List[Material]) -> Model: elif "GEOM" in next_header: model.geometry = [] with modl.read_child() as geom: + while geom.could_have_child(): + next_header_geom = geom.peak_next_header() - next_header_modl = geom.peak_next_header() + if "SEGM" in next_header_geom: + with geom.read_child() as segm: + model.geometry.append(_read_segm(segm, materials_list)) - if "SEGM" in next_header_modl: - with geom.read_child() as segm: - model.geometry.append(_read_segm(segm, materials_list)) + elif "ENVL" in next_header_geom: + with geom.read_child() as envl: + global envls + num_indicies = envl.read_u32() + print("reading ENVL with " + str(num_indicies) + " indicies") + envls += [envl.read_u32() for _ in range(num_indicies)] + + else: + with geom.read_child() as null: + pass elif "SWCI" in next_header: prim = CollisionPrimitive() @@ -191,7 +211,7 @@ def _read_tran(tran: Reader) -> ModelTransform: xform = ModelTransform() - tran.skip_bytes(4 * 3) #ignore scale + tran.skip_bytes(12) #ignore scale rot = tran.read_f32(4) xform.rotation = Quaternion((rot[3], rot[0], rot[1], rot[2])) @@ -264,6 +284,10 @@ def _read_segm(segm: Reader, materials_list: List[Material]) -> GeometrySegment: if segm.read_u16 != 0: #trailing 0 bug https://schlechtwetterfront.github.io/ze_filetypes/msh.html#STRP segm.skip_bytes(-2) + elif "WGHT" in next_header: + with segm.read_child() as null: + pass + else: with segm.read_child() as unknown: pass @@ -272,3 +296,35 @@ def _read_segm(segm: Reader, materials_list: List[Material]) -> GeometrySegment: +def _read_anm2(anm2: Reader, models): + + hash_dict = {} + for model in models: + hash_dict[crc(model.name)] = model.name + + while anm2.could_have_child(): + + next_header = anm2.peak_next_header() + + if "CYCL" in next_header: + with anm2.read_child() as cycl: + pass + + elif "KFR3" in next_header: + with anm2.read_child() as kfr3: + + num_bones = kfr3.read_u32() + + for _ in range(num_bones): + + kfr3.read_u32() + + frametype = kfr3.read_u32() + + num_loc_frames = kfr3.read_u32() + num_rot_frames = kfr3.read_u32() + + kfr3.skip_bytes(16 * num_loc_frames + 20 * num_rot_frames) + + + diff --git a/addons/io_scene_swbf_msh/msh_to_blend.py b/addons/io_scene_swbf_msh/msh_to_blend.py index ac3e933..c2715ce 100644 --- a/addons/io_scene_swbf_msh/msh_to_blend.py +++ b/addons/io_scene_swbf_msh/msh_to_blend.py @@ -14,6 +14,8 @@ from .msh_utilities import * from .msh_model_gather import * from .crc import * +import os + def extract_models(scene: Scene, materials_map): @@ -21,9 +23,13 @@ def extract_models(scene: Scene, materials_map): model_map = {} for model in sort_by_parent(scene.models): + new_obj = None + + if model.name.startswith("p_") or "collision" in model.name: + continue + + if model.model_type == ModelType.STATIC or model.model_type == ModelType.SKIN: - if model.model_type == ModelType.STATIC: - new_mesh = bpy.data.meshes.new(model.name) verts = [] faces = [] @@ -42,10 +48,11 @@ def extract_models(scene: Scene, materials_map): offset += len(seg.positions) new_mesh.from_pydata(verts, [], faces) + new_mesh.update() new_mesh.validate() - + ''' edit_mesh = bmesh.new() edit_mesh.from_mesh(new_mesh) @@ -59,12 +66,12 @@ def extract_models(scene: Scene, materials_map): loop[uvlayer].uv = tuple([texcoord.x, texcoord.y]) edit_mesh.to_mesh(new_mesh) - edit_mesh.free() + edit_mesh.free() + ''' + - new_obj = bpy.data.objects.new(new_mesh.name, new_mesh) - ''' Assign Materials - will do per segment later... ''' @@ -97,7 +104,7 @@ def extract_models(scene: Scene, materials_map): -def extract_materials(scene: Scene) -> Dict[str,bpy.types.Material]: +def extract_materials(folder_path: str, scene: Scene) -> Dict[str,bpy.types.Material]: extracted_materials = {} @@ -106,9 +113,16 @@ def extract_materials(scene: Scene) -> Dict[str,bpy.types.Material]: new_mat = bpy.data.materials.new(name=material_name) new_mat.use_nodes = True bsdf = new_mat.node_tree.nodes["Principled BSDF"] - texImage = new_mat.node_tree.nodes.new('ShaderNodeTexImage') - texImage.image = bpy.data.images.load("/Users/will/Desktop/grad.jpg") - new_mat.node_tree.links.new(bsdf.inputs['Base Color'], texImage.outputs['Color']) + + tex_path_def = os.path.join(folder_path, scene.materials[material_name].texture0) + tex_path_alt = os.path.join(folder_path, "PC", scene.materials[material_name].texture0) + + tex_path = tex_path_def if os.path.exists(tex_path_def) else tex_path_alt + + if os.path.exists(tex_path): + texImage = new_mat.node_tree.nodes.new('ShaderNodeTexImage') + texImage.image = bpy.data.images.load(tex_path) + new_mat.node_tree.links.new(bsdf.inputs['Base Color'], texImage.outputs['Color']) extracted_materials[material_name] = new_mat @@ -116,10 +130,12 @@ def extract_materials(scene: Scene) -> Dict[str,bpy.types.Material]: -def extract_scene(scene: Scene): - return None - #matmap = extract_materials(scene) - #extract_models(scene, matmap) +def extract_scene(filepath: str, scene: Scene): + + folder = os.path.join(os.path.dirname(filepath),"") + + matmap = extract_materials(folder,scene) + extract_models(scene, matmap) From 8273e01167d92a53e55ed186eb95225b2b4b3a70 Mon Sep 17 00:00:00 2001 From: William Herald Snyder Date: Wed, 25 Nov 2020 23:10:14 -0500 Subject: [PATCH 6/7] Correct skeleton imports, though what to do with re-parenting and skeleton impurities such as effector nulls still uncertain --- addons/io_scene_swbf_msh/msh_model.py | 4 +- .../io_scene_swbf_msh/msh_model_utilities.py | 10 ++ addons/io_scene_swbf_msh/msh_reader.py | 4 +- addons/io_scene_swbf_msh/msh_scene_read.py | 28 +++- addons/io_scene_swbf_msh/msh_to_blend.py | 150 ++++++++++++++++-- 5 files changed, 170 insertions(+), 26 deletions(-) diff --git a/addons/io_scene_swbf_msh/msh_model.py b/addons/io_scene_swbf_msh/msh_model.py index c23c5c5..bb052eb 100644 --- a/addons/io_scene_swbf_msh/msh_model.py +++ b/addons/io_scene_swbf_msh/msh_model.py @@ -2,7 +2,7 @@ saved to a .msh file. """ from dataclasses import dataclass, field -from typing import List +from typing import List, Tuple from enum import Enum from mathutils import Vector, Quaternion @@ -44,6 +44,8 @@ class GeometrySegment: triangles: List[List[int]] = field(default_factory=list) triangle_strips: List[List[int]] = None + weights: List[List[Tuple[int, float]]] = None + @dataclass class CollisionPrimitive: """ Class representing a 'SWCI' section in a .msh file. """ diff --git a/addons/io_scene_swbf_msh/msh_model_utilities.py b/addons/io_scene_swbf_msh/msh_model_utilities.py index 1547697..98ae83e 100644 --- a/addons/io_scene_swbf_msh/msh_model_utilities.py +++ b/addons/io_scene_swbf_msh/msh_model_utilities.py @@ -5,6 +5,16 @@ from .msh_model import * from .msh_utilities import * from mathutils import Vector, Matrix + +def convert_vector_space_(vec: Vector) -> Vector: + return Vector((-vec.x, vec.z, vec.y)) + +def convert_rotation_space_(quat: Quaternion) -> Quaternion: + return Quaternion((-quat.w, quat.x, -quat.z, -quat.y)) + +def model_transform_to_matrix(transform: ModelTransform): + return Matrix.Translation(convert_vector_space_(transform.translation)) @ convert_rotation_space_(transform.rotation).to_matrix().to_4x4() + def scale_segments(scale: Vector, segments: List[GeometrySegment]): """ Scales are positions in the GeometrySegment list. """ diff --git a/addons/io_scene_swbf_msh/msh_reader.py b/addons/io_scene_swbf_msh/msh_reader.py index 3203955..f81a077 100644 --- a/addons/io_scene_swbf_msh/msh_reader.py +++ b/addons/io_scene_swbf_msh/msh_reader.py @@ -19,7 +19,7 @@ class Reader: 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)) + #print(self.indent + "Begin " + self.header + ", Size: " + str(self.size) + ", Pos: " + str(self.size_pos)) return self @@ -28,7 +28,7 @@ class Reader: 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) + #print(self.indent + "End " + self.header) self.file.seek(self.end_pos) diff --git a/addons/io_scene_swbf_msh/msh_scene_read.py b/addons/io_scene_swbf_msh/msh_scene_read.py index b774d5a..b4d6462 100644 --- a/addons/io_scene_swbf_msh/msh_scene_read.py +++ b/addons/io_scene_swbf_msh/msh_scene_read.py @@ -57,11 +57,12 @@ def read_scene(input_file) -> Scene: with hedr.read_child() as skl2: num_bones = skl2.read_u32() scene.skeleton = [skl2.read_u32(5)[0] for i in range(num_bones)] - print("Skeleton models: ") + #print("Skeleton models: ") for crc_hash in scene.skeleton: for model in scene.models: if crc_hash == crc(model.name): - print("\t" + model.name + " with type: " + str(model.model_type)) + pass + #print("\t" + model.name + " with type: " + str(model.model_type)) elif "ANM2" in next_header: with hedr.read_child() as anm2: @@ -70,11 +71,7 @@ def read_scene(input_file) -> Scene: else: with hedr.read_child() as null: pass - - print("Models indexed by ENVLs: ") - for envl_index in set(envls): - print("\t" + scene.models[envl_index].name) - + return scene @@ -285,9 +282,24 @@ def _read_segm(segm: Reader, materials_list: List[Material]) -> GeometrySegment: segm.skip_bytes(-2) elif "WGHT" in next_header: - with segm.read_child() as null: + with segm.read_child() as wght: pass + ''' + geometry_seg.weights = [] + num_weights = wght.read_u32() + + for _ in range(num_weights): + weight_set = [] + for _ in range(4): + index = wght.read_u32() + value = wght.read_f32() + + if value > 0.000001: + weight_set.append((index,value)) + + geometry_seg.weights.append(weight_set) + ''' else: with segm.read_child() as unknown: pass diff --git a/addons/io_scene_swbf_msh/msh_to_blend.py b/addons/io_scene_swbf_msh/msh_to_blend.py index c2715ce..a768cc5 100644 --- a/addons/io_scene_swbf_msh/msh_to_blend.py +++ b/addons/io_scene_swbf_msh/msh_to_blend.py @@ -18,6 +18,102 @@ import os + +def refined_skeleton_to_armature(refined_skeleton : List[Model], model_map): + + armature = bpy.data.armatures.new("skeleton") + armature_obj = bpy.data.objects.new("skeleton", armature) + + bpy.context.view_layer.active_layer_collection.collection.objects.link(armature_obj) + armature_obj.select_set(True) + + bpy.context.view_layer.objects.active = armature_obj + bpy.ops.object.mode_set(mode='EDIT') + + for bone in refined_skeleton: + + edit_bone = armature.edit_bones.new(bone.name) + + if bone.parent: + edit_bone.parent = armature.edit_bones[bone.parent] + + edit_bone.head = model_map[bone.name].matrix_world.translation + + bone_children = [b for b in get_model_children(bone, refined_skeleton)] + + if len(bone_children) > 0: + edit_bone.tail = Vector((0.0,0.0,0.0)) + for bone_child in bone_children: + edit_bone.tail += model_map[bone_child.name].matrix_world.translation + edit_bone.tail = edit_bone.tail / len(bone_children) + else: + edit_bone.tail = model_map[bone.name].matrix_world @ Vector((-0.2,0.0,0.0)) + + + bpy.ops.object.mode_set(mode='OBJECT') + armature_obj.select_set(True) + bpy.context.view_layer.update() + + + + + + +def extract_refined_skeleton(scene: Scene): + + model_dict = {} + children_dict = {} + skeleton_models = [] + + for model in scene.models: + model_dict[model.name] = model + children_dict[model.name] = [] + + for model in scene.models: + if model.parent: + children_dict[model.parent].append(model) + + if crc(model.name) in scene.skeleton: + skeleton_models.append(model) + + refined_skeleton_models = [] + + for bone in skeleton_models: + + if bone.parent: + + curr_ancestor = model_dict[bone.parent] + stacked_transform = model_transform_to_matrix(bone.transform) + + while True: + + if crc(curr_ancestor.name) in scene.skeleton or "dummyroot" in curr_ancestor.name.lower(): + new_model = Model() + new_model.name = bone.name + new_model.parent = curr_ancestor.name if "dummyroot" not in curr_ancestor.name.lower() else "" + + loc, rot, _ = stacked_transform.decompose() + + new_model.transform.rotation = rot + new_model.transform.translation = loc + + refined_skeleton_models.append(new_model) + break + + else: + curr_ancestor = model_dict[curr_ancestor.parent] + stacked_transform = model_transform_to_matrix(curr_ancestor.transform) @ stacked_transform + + return sort_by_parent(refined_skeleton_models) + + + + + + + + + def extract_models(scene: Scene, materials_map): model_map = {} @@ -25,7 +121,13 @@ def extract_models(scene: Scene, materials_map): for model in sort_by_parent(scene.models): new_obj = None - if model.name.startswith("p_") or "collision" in model.name: + if "bone_l_toe" in model.name: + loc = get_model_world_matrix(model, scene.models).translation + print("World bone_l_toe: " + str(loc)) + + + + if model.name.startswith("p_") or "collision" in model.name or model.name.startswith("c_") or model.name.startswith("sv_"): continue if model.model_type == ModelType.STATIC or model.model_type == ModelType.SKIN: @@ -37,37 +139,47 @@ def extract_models(scene: Scene, materials_map): mat_name = "" + full_texcoords = [] + for i,seg in enumerate(model.geometry): if i == 0: mat_name = seg.material_name verts += [tuple(convert_vector_space(v)) for v in seg.positions] + + if seg.texcoords is not None: + full_texcoords += seg.texcoords + else: + full_texcoords += [(0.0,0.0) for _ in range(len(seg.positions))] + faces += [tuple([ind + offset for ind in tri]) for tri in seg.triangles] offset += len(seg.positions) new_mesh.from_pydata(verts, [], faces) - new_mesh.update() new_mesh.validate() - ''' - edit_mesh = bmesh.new() - edit_mesh.from_mesh(new_mesh) + + if len(full_texcoords) > 0: - uvlayer = edit_mesh.loops.layers.uv.verify() + edit_mesh = bmesh.new() + edit_mesh.from_mesh(new_mesh) - for edit_mesh_face in edit_mesh.faces: - mesh_face = faces[edit_mesh_face.index] + uvlayer = edit_mesh.loops.layers.uv.verify() - for i,loop in enumerate(edit_mesh_face.loops): - texcoord = seg.texcoords[mesh_face[i]] - loop[uvlayer].uv = tuple([texcoord.x, texcoord.y]) + for edit_mesh_face in edit_mesh.faces: + mesh_face = faces[edit_mesh_face.index] - edit_mesh.to_mesh(new_mesh) - edit_mesh.free() - ''' + for i,loop in enumerate(edit_mesh_face.loops): + + texcoord = full_texcoords[mesh_face[i]] + loop[uvlayer].uv = tuple([texcoord.x, texcoord.y]) + + edit_mesh.to_mesh(new_mesh) + edit_mesh.free() + new_obj = bpy.data.objects.new(new_mesh.name, new_mesh) @@ -102,6 +214,8 @@ def extract_models(scene: Scene, materials_map): bpy.context.collection.objects.link(new_obj) + return model_map + def extract_materials(folder_path: str, scene: Scene) -> Dict[str,bpy.types.Material]: @@ -135,7 +249,13 @@ def extract_scene(filepath: str, scene: Scene): folder = os.path.join(os.path.dirname(filepath),"") matmap = extract_materials(folder,scene) - extract_models(scene, matmap) + + model_map = extract_models(scene, matmap) + + + skel = extract_refined_skeleton(scene) + refined_skeleton_to_armature(skel, model_map) + From aa62fd47ea7d410c9e203f9b23a66e5dcd0bdb3d Mon Sep 17 00:00:00 2001 From: William Herald Snyder Date: Sat, 28 Nov 2020 21:42:27 -0500 Subject: [PATCH 7/7] Vertex groups importing properly. ENVL indicies refer to each model's MNDX, which start from 1, not 0/don't necessarily refer to order in file... --- addons/io_scene_swbf_msh/msh_scene_read.py | 41 ++++++++++++++++++---- addons/io_scene_swbf_msh/msh_to_blend.py | 37 +++++++++++++------ 2 files changed, 61 insertions(+), 17 deletions(-) diff --git a/addons/io_scene_swbf_msh/msh_scene_read.py b/addons/io_scene_swbf_msh/msh_scene_read.py index b4d6462..a2563f9 100644 --- a/addons/io_scene_swbf_msh/msh_scene_read.py +++ b/addons/io_scene_swbf_msh/msh_scene_read.py @@ -12,6 +12,8 @@ from .crc import * envls = [] +model_counter = 0 + def read_scene(input_file) -> Scene: scene = Scene() @@ -71,6 +73,11 @@ def read_scene(input_file) -> Scene: else: with hedr.read_child() as null: pass + + for envl in envls: + #print("Envelope: ") + for index in envl: + pass#print("\t" + scene.models[index].name) return scene @@ -149,7 +156,10 @@ def _read_modl(modl: Reader, materials_list: List[Material]) -> Model: elif "MNDX" in next_header: with modl.read_child() as mndx: - pass + index = mndx.read_u32() + global model_counter + print("Encountered model {} with index {}".format(model_counter, index)) + model_counter += 1 elif "NAME" in next_header: with modl.read_child() as name: @@ -169,7 +179,10 @@ def _read_modl(modl: Reader, materials_list: List[Material]) -> Model: elif "GEOM" in next_header: model.geometry = [] + envelope = [] + with modl.read_child() as geom: + while geom.could_have_child(): next_header_geom = geom.peak_next_header() @@ -179,14 +192,22 @@ def _read_modl(modl: Reader, materials_list: List[Material]) -> Model: elif "ENVL" in next_header_geom: with geom.read_child() as envl: - global envls num_indicies = envl.read_u32() - print("reading ENVL with " + str(num_indicies) + " indicies") - envls += [envl.read_u32() for _ in range(num_indicies)] + envelope += [envl.read_u32() - 1 for _ in range(num_indicies)] else: with geom.read_child() as null: pass + if envelope: + global envls + envls.append(envelope) + + for seg in model.geometry: + if seg.weights: + for weight_set in seg.weights: + for i in range(len(weight_set)): + weight = weight_set[i] + weight_set[i] = (envelope[weight[0]], weight[1]) elif "SWCI" in next_header: prim = CollisionPrimitive() @@ -282,24 +303,30 @@ def _read_segm(segm: Reader, materials_list: List[Material]) -> GeometrySegment: segm.skip_bytes(-2) elif "WGHT" in next_header: + print("===================================") with segm.read_child() as wght: - pass - ''' + geometry_seg.weights = [] num_weights = wght.read_u32() for _ in range(num_weights): weight_set = [] + print_str = "" for _ in range(4): index = wght.read_u32() value = wght.read_f32() + print_str += "({}, {}) ".format(index,value) + if value > 0.000001: weight_set.append((index,value)) + #print(print_str) + geometry_seg.weights.append(weight_set) - ''' + print("===================================") + else: with segm.read_child() as unknown: pass diff --git a/addons/io_scene_swbf_msh/msh_to_blend.py b/addons/io_scene_swbf_msh/msh_to_blend.py index a768cc5..49be9f4 100644 --- a/addons/io_scene_swbf_msh/msh_to_blend.py +++ b/addons/io_scene_swbf_msh/msh_to_blend.py @@ -41,7 +41,7 @@ def refined_skeleton_to_armature(refined_skeleton : List[Model], model_map): bone_children = [b for b in get_model_children(bone, refined_skeleton)] - if len(bone_children) > 0: + if bone_children: edit_bone.tail = Vector((0.0,0.0,0.0)) for bone_child in bone_children: edit_bone.tail += model_map[bone_child.name].matrix_world.translation @@ -50,6 +50,7 @@ def refined_skeleton_to_armature(refined_skeleton : List[Model], model_map): edit_bone.tail = model_map[bone.name].matrix_world @ Vector((-0.2,0.0,0.0)) + bpy.ops.object.mode_set(mode='OBJECT') armature_obj.select_set(True) bpy.context.view_layer.update() @@ -121,12 +122,6 @@ def extract_models(scene: Scene, materials_map): for model in sort_by_parent(scene.models): new_obj = None - if "bone_l_toe" in model.name: - loc = get_model_world_matrix(model, scene.models).translation - print("World bone_l_toe: " + str(loc)) - - - if model.name.startswith("p_") or "collision" in model.name or model.name.startswith("c_") or model.name.startswith("sv_"): continue @@ -141,6 +136,8 @@ def extract_models(scene: Scene, materials_map): full_texcoords = [] + weights_offsets = {} + for i,seg in enumerate(model.geometry): if i == 0: @@ -148,6 +145,9 @@ def extract_models(scene: Scene, materials_map): verts += [tuple(convert_vector_space(v)) for v in seg.positions] + if seg.weights: + weights_offsets[offset] = seg.weights + if seg.texcoords is not None: full_texcoords += seg.texcoords else: @@ -162,7 +162,7 @@ def extract_models(scene: Scene, materials_map): new_mesh.validate() - if len(full_texcoords) > 0: + if full_texcoords: edit_mesh = bmesh.new() edit_mesh.from_mesh(new_mesh) @@ -180,10 +180,21 @@ def extract_models(scene: Scene, materials_map): edit_mesh.to_mesh(new_mesh) edit_mesh.free() - - new_obj = bpy.data.objects.new(new_mesh.name, new_mesh) + + for offset in weights_offsets: + vertex_groups_indicies = {} + for i, weight_set in enumerate(weights_offsets[offset]): + for weight in weight_set: + index = weight[0] + + if index not in vertex_groups_indicies: + model_name = scene.models[index].name + vertex_groups_indicies[index] = new_obj.vertex_groups.new(name=model_name) + + vertex_groups_indicies[index].add([offset + i], weight[1], 'ADD') + ''' Assign Materials - will do per segment later... ''' @@ -250,6 +261,12 @@ def extract_scene(filepath: str, scene: Scene): matmap = extract_materials(folder,scene) + if scene.skeleton: + #print("Skeleton models: ") + for model in scene.models: + if crc(model.name) in scene.skeleton: + pass#print("\tName: " + model.name + " Parent: " + model.parent) + model_map = extract_models(scene, matmap)