2
0

threeJsFileTranslator.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  1. __author__ = 'Sean Griffin'
  2. __version__ = '1.0.0'
  3. __email__ = '[email protected]'
  4. import sys
  5. import os.path
  6. import json
  7. import shutil
  8. from pymel.core import *
  9. from maya.OpenMaya import *
  10. from maya.OpenMayaMPx import *
  11. kPluginTranslatorTypeName = 'Three.js'
  12. kOptionScript = 'ThreeJsExportScript'
  13. kDefaultOptionsString = '0'
  14. FLOAT_PRECISION = 8
  15. class ThreeJsWriter(object):
  16. def __init__(self):
  17. self.componentKeys = ['vertices', 'normals', 'colors', 'uvs', 'faces',
  18. 'materials', 'colorMaps', 'specularMaps', 'bumpMaps', 'copyTextures',
  19. 'bones', 'skeletalAnim', 'bakeAnimations', 'prettyOutput']
  20. def write(self, path, optionString, accessMode):
  21. self.path = path
  22. self.accessMode = accessMode
  23. self._parseOptions(optionString)
  24. self.verticeOffset = 0
  25. self.uvOffset = 0
  26. self.normalOffset = 0
  27. self.vertices = []
  28. self.materials = []
  29. self.faces = []
  30. self.normals = []
  31. self.uvs = []
  32. self.morphTargets = []
  33. self.bones = []
  34. self.animations = []
  35. self.skinIndices = []
  36. self.skinWeights = []
  37. # Materials are used when exporting faces, so do them first.
  38. if self.options["materials"]:
  39. print("exporting materials")
  40. self._exportMaterials()
  41. print("exporting meshes")
  42. self._exportMeshes()
  43. if not self.accessMode == MPxFileTranslator.kExportActiveAccessMode :
  44. if self.options["bakeAnimations"]:
  45. print("exporting animations")
  46. self._exportAnimations()
  47. self._goToFrame(self.options["startFrame"])
  48. if self.options["bones"]:
  49. print("exporting bones")
  50. select(map(lambda m: m.getParent(), ls(type='mesh')))
  51. runtime.GoToBindPose()
  52. self._exportBones()
  53. print("exporting skins")
  54. self._exportSkins()
  55. if self.options["skeletalAnim"]:
  56. print("exporting keyframe animations")
  57. self._exportKeyframeAnimations()
  58. print("writing file")
  59. output = {
  60. 'metadata': {
  61. 'formatVersion': 3.1,
  62. 'generatedBy': 'Maya Exporter'
  63. },
  64. 'vertices': self.vertices,
  65. 'uvs': [self.uvs],
  66. 'faces': self.faces,
  67. 'normals': self.normals,
  68. 'materials': self.materials,
  69. }
  70. if not self.accessMode == MPxFileTranslator.kExportActiveAccessMode :
  71. if self.options['bakeAnimations']:
  72. output['morphTargets'] = self.morphTargets
  73. if self.options['bones']:
  74. output['bones'] = self.bones
  75. output['skinIndices'] = self.skinIndices
  76. output['skinWeights'] = self.skinWeights
  77. output['influencesPerVertex'] = self.options["influencesPerVertex"]
  78. if self.options['skeletalAnim']:
  79. output['animations'] = self.animations
  80. with file(path, 'w') as f:
  81. if self.options['prettyOutput']:
  82. f.write(json.dumps(output, indent=4, separators=(", ", ": ")))
  83. else:
  84. f.write(json.dumps(output, separators=(",",":")))
  85. def _allMeshes(self):
  86. if not self.accessMode == MPxFileTranslator.kExportActiveAccessMode :
  87. print("*** Exporting ALL (NEW) ***")
  88. self.__allMeshes = filter(lambda m: len(m.listConnections()) > 0, ls(type='mesh'))
  89. else :
  90. print("### Exporting SELECTED ###")
  91. self.__allMeshes = ls(selection=True)
  92. return self.__allMeshes
  93. def _parseOptions(self, optionsString):
  94. self.options = dict([(x, False) for x in self.componentKeys])
  95. for key in self.componentKeys:
  96. self.options[key] = key in optionsString
  97. if self.options["bones"]:
  98. boneOptionsString = optionsString[optionsString.find("bones"):]
  99. boneOptions = boneOptionsString.split(' ')
  100. self.options["influencesPerVertex"] = int(boneOptions[1])
  101. if self.options["bakeAnimations"]:
  102. bakeAnimOptionsString = optionsString[optionsString.find("bakeAnimations"):]
  103. bakeAnimOptions = bakeAnimOptionsString.split(' ')
  104. self.options["startFrame"] = int(bakeAnimOptions[1])
  105. self.options["endFrame"] = int(bakeAnimOptions[2])
  106. self.options["stepFrame"] = int(bakeAnimOptions[3])
  107. def _exportMeshes(self):
  108. if self.options['vertices']:
  109. self._exportVertices()
  110. for mesh in self._allMeshes():
  111. self._exportMesh(mesh)
  112. def _exportMesh(self, mesh):
  113. print("Exporting " + mesh.name())
  114. if self.options['faces']:
  115. print("Exporting faces")
  116. self._exportFaces(mesh)
  117. self.verticeOffset += len(mesh.getPoints())
  118. self.uvOffset += mesh.numUVs()
  119. self.normalOffset += mesh.numNormals()
  120. if self.options['normals']:
  121. print("Exporting normals")
  122. self._exportNormals(mesh)
  123. if self.options['uvs']:
  124. print("Exporting UVs")
  125. self._exportUVs(mesh)
  126. def _getMaterialIndex(self, face, mesh):
  127. if not hasattr(self, '_materialIndices'):
  128. self._materialIndices = dict([(mat['DbgName'], i) for i, mat in enumerate(self.materials)])
  129. if self.options['materials']:
  130. for engine in mesh.listConnections(type='shadingEngine'):
  131. if sets(engine, isMember=face) or sets(engine, isMember=mesh):
  132. for material in engine.listConnections(type='lambert'):
  133. if self._materialIndices.has_key(material.name()):
  134. return self._materialIndices[material.name()]
  135. return -1
  136. def _exportVertices(self):
  137. self.vertices += self._getVertices()
  138. def _exportAnimations(self):
  139. for frame in self._framesToExport():
  140. self._exportAnimationForFrame(frame)
  141. def _framesToExport(self):
  142. return range(self.options["startFrame"], self.options["endFrame"], self.options["stepFrame"])
  143. def _exportAnimationForFrame(self, frame):
  144. print("exporting frame " + str(frame))
  145. self._goToFrame(frame)
  146. self.morphTargets.append({
  147. 'name': "frame_" + str(frame),
  148. 'vertices': self._getVertices()
  149. })
  150. def _getVertices(self):
  151. return [coord for mesh in self._allMeshes() for point in mesh.getPoints(space='world') for coord in [round(point.x, FLOAT_PRECISION), round(point.y, FLOAT_PRECISION), round(point.z, FLOAT_PRECISION)]]
  152. def _goToFrame(self, frame):
  153. currentTime(frame)
  154. def _exportFaces(self, mesh):
  155. typeBitmask = self._getTypeBitmask()
  156. for face in mesh.faces:
  157. materialIndex = self._getMaterialIndex(face, mesh)
  158. hasMaterial = materialIndex != -1
  159. self._exportFaceBitmask(face, typeBitmask, hasMaterial=hasMaterial)
  160. self.faces += map(lambda x: x + self.verticeOffset, face.getVertices())
  161. if self.options['materials']:
  162. if hasMaterial:
  163. self.faces.append(materialIndex)
  164. if self.options['uvs'] and face.hasUVs():
  165. self.faces += map(lambda v: face.getUVIndex(v) + self.uvOffset, range(face.polygonVertexCount()))
  166. if self.options['normals']:
  167. self._exportFaceVertexNormals(face)
  168. def _exportFaceBitmask(self, face, typeBitmask, hasMaterial=True):
  169. vertexCount = face.polygonVertexCount()
  170. if vertexCount == 4:
  171. faceBitmask = 1
  172. elif vertexCount == 3:
  173. faceBitmask = 0
  174. else:
  175. raise ValueError('Faces must have 3 or 4 vertices')
  176. if hasMaterial:
  177. faceBitmask |= (1 << 1)
  178. if self.options['uvs'] and face.hasUVs():
  179. faceBitmask |= (1 << 3)
  180. self.faces.append(typeBitmask | faceBitmask)
  181. def _exportFaceVertexNormals(self, face):
  182. for i in range(face.polygonVertexCount()):
  183. self.faces.append(face.normalIndex(i) + self.normalOffset)
  184. def _exportNormals(self, mesh):
  185. for normal in mesh.getNormals():
  186. self.normals += [round(normal.x, FLOAT_PRECISION), round(normal.y, FLOAT_PRECISION), round(normal.z, FLOAT_PRECISION)]
  187. def _exportUVs(self, mesh):
  188. us, vs = mesh.getUVs()
  189. for i, u in enumerate(us):
  190. self.uvs.append(u)
  191. self.uvs.append(vs[i])
  192. def _getTypeBitmask(self):
  193. bitmask = 0
  194. if self.options['normals']:
  195. bitmask |= 32
  196. return bitmask
  197. def _exportMaterials(self):
  198. hist = listHistory( self._allMeshes(), f=1 )
  199. mats = listConnections( hist, type='lambert' )
  200. for mat in mats:
  201. print("material: " + mat)
  202. self.materials.append(self._exportMaterial(mat))
  203. def _exportMaterial(self, mat):
  204. result = {
  205. "DbgName": mat.name(),
  206. "blending": "NormalBlending",
  207. "colorDiffuse": map(lambda i: i * mat.getDiffuseCoeff(), mat.getColor().rgb),
  208. "depthTest": True,
  209. "depthWrite": True,
  210. "shading": mat.__class__.__name__,
  211. "opacity": 1.0 - mat.getTransparency().r,
  212. "transparent": mat.getTransparency().r != 0.0,
  213. "vertexColors": False
  214. }
  215. if isinstance(mat, nodetypes.Phong):
  216. result["colorSpecular"] = mat.getSpecularColor().rgb
  217. result["reflectivity"] = mat.getReflectivity()
  218. result["specularCoef"] = mat.getCosPower()
  219. if self.options["specularMaps"]:
  220. self._exportSpecularMap(result, mat)
  221. if self.options["bumpMaps"]:
  222. self._exportBumpMap(result, mat)
  223. if self.options["colorMaps"]:
  224. self._exportColorMap(result, mat)
  225. return result
  226. def _exportBumpMap(self, result, mat):
  227. for bump in mat.listConnections(type='bump2d'):
  228. for f in bump.listConnections(type='file'):
  229. result["mapNormalFactor"] = 1
  230. self._exportFile(result, f, "Normal")
  231. def _exportColorMap(self, result, mat):
  232. for f in mat.attr('color').inputs():
  233. result["colorDiffuse"] = f.attr('defaultColor').get()
  234. self._exportFile(result, f, "Diffuse")
  235. def _exportSpecularMap(self, result, mat):
  236. for f in mat.attr('specularColor').inputs():
  237. result["colorSpecular"] = f.attr('defaultColor').get()
  238. self._exportFile(result, f, "Specular")
  239. def _exportFile(self, result, mapFile, mapType):
  240. src = mapFile.ftn.get()
  241. targetDir = os.path.dirname(self.path)
  242. fName = os.path.basename(src)
  243. if self.options['copyTextures']:
  244. shutil.copy2(src, os.path.join(targetDir, fName))
  245. result["map" + mapType] = fName
  246. result["map" + mapType + "Repeat"] = [1, 1]
  247. result["map" + mapType + "Wrap"] = ["repeat", "repeat"]
  248. result["map" + mapType + "Anisotropy"] = 4
  249. def _exportBones(self):
  250. hist = listHistory( self._allMeshes(), f=1 )
  251. joints = listConnections( hist, type="joint")
  252. for joint in joints:
  253. if joint.getParent():
  254. parentIndex = self._indexOfJoint(joint.getParent().name())
  255. else:
  256. parentIndex = -1
  257. rotq = joint.getRotation(quaternion=True) * joint.getOrientation()
  258. pos = joint.getTranslation()
  259. self.bones.append({
  260. "parent": parentIndex,
  261. "name": joint.name(),
  262. "pos": self._roundPos(pos),
  263. "rotq": self._roundQuat(rotq)
  264. })
  265. def _indexOfJoint(self, name):
  266. if not hasattr(self, '_jointNames'):
  267. self._jointNames = dict([(joint.name(), i) for i, joint in enumerate(ls(type='joint'))])
  268. if name in self._jointNames:
  269. return self._jointNames[name]
  270. else:
  271. return -1
  272. def _exportKeyframeAnimations(self):
  273. hierarchy = []
  274. i = -1
  275. frameRate = FramesPerSecond(currentUnit(query=True, time=True)).value()
  276. hist = listHistory( self._allMeshes(), f=1 )
  277. joints = listConnections( hist, type="joint")
  278. for joint in joints:
  279. hierarchy.append({
  280. "parent": i,
  281. "keys": self._getKeyframes(joint, frameRate)
  282. })
  283. i += 1
  284. self.animations.append({
  285. "name": "skeletalAction.001",
  286. "length": (playbackOptions(maxTime=True, query=True) - playbackOptions(minTime=True, query=True)) / frameRate,
  287. "fps": 1,
  288. "hierarchy": hierarchy
  289. })
  290. def _getKeyframes(self, joint, frameRate):
  291. firstFrame = playbackOptions(minTime=True, query=True)
  292. lastFrame = playbackOptions(maxTime=True, query=True)
  293. frames = sorted(list(set(keyframe(joint, query=True) + [firstFrame, lastFrame])))
  294. keys = []
  295. print("joint " + joint.name() + " has " + str(len(frames)) + " keyframes")
  296. for frame in frames:
  297. self._goToFrame(frame)
  298. keys.append(self._getCurrentKeyframe(joint, frame, frameRate))
  299. return keys
  300. def _getCurrentKeyframe(self, joint, frame, frameRate):
  301. pos = joint.getTranslation()
  302. rot = joint.getRotation(quaternion=True) * joint.getOrientation()
  303. return {
  304. 'time': (frame - playbackOptions(minTime=True, query=True)) / frameRate,
  305. 'pos': self._roundPos(pos),
  306. 'rot': self._roundQuat(rot),
  307. 'scl': [1,1,1]
  308. }
  309. def _roundPos(self, pos):
  310. return map(lambda x: round(x, FLOAT_PRECISION), [pos.x, pos.y, pos.z])
  311. def _roundQuat(self, rot):
  312. return map(lambda x: round(x, FLOAT_PRECISION), [rot.x, rot.y, rot.z, rot.w])
  313. def _exportSkins(self):
  314. for mesh in self._allMeshes():
  315. print("exporting skins for mesh: " + mesh.name())
  316. hist = listHistory( mesh, f=1 )
  317. skins = listConnections( hist, type='skinCluster')
  318. if len(skins) > 0:
  319. print("mesh has " + str(len(skins)) + " skins")
  320. skin = skins[0]
  321. joints = skin.influenceObjects()
  322. for weights in skin.getWeights(mesh.vtx):
  323. numWeights = 0
  324. for i in range(0, len(weights)):
  325. if weights[i] > 0:
  326. self.skinWeights.append(weights[i])
  327. self.skinIndices.append(self._indexOfJoint(joints[i].name()))
  328. numWeights += 1
  329. if numWeights > self.options["influencesPerVertex"]:
  330. raise Exception("More than " + str(self.options["influencesPerVertex"]) + " influences on a vertex in " + mesh.name() + ".")
  331. for i in range(0, self.options["influencesPerVertex"] - numWeights):
  332. self.skinWeights.append(0)
  333. self.skinIndices.append(0)
  334. else:
  335. print("mesh has no skins, appending 0")
  336. for i in range(0, len(mesh.getPoints()) * self.options["influencesPerVertex"]):
  337. self.skinWeights.append(0)
  338. self.skinIndices.append(0)
  339. class NullAnimCurve(object):
  340. def getValue(self, index):
  341. return 0.0
  342. class ThreeJsTranslator(MPxFileTranslator):
  343. def __init__(self):
  344. MPxFileTranslator.__init__(self)
  345. def haveWriteMethod(self):
  346. return True
  347. def filter(self):
  348. return '*.json'
  349. def defaultExtension(self):
  350. return 'json'
  351. def writer(self, fileObject, optionString, accessMode):
  352. path = fileObject.fullName()
  353. writer = ThreeJsWriter()
  354. writer.write(path, optionString, accessMode)
  355. def translatorCreator():
  356. return asMPxPtr(ThreeJsTranslator())
  357. def initializePlugin(mobject):
  358. mplugin = MFnPlugin(mobject)
  359. try:
  360. mplugin.registerFileTranslator(kPluginTranslatorTypeName, None, translatorCreator, kOptionScript, kDefaultOptionsString)
  361. except:
  362. sys.stderr.write('Failed to register translator: %s' % kPluginTranslatorTypeName)
  363. raise
  364. def uninitializePlugin(mobject):
  365. mplugin = MFnPlugin(mobject)
  366. try:
  367. mplugin.deregisterFileTranslator(kPluginTranslatorTypeName)
  368. except:
  369. sys.stderr.write('Failed to deregister translator: %s' % kPluginTranslatorTypeName)
  370. raise
  371. class FramesPerSecond(object):
  372. MAYA_VALUES = {
  373. 'game': 15,
  374. 'film': 24,
  375. 'pal': 25,
  376. 'ntsc': 30,
  377. 'show': 48,
  378. 'palf': 50,
  379. 'ntscf': 60
  380. }
  381. def __init__(self, fpsString):
  382. self.fpsString = fpsString
  383. def value(self):
  384. if self.fpsString in FramesPerSecond.MAYA_VALUES:
  385. return FramesPerSecond.MAYA_VALUES[self.fpsString]
  386. else:
  387. return int(filter(lambda c: c.isdigit(), self.fpsString))
  388. ###################################################################
  389. ## The code below was taken from the Blender 3JS Exporter
  390. ## It's purpose is to fix the JSON output so that it does not
  391. ## put each array value on it's own line, which is ridiculous
  392. ## for this type of output.
  393. ###################################################################
  394. ROUND = 6
  395. ## THREE override function
  396. def _json_floatstr(o):
  397. if ROUND is not None:
  398. o = round(o, ROUND)
  399. return '%g' % o
  400. def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
  401. _key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot,
  402. ## HACK: hand-optimized bytecode; turn globals into locals
  403. ValueError=ValueError,
  404. basestring=basestring,
  405. dict=dict,
  406. float=float,
  407. id=id,
  408. int=int,
  409. isinstance=isinstance,
  410. list=list,
  411. long=long,
  412. str=str,
  413. tuple=tuple,
  414. ):
  415. def _iterencode_list(lst, _current_indent_level):
  416. if not lst:
  417. yield '[]'
  418. return
  419. if markers is not None:
  420. markerid = id(lst)
  421. if markerid in markers:
  422. raise ValueError("Circular reference detected")
  423. markers[markerid] = lst
  424. buf = '['
  425. #if _indent is not None:
  426. # _current_indent_level += 1
  427. # newline_indent = '\n' + (' ' * (_indent * _current_indent_level))
  428. # separator = _item_separator + newline_indent
  429. # buf += newline_indent
  430. #else:
  431. newline_indent = None
  432. separator = _item_separator
  433. first = True
  434. for value in lst:
  435. if first:
  436. first = False
  437. else:
  438. buf = separator
  439. if isinstance(value, basestring):
  440. yield buf + _encoder(value)
  441. elif value is None:
  442. yield buf + 'null'
  443. elif value is True:
  444. yield buf + 'true'
  445. elif value is False:
  446. yield buf + 'false'
  447. elif isinstance(value, (int, long)):
  448. yield buf + str(value)
  449. elif isinstance(value, float):
  450. yield buf + _floatstr(value)
  451. else:
  452. yield buf
  453. if isinstance(value, (list, tuple)):
  454. chunks = _iterencode_list(value, _current_indent_level)
  455. elif isinstance(value, dict):
  456. chunks = _iterencode_dict(value, _current_indent_level)
  457. else:
  458. chunks = _iterencode(value, _current_indent_level)
  459. for chunk in chunks:
  460. yield chunk
  461. if newline_indent is not None:
  462. _current_indent_level -= 1
  463. yield '\n' + (' ' * (_indent * _current_indent_level))
  464. yield ']'
  465. if markers is not None:
  466. del markers[markerid]
  467. def _iterencode_dict(dct, _current_indent_level):
  468. if not dct:
  469. yield '{}'
  470. return
  471. if markers is not None:
  472. markerid = id(dct)
  473. if markerid in markers:
  474. raise ValueError("Circular reference detected")
  475. markers[markerid] = dct
  476. yield '{'
  477. if _indent is not None:
  478. _current_indent_level += 1
  479. newline_indent = '\n' + (' ' * (_indent * _current_indent_level))
  480. item_separator = _item_separator + newline_indent
  481. yield newline_indent
  482. else:
  483. newline_indent = None
  484. item_separator = _item_separator
  485. first = True
  486. if _sort_keys:
  487. items = sorted(dct.items(), key=lambda kv: kv[0])
  488. else:
  489. items = dct.iteritems()
  490. for key, value in items:
  491. if isinstance(key, basestring):
  492. pass
  493. # JavaScript is weakly typed for these, so it makes sense to
  494. # also allow them. Many encoders seem to do something like this.
  495. elif isinstance(key, float):
  496. key = _floatstr(key)
  497. elif key is True:
  498. key = 'true'
  499. elif key is False:
  500. key = 'false'
  501. elif key is None:
  502. key = 'null'
  503. elif isinstance(key, (int, long)):
  504. key = str(key)
  505. elif _skipkeys:
  506. continue
  507. else:
  508. raise TypeError("key " + repr(key) + " is not a string")
  509. if first:
  510. first = False
  511. else:
  512. yield item_separator
  513. yield _encoder(key)
  514. yield _key_separator
  515. if isinstance(value, basestring):
  516. yield _encoder(value)
  517. elif value is None:
  518. yield 'null'
  519. elif value is True:
  520. yield 'true'
  521. elif value is False:
  522. yield 'false'
  523. elif isinstance(value, (int, long)):
  524. yield str(value)
  525. elif isinstance(value, float):
  526. yield _floatstr(value)
  527. else:
  528. if isinstance(value, (list, tuple)):
  529. chunks = _iterencode_list(value, _current_indent_level)
  530. elif isinstance(value, dict):
  531. chunks = _iterencode_dict(value, _current_indent_level)
  532. else:
  533. chunks = _iterencode(value, _current_indent_level)
  534. for chunk in chunks:
  535. yield chunk
  536. if newline_indent is not None:
  537. _current_indent_level -= 1
  538. yield '\n' + (' ' * (_indent * _current_indent_level))
  539. yield '}'
  540. if markers is not None:
  541. del markers[markerid]
  542. def _iterencode(o, _current_indent_level):
  543. if isinstance(o, basestring):
  544. yield _encoder(o)
  545. elif o is None:
  546. yield 'null'
  547. elif o is True:
  548. yield 'true'
  549. elif o is False:
  550. yield 'false'
  551. elif isinstance(o, (int, long)):
  552. yield str(o)
  553. elif isinstance(o, float):
  554. yield _floatstr(o)
  555. elif isinstance(o, (list, tuple)):
  556. for chunk in _iterencode_list(o, _current_indent_level):
  557. yield chunk
  558. elif isinstance(o, dict):
  559. for chunk in _iterencode_dict(o, _current_indent_level):
  560. yield chunk
  561. else:
  562. if markers is not None:
  563. markerid = id(o)
  564. if markerid in markers:
  565. raise ValueError("Circular reference detected")
  566. markers[markerid] = o
  567. o = _default(o)
  568. for chunk in _iterencode(o, _current_indent_level):
  569. yield chunk
  570. if markers is not None:
  571. del markers[markerid]
  572. return _iterencode
  573. # override the encoder
  574. json.encoder._make_iterencode = _make_iterencode