TextStlWriter.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.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 java.io.UncheckedIOException if an I/O error occurs
- */
- public void startSolid() {
- 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 IllegalArgumentException if {@code solidName} contains new line characters
- * @throws IllegalStateException if a solid definition has already been started
- * @throws java.io.UncheckedIOException if an I/O error occurs
- */
- public void startSolid(final String solidName) {
- 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 java.io.UncheckedIOException if an I/O error occurs
- */
- public void endSolid() {
- 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 java.io.UncheckedIOException if an I/O error occurs
- * @see PlaneConvexSubset#toTriangles()
- */
- public void writeTriangles(final PlaneConvexSubset boundary) {
- 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 java.io.UncheckedIOException if an I/O error occurs
- * @see #writeTriangle(Vector3D, Vector3D, Vector3D, Vector3D)
- */
- public void writeTriangles(final FacetDefinition facet) {
- 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 java.io.UncheckedIOException if an I/O error occurs
- */
- public void writeTriangles(final List<Vector3D> vertices, final Vector3D normal) {
- 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 java.io.UncheckedIOException if an I/O error occurs
- */
- public void writeTriangle(final Vector3D p1, final Vector3D p2, final Vector3D p3, final Vector3D normal) {
- 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() {
- if (started) {
- endSolid();
- }
- super.close();
- }
- /** Write a triangle vertex to the output.
- * @param vertex triangle vertex
- * @throws java.io.UncheckedIOException if an I/O error occurs
- */
- private void writeTriangleVertex(final Vector3D vertex) {
- write(StlConstants.VERTEX_KEYWORD);
- write(SPACE);
- writeVector(vertex);
- writeNewLine();
- }
- /** Write a vector to the output.
- * @param vec vector to write
- * @throws java.io.UncheckedIOException if an I/O error occurs
- */
- private void writeVector(final Vector3D vec) {
- 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 java.io.UncheckedIOException if an I/O error occurs
- */
- private void writeBeginOrEndLine(final String keyword) {
- write(keyword);
- write(SPACE);
- if (name != null) {
- write(name);
- }
- writeNewLine();
- }
- }