1 package edu.berkeley.qfat;
2 import com.sun.j3d.loaders.Loader;
3 import com.sun.j3d.loaders.Scene;
4 import com.sun.j3d.loaders.SceneBase;
5 import com.sun.j3d.loaders.IncorrectFormatException;
6 import com.sun.j3d.loaders.ParsingErrorException;
7 import com.sun.j3d.utils.geometry.GeometryInfo;
10 import java.net.MalformedURLException;
12 import java.io.Reader;
13 import java.io.BufferedReader;
14 import java.io.FileReader;
15 import java.io.InputStreamReader;
16 import java.io.FileInputStream;
17 import java.io.FileNotFoundException;
18 import java.io.IOException;
20 import java.util.StringTokenizer;
21 import java.util.ArrayList;
23 import javax.vecmath.Point3f;
24 import javax.vecmath.Vector3f;
26 import javax.media.j3d.BranchGroup;
27 import javax.media.j3d.Shape3D;
29 // New from JDK 1.4 for endian related problems
30 import java.nio.ByteOrder;
31 import java.nio.ByteBuffer;
37 * Description: STL files loader (Supports ASCII and binary files) for Java3D
38 * Needs JDK 1.4 due to endian problems
39 * Company: Universidad del Pais Vasco (UPV/EHU)
40 * @author: Carlos Pedrinaci Godoy
43 * Contact : xenicp@yahoo.es
47 * 1.-We can't read binary files over the net.
48 * 2.-For binary files if size is lower than expected (calculated with the number of faces)
49 * the program will block.
50 * 3.-Improve the way for detecting the kind of stl file?
51 * Can give us problems if the comment of the binary file begins by "solid"
54 public class StlFile implements Loader
56 private static final int DEBUG = 0; // Sets mode to Debug: outputs every action done
58 // Maximum length (in chars) of basePath
59 private static final int MAX_PATH_LENGTH = 1024;
62 private int flag; // Needed cause implements Loader
64 private URL baseUrl = null; // Reading files over Internet
65 private String basePath = null; // For local files
67 private boolean fromUrl = false; // Usefull for binary files
68 private boolean Ascii = true; // File type Ascii -> true o binary -> false
69 private String fileName = null;
71 // Arrays with coordinates and normals
72 // Needed for reading ASCII files because its size is unknown until the end
73 private ArrayList coordList; // Holds Point3f
74 private ArrayList normList; // Holds Vector3f
76 // GeometryInfo needs Arrays
77 public Point3f[] coordArray = null;
78 public Vector3f[] normArray = null;
80 // Needed because TRIANGLE_STRIP_ARRAY
81 // As the number of strips = the number of faces it's filled in objectToVectorArray
82 public int[] stripCounts = null;
84 // Default = Not available
85 private String objectName=new String("Not available");
95 * Method that reads the EOL
96 * Needed for verifying that the file has a correct format
98 * @param parser The file parser. An instance of StlFileParser.
100 private void readEOL(StlFileParser parser)
105 catch (IOException e)
107 System.err.println("IO Error on line " + parser.lineno() + ": " + e.getMessage());
109 if(parser.ttype != StlFileParser.TT_EOL)
111 System.err.println("Format Error:expecting End Of Line on line " + parser.lineno());
116 * Method that reads the word "solid" and stores the object name.
117 * It also detects what kind of file it is
119 * 1.- Better way control of exceptions?
120 * 2.- Better way to decide between Ascii and Binary?
122 * @param parser The file parser. An instance of StlFileParser.
124 private void readSolid(StlFileParser parser)
126 if( !parser.sval.equals("solid"))
128 //System.out.println("Expecting solid on line " + parser.lineno());
129 // If the first word is not "solid" then we consider the file is binary
130 // Can give us problems if the comment of the binary file begins by "solid"
131 this.setAscii(false);
133 else // It's an ASCII file
138 catch (IOException e)
140 System.err.println("IO Error on line " + parser.lineno() + ": " + e.getMessage());
142 if( parser.ttype != StlFileParser.TT_WORD)
144 // Is the object name always provided???
145 System.err.println("Format Error:expecting the object name on line " + parser.lineno());
148 { // Store the object Name
149 this.setObjectName(new String(parser.sval));
152 System.out.println("Object Name:" + this.getObjectName().toString());
154 this.readEOL(parser);
160 * Method that reads a normal
162 * @param parser The file parser. An instance of StlFileParser.
164 private void readNormal(StlFileParser parser)
166 Vector3f v = new Vector3f();
168 if(!(parser.ttype==StlFileParser.TT_WORD && parser.sval.equals("normal")))
170 System.err.println("Format Error:expecting 'normal' on line " + parser.lineno());
174 if (parser.getNumber())
176 v.x=(float)parser.nval;
180 System.out.println("Normal:");
181 System.out.print("X=" + v.x + " ");
184 if (parser.getNumber())
186 v.y=(float)parser.nval;
188 System.out.print("Y=" + v.y + " ");
190 if (parser.getNumber())
192 v.z=(float)parser.nval;
194 System.out.println("Z=" + v.z);
196 // We add that vector to the Normal's array
197 this.normList.add(v);
198 this.readEOL(parser);
200 else System.err.println("Format Error:expecting coordinate on line " + parser.lineno());
202 else System.err.println("Format Error:expecting coordinate on line " + parser.lineno());
204 else System.err.println("Format Error:expecting coordinate on line " + parser.lineno());
206 }// End of Read Normal
209 * Method that reads the coordinates of a vector
211 * @param parser The file parser. An instance of StlFileParser.
213 private void readVertex(StlFileParser parser)
215 Point3f p = new Point3f();
217 if(!(parser.ttype==StlFileParser.TT_WORD && parser.sval.equals("vertex")))
219 System.err.println("Format Error:expecting 'vertex' on line " + parser.lineno());
223 if (parser.getNumber())
225 p.x=(float)parser.nval;
229 System.out.println("Vertex:");
230 System.out.print("X=" + p.x + " ");
233 if (parser.getNumber())
235 p.y=(float)parser.nval;
237 System.out.print("Y=" + p.y + " ");
239 if (parser.getNumber())
241 p.z=(float)parser.nval;
243 System.out.println("Z=" + p.z);
245 // We add that vertex to the array of vertex
249 else System.err.println("Format Error: expecting coordinate on line " + parser.lineno());
251 else System.err.println("Format Error: expecting coordinate on line " + parser.lineno());
253 else System.err.println("Format Error: expecting coordinate on line " + parser.lineno());
255 }//End of read vertex
258 * Method that reads "outer loop" and then EOL
260 * @param parser The file parser. An instance of StlFileParser.
262 private void readLoop(StlFileParser parser)
264 if(!(parser.ttype==StlFileParser.TT_WORD && parser.sval.equals("outer")))
266 System.err.println("Format Error:expecting 'outer' on line " + parser.lineno());
273 catch (IOException e)
275 System.err.println("IO error on line " + parser.lineno() + ": " + e.getMessage());
277 if(!(parser.ttype==StlFileParser.TT_WORD && parser.sval.equals("loop")))
279 System.err.println("Format Error:expecting 'loop' on line " + parser.lineno());
281 else readEOL(parser);
286 * Method that reads "endloop" then EOL
288 * @param parser The file parser. An instance of StlFileParser.
290 private void readEndLoop(StlFileParser parser)
292 if(!(parser.ttype==StlFileParser.TT_WORD && parser.sval.equals("endloop")))
294 System.err.println("Format Error:expecting 'endloop' on line " + parser.lineno());
296 else readEOL(parser);
297 }//End of readEndLoop
300 * Method that reads "endfacet" then EOL
302 * @param parser The file parser. An instance of StlFileParser.
304 private void readEndFacet(StlFileParser parser)
306 if(!(parser.ttype==StlFileParser.TT_WORD && parser.sval.equals("endfacet")))
308 System.err.println("Format Error:expecting 'endfacet' on line " + parser.lineno());
310 else readEOL(parser);
311 }//End of readEndFacet
314 * Method that reads a face of the object
315 * (Cares about the format)
317 * @param parser The file parser. An instance of StlFileParser.
319 private void readFacet(StlFileParser parser)
321 if(!(parser.ttype==StlFileParser.TT_WORD && parser.sval.equals("facet")))
323 System.err.println("Format Error:expecting 'facet' on line " + parser.lineno());
347 readEndFacet(parser);
349 catch (IOException e)
351 System.err.println("IO Error on line " + parser.lineno() + ": " + e.getMessage());
357 * Method that reads a face in binary files
358 * All binary versions of the methods end by 'B'
359 * As in binary files we can read the number of faces, we don't need
360 * to use coordArray and normArray (reading binary files should be faster)
362 * @param in The ByteBuffer with the data of the object.
363 * @param index The facet index
365 * @throws IOException
367 private void readFacetB(ByteBuffer in, int index) throws IOException
369 //File structure: Normal Vertex1 Vertex2 Vertex3
370 Vector3f normal = new Vector3f();
371 Point3f vertex = new Point3f();
374 System.out.println("Reading face number " + index);
377 normArray[index]=new Vector3f();
378 normArray[index].x=in.getFloat();
379 normArray[index].y=in.getFloat();
380 normArray[index].z=in.getFloat();
383 System.out.println("Normal: X=" + normArray[index].x + " Y=" + normArray[index].y + " Z=" + normArray[index].z);
386 coordArray[index*3] = new Point3f();
387 coordArray[index*3].x=in.getFloat();
388 coordArray[index*3].y=in.getFloat();
389 coordArray[index*3].z=in.getFloat();
392 System.out.println("Vertex 1: X=" + coordArray[index*3].x + " Y=" + coordArray[index*3].y + " Z=" + coordArray[index*3].z);
395 coordArray[index*3+1] = new Point3f();
396 coordArray[index*3+1].x=in.getFloat();
397 coordArray[index*3+1].y=in.getFloat();
398 coordArray[index*3+1].z=in.getFloat();
401 System.out.println("Vertex 2: X=" + coordArray[index*3+1].x + " Y=" + coordArray[index*3+1].y + " Z=" + coordArray[index*3+1].z);
404 coordArray[index*3+2] = new Point3f();
405 coordArray[index*3+2].x=in.getFloat();
406 coordArray[index*3+2].y=in.getFloat();
407 coordArray[index*3+2].z=in.getFloat();
410 System.out.println("Vertex 3: X=" + coordArray[index*3+2].x + " Y=" + coordArray[index*3+2].y + " Z=" + coordArray[index*3+2].z);
412 }// End of readFacetB
415 * Method for reading binary files
416 * Execution is completly different
417 * It uses ByteBuffer for reading data and ByteOrder for retrieving the machine's endian
421 * 1.-Be able to read files over Internet
422 * 2.-If the amount of data expected is bigger than what is on the file then
423 * the program will block forever
425 * @param file The name of the file
427 * @throws IOException
429 private void readBinaryFile(String file) throws IOException
431 FileInputStream data; // For reading the file
432 ByteBuffer dataBuffer; // For reading in the correct endian
433 byte[] Info=new byte[80]; // Header data
434 byte[] Array_number= new byte[4]; // Holds the number of faces
435 byte[] Temp_Info; // Intermediate array
437 int Number_faces; // First info (after the header) on the file
440 System.out.println("Machine's endian: " + ByteOrder.nativeOrder());
445 // FileInputStream can only read local files!?
446 System.out.println("This version doesn't support reading binary files from internet");
449 { // It's a local file
450 data = new FileInputStream(file);
452 // First 80 bytes aren't important
453 if(80 != data.read(Info))
454 { // File is incorrect
455 //System.out.println("Format Error: 80 bytes expected");
456 throw new IncorrectFormatException();
459 { // We must first read the number of faces -> 4 bytes int
460 // It depends on the endian so..
462 data.read(Array_number); // We get the 4 bytes
463 dataBuffer = ByteBuffer.wrap(Array_number); // ByteBuffer for reading correctly the int
464 dataBuffer.order(ByteOrder.nativeOrder()); // Set the right order
465 Number_faces = dataBuffer.getInt();
467 Temp_Info = new byte[50*Number_faces]; // Each face has 50 bytes of data
469 data.read(Temp_Info); // We get the rest of the file
471 dataBuffer = ByteBuffer.wrap(Temp_Info); // Now we have all the data in this ByteBuffer
472 dataBuffer.order(ByteOrder.nativeOrder());
475 System.out.println("Number of faces= " + Number_faces);
477 // We can create that array directly as we know how big it's going to be
478 coordArray = new Point3f[Number_faces*3]; // Each face has 3 vertex
479 normArray = new Vector3f[Number_faces];
480 stripCounts = new int[Number_faces];
482 for(int i=0;i<Number_faces;i++)
487 readFacetB(dataBuffer,i);
488 // After each facet there are 2 bytes without information
489 // In the last iteration we dont have to skip those bytes..
490 if(i != Number_faces - 1)
496 catch (IOException e)
499 System.out.println("Format Error: iteration number " + i);
500 throw new IncorrectFormatException();
505 }// End of readBinaryFile
508 * Method that reads ASCII files
509 * Uses StlFileParser for correct reading and format checking
510 * The beggining of that method is common to binary and ASCII files
511 * We try to detect what king of file it is
514 * 1.- Find a best way to decide what kind of file it is
515 * 2.- Is that return (first catch) the best thing to do?
517 * @param parser The file parser. An instance of StlFileParser.
519 private void readFile(StlFileParser parser)
526 catch (IOException e)
528 System.err.println("IO Error on line " + parser.lineno() + ": " + e.getMessage());
529 System.err.println("File seems to be empty");
530 return; // ????? Throw ?????
533 // Here we try to detect what kind of file it is (see readSolid)
542 catch (IOException e)
544 System.err.println("IO Error on line " + parser.lineno() + ": " + e.getMessage());
547 // Read all the facets of the object
548 while (parser.ttype != StlFileParser.TT_EOF && !parser.sval.equals("endsolid"))
555 catch (IOException e)
557 System.err.println("IO Error on line " + parser.lineno() + ": " + e.getMessage());
561 // Why are we out of the while?: EOF or endsolid
562 if(parser.ttype == StlFileParser.TT_EOF)
563 System.err.println("Format Error:expecting 'endsolid', line " + parser.lineno());
567 System.out.println("File readed");
569 }//End of Ascii reading
574 readBinaryFile(getFileName());
578 System.err.println("Format Error: reading the binary file");
580 }// End of binary file
584 * The Stl File is loaded from the .stl file specified by
586 * To attach the model to your scene, call getSceneGroup() on
587 * the Scene object passed back, and attach the returned
588 * BranchGroup to your scene graph. For an example, see
589 * $J3D/programs/examples/ObjLoad/ObjLoad.java.
591 * @param filename The name of the file with the object to load
593 * @return Scene The scene with the object loaded.
595 * @throws FileNotFoundException
596 * @throws IncorrectFormatException
597 * @throws ParsingErrorException
599 public Scene load(String filename) throws FileNotFoundException,
600 IncorrectFormatException,
601 ParsingErrorException
603 setBasePathFromFilename(filename);
604 setFileName(filename); // For binary files
606 Reader reader = new BufferedReader(new FileReader(filename));
608 } // End of load(String)
611 * The Stl file is loaded off of the web.
612 * To attach the model to your scene, call getSceneGroup() on
613 * the Scene object passed back, and attach the returned
614 * BranchGroup to your scene graph. For an example, see
615 * $J3D/programs/examples/ObjLoad/ObjLoad.java.
617 * @param url The url to load the onject from
619 * @return Scene The scene with the object loaded.
621 * @throws FileNotFoundException
622 * @throws IncorrectFormatException
623 * @throws ParsingErrorException
625 public Scene load(URL url) throws FileNotFoundException,
626 IncorrectFormatException,
627 ParsingErrorException
629 BufferedReader reader;
631 setBaseUrlFromUrl(url);
634 reader = new BufferedReader(new InputStreamReader(url.openStream()));
636 catch (IOException e) {
637 throw new FileNotFoundException();
641 } // End of load(URL)
644 * The Stl File is loaded from the already opened file.
645 * To attach the model to your scene, call getSceneGroup() on
646 * the Scene object passed back, and attach the returned
647 * BranchGroup to your scene graph. For an example, see
648 * $J3D/programs/examples/ObjLoad/ObjLoad.java.
650 * @param reader The reader to read the object from
652 * @return Scene The scene with the object loaded.
654 * @throws FileNotFoundException
655 * @throws IncorrectFormatException
656 * @throws ParsingErrorException
658 public Scene load(Reader reader) throws FileNotFoundException,
659 IncorrectFormatException,
660 ParsingErrorException
662 // That method calls the method that loads the file for real..
663 // Even if the Stl format is not complicated I've decided to use
664 // a parser as in the Obj's loader included in Java3D
666 StlFileParser st=new StlFileParser(reader);
669 coordList = new ArrayList();
670 normList = new ArrayList();
671 setAscii(true); // Default ascii
678 * Method that takes the info from an ArrayList of Point3f
679 * and returns a Point3f[].
680 * Needed for ASCII files as we don't know the number of facets until the end
682 * @param inList The list to transform into Point3f[]
684 * @return Point3f[] The result.
686 private Point3f[] objectToPoint3Array(ArrayList inList)
688 Point3f outList[] = new Point3f[inList.size()];
690 for (int i = 0 ; i < inList.size() ; i++) {
691 outList[i] = (Point3f)inList.get(i);
694 } // End of objectToPoint3Array
697 * Method that takes the info from an ArrayList of Vector3f
698 * and returns a Vector3f[].
699 * Needed for ASCII files as we don't know the number of facets until the end
702 * 1.- Here we fill stripCounts...
703 * Find a better place to do it?
705 * @param inList The list to transform into Point3f[]
707 * @return Vector3f[] The result.
709 private Vector3f[] objectToVectorArray(ArrayList inList)
711 Vector3f outList[] = new Vector3f[inList.size()];
714 System.out.println("Number of facets of the object=" + inList.size());
717 stripCounts = new int[inList.size()];
718 for (int i = 0 ; i < inList.size() ; i++) {
719 outList[i] = (Vector3f)inList.get(i);
724 } // End of objectToVectorArray
727 * Method that creates the SceneBase with the stl file info
729 * @return SceneBase The scene
731 private SceneBase makeScene()
733 // Create Scene to pass back
734 SceneBase scene = new SceneBase();
735 BranchGroup group = new BranchGroup();
736 scene.setSceneGroup(group);
738 // Store the scene info on a GeometryInfo
739 GeometryInfo gi = new GeometryInfo(GeometryInfo.TRIANGLE_STRIP_ARRAY);
741 // Convert ArrayLists to arrays: only needed if file was not binary
744 coordArray = objectToPoint3Array(coordList);
745 normArray = objectToVectorArray(normList);
748 gi.setCoordinates(coordArray);
749 gi.setNormals(normArray);
750 gi.setStripCounts(stripCounts);
752 // Put geometry into Shape3d
753 Shape3D shape = new Shape3D();
754 shape.setGeometry(gi.getGeometryArray());
756 group.addChild(shape);
757 scene.addNamedObject(objectName, shape);
760 } // end of makeScene
762 /////////////////////// Accessors and Modifiers ///////////////////////////
764 public URL getBaseUrl()
770 * Modifier for baseUrl, if accessing internet.
772 * @param url The new url
774 public void setBaseUrl(URL url)
779 private void setBaseUrlFromUrl(URL url)
781 StringTokenizer stok =
782 new StringTokenizer(url.toString(), "/\\", true);
783 int tocount = stok.countTokens() - 1;
784 StringBuffer sb = new StringBuffer(MAX_PATH_LENGTH);
785 for(int i = 0; i < tocount ; i++) {
786 String a = stok.nextToken();
788 // if((i == 0) && (!a.equals("file:"))) {
790 // sb.append(java.io.File.separator);
791 // sb.append(java.io.File.separator);
794 // sb.append( java.io.File.separator );
798 baseUrl = new URL(sb.toString());
800 catch (MalformedURLException e) {
801 System.err.println("Error setting base URL: " + e.getMessage());
803 } // End of setBaseUrlFromUrl
806 public String getBasePath()
812 * Set the path where files associated with this .stl file are
814 * Only needs to be called to set it to a different directory
815 * from that containing the .stl file.
817 * @param pathName The new Path to the file
819 public void setBasePath(String pathName)
822 if (basePath == null || basePath == "")
823 basePath = "." + java.io.File.separator;
824 basePath = basePath.replace('/', java.io.File.separatorChar);
825 basePath = basePath.replace('\\', java.io.File.separatorChar);
826 if (!basePath.endsWith(java.io.File.separator))
827 basePath = basePath + java.io.File.separator;
828 } // End of setBasePath
831 * Takes a file name and sets the base path to the directory
832 * containing that file.
834 private void setBasePathFromFilename(String fileName)
836 // Get ready to parse the file name
837 StringTokenizer stok =
838 new StringTokenizer(fileName, java.io.File.separator);
840 // Get memory in which to put the path
841 StringBuffer sb = new StringBuffer(MAX_PATH_LENGTH);
843 // Check for initial slash
844 if (fileName!= null && fileName.startsWith(java.io.File.separator))
845 sb.append(java.io.File.separator);
847 // Copy everything into path except the file name
848 for(int i = stok.countTokens() - 1 ; i > 0 ; i--) {
849 String a = stok.nextToken();
851 sb.append(java.io.File.separator);
853 setBasePath(sb.toString());
854 } // End of setBasePathFromFilename
856 public int getFlags()
861 public void setFlags(int parm)
866 public boolean getAscii()
871 public void setAscii(boolean tipo)
877 public String getFileName()
879 return this.fileName;
882 public void setFileName(String filename)
884 this.fileName=new String(filename);
888 public String getObjectName()
890 return this.objectName;
893 public void setObjectName(String name)
895 this.objectName = name;
898 } // End of package stl_loader