Package: numpy-stl
Simple library to make working with STL files (and 3D objects in general) fast
and easy.Due to all operations heavily relying onnumpythis is one of the fastest
STL editing libraries for Python available.Security contact informationTo report a security vulnerability, please use theTidelift security contact.
Tidelift will coordinate the fix and disclosure.IssuesIf you encounter any issues, make sure you report themhere. Be sure to search for existing issues however. Many issues have been covered before.
While this project usesnumpyas it’s main dependency, it is not in any way affiliated to thenumpyproject or the NumFocus organisation.LinksThe source:https://github.com/WoLpH/numpy-stlProject page:https://pypi.python.org/pypi/numpy-stlReporting bugs:https://github.com/WoLpH/numpy-stl/issuesDocumentation:http://numpy-stl.readthedocs.org/en/latest/My blog:https://wol.ph/Requirements for installing:numpyany recent versionpython-utilsversion 1.6 or greaterInstallation:pip install numpy-stlInitial usage:After installing the package, you should be able to run the following commands
similar to how you can runpip.$stl2binyour_ascii_stl_file.stlnew_binary_stl_file.stl$stl2asciiyour_binary_stl_file.stlnew_ascii_stl_file.stl$stlyour_ascii_stl_file.stlnew_binary_stl_file.stlContributing:Contributions are always welcome. Please view the guidelines to get started:https://github.com/WoLpH/numpy-stl/blob/develop/CONTRIBUTING.rstQuickstartimportnumpyfromstlimportmesh# Using an existing stl file:your_mesh=mesh.Mesh.from_file('some_file.stl')# Or creating a new mesh (make sure not to overwrite the `mesh` import by# naming it `mesh`):VERTICE_COUNT=100data=numpy.zeros(VERTICE_COUNT,dtype=mesh.Mesh.dtype)your_mesh=mesh.Mesh(data,remove_empty_areas=False)# The mesh normals (calculated automatically)your_mesh.normals# The mesh vectorsyour_mesh.v0,your_mesh.v1,your_mesh.v2# Accessing individual points (concatenation of v0, v1 and v2 in triplets)assert(your_mesh.points[0][0:3]==your_mesh.v0[0]).all()assert(your_mesh.points[0][3:6]==your_mesh.v1[0]).all()assert(your_mesh.points[0][6:9]==your_mesh.v2[0]).all()assert(your_mesh.points[1][0:3]==your_mesh.v0[1]).all()your_mesh.save('new_stl_file.stl')Plotting usingmatplotlibis equally easy:fromstlimportmeshfrommpl_toolkitsimportmplot3dfrommatplotlibimportpyplot# Create a new plotfigure=pyplot.figure()axes=figure.add_subplot(projection='3d')# Load the STL files and add the vectors to the plotyour_mesh=mesh.Mesh.from_file('tests/stl_binary/HalfDonut.stl')axes.add_collection3d(mplot3d.art3d.Poly3DCollection(your_mesh.vectors))# Auto scale to the mesh sizescale=your_mesh.points.flatten()axes.auto_scale_xyz(scale,scale,scale)# Show the plot to the screenpyplot.show()Experimental support for reading 3MF filesimportpathlibimportstlpath=pathlib.Path('tests/3mf/Moon.3mf')# Load the 3MF fileforminstl.Mesh.from_3mf_file(path):# Do something with the meshprint('mesh',m)Note that this is still experimental and may not work for all 3MF files.
Additionally it only allows reading 3mf files, not writing them.Modifying Mesh objectsfromstlimportmeshimportmathimportnumpy# Create 3 faces of a cubedata=numpy.zeros(6,dtype=mesh.Mesh.dtype)# Top of the cubedata['vectors'][0]=numpy.array([[0,1,1],[1,0,1],[0,0,1]])data['vectors'][1]=numpy.array([[1,0,1],[0,1,1],[1,1,1]])# Front facedata['vectors'][2]=numpy.array([[1,0,0],[1,0,1],[1,1,0]])data['vectors'][3]=numpy.array([[1,1,1],[1,0,1],[1,1,0]])# Left facedata['vectors'][4]=numpy.array([[0,0,0],[1,0,0],[1,0,1]])data['vectors'][5]=numpy.array([[0,0,0],[0,0,1],[1,0,1]])# Since the cube faces are from 0 to 1 we can move it to the middle by# substracting .5data['vectors']-=.5# Generate 4 different meshes so we can rotate them latermeshes=[mesh.Mesh(data.copy())for_inrange(4)]# Rotate 90 degrees over the Y axismeshes[0].rotate([0.0,0.5,0.0],math.radians(90))# Translate 2 points over the X axismeshes[1].x+=2# Rotate 90 degrees over the X axismeshes[2].rotate([0.5,0.0,0.0],math.radians(90))# Translate 2 points over the X and Y pointsmeshes[2].x+=2meshes[2].y+=2# Rotate 90 degrees over the X and Y axismeshes[3].rotate([0.5,0.0,0.0],math.radians(90))meshes[3].rotate([0.0,0.5,0.0],math.radians(90))# Translate 2 points over the Y axismeshes[3].y+=2# Optionally render the rotated cube facesfrommatplotlibimportpyplotfrommpl_toolkitsimportmplot3d# Create a new plotfigure=pyplot.figure()axes=figure.add_subplot(projection='3d')# Render the cube facesforminmeshes:axes.add_collection3d(mplot3d.art3d.Poly3DCollection(m.vectors))# Auto scale to the mesh sizescale=numpy.concatenate([m.pointsforminmeshes]).flatten()axes.auto_scale_xyz(scale,scale,scale)# Show the plot to the screenpyplot.show()Extending Mesh objectsfromstlimportmeshimportmathimportnumpy# Create 3 faces of a cubedata=numpy.zeros(6,dtype=mesh.Mesh.dtype)# Top of the cubedata['vectors'][0]=numpy.array([[0,1,1],[1,0,1],[0,0,1]])data['vectors'][1]=numpy.array([[1,0,1],[0,1,1],[1,1,1]])# Front facedata['vectors'][2]=numpy.array([[1,0,0],[1,0,1],[1,1,0]])data['vectors'][3]=numpy.array([[1,1,1],[1,0,1],[1,1,0]])# Left facedata['vectors'][4]=numpy.array([[0,0,0],[1,0,0],[1,0,1]])data['vectors'][5]=numpy.array([[0,0,0],[0,0,1],[1,0,1]])# Since the cube faces are from 0 to 1 we can move it to the middle by# substracting .5data['vectors']-=.5cube_back=mesh.Mesh(data.copy())cube_front=mesh.Mesh(data.copy())# Rotate 90 degrees over the X axis followed by the Y axis followed by the# X axiscube_back.rotate([0.5,0.0,0.0],math.radians(90))cube_back.rotate([0.0,0.5,0.0],math.radians(90))cube_back.rotate([0.5,0.0,0.0],math.radians(90))cube=mesh.Mesh(numpy.concatenate([cube_back.data.copy(),cube_front.data.copy(),]))# Optionally render the rotated cube facesfrommatplotlibimportpyplotfrommpl_toolkitsimportmplot3d# Create a new plotfigure=pyplot.figure()axes=figure.add_subplot(projection='3d')# Render the cubeaxes.add_collection3d(mplot3d.art3d.Poly3DCollection(cube.vectors))# Auto scale to the mesh sizescale=cube_back.points.flatten()axes.auto_scale_xyz(scale,scale,scale)# Show the plot to the screenpyplot.show()Creating Mesh objects from a list of vertices and facesimportnumpyasnpfromstlimportmesh# Define the 8 vertices of the cubevertices=np.array([\[-1,-1,-1],[+1,-1,-1],[+1,+1,-1],[-1,+1,-1],[-1,-1,+1],[+1,-1,+1],[+1,+1,+1],[-1,+1,+1]])# Define the 12 triangles composing the cubefaces=np.array([\[0,3,1],[1,3,2],[0,4,7],[0,7,3],[4,5,6],[4,6,7],[5,1,2],[5,2,6],[2,3,6],[3,7,6],[0,1,5],[0,5,4]])# Create the meshcube=mesh.Mesh(np.zeros(faces.shape[0],dtype=mesh.Mesh.dtype))fori,finenumerate(faces):forjinrange(3):cube.vectors[i][j]=vertices[f[j],:]# Write the mesh to file "cube.stl"cube.save('cube.stl')Evaluating Mesh properties (Volume, Center of gravity, Inertia)importnumpyasnpfromstlimportmesh# Using an existing closed stl file:your_mesh=mesh.Mesh.from_file('some_file.stl')volume,cog,inertia=your_mesh.get_mass_properties()print("Volume                                  ={0}".format(volume))print("Position of the center of gravity (COG) ={0}".format(cog))print("Inertia matrix at expressed at the COG  ={0}".format(inertia[0,:]))print("{0}".format(inertia[1,:]))print("{0}".format(inertia[2,:]))Combining multiple STL filesimportmathimportstlfromstlimportmeshimportnumpy# find the max dimensions, so we can know the bounding box, getting the height,# width, length (because these are the step size)...deffind_mins_maxs(obj):minx=obj.x.min()maxx=obj.x.max()miny=obj.y.min()maxy=obj.y.max()minz=obj.z.min()maxz=obj.z.max()returnminx,maxx,miny,maxy,minz,maxzdeftranslate(_solid,step,padding,multiplier,axis):if'x'==axis:items=0,3,6elif'y'==axis:items=1,4,7elif'z'==axis:items=2,5,8else:raiseRuntimeError('Unknown axis%r, expected x, y or z'%axis)# _solid.points.shape == [:, ((x, y, z), (x, y, z), (x, y, z))]_solid.points[:,items]+=(step*multiplier)+(padding*multiplier)defcopy_obj(obj,dims,num_rows,num_cols,num_layers):w,l,h=dimscopies=[]forlayerinrange(num_layers):forrowinrange(num_rows):forcolinrange(num_cols):# skip the position where original being copied isifrow==0andcol==0andlayer==0:continue_copy=mesh.Mesh(obj.data.copy())# pad the space between objects by 10% of the dimension being# translatedifcol!=0:translate(_copy,w,w/10.,col,'x')ifrow!=0:translate(_copy,l,l/10.,row,'y')iflayer!=0:translate(_copy,h,h/10.,layer,'z')copies.append(_copy)returncopies# Using an existing stl file:main_body=mesh.Mesh.from_file('ball_and_socket_simplified_-_main_body.stl')# rotate along Ymain_body.rotate([0.0,0.5,0.0],math.radians(90))minx,maxx,miny,maxy,minz,maxz=find_mins_maxs(main_body)w1=maxx-minxl1=maxy-minyh1=maxz-minzcopies=copy_obj(main_body,(w1,l1,h1),2,2,1)# I wanted to add another related STL to the final STLtwist_lock=mesh.Mesh.from_file('ball_and_socket_simplified_-_twist_lock.stl')minx,maxx,miny,maxy,minz,maxz=find_mins_maxs(twist_lock)w2=maxx-minxl2=maxy-minyh2=maxz-minztranslate(twist_lock,w1,w1/10.,3,'x')copies2=copy_obj(twist_lock,(w2,l2,h2),2,2,1)combined=mesh.Mesh(numpy.concatenate([main_body.data,twist_lock.data]+[copy.dataforcopyincopies]+[copy.dataforcopyincopies2]))combined.save('combined.stl',mode=stl.Mode.ASCII)# save as ASCIIKnown limitationsWhen speedups are enabled the STL name is automatically converted to
lowercase.