| /* |
| * 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.IOException; |
| import java.io.Writer; |
| import java.util.List; |
| |
| import org.apache.commons.geometry.euclidean.internal.EuclideanUtils; |
| 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.io.core.utils.AbstractTextFormatWriter; |
| import org.apache.commons.geometry.io.euclidean.threed.FacetDefinition; |
| |
| /** Class for writing the text-based (i.e., "ASCII") STL format. |
| * @see <a href="https://en.wikipedia.org/wiki/STL_%28file_format%29#ASCII_STL">ASCII STL</a> |
| */ |
| public class TextStlWriter extends AbstractTextFormatWriter { |
| |
| /** Space character. */ |
| private static final char SPACE = ' '; |
| |
| /** Name of the current STL solid. */ |
| private String name; |
| |
| /** True if an STL solid definition has been written. */ |
| private boolean started; |
| |
| /** Construct a new instance for writing STL content to the given writer. |
| * @param writer writer to write to |
| */ |
| public TextStlWriter(final Writer writer) { |
| super(writer); |
| } |
| |
| /** Write the start of an unnamed STL solid definition. This method is equivalent to calling |
| * {@code stlWriter.startSolid(null);} |
| * @throws IOException if an I/O error occurs |
| */ |
| public void startSolid() throws IOException { |
| startSolid(null); |
| } |
| |
| /** Write the start of an STL solid definition with the given name. |
| * @param solidName the name of the solid; may be null |
| * @throws IllegalStateException if a solid definition has already been started |
| * @throws IllegalArgumentException if {@code solidName} contains new line characters |
| * @throws IOException if an I/O error occurs |
| */ |
| public void startSolid(final String solidName) throws IOException { |
| if (started) { |
| throw new IllegalStateException("Cannot start solid definition: a solid is already being written"); |
| } |
| if (solidName != null && (solidName.indexOf('\r') > -1 || solidName.indexOf('\n') > -1)) { |
| throw new IllegalArgumentException("Solid name cannot contain new line characters"); |
| } |
| |
| name = solidName; |
| writeBeginOrEndLine(StlConstants.SOLID_START_KEYWORD); |
| |
| started = true; |
| } |
| |
| /** Write the end of the current STL solid definition. This method is called automatically on |
| * {@link #close()} if needed. |
| * @throws IllegalStateException if no solid definition has been started |
| * @throws IOException if an I/O error occurs |
| */ |
| public void endSolid() throws IOException { |
| if (!started) { |
| throw new IllegalStateException("Cannot end solid definition: no solid has been started"); |
| } |
| |
| writeBeginOrEndLine(StlConstants.SOLID_END_KEYWORD); |
| name = null; |
| started = false; |
| } |
| |
| /** Write the given boundary to the output as triangles. |
| * @param boundary boundary to write |
| * @throws IllegalStateException if no solid has been started yet |
| * @throws IOException if an I/O error occurs |
| * @see PlaneConvexSubset#toTriangles() |
| */ |
| public void writeTriangles(final PlaneConvexSubset boundary) throws IOException { |
| for (final Triangle3D tri : boundary.toTriangles()) { |
| writeTriangles(tri.getVertices(), tri.getPlane().getNormal()); |
| } |
| } |
| |
| /** Write the given facet definition to the output as triangles. |
| * @param facet facet definition to write |
| * @throws IllegalStateException if no solid has been started yet |
| * @throws IOException if an I/O error occurs |
| * @see #writeTriangle(Vector3D, Vector3D, Vector3D, Vector3D) |
| */ |
| public void writeTriangles(final FacetDefinition facet) throws IOException { |
| writeTriangles(facet.getVertices(), facet.getNormal()); |
| } |
| |
| /** Write the facet defined by the given vertices and normal to the output as triangles. |
| * If the the given list of vertices contains more than 3 vertices, it is converted to |
| * triangles using a triangle fan. Callers are responsible for ensuring that the given |
| * vertices represent a valid convex polygon. |
| * |
| * <p>If a non-zero normal is given, the vertices are ordered using the right-hand rule, |
| * meaning that they will be in a counter-clockwise orientation when looking down |
| * the normal. If no normal is given, or the given value cannot be normalized, a normal |
| * is computed from the triangle vertices, also using the right-hand rule. If this also |
| * fails (for example, if the triangle vertices do not define a plane), then the |
| * zero vector is used.</p> |
| * @param vertices vertices defining the facet |
| * @param normal facet normal; may be null |
| * @throws IllegalStateException if no solid has been started yet or fewer than 3 vertices |
| * are given |
| * @throws IOException if an I/O error occurs |
| */ |
| public void writeTriangles(final List<Vector3D> vertices, final Vector3D normal) throws IOException { |
| for (final List<Vector3D> triangle : EuclideanUtils.convexPolygonToTriangleFan(vertices, t -> t)) { |
| writeTriangle( |
| triangle.get(0), |
| triangle.get(1), |
| triangle.get(2), |
| normal); |
| } |
| } |
| |
| /** Write a triangle to the output. |
| * |
| * <p>If a non-zero normal is given, the vertices are ordered using the right-hand rule, |
| * meaning that they will be in a counter-clockwise orientation when looking down |
| * the normal. If no normal is given, or the given value cannot be normalized, a normal |
| * is computed from the triangle vertices, also using the right-hand rule. If this also |
| * fails (for example, if the triangle vertices do not define a plane), then the |
| * zero vector is used.</p> |
| * @param p1 first point |
| * @param p2 second point |
| * @param p3 third point |
| * @param normal facet normal; may be null |
| * @throws IllegalStateException if no solid has been started yet |
| * @throws IOException if an I/O error occurs |
| */ |
| public void writeTriangle(final Vector3D p1, final Vector3D p2, final Vector3D p3, final Vector3D normal) |
| throws IOException { |
| if (!started) { |
| throw new IllegalStateException("Cannot write triangle: no solid has been started"); |
| } |
| |
| write(StlConstants.FACET_START_KEYWORD); |
| write(SPACE); |
| writeVector(StlUtils.determineNormal(p1, p2, p3, normal)); |
| writeNewLine(); |
| |
| write(StlConstants.OUTER_KEYWORD); |
| write(SPACE); |
| write(StlConstants.LOOP_START_KEYWORD); |
| writeNewLine(); |
| |
| writeTriangleVertex(p1); |
| |
| if (StlUtils.pointsAreCounterClockwise(p1, p2, p3, normal)) { |
| writeTriangleVertex(p2); |
| writeTriangleVertex(p3); |
| } else { |
| writeTriangleVertex(p3); |
| writeTriangleVertex(p2); |
| } |
| |
| write(StlConstants.LOOP_END_KEYWORD); |
| writeNewLine(); |
| |
| write(StlConstants.FACET_END_KEYWORD); |
| writeNewLine(); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public void close() throws IOException { |
| if (started) { |
| endSolid(); |
| } |
| |
| super.close(); |
| } |
| |
| /** Write a triangle vertex to the output. |
| * @param vertex triangle vertex |
| * @throws IOException if an I/O error occurs |
| */ |
| private void writeTriangleVertex(final Vector3D vertex) throws IOException { |
| write(StlConstants.VERTEX_KEYWORD); |
| write(SPACE); |
| writeVector(vertex); |
| writeNewLine(); |
| } |
| |
| /** Write a vector to the output. |
| * @param vec vector to write |
| * @throws IOException if an I/O error occurs |
| */ |
| private void writeVector(final Vector3D vec) throws IOException { |
| write(vec.getX()); |
| write(SPACE); |
| write(vec.getY()); |
| write(SPACE); |
| write(vec.getZ()); |
| } |
| |
| /** Write the beginning or ending line of the solid definition. |
| * @param keyword keyword at the start of the line |
| * @throws IOException if an I/O error occurs |
| */ |
| private void writeBeginOrEndLine(final String keyword) throws IOException { |
| write(keyword); |
| write(SPACE); |
| |
| if (name != null) { |
| write(name); |
| } |
| |
| writeNewLine(); |
| } |
| } |