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.ByteArrayOutputStream;
20 import java.io.IOException;
21 import java.io.OutputStream;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.stream.Stream;
25
26 import org.apache.commons.geometry.euclidean.internal.EuclideanUtils;
27 import org.apache.commons.geometry.euclidean.threed.BoundarySource3D;
28 import org.apache.commons.geometry.euclidean.threed.PlaneConvexSubset;
29 import org.apache.commons.geometry.euclidean.threed.Triangle3D;
30 import org.apache.commons.geometry.euclidean.threed.Vector3D;
31 import org.apache.commons.geometry.euclidean.threed.mesh.TriangleMesh;
32 import org.apache.commons.geometry.io.core.GeometryFormat;
33 import org.apache.commons.geometry.io.core.internal.GeometryIOUtils;
34 import org.apache.commons.geometry.io.core.output.GeometryOutput;
35 import org.apache.commons.geometry.io.euclidean.threed.AbstractBoundaryWriteHandler3D;
36 import org.apache.commons.geometry.io.euclidean.threed.FacetDefinition;
37 import org.apache.commons.geometry.io.euclidean.threed.GeometryFormat3D;
38
39 /** {@link org.apache.commons.geometry.io.euclidean.threed.BoundaryWriteHandler3D BoundaryWriteHandler3D}
40 * implementation for writing STL content. Because of its compact nature, all STL content is written in
41 * binary format, as opposed the text (i.e. "ASCII") format. Callers should use the {@link TextStlWriter}
42 * class directly in order to create text STL content.
43 */
44 public class StlBoundaryWriteHandler3D extends AbstractBoundaryWriteHandler3D {
45
46 /** Initial size of the data buffer. */
47 private static final int DEFAULT_BUFFER_SIZE = 1024 * StlConstants.BINARY_TRIANGLE_BYTES;
48
49 /** Initial size of data buffers used during write operations. */
50 private int initialBufferSize = DEFAULT_BUFFER_SIZE;
51
52 /** {@inheritDoc} */
53 @Override
54 public GeometryFormat getFormat() {
55 return GeometryFormat3D.STL;
56 }
57
58 /** Get the initial size of the data buffers used by this instance.
59 *
60 * <p>The buffer is used in situations where it is not clear how many
61 * triangles will ultimately be written to the output. In these cases, the
62 * triangle data is first written to an internal buffer. Once all triangles are
63 * written, the STL header containing the total triangle count is written
64 * to the output, followed by the buffered triangle data.</p>
65 * @return initial buffer size
66 */
67 public int getinitialBufferSize() {
68 return initialBufferSize;
69 }
70
71 /** Set the initial size of the data buffers used by this instance.
72 *
73 * <p>The buffer is used in situations where it is not clear how many
74 * triangles will ultimately be written to the output. In these cases, the
75 * triangle data is first written to an internal buffer. Once all triangles are
76 * written, the STL header containing the total triangle count is written
77 * to the output, followed by the buffered triangle data.</p>
78 * @param initialBufferSize initial buffer size
79 */
80 public void setInitialBufferSize(final int initialBufferSize) {
81 if (initialBufferSize < 1) {
82 throw new IllegalArgumentException("Buffer size must be greater than 0");
83 }
84 this.initialBufferSize = initialBufferSize;
85 }
86
87 /** {@inheritDoc} */
88 @Override
89 public void write(final BoundarySource3D src, final GeometryOutput out) {
90 // handle cases where we know the number of triangles to be written up front
91 // and do not need to buffer the content
92 if (src instanceof TriangleMesh) {
93 writeTriangleMesh((TriangleMesh) src, out);
94 } else {
95 // unknown number of triangles; proceed with a buffered write
96 super.write(src, out);
97 }
98 }
99
100 /** {@inheritDoc} */
101 @Override
102 public void write(final Stream<? extends PlaneConvexSubset> boundaries, final GeometryOutput out) {
103
104 // write the triangle data to a buffer and track how many we write
105 int triangleCount = 0;
106 final ByteArrayOutputStream triangleBuffer = new ByteArrayOutputStream(initialBufferSize);
107
108 try (BinaryStlWriter stlWriter = new BinaryStlWriter(triangleBuffer)) {
109 final Iterator<? extends PlaneConvexSubset> it = boundaries.iterator();
110
111 while (it.hasNext()) {
112 for (final Triangle3D tri : it.next().toTriangles()) {
113
114 stlWriter.writeTriangle(
115 tri.getPoint1(),
116 tri.getPoint2(),
117 tri.getPoint3(),
118 tri.getPlane().getNormal());
119
120 ++triangleCount;
121 }
122 }
123 }
124
125 // write the header and copy the data
126 writeWithHeader(triangleBuffer, triangleCount, out);
127 }
128
129 /** {@inheritDoc} */
130 @Override
131 public void writeFacets(final Stream<? extends FacetDefinition> facets, final GeometryOutput out) {
132
133 // write the triangle data to a buffer and track how many we write
134 int triangleCount = 0;
135 final ByteArrayOutputStream triangleBuffer = new ByteArrayOutputStream(initialBufferSize);
136
137 try (BinaryStlWriter dataWriter = new BinaryStlWriter(triangleBuffer)) {
138 final Iterator<? extends FacetDefinition> it = facets.iterator();
139
140 FacetDefinition facet;
141 int attributeValue;
142
143 while (it.hasNext()) {
144 facet = it.next();
145 attributeValue = getFacetAttributeValue(facet);
146
147 for (final List<Vector3D> tri :
148 EuclideanUtils.convexPolygonToTriangleFan(facet.getVertices(), t -> t)) {
149
150 dataWriter.writeTriangle(
151 tri.get(0),
152 tri.get(1),
153 tri.get(2),
154 facet.getNormal(),
155 attributeValue);
156
157 ++triangleCount;
158 }
159 }
160 }
161
162 // write the header and copy the data
163 writeWithHeader(triangleBuffer, triangleCount, out);
164 }
165
166 /** Write the given triangle data prefixed with an STL header to the output stream from {@code out}.
167 * @param triangleBuffer buffer containing STL triangle data
168 * @param count number of triangles in {@code triangleBuffer}
169 * @param out output to write to
170 * @throws java.io.UncheckedIOException if an I/O error occurs
171 */
172 private void writeWithHeader(final ByteArrayOutputStream triangleBuffer, final int count,
173 final GeometryOutput out) {
174 // write the header and copy the data
175 try (OutputStream os = out.getOutputStream()) {
176 BinaryStlWriter.writeHeader(null, count, os);
177 triangleBuffer.writeTo(os);
178 } catch (IOException exc) {
179 throw GeometryIOUtils.createUnchecked(exc);
180 }
181 }
182
183 /** Write all triangles in the given mesh to the output using the binary STL
184 * format.
185 * @param mesh mesh to write
186 * @param output output to write to
187 * @throws java.io.UncheckedIOException if an I/O error occurs
188 */
189 private void writeTriangleMesh(final TriangleMesh mesh, final GeometryOutput output) {
190 try (BinaryStlWriter stlWriter = new BinaryStlWriter(output.getOutputStream())) {
191 // write the header
192 stlWriter.writeHeader(null, mesh.getFaceCount());
193
194 // write each triangle
195 Triangle3D tri;
196 for (final TriangleMesh.Face face : mesh.faces()) {
197 tri = face.getPolygon();
198
199 stlWriter.writeTriangle(
200 tri.getPoint1(),
201 tri.getPoint2(),
202 tri.getPoint3(),
203 tri.getPlane().getNormal());
204 }
205 }
206 }
207
208 /** Get the attribute value that should be used for the given facet.
209 * @param facet facet to get the attribute value for
210 * @return attribute value
211 */
212 private int getFacetAttributeValue(final FacetDefinition facet) {
213 if (facet instanceof BinaryStlFacetDefinition) {
214 return ((BinaryStlFacetDefinition) facet).getAttributeValue();
215 }
216
217 return 0;
218 }
219 }