Skip to content

Instantly share code, notes, and snippets.

Last active May 28, 2021 18:15
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save willstott101/f3b812f31396e1fd16ac to your computer and use it in GitHub Desktop.
.stl to .obj file converter and slight api.
from struct import unpack
import math
import numpy as np
except ImportError:
print('numpy is required to enable STL transformation.')
class _STLFile(object):
name = None
transformation = None
def __init__(self, filename, name=None, transformation=None):
self.filename = filename
if name is not None: = name
if transformation is not None:
self.transformation = transformation
def transform_vert(self, vert):
tra = np.array(self.transformation)
vert = np.array([[vert[0]],
vert =
return np.array(vert).flatten()[:-1]
def __iter__(self):
itr = self.read_vectors()
while True:
n = next(itr)
verts = [next(itr), next(itr), next(itr)]
if self.transformation is not None:
verts = [self.transform_vert(v) for v in verts]
yield n, verts[0], verts[1], verts[2]
def read_vectors(self):
raise NotImplementedError()
def to_obj(self, obj, name=None, offset=0):
obj.write('g default\n')
vert_str = 'v {0:6f} {1:6f} {2:6f}\n'
count = 0
for face in self:
count += 3
name = name or or 'default'
obj.write('s 1\ng %s\n' % name)
for i in range(offset, offset + count, 3):
obj.write('f %s %s %s\n' % (i+1, i+2, i+3))
return count
def to_ascii(self, stlfile, name=None):
name = name or or 'default'
stlfile.write('solid %s\n' % name)
for face in self:
stlfile.write('facet normal {:6f} {:6f} {:6f}\n'.format(*face[0]))
stlfile.write('outer loop\n')
stlfile.write('vertex {:6f} {:6f} {:6f}\n'.format(*face[1]))
stlfile.write('vertex {:6f} {:6f} {:6f}\n'.format(*face[2]))
stlfile.write('vertex {:6f} {:6f} {:6f}\n'.format(*face[3]))
stlfile.write('endsolid %s\n' % name)
class AsciiSTLFile(_STLFile):
name = None
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if is None: = self.read_name()
def read_name(self):
with open(self.filename) as stlfile:
line = next(stlfile)
name = line.split(' ', 1)[1]
return name
def read_vectors(self):
with open(self.filename) as stlfile:
name = self.read_name()
if is None: = name
line = next(stlfile).strip()
while line.startswith('facet normal'):
# facet normal # # #
yield [float(n) for n in line.split()[-3:]]
next(stlfile) # outer loop
for ___ in range(3): # vertex # # # (x3)
verts = next(stlfile).strip().split()[-3:]
yield [float(v) for v in verts]
next(stlfile) # endloop
next(stlfile) # endfacet
line = next(stlfile).strip()
class BinarySTLFile(_STLFile):
def read_vectors(self):
with open(self.filename, 'rb') as stlfile:
length = unpack('<I',[0]
for ___ in range(length):
# Normal, V1, V2, V3
yield unpack('3f',
yield unpack('3f',
yield unpack('3f',
yield unpack('3f',, 1)
def open_stl(filename, *args, **kwargs):
with open(filename) as stlfile:
word = stlfile.readline(5)
if word == 'solid':
return AsciiSTLFile(filename, *args, **kwargs)
except UnicodeDecodeError:
return BinarySTLFile(filename, *args, **kwargs)
def main():
from . import transformations as t_
stl = open_stl('cube.stl')
with open('cube.obj', 'w') as obj:
tra = t_.compose_matrix(
scale=[0.5, 0.5, 0.5],
translate=[-2, 1, 2],
angles=[math.pi/4, 0, math.pi/5])
stl.transformation = tra
with open('cube-ascii.stl', 'w') as asciistl:
if __name__ == '__main__':
Copy link

Prarthana1116 commented Sep 21, 2018

If you have time, could you please explain how the code works.Thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment