helper.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. #-*- coding: UTF-8 -*-
  2. """
  3. Some fancy helper functions.
  4. """
  5. import os
  6. import ctypes
  7. from ctypes import POINTER
  8. import operator
  9. try: import numpy
  10. except: numpy = None
  11. import logging;logger = logging.getLogger("pyassimp")
  12. from .errors import AssimpError
  13. additional_dirs, ext_whitelist = [],[]
  14. # populate search directories and lists of allowed file extensions
  15. # depending on the platform we're running on.
  16. if os.name=='posix':
  17. additional_dirs.append('./')
  18. additional_dirs.append('/usr/lib/')
  19. additional_dirs.append('/usr/lib/x86_64-linux-gnu')
  20. additional_dirs.append('/usr/local/lib/')
  21. # note - this won't catch libassimp.so.N.n, but
  22. # currently there's always a symlink called
  23. # libassimp.so in /usr/local/lib.
  24. ext_whitelist.append('.so')
  25. # libassimp.dylib in /usr/local/lib
  26. ext_whitelist.append('.dylib')
  27. elif os.name=='nt':
  28. ext_whitelist.append('.dll')
  29. path_dirs = os.environ['PATH'].split(';')
  30. for dir_candidate in path_dirs:
  31. if 'assimp' in dir_candidate.lower():
  32. additional_dirs.append(dir_candidate)
  33. #print(additional_dirs)
  34. def vec2tuple(x):
  35. """ Converts a VECTOR3D to a Tuple """
  36. return (x.x, x.y, x.z)
  37. def transform(vector3, matrix4x4):
  38. """ Apply a transformation matrix on a 3D vector.
  39. :param vector3: array with 3 elements
  40. :param matrix4x4: 4x4 matrix
  41. """
  42. if numpy:
  43. return numpy.dot(matrix4x4, numpy.append(vector3, 1.))
  44. else:
  45. m0,m1,m2,m3 = matrix4x4; x,y,z = vector3
  46. return [
  47. m0[0]*x + m0[1]*y + m0[2]*z + m0[3],
  48. m1[0]*x + m1[1]*y + m1[2]*z + m1[3],
  49. m2[0]*x + m2[1]*y + m2[2]*z + m2[3],
  50. m3[0]*x + m3[1]*y + m3[2]*z + m3[3]
  51. ]
  52. def _inv(matrix4x4):
  53. m0,m1,m2,m3 = matrix4x4
  54. det = m0[3]*m1[2]*m2[1]*m3[0] - m0[2]*m1[3]*m2[1]*m3[0] - \
  55. m0[3]*m1[1]*m2[2]*m3[0] + m0[1]*m1[3]*m2[2]*m3[0] + \
  56. m0[2]*m1[1]*m2[3]*m3[0] - m0[1]*m1[2]*m2[3]*m3[0] - \
  57. m0[3]*m1[2]*m2[0]*m3[1] + m0[2]*m1[3]*m2[0]*m3[1] + \
  58. m0[3]*m1[0]*m2[2]*m3[1] - m0[0]*m1[3]*m2[2]*m3[1] - \
  59. m0[2]*m1[0]*m2[3]*m3[1] + m0[0]*m1[2]*m2[3]*m3[1] + \
  60. m0[3]*m1[1]*m2[0]*m3[2] - m0[1]*m1[3]*m2[0]*m3[2] - \
  61. m0[3]*m1[0]*m2[1]*m3[2] + m0[0]*m1[3]*m2[1]*m3[2] + \
  62. m0[1]*m1[0]*m2[3]*m3[2] - m0[0]*m1[1]*m2[3]*m3[2] - \
  63. m0[2]*m1[1]*m2[0]*m3[3] + m0[1]*m1[2]*m2[0]*m3[3] + \
  64. m0[2]*m1[0]*m2[1]*m3[3] - m0[0]*m1[2]*m2[1]*m3[3] - \
  65. m0[1]*m1[0]*m2[2]*m3[3] + m0[0]*m1[1]*m2[2]*m3[3]
  66. return[[( m1[2]*m2[3]*m3[1] - m1[3]*m2[2]*m3[1] + m1[3]*m2[1]*m3[2] - m1[1]*m2[3]*m3[2] - m1[2]*m2[1]*m3[3] + m1[1]*m2[2]*m3[3]) /det,
  67. ( m0[3]*m2[2]*m3[1] - m0[2]*m2[3]*m3[1] - m0[3]*m2[1]*m3[2] + m0[1]*m2[3]*m3[2] + m0[2]*m2[1]*m3[3] - m0[1]*m2[2]*m3[3]) /det,
  68. ( m0[2]*m1[3]*m3[1] - m0[3]*m1[2]*m3[1] + m0[3]*m1[1]*m3[2] - m0[1]*m1[3]*m3[2] - m0[2]*m1[1]*m3[3] + m0[1]*m1[2]*m3[3]) /det,
  69. ( m0[3]*m1[2]*m2[1] - m0[2]*m1[3]*m2[1] - m0[3]*m1[1]*m2[2] + m0[1]*m1[3]*m2[2] + m0[2]*m1[1]*m2[3] - m0[1]*m1[2]*m2[3]) /det],
  70. [( m1[3]*m2[2]*m3[0] - m1[2]*m2[3]*m3[0] - m1[3]*m2[0]*m3[2] + m1[0]*m2[3]*m3[2] + m1[2]*m2[0]*m3[3] - m1[0]*m2[2]*m3[3]) /det,
  71. ( m0[2]*m2[3]*m3[0] - m0[3]*m2[2]*m3[0] + m0[3]*m2[0]*m3[2] - m0[0]*m2[3]*m3[2] - m0[2]*m2[0]*m3[3] + m0[0]*m2[2]*m3[3]) /det,
  72. ( m0[3]*m1[2]*m3[0] - m0[2]*m1[3]*m3[0] - m0[3]*m1[0]*m3[2] + m0[0]*m1[3]*m3[2] + m0[2]*m1[0]*m3[3] - m0[0]*m1[2]*m3[3]) /det,
  73. ( m0[2]*m1[3]*m2[0] - m0[3]*m1[2]*m2[0] + m0[3]*m1[0]*m2[2] - m0[0]*m1[3]*m2[2] - m0[2]*m1[0]*m2[3] + m0[0]*m1[2]*m2[3]) /det],
  74. [( m1[1]*m2[3]*m3[0] - m1[3]*m2[1]*m3[0] + m1[3]*m2[0]*m3[1] - m1[0]*m2[3]*m3[1] - m1[1]*m2[0]*m3[3] + m1[0]*m2[1]*m3[3]) /det,
  75. ( m0[3]*m2[1]*m3[0] - m0[1]*m2[3]*m3[0] - m0[3]*m2[0]*m3[1] + m0[0]*m2[3]*m3[1] + m0[1]*m2[0]*m3[3] - m0[0]*m2[1]*m3[3]) /det,
  76. ( m0[1]*m1[3]*m3[0] - m0[3]*m1[1]*m3[0] + m0[3]*m1[0]*m3[1] - m0[0]*m1[3]*m3[1] - m0[1]*m1[0]*m3[3] + m0[0]*m1[1]*m3[3]) /det,
  77. ( m0[3]*m1[1]*m2[0] - m0[1]*m1[3]*m2[0] - m0[3]*m1[0]*m2[1] + m0[0]*m1[3]*m2[1] + m0[1]*m1[0]*m2[3] - m0[0]*m1[1]*m2[3]) /det],
  78. [( m1[2]*m2[1]*m3[0] - m1[1]*m2[2]*m3[0] - m1[2]*m2[0]*m3[1] + m1[0]*m2[2]*m3[1] + m1[1]*m2[0]*m3[2] - m1[0]*m2[1]*m3[2]) /det,
  79. ( m0[1]*m2[2]*m3[0] - m0[2]*m2[1]*m3[0] + m0[2]*m2[0]*m3[1] - m0[0]*m2[2]*m3[1] - m0[1]*m2[0]*m3[2] + m0[0]*m2[1]*m3[2]) /det,
  80. ( m0[2]*m1[1]*m3[0] - m0[1]*m1[2]*m3[0] - m0[2]*m1[0]*m3[1] + m0[0]*m1[2]*m3[1] + m0[1]*m1[0]*m3[2] - m0[0]*m1[1]*m3[2]) /det,
  81. ( m0[1]*m1[2]*m2[0] - m0[2]*m1[1]*m2[0] + m0[2]*m1[0]*m2[1] - m0[0]*m1[2]*m2[1] - m0[1]*m1[0]*m2[2] + m0[0]*m1[1]*m2[2]) /det]]
  82. def get_bounding_box(scene):
  83. bb_min = [1e10, 1e10, 1e10] # x,y,z
  84. bb_max = [-1e10, -1e10, -1e10] # x,y,z
  85. inv = numpy.linalg.inv if numpy else _inv
  86. return get_bounding_box_for_node(scene.rootnode, bb_min, bb_max, inv(scene.rootnode.transformation))
  87. def get_bounding_box_for_node(node, bb_min, bb_max, transformation):
  88. if numpy:
  89. transformation = numpy.dot(transformation, node.transformation)
  90. else:
  91. t0,t1,t2,t3 = transformation
  92. T0,T1,T2,T3 = node.transformation
  93. transformation = [ [
  94. t0[0]*T0[0] + t0[1]*T1[0] + t0[2]*T2[0] + t0[3]*T3[0],
  95. t0[0]*T0[1] + t0[1]*T1[1] + t0[2]*T2[1] + t0[3]*T3[1],
  96. t0[0]*T0[2] + t0[1]*T1[2] + t0[2]*T2[2] + t0[3]*T3[2],
  97. t0[0]*T0[3] + t0[1]*T1[3] + t0[2]*T2[3] + t0[3]*T3[3]
  98. ],[
  99. t1[0]*T0[0] + t1[1]*T1[0] + t1[2]*T2[0] + t1[3]*T3[0],
  100. t1[0]*T0[1] + t1[1]*T1[1] + t1[2]*T2[1] + t1[3]*T3[1],
  101. t1[0]*T0[2] + t1[1]*T1[2] + t1[2]*T2[2] + t1[3]*T3[2],
  102. t1[0]*T0[3] + t1[1]*T1[3] + t1[2]*T2[3] + t1[3]*T3[3]
  103. ],[
  104. t2[0]*T0[0] + t2[1]*T1[0] + t2[2]*T2[0] + t2[3]*T3[0],
  105. t2[0]*T0[1] + t2[1]*T1[1] + t2[2]*T2[1] + t2[3]*T3[1],
  106. t2[0]*T0[2] + t2[1]*T1[2] + t2[2]*T2[2] + t2[3]*T3[2],
  107. t2[0]*T0[3] + t2[1]*T1[3] + t2[2]*T2[3] + t2[3]*T3[3]
  108. ],[
  109. t3[0]*T0[0] + t3[1]*T1[0] + t3[2]*T2[0] + t3[3]*T3[0],
  110. t3[0]*T0[1] + t3[1]*T1[1] + t3[2]*T2[1] + t3[3]*T3[1],
  111. t3[0]*T0[2] + t3[1]*T1[2] + t3[2]*T2[2] + t3[3]*T3[2],
  112. t3[0]*T0[3] + t3[1]*T1[3] + t3[2]*T2[3] + t3[3]*T3[3]
  113. ] ]
  114. for mesh in node.meshes:
  115. for v in mesh.vertices:
  116. v = transform(v, transformation)
  117. bb_min[0] = min(bb_min[0], v[0])
  118. bb_min[1] = min(bb_min[1], v[1])
  119. bb_min[2] = min(bb_min[2], v[2])
  120. bb_max[0] = max(bb_max[0], v[0])
  121. bb_max[1] = max(bb_max[1], v[1])
  122. bb_max[2] = max(bb_max[2], v[2])
  123. for child in node.children:
  124. bb_min, bb_max = get_bounding_box_for_node(child, bb_min, bb_max, transformation)
  125. return bb_min, bb_max
  126. def try_load_functions(library_path, dll):
  127. '''
  128. Try to bind to aiImportFile and aiReleaseImport
  129. Arguments
  130. ---------
  131. library_path: path to current lib
  132. dll: ctypes handle to library
  133. Returns
  134. ---------
  135. If unsuccessful:
  136. None
  137. If successful:
  138. Tuple containing (library_path,
  139. load from filename function,
  140. load from memory function,
  141. export to filename function,
  142. release function,
  143. ctypes handle to assimp library)
  144. '''
  145. try:
  146. load = dll.aiImportFile
  147. release = dll.aiReleaseImport
  148. load_mem = dll.aiImportFileFromMemory
  149. export = dll.aiExportScene
  150. except AttributeError:
  151. #OK, this is a library, but it doesn't have the functions we need
  152. return None
  153. # library found!
  154. from .structs import Scene
  155. load.restype = POINTER(Scene)
  156. load_mem.restype = POINTER(Scene)
  157. return (library_path, load, load_mem, export, release, dll)
  158. def search_library():
  159. '''
  160. Loads the assimp library.
  161. Throws exception AssimpError if no library_path is found
  162. Returns: tuple, (load from filename function,
  163. load from memory function,
  164. export to filename function,
  165. release function,
  166. dll)
  167. '''
  168. #this path
  169. folder = os.path.dirname(__file__)
  170. # silence 'DLL not found' message boxes on win
  171. try:
  172. ctypes.windll.kernel32.SetErrorMode(0x8007)
  173. except AttributeError:
  174. pass
  175. candidates = []
  176. # test every file
  177. for curfolder in [folder]+additional_dirs:
  178. if os.path.isdir(curfolder):
  179. for filename in os.listdir(curfolder):
  180. # our minimum requirement for candidates is that
  181. # they should contain 'assimp' somewhere in
  182. # their name
  183. if filename.lower().find('assimp')==-1 or\
  184. os.path.splitext(filename)[-1].lower() not in ext_whitelist:
  185. continue
  186. library_path = os.path.join(curfolder, filename)
  187. logger.debug('Try ' + library_path)
  188. try:
  189. dll = ctypes.cdll.LoadLibrary(library_path)
  190. except Exception as e:
  191. logger.warning(str(e))
  192. # OK, this except is evil. But different OSs will throw different
  193. # errors. So just ignore any errors.
  194. continue
  195. # see if the functions we need are in the dll
  196. loaded = try_load_functions(library_path, dll)
  197. if loaded: candidates.append(loaded)
  198. if not candidates:
  199. # no library found
  200. raise AssimpError("assimp library not found")
  201. else:
  202. # get the newest library_path
  203. candidates = map(lambda x: (os.lstat(x[0])[-2], x), candidates)
  204. res = max(candidates, key=operator.itemgetter(0))[1]
  205. logger.debug('Using assimp library located at ' + res[0])
  206. # XXX: if there are 1000 dll/so files containing 'assimp'
  207. # in their name, do we have all of them in our address
  208. # space now until gc kicks in?
  209. # XXX: take version postfix of the .so on linux?
  210. return res[1:]
  211. def hasattr_silent(object, name):
  212. """
  213. Calls hasttr() with the given parameters and preserves the legacy (pre-Python 3.2)
  214. functionality of silently catching exceptions.
  215. Returns the result of hasatter() or False if an exception was raised.
  216. """
  217. try:
  218. return hasattr(object, name)
  219. except:
  220. return False