StlBoundaryWriteHandler3D.java
- /*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package org.apache.commons.geometry.io.euclidean.threed.stl;
- import java.io.ByteArrayOutputStream;
- import java.io.IOException;
- import java.io.OutputStream;
- import java.util.Iterator;
- import java.util.List;
- import java.util.stream.Stream;
- import org.apache.commons.geometry.euclidean.internal.EuclideanUtils;
- import org.apache.commons.geometry.euclidean.threed.BoundarySource3D;
- import org.apache.commons.geometry.euclidean.threed.PlaneConvexSubset;
- import org.apache.commons.geometry.euclidean.threed.Triangle3D;
- import org.apache.commons.geometry.euclidean.threed.Vector3D;
- import org.apache.commons.geometry.euclidean.threed.mesh.TriangleMesh;
- import org.apache.commons.geometry.io.core.GeometryFormat;
- import org.apache.commons.geometry.io.core.internal.GeometryIOUtils;
- import org.apache.commons.geometry.io.core.output.GeometryOutput;
- import org.apache.commons.geometry.io.euclidean.threed.AbstractBoundaryWriteHandler3D;
- import org.apache.commons.geometry.io.euclidean.threed.FacetDefinition;
- import org.apache.commons.geometry.io.euclidean.threed.GeometryFormat3D;
- /** {@link org.apache.commons.geometry.io.euclidean.threed.BoundaryWriteHandler3D BoundaryWriteHandler3D}
- * implementation for writing STL content. Because of its compact nature, all STL content is written in
- * binary format, as opposed the text (i.e. "ASCII") format. Callers should use the {@link TextStlWriter}
- * class directly in order to create text STL content.
- */
- public class StlBoundaryWriteHandler3D extends AbstractBoundaryWriteHandler3D {
- /** Initial size of the data buffer. */
- private static final int DEFAULT_BUFFER_SIZE = 1024 * StlConstants.BINARY_TRIANGLE_BYTES;
- /** Initial size of data buffers used during write operations. */
- private int initialBufferSize = DEFAULT_BUFFER_SIZE;
- /** {@inheritDoc} */
- @Override
- public GeometryFormat getFormat() {
- return GeometryFormat3D.STL;
- }
- /** Get the initial size of the data buffers used by this instance.
- *
- * <p>The buffer is used in situations where it is not clear how many
- * triangles will ultimately be written to the output. In these cases, the
- * triangle data is first written to an internal buffer. Once all triangles are
- * written, the STL header containing the total triangle count is written
- * to the output, followed by the buffered triangle data.</p>
- * @return initial buffer size
- */
- public int getinitialBufferSize() {
- return initialBufferSize;
- }
- /** Set the initial size of the data buffers used by this instance.
- *
- * <p>The buffer is used in situations where it is not clear how many
- * triangles will ultimately be written to the output. In these cases, the
- * triangle data is first written to an internal buffer. Once all triangles are
- * written, the STL header containing the total triangle count is written
- * to the output, followed by the buffered triangle data.</p>
- * @param initialBufferSize initial buffer size
- */
- public void setInitialBufferSize(final int initialBufferSize) {
- if (initialBufferSize < 1) {
- throw new IllegalArgumentException("Buffer size must be greater than 0");
- }
- this.initialBufferSize = initialBufferSize;
- }
- /** {@inheritDoc} */
- @Override
- public void write(final BoundarySource3D src, final GeometryOutput out) {
- // handle cases where we know the number of triangles to be written up front
- // and do not need to buffer the content
- if (src instanceof TriangleMesh) {
- writeTriangleMesh((TriangleMesh) src, out);
- } else {
- // unknown number of triangles; proceed with a buffered write
- super.write(src, out);
- }
- }
- /** {@inheritDoc} */
- @Override
- public void write(final Stream<? extends PlaneConvexSubset> boundaries, final GeometryOutput out) {
- // write the triangle data to a buffer and track how many we write
- int triangleCount = 0;
- final ByteArrayOutputStream triangleBuffer = new ByteArrayOutputStream(initialBufferSize);
- try (BinaryStlWriter stlWriter = new BinaryStlWriter(triangleBuffer)) {
- final Iterator<? extends PlaneConvexSubset> it = boundaries.iterator();
- while (it.hasNext()) {
- for (final Triangle3D tri : it.next().toTriangles()) {
- stlWriter.writeTriangle(
- tri.getPoint1(),
- tri.getPoint2(),
- tri.getPoint3(),
- tri.getPlane().getNormal());
- ++triangleCount;
- }
- }
- }
- // write the header and copy the data
- writeWithHeader(triangleBuffer, triangleCount, out);
- }
- /** {@inheritDoc} */
- @Override
- public void writeFacets(final Stream<? extends FacetDefinition> facets, final GeometryOutput out) {
- // write the triangle data to a buffer and track how many we write
- int triangleCount = 0;
- final ByteArrayOutputStream triangleBuffer = new ByteArrayOutputStream(initialBufferSize);
- try (BinaryStlWriter dataWriter = new BinaryStlWriter(triangleBuffer)) {
- final Iterator<? extends FacetDefinition> it = facets.iterator();
- FacetDefinition facet;
- int attributeValue;
- while (it.hasNext()) {
- facet = it.next();
- attributeValue = getFacetAttributeValue(facet);
- for (final List<Vector3D> tri :
- EuclideanUtils.convexPolygonToTriangleFan(facet.getVertices(), t -> t)) {
- dataWriter.writeTriangle(
- tri.get(0),
- tri.get(1),
- tri.get(2),
- facet.getNormal(),
- attributeValue);
- ++triangleCount;
- }
- }
- }
- // write the header and copy the data
- writeWithHeader(triangleBuffer, triangleCount, out);
- }
- /** Write the given triangle data prefixed with an STL header to the output stream from {@code out}.
- * @param triangleBuffer buffer containing STL triangle data
- * @param count number of triangles in {@code triangleBuffer}
- * @param out output to write to
- * @throws java.io.UncheckedIOException if an I/O error occurs
- */
- private void writeWithHeader(final ByteArrayOutputStream triangleBuffer, final int count,
- final GeometryOutput out) {
- // write the header and copy the data
- try (OutputStream os = out.getOutputStream()) {
- BinaryStlWriter.writeHeader(null, count, os);
- triangleBuffer.writeTo(os);
- } catch (IOException exc) {
- throw GeometryIOUtils.createUnchecked(exc);
- }
- }
- /** Write all triangles in the given mesh to the output using the binary STL
- * format.
- * @param mesh mesh to write
- * @param output output to write to
- * @throws java.io.UncheckedIOException if an I/O error occurs
- */
- private void writeTriangleMesh(final TriangleMesh mesh, final GeometryOutput output) {
- try (BinaryStlWriter stlWriter = new BinaryStlWriter(output.getOutputStream())) {
- // write the header
- stlWriter.writeHeader(null, mesh.getFaceCount());
- // write each triangle
- Triangle3D tri;
- for (final TriangleMesh.Face face : mesh.faces()) {
- tri = face.getPolygon();
- stlWriter.writeTriangle(
- tri.getPoint1(),
- tri.getPoint2(),
- tri.getPoint3(),
- tri.getPlane().getNormal());
- }
- }
- }
- /** Get the attribute value that should be used for the given facet.
- * @param facet facet to get the attribute value for
- * @return attribute value
- */
- private int getFacetAttributeValue(final FacetDefinition facet) {
- if (facet instanceof BinaryStlFacetDefinition) {
- return ((BinaryStlFacetDefinition) facet).getAttributeValue();
- }
- return 0;
- }
- }