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.StringWriter;
    20  import java.text.DecimalFormat;
    21  import java.text.DecimalFormatSymbols;
    22  import java.util.Arrays;
    23  import java.util.Collections;
    24  import java.util.List;
    25  import java.util.Locale;
    26
    27  import org.apache.commons.geometry.core.GeometryTestUtils;
    28  import org.apache.commons.geometry.euclidean.threed.ConvexPolygon3D;
    29  import org.apache.commons.geometry.euclidean.threed.Planes;
    30  import org.apache.commons.geometry.euclidean.threed.Vector3D;
    31  import org.apache.commons.geometry.io.core.test.CloseCountWriter;
    32  import org.apache.commons.geometry.io.euclidean.threed.FacetDefinition;
    33  import org.apache.commons.geometry.io.euclidean.threed.SimpleFacetDefinition;
    34  import org.apache.commons.numbers.core.Precision;
    35  import org.junit.jupiter.api.Assertions;
    36  import org.junit.jupiter.api.Test;
    37
    38  class TextStlWriterTest {
    39
    40      private static final double TEST_EPS = 1e-10;
    41
    42      private static final Precision.DoubleEquivalence TEST_PRECISION =
    43              Precision.doubleEquivalenceOfEpsilon(TEST_EPS);
    44
    45      private final StringWriter out = new StringWriter();
    46
    47      @Test
    48      void testDefaultProperties() {
    49          // act/assert
    50          try (TextStlWriter writer = new TextStlWriter(out)) {
    51              Assertions.assertNotNull(writer.getDoubleFormat());
    52              Assertions.assertEquals("\n", writer.getLineSeparator());
    53          }
    54      }
    55
    56      @Test
    57      void testNoContent() {
    58          // arrange
    59          final CloseCountWriter countWriter = new CloseCountWriter(out);
    60
    61          // act
    62          try (TextStlWriter writer = new TextStlWriter(countWriter)) {
    63              Assertions.assertEquals(0, countWriter.getCloseCount());
    64          }
    65
    66          // assert
    67          Assertions.assertEquals(1, countWriter.getCloseCount());
    68          Assertions.assertEquals("", out.toString());
    69      }
    70
    71      @Test
    72      void testStartSolid_alreadyStarted() {
    73          // arrange
    74          try (TextStlWriter writer = new TextStlWriter(out)) {
    75              writer.startSolid();
    76
    77              // act/assert
    78              GeometryTestUtils.assertThrowsWithMessage(
    79                      () -> writer.startSolid(),
    80                      IllegalStateException.class, "Cannot start solid definition: a solid is already being written");
    81          }
    82      }
    83
    84      @Test
    85      void testEndSolid_notStarted() {
    86          // arrange
    87          try (TextStlWriter writer = new TextStlWriter(out)) {
    88              // act/assert
    89              GeometryTestUtils.assertThrowsWithMessage(
    90                      () -> writer.endSolid(),
    91                      IllegalStateException.class, "Cannot end solid definition: no solid has been started");
    92          }
    93      }
    94
    95      @Test
    96      void testEmpty_noName() {
    97          // arrange
    98          final CloseCountWriter countWriter = new CloseCountWriter(out);
    99
    100         // act
    101         try (TextStlWriter writer = new TextStlWriter(countWriter)) {
    102             writer.startSolid();
    103             writer.endSolid();
    104
    105             Assertions.assertEquals(0, countWriter.getCloseCount());
    106         }
    107
    108         // assert
    109         Assertions.assertEquals(1, countWriter.getCloseCount());
    110         Assertions.assertEquals(
    111                 "solid \n" +
    112                 "endsolid \n", out.toString());
    113     }
    114
    115     @Test
    116     void testEmpty_withName() {
    117         // arrange
    118         final CloseCountWriter countWriter = new CloseCountWriter(out);
    119
    120         // act
    121         try (TextStlWriter writer = new TextStlWriter(countWriter)) {
    122             writer.startSolid("Name of the solid");
    123             writer.endSolid();
    124
    125             Assertions.assertEquals(0, countWriter.getCloseCount());
    126         }
    127
    128         // assert
    129         Assertions.assertEquals(1, countWriter.getCloseCount());
    130         Assertions.assertEquals(
    131                 "solid Name of the solid\n" +
    132                 "endsolid Name of the solid\n", out.toString());
    133     }
    134
    135     @Test
    136     void testClose_endsSolid() {
    137         // arrange
    138         final CloseCountWriter countWriter = new CloseCountWriter(out);
    139
    140         // act
    141         try (TextStlWriter writer = new TextStlWriter(countWriter)) {
    142             writer.startSolid("name");
    143
    144             Assertions.assertEquals(0, countWriter.getCloseCount());
    145         }
    146
    147         // assert
    148         Assertions.assertEquals(1, countWriter.getCloseCount());
    149         Assertions.assertEquals(
    150                 "solid name\n" +
    151                 "endsolid name\n", out.toString());
    152     }
    153
    154     @Test
    155     void testStartSolid_containsNewLine() {
    156         // arrange
    157         try (TextStlWriter writer = new TextStlWriter(out)) {
    158             final String err = "Solid name cannot contain new line characters";
    159
    160             // act/assert
    161             GeometryTestUtils.assertThrowsWithMessage(
    162                     () -> writer.startSolid("Hi\nthere"),
    163                     IllegalArgumentException.class, err);
    164             GeometryTestUtils.assertThrowsWithMessage(
    165                     () -> writer.startSolid("Hi\r\nthere"),
    166                     IllegalArgumentException.class, err);
    167             GeometryTestUtils.assertThrowsWithMessage(
    168                     () -> writer.startSolid("Hi\rthere"),
    169                     IllegalArgumentException.class, err);
    170         }
    171     }
    172
    173     @Test
    174     void testWriteTriangle_noNormal_computesNormal() {
    175         // arrange
    176         final Vector3D p1 = Vector3D.of(0, 4, 0);
    177         final Vector3D p2 = Vector3D.of(1.0 / 3.0, 0, 0);
    178         final Vector3D p3 = Vector3D.of(0, 0.5, 10);
    179
    180         // act
    181         try (TextStlWriter writer = new TextStlWriter(out)) {
    182             writer.startSolid();
    183             writer.writeTriangle(p1, p2, p3, null);
    184         }
    185
    186         // assert
    187         Assertions.assertEquals(
    188             "solid \n" +
    189             "facet -0.9961250701090868 -0.08301042250909056 -0.029053647878181696\n" +
    190             "outer loop\n" +
    191             "vertex 0.0 4.0 0.0\n" +
    192             "vertex 0.3333333333333333 0.0 0.0\n" +
    193             "vertex 0.0 0.5 10.0\n" +
    194             "endloop\n" +
    195             "endfacet\n" +
    196             "endsolid \n", out.toString());
    197     }
    198
    199     @Test
    200     void testWriteTriangle_zeroNormal_computesNormal() {
    201         // arrange
    202         final Vector3D p1 = Vector3D.of(0, 4, 0);
    203         final Vector3D p2 = Vector3D.of(1.0 / 3.0, 0, 0);
    204         final Vector3D p3 = Vector3D.of(0, 0.5, 10);
    205
    206         // act
    207         try (TextStlWriter writer = new TextStlWriter(out)) {
    208             writer.startSolid();
    209             writer.writeTriangle(p1, p2, p3, Vector3D.ZERO);
    210         }
    211
    212         // assert
    213         Assertions.assertEquals(
    214             "solid \n" +
    215             "facet -0.9961250701090868 -0.08301042250909056 -0.029053647878181696\n" +
    216             "outer loop\n" +
    217             "vertex 0.0 4.0 0.0\n" +
    218             "vertex 0.3333333333333333 0.0 0.0\n" +
    219             "vertex 0.0 0.5 10.0\n" +
    220             "endloop\n" +
    221             "endfacet\n" +
    222             "endsolid \n", out.toString());
    223     }
    224
    225     @Test
    226     void testWriteTriangle_noNormal_cannotComputeNormal() {
    227         // arrange
    228         final Vector3D p1 = Vector3D.ZERO;
    229         final Vector3D p2 = Vector3D.of(1.0 / 3.0, 0, 0);
    230         final Vector3D p3 = Vector3D.ZERO;
    231
    232         // act
    233         try (TextStlWriter writer = new TextStlWriter(out)) {
    234             writer.startSolid();
    235             writer.writeTriangle(p1, p2, p3, null);
    236         }
    237
    238         // assert
    239         Assertions.assertEquals(
    240             "solid \n" +
    241             "facet 0.0 0.0 0.0\n" +
    242             "outer loop\n" +
    243             "vertex 0.0 0.0 0.0\n" +
    244             "vertex 0.3333333333333333 0.0 0.0\n" +
    245             "vertex 0.0 0.0 0.0\n" +
    246             "endloop\n" +
    247             "endfacet\n" +
    248             "endsolid \n", out.toString());
    249     }
    250
    251     @Test
    252     void testWriteTriangle_withNormal_correctOrientation() {
    253         // arrange
    254         final Vector3D p1 = Vector3D.of(0, 4, 0);
    255         final Vector3D p2 = Vector3D.of(1.0 / 3.0, 0, 0);
    256         final Vector3D p3 = Vector3D.of(0, 0.5, 10);
    257
    258         final Vector3D normal = p1.vectorTo(p2).cross(p1.vectorTo(p3)).normalize();
    259
    260         // act
    261         try (TextStlWriter writer = new TextStlWriter(out)) {
    262             writer.startSolid();
    263             writer.writeTriangle(p1, p2, p3, normal);
    264         }
    265
    266         // assert
    267         Assertions.assertEquals(
    268             "solid \n" +
    269             "facet -0.9961250701090868 -0.08301042250909056 -0.029053647878181696\n" +
    270             "outer loop\n" +
    271             "vertex 0.0 4.0 0.0\n" +
    272             "vertex 0.3333333333333333 0.0 0.0\n" +
    273             "vertex 0.0 0.5 10.0\n" +
    274             "endloop\n" +
    275             "endfacet\n" +
    276             "endsolid \n", out.toString());
    277     }
    278
    279     @Test
    280     void testWriteTriangle_withNormal_reversedOrientation() {
    281         // arrange
    282         final Vector3D p1 = Vector3D.of(0, 4, 0);
    283         final Vector3D p2 = Vector3D.of(1.0 / 3.0, 0, 0);
    284         final Vector3D p3 = Vector3D.of(0, 0.5, 10);
    285
    286         final Vector3D normal = p1.vectorTo(p2).cross(p1.vectorTo(p3)).normalize();
    287
    288         // act
    289         try (TextStlWriter writer = new TextStlWriter(out)) {
    290             writer.startSolid();
    291             writer.writeTriangle(p1, p2, p3, normal.negate());
    292         }
    293
    294         // assert
    295         Assertions.assertEquals(
    296             "solid \n" +
    297             "facet 0.9961250701090868 0.08301042250909056 0.029053647878181696\n" +
    298             "outer loop\n" +
    299             "vertex 0.0 4.0 0.0\n" +
    300             "vertex 0.0 0.5 10.0\n" +
    301             "vertex 0.3333333333333333 0.0 0.0\n" +
    302             "endloop\n" +
    303             "endfacet\n" +
    304             "endsolid \n", out.toString());
    305     }
    306
    307     @Test
    308     void testWrite_verticesAndNormal() {
    309         // arrange
    310         final List<Vector3D> vertices = Arrays.asList(
    311                 Vector3D.ZERO, Vector3D.of(1, 0, 0), Vector3D.of(0, 1, 0));
    312         final Vector3D n1 = Vector3D.of(0, 0, 100);
    313         final Vector3D n2 = Vector3D.Unit.MINUS_Z;
    314
    315         // act
    316         try (TextStlWriter writer = new TextStlWriter(out)) {
    317             writer.startSolid();
    318
    319             writer.writeTriangles(vertices, n1);
    320             writer.writeTriangles(vertices, n2);
    321             writer.writeTriangles(vertices, null);
    322         }
    323
    324         // assert
    325         Assertions.assertEquals(
    326             "solid \n" +
    327             "facet 0.0 0.0 1.0\n" +
    328             "outer loop\n" +
    329             "vertex 0.0 0.0 0.0\n" +
    330             "vertex 1.0 0.0 0.0\n" +
    331             "vertex 0.0 1.0 0.0\n" +
    332             "endloop\n" +
    333             "endfacet\n" +
    334             "facet 0.0 0.0 -1.0\n" +
    335             "outer loop\n" +
    336             "vertex 0.0 0.0 0.0\n" +
    337             "vertex 0.0 1.0 0.0\n" +
    338             "vertex 1.0 0.0 0.0\n" +
    339             "endloop\n" +
    340             "endfacet\n" +
    341             "facet 0.0 0.0 1.0\n" +
    342             "outer loop\n" +
    343             "vertex 0.0 0.0 0.0\n" +
    344             "vertex 1.0 0.0 0.0\n" +
    345             "vertex 0.0 1.0 0.0\n" +
    346             "endloop\n" +
    347             "endfacet\n" +
    348             "endsolid \n", out.toString());
    349     }
    350
    351     @Test
    352     void testWrite_verticesAndNormal_moreThanThreeVertices() {
    353         // arrange
    354         final List<Vector3D> vertices = Arrays.asList(
    355                 Vector3D.ZERO, Vector3D.of(1, 0, 0), Vector3D.of(1, 1, 0), Vector3D.of(0, 1, 0));
    356         final Vector3D normal = Vector3D.Unit.PLUS_Z;
    357
    358         // act
    359         try (TextStlWriter writer = new TextStlWriter(out)) {
    360             writer.startSolid();
    361
    362             writer.writeTriangles(vertices, normal);
    363         }
    364
    365         // assert
    366         Assertions.assertEquals(
    367             "solid \n" +
    368             "facet 0.0 0.0 1.0\n" +
    369             "outer loop\n" +
    370             "vertex 0.0 0.0 0.0\n" +
    371             "vertex 1.0 0.0 0.0\n" +
    372             "vertex 1.0 1.0 0.0\n" +
    373             "endloop\n" +
    374             "endfacet\n" +
    375             "facet 0.0 0.0 1.0\n" +
    376             "outer loop\n" +
    377             "vertex 0.0 0.0 0.0\n" +
    378             "vertex 1.0 1.0 0.0\n" +
    379             "vertex 0.0 1.0 0.0\n" +
    380             "endloop\n" +
    381             "endfacet\n" +
    382             "endsolid \n", out.toString());
    383     }
    384
    385     @Test
    386     void testWrite_verticesAndNormal_fewerThanThreeVertices() {
    387         // arrange
    388         try (TextStlWriter writer = new TextStlWriter(out)) {
    389             writer.startSolid();
    390
    391             final List<Vector3D> noElements = Collections.emptyList();
    392             final List<Vector3D> singleElement = Collections.singletonList(Vector3D.ZERO);
    393             final List<Vector3D> twoElements = Arrays.asList(Vector3D.ZERO, Vector3D.of(1, 1, 1));
    394
    395             // act/assert
    396             Assertions.assertThrows(IllegalArgumentException.class,
    397                     () -> writer.writeTriangles(noElements, null));
    398             Assertions.assertThrows(IllegalArgumentException.class,
    399                     () -> writer.writeTriangles(singleElement, null));
    400             Assertions.assertThrows(IllegalArgumentException.class,
    401                     () -> writer.writeTriangles(twoElements, null));
    402         }
    403     }
    404
    405     @Test
    406     void testWrite_boundary() {
    407         // arrange
    408         final ConvexPolygon3D boundary = Planes.convexPolygonFromVertices(
    409                 Arrays.asList(Vector3D.ZERO, Vector3D.of(1, 0, 0), Vector3D.of(1, 0, 1), Vector3D.of(0, 0, 1)),
    410                 TEST_PRECISION);
    411
    412         // act
    413         try (TextStlWriter writer = new TextStlWriter(out)) {
    414             writer.startSolid();
    415
    416             writer.writeTriangles(boundary);
    417         }
    418
    419         // assert
    420         Assertions.assertEquals(
    421             "solid \n" +
    422             "facet 0.0 -1.0 0.0\n" +
    423             "outer loop\n" +
    424             "vertex 0.0 0.0 0.0\n" +
    425             "vertex 1.0 0.0 0.0\n" +
    426             "vertex 1.0 0.0 1.0\n" +
    427             "endloop\n" +
    428             "endfacet\n" +
    429             "facet 0.0 -1.0 0.0\n" +
    430             "outer loop\n" +
    431             "vertex 0.0 0.0 0.0\n" +
    432             "vertex 1.0 0.0 1.0\n" +
    433             "vertex 0.0 0.0 1.0\n" +
    434             "endloop\n" +
    435             "endfacet\n" +
    436             "endsolid \n", out.toString());
    437     }
    438
    439     @Test
    440     void testWrite_facetDefinition_noNormal() {
    441         // arrange
    442         final FacetDefinition facet = new SimpleFacetDefinition(Arrays.asList(
    443                 Vector3D.ZERO, Vector3D.of(1, 0, 0), Vector3D.of(1, 1, 0), Vector3D.of(0, 1, 0)));
    444
    445         // act
    446         try (TextStlWriter writer = new TextStlWriter(out)) {
    447             writer.startSolid();
    448
    449             writer.writeTriangles(facet);
    450         }
    451
    452         // assert
    453         Assertions.assertEquals(
    454             "solid \n" +
    455             "facet 0.0 0.0 1.0\n" +
    456             "outer loop\n" +
    457             "vertex 0.0 0.0 0.0\n" +
    458             "vertex 1.0 0.0 0.0\n" +
    459             "vertex 1.0 1.0 0.0\n" +
    460             "endloop\n" +
    461             "endfacet\n" +
    462             "facet 0.0 0.0 1.0\n" +
    463             "outer loop\n" +
    464             "vertex 0.0 0.0 0.0\n" +
    465             "vertex 1.0 1.0 0.0\n" +
    466             "vertex 0.0 1.0 0.0\n" +
    467             "endloop\n" +
    468             "endfacet\n" +
    469             "endsolid \n", out.toString());
    470     }
    471
    472     @Test
    473     void testWrite_facetDefinition_withNormal() {
    474         // arrange
    475         final Vector3D normal = Vector3D.Unit.PLUS_Z;
    476         final FacetDefinition facet = new SimpleFacetDefinition(Arrays.asList(
    477                 Vector3D.ZERO, Vector3D.of(1, 0, 0), Vector3D.of(1, 1, 0), Vector3D.of(0, 1, 0)),
    478                 normal);
    479
    480         // act
    481         try (TextStlWriter writer = new TextStlWriter(out)) {
    482             writer.startSolid();
    483
    484             writer.writeTriangles(facet);
    485         }
    486
    487         // assert
    488         Assertions.assertEquals(
    489             "solid \n" +
    490             "facet 0.0 0.0 1.0\n" +
    491             "outer loop\n" +
    492             "vertex 0.0 0.0 0.0\n" +
    493             "vertex 1.0 0.0 0.0\n" +
    494             "vertex 1.0 1.0 0.0\n" +
    495             "endloop\n" +
    496             "endfacet\n" +
    497             "facet 0.0 0.0 1.0\n" +
    498             "outer loop\n" +
    499             "vertex 0.0 0.0 0.0\n" +
    500             "vertex 1.0 1.0 0.0\n" +
    501             "vertex 0.0 1.0 0.0\n" +
    502             "endloop\n" +
    503             "endfacet\n" +
    504             "endsolid \n", out.toString());
    505     }
    506
    507     @Test
    508     void testWrite_noSolidStarted() {
    509         // arrange
    510         final List<Vector3D> vertices = Arrays.asList(
    511                 Vector3D.ZERO, Vector3D.of(1, 0, 0), Vector3D.of(0, 1, 0));
    512         final Vector3D normal = Vector3D.Unit.PLUS_Z;
    513
    514         final String msg = "Cannot write triangle: no solid has been started";
    515
    516         try (TextStlWriter writer = new TextStlWriter(out)) {
    517
    518             // act/assert
    519             GeometryTestUtils.assertThrowsWithMessage(
    520                     () -> writer.writeTriangle(vertices.get(0), vertices.get(1), vertices.get(2), normal),
    521                     IllegalStateException.class, msg);
    522
    523             GeometryTestUtils.assertThrowsWithMessage(
    524                     () -> writer.writeTriangles(vertices, normal),
    525                     IllegalStateException.class, msg);
    526
    527             GeometryTestUtils.assertThrowsWithMessage(
    528                     () -> writer.writeTriangles(new SimpleFacetDefinition(vertices, normal)),
    529                     IllegalStateException.class, msg);
    530
    531             GeometryTestUtils.assertThrowsWithMessage(
    532                     () -> writer.writeTriangles(Planes.convexPolygonFromVertices(vertices, TEST_PRECISION)),
    533                     IllegalStateException.class, msg);
    534         }
    535     }
    536
    537     @Test
    538     void testWrite_customFormat() {
    539         // arrange
    540         final List<Vector3D> vertices = Arrays.asList(
    541                 Vector3D.ZERO, Vector3D.of(1.0 / 3.0, 0, 0), Vector3D.of(0, 1.0 / 3.0, 0));
    542         final Vector3D normal = Vector3D.Unit.PLUS_Z;
    543
    544         final DecimalFormat fmt =
    545                 new DecimalFormat("0.0##", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
    546
    547         try (TextStlWriter writer = new TextStlWriter(out)) {
    548
    549             writer.setDoubleFormat(fmt::format);
    550             writer.setLineSeparator("\r\n");
    551
    552             // act
    553             writer.startSolid();
    554             writer.writeTriangles(vertices, normal);
    555         }
    556
    557         // assert
    558         Assertions.assertEquals(
    559             "solid \r\n" +
    560             "facet 0.0 0.0 1.0\r\n" +
    561             "outer loop\r\n" +
    562             "vertex 0.0 0.0 0.0\r\n" +
    563             "vertex 0.333 0.0 0.0\r\n" +
    564             "vertex 0.0 0.333 0.0\r\n" +
    565             "endloop\r\n" +
    566             "endfacet\r\n" +
    567             "endsolid \r\n", out.toString());
    568     }
    569
    570     @Test
    571     void testWrite_badFacet_withNormal() {
    572         // arrange
    573         final List<Vector3D> vertices = Arrays.asList(
    574                 Vector3D.ZERO, Vector3D.ZERO, Vector3D.ZERO);
    575         final Vector3D normal = Vector3D.Unit.PLUS_Z;
    576
    577         try (TextStlWriter writer = new TextStlWriter(out)) {
    578             // act
    579             writer.startSolid();
    580             writer.writeTriangles(vertices, normal);
    581         }
    582
    583         // assert
    584         Assertions.assertEquals(
    585             "solid \n" +
    586             "facet 0.0 0.0 1.0\n" +
    587             "outer loop\n" +
    588             "vertex 0.0 0.0 0.0\n" +
    589             "vertex 0.0 0.0 0.0\n" +
    590             "vertex 0.0 0.0 0.0\n" +
    591             "endloop\n" +
    592             "endfacet\n" +
    593             "endsolid \n", out.toString());
    594     }
    595
    596     @Test
    597     void testWrite_badFacet_noNormal() {
    598         // arrange
    599         final List<Vector3D> vertices = Arrays.asList(
    600                 Vector3D.ZERO, Vector3D.ZERO, Vector3D.ZERO);
    601
    602         try (TextStlWriter writer = new TextStlWriter(out)) {
    603             // act
    604             writer.startSolid();
    605             writer.writeTriangles(vertices, null);
    606         }
    607
    608         // assert
    609         Assertions.assertEquals(
    610             "solid \n" +
    611             "facet 0.0 0.0 0.0\n" +
    612             "outer loop\n" +
    613             "vertex 0.0 0.0 0.0\n" +
    614             "vertex 0.0 0.0 0.0\n" +
    615             "vertex 0.0 0.0 0.0\n" +
    616             "endloop\n" +
    617             "endfacet\n" +
    618             "endsolid \n", out.toString());
    619     }
    620 }