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}