ATI nmf exporter for NormalMapper

If anyone is interested in using ATI’s NormalMapper directly with Blender, read this post:
http://www.blender.org/modules.php?op=modload&name=phpBB2&file=viewtopic&t=5842

Sweet, I’ll have to give this a go. I tried using the other version tha can take .obj files, but most of the time it was producing invisible normal maps <sigh>.

EDIT: Just tried it, it all seems to work until I actually try to generate the normal map. The blender dos window thing did calculations (took about 10 seconds to do a 240k mesh) and ended up saying SUCCESS. I then (using the UI) went into the normal mapper, selected the models and output file name. Set the size to 256 (as a test) and ran it. It does it’s thing for a few seconds as it loads in the model, then says the normal map is completed. I open up the file in GIMP and all I have is a blank image.

Any ideas? I’d really like to get this working properly as I could really use it for the mod I’m in.

going through old threads thought i’d give this a bump

LetterRip

I installed the script, but when I ran it there was no GUI… even if I did not have an ATI card, shouldn’t the GUI still be present?

It did create a .nmf file in bpydata that was 2.4 Mb, (I was following the tutorial, except I only had 2 of the 4 “tiles” made.

I think people with sufficently recent graphics cards [as in, they support pixel shaders] should use nvidia’s tool instead

http://developer.nvidia.com/object/melody_home.html

ati’s tool seems to be more aimed towards those that might want to do this on the command line…

I was a little afraid of that, because it means I would have to export the blender model to .3ds format first, and then port it to Ogre… flying blind in the .3ds phase because I don’t happen to own 3d studio max.

I was a little afraid of that, because it means I would have to export the blender model to .3ds format first, and then port it to Ogre… flying blind in the .3ds phase because I don’t happen to own 3d studio max.[/quote]
what?

melody can load obj files, and exports them too

since it adds new uv coordinates to the object [somewhat annoying, result has multiple sets of uvs] you’ll kinda have to convert from whatever your export from melody to something that your engine can handle without loosing the second set…

edit: I forgot to mention that melody likes to export dxt [directx texture format] images…

well-behavin’ nmf pipeline done in c++ would be great …

http://neuralfuzz.com/opengl/Blender_NM_GUI.jpg
I’ve added a GUI to the exporter so that you can do everything inside Blender now. The exporter runs as a seperate process so you can continue Blendering while the map is built.

Here is the new download: http://neuralfuzz.com/opengl/NormalMapper.zip

You can also view online help and some WIP tutorials here: http://www.neuralfuzz.com/opengl/blender/normalmapping/Help

nmf’s export fine and fast, but for some odd reason, normalmapper.exe repeatedly *causes errors … * in devil.dll … and crashes (blender and script remain un-affected).

I downloaded the zip file and moved the script into the blender script folder, but it doesnt work. It says that there was a python script error: check console. It seems to me that I did everything right but it just doesnt load. I thought that maybe I had to have a plane or something selected so i made a high poly and low poly modlel and it still didnt work…what am i not doing?

what am i not doing?

LOL … the right thing ?

:stuck_out_tongue:

Yeah i guess so…you got my hopes up there. I thought someone wrote what to do.



import Blender, sys, os, math, string
from Blender import Mathutils
from Blender.Mathutils import*
from struct import*
from Blender.BGL import*
from Blender import Draw

KEEP_SETTINGS = 1

if KEEP_SETTINGS:
	try: import pickle

	except:
		Blender.Draw.PupMenu("Can't import pickle module!%t|Permanent settings disabled.")
		KEEP_SETTINGS = 0

NmHeader = '4s1I'
NmRawTriangle = '9f 9f 6f'
ATI_NORMALMAPPER = 'C:\\NormalMapper'
configTextName = 'nmfexport.cfg'
objectMeshList = Blender.Object.Get()
HighPolyModelMenu = Draw.Create(0)
LowPolyModelMenu = Draw.Create(0)
TextureSizeMenu = Draw.Create(512)
SamplePerTexelMenu = Draw.Create(1)
BumpMapString = Draw.Create('')
NormalMapNameString = Draw.Create('NM')
pathString = Draw.Create(os.path.dirname(Blender.Get('filename')))
bumpMapToggle = Draw.Create(0)
EXEpathString = Draw.Create(ATI_NORMALMAPPER)
heightScaleNumber = Draw.Create(1.0)
boxFilterToggle = Draw.Create(1)
normalSpaceMenu = Draw.Create(0)
borderExpandMenu = Draw.Create(-1)
meshHPExported = 0
meshLPExported = 0
BUTTON_EVENT_OK = 101
BUTTON_EVENT_QUIT = 102
BUTTON_EVENT_SELECTED_HPMODELMENU = 1020
BUTTON_EVENT_SELECTED_LPMODELMENU = 1021
BUTTON_EVENT_BUILD_NORMALMAP = 1022
BUTTON_EVENT_SELECTED_TEXTURESIZE = 1023
BUTTON_EVENT_EXPORT_NMF = 1024
BUTTON_EVENT_BUMPMAPTOGGLE = 1025
BUTTON_EVENT_BUMPSTRING = 1026
BUTTON_EVENT_NORMALMAPSTRING = 1027
BUTTON_EVENT_HEIGHTSCALENUMBER = 1028
BUTTON_EVENT_BOXFILTER = 1029
BUTTON_EVENT_SAMPLEPERTEXEL = 1030
BUTTON_EVENT_NORMALSPACE = 1031
BUTTON_EVENT_BORDEREXPAND = 1032
BUTTON_EVENT_PATHSTRING = 110
BUTTON_EVENT_PATHBUTTON = 111
BUTTON_EVENT_BUMPBUTTON = 112
BUTTON_EVENT_EXEPATHSTRING = 113
BUTTON_EVENT_EXEPATHBUTTON = 114
BUTTON_EVENT_HEIGHTSCALE = 115

def saveSettings ():
	global bumpMapToggle, BumpMapString, NormalMapNameString, EXEpathString
	global heightScaleNumber, TextureSizeMenu, pathString, HighPolyModelMenu
	global HighPolyModelMenu, LowPolyModelMenu, configTextName, boxFilterToggle
	global SamplePerTexelMenu, normalSpaceMenu, borderExpandMenu
	settingsDict = {}
	success = 0
	settingsDict['TextureSize'] = TextureSizeMenu.val
	settingsDict['OutputPath'] = pathString.val
	settingsDict['ExePath'] = EXEpathString.val
	settingsDict['heightScale'] = heightScaleNumber.val
	settingsDict['HighPolyModel'] = HighPolyModelMenu.val
	settingsDict['LowPolyModel'] = LowPolyModelMenu.val
	settingsDict['NormalMapName'] = NormalMapNameString.val
	settingsDict['BumpMapName'] = BumpMapString.val
	settingsDict['UseBumpMap'] = bumpMapToggle.val
	settingsDict['BoxFilter'] = boxFilterToggle.val
	settingsDict['SamplePerTexel'] = SamplePerTexelMenu.val
	settingsDict['NormalSpace'] = normalSpaceMenu.val
	settingsDict['BorderExpand'] = borderExpandMenu.val

	if configTextName in [text.getName() for text in Blender.Text.Get()]:
		oldConfigText = Blender.Text.Get(configTextName)
		oldConfigText.setName(configTextName + '.old')
		Blender.Text.unlink(oldConfigText)

	configText = Blender.Text.New(configTextName)
	configText.write('nmfexport configuration file.

This file is automatically created. Please don\'t edit this file directly.

')

	try:
		configText.write(pickle.dumps(settingsDict))

	except:
		pass

	else:
		success = 1

	return success

def loadSettings (filename):
	global bumpMapToggle, BumpMapString, NormalMapNameString, EXEpathString
	global heightScaleNumber, TextureSizeMenu, pathString, HighPolyModelMenu
	global HighPolyModelMenu, LowPolyModelMenu, boxFilterToggle, SamplePerTexelMenu
	global normalSpaceMenu, borderExpandMenu
	settingsDict = {}
	success = 0

	if configTextName in [text.getName() for text in Blender.Text.Get()]:
		configText = Blender.Text.Get(configTextName)

		try:
			settingsDict = pickle.loads(string.join (configText.asLines()[4:], '
'))

		except:
			pass

		else:
			success = 1

	if not success and os.path.isfile(filename):
		try:
			fileHandle = open (filename, 'r')

		except IOError, (errno, strerror):
			print "I/O Error(%s): %s" % (errno, strerror)

		else:
			try:
				unpickler = pickle.Unpickler(fileHandle)
				settingsDict = unpickler.load()
				fileHandle.close()

			except EOFError:
				print "EOF Error"

			else:
				success = 1

	if settingsDict.has_key('TextureSize'):
		TextureSizeMenu = Blender.Draw.Create(settingsDict['TextureSize'])

	if settingsDict.has_key('OutputPath'):
		pathString = Blender.Draw.Create(settingsDict['OutputPath'])

	if settingsDict.has_key('ExePath'):
		EXEpathString = Blender.Draw.Create(settingsDict['ExePath'])

	if settingsDict.has_key('heightScale'):
		heightScaleNumber = Blender.Draw.Create(settingsDict['heightScale'])

	if settingsDict.has_key('BumpMapName'):
		BumpMapString = Blender.Draw.Create(settingsDict['BumpMapName'])

	if settingsDict.has_key('UseBumpMap'):
		bumpMapToggle = Blender.Draw.Create(settingsDict['UseBumpMap'])

	if settingsDict.has_key('NormalMapName'):
		NormalMapNameString = Blender.Draw.Create(settingsDict['NormalMapName'])

	if settingsDict.has_key('HighPolyModel'):
		HighPolyModelMenu = Blender.Draw.Create(settingsDict['HighPolyModel'])

	if settingsDict.has_key('LowPolyModel'):
		LowPolyModelMenu = Blender.Draw.Create(settingsDict['LowPolyModel'])

	if settingsDict.has_key('BoxFilter'):
		boxFilterToggle = Blender.Draw.Create(settingsDict['BoxFilter'])

	if settingsDict.has_key('SamplePerTexel'):
		SamplePerTexelMenu = Blender.Draw.Create(settingsDict['SamplePerTexel'])

	if settingsDict.has_key('NormalSpace'):
		normalSpaceMenu = Blender.Draw.Create(settingsDict['NormalSpace'])

	if settingsDict.has_key('BorderExpand'):
		borderExpandMenu = Blender.Draw.Create(settingsDict['BorderExpand'])

	return success

def buildRawTriangle (f, i0, i1, i2, hasUV):
	v0 = Vector(f.v[i0].co[:])
	v1 = Vector(f.v[i1].co[:])
	v2 = Vector(f.v[i2].co[:])

	if f.smooth == 0:
		n0 = n1 = n2 = Vector(f.no[:])

	else:
		n0 = Vector(f.v[i0].no[:])
		n1 = Vector(f.v[i1].no[:])
		n2 = Vector(f.v[i2].no[:])

	if hasUV:
		uv0 = Vector([f.uv[i0][0], f.uv[i0][1]])
		uv1 = Vector([f.uv[i1][0], f.uv[i1][1]])
		uv2 = Vector([f.uv[i2][0], f.uv[i2][1]])

	else:
		uv0 = uv1 = uv2 = Vector([0, 1])

	return pack (NmRawTriangle, v0.x, v0.y, v0.z, v1.x, v1.y, v1.z, v2.x, v2.y, v2.z, n0.x, n0.y, n0.z, n1.x, n1.y, n1.z, n2.x, n2.y, n2.z, uv0.x, uv0.y, uv1.x, uv1.y, uv2.x, uv2.y)

def splitFace (f, hasUV):
	sp = ''

	if len(f.v) &gt;= 3:
		sp = buildRawTriangle (f, 0, 1, 2, hasUV)

	if len(f.v) == 4:
		sp += buildRawTriangle (f, 2, 3, 0, hasUV)

	return sp

def exportObject (obj):
	dataDir = pathString.val+'\\'
	outputFileName = obj.getName()+'.nmf'
	print "Object :"
	print outputFileName, "
"
	print "outputing file to:"
	print dataDir, "
"
	fp = open (dataDir + outputFileName, 'wb')
	ndata = obj.getData()
	numTris = 0

	for f in ndata.faces:
		if len(f.v) &gt;= 3:
			numTris += 1

		if len(f.v) == 4:
			numTris += 1

	print "Writing main NMF Header to file"
	headersize = calcsize(NmHeader)+calcsize('I') + calcsize(NmRawTriangle) * numTris
	fp.write(pack (NmHeader, 'NMF ', headersize))
	print "Writing TRIS Header to file"
	headersize = calcsize('I')+calcsize(NmRawTriangle) * numTris
	fp.write(pack (NmHeader, 'TRIS', headersize))
	fp.write(pack ('1I', numTris))
	print "Writing", numTris, " Triangles to file
"

	for f in ndata.faces:
		fp.write(splitFace (f, ndata.hasFaceUV()))

	fp.close()
	return

def exportHighLowMeshesToNMF ():
	global objectMeshList, HighPolyModelMenu, LowPolyModelMenu
	global meshHPExported, meshLPExported
	startTime = Blender.sys.time()
	print
	print "Starting to export selected objects 
",
	obj = objectMeshList[HighPolyModelMenu.val]

	if obj.getType() == "Mesh":
		exportObject(obj)
		meshHPExported = 1

	else:
		print "No High Poly mesh selected so no NMF export"
		meshHPExported = 0

	obj = objectMeshList[LowPolyModelMenu.val]

	if obj.getType() == "Mesh":
		exportObject(obj)
		meshLPExported = 1

	else:
		print "No Low Poly mesh selected so no NMF export"
		meshLPExported = 0

	print "took", Blender.sys.time() - startTime, "seconds"

	if meshHPExported and meshLPExported:
		Blender.Draw.PupMenu("ATI nmf exporter finished")
		print "######## SUCCESS ########"

	else:
		if not meshHPExported:
			Blender.Draw.PupMenu("WARNING: no High Poly Mesh exported")

		if not meshLPExported:
			Blender.Draw.PupMenu("WARNING: no low Poly Mesh exported")

	return

def generateNormalMap ():
	global bumpMapToggle, BumpMapString, NormalMapNameString, EXEpathString
	global heightScaleNumber, boxFilterToggle, SamplePerTexelMenu, normalSpaceMenu
	global borderExpandMenu

	if EXEpathString.val != '':
		dataDir = pathString.val+'\\'
		textureSize = str(TextureSizeMenu.val)+' '
		meshHPFileName = '"'+dataDir + objectMeshList[HighPolyModelMenu.val].getName() + '.nmf"'
		meshLPFileName = '"'+dataDir + objectMeshList[LowPolyModelMenu.val].getName() + '.nmf"'
		commandLine = EXEpathString.val+'\\NormalMapper.exe'
		cmdLineArgs = ' -'

		if boxFilterToggle.val:
			cmdLineArgs += 'B'

		if SamplePerTexelMenu.val &gt; -1:
			cmdLineArgs += str(SamplePerTexelMenu.val)

		if normalSpaceMenu.val &gt; 0:
			cmdLineArgs += 'w'

		if borderExpandMenu.val &gt; -1:
			if borderExpandMenu.val == 0:
				cmdLineArgs += 'e'

			elif borderExpandMenu.val == 1:
				cmdLineArgs += 'E'

			elif borderExpandMenu.val == 2:
				cmdLineArgs += 'g'

			elif borderExpandMenu.val == 3:
				cmdLineArgs += 'G'

		if cmdLineArgs == ' -':
			cmdLineArgs = ' '

		else:
			cmdLineArgs += ' '

		cmdLineArgs += meshLPFileName + " " + meshHPFileName + ' ' + textureSize + textureSize

		if bumpMapToggle.val:
			cmdLineArgs += ' "' + BumpMapString.val + '" ' + str(heightScaleNumber.val) + ' '

		cmdLineArgs += '"' + dataDir + NormalMapNameString.val + '"'
		print "starting ATI NormalMapper.exe:"
		print commandLine, cmdLineArgs
		os.spawnl (os.P_NOWAIT, commandLine, cmdLineArgs);

	return

def pathSelectCallback (fileName):
	global pathString
	pathString = Blender.Draw.Create(os.path.dirname(fileName))
	return

def EXEpathSelectCallback (fileName):
	global EXEpathString
	EXEpathString = Blender.Draw.Create(os.path.dirname(fileName))
	return

def bumpFileSelectCallback (fileName):
	global BumpMapString
	BumpMapString = Blender.Draw.Create(os.path.abspath(fileName))
	return

def frameDecorator (x, y, width):
	glColor3f (0.5, 0.5, 0.5)
	glRectf (x, y - 36, x + width, y - 16)
	glColor3f (0.9, 0.9, 0.9)
	glRasterPos2i (x + 85, y - 30)
	Draw.Text ("ATI Normal Mapper Tool GUI", "normal")
	return 20

def drawText (text, x, y):
	glColor3f (0.1, 0.1, 0.1)
	glRasterPos2i (x, y)
	Draw.Text (text, "normal")
	return x + Draw.GetStringWidth(text) + 5

def refreshGUI ():
	return

def initGUI ():
	if KEEP_SETTINGS:
		loadSettings(Blender.Get('filename') + ".txt")

	refreshGUI()
	return

def exitGUI ():
	if KEEP_SETTINGS:
		saveSettings()

	Blender.Draw.Exit()
	return

def buttonCallback (event):
	global eventCallback, buttonCallback
	global HighPolyModelMenu, LowPolyModelMenu
	global meshHPExported, meshLPExported

	if (event == BUTTON_EVENT_OK):
		refreshGUI()
		Draw.Register (gui, eventCallback, buttonCallback)

	elif (event == BUTTON_EVENT_SELECTED_HPMODELMENU):
		meshHPExported = 0
		Draw.Redraw(1)

	elif (event == BUTTON_EVENT_SELECTED_LPMODELMENU):
		meshLPExported = 0
		Draw.Redraw(1)

	elif (event == BUTTON_EVENT_EXPORT_NMF):
		exportHighLowMeshesToNMF()
		Draw.Redraw(1)

	elif (event == BUTTON_EVENT_QUIT):
		exitGUI()

	elif (event == BUTTON_EVENT_PATHBUTTON):
		Blender.Window.FileSelector (pathSelectCallback, "Export Directory", pathString.val)
		Draw.Redraw(1)

	elif (event == BUTTON_EVENT_EXEPATHBUTTON):
		Blender.Window.FileSelector (EXEpathSelectCallback, "EXE Directory", EXEpathString.val)
		Draw.Redraw(1)

	elif (event == BUTTON_EVENT_BUMPBUTTON):
		Blender.Window.FileSelector (bumpFileSelectCallback, "Bump Map File", BumpMapString.val)
		Draw.Redraw(1)

	elif (event == BUTTON_EVENT_BUMPMAPTOGGLE):
		Draw.Redraw(1)

	elif (event == BUTTON_EVENT_BUILD_NORMALMAP):
		exportHighLowMeshesToNMF()

		if meshHPExported and meshLPExported:
			generateNormalMap()

		Draw.Redraw(1)

	return

def eventCallback (event, value):
	if (value != 0):
		if (event == Draw.ESCKEY):
			exitGUI()

		if (event == Draw.QKEY):
			exitGUI()

	return

def gui ():
	global pathString, EXEpathString, TextureSizeMenu, BumpMapString, bumpMapToggle
	global selectedObjectsList, HighPolyModelMenu, LowPolyModelMenu, NormalMapNameString
	global heightScaleNumber, boxFilterToggle, SamplePerTexelMenu, normalSpaceMenu
	global borderExpandMenu
	guiRectBuffer = Buffer (GL_FLOAT, 4)
	glGetFloatv (GL_SCISSOR_BOX, guiRectBuffer)
	guiRect = [0, 0, int(guiRectBuffer.list[2]), int(guiRectBuffer.list[3])]
	remainRect = guiRect[:]
	remainRect[0] += 10
	remainRect[1] += 10
	remainRect[2] -= 10
	remainRect[3] -= 10
	glClearColor (0.7, 0.7, 0.7, 1.0)
	glClear(GL_COLOR_BUFFER_BIT)
	remainRect[3] -= frameDecorator (remainRect[0], remainRect[3], remainRect[2] - remainRect[0])
	remainRect[1] += 70
	remainRect[3] -= 40
	pathString = Draw.String ("Output Path: ", BUTTON_EVENT_PATHSTRING, \
								  10, remainRect[3] - 20, guiRect[2] - 91, 20, \
								  pathString.val, 255, "the directory where the mesh.nmf and normal map files are saved")
	Draw.Button ("Select", BUTTON_EVENT_PATHBUTTON, guiRect[2] - 80, remainRect[3] - 20, 70, 20, "select the directory where the files are saved")
	remainRect[3] -= 20
	EXEpathString = Draw.String ("NormalMapper.exe Path: ", BUTTON_EVENT_EXEPATHSTRING, \
									 10, remainRect[3] - 20, guiRect[2] - 91, 20, \
									 EXEpathString.val, 255, "the directory where the ATI NormalMapper.exe is")
	Draw.Button ("Select", BUTTON_EVENT_EXEPATHBUTTON, guiRect[2] - 80, remainRect[3] - 20, 70, 20, "select the directory where the NormalMapper.exe file is saved")
	remainRect[3] -= 30
	Draw.Button ("Quit", BUTTON_EVENT_QUIT, guiRect[2] - 110, 10, 100, 30, "quit without exporting")
	objectMeshMenuName = "Meshes %t|"
	objectMeshMenuIndex = 0

	if (len(objectMeshList) &gt; 0):
		for object in objectMeshList:
			if object.getType() == "Mesh":
				objectMeshMenuName += object.getName() + " %x" + ("%d" % objectMeshMenuIndex) + "|"

			objectMeshMenuIndex += 1

	else:
		objectMeshMenuName = "No Mesh objects found in scene! %t"

	remainRect[0] = drawText ("High Poly Mesh: ", remainRect[0], remainRect[3] - 15)
	HighPolyModelMenu = Draw.Menu (objectMeshMenuName, BUTTON_EVENT_SELECTED_HPMODELMENU, \
									   remainRect[0], remainRect[3] - 20, 140, 20, \
									   HighPolyModelMenu.val, "High Poly Object selected for Normal map")
	Draw.Button ("Export NMF", BUTTON_EVENT_EXPORT_NMF, remainRect[0] + 150, remainRect[3] - 32, 80, 20, "export selected objects to ATI normal map format")
	remainRect[3] -= 20
	drawText (" Low Poly Mesh: ", 10, remainRect[3] - 15)
	LowPolyModelMenu = Draw.Menu (objectMeshMenuName, BUTTON_EVENT_SELECTED_LPMODELMENU, \
									  remainRect[0], remainRect[3] - 20, 140, 20, \
									  LowPolyModelMenu.val, "Low Poly Object selected for Normal map")
	remainRect[3] -= 30
	remainRect[0] = 10
	boxFilterToggle = Draw.Toggle ("Box Filter", BUTTON_EVENT_BOXFILTER, \
									   remainRect[0], remainRect[3] - 25, 60, 20, \
									   boxFilterToggle.val, "apply box filter post processing to normal map")
	SamplePerTexelMenu = Draw.Menu ( \
							 "Samples per Texel %t|1 sample %x-1|5 samples %x1|10 samples %x2|70 samples %x3", \
								 BUTTON_EVENT_SAMPLEPERTEXEL, \
								 remainRect[0] + 70, remainRect[3] - 25, 90, 20, \
								 SamplePerTexelMenu.val, "number of samples per texel for Ray casting")
	normalSpaceMenu = Draw.Menu ( \
						  "Normal %t|Tangent Space %x0|World Space %x1", \
							  BUTTON_EVENT_NORMALSPACE, \
							  remainRect[0] + 170, remainRect[3] - 25, 110, 20, \
							  normalSpaceMenu.val, "The space that the Normal Vector is in")
	borderExpandMenu = Draw.Menu ( \
						   "Border Expansion %t|Border : None %x0|Border : 10 %x-1|Border : 15 %x1|Border : 20 %x2|Border : 30 %x3", \
							   BUTTON_EVENT_BORDEREXPAND, \
							   remainRect[0] + 290, remainRect[3] - 25, 110, 20, \
							   borderExpandMenu.val, "Border expansion in texels")
	remainRect[3] -= 25
	bumpMapToggle = Draw.Toggle ("Use Bump Map", BUTTON_EVENT_BUMPMAPTOGGLE, \
									 remainRect[0], remainRect[3] - 25, 90, 20, \
									 bumpMapToggle.val, "apply bump map to normal map")

	if bumpMapToggle.val:
		heightScaleNumber = Draw.Number ("Height Scale : ", BUTTON_EVENT_HEIGHTSCALENUMBER, \
											 remainRect[0] + 130, remainRect[3] - 25, 220, 20, \
											 heightScaleNumber.val, 0.0, 5.0, "scale factor")
		remainRect[3] -= 20
		BumpMapString = Draw.String ("Bump Map File: ", BUTTON_EVENT_BUMPSTRING, \
										 10, remainRect[3] - 25, guiRect[2] - 91, 20, \
										 BumpMapString.val, 255, "the bump map file to be applied to the normal map")
		Draw.Button ("Select", BUTTON_EVENT_BUMPBUTTON, guiRect[2] - 80, remainRect[3] - 25, 70, 20, "select the bump map file to use")

	remainRect[3] -= 50
	remainRect[0] = drawText (" Normal Map Size: ", 10, remainRect[3] - 15)
	TextureSizeMenu = Draw.Menu ("Size %t|64 %x64|128 %x128|256 %x256|512 %x512|1024 %x1024", BUTTON_EVENT_SELECTED_TEXTURESIZE, \
									 remainRect[0], remainRect[3] - 20, 60, 20, \
									 TextureSizeMenu.val, "Normal Map texture size")
	remainRect[3] -= 25
	Draw.Button ("Build Normal Map", BUTTON_EVENT_BUILD_NORMALMAP, remainRect[0] + 150, remainRect[3] - 20, 130, 45, "export selected objects and build normal map")
	NormalMapNameString = Draw.String ("Normal Map File Name: ", BUTTON_EVENT_NORMALMAPSTRING, \
	10, remainRect[3] - 20, 250, 20, \
	NormalMapNameString.val, 50, "file name of output texture from Normal Mapper")
	remainRect[3] -= 25
	return

initGUI()
Draw.Register (gui, eventCallback, buttonCallback)

try to run this from text window, alt-p. and unwrap lines manually if need be. that said, this is unsupported version :stuck_out_tongue:

edit: yes, some lines got wrapped. you’ll need to do some cleanup, but not too much.

I copy pasted it and alt-p and it still didnt work…

When is your build from,

there was a recent commit to python that seems to have made some previously working scripts no longer function.

So if it is a CVS build in the last 2 or 3 days, try an earlier build first.

LetterRip

Its not a CVS build its just blender 2.36 from the blender3d website. Maybe it will work in blender 2.37 which should come out soon.

themonkey: I have been using it with Blender 2.36 + Python 2.3.5 for about 3 months now with no problems. Could you post what comes up in the console window when you run the script and get the script error.

The installation instructions can be found here in the help section:
http://www.neuralfuzz.com/opengl/blender/normalmapping/Help

Error: Python script error: check console

ok, and what did it say in the console?