1#!BPY
2
3"""
4Name: 'MS3D ASCII (.txt) v 1.5.2'
5Blender: 242
6Group: 'Export'
7Tooltip: 'MilkShape3d ASCII format for Scorched3d models v1.5.2'
8"""
9
10#Copyright (C) 2004-2007 Paul Vint cbx550f@sourceforge.net
11#
12#  This program is free software; you can redistribute it and/or modify
13#  it under the terms of the GNU General Public License as published by
14#  the Free Software Foundation; either version 2 of the License, or
15#  (at your option) any later version.
16#
17#  This program is distributed in the hope that it will be useful,
18#  but WITHOUT ANY WARRANTY; without even the implied warranty of
19#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20#  GNU General Public License for more details.
21#
22#  You should have received a copy of the GNU General Public License
23#  along with this program; if not, write to the Free Software
24#  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25#
26#
27#-------------------------------------------------------------
28#
29# initial alpha release, 17/08/2004
30# update: Fixed point of view - front, side top etc are now correct 27/08/2004
31# Added error checking for number of verts per face 07/09/2004
32# Reversed vertex winding to fix flipped normals problem & misc speedups 12/03/06
33# Updated for Blender 2.4x - thanks for the help, Berem!
34# TODO: Stop using face nrmals, should be vertex normals! - done. 05/09/06
35# Added proper support for emitted light 05/11/06
36# TODO - Have it export quads as triangles automagically (a la 3ds export)
37# Fix: Smoothing groups are now properly supported 28/10/2007
38
39import Blender
40from Blender import Draw, BGL
41from Blender.BGL import *
42from Blender.Draw import *
43import os, time, sys
44import meshtools
45
46## Events
47EVENT_NOEVENT = 1
48EVENT_DRAW = 2
49EVENT_EXIT = 3
50EVENT_EXPORT = 4
51EVENT_CHOOSE_FILENAME = 5
52EVENT_PROGRESS = 6
53
54def stripPath(path):
55        return path.split('/')[-1].split('\\')[-1]
56
57def fs_callback(filename):
58	global FILENAME
59        if filename.find('.txt', -4) <= 0: filename += '.txt'
60	FILENAME.val = filename
61        #write(filename)
62
63def createS3DPath():
64	global FILENAME
65        filename = Blender.Get('filename')
66
67        if filename.find('.') != -1:
68                filename = filename.split('.')[0]
69                filename += ".txt"
70
71        #return filename
72	FILENAME.val = filename
73	return filename
74
75def fileSelect():
76        Blender.Window.FileSelector(fs_callback, "Select", createS3DPath())
77
78FILENAME = Create('filename')
79createS3DPath()
80
81showProgress = Create(1)
82
83### Basic GUI functions
84def draw():     # Define the draw function (which draws your GUI).
85        #Blender.BGL.glClear(Blender.BGL.GL_COLOR_BUFFER_BIT) # This clears the window
86        # Add here drawing commands to draw your GUI, for example:
87        #Blender.Draw.Toggle("Clear origin",1,10,20,100,20,0,"Tooltip")
88        # The line abive will draw a toggle button.
89        # Note the first number, '1' means this is button number 1
90	global EVENT_NOEVENT,EVENT_DRAW,EVENT_EXIT,EVENT_EXPORT,EVENT_CHOOSE_FILENAME,FILENAME,EVENT_PROGRESS, showProgress
91
92	ypos = 110
93	glClear(GL_COLOR_BUFFER_BIT)
94	glRasterPos2d(8, ypos)
95	Text("MilkShape3d ASCII Export version 1.5")
96	glRasterPos2d(8, ypos - 15)
97	Text("Copyright 2007 - Paul Vint cbx550f@users.sourceforge.net")
98	glRasterPos2d(8, ypos - 30)
99	Text("For updates visit http://sourceforge.net/projects/scorched3d-mods")
100
101	## Buttons etc
102	glRasterPos2d(8, 83)
103	Button("Export",EVENT_EXPORT, 10, 10, 80, 18)
104	Button("Exit",EVENT_EXIT, 140, 10, 80, 18)
105	showProgress = Toggle("Show Progress",EVENT_PROGRESS,10, 30, 80, 18, showProgress.val, "Deselect to disable the progress bar (faster exporting)")
106
107	glRasterPos2d(8, 83)
108        FILENAME = String("Filename: ", EVENT_NOEVENT, 10, 55, 210, 18,
109                            FILENAME.val, 255, ".txt file to export")
110        Button("Browse",EVENT_CHOOSE_FILENAME,220,55,80,18)
111
112
113
114def event(evt,val):  # Define mouse and keyboard press events
115        if (evt == Blender.Draw.ESCKEY): # Example if esc key pressed
116                Blender.Draw.Exit()    # then exit script
117                return                 # return from the function
118
119def bevent(evt): #Manage button events
120	global FILENAME,showProgress
121	if (evt == EVENT_EXIT):
122		Blender.Draw.Exit()
123		return
124	elif (evt == EVENT_CHOOSE_FILENAME):
125		fileSelect()
126		Blender.Redraw()
127		return
128	elif (evt == EVENT_EXPORT):
129		#print showProgress
130		#showProgress = showProgress
131		write(FILENAME.val)
132		Blender.Draw.Exit()
133		return
134
135
136#Blender.Draw.Register(draw,event,button)
137
138
139#======= Write ms3d .txt format =========
140
141dbg = 0   #set to enable debugging
142
143
144def write(filename):
145	global showProgress
146	print showProgress
147	start = time.clock()
148
149	convertToTris = 0;
150	if Blender.sys.exists(filename) and (Blender.Draw.PupMenu("File exists, replace?%t|YES|NO") == 2):
151		fileSelect()
152		return
153
154
155	file = open(filename, "wb")
156
157	# ms3d header
158	file.write("// MilkShape 3D ASCII\n\nFrames: 30\nFrame: 1\n\n")
159
160	file.write("Meshes: " + str(len(Blender.Object.GetSelected())) + '\n')
161
162	objects = Blender.Object.GetSelected()
163
164	for obj in range(len(objects)):
165		meshname = objects[ obj].data.name
166		mesh = Blender.NMesh.GetRaw(meshname)
167		thismesh = meshname
168
169		if dbg:
170			print "\nDEBUG: Mesh", mesh, "Obj: ", obj, "Name: ", objects[obj].data.name
171		if mesh:
172			print "Mesh",objects[obj].data.name,
173			meshname = "\"" + objects[obj].data.name + "\" 0 " + str(obj) + "\n"  # Setting flags=0, mat index=object number for now, also note that I'm using object name, not meshname
174			file.write(meshname)
175
176			file.write(str(3 * len(mesh.faces)) + '\n') # should be better way to get # of verts, but this is fast
177			for i in range(len(mesh.faces)):  # New in v 0.3 - getting verts from faces directly
178				faceVerts = len(mesh.faces[i].v) # Reversing the winding to fix flipped normals problem
179				for fv in range(faceVerts):  # Scan through each vert in face
180					line = '0 ' + str(-round(mesh.faces[i].v[faceVerts-fv-1][0],4)) + ' ' + str(round(mesh.faces[i].v[faceVerts-fv-1][2],4)) + ' ' + str(round(-mesh.faces[i].v[faceVerts-fv-1][1],4)) + ' ' #+ str(round(mesh.faces[i].uv[fv][0],5)) + ' ' + str(round(mesh.faces[i].uv[fv][1],5))  + ' -1\n'
181					if (len(mesh.faces[i].uv)):
182						line += str(round(mesh.faces[i].uv[faceVerts-fv-1][0],5)) + ' ' + str(round(1-mesh.faces[i].uv[faceVerts-fv-1][1],5))  + ' -1\n'
183					else:
184						line += '0.0 0.0 -1\n'
185
186					if not i%50 and showProgress.val:
187						Blender.Window.DrawProgressBar(float(i)/len(mesh.faces), thismesh + "Verts")
188					file.write(line)
189
190			# Normals
191			#print "Norms: ",len(mesh.faces),  #FIX! Temp only using # of faces
192			file.write(str(3 * len(mesh.faces)))
193			for i in range(len(mesh.faces)):
194				if not i%50 and showProgress.val:
195					Blender.Window.DrawProgressBar(float(i)/len(mesh.faces), thismesh + "Normals")
196				#file.write('\n' + str(round(mesh.faces[i].no[0],4)) + ' ' + str(round(mesh.faces[i].no[2],4)) + ' ' + str(round(mesh.faces[i].no[1],4)))
197				faceVerts = len(mesh.faces[i].v)
198				for fv in range(faceVerts):  # Scan through each vert in face
199					# Check if the face is smoothed or not:
200					if (mesh.faces[i].smooth):
201						file.write('\n' + str(round(-mesh.faces[i].v[faceVerts-fv-1].no[0],4)))
202						file.write(' ' + str(round(mesh.faces[i].v[faceVerts-fv-1].no[2],4)))
203						file.write(' ' + str(round(-mesh.faces[i].v[faceVerts-fv-1].no[1],4)))
204					else:
205						file.write('\n' + str(round(-mesh.faces[i].no[0],4)) + ' ' + str(round(mesh.faces[i].no[2],4)) + ' ' + str(round(-mesh.faces[i].no[1],4)))
206
207			# Faces
208			print "Triangles: " ,len(mesh.faces)
209			file.write('\n' + str(len(mesh.faces)) + '\n')
210			idx = 0
211			line = ""
212			for i in range(len(mesh.faces)):  # one face at a time
213				if not i%50 and showProgress.val:
214					Blender.Window.DrawProgressBar(float(i)/len(mesh.faces), thismesh + "Triangles")
215				if (len(mesh.faces[i].v) != 3):       #Ensure each face is a triangle!
216					print "\n\n*********** Error!   **********\n"
217					print "Object",objects[obj].data.name, "has", len(mesh.faces[i].v), "vertices in face", i, "Must have THREE verts per face - convert to triangles with CTRL-T!"
218					Blender.Window.WaitCursor(0)
219					exitmsg = 'MS3D Export Error:|Mesh \"' + objects[obj].data.name + '\" has ' + str(len(mesh.faces[i].v)) + ' verts in a face, needs to be converted to triangles with CTRL-T!'
220					Blender.Draw.PupMenu(exitmsg)
221					return
222					######## Keeping depricated (and broken) tri2quad for future reference
223					#if convertToTris==0:	#only ask once
224					#	print "Object",objects[obj].data.name, "has", len(mesh.faces[i].v), "vertices in face", i, "Must have THREE verts per face"
225					#	convertToTris=Blender.Draw.PupMenu("Model not made entirely out of Triangles-Convert?%t|YES|NO")
226					#if convertToTris==1:
227					#	for f in mesh.faces:
228					#		f.sel = 1
229					#	Blender.Mesh.Mode(3)
230					#	mesh.quadToTriangle(0)
231					#elif convertToTris==2:
232					#	exitmsg = 'MS3D Export Error:|Mesh \"' + objects[obj].data.name + '\" has ' + str(len(mesh.faces[i].v)) + ' verts in a face, needs to be converted to triangles with CTRL-T!'
233					#	Blender.Draw.PupMenu(exitmsg)
234					#	return
235
236
237				for v in range(len(mesh.faces[i].v)):  #Go through each vertex in face - only need triangles for MS3D, but this would allow for quads too for portability
238					line += str(idx) + " "
239					idx += 1
240						#print cnt  #debug info
241				#file.write('1 ' + line + str( i) + ' ' + str(i) + ' ' + str(i) + ' 1\n')
242				file.write('1 ' + line + line + ' 1\n')
243
244				line = ""  # fresh start for next face
245
246		######### Materials! ##########
247	numMats = 0
248	for obj in range(len(objects)):         #FIXME! Stupid cludge for counting materials
249		meshname = objects[ obj].data.name
250		mesh = Blender.NMesh.GetRaw(meshname)
251		if (len(mesh.materials) == 0):	#Ensure the mesh has a material
252			print '*** Error! ', meshname, 'must have a material & texture!'
253			message = 'MS3D Export Error:|Mesh \"' + objects[obj].data.name + '\"needs to have a material!'
254			meshtools.print_boxed(message)
255        		Blender.Window.WaitCursor(0)
256        		Blender.Draw.PupMenu(message)
257        		return
258
259		if mesh.materials[0]:
260			numMats+=1
261	file.write("\nMaterials: " + str(numMats) + "\n")
262
263	for obj in range(len(objects)):
264		meshname = objects[ obj].data.name
265		mesh = Blender.NMesh.GetRaw(meshname)
266
267		for material in mesh.materials:
268			file.write("\"" + material.name + "\"\n")
269
270			#file.write(str(round(material.ref,5))+ " " + str(round(material.ref,5))+ " " + str(round(material.ref,5)) + " " + str(round(material.alpha,5)) + "\n")
271			#TODO Why are these values the same??? TODO
272			# file.write(str("%5f %5f %5f %5f\n" % (material.ref,material.ref,material.ref,material.alpha)))
273			file.write(str("%5f %5f %5f %5f\n" % (material.amb,material.amb,material.amb,material.alpha)))
274			#file.write(str(round(material.rgbCol[0],5)) + " " + str(round(material.rgbCol[1],5)) + " " + str(round(material.rgbCol[2],5)) + " " + str(round(material.alpha,5)) + "\n")
275			file.write(str("%5f %5f %5f %5f\n" % (material.rgbCol[0],material.rgbCol[1],material.rgbCol[2],material.alpha)))
276			file.write(str("%5f %5f %5f %5f\n" % (material.spec,material.spec,material.spec,material.alpha)))
277
278			#file.write(str(round(material.spec,5)) + " " + str(round(material.spec,5)) + " " + str(round(material.spec,5)) + " " + str(round(material.alpha,5)))
279			file.write(str("%5f %5f %5f %5f\n" % (material.emit,material.emit,material.emit,material.alpha)))
280			file.write(str(material.ref) + "\n")
281			file.write(str(material.alpha) + "\n")
282			# get the current texture image
283			imageName=""
284			mytex = material.getTextures()
285			if (dbg):
286				print 'MyTex: ', mytex[0]
287			if ( mytex[0] != None ):
288				image = material.getTextures()[0].tex.getImage()
289				imageName = Blender.sys.basename(image.getFilename())
290
291				file.write("\".\\" + stripPath(imageName) + "\"")
292
293			else:
294				print 'Warning: Mesh ' + meshname + ' does not have a texture image!'
295				message = 'MS3D Export Warning:|Mesh \"' + meshname + '\" does not have a texture image!'
296				meshtools.print_boxed(message)
297				Blender.Window.WaitCursor(0)
298				Blender.Draw.PupMenu(message)
299
300				file.write("\"\"")
301
302			#file.write("\".\\" + imageName + "\"")
303			file.write("\n\"\"\n")
304
305
306
307
308	file.write('\nBones: 0\n') # we don't need no stinking bones for Scorched! ;)
309	file.write('GroupComments: 0\n')
310	file.write('MaterialComments: 0\n')
311	file.write('BoneComments: 0\n')
312	file.write('ModelComment: 0\n')
313	Blender.Window.DrawProgressBar(1.0, '') #clear progressbar
314 	file.close()
315	end = time.clock()
316	seconds = " in %.2f %s" % (end-start, "seconds")
317	message = "Successfully exported " + os.path.basename(filename) + seconds
318	meshtools.print_boxed(message)
319	Blender.Window.WaitCursor(0)
320        Blender.Draw.PupMenu(message)
321	return
322
323
324Blender.Draw.Register(draw,event,bevent)
325
326
327
328