#!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 #print "Processing " + Blender.sys.basename(filename) 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 whether we overwrite or make versioned new_objname = create_mesh(verts, faces, objname) #print "Created " + new_objname Blender.Window.DrawProgressBar(1.0, '') # clear progressbar at end # Grab object we just imported mesh_ob = Blender.Object.Get(new_objname) # make all the objects faces smooth # 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: set_smooth(the_mesh) # start nwightma remove_doubles(mesh_ob) # we need to find the center as the getBoundBox() doesnt appear to work center = find_center(verts) center_new(mesh_ob, center) # end nwightma print "Successfully imported " + Blender.sys.basename(filename) + " as " + new_objname # A method to do a 'Set Smooth' on a mesh def set_smooth(mesh): for face in mesh.faces: face.smooth = 1 mesh.update(1) # start nwightma # A method to remove double verts, using standard "remove doubles" code def remove_doubles(obj): mesh = obj.getData(mesh=1) # select all verts for v in mesh.verts: v.sel=1 mesh.remDoubles(0.001) # end nwightma # start nwightma # WARNING : this will scan all verts and could affect the overall performance # why doesnt getBoundingBox work def find_center(verts): count = 0 sum_x = 0 sum_y = 0 sum_z = 0 for i in range(len(verts)): x, y, z = verts[i] sum_x = sum_x + x sum_y = sum_y + y sum_z = sum_z + z count = count + 1 xavg = sum_x / count yavg = sum_y / count zavg = sum_z / count return [xavg, yavg, zavg] # end nwightma # start nwightma # a center new operation def center_new(OB, center): #-------------------------- # center new mesh object jmsoler 2005 #-------------------------- # see http://jmsoler.free.fr/didacticiel/blender/tutor/cpl_pythonresizerot.htm#centernew ME=OB.getData() MAT= OB.getMatrix() ME.transform(MAT) ME.update() MAT=Blender.Mathutils.Matrix( [1.0,0.0,0.0,0.0], [0.0,1.0,0.0,0.0], [0.0,0.0,1.0,0.0], [0.0,0.0,0.0,1.0]) OB.setMatrix(MAT) #BB= OB.getBoundBox() # BB always appears to be -1 values #CU=[BB[0][n]+(BB[-2][n]-BB[0][n])/2.0 for n in [0,1,2]] CU = center # use from calculation from verts MAT= OB.getMatrix() MAT=Blender.Mathutils.Matrix(MAT[0][:], MAT[1][:], MAT[2][:], [-CU[0],-CU[1],-CU[2],1.0]) ME.transform(MAT) ME.update() MAT=Blender.Mathutils.Matrix(MAT[0][:], MAT[1][:], MAT[2][:], [CU[0],CU[1],CU[2],1.0]) OB.setMatrix(MAT) # end nwightma ###################################################### # 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)