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.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 }