slim_threejs_export.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946
  1. #!BPY
  2. '''
  3. Nothing fancy
  4. Just wrapping the Existing OBJ exporter in Blender 2.49 to do the obj export and conversion to three.js in one go
  5. '''
  6. """
  7. Name: 'three.js(slim) (.js)...'
  8. Blender: 249
  9. Group: 'Export'
  10. Tooltip: 'Save a three.js File'
  11. """
  12. __author__ = "Campbell Barton, Jiri Hnidek, Paolo Ciccone,George Profenza,AlteredQualia"
  13. __url__ = ['http://wiki.blender.org/index.php/Scripts/Manual/Export/wavefront_obj', 'www.blender.org', 'blenderartists.org','AlteredQualia http://alteredqualia.com','tomaterial.blogspot.com']
  14. __version__ = "1.22"
  15. __bpydoc__ = """\
  16. This script is an exporter to OBJ file format.
  17. Usage:
  18. Select the objects you wish to export and run this script from "File->Export" menu.
  19. Selecting the default options from the popup box will be good in most cases.
  20. All objects that can be represented as a mesh (mesh, curve, metaball, surface, text3d)
  21. will be exported as mesh data.
  22. """
  23. # ***** BEGIN GPL LICENSE BLOCK *****
  24. #
  25. # Script copyright (C) Campbell J Barton 2007-2009
  26. # - V1.22- bspline import/export added (funded by PolyDimensions GmbH)
  27. #
  28. # This program is free software; you can redistribute it and/or
  29. # modify it under the terms of the GNU General Public License
  30. # as published by the Free Software Foundation; either version 2
  31. # of the License, or (at your option) any later version.
  32. #
  33. # This program is distributed in the hope that it will be useful,
  34. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  35. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  36. # GNU General Public License for more details.
  37. #
  38. # You should have received a copy of the GNU General Public License
  39. # along with this program; if not, write to the Free Software Foundation,
  40. # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  41. #
  42. # ***** END GPL LICENCE BLOCK *****
  43. # --------------------------------------------------------------------------
  44. import Blender
  45. from Blender import Mesh, Scene, Window, sys, Image, Draw
  46. import BPyMesh
  47. import BPyObject
  48. import BPySys
  49. import BPyMessages
  50. import re,os,subprocess
  51. # Returns a tuple - path,extension.
  52. # 'hello.obj' > ('hello', '.obj')
  53. def splitExt(path):
  54. dotidx = path.rfind('.')
  55. if dotidx == -1:
  56. return path, ''
  57. else:
  58. return path[:dotidx], path[dotidx:]
  59. def fixName(name):
  60. if name == None:
  61. return 'None'
  62. else:
  63. return name.replace(' ', '_')
  64. # A Dict of Materials
  65. # (material.name, image.name):matname_imagename # matname_imagename has gaps removed.
  66. MTL_DICT = {}
  67. def write_mtl(filename):
  68. world = Blender.World.GetCurrent()
  69. if world:
  70. worldAmb = world.getAmb()
  71. else:
  72. worldAmb = (0,0,0) # Default value
  73. file = open(filename, "w")
  74. file.write('# Blender3D MTL File: %s\n' % Blender.Get('filename').split('\\')[-1].split('/')[-1])
  75. file.write('# Material Count: %i\n' % len(MTL_DICT))
  76. # Write material/image combinations we have used.
  77. for key, (mtl_mat_name, mat, img) in MTL_DICT.iteritems():
  78. # Get the Blender data for the material and the image.
  79. # Having an image named None will make a bug, dont do it :)
  80. file.write('newmtl %s\n' % mtl_mat_name) # Define a new material: matname_imgname
  81. if mat:
  82. file.write('Ns %.6f\n' % ((mat.getHardness()-1) * 1.9607843137254901) ) # Hardness, convert blenders 1-511 to MTL's
  83. file.write('Ka %.6f %.6f %.6f\n' % tuple([c*mat.amb for c in worldAmb]) ) # Ambient, uses mirror colour,
  84. file.write('Kd %.6f %.6f %.6f\n' % tuple([c*mat.ref for c in mat.rgbCol]) ) # Diffuse
  85. file.write('Ks %.6f %.6f %.6f\n' % tuple([c*mat.spec for c in mat.specCol]) ) # Specular
  86. file.write('Ni %.6f\n' % mat.IOR) # Refraction index
  87. file.write('d %.6f\n' % mat.alpha) # Alpha (obj uses 'd' for dissolve)
  88. # 0 to disable lighting, 1 for ambient & diffuse only (specular color set to black), 2 for full lighting.
  89. if mat.getMode() & Blender.Material.Modes['SHADELESS']:
  90. file.write('illum 0\n') # ignore lighting
  91. elif mat.getSpec() == 0:
  92. file.write('illum 1\n') # no specular.
  93. else:
  94. file.write('illum 2\n') # light normaly
  95. else:
  96. #write a dummy material here?
  97. file.write('Ns 0\n')
  98. file.write('Ka %.6f %.6f %.6f\n' % tuple([c for c in worldAmb]) ) # Ambient, uses mirror colour,
  99. file.write('Kd 0.8 0.8 0.8\n')
  100. file.write('Ks 0.8 0.8 0.8\n')
  101. file.write('d 1\n') # No alpha
  102. file.write('illum 2\n') # light normaly
  103. # Write images!
  104. if img: # We have an image on the face!
  105. file.write('map_Kd %s\n' % img.filename.split('\\')[-1].split('/')[-1]) # Diffuse mapping image
  106. elif mat: # No face image. if we havea material search for MTex image.
  107. for mtex in mat.getTextures():
  108. if mtex and mtex.tex.type == Blender.Texture.Types.IMAGE:
  109. try:
  110. filename = mtex.tex.image.filename.split('\\')[-1].split('/')[-1]
  111. file.write('map_Kd %s\n' % filename) # Diffuse mapping image
  112. break
  113. except:
  114. # Texture has no image though its an image type, best ignore.
  115. pass
  116. file.write('\n\n')
  117. file.close()
  118. def copy_file(source, dest):
  119. file = open(source, 'rb')
  120. data = file.read()
  121. file.close()
  122. file = open(dest, 'wb')
  123. file.write(data)
  124. file.close()
  125. def copy_images(dest_dir):
  126. if dest_dir[-1] != sys.sep:
  127. dest_dir += sys.sep
  128. # Get unique image names
  129. uniqueImages = {}
  130. for matname, mat, image in MTL_DICT.itervalues(): # Only use image name
  131. # Get Texface images
  132. if image:
  133. uniqueImages[image] = image # Should use sets here. wait until Python 2.4 is default.
  134. # Get MTex images
  135. if mat:
  136. for mtex in mat.getTextures():
  137. if mtex and mtex.tex.type == Blender.Texture.Types.IMAGE:
  138. image_tex = mtex.tex.image
  139. if image_tex:
  140. try:
  141. uniqueImages[image_tex] = image_tex
  142. except:
  143. pass
  144. # Now copy images
  145. copyCount = 0
  146. for bImage in uniqueImages.itervalues():
  147. image_path = sys.expandpath(bImage.filename)
  148. if sys.exists(image_path):
  149. # Make a name for the target path.
  150. dest_image_path = dest_dir + image_path.split('\\')[-1].split('/')[-1]
  151. if not sys.exists(dest_image_path): # Image isnt alredy there
  152. print '\tCopying "%s" > "%s"' % (image_path, dest_image_path)
  153. copy_file(image_path, dest_image_path)
  154. copyCount+=1
  155. print '\tCopied %d images' % copyCount
  156. def test_nurbs_compat(ob):
  157. if ob.type != 'Curve':
  158. return False
  159. for nu in ob.data:
  160. if (not nu.knotsV) and nu.type != 1: # not a surface and not bezier
  161. return True
  162. return False
  163. def write_nurb(file, ob, ob_mat):
  164. tot_verts = 0
  165. cu = ob.data
  166. # use negative indices
  167. Vector = Blender.Mathutils.Vector
  168. for nu in cu:
  169. if nu.type==0: DEG_ORDER_U = 1
  170. else: DEG_ORDER_U = nu.orderU-1 # Tested to be correct
  171. if nu.type==1:
  172. print "\tWarning, bezier curve:", ob.name, "only poly and nurbs curves supported"
  173. continue
  174. if nu.knotsV:
  175. print "\tWarning, surface:", ob.name, "only poly and nurbs curves supported"
  176. continue
  177. if len(nu) <= DEG_ORDER_U:
  178. print "\tWarning, orderU is lower then vert count, skipping:", ob.name
  179. continue
  180. pt_num = 0
  181. do_closed = (nu.flagU & 1)
  182. do_endpoints = (do_closed==0) and (nu.flagU & 2)
  183. for pt in nu:
  184. pt = Vector(pt[0], pt[1], pt[2]) * ob_mat
  185. file.write('v %.6f %.6f %.6f\n' % (pt[0], pt[1], pt[2]))
  186. pt_num += 1
  187. tot_verts += pt_num
  188. file.write('g %s\n' % (fixName(ob.name))) # fixName(ob.getData(1)) could use the data name too
  189. file.write('cstype bspline\n') # not ideal, hard coded
  190. file.write('deg %d\n' % DEG_ORDER_U) # not used for curves but most files have it still
  191. curve_ls = [-(i+1) for i in xrange(pt_num)]
  192. # 'curv' keyword
  193. if do_closed:
  194. if DEG_ORDER_U == 1:
  195. pt_num += 1
  196. curve_ls.append(-1)
  197. else:
  198. pt_num += DEG_ORDER_U
  199. curve_ls = curve_ls + curve_ls[0:DEG_ORDER_U]
  200. file.write('curv 0.0 1.0 %s\n' % (' '.join( [str(i) for i in curve_ls] ))) # Blender has no U and V values for the curve
  201. # 'parm' keyword
  202. tot_parm = (DEG_ORDER_U + 1) + pt_num
  203. tot_parm_div = float(tot_parm-1)
  204. parm_ls = [(i/tot_parm_div) for i in xrange(tot_parm)]
  205. if do_endpoints: # end points, force param
  206. for i in xrange(DEG_ORDER_U+1):
  207. parm_ls[i] = 0.0
  208. parm_ls[-(1+i)] = 1.0
  209. file.write('parm u %s\n' % ' '.join( [str(i) for i in parm_ls] ))
  210. file.write('end\n')
  211. return tot_verts
  212. def write(filename, objects,\
  213. EXPORT_TRI=False, EXPORT_EDGES=False, EXPORT_NORMALS=False, EXPORT_NORMALS_HQ=False,\
  214. EXPORT_UV=True, EXPORT_MTL=True, EXPORT_COPY_IMAGES=False,\
  215. EXPORT_APPLY_MODIFIERS=True, EXPORT_ROTX90=True, EXPORT_BLEN_OBS=True,\
  216. EXPORT_GROUP_BY_OB=False, EXPORT_GROUP_BY_MAT=False, EXPORT_KEEP_VERT_ORDER=False,\
  217. EXPORT_POLYGROUPS=False, EXPORT_CURVE_AS_NURBS=True):
  218. '''
  219. Basic write function. The context and options must be alredy set
  220. This can be accessed externaly
  221. eg.
  222. write( 'c:\\test\\foobar.obj', Blender.Object.GetSelected() ) # Using default options.
  223. '''
  224. def veckey3d(v):
  225. return round(v.x, 6), round(v.y, 6), round(v.z, 6)
  226. def veckey2d(v):
  227. return round(v.x, 6), round(v.y, 6)
  228. def findVertexGroupName(face, vWeightMap):
  229. """
  230. Searches the vertexDict to see what groups is assigned to a given face.
  231. We use a frequency system in order to sort out the name because a given vetex can
  232. belong to two or more groups at the same time. To find the right name for the face
  233. we list all the possible vertex group names with their frequency and then sort by
  234. frequency in descend order. The top element is the one shared by the highest number
  235. of vertices is the face's group
  236. """
  237. weightDict = {}
  238. for vert in face:
  239. vWeights = vWeightMap[vert.index]
  240. for vGroupName, weight in vWeights:
  241. weightDict[vGroupName] = weightDict.get(vGroupName, 0) + weight
  242. if weightDict:
  243. alist = [(weight,vGroupName) for vGroupName, weight in weightDict.iteritems()] # sort least to greatest amount of weight
  244. alist.sort()
  245. return(alist[-1][1]) # highest value last
  246. else:
  247. return '(null)'
  248. print 'OBJ Export path: "%s"' % filename
  249. temp_mesh_name = '~tmp-mesh'
  250. time1 = sys.time()
  251. scn = Scene.GetCurrent()
  252. file = open(filename, "w")
  253. # Write Header
  254. file.write('# Blender3D v%s OBJ File: %s\n' % (Blender.Get('version'), Blender.Get('filename').split('/')[-1].split('\\')[-1] ))
  255. file.write('# www.blender3d.org\n')
  256. # Tell the obj file what material file to use.
  257. if EXPORT_MTL:
  258. mtlfilename = '%s.mtl' % '.'.join(filename.split('.')[:-1])
  259. file.write('mtllib %s\n' % ( mtlfilename.split('\\')[-1].split('/')[-1] ))
  260. # Get the container mesh. - used for applying modifiers and non mesh objects.
  261. containerMesh = meshName = tempMesh = None
  262. for meshName in Blender.NMesh.GetNames():
  263. if meshName.startswith(temp_mesh_name):
  264. tempMesh = Mesh.Get(meshName)
  265. if not tempMesh.users:
  266. containerMesh = tempMesh
  267. if not containerMesh:
  268. containerMesh = Mesh.New(temp_mesh_name)
  269. if EXPORT_ROTX90:
  270. mat_xrot90= Blender.Mathutils.RotationMatrix(-90, 4, 'x')
  271. del meshName
  272. del tempMesh
  273. # Initialize totals, these are updated each object
  274. totverts = totuvco = totno = 1
  275. face_vert_index = 1
  276. globalNormals = {}
  277. # Get all meshes
  278. for ob_main in objects:
  279. for ob, ob_mat in BPyObject.getDerivedObjects(ob_main):
  280. # Nurbs curve support
  281. if EXPORT_CURVE_AS_NURBS and test_nurbs_compat(ob):
  282. if EXPORT_ROTX90:
  283. ob_mat = ob_mat * mat_xrot90
  284. totverts += write_nurb(file, ob, ob_mat)
  285. continue
  286. # end nurbs
  287. # Will work for non meshes now! :)
  288. # getMeshFromObject(ob, container_mesh=None, apply_modifiers=True, vgroups=True, scn=None)
  289. me= BPyMesh.getMeshFromObject(ob, containerMesh, EXPORT_APPLY_MODIFIERS, EXPORT_POLYGROUPS, scn)
  290. if not me:
  291. continue
  292. if EXPORT_UV:
  293. faceuv= me.faceUV
  294. else:
  295. faceuv = False
  296. # We have a valid mesh
  297. if EXPORT_TRI and me.faces:
  298. # Add a dummy object to it.
  299. has_quads = False
  300. for f in me.faces:
  301. if len(f) == 4:
  302. has_quads = True
  303. break
  304. if has_quads:
  305. oldmode = Mesh.Mode()
  306. Mesh.Mode(Mesh.SelectModes['FACE'])
  307. me.sel = True
  308. tempob = scn.objects.new(me)
  309. me.quadToTriangle(0) # more=0 shortest length
  310. oldmode = Mesh.Mode(oldmode)
  311. scn.objects.unlink(tempob)
  312. Mesh.Mode(oldmode)
  313. # Make our own list so it can be sorted to reduce context switching
  314. faces = [ f for f in me.faces ]
  315. if EXPORT_EDGES:
  316. edges = me.edges
  317. else:
  318. edges = []
  319. if not (len(faces)+len(edges)+len(me.verts)): # Make sure there is somthing to write
  320. continue # dont bother with this mesh.
  321. if EXPORT_ROTX90:
  322. me.transform(ob_mat*mat_xrot90)
  323. else:
  324. me.transform(ob_mat)
  325. # High Quality Normals
  326. if EXPORT_NORMALS and faces:
  327. if EXPORT_NORMALS_HQ:
  328. BPyMesh.meshCalcNormals(me)
  329. else:
  330. # transforming normals is incorrect
  331. # when the matrix is scaled,
  332. # better to recalculate them
  333. me.calcNormals()
  334. # # Crash Blender
  335. #materials = me.getMaterials(1) # 1 == will return None in the list.
  336. materials = me.materials
  337. materialNames = []
  338. materialItems = materials[:]
  339. if materials:
  340. for mat in materials:
  341. if mat: # !=None
  342. materialNames.append(mat.name)
  343. else:
  344. materialNames.append(None)
  345. # Cant use LC because some materials are None.
  346. # materialNames = map(lambda mat: mat.name, materials) # Bug Blender, dosent account for null materials, still broken.
  347. # Possible there null materials, will mess up indicies
  348. # but at least it will export, wait until Blender gets fixed.
  349. materialNames.extend((16-len(materialNames)) * [None])
  350. materialItems.extend((16-len(materialItems)) * [None])
  351. # Sort by Material, then images
  352. # so we dont over context switch in the obj file.
  353. if EXPORT_KEEP_VERT_ORDER:
  354. pass
  355. elif faceuv:
  356. try: faces.sort(key = lambda a: (a.mat, a.image, a.smooth))
  357. except: faces.sort(lambda a,b: cmp((a.mat, a.image, a.smooth), (b.mat, b.image, b.smooth)))
  358. elif len(materials) > 1:
  359. try: faces.sort(key = lambda a: (a.mat, a.smooth))
  360. except: faces.sort(lambda a,b: cmp((a.mat, a.smooth), (b.mat, b.smooth)))
  361. else:
  362. # no materials
  363. try: faces.sort(key = lambda a: a.smooth)
  364. except: faces.sort(lambda a,b: cmp(a.smooth, b.smooth))
  365. # Set the default mat to no material and no image.
  366. contextMat = (0, 0) # Can never be this, so we will label a new material teh first chance we get.
  367. contextSmooth = None # Will either be true or false, set bad to force initialization switch.
  368. if EXPORT_BLEN_OBS or EXPORT_GROUP_BY_OB:
  369. name1 = ob.name
  370. name2 = ob.getData(1)
  371. if name1 == name2:
  372. obnamestring = fixName(name1)
  373. else:
  374. obnamestring = '%s_%s' % (fixName(name1), fixName(name2))
  375. if EXPORT_BLEN_OBS:
  376. file.write('o %s\n' % obnamestring) # Write Object name
  377. else: # if EXPORT_GROUP_BY_OB:
  378. file.write('g %s\n' % obnamestring)
  379. # Vert
  380. for v in me.verts:
  381. file.write('v %.6f %.6f %.6f\n' % tuple(v.co))
  382. # UV
  383. if faceuv:
  384. uv_face_mapping = [[0,0,0,0] for f in faces] # a bit of a waste for tri's :/
  385. uv_dict = {} # could use a set() here
  386. for f_index, f in enumerate(faces):
  387. for uv_index, uv in enumerate(f.uv):
  388. uvkey = veckey2d(uv)
  389. try:
  390. uv_face_mapping[f_index][uv_index] = uv_dict[uvkey]
  391. except:
  392. uv_face_mapping[f_index][uv_index] = uv_dict[uvkey] = len(uv_dict)
  393. file.write('vt %.6f %.6f\n' % tuple(uv))
  394. uv_unique_count = len(uv_dict)
  395. del uv, uvkey, uv_dict, f_index, uv_index
  396. # Only need uv_unique_count and uv_face_mapping
  397. # NORMAL, Smooth/Non smoothed.
  398. if EXPORT_NORMALS:
  399. for f in faces:
  400. if f.smooth:
  401. for v in f:
  402. noKey = veckey3d(v.no)
  403. if not globalNormals.has_key( noKey ):
  404. globalNormals[noKey] = totno
  405. totno +=1
  406. file.write('vn %.6f %.6f %.6f\n' % noKey)
  407. else:
  408. # Hard, 1 normal from the face.
  409. noKey = veckey3d(f.no)
  410. if not globalNormals.has_key( noKey ):
  411. globalNormals[noKey] = totno
  412. totno +=1
  413. file.write('vn %.6f %.6f %.6f\n' % noKey)
  414. if not faceuv:
  415. f_image = None
  416. if EXPORT_POLYGROUPS:
  417. # Retrieve the list of vertex groups
  418. vertGroupNames = me.getVertGroupNames()
  419. currentVGroup = ''
  420. # Create a dictionary keyed by face id and listing, for each vertex, the vertex groups it belongs to
  421. vgroupsMap = [[] for _i in xrange(len(me.verts))]
  422. for vertexGroupName in vertGroupNames:
  423. for vIdx, vWeight in me.getVertsFromGroup(vertexGroupName, 1):
  424. vgroupsMap[vIdx].append((vertexGroupName, vWeight))
  425. for f_index, f in enumerate(faces):
  426. f_v= f.v
  427. f_smooth= f.smooth
  428. f_mat = min(f.mat, len(materialNames)-1)
  429. if faceuv:
  430. f_image = f.image
  431. f_uv= f.uv
  432. # MAKE KEY
  433. if faceuv and f_image: # Object is always true.
  434. key = materialNames[f_mat], f_image.name
  435. else:
  436. key = materialNames[f_mat], None # No image, use None instead.
  437. # Write the vertex group
  438. if EXPORT_POLYGROUPS:
  439. if vertGroupNames:
  440. # find what vertext group the face belongs to
  441. theVGroup = findVertexGroupName(f,vgroupsMap)
  442. if theVGroup != currentVGroup:
  443. currentVGroup = theVGroup
  444. file.write('g %s\n' % theVGroup)
  445. # CHECK FOR CONTEXT SWITCH
  446. if key == contextMat:
  447. pass # Context alredy switched, dont do anything
  448. else:
  449. if key[0] == None and key[1] == None:
  450. # Write a null material, since we know the context has changed.
  451. if EXPORT_GROUP_BY_MAT:
  452. file.write('g %s_%s\n' % (fixName(ob.name), fixName(ob.getData(1))) ) # can be mat_image or (null)
  453. file.write('usemtl (null)\n') # mat, image
  454. else:
  455. mat_data= MTL_DICT.get(key)
  456. if not mat_data:
  457. # First add to global dict so we can export to mtl
  458. # Then write mtl
  459. # Make a new names from the mat and image name,
  460. # converting any spaces to underscores with fixName.
  461. # If none image dont bother adding it to the name
  462. if key[1] == None:
  463. mat_data = MTL_DICT[key] = ('%s'%fixName(key[0])), materialItems[f_mat], f_image
  464. else:
  465. mat_data = MTL_DICT[key] = ('%s_%s' % (fixName(key[0]), fixName(key[1]))), materialItems[f_mat], f_image
  466. if EXPORT_GROUP_BY_MAT:
  467. file.write('g %s_%s_%s\n' % (fixName(ob.name), fixName(ob.getData(1)), mat_data[0]) ) # can be mat_image or (null)
  468. file.write('usemtl %s\n' % mat_data[0]) # can be mat_image or (null)
  469. contextMat = key
  470. if f_smooth != contextSmooth:
  471. if f_smooth: # on now off
  472. file.write('s 1\n')
  473. contextSmooth = f_smooth
  474. else: # was off now on
  475. file.write('s off\n')
  476. contextSmooth = f_smooth
  477. file.write('f')
  478. if faceuv:
  479. if EXPORT_NORMALS:
  480. if f_smooth: # Smoothed, use vertex normals
  481. for vi, v in enumerate(f_v):
  482. file.write( ' %d/%d/%d' % (\
  483. v.index+totverts,\
  484. totuvco + uv_face_mapping[f_index][vi],\
  485. globalNormals[ veckey3d(v.no) ])) # vert, uv, normal
  486. else: # No smoothing, face normals
  487. no = globalNormals[ veckey3d(f.no) ]
  488. for vi, v in enumerate(f_v):
  489. file.write( ' %d/%d/%d' % (\
  490. v.index+totverts,\
  491. totuvco + uv_face_mapping[f_index][vi],\
  492. no)) # vert, uv, normal
  493. else: # No Normals
  494. for vi, v in enumerate(f_v):
  495. file.write( ' %d/%d' % (\
  496. v.index+totverts,\
  497. totuvco + uv_face_mapping[f_index][vi])) # vert, uv
  498. face_vert_index += len(f_v)
  499. else: # No UV's
  500. if EXPORT_NORMALS:
  501. if f_smooth: # Smoothed, use vertex normals
  502. for v in f_v:
  503. file.write( ' %d//%d' % (\
  504. v.index+totverts,\
  505. globalNormals[ veckey3d(v.no) ]))
  506. else: # No smoothing, face normals
  507. no = globalNormals[ veckey3d(f.no) ]
  508. for v in f_v:
  509. file.write( ' %d//%d' % (\
  510. v.index+totverts,\
  511. no))
  512. else: # No Normals
  513. for v in f_v:
  514. file.write( ' %d' % (\
  515. v.index+totverts))
  516. file.write('\n')
  517. # Write edges.
  518. if EXPORT_EDGES:
  519. LOOSE= Mesh.EdgeFlags.LOOSE
  520. for ed in edges:
  521. if ed.flag & LOOSE:
  522. file.write('f %d %d\n' % (ed.v1.index+totverts, ed.v2.index+totverts))
  523. # Make the indicies global rather then per mesh
  524. totverts += len(me.verts)
  525. if faceuv:
  526. totuvco += uv_unique_count
  527. me.verts= None
  528. file.close()
  529. # Now we have all our materials, save them
  530. if EXPORT_MTL:
  531. write_mtl(mtlfilename)
  532. if EXPORT_COPY_IMAGES:
  533. dest_dir = filename
  534. # Remove chars until we are just the path.
  535. while dest_dir and dest_dir[-1] not in '\\/':
  536. dest_dir = dest_dir[:-1]
  537. if dest_dir:
  538. copy_images(dest_dir)
  539. else:
  540. print '\tError: "%s" could not be used as a base for an image path.' % filename
  541. print "Export time: %.2f" % (sys.time() - time1)
  542. convert = ['python', Blender.Get('scriptsdir')+'/convert_obj_threejs_slim.py', '-i', filename, '-o', filename.replace('.obj','.js')]
  543. try:
  544. p = subprocess.Popen(convert, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  545. while p.poll() == None:
  546. pass
  547. except subprocess.CalledProcessError:
  548. print 'Error doing conversion!'
  549. print 'done'
  550. os.remove(filename)
  551. os.remove(filename.replace('.obj','.mtl'))
  552. def write_ui(filename):
  553. if not filename.lower().endswith('.obj'):
  554. filename += '.obj'
  555. if not BPyMessages.Warning_SaveOver(filename):
  556. return
  557. global EXPORT_APPLY_MODIFIERS, EXPORT_ROTX90, EXPORT_TRI, EXPORT_EDGES,\
  558. EXPORT_NORMALS, EXPORT_NORMALS_HQ, EXPORT_UV,\
  559. EXPORT_MTL, EXPORT_SEL_ONLY, EXPORT_ALL_SCENES,\
  560. EXPORT_ANIMATION, EXPORT_COPY_IMAGES, EXPORT_BLEN_OBS,\
  561. EXPORT_GROUP_BY_OB, EXPORT_GROUP_BY_MAT, EXPORT_KEEP_VERT_ORDER,\
  562. EXPORT_POLYGROUPS, EXPORT_CURVE_AS_NURBS
  563. EXPORT_APPLY_MODIFIERS = Draw.Create(0)
  564. EXPORT_ROTX90 = Draw.Create(1)
  565. EXPORT_TRI = Draw.Create(0)
  566. EXPORT_EDGES = Draw.Create(1)
  567. EXPORT_NORMALS = Draw.Create(0)
  568. EXPORT_NORMALS_HQ = Draw.Create(0)
  569. EXPORT_UV = Draw.Create(1)
  570. EXPORT_MTL = Draw.Create(1)
  571. EXPORT_SEL_ONLY = Draw.Create(1)
  572. EXPORT_ALL_SCENES = Draw.Create(0)
  573. EXPORT_ANIMATION = Draw.Create(0)
  574. EXPORT_COPY_IMAGES = Draw.Create(0)
  575. EXPORT_BLEN_OBS = Draw.Create(0)
  576. EXPORT_GROUP_BY_OB = Draw.Create(0)
  577. EXPORT_GROUP_BY_MAT = Draw.Create(0)
  578. EXPORT_KEEP_VERT_ORDER = Draw.Create(1)
  579. EXPORT_POLYGROUPS = Draw.Create(0)
  580. EXPORT_CURVE_AS_NURBS = Draw.Create(1)
  581. # Old UI
  582. '''
  583. # removed too many options are bad!
  584. # Get USER Options
  585. pup_block = [\
  586. ('Context...'),\
  587. ('Selection Only', EXPORT_SEL_ONLY, 'Only export objects in visible selection. Else export whole scene.'),\
  588. ('All Scenes', EXPORT_ALL_SCENES, 'Each scene as a separate OBJ file.'),\
  589. ('Animation', EXPORT_ANIMATION, 'Each frame as a numbered OBJ file.'),\
  590. ('Object Prefs...'),\
  591. ('Apply Modifiers', EXPORT_APPLY_MODIFIERS, 'Use transformed mesh data from each object. May break vert order for morph targets.'),\
  592. ('Rotate X90', EXPORT_ROTX90 , 'Rotate on export so Blenders UP is translated into OBJs UP'),\
  593. ('Keep Vert Order', EXPORT_KEEP_VERT_ORDER, 'Keep vert and face order, disables some other options.'),\
  594. ('Extra Data...'),\
  595. ('Edges', EXPORT_EDGES, 'Edges not connected to faces.'),\
  596. ('Normals', EXPORT_NORMALS, 'Export vertex normal data (Ignored on import).'),\
  597. ('High Quality Normals', EXPORT_NORMALS_HQ, 'Calculate high quality normals for rendering.'),\
  598. ('UVs', EXPORT_UV, 'Export texface UV coords.'),\
  599. ('Materials', EXPORT_MTL, 'Write a separate MTL file with the OBJ.'),\
  600. ('Copy Images', EXPORT_COPY_IMAGES, 'Copy image files to the export directory, never overwrite.'),\
  601. ('Triangulate', EXPORT_TRI, 'Triangulate quads.'),\
  602. ('Grouping...'),\
  603. ('Objects', EXPORT_BLEN_OBS, 'Export blender objects as "OBJ objects".'),\
  604. ('Object Groups', EXPORT_GROUP_BY_OB, 'Export blender objects as "OBJ Groups".'),\
  605. ('Material Groups', EXPORT_GROUP_BY_MAT, 'Group by materials.'),\
  606. ]
  607. if not Draw.PupBlock('Export...', pup_block):
  608. return
  609. '''
  610. # BEGIN ALTERNATIVE UI *******************
  611. if True:
  612. EVENT_NONE = 0
  613. EVENT_EXIT = 1
  614. EVENT_REDRAW = 2
  615. EVENT_EXPORT = 3
  616. GLOBALS = {}
  617. GLOBALS['EVENT'] = EVENT_REDRAW
  618. #GLOBALS['MOUSE'] = Window.GetMouseCoords()
  619. GLOBALS['MOUSE'] = [i/2 for i in Window.GetScreenSize()]
  620. def obj_ui_set_event(e,v):
  621. GLOBALS['EVENT'] = e
  622. def do_split(e,v):
  623. global EXPORT_BLEN_OBS, EXPORT_GROUP_BY_OB, EXPORT_GROUP_BY_MAT, EXPORT_APPLY_MODIFIERS, KEEP_VERT_ORDER, EXPORT_POLYGROUPS
  624. if EXPORT_BLEN_OBS.val or EXPORT_GROUP_BY_OB.val or EXPORT_GROUP_BY_MAT.val or EXPORT_APPLY_MODIFIERS.val:
  625. EXPORT_KEEP_VERT_ORDER.val = 0
  626. else:
  627. EXPORT_KEEP_VERT_ORDER.val = 1
  628. def do_vertorder(e,v):
  629. global EXPORT_BLEN_OBS, EXPORT_GROUP_BY_OB, EXPORT_GROUP_BY_MAT, EXPORT_APPLY_MODIFIERS, KEEP_VERT_ORDER
  630. if EXPORT_KEEP_VERT_ORDER.val:
  631. EXPORT_BLEN_OBS.val = EXPORT_GROUP_BY_OB.val = EXPORT_GROUP_BY_MAT.val = EXPORT_APPLY_MODIFIERS.val = 0
  632. else:
  633. if not (EXPORT_BLEN_OBS.val or EXPORT_GROUP_BY_OB.val or EXPORT_GROUP_BY_MAT.val or EXPORT_APPLY_MODIFIERS.val):
  634. EXPORT_KEEP_VERT_ORDER.val = 1
  635. def do_help(e,v):
  636. url = __url__[0]
  637. print 'Trying to open web browser with documentation at this address...'
  638. print '\t' + url
  639. try:
  640. import webbrowser
  641. webbrowser.open(url)
  642. except:
  643. print '...could not open a browser window.'
  644. def obj_ui():
  645. ui_x, ui_y = GLOBALS['MOUSE']
  646. # Center based on overall pup size
  647. ui_x -= 165
  648. ui_y -= 140
  649. global EXPORT_APPLY_MODIFIERS, EXPORT_ROTX90, EXPORT_TRI, EXPORT_EDGES,\
  650. EXPORT_NORMALS, EXPORT_NORMALS_HQ, EXPORT_UV,\
  651. EXPORT_MTL, EXPORT_SEL_ONLY, EXPORT_ALL_SCENES,\
  652. EXPORT_ANIMATION, EXPORT_COPY_IMAGES, EXPORT_BLEN_OBS,\
  653. EXPORT_GROUP_BY_OB, EXPORT_GROUP_BY_MAT, EXPORT_KEEP_VERT_ORDER,\
  654. EXPORT_POLYGROUPS, EXPORT_CURVE_AS_NURBS
  655. Draw.Label('Context...', ui_x+9, ui_y+239, 220, 20)
  656. Draw.BeginAlign()
  657. EXPORT_SEL_ONLY = Draw.Toggle('Selection Only', EVENT_NONE, ui_x+9, ui_y+219, 110, 20, EXPORT_SEL_ONLY.val, 'Only export objects in visible selection. Else export whole scene.')
  658. EXPORT_ALL_SCENES = Draw.Toggle('All Scenes', EVENT_NONE, ui_x+119, ui_y+219, 110, 20, EXPORT_ALL_SCENES.val, 'Each scene as a separate OBJ file.')
  659. EXPORT_ANIMATION = Draw.Toggle('Animation', EVENT_NONE, ui_x+229, ui_y+219, 110, 20, EXPORT_ANIMATION.val, 'Each frame as a numbered OBJ file.')
  660. Draw.EndAlign()
  661. Draw.Label('Output Options...', ui_x+9, ui_y+189, 220, 20)
  662. Draw.BeginAlign()
  663. EXPORT_APPLY_MODIFIERS = Draw.Toggle('Apply Modifiers', EVENT_REDRAW, ui_x+9, ui_y+170, 110, 20, EXPORT_APPLY_MODIFIERS.val, 'Use transformed mesh data from each object. May break vert order for morph targets.', do_split)
  664. EXPORT_ROTX90 = Draw.Toggle('Rotate X90', EVENT_NONE, ui_x+119, ui_y+170, 110, 20, EXPORT_ROTX90.val, 'Rotate on export so Blenders UP is translated into OBJs UP')
  665. EXPORT_COPY_IMAGES = Draw.Toggle('Copy Images', EVENT_NONE, ui_x+229, ui_y+170, 110, 20, EXPORT_COPY_IMAGES.val, 'Copy image files to the export directory, never overwrite.')
  666. Draw.EndAlign()
  667. Draw.Label('Export...', ui_x+9, ui_y+139, 220, 20)
  668. Draw.BeginAlign()
  669. EXPORT_EDGES = Draw.Toggle('Edges', EVENT_NONE, ui_x+9, ui_y+120, 50, 20, EXPORT_EDGES.val, 'Edges not connected to faces.')
  670. EXPORT_TRI = Draw.Toggle('Triangulate', EVENT_NONE, ui_x+59, ui_y+120, 70, 20, EXPORT_TRI.val, 'Triangulate quads.')
  671. Draw.EndAlign()
  672. Draw.BeginAlign()
  673. EXPORT_MTL = Draw.Toggle('Materials', EVENT_NONE, ui_x+139, ui_y+120, 70, 20, EXPORT_MTL.val, 'Write a separate MTL file with the OBJ.')
  674. EXPORT_UV = Draw.Toggle('UVs', EVENT_NONE, ui_x+209, ui_y+120, 31, 20, EXPORT_UV.val, 'Export texface UV coords.')
  675. Draw.EndAlign()
  676. Draw.BeginAlign()
  677. EXPORT_NORMALS = Draw.Toggle('Normals', EVENT_NONE, ui_x+250, ui_y+120, 59, 20, EXPORT_NORMALS.val, 'Export vertex normal data (Ignored on import).')
  678. EXPORT_NORMALS_HQ = Draw.Toggle('HQ', EVENT_NONE, ui_x+309, ui_y+120, 31, 20, EXPORT_NORMALS_HQ.val, 'Calculate high quality normals for rendering.')
  679. Draw.EndAlign()
  680. EXPORT_POLYGROUPS = Draw.Toggle('Polygroups', EVENT_REDRAW, ui_x+9, ui_y+95, 120, 20, EXPORT_POLYGROUPS.val, 'Export vertex groups as OBJ groups (one group per face approximation).')
  681. EXPORT_CURVE_AS_NURBS = Draw.Toggle('Nurbs', EVENT_NONE, ui_x+139, ui_y+95, 100, 20, EXPORT_CURVE_AS_NURBS.val, 'Export 3D nurbs curves and polylines as OBJ curves, (bezier not supported).')
  682. Draw.Label('Blender Objects as OBJ:', ui_x+9, ui_y+59, 220, 20)
  683. Draw.BeginAlign()
  684. EXPORT_BLEN_OBS = Draw.Toggle('Objects', EVENT_REDRAW, ui_x+9, ui_y+39, 60, 20, EXPORT_BLEN_OBS.val, 'Export blender objects as "OBJ objects".', do_split)
  685. EXPORT_GROUP_BY_OB = Draw.Toggle('Groups', EVENT_REDRAW, ui_x+69, ui_y+39, 60, 20, EXPORT_GROUP_BY_OB.val, 'Export blender objects as "OBJ Groups".', do_split)
  686. EXPORT_GROUP_BY_MAT = Draw.Toggle('Material Groups', EVENT_REDRAW, ui_x+129, ui_y+39, 100, 20, EXPORT_GROUP_BY_MAT.val, 'Group by materials.', do_split)
  687. Draw.EndAlign()
  688. EXPORT_KEEP_VERT_ORDER = Draw.Toggle('Keep Vert Order', EVENT_REDRAW, ui_x+239, ui_y+39, 100, 20, EXPORT_KEEP_VERT_ORDER.val, 'Keep vert and face order, disables some other options. Use for morph targets.', do_vertorder)
  689. Draw.BeginAlign()
  690. Draw.PushButton('Online Help', EVENT_REDRAW, ui_x+9, ui_y+9, 110, 20, 'Load the wiki page for this script', do_help)
  691. Draw.PushButton('Cancel', EVENT_EXIT, ui_x+119, ui_y+9, 110, 20, '', obj_ui_set_event)
  692. Draw.PushButton('Export', EVENT_EXPORT, ui_x+229, ui_y+9, 110, 20, 'Export with these settings', obj_ui_set_event)
  693. Draw.EndAlign()
  694. # hack so the toggle buttons redraw. this is not nice at all
  695. while GLOBALS['EVENT'] not in (EVENT_EXIT, EVENT_EXPORT):
  696. Draw.UIBlock(obj_ui, 0)
  697. if GLOBALS['EVENT'] != EVENT_EXPORT:
  698. return
  699. # END ALTERNATIVE UI *********************
  700. if EXPORT_KEEP_VERT_ORDER.val:
  701. EXPORT_BLEN_OBS.val = False
  702. EXPORT_GROUP_BY_OB.val = False
  703. EXPORT_GROUP_BY_MAT.val = False
  704. EXPORT_APPLY_MODIFIERS.val = False
  705. Window.EditMode(0)
  706. Window.WaitCursor(1)
  707. EXPORT_APPLY_MODIFIERS = EXPORT_APPLY_MODIFIERS.val
  708. EXPORT_ROTX90 = EXPORT_ROTX90.val
  709. EXPORT_TRI = EXPORT_TRI.val
  710. EXPORT_EDGES = EXPORT_EDGES.val
  711. EXPORT_NORMALS = EXPORT_NORMALS.val
  712. EXPORT_NORMALS_HQ = EXPORT_NORMALS_HQ.val
  713. EXPORT_UV = EXPORT_UV.val
  714. EXPORT_MTL = EXPORT_MTL.val
  715. EXPORT_SEL_ONLY = EXPORT_SEL_ONLY.val
  716. EXPORT_ALL_SCENES = EXPORT_ALL_SCENES.val
  717. EXPORT_ANIMATION = EXPORT_ANIMATION.val
  718. EXPORT_COPY_IMAGES = EXPORT_COPY_IMAGES.val
  719. EXPORT_BLEN_OBS = EXPORT_BLEN_OBS.val
  720. EXPORT_GROUP_BY_OB = EXPORT_GROUP_BY_OB.val
  721. EXPORT_GROUP_BY_MAT = EXPORT_GROUP_BY_MAT.val
  722. EXPORT_KEEP_VERT_ORDER = EXPORT_KEEP_VERT_ORDER.val
  723. EXPORT_POLYGROUPS = EXPORT_POLYGROUPS.val
  724. EXPORT_CURVE_AS_NURBS = EXPORT_CURVE_AS_NURBS.val
  725. base_name, ext = splitExt(filename)
  726. context_name = [base_name, '', '', ext] # basename, scene_name, framenumber, extension
  727. # Use the options to export the data using write()
  728. # def write(filename, objects, EXPORT_EDGES=False, EXPORT_NORMALS=False, EXPORT_MTL=True, EXPORT_COPY_IMAGES=False, EXPORT_APPLY_MODIFIERS=True):
  729. orig_scene = Scene.GetCurrent()
  730. if EXPORT_ALL_SCENES:
  731. export_scenes = Scene.Get()
  732. else:
  733. export_scenes = [orig_scene]
  734. # Export all scenes.
  735. for scn in export_scenes:
  736. scn.makeCurrent() # If alredy current, this is not slow.
  737. context = scn.getRenderingContext()
  738. orig_frame = Blender.Get('curframe')
  739. if EXPORT_ALL_SCENES: # Add scene name into the context_name
  740. context_name[1] = '_%s' % BPySys.cleanName(scn.name) # WARNING, its possible that this could cause a collision. we could fix if were feeling parranoied.
  741. # Export an animation?
  742. if EXPORT_ANIMATION:
  743. scene_frames = xrange(context.startFrame(), context.endFrame()+1) # up to and including the end frame.
  744. else:
  745. scene_frames = [orig_frame] # Dont export an animation.
  746. # Loop through all frames in the scene and export.
  747. for frame in scene_frames:
  748. if EXPORT_ANIMATION: # Add frame to the filename.
  749. context_name[2] = '_%.6d' % frame
  750. Blender.Set('curframe', frame)
  751. if EXPORT_SEL_ONLY:
  752. export_objects = scn.objects.context
  753. else:
  754. export_objects = scn.objects
  755. full_path= ''.join(context_name)
  756. # erm... bit of a problem here, this can overwrite files when exporting frames. not too bad.
  757. # EXPORT THE FILE.
  758. write(full_path, export_objects,\
  759. EXPORT_TRI, EXPORT_EDGES, EXPORT_NORMALS,\
  760. EXPORT_NORMALS_HQ, EXPORT_UV, EXPORT_MTL,\
  761. EXPORT_COPY_IMAGES, EXPORT_APPLY_MODIFIERS,\
  762. EXPORT_ROTX90, EXPORT_BLEN_OBS,\
  763. EXPORT_GROUP_BY_OB, EXPORT_GROUP_BY_MAT, EXPORT_KEEP_VERT_ORDER,\
  764. EXPORT_POLYGROUPS, EXPORT_CURVE_AS_NURBS)
  765. Blender.Set('curframe', orig_frame)
  766. # Restore old active scene.
  767. orig_scene.makeCurrent()
  768. Window.WaitCursor(0)
  769. if __name__ == '__main__':
  770. Window.FileSelector(write_ui, 'Export thee.js (slim)', sys.makename(ext='.obj'))