001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.geometry.io.euclidean.threed.stl; 018 019import java.io.ByteArrayOutputStream; 020import java.io.IOException; 021import java.io.OutputStream; 022import java.util.Iterator; 023import java.util.List; 024import java.util.stream.Stream; 025 026import org.apache.commons.geometry.euclidean.internal.EuclideanUtils; 027import org.apache.commons.geometry.euclidean.threed.BoundarySource3D; 028import org.apache.commons.geometry.euclidean.threed.PlaneConvexSubset; 029import org.apache.commons.geometry.euclidean.threed.Triangle3D; 030import org.apache.commons.geometry.euclidean.threed.Vector3D; 031import org.apache.commons.geometry.euclidean.threed.mesh.TriangleMesh; 032import org.apache.commons.geometry.io.core.GeometryFormat; 033import org.apache.commons.geometry.io.core.internal.GeometryIOUtils; 034import org.apache.commons.geometry.io.core.output.GeometryOutput; 035import org.apache.commons.geometry.io.euclidean.threed.AbstractBoundaryWriteHandler3D; 036import org.apache.commons.geometry.io.euclidean.threed.FacetDefinition; 037import org.apache.commons.geometry.io.euclidean.threed.GeometryFormat3D; 038 039/** {@link org.apache.commons.geometry.io.euclidean.threed.BoundaryWriteHandler3D BoundaryWriteHandler3D} 040 * implementation for writing STL content. Because of its compact nature, all STL content is written in 041 * binary format, as opposed the text (i.e. "ASCII") format. Callers should use the {@link TextStlWriter} 042 * class directly in order to create text STL content. 043 */ 044public class StlBoundaryWriteHandler3D extends AbstractBoundaryWriteHandler3D { 045 046 /** Initial size of the data buffer. */ 047 private static final int DEFAULT_BUFFER_SIZE = 1024 * StlConstants.BINARY_TRIANGLE_BYTES; 048 049 /** Initial size of data buffers used during write operations. */ 050 private int initialBufferSize = DEFAULT_BUFFER_SIZE; 051 052 /** {@inheritDoc} */ 053 @Override 054 public GeometryFormat getFormat() { 055 return GeometryFormat3D.STL; 056 } 057 058 /** Get the initial size of the data buffers used by this instance. 059 * 060 * <p>The buffer is used in situations where it is not clear how many 061 * triangles will ultimately be written to the output. In these cases, the 062 * triangle data is first written to an internal buffer. Once all triangles are 063 * written, the STL header containing the total triangle count is written 064 * to the output, followed by the buffered triangle data.</p> 065 * @return initial buffer size 066 */ 067 public int getinitialBufferSize() { 068 return initialBufferSize; 069 } 070 071 /** Set the initial size of the data buffers used by this instance. 072 * 073 * <p>The buffer is used in situations where it is not clear how many 074 * triangles will ultimately be written to the output. In these cases, the 075 * triangle data is first written to an internal buffer. Once all triangles are 076 * written, the STL header containing the total triangle count is written 077 * to the output, followed by the buffered triangle data.</p> 078 * @param initialBufferSize initial buffer size 079 */ 080 public void setInitialBufferSize(final int initialBufferSize) { 081 if (initialBufferSize < 1) { 082 throw new IllegalArgumentException("Buffer size must be greater than 0"); 083 } 084 this.initialBufferSize = initialBufferSize; 085 } 086 087 /** {@inheritDoc} */ 088 @Override 089 public void write(final BoundarySource3D src, final GeometryOutput out) { 090 // handle cases where we know the number of triangles to be written up front 091 // and do not need to buffer the content 092 if (src instanceof TriangleMesh) { 093 writeTriangleMesh((TriangleMesh) src, out); 094 } else { 095 // unknown number of triangles; proceed with a buffered write 096 super.write(src, out); 097 } 098 } 099 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}