View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  package org.apache.commons.geometry.io.euclidean.threed.stl;
    18
    19  import java.io.Reader;
    20  import java.util.Arrays;
    21
    22  import org.apache.commons.geometry.euclidean.threed.Vector3D;
    23  import org.apache.commons.geometry.io.core.internal.GeometryIOUtils;
    24  import org.apache.commons.geometry.io.core.internal.SimpleTextParser;
    25  import org.apache.commons.geometry.io.euclidean.threed.FacetDefinition;
    26  import org.apache.commons.geometry.io.euclidean.threed.FacetDefinitionReader;
    27  import org.apache.commons.geometry.io.euclidean.threed.SimpleFacetDefinition;
    28
    29  /** {@link FacetDefinitionReader} for reading the text (i.e., "ASCII") version of the STL file format.
    30   * @see <a href="https://en.wikipedia.org/wiki/STL_%28file_format%29#ASCII_STL">ASCII STL</a>
    31   */
    32  public class TextStlFacetDefinitionReader implements FacetDefinitionReader {
    33
    34      /** Underlying reader instance. */
    35      private Reader reader;
    36
    37      /** Text parser. */
    38      private SimpleTextParser parser;
    39
    40      /** Flag indicating if the start of a solid definition was detected. */
    41      private boolean foundSolidStart;
    42
    43      /** Flag indicating if the end of a solid definition was detected. */
    44      private boolean foundSolidEnd;
    45
    46      /** The name of the solid being read. */
    47      private String solidName;
    48
    49      /** Construct a new instance for reading text STL content from the given reader.
    50       * @param reader reader to read characters from
    51       */
    52      public TextStlFacetDefinitionReader(final Reader reader) {
    53          this.reader = reader;
    54          this.parser = new SimpleTextParser(reader);
    55      }
    56
    57      /** Get the name of the STL solid being read or null if no name was specified.
    58       * @return the name of the STL solid being read or null if no name was specified
    59       * @throws IllegalStateException if a data format error occurs
    60       * @throws java.io.UncheckedIOException if an I/O error occurs
    61       */
    62      public String getSolidName() {
    63          ensureSolidStarted();
    64
    65          return solidName;
    66      }
    67
    68      /** {@inheritDoc} */
    69      @Override
    70      public FacetDefinition readFacet() {
    71          if (!foundSolidEnd && parser.hasMoreCharacters()) {
    72              ensureSolidStarted();
    73
    74              nextWord();
    75
    76              int choice = parser.chooseIgnoreCase(
    77                      StlConstants.FACET_START_KEYWORD,
    78                      StlConstants.SOLID_END_KEYWORD);
    79
    80              if (choice == 0) {
    81                  return readFacetInternal();
    82              } else {
    83                  foundSolidEnd = true;
    84              }
    85          }
    86
    87          return null;
    88      }
    89
    90      /** {@inheritDoc} */
    91      @Override
    92      public void close() {
    93          GeometryIOUtils.closeUnchecked(reader);
    94      }
    95
    96      /** Internal method to read a single facet from the STL content.
    97       * @return next facet definition
    98       * @throws IllegalStateException if a data format error occurs
    99       * @throws java.io.UncheckedIOException if an I/O error occurs
    100      */
    101     private FacetDefinition readFacetInternal() {
    102         matchKeyword(StlConstants.NORMAL_KEYWORD);
    103         final Vector3D normal = readVector();
    104
    105         matchKeyword(StlConstants.OUTER_KEYWORD);
    106         matchKeyword(StlConstants.LOOP_START_KEYWORD);
    107
    108         matchKeyword(StlConstants.VERTEX_KEYWORD);
    109         final Vector3D p1 = readVector();
    110
    111         matchKeyword(StlConstants.VERTEX_KEYWORD);
    112         final Vector3D p2 = readVector();
    113
    114         matchKeyword(StlConstants.VERTEX_KEYWORD);
    115         final Vector3D p3 = readVector();
    116
    117         matchKeyword(StlConstants.LOOP_END_KEYWORD);
    118         matchKeyword(StlConstants.FACET_END_KEYWORD);
    119
    120         return new SimpleFacetDefinition(Arrays.asList(p1, p2, p3), normal);
    121     }
    122
    123     /** Ensure that an STL solid definition is in the process of being read. If not, the beginning
    124      * of a the definition is attempted to be read from the input.
    125      * @throws IllegalStateException if a data format error occurs
    126      * @throws java.io.UncheckedIOException if an I/O error occurs
    127      */
    128     private void ensureSolidStarted() {
    129         if (!foundSolidStart) {
    130             beginSolid();
    131
    132             foundSolidStart = true;
    133         }
    134     }
    135
    136     /** Begin reading an STL solid definition. The "solid" keyword is read
    137      * along with the name of the solid.
    138      * @throws IllegalStateException if a data format error occurs
    139      * @throws java.io.UncheckedIOException if an I/O error occurs
    140      */
    141     private void beginSolid() {
    142         matchKeyword(StlConstants.SOLID_START_KEYWORD);
    143
    144         solidName = trimmedOrNull(parser.nextLine()
    145                 .getCurrentToken());
    146     }
    147
    148     /** Read the next word from the content, discarding preceding whitespace.
    149      * @throws IllegalStateException if a data format error occurs
    150      * @throws java.io.UncheckedIOException if an I/O error occurs
    151      */
    152     private void nextWord() {
    153         parser.discardWhitespace()
    154             .nextAlphanumeric();
    155     }
    156
    157     /** Read the next word from the content and match it against the given keyword.
    158      * @param keyword keyword to match against
    159      * @throws IllegalStateException if the read content does not match the given keyword
    160      * @throws java.io.UncheckedIOException if an I/O error occurs or
    161      */
    162     private void matchKeyword(final String keyword) {
    163         nextWord();
    164         parser.matchIgnoreCase(keyword);
    165     }
    166
    167     /** Read a vector from the input.
    168      * @return the vector read from the input
    169      * @throws IllegalStateException if a data format error occurs
    170      * @throws java.io.UncheckedIOException if an I/O error occurs
    171      */
    172     private Vector3D readVector() {
    173         final double x = readDouble();
    174         final double y = readDouble();
    175         final double z = readDouble();
    176
    177         return Vector3D.of(x, y, z);
    178     }
    179
    180     /** Read a double value from the input.
    181      * @return double value read from the input
    182      * @throws IllegalStateException if a data format error occurs
    183      * @throws java.io.UncheckedIOException if an I/O error occurs
    184      */
    185     private double readDouble() {
    186         return parser
    187                 .discardWhitespace()
    188                 .next(SimpleTextParser::isDecimalPart)
    189                 .getCurrentTokenAsDouble();
    190     }
    191
    192     /** Return a trimmed version of the given string or null if the string contains
    193      * only whitespace.
    194      * @param str input stream
    195      * @return a trimmed version of the given string or null if the string contains only
    196      *      whitespace
    197      */
    198     private static String trimmedOrNull(final String str) {
    199         if (str != null) {
    200             final String trimmed = str.trim();
    201             if (!trimmed.isEmpty()) {
    202                 return trimmed;
    203             }
    204         }
    205
    206         return null;
    207     }
    208 }