diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index ba898d9d5..e634dd138 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -556,19 +556,9 @@ std::string Model::propose_export_file_name_and_path() const for (const ModelObject *model_object : this->objects) for (ModelInstance *model_instance : model_object->instances) if (model_instance->is_printable()) { - input_file = model_object->input_file; - if (! model_object->name.empty()) { - if (input_file.empty()) - // model_object->input_file was empty, just use model_object->name - input_file = model_object->name; - else { - // Replace file name in input_file with model_object->name, but keep the path and file extension. - input_file = (boost::filesystem::path(model_object->name).parent_path().empty()) ? - (boost::filesystem::path(input_file).parent_path() / model_object->name).make_preferred().string() : - model_object->name; - } - } - if (! input_file.empty()) + input_file = model_object->get_export_filename(); + + if (!input_file.empty()) goto end; // Other instances will produce the same name, skip them. break; @@ -1433,6 +1423,26 @@ void ModelObject::print_info() const cout << "volume = " << mesh.volume() << endl; } +std::string ModelObject::get_export_filename() const +{ + std::string ret = input_file; + + if (!name.empty()) + { + if (ret.empty()) + // input_file was empty, just use name + ret = name; + else + { + // Replace file name in input_file with name, but keep the path and file extension. + ret = (boost::filesystem::path(name).parent_path().empty()) ? + (boost::filesystem::path(ret).parent_path() / name).make_preferred().string() : name; + } + } + + return ret; +} + void ModelVolume::set_material_id(t_model_material_id material_id) { m_material_id = material_id; diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 5cf7f49ca..951401243 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -275,6 +275,8 @@ public: // Print object statistics to console. void print_info() const; + std::string get_export_filename() const; + protected: friend class Print; friend class SLAPrint; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 794390133..c13f0bc2a 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -10,7 +10,7 @@ #include "GCode/WipeTowerPrusaMM.hpp" #include "Utils.hpp" -#include "PrintExport.hpp" +//#include "PrintExport.hpp" #include #include diff --git a/src/libslic3r/PrintExport.hpp b/src/libslic3r/PrintExport.hpp index 04b993a52..ce62f7cb0 100644 --- a/src/libslic3r/PrintExport.hpp +++ b/src/libslic3r/PrintExport.hpp @@ -7,6 +7,7 @@ #include #include +#include #include "Rasterizer/Rasterizer.hpp" //#include @@ -72,7 +73,8 @@ public: void finish_layer(); // Save all the layers into the file (or dir) specified in the path argument - void save(const std::string& path); + // An optional project name can be added to be used for the layer file names + void save(const std::string& path, const std::string& projectname = ""); // Save only the selected layer to the file specified in path argument. void save_layer(unsigned lyr, const std::string& path); @@ -86,7 +88,8 @@ template struct VeryFalse { static const bool value = false; }; template class LayerWriter { public: - LayerWriter(const std::string& /*zipfile_path*/) { + LayerWriter(const std::string& /*zipfile_path*/) + { static_assert(VeryFalse::value, "No layer writer implementation provided!"); } @@ -99,10 +102,6 @@ public: void binary_entry(const std::string& /*fname*/, const std::uint8_t* buf, size_t len); - // Get the name of the archive but only the name part without the path or - // the extension. - std::string get_name() { return ""; } - // Test whether the object can still be used for writing. bool is_ok() { return false; } @@ -253,12 +252,14 @@ public: } template - inline void save(const std::string& path) { + inline void save(const std::string& fpath, const std::string& prjname = "") + { try { - LayerWriter writer(path); + LayerWriter writer(fpath); if(!writer.is_ok()) return; - std::string project = writer.get_name(); + std::string project = prjname.empty()? + boost::filesystem::path(fpath).stem().string() : prjname; writer.next_entry("config.ini"); if(!writer.is_ok()) return; diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index a1e382acb..d4443d915 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -320,10 +320,8 @@ struct SLAPrintStatistics } }; -struct SLAminzZipper {}; - // The implementation of creating zipped archives with wxWidgets -template<> class LayerWriter { +template<> class LayerWriter { Zipper m_zip; public: @@ -332,16 +330,12 @@ public: void next_entry(const std::string& fname) { m_zip.add_entry(fname); } void binary_entry(const std::string& fname, - const std::uint8_t* buf, - size_t l) + const std::uint8_t* buf, + size_t l) { m_zip.add_entry(fname, buf, l); } - std::string get_name() const { - return m_zip.get_name(); - } - template inline LayerWriter& operator<<(T&& arg) { m_zip << std::forward(arg); return *this; } @@ -389,9 +383,11 @@ public: // Returns true if the last step was finished with success. bool finished() const override { return this->is_step_done(slaposSliceSupports) && this->Inherited::is_step_done(slapsRasterize); } - template - void export_raster(const std::string& fname) { - if(m_printer) m_printer->save(fname); + template + inline void export_raster(const std::string& fpath, + const std::string& projectname = "") + { + if(m_printer) m_printer->save(fpath, projectname); } const PrintObjects& objects() const { return m_objects; } diff --git a/src/libslic3r/Zipper.cpp b/src/libslic3r/Zipper.cpp index 6b7faaddc..4466f1b04 100644 --- a/src/libslic3r/Zipper.cpp +++ b/src/libslic3r/Zipper.cpp @@ -4,7 +4,6 @@ #include "Zipper.hpp" #include "miniz/miniz_zip.h" -#include #include #include "I18N.hpp" @@ -213,10 +212,6 @@ void Zipper::finish_entry() m_entry.clear(); } -std::string Zipper::get_name() const { - return boost::filesystem::path(m_impl->m_zipname).stem().string(); -} - void Zipper::finalize() { finish_entry(); diff --git a/src/libslic3r/Zipper.hpp b/src/libslic3r/Zipper.hpp index 6566dad42..7d95ffdac 100644 --- a/src/libslic3r/Zipper.hpp +++ b/src/libslic3r/Zipper.hpp @@ -81,9 +81,6 @@ public: /// file is up to minz after the erroneous write. void finish_entry(); - /// Gets the name of the archive without the path or extension. - std::string get_name() const; - void finalize(); }; diff --git a/src/slic3r.cpp b/src/slic3r.cpp index 958b66305..780efea7b 100644 --- a/src/slic3r.cpp +++ b/src/slic3r.cpp @@ -397,8 +397,9 @@ int CLI::run(int argc, char **argv) outfile_final = fff_print.print_statistics().finalize_output_path(outfile); } else { outfile = sla_print.output_filepath(outfile); - sla_print.export_raster(outfile); - outfile_final = sla_print.print_statistics().finalize_output_path(outfile); + // We need to finalize the filename beforehand because the export function sets the filename inside the zip metadata + outfile_final = sla_print.print_statistics().finalize_output_path(outfile); + sla_print.export_raster(outfile_final); } if (outfile != outfile_final && Slic3r::rename_file(outfile, outfile_final) != 0) { boost::nowide::cerr << "Renaming file " << outfile << " to " << outfile_final << " failed" << std::endl; diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 2601842ef..c6a73864d 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -98,8 +98,9 @@ void BackgroundSlicingProcess::process_sla() m_print->process(); if (this->set_step_started(bspsGCodeFinalize)) { if (! m_export_path.empty()) { - m_sla_print->export_raster(m_export_path); - m_print->set_status(100, "Masked SLA file exported to " + m_export_path); + const std::string export_path = m_sla_print->print_statistics().finalize_output_path(m_export_path); + m_sla_print->export_raster(export_path); + m_print->set_status(100, "Masked SLA file exported to " + export_path); } else if (! m_upload_job.empty()) { prepare_upload(); } else { @@ -389,7 +390,7 @@ void BackgroundSlicingProcess::prepare_upload() // Generate a unique temp path to which the gcode/zip file is copied/exported boost::filesystem::path source_path = boost::filesystem::temp_directory_path() - / boost::filesystem::unique_path(".printhost.%%%%-%%%%-%%%%-%%%%.gcode"); + / boost::filesystem::unique_path(".Slic3rPE.upload.%%%%-%%%%-%%%%-%%%%"); if (m_print == m_fff_print) { m_print->set_status(95, "Running post-processing scripts"); @@ -399,8 +400,8 @@ void BackgroundSlicingProcess::prepare_upload() run_post_process_scripts(source_path.string(), m_fff_print->config()); m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string()); } else { - m_sla_print->export_raster(source_path.string()); - // TODO: Also finalize upload path like with FFF when there are statistics for SLA print + m_upload_job.upload_data.upload_path = m_sla_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string()); + m_sla_print->export_raster(source_path.string(), m_upload_job.upload_data.upload_path.string()); } m_print->set_status(100, (boost::format("Scheduling upload to `%1%`. See Window -> Print Host Upload Queue") % m_upload_job.printhost->get_host()).str()); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index c725fae43..7148be513 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1025,7 +1025,7 @@ void ObjectList::append_menu_item_fix_through_netfabb(wxMenu* menu) void ObjectList::append_menu_item_export_stl(wxMenu* menu) const { - append_menu_item(menu, wxID_ANY, _(L("Export object as STL")) + dots, "", + append_menu_item(menu, wxID_ANY, _(L("Export as STL")) + dots, "", [](wxCommandEvent&) { wxGetApp().plater()->export_stl(true); }, "", menu); menu->AppendSeparator(); } @@ -1101,6 +1101,7 @@ void ObjectList::create_part_popupmenu(wxMenu *menu) #endif // __WXOSX__ append_menu_item_fix_through_netfabb(menu); + append_menu_item_export_stl(menu); m_menu_item_split_part = append_menu_item_split(menu); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 02d663e93..5eb0d0583 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -15,12 +15,6 @@ namespace Slic3r { namespace GUI { - - - - -// GLGizmoCut - class GLGizmoCutPanel : public wxPanel { public: @@ -192,7 +186,7 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit, co m_imgui->set_next_window_bg_alpha(0.5f); m_imgui->begin(_(L("Cut")), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - ImGui::PushItemWidth(100.0f); + ImGui::PushItemWidth(m_imgui->scaled(5.0f)); bool _value_changed = ImGui::InputDouble("Z", &m_cut_z, 0.0f, 0.0f, "%.2f"); m_imgui->checkbox(_(L("Keep upper part")), m_keep_upper); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 435d9548f..b9eea5bd4 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -38,6 +38,8 @@ #include "libslic3r/SLA/SLARotfinder.hpp" #include "libslic3r/Utils.hpp" +#include "libnest2d/optimizers/nlopt/genetic.hpp" + #include "GUI.hpp" #include "GUI_App.hpp" #include "GUI_ObjectList.hpp" @@ -1199,7 +1201,7 @@ struct Plater::priv BoundingBox scaled_bed_shape_bb() const; std::vector load_files(const std::vector& input_files, bool load_model, bool load_config); std::vector load_model_objects(const ModelObjectPtrs &model_objects); - std::unique_ptr get_export_file(GUI::FileType file_type); + wxString get_export_file(GUI::FileType file_type); const Selection& get_selection() const; Selection& get_selection(); @@ -1617,6 +1619,45 @@ std::vector Plater::priv::load_files(const std::vector& input_ } #if ENABLE_VOLUMES_CENTERING_FIXES } + else if ((wxGetApp().get_mode() == comSimple) && (type_3mf || type_any_amf)) + { + bool advanced = false; + for (const ModelObject* model_object : model.objects) + { + // is there more than one instance ? + if (model_object->instances.size() > 1) + { + advanced = true; + break; + } + + // is there any modifier ? + for (const ModelVolume* model_volume : model_object->volumes) + { + if (!model_volume->is_model_part()) + { + advanced = true; + break; + } + } + + if (advanced) + break; + } + + if (advanced) + { + wxMessageDialog dlg(q, _(L("This file cannot be loaded in simple mode. Do you want to switch to expert mode?\n")), + _(L("Detected advanced data")), wxICON_WARNING | wxYES | wxNO); + if (dlg.ShowModal() == wxID_YES) + { + Slic3r::GUI::wxGetApp().save_mode(comExpert); + view3D->set_as_dirty(); + } + else + return obj_idxs; + } + } #endif // ENABLE_VOLUMES_CENTERING_FIXES #if !ENABLE_VOLUMES_CENTERING_FIXES @@ -1642,7 +1683,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ Slic3r::GUI::show_error(nullptr, wxString::Format(_(L("You can't to add the object(s) from %s because of one or some of them is(are) multi-part")), from_path(filename))); - return std::vector(); + return obj_idxs; } } @@ -1784,7 +1825,7 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mode return obj_idxs; } -std::unique_ptr Plater::priv::get_export_file(GUI::FileType file_type) +wxString Plater::priv::get_export_file(GUI::FileType file_type) { wxString wildcard; switch (file_type) { @@ -1801,34 +1842,56 @@ std::unique_ptr Plater::priv::get_export_file(GUI::FileType // Update printbility state of each of the ModelInstances. this->update_print_volume_state(); - // Find the file name of the first printable object. - fs::path output_file = this->model.propose_export_file_name_and_path(); + const Selection& selection = get_selection(); + int obj_idx = selection.get_object_idx(); + + fs::path output_file; + // first try to get the file name from the current selection + if ((0 <= obj_idx) && (obj_idx < (int)this->model.objects.size())) + output_file = this->model.objects[obj_idx]->get_export_filename(); + + if (output_file.empty()) + // Find the file name of the first printable object. + output_file = this->model.propose_export_file_name_and_path(); + + wxString dlg_title; switch (file_type) { - case FT_STL: output_file.replace_extension("stl"); break; - case FT_AMF: output_file.replace_extension("zip.amf"); break; // XXX: Problem on OS X with double extension? - case FT_3MF: output_file.replace_extension("3mf"); break; + case FT_STL: + { + output_file.replace_extension("stl"); + dlg_title = _(L("Export STL file:")); + break; + } + case FT_AMF: + { + // XXX: Problem on OS X with double extension? + output_file.replace_extension("zip.amf"); + dlg_title = _(L("Export AMF file:")); + break; + } + case FT_3MF: + { + output_file.replace_extension("3mf"); + dlg_title = _(L("Save file as:")); + break; + } default: break; } - auto dlg = Slic3r::make_unique(q, - ((file_type == FT_AMF) || (file_type == FT_3MF)) ? _(L("Export print config")) : "", - true, - _(L("Save file as:")), - from_path(output_file.parent_path()), - from_path(output_file.filename()), - wildcard, - wxFD_SAVE | wxFD_OVERWRITE_PROMPT - ); + wxFileDialog* dlg = new wxFileDialog(q, dlg_title, + from_path(output_file.parent_path()), from_path(output_file.filename()), + wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (dlg->ShowModal() != wxID_OK) { - return nullptr; + return wxEmptyString; } - fs::path path(into_path(dlg->GetPath())); + wxString out_path = dlg->GetPath(); + fs::path path(into_path(out_path)); wxGetApp().app_config->update_last_output_dir(path.parent_path().string()); - return dlg; + return out_path; } const Selection& Plater::priv::get_selection() const @@ -2039,7 +2102,9 @@ void Plater::priv::sla_optimize_rotation() { rotoptimizing.store(true); int obj_idx = get_selected_object_idx(); - ModelObject * o = model.objects[obj_idx]; + if(obj_idx < 0) { rotoptimizing.store(false); return; } + + ModelObject * o = model.objects[size_t(obj_idx)]; background_process.stop(); @@ -2047,7 +2112,7 @@ void Plater::priv::sla_optimize_rotation() { statusbar()->set_range(100); auto stfn = [this] (unsigned st, const std::string& msg) { - statusbar()->set_progress(st); + statusbar()->set_progress(int(st)); statusbar()->set_status_text(msg); // could be problematic, but we need the cancel button. @@ -2065,8 +2130,59 @@ void Plater::priv::sla_optimize_rotation() { [this](){ return !rotoptimizing.load(); } ); + const auto *bed_shape_opt = config->opt("bed_shape"); + assert(bed_shape_opt); + + auto& bedpoints = bed_shape_opt->values; + Polyline bed; bed.points.reserve(bedpoints.size()); + for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); + + double mindist = 6.0; // FIXME + double offs = mindist / 2.0 - EPSILON; + if(rotoptimizing.load()) // wasn't canceled - for(ModelInstance * oi : o->instances) oi->set_rotation({r[X], r[Y], r[Z]}); + for(ModelInstance * oi : o->instances) { + oi->set_rotation({r[X], r[Y], r[Z]}); + + auto trchull = o->convex_hull_2d(oi->get_transformation().get_matrix()); + + namespace opt = libnest2d::opt; + opt::StopCriteria stopcr; + stopcr.relative_score_difference = 0.01; + stopcr.max_iterations = 10000; + stopcr.stop_score = 0.0; + opt::GeneticOptimizer solver(stopcr); + Polygon pbed(bed); + + auto bin = pbed.bounding_box(); + double binw = bin.size()(X) * SCALING_FACTOR - offs; + double binh = bin.size()(Y) * SCALING_FACTOR - offs; + + auto result = solver.optimize_min([&trchull, binw, binh](double rot){ + auto chull = trchull; + chull.rotate(rot); + + auto bb = chull.bounding_box(); + double bbw = bb.size()(X) * SCALING_FACTOR; + double bbh = bb.size()(Y) * SCALING_FACTOR; + + auto wdiff = bbw - binw; + auto hdiff = bbh - binh; + double diff = 0; + if(wdiff < 0 && hdiff < 0) diff = wdiff + hdiff; + if(wdiff > 0) diff += wdiff; + if(hdiff > 0) diff += hdiff; + + return diff; + }, opt::initvals(0.0), opt::bound(-PI/2, PI/2)); + + double r = std::get<0>(result.optimum); + + Vec3d rt = oi->get_rotation(); rt(Z) += r; + oi->set_rotation(rt); + } + + arr::find_new_position(model, o->instances, coord_t(mindist/SCALING_FACTOR), bed); // Correct the z offset of the object which was corrupted be the rotation o->ensure_on_bed(); @@ -2714,7 +2830,10 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ if (is_part) { item_delete = append_menu_item(menu, wxID_ANY, _(L("Delete")) + "\tDel", _(L("Remove the selected object")), [this](wxCommandEvent&) { q->remove_selected(); }, "brick_delete.png"); - } else { + + sidebar->obj_list()->append_menu_item_export_stl(menu); + } + else { wxMenuItem* item_increase = append_menu_item(menu, wxID_ANY, _(L("Increase copies")) + "\t+", _(L("Place one more copy of the selected object")), [this](wxCommandEvent&) { q->increase_instances(); }, "add.png"); wxMenuItem* item_decrease = append_menu_item(menu, wxID_ANY, _(L("Decrease copies")) + "\t-", _(L("Remove one copy of the selected object")), @@ -2747,8 +2866,9 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ append_menu_item(menu, wxID_ANY, _(L("Export object as STL")) + dots, _(L("Export this single object as STL file")), [this](wxCommandEvent&) { q->export_stl(true); }); + + menu->AppendSeparator(); } - menu->AppendSeparator(); sidebar->obj_list()->append_menu_item_fix_through_netfabb(menu); @@ -3243,11 +3363,8 @@ void Plater::export_stl(bool selection_only) { if (p->model.objects.empty()) { return; } - auto dialog = p->get_export_file(FT_STL); - if (! dialog) { return; } - - // Store a binary STL - const wxString path = dialog->GetPath(); + wxString path = p->get_export_file(FT_STL); + if (path.empty()) { return; } const std::string path_u8 = into_u8(path); wxBusyCursor wait; @@ -3259,8 +3376,17 @@ void Plater::export_stl(bool selection_only) const auto obj_idx = selection.get_object_idx(); if (obj_idx == -1) { return; } - mesh = p->model.objects[obj_idx]->mesh(); - } else { + + if (selection.get_mode() == Selection::Instance) + mesh = p->model.objects[obj_idx]->mesh(); + else + { + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + mesh = p->model.objects[obj_idx]->volumes[volume->volume_idx()]->mesh; + mesh.transform(volume->get_volume_transformation().get_matrix()); + } + } + else { mesh = p->model.mesh(); } @@ -3272,15 +3398,14 @@ void Plater::export_amf() { if (p->model.objects.empty()) { return; } - auto dialog = p->get_export_file(FT_AMF); - if (! dialog) { return; } - - const wxString path = dialog->GetPath(); + wxString path = p->get_export_file(FT_AMF); + if (path.empty()) { return; } const std::string path_u8 = into_u8(path); - DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure(); wxBusyCursor wait; - if (Slic3r::store_amf(path_u8.c_str(), &p->model, dialog->get_checkbox_value() ? &cfg : nullptr)) { + bool export_config = true; + DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure(); + if (Slic3r::store_amf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr)) { // Success p->statusbar()->set_status_text(wxString::Format(_(L("AMF file exported to %s")), path)); } else { @@ -3297,10 +3422,8 @@ void Plater::export_3mf(const boost::filesystem::path& output_path) bool export_config = true; if (output_path.empty()) { - auto dialog = p->get_export_file(FT_3MF); - if (!dialog) { return; } - path = dialog->GetPath(); - export_config = dialog->get_checkbox_value(); + path = p->get_export_file(FT_3MF); + if (path.empty()) { return; } } else path = from_path(output_path);