11/*
22The MIT License (MIT)
33
4- Copyright (c) 2014 CCHall (aka Cyanobacterium aka cyanobacteruim)
4+ Copyright (c) 2014 CCHall (aka Cyanobacterium aka cyanobacteruim), 2017 Andrew Goh
55
66Permission is hereby granted, free of charge, to any person obtaining a copy
77of this software and associated documentation files (the "Software"), to deal
@@ -33,15 +33,24 @@ of this software and associated documentation files (the "Software"), to deal
3333import java .nio .file .Files ;
3434import java .nio .file .Path ;
3535import java .util .ArrayList ;
36+ import java .util .Arrays ;
3637import java .util .List ;
38+ import java .util .StringTokenizer ;
3739import java .util .logging .Level ;
3840import java .util .logging .Logger ;
3941
42+ import sun .security .util .ByteArrayTagOrder ;
43+
4044/**
4145 * This class is a parser for STL files. Currently, normals specified in the
4246 * file are ignored and recalculated under the assumption that the coordinates
4347 * are provided in right-handed coordinate space (counter-clockwise).
4448 * @author CCHall
49+ * @author Andrew Goh
50+ *
51+ * -reversion: mar 2017 Andrew
52+ * updated logic to handle binary STL files with "solid" in the header
53+ *
4554 */
4655public class STLParser {
4756 /**
@@ -55,28 +64,116 @@ public class STLParser {
5564 * @throws IllegalArgumentException Thrown if the STL is not properly
5665 * formatted
5766 */
58- public static List <Triangle > parseSTLFile (Path filepath ) throws IOException {
67+ public List <Triangle > parseSTLFile (Path filepath )
68+ throws IOException , IllegalArgumentException {
5969 byte [] allBytes = Files .readAllBytes (filepath );
6070 // determine if it is ASCII or binary STL
61- Charset charset = Charset .forName ("UTF-8" );
62- CharBuffer decode = charset .decode (ByteBuffer .wrap (allBytes , 0 , 80 ));
63- String headerString = decode .toString ();
64- int index = 0 ;
65- while (Character .isWhitespace (headerString .charAt (index )) && index < 80 ){
66- index ++;
71+
72+ //some binary STL files has "solid" in the first 80 chars
73+ //this breaks logic that determines if a file is ascii based on it
74+ //simply beginning with "solid"
75+ boolean isASCIISTL = false ;
76+
77+ //read the first 512 chars or less
78+ String buf = readblock (allBytes , 0 , 512 );
79+ StringBuffer sb = new StringBuffer ();
80+ int inl = readline (buf , sb , 0 );
81+ String line = sb .toString ();
82+ StringTokenizer st = new StringTokenizer (line );
83+ String token = st .nextToken ();
84+ if (token .equals ("solid" )) { //start with "solid"
85+ if (inl >-1 ) { //found new line for next line
86+ sb = new StringBuffer ();
87+ inl = readline (buf , sb , inl +1 ); //read next line, update inl
88+ line = sb .toString ();
89+ st = new StringTokenizer (line );
90+ token = st .nextToken ();
91+ if (token .equals ("endsolid" ))
92+ isASCIISTL = true ; //empty ascii file
93+ else if (token .equals ("facet" )) {
94+ isASCIISTL = true ; //ascii file
95+ } else if (isbinaryfile (allBytes ))
96+ isASCIISTL = false ; //binary file
97+ } else { //no linefeed
98+ if (isbinaryfile (allBytes ))
99+ isASCIISTL = false ; //binary file
100+ }
101+ } else {//does not starts with "solid"
102+ if (isbinaryfile (allBytes ))
103+ isASCIISTL = false ; //binary file
67104 }
68- String firstWord = headerString .substring (index );
69- boolean isASCIISTL = (firstWord .toLowerCase ().startsWith ("solid" ));
70-
105+
71106 // read file to array of triangles
72107 List <Triangle > mesh ;
73108 if (isASCIISTL ){
109+ Charset charset = Charset .forName ("UTF-8" );
74110 mesh = readASCII (charset .decode (ByteBuffer .wrap (allBytes )).toString ().toLowerCase ());
75111 } else {
76112 mesh = readBinary (allBytes );
77113 }
78114 return mesh ;
79115 }
116+
117+ public String readblock (byte [] allBytes , int offset , int length ) {
118+ if (allBytes .length -offset <length ) length = allBytes .length -offset ;
119+ Charset charset = Charset .forName ("UTF-8" );
120+ CharBuffer decode = charset .decode (ByteBuffer .wrap (allBytes , offset , length ));
121+ return decode .toString ().toLowerCase ();
122+ }
123+
124+ public int readline (String buf , StringBuffer sb , int offset ) {
125+ int il = buf .indexOf ('\n' , offset );
126+ if (il >-1 )
127+ sb .append (buf .substring (offset , il -1 ));
128+ else
129+ sb .append (buf .substring (offset ));
130+ return il ;
131+ }
132+
133+ public String lastline (String buf ) {
134+ int i = buf .length ();
135+ while (--i >-1 ) {
136+ if (buf .charAt (i ) == '\n' )
137+ break ;
138+ }
139+ if (i >-1 )
140+ return buf .substring (i +1 );
141+ else
142+ return "" ;
143+ }
144+
145+ public boolean isbinaryfile (byte [] allBytes ) throws IllegalArgumentException {
146+ if (allBytes .length <84 )
147+ throw new IllegalArgumentException ("invalid binary file, length<84" );
148+ int numtriangles = byteatoint (Arrays .copyOfRange (allBytes , 80 , 84 ));
149+ if (allBytes .length >= 84 + numtriangles * 50 )
150+ return true ; //is binary file
151+ else {
152+ String msg = "invalid binary file, num triangles does not match length specs" ;
153+ throw new IllegalArgumentException (msg );
154+ }
155+ }
156+
157+ // little endian
158+ public int byteatoint (byte [] bytes ) {
159+ assert (bytes .length == 4 );
160+ int r = 0 ;
161+ r = bytes [0 ] & 0xff ;
162+ r |= (bytes [1 ] & 0xff ) << 8 ;
163+ r |= (bytes [2 ] & 0xff ) << 16 ;
164+ r |= (bytes [3 ] & 0xff ) << 24 ;
165+ return r ;
166+ }
167+
168+ public byte [] inttobytea (int value ) {
169+ byte bytes [] = new byte [4 ];
170+ bytes [0 ] = (byte ) value ;
171+ bytes [1 ] = (byte )(value >> 8 );
172+ bytes [2 ] = (byte )(value >> 16 );
173+ bytes [3 ] = (byte )(value >> 24 );
174+ return bytes ;
175+ }
176+
80177 /**
81178 * Reads an STL ASCII file content provided as a String
82179 * @param content ASCII STL
@@ -85,7 +182,7 @@ public static List<Triangle> parseSTLFile(Path filepath) throws IOException{
85182 * @throws IllegalArgumentException Thrown if the STL is not properly
86183 * formatted
87184 */
88- public static List <Triangle > readASCII (String content ) throws IllegalArgumentException {
185+ public List <Triangle > readASCII (String content ) throws IllegalArgumentException {
89186 Logger .getLogger (STLParser .class .getName ()).log (Level .FINEST ,"Parsing ASCII STL format" );
90187 // string is lowercase
91188 ArrayList <Triangle > triangles = new ArrayList <>();
@@ -149,7 +246,7 @@ public static List<Triangle> readASCII(String content) throws IllegalArgumentExc
149246 * @throws IllegalArgumentException Thrown if the STL is not properly
150247 * formatted
151248 */
152- public static List <Triangle > readBinary (byte [] allBytes ) throws IllegalArgumentException {
249+ public List <Triangle > readBinary (byte [] allBytes ) throws IllegalArgumentException {
153250 Logger .getLogger (STLParser .class .getName ()).log (Level .FINEST ,"Parsing binary STL format" );
154251 DataInputStream in = new DataInputStream (new ByteArrayInputStream (allBytes ));
155252 ArrayList <Triangle > triangles = new ArrayList <>();
0 commit comments