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.InputStream;
18 import java.io.FileNotFoundException;
19 import java.io.IOException;
21 import java.util.StringTokenizer;
22 import java.util.ArrayList;
24 import javax.vecmath.Point3f;
25 import javax.vecmath.Vector3f;
27 import javax.media.j3d.BranchGroup;
28 import javax.media.j3d.Shape3D;
30 // New from JDK 1.4 for endian related problems
31 import java.nio.ByteOrder;
32 import java.nio.ByteBuffer;
38 * Description: STL files loader (Supports ASCII and binary files) for Java3D
39 * Needs JDK 1.4 due to endian problems
40 * Company: Universidad del Pais Vasco (UPV/EHU)
41 * @author: Carlos Pedrinaci Godoy
44 * Contact : xenicp@yahoo.es
48 * 1.-We can't read binary files over the net.
49 * 2.-For binary files if size is lower than expected (calculated with the number of faces)
50 * the program will block.
51 * 3.-Improve the way for detecting the kind of stl file?
52 * Can give us problems if the comment of the binary file begins by "solid"
55 public class StlFile implements Loader
57 private static final int DEBUG = 0; // Sets mode to Debug: outputs every action done
59 // Maximum length (in chars) of basePath
60 private static final int MAX_PATH_LENGTH = 1024;
63 private int flag; // Needed cause implements Loader
65 private URL baseUrl = null; // Reading files over Internet
66 private String basePath = null; // For local files
68 private boolean fromUrl = false; // Usefull for binary files
69 private boolean Ascii = true; // File type Ascii -> true o binary -> false
70 private String fileName = null;
72 // Arrays with coordinates and normals
73 // Needed for reading ASCII files because its size is unknown until the end
74 private ArrayList coordList; // Holds Point3f
75 private ArrayList normList; // Holds Vector3f
77 // GeometryInfo needs Arrays
78 public Point3f[] coordArray = null;
79 public Vector3f[] normArray = null;
81 // Needed because TRIANGLE_STRIP_ARRAY
82 // As the number of strips = the number of faces it's filled in objectToVectorArray
83 public int[] stripCounts = null;
85 // Default = Not available
86 private String objectName=new String("Not available");
96 * Method that reads the EOL
97 * Needed for verifying that the file has a correct format
99 * @param parser The file parser. An instance of StlFileParser.
101 private void readEOL(StlFileParser parser)
106 catch (IOException e)
108 System.err.println("IO Error on line " + parser.lineno() + ": " + e.getMessage());
110 if(parser.ttype != StlFileParser.TT_EOL)
112 System.err.println("Format Error:expecting End Of Line on line " + parser.lineno());
117 * Method that reads the word "solid" and stores the object name.
118 * It also detects what kind of file it is
120 * 1.- Better way control of exceptions?
121 * 2.- Better way to decide between Ascii and Binary?
123 * @param parser The file parser. An instance of StlFileParser.
125 private void readSolid(StlFileParser parser)
127 if( !parser.sval.equals("solid"))
129 //System.out.println("Expecting solid on line " + parser.lineno());
130 // If the first word is not "solid" then we consider the file is binary
131 // Can give us problems if the comment of the binary file begins by "solid"
132 this.setAscii(false);
134 else // It's an ASCII file
139 catch (IOException e)
141 System.err.println("IO Error on line " + parser.lineno() + ": " + e.getMessage());
143 if( parser.ttype != StlFileParser.TT_WORD)
145 // Is the object name always provided???
146 System.err.println("Format Error:expecting the object name on line " + parser.lineno());
149 { // Store the object Name
150 this.setObjectName(new String(parser.sval));
153 System.out.println("Object Name:" + this.getObjectName().toString());
155 this.readEOL(parser);
161 * Method that reads a normal
163 * @param parser The file parser. An instance of StlFileParser.
165 private void readNormal(StlFileParser parser)
167 Vector3f v = new Vector3f();
169 if(!(parser.ttype==StlFileParser.TT_WORD && parser.sval.equals("normal")))
171 System.err.println("Format Error:expecting 'normal' on line " + parser.lineno());
175 if (parser.getNumber())
177 v.x=(float)parser.nval;
181 System.out.println("Normal:");
182 System.out.print("X=" + v.x + " ");
185 if (parser.getNumber())
187 v.y=(float)parser.nval;
189 System.out.print("Y=" + v.y + " ");
191 if (parser.getNumber())
193 v.z=(float)parser.nval;
195 System.out.println("Z=" + v.z);
197 // We add that vector to the Normal's array
198 this.normList.add(v);
199 this.readEOL(parser);
201 else System.err.println("Format Error:expecting coordinate on line " + parser.lineno());
203 else System.err.println("Format Error:expecting coordinate on line " + parser.lineno());
205 else System.err.println("Format Error:expecting coordinate on line " + parser.lineno());
207 }// End of Read Normal
210 * Method that reads the coordinates of a vector
212 * @param parser The file parser. An instance of StlFileParser.
214 private void readVertex(StlFileParser parser)
216 Point3f p = new Point3f();
218 if(!(parser.ttype==StlFileParser.TT_WORD && parser.sval.equals("vertex")))
220 System.err.println("Format Error:expecting 'vertex' on line " + parser.lineno());
224 if (parser.getNumber())
226 p.x=(float)parser.nval;
230 System.out.println("Vertex:");
231 System.out.print("X=" + p.x + " ");
234 if (parser.getNumber())
236 p.y=(float)parser.nval;
238 System.out.print("Y=" + p.y + " ");
240 if (parser.getNumber())
242 p.z=(float)parser.nval;
244 System.out.println("Z=" + p.z);
246 // We add that vertex to the array of vertex
250 else System.err.println("Format Error: expecting coordinate on line " + parser.lineno());
252 else System.err.println("Format Error: expecting coordinate on line " + parser.lineno());
254 else System.err.println("Format Error: expecting coordinate on line " + parser.lineno());
256 }//End of read vertex
259 * Method that reads "outer loop" and then EOL
261 * @param parser The file parser. An instance of StlFileParser.
263 private void readLoop(StlFileParser parser)
265 if(!(parser.ttype==StlFileParser.TT_WORD && parser.sval.equals("outer")))
267 System.err.println("Format Error:expecting 'outer' on line " + parser.lineno());
274 catch (IOException e)
276 System.err.println("IO error on line " + parser.lineno() + ": " + e.getMessage());
278 if(!(parser.ttype==StlFileParser.TT_WORD && parser.sval.equals("loop")))
280 System.err.println("Format Error:expecting 'loop' on line " + parser.lineno());
282 else readEOL(parser);
287 * Method that reads "endloop" then EOL
289 * @param parser The file parser. An instance of StlFileParser.
291 private void readEndLoop(StlFileParser parser)
293 if(!(parser.ttype==StlFileParser.TT_WORD && parser.sval.equals("endloop")))
295 System.err.println("Format Error:expecting 'endloop' on line " + parser.lineno());
297 else readEOL(parser);
298 }//End of readEndLoop
301 * Method that reads "endfacet" then EOL
303 * @param parser The file parser. An instance of StlFileParser.
305 private void readEndFacet(StlFileParser parser)
307 if(!(parser.ttype==StlFileParser.TT_WORD && parser.sval.equals("endfacet")))
309 System.err.println("Format Error:expecting 'endfacet' on line " + parser.lineno());
311 else readEOL(parser);
312 }//End of readEndFacet
315 * Method that reads a face of the object
316 * (Cares about the format)
318 * @param parser The file parser. An instance of StlFileParser.
320 private void readFacet(StlFileParser parser)
322 if(!(parser.ttype==StlFileParser.TT_WORD && parser.sval.equals("facet")))
324 System.err.println("Format Error:expecting 'facet' on line " + parser.lineno());
348 readEndFacet(parser);
350 catch (IOException e)
352 System.err.println("IO Error on line " + parser.lineno() + ": " + e.getMessage());
358 * Method that reads a face in binary files
359 * All binary versions of the methods end by 'B'
360 * As in binary files we can read the number of faces, we don't need
361 * to use coordArray and normArray (reading binary files should be faster)
363 * @param in The ByteBuffer with the data of the object.
364 * @param index The facet index
366 * @throws IOException
368 private void readFacetB(ByteBuffer in, int index) throws IOException
370 //File structure: Normal Vertex1 Vertex2 Vertex3
371 Vector3f normal = new Vector3f();
372 Point3f vertex = new Point3f();
375 System.out.println("Reading face number " + index);
378 normArray[index]=new Vector3f();
379 normArray[index].x=in.getFloat();
380 normArray[index].y=in.getFloat();
381 normArray[index].z=in.getFloat();
384 System.out.println("Normal: X=" + normArray[index].x + " Y=" + normArray[index].y + " Z=" + normArray[index].z);
387 coordArray[index*3] = new Point3f();
388 coordArray[index*3].x=in.getFloat();
389 coordArray[index*3].y=in.getFloat();
390 coordArray[index*3].z=in.getFloat();
393 System.out.println("Vertex 1: X=" + coordArray[index*3].x + " Y=" + coordArray[index*3].y + " Z=" + coordArray[index*3].z);
396 coordArray[index*3+1] = new Point3f();
397 coordArray[index*3+1].x=in.getFloat();
398 coordArray[index*3+1].y=in.getFloat();
399 coordArray[index*3+1].z=in.getFloat();
402 System.out.println("Vertex 2: X=" + coordArray[index*3+1].x + " Y=" + coordArray[index*3+1].y + " Z=" + coordArray[index*3+1].z);
405 coordArray[index*3+2] = new Point3f();
406 coordArray[index*3+2].x=in.getFloat();
407 coordArray[index*3+2].y=in.getFloat();
408 coordArray[index*3+2].z=in.getFloat();
411 System.out.println("Vertex 3: X=" + coordArray[index*3+2].x + " Y=" + coordArray[index*3+2].y + " Z=" + coordArray[index*3+2].z);
413 }// End of readFacetB
416 * Method for reading binary files
417 * Execution is completly different
418 * It uses ByteBuffer for reading data and ByteOrder for retrieving the machine's endian
422 * 1.-Be able to read files over Internet
423 * 2.-If the amount of data expected is bigger than what is on the file then
424 * the program will block forever
426 * @param file The name of the file
428 * @throws IOException
430 public void readBinaryFile(String file, InputStream data) throws IOException
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
451 // First 80 bytes aren't important
452 if(80 != data.read(Info))
453 { // File is incorrect
454 //System.out.println("Format Error: 80 bytes expected");
455 throw new IncorrectFormatException();
458 { // We must first read the number of faces -> 4 bytes int
459 // It depends on the endian so..
461 data.read(Array_number); // We get the 4 bytes
462 dataBuffer = ByteBuffer.wrap(Array_number); // ByteBuffer for reading correctly the int
463 dataBuffer.order(ByteOrder.nativeOrder()); // Set the right order
464 Number_faces = dataBuffer.getInt();
466 Temp_Info = new byte[50*Number_faces]; // Each face has 50 bytes of data
468 data.read(Temp_Info); // We get the rest of the file
470 dataBuffer = ByteBuffer.wrap(Temp_Info); // Now we have all the data in this ByteBuffer
471 dataBuffer.order(ByteOrder.nativeOrder());
474 System.out.println("Number of faces= " + Number_faces);
476 // We can create that array directly as we know how big it's going to be
477 coordArray = new Point3f[Number_faces*3]; // Each face has 3 vertex
478 normArray = new Vector3f[Number_faces];
479 stripCounts = new int[Number_faces];
481 for(int i=0;i<Number_faces;i++)
486 readFacetB(dataBuffer,i);
487 // After each facet there are 2 bytes without information
488 // In the last iteration we dont have to skip those bytes..
489 if(i != Number_faces - 1)
495 catch (IOException e)
498 System.out.println("Format Error: iteration number " + i);
499 throw new IncorrectFormatException();
504 }// End of readBinaryFile
507 * Method that reads ASCII files
508 * Uses StlFileParser for correct reading and format checking
509 * The beggining of that method is common to binary and ASCII files
510 * We try to detect what king of file it is
513 * 1.- Find a best way to decide what kind of file it is
514 * 2.- Is that return (first catch) the best thing to do?
516 * @param parser The file parser. An instance of StlFileParser.
518 private void readFile(StlFileParser parser)
525 catch (IOException e)
527 System.err.println("IO Error on line " + parser.lineno() + ": " + e.getMessage());
528 System.err.println("File seems to be empty");
529 return; // ????? Throw ?????
532 // Here we try to detect what kind of file it is (see readSolid)
541 catch (IOException e)
543 System.err.println("IO Error on line " + parser.lineno() + ": " + e.getMessage());
546 // Read all the facets of the object
547 while (parser.ttype != StlFileParser.TT_EOF && !parser.sval.equals("endsolid"))
554 catch (IOException e)
556 System.err.println("IO Error on line " + parser.lineno() + ": " + e.getMessage());
560 // Why are we out of the while?: EOF or endsolid
561 if(parser.ttype == StlFileParser.TT_EOF)
562 System.err.println("Format Error:expecting 'endsolid', line " + parser.lineno());
566 System.out.println("File readed");
568 }//End of Ascii reading
573 readBinaryFile(getFileName(), new FileInputStream(getFileName()));
577 System.err.println("Format Error: reading the binary file");
579 }// End of binary file
583 * The Stl File is loaded from the .stl file specified by
585 * To attach the model to your scene, call getSceneGroup() on
586 * the Scene object passed back, and attach the returned
587 * BranchGroup to your scene graph. For an example, see
588 * $J3D/programs/examples/ObjLoad/ObjLoad.java.
590 * @param filename The name of the file with the object to load
592 * @return Scene The scene with the object loaded.
594 * @throws FileNotFoundException
595 * @throws IncorrectFormatException
596 * @throws ParsingErrorException
598 public Scene load(String filename) throws FileNotFoundException,
599 IncorrectFormatException,
600 ParsingErrorException
602 setBasePathFromFilename(filename);
603 setFileName(filename); // For binary files
605 Reader reader = new BufferedReader(new FileReader(filename));
607 } // End of load(String)
610 * The Stl file is loaded off of the web.
611 * To attach the model to your scene, call getSceneGroup() on
612 * the Scene object passed back, and attach the returned
613 * BranchGroup to your scene graph. For an example, see
614 * $J3D/programs/examples/ObjLoad/ObjLoad.java.
616 * @param url The url to load the onject from
618 * @return Scene The scene with the object loaded.
620 * @throws FileNotFoundException
621 * @throws IncorrectFormatException
622 * @throws ParsingErrorException
624 public Scene load(URL url) throws FileNotFoundException,
625 IncorrectFormatException,
626 ParsingErrorException
628 BufferedReader reader;
630 setBaseUrlFromUrl(url);
633 reader = new BufferedReader(new InputStreamReader(url.openStream()));
635 catch (IOException e) {
636 throw new FileNotFoundException();
640 } // End of load(URL)
643 * The Stl File is loaded from the already opened file.
644 * To attach the model to your scene, call getSceneGroup() on
645 * the Scene object passed back, and attach the returned
646 * BranchGroup to your scene graph. For an example, see
647 * $J3D/programs/examples/ObjLoad/ObjLoad.java.
649 * @param reader The reader to read the object from
651 * @return Scene The scene with the object loaded.
653 * @throws FileNotFoundException
654 * @throws IncorrectFormatException
655 * @throws ParsingErrorException
657 public Scene load(Reader reader) throws FileNotFoundException,
658 IncorrectFormatException,
659 ParsingErrorException
661 // That method calls the method that loads the file for real..
662 // Even if the Stl format is not complicated I've decided to use
663 // a parser as in the Obj's loader included in Java3D
665 StlFileParser st=new StlFileParser(reader);
668 coordList = new ArrayList();
669 normList = new ArrayList();
670 setAscii(true); // Default ascii
677 * Method that takes the info from an ArrayList of Point3f
678 * and returns a Point3f[].
679 * Needed for ASCII files as we don't know the number of facets until the end
681 * @param inList The list to transform into Point3f[]
683 * @return Point3f[] The result.
685 private Point3f[] objectToPoint3Array(ArrayList inList)
687 Point3f outList[] = new Point3f[inList.size()];
689 for (int i = 0 ; i < inList.size() ; i++) {
690 outList[i] = (Point3f)inList.get(i);
693 } // End of objectToPoint3Array
696 * Method that takes the info from an ArrayList of Vector3f
697 * and returns a Vector3f[].
698 * Needed for ASCII files as we don't know the number of facets until the end
701 * 1.- Here we fill stripCounts...
702 * Find a better place to do it?
704 * @param inList The list to transform into Point3f[]
706 * @return Vector3f[] The result.
708 private Vector3f[] objectToVectorArray(ArrayList inList)
710 Vector3f outList[] = new Vector3f[inList.size()];
713 System.out.println("Number of facets of the object=" + inList.size());
716 stripCounts = new int[inList.size()];
717 for (int i = 0 ; i < inList.size() ; i++) {
718 outList[i] = (Vector3f)inList.get(i);
723 } // End of objectToVectorArray
726 * Method that creates the SceneBase with the stl file info
728 * @return SceneBase The scene
730 private SceneBase makeScene()
732 // Create Scene to pass back
733 SceneBase scene = new SceneBase();
734 BranchGroup group = new BranchGroup();
735 scene.setSceneGroup(group);
737 // Store the scene info on a GeometryInfo
738 GeometryInfo gi = new GeometryInfo(GeometryInfo.TRIANGLE_STRIP_ARRAY);
740 // Convert ArrayLists to arrays: only needed if file was not binary
743 coordArray = objectToPoint3Array(coordList);
744 normArray = objectToVectorArray(normList);
747 gi.setCoordinates(coordArray);
748 gi.setNormals(normArray);
749 gi.setStripCounts(stripCounts);
751 // Put geometry into Shape3d
752 Shape3D shape = new Shape3D();
753 shape.setGeometry(gi.getGeometryArray());
755 group.addChild(shape);
756 scene.addNamedObject(objectName, shape);
759 } // end of makeScene
761 /////////////////////// Accessors and Modifiers ///////////////////////////
763 public URL getBaseUrl()
769 * Modifier for baseUrl, if accessing internet.
771 * @param url The new url
773 public void setBaseUrl(URL url)
778 private void setBaseUrlFromUrl(URL url)
780 StringTokenizer stok =
781 new StringTokenizer(url.toString(), "/\\", true);
782 int tocount = stok.countTokens() - 1;
783 StringBuffer sb = new StringBuffer(MAX_PATH_LENGTH);
784 for(int i = 0; i < tocount ; i++) {
785 String a = stok.nextToken();
787 // if((i == 0) && (!a.equals("file:"))) {
789 // sb.append(java.io.File.separator);
790 // sb.append(java.io.File.separator);
793 // sb.append( java.io.File.separator );
797 baseUrl = new URL(sb.toString());
799 catch (MalformedURLException e) {
800 System.err.println("Error setting base URL: " + e.getMessage());
802 } // End of setBaseUrlFromUrl
805 public String getBasePath()
811 * Set the path where files associated with this .stl file are
813 * Only needs to be called to set it to a different directory
814 * from that containing the .stl file.
816 * @param pathName The new Path to the file
818 public void setBasePath(String pathName)
821 if (basePath == null || basePath == "")
822 basePath = "." + java.io.File.separator;
823 basePath = basePath.replace('/', java.io.File.separatorChar);
824 basePath = basePath.replace('\\', java.io.File.separatorChar);
825 if (!basePath.endsWith(java.io.File.separator))
826 basePath = basePath + java.io.File.separator;
827 } // End of setBasePath
830 * Takes a file name and sets the base path to the directory
831 * containing that file.
833 private void setBasePathFromFilename(String fileName)
835 // Get ready to parse the file name
836 StringTokenizer stok =
837 new StringTokenizer(fileName, java.io.File.separator);
839 // Get memory in which to put the path
840 StringBuffer sb = new StringBuffer(MAX_PATH_LENGTH);
842 // Check for initial slash
843 if (fileName!= null && fileName.startsWith(java.io.File.separator))
844 sb.append(java.io.File.separator);
846 // Copy everything into path except the file name
847 for(int i = stok.countTokens() - 1 ; i > 0 ; i--) {
848 String a = stok.nextToken();
850 sb.append(java.io.File.separator);
852 setBasePath(sb.toString());
853 } // End of setBasePathFromFilename
855 public int getFlags()
860 public void setFlags(int parm)
865 public boolean getAscii()
870 public void setAscii(boolean tipo)
876 public String getFileName()
878 return this.fileName;
881 public void setFileName(String filename)
883 this.fileName=new String(filename);
887 public String getObjectName()
889 return this.objectName;
892 public void setObjectName(String name)
894 this.objectName = name;
897 } // End of package stl_loader