Last active
May 28, 2021 18:15
-
-
Save willstott101/f3b812f31396e1fd16ac to your computer and use it in GitHub Desktop.
.stl to .obj file converter and slight api.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from struct import unpack | |
import math | |
try: | |
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: | |
self.name = 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[1]], | |
[vert[2]], | |
[1]]) | |
vert = tra.dot(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: | |
obj.write(vert_str.format(*face[1])) | |
obj.write(vert_str.format(*face[2])) | |
obj.write(vert_str.format(*face[3])) | |
count += 3 | |
name = name or self.name 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 self.name 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('endloop\n') | |
stlfile.write('endfacet\n') | |
stlfile.write('endsolid %s\n' % name) | |
class AsciiSTLFile(_STLFile): | |
name = None | |
def __init__(self, *args, **kwargs): | |
super().__init__(*args, **kwargs) | |
if self.name is None: | |
self.name = 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 self.name is None: | |
self.name = 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: | |
stlfile.seek(80) | |
length = unpack('<I', stlfile.read(4))[0] | |
for ___ in range(length): | |
# Normal, V1, V2, V3 | |
yield unpack('3f', stlfile.read(12)) | |
yield unpack('3f', stlfile.read(12)) | |
yield unpack('3f', stlfile.read(12)) | |
yield unpack('3f', stlfile.read(12)) | |
stlfile.seek(2, 1) | |
def open_stl(filename, *args, **kwargs): | |
try: | |
with open(filename) as stlfile: | |
word = stlfile.readline(5) | |
if word == 'solid': | |
return AsciiSTLFile(filename, *args, **kwargs) | |
except UnicodeDecodeError: | |
pass | |
return BinarySTLFile(filename, *args, **kwargs) | |
def main(): | |
# http://www.lfd.uci.edu/~gohlke/code/transformations.py.html | |
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]) | |
print(tra) | |
stl.transformation = tra | |
stl.to_obj(obj) | |
with open('cube-ascii.stl', 'w') as asciistl: | |
stl.to_ascii(asciistl) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
If you have time, could you please explain how the code works.Thank you