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 }