Advertisement
Guest User

Legend of Grimrock 2 - Blender - Model/Anim Importer

a guest
Nov 9th, 2014
487
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 73.99 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 and animations in .dat container are determined by doing an extremly simple scan of lua compiled files.
  5.  
  6.  
  7. bl_info = {
  8.     "name": "Legend of Grimrock 2 Model Format (.model)",
  9.     "author": "",
  10.     "version": (1, 1, 0),
  11.     "blender": (2, 71, 0),
  12.     "api": 36339,
  13.     "location": "File > Import > Legend of Grimrock 2 Model (.model)",
  14.     "description": "Import Legend of Grimrock Models (.model)",
  15.     "warning": "",
  16.     "wiki_url": "",
  17.     "tracker_url": "",
  18.     "category": "Import-Export"}
  19.  
  20.  
  21. import os
  22. import struct
  23. import sys
  24. import math
  25.  
  26. import bpy
  27. import bmesh
  28.  
  29. from mathutils import *
  30.  
  31. from bpy.props import *
  32. from bpy_extras.io_utils import ExportHelper, ImportHelper, axis_conversion
  33. from bpy_extras.image_utils import load_image
  34.  
  35.  
  36. # Vertex data types
  37. VTX_DATA_BYTE   = 0
  38. VTX_DATA_SHORT  = 1
  39. VTX_DATA_INT    = 2
  40. VTX_DATA_FLOAT  = 3
  41.  
  42.  
  43. # Vertex array types
  44. VTX_ARRAY_POSITION      = 0
  45. VTX_ARRAY_NORMAL        = 1
  46. VTX_ARRAY_TANGENT       = 2
  47. VTX_ARRAY_BITANGENT     = 3
  48. VTX_ARRAY_COLOR         = 4
  49. VTX_ARRAY_TEXCOORD0     = 5
  50. VTX_ARRAY_TEXCOORD1     = 6
  51. VTX_ARRAY_TEXCOORD2     = 7
  52. VTX_ARRAY_TEXCOORD3     = 8
  53. VTX_ARRAY_TEXCOORD4     = 9
  54. VTX_ARRAY_TEXCOORD5     = 10
  55. VTX_ARRAY_TEXCOORD6     = 11
  56. VTX_ARRAY_TEXCOORD7     = 12
  57. VTX_ARRAY_BONE_INDEX    = 13
  58. VTX_ARRAY_BONE_WEIGHT   = 14
  59.  
  60.  
  61. # Reads file magic from file
  62. def read_magic(file_object, endian = '<'):
  63.     data = struct.unpack(endian+"4s", file_object.read(4))[0]
  64.     return data;
  65.  
  66. # read_uint
  67. #   Read unsigned integer from file
  68. def read_uint(file_object, endian = '<'):
  69.     data = struct.unpack(endian+'I', file_object.read(4))[0]
  70.     return data
  71.  
  72. # read_int
  73. #   Read signed integer from file
  74. def read_int(file_object, endian = '<'):
  75.     data = struct.unpack(endian+'i', file_object.read(4))[0]
  76.     return data
  77.  
  78. # read_int2
  79. #   Read two signed integers from file
  80. def read_int2(file_object, endian = '<'):
  81.     data = struct.unpack(endian+'ii', file_object.read(8))
  82.     return data
  83.  
  84. # read_int3
  85. #   Read three signed integers from file    
  86. def read_int3(file_object, endian = '<'):
  87.     data = struct.unpack(endian+'iii', file_object.read(12))
  88.     return data
  89.    
  90. # read_int4
  91. #   Read four signed integers from file
  92. def read_int4(file_object, endian = '<'):
  93.     data = struct.unpack(endian+'iiii', file_object.read(16))
  94.     return data
  95.  
  96. # read_float
  97. #   Read float from file
  98. def read_float(file_object, endian = '<'):
  99.     data = struct.unpack(endian+'f', file_object.read(4))[0]
  100.     return data
  101.  
  102. # read_float2
  103. #   Read two floats from file
  104. def read_float2(file_object, endian = '<'):
  105.     data = struct.unpack(endian+'ff', file_object.read(8))
  106.     return data
  107.  
  108. # read_float3
  109. #   Read three floats from file
  110. def read_float3(file_object, endian = '<'):
  111.     data = struct.unpack(endian+'fff', file_object.read(12))
  112.     return data
  113.  
  114. # read_float4
  115. #   Read four floats from file
  116. def read_float4(file_object, endian = '<'):
  117.     data = struct.unpack(endian+'ffff', file_object.read(16))
  118.     return data
  119.  
  120. # read_matrix4x3
  121. #   Read a matrix consisting of four rows with three columns
  122. def read_matrix4x3(file_object, endian = '<'):
  123.     data = struct.unpack(endian+'ffffffffffff', file_object.read(48))
  124.     return data
  125.  
  126. # read_short
  127. #   Read signed short from file
  128. def read_short(file_object, endian = '<'):
  129.     data = struct.unpack(endian+'h', file_object.read(2))[0]
  130.     return data
  131.  
  132. # read_short2
  133. #   Read two signed shorts from file
  134. def read_short2(file_object, endian = '<'):
  135.     data = struct.unpack(endian+'hh', file_object.read(4))
  136.     return data
  137.  
  138. # read_short3
  139. #   Read three signed shorts from file
  140. def read_short3(file_object, endian = '<'):
  141.     data = struct.unpack(endian+'hhh', file_object.read(6))
  142.     return data
  143.  
  144. # read_short4
  145. #   Read four signed shorts from file
  146. def read_short4(file_object, endian = '<'):
  147.     data = struct.unpack(endian+'hhhh', file_object.read(8))
  148.     return data
  149.  
  150. # read_byte
  151. #   Read unsigned byte from file
  152. def read_byte(file_object, endian = '<'):
  153.     data = struct.unpack(endian+'B', file_object.read(1))[0]
  154.     return data
  155.  
  156. # read_byte2
  157. #   Read two unsigned bytes from file
  158. def read_byte2(file_object, endian = '<'):
  159.     data = struct.unpack(endian+'BB', file_object.read(2))
  160.     return data
  161.  
  162. # read_byte3
  163. #   Read three unsigned bytes from file
  164. def read_byte3(file_object, endian = '<'):
  165.     data = struct.unpack(endian+'BBB', file_object.read(3))
  166.     return data
  167.  
  168. # read_byte4
  169. #   Read four unsigned bytes from file
  170. def read_byte4(file_object, endian = '<'):
  171.     data = struct.unpack(endian+'BBBB', file_object.read(4))
  172.     return data
  173.  
  174. # read_string
  175. #   Read string from file
  176. def read_string(file_object, num, endian = '<'):
  177.     raw_string = struct.unpack(endian+str(num)+'s', file_object.read(num))[0]
  178.     data = raw_string.decode("utf-8", "ignore")
  179.     return data
  180.  
  181. # read_len_string
  182. #   Read unsigned integer from file used to read back a string using integer as length from the same file object
  183. def read_len_string(file_object, endian = '<'):
  184.     num = read_int(file_object, endian)
  185.     if num == 0:
  186.         return ""
  187.     return read_string(file_object, num, endian)
  188.  
  189. # decode_string
  190. #   Decode string from buffer
  191. def decode_string(buffer_object, endian = '<'):
  192.     raw_string = struct.unpack(endian+str(len(buffer_object))+'s', buffer_object)[0]
  193.     data = raw_string.decode("utf-8", "ignore")
  194.     return data
  195.  
  196.  
  197. # Vec3
  198. class Vec3():
  199.     __slots__ = (
  200.         "x",
  201.         "y",
  202.         "z",
  203.         )
  204.  
  205.     def __init__(self, x = 0.0, y = 0.0, z = 0.0):
  206.         self.x = x
  207.         self.y = y
  208.         self.z = z
  209.  
  210.     # read
  211.     #   Reads a 3 dimensional vector using Grimrock 2 documentation
  212.     #       float x
  213.     #       float y
  214.     #       float z
  215.     def read(self, file_object):
  216.         self.x = read_float(file_object)
  217.         self.y = read_float(file_object)
  218.         self.z = read_float(file_object)
  219.  
  220.  
  221. # Quat
  222. class Quat():
  223.     __slots__ = (
  224.         "x",
  225.         "y",
  226.         "z",
  227.         "w",
  228.         )
  229.  
  230.     def __init__(self, x = 0.0, y = 0.0, z = 0.0, w = 1.0):
  231.         self.x = x
  232.         self.y = y
  233.         self.z = z
  234.         self.w = w
  235.  
  236.     # read
  237.     #   Reads a 4 component quaternion using Grimrock 2 documentation
  238.     #       float x
  239.     #       float y
  240.     #       float z
  241.     #       float w
  242.     def read(self, file_object):
  243.         self.x = read_float(file_object)
  244.         self.y = read_float(file_object)
  245.         self.z = read_float(file_object)
  246.         self.w = read_float(file_object)
  247.  
  248.  
  249. # Mat4x3
  250. class Mat4x3():
  251.     __slots__ = (
  252.         "rows"
  253.         )
  254.  
  255.     def __init__(self):
  256.         self.rows = [
  257.             Vec3( x = 1.0, y = 0.0, z = 0.0 ),
  258.             Vec3( x = 0.0, y = 1.0, z = 0.0 ),
  259.             Vec3( x = 0.0, y = 0.0, z = 1.0 ),
  260.             Vec3( x = 0.0, y = 0.0, z = 0.0 )
  261.         ]
  262.  
  263.     # read
  264.     #   Reads a 4x3 matrix using Grimrock 2 documentation
  265.     #       vec3 baseX
  266.     #       vec3 baseY
  267.     #       vec3 baseZ
  268.     #       vec3 translation
  269.     def read(self, file_object):
  270.         for i in range(4):
  271.             self.rows[i].read(file_object)
  272.  
  273.     def to_matrix(self):
  274.         r1 = [self.rows[0].x, self.rows[1].x, self.rows[2].x, self.rows[3].x]
  275.         r2 = [self.rows[0].y, self.rows[1].y, self.rows[2].y, self.rows[3].y]
  276.         r3 = [self.rows[0].z, self.rows[1].z, self.rows[2].z, self.rows[3].z]
  277.         r4 = [0.0, 0.0, 0.0, 1.0]
  278.         return Matrix((r1,r2,r3,r4))
  279.  
  280.  
  281. # Bone
  282. class Bone():
  283.     __slots__ = (
  284.         "node_index",
  285.         "model_to_bone",
  286.         )
  287.  
  288.     def __init__(self):
  289.         self.node_index = -1
  290.         self.model_to_bone = Mat4x3()
  291.  
  292.     # read
  293.     #   Reads a Bone structure using Grimrock 2 documentation
  294.     #       int32 boneNodeIndex
  295.     #       Mat4x3 invRestMatrix
  296.     def read(self, file_object):
  297.         self.node_index = read_int(file_object)
  298.         self.model_to_bone.read(file_object)
  299.  
  300.  
  301. # MeshSegment
  302. class MeshSegment():
  303.     __slots__ = (
  304.         "material",
  305.         "primitive_type",
  306.         "index_offset",
  307.         "num_triangles",
  308.         )
  309.  
  310.     def __init__(self):
  311.         self.material = None
  312.         self.primitive_type = 0
  313.         self.index_offset = 0
  314.         self.num_triangles = 0
  315.  
  316.     # read
  317.     #   Reads a MeshSegment using Grimrock 2 documentation
  318.     #       string material
  319.     #       int32 primitiveType
  320.     #       int32 firstIndex
  321.     #       int32 count
  322.     def read(self, file_object):
  323.         self.material = read_len_string(file_object)
  324.         self.primitive_type = read_int(file_object)
  325.         self.index_offset = read_int(file_object)
  326.         self.num_triangles = read_int(file_object)
  327.  
  328.  
  329. # VertexArray
  330. class VertexArray():
  331.     __slots__ = (
  332.         "data_type",
  333.         "dim",
  334.         "stride",
  335.         "data",
  336.         )
  337.  
  338.     def __init__(self):
  339.         self.data_type = 0
  340.         self.dim = 0
  341.         self.stride = 0
  342.         self.data = None
  343.  
  344.     # is_valid_type
  345.     #   Helper to determine if data_type value is valid for read_data_type calls
  346.     def is_valid_type(self):
  347.         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)
  348.  
  349.     # is_valid_dim
  350.     #   Helper to determine if dimensional value is in valid range for read_data_type calls
  351.     def is_valid_dim(self):
  352.         return (self.dim >= 1 and self.dim <= 4)
  353.  
  354.     # read_data_type
  355.     #   Helper to read dimensional attribute of each data type, (ex. 3 bytes, 2 floats, 4 shorts etc.)
  356.     def read_data_type(self, file_object):
  357.         if self.data_type == VTX_DATA_BYTE:
  358.             if self.dim == 1:
  359.                 return read_byte(file_object)
  360.             elif self.dim == 2:
  361.                 return read_byte2(file_object)
  362.             elif self.dim == 3:
  363.                 return read_byte3(file_object)
  364.             elif self.dim == 4:
  365.                 return read_byte4(file_object)
  366.         elif self.data_type == VTX_DATA_SHORT:
  367.             if self.dim == 1:
  368.                 return read_short(file_object)
  369.             elif self.dim == 2:
  370.                 return read_short2(file_object)
  371.             elif self.dim == 3:
  372.                 return read_short3(file_object)
  373.             elif self.dim == 4:
  374.                 return read_short4(file_object)
  375.         elif self.data_type == VTX_DATA_INT:
  376.             if self.dim == 1:
  377.                 return read_int(file_object)
  378.             elif self.dim == 2:
  379.                 return read_int2(file_object)
  380.             elif self.dim == 3:
  381.                 return read_int3(file_object)
  382.             elif self.dim == 4:
  383.                 return read_int4(file_object)
  384.         elif self.data_type == VTX_DATA_FLOAT:
  385.             if self.dim == 1:
  386.                 return read_float(file_object)
  387.             elif self.dim == 2:
  388.                 return read_float2(file_object)
  389.             elif self.dim == 3:
  390.                 return read_float3(file_object)
  391.             elif self.dim == 4:
  392.                 return read_float4(file_object)
  393.  
  394.     # read
  395.     #   Reads VertexArray data using Grimrock 2 documentation
  396.     #       int32 dataType
  397.     #       int32 dim
  398.     #       int32 stride
  399.     #       byte num_vertices*stride
  400.     def read(self, num_vertices, file_object):
  401.         # Read type, dimension and stride
  402.         self.data_type = read_int(file_object)
  403.         self.dim = read_int(file_object)
  404.         self.stride = read_int(file_object)
  405.        
  406.         # Skip if size between vertex to vertex is zero
  407.         if self.stride == 0:
  408.             return
  409.  
  410.         # Pre allocate the data, read data type if valid otherwise just call raw read for each entry
  411.         self.data = num_vertices * [None]
  412.         if self.is_valid_type() and self.is_valid_dim():
  413.             for i in range(num_vertices):
  414.                 self.data[i] = self.read_data_type(file_object)
  415.         else:
  416.             print("Unknown VertexArray data, type(%d), dimension(%d), stride(%d)" % (self.data_type, self.dim, self.stride))
  417.             for i in range(num_vertices):
  418.                 self.data[i] = file_object.read(self.stride)
  419.  
  420.  
  421. # MeshData
  422. class MeshData():
  423.     __slots__ = (
  424.         "magic",
  425.         "version",
  426.         "num_vertices",
  427.         "vertex_arrays",
  428.         "num_indices",
  429.         "indices",
  430.         "num_segments",
  431.         "segments",
  432.         "bound_center",
  433.         "bound_radius",
  434.         "bound_min",
  435.         "bound_max",
  436.         )
  437.  
  438.     def __init__(self):
  439.         self.magic = 0
  440.         self.version = 0
  441.         self.num_vertices = 0
  442.         self.vertex_arrays = 15 * [None]
  443.         self.num_indices = 0
  444.         self.indices = None
  445.         self.num_segments = 0
  446.         self.segments = None
  447.         self.bound_center = [0.0, 0.0, 0.0]
  448.         self.bound_radius = 0.0
  449.         self.bound_min = [0.0, 0.0, 0.0]
  450.         self.bound_max = [0.0, 0.0, 0.0]
  451.  
  452.     # read
  453.     #   Reads MeshData using Grimrock 2 documentation
  454.     #       FourCC magic
  455.     #       int32 version
  456.     #       int32 numVertices
  457.     #       VertexArray * 15
  458.     #       int32 numIndices
  459.     #       int32 * numIndices
  460.     #       int32 numSegents
  461.     #       MeshSegment * numSegments
  462.     #       vec3 boundCenter
  463.     #       float boundRadius
  464.     #       vec3 boundMin
  465.     #       vec3 boundMax
  466.     def read(self, file_object):
  467.         # Read MeshData magic, skip if not equal 'MESH'
  468.         self.magic = read_magic(file_object)
  469.         if self.magic != b'MESH':
  470.             print("Invalid MeshData magic '%s', expected 'MESH'" % self.magic)
  471.             return False
  472.  
  473.         # Read version, skip if version isn't equal to 2
  474.         self.version = read_int(file_object)
  475.         if self.version != 2:
  476.             print("Invalid MeshData version %d, expected 2" % self.version)
  477.             return False
  478.  
  479.         # Read number of vertices
  480.         self.num_vertices = read_int(file_object)
  481.  
  482.         # Read vertex-array data
  483.         for i in range(15):
  484.             vertex_array = VertexArray()
  485.             vertex_array.read(self.num_vertices, file_object)
  486.             if vertex_array.data != None:
  487.                 self.vertex_arrays[i] = vertex_array
  488.  
  489.         # Read number of indices
  490.         self.num_indices = read_int(file_object)
  491.  
  492.         # Read index buffer data
  493.         if self.num_indices > 0:
  494.             self.indices = self.num_indices * [0]
  495.             for i in range(self.num_indices):
  496.                 self.indices[i] = read_int(file_object)
  497.  
  498.         # Read number of segments
  499.         self.num_segments = read_int(file_object)
  500.        
  501.         # Read MeshSegment data
  502.         if self.num_segments > 0:
  503.             self.segments = self.num_segments * [None]
  504.             for i in range(self.num_segments):
  505.                 segment = MeshSegment()
  506.                 segment.read(file_object)
  507.                 self.segments[i] = segment
  508.  
  509.         # Read bounds information
  510.         self.bound_center = read_float3(file_object)
  511.         self.bound_radius = read_float(file_object)
  512.         self.bound_min = read_float3(file_object)
  513.         self.bound_max = read_float3(file_object)
  514.  
  515.         return True
  516.  
  517.  
  518. # MeshEntity
  519. class MeshEntity():
  520.     __slots__ = (
  521.         "mesh_data",
  522.         "num_bones",
  523.         "bones",
  524.         "emissive_color",
  525.         "cast_shadow",
  526.         )
  527.  
  528.     def __init__(self):
  529.         self.mesh_data = None
  530.         self.num_bones = 0
  531.         self.bones = None
  532.         self.emissive_color = Vec3()
  533.         self.cast_shadow = False
  534.  
  535.     # read
  536.     #   Reads MeshEntity using Grimrock 2 documentation
  537.     #       MeshData
  538.     #       int32 numBones
  539.     #       Bone * numBones
  540.     #       Vec3 emissiveColor
  541.     #       byte castShadows
  542.     def read(self, file_object):
  543.         # Read mesh data
  544.         self.mesh_data = MeshData()
  545.         if not self.mesh_data.read(file_object):
  546.             return False
  547.  
  548.         # Read number of bones
  549.         self.num_bones = read_int(file_object)
  550.  
  551.         # Read bones data
  552.         if self.num_bones > 0:
  553.             self.bones = self.num_bones * [None]
  554.             for i in range(self.num_bones):
  555.                 bone = Bone()
  556.                 bone.read(file_object)
  557.                 self.bones[i] = bone
  558.  
  559.         # Read emissive color
  560.         self.emissive_color.read(file_object)
  561.  
  562.         # Read cast shadows property
  563.         cast_shadows = read_byte(file_object)
  564.         if cast_shadows != 0:
  565.             self.cast_shadow = True
  566.         else:
  567.             self.cast_shadow = False
  568.  
  569.         return True
  570.  
  571.  
  572. # Node
  573. class Node():
  574.     __slots__ = (
  575.         "name",
  576.         "local_to_parent",
  577.         "parent",
  578.         "type",
  579.         "mesh_entity",
  580.         )
  581.  
  582.     def __init__(self):
  583.         self.name = ""
  584.         self.local_to_parent = Mat4x3()
  585.         self.parent = -1
  586.         self.type = -1
  587.         self.mesh_entity = None
  588.  
  589.     # read
  590.     #   Reads a Node using Grimrock 2 documentation
  591.     #       string name
  592.     #       Mat4x3 localToParent
  593.     #       int32 parent
  594.     #       int32 type
  595.     #       (MeshEntity)
  596.     def read(self, file_object):
  597.         # Read name of node
  598.         self.name = read_len_string(file_object)
  599.  
  600.         # Read local to parent transform
  601.         self.local_to_parent.read(file_object)
  602.        
  603.         # Read parent node
  604.         self.parent = read_int(file_object)
  605.  
  606.         # Read node type
  607.         self.type = read_int(file_object)
  608.  
  609.         # Read mesh entity if type is zero
  610.         if self.type == 0:
  611.             self.mesh_entity = MeshEntity()
  612.             if not self.mesh_entity.read(file_object):
  613.                 return False
  614.  
  615.         return True
  616.  
  617.  
  618. # Model
  619. class Model():
  620.     __slots__ = (
  621.         "magic",
  622.         "version",
  623.         "num_nodes",
  624.         "nodes",
  625.         )
  626.  
  627.     def __init__(self):
  628.         self.magic = 0
  629.         self.version = 0
  630.         self.num_nodes = 0
  631.         self.nodes = None
  632.  
  633.     # read
  634.     #   Reads a ModelFile using Grimrock 2 documentation
  635.     #       int32 magic
  636.     #       int32 version
  637.     #       int32 numNodes
  638.     #       Node * numNodes
  639.     def read(self, file_object):
  640.         # Read magic, skip if not equal 'MDL1'
  641.         self.magic = read_magic(file_object)
  642.         if self.magic != b'MDL1':
  643.             print("Invalid ModelFile magic '%s', expected 'MDL1'" % self.magic)
  644.             return False
  645.  
  646.         # Read version, skip if not equal 2
  647.         self.version = read_int(file_object)
  648.         if self.version != 2:
  649.             print("Invalid ModelFile version %d, expected 2" % self.version)
  650.             return False
  651.  
  652.         # Read number of nodes
  653.         self.num_nodes = read_int(file_object)
  654.  
  655.         # Read in nodes
  656.         if self.num_nodes > 0:
  657.             self.nodes = self.num_nodes * [None]
  658.             for i in range(self.num_nodes):
  659.                 node = Node()
  660.                 node.read(file_object)
  661.                 self.nodes[i] = node
  662.  
  663.         return True
  664.  
  665.  
  666. class NodeItem():
  667.     __slots__ = (
  668.         "node_name",
  669.         "num_positions",
  670.         "positions",
  671.         "num_rotations",
  672.         "rotations",
  673.         "num_scales",
  674.         "scales",
  675.         )
  676.  
  677.     def __init__(self):
  678.         self.node_name = ""
  679.         self.num_positions = 0
  680.         self.positions = None
  681.         self.num_rotations = 0
  682.         self.rotations = None
  683.         self.num_scales = 0
  684.         self.scales = None
  685.  
  686.  
  687.     def read(self, file_object):
  688.         self.node_name = read_len_string(file_object)
  689.  
  690.         self.num_positions = read_int(file_object)
  691.         if self.num_positions > 0:
  692.             self.positions = self.num_positions * [None]
  693.             for i in range(self.num_positions):
  694.                 position = Vec3()
  695.                 position.read(file_object)
  696.                 self.positions[i] = position
  697.  
  698.         self.num_rotations = read_int(file_object)
  699.         if self.num_rotations > 0:
  700.             self.rotations = self.num_rotations * [None]
  701.             for i in range(self.num_rotations):
  702.                 rotation = Quat()
  703.                 rotation.read(file_object)
  704.                 self.rotations[i] = rotation
  705.  
  706.         self.num_scales = read_int(file_object)
  707.         if self.num_scales > 0:
  708.             self.scales = self.num_scales * [None]
  709.             for i in range(self.num_scales):
  710.                 scale = Vec3()
  711.                 scale.read(file_object)
  712.                 self.scales[i] = scale
  713.  
  714.  
  715. # Animation
  716. #
  717. class Animation():
  718.     __slots__ = (
  719.         "magic",
  720.         "version",
  721.         "name",
  722.         "frames_per_second",
  723.         "num_frames",
  724.         "num_items",
  725.         "items",
  726.         )
  727.  
  728.     def __init__(self):
  729.         self.magic = 0
  730.         self.version = 0
  731.         self.name = ""
  732.         self.frames_per_second = 0
  733.         self.num_frames = 0
  734.         self.num_items = 0
  735.         self.items = None
  736.  
  737.     def read(self, file_object):
  738.         # Read magic, skip if not equal 'ANIM'
  739.         self.magic = read_magic(file_object)
  740.         if self.magic != b'ANIM':
  741.             print("Invalid AnimationFile magic '%s', expected 'ANIM'" % self.magic)
  742.             return False
  743.  
  744.         # Read version, skip if not equal 2
  745.         self.version = read_int(file_object)
  746.         if self.version != 2:
  747.             print("Invalid AnimationFile version %d, expected 2" % self.version)
  748.             return False
  749.  
  750.         self.name = read_len_string(file_object)
  751.         self.frames_per_second = read_float(file_object)
  752.         self.num_frames = read_int(file_object)
  753.         self.num_items = read_int(file_object)
  754.  
  755.         # Read in animation node items
  756.         if self.num_items > 0:
  757.             self.items = self.num_items * [None]
  758.             for i in range(self.num_items):
  759.                 item = NodeItem()
  760.                 item.read(file_object)
  761.                 self.items[i] = item
  762.  
  763.         return True
  764.  
  765.  
  766. # FileEntry
  767. #   File entry information in .dat container
  768. class FileEntry(object):
  769.     __slots__ = (
  770.         "hash_name",
  771.         "file_offset",
  772.         "size_compressed",
  773.         "size_uncompressed",
  774.         "unknown",
  775.         "name",
  776.         )
  777.        
  778.     def __init__(self):
  779.         self.hash_name = 0
  780.         self.file_offset = 0
  781.         self.size_compressed = 0
  782.         self.size_uncompressed = 0
  783.         self.unknown = 0
  784.         self.name = None
  785.  
  786.  
  787. # ArmatureInfo
  788. #   This is a complete MESS, clean it up and make it more understandable/readable
  789. #   Alot of these things have weird namings and/or doesn't really match what we want
  790. #   There is also alot of unnecesary work done in here
  791. class ArmatureInfo():
  792.     __slots__ = (
  793.         "model",
  794.         "node",
  795.         "bones",
  796.         "nodes",
  797.         "b2m_transforms",
  798.         "node_to_bone_index",
  799.         "bone_parents",
  800.         "bone_childs",
  801.         "bone_index_order",
  802.         )
  803.  
  804.     def __init__(self, model, node):
  805.         self.model = model
  806.         self.node = node
  807.         self.bones = node.mesh_entity.bones
  808.         self.nodes = model.nodes
  809.  
  810.         # Don't change order, dependencies within function calls
  811.         self.create_b2m_transforms()
  812.         self.create_node_to_bone_indices()
  813.         self.create_bone_parents()
  814.         self.create_bone_childs()
  815.         self.create_bone_index_order()
  816.    
  817.     # create_b2m_transforms
  818.     #   Creates bone to model space transforms for each bone
  819.     def create_b2m_transforms(self):
  820.         # Calculate all bone to model transforms
  821.         self.b2m_transforms = []
  822.         for bone in self.bones:
  823.             # Fetch deform node
  824.             deform_node = self.nodes[bone.node_index]
  825.  
  826.             # Fetch model to bone matrix and invert it to get the bone to model matrix
  827.             m2b = bone.model_to_bone.to_matrix()
  828.             b2m = m2b.inverted()
  829.  
  830.             # Store calculates bone to model matrix
  831.             self.b2m_transforms.append(b2m)
  832.  
  833.     # create_node_to_bone_indices
  834.     #   Creates a node to bone index mapping
  835.     def create_node_to_bone_indices(self):
  836.         # Create a node to bone mapping
  837.         self.node_to_bone_index = [-1] * len(self.nodes)
  838.         for bone_index in range(len(self.bones)):
  839.             node_index = self.bones[bone_index].node_index
  840.             self.node_to_bone_index[node_index] = bone_index
  841.  
  842.     # create_bone_parents
  843.     #   Generate a list of bone parents for each bone
  844.     def create_bone_parents(self):
  845.         # Figure out all parent bones
  846.         self.bone_parents = []
  847.         for bone in self.bones:
  848.             parent_bone_index = -1
  849.             parent_bone = None
  850.            
  851.             # Walk the node chain backwards until we find a bone or there are no more parents
  852.             node = self.nodes[bone.node_index]
  853.             while node.parent != -1 and parent_bone_index == -1:
  854.                 parent_bone_index = self.node_to_bone_index[node.parent]
  855.                 node = self.nodes[node.parent]
  856.  
  857.             # If we found a parent bone index while walking the chain backwards fetch the bone
  858.             if parent_bone_index != -1:
  859.                 parent_bone = self.bones[parent_bone_index]
  860.  
  861.             # Append either None or a valid parent bone
  862.             self.bone_parents.append(parent_bone)
  863.  
  864.     # create_bone_childs
  865.     #   Generates lists of childrens for each bone
  866.     def create_bone_childs(self):
  867.         # Map childrens for each bone
  868.         self.bone_childs = []
  869.         for parent_bone in self.bones:
  870.             children = []
  871.  
  872.             for bone in self.bones:
  873.                 if bone == parent_bone:
  874.                     continue
  875.  
  876.                 # Check against current parent bone and see if we're a child of it
  877.                 bone_index = self.node_to_bone_index[bone.node_index]
  878.                 if self.bone_parents[bone_index] == parent_bone:
  879.                     children.append(bone)
  880.  
  881.             # Add the list of children to this bone (can be empty)
  882.             self.bone_childs.append(children)
  883.  
  884.     # create_bone_index_order
  885.     #   Creates a index list ordered such as root always comes first
  886.     def create_bone_index_order(self):
  887.         # Figure out the order in which we want to create the bones, since we want to start
  888.         # with bones not having any parents and walking down the chain so we can parent them while
  889.         # we are building them
  890.         self.bone_index_order = []
  891.  
  892.         # Start by adding bones without parents
  893.         for bone_index in range(len(self.bones)):
  894.             if self.bone_parents[bone_index] == None:
  895.                 self.bone_index_order.append(bone_index)
  896.  
  897.         # Start from the beginning of the bone index list and run a pass until index reaches the end.
  898.         # This will happen naturally after bones stops being added to the list
  899.         idx = 0
  900.         while idx < len(self.bone_index_order):
  901.             find_bone_index = self.bone_index_order[idx]
  902.             find_node_index = self.bones[find_bone_index].node_index
  903.  
  904.             # Go through all bone parents and add them if a bone is parented to the current bone we are
  905.             # scanning for. Also make sure we're not scanning ourselves
  906.             for bone_index in range(len(self.bone_parents)):
  907.                 if bone_index == find_bone_index:
  908.                     continue
  909.  
  910.                 parent_bone = self.bone_parents[bone_index]
  911.                 if parent_bone == None:
  912.                     continue
  913.  
  914.                 if parent_bone.node_index == find_node_index:
  915.                     self.bone_index_order.append(bone_index)
  916.             idx += 1
  917.  
  918.     # get_bone_node
  919.     #   Returns node that bone drives
  920.     def get_bone_node(self, bone):
  921.         return self.nodes[bone.node_index]
  922.  
  923.     # get_bone_to_model
  924.     #   Returns the bone to model-space matrix
  925.     def get_bone_to_model(self, bone):
  926.         bone_index = self.node_to_bone_index[bone.node_index]
  927.         return self.b2m_transforms[bone_index]
  928.  
  929.     # get_length
  930.     #   Returns the length between bone and location
  931.     def get_length(self, bone, origin):
  932.         bone_index = self.node_to_bone_index[bone.node_index]
  933.         bone_b2m = self.b2m_transforms[bone_index]
  934.         bone_origin = bone_b2m.to_translation()
  935.  
  936.         vec = origin - bone_origin
  937.         return vec.length
  938.  
  939.     # get_forward
  940.     #   CLEANUP
  941.     #   Not quite, used to fetch the 'forward' orientation of a rest bone matrix
  942.     def get_forward(self, matrix):
  943.         return Vector([matrix[0].x,matrix[1].x,matrix[2].x]).normalized()
  944.         #return matrix[2].xyz.normalized()
  945.  
  946.     # get_bone_origin
  947.     #   CLEANUP
  948.     #   Incorrect, simply fetches an untransformed rest bone origin
  949.     def get_bone_origin(self, bone):
  950.         bone_index = self.node_to_bone_index[bone.node_index]
  951.         bone_b2m = self.b2m_transforms[bone_index]
  952.         return bone_b2m.to_translation()
  953.  
  954.     # get_look_at_child
  955.     #   CLEANUP
  956.     #   Returns a child bone that the input is looking at (can return None if not looking at a particular child)
  957.     #   Finds a child bone that the parent is aiming at
  958.     def get_look_at_child(self, bone):
  959.         # Don't do anything if we don't have children
  960.         bone_index = self.node_to_bone_index[bone.node_index]
  961.         if len(self.bone_childs[bone_index]) <= 0:
  962.             return None
  963.  
  964.         # bone transform and origin
  965.         bone_b2m = self.b2m_transforms[bone_index]
  966.         bone_origin = bone_b2m.to_translation()
  967.        
  968.         # Get bone direction as a normalized vector
  969.         bone_forward = self.get_forward(bone_b2m)
  970.  
  971.         # Don't break on first child, check if we have a child closer to ourselves
  972.         num_look = 0
  973.         max_length = -sys.float_info.max
  974.         min_length = sys.float_info.max
  975.         for child in self.bone_childs[bone_index]:
  976.             child_origin = self.get_bone_origin(child)
  977.  
  978.             # Create a vector pointing towards the child and check the angle between them
  979.             # Subtract epsilon from 1 to allow some small slack to determine if point is
  980.             # looking straight at child
  981.             vec = (child_origin - bone_origin)
  982.             cosv = bone_forward.dot(vec.normalized())
  983.             if cosv >= (1.0 - 0.000001):
  984.                 vec_len = vec.length
  985.                 max_length = max( max_length, vec_len )
  986.                 min_length = min( min_length, vec_len )
  987.                 num_look += 1
  988.  
  989.         # If we didn't look at any point, return 'nothing'
  990.         if num_look == 0:
  991.             return None
  992.  
  993.         # Return a point inbetween all points we where looking straight at
  994.         return bone_origin + bone_forward * ( min_length + max_length ) * 0.5
  995.  
  996.     # get_child_midpoint
  997.     #   CLEANUP
  998.     #   Sort of does what it says, it takes all childs of a bone and calculates a tail
  999.     #   that lies inbetween the mean average of all child bones.
  1000.     def get_child_midpoint(self, bone):
  1001.         # Don't do anything if we don't have children
  1002.         bone_index = self.node_to_bone_index[bone.node_index]
  1003.         if len(self.bone_childs[bone_index]) <= 1:
  1004.             return None
  1005.  
  1006.         # bone transform and origin
  1007.         bone_b2m = self.b2m_transforms[bone_index]
  1008.         bone_origin = bone_b2m.to_translation()
  1009.        
  1010.         # Get bone direction as a normalized vector
  1011.         bone_forward = self.get_forward(bone_b2m)
  1012.  
  1013.         children = self.bone_childs[bone_index]
  1014.         mid_origin = self.get_bone_origin(children[0])
  1015.         for i in range(len(children)-1):
  1016.             mid_origin += (self.get_bone_origin(children[i+1]) - mid_origin) * 0.5
  1017.  
  1018.         return bone_origin + bone_forward * (mid_origin - bone_origin).length
  1019.  
  1020.     # project_vertex_group
  1021.     #   CLEANUP
  1022.     #   It does NOT project a vertex group against anything.
  1023.     #   Projects untransformed vertices onto untransformed rest bone and extrudes the
  1024.     #   tail to most extending vertex
  1025.     def project_vertex_group(self, bone, vertex_groups):
  1026.         if vertex_groups == None:
  1027.             return None
  1028.  
  1029.         bone_name = self.nodes[bone.node_index].name
  1030.         if bone_name not in vertex_groups.keys():
  1031.             return None
  1032.  
  1033.         mesh_data = self.node.mesh_entity.mesh_data
  1034.         positions = get_vertex_array(mesh_data, VTX_ARRAY_POSITION)
  1035.         if positions == None:
  1036.             return None
  1037.  
  1038.         bone_index = self.node_to_bone_index[bone.node_index]
  1039.        
  1040.         bone_b2m = self.b2m_transforms[bone_index]
  1041.         bone_origin = bone_b2m.to_translation()
  1042.        
  1043.         # Get bone direction as a normalized vector
  1044.         bone_forward = self.get_forward(bone_b2m)
  1045.  
  1046.         group = vertex_groups[bone_name]
  1047.         max_length = -sys.float_info.max
  1048.         for (vi, w) in group:
  1049.             if w < 0.000001:
  1050.                 continue
  1051.             vpos = Vector(positions[vi])
  1052.             vec = vpos - bone_origin
  1053.             if bone_forward.dot(vec) <= 0.000001:
  1054.                 continue
  1055.  
  1056.             max_length = max(max_length, vec.length * math.sqrt(w))
  1057.  
  1058.         return bone_origin + bone_forward * max_length
  1059.  
  1060.  
  1061. # FrameObject
  1062. #   Used for posing an object or bone during animation, only represents current rotations
  1063. class FrameObject():
  1064.     __slots__ = (
  1065.         "name",
  1066.         "parent",
  1067.         "matrix_local",
  1068.         "matrix_world",
  1069.         "has_position_track",
  1070.         "has_rotation_track",
  1071.         "has_scale_track",
  1072.         "has_any_track",
  1073.         )
  1074.  
  1075.     def __init__(self, bl_object):
  1076.         self.name = bl_object.name
  1077.         self.parent = bl_object.parent
  1078.         self.matrix_local = bl_object.matrix_local
  1079.         self.matrix_world = bl_object.matrix_world
  1080.         self.has_position_track = False
  1081.         self.has_rotation_track = False
  1082.         self.has_scale_track = False
  1083.         self.has_any_track = False
  1084.  
  1085.     # clear_tracks
  1086.     #   Remove animation frame tracks for this frame
  1087.     def clear_tracks(self):
  1088.         self.has_position_track = False
  1089.         self.has_rotation_track = False
  1090.         self.has_scale_track = False
  1091.         self.has_any_track = False
  1092.  
  1093.     # set_tracks
  1094.     #   Specify animation frame tracks used this frame
  1095.     def set_tracks(self, position, rotation, scale):
  1096.         self.has_position_track = position
  1097.         self.has_rotation_track = rotation
  1098.         self.has_scale_track = scale
  1099.         self.has_any_track = position or rotation or scale
  1100.  
  1101. # ObjectKeyFrame
  1102. #   Specifies an object setup to generate an animation frame from
  1103. class ObjectKeyframe():
  1104.     __slots__ = (
  1105.         "frame_objects",
  1106.         "name_to_index_map"
  1107.         )
  1108.  
  1109.     def __init__(self):
  1110.         self.frame_objects = []
  1111.         self.name_to_index_map = {}
  1112.  
  1113.     # add_object
  1114.     #   Adds a frame object using a Blender object as input
  1115.     def add_object(self, bl_object):
  1116.         frame_object = FrameObject(bl_object)
  1117.  
  1118.         self.name_to_index_map[frame_object.name] = len(self.frame_objects)
  1119.         self.frame_objects.append(frame_object)
  1120.  
  1121.     # get_object
  1122.     #   Returns a frame object mapped to a specific name
  1123.     def get_object(self, name):
  1124.         index = self.name_to_index_map[name]
  1125.         return self.frame_objects[index]
  1126.  
  1127.     # evaluate
  1128.     #   Traverses the objects and calculates the world matrix in sequential order
  1129.     #   Parents are garaunteed to be evaluated before children
  1130.     def evaluate(self):
  1131.         for frame_object in self.frame_objects:
  1132.             if frame_object.parent != None:
  1133.                 parent = self.get_object(frame_object.parent.name)
  1134.                 frame_object.matrix_world = parent.matrix_world * frame_object.matrix_local
  1135.             else:
  1136.                 frame_object.matrix_world = frame_object.matrix_local
  1137.  
  1138.  
  1139. # create_game_to_blender_matrix
  1140. #   Generates a matrix that goes from LHS to RHS and orients the model in a more Blender friendly direction
  1141. def create_game_to_blender_matrix():
  1142.     return Matrix(([1,0,0,0],[0,1,0,0],[0,0,-1,0],[0,0,0,1])) * axis_conversion("Z", "Y", "X", "-Z").to_4x4()
  1143.  
  1144.  
  1145. # create_bone_space_matrix
  1146. #   Generates a matrix that transforms a matrix in to Blender bone space orientation
  1147. def create_bone_space_matrix():
  1148.     return Matrix(([ 0, 1, 0, 0],[-1, 0, 0, 0],[ 0, 0, 1, 0],[ 0, 0, 0, 1]))
  1149.  
  1150.  
  1151. # get_vertex_array
  1152. #   Returns either a valid vertex arrays data container or None if invalid.
  1153. def get_vertex_array(mesh_data, vtx_array_type):
  1154.     if mesh_data.vertex_arrays[vtx_array_type] != None:
  1155.         return mesh_data.vertex_arrays[vtx_array_type].data
  1156.     return None
  1157.  
  1158.  
  1159. # convert_colors
  1160. #   Converts an array of byte range [0, 255] colors to float range [0, 1] values
  1161. #   It simpl does so by multiplying all values with the reciprocal of 255
  1162. def convert_colors(byte_colors):
  1163.     num = len(byte_colors)
  1164.     dim = len(byte_colors[0])
  1165.     float_colors = num * [dim * [0.0]]
  1166.     scale = 1.0 / 255.0
  1167.     for i in range(num):
  1168.         for j in range(dim):
  1169.             float_colors[i][j] = float( byte_colors[i][j] ) * scale
  1170.  
  1171.     return float_colors
  1172.  
  1173.  
  1174. # apply_local_matrix
  1175. #   Applies a nodes local to parent matrix to a Blender objects local matrix
  1176. def apply_local_matrix(ob, node):
  1177.     ob.matrix_local = node.local_to_parent.to_matrix()
  1178.  
  1179.  
  1180. # calculate_frame
  1181. #   Creates world transforms for all nodes
  1182. def calculate_frame(nodes, game_matrix):
  1183.     num_nodes = len(nodes)
  1184.     unsorted_node_indices = range(num_nodes)
  1185.  
  1186.     sorted_node_indices = []
  1187.    
  1188.     # Collect the root nodes only
  1189.     for node_index in unsorted_node_indices:
  1190.         node = nodes[node_index]
  1191.         if node.parent == -1:
  1192.             sorted_node_indices.append(node_index)
  1193.  
  1194.     # Run a pass and add nodes until all unsorted nodes have been added to the sorted list
  1195.     idx = 0
  1196.     while idx < len(sorted_node_indices):
  1197.         # Get the node that we should check parents against
  1198.         find_node_index = sorted_node_indices[idx]
  1199.        
  1200.         # For each node in the unsorted list, check if it's parented to the current sorted node we're looking at
  1201.         for node_index in unsorted_node_indices:
  1202.             # Skip ourselves
  1203.             if find_node_index == node_index:
  1204.                 continue
  1205.            
  1206.             if nodes[node_index].parent == -1:
  1207.                 continue
  1208.  
  1209.             # Append if unsorted parent node is the same as current sorted node
  1210.             if nodes[node_index].parent == find_node_index:
  1211.                 sorted_node_indices.append(node_index)
  1212.  
  1213.         idx += 1
  1214.  
  1215.     # Create the base transform, we add the game_matrix here since it's not part of the node hierarchy
  1216.     base_transform = Matrix.Identity(4) * game_matrix
  1217.    
  1218.     # Transform all nodes into world space using sorted list so we are garantueed to have a composite matrix
  1219.     # for parents that should use it
  1220.     unsorted_node_transforms = [None] * num_nodes
  1221.     frame = [None] * num_nodes
  1222.     for i in unsorted_node_indices:
  1223.         node_index = sorted_node_indices[i]
  1224.         node = nodes[node_index]
  1225.  
  1226.         local_to_parent = node.local_to_parent.to_matrix()
  1227.         if node.parent != -1:
  1228.             unsorted_node_transforms[node_index] = unsorted_node_transforms[node.parent] * local_to_parent
  1229.         else:
  1230.             unsorted_node_transforms[node_index] = base_transform * local_to_parent
  1231.  
  1232.         frame[i] = [node_index, unsorted_node_transforms[node_index]]
  1233.  
  1234.     # Return frame
  1235.     return frame
  1236.  
  1237.  
  1238. # create_object_frame
  1239. #   Create a frame structure for animation purposes out of object heirarcy in blender scene
  1240. def create_object_frame():
  1241.     if "game_matrix" not in bpy.data.objects.keys():
  1242.         return None
  1243.  
  1244.     frame = ObjectKeyframe()
  1245.  
  1246.     objects = []
  1247.     objects.append(bpy.data.objects["game_matrix"])
  1248.  
  1249.     idx = 0
  1250.     while idx < len(objects):
  1251.         obj = objects[idx]
  1252.        
  1253.         frame.add_object(obj)
  1254.         for child in obj.children:
  1255.             objects.append(child)
  1256.        
  1257.         idx += 1
  1258.  
  1259.     return frame
  1260.  
  1261.  
  1262. # load_binary_model
  1263. #   Loads a binary model using Grimrock 2 documentation
  1264. #       FourCC magic
  1265. #       int32 version
  1266. #       int32 numNodes
  1267. #       (Node) * numNodes
  1268. def load_binary_model(file_object, context):
  1269.  
  1270.     # Try to read the binary model
  1271.     model = Model()
  1272.     if not model.read(file_object):
  1273.         print("Failed to load ModelFile")
  1274.         return False
  1275.  
  1276.     build_model(model)
  1277.  
  1278.     return True
  1279.  
  1280.  
  1281. # load_binary_animation
  1282. #   Loads a binary model using Grimrock 2 documentation
  1283. def load_binary_animation(file_object, armature, context):
  1284.  
  1285.     # Try to read the binary animation
  1286.     anim = Animation()
  1287.     if not anim.read(file_object):
  1288.         print("Failed to load AnimationFile")
  1289.         return False
  1290.  
  1291.     # Apply the animation to the armature
  1292.     build_animation(armature, anim)
  1293.  
  1294.     return True
  1295.  
  1296.  
  1297. # build_vertex_groups
  1298. #   Builds vertex groups for skinning
  1299. def build_vertex_groups(model, node):
  1300.     if node.mesh_entity.num_bones <= 0:
  1301.         return None
  1302.  
  1303.     bone_indices = get_vertex_array(node.mesh_entity.mesh_data, VTX_ARRAY_BONE_INDEX)
  1304.     bone_weights = get_vertex_array(node.mesh_entity.mesh_data, VTX_ARRAY_BONE_WEIGHT)
  1305.     if bone_indices == None or bone_weights == None:
  1306.         return None
  1307.  
  1308.     vertex_groups = {}
  1309.     for i in range(node.mesh_entity.mesh_data.num_vertices):
  1310.         vtx_bone_indices = bone_indices[i]
  1311.         vtx_bone_weights = bone_weights[i]
  1312.  
  1313.         num_bone_indices = len(vtx_bone_indices)
  1314.         for j in range(num_bone_indices):
  1315.             bone_index = vtx_bone_indices[j]
  1316.             bone_weight = vtx_bone_weights[j]
  1317.  
  1318.             bone = node.mesh_entity.bones[bone_index]
  1319.             deform_node = model.nodes[bone.node_index]
  1320.  
  1321.             if deform_node.name not in vertex_groups.keys():
  1322.                 vertex_groups[deform_node.name] = []
  1323.  
  1324.             vertex_groups[deform_node.name].append([i, bone_weight])
  1325.  
  1326.     return vertex_groups
  1327.  
  1328.  
  1329. # build_armature
  1330. #   Builds a blender armature from Grimrock Model description
  1331. def build_armature(model, node, vertex_groups, game_matrix):
  1332.     if node.mesh_entity.num_bones <= 0:
  1333.         return None
  1334.  
  1335.     # Create an armature object
  1336.     armature = bpy.data.armatures.new(node.name + "_rig")
  1337.     armature.draw_type = 'STICK'
  1338.  
  1339.     # Create a rig
  1340.     rig = bpy.data.objects.new(node.name + "_rig", armature)
  1341.     rig.show_x_ray = True
  1342.  
  1343.     if node.parent != -1:
  1344.         rig.parent = bpy.data.objects[model.nodes[node.parent].name]
  1345.     #apply_transform(rig, model.nodes, node, game_matrix)
  1346.     apply_local_matrix(rig, node)
  1347.  
  1348.     # Update scene and set active rig
  1349.     bpy.context.scene.objects.link(rig)
  1350.     bpy.context.scene.objects.active = rig
  1351.     bpy.context.scene.update()
  1352.  
  1353.     # Switch to edit mode and create all bones
  1354.     if bpy.ops.object.mode_set.poll():
  1355.         bpy.ops.object.mode_set(mode='EDIT')
  1356.  
  1357.     # Do some pre-processing to make it easier for us when generating the armature
  1358.     info = ArmatureInfo(model, node)
  1359.  
  1360.     # Calculate world transforms for all nodes
  1361.     frame = calculate_frame(model.nodes, game_matrix)
  1362.  
  1363.     # Build bones using our bone index order list (this ensures roots are created before children)
  1364.     for bone_index in info.bone_index_order:
  1365.         bone = info.bones[bone_index]
  1366.         deform_node = info.nodes[bone.node_index]
  1367.         b2m = info.b2m_transforms[bone_index]
  1368.        
  1369.         # Start by checking if we're looking at any particular child (crossing their head positions, if any)
  1370.         tail_origin = info.get_look_at_child(bone)
  1371.        
  1372.         # If we're not crossing any child bones, fetch the middle point of all childs (if any)
  1373.         if tail_origin == None:
  1374.             tail_origin = info.get_child_midpoint(bone)
  1375.  
  1376.         # Try projecting it towards vertices influenced by bone
  1377.         if tail_origin == None:
  1378.             tail_origin = info.project_vertex_group(bone, vertex_groups)
  1379.  
  1380.         # If no tail origin has been found, just extrude the bone in it's forward direction
  1381.         if tail_origin == None:
  1382.             tail_origin = b2m.to_translation() + info.get_forward(b2m)
  1383.  
  1384.         bone_length = info.get_length(bone, tail_origin)
  1385.         bone_length = max(bone_length, 0.001)
  1386.  
  1387.         # Fetch Blender EditBone parent
  1388.         bl_bone_parent = None
  1389.         bone_parent = info.bone_parents[bone_index]
  1390.         if bone_parent != None:
  1391.             parent_node = model.nodes[bone_parent.node_index]
  1392.             bl_bone_parent = armature.edit_bones[parent_node.name]
  1393.  
  1394.         bl_bone = armature.edit_bones.new(deform_node.name)
  1395.         bl_bone.head = Vector([0, 0, 0])
  1396.         bl_bone.tail = Vector([bone_length, 0, 0])
  1397.         bl_bone.transform(b2m.to_3x3())
  1398.         bl_bone.translate(b2m.to_translation())
  1399.         bl_bone.parent = bl_bone_parent
  1400.  
  1401.     # Switch back to object mode in order to refresh armature
  1402.     if bpy.ops.object.mode_set.poll():
  1403.         bpy.ops.object.mode_set(mode='OBJECT')
  1404.  
  1405.     # Switch to pose mode and pose the model in initial position
  1406.     if bpy.ops.object.mode_set.poll():
  1407.         bpy.ops.object.mode_set(mode='POSE')
  1408.  
  1409.     # pose the bones in initial state
  1410.     bl_bone_space = create_bone_space_matrix()
  1411.  
  1412.     for (node_index, node_transform) in frame:
  1413.         deform_node = model.nodes[node_index]
  1414.         if not deform_node.name in rig.pose.bones.keys():
  1415.             continue
  1416.  
  1417.         pose_bone = rig.pose.bones[deform_node.name]
  1418.        
  1419.         pm = rig.matrix_world.inverted() * node_transform * bl_bone_space
  1420.         pose_bone.matrix = pm
  1421.  
  1422.         bpy.context.scene.update()
  1423.  
  1424.     # Done, switch back to object mode
  1425.     if bpy.ops.object.mode_set.poll():
  1426.         bpy.ops.object.mode_set(mode='OBJECT')
  1427.  
  1428.     bpy.context.scene.update()
  1429.  
  1430.     return rig
  1431.  
  1432.  
  1433. # build_model
  1434. #   Builds Blender objects from Grimrock Model
  1435. def build_model(model):
  1436.     game_matrix = create_game_to_blender_matrix()
  1437.  
  1438.     # Calculate world transforms for all nodes
  1439.     frame = calculate_frame(model.nodes, game_matrix)
  1440.  
  1441.     # Before adding any meshes or armatures go into Object mode.
  1442.     if bpy.ops.object.mode_set.poll():
  1443.         bpy.ops.object.mode_set(mode='OBJECT')
  1444.  
  1445.     # Build top most game matrix object
  1446.     game_matrix_ob = bpy.data.objects.new("game_matrix", None)
  1447.     game_matrix_ob.empty_draw_type = 'ARROWS'
  1448.     game_matrix_ob.empty_draw_size = 0.25
  1449.     game_matrix_ob.matrix_local = game_matrix
  1450.     bpy.context.scene.objects.link(game_matrix_ob)
  1451.  
  1452.     # Build all nodes but skip mesh entity nodes since we create them in a later loop
  1453.     for (node_index, node_transform) in frame:
  1454.         node = model.nodes[node_index]
  1455.         if node.mesh_entity != None:
  1456.             continue
  1457.  
  1458.         # Fetch parent object, if there is no parent for the node we use the
  1459.         # top most game matrix conversion object
  1460.         parent_ob = None
  1461.         if node.parent != -1:
  1462.             parent_name = model.nodes[node.parent].name
  1463.             parent_ob = bpy.data.objects[parent_name]
  1464.         else:
  1465.             parent_ob = game_matrix_ob
  1466.  
  1467.         node_ob = bpy.data.objects.new(node.name, None)
  1468.         bpy.context.scene.objects.link(node_ob)
  1469.  
  1470.         node_ob.empty_draw_type = 'ARROWS'
  1471.         node_ob.empty_draw_size = 0.25
  1472.         node_ob.parent = parent_ob
  1473.         node_ob.matrix_world = node_transform
  1474.  
  1475.     # Update the scene after all objects have been created
  1476.     bpy.context.scene.update()
  1477.  
  1478.     # Now loop through all nodes again, but this time create the actuall mesh objects
  1479.     for (node_index, node_transform) in frame:
  1480.         node = model.nodes[node_index]
  1481.         if node.mesh_entity == None:
  1482.             continue
  1483.  
  1484.         # Build vertex groups for skinning
  1485.         vertex_groups = build_vertex_groups(model, node)
  1486.  
  1487.         # Build the armature and pose it in inital state
  1488.         bl_rig = build_armature(model, node, vertex_groups, game_matrix)
  1489.  
  1490.         # Fetch needed data to build
  1491.         mesh_data = node.mesh_entity.mesh_data
  1492.        
  1493.         positions = get_vertex_array(mesh_data, VTX_ARRAY_POSITION)
  1494.         normals = get_vertex_array(mesh_data, VTX_ARRAY_NORMAL)
  1495.         colors = get_vertex_array(mesh_data, VTX_ARRAY_COLOR)
  1496.         indices = mesh_data.indices
  1497.  
  1498.         num_faces = int( mesh_data.num_indices / 3 )
  1499.  
  1500.         # Create Mesh to work with
  1501.         me = bpy.data.meshes.new(node.name)
  1502.  
  1503.         # Add vertices
  1504.         #   Note: These are in native game format, object hierarchy takes care of the transform
  1505.         me.vertices.add(mesh_data.num_vertices)
  1506.         for i in range(mesh_data.num_vertices):
  1507.             co = Vector(positions[i])
  1508.             me.vertices[i].co = (co.x, co.y, co.z)
  1509.  
  1510.         # Add normals
  1511.         #   Note: These are in native game format, object hierarchy takes care of the transform
  1512.         if normals != None:
  1513.             for i in range(mesh_data.num_vertices):
  1514.                 normal = Vector(normals[i])
  1515.                 me.vertices[i].normal = (normal.x, normal.y, normal.z)
  1516.  
  1517.         # Add faces
  1518.         #   Note: No flipping, object hierarchy makes sure this comes out correct
  1519.         me.tessfaces.add(num_faces)
  1520.         for i in range(num_faces):
  1521.             idx = i * 3
  1522.             me.tessfaces[i].vertices_raw = (indices[idx+0], indices[idx+1], indices[idx+2], 0)
  1523.        
  1524.         # Add colors
  1525.         if colors != None:
  1526.             # Create color-set layer
  1527.             color_layer = me.tessface_vertex_colors.new("colorset")
  1528.             me.tessface_vertex_colors.active = color_layer
  1529.            
  1530.             # Convert to float range
  1531.             float_colors = convert_colors(colors)
  1532.  
  1533.             # Assign colors
  1534.             for f in me.tessfaces:
  1535.                 color_layer.data[f.index].color1 = float_colors[f.vertices[0]][0:3]
  1536.                 color_layer.data[f.index].color2 = float_colors[f.vertices[1]][0:3]
  1537.                 color_layer.data[f.index].color3 = float_colors[f.vertices[2]][0:3]
  1538.  
  1539.         # Add texture coordinate sets
  1540.         #   Note: We flip the v-coordinates, so remember to reverse that when exporting out!
  1541.         first_uv_layer = None
  1542.         for i in range(8):
  1543.             tex_coords = get_vertex_array(mesh_data, VTX_ARRAY_TEXCOORD0 + i)
  1544.             if tex_coords == None:
  1545.                 continue
  1546.  
  1547.             # Create uv-layer for these texture coordinates
  1548.             uv_layer = me.tessface_uv_textures.new("uvset%d" % i)
  1549.             me.tessface_uv_textures.active = uv_layer
  1550.             if first_uv_layer == None:
  1551.                 first_uv_layer = uv_layer
  1552.  
  1553.             # Assign uv coordinates to layer faces
  1554.             for f in me.tessfaces:
  1555.                 uvco1 = tex_coords[f.vertices[0]][0:2]
  1556.                 uvco2 = tex_coords[f.vertices[1]][0:2]
  1557.                 uvco3 = tex_coords[f.vertices[2]][0:2]
  1558.  
  1559.                 # Flip v coordinates
  1560.                 uvco1 = (uvco1[0], 1.0 - uvco1[1])
  1561.                 uvco2 = (uvco2[0], 1.0 - uvco2[1])
  1562.                 uvco3 = (uvco3[0], 1.0 - uvco3[1])
  1563.  
  1564.                 uv_layer.data[f.index].uv = (uvco1, uvco2, uvco3)
  1565.  
  1566.         # Set first created uv-layer as active layer
  1567.         if first_uv_layer != None:
  1568.             me.tessface_uv_textures.active = first_uv_layer
  1569.  
  1570.         # Figure out parent object for this node, if none we automatically add it to the game matrix object
  1571.         parent_ob = game_matrix_ob
  1572.         if node.parent != -1:
  1573.             parent_node = model.nodes[node.parent]
  1574.             parent_ob = bpy.data.objects[parent_node.name]
  1575.  
  1576.         # Create mesh object and link with scene
  1577.         node_ob = bpy.data.objects.new(node.name, me)
  1578.         node_ob.parent = parent_ob
  1579.         node_ob.matrix_world = node_transform
  1580.  
  1581.         # Link with scene
  1582.         bpy.context.scene.objects.link(node_ob)
  1583.  
  1584.         # Create the skinning groups for this object as well as a modifier for the rig
  1585.         if vertex_groups != None:
  1586.             for group_name, group in vertex_groups.items():
  1587.                 bl_group = node_ob.vertex_groups.new(group_name)
  1588.                 for (v, w) in group:
  1589.                     bl_group.add([v], w, 'ADD')
  1590.             mod = node_ob.modifiers.new('RigModifier', 'ARMATURE')
  1591.             mod.object = bl_rig
  1592.             mod.use_bone_envelopes = False
  1593.             mod.use_vertex_groups = True
  1594.  
  1595.         # Update the mesh
  1596.         me.update()
  1597.  
  1598.     # Update scene
  1599.     bpy.context.scene.update()
  1600.  
  1601.  
  1602. # build_animation
  1603. def build_animation(armature_object, anim):
  1604.  
  1605.     # This isn't quite correct.
  1606.     # fps isn't right since we simply truncate to integer
  1607.     fps = int(anim.frames_per_second)
  1608.  
  1609.     # Figure out range of animation
  1610.     frame_start = 1
  1611.     frame_end = 1 + anim.num_frames
  1612.  
  1613.     # Update frame settings
  1614.     bpy.context.scene.render.fps = fps
  1615.     bpy.context.scene.render.fps_base = 1.0
  1616.     bpy.context.scene.frame_start = frame_start
  1617.     bpy.context.scene.frame_end = frame_end
  1618.  
  1619.     bpy.context.scene.update()
  1620.  
  1621.     game_matrix = create_game_to_blender_matrix()
  1622.  
  1623.     # Set the armature as the current selected object
  1624.     bpy.context.scene.objects.active = armature_object
  1625.  
  1626.     # Switch to pose mode
  1627.     if bpy.ops.object.mode_set.poll():
  1628.         bpy.ops.object.mode_set(mode='POSE')
  1629.  
  1630.     armature = armature_object.data
  1631.     pose = armature_object.pose
  1632.  
  1633.     frame = create_object_frame()
  1634.     bl_bone_space = create_bone_space_matrix()
  1635.  
  1636.     for i in range(anim.num_frames):
  1637.         for item in anim.items:
  1638.             frame_object = frame.get_object(item.node_name)
  1639.  
  1640.             has_position_track = i < item.num_positions
  1641.             has_rotation_track = i < item.num_rotations
  1642.             has_scale_track = i < item.num_scales
  1643.  
  1644.             # Tell frame object what tracks are valid for this frame
  1645.             frame_object.set_tracks(has_position_track, has_rotation_track, has_scale_track)
  1646.  
  1647.             # Skip matrix update if there are no tracks available for this object this frame
  1648.             if not frame_object.has_any_track:
  1649.                 continue
  1650.  
  1651.             # Fetch current local matrix and decompose it in to location, rotation and scale components
  1652.             matrix_local = frame_object.matrix_local
  1653.             loc, rot, scl = matrix_local.decompose()
  1654.  
  1655.             # Apply animation tracks for valid
  1656.             if has_position_track:
  1657.                 p = item.positions[i]
  1658.                 loc = Vector([p.x, p.y, p.z])
  1659.  
  1660.             if has_rotation_track:
  1661.                 q = item.rotations[i]
  1662.                 rot = Quaternion((q.w, q.x, q.y, q.z))
  1663.  
  1664.             if has_scale_track:
  1665.                 s = item.scales[i]
  1666.                 scl = Vector([s.x, s.y, s.z])
  1667.  
  1668.             # Create matrices out of our frame components
  1669.             mat_loc = Matrix.Translation((loc.x, loc.y, loc.z))
  1670.             mat_scl = Matrix(([scl.x,0,0,0],[0,scl.y,0,0],[0,0,scl.z,0],[0,0,0,1]))
  1671.             mat_rot = rot.to_matrix().to_4x4()
  1672.  
  1673.             # Compose the final local matrix
  1674.             matrix_local = mat_loc * mat_rot * mat_scl
  1675.  
  1676.             # Set the matrix in the frame object
  1677.             frame_object.matrix_local = matrix_local
  1678.  
  1679.         # Calculate world matrices
  1680.         frame.evaluate()
  1681.  
  1682.         frame_index = i + 1
  1683.         for frame_object in frame.frame_objects:
  1684.             # Skip object if no tracks assigned this frame
  1685.             if not frame_object.has_any_track:
  1686.                 continue
  1687.  
  1688.             # If frame object exists as a pose bone, alter that instead of the actual object
  1689.             keyframe_object = None
  1690.             if frame_object.name in pose.bones.keys():
  1691.                 # Find the post bone and update it
  1692.                 pose_bone = pose.bones[frame_object.name]
  1693.  
  1694.                 pm = armature_object.matrix_world.inverted() * frame_object.matrix_world * bl_bone_space
  1695.                 pose_bone.matrix = pm
  1696.  
  1697.                 keyframe_object = pose_bone
  1698.             else:
  1699.                 # Update actual blender object
  1700.                 bl_object = bpy.data.objects[frame_object.name]
  1701.                 bl_object.matrix_world = frame_object.matrix_world
  1702.                 keyframe_object = bl_object
  1703.  
  1704.             bpy.context.scene.update()
  1705.  
  1706.             # Add a keyframe(s) at this location
  1707.             if keyframe_object != None:
  1708.                 if frame_object.has_position_track:
  1709.                     keyframe_object.keyframe_insert(data_path="location", frame = frame_index)
  1710.                 if frame_object.has_rotation_track:
  1711.                     keyframe_object.keyframe_insert(data_path="rotation_quaternion", frame = frame_index)
  1712.                 if frame_object.has_scale_track:
  1713.                     keyframe_object.keyframe_insert(data_path="scale", frame = frame_index)
  1714.  
  1715.         # Go to next frame
  1716.  
  1717.     # Switch to object mode
  1718.     if bpy.ops.object.mode_set.poll():
  1719.         bpy.ops.object.mode_set(mode='OBJECT')
  1720.  
  1721.     return True
  1722.  
  1723.  
  1724. # fnv1a
  1725. #   Hash a string using fnv1-a
  1726. def fnv1a(string, seed = 0x811C9DC5, prime = 0x01000193):
  1727.     uint32_max = 2 ** 32
  1728.  
  1729.     hash_value = seed
  1730.     for c in string:
  1731.         hash_value = ( ( ord( c ) ^ hash_value ) * prime ) % uint32_max
  1732.     return hash_value
  1733.  
  1734.  
  1735. # load_file_table
  1736. #   Loads the .dat container file table header
  1737. def load_file_table(file_object):
  1738.     file_table = None
  1739.  
  1740.     dat_magic = 0
  1741.     try:
  1742.         dat_magic = read_magic(file_object)
  1743.     except:
  1744.         print("Error parsing .dat file header!")
  1745.         file_object.close()
  1746.         return file_table
  1747.        
  1748.     # Figure out if it's a valid dat package
  1749.     if dat_magic != b'GRA2':
  1750.         print("Not a valid Legend of Grimrock 2 .dat file!")
  1751.         file_object.close()
  1752.         return file_table
  1753.  
  1754.     # Read number of file headers
  1755.     num_files = read_int(file_object)
  1756.  
  1757.     # Read in the file header table for all entries
  1758.     file_table = num_files * [None]
  1759.     for i in range(num_files):
  1760.         entry = FileEntry()
  1761.         entry.hash_name = read_uint(file_object)
  1762.         entry.file_offset = read_uint(file_object)
  1763.         entry.size_compressed = read_uint(file_object)
  1764.         entry.size_uncompressed = read_uint(file_object)
  1765.         entry.unknown = read_uint(file_object)
  1766.         file_table[ i ] = entry
  1767.  
  1768.     return file_table
  1769.  
  1770.  
  1771. # load_animation_table
  1772. #   Loads a .dat container animation information
  1773. def load_animation_table(filename):
  1774.     anim_table = []
  1775.  
  1776.     file_object = open(filename, 'rb')
  1777.     file_table = load_file_table(file_object)
  1778.     if file_table == None or len(file_table) <= 0:
  1779.         return anim_table
  1780.  
  1781.     # Only care about lua script files and animation files
  1782.     # We use the lua to scan for animation asset names so we can name the animation table properly
  1783.     magic_lua = 0x014a4c1b
  1784.     magic_anim = 0x4d494e41    #b'ANIM'
  1785.    
  1786.     # Create search strings to use when scaning the lua files
  1787.     anim_search = b'assets/animations/'
  1788.     fbx_search = b'.fbx'
  1789.  
  1790.     # Seek to all file entries, read the magic and place table indices for different file types
  1791.     anim_names = {}
  1792.     for entry in file_table:
  1793.         # Haven't come across any, think I recall them being zlib compressed ?
  1794.         # Skip them for now
  1795.         if entry.size_compressed != 0:
  1796.             print("Compressed file in .dat package, skipping entry")
  1797.             continue
  1798.  
  1799.         # Haven't come across any, I have no idea what this might be
  1800.         # Skip these entries for now
  1801.         if entry.unknown != 0:
  1802.             print("Found unknown data in .dat package, skipping entry")
  1803.             continue
  1804.  
  1805.         # Seek to start of internal file and read the first four bytes and treat them as
  1806.         # a 'type' magic of what that file entry actually is.
  1807.         # This is of course not correct, but for the sake of finding animations and lua files it works just fine.
  1808.         file_object.seek(entry.file_offset, os.SEEK_SET)
  1809.         file_magic = read_uint(file_object)
  1810.        
  1811.         # Handle lua file entries
  1812.         if file_magic == magic_lua:
  1813.             # Read out the entire contents of the file into a bytearray
  1814.             lua_buffer = bytearray(file_object.read(entry.size_uncompressed))
  1815.  
  1816.             # Search the bytearray 'for assets/animations/' until no more could be found
  1817.             buffer_index = lua_buffer.find(anim_search, 0)
  1818.             while buffer_index >= 0:
  1819.                 # Lookup .fbx ending
  1820.                 # Now, we could potentially found .fbx several hundred bytes later.
  1821.                 # It's a bit dangerous to assume it always ends with .fbx, but it works for now
  1822.                 end_index = lua_buffer.find(fbx_search, buffer_index + 14)
  1823.                
  1824.                 # If we didn't find an .fbx ending, abort now
  1825.                 if end_index < 0:
  1826.                     break
  1827.  
  1828.                 # Decode a string from our search indices and append a .animation ending
  1829.                 asset_name = decode_string(lua_buffer[buffer_index:end_index]) + ".animation"
  1830.                
  1831.                 # Use FNV1a to hash the asset name
  1832.                 hash_name = fnv1a(asset_name)
  1833.                
  1834.                 # If hash name isn't already in list, append a new entry pointing to the real asset name
  1835.                 # so we can 'decode' file names later
  1836.                 if hash_name not in anim_names:
  1837.                     anim_names[hash_name] = asset_name
  1838.  
  1839.                 # Restart the search from the end of the last search result
  1840.                 buffer_index = lua_buffer.find(anim_search, end_index + 4)
  1841.        
  1842.         # Handle animation file entries, this simply adds the table entry to the animation table
  1843.         if file_magic == magic_anim:
  1844.             anim_table.append(entry)
  1845.  
  1846.     # We're done with the file for now, close it
  1847.     file_object.close()
  1848.  
  1849.     # Go through our animation table and attempt to resolve all the asset names
  1850.     # If we couldn't find one, simply build a name using the hash name
  1851.     num_unresolved = 0
  1852.     for entry in anim_table:
  1853.         if entry.hash_name in anim_names:
  1854.             entry.name = anim_names[entry.hash_name]
  1855.         else:
  1856.             entry.name = "hash_%8x" % entry.hash_name
  1857.             num_unresolved += 1
  1858.  
  1859.     return anim_table
  1860.  
  1861.  
  1862. # load_model_table
  1863. #   Loads .dat container model information
  1864. def load_model_table(filename):
  1865.     model_table = []
  1866.     file_object = open(filename, 'rb')
  1867.     file_table = load_file_table(file_object)
  1868.     if file_table == None or len(file_table) <= 0:
  1869.         return model_table
  1870.  
  1871.     # Only care about lua script files and model files
  1872.     # We use the lua to scan for model asset names so we can name the model table properly
  1873.     magic_lua = 0x014a4c1b
  1874.     magic_model = 0x314c444d    #b'MDL1'
  1875.    
  1876.     # Create search strings to use when scaning the lua files
  1877.     model_search = b'assets/models/'
  1878.     fbx_search = b'.fbx'
  1879.  
  1880.     # Seek to all file entries, read the magic and place table indices for different file types
  1881.     model_names = {}
  1882.     for entry in file_table:
  1883.         # Haven't come across any, think I recall them being zlib compressed ?
  1884.         # Skip them for now
  1885.         if entry.size_compressed != 0:
  1886.            
  1887.             print("Compressed file in .dat package, skipping entry")
  1888.             continue
  1889.  
  1890.         # Haven't come across any, I have no idea what this might be
  1891.         # Skip these entries for now
  1892.         if entry.unknown != 0:
  1893.             print("Found unknown data in .dat package, skipping entry")
  1894.             continue
  1895.  
  1896.         # Seek to start of internal file and read the first four bytes and treat them as
  1897.         # a 'type' magic of what that file entry actually is.
  1898.         # This is of course not correct, but for the sake of finding models and lua files it works just fine.
  1899.         file_object.seek(entry.file_offset, os.SEEK_SET)
  1900.         file_magic = read_uint(file_object)
  1901.        
  1902.         # Handle lua file entries
  1903.         if file_magic == magic_lua:
  1904.             # Read out the entire contents of the file into a bytearray
  1905.             lua_buffer = bytearray(file_object.read(entry.size_uncompressed))
  1906.  
  1907.             # Search the bytearray 'for assets/models/' until no more could be found
  1908.             buffer_index = lua_buffer.find(model_search, 0)
  1909.             while buffer_index >= 0:
  1910.                 # Lookup .fbx ending
  1911.                 # Now, we could potentially found .fbx several hundred bytes later.
  1912.                 # It's a bit dangerous to assume it always ends with .fbx, but it works for now
  1913.                 end_index = lua_buffer.find(fbx_search, buffer_index + 14)
  1914.                
  1915.                 # If we didn't find an .fbx ending, abort now
  1916.                 if end_index < 0:
  1917.                     break
  1918.  
  1919.                 # Decode a string from our search indices and append a .model ending
  1920.                 asset_name = decode_string(lua_buffer[buffer_index:end_index]) + ".model"
  1921.                
  1922.                 # Use FNV1a to hash the asset name
  1923.                 hash_name = fnv1a(asset_name)
  1924.                
  1925.                 # If hash name isn't already in list, append a new entry pointing to the real asset name
  1926.                 # so we can 'decode' file names later
  1927.                 if hash_name not in model_names:
  1928.                     model_names[hash_name] = asset_name
  1929.  
  1930.                 # Restart the search from the end of the last search result
  1931.                 buffer_index = lua_buffer.find(model_search, end_index + 4)
  1932.        
  1933.         # Handle model file entries, this simply adds the table entry to the model table
  1934.         if file_magic == magic_model:
  1935.             model_table.append(entry)
  1936.  
  1937.     # We're done with the file for now, close it
  1938.     file_object.close()
  1939.  
  1940.     # Go through our model table and attempt to resolve all the asset names
  1941.     # If we couldn't find one, simply build a name using the hash name
  1942.     num_unresolved = 0
  1943.     for entry in model_table:
  1944.         if entry.hash_name in model_names:
  1945.             entry.name = model_names[entry.hash_name]
  1946.         else:
  1947.             entry.name = "hash_%8x" % entry.hash_name
  1948.             num_unresolved += 1
  1949.  
  1950.     return model_table
  1951.  
  1952.  
  1953. # load_model
  1954. #   Loads a model from a binary file, uses a file offset if we're reading directly from a .dat container
  1955. def load_model(filename, file_offset, context):
  1956.     name, ext = os.path.splitext(os.path.basename(filename))
  1957.  
  1958.     # Seek to offset in file, this is only used if loading from .dat containers
  1959.     file_object = open(filename, 'rb')
  1960.     if file_offset > 0:
  1961.         file_object.seek(file_offset, os.SEEK_SET)
  1962.  
  1963.     # Load the binary model data
  1964.     load_binary_model(file_object, context)
  1965.    
  1966.     # Close file if load_binary_model hasn't already done so
  1967.     if not file_object.closed:
  1968.         file_object.close()
  1969.  
  1970.     return True
  1971.  
  1972.  
  1973. # load_animation
  1974. #   Loads an animation from a binary file, uses a file offset if we're reading directly from a .dat container
  1975. def load_animation(filename, file_offset, armature, context):
  1976.     name, ext = os.path.splitext(os.path.basename(filename))
  1977.  
  1978.     # Seek to offset in file, this is only used if loading from .dat containers
  1979.     file_object = open(filename, 'rb')
  1980.     if file_offset > 0:
  1981.         file_object.seek(file_offset, os.SEEK_SET)
  1982.  
  1983.     # Load the binary model data
  1984.     load_binary_animation(file_object, armature, context)
  1985.    
  1986.     # Close file if load_binary_model hasn't already done so
  1987.     if not file_object.closed:
  1988.         file_object.close()
  1989.  
  1990.     return True
  1991.  
  1992.  
  1993. # IMPORT_OT_model
  1994. #   Blender UI and base for importing models
  1995. class IMPORT_OT_model(bpy.types.Operator, ImportHelper):
  1996.     # Import Model Operator.
  1997.     bl_idname = "import_scene.model"
  1998.     bl_label = "Import Model"
  1999.     bl_description = "Import a Legend of Grimrock 2 model"
  2000.     bl_options = { 'REGISTER', 'UNDO' }
  2001.    
  2002.     # File selection UI property
  2003.     filepath = StringProperty(name="File Path", description="Filepath used for importing the mesh file.", maxlen=1024, default="")
  2004.    
  2005.     # File list UI properties for .dat containers
  2006.     file_list = CollectionProperty(type=bpy.types.PropertyGroup)
  2007.     file_list_index = IntProperty()
  2008.  
  2009.     # Holds information if .dat container is being imported with specific model selection
  2010.     dat_file = ""
  2011.     model_table = []
  2012.  
  2013.     def execute(self, context):
  2014.         file_offset = 0
  2015.  
  2016.         # Dig out file offset if loading from .dat container
  2017.         if self.dat_file == self.filepath:
  2018.             file_offset = self.model_table[self.file_list_index].file_offset
  2019.  
  2020.         # Load from binary file
  2021.         load_model(self.filepath, file_offset, context)
  2022.  
  2023.         return {'FINISHED'}
  2024.  
  2025.  
  2026.     # clear_file_list
  2027.     #   Clears the file_list UI property of all entries and resets the dat_file cached value
  2028.     def clear_file_list(self):
  2029.         self.dat_file = ""
  2030.  
  2031.         num = len(self.file_list)
  2032.         while num > 0:
  2033.             self.file_list.remove(num-1)
  2034.             num -= 1
  2035.  
  2036.  
  2037.     # build_file_list
  2038.     #   Updates the file_list UI property from selected .dat file, or cleans it out if needed
  2039.     def build_file_list(self):
  2040.         # Figure out if we selected a .dat file or if we slected a different .dat file
  2041.         name, ext = os.path.splitext(os.path.basename(self.filepath))
  2042.         if ext.lower() != ".dat":
  2043.             self.clear_file_list()
  2044.             return
  2045.  
  2046.         # Cached dat_file is still up to date, simply ignore any updates
  2047.         if self.filepath == self.dat_file:
  2048.             return
  2049.  
  2050.         # Clean out any previous entries in the UI file list
  2051.         self.clear_file_list()
  2052.  
  2053.         # Load package header and extract model information
  2054.         self.dat_file = self.filepath
  2055.         self.model_table = load_model_table(self.filepath)
  2056.  
  2057.         # Add all the model table entries to the UI file list
  2058.         for entry in self.model_table:
  2059.             item = self.file_list.add()
  2060.             item.name = entry.name
  2061.  
  2062.  
  2063.     # draw
  2064.     def draw(self, context):
  2065.         layout = self.layout
  2066.  
  2067.         # Update the file_list UI property if needed
  2068.         self.build_file_list()
  2069.  
  2070.         row = layout.row(True)
  2071.         row.label("Legend of Grimrock 2 .dat container")
  2072.         layout.template_list("UI_UL_list", "OpenFileDAT", self, "file_list", self, "file_list_index", rows=15)
  2073.  
  2074.  
  2075.     def invoke(self, context, event):
  2076.         wm = context.window_manager
  2077.         wm.fileselect_add(self)
  2078.         return {'RUNNING_MODAL'}
  2079.  
  2080.  
  2081. # IMPORT_OT_anim
  2082. #   Blender UI and base for importing animations
  2083. class IMPORT_OT_anim(bpy.types.Operator, ImportHelper):
  2084.     # Import Animation Operator.
  2085.     bl_idname = "import_scene.animation"
  2086.     bl_label = "Import Animation"
  2087.     bl_description = "Import a Legend of Grimrock 2 animation"
  2088.     bl_options = { 'REGISTER', 'UNDO' }
  2089.    
  2090.     # File selection UI property
  2091.     filepath = StringProperty(name="File Path", description="Filepath used for importing the mesh file.", maxlen=1024, default="")
  2092.    
  2093.     # Armature selection UI property
  2094.     armature_name = StringProperty(name="Armature", description="Armature to apply animation data on.", maxlen=1024, default="")
  2095.  
  2096.     # File list UI properties for .dat containers
  2097.     file_list = CollectionProperty(type=bpy.types.PropertyGroup)
  2098.     file_list_index = IntProperty()
  2099.  
  2100.     # Holds information if .dat container is being imported with specific model selection
  2101.     dat_file = ""
  2102.     animation_table = []
  2103.  
  2104.  
  2105.     # execute
  2106.     def execute(self, context):
  2107.         file_offset = 0
  2108.  
  2109.         # Dig out file offset if loading from .dat container
  2110.         if self.dat_file == self.filepath:
  2111.             file_offset = self.animation_table[self.file_list_index].file_offset
  2112.  
  2113.         if len(self.armature_name) <= 0 or not context.scene.objects[self.armature_name]:
  2114.             print("Can't load animation, couldn't find selected armature.")
  2115.             return {'FINISHED'}
  2116.  
  2117.         armature = context.scene.objects[self.armature_name]
  2118.         if armature.type != 'ARMATURE':
  2119.             print("Can't load animation, object to apply on is not of type ARMATURE.")
  2120.             return {'FINISHED'}
  2121.        
  2122.         # Load from binary file
  2123.         load_animation(self.filepath, file_offset, armature, context)
  2124.  
  2125.         return {'FINISHED'}
  2126.  
  2127.  
  2128.     # clear_file_list
  2129.     #   Clears the file_list UI property of all entries and resets the dat_file cached value
  2130.     def clear_file_list(self):
  2131.         self.dat_file = ""
  2132.  
  2133.         num = len(self.file_list)
  2134.         while num > 0:
  2135.             self.file_list.remove(num-1)
  2136.             num -= 1
  2137.  
  2138.  
  2139.     # build_file_list
  2140.     #   Updates the file_list UI property from selected .dat file, or cleans it out if needed
  2141.     def build_file_list(self):
  2142.         # Figure out if we selected a .dat file or if we slected a different .dat file
  2143.         name, ext = os.path.splitext(os.path.basename(self.filepath))
  2144.         if ext.lower() != ".dat":
  2145.             self.clear_file_list()
  2146.             return
  2147.  
  2148.         # Cached dat_file is still up to date, simply ignore any updates
  2149.         if self.filepath == self.dat_file:
  2150.             return
  2151.  
  2152.         # Clean out any previous entries in the UI file list
  2153.         self.clear_file_list()
  2154.  
  2155.         # Load package header and extract animation information
  2156.         self.dat_file = self.filepath
  2157.         self.animation_table = load_animation_table(self.filepath)
  2158.  
  2159.         # Add all the animation table entries to the UI file list
  2160.         for entry in self.animation_table:
  2161.             item = self.file_list.add()
  2162.             item.name = entry.name
  2163.  
  2164.  
  2165.     # draw
  2166.     def draw(self, context):
  2167.         layout = self.layout
  2168.  
  2169.         # Update the file_list UI property if needed
  2170.         self.build_file_list()
  2171.  
  2172.         row = layout.row(True)
  2173.         row.prop_search(self, "armature_name", bpy.data, "armatures")
  2174.  
  2175.         row = layout.row(True)
  2176.         row.label("Legend of Grimrock 2 .dat container")
  2177.         layout.template_list("UI_UL_list", "OpenFileDAT", self, "file_list", self, "file_list_index", rows=15)
  2178.  
  2179.  
  2180.     # invoke
  2181.     def invoke(self, context, event):
  2182.         wm = context.window_manager
  2183.         wm.fileselect_add(self)
  2184.         return {'RUNNING_MODAL'}
  2185.  
  2186.  
  2187. # menu_func_import_model
  2188. #   Blender menu operator to invoke model importer
  2189. def menu_func_import_model(self, context):
  2190.     self.layout.operator(IMPORT_OT_model.bl_idname, text="Legend of Grimrock 2 Model (.model)")
  2191.  
  2192.  
  2193. # menu_func_import_model
  2194. #   Blender menu operator to invoke animation importer
  2195. def menu_func_import_anim(self, context):
  2196.     self.layout.operator(IMPORT_OT_anim.bl_idname, text="Legend of Grimrock 2 Animation (.animation)")
  2197.  
  2198.  
  2199. # register
  2200. #   Registers menu functions
  2201. def register():
  2202.     bpy.utils.register_module(__name__)
  2203.     bpy.types.INFO_MT_file_import.append(menu_func_import_model)
  2204.     bpy.types.INFO_MT_file_import.append(menu_func_import_anim)
  2205.  
  2206.  
  2207. # unregister
  2208. #   Unregisters menu functions
  2209. def unregister():
  2210.     bpy.utils.unregister_module(__name__)
  2211.     bpy.types.INFO_MT_file_import.remove(menu_func_import_model)
  2212.     bpy.types.INFO_MT_file_import.remove(menu_func_import_anim)
  2213.  
  2214.  
  2215. # Main function
  2216. if __name__ == "__main__":
  2217.     register()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement