#!BPY

"""
Name: 'Stl Batch (.stl)...'
Blender: 239
Group: 'Import'
Tooltip: 'Import Stereo Lithography (.stl) File Format'
"""

__author__ = "Mitch Hughes (lobo_nz)"
__url__ = ("blender", "Author's homepage, http://blender.formworks.co.nz")
__version__ = "0.6"

__bpydoc__ = """\
This script batch imports binary Stereo Lithography files into Blender.
OR imports a single stl file

Usage:
	
Add the script to your blender/scripts directory
Execute this script from the "File->Import" menu and choose STL Batch file,
select a file in the directory you would like to batch import .stl files from
OR select a single .stl file and toggle the single file option.
"""

# $Id: batch_stl.py,v 0.6 2005/11/03 20:03:10$
#
# --------------------------------------------------------------------------
# Stl Batch by Mitch Hughes (AKA lobo_nz)
# --------------------------------------------------------------------------
# ***** BEGIN GPL LICENSE BLOCK *****
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****
# --------------------------------------------------------------------------

import Blender, os, struct, sys, string
from struct import *
from types import *

from Blender import NMesh, Scene, Object, Draw, Image, BGL
from Blender.Window import *

#Globals

#Detect OS
#TODO: use built in functions to do this
if (os.name == 'nt'):
	g_os_separator = '\\'
else:
	g_os_separator = '/'
	
g_working_dir=Draw.Create(Blender.sys.dirname(Blender.Get('filename'))+g_os_separator);

g_file_name=Draw.Create("")
	
#Defaults
g_scale=Draw.Create(0.01)
g_set_smooth=Draw.Create(1)
g_auto_smooth=Draw.Create(1)
g_single_file=Draw.Create(0)

g_show_progress = Draw.Create(1)			# Set to 0 for faster performance
g_overwrite_mesh_name = Draw.Create(1) 	# Set to 0 to increment object-name version

g_drawy = 0

# Events
EVENT_NOEVENT=1
EVENT_LOAD_STL=2
EVENT_CHOOSE_FILENAME=3
EVENT_TOGGLE_SETSMOOTH=4
EVENT_TOGGLE_AUTOSMOOTH=5
EVENT_TOGGLE_SINGLEFILE=6
EVENT_TOGGLE_PROGRESS=7
EVENT_TOGGLE_REPLACEXISTING=8
EVENT_EXIT=100

###Code between ###START and ###END markers modified from
### Anthony D'Agostino's meshtools.py module 
###Copyright (c) 2001 Anthony D'Agostino http://www.redrival.com/scorpius 
###START derived from meshtools.py
######################################################
# === Append Faces To Face List ===
######################################################
def append_faces(mesh, faces, facesuv, uvcoords):
	for i in range(len(faces)):
		if not i%100 and g_show_progress.val: Blender.Window.DrawProgressBar(float(i)/len(faces), "Generating Faces")
		numfaceverts=len(faces[i])
		if numfaceverts == 2: #This is not a face is an edge
			if mesh.edges == None:  #first run
				mesh.addEdgeData()
			#rev_face = revert(cur_face)
			i1 = faces[i][0]
			i2 = faces[i][1]
			ee = mesh.addEdge(mesh.verts[i1],mesh.verts[i2])
			ee.flag |= Blender.NMesh.EdgeFlags.EDGEDRAW
			ee.flag |= Blender.NMesh.EdgeFlags.EDGERENDER
		elif numfaceverts in [3,4]:				# This face is a triangle or quad
			face = Blender.NMesh.Face()
			for j in range(numfaceverts):
				index = faces[i][j]
				face.v.append(mesh.verts[index])
				if len(uvcoords) > 1:
					uvidx = facesuv[i][j]
					face.uv.append(uvcoords[uvidx])
					face.mode = 0
					face.col = [Blender.NMesh.Col()]*4
			mesh.faces.append(face)
		else:								# Triangulate n-sided convex polygon.
			a, b, c = 0, 1, 2				# Indices of first triangle.
			for j in range(numfaceverts-2): # Number of triangles in polygon.
				face = Blender.NMesh.Face()
				face.v.append(mesh.verts[faces[i][a]])
				face.v.append(mesh.verts[faces[i][b]])
				face.v.append(mesh.verts[faces[i][c]])
				b = c; c += 1
				mesh.faces.append(face)
		#face.smooth = 1
