ThreeJSExporter.ms 18 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070
  1. -------------------------------------------------------------------------------------
  2. -- ThreeJSExporter.ms
  3. -- Exports geometry from 3ds max to Three.js models in ASCII JSON format v2
  4. -- By alteredq / http://alteredqualia.com
  5. -------------------------------------------------------------------------------------
  6. rollout ThreeJSExporter "ThreeJSExporter"
  7. (
  8. -- Variables
  9. local ostream,
  10. headerFormat = "// Converted from: %
  11. // vertices: %
  12. // normals: %
  13. // colors: %
  14. // uvs: %
  15. // triangles: %
  16. // materials: %
  17. //
  18. // Generated with 3ds max ThreeJSExporter
  19. // http://github.com/alteredq/three.js/blob/master/utils/exporters/max/ThreeJSExporter.ms
  20. ",
  21. vertexFormat = "%,%,%",
  22. vertexNormalFormat = "%,%,%",
  23. UVFormat = "%,%",
  24. triFormat = "%,%,%,%",
  25. triUVFormat = "%,%,%,%,%,%,%",
  26. triNFormat = "%,%,%,%,%,%,%",
  27. triUVNFormat = "%,%,%,%,%,%,%,%,%,%",
  28. footerFormat = "}\n\npostMessage( model );"
  29. -------------------------------------------------------------------------------------
  30. -- User interface
  31. group "ThreeJSExporter v0.5"
  32. (
  33. label msg "Exports selected meshes in Three.js ascii JSON format" align:#left
  34. hyperLink lab1 "Original source at GitHub" address:"https://github.com/alteredq/three.js/blob/master/utils/exporters/max/ThreeJSExporter.ms" color:(color 255 120 0) align:#left
  35. label dummy1 "--------------------------------------------------------" align:#left
  36. checkbox exportColor "Export vertex colors" checked:false enabled:true
  37. checkbox exportUv "Export uvs" checked:true enabled:true
  38. checkbox exportNormal "Export normals" checked:true enabled:true
  39. checkbox smoothNormal "Use vertex normals" checked:false enabled:true
  40. label dummy2 "--------------------------------------------------------" align:#left
  41. checkbox flipYZ "Flip YZ" checked:true enabled:true
  42. checkbox flipUV "Flip UV" checked:true enabled:true
  43. checkbox flipFace "Flip all faces" checked:false enabled:true
  44. checkbox autoflipFace "Try fixing flipped faces" checked:false enabled:true
  45. label dummy3 "--------------------------------------------------------" align:#left
  46. button btn_export "Export selected objects"
  47. )
  48. -------------------------------------------------------------------------------------
  49. -- Dump vertices
  50. function DumpVertices src =
  51. (
  52. Format "'vertices': [" to:ostream
  53. num = src.count
  54. if num > 0 then
  55. (
  56. for i = 1 to num do
  57. (
  58. vert = src[i]
  59. if flipYZ.checked then
  60. (
  61. x = vert.x
  62. y = vert.z
  63. z = vert.y
  64. z *= -1
  65. )
  66. else
  67. (
  68. x = vert.x
  69. y = vert.y
  70. z = vert.z
  71. )
  72. Format vertexFormat x y z to:ostream
  73. if i < num then Format "," to:ostream
  74. )
  75. )
  76. Format "],\n\n" to:ostream
  77. )
  78. -------------------------------------------------------------------------------------
  79. -- Dump colors
  80. function DumpColors src useColors =
  81. (
  82. Format "'colors': [" to:ostream
  83. num = src.count
  84. if num > 0 and useColors then
  85. (
  86. for i = 1 to num do
  87. (
  88. col = src[i]
  89. r = col.r as Integer
  90. g = col.g as Integer
  91. b = col.b as Integer
  92. hexNum = ( bit.shift r 16 ) + ( bit.shift g 8 ) + b
  93. hexColor = formattedPrint hexNum format:"#x"
  94. Format "%" hexColor to:ostream
  95. if i < num then Format "," to:ostream
  96. )
  97. )
  98. Format "],\n\n" to:ostream
  99. )
  100. -------------------------------------------------------------------------------------
  101. -- Dump normals
  102. function DumpNormals src =
  103. (
  104. Format "'normals': [" to:ostream
  105. num = src.count
  106. if num > 0 and exportNormal.checked then
  107. (
  108. for i = 1 to num do
  109. (
  110. normal = src[i]
  111. normal = normalize normal as point3
  112. if flipYZ.checked then
  113. (
  114. x = normal.x
  115. y = normal.z
  116. z = normal.y
  117. z *= -1
  118. )
  119. else
  120. (
  121. x = normal.x
  122. y = normal.y
  123. z = normal.z
  124. )
  125. Format vertexNormalFormat x y z to:ostream
  126. if i < num then Format "," to:ostream
  127. )
  128. )
  129. Format "],\n\n" to:ostream
  130. )
  131. -------------------------------------------------------------------------------------
  132. -- Dump uvs
  133. function DumpUvs src =
  134. (
  135. Format "'uvs': [[" to:ostream
  136. num = src.count
  137. if num > 0 and exportUv.checked then
  138. (
  139. for i = 1 to num do
  140. (
  141. uvw = src[i]
  142. u = uvw.x
  143. if flipUV.checked then
  144. (
  145. v = 1 - uvw.y
  146. )
  147. else
  148. (
  149. v = uvw.y
  150. )
  151. Format UVFormat u v to:ostream
  152. if i < num then Format "," to:ostream
  153. )
  154. )
  155. Format "]],\n\n" to:ostream
  156. )
  157. -------------------------------------------------------------------------------------
  158. -- Dump faces
  159. function DumpFaces src useColors =
  160. (
  161. Format "'faces': [" to:ostream
  162. num = src.count
  163. if num > 0 then
  164. (
  165. for i = 1 to num do
  166. (
  167. zface = src[i]
  168. fv = zface[1]
  169. fuv = zface[2]
  170. m = zface[3] - 1
  171. fc = zface[4]
  172. needsFlip = zface[5]
  173. isTriangle = true
  174. hasMaterial = true
  175. hasFaceUvs = false
  176. hasFaceVertexUvs = ((classof fuv == Point3) and exportUv.checked)
  177. hasFaceNormals = false
  178. hasFaceVertexNormals = (exportNormal.checked)
  179. hasFaceColors = false
  180. hasFaceVertexColors = ((classof fc == Point3) and useColors)
  181. faceType = 0
  182. faceType = bit.set faceType 1 (not isTriangle)
  183. faceType = bit.set faceType 2 hasMaterial
  184. faceType = bit.set faceType 3 hasFaceUvs
  185. faceType = bit.set faceType 4 hasFaceVertexUvs
  186. faceType = bit.set faceType 5 hasFaceNormals
  187. faceType = bit.set faceType 6 hasFaceVertexNormals
  188. faceType = bit.set faceType 7 hasFaceColors
  189. faceType = bit.set faceType 8 hasFaceVertexColors
  190. if i > 1 then
  191. (
  192. Format "," faceType to:ostream
  193. )
  194. Format "%" faceType to:ostream
  195. if isTriangle then
  196. (
  197. va = (fv.x - 1) as Integer
  198. vb = (fv.y - 1) as Integer
  199. vc = (fv.z - 1) as Integer
  200. if flipFace.checked or needsFlip then
  201. (
  202. tmp = vb
  203. vb = vc
  204. vc = tmp
  205. )
  206. Format ",%,%,%" va vb vc to:ostream
  207. if hasMaterial then
  208. (
  209. Format ",%" m to:ostream
  210. )
  211. if hasFaceVertexUvs then
  212. (
  213. ua = (fuv.x - 1) as Integer
  214. ub = (fuv.y - 1) as Integer
  215. uc = (fuv.z - 1) as Integer
  216. if flipFace.checked or needsFlip then
  217. (
  218. tmp = ub
  219. ub = uc
  220. uc = tmp
  221. )
  222. Format ",%,%,%" ua ub uc to:ostream
  223. )
  224. if hasFaceVertexNormals then
  225. (
  226. if smoothNormal.checked then
  227. (
  228. -- normals have the same indices as vertices
  229. na = va
  230. nb = vb
  231. nc = vc
  232. )
  233. else
  234. (
  235. -- normals have the same indices as face
  236. na = i - 1
  237. nb = na
  238. nc = na
  239. )
  240. if flipFace.checked or needsFlip then
  241. (
  242. tmp = nb
  243. nb = nc
  244. nc = tmp
  245. )
  246. Format ",%,%,%" na nb nc to:ostream
  247. )
  248. if hasFaceVertexColors then
  249. (
  250. ca = (fc.x - 1) as Integer
  251. cb = (fc.y - 1) as Integer
  252. cc = (fc.z - 1) as Integer
  253. if flipFace.checked or needsFlip then
  254. (
  255. tmp = cb
  256. cb = cc
  257. cc = tmp
  258. )
  259. Format ",%,%,%" ca cb cc to:ostream
  260. )
  261. )
  262. )
  263. )
  264. Format "]\n\n" to:ostream
  265. )
  266. -------------------------------------------------------------------------------------
  267. -- Dump color
  268. function DumpColor pcolor label =
  269. (
  270. r = pcolor.r / 255
  271. g = pcolor.g / 255
  272. b = pcolor.b / 255
  273. fr = formattedPrint r format:".4f"
  274. fg = formattedPrint g format:".4f"
  275. fb = formattedPrint b format:".4f"
  276. Format "'%' : [%, %, %],\n" label fr fg fb to:ostream
  277. )
  278. -------------------------------------------------------------------------------------
  279. -- Dump map
  280. function DumpMap pmap label =
  281. (
  282. if classof pmap == BitmapTexture then
  283. (
  284. bm = pmap.bitmap
  285. if bm != undefined then
  286. (
  287. fname = filenameFromPath bm.filename
  288. Format "'%' : '%',\n" label fname to:ostream
  289. )
  290. )
  291. )
  292. -------------------------------------------------------------------------------------
  293. -- Export materials
  294. function ExportMaterials zmaterials zcolors =
  295. (
  296. Format "'materials': [\n" to:ostream
  297. totalMaterials = zmaterials.count
  298. for i = 1 to totalMaterials do
  299. (
  300. mat = zmaterials[i]
  301. Format "{\n" to:ostream
  302. -- debug
  303. Format "'DbgIndex' : %,\n" (i-1) to:ostream
  304. if classof mat != BooleanClass then
  305. (
  306. useVertexColors = zcolors[i]
  307. Format "'DbgName' : '%',\n" mat.name to:ostream
  308. -- colors
  309. DumpColor mat.diffuse "colorDiffuse"
  310. DumpColor mat.ambient "colorAmbient"
  311. DumpColor mat.specular "colorSpecular"
  312. t = mat.opacity / 100
  313. s = mat.glossiness
  314. Format "'transparency' : %,\n" t to:ostream
  315. Format "'specularCoef' : %,\n" s to:ostream
  316. -- maps
  317. DumpMap mat.diffuseMap "mapDiffuse"
  318. DumpMap mat.ambientMap "mapAmbient"
  319. DumpMap mat.specularMap "mapSpecular"
  320. DumpMap mat.bumpMap "mapBump"
  321. DumpMap mat.opacityMap "mapAlpha"
  322. )
  323. else
  324. (
  325. Format "'DbgName' : '%',\n" "dummy" to:ostream
  326. DumpColor red "colorDiffuse"
  327. )
  328. Format "'vertexColors' : %\n" useVertexColors to:ostream
  329. Format "}" to:ostream
  330. if ( i < totalMaterials ) then Format "," to:ostream
  331. Format "\n\n" to:ostream
  332. )
  333. Format "],\n\n" to:ostream
  334. )
  335. -------------------------------------------------------------------------------------
  336. -- Extract vertices from mesh
  337. function ExtractVertices obj whereto =
  338. (
  339. n = obj.numVerts
  340. for i = 1 to n do
  341. (
  342. v = GetVert obj i
  343. append whereto v
  344. )
  345. )
  346. -------------------------------------------------------------------------------------
  347. -- Extract vertex colors from mesh
  348. function ExtractColors obj whereto =
  349. (
  350. nColors = GetNumCPVVerts obj
  351. if nColors > 0 then
  352. (
  353. for i = 1 to nColors do
  354. (
  355. c = GetVertColor obj i
  356. append whereto c
  357. )
  358. )
  359. )
  360. -------------------------------------------------------------------------------------
  361. -- Extract normals from mesh
  362. function ExtractNormals obj whereto needsFlip =
  363. (
  364. if smoothNormal.checked then
  365. (
  366. num = obj.numVerts
  367. for i = 1 to num do
  368. (
  369. n = GetNormal obj i
  370. if flipFace.checked or needsFlip then
  371. (
  372. n.x *= -1
  373. n.y *= -1
  374. n.z *= -1
  375. )
  376. append whereto n
  377. )
  378. )
  379. else
  380. (
  381. num = obj.numFaces
  382. for i = 1 to num do
  383. (
  384. n = GetFaceNormal obj i
  385. if flipFace.checked or needsFlip then
  386. (
  387. n.x *= -1
  388. n.y *= -1
  389. n.z *= -1
  390. )
  391. append whereto n
  392. )
  393. )
  394. )
  395. -------------------------------------------------------------------------------------
  396. -- Extract uvs from mesh
  397. function ExtractUvs obj whereto =
  398. (
  399. n = obj.numTVerts
  400. for i = 1 to n do
  401. (
  402. v = GetTVert obj i
  403. append whereto v
  404. )
  405. )
  406. -------------------------------------------------------------------------------------
  407. -- Extract faces from mesh
  408. function ExtractFaces objMesh objMaterial whereto allMaterials needsFlip hasVColors offsetVert offsetUv offsetColor =
  409. (
  410. n = objMesh.numFaces
  411. hasUVs = objMesh.numTVerts > 0
  412. useMultiMaterial = false
  413. materialIDList = #()
  414. materialClass = classof objMaterial
  415. if materialClass == StandardMaterial then
  416. (
  417. fm = findItem allMaterials objMaterial
  418. )
  419. else if materialClass == MultiMaterial then
  420. (
  421. useMultiMaterial = true
  422. for i = 1 to n do
  423. (
  424. mID = GetFaceMatID objMesh i
  425. materialIndex = findItem objMaterial.materialIDList mID
  426. if materialIndex > 0 then
  427. (
  428. subMaterial = objMaterial.materialList[materialIndex]
  429. mMergedIndex = findItem allMaterials subMaterial
  430. if mMergedIndex > 0 then
  431. (
  432. materialIDList[mID] = mMergedIndex
  433. )
  434. else
  435. (
  436. materialIDList[mID] = findItem allMaterials false
  437. )
  438. )
  439. else
  440. (
  441. materialIDList[mID] = findItem allMaterials false
  442. )
  443. )
  444. )
  445. else
  446. (
  447. -- undefined material
  448. fm = findItem allMaterials false
  449. )
  450. for i = 1 to n do
  451. (
  452. zface = #()
  453. fv = GetFace objMesh i
  454. fv.x += offsetVert
  455. fv.y += offsetVert
  456. fv.z += offsetVert
  457. if useMultiMaterial then
  458. (
  459. mID = GetFaceMatID objMesh i
  460. fm = materialIDList[mID]
  461. )
  462. if hasUVs then
  463. (
  464. fuv = GetTVFace objMesh i
  465. fuv.x += offsetUv
  466. fuv.y += offsetUv
  467. fuv.z += offsetUv
  468. )
  469. else
  470. (
  471. fuv = false
  472. )
  473. if hasVColors then
  474. (
  475. fc = GetVCFace objMesh i
  476. fc.x += offsetColor
  477. fc.y += offsetColor
  478. fc.z += offsetColor
  479. )
  480. else
  481. (
  482. fc = false
  483. )
  484. append zface fv
  485. append zface fuv
  486. append zface fm
  487. append zface fc
  488. append zface needsFlip
  489. append whereto zface
  490. )
  491. )
  492. -------------------------------------------------------------------------------------
  493. -- Extract materials from eventual multi-material
  494. function ExtractMaterials objMesh objMaterial whereto wheretoColors zname hasVColors =
  495. (
  496. materialClass = classof objMaterial
  497. if materialClass == StandardMaterial then
  498. (
  499. if ( findItem whereto objMaterial ) == 0 then
  500. (
  501. append whereto objMaterial
  502. append wheretoColors hasVColors
  503. )
  504. )
  505. else if materialClass == MultiMaterial then
  506. (
  507. n = objMesh.numFaces
  508. for i = 1 to n do
  509. (
  510. mID = getFaceMatId objMesh i
  511. materialIndex = findItem objMaterial.materialIDList mID
  512. if materialIndex > 0 then
  513. (
  514. subMaterial = objMaterial.materialList[materialIndex]
  515. if ( findItem whereto subMaterial ) == 0 then
  516. (
  517. append whereto subMaterial
  518. append wheretoColors hasVColors
  519. )
  520. )
  521. )
  522. )
  523. else
  524. (
  525. -- unknown or undefined material
  526. append whereto false
  527. append wheretoColors false
  528. )
  529. )
  530. -------------------------------------------------------------------------------------
  531. -- Hack to figure out if normals are messed up
  532. function NeedsFaceFlip node =
  533. (
  534. needsFlip = false
  535. local tmp = Snapshot node
  536. face_normal = normalize ( getfacenormal tmp 1 )
  537. face = getface tmp 1
  538. va = getvert tmp face[1]
  539. vb = getvert tmp face[2]
  540. vc = getvert tmp face[3]
  541. computed_normal = normalize ( cross (vc - vb) (va - vb) )
  542. if distance computed_normal face_normal > 0.1 then needsFlip = true
  543. delete tmp
  544. return needsFlip
  545. )
  546. -------------------------------------------------------------------------------------
  547. -- Extract only things that either already are or can be converted to meshes
  548. function ExtractMesh node =
  549. (
  550. if SuperClassOf node == GeometryClass then
  551. (
  552. needsFlip = false
  553. hasVColors = false
  554. zmesh = SnapshotAsMesh node
  555. if autoflipFace.checked then
  556. (
  557. needsFlip = NeedsFaceFlip node
  558. )
  559. if exportColor.checked and ( getNumCPVVerts zmesh ) > 0 then
  560. (
  561. hasVColors = true
  562. )
  563. return #( zmesh, node.name, node.material, needsFlip, hasVColors )
  564. )
  565. -- Not geometry ... could be a camera, light, etc.
  566. return #( false, node.name, 0, false, false )
  567. )
  568. -------------------------------------------------------------------------------------
  569. -- Export scene
  570. function ExportScene =
  571. (
  572. -- Extract meshes
  573. meshObjects = #()
  574. mergedVertices = #()
  575. mergedNormals = #()
  576. mergedColors = #()
  577. mergedUvs = #()
  578. mergedFaces = #()
  579. mergedMaterials = #()
  580. mergedMaterialsColors = #()
  581. sceneHasVColors = false
  582. for obj in selection do
  583. (
  584. result = ExtractMesh obj
  585. meshObj = result[1]
  586. if ClassOf meshObj == TriMesh then
  587. (
  588. meshName = result[2]
  589. meshMaterial = result[3]
  590. needsFlip = result[4]
  591. hasVColors = result[5]
  592. sceneHasVColors = sceneHasVColors or hasVColors
  593. append meshObjects result
  594. vertexOffset = mergedVertices.count
  595. uvOffset = mergedUvs.count
  596. colorOffset = mergedColors.count
  597. ExtractMaterials meshObj meshMaterial mergedMaterials mergedMaterialsColors meshName hasVColors
  598. ExtractVertices meshObj mergedVertices
  599. ExtractNormals meshObj mergedNormals needsFlip
  600. ExtractColors meshObj mergedColors
  601. ExtractUvs meshObj mergedUvs
  602. ExtractFaces meshObj meshMaterial mergedFaces mergedMaterials needsFlip hasVColors vertexOffset uvOffset colorOffset
  603. )
  604. )
  605. totalVertices = mergedVertices.count
  606. totalFaces = mergedFaces.count
  607. totalMaterials = mergedMaterials.count
  608. totalColors = 0
  609. totalNormals = 0
  610. totalUvs = 0
  611. useColors = false
  612. if sceneHasVColors and exportColor.checked then
  613. (
  614. totalColors = mergedColors.count
  615. useColors = true
  616. )
  617. if exportNormal.checked then
  618. (
  619. totalNormals = mergedNormals.count
  620. )
  621. if exportUv.checked then
  622. (
  623. totalUvs = mergedUvs.count
  624. )
  625. -- Dump header
  626. Format headerFormat maxFileName totalVertices totalNormals totalColors totalUvs totalFaces totalMaterials to:ostream
  627. -- Dump objects (debug)
  628. Format "// Source objects:\n\n" to:ostream
  629. i = 0
  630. for obj in meshObjects do
  631. (
  632. meshName = obj[2]
  633. Format "// %: %\n" i meshName to:ostream
  634. i += 1
  635. )
  636. -- Dump model
  637. Format "\n\nvar model = {\n\n" to:ostream
  638. Format "'version' :2,\n\n" to:ostream
  639. -- Dump all materials in the scene
  640. ExportMaterials mergedMaterials mergedMaterialsColors
  641. -- Dump merged data from all selected geometries
  642. DumpVertices mergedVertices
  643. DumpNormals mergedNormals
  644. DumpColors mergedColors useColors
  645. DumpUvs mergedUvs
  646. DumpFaces mergedFaces useColors
  647. -- Dump footer
  648. Format footerFormat to:ostream
  649. )
  650. -------------------------------------------------------------------------------------
  651. -- Open and prepare a file handle for writing
  652. function GetSaveFileStream =
  653. (
  654. zname = getFilenameFile maxFileName
  655. zname += ".js"
  656. fname = GetSaveFileName filename:zname types:"JavaScript file (*.js)|*.js|All Files(*.*)|*.*|"
  657. if fname == undefined then
  658. (
  659. return undefined
  660. )
  661. ostream = CreateFile fname
  662. if ostream == undefined then
  663. (
  664. MessageBox "Couldn't open file for writing !"
  665. return undefined
  666. )
  667. return ostream
  668. )
  669. -------------------------------------------------------------------------------------
  670. -- Export button click handler
  671. on btn_export pressed do
  672. (
  673. ostream = GetSaveFileStream()
  674. if ostream != undefined then
  675. (
  676. ExportScene()
  677. close ostream
  678. )
  679. )
  680. )
  681. createDialog ThreeJSExporter width:300