pyassimp.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. #-*- coding: UTF-8 -*-
  2. """
  3. PyAssimp
  4. This is the main-module of PyAssimp.
  5. """
  6. import structs
  7. import ctypes
  8. import os
  9. import helper
  10. from errors import AssimpError
  11. class AssimpLib(object):
  12. """
  13. Assimp-Singleton
  14. """
  15. load, release = helper.search_library()
  16. class AssimpBase(object):
  17. """
  18. Base class for all Assimp-classes.
  19. """
  20. @staticmethod
  21. def _load_array(data, count, cons):
  22. """
  23. Loads a whole array out of data, and constructs a new object. If data
  24. is NULL, an empty list will be returned.
  25. data - pointer to array
  26. count - size of the array
  27. cons - constructor
  28. result array data
  29. """
  30. if data:
  31. return [cons(data[i]) for i in range(count)]
  32. else:
  33. return []
  34. @staticmethod
  35. def make_loader(function):
  36. """
  37. Creates a loader function for "_load_array".
  38. function - function to be applied to the content of an element
  39. """
  40. def loader(x):
  41. return function(x.contents)
  42. return loader
  43. class Material(object):
  44. """
  45. A Material.
  46. """
  47. def __init__(self, material):
  48. """
  49. Converts the raw material data to a material.
  50. """
  51. self.properties = self._load_properties(material.mProperties,
  52. material.mNumProperties)
  53. def _load_properties(self, data, size):
  54. """
  55. Loads all properties of this mateiral.
  56. data - properties
  57. size - elements in properties
  58. """
  59. result = {}
  60. #read all properties
  61. for i in range(size):
  62. p = data[i].contents
  63. #the name
  64. key = p.mKey.data
  65. #the data
  66. from ctypes import POINTER, cast, c_int, c_float, sizeof
  67. if p.mType == 1:
  68. arr = cast(p.mData, POINTER(c_float*(p.mDataLength/sizeof(c_float)) )).contents
  69. value = [x for x in arr]
  70. elif p.mType == 3: #string can't be an array
  71. value = cast(p.mData, POINTER(structs.STRING)).contents.data
  72. elif p.mType == 4:
  73. arr = cast(p.mData, POINTER(c_int*(p.mDataLength/sizeof(c_int)) )).contents
  74. value = [x for x in arr]
  75. else:
  76. value = p.mData[:p.mDataLength]
  77. result[key] = str(value)
  78. return result
  79. def __repr__(self):
  80. return repr(self.properties)
  81. def __str__(self):
  82. return str(self.properties)
  83. class Matrix(AssimpBase):
  84. """
  85. Assimp 4x4-matrix
  86. """
  87. def __init__(self, matrix):
  88. """
  89. Copies matrix data to this structure.
  90. matrix - raw matrix data
  91. """
  92. m = matrix
  93. self.data = [
  94. [m.a1, m.a2, m.a3, m.a4],
  95. [m.b1, m.b2, m.b3, m.b4],
  96. [m.c1, m.c2, m.c3, m.c4],
  97. [m.d1, m.d2, m.d3, m.d4],
  98. ]
  99. def __getitem__(self, index):
  100. """
  101. Returns an item out of the matrix data. Use (row, column) to access
  102. data directly or an natural number n to access the n-th row.
  103. index - matrix index
  104. result element or row
  105. """
  106. try:
  107. #tuple as index?
  108. x, y = index
  109. return data[x][y]
  110. except TypeError:
  111. #index as index
  112. return data[index]
  113. def __setitem__(self, index, value):
  114. """
  115. Sets an item of the matrix data. Use (row, column) to access
  116. data directly or an natural number n to access the n-th row.
  117. index - matrix index
  118. value - new value
  119. """
  120. try:
  121. #tuple as index?
  122. x, y = index
  123. data[x][y] = value
  124. except TypeError:
  125. #index as index
  126. data[index] = value
  127. class VertexWeight(AssimpBase):
  128. """
  129. Weight for vertices.
  130. """
  131. def __init__(self, weight):
  132. """
  133. Copies vertex weights to this structure.
  134. weight - new weight
  135. """
  136. #corresponding vertex id
  137. self.vertex = weight.mVertexId
  138. #my weight
  139. self.weight = weight.mWeight
  140. class Bone(AssimpBase):
  141. """
  142. Single bone of a mesh. A bone has a name by which it can be found
  143. in the frame hierarchy and by which it can be addressed by animations.
  144. """
  145. def __init__(self, bone):
  146. """
  147. Converts an ASSIMP-bone to a PyAssimp-bone.
  148. """
  149. #the name is easy
  150. self.name = str(bone.mName)
  151. #matrix that transforms from mesh space to bone space in bind pose
  152. self.matrix = Matrix(bone.mOffsetMatrix)
  153. #and of course the weights!
  154. Bone._load_array(bone.mWeights,
  155. bone.mNumWeights,
  156. VertexWeight)
  157. class Texture(AssimpBase):
  158. """
  159. Texture included in the model.
  160. """
  161. def __init__(self, texture):
  162. """
  163. Convertes the raw data to a texture.
  164. texture - raw data
  165. """
  166. #dimensions
  167. self.width = texture.mWidth
  168. self.height = texture.mHeight
  169. #format hint
  170. self.hint = texture.achFormatHint
  171. #load data
  172. self.data = self._load_data(texture)
  173. def _load_data(self, texture):
  174. """
  175. Loads the texture data.
  176. texture - the texture
  177. result texture data in (red, green, blue, alpha)
  178. """
  179. if self.height == 0:
  180. #compressed data
  181. size = self.width
  182. else:
  183. size = self.width * self.height
  184. #load!
  185. return Texture._load_array(texture.pcData,
  186. size,
  187. lambda x: (x.r, x.g, x.b, x.a))
  188. class Scene(AssimpBase):
  189. """
  190. The root structure of the imported data.
  191. Everything that was imported from the given file can be accessed from here.
  192. """
  193. #possible flags
  194. FLAGS = {}
  195. for key in structs.SCENE.__dict__:
  196. if key.startswith("AI_SCENE_FLAGS_"):
  197. FLAGS[structs.SCENE.__dict__[key]] = key
  198. def __init__(self, model):
  199. """
  200. Converts the model-data to a real scene
  201. model - the raw model-data
  202. """
  203. #process data
  204. self._load(model)
  205. def _load(self, model):
  206. """
  207. Converts model from raw-data to fancy data!
  208. model - pointer to data
  209. """
  210. #store scene flags
  211. self.flags = model.mFlags
  212. #load mesh-data
  213. self.meshes = Scene._load_array(model.mMeshes,
  214. model.mNumMeshes,
  215. Scene.make_loader(Mesh))
  216. #load materials
  217. self.materials = Scene._load_array(model.mMaterials,
  218. model.mNumMaterials,
  219. Scene.make_loader(Material))
  220. #load textures
  221. self.textures = Scene._load_array(model.mTextures,
  222. model.mNumTextures,
  223. Scene.make_loader(Texture))
  224. def list_flags(self):
  225. """
  226. Returns a list of all used flags.
  227. result list of flags
  228. """
  229. return [name for (key, value) in Scene.FLAGS.iteritems()
  230. if (key & self.flags)>0]
  231. class Face(AssimpBase):
  232. """
  233. A single face in a mesh, referring to multiple vertices.
  234. If the number of indices is 3, the face is a triangle,
  235. for more than 3 it is a polygon.
  236. Point and line primitives are rarely used and are NOT supported. However,
  237. a load could pass them as degenerated triangles.
  238. """
  239. def __init__(self, face):
  240. """
  241. Loads a face from raw-data.
  242. """
  243. self.indices = [face.mIndices[i] for i in range(face.mNumIndices)]
  244. def __repr__(self):
  245. return str(self.indices)
  246. class Mesh(AssimpBase):
  247. """
  248. A mesh represents a geometry or model with a single material.
  249. It usually consists of a number of vertices and a series of primitives/faces
  250. referencing the vertices. In addition there might be a series of bones, each
  251. of them addressing a number of vertices with a certain weight. Vertex data
  252. is presented in channels with each channel containing a single per-vertex
  253. information such as a set of texture coords or a normal vector.
  254. If a data pointer is non-null, the corresponding data stream is present.
  255. A Mesh uses only a single material which is referenced by a material ID.
  256. """
  257. def __init__(self, mesh):
  258. """
  259. Loads mesh from raw-data.
  260. """
  261. #process data
  262. self._load(mesh)
  263. def _load(self, mesh):
  264. """
  265. Loads mesh-data from raw data
  266. mesh - raw mesh-data
  267. """
  268. #load vertices
  269. self.vertices = Mesh._load_array(mesh.mVertices,
  270. mesh.mNumVertices,
  271. helper.vec2tuple)
  272. #load normals
  273. self.normals = Mesh._load_array(mesh.mNormals,
  274. mesh.mNumVertices,
  275. helper.vec2tuple)
  276. #load tangents
  277. self.tangents = Mesh._load_array(mesh.mTangents,
  278. mesh.mNumVertices,
  279. helper.vec2tuple)
  280. #load bitangents
  281. self.bitangents = Mesh._load_array(mesh.mBitangents,
  282. mesh.mNumVertices,
  283. helper.vec2tuple)
  284. #vertex color sets
  285. self.colors = self._load_colors(mesh)
  286. #number of coordinates per uv-channel
  287. self.uvsize = self._load_uv_component_count(mesh)
  288. #number of uv channels
  289. self.texcoords = self._load_texture_coords(mesh)
  290. #the used material
  291. self.material_index = int(mesh.mMaterialIndex)
  292. #faces
  293. self.faces = self._load_faces(mesh)
  294. #bones
  295. self.bones = self._load_bones(mesh)
  296. def _load_bones(self, mesh):
  297. """
  298. Loads bones of this mesh.
  299. mesh - mesh-data
  300. result bones
  301. """
  302. count = mesh.mNumBones
  303. if count==0:
  304. #no bones
  305. return []
  306. #read bones
  307. bones = mesh.mBones.contents
  308. return Mesh._load_array(bones,
  309. count,
  310. Bone)
  311. def _load_faces(self, mesh):
  312. """
  313. Loads all faces.
  314. mesh - mesh-data
  315. result faces
  316. """
  317. return [Face(mesh.mFaces[i]) for i in range(mesh.mNumFaces)]
  318. def _load_uv_component_count(self, mesh):
  319. """
  320. Loads the number of components for a given UV channel.
  321. mesh - mesh-data
  322. result (count channel 1, count channel 2, ...)
  323. """
  324. return tuple(mesh.mNumUVComponents[i]
  325. for i in range(structs.MESH.AI_MAX_NUMBER_OF_TEXTURECOORDS))
  326. def _load_texture_coords(self, mesh):
  327. """
  328. Loads texture coordinates.
  329. mesh - mesh-data
  330. result texture coordinates
  331. """
  332. result = []
  333. for i in range(structs.MESH.AI_MAX_NUMBER_OF_TEXTURECOORDS):
  334. result.append(Mesh._load_array(mesh.mTextureCoords[i],
  335. mesh.mNumVertices,
  336. helper.vec2tuple))
  337. return result
  338. def _load_colors(self, mesh):
  339. """
  340. Loads color sets.
  341. mesh - mesh with color sets
  342. result all color sets
  343. """
  344. result = []
  345. #for all possible sets
  346. for i in range(structs.MESH.AI_MAX_NUMBER_OF_COLOR_SETS):
  347. #try this set
  348. x = mesh.mColors[i]
  349. if x:
  350. channel = []
  351. #read data for al vertices!
  352. for j in range(mesh.mNumVertices):
  353. c = x[j]
  354. channel.append((c.r, c.g, c.b, c.a))
  355. result.append(channel)
  356. return result
  357. #the loader as singleton
  358. _assimp_lib = AssimpLib()
  359. def load(filename, processing=0):
  360. """
  361. Loads the model with some specific processing parameters.
  362. filename - file to load model from
  363. processing - processing parameters
  364. result Scene-object with model-data
  365. throws AssimpError - could not open file
  366. """
  367. #read pure data
  368. model = _assimp_lib.load(filename, processing)
  369. if not model:
  370. #Uhhh, something went wrong!
  371. raise AssimpError, ("could not import file: %s" % filename)
  372. try:
  373. #create scene
  374. return Scene(model.contents)
  375. finally:
  376. #forget raw data
  377. _assimp_lib.release(model)