1 import com.sun.j3d.loaders.Loader;
2 import com.sun.j3d.loaders.Scene;
3 import com.sun.j3d.loaders.SceneBase;
4 import com.sun.j3d.loaders.IncorrectFormatException;
5 import com.sun.j3d.loaders.ParsingErrorException;
6 import com.sun.j3d.utils.geometry.GeometryInfo;
9 import java.net.MalformedURLException;
11 import java.io.Reader;
12 import java.io.BufferedReader;
13 import java.io.FileReader;
14 import java.io.InputStreamReader;
15 import java.io.FileInputStream;
16 import java.io.FileNotFoundException;
17 import java.io.IOException;
19 import java.util.StringTokenizer;
20 import java.util.ArrayList;
22 import javax.vecmath.Point3f;
23 import javax.vecmath.Vector3f;
25 import javax.media.j3d.BranchGroup;
26 import javax.media.j3d.Shape3D;
28 // New from JDK 1.4 for endian related problems
29 import java.nio.ByteOrder;
30 import java.nio.ByteBuffer;
36 * Description: STL files loader (Supports ASCII and binary files) for Java3D
37 * Needs JDK 1.4 due to endian problems
38 * Company: Universidad del Pais Vasco (UPV/EHU)
39 * @author: Carlos Pedrinaci Godoy
42 * Contact : xenicp@yahoo.es
46 * 1.-We can't read binary files over the net.
47 * 2.-For binary files if size is lower than expected (calculated with the number of faces)
48 * the program will block.
49 * 3.-Improve the way for detecting the kind of stl file?
50 * Can give us problems if the comment of the binary file begins by "solid"
53 public class StlFile implements Loader
55 private static final int DEBUG = 0; // Sets mode to Debug: outputs every action done
57 // Maximum length (in chars) of basePath
58 private static final int MAX_PATH_LENGTH = 1024;
61 private int flag; // Needed cause implements Loader
63 private URL baseUrl = null; // Reading files over Internet
64 private String basePath = null; // For local files
66 private boolean fromUrl = false; // Usefull for binary files
67 private boolean Ascii = true; // File type Ascii -> true o binary -> false
68 private String fileName = null;
70 // Arrays with coordinates and normals
71 // Needed for reading ASCII files because its size is unknown until the end
72 private ArrayList coordList; // Holds Point3f
73 private ArrayList normList; // Holds Vector3f
75 // GeometryInfo needs Arrays
76 public Point3f[] coordArray = null;
77 public Vector3f[] normArray = null;
79 // Needed because TRIANGLE_STRIP_ARRAY
80 // As the number of strips = the number of faces it's filled in objectToVectorArray
81 public int[] stripCounts = null;
83 // Default = Not available
84 private String objectName=new String("Not available");
94 * Method that reads the EOL
95 * Needed for verifying that the file has a correct format
97 * @param parser The file parser. An instance of StlFileParser.
99 private void readEOL(StlFileParser parser)
104 catch (IOException e)
106 System.err.println("IO Error on line " + parser.lineno() + ": " + e.getMessage());
108 if(parser.ttype != StlFileParser.TT_EOL)
110 System.err.println("Format Error:expecting End Of Line on line " + parser.lineno());
115 * Method that reads the word "solid" and stores the object name.
116 * It also detects what kind of file it is
118 * 1.- Better way control of exceptions?
119 * 2.- Better way to decide between Ascii and Binary?
121 * @param parser The file parser. An instance of StlFileParser.
123 private void readSolid(StlFileParser parser)
125 if( !parser.sval.equals("solid"))
127 //System.out.println("Expecting solid on line " + parser.lineno());
128 // If the first word is not "solid" then we consider the file is binary
129 // Can give us problems if the comment of the binary file begins by "solid"
130 this.setAscii(false);
132 else // It's an ASCII file
137 catch (IOException e)
139 System.err.println("IO Error on line " + parser.lineno() + ": " + e.getMessage());
141 if( parser.ttype != StlFileParser.TT_WORD)
143 // Is the object name always provided???
144 System.err.println("Format Error:expecting the object name on line " + parser.lineno());
147 { // Store the object Name
148 this.setObjectName(new String(parser.sval));
151 System.out.println("Object Name:" + this.getObjectName().toString());
153 this.readEOL(parser);
159 * Method that reads a normal
161 * @param parser The file parser. An instance of StlFileParser.
163 private void readNormal(StlFileParser parser)
165 Vector3f v = new Vector3f();
167 if(!(parser.ttype==StlFileParser.TT_WORD && parser.sval.equals("normal")))
169 System.err.println("Format Error:expecting 'normal' on line " + parser.lineno());
173 if (parser.getNumber())
175 v.x=(float)parser.nval;
179 System.out.println("Normal:");
180 System.out.print("X=" + v.x + " ");
183 if (parser.getNumber())
185 v.y=(float)parser.nval;
187 System.out.print("Y=" + v.y + " ");
189 if (parser.getNumber())
191 v.z=(float)parser.nval;
193 System.out.println("Z=" + v.z);
195 // We add that vector to the Normal's array
196 this.normList.add(v);
197 this.readEOL(parser);
199 else System.err.println("Format Error:expecting coordinate on line " + parser.lineno());
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 }// End of Read Normal
208 * Method that reads the coordinates of a vector
210 * @param parser The file parser. An instance of StlFileParser.
212 private void readVertex(StlFileParser parser)
214 Point3f p = new Point3f();
216 if(!(parser.ttype==StlFileParser.TT_WORD && parser.sval.equals("vertex")))
218 System.err.println("Format Error:expecting 'vertex' on line " + parser.lineno());
222 if (parser.getNumber())
224 p.x=(float)parser.nval;
228 System.out.println("Vertex:");
229 System.out.print("X=" + p.x + " ");
232 if (parser.getNumber())
234 p.y=(float)parser.nval;
236 System.out.print("Y=" + p.y + " ");
238 if (parser.getNumber())
240 p.z=(float)parser.nval;
242 System.out.println("Z=" + p.z);
244 // We add that vertex to the array of vertex
248 else System.err.println("Format Error: expecting coordinate on line " + parser.lineno());
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 }//End of read vertex
257 * Method that reads "outer loop" and then EOL
259 * @param parser The file parser. An instance of StlFileParser.
261 private void readLoop(StlFileParser parser)
263 if(!(parser.ttype==StlFileParser.TT_WORD && parser.sval.equals("outer")))
265 System.err.println("Format Error:expecting 'outer' on line " + parser.lineno());
272 catch (IOException e)
274 System.err.println("IO error on line " + parser.lineno() + ": " + e.getMessage());
276 if(!(parser.ttype==StlFileParser.TT_WORD && parser.sval.equals("loop")))
278 System.err.println("Format Error:expecting 'loop' on line " + parser.lineno());
280 else readEOL(parser);
285 * Method that reads "endloop" then EOL
287 * @param parser The file parser. An instance of StlFileParser.
289 private void readEndLoop(StlFileParser parser)
291 if(!(parser.ttype==StlFileParser.TT_WORD && parser.sval.equals("endloop")))
293 System.err.println("Format Error:expecting 'endloop' on line " + parser.lineno());
295 else readEOL(parser);
296 }//End of readEndLoop
299 * Method that reads "endfacet" then EOL
301 * @param parser The file parser. An instance of StlFileParser.
303 private void readEndFacet(StlFileParser parser)
305 if(!(parser.ttype==StlFileParser.TT_WORD && parser.sval.equals("endfacet")))
307 System.err.println("Format Error:expecting 'endfacet' on line " + parser.lineno());
309 else readEOL(parser);
310 }//End of readEndFacet
313 * Method that reads a face of the object
314 * (Cares about the format)
316 * @param parser The file parser. An instance of StlFileParser.
318 private void readFacet(StlFileParser parser)
320 if(!(parser.ttype==StlFileParser.TT_WORD && parser.sval.equals("facet")))
322 System.err.println("Format Error:expecting 'facet' on line " + parser.lineno());
346 readEndFacet(parser);
348 catch (IOException e)
350 System.err.println("IO Error on line " + parser.lineno() + ": " + e.getMessage());
356 * Method that reads a face in binary files
357 * All binary versions of the methods end by 'B'
358 * As in binary files we can read the number of faces, we don't need
359 * to use coordArray and normArray (reading binary files should be faster)
361 * @param in The ByteBuffer with the data of the object.
362 * @param index The facet index
364 * @throws IOException
366 private void readFacetB(ByteBuffer in, int index) throws IOException
368 //File structure: Normal Vertex1 Vertex2 Vertex3
369 Vector3f normal = new Vector3f();
370 Point3f vertex = new Point3f();
373 System.out.println("Reading face number " + index);
376 normArray[index]=new Vector3f();
377 normArray[index].x=in.getFloat();
378 normArray[index].y=in.getFloat();
379 normArray[index].z=in.getFloat();
382 System.out.println("Normal: X=" + normArray[index].x + " Y=" + normArray[index].y + " Z=" + normArray[index].z);
385 coordArray[index*3] = new Point3f();
386 coordArray[index*3].x=in.getFloat();
387 coordArray[index*3].y=in.getFloat();
388 coordArray[index*3].z=in.getFloat();
391 System.out.println("Vertex 1: X=" + coordArray[index*3].x + " Y=" + coordArray[index*3].y + " Z=" + coordArray[index*3].z);
394 coordArray[index*3+1] = new Point3f();
395 coordArray[index*3+1].x=in.getFloat();
396 coordArray[index*3+1].y=in.getFloat();
397 coordArray[index*3+1].z=in.getFloat();
400 System.out.println("Vertex 2: X=" + coordArray[index*3+1].x + " Y=" + coordArray[index*3+1].y + " Z=" + coordArray[index*3+1].z);
403 coordArray[index*3+2] = new Point3f();
404 coordArray[index*3+2].x=in.getFloat();
405 coordArray[index*3+2].y=in.getFloat();
406 coordArray[index*3+2].z=in.getFloat();
409 System.out.println("Vertex 3: X=" + coordArray[index*3+2].x + " Y=" + coordArray[index*3+2].y + " Z=" + coordArray[index*3+2].z);
411 }// End of readFacetB
414 * Method for reading binary files
415 * Execution is completly different
416 * It uses ByteBuffer for reading data and ByteOrder for retrieving the machine's endian
420 * 1.-Be able to read files over Internet
421 * 2.-If the amount of data expected is bigger than what is on the file then
422 * the program will block forever
424 * @param file The name of the file
426 * @throws IOException
428 private void readBinaryFile(String file) throws IOException
430 FileInputStream data; // For reading the file
431 ByteBuffer dataBuffer; // For reading in the correct endian
432 byte[] Info=new byte[80]; // Header data
433 byte[] Array_number= new byte[4]; // Holds the number of faces
434 byte[] Temp_Info; // Intermediate array
436 int Number_faces; // First info (after the header) on the file
439 System.out.println("Machine's endian: " + ByteOrder.nativeOrder());
444 // FileInputStream can only read local files!?
445 System.out.println("This version doesn't support reading binary files from internet");
448 { // It's a local file
449 data = new FileInputStream(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());
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