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.ByteArrayInputStream;
20  import java.io.IOException;
21  import java.net.URL;
22  import java.nio.charset.StandardCharsets;
23  import java.util.List;
24  import java.util.stream.Collectors;
25  import java.util.stream.Stream;
26  
27  import org.apache.commons.geometry.euclidean.threed.BoundaryList3D;
28  import org.apache.commons.geometry.euclidean.threed.BoundarySource3D;
29  import org.apache.commons.geometry.euclidean.threed.ConvexPolygon3D;
30  import org.apache.commons.geometry.euclidean.threed.PlaneConvexSubset;
31  import org.apache.commons.geometry.io.core.input.GeometryInput;
32  import org.apache.commons.geometry.io.core.input.StreamGeometryInput;
33  import org.apache.commons.geometry.io.core.input.UrlGeometryInput;
34  import org.apache.commons.geometry.io.core.test.CloseCountInputStream;
35  import org.apache.commons.geometry.io.euclidean.EuclideanIOTestUtils;
36  import org.apache.commons.geometry.io.euclidean.threed.FacetDefinition;
37  import org.apache.commons.geometry.io.euclidean.threed.FacetDefinitionReader;
38  import org.apache.commons.geometry.io.euclidean.threed.FacetDefinitions;
39  import org.apache.commons.geometry.io.euclidean.threed.GeometryFormat3D;
40  import org.apache.commons.numbers.core.Precision;
41  import org.junit.jupiter.api.Assertions;
42  import org.junit.jupiter.api.Test;
43  
44  class StlBoundaryReadHandler3DTest {
45  
46      private static final double TEST_EPS = 1e-10;
47  
48      private static final Precision.DoubleEquivalence TEST_PRECISION =
49              Precision.doubleEquivalenceOfEpsilon(TEST_EPS);
50  
51      private final StlBoundaryReadHandler3D handler = new StlBoundaryReadHandler3D();
52  
53      @Test
54      void testProperties() {
55          // assert
56          Assertions.assertEquals(GeometryFormat3D.STL, handler.getFormat());
57          Assertions.assertEquals(StandardCharsets.UTF_8, handler.getDefaultCharset());
58      }
59  
60      @Test
61      void testReadMethods_cubeAscii() throws IOException {
62          // arrange
63          final URL url = EuclideanIOTestUtils.resource("/models/cube-ascii.stl");
64          final GeometryInput input = new UrlGeometryInput(url);
65  
66          // act/assert
67          EuclideanIOTestUtils.assertCube(readerToBoundarySource(handler.facetDefinitionReader(input)), TEST_EPS);
68          EuclideanIOTestUtils.assertCube(facetsToBoundarySource(handler.facets(input)), TEST_EPS);
69          EuclideanIOTestUtils.assertCube(handler.read(input, TEST_PRECISION), TEST_EPS);
70          EuclideanIOTestUtils.assertCube(handler.readTriangleMesh(input, TEST_PRECISION), TEST_EPS);
71          EuclideanIOTestUtils.assertCube(
72                  boundariesToBoundarySource(handler.boundaries(input, TEST_PRECISION)), TEST_EPS);
73      }
74  
75      @Test
76      void testReadMethods_cubeBinary() throws IOException {
77          // arrange
78          final URL url = EuclideanIOTestUtils.resource("/models/cube-ascii.stl");
79          final GeometryInput input = new UrlGeometryInput(url);
80  
81          // act/assert
82          EuclideanIOTestUtils.assertCube(readerToBoundarySource(handler.facetDefinitionReader(input)), TEST_EPS);
83          EuclideanIOTestUtils.assertCube(facetsToBoundarySource(handler.facets(input)), TEST_EPS);
84          EuclideanIOTestUtils.assertCube(handler.read(input, TEST_PRECISION), TEST_EPS);
85          EuclideanIOTestUtils.assertCube(handler.readTriangleMesh(input, TEST_PRECISION), TEST_EPS);
86          EuclideanIOTestUtils.assertCube(
87                  boundariesToBoundarySource(handler.boundaries(input, TEST_PRECISION)), TEST_EPS);
88      }
89  
90      @Test
91      void testRead_usesInputCharset() {
92          // arrange
93          final String content = "solid test\n" +
94                  "facet normal 1 2 3 " +
95                  "outer loop " +
96                      "vertex 4 5 6 " +
97                      "vertex 7 8 9 " +
98                      "vertex 10 11 12 " +
99                  "endloop " +
100             "endfacet " +
101             "endsolid test";
102 
103         final ByteArrayInputStream in = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_16));
104         final GeometryInput input = new StreamGeometryInput(in, null, StandardCharsets.UTF_16);
105 
106         // act/assert
107         try (FacetDefinitionReader reader = handler.facetDefinitionReader(input)) {
108             Assertions.assertNotNull(reader.readFacet());
109             Assertions.assertNull(reader.readFacet());
110         }
111     }
112 
113     @Test
114     void testRead_setDefaultCharset() {
115         // arrange
116         final String content = "solid test\n" +
117                 "facet normal 1 2 3 " +
118                 "outer loop " +
119                     "vertex 4 5 6 " +
120                     "vertex 7 8 9 " +
121                     "vertex 10 11 12 " +
122                 "endloop " +
123             "endfacet " +
124             "endsolid test";
125 
126         final ByteArrayInputStream in = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_16));
127         final GeometryInput input = new StreamGeometryInput(in);
128 
129         // act
130         handler.setDefaultCharset(StandardCharsets.UTF_16);
131 
132         // assert
133         try (FacetDefinitionReader reader = handler.facetDefinitionReader(input)) {
134             Assertions.assertNotNull(reader.readFacet());
135             Assertions.assertNull(reader.readFacet());
136         }
137     }
138 
139     @Test
140     void testRead_incorrectCharset() {
141         // arrange
142         final String content = "solid test\n" +
143                 "facet normal 1 2 3 " +
144                 "outer loop " +
145                     "vertex 4 5 6 " +
146                     "vertex 7 8 9 " +
147                     "vertex 10 11 12 " +
148                 "endloop " +
149             "endfacet " +
150             "endsolid test";
151 
152         final ByteArrayInputStream in = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_16));
153         final GeometryInput input = new StreamGeometryInput(in);
154 
155         // act/assert
156         try (FacetDefinitionReader reader = handler.facetDefinitionReader(input)) {
157             Assertions.assertNotNull(reader.readFacet());
158             Assertions.assertNotNull(reader.readFacet());
159 
160             Assertions.assertThrows(IllegalStateException.class, () -> reader.readFacet());
161         }
162     }
163 
164     @Test
165     void testRead_notEnoughBytes() {
166         // arrange
167         final ByteArrayInputStream in = new ByteArrayInputStream(new byte[1]);
168         final GeometryInput input = new StreamGeometryInput(in);
169 
170         // act/assert
171         Assertions.assertThrows(IllegalStateException.class, () -> handler.facetDefinitionReader(input));
172     }
173 
174     @Test
175     void testRead_closesInputOnReaderCreationFailure() {
176         // arrange
177         final CloseCountInputStream in = new CloseCountInputStream(new ByteArrayInputStream(new byte[1]));
178         final GeometryInput input = new StreamGeometryInput(in);
179 
180         // act/assert
181         Assertions.assertThrows(IllegalStateException.class, () -> handler.facetDefinitionReader(input));
182 
183         Assertions.assertEquals(1, in.getCloseCount());
184     }
185 
186     private static BoundarySource3D boundariesToBoundarySource(final Stream<? extends PlaneConvexSubset> boundaries) {
187         try (Stream<? extends PlaneConvexSubset> toClose = boundaries) {
188             return new BoundaryList3D(boundaries.collect(Collectors.toList()));
189         }
190     }
191 
192     private static BoundarySource3D facetsToBoundarySource(final Stream<? extends FacetDefinition> facets) {
193         try (Stream<? extends FacetDefinition> toClose = facets) {
194             final List<ConvexPolygon3D> polygons = facets
195                     .map(f -> FacetDefinitions.toPolygon(f, TEST_PRECISION))
196                     .collect(Collectors.toList());
197             return new BoundaryList3D(polygons);
198         }
199     }
200 
201     private static BoundarySource3D readerToBoundarySource(final FacetDefinitionReader reader) {
202         return facetsToBoundarySource(EuclideanIOTestUtils.readAll(reader).stream());
203     }
204 }