// This file is part of libigl, a simple c++ geometry processing library. // // Copyright (C) 2014 Alec Jacobson // Copyright (C) 2018 Qingnan Zhou // Copyright (C) 2020 Jérémie Dumas // // This Source Code Form is subject to the terms of the Mozilla Public License // v. 2.0. If a copy of the MPL was not distributed with this file, You can // obtain one at http://mozilla.org/MPL/2.0/. #include "readSTL.h" #include "list_to_matrix.h" #include "string_utils.h" #include "read_file_binary.h" #include "FileMemoryStream.h" #include namespace igl { template IGL_INLINE bool readSTL(std::istream &input, Eigen::PlainObjectBase &V, Eigen::PlainObjectBase &F, Eigen::PlainObjectBase &N) { std::vector> vV; std::vector> vN; std::vector> vF; if (!readSTL(input, vV, vF, vN)) { return false; } if (!list_to_matrix(vV, V)) { return false; } if (!list_to_matrix(vF, F)) { return false; } if (!list_to_matrix(vN, N)) { return false; } return true; } IGL_INLINE bool is_stl_binary(std::istream &input) { std::streampos start_pos = input.tellg(); constexpr size_t HEADER_SIZE = 80; char header[HEADER_SIZE]; input.read(header, HEADER_SIZE); if (!starts_with(header, "solid")) { input.seekg(start_pos); return true; } if (!input.good()) { input.seekg(start_pos); return false; } // Check if filesize matches the number of faces claimed. char buf[4]; input.read(buf, 4); size_t num_faces = *reinterpret_cast(buf); input.seekg(0, input.end); size_t file_size = input.tellg(); input.seekg(start_pos); if (file_size == 80 + 4 + (4 * 12 + 2) * num_faces) { return true; } else { return false; } } template IGL_INLINE bool read_stl_ascii(std::istream &input, std::vector> &V, std::vector> &F, std::vector> &N) { constexpr size_t LINE_SIZE = 256; char line[LINE_SIZE]; bool success = true; if (!input) { throw std::runtime_error("Failed to open file"); } // skip header line. input.getline(line, LINE_SIZE); auto parse_ascii_normal = [&N](const char *line) { double x, y, z; size_t n = sscanf(line, " facet normal %lf %lf %lf", &x, &y, &z); assert(n == 3); if (n != 3) { return false; } N.push_back({{static_cast(x), static_cast(y), static_cast(z)}}); return true; }; auto parse_ascii_vertex = [&V](const char *line) { double x, y, z; size_t n = sscanf(line, " vertex %lf %lf %lf", &x, &y, &z); assert(n == 3); if (n != 3) { return false; } V.push_back({{static_cast(x), static_cast(y), static_cast(z)}}); return true; }; auto parse_ascii_facet = [&parse_ascii_vertex, &parse_ascii_normal](std::istream &fin) { constexpr size_t LINE_SIZE = 256; constexpr size_t WORD_SIZE = 128; char line[LINE_SIZE]; char first_word[WORD_SIZE]; const char *face_begin = "facet"; const char *face_end = "endfacet"; const char *loop_begin = "outer"; const char *loop_end = "endloop"; const char *vertex_flag = "vertex"; bool reading_facet = false; bool reading_loop = false; bool success = true; size_t num_vts = 0; while (!fin.eof()) { fin.getline(line, LINE_SIZE); size_t n = sscanf(line, " %s", first_word); if (n == 0) continue; if (starts_with(first_word, face_begin)) { success = parse_ascii_normal(line); assert(success); reading_facet = true; } else if (starts_with(first_word, face_end)) { assert(reading_facet); reading_facet = false; } else if (starts_with(first_word, loop_begin)) { reading_loop = true; } else if (starts_with(first_word, loop_end)) { assert(reading_loop); reading_loop = false; } else if (starts_with(first_word, vertex_flag)) { assert(reading_facet); assert(reading_loop); success = parse_ascii_vertex(line); assert(success); num_vts += 1; } if (!success) { return false; } if (!reading_facet) { break; } } if (num_vts == 0) { return true; } assert(num_vts == 3); if (num_vts != 3) { std::cerr << "Warning: mesh contain face made of " << num_vts << " vertices" << std::endl; return false; } return true; }; while (!input.eof()) { success = parse_ascii_facet(input); if (!success) { return false; } } F.resize(V.size() / 3); for (size_t f = 0; f < F.size(); ++f) { auto v = static_cast(f * 3); F[f] = {{v, v + 1, v + 2}}; } return success; } template IGL_INLINE bool read_stl_binary(std::istream &input, std::vector> &V, std::vector> &F, std::vector> &N) { if (!input) { throw std::runtime_error("Failed to open file"); } constexpr size_t FLOAT_SIZE = sizeof(float); static_assert(FLOAT_SIZE == 4, "float type is not 4 bytes"); constexpr size_t LINE_SIZE = 256; char buf[LINE_SIZE]; // 80 bytes header, no data significance. input.read(buf, 80); if (!input.good()) { throw std::runtime_error("Unable to parse STL header."); } input.read(buf, 4); const size_t num_faces = *reinterpret_cast(buf); if (!input.good()) { throw std::runtime_error("Unable to parse STL number of faces."); } for (size_t i = 0; i < num_faces; i++) { // Parse normal input.read(buf, FLOAT_SIZE * 3); auto nx = static_cast(*reinterpret_cast(buf)); auto ny = static_cast(*reinterpret_cast(buf + FLOAT_SIZE)); auto nz = static_cast(*reinterpret_cast(buf + FLOAT_SIZE * 2)); assert(input.good()); // vertex 1 input.read(buf, FLOAT_SIZE * 3); auto v1x = static_cast(*reinterpret_cast(buf)); auto v1y = static_cast(*reinterpret_cast(buf + FLOAT_SIZE)); auto v1z = static_cast(*reinterpret_cast(buf + FLOAT_SIZE * 2)); assert(input.good()); // vertex 2 input.read(buf, FLOAT_SIZE * 3); auto v2x = static_cast(*reinterpret_cast(buf)); auto v2y = static_cast(*reinterpret_cast(buf + FLOAT_SIZE)); auto v2z = static_cast(*reinterpret_cast(buf + FLOAT_SIZE * 2)); assert(input.good()); // vertex 3 input.read(buf, FLOAT_SIZE * 3); auto v3x = static_cast(*reinterpret_cast(buf)); auto v3y = static_cast(*reinterpret_cast(buf + FLOAT_SIZE)); auto v3z = static_cast(*reinterpret_cast(buf + FLOAT_SIZE * 2)); assert(input.good()); // attribute (2 bytes), not sure what purpose they serve. input.read(buf, 2); N.push_back({{nx, ny, nz}}); V.push_back({{v1x, v1y, v1z}}); V.push_back({{v2x, v2y, v2z}}); V.push_back({{v3x, v3y, v3z}}); assert(input.good()); if (!input.good()) { std::stringstream err_msg; err_msg << "Failed to parse face " << i << " from STL file"; throw std::runtime_error(err_msg.str()); } } std::for_each(V.begin(), V.end(), [](const std::array &v) { for (auto x : v) { if (!std::isfinite(x)) { throw std::runtime_error("NaN or Inf detected in input file."); } } }); if (!V.empty()) { F.resize(V.size() / 3); for (size_t f = 0; f < F.size(); ++f) { auto v = static_cast(f * 3); F[f] = {{v, v + 1, v + 2}}; } } return true; } template IGL_INLINE bool readSTL(std::istream &input, std::vector> &V, std::vector> &F, std::vector> &N) { bool success = false; if (is_stl_binary(input)) { success = read_stl_binary(input, V, F, N); } else { success = read_stl_ascii(input, V, F, N); } return success; } template IGL_INLINE bool readSTL( FILE * fp, Eigen::PlainObjectBase & V, Eigen::PlainObjectBase & F, Eigen::PlainObjectBase & N) { std::vector fileBufferBytes; read_file_binary(fp,fileBufferBytes); FileMemoryStream stream((char*)fileBufferBytes.data(), fileBufferBytes.size()); return readSTL(stream, V, F, N); } } // namespace igl #ifdef IGL_STATIC_LIBRARY // Explicit template instantiation // generated by autoexplicit.sh template bool igl::readSTL, Eigen::Matrix, Eigen::Matrix >(FILE*, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); template bool igl::readSTL, Eigen::Matrix, Eigen::Matrix >(FILE*, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); template bool igl::readSTL, Eigen::Matrix, Eigen::Matrix >(FILE*, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); template bool igl::readSTL, Eigen::Matrix, Eigen::Matrix >(FILE*, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); template bool igl::readSTL, Eigen::Matrix, Eigen::Matrix >(FILE*, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); template bool igl::readSTL, Eigen::Matrix, Eigen::Matrix >(FILE*, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); template bool igl::readSTL, Eigen::Matrix, Eigen::Matrix >(FILE*, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); template bool igl::readSTL, Eigen::Matrix, Eigen::Matrix >(FILE*, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); template bool igl::readSTL, Eigen::Matrix, Eigen::Matrix >(FILE*, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); template bool igl::readSTL, Eigen::Matrix, Eigen::Matrix >(FILE*, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); #endif