helper.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. #-*- coding: UTF-8 -*-
  2. """
  3. Some fancy helper functions.
  4. """
  5. import os
  6. import ctypes
  7. import operator
  8. from distutils.sysconfig import get_python_lib
  9. import re
  10. import sys
  11. try: import numpy
  12. except ImportError: numpy = None
  13. import logging;logger = logging.getLogger("pyassimp")
  14. from .errors import AssimpError
  15. additional_dirs, ext_whitelist = [],[]
  16. # populate search directories and lists of allowed file extensions
  17. # depending on the platform we're running on.
  18. if os.name=='posix':
  19. additional_dirs.append('./')
  20. additional_dirs.append('/usr/lib/')
  21. additional_dirs.append('/usr/lib/x86_64-linux-gnu/')
  22. additional_dirs.append('/usr/lib/aarch64-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. export to blob function,
  152. release function,
  153. ctypes handle to assimp library)
  154. '''
  155. try:
  156. load = dll.aiImportFile
  157. release = dll.aiReleaseImport
  158. load_mem = dll.aiImportFileFromMemory
  159. export = dll.aiExportScene
  160. export2blob = dll.aiExportSceneToBlob
  161. except AttributeError:
  162. #OK, this is a library, but it doesn't have the functions we need
  163. return None
  164. # library found!
  165. from .structs import Scene, ExportDataBlob
  166. load.restype = ctypes.POINTER(Scene)
  167. load_mem.restype = ctypes.POINTER(Scene)
  168. export2blob.restype = ctypes.POINTER(ExportDataBlob)
  169. return (library_path, load, load_mem, export, export2blob, release, dll)
  170. def search_library():
  171. '''
  172. Loads the assimp library.
  173. Throws exception AssimpError if no library_path is found
  174. Returns: tuple, (load from filename function,
  175. load from memory function,
  176. export to filename function,
  177. export to blob function,
  178. release function,
  179. dll)
  180. '''
  181. #this path
  182. folder = os.path.dirname(__file__)
  183. # silence 'DLL not found' message boxes on win
  184. try:
  185. ctypes.windll.kernel32.SetErrorMode(0x8007)
  186. except AttributeError:
  187. pass
  188. candidates = []
  189. # test every file
  190. for curfolder in [folder]+additional_dirs:
  191. if os.path.isdir(curfolder):
  192. for filename in os.listdir(curfolder):
  193. # our minimum requirement for candidates is that
  194. # they should contain 'assimp' somewhere in
  195. # their name
  196. if filename.lower().find('assimp')==-1 :
  197. continue
  198. is_out=1
  199. for et in ext_whitelist:
  200. if et in filename.lower():
  201. is_out=0
  202. break
  203. if is_out:
  204. continue
  205. library_path = os.path.join(curfolder, filename)
  206. logger.debug('Try ' + library_path)
  207. try:
  208. dll = ctypes.cdll.LoadLibrary(library_path)
  209. except Exception as e:
  210. logger.warning(str(e))
  211. # OK, this except is evil. But different OSs will throw different
  212. # errors. So just ignore any errors.
  213. continue
  214. # see if the functions we need are in the dll
  215. loaded = try_load_functions(library_path, dll)
  216. if loaded: candidates.append(loaded)
  217. if not candidates:
  218. # no library found
  219. raise AssimpError("assimp library not found")
  220. else:
  221. # get the newest library_path
  222. candidates = map(lambda x: (os.lstat(x[0])[-2], x), candidates)
  223. res = max(candidates, key=operator.itemgetter(0))[1]
  224. logger.debug('Using assimp library located at ' + res[0])
  225. # XXX: if there are 1000 dll/so files containing 'assimp'
  226. # in their name, do we have all of them in our address
  227. # space now until gc kicks in?
  228. # XXX: take version postfix of the .so on linux?
  229. return res[1:]
  230. def hasattr_silent(object, name):
  231. """
  232. Calls hasttr() with the given parameters and preserves the legacy (pre-Python 3.2)
  233. functionality of silently catching exceptions.
  234. Returns the result of hasatter() or False if an exception was raised.
  235. """
  236. try:
  237. if not object:
  238. return False
  239. return hasattr(object, name)
  240. except AttributeError:
  241. return False