diff --git a/addons/io_scene_swbf_msh/__init__.py b/addons/io_scene_swbf_msh/__init__.py index 8b97f68..eea25b8 100644 --- a/addons/io_scene_swbf_msh/__init__.py +++ b/addons/io_scene_swbf_msh/__init__.py @@ -53,12 +53,15 @@ 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 * +from .msh_to_blend import * + class ExportMSH(Operator, ExportHelper): """ Export the current scene as a SWBF .msh file. """ @@ -126,16 +129,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: + extract_scene(self.filepath, 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) @@ -143,7 +177,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_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_model.py b/addons/io_scene_swbf_msh/msh_model.py index fbf5dc8..8976991 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, Tuple from enum import Enum from mathutils import Vector, Quaternion @@ -12,12 +12,13 @@ class ModelType(Enum): CLOTH = 2 BONE = 3 STATIC = 4 + SHADOWVOLUME = 6 class CollisionPrimitiveShape(Enum): SPHERE = 0 - # ELLIPSOID = 1 + ELLIPSOID = 1 CYLINDER = 2 - # MESH = 3 + MESH = 3 BOX = 4 @dataclass @@ -51,6 +52,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. """ @@ -76,6 +79,7 @@ class Model: geometry: List[GeometrySegment] = None collisionprimitive: CollisionPrimitive = None + @dataclass class Animation: """ Class representing 'CYCL' + 'KFR3' sections in a .msh file """ @@ -86,4 +90,4 @@ class Animation: framerate: float = 29.97 start_index : int = 0 end_index : int = 0 - + \ No newline at end of file diff --git a/addons/io_scene_swbf_msh/msh_model_gather.py b/addons/io_scene_swbf_msh/msh_model_gather.py index ea1fe03..776222d 100644 --- a/addons/io_scene_swbf_msh/msh_model_gather.py +++ b/addons/io_scene_swbf_msh/msh_model_gather.py @@ -4,7 +4,7 @@ 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 * @@ -80,6 +80,7 @@ def gather_models(apply_modifiers: bool, export_target: str, skeleton_only: bool mesh = obj.to_mesh() model.geometry = create_mesh_geometry(mesh, obj.vertex_groups) + obj.to_mesh_clear() _, _, world_scale = obj.matrix_world.decompose() @@ -103,7 +104,6 @@ def gather_models(apply_modifiers: bool, export_target: str, skeleton_only: bool models_list.append(model) - return models_list @@ -119,7 +119,6 @@ def create_parents_set() -> Set[str]: return parents - def create_mesh_geometry(mesh: bpy.types.Mesh, has_weights: bool) -> List[GeometrySegment]: """ Creates a list of GeometrySegment objects from a Blender mesh. Does NOT create triangle strips in the GeometrySegment however. """ @@ -218,7 +217,6 @@ def create_mesh_geometry(mesh: bpy.types.Mesh, has_weights: 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([ diff --git a/addons/io_scene_swbf_msh/msh_model_utilities.py b/addons/io_scene_swbf_msh/msh_model_utilities.py index eec1450..afc692a 100644 --- a/addons/io_scene_swbf_msh/msh_model_utilities.py +++ b/addons/io_scene_swbf_msh/msh_model_utilities.py @@ -26,6 +26,14 @@ def inject_dummy_data(model : Model): model.geometry = [dummy_seg] model.model_type = ModelType.STATIC +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 new file mode 100644 index 0000000..f81a077 --- /dev/null +++ b/addons/io_scene_swbf_msh/msh_reader.py @@ -0,0 +1,105 @@ + +import io +import struct + +class Reader: + def __init__(self, file, parent=None, indent=0): + self.file = file + self.size: int = 0 + self.size_pos = None + self.parent = parent + self.indent = " " * indent #for print debugging + + + def __enter__(self): + self.size_pos = self.file.tell() + 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 + 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 = Reader(self.file, 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 diff --git a/addons/io_scene_swbf_msh/msh_scene.py b/addons/io_scene_swbf_msh/msh_scene.py index 2f37f9a..c6337f9 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,8 @@ 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) + skeleton: List[int] = field(default_factory=list) def create_scene(generate_triangle_strips: bool, apply_modifiers: bool, export_target: str, skel_only: bool) -> 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 new file mode 100644 index 0000000..a2563f9 --- /dev/null +++ b/addons/io_scene_swbf_msh/msh_scene_read.py @@ -0,0 +1,369 @@ +""" Contains functions for extracting a scene from 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 * + +envls = [] + +model_counter = 0 + +def read_scene(input_file) -> Scene: + + scene = Scene() + scene.models = [] + scene.materials = {} + + with Reader(file=input_file) as hedr: + + while hedr.could_have_child(): + + next_header = hedr.peak_next_header() + + if "MSH2" in next_header: + + with hedr.read_child() as msh2: + + materials_list = [] + + while (msh2.could_have_child()): + + next_header = msh2.peak_next_header() + + if "SINF" in next_header: + with msh2.read_child() as sinf: + pass + + elif "MATL" in next_header: + 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() as modl: + scene.models.append(_read_modl(modl, materials_list)) + + else: + 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): + pass + #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 null: + pass + + for envl in envls: + #print("Envelope: ") + for index in envl: + pass#print("\t" + scene.models[index].name) + + return scene + + +def _read_matl_and_get_materials_list(matl: Reader) -> List[Material]: + materials_list: List[Material] = [] + + num_mats = matl.read_u32() + + for _ in range(num_mats): + with matl.read_child() 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() as name: + mat.name = name.read_string() + + elif "DATA" in next_header: + 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() 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() as tx0d: + mat.texture0 = tx0d.read_string() + + elif "TX1D" in next_header: + with matd.read_child() as tx1d: + mat.texture1 = tx1d.read_string() + + elif "TX2D" in next_header: + with matd.read_child() as tx2d: + mat.texture2 = tx2d.read_string() + + elif "TX3D" in next_header: + with matd.read_child() as tx3d: + mat.texture3 = tx3d.read_string() + + else: + matd.skip_bytes(4) + + return mat + + +def _read_modl(modl: Reader, materials_list: List[Material]) -> Model: + + model = Model() + + while modl.could_have_child(): + + next_header = modl.peak_next_header() + + if "MTYP" in next_header: + with modl.read_child() as mtyp: + model.model_type = ModelType(mtyp.read_u32()) + + elif "MNDX" in next_header: + with modl.read_child() as mndx: + 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: + model.name = name.read_string() + + elif "PRNT" in next_header: + with modl.read_child() as prnt: + model.parent = prnt.read_string() + + elif "FLGS" in next_header: + with modl.read_child() as flgs: + model.hidden = flgs.read_u32() + + elif "TRAN" in next_header: + with modl.read_child() as tran: + model.transform = _read_tran(tran) + + 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() + + if "SEGM" in next_header_geom: + 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: + num_indicies = envl.read_u32() + 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() + 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() as unknown: + pass + + return model + + +def _read_tran(tran: Reader) -> ModelTransform: + + xform = ModelTransform() + + tran.skip_bytes(12) #ignore scale + + 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[Material]) -> GeometrySegment: + + geometry_seg = GeometrySegment() + + while segm.could_have_child(): + + next_header = segm.peak_next_header() + + if "MATI" in next_header: + 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() 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() as nrml: + num_normals = nrml.read_u32() + + for _ in range(num_positions): + geometry_seg.normals.append(Vector(nrml.read_f32(3))) + + elif "CLRL" in next_header: + geometry_seg.colors = [] + + 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() 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() 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() 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() as strp: + pass + + 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: + print("===================================") + with segm.read_child() as wght: + + 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 + + return geometry_seg + + + +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_scene_save.py b/addons/io_scene_swbf_msh/msh_scene_save.py index 6507b17..67c1f10 100644 --- a/addons/io_scene_swbf_msh/msh_scene_save.py +++ b/addons/io_scene_swbf_msh/msh_scene_save.py @@ -8,9 +8,13 @@ from .msh_material import * from .msh_writer import Writer from .msh_utilities import * +<<<<<<< HEAD from .crc import * def save_scene(output_file, scene: Scene, is_animated: bool): +======= +def save_scene(output_file, scene: Scene): +>>>>>>> mshread """ Saves scene to the supplied file. """ with Writer(file=output_file, chunk_id="HEDR") as hedr: @@ -52,7 +56,7 @@ def _write_sinf(sinf: Writer, scene: Scene): with sinf.create_child("FRAM") as fram: fram.write_i32(0, 1) - fram.write_f32(29.97) + fram.write_f32(29.97003) with sinf.create_child("BBOX") as bbox: aabb = create_scene_aabb(scene) @@ -298,6 +302,3 @@ def _write_anm2(anm2: Writer, anim: Animation): 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..49be9f4 --- /dev/null +++ b/addons/io_scene_swbf_msh/msh_to_blend.py @@ -0,0 +1,280 @@ +""" 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 * +from .crc import * + +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 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 + 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 = {} + + for model in sort_by_parent(scene.models): + new_obj = None + + 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: + + new_mesh = bpy.data.meshes.new(model.name) + verts = [] + faces = [] + offset = 0 + + mat_name = "" + + full_texcoords = [] + + weights_offsets = {} + + 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.weights: + weights_offsets[offset] = seg.weights + + 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() + + + if full_texcoords: + + 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 = 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) + + + 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... + ''' + 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) + + else: + + new_obj = bpy.data.objects.new(model.name, None) + new_obj.empty_display_size = 1 + new_obj.empty_display_type = 'PLAIN_AXES' + + + + 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) + + return model_map + + + +def extract_materials(folder_path: str, 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"] + + 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 + + return extracted_materials + + + +def extract_scene(filepath: str, scene: Scene): + + folder = os.path.join(os.path.dirname(filepath),"") + + 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) + + + skel = extract_refined_skeleton(scene) + refined_skeleton_to_armature(skel, model_map) + + + + + + diff --git a/addons/io_scene_swbf_msh/msh_utilities.py b/addons/io_scene_swbf_msh/msh_utilities.py index 5a3fa6e..1c7424f 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 vec_to_str(vec): @@ -36,3 +37,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]