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.Writer;
    20  import java.util.List;
    21
    22  import org.apache.commons.geometry.euclidean.internal.EuclideanUtils;
    23  import org.apache.commons.geometry.euclidean.threed.PlaneConvexSubset;
    24  import org.apache.commons.geometry.euclidean.threed.Triangle3D;
    25  import org.apache.commons.geometry.euclidean.threed.Vector3D;
    26  import org.apache.commons.geometry.io.core.utils.AbstractTextFormatWriter;
    27  import org.apache.commons.geometry.io.euclidean.threed.FacetDefinition;
    28
    29  /** Class for writing the text-based (i.e., "ASCII") STL format.
    30   * @see <a href="https://en.wikipedia.org/wiki/STL_%28file_format%29#ASCII_STL">ASCII STL</a>
    31   */
    32  public class TextStlWriter extends AbstractTextFormatWriter {
    33
    34      /** Space character. */
    35      private static final char SPACE = ' ';
    36
    37      /** Name of the current STL solid. */
    38      private String name;
    39
    40      /** True if an STL solid definition has been written. */
    41      private boolean started;
    42
    43      /** Construct a new instance for writing STL content to the given writer.
    44       * @param writer writer to write to
    45       */
    46      public TextStlWriter(final Writer writer) {
    47          super(writer);
    48      }
    49
    50      /** Write the start of an unnamed STL solid definition. This method is equivalent to calling
    51       * {@code stlWriter.startSolid(null);}
    52       * @throws java.io.UncheckedIOException if an I/O error occurs
    53       */
    54      public void startSolid() {
    55          startSolid(null);
    56      }
    57
    58      /** Write the start of an STL solid definition with the given name.
    59       * @param solidName the name of the solid; may be null
    60       * @throws IllegalArgumentException if {@code solidName} contains new line characters
    61       * @throws IllegalStateException if a solid definition has already been started
    62       * @throws java.io.UncheckedIOException if an I/O error occurs
    63       */
    64      public void startSolid(final String solidName) {
    65          if (started) {
    66              throw new IllegalStateException("Cannot start solid definition: a solid is already being written");
    67          }
    68          if (solidName != null && (solidName.indexOf('\r') > -1 || solidName.indexOf('\n') > -1)) {
    69              throw new IllegalArgumentException("Solid name cannot contain new line characters");
    70          }
    71
    72          name = solidName;
    73          writeBeginOrEndLine(StlConstants.SOLID_START_KEYWORD);
    74
    75          started = true;
    76      }
    77
    78      /** Write the end of the current STL solid definition. This method is called automatically on
    79       * {@link #close()} if needed.
    80       * @throws IllegalStateException if no solid definition has been started
    81       * @throws java.io.UncheckedIOException if an I/O error occurs
    82       */
    83      public void endSolid() {
    84          if (!started) {
    85              throw new IllegalStateException("Cannot end solid definition: no solid has been started");
    86          }
    87
    88          writeBeginOrEndLine(StlConstants.SOLID_END_KEYWORD);
    89          name = null;
    90          started = false;
    91      }
    92
    93      /** Write the given boundary to the output as triangles.
    94       * @param boundary boundary to write
    95       * @throws IllegalStateException if no solid has been started yet
    96       * @throws java.io.UncheckedIOException if an I/O error occurs
    97       * @see PlaneConvexSubset#toTriangles()
    98       */
    99      public void writeTriangles(final PlaneConvexSubset boundary) {
    100         for (final Triangle3D tri : boundary.toTriangles()) {
    101             writeTriangles(tri.getVertices(), tri.getPlane().getNormal());
    102         }
    103     }
    104
    105     /** Write the given facet definition to the output as triangles.
    106      * @param facet facet definition to write
    107      * @throws IllegalStateException if no solid has been started yet
    108      * @throws java.io.UncheckedIOException if an I/O error occurs
    109      * @see #writeTriangle(Vector3D, Vector3D, Vector3D, Vector3D)
    110      */
    111     public void writeTriangles(final FacetDefinition facet) {
    112         writeTriangles(facet.getVertices(), facet.getNormal());
    113     }
    114
    115     /** Write the facet defined by the given vertices and normal to the output as triangles.
    116      * If the the given list of vertices contains more than 3 vertices, it is converted to
    117      * triangles using a triangle fan. Callers are responsible for ensuring that the given
    118      * vertices represent a valid convex polygon.
    119      *
    120      * <p>If a non-zero normal is given, the vertices are ordered using the right-hand rule,
    121      * meaning that they will be in a counter-clockwise orientation when looking down
    122      * the normal. If no normal is given, or the given value cannot be normalized, a normal
    123      * is computed from the triangle vertices, also using the right-hand rule. If this also
    124      * fails (for example, if the triangle vertices do not define a plane), then the
    125      * zero vector is used.</p>
    126      * @param vertices vertices defining the facet
    127      * @param normal facet normal; may be null
    128      * @throws IllegalStateException if no solid has been started yet or fewer than 3 vertices
    129      *      are given
    130      * @throws java.io.UncheckedIOException if an I/O error occurs
    131      */
    132     public void writeTriangles(final List<Vector3D> vertices, final Vector3D normal) {
    133         for (final List<Vector3D> triangle : EuclideanUtils.convexPolygonToTriangleFan(vertices, t -> t)) {
    134             writeTriangle(
    135                     triangle.get(0),
    136                     triangle.get(1),
    137                     triangle.get(2),
    138                     normal);
    139         }
    140     }
    141
    142     /** Write a triangle to the output.
    143      *
    144      * <p>If a non-zero normal is given, the vertices are ordered using the right-hand rule,
    145      * meaning that they will be in a counter-clockwise orientation when looking down
    146      * the normal. If no normal is given, or the given value cannot be normalized, a normal
    147      * is computed from the triangle vertices, also using the right-hand rule. If this also
    148      * fails (for example, if the triangle vertices do not define a plane), then the
    149      * zero vector is used.</p>
    150      * @param p1 first point
    151      * @param p2 second point
    152      * @param p3 third point
    153      * @param normal facet normal; may be null
    154      * @throws IllegalStateException if no solid has been started yet
    155      * @throws java.io.UncheckedIOException if an I/O error occurs
    156      */
    157     public void writeTriangle(final Vector3D p1, final Vector3D p2, final Vector3D p3, final Vector3D normal) {
    158         if (!started) {
    159             throw new IllegalStateException("Cannot write triangle: no solid has been started");
    160         }
    161
    162         write(StlConstants.FACET_START_KEYWORD);
    163         write(SPACE);
    164         writeVector(StlUtils.determineNormal(p1, p2, p3, normal));
    165         writeNewLine();
    166
    167         write(StlConstants.OUTER_KEYWORD);
    168         write(SPACE);
    169         write(StlConstants.LOOP_START_KEYWORD);
    170         writeNewLine();
    171
    172         writeTriangleVertex(p1);
    173
    174         if (StlUtils.pointsAreCounterClockwise(p1, p2, p3, normal)) {
    175             writeTriangleVertex(p2);
    176             writeTriangleVertex(p3);
    177         } else {
    178             writeTriangleVertex(p3);
    179             writeTriangleVertex(p2);
    180         }
    181
    182         write(StlConstants.LOOP_END_KEYWORD);
    183         writeNewLine();
    184
    185         write(StlConstants.FACET_END_KEYWORD);
    186         writeNewLine();
    187     }
    188
    189     /** {@inheritDoc} */
    190     @Override
    191     public void close() {
    192         if (started) {
    193             endSolid();
    194         }
    195
    196         super.close();
    197     }
    198
    199     /** Write a triangle vertex to the output.
    200      * @param vertex triangle vertex
    201      * @throws java.io.UncheckedIOException if an I/O error occurs
    202      */
    203     private void writeTriangleVertex(final Vector3D vertex) {
    204         write(StlConstants.VERTEX_KEYWORD);
    205         write(SPACE);
    206         writeVector(vertex);
    207         writeNewLine();
    208     }
    209
    210     /** Write a vector to the output.
    211      * @param vec vector to write
    212      * @throws java.io.UncheckedIOException if an I/O error occurs
    213      */
    214     private void writeVector(final Vector3D vec) {
    215         write(vec.getX());
    216         write(SPACE);
    217         write(vec.getY());
    218         write(SPACE);
    219         write(vec.getZ());
    220     }
    221
    222     /** Write the beginning or ending line of the solid definition.
    223      * @param keyword keyword at the start of the line
    224      * @throws java.io.UncheckedIOException if an I/O error occurs
    225      */
    226     private void writeBeginOrEndLine(final String keyword) {
    227         write(keyword);
    228         write(SPACE);
    229
    230         if (name != null) {
    231             write(name);
    232         }
    233
    234         writeNewLine();
    235     }
    236 }