1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
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
68 Assertions.assertEquals(GeometryFormat3D.STL, handler.getFormat());
69 Assertions.assertEquals(51200, handler.getinitialBufferSize());
70 }
71
72 @Test
73 void testSetInitialBufferSize() {
74
75 handler.setInitialBufferSize(10);
76
77
78 Assertions.assertEquals(10, handler.getinitialBufferSize());
79 }
80
81 @Test
82 void setInitialBufferSize_invalidArg() {
83
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
92 final BoundarySource3D src = BoundarySource3D.of();
93
94
95 handler.write(src, new StreamGeometryOutput(out));
96
97
98 Assertions.assertEquals(0, readOutput().count());
99 }
100
101 @Test
102 void testWrite_boundaryList() {
103
104 final BoundarySource3D src = EuclideanIOTestUtils.cubeMinusSphere(TEST_PRECISION);
105
106
107 handler.write(src, new StreamGeometryOutput(out));
108
109
110 EuclideanIOTestUtils.assertCubeMinusSphere(readOutput(), MODEL_TEST_EPS);
111 }
112
113 @Test
114 void testWrite_triangleMesh() {
115
116 final TriangleMesh mesh = EuclideanIOTestUtils.cubeMinusSphere(TEST_PRECISION)
117 .toTriangleMesh(TEST_PRECISION);
118
119
120 handler.write(mesh, new StreamGeometryOutput(out));
121
122
123 EuclideanIOTestUtils.assertCubeMinusSphere(readOutput(), MODEL_TEST_EPS);
124 }
125
126 @Test
127 void testWrite_triangleMesh_empty() {
128
129 final TriangleMesh mesh = SimpleTriangleMesh.builder(TEST_PRECISION)
130 .build();
131
132
133 handler.write(mesh, new StreamGeometryOutput(out));
134
135
136 Assertions.assertEquals(0, readOutput().count());
137 }
138
139 @Test
140 void testWriteStream_ioException() {
141
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
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
157 GeometryTestUtils.assertThrowsWithMessage(
158 () -> handler.write(stream, output),
159 UncheckedIOException.class,
160 "IOException: close");
161 }
162
163 @Test
164 void testWriteFacets_list() {
165
166 final List<FacetDefinition> facets = cubeFacets();
167
168
169 handler.writeFacets(facets, new StreamGeometryOutput(out));
170
171
172 EuclideanIOTestUtils.assertCube(readOutput(), MODEL_TEST_EPS);
173 }
174
175 @Test
176 void testWriteFacets_list_empty() {
177
178 handler.writeFacets(Collections.emptyList(), new StreamGeometryOutput(out));
179
180
181 Assertions.assertEquals(0, readOutput().count());
182 }
183
184 @Test
185 void testWriteFacets_includesStlFacetAttribute() {
186
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
194 handler.writeFacets(Collections.singletonList(facet), new StreamGeometryOutput(out));
195
196
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 }