001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.geometry.io.euclidean.threed.stl; 018 019import java.io.Reader; 020import java.util.Arrays; 021 022import org.apache.commons.geometry.euclidean.threed.Vector3D; 023import org.apache.commons.geometry.io.core.internal.GeometryIOUtils; 024import org.apache.commons.geometry.io.core.internal.SimpleTextParser; 025import org.apache.commons.geometry.io.euclidean.threed.FacetDefinition; 026import org.apache.commons.geometry.io.euclidean.threed.FacetDefinitionReader; 027import org.apache.commons.geometry.io.euclidean.threed.SimpleFacetDefinition; 028 029/** {@link FacetDefinitionReader} for reading the text (i.e., "ASCII") version of the STL file format. 030 * @see <a href="https://en.wikipedia.org/wiki/STL_%28file_format%29#ASCII_STL">ASCII STL</a> 031 */ 032public class TextStlFacetDefinitionReader implements FacetDefinitionReader { 033 034 /** Underlying reader instance. */ 035 private Reader reader; 036 037 /** Text parser. */ 038 private SimpleTextParser parser; 039 040 /** Flag indicating if the start of a solid definition was detected. */ 041 private boolean foundSolidStart; 042 043 /** Flag indicating if the end of a solid definition was detected. */ 044 private boolean foundSolidEnd; 045 046 /** The name of the solid being read. */ 047 private String solidName; 048 049 /** Construct a new instance for reading text STL content from the given reader. 050 * @param reader reader to read characters from 051 */ 052 public TextStlFacetDefinitionReader(final Reader reader) { 053 this.reader = reader; 054 this.parser = new SimpleTextParser(reader); 055 } 056 057 /** Get the name of the STL solid being read or null if no name was specified. 058 * @return the name of the STL solid being read or null if no name was specified 059 * @throws IllegalStateException if a data format error occurs 060 * @throws java.io.UncheckedIOException if an I/O error occurs 061 */ 062 public String getSolidName() { 063 ensureSolidStarted(); 064 065 return solidName; 066 } 067 068 /** {@inheritDoc} */ 069 @Override 070 public FacetDefinition readFacet() { 071 if (!foundSolidEnd && parser.hasMoreCharacters()) { 072 ensureSolidStarted(); 073 074 nextWord(); 075 076 int choice = parser.chooseIgnoreCase( 077 StlConstants.FACET_START_KEYWORD, 078 StlConstants.SOLID_END_KEYWORD); 079 080 if (choice == 0) { 081 return readFacetInternal(); 082 } else { 083 foundSolidEnd = true; 084 } 085 } 086 087 return null; 088 } 089 090 /** {@inheritDoc} */ 091 @Override 092 public void close() { 093 GeometryIOUtils.closeUnchecked(reader); 094 } 095 096 /** Internal method to read a single facet from the STL content. 097 * @return next facet definition 098 * @throws IllegalStateException if a data format error occurs 099 * @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}