threeJsFileTranslator.py 23 KB

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