Basic reading/writing

This commit is contained in:
Will Snyder 2020-11-29 20:27:46 -05:00
commit 500c3f2bd1
11 changed files with 832 additions and 18 deletions

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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([

View File

@ -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. """

View 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

View File

@ -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. """

View 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)

View File

@ -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)

View 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)

View File

@ -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]