mesh.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850
  1. from .logger import *
  2. from .package_level import *
  3. from .f_curve_animatable import *
  4. from .armature import *
  5. from .material import *
  6. import bpy
  7. import math
  8. from mathutils import Vector
  9. import shutil
  10. # output related constants
  11. MAX_VERTEX_ELEMENTS = 65535
  12. MAX_VERTEX_ELEMENTS_32Bit = 16777216
  13. COMPRESS_MATRIX_INDICES = True # this is True for .babylon exporter & False for TOB
  14. # used in Mesh & Node constructors, defined in BABYLON.AbstractMesh
  15. BILLBOARDMODE_NONE = 0
  16. #BILLBOARDMODE_X = 1
  17. #BILLBOARDMODE_Y = 2
  18. #BILLBOARDMODE_Z = 4
  19. BILLBOARDMODE_ALL = 7
  20. # used in Mesh constructor, defined in BABYLON.PhysicsEngine
  21. SPHERE_IMPOSTER = 1
  22. BOX_IMPOSTER = 2
  23. #PLANE_IMPOSTER = 3
  24. MESH_IMPOSTER = 4
  25. CAPSULE_IMPOSTER = 5
  26. CONE_IMPOSTER = 6
  27. CYLINDER_IMPOSTER = 7
  28. CONVEX_HULL_IMPOSTER = 8
  29. #===============================================================================
  30. class Mesh(FCurveAnimatable):
  31. def __init__(self, object, scene, startFace, forcedParent, nameID, exporter):
  32. self.name = object.name + str(nameID)
  33. Logger.log('processing begun of mesh: ' + self.name)
  34. self.define_animations(object, True, True, True) #Should animations be done when forcedParent
  35. self.isVisible = not object.hide_render
  36. self.isEnabled = not object.data.loadDisabled
  37. useFlatShading = scene.export_flatshadeScene or object.data.useFlatShading
  38. self.checkCollisions = object.data.checkCollisions
  39. self.receiveShadows = object.data.receiveShadows
  40. self.castShadows = object.data.castShadows
  41. self.freezeWorldMatrix = object.data.freezeWorldMatrix
  42. self.layer = getLayer(object) # used only for lights with 'This Layer Only' checked, not exported
  43. # hasSkeleton detection & skeletonID determination
  44. hasSkeleton = False
  45. objArmature = None # if there's an armature, this will be the one!
  46. if len(object.vertex_groups) > 0 and not object.data.ignoreSkeleton:
  47. objArmature = object.find_armature()
  48. if objArmature != None:
  49. hasSkeleton = True
  50. # used to get bone index, since could be skipping IK bones
  51. skeleton = exporter.get_skeleton(objArmature.name)
  52. i = 0
  53. for obj in scene.objects:
  54. if obj.type == "ARMATURE":
  55. if obj == objArmature:
  56. self.skeletonId = i
  57. break
  58. else:
  59. i += 1
  60. # determine Position, rotation, & scaling
  61. if forcedParent is None:
  62. # Use local matrix
  63. locMatrix = object.matrix_local
  64. if objArmature != None:
  65. # unless the armature is the parent
  66. if object.parent and object.parent == objArmature:
  67. locMatrix = object.matrix_world * object.parent.matrix_world.inverted()
  68. loc, rot, scale = locMatrix.decompose()
  69. self.position = loc
  70. if object.rotation_mode == 'QUATERNION':
  71. self.rotationQuaternion = rot
  72. else:
  73. self.rotation = scale_vector(rot.to_euler('XYZ'), -1)
  74. self.scaling = scale
  75. else:
  76. # use defaults when not None
  77. self.position = Vector((0, 0, 0))
  78. self.rotation = scale_vector(Vector((0, 0, 0)), 1) # isn't scaling 0's by 1 same as 0?
  79. self.scaling = Vector((1, 1, 1))
  80. # ensure no unapplied rotation or scale, when there is an armature
  81. self.hasUnappliedTransforms = (self.scaling.x != 1 or self.scaling.y != 1 or self.scaling.z != 1 or
  82. (hasattr(self, 'rotation' ) and (self.rotation .x != 0 or self.rotation .y != 0 or self.rotation .z != 0)) or
  83. (hasattr(self, 'rotationQuaternion') and (self.rotationQuaternion.x != 0 or self.rotationQuaternion.y != 0 or self.rotationQuaternion.z != 0 or self.rotationQuaternion.w != 1))
  84. )
  85. # determine parent & dataName
  86. if forcedParent is None:
  87. self.dataName = object.data.name # used to support shared vertex instances in later passed
  88. if object.parent and object.parent.type != 'ARMATURE':
  89. self.parentId = object.parent.name
  90. else:
  91. self.dataName = self.name
  92. self.parentId = forcedParent.name
  93. # Get if this will be an instance of another, before processing materials, to avoid multi-bakes
  94. sourceMesh = exporter.getSourceMeshInstance(self.dataName)
  95. if sourceMesh is not None:
  96. #need to make sure rotation mode matches, since value initially copied in InstancedMesh constructor
  97. if hasattr(sourceMesh, 'rotationQuaternion'):
  98. instRot = None
  99. instRotq = rot
  100. else:
  101. instRot = scale_vector(rot.to_euler('XYZ'), -1)
  102. instRotq = None
  103. instance = MeshInstance(self, instRot, instRotq)
  104. sourceMesh.instances.append(instance)
  105. Logger.log('mesh is an instance of : ' + sourceMesh.name + '. Processing halted.', 2)
  106. return
  107. else:
  108. self.instances = []
  109. # Physics
  110. if object.rigid_body != None:
  111. shape_items = {'SPHERE' : SPHERE_IMPOSTER,
  112. 'BOX' : BOX_IMPOSTER,
  113. 'MESH' : MESH_IMPOSTER,
  114. 'CAPSULE' : CAPSULE_IMPOSTER,
  115. 'CONE' : CONE_IMPOSTER,
  116. 'CYLINDER' : CYLINDER_IMPOSTER,
  117. 'CONVEX_HULL': CONVEX_HULL_IMPOSTER}
  118. shape_type = shape_items[object.rigid_body.collision_shape]
  119. self.physicsImpostor = shape_type
  120. mass = object.rigid_body.mass
  121. if mass < 0.005:
  122. mass = 0
  123. self.physicsMass = mass
  124. self.physicsFriction = object.rigid_body.friction
  125. self.physicsRestitution = object.rigid_body.restitution
  126. # process all of the materials required
  127. maxVerts = MAX_VERTEX_ELEMENTS # change for multi-materials
  128. recipe = BakingRecipe(object)
  129. self.billboardMode = BILLBOARDMODE_ALL if recipe.isBillboard else BILLBOARDMODE_NONE
  130. if recipe.needsBaking:
  131. if recipe.multipleRenders:
  132. Logger.warn('Mixing of Cycles & Blender Render in same mesh not supported. No materials exported.', 2)
  133. else:
  134. bakedMat = BakedMaterial(exporter, object, recipe)
  135. exporter.materials.append(bakedMat)
  136. self.materialId = bakedMat.name
  137. else:
  138. bjs_material_slots = []
  139. for slot in object.material_slots:
  140. # None will be returned when either the first encounter or must be unique due to baked textures
  141. material = exporter.getMaterial(slot.name)
  142. if (material != None):
  143. Logger.log('registered as also a user of material: ' + slot.name, 2)
  144. else:
  145. material = StdMaterial(slot, exporter, object)
  146. exporter.materials.append(material)
  147. bjs_material_slots.append(material)
  148. if len(bjs_material_slots) == 1:
  149. self.materialId = bjs_material_slots[0].name
  150. elif len(bjs_material_slots) > 1:
  151. multimat = MultiMaterial(bjs_material_slots, len(exporter.multiMaterials), exporter.nameSpace)
  152. self.materialId = multimat.name
  153. exporter.multiMaterials.append(multimat)
  154. maxVerts = MAX_VERTEX_ELEMENTS_32Bit
  155. else:
  156. Logger.warn('No materials have been assigned: ', 2)
  157. # Get mesh
  158. mesh = object.to_mesh(scene, True, 'PREVIEW')
  159. # Triangulate mesh if required
  160. Mesh.mesh_triangulate(mesh)
  161. # Getting vertices and indices
  162. self.positions = []
  163. self.normals = []
  164. self.uvs = [] # not always used
  165. self.uvs2 = [] # not always used
  166. self.colors = [] # not always used
  167. self.indices = []
  168. self.subMeshes = []
  169. hasUV = len(mesh.tessface_uv_textures) > 0
  170. if hasUV:
  171. which = len(mesh.tessface_uv_textures) - 1 if recipe.needsBaking else 0
  172. UVmap = mesh.tessface_uv_textures[which].data
  173. hasUV2 = len(mesh.tessface_uv_textures) > 1 and not recipe.needsBaking
  174. if hasUV2:
  175. UV2map = mesh.tessface_uv_textures[1].data
  176. hasVertexColor = len(mesh.vertex_colors) > 0
  177. if hasVertexColor:
  178. Colormap = mesh.tessface_vertex_colors.active.data
  179. if hasSkeleton:
  180. weightsPerVertex = []
  181. indicesPerVertex = []
  182. influenceCounts = [0, 0, 0, 0, 0, 0, 0, 0, 0] # 9, so accessed orign 1; 0 used for all those greater than 8
  183. totalInfluencers = 0
  184. highestInfluenceObserved = 0
  185. # used tracking of vertices as they are received
  186. alreadySavedVertices = []
  187. vertices_Normals = []
  188. vertices_UVs = []
  189. vertices_UV2s = []
  190. vertices_Colors = []
  191. vertices_indices = []
  192. vertices_sk_weights = []
  193. vertices_sk_indices = []
  194. self.offsetFace = 0
  195. for v in range(0, len(mesh.vertices)):
  196. alreadySavedVertices.append(False)
  197. vertices_Normals.append([])
  198. vertices_UVs.append([])
  199. vertices_UV2s.append([])
  200. vertices_Colors.append([])
  201. vertices_indices.append([])
  202. vertices_sk_weights.append([])
  203. vertices_sk_indices.append([])
  204. materialsCount = 1 if recipe.needsBaking else max(1, len(object.material_slots))
  205. verticesCount = 0
  206. indicesCount = 0
  207. for materialIndex in range(materialsCount):
  208. if self.offsetFace != 0:
  209. break
  210. subMeshVerticesStart = verticesCount
  211. subMeshIndexStart = indicesCount
  212. for faceIndex in range(startFace, len(mesh.tessfaces)): # For each face
  213. face = mesh.tessfaces[faceIndex]
  214. if face.material_index != materialIndex and not recipe.needsBaking:
  215. continue
  216. if verticesCount + 3 > maxVerts:
  217. self.offsetFace = faceIndex
  218. break
  219. for v in range(3): # For each vertex in face
  220. vertex_index = face.vertices[v]
  221. vertex = mesh.vertices[vertex_index]
  222. position = vertex.co
  223. normal = face.normal if useFlatShading else vertex.normal
  224. #skeletons
  225. if hasSkeleton:
  226. matricesWeights = []
  227. matricesIndices = []
  228. # Getting influences
  229. for group in vertex.groups:
  230. index = group.group
  231. weight = group.weight
  232. for bone in objArmature.pose.bones:
  233. if object.vertex_groups[index].name == bone.name:
  234. matricesWeights.append(weight)
  235. matricesIndices.append(skeleton.get_index_of_bone(bone.name))
  236. # Texture coordinates
  237. if hasUV:
  238. vertex_UV = UVmap[face.index].uv[v]
  239. if hasUV2:
  240. vertex_UV2 = UV2map[face.index].uv[v]
  241. # Vertex color
  242. if hasVertexColor:
  243. if v == 0:
  244. vertex_Color = Colormap[face.index].color1
  245. if v == 1:
  246. vertex_Color = Colormap[face.index].color2
  247. if v == 2:
  248. vertex_Color = Colormap[face.index].color3
  249. # Check if the current vertex is already saved
  250. alreadySaved = alreadySavedVertices[vertex_index] and not useFlatShading
  251. if alreadySaved:
  252. alreadySaved = False
  253. # UV
  254. index_UV = 0
  255. for savedIndex in vertices_indices[vertex_index]:
  256. vNormal = vertices_Normals[vertex_index][index_UV]
  257. if not same_vertex(normal, vNormal):
  258. continue;
  259. if hasUV:
  260. vUV = vertices_UVs[vertex_index][index_UV]
  261. if not same_array(vertex_UV, vUV):
  262. continue
  263. if hasUV2:
  264. vUV2 = vertices_UV2s[vertex_index][index_UV]
  265. if not same_array(vertex_UV2, vUV2):
  266. continue
  267. if hasVertexColor:
  268. vColor = vertices_Colors[vertex_index][index_UV]
  269. if vColor.r != vertex_Color.r or vColor.g != vertex_Color.g or vColor.b != vertex_Color.b:
  270. continue
  271. if hasSkeleton:
  272. vSkWeight = vertices_sk_weights[vertex_index]
  273. vSkIndices = vertices_sk_indices[vertex_index]
  274. if not same_array(vSkWeight[index_UV], matricesWeights) or not same_array(vSkIndices[index_UV], matricesIndices):
  275. continue
  276. if vertices_indices[vertex_index][index_UV] >= subMeshVerticesStart:
  277. alreadySaved = True
  278. break
  279. index_UV += 1
  280. if (alreadySaved):
  281. # Reuse vertex
  282. index = vertices_indices[vertex_index][index_UV]
  283. else:
  284. # Export new one
  285. index = verticesCount
  286. alreadySavedVertices[vertex_index] = True
  287. vertices_Normals[vertex_index].append(normal)
  288. self.normals.append(normal)
  289. if hasUV:
  290. vertices_UVs[vertex_index].append(vertex_UV)
  291. self.uvs.append(vertex_UV[0])
  292. self.uvs.append(vertex_UV[1])
  293. if hasUV2:
  294. vertices_UV2s[vertex_index].append(vertex_UV2)
  295. self.uvs2.append(vertex_UV2[0])
  296. self.uvs2.append(vertex_UV2[1])
  297. if hasVertexColor:
  298. vertices_Colors[vertex_index].append(vertex_Color)
  299. self.colors.append(vertex_Color.r)
  300. self.colors.append(vertex_Color.g)
  301. self.colors.append(vertex_Color.b)
  302. self.colors.append(1.0)
  303. if hasSkeleton:
  304. vertices_sk_weights[vertex_index].append(matricesWeights)
  305. vertices_sk_indices[vertex_index].append(matricesIndices)
  306. nInfluencers = len(matricesWeights)
  307. totalInfluencers += nInfluencers
  308. if nInfluencers <= 8:
  309. influenceCounts[nInfluencers] += 1
  310. else:
  311. influenceCounts[0] += 1
  312. highestInfluenceObserved = nInfluencers if nInfluencers > highestInfluenceObserved else highestInfluenceObserved
  313. weightsPerVertex.append(matricesWeights)
  314. indicesPerVertex.append(matricesIndices)
  315. vertices_indices[vertex_index].append(index)
  316. self.positions.append(position)
  317. verticesCount += 1
  318. self.indices.append(index)
  319. indicesCount += 1
  320. self.subMeshes.append(SubMesh(materialIndex, subMeshVerticesStart, subMeshIndexStart, verticesCount - subMeshVerticesStart, indicesCount - subMeshIndexStart))
  321. if verticesCount > MAX_VERTEX_ELEMENTS:
  322. Logger.warn('Due to multi-materials / Shapekeys & this meshes size, 32bit indices must be used. This may not run on all hardware.', 2)
  323. BakedMaterial.meshBakingClean(object)
  324. Logger.log('num positions : ' + str(len(self.positions)), 2)
  325. Logger.log('num normals : ' + str(len(self.normals )), 2)
  326. Logger.log('num uvs : ' + str(len(self.uvs )), 2)
  327. Logger.log('num uvs2 : ' + str(len(self.uvs2 )), 2)
  328. Logger.log('num colors : ' + str(len(self.colors )), 2)
  329. Logger.log('num indices : ' + str(len(self.indices )), 2)
  330. if hasSkeleton:
  331. Logger.log('Skeleton stats: ', 2)
  332. self.toFixedInfluencers(weightsPerVertex, indicesPerVertex, object.data.maxInfluencers, highestInfluenceObserved)
  333. if (COMPRESS_MATRIX_INDICES):
  334. self.skeletonIndices = Mesh.packSkeletonIndices(self.skeletonIndices)
  335. if (self.numBoneInfluencers > 4):
  336. self.skeletonIndicesExtra = Mesh.packSkeletonIndices(self.skeletonIndicesExtra)
  337. Logger.log('Total Influencers: ' + format_f(totalInfluencers), 3)
  338. Logger.log('Avg # of influencers per vertex: ' + format_f(totalInfluencers / len(self.positions)), 3)
  339. Logger.log('Highest # of influencers observed: ' + str(highestInfluenceObserved) + ', num vertices with this: ' + format_int(influenceCounts[highestInfluenceObserved if highestInfluenceObserved < 9 else 0]), 3)
  340. Logger.log('exported as ' + str(self.numBoneInfluencers) + ' influencers', 3)
  341. nWeights = len(self.skeletonWeights) + (len(self.skeletonWeightsExtra) if hasattr(self, 'skeletonWeightsExtra') else 0)
  342. Logger.log('num skeletonWeights and skeletonIndices: ' + str(nWeights), 3)
  343. numZeroAreaFaces = self.find_zero_area_faces()
  344. if numZeroAreaFaces > 0:
  345. Logger.warn('# of 0 area faces found: ' + str(numZeroAreaFaces), 2)
  346. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  347. def find_zero_area_faces(self):
  348. nFaces = int(len(self.indices) / 3)
  349. nZeroAreaFaces = 0
  350. for f in range(0, nFaces):
  351. faceOffset = f * 3
  352. p1 = self.positions[self.indices[faceOffset ]]
  353. p2 = self.positions[self.indices[faceOffset + 1]]
  354. p3 = self.positions[self.indices[faceOffset + 2]]
  355. if same_vertex(p1, p2) or same_vertex(p1, p3) or same_vertex(p2, p3): nZeroAreaFaces += 1
  356. return nZeroAreaFaces
  357. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  358. @staticmethod
  359. def mesh_triangulate(mesh):
  360. try:
  361. import bmesh
  362. bm = bmesh.new()
  363. bm.from_mesh(mesh)
  364. bmesh.ops.triangulate(bm, faces = bm.faces)
  365. bm.to_mesh(mesh)
  366. mesh.calc_tessface()
  367. bm.free()
  368. except:
  369. pass
  370. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  371. def toFixedInfluencers(self, weightsPerVertex, indicesPerVertex, maxInfluencers, highestObserved):
  372. if (maxInfluencers > 8 or maxInfluencers < 1):
  373. maxInfluencers = 8
  374. Logger.warn('Maximum # of influencers invalid, set to 8', 3)
  375. self.numBoneInfluencers = maxInfluencers if maxInfluencers < highestObserved else highestObserved
  376. needExtras = self.numBoneInfluencers > 4
  377. maxInfluencersExceeded = 0
  378. fixedWeights = []
  379. fixedIndices = []
  380. fixedWeightsExtra = []
  381. fixedIndicesExtra = []
  382. for i in range(len(weightsPerVertex)):
  383. weights = weightsPerVertex[i]
  384. indices = indicesPerVertex[i]
  385. nInfluencers = len(weights)
  386. if (nInfluencers > self.numBoneInfluencers):
  387. maxInfluencersExceeded += 1
  388. Mesh.sortByDescendingInfluence(weights, indices)
  389. for j in range(4):
  390. fixedWeights.append(weights[j] if nInfluencers > j else 0.0)
  391. fixedIndices.append(indices[j] if nInfluencers > j else 0 )
  392. if needExtras:
  393. for j in range(4, 8):
  394. fixedWeightsExtra.append(weights[j] if nInfluencers > j else 0.0)
  395. fixedIndicesExtra.append(indices[j] if nInfluencers > j else 0 )
  396. self.skeletonWeights = fixedWeights
  397. self.skeletonIndices = fixedIndices
  398. if needExtras:
  399. self.skeletonWeightsExtra = fixedWeightsExtra
  400. self.skeletonIndicesExtra = fixedIndicesExtra
  401. if maxInfluencersExceeded > 0:
  402. Logger.warn('Maximum # of influencers exceeded for ' + format_int(maxInfluencersExceeded) + ' vertices, extras ignored', 3)
  403. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  404. # sorts one set of weights & indices by descending weight, by reference
  405. # not shown to help with MakeHuman, but did not hurt. In just so it is not lost for future.
  406. @staticmethod
  407. def sortByDescendingInfluence(weights, indices):
  408. notSorted = True
  409. while(notSorted):
  410. notSorted = False
  411. for idx in range(1, len(weights)):
  412. if weights[idx - 1] < weights[idx]:
  413. tmp = weights[idx]
  414. weights[idx ] = weights[idx - 1]
  415. weights[idx - 1] = tmp
  416. tmp = indices[idx]
  417. indices[idx ] = indices[idx - 1]
  418. indices[idx - 1] = tmp
  419. notSorted = True
  420. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  421. # assume that toFixedInfluencers has already run, which ensures indices length is a multiple of 4
  422. @staticmethod
  423. def packSkeletonIndices(indices):
  424. compressedIndices = []
  425. for i in range(math.floor(len(indices) / 4)):
  426. idx = i * 4
  427. matricesIndicesCompressed = indices[idx ]
  428. matricesIndicesCompressed += indices[idx + 1] << 8
  429. matricesIndicesCompressed += indices[idx + 2] << 16
  430. matricesIndicesCompressed += indices[idx + 3] << 24
  431. compressedIndices.append(matricesIndicesCompressed)
  432. return compressedIndices
  433. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  434. def to_scene_file(self, file_handler):
  435. file_handler.write('{')
  436. write_string(file_handler, 'name', self.name, True)
  437. write_string(file_handler, 'id', self.name)
  438. if hasattr(self, 'parentId'): write_string(file_handler, 'parentId', self.parentId)
  439. if hasattr(self, 'materialId'): write_string(file_handler, 'materialId', self.materialId)
  440. write_int(file_handler, 'billboardMode', self.billboardMode)
  441. write_vector(file_handler, 'position', self.position)
  442. if hasattr(self, "rotationQuaternion"):
  443. write_quaternion(file_handler, 'rotationQuaternion', self.rotationQuaternion)
  444. else:
  445. write_vector(file_handler, 'rotation', self.rotation)
  446. write_vector(file_handler, 'scaling', self.scaling)
  447. write_bool(file_handler, 'isVisible', self.isVisible)
  448. write_bool(file_handler, 'freezeWorldMatrix', self.freezeWorldMatrix)
  449. write_bool(file_handler, 'isEnabled', self.isEnabled)
  450. write_bool(file_handler, 'checkCollisions', self.checkCollisions)
  451. write_bool(file_handler, 'receiveShadows', self.receiveShadows)
  452. if hasattr(self, 'physicsImpostor'):
  453. write_int(file_handler, 'physicsImpostor', self.physicsImpostor)
  454. write_float(file_handler, 'physicsMass', self.physicsMass)
  455. write_float(file_handler, 'physicsFriction', self.physicsFriction)
  456. write_float(file_handler, 'physicsRestitution', self.physicsRestitution)
  457. # Geometry
  458. if hasattr(self, 'skeletonId'):
  459. write_int(file_handler, 'skeletonId', self.skeletonId)
  460. write_int(file_handler, 'numBoneInfluencers', self.numBoneInfluencers)
  461. write_vector_array(file_handler, 'positions', self.positions)
  462. write_vector_array(file_handler, 'normals' , self.normals )
  463. if len(self.uvs) > 0:
  464. write_array(file_handler, 'uvs', self.uvs)
  465. if len(self.uvs2) > 0:
  466. write_array(file_handler, 'uvs2', self.uvs2)
  467. if len(self.colors) > 0:
  468. write_array(file_handler, 'colors', self.colors)
  469. if hasattr(self, 'skeletonWeights'):
  470. write_array(file_handler, 'matricesWeights', self.skeletonWeights)
  471. write_array(file_handler, 'matricesIndices', self.skeletonIndices)
  472. if hasattr(self, 'skeletonWeightsExtra'):
  473. write_array(file_handler, 'matricesWeightsExtra', self.skeletonWeightsExtra)
  474. write_array(file_handler, 'matricesIndicesExtra', self.skeletonIndicesExtra)
  475. write_array(file_handler, 'indices', self.indices)
  476. # Sub meshes
  477. file_handler.write('\n,"subMeshes":[')
  478. first = True
  479. for subMesh in self.subMeshes:
  480. if first == False:
  481. file_handler.write(',')
  482. subMesh.to_scene_file(file_handler)
  483. first = False
  484. file_handler.write(']')
  485. super().to_scene_file(file_handler) # Animations
  486. # Instances
  487. first = True
  488. file_handler.write('\n,"instances":[')
  489. for instance in self.instances:
  490. if first == False:
  491. file_handler.write(',')
  492. instance.to_scene_file(file_handler)
  493. first = False
  494. file_handler.write(']')
  495. # Close mesh
  496. file_handler.write('}\n')
  497. self.alreadyExported = True
  498. #===============================================================================
  499. class MeshInstance:
  500. def __init__(self, instancedMesh, rotation, rotationQuaternion):
  501. self.name = instancedMesh.name
  502. if hasattr(instancedMesh, 'parentId'): self.parentId = instancedMesh.parentId
  503. self.position = instancedMesh.position
  504. if rotation is not None:
  505. self.rotation = rotation
  506. if rotationQuaternion is not None:
  507. self.rotationQuaternion = rotationQuaternion
  508. self.scaling = instancedMesh.scaling
  509. self.freezeWorldMatrix = instancedMesh.freezeWorldMatrix
  510. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  511. def to_scene_file(self, file_handler):
  512. file_handler.write('{')
  513. write_string(file_handler, 'name', self.name, True)
  514. if hasattr(self, 'parentId'): write_string(file_handler, 'parentId', self.parentId)
  515. write_vector(file_handler, 'position', self.position)
  516. if hasattr(self, 'rotation'):
  517. write_vector(file_handler, 'rotation', self.rotation)
  518. else:
  519. write_quaternion(file_handler, 'rotationQuaternion', self.rotationQuaternion)
  520. write_vector(file_handler, 'scaling', self.scaling)
  521. # freeze World Matrix currently ignored for instances
  522. write_bool(file_handler, 'freezeWorldMatrix', self.freezeWorldMatrix)
  523. file_handler.write('}')
  524. #===============================================================================
  525. class Node(FCurveAnimatable):
  526. def __init__(self, node):
  527. Logger.log('processing begun of node: ' + node.name)
  528. self.define_animations(node, True, True, True) #Should animations be done when forcedParent
  529. self.name = node.name
  530. if node.parent and node.parent.type != 'ARMATURE':
  531. self.parentId = node.parent.name
  532. loc, rot, scale = node.matrix_local.decompose()
  533. self.position = loc
  534. if node.rotation_mode == 'QUATERNION':
  535. self.rotationQuaternion = rot
  536. else:
  537. self.rotation = scale_vector(rot.to_euler('XYZ'), -1)
  538. self.scaling = scale
  539. self.isVisible = False
  540. self.isEnabled = True
  541. self.checkCollisions = False
  542. self.billboardMode = BILLBOARDMODE_NONE
  543. self.castShadows = False
  544. self.receiveShadows = False
  545. self.layer = -1 # nodes do not have layers attribute
  546. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  547. def to_scene_file(self, file_handler):
  548. file_handler.write('{')
  549. write_string(file_handler, 'name', self.name, True)
  550. write_string(file_handler, 'id', self.name)
  551. if hasattr(self, 'parentId'): write_string(file_handler, 'parentId', self.parentId)
  552. write_vector(file_handler, 'position', self.position)
  553. if hasattr(self, "rotationQuaternion"):
  554. write_quaternion(file_handler, "rotationQuaternion", self.rotationQuaternion)
  555. else:
  556. write_vector(file_handler, 'rotation', self.rotation)
  557. write_vector(file_handler, 'scaling', self.scaling)
  558. write_bool(file_handler, 'isVisible', self.isVisible)
  559. write_bool(file_handler, 'isEnabled', self.isEnabled)
  560. write_bool(file_handler, 'checkCollisions', self.checkCollisions)
  561. write_int(file_handler, 'billboardMode', self.billboardMode)
  562. write_bool(file_handler, 'receiveShadows', self.receiveShadows)
  563. super().to_scene_file(file_handler) # Animations
  564. file_handler.write('}')
  565. #===============================================================================
  566. class SubMesh:
  567. def __init__(self, materialIndex, verticesStart, indexStart, verticesCount, indexCount):
  568. self.materialIndex = materialIndex
  569. self.verticesStart = verticesStart
  570. self.indexStart = indexStart
  571. self.verticesCount = verticesCount
  572. self.indexCount = indexCount
  573. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  574. def to_scene_file(self, file_handler):
  575. file_handler.write('{')
  576. write_int(file_handler, 'materialIndex', self.materialIndex, True)
  577. write_int(file_handler, 'verticesStart', self.verticesStart)
  578. write_int(file_handler, 'verticesCount', self.verticesCount)
  579. write_int(file_handler, 'indexStart' , self.indexStart)
  580. write_int(file_handler, 'indexCount' , self.indexCount)
  581. file_handler.write('}')
  582. #===============================================================================
  583. bpy.types.Mesh.autoAnimate = bpy.props.BoolProperty(
  584. name='Auto launch animations',
  585. description='',
  586. default = False
  587. )
  588. bpy.types.Mesh.useFlatShading = bpy.props.BoolProperty(
  589. name='Use Flat Shading',
  590. description='Use face normals. Increases vertices.',
  591. default = False
  592. )
  593. bpy.types.Mesh.checkCollisions = bpy.props.BoolProperty(
  594. name='Check Collisions',
  595. description='Indicates mesh should be checked that it does not run into anything.',
  596. default = False
  597. )
  598. bpy.types.Mesh.castShadows = bpy.props.BoolProperty(
  599. name='Cast Shadows',
  600. description='',
  601. default = False
  602. )
  603. bpy.types.Mesh.receiveShadows = bpy.props.BoolProperty(
  604. name='Receive Shadows',
  605. description='',
  606. default = False
  607. )
  608. # not currently in use
  609. bpy.types.Mesh.forceBaking = bpy.props.BoolProperty(
  610. name='Combine Multi-textures / resize',
  611. description='Also good to adjust single texture\'s size /compression.',
  612. default = False
  613. )
  614. # not currently in use
  615. bpy.types.Mesh.usePNG = bpy.props.BoolProperty(
  616. name='Need Alpha',
  617. description='Saved as PNG when alpha is required, else JPG.',
  618. default = False
  619. )
  620. bpy.types.Mesh.bakeSize = bpy.props.IntProperty(
  621. name='Texture Size',
  622. description='Final dimensions of texture(s). Not required to be a power of 2, but recommended.',
  623. default = 1024
  624. )
  625. bpy.types.Mesh.bakeQuality = bpy.props.IntProperty(
  626. name='Quality 1-100',
  627. description='For JPG: The trade-off between Quality - File size(100 highest quality)\nFor PNG: amount of time spent for compression',
  628. default = 50, min = 1, max = 100
  629. )
  630. bpy.types.Mesh.materialNameSpace = bpy.props.StringProperty(
  631. name='Name Space',
  632. description='Prefix to use for materials for sharing across .blends.',
  633. default = DEFAULT_MATERIAL_NAMESPACE
  634. )
  635. bpy.types.Mesh.maxSimultaneousLights = bpy.props.IntProperty(
  636. name='Max Simultaneous Lights 0 - 32',
  637. description='BJS property set on each material of this mesh.\nSet higher for more complex lighting.\nSet lower for armatures on mobile',
  638. default = 4, min = 0, max = 32
  639. )
  640. bpy.types.Mesh.checkReadyOnlyOnce = bpy.props.BoolProperty(
  641. name='Check Ready Only Once',
  642. description='When checked better CPU utilization. Advanced user option.',
  643. default = False
  644. )
  645. bpy.types.Mesh.freezeWorldMatrix = bpy.props.BoolProperty(
  646. name='Freeze World Matrix',
  647. description='Indicate the position, rotation, & scale do not change for performance reasons',
  648. default = False
  649. )
  650. bpy.types.Mesh.loadDisabled = bpy.props.BoolProperty(
  651. name='Load Disabled',
  652. description='Indicate this mesh & children should not be active until enabled by code.',
  653. default = False
  654. )
  655. bpy.types.Mesh.attachedSound = bpy.props.StringProperty(
  656. name='Sound',
  657. description='',
  658. default = ''
  659. )
  660. bpy.types.Mesh.loopSound = bpy.props.BoolProperty(
  661. name='Loop sound',
  662. description='',
  663. default = True
  664. )
  665. bpy.types.Mesh.autoPlaySound = bpy.props.BoolProperty(
  666. name='Auto play sound',
  667. description='',
  668. default = True
  669. )
  670. bpy.types.Mesh.maxSoundDistance = bpy.props.FloatProperty(
  671. name='Max sound distance',
  672. description='',
  673. default = 100
  674. )
  675. bpy.types.Mesh.ignoreSkeleton = bpy.props.BoolProperty(
  676. name='Ignore',
  677. description='Do not export assignment to a skeleton',
  678. default = False
  679. )
  680. bpy.types.Mesh.maxInfluencers = bpy.props.IntProperty(
  681. name='Max bone Influencers / Vertex',
  682. description='When fewer than this are observed, the lower value is used.',
  683. default = 8, min = 1, max = 8
  684. )
  685. #===============================================================================
  686. class MeshPanel(bpy.types.Panel):
  687. bl_label = get_title()
  688. bl_space_type = 'PROPERTIES'
  689. bl_region_type = 'WINDOW'
  690. bl_context = 'data'
  691. @classmethod
  692. def poll(cls, context):
  693. ob = context.object
  694. return ob is not None and isinstance(ob.data, bpy.types.Mesh)
  695. def draw(self, context):
  696. ob = context.object
  697. layout = self.layout
  698. row = layout.row()
  699. row.prop(ob.data, 'useFlatShading')
  700. row.prop(ob.data, 'checkCollisions')
  701. row = layout.row()
  702. row.prop(ob.data, 'castShadows')
  703. row.prop(ob.data, 'receiveShadows')
  704. row = layout.row()
  705. row.prop(ob.data, 'freezeWorldMatrix')
  706. row.prop(ob.data, 'loadDisabled')
  707. layout.prop(ob.data, 'autoAnimate')
  708. box = layout.box()
  709. box.label(text='Skeleton:')
  710. box.prop(ob.data, 'ignoreSkeleton')
  711. row = box.row()
  712. row.enabled = not ob.data.ignoreSkeleton
  713. row.prop(ob.data, 'maxInfluencers')
  714. box = layout.box()
  715. box.label('Materials')
  716. box.prop(ob.data, 'materialNameSpace')
  717. box.prop(ob.data, 'maxSimultaneousLights')
  718. box.prop(ob.data, 'checkReadyOnlyOnce')
  719. box = layout.box()
  720. box.label(text='Procedural Texture / Cycles Baking')
  721. # box.prop(ob.data, 'forceBaking')
  722. # box.prop(ob.data, 'usePNG')
  723. box.prop(ob.data, 'bakeSize')
  724. box.prop(ob.data, 'bakeQuality')
  725. box = layout.box()
  726. box.prop(ob.data, 'attachedSound')
  727. row = box.row()
  728. row.prop(ob.data, 'autoPlaySound')
  729. row.prop(ob.data, 'loopSound')
  730. box.prop(ob.data, 'maxSoundDistance')