GCodeViewer -> Export of extrude toolpaths to obj files

This commit is contained in:
enricoturri1966 2020-07-03 12:17:12 +02:00
parent feb4857cf8
commit 0b1086f390
6 changed files with 300 additions and 10 deletions

View file

@ -1017,6 +1017,7 @@ bool GLVolumeCollection::has_toolpaths_to_export() const
return false; return false;
} }
#if !ENABLE_GCODE_VIEWER
void GLVolumeCollection::export_toolpaths_to_obj(const char* filename) const void GLVolumeCollection::export_toolpaths_to_obj(const char* filename) const
{ {
if (filename == nullptr) if (filename == nullptr)
@ -1298,6 +1299,7 @@ void GLVolumeCollection::export_toolpaths_to_obj(const char* filename) const
fclose(fp); fclose(fp);
} }
#endif // !ENABLE_GCODE_VIEWER
// caller is responsible for supplying NO lines with zero length // caller is responsible for supplying NO lines with zero length
static void thick_lines_to_indexed_vertex_array( static void thick_lines_to_indexed_vertex_array(

View file

@ -597,8 +597,10 @@ public:
std::string log_memory_info() const; std::string log_memory_info() const;
bool has_toolpaths_to_export() const; bool has_toolpaths_to_export() const;
#if !ENABLE_GCODE_VIEWER
// Export the geometry of the GLVolumes toolpaths of this collection into the file with the given path, in obj format // Export the geometry of the GLVolumes toolpaths of this collection into the file with the given path, in obj format
void export_toolpaths_to_obj(const char* filename) const; void export_toolpaths_to_obj(const char* filename) const;
#endif // !ENABLE_GCODE_VIEWER
private: private:
GLVolumeCollection(const GLVolumeCollection &other); GLVolumeCollection(const GLVolumeCollection &other);

View file

@ -24,6 +24,7 @@
#include <GL/glew.h> #include <GL/glew.h>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <boost/nowide/cstdio.hpp>
#include <array> #include <array>
#include <algorithm> #include <algorithm>
@ -487,6 +488,284 @@ void GCodeViewer::set_layers_z_range(const std::array<double, 2>& layers_z_range
wxGetApp().plater()->update_preview_moves_slider(); wxGetApp().plater()->update_preview_moves_slider();
} }
void GCodeViewer::export_toolpaths_to_obj(const char* filename) const
{
if (filename == nullptr)
return;
if (!has_data())
return;
wxBusyCursor busy;
// the data needed is contained into the Extrude TBuffer
const TBuffer& buffer = m_buffers[buffer_id(GCodeProcessor::EMoveType::Extrude)];
if (buffer.vertices.id == 0 || buffer.indices.id == 0)
return;
// collect color information to generate materials
std::vector<Color> colors;
for (const RenderPath& path : buffer.render_paths) {
colors.push_back(path.color);
}
// save materials file
boost::filesystem::path mat_filename(filename);
mat_filename.replace_extension("mtl");
FILE* fp = boost::nowide::fopen(mat_filename.string().c_str(), "w");
if (fp == nullptr) {
BOOST_LOG_TRIVIAL(error) << "GCodeViewer::export_toolpaths_to_obj: Couldn't open " << mat_filename.string().c_str() << " for writing";
return;
}
fprintf(fp, "# G-Code Toolpaths Materials\n");
fprintf(fp, "# Generated by %s based on Slic3r\n", SLIC3R_BUILD_ID);
unsigned int colors_count = 1;
for (const Color& color : colors)
{
fprintf(fp, "\nnewmtl material_%d\n", colors_count++);
fprintf(fp, "Ka 1 1 1\n");
fprintf(fp, "Kd %f %f %f\n", color[0], color[1], color[2]);
fprintf(fp, "Ks 0 0 0\n");
}
fclose(fp);
// save geometry file
fp = boost::nowide::fopen(filename, "w");
if (fp == nullptr) {
BOOST_LOG_TRIVIAL(error) << "GCodeViewer::export_toolpaths_to_obj: Couldn't open " << filename << " for writing";
return;
}
fprintf(fp, "# G-Code Toolpaths\n");
fprintf(fp, "# Generated by %s based on Slic3r\n", SLIC3R_BUILD_ID);
fprintf(fp, "\nmtllib ./%s\n", mat_filename.filename().string().c_str());
// get vertices data from vertex buffer on gpu
size_t floats_per_vertex = buffer.vertices.vertex_size_floats();
std::vector<float> vertices = std::vector<float>(buffer.vertices.count * floats_per_vertex);
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vertices.id));
glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, 0, buffer.vertices.data_size_bytes(), vertices.data()));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
auto get_vertex = [&vertices, floats_per_vertex](size_t id) {
// extract vertex from vector of floats
size_t base_id = id * floats_per_vertex;
return Vec3f(vertices[base_id + 0], vertices[base_id + 1], vertices[base_id + 2]);
};
struct Segment
{
Vec3f v1;
Vec3f v2;
Vec3f dir;
Vec3f right;
Vec3f up;
Vec3f rl_displacement;
Vec3f tb_displacement;
float length;
};
auto generate_segment = [get_vertex](size_t start_id, float half_width, float half_height) {
auto local_basis = [](const Vec3f& dir) {
// calculate local basis (dir, right, up) on given segment
std::array<Vec3f, 3> ret;
ret[0] = dir.normalized();
if (std::abs(ret[0][2]) < EPSILON) {
// segment parallel to XY plane
ret[1] = { ret[0][1], -ret[0][0], 0.0f };
ret[2] = Vec3f::UnitZ();
}
else if (std::abs(std::abs(ret[0].dot(Vec3f::UnitZ())) - 1.0f) < EPSILON) {
// segment parallel to Z axis
ret[1] = Vec3f::UnitX();
ret[2] = Vec3f::UnitY();
}
else {
ret[0] = dir.normalized();
ret[1] = ret[0].cross(Vec3f::UnitZ()).normalized();
ret[2] = ret[1].cross(ret[0]);
}
return ret;
};
Vec3f v1 = get_vertex(start_id);
Vec3f v2 = get_vertex(start_id + 1);
float length = (v2 - v1).norm();
const auto&& [dir, right, up] = local_basis(v2 - v1);
return Segment({ v1, v2, dir, right, up, half_width * right, half_height * up, length });
};
size_t out_vertices_count = 0;
for (size_t i = 0; i < buffer.render_paths.size(); ++i) {
// get paths segments from buffer paths
const RenderPath& render_path = buffer.render_paths[i];
const Path& path = buffer.paths[render_path.path_id];
float half_width = 0.5f * path.width;
float half_height = 0.5f * path.height;
// generates vertices/normals/triangles
std::vector<Vec3f> out_vertices;
std::vector<Vec3f> out_normals;
using Triangle = std::array<size_t, 3>;
std::vector<Triangle> out_triangles;
for (size_t j = 0; j < render_path.offsets.size(); ++j) {
unsigned int start = static_cast<unsigned int>(render_path.offsets[j] / sizeof(unsigned int));
unsigned int end = start + render_path.sizes[j];
for (size_t k = start; k < end; k += 2) {
Segment curr = generate_segment(k, half_width, half_height);
if (k == start) {
// starting endpoint vertices/normals
out_vertices.push_back(curr.v1 + curr.rl_displacement); out_normals.push_back(curr.right); // right
out_vertices.push_back(curr.v1 + curr.tb_displacement); out_normals.push_back(curr.up); // top
out_vertices.push_back(curr.v1 - curr.rl_displacement); out_normals.push_back(-curr.right); // left
out_vertices.push_back(curr.v1 - curr.tb_displacement); out_normals.push_back(-curr.up); // bottom
out_vertices_count += 4;
// starting cap triangles
size_t base_id = out_vertices_count - 4 + 1;
out_triangles.push_back({ base_id + 0, base_id + 1, base_id + 2 });
out_triangles.push_back({ base_id + 0, base_id + 2, base_id + 3 });
}
else {
// for the endpoint shared by the current and the previous segments
// we keep the top and bottom vertices of the previous vertices
// and add new left/right vertices for the current segment
out_vertices.push_back(curr.v1 + curr.rl_displacement); out_normals.push_back(curr.right); // right
out_vertices.push_back(curr.v1 - curr.rl_displacement); out_normals.push_back(-curr.right); // left
out_vertices_count += 2;
Segment prev = generate_segment(k - 2, half_width, half_height);
Vec3f med_dir = (prev.dir + curr.dir).normalized();
float disp = half_width * ::tan(::acos(std::clamp(curr.dir.dot(med_dir), -1.0f, 1.0f)));
Vec3f disp_vec = disp * prev.dir;
bool is_right_turn = prev.up.dot(prev.dir.cross(curr.dir)) <= 0.0f;
if (prev.dir.dot(curr.dir) < 0.7071068f) {
// if the angle between two consecutive segments is greater than 45 degrees
// we add a cap in the outside corner
// and displace the vertices in the inside corner to the same position, if possible
if (is_right_turn) {
// corner cap triangles (left)
size_t base_id = out_vertices_count - 6 + 1;
out_triangles.push_back({ base_id + 5, base_id + 2, base_id + 1 });
out_triangles.push_back({ base_id + 5, base_id + 3, base_id + 2 });
// update right vertices
if (disp < prev.length) {
base_id = out_vertices.size() - 6;
out_vertices[base_id + 0] -= disp_vec;
out_vertices[base_id + 4] = out_vertices[base_id + 0];
}
}
else {
// corner cap triangles (right)
size_t base_id = out_vertices_count - 6 + 1;
out_triangles.push_back({ base_id + 0, base_id + 4, base_id + 1 });
out_triangles.push_back({ base_id + 0, base_id + 3, base_id + 4 });
// update left vertices
if (disp < prev.length) {
base_id = out_vertices.size() - 6;
out_vertices[base_id + 2] -= disp_vec;
out_vertices[base_id + 5] = out_vertices[base_id + 2];
}
}
}
else {
// if the angle between two consecutive segments is lesser than 45 degrees
// displace the vertices to the same position
if (is_right_turn) {
size_t base_id = out_vertices.size() - 6;
// right
out_vertices[base_id + 0] -= disp_vec;
out_vertices[base_id + 4] = out_vertices[base_id + 0];
// left
out_vertices[base_id + 2] += disp_vec;
out_vertices[base_id + 5] = out_vertices[base_id + 2];
}
else {
size_t base_id = out_vertices.size() - 6;
// right
out_vertices[base_id + 0] += disp_vec;
out_vertices[base_id + 4] = out_vertices[base_id + 0];
// left
out_vertices[base_id + 2] -= disp_vec;
out_vertices[base_id + 5] = out_vertices[base_id + 2];
}
}
}
// current second endpoint vertices/normals
out_vertices.push_back(curr.v2 + curr.rl_displacement); out_normals.push_back(curr.right); // right
out_vertices.push_back(curr.v2 + curr.tb_displacement); out_normals.push_back(curr.up); // top
out_vertices.push_back(curr.v2 - curr.rl_displacement); out_normals.push_back(-curr.right); // left
out_vertices.push_back(curr.v2 - curr.tb_displacement); out_normals.push_back(-curr.up); // bottom
out_vertices_count += 4;
// sides triangles
if (k == start) {
size_t base_id = out_vertices_count - 8 + 1;
out_triangles.push_back({ base_id + 0, base_id + 4, base_id + 5 });
out_triangles.push_back({ base_id + 0, base_id + 5, base_id + 1 });
out_triangles.push_back({ base_id + 1, base_id + 5, base_id + 6 });
out_triangles.push_back({ base_id + 1, base_id + 6, base_id + 2 });
out_triangles.push_back({ base_id + 2, base_id + 6, base_id + 7 });
out_triangles.push_back({ base_id + 2, base_id + 7, base_id + 3 });
out_triangles.push_back({ base_id + 3, base_id + 7, base_id + 4 });
out_triangles.push_back({ base_id + 3, base_id + 4, base_id + 0 });
}
else {
size_t base_id = out_vertices_count - 10 + 1;
out_triangles.push_back({ base_id + 4, base_id + 6, base_id + 7 });
out_triangles.push_back({ base_id + 4, base_id + 7, base_id + 1 });
out_triangles.push_back({ base_id + 1, base_id + 7, base_id + 8 });
out_triangles.push_back({ base_id + 1, base_id + 8, base_id + 5 });
out_triangles.push_back({ base_id + 5, base_id + 8, base_id + 9 });
out_triangles.push_back({ base_id + 5, base_id + 9, base_id + 3 });
out_triangles.push_back({ base_id + 3, base_id + 9, base_id + 6 });
out_triangles.push_back({ base_id + 3, base_id + 6, base_id + 4 });
}
if (k + 2 == end) {
// ending cap triangles
size_t base_id = out_vertices_count - 4 + 1;
out_triangles.push_back({ base_id + 0, base_id + 2, base_id + 1 });
out_triangles.push_back({ base_id + 0, base_id + 3, base_id + 2 });
}
}
}
// save to file
fprintf(fp, "\n# vertices path %lld\n", i + 1);
for (const Vec3f& v : out_vertices) {
fprintf(fp, "v %g %g %g\n", v[0], v[1], v[2]);
}
fprintf(fp, "\n# normals path %lld\n", i + 1);
for (const Vec3f& n : out_normals) {
fprintf(fp, "vn %g %g %g\n", n[0], n[1], n[2]);
}
fprintf(fp, "\n# material path %lld\n", i + 1);
fprintf(fp, "usemtl material_%lld\n", i + 1);
fprintf(fp, "\n# triangles path %lld\n", i + 1);
for (const Triangle& t : out_triangles) {
fprintf(fp, "f %lld//%lld %lld//%lld %lld//%lld\n", t[0], t[0], t[1], t[1], t[2], t[2]);
}
}
// fprintf(fp, "\n#vertices count %lld\n", out_vertices_count);
fclose(fp);
}
void GCodeViewer::init_shaders() void GCodeViewer::init_shaders()
{ {
unsigned char begin_id = buffer_id(GCodeProcessor::EMoveType::Retract); unsigned char begin_id = buffer_id(GCodeProcessor::EMoveType::Retract);
@ -641,7 +920,6 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
const std::vector<unsigned int>& buffer_indices = indices[i]; const std::vector<unsigned int>& buffer_indices = indices[i];
buffer.indices.count = buffer_indices.size(); buffer.indices.count = buffer_indices.size();
#if ENABLE_GCODE_VIEWER_STATISTICS #if ENABLE_GCODE_VIEWER_STATISTICS
m_statistics.indices_size += SLIC3R_STDVEC_MEMSIZE(buffer_indices, unsigned int);
m_statistics.indices_gpu_size += buffer.indices.count * sizeof(unsigned int); m_statistics.indices_gpu_size += buffer.indices.count * sizeof(unsigned int);
#endif // ENABLE_GCODE_VIEWER_STATISTICS #endif // ENABLE_GCODE_VIEWER_STATISTICS
@ -876,6 +1154,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
if (it == buffer->render_paths.end()) { if (it == buffer->render_paths.end()) {
it = buffer->render_paths.insert(buffer->render_paths.end(), RenderPath()); it = buffer->render_paths.insert(buffer->render_paths.end(), RenderPath());
it->color = color; it->color = color;
it->path_id = id;
} }
unsigned int size = std::min(m_sequential_view.current.last, path.last.s_id) - std::max(m_sequential_view.current.first, path.first.s_id) + 1; unsigned int size = std::min(m_sequential_view.current.last, path.last.s_id) - std::max(m_sequential_view.current.first, path.first.s_id) + 1;
@ -1482,12 +1761,6 @@ void GCodeViewer::render_statistics() const
ImGui::Separator(); ImGui::Separator();
ImGui::PushStyleColor(ImGuiCol_Text, ORANGE);
imgui.text(std::string("Indices CPU:"));
ImGui::PopStyleColor();
ImGui::SameLine(offset);
imgui.text(std::to_string(m_statistics.indices_size) + " bytes");
ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); ImGui::PushStyleColor(ImGuiCol_Text, ORANGE);
imgui.text(std::string("Paths CPU:")); imgui.text(std::string("Paths CPU:"));
ImGui::PopStyleColor(); ImGui::PopStyleColor();

View file

@ -108,6 +108,7 @@ class GCodeViewer
struct RenderPath struct RenderPath
{ {
Color color; Color color;
size_t path_id;
std::vector<unsigned int> sizes; std::vector<unsigned int> sizes;
std::vector<size_t> offsets; // use size_t because we need the pointer's size (used in the call glMultiDrawElements()) std::vector<size_t> offsets; // use size_t because we need the pointer's size (used in the call glMultiDrawElements())
}; };
@ -201,7 +202,6 @@ class GCodeViewer
// memory // memory
long long results_size{ 0 }; long long results_size{ 0 };
long long vertices_gpu_size{ 0 }; long long vertices_gpu_size{ 0 };
long long indices_size{ 0 };
long long indices_gpu_size{ 0 }; long long indices_gpu_size{ 0 };
long long paths_size{ 0 }; long long paths_size{ 0 };
long long render_paths_size{ 0 }; long long render_paths_size{ 0 };
@ -231,7 +231,6 @@ class GCodeViewer
void reset_sizes() { void reset_sizes() {
results_size = 0; results_size = 0;
vertices_gpu_size = 0; vertices_gpu_size = 0;
indices_size = 0;
indices_gpu_size = 0; indices_gpu_size = 0;
paths_size = 0; paths_size = 0;
render_paths_size = 0; render_paths_size = 0;
@ -397,6 +396,8 @@ public:
bool is_legend_enabled() const { return m_legend_enabled; } bool is_legend_enabled() const { return m_legend_enabled; }
void enable_legend(bool enable) { m_legend_enabled = enable; } void enable_legend(bool enable) { m_legend_enabled = enable; }
void export_toolpaths_to_obj(const char* filename) const;
private: private:
void init_shaders(); void init_shaders();
void load_toolpaths(const GCodeProcessor::Result& gcode_result); void load_toolpaths(const GCodeProcessor::Result& gcode_result);

View file

@ -4349,12 +4349,20 @@ void GLCanvas3D::update_tooltip_for_settings_item_in_main_toolbar()
bool GLCanvas3D::has_toolpaths_to_export() const bool GLCanvas3D::has_toolpaths_to_export() const
{ {
#if ENABLE_GCODE_VIEWER
return m_gcode_viewer.has_data();
#else
return m_volumes.has_toolpaths_to_export(); return m_volumes.has_toolpaths_to_export();
#endif // ENABLE_GCODE_VIEWER
} }
void GLCanvas3D::export_toolpaths_to_obj(const char* filename) const void GLCanvas3D::export_toolpaths_to_obj(const char* filename) const
{ {
#if ENABLE_GCODE_VIEWER
m_gcode_viewer.export_toolpaths_to_obj(filename);
#else
m_volumes.export_toolpaths_to_obj(filename); m_volumes.export_toolpaths_to_obj(filename);
#endif // ENABLE_GCODE_VIEWER
} }
void GLCanvas3D::mouse_up_cleanup() void GLCanvas3D::mouse_up_cleanup()

View file

@ -1373,9 +1373,13 @@ void MainFrame::init_gcodeviewer_menubar()
wxMenu* fileMenu = new wxMenu; wxMenu* fileMenu = new wxMenu;
{ {
append_menu_item(fileMenu, wxID_ANY, _L("&Open G-code") + dots + "\tCtrl+O", _L("Open a G-code file"), append_menu_item(fileMenu, wxID_ANY, _L("&Open G-code") + dots + "\tCtrl+O", _L("Open a G-code file"),
[this](wxCommandEvent&) { if (m_plater) m_plater->load_gcode(); }, "open", nullptr, [this](wxCommandEvent&) { if (m_plater != nullptr) m_plater->load_gcode(); }, "open", nullptr,
[this]() {return m_plater != nullptr; }, this); [this]() {return m_plater != nullptr; }, this);
fileMenu->AppendSeparator(); fileMenu->AppendSeparator();
append_menu_item(fileMenu, wxID_ANY, _L("Export &toolpaths as OBJ") + dots, _L("Export toolpaths as OBJ"),
[this](wxCommandEvent&) { if (m_plater != nullptr) m_plater->export_toolpaths_to_obj(); }, "export_plater", nullptr,
[this]() {return can_export_toolpaths(); }, this);
fileMenu->AppendSeparator();
append_menu_item(fileMenu, wxID_ANY, _L("Exit &G-code preview"), _L("Switch to editor mode"), append_menu_item(fileMenu, wxID_ANY, _L("Exit &G-code preview"), _L("Switch to editor mode"),
[this](wxCommandEvent&) { set_mode(EMode::Editor); }); [this](wxCommandEvent&) { set_mode(EMode::Editor); });
fileMenu->AppendSeparator(); fileMenu->AppendSeparator();