diff --git a/src/libslic3r/Format/DRC.cpp b/src/libslic3r/Format/DRC.cpp index 3ae0bc9e52..5e6bae39b9 100644 --- a/src/libslic3r/Format/DRC.cpp +++ b/src/libslic3r/Format/DRC.cpp @@ -4,6 +4,7 @@ #include +#include #include #include #include @@ -96,22 +97,69 @@ bool load_drc(const char *path, Model *model, const char *object_name_in) return ret; } -bool store_drc(const char *path, TriangleMesh *mesh, bool binary) +bool store_drc(const char *path, TriangleMesh *mesh, int bits, int speed) { - // TODO - return false; + try { + const std::vector* indices = &(mesh->its.indices); + const std::vector* vertices = &(mesh->its.vertices); + + Mesh dracoMesh; + + dracoMesh.set_num_points(vertices->size()); + + GeometryAttribute gaPos; + gaPos.Init(GeometryAttribute::POSITION, nullptr, 3, DT_FLOAT32, false, sizeof(float)*3, 0); + int32_t idPos = dracoMesh.AddAttribute(gaPos, true, indices->size() * 3); + + dracoMesh.attribute(idPos)->Resize(vertices->size()); + + for (size_t i = 0; i < vertices->size(); ++ i) { + float vertex[3]; + vertex[0] = vertices->at(i)(0); + vertex[1] = vertices->at(i)(1); + vertex[2] = vertices->at(i)(2); + dracoMesh.attribute(idPos)->SetAttributeValue(AttributeValueIndex(i), vertex); + } + + dracoMesh.SetNumFaces(indices->size()); + for (size_t i = 0; i < indices->size(); ++ i) { + Mesh::Face face; + face[0] = PointIndex(indices->at(i)[0]); + face[1] = PointIndex(indices->at(i)[1]); + face[2] = PointIndex(indices->at(i)[2]); + dracoMesh.SetFace(FaceIndex(i), face); + } + + Encoder encoder; + encoder.SetSpeedOptions(speed, speed); + encoder.SetAttributeQuantization(GeometryAttribute::POSITION, bits); + + EncoderBuffer buffer; + encoder.EncodeMeshToBuffer(dracoMesh, &buffer); + + FILE* fp = boost::nowide::fopen(path, "wb"); + if (!fp) return false; + size_t written = fwrite(buffer.data(), 1, buffer.size(), fp); + fclose(fp); + + if (written != buffer.size()) return false; + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "store_drc: " << e.what(); + return false; + } + return true; } -bool store_drc(const char *path, ModelObject *model_object, bool binary) +bool store_drc(const char *path, ModelObject *model_object, int bits, int speed) { TriangleMesh mesh = model_object->mesh(); - return store_drc(path, &mesh, binary); + return store_drc(path, &mesh, bits, speed); } -bool store_drc(const char *path, Model *model, bool binary) +bool store_drc(const char *path, Model *model, int bits, int speed) { TriangleMesh mesh = model->mesh(); - return store_drc(path, &mesh, binary); + return store_drc(path, &mesh, bits, speed); } }; // namespace Slic3r diff --git a/src/libslic3r/Format/DRC.hpp b/src/libslic3r/Format/DRC.hpp index 9178f12526..22c0d1f000 100644 --- a/src/libslic3r/Format/DRC.hpp +++ b/src/libslic3r/Format/DRC.hpp @@ -11,9 +11,9 @@ class Model; extern bool load_drc(const char *path, TriangleMesh *meshptr); extern bool load_drc(const char *path, Model *model, const char *object_name = nullptr); -extern bool store_drc(const char *path, TriangleMesh *mesh, bool binary); -extern bool store_drc(const char *path, ModelObject *model_object, bool binary); -extern bool store_drc(const char *path, Model *model, bool binary); +extern bool store_drc(const char *path, TriangleMesh *mesh, int bits, int speed); +extern bool store_drc(const char *path, ModelObject *model_object, int bits, int speed); +extern bool store_drc(const char *path, Model *model, int bits, int speed); }; // namespace Slic3r diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 3c1a03b45e..c9b0cb0ef0 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -529,6 +529,7 @@ static const FileWildcards file_wildcards_by_type[FT_SIZE] = { /* FT_SVG */ { "SVG files"sv, { ".svg"sv } }, /* FT_TEX */ { "Texture"sv, { ".png"sv, ".svg"sv } }, /* FT_SL1 */ { "Masked SLA files"sv, { ".sl1"sv, ".sl1s"sv } }, + /* FT_DRC */ { "Draco files"sv, { ".drc"sv } }, }; // This function produces a Win32 file dialog file template mask to be consumed by wxWidgets on all platforms. diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 7f390d1f66..7b2a343de6 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -105,6 +105,8 @@ enum FileType FT_SL1, + FT_DRC, + FT_SIZE, }; diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index ac9ce3b4b4..8df74edd78 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -855,6 +855,27 @@ void MenuFactory::append_menu_item_export_stl(wxMenu* menu, bool is_mulity_menu) }, m_parent); } +void MenuFactory::append_menu_item_export_drc(wxMenu* menu, bool is_mulity_menu) +{ + append_menu_item(menu, wxID_ANY, _L("Export as one DRC") + dots, "", + [](wxCommandEvent&) { plater()->export_drc(false, true); }, "", nullptr, + [is_mulity_menu]() { + const Selection& selection = plater()->canvas3D()->get_selection(); + if (is_mulity_menu) + return selection.is_multiple_full_instance() || selection.is_multiple_full_object(); + else + return selection.is_single_full_instance() || selection.is_single_full_object(); + }, m_parent); + if (!is_mulity_menu) + return; + append_menu_item(menu, wxID_ANY, _L("Export as DRCs") + dots, "", + [](wxCommandEvent&) { plater()->export_drc(false, true, true); }, "", nullptr, + []() { + const Selection& selection = plater()->canvas3D()->get_selection(); + return selection.is_multiple_full_instance() || selection.is_multiple_full_object(); + }, m_parent); +} + void MenuFactory::append_menu_item_reload_from_disk(wxMenu* menu) { append_menu_item(menu, wxID_ANY, _L("Reload from disk"), _L("Reload the selected parts from disk"), @@ -1269,6 +1290,7 @@ void MenuFactory::create_common_object_menu(wxMenu* menu) // BBS append_menu_item_reload_from_disk(menu); append_menu_item_export_stl(menu); + append_menu_item_export_drc(menu); // "Scale to print volume" makes a sense just for whole object append_menu_item_scale_selection_to_fit_print_volume(menu); @@ -1348,6 +1370,7 @@ void MenuFactory::create_extra_object_menu() append_menu_item_reload_from_disk(&m_object_menu); append_menu_item_replace_with_stl(&m_object_menu); append_menu_item_export_stl(&m_object_menu); + append_menu_item_export_drc(&m_object_menu); } void MenuFactory::create_bbl_assemble_object_menu() @@ -1382,6 +1405,7 @@ void MenuFactory::create_part_menu() append_menu_item_delete(menu); append_menu_item_reload_from_disk(menu); append_menu_item_export_stl(menu); + append_menu_item_export_drc(menu); append_menu_item_fix_through_netfabb(menu); append_menu_items_mirror(menu); append_menu_item_merge_parts_to_single_part(menu); @@ -1697,6 +1721,7 @@ wxMenu* MenuFactory::multi_selection_menu() append_menu_item_change_filament(menu); menu->AppendSeparator(); append_menu_item_export_stl(menu, true); + append_menu_item_export_drc(menu, true); } else { append_menu_item_center(menu); diff --git a/src/slic3r/GUI/GUI_Factories.hpp b/src/slic3r/GUI/GUI_Factories.hpp index 7c73e0facf..bb90582f86 100644 --- a/src/slic3r/GUI/GUI_Factories.hpp +++ b/src/slic3r/GUI/GUI_Factories.hpp @@ -135,6 +135,7 @@ private: wxMenuItem* append_menu_item_fix_through_netfabb(wxMenu* menu); //wxMenuItem* append_menu_item_simplify(wxMenu* menu); void append_menu_item_export_stl(wxMenu* menu, bool is_mulity_menu = false); + void append_menu_item_export_drc(wxMenu* menu, bool is_mulity_menu = false); void append_menu_item_reload_from_disk(wxMenu* menu); void append_menu_item_replace_with_stl(wxMenu* menu); void append_menu_item_change_extruder(wxMenu* menu); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index ff030e111b..54a57db5d0 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -2403,6 +2403,12 @@ void MainFrame::init_menubar_as_editor() append_menu_item(export_menu, wxID_ANY, _L("Export all objects as STLs") + dots, _L("Export all objects as STLs"), [this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(false, false, true); }, "menu_export_stl", nullptr, [this](){return can_export_model(); }, this); + append_menu_item(export_menu, wxID_ANY, _L("Export all objects as one DRC") + dots, _L("Export all objects as one DRC"), + [this](wxCommandEvent&) { if (m_plater) m_plater->export_drc(); }, "menu_export_stl", nullptr, + [this](){return can_export_model(); }, this); + append_menu_item(export_menu, wxID_ANY, _L("Export all objects as DRCs") + dots, _L("Export all objects as DRCs"), + [this](wxCommandEvent&) { if (m_plater) m_plater->export_drc(false, false, true); }, "menu_export_stl", nullptr, + [this](){return can_export_model(); }, this); append_menu_item(export_menu, wxID_ANY, _L("Export Generic 3MF") + dots/* + "\tCtrl+G"*/, _L("Export 3mf file without using some 3mf-extensions"), [this](wxCommandEvent&) { if (m_plater) m_plater->export_core_3mf(); }, "menu_export_sliced_file", nullptr, [this](){return can_export_model(); }, this); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index efdac5e4d4..7e4ccf7d5e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -47,6 +47,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/Format/STL.hpp" +#include "libslic3r/Format/DRC.hpp" #include "libslic3r/Format/STEP.hpp" #include "libslic3r/Format/AMF.hpp" //#include "libslic3r/Format/3mf.hpp" @@ -4766,6 +4767,7 @@ wxString Plater::priv::get_export_file(GUI::FileType file_type) wxString wildcard; switch (file_type) { case FT_STL: + case FT_DRC: case FT_AMF: case FT_3MF: case FT_GCODE: @@ -4787,6 +4789,12 @@ wxString Plater::priv::get_export_file(GUI::FileType file_type) dlg_title = _L("Export STL file:"); break; } + case FT_DRC: + { + output_file.replace_extension("drc"); + dlg_title = _L("Export Draco file:"); + break; + } case FT_AMF: { // XXX: Problem on OS X with double extension? @@ -12135,6 +12143,225 @@ void Plater::export_stl(bool extended, bool selection_only, bool multi_stls) } }*/ +void Plater::export_drc(bool extended, bool selection_only, bool multi_drcs) +{ + if (p->model.objects.empty()) { return; } + + wxString path; + int bits = 16; + int speed = 0; + if (multi_drcs) { + wxDirDialog dlg(this, _L("Choose a directory"), from_u8(wxGetApp().app_config->get_last_dir()), + wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST); + if (dlg.ShowModal() == wxID_OK) { + path = dlg.GetPath() + "/"; + } + } else { + path = p->get_export_file(FT_DRC); + } + if (path.empty()) { return; } + const std::string path_u8 = into_u8(path); + + wxBusyCursor wait; + const auto& selection = p->get_selection(); + const auto obj_idx = selection.get_object_idx(); + +#if EXPORT_WITH_BOOLEAN + if (selection_only && (obj_idx == -1 || selection.is_wipe_tower())) + return; +#else + // BBS support selecting multiple objects + if (selection_only && selection.is_wipe_tower()) return; + + // BBS + if (selection_only) { + // only support selection single full object and mulitiple full object + if (!selection.is_single_full_object() && !selection.is_multiple_full_object()) return; + } + + // Following lambda generates a combined mesh for export with normals pointing outwards. + auto mesh_to_export_fff_no_boolean = [this](const ModelObject &mo, int instance_id) { + TriangleMesh mesh; + + //Prusa export negative parts + std::vector csgmesh; + csgmesh.reserve(2 * mo.volumes.size()); + csg::model_to_csgmesh(mo, Transform3d::Identity(), std::back_inserter(csgmesh), + csg::mpartsPositive | csg::mpartsNegative | csg::mpartsDoSplits); + + auto csgrange = range(csgmesh); + if (csg::is_all_positive(csgrange)) { + mesh = TriangleMesh{csg::csgmesh_merge_positive_parts(csgrange)}; + } else if (std::get<2>(csg::check_csgmesh_booleans(csgrange)) == csgrange.end()) { + try { + auto cgalm = csg::perform_csgmesh_booleans(csgrange); + mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*cgalm); + } catch (...) {} + } + + if (mesh.empty()) { + get_notification_manager()->push_plater_error_notification( + _u8L("Unable to perform boolean operation on model meshes. " + "Only positive parts will be exported.")); + + for (const ModelVolume* v : mo.volumes) + if (v->is_model_part()) { + TriangleMesh vol_mesh(v->mesh()); + vol_mesh.transform(v->get_matrix(), true); + mesh.merge(vol_mesh); + } + } + if (instance_id == -1) { + TriangleMesh vols_mesh(mesh); + mesh = TriangleMesh(); + for (const ModelInstance *i : mo.instances) { + TriangleMesh m = vols_mesh; + m.transform(i->get_matrix(), true); + mesh.merge(m); + } + } else if (0 <= instance_id && instance_id < int(mo.instances.size())) + mesh.transform(mo.instances[instance_id]->get_matrix(), true); + return mesh; + }; +#endif + auto mesh_to_export_sla = [&, this](const ModelObject& mo, int instance_id) { + TriangleMesh mesh; + + const SLAPrintObject *object = this->p->sla_print.get_print_object_by_model_object_id(mo.id()); + + if (auto m = object->get_mesh_to_print(); m.empty()) + mesh = combine_mesh_fff(mo, instance_id, [this](const std::string& msg) {return get_notification_manager()->push_plater_error_notification(msg); }); + else { + const Transform3d mesh_trafo_inv = object->trafo().inverse(); + const bool is_left_handed = object->is_left_handed(); + + auto pad_mesh = extended? object->pad_mesh() : TriangleMesh{}; + pad_mesh.transform(mesh_trafo_inv); + + auto supports_mesh = extended ? object->support_mesh() : TriangleMesh{}; + supports_mesh.transform(mesh_trafo_inv); + + const std::vector& obj_instances = object->instances(); + for (const SLAPrintObject::Instance& obj_instance : obj_instances) { + auto it = std::find_if(object->model_object()->instances.begin(), object->model_object()->instances.end(), + [&obj_instance](const ModelInstance *mi) { return mi->id() == obj_instance.instance_id; }); + assert(it != object->model_object()->instances.end()); + + if (it != object->model_object()->instances.end()) { + const bool one_inst_only = selection_only && ! selection.is_single_full_object(); + + const int instance_idx = it - object->model_object()->instances.begin(); + const Transform3d& inst_transform = one_inst_only + ? Transform3d::Identity() + : object->model_object()->instances[instance_idx]->get_transformation().get_matrix(); + + TriangleMesh inst_mesh; + + if (!pad_mesh.empty()) { + TriangleMesh inst_pad_mesh = pad_mesh; + inst_pad_mesh.transform(inst_transform, is_left_handed); + inst_mesh.merge(inst_pad_mesh); + } + + if (!supports_mesh.empty()) { + TriangleMesh inst_supports_mesh = supports_mesh; + inst_supports_mesh.transform(inst_transform, is_left_handed); + inst_mesh.merge(inst_supports_mesh); + } + + TriangleMesh inst_object_mesh = object->get_mesh_to_print(); + + inst_object_mesh.transform(mesh_trafo_inv); + inst_object_mesh.transform(inst_transform, is_left_handed); + + inst_mesh.merge(inst_object_mesh); + + // ensure that the instance lays on the bed + inst_mesh.translate(0.0f, 0.0f, -inst_mesh.bounding_box().min.z()); + + // merge instance with global mesh + mesh.merge(inst_mesh); + + if (one_inst_only) + break; + } + } + } + + return mesh; + }; + + std::function + mesh_to_export; + + if (p->printer_technology == ptFFF) +#if EXPORT_WITH_BOOLEAN + mesh_to_export = [this](const ModelObject& mo, int instance_id) {return Plater::combine_mesh_fff(mo, instance_id, + [this](const std::string& msg) {return get_notification_manager()->push_plater_error_notification(msg); }); }; +#else + mesh_to_export = mesh_to_export_fff_no_boolean; +#endif + else + mesh_to_export = mesh_to_export_sla; + + auto get_save_file = [](std::string const & dir, std::string const & name) { + auto path = dir + name + ".drc"; + int n = 1; + while (boost::filesystem::exists(path)) + path = dir + name + "(" + std::to_string(n++) + ").drc"; + return path; + }; + + TriangleMesh mesh; + if (selection_only) { + if (selection.is_single_full_object()) { + const auto obj_idx = selection.get_object_idx(); + const ModelObject* model_object = p->model.objects[obj_idx]; + if (selection.get_mode() == Selection::Instance) + mesh = mesh_to_export(*model_object, (model_object->instances.size() > 1) ? -1 : selection.get_instance_idx()); + else { + const GLVolume* volume = selection.get_first_volume(); + mesh = model_object->volumes[volume->volume_idx()]->mesh(); + mesh.transform(volume->get_volume_transformation().get_matrix(), true); + } + + if (model_object->instances.size() == 1) mesh.translate(-model_object->origin_translation.cast()); + } + else if (selection.is_multiple_full_object() && !multi_drcs) { + const std::set>& instances_idxs = p->get_selection().get_selected_object_instances(); + for (const std::pair& i : instances_idxs) { + ModelObject* object = p->model.objects[i.first]; + mesh.merge(mesh_to_export(*object, i.second)); + } + } + else if (selection.is_multiple_full_object() && multi_drcs) { + const std::set> &instances_idxs = p->get_selection().get_selected_object_instances(); + for (const std::pair &i : instances_idxs) { + ModelObject *object = p->model.objects[i.first]; + auto mesh = mesh_to_export(*object, i.second); + mesh.translate(-object->origin_translation.cast()); + + Slic3r::store_drc(get_save_file(path_u8, object->name).c_str(), &mesh, bits, speed); + } + return; + } + } + else if (!multi_drcs) { + for (const ModelObject* o : p->model.objects) { + mesh.merge(mesh_to_export(*o, -1)); + } + } else { + for (const ModelObject* o : p->model.objects) { + auto mesh = mesh_to_export(*o, -1); + mesh.translate(-o->origin_translation.cast()); + Slic3r::store_drc(get_save_file(path_u8, o->name).c_str(), &mesh, bits, speed); + } + return; + } + + Slic3r::store_drc(path_u8.c_str(), &mesh, bits, speed); +} + namespace { std::string get_file_name(const std::string &file_path) { diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index ece34ba829..28d4b33651 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -414,6 +414,7 @@ public: void export_core_3mf(); static TriangleMesh combine_mesh_fff(const ModelObject& mo, int instance_id, std::function notify_func = {}); void export_stl(bool extended = false, bool selection_only = false, bool multi_stls = false); + void export_drc(bool extended = false, bool selection_only = false, bool multi_drcs = false); //BBS: remove amf //void export_amf(); //BBS add extra param for exporting 3mf silence