helper.py 12 KB

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