Basic reading/writing
This commit is contained in:
commit
500c3f2bd1
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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([
|
||||
|
@ -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. """
|
||||
|
105
addons/io_scene_swbf_msh/msh_reader.py
Normal file
105
addons/io_scene_swbf_msh/msh_reader.py
Normal file
@ -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
|
@ -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. """
|
||||
|
369
addons/io_scene_swbf_msh/msh_scene_read.py
Normal file
369
addons/io_scene_swbf_msh/msh_scene_read.py
Normal file
@ -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)
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
280
addons/io_scene_swbf_msh/msh_to_blend.py
Normal file
280
addons/io_scene_swbf_msh/msh_to_blend.py
Normal file
@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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]
|
||||
|
Loading…
Reference in New Issue
Block a user