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 }