001/*
002 *   VECCGeomReader  -- A class that can read and write a VECC MK5 formatted geometry file.
003 *
004 *   Copyright (C) 2009-2016, 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 static geomss.geom.reader.AbstractGeomReader.RESOURCES;
026import java.io.*;
027import java.nio.charset.StandardCharsets;
028import java.text.MessageFormat;
029import java.util.Locale;
030import static java.util.Objects.isNull;
031import static java.util.Objects.nonNull;
032import static java.util.Objects.requireNonNull;
033import javax.measure.quantity.Length;
034import javax.measure.unit.NonSI;
035import javax.measure.unit.SI;
036import javax.measure.unit.Unit;
037import javolution.context.StackContext;
038import javolution.text.Text;
039import javolution.text.TypeFormat;
040import javolution.util.FastTable;
041
042/**
043 * A {@link GeomReader} for reading and writing vehicle geometry from/to a VECC (Viscous
044 * Effects on Complex Configurations) MK5 formatted geometry file. This class can also
045 * read the related VECC GEO file.
046 *
047 * <p> Modified by: Joseph A. Huwaldt </p>
048 *
049 * @author Joseph A. Huwaldt, Date: April 10, 2009
050 * @version September 9, 2016
051 */
052public class VECCGeomReader extends AbstractGeomReader {
053
054    //  Debug output flag.
055    private static final boolean DEBUG = false;
056
057    //  A brief description of the data read by this reaader.
058    private static final String DESCRIPTION = RESOURCES.getString("veccDescription");
059
060    //  The preferred file extension for files of this reader's type.
061    public static final String EXTENSION = "mk5";
062
063    //  Units strings in Text format.
064    private static final Text IN_TEXT = Text.intern("in");
065    private static final Text FT_TEXT = Text.intern("ft");
066    private static final Text CM_TEXT = Text.intern("cm");
067    private static final Text MM_TEXT = Text.intern("mm");
068    private static final Text ME_TEXT = Text.intern("me");
069
070    private boolean _isUnitAware = true;
071
072    /**
073     * Returns a string representation of the object. This will return a brief description
074     * of the format read by this reader.
075     *
076     * @return A brief description of the format read by this reader.
077     */
078    @Override
079    public String toString() {
080        return DESCRIPTION;
081    }
082
083    /**
084     * Returns the preferred file extension (not including the ".") for files of this
085     * GeomReader's type.
086     *
087     * @return The preferred file extension for files of this readers type.
088     */
089    @Override
090    public String getExtension() {
091        return EXTENSION;
092    }
093
094    /**
095     * Method that determines if this reader can read paneled geometry from the specified
096     * input file.
097     *
098     * @param inputFile The input file containing the geometry to be read in.
099     * @return GeomReader.NO if the file format is not recognized by this reader.
100     *         GeomReader.YES if the file has the extension ".geo" or ".mk5".
101     *         GeomReader.MAYBE if the file has the extension ".lib".
102     * @throws java.io.IOException If there is a problem reading from the specified
103     * file.
104     */
105    @Override
106    public int canReadData(File inputFile) throws IOException {
107
108        int response = NO;
109        String name = inputFile.getName();
110        name = name.toLowerCase().trim();
111        if (name.endsWith(".geo")) {
112            response = YES;
113            _isUnitAware = false;
114        } else if (name.endsWith(".mk5")) {
115            response = YES;
116            _isUnitAware = true;
117        } else if (name.endsWith(".lib")) {
118            response = MAYBE;
119            _isUnitAware = false;
120        }
121
122        return response;
123    }
124
125    /**
126     * Returns true. This class can write PointVehicle data to a VECC Geometry file.
127     *
128     * @return this method always returns true.
129     */
130    @Override
131    public boolean canWriteData() {
132        return true;
133    }
134
135    /**
136     * Reads in a VECC MK5 or GEO geometry file from the specified input file and returns
137     * a {@link PointVehicle} object that contains the geometry from the file.
138     *
139     * @param inputFile The input file containing the geometry to be read in. May not be
140     *                  null.
141     * @return A {@link PointVehicle} object containing the geometry read in from the
142     *         file. If the file has no geometry in it, then this list will have no
143     *         components in it (will have a size() of zero).
144     * @throws IOException If there is a problem reading the specified file.
145     */
146    @Override
147    public PointVehicle read(File inputFile) throws IOException {
148        requireNonNull(inputFile);
149        _warnings.clear();
150
151        // Create an empty vehicle with the provided name as the vehicle name.
152        PointVehicle vehicle = PointVehicle.newInstance(inputFile.getName());
153
154        //  VECC/GEO files are required to be in ASCII with U.S. style number formatting.
155        //  Get the default locale and save it.  Then change the default locale to US.
156        Locale defLocale = Locale.getDefault();
157        Locale.setDefault(Locale.US);
158
159        // Create a reader to access the ASCII file.
160        try (LineNumberReader reader = new LineNumberReader(new FileReader(inputFile))) {
161            reader.mark(1024);
162
163            // Is this a MK5 file or a plain GEO file?
164            String aLine = readLine(reader);
165            if (aLine.contains("new mk IV"))
166                //  We have a mk5 file.
167                read_habp(reader, vehicle);
168            
169            else {
170                //  We just have a plain GEO file, so read in the panels into a single component.
171
172                //  Read in the panels (and any sections that the panels may contain).
173                reader.reset();
174                PointComponent panelList = PointComponent.newInstance();
175                read_panels(reader, panelList);
176
177                if (panelList.size() > 0) {
178                    //  Create the component that will go in the vehicle.
179                    PointComponent component = PointComponent.newInstance();
180
181                    //  Loop over all the panels in the panel list.
182                    for (PointArray panel : panelList) {
183                        //  Add the panel to the component.
184                        component.add(panel);
185
186                        //  Add any sections temporary stored in the panel's user data.
187                        extractSections(panel, component);
188                    }
189
190                    //  Add the component to the vehicle.
191                    vehicle.add(component);
192                }
193            }
194
195        } finally {
196            //  Restore the default locale.
197            Locale.setDefault(defLocale);
198        }
199
200        return vehicle;
201    }
202
203    /**
204     * Writes out a VECC MK5 geometry file for the geometry contained in the supplied
205     * {@link PointVehicle} object.
206     *
207     * @param outputFile The output File to which the geometry is to be written. May not
208     *                   be null.
209     * @param geometry   The {@link PointVehicle} object to be written out. May not be
210     *                   null.
211     * @throws IOException If there is a problem writing to the specified file.
212     */
213    @Override
214    public void write(File outputFile, GeometryList geometry) throws IOException {
215        requireNonNull(outputFile);
216        _warnings.clear();
217
218        //  Convert the input generic geometry list to a PointVehicle.
219        PointVehicle vehicle = geomList2PointVehicle(requireNonNull(geometry));
220        if (!vehicle.containsGeometry()) {
221            _warnings.add(RESOURCES.getString("noGeometryWarning"));
222            return;
223        }
224
225        //  Determine the unit code for the input geometry (and user's locale).
226        int unitCode = getUnitCode(vehicle);
227
228        //  VECC/GEO files are required to be in ASCII with U.S. style number formatting.
229        //  Get the default locale and save it.  Then change the default locale to US.
230        Locale defLocale = Locale.getDefault();
231        Locale.setDefault(Locale.US);
232
233        // Get a reference to the output stream writer.
234        StackContext.enter();
235        try (PrintWriter writer = new PrintWriter(outputFile, StandardCharsets.US_ASCII.name())) {
236
237            //  Write the executive control card.
238            writer.println("10                                                    new mk IV 0 4");
239
240            //  Write the system control card.
241            writer.println("10000000000000000000");
242
243            //  Write the geometry control card.
244            writer.print(" 800000");
245            writer.print(padSpaces(String.valueOf(vehicle.size()), 2));
246            String name = vehicle.getName();
247            if (isNull(name) || name.equals(""))
248                name = RESOURCES.getString("veccDefaultVehName");
249            writer.print(padSpaces(name, 60));
250            writer.println(unitCode);
251
252            //  Combine all the arrays in all the components in this vehicle into one list of panels
253            //  for writing out.
254            PointComponent panelList = PointComponent.newInstance();
255            for (PointComponent comp : vehicle) {
256                if (comp.containsGeometry())
257                    panelList.addAll(comp);
258            }
259
260            //  Write out the geometry.
261            writeGeometry(writer, panelList);
262
263            //  Write the component names.
264            writeComponentNames(writer, vehicle, panelList);
265
266            //  Write out the other misc. stuff.
267            writer.println("  0 other run(s) follow");
268            writer.println("  0 quadstream inputs");
269            writer.println("  1 trim inputs");
270            writer.println("  0");
271            writer.println("  0");
272            writer.println("  1 Cg locations follow");
273            writer.println("  -100.0000     0.0000     0.0000");
274
275        } finally {
276            StackContext.exit();
277
278            //  Restore the default locale.
279            Locale.setDefault(defLocale);
280        }
281
282    }
283
284    /**
285     * Returns true if this reader is unit aware and false if it is not. VECC MK5 files
286     * are unit aware and this method returns true by default. However, the related GEO
287     * files do not encode the units that are being used (are not unit aware). You must
288     * call <code>setFileUnits</code> to set the units being used before reading from a
289     * file of GEO format. If "canReadData" is called with either a GEO or MK5 file before
290     * calling this method, then this method will return the correct value.
291     *
292     * @return true if this reader is unit aware.
293     * @see #setFileUnits(javax.measure.unit.Unit)
294     * @see #canReadData(java.io.File)
295     */
296    @Override
297    public boolean isUnitAware() {
298        return _isUnitAware;
299    }
300
301    /**
302     * Read in geometry stored in a MK5 file (which has some header info and a lot of
303     * other stuff we are ignoring.
304     */
305    private void read_habp(LineNumberReader in, PointVehicle vehicle) throws IOException {
306        try {
307            //  Read system control card
308            String aLine = readLine(in);
309            Text text = Text.valueOf(aLine).trim();
310
311            //  Read in the geometry from the mk5 file.
312            for (int i = 0; i < 20; ++i) {
313                int ipg = TypeFormat.parseInt(text.subtext(i, i + 1));
314                if (ipg == 1) {
315                    read_habp_geom(in, vehicle);
316                    break;
317                }
318            }
319            if (vehicle.size() == 0)
320                return;
321
322            //  Extract the temporary list of panels that have been read in.
323            PointComponent panelList = vehicle.get(0);
324
325            //  Find the list of components in the mk5 file.
326            aLine = readLine(in);
327            while (!aLine.startsWith("component names")) {
328                aLine = in.readLine();
329            }
330
331            //  Parse out the number of components.
332            int nc = TypeFormat.parseInt(aLine.substring(15, 19).trim());
333            if (DEBUG)
334                System.out.println("Number of Components: nc = " + nc + ", numPanels = " + panelList.size());
335
336            if (nc > 0)
337                vehicle.remove(0);  //  Remove the temporary list from the vehicle.
338
339            //  Loop over each of the components.
340            for (int i = 0; i < nc; ++i) {
341                //  Read in the next line.
342                aLine = readLine(in);
343                text = Text.valueOf(aLine);
344
345                //  Parse out the number of panels.
346                int np = TypeFormat.parseInt(text.subtext(0, 2).trim());
347
348                //  Parse out the name of the component.
349                Text title = text.subtext(2).trim();
350
351                //  Create a new component.
352                PointComponent comp = PointComponent.newInstance(title.toString());
353
354                if (DEBUG)
355                    System.out.println("Component #" + (i + 1) + ":  title = " + title + ", np = " + np);
356
357                //  Add each panel from the panelList as needed.
358                aLine = readLine(in);
359                text = Text.valueOf(aLine);
360                for (int j = 0; j < np; ++j) {
361                    int pos = j * 2;
362                    Text numText = text.subtext(pos, pos + 2).trim();
363                    int ipanel = TypeFormat.parseInt(numText);
364
365                    //  Get the panel indicated.
366                    PointArray panel = panelList.get(ipanel - 1);
367
368                    //  Add the panel to the component.
369                    comp.add(panel);
370
371                    if (DEBUG)
372                        System.out.println("    ipanel = " + ipanel + ", name = " + panel.getName());
373
374                    //  Does the panel have any sections?  If so, store them
375                    //  in the component as panels.
376                    extractSections(panel, comp);
377                }
378
379                //  Add the component to the vehicle.
380                if (comp.size() > 0)
381                    vehicle.add(comp);
382            }
383
384        } catch (NumberFormatException e) {
385            e.printStackTrace();
386            throw new IOException(MessageFormat.format(
387                    RESOURCES.getString("parseErrMsg"), DESCRIPTION, in.getLineNumber()));
388        }
389    }
390
391    /**
392     * Extracts any "sections" stored in the user data of the provided panel and adds them
393     * to the specified component as panels.
394     */
395    private void extractSections(PointArray panel, PointComponent component) {
396        //  Does the panel have any sections?
397        FastTable<PointArray> sectionList = (FastTable<PointArray>)panel.getUserData("Sections");
398        if (nonNull(sectionList)) {
399            //  Add all the sections to the component as panels.
400            for (PointArray section : sectionList) {
401                if (DEBUG) {
402                    System.out.println("    section name = " + section.getName());
403                }
404                component.add(section);
405            }
406
407            //  Remove the sections from the user data of the main panel.
408            panel.removeUserData("Sections");
409            FastTable.recycle(sectionList);
410        }
411    }
412
413    /**
414     * Read in geometry stored in a MK5 file (which has some header info and a lot of
415     * other stuff we are ignoring.
416     */
417    private void read_habp_geom(LineNumberReader in, PointVehicle vehicle)
418            throws IOException, NumberFormatException {
419        //  Read geometry control card
420        String aLine = readLine(in);
421        Text text = Text.valueOf(aLine);
422
423        //  Parse out the configuration name.
424        //Text name = text.subtext(8, 8+61);
425        //vehicle.setName(name);
426        
427        //  Parse out the units.
428        int unitCode = TypeFormat.parseInt(text.subtext(9 + 60).trim());
429        switch (unitCode) {
430            case 0:
431                setFileUnits(NonSI.INCH);
432                break;
433            case 1:
434                setFileUnits(NonSI.FOOT);
435                break;
436            case 2:
437                setFileUnits(SI.MILLIMETER);
438                break;
439            case 3:
440                setFileUnits(SI.CENTIMETER);
441                break;
442            case 4:
443                setFileUnits(SI.METER);
444                break;
445        }
446
447        //  Now read in the panels into a temporary component which is a list of all the panels.
448        PointComponent component = PointComponent.newInstance();
449        read_panels(in, component);
450
451        //  Store the temporary component in the vehicle.
452        if (component.size() > 0)
453            vehicle.add(component);
454    }
455
456    /**
457     * Read in the panels stored in a GEO file (which may or may not have a header
458     * identifying the panel name, etc).
459     *
460     * @param in        The reader used to read text from the file.
461     * @param component The component to add this panel to.
462     */
463    private void read_panels(LineNumberReader in, PointComponent component) throws IOException {
464        int last = 0;
465
466        //  Loop over all the panels
467        while (last == 0) {
468            int isymm = 0;
469            int itype = 3;
470            Text name = Text.EMPTY;
471
472            //  Read in the 1st line.
473            in.mark(1024);
474            String aLine = readLine(in);
475            Text text = Text.valueOf(aLine);
476
477            //  See if the first record is a panel header by attempting to 
478            //  parse grid points from it.
479            try {
480                TypeFormat.parseDouble(text.subtext(0, 10).trim());
481                TypeFormat.parseDouble(text.subtext(10, 20).trim());
482                TypeFormat.parseDouble(text.subtext(20, 30).trim());
483                TypeFormat.parseInt(text.subtext(30, 31));
484                in.reset();
485                //  If no error, there are not any header lines
486
487            } catch (NumberFormatException e) {
488                // Parse the panel header using defaults for any errors.
489
490                try {
491                    //  Read in the last flag.  If it is != 0, this is the last panel.
492                    last = TypeFormat.parseInt(text.subtext(4, 5));
493
494                    //  Read in the symmetry flag.
495                    isymm = TypeFormat.parseInt(text.subtext(16, 17));
496
497                    //  Read in the panel type.
498                    itype = TypeFormat.parseInt(text.subtext(26, 27));
499
500                    //  Read in the name.
501                    name = text.subtext(29).trim();
502                } catch (NumberFormatException err) { /* ignore */ }
503
504                if (name.equals(Text.EMPTY))
505                    name = text.subtext(0, 5);
506
507                //  Read in the 2nd part of the header.
508                aLine = readLine(in);
509                parse_units(Text.valueOf(aLine));
510            }
511
512            if (DEBUG) {
513                System.out.println("name = " + name + ", last = " + last + ", isymm = "
514                        + isymm + ", itype = " + itype);
515                System.out.println("units = " + getFileUnits());
516            }
517
518            //  Create a new PointArray object for this panel.
519            PointArray panel = PointArray.newInstance(name.toString());
520            panel.putUserData("Symmetry", (isymm == 0 ? 1 : 0));
521            panel.putUserData("HABPType", itype);
522
523            //  Read in all the sections.
524            read_sections(in, panel);
525
526            //  Add the panel to the component.
527            component.add(panel);
528
529        }   //  Next panel.
530    }
531
532    /**
533     * Parses the units from the 2nd line of the GEO header (if there are any).
534     */
535    private void parse_units(Text aLine) {
536        Text text = aLine.subtext(2, 4).toLowerCase();
537
538        if (text.equals(IN_TEXT))
539            setFileUnits(NonSI.INCH);
540        else if (text.equals(FT_TEXT))
541            setFileUnits(NonSI.FOOT);
542        else if (text.equals(CM_TEXT))
543            setFileUnits(SI.CENTIMETER);
544        else if (text.equals(MM_TEXT))
545            setFileUnits(SI.MILLIMETER);
546        else if (text.equals(ME_TEXT))
547            setFileUnits(SI.METER);
548    }
549
550    /**
551     * Read in all the single sections for a single panel from a GEO file.
552     *
553     * @param in    The reader used to read text from the file.
554     * @param panel The panel to add this section to.
555     *
556     */
557    private void read_sections(LineNumberReader in, PointArray panel) throws IOException {
558        PointArray mainPanel = panel;
559        int sectionCount = 1;
560
561        //  Create a new string.
562        PointString section = PointString.newInstance();
563
564        try {
565            boolean first = true;
566            boolean moreSections = true;
567            readLoop:
568            while (moreSections) {
569                //  Read in a line.
570                String aLine = readLine(in);
571                Text text = Text.valueOf(aLine);
572
573                //  Try to read two points from the record.
574                for (int jj = 0; jj < 2; ++jj) {
575                    int icol = jj * 31;
576                    Text pointText = text.subtext(icol, icol + 31);
577                    if (pointText.trim().equals(Text.EMPTY))
578                        continue readLoop;
579
580                    //  Parse out the coordinate values.
581                    double xValue = TypeFormat.parseDouble(pointText.subtext(0, 10).trim());
582                    double yValue = TypeFormat.parseDouble(pointText.subtext(10, 20).trim());
583                    double zValue = TypeFormat.parseDouble(pointText.subtext(20, 30).trim());
584                    int iflag = TypeFormat.parseInt(pointText.subtext(30, 31));
585
586                    //  Create a point.
587                    Point point = Point.valueOf(xValue, yValue, zValue, getFileUnits());
588
589                    if (iflag == 1 || (!first && iflag == 2)) {
590                        //  We are done with this section, start a new one.
591                        panel.add(section);
592                        section = PointString.newInstance();
593                    }
594
595                    if (!first && iflag == 2) {
596                        //  Create a new panel, based on the last panel, for the new sections.
597                        //  Store them in the main panel's user data for now.
598                        PointArray newPanel = PointArray.newInstance(
599                                mainPanel.getName() + Integer.toString(sectionCount++));
600                        Object ud = mainPanel.getUserData("Symmetry");
601                        if (nonNull(ud))
602                            newPanel.putUserData("Symmetry", ud);
603                        ud = mainPanel.getUserData("HABPType");
604                        if (nonNull(ud))
605                            newPanel.putUserData("HABPType", ud);
606                        FastTable<PointArray> sectionList
607                                = (FastTable<PointArray>)mainPanel.getUserData("Sections");
608                        if (isNull(sectionList)) {
609                            sectionList = FastTable.newInstance();
610                            mainPanel.putUserData("Sections", sectionList);
611                        }
612                        sectionList.add(newPanel);
613                        panel = newPanel;
614                    }
615
616                    //  Add this point to the section.
617                    section.add(point);
618
619                    if (iflag == 3) {
620                        //  If this is the end of the panel we are finished.
621                        panel.add(section);
622                        moreSections = false;
623                    }
624
625                    first = false;
626                }
627
628            }
629        } catch (NumberFormatException e) {
630            e.printStackTrace();
631            throw new IOException(MessageFormat.format(
632                    RESOURCES.getString("parseErrMsg"), DESCRIPTION, in.getLineNumber()));
633        }
634    }
635
636    /**
637     * Add spaces to the right of a string of text until that text equals the specified
638     * length.
639     *
640     */
641    private String padSpaces(String input, int length) {
642        int inputLength = input.length();
643        if (inputLength < length) {
644            StringBuilder buf = new StringBuilder(input);
645            for (int i = inputLength; i < length; ++i) {
646                buf.append(" ");
647            }
648            input = buf.toString();
649        }
650        return input;
651    }
652
653    /**
654     * Returns the unit code for the input geometry. This method gets the unit for the
655     * minimum bounds of the geometry, then makes ure it is a unit that the VECC format
656     * recognizes (and changes it to one that it does recognize if necessary). Then the
657     * unit is stored using "setFileUnits". All input geometry must be converted to this
658     * unit for storage in the VECC file.
659     *
660     */
661    private int getUnitCode(PointVehicle geometry) {
662
663        GeomPoint point = geometry.getBoundsMin();
664        Unit<Length> unit = point.getUnit();
665
666        int unitCode;
667        if (unit.equals(NonSI.INCH))
668            unitCode = 0;
669        else if (unit.equals(NonSI.FOOT))
670            unitCode = 1;
671        else if (unit.equals(SI.MILLIMETER))
672            unitCode = 2;
673        else if (unit.equals(SI.CENTIMETER))
674            unitCode = 3;
675        else if (unit.equals(SI.METER))
676            unitCode = 4;
677        else {
678            //  Convert to a locale specific option.
679            if (java.util.Locale.getDefault().equals(java.util.Locale.US)) {
680                unit = NonSI.INCH;
681                unitCode = 0;
682
683            } else {
684                unit = SI.METER;
685                unitCode = 4;
686            }
687        }
688
689        setFileUnits(unit);
690
691        return unitCode;
692    }
693
694    /**
695     * Writes out the HABP geometry to the file.
696     *
697     */
698    private void writeGeometry(PrintWriter out, PointComponent panelList) throws IOException {
699
700        //  Loop over all the panels.
701        int size = panelList.size();
702        for (int i = 0; i < size; ++i) {
703            PointArray panel = panelList.get(i);
704            if (!panel.containsGeometry())
705                continue;       //  Skip any empty panels.
706
707            //  Write out this panel.
708            write_panel(out, panel, (i == size - 1), i + 1);
709        }
710
711    }
712
713    /**
714     * Writes out a single panel.
715     */
716    private void write_panel(PrintWriter out, PointArray<? extends GeomPoint> panel, boolean last, int count) throws IOException {
717
718        //  Write the panel header
719        
720        //  Write out the name.
721        String fullName = panel.getName();
722        if (isNull(fullName)) {
723            //  No name given, so use a panel count instead (must start with a letter though).
724            fullName = "P" + String.valueOf(count);
725            if (fullName.length() > 4)
726                fullName = "P" + fullName.substring(fullName.length() - 3);
727        }
728        String name = fullName;
729        if (name.length() > 4)
730            name = name.substring(0, 4);
731        else
732            name = padSpaces(name, 4);
733        out.print(name);
734
735        //  Indicate if this is the last panel or not.
736        out.print((last ? 1 : 0));
737        out.print("01         ");
738
739        //  Write out the symmetry flag (0 == symmetric, 1 = non-symmetric).
740        Integer isymm = (Integer)panel.getUserData("Symmetry");
741        if (isNull(isymm))
742            out.print("0");
743        else
744            out.print((isymm == 0 ? 1 : 0));
745        out.print("0        ");
746
747        //  Write out the panel type.
748        Integer itype = (Integer)panel.getUserData("HABPType");
749        if (isNull(itype))
750            out.print("3");
751        else
752            out.print(itype.intValue());
753        out.print("  ");
754
755        //  Write out the name again (but longer this time).
756        if (fullName.length() > 16)
757            fullName = fullName.substring(0, 16);
758        else
759            fullName = padSpaces(fullName, 16);
760        out.println(fullName);
761
762        out.println(" 1 000");
763
764        //  Write out the array of data.
765        int icol = 1;
766        int iflag = 2;  //  flag indicating we are at the start of a panel.
767        int nCuts = panel.size() - 1;
768        int nPoints = panel.get(0).size() - 1;
769
770        //  Loop over all the cuts (strings) in this panel.
771        for (int l = 0; l <= nCuts; ++l) {
772            PointString<?> cut = panel.get(l);
773
774            //  Loop over all the points in this cut/string.
775            for (int m = 0; m <= nPoints; ++m) {
776                if (m == nPoints && l == nCuts)
777                    iflag = 3;  //  flag indicating we are at the end of the panel.
778
779                //  Get the point (in the right units).
780                GeomPoint point = cut.get(m).to(getFileUnits());
781                double x = point.getValue(0);
782                double y = point.getValue(1);
783                double z = point.getValue(2);
784
785                //  Write it out.
786                out.printf("%10.4f", x);
787                out.printf("%10.4f", y);
788                out.printf("%10.4f", z);
789
790                //  Write the iflag.
791                out.print(iflag);
792
793                //  Deal with the columns.
794                if (icol != 1) {
795                    out.println("         3");
796                    icol = 0;
797                }
798                ++icol;
799
800                //  Change the iflag to indicate we are in the middle of a cut.
801                iflag = 0;
802            }
803            //  Change the iflag to indicate we are starting a new cut.
804            iflag = 1;
805        }
806
807        //  If there were an odd number of points, output the last line ending.
808        if (icol != 1)
809            out.println("                                        3");
810
811    }
812
813    /**
814     * Write out the names of all the components.
815     */
816    private void writeComponentNames(PrintWriter out, PointVehicle geometry, PointComponent panelList) throws IOException {
817
818        //  Collect only non-empty components.
819        PointVehicle geomV = PointVehicle.newInstance(geometry.getName());
820        for (PointComponent comp : geometry) {
821            if (comp.containsGeometry())
822                geomV.add(comp);
823        }
824
825        //  Output header.
826        out.printf("component names %3d", geomV.size());
827        out.println();
828
829        for (PointComponent comp : geomV) {
830
831            //  Get a 16 character title.
832            String title = comp.getName();
833            if (isNull(title))
834                title = String.valueOf(comp.getID());
835            if (title.length() > 16)
836                title = title.substring(0, 16);
837            else
838                title = padSpaces(title, 16);
839
840            //  Write out the number of panels in this component.
841            int size = comp.size();
842            out.printf("%2d", size);
843
844            //  Write out the name of the panel.
845            out.println(title);
846
847            //  Write out the indices to each panel in this component.
848            for (PointArray array : comp) {
849                if (array.containsGeometry()) {
850                    int index = panelList.indexOf(array);
851                    out.printf("%2d", index + 1);
852                }
853            }
854            out.println();
855        }
856
857    }
858}