From 166c95f279415319708ed96a72aa9ccfac8e4ec1 Mon Sep 17 00:00:00 2001 From: Tim Felle Olsen Date: Wed, 6 Nov 2024 11:26:43 +0100 Subject: [PATCH 1/4] Addition of an STL Loader --- src/GEL/HMesh/HMesh.h | 1 + src/GEL/HMesh/load.cpp | 4 + src/GEL/HMesh/stl_load.cpp | 212 +++++++++++++++++++++++++++++++++++++ src/GEL/HMesh/stl_load.h | 29 +++++ 4 files changed, 246 insertions(+) create mode 100644 src/GEL/HMesh/stl_load.cpp create mode 100644 src/GEL/HMesh/stl_load.h diff --git a/src/GEL/HMesh/HMesh.h b/src/GEL/HMesh/HMesh.h index 887d7ae4..cb1523bd 100644 --- a/src/GEL/HMesh/HMesh.h +++ b/src/GEL/HMesh/HMesh.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include diff --git a/src/GEL/HMesh/load.cpp b/src/GEL/HMesh/load.cpp index 312ba313..1dbb8e1c 100644 --- a/src/GEL/HMesh/load.cpp +++ b/src/GEL/HMesh/load.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -51,6 +52,9 @@ namespace HMesh mani.deserialize(ser); return true; } + else if(ext==".stl") { + return stl_load(file_name, mani); + } return false; } } diff --git a/src/GEL/HMesh/stl_load.cpp b/src/GEL/HMesh/stl_load.cpp new file mode 100644 index 00000000..cd3fb661 --- /dev/null +++ b/src/GEL/HMesh/stl_load.cpp @@ -0,0 +1,212 @@ +/* ----------------------------------------------------------------------- * + * This file is part of GEL, http://www.imm.dtu.dk/GEL + * Copyright (C) the authors and DTU Informatics + * For license and list of authors, see ../../doc/intro.pdf + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace CGLA; + +namespace HMesh { + using std::string; + + void right_trim_stl(std::string& str) { + if (!str.empty()) + while (isspace(str.back())) str.pop_back(); + } + + istream& get_multi_line_stl(istream& is, string& buf) { + getline(is, buf); + right_trim_stl(buf); + if (!buf.empty()) + while (buf.back() == '\\') { + buf.pop_back(); + string continuation; + getline(is, continuation); + right_trim_stl(continuation); + buf += continuation; + } + return is; + } + + bool stl_load_ascii( + const std::string& filename, Manifold& m, + VertexAttributeVector& orig_vertex_indices) { + ifstream stl_file(filename.data()); + + if (stl_file) { + string buf; + vector vertices; + vector faces; + vector indices; + Vec3d normal; + while (get_multi_line_stl(stl_file, buf)) { + istringstream iss(std::move(buf)); + string code; + if (iss >> code) { + if (code == "facet") { + if (iss >> code && code == "normal") { iss >> normal; } + } else if (code == "vertex") { + Vec3d v; + iss >> v; + vertices.push_back(v); + } else if (code == "endfacet") { + // Determine the order of indices to match the normal + // direction + Vec3d n(normal); + Vec3d e1 = vertices[vertices.size() - 2] - + vertices[vertices.size() - 3]; + Vec3d e2 = vertices[vertices.size() - 1] - + vertices[vertices.size() - 3]; + Vec3d n2 = cross(e1, e2); + + if (dot(n, n2) > 0) { + indices.push_back(vertices.size() - 3); + indices.push_back(vertices.size() - 2); + indices.push_back(vertices.size() - 1); + } else { + indices.push_back(vertices.size() - 3); + indices.push_back(vertices.size() - 1); + indices.push_back(vertices.size() - 2); + } + faces.push_back(3); + } + } + } + // cout << "Loaded " << filename << " : " << vertices.size() + // << " vertices and " << faces.size() << " faces" << endl; + m.clear(); + + orig_vertex_indices = build( + m, vertices.size(), reinterpret_cast(&vertices[0]), + faces.size(), &faces[0], &indices[0]); + + return true; + } + return false; + } + + bool stl_load_binary( + const std::string& filename, Manifold& m, + VertexAttributeVector& orig_vertex_indices) { + ifstream stl_file(filename.data(), ios::binary); + if (stl_file) { + + char header[80]; + stl_file.read(header, 80); + + uint32_t num_triangles; + stl_file.read(reinterpret_cast(&num_triangles), 4); + + vector vertices; + vector faces; + vector indices; + for (uint32_t i = 0; i < num_triangles; ++i) { + Vec3f normal; + stl_file.read(reinterpret_cast(&normal), 12); + + Vec3f v[3]; + for (int j = 0; j < 3; ++j) { + stl_file.read(reinterpret_cast(&v[j]), 12); + vertices.push_back(Vec3d(v[j])); + } + uint16_t attr; + stl_file.read(reinterpret_cast(&attr), 2); + + // Determine the order of indices to match the normal direction + Vec3d n(normal); + Vec3d e1 = vertices[vertices.size() - 2] - + vertices[vertices.size() - 3]; + Vec3d e2 = vertices[vertices.size() - 1] - + vertices[vertices.size() - 3]; + Vec3d n2 = cross(e1, e2); + if (dot(n, n2) > 0) { + indices.push_back(vertices.size() - 3); + indices.push_back(vertices.size() - 2); + indices.push_back(vertices.size() - 1); + } else { + indices.push_back(vertices.size() - 3); + indices.push_back(vertices.size() - 1); + indices.push_back(vertices.size() - 2); + } + faces.push_back(3); + } + + // cout << "Loaded " << filename << " : " << vertices.size() + // << " vertices and " << faces.size() << " faces" << endl; + m.clear(); + + orig_vertex_indices = build( + m, vertices.size(), reinterpret_cast(&vertices[0]), + faces.size(), &faces[0], &indices[0]); + + return true; + } + return false; + } + + bool stl_load( + const std::string& filename, Manifold& m, + VertexAttributeVector& orig_vertex_indices) { + + // Open the file + ifstream stl_file(filename.data(), ios::binary); + if (!stl_file) { + cerr << "Could not open file " << filename << endl; + return false; + } + + // Read the first 5 bytes to determine if the file is ASCII or binary + char header[5]; + stl_file.read(header, 5); + stl_file.close(); + + // Check if the file is ASCII or binary (ASCII files start with "solid") + bool is_ascii = (header[0] == 's' || header[0] == 'S') && + (header[1] == 'o' || header[1] == 'O') && + (header[2] == 'l' || header[2] == 'L') && + (header[3] == 'i' || header[3] == 'I') && + (header[4] == 'd' || header[4] == 'D'); + + // Call the appropriate loader + bool status = false; + if (is_ascii) { + status = stl_load_ascii(filename, m, orig_vertex_indices); + } else { + status = stl_load_binary(filename, m, orig_vertex_indices); + } + + if (!status) { + cerr << "Could not load file " << filename << endl; + return false; + } + + // The current state of the mesh is not valid, so we need to clean it up + // since it is just a triangle soup. + double e_len = HMesh::average_edge_length(m); + + int missing_edges = stitch_mesh(m, 1e-6 * e_len); + + if (missing_edges > 0) { + cerr << "Could not stitch " << missing_edges << " edges" << endl; + return false; + } + + m.cleanup(); + return true; + } + + bool stl_load(const string& filename, Manifold& m) { + VertexAttributeVector orig_vertex_indices; + return stl_load(filename, m, orig_vertex_indices); + } + +} // namespace HMesh diff --git a/src/GEL/HMesh/stl_load.h b/src/GEL/HMesh/stl_load.h new file mode 100644 index 00000000..ef4079d5 --- /dev/null +++ b/src/GEL/HMesh/stl_load.h @@ -0,0 +1,29 @@ +/* ----------------------------------------------------------------------- * + * This file is part of GEL, http://www.imm.dtu.dk/GEL + * Copyright (C) the authors and DTU Informatics + * For license and list of authors, see ../../doc/intro.pdf + * ----------------------------------------------------------------------- */ + +/** + * @file HMesh/stl_load.h + * @brief Load Manifold from stl file + */ + +#ifndef __HMESH_STLLOAD__H__ +#define __HMESH_STLLOAD__H__ + +#include +#include + +namespace HMesh +{ + /** Load a STL file. + The first argument is a string containing the file name (including path) + and the second is the Manifold into which the mesh is loaded. The third argument + is an attribute vector containing the indices of the original + points. */ + + bool stl_load(const std::string&, Manifold& m, VertexAttributeVector& orig_vertex_indices); + bool stl_load(const std::string&, Manifold& m); +} +#endif From 4151dcd87c8747bf5e73c3e0447ad77963ddbd15 Mon Sep 17 00:00:00 2001 From: Tim Felle Olsen Date: Wed, 6 Nov 2024 13:34:51 +0100 Subject: [PATCH 2/4] Add stl writer --- src/GEL/HMesh/HMesh.h | 1 + src/GEL/HMesh/stl_save.cpp | 98 ++++++++++++++++++++++++++++++++++++++ src/GEL/HMesh/stl_save.h | 25 ++++++++++ 3 files changed, 124 insertions(+) create mode 100644 src/GEL/HMesh/stl_save.cpp create mode 100644 src/GEL/HMesh/stl_save.h diff --git a/src/GEL/HMesh/HMesh.h b/src/GEL/HMesh/HMesh.h index cb1523bd..82b368db 100644 --- a/src/GEL/HMesh/HMesh.h +++ b/src/GEL/HMesh/HMesh.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include diff --git a/src/GEL/HMesh/stl_save.cpp b/src/GEL/HMesh/stl_save.cpp new file mode 100644 index 00000000..de4f01f2 --- /dev/null +++ b/src/GEL/HMesh/stl_save.cpp @@ -0,0 +1,98 @@ +/* ----------------------------------------------------------------------- * + * This file is part of GEL, http://www.imm.dtu.dk/GEL + * Copyright (C) the authors and DTU Informatics + * For license and list of authors, see ../../doc/intro.pdf + * ----------------------------------------------------------------------- */ + +#include + +#include +#include +#include +#include + +#include + +#include +#include + +namespace HMesh { + using namespace std; + using namespace CGLA; + + bool stl_save_ascii(const string& filename, Manifold& m) { + + ofstream os(filename.data()); + if (!os) { + cerr << "stl_save: could not open file " << filename << endl; + return false; + } + + // Build the header + os << "solid " << filename << endl; + + // Write the faces + for (FaceIDIterator f = m.faces_begin(); f != m.faces_end(); ++f) { + os << "facet normal "; + Vec3d n = normal(m, *f); + os << n[0] << " " << n[1] << " " << n[2] << endl; + os << "\touter loop" << endl; + for (Walker w = m.walker(*f); !w.full_circle(); + w = w.circulate_face_ccw()) { + Vec3d p = m.pos(w.vertex()); + os << "\t\tvertex " << p[0] << " " << p[1] << " " << p[2] + << endl; + } + os << "\tendloop" << endl; + os << "endfacet" << endl; + } + + // End the file + os << "endsolid " << filename << endl; + + return true; + } + + bool stl_save_binary(const string& filename, Manifold& m) { + + ofstream os(filename.data(), ios::binary); + if (!os) { + cerr << "stl_save: could not open file " << filename << endl; + return false; + } + + // Write the header + char header[80]; + for (int i = 0; i < 80; ++i) header[i] = 0; + os.write(header, 80); + + // Write the number of faces (4-byte little-endian unsigned integer) + unsigned int n_faces = m.no_faces(); + os.write(reinterpret_cast(&n_faces), 4); + + // Write the faces + for (FaceIDIterator f = m.faces_begin(); f != m.faces_end(); ++f) { + Vec3d n = normal(m, *f); + normalize(n); + + Vec3f float_n(n[0], n[1], n[2]); + os.write(reinterpret_cast(&float_n[0]), 12); + for (Walker w = m.walker(*f); !w.full_circle(); + w = w.circulate_face_ccw()) { + Vec3d p = m.pos(w.vertex()); + Vec3f float_p(p[0], p[1], p[2]); + os.write(reinterpret_cast(&float_p[0]), 12); + } + unsigned short attr = 0; + os.write(reinterpret_cast(&attr), 2); + } + return false; + } + + bool stl_save(const string& filename, Manifold& m, bool is_binary) { + if (is_binary) + return stl_save_binary(filename, m); + else + return stl_save_ascii(filename, m); + } +} // namespace HMesh diff --git a/src/GEL/HMesh/stl_save.h b/src/GEL/HMesh/stl_save.h new file mode 100644 index 00000000..47b94b92 --- /dev/null +++ b/src/GEL/HMesh/stl_save.h @@ -0,0 +1,25 @@ +/* ----------------------------------------------------------------------- * + * This file is part of GEL, http://www.imm.dtu.dk/GEL + * Copyright (C) the authors and DTU Informatics + * For license and list of authors, see ../../doc/intro.pdf + * ----------------------------------------------------------------------- */ + +/** + * @file stl_save.h + * @brief Save Manifold to STL. + */ + +#ifndef __HMESH_STLSAVE__H__ +#define __HMESH_STLSAVE__H__ + +#include +#include + +namespace HMesh +{ + class Manifold; + /// \brief Save in STL format. + bool stl_save(const std::string&, Manifold& m, bool is_binary = false); + +} +#endif From 349e87e586f91a987e2955637d607eb95e09866e Mon Sep 17 00:00:00 2001 From: Tim Felle Olsen Date: Wed, 6 Nov 2024 13:35:13 +0100 Subject: [PATCH 3/4] Allow stil writer to be called from MeshEdit console --- src/GEL/GLGraphics/MeshEditor.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/GEL/GLGraphics/MeshEditor.cpp b/src/GEL/GLGraphics/MeshEditor.cpp index ebd7a242..12d0bbc6 100644 --- a/src/GEL/GLGraphics/MeshEditor.cpp +++ b/src/GEL/GLGraphics/MeshEditor.cpp @@ -609,6 +609,23 @@ namespace GLGraphics { Serialization ser(file_name, std::ios_base::out); me->active_mesh().serialize(ser); return; + } else if (extension == ".stl") { + bool is_binary = false; + if (args.size() < 2) { + me->printf("please specify binary or ascii format"); + return; + } else { + if (args[1] == "binary") { + is_binary = true; + } else if (args[1] == "ascii") { + is_binary = false; + } else { + me->printf("please specify binary or ascii format"); + return; + } + stl_save(file_name, me->active_mesh(), is_binary); + } + return; } me->printf("unknown format"); return; From 86fc6a57ad85ff1abc9a20f91196904b92d7f974 Mon Sep 17 00:00:00 2001 From: Tim Felle Olsen Date: Wed, 6 Nov 2024 13:49:01 +0100 Subject: [PATCH 4/4] Do not stop if edges could not be stitched --- src/GEL/HMesh/stl_load.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/GEL/HMesh/stl_load.cpp b/src/GEL/HMesh/stl_load.cpp index cd3fb661..3717bde3 100644 --- a/src/GEL/HMesh/stl_load.cpp +++ b/src/GEL/HMesh/stl_load.cpp @@ -197,7 +197,6 @@ namespace HMesh { if (missing_edges > 0) { cerr << "Could not stitch " << missing_edges << " edges" << endl; - return false; } m.cleanup();