StlBoundaryWriteHandler3D.java

  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. import java.io.ByteArrayOutputStream;
  19. import java.io.IOException;
  20. import java.io.OutputStream;
  21. import java.util.Iterator;
  22. import java.util.List;
  23. import java.util.stream.Stream;

  24. import org.apache.commons.geometry.euclidean.internal.EuclideanUtils;
  25. import org.apache.commons.geometry.euclidean.threed.BoundarySource3D;
  26. import org.apache.commons.geometry.euclidean.threed.PlaneConvexSubset;
  27. import org.apache.commons.geometry.euclidean.threed.Triangle3D;
  28. import org.apache.commons.geometry.euclidean.threed.Vector3D;
  29. import org.apache.commons.geometry.euclidean.threed.mesh.TriangleMesh;
  30. import org.apache.commons.geometry.io.core.GeometryFormat;
  31. import org.apache.commons.geometry.io.core.internal.GeometryIOUtils;
  32. import org.apache.commons.geometry.io.core.output.GeometryOutput;
  33. import org.apache.commons.geometry.io.euclidean.threed.AbstractBoundaryWriteHandler3D;
  34. import org.apache.commons.geometry.io.euclidean.threed.FacetDefinition;
  35. import org.apache.commons.geometry.io.euclidean.threed.GeometryFormat3D;

  36. /** {@link org.apache.commons.geometry.io.euclidean.threed.BoundaryWriteHandler3D BoundaryWriteHandler3D}
  37.  * implementation for writing STL content. Because of its compact nature, all STL content is written in
  38.  * binary format, as opposed the text (i.e. "ASCII") format. Callers should use the {@link TextStlWriter}
  39.  * class directly in order to create text STL content.
  40.  */
  41. public class StlBoundaryWriteHandler3D extends AbstractBoundaryWriteHandler3D {

  42.     /** Initial size of the data buffer. */
  43.     private static final int DEFAULT_BUFFER_SIZE = 1024 * StlConstants.BINARY_TRIANGLE_BYTES;

  44.     /** Initial size of data buffers used during write operations. */
  45.     private int initialBufferSize = DEFAULT_BUFFER_SIZE;

  46.     /** {@inheritDoc} */
  47.     @Override
  48.     public GeometryFormat getFormat() {
  49.         return GeometryFormat3D.STL;
  50.     }

  51.     /** Get the initial size of the data buffers used by this instance.
  52.      *
  53.      * <p>The buffer is used in situations where it is not clear how many
  54.      * triangles will ultimately be written to the output. In these cases, the
  55.      * triangle data is first written to an internal buffer. Once all triangles are
  56.      * written, the STL header containing the total triangle count is written
  57.      * to the output, followed by the buffered triangle data.</p>
  58.      * @return initial buffer size
  59.      */
  60.     public int getinitialBufferSize() {
  61.         return initialBufferSize;
  62.     }

  63.     /** Set the initial size of the data buffers used by this instance.
  64.      *
  65.      * <p>The buffer is used in situations where it is not clear how many
  66.      * triangles will ultimately be written to the output. In these cases, the
  67.      * triangle data is first written to an internal buffer. Once all triangles are
  68.      * written, the STL header containing the total triangle count is written
  69.      * to the output, followed by the buffered triangle data.</p>
  70.      * @param initialBufferSize initial buffer size
  71.      */
  72.     public void setInitialBufferSize(final int initialBufferSize) {
  73.         if (initialBufferSize < 1) {
  74.             throw new IllegalArgumentException("Buffer size must be greater than 0");
  75.         }
  76.         this.initialBufferSize = initialBufferSize;
  77.     }

  78.     /** {@inheritDoc} */
  79.     @Override
  80.     public void write(final BoundarySource3D src, final GeometryOutput out) {
  81.         // handle cases where we know the number of triangles to be written up front
  82.         // and do not need to buffer the content
  83.         if (src instanceof TriangleMesh) {
  84.             writeTriangleMesh((TriangleMesh) src, out);
  85.         } else {
  86.             // unknown number of triangles; proceed with a buffered write
  87.             super.write(src, out);
  88.         }
  89.     }

  90.     /** {@inheritDoc} */
  91.     @Override
  92.     public void write(final Stream<? extends PlaneConvexSubset> boundaries, final GeometryOutput out) {

  93.         // write the triangle data to a buffer and track how many we write
  94.         int triangleCount = 0;
  95.         final ByteArrayOutputStream triangleBuffer = new ByteArrayOutputStream(initialBufferSize);

  96.         try (BinaryStlWriter stlWriter = new BinaryStlWriter(triangleBuffer)) {
  97.             final Iterator<? extends PlaneConvexSubset> it = boundaries.iterator();

  98.             while (it.hasNext()) {
  99.                 for (final Triangle3D tri : it.next().toTriangles()) {

  100.                     stlWriter.writeTriangle(
  101.                             tri.getPoint1(),
  102.                             tri.getPoint2(),
  103.                             tri.getPoint3(),
  104.                             tri.getPlane().getNormal());

  105.                     ++triangleCount;
  106.                 }
  107.             }
  108.         }

  109.         // write the header and copy the data
  110.         writeWithHeader(triangleBuffer, triangleCount, out);
  111.     }

  112.     /** {@inheritDoc} */
  113.     @Override
  114.     public void writeFacets(final Stream<? extends FacetDefinition> facets, final GeometryOutput out) {

  115.         // write the triangle data to a buffer and track how many we write
  116.         int triangleCount = 0;
  117.         final ByteArrayOutputStream triangleBuffer = new ByteArrayOutputStream(initialBufferSize);

  118.         try (BinaryStlWriter dataWriter = new BinaryStlWriter(triangleBuffer)) {
  119.             final Iterator<? extends FacetDefinition> it = facets.iterator();

  120.             FacetDefinition facet;
  121.             int attributeValue;

  122.             while (it.hasNext()) {
  123.                 facet = it.next();
  124.                 attributeValue = getFacetAttributeValue(facet);

  125.                 for (final List<Vector3D> tri :
  126.                     EuclideanUtils.convexPolygonToTriangleFan(facet.getVertices(), t -> t)) {

  127.                     dataWriter.writeTriangle(
  128.                             tri.get(0),
  129.                             tri.get(1),
  130.                             tri.get(2),
  131.                             facet.getNormal(),
  132.                             attributeValue);

  133.                     ++triangleCount;
  134.                 }
  135.             }
  136.         }

  137.         // write the header and copy the data
  138.         writeWithHeader(triangleBuffer, triangleCount, out);
  139.     }

  140.     /** Write the given triangle data prefixed with an STL header to the output stream from {@code out}.
  141.      * @param triangleBuffer buffer containing STL triangle data
  142.      * @param count number of triangles in {@code triangleBuffer}
  143.      * @param out output to write to
  144.      * @throws java.io.UncheckedIOException if an I/O error occurs
  145.      */
  146.     private void writeWithHeader(final ByteArrayOutputStream triangleBuffer, final int count,
  147.             final GeometryOutput out) {
  148.         // write the header and copy the data
  149.         try (OutputStream os = out.getOutputStream()) {
  150.             BinaryStlWriter.writeHeader(null, count, os);
  151.             triangleBuffer.writeTo(os);
  152.         } catch (IOException exc) {
  153.             throw GeometryIOUtils.createUnchecked(exc);
  154.         }
  155.     }

  156.     /** Write all triangles in the given mesh to the output using the binary STL
  157.      * format.
  158.      * @param mesh mesh to write
  159.      * @param output output to write to
  160.      * @throws java.io.UncheckedIOException if an I/O error occurs
  161.      */
  162.     private void writeTriangleMesh(final TriangleMesh mesh, final GeometryOutput output) {
  163.         try (BinaryStlWriter stlWriter = new BinaryStlWriter(output.getOutputStream())) {
  164.             // write the header
  165.             stlWriter.writeHeader(null, mesh.getFaceCount());

  166.             // write each triangle
  167.             Triangle3D tri;
  168.             for (final TriangleMesh.Face face : mesh.faces()) {
  169.                 tri = face.getPolygon();

  170.                 stlWriter.writeTriangle(
  171.                         tri.getPoint1(),
  172.                         tri.getPoint2(),
  173.                         tri.getPoint3(),
  174.                         tri.getPlane().getNormal());
  175.             }
  176.         }
  177.     }

  178.     /** Get the attribute value that should be used for the given facet.
  179.      * @param facet facet to get the attribute value for
  180.      * @return attribute value
  181.      */
  182.     private int getFacetAttributeValue(final FacetDefinition facet) {
  183.         if (facet instanceof BinaryStlFacetDefinition) {
  184.             return ((BinaryStlFacetDefinition) facet).getAttributeValue();
  185.         }

  186.         return 0;
  187.     }
  188. }