選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 
 
 
 
 

414 行
13 KiB

  1. #-*- coding: UTF-8 -*-
  2. """
  3. PyAssimp
  4. This is the main-module of PyAssimp.
  5. """
  6. import sys
  7. if sys.version_info < (2,6):
  8. raise 'pyassimp: need python 2.6 or newer'
  9. import ctypes
  10. import os
  11. import numpy
  12. import logging; logger = logging.getLogger("pyassimp")
  13. # Attach a default, null handler, to the logger.
  14. # applications can easily get log messages from pyassimp
  15. # by calling for instance
  16. # >>> logging.basicConfig(level=logging.DEBUG)
  17. # before importing pyassimp
  18. class NullHandler(logging.Handler):
  19. def emit(self, record):
  20. pass
  21. h = NullHandler()
  22. logger.addHandler(h)
  23. from . import structs
  24. from .errors import AssimpError
  25. from . import helper
  26. assimp_structs_as_tuple = (
  27. structs.Matrix4x4,
  28. structs.Matrix3x3,
  29. structs.Vector2D,
  30. structs.Vector3D,
  31. structs.Color3D,
  32. structs.Color4D,
  33. structs.Quaternion,
  34. structs.Plane,
  35. structs.Texel)
  36. def make_tuple(ai_obj, type = None):
  37. res = None
  38. if isinstance(ai_obj, structs.Matrix4x4):
  39. res = numpy.array([getattr(ai_obj, e[0]) for e in ai_obj._fields_]).reshape((4,4))
  40. #import pdb;pdb.set_trace()
  41. elif isinstance(ai_obj, structs.Matrix3x3):
  42. res = numpy.array([getattr(ai_obj, e[0]) for e in ai_obj._fields_]).reshape((3,3))
  43. else:
  44. res = numpy.array([getattr(ai_obj, e[0]) for e in ai_obj._fields_])
  45. return res
  46. # It is faster and more correct to have an init function for each assimp class
  47. def _init_face(aiFace):
  48. aiFace.indices = [aiFace.mIndices[i] for i in range(aiFace.mNumIndices)]
  49. assimp_struct_inits = \
  50. { structs.Face : _init_face }
  51. def call_init(obj, caller = None):
  52. if helper.hasattr_silent(obj,'contents'): #pointer
  53. _init(obj.contents, obj, caller)
  54. else:
  55. _init(obj,parent=caller)
  56. def _is_init_type(obj):
  57. if helper.hasattr_silent(obj,'contents'): #pointer
  58. return _is_init_type(obj[0])
  59. # null-pointer case that arises when we reach a mesh attribute
  60. # like mBitangents which use mNumVertices rather than mNumBitangents
  61. # so it breaks the 'is iterable' check.
  62. # Basically:
  63. # FIXME!
  64. elif not bool(obj):
  65. return False
  66. tname = obj.__class__.__name__
  67. return not (tname[:2] == 'c_' or tname == 'Structure' \
  68. or tname == 'POINTER') and not isinstance(obj,int)
  69. def _init(self, target = None, parent = None):
  70. """
  71. Custom initialize() for C structs, adds safely accessable member functionality.
  72. :param target: set the object which receive the added methods. Useful when manipulating
  73. pointers, to skip the intermediate 'contents' deferencing.
  74. """
  75. if not target:
  76. target = self
  77. dirself = dir(self)
  78. for m in dirself:
  79. if m.startswith("_"):
  80. continue
  81. if m.startswith('mNum'):
  82. if 'm' + m[4:] in dirself:
  83. continue # will be processed later on
  84. else:
  85. name = m[1:].lower()
  86. obj = getattr(self, m)
  87. setattr(target, name, obj)
  88. continue
  89. if m == 'mName':
  90. obj = self.mName
  91. target.name = str(obj.data.decode("utf-8"))
  92. target.__class__.__repr__ = lambda x: str(x.__class__) + "(" + x.name + ")"
  93. target.__class__.__str__ = lambda x: x.name
  94. continue
  95. name = m[1:].lower()
  96. obj = getattr(self, m)
  97. # Create tuples
  98. if isinstance(obj, assimp_structs_as_tuple):
  99. setattr(target, name, make_tuple(obj))
  100. logger.debug(str(self) + ": Added array " + str(getattr(target, name)) + " as self." + name.lower())
  101. continue
  102. if m.startswith('m'):
  103. if name == "parent":
  104. setattr(target, name, parent)
  105. logger.debug("Added a parent as self." + name)
  106. continue
  107. if helper.hasattr_silent(self, 'mNum' + m[1:]):
  108. length = getattr(self, 'mNum' + m[1:])
  109. # -> special case: properties are
  110. # stored as a dict.
  111. if m == 'mProperties':
  112. setattr(target, name, _get_properties(obj, length))
  113. continue
  114. if not length: # empty!
  115. setattr(target, name, [])
  116. logger.debug(str(self) + ": " + name + " is an empty list.")
  117. continue
  118. try:
  119. if obj._type_ in assimp_structs_as_tuple:
  120. setattr(target, name, numpy.array([make_tuple(obj[i]) for i in range(length)], dtype=numpy.float32))
  121. logger.debug(str(self) + ": Added an array of numpy arrays (type "+ str(type(obj)) + ") as self." + name)
  122. else:
  123. setattr(target, name, [obj[i] for i in range(length)]) #TODO: maybe not necessary to recreate an array?
  124. logger.debug(str(self) + ": Added list of " + str(obj) + " " + name + " as self." + name + " (type: " + str(type(obj)) + ")")
  125. # initialize array elements
  126. try:
  127. init = assimp_struct_inits[type(obj[0])]
  128. except KeyError:
  129. if _is_init_type(obj[0]):
  130. for e in getattr(target, name):
  131. call_init(e, target)
  132. else:
  133. for e in getattr(target, name):
  134. init(e)
  135. except IndexError:
  136. logger.error("in " + str(self) +" : mismatch between mNum" + name + " and the actual amount of data in m" + name + ". This may be due to version mismatch between libassimp and pyassimp. Quitting now.")
  137. sys.exit(1)
  138. except ValueError as e:
  139. logger.error("In " + str(self) + "->" + name + ": " + str(e) + ". Quitting now.")
  140. if "setting an array element with a sequence" in str(e):
  141. logger.error("Note that pyassimp does not currently "
  142. "support meshes with mixed triangles "
  143. "and quads. Try to load your mesh with"
  144. " a post-processing to triangulate your"
  145. " faces.")
  146. sys.exit(1)
  147. else: # starts with 'm' but not iterable
  148. setattr(target, name, obj)
  149. logger.debug("Added " + name + " as self." + name + " (type: " + str(type(obj)) + ")")
  150. if _is_init_type(obj):
  151. call_init(obj, target)
  152. if isinstance(self, structs.Mesh):
  153. _finalize_mesh(self, target)
  154. if isinstance(self, structs.Texture):
  155. _finalize_texture(self, target)
  156. return self
  157. class AssimpLib(object):
  158. """
  159. Assimp-Singleton
  160. """
  161. load, release, dll = helper.search_library()
  162. #the loader as singleton
  163. _assimp_lib = AssimpLib()
  164. def pythonize_assimp(type, obj, scene):
  165. """ This method modify the Assimp data structures
  166. to make them easier to work with in Python.
  167. Supported operations:
  168. - MESH: replace a list of mesh IDs by reference to these meshes
  169. - ADDTRANSFORMATION: add a reference to an object's transformation taken from their associated node.
  170. :param type: the type of modification to operate (cf above)
  171. :param obj: the input object to modify
  172. :param scene: a reference to the whole scene
  173. """
  174. if type == "MESH":
  175. meshes = []
  176. for i in obj:
  177. meshes.append(scene.meshes[i])
  178. return meshes
  179. if type == "ADDTRANSFORMATION":
  180. def getnode(node, name):
  181. if node.name == name: return node
  182. for child in node.children:
  183. n = getnode(child, name)
  184. if n: return n
  185. node = getnode(scene.rootnode, obj.name)
  186. if not node:
  187. raise AssimpError("Object " + str(obj) + " has no associated node!")
  188. setattr(obj, "transformation", node.transformation)
  189. def recur_pythonize(node, scene):
  190. """ Recursively call pythonize_assimp on
  191. nodes tree to apply several post-processing to
  192. pythonize the assimp datastructures.
  193. """
  194. node.meshes = pythonize_assimp("MESH", node.meshes, scene)
  195. for mesh in node.meshes:
  196. mesh.material = scene.materials[mesh.materialindex]
  197. for cam in scene.cameras:
  198. pythonize_assimp("ADDTRANSFORMATION", cam, scene)
  199. #for light in scene.lights:
  200. # pythonize_assimp("ADDTRANSFORMATION", light, scene)
  201. for c in node.children:
  202. recur_pythonize(c, scene)
  203. def load(filename, processing=0):
  204. """
  205. Loads the model with some specific processing parameters.
  206. filename - file to load model from
  207. processing - processing parameters
  208. result Scene-object with model-data
  209. throws AssimpError - could not open file
  210. """
  211. #read pure data
  212. #from ctypes import c_char_p, c_uint
  213. #model = _assimp_lib.load(c_char_p(filename), c_uint(processing))
  214. model = _assimp_lib.load(filename.encode("ascii"), processing)
  215. if not model:
  216. #Uhhh, something went wrong!
  217. raise AssimpError("could not import file: %s" % filename)
  218. scene = _init(model.contents)
  219. recur_pythonize(scene.rootnode, scene)
  220. return scene
  221. def release(scene):
  222. from ctypes import pointer
  223. _assimp_lib.release(pointer(scene))
  224. def _finalize_texture(tex, target):
  225. setattr(target, "achformathint", tex.achFormatHint)
  226. data = numpy.array([make_tuple(getattr(tex, "pcData")[i]) for i in range(tex.mWidth * tex.mHeight)])
  227. setattr(target, "data", data)
  228. def _finalize_mesh(mesh, target):
  229. """ Building of meshes is a bit specific.
  230. We override here the various datasets that can
  231. not be process as regular fields.
  232. For instance, the length of the normals array is
  233. mNumVertices (no mNumNormals is available)
  234. """
  235. nb_vertices = getattr(mesh, "mNumVertices")
  236. def fill(name):
  237. mAttr = getattr(mesh, name)
  238. if mAttr:
  239. data = numpy.array([make_tuple(getattr(mesh, name)[i]) for i in range(nb_vertices)], dtype=numpy.float32)
  240. setattr(target, name[1:].lower(), data)
  241. else:
  242. setattr(target, name[1:].lower(), numpy.array([], dtype="float32"))
  243. def fillarray(name):
  244. mAttr = getattr(mesh, name)
  245. data = []
  246. for index, mSubAttr in enumerate(mAttr):
  247. if mSubAttr:
  248. data.append([make_tuple(getattr(mesh, name)[index][i]) for i in range(nb_vertices)])
  249. setattr(target, name[1:].lower(), numpy.array(data, dtype=numpy.float32))
  250. fill("mNormals")
  251. fill("mTangents")
  252. fill("mBitangents")
  253. fillarray("mColors")
  254. fillarray("mTextureCoords")
  255. # prepare faces
  256. faces = numpy.array([f.indices for f in target.faces], dtype=numpy.int32)
  257. setattr(target, 'faces', faces)
  258. class PropertyGetter(dict):
  259. def __getitem__(self, key):
  260. semantic = 0
  261. if isinstance(key, tuple):
  262. key, semantic = key
  263. return dict.__getitem__(self, (key, semantic))
  264. def keys(self):
  265. for k in dict.keys(self):
  266. yield k[0]
  267. def __iter__(self):
  268. return self.keys()
  269. def items(self):
  270. for k, v in dict.items(self):
  271. yield k[0], v
  272. def _get_properties(properties, length):
  273. """
  274. Convenience Function to get the material properties as a dict
  275. and values in a python format.
  276. """
  277. result = {}
  278. #read all properties
  279. for p in [properties[i] for i in range(length)]:
  280. #the name
  281. p = p.contents
  282. key = (str(p.mKey.data.decode("utf-8")).split('.')[1], p.mSemantic)
  283. #the data
  284. from ctypes import POINTER, cast, c_int, c_float, sizeof
  285. if p.mType == 1:
  286. arr = cast(p.mData, POINTER(c_float * int(p.mDataLength/sizeof(c_float)) )).contents
  287. value = [x for x in arr]
  288. elif p.mType == 3: #string can't be an array
  289. value = cast(p.mData, POINTER(structs.MaterialPropertyString)).contents.data.decode("utf-8")
  290. elif p.mType == 4:
  291. arr = cast(p.mData, POINTER(c_int * int(p.mDataLength/sizeof(c_int)) )).contents
  292. value = [x for x in arr]
  293. else:
  294. value = p.mData[:p.mDataLength]
  295. if len(value) == 1:
  296. [value] = value
  297. result[key] = value
  298. return PropertyGetter(result)
  299. def decompose_matrix(matrix):
  300. if not isinstance(matrix, structs.Matrix4x4):
  301. raise AssimpError("pyassimp.decompose_matrix failed: Not a Matrix4x4!")
  302. scaling = structs.Vector3D()
  303. rotation = structs.Quaternion()
  304. position = structs.Vector3D()
  305. from ctypes import byref, pointer
  306. _assimp_lib.dll.aiDecomposeMatrix(pointer(matrix), byref(scaling), byref(rotation), byref(position))
  307. return scaling._init(), rotation._init(), position._init()