Guest User

Blender - Legend of Grimrock 2 - Importer / Exporter 1.3

a guest
Nov 15th, 2014
327
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. # bitcpy
  2.  
  3.  
  4. # NOTE
  5. #   Loads a Legend of Grimrock 2 model from either a .model file or a .dat container
  6. #   Loads a Legend of Grimrock 2 animation from either a .animation file or a .dat container
  7. #   Saves a Legend of Grimrock 2 model
  8. #
  9. #   Names of models and animations in .dat container are determined by doing an extremly simple scan of lua compiled files.
  10.  
  11.  
  12. # FIX
  13. #  . Needs a cleanup pass by now
  14. #  . Need to fix quad split so it matches Mikkelsen, that's how Blender does it when baking if IRC
  15. #  . Fix so animations can be imported even if there are nodes missing
  16. #  . Should we remove nodes that are bone transforms during import, or keep them for completeness sake?
  17. #  . Exporter should create missing nodes for bones, no point in forcing user to place them
  18. #  . Make sure to clean out action sequence of old data when importing new animations
  19. #  . Animation import shouldn't have to flush and update for each bone being posed for every keyframe, kills performance completely.
  20. #  . Add shadow casting depending on material settings
  21. #  . Importing animations from LoG 1 has a different format, should be supported though
  22. #  . germanny experienced distortion when importing 'wizard_check_door' on the model 'wizard', skinning issue perhaps, but shouldn't all animations break if that was the case?
  23. #  . Animation Exporter
  24.  
  25.  
  26. bl_info = {
  27.     "name": "Legend of Grimrock 2 Import/Export (model / animation)",
  28.     "author": "",
  29.     "version": (1, 3, 0),
  30.     "blender": (2, 71, 0),
  31.     "api": 36339,
  32.     "location": "File > Import > Legend of Grimrock 2 Model (.model)",
  33.     "description": "Import/Export Legend of Grimrock Models/Anims (.model/.anim)",
  34.     "warning": "",
  35.     "wiki_url": "",
  36.     "tracker_url": "",
  37.     "category": "Import-Export"}
  38.  
  39.  
  40. import os
  41. import struct
  42. import sys
  43. import math
  44.  
  45. import bpy
  46. import bmesh
  47. import time
  48.  
  49. from mathutils import *
  50.  
  51. from bpy.props import *
  52. from bpy_extras.io_utils import ExportHelper, ImportHelper, axis_conversion
  53. from bpy_extras.image_utils import load_image
  54.  
  55.  
  56. # Vertex data types
  57. VTX_DATA_BYTE   = 0
  58. VTX_DATA_SHORT  = 1
  59. VTX_DATA_INT    = 2
  60. VTX_DATA_FLOAT  = 3
  61.  
  62.  
  63. # Vertex array types
  64. VTX_ARRAY_POSITION      = 0
  65. VTX_ARRAY_NORMAL        = 1
  66. VTX_ARRAY_TANGENT       = 2
  67. VTX_ARRAY_BITANGENT     = 3
  68. VTX_ARRAY_COLOR         = 4
  69. VTX_ARRAY_TEXCOORD0     = 5
  70. VTX_ARRAY_TEXCOORD1     = 6
  71. VTX_ARRAY_TEXCOORD2     = 7
  72. VTX_ARRAY_TEXCOORD3     = 8
  73. VTX_ARRAY_TEXCOORD4     = 9
  74. VTX_ARRAY_TEXCOORD5     = 10
  75. VTX_ARRAY_TEXCOORD6     = 11
  76. VTX_ARRAY_TEXCOORD7     = 12
  77. VTX_ARRAY_BONE_INDEX    = 13
  78. VTX_ARRAY_BONE_WEIGHT   = 14
  79.  
  80.  
  81. # Writes a little-endian byte to file
  82. def write_le_byte(file_object, value):
  83.     file_object.write(struct.pack("<B", value))
  84.     file_object.flush()
  85.  
  86. # Writes a little-endian short to file
  87. def write_le_short(file_object, value):
  88.     file_object.write(struct.pack("<h", value))
  89.     file_object.flush()
  90.  
  91. # Writes a little-endian integer to file
  92. def write_le_int(file_object, value):
  93.     file_object.write(struct.pack("<i", value))
  94.     file_object.flush()
  95.  
  96. # Writes a little-endian float to file
  97. def write_le_float(file_object, value):
  98.     file_object.write(struct.pack("<f", value))
  99.     file_object.flush()
  100.  
  101. # write_le_string
  102. #   Writes a little-endian ANSI encoded string to file
  103. def write_le_string(file_object, value):
  104.     raw = value.encode("ascii", "ignore")
  105.     file_object.write(struct.pack("<i", len(raw)) + raw)
  106.     file_object.flush()
  107.  
  108.  
  109. # read_len_string
  110. #   Read unsigned integer from file used to read back a string using integer as length from the same file object
  111. def read_len_string(file_object, endian = '<'):
  112.     num = read_int(file_object, endian)
  113.     if num == 0:
  114.         return ""
  115.     return read_string(file_object, num, endian)
  116.  
  117.  
  118. # Reads file magic from file
  119. def read_magic(file_object, endian = '<'):
  120.     data = struct.unpack(endian+"4s", file_object.read(4))[0]
  121.     return data;
  122.  
  123. # read_uint
  124. #   Read unsigned integer from file
  125. def read_uint(file_object, endian = '<'):
  126.     data = struct.unpack(endian+'I', file_object.read(4))[0]
  127.     return data
  128.  
  129. # read_int
  130. #   Read signed integer from file
  131. def read_int(file_object, endian = '<'):
  132.     data = struct.unpack(endian+'i', file_object.read(4))[0]
  133.     return data
  134.  
  135. # read_int2
  136. #   Read two signed integers from file
  137. def read_int2(file_object, endian = '<'):
  138.     data = struct.unpack(endian+'ii', file_object.read(8))
  139.     return data
  140.  
  141. # read_int3
  142. #   Read three signed integers from file    
  143. def read_int3(file_object, endian = '<'):
  144.     data = struct.unpack(endian+'iii', file_object.read(12))
  145.     return data
  146.    
  147. # read_int4
  148. #   Read four signed integers from file
  149. def read_int4(file_object, endian = '<'):
  150.     data = struct.unpack(endian+'iiii', file_object.read(16))
  151.     return data
  152.  
  153. # read_float
  154. #   Read float from file
  155. def read_float(file_object, endian = '<'):
  156.     data = struct.unpack(endian+'f', file_object.read(4))[0]
  157.     return data
  158.  
  159. # read_float2
  160. #   Read two floats from file
  161. def read_float2(file_object, endian = '<'):
  162.     data = struct.unpack(endian+'ff', file_object.read(8))
  163.     return data
  164.  
  165. # read_float3
  166. #   Read three floats from file
  167. def read_float3(file_object, endian = '<'):
  168.     data = struct.unpack(endian+'fff', file_object.read(12))
  169.     return data
  170.  
  171. # read_float4
  172. #   Read four floats from file
  173. def read_float4(file_object, endian = '<'):
  174.     data = struct.unpack(endian+'ffff', file_object.read(16))
  175.     return data
  176.  
  177. # read_matrix4x3
  178. #   Read a matrix consisting of four rows with three columns
  179. def read_matrix4x3(file_object, endian = '<'):
  180.     data = struct.unpack(endian+'ffffffffffff', file_object.read(48))
  181.     return data
  182.  
  183. # read_short
  184. #   Read signed short from file
  185. def read_short(file_object, endian = '<'):
  186.     data = struct.unpack(endian+'h', file_object.read(2))[0]
  187.     return data
  188.  
  189. # read_short2
  190. #   Read two signed shorts from file
  191. def read_short2(file_object, endian = '<'):
  192.     data = struct.unpack(endian+'hh', file_object.read(4))
  193.     return data
  194.  
  195. # read_short3
  196. #   Read three signed shorts from file
  197. def read_short3(file_object, endian = '<'):
  198.     data = struct.unpack(endian+'hhh', file_object.read(6))
  199.     return data
  200.  
  201. # read_short4
  202. #   Read four signed shorts from file
  203. def read_short4(file_object, endian = '<'):
  204.     data = struct.unpack(endian+'hhhh', file_object.read(8))
  205.     return data
  206.  
  207. # read_byte
  208. #   Read unsigned byte from file
  209. def read_byte(file_object, endian = '<'):
  210.     data = struct.unpack(endian+'B', file_object.read(1))[0]
  211.     return data
  212.  
  213. # read_byte2
  214. #   Read two unsigned bytes from file
  215. def read_byte2(file_object, endian = '<'):
  216.     data = struct.unpack(endian+'BB', file_object.read(2))
  217.     return data
  218.  
  219. # read_byte3
  220. #   Read three unsigned bytes from file
  221. def read_byte3(file_object, endian = '<'):
  222.     data = struct.unpack(endian+'BBB', file_object.read(3))
  223.     return data
  224.  
  225. # read_byte4
  226. #   Read four unsigned bytes from file
  227. def read_byte4(file_object, endian = '<'):
  228.     data = struct.unpack(endian+'BBBB', file_object.read(4))
  229.     return data
  230.  
  231. # read_string
  232. #   Read string from file
  233. def read_string(file_object, num, endian = '<'):
  234.     raw_string = struct.unpack(endian+str(num)+'s', file_object.read(num))[0]
  235.     data = raw_string.decode("utf-8", "ignore")
  236.     return data
  237.  
  238. # read_len_string
  239. #   Read unsigned integer from file used to read back a string using integer as length from the same file object
  240. def read_len_string(file_object, endian = '<'):
  241.     num = read_int(file_object, endian)
  242.     if num == 0:
  243.         return ""
  244.     return read_string(file_object, num, endian)
  245.  
  246. # decode_string
  247. #   Decode string from buffer
  248. def decode_string(buffer_object, endian = '<'):
  249.     raw_string = struct.unpack(endian+str(len(buffer_object))+'s', buffer_object)[0]
  250.     data = raw_string.decode("utf-8", "ignore")
  251.     return data
  252.  
  253.  
  254. # Vec3
  255. class Vec3():
  256.     __slots__ = (
  257.         "x",
  258.         "y",
  259.         "z",
  260.         )
  261.  
  262.     def __init__(self, x = 0.0, y = 0.0, z = 0.0):
  263.         self.x = x
  264.         self.y = y
  265.         self.z = z
  266.  
  267.     # read
  268.     #   Reads a 3 dimensional vector using Grimrock 2 documentation
  269.     #       float x
  270.     #       float y
  271.     #       float z
  272.     def read(self, file_object):
  273.         self.x = read_float(file_object)
  274.         self.y = read_float(file_object)
  275.         self.z = read_float(file_object)
  276.  
  277.     # write
  278.     #   Writes a 4 dimensional vector to disk
  279.     def write(self, file_object):
  280.         write_le_float(file_object, self.x)
  281.         write_le_float(file_object, self.y)
  282.         write_le_float(file_object, self.z)
  283.  
  284.  
  285. # Quat
  286. class Quat():
  287.     __slots__ = (
  288.         "x",
  289.         "y",
  290.         "z",
  291.         "w",
  292.         )
  293.  
  294.     def __init__(self, x = 0.0, y = 0.0, z = 0.0, w = 1.0):
  295.         self.x = x
  296.         self.y = y
  297.         self.z = z
  298.         self.w = w
  299.  
  300.     # read
  301.     #   Reads a 4 component quaternion using Grimrock 2 documentation
  302.     #       float x
  303.     #       float y
  304.     #       float z
  305.     #       float w
  306.     def read(self, file_object):
  307.         self.x = read_float(file_object)
  308.         self.y = read_float(file_object)
  309.         self.z = read_float(file_object)
  310.         self.w = read_float(file_object)
  311.  
  312.     # write
  313.     #   Writes a 4 component quaternion to disk
  314.     def write(self, file_object):
  315.         write_le_float(file_object, self.x)
  316.         write_le_float(file_object, self.y)
  317.         write_le_float(file_object, self.z)
  318.         write_le_float(file_object, self.w)
  319.  
  320.  
  321. # Mat4x3
  322. class Mat4x3():
  323.     __slots__ = (
  324.         "rows"
  325.         )
  326.  
  327.     def __init__(self):
  328.         self.rows = [
  329.             Vec3( x = 1.0, y = 0.0, z = 0.0 ),
  330.             Vec3( x = 0.0, y = 1.0, z = 0.0 ),
  331.             Vec3( x = 0.0, y = 0.0, z = 1.0 ),
  332.             Vec3( x = 0.0, y = 0.0, z = 0.0 )
  333.         ]
  334.  
  335.     # read
  336.     #   Reads a 4x3 matrix using Grimrock 2 documentation
  337.     #       vec3 baseX
  338.     #       vec3 baseY
  339.     #       vec3 baseZ
  340.     #       vec3 translation
  341.     def read(self, file_object):
  342.         for i in range(4):
  343.             self.rows[i].read(file_object)
  344.  
  345.     # write
  346.     #   Writes a 4x4 matrix to disk
  347.     def write(self, file_object):
  348.         for i in range(4):
  349.             self.rows[i].write(file_object)
  350.  
  351.     def to_matrix(self):
  352.         r1 = [self.rows[0].x, self.rows[1].x, self.rows[2].x, self.rows[3].x]
  353.         r2 = [self.rows[0].y, self.rows[1].y, self.rows[2].y, self.rows[3].y]
  354.         r3 = [self.rows[0].z, self.rows[1].z, self.rows[2].z, self.rows[3].z]
  355.         r4 = [0.0, 0.0, 0.0, 1.0]
  356.         return Matrix((r1,r2,r3,r4))
  357.  
  358.     # from_matrix
  359.     #   Sets Mat4x3 using a 4x4 Blender matrix
  360.     def from_matrix(self, bl_mat):
  361.         self.rows[0] = Vec3( x = bl_mat[0].x, y = bl_mat[1].x, z = bl_mat[2].x )
  362.         self.rows[1] = Vec3( x = bl_mat[0].y, y = bl_mat[1].y, z = bl_mat[2].y )
  363.         self.rows[2] = Vec3( x = bl_mat[0].z, y = bl_mat[1].z, z = bl_mat[2].z )
  364.         self.rows[3] = Vec3( x = bl_mat[0].w, y = bl_mat[1].w, z = bl_mat[2].w )
  365.  
  366.  
  367. # Bone
  368. class Bone():
  369.     __slots__ = (
  370.         "node_index",
  371.         "model_to_bone",
  372.         )
  373.  
  374.     def __init__(self):
  375.         self.node_index = -1
  376.         self.model_to_bone = Mat4x3()
  377.  
  378.     def create_empty(self, node_index):
  379.         self.node_index = node_index
  380.         self.model_to_bone = Mat4x3()
  381.  
  382.     # read
  383.     #   Reads a Bone structure using Grimrock 2 documentation
  384.     #       int32 boneNodeIndex
  385.     #       Mat4x3 invRestMatrix
  386.     def read(self, file_object):
  387.         self.node_index = read_int(file_object)
  388.         self.model_to_bone.read(file_object)
  389.  
  390.     # write
  391.     #   Writes bone structure to disk
  392.     def write(self, file_object):
  393.         write_le_int(file_object, self.node_index)
  394.         self.model_to_bone.write(file_object)
  395.  
  396.  
  397. # MeshSegment
  398. class MeshSegment():
  399.     __slots__ = (
  400.         "material",
  401.         "primitive_type",
  402.         "index_offset",
  403.         "num_triangles",
  404.         )
  405.  
  406.     def __init__(self):
  407.         self.material = None
  408.         self.primitive_type = 0
  409.         self.index_offset = 0
  410.         self.num_triangles = 0
  411.  
  412.     # create_empty
  413.     #   Creates an empty valid mesh segment
  414.     def create_empty(self):
  415.         self.material = "none"
  416.         self.primitive_type = 2
  417.         self.index_offset = 0
  418.         self.num_triangles = 0
  419.  
  420.     # read
  421.     #   Reads a MeshSegment using Grimrock 2 documentation
  422.     #       string material
  423.     #       int32 primitiveType
  424.     #       int32 firstIndex
  425.     #       int32 count
  426.     def read(self, file_object):
  427.         self.material = read_len_string(file_object)
  428.         self.primitive_type = read_int(file_object)
  429.         self.index_offset = read_int(file_object)
  430.         self.num_triangles = read_int(file_object)
  431.  
  432.     # write
  433.     #   Writes mesh segment structure to disk
  434.     def write(self, file_object):
  435.         write_le_string(file_object, self.material)
  436.         write_le_int(file_object, self.primitive_type)
  437.         write_le_int(file_object, self.index_offset)
  438.         write_le_int(file_object, self.num_triangles)
  439.  
  440.  
  441. # VertexArray
  442. class VertexArray():
  443.     __slots__ = (
  444.         "data_type",
  445.         "dim",
  446.         "stride",
  447.         "data",
  448.         )
  449.  
  450.     def __init__(self):
  451.         self.data_type = 0
  452.         self.dim = 0
  453.         self.stride = 0
  454.         self.data = None
  455.  
  456.     # create_empty
  457.     #   Creates empty vertex array data buffer
  458.     def create_empty(self):
  459.         self.data_type = 0
  460.         self.dim = 0
  461.         self.stride = 0
  462.         self.data = []
  463.  
  464.     # is_valid_type
  465.     #   Helper to determine if data_type value is valid for read_data_type calls
  466.     def is_valid_type(self):
  467.         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)
  468.  
  469.     # is_valid_dim
  470.     #   Helper to determine if dimensional value is in valid range for read_data_type calls
  471.     def is_valid_dim(self):
  472.         return (self.dim >= 1 and self.dim <= 4)
  473.  
  474.     # read_data_type
  475.     #   Helper to read dimensional attribute of each data type, (ex. 3 bytes, 2 floats, 4 shorts etc.)
  476.     def read_data_type(self, file_object):
  477.         if self.data_type == VTX_DATA_BYTE:
  478.             if self.dim == 1:
  479.                 return read_byte(file_object)
  480.             elif self.dim == 2:
  481.                 return read_byte2(file_object)
  482.             elif self.dim == 3:
  483.                 return read_byte3(file_object)
  484.             elif self.dim == 4:
  485.                 return read_byte4(file_object)
  486.         elif self.data_type == VTX_DATA_SHORT:
  487.             if self.dim == 1:
  488.                 return read_short(file_object)
  489.             elif self.dim == 2:
  490.                 return read_short2(file_object)
  491.             elif self.dim == 3:
  492.                 return read_short3(file_object)
  493.             elif self.dim == 4:
  494.                 return read_short4(file_object)
  495.         elif self.data_type == VTX_DATA_INT:
  496.             if self.dim == 1:
  497.                 return read_int(file_object)
  498.             elif self.dim == 2:
  499.                 return read_int2(file_object)
  500.             elif self.dim == 3:
  501.                 return read_int3(file_object)
  502.             elif self.dim == 4:
  503.                 return read_int4(file_object)
  504.         elif self.data_type == VTX_DATA_FLOAT:
  505.             if self.dim == 1:
  506.                 return read_float(file_object)
  507.             elif self.dim == 2:
  508.                 return read_float2(file_object)
  509.             elif self.dim == 3:
  510.                 return read_float3(file_object)
  511.             elif self.dim == 4:
  512.                 return read_float4(file_object)
  513.  
  514.     # read
  515.     #   Reads VertexArray data using Grimrock 2 documentation
  516.     #       int32 dataType
  517.     #       int32 dim
  518.     #       int32 stride
  519.     #       byte num_vertices*stride
  520.     def read(self, num_vertices, file_object):
  521.         # Read type, dimension and stride
  522.         self.data_type = read_int(file_object)
  523.         self.dim = read_int(file_object)
  524.         self.stride = read_int(file_object)
  525.        
  526.         # Skip if size between vertex to vertex is zero
  527.         if self.stride == 0:
  528.             return
  529.  
  530.         # Pre allocate the data, read data type if valid otherwise just call raw read for each entry
  531.         self.data = num_vertices * [None]
  532.         if self.is_valid_type() and self.is_valid_dim():
  533.             for i in range(num_vertices):
  534.                 self.data[i] = self.read_data_type(file_object)
  535.         else:
  536.             print("Unknown VertexArray data, type(%d), dimension(%d), stride(%d)" % (self.data_type, self.dim, self.stride))
  537.             for i in range(num_vertices):
  538.                 self.data[i] = file_object.read(self.stride)
  539.  
  540.     # write
  541.     #   Writes VertexArray data to disk
  542.     def write(self, file_object):
  543.         write_le_int(file_object, self.data_type)
  544.         write_le_int(file_object, self.dim)
  545.         write_le_int(file_object, self.stride)
  546.         if self.stride > 0:
  547.             # VERY slow
  548.             for entry in self.data:
  549.                 for value in entry:
  550.                     if self.data_type == VTX_DATA_BYTE:
  551.                         write_le_byte(file_object, value)
  552.                     elif self.data_type == VTX_DATA_SHORT:
  553.                         write_le_short(file_object, value)
  554.                     elif self.data_type == VTX_DATA_INT:
  555.                         write_le_int(file_object, value)
  556.                     elif self.data_type == VTX_DATA_FLOAT:
  557.                         write_le_float(file_object, value)
  558.  
  559.  
  560. # MeshData
  561. class MeshData():
  562.     __slots__ = (
  563.         "magic",
  564.         "version",
  565.         "num_vertices",
  566.         "vertex_arrays",
  567.         "num_indices",
  568.         "indices",
  569.         "num_segments",
  570.         "segments",
  571.         "bound_center",
  572.         "bound_radius",
  573.         "bound_min",
  574.         "bound_max",
  575.         )
  576.  
  577.     def __init__(self):
  578.         self.magic = 0
  579.         self.version = 0
  580.         self.num_vertices = 0
  581.         self.vertex_arrays = 15 * [None]
  582.         self.num_indices = 0
  583.         self.indices = None
  584.         self.num_segments = 0
  585.         self.segments = None
  586.         self.bound_center = [0.0, 0.0, 0.0]
  587.         self.bound_radius = 0.0
  588.         self.bound_min = [0.0, 0.0, 0.0]
  589.         self.bound_max = [0.0, 0.0, 0.0]
  590.  
  591.     # create_empty
  592.     #   Creates empty valid mesh data
  593.     def create_empty(self):
  594.         self.magic = 0x4853454d     # 'MESH'
  595.         self.version = 2
  596.         self.num_vertices = 0
  597.         self.vertex_arrays = 15 * [None]
  598.         for i in range(len(self.vertex_arrays)):
  599.             vertex_array = VertexArray()
  600.             vertex_array.create_empty()
  601.             self.vertex_arrays[i] = vertex_array
  602.         self.num_indices = 0
  603.         self.indices = []
  604.         self.num_segments = 0
  605.         self.segments = []
  606.         self.bound_center = [0.0, 0.0, 0.0]
  607.         self.bound_radius = 0.0
  608.         self.bound_min = [0.0, 0.0, 0.0]
  609.         self.bound_max = [0.0, 0.0, 0.0]
  610.  
  611.     # read
  612.     #   Reads MeshData using Grimrock 2 documentation
  613.     #       FourCC magic
  614.     #       int32 version
  615.     #       int32 numVertices
  616.     #       VertexArray * 15
  617.     #       int32 numIndices
  618.     #       int32 * numIndices
  619.     #       int32 numSegents
  620.     #       MeshSegment * numSegments
  621.     #       vec3 boundCenter
  622.     #       float boundRadius
  623.     #       vec3 boundMin
  624.     #       vec3 boundMax
  625.     def read(self, file_object):
  626.         # Read MeshData magic, skip if not equal 'MESH'
  627.         self.magic = read_magic(file_object)
  628.         if self.magic != b'MESH':
  629.             print("Invalid MeshData magic '%s', expected 'MESH'" % self.magic)
  630.             return False
  631.  
  632.         # Read version, skip if version isn't equal to 2
  633.         self.version = read_int(file_object)
  634.         if self.version != 2:
  635.             print("Invalid MeshData version %d, expected 2" % self.version)
  636.             return False
  637.  
  638.         # Read number of vertices
  639.         self.num_vertices = read_int(file_object)
  640.  
  641.         # Read vertex-array data
  642.         for i in range(15):
  643.             vertex_array = VertexArray()
  644.             vertex_array.read(self.num_vertices, file_object)
  645.             if vertex_array.data != None:
  646.                 self.vertex_arrays[i] = vertex_array
  647.  
  648.         # Read number of indices
  649.         self.num_indices = read_int(file_object)
  650.  
  651.         # Read index buffer data
  652.         if self.num_indices > 0:
  653.             self.indices = self.num_indices * [0]
  654.             for i in range(self.num_indices):
  655.                 self.indices[i] = read_int(file_object)
  656.  
  657.         # Read number of segments
  658.         self.num_segments = read_int(file_object)
  659.        
  660.         # Read MeshSegment data
  661.         if self.num_segments > 0:
  662.             self.segments = self.num_segments * [None]
  663.             for i in range(self.num_segments):
  664.                 segment = MeshSegment()
  665.                 segment.read(file_object)
  666.                 self.segments[i] = segment
  667.  
  668.         # Read bounds information
  669.         self.bound_center = read_float3(file_object)
  670.         self.bound_radius = read_float(file_object)
  671.         self.bound_min = read_float3(file_object)
  672.         self.bound_max = read_float3(file_object)
  673.  
  674.         return True
  675.  
  676.     # write
  677.     #   Writes mesh structure to disk
  678.     def write(self, file_object):
  679.         write_le_int(file_object, self.magic)
  680.         write_le_int(file_object, self.version)
  681.         write_le_int(file_object, self.num_vertices)
  682.         array_index = 0
  683.         for vertex_array in self.vertex_arrays:
  684.             array_index += 1
  685.             vertex_array.write(file_object)
  686.  
  687.         write_le_int(file_object, self.num_indices)
  688.         for index in self.indices:
  689.             write_le_int(file_object, index)
  690.         write_le_int(file_object, self.num_segments)
  691.         for segment in self.segments:
  692.             segment.write(file_object)
  693.         for comp in self.bound_center:
  694.             write_le_float(file_object, comp)
  695.         write_le_float(file_object, self.bound_radius)
  696.         for comp in self.bound_min:
  697.             write_le_float(file_object, comp)
  698.         for comp in self.bound_max:
  699.             write_le_float(file_object, comp)
  700.  
  701.  
  702. # MeshEntity
  703. class MeshEntity():
  704.     __slots__ = (
  705.         "mesh_data",
  706.         "num_bones",
  707.         "bones",
  708.         "emissive_color",
  709.         "cast_shadow",
  710.         )
  711.  
  712.     def __init__(self):
  713.         self.mesh_data = None
  714.         self.num_bones = 0
  715.         self.bones = None
  716.         self.emissive_color = Vec3()
  717.         self.cast_shadow = False
  718.  
  719.     # create_empty
  720.     #   Creates empty mesh entity
  721.     def create_empty(self):
  722.         mesh_data = MeshData()
  723.         mesh_data.create_empty()
  724.  
  725.         self.mesh_data = mesh_data
  726.         self.num_bones = 0
  727.         self.bones = []
  728.         self.emissive_color = Vec3()
  729.         self.cast_shadow = True
  730.  
  731.     # read
  732.     #   Reads MeshEntity using Grimrock 2 documentation
  733.     #       MeshData
  734.     #       int32 numBones
  735.     #       Bone * numBones
  736.     #       Vec3 emissiveColor
  737.     #       byte castShadows
  738.     def read(self, file_object):
  739.         # Read mesh data
  740.         self.mesh_data = MeshData()
  741.         if not self.mesh_data.read(file_object):
  742.             return False
  743.  
  744.         # Read number of bones
  745.         self.num_bones = read_int(file_object)
  746.  
  747.         # Read bones data
  748.         if self.num_bones > 0:
  749.             self.bones = self.num_bones * [None]
  750.             for i in range(self.num_bones):
  751.                 bone = Bone()
  752.                 bone.read(file_object)
  753.                 self.bones[i] = bone
  754.  
  755.         # Read emissive color
  756.         self.emissive_color.read(file_object)
  757.  
  758.         # Read cast shadows property
  759.         cast_shadows = read_byte(file_object)
  760.         if cast_shadows != 0:
  761.             self.cast_shadow = True
  762.         else:
  763.             self.cast_shadow = False
  764.  
  765.         return True
  766.  
  767.     # write
  768.     #   Writes mesh entity structure to disk
  769.     def write(self, file_object):
  770.         self.mesh_data.write(file_object)
  771.        
  772.         write_le_int(file_object, self.num_bones)
  773.        
  774.         for bone in self.bones:
  775.             bone.write(file_object)
  776.  
  777.         self.emissive_color.write(file_object)
  778.  
  779.         cast_shadows = 0
  780.         if self.cast_shadow:
  781.             cast_shadows = 1
  782.         write_le_byte(file_object, cast_shadows)
  783.  
  784.  
  785. # Node
  786. class Node():
  787.     __slots__ = (
  788.         "name",
  789.         "local_to_parent",
  790.         "parent",
  791.         "type",
  792.         "mesh_entity",
  793.         )
  794.  
  795.     def __init__(self):
  796.         self.name = ""
  797.         self.local_to_parent = Mat4x3()
  798.         self.parent = -1
  799.         self.type = -1
  800.         self.mesh_entity = None
  801.  
  802.     # create_empty
  803.     #   Creates an empty node to work with
  804.     def create_empty(self, name, local_to_parent):
  805.         self.name = name
  806.         self.local_to_parent.from_matrix(local_to_parent)
  807.         self.parent = -1
  808.         self.type = -1
  809.         self.mesh_entity = None
  810.  
  811.     # set_mesh_entity
  812.     #   Assigns a mesh entity to the node
  813.     def set_mesh_entity(self, mesh_entity):
  814.         self.mesh_entity = mesh_entity
  815.         if self.mesh_entity == None:
  816.             self.type = -1
  817.         else:
  818.             self.type = 0
  819.  
  820.     # read
  821.     #   Reads a Node using Grimrock 2 documentation
  822.     #       string name
  823.     #       Mat4x3 localToParent
  824.     #       int32 parent
  825.     #       int32 type
  826.     #       (MeshEntity)
  827.     def read(self, file_object):
  828.         # Read name of node
  829.         self.name = read_len_string(file_object)
  830.  
  831.         # Read local to parent transform
  832.         self.local_to_parent.read(file_object)
  833.        
  834.         # Read parent node
  835.         self.parent = read_int(file_object)
  836.  
  837.         # Read node type
  838.         self.type = read_int(file_object)
  839.  
  840.         # Read mesh entity if type is zero
  841.         if self.type == 0:
  842.             self.mesh_entity = MeshEntity()
  843.             if not self.mesh_entity.read(file_object):
  844.                 return False
  845.  
  846.         return True
  847.  
  848.     # write
  849.     #   Writes a node structure to file
  850.     def write(self, file_object):
  851.         write_le_string(file_object, self.name)
  852.         self.local_to_parent.write(file_object)
  853.         write_le_int(file_object, self.parent)
  854.         write_le_int(file_object, self.type)
  855.  
  856.         if self.mesh_entity != None:
  857.             self.mesh_entity.write(file_object)
  858.  
  859.  
  860. # Model
  861. class Model():
  862.     __slots__ = (
  863.         "magic",
  864.         "version",
  865.         "num_nodes",
  866.         "nodes",
  867.         )
  868.  
  869.     def __init__(self):
  870.         self.magic = 0
  871.         self.version = 0
  872.         self.num_nodes = 0
  873.         self.nodes = None
  874.  
  875.     # init
  876.     #   creates an empty valid Model structure
  877.     def create_empty(self):
  878.         self.magic = 0x314c444d
  879.         self.version = 2
  880.         self.nodes = []
  881.  
  882.     # add_node
  883.     #   Appends a node to the model
  884.     def add_node(self, node):
  885.         self.nodes.append(node)
  886.         self.num_nodes = len(self.nodes)
  887.  
  888.     # find_node_index
  889.     #   Searches for a certain node and returns the index in the list, -1 if not found
  890.     def find_node_index(self, name):
  891.         for node_index in range(self.num_nodes):
  892.             node = self.nodes[node_index]
  893.             if node.name == name:
  894.                 return node_index
  895.         return -1
  896.  
  897.     # read
  898.     #   Reads a ModelFile using Grimrock 2 documentation
  899.     #       int32 magic
  900.     #       int32 version
  901.     #       int32 numNodes
  902.     #       Node * numNodes
  903.     def read(self, file_object):
  904.         # Read magic, skip if not equal 'MDL1'
  905.         self.magic = read_magic(file_object)
  906.         if self.magic != b'MDL1':
  907.             print("Invalid ModelFile magic '%s', expected 'MDL1'" % self.magic)
  908.             return False
  909.  
  910.         # Read version, skip if not equal 2
  911.         self.version = read_int(file_object)
  912.         if self.version != 2:
  913.             print("Invalid ModelFile version %d, expected 2" % self.version)
  914.             return False
  915.  
  916.         # Read number of nodes
  917.         self.num_nodes = read_int(file_object)
  918.  
  919.         # Read in nodes
  920.         if self.num_nodes > 0:
  921.             self.nodes = self.num_nodes * [None]
  922.             for i in range(self.num_nodes):
  923.                 node = Node()
  924.                 node.read(file_object)
  925.                 self.nodes[i] = node
  926.  
  927.         return True
  928.  
  929.     # write
  930.     #   Writes the model structure to disk
  931.     def write(self, file_object):
  932.         write_le_int(file_object, self.magic)
  933.         write_le_int(file_object, self.version)
  934.         write_le_int(file_object, self.num_nodes)
  935.         for node in self.nodes:
  936.             node.write(file_object)
  937.         return True
  938.  
  939.  
  940. class NodeItem():
  941.     __slots__ = (
  942.         "node_name",
  943.         "num_positions",
  944.         "positions",
  945.         "num_rotations",
  946.         "rotations",
  947.         "num_scales",
  948.         "scales",
  949.         )
  950.  
  951.     def __init__(self):
  952.         self.node_name = ""
  953.         self.num_positions = 0
  954.         self.positions = None
  955.         self.num_rotations = 0
  956.         self.rotations = None
  957.         self.num_scales = 0
  958.         self.scales = None
  959.  
  960.     # read
  961.     #   Reads a Item from animation files using Grimrock 2 documentation
  962.     def read(self, file_object):
  963.         self.node_name = read_len_string(file_object)
  964.  
  965.         self.num_positions = read_int(file_object)
  966.         if self.num_positions > 0:
  967.             self.positions = self.num_positions * [None]
  968.             for i in range(self.num_positions):
  969.                 position = Vec3()
  970.                 position.read(file_object)
  971.                 self.positions[i] = position
  972.  
  973.         self.num_rotations = read_int(file_object)
  974.         if self.num_rotations > 0:
  975.             self.rotations = self.num_rotations * [None]
  976.             for i in range(self.num_rotations):
  977.                 rotation = Quat()
  978.                 rotation.read(file_object)
  979.                 self.rotations[i] = rotation
  980.  
  981.         self.num_scales = read_int(file_object)
  982.         if self.num_scales > 0:
  983.             self.scales = self.num_scales * [None]
  984.             for i in range(self.num_scales):
  985.                 scale = Vec3()
  986.                 scale.read(file_object)
  987.                 self.scales[i] = scale
  988.  
  989.  
  990. # Animation
  991. #
  992. class Animation():
  993.     __slots__ = (
  994.         "magic",
  995.         "version",
  996.         "name",
  997.         "frames_per_second",
  998.         "num_frames",
  999.         "num_items",
  1000.         "items",
  1001.         )
  1002.  
  1003.     def __init__(self):
  1004.         self.magic = 0
  1005.         self.version = 0
  1006.         self.name = ""
  1007.         self.frames_per_second = 0
  1008.         self.num_frames = 0
  1009.         self.num_items = 0
  1010.         self.items = None
  1011.  
  1012.     # read
  1013.     #   Reads an AnimationFile using Grimrock 2 documentation
  1014.     def read(self, file_object):
  1015.         # Read magic, skip if not equal 'ANIM'
  1016.         self.magic = read_magic(file_object)
  1017.         if self.magic != b'ANIM':
  1018.             print("Invalid AnimationFile magic '%s', expected 'ANIM'" % self.magic)
  1019.             return False
  1020.  
  1021.         # Read version, skip if not equal 2
  1022.         self.version = read_int(file_object)
  1023.         if self.version != 2:
  1024.             print("Invalid AnimationFile version %d, expected 2" % self.version)
  1025.             return False
  1026.  
  1027.         self.name = read_len_string(file_object)
  1028.         self.frames_per_second = read_float(file_object)
  1029.         self.num_frames = read_int(file_object)
  1030.         self.num_items = read_int(file_object)
  1031.  
  1032.         # Read in animation node items
  1033.         if self.num_items > 0:
  1034.             self.items = self.num_items * [None]
  1035.             for i in range(self.num_items):
  1036.                 item = NodeItem()
  1037.                 item.read(file_object)
  1038.                 self.items[i] = item
  1039.  
  1040.         return True
  1041.  
  1042.  
  1043. # FileEntry
  1044. #   File entry information in .dat container
  1045. class FileEntry(object):
  1046.     __slots__ = (
  1047.         "hash_name",
  1048.         "file_offset",
  1049.         "size_compressed",
  1050.         "size_uncompressed",
  1051.         "unknown",
  1052.         "name",
  1053.         )
  1054.        
  1055.     def __init__(self):
  1056.         self.hash_name = 0
  1057.         self.file_offset = 0
  1058.         self.size_compressed = 0
  1059.         self.size_uncompressed = 0
  1060.         self.unknown = 0
  1061.         self.name = None
  1062.  
  1063.  
  1064. # ObjectTransform
  1065. #   Simple helper to store/restore an object matrix
  1066. class ObjectTransform():
  1067.     __slots__ = (
  1068.         "name",
  1069.         "matrix_world",
  1070.         )
  1071.  
  1072.     def __init__(self, name):
  1073.         self.name = name
  1074.         self.matrix_world = None
  1075.  
  1076.     # clear_transform
  1077.     #   Caches a transform and clears it on the Blender object
  1078.     def clear_transform(self):
  1079.         self.matrix_world = None
  1080.         if self.name in bpy.data.objects:
  1081.             bl_object = bpy.data.objects[self.name]
  1082.             self.matrix_world = bl_object.matrix_world.copy()
  1083.             bl_object.matrix_world = Matrix.Identity(4)
  1084.             bpy.context.scene.update()
  1085.  
  1086.     # restore_transform
  1087.     #   Restores a cached transform on a Blender object
  1088.     def restore_transform(self):
  1089.         if self.matrix_world != None and self.name in bpy.data.objects:
  1090.             bl_object = bpy.data.objects[self.name]
  1091.             bl_object.matrix_world = self.matrix_world.copy()
  1092.             self.matrix_world = None
  1093.             bpy.context.scene.update()
  1094.  
  1095.  
  1096. # ArmatureInfo
  1097. #   FIXME: CLEAN !!
  1098. #   This is a complete MESS, clean it up and make it more understandable/readable
  1099. #   Alot of these things have weird namings and/or doesn't really match what we want
  1100. #   There is also alot of unnecesary work done in here
  1101. class ArmatureInfo():
  1102.     __slots__ = (
  1103.         "model",
  1104.         "node",
  1105.         "bones",
  1106.         "nodes",
  1107.         "b2m_transforms",
  1108.         "node_to_bone_index",
  1109.         "bone_parents",
  1110.         "bone_childs",
  1111.         "bone_index_order",
  1112.         )
  1113.  
  1114.     def __init__(self, model, node):
  1115.         self.model = model
  1116.         self.node = node
  1117.         self.bones = node.mesh_entity.bones
  1118.         self.nodes = model.nodes
  1119.  
  1120.         # Don't change order, dependencies within function calls
  1121.         self.create_b2m_transforms()
  1122.         self.create_node_to_bone_indices()
  1123.         self.create_bone_parents()
  1124.         self.create_bone_childs()
  1125.         self.create_bone_index_order()
  1126.    
  1127.     # create_b2m_transforms
  1128.     #   Creates bone to model space transforms for each bone
  1129.     def create_b2m_transforms(self):
  1130.         # Calculate all bone to model transforms
  1131.         self.b2m_transforms = []
  1132.         for bone in self.bones:
  1133.             # Fetch deform node
  1134.             deform_node = self.nodes[bone.node_index]
  1135.  
  1136.             # Fetch model to bone matrix and invert it to get the bone to model matrix
  1137.             m2b = bone.model_to_bone.to_matrix()
  1138.             b2m = m2b.inverted()
  1139.  
  1140.             # Store calculates bone to model matrix
  1141.             self.b2m_transforms.append(b2m)
  1142.  
  1143.     # create_node_to_bone_indices
  1144.     #   Creates a node to bone index mapping
  1145.     def create_node_to_bone_indices(self):
  1146.         # Create a node to bone mapping
  1147.         self.node_to_bone_index = [-1] * len(self.nodes)
  1148.         for bone_index in range(len(self.bones)):
  1149.             node_index = self.bones[bone_index].node_index
  1150.             self.node_to_bone_index[node_index] = bone_index
  1151.  
  1152.     # create_bone_parents
  1153.     #   Generate a list of bone parents for each bone
  1154.     def create_bone_parents(self):
  1155.         # Figure out all parent bones
  1156.         self.bone_parents = []
  1157.         for bone in self.bones:
  1158.             parent_bone_index = -1
  1159.             parent_bone = None
  1160.            
  1161.             # Walk the node chain backwards until we find a bone or there are no more parents
  1162.             node = self.nodes[bone.node_index]
  1163.             while node.parent != -1 and parent_bone_index == -1:
  1164.                 parent_bone_index = self.node_to_bone_index[node.parent]
  1165.                 node = self.nodes[node.parent]
  1166.  
  1167.             # If we found a parent bone index while walking the chain backwards fetch the bone
  1168.             if parent_bone_index != -1:
  1169.                 parent_bone = self.bones[parent_bone_index]
  1170.  
  1171.             # Append either None or a valid parent bone
  1172.             self.bone_parents.append(parent_bone)
  1173.  
  1174.     # create_bone_childs
  1175.     #   Generates lists of childrens for each bone
  1176.     def create_bone_childs(self):
  1177.         # Map childrens for each bone
  1178.         self.bone_childs = []
  1179.         for parent_bone in self.bones:
  1180.             children = []
  1181.  
  1182.             for bone in self.bones:
  1183.                 if bone == parent_bone:
  1184.                     continue
  1185.  
  1186.                 # Check against current parent bone and see if we're a child of it
  1187.                 bone_index = self.node_to_bone_index[bone.node_index]
  1188.                 if self.bone_parents[bone_index] == parent_bone:
  1189.                     children.append(bone)
  1190.  
  1191.             # Add the list of children to this bone (can be empty)
  1192.             self.bone_childs.append(children)
  1193.  
  1194.     # create_bone_index_order
  1195.     #   Creates a index list ordered such as root always comes first
  1196.     def create_bone_index_order(self):
  1197.         # Figure out the order in which we want to create the bones, since we want to start
  1198.         # with bones not having any parents and walking down the chain so we can parent them while
  1199.         # we are building them
  1200.         self.bone_index_order = []
  1201.  
  1202.         # Start by adding bones without parents
  1203.         for bone_index in range(len(self.bones)):
  1204.             if self.bone_parents[bone_index] == None:
  1205.                 self.bone_index_order.append(bone_index)
  1206.  
  1207.         # Start from the beginning of the bone index list and run a pass until index reaches the end.
  1208.         # This will happen naturally after bones stops being added to the list
  1209.         idx = 0
  1210.         while idx < len(self.bone_index_order):
  1211.             find_bone_index = self.bone_index_order[idx]
  1212.             find_node_index = self.bones[find_bone_index].node_index
  1213.  
  1214.             # Go through all bone parents and add them if a bone is parented to the current bone we are
  1215.             # scanning for. Also make sure we're not scanning ourselves
  1216.             for bone_index in range(len(self.bone_parents)):
  1217.                 if bone_index == find_bone_index:
  1218.                     continue
  1219.  
  1220.                 parent_bone = self.bone_parents[bone_index]
  1221.                 if parent_bone == None:
  1222.                     continue
  1223.  
  1224.                 if parent_bone.node_index == find_node_index:
  1225.                     self.bone_index_order.append(bone_index)
  1226.             idx += 1
  1227.  
  1228.     # get_bone_node
  1229.     #   Returns node that bone drives
  1230.     def get_bone_node(self, bone):
  1231.         return self.nodes[bone.node_index]
  1232.  
  1233.     # get_bone_to_model
  1234.     #   Returns the bone to model-space matrix
  1235.     def get_bone_to_model(self, bone):
  1236.         bone_index = self.node_to_bone_index[bone.node_index]
  1237.         return self.b2m_transforms[bone_index]
  1238.  
  1239.     # get_length
  1240.     #   Returns the length between bone and location
  1241.     def get_length(self, bone, origin):
  1242.         bone_index = self.node_to_bone_index[bone.node_index]
  1243.         bone_b2m = self.b2m_transforms[bone_index]
  1244.         bone_origin = bone_b2m.to_translation()
  1245.  
  1246.         vec = origin - bone_origin
  1247.         return vec.length
  1248.  
  1249.     # get_forward
  1250.     #   CLEANUP
  1251.     #   Not quite, used to fetch the 'forward' orientation of a rest bone matrix
  1252.     def get_forward(self, matrix):
  1253.         return Vector([matrix[0].x,matrix[1].x,matrix[2].x]).normalized()
  1254.         #return matrix[2].xyz.normalized()
  1255.  
  1256.     # get_bone_origin
  1257.     #   CLEANUP
  1258.     #   Incorrect, simply fetches an untransformed rest bone origin
  1259.     def get_bone_origin(self, bone):
  1260.         bone_index = self.node_to_bone_index[bone.node_index]
  1261.         bone_b2m = self.b2m_transforms[bone_index]
  1262.         return bone_b2m.to_translation()
  1263.  
  1264.     # get_look_at_child
  1265.     #   CLEANUP
  1266.     #   Returns a child bone that the input is looking at (can return None if not looking at a particular child)
  1267.     #   Finds a child bone that the parent is aiming at
  1268.     def get_look_at_child(self, bone):
  1269.         # Don't do anything if we don't have children
  1270.         bone_index = self.node_to_bone_index[bone.node_index]
  1271.         if len(self.bone_childs[bone_index]) <= 0:
  1272.             return None
  1273.  
  1274.         # bone transform and origin
  1275.         bone_b2m = self.b2m_transforms[bone_index]
  1276.         bone_origin = bone_b2m.to_translation()
  1277.        
  1278.         # Get bone direction as a normalized vector
  1279.         bone_forward = self.get_forward(bone_b2m)
  1280.  
  1281.         # Don't break on first child, check if we have a child closer to ourselves
  1282.         num_look = 0
  1283.         max_length = -sys.float_info.max
  1284.         min_length = sys.float_info.max
  1285.         for child in self.bone_childs[bone_index]:
  1286.             child_origin = self.get_bone_origin(child)
  1287.  
  1288.             # Create a vector pointing towards the child and check the angle between them
  1289.             # Subtract epsilon from 1 to allow some small slack to determine if point is
  1290.             # looking straight at child
  1291.             vec = (child_origin - bone_origin)
  1292.             cosv = bone_forward.dot(vec.normalized())
  1293.             if cosv >= (1.0 - 0.000001):
  1294.                 vec_len = vec.length
  1295.                 max_length = max( max_length, vec_len )
  1296.                 min_length = min( min_length, vec_len )
  1297.                 num_look += 1
  1298.  
  1299.         # If we didn't look at any point, return 'nothing'
  1300.         if num_look == 0:
  1301.             return None
  1302.  
  1303.         # Return a point inbetween all points we where looking straight at
  1304.         return bone_origin + bone_forward * ( min_length + max_length ) * 0.5
  1305.  
  1306.     # get_child_midpoint
  1307.     #   CLEANUP
  1308.     #   Sort of does what it says, it takes all childs of a bone and calculates a tail
  1309.     #   that lies inbetween the mean average of all child bones.
  1310.     def get_child_midpoint(self, bone):
  1311.         # Don't do anything if we don't have children
  1312.         bone_index = self.node_to_bone_index[bone.node_index]
  1313.         if len(self.bone_childs[bone_index]) <= 1:
  1314.             return None
  1315.  
  1316.         # bone transform and origin
  1317.         bone_b2m = self.b2m_transforms[bone_index]
  1318.         bone_origin = bone_b2m.to_translation()
  1319.        
  1320.         # Get bone direction as a normalized vector
  1321.         bone_forward = self.get_forward(bone_b2m)
  1322.  
  1323.         children = self.bone_childs[bone_index]
  1324.         mid_origin = self.get_bone_origin(children[0])
  1325.         for i in range(len(children)-1):
  1326.             mid_origin += (self.get_bone_origin(children[i+1]) - mid_origin) * 0.5
  1327.  
  1328.         return bone_origin + bone_forward * (mid_origin - bone_origin).length
  1329.  
  1330.     # project_vertex_group
  1331.     #   CLEANUP
  1332.     #   It does NOT project a vertex group against anything.
  1333.     #   Projects untransformed vertices onto untransformed rest bone and extrudes the
  1334.     #   tail to most extending vertex
  1335.     def project_vertex_group(self, bone, vertex_groups):
  1336.         if vertex_groups == None:
  1337.             return None
  1338.  
  1339.         bone_name = self.nodes[bone.node_index].name
  1340.         if bone_name not in vertex_groups.keys():
  1341.             return None
  1342.  
  1343.         mesh_data = self.node.mesh_entity.mesh_data
  1344.         positions = get_vertex_array(mesh_data, VTX_ARRAY_POSITION)
  1345.         if positions == None:
  1346.             return None
  1347.  
  1348.         bone_index = self.node_to_bone_index[bone.node_index]
  1349.        
  1350.         bone_b2m = self.b2m_transforms[bone_index]
  1351.         bone_origin = bone_b2m.to_translation()
  1352.        
  1353.         # Get bone direction as a normalized vector
  1354.         bone_forward = self.get_forward(bone_b2m)
  1355.  
  1356.         group = vertex_groups[bone_name]
  1357.         max_length = -sys.float_info.max
  1358.         for (vi, w) in group:
  1359.             if w < 0.000001:
  1360.                 continue
  1361.             vpos = Vector(positions[vi])
  1362.             vec = vpos - bone_origin
  1363.             if bone_forward.dot(vec) <= 0.000001:
  1364.                 continue
  1365.  
  1366.             max_length = max(max_length, vec.length * math.sqrt(w))
  1367.  
  1368.         return bone_origin + bone_forward * max_length
  1369.  
  1370.  
  1371. # FrameObject
  1372. #   Used for posing an object or bone during animation, only represents current rotations
  1373. class FrameObject():
  1374.     __slots__ = (
  1375.         "name",
  1376.         "parent",
  1377.         "matrix_local",
  1378.         "matrix_world",
  1379.         "has_position_track",
  1380.         "has_rotation_track",
  1381.         "has_scale_track",
  1382.         "has_any_track",
  1383.         )
  1384.  
  1385.     def __init__(self, bl_object):
  1386.         self.name = bl_object.name
  1387.         self.parent = bl_object.parent
  1388.         self.matrix_local = bl_object.matrix_local
  1389.         self.matrix_world = bl_object.matrix_world
  1390.         self.has_position_track = False
  1391.         self.has_rotation_track = False
  1392.         self.has_scale_track = False
  1393.         self.has_any_track = False
  1394.  
  1395.     # clear_tracks
  1396.     #   Remove animation frame tracks for this frame
  1397.     def clear_tracks(self):
  1398.         self.has_position_track = False
  1399.         self.has_rotation_track = False
  1400.         self.has_scale_track = False
  1401.         self.has_any_track = False
  1402.  
  1403.     # set_tracks
  1404.     #   Specify animation frame tracks used this frame
  1405.     def set_tracks(self, position, rotation, scale):
  1406.         self.has_position_track = position
  1407.         self.has_rotation_track = rotation
  1408.         self.has_scale_track = scale
  1409.         self.has_any_track = position or rotation or scale
  1410.  
  1411. # ObjectKeyFrame
  1412. #   Specifies an object setup to generate an animation frame from
  1413. class ObjectKeyframe():
  1414.     __slots__ = (
  1415.         "frame_objects",
  1416.         "name_to_index_map"
  1417.         )
  1418.  
  1419.     def __init__(self):
  1420.         self.frame_objects = []
  1421.         self.name_to_index_map = {}
  1422.  
  1423.     # add_object
  1424.     #   Adds a frame object using a Blender object as input
  1425.     def add_object(self, bl_object):
  1426.         frame_object = FrameObject(bl_object)
  1427.  
  1428.         self.name_to_index_map[frame_object.name] = len(self.frame_objects)
  1429.         self.frame_objects.append(frame_object)
  1430.  
  1431.     # get_object
  1432.     #   Returns a frame object mapped to a specific name
  1433.     def get_object(self, name):
  1434.         index = self.name_to_index_map[name]
  1435.         return self.frame_objects[index]
  1436.  
  1437.     # evaluate
  1438.     #   Traverses the objects and calculates the world matrix in sequential order
  1439.     #   Parents are garaunteed to be evaluated before children
  1440.     def evaluate(self):
  1441.         for frame_object in self.frame_objects:
  1442.             if frame_object.parent != None:
  1443.                 parent = self.get_object(frame_object.parent.name)
  1444.                 frame_object.matrix_world = parent.matrix_world * frame_object.matrix_local
  1445.             else:
  1446.                 frame_object.matrix_world = frame_object.matrix_local
  1447.  
  1448.  
  1449. class TriMaterial():
  1450.     __slots__ = (
  1451.         "material_index",
  1452.         "indices",
  1453.         )
  1454.  
  1455.     def __init__(self):
  1456.         self.material_index = -1
  1457.         self.indices = []
  1458.  
  1459. class TriSurface():
  1460.     __slots__ = (
  1461.         "bl_vertex_weights",
  1462.         "vertices",
  1463.         "colors",
  1464.         "normals",
  1465.         "uvs",
  1466.         "tangents",
  1467.         "bitangents",
  1468.         "bone_weights",
  1469.         "bone_indices",
  1470.         "materials",
  1471.         "num_indices",
  1472.         "vertex_hash",
  1473.         "bound_hash",
  1474.         "bound_min",
  1475.         "bound_max",
  1476.         "has_uv",
  1477.         "has_color",
  1478.         "has_tangents",
  1479.         "has_weights",
  1480.         )
  1481.  
  1482.     def __init__(self):
  1483.         self.bl_vertex_weights = []
  1484.         self.vertices = []
  1485.         self.colors = []
  1486.         self.normals = []
  1487.         self.uvs = []
  1488.         self.tangents = []
  1489.         self.bitangents = []
  1490.         self.bone_weights = []
  1491.         self.bone_indices = []
  1492.         self.materials = []
  1493.         self.num_indices = 0
  1494.         self.vertex_hash = {k: [] for k in range(1024)}
  1495.         self.bound_hash = [0.0, 0.0, 0.0]
  1496.         self.bound_min = [0.0, 0.0, 0.0]
  1497.         self.bound_max = [0.0, 0.0, 0.0]
  1498.         self.has_uv = False
  1499.         self.has_color = False
  1500.         self.has_tangents = False
  1501.         self.has_weights = False
  1502.  
  1503.     @staticmethod
  1504.     def compare_vec3(vec1, vec2, epsilon = 0.000001):
  1505.         return abs(vec1[0] - vec2[0]) <= epsilon and abs(vec1[1] - vec2[1]) <= epsilon and abs(vec1[2] - vec2[2]) <= epsilon
  1506.  
  1507.     @staticmethod
  1508.     def compare_vec2(vec1, vec2, epsilon = 0.000001):
  1509.         return abs(vec1[0] - vec2[0]) <= epsilon and abs(vec1[1] - vec2[1]) <= epsilon
  1510.  
  1511.     # get_vertex_hash_key
  1512.     #   Generates a hash key for a vertex coordinate
  1513.     def get_vertex_hash_key(self, vertex):
  1514.         x = int((vertex[0] - self.bound_min[0]) * self.bound_hash[0])
  1515.         y = int((vertex[1] - self.bound_min[1]) * self.bound_hash[1])
  1516.         z = int((vertex[2] - self.bound_min[2]) * self.bound_hash[2])
  1517.         return (x + y + z) % 1024
  1518.  
  1519.     # add_unique_vertex
  1520.     #   Vertex welding, vertex coordinate, uv, color and tangent and bitangent must all be equal to count as single vertex
  1521.     def add_unique_vertex(self, bl_mesh, loop_index, poly_loop, uv_loops, color_loops):
  1522.         # Fetch vertex coordinate
  1523.         vertex = bl_mesh.vertices[poly_loop.vertex_index].co
  1524.         #vertex = (vertex.x, vertex.y, vertex.z)
  1525.  
  1526.         # Fetch normal
  1527.         normal = poly_loop.normal
  1528.  
  1529.         # Fetch color if available
  1530.         color = None
  1531.         if color_loops != None:
  1532.             color = color_loops[loop_index].color
  1533.  
  1534.         # Fetch uv, tangent and bitangent if available
  1535.         uv = None
  1536.         tangent = None
  1537.         bitangent = None
  1538.         if uv_loops != None:
  1539.             uv = uv_loops[loop_index].uv
  1540.             tangent = poly_loop.tangent
  1541.             bitangent = poly_loop.bitangent
  1542.  
  1543.         # Generate hash-key for vertex coordinate
  1544.         hash_key = self.get_vertex_hash_key(vertex)
  1545.  
  1546.         # Check if vertex already exists or we need to create one
  1547.         for vertex_index in self.vertex_hash[hash_key]:
  1548.             if not TriSurface.compare_vec3(self.vertices[vertex_index], vertex):
  1549.                 continue
  1550.  
  1551.             if not TriSurface.compare_vec3(self.normals[vertex_index], normal):
  1552.                 continue
  1553.  
  1554.             if uv_loops != None:
  1555.                 if not TriSurface.compare_vec2(self.uvs[vertex_index], uv):
  1556.                     continue
  1557.  
  1558.                 if not TriSurface.compare_vec3(self.tangents[vertex_index], tangent):
  1559.                     continue
  1560.  
  1561.                 # Maybe check sign only ?
  1562.                 if not TriSurface.compare_vec3(self.bitangents[vertex_index], bitangent):
  1563.                     continue
  1564.  
  1565.             if color_loops != None:
  1566.                 if not TriSurface.compare_vec3(self.colors[vertex_index], color, epsilon = 0.001):
  1567.                     continue
  1568.  
  1569.             # Everything is a match, return this vertex
  1570.             return vertex_index
  1571.  
  1572.         # Nothing found, create new unique vertex
  1573.         new_vertex_index = len(self.vertices)
  1574.  
  1575.         # Add to hash for lookup
  1576.         self.vertex_hash[hash_key].append(new_vertex_index)
  1577.  
  1578.         # Add vertex coordinate and normal to lists
  1579.         self.vertices.append((vertex[0], vertex[1], vertex[2]))
  1580.         self.normals.append((normal[0], normal[1], normal[2]))
  1581.  
  1582.         # Add color to list, if available
  1583.         if color_loops != None:
  1584.             self.colors.append((color.r, color.g, color.b))
  1585.  
  1586.         # Add uv, tangent and bitangent to lists, if available
  1587.         if uv_loops != None:
  1588.             self.uvs.append((uv[0], uv[1]))
  1589.             self.tangents.append((tangent[0], tangent[1], tangent[2]))
  1590.             self.bitangents.append((bitangent[0], bitangent[1], bitangent[2]))
  1591.  
  1592.         # Add weights, if available
  1593.         if self.has_weights:
  1594.             bl_bone_weights, bl_bone_indices = zip(*self.bl_vertex_weights[poly_loop.vertex_index])
  1595.             self.bone_weights.append((bl_bone_weights[0], bl_bone_weights[1], bl_bone_weights[2], bl_bone_weights[3]))
  1596.             self.bone_indices.append((bl_bone_indices[0], bl_bone_indices[1], bl_bone_indices[2], bl_bone_indices[3]))
  1597.  
  1598.         # Return the index of the newly created vertex
  1599.         return new_vertex_index
  1600.  
  1601.     # create_from_mesh
  1602.     #   Generate a triangle surface
  1603.     def create_from_mesh(self, settings, bl_mesh, bones_name, bl_vertex_groups, bl_bounds):
  1604.         # Figure out hash bounds
  1605.         bound_min = [bl_bounds[0][0], bl_bounds[0][1], bl_bounds[0][2]]
  1606.         bound_max = [bl_bounds[0][0], bl_bounds[0][1], bl_bounds[0][2]]
  1607.         for i in range(8):
  1608.             bl_bound = bl_bounds[i]
  1609.             for j in range(3):
  1610.                 bound_min[j] = min(bound_min[j], bl_bound[j])
  1611.                 bound_max[j] = max(bound_max[j], bl_bound[j])
  1612.  
  1613.         for i in range(3):
  1614.             size = bound_max[i] - bound_min[i]
  1615.             self.bound_hash[i] = 1024.0 / size
  1616.             self.bound_min[i] = bound_min[i]
  1617.             self.bound_max[i] = bound_max[i]
  1618.        
  1619.         # Only allow exporting from quads or tris
  1620.         # Select invalid polygons so we can show users where problems occoured
  1621.         # FIXME: Fix the selection, it could be nice to tell the user it failed and show them why
  1622.         #for polygon in bl_mesh.polygons:
  1623.         #    if polygon.loop_total > 4:
  1624.         #        return None
  1625.  
  1626.         # Only calculate tangents if we have a active uv-layer, and we selected tangents in settings
  1627.         if bl_mesh.uv_layers.active != None and settings.option_tangents:
  1628.             bl_mesh.calc_tangents(uvmap = bl_mesh.uv_layers.active.name)
  1629.             self.has_tangents = True
  1630.  
  1631.         # Calculate split normals if we never calculated the tangents
  1632.         if not self.has_tangents:
  1633.             bl_mesh.calc_normals_split()
  1634.  
  1635.         # Collect weights for each vertex, make sure each vertex has exactly 4 weights, if bones are available
  1636.         if bones_name != None:
  1637.             self.has_weights = True
  1638.             for vertex in bl_mesh.vertices:
  1639.  
  1640.                 # Figure out bones and weights for vertex
  1641.                 bl_vertex_weights = []
  1642.                 for group in vertex.groups:
  1643.                     vertex_group = bl_vertex_groups[group.group]
  1644.                     bone_index = bones_name.index(vertex_group.name)
  1645.                     bone_weight = vertex_group.weight(vertex.index)
  1646.                     bl_vertex_weights.append((bone_weight, bone_index))
  1647.                
  1648.                 # Ensure exactly 4 weights, always
  1649.                 num_weights = len(bl_vertex_weights)
  1650.                 if num_weights > 4:
  1651.                     bl_vertex_weights.sort()
  1652.                     bl_vertex_weights = bl_vertex_weights[num_weights - 4:]
  1653.                 elif num_weights < 4:
  1654.                     bl_vertex_weights += [(0.0, 0)] * (4 - num_weights)
  1655.  
  1656.                 # Add the four weights and indices
  1657.                 self.bl_vertex_weights.append(bl_vertex_weights)
  1658.  
  1659.         # Create material slots
  1660.         for bl_material in bl_mesh.materials:
  1661.             material = TriMaterial()
  1662.             self.materials.append(material)
  1663.  
  1664.         # Select active uv- and color layer
  1665.         uv_layer = bl_mesh.uv_layers.active
  1666.         color_layer = bl_mesh.vertex_colors.active
  1667.  
  1668.         # Fetch the uv-loops
  1669.         uv_loops = None
  1670.         if uv_layer != None:
  1671.             self.has_uv = True
  1672.             uv_loops = uv_layer.data
  1673.  
  1674.         # Fetch the color-loops
  1675.         color_loops = None
  1676.         if color_layer != None:
  1677.             self.has_color = True
  1678.             color_loops = color_layer.data
  1679.  
  1680.         # Fetch the geometry loops
  1681.         poly_loops = bl_mesh.loops
  1682.         poly_indices = [None,None,None,None]
  1683.  
  1684.         # Create vertices
  1685.         for polygon in bl_mesh.polygons:
  1686.             is_quad = len(polygon.loop_indices) == 4
  1687.             for i, loop_index in enumerate(polygon.loop_indices):
  1688.                 poly_indices[i] = self.add_unique_vertex(bl_mesh, loop_index, poly_loops[loop_index], uv_loops, color_loops)
  1689.  
  1690.             material = self.materials[polygon.material_index]
  1691.  
  1692.             material.indices.append(poly_indices[0])
  1693.             for i in range(2):
  1694.                 material.indices.append(poly_indices[1+i])
  1695.            
  1696.             if is_quad:
  1697.                 # FIXME: Need to split quads the same way Mikkelsen does it, otherwise we break the tangent space
  1698.                 material.indices.append(poly_indices[0])
  1699.                 for i in range(2):
  1700.                     material.indices.append(poly_indices[2+i])
  1701.  
  1702.         # Accumulate total number of indices from all materials
  1703.         for material in self.materials:
  1704.             self.num_indices += len(material.indices)
  1705.  
  1706.  
  1707. # create_game_to_blender_matrix
  1708. #   Generates a matrix that goes from LHS to RHS and orients the model in a more Blender friendly direction
  1709. def create_game_to_blender_matrix():
  1710.     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()
  1711.  
  1712.  
  1713. # create_bone_space_matrix
  1714. #   Generates a matrix that transforms a matrix in to Blender bone space orientation
  1715. def create_bone_space_matrix():
  1716.     return Matrix(([ 0, 1, 0, 0],[-1, 0, 0, 0],[ 0, 0, 1, 0],[ 0, 0, 0, 1]))
  1717.  
  1718.  
  1719. # get_vertex_array
  1720. #   Returns either a valid vertex arrays data container or None if invalid.
  1721. def get_vertex_array(mesh_data, vtx_array_type):
  1722.     if mesh_data.vertex_arrays[vtx_array_type] != None:
  1723.         return mesh_data.vertex_arrays[vtx_array_type].data
  1724.     return None
  1725.  
  1726.  
  1727. # convert_colors
  1728. #   Converts an array of byte range [0, 255] colors to float range [0, 1] values
  1729. #   It simply does so by multiplying all values with the reciprocal of 255
  1730. def convert_colors(byte_colors):
  1731.     num = len(byte_colors)
  1732.     dim = len(byte_colors[0])
  1733.     float_colors = num * [dim * [0.0]]
  1734.     scale = 1.0 / 255.0
  1735.     for i in range(num):
  1736.         for j in range(dim):
  1737.             float_colors[i][j] = float( byte_colors[i][j] ) * scale
  1738.  
  1739.     return float_colors
  1740.  
  1741.  
  1742. # convert_color_float3_to_byte4
  1743. #   Simple float to byte color conversion
  1744. def convert_color_float3_to_byte4(float_color):
  1745.     # 1.0 / 255.0 = 0.003921568627451
  1746.     return (
  1747.         max(0, min(255, int(float_color[0] * 0.003921568627451))),
  1748.         max(0, min(255, int(float_color[1] * 0.003921568627451))),
  1749.         max(0, min(255, int(float_color[2] * 0.003921568627451))),
  1750.         255
  1751.         )
  1752.  
  1753.  
  1754. # apply_local_matrix
  1755. #   Applies a nodes local to parent matrix to a Blender objects local matrix
  1756. def apply_local_matrix(ob, node):
  1757.     ob.matrix_local = node.local_to_parent.to_matrix()
  1758.  
  1759.  
  1760. # calculate_frame
  1761. #   Creates world transforms for all nodes
  1762. def calculate_frame(nodes, game_matrix):
  1763.     num_nodes = len(nodes)
  1764.     unsorted_node_indices = range(num_nodes)
  1765.  
  1766.     sorted_node_indices = []
  1767.    
  1768.     # Collect the root nodes only
  1769.     for node_index in unsorted_node_indices:
  1770.         node = nodes[node_index]
  1771.         if node.parent == -1:
  1772.             sorted_node_indices.append(node_index)
  1773.  
  1774.     # Run a pass and add nodes until all unsorted nodes have been added to the sorted list
  1775.     idx = 0
  1776.     while idx < len(sorted_node_indices):
  1777.         # Get the node that we should check parents against
  1778.         find_node_index = sorted_node_indices[idx]
  1779.        
  1780.         # For each node in the unsorted list, check if it's parented to the current sorted node we're looking at
  1781.         for node_index in unsorted_node_indices:
  1782.             # Skip ourselves
  1783.             if find_node_index == node_index:
  1784.                 continue
  1785.            
  1786.             if nodes[node_index].parent == -1:
  1787.                 continue
  1788.  
  1789.             # Append if unsorted parent node is the same as current sorted node
  1790.             if nodes[node_index].parent == find_node_index:
  1791.                 sorted_node_indices.append(node_index)
  1792.  
  1793.         idx += 1
  1794.  
  1795.     # Create the base transform, we add the game_matrix here since it's not part of the node hierarchy
  1796.     base_transform = Matrix.Identity(4) * game_matrix
  1797.    
  1798.     # Transform all nodes into world space using sorted list so we are garantueed to have a composite matrix
  1799.     # for parents that should use it
  1800.     unsorted_node_transforms = [None] * num_nodes
  1801.     frame = [None] * num_nodes
  1802.     for i in unsorted_node_indices:
  1803.         node_index = sorted_node_indices[i]
  1804.         node = nodes[node_index]
  1805.  
  1806.         local_to_parent = node.local_to_parent.to_matrix()
  1807.         if node.parent != -1:
  1808.             unsorted_node_transforms[node_index] = unsorted_node_transforms[node.parent] * local_to_parent
  1809.         else:
  1810.             unsorted_node_transforms[node_index] = base_transform * local_to_parent
  1811.  
  1812.         frame[i] = [node_index, unsorted_node_transforms[node_index]]
  1813.  
  1814.     # Return frame
  1815.     return frame
  1816.  
  1817.  
  1818. # create_object_frame
  1819. #   Create a frame structure for animation purposes out of object heirarcy in blender scene
  1820. def create_object_frame():
  1821.     if "game_matrix" not in bpy.data.objects.keys():
  1822.         return None
  1823.  
  1824.     frame = ObjectKeyframe()
  1825.  
  1826.     objects = []
  1827.     objects.append(bpy.data.objects["game_matrix"])
  1828.  
  1829.     idx = 0
  1830.     while idx < len(objects):
  1831.         obj = objects[idx]
  1832.        
  1833.         frame.add_object(obj)
  1834.         for child in obj.children:
  1835.             objects.append(child)
  1836.        
  1837.         idx += 1
  1838.  
  1839.     return frame
  1840.  
  1841.  
  1842. # load_binary_model
  1843. #   Loads a binary model using Grimrock 2 documentation
  1844. #       FourCC magic
  1845. #       int32 version
  1846. #       int32 numNodes
  1847. #       (Node) * numNodes
  1848. def load_binary_model(file_object, context):
  1849.  
  1850.     # Try to read the binary model
  1851.     model = Model()
  1852.     if not model.read(file_object):
  1853.         print("Failed to load ModelFile")
  1854.         return False
  1855.  
  1856.     build_model(model)
  1857.  
  1858.     return True
  1859.  
  1860.  
  1861. # save_binary_model
  1862. #   Saves blender scene into a binary model compatible with Grimrock 2
  1863. def save_binary_model(file_object, settings, context):
  1864.     model = create_model(settings)
  1865.     model.write(file_object)
  1866.     return True
  1867.  
  1868. # load_binary_animation
  1869. #   Loads a binary model using Grimrock 2 documentation
  1870. def load_binary_animation(file_object, armature, context):
  1871.  
  1872.     # Try to read the binary animation
  1873.     anim = Animation()
  1874.     if not anim.read(file_object):
  1875.         print("Failed to load AnimationFile")
  1876.         return False
  1877.  
  1878.     # Apply the animation to the armature
  1879.     build_animation(armature, anim)
  1880.  
  1881.     return True
  1882.  
  1883.  
  1884. # build_vertex_groups
  1885. #   Builds vertex groups for skinning
  1886. def build_vertex_groups(model, node):
  1887.     if node.mesh_entity.num_bones <= 0:
  1888.         return None
  1889.  
  1890.     bone_indices = get_vertex_array(node.mesh_entity.mesh_data, VTX_ARRAY_BONE_INDEX)
  1891.     bone_weights = get_vertex_array(node.mesh_entity.mesh_data, VTX_ARRAY_BONE_WEIGHT)
  1892.     if bone_indices == None or bone_weights == None:
  1893.         return None
  1894.  
  1895.     vertex_groups = {}
  1896.     for i in range(node.mesh_entity.mesh_data.num_vertices):
  1897.         vtx_bone_indices = bone_indices[i]
  1898.         vtx_bone_weights = bone_weights[i]
  1899.  
  1900.         num_bone_indices = len(vtx_bone_indices)
  1901.         for j in range(num_bone_indices):
  1902.             bone_index = vtx_bone_indices[j]
  1903.             bone_weight = vtx_bone_weights[j]
  1904.  
  1905.             bone = node.mesh_entity.bones[bone_index]
  1906.             deform_node = model.nodes[bone.node_index]
  1907.  
  1908.             if deform_node.name not in vertex_groups.keys():
  1909.                 vertex_groups[deform_node.name] = []
  1910.  
  1911.             vertex_groups[deform_node.name].append([i, bone_weight])
  1912.  
  1913.     return vertex_groups
  1914.  
  1915.  
  1916. # build_armature
  1917. #   Builds a blender armature from Grimrock Model description
  1918. def build_armature(model, node, vertex_groups, game_matrix):
  1919.     if node.mesh_entity.num_bones <= 0:
  1920.         return None
  1921.  
  1922.     # Create an armature object
  1923.     armature = bpy.data.armatures.new(node.name + "_rig")
  1924.     armature.draw_type = 'STICK'
  1925.  
  1926.     # Create a rig
  1927.     rig = bpy.data.objects.new(node.name + "_rig", armature)
  1928.     rig.show_x_ray = True
  1929.  
  1930.     if node.parent != -1:
  1931.         rig.parent = bpy.data.objects[model.nodes[node.parent].name]
  1932.     #apply_transform(rig, model.nodes, node, game_matrix)
  1933.     apply_local_matrix(rig, node)
  1934.  
  1935.     # Update scene and set active rig
  1936.     bpy.context.scene.objects.link(rig)
  1937.     bpy.context.scene.objects.active = rig
  1938.     bpy.context.scene.update()
  1939.  
  1940.     # Switch to edit mode and create all bones
  1941.     if bpy.ops.object.mode_set.poll():
  1942.         bpy.ops.object.mode_set(mode='EDIT')
  1943.  
  1944.     # Do some pre-processing to make it easier for us when generating the armature
  1945.     info = ArmatureInfo(model, node)
  1946.  
  1947.     # Calculate world transforms for all nodes
  1948.     frame = calculate_frame(model.nodes, game_matrix)
  1949.  
  1950.     # Build bones using our bone index order list (this ensures roots are created before children)
  1951.     for bone_index in info.bone_index_order:
  1952.         bone = info.bones[bone_index]
  1953.         deform_node = info.nodes[bone.node_index]
  1954.         b2m = info.b2m_transforms[bone_index]
  1955.        
  1956.         # Start by checking if we're looking at any particular child (crossing their head positions, if any)
  1957.         tail_origin = info.get_look_at_child(bone)
  1958.        
  1959.         # If we're not crossing any child bones, fetch the middle point of all childs (if any)
  1960.         if tail_origin == None:
  1961.             tail_origin = info.get_child_midpoint(bone)
  1962.  
  1963.         # Try projecting it towards vertices influenced by bone
  1964.         if tail_origin == None:
  1965.             tail_origin = info.project_vertex_group(bone, vertex_groups)
  1966.  
  1967.         # If no tail origin has been found, just extrude the bone in it's forward direction
  1968.         if tail_origin == None:
  1969.             tail_origin = b2m.to_translation() + info.get_forward(b2m)
  1970.  
  1971.         bone_length = info.get_length(bone, tail_origin)
  1972.         bone_length = max(bone_length, 0.001)
  1973.  
  1974.         # Fetch Blender EditBone parent
  1975.         bl_bone_parent = None
  1976.         bone_parent = info.bone_parents[bone_index]
  1977.         if bone_parent != None:
  1978.             parent_node = model.nodes[bone_parent.node_index]
  1979.             bl_bone_parent = armature.edit_bones[parent_node.name]
  1980.  
  1981.         bl_bone = armature.edit_bones.new(deform_node.name)
  1982.         bl_bone.head = Vector([0, 0, 0])
  1983.         bl_bone.tail = Vector([bone_length, 0, 0])
  1984.         bl_bone.transform(b2m.to_3x3())
  1985.         bl_bone.translate(b2m.to_translation())
  1986.         bl_bone.parent = bl_bone_parent
  1987.  
  1988.     # Switch back to object mode in order to refresh armature
  1989.     if bpy.ops.object.mode_set.poll():
  1990.         bpy.ops.object.mode_set(mode='OBJECT')
  1991.  
  1992.     # Switch to pose mode and pose the model in initial position
  1993.     if bpy.ops.object.mode_set.poll():
  1994.         bpy.ops.object.mode_set(mode='POSE')
  1995.  
  1996.     # pose the bones in initial state
  1997.     bl_bone_space = create_bone_space_matrix()
  1998.  
  1999.     bpy.context.scene.update()
  2000.  
  2001.     for (node_index, node_transform) in frame:
  2002.         deform_node = model.nodes[node_index]
  2003.         if not deform_node.name in rig.pose.bones.keys():
  2004.             continue
  2005.  
  2006.         pose_bone = rig.pose.bones[deform_node.name]
  2007.        
  2008.         pm = rig.matrix_world.inverted() * node_transform * bl_bone_space
  2009.         pose_bone.matrix = pm
  2010.  
  2011.         # FIXME: hack, flush the animation system in Blender
  2012.         #bpy.context.scene.frame_set(0)
  2013.         bpy.context.scene.update()
  2014.  
  2015.     bpy.context.scene.update()
  2016.  
  2017.     # Done, switch back to object mode
  2018.     if bpy.ops.object.mode_set.poll():
  2019.         bpy.ops.object.mode_set(mode='OBJECT')
  2020.  
  2021.     bpy.context.scene.update()
  2022.  
  2023.     return rig
  2024.  
  2025.  
  2026. # build_model
  2027. #   Builds Blender objects from Grimrock Model
  2028. def build_model(model):
  2029.     game_matrix = create_game_to_blender_matrix()
  2030.  
  2031.     # Calculate world transforms for all nodes
  2032.     frame = calculate_frame(model.nodes, game_matrix)
  2033.  
  2034.     # Before adding any meshes or armatures go into Object mode.
  2035.     if bpy.ops.object.mode_set.poll():
  2036.         bpy.ops.object.mode_set(mode='OBJECT')
  2037.  
  2038.     # Build top most game matrix object
  2039.     game_matrix_ob = bpy.data.objects.new("game_matrix", None)
  2040.     game_matrix_ob.empty_draw_type = 'ARROWS'
  2041.     game_matrix_ob.empty_draw_size = 0.25
  2042.     game_matrix_ob.matrix_local = game_matrix
  2043.     bpy.context.scene.objects.link(game_matrix_ob)
  2044.  
  2045.     # Build all nodes but skip mesh entity nodes since we create them in a later loop
  2046.     for (node_index, node_transform) in frame:
  2047.         node = model.nodes[node_index]
  2048.         if node.mesh_entity != None:
  2049.             continue
  2050.  
  2051.         # Fetch parent object, if there is no parent for the node we use the
  2052.         # top most game matrix conversion object
  2053.         parent_ob = None
  2054.         if node.parent != -1:
  2055.             parent_name = model.nodes[node.parent].name
  2056.             parent_ob = bpy.data.objects[parent_name]
  2057.         else:
  2058.             parent_ob = game_matrix_ob
  2059.  
  2060.         node_ob = bpy.data.objects.new(node.name, None)
  2061.         bpy.context.scene.objects.link(node_ob)
  2062.  
  2063.         node_ob.empty_draw_type = 'ARROWS'
  2064.         node_ob.empty_draw_size = 0.25
  2065.         node_ob.parent = parent_ob
  2066.         node_ob.matrix_world = node_transform
  2067.  
  2068.     # Update the scene after all objects have been created
  2069.     bpy.context.scene.update()
  2070.  
  2071.     # Now loop through all nodes again, but this time create the actuall mesh objects
  2072.     for (node_index, node_transform) in frame:
  2073.         node = model.nodes[node_index]
  2074.         if node.mesh_entity == None:
  2075.             continue
  2076.  
  2077.         # Build vertex groups for skinning
  2078.         vertex_groups = build_vertex_groups(model, node)
  2079.  
  2080.         # Build the armature and pose it in inital state
  2081.         bl_rig = build_armature(model, node, vertex_groups, game_matrix)
  2082.  
  2083.         # Fetch needed data to build
  2084.         mesh_data = node.mesh_entity.mesh_data
  2085.        
  2086.         positions = get_vertex_array(mesh_data, VTX_ARRAY_POSITION)
  2087.         normals = get_vertex_array(mesh_data, VTX_ARRAY_NORMAL)
  2088.         colors = get_vertex_array(mesh_data, VTX_ARRAY_COLOR)
  2089.         indices = mesh_data.indices
  2090.  
  2091.         num_faces = int( mesh_data.num_indices / 3 )
  2092.  
  2093.         # Create Mesh to work with
  2094.         me = bpy.data.meshes.new(node.name)
  2095.  
  2096.         # Auto smooth the mesh
  2097.         me.use_auto_smooth = True
  2098.  
  2099.         # Add vertices
  2100.         #   Note: These are in native game format, object hierarchy takes care of the transform
  2101.         me.vertices.add(mesh_data.num_vertices)
  2102.         for i in range(mesh_data.num_vertices):
  2103.             co = Vector(positions[i])
  2104.             me.vertices[i].co = (co.x, co.y, co.z)
  2105.  
  2106.         # Add normals
  2107.         #   Note: These are in native game format, object hierarchy takes care of the transform
  2108.         if normals != None:
  2109.             for i in range(mesh_data.num_vertices):
  2110.                 normal = Vector(normals[i])
  2111.                 me.vertices[i].normal = (normal.x, normal.y, normal.z)
  2112.  
  2113.         # Add faces
  2114.         #   Note: No flipping, object hierarchy makes sure this comes out correct
  2115.         me.tessfaces.add(num_faces)
  2116.         for i in range(num_faces):
  2117.             idx = i * 3
  2118.             me.tessfaces[i].vertices_raw = (indices[idx+0], indices[idx+1], indices[idx+2], 0)
  2119.             me.tessfaces[i].use_smooth = True
  2120.        
  2121.         # Add colors
  2122.         if colors != None:
  2123.             # Create color-set layer
  2124.             color_layer = me.tessface_vertex_colors.new("colorset")
  2125.             me.tessface_vertex_colors.active = color_layer
  2126.            
  2127.             # Convert to float range
  2128.             float_colors = convert_colors(colors)
  2129.  
  2130.             # Assign colors
  2131.             for f in me.tessfaces:
  2132.                 color_layer.data[f.index].color1 = float_colors[f.vertices[0]][0:3]
  2133.                 color_layer.data[f.index].color2 = float_colors[f.vertices[1]][0:3]
  2134.                 color_layer.data[f.index].color3 = float_colors[f.vertices[2]][0:3]
  2135.  
  2136.         # Add texture coordinate sets
  2137.         #   Note: We flip the v-coordinates, so remember to reverse that when exporting out!
  2138.         first_uv_layer = None
  2139.         for i in range(8):
  2140.             tex_coords = get_vertex_array(mesh_data, VTX_ARRAY_TEXCOORD0 + i)
  2141.             if tex_coords == None:
  2142.                 continue
  2143.  
  2144.             # Create uv-layer for these texture coordinates
  2145.             uv_layer = me.tessface_uv_textures.new("uvset%d" % i)
  2146.             me.tessface_uv_textures.active = uv_layer
  2147.             if first_uv_layer == None:
  2148.                 first_uv_layer = uv_layer
  2149.  
  2150.             # Assign uv coordinates to layer faces
  2151.             for f in me.tessfaces:
  2152.                 uvco1 = tex_coords[f.vertices[0]][0:2]
  2153.                 uvco2 = tex_coords[f.vertices[1]][0:2]
  2154.                 uvco3 = tex_coords[f.vertices[2]][0:2]
  2155.  
  2156.                 # Flip v coordinates
  2157.                 uvco1 = (uvco1[0], 1.0 - uvco1[1])
  2158.                 uvco2 = (uvco2[0], 1.0 - uvco2[1])
  2159.                 uvco3 = (uvco3[0], 1.0 - uvco3[1])
  2160.  
  2161.                 uv_layer.data[f.index].uv = (uvco1, uvco2, uvco3)
  2162.  
  2163.         # Set first created uv-layer as active layer
  2164.         if first_uv_layer != None:
  2165.             me.tessface_uv_textures.active = first_uv_layer
  2166.  
  2167.         # Create and assign materials
  2168.         for segment in mesh_data.segments:
  2169.             bl_material = None
  2170.             if segment.material not in bpy.data.materials.keys():
  2171.                 bl_material = bpy.data.materials.new(segment.material)
  2172.             else:
  2173.                 bl_material = bpy.data.materials[segment.material]
  2174.  
  2175.             material_index = len(me.materials)
  2176.             me.materials.append(bl_material)
  2177.             face_offset = int(segment.index_offset / 3)
  2178.             for fi in range(segment.num_triangles):
  2179.                 me.tessfaces[fi].material_index = material_index
  2180.  
  2181.         # Figure out parent object for this node, if none we automatically add it to the game matrix object
  2182.         parent_ob = game_matrix_ob
  2183.         if node.parent != -1:
  2184.             parent_node = model.nodes[node.parent]
  2185.             parent_ob = bpy.data.objects[parent_node.name]
  2186.  
  2187.         # Create mesh object and link with scene
  2188.         node_ob = bpy.data.objects.new(node.name, me)
  2189.         node_ob.parent = parent_ob
  2190.         node_ob.matrix_world = node_transform
  2191.  
  2192.         # Link with scene
  2193.         bpy.context.scene.objects.link(node_ob)
  2194.  
  2195.         # Create the skinning groups for this object as well as a modifier for the rig
  2196.         if vertex_groups != None:
  2197.             for group_name, group in vertex_groups.items():
  2198.                 bl_group = node_ob.vertex_groups.new(group_name)
  2199.                 for (v, w) in group:
  2200.                     bl_group.add([v], w, 'ADD')
  2201.             mod = node_ob.modifiers.new('RigModifier', 'ARMATURE')
  2202.             mod.object = bl_rig
  2203.             mod.use_bone_envelopes = False
  2204.             mod.use_vertex_groups = True
  2205.  
  2206.         # Update the mesh
  2207.         me.update()
  2208.  
  2209.     # Update scene
  2210.     bpy.context.scene.update()
  2211.  
  2212.  
  2213. # build_animation
  2214. def build_animation(armature_object, anim):
  2215.  
  2216.     #bpy.context.window_manager.progress_begin(0, 9999)
  2217.  
  2218.     # This isn't quite correct.
  2219.     # fps isn't right since we simply truncate to integer
  2220.     fps = int(anim.frames_per_second)
  2221.  
  2222.     # Figure out range of animation
  2223.     frame_start = 1
  2224.     frame_end = 1 + anim.num_frames
  2225.  
  2226.     # Update frame settings
  2227.     bpy.context.scene.render.fps = fps
  2228.     bpy.context.scene.render.fps_base = 1.0
  2229.     bpy.context.scene.frame_start = frame_start
  2230.     bpy.context.scene.frame_end = frame_end
  2231.  
  2232.     bpy.context.scene.update()
  2233.  
  2234.     game_matrix = create_game_to_blender_matrix()
  2235.  
  2236.     # Set the armature as the current selected object
  2237.     bpy.context.scene.objects.active = armature_object
  2238.  
  2239.     # Switch to pose mode
  2240.     if bpy.ops.object.mode_set.poll():
  2241.         bpy.ops.object.mode_set(mode='POSE')
  2242.  
  2243.     armature = armature_object.data
  2244.     pose = armature_object.pose
  2245.  
  2246.     frame = create_object_frame()
  2247.     bl_bone_space = create_bone_space_matrix()
  2248.  
  2249.     for i in range(anim.num_frames):
  2250.         for item in anim.items:
  2251.             frame_object = frame.get_object(item.node_name)
  2252.  
  2253.             has_position_track = i < item.num_positions
  2254.             has_rotation_track = i < item.num_rotations
  2255.             has_scale_track = i < item.num_scales
  2256.  
  2257.             # Tell frame object what tracks are valid for this frame
  2258.             frame_object.set_tracks(has_position_track, has_rotation_track, has_scale_track)
  2259.  
  2260.             # Skip matrix update if there are no tracks available for this object this frame
  2261.             if not frame_object.has_any_track:
  2262.                 continue
  2263.  
  2264.             # Fetch current local matrix and decompose it in to location, rotation and scale components
  2265.             matrix_local = frame_object.matrix_local
  2266.             loc, rot, scl = matrix_local.decompose()
  2267.  
  2268.             # Apply animation tracks for valid
  2269.             if has_position_track:
  2270.                 p = item.positions[i]
  2271.                 loc = Vector([p.x, p.y, p.z])
  2272.  
  2273.             if has_rotation_track:
  2274.                 q = item.rotations[i]
  2275.                 rot = Quaternion((q.w, q.x, q.y, q.z))
  2276.  
  2277.             if has_scale_track:
  2278.                 s = item.scales[i]
  2279.                 scl = Vector([s.x, s.y, s.z])
  2280.  
  2281.             # Create matrices out of our frame components
  2282.             mat_loc = Matrix.Translation((loc.x, loc.y, loc.z))
  2283.             mat_scl = Matrix(([scl.x,0,0,0],[0,scl.y,0,0],[0,0,scl.z,0],[0,0,0,1]))
  2284.             mat_rot = rot.to_matrix().to_4x4()
  2285.  
  2286.             # Compose the final local matrix
  2287.             matrix_local = mat_loc * mat_rot * mat_scl
  2288.  
  2289.             # Set the matrix in the frame object
  2290.             frame_object.matrix_local = matrix_local
  2291.  
  2292.         # Calculate world matrices
  2293.         frame.evaluate()
  2294.        
  2295.         frame_index = i + 1
  2296.         for frame_object in frame.frame_objects:
  2297.             # Skip object if no tracks assigned this frame
  2298.             if not frame_object.has_any_track:
  2299.                 continue
  2300.  
  2301.             # If frame object exists as a pose bone, alter that instead of the actual object
  2302.             keyframe_object = None
  2303.             if frame_object.name in pose.bones.keys():
  2304.                 # Find the post bone and update it
  2305.                 pose_bone = pose.bones[frame_object.name]
  2306.  
  2307.                 pm = armature_object.matrix_world.inverted() * frame_object.matrix_world * bl_bone_space
  2308.                 pose_bone.matrix = pm
  2309.  
  2310.                 keyframe_object = pose_bone
  2311.             else:
  2312.                 # Update actual blender object
  2313.                 bl_object = bpy.data.objects[frame_object.name]
  2314.                 bl_object.matrix_world = frame_object.matrix_world
  2315.                 keyframe_object = bl_object
  2316.  
  2317.             # FIXME: hack, flush the animation system in Blender
  2318.             #bpy.context.scene.frame_set(frame_index)
  2319.             bpy.context.scene.update()
  2320.  
  2321.             # Add a keyframe(s) at this location
  2322.             if keyframe_object != None:
  2323.                 if frame_object.has_position_track:
  2324.                     keyframe_object.keyframe_insert(data_path="location", frame = frame_index)
  2325.                 if frame_object.has_rotation_track:
  2326.                     keyframe_object.keyframe_insert(data_path="rotation_quaternion", frame = frame_index)
  2327.                 if frame_object.has_scale_track:
  2328.                     keyframe_object.keyframe_insert(data_path="scale", frame = frame_index)
  2329.  
  2330.         # Go to next frame
  2331.         #progress_value = min(int((frame_index / anim.num_frames) * 99.0), 99) * 100
  2332.         #bpy.context.window_manager.progress_update(progress_value)
  2333.  
  2334.     #bpy.context.window_manager.progress_end()
  2335.  
  2336.     # Switch to object mode
  2337.     if bpy.ops.object.mode_set.poll():
  2338.         bpy.ops.object.mode_set(mode='OBJECT')
  2339.  
  2340.     return True
  2341.  
  2342.  
  2343. # create_model
  2344. #   Creates a Model() from current blender scene
  2345. def create_model(settings):
  2346.     #print("Export skinning: " + str(settings.option_skinning))
  2347.     #print("Export tangents: " + str(settings.option_tangents))
  2348.  
  2349.     # Switch to object mode before doing anything
  2350.     if bpy.ops.object.mode_set.poll():
  2351.         bpy.ops.object.mode_set(mode='OBJECT')
  2352.  
  2353.     # Create an empty model to work with
  2354.     model = Model()
  2355.     model.create_empty()
  2356.  
  2357.     # Collect all root objects, also attempt to find the game object
  2358.     objects = []
  2359.     mesh_objects = []
  2360.     for obj in bpy.data.objects:
  2361.         if obj.parent != None:
  2362.             continue
  2363.  
  2364.         # Only care about EMPTY and MESH objects
  2365.         if obj.type == 'EMPTY' or obj.type == 'MESH':
  2366.             objects.append(obj)
  2367.  
  2368.     # Append all children
  2369.     idx = 0
  2370.     while idx < len(objects):
  2371.         obj = objects[idx]
  2372.         for child in obj.children:
  2373.             # Only care about EMPTY and MESH objects
  2374.             if child.type != 'EMPTY' and child.type != 'MESH':
  2375.                 continue
  2376.  
  2377.             # Add mesh objects to a special list that we want to iterate at a later stage
  2378.             if child.type == 'MESH':
  2379.                 mesh_objects.append(child)
  2380.  
  2381.             objects.append(child)
  2382.         idx += 1
  2383.  
  2384.     # Generate nodes for all collected objects
  2385.     for obj in objects:
  2386.         # Ignore game_matrix object, it's simply a helper object to transform model into Blender friendly space
  2387.         if obj.name == "game_matrix":
  2388.             continue
  2389.  
  2390.         node = Node()
  2391.         node.create_empty(obj.name, obj.matrix_local)
  2392.  
  2393.         # Set the parent for the node
  2394.         if obj.parent != None and obj.parent.name != "game_matrix":
  2395.             node.parent = model.find_node_index(obj.parent.name)
  2396.  
  2397.         # Add node to model
  2398.         model.add_node(node)
  2399.  
  2400.     # Build the mesh entities for mesh objects
  2401.     for obj in mesh_objects:
  2402.         bl_rig = None
  2403.         bl_rig_state = 'REST'
  2404.        
  2405.         # Find the armature, cache the rig and the state it's currently in
  2406.         armature_ob = obj.find_armature()
  2407.         if armature_ob != None:
  2408.             bl_rig = armature_ob.data
  2409.             bl_rig_state = bl_rig.pose_position
  2410.  
  2411.         # Set rig in rest position if we're currently looking at it as posed
  2412.         if bl_rig != None and bl_rig_state != settings.option_rig_state:
  2413.             bl_rig.pose_position = settings.option_rig_state
  2414.             bpy.context.scene.update()
  2415.  
  2416.         bones_info = None
  2417.         bones_name = None
  2418.         if bl_rig != None and settings.option_skinning:
  2419.             # Collect bones and parent bone index
  2420.             bones_info = []
  2421.             for bl_bone in bl_rig.bones:
  2422.                 parent_bone_index = -1
  2423.                 if bl_bone.parent != None:
  2424.                     # Find parent bone
  2425.                     for index in range(len(bl_rig.bones)):
  2426.                         if bl_rig.bones[index].name == bl_bone.parent.name:
  2427.                             parent_bone_index = index
  2428.                             break
  2429.  
  2430.                 bones_info.append([bl_bone, parent_bone_index])
  2431.  
  2432.             # Sort by parent index
  2433.             bones_info = sorted(bones_info, key = lambda bone_info: bone_info[1])
  2434.            
  2435.             # Create named list of bones info
  2436.             bones_name = []
  2437.             for bone_info in bones_info:
  2438.                 bones_name.append(bone_info[0].name)
  2439.  
  2440.         # Get the mesh data
  2441.         bl_mesh = obj.to_mesh(bpy.context.scene, True, 'RENDER', calc_tessface = False, calc_undeformed = False)
  2442.  
  2443.         surface = TriSurface()
  2444.         surface.create_from_mesh(settings, bl_mesh, bones_name, obj.vertex_groups, obj.bound_box)
  2445.  
  2446.         mesh_entity = MeshEntity()
  2447.         mesh_entity.create_empty()
  2448.  
  2449.         mesh_data = mesh_entity.mesh_data
  2450.         mesh_data.num_vertices = len(surface.vertices)
  2451.  
  2452.         # Copy vertex coordinates
  2453.         positions = mesh_data.vertex_arrays[VTX_ARRAY_POSITION]
  2454.         positions.data_type = VTX_DATA_FLOAT
  2455.         positions.dim = 3
  2456.         positions.stride = 12
  2457.         positions.data = [None] * mesh_data.num_vertices
  2458.         for vi, vertex in enumerate(surface.vertices):
  2459.             positions.data[vi] = (vertex[0], vertex[1], vertex[2])
  2460.  
  2461.         # Copy normals
  2462.         normals = mesh_data.vertex_arrays[VTX_ARRAY_NORMAL]
  2463.         normals.data_type = VTX_DATA_FLOAT
  2464.         normals.dim = 3
  2465.         normals.stride = 12
  2466.         normals.data = [None] * mesh_data.num_vertices
  2467.         for vi, normal in enumerate(surface.normals):
  2468.             normals.data[vi] = (normal[0], normal[1], normal[2])
  2469.  
  2470.         # Copy uv coordinates
  2471.         if surface.has_uv:
  2472.             texcoords = mesh_data.vertex_arrays[VTX_ARRAY_TEXCOORD0]
  2473.             texcoords.data_type = VTX_DATA_FLOAT
  2474.             texcoords.dim = 2
  2475.             texcoords.stride = 8
  2476.             texcoords.data = [None] * mesh_data.num_vertices
  2477.             for vi, uv in enumerate(surface.uvs):
  2478.                 texcoords.data[vi] = (uv[0], 1.0 - uv[1])
  2479.  
  2480.         # Copy tangents and bitangents
  2481.         if surface.has_tangents:
  2482.             tangents = mesh_data.vertex_arrays[VTX_ARRAY_TANGENT]
  2483.             tangents.data_type = VTX_DATA_FLOAT
  2484.             tangents.dim = 3
  2485.             tangents.stride = 12
  2486.             tangents.data = [None] * mesh_data.num_vertices
  2487.             for vi, tangent in enumerate(surface.tangents):
  2488.                 tangents.data[vi] = (tangent[0], tangent[1], tangent[2])
  2489.  
  2490.             bitangents = mesh_data.vertex_arrays[VTX_ARRAY_BITANGENT]
  2491.             bitangents.data_type = VTX_DATA_FLOAT
  2492.             bitangents.dim = 3
  2493.             bitangents.stride = 12
  2494.             bitangents.data = [None] * mesh_data.num_vertices
  2495.             for vi, bitangent in enumerate(surface.bitangents):
  2496.                 bitangents.data[vi] = (bitangent[0], bitangent[1], bitangent[2])
  2497.  
  2498.         # Copy colors
  2499.         if surface.has_color:
  2500.             colors = mesh_data.vertex_arrays[VTX_ARRAY_COLOR]
  2501.             colors.data_type = VTX_DATA_BYTE
  2502.             colors.dim = 4
  2503.             colors.stride = 4
  2504.             colors.data = [None] * mesh_data.num_vertices
  2505.             for vi, color in enumerate(surface.colors):
  2506.                 colors.data[vi] = convert_color_float3_to_byte4(color)
  2507.  
  2508.         # Copy vertex bone- weights and indices
  2509.         if surface.has_weights:
  2510.             bone_indices = mesh_data.vertex_arrays[VTX_ARRAY_BONE_INDEX]
  2511.             bone_indices.data_type = VTX_DATA_INT
  2512.             bone_indices.dim = 4
  2513.             bone_indices.stride = 16
  2514.             bone_indices.data = [None] * mesh_data.num_vertices
  2515.             for vi, indices in enumerate(surface.bone_indices):
  2516.                 bone_indices.data[vi] = (indices[0], indices[1], indices[2], indices[3])
  2517.  
  2518.             bone_weights = mesh_data.vertex_arrays[VTX_ARRAY_BONE_WEIGHT]
  2519.             bone_weights.data_type = VTX_DATA_FLOAT
  2520.             bone_weights.dim = 4
  2521.             bone_weights.stride = 16
  2522.             bone_weights.data = [None] * mesh_data.num_vertices
  2523.             for vi, weights in enumerate(surface.bone_weights):
  2524.                 bone_weights.data[vi] = (weights[0], weights[1], weights[2], weights[3])
  2525.  
  2526.         if surface.has_weights:
  2527.             # Set number of bones
  2528.             mesh_entity.num_bones = len(bones_info)
  2529.  
  2530.             # Add bones to mesh entity
  2531.             bl_bone_space = create_bone_space_matrix()
  2532.             for bl_bone, parent_index in bones_info:
  2533.                 node_index = model.find_node_index(bl_bone.name)
  2534.  
  2535.                 m2b = bl_bone_space * bl_bone.matrix_local.inverted()
  2536.  
  2537.                 bone = Bone()
  2538.                 bone.create_empty(node_index)
  2539.                 bone.model_to_bone.from_matrix(m2b)
  2540.                 mesh_entity.bones.append(bone)
  2541.  
  2542.         # Copy indices and create segments
  2543.         for material_index, tri_material in enumerate(surface.materials):
  2544.             num_indices = len(tri_material.indices)
  2545.             if num_indices <= 0:
  2546.                 continue
  2547.  
  2548.             segment = MeshSegment()
  2549.             segment.create_empty()
  2550.  
  2551.             segment.material = bl_mesh.materials[material_index].name
  2552.             segment.index_offset = len(mesh_data.indices)
  2553.             segment.num_triangles = int(num_indices / 3)
  2554.             mesh_data.segments.append(segment)
  2555.  
  2556.             for index in tri_material.indices:
  2557.                 mesh_data.indices.append(index)
  2558.  
  2559.         mesh_data.num_indices = len(mesh_data.indices)
  2560.         mesh_data.num_segments = len(mesh_data.segments)
  2561.  
  2562.         mesh_data.bound_min = [surface.bound_min[0], surface.bound_min[1], surface.bound_min[2]]
  2563.         mesh_data.bound_max = [surface.bound_max[0], surface.bound_max[1], surface.bound_max[2]]
  2564.  
  2565.         # Calculate bounds
  2566.         len_sqr = 0.0
  2567.         for i in range(3):
  2568.             mesh_data.bound_min[i] = surface.bound_min[i]
  2569.             mesh_data.bound_max[i] = surface.bound_max[i]
  2570.             mesh_data.bound_center[i] = (surface.bound_min[i] + surface.bound_max[i]) * 0.5
  2571.             len_sqr += (surface.bound_max[i] - mesh_data.bound_center[i]) ** 2
  2572.  
  2573.         if len_sqr > 0.000001:
  2574.             mesh_data.bound_radius = math.sqrt(len_sqr)
  2575.  
  2576.         # Set created mesh entity on node
  2577.         node_index = model.find_node_index(obj.name)
  2578.         node = model.nodes[node_index]
  2579.         node.set_mesh_entity(mesh_entity)
  2580.  
  2581.         # Put rig back in pose position
  2582.         if bl_rig != None and bl_rig_state != settings.option_rig_state:
  2583.             bl_rig.pose_position = bl_rig_state
  2584.             bpy.context.scene.update()
  2585.  
  2586.     return model
  2587.  
  2588. # fnv1a
  2589. #   Hash a string using fnv1-a
  2590. def fnv1a(string, seed = 0x811C9DC5, prime = 0x01000193):
  2591.     uint32_max = 2 ** 32
  2592.  
  2593.     hash_value = seed
  2594.     for c in string:
  2595.         hash_value = ( ( ord( c ) ^ hash_value ) * prime ) % uint32_max
  2596.     return hash_value
  2597.  
  2598.  
  2599. # load_file_table
  2600. #   Loads the .dat container file table header
  2601. def load_file_table(file_object):
  2602.     file_table = None
  2603.  
  2604.     dat_magic = 0
  2605.     try:
  2606.         dat_magic = read_magic(file_object)
  2607.     except:
  2608.         print("Error parsing .dat file header!")
  2609.         file_object.close()
  2610.         return file_table
  2611.        
  2612.     # Figure out if it's a valid dat package
  2613.     if dat_magic != b'GRA2':
  2614.         print("Not a valid Legend of Grimrock 2 .dat file!")
  2615.         file_object.close()
  2616.         return file_table
  2617.  
  2618.     # Read number of file headers
  2619.     num_files = read_int(file_object)
  2620.  
  2621.     # Read in the file header table for all entries
  2622.     file_table = num_files * [None]
  2623.     for i in range(num_files):
  2624.         entry = FileEntry()
  2625.         entry.hash_name = read_uint(file_object)
  2626.         entry.file_offset = read_uint(file_object)
  2627.         entry.size_compressed = read_uint(file_object)
  2628.         entry.size_uncompressed = read_uint(file_object)
  2629.         entry.unknown = read_uint(file_object)
  2630.         file_table[ i ] = entry
  2631.  
  2632.     return file_table
  2633.  
  2634.  
  2635. # load_animation_table
  2636. #   Loads a .dat container animation information
  2637. def load_animation_table(filename):
  2638.     anim_table = []
  2639.  
  2640.     file_object = open(filename, 'rb')
  2641.     file_table = load_file_table(file_object)
  2642.     if file_table == None or len(file_table) <= 0:
  2643.         return anim_table
  2644.  
  2645.     # Only care about lua script files and animation files
  2646.     # We use the lua to scan for animation asset names so we can name the animation table properly
  2647.     magic_lua = 0x014a4c1b
  2648.     magic_anim = 0x4d494e41    #b'ANIM'
  2649.    
  2650.     # Create search strings to use when scaning the lua files
  2651.     anim_search = b'assets/animations/'
  2652.     fbx_search = b'.fbx'
  2653.  
  2654.     # Seek to all file entries, read the magic and place table indices for different file types
  2655.     anim_names = {}
  2656.     for entry in file_table:
  2657.         # Haven't come across any, think I recall them being zlib compressed ?
  2658.         # Skip them for now
  2659.         if entry.size_compressed != 0:
  2660.             print("Compressed file in .dat package, skipping entry")
  2661.             continue
  2662.  
  2663.         # Haven't come across any, I have no idea what this might be
  2664.         # Skip these entries for now
  2665.         if entry.unknown != 0:
  2666.             print("Found unknown data in .dat package, skipping entry")
  2667.             continue
  2668.  
  2669.         # Seek to start of internal file and read the first four bytes and treat them as
  2670.         # a 'type' magic of what that file entry actually is.
  2671.         # This is of course not correct, but for the sake of finding animations and lua files it works just fine.
  2672.         file_object.seek(entry.file_offset, os.SEEK_SET)
  2673.         file_magic = read_uint(file_object)
  2674.        
  2675.         # Handle lua file entries
  2676.         if file_magic == magic_lua:
  2677.             # Read out the entire contents of the file into a bytearray
  2678.             lua_buffer = bytearray(file_object.read(entry.size_uncompressed))
  2679.  
  2680.             # Search the bytearray 'for assets/animations/' until no more could be found
  2681.             buffer_index = lua_buffer.find(anim_search, 0)
  2682.             while buffer_index >= 0:
  2683.                 # Lookup .fbx ending
  2684.                 # Now, we could potentially found .fbx several hundred bytes later.
  2685.                 # It's a bit dangerous to assume it always ends with .fbx, but it works for now
  2686.                 end_index = lua_buffer.find(fbx_search, buffer_index + 14)
  2687.                
  2688.                 # If we didn't find an .fbx ending, abort now
  2689.                 if end_index < 0:
  2690.                     break
  2691.  
  2692.                 # Decode a string from our search indices and append a .animation ending
  2693.                 asset_name = decode_string(lua_buffer[buffer_index:end_index]) + ".animation"
  2694.                
  2695.                 # Use FNV1a to hash the asset name
  2696.                 hash_name = fnv1a(asset_name)
  2697.                
  2698.                 # If hash name isn't already in list, append a new entry pointing to the real asset name
  2699.                 # so we can 'decode' file names later
  2700.                 if hash_name not in anim_names:
  2701.                     anim_names[hash_name] = asset_name
  2702.  
  2703.                 # Restart the search from the end of the last search result
  2704.                 buffer_index = lua_buffer.find(anim_search, end_index + 4)
  2705.        
  2706.         # Handle animation file entries, this simply adds the table entry to the animation table
  2707.         if file_magic == magic_anim:
  2708.             anim_table.append(entry)
  2709.  
  2710.     # We're done with the file for now, close it
  2711.     file_object.close()
  2712.  
  2713.     # Go through our animation table and attempt to resolve all the asset names
  2714.     # If we couldn't find one, simply build a name using the hash name
  2715.     num_unresolved = 0
  2716.     for entry in anim_table:
  2717.         if entry.hash_name in anim_names:
  2718.             entry.name = anim_names[entry.hash_name]
  2719.         else:
  2720.             entry.name = "hash_%8x" % entry.hash_name
  2721.             num_unresolved += 1
  2722.  
  2723.     return anim_table
  2724.  
  2725.  
  2726. # load_model_table
  2727. #   Loads .dat container model information
  2728. def load_model_table(filename):
  2729.     model_table = []
  2730.     file_object = open(filename, 'rb')
  2731.     file_table = load_file_table(file_object)
  2732.     if file_table == None or len(file_table) <= 0:
  2733.         return model_table
  2734.  
  2735.     # Only care about lua script files and model files
  2736.     # We use the lua to scan for model asset names so we can name the model table properly
  2737.     magic_lua = 0x014a4c1b
  2738.     magic_model = 0x314c444d    #b'MDL1'
  2739.    
  2740.     # Create search strings to use when scaning the lua files
  2741.     model_search = b'assets/models/'
  2742.     fbx_search = b'.fbx'
  2743.  
  2744.     # Seek to all file entries, read the magic and place table indices for different file types
  2745.     model_names = {}
  2746.     for entry in file_table:
  2747.         # Haven't come across any, think I recall them being zlib compressed ?
  2748.         # Skip them for now
  2749.         if entry.size_compressed != 0:
  2750.            
  2751.             print("Compressed file in .dat package, skipping entry")
  2752.             continue
  2753.  
  2754.         # Haven't come across any, I have no idea what this might be
  2755.         # Skip these entries for now
  2756.         if entry.unknown != 0:
  2757.             print("Found unknown data in .dat package, skipping entry")
  2758.             continue
  2759.  
  2760.         # Seek to start of internal file and read the first four bytes and treat them as
  2761.         # a 'type' magic of what that file entry actually is.
  2762.         # This is of course not correct, but for the sake of finding models and lua files it works just fine.
  2763.         file_object.seek(entry.file_offset, os.SEEK_SET)
  2764.         file_magic = read_uint(file_object)
  2765.        
  2766.         # Handle lua file entries
  2767.         if file_magic == magic_lua:
  2768.             # Read out the entire contents of the file into a bytearray
  2769.             lua_buffer = bytearray(file_object.read(entry.size_uncompressed))
  2770.  
  2771.             # Search the bytearray 'for assets/models/' until no more could be found
  2772.             buffer_index = lua_buffer.find(model_search, 0)
  2773.             while buffer_index >= 0:
  2774.                 # Lookup .fbx ending
  2775.                 # Now, we could potentially found .fbx several hundred bytes later.
  2776.                 # It's a bit dangerous to assume it always ends with .fbx, but it works for now
  2777.                 end_index = lua_buffer.find(fbx_search, buffer_index + 14)
  2778.                
  2779.                 # If we didn't find an .fbx ending, abort now
  2780.                 if end_index < 0:
  2781.                     break
  2782.  
  2783.                 # Decode a string from our search indices and append a .model ending
  2784.                 asset_name = decode_string(lua_buffer[buffer_index:end_index]) + ".model"
  2785.                
  2786.                 # Use FNV1a to hash the asset name
  2787.                 hash_name = fnv1a(asset_name)
  2788.                
  2789.                 # If hash name isn't already in list, append a new entry pointing to the real asset name
  2790.                 # so we can 'decode' file names later
  2791.                 if hash_name not in model_names:
  2792.                     model_names[hash_name] = asset_name
  2793.  
  2794.                 # Restart the search from the end of the last search result
  2795.                 buffer_index = lua_buffer.find(model_search, end_index + 4)
  2796.        
  2797.         # Handle model file entries, this simply adds the table entry to the model table
  2798.         if file_magic == magic_model:
  2799.             model_table.append(entry)
  2800.  
  2801.     # We're done with the file for now, close it
  2802.     file_object.close()
  2803.  
  2804.     # Go through our model table and attempt to resolve all the asset names
  2805.     # If we couldn't find one, simply build a name using the hash name
  2806.     num_unresolved = 0
  2807.     for entry in model_table:
  2808.         if entry.hash_name in model_names:
  2809.             entry.name = model_names[entry.hash_name]
  2810.         else:
  2811.             entry.name = "hash_%8x" % entry.hash_name
  2812.             num_unresolved += 1
  2813.  
  2814.     return model_table
  2815.  
  2816.  
  2817. # load_model
  2818. #   Loads a model from a binary file, uses a file offset if we're reading directly from a .dat container
  2819. def load_model(filename, file_offset, context):
  2820.     name, ext = os.path.splitext(os.path.basename(filename))
  2821.  
  2822.     # Seek to offset in file, this is only used if loading from .dat containers
  2823.     file_object = open(filename, 'rb')
  2824.     if file_offset > 0:
  2825.         file_object.seek(file_offset, os.SEEK_SET)
  2826.  
  2827.     # Load the binary model data
  2828.     load_binary_model(file_object, context)
  2829.    
  2830.     # Close file if load_binary_model hasn't already done so
  2831.     if not file_object.closed:
  2832.         file_object.close()
  2833.  
  2834.     return True
  2835.  
  2836.  
  2837. # save_model
  2838. #   Saves a scene to binary model file
  2839. def save_model(settings, context):
  2840.     # Clear the game-matrix transform
  2841.     game_matrix = ObjectTransform("game_matrix")
  2842.     game_matrix.clear_transform()
  2843.  
  2844.     try:
  2845.         file_object = open(settings.filename, 'wb')
  2846.         save_binary_model(file_object, settings, context)
  2847.     except:
  2848.         # Restore any game-matrix changes
  2849.         game_matrix.restore_transform()
  2850.  
  2851.         # Make sure we close the file on errors!
  2852.         if not file_object.closed:
  2853.             file_object.close()
  2854.  
  2855.         # Done with error handling, re-raise exception
  2856.         raise
  2857.  
  2858.     # Restore any game-matrix changes
  2859.     game_matrix.restore_transform()
  2860.  
  2861.     # Close the file if save_binary_model hasn't already done so
  2862.     if not file_object.closed:
  2863.         file_object.close()
  2864.  
  2865.     return True
  2866.  
  2867.  
  2868. # load_animation
  2869. #   Loads an animation from a binary file, uses a file offset if we're reading directly from a .dat container
  2870. def load_animation(filename, file_offset, armature, context):
  2871.     name, ext = os.path.splitext(os.path.basename(filename))
  2872.  
  2873.     # Seek to offset in file, this is only used if loading from .dat containers
  2874.     file_object = open(filename, 'rb')
  2875.     if file_offset > 0:
  2876.         file_object.seek(file_offset, os.SEEK_SET)
  2877.  
  2878.     # Load the binary model data
  2879.     load_binary_animation(file_object, armature, context)
  2880.    
  2881.     # Close file if load_binary_animation hasn't already done so
  2882.     if not file_object.closed:
  2883.         file_object.close()
  2884.  
  2885.     return True
  2886.  
  2887.  
  2888. # IMPORT_OT_model
  2889. #   Blender UI and base for importing models
  2890. class IMPORT_OT_model(bpy.types.Operator, ImportHelper):
  2891.     # Import Model Operator.
  2892.     bl_idname = "import_scene.model"
  2893.     bl_label = "Import Model"
  2894.     bl_description = "Import a Legend of Grimrock 2 model"
  2895.     bl_options = { 'REGISTER', 'UNDO' }
  2896.    
  2897.     # File selection UI property
  2898.     filepath = StringProperty(name="File Path", description="Filepath used for importing the model file.", maxlen=1024, default="")
  2899.    
  2900.     # File list UI properties for .dat containers
  2901.     file_list = CollectionProperty(type=bpy.types.PropertyGroup)
  2902.     file_list_index = IntProperty()
  2903.  
  2904.     # Holds information if .dat container is being imported with specific model selection
  2905.     dat_file = ""
  2906.     model_table = []
  2907.  
  2908.     def execute(self, context):
  2909.         file_offset = 0
  2910.  
  2911.         # Dig out file offset if loading from .dat container
  2912.         if self.dat_file == self.filepath:
  2913.             file_offset = self.model_table[self.file_list_index].file_offset
  2914.  
  2915.         # Load from binary file
  2916.         load_model(self.filepath, file_offset, context)
  2917.  
  2918.         return {'FINISHED'}
  2919.  
  2920.  
  2921.     # clear_file_list
  2922.     #   Clears the file_list UI property of all entries and resets the dat_file cached value
  2923.     def clear_file_list(self):
  2924.         self.dat_file = ""
  2925.  
  2926.         num = len(self.file_list)
  2927.         while num > 0:
  2928.             self.file_list.remove(num-1)
  2929.             num -= 1
  2930.  
  2931.  
  2932.     # build_file_list
  2933.     #   Updates the file_list UI property from selected .dat file, or cleans it out if needed
  2934.     def build_file_list(self):
  2935.         # Figure out if we selected a .dat file or if we slected a different .dat file
  2936.         name, ext = os.path.splitext(os.path.basename(self.filepath))
  2937.         if ext.lower() != ".dat":
  2938.             self.clear_file_list()
  2939.             return
  2940.  
  2941.         # Cached dat_file is still up to date, simply ignore any updates
  2942.         if self.filepath == self.dat_file:
  2943.             return
  2944.  
  2945.         # Clean out any previous entries in the UI file list
  2946.         self.clear_file_list()
  2947.  
  2948.         # Load package header and extract model information
  2949.         self.dat_file = self.filepath
  2950.         self.model_table = load_model_table(self.filepath)
  2951.  
  2952.         # Add all the model table entries to the UI file list
  2953.         for entry in self.model_table:
  2954.             item = self.file_list.add()
  2955.             item.name = entry.name
  2956.  
  2957.  
  2958.     # draw
  2959.     def draw(self, context):
  2960.         layout = self.layout
  2961.  
  2962.         # Update the file_list UI property if needed
  2963.         self.build_file_list()
  2964.  
  2965.         row = layout.row(True)
  2966.         row.label("Legend of Grimrock 2 .dat container")
  2967.         layout.template_list("UI_UL_list", "OpenFileDAT", self, "file_list", self, "file_list_index", rows=15)
  2968.  
  2969.  
  2970.     def invoke(self, context, event):
  2971.         wm = context.window_manager
  2972.         wm.fileselect_add(self)
  2973.         return {'RUNNING_MODAL'}
  2974.  
  2975.  
  2976. # IMPORT_OT_anim
  2977. #   Blender UI and base for importing animations
  2978. class IMPORT_OT_anim(bpy.types.Operator, ImportHelper):
  2979.     # Import Animation Operator.
  2980.     bl_idname = "import_scene.animation"
  2981.     bl_label = "Import Animation"
  2982.     bl_description = "Import a Legend of Grimrock 2 animation"
  2983.     bl_options = { 'REGISTER', 'UNDO' }
  2984.    
  2985.     # File selection UI property
  2986.     filepath = StringProperty(name="File Path", description="Filepath used for importing the animation file.", maxlen=1024, default="")
  2987.    
  2988.     # Armature selection UI property
  2989.     armature_name = StringProperty(name="Armature", description="Armature to apply animation data on.", maxlen=1024, default="")
  2990.  
  2991.     # File list UI properties for .dat containers
  2992.     file_list = CollectionProperty(type=bpy.types.PropertyGroup)
  2993.     file_list_index = IntProperty()
  2994.  
  2995.     # Holds information if .dat container is being imported with specific model selection
  2996.     dat_file = ""
  2997.     animation_table = []
  2998.  
  2999.     # execute
  3000.     def execute(self, context):
  3001.         file_offset = 0
  3002.  
  3003.         # Dig out file offset if loading from .dat container
  3004.         if self.dat_file == self.filepath:
  3005.             file_offset = self.animation_table[self.file_list_index].file_offset
  3006.  
  3007.         if len(self.armature_name) <= 0 or not context.scene.objects[self.armature_name]:
  3008.             print("Can't load animation, couldn't find selected armature.")
  3009.             return {'FINISHED'}
  3010.  
  3011.         armature = context.scene.objects[self.armature_name]
  3012.         if armature.type != 'ARMATURE':
  3013.             print("Can't load animation, object to apply on is not of type ARMATURE.")
  3014.             return {'FINISHED'}
  3015.        
  3016.         # Load from binary file
  3017.         load_animation(self.filepath, file_offset, armature, context)
  3018.  
  3019.         return {'FINISHED'}
  3020.  
  3021.  
  3022.     # clear_file_list
  3023.     #   Clears the file_list UI property of all entries and resets the dat_file cached value
  3024.     def clear_file_list(self):
  3025.         self.dat_file = ""
  3026.  
  3027.         num = len(self.file_list)
  3028.         while num > 0:
  3029.             self.file_list.remove(num-1)
  3030.             num -= 1
  3031.  
  3032.  
  3033.     # build_file_list
  3034.     #   Updates the file_list UI property from selected .dat file, or cleans it out if needed
  3035.     def build_file_list(self):
  3036.         # Figure out if we selected a .dat file or if we slected a different .dat file
  3037.         name, ext = os.path.splitext(os.path.basename(self.filepath))
  3038.         if ext.lower() != ".dat":
  3039.             self.clear_file_list()
  3040.             return
  3041.  
  3042.         # Cached dat_file is still up to date, simply ignore any updates
  3043.         if self.filepath == self.dat_file:
  3044.             return
  3045.  
  3046.         # Clean out any previous entries in the UI file list
  3047.         self.clear_file_list()
  3048.  
  3049.         # Load package header and extract animation information
  3050.         self.dat_file = self.filepath
  3051.         self.animation_table = load_animation_table(self.filepath)
  3052.  
  3053.         # Add all the animation table entries to the UI file list
  3054.         for entry in self.animation_table:
  3055.             item = self.file_list.add()
  3056.             item.name = entry.name
  3057.  
  3058.  
  3059.     # draw
  3060.     def draw(self, context):
  3061.         layout = self.layout
  3062.  
  3063.         # Update the file_list UI property if needed
  3064.         self.build_file_list()
  3065.  
  3066.         row = layout.row(True)
  3067.         row.prop_search(self, "armature_name", bpy.data, "armatures")
  3068.  
  3069.         row = layout.row(True)
  3070.         row.label("Legend of Grimrock 2 .dat container")
  3071.         layout.template_list("UI_UL_list", "OpenFileDAT", self, "file_list", self, "file_list_index", rows=15)
  3072.  
  3073.  
  3074.     # invoke
  3075.     def invoke(self, context, event):
  3076.         wm = context.window_manager
  3077.         wm.fileselect_add(self)
  3078.         return {'RUNNING_MODAL'}
  3079.  
  3080.  
  3081. # ExportSettings
  3082. #   Settings for save_model
  3083. class ExportSettings():
  3084.     __slots__ = (
  3085.         "filename",
  3086.         "option_skinning",
  3087.         "option_tangents",
  3088.         "option_rig_state",
  3089.         )
  3090.  
  3091.     def __init__(self):
  3092.         self.filename = ""
  3093.         self.option_skinning = True
  3094.         self.option_tangents = True
  3095.         self.option_rig_state = 'REST'
  3096.  
  3097.  
  3098. # EXPORT_OT_model
  3099. #   Blender UI and base for exporting models
  3100. class EXPORT_OT_model(bpy.types.Operator, ExportHelper):
  3101.     # Export Model Operator.
  3102.     bl_idname = "export_scene.model"
  3103.     bl_label = "Export Model"
  3104.     bl_description = "Export to a Legend of Grimrock 2 model"
  3105.     bl_options = { 'REGISTER', 'UNDO' }
  3106.    
  3107.     filename_ext = ".model"
  3108.     filter_glob = StringProperty( default="*.model", options={'HIDDEN'} )
  3109.  
  3110.     option_skinning = BoolProperty(name="Skinning", description="If true, bones and skinning weights is exported.", default=True)
  3111.     option_tangents = BoolProperty(name="Tangents", description="If true, tangents and bi-tangents are calculated and exported.", default=True)
  3112.     options_posing = EnumProperty(items=[
  3113.         ('POSE', 'Pose', "Exports the mesh in a posed state rather than the default rest state.", 'POSE_DATA', 1),
  3114.         ('REST', 'Rest', "Exports the mesh in unposed rest state.", 'ARMATURE_DATA', 2) #OUTLINER_OB_ARMATURE
  3115.         ], name = "", description="Mesh deformation setting", default='REST')
  3116.     # File selection UI property
  3117.     filepath = StringProperty(name="File Path", description="Filepath used for exporting the model file.", maxlen=1024, default="")
  3118.    
  3119.     # execute
  3120.     def execute(self, context):
  3121.         # Create settings to send to binary model export
  3122.         settings = ExportSettings()
  3123.         settings.filename = self.filepath
  3124.         settings.option_rig_state = self.options_posing
  3125.         settings.option_skinning = self.option_skinning
  3126.         settings.option_tangents = self.option_tangents
  3127.  
  3128.         # Save to binary file
  3129.         save_model(settings, context)
  3130.         return {'FINISHED'}
  3131.  
  3132.     # draw
  3133.     def draw(self, context):
  3134.         layout = self.layout
  3135.  
  3136.         row = layout.row(True)
  3137.         row.label(text="", icon = 'MOD_ARMATURE')
  3138.         row.prop(self, "option_skinning")
  3139.  
  3140.         row = layout.row(True)
  3141.         row.label(text="", icon = 'MANIPUL')
  3142.         row.prop(self, "option_tangents")
  3143.  
  3144.         row = layout.row(True)
  3145.         row.label(text="Armature State")
  3146.         row = layout.row(True)
  3147.         row.prop(self, "options_posing")
  3148.  
  3149.     # invoke
  3150.     def invoke(self, context, event):
  3151.         wm = context.window_manager
  3152.         wm.fileselect_add(self)
  3153.         return {'RUNNING_MODAL'}
  3154.  
  3155.  
  3156. # menu_func_import_model
  3157. #   Blender menu operator to invoke model importer
  3158. def menu_func_import_model(self, context):
  3159.     self.layout.operator(IMPORT_OT_model.bl_idname, text="Legend of Grimrock 2 Model (.model)")
  3160.  
  3161.  
  3162. # menu_func_export_model
  3163. #   Blender menu operator to invoke model exporter
  3164. def menu_func_export_model(self, context):
  3165.     self.layout.operator(EXPORT_OT_model.bl_idname, text="Legend of Grimrock 2 Model (.model)")
  3166.  
  3167.  
  3168. # menu_func_import_anim
  3169. #   Blender menu operator to invoke animation importer
  3170. def menu_func_import_anim(self, context):
  3171.     self.layout.operator(IMPORT_OT_anim.bl_idname, text="Legend of Grimrock 2 Animation (.animation)")
  3172.  
  3173.  
  3174. # register
  3175. #   Registers menu functions
  3176. def register():
  3177.     bpy.utils.register_module(__name__)
  3178.     bpy.types.INFO_MT_file_import.append(menu_func_import_model)
  3179.     bpy.types.INFO_MT_file_import.append(menu_func_import_anim)
  3180.     bpy.types.INFO_MT_file_export.append(menu_func_export_model)
  3181.  
  3182.  
  3183. # unregister
  3184. #   Unregisters menu functions
  3185. def unregister():
  3186.     bpy.utils.unregister_module(__name__)
  3187.     bpy.types.INFO_MT_file_import.remove(menu_func_import_model)
  3188.     bpy.types.INFO_MT_file_import.remove(menu_func_import_anim)
  3189.     bpy.types.INFO_MT_file_export.remove(menu_func_export_model)
  3190.  
  3191.  
  3192. # Main function
  3193. if __name__ == "__main__":
  3194.     register()
RAW Paste Data