From bb30999673cdac7644f7c4529259cce236c3b813 Mon Sep 17 00:00:00 2001 From: Maeyanie Date: Fri, 6 Feb 2026 05:27:17 -0500 Subject: [PATCH] Add support for Draco (.drc) format (#10681) * Add read support for Google's Draco (.drc) format. * Fix build on Linux * Use boost instead of fstat. * Switch to boost memory-mapped file to save RAM and potentially improve performance. * Trim trailing whitespace. * Initial Draco write support. Currently always exports with 16-bit precision and speed 0 (best compression). The back-end function does have arguments to specify them, it's just not hooked into the GUI. * Add Draco to the About dialogue. * Fix Linux compile (hopefully) * Add an option to associate DRC files on Windows. * Implement a Preferences option to set Draco position quantization bits * Update src/slic3r/GUI/Preferences.cpp Co-authored-by: Ian Bassi * Some slight changes to ianalexis's suggestion. * Implement a create_item_spinctrl() function for numeric inputs, and use that instead of create_item_input(). * Move "bits" to inside the spinctrl box. * Refactor following yw4z's feedback * Update src/slic3r/GUI/Preferences.cpp Co-authored-by: Ian Bassi * Change to 0 bits as the default setting for Draco export precision. * Change to a lossy checkbox and a bits field with a range of 8-30. * Proper SpinInput code from yw4z * Revert "Proper SpinInput code from yw4z" This reverts commit 7e9c85f31a0d776860690595b71441498c2034d1. * Revert "Change to a lossy checkbox and a bits field with a range of 8-30." This reverts commit d642c9bcc0c51b35bf915e04f35d9991c8485ddd. * Redo preferences based on SoftFever's feedback * Refactor to minimize code duplication * Fix padding * Improve Draco export quality level tooltip clarity Clarify that 0 means lossless compression (not uncompressed), document the valid lossy range (8-30), and better explain the tradeoff between file size and geometric detail. --------- Co-authored-by: SoftFever Co-authored-by: Noisyfox Co-authored-by: Ian Bassi --- cmake/modules/FindDraco.cmake | 26 +++++ deps/CMakeLists.txt | 3 + deps/Draco/Draco.cmake | 4 + src/libslic3r/AppConfig.cpp | 6 ++ src/libslic3r/CMakeLists.txt | 4 + src/libslic3r/Format/DRC.cpp | 166 +++++++++++++++++++++++++++++++ src/libslic3r/Format/DRC.hpp | 26 +++++ src/libslic3r/Model.cpp | 3 + src/slic3r/GUI/AboutDialog.cpp | 1 + src/slic3r/GUI/GUI_App.cpp | 5 +- src/slic3r/GUI/GUI_App.hpp | 2 + src/slic3r/GUI/GUI_Factories.cpp | 25 +++++ src/slic3r/GUI/GUI_Factories.hpp | 1 + src/slic3r/GUI/MainFrame.cpp | 6 ++ src/slic3r/GUI/Plater.cpp | 54 ++++++++-- src/slic3r/GUI/Plater.hpp | 2 +- src/slic3r/GUI/Preferences.cpp | 72 ++++++++++++++ src/slic3r/GUI/Preferences.hpp | 2 + 18 files changed, 396 insertions(+), 12 deletions(-) create mode 100644 cmake/modules/FindDraco.cmake create mode 100644 deps/Draco/Draco.cmake create mode 100644 src/libslic3r/Format/DRC.cpp create mode 100644 src/libslic3r/Format/DRC.hpp diff --git a/cmake/modules/FindDraco.cmake b/cmake/modules/FindDraco.cmake new file mode 100644 index 0000000000..3b41890a4a --- /dev/null +++ b/cmake/modules/FindDraco.cmake @@ -0,0 +1,26 @@ +set(_q "") +if(${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY) + set(_q QUIET) + set(_quietly TRUE) +endif() +find_package(${CMAKE_FIND_PACKAGE_NAME} ${${CMAKE_FIND_PACKAGE_NAME}_FIND_VERSION} CONFIG ${_q}) + +if (NOT ${CMAKE_FIND_PACKAGE_NAME}_FOUND) + include(CheckIncludeFileCXX) + add_library(draco INTERFACE) + target_include_directories(draco INTERFACE include) + + if (_quietly) + set(CMAKE_REQUIRED_QUIET ON) + endif() + CHECK_INCLUDE_FILE_CXX("draco/draco_features.h" HAVE_DRACO_H) + + if (NOT HAVE_DRACO_H) + if (${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED) + message(FATAL_ERROR "Draco library not found. Please install the dependency.") + elseif(NOT _quietly) + message(WARNING "Draco library not found.") + endif() + endif () +endif() + diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 798f7e631b..492696a58a 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -336,6 +336,8 @@ include(CGAL/CGAL.cmake) include(NLopt/NLopt.cmake) include(libnoise/libnoise.cmake) +include(Draco/Draco.cmake) + # I *think* 1.1 is used for *just* md5 hashing? # 3.1 has everything in the right place, but the md5 funcs used are deprecated @@ -399,6 +401,7 @@ set(_dep_list ${CURL_PKG} ${WXWIDGETS_PKG} dep_Cereal + dep_Draco dep_NLopt dep_OpenVDB dep_OpenCSG diff --git a/deps/Draco/Draco.cmake b/deps/Draco/Draco.cmake new file mode 100644 index 0000000000..45e349cb19 --- /dev/null +++ b/deps/Draco/Draco.cmake @@ -0,0 +1,4 @@ +orcaslicer_add_cmake_project(Draco + URL https://github.com/google/draco/archive/refs/tags/1.5.7.zip + URL_HASH SHA256=27b72ba2d5ff3d0a9814ad40d4cb88f8dc89a35491c0866d952473f8f9416b77 +) \ No newline at end of file diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 42767b7349..bf21a965fa 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -1,5 +1,6 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" +#include "libslic3r/Format/DRC.hpp" #include "AppConfig.hpp" //BBS #include "Preset.hpp" @@ -124,6 +125,8 @@ void AppConfig::set_defaults() #ifdef _WIN32 if (get("associate_3mf").empty()) set_bool("associate_3mf", false); + if (get("associate_drc").empty()) + set_bool("associate_drc", false); if (get("associate_stl").empty()) set_bool("associate_stl", false); if (get("associate_step").empty()) @@ -234,6 +237,9 @@ void AppConfig::set_defaults() if (get("enable_multi_machine").empty()) set_bool("enable_multi_machine", false); + if (get("drc_bits").empty()) + set("drc_bits", DRC_BITS_DEFAULT_STR); + if (get("show_gcode_window").empty()) set_bool("show_gcode_window", true); diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 1817ae326c..4ab335879d 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -183,6 +183,8 @@ set(lisbslic3r_sources Format/3mf.hpp Format/AMF.cpp Format/AMF.hpp + Format/DRC.cpp + Format/DRC.hpp Format/bbs_3mf.cpp Format/bbs_3mf.hpp format.hpp @@ -538,6 +540,7 @@ find_package(OpenCASCADE REQUIRED) target_include_directories(libslic3r SYSTEM PUBLIC ${OpenCASCADE_INCLUDE_DIR}) find_package(JPEG REQUIRED) +find_package(draco REQUIRED) set(OCCT_LIBS TKXDESTEP @@ -583,6 +586,7 @@ target_link_libraries(libslic3r cereal::cereal clipper Clipper2 + draco::draco eigen glu-libtess JPEG::JPEG diff --git a/src/libslic3r/Format/DRC.cpp b/src/libslic3r/Format/DRC.cpp new file mode 100644 index 0000000000..3f6305b6bf --- /dev/null +++ b/src/libslic3r/Format/DRC.cpp @@ -0,0 +1,166 @@ +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +using namespace draco; + +#include "libslic3r/Model.hpp" +#include "libslic3r/TriangleMesh.hpp" +#include "DRC.hpp" + +#ifdef _WIN32 +#define DIR_SEPARATOR '\\' +#else +#define DIR_SEPARATOR '/' +#endif + +namespace Slic3r { + +bool load_drc(const char *path, TriangleMesh *meshptr) +{ + try { + boost::iostreams::mapped_file_source file(path); + + DecoderBuffer buffer; + buffer.Init(file.data(), file.size()); + + auto geotype = Decoder::GetEncodedGeometryType(&buffer); + if ((!geotype.ok()) || geotype.value() != TRIANGULAR_MESH) { + return false; + } + + Decoder decoder; + Mesh dracoMesh; + Status status = decoder.DecodeBufferToGeometry(&buffer, &dracoMesh); + if (!status.ok()) { + return false; + } + file.close(); + + + indexed_triangle_set its; + + const PointAttribute *const positions = dracoMesh.GetNamedAttribute(GeometryAttribute::POSITION); + size_t num_vertices = positions->size(); + its.vertices.reserve(num_vertices); + for (AttributeValueIndex i(0); i < num_vertices; ++ i) { + float pos[3]; + positions->ConvertValue(i, 3, pos); + its.vertices.emplace_back(pos[0], pos[1], pos[2]); + } + + size_t num_faces = dracoMesh.num_faces(); + its.indices.reserve(num_faces); + for (FaceIndex i(0); i < num_faces; ++ i) { + Mesh::Face face = dracoMesh.face(i); + + its.indices.emplace_back( + positions->mapped_index(face[0]).value(), + positions->mapped_index(face[1]).value(), + positions->mapped_index(face[2]).value() + ); + } + + *meshptr = TriangleMesh(std::move(its)); + if (meshptr->volume() < 0) + meshptr->flip_triangles(); + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "load_drc: " << e.what(); + return false; + } + return true; +} + +bool load_drc(const char *path, Model *model, const char *object_name_in) +{ + TriangleMesh mesh; + + bool ret = load_drc(path, &mesh); + + if (ret) { + std::string object_name; + if (object_name_in == nullptr) { + const char *last_slash = strrchr(path, DIR_SEPARATOR); + object_name.assign((last_slash == nullptr) ? path : last_slash + 1); + } else + object_name.assign(object_name_in); + + model->add_object(object_name.c_str(), path, std::move(mesh)); + } + + return ret; +} + +bool store_drc(const char *path, TriangleMesh *mesh, int bits, int speed) +{ + 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, int bits, int speed) +{ + TriangleMesh mesh = model_object->mesh(); + return store_drc(path, &mesh, bits, speed); +} + +bool store_drc(const char *path, Model *model, int bits, int speed) +{ + TriangleMesh mesh = model->mesh(); + return store_drc(path, &mesh, bits, speed); +} + +}; // namespace Slic3r diff --git a/src/libslic3r/Format/DRC.hpp b/src/libslic3r/Format/DRC.hpp new file mode 100644 index 0000000000..18ba6882e9 --- /dev/null +++ b/src/libslic3r/Format/DRC.hpp @@ -0,0 +1,26 @@ +#ifndef slic3r_Format_DRC_hpp_ +#define slic3r_Format_DRC_hpp_ + +namespace Slic3r { + +#define DRC_BITS_MIN 8 +#define DRC_BITS_MAX 30 +#define DRC_BITS_DEFAULT 0 +#define DRC_BITS_DEFAULT_STR "0" +#define DRC_SPEED_DEFAULT 0 + +class TriangleMesh; +class ModelObject; +class Model; + +// Load a Draco file into a provided 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, int bits, int speed = DRC_SPEED_DEFAULT); +extern bool store_drc(const char* path, ModelObject* model_object, int bits, int speed = DRC_SPEED_DEFAULT); +extern bool store_drc(const char* path, Model* model, int bits, int speed = DRC_SPEED_DEFAULT); + +}; // namespace Slic3r + +#endif /* slic3r_Format_DRC_hpp_ */ diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 8a128d511a..dbf1174556 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -14,6 +14,7 @@ #include "Format/AMF.hpp" #include "Format/svg.hpp" +#include "Format/DRC.hpp" // BBS #include "FaceDetector.hpp" @@ -311,6 +312,8 @@ Model Model::read_from_file(const std::string& result = load_svg(input_file.c_str(), &model, message); //BBS: remove the old .amf.xml files //else if (boost::algorithm::iends_with(input_file, ".amf") || boost::algorithm::iends_with(input_file, ".amf.xml")) + else if (boost::algorithm::iends_with(input_file, ".drc")) + result = load_drc(input_file.c_str(), &model); else if (boost::algorithm::iends_with(input_file, ".amf")) //BBS: is_xxx is used for is_inches when load amf result = load_amf(input_file.c_str(), config, config_substitutions, &model, is_xxx); diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index 72e456426c..17a6f958ca 100644 --- a/src/slic3r/GUI/AboutDialog.cpp +++ b/src/slic3r/GUI/AboutDialog.cpp @@ -90,6 +90,7 @@ void CopyrightsDialog::fill_entries() { "CGAL", "", "https://www.cgal.org" }, { "Clipper", "", "http://www.angusj.co" }, { "libcurl", "", "https://curl.se/libcurl" }, + { "Draco", "", "https://google.github.io/draco/" }, { "Eigen3", "", "http://eigen.tuxfamily.org" }, { "Expat", "", "http://www.libexpat.org" }, { "fast_float", "", "https://github.com/fastfloat/fast_float" }, diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 6d328c490a..6c8dd5f421 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -537,10 +537,10 @@ static const FileWildcards file_wildcards_by_type[FT_SIZE] = { /* FT_GCODE */ { "G-code files"sv, { ".gcode"sv} }, #ifdef __APPLE__ /* FT_MODEL */ - {"Supported files"sv, {".3mf"sv, ".stl"sv, ".oltp"sv, ".stp"sv, ".step"sv, ".svg"sv, ".amf"sv, ".obj"sv, ".usd"sv, ".usda"sv, ".usdc"sv, ".usdz"sv, ".abc"sv, ".ply"sv}}, + {"Supported files"sv, {".3mf"sv, ".stl"sv, ".oltp"sv, ".stp"sv, ".step"sv, ".svg"sv, ".amf"sv, ".obj"sv, ".usd"sv, ".usda"sv, ".usdc"sv, ".usdz"sv, ".abc"sv, ".ply"sv, ".drc"sv}}, #else /* FT_MODEL */ - {"Supported files"sv, {".3mf"sv, ".stl"sv, ".oltp"sv, ".stp"sv, ".step"sv, ".svg"sv, ".amf"sv, ".obj"sv}}, + {"Supported files"sv, {".3mf"sv, ".stl"sv, ".oltp"sv, ".stp"sv, ".step"sv, ".svg"sv, ".amf"sv, ".obj"sv, ".drc"sv}}, #endif /* FT_ZIP */ { "ZIP files"sv, { ".zip"sv } }, /* FT_PROJECT */ { "Project files"sv, { ".3mf"sv} }, @@ -550,6 +550,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 ecdab5a676..c879b1e5bb 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -106,6 +106,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 44dd7b8f6e..0fe474fc25 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -863,6 +863,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_stl(false, true, false, FT_DRC); }, "", 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_stl(false, true, true, FT_DRC); }, "", 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"), @@ -1282,6 +1303,7 @@ void MenuFactory::create_common_object_menu(wxMenu* menu) 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); @@ -1367,6 +1389,7 @@ void MenuFactory::create_extra_object_menu() append_menu_item_replace_with_stl(&m_object_menu); append_menu_item_replace_all_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() @@ -1401,6 +1424,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); @@ -1782,6 +1806,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 00924f502d..7f1947814d 100644 --- a/src/slic3r/GUI/GUI_Factories.hpp +++ b/src/slic3r/GUI/GUI_Factories.hpp @@ -141,6 +141,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_replace_all_with_stl(wxMenu* menu); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index d98de4e2ba..8e74eda926 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -2516,6 +2516,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_stl(false, false, false, FT_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_stl(false, false, true, FT_DRC); }, "menu_export_stl", nullptr, + [this](){return can_export_model(); }, this); append_menu_item(export_menu, wxID_ANY, _L("Export Generic 3MF") + dots/* + "\t" + ctrl + "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 425d901bdd..590f842aa4 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -49,6 +49,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" @@ -6967,6 +6968,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: @@ -6988,6 +6990,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? @@ -13685,7 +13693,7 @@ void ProjectDropDialog::on_dpi_changed(const wxRect& suggested_rect) //BBS: remove GCodeViewer as seperate APP logic bool Plater::load_files(const wxArrayString& filenames) { - const std::regex pattern_drop(".*[.](stp|step|stl|oltp|obj|amf|3mf|svg|zip)", std::regex::icase); + const std::regex pattern_drop(".*[.](stp|step|stl|oltp|obj|amf|3mf|svg|zip|drc)", std::regex::icase); const std::regex pattern_gcode_drop(".*[.](gcode|g)", std::regex::icase); std::vector normal_paths; @@ -14722,10 +14730,22 @@ TriangleMesh Plater::combine_mesh_fff(const ModelObject& mo, int instance_id, st // BBS export with/without boolean, however, stil merge mesh #define EXPORT_WITH_BOOLEAN 0 -void Plater::export_stl(bool extended, bool selection_only, bool multi_stls) +void Plater::export_stl(bool extended, bool selection_only, bool multi_stls, FileType file_type) { if (p->model.objects.empty()) { return; } + int quality = 0; + + switch (file_type) { + case FT_DRC: + AppConfig* app_config = wxGetApp().app_config; + if (app_config) + quality = stoi(app_config->get("drc_bits")); + else + quality = DRC_BITS_DEFAULT; + break; + } + wxString path; if (multi_stls) { wxDirDialog dlg(this, _L("Choose a directory"), from_u8(wxGetApp().app_config->get_last_dir()), @@ -14734,7 +14754,7 @@ void Plater::export_stl(bool extended, bool selection_only, bool multi_stls) path = dlg.GetPath() + "/"; } } else { - path = p->get_export_file(FT_STL); + path = p->get_export_file(file_type); } if (path.empty()) { return; } const std::string path_u8 = into_u8(path); @@ -14881,11 +14901,17 @@ void Plater::export_stl(bool extended, bool selection_only, bool multi_stls) else mesh_to_export = mesh_to_export_sla; - auto get_save_file = [](std::string const & dir, std::string const & name) { - auto path = dir + name + ".stl"; + auto get_save_file = [file_type](std::string const & dir, std::string const & name) { + std::string ext = ""; + switch (file_type) { + case FT_STL: ext = ".stl"; break; + case FT_DRC: ext = ".drc"; break; + } + + auto path = dir + name + ext; int n = 1; while (boost::filesystem::exists(path)) - path = dir + name + "(" + std::to_string(n++) + ").stl"; + path = dir + name + "(" + std::to_string(n++) + ")"+ext; return path; }; @@ -14918,7 +14944,10 @@ void Plater::export_stl(bool extended, bool selection_only, bool multi_stls) auto mesh = mesh_to_export(*object, i.second); mesh.translate(-object->origin_translation.cast()); - Slic3r::store_stl(get_save_file(path_u8, object->name).c_str(), &mesh, true); + switch (file_type) { + case FT_STL: Slic3r::store_stl(get_save_file(path_u8, object->name).c_str(), &mesh, true); break; + case FT_DRC: Slic3r::store_drc(get_save_file(path_u8, object->name).c_str(), &mesh, quality); break; + } } return; } @@ -14931,12 +14960,19 @@ void Plater::export_stl(bool extended, bool selection_only, bool multi_stls) for (const ModelObject* o : p->model.objects) { auto mesh = mesh_to_export(*o, -1); mesh.translate(-o->origin_translation.cast()); - Slic3r::store_stl(get_save_file(path_u8, o->name).c_str(), &mesh, true); + + switch (file_type) { + case FT_STL: Slic3r::store_stl(get_save_file(path_u8, o->name).c_str(), &mesh, true); break; + case FT_DRC: Slic3r::store_drc(get_save_file(path_u8, o->name).c_str(), &mesh, quality); break; + } } return; } - Slic3r::store_stl(path_u8.c_str(), &mesh, true); + switch (file_type) { + case FT_STL: Slic3r::store_stl(path_u8.c_str(), &mesh, true); break; + case FT_DRC: Slic3r::store_drc(path_u8.c_str(), &mesh, quality); break; + } } //BBS: remove amf export diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index b53b3c2a90..6dccc59b2b 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -485,7 +485,7 @@ public: void send_gcode_finish(wxString name); 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_stl(bool extended = false, bool selection_only = false, bool multi_stls = false, FileType file_type = FT_STL); //BBS: remove amf //void export_amf(); //BBS add extra param for exporting 3mf silence diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index d3a45ef116..28868d3ac5 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -6,6 +6,7 @@ #include "MsgDialog.hpp" #include "I18N.hpp" #include "libslic3r/AppConfig.hpp" +#include "libslic3r/Format/DRC.hpp" #include #include "OG_CustomCtrl.hpp" #include "wx/graphics.h" @@ -750,6 +751,58 @@ wxBoxSizer *PreferencesDialog::create_item_auto_reslice(wxString title, wxString return sizer_row; } +wxBoxSizer* PreferencesDialog::create_item_draco(wxString title, wxString side_label, wxString tooltip) +{ + wxBoxSizer* sizer_input = new wxBoxSizer(wxHORIZONTAL); + + auto input_title = new wxStaticText(m_parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE); + input_title->SetForegroundColour(DESIGN_GRAY900_COLOR); + input_title->SetFont(::Label::Body_14); + input_title->SetToolTip(tooltip); + input_title->Wrap(DESIGN_TITLE_SIZE.x); + input_title->SetToolTip(tooltip); + + auto input = new ::TextInput(m_parent, wxEmptyString, side_label, wxEmptyString, wxDefaultPosition, DESIGN_INPUT_SIZE, wxTE_PROCESS_ENTER); + StateColor input_bg(std::pair(wxColour("#F0F0F1"), StateColor::Disabled), + std::pair(*wxWHITE, StateColor::Enabled)); + input->SetBackgroundColor(input_bg); + input->GetTextCtrl()->SetValue(app_config->get("drc_bits")); + wxTextValidator validator(wxFILTER_DIGITS); + input->SetToolTip(tooltip); + input->GetTextCtrl()->SetValidator(validator); + + sizer_input->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN)); + sizer_input->Add(input_title, 0, wxALIGN_CENTER_VERTICAL); + sizer_input->Add(input , 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(5)); + + std::function set_draco_bits = [this, input]() { + long drc_bits = DRC_BITS_DEFAULT; + input->GetTextCtrl()->GetValue().ToLong(&drc_bits); + if (drc_bits > DRC_BITS_MAX) { + drc_bits = DRC_BITS_MAX; + input->GetTextCtrl()->SetValue(std::to_string(drc_bits)); + } else if (drc_bits < DRC_BITS_MIN && drc_bits != 0) { + drc_bits = DRC_BITS_MIN; + input->GetTextCtrl()->SetValue(std::to_string(drc_bits)); + } + + app_config->set("drc_bits", std::to_string(drc_bits)); + app_config->save(); + }; + + input->GetTextCtrl()->Bind(wxEVT_TEXT_ENTER, [set_draco_bits](wxCommandEvent& e) { + set_draco_bits(); + e.Skip(); + }); + + input->GetTextCtrl()->Bind(wxEVT_KILL_FOCUS, [set_draco_bits](wxFocusEvent& e) { + set_draco_bits(); + e.Skip(); + }); + + return sizer_input; +} + wxBoxSizer* PreferencesDialog::create_item_darkmode(wxString title,wxString tooltip, std::string param) { wxBoxSizer* m_sizer_checkbox = new wxBoxSizer(wxHORIZONTAL); @@ -864,6 +917,15 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too } } + if (param == "associate_drc") { + bool pbool = app_config->get("associate_drc") == "true" ? true : false; + if (pbool) { + wxGetApp().associate_files(L"drc"); + } else { + wxGetApp().disassociate_files(L"drc"); + } + } + if (param == "associate_stl") { bool pbool = app_config->get("associate_stl") == "true" ? true : false; if (pbool) { @@ -1306,6 +1368,13 @@ void PreferencesDialog::create_items() g_sizer->Add(item_pop_up_filament_map_dialog); #endif + auto item_draco_bits = create_item_draco(_L("Quality level for Draco export"), + _L("bits"), + _L("Controls the quantization bit depth used when compressing the mesh to Draco format.\n" + "0 = lossless compression (geometry is preserved at full precision). Valid lossy values range from 8 to 30.\n" + "Lower values produce smaller files but lose more geometric detail; higher values preserve more detail at the cost of larger files.")); + g_sizer->Add(item_draco_bits); + g_sizer->AddSpacer(FromDIP(10)); sizer_page->Add(g_sizer, 0, wxEXPAND); @@ -1554,6 +1623,9 @@ void PreferencesDialog::create_items() auto item_associate_3mf = create_item_checkbox(_L("Associate 3MF files to OrcaSlicer"), _L("If enabled, sets OrcaSlicer as default application to open 3MF files.") , "associate_3mf"); g_sizer->Add(item_associate_3mf); + auto item_associate_drc = create_item_checkbox(_L("Associate DRC files to OrcaSlicer"), _L("If enabled, sets OrcaSlicer as default application to open DRC files."), "associate_drc"); + g_sizer->Add(item_associate_drc); + auto item_associate_stl = create_item_checkbox(_L("Associate STL files to OrcaSlicer"), _L("If enabled, sets OrcaSlicer as default application to open STL files.") , "associate_stl"); g_sizer->Add(item_associate_stl); diff --git a/src/slic3r/GUI/Preferences.hpp b/src/slic3r/GUI/Preferences.hpp index 7470d53224..886f5f7cec 100644 --- a/src/slic3r/GUI/Preferences.hpp +++ b/src/slic3r/GUI/Preferences.hpp @@ -91,9 +91,11 @@ public: wxBoxSizer *create_item_button(wxString title, wxString title2, wxString tooltip, wxString tooltip2, std::function onclick); wxBoxSizer *create_item_downloads(wxString title, wxString tooltip); wxBoxSizer *create_item_input(wxString title, wxString title2, wxString tooltip, std::string param, std::function onchange = {}); + wxBoxSizer *create_item_spinctrl(wxString title, wxString title2, wxString side_label, wxString tooltip, std::string param, int min, int max, std::function onchange = nullptr); wxBoxSizer *create_camera_orbit_mult_input(wxString title, wxString tooltip); wxBoxSizer *create_item_backup(wxString title, wxString tooltip); wxBoxSizer *create_item_auto_reslice(wxString title, wxString checkbox_tooltip, wxString delay_tooltip); + wxBoxSizer *create_item_draco(wxString title, wxString side_label, wxString tooltip); wxBoxSizer *create_item_multiple_combobox(wxString title, wxString tooltip, std::string parama, std::vector vlista, std::vector vlistb); #ifdef WIN32 wxBoxSizer *create_item_link_association(wxString url_prefix, wxString website_name);