######################################################
# === Append Verts to Vertex List ===
######################################################
def append_verts(mesh, verts, normals):
	#print "Number of normals:", len(normals)
	#print "Number of verts  :", len(verts)
	for i in range(len(verts)):
		if not i%100 and g_show_progress.val: Blender.Window.DrawProgressBar(float(i)/len(verts), "Generating Verts")
		x, y, z = verts[i]
		mesh.verts.append(Blender.NMesh.Vert(x, y, z))
		if normals:
			mesh.verts[i].no[0] = normals[i][0]
			mesh.verts[i].no[1] = normals[i][1]
			mesh.verts[i].no[2] = normals[i][2]

######################################################
# === Create Blender Mesh ===
######################################################
def create_mesh(verts, faces, objname, facesuv=[], uvcoords=[], normals=[]):
	if normals: normal_flag = 0
	else: normal_flag = 1
	mesh = Blender.NMesh.GetRaw()
	append_verts(mesh, verts, normals)
	append_faces(mesh, faces, facesuv, uvcoords)
	if not g_overwrite_mesh_name.val:
		objname = versioned_name(objname)
	new_obj = Blender.NMesh.PutRaw(mesh, objname, normal_flag)	# Name the Mesh
	
	if new_obj != None:
		new_obj.name=objname		# Name the Object if its new
		objname = new_obj.name
	Blender.Redraw()
	return objname
	
######################################################
# === Increment Name Version ===
#######################################################
def versioned_name(objname):
	existing_names = []
	for object in Blender.Object.Get():
		existing_names.append(object.name)
		data = object.data
		if data: existing_names.append(data.name)
	if objname in existing_names: # don't over-write other names
		try:
			name, ext = objname.split('.')
		except ValueError:
			name, ext = objname, ''
		try:
			num = int(ext)
			root = name
		except ValueError:
			root = objname
		for i in xrange(1, 1000):
			objname = "%s.%03d" % (root, i)
			if objname not in existing_names:
				break
	return objname
###END derived from meshtools.py

######################################################
# Callbacks for Window functions
######################################################
def filename_callback(filename_in_dir):
	global g_working_dir
	global g_file_name
	g_file_name = Blender.sys.basename(filename_in_dir)
	# Get the target directory to batch import
	g_working_dir.val=Blender.sys.dirname(filename_in_dir)
	
######################################################
# GUI Loader
# GUI Code taken from Bob Holcomb's ASCII stl_import script and modified
######################################################
def DRAWY(val):
	global g_drawy
	if val >= 0:
		g_drawy=g_drawy-val
		return g_drawy
	else:
		g_drawy = val*-1
		return g_drawy
		
