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 }