├── README.md └── convertSTL.rb /README.md: -------------------------------------------------------------------------------- 1 | convertSTL 2 | ========== 3 | 4 | A utility for converting STL files between ASCII and binary encoding. 5 | I wrote this to save disk space and bandwidth when handling STL files. Some modeling programs include this functionality, but it is nice to have something that is more lightweight and can be called from a command line. 6 | 7 | USAGE 8 | --------- 9 | $ ruby convertSTL.rb [filename(s) of .stl to be converted] 10 | or 'chmod +x' the script and run as: 11 | 12 | $ ./convertSTL.rb [filename(s) of .stl to be converted] 13 | The script will then translate the STL to the opposite encoding and save it as either `-ascii.stl` or `-binary.stl` 14 | 15 | AUTHOR 16 | ----------- 17 | Created by Chris Polis([@ChrisPolis](http://twitter.com/chrispolis)) under the MIT License 18 | -------------------------------------------------------------------------------- /convertSTL.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # convertSTL.rb - Converts STL files between binary and ASCII encoding 3 | # by Chris Polis 4 | # 5 | # This script detects which encoding the stl file is in and converts it to 6 | # the opposite encoding and saves the file as *-ascii.stl or *-binary.stl. 7 | # I wrote this script to save disk space and bandwidth when using stl files. 8 | # 9 | # USAGE: 10 | # $ ruby convertSTL.rb [filename(s) of .stl to be converted] 11 | # or 'chmod +x' and run as ./convertSTL.rb 12 | # 13 | # note: path wildcards, (e.g.: "*.stl") are supported and all matching 14 | # files will be converted 15 | 16 | 17 | # Helper methods 18 | class Float 19 | def to_sn # to scientific notation 20 | "%E" % self 21 | end 22 | 23 | def self.from_sn str # generate a float from scientific notation 24 | ("%f" % str).to_f 25 | end 26 | end 27 | 28 | # Pass in filename as only argument 29 | if ARGV.size == 0 30 | puts "Usage: ./converSTL.rb [stl filename]" 31 | exit 32 | end 33 | 34 | begin 35 | # Read all files matching arg, "foo.stl", "*.stl", "../stl/*", etc... 36 | ARGV.each do |originalFilename| 37 | original = File.new(originalFilename, "r") 38 | 39 | # Read first line - check binary or ASCII 40 | tempLine = original.gets 41 | if tempLine.start_with? "solid" 42 | outFilename = originalFilename.sub(/\.stl/i, '-binary.stl') 43 | puts "#{originalFilename} is in ASCII format, converting to BINARY: #{outFilename}" 44 | outFile = File.new(outFilename, "w") 45 | outFile.write("\0" * 80) # 80 bit header - ignored 46 | outFile.write("FFFF") # 4 bit integer # of triangles - filled later 47 | triCount = 0 48 | 49 | # ASCII STL format (from Wikipedia): 50 | # solid name(optional) 51 | # 52 | # [foreach triangle] 53 | # facet normal ni nj nk 54 | # outer loop 55 | # vertex v1x v1y v1z 56 | # vertex v2x v2y v2z 57 | # vertex v3x v3y v3z 58 | # endloop 59 | # endfacet 60 | # endsolid name(optional) 61 | 62 | while temp = original.gets 63 | next if temp =~ /^\s*$/ or temp.include? 'endsolid' # ignore whitespace 64 | temp.sub! /facet normal/, '' 65 | normal = temp.split(' ').map{ |num| Float.from_sn num } 66 | triCount += 1 67 | temp = original.gets # 'outer loop' 68 | 69 | temp = original.gets 70 | vertexA = temp.sub(/vertex/, '').split(' ').map{ |num| Float.from_sn num } 71 | temp = original.gets 72 | vertexB = temp.sub(/vertex/, '').split(' ').map{ |num| Float.from_sn num } 73 | temp = original.gets 74 | vertexC = temp.sub(/vertex/, '').split(' ').map{ |num| Float.from_sn num } 75 | 76 | temp = original.gets # 'endsolid' 77 | temp = original.gets # 'endfacet' 78 | 79 | outFile.write(normal.pack("FFF")) 80 | outFile.write(vertexA.pack("FFF")) 81 | outFile.write(vertexB.pack("FFF")) 82 | outFile.write(vertexC.pack("FFF")) 83 | outFile.write("\0\0") 84 | end 85 | outFile.seek(80, IO::SEEK_SET) 86 | outFile.write([ triCount ].pack("V")) 87 | outFile.close 88 | 89 | else 90 | outFilename = originalFilename.sub(/\.stl/i, '-ascii.stl') 91 | puts "#{originalFilename} is in BINARY format, converting to ASCII: #{outFilename}" 92 | outFile = File.new(outFilename, "w") 93 | outFile.write("solid \n") 94 | 95 | # Binary STL format (from Wikipedia): 96 | # UINT8[80] – Header 97 | # UINT32 – Number of triangles 98 | # 99 | # foreach triangle 100 | # REAL32[3] – Normal vector 101 | # REAL32[3] – Vertex 1 102 | # REAL32[3] – Vertex 2 103 | # REAL32[3] – Vertex 3 104 | # UINT16 – Attribute byte count 105 | # end 106 | original.seek(80, IO::SEEK_SET) 107 | triCount = original.read(4).unpack('V')[0] 108 | triCount.times do |triNdx| 109 | normal = original.read(12).unpack('FFF') 110 | vertexA = original.read(12).unpack('FFF') 111 | vertexB = original.read(12).unpack('FFF') 112 | vertexC = original.read(12).unpack('FFF') 113 | original.seek(2, IO::SEEK_CUR) 114 | 115 | outFile.write(" facet normal #{normal[0].to_sn} #{normal[1].to_sn} #{normal[2].to_sn}\n") 116 | outFile.write(" outer loop\n") 117 | outFile.write(" vertex #{vertexA[0].to_sn} #{vertexA[1].to_sn} #{vertexA[2].to_sn}\n") 118 | outFile.write(" vertex #{vertexB[0].to_sn} #{vertexB[1].to_sn} #{vertexB[2].to_sn}\n") 119 | outFile.write(" vertex #{vertexC[0].to_sn} #{vertexC[1].to_sn} #{vertexC[2].to_sn}\n") 120 | outFile.write(" endloop\n") 121 | outFile.write(" endfacet\n") 122 | end 123 | 124 | outFile.write("endsolid \n") 125 | outFile.close 126 | end 127 | original.close 128 | end 129 | rescue => error 130 | puts "Error: #{error}" 131 | end 132 | --------------------------------------------------------------------------------