def draw_gui():
	global g_scale
	global g_working_dir
	global g_scale_slider
	global g_set_smooth
	global g_auto_smooth
	global EVENT_NOEVENT,EVENT_LOAD_STL,EVENT_CHOOSE_FILENAME,EVENT_TOGGLE_SETSMOOTH,EVENT_TOGGLE_AUTOSMOOTH,EVENT_EXIT, EVENT_TOGGLE_SINGLEFILE, EVENT_TOGGLE_PROGRESS, EVENT_TOGGLE_REPLACEXISTING
	
	DRAWY(-280)
	
	########## Titles
	BGL.glClear(BGL.GL_COLOR_BUFFER_BIT)
	BGL.glRasterPos2d(8, DRAWY(0))
	Draw.Text("			Binary STL Batch Importer		 ")
	BGL.glRasterPos2d(8, DRAWY(20))
	Draw.Text("1) Select a file from the directory you")
	BGL.glRasterPos2d(8, DRAWY(15))
	Draw.Text("	wish to Batch Import files from")
	
	######### Parameters GUI Buttons
	g_working_dir = Draw.String("", EVENT_NOEVENT, 10, DRAWY(25), 250, 18,
							g_working_dir.val, 255, "Directory to Batch Import")
	########## stl File Select Button
	Draw.Button("Select",EVENT_CHOOSE_FILENAME,95,DRAWY(20),80,18)
	
	BGL.glRasterPos2d(8, DRAWY(25))
	Draw.Text("2) Set the desired Scaling Factor")
	
	########## Scale slider-default is 0.1
	g_scale= Draw.Slider("Scale Factor: ", EVENT_NOEVENT, 10, DRAWY(25), 250, 18,
					g_scale.val, 0.001, 10.0, 1, "Scale factor for STL Models");
	
	BGL.glRasterPos2d(8, DRAWY(25))
	Draw.Text("3) Set Options")
	Draw.Toggle("Set Smooth", EVENT_TOGGLE_SETSMOOTH, 10, DRAWY(25), 75, 18, g_set_smooth.val,"Smooths all faces on imported meshes")
	Draw.Toggle("Auto Smooth", EVENT_TOGGLE_AUTOSMOOTH, 98, DRAWY(0), 75, 18, g_auto_smooth.val,"Activates Auto Smooth on imported meshes")
	Draw.Toggle("Single File", EVENT_TOGGLE_SINGLEFILE, 185, DRAWY(0), 75, 18, g_single_file.val,"Imports single file rather than whole directory")
	
	Draw.Toggle("Replace Existing", EVENT_TOGGLE_REPLACEXISTING, 30, DRAWY(25), 105, 18, g_overwrite_mesh_name.val,"Replaces exisintg meshs if named the same as the file")
	Draw.Toggle("Show Progress", EVENT_TOGGLE_PROGRESS, 145, DRAWY(0), 105, 18, g_show_progress.val,"Show Progress in header - Faster Off")
	
	BGL.glRasterPos2d(8, DRAWY(25))
	Draw.Text("4) Press Import")

	######### Draw and Exit Buttons
	Draw.Button("Import",EVENT_LOAD_STL , 10, DRAWY(25), 80, 18)
	Draw.Button("Exit",EVENT_EXIT , 180, DRAWY(0), 80, 18)

def event(evt, val):
	if (evt == Draw.QKEY and not val):
		Draw.Exit()

def bevent(evt):
	global g_working_dir
	global g_material_filename
	global g_set_smooth
	global g_auto_smooth
	global g_file_name
	global EVENT_NOEVENT,EVENT_LOAD_STL,EVENT_CHOOSE_FILENAME,EVENT_TOGGLE_SETSMOOTH,EVENT_TOGGLE_AUTOSMOOTH,EVENT_EXIT, EVENT_TOGGLE_SINGLEFILE, EVENT_TOGGLE_PROGRESS, EVENT_TOGGLE_REPLACEXISTING

	######### Manages GUI events
	if (evt==EVENT_EXIT):
		Draw.Exit()
		return
	elif (evt==EVENT_CHOOSE_FILENAME):
		FileSelector(filename_callback, "Select Directory")
	elif (evt==EVENT_TOGGLE_SETSMOOTH):
		g_set_smooth.val = 1 - g_set_smooth.val
	elif (evt==EVENT_TOGGLE_AUTOSMOOTH):
		g_auto_smooth.val = 1 - g_auto_smooth.val
	elif (evt==EVENT_TOGGLE_SINGLEFILE):
		g_single_file.val = 1 - g_single_file.val
	elif (evt==EVENT_TOGGLE_PROGRESS):
		g_show_progress.val = 1 - g_show_progress.val
	elif (evt==EVENT_TOGGLE_REPLACEXISTING):
		g_overwrite_mesh_name.val = 1 - g_overwrite_mesh_name.val
	#load the object and materials
	elif (evt==EVENT_LOAD_STL):
		if (g_working_dir.val == "/path/to/import/from"):
			Draw.Exit()
			return
		else:
			if  (g_single_file.val):
				if string.rfind(string.lower(g_file_name), '.stl')>0:
					read_file(g_working_dir.val+g_os_separator+g_file_name)
				else:
					print "Omitting "+file_name
			else:
				iterate_directory(g_working_dir.val)
			Blender.Redraw()
			Draw.Exit()
			return
	Draw.Redraw(1)

