123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- __author__ = 'Chris Lewis'
- __version__ = '0.1.0'
- __email__ = '[email protected]'
- import sys
- import json
- import maya.cmds as mc
- from maya.OpenMaya import *
- from maya.OpenMayaMPx import *
- kPluginTranslatorTypeName = 'Three.js'
- kOptionScript = 'ThreeJsExportScript'
- kDefaultOptionsString = '0'
- FLOAT_PRECISION = 8
- # adds decimal precision to JSON encoding
- class DecimalEncoder(json.JSONEncoder):
- def _iterencode(self, o, markers=None):
- if isinstance(o, float):
- s = str(o)
- if '.' in s and len(s[s.index('.'):]) > FLOAT_PRECISION - 1:
- s = '%.{0}f'.format(FLOAT_PRECISION) % o
- while '.' in s and s[-1] == '0':
- s = s[-2]
- return (s for s in [s])
- return super(DecimalEncoder, self)._iterencode(o, markers)
- class ThreeJsError(Exception):
- pass
- class ThreeJsWriter(object):
- def __init__(self):
- self.componentKeys = ['vertices', 'normals', 'colors', 'uvs', 'materials', 'faces']
- def _parseOptions(self, optionsString):
- self.options = dict([(x, False) for x in self.componentKeys])
- optionsString = optionsString[2:] # trim off the "0;" that Maya adds to the options string
- for option in optionsString.split(' '):
- self.options[option] = True
- def _updateOffsets(self):
- for key in self.componentKeys:
- if key == 'uvs':
- continue
- self.offsets[key] = len(getattr(self, key))
- for i in range(len(self.uvs)):
- self.offsets['uvs'][i] = len(self.uvs[i])
- def _getTypeBitmask(self, options):
- bitmask = 0
- if options['materials']:
- bitmask |= 2
- if options['uvs']:
- bitmask |= 8
- if options['normals']:
- bitmask |= 32
- if options['colors']:
- bitmask |= 128
- return bitmask
- def _exportMesh(self, dagPath, component):
- mesh = MFnMesh(dagPath)
- options = self.options.copy()
- self._updateOffsets()
- # export vertex data
- if options['vertices']:
- try:
- iterVerts = MItMeshVertex(dagPath, component)
- while not iterVerts.isDone():
- point = iterVerts.position(MSpace.kWorld)
- self.vertices += [point.x, point.y, point.z]
- iterVerts.next()
- except:
- options['vertices'] = False
- # export material data
- # TODO: actually parse material data
- materialIndices = MIntArray()
- if options['materials']:
- try:
- shaders = MObjectArray()
- mesh.getConnectedShaders(0, shaders, materialIndices)
- while len(self.materials) < shaders.length():
- self.materials.append({}) # placeholder material definition
- except:
- self.materials = [{}]
- # export uv data
- if options['uvs']:
- try:
- uvLayers = []
- mesh.getUVSetNames(uvLayers)
- while len(uvLayers) > len(self.uvs):
- self.uvs.append([])
- self.offsets['uvs'].append(0)
- for i, layer in enumerate(uvLayers):
- uList = MFloatArray()
- vList = MFloatArray()
- mesh.getUVs(uList, vList, layer)
- for j in xrange(uList.length()):
- self.uvs[i] += [uList[j], vList[j]]
- except:
- options['uvs'] = False
- # export normal data
- if options['normals']:
- try:
- normals = MFloatVectorArray()
- mesh.getNormals(normals, MSpace.kWorld)
- for i in xrange(normals.length()):
- point = normals[i]
- self.normals += [point.x, point.y, point.z]
- except:
- options['normals'] = False
- # export color data
- if options['colors']:
- try:
- colors = MColorArray()
- mesh.getColors(colors)
- for i in xrange(colors.length()):
- color = colors[i]
- # uncolored vertices are set to (-1, -1, -1). Clamps colors to (0, 0, 0).
- self.colors += [max(color.r, 0), max(color.g, 0), max(color.b, 0)]
- except:
- options['colors'] = False
- # export face data
- if not options['vertices']:
- return
- bitmask = self._getTypeBitmask(options)
- iterPolys = MItMeshPolygon(dagPath, component)
- while not iterPolys.isDone():
- self.faces.append(bitmask)
- # export face vertices
- verts = MIntArray()
- iterPolys.getVertices(verts)
- for i in xrange(verts.length()):
- self.faces.append(verts[i] + self.offsets['vertices'])
- # export face vertex materials
- if options['materials']:
- if materialIndices.length():
- self.faces.append(materialIndices[iterPolys.index()])
- # export face vertex uvs
- if options['uvs']:
- util = MScriptUtil()
- uvPtr = util.asIntPtr()
- for i, layer in enumerate(uvLayers):
- for j in xrange(verts.length()):
- iterPolys.getUVIndex(j, uvPtr, layer)
- uvIndex = util.getInt(uvPtr)
- self.faces.append(uvIndex + self.offsets['uvs'][i])
- # export face vertex normals
- if options['normals']:
- for i in xrange(3):
- normalIndex = iterPolys.normalIndex(i)
- self.faces.append(normalIndex + self.offsets['normals'])
- # export face vertex colors
- if options['colors']:
- colors = MIntArray()
- iterPolys.getColorIndices(colors)
- for i in xrange(colors.length()):
- self.faces.append(colors[i] + self.offsets['colors'])
- iterPolys.next()
- def _getMeshes(self, nodes):
- meshes = []
- for node in nodes:
- if mc.nodeType(node) == 'mesh':
- meshes.append(node)
- else:
- for child in mc.listRelatives(node, s=1):
- if mc.nodeType(child) == 'mesh':
- meshes.append(child)
- return meshes
- def _exportMeshes(self):
- # export all
- if self.accessMode == MPxFileTranslator.kExportAccessMode:
- mc.select(self._getMeshes(mc.ls(typ='mesh')))
- # export selection
- elif self.accessMode == MPxFileTranslator.kExportActiveAccessMode:
- mc.select(self._getMeshes(mc.ls(sl=1)))
- else:
- raise ThreeJsError('Unsupported access mode: {0}'.format(self.accessMode))
- dups = [mc.duplicate(mesh)[0] for mesh in mc.ls(sl=1)]
- combined = mc.polyUnite(dups, mergeUVSets=1, ch=0) if len(dups) > 1 else dups[0]
- mc.polyTriangulate(combined)
- mc.select(combined)
- sel = MSelectionList()
- MGlobal.getActiveSelectionList(sel)
- mDag = MDagPath()
- mComp = MObject()
- sel.getDagPath(0, mDag, mComp)
- self._exportMesh(mDag, mComp)
- mc.delete(combined)
- def write(self, path, optionString, accessMode):
- self.path = path
- self._parseOptions(optionString)
- self.accessMode = accessMode
- self.root = dict(metadata=dict(formatVersion=3))
- self.offsets = dict()
- for key in self.componentKeys:
- setattr(self, key, [])
- self.offsets[key] = 0
- self.offsets['uvs'] = []
- self.uvs = []
-
- self._exportMeshes()
- # add the component buffers to the root JSON object
- for key in self.componentKeys:
- buffer_ = getattr(self, key)
- if buffer_:
- self.root[key] = buffer_
- # materials are required for parsing
- if not self.root.has_key('materials'):
- self.root['materials'] = [{}]
- # write the file
- with file(self.path, 'w') as f:
- f.write(json.dumps(self.root, separators=(',',':'), cls=DecimalEncoder))
- class ThreeJsTranslator(MPxFileTranslator):
- def __init__(self):
- MPxFileTranslator.__init__(self)
- def haveWriteMethod(self):
- return True
- def filter(self):
- return '*.js'
- def defaultExtension(self):
- return 'js'
- def writer(self, fileObject, optionString, accessMode):
- path = fileObject.fullName()
- writer = ThreeJsWriter()
- writer.write(path, optionString, accessMode)
- def translatorCreator():
- return asMPxPtr(ThreeJsTranslator())
- def initializePlugin(mobject):
- mplugin = MFnPlugin(mobject)
- try:
- mplugin.registerFileTranslator(kPluginTranslatorTypeName, None, translatorCreator, kOptionScript, kDefaultOptionsString)
- except:
- sys.stderr.write('Failed to register translator: %s' % kPluginTranslatorTypeName)
- raise
-
- def uninitializePlugin(mobject):
- mplugin = MFnPlugin(mobject)
- try:
- mplugin.deregisterFileTranslator(kPluginTranslatorTypeName)
- except:
- sys.stderr.write('Failed to deregister translator: %s' % kPluginTranslatorTypeName)
- raise
|