001/**
002 * CARDGeomReader -- A class that can read and write an APAS II CARD formatted geometry
003 * file.
004 *
005 * Copyright (C) 2009-2016, Joseph A. Huwaldt. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or modify it under the terms
008 * of the GNU Lesser General Public License as published by the Free Software Foundation;
009 * either version 2.1 of the License, or (at your option) any later version.
010 *
011 * This library is distributed in the hope that it will be useful, but WITHOUT ANY
012 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
013 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
014 *
015 * You should have received a copy of the GNU Lesser General Public License along with
016 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place -
017 * Suite 330, Boston, MA 02111-1307, USA. Or visit: http://www.gnu.org/licenses/lgpl.html
018 */
019package geomss.geom.reader;
020
021import geomss.geom.*;
022import jahuwaldt.js.param.EulerAngles;
023import jahuwaldt.js.param.Parameter;
024import java.io.*;
025import java.nio.charset.StandardCharsets;
026import java.text.MessageFormat;
027import java.util.Locale;
028import static java.util.Objects.isNull;
029import static java.util.Objects.requireNonNull;
030import javax.measure.quantity.Angle;
031import javax.measure.quantity.Length;
032import javax.measure.unit.NonSI;
033import javax.measure.unit.SI;
034import javax.measure.unit.Unit;
035import javolution.context.StackContext;
036import javolution.text.Text;
037import javolution.text.TypeFormat;
038import javolution.util.FastTable;
039import org.jscience.mathematics.number.Float64;
040import org.jscience.mathematics.number.Integer64;
041
042/**
043 * A {@link GeomReader} for reading and writing vehicle geometry from/to an APAS II
044 * (Airplane Preliminary Analysis System) CARD formatted geometry file.
045 *
046 * <p> Modified by: Joseph A. Huwaldt </p>
047 *
048 * @author Joseph A. Huwaldt, Date: May 27, 2009
049 * @version September 9, 2016
050 */
051public class CARDGeomReader extends AbstractGeomReader {
052
053    //  Debug output flag.
054    private static final boolean DEBUG = false;
055
056    //  A brief description of the data read by this reaader.
057    private static final String DESCRIPTION = RESOURCES.getString("cardDescription");
058
059    //  The preferred file extension for files of this reader's type.
060    public static final String EXTENSION = "card";
061
062    /**
063     * Returns a string representation of the object. This will return a brief description
064     * of the format read by this reader.
065     *
066     * @return A brief description of the format read by this reader.
067     */
068    @Override
069    public String toString() {
070        return DESCRIPTION;
071    }
072
073    /**
074     * Returns the preferred file extension (not including the ".") for files of this
075     * GeomReader's type.
076     *
077     * @return The preferred file extension for files of this readers type.
078     */
079    @Override
080    public String getExtension() {
081        return EXTENSION;
082    }
083
084    /**
085     * Method that determines if this reader can read geometry from the specified input
086     * file.
087     *
088     * @param inputFile The input file containing the geometry to be read in. May not be
089     *                  null.
090     * @return GeomReader.NO if the file format is not recognized by this reader.
091     *         GeomReader.YES if the file has the extension ".geo" or ".mk5".
092     *         GeomReader.MAYBE if the file has the extension ".lib".
093     * @throws java.io.IOException if there is a problem reading from the specified file
094     */
095    @Override
096    public int canReadData(File inputFile) throws IOException {
097
098        int response = NO;
099        String name = inputFile.getName();
100        name = name.toLowerCase().trim();
101        if (name.endsWith(".card")) {
102            response = MAYBE;
103        }
104
105        return response;
106    }
107
108    /**
109     * Returns true. This class can write point geometry to an APAS CARD formatted file.
110     *
111     * @return true
112     */
113    @Override
114    public boolean canWriteData() {
115        return true;
116    }
117
118    /**
119     * Reads in an APAS CARD formatted geometry file from the specified input file and
120     * returns a {@link PointVehicle} object that contains the geometry from the file.
121     *
122     * @param inputFile The input file containing the geometry to be read in. May not be
123     *                  null.
124     * @return A {@link PointVehicle} object containing the geometry read in from the
125     *         file. If the file has no geometry in it, then this list will have no
126     *         components in it (will have a size() of zero).
127     * @throws IOException If there is a problem reading the specified file.
128     */
129    @Override
130    public PointVehicle read(File inputFile) throws IOException {
131        requireNonNull(inputFile);
132        _warnings.clear();
133
134        // Create an empty vehicle with the provided name as the vehicle name.
135        PointVehicle vehicle = PointVehicle.newInstance();
136
137        //  CARD files are required to be in ASCII with U.S. style number formatting.
138        //  Get the default locale and save it.  Then change the default locale to US.
139        Locale defLocale = Locale.getDefault();
140        Locale.setDefault(Locale.US);
141
142        // Create a reader to access the ASCII file.
143        try (LineNumberReader reader = new LineNumberReader(new FileReader(inputFile))) {
144
145            // Get the number of records and the units.
146            String aLine = readLine(reader);
147            Text text = Text.valueOf(aLine);
148            //int nRecords = TypeFormat.parseInt(text.subtext(0, 5).trim());
149            int unitCode = TypeFormat.parseInt(text.subtext(5).trim());
150            selectUnits(unitCode);
151
152            //  Get the title of the configuration.
153            aLine = readLine(reader);
154            vehicle.setName(aLine.trim());
155
156            //  Get a list of component numbers from the next two lines.
157            aLine = readLine(reader);
158            String aLine2 = readLine(reader);
159            text = Text.valueOf(aLine).concat(Text.valueOf(aLine2));
160            int length = text.length();
161            if (length <= 8)
162                throw new IOException(RESOURCES.getString("cardMissingCompNum"));
163
164            FastTable<Integer64> compNumbers = FastTable.newInstance();
165            int pos = 0;
166            int posp8 = 8;
167            while (pos < length) {
168                Text token = text.subtext(pos, posp8).trim();
169                int num = (int)TypeFormat.parseDouble(token);
170                compNumbers.add(Integer64.valueOf(num));
171                pos = posp8;
172                posp8 = pos + 8;
173                if (posp8 > length)
174                    posp8 = length;
175            }
176
177            int numComponents = compNumbers.size();
178            if (numComponents == 0)
179                throw new IOException(RESOURCES.getString("cardCompNumParseErr"));
180
181            //  Read in each component in turn.
182            for (int i = 0; i < numComponents; ++i) {
183                PointComponent comp = readComponent(reader);
184                vehicle.add(comp);
185            }
186
187            //  Cleanup before leaving.
188            FastTable.recycle(compNumbers);
189
190        } finally {
191            //  Restore the default locale.
192            Locale.setDefault(defLocale);
193        }
194
195        return vehicle;
196    }
197
198    /**
199     * Writes out an APAS CARD geometry file for the geometry contained in the supplied
200     * {@link PointVehicle} object.
201     *
202     * @param outputFile The output File to which the geometry is to be written. May not
203     *                   be null.
204     * @param geometry   The {@link PointVehicle} object to be written out. May not be
205     *                   null.
206     * @throws IOException If there is a problem writing to the specified file.
207     */
208    @Override
209    public void write(File outputFile, GeometryList geometry) throws IOException {
210        requireNonNull(outputFile);
211        _warnings.clear();
212        if (!geometry.containsGeometry()) {
213            _warnings.add(RESOURCES.getString("noGeometryWarning"));
214            return;
215        }
216
217        //  Convert the input generic geometry list to a PointVehicle.
218        PointVehicle vehicle = geomList2PointVehicle(geometry);
219
220        if (!vehicle.containsGeometry())
221            return;
222
223        //  Determine the unit code for the input geometry (and user's locale).
224        int unitCode = getUnitCode(vehicle);
225
226        //  CARD files are required to be in ASCII with U.S. style number formatting.
227        //  Get the default locale and save it.  Then change the default locale to US.
228        Locale defLocale = Locale.getDefault();
229        Locale.setDefault(Locale.US);
230
231        StackContext.enter();
232        // Get a reference to the output stream writer.
233        try (PrintWriter writer = new PrintWriter(outputFile, StandardCharsets.US_ASCII.name())) {
234
235            //  Write out the number of records and the units.
236            int size = vehicle.size();
237            writer.printf("%5d", size + 1);
238            writer.printf("%5d", unitCode);
239            writer.println();
240
241            //  Write out the name of the configuration (vehicle name).
242            String name = vehicle.getName();
243            if (isNull(name))
244                name = "vehicle";
245            writer.println(name);
246
247            //  Write the component numbers out on the next two lines.
248            int col = 0;
249            for (int i = 0; i < size; ++i) {
250                PointComponent comp = vehicle.get(i);
251                Integer compNumber = (Integer)comp.getUserData("APASComponentNumber");
252                if (isNull(compNumber)) {
253                    compNumber = 10 + i;
254                    comp.putUserData("APASComponentNumber", compNumber);
255                }
256
257                writer.printf("%5d.00", compNumber);
258                ++col;
259
260                if (col > 9 || i + 1 == size) {
261                    writer.println();
262                    col = 0;
263                }
264            }
265
266            //  Write out each component to the file.
267            for (PointComponent comp : vehicle) {
268                writeComponent(writer, comp);
269            }
270
271        } finally {
272            StackContext.exit();
273
274            //  Restore the default locale.
275            Locale.setDefault(defLocale);
276        }
277
278    }
279
280    /**
281     * Returns true if this reader is unit aware and false if it is not. APAS CARD files
282     * are unit aware and this method returns true.
283     *
284     * @return this implementation always returns true.
285     */
286    @Override
287    public boolean isUnitAware() {
288        return true;
289    }
290
291    /**
292     * Selects the appropriate units for the unit code provided.
293     */
294    private void selectUnits(int unitCode) throws IOException {
295        switch (unitCode) {
296            case 1:
297                setFileUnits(SI.METER);
298                break;
299            case 2:
300                setFileUnits(NonSI.INCH);
301                break;
302            case 3:
303                setFileUnits(SI.CENTIMETER);
304                break;
305            default:
306                throw new IOException(
307                        MessageFormat.format(RESOURCES.getString("cardbadTypeCode"), unitCode));
308        }
309    }
310
311    /**
312     * Method that reads in a single component from the file.
313     */
314    private PointComponent readComponent(LineNumberReader in) throws IOException {
315        Unit<Length> units = getFileUnits();
316
317        //  Create an empty component.
318        PointComponent component = PointComponent.newInstance();
319
320        try {
321            //  Parse the 1st line.
322            String aLine = readLine(in);
323            Text text = Text.valueOf(aLine);
324
325            //  Get the component number.
326            Text token = text.subtext(0, 8).trim();
327            int compNum = (int)TypeFormat.parseDouble(token);
328            component.putUserData("APASComponentNumber", compNum);
329
330            //  Get the component type code.
331            token = text.subtext(8, 10).trim();
332            int typeCode = TypeFormat.parseInt(token);
333            component.putUserData("APASType", typeCode);
334
335            //  Get the component name.
336            Text name = text.subtext(10, 26).trim();
337            component.setName(name.toString());
338
339            //  Get the number of cross sections in the component.
340            token = text.subtext(26, 28).trim();
341            int numXSections = TypeFormat.parseInt(token);
342
343            //  Get the number of points per cross section.
344            token = text.subtext(28, 30).trim();
345            int numPointsPerXSect = TypeFormat.parseInt(token);
346
347            //  Get the number of segments per cross section.
348            token = text.subtext(30, 32).trim();
349            int numSegmentsPerXSect = TypeFormat.parseInt(token);
350
351            //  Get the symmetry code.
352            token = text.subtext(32, 34).trim();
353            int symCode = TypeFormat.parseInt(token);
354            component.putUserData("APASSymmetry", symCode);
355
356            //  Get the panel code.
357            token = text.subtext(34, 36).trim();
358            int panelCode = TypeFormat.parseInt(token);
359            component.putUserData("APASPanelCode", panelCode);
360
361            //  Get the number of points in each segment.
362            FastTable<Integer64> numPointsPerSeg = FastTable.newInstance();
363            int total = 0;
364            int pos = 36;
365            for (int i = 0; i < numSegmentsPerXSect; ++i) {
366                int posp2 = pos + 2;
367                token = text.subtext(pos, posp2).trim();
368                int num = TypeFormat.parseInt(token);
369                total += num;
370                numPointsPerSeg.add(Integer64.valueOf(num));
371                pos = posp2;
372            }
373            if (total != numPointsPerXSect)
374                throw new IOException(
375                        MessageFormat.format(RESOURCES.getString("cardSegPointCountErr"),
376                                in.getLineNumber()));
377
378            //  Get the wetted/unwetted segment flag.
379            FastTable<Integer> wettedSegFlags = FastTable.newInstance();
380            for (int i = 0; i < numSegmentsPerXSect; ++i) {
381                int posp2 = pos + 2;
382                token = text.subtext(pos, posp2).trim();
383                int num = TypeFormat.parseInt(token);
384                wettedSegFlags.add(num);
385                pos = posp2;
386            }
387
388            if (DEBUG) {
389                System.out.println("CompID = " + compNum + ", type = " + typeCode + ", name = " + name);
390                System.out.println("numXSections = " + numXSections + ", numPointsPerXSect = " + numPointsPerXSect);
391                System.out.println("numSegmentsPerXSect = " + numSegmentsPerXSect + ", symCode = " + symCode);
392                System.out.println("numPointsPerSeg = " + numPointsPerSeg + ", panelCode = " + panelCode);
393                System.out.println("wettedSegFlags = " + wettedSegFlags);
394            }
395
396            //  Read in the geometry position records.
397            aLine = readLine(in);
398            text = Text.valueOf(aLine);
399
400            //  Get the geometry position offsets.
401            pos = 0;
402            int posp12 = 12;
403            token = text.subtext(pos, posp12).trim();
404            double dx = TypeFormat.parseDouble(token);
405
406            pos = posp12;
407            posp12 = pos + 12;
408            token = text.subtext(pos, posp12).trim();
409            double dy = TypeFormat.parseDouble(token);
410
411            pos = posp12;
412            posp12 = pos + 12;
413            token = text.subtext(pos, posp12).trim();
414            double dz = TypeFormat.parseDouble(token);
415
416            //  Get the geometry rotation angles.
417            pos = posp12;
418            posp12 = pos + 12;
419            token = text.subtext(pos, posp12).trim();
420            double yaw = TypeFormat.parseDouble(token);
421
422            pos = posp12;
423            posp12 = pos + 12;
424            token = text.subtext(pos, posp12).trim();
425            double pitch = TypeFormat.parseDouble(token);
426
427            pos = posp12;
428            token = text.subtext(pos).trim();
429            double roll = TypeFormat.parseDouble(token);
430
431            //  Create a transform if necessary.
432            GTransform TM = GTransform.IDENTITY;
433            if (dx != 0 || dy != 0 || dz != 0 || pitch != 0 || roll != 0 || yaw != 0) {
434                Parameter<Angle> yawP = Parameter.valueOf(yaw, NonSI.DEGREE_ANGLE);
435                Parameter<Angle> pitchP = Parameter.valueOf(pitch, NonSI.DEGREE_ANGLE);
436                Parameter<Angle> rollP = Parameter.valueOf(roll, NonSI.DEGREE_ANGLE);
437                EulerAngles ea = EulerAngles.valueOf(yawP, pitchP, rollP, EulerAngles.YAW_PITCH_ROLL);
438                TM = GTransform.valueOf(ea.toDCM(), 1, Vector.valueOf(units, dx, dy, dz));
439            }
440            if (DEBUG) {
441                if (TM.equals(GTransform.IDENTITY))
442                    System.out.println("TM = IDENTITY");
443                else {
444                    System.out.println("dx,dy,z = " + dx + "," + dy + "," + dz);
445                    System.out.println("pitch,roll,yaw = " + pitch + "," + roll + "," + yaw);
446                    System.out.println("TM = \n" + TM);
447                }
448            }
449
450            //  Skip the min and max values.
451            in.readLine();
452
453            //  Skip the HABP parameter lines.
454            if (typeCode < 5) {
455                for (int i = 0; i < 4; ++i) {
456                    in.readLine();
457                }
458            }
459
460            //  Read in all the coordinate values for this component.
461            int numValues = numXSections * numPointsPerXSect;
462            FastTable<Float64> xValues = readAxisValues(in, numValues);
463            FastTable<Float64> yValues = readAxisValues(in, numValues);
464            FastTable<Float64> zValues = readAxisValues(in, numValues);
465
466            //  Break up the data into arrays (each segment across all the cross sections is an array).
467            pos = 0;
468            for (int i = 0; i < numSegmentsPerXSect; ++i) {
469                PointArray array = PointArray.newInstance();
470                array.putUserData("APASWetted", wettedSegFlags.get(i));
471                int numPoints = (int)numPointsPerSeg.get(i).longValue();
472
473                for (int j = 0; j < numXSections; ++j) {
474                    PointString str = PointString.newInstance();
475                    int idx = pos + j * numPointsPerXSect;
476
477                    for (int k = 0; k < numPoints; ++k) {
478                        double x = xValues.get(idx).doubleValue();
479                        double y = yValues.get(idx).doubleValue();
480                        double z = zValues.get(idx).doubleValue();
481                        ++idx;
482                        str.add(Point.valueOf(x, y, z, units));
483                    }
484                    array.add(str);
485                }
486                //  Apply any transform to each array.
487                if (!TM.equals(GTransform.IDENTITY))
488                    array = array.getTransformed(TM);
489
490                //  Save the array in the component.
491                component.add(array);
492                pos += numPoints;
493            }
494
495            //  Cleanup before leaving.
496            FastTable.recycle(xValues);
497            FastTable.recycle(yValues);
498            FastTable.recycle(zValues);
499            FastTable.recycle(wettedSegFlags);
500            FastTable.recycle(numPointsPerSeg);
501
502        } catch (IndexOutOfBoundsException e) {
503            e.printStackTrace();
504            throw new IOException(
505                    MessageFormat.format(RESOURCES.getString("missingDataOnLine"),
506                            in.getLineNumber()));
507        }
508
509        return component;
510    }
511
512    /**
513     * Read in a table of coordinate axis (X, Y, or Z) values from the file.
514     */
515    private FastTable<Float64> readAxisValues(LineNumberReader in, int numValues) throws IOException {
516
517        FastTable<Float64> values = FastTable.newInstance();
518
519        StackContext.enter();
520        try {
521            Text text = null;
522            int posp12 = 12;
523            int col = 6;
524            for (int i = 0; i < numValues; ++i) {
525                //  Deal with the 6 column width of the CARD format.
526                if (col == 6) {
527                    String aLine = readLine(in);
528                    text = Text.valueOf(aLine);
529                    posp12 = 0;
530                    col = 0;
531                }
532
533                int pos = posp12;
534                posp12 = pos + 12;
535                @SuppressWarnings("null")
536                Text token = text.subtext(pos, posp12).trim();
537                double value = TypeFormat.parseDouble(token);
538                values.add(StackContext.outerCopy(Float64.valueOf(value)));
539
540                ++col;
541            }
542
543        } finally {
544            StackContext.exit();
545        }
546
547        return values;
548    }
549
550    /**
551     * Returns the unit code for the input geometry. This method gets the unit for the
552     * minimum bounds of the geometry, then makes sure it is a unit that the APAS CARD
553     * format recognizes (and changes it to one that it does recognize if necessary). Then
554     * the unit is stored using "setFileUnits". All input geometry must be converted to
555     * this unit for storage in the APAS CARD file.
556     */
557    private int getUnitCode(PointVehicle geometry) {
558
559        GeomPoint point = geometry.getBoundsMin();
560        Unit<Length> unit = point.getUnit();
561
562        int unitCode;
563        if (unit.equals(SI.METER))
564            unitCode = 1;
565        else if (unit.equals(NonSI.INCH))
566            unitCode = 2;
567        else if (unit.equals(SI.CENTIMETER))
568            unitCode = 3;
569        else {
570            //  Convert to a locale specific option.
571            if (Locale.getDefault().equals(Locale.US)) {
572                unit = NonSI.INCH;
573                unitCode = 2;
574
575            } else {
576                unit = SI.METER;
577                unitCode = 1;
578            }
579        }
580
581        setFileUnits(unit);
582
583        return unitCode;
584    }
585
586    /**
587     * Add spaces to the right of a string of text until that text equals the specified
588     * length.
589     */
590    private String padSpaces(String input, int length) {
591        int inputLength = input.length();
592        if (inputLength < length) {
593            StringBuilder buf = new StringBuilder(input);
594            for (int i = inputLength; i < length; ++i) {
595                buf.append(" ");
596            }
597            input = buf.toString();
598        }
599        return input;
600    }
601
602    /**
603     * Writes out the geometry for a single component to an APAS CARD file.
604     */
605    private void writeComponent(PrintWriter out, PointComponent comp) throws IOException {
606        Unit<Length> units = getFileUnits();
607
608        //  Write the component number.
609        Integer num = (Integer)comp.getUserData("APASComponentNumber");
610        out.printf("%5d.00", num);
611
612        //  Write out the APAS type code.
613        num = (Integer)comp.getUserData("APASType");
614        if (isNull(num))
615            num = 1;
616        out.printf("%2d", num);
617        int typeCode = num;
618
619        //  Write out the component name.
620        String name = comp.getName();
621        if (isNull(name))
622            name = "comp" + num;
623        if (name.length() > 16)
624            name = name.substring(0, 16);
625        out.print(padSpaces(name, 16));
626
627        //  Extract some data about the geometry.
628        int numSegmentsPerXSect = comp.size();
629        int numXSections = comp.get(0).size();
630        int numPointsPerXSect = 0;
631        for (PointArray arr : comp) {
632            int numPoints = arr.get(0).size();
633            numPointsPerXSect += numPoints;
634        }
635
636        //  Write out the number of cross sections in the component.
637        out.printf("%2d", numXSections);
638
639        //  Write out the number of points per cross section.
640        out.printf("%2d", numPointsPerXSect);
641
642        //  Write out the number of segments per cross section.
643        out.printf("%2d", numSegmentsPerXSect);
644
645        //  Write out the symmetry code.
646        num = (Integer)comp.getUserData("APASSymmetry");
647        if (isNull(num))
648            num = 2;
649        out.printf("%2d", num);
650
651        //  Write out the panel code.
652        num = (Integer)comp.getUserData("APASPanelCode");
653        if (isNull(num))
654            num = 0;
655        out.printf("%2d", num);
656
657        //  Write out the number of points in each segment.
658        for (PointArray arr : comp) {
659            int numPoints = arr.get(0).size();
660            out.printf("%2d", numPoints);
661        }
662
663        //  Write out the wetted/unwetted flag.
664        for (PointArray arr : comp) {
665            num = (Integer)arr.getUserData("APASWetted");
666            if (isNull(num))
667                num = 1;
668            out.printf("%2d", num);
669        }
670        out.println();
671
672        //  Write out the geometry position and rotation records.
673        out.println(" 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00 0.00000E+00");
674
675        //  Write out the min & max bounds for the component.
676        GeomPoint boundsMin = comp.getBoundsMin().to(units);
677        GeomPoint boundsMax = comp.getBoundsMax().to(units);
678        out.printf("%12.5E", boundsMin.getValue(0));
679        out.printf("%12.5E", boundsMax.getValue(0));
680        out.printf("%12.5E", boundsMin.getValue(1));
681        out.printf("%12.5E", boundsMax.getValue(1));
682        out.printf("%12.5E", boundsMin.getValue(2));
683        out.printf("%12.5E", boundsMax.getValue(2));
684        out.println();
685
686        //  Write out dummy HABP parameter lines.
687        if (typeCode < 5) {
688            out.println("    0    0    0    0  0.0000      0.0000      0.0000      0.0000    ");
689            out.println("    0    0    0    0  0.0000      0.0000      0.0000      0.0000    ");
690            out.println("    0    0    0    0  0.0000      0.0000      0.0000      0.0000    ");
691            out.println("  0.0000      0.0000      0.0000       1");
692        }
693
694        //  Convert the component to the output units.
695        comp = comp.to(units);
696
697        //  Write out all the coordinate values for this component.
698        writeAxisValues(out, Point.X, comp);
699        writeAxisValues(out, Point.Y, comp);
700        writeAxisValues(out, Point.Z, comp);
701    }
702
703    /**
704     * Write out all the coordinate values for a component for the specified axis. These
705     * are written out in cross sections (columns) through all the arrays.
706     */
707    private void writeAxisValues(PrintWriter out, int axis, PointComponent comp) {
708
709        int numXSections = comp.get(0).size();
710        int col = 0;
711        for (int i = 0; i < numXSections; ++i) {
712            for (PointArray<?> arr : comp) {
713                PointString<?> str = arr.get(i);
714                for (GeomPoint point : str) {
715                    out.printf("%12.5E", point.getValue(axis));
716                    ++col;
717                    if (col == 6) {
718                        col = 0;
719                        out.println();
720                    }
721                }
722            }
723        }
724        if (col != 0)
725            out.println();
726    }
727}