threeJsFileTranlator.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. __author__ = 'Chris Lewis'
  2. __version__ = '0.1.0'
  3. __email__ = '[email protected]'
  4. import sys
  5. import json
  6. import maya.cmds as mc
  7. from maya.OpenMaya import *
  8. from maya.OpenMayaMPx import *
  9. kPluginTranslatorTypeName = 'Three.js'
  10. kOptionScript = 'ThreeJsExportScript'
  11. kDefaultOptionsString = '0'
  12. FLOAT_PRECISION = 8
  13. # adds decimal precision to JSON encoding
  14. class DecimalEncoder(json.JSONEncoder):
  15. def _iterencode(self, o, markers=None):
  16. if isinstance(o, float):
  17. s = str(o)
  18. if '.' in s and len(s[s.index('.'):]) > FLOAT_PRECISION - 1:
  19. s = '%.{0}f'.format(FLOAT_PRECISION) % o
  20. while '.' in s and s[-1] == '0':
  21. s = s[-2]
  22. return (s for s in [s])
  23. return super(DecimalEncoder, self)._iterencode(o, markers)
  24. class ThreeJsError(Exception):
  25. pass
  26. class ThreeJsWriter(object):
  27. def __init__(self):
  28. self.componentKeys = ['vertices', 'normals', 'colors', 'uvs', 'materials', 'faces']
  29. def _parseOptions(self, optionsString):
  30. self.options = dict([(x, False) for x in self.componentKeys])
  31. optionsString = optionsString[2:] # trim off the "0;" that Maya adds to the options string
  32. for option in optionsString.split(' '):
  33. self.options[option] = True
  34. def _updateOffsets(self):
  35. for key in self.componentKeys:
  36. if key == 'uvs':
  37. continue
  38. self.offsets[key] = len(getattr(self, key))
  39. for i in range(len(self.uvs)):
  40. self.offsets['uvs'][i] = len(self.uvs[i])
  41. def _getTypeBitmask(self, options):
  42. bitmask = 0
  43. if options['materials']:
  44. bitmask |= 2
  45. if options['uvs']:
  46. bitmask |= 8
  47. if options['normals']:
  48. bitmask |= 32
  49. if options['colors']:
  50. bitmask |= 128
  51. return bitmask
  52. def _exportMesh(self, dagPath, component):
  53. mesh = MFnMesh(dagPath)
  54. options = self.options.copy()
  55. self._updateOffsets()
  56. # export vertex data
  57. if options['vertices']:
  58. try:
  59. iterVerts = MItMeshVertex(dagPath, component)
  60. while not iterVerts.isDone():
  61. point = iterVerts.position(MSpace.kWorld)
  62. self.vertices += [point.x, point.y, point.z]
  63. iterVerts.next()
  64. except:
  65. options['vertices'] = False
  66. # export material data
  67. # TODO: actually parse material data
  68. materialIndices = MIntArray()
  69. if options['materials']:
  70. try:
  71. shaders = MObjectArray()
  72. mesh.getConnectedShaders(0, shaders, materialIndices)
  73. while len(self.materials) < shaders.length():
  74. self.materials.append({}) # placeholder material definition
  75. except:
  76. self.materials = [{}]
  77. # export uv data
  78. if options['uvs']:
  79. try:
  80. uvLayers = []
  81. mesh.getUVSetNames(uvLayers)
  82. while len(uvLayers) > len(self.uvs):
  83. self.uvs.append([])
  84. self.offsets['uvs'].append(0)
  85. for i, layer in enumerate(uvLayers):
  86. uList = MFloatArray()
  87. vList = MFloatArray()
  88. mesh.getUVs(uList, vList, layer)
  89. for j in xrange(uList.length()):
  90. self.uvs[i] += [uList[j], vList[j]]
  91. except:
  92. options['uvs'] = False
  93. # export normal data
  94. if options['normals']:
  95. try:
  96. normals = MFloatVectorArray()
  97. mesh.getNormals(normals, MSpace.kWorld)
  98. for i in xrange(normals.length()):
  99. point = normals[i]
  100. self.normals += [point.x, point.y, point.z]
  101. except:
  102. options['normals'] = False
  103. # export color data
  104. if options['colors']:
  105. try:
  106. colors = MColorArray()
  107. mesh.getColors(colors)
  108. for i in xrange(colors.length()):
  109. color = colors[i]
  110. # uncolored vertices are set to (-1, -1, -1). Clamps colors to (0, 0, 0).
  111. self.colors += [max(color.r, 0), max(color.g, 0), max(color.b, 0)]
  112. except:
  113. options['colors'] = False
  114. # export face data
  115. if not options['vertices']:
  116. return
  117. bitmask = self._getTypeBitmask(options)
  118. iterPolys = MItMeshPolygon(dagPath, component)
  119. while not iterPolys.isDone():
  120. self.faces.append(bitmask)
  121. # export face vertices
  122. verts = MIntArray()
  123. iterPolys.getVertices(verts)
  124. for i in xrange(verts.length()):
  125. self.faces.append(verts[i] + self.offsets['vertices'])
  126. # export face vertex materials
  127. if options['materials']:
  128. if materialIndices.length():
  129. self.faces.append(materialIndices[iterPolys.index()])
  130. # export face vertex uvs
  131. if options['uvs']:
  132. util = MScriptUtil()
  133. uvPtr = util.asIntPtr()
  134. for i, layer in enumerate(uvLayers):
  135. for j in xrange(verts.length()):
  136. iterPolys.getUVIndex(j, uvPtr, layer)
  137. uvIndex = util.getInt(uvPtr)
  138. self.faces.append(uvIndex + self.offsets['uvs'][i])
  139. # export face vertex normals
  140. if options['normals']:
  141. for i in xrange(3):
  142. normalIndex = iterPolys.normalIndex(i)
  143. self.faces.append(normalIndex + self.offsets['normals'])
  144. # export face vertex colors
  145. if options['colors']:
  146. colors = MIntArray()
  147. iterPolys.getColorIndices(colors)
  148. for i in xrange(colors.length()):
  149. self.faces.append(colors[i] + self.offsets['colors'])
  150. iterPolys.next()
  151. def _getMeshes(self, nodes):
  152. meshes = []
  153. for node in nodes:
  154. if mc.nodeType(node) == 'mesh':
  155. meshes.append(node)
  156. else:
  157. for child in mc.listRelatives(node, s=1):
  158. if mc.nodeType(child) == 'mesh':
  159. meshes.append(child)
  160. return meshes
  161. def _exportMeshes(self):
  162. # export all
  163. if self.accessMode == MPxFileTranslator.kExportAccessMode:
  164. mc.select(self._getMeshes(mc.ls(typ='mesh')))
  165. # export selection
  166. elif self.accessMode == MPxFileTranslator.kExportActiveAccessMode:
  167. mc.select(self._getMeshes(mc.ls(sl=1)))
  168. else:
  169. raise ThreeJsError('Unsupported access mode: {0}'.format(self.accessMode))
  170. dups = [mc.duplicate(mesh)[0] for mesh in mc.ls(sl=1)]
  171. combined = mc.polyUnite(dups, mergeUVSets=1, ch=0) if len(dups) > 1 else dups[0]
  172. mc.polyTriangulate(combined)
  173. mc.select(combined)
  174. sel = MSelectionList()
  175. MGlobal.getActiveSelectionList(sel)
  176. mDag = MDagPath()
  177. mComp = MObject()
  178. sel.getDagPath(0, mDag, mComp)
  179. self._exportMesh(mDag, mComp)
  180. mc.delete(combined)
  181. def write(self, path, optionString, accessMode):
  182. self.path = path
  183. self._parseOptions(optionString)
  184. self.accessMode = accessMode
  185. self.root = dict(metadata=dict(formatVersion=3))
  186. self.offsets = dict()
  187. for key in self.componentKeys:
  188. setattr(self, key, [])
  189. self.offsets[key] = 0
  190. self.offsets['uvs'] = []
  191. self.uvs = []
  192. self._exportMeshes()
  193. # add the component buffers to the root JSON object
  194. for key in self.componentKeys:
  195. buffer_ = getattr(self, key)
  196. if buffer_:
  197. self.root[key] = buffer_
  198. # materials are required for parsing
  199. if not self.root.has_key('materials'):
  200. self.root['materials'] = [{}]
  201. # write the file
  202. with file(self.path, 'w') as f:
  203. f.write(json.dumps(self.root, separators=(',',':'), cls=DecimalEncoder))
  204. class ThreeJsTranslator(MPxFileTranslator):
  205. def __init__(self):
  206. MPxFileTranslator.__init__(self)
  207. def haveWriteMethod(self):
  208. return True
  209. def filter(self):
  210. return '*.js'
  211. def defaultExtension(self):
  212. return 'js'
  213. def writer(self, fileObject, optionString, accessMode):
  214. path = fileObject.fullName()
  215. writer = ThreeJsWriter()
  216. writer.write(path, optionString, accessMode)
  217. def translatorCreator():
  218. return asMPxPtr(ThreeJsTranslator())
  219. def initializePlugin(mobject):
  220. mplugin = MFnPlugin(mobject)
  221. try:
  222. mplugin.registerFileTranslator(kPluginTranslatorTypeName, None, translatorCreator, kOptionScript, kDefaultOptionsString)
  223. except:
  224. sys.stderr.write('Failed to register translator: %s' % kPluginTranslatorTypeName)
  225. raise
  226. def uninitializePlugin(mobject):
  227. mplugin = MFnPlugin(mobject)
  228. try:
  229. mplugin.deregisterFileTranslator(kPluginTranslatorTypeName)
  230. except:
  231. sys.stderr.write('Failed to deregister translator: %s' % kPluginTranslatorTypeName)
  232. raise