######################################################
# Read STL Triangle Format
######################################################

def read_file(filename):
	global g_scale
	global g_set_smooth
	global g_auto_smooth
	
	file = open(filename, "rb")
	
	#80 Any text such as the	global g_working_dir
	#creator's name
	header = unpack("<80s", file.read(80))
	
	#4 int equal to the number of facets in file
	facets = unpack("i", file.read(4))
	
	#4 vertice data in the rest of the data excapt last 2 bytes
	# Collect vert data from RAW format
	faces = []
	for i in range(0, facets[0]):
		n_x = unpack("f", file.read(4))
		n_y = unpack("f", file.read(4))
		n_z = unpack("f", file.read(4))
		v1_x = unpack("f", file.read(4))
		v1_y = unpack("f", file.read(4))
		v1_z = unpack("f", file.read(4))
		v2_x = unpack("f", file.read(4))
		v2_y = unpack("f", file.read(4))
		v2_z = unpack("f", file.read(4))
		v3_x = unpack("f", file.read(4))
		v3_y = unpack("f", file.read(4))
		v3_z = unpack("f", file.read(4))
		unused = file.read(2)
		
		faces.append([(v1_x[0]*g_scale.val, v1_y[0]*g_scale.val, v1_z[0]*g_scale.val), (v2_x[0]*g_scale.val, v2_y[0]*g_scale.val, v2_z[0]*g_scale.val), (v3_x[0]*g_scale.val, v3_y[0]*g_scale.val, v3_z[0]*g_scale.val)])
	file.close()
	
	# The fine piece of code below for eliminating duplicate verts
	# and creating the object in blender was copied directly from
	# the slp import export script written by
	# Anthony D'Agostino (Scorpius)
	# Generate verts and faces lists, without duplicates
	verts = []
	coords = {}
	index = 0
	for i in range(len(faces)):
		for j in range(len(faces[i])):
			vertex = faces[i][j]
			if not coords.has_key(vertex):
				coords[vertex] = index
				index += 1
				verts.append(vertex)
			faces[i][j] = coords[vertex]

	objname = Blender.sys.splitext(Blender.sys.basename(filename))[0]
	
	#we may get a new mesh name depending on wether we overwrite or make versioned
	new_objname = create_mesh(verts, faces, objname)
	new_objname 
	print new_objname
	Blender.Window.DrawProgressBar(1.0, '')  # clear progressbar
	
	#make all the objects faces smooth
	# Grab object we just imported
	mesh_ob = Blender.Object.Get(new_objname)
	
	#Apply options to Mesh
	if mesh_ob.getType() == 'Mesh':
		the_mesh = mesh_ob.getData()
		if g_auto_smooth.val == 1:
			the_mesh.setMode("AutoSmooth")
		if g_set_smooth.val == 1:
			setSmooth(the_mesh)

	print "Successfully imported " + Blender.sys.basename(filename)

# A method to do a 'Set Smooth' on a mesh
def setSmooth(mesh):
	for face in mesh.faces:
		face.smooth = 1
	
	mesh.update(1)
######################################################
# Read Files in Directory
######################################################

def iterate_directory(working_dir):
	#Check for stl extension so we dont try and 
	#import other files in the directory
	#stl_extension = re.compile('\.stl$', re.IGNORECASE)
	
	for file_name in os.listdir (working_dir):
		if string.rfind(string.lower(file_name), '.stl')>0:
			read_file(working_dir+g_os_separator+file_name)
		else:
			print "Omitting "+file_name

	
#def fs_callback(filename_in_dir):
#	iterate_directory(filename_in_dir)
#Blender.Window.FileSelector(fs_callback, "Batch STL")

Draw.Register(draw_gui, event, bevent)

