001/**
002 * STLGeomReader -- A class that can read and write a binary STL formatted geometry file.
003 *
004 * Copyright (C) 2015-2016, Joseph A. Huwaldt. All rights reserved.
005 *
006 * This library is free software; you can redistribute it and/or modify it under the terms
007 * of the GNU Lesser General Public License as published by the Free Software Foundation;
008 * either version 2.1 of the License, or (at your option) any later version.
009 *
010 * This library is distributed in the hope that it will be useful, but WITHOUT ANY
011 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
012 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
013 *
014 * You should have received a copy of the GNU Lesser General Public License along with
015 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place -
016 * Suite 330, Boston, MA 02111-1307, USA. Or visit: http://www.gnu.org/licenses/lgpl.html
017 */
018package geomss.geom.reader;
019
020import geomss.geom.*;
021import jahuwaldt.io.FileUtils;
022import java.io.*;
023import java.nio.ByteBuffer;
024import java.nio.ByteOrder;
025import static java.util.Objects.requireNonNull;
026import javax.measure.quantity.Dimensionless;
027import javax.measure.quantity.Length;
028import javax.measure.unit.Unit;
029
030/**
031 * A {@link GeomReader} for reading and writing geometry from/to a binary STL
032 * (STereoLithography) formatted geometry file.
033 *
034 * <p> Modified by: Joseph A. Huwaldt </p>
035 *
036 * @author Joseph A. Huwaldt, Date: September 4, 2015
037 * @version September 9, 2016
038 */
039public class STLGeomReader extends AbstractGeomReader {
040
041    //  Debug output flag.
042    //private static final boolean DEBUG = false;
043
044    //  A brief description of the data read by this reaader.
045    private static final String DESCRIPTION = RESOURCES.getString("stlDescription");
046
047    //  The preferred file extension for files of this reader's type.
048    public static final String EXTENSION = "stl";
049
050    /**
051     * Returns a string representation of the object. This will return a brief description
052     * of the format read by this reader.
053     *
054     * @return A brief description of the format read by this reader.
055     */
056    @Override
057    public String toString() {
058        return DESCRIPTION;
059    }
060
061    /**
062     * Returns the preferred file extension (not including the ".") for files of this
063     * GeomReader's type.
064     *
065     * @return The preferred file extension for files of this readers type.
066     */
067    @Override
068    public String getExtension() {
069        return EXTENSION;
070    }
071
072    /**
073     * Method that determines if this reader can read geometry from the specified input
074     * file.
075     *
076     * @param inputFile The input file containing the geometry to be read in.
077     * @return GeomReader.NO if the file format is not recognized by this reader.
078     *         GeomReader.YES if the file has the extension ".geo" or ".mk5".
079     *         GeomReader.MAYBE if the file has the extension ".lib".
080     * @throws java.io.IOException If there is a problem reading from the specified
081     * file.
082     */
083    @Override
084    public int canReadData(File inputFile) throws IOException {
085
086        int response = NO;
087        String name = inputFile.getName();
088        name = name.toLowerCase().trim();
089        if (name.endsWith(".stl")) {
090            response = MAYBE;
091        }
092
093        return response;
094    }
095
096    /**
097     * Returns true. This class can write triangle and quad-paneled point geometry data to
098     * a Binary STL formatted file.
099     *
100     * @return this method always returns true
101     */
102    @Override
103    public boolean canWriteData() {
104        return true;
105    }
106
107    /**
108     * Reads in a binary STL formatted geometry file from the specified input file and
109     * returns a {@link TriangleList} object that contains the triangle geometry from the
110     * file.
111     * <p>
112     * WARNING: This file format is not unit aware. You must set the units
113     * to be used by calling "setFileUnits()" before calling this method!
114     * </p>
115     *
116     * @param inputFile The input file containing the geometry to be read in. May not be
117     *                  null.
118     * @return A {@link TriangleList} object containing the geometry read in from the
119     *         file. If the file has no geometry in it, then this list will have no
120     *         triangles in it (will have a size() of zero).
121     * @throws IOException If there is a problem reading the specified file.
122     * @see #setFileUnits(javax.measure.unit.Unit)
123     */
124    @Override
125    public TriangleList read(File inputFile) throws IOException {
126        requireNonNull(inputFile);
127        _warnings.clear();
128
129        //  Read all the bytes from the input file.
130        ByteBuffer buf = FileUtils.file2ByteBuffer(inputFile);
131
132        //  An STL file is always little-endian
133        buf = buf.order(ByteOrder.LITTLE_ENDIAN);
134
135        //  Read in the the triangle count.
136        buf.position(80);
137        int numTris = buf.getInt();
138
139        //  Read in all the triangles and add them to the output list.
140        Unit<Length> units = this.getFileUnits();
141        Point[] verts = new Point[3];
142        TriangleList triList = TriangleList.newInstance();
143        for (int i = 0; i < numTris; ++i) {
144            //  Skip the redundant normal vector
145            buf.getFloat();
146            buf.getFloat();
147            buf.getFloat();
148
149            //  Read in the vertex points.
150            for (int j = 0; j < 3; ++j) {
151                float x = buf.getFloat();
152                float y = buf.getFloat();
153                float z = buf.getFloat();
154                Point pnt = Point.valueOf(units, x, y, z);
155                verts[j] = pnt;
156            }
157
158            //  Skip the unused attribute byte count.
159            buf.get();
160            buf.get();
161
162            //  Create and store the triangle.
163            Triangle tri = Triangle.valueOf(verts);
164            triList.add(tri);
165        }
166
167        return triList;
168    }
169
170    /**
171     * Writes out an binary STL formatted geometry file for the geometry contained in the
172     * supplied geometry list. If the list contains triangles or lists of triangles, they
173     * are all written out to the file directly. If the list contains array based paneled
174     * geometry such as a PointVehicle or PointComponent, then all the arrays in these
175     * objects are converted into triangles and those triangles are written out to the
176     * file. If no triangle or point array based geometry can be found in the geometry
177     * list, then nothing will happen (the output file will not be created).
178     * <p>
179     * The 80 byte header of the STL file is not used and is filled with zeros.
180     * </p>
181     * <p>
182     * WARNING: This format is not unit aware. The geometry will be written out in
183     * whatever its current units are! Make sure to convert to the desired units for the
184     * file before calling this method.
185     * </p>
186     *
187     * @param outputFile The output File to which the geometry is to be written. May not
188     *                   be null.
189     * @param geometry   The list of triangle or point array geometry to be written out.
190     *                   May not be null.
191     * @throws IOException If there is a problem writing to the specified file.
192     */
193    @Override
194    public void write(File outputFile, GeometryList geometry) throws IOException {
195        requireNonNull(outputFile);
196        _warnings.clear();
197        if (!geometry.containsGeometry()) {
198            _warnings.add(RESOURCES.getString("noGeometryWarning"));
199            return;
200        }
201
202        //  Convert the input geometry into a single list of triangles.
203        TriangleList<? extends GeomTriangle> tris = TriangleList.newInstance();
204        convertGeometry(geometry, tris);
205        int numTris = tris.size();
206        if (numTris == 0)
207            return;
208
209        //  Remove any degenerate triangles.
210        tris = tris.removeDegenerate(null);
211        numTris = tris.size();
212
213        //  Convert all the triangles to the same units.
214        Unit<Length> units = geometry.getUnit();
215
216        //  Allocate a little-endian byte buffer for the data to be output (STL is always little-endian).
217        int numBytes = 80 + 4 + numTris * (12 * 32 + 16) / 8;
218        ByteBuffer buf = ByteBuffer.allocate(numBytes).order(ByteOrder.LITTLE_ENDIAN);
219
220        //  Write in the 80 byte header (blank).
221        for (int i = 0; i < 80; ++i)
222            buf.put((byte)0);
223
224        //  Write out the triangle count.
225        buf.putInt(numTris);
226
227        //  Loop over all the triangles in the list.
228        GeomPoint[] verts = new GeomPoint[3];
229        for (GeomTriangle tri : tris) {
230            GeomVector<Dimensionless> n = tri.getNormal();
231            tri.getPoints(verts);
232
233            //  Write normal vector.
234            buf.putFloat((float)n.getValue(Point.X));
235            buf.putFloat((float)n.getValue(Point.Y));
236            buf.putFloat((float)n.getValue(Point.Z));
237
238            //  Write out vertices.
239            for (int j = 0; j < 3; ++j) {
240                GeomPoint p = verts[j];
241                float x = (float)p.getValue(Point.X, units);
242                float y = (float)p.getValue(Point.Y, units);
243                float z = (float)p.getValue(Point.Z, units);
244                buf.putFloat(x);
245                buf.putFloat(y);
246                buf.putFloat(z);
247            }
248
249            //  Write out the unused attribute byte count.
250            buf.putShort((short)0);
251        }
252
253        //  Write the byte buffer out to the file.
254        FileUtils.byteBuffer2File(buf, outputFile);
255
256    }
257
258    /**
259     * Convert the input list of geometry into triangles by recursively processing lists
260     * until either PointArray or TriangleList or GeomTriangle objects are encountered.
261     *
262     * @param geom   The list of geometry to be processed.
263     * @param output The list that the triangles are all added to.
264     */
265    private static void convertGeometry(GeometryList<?, GeomElement> geom, TriangleList output) {
266        //  Is the input list a list of triangles?
267        if (geom instanceof TriangleList) {
268            output.addAll((TriangleList)geom);
269            return;
270        }
271
272        //  Is the input list a PointArray?
273        if (geom instanceof PointArray) {
274            PointArray arr = (PointArray)geom;
275            output.addAll(arr.triangulate());
276            return;
277        }
278
279        //  Loop over the items in the list.
280        for (GeomElement elem : geom) {
281            if (elem instanceof GeomTriangle)
282                output.add((GeomTriangle)elem);
283            else if (elem instanceof GeometryList) {
284                //  Recurse down into the sub-list.
285                convertGeometry((GeometryList)elem, output);
286            }
287        }
288    }
289
290    /**
291     * This method always returns <code>false</code> as STL files do not encode the units
292     * that are being used. You must call <code>setFileUnits</code> to set the units being
293     * used before reading from a file of this format.
294     *
295     * @return This implementation always returns false.
296     * @see #setFileUnits(javax.measure.unit.Unit)
297     */
298    @Override
299    public boolean isUnitAware() {
300        return false;
301    }
302
303}