diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index f53e4a55c2..c9c7b72f37 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -1017,6 +1017,7 @@ bool GLVolumeCollection::has_toolpaths_to_export() const return false; } +#if !ENABLE_GCODE_VIEWER void GLVolumeCollection::export_toolpaths_to_obj(const char* filename) const { if (filename == nullptr) @@ -1298,6 +1299,7 @@ void GLVolumeCollection::export_toolpaths_to_obj(const char* filename) const fclose(fp); } +#endif // !ENABLE_GCODE_VIEWER // caller is responsible for supplying NO lines with zero length static void thick_lines_to_indexed_vertex_array( diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 1b4d0fc4f2..7e8ae6fe33 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -597,8 +597,10 @@ public: std::string log_memory_info() 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 void export_toolpaths_to_obj(const char* filename) const; +#endif // !ENABLE_GCODE_VIEWER private: GLVolumeCollection(const GLVolumeCollection &other); diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 6d6ba557e2..688134f7c9 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -487,6 +488,284 @@ void GCodeViewer::set_layers_z_range(const std::array& layers_z_range 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 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 vertices = std::vector(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 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 out_vertices; + std::vector out_normals; + using Triangle = std::array; + std::vector out_triangles; + for (size_t j = 0; j < render_path.offsets.size(); ++j) { + unsigned int start = static_cast(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() { 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& buffer_indices = indices[i]; buffer.indices.count = buffer_indices.size(); #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); #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()) { it = buffer->render_paths.insert(buffer->render_paths.end(), RenderPath()); 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; @@ -1482,12 +1761,6 @@ void GCodeViewer::render_statistics() const 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.text(std::string("Paths CPU:")); ImGui::PopStyleColor(); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 5cddeaa75a..137ae89af7 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -108,6 +108,7 @@ class GCodeViewer struct RenderPath { Color color; + size_t path_id; std::vector sizes; std::vector offsets; // use size_t because we need the pointer's size (used in the call glMultiDrawElements()) }; @@ -201,7 +202,6 @@ class GCodeViewer // memory long long results_size{ 0 }; long long vertices_gpu_size{ 0 }; - long long indices_size{ 0 }; long long indices_gpu_size{ 0 }; long long paths_size{ 0 }; long long render_paths_size{ 0 }; @@ -231,7 +231,6 @@ class GCodeViewer void reset_sizes() { results_size = 0; vertices_gpu_size = 0; - indices_size = 0; indices_gpu_size = 0; paths_size = 0; render_paths_size = 0; @@ -397,6 +396,8 @@ public: bool is_legend_enabled() const { return m_legend_enabled; } void enable_legend(bool enable) { m_legend_enabled = enable; } + void export_toolpaths_to_obj(const char* filename) const; + private: void init_shaders(); void load_toolpaths(const GCodeProcessor::Result& gcode_result); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 0ee30a8084..f8ff9406f3 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4349,12 +4349,20 @@ void GLCanvas3D::update_tooltip_for_settings_item_in_main_toolbar() 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(); +#endif // ENABLE_GCODE_VIEWER } 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); +#endif // ENABLE_GCODE_VIEWER } void GLCanvas3D::mouse_up_cleanup() diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 5e26979c3c..9c98f0f2e5 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1373,9 +1373,13 @@ void MainFrame::init_gcodeviewer_menubar() wxMenu* fileMenu = new wxMenu; { 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); 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"), [this](wxCommandEvent&) { set_mode(EMode::Editor); }); fileMenu->AppendSeparator();