#!/usr/bin/env python # STL ASCII-to-Binary converter. # Author: Brad Atcheson (atcheson@cs.ubc.ca) # Last modified: December 2010 # # Requirements: # * PYL (available on Python package index: pypi.python.org/pypi) # # Usage: # python stl_ascii2bin.py --input myasciifile.stl --output mybinaryfile.stl import ply.lex as lex import ply.yacc as yacc import struct from optparse import OptionParser ### LEX ###################################################################### # Reserved keywords reserved = { 'solid' : 'solid', 'facet' : 'facet', 'normal' : 'normal', 'outer' : 'outer', 'loop' : 'loop', 'vertex' : 'vertex', 'endloop' : 'endloop', 'endfacet' : 'endfacet', 'endsolid' : 'endsolid' } # List of token names tokens = ['NUMBER', 'ID'] + list(reserved.values()) # Regex rules with action code def t_ID(t): r'[a-zA-Z_][a-zA-Z_0-9]*' # defaults to ID if not a reserved word t.type = reserved.get(t.value, 'ID') return t def t_NUMBER(t): r'-?\d+\.\d+(e-?[0-9]+)?' t.value = float(t.value) return t # For tracking line numbers def t_newline(t): r'\n+' t.lexer.lineno += len(t.value) # Ignored characters t_ignore = ' \t' # Error handling rule def t_error(t): print "Illegal character '%s'" % t.value[0] t.lexer.skip(1) # Build the lexer lexer = lex.lex() # Test it out data = """ solid RotaryStage facet normal 0.0 0.0 0.0 outer loop vertex 1.0 1.0 1.0 vertex 2.0 2.0 2.0 vertex 3.0 3.0 3.0 endloop endfacet facet normal 0.0 0.0 0.0 outer loop vertex -12.5 -65.0 -5.63993296509579e-15 vertex -12.204968407608 -57.9955060272833 -2.8199664825479e-15 vertex 12.5 -65.0 -5.63993296509579e-15 endloop endfacet endsolid RotaryStage """ #lexer.input(data) #for tok in lexer: # print tok ### YACC ##################################################################### def p_vector(p): 'vector : NUMBER NUMBER NUMBER' p[0] = (p[1], p[2], p[3]) def p_loop_helper(p): 'loop_helper : outer loop vertex vector vertex vector vertex vector endloop' p[0] = (p[4], p[6], p[8]) def p_triangle(p): 'triangle : facet normal vector loop_helper endfacet' p[0] = (p[3], p[4]) def p_triangles(p): '''triangles : triangles triangle | triangle''' if len(p) == 2: p[0] = p[1] else: p[0] = p[1] + p[2] def p_object(p): 'object : solid ID triangles endsolid ID' p[0] = p[3] if p[2] != p[5]: print "Warning: solid name mismatch" def p_error(p): print "Syntax error in input" # Build the parser parser = yacc.yacc(start='object') # Test it out #result = parser.parse(data, lexer=lexer) #print("Complete result:") #print result #print("***") ### STL ###################################################################### if __name__ == '__main__': cmd = OptionParser() cmd.add_option("--input", default="input.stl", type="string", metavar='input', help="input filename, ASCII STL format [%default]") cmd.add_option("--output", default="output.stl", type="string", metavar="output", help="output filename, binary STL format [%default]") (opt,args) = cmd.parse_args() with open(opt.input) as f: data = f.read() result = parser.parse(data, lexer=lexer) with open(opt.output, 'wb') as f: # dummy 80byte header f.write( 'X'*80 ) # 32bit UINT count nTriangles = len(result)/2 f.write( struct.pack('=I',nTriangles) ) # 12 FLOAT32 + UINT16 per triangle pairs = zip(result[0::2], result[1::2]) for (normal, vertices) in pairs: (v1,v2,v3) = vertices f.write( struct.pack('='+'f'*12+'H', *(normal+v1+v2+v3+(0,))) )