LCOV - code coverage report
Current view: top level - src/mesh - stl_io.C (source / functions) Hit Total Coverage
Test: libMesh/libmesh: #4229 (6a9aeb) with base 727f46 Lines: 141 199 70.9 %
Date: 2025-08-19 19:27:09 Functions: 6 8 75.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // The libMesh Finite Element Library.
       2             : // Copyright (C) 2002-2025 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
       3             : 
       4             : // This library is free software; you can redistribute it and/or
       5             : // modify it under the terms of the GNU Lesser General Public
       6             : // License as published by the Free Software Foundation; either
       7             : // version 2.1 of the License, or (at your option) any later version.
       8             : 
       9             : // This library is distributed in the hope that it will be useful,
      10             : // but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      12             : // Lesser General Public License for more details.
      13             : 
      14             : // You should have received a copy of the GNU Lesser General Public
      15             : // License along with this library; if not, write to the Free Software
      16             : // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
      17             : 
      18             : // Local includes
      19             : #include "libmesh/stl_io.h"
      20             : 
      21             : #include "libmesh/distributed_mesh.h"
      22             : #include "libmesh/enum_elem_type.h"
      23             : #include "libmesh/face_tri3.h"
      24             : #include "libmesh/libmesh_config.h"
      25             : #include "libmesh/libmesh_logging.h"
      26             : #include "libmesh/mesh_base.h"
      27             : #include "libmesh/point.h"
      28             : #include "libmesh/utility.h"
      29             : 
      30             : // gzstream for reading compressed files as a stream
      31             : #ifdef LIBMESH_HAVE_GZSTREAM
      32             : # include "gzstream.h"
      33             : #endif
      34             : 
      35             : // C++ includes
      36             : #include <iomanip>
      37             : #include <fstream>
      38             : #include <vector>
      39             : #include <cctype> // isspace
      40             : 
      41             : #ifdef LIBMESH_HAVE_CXX11_REGEX
      42             : #include <regex>
      43             : #endif
      44             : 
      45             : namespace {
      46             : 
      47        5592 : void test_and_add(std::unique_ptr<libMesh::Elem> e,
      48             :                   libMesh::MeshBase & mesh)
      49             : {
      50        5592 :   const libMesh::Real volume = e->volume();
      51             :   if (volume < libMesh::TOLERANCE * libMesh::TOLERANCE)
      52             :     libmesh_warning
      53             :       ("Warning: STL file contained sliver element with volume " <<
      54             :        volume << "!\n");
      55        6058 :   mesh.add_elem(std::move(e));
      56        5592 : }
      57             : 
      58             : }
      59             : 
      60             : 
      61             : namespace libMesh
      62             : {
      63             : 
      64           0 : STLIO::STLIO (const MeshBase & mesh) :
      65             :   MeshOutput<MeshBase>    (mesh),
      66           0 :   _subdivide_second_order (true)
      67             : {
      68           0 : }
      69             : 
      70             : 
      71             : 
      72         142 : STLIO::STLIO (MeshBase & mesh) :
      73             :   MeshInput<MeshBase> (mesh),
      74             :   MeshOutput<MeshBase>(mesh),
      75         142 :   _subdivide_second_order (true)
      76             : {
      77         142 : }
      78             : 
      79             : 
      80             : 
      81           0 : void STLIO::write (const std::string & fname)
      82             : {
      83           0 :   LOG_SCOPE("write()", "STLIO");
      84             : 
      85             :   // Get a reference to the mesh
      86           0 :   const MeshBase * mesh_ptr = &MeshOutput<MeshBase>::mesh();
      87             : 
      88           0 :   libmesh_parallel_only(mesh_ptr->comm());
      89             : 
      90             :   // If necessary, serialize to proc 0, which will do all the writing
      91           0 :   std::unique_ptr<DistributedMesh> mesh_copy;
      92           0 :   std::unique_ptr<MeshSerializer> serializer;
      93           0 :   if (!mesh_ptr->is_serial())
      94             :     {
      95           0 :       mesh_copy = std::make_unique<DistributedMesh>(*mesh_ptr);
      96           0 :       mesh_ptr = mesh_copy.get();
      97           0 :       serializer = std::make_unique<MeshSerializer>(*mesh_copy, true, true);
      98             :     }
      99             : 
     100           0 :   const MeshBase & mesh = *mesh_ptr;
     101             : 
     102           0 :   if (mesh.processor_id() != 0)
     103           0 :     return;
     104             : 
     105             :   // Open the output file stream
     106           0 :   std::ofstream out_stream (fname.c_str());
     107             : 
     108           0 :   out_stream << std::setprecision(this->ascii_precision());
     109             : 
     110             :   // Make sure it opened correctly
     111           0 :   if (!out_stream.good())
     112           0 :     libmesh_file_error(fname.c_str());
     113             : 
     114             :   // Write the header
     115           0 :   out_stream << "solid " << this->_name << '\n';
     116             : 
     117             :   // Write all our triangles
     118             :   // scream if we see a non-triangle
     119           0 :   for (auto elem : mesh.element_ptr_range())
     120             :     {
     121             :       // Get to this later
     122           0 :       if (elem->type() == TRI6 || elem->type() == TRI7)
     123             :         {
     124           0 :           if (this->_subdivide_second_order)
     125           0 :             libmesh_not_implemented();
     126             : 
     127           0 :           libmesh_error_msg("Tried to write a non-linear triangle to an STL file");
     128             :         }
     129             : 
     130           0 :       libmesh_error_msg_if(elem->type() != TRI3,
     131             :                            "Tried to write a non-triangle to an STL file");
     132             : 
     133           0 :       auto n = (elem->point(1)-elem->point(0)).cross(elem->point(2)-elem->point(0));
     134             : 
     135             :       // Other STL files have slivers, I guess ours can too
     136           0 :       if (auto length = n.norm())
     137           0 :         n /= length;
     138             : 
     139           0 :       out_stream << "facet normal " <<
     140           0 :         n(0) << ' ' <<
     141           0 :         n(1) << ' ' <<
     142           0 :         n(2) << "\n"
     143             :         "    outer loop\n"
     144           0 :         "        vertex " <<
     145           0 :         elem->point(0)(0) << ' ' <<
     146           0 :         elem->point(0)(1) << ' ' <<
     147           0 :         elem->point(0)(2) << "\n"
     148           0 :         "        vertex " <<
     149           0 :         elem->point(1)(0) << ' ' <<
     150           0 :         elem->point(1)(1) << ' ' <<
     151           0 :         elem->point(1)(2) << "\n"
     152           0 :         "        vertex " <<
     153           0 :         elem->point(2)(0) << ' ' <<
     154           0 :         elem->point(2)(1) << ' ' <<
     155           0 :         elem->point(2)(2) << "\n"
     156             :         "    endloop\n"
     157           0 :         "endfacet\n";
     158           0 :     }
     159             : 
     160           0 :   out_stream << "endsolid" << std::endl;
     161           0 : }
     162             : 
     163             : 
     164             : 
     165          24 : void STLIO::read (const std::string & filename)
     166             : {
     167           4 :   LOG_SCOPE("read()", "STLIO");
     168             : 
     169             :   // This is a serial-only process for now;
     170             :   // the Mesh should be read on processor 0 and
     171             :   // broadcast later
     172           2 :   libmesh_assert_equal_to (MeshOutput<MeshBase>::mesh().processor_id(), 0);
     173             : 
     174             :   // Clear the mesh so we are sure to start from a pristine state.
     175          24 :   MeshBase & mesh = MeshInput<MeshBase>::mesh();
     176          24 :   mesh.clear();
     177          24 :   mesh.set_mesh_dimension(2);
     178             : 
     179             :   std::unique_ptr<std::istream> fstream =
     180          26 :     this->open_file(filename);
     181             : 
     182             :   char c;
     183           2 :   const char * expected_header = "solid!";
     184           2 :   bool is_ascii_stl = false,
     185           2 :        is_binary_stl = false;
     186          72 :   while (fstream->get(c))
     187             :     {
     188          72 :       if (c == ' ')
     189           0 :         continue;
     190          72 :       if (c == *expected_header)
     191             :         {
     192          60 :           ++expected_header;
     193          60 :           if (*expected_header == '!')
     194             :           {
     195           1 :             is_ascii_stl = true;
     196           1 :             break;
     197             :           }
     198             :         }
     199             :       else
     200             :         {
     201           1 :           is_binary_stl = true; // probably
     202           1 :           break;
     203             :         }
     204             :     }
     205             : 
     206          24 :   if (is_ascii_stl)
     207             :     {
     208          12 :       fstream->seekg(0);
     209          13 :       this->read_ascii(*fstream);
     210             :     }
     211          12 :   else if (is_binary_stl)
     212             :     {
     213          12 :       fstream->seekg(0, std::ios_base::end);
     214          12 :       std::size_t length = fstream->tellg();
     215          12 :       fstream->seekg(0);
     216          13 :       this->read_binary(*fstream, length);
     217             :     }
     218             :   else
     219           0 :     libmesh_error_msg("Failed to read an STL header in " << filename);
     220          24 : }
     221             : 
     222             : 
     223          24 : std::unique_ptr<std::istream> STLIO::open_file (const std::string & filename)
     224             : {
     225          24 :   const bool gzipped_file = Utility::ends_with(filename, ".gz");
     226             : 
     227          24 :   std::unique_ptr<std::istream> file;
     228             : 
     229          24 :   if (gzipped_file)
     230             :     {
     231             : #ifdef LIBMESH_HAVE_GZSTREAM
     232           0 :       auto inf = std::make_unique<igzstream>();
     233           0 :       libmesh_assert(inf);
     234           0 :       inf->open(filename.c_str(), std::ios::in);
     235           0 :       file = std::move(inf);
     236             : #else
     237             :       libmesh_error_msg("ERROR: need gzstream to handle .gz files!!!");
     238             : #endif
     239           0 :     }
     240             :   else
     241             :     {
     242          26 :       auto inf = std::make_unique<std::ifstream>();
     243           2 :       libmesh_assert(inf);
     244             : 
     245          26 :       std::string new_name = Utility::unzip_file(filename);
     246             : 
     247          24 :       inf->open(new_name.c_str(), std::ios::in);
     248          22 :       file = std::move(inf);
     249          20 :     }
     250             : 
     251          24 :   return file;
     252           0 : }
     253             : 
     254             : 
     255             : 
     256          12 : void STLIO::read_ascii (std::istream & file)
     257             : {
     258           2 :   LOG_SCOPE("read_ascii()", "STLIO");
     259             : 
     260             : #ifndef LIBMESH_HAVE_CXX11_REGEX
     261             :   libmesh_not_implemented();  // What is your compiler?!?  Email us!
     262             :   libmesh_ignore(file);
     263             : #else
     264             : 
     265          12 :   MeshBase & mesh = MeshInput<MeshBase>::mesh();
     266             : 
     267             :   const std::regex all_expected_chars
     268          14 :     ("^[\\w\\d\\-\\.\\^\\$]*$");
     269             : 
     270             :   const std::regex start_regex
     271          14 :     ("^\\s*solid");
     272             :   const std::regex start_with_name_regex
     273          14 :     ("^\\s*solid\\s+(\\w+)");
     274             : 
     275             :   const std::regex start_facet_regex
     276          14 :     ("^\\s*facet");
     277             : 
     278             :   // We'll ignore facets' normals for now
     279             :   /*
     280             :   const std::regex facet_with_normal_regex
     281             :     ("^\\s*facet\\s+normal"
     282             :      "\\s+([+-]?(\\d+([.]\\d*)?([eE][+-]?\\d+)?|[.]\\d+([eE][+-]?\\d+)?)"
     283             :      "\\s+[+-]?(\\d+([.]\\d*)?([eE][+-]?\\d+)?|[.]\\d+([eE][+-]?\\d+)?)"
     284             :      "\\s+[+-]?(\\d+([.]\\d*)?([eE][+-]?\\d+)?|[.]\\d+([eE][+-]?\\d+)?))");
     285             :   */
     286             : 
     287             :   const std::regex start_loop_regex
     288          14 :     ("^\\s*outer\\s+loop");
     289             : 
     290             :   const std::regex vertex_regex
     291             :     ("^\\s*vertex"
     292             :      "\\s+([+-]?(\\d+([.]\\d*)?([eE][+-]?\\d+)?|[.]\\d+([eE][+-]?\\d+)?)"
     293             :      "\\s+[+-]?(\\d+([.]\\d*)?([eE][+-]?\\d+)?|[.]\\d+([eE][+-]?\\d+)?)"
     294          14 :      "\\s+[+-]?(\\d+([.]\\d*)?([eE][+-]?\\d+)?|[.]\\d+([eE][+-]?\\d+)?))");
     295             : 
     296             :   const std::regex end_facet_regex
     297          14 :     ("^\\s*end\\s*facet");
     298             : 
     299             :   const std::regex end_loop_regex
     300          14 :     ("^\\s*end\\s*loop");
     301             : 
     302           1 :   bool have_started = false;
     303           1 :   bool in_facet = false;
     304           1 :   bool in_vertex_loop = false;
     305          11 :   std::unique_ptr<Tri3> triangle;
     306           1 :   int next_vertex = 0;
     307           1 :   int line_num = 0;
     308             : 
     309           2 :   std::unordered_map<Point, Node *> mesh_points;
     310             : 
     311        3396 :   for (std::string line; std::getline(file, line);)
     312             :     {
     313        3384 :       ++line_num;
     314         564 :       std::smatch sm;
     315             : 
     316       64596 :       for (char & c : line)
     317             :         {
     318       61212 :           libmesh_error_msg_if
     319             :             (c != '\n' && c != '\r' &&
     320             :              (c < ' ' || c > '~'),
     321             :              "Found non-ASCII character " << int(c) << " on line " <<
     322             :              line_num << "\nSTLIO does not yet support binary files.");
     323             :         }
     324             : 
     325        3384 :       if (std::regex_search(line, sm, start_regex))
     326             :         {
     327          12 :           if (std::regex_search(line, sm, start_with_name_regex))
     328          23 :             this->_name = sm[1];
     329          12 :           libmesh_error_msg_if
     330             :             (have_started,
     331             :              "Found two 'solid' lines starting the same STL file?");
     332           1 :           have_started = true;
     333             :         }
     334             : 
     335        3372 :       else if (std::regex_search(line, sm, start_facet_regex))
     336             :         {
     337         480 :           libmesh_error_msg_if
     338             :             (in_facet,
     339             :              "Found a repeated 'facet' line with no 'endfacet' between them.");
     340          40 :           in_facet = true;
     341             : 
     342             :           /*
     343             :           if (std::regex_search(line, sm, facet_with_normal_regex))
     344             :             {
     345             :               const std::string normalvec = sm[1];
     346             : 
     347             :               // Try to be compatible with higher-than-double T; maybe
     348             :               // *someone* out there is doing STL in 128 bits? ...
     349             :               std::stringstream ss(normalvec);
     350             :               ss >> normal(0);
     351             :               ss >> normal(1);
     352             :               ss >> normal(2);
     353             :               }
     354             :           */
     355             :         }
     356             : 
     357        2892 :       else if (std::regex_search(line, end_facet_regex))
     358             :         {
     359         480 :           libmesh_error_msg_if
     360             :             (!in_facet,
     361             :              "Found an 'endfacet' line with no matching 'facet'.");
     362          40 :           in_facet = false;
     363             :         }
     364             : 
     365        2412 :       else if (std::regex_search(line, start_loop_regex))
     366             :         {
     367         480 :           libmesh_error_msg_if
     368             :             (!in_facet,
     369             :              "Found an 'outer loop' line with no matching 'facet'.");
     370         480 :           libmesh_error_msg_if
     371             :             (in_vertex_loop,
     372             :              "Found a repeated 'outer loop' line with no 'endloop' between them.");
     373          40 :           in_vertex_loop = true;
     374         920 :           triangle = std::make_unique<Tri3>();
     375          40 :           next_vertex = 0;
     376             :         }
     377             : 
     378        1932 :       else if (std::regex_search(line, sm, vertex_regex))
     379             :         {
     380        1560 :           const std::string normalvec = sm[1];
     381             : 
     382             :           // Try to be compatible with higher-than-double-precision T;
     383             :           // maybe *someone* out there is doing STL in 128 bits? ...
     384        1680 :           std::stringstream ss(normalvec);
     385         120 :           Point p;
     386         120 :           ss >> p(0);
     387         120 :           ss >> p(1);
     388         120 :           ss >> p(2);
     389             : 
     390             :           Node * node;
     391        1440 :           if (auto it = mesh_points.find(p); it != mesh_points.end())
     392             :             {
     393         720 :               node = it->second;
     394             :             }
     395             :           else
     396             :             {
     397         720 :               node = mesh.add_point(p);
     398         720 :               mesh_points[p] = node;
     399             :             }
     400             : 
     401        1440 :           libmesh_error_msg_if
     402             :             (next_vertex > 2,
     403             :              "Found more than 3 vertices in a loop; STLIO only supports Tri3.");
     404        1440 :           triangle->set_node(next_vertex++, node);
     405        1200 :         }
     406             : 
     407         492 :       else if (std::regex_search(line, end_loop_regex))
     408             :         {
     409         480 :           libmesh_error_msg_if
     410             :             (next_vertex != 3,
     411             :              "Found an 'endloop' line after only seeing " << next_vertex << " vertices in 'loop'.");
     412         480 :           libmesh_error_msg_if
     413             :             (!in_vertex_loop,
     414             :              "Found an 'endloop' line with no matching 'loop'.");
     415          40 :           in_vertex_loop = false;
     416             : 
     417         520 :           test_and_add(std::move(triangle), mesh);
     418             :         }
     419             :     }
     420             : 
     421          12 :   libmesh_error_msg_if
     422             :     (in_facet,
     423             :      "File ended without ending a facet first.");
     424             : 
     425          12 :   libmesh_error_msg_if
     426             :     (in_vertex_loop,
     427             :      "File ended without ending an outer loop first.");
     428             : #endif // LIBMESH_HAVE_CXX11_REGEX
     429          12 : }
     430             : 
     431             : 
     432             : 
     433          12 : void STLIO::read_binary (std::istream & file,
     434             :                          std::size_t input_size)
     435             : {
     436           2 :   LOG_SCOPE("read_binary()", "STLIO");
     437             : 
     438          12 :   MeshBase & mesh = MeshInput<MeshBase>::mesh();
     439             : 
     440             :   char header_buffer[80];
     441             : 
     442             :   // 80-character header which is generally ignored - Wikipedia
     443          12 :   file.read(header_buffer, 80);
     444             : 
     445             :   // What's our endianness here?  Input binary files are specified to
     446             :   // be little endian, and we might need to do conversions.
     447             : 
     448           1 :   uint32_t test_int = 0x87654321;
     449           1 :   const bool big_endian = ((*reinterpret_cast<char *>(&test_int)) != 0x21);
     450           1 :   const Utility::ReverseBytes endian_fix{big_endian};
     451             : 
     452             :   // C++ doesn't specify a size for float, but we really need 4-byte
     453             :   // floats here.  Fortunately basically every implementation ever
     454             :   // uses exactly 4 bytes for float.
     455             :   if constexpr (sizeof(float) != 4)
     456             :     libmesh_error_msg("Trying to read 4 byte floats without 4-byte float?");
     457             : 
     458          12 :   uint32_t n_elem = 0;
     459             : 
     460          12 :   file.read(reinterpret_cast<char*>(&n_elem), 4);
     461          11 :   endian_fix(n_elem);
     462             : 
     463          12 :   libmesh_error_msg_if
     464             :     (input_size &&
     465             :      (input_size < 84+n_elem*50),
     466             :      "Not enough data for " << n_elem << " STL triangles in " <<
     467             :      input_size << " uncompressed bytes.");
     468             : 
     469          11 :   std::unique_ptr<Tri3> triangle;
     470           2 :   std::unordered_map<Point, Node *> mesh_points;
     471             : 
     472        5124 :   for (unsigned int e : make_range(n_elem))
     473             :   {
     474         426 :     libmesh_ignore(e);
     475             : 
     476        9372 :     triangle = std::make_unique<Tri3>();
     477             : 
     478             :     // We'll ignore facets' normals for now
     479             :     char ignored_buffer[12];
     480        5112 :     file.read(ignored_buffer, 12);
     481             : 
     482             :     // Read vertex locations
     483       20448 :     for (int i : make_range(3))
     484             :       {
     485             :         float point_buffer[3];
     486       15336 :         file.read(reinterpret_cast<char*>(point_buffer), 12);
     487       14058 :         endian_fix(point_buffer[0]);
     488       14058 :         endian_fix(point_buffer[1]);
     489       14058 :         endian_fix(point_buffer[2]);
     490       15336 :         const Point p {point_buffer[0],
     491       15336 :                        point_buffer[1],
     492       15336 :                        point_buffer[2]};
     493             : 
     494             :         Node * node;
     495       15336 :         if (auto it = mesh_points.find(p); it != mesh_points.end())
     496             :           {
     497       12756 :             node = it->second;
     498             :           }
     499             :         else
     500             :           {
     501        2580 :             node = mesh.add_point(p);
     502        2580 :             mesh_points[p] = node;
     503             :           }
     504             : 
     505       15336 :         triangle->set_node(i, node);
     506             :       }
     507             : 
     508             :     // The 2-byte "attribute byte count" is unstandardized; typically
     509             :     // 0, or sometimes triangle color.  Ignore it.
     510        5112 :     file.read(ignored_buffer, 2);
     511             : 
     512        5538 :     test_and_add(std::move(triangle), mesh);
     513             :   }
     514          12 : }
     515             : 
     516             : 
     517             : } // namespace libMesh

Generated by: LCOV version 1.14