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.ByteArrayOutputStream;
    21  import java.io.IOException;
    22  import java.io.OutputStream;
    23  import java.io.UncheckedIOException;
    24  import java.util.Arrays;
    25  import java.util.Collections;
    26  import java.util.List;
    27  import java.util.stream.Collectors;
    28  import java.util.stream.Stream;
    29
    30  import org.apache.commons.geometry.core.GeometryTestUtils;
    31  import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
    32  import org.apache.commons.geometry.euclidean.threed.BoundaryList3D;
    33  import org.apache.commons.geometry.euclidean.threed.BoundarySource3D;
    34  import org.apache.commons.geometry.euclidean.threed.PlaneConvexSubset;
    35  import org.apache.commons.geometry.euclidean.threed.Vector3D;
    36  import org.apache.commons.geometry.euclidean.threed.mesh.SimpleTriangleMesh;
    37  import org.apache.commons.geometry.euclidean.threed.mesh.TriangleMesh;
    38  import org.apache.commons.geometry.euclidean.threed.shape.Parallelepiped;
    39  import org.apache.commons.geometry.io.core.input.GeometryInput;
    40  import org.apache.commons.geometry.io.core.input.StreamGeometryInput;
    41  import org.apache.commons.geometry.io.core.output.GeometryOutput;
    42  import org.apache.commons.geometry.io.core.output.StreamGeometryOutput;
    43  import org.apache.commons.geometry.io.euclidean.EuclideanIOTestUtils;
    44  import org.apache.commons.geometry.io.euclidean.threed.FacetDefinition;
    45  import org.apache.commons.geometry.io.euclidean.threed.GeometryFormat3D;
    46  import org.apache.commons.geometry.io.euclidean.threed.SimpleFacetDefinition;
    47  import org.apache.commons.numbers.core.Precision;
    48  import org.junit.jupiter.api.Assertions;
    49  import org.junit.jupiter.api.Test;
    50
    51  class StlBoundaryWriteHandler3DTest {
    52
    53      private static final double TEST_EPS = 1e-10;
    54
    55      /** Lower test epsilon accounting for the use of floats in the binary output. */
    56      private static final double MODEL_TEST_EPS = 1e-7;
    57
    58      private static final Precision.DoubleEquivalence TEST_PRECISION =
    59              Precision.doubleEquivalenceOfEpsilon(TEST_EPS);
    60
    61      private final StlBoundaryWriteHandler3D handler = new StlBoundaryWriteHandler3D();
    62
    63      private final ByteArrayOutputStream out = new ByteArrayOutputStream();
    64
    65      @Test
    66      void testProperties() {
    67          // assert
    68          Assertions.assertEquals(GeometryFormat3D.STL, handler.getFormat());
    69          Assertions.assertEquals(51200, handler.getinitialBufferSize());
    70      }
    71
    72      @Test
    73      void testSetInitialBufferSize() {
    74          // act
    75          handler.setInitialBufferSize(10);
    76
    77          // assert
    78          Assertions.assertEquals(10, handler.getinitialBufferSize());
    79      }
    80
    81      @Test
    82      void setInitialBufferSize_invalidArg() {
    83          // act/assert
    84          GeometryTestUtils.assertThrowsWithMessage(
    85                  () -> handler.setInitialBufferSize(0),
    86                  IllegalArgumentException.class, "Buffer size must be greater than 0");
    87      }
    88
    89      @Test
    90      void testWrite_boundarySource_empty() {
    91          // arrange
    92          final BoundarySource3D src = BoundarySource3D.of();
    93
    94          // act
    95          handler.write(src, new StreamGeometryOutput(out));
    96
    97          // assert
    98          Assertions.assertEquals(0, readOutput().count());
    99      }
    100
    101     @Test
    102     void testWrite_boundaryList() {
    103         // arrange
    104         final BoundarySource3D src = EuclideanIOTestUtils.cubeMinusSphere(TEST_PRECISION);
    105
    106         // act
    107         handler.write(src, new StreamGeometryOutput(out));
    108
    109         // assert
    110         EuclideanIOTestUtils.assertCubeMinusSphere(readOutput(), MODEL_TEST_EPS);
    111     }
    112
    113     @Test
    114     void testWrite_triangleMesh() {
    115         // arrange
    116         final TriangleMesh mesh = EuclideanIOTestUtils.cubeMinusSphere(TEST_PRECISION)
    117                 .toTriangleMesh(TEST_PRECISION);
    118
    119         // act
    120         handler.write(mesh, new StreamGeometryOutput(out));
    121
    122         // assert
    123         EuclideanIOTestUtils.assertCubeMinusSphere(readOutput(), MODEL_TEST_EPS);
    124     }
    125
    126     @Test
    127     void testWrite_triangleMesh_empty() {
    128         // arrange
    129         final TriangleMesh mesh = SimpleTriangleMesh.builder(TEST_PRECISION)
    130                 .build();
    131
    132         // act
    133         handler.write(mesh, new StreamGeometryOutput(out));
    134
    135         // assert
    136         Assertions.assertEquals(0, readOutput().count());
    137     }
    138
    139     @Test
    140     void testWriteStream_ioException() {
    141         // arrange
    142         final Stream<PlaneConvexSubset> stream = EuclideanIOTestUtils.cubeMinusSphere(TEST_PRECISION).boundaryStream();
    143         final OutputStream failOut = new OutputStream() {
    144             @Override
    145             public void write(final int b) throws IOException {
    146                 // do nothing
    147             }
    148
    149             @Override
    150             public void close() throws IOException {
    151                 throw new IOException("close");
    152             }
    153         };
    154         final GeometryOutput output = new StreamGeometryOutput(failOut);
    155
    156         // act/assert
    157         GeometryTestUtils.assertThrowsWithMessage(
    158                 () -> handler.write(stream, output),
    159                 UncheckedIOException.class,
    160                 "IOException: close");
    161     }
    162
    163     @Test
    164     void testWriteFacets_list() {
    165         // arrange
    166         final List<FacetDefinition> facets = cubeFacets();
    167
    168         // act
    169         handler.writeFacets(facets, new StreamGeometryOutput(out));
    170
    171         // assert
    172         EuclideanIOTestUtils.assertCube(readOutput(), MODEL_TEST_EPS);
    173     }
    174
    175     @Test
    176     void testWriteFacets_list_empty() {
    177         // act
    178         handler.writeFacets(Collections.emptyList(), new StreamGeometryOutput(out));
    179
    180         // assert
    181         Assertions.assertEquals(0, readOutput().count());
    182     }
    183
    184     @Test
    185     void testWriteFacets_includesStlFacetAttribute() {
    186         // arrange
    187         final List<Vector3D> vertices = Arrays.asList(Vector3D.ZERO, Vector3D.of(1, 0, 0), Vector3D.of(0, 1, 0));
    188         final Vector3D normal = Vector3D.Unit.PLUS_Z;
    189         final int attr = 12;
    190
    191         final BinaryStlFacetDefinition facet = new BinaryStlFacetDefinition(vertices, normal, attr);
    192
    193         // act
    194         handler.writeFacets(Collections.singletonList(facet), new StreamGeometryOutput(out));
    195
    196         // assert
    197         BinaryStlFacetDefinitionReader reader =
    198                 new BinaryStlFacetDefinitionReader(new ByteArrayInputStream(out.toByteArray()));
    199         BinaryStlFacetDefinition result = reader.readFacet();
    200
    201         EuclideanIOTestUtils.assertFacetVertices(result, vertices, MODEL_TEST_EPS);
    202         EuclideanTestUtils.assertCoordinatesEqual(normal, result.getNormal(), MODEL_TEST_EPS);
    203         Assertions.assertEquals(attr, result.getAttributeValue());
    204     }
    205
    206     private BoundaryList3D readOutput() {
    207         final GeometryInput input = new StreamGeometryInput(new ByteArrayInputStream(out.toByteArray()));
    208
    209         final StlBoundaryReadHandler3D readHandler = new StlBoundaryReadHandler3D();
    210         return readHandler.read(input, TEST_PRECISION).toList();
    211     }
    212
    213     private static List<FacetDefinition> cubeFacets() {
    214         final BoundarySource3D cube = Parallelepiped.unitCube(TEST_PRECISION);
    215         return cube.triangleStream()
    216             .map(t -> new SimpleFacetDefinition(t.getVertices(), t.getPlane().getNormal()))
    217             .collect(Collectors.toList());
    218     }
    219 }