mesh.py 36 KB

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