TextStlFacetDefinitionReader.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.geometry.io.euclidean.threed.stl;
import java.io.Reader;
import java.util.Arrays;
import org.apache.commons.geometry.euclidean.threed.Vector3D;
import org.apache.commons.geometry.io.core.internal.GeometryIOUtils;
import org.apache.commons.geometry.io.core.internal.SimpleTextParser;
import org.apache.commons.geometry.io.euclidean.threed.FacetDefinition;
import org.apache.commons.geometry.io.euclidean.threed.FacetDefinitionReader;
import org.apache.commons.geometry.io.euclidean.threed.SimpleFacetDefinition;
/** {@link FacetDefinitionReader} for reading the text (i.e., "ASCII") version of the STL file format.
* @see <a href="https://en.wikipedia.org/wiki/STL_%28file_format%29#ASCII_STL">ASCII STL</a>
*/
public class TextStlFacetDefinitionReader implements FacetDefinitionReader {
/** Underlying reader instance. */
private Reader reader;
/** Text parser. */
private SimpleTextParser parser;
/** Flag indicating if the start of a solid definition was detected. */
private boolean foundSolidStart;
/** Flag indicating if the end of a solid definition was detected. */
private boolean foundSolidEnd;
/** The name of the solid being read. */
private String solidName;
/** Construct a new instance for reading text STL content from the given reader.
* @param reader reader to read characters from
*/
public TextStlFacetDefinitionReader(final Reader reader) {
this.reader = reader;
this.parser = new SimpleTextParser(reader);
}
/** Get the name of the STL solid being read or null if no name was specified.
* @return the name of the STL solid being read or null if no name was specified
* @throws IllegalStateException if a data format error occurs
* @throws java.io.UncheckedIOException if an I/O error occurs
*/
public String getSolidName() {
ensureSolidStarted();
return solidName;
}
/** {@inheritDoc} */
@Override
public FacetDefinition readFacet() {
if (!foundSolidEnd && parser.hasMoreCharacters()) {
ensureSolidStarted();
nextWord();
int choice = parser.chooseIgnoreCase(
StlConstants.FACET_START_KEYWORD,
StlConstants.SOLID_END_KEYWORD);
if (choice == 0) {
return readFacetInternal();
} else {
foundSolidEnd = true;
}
}
return null;
}
/** {@inheritDoc} */
@Override
public void close() {
GeometryIOUtils.closeUnchecked(reader);
}
/** Internal method to read a single facet from the STL content.
* @return next facet definition
* @throws IllegalStateException if a data format error occurs
* @throws java.io.UncheckedIOException if an I/O error occurs
*/
private FacetDefinition readFacetInternal() {
matchKeyword(StlConstants.NORMAL_KEYWORD);
final Vector3D normal = readVector();
matchKeyword(StlConstants.OUTER_KEYWORD);
matchKeyword(StlConstants.LOOP_START_KEYWORD);
matchKeyword(StlConstants.VERTEX_KEYWORD);
final Vector3D p1 = readVector();
matchKeyword(StlConstants.VERTEX_KEYWORD);
final Vector3D p2 = readVector();
matchKeyword(StlConstants.VERTEX_KEYWORD);
final Vector3D p3 = readVector();
matchKeyword(StlConstants.LOOP_END_KEYWORD);
matchKeyword(StlConstants.FACET_END_KEYWORD);
return new SimpleFacetDefinition(Arrays.asList(p1, p2, p3), normal);
}
/** Ensure that an STL solid definition is in the process of being read. If not, the beginning
* of a the definition is attempted to be read from the input.
* @throws IllegalStateException if a data format error occurs
* @throws java.io.UncheckedIOException if an I/O error occurs
*/
private void ensureSolidStarted() {
if (!foundSolidStart) {
beginSolid();
foundSolidStart = true;
}
}
/** Begin reading an STL solid definition. The "solid" keyword is read
* along with the name of the solid.
* @throws IllegalStateException if a data format error occurs
* @throws java.io.UncheckedIOException if an I/O error occurs
*/
private void beginSolid() {
matchKeyword(StlConstants.SOLID_START_KEYWORD);
solidName = trimmedOrNull(parser.nextLine()
.getCurrentToken());
}
/** Read the next word from the content, discarding preceding whitespace.
* @throws IllegalStateException if a data format error occurs
* @throws java.io.UncheckedIOException if an I/O error occurs
*/
private void nextWord() {
parser.discardWhitespace()
.nextAlphanumeric();
}
/** Read the next word from the content and match it against the given keyword.
* @param keyword keyword to match against
* @throws IllegalStateException if the read content does not match the given keyword
* @throws java.io.UncheckedIOException if an I/O error occurs or
*/
private void matchKeyword(final String keyword) {
nextWord();
parser.matchIgnoreCase(keyword);
}
/** Read a vector from the input.
* @return the vector read from the input
* @throws IllegalStateException if a data format error occurs
* @throws java.io.UncheckedIOException if an I/O error occurs
*/
private Vector3D readVector() {
final double x = readDouble();
final double y = readDouble();
final double z = readDouble();
return Vector3D.of(x, y, z);
}
/** Read a double value from the input.
* @return double value read from the input
* @throws IllegalStateException if a data format error occurs
* @throws java.io.UncheckedIOException if an I/O error occurs
*/
private double readDouble() {
return parser
.discardWhitespace()
.next(SimpleTextParser::isDecimalPart)
.getCurrentTokenAsDouble();
}
/** Return a trimmed version of the given string or null if the string contains
* only whitespace.
* @param str input stream
* @return a trimmed version of the given string or null if the string contains only
* whitespace
*/
private static String trimmedOrNull(final String str) {
if (str != null) {
final String trimmed = str.trim();
if (!trimmed.isEmpty()) {
return trimmed;
}
}
return null;
}
}