diff --git a/resources/localization/list.txt b/resources/localization/list.txt index 849aff0267..65acc239ca 100644 --- a/resources/localization/list.txt +++ b/resources/localization/list.txt @@ -88,6 +88,7 @@ src/slic3r/GUI/Jobs/SLAImportDialog.hpp src/slic3r/GUI/Jobs/SLAImportJob.cpp src/slic3r/GUI/KBShortcutsDialog.cpp src/slic3r/GUI/BulkExportDialog.cpp +src/slic3r/GUI/LoadStepDialog.cpp src/slic3r/GUI/LoginDialog.cpp src/slic3r/GUI/MainFrame.cpp src/slic3r/GUI/Mouse3DController.cpp diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 8b451a64a4..4e7338b0ab 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -235,6 +235,15 @@ void AppConfig::set_defaults() set("sys_menu_enabled", "1"); #endif // _WIN32 + if (get("show_step_import_parameters").empty()) + set("show_step_import_parameters", "1"); + + if (get("linear_precision").empty()) + set("linear_precision", "0.005"); + + if (get("angle_precision").empty()) + set("angle_precision", "1."); + // Remove legacy window positions/sizes erase("", "main_frame_maximized"); erase("", "main_frame_pos"); diff --git a/src/libslic3r/FileReader.cpp b/src/libslic3r/FileReader.cpp index 71c87dd1df..d9d75b5edd 100644 --- a/src/libslic3r/FileReader.cpp +++ b/src/libslic3r/FileReader.cpp @@ -37,7 +37,7 @@ bool is_project_file(const std::string& input_file) } // Loading model from a file, it may be a simple geometry file as STL or OBJ, however it may be a project file as well. -static Model read_model_from_file(const std::string& input_file, LoadAttributes options) +static Model read_model_from_file(const std::string& input_file, LoadAttributes options, const std::optional>& step_deflections = std::nullopt) { Model model; @@ -49,8 +49,9 @@ static Model read_model_from_file(const std::string& input_file, LoadAttributes result = load_stl(input_file.c_str(), &model); else if (boost::algorithm::iends_with(input_file, ".obj")) result = load_obj(input_file.c_str(), &model); - else if (boost::algorithm::iends_with(input_file, ".step") || boost::algorithm::iends_with(input_file, ".stp")) - result = load_step(input_file.c_str(), &model); + else if (boost::algorithm::iends_with(input_file, ".step") || boost::algorithm::iends_with(input_file, ".stp")) { + result = load_step(input_file.c_str(), &model, step_deflections); + } else if (boost::algorithm::iends_with(input_file, ".amf") || boost::algorithm::iends_with(input_file, ".amf.xml")) //? result = load_amf(input_file.c_str(), &temp_config, &temp_config_substitutions_context, &model, options & LoadAttribute::CheckVersion); //? LoadAttribute::CheckVersion is needed here, when we loading just a geometry @@ -216,9 +217,10 @@ static int removed_objects_with_zero_volume(Model& model) Model load_model(const std::string& input_file, LoadAttributes options/* = LoadAttribute::AddDefaultInstances*/, - LoadStats* stats/*= nullptr*/) + LoadStats* stats/*= nullptr*/, + std::optional> step_deflections/* = std::nullopt*/) { - Model model = read_model_from_file(input_file, options); + Model model = read_model_from_file(input_file, options, step_deflections); for (auto obj : model.objects) if (obj->name.empty()) diff --git a/src/libslic3r/FileReader.hpp b/src/libslic3r/FileReader.hpp index 661328b580..1eef076e04 100644 --- a/src/libslic3r/FileReader.hpp +++ b/src/libslic3r/FileReader.hpp @@ -14,6 +14,8 @@ #include "PrintConfig.hpp" #include "enum_bitmask.hpp" +#include +#include namespace Slic3r { @@ -46,7 +48,8 @@ namespace FileReader // Exceptions don't catched inside Model load_model(const std::string& input_file, LoadAttributes options = LoadAttribute::AddDefaultInstances, - LoadStats* statistics = nullptr); + LoadStats* statistics = nullptr, + std::optional> step_deflections = std::nullopt); // Load model, config and config substitutions from input file and fill statistics if it's required. // Exceptions don't catched inside diff --git a/src/libslic3r/Format/STEP.cpp b/src/libslic3r/Format/STEP.cpp index c2761e07c0..c55f0dd861 100644 --- a/src/libslic3r/Format/STEP.cpp +++ b/src/libslic3r/Format/STEP.cpp @@ -27,9 +27,12 @@ namespace Slic3r { #if __APPLE__ -extern "C" bool load_step_internal(const char *path, OCCTResult* res); +extern "C" bool load_step_internal(const char *path, OCCTResult* res, std::optional> deflections /*= std::nullopt*/); #endif +// Inside deflections pair: +// * first value is linear deflection +// * second value is angle deflection LoadStepFn get_load_step_fn() { static LoadStepFn load_step_fn = nullptr; @@ -80,7 +83,7 @@ LoadStepFn get_load_step_fn() return load_step_fn; } -bool load_step(const char *path, Model *model /*BBS:, ImportStepProgressFn proFn*/) +bool load_step(const char *path, Model *model /*BBS:, ImportStepProgressFn proFn*/, std::optional> deflections) { OCCTResult occt_object; @@ -89,7 +92,7 @@ bool load_step(const char *path, Model *model /*BBS:, ImportStepProgressFn proFn if (!load_step_fn) return false; - load_step_fn(path, &occt_object); + load_step_fn(path, &occt_object, deflections); assert(! occt_object.volumes.empty()); diff --git a/src/libslic3r/Format/STEP.hpp b/src/libslic3r/Format/STEP.hpp index 0214a756ca..662908130f 100644 --- a/src/libslic3r/Format/STEP.hpp +++ b/src/libslic3r/Format/STEP.hpp @@ -9,6 +9,9 @@ #ifndef slic3r_Format_STEP_hpp_ #define slic3r_Format_STEP_hpp_ +#include +#include + namespace Slic3r { class Model; @@ -16,7 +19,10 @@ class Model; //typedef std::function ImportStepProgressFn; // Load a step file into a provided model. -extern bool load_step(const char *path_str, Model *model /*LMBBS:, ImportStepProgressFn proFn = nullptr*/); +// Inside deflections pair: +// * first value is linear deflection +// * second value is angle deflection +extern bool load_step(const char *path_str, Model *model /*LMBBS:, ImportStepProgressFn proFn = nullptr*/, std::optional> deflections = std::nullopt); }; // namespace Slic3r diff --git a/src/occt_wrapper/OCCTWrapper.cpp b/src/occt_wrapper/OCCTWrapper.cpp index 6ed3870c2d..0ec92cd1eb 100644 --- a/src/occt_wrapper/OCCTWrapper.cpp +++ b/src/occt_wrapper/OCCTWrapper.cpp @@ -79,7 +79,7 @@ static void getNamedSolids(const TopLoc_Location& location, const Handle(XCAFDoc } } -extern "C" OCCTWRAPPER_EXPORT bool load_step_internal(const char *path, OCCTResult* res /*BBS:, ImportStepProgressFn proFn*/) +extern "C" OCCTWRAPPER_EXPORT bool load_step_internal(const char *path, OCCTResult* res /*BBS:, ImportStepProgressFn proFn*/, std::optional> deflections /*= std::nullopt*/) { try { //bool cb_cancel = false; @@ -129,7 +129,9 @@ try { res->object_name = obj_name; for (const NamedSolid &namedSolid : namedSolids) { - BRepMesh_IncrementalMesh mesh(namedSolid.solid, STEP_TRANS_CHORD_ERROR, false, STEP_TRANS_ANGLE_RES, true); + BRepMesh_IncrementalMesh mesh(namedSolid.solid, + deflections.has_value() ? deflections.value().first : STEP_TRANS_CHORD_ERROR, false, + deflections.has_value() ? deflections.value().second : STEP_TRANS_ANGLE_RES, true); res->volumes.emplace_back(); std::vector vertices; diff --git a/src/occt_wrapper/OCCTWrapper.hpp b/src/occt_wrapper/OCCTWrapper.hpp index 7c86998f21..c5895e2fba 100644 --- a/src/occt_wrapper/OCCTWrapper.hpp +++ b/src/occt_wrapper/OCCTWrapper.hpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include struct stl_facet; @@ -21,7 +23,7 @@ struct OCCTResult { std::vector volumes; }; -using LoadStepFn = bool (*)(const char *path, OCCTResult* occt_result); +using LoadStepFn = bool (*)(const char *path, OCCTResult* occt_result, std::optional> deflections); }; // namespace Slic3r diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 1645f3ccad..8ff466174a 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -143,6 +143,8 @@ set(SLIC3R_GUI_SOURCES GUI/Sidebar.hpp GUI/Plater.cpp GUI/Plater.hpp + GUI/LoadStepDialog.cpp + GUI/LoadStepDialog.hpp GUI/PresetComboBoxes.hpp GUI/PresetComboBoxes.cpp GUI/BitmapComboBox.hpp diff --git a/src/slic3r/GUI/LoadStepDialog.cpp b/src/slic3r/GUI/LoadStepDialog.cpp new file mode 100644 index 0000000000..fc195990ec --- /dev/null +++ b/src/slic3r/GUI/LoadStepDialog.cpp @@ -0,0 +1,247 @@ +///|/ Copyright (c) Prusa Research 2018 - 2023 Vojtěch Bubník @bubnikv, Lukáš Matěna @lukasmatena, Oleksandra Iushchenko @YuSanka, Enrico Turri @enricoturri1966, Tomáš Mészáros @tamasmeszaros, David Kocík @kocikdav, Lukáš Hejl @hejllukas, Pavel Mikuš @Godrak, Filip Sykala @Jony01, Vojtěch Král @vojtechkral +///|/ Copyright (c) 2022 Michael Kirsch +///|/ Copyright (c) 2021 Boleslaw Ciesielski +///|/ Copyright (c) 2019 John Drake @foxox +///|/ +///|/ ported from lib/Slic3r/GUI/Plater.pm: +///|/ Copyright (c) Prusa Research 2016 - 2019 Vojtěch Bubník @bubnikv, Vojtěch Král @vojtechkral, Enrico Turri @enricoturri1966, Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, Tomáš Mészáros @tamasmeszaros +///|/ Copyright (c) 2018 Martin Loidl @LoidlM +///|/ Copyright (c) 2017 Matthias Gazzari @qtux +///|/ Copyright (c) Slic3r 2012 - 2016 Alessandro Ranellucci @alranel +///|/ Copyright (c) 2017 Joseph Lenox @lordofhyphens +///|/ Copyright (c) 2015 Daren Schwenke +///|/ Copyright (c) 2014 Mark Hindess +///|/ Copyright (c) 2012 Mike Sheldrake @mesheldrake +///|/ Copyright (c) 2012 Henrik Brix Andersen @henrikbrixandersen +///|/ Copyright (c) 2012 Sam Wong +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ + +#include "LoadStepDialog.hpp" +#include +#include +#include +#include +#include +#include +#include + +#include "GUI_App.hpp" +#include "format.hpp" +#include "MsgDialog.hpp" +#include "Widgets/CheckBox.hpp" + +namespace Slic3r::GUI { + +static std::vector> default_step_import_params = { + {"Low" , {0.1, 1. }}, + {"Medium" , {0.05, 0.5 }}, + {"High" , {0.001, 0.03}}, +}; + +LoadStepDialog::LoadStepDialog(wxWindow* parent, const std::string& filename, double linear_precision, double angle_precision, bool multiple_loading) + : DPIDialog(parent, wxID_ANY, format_wxstr(_L("STEP import quality (%1%)"), filename), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), + m_params({ linear_precision, angle_precision }) +{ + +#ifdef _WIN32 + wxGetApp().UpdateDarkUI(this); +#else + //SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); +#endif + const wxFont& font = wxGetApp().normal_font(); + SetFont(font); + + // Call your custom function manually after constructing base + wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); // Get the sizer + + add_params(main_sizer); + + main_sizer->Add(new StaticLine(this), 0, wxEXPAND | wxLEFT | wxRIGHT, em_unit()); + + wxBoxSizer* bottom_sizer = new wxBoxSizer(wxHORIZONTAL); + m_remember_chb = new ::CheckBox(this, _L("Remember my choice")); + + bottom_sizer->Add(m_remember_chb, 0, wxEXPAND | wxRIGHT, 5); + bottom_sizer->AddStretchSpacer(); + + auto buttons_sizer = CreateStdDialogButtonSizer(wxOK | wxCANCEL); + + if (multiple_loading) { + auto apply_btn = new wxButton(this, wxID_APPLY, "Apply to all"); + apply_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { + m_apply_to_all = true; + EndModal(wxID_OK); + }); + buttons_sizer->Insert(0, apply_btn, 0, wxRIGHT, 5); + } + + bottom_sizer->Add(buttons_sizer, 0, wxEXPAND | wxLEFT, 5); + main_sizer->Add(bottom_sizer, 0, wxEXPAND | wxALL, 10); + + SetSizer(main_sizer); + main_sizer->SetSizeHints(this); + + m_custom_sizer->ShowItems(!m_default); + + // Update DarkUi just for buttons + wxGetApp().UpdateDlgDarkUI(this, true); + +} + +void LoadStepDialog::add_params(wxSizer* sizer) +{ + wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); + main_sizer->Add(new wxStaticText(this, wxID_ANY, _L("Select requested quality of the mesh after import: "))); + + // add radio buttons for selection default parameters + + for (const auto& [name, params] : default_step_import_params) { + wxRadioButton* radio_def = new wxRadioButton(this, wxID_ANY, format_wxstr("%1%", _L(name))); + radio_def->Bind(wxEVT_RADIOBUTTON, [params_copy = params, this](wxEvent&) { + m_params.linear = params_copy.linear; + m_params.angle = params_copy.angle; + + m_custom_sizer->ShowItems(false); + }); + bool is_selected = m_params.linear == params.linear && params.angle == params.angle; + radio_def->SetValue(is_selected); + + m_default |= is_selected; + + main_sizer->Add(radio_def, 0, wxLEFT | wxTOP, em_unit()); + } + + // add radio buttons for set custom parameters + + wxRadioButton* radio_custom = new wxRadioButton(this, wxID_ANY, _L("Custom")); + radio_custom->Bind(wxEVT_RADIOBUTTON, [&, this](wxEvent&) { + m_custom_sizer->ShowItems(true); +#ifdef __linux__ + this->Fit(); +#endif // __linux__ + m_params.linear = string_to_double_decimal_point(m_linear_precision_val->GetValue().ToStdString()); + m_params.angle = string_to_double_decimal_point(m_angle_precision_val->GetValue().ToStdString()); + }); + + main_sizer->Add(radio_custom, 0, wxLEFT | wxTOP, em_unit()); + radio_custom->SetValue(!m_default); + + long slyder_style = wxSL_HORIZONTAL | wxSL_TICKS; + long text_ctrl_style = wxTE_PROCESS_ENTER; +#ifdef _WIN32 + text_ctrl_style |= wxBORDER_SIMPLE; +#endif + + const wxSize def_slider_size = wxSize(15 * em_unit(), wxDefaultCoord); + const wxSize def_editor_size = wxSize(5 * em_unit(), wxDefaultCoord); + + const int hgap = 5; + wxFlexGridSizer* grid_sizer = new wxFlexGridSizer(4, em_unit(), hgap); + grid_sizer->SetFlexibleDirection(wxBOTH); + grid_sizer->AddGrowableCol(1, 1); + grid_sizer->AddGrowableRow(0, 1); + grid_sizer->AddGrowableRow(1, 1); + + wxBoxSizer* labels_sizer = new wxBoxSizer(wxHORIZONTAL); + { + const wxString left_text = _L("Lower quality"); + const int left_text_gap = std::max(GetTextExtent(_L("Linear precision")).x, GetTextExtent(_L("Angle precision")).x) + 4 * hgap - GetTextExtent(left_text).x * 0.5; + const wxString right_text = _L("Higher quality"); + const int right_text_gap = GetTextExtent(_L("mm")).x + def_editor_size.x + 4 * hgap - GetTextExtent(right_text).x * 0.5; + labels_sizer->Add(new wxStaticText(this, wxID_ANY, left_text), 0, wxLEFT, left_text_gap); + labels_sizer->Add(new wxStaticText(this, wxID_ANY, wxEmptyString), 1, wxEXPAND); + labels_sizer->Add(new wxStaticText(this, wxID_ANY, right_text), 0, wxRIGHT, right_text_gap); + } + + auto high_vals = std::find_if(default_step_import_params.begin(), default_step_import_params.end(), + [](const std::pair& val) { return val.first == "High"; }); + auto low_vals = std::find_if(default_step_import_params.begin(), default_step_import_params.end(), + [](const std::pair& val) { return val.first == "Low"; }); + assert(high_vals != default_step_import_params.end() && low_vals != default_step_import_params.end()); + + m_linear_precision_sl.init(high_vals->second.linear, low_vals->second.linear, 0.001); + m_angle_precision_sl.init(high_vals->second.angle, low_vals->second.angle, 0.01); + + auto process_value_change = [](double& precision, wxTextCtrl* text_ctrl, wxSlider* slider, const SliderHelper& sl_helper) -> void { + wxString str_val = text_ctrl->GetValue(); + double val = string_to_double_decimal_point(str_val.ToStdString()); + precision = sl_helper.adjust_to_region(val); + slider->SetValue(sl_helper.get_pos(precision)); + + if (wxString str_precision = format_wxstr("%1%", precision); str_precision != str_val) + text_ctrl->SetValue(str_precision); + }; + + // Add "Linear precision" + + m_linear_precision_slider = new wxSlider(this, wxID_ANY, m_linear_precision_sl.get_pos(m_params.linear), m_linear_precision_sl.beg_sl_pos, m_linear_precision_sl.end_sl_pos, wxDefaultPosition, def_slider_size, slyder_style); + m_linear_precision_slider->SetTickFreq(10); + m_linear_precision_slider->Bind(wxEVT_SLIDER, [this](wxCommandEvent e) { + m_params.linear = m_linear_precision_sl.get_value(m_linear_precision_slider->GetValue()); + m_linear_precision_val->SetValue(format_wxstr("%1%", m_params.linear)); + }); + + m_linear_precision_val = new wxTextCtrl(this, wxID_ANY, format_wxstr("%1%", m_linear_precision_sl.adjust_to_region(m_params.linear)), wxDefaultPosition, def_editor_size, text_ctrl_style); + m_linear_precision_val->SetToolTip(format_wxstr("Set value from the range [%1%; %2%] with %3% step", + m_linear_precision_sl.min_val, m_linear_precision_sl.max_val, m_linear_precision_sl.val_step)); + + m_linear_precision_val->Bind(wxEVT_TEXT_ENTER, [process_value_change, this](wxCommandEvent& e) { + process_value_change(m_params.linear, m_linear_precision_val, m_linear_precision_slider, m_linear_precision_sl); + }); + m_linear_precision_val->Bind(wxEVT_KILL_FOCUS, [process_value_change, this](wxFocusEvent& e) { + process_value_change(m_params.linear, m_linear_precision_val, m_linear_precision_slider, m_linear_precision_sl); + e.Skip(); + }); + + grid_sizer->Add(new wxStaticText(this, wxID_ANY, _L("Linear precision") + ": "), 0, wxALIGN_CENTER_VERTICAL); + grid_sizer->Add(m_linear_precision_slider, 1, wxEXPAND); + grid_sizer->Add(m_linear_precision_val, 0, wxALIGN_CENTER_VERTICAL); + grid_sizer->Add(new wxStaticText(this, wxID_ANY, _L("mm")), 0, wxALIGN_CENTER_VERTICAL); + + // Add "Angle precision" + + m_angle_precision_slider = new wxSlider(this, wxID_ANY, m_angle_precision_sl.get_pos(m_params.angle), m_angle_precision_sl.beg_sl_pos, m_angle_precision_sl.end_sl_pos, wxDefaultPosition, def_slider_size, slyder_style); + m_angle_precision_slider->SetTickFreq(10); + m_angle_precision_slider->Bind(wxEVT_SLIDER, [this](wxCommandEvent e) { + m_params.angle = m_angle_precision_sl.get_value(m_angle_precision_slider->GetValue()); + m_angle_precision_val->SetValue(format_wxstr("%1%", m_params.angle)); + }); + + m_angle_precision_val = new wxTextCtrl(this, wxID_ANY, format_wxstr("%1%", m_angle_precision_sl.adjust_to_region(m_params.angle)), wxDefaultPosition, def_editor_size, text_ctrl_style); + m_angle_precision_val->SetToolTip(format_wxstr("Set value from the range [%1%; %2%] with %3% step", + m_angle_precision_sl.min_val, m_angle_precision_sl.max_val, m_angle_precision_sl.val_step)); + + m_angle_precision_val->Bind(wxEVT_TEXT_ENTER, [process_value_change, this](wxCommandEvent& e) { + process_value_change(m_params.angle, m_angle_precision_val, m_angle_precision_slider, m_angle_precision_sl); + }); + m_angle_precision_val->Bind(wxEVT_KILL_FOCUS, [process_value_change, this](wxFocusEvent& e) { + process_value_change(m_params.angle, m_angle_precision_val, m_angle_precision_slider, m_angle_precision_sl); + e.Skip(); + }); + + grid_sizer->Add(new wxStaticText(this, wxID_ANY, _L("Angle precision") + ": "), 0, wxALIGN_CENTER_VERTICAL); + grid_sizer->Add(m_angle_precision_slider, 1, wxEXPAND); + grid_sizer->Add(m_angle_precision_val, 0, wxALIGN_CENTER_VERTICAL); + grid_sizer->Add(new wxStaticText(this, wxID_ANY, _L("°")), 0, wxALIGN_CENTER_VERTICAL); + + m_custom_sizer = new wxBoxSizer(wxVERTICAL); + m_custom_sizer->Add(labels_sizer, 0, wxEXPAND | wxBOTTOM | wxTOP, em_unit()); + m_custom_sizer->Add(grid_sizer, 1, wxEXPAND); + + main_sizer->Add(m_custom_sizer, 1, wxEXPAND | wxLEFT, 3 * em_unit()); + sizer->Add(main_sizer, 1, wxEXPAND | wxALL, em_unit()); +} + +bool LoadStepDialog::IsCheckBoxChecked() +{ + return m_remember_chb && m_remember_chb->GetValue(); +} + +bool LoadStepDialog::IsApplyToAllClicked() +{ + return m_apply_to_all; +} + +} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/LoadStepDialog.hpp b/src/slic3r/GUI/LoadStepDialog.hpp new file mode 100644 index 0000000000..125d61a061 --- /dev/null +++ b/src/slic3r/GUI/LoadStepDialog.hpp @@ -0,0 +1,99 @@ +///|/ Copyright (c) Prusa Research 2018 - 2025 Oleksandra Iushchenko @YuSanka +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ + +#ifndef slic3r_LoadStepDialog_hpp_ +#define slic3r_LoadStepDialog_hpp_ + +#include +#include +#include "GUI_Utils.hpp" + +class wxBoxSizer; +class wxTextCtrl; +class wxSlider; +class CheckBox; + +namespace Slic3r::GUI { + +struct PrecisionParams +{ + double linear; + double angle; +}; + +struct SliderHelper +{ + double min_val; + double max_val; + double val_step; + int beg_sl_pos; + int end_sl_pos; + + void init(double min, double max, double step, int beg_pos = 1) { + assert(val_step != 0.); + min_val = min; + max_val = max; + val_step = step; + + beg_sl_pos = beg_pos; + end_sl_pos = beg_sl_pos + int(double(max_val - min_val) / val_step); + } + + double get_value(int pos) const { + return max_val - val_step * (pos - beg_sl_pos); + } + + int get_pos(double value) const { + return beg_sl_pos + int((max_val - value) / val_step); + } + + double adjust_to_region(double value) const { + return std::max(std::min(value, max_val), min_val); + } +}; + +class LoadStepDialog : public DPIDialog +{ +public: + LoadStepDialog(wxWindow* parent, const std::string& filename, double linear_precision, double angle_precision, bool multiple_loading); + ~LoadStepDialog() = default; + + bool IsCheckBoxChecked(); + bool IsApplyToAllClicked(); + + double get_linear_precision() { return m_params.linear; } + double get_angle_precision() { return m_params.angle; } + +protected: + void on_dpi_changed(const wxRect& suggested_rect) override {} + void on_sys_color_changed() override {}; + + +private: + void add_params(wxSizer* sizer); + +private: + PrecisionParams m_params; + + ::CheckBox* m_remember_chb { nullptr }; + + wxTextCtrl* m_linear_precision_val { nullptr }; + wxTextCtrl* m_angle_precision_val { nullptr }; + + wxSlider* m_linear_precision_slider { nullptr }; + wxSlider* m_angle_precision_slider { nullptr }; + + wxBoxSizer* m_custom_sizer { nullptr }; + + bool m_default { false }; + bool m_apply_to_all { false }; + + SliderHelper m_linear_precision_sl; + SliderHelper m_angle_precision_sl; +}; + +} // namespace Slic3r::GUI + +#endif diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index f40b858e8e..9c6b45e189 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -138,6 +139,7 @@ #include "ConfigWizardWebViewPage.hpp" #include "PresetArchiveDatabase.hpp" #include "BulkExportDialog.hpp" +#include "LoadStepDialog.hpp" #include "libslic3r/ArrangeHelper.hpp" @@ -325,6 +327,7 @@ struct Plater::priv static const std::regex pattern_prusa; static const std::regex pattern_zip; static const std::regex pattern_printRequest; + static const std::regex pattern_step; priv(Plater *q, MainFrame *main_frame); ~priv(); @@ -615,11 +618,13 @@ private: bool show_warning_dialog { false }; }; +// FIXME: Some of the regex patterns are wrong (missing [.] before file extension). const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|3mf)", std::regex::icase); const std::regex Plater::priv::pattern_3mf(".*3mf", std::regex::icase); const std::regex Plater::priv::pattern_any_amf(".*[.](amf|amf[.]xml|zip[.]amf)", std::regex::icase); const std::regex Plater::priv::pattern_zip(".*zip", std::regex::icase); const std::regex Plater::priv::pattern_printRequest(".*printRequest", std::regex::icase); +const std::regex Plater::priv::pattern_step(".*[.](step|stp)", std::regex::icase); Plater::priv::priv(Plater* q, MainFrame* main_frame) : q(q) @@ -1288,6 +1293,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ int answer_convert_from_meters = wxOK_DEFAULT; int answer_convert_from_imperial_units = wxOK_DEFAULT; int answer_consider_as_multi_part_objects = wxOK_DEFAULT; + bool apply_step_import_parameters_to_all { false }; bool in_temp = false; const fs::path temp_path = wxStandardPaths::Get().GetTempDir().utf8_str().data(); @@ -1303,7 +1309,26 @@ std::vector Plater::priv::load_files(const std::vector& input_ const auto &path = input_files[i]; #endif // _WIN32 in_temp = (path.parent_path() == temp_path); - const auto filename = path.filename(); + const boost::filesystem::path filename = path.filename(); + + const bool type_step = std::regex_match(path.string(), pattern_step); + if (type_step && !apply_step_import_parameters_to_all && + wxGetApp().app_config->get_bool("show_step_import_parameters")) { + + double linear_precision = string_to_double_decimal_point(wxGetApp().app_config->get("linear_precision")); + double angle_precision = string_to_double_decimal_point(wxGetApp().app_config->get("angle_precision")); + + LoadStepDialog dlg(q, filename.string(), linear_precision, angle_precision, (input_files_size - i) > 1); + if (dlg.ShowModal() == wxID_OK) { + wxGetApp().app_config->set("linear_precision", float_to_string_decimal_point(dlg.get_linear_precision())); + wxGetApp().app_config->set("angle_precision", float_to_string_decimal_point(dlg.get_angle_precision())); + if (dlg.IsCheckBoxChecked()) + wxGetApp().app_config->set("show_step_import_parameters", "0"); + apply_step_import_parameters_to_all = dlg.IsApplyToAllClicked(); + } else + continue; + } + if (progress_dlg) { progress_dlg->Update(static_cast(100.0f * static_cast(i) / static_cast(input_files.size())), _L("Loading file") + ": " + from_path(filename)); progress_dlg->Fit(); @@ -1352,7 +1377,14 @@ std::vector Plater::priv::load_files(const std::vector& input_ model = FileReader::load_model_with_config(path.string(), &config_loaded, &config_substitutions, prusaslicer_generator_version, FileReader::LoadAttribute::CheckVersion, &load_stats); } else if (load_model) { - model = FileReader::load_model(path.string(), FileReader::LoadAttributes{}, &load_stats); + if (type_step) { + double linear_precision = string_to_double_decimal_point(wxGetApp().app_config->get("linear_precision")); + double angle_precision = string_to_double_decimal_point(wxGetApp().app_config->get("angle_precision")); + model = FileReader::load_model(path.string(), FileReader::LoadAttributes{}, &load_stats, + std::make_pair(linear_precision, angle_precision)); + } + else + model = FileReader::load_model(path.string(), FileReader::LoadAttributes{}, &load_stats); } } catch (const ConfigurationError &e) { std::string message = GUI::format(_L("Failed loading file \"%1%\" due to an invalid configuration."), filename.string()) + "\n\n" + e.what(); diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index ae92250230..e484e4e945 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -135,7 +135,7 @@ void PreferencesDialog::show(const std::string& highlight_opt_key /*= std::strin downloader->set_path_name(app_config->get("url_downloader_dest")); downloader->allow(!app_config->has("downloader_url_registered") || app_config->get_bool("downloader_url_registered")); - for (const std::string opt_key : {"suppress_hyperlinks", "downloader_url_registered", "show_login_button"}) + for (const std::string opt_key : {"suppress_hyperlinks", "downloader_url_registered", "show_login_button", "show_step_import_parameters"}) m_optgroup_other->set_value(opt_key, app_config->get_bool(opt_key)); // by default "Log in" button is visible if (!app_config->has("show_login_button")) @@ -629,6 +629,11 @@ void PreferencesDialog::build() // "If disabled, the descriptions of configuration parameters in settings tabs will work as hyperlinks."), app_config->get_bool("suppress_hyperlinks")); + append_bool_option(m_optgroup_other, "show_step_import_parameters", + L("Show STEP file import parameters"), + L("If enabled, PrusaSlicer will show dialog with quality selection when importing STEP file."), + app_config->get_bool("show_step_import_parameters")); + append_bool_option(m_optgroup_other, "show_login_button", L("Show \"Log in\" button in application top bar"), L("If enabled, PrusaSlicer will show up \"Log in\" button in application top bar."),