diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index ad2d9305f..8d1a154b4 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -25,6 +25,8 @@ #include #include +#include + #include #include @@ -853,6 +855,209 @@ std::string GLVolumeCollection::log_memory_info() const return " (GLVolumeCollection RAM: " + format_memsize_MB(this->cpu_memory_used()) + " GPU: " + format_memsize_MB(this->gpu_memory_used()) + " Both: " + format_memsize_MB(this->gpu_memory_used()) + ")"; } +bool can_export_to_obj(const GLVolume& volume) +{ + if (!volume.is_active || !volume.is_extrusion_path) + return false; + + if (volume.indexed_vertex_array.triangle_indices.empty() && (std::min(volume.indexed_vertex_array.triangle_indices_size, volume.tverts_range.second - volume.tverts_range.first) == 0)) + return false; + + if (volume.indexed_vertex_array.quad_indices.empty() && (std::min(volume.indexed_vertex_array.quad_indices_size, volume.qverts_range.second - volume.qverts_range.first) == 0)) + return false; + + return true; +} + +bool GLVolumeCollection::has_toolpaths_to_export() const +{ + for (const GLVolume* volume : this->volumes) + { + if (can_export_to_obj(*volume)) + return true; + } + + return false; +} + +void GLVolumeCollection::export_toolpaths_to_obj(const char* filename) const +{ + if (filename == nullptr) + return; + + if (!has_toolpaths_to_export()) + return; + + // collect color information to generate materials + std::set> colors; + for (const GLVolume* volume : this->volumes) + { + if (!can_export_to_obj(*volume)) + continue; + + std::array color; + ::memcpy((void*)color.data(), (const void*)volume->color, 4 * sizeof(float)); + colors.insert(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) << "GLVolumeCollection::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 std::array& 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) << "GLVolumeCollection::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()); + + unsigned int vertices_count = 0; + unsigned int volumes_count = 0; + + for (const GLVolume* volume : this->volumes) + { + if (!can_export_to_obj(*volume)) + continue; + + std::vector vertices_and_normals_interleaved; + std::vector triangle_indices; + std::vector quad_indices; + + if (!volume->indexed_vertex_array.vertices_and_normals_interleaved.empty()) + // data are in CPU memory + vertices_and_normals_interleaved = volume->indexed_vertex_array.vertices_and_normals_interleaved; + else if ((volume->indexed_vertex_array.vertices_and_normals_interleaved_VBO_id != 0) && (volume->indexed_vertex_array.vertices_and_normals_interleaved_size != 0)) + { + // data are in GPU memory + vertices_and_normals_interleaved = std::vector(volume->indexed_vertex_array.vertices_and_normals_interleaved_size, 0.0f); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, volume->indexed_vertex_array.vertices_and_normals_interleaved_VBO_id)); + glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, 0, vertices_and_normals_interleaved.size() * sizeof(float), vertices_and_normals_interleaved.data())); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + } + else + continue; + + if (!volume->indexed_vertex_array.triangle_indices.empty()) + { + // data are in CPU memory + size_t size = std::min(volume->indexed_vertex_array.triangle_indices.size(), volume->tverts_range.second - volume->tverts_range.first); + if (size != 0) + { + std::vector::const_iterator it_begin = volume->indexed_vertex_array.triangle_indices.begin() + volume->tverts_range.first; + std::vector::const_iterator it_end = volume->indexed_vertex_array.triangle_indices.begin() + volume->tverts_range.first + size; + std::copy(it_begin, it_end, std::back_inserter(triangle_indices)); + } + } + else if ((volume->indexed_vertex_array.triangle_indices_VBO_id != 0) && (volume->indexed_vertex_array.triangle_indices_size != 0)) + { + // data are in GPU memory + size_t size = std::min(volume->indexed_vertex_array.triangle_indices_size, volume->tverts_range.second - volume->tverts_range.first); + if (size != 0) + { + triangle_indices = std::vector(size, 0); + + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, volume->indexed_vertex_array.triangle_indices_VBO_id)); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, volume->tverts_range.first * sizeof(int), size * sizeof(int), triangle_indices.data())); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } + } + + if (!volume->indexed_vertex_array.quad_indices.empty()) + { + // data are in CPU memory + size_t size = std::min(volume->indexed_vertex_array.quad_indices.size(), volume->qverts_range.second - volume->qverts_range.first); + if (size != 0) + { + std::vector::const_iterator it_begin = volume->indexed_vertex_array.quad_indices.begin() + volume->qverts_range.first; + std::vector::const_iterator it_end = volume->indexed_vertex_array.quad_indices.begin() + volume->qverts_range.first + size; + std::copy(it_begin, it_end, std::back_inserter(quad_indices)); + } + } + else if ((volume->indexed_vertex_array.quad_indices_VBO_id != 0) && (volume->indexed_vertex_array.quad_indices_size != 0)) + { + // data are in GPU memory + size_t size = std::min(volume->indexed_vertex_array.quad_indices_size, volume->qverts_range.second - volume->qverts_range.first); + if (size != 0) + { + quad_indices = std::vector(size, 0); + + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, volume->indexed_vertex_array.quad_indices_VBO_id)); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, volume->qverts_range.first * sizeof(int), size * sizeof(int), quad_indices.data())); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } + } + + if (triangle_indices.empty() && quad_indices.empty()) + continue; + + fprintf(fp, "\n# vertices volume %d\n", volumes_count); + for (unsigned int i = 0; i < vertices_and_normals_interleaved.size(); i += 6) + { + fprintf(fp, "v %f %f %f\n", vertices_and_normals_interleaved[i + 3], vertices_and_normals_interleaved[i + 4], vertices_and_normals_interleaved[i + 5]); + } + + fprintf(fp, "\n# normals volume %d\n", volumes_count); + for (unsigned int i = 0; i < vertices_and_normals_interleaved.size(); i += 6) + { + fprintf(fp, "vn %f %f %f\n", vertices_and_normals_interleaved[i + 0], vertices_and_normals_interleaved[i + 1], vertices_and_normals_interleaved[i + 2]); + } + + std::array color; + ::memcpy((void*)color.data(), (const void*)volume->color, 4 * sizeof(float)); + colors.insert(color); + fprintf(fp, "\n# material volume %d\n", volumes_count); + fprintf(fp, "usemtl material_%lld\n", 1 + std::distance(colors.begin(), colors.find(color))); + + fprintf(fp, "\n# triangular facets volume %d\n", volumes_count); + for (unsigned int i = 0; i < triangle_indices.size(); i += 3) + { + int id_v1 = vertices_count + 1 + triangle_indices[i + 0]; + int id_v2 = vertices_count + 1 + triangle_indices[i + 1]; + int id_v3 = vertices_count + 1 + triangle_indices[i + 2]; + fprintf(fp, "f %d//%d %d//%d %d//%d\n", id_v1, id_v1, id_v2, id_v2, id_v3, id_v3); + } + + fprintf(fp, "\n# quadrangular facets volume %d\n", volumes_count); + for (unsigned int i = 0; i < quad_indices.size(); i += 4) + { + int id_v1 = vertices_count + 1 + quad_indices[i + 0]; + int id_v2 = vertices_count + 1 + quad_indices[i + 1]; + int id_v3 = vertices_count + 1 + quad_indices[i + 2]; + int id_v4 = vertices_count + 1 + quad_indices[i + 3]; + fprintf(fp, "f %d//%d %d//%d %d//%d %d//%d\n", id_v1, id_v1, id_v2, id_v2, id_v3, id_v3, id_v4, id_v4); + } + + ++volumes_count; + vertices_count += vertices_and_normals_interleaved.size() / 6; + } + + fclose(fp); +} + // caller is responsible for supplying NO lines with zero length static void thick_lines_to_indexed_vertex_array( const Lines &lines, diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 482c2f580..e0603ebc0 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -439,7 +439,7 @@ public: bool empty() const { return this->indexed_vertex_array.empty(); } - void set_range(coordf_t low, coordf_t high); + void set_range(double low, double high); void render() const; void render(int color_id, int detection_id, int worldmatrix_id) const; @@ -564,6 +564,10 @@ public: // Return CPU, GPU and total memory log line. std::string log_memory_info() const; + bool has_toolpaths_to_export() const; + // 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; + private: GLVolumeCollection(const GLVolumeCollection &other); GLVolumeCollection& operator=(const GLVolumeCollection &); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 981cb38a9..67f30c6e4 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3403,6 +3403,16 @@ void GLCanvas3D::msw_rescale() m_warning_texture.msw_rescale(*this); } +bool GLCanvas3D::has_toolpaths_to_export() const +{ + return m_volumes.has_toolpaths_to_export(); +} + +void GLCanvas3D::export_toolpaths_to_obj(const char* filename) const +{ + m_volumes.export_toolpaths_to_obj(filename); +} + bool GLCanvas3D::_is_shown_on_screen() const { return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false; diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 577682fe2..d23e29478 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -644,6 +644,9 @@ public: void get_undoredo_toolbar_additional_tooltip(unsigned int item_id, std::string& text) { return m_undoredo_toolbar.get_additional_tooltip(item_id, text); } void set_undoredo_toolbar_additional_tooltip(unsigned int item_id, const std::string& text) { m_undoredo_toolbar.set_additional_tooltip(item_id, text); } + bool has_toolpaths_to_export() const; + void export_toolpaths_to_obj(const char* filename) const; + private: bool _is_shown_on_screen() const; diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index b626bd7bd..401b17a4b 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -130,6 +130,8 @@ public: void update_view_type(); + bool is_loaded() const { return m_loaded; } + private: bool init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 12a38d2fc..6e3d1406b 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -247,6 +247,11 @@ bool MainFrame::can_export_model() const return (m_plater != nullptr) && !m_plater->model().objects.empty(); } +bool MainFrame::can_export_toolpaths() const +{ + return (m_plater != nullptr) && (m_plater->printer_technology() == ptFFF) && m_plater->is_preview_shown() && m_plater->is_preview_loaded() && m_plater->has_toolpaths_to_export(); +} + bool MainFrame::can_export_supports() const { if ((m_plater == nullptr) || (m_plater->printer_technology() != ptSLA) || m_plater->model().objects.empty()) @@ -473,13 +478,17 @@ void MainFrame::init_menubar() append_menu_item(export_menu, wxID_ANY, _(L("Export plate as &STL")) + dots, _(L("Export current plate as STL")), [this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(); }, menu_icon("export_plater"), nullptr, [this](){return can_export_model(); }, this); - append_menu_item(export_menu, wxID_ANY, _(L("Export plate as STL including supports")) + dots, _(L("Export current plate as STL including supports")), + append_menu_item(export_menu, wxID_ANY, _(L("Export plate as STL &including supports")) + dots, _(L("Export current plate as STL including supports")), [this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(true); }, menu_icon("export_plater"), nullptr, [this](){return can_export_supports(); }, this); append_menu_item(export_menu, wxID_ANY, _(L("Export plate as &AMF")) + dots, _(L("Export current plate as AMF")), [this](wxCommandEvent&) { if (m_plater) m_plater->export_amf(); }, menu_icon("export_plater"), nullptr, [this](){return can_export_model(); }, this); export_menu->AppendSeparator(); + append_menu_item(export_menu, wxID_ANY, _(L("Export &toolpaths as OBJ")) + dots, _(L("Export toolpaths as OBJ")), + [this](wxCommandEvent&) { if (m_plater) m_plater->export_toolpaths_to_obj(); }, menu_icon("export_plater"), nullptr, + [this]() {return can_export_toolpaths(); }, this); + export_menu->AppendSeparator(); append_menu_item(export_menu, wxID_ANY, _(L("Export &Config")) +dots +"\tCtrl+E", _(L("Export current configuration to file")), [this](wxCommandEvent&) { export_config(); }, menu_icon("export_config")); append_menu_item(export_menu, wxID_ANY, _(L("Export Config &Bundle")) + dots, _(L("Export all presets to file")), diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 0e8a053e0..aa1e3d500 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -65,6 +65,7 @@ class MainFrame : public DPIFrame bool can_start_new_project() const; bool can_save() const; bool can_export_model() const; + bool can_export_toolpaths() const; bool can_export_supports() const; bool can_export_gcode() const; bool can_send_gcode() const; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 476caead1..bb6c1e2f6 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1763,6 +1763,11 @@ struct Plater::priv void select_view(const std::string& direction); void select_view_3D(const std::string& name); void select_next_view_3D(); + + bool is_preview_shown() const { return current_panel == preview; } + bool is_preview_loaded() const { return preview->is_loaded(); } + bool is_view3D_shown() const { return current_panel == view3D; } + void reset_all_gizmos(); void update_ui_from_settings(); ProgressStatusBar* statusbar(); @@ -2457,6 +2462,7 @@ wxString Plater::priv::get_export_file(GUI::FileType file_type) case FT_AMF: case FT_3MF: case FT_GCODE: + case FT_OBJ: wildcard = file_wildcards(file_type); break; default: @@ -2507,6 +2513,12 @@ wxString Plater::priv::get_export_file(GUI::FileType file_type) dlg_title = _(L("Save file as:")); break; } + case FT_OBJ: + { + output_file.replace_extension("obj"); + dlg_title = _(L("Export OBJ file:")); + break; + } default: break; } @@ -4095,6 +4107,10 @@ void Plater::select_view(const std::string& direction) { p->select_view(directio void Plater::select_view_3D(const std::string& name) { p->select_view_3D(name); } +bool Plater::is_preview_shown() const { return p->is_preview_shown(); } +bool Plater::is_preview_loaded() const { return p->is_preview_loaded(); } +bool Plater::is_view3D_shown() const { return p->is_view3D_shown(); } + void Plater::select_all() { p->select_all(); } void Plater::deselect_all() { p->deselect_all(); } @@ -4418,6 +4434,24 @@ void Plater::export_3mf(const boost::filesystem::path& output_path) } } +bool Plater::has_toolpaths_to_export() const +{ + return p->preview->get_canvas3d()->has_toolpaths_to_export(); +} + +void Plater::export_toolpaths_to_obj() const +{ + if ((printer_technology() != ptFFF) || !is_preview_loaded()) + return; + + wxString path = p->get_export_file(FT_OBJ); + if (path.empty()) + return; + + wxBusyCursor wait; + p->preview->get_canvas3d()->export_toolpaths_to_obj(into_u8(path).c_str()); +} + void Plater::reslice() { // Stop arrange and (or) optimize rotation tasks. diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index d7f7f3791..0bd01835e 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -156,6 +156,10 @@ public: void select_view(const std::string& direction); void select_view_3D(const std::string& name); + bool is_preview_shown() const; + bool is_preview_loaded() const; + bool is_view3D_shown() const; + // Called after the Preferences dialog is closed and the program settings are saved. // Update the UI based on the current preferences. void update_ui_from_settings(); @@ -179,6 +183,8 @@ public: void export_stl(bool extended = false, bool selection_only = false); void export_amf(); void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); + bool has_toolpaths_to_export() const; + void export_toolpaths_to_obj() const; void reslice(); void reslice_SLA_supports(const ModelObject &object); void changed_object(int obj_idx);