/** * File: STLtoTRI.java * Author: Marsette Vona * Created: 3/14/10 **/ import java.io.*; import java.util.*; import static java.io.StreamTokenizer.*; /** *

This is a program to convert an ASCII STL file into the TRI format used * in CS4300 Homework 4.

* *

To compile: javac STLtoTRI.java

* *

To generate doc: javadoc -d doc STLtoTRI.java

* *

To run: java STLtoTRI [scale] [R G B] < input.stl > output.tri

* *

Scale must be a floating point number. All coordinates in the input will * be scaled by the indicated amount. Scale defaults to 1.0.

* *

R, G, and B must be floating point numbers in the range 0.0 to 1.0 (they * will be clamped if outside this range). All vertices of triangles in the * output are assigned this color. If omitted, {@link #DEF_COLOR} is used. * *

Note: this code may require Java 1.6 or greater.

* *

STL file format documentation * is available on Wikipedia.

**/ public class STLtoTRI { /** default scale **/ public static final float DEF_SCALE = 1.0f; /** default color **/ public static final float[] DEF_COLOR = {128.0f/255.0f, 163.0f/255.0f, 201.0f/255.0f}; /** maximum absolute output value **/ public static final float MAX_ABS_OUTPUT_VALUE = 9999.9999f; /** see class header doc **/ public static void main(String[] arg) { //parse command line arguments float[] color = new float[3]; for (int i = 0; i < 3; i++) color[i] = DEF_COLOR[i]; float scale = DEF_SCALE; if ((arg.length != 0) && (arg.length != 1) && (arg.length != 4)) { System.err.println("E: invalid command line"); printUsage(); System.exit(1); } if ((arg.length == 1) || (arg.length == 4)) { //scale specified on command line try { scale = Float.parseFloat(arg[0]); } catch (NumberFormatException nfe) { System.err.println("E: error parsing scale as a float"); printUsage(); System.exit(1); } } if ((arg.length == 3) || (arg.length == 4)) { //color specified on command line int first = (arg.length > 3) ? 1 : 0; for (int i = 0; i < 3; i++) { try { color[i] = Float.parseFloat(arg[first+i]); } catch (NumberFormatException nfe) { System.err.println( "E: error parsing color component "+i+" as a float"); printUsage(); System.exit(1); } if (color[i] < 0.0) { System.err.println("W: clamping color component "+i+" to 0.0"); color[i] = 0.0f; } if (color[i] > 1.0) { System.err.println("W: clamping color component "+i+" to 1.0"); color[i] = 1.0f; } } } //set up the tokenizer StreamTokenizer t = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in))); //we must parse numbers as words because StreamTokenizer does not //understand scientific notation t.resetSyntax(); t.wordChars('a', 'z'); t.wordChars('A', 'Z'); t.wordChars('0', '9'); t.wordChars('+', '+'); t.wordChars('-', '-'); t.wordChars('.', '.'); t.whitespaceChars('\u0000', '\u0020'); t.commentChar('#'); t.eolIsSignificant(false); t.lowerCaseMode(true); //parse input and generate output try { //parse header if (!parseWord(t, "solid", "E: input does not look like ASCII STL")) System.exit(1); String name = parseAny(t, "E: expected model name"); if (name == null) System.exit(1); System.out.println( "# generated by STLtoTRI from ASCII STL model \""+name+"\""); System.out.println( "# STL to TRI conversion scale factor: "+scale); System.out.println( "# number of output triangles and bounding box at end of file"); System.out.print("# STL to TRI conversion vertex color: "); printArray(color, false); //parse and convert triangles float[][] v = new float[3][3]; float[] bbMin = new float[3], bbMax = new float[3]; for (int i = 0; i < 3; i++) { bbMin[i] = Float.POSITIVE_INFINITY; bbMax[i] = Float.NEGATIVE_INFINITY; } int n = 0; for (;;) { t.nextToken(); if (t.ttype != TT_WORD) { System.err.println( "W: parse error, expected \"facet\" or \"endsolid\": "+t); break; } if ("facet".equals(t.sval)) { //ignore normal if (!parseArray(t, "normal", v[0])) break; //parse vertex coordinates if (!parseWord(t, "outer")) break; if (!parseWord(t, "loop")) break; for (int i = 0; i < 3; i++) if (!parseArray(t, "vertex", v[i])) break; if (!parseWord(t, "endloop")) break; if (!parseWord(t, "endfacet")) break; //generate output for this triangle boolean inBounds = true; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { v[i][j] *= scale; if (Math.abs(v[i][j]) > MAX_ABS_OUTPUT_VALUE) inBounds = false; } } if (inBounds) { for (int i = 0; i < 3; i++) { //print vertex coords printArray(v[i], true); //update bounding box for (int j = 0; j < 3; j++) { if (v[i][j] < bbMin[j]) bbMin[j] = v[i][j]; if (v[i][j] > bbMax[j]) bbMax[j] = v[i][j]; } } //print vertex colors printArray(color, true); printArray(color, true); printArray(color, false); System.out.flush(); n++; } else { System.err.println( "W: ignoring input triangle with vertex coordinate larger than "+ MAX_ABS_OUTPUT_VALUE+": "+t); } } else if ("endsolid".equals(t.sval)) { if (parseAny(t, "W: expected model name") == null) break; t.nextToken(); if (t.ttype != TT_EOF) System.err.println("W: expected end of file: "+t); break; } else { //unexpected token value System.err.println("E: expected \"facet\" or \"endsolid\": "+t); break; } } System.out.println("# "+n+" triangles"); if (n > 0) { System.out.print("# bounding box min: "); printArray(bbMin, false); System.out.print("# bounding box max: "); printArray(bbMax, false); } System.out.flush(); } catch (IOException ioe) { System.err.println("E: I/O exception: "+ioe); System.exit(1); } } /** display a usage message on stderr **/ protected static void printUsage() { System.err.println( "USAGE: java STLtoTRI [scale] [R G B] < input.stl > output.tri"); } /** *

Try to parse a specific word from a tokenizer.

* * @param t the tokenizer to read from * @param word the word to expect * @param msg the message to display on error, null for default * * @return true iff word was successfully parsed **/ protected static boolean parseWord(StreamTokenizer t, String word, String msg) throws IOException { if (msg == null) msg = "E: parse error"; t.nextToken(); if ((t.ttype != TT_WORD) || !word.equals(t.sval)) { System.err.println(msg+", expected \""+word+"\": "+t); return false; } return true; } /** *

{@link #parseWord(StreamTokenizer, String, String)} with default * message.

**/ protected static boolean parseWord(StreamTokenizer t, String word) throws IOException { return parseWord(t, word, null); } /** *

Try to parse a any word from a tokenizer.

* * @param t the tokenizer to read from * @param msg the message to display on error, null for default * * @return the parsed word or null if none **/ protected static String parseAny(StreamTokenizer t, String msg) throws IOException { if (msg == null) msg = "E: parse error"; t.nextToken(); if (t.ttype == TT_EOF) { System.err.println(msg+": "+t); return null; } return t.sval; } /** *

{@link #parseAny(StreamTokenizer, String)} with default message.

**/ protected static String parseAny(StreamTokenizer t) throws IOException { return parseAny(t, null); } /** *

Try to parse an array of floats from a tokenizer.

* * @param t the tokenizer to read from * @param word the prefix word * @param v the array to read * @param msg the message to display on error, null for default * * @return true iff v.length floats were successfully parsed **/ protected static boolean parseArray(StreamTokenizer t, String word, float[] v, String msg) throws IOException { if (msg == null) msg = "E: parse error"; if (!parseWord(t, word, msg)) return false; for (int i = 0; i < v.length; i++) { t.nextToken(); if (t.ttype != TT_WORD) { System.err.println(msg+", expected number: "+t); return false; } try { v[i] = "nan".equals(t.sval) ? Float.NaN : Float.parseFloat(t.sval); } catch (NumberFormatException nfe) { System.err.println(msg+", expected number: "+t); return false; } } return true; } /** *

{@link #parseArray(StreamTokenizer, String, float[], String)} with * default message.

**/ protected static boolean parseArray(StreamTokenizer t, String word, float[] v) throws IOException { return parseArray(t, word, v, null); } /** *

Print an array to stdout.

* * @param v the array to print * @param trailingSpace whether to print a trailing space character, * otherwise print newline **/ protected static void printArray(float[] v, boolean trailingSpace) { for (int i = 0; i < v.length; i++) { System.out.printf("%.4f", v[i]); if (i < (v.length-1)) { System.out.print(" "); } else { if (trailingSpace) System.out.print(" "); else System.out.println(); } } } }