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 }