Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # bitcpy
- # Loads a Legend of Grimrock 2 model from either a .model file or a .dat container
- # Names of models in .dat container are determined by doing an extremly simple scan of lua compiled files.
- bl_info = {
- "name": "Legend of Grimrock 2 Model Format (.model)",
- "author": "",
- "version": (1, 0, 0),
- "blender": (2, 71, 0),
- "api": 36339,
- "location": "File > Import > Legend of Grimrock 2 Model (.model)",
- "description": "Import Legend of Grimrock Models (.model)",
- "warning": "",
- "wiki_url": "",
- "tracker_url": "",
- "category": "Import-Export"}
- import os
- import struct
- import sys
- import bpy
- import bmesh
- from mathutils import *
- from bpy.props import *
- from bpy_extras.io_utils import ExportHelper, ImportHelper
- from bpy_extras.image_utils import load_image
- # Vertex data types
- VTX_DATA_BYTE = 0
- VTX_DATA_SHORT = 1
- VTX_DATA_INT = 2
- VTX_DATA_FLOAT = 3
- # Vertex array types
- VTX_ARRAY_POSITION = 0
- VTX_ARRAY_NORMAL = 1
- VTX_ARRAY_TANGENT = 2
- VTX_ARRAY_BITANGENT = 3
- VTX_ARRAY_COLOR = 4
- VTX_ARRAY_TEXCOORD0 = 5
- VTX_ARRAY_TEXCOORD1 = 6
- VTX_ARRAY_TEXCOORD2 = 7
- VTX_ARRAY_TEXCOORD3 = 8
- VTX_ARRAY_TEXCOORD4 = 9
- VTX_ARRAY_TEXCOORD5 = 10
- VTX_ARRAY_TEXCOORD6 = 11
- VTX_ARRAY_TEXCOORD7 = 12
- VTX_ARRAY_BONE_INDEX = 13
- VTX_ARRAY_BONE_WEIGHT = 14
- # Reads file magic from file
- def read_magic(file_object, endian = '<'):
- data = struct.unpack(endian+"4s", file_object.read(4))[0]
- return data;
- # read_uint
- # Read unsigned integer from file
- def read_uint(file_object, endian = '<'):
- data = struct.unpack(endian+'I', file_object.read(4))[0]
- return data
- # read_int
- # Read signed integer from file
- def read_int(file_object, endian = '<'):
- data = struct.unpack(endian+'i', file_object.read(4))[0]
- return data
- # read_int2
- # Read two signed integers from file
- def read_int2(file_object, endian = '<'):
- data = struct.unpack(endian+'ii', file_object.read(8))
- return data
- # read_int3
- # Read three signed integers from file
- def read_int3(file_object, endian = '<'):
- data = struct.unpack(endian+'iii', file_object.read(12))
- return data
- # read_int4
- # Read four signed integers from file
- def read_int4(file_object, endian = '<'):
- data = struct.unpack(endian+'iiii', file_object.read(16))
- return data
- # read_float
- # Read float from file
- def read_float(file_object, endian = '<'):
- data = struct.unpack(endian+'f', file_object.read(4))[0]
- return data
- # read_float2
- # Read two floats from file
- def read_float2(file_object, endian = '<'):
- data = struct.unpack(endian+'ff', file_object.read(8))
- return data
- # read_float3
- # Read three floats from file
- def read_float3(file_object, endian = '<'):
- data = struct.unpack(endian+'fff', file_object.read(12))
- return data
- # read_float4
- # Read four floats from file
- def read_float4(file_object, endian = '<'):
- data = struct.unpack(endian+'ffff', file_object.read(16))
- return data
- # read_matrix4x3
- # Read a matrix consisting of four rows with three columns
- def read_matrix4x3(file_object, endian = '<'):
- data = struct.unpack(endian+'ffffffffffff', file_object.read(48))
- return data
- # read_short
- # Read signed short from file
- def read_short(file_object, endian = '<'):
- data = struct.unpack(endian+'h', file_object.read(2))[0]
- return data
- # read_short2
- # Read two signed shorts from file
- def read_short2(file_object, endian = '<'):
- data = struct.unpack(endian+'hh', file_object.read(4))
- return data
- # read_short3
- # Read three signed shorts from file
- def read_short3(file_object, endian = '<'):
- data = struct.unpack(endian+'hhh', file_object.read(6))
- return data
- # read_short4
- # Read four signed shorts from file
- def read_short4(file_object, endian = '<'):
- data = struct.unpack(endian+'hhhh', file_object.read(8))
- return data
- # read_byte
- # Read unsigned byte from file
- def read_byte(file_object, endian = '<'):
- data = struct.unpack(endian+'B', file_object.read(1))[0]
- return data
- # read_byte2
- # Read two unsigned bytes from file
- def read_byte2(file_object, endian = '<'):
- data = struct.unpack(endian+'BB', file_object.read(2))
- return data
- # read_byte3
- # Read three unsigned bytes from file
- def read_byte3(file_object, endian = '<'):
- data = struct.unpack(endian+'BBB', file_object.read(3))
- return data
- # read_byte4
- # Read four unsigned bytes from file
- def read_byte4(file_object, endian = '<'):
- data = struct.unpack(endian+'BBBB', file_object.read(4))
- return data
- # read_string
- # Read string from file
- def read_string(file_object, num, endian = '<'):
- raw_string = struct.unpack(endian+str(num)+'s', file_object.read(num))[0]
- data = raw_string.decode("utf-8", "ignore")
- return data
- # read_len_string
- # Read unsigned integer from file used to read back a string using integer as length from the same file object
- def read_len_string(file_object, endian = '<'):
- num = read_int(file_object, endian)
- return read_string(file_object, num, endian)
- # decode_string
- # Decode string from buffer
- def decode_string(buffer_object, endian = '<'):
- raw_string = struct.unpack(endian+str(len(buffer_object))+'s', buffer_object)[0]
- data = raw_string.decode("utf-8", "ignore")
- return data
- # Vec3
- class Vec3():
- __slots__ = (
- "x",
- "y",
- "z",
- )
- def __init__(self, x = 0.0, y = 0.0, z = 0.0):
- self.x = x
- self.y = y
- self.z = z
- # read
- # Reads a 3 dimensional vector using Grimrock 1 documentation
- # float x
- # float y
- # float z
- def read(self, file_object):
- self.x = read_float(file_object)
- self.y = read_float(file_object)
- self.z = read_float(file_object)
- # Mat4x3
- class Mat4x3():
- __slots__ = (
- "rows"
- )
- def __init__(self):
- self.rows = [
- Vec3( x = 1.0, y = 0.0, z = 0.0 ),
- Vec3( x = 0.0, y = 1.0, z = 0.0 ),
- Vec3( x = 0.0, y = 0.0, z = 1.0 ),
- Vec3( x = 0.0, y = 0.0, z = 0.0 )
- ]
- # read
- # Reads a 4x3 matrix using Grimrock 1 documentation
- # vec3 baseX
- # vec3 baseY
- # vec3 baseZ
- # vec3 translation
- def read(self, file_object):
- for i in range(4):
- self.rows[i].read(file_object)
- # Bone
- class Bone():
- __slots__ = (
- "node_index",
- "mat_model_to_bone",
- )
- def __init__(self):
- self.node_index = -1
- self.mat_model_to_bone = Mat4x3()
- # read
- # Reads a Bone structure using Grimrock 1 documentation
- # int32 boneNodeIndex
- # Mat4x3 invRestMatrix
- def read(self, file_object):
- self.node_index = read_int(file_object)
- self.mat_model_to_bone.read(file_object)
- # MeshSegment
- class MeshSegment():
- __slots__ = (
- "material",
- "primitive_type",
- "index_offset",
- "num_triangles",
- )
- def __init__(self):
- self.material = None
- self.primitive_type = 0
- self.index_offset = 0
- self.num_triangles = 0
- # read
- # Reads a MeshSegment using Grimrock 1 documentation
- # string material
- # int32 primitiveType
- # int32 firstIndex
- # int32 count
- def read(self, file_object):
- self.material = read_len_string(file_object)
- self.primitive_type = read_int(file_object)
- self.index_offset = read_int(file_object)
- self.num_triangles = read_int(file_object)
- # VertexArray
- class VertexArray():
- __slots__ = (
- "data_type",
- "dim",
- "stride",
- "data",
- )
- def __init__(self):
- self.data_type = 0
- self.dim = 0
- self.stride = 0
- self.data = None
- # is_valid_type
- # Helper to determine if data_type value is valid for read_data_type calls
- def is_valid_type(self):
- return (self.data_type == VTX_DATA_BYTE or self.data_type == VTX_DATA_SHORT or self.data_type == VTX_DATA_INT or self.data_type == VTX_DATA_FLOAT)
- # is_valid_dim
- # Helper to determine if dimensional value is in valid range for read_data_type calls
- def is_valid_dim(self):
- return (self.dim >= 1 and self.dim <= 4)
- # read_data_type
- # Helper to read dimensional attribute of each data type, (ex. 3 bytes, 2 floats, 4 shorts etc.)
- def read_data_type(self, file_object):
- if self.data_type == VTX_DATA_BYTE:
- if self.dim == 1:
- return read_byte(file_object)
- elif self.dim == 2:
- return read_byte2(file_object)
- elif self.dim == 3:
- return read_byte3(file_object)
- elif self.dim == 4:
- return read_byte4(file_object)
- elif self.data_type == VTX_DATA_SHORT:
- if self.dim == 1:
- return read_short(file_object)
- elif self.dim == 2:
- return read_short2(file_object)
- elif self.dim == 3:
- return read_short3(file_object)
- elif self.dim == 4:
- return read_short4(file_object)
- elif self.data_type == VTX_DATA_INT:
- if self.dim == 1:
- return read_int(file_object)
- elif self.dim == 2:
- return read_int2(file_object)
- elif self.dim == 3:
- return read_int3(file_object)
- elif self.dim == 4:
- return read_int4(file_object)
- elif self.data_type == VTX_DATA_FLOAT:
- if self.dim == 1:
- return read_float(file_object)
- elif self.dim == 2:
- return read_float2(file_object)
- elif self.dim == 3:
- return read_float3(file_object)
- elif self.dim == 4:
- return read_float4(file_object)
- # read
- # Reads VertexArray data using Grimrock 1 documentation
- # int32 dataType
- # int32 dim
- # int32 stride
- # byte num_vertices*stride
- def read(self, num_vertices, file_object):
- # Read type, dimension and stride
- self.data_type = read_int(file_object)
- self.dim = read_int(file_object)
- self.stride = read_int(file_object)
- # Skip if size between vertex to vertex is zero
- if self.stride == 0:
- return
- # Pre allocate the data, read data type if valid otherwise just call raw read for each entry
- self.data = num_vertices * [None]
- if self.is_valid_type() and self.is_valid_dim():
- for i in range(num_vertices):
- self.data[i] = self.read_data_type(file_object)
- else:
- print("Unknown VertexArray data, type(%d), dimension(%d), stride(%d)" % (self.data_type, self.dim, self.stride))
- for i in range(num_vertices):
- self.data[i] = file_object.read(self.stride)
- # MeshData
- class MeshData():
- __slots__ = (
- "magic",
- "version",
- "num_vertices",
- "vertex_arrays",
- "num_indices",
- "indices",
- "num_segments",
- "segments",
- "bound_center",
- "bound_radius",
- "bound_min",
- "bound_max",
- )
- def __init__(self):
- self.magic = 0
- self.version = 0
- self.num_vertices = 0
- self.vertex_arrays = 15 * [None]
- self.num_indices = 0
- self.indices = None
- self.num_segments = 0
- self.segments = None
- self.bound_center = [0.0, 0.0, 0.0]
- self.bound_radius = 0.0
- self.bound_min = [0.0, 0.0, 0.0]
- self.bound_max = [0.0, 0.0, 0.0]
- # read
- # Reads MeshData using Grimrock 1 documentation
- # FourCC magic
- # int32 version
- # int32 numVertices
- # VertexArray * 15
- # int32 numIndices
- # int32 * numIndices
- # int32 numSegents
- # MeshSegment * numSegments
- # vec3 boundCenter
- # float boundRadius
- # vec3 boundMin
- # vec3 boundMax
- def read(self, file_object):
- # Read MeshData magic, skip if not equal 'MESH'
- self.magic = read_magic(file_object)
- if self.magic != b'MESH':
- print("Invalid MeshData magic '%s', expected 'MESH'" % self.magic)
- return False
- # Read version, skip if version isn't equal to 2
- self.version = read_int(file_object)
- if self.version != 2:
- print("Invalid MeshData version %d, expected 2" % self.version)
- return False
- # Read number of vertices
- self.num_vertices = read_int(file_object)
- # Read vertex-array data
- for i in range(15):
- vertex_array = VertexArray()
- vertex_array.read(self.num_vertices, file_object)
- if vertex_array.data != None:
- self.vertex_arrays[i] = vertex_array
- # Read number of indices
- self.num_indices = read_int(file_object)
- # Read index buffer data
- if self.num_indices > 0:
- self.indices = self.num_indices * [0]
- for i in range(self.num_indices):
- self.indices[i] = read_int(file_object)
- # Read number of segments
- self.num_segments = read_int(file_object)
- # Read MeshSegment data
- if self.num_segments > 0:
- self.segments = self.num_segments * [None]
- for i in range(self.num_segments):
- segment = MeshSegment()
- segment.read(file_object)
- self.segments[i] = segment
- # Read bounds information
- self.bound_center = read_float3(file_object)
- self.bound_radius = read_float(file_object)
- self.bound_min = read_float3(file_object)
- self.bound_max = read_float3(file_object)
- return True
- # MeshEntity
- class MeshEntity():
- __slots__ = (
- "mesh_data",
- "num_bones",
- "bones",
- "emissive_color",
- "cast_shadow",
- )
- def __init__(self):
- self.mesh_data = None
- self.num_bones = 0
- self.bones = None
- self.emissive_color = Vec3()
- self.cast_shadow = False
- # read
- # Reads MeshEntity using Grimrock 1 documentation
- # MeshData
- # int32 numBones
- # Bone * numBones
- # Vec3 emissiveColor
- # byte castShadows
- def read(self, file_object):
- # Read mesh data
- self.mesh_data = MeshData()
- if not self.mesh_data.read(file_object):
- return False
- # Read number of bones
- self.num_bones = read_int(file_object)
- # Read bones data
- if self.num_bones > 0:
- self.bones = self.num_bones * [None]
- for i in range(self.num_bones):
- bone = Bone()
- bone.read(file_object)
- self.bones[i] = bone
- # Read emissive color
- self.emissive_color.read(file_object)
- # Read cast shadows property
- cast_shadows = read_byte(file_object)
- if cast_shadows != 0:
- self.cast_shadow = True
- else:
- self.cast_shadow = False
- return True
- # Node
- class Node():
- __slots__ = (
- "name",
- "mat_local_to_parent",
- "parent",
- "type",
- "mesh_entity",
- )
- def __init__(self):
- self.name = ""
- self.mat_local_to_parent = Mat4x3()
- self.parent = -1
- self.type = -1
- self.mesh_entity = None
- # read
- # Reads a Node using Grimrock 1 documentation
- # string name
- # Mat4x3 localToParent
- # int32 parent
- # int32 type
- # (MeshEntity)
- def read(self, file_object):
- # Read name of node
- self.name = read_len_string(file_object)
- # Read local to parent transform
- self.mat_local_to_parent.read(file_object)
- # Read parent node
- self.parent = read_int(file_object)
- # Read node type
- self.type = read_int(file_object)
- # Read mesh entity if type is zero
- if self.type == 0:
- self.mesh_entity = MeshEntity()
- if not self.mesh_entity.read(file_object):
- return False
- return True
- # Model
- class Model():
- __slots__ = (
- "magic",
- "version",
- "num_nodes",
- "nodes",
- )
- def __init__(self):
- self.magic = 0
- self.version = 0
- self.num_nodes = 0
- self.nodes = None
- # read
- # Reads a ModelFile using Grimrock 1 documentation
- # int32 magic
- # int32 version
- # int32 numNodes
- # Node * numNodes
- def read(self, file_object):
- # Read magic, skip if not equal 'MDL1'
- self.magic = read_magic(file_object)
- if self.magic != b'MDL1':
- print("Invalid ModelFile magic '%s', expected 'MDL1'" % self.magic)
- return False
- # Read version, skip if not equal 2
- self.version = read_int(file_object)
- if self.version != 2:
- print("Invalid ModelFile version %d, expected 2" % self.version)
- return False
- # Read number of nodes
- self.num_nodes = read_int(file_object)
- # Read in nodes
- if self.num_nodes > 0:
- self.nodes = self.num_nodes * [None]
- for i in range(self.num_nodes):
- node = Node()
- node.read(file_object)
- self.nodes[i] = node
- return True
- # file_entry
- # File entry information in .dat container
- class file_entry(object):
- __slots__ = (
- "hash_name",
- "file_offset",
- "size_compressed",
- "size_uncompressed",
- "unknown",
- "name",
- )
- def __init__(self):
- self.hash_name = 0
- self.file_offset = 0
- self.size_compressed = 0
- self.size_uncompressed = 0
- self.unknown = 0
- self.name = None
- # load_binary_model
- # Loads a binary model using Grimrock 1 documentation
- # FourCC magic
- # int32 version
- # int32 numNodes
- # (Node) * numNodes
- def load_binary_model(file_object, context):
- # Try to read the binary model
- model = Model()
- if not model.read(file_object):
- print("Failed to load ModelFile")
- return False
- build_model(model)
- return True
- def get_vertex_array(mesh_data, vtx_array_type):
- if mesh_data.vertex_arrays[vtx_array_type] != None:
- return mesh_data.vertex_arrays[vtx_array_type].data
- return None
- # convert_colors
- # Converts an array of byte range [0, 255] colors to float range [0, 1] values
- # It simpl does so by multiplying all values with the reciprocal of 255
- def convert_colors(byte_colors):
- num = len(byte_colors)
- dim = len(byte_colors[0])
- float_colors = num * [dim * [0.0]]
- scale = 1.0 / 255.0
- for i in range(num):
- for j in range(dim):
- float_colors[i][j] = float( byte_colors[i][j] ) * scale
- return float_colors
- # build_model
- # Builds Blender objects from Grimrock Model
- def build_model(model):
- # Before adding any meshes or armatures go into Object mode.
- if bpy.ops.object.mode_set.poll():
- bpy.ops.object.mode_set(mode='OBJECT')
- for node in model.nodes:
- if node.mesh_entity == None:
- continue
- mesh_data = node.mesh_entity.mesh_data
- positions = get_vertex_array(mesh_data, VTX_ARRAY_POSITION)
- normals = get_vertex_array(mesh_data, VTX_ARRAY_NORMAL)
- colors = get_vertex_array(mesh_data, VTX_ARRAY_COLOR)
- indices = mesh_data.indices
- num_faces = int( mesh_data.num_indices / 3 )
- # Create Mesh to work with
- me = bpy.data.meshes.new(node.name)
- # Add vertices
- # Flip y-z component so it looks more natural in blender
- me.vertices.add(mesh_data.num_vertices)
- for i in range(mesh_data.num_vertices):
- me.vertices[i].co = (positions[i][0], positions[i][2], positions[i][1])
- # Add normals
- if normals != None:
- for i in range(mesh_data.num_vertices):
- me.vertices[i].normal = (normals[i][0], normals[i][2], normals[i][1])
- # Add faces
- # Flip indices because of vertex flip
- me.tessfaces.add(num_faces)
- for i in range(num_faces):
- idx = i * 3
- me.tessfaces[i].vertices_raw = (indices[idx+2], indices[idx+1], indices[idx+0], 0)
- # Add colors
- if colors != None:
- # Create color-set layer
- color_layer = me.tessface_vertex_colors.new("colorset")
- me.tessface_vertex_colors.active = color_layer
- # Convert to float range
- float_colors = convert_colors(colors)
- # Assign colors
- for f in me.tessfaces:
- color_layer.data[f.index].color1 = float_colors[f.vertices[0]][0:3]
- color_layer.data[f.index].color2 = float_colors[f.vertices[1]][0:3]
- color_layer.data[f.index].color3 = float_colors[f.vertices[2]][0:3]
- # Add texture coordinate sets
- first_uv_layer = None
- for i in range(8):
- tex_coords = get_vertex_array(mesh_data, VTX_ARRAY_TEXCOORD0 + i)
- if tex_coords == None:
- continue
- # Create uv-layer for these texture coordinates
- uv_layer = me.tessface_uv_textures.new("uvset%d" % i)
- me.tessface_uv_textures.active = uv_layer
- if first_uv_layer == None:
- first_uv_layer = uv_layer
- # Assign uv coordinates to layer faces
- for f in me.tessfaces:
- uvco1 = tex_coords[f.vertices[0]][0:2]
- uvco2 = tex_coords[f.vertices[1]][0:2]
- uvco3 = tex_coords[f.vertices[2]][0:2]
- # Flip v coordinates
- uvco1 = (uvco1[0], 1.0 - uvco1[1])
- uvco2 = (uvco2[0], 1.0 - uvco2[1])
- uvco3 = (uvco3[0], 1.0 - uvco3[1])
- uv_layer.data[f.index].uv = (uvco1, uvco2, uvco3)
- # Set first created uv-layer as active layer
- if first_uv_layer != None:
- me.tessface_uv_textures.active = first_uv_layer
- # Create object and link with scene
- ob = bpy.data.objects.new(node.name, me)
- bpy.context.scene.objects.link(ob)
- # Update mesh
- me.update()
- # Update scene
- bpy.context.scene.update()
- # fnv1a
- # Hash a string using fnv1-a
- def fnv1a(string, seed = 0x811C9DC5, prime = 0x01000193):
- uint32_max = 2 ** 32
- hash_value = seed
- for c in string:
- hash_value = ( ( ord( c ) ^ hash_value ) * prime ) % uint32_max
- return hash_value
- # load_model_table
- # Loads .dat container model information
- def load_model_table(filename):
- model_table = []
- file_object = open(filename, 'rb')
- dat_magic = 0
- try:
- dat_magic = read_magic(file_object)
- except:
- print("Error parsing .dat file header!")
- file_object.close()
- return model_table
- # Figure out if it's a valid dat package
- if dat_magic != b'GRA2':
- print("Not a valid Legend of Grimrock 2 .dat file!")
- file_object.close()
- return model_table
- # Read number of file headers
- num_files = read_int(file_object)
- # Read in the file header table for all entries
- file_table = num_files * [None]
- for i in range(num_files):
- entry = file_entry()
- entry.hash_name = read_uint(file_object)
- entry.file_offset = read_uint(file_object)
- entry.size_compressed = read_uint(file_object)
- entry.size_uncompressed = read_uint(file_object)
- entry.unknown = read_uint(file_object)
- file_table[ i ] = entry
- # Only care about lua script files and model files
- # We use the lua to scan for model asset names so we can name the model table properly
- magic_lua = 0x014a4c1b
- magic_model = 0x314c444d #b'MDL1'
- # Create search strings to use when scaning the lua files
- model_search = b'assets/models/'
- fbx_search = b'.fbx'
- # Seek to all file entries, read the magic and place table indices for different file types
- model_names = {}
- for entry in file_table:
- # Haven't come across any, think I recall them being zlib compressed ?
- # Skip them for now
- if entry.size_compressed != 0:
- print("Compressed file in .dat package, skipping entry")
- continue
- # Haven't come across any, I have no idea what this might be
- # Skip these entries for now
- if entry.unknown != 0:
- print("Found unknown data in .dat package, skipping entry")
- continue
- # Seek to start of internal file and read the first four bytes and treat them as
- # a 'type' magic of what that file entry actually is.
- # This is of course not correct, but for the sake of finding models and lua files it works just fine.
- file_object.seek(entry.file_offset, os.SEEK_SET)
- file_magic = read_uint(file_object)
- # Handle lua file entries
- if file_magic == magic_lua:
- # Read out the entire contents of the file into a bytearray
- lua_buffer = bytearray(file_object.read(entry.size_uncompressed))
- # Search the bytearray 'for assets/models/' until no more could be found
- buffer_index = lua_buffer.find(model_search, 0)
- while buffer_index >= 0:
- # Lookup .fbx ending
- # Now, we could potentially found .fbx several hundred bytes later.
- # It's a bit dangerous to assume it always ends with .fbx, but it works for now
- end_index = lua_buffer.find(fbx_search, buffer_index + 14)
- # If we didn't find an .fbx ending, abort now
- if end_index < 0:
- break
- # Decode a string from our search indices and append a .model ending
- asset_name = decode_string(lua_buffer[buffer_index:end_index]) + ".model"
- # Use FNV1a to hash the asset name
- hash_name = fnv1a(asset_name)
- # If hash name isn't already in list, append a new entry pointing to the real asset name
- # so we can 'decode' file names later
- if hash_name not in model_names:
- model_names[hash_name] = asset_name
- # Restart the search from the end of the last search result
- buffer_index = lua_buffer.find(model_search, end_index + 4)
- # Handle model file entries, this simply adds the table entry to the model table
- if file_magic == magic_model:
- model_table.append(entry)
- # We're done with the file for now, close it
- file_object.close()
- # Go through out model table and attempt to resolve all the asset names
- # If we couldn't find one, simply build a name using the hash name
- num_unresolved = 0
- for entry in model_table:
- if entry.hash_name in model_names:
- entry.name = model_names[entry.hash_name]
- else:
- entry.name = "hash_%8x" % entry.hash_name
- num_unresolved += 1
- return model_table
- # load_binary
- def load_binary(filename, file_offset, context):
- name, ext = os.path.splitext(os.path.basename(filename))
- # Seek to offset in file, this is only used if loading from .dat containers
- file_object = open(filename, 'rb')
- if file_offset > 0:
- file_object.seek(file_offset, os.SEEK_SET)
- # Load the binary model data
- load_binary_model(file_object, context)
- # Close file if load_binary_model hasn't already done so
- if not file_object.closed:
- file_object.close()
- return True
- class IMPORT_OT_model(bpy.types.Operator, ImportHelper):
- # Import Model Operator.
- bl_idname = "import_scene.model"
- bl_label = "Import Model"
- bl_description = "Import a Legend of Grimrock 2 model"
- bl_options = { 'REGISTER', 'UNDO' }
- # File selection UI property
- filepath = StringProperty(name="File Path", description="Filepath used for importing the mesh file.", maxlen=1024, default="")
- # File list UI properties for .dat containers
- file_list = CollectionProperty(type=bpy.types.PropertyGroup)
- file_list_index = IntProperty()
- # Holds information if .dat container is being imported with specific model selection
- dat_file = ""
- model_table = []
- def execute(self, context):
- file_offset = 0
- # Dig out file offset if loading from .dat container
- if self.dat_file == self.filepath:
- file_offset = self.model_table[self.file_list_index].file_offset
- # Load from binary file
- load_binary(self.filepath, file_offset, context)
- return {'FINISHED'}
- # clear_file_list
- # Clears the file_list UI property of all entries and resets the dat_file cached value
- def clear_file_list(self):
- self.dat_file = ""
- num = len(self.file_list)
- while num > 0:
- self.file_list.remove(num-1)
- num -= 1
- # build_file_list
- # Updates the file_list UI property from selected .dat file, or cleans it out if needed
- def build_file_list(self):
- # Figure out if we selected a .dat file or if we slected a different .dat file
- name, ext = os.path.splitext(os.path.basename(self.filepath))
- if ext.lower() != ".dat":
- self.clear_file_list()
- return
- # Cached dat_file is still up to date, simply ignore any updates
- if self.filepath == self.dat_file:
- return
- # Clean out any previous entries in the UI file list
- self.clear_file_list()
- # Load package header and extract model information
- self.dat_file = self.filepath
- self.model_table = load_model_table(self.filepath)
- # Add all the model table entries to the UI file list
- for entry in self.model_table:
- item = self.file_list.add()
- item.name = entry.name
- # draw
- def draw(self, context):
- layout = self.layout
- # Update the file_list UI property if needed
- self.build_file_list()
- row = layout.row(True)
- row.label("Legend of Grimrock 2 .dat container")
- layout.template_list("UI_UL_list", "OpenFileDAT", self, "file_list", self, "file_list_index", rows=15)
- def invoke(self, context, event):
- wm = context.window_manager
- wm.fileselect_add(self)
- return {'RUNNING_MODAL'}
- def menu_func(self, context):
- self.layout.operator(IMPORT_OT_model.bl_idname, text="Legend of Grimrock 2 Model (.model)")
- def register():
- bpy.utils.register_module(__name__)
- bpy.types.INFO_MT_file_import.append(menu_func)
- def unregister():
- bpy.utils.unregister_module(__name__)
- bpy.types.INFO_MT_file_import.remove(menu_func)
- if __name__ == "__main__":
- register()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement