Advertisement
Guest User

Blender - Legend of Grimrock 2 importer (.model / .dat )

a guest
Nov 2nd, 2014
945
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 31.62 KB | None | 0 0
  1. # bitcpy
  2.  
  3. # Loads a Legend of Grimrock 2 model from either a .model file or a .dat container
  4. # Names of models in .dat container are determined by doing an extremly simple scan of lua compiled files.
  5.  
  6. bl_info = {
  7.     "name": "Legend of Grimrock 2 Model Format (.model)",
  8.     "author": "",
  9.     "version": (1, 0, 0),
  10.     "blender": (2, 71, 0),
  11.     "api": 36339,
  12.     "location": "File > Import > Legend of Grimrock 2 Model (.model)",
  13.     "description": "Import Legend of Grimrock Models (.model)",
  14.     "warning": "",
  15.     "wiki_url": "",
  16.     "tracker_url": "",
  17.     "category": "Import-Export"}
  18.  
  19. import os
  20. import struct
  21. import sys
  22.  
  23. import bpy
  24. import bmesh
  25.  
  26. from mathutils import *
  27.  
  28. from bpy.props import *
  29. from bpy_extras.io_utils import ExportHelper, ImportHelper
  30. from bpy_extras.image_utils import load_image
  31.  
  32.  
  33. # Vertex data types
  34. VTX_DATA_BYTE   = 0
  35. VTX_DATA_SHORT  = 1
  36. VTX_DATA_INT    = 2
  37. VTX_DATA_FLOAT  = 3
  38.  
  39.  
  40. # Vertex array types
  41. VTX_ARRAY_POSITION      = 0
  42. VTX_ARRAY_NORMAL        = 1
  43. VTX_ARRAY_TANGENT       = 2
  44. VTX_ARRAY_BITANGENT     = 3
  45. VTX_ARRAY_COLOR         = 4
  46. VTX_ARRAY_TEXCOORD0     = 5
  47. VTX_ARRAY_TEXCOORD1     = 6
  48. VTX_ARRAY_TEXCOORD2     = 7
  49. VTX_ARRAY_TEXCOORD3     = 8
  50. VTX_ARRAY_TEXCOORD4     = 9
  51. VTX_ARRAY_TEXCOORD5     = 10
  52. VTX_ARRAY_TEXCOORD6     = 11
  53. VTX_ARRAY_TEXCOORD7     = 12
  54. VTX_ARRAY_BONE_INDEX    = 13
  55. VTX_ARRAY_BONE_WEIGHT   = 14
  56.  
  57.  
  58. # Reads file magic from file
  59. def read_magic(file_object, endian = '<'):
  60.     data = struct.unpack(endian+"4s", file_object.read(4))[0]
  61.     return data;
  62.  
  63. # read_uint
  64. #   Read unsigned integer from file
  65. def read_uint(file_object, endian = '<'):
  66.     data = struct.unpack(endian+'I', file_object.read(4))[0]
  67.     return data
  68.  
  69. # read_int
  70. #   Read signed integer from file
  71. def read_int(file_object, endian = '<'):
  72.     data = struct.unpack(endian+'i', file_object.read(4))[0]
  73.     return data
  74.  
  75. # read_int2
  76. #   Read two signed integers from file
  77. def read_int2(file_object, endian = '<'):
  78.     data = struct.unpack(endian+'ii', file_object.read(8))
  79.     return data
  80.  
  81. # read_int3
  82. #   Read three signed integers from file    
  83. def read_int3(file_object, endian = '<'):
  84.     data = struct.unpack(endian+'iii', file_object.read(12))
  85.     return data
  86.    
  87. # read_int4
  88. #   Read four signed integers from file
  89. def read_int4(file_object, endian = '<'):
  90.     data = struct.unpack(endian+'iiii', file_object.read(16))
  91.     return data
  92.  
  93. # read_float
  94. #   Read float from file
  95. def read_float(file_object, endian = '<'):
  96.     data = struct.unpack(endian+'f', file_object.read(4))[0]
  97.     return data
  98.  
  99. # read_float2
  100. #   Read two floats from file
  101. def read_float2(file_object, endian = '<'):
  102.     data = struct.unpack(endian+'ff', file_object.read(8))
  103.     return data
  104.  
  105. # read_float3
  106. #   Read three floats from file
  107. def read_float3(file_object, endian = '<'):
  108.     data = struct.unpack(endian+'fff', file_object.read(12))
  109.     return data
  110.  
  111. # read_float4
  112. #   Read four floats from file
  113. def read_float4(file_object, endian = '<'):
  114.     data = struct.unpack(endian+'ffff', file_object.read(16))
  115.     return data
  116.  
  117. # read_matrix4x3
  118. #   Read a matrix consisting of four rows with three columns
  119. def read_matrix4x3(file_object, endian = '<'):
  120.     data = struct.unpack(endian+'ffffffffffff', file_object.read(48))
  121.     return data
  122.  
  123. # read_short
  124. #   Read signed short from file
  125. def read_short(file_object, endian = '<'):
  126.     data = struct.unpack(endian+'h', file_object.read(2))[0]
  127.     return data
  128.  
  129. # read_short2
  130. #   Read two signed shorts from file
  131. def read_short2(file_object, endian = '<'):
  132.     data = struct.unpack(endian+'hh', file_object.read(4))
  133.     return data
  134.  
  135. # read_short3
  136. #   Read three signed shorts from file
  137. def read_short3(file_object, endian = '<'):
  138.     data = struct.unpack(endian+'hhh', file_object.read(6))
  139.     return data
  140.  
  141. # read_short4
  142. #   Read four signed shorts from file
  143. def read_short4(file_object, endian = '<'):
  144.     data = struct.unpack(endian+'hhhh', file_object.read(8))
  145.     return data
  146.  
  147. # read_byte
  148. #   Read unsigned byte from file
  149. def read_byte(file_object, endian = '<'):
  150.     data = struct.unpack(endian+'B', file_object.read(1))[0]
  151.     return data
  152.  
  153. # read_byte2
  154. #   Read two unsigned bytes from file
  155. def read_byte2(file_object, endian = '<'):
  156.     data = struct.unpack(endian+'BB', file_object.read(2))
  157.     return data
  158.  
  159. # read_byte3
  160. #   Read three unsigned bytes from file
  161. def read_byte3(file_object, endian = '<'):
  162.     data = struct.unpack(endian+'BBB', file_object.read(3))
  163.     return data
  164.  
  165. # read_byte4
  166. #   Read four unsigned bytes from file
  167. def read_byte4(file_object, endian = '<'):
  168.     data = struct.unpack(endian+'BBBB', file_object.read(4))
  169.     return data
  170.  
  171. # read_string
  172. #   Read string from file
  173. def read_string(file_object, num, endian = '<'):
  174.     raw_string = struct.unpack(endian+str(num)+'s', file_object.read(num))[0]
  175.     data = raw_string.decode("utf-8", "ignore")
  176.     return data
  177.  
  178. # read_len_string
  179. #   Read unsigned integer from file used to read back a string using integer as length from the same file object
  180. def read_len_string(file_object, endian = '<'):
  181.     num = read_int(file_object, endian)
  182.     return read_string(file_object, num, endian)
  183.  
  184. # decode_string
  185. #   Decode string from buffer
  186. def decode_string(buffer_object, endian = '<'):
  187.     raw_string = struct.unpack(endian+str(len(buffer_object))+'s', buffer_object)[0]
  188.     data = raw_string.decode("utf-8", "ignore")
  189.     return data
  190.  
  191.  
  192. # Vec3
  193. class Vec3():
  194.     __slots__ = (
  195.         "x",
  196.         "y",
  197.         "z",
  198.         )
  199.  
  200.     def __init__(self, x = 0.0, y = 0.0, z = 0.0):
  201.         self.x = x
  202.         self.y = y
  203.         self.z = z
  204.  
  205.     # read
  206.     #   Reads a 3 dimensional vector using Grimrock 1 documentation
  207.     #       float x
  208.     #       float y
  209.     #       float z
  210.     def read(self, file_object):
  211.         self.x = read_float(file_object)
  212.         self.y = read_float(file_object)
  213.         self.z = read_float(file_object)
  214.  
  215.  
  216. # Mat4x3
  217. class Mat4x3():
  218.     __slots__ = (
  219.         "rows"
  220.         )
  221.  
  222.     def __init__(self):
  223.         self.rows = [
  224.             Vec3( x = 1.0, y = 0.0, z = 0.0 ),
  225.             Vec3( x = 0.0, y = 1.0, z = 0.0 ),
  226.             Vec3( x = 0.0, y = 0.0, z = 1.0 ),
  227.             Vec3( x = 0.0, y = 0.0, z = 0.0 )
  228.         ]
  229.  
  230.     # read
  231.     #   Reads a 4x3 matrix using Grimrock 1 documentation
  232.     #       vec3 baseX
  233.     #       vec3 baseY
  234.     #       vec3 baseZ
  235.     #       vec3 translation
  236.     def read(self, file_object):
  237.         for i in range(4):
  238.             self.rows[i].read(file_object)
  239.  
  240.  
  241. # Bone
  242. class Bone():
  243.     __slots__ = (
  244.         "node_index",
  245.         "mat_model_to_bone",
  246.         )
  247.  
  248.     def __init__(self):
  249.         self.node_index = -1
  250.         self.mat_model_to_bone = Mat4x3()
  251.  
  252.     # read
  253.     #   Reads a Bone structure using Grimrock 1 documentation
  254.     #       int32 boneNodeIndex
  255.     #       Mat4x3 invRestMatrix
  256.     def read(self, file_object):
  257.         self.node_index = read_int(file_object)
  258.         self.mat_model_to_bone.read(file_object)
  259.  
  260.  
  261. # MeshSegment
  262. class MeshSegment():
  263.     __slots__ = (
  264.         "material",
  265.         "primitive_type",
  266.         "index_offset",
  267.         "num_triangles",
  268.         )
  269.  
  270.     def __init__(self):
  271.         self.material = None
  272.         self.primitive_type = 0
  273.         self.index_offset = 0
  274.         self.num_triangles = 0
  275.  
  276.     # read
  277.     #   Reads a MeshSegment using Grimrock 1 documentation
  278.     #       string material
  279.     #       int32 primitiveType
  280.     #       int32 firstIndex
  281.     #       int32 count
  282.     def read(self, file_object):
  283.         self.material = read_len_string(file_object)
  284.         self.primitive_type = read_int(file_object)
  285.         self.index_offset = read_int(file_object)
  286.         self.num_triangles = read_int(file_object)
  287.  
  288.  
  289. # VertexArray
  290. class VertexArray():
  291.     __slots__ = (
  292.         "data_type",
  293.         "dim",
  294.         "stride",
  295.         "data",
  296.         )
  297.  
  298.     def __init__(self):
  299.         self.data_type = 0
  300.         self.dim = 0
  301.         self.stride = 0
  302.         self.data = None
  303.  
  304.     # is_valid_type
  305.     #   Helper to determine if data_type value is valid for read_data_type calls
  306.     def is_valid_type(self):
  307.         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)
  308.  
  309.     # is_valid_dim
  310.     #   Helper to determine if dimensional value is in valid range for read_data_type calls
  311.     def is_valid_dim(self):
  312.         return (self.dim >= 1 and self.dim <= 4)
  313.  
  314.     # read_data_type
  315.     #   Helper to read dimensional attribute of each data type, (ex. 3 bytes, 2 floats, 4 shorts etc.)
  316.     def read_data_type(self, file_object):
  317.         if self.data_type == VTX_DATA_BYTE:
  318.             if self.dim == 1:
  319.                 return read_byte(file_object)
  320.             elif self.dim == 2:
  321.                 return read_byte2(file_object)
  322.             elif self.dim == 3:
  323.                 return read_byte3(file_object)
  324.             elif self.dim == 4:
  325.                 return read_byte4(file_object)
  326.         elif self.data_type == VTX_DATA_SHORT:
  327.             if self.dim == 1:
  328.                 return read_short(file_object)
  329.             elif self.dim == 2:
  330.                 return read_short2(file_object)
  331.             elif self.dim == 3:
  332.                 return read_short3(file_object)
  333.             elif self.dim == 4:
  334.                 return read_short4(file_object)
  335.         elif self.data_type == VTX_DATA_INT:
  336.             if self.dim == 1:
  337.                 return read_int(file_object)
  338.             elif self.dim == 2:
  339.                 return read_int2(file_object)
  340.             elif self.dim == 3:
  341.                 return read_int3(file_object)
  342.             elif self.dim == 4:
  343.                 return read_int4(file_object)
  344.         elif self.data_type == VTX_DATA_FLOAT:
  345.             if self.dim == 1:
  346.                 return read_float(file_object)
  347.             elif self.dim == 2:
  348.                 return read_float2(file_object)
  349.             elif self.dim == 3:
  350.                 return read_float3(file_object)
  351.             elif self.dim == 4:
  352.                 return read_float4(file_object)
  353.  
  354.     # read
  355.     #   Reads VertexArray data using Grimrock 1 documentation
  356.     #       int32 dataType
  357.     #       int32 dim
  358.     #       int32 stride
  359.     #       byte num_vertices*stride
  360.     def read(self, num_vertices, file_object):
  361.         # Read type, dimension and stride
  362.         self.data_type = read_int(file_object)
  363.         self.dim = read_int(file_object)
  364.         self.stride = read_int(file_object)
  365.        
  366.         # Skip if size between vertex to vertex is zero
  367.         if self.stride == 0:
  368.             return
  369.  
  370.         # Pre allocate the data, read data type if valid otherwise just call raw read for each entry
  371.         self.data = num_vertices * [None]
  372.         if self.is_valid_type() and self.is_valid_dim():
  373.             for i in range(num_vertices):
  374.                 self.data[i] = self.read_data_type(file_object)
  375.         else:
  376.             print("Unknown VertexArray data, type(%d), dimension(%d), stride(%d)" % (self.data_type, self.dim, self.stride))
  377.             for i in range(num_vertices):
  378.                 self.data[i] = file_object.read(self.stride)
  379.  
  380.  
  381. # MeshData
  382. class MeshData():
  383.     __slots__ = (
  384.         "magic",
  385.         "version",
  386.         "num_vertices",
  387.         "vertex_arrays",
  388.         "num_indices",
  389.         "indices",
  390.         "num_segments",
  391.         "segments",
  392.         "bound_center",
  393.         "bound_radius",
  394.         "bound_min",
  395.         "bound_max",
  396.         )
  397.  
  398.     def __init__(self):
  399.         self.magic = 0
  400.         self.version = 0
  401.         self.num_vertices = 0
  402.         self.vertex_arrays = 15 * [None]
  403.         self.num_indices = 0
  404.         self.indices = None
  405.         self.num_segments = 0
  406.         self.segments = None
  407.         self.bound_center = [0.0, 0.0, 0.0]
  408.         self.bound_radius = 0.0
  409.         self.bound_min = [0.0, 0.0, 0.0]
  410.         self.bound_max = [0.0, 0.0, 0.0]
  411.  
  412.     # read
  413.     #   Reads MeshData using Grimrock 1 documentation
  414.     #       FourCC magic
  415.     #       int32 version
  416.     #       int32 numVertices
  417.     #       VertexArray * 15
  418.     #       int32 numIndices
  419.     #       int32 * numIndices
  420.     #       int32 numSegents
  421.     #       MeshSegment * numSegments
  422.     #       vec3 boundCenter
  423.     #       float boundRadius
  424.     #       vec3 boundMin
  425.     #       vec3 boundMax
  426.     def read(self, file_object):
  427.         # Read MeshData magic, skip if not equal 'MESH'
  428.         self.magic = read_magic(file_object)
  429.         if self.magic != b'MESH':
  430.             print("Invalid MeshData magic '%s', expected 'MESH'" % self.magic)
  431.             return False
  432.  
  433.         # Read version, skip if version isn't equal to 2
  434.         self.version = read_int(file_object)
  435.         if self.version != 2:
  436.             print("Invalid MeshData version %d, expected 2" % self.version)
  437.             return False
  438.  
  439.         # Read number of vertices
  440.         self.num_vertices = read_int(file_object)
  441.  
  442.         # Read vertex-array data
  443.         for i in range(15):
  444.             vertex_array = VertexArray()
  445.             vertex_array.read(self.num_vertices, file_object)
  446.             if vertex_array.data != None:
  447.                 self.vertex_arrays[i] = vertex_array
  448.  
  449.         # Read number of indices
  450.         self.num_indices = read_int(file_object)
  451.  
  452.         # Read index buffer data
  453.         if self.num_indices > 0:
  454.             self.indices = self.num_indices * [0]
  455.             for i in range(self.num_indices):
  456.                 self.indices[i] = read_int(file_object)
  457.  
  458.         # Read number of segments
  459.         self.num_segments = read_int(file_object)
  460.        
  461.         # Read MeshSegment data
  462.         if self.num_segments > 0:
  463.             self.segments = self.num_segments * [None]
  464.             for i in range(self.num_segments):
  465.                 segment = MeshSegment()
  466.                 segment.read(file_object)
  467.                 self.segments[i] = segment
  468.  
  469.         # Read bounds information
  470.         self.bound_center = read_float3(file_object)
  471.         self.bound_radius = read_float(file_object)
  472.         self.bound_min = read_float3(file_object)
  473.         self.bound_max = read_float3(file_object)
  474.  
  475.         return True
  476.  
  477.  
  478. # MeshEntity
  479. class MeshEntity():
  480.     __slots__ = (
  481.         "mesh_data",
  482.         "num_bones",
  483.         "bones",
  484.         "emissive_color",
  485.         "cast_shadow",
  486.         )
  487.  
  488.     def __init__(self):
  489.         self.mesh_data = None
  490.         self.num_bones = 0
  491.         self.bones = None
  492.         self.emissive_color = Vec3()
  493.         self.cast_shadow = False
  494.  
  495.     # read
  496.     #   Reads MeshEntity using Grimrock 1 documentation
  497.     #       MeshData
  498.     #       int32 numBones
  499.     #       Bone * numBones
  500.     #       Vec3 emissiveColor
  501.     #       byte castShadows
  502.     def read(self, file_object):
  503.         # Read mesh data
  504.         self.mesh_data = MeshData()
  505.         if not self.mesh_data.read(file_object):
  506.             return False
  507.  
  508.         # Read number of bones
  509.         self.num_bones = read_int(file_object)
  510.  
  511.         # Read bones data
  512.         if self.num_bones > 0:
  513.             self.bones = self.num_bones * [None]
  514.             for i in range(self.num_bones):
  515.                 bone = Bone()
  516.                 bone.read(file_object)
  517.                 self.bones[i] = bone
  518.  
  519.         # Read emissive color
  520.         self.emissive_color.read(file_object)
  521.  
  522.         # Read cast shadows property
  523.         cast_shadows = read_byte(file_object)
  524.         if cast_shadows != 0:
  525.             self.cast_shadow = True
  526.         else:
  527.             self.cast_shadow = False
  528.  
  529.         return True
  530.  
  531.  
  532. # Node
  533. class Node():
  534.     __slots__ = (
  535.         "name",
  536.         "mat_local_to_parent",
  537.         "parent",
  538.         "type",
  539.         "mesh_entity",
  540.         )
  541.  
  542.     def __init__(self):
  543.         self.name = ""
  544.         self.mat_local_to_parent = Mat4x3()
  545.         self.parent = -1
  546.         self.type = -1
  547.         self.mesh_entity = None
  548.  
  549.     # read
  550.     #   Reads a Node using Grimrock 1 documentation
  551.     #       string name
  552.     #       Mat4x3 localToParent
  553.     #       int32 parent
  554.     #       int32 type
  555.     #       (MeshEntity)
  556.     def read(self, file_object):
  557.         # Read name of node
  558.         self.name = read_len_string(file_object)
  559.  
  560.         # Read local to parent transform
  561.         self.mat_local_to_parent.read(file_object)
  562.        
  563.         # Read parent node
  564.         self.parent = read_int(file_object)
  565.  
  566.         # Read node type
  567.         self.type = read_int(file_object)
  568.  
  569.         # Read mesh entity if type is zero
  570.         if self.type == 0:
  571.             self.mesh_entity = MeshEntity()
  572.             if not self.mesh_entity.read(file_object):
  573.                 return False
  574.  
  575.         return True
  576.  
  577.  
  578. # Model
  579. class Model():
  580.     __slots__ = (
  581.         "magic",
  582.         "version",
  583.         "num_nodes",
  584.         "nodes",
  585.         )
  586.  
  587.     def __init__(self):
  588.         self.magic = 0
  589.         self.version = 0
  590.         self.num_nodes = 0
  591.         self.nodes = None
  592.  
  593.     # read
  594.     #   Reads a ModelFile using Grimrock 1 documentation
  595.     #       int32 magic
  596.     #       int32 version
  597.     #       int32 numNodes
  598.     #       Node * numNodes
  599.     def read(self, file_object):
  600.         # Read magic, skip if not equal 'MDL1'
  601.         self.magic = read_magic(file_object)
  602.         if self.magic != b'MDL1':
  603.             print("Invalid ModelFile magic '%s', expected 'MDL1'" % self.magic)
  604.             return False
  605.  
  606.         # Read version, skip if not equal 2
  607.         self.version = read_int(file_object)
  608.         if self.version != 2:
  609.             print("Invalid ModelFile version %d, expected 2" % self.version)
  610.             return False
  611.  
  612.         # Read number of nodes
  613.         self.num_nodes = read_int(file_object)
  614.  
  615.         # Read in nodes
  616.         if self.num_nodes > 0:
  617.             self.nodes = self.num_nodes * [None]
  618.             for i in range(self.num_nodes):
  619.                 node = Node()
  620.                 node.read(file_object)
  621.                 self.nodes[i] = node
  622.  
  623.         return True
  624.  
  625.  
  626. # file_entry
  627. #   File entry information in .dat container
  628. class file_entry(object):
  629.     __slots__ = (
  630.         "hash_name",
  631.         "file_offset",
  632.         "size_compressed",
  633.         "size_uncompressed",
  634.         "unknown",
  635.         "name",
  636.         )
  637.        
  638.     def __init__(self):
  639.         self.hash_name = 0
  640.         self.file_offset = 0
  641.         self.size_compressed = 0
  642.         self.size_uncompressed = 0
  643.         self.unknown = 0
  644.         self.name = None
  645.  
  646.  
  647. # load_binary_model
  648. #   Loads a binary model using Grimrock 1 documentation
  649. #       FourCC magic
  650. #       int32 version
  651. #       int32 numNodes
  652. #       (Node) * numNodes
  653. def load_binary_model(file_object, context):
  654.  
  655.     # Try to read the binary model
  656.     model = Model()
  657.     if not model.read(file_object):
  658.         print("Failed to load ModelFile")
  659.         return False
  660.  
  661.     build_model(model)
  662.  
  663.     return True
  664.  
  665.  
  666. def get_vertex_array(mesh_data, vtx_array_type):
  667.     if mesh_data.vertex_arrays[vtx_array_type] != None:
  668.         return mesh_data.vertex_arrays[vtx_array_type].data
  669.     return None
  670.  
  671.  
  672. # convert_colors
  673. #   Converts an array of byte range [0, 255] colors to float range [0, 1] values
  674. #   It simpl does so by multiplying all values with the reciprocal of 255
  675. def convert_colors(byte_colors):
  676.     num = len(byte_colors)
  677.     dim = len(byte_colors[0])
  678.     float_colors = num * [dim * [0.0]]
  679.     scale = 1.0 / 255.0
  680.     for i in range(num):
  681.         for j in range(dim):
  682.             float_colors[i][j] = float( byte_colors[i][j] ) * scale
  683.  
  684.     return float_colors
  685.  
  686.  
  687. # build_model
  688. #   Builds Blender objects from Grimrock Model
  689. def build_model(model):
  690.  
  691.     # Before adding any meshes or armatures go into Object mode.
  692.     if bpy.ops.object.mode_set.poll():
  693.         bpy.ops.object.mode_set(mode='OBJECT')
  694.  
  695.     for node in model.nodes:
  696.         if node.mesh_entity == None:
  697.             continue
  698.  
  699.         mesh_data = node.mesh_entity.mesh_data
  700.        
  701.         positions = get_vertex_array(mesh_data, VTX_ARRAY_POSITION)
  702.         normals = get_vertex_array(mesh_data, VTX_ARRAY_NORMAL)
  703.         colors = get_vertex_array(mesh_data, VTX_ARRAY_COLOR)
  704.         indices = mesh_data.indices
  705.  
  706.         num_faces = int( mesh_data.num_indices / 3 )
  707.  
  708.         # Create Mesh to work with
  709.         me = bpy.data.meshes.new(node.name)
  710.  
  711.         # Add vertices
  712.         # Flip y-z component so it looks more natural in blender
  713.         me.vertices.add(mesh_data.num_vertices)
  714.         for i in range(mesh_data.num_vertices):
  715.             me.vertices[i].co = (positions[i][0], positions[i][2], positions[i][1])
  716.  
  717.         # Add normals
  718.         if normals != None:
  719.             for i in range(mesh_data.num_vertices):
  720.                 me.vertices[i].normal = (normals[i][0], normals[i][2], normals[i][1])
  721.  
  722.         # Add faces
  723.         # Flip indices because of vertex flip
  724.         me.tessfaces.add(num_faces)
  725.         for i in range(num_faces):
  726.             idx = i * 3
  727.             me.tessfaces[i].vertices_raw = (indices[idx+2], indices[idx+1], indices[idx+0], 0)
  728.        
  729.         # Add colors
  730.         if colors != None:
  731.             # Create color-set layer
  732.             color_layer = me.tessface_vertex_colors.new("colorset")
  733.             me.tessface_vertex_colors.active = color_layer
  734.            
  735.             # Convert to float range
  736.             float_colors = convert_colors(colors)
  737.  
  738.             # Assign colors
  739.             for f in me.tessfaces:
  740.                 color_layer.data[f.index].color1 = float_colors[f.vertices[0]][0:3]
  741.                 color_layer.data[f.index].color2 = float_colors[f.vertices[1]][0:3]
  742.                 color_layer.data[f.index].color3 = float_colors[f.vertices[2]][0:3]
  743.  
  744.         # Add texture coordinate sets
  745.         first_uv_layer = None
  746.         for i in range(8):
  747.             tex_coords = get_vertex_array(mesh_data, VTX_ARRAY_TEXCOORD0 + i)
  748.             if tex_coords == None:
  749.                 continue
  750.  
  751.             # Create uv-layer for these texture coordinates
  752.             uv_layer = me.tessface_uv_textures.new("uvset%d" % i)
  753.             me.tessface_uv_textures.active = uv_layer
  754.             if first_uv_layer == None:
  755.                 first_uv_layer = uv_layer
  756.  
  757.             # Assign uv coordinates to layer faces
  758.             for f in me.tessfaces:
  759.                 uvco1 = tex_coords[f.vertices[0]][0:2]
  760.                 uvco2 = tex_coords[f.vertices[1]][0:2]
  761.                 uvco3 = tex_coords[f.vertices[2]][0:2]
  762.  
  763.                 # Flip v coordinates
  764.                 uvco1 = (uvco1[0], 1.0 - uvco1[1])
  765.                 uvco2 = (uvco2[0], 1.0 - uvco2[1])
  766.                 uvco3 = (uvco3[0], 1.0 - uvco3[1])
  767.  
  768.                 uv_layer.data[f.index].uv = (uvco1, uvco2, uvco3)
  769.  
  770.         # Set first created uv-layer as active layer
  771.         if first_uv_layer != None:
  772.             me.tessface_uv_textures.active = first_uv_layer
  773.  
  774.         # Create object and link with scene
  775.         ob = bpy.data.objects.new(node.name, me)
  776.         bpy.context.scene.objects.link(ob)
  777.  
  778.         # Update mesh
  779.         me.update()
  780.  
  781.     # Update scene
  782.     bpy.context.scene.update()
  783.  
  784.  
  785. # fnv1a
  786. #   Hash a string using fnv1-a
  787. def fnv1a(string, seed = 0x811C9DC5, prime = 0x01000193):
  788.     uint32_max = 2 ** 32
  789.  
  790.     hash_value = seed
  791.     for c in string:
  792.         hash_value = ( ( ord( c ) ^ hash_value ) * prime ) % uint32_max
  793.     return hash_value
  794.  
  795.  
  796. # load_model_table
  797. #   Loads .dat container model information
  798. def load_model_table(filename):
  799.     model_table = []
  800.     file_object = open(filename, 'rb')
  801.  
  802.     dat_magic = 0
  803.     try:
  804.         dat_magic = read_magic(file_object)
  805.     except:
  806.         print("Error parsing .dat file header!")
  807.         file_object.close()
  808.         return model_table
  809.        
  810.     # Figure out if it's a valid dat package
  811.     if dat_magic != b'GRA2':
  812.         print("Not a valid Legend of Grimrock 2 .dat file!")
  813.         file_object.close()
  814.         return model_table
  815.  
  816.     # Read number of file headers
  817.     num_files = read_int(file_object)
  818.  
  819.     # Read in the file header table for all entries
  820.     file_table = num_files * [None]
  821.     for i in range(num_files):
  822.         entry = file_entry()
  823.         entry.hash_name = read_uint(file_object)
  824.         entry.file_offset = read_uint(file_object)
  825.         entry.size_compressed = read_uint(file_object)
  826.         entry.size_uncompressed = read_uint(file_object)
  827.         entry.unknown = read_uint(file_object)
  828.         file_table[ i ] = entry
  829.  
  830.     # Only care about lua script files and model files
  831.     # We use the lua to scan for model asset names so we can name the model table properly
  832.     magic_lua = 0x014a4c1b
  833.     magic_model = 0x314c444d    #b'MDL1'
  834.    
  835.     # Create search strings to use when scaning the lua files
  836.     model_search = b'assets/models/'
  837.     fbx_search = b'.fbx'
  838.  
  839.     # Seek to all file entries, read the magic and place table indices for different file types
  840.     model_names = {}
  841.     for entry in file_table:
  842.         # Haven't come across any, think I recall them being zlib compressed ?
  843.         # Skip them for now
  844.         if entry.size_compressed != 0:
  845.            
  846.             print("Compressed file in .dat package, skipping entry")
  847.             continue
  848.  
  849.         # Haven't come across any, I have no idea what this might be
  850.         # Skip these entries for now
  851.         if entry.unknown != 0:
  852.             print("Found unknown data in .dat package, skipping entry")
  853.             continue
  854.  
  855.         # Seek to start of internal file and read the first four bytes and treat them as
  856.         # a 'type' magic of what that file entry actually is.
  857.         # This is of course not correct, but for the sake of finding models and lua files it works just fine.
  858.         file_object.seek(entry.file_offset, os.SEEK_SET)
  859.         file_magic = read_uint(file_object)
  860.        
  861.         # Handle lua file entries
  862.         if file_magic == magic_lua:
  863.             # Read out the entire contents of the file into a bytearray
  864.             lua_buffer = bytearray(file_object.read(entry.size_uncompressed))
  865.  
  866.             # Search the bytearray 'for assets/models/' until no more could be found
  867.             buffer_index = lua_buffer.find(model_search, 0)
  868.             while buffer_index >= 0:
  869.                 # Lookup .fbx ending
  870.                 # Now, we could potentially found .fbx several hundred bytes later.
  871.                 # It's a bit dangerous to assume it always ends with .fbx, but it works for now
  872.                 end_index = lua_buffer.find(fbx_search, buffer_index + 14)
  873.                
  874.                 # If we didn't find an .fbx ending, abort now
  875.                 if end_index < 0:
  876.                     break
  877.  
  878.                 # Decode a string from our search indices and append a .model ending
  879.                 asset_name = decode_string(lua_buffer[buffer_index:end_index]) + ".model"
  880.                
  881.                 # Use FNV1a to hash the asset name
  882.                 hash_name = fnv1a(asset_name)
  883.                
  884.                 # If hash name isn't already in list, append a new entry pointing to the real asset name
  885.                 # so we can 'decode' file names later
  886.                 if hash_name not in model_names:
  887.                     model_names[hash_name] = asset_name
  888.  
  889.                 # Restart the search from the end of the last search result
  890.                 buffer_index = lua_buffer.find(model_search, end_index + 4)
  891.        
  892.         # Handle model file entries, this simply adds the table entry to the model table
  893.         if file_magic == magic_model:
  894.             model_table.append(entry)
  895.  
  896.     # We're done with the file for now, close it
  897.     file_object.close()
  898.  
  899.     # Go through out model table and attempt to resolve all the asset names
  900.     # If we couldn't find one, simply build a name using the hash name
  901.     num_unresolved = 0
  902.     for entry in model_table:
  903.         if entry.hash_name in model_names:
  904.             entry.name = model_names[entry.hash_name]
  905.         else:
  906.             entry.name = "hash_%8x" % entry.hash_name
  907.             num_unresolved += 1
  908.  
  909.     return model_table
  910.  
  911. # load_binary
  912. def load_binary(filename, file_offset, context):
  913.     name, ext = os.path.splitext(os.path.basename(filename))
  914.  
  915.     # Seek to offset in file, this is only used if loading from .dat containers
  916.     file_object = open(filename, 'rb')
  917.     if file_offset > 0:
  918.         file_object.seek(file_offset, os.SEEK_SET)
  919.  
  920.     # Load the binary model data
  921.     load_binary_model(file_object, context)
  922.    
  923.     # Close file if load_binary_model hasn't already done so
  924.     if not file_object.closed:
  925.         file_object.close()
  926.  
  927.     return True
  928.  
  929.  
  930. class IMPORT_OT_model(bpy.types.Operator, ImportHelper):
  931.     # Import Model Operator.
  932.     bl_idname = "import_scene.model"
  933.     bl_label = "Import Model"
  934.     bl_description = "Import a Legend of Grimrock 2 model"
  935.     bl_options = { 'REGISTER', 'UNDO' }
  936.    
  937.     # File selection UI property
  938.     filepath = StringProperty(name="File Path", description="Filepath used for importing the mesh file.", maxlen=1024, default="")
  939.    
  940.     # File list UI properties for .dat containers
  941.     file_list = CollectionProperty(type=bpy.types.PropertyGroup)
  942.     file_list_index = IntProperty()
  943.  
  944.     # Holds information if .dat container is being imported with specific model selection
  945.     dat_file = ""
  946.     model_table = []
  947.  
  948.     def execute(self, context):
  949.         file_offset = 0
  950.  
  951.         # Dig out file offset if loading from .dat container
  952.         if self.dat_file == self.filepath:
  953.             file_offset = self.model_table[self.file_list_index].file_offset
  954.  
  955.         # Load from binary file
  956.         load_binary(self.filepath, file_offset, context)
  957.  
  958.         return {'FINISHED'}
  959.  
  960.  
  961.     # clear_file_list
  962.     #   Clears the file_list UI property of all entries and resets the dat_file cached value
  963.     def clear_file_list(self):
  964.         self.dat_file = ""
  965.  
  966.         num = len(self.file_list)
  967.         while num > 0:
  968.             self.file_list.remove(num-1)
  969.             num -= 1
  970.  
  971.  
  972.     # build_file_list
  973.     #   Updates the file_list UI property from selected .dat file, or cleans it out if needed
  974.     def build_file_list(self):
  975.         # Figure out if we selected a .dat file or if we slected a different .dat file
  976.         name, ext = os.path.splitext(os.path.basename(self.filepath))
  977.         if ext.lower() != ".dat":
  978.             self.clear_file_list()
  979.             return
  980.  
  981.         # Cached dat_file is still up to date, simply ignore any updates
  982.         if self.filepath == self.dat_file:
  983.             return
  984.  
  985.         # Clean out any previous entries in the UI file list
  986.         self.clear_file_list()
  987.  
  988.         # Load package header and extract model information
  989.         self.dat_file = self.filepath
  990.         self.model_table = load_model_table(self.filepath)
  991.  
  992.         # Add all the model table entries to the UI file list
  993.         for entry in self.model_table:
  994.             item = self.file_list.add()
  995.             item.name = entry.name
  996.  
  997.  
  998.     # draw
  999.     def draw(self, context):
  1000.         layout = self.layout
  1001.  
  1002.         # Update the file_list UI property if needed
  1003.         self.build_file_list()
  1004.  
  1005.         row = layout.row(True)
  1006.         row.label("Legend of Grimrock 2 .dat container")
  1007.         layout.template_list("UI_UL_list", "OpenFileDAT", self, "file_list", self, "file_list_index", rows=15)
  1008.  
  1009.  
  1010.     def invoke(self, context, event):
  1011.         wm = context.window_manager
  1012.         wm.fileselect_add(self)
  1013.         return {'RUNNING_MODAL'}
  1014.  
  1015.  
  1016. def menu_func(self, context):
  1017.     self.layout.operator(IMPORT_OT_model.bl_idname, text="Legend of Grimrock 2 Model (.model)")
  1018.  
  1019.  
  1020. def register():
  1021.     bpy.utils.register_module(__name__)
  1022.     bpy.types.INFO_MT_file_import.append(menu_func)
  1023.  
  1024.  
  1025. def unregister():
  1026.     bpy.utils.unregister_module(__name__)
  1027.     bpy.types.INFO_MT_file_import.remove(menu_func)
  1028.  
  1029.  
  1030. if __name__ == "__main__":
  1031.     register()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement