helper.py 11 KB

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