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}