001/** 002 * TecplotGeomReader -- A class that can read and write an ASCII Tecplot formatted triangle file. 003 * 004 * Copyright (C) 2019, 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.GeomList; 021import geomss.geom.Point; 022import geomss.geom.Triangle; 023import geomss.geom.TriangleList; 024import java.io.*; 025import java.nio.file.Files; 026import java.text.MessageFormat; 027import java.util.ArrayList; 028import java.util.List; 029import java.util.Locale; 030import static java.util.Objects.requireNonNull; 031import javax.measure.quantity.Length; 032import javax.measure.unit.Unit; 033import javolution.text.TypeFormat; 034import javolution.util.FastTable; 035 036/** 037 * A {@link GeomReader} for reading and writing geometry from/to an Tecplot ASCII 038 * formatted triangle file. The first 3 columns of data are assumed to be the X,Y,Z points. 039 * 040 * <p> Modified by: Joseph A. Huwaldt </p> 041 * 042 * @author Joseph A. Huwaldt, Date: July 12, 2019 043 * @version July 12, 2019 044 */ 045public class TecplotGeomReader extends AbstractGeomReader { 046 047 // Debug output flag. 048 //private static final boolean DEBUG = false; 049 050 // A brief description of the data read by this reaader. 051 private static final String DESCRIPTION = RESOURCES.getString("tecplotDescription"); 052 053 // The preferred file extension for files of this reader's type. 054 public static final String EXTENSION = "dat"; 055 056 // Used to split strings on white space. 057 private static final String WHITESPACE = "\\s+"; 058 059 /** 060 * Returns a string representation of the object. This will return a brief description 061 * of the format read by this reader. 062 * 063 * @return A brief description of the format read by this reader. 064 */ 065 @Override 066 public String toString() { 067 return DESCRIPTION; 068 } 069 070 /** 071 * Returns the preferred file extension (not including the ".") for files of this 072 * GeomReader's type. 073 * 074 * @return The preferred file extension for files of this readers type. 075 */ 076 @Override 077 public String getExtension() { 078 return EXTENSION; 079 } 080 081 /** 082 * Method that determines if this reader can read geometry from the specified input 083 * file. 084 * 085 * @param inputFile The input file containing the geometry to be read in. 086 * @return GeomReader.NO if the file format is not recognized by this reader. 087 * GeomReader.YES if the file has the extension ".dat" and contains a Tecplot header. 088 * @throws java.io.IOException If there is a problem reading from the specified file. 089 */ 090 @Override 091 public int canReadData(File inputFile) throws IOException { 092 093 String name = inputFile.getName(); 094 name = name.toLowerCase().trim(); 095 if (name.endsWith(".dat")) { 096 // Try reading the number of variables from the file. 097 try (BufferedReader reader = Files.newBufferedReader(inputFile.toPath())) { 098 099 List<String> variables = getVariables(reader); 100 if (!variables.isEmpty()) 101 return YES; 102 103 } catch (IOException e) { 104 } 105 } 106 107 return NO; 108 } 109 110 /** 111 * Reads in a Tecplot ASCII formatted triangle geometry file from the 112 * specified input file and returns a {@link GeomList} object that contains a set of 113 * {@link TriangleList} objects (1 for each "zone" in the Tecplot file). 114 * <p> 115 * WARNING: This file format is not unit aware. You must set the units 116 * to be used by calling "setFileUnits()" before calling this method! 117 * </p> 118 * 119 * @param inputFile The input file containing the geometry to be read in. May not be 120 * null. 121 * @return A {@link GeomList} object containing the geometry read in from the file. If 122 * the file has no geometry in it, then this list will have no triangles in it 123 * (will have a size() of zero). 124 * @throws IOException If there is a problem reading the specified file. 125 * @see #setFileUnits(javax.measure.unit.Unit) 126 */ 127 @Override 128 public GeomList<TriangleList> read(File inputFile) throws IOException { 129 requireNonNull(inputFile); 130 _warnings.clear(); 131 132 // Create an initially empty geometry list for output. 133 GeomList<TriangleList> output = GeomList.newInstance(); 134 135 // Tecplot ASCII files are required to be in ASCII with U.S. style number formatting. 136 // Get the default locale and save it. Then change the default locale to US. 137 Locale defLocale = Locale.getDefault(); 138 Locale.setDefault(Locale.US); 139 140 // Create a reader to access the ASCII file. 141 try (LineNumberReader reader = new LineNumberReader(new FileReader(inputFile))) { 142 try { 143 Unit<Length> units = this.getFileUnits(); 144 145 // Get the variables in the file. 146 reader.mark(8192); 147 List<String> variables = getVariables(reader); 148 reader.reset(); 149 if (variables.size() < 3) { 150 _warnings.add("No variables found in the \"" + inputFile + "\" file."); 151 return output; 152 } 153 154 // REad in the title. 155 String[] tokens = reader.readLine().split("="); 156 if (tokens.length > 1) 157 output.setName(tokens[1].trim()); 158 159 // Loop over all the zones in the file. 160 String zone = getNextZone(reader); 161 while (zone != null) { 162 163 // Read in the number of verticies and triangles in the file. 164 reader.readLine(); // Skip a line. 165 String line = reader.readLine(); 166 tokens = line.trim().split(","); 167 // First one should be the number of verticies. 168 String[] parts = tokens[0].split("="); 169 int numVerts = TypeFormat.parseInt(parts[1]); 170 // Second one should be the number of triangles. 171 parts = tokens[1].split("="); 172 int numTris = TypeFormat.parseInt(parts[1]); 173 174 // Skip 2 lines. 175 reader.readLine(); 176 reader.readLine(); 177 178 // Read in all the vertices. 179 FastTable<Point> vertices = FastTable.newInstance(); 180 for (int i = 0; i < numVerts; ++i) { 181 line = reader.readLine(); 182 tokens = line.trim().split(WHITESPACE); 183 double x = TypeFormat.parseDouble(tokens[0]); 184 double y = TypeFormat.parseDouble(tokens[1]); 185 double z = TypeFormat.parseDouble(tokens[2]); 186 Point p = Point.valueOf(x, y, z, units); 187 vertices.add(p); 188 } 189 190 191 // Read in the array of indexes. 192 int numIndexes = numTris * 3; 193 int[] indexes = new int[numIndexes]; 194 int pos = 0; 195 for (int i = 0; i < numTris; ++i) { 196 line = reader.readLine(); 197 tokens = line.trim().split(WHITESPACE); 198 for (int j = 0; j < 3; ++j) { 199 int idx = TypeFormat.parseInt(tokens[j]) - 1; // Convert to 0 offset indexes. 200 indexes[pos++] = idx; 201 } 202 } 203 204 // Convert the vertices and indexes into a list of triangles. 205 TriangleList<Triangle> tris = TriangleList.valueOf(vertices, indexes); 206 tris.setName(zone); 207 output.add(tris); 208 FastTable.recycle(vertices); 209 210 // Read the next zone. 211 zone = getNextZone(reader); 212 } 213 214 } catch (NumberFormatException e) { 215 throw new IOException(MessageFormat.format( 216 RESOURCES.getString("parseErrMsg"), DESCRIPTION, reader.getLineNumber())); 217 } 218 219 } finally { 220 // Restore the default locale. 221 Locale.setDefault(defLocale); 222 } 223 224 return output; 225 } 226 227 /** 228 * Returns the variables used in the first zone in the file. It is assumed that all 229 * zones have the same set of variables in the same order. 230 * 231 * @param reader The buffered reader for this file. 232 * @return A list of the variables in the file. 233 * @throws IOException if there are any problems reading from the file. 234 */ 235 private static List<String> getVariables(BufferedReader reader) throws IOException { 236 List<String> variables = new ArrayList(); 237 238 String line = reader.readLine(); 239 while (line != null) { 240 line = line.trim(); 241 if (line.startsWith("VARIABLES") || line.startsWith("\"")) { 242 String[] parts = line.split(WHITESPACE); 243 244 // Strip off white space and quote characters. 245 for (int j=0; j < parts.length; ++j) { 246 String part = parts[j].trim(); 247 if (part.length() > 0 && !part.equals("=") && !part.startsWith("VARIABLES")) { 248 part = part.replaceAll("^\"|\"$", ""); 249 variables.add(part); 250 } 251 } 252 } else if (line.startsWith("ZONE")) 253 break; 254 255 line = reader.readLine(); 256 } 257 258 return variables; 259 } 260 261 /** 262 * Return the next zone in the file or null if there are no more zones. 263 * 264 * @param reader The buffered reader for this file. 265 * @return The name of the next zone or null if there are no more zones. 266 * @throws IOException If there is a problem reading from the file. 267 */ 268 private static String getNextZone(BufferedReader reader) throws IOException { 269 String zone = null; 270 271 String line = reader.readLine(); 272 while (line != null) { 273 line = line.trim(); 274 if (line.startsWith("ZONE")) { 275 int idx = line.indexOf("T="); 276 idx = line.indexOf("\"", idx); 277 ++idx; 278 int idx2 = line.indexOf("\"", idx); 279 zone = line.substring(idx, idx2); 280 break; 281 } 282 283 line = reader.readLine(); 284 } 285 286 return zone; 287 } 288 /** 289 * This method always returns <code>false</code> as TRI files do not encode the units 290 * that are being used. You must call <code>setFileUnits</code> to set the units being 291 * used before reading from a file of this format. 292 * 293 * @return This implementation always returns false. 294 * @see #setFileUnits(javax.measure.unit.Unit) 295 */ 296 @Override 297 public boolean isUnitAware() { 298 return false; 299 } 300 301}