001/*
002 *   GTCGeomReader  -- A class that can read GridTool formatted restart file.
003 *
004 *   Copyright (C) 2013-2025, Joseph A. Huwaldt
005 *   All rights reserved.
006 *
007 *   This library is free software; you can redistribute it and/or
008 *   modify it under the terms of the GNU Lesser General Public
009 *   License as published by the Free Software Foundation; either
010 *   version 2.1 of the License, or (at your option) any later version.
011 *
012 *   This library is distributed in the hope that it will be useful,
013 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
014 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015 *   Lesser General Public License for more details.
016 *
017 *   You should have received a copy of the GNU Lesser General Public License
018 *   along with this program; if not, write to the Free Software
019 *   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
020 *   Or visit:  http://www.gnu.org/licenses/lgpl.html
021 */
022package geomss.geom.reader;
023
024import geomss.geom.*;
025import geomss.geom.nurbs.*;
026import jahuwaldt.js.param.Parameter;
027import jahuwaldt.js.util.TextTokenizer;
028import jahuwaldt.tools.math.MathTools;
029import java.io.*;
030import java.nio.charset.StandardCharsets;
031import java.text.MessageFormat;
032import java.text.SimpleDateFormat;
033import java.util.Date;
034import java.util.List;
035import java.util.Locale;
036import static java.util.Objects.isNull;
037import static java.util.Objects.nonNull;
038import static java.util.Objects.requireNonNull;
039import javax.measure.quantity.Length;
040import javax.measure.unit.Unit;
041import javolution.context.StackContext;
042import javolution.text.Text;
043import javolution.text.TypeFormat;
044import javolution.util.FastTable;
045
046/**
047 * A {@link GeomReader} for reading geometry from a GridTool restart file. GridTool is a
048 * NASA developed grid/geometry program that is a part of the NASA TetrUSS CFD system.
049 * More information can be found here: <a href="http://geolab.larc.nasa.gov/GridTool/">
050 * http://geolab.larc.nasa.gov/GridTool/</a>
051 *
052 * <p> Modified by: Joseph A. Huwaldt </p>
053 *
054 * @author Joseph A. Huwaldt, Date: December 26, 2013
055 * @version February 17, 2025
056 */
057public class GTCGeomReader extends AbstractGeomReader {
058
059    //  Debug output flag.
060    //private static final boolean DEBUG = true;
061    
062    //  A brief description of the data read by this reaader.
063    private static final String DESCRIPTION = RESOURCES.getString("gtcDescription");
064
065    //  The preferred file extension for files of this reader's type.
066    public static final String EXTENSION = "rst";
067
068    //  10 kilobytes
069    private static final int TENK = 10240;
070    
071    /**
072     * Returns a string representation of the object. This will return a brief description
073     * of the format read by this reader.
074     *
075     * @return A brief description of the format read by this reader.
076     */
077    @Override
078    public String toString() {
079        return DESCRIPTION;
080    }
081
082    /**
083     * Returns the preferred file extension (not including the ".") for files of this
084     * GeomReader's type.
085     *
086     * @return The preferred file extension for files of this readers type.
087     */
088    @Override
089    public String getExtension() {
090        return EXTENSION;
091    }
092
093    /**
094     * This method always returns <code>false</code> as GTC restart files do not encode
095     * the units that are being used. You must call <code>setFileUnits</code> to set the
096     * units being used before reading from a file of this format, otherwise
097     * default system units will be assumed.
098     *
099     * @return this implementation always returns false
100     * @see #setFileUnits(javax.measure.unit.Unit) 
101     */
102    @Override
103    public boolean isUnitAware() {
104        return false;
105    }
106
107    /**
108     * Method that determines if this reader can read geometry from the specified input
109     * file.
110     *
111     * @param inputFile The input file containing the geometry to be read in. May not be
112     *                  null.
113     * @return GeomReader.NO if the file format is not recognized by this reader.
114     * GeomReader.YES if the file format is definitely recognized by this reader.
115     * GeomReader.MAYBE if the file format might be readable by this reader, but that
116     * can't easily be determined without actually reading the file.
117     * @throws java.io.IOException If there is a problem reading from the specified file.
118     */
119    @Override
120    public int canReadData(File inputFile) throws IOException {
121        requireNonNull(inputFile);
122        
123        //  GTC files are required to be in ASCII with U.S. style number formatting.
124        //  Get the default locale and save it.  Then change the default locale to US.
125        Locale defLocale = Locale.getDefault();
126        Locale.setDefault(Locale.US);
127
128        int response = NO;
129        //  Create an input stream from the file.
130        try (FileInputStream input = new FileInputStream(inputFile)) {
131
132            //  Create a buffer to hold the data read in from the file.
133            byte[] buffer = new byte[TENK];
134
135            //  Read in up to 10k worth of data.
136            int byteCount = input.read(buffer, 0, TENK);
137            if (byteCount < 0)
138                return NO;
139
140            //  Turn the buffer into a (potentially 10k long) string.
141            String str = new String(buffer, 0, byteCount);
142
143            //  Convert the string into a line number reader.
144            LineNumberReader lnr = new LineNumberReader(new StringReader(str));
145
146            //  The 5th line should start with "Version X.X" where X.X is >= 4.1
147            lnr.readLine();
148            lnr.readLine();
149            lnr.readLine();
150            lnr.readLine();
151            String aLine = lnr.readLine();
152            TextTokenizer tokenizer = TextTokenizer.valueOf(aLine, " ");
153            Text token = tokenizer.nextToken();
154            if (token.equals(Text.valueOf("Version"))) {
155                token = tokenizer.nextToken();
156                String[] parts = token.toString().split("\\.");
157                if (parts.length > 2)
158                    return NO;
159                double major = TypeFormat.parseInt(parts[0]);
160                double minor = TypeFormat.parseInt(parts[1]);
161                double ver = major + minor / 10;
162                if (ver == 4.1)
163                    return YES;
164            }
165
166        } catch (Throwable e) {
167            //  Any problem reading is a failure.
168            response = NO;
169
170        } finally {
171            //  Restore the default locale.
172            Locale.setDefault(defLocale);
173        }
174
175        return response;
176    }
177
178    /**
179     * <p>
180     * Reads in a GridTool restart file from the specified input file and returns a
181     * {@link GeometryList} object that contains the geometry from the file. The output
182     * list contains 4 sub-lists: SURFACES, CURVES, PATCHES and SOURCES. Empty lists are
183     * included if any of these entities is not included in the file.
184     * </p>
185     *
186     * The top-level list has the following data stored in the user data using the
187     * following keys:
188     * <UL>
189     * <LI> GTC_Version -- The GTC version code. Must be &ge; 1.4 to be read by this reader.
190     * <LI> GTC_Tol -- The gobal geometry tolerance used.
191     * <LI> GTC_Name -- The GTC model name.
192     * </UL>
193     *
194     * Each sub-list is defined as follows:
195     * <DL>
196     * <DT> SURFACES
197     * <DD> All the NURBS surfaces and NURBS curves in the geometry. Each surface &amp; curve
198     * has stored in its user data the minimum and maximum parameter ranges for the
199     * original geometry using the GTC_U0, GTC_U1, GTC_V0, and GTC_V1 keys. All NURBS
200     * surfaces &amp; curves in GeomSS have parameter ranges from 0 to 1, but other geometry
201     * systems allow different parameter ranges. In addition, the IGES type code for the
202     * geometry is stored with the GTC_Type key and the family name is stored with the
203     * GTC_Family key.
204     * <DT> CURVES
205     * <DD> This list contains PointString objects (which are lists of points) for each
206     * "curve". Each point stored in each point string is either free (in X,Y,Z) or
207     * subranged onto a NURBS surface or curve.
208     * <DT> PATCHES
209     * <DD> Each patch in the PATCHES list contains two lists, one containing either
210     * nothing or the NURBS surface associated with the patch and one containing a list of
211     * edge curves (PointStrings) for the patch. Each patch has user data stored with it
212     * using the following keys:
213     * <UL>
214     * <LI> GTC_Type -- The patch type code
215     * <LI> GTC_BCType -- The boundary condition type code
216     * <LI> GTC_Family -- The family name
217     * <LI> GTC_ValidPatch -- A patch validity flag
218     * <LI> GTC_D3MNumber -- The d3m number for the patch
219     * <LI> GTC_EdgeSideIndexes -- A list of side indexes for each edge (which side does
220     * each edge belong to)
221     * <LI> GTC_EdgeReversedFlags -- A list of flags indicating if each edge is reversed or not.
222     * </UL>
223     * <DT> SOURCES
224     * <DD> The sources for the "background grid" are stored in this list as PointString
225     * objects. Each PointString has two points for the ends of the source (though the 2nd
226     * one is sometimes not required, it is always there). As with curves, the points can
227     * either be in free space (X,Y,Z) or subranged onto a NURBS curve or surface. The
228     * global source list has the following user data keys associated with it:
229     * <UL>
230     * <LI> GTC_Delta1, GTC_Rate1, &amp; GTC_Rate2 -- Global boundary layer growth parameters.
231     * <LI> GTC_NLayers -- The number of layers in the boundary layer grid
232     * <LI> GTC_NIterations -- The number of refinement iterations for the boundary layer grid.
233     * <LI> GTC_Stretching -- Global flags indicating if there is cell stretching in this grid
234     * <LI> GTC_ViscousLayers -- Global flag indicating if viscous layers should be grown or not.
235     * </UL>
236     * Each source in the SOURCES list has the following user data keys associated with it:
237     * <UL>
238     * <LI> GTC_SourceType -- The type code for the source.
239     * <LI> GTC_Family -- The family name
240     * <LI> GTC_StretchingDir -- The stretching direction code.
241     * <LI> GTC_an, GTC_bn, and GTC_Alpha -- Source strength parameters
242     * </UL>
243     * Finally, each point in each source has the following keys stored in it's user data:
244     * <UL>
245     * <LI> GTC_s, GTC_S -- Source strength information
246     * <LI> GTC_ro, GTC_ri -- Outer and inner radii for volume sources if required by the source type.
247     * <LI> GTC_Delta1, GTC_Rate1, GTC_Rate2 -- Values to override the global boundary layer
248     * grown parameters.
249     * </UL>
250     * </DL>
251     * <p>
252     * WARNING: This file format is not unit aware. You must set the units
253     * to be used by calling "setFileUnits()" before calling this method!
254     * </p>
255     *
256     * @param inputFile The input file containing the geometry to be read in. May not be 
257     *                  null.
258     * @return A {@link GeometryList} object containing the geometry read in from the
259     *         file. If the file has no geometry in it, then this list will have no
260     *         components in it (will have a size() of zero).
261     * @throws IOException If there is a problem reading the specified file.
262     * @see #setFileUnits(javax.measure.unit.Unit) 
263     */
264    @Override
265    @SuppressWarnings("null")
266    public GeometryList read(File inputFile) throws IOException {
267        requireNonNull(inputFile);
268        _warnings.clear();
269
270        //  Create an empty geometry list.
271        GeometryList output = GeomList.newInstance();
272        GeomList surfaces = GeomList.newInstance();
273        surfaces.setName("SURFACES");
274        output.add(surfaces);
275        GeomList<PointString> curves = GeomList.newInstance();
276        curves.setName("CURVES");
277        output.add(curves);
278        GeomList patches = GeomList.newInstance();
279        patches.setName("PATCHES");
280        output.add(patches);
281        GeomList sources = GeomList.newInstance();
282        sources.setName("SOURCES");
283        output.add(sources);
284
285        Unit<Length> unit = getFileUnits();
286        TextTokenizer tokenizer = TextTokenizer.newInstance();
287        tokenizer.setDelimiters(" ");
288
289        //  GTC files are required to be in ASCII with U.S. style number formatting.
290        //  Get the default locale and save it.  Then change the default locale to US.
291        Locale defLocale = Locale.getDefault();
292        Locale.setDefault(Locale.US);
293
294        // Create a reader to access the ASCII file.
295        try (LineNumberReader reader = new LineNumberReader(new FileReader(inputFile))) {
296            try {
297                //  The 1st 4 lines are comments.
298                for (int i = 0; i < 4; ++i)
299                    readLine(reader);
300
301                //  Get the GTC version information.
302                String aLine = readLine(reader);
303                tokenizer.setText(aLine);
304                tokenizer.nextToken();
305                String version = tokenizer.nextToken().toString();
306                output.putUserData("GTC_Version", version);
307
308                //  Read in the global tolerance parameter.
309                Text token = tokenizer.nextToken();
310                double tol = TypeFormat.parseDouble(token);
311                output.putUserData("GTC_Tol", Parameter.valueOf(tol, unit));
312
313                //  Read in the name of the model.
314                String name = readLine(reader).trim();
315                output.setName(name);
316                output.putUserData("GTC_Name", name);
317
318                //  Skip the General Display Constants.
319                for (int i = 0; i < 4; ++i)
320                    readLine(reader);
321
322                //  Read in all the surface definitions and put them in the "surfaces" list.
323                readSurfaces(reader, tokenizer, unit, surfaces);
324
325                //  Read in all the "curve" definitions (which are actually strings of points).
326                readCurves(reader, tokenizer, unit, surfaces, curves);
327
328                //  Read in the patch defintitions into the "patches" list.
329                readPatches(reader, tokenizer, surfaces, curves, patches);
330
331                //  Read in the source definitions into the "sources" list.
332                readSources(reader, tokenizer, sources, unit, surfaces);
333
334            } catch (NumberFormatException e) {
335                throw new IOException(MessageFormat.format(RESOURCES.getString("parseErrMsg"),
336                        DESCRIPTION, reader.getLineNumber()));
337            }
338        } finally {
339            //  Restore the default locale.
340            Locale.setDefault(defLocale);
341        }
342
343        return output;
344    }
345
346    /**
347     * Read in surface (and curve) definitions from the file./
348     *
349     * @param reader    The reader for reading lines of data from the file.
350     * @param tokenizer The tokenizer to use to tokenize each line.
351     * @param unit      The unit used for interpreting the data.
352     * @param surfaces  The list of surfaces to have all the new ones added to.
353     * @throws IOException If there is any problem reading from the file.
354     */
355    private static void readSurfaces(LineNumberReader reader, TextTokenizer tokenizer,
356            Unit<Length> unit, GeomList surfaces) throws IllegalArgumentException, IOException {
357
358        //  The next line should contain surface definitions.
359        String aLine = readLine(reader).trim();
360        if (!"SURFACES".equals(aLine) && !"GTSURFACES".equals(aLine))
361            throw new IOException(MessageFormat.format(
362                    RESOURCES.getString("missingTypeData"), "SURFACES"));
363
364        //  Read in the number of surfaces.
365        aLine = readLine(reader);
366        int nsurf = TypeFormat.parseInt(aLine);
367
368        //  Skip 2 lines.
369        for (int i = 0; i < 2; ++i)
370            readLine(reader);
371
372        //  Loop over all the surfaces.
373        for (int sidx = 0; sidx < nsurf; ++sidx) {
374            //  Read in the code indicating if this is a trimmed surface.
375            aLine = readLine(reader);
376            tokenizer.setText(aLine);
377            tokenizer.nextToken();
378            tokenizer.nextToken();
379            Text token = tokenizer.nextToken();
380            int itrim = TypeFormat.parseInt(token);
381            if (itrim == 1) {
382                System.out.println("itrim == 1, sidx = " + sidx);
383                System.out.println("Processing stopped");
384                break;
385            }
386
387            //  Read in the number of control points and degree of the surface.
388            aLine = readLine(reader);
389            tokenizer.setText(aLine);
390            token = tokenizer.nextToken();
391            int k1 = TypeFormat.parseInt(token);
392            token = tokenizer.nextToken();
393            int k2 = TypeFormat.parseInt(token);
394            tokenizer.nextToken();
395            tokenizer.nextToken();
396            token = tokenizer.nextToken();
397            int m1 = TypeFormat.parseInt(token);
398            token = tokenizer.nextToken();
399            int m2 = TypeFormat.parseInt(token);
400
401            //  Read in the family name & IGES type.
402            aLine = readLine(reader);
403            tokenizer.setText(aLine);
404            tokenizer.nextToken();
405            String family = tokenizer.nextToken().toString();
406            String IGEStype = tokenizer.nextToken().toString();
407
408            //  Skip a line.
409            readLine(reader);
410
411            //  Read in the S-direction knot vector.
412            int n = k1 + 1 + m1 + 1;        //  Number of knots.
413            double sStart = Double.MAX_VALUE;
414            double sEnd = -Double.MAX_VALUE;
415            double[] sKnots = new double[n];
416            for (int i = 0; i < n; ++i) {
417                aLine = readLine(reader).trim();
418                double u = TypeFormat.parseDouble(aLine);
419                sKnots[i] = u;
420                if (u < sStart)
421                    sStart = u;
422                if (u > sEnd)
423                    sEnd = u;
424            }
425
426            //  Scale the knots into the range 0-1.
427            double[] scaledSKnots = sKnots;
428            if (Math.abs(sStart) > MathTools.EPS || Math.abs(sEnd - 1.0) > MathTools.EPS) {
429                scaledSKnots = new double[n];
430                double m = 1. / (sEnd - sStart);
431                double b = -m * sStart;
432                for (int i = 0; i < n; ++i) {
433                    double kv = sKnots[i];
434                    kv = scaleParam(m, b, kv);
435                    scaledSKnots[i] = kv;
436                }
437            }
438
439            //  Read in the T-direction knot vector.
440            n = k2 + 1 + m2 + 1;          //  Number of knots.
441            double[] tKnots = new double[n];
442            double tStart = Double.MAX_VALUE;
443            double tEnd = -Double.MAX_VALUE;
444            for (int i = 0; i < n; ++i) {
445                aLine = readLine(reader).trim();
446                double v = TypeFormat.parseDouble(aLine);
447                tKnots[i] = v;
448                if (v < tStart)
449                    tStart = v;
450                if (v > tEnd)
451                    tEnd = v;
452            }
453
454            //  Scale the knots into the range 0-1.
455            double[] scaledTKnots = tKnots;
456            if (Math.abs(tStart) > MathTools.EPS || Math.abs(tEnd - 1.0) > MathTools.EPS) {
457                scaledTKnots = new double[n];
458                double m = 1. / (tEnd - tStart);
459                double b = -m * tStart;
460                for (int i = 0; i < n; ++i) {
461                    double kv = tKnots[i];
462                    kv = scaleParam(m, b, kv);
463                    scaledTKnots[i] = kv;
464                }
465            }
466
467            //  Create the knot vectors.
468            KnotVector kvS = KnotVector.newInstance(m1, scaledSKnots);
469            KnotVector kvT = KnotVector.newInstance(m2, scaledTKnots);
470
471            //  Read in the control point coordinates.
472            double[][] cpsX = new double[k1 + 1][k2 + 1];
473            double[][] cpsY = new double[k1 + 1][k2 + 1];
474            double[][] cpsZ = new double[k1 + 1][k2 + 1];
475            double[][] cpsW = new double[k1 + 1][k2 + 1];
476            readArray(reader, tokenizer, k1, k2, cpsX);
477            readArray(reader, tokenizer, k1, k2, cpsY);
478            readArray(reader, tokenizer, k1, k2, cpsZ);
479            readArray(reader, tokenizer, k1, k2, cpsW);
480
481            //  Skip 18 lines.
482            for (int i = 0; i < 18; ++i)
483                readLine(reader);
484
485            if (IGEStype.equals("-126")) {
486                //  Collapsed surface. It's actually a curve.
487                FastTable<ControlPoint> cps = FastTable.newInstance();
488                for (int i = 0; i <= k1; ++i) {
489                    double weight = cpsW[i][0];
490                    double x = cpsX[i][0];
491                    double y = cpsY[i][0];
492                    double z = cpsZ[i][0];
493                    Point pnt = Point.valueOf(x, y, z, unit);
494                    cps.add(ControlPoint.valueOf(pnt, weight));
495                }
496
497                //  Create the curve.
498                BasicNurbsCurve crv = BasicNurbsCurve.newInstance(cps, kvS);
499                crv.putUserData("GTC_U0", sStart);
500                crv.putUserData("GTC_U1", sEnd);
501                crv.putUserData("GTC_Family", family);
502                crv.putUserData("GTC_Type", IGEStype);
503                crv.setName(family);
504                surfaces.add(crv);
505
506            } else {
507                //  A normal surface.
508
509                //  Create the control point network.
510                ControlPoint[][] controlPoints = new ControlPoint[k2 + 1][k1 + 1];
511                for (int i = 0; i <= k1; ++i) {
512                    for (int j = 0; j <= k2; ++j) {
513                        double weight = cpsW[i][j];
514                        double x = cpsX[i][j];
515                        double y = cpsY[i][j];
516                        double z = cpsZ[i][j];
517                        Point pnt = Point.valueOf(x, y, z, unit);
518                        controlPoints[j][i] = ControlPoint.valueOf(pnt, weight);
519                    }
520                }
521                ControlPointNet cpNet = ControlPointNet.valueOf(controlPoints);
522
523                //  Create the surface.
524                BasicNurbsSurface surface = BasicNurbsSurface.newInstance(cpNet, kvS, kvT);
525                surface.putUserData("GTC_U0", sStart);
526                surface.putUserData("GTC_U1", sEnd);
527                surface.putUserData("GTC_V0", tStart);
528                surface.putUserData("GTC_V1", tEnd);
529                surface.putUserData("GTC_Family", family);
530                surface.putUserData("GTC_Type", IGEStype);
531                surface.setName(family);
532                surfaces.add(surface);
533            }
534
535        }
536    }   //  end readSurfaces()
537
538    /**
539     * Read a single array of data from the file.
540     */
541    private static void readArray(LineNumberReader reader, TextTokenizer tokenizer,
542            int n1, int n2, double[][] arr) throws IOException, NumberFormatException {
543        String aLine = readLine(reader);
544        tokenizer.setText(aLine);
545        for (int i = 0; i <= n1; ++i) {
546            for (int j = 0; j <= n2; ++j) {
547                if (!tokenizer.hasMoreTokens()) {
548                    aLine = readLine(reader);
549                    tokenizer.setText(aLine);
550                }
551                Text token = tokenizer.nextToken();
552                double v = TypeFormat.parseDouble(token);
553                arr[i][j] = v;
554            }
555        }
556    }
557
558    /**
559     * Scale the parameter value in the the required range from 0.0 to 1.0.
560     */
561    private static double scaleParam(double m, double b, double value) {
562        //  Scale the parameter value.
563        double kv = m * value + b;
564
565        //  Watch for roundoff.
566        if (kv < 0.0)
567            kv = 0.0;
568        else if (kv > 1.0)
569            kv = 1.0;
570
571        return kv;
572    }
573
574    /**
575     * Read in the list of curves from the data file. "curves" are actually lists of
576     * points in GTC terminology.
577     *
578     * @param reader    The reader for reading lines of data from the file.
579     * @param tokenizer The tokenizer to use to tokenize each line.
580     * @param unit      The unit used for interpreting the data.
581     * @param surfaces  The list of already existing surfaces (some curve points are
582     *                  subranges onto surfaces).
583     * @param curves    The list to be filled in with PointString objects for each
584     *                  "curve".
585     * @throws IOException If there is any problem reading from the file.
586     */
587    private static void readCurves(LineNumberReader reader, TextTokenizer tokenizer, Unit<Length> unit,
588            GeomList surfaces, GeomList<PointString> curves) throws NumberFormatException, IOException {
589
590        //  The next line should contain "curve" definitions.
591        String aLine = readLine(reader).trim();
592        if (!"CURVES".equals(aLine) && !"GTCURVES".equals(aLine))
593            throw new IOException(MessageFormat.format(
594                    RESOURCES.getString("missingTypeData"), "CURVES"));
595
596        //  Read in the number of curves.
597        aLine = readLine(reader);
598        int ncrvs = TypeFormat.parseInt(aLine);
599
600        //  Skip 2 lines.
601        for (int i = 0; i < 2; ++i)
602            readLine(reader);
603
604        //  Loop over all the "curves" (which are actually stings of points).
605        for (int cidx = 0; cidx < ncrvs; ++cidx) {
606            //  Read in the number of points in this curve.
607            aLine = readLine(reader);
608            tokenizer.setText(aLine);
609            tokenizer.nextToken();
610            tokenizer.nextToken();
611            Text token = tokenizer.nextToken();
612            int npts = TypeFormat.parseInt(token);
613
614            //  Get the name of this "curve".
615            tokenizer.nextToken();
616            tokenizer.nextToken();
617            String crvName = tokenizer.nextToken().toString();
618
619            //  Skip a line.
620            readLine(reader);
621
622            //  Read in all the points for this curve.
623            PointString str = PointString.newInstance();
624            str.setName(crvName);
625            for (int i = 0; i < npts; ++i) {
626                aLine = readLine(reader);
627                tokenizer.setText(aLine);
628                token = tokenizer.nextToken();
629                double x = TypeFormat.parseDouble(token);
630                token = tokenizer.nextToken();
631                double y = TypeFormat.parseDouble(token);
632                token = tokenizer.nextToken();
633                double z = TypeFormat.parseDouble(token);
634                token = tokenizer.nextToken();
635                double u = TypeFormat.parseDouble(token);
636                token = tokenizer.nextToken();
637                double v = TypeFormat.parseDouble(token);
638                token = tokenizer.nextToken();
639                int j = TypeFormat.parseInt(token);
640
641                if (j == 0) {
642                    //  Not attached to a "surface".
643                    Point p = Point.valueOf(x, y, z, unit);
644                    str.add(p);
645
646                } else {
647                    SubrangePoint p;
648                    --j;    //  Convert index from unit offset to 0 offset.
649                    if (surfaces.get(j) instanceof BasicNurbsSurface) {
650                        BasicNurbsSurface Ssrf = (BasicNurbsSurface)surfaces.get(j);
651
652                        //  Extract the original parametric bounds of the surface.
653                        double sStart = (Double)Ssrf.getUserData("GTC_U0");
654                        double tStart = (Double)Ssrf.getUserData("GTC_V0");
655                        double sEnd = (Double)Ssrf.getUserData("GTC_U1");
656                        double tEnd = (Double)Ssrf.getUserData("GTC_V1");
657
658                        //  Scale the parameters into range.
659                        double mS = 1. / (sEnd - sStart);
660                        double bS = -mS * sStart;
661                        double mT = 1. / (tEnd - tStart);
662                        double bT = -mT * tStart;
663                        u = scaleParam(mS, bS, u);
664                        v = scaleParam(mT, bT, v);
665
666                        //  Create the point.
667                        p = Ssrf.getPoint(u, v);
668
669                    } else {
670                        BasicNurbsCurve Bcrv = (BasicNurbsCurve)surfaces.get(j);
671
672                        //  Extract the original parametric bounds of the surface.
673                        double sStart = (Double)Bcrv.getUserData("GTC_U0");
674                        double sEnd = (Double)Bcrv.getUserData("GTC_U1");
675
676                        //  Scale the parameter into range.
677                        double mS = 1. / (sEnd - sStart);
678                        double bS = -mS * sStart;
679                        u = scaleParam(mS, bS, u);
680
681                        //  Create the point.
682                        p = Bcrv.getPoint(u);
683                    }
684                    str.add(p);
685                }
686            }
687            curves.add(str);
688        }
689
690    }   //  end readCurves()
691
692    /**
693     * Read in the patch definitions from the file.
694     *
695     * @param reader    The reader for reading lines of data from the file.
696     * @param tokenizer The tokenizer to use to tokenize each line.
697     * @param unit      The unit used for interpreting the data.
698     * @param surfaces  The list of already existing surfaces (some patches lie on top of
699     *                  surfaces).
700     * @param curves    The list of already existing curve definitions (patch boundaries
701     *                  are made up of curves).
702     * @param patches   The list of patches to be filled in by this method.
703     * @throws IOException If there is any problem reading from the file.
704     */
705    private static void readPatches(LineNumberReader reader, TextTokenizer tokenizer, GeomList surfaces,
706            GeomList<PointString> curves, GeomList patches) throws IOException {
707
708        //  The next line should contain "patch" definitions.
709        String aLine = readLine(reader).trim();
710        if (!"PATCHES".equals(aLine) && !"GTPATCHES".equals(aLine))
711            throw new IOException(MessageFormat.format(
712                    RESOURCES.getString("missingTypeData"), "PATCHES"));
713
714        //  Read in the number of patches.
715        aLine = readLine(reader);
716        int npatches = TypeFormat.parseInt(aLine);
717
718        //  Skip 3 lines.
719        for (int i = 0; i < 3; ++i)
720            readLine(reader);
721
722        //  Loop over all the patches.
723        for (int pidx = 0; pidx < npatches; ++pidx) {
724            GeomList patch = GeomList.newInstance();
725
726            aLine = readLine(reader);
727            tokenizer.setText(aLine);
728            tokenizer.nextToken();
729            tokenizer.nextToken();
730            tokenizer.nextToken();
731            tokenizer.nextToken();
732
733            //  Read in the number of edges for the patch.
734            Text token = tokenizer.nextToken();
735            int nedges = TypeFormat.parseInt(token);
736
737            //  Read in the patch type code.
738            token = tokenizer.nextToken();
739            int pType = TypeFormat.parseInt(token);
740            patch.putUserData("GTC_PatchType", pType);
741
742            //  Read in the boundary condition type code.
743            token = tokenizer.nextToken();
744            int BCType = TypeFormat.parseInt(token);
745            patch.putUserData("GTC_BCType", BCType);
746
747            //  Read in the family name.
748            String family = tokenizer.nextToken().toString();
749            patch.putUserData("GTC_Family", family);
750
751            //  Read in the next line.
752            aLine = readLine(reader);
753            tokenizer.setText(aLine);
754
755            //  Read in the d3m number
756            token = tokenizer.nextToken();
757            int d3m = TypeFormat.parseInt(token);
758            patch.putUserData("GTC_D3MNumber", d3m);
759            patch.putUserData("GTC_ValidPatch", (d3m == -1 ? Boolean.FALSE : Boolean.TRUE));
760
761            //  Read in the number of surfaces (should be either 0 or 1).
762            token = tokenizer.nextToken();
763            int nsrf = TypeFormat.parseInt(token);
764            if (nsrf < 0 || nsrf > 1)
765                throw new IOException(MessageFormat.format(
766                        RESOURCES.getString("patchSrfNumErr"), (pidx + 1), nsrf));
767
768            GeomList srfLst = GeomList.newInstance();
769            patch.add(srfLst);
770            if (nsrf > 0) {
771                //  Read in the surface related information.
772                aLine = readLine(reader);
773                tokenizer.setText(aLine);
774
775                //  Read in the surface ID number
776                token = tokenizer.nextToken();
777                int srfID = TypeFormat.parseInt(token);
778                srfLst.add(surfaces.get(srfID - 1));
779
780                // Skip the next two lines.
781                readLine(reader);
782                readLine(reader);
783            }
784
785            //  Create a list of edges.
786            GeomList<PointString> edgeLst = GeomList.newInstance();
787            patch.add(edgeLst);
788
789            //  Create a list of side index numbers.
790            FastTable<Integer> sides = FastTable.newInstance();
791            patch.putUserData("GTC_EdgeSideIndexes", sides);
792
793            //  Create a list of flags indicating if the edge curve is reversed or not.
794            FastTable<Boolean> reversed = FastTable.newInstance();
795            patch.putUserData("GTC_EdgeReversedFlags", reversed);
796
797            //  Loop over the edgeLst.
798            int side = 0;
799            for (int eidx = 0; eidx < nedges; ++eidx) {
800                //  Read in the edge.
801                aLine = readLine(reader);
802                tokenizer.setText(aLine);
803
804                //  Skip the edge ID number (sequential).
805                tokenizer.nextToken();
806
807                //  Is the edge reversed?
808                token = tokenizer.nextToken();
809                int rev = TypeFormat.parseInt(token);
810                if (rev == -1)
811                    reversed.add(Boolean.TRUE);
812                else
813                    reversed.add(Boolean.FALSE);
814
815                //  Read in the curve ID number.
816                token = tokenizer.nextToken();
817                int crvID = TypeFormat.parseInt(token);
818                edgeLst.add(curves.get(crvID - 1));
819
820                //  Read in the side flag.
821                token = tokenizer.nextToken();
822                int sideFlg = TypeFormat.parseInt(token);
823                if (sideFlg == 1 || eidx == 0)
824                    ++side;
825                sides.add(side);
826            }
827
828            //  Save this patch in the output list.
829            patches.add(patch);
830
831        }   //  Next pidx
832
833    }   //  end readPatches()
834
835    /**
836     * Read in the list of sources (or "background grid") from the file.
837     *
838     * @param reader    The reader for reading lines of data from the file.
839     * @param tokenizer The tokenizer to use to tokenize each line.
840     * @param unit      The unit used for interpreting the data.
841     * @param surfaces  The list of already existing surfaces (some curve source are
842     *                  subranges onto surfaces).
843     * @param sources   The list to be filled in with information for each source.
844     * @throws IOException If there is a problem reading from the file.
845     */
846    private static void readSources(LineNumberReader reader, TextTokenizer tokenizer, GeomList sources,
847            Unit<Length> unit, GeomList surfaces) throws IOException, NumberFormatException {
848
849        //  The next line should contain "source" definitions.
850        String aLine = readLine(reader).trim();
851        if (!"Background Grid".equals(aLine))
852            throw new IOException(MessageFormat.format(
853                    RESOURCES.getString("missingTypeData"), "Background Grid"));
854
855        //  Read in the number of sources.
856        aLine = readLine(reader);
857        int nsources = TypeFormat.parseInt(aLine);
858
859        //  Read in the global parameters.
860        aLine = readLine(reader);
861        tokenizer.setText(aLine);
862
863        //  Read in the delta1 parameter.
864        Text token = tokenizer.nextToken();
865        double delta1 = TypeFormat.parseDouble(token);
866        sources.putUserData("GTC_Delta1", delta1);
867
868        //  Read in the rate1 parameter.
869        token = tokenizer.nextToken();
870        double rate1 = TypeFormat.parseDouble(token);
871        sources.putUserData("GTC_Rate1", rate1);
872
873        //  Read in the rate2 parameter.
874        token = tokenizer.nextToken();
875        double rate2 = TypeFormat.parseDouble(token);
876        sources.putUserData("GTC_Rate2", rate2);
877
878        //  Read in the number of layers parameter.
879        token = tokenizer.nextToken();
880        int nLayer = TypeFormat.parseInt(token);
881        sources.putUserData("GTC_NLayers", nLayer);
882
883        aLine = readLine(reader);
884        tokenizer.setText(aLine);
885
886        //  Read in the ni parameter.
887        token = tokenizer.nextToken();
888        int ni = TypeFormat.parseInt(token);
889        sources.putUserData("GTC_NIterations", ni);
890
891        //  Skip one.
892        tokenizer.nextToken();
893
894        //  Read in the stretching flag.
895        token = tokenizer.nextToken();
896        int stretching = TypeFormat.parseInt(token);
897        sources.putUserData("GTC_Stretching", (stretching == 1 ? Boolean.TRUE : Boolean.FALSE));
898
899        //  Read in the grow viscous layers flag.
900        token = tokenizer.nextToken();
901        int viscous = TypeFormat.parseInt(token);
902        sources.putUserData("GTC_ViscousLayers", (viscous == 1 ? Boolean.TRUE : Boolean.FALSE));
903
904        //  Loop over all the sources.
905        for (int sidx = 0; sidx < nsources; ++sidx) {
906            //  A "source" consists of two points.  The 2nd one is sometimes unnecessary, but always present.
907            PointString<GeomPoint> source = PointString.newInstance();
908            sources.add(source);
909
910            aLine = readLine(reader);
911            tokenizer.setText(aLine);
912            tokenizer.nextToken();
913            tokenizer.nextToken();
914            tokenizer.nextToken();
915            tokenizer.nextToken();
916
917            //  Read in the source type.
918            token = tokenizer.nextToken();
919            int type = TypeFormat.parseInt(token);
920            source.putUserData("GTC_SourceType", type);
921
922            //  Read in the source family.
923            String family = tokenizer.nextToken().toString();
924            source.putUserData("GTC_Family", family);
925
926            //  Read in the stretching direction.
927            aLine = readLine(reader);
928            tokenizer.setText(aLine);
929            token = tokenizer.nextToken();
930            int stetchingDir = TypeFormat.parseInt(token);
931            source.putUserData("GTC_StretchingDir", stetchingDir);
932
933            //  Read in an.
934            aLine = readLine(reader);
935            tokenizer.setText(aLine);
936            token = tokenizer.nextToken();
937            double an = TypeFormat.parseDouble(token);
938            source.putUserData("GTC_an", an);
939
940            //  Read in bn.
941            token = tokenizer.nextToken();
942            double bn = TypeFormat.parseDouble(token);
943            source.putUserData("GTC_bn", bn);
944
945            //  Read in alpha.
946            token = tokenizer.nextToken();
947            double alpha = TypeFormat.parseDouble(token);
948            source.putUserData("GTC_Alpha", alpha);
949
950            //  Read in the two points.
951            for (int i = 0; i < 2; ++i) {
952                //  Read in s.
953                aLine = readLine(reader);
954                tokenizer.setText(aLine);
955                token = tokenizer.nextToken();
956                double smalls = TypeFormat.parseDouble(token);
957
958                //  Read in S.
959                token = tokenizer.nextToken();
960                double capS = TypeFormat.parseDouble(token);
961
962                //  Read in ro.
963                token = tokenizer.nextToken();
964                double ro = TypeFormat.parseDouble(token);
965
966                //  Read in ri.
967                token = tokenizer.nextToken();
968                double ri = TypeFormat.parseDouble(token);
969
970                //  Skip next line.
971                readLine(reader);
972
973                //  Read in delta1.
974                aLine = readLine(reader);
975                tokenizer.setText(aLine);
976                token = tokenizer.nextToken();
977                delta1 = TypeFormat.parseDouble(token);
978
979                //  Read in rate1.
980                token = tokenizer.nextToken();
981                rate1 = TypeFormat.parseDouble(token);
982
983                //  Read in rate2.
984                token = tokenizer.nextToken();
985                rate2 = TypeFormat.parseDouble(token);
986
987                //  Read in the point coordinates.
988                aLine = readLine(reader);
989                tokenizer.setText(aLine);
990                token = tokenizer.nextToken();
991                double x = TypeFormat.parseDouble(token);
992                token = tokenizer.nextToken();
993                double y = TypeFormat.parseDouble(token);
994                token = tokenizer.nextToken();
995                double z = TypeFormat.parseDouble(token);
996                token = tokenizer.nextToken();
997                double u = TypeFormat.parseDouble(token);
998                token = tokenizer.nextToken();
999                double v = TypeFormat.parseDouble(token);
1000                token = tokenizer.nextToken();
1001                int j = TypeFormat.parseInt(token);
1002
1003                if (j == 0) {
1004                    //  Not attached to a "surface".
1005                    Point p = Point.valueOf(x, y, z, unit);
1006                    source.add(p);
1007
1008                } else {
1009                    SubrangePoint p;
1010                    --j;    //  Convert index from unit offset to 0 offset.
1011                    if (surfaces.get(j) instanceof BasicNurbsSurface) {
1012                        BasicNurbsSurface Ssrf = (BasicNurbsSurface)surfaces.get(j);
1013
1014                        //  Extract the original parametric bounds of the surface.
1015                        double sStart = (Double)Ssrf.getUserData("GTC_U0");
1016                        double tStart = (Double)Ssrf.getUserData("GTC_V0");
1017                        double sEnd = (Double)Ssrf.getUserData("GTC_U1");
1018                        double tEnd = (Double)Ssrf.getUserData("GTC_V1");
1019
1020                        //  Scale the parameters into range.
1021                        double mS = 1. / (sEnd - sStart);
1022                        double bS = -mS * sStart;
1023                        double mT = 1. / (tEnd - tStart);
1024                        double bT = -mT * tStart;
1025                        u = scaleParam(mS, bS, u);
1026                        v = scaleParam(mT, bT, v);
1027
1028                        //  Create the point.
1029                        p = Ssrf.getPoint(u, v);
1030
1031                    } else {
1032                        BasicNurbsCurve Bcrv = (BasicNurbsCurve)surfaces.get(j);
1033
1034                        //  Extract the original parametric bounds of the surface.
1035                        double sStart = (Double)Bcrv.getUserData("GTC_U0");
1036                        double sEnd = (Double)Bcrv.getUserData("GTC_U1");
1037
1038                        //  Scale the parameter into range.
1039                        double mS = 1. / (sEnd - sStart);
1040                        double bS = -mS * sStart;
1041                        u = scaleParam(mS, bS, u);
1042
1043                        //  Create the point.
1044                        p = Bcrv.getPoint(u);
1045                    }
1046                    source.add(p);
1047                }
1048
1049                //  Store the meta-data for this point.
1050                GeomPoint p = source.get(i);
1051                p.putUserData("GTC_s", smalls);
1052                p.putUserData("GTC_S", capS);
1053                p.putUserData("GTC_ro", ro);
1054                p.putUserData("GTC_ri", ri);
1055                p.putUserData("GTC_Delta1", delta1);
1056                p.putUserData("GTC_Rate1", rate1);
1057                p.putUserData("GTC_Rate2", rate2);
1058
1059            }   //  Next i
1060
1061            //  Skip the next 8*4 lines.
1062            for (int i = 0; i < 8 * 4; ++i) {
1063                readLine(reader);
1064            }
1065
1066        }   //  Next sidx
1067
1068    }   //  end readSources()
1069
1070    /**
1071     * Returns <code>true</code>. This reader can write some entity types to a GTC file.
1072     *
1073     * @return true
1074     */
1075    @Override
1076    public boolean canWriteData() {
1077        return true;
1078    }
1079
1080    /**
1081     * Writes out a geometry file for the geometry contained in the supplied
1082     * {@link GeometryList} object. If the input geometry is not 3D, it will be forced to
1083     * be 3D (by padding if there are too few or by truncating additional dimensions).
1084     * <p>
1085     * WARNING: This format is not unit aware. The geometry will be written out in
1086     * whatever its current units are! Make sure to convert to the desired units for the
1087     * file before calling this method.
1088     * </p>
1089     *
1090     * @param outputFile The output File to which the geometry is to be written. May not
1091     *                   be null.
1092     * @param geometry   The {@link GeometryList} object containing the geometry to be
1093     *                   written out. May not be null.
1094     * @throws IOException If there is a problem writing to the specified file.
1095     */
1096    @Override
1097    public void write(File outputFile, GeometryList geometry) throws IOException {
1098        requireNonNull(outputFile);
1099        
1100        _warnings.clear();
1101        if (!geometry.containsGeometry()) {
1102            _warnings.add(RESOURCES.getString("noGeometryWarning"));
1103            return;
1104        }
1105
1106        //  Check for input data formatting.
1107        int size = geometry.size();
1108        if (size > 4)
1109            throw new IOException(MessageFormat.format(RESOURCES.getString("inputGeomErr1"), size));
1110        for (int i = 0; i < size; ++i) {
1111            Object elem = geometry.get(i);
1112            if (!(elem instanceof GeomList))
1113                throw new IOException(MessageFormat.format(
1114                        RESOURCES.getString("inputGeomErr2"), elem.getClass(), i + 1));
1115        }
1116
1117        //  Pull out the big pieces.
1118        GeomList surfaces = (GeomList)geometry.get(0);
1119        GeomList<PointString<GeomPoint>> curves;
1120        if (size > 1)
1121            curves = (GeomList<PointString<GeomPoint>>)geometry.get(1);
1122        else
1123            curves = GeomList.newInstance();
1124        GeomList<GeomList> patches;
1125        if (size > 2)
1126            patches = (GeomList<GeomList>)geometry.get(2);
1127        else
1128            patches = GeomList.newInstance();
1129        GeomList<PointString<GeomPoint>> sources;
1130        if (size == 4)
1131            sources = (GeomList<PointString<GeomPoint>>)geometry.get(3);
1132        else
1133            sources = GeomList.newInstance();
1134
1135        //  The surfaces list must contain only NURBS surfaces and curves.
1136        size = surfaces.size();
1137        for (int i = 0; i < size; ++i) {
1138            Object elem = surfaces.get(i);
1139            if (!(elem instanceof NurbsSurface) && !(elem instanceof NurbsCurve))
1140                throw new IOException(MessageFormat.format(RESOURCES.getString("elementTypeErr"),
1141                        "SURFACES", "NURBS surfaces & curves", elem.getClass(), i + 1));
1142        }
1143
1144        //  The curves list must contain only PointString objects.
1145        size = curves.size();
1146        for (int i = 0; i < size; ++i) {
1147            Object elem = curves.get(i);
1148            if (!(elem instanceof PointString))
1149                throw new IOException(MessageFormat.format(RESOURCES.getString("elementTypeErr"),
1150                        "CURVES", "PointString objects", elem.getClass(), i + 1));
1151        }
1152
1153        //  The patches list must contain lists that contain exactly two geometry lists;
1154        //  1st one with at most 1 surface, 2nd with a list of PointStrings.
1155        size = patches.size();
1156        for (int i = 0; i < size; ++i) {
1157            Object elem = patches.get(i);
1158            if (!(elem instanceof GeomList))
1159                throw new IOException(MessageFormat.format(RESOURCES.getString("elementTypeErr"),
1160                        "PATCHES", "GeomList objects", elem.getClass(), i + 1));
1161
1162            GeomList<GeomList> patch = (GeomList<GeomList>)elem;
1163            if (patch.size() != 2)
1164                throw new IOException(MessageFormat.format(
1165                        RESOURCES.getString("patchInputErr1"), patch.size(), i + 1));
1166
1167            for (int j = 0; j < 2; ++j) {
1168                elem = patch.get(j);
1169                if (!(elem instanceof GeomList))
1170                    throw new IOException(MessageFormat.format(
1171                            RESOURCES.getString("patchInputErr2"), elem.getClass(), j + 1, i + 1));
1172            }
1173
1174            if (patch.get(0).size() > 1)
1175                throw new IOException(MessageFormat.format(
1176                        RESOURCES.getString("patchMultSrfErr"), patch.get(0).size(), i + 1));
1177            GeomList srfLst = patch.get(0);
1178            if (srfLst.size() > 0 && !(srfLst.get(0) instanceof NurbsSurface))
1179                throw new IOException(MessageFormat.format(
1180                        RESOURCES.getString("patchSubListTypeErr"), srfLst.get(0).getClass()));
1181            GeomList edges = patch.get(1);
1182            if (edges.size() < 3)
1183                throw new IOException(MessageFormat.format(
1184                        RESOURCES.getString("patchEdgeCountErr"), edges.size(), i + 1));
1185            for (int j = 0; j < edges.size(); ++j) {
1186                elem = edges.get(j);
1187                if (!(elem instanceof PointString))
1188                    throw new IOException(MessageFormat.format(
1189                            RESOURCES.getString("patchEdgeTypeErr"), elem.getClass(), j + 1, i + 1));
1190            }
1191        }
1192
1193        //  The sources list must contain only PointString objects each of which contains exactly 2 points.
1194        size = sources.size();
1195        for (int i = 0; i < size; ++i) {
1196            Object elem = sources.get(i);
1197            if (!(elem instanceof PointString))
1198                throw new IOException(MessageFormat.format(RESOURCES.getString("elementTypeErr"),
1199                        "SOURCES", "PointString objects", elem.getClass(), i + 1));
1200            PointString source = sources.get(i);
1201            if (source.size() != 2)
1202                throw new IOException(MessageFormat.format(
1203                        RESOURCES.getString("sourceSizeErr"), source.size(), i + 1));
1204        }
1205
1206        //  GTC files are required to be in ASCII with U.S. style number formatting.
1207        //  Get the default locale and save it.  Then change the default locale to US.
1208        Locale defLocale = Locale.getDefault();
1209        Locale.setDefault(Locale.US);
1210
1211        //  Create a PrintWriter that writes to the specified file.
1212        StackContext.enter();
1213        try (PrintWriter writer = new PrintWriter(outputFile, StandardCharsets.US_ASCII.name())) {
1214
1215            //  Write out the header information.
1216            Unit<Length> units = geometry.getUnit();
1217            setFileUnits(units);
1218            writeHeader(writer, outputFile.getAbsolutePath(), geometry);
1219
1220            //  Write out the NURBS surfaces & curves.
1221            writeSurfaces(writer, surfaces, units);
1222
1223            //  Write out the CURVE data.
1224            writeCurves(writer, curves, units, surfaces);
1225
1226            //  Write out the PATCHES data.
1227            writePatches(writer, patches, surfaces, curves);
1228
1229            //  Write out the SOURCES data.
1230            writer.println("Background Grid");
1231            int nSources = sources.size();
1232            writer.println(0);
1233            writer.println("0.005 0.15 0.02 100");
1234            writer.println("20 0 0 0");
1235
1236            //  Write out some mystery numbers.
1237            writer.println("1");
1238            writer.println("0");
1239            writer.println("0");
1240            writer.println("0");
1241            writer.println("1");
1242            writer.println("0");
1243            writer.println("0");
1244            writer.println("0");
1245            writer.println("1");
1246            writer.println("1 1 20");
1247            writer.println("1 -1 0 0");
1248
1249            //  Write out the default SPAR parameters.
1250            writer.println("SPAR");
1251            writer.println("0");
1252            writer.println("15");
1253            writer.println("0.1");
1254            writer.println("0.1");
1255            writer.println("0.1");
1256            writer.println("0");
1257            writer.println("0");
1258
1259        } finally {
1260            StackContext.exit();
1261
1262            //  Restore the default locale.
1263            Locale.setDefault(defLocale);
1264        }
1265
1266    }
1267
1268    /**
1269     * Write out the header for the file.
1270     *
1271     * @param writer   The PrintWriter we are writing to the file with.
1272     * @param fileName The path to the file being written out.
1273     * @param geometry The geometry being written out.
1274     */
1275    private void writeHeader(PrintWriter writer, String fileName, GeometryList geometry) {
1276        //  Write out the date & time this file was written.
1277        Date theDate = new Date();
1278        SimpleDateFormat fmt = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy", Locale.US);
1279        writer.print("The restart file was written on ");
1280        writer.println(fmt.format(theDate));
1281
1282        //  Write out the file name.
1283        writer.print("File name is ");
1284        writer.println(fileName);
1285
1286        //  Write out GTC information.
1287        writer.println("GTC 2010 - Build 20091209");
1288
1289        //  Write out general constants.
1290        writer.println("General Constants");
1291
1292        //  Write out the version.
1293        String ver = (String)geometry.getUserData("GTC_Version");
1294        if (isNull(ver))
1295            ver = "4.1";
1296        writer.print("Version ");   writer.print(ver);
1297
1298        //  Write out the global tolerance value.
1299        Parameter<Length> tol = (Parameter<Length>)geometry.getUserData("GTC_Tol");
1300        if (isNull(tol))
1301            tol = Parameter.valueOf(1e-6, getFileUnits());
1302        writer.print(" ");  writer.print(tol.getValue());
1303        writer.println(" -1");
1304
1305        //  Write out the model name.
1306        String name = (String)geometry.getUserData("GTC_Name");
1307        if (isNull(name))
1308            name = geometry.getName();
1309        if (isNull(name))
1310            name = "MyProject";
1311        writer.println(name);
1312
1313        //  Write out general display constants.
1314        writer.println("General Display Constants");
1315        writer.println("0 0");
1316        writer.println("99 99 0 0");
1317        writer.println("99 99 0 0");
1318
1319    }
1320
1321    /**
1322     * Write out the list of surfaces to a GTC restart file.
1323     *
1324     * @param writer   The PrintWriter we are writing to the file with.
1325     * @param surfaces The list of surfaces to be written out.
1326     * @param units    The units for the geometry in the file.
1327     */
1328    private static void writeSurfaces(PrintWriter writer, GeomList surfaces, Unit<Length> units) {
1329
1330        //  Write out the SURFACE data.
1331        writer.println("SURFACES");
1332        int nSrfs = surfaces.size();
1333        writer.println(nSrfs);
1334        writer.println("99 99 99 99");
1335        writer.println("99 99 99 99");
1336
1337        //  Loop over all the NURBS surfaces & curves.
1338        Parameter<Length> srfTol = Parameter.valueOf(1e-4, units);
1339        for (int sidx = 0; sidx < nSrfs; ++sidx) {
1340            Object elem = surfaces.get(sidx);
1341
1342            boolean isCrv = false;
1343            NurbsSurface srf;
1344            if (elem instanceof NurbsCurve) {
1345                // Convert curves to collapsed surfaces.
1346                NurbsCurve crv = ((NurbsCurve)elem).to(units);
1347                GeomList<NurbsCurve> crvs = GeomList.newInstance();
1348                crvs.add(crv);
1349                crvs.add(crv);
1350                srf = SurfaceFactory.createSkinnedSurface(crvs, 1, srfTol);
1351                srf.putAllUserData(crv.getAllUserData());
1352                srf.setName(crv.getName());
1353                isCrv = true;
1354            } else
1355                srf = ((NurbsSurface)elem).to(units);
1356
1357            //  Write out the surface number line.
1358            writer.print("Surface_Number ");
1359            writer.print(sidx + 1);
1360            writer.println(" 0");
1361
1362            //  Write out the number of control points and degree of the surface in each direction.
1363            int k1 = srf.getNumberOfRows();
1364            int k2 = srf.getNumberOfColumns();
1365            int m1 = srf.getSDegree();
1366            int m2 = srf.getTDegree();
1367            int n1 = k1 + m1;
1368            int n2 = k2 + m2;
1369            writer.print(k1 - 1);   writer.print(" ");
1370            writer.print(k2 - 1);   writer.print(" ");
1371            writer.print(n1);       writer.print(" ");
1372            writer.print(n2);       writer.print(" ");
1373            writer.print(m1);       writer.print(" ");
1374            writer.println(m2);
1375
1376            //  Write out a mystery number.
1377            writer.print("1 ");
1378
1379            //  Write out the family name.
1380            String family = (String)srf.getUserData("GTC_Family");
1381            if (isNull(family)) {
1382                family = srf.getName();
1383                if (isNull(family))
1384                    family = (isCrv ? "L0E126" : "L0E128");
1385            }
1386            writer.print(family);   writer.print(" ");
1387
1388            //  Write out the surface IGES type code.
1389            Object IGESType = srf.getUserData("GTC_Type");
1390            if (isNull(IGESType))
1391                IGESType = (isCrv ? -126 : 128);
1392            writer.print(IGESType);
1393            writer.println(" 99");
1394
1395            //  Write a line of mystery numbers.
1396            writer.println("0 0 0 0");
1397
1398            //  Write out the knot vector in the S direction.
1399            KnotVector kS = srf.getSKnotVector();
1400            int nKnots = kS.length();
1401            for (int k = 0; k < nKnots; ++k) {
1402                double value = kS.getValue(k);
1403                writer.printf("%15.9g", value);
1404                writer.println();
1405            }
1406
1407            //  Write out the knot vector in the T direction.
1408            KnotVector kT = srf.getTKnotVector();
1409            nKnots = kT.length();
1410            for (int k = 0; k < nKnots; ++k) {
1411                double value = kT.getValue(k);
1412                writer.printf("%15.9g", value);
1413                writer.println();
1414            }
1415
1416            //  Write out the control points.
1417            ControlPointNet cpNet = srf.getControlPoints();
1418
1419            //  Write out the X-coordinates.
1420            int count = 0;
1421            for (int i = 0; i < k1; ++i) {
1422                for (int j = 0; j < k2; ++j) {
1423                    ControlPoint cp = cpNet.get(i, j);
1424                    writer.printf("%15.9g ", cp.getPoint().getValue(0));
1425                    ++count;
1426                    if (count > 4) {
1427                        count = 0;
1428                        writer.println();
1429                    }
1430                }
1431            }
1432            if (count != 0)
1433                writer.println();
1434
1435            //  Write out the Y-coordinates.
1436            count = 0;
1437            for (int i = 0; i < k1; ++i) {
1438                for (int j = 0; j < k2; ++j) {
1439                    ControlPoint cp = cpNet.get(i, j);
1440                    writer.printf("%15.9g ", cp.getPoint().getValue(1));
1441                    ++count;
1442                    if (count > 4) {
1443                        count = 0;
1444                        writer.println();
1445                    }
1446                }
1447            }
1448            if (count != 0)
1449                writer.println();
1450
1451            //  Write out the Z-coordinates.
1452            count = 0;
1453            for (int i = 0; i < k1; ++i) {
1454                for (int j = 0; j < k2; ++j) {
1455                    ControlPoint cp = cpNet.get(i, j);
1456                    writer.printf("%15.9g ", cp.getPoint().getValue(2));
1457                    ++count;
1458                    if (count > 4) {
1459                        count = 0;
1460                        writer.println();
1461                    }
1462                }
1463            }
1464            if (count != 0)
1465                writer.println();
1466
1467            //  Write out k1*k2 "1" values (I don't know what they are for).
1468            int size = k1 * k2;
1469            count = 0;
1470            for (int i = 0; i < size; ++i) {
1471                writer.print("              1 ");
1472                ++count;
1473                if (count > 4) {
1474                    count = 0;
1475                    writer.println();
1476                }
1477            }
1478            if (count != 0)
1479                writer.println();
1480
1481            //  Write out 8 mystery number lines.
1482            writer.println("              0               1               0               1");
1483            writer.println("              0               1               0               1");
1484            writer.println("         0.0001          0.0001             0.5             0.5");
1485            writer.println("            0.1           1e-05               0               0");
1486            writer.println("0 1 0 1");
1487            writer.println("0 0 0 0");
1488            writer.println("0 0 0 0");
1489            writer.println("0 0 0 0");
1490
1491            //  Write out the default display path.
1492            writer.print("0 ");
1493            if (isCrv) {
1494                if (k1 < 2)
1495                    k1 = 3;
1496                k2 = 2;
1497            } else {
1498                if (k1 < 2)
1499                    k1 = 3;
1500                if (k2 < 2)
1501                    k2 = 3;
1502            }
1503            writer.print(k1);   writer.print(" 0 ");
1504            writer.println(k2);
1505
1506            //  Write out the current display path.
1507            writer.print("0 ");
1508            writer.print(k1);   writer.print(" 1 ");
1509            writer.println(k1);
1510            writer.print("0 ");
1511            writer.print(k2);   writer.print(" 1 ");
1512            writer.println(k2);
1513
1514            //  Write some mystery numbers.
1515            writer.println("0 0 0 0");
1516            writer.println("1 2 1000 0");
1517
1518            //  Write out display code and color (0 = don't draw, 1 = boundary, 2 = wireframe).
1519            //  Don't understand how colors are encoded, but 4287365120 == 0,0,140 in RGB.
1520            writer.println("2 4287365120 99 99");
1521            for (int i = 0; i < 4; ++i) {
1522                writer.println("99 99 99 99");
1523            }
1524
1525        }   //  Next sidx
1526
1527    }   //  end writeSurfaces()
1528
1529    /**
1530     *
1531     * @param writer   The PrintWriter we are writing to the file with.
1532     * @param curves   The list of curve entities to write out (actually a list of
1533     *                 PointStrings).
1534     * @param units    The units for the geometry in the file.
1535     * @param surfaces The list of surfaces that the points in the curves may be subranged
1536     *                 onto.
1537     * @throws IOException If there is any problem writing to the file.
1538     */
1539    private static void writeCurves(PrintWriter writer, GeomList<PointString<GeomPoint>> curves,
1540            Unit<Length> units, GeomList surfaces) throws IOException {
1541
1542        //  Write out the CURVE data.
1543        writer.println("CURVES");
1544        int nCrvs = curves.size();
1545        writer.println(nCrvs);
1546        writer.println("99 99 99 99");
1547        writer.println("99 99 99 99");
1548
1549        //  Loop over all the curves.
1550        for (int cidx = 0; cidx < nCrvs; ++cidx) {
1551            PointString<GeomPoint> crv = curves.get(cidx).to(units);
1552            int npts = crv.size();
1553
1554            //  Write out the curve index
1555            writer.print("Curve_Number ");
1556            writer.print(cidx + 1); writer.print(" ");
1557
1558            //  Write out the number of points in the curve.
1559            writer.print(npts);     writer.print(" ");
1560
1561            //  Write out a pair of mystery numbers.
1562            writer.print("0 2271780 ");
1563
1564            //  Write out the family name for the curve.
1565            String family = (String)crv.getUserData("GTC_Family");
1566            if (isNull(family)) {
1567                family = crv.getName();
1568                if (isNull(family))
1569                    family = "Addams";
1570            }
1571            writer.println(family);
1572
1573            //  Write some mystery numbers.
1574            writer.println("99 99 99 99");
1575
1576            //  Loop over all the points in this curve.
1577            for (int pidx = 0; pidx < npts; ++pidx) {
1578                GeomPoint p = crv.get(pidx);
1579
1580                //  Write out the coordinates of the point.
1581                writer.printf("%15g %15g %15g ", p.getValue(0), p.getValue(1), p.getValue(2));
1582
1583                //  Write out the parametric coordinates (if any).
1584                if (p instanceof SubrangePoint) {
1585                    SubrangePoint sp = (SubrangePoint)p;
1586                    GeomPoint parPos = sp.getParPosition();
1587
1588                    //  Get the index of the child object.
1589                    ParametricGeometry child = sp.getChild();
1590                    int sidx = surfaces.indexOf(child);
1591                    if (sidx < 0) {
1592                        writer.println("             -1              -1 0");
1593                    } else {
1594                        writer.printf("%15g %15g ", parPos.getValue(0),
1595                                (parPos.getPhyDimension() > 1 ? parPos.getValue(1) : 0));
1596                        writer.println(sidx + 1);
1597                    }
1598
1599                } else
1600                    writer.println("             -1              -1 0");
1601            }
1602        }   //  Next cidx
1603
1604    }   //  end writeCurves()
1605
1606    /**
1607     * Write out patch data to the GTC file.
1608     *
1609     * @param writer   The PrintWriter we are writing to the file with.
1610     * @param patches  The list of patches to be written out.
1611     * @param surfaces The list of surfaces that a patch may be associated with.
1612     * @param curves   The list of curves (PointStrings) that the edges of this patch are
1613     *                 contained in.
1614     * @throws IOException
1615     */
1616    private static void writePatches(PrintWriter writer, GeomList<GeomList> patches, GeomList surfaces,
1617            GeomList<PointString<GeomPoint>> curves) throws IOException {
1618
1619        //  Write out the PATCHES data.
1620        writer.println("PATCHES");
1621        int nPatches = patches.size();
1622        writer.println(nPatches);
1623        writer.print(nPatches);
1624        writer.println(" 0 0 2 2 0 0");
1625        writer.println("99 99 99 99");
1626        writer.println("99 99 99 99");
1627
1628        //  Loop over all the patches.
1629        int d3mNum = 1;
1630        for (int pidx = 0; pidx < nPatches; ++pidx) {
1631            GeomList<GeomList> patch = patches.get(pidx);
1632
1633            //  Write out the patch number.
1634            writer.print("Patch_Number ");
1635            writer.print(pidx + 1);
1636
1637            //  Write out some mystery numbers.
1638            writer.print(" 0 16748573 ");
1639
1640            //  Write out the number of edges.
1641            GeomList<PointString> edgeLst = patch.get(1);
1642            int nedges = edgeLst.size();
1643            writer.print(nedges);   writer.print(" ");
1644
1645            //  Write out the patch type.
1646            Integer type = (Integer)patch.getUserData("GTC_PatchType");
1647            if (isNull(type))
1648                type = -1;
1649            writer.print(type);     writer.print(" ");
1650
1651            //  Write out the boundary condition type.
1652            Integer BCType = (Integer)patch.getUserData("GTC_BCType");
1653            if (isNull(BCType))
1654                BCType = 5;
1655            writer.print(BCType);   writer.print(" ");
1656
1657            //  Write out the family name.
1658            String family = (String)patch.getUserData("GTC_Family");
1659            if (isNull(family)) {
1660                family = patch.getName();
1661                if (isNull(family))
1662                    family = "Addams";
1663            }
1664            writer.println(family);
1665
1666            //  Write out the D3M number.
1667            Boolean isValid = (Boolean)patch.getUserData("GTC_ValidPatch");
1668            if (isNull(isValid))
1669                isValid = Boolean.FALSE;
1670            if (isValid) {
1671                writer.print(d3mNum);   writer.print(" ");
1672                ++d3mNum;
1673            } else
1674                writer.print("-1 ");
1675
1676            //  Write out the number of surfaces associated with this patch (0 or 1).
1677            int nsrfs = patch.get(0).size();
1678            if (nsrfs > 1)
1679                throw new IOException(MessageFormat.format(
1680                        RESOURCES.getString("sourceSizeErr"), nsrfs, pidx + 1));
1681            writer.print(nsrfs);
1682            writer.println(" 99 99");
1683
1684            if (nsrfs > 0) {
1685                //  Write out the surface information.
1686                ParametricGeometry srf = (ParametricGeometry)patch.get(0).get(0);
1687                int sidx = surfaces.indexOf(srf);
1688                if (sidx < 0)
1689                    throw new IOException(MessageFormat.format(
1690                            RESOURCES.getString("missingPatchSrf"), pidx + 1));
1691                writer.print(sidx + 1); writer.print(" ");
1692
1693                //  Write out the display path.
1694                boolean isCrv = false;
1695                int k1, k2;
1696                if (srf instanceof NurbsCurve) {
1697                    isCrv = true;
1698                    NurbsCurve ncrv = (NurbsCurve)srf;
1699                    k1 = ncrv.getControlPoints().size();
1700                    k2 = 2;
1701                } else {
1702                    NurbsSurface nsrf = (NurbsSurface)srf;
1703                    k1 = nsrf.getNumberOfRows();
1704                    k2 = nsrf.getNumberOfColumns();
1705                }
1706                if (isCrv) {
1707                    if (k1 < 2)
1708                        k1 = 3;
1709                    k2 = 2;
1710                } else {
1711                    if (k1 < 2)
1712                        k1 = 3;
1713                    if (k2 < 2)
1714                        k2 = 3;
1715                }
1716                writer.print(k1);   writer.print(" ");
1717                writer.print(k2);   writer.print(" ");
1718
1719                //  Write out two mystery numbers.
1720                writer.println("1 1000");
1721
1722                //  Write out the min/max U,V for the surface.
1723                writer.println("     0 1 0 1");
1724
1725                //  Write out the patch projection parameters.
1726                writer.println("     0.0001 0.0001 0.100000 1e-05");
1727            }
1728
1729            //  Get the side indexes and reversed edge flags.
1730            List<Integer> sideLst = (List<Integer>)patch.getUserData("GTC_EdgeSideIndexes");
1731            List<Boolean> reversedLst = (List<Boolean>)patch.getUserData("GTC_EdgeReversedFlags");
1732
1733            //  Loop over the edges.
1734            int prevSide = 0;
1735            for (int eidx = 0; eidx < nedges; ++eidx) {
1736                PointString edge = edgeLst.get(eidx);
1737                int ip1 = eidx + 1;
1738
1739                //  Write the edge index.
1740                writer.print("           ");
1741                writer.print(ip1);
1742
1743                //  Write out the edge reversed flag.
1744                String reversed = " 1 ";
1745                if (nonNull(reversedLst) && reversedLst.get(eidx))
1746                    reversed = " -1 ";
1747                writer.print(reversed);
1748
1749                //  Write out the edge curve number
1750                int cidx = curves.indexOf(edge);
1751                if (cidx < 0)
1752                    throw new IOException(MessageFormat.format(
1753                            RESOURCES.getString("missingPatchEdge"), ip1, pidx + 1));
1754                writer.print(cidx + 1);
1755
1756                //  Write out the side indicator.
1757                int side = ip1;
1758                if (nonNull(sideLst))
1759                    side = sideLst.get(eidx);
1760                if (side != prevSide || eidx == 0)
1761                    writer.print(" 1 ");
1762                else
1763                    writer.print(" -1 ");
1764                prevSide = side;
1765                writer.println("99");
1766            }
1767        }   //  Next pidx
1768
1769    }   //  end writePatches()
1770
1771}