@@ -99,10 +100,8 @@ void BackgroundSlicingProcess::process_fff()
//FIXME localize the messages
// Perform the final post-processing of the export path by applying the print statistics over the file name.
std::string export_path = m_fff_print->print_statistics().finalize_output_path(m_export_path);
- GUI::RemovableDriveManager::get_instance().update();
- bool with_check = GUI::RemovableDriveManager::get_instance().is_path_on_removable_drive(export_path);
- int copy_ret_val = copy_file(m_temp_output_path, export_path, with_check);
- switch (copy_ret_val){
+ int copy_ret_val = copy_file(m_temp_output_path, export_path, m_export_path_on_removable_media);
+ switch (copy_ret_val) {
case SUCCESS: break; // no error
case FAIL_COPY_FILE:
throw std::runtime_error(_utf8(L("Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?")));
@@ -218,7 +217,7 @@ void BackgroundSlicingProcess::thread_proc()
// Canceled, this is all right.
assert(m_print->canceled());
} catch (const std::bad_alloc& ex) {
- wxString errmsg = wxString::FromUTF8((boost::format(_utf8(L("%s has encountered an error. It was likely caused by running out of memory. "
+ wxString errmsg = GUI::from_u8((boost::format(_utf8(L("%s has encountered an error. It was likely caused by running out of memory. "
"If you are sure you have enough RAM on your system, this may also be a bug and we would "
"be glad if you reported it."))) % SLIC3R_APP_NAME).str());
error = std::string(errmsg.ToUTF8()) + "\n\n" + std::string(ex.what());
@@ -234,7 +233,7 @@ void BackgroundSlicingProcess::thread_proc()
// Only post the canceled event, if canceled by user.
// Don't post the canceled event, if canceled from Print::apply().
wxCommandEvent evt(m_event_finished_id);
- evt.SetString(error);
+ evt.SetString(GUI::from_u8(error));
evt.SetInt(m_print->canceled() ? -1 : (error.empty() ? 1 : 0));
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone());
}
@@ -401,7 +400,7 @@ void BackgroundSlicingProcess::set_task(const PrintBase::TaskParams ¶ms)
}
// Set the output path of the G-code.
-void BackgroundSlicingProcess::schedule_export(const std::string &path)
+void BackgroundSlicingProcess::schedule_export(const std::string &path, bool export_path_on_removable_media)
{
assert(m_export_path.empty());
if (! m_export_path.empty())
@@ -411,6 +410,7 @@ void BackgroundSlicingProcess::schedule_export(const std::string &path)
tbb::mutex::scoped_lock lock(m_print->state_mutex());
this->invalidate_step(bspsGCodeFinalize);
m_export_path = path;
+ m_export_path_on_removable_media = export_path_on_removable_media;
}
void BackgroundSlicingProcess::schedule_upload(Slic3r::PrintHostJob upload_job)
@@ -431,6 +431,7 @@ void BackgroundSlicingProcess::reset_export()
assert(! this->running());
if (! this->running()) {
m_export_path.clear();
+ m_export_path_on_removable_media = false;
// invalidate_step expects the mutex to be locked.
tbb::mutex::scoped_lock lock(m_print->state_mutex());
this->invalidate_step(bspsGCodeFinalize);
diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp
index 6373bad7ed..02f65d482f 100644
--- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp
+++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp
@@ -98,7 +98,7 @@ public:
// Set the export path of the G-code.
// Once the path is set, the G-code
- void schedule_export(const std::string &path);
+ void schedule_export(const std::string &path, bool export_path_on_removable_media);
// Set print host upload job data to be enqueued to the PrintHostJobQueue
// after current print slicing is complete
void schedule_upload(Slic3r::PrintHostJob upload_job);
@@ -157,13 +157,14 @@ private:
GCodePreviewData *m_gcode_preview_data = nullptr;
#if ENABLE_THUMBNAIL_GENERATOR
// Callback function, used to write thumbnails into gcode.
- ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr;
+ ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr;
#endif // ENABLE_THUMBNAIL_GENERATOR
// Temporary G-code, there is one defined for the BackgroundSlicingProcess, differentiated from the other processes by a process ID.
std::string m_temp_output_path;
// Output path provided by the user. The output path may be set even if the slicing is running,
// but once set, it cannot be re-set.
std::string m_export_path;
+ bool m_export_path_on_removable_media = false;
// Print host upload job to schedule after slicing is complete, used by schedule_upload(),
// empty by default (ie. no upload to schedule)
PrintHostJob m_upload_job;
diff --git a/src/slic3r/GUI/BonjourDialog.cpp b/src/slic3r/GUI/BonjourDialog.cpp
index 42750885cd..8ee01c9493 100644
--- a/src/slic3r/GUI/BonjourDialog.cpp
+++ b/src/slic3r/GUI/BonjourDialog.cpp
@@ -219,10 +219,10 @@ void BonjourDialog::on_timer(wxTimerEvent &)
if (timer_state > 0) {
const std::string dots(timer_state, '.');
- label->SetLabel(wxString::FromUTF8((boost::format("%1% %2%") % search_str % dots).str()));
+ label->SetLabel(GUI::from_u8((boost::format("%1% %2%") % search_str % dots).str()));
timer_state = (timer_state) % 3 + 1;
} else {
- label->SetLabel(wxString::FromUTF8((boost::format("%1%: %2%") % search_str % (_utf8(L("Finished"))+".")).str()));
+ label->SetLabel(GUI::from_u8((boost::format("%1%: %2%") % search_str % (_utf8(L("Finished"))+".")).str()));
timer->Stop();
}
}
diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp
index 0a0b02c6f0..74c21d3e80 100644
--- a/src/slic3r/GUI/Camera.cpp
+++ b/src/slic3r/GUI/Camera.cpp
@@ -260,7 +260,7 @@ void Camera::debug_render() const
imgui.begin(std::string("Camera statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
std::string type = get_type_as_string();
- if (wxGetApp().plater()->get_mouse3d_controller().is_running() || (wxGetApp().app_config->get("use_free_camera") == "1"))
+ if (wxGetApp().plater()->get_mouse3d_controller().connected() || (wxGetApp().app_config->get("use_free_camera") == "1"))
type += "/free";
else
type += "/constrained";
@@ -537,6 +537,7 @@ void Camera::look_at(const Vec3d& position, const Vec3d& target, const Vec3d& up
Vec3d unit_y = unit_z.cross(unit_x).normalized();
m_target = target;
+ m_distance = (position - target).norm();
Vec3d new_position = m_target + m_distance * unit_z;
m_view_matrix(0, 0) = unit_x(0);
diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp
index f592dbcd22..b784e7b96a 100644
--- a/src/slic3r/GUI/Camera.hpp
+++ b/src/slic3r/GUI/Camera.hpp
@@ -122,6 +122,15 @@ public:
// returns true if the camera z axis (forward) is pointing in the negative direction of the world z axis
bool is_looking_downward() const { return get_dir_forward().dot(Vec3d::UnitZ()) < 0.0; }
+ // forces camera right vector to be parallel to XY plane
+ void recover_from_free_camera()
+ {
+ if (std::abs(get_dir_right()(2)) > EPSILON)
+ look_at(get_position(), m_target, Vec3d::UnitZ());
+ }
+
+ void look_at(const Vec3d& position, const Vec3d& target, const Vec3d& up);
+
double max_zoom() const { return 100.0; }
double min_zoom() const;
@@ -137,7 +146,6 @@ private:
#endif // ENABLE_THUMBNAIL_GENERATOR
void set_distance(double distance) const;
- void look_at(const Vec3d& position, const Vec3d& target, const Vec3d& up);
void set_default_orientation();
Vec3d validate_target(const Vec3d& target) const;
void update_zenit();
diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp
index c9143b6450..8d1daeb8e3 100644
--- a/src/slic3r/GUI/ConfigManipulation.cpp
+++ b/src/slic3r/GUI/ConfigManipulation.cpp
@@ -268,8 +268,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
"bridge_acceleration", "first_layer_acceleration" })
toggle_field(el, have_default_acceleration);
- bool have_skirt = config->opt_int("skirts") > 0 || config->opt_float("min_skirt_length") > 0;
- for (auto el : { "skirt_distance", "skirt_height" })
+ bool have_skirt = config->opt_int("skirts") > 0;
+ toggle_field("skirt_height", have_skirt && !config->opt_bool("draft_shield"));
+ for (auto el : { "skirt_distance", "draft_shield", "min_skirt_length" })
toggle_field(el, have_skirt);
bool have_brim = config->opt_float("brim_width") > 0;
diff --git a/src/slic3r/GUI/ConfigSnapshotDialog.cpp b/src/slic3r/GUI/ConfigSnapshotDialog.cpp
index b49e994a9c..6a44b96dc6 100644
--- a/src/slic3r/GUI/ConfigSnapshotDialog.cpp
+++ b/src/slic3r/GUI/ConfigSnapshotDialog.cpp
@@ -72,7 +72,7 @@ static wxString generate_html_row(const Config::Snapshot &snapshot, bool row_eve
}
if (! compatible) {
- text += "" + wxString::FromUTF8((boost::format(_utf8(L("Incompatible with this %s"))) % SLIC3R_APP_NAME).str()) + "
";
+ text += "" + from_u8((boost::format(_utf8(L("Incompatible with this %s"))) % SLIC3R_APP_NAME).str()) + "
";
}
else if (! snapshot_active)
text += "" + _(L("Activate")) + "
";
diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp
index 1f35d941d6..d0c768de50 100644
--- a/src/slic3r/GUI/ConfigWizard.cpp
+++ b/src/slic3r/GUI/ConfigWizard.cpp
@@ -42,16 +42,27 @@ using Config::SnapshotDB;
// Configuration data structures extensions needed for the wizard
-Bundle::Bundle(fs::path source_path, bool is_in_resources, bool is_prusa_bundle)
- : preset_bundle(new PresetBundle)
- , vendor_profile(nullptr)
- , is_in_resources(is_in_resources)
- , is_prusa_bundle(is_prusa_bundle)
+bool Bundle::load(fs::path source_path, bool ais_in_resources, bool ais_prusa_bundle)
{
- preset_bundle->load_configbundle(source_path.string(), PresetBundle::LOAD_CFGBNDLE_SYSTEM);
+ this->preset_bundle = std::make_unique();
+ this->is_in_resources = ais_in_resources;
+ this->is_prusa_bundle = ais_prusa_bundle;
+
+ std::string path_string = source_path.string();
+ size_t presets_loaded = preset_bundle->load_configbundle(path_string, PresetBundle::LOAD_CFGBNDLE_SYSTEM);
auto first_vendor = preset_bundle->vendors.begin();
- wxCHECK_RET(first_vendor != preset_bundle->vendors.end(), "Failed to load preset bundle");
- vendor_profile = &first_vendor->second;
+ if (first_vendor == preset_bundle->vendors.end()) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No vendor information defined, cannot install.") % path_string;
+ return false;
+ }
+ if (presets_loaded == 0) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No profile loaded.") % path_string;
+ return false;
+ }
+
+ BOOST_LOG_TRIVIAL(trace) << boost::format("Vendor bundle: `%1%`: %2% profiles loaded.") % path_string % presets_loaded;
+ this->vendor_profile = &first_vendor->second;
+ return true;
}
Bundle::Bundle(Bundle &&other)
@@ -76,8 +87,11 @@ BundleMap BundleMap::load()
prusa_bundle_path = (rsrc_vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini");
prusa_bundle_rsrc = true;
}
- Bundle prusa_bundle(std::move(prusa_bundle_path), prusa_bundle_rsrc, true);
- res.emplace(PresetBundle::PRUSA_BUNDLE, std::move(prusa_bundle));
+ {
+ Bundle prusa_bundle;
+ if (prusa_bundle.load(std::move(prusa_bundle_path), prusa_bundle_rsrc, true))
+ res.emplace(PresetBundle::PRUSA_BUNDLE, std::move(prusa_bundle));
+ }
// Load the other bundles in the datadir/vendor directory
// and then additionally from resources/profiles.
@@ -90,8 +104,9 @@ BundleMap BundleMap::load()
// Don't load this bundle if we've already loaded it.
if (res.find(id) != res.end()) { continue; }
- Bundle bundle(dir_entry.path(), is_in_resources);
- res.emplace(std::move(id), std::move(bundle));
+ Bundle bundle;
+ if (bundle.load(dir_entry.path(), is_in_resources))
+ res.emplace(std::move(id), std::move(bundle));
}
}
@@ -173,7 +188,7 @@ PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxSt
wxBitmap bitmap;
int bitmap_width = 0;
- const wxString bitmap_file = GUI::from_u8(Slic3r::var((boost::format("printers/%1%_%2%.png") % vendor.id % model.id).str()));
+ const wxString bitmap_file = GUI::from_u8(Slic3r::resources_dir() + "/profiles/" + vendor.id + "/" + model.id + "_thumbnail.png");
if (wxFileExists(bitmap_file)) {
bitmap.LoadFile(bitmap_file, wxBITMAP_TYPE_PNG);
bitmap_width = bitmap.GetWidth();
@@ -422,20 +437,20 @@ void ConfigWizardPage::append_spacer(int space)
// Wizard pages
PageWelcome::PageWelcome(ConfigWizard *parent)
- : ConfigWizardPage(parent, wxString::FromUTF8((boost::format(
+ : ConfigWizardPage(parent, from_u8((boost::format(
#ifdef __APPLE__
_utf8(L("Welcome to the %s Configuration Assistant"))
#else
_utf8(L("Welcome to the %s Configuration Wizard"))
#endif
) % SLIC3R_APP_NAME).str()), _(L("Welcome")))
- , welcome_text(append_text(wxString::FromUTF8((boost::format(
+ , welcome_text(append_text(from_u8((boost::format(
_utf8(L("Hello, welcome to %s! This %s helps you with the initial configuration; just a few settings and you will be ready to print.")))
% SLIC3R_APP_NAME
% _utf8(ConfigWizard::name())).str())
))
, cbox_reset(append(
- new wxCheckBox(this, wxID_ANY, _(L("Remove user profiles - install from scratch (a snapshot will be taken beforehand)")))
+ new wxCheckBox(this, wxID_ANY, _(L("Remove user profiles (a snapshot will be taken beforehand)")))
))
{
welcome_text->Hide();
@@ -478,7 +493,7 @@ PagePrinters::PagePrinters(ConfigWizard *parent,
continue;
}
- const auto picker_title = family.empty() ? wxString() : wxString::FromUTF8((boost::format(_utf8(L("%s Family"))) % family).str());
+ const auto picker_title = family.empty() ? wxString() : from_u8((boost::format(_utf8(L("%s Family"))) % family).str());
auto *picker = new PrinterPicker(this, vendor, picker_title, MAX_COLS, *appconfig, filter);
picker->Bind(EVT_PRINTER_PICK, [this, appconfig](const PrinterPickerEvent &evt) {
@@ -1458,12 +1473,41 @@ void ConfigWizard::priv::load_vendors()
pair.second.preset_bundle->load_installed_printers(appconfig_new);
}
- if (app_config->has_section(AppConfig::SECTION_FILAMENTS)) {
- appconfig_new.set_section(AppConfig::SECTION_FILAMENTS, app_config->get_section(AppConfig::SECTION_FILAMENTS));
- }
- if (app_config->has_section(AppConfig::SECTION_MATERIALS)) {
- appconfig_new.set_section(AppConfig::SECTION_MATERIALS, app_config->get_section(AppConfig::SECTION_MATERIALS));
- }
+ // Copy installed filaments and SLA material names from app_config to appconfig_new
+ // while resolving current names of profiles, which were renamed in the meantime.
+ for (PrinterTechnology technology : { ptFFF, ptSLA }) {
+ const std::string §ion_name = (technology == ptFFF) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS;
+ std::map section_new;
+ if (app_config->has_section(section_name)) {
+ const std::map §ion_old = app_config->get_section(section_name);
+ for (const std::pair &material_name_and_installed : section_old)
+ if (material_name_and_installed.second == "1") {
+ // Material is installed. Resolve it in bundles.
+ size_t num_found = 0;
+ const std::string &material_name = material_name_and_installed.first;
+ for (auto &bundle : bundles) {
+ const PresetCollection &materials = bundle.second.preset_bundle->materials(technology);
+ const Preset *preset = materials.find_preset(material_name);
+ if (preset == nullptr) {
+ // Not found. Maybe the material preset is there, bu it was was renamed?
+ const std::string *new_name = materials.get_preset_name_renamed(material_name);
+ if (new_name != nullptr)
+ preset = materials.find_preset(*new_name);
+ }
+ if (preset != nullptr) {
+ // Materal preset was found, mark it as installed.
+ section_new[preset->name] = "1";
+ ++ num_found;
+ }
+ }
+ if (num_found == 0)
+ BOOST_LOG_TRIVIAL(error) << boost::format("Profile %1% was not found in installed vendor Preset Bundles.") % material_name;
+ else if (num_found > 1)
+ BOOST_LOG_TRIVIAL(error) << boost::format("Profile %1% was found in %2% vendor Preset Bundles.") % material_name % num_found;
+ }
+ }
+ appconfig_new.set_section(section_name, section_new);
+ };
}
void ConfigWizard::priv::add_page(ConfigWizardPage *page)
@@ -1627,9 +1671,9 @@ void ConfigWizard::priv::on_printer_pick(PagePrinters *page, const PrinterPicker
}
}
- // if at list one printer is selected but there in no one selected material,
- // select materials which is default for selected printer(s)
- select_default_materials_if_needed(pair.second.vendor_profile, page->technology, evt.model_id);
+ // When a printer model is picked, but there is no material installed compatible with this printer model,
+ // install default materials for selected printer model silently.
+ check_and_install_missing_materials(page->technology, evt.model_id);
}
if (page->technology & T_FFF) {
@@ -1639,41 +1683,26 @@ void ConfigWizard::priv::on_printer_pick(PagePrinters *page, const PrinterPicker
}
}
-void ConfigWizard::priv::select_default_materials_for_printer_model(const std::vector& models, Technology technology, const std::string& model_id)
+void ConfigWizard::priv::select_default_materials_for_printer_model(const VendorProfile::PrinterModel &printer_model, Technology technology)
{
PageMaterials* page_materials = technology & T_FFF ? page_filaments : page_sla_materials;
-
- auto it = std::find_if(models.begin(), models.end(), [model_id](VendorProfile::PrinterModel model) {return model_id == model.id; });
- if (it != models.end())
- for (const std::string& material : it->default_materials)
- appconfig_new.set(page_materials->materials->appconfig_section(), material, "1");
+ for (const std::string& material : printer_model.default_materials)
+ appconfig_new.set(page_materials->materials->appconfig_section(), material, "1");
}
-void ConfigWizard::priv::select_default_materials_if_needed(VendorProfile* vendor_profile, Technology technology, const std::string& model_id)
+void ConfigWizard::priv::select_default_materials_for_printer_models(Technology technology, const std::set &printer_models)
{
- if ((technology & T_FFF && !any_fff_selected) ||
- (technology & T_SLA && !any_sla_selected) ||
- check_materials_in_config(technology, false))
- return;
+ PageMaterials *page_materials = technology & T_FFF ? page_filaments : page_sla_materials;
+ const std::string &appconfig_section = page_materials->materials->appconfig_section();
- select_default_materials_for_printer_model(vendor_profile->models, technology, model_id);
-}
-
-void ConfigWizard::priv::selected_default_materials(Technology technology)
-{
- auto select_default_materials_for_printer_page = [this](PagePrinters * page_printers, Technology technology)
+ auto select_default_materials_for_printer_page = [this, appconfig_section, printer_models](PagePrinters *page_printers, Technology technology)
{
- std::set selected_models = page_printers->get_selected_models();
- const std::string vendor_id = page_printers->get_vendor_id();
-
+ const std::string vendor_id = page_printers->get_vendor_id();
for (auto& pair : bundles)
- {
- if (pair.first != vendor_id)
- continue;
-
- for (const std::string& model_id : selected_models)
- select_default_materials_for_printer_model(pair.second.vendor_profile->models, technology, model_id);
- }
+ if (pair.first == vendor_id)
+ for (const VendorProfile::PrinterModel *printer_model : printer_models)
+ for (const std::string &material : printer_model->default_materials)
+ appconfig_new.set(appconfig_section, material, "1");
};
PagePrinters* page_printers = technology & T_FFF ? page_fff : page_msla;
@@ -1687,7 +1716,7 @@ void ConfigWizard::priv::selected_default_materials(Technology technology)
}
update_materials(technology);
- (technology& T_FFF ? page_filaments : page_sla_materials)->reload_presets();
+ ((technology & T_FFF) ? page_filaments : page_sla_materials)->reload_presets();
}
void ConfigWizard::priv::on_3rdparty_install(const VendorProfile *vendor, bool install)
@@ -1728,51 +1757,105 @@ bool ConfigWizard::priv::on_bnt_finish()
// theres no need to check that filament is selected if we have only custom printer
if (custom_printer_selected && !any_fff_selected && !any_sla_selected) return true;
// check, that there is selected at least one filament/material
- return check_materials_in_config(T_ANY);
+ return check_and_install_missing_materials(T_ANY);
}
-bool ConfigWizard::priv::check_materials_in_config(Technology technology, bool show_info_msg)
+// This allmighty method verifies, whether there is at least a single compatible filament or SLA material installed
+// for each Printer preset of each Printer Model installed.
+//
+// In case only_for_model_id is set, then the test is done for that particular printer model only, and the default materials are installed silently.
+// Otherwise the user is quieried whether to install the missing default materials or not.
+//
+// Return true if the tested Printer Models already had materials installed.
+// Return false if there were some Printer Models with missing materials, independent from whether the defaults were installed for these
+// respective Printer Models or not.
+bool ConfigWizard::priv::check_and_install_missing_materials(Technology technology, const std::string &only_for_model_id)
{
- const auto exist_preset = [this](const std::string& section, const Materials& materials)
+ // Walk over all installed Printer presets and verify whether there is a filament or SLA material profile installed at the same PresetBundle,
+ // which is compatible with it.
+ const auto printer_models_missing_materials = [this, only_for_model_id](PrinterTechnology technology, const std::string §ion)
{
- if (appconfig_new.has_section(section) &&
- !appconfig_new.get_section(section).empty())
- {
- const std::map& appconfig_presets = appconfig_new.get_section(section);
- for (const auto& preset : appconfig_presets)
- if (materials.exist_preset(preset.first))
- return true;
+ const std::map &appconfig_presets = appconfig_new.has_section(section) ? appconfig_new.get_section(section) : std::map();
+ std::set printer_models_without_material;
+ for (const auto &pair : bundles) {
+ const PresetCollection &materials = pair.second.preset_bundle->materials(technology);
+ for (const auto &printer : pair.second.preset_bundle->printers) {
+ if (printer.is_visible && printer.printer_technology() == technology) {
+ const VendorProfile::PrinterModel *printer_model = PresetUtils::system_printer_model(printer);
+ assert(printer_model != nullptr);
+ if ((only_for_model_id.empty() || only_for_model_id == printer_model->id) &&
+ printer_models_without_material.find(printer_model) == printer_models_without_material.end()) {
+ bool has_material = false;
+ for (const std::pair &preset : appconfig_presets) {
+ if (preset.second == "1") {
+ const Preset *material = materials.find_preset(preset.first, false);
+ if (material != nullptr && is_compatible_with_printer(PresetWithVendorProfile(*material, nullptr), PresetWithVendorProfile(printer, nullptr))) {
+ has_material = true;
+ break;
+ }
+ }
+ }
+ if (! has_material)
+ printer_models_without_material.insert(printer_model);
+ }
+ }
+ }
}
- return false;
+ assert(printer_models_without_material.empty() || only_for_model_id.empty() || only_for_model_id == (*printer_models_without_material.begin())->id);
+ return printer_models_without_material;
};
- const auto ask_and_selected_default_materials = [this](wxString message, Technology technology)
+ const auto ask_and_select_default_materials = [this](const wxString &message, const std::set &printer_models, Technology technology)
{
wxMessageDialog msg(q, message, _(L("Notice")), wxYES_NO);
if (msg.ShowModal() == wxID_YES)
- selected_default_materials(technology);
+ select_default_materials_for_printer_models(technology, printer_models);
};
- if (any_fff_selected && technology & T_FFF && !exist_preset(AppConfig::SECTION_FILAMENTS, filaments))
- {
- if (show_info_msg)
- {
- wxString message = _(L("You have to select at least one filament for selected printers")) + "\n\n\t" +
- _(L("Do you want to automatic select default filaments?"));
- ask_and_selected_default_materials(message, T_FFF);
+ const auto printer_model_list = [](const std::set &printer_models) -> wxString {
+ wxString out;
+ for (const VendorProfile::PrinterModel *printer_model : printer_models) {
+ out += "\t\t";
+ out += from_u8(printer_model->name);
+ out += "\n";
+ }
+ return out;
+ };
+
+ if (any_fff_selected && (technology & T_FFF)) {
+ std::set printer_models_without_material = printer_models_missing_materials(ptFFF, AppConfig::SECTION_FILAMENTS);
+ if (! printer_models_without_material.empty()) {
+ if (only_for_model_id.empty())
+ ask_and_select_default_materials(
+ _L("The following FFF printer models have no filament selected:") +
+ "\n\n\t" +
+ printer_model_list(printer_models_without_material) +
+ "\n\n\t" +
+ _L("Do you want to select default filaments for these FFF printer models?"),
+ printer_models_without_material,
+ T_FFF);
+ else
+ select_default_materials_for_printer_model(**printer_models_without_material.begin(), T_FFF);
+ return false;
}
- return false;
}
- if (any_sla_selected && technology & T_SLA && !exist_preset(AppConfig::SECTION_MATERIALS, sla_materials))
- {
- if (show_info_msg)
- {
- wxString message = _(L("You have to select at least one material for selected printers")) + "\n\n\t" +
- _(L("Do you want to automatic select default materials?"));
- ask_and_selected_default_materials(message, T_SLA);
- }
- return false;
+ if (any_sla_selected && (technology & T_SLA)) {
+ std::set printer_models_without_material = printer_models_missing_materials(ptSLA, AppConfig::SECTION_MATERIALS);
+ if (! printer_models_without_material.empty()) {
+ if (only_for_model_id.empty())
+ ask_and_select_default_materials(
+ _L("The following SLA printer models have no materials selected:") +
+ "\n\n\t" +
+ printer_model_list(printer_models_without_material) +
+ "\n\n\t" +
+ _L("Do you want to select default SLA materials for these printer models?"),
+ printer_models_without_material,
+ T_SLA);
+ else
+ select_default_materials_for_printer_model(**printer_models_without_material.begin(), T_SLA);
+ return false;
+ }
}
return true;
@@ -1911,6 +1994,7 @@ void ConfigWizard::priv::update_presets_in_config(const std::string& section, co
const PresetAliases& aliases = section == AppConfig::SECTION_FILAMENTS ? aliases_fff : aliases_sla;
auto update = [this, add](const std::string& s, const std::string& key) {
+ assert(! s.empty());
if (add)
appconfig_new.set(s, key, "1");
else
@@ -2046,8 +2130,11 @@ ConfigWizard::ConfigWizard(wxWindow *parent)
{
// check, that there is selected at least one filament/material
ConfigWizardPage* active_page = this->p->index->active_page();
- if ( (active_page == p->page_filaments || active_page == p->page_sla_materials)
- && !p->check_materials_in_config(dynamic_cast(active_page)->materials->technology))
+ if (// Leaving the filaments or SLA materials page and
+ (active_page == p->page_filaments || active_page == p->page_sla_materials) &&
+ // some Printer models had no filament or SLA material selected.
+ ! p->check_and_install_missing_materials(dynamic_cast(active_page)->materials->technology))
+ // In that case don't leave the page and the function above queried the user whether to install default materials.
return;
this->p->index->go_next();
});
diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp
index 1d4b642212..49993bfb1b 100644
--- a/src/slic3r/GUI/ConfigWizard_private.hpp
+++ b/src/slic3r/GUI/ConfigWizard_private.hpp
@@ -82,14 +82,6 @@ struct Materials
}
}
- bool exist_preset(const std::string& preset_name) const
- {
- for (const Preset* preset : presets)
- if (preset->name == preset_name)
- return true;
- return false;
- }
-
static const std::string UNKNOWN;
static const std::string& get_filament_type(const Preset *preset);
static const std::string& get_filament_vendor(const Preset *preset);
@@ -100,13 +92,16 @@ struct Materials
struct Bundle
{
std::unique_ptr preset_bundle;
- VendorProfile *vendor_profile;
- const bool is_in_resources;
- const bool is_prusa_bundle;
+ VendorProfile *vendor_profile { nullptr };
+ bool is_in_resources { false };
+ bool is_prusa_bundle { false };
- Bundle(fs::path source_path, bool is_in_resources, bool is_prusa_bundle = false);
+ Bundle() = default;
Bundle(Bundle &&other);
+ // Returns false if not loaded. Reason for that is logged as boost::log error.
+ bool load(fs::path source_path, bool is_in_resources, bool is_prusa_bundle = false);
+
const std::string& vendor_id() const { return vendor_profile->id; }
};
@@ -500,17 +495,12 @@ struct ConfigWizard::priv
void on_custom_setup(const bool custom_wanted);
void on_printer_pick(PagePrinters *page, const PrinterPickerEvent &evt);
- void select_default_materials_for_printer_model(const std::vector &models,
- Technology technology,
- const std::string & model_id);
- void select_default_materials_if_needed(VendorProfile* vendor_profile,
- Technology technology,
- const std::string &model_id);
- void selected_default_materials(Technology technology);
+ void select_default_materials_for_printer_model(const VendorProfile::PrinterModel &printer_model, Technology technology);
+ void select_default_materials_for_printer_models(Technology technology, const std::set &printer_models);
void on_3rdparty_install(const VendorProfile *vendor, bool install);
bool on_bnt_finish();
- bool check_materials_in_config(Technology technology, bool show_info_msg = true);
+ bool check_and_install_missing_materials(Technology technology, const std::string &only_for_model_id = std::string());
void apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater);
// #ys_FIXME_alise
void update_presets_in_config(const std::string& section, const std::string& alias_key, bool add);
diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp
index 911d0f532b..7318153ef9 100644
--- a/src/slic3r/GUI/DoubleSlider.cpp
+++ b/src/slic3r/GUI/DoubleSlider.cpp
@@ -952,7 +952,7 @@ wxString Control::get_tooltip(int tick/*=-1*/)
return _(L("Discard all custom changes"));
if (m_focus == fiCogIcon)
return m_mode == t_mode::MultiAsSingle ?
- wxString::FromUTF8((boost::format(_utf8(L("Jump to height %s or "
+ GUI::from_u8((boost::format(_utf8(L("Jump to height %s or "
"Set extruder sequence for the entire print"))) % " (Shift + G)\n").str()) :
_(L("Jump to height")) + " (Shift + G)";
if (m_focus == fiColorBand)
@@ -1914,8 +1914,8 @@ bool Control::check_ticks_changed_event(const std::string& gcode)
{
wxString message = m_mode == t_mode::SingleExtruder ? (
_(L("The last color change data was saved for a multi extruder printing.")) + "\n\n" +
- _(L("Select YES if you want to delete all saved tool changes, \n\t"
- "NO if you want all tool changes switch to color changes, \n\t"
+ _(L("Select YES if you want to delete all saved tool changes, \n"
+ "NO if you want all tool changes switch to color changes, \n"
"or CANCEL to leave it unchanged.")) + "\n\n\t" +
_(L("Do you want to delete all saved tool changes?"))
) : ( // t_mode::MultiExtruder
diff --git a/src/slic3r/GUI/Event.hpp b/src/slic3r/GUI/Event.hpp
index 429ef99b02..b9816a747e 100644
--- a/src/slic3r/GUI/Event.hpp
+++ b/src/slic3r/GUI/Event.hpp
@@ -40,11 +40,19 @@ template struct ArrayEvent : public wxEvent
return new ArrayEvent(GetEventType(), data, GetEventObject());
}
};
-template struct ArrayEvent : public wxEvent
+
+template struct Event : public wxEvent
{
T data;
- ArrayEvent(wxEventType type, T data, wxObject* origin = nullptr)
+ Event(wxEventType type, const T &data, wxObject* origin = nullptr)
+ : wxEvent(0, type), data(std::move(data))
+ {
+ m_propagationLevel = wxEVENT_PROPAGATE_MAX;
+ SetEventObject(origin);
+ }
+
+ Event(wxEventType type, T&& data, wxObject* origin = nullptr)
: wxEvent(0, type), data(std::move(data))
{
m_propagationLevel = wxEVENT_PROPAGATE_MAX;
@@ -53,13 +61,10 @@ template struct ArrayEvent : public wxEvent
virtual wxEvent* Clone() const
{
- return new ArrayEvent(GetEventType(), data, GetEventObject());
+ return new Event(GetEventType(), data, GetEventObject());
}
};
-template using Event = ArrayEvent;
-
-
}
}
diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp
index b6f8be4995..6d90079746 100644
--- a/src/slic3r/GUI/Field.cpp
+++ b/src/slic3r/GUI/Field.cpp
@@ -375,12 +375,11 @@ void TextCtrl::BUILD() {
{
e.Skip();
#ifdef __WXOSX__
- // OSX issue: For some unknown reason wxEVT_KILL_FOCUS is emitted twice in a row
+ // OSX issue: For some unknown reason wxEVT_KILL_FOCUS is emitted twice in a row in some cases
+ // (like when information dialog is shown during an update of the option value)
// Thus, suppress its second call
- if (bKilledFocus) {
- bKilledFocus = false;
+ if (bKilledFocus)
return;
- }
bKilledFocus = true;
#endif // __WXOSX__
@@ -391,6 +390,10 @@ void TextCtrl::BUILD() {
bEnterPressed = false;
else
propagate_value();
+#ifdef __WXOSX__
+ // After processing of KILL_FOCUS event we should to invalidate a bKilledFocus flag
+ bKilledFocus = false;
+#endif // __WXOSX__
}), temp->GetId());
// select all text using Ctrl+A
diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp
index ca0c77d5c5..1f8b642b10 100644
--- a/src/slic3r/GUI/Field.hpp
+++ b/src/slic3r/GUI/Field.hpp
@@ -284,7 +284,7 @@ public:
TextCtrl(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
~TextCtrl() {}
- void BUILD();
+ void BUILD() override;
bool value_was_changed();
// Propagate value from field to the OptionGroupe and Config after kill_focus/ENTER
void propagate_value();
@@ -303,9 +303,9 @@ public:
void msw_rescale(bool rescale_sidetext = false) override;
- virtual void enable();
- virtual void disable();
- virtual wxWindow* getWindow() { return window; }
+ void enable() override;
+ void disable() override;
+ wxWindow* getWindow() override { return window; }
};
class CheckBox : public Field {
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index d3b83057e2..9edd308710 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -63,9 +63,11 @@
#include
#include
#include "DoubleSlider.hpp"
+#if !ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
#if ENABLE_RENDER_STATISTICS
#include
#endif // ENABLE_RENDER_STATISTICS
+#endif // !ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
#include
@@ -665,7 +667,7 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool
if (it != m_warnings.end()) // this warning is already set to be shown
return;
- m_warnings.push_back(warning);
+ m_warnings.emplace_back(warning);
std::sort(m_warnings.begin(), m_warnings.end());
}
else {
@@ -1291,7 +1293,7 @@ void GLCanvas3D::Labels::render(const std::vector& sorted_
if (model_object->instances.size() > 1)
owner.label += " (" + std::to_string(inst_idx + 1) + ")";
owner.selected = volume->selected;
- owners.push_back(owner);
+ owners.emplace_back(owner);
}
}
}
@@ -1372,6 +1374,88 @@ void GLCanvas3D::Labels::render(const std::vector& sorted_
}
}
+#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI
+#if ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
+void GLCanvas3D::Tooltip::set_text(const std::string& text)
+{
+ // If the mouse is inside an ImGUI dialog, then the tooltip is suppressed.
+ const std::string &new_text = m_in_imgui ? std::string() : text;
+ if (m_text != new_text)
+ {
+ if (m_text.empty())
+ m_start_time = std::chrono::steady_clock::now();
+
+ m_text = new_text;
+ }
+}
+#endif // ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
+
+#if ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
+void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas) const
+#else
+void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position) const
+#endif // ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
+{
+#if ENABLE_CANVAS_CONSTRAINED_TOOLTIP_USING_IMGUI
+ static ImVec2 size(0.0f, 0.0f);
+
+ auto validate_position = [](const Vec2d& position, const GLCanvas3D& canvas, const ImVec2& wnd_size) {
+ Size cnv_size = canvas.get_canvas_size();
+ float x = std::clamp((float)position(0), 0.0f, (float)cnv_size.get_width() - wnd_size.x);
+ float y = std::clamp((float)position(1) + 16, 0.0f, (float)cnv_size.get_height() - wnd_size.y);
+ return Vec2f(x, y);
+ };
+#endif // ENABLE_CANVAS_CONSTRAINED_TOOLTIP_USING_IMGUI
+
+#if ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
+ if (m_text.empty())
+ return;
+
+ // draw the tooltip as hidden until the delay is expired
+ float alpha = (std::chrono::duration_cast(std::chrono::steady_clock::now() - m_start_time).count() < 500) ? 0.0f : 1.0;
+#else
+ if (m_text.empty())
+ return;
+#endif // ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
+
+#if ENABLE_CANVAS_CONSTRAINED_TOOLTIP_USING_IMGUI
+ Vec2f position = validate_position(mouse_position, canvas, size);
+#endif // ENABLE_CANVAS_CONSTRAINED_TOOLTIP_USING_IMGUI
+
+ ImGuiWrapper& imgui = *wxGetApp().imgui();
+ ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
+#if ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
+ ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha);
+#endif // ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
+#if ENABLE_CANVAS_CONSTRAINED_TOOLTIP_USING_IMGUI
+ imgui.set_next_window_pos(position(0), position(1), ImGuiCond_Always, 0.0f, 0.0f);
+#else
+ imgui.set_next_window_pos(mouse_position(0), mouse_position(1) + 16, ImGuiCond_Always, 0.0f, 0.0f);
+#endif // ENABLE_CANVAS_CONSTRAINED_TOOLTIP_USING_IMGUI
+
+ imgui.begin(_(L("canvas_tooltip")), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoFocusOnAppearing);
+ ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow());
+ ImGui::TextUnformatted(m_text.c_str());
+
+#if ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
+ // force re-render while the windows gets to its final size (it may take several frames) or while hidden
+ if (alpha == 0.0f || ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowExpectedSize(ImGui::GetCurrentWindow()).x)
+ canvas.request_extra_frame();
+#endif // ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
+
+#if ENABLE_CANVAS_CONSTRAINED_TOOLTIP_USING_IMGUI
+ size = ImGui::GetWindowSize();
+#endif // ENABLE_CANVAS_CONSTRAINED_TOOLTIP_USING_IMGUI
+
+ imgui.end();
+#if ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
+ ImGui::PopStyleVar(2);
+#else
+ ImGui::PopStyleVar();
+#endif // ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
+}
+#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI
+
wxDEFINE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, RBtnEvent);
@@ -1943,6 +2027,38 @@ void GLCanvas3D::render()
m_camera.debug_render();
#endif // ENABLE_CAMERA_STATISTICS
+#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI
+ std::string tooltip;
+
+ // Negative coordinate means out of the window, likely because the window was deactivated.
+ // In that case the tooltip should be hidden.
+ if (m_mouse.position.x() >= 0. && m_mouse.position.y() >= 0.)
+ {
+ if (tooltip.empty())
+ tooltip = m_layers_editing.get_tooltip(*this);
+
+ if (tooltip.empty())
+ tooltip = m_gizmos.get_tooltip();
+
+ if (tooltip.empty())
+ tooltip = m_main_toolbar.get_tooltip();
+
+ if (tooltip.empty())
+ tooltip = m_undoredo_toolbar.get_tooltip();
+
+ if (tooltip.empty())
+ tooltip = m_view_toolbar.get_tooltip();
+ }
+
+ set_tooltip(tooltip);
+
+#if ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
+ m_tooltip.render(m_mouse.position, *this);
+#else
+ m_tooltip.render(m_mouse.position);
+#endif // ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
+#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI
+
wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog(*this);
wxGetApp().imgui()->render();
@@ -1953,6 +2069,27 @@ void GLCanvas3D::render()
auto end_time = std::chrono::high_resolution_clock::now();
m_render_stats.last_frame = std::chrono::duration_cast(end_time - start_time).count();
#endif // ENABLE_RENDER_STATISTICS
+
+#if !ENABLE_CANVAS_TOOLTIP_USING_IMGUI
+ std::string tooltip = "";
+
+ if (tooltip.empty())
+ tooltip = m_layers_editing.get_tooltip(*this);
+
+ if (tooltip.empty())
+ tooltip = m_gizmos.get_tooltip();
+
+ if (tooltip.empty())
+ tooltip = m_main_toolbar.get_tooltip();
+
+ if (tooltip.empty())
+ tooltip = m_undoredo_toolbar.get_tooltip();
+
+ if (tooltip.empty())
+ tooltip = m_view_toolbar.get_tooltip();
+
+ set_tooltip(tooltip);
+#endif // !ENABLE_CANVAS_TOOLTIP_USING_IMGUI
}
#if ENABLE_THUMBNAIL_GENERATOR
@@ -2031,7 +2168,7 @@ std::vector GLCanvas3D::load_object(const ModelObject& model_object, int ob
{
for (unsigned int i = 0; i < model_object.instances.size(); ++i)
{
- instance_idxs.push_back(i);
+ instance_idxs.emplace_back(i);
}
}
return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_color_by, m_initialized);
@@ -2471,9 +2608,9 @@ static void load_gcode_retractions(const GCodePreviewData::Retraction& retractio
for (const GCodePreviewData::Retraction::Position& position : copy)
{
- volume->print_zs.push_back(unscale(position.position(2)));
- volume->offsets.push_back(volume->indexed_vertex_array.quad_indices.size());
- volume->offsets.push_back(volume->indexed_vertex_array.triangle_indices.size());
+ volume->print_zs.emplace_back(unscale(position.position(2)));
+ volume->offsets.emplace_back(volume->indexed_vertex_array.quad_indices.size());
+ volume->offsets.emplace_back(volume->indexed_vertex_array.triangle_indices.size());
_3DScene::point3_to_verts(position.position, position.width, position.height, *volume);
@@ -3084,10 +3221,11 @@ void GLCanvas3D::on_key(wxKeyEvent& evt)
void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
{
- // try to filter out events coming from mouse 3d
- Mouse3DController& controller = wxGetApp().plater()->get_mouse3d_controller();
- if (controller.process_mouse_wheel())
+#ifdef WIN32
+ // Try to filter out spurious mouse wheel events comming from 3D mouse.
+ if (wxGetApp().plater()->get_mouse3d_controller().process_mouse_wheel())
return;
+#endif
if (!m_initialized)
return;
@@ -3192,22 +3330,33 @@ std::string format_mouse_event_debug_message(const wxMouseEvent &evt)
void GLCanvas3D::on_mouse(wxMouseEvent& evt)
{
+ if (!m_initialized || !_set_current())
+ return;
+
#if ENABLE_RETINA_GL
const float scale = m_retina_helper->get_scale_factor();
evt.SetX(evt.GetX() * scale);
evt.SetY(evt.GetY() * scale);
#endif
- Point pos(evt.GetX(), evt.GetY());
+ Point pos(evt.GetX(), evt.GetY());
- ImGuiWrapper *imgui = wxGetApp().imgui();
+ ImGuiWrapper* imgui = wxGetApp().imgui();
+ m_tooltip.set_in_imgui(false);
if (imgui->update_mouse_data(evt)) {
m_mouse.position = evt.Leaving() ? Vec2d(-1.0, -1.0) : pos.cast();
+ m_tooltip.set_in_imgui(true);
render();
#ifdef SLIC3R_DEBUG_MOUSE_EVENTS
- printf((format_mouse_event_debug_message(evt) + " - Consumed by ImGUI\n").c_str());
+ printf((format_mouse_event_debug_message(evt) + " - Consumed by ImGUI\n").c_str());
#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */
- return;
+ // do not return if dragging or tooltip not empty to allow for tooltip update
+#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI
+ if (!m_mouse.dragging && m_tooltip.is_empty())
+#else
+ if (!m_mouse.dragging && m_canvas->GetToolTipText().empty())
+#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI
+ return;
}
#ifdef __WXMSW__
@@ -3258,12 +3407,12 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
mouse_up_cleanup();
m_mouse.set_start_position_3D_as_invalid();
+#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI
+ m_mouse.position = pos.cast();
+#endif /// ENABLE_CANVAS_TOOLTIP_USING_IMGUI
return;
}
- if (m_picking_enabled)
- _set_current();
-
int selected_object_idx = m_selection.get_object_idx();
int layer_editing_object_idx = is_layers_editing_enabled() ? selected_object_idx : -1;
m_layers_editing.select_object(*m_model, layer_editing_object_idx);
@@ -3467,11 +3616,26 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
if (m_hover_volume_idxs.empty() && m_mouse.is_start_position_3D_defined())
{
const Vec3d rot = (Vec3d(pos.x(), pos.y(), 0.) - m_mouse.drag.start_position_3D) * (PI * TRACKBALLSIZE / 180.);
- if (wxGetApp().plater()->get_mouse3d_controller().is_running() || (wxGetApp().app_config->get("use_free_camera") == "1"))
+#if ENABLE_AUTO_CONSTRAINED_CAMERA
+ if (wxGetApp().app_config->get("use_free_camera") == "1")
+ // Virtual track ball (similar to the 3DConnexion mouse).
+ m_camera.rotate_local_around_target(Vec3d(rot.y(), rot.x(), 0.));
+ else
+ {
+ // Forces camera right vector to be parallel to XY plane in case it has been misaligned using the 3D mouse free rotation.
+ // It is cheaper to call this function right away instead of testing wxGetApp().plater()->get_mouse3d_controller().connected(),
+ // which checks an atomics (flushes CPU caches).
+ // See GH issue #3816.
+ m_camera.recover_from_free_camera();
+ m_camera.rotate_on_sphere(rot.x(), rot.y(), wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA);
+ }
+#else
+ if (wxGetApp().plater()->get_mouse3d_controller().connected() || (wxGetApp().app_config->get("use_free_camera") == "1"))
// Virtual track ball (similar to the 3DConnexion mouse).
m_camera.rotate_local_around_target(Vec3d(rot.y(), rot.x(), 0.));
else
m_camera.rotate_on_sphere(rot.x(), rot.y(), wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA);
+#endif // ENABLE_AUTO_CONSTRAINED_CAMERA
m_dirty = true;
}
@@ -3486,6 +3650,15 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
float z = 0.0f;
const Vec3d& cur_pos = _mouse_to_3d(pos, &z);
Vec3d orig = _mouse_to_3d(m_mouse.drag.start_position_2D, &z);
+#if ENABLE_AUTO_CONSTRAINED_CAMERA
+ if (wxGetApp().app_config->get("use_free_camera") != "1")
+ // Forces camera right vector to be parallel to XY plane in case it has been misaligned using the 3D mouse free rotation.
+ // It is cheaper to call this function right away instead of testing wxGetApp().plater()->get_mouse3d_controller().connected(),
+ // which checks an atomics (flushes CPU caches).
+ // See GH issue #3816.
+ m_camera.recover_from_free_camera();
+#endif // ENABLE_AUTO_CONSTRAINED_CAMERA
+
m_camera.set_target(m_camera.get_target() + orig - cur_pos);
m_dirty = true;
}
@@ -3564,24 +3737,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
else if (evt.Moving())
{
m_mouse.position = pos.cast();
- std::string tooltip = "";
-
- if (tooltip.empty())
- tooltip = m_layers_editing.get_tooltip(*this);
-
- if (tooltip.empty())
- tooltip = m_gizmos.get_tooltip();
-
- if (tooltip.empty())
- tooltip = m_main_toolbar.get_tooltip();
-
- if (tooltip.empty())
- tooltip = m_undoredo_toolbar.get_tooltip();
-
- if (tooltip.empty())
- tooltip = m_view_toolbar.get_tooltip();
-
- set_tooltip(tooltip);
// updates gizmos overlay
if (m_selection.is_empty())
@@ -3656,20 +3811,27 @@ void GLCanvas3D::set_tooltip(const std::string& tooltip) const
{
if (m_canvas != nullptr)
{
- wxToolTip* t = m_canvas->GetToolTip();
- if (t != nullptr)
- {
- if (tooltip.empty())
- m_canvas->UnsetToolTip();
- else
- t->SetTip(wxString::FromUTF8(tooltip.data()));
- }
- else if (!tooltip.empty()) // Avoid "empty" tooltips => unset of the empty tooltip leads to application crash under OSX
- m_canvas->SetToolTip(wxString::FromUTF8(tooltip.data()));
+#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI
+ m_tooltip.set_text(tooltip);
+#else
+ wxString txt = wxString::FromUTF8(tooltip.data());
+ if (m_canvas->GetToolTipText() != txt)
+ m_canvas->SetToolTip(txt);
+
+// wxToolTip* t = m_canvas->GetToolTip();
+// if (t != nullptr)
+// {
+// if (tooltip.empty())
+// m_canvas->UnsetToolTip();
+// else
+// t->SetTip(wxString::FromUTF8(tooltip.data()));
+// }
+// else if (!tooltip.empty()) // Avoid "empty" tooltips => unset of the empty tooltip leads to application crash under OSX
+// m_canvas->SetToolTip(wxString::FromUTF8(tooltip.data()));
+#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI
}
}
-
void GLCanvas3D::do_move(const std::string& snapshot_type)
{
if (m_model == nullptr)
@@ -3933,7 +4095,6 @@ void GLCanvas3D::handle_layers_data_focus_event(const t_layer_height_range range
void GLCanvas3D::update_ui_from_settings()
{
- m_camera.set_type(wxGetApp().app_config->get("use_perspective_camera"));
m_dirty = true;
#if ENABLE_RETINA_GL
@@ -4118,7 +4279,7 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool
if (!vol->is_modifier && !vol->is_wipe_tower && (!parts_only || (vol->composite_id.volume_id >= 0)))
{
if (!printable_only || is_visible(*vol))
- visible_volumes.push_back(vol);
+ visible_volumes.emplace_back(vol);
}
}
@@ -4822,7 +4983,7 @@ void GLCanvas3D::_picking_pass() const
}
if ((0 <= volume_id) && (volume_id < (int)m_volumes.volumes.size()))
{
- m_hover_volume_idxs.push_back(volume_id);
+ m_hover_volume_idxs.emplace_back(volume_id);
m_gizmos.set_hover_id(-1);
}
else
@@ -5040,6 +5201,19 @@ void GLCanvas3D::_render_overlays() const
_render_gizmos_overlay();
_render_warning_texture();
_render_legend_texture();
+
+ // main toolbar and undoredo toolbar need to be both updated before rendering because both their sizes are needed
+ // to correctly place them
+#if ENABLE_RETINA_GL
+ const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(true);
+ m_main_toolbar.set_scale(scale);
+ m_undoredo_toolbar.set_scale(scale);
+#else
+ const float size = int(GLToolbar::Default_Icons_Size * wxGetApp().toolbar_icon_scale(true));
+ m_main_toolbar.set_icons_size(size);
+ m_undoredo_toolbar.set_icons_size(size);
+#endif // ENABLE_RETINA_GL
+
_render_main_toolbar();
_render_undoredo_toolbar();
_render_view_toolbar();
@@ -5053,7 +5227,7 @@ void GLCanvas3D::_render_overlays() const
if (sequential_print) {
for (ModelObject* model_object : m_model->objects)
for (ModelInstance* model_instance : model_object->instances) {
- sorted_instances.push_back(model_instance);
+ sorted_instances.emplace_back(model_instance);
}
}
m_labels.render(sorted_instances);
@@ -5132,17 +5306,6 @@ void GLCanvas3D::_render_main_toolbar() const
if (!m_main_toolbar.is_enabled())
return;
-#if ENABLE_RETINA_GL
-// m_main_toolbar.set_scale(m_retina_helper->get_scale_factor());
- const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(true);
- m_main_toolbar.set_scale(scale); //! #ys_FIXME_experiment
-#else
-// m_main_toolbar.set_scale(m_canvas->GetContentScaleFactor());
-// m_main_toolbar.set_scale(wxGetApp().em_unit()*0.1f);
- const float size = int(GLToolbar::Default_Icons_Size * wxGetApp().toolbar_icon_scale(true));
- m_main_toolbar.set_icons_size(size); //! #ys_FIXME_experiment
-#endif // ENABLE_RETINA_GL
-
Size cnv_size = get_canvas_size();
float inv_zoom = (float)m_camera.get_inv_zoom();
@@ -5158,17 +5321,6 @@ void GLCanvas3D::_render_undoredo_toolbar() const
if (!m_undoredo_toolbar.is_enabled())
return;
-#if ENABLE_RETINA_GL
-// m_undoredo_toolbar.set_scale(m_retina_helper->get_scale_factor());
- const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(true);
- m_undoredo_toolbar.set_scale(scale); //! #ys_FIXME_experiment
-#else
-// m_undoredo_toolbar.set_scale(m_canvas->GetContentScaleFactor());
-// m_undoredo_toolbar.set_scale(wxGetApp().em_unit()*0.1f);
- const float size = int(GLToolbar::Default_Icons_Size * wxGetApp().toolbar_icon_scale(true));
- m_undoredo_toolbar.set_icons_size(size); //! #ys_FIXME_experiment
-#endif // ENABLE_RETINA_GL
-
Size cnv_size = get_canvas_size();
float inv_zoom = (float)m_camera.get_inv_zoom();
@@ -5533,29 +5685,26 @@ void GLCanvas3D::_load_print_toolpaths()
if ((skirt_height == 0) && (print->config().brim_width.value > 0))
skirt_height = 1;
- // get first skirt_height layers (maybe this should be moved to a PrintObject method?)
- const PrintObject* object0 = print->objects().front();
+ // Get first skirt_height layers.
+ //FIXME This code is fishy. It may not work for multiple objects with different layering due to variable layer height feature.
+ // This is not critical as this is just an initial preview.
+ const PrintObject* highest_object = *std::max_element(print->objects().begin(), print->objects().end(), [](auto l, auto r){ return l->layers().size() < r->layers().size(); });
std::vector print_zs;
print_zs.reserve(skirt_height * 2);
- for (size_t i = 0; i < std::min(skirt_height, object0->layers().size()); ++i)
- {
- print_zs.push_back(float(object0->layers()[i]->print_z));
- }
- //FIXME why there are support layers?
- for (size_t i = 0; i < std::min(skirt_height, object0->support_layers().size()); ++i)
- {
- print_zs.push_back(float(object0->support_layers()[i]->print_z));
- }
+ for (size_t i = 0; i < std::min(skirt_height, highest_object->layers().size()); ++ i)
+ print_zs.emplace_back(float(highest_object->layers()[i]->print_z));
+ // Only add skirt for the raft layers.
+ for (size_t i = 0; i < std::min(skirt_height, std::min(highest_object->slicing_parameters().raft_layers(), highest_object->support_layers().size())); ++ i)
+ print_zs.emplace_back(float(highest_object->support_layers()[i]->print_z));
sort_remove_duplicates(print_zs);
- if (print_zs.size() > skirt_height)
- print_zs.erase(print_zs.begin() + skirt_height, print_zs.end());
-
+ skirt_height = std::min(skirt_height, print_zs.size());
+ print_zs.erase(print_zs.begin() + skirt_height, print_zs.end());
GLVolume *volume = m_volumes.new_toolpath_volume(color, VERTEX_BUFFER_RESERVE_SIZE);
- for (size_t i = 0; i < skirt_height; ++i) {
- volume->print_zs.push_back(print_zs[i]);
- volume->offsets.push_back(volume->indexed_vertex_array.quad_indices.size());
- volume->offsets.push_back(volume->indexed_vertex_array.triangle_indices.size());
+ for (size_t i = 0; i < skirt_height; ++ i) {
+ volume->print_zs.emplace_back(print_zs[i]);
+ volume->offsets.emplace_back(volume->indexed_vertex_array.quad_indices.size());
+ volume->offsets.emplace_back(volume->indexed_vertex_array.triangle_indices.size());
if (i == 0)
_3DScene::extrusionentity_to_verts(print->brim(), print_zs[i], Point(0, 0), *volume);
_3DScene::extrusionentity_to_verts(print->skirt(), print_zs[i], Point(0, 0), *volume);
@@ -5721,10 +5870,10 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
}
if (ctxt.has_perimeters || ctxt.has_infill)
for (const Layer *layer : print_object.layers())
- ctxt.layers.push_back(layer);
+ ctxt.layers.emplace_back(layer);
if (ctxt.has_support)
for (const Layer *layer : print_object.support_layers())
- ctxt.layers.push_back(layer);
+ ctxt.layers.emplace_back(layer);
std::sort(ctxt.layers.begin(), ctxt.layers.end(), [](const Layer *l1, const Layer *l2) { return l1->print_z < l2->print_z; });
// Maximum size of an allocation block: 32MB / sizeof(float)
@@ -5793,9 +5942,9 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
for (GLVolume *vol : vols)
if (vol->print_zs.empty() || vol->print_zs.back() != layer->print_z) {
- vol->print_zs.push_back(layer->print_z);
- vol->offsets.push_back(vol->indexed_vertex_array.quad_indices.size());
- vol->offsets.push_back(vol->indexed_vertex_array.triangle_indices.size());
+ vol->print_zs.emplace_back(layer->print_z);
+ vol->offsets.emplace_back(vol->indexed_vertex_array.quad_indices.size());
+ vol->offsets.emplace_back(vol->indexed_vertex_array.triangle_indices.size());
}
for (const PrintInstance &instance : *ctxt.shifted_copies) {
const Point © = instance.shift;
@@ -5951,9 +6100,9 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_
for (size_t i = 0; i < vols.size(); ++i) {
GLVolume &vol = *vols[i];
if (vol.print_zs.empty() || vol.print_zs.back() != layer.front().print_z) {
- vol.print_zs.push_back(layer.front().print_z);
- vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size());
- vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size());
+ vol.print_zs.emplace_back(layer.front().print_z);
+ vol.offsets.emplace_back(vol.indexed_vertex_array.quad_indices.size());
+ vol.offsets.emplace_back(vol.indexed_vertex_array.triangle_indices.size());
}
}
for (const WipeTower::ToolChangeResult &extrusions : layer) {
@@ -6166,9 +6315,9 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat
assert(it_filter != filters.end() && key.first == it_filter->first);
GLVolume& vol = *it_filter->second;
- vol.print_zs.push_back(layer.z);
- vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size());
- vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size());
+ vol.print_zs.emplace_back(layer.z);
+ vol.offsets.emplace_back(vol.indexed_vertex_array.quad_indices.size());
+ vol.offsets.emplace_back(vol.indexed_vertex_array.triangle_indices.size());
_3DScene::extrusionentity_to_verts(path.polyline, path.width, path.height, layer.z, vol);
}
@@ -6240,9 +6389,9 @@ inline void travel_paths_internal(
assert(it != by_type.end() && it->first == func_value(polyline));
GLVolume& vol = *it->second;
- vol.print_zs.push_back(unscale(polyline.polyline.bounding_box().min(2)));
- vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size());
- vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size());
+ vol.print_zs.emplace_back(unscale(polyline.polyline.bounding_box().min(2)));
+ vol.offsets.emplace_back(vol.indexed_vertex_array.quad_indices.size());
+ vol.offsets.emplace_back(vol.indexed_vertex_array.triangle_indices.size());
_3DScene::polyline3_to_verts(polyline.polyline, preview_data.travel.width, preview_data.travel.height, vol);
diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp
index 2e84782aaf..5bd060cddf 100644
--- a/src/slic3r/GUI/GLCanvas3D.hpp
+++ b/src/slic3r/GUI/GLCanvas3D.hpp
@@ -3,6 +3,9 @@
#include
#include
+#if ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
+#include
+#endif // ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
#include "3DScene.hpp"
#include "GLToolbar.hpp"
@@ -386,6 +389,30 @@ private:
void render(const std::vector& sorted_instances) const;
};
+#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI
+ class Tooltip
+ {
+ std::string m_text;
+#if ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
+ std::chrono::steady_clock::time_point m_start_time;
+#endif // ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
+ // Indicator that the mouse is inside an ImGUI dialog, therefore the tooltip should be suppressed.
+ bool m_in_imgui = false;
+
+ public:
+ bool is_empty() const { return m_text.empty(); }
+#if ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
+ void set_text(const std::string& text);
+ void render(const Vec2d& mouse_position, GLCanvas3D& canvas) const;
+#else
+ void set_text(const std::string& text) { m_text = text; }
+ void render(const Vec2d& mouse_position) const;
+#endif // ENABLE_CANVAS_DELAYED_TOOLTIP_USING_IMGUI
+ // Indicates that the mouse is inside an ImGUI dialog, therefore the tooltip should be suppressed.
+ void set_in_imgui(bool b) { m_in_imgui = b; }
+ };
+#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI
+
public:
enum ECursorType : unsigned char
{
@@ -464,6 +491,9 @@ private:
int m_selected_extruder;
Labels m_labels;
+#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI
+ mutable Tooltip m_tooltip;
+#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI
public:
GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar);
diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp
index 079e69b21b..7809186013 100644
--- a/src/slic3r/GUI/GLToolbar.cpp
+++ b/src/slic3r/GUI/GLToolbar.cpp
@@ -420,14 +420,60 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent)
// mouse anywhere
if (!evt.Dragging() && !evt.Leaving() && !evt.Entering() && (m_mouse_capture.parent != nullptr))
{
- if (m_mouse_capture.any() && (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()))
+ if (m_mouse_capture.any() && (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())) {
// prevents loosing selection into the scene if mouse down was done inside the toolbar and mouse up was down outside it,
// as when switching between views
- processed = true;
-
+ m_mouse_capture.reset();
+ if (contains_mouse(mouse_pos, parent) == -1)
+ // mouse is outside the toolbar
+ m_tooltip.clear();
+ return true;
+ }
m_mouse_capture.reset();
}
+#if ENABLE_MODIFIED_TOOLBAR_MOUSE_EVENT_HANDLING
+ if (evt.Moving())
+ m_tooltip = update_hover_state(mouse_pos, parent);
+ else if (evt.LeftUp())
+ {
+ if (m_mouse_capture.left)
+ {
+ processed = true;
+ m_mouse_capture.left = false;
+ }
+ else
+ return false;
+ }
+ else if (evt.MiddleUp())
+ {
+ if (m_mouse_capture.middle)
+ {
+ processed = true;
+ m_mouse_capture.middle = false;
+ }
+ else
+ return false;
+ }
+ else if (evt.RightUp())
+ {
+ if (m_mouse_capture.right)
+ {
+ processed = true;
+ m_mouse_capture.right = false;
+ }
+ else
+ return false;
+ }
+ else if (evt.Dragging())
+ {
+ if (m_mouse_capture.any())
+ // if the button down was done on this toolbar, prevent from dragging into the scene
+ processed = true;
+ else
+ return false;
+ }
+#else
if (evt.Moving())
m_tooltip = update_hover_state(mouse_pos, parent);
else if (evt.LeftUp())
@@ -439,6 +485,7 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent)
else if (evt.Dragging() && m_mouse_capture.any())
// if the button down was done on this toolbar, prevent from dragging into the scene
processed = true;
+#endif // ENABLE_MODIFIED_TOOLBAR_MOUSE_EVENT_HANDLING
int item_id = contains_mouse(mouse_pos, parent);
if (item_id == -1)
@@ -478,8 +525,10 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent)
parent.set_as_dirty();
}
}
+#if !ENABLE_MODIFIED_TOOLBAR_MOUSE_EVENT_HANDLING
else if (evt.LeftUp())
processed = true;
+#endif // !ENABLE_MODIFIED_TOOLBAR_MOUSE_EVENT_HANDLING
}
return processed;
diff --git a/src/slic3r/GUI/GUI.hpp b/src/slic3r/GUI/GUI.hpp
index 2bfddf718c..a54288df45 100644
--- a/src/slic3r/GUI/GUI.hpp
+++ b/src/slic3r/GUI/GUI.hpp
@@ -1,7 +1,8 @@
#ifndef slic3r_GUI_hpp_
#define slic3r_GUI_hpp_
-#include
+namespace boost { class any; }
+namespace boost::filesystem { class path; }
#include
diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp
index 7ca8753a18..cc6c066904 100644
--- a/src/slic3r/GUI/GUI_App.cpp
+++ b/src/slic3r/GUI/GUI_App.cpp
@@ -10,6 +10,7 @@
#include
#include
#include
+#include
#include
#include
@@ -50,6 +51,7 @@
#ifdef __WXMSW__
#include
+#include
#endif // __WXMSW__
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG
@@ -60,6 +62,7 @@
namespace Slic3r {
namespace GUI {
+class MainFrame;
wxString file_wildcards(FileType file_type, const std::string &custom_extension)
{
@@ -96,9 +99,9 @@ wxString file_wildcards(FileType file_type, const std::string &custom_extension)
static std::string libslic3r_translate_callback(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str().data(); }
-static void register_dpi_event()
-{
#ifdef WIN32
+static void register_win32_dpi_event()
+{
enum { WM_DPICHANGED_ = 0x02e0 };
wxWindow::MSWRegisterMessageHandler(WM_DPICHANGED_, [](wxWindow *win, WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) {
@@ -111,9 +114,52 @@ static void register_dpi_event()
return true;
});
-#endif
}
+static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 };
+
+static void register_win32_device_notification_event()
+{
+ enum { WM_DPICHANGED_ = 0x02e0 };
+
+ wxWindow::MSWRegisterMessageHandler(WM_DEVICECHANGE, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) {
+ // Some messages are sent to top level windows by default, some messages are sent to only registered windows, and we explictely register on MainFrame only.
+ auto main_frame = dynamic_cast(win);
+ auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater();
+ if (plater == nullptr)
+ // Maybe some other top level window like a dialog or maybe a pop-up menu?
+ return true;
+ PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam;
+ switch (wParam) {
+ case DBT_DEVICEARRIVAL:
+ if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME)
+ plater->GetEventHandler()->AddPendingEvent(VolumeAttachedEvent(EVT_VOLUME_ATTACHED));
+ else if (lpdb->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {
+ PDEV_BROADCAST_DEVICEINTERFACE lpdbi = (PDEV_BROADCAST_DEVICEINTERFACE)lpdb;
+// if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_VOLUME) {
+// printf("DBT_DEVICEARRIVAL %d - Media has arrived: %ws\n", msg_count, lpdbi->dbcc_name);
+ if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_HID)
+ plater->GetEventHandler()->AddPendingEvent(HIDDeviceAttachedEvent(EVT_HID_DEVICE_ATTACHED, boost::nowide::narrow(lpdbi->dbcc_name)));
+ }
+ break;
+ case DBT_DEVICEREMOVECOMPLETE:
+ if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME)
+ plater->GetEventHandler()->AddPendingEvent(VolumeDetachedEvent(EVT_VOLUME_DETACHED));
+ else if (lpdb->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {
+ PDEV_BROADCAST_DEVICEINTERFACE lpdbi = (PDEV_BROADCAST_DEVICEINTERFACE)lpdb;
+// if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_VOLUME)
+// printf("DBT_DEVICEARRIVAL %d - Media was removed: %ws\n", msg_count, lpdbi->dbcc_name);
+ if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_HID)
+ plater->GetEventHandler()->AddPendingEvent(HIDDeviceDetachedEvent(EVT_HID_DEVICE_DETACHED, boost::nowide::narrow(lpdbi->dbcc_name)));
+ }
+ break;
+ default:
+ break;
+ }
+ return true;
+ });
+}
+#endif // WIN32
static void generic_exception_handle()
{
@@ -155,6 +201,7 @@ GUI_App::GUI_App()
, m_em_unit(10)
, m_imgui(new ImGuiWrapper())
, m_wizard(nullptr)
+ , m_removable_drive_manager(std::make_unique())
{}
GUI_App::~GUI_App()
@@ -247,7 +294,10 @@ bool GUI_App::on_init_inner()
show_error(nullptr, ex.what());
}
- register_dpi_event();
+#ifdef WIN32
+ register_win32_dpi_event();
+ register_win32_device_notification_event();
+#endif // WIN32
// Let the libslic3r know the callback, which will translate messages on demand.
Slic3r::I18N::set_translate_callback(libslic3r_translate_callback);
@@ -262,7 +312,6 @@ bool GUI_App::on_init_inner()
m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg()));
- RemovableDriveManager::get_instance().init();
Bind(wxEVT_IDLE, [this](wxIdleEvent& event)
{
@@ -274,10 +323,6 @@ bool GUI_App::on_init_inner()
this->obj_manipul()->update_if_dirty();
-#if !__APPLE__
- RemovableDriveManager::get_instance().update(wxGetLocalTime(), true);
-#endif
-
// Preset updating & Configwizard are done after the above initializations,
// and after MainFrame is created & shown.
// The extra CallAfter() is needed because of Mac, where this is the only way
@@ -437,46 +482,30 @@ float GUI_App::toolbar_icon_scale(const bool is_limited/* = false*/) const
void GUI_App::recreate_GUI()
{
- // Weird things happen as the Paint messages are floating around the windows being destructed.
- // Avoid the Paint messages by hiding the main window.
- // Also the application closes much faster without these unnecessary screen refreshes.
- // In addition, there were some crashes due to the Paint events sent to already destructed windows.
- mainframe->Show(false);
+ mainframe->shutdown();
const auto msg_name = _(L("Changing of an application language")) + dots;
wxProgressDialog dlg(msg_name, msg_name);
dlg.Pulse();
-
- // to make sure nobody accesses data from the soon-to-be-destroyed widgets:
- tabs_list.clear();
- plater_ = nullptr;
-
dlg.Update(10, _(L("Recreating")) + dots);
- MainFrame* topwindow = mainframe;
+ MainFrame *old_main_frame = mainframe;
mainframe = new MainFrame();
- sidebar().obj_list()->init_objects(); // propagate model objects to object list
+ // Propagate model objects to object list.
+ sidebar().obj_list()->init_objects();
+ SetTopWindow(mainframe);
- if (topwindow) {
- SetTopWindow(mainframe);
-
- dlg.Update(30, _(L("Recreating")) + dots);
- topwindow->Destroy();
-
- // For this moment ConfigWizard is deleted, invalidate it
- m_wizard = nullptr;
- }
+ dlg.Update(30, _(L("Recreating")) + dots);
+ old_main_frame->Destroy();
+ // For this moment ConfigWizard is deleted, invalidate it.
+ m_wizard = nullptr;
dlg.Update(80, _(L("Loading of current presets")) + dots);
-
m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg()));
-
load_current_presets();
-
mainframe->Show(true);
dlg.Update(90, _(L("Loading of a mode view")) + dots);
-
/* Temporary workaround for the correct behavior of the Scrolled sidebar panel:
* change min hight of object list to the normal min value (15 * wxGetApp().em_unit())
* after first whole Mainframe updating/layouting
@@ -484,7 +513,6 @@ void GUI_App::recreate_GUI()
const int list_min_height = 15 * em_unit();
if (obj_list()->GetMinSize().GetY() > list_min_height)
obj_list()->SetMinSize(wxSize(-1, list_min_height));
-
update_mode();
// #ys_FIXME_delete_after_testing Do we still need this ?
@@ -579,7 +607,6 @@ void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) const
bool GUI_App::switch_language()
{
if (select_language()) {
- _3DScene::remove_all_canvases();
recreate_GUI();
return true;
} else {
@@ -882,14 +909,19 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
/* Before change application language, let's check unsaved changes on 3D-Scene
* and draw user's attention to the application restarting after a language change
*/
- wxMessageDialog dialog(nullptr,
- _(L("Switching the language will trigger application restart.\n"
- "You will lose content of the plater.")) + "\n\n" +
- _(L("Do you want to proceed?")),
- wxString(SLIC3R_APP_NAME) + " - " + _(L("Language selection")),
- wxICON_QUESTION | wxOK | wxCANCEL);
- if ( dialog.ShowModal() == wxID_CANCEL)
- return;
+ {
+ // the dialog needs to be destroyed before the call to switch_language()
+ // or sometimes the application crashes into wxDialogBase() destructor
+ // so we put it into an inner scope
+ wxMessageDialog dialog(nullptr,
+ _(L("Switching the language will trigger application restart.\n"
+ "You will lose content of the plater.")) + "\n\n" +
+ _(L("Do you want to proceed?")),
+ wxString(SLIC3R_APP_NAME) + " - " + _(L("Language selection")),
+ wxICON_QUESTION | wxOK | wxCANCEL);
+ if (dialog.ShowModal() == wxID_CANCEL)
+ return;
+ }
switch_language();
break;
diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp
index 10b09b1da7..d02a60ba91 100644
--- a/src/slic3r/GUI/GUI_App.hpp
+++ b/src/slic3r/GUI/GUI_App.hpp
@@ -29,9 +29,9 @@ class PresetUpdater;
class ModelObject;
class PrintHostJobQueue;
-namespace GUI
-{
+namespace GUI{
+class RemovableDriveManager;
enum FileType
{
FT_STL,
@@ -96,6 +96,8 @@ class GUI_App : public wxApp
// Best translation language, provided by Windows or OSX, owned by wxWidgets.
const wxLanguageInfo *m_language_info_best = nullptr;
+ std::unique_ptr m_removable_drive_manager;
+
std::unique_ptr m_imgui;
std::unique_ptr m_printhost_job_queue;
ConfigWizard* m_wizard; // Managed by wxWindow tree
@@ -180,6 +182,8 @@ public:
std::vector tabs_list;
+ RemovableDriveManager* removable_drive_manager() { return m_removable_drive_manager.get(); }
+
ImGuiWrapper* imgui() { return m_imgui.get(); }
PrintHostJobQueue& printhost_job_queue() { return *m_printhost_job_queue.get(); }
diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp
index d209214ae6..157e45ab49 100644
--- a/src/slic3r/GUI/GUI_ObjectLayers.cpp
+++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp
@@ -58,7 +58,7 @@ void ObjectLayers::select_editor(LayerRangeEditor* editor, const bool is_last_ed
}
}
-wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range)
+wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range, PlusMinusButton *delete_button, PlusMinusButton *add_button)
{
const bool is_last_edited_range = range == m_selectable_range;
@@ -79,8 +79,8 @@ wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range)
// Add control for the "Min Z"
- auto editor = new LayerRangeEditor(this, double_to_string(range.first), etMinZ,
- set_focus_data, [range, update_focus_data, this](coordf_t min_z, bool enter_pressed)
+ auto editor = new LayerRangeEditor(this, double_to_string(range.first), etMinZ, set_focus_data,
+ [range, update_focus_data, this, delete_button, add_button](coordf_t min_z, bool enter_pressed, bool dont_update_ui)
{
if (fabs(min_z - range.first) < EPSILON) {
m_selection_type = etUndef;
@@ -89,10 +89,14 @@ wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range)
// data for next focusing
coordf_t max_z = min_z < range.second ? range.second : min_z + 0.5;
- const t_layer_height_range& new_range = { min_z, max_z };
+ const t_layer_height_range new_range = { min_z, max_z };
+ if (delete_button)
+ delete_button->range = new_range;
+ if (add_button)
+ add_button->range = new_range;
update_focus_data(new_range, etMinZ, enter_pressed);
- return wxGetApp().obj_list()->edit_layer_range(range, new_range);
+ return wxGetApp().obj_list()->edit_layer_range(range, new_range, dont_update_ui);
});
select_editor(editor, is_last_edited_range);
@@ -100,8 +104,8 @@ wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range)
// Add control for the "Max Z"
- editor = new LayerRangeEditor(this, double_to_string(range.second), etMaxZ,
- set_focus_data, [range, update_focus_data, this](coordf_t max_z, bool enter_pressed)
+ editor = new LayerRangeEditor(this, double_to_string(range.second), etMaxZ, set_focus_data,
+ [range, update_focus_data, this, delete_button, add_button](coordf_t max_z, bool enter_pressed, bool dont_update_ui)
{
if (fabs(max_z - range.second) < EPSILON || range.first > max_z) {
m_selection_type = etUndef;
@@ -110,9 +114,13 @@ wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range)
// data for next focusing
const t_layer_height_range& new_range = { range.first, max_z };
+ if (delete_button)
+ delete_button->range = new_range;
+ if (add_button)
+ add_button->range = new_range;
update_focus_data(new_range, etMaxZ, enter_pressed);
- return wxGetApp().obj_list()->edit_layer_range(range, new_range);
+ return wxGetApp().obj_list()->edit_layer_range(range, new_range, dont_update_ui);
});
select_editor(editor, is_last_edited_range);
@@ -120,9 +128,8 @@ wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range)
// Add control for the "Layer height"
- editor = new LayerRangeEditor(this,
- double_to_string(m_object->layer_config_ranges[range].option("layer_height")->getFloat()),
- etLayerHeight, set_focus_data, [range, this](coordf_t layer_height, bool)
+ editor = new LayerRangeEditor(this, double_to_string(m_object->layer_config_ranges[range].option("layer_height")->getFloat()), etLayerHeight, set_focus_data,
+ [range, this](coordf_t layer_height, bool, bool)
{
return wxGetApp().obj_list()->edit_layer_range(range, layer_height);
});
@@ -141,30 +148,30 @@ wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range)
return sizer;
}
-
+
void ObjectLayers::create_layers_list()
{
for (const auto layer : m_object->layer_config_ranges)
{
const t_layer_height_range& range = layer.first;
- auto sizer = create_layer(range);
-
- auto del_btn = new ScalableButton(m_parent, wxID_ANY, m_bmp_delete);
+ auto del_btn = new PlusMinusButton(m_parent, m_bmp_delete, range);
del_btn->SetToolTip(_(L("Remove layer range")));
+ auto add_btn = new PlusMinusButton(m_parent, m_bmp_add, range);
+ wxString tooltip = wxGetApp().obj_list()->can_add_new_range_after_current(range);
+ add_btn->SetToolTip(tooltip.IsEmpty() ? _(L("Add layer range")) : tooltip);
+ add_btn->Enable(tooltip.IsEmpty());
+
+ auto sizer = create_layer(range, del_btn, add_btn);
sizer->Add(del_btn, 0, wxRIGHT | wxLEFT, em_unit(m_parent));
-
- del_btn->Bind(wxEVT_BUTTON, [range](wxEvent &) {
- wxGetApp().obj_list()->del_layer_range(range);
- });
-
- auto add_btn = new ScalableButton(m_parent, wxID_ANY, m_bmp_add);
- add_btn->SetToolTip(_(L("Add layer range")));
-
sizer->Add(add_btn);
- add_btn->Bind(wxEVT_BUTTON, [range](wxEvent &) {
- wxGetApp().obj_list()->add_layer_range_after_current(range);
+ del_btn->Bind(wxEVT_BUTTON, [del_btn](wxEvent &) {
+ wxGetApp().obj_list()->del_layer_range(del_btn->range);
+ });
+
+ add_btn->Bind(wxEVT_BUTTON, [add_btn](wxEvent &) {
+ wxGetApp().obj_list()->add_layer_range_after_current(add_btn->range);
});
}
}
@@ -204,7 +211,7 @@ void ObjectLayers::update_layers_list()
if (type & itLayerRoot)
create_layers_list();
else
- create_layer(objects_ctrl->GetModel()->GetLayerRangeByItem(item));
+ create_layer(objects_ctrl->GetModel()->GetLayerRangeByItem(item), nullptr, nullptr);
m_parent->Layout();
}
@@ -255,7 +262,7 @@ void ObjectLayers::msw_rescale()
{
wxSizerItem* b_item = item->GetSizer()->GetItem(btn);
if (b_item->IsWindow()) {
- ScalableButton* button = dynamic_cast(b_item->GetWindow());
+ auto button = dynamic_cast(b_item->GetWindow());
if (button != nullptr)
button->msw_rescale();
}
@@ -275,7 +282,7 @@ LayerRangeEditor::LayerRangeEditor( ObjectLayers* parent,
const wxString& value,
EditorType type,
std::function set_focus_data_fn,
- std::function edit_fn
+ std::function edit_fn
) :
m_valid_value(value),
m_type(type),
@@ -293,13 +300,13 @@ LayerRangeEditor::LayerRangeEditor( ObjectLayers* parent,
m_enter_pressed = true;
// If LayersList wasn't updated/recreated, we can call wxEVT_KILL_FOCUS.Skip()
if (m_type&etLayerHeight) {
- if (!edit_fn(get_value(), true))
+ if (!edit_fn(get_value(), true, false))
SetValue(m_valid_value);
else
m_valid_value = double_to_string(get_value());
m_call_kill_focus = true;
}
- else if (!edit_fn(get_value(), true)) {
+ else if (!edit_fn(get_value(), true, false)) {
SetValue(m_valid_value);
m_call_kill_focus = true;
}
@@ -319,13 +326,13 @@ LayerRangeEditor::LayerRangeEditor( ObjectLayers* parent,
#endif // not __WXGTK__
// If LayersList wasn't updated/recreated, we should call e.Skip()
if (m_type & etLayerHeight) {
- if (!edit_fn(get_value(), false))
+ if (!edit_fn(get_value(), false, dynamic_cast(e.GetWindow()) != nullptr))
SetValue(m_valid_value);
else
m_valid_value = double_to_string(get_value());
e.Skip();
}
- else if (!edit_fn(get_value(), false)) {
+ else if (!edit_fn(get_value(), false, dynamic_cast(e.GetWindow()) != nullptr)) {
SetValue(m_valid_value);
e.Skip();
}
@@ -387,4 +394,4 @@ void LayerRangeEditor::msw_rescale()
}
} //namespace GUI
-} //namespace Slic3r
\ No newline at end of file
+} //namespace Slic3r
diff --git a/src/slic3r/GUI/GUI_ObjectLayers.hpp b/src/slic3r/GUI/GUI_ObjectLayers.hpp
index c0de3be4ce..08b5949103 100644
--- a/src/slic3r/GUI/GUI_ObjectLayers.hpp
+++ b/src/slic3r/GUI/GUI_ObjectLayers.hpp
@@ -43,7 +43,8 @@ public:
const wxString& value = wxEmptyString,
EditorType type = etUndef,
std::function set_focus_data_fn = [](EditorType) {;},
- std::function edit_fn = [](coordf_t, bool) {return false; }
+ // callback parameters: new value, from enter, dont't update panel UI (when called from edit field's kill focus handler for the PlusMinusButton)
+ std::function edit_fn = [](coordf_t, bool, bool) {return false; }
);
~LayerRangeEditor() {}
@@ -69,8 +70,23 @@ public:
ObjectLayers(wxWindow* parent);
~ObjectLayers() {}
+
+ // Button remembers the layer height range, for which it has been created.
+ // The layer height range for this button is updated when the low or high boundary of the layer height range is updated
+ // by the respective text edit field, so that this button emits an action for an up to date layer height range value.
+ class PlusMinusButton : public ScalableButton
+ {
+ public:
+ PlusMinusButton(wxWindow *parent, const ScalableBitmap &bitmap, std::pair range) : ScalableButton(parent, wxID_ANY, bitmap), range(range) {}
+ // updated when the text edit field loses focus for any PlusMinusButton.
+ std::pair range;
+ };
+
void select_editor(LayerRangeEditor* editor, const bool is_last_edited_range);
- wxSizer* create_layer(const t_layer_height_range& range); // without_buttons
+ // Create sizer with layer height range and layer height text edit fields, without buttons.
+ // If the delete and add buttons are provided, the respective text edit fields will modify the layer height ranges of thes buttons
+ // on value change, so that these buttons work with up to date values.
+ wxSizer* create_layer(const t_layer_height_range& range, PlusMinusButton *delete_button, PlusMinusButton *add_button);
void create_layers_list();
void update_layers_list();
diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp
index afcf62ae15..5d06edad9c 100644
--- a/src/slic3r/GUI/GUI_ObjectList.cpp
+++ b/src/slic3r/GUI/GUI_ObjectList.cpp
@@ -115,11 +115,23 @@ ObjectList::ObjectList(wxWindow* parent) :
// describe control behavior
Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxDataViewEvent& event) {
+ // detect the current mouse position here, to pass it to list_manipulation() method
+ // if we detect it later, the user may have moved the mouse pointer while calculations are performed, and this would mess-up the HitTest() call performed into list_manipulation()
+ // see: https://github.com/prusa3d/PrusaSlicer/issues/3802
+ const wxPoint mouse_pos = this->get_mouse_position_in_control();
+
#ifndef __APPLE__
// On Windows and Linux, forces a kill focus emulation on the object manipulator fields because this event handler is called
// before the kill focus event handler on the object manipulator when changing selection in the list, invalidating the object
// manipulator cache with the following call to selection_changed()
- wxGetApp().obj_manipul()->emulate_kill_focus();
+// wxGetApp().obj_manipul()->emulate_kill_focus(); // It's not necessury anymore #ys_FIXME delete after testing
+
+ // On Windows and Linux:
+ // It's not invoked KillFocus event for "temporary" panels (like "Manipulation panel", "Settings", "Layer ranges"),
+ // if we change selection in object list.
+ // see https://github.com/prusa3d/PrusaSlicer/issues/3303
+ // But, if we call SetFocus() for ObjectList it will cause an invoking of a KillFocus event for "temporary" panels
+ this->SetFocus();
#else
// To avoid selection update from SetSelection() and UnselectAll() under osx
if (m_prevent_list_events)
@@ -150,7 +162,7 @@ ObjectList::ObjectList(wxWindow* parent) :
// Workaround for entering the column editing mode on Windows. Simulate keyboard enter when another column of the active line is selected.
wxDataViewItem item;
wxDataViewColumn *col;
- this->HitTest(get_mouse_position_in_control(), item, col);
+ this->HitTest(this->get_mouse_position_in_control(), item, col);
new_selected_column = (col == nullptr) ? -1 : (int)col->GetModelColumn();
if (new_selected_item == m_last_selected_item && m_last_selected_column != -1 && m_last_selected_column != new_selected_column) {
// Mouse clicked on another column of the active row. Simulate keyboard enter to enter the editing mode of the current column.
@@ -166,11 +178,11 @@ ObjectList::ObjectList(wxWindow* parent) :
selection_changed();
#ifndef __WXMSW__
- set_tooltip_for_item(get_mouse_position_in_control());
+ set_tooltip_for_item(this->get_mouse_position_in_control());
#endif //__WXMSW__
#ifndef __WXOSX__
- list_manipulation();
+ list_manipulation(mouse_pos);
#endif //__WXOSX__
});
@@ -206,7 +218,7 @@ ObjectList::ObjectList(wxWindow* parent) :
#ifdef __WXMSW__
GetMainWindow()->Bind(wxEVT_MOTION, [this](wxMouseEvent& event) {
- set_tooltip_for_item(get_mouse_position_in_control());
+ set_tooltip_for_item(this->get_mouse_position_in_control());
event.Skip();
});
#endif //__WXMSW__
@@ -414,14 +426,6 @@ void ObjectList::set_tooltip_for_item(const wxPoint& pt)
GetMainWindow()->SetToolTip(tooltip);
}
-wxPoint ObjectList::get_mouse_position_in_control()
-{
- const wxPoint& pt = wxGetMousePosition();
-// wxWindow* win = GetMainWindow();
-// wxPoint screen_pos = win->GetScreenPosition();
- return wxPoint(pt.x - /*win->*/GetScreenPosition().x, pt.y - /*win->*/GetScreenPosition().y);
-}
-
int ObjectList::get_selected_obj_idx() const
{
if (GetSelectedItemsCount() == 1)
@@ -783,23 +787,31 @@ void ObjectList::OnChar(wxKeyEvent& event)
*/
#endif /* __WXOSX__ */
-void ObjectList::OnContextMenu(wxDataViewEvent&)
+void ObjectList::OnContextMenu(wxDataViewEvent& evt)
{
+ // The mouse position returned by get_mouse_position_in_control() here is the one at the time the mouse button is released (mouse up event)
+ wxPoint mouse_pos = this->get_mouse_position_in_control();
+
// Do not show the context menu if the user pressed the right mouse button on the 3D scene and released it on the objects list
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
bool evt_context_menu = (canvas != nullptr) ? !canvas->is_mouse_dragging() : true;
if (!evt_context_menu)
canvas->mouse_up_cleanup();
- list_manipulation(evt_context_menu);
+ list_manipulation(mouse_pos, evt_context_menu);
}
-void ObjectList::list_manipulation(bool evt_context_menu/* = false*/)
+void ObjectList::list_manipulation(const wxPoint& mouse_pos, bool evt_context_menu/* = false*/)
{
+ // Interesting fact: when mouse_pos.x < 0, HitTest(mouse_pos, item, col) returns item = null, but column = last column.
+ // So, when mouse was moved to scene immediately after clicking in ObjectList, in the scene will be shown context menu for the Editing column.
+ // see: https://github.com/prusa3d/PrusaSlicer/issues/3802
+ if (mouse_pos.x < 0)
+ return;
+
wxDataViewItem item;
wxDataViewColumn* col = nullptr;
- const wxPoint pt = get_mouse_position_in_control();
- HitTest(pt, item, col);
+ HitTest(mouse_pos, item, col);
if (m_extruder_editor)
m_extruder_editor->Hide();
@@ -836,30 +848,32 @@ void ObjectList::list_manipulation(bool evt_context_menu/* = false*/)
Select(item);
}
- const wxString title = col->GetTitle();
-
- if (title == " ")
- toggle_printable_state(item);
- else if (title == _("Editing"))
- show_context_menu(evt_context_menu);
- else if (title == _("Name"))
+ if (col != nullptr)
{
- if (wxOSX)
- show_context_menu(evt_context_menu); // return context menu under OSX (related to #2909)
+ const wxString title = col->GetTitle();
+ if (title == " ")
+ toggle_printable_state(item);
+ else if (title == _("Editing"))
+ show_context_menu(evt_context_menu);
+ else if (title == _("Name"))
+ {
+ if (wxOSX)
+ show_context_menu(evt_context_menu); // return context menu under OSX (related to #2909)
- if (is_windows10())
- {
- int obj_idx, vol_idx;
- get_selected_item_indexes(obj_idx, vol_idx, item);
+ if (is_windows10())
+ {
+ int obj_idx, vol_idx;
+ get_selected_item_indexes(obj_idx, vol_idx, item);
- if (get_mesh_errors_count(obj_idx, vol_idx) > 0 &&
- pt.x > 2*wxGetApp().em_unit() && pt.x < 4*wxGetApp().em_unit() )
- fix_through_netfabb();
- }
- }
- // workaround for extruder editing under OSX
- else if (wxOSX && evt_context_menu && title == _("Extruder"))
- extruder_editing();
+ if (get_mesh_errors_count(obj_idx, vol_idx) > 0 &&
+ mouse_pos.x > 2 * wxGetApp().em_unit() && mouse_pos.x < 4 * wxGetApp().em_unit())
+ fix_through_netfabb();
+ }
+ }
+ // workaround for extruder editing under OSX
+ else if (wxOSX && evt_context_menu && title == _("Extruder"))
+ extruder_editing();
+ }
#ifndef __WXMSW__
GetMainWindow()->SetToolTip(""); // hide tooltip
@@ -909,7 +923,7 @@ void ObjectList::extruder_editing()
const int column_width = GetColumn(colExtruder)->GetWidth() + wxSystemSettings::GetMetric(wxSYS_VSCROLL_X) + 5;
- wxPoint pos = get_mouse_position_in_control();
+ wxPoint pos = this->get_mouse_position_in_control();
wxSize size = wxSize(column_width, -1);
pos.x = GetColumn(colName)->GetWidth() + GetColumn(colPrint)->GetWidth() + 5;
pos.y -= GetTextExtent("m").y;
@@ -2864,13 +2878,13 @@ void ObjectList::del_layer_range(const t_layer_height_range& range)
static double get_min_layer_height(const int extruder_idx)
{
const DynamicPrintConfig& config = wxGetApp().preset_bundle->printers.get_edited_preset().config;
- return config.opt_float("min_layer_height", extruder_idx <= 0 ? 0 : extruder_idx-1);
+ return config.opt_float("min_layer_height", std::max(0, extruder_idx - 1));
}
static double get_max_layer_height(const int extruder_idx)
{
const DynamicPrintConfig& config = wxGetApp().preset_bundle->printers.get_edited_preset().config;
- int extruder_idx_zero_based = extruder_idx <= 0 ? 0 : extruder_idx-1;
+ int extruder_idx_zero_based = std::max(0, extruder_idx - 1);
double max_layer_height = config.opt_float("max_layer_height", extruder_idx_zero_based);
// In case max_layer_height is set to zero, it should default to 75 % of nozzle diameter:
@@ -2880,80 +2894,139 @@ static double get_max_layer_height(const int extruder_idx)
return max_layer_height;
}
-void ObjectList::add_layer_range_after_current(const t_layer_height_range& current_range)
+// When editing this function, please synchronize the conditions with can_add_new_range_after_current().
+void ObjectList::add_layer_range_after_current(const t_layer_height_range current_range)
{
const int obj_idx = get_selected_obj_idx();
- if (obj_idx < 0) return;
+ assert(obj_idx >= 0);
+ if (obj_idx < 0)
+ // This should not happen.
+ return;
const wxDataViewItem layers_item = GetSelection();
t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges;
+ auto it_range = ranges.find(current_range);
+ assert(it_range != ranges.end());
+ if (it_range == ranges.end())
+ // This shoudl not happen.
+ return;
- const t_layer_height_range& last_range = (--ranges.end())->first;
-
- if (current_range == last_range)
+ auto it_next_range = it_range;
+ bool changed = false;
+ if (++ it_next_range == ranges.end())
{
+ // Adding a new layer height range after the last one.
take_snapshot(_(L("Add Height Range")));
+ changed = true;
- const t_layer_height_range& new_range = { last_range.second, last_range.second + 2. };
+ const t_layer_height_range new_range = { current_range.second, current_range.second + 2. };
ranges[new_range] = get_default_layer_config(obj_idx);
add_layer_item(new_range, layers_item);
}
- else
+ else if (const std::pair &next_range = it_next_range->first; current_range.second <= next_range.first)
{
- const t_layer_height_range& next_range = (++ranges.find(current_range))->first;
-
- if (current_range.second > next_range.first)
- return; // range division has no sense
-
const int layer_idx = m_objects_model->GetItemIdByLayerRange(obj_idx, next_range);
- if (layer_idx < 0)
- return;
-
- if (current_range.second == next_range.first)
+ assert(layer_idx >= 0);
+ if (layer_idx >= 0)
{
- const auto old_config = ranges.at(next_range);
+ if (current_range.second == next_range.first)
+ {
+ // Splitting the next layer height range to two.
+ const auto old_config = ranges.at(next_range);
+ const coordf_t delta = next_range.second - next_range.first;
+ // Layer height of the current layer.
+ const coordf_t old_min_layer_height = get_min_layer_height(old_config.opt_int("extruder"));
+ // Layer height of the layer to be inserted.
+ const coordf_t new_min_layer_height = get_min_layer_height(0);
+ if (delta >= old_min_layer_height + new_min_layer_height - EPSILON) {
+ const coordf_t middle_layer_z = (new_min_layer_height > 0.5 * delta) ?
+ next_range.second - new_min_layer_height :
+ next_range.first + std::max(old_min_layer_height, 0.5 * delta);
+ t_layer_height_range new_range = { middle_layer_z, next_range.second };
- const coordf_t delta = (next_range.second - next_range.first);
- if (delta < get_min_layer_height(old_config.opt_int("extruder"))/*0.05f*/) // next range division has no sense
- return;
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Add Height Range")));
+ changed = true;
- const coordf_t midl_layer = next_range.first + 0.5 * delta;
-
- t_layer_height_range new_range = { midl_layer, next_range.second };
+ // create new 2 layers instead of deleted one
+ // delete old layer
- Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Add Height Range")));
+ wxDataViewItem layer_item = m_objects_model->GetItemByLayerRange(obj_idx, next_range);
+ del_subobject_item(layer_item);
- // create new 2 layers instead of deleted one
+ ranges[new_range] = old_config;
+ add_layer_item(new_range, layers_item, layer_idx);
- // delete old layer
+ new_range = { current_range.second, middle_layer_z };
+ ranges[new_range] = get_default_layer_config(obj_idx);
+ add_layer_item(new_range, layers_item, layer_idx);
+ }
+ }
+ else if (next_range.first - current_range.second >= get_min_layer_height(0) - EPSILON)
+ {
+ // Filling in a gap between the current and a new layer height range with a new one.
+ take_snapshot(_(L("Add Height Range")));
+ changed = true;
- wxDataViewItem layer_item = m_objects_model->GetItemByLayerRange(obj_idx, next_range);
- del_subobject_item(layer_item);
-
- ranges[new_range] = old_config;
- add_layer_item(new_range, layers_item, layer_idx);
-
- new_range = { current_range.second, midl_layer };
- ranges[new_range] = get_default_layer_config(obj_idx);
- add_layer_item(new_range, layers_item, layer_idx);
+ const t_layer_height_range new_range = { current_range.second, next_range.first };
+ ranges[new_range] = get_default_layer_config(obj_idx);
+ add_layer_item(new_range, layers_item, layer_idx);
+ }
}
- else
- {
- take_snapshot(_(L("Add Height Range")));
-
- const t_layer_height_range new_range = { current_range.second, next_range.first };
- ranges[new_range] = get_default_layer_config(obj_idx);
- add_layer_item(new_range, layers_item, layer_idx);
- }
}
- changed_object(obj_idx);
+ if (changed)
+ changed_object(obj_idx);
+ // The layer range panel is updated even if this function does not change the layer ranges, as the panel update
+ // may have been postponed from the "kill focus" event of a text field, if the focus was lost for the "add layer" button.
// select item to update layers sizer
select_item(layers_item);
}
+// Returning an empty string means that the layer could be added after the current layer.
+// Otherwise an error tooltip is returned.
+// When editing this function, please synchronize the conditions with add_layer_range_after_current().
+wxString ObjectList::can_add_new_range_after_current(const t_layer_height_range current_range)
+{
+ const int obj_idx = get_selected_obj_idx();
+ assert(obj_idx >= 0);
+ if (obj_idx < 0)
+ // This should not happen.
+ return "ObjectList assert";
+
+ t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges;
+ auto it_range = ranges.find(current_range);
+ assert(it_range != ranges.end());
+ if (it_range == ranges.end())
+ // This shoudl not happen.
+ return "ObjectList assert";
+
+ auto it_next_range = it_range;
+ if (++ it_next_range == ranges.end())
+ // Adding a layer after the last layer is always possible.
+ return "";
+
+ if (const std::pair& next_range = it_next_range->first; current_range.second <= next_range.first)
+ {
+ if (current_range.second == next_range.first) {
+ if (next_range.second - next_range.first < get_min_layer_height(it_next_range->second.opt_int("extruder")) + get_min_layer_height(0) - EPSILON)
+ return _(L("Cannot insert a new layer range after the current layer range.\n"
+ "The next layer range is too thin to be split to two\n"
+ "without violating the minimum layer height."));
+ } else if (next_range.first - current_range.second < get_min_layer_height(0) - EPSILON) {
+ return _(L("Cannot insert a new layer range between the current and the next layer range.\n"
+ "The gap between the current layer range and the next layer range\n"
+ "is thinner than the minimum layer height allowed."));
+ }
+ } else
+ return _(L("Cannot insert a new layer range after the current layer range.\n"
+ "Current layer range overlaps with the next layer range."));
+
+ // All right, new layer height range could be inserted.
+ return "";
+}
+
void ObjectList::add_layer_item(const t_layer_height_range& range,
const wxDataViewItem layers_item,
const int layer_idx /* = -1*/)
@@ -2974,7 +3047,10 @@ void ObjectList::add_layer_item(const t_layer_height_range& range,
bool ObjectList::edit_layer_range(const t_layer_height_range& range, coordf_t layer_height)
{
- const int obj_idx = get_selected_obj_idx();
+ // Use m_selected_object_id instead of get_selected_obj_idx()
+ // because of get_selected_obj_idx() return obj_idx for currently selected item.
+ // But edit_layer_range(...) function can be called, when Selection in ObjectList could be changed
+ const int obj_idx = m_selected_object_id ;
if (obj_idx < 0)
return false;
@@ -2995,9 +3071,12 @@ bool ObjectList::edit_layer_range(const t_layer_height_range& range, coordf_t la
return false;
}
-bool ObjectList::edit_layer_range(const t_layer_height_range& range, const t_layer_height_range& new_range)
+bool ObjectList::edit_layer_range(const t_layer_height_range& range, const t_layer_height_range& new_range, bool dont_update_ui)
{
- const int obj_idx = get_selected_obj_idx();
+ // Use m_selected_object_id instead of get_selected_obj_idx()
+ // because of get_selected_obj_idx() return obj_idx for currently selected item.
+ // But edit_layer_range(...) function can be called, when Selection in ObjectList could be changed
+ const int obj_idx = m_selected_object_id;
if (obj_idx < 0) return false;
take_snapshot(_(L("Edit Height Range")));
@@ -3011,21 +3090,26 @@ bool ObjectList::edit_layer_range(const t_layer_height_range& range, const t_lay
ranges.erase(range);
ranges[new_range] = config;
changed_object(obj_idx);
-
+
wxDataViewItem root_item = m_objects_model->GetLayerRootItem(m_objects_model->GetItemById(obj_idx));
// To avoid update selection after deleting of a selected item (under GTK)
// set m_prevent_list_events to true
m_prevent_list_events = true;
m_objects_model->DeleteChildren(root_item);
- if (root_item.IsOk())
+ if (root_item.IsOk()) {
// create Layer item(s) according to the layer_config_ranges
for (const auto& r : ranges)
add_layer_item(r.first, root_item);
+ }
+
+ // if this function was invoked from wxEVT_CHANGE_SELECTION selected item could be other than itLayer or itLayerRoot
+ if (!dont_update_ui && (sel_type & (itLayer | itLayerRoot)))
+ select_item(sel_type&itLayer ? m_objects_model->GetItemByLayerRange(obj_idx, new_range) : root_item);
- select_item(sel_type&itLayer ? m_objects_model->GetItemByLayerRange(obj_idx, new_range) : root_item);
Expand(root_item);
+ m_prevent_list_events = false;
return true;
}
diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp
index edb7d800ed..609411cd59 100644
--- a/src/slic3r/GUI/GUI_ObjectList.hpp
+++ b/src/slic3r/GUI/GUI_ObjectList.hpp
@@ -284,7 +284,7 @@ public:
bool selected_instances_of_same_object();
bool can_split_instances();
- wxPoint get_mouse_position_in_control();
+ wxPoint get_mouse_position_in_control() const { return wxGetMousePosition() - this->GetScreenPosition(); }
wxBoxSizer* get_sizer() {return m_sizer;}
int get_selected_obj_idx() const;
DynamicPrintConfig& get_item_config(const wxDataViewItem& item) const;
@@ -320,13 +320,27 @@ public:
// Remove objects/sub-object from the list
void remove();
void del_layer_range(const t_layer_height_range& range);
- void add_layer_range_after_current(const t_layer_height_range& current_range);
+ // Add a new layer height after the current range if possible.
+ // The current range is shortened and the new range is entered after the shortened current range if it fits.
+ // If no range fits after the current range, then no range is inserted.
+ // The layer range panel is updated even if this function does not change the layer ranges, as the panel update
+ // may have been postponed from the "kill focus" event of a text field, if the focus was lost for the "add layer" button.
+ // Rather providing the range by a value than by a reference, so that the memory referenced cannot be invalidated.
+ void add_layer_range_after_current(const t_layer_height_range current_range);
+ wxString can_add_new_range_after_current( t_layer_height_range current_range);
void add_layer_item (const t_layer_height_range& range,
const wxDataViewItem layers_item,
const int layer_idx = -1);
bool edit_layer_range(const t_layer_height_range& range, coordf_t layer_height);
+ // This function may be called when a text field loses focus for a "add layer" or "remove layer" button.
+ // In that case we don't want to destroy the panel with that "add layer" or "remove layer" buttons, as some messages
+ // are already planned for them and destroying these widgets leads to crashes at least on OSX.
+ // In that case the "add layer" or "remove layer" button handlers are responsible for always rebuilding the panel
+ // even if the "add layer" or "remove layer" buttons did not update the layer spans or layer heights.
bool edit_layer_range(const t_layer_height_range& range,
- const t_layer_height_range& new_range);
+ const t_layer_height_range& new_range,
+ // Don't destroy the panel with the "add layer" or "remove layer" buttons.
+ bool suppress_ui_update = false);
void init_objects();
bool multiple_selection() const ;
@@ -381,7 +395,7 @@ private:
// void OnChar(wxKeyEvent& event);
#endif /* __WXOSX__ */
void OnContextMenu(wxDataViewEvent &event);
- void list_manipulation(bool evt_context_menu = false);
+ void list_manipulation(const wxPoint& mouse_pos, bool evt_context_menu = false);
void OnBeginDrag(wxDataViewEvent &event);
void OnDropPossible(wxDataViewEvent &event);
diff --git a/src/slic3r/GUI/GUI_Utils.cpp b/src/slic3r/GUI/GUI_Utils.cpp
index 1e452b220e..dc64141bae 100644
--- a/src/slic3r/GUI/GUI_Utils.cpp
+++ b/src/slic3r/GUI/GUI_Utils.cpp
@@ -21,6 +21,12 @@
namespace Slic3r {
namespace GUI {
+#ifdef _WIN32
+wxDEFINE_EVENT(EVT_HID_DEVICE_ATTACHED, HIDDeviceAttachedEvent);
+wxDEFINE_EVENT(EVT_HID_DEVICE_DETACHED, HIDDeviceDetachedEvent);
+wxDEFINE_EVENT(EVT_VOLUME_ATTACHED, VolumeAttachedEvent);
+wxDEFINE_EVENT(EVT_VOLUME_DETACHED, VolumeDetachedEvent);
+#endif // _WIN32
wxTopLevelWindow* find_toplevel_parent(wxWindow *window)
{
diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp
index f7bebd5778..0d5249e254 100644
--- a/src/slic3r/GUI/GUI_Utils.hpp
+++ b/src/slic3r/GUI/GUI_Utils.hpp
@@ -18,6 +18,8 @@
#include
#include
+#include "Event.hpp"
+
class wxCheckBox;
class wxTopLevelWindow;
class wxRect;
@@ -26,6 +28,19 @@ class wxRect;
namespace Slic3r {
namespace GUI {
+#ifdef _WIN32
+// USB HID attach / detach events from Windows OS.
+using HIDDeviceAttachedEvent = Event;
+using HIDDeviceDetachedEvent = Event;
+wxDECLARE_EVENT(EVT_HID_DEVICE_ATTACHED, HIDDeviceAttachedEvent);
+wxDECLARE_EVENT(EVT_HID_DEVICE_DETACHED, HIDDeviceDetachedEvent);
+
+// Disk aka Volume attach / detach events from Windows OS.
+using VolumeAttachedEvent = SimpleEvent;
+using VolumeDetachedEvent = SimpleEvent;
+wxDECLARE_EVENT(EVT_VOLUME_ATTACHED, VolumeAttachedEvent);
+wxDECLARE_EVENT(EVT_VOLUME_DETACHED, VolumeDetachedEvent);
+#endif /* _WIN32 */
wxTopLevelWindow* find_toplevel_parent(wxWindow *window);
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp
index 2f988db12f..46abe8a956 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp
@@ -262,12 +262,6 @@ void GLGizmoBase::render_grabbers_for_picking(const BoundingBoxf3& box) const
}
}
-
-void GLGizmoBase::set_tooltip(const std::string& tooltip) const
-{
- m_parent.set_tooltip(tooltip);
-}
-
std::string GLGizmoBase::format(float value, unsigned int decimals) const
{
return Slic3r::string_printf("%.*f", decimals, value);
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp
index 5f159420ff..0d19a86afe 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp
@@ -100,6 +100,7 @@ protected:
mutable std::vector m_grabbers;
ImGuiWrapper* m_imgui;
bool m_first_input_window_render;
+ mutable std::string m_tooltip;
public:
GLGizmoBase(GLCanvas3D& parent,
@@ -145,10 +146,12 @@ public:
void update(const UpdateData& data);
- void render() const { on_render(); }
+ void render() const { m_tooltip.clear(); on_render(); }
void render_for_picking() const { on_render_for_picking(); }
void render_input_window(float x, float y, float bottom_limit);
+ virtual std::string get_tooltip() const { return ""; }
+
protected:
virtual bool on_init() = 0;
virtual void on_load(cereal::BinaryInputArchive& ar) {}
@@ -174,7 +177,6 @@ protected:
void render_grabbers(float size) const;
void render_grabbers_for_picking(const BoundingBoxf3& box) const;
- void set_tooltip(const std::string& tooltip) const;
std::string format(float value, unsigned int decimals) const;
};
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp
index 52d710249b..9382579ea3 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp
@@ -30,6 +30,11 @@ GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, uns
, m_rotate_lower(false)
{}
+std::string GLGizmoCut::get_tooltip() const
+{
+ return (m_hover_id == 0 || m_grabbers[0].dragging) ? "Z: " + format(m_cut_z, 2) : "";
+}
+
bool GLGizmoCut::on_init()
{
m_grabbers.emplace_back();
@@ -79,10 +84,6 @@ void GLGizmoCut::on_update(const UpdateData& data)
void GLGizmoCut::on_render() const
{
- if (m_grabbers[0].dragging) {
- set_tooltip("Z: " + format(m_cut_z, 2));
- }
-
const Selection& selection = m_parent.get_selection();
update_max_z(selection);
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp
index b6e10861fc..c0f33978f6 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp
@@ -28,6 +28,8 @@ public:
double get_cut_z() const { return m_cut_z; }
void set_cut_z(double cut_z) const;
+ std::string get_tooltip() const override;
+
protected:
virtual bool on_init();
virtual void on_load(cereal::BinaryInputArchive& ar) { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); }
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp
index 6be108c852..f349776ab3 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp
@@ -31,6 +31,22 @@ GLGizmoMove3D::~GLGizmoMove3D()
::gluDeleteQuadric(m_quadric);
}
+std::string GLGizmoMove3D::get_tooltip() const
+{
+ const Selection& selection = m_parent.get_selection();
+ bool show_position = selection.is_single_full_instance();
+ const Vec3d& position = selection.get_bounding_box().center();
+
+ if (m_hover_id == 0 || m_grabbers[0].dragging)
+ return "X: " + format(show_position ? position(0) : m_displacement(0), 2);
+ else if (m_hover_id == 1 || m_grabbers[1].dragging)
+ return "Y: " + format(show_position ? position(1) : m_displacement(1), 2);
+ else if (m_hover_id == 2 || m_grabbers[2].dragging)
+ return "Z: " + format(show_position ? position(2) : m_displacement(2), 2);
+ else
+ return "";
+}
+
bool GLGizmoMove3D::on_init()
{
for (int i = 0; i < 3; ++i)
@@ -85,22 +101,6 @@ void GLGizmoMove3D::on_render() const
{
const Selection& selection = m_parent.get_selection();
- bool show_position = selection.is_single_full_instance();
- const Vec3d& position = selection.get_bounding_box().center();
-
- if ((show_position && (m_hover_id == 0)) || m_grabbers[0].dragging)
- set_tooltip("X: " + format(show_position ? position(0) : m_displacement(0), 2));
- else if (!m_grabbers[0].dragging && (m_hover_id == 0))
- set_tooltip("X");
- else if ((show_position && (m_hover_id == 1)) || m_grabbers[1].dragging)
- set_tooltip("Y: " + format(show_position ? position(1) : m_displacement(1), 2));
- else if (!m_grabbers[1].dragging && (m_hover_id == 1))
- set_tooltip("Y");
- else if ((show_position && (m_hover_id == 2)) || m_grabbers[2].dragging)
- set_tooltip("Z: " + format(show_position ? position(2) : m_displacement(2), 2));
- else if (!m_grabbers[2].dragging && (m_hover_id == 2))
- set_tooltip("Z");
-
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
glsafe(::glEnable(GL_DEPTH_TEST));
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp
index b2367ac685..5a4275b7fc 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp
@@ -30,6 +30,8 @@ public:
const Vec3d& get_displacement() const { return m_displacement; }
+ std::string get_tooltip() const override;
+
protected:
virtual bool on_init();
virtual std::string on_get_name() const;
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp
index 9a0d750339..f3e5656860 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp
@@ -67,6 +67,18 @@ void GLGizmoRotate::set_angle(double angle)
m_angle = angle;
}
+std::string GLGizmoRotate::get_tooltip() const
+{
+ std::string axis;
+ switch (m_axis)
+ {
+ case X: { axis = "X"; break; }
+ case Y: { axis = "Y"; break; }
+ case Z: { axis = "Z"; break; }
+ }
+ return (m_hover_id == 0 || m_grabbers[0].dragging) ? axis + ": " + format((float)Geometry::rad2deg(m_angle), 4) : "";
+}
+
bool GLGizmoRotate::on_init()
{
m_grabbers.push_back(Grabber());
@@ -127,19 +139,7 @@ void GLGizmoRotate::on_render() const
const Selection& selection = m_parent.get_selection();
const BoundingBoxf3& box = selection.get_bounding_box();
- std::string axis;
- switch (m_axis)
- {
- case X: { axis = "X"; break; }
- case Y: { axis = "Y"; break; }
- case Z: { axis = "Z"; break; }
- }
-
- if (!m_dragging && (m_hover_id == 0))
- set_tooltip(axis);
- else if (m_dragging)
- set_tooltip(axis + ": " + format((float)Geometry::rad2deg(m_angle), 4) + "\u00B0");
- else
+ if (m_hover_id != 0 && !m_grabbers[0].dragging)
{
m_center = box.center();
m_radius = Offset + box.radius();
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp
index 6e7bf1a098..7365a20c36 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp
@@ -49,6 +49,8 @@ public:
double get_angle() const { return m_angle; }
void set_angle(double angle);
+ std::string get_tooltip() const override;
+
protected:
virtual bool on_init();
virtual std::string on_get_name() const { return ""; }
@@ -81,6 +83,16 @@ public:
Vec3d get_rotation() const { return Vec3d(m_gizmos[X].get_angle(), m_gizmos[Y].get_angle(), m_gizmos[Z].get_angle()); }
void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation(0)); m_gizmos[Y].set_angle(rotation(1)); m_gizmos[Z].set_angle(rotation(2)); }
+ std::string get_tooltip() const override
+ {
+ std::string tooltip = m_gizmos[X].get_tooltip();
+ if (tooltip.empty())
+ tooltip = m_gizmos[Y].get_tooltip();
+ if (tooltip.empty())
+ tooltip = m_gizmos[Z].get_tooltip();
+ return tooltip;
+ }
+
protected:
virtual bool on_init();
virtual std::string on_get_name() const;
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp
index b4972e6fad..80cc16ba20 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp
@@ -20,6 +20,38 @@ GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filen
{
}
+std::string GLGizmoScale3D::get_tooltip() const
+{
+ const Selection& selection = m_parent.get_selection();
+
+ bool single_instance = selection.is_single_full_instance();
+ bool single_volume = selection.is_single_modifier() || selection.is_single_volume();
+ bool single_selection = single_instance || single_volume;
+
+ Vec3f scale = 100.0f * Vec3f::Ones();
+ if (single_instance)
+ scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor().cast();
+ else if (single_volume)
+ scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_scaling_factor().cast();
+
+ if (m_hover_id == 0 || m_hover_id == 1 || m_grabbers[0].dragging || m_grabbers[1].dragging)
+ return "X: " + format(scale(0), 4) + "%";
+ else if (m_hover_id == 2 || m_hover_id == 3 || m_grabbers[2].dragging || m_grabbers[3].dragging)
+ return "Y: " + format(scale(1), 4) + "%";
+ else if (m_hover_id == 4 || m_hover_id == 5 || m_grabbers[4].dragging || m_grabbers[5].dragging)
+ return "Z: " + format(scale(2), 4) + "%";
+ else if (m_hover_id == 6 || m_hover_id == 7 || m_hover_id == 8 || m_hover_id == 9 ||
+ m_grabbers[6].dragging || m_grabbers[7].dragging || m_grabbers[8].dragging || m_grabbers[9].dragging)
+ {
+ std::string tooltip = "X: " + format(scale(0), 4) + "%\n";
+ tooltip += "Y: " + format(scale(1), 4) + "%\n";
+ tooltip += "Z: " + format(scale(2), 4) + "%";
+ return tooltip;
+ }
+ else
+ return "";
+}
+
bool GLGizmoScale3D::on_init()
{
for (int i = 0; i < 10; ++i)
@@ -89,37 +121,6 @@ void GLGizmoScale3D::on_render() const
bool single_instance = selection.is_single_full_instance();
bool single_volume = selection.is_single_modifier() || selection.is_single_volume();
- bool single_selection = single_instance || single_volume;
-
- Vec3f scale = 100.0f * Vec3f::Ones();
- if (single_instance)
- scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor().cast();
- else if (single_volume)
- scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_scaling_factor().cast();
-
- if ((single_selection && ((m_hover_id == 0) || (m_hover_id == 1))) || m_grabbers[0].dragging || m_grabbers[1].dragging)
- set_tooltip("X: " + format(scale(0), 4) + "%");
- else if (!m_grabbers[0].dragging && !m_grabbers[1].dragging && ((m_hover_id == 0) || (m_hover_id == 1)))
- set_tooltip("X");
- else if ((single_selection && ((m_hover_id == 2) || (m_hover_id == 3))) || m_grabbers[2].dragging || m_grabbers[3].dragging)
- set_tooltip("Y: " + format(scale(1), 4) + "%");
- else if (!m_grabbers[2].dragging && !m_grabbers[3].dragging && ((m_hover_id == 2) || (m_hover_id == 3)))
- set_tooltip("Y");
- else if ((single_selection && ((m_hover_id == 4) || (m_hover_id == 5))) || m_grabbers[4].dragging || m_grabbers[5].dragging)
- set_tooltip("Z: " + format(scale(2), 4) + "%");
- else if (!m_grabbers[4].dragging && !m_grabbers[5].dragging && ((m_hover_id == 4) || (m_hover_id == 5)))
- set_tooltip("Z");
- else if ((single_selection && ((m_hover_id == 6) || (m_hover_id == 7) || (m_hover_id == 8) || (m_hover_id == 9)))
- || m_grabbers[6].dragging || m_grabbers[7].dragging || m_grabbers[8].dragging || m_grabbers[9].dragging)
- {
- std::string tooltip = "X: " + format(scale(0), 4) + "%\n";
- tooltip += "Y: " + format(scale(1), 4) + "%\n";
- tooltip += "Z: " + format(scale(2), 4) + "%";
- set_tooltip(tooltip);
- }
- else if (!m_grabbers[6].dragging && !m_grabbers[7].dragging && !m_grabbers[8].dragging && !m_grabbers[9].dragging &&
- ((m_hover_id == 6) || (m_hover_id == 7) || (m_hover_id == 8) || (m_hover_id == 9)))
- set_tooltip("X/Y/Z");
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
glsafe(::glEnable(GL_DEPTH_TEST));
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp
index d49770dce1..71f7932799 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp
@@ -42,6 +42,8 @@ public:
const Vec3d& get_offset() const { return m_offset; }
+ std::string get_tooltip() const override;
+
protected:
virtual bool on_init();
virtual std::string on_get_name() const;
diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
index d465196f30..09eaf9c870 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
@@ -226,7 +226,7 @@ void GLGizmosManager::update_data()
set_scale(Vec3d::Ones());
set_rotation(Vec3d::Zero());
set_flattening_data(selection.is_from_single_object() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr);
- set_sla_support_data(nullptr);
+ set_sla_support_data(selection.is_from_single_instance() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr);
}
}
@@ -423,6 +423,15 @@ void GLGizmosManager::render_overlay() const
do_render_overlay();
}
+std::string GLGizmosManager::get_tooltip() const
+{
+ if (!m_tooltip.empty())
+ return m_tooltip;
+
+ const GLGizmoBase* curr = get_current();
+ return (curr != nullptr) ? curr->get_tooltip() : "";
+}
+
bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt)
{
bool processed = false;
@@ -448,6 +457,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
int selected_object_idx = selection.get_object_idx();
bool processed = false;
+#if !ENABLE_MODIFIED_GIZMOBAR_MOUSE_EVENT_HANDLING
// mouse anywhere
if (!evt.Dragging() && !evt.Leaving() && !evt.Entering() && (m_mouse_capture.parent != nullptr))
{
@@ -457,10 +467,81 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
m_mouse_capture.reset();
}
+#endif // !ENABLE_MODIFIED_GIZMOBAR_MOUSE_EVENT_HANDLING
// mouse anywhere
if (evt.Moving())
m_tooltip = update_hover_state(mouse_pos);
+#if ENABLE_MODIFIED_GIZMOBAR_MOUSE_EVENT_HANDLING
+ else if (evt.LeftUp())
+ {
+ if (m_mouse_capture.left)
+ {
+ processed = true;
+ m_mouse_capture.left = false;
+ }
+ else if (is_dragging())
+ {
+ switch (m_current) {
+ case Move: m_parent.do_move(L("Gizmo-Move")); break;
+ case Scale: m_parent.do_scale(L("Gizmo-Scale")); break;
+ case Rotate: m_parent.do_rotate(L("Gizmo-Rotate")); break;
+ default: break;
+ }
+
+ stop_dragging();
+ update_data();
+
+ wxGetApp().obj_manipul()->set_dirty();
+ // Let the plater know that the dragging finished, so a delayed refresh
+ // of the scene with the background processing data should be performed.
+ m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED));
+ // updates camera target constraints
+ m_parent.refresh_camera_scene_box();
+
+ processed = true;
+ }
+// else
+// return false;
+ }
+ else if (evt.MiddleUp())
+ {
+ if (m_mouse_capture.middle)
+ {
+ processed = true;
+ m_mouse_capture.middle = false;
+ }
+ else
+ return false;
+ }
+ else if (evt.RightUp())
+ {
+ if (pending_right_up)
+ {
+ pending_right_up = false;
+ return true;
+ }
+ if (m_mouse_capture.right)
+ {
+ processed = true;
+ m_mouse_capture.right = false;
+ }
+ else
+ return false;
+ }
+#if ENABLE_GIZMO_TOOLBAR_DRAGGING_FIX
+ else if (evt.Dragging() && !is_dragging())
+#else
+ else if (evt.Dragging()))
+#endif // ENABLE_GIZMO_TOOLBAR_DRAGGING_FIX
+ {
+ if (m_mouse_capture.any())
+ // if the button down was done on this toolbar, prevent from dragging into the scene
+ processed = true;
+// else
+// return false;
+ }
+#else
else if (evt.LeftUp())
m_mouse_capture.left = false;
else if (evt.MiddleUp())
@@ -477,6 +558,55 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
else if (evt.Dragging() && m_mouse_capture.any())
// if the button down was done on this toolbar, prevent from dragging into the scene
processed = true;
+#endif // ENABLE_MODIFIED_GIZMOBAR_MOUSE_EVENT_HANDLING
+#if ENABLE_GIZMO_TOOLBAR_DRAGGING_FIX
+ else if (evt.Dragging() && is_dragging())
+ {
+ if (!m_parent.get_wxglcanvas()->HasCapture())
+ m_parent.get_wxglcanvas()->CaptureMouse();
+
+ m_parent.set_mouse_as_dragging();
+ update(m_parent.mouse_ray(pos), pos);
+
+ switch (m_current)
+ {
+ case Move:
+ {
+ // Apply new temporary offset
+ selection.translate(get_displacement());
+ wxGetApp().obj_manipul()->set_dirty();
+ break;
+ }
+ case Scale:
+ {
+ // Apply new temporary scale factors
+ TransformationType transformation_type(TransformationType::Local_Absolute_Joint);
+ if (evt.AltDown())
+ transformation_type.set_independent();
+ selection.scale(get_scale(), transformation_type);
+ if (evt.ControlDown())
+ selection.translate(get_scale_offset(), true);
+ wxGetApp().obj_manipul()->set_dirty();
+ break;
+ }
+ case Rotate:
+ {
+ // Apply new temporary rotations
+ TransformationType transformation_type(TransformationType::World_Relative_Joint);
+ if (evt.AltDown())
+ transformation_type.set_independent();
+ selection.rotate(get_rotation(), transformation_type);
+ wxGetApp().obj_manipul()->set_dirty();
+ break;
+ }
+ default:
+ break;
+ }
+
+ m_parent.set_as_dirty();
+ processed = true;
+ }
+#endif // ENABLE_GIZMO_TOOLBAR_DRAGGING_FIX
if (get_gizmo_idx_from_mouse(mouse_pos) == Undefined)
{
@@ -519,6 +649,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
m_parent.set_as_dirty();
processed = true;
}
+#if !ENABLE_GIZMO_TOOLBAR_DRAGGING_FIX
else if (evt.Dragging() && is_dragging())
{
if (!m_parent.get_wxglcanvas()->HasCapture())
@@ -565,6 +696,8 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
m_parent.set_as_dirty();
processed = true;
}
+#endif // !ENABLE_GIZMO_TOOLBAR_DRAGGING_FIX
+#if !ENABLE_MODIFIED_GIZMOBAR_MOUSE_EVENT_HANDLING
else if (evt.LeftUp() && is_dragging())
{
switch (m_current) {
@@ -586,6 +719,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
processed = true;
}
+#endif // !ENABLE_MODIFIED_GIZMOBAR_MOUSE_EVENT_HANDLING
else if (evt.LeftUp() && (m_current == SlaSupports || m_current == Hollow) && !m_parent.is_mouse_dragging())
{
// in case SLA gizmo is selected, we just pass the LeftUp event and stop processing - neither
@@ -624,8 +758,10 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
m_mouse_capture.right = true;
m_mouse_capture.parent = &m_parent;
}
+#if !ENABLE_MODIFIED_GIZMOBAR_MOUSE_EVENT_HANDLING
else if (evt.LeftUp())
processed = true;
+#endif // !ENABLE_MODIFIED_GIZMOBAR_MOUSE_EVENT_HANDLING
}
return processed;
diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp
index 2eac470f90..7ae1fa661e 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp
@@ -206,7 +206,7 @@ public:
void render_overlay() const;
- const std::string& get_tooltip() const { return m_tooltip; }
+ std::string get_tooltip() const;
bool on_mouse(wxMouseEvent& evt);
bool on_mouse_wheel(wxMouseEvent& evt);
diff --git a/src/slic3r/GUI/I18N.hpp b/src/slic3r/GUI/I18N.hpp
index f65e03b507..25e46930ba 100644
--- a/src/slic3r/GUI/I18N.hpp
+++ b/src/slic3r/GUI/I18N.hpp
@@ -1,10 +1,12 @@
#ifndef _
-#define _(s) Slic3r::GUI::I18N::translate((s))
+#define _(s) Slic3r::GUI::I18N::translate((s))
+#define _L(s) Slic3r::GUI::I18N::translate((s))
#define _utf8(s) Slic3r::GUI::I18N::translate_utf8((s))
+#define _u8L(s) Slic3r::GUI::I18N::translate_utf8((s))
#endif /* _ */
#ifndef _CTX
-#define _CTX(s, ctx) Slic3r::GUI::I18N::translate((s), (ctx))
+#define _CTX(s, ctx) Slic3r::GUI::I18N::translate((s), (ctx))
#define _CTX_utf8(s, ctx) Slic3r::GUI::I18N::translate_utf8((s), (ctx))
#endif /* _ */
diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp
index 3efa800a92..a44e843b83 100644
--- a/src/slic3r/GUI/ImGuiWrapper.cpp
+++ b/src/slic3r/GUI/ImGuiWrapper.cpp
@@ -441,15 +441,37 @@ bool ImGuiWrapper::want_any_input() const
return io.WantCaptureMouse || io.WantCaptureKeyboard || io.WantTextInput;
}
+#ifdef __APPLE__
+static const ImWchar ranges_keyboard_shortcuts[] =
+{
+ 0x21E7, 0x21E7, // OSX Shift Key symbol
+ 0x2318, 0x2318, // OSX Command Key symbol
+ 0x2325, 0x2325, // OSX Option Key symbol
+ 0,
+};
+#endif // __APPLE__
+
void ImGuiWrapper::init_font(bool compress)
{
destroy_font();
ImGuiIO& io = ImGui::GetIO();
io.Fonts->Clear();
- //FIXME replace with io.Fonts->AddFontFromMemoryTTF(buf_decompressed_data, (int)buf_decompressed_size, m_font_size, nullptr, m_glyph_ranges);
+
+ // Create ranges of characters from m_glyph_ranges, possibly adding some OS specific special characters.
+ ImVector ranges;
+ ImFontAtlas::GlyphRangesBuilder builder;
+ builder.AddRanges(m_glyph_ranges);
+#ifdef __APPLE__
+ if (m_font_cjk)
+ // Apple keyboard shortcuts are only contained in the CJK fonts.
+ builder.AddRanges(ranges_keyboard_shortcuts);
+#endif
+ builder.BuildRanges(&ranges); // Build the final result (ordered ranges with all the unique characters submitted)
+
+ //FIXME replace with io.Fonts->AddFontFromMemoryTTF(buf_decompressed_data, (int)buf_decompressed_size, m_font_size, nullptr, ranges.Data);
//https://github.com/ocornut/imgui/issues/220
- ImFont* font = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/" + (m_font_cjk ? "NotoSansCJK-Regular.ttc" : "NotoSans-Regular.ttf")).c_str(), m_font_size, nullptr, m_glyph_ranges);
+ ImFont* font = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/" + (m_font_cjk ? "NotoSansCJK-Regular.ttc" : "NotoSans-Regular.ttf")).c_str(), m_font_size, nullptr, ranges.Data);
if (font == nullptr) {
font = io.Fonts->AddFontDefault();
if (font == nullptr) {
@@ -457,6 +479,16 @@ void ImGuiWrapper::init_font(bool compress)
}
}
+#ifdef __APPLE__
+ ImFontConfig config;
+ config.MergeMode = true;
+ if (! m_font_cjk) {
+ // Apple keyboard shortcuts are only contained in the CJK fonts.
+ ImFont *font_cjk = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/NotoSansCJK-Regular.ttc").c_str(), m_font_size, &config, ranges_keyboard_shortcuts);
+ assert(font_cjk != nullptr);
+ }
+#endif
+
// Build texture atlas
unsigned char* pixels;
int width, height;
diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp
index b6e55a7c9d..842cec5e2c 100644
--- a/src/slic3r/GUI/KBShortcutsDialog.cpp
+++ b/src/slic3r/GUI/KBShortcutsDialog.cpp
@@ -122,6 +122,8 @@ void KBShortcutsDialog::fill_shortcuts()
{ ctrl + "G", L("Export G-code") },
{ ctrl + "Shift+" + "G", L("Send G-code") },
{ ctrl + "E", L("Export config") },
+ { ctrl + "U", L("Export to SD card / Flash drive") },
+ { ctrl + "T", L("Eject SD card / Flash drive") },
// Edit
{ ctrl + "A", L("Select all objects") },
{ "Esc", L("Deselect all") },
@@ -142,9 +144,7 @@ void KBShortcutsDialog::fill_shortcuts()
{ ctrl + "J", L("Print host upload queue") },
// View
{ "0-6", L("Camera view") },
-#if ENABLE_SHOW_SCENE_LABELS
{ "E", L("Show/Hide object/instance labels") },
-#endif // ENABLE_SHOW_SCENE_LABELS
// Configuration
{ ctrl + "P", L("Preferences") },
// Help
diff --git a/src/slic3r/GUI/LambdaObjectDialog.hpp b/src/slic3r/GUI/LambdaObjectDialog.hpp
index 6cc99c8a74..5bc2d19a53 100644
--- a/src/slic3r/GUI/LambdaObjectDialog.hpp
+++ b/src/slic3r/GUI/LambdaObjectDialog.hpp
@@ -1,7 +1,8 @@
#ifndef slic3r_LambdaObjectDialog_hpp_
#define slic3r_LambdaObjectDialog_hpp_
-#include "GUI.hpp"
+#include
+#include
#include
#include
diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp
index 18e43388a9..1e22359ab2 100644
--- a/src/slic3r/GUI/MainFrame.cpp
+++ b/src/slic3r/GUI/MainFrame.cpp
@@ -25,11 +25,16 @@
#include "wxExtensions.hpp"
#include "GUI_ObjectList.hpp"
#include "Mouse3DController.hpp"
+#include "RemovableDriveManager.hpp"
#include "I18N.hpp"
#include
#include "GUI_App.hpp"
+#ifdef _WIN32
+#include
+#endif // _WIN32
+
namespace Slic3r {
namespace GUI {
@@ -103,33 +108,37 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
update_title();
// declare events
+ Bind(wxEVT_CREATE, [this](wxWindowCreateEvent& event) {
+
+#ifdef _WIN32
+ //static GUID GUID_DEVINTERFACE_USB_DEVICE = { 0xA5DCBF10, 0x6530, 0x11D2, 0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED };
+ //static GUID GUID_DEVINTERFACE_DISK = { 0x53f56307, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b };
+ //static GUID GUID_DEVINTERFACE_VOLUME = { 0x71a27cdd, 0x812a, 0x11d0, 0xbe, 0xc7, 0x08, 0x00, 0x2b, 0xe2, 0x09, 0x2f };
+ static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 };
+
+ // Register USB HID (Human Interface Devices) notifications to trigger the 3DConnexion enumeration.
+ DEV_BROADCAST_DEVICEINTERFACE NotificationFilter = { 0 };
+ NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
+ NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
+ NotificationFilter.dbcc_classguid = GUID_DEVINTERFACE_HID;
+ m_hDeviceNotify = ::RegisterDeviceNotification(this->GetHWND(), &NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE);
+
+// or register for file handle change?
+// DEV_BROADCAST_HANDLE NotificationFilter = { 0 };
+// NotificationFilter.dbch_size = sizeof(DEV_BROADCAST_HANDLE);
+// NotificationFilter.dbch_devicetype = DBT_DEVTYP_HANDLE;
+#endif // _WIN32
+
+ // propagate event
+ event.Skip();
+ });
+
Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& event) {
if (event.CanVeto() && !wxGetApp().check_unsaved_changes()) {
event.Veto();
return;
}
-
- if(m_plater) m_plater->stop_jobs();
-
- // Weird things happen as the Paint messages are floating around the windows being destructed.
- // Avoid the Paint messages by hiding the main window.
- // Also the application closes much faster without these unnecessary screen refreshes.
- // In addition, there were some crashes due to the Paint events sent to already destructed windows.
- this->Show(false);
-
- // Save the slic3r.ini.Usually the ini file is saved from "on idle" callback,
- // but in rare cases it may not have been called yet.
- wxGetApp().app_config->save();
-// if (m_plater)
-// m_plater->print = undef;
- _3DScene::remove_all_canvases();
-// Slic3r::GUI::deregister_on_request_update_callback();
-
- // set to null tabs and a plater
- // to avoid any manipulations with them from App->wxEVT_IDLE after of the mainframe closing
- wxGetApp().tabs_list.clear();
- wxGetApp().plater_ = nullptr;
-
+ this->shutdown();
// propagate event
event.Skip();
});
@@ -148,6 +157,46 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
m_plater->show_action_buttons(true);
}
+// Called when closing the application and when switching the application language.
+void MainFrame::shutdown()
+{
+#ifdef _WIN32
+ ::UnregisterDeviceNotification(HDEVNOTIFY(m_hDeviceNotify));
+ m_hDeviceNotify = nullptr;
+#endif // _WIN32
+
+ if (m_plater)
+ m_plater->stop_jobs();
+
+ // Weird things happen as the Paint messages are floating around the windows being destructed.
+ // Avoid the Paint messages by hiding the main window.
+ // Also the application closes much faster without these unnecessary screen refreshes.
+ // In addition, there were some crashes due to the Paint events sent to already destructed windows.
+ this->Show(false);
+
+ // Stop the background thread (Windows and Linux).
+ // Disconnect from a 3DConnextion driver (OSX).
+ m_plater->get_mouse3d_controller().shutdown();
+ // Store the device parameter database back to appconfig.
+ m_plater->get_mouse3d_controller().save_config(*wxGetApp().app_config);
+
+ // Stop the background thread of the removable drive manager, so that no new updates will be sent to the Plater.
+ wxGetApp().removable_drive_manager()->shutdown();
+
+ // Save the slic3r.ini.Usually the ini file is saved from "on idle" callback,
+ // but in rare cases it may not have been called yet.
+ wxGetApp().app_config->save();
+// if (m_plater)
+// m_plater->print = undef;
+ _3DScene::remove_all_canvases();
+// Slic3r::GUI::deregister_on_request_update_callback();
+
+ // set to null tabs and a plater
+ // to avoid any manipulations with them from App->wxEVT_IDLE after of the mainframe closing
+ wxGetApp().tabs_list.clear();
+ wxGetApp().plater_ = nullptr;
+}
+
void MainFrame::update_title()
{
wxString title = wxEmptyString;
@@ -316,6 +365,27 @@ bool MainFrame::can_send_gcode() const
return print_host_opt != nullptr && !print_host_opt->value.empty();
}
+bool MainFrame::can_export_gcode_sd() const
+{
+ if (m_plater == nullptr)
+ return false;
+
+ if (m_plater->model().objects.empty())
+ return false;
+
+ if (m_plater->is_export_gcode_scheduled())
+ return false;
+
+ // TODO:: add other filters
+
+ return wxGetApp().removable_drive_manager()->status().has_removable_drives;
+}
+
+bool MainFrame::can_eject() const
+{
+ return wxGetApp().removable_drive_manager()->status().has_eject;
+}
+
bool MainFrame::can_slice() const
{
bool bg_proc = wxGetApp().app_config->get("background_processing") == "1";
@@ -422,7 +492,7 @@ void MainFrame::init_menubar()
m_plater->load_project(filename);
else
{
- wxMessageDialog msg(this, _(L("The selected project is no longer available.\nDo you want to remove it from the recent projects list ?")), _(L("Error")), wxYES_NO | wxYES_DEFAULT);
+ wxMessageDialog msg(this, _(L("The selected project is no longer available.\nDo you want to remove it from the recent projects list?")), _(L("Error")), wxYES_NO | wxYES_DEFAULT);
if (msg.ShowModal() == wxID_YES)
{
m_recent_projects.RemoveFileFromHistory(file_id);
@@ -486,6 +556,9 @@ void MainFrame::init_menubar()
[this](wxCommandEvent&) { if (m_plater) m_plater->send_gcode(); }, "export_gcode", nullptr,
[this](){return can_send_gcode(); }, this);
m_changeable_menu_items.push_back(item_send_gcode);
+ append_menu_item(export_menu, wxID_ANY, _(L("Export G-code to SD card / Flash drive")) + dots + "\tCtrl+U", _(L("Export current plate as G-code to SD card / Flash drive")),
+ [this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(true); }, "export_to_sd", nullptr,
+ [this]() {return can_export_gcode_sd(); }, this);
export_menu->AppendSeparator();
append_menu_item(export_menu, wxID_ANY, _(L("Export plate as &STL")) + dots, _(L("Export current plate as STL")),
[this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(); }, "export_plater", nullptr,
@@ -509,6 +582,10 @@ void MainFrame::init_menubar()
[this]() {return true; }, this);
append_submenu(fileMenu, export_menu, wxID_ANY, _(L("&Export")), "");
+ append_menu_item(fileMenu, wxID_ANY, _(L("Ejec&t SD card / Flash drive")) + dots + "\tCtrl+T", _(L("Eject SD card / Flash drive after the G-code was exported to it.")),
+ [this](wxCommandEvent&) { if (m_plater) m_plater->eject_drive(); }, "eject_sd", nullptr,
+ [this]() {return can_eject(); }, this);
+
fileMenu->AppendSeparator();
#if 0
@@ -1029,7 +1106,7 @@ void MainFrame::load_configbundle(wxString file/* = wxEmptyString, const bool re
wxGetApp().load_current_presets();
const auto message = wxString::Format(_(L("%d presets successfully imported.")), presets_imported);
- Slic3r::GUI::show_info(this, message, "Info");
+ Slic3r::GUI::show_info(this, message, wxString("Info"));
}
// Load a provied DynamicConfig into the Print / Filament / Printer tabs, thus modifying the active preset.
diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp
index a6d0749ab6..8c8b98090a 100644
--- a/src/slic3r/GUI/MainFrame.hpp
+++ b/src/slic3r/GUI/MainFrame.hpp
@@ -70,6 +70,8 @@ class MainFrame : public DPIFrame
bool can_export_supports() const;
bool can_export_gcode() const;
bool can_send_gcode() const;
+ bool can_export_gcode_sd() const;
+ bool can_eject() const;
bool can_slice() const;
bool can_change_view() const;
bool can_select() const;
@@ -98,6 +100,9 @@ public:
MainFrame();
~MainFrame() = default;
+ // Called when closing the application and when switching the application language.
+ void shutdown();
+
Plater* plater() { return m_plater; }
void update_title();
@@ -136,6 +141,10 @@ public:
wxNotebook* m_tabpanel { nullptr };
wxProgressDialog* m_progress_dialog { nullptr };
std::shared_ptr m_statusbar;
+
+#ifdef _WIN32
+ void* m_hDeviceNotify { nullptr };
+#endif // _WIN32
};
} // GUI
diff --git a/src/slic3r/GUI/Mouse3DController.cpp b/src/slic3r/GUI/Mouse3DController.cpp
index c925574170..75ec9c3bcb 100644
--- a/src/slic3r/GUI/Mouse3DController.cpp
+++ b/src/slic3r/GUI/Mouse3DController.cpp
@@ -53,205 +53,187 @@ static const std::vector _3DCONNEXION_DEVICES =
namespace Slic3r {
namespace GUI {
-
-const double Mouse3DController::State::DefaultTranslationScale = 2.5;
-const double Mouse3DController::State::MaxTranslationDeadzone = 0.2;
-const double Mouse3DController::State::DefaultTranslationDeadzone = 0.5 * Mouse3DController::State::MaxTranslationDeadzone;
-const float Mouse3DController::State::DefaultRotationScale = 1.0f;
-const float Mouse3DController::State::MaxRotationDeadzone = 0.2f;
-const float Mouse3DController::State::DefaultRotationDeadzone = 0.5f * Mouse3DController::State::MaxRotationDeadzone;
-const double Mouse3DController::State::DefaultZoomScale = 0.1;
-Mouse3DController::State::State()
- : m_buttons_enabled(false)
- , m_translation_params(DefaultTranslationScale, DefaultTranslationDeadzone)
- , m_rotation_params(DefaultRotationScale, DefaultRotationDeadzone)
- , m_zoom_params(DefaultZoomScale, 0.0)
- , m_mouse_wheel_counter(0)
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
- , m_translation_queue_max_size(0)
- , m_rotation_queue_max_size(0)
- , m_buttons_queue_max_size(0)
-#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+template
+void update_maximum(std::atomic& maximum_value, T const& value) noexcept
{
+ T prev_value = maximum_value;
+ while (prev_value < value && ! maximum_value.compare_exchange_weak(prev_value, value)) ;
}
+#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
-void Mouse3DController::State::append_translation(const Vec3d& translation)
+void Mouse3DController::State::append_translation(const Vec3d& translation, size_t input_queue_max_size)
{
- while (m_translation.queue.size() >= m_translation.max_size)
- {
- m_translation.queue.pop();
- }
- m_translation.queue.push(translation);
+ tbb::mutex::scoped_lock lock(m_input_queue_mutex);
+ while (m_input_queue.size() >= input_queue_max_size)
+ m_input_queue.pop_front();
+ m_input_queue.emplace_back(QueueItem::translation(translation));
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
- m_translation_queue_max_size = std::max(m_translation_queue_max_size, m_translation.queue.size());
+ update_maximum(input_queue_max_size_achieved, m_input_queue.size());
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
}
-void Mouse3DController::State::append_rotation(const Vec3f& rotation)
+void Mouse3DController::State::append_rotation(const Vec3f& rotation, size_t input_queue_max_size)
{
- while (m_rotation.queue.size() >= m_rotation.max_size)
- {
- m_rotation.queue.pop();
- }
- m_rotation.queue.push(rotation);
+ tbb::mutex::scoped_lock lock(m_input_queue_mutex);
+ while (m_input_queue.size() >= input_queue_max_size)
+ m_input_queue.pop_front();
+ m_input_queue.emplace_back(QueueItem::rotation(rotation.cast()));
+#ifdef WIN32
+ if (rotation.x() != 0.0f)
+ ++ m_mouse_wheel_counter;
+#endif // WIN32
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
- m_rotation_queue_max_size = std::max(m_rotation_queue_max_size, m_rotation.queue.size());
-#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
- if (rotation(0) != 0.0f)
- ++m_mouse_wheel_counter;
-}
-
-void Mouse3DController::State::append_button(unsigned int id)
-{
- if (!m_buttons_enabled)
- return;
-
- m_buttons.push(id);
-#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
- m_buttons_queue_max_size = std::max(m_buttons_queue_max_size, m_buttons.size());
+ update_maximum(input_queue_max_size_achieved, m_input_queue.size());
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
}
+void Mouse3DController::State::append_button(unsigned int id, size_t /* input_queue_max_size */)
+{
+ tbb::mutex::scoped_lock lock(m_input_queue_mutex);
+ m_input_queue.emplace_back(QueueItem::buttons(id));
+#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ update_maximum(input_queue_max_size_achieved, m_input_queue.size());
+#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+}
+
+#ifdef WIN32
+// Called by Win32 HID enumeration callback.
+void Mouse3DController::device_attached(const std::string &device)
+{
+ int vid = 0;
+ int pid = 0;
+ if (sscanf(device.c_str(), "\\\\?\\HID#VID_%x&PID_%x&", &vid, &pid) == 2) {
+// BOOST_LOG_TRIVIAL(trace) << boost::format("Mouse3DController::device_attached(VID_%04xxPID_%04x)") % vid % pid;
+// BOOST_LOG_TRIVIAL(trace) << "Mouse3DController::device_attached: " << device;
+ if (std::find(_3DCONNEXION_VENDORS.begin(), _3DCONNEXION_VENDORS.end(), vid) != _3DCONNEXION_VENDORS.end()) {
+ // Signal the worker thread to wake up and enumerate HID devices, if not connected at the moment.
+ // The message may come multiple times per each USB device. For example, some USB wireless dongles register as multiple HID sockets
+ // for multiple devices to connect to.
+ // Never mind, enumeration will be performed until connected.
+ m_wakeup = true;
+ m_stop_condition.notify_all();
+ }
+ }
+}
+
+// Filter out mouse scroll events produced by the 3DConnexion driver.
bool Mouse3DController::State::process_mouse_wheel()
{
- if (m_mouse_wheel_counter.load() == 0)
+ tbb::mutex::scoped_lock lock(m_input_queue_mutex);
+ if (m_mouse_wheel_counter == 0)
+ // No 3DConnexion rotation has been captured since the last mouse scroll event.
return false;
- else if (!m_rotation.queue.empty())
- {
- --m_mouse_wheel_counter;
+ if (std::find_if(m_input_queue.begin(), m_input_queue.end(), [](const QueueItem &item){ return item.is_rotation(); }) != m_input_queue.end()) {
+ // There is a rotation stored in the queue. Suppress one mouse scroll event.
+ -- m_mouse_wheel_counter;
return true;
}
-
- m_mouse_wheel_counter.store(0);
+ m_mouse_wheel_counter = 0;
return true;
}
+#endif // WIN32
-void Mouse3DController::State::set_queues_max_size(size_t size)
+bool Mouse3DController::State::apply(const Mouse3DController::Params ¶ms, Camera& camera)
{
- if (size > 0)
- {
- m_translation.max_size = size;
- m_rotation.max_size = size;
-
-#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
- m_translation_queue_max_size = 0;
- m_rotation_queue_max_size = 0;
- m_buttons_queue_max_size = 0;
-#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
- }
-}
-
-bool Mouse3DController::State::apply(Camera& camera)
-{
- if (!wxGetApp().IsActive())
+ if (! wxGetApp().IsActive())
return false;
- bool ret = false;
-
- if (has_translation())
+ std::deque input_queue;
{
- const Vec3d& translation = m_translation.queue.front();
- double zoom_factor = camera.min_zoom() / camera.get_zoom();
- camera.set_target(camera.get_target() + zoom_factor * m_translation_params.scale * (translation(0) * camera.get_dir_right() + translation(2) * camera.get_dir_up()));
- if (translation(1) != 0.0)
- camera.update_zoom(m_zoom_params.scale * translation(1) / std::abs(translation(1)));
- m_translation.queue.pop();
- ret = true;
+ // Atomically move m_input_queue to input_queue.
+ tbb::mutex::scoped_lock lock(m_input_queue_mutex);
+ input_queue = std::move(m_input_queue);
+ m_input_queue.clear();
}
- if (has_rotation())
- {
- Vec3d rot = (m_rotation_params.scale * m_rotation.queue.front()).cast() * (PI / 180.);
- camera.rotate_local_around_target(Vec3d(rot.x(), - rot.z(), rot.y()));
- m_rotation.queue.pop();
- ret = true;
+ for (const QueueItem &input_queue_item : input_queue) {
+ if (input_queue_item.is_translation()) {
+ Vec3d translation = params.swap_yz ? Vec3d(input_queue_item.vector.x(), - input_queue_item.vector.z(), input_queue_item.vector.y()) : input_queue_item.vector;
+ double zoom_factor = camera.min_zoom() / camera.get_zoom();
+ camera.set_target(camera.get_target() + zoom_factor * params.translation.scale * (translation.x() * camera.get_dir_right() + translation.z() * camera.get_dir_up()));
+ if (translation.y() != 0.0)
+ camera.update_zoom(params.zoom.scale * translation.y());
+ } else if (input_queue_item.is_rotation()) {
+ Vec3d rot = params.rotation.scale * input_queue_item.vector * (PI / 180.);
+ if (params.swap_yz)
+ rot = Vec3d(rot.x(), -rot.z(), rot.y());
+ camera.rotate_local_around_target(Vec3d(rot.x(), - rot.z(), rot.y()));
+ break;
+ } else {
+ assert(input_queue_item.is_buttons());
+ switch (input_queue_item.type_or_buttons) {
+ case 0: camera.update_zoom(1.0); break;
+ case 1: camera.update_zoom(-1.0); break;
+ default: break;
+ }
+ }
}
- if (m_buttons_enabled && has_button())
- {
- unsigned int button = m_buttons.front();
- switch (button)
- {
- case 0: { camera.update_zoom(1.0); break; }
- case 1: { camera.update_zoom(-1.0); break; }
- default: { break; }
- }
- m_buttons.pop();
- ret = true;
- }
-
- return ret;
+ return ! input_queue.empty();
}
-Mouse3DController::Mouse3DController()
- : m_initialized(false)
- , m_device(nullptr)
- , m_device_str("")
- , m_running(false)
- , m_show_settings_dialog(false)
- , m_mac_mouse_connected(false)
- , m_settings_dialog_closed_by_user(false)
-#if __APPLE__
- ,m_handler_mac(new Mouse3DHandlerMac(this))
-#endif //__APPLE__
+// Load the device parameter database from appconfig. To be called on application startup.
+void Mouse3DController::load_config(const AppConfig &appconfig)
{
- m_last_time = std::chrono::high_resolution_clock::now();
+ // We do not synchronize m_params_by_device with the background thread explicitely
+ // as there should be a full memory barrier executed once the background thread is started.
+ m_params_by_device.clear();
+
+ for (const std::string &device_name : appconfig.get_mouse_device_names()) {
+ double translation_speed = 4.0;
+ float rotation_speed = 4.0;
+ double translation_deadzone = Params::DefaultTranslationDeadzone;
+ float rotation_deadzone = Params::DefaultRotationDeadzone;
+ double zoom_speed = 2.0;
+ bool swap_yz = false;
+ appconfig.get_mouse_device_translation_speed(device_name, translation_speed);
+ appconfig.get_mouse_device_translation_deadzone(device_name, translation_deadzone);
+ appconfig.get_mouse_device_rotation_speed(device_name, rotation_speed);
+ appconfig.get_mouse_device_rotation_deadzone(device_name, rotation_deadzone);
+ appconfig.get_mouse_device_zoom_speed(device_name, zoom_speed);
+ appconfig.get_mouse_device_swap_yz(device_name, swap_yz);
+ // clamp to valid values
+ Params params;
+ params.translation.scale = Params::DefaultTranslationScale * std::clamp(translation_speed, 0.1, 10.0);
+ params.translation.deadzone = std::clamp(translation_deadzone, 0.0, Params::MaxTranslationDeadzone);
+ params.rotation.scale = Params::DefaultRotationScale * std::clamp(rotation_speed, 0.1f, 10.0f);
+ params.rotation.deadzone = std::clamp(rotation_deadzone, 0.0f, Params::MaxRotationDeadzone);
+ params.zoom.scale = Params::DefaultZoomScale * std::clamp(zoom_speed, 0.1, 10.0);
+ params.swap_yz = swap_yz;
+ m_params_by_device[device_name] = std::move(params);
+ }
}
-void Mouse3DController::init()
+// Store the device parameter database back to appconfig. To be called on application closeup.
+void Mouse3DController::save_config(AppConfig &appconfig) const
{
- if (m_initialized)
- return;
-
- // Initialize the hidapi library
- int res = hid_init();
- if (res != 0)
- {
- BOOST_LOG_TRIVIAL(error) << "Unable to initialize hidapi library";
- return;
+ // We do not synchronize m_params_by_device with the background thread explicitely
+ // as there should be a full memory barrier executed once the background thread is stopped.
+ for (const std::pair &key_value_pair : m_params_by_device) {
+ const std::string &device_name = key_value_pair.first;
+ const Params ¶ms = key_value_pair.second;
+ // Store current device parameters into the config
+ appconfig.set_mouse_device(device_name, params.translation.scale / Params::DefaultTranslationScale, params.translation.deadzone,
+ params.rotation.scale / Params::DefaultRotationScale, params.rotation.deadzone, params.zoom.scale / Params::DefaultZoomScale, params.swap_yz);
}
-
- m_initialized = true;
-}
-
-void Mouse3DController::shutdown()
-{
- if (!m_initialized)
- return;
-
- stop();
- disconnect_device();
-
- // Finalize the hidapi library
- hid_exit();
- m_initialized = false;
}
bool Mouse3DController::apply(Camera& camera)
{
- if (!m_initialized)
- return false;
-
// check if the user unplugged the device
- if (!is_running() && is_device_connected())
- {
- disconnect_device();
+ if (! m_connected) {
// hides the settings dialog if the user un-plug the device
m_show_settings_dialog = false;
m_settings_dialog_closed_by_user = false;
}
-
- // check if the user plugged the device
- if (connect_device())
- start();
-
- return is_device_connected() ? m_state.apply(camera) : false;
+ return m_state.apply(m_params, camera);
}
void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const
{
- if (!is_running() || !m_show_settings_dialog)
+ if (! m_show_settings_dialog || ! m_connected)
return;
// when the user clicks on [X] or [Close] button we need to trigger
@@ -264,6 +246,13 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const
return;
}
+ Params params_copy;
+ bool params_changed = false;
+ {
+ tbb::mutex::scoped_lock lock(m_params_ui_mutex);
+ params_copy = m_params_ui;
+ }
+
Size cnv_size = canvas.get_canvas_size();
ImGuiWrapper& imgui = *wxGetApp().imgui();
@@ -296,30 +285,51 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const
imgui.text(_(L("Speed:")));
ImGui::PopStyleColor();
- float translation_scale = (float)m_state.get_translation_scale() / State::DefaultTranslationScale;
- if (imgui.slider_float(_(L("Translation")) + "##1", &translation_scale, 0.1f, 10.0f, "%.1f"))
- m_state.set_translation_scale(State::DefaultTranslationScale * (double)translation_scale);
+ float translation_scale = (float)params_copy.translation.scale / Params::DefaultTranslationScale;
+ if (imgui.slider_float(_(L("Translation")) + "##1", &translation_scale, 0.1f, 10.0f, "%.1f")) {
+ params_copy.translation.scale = Params::DefaultTranslationScale * (double)translation_scale;
+ params_changed = true;
+ }
- float rotation_scale = m_state.get_rotation_scale() / State::DefaultRotationScale;
- if (imgui.slider_float(_(L("Rotation")) + "##1", &rotation_scale, 0.1f, 10.0f, "%.1f"))
- m_state.set_rotation_scale(State::DefaultRotationScale * rotation_scale);
+ float rotation_scale = params_copy.rotation.scale / Params::DefaultRotationScale;
+ if (imgui.slider_float(_(L("Rotation")) + "##1", &rotation_scale, 0.1f, 10.0f, "%.1f")) {
+ params_copy.rotation.scale = Params::DefaultRotationScale * rotation_scale;
+ params_changed = true;
+ }
- float zoom_scale = m_state.get_zoom_scale() / State::DefaultZoomScale;
- if (imgui.slider_float(_(L("Zoom")), &zoom_scale, 0.1f, 10.0f, "%.1f"))
- m_state.set_zoom_scale(State::DefaultZoomScale * zoom_scale);
+ float zoom_scale = params_copy.zoom.scale / Params::DefaultZoomScale;
+ if (imgui.slider_float(_(L("Zoom")), &zoom_scale, 0.1f, 10.0f, "%.1f")) {
+ params_copy.zoom.scale = Params::DefaultZoomScale * zoom_scale;
+ params_changed = true;
+ }
ImGui::Separator();
ImGui::PushStyleColor(ImGuiCol_Text, color);
imgui.text(_(L("Deadzone:")));
ImGui::PopStyleColor();
- float translation_deadzone = (float)m_state.get_translation_deadzone();
- if (imgui.slider_float(_(L("Translation")) + "/" + _(L("Zoom")), &translation_deadzone, 0.0f, (float)State::MaxTranslationDeadzone, "%.2f"))
- m_state.set_translation_deadzone((double)translation_deadzone);
+ float translation_deadzone = (float)params_copy.translation.deadzone;
+ if (imgui.slider_float(_(L("Translation")) + "/" + _(L("Zoom")), &translation_deadzone, 0.0f, (float)Params::MaxTranslationDeadzone, "%.2f")) {
+ params_copy.translation.deadzone = (double)translation_deadzone;
+ params_changed = true;
+ }
- float rotation_deadzone = m_state.get_rotation_deadzone();
- if (imgui.slider_float(_(L("Rotation")) + "##2", &rotation_deadzone, 0.0f, State::MaxRotationDeadzone, "%.2f"))
- m_state.set_rotation_deadzone(rotation_deadzone);
+ float rotation_deadzone = params_copy.rotation.deadzone;
+ if (imgui.slider_float(_(L("Rotation")) + "##2", &rotation_deadzone, 0.0f, Params::MaxRotationDeadzone, "%.2f")) {
+ params_copy.rotation.deadzone = rotation_deadzone;
+ params_changed = true;
+ }
+
+ ImGui::Separator();
+ ImGui::PushStyleColor(ImGuiCol_Text, color);
+ imgui.text(_(L("Options:")));
+ ImGui::PopStyleColor();
+
+ bool swap_yz = params_copy.swap_yz;
+ if (imgui.checkbox("Swap Y/Z axes", swap_yz)) {
+ params_copy.swap_yz = swap_yz;
+ params_changed = true;
+ }
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
ImGui::Separator();
@@ -328,8 +338,8 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const
imgui.text("DEBUG:");
imgui.text("Vectors:");
ImGui::PopStyleColor();
- Vec3f translation = m_state.get_translation().cast();
- Vec3f rotation = m_state.get_rotation();
+ Vec3f translation = m_state.get_first_vector_of_type(State::QueueItem::TranslationType).cast();
+ Vec3f rotation = m_state.get_first_vector_of_type(State::QueueItem::RotationType).cast();
ImGui::InputFloat3("Translation##3", translation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly);
ImGui::InputFloat3("Rotation##3", rotation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly);
@@ -337,19 +347,16 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const
imgui.text("Queue size:");
ImGui::PopStyleColor();
- int translation_size[2] = { (int)m_state.get_translation_queue_size(), (int)m_state.get_translation_queue_max_size() };
- int rotation_size[2] = { (int)m_state.get_rotation_queue_size(), (int)m_state.get_rotation_queue_max_size() };
- int buttons_size[2] = { (int)m_state.get_buttons_queue_size(), (int)m_state.get_buttons_queue_max_size() };
+ int input_queue_size_current[2] = { int(m_state.input_queue_size_current()), int(m_state.input_queue_max_size_achieved) };
+ ImGui::InputInt2("Current##4", input_queue_size_current, ImGuiInputTextFlags_ReadOnly);
- ImGui::InputInt2("Translation##4", translation_size, ImGuiInputTextFlags_ReadOnly);
- ImGui::InputInt2("Rotation##4", rotation_size, ImGuiInputTextFlags_ReadOnly);
- ImGui::InputInt2("Buttons", buttons_size, ImGuiInputTextFlags_ReadOnly);
-
- int queue_size = (int)m_state.get_queues_max_size();
- if (ImGui::InputInt("Max size", &queue_size, 1, 1, ImGuiInputTextFlags_ReadOnly))
+ int input_queue_size_param = int(params_copy.input_queue_max_size);
+ if (ImGui::InputInt("Max size", &input_queue_size_param, 1, 1, ImGuiInputTextFlags_ReadOnly))
{
- if (queue_size > 0)
- m_state.set_queues_max_size(queue_size);
+ if (input_queue_size_param > 0) {
+ params_copy.input_queue_max_size = input_queue_size_param;
+ params_changed = true;
+ }
}
ImGui::Separator();
@@ -377,32 +384,208 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const
}
imgui.end();
+
+ if (params_changed) {
+ // Synchronize front end parameters to back end.
+ tbb::mutex::scoped_lock lock(m_params_ui_mutex);
+ auto pthis = const_cast(this);
+#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ if (params_copy.input_queue_max_size != params_copy.input_queue_max_size)
+ // Reset the statistics counter.
+ m_state.input_queue_max_size_achieved = 0;
+#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ pthis->m_params_ui = params_copy;
+ pthis->m_params_ui_changed = true;
+ }
+}
+
+#if __APPLE__
+
+void Mouse3DController::connected(std::string device_name)
+{
+ assert(! m_connected);
+ assert(m_device_str.empty());
+ m_device_str = device_name;
+ // Copy the parameters for m_device_str into the current parameters.
+ if (auto it_params = m_params_by_device.find(m_device_str); it_params != m_params_by_device.end()) {
+ tbb::mutex::scoped_lock lock(m_params_ui_mutex);
+ m_params = m_params_ui = it_params->second;
+ }
+ m_connected = true;
+}
+
+void Mouse3DController::disconnected()
+{
+ // Copy the current parameters for m_device_str into the parameter database.
+ assert(m_connected == ! m_device_str.empty());
+ if (m_connected) {
+ tbb::mutex::scoped_lock lock(m_params_ui_mutex);
+ m_params_by_device[m_device_str] = m_params_ui;
+ m_device_str.clear();
+ m_connected = false;
+ wxGetApp().plater()->CallAfter([]() {
+ Plater *plater = wxGetApp().plater();
+ if (plater != nullptr) {
+ plater->get_camera().recover_from_free_camera();
+ plater->set_current_canvas_as_dirty();
+ }
+ });
+ }
+}
+
+bool Mouse3DController::handle_input(const DataPacketAxis& packet)
+{
+ if (! wxGetApp().IsActive())
+ return false;
+
+ {
+ // Synchronize parameters between the UI thread and the background thread.
+ //FIXME is this necessary on OSX? Are these notifications triggered from the main thread or from a worker thread?
+ tbb::mutex::scoped_lock lock(m_params_ui_mutex);
+ if (m_params_ui_changed) {
+ m_params = m_params_ui;
+ m_params_ui_changed = false;
+ }
+ }
+
+ bool updated = false;
+ // translation
+ double deadzone = m_params.translation.deadzone;
+ Vec3d translation(std::abs(packet[0]) > deadzone ? -packet[0] : 0.0,
+ std::abs(packet[1]) > deadzone ? packet[1] : 0.0,
+ std::abs(packet[2]) > deadzone ? packet[2] : 0.0);
+ if (! translation.isApprox(Vec3d::Zero())) {
+ m_state.append_translation(translation, m_params.input_queue_max_size);
+ updated = true;
+ }
+ // rotation
+ deadzone = m_params.rotation.deadzone;
+ Vec3f rotation(std::abs(packet[3]) > deadzone ? (float)packet[3] : 0.0,
+ std::abs(packet[4]) > deadzone ? (float)packet[4] : 0.0,
+ std::abs(packet[5]) > deadzone ? (float)packet[5] : 0.0);
+ if (! rotation.isApprox(Vec3f::Zero())) {
+ m_state.append_rotation(rotation, m_params.input_queue_max_size);
+ updated = true;
+ }
+
+ if (updated) {
+ wxGetApp().plater()->set_current_canvas_as_dirty();
+ // ask for an idle event to update 3D scene
+ wxWakeUpIdle();
+ }
+ return updated;
+}
+
+#else //__APPLE__
+
+// Initialize the application.
+void Mouse3DController::init()
+{
+ assert(! m_thread.joinable());
+ if (! m_thread.joinable()) {
+ m_stop = false;
+ m_thread = std::thread(&Mouse3DController::run, this);
+ }
+}
+
+// Closing the application.
+void Mouse3DController::shutdown()
+{
+ if (m_thread.joinable()) {
+ // Stop the worker thread, if running.
+ {
+ // Notify the worker thread to cancel wait on detection polling.
+ std::lock_guard lock(m_stop_condition_mutex);
+ m_stop = true;
+ }
+ m_stop_condition.notify_all();
+ // Wait for the worker thread to stop.
+ m_thread.join();
+ m_stop = false;
+ }
+}
+
+// Main routine of the worker thread.
+void Mouse3DController::run()
+{
+ // Initialize the hidapi library
+ int res = hid_init();
+ if (res != 0) {
+ // Give up.
+#if defined(__unix__) || defined(__unix) || defined(unix)
+ if (res == -1)
+ // Hopefully this error code comes from our bundled patched hidapi. In that case, -1 is returned by hid_wrapper_udev_init() and it mean
+ BOOST_LOG_TRIVIAL(error) << "Unable to initialize hidapi library: failed to load libudev.so.1 or libudev.so.0";
+ else if (res == -2)
+ // Hopefully this error code comes from our bundled patched hidapi. In that case, -2 is returned by hid_wrapper_udev_init() and it mean
+ BOOST_LOG_TRIVIAL(error) << "Unable to initialize hidapi library: failed to resolve some function from libudev.so.1 or libudev.so.0";
+ else
+#endif // unixes
+ BOOST_LOG_TRIVIAL(error) << "Unable to initialize hidapi library";
+ return;
+ }
+
+#ifdef _WIN32
+ // Enumerate once just after thread start.
+ m_wakeup = true;
+#endif // _WIN32
+
+ for (;;) {
+ {
+ tbb::mutex::scoped_lock lock(m_params_ui_mutex);
+ if (m_stop)
+ break;
+ if (m_params_ui_changed) {
+ m_params = m_params_ui;
+ m_params_ui_changed = false;
+ }
+ }
+ if (m_device == nullptr)
+ // Polls the HID devices, blocks for maximum 2 seconds.
+ m_connected = this->connect_device();
+ else
+ // Waits for 3DConnexion mouse input for maximum 100ms, then repeats.
+ this->collect_input();
+ }
+
+ this->disconnect_device();
+
+ // Finalize the hidapi library
+ hid_exit();
}
bool Mouse3DController::connect_device()
{
-#ifdef __APPLE__
- return false;
-#endif//__APPLE__
- static const long long DETECTION_TIME_MS = 2000; // two seconds
+ if (m_stop)
+ return false;
- if (is_device_connected())
- return false;
+ {
+ // Wait for 2 seconds, but cancellable by m_stop.
+ std::unique_lock lock(m_stop_condition_mutex);
+#ifdef _WIN32
+ // Wait indifinetely for the stop signal.
+ m_stop_condition.wait(lock, [this]{ return m_stop || m_wakeup; });
+ m_wakeup = false;
+#else
+ m_stop_condition.wait_for(lock, std::chrono::seconds(2), [this]{ return m_stop; });
+#endif
+ }
- // check time since last detection took place
- if (std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - m_last_time).count() < DETECTION_TIME_MS)
- return false;
-
- m_last_time = std::chrono::high_resolution_clock::now();
+ if (m_stop)
+ return false;
// Enumerates devices
hid_device_info* devices = hid_enumerate(0, 0);
if (devices == nullptr)
{
- BOOST_LOG_TRIVIAL(error) << "Unable to enumerate HID devices";
+ BOOST_LOG_TRIVIAL(trace) << "Mouse3DController::connect_device() - no HID device enumerated.";
return false;
}
+#ifdef _WIN32
+ BOOST_LOG_TRIVIAL(trace) << "Mouse3DController::connect_device() - enumerating HID devices.";
+#endif // _WIN32
+
// Searches for 1st connected 3Dconnexion device
struct DeviceData
{
@@ -623,23 +806,11 @@ bool Mouse3DController::connect_device()
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
std::cout << "Opened device." << std::endl;
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
- // get device parameters from the config, if present
- double translation_speed = 4.0;
- float rotation_speed = 4.0;
- double translation_deadzone = State::DefaultTranslationDeadzone;
- float rotation_deadzone = State::DefaultRotationDeadzone;
- double zoom_speed = 2.0;
- wxGetApp().app_config->get_mouse_device_translation_speed(m_device_str, translation_speed);
- wxGetApp().app_config->get_mouse_device_translation_deadzone(m_device_str, translation_deadzone);
- wxGetApp().app_config->get_mouse_device_rotation_speed(m_device_str, rotation_speed);
- wxGetApp().app_config->get_mouse_device_rotation_deadzone(m_device_str, rotation_deadzone);
- wxGetApp().app_config->get_mouse_device_zoom_speed(m_device_str, zoom_speed);
- // clamp to valid values
- m_state.set_translation_scale(State::DefaultTranslationScale * std::clamp(translation_speed, 0.1, 10.0));
- m_state.set_translation_deadzone(std::clamp(translation_deadzone, 0.0, State::MaxTranslationDeadzone));
- m_state.set_rotation_scale(State::DefaultRotationScale * std::clamp(rotation_speed, 0.1f, 10.0f));
- m_state.set_rotation_deadzone(std::clamp(rotation_deadzone, 0.0f, State::MaxRotationDeadzone));
- m_state.set_zoom_scale(State::DefaultZoomScale * std::clamp(zoom_speed, 0.1, 10.0));
+ // Copy the parameters for m_device_str into the current parameters.
+ if (auto it_params = m_params_by_device.find(m_device_str); it_params != m_params_by_device.end()) {
+ tbb::mutex::scoped_lock lock(m_params_ui_mutex);
+ m_params = m_params_ui = it_params->second;
+ }
}
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
else
@@ -657,138 +828,96 @@ bool Mouse3DController::connect_device()
void Mouse3DController::disconnect_device()
{
- if (!is_device_connected())
- return;
-
- // Stop the secondary thread, if running
- if (m_thread.joinable())
- m_thread.join();
-
- // Store current device parameters into the config
- wxGetApp().app_config->set_mouse_device(m_device_str, m_state.get_translation_scale() / State::DefaultTranslationScale, m_state.get_translation_deadzone(),
- m_state.get_rotation_scale() / State::DefaultRotationScale, m_state.get_rotation_deadzone(), m_state.get_zoom_scale() / State::DefaultZoomScale);
-
- wxGetApp().app_config->save();
-
- // Close the 3Dconnexion device
- hid_close(m_device);
- m_device = nullptr;
-
- BOOST_LOG_TRIVIAL(info) << "Disconnected device: " << m_device_str;
-
- m_device_str = "";
-}
-
-void Mouse3DController::start()
-{
- if (!is_device_connected() || m_running)
- return;
-
- m_thread = std::thread(&Mouse3DController::run, this);
-}
-
-void Mouse3DController::run()
-{
- m_running = true;
- while (m_running)
- {
- collect_input();
+ if (m_device) {
+ hid_close(m_device);
+ m_device = nullptr;
+ BOOST_LOG_TRIVIAL(info) << "Disconnected device: " << m_device_str;
+ // Copy the current parameters for m_device_str into the parameter database.
+ {
+ tbb::mutex::scoped_lock lock(m_params_ui_mutex);
+ m_params_by_device[m_device_str] = m_params_ui;
+ }
+ m_device_str.clear();
+ m_connected = false;
+#ifdef _WIN32
+ // Enumerate once immediately after disconnect.
+ m_wakeup = true;
+#endif // _WIN32
+ wxGetApp().plater()->CallAfter([]() {
+ Plater *plater = wxGetApp().plater();
+ if (plater != nullptr) {
+ plater->get_camera().recover_from_free_camera();
+ plater->set_current_canvas_as_dirty();
+ }
+ });
}
}
+
void Mouse3DController::collect_input()
{
DataPacketRaw packet = { 0 };
+ // Read packet, block maximum 100 ms. That means when closing the application, closing the application will be delayed by 100 ms.
int res = hid_read_timeout(m_device, packet.data(), packet.size(), 100);
- if (res < 0)
- {
- // An error occourred (device detached from pc ?)
- stop();
- return;
- }
- handle_input(packet, res);
+ if (res < 0) {
+ // An error occourred (device detached from pc ?). Close the 3Dconnexion device.
+ this->disconnect_device();
+ } else
+ this->handle_input(packet, res, m_params, m_state);
}
-
-void Mouse3DController::handle_input_axis(const DataPacketAxis& packet)
+
+// Unpack raw 3DConnexion HID packet of a wired 3D mouse into m_state. Called by the worker thread.
+bool Mouse3DController::handle_input(const DataPacketRaw& packet, const int packet_lenght, const Params ¶ms, State &state_in_out)
{
- if (!wxGetApp().IsActive())
- return;
- bool appended = false;
- //translation
- double deadzone = m_state.get_translation_deadzone();
- Vec3d translation(std::abs(packet[0]) > deadzone ? -packet[0] : 0.0,
- std::abs(packet[1]) > deadzone ? packet[1] : 0.0,
- std::abs(packet[2]) > deadzone ? packet[2] : 0.0);
- if (!translation.isApprox(Vec3d::Zero()))
- {
- m_state.append_translation(translation);
- appended = true;
- }
- //rotation
- deadzone = m_state.get_rotation_deadzone();
- Vec3f rotation(std::abs(packet[3]) > deadzone ? (float)packet[3] : 0.0,
- std::abs(packet[4]) > deadzone ? (float)packet[4] : 0.0,
- std::abs(packet[5]) > deadzone ? (float)packet[5] : 0.0);
- if (!rotation.isApprox(Vec3f::Zero()))
- {
- m_state.append_rotation(rotation);
- appended = true;
- }
- if (appended)
- {
- wxGetApp().plater()->set_current_canvas_as_dirty();
- // ask for an idle event to update 3D scene
- wxWakeUpIdle();
- }
-}
-void Mouse3DController::handle_input(const DataPacketRaw& packet, const int packet_lenght)
-{
- if (!wxGetApp().IsActive())
- return;
+ if (! wxGetApp().IsActive())
+ return false;
int res = packet_lenght;
bool updated = false;
if (res == 7)
- updated = handle_packet(packet);
+ updated = handle_packet(packet, params, state_in_out);
else if (res == 13)
- updated = handle_wireless_packet(packet);
+ updated = handle_wireless_packet(packet, params, state_in_out);
else if ((res == 3) && (packet[0] == 3))
// On Mac button packets can be 3 bytes long
- updated = handle_packet(packet);
+ updated = handle_packet(packet, params, state_in_out);
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
else if (res > 0)
std::cout << "Got unknown data packet of length: " << res << ", code:" << (int)packet[0] << std::endl;
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
- if (updated)
- {
+#if 1
+ if (updated) {
wxGetApp().plater()->set_current_canvas_as_dirty();
// ask for an idle event to update 3D scene
wxWakeUpIdle();
}
+#endif
+ return updated;
}
-bool Mouse3DController::handle_packet(const DataPacketRaw& packet)
+// Unpack raw 3DConnexion HID packet of a wired 3D mouse into m_state. Called by handle_input() from the worker thread.
+bool Mouse3DController::handle_packet(const DataPacketRaw& packet, const Params ¶ms, State &state_in_out)
{
switch (packet[0])
{
case 1: // Translation
{
- if (handle_packet_translation(packet))
+ if (handle_packet_translation(packet, params, state_in_out))
return true;
break;
}
case 2: // Rotation
{
- if (handle_packet_rotation(packet, 1))
+ if (handle_packet_rotation(packet, 1, params, state_in_out))
return true;
break;
}
case 3: // Button
{
- if (handle_packet_button(packet, packet.size() - 1))
+ if (params.buttons_enabled && handle_packet_button(packet, packet.size() - 1, params, state_in_out))
return true;
break;
@@ -796,14 +925,14 @@ bool Mouse3DController::handle_packet(const DataPacketRaw& packet)
case 23: // Battery charge
{
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
- std::cout << m_device_str << " - battery level: " << (int)packet[1] << " percent" << std::endl;
+ std::cout << "3DConnexion - battery level: " << (int)packet[1] << " percent" << std::endl;
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
break;
}
default:
{
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
- std::cout << "Got unknown data packet of code: " << (int)packet[0] << std::endl;
+ std::cout << "3DConnexion - Got unknown data packet of code: " << (int)packet[0] << std::endl;
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
break;
}
@@ -812,14 +941,15 @@ bool Mouse3DController::handle_packet(const DataPacketRaw& packet)
return false;
}
-bool Mouse3DController::handle_wireless_packet(const DataPacketRaw& packet)
+// Unpack raw 3DConnexion HID packet of a wireless 3D mouse into m_state. Called by handle_input() from the worker thread.
+bool Mouse3DController::handle_wireless_packet(const DataPacketRaw& packet, const Params ¶ms, State &state_in_out)
{
switch (packet[0])
{
case 1: // Translation + Rotation
{
- bool updated = handle_packet_translation(packet);
- updated |= handle_packet_rotation(packet, 7);
+ bool updated = handle_packet_translation(packet, params, state_in_out);
+ updated |= handle_packet_rotation(packet, 7, params, state_in_out);
if (updated)
return true;
@@ -828,7 +958,7 @@ bool Mouse3DController::handle_wireless_packet(const DataPacketRaw& packet)
}
case 3: // Button
{
- if (handle_packet_button(packet, 12))
+ if (params.buttons_enabled && handle_packet_button(packet, 12, params, state_in_out))
return true;
break;
@@ -836,14 +966,14 @@ bool Mouse3DController::handle_wireless_packet(const DataPacketRaw& packet)
case 23: // Battery charge
{
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
- std::cout << m_device_str << " - battery level: " << (int)packet[1] << " percent" << std::endl;
+ std::cout << "3DConnexion - battery level: " << (int)packet[1] << " percent" << std::endl;
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
break;
}
default:
{
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
- std::cout << "Got unknown data packet of code: " << (int)packet[0] << std::endl;
+ std::cout << "3DConnexion - Got unknown data packet of code: " << (int)packet[0] << std::endl;
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
break;
}
@@ -852,46 +982,52 @@ bool Mouse3DController::handle_wireless_packet(const DataPacketRaw& packet)
return false;
}
-double convert_input(unsigned char first, unsigned char second, double deadzone)
+// Convert a signed 16bit word from a 3DConnexion mouse HID packet into a double coordinate, apply a dead zone.
+static double convert_input(int coord_byte_low, int coord_byte_high, double deadzone)
{
- short value = first | second << 8;
+ int value = coord_byte_low | (coord_byte_high << 8);
+ if (value >= 32768)
+ value = value - 65536;
double ret = (double)value / 350.0;
return (std::abs(ret) > deadzone) ? ret : 0.0;
}
-bool Mouse3DController::handle_packet_translation(const DataPacketRaw& packet)
+// Unpack raw 3DConnexion HID packet, decode state of translation axes into state_in_out. Called by handle_input() from the worker thread.
+bool Mouse3DController::handle_packet_translation(const DataPacketRaw& packet, const Params ¶ms, State &state_in_out)
{
- double deadzone = m_state.get_translation_deadzone();
+ double deadzone = params.translation.deadzone;
Vec3d translation(-convert_input(packet[1], packet[2], deadzone),
convert_input(packet[3], packet[4], deadzone),
convert_input(packet[5], packet[6], deadzone));
if (!translation.isApprox(Vec3d::Zero()))
{
- m_state.append_translation(translation);
+ state_in_out.append_translation(translation, params.input_queue_max_size);
return true;
}
return false;
}
-bool Mouse3DController::handle_packet_rotation(const DataPacketRaw& packet, unsigned int first_byte)
+// Unpack raw 3DConnexion HID packet, decode state of rotation axes into state_in_out. Called by the handle_input() from worker thread.
+bool Mouse3DController::handle_packet_rotation(const DataPacketRaw& packet, unsigned int first_byte, const Params ¶ms, State &state_in_out)
{
- double deadzone = (double)m_state.get_rotation_deadzone();
+ double deadzone = (double)params.rotation.deadzone;
Vec3f rotation((float)convert_input(packet[first_byte + 0], packet[first_byte + 1], deadzone),
(float)convert_input(packet[first_byte + 2], packet[first_byte + 3], deadzone),
(float)convert_input(packet[first_byte + 4], packet[first_byte + 5], deadzone));
if (!rotation.isApprox(Vec3f::Zero()))
{
- m_state.append_rotation(rotation);
+ state_in_out.append_rotation(rotation, params.input_queue_max_size);
return true;
}
return false;
}
-bool Mouse3DController::handle_packet_button(const DataPacketRaw& packet, unsigned int packet_size)
+// Unpack raw 3DConnexion HID packet, decode button state into state_in_out. Called by handle_input() from the worker thread.
+bool Mouse3DController::handle_packet_button(const DataPacketRaw& packet, unsigned int packet_size, const Params ¶ms, State &state_in_out)
{
unsigned int data = 0;
for (unsigned int i = 1; i < packet_size; ++i)
@@ -904,7 +1040,7 @@ bool Mouse3DController::handle_packet_button(const DataPacketRaw& packet, unsign
{
if (data_bits.test(i))
{
- m_state.append_button((unsigned int)i);
+ state_in_out.append_button((unsigned int)i, params.input_queue_max_size);
return true;
}
}
@@ -912,5 +1048,7 @@ bool Mouse3DController::handle_packet_button(const DataPacketRaw& packet, unsign
return false;
}
+#endif //__APPLE__
+
} // namespace GUI
} // namespace Slic3r
diff --git a/src/slic3r/GUI/Mouse3DController.hpp b/src/slic3r/GUI/Mouse3DController.hpp
index f987451245..eb1425b88c 100644
--- a/src/slic3r/GUI/Mouse3DController.hpp
+++ b/src/slic3r/GUI/Mouse3DController.hpp
@@ -10,67 +10,83 @@
#include
#include
-#include
#include
#include
+#include
-
+#include
namespace Slic3r {
+
+class AppConfig;
+
namespace GUI {
-#if __APPLE__
- class Mouse3DHandlerMac;
-#endif//__APPLE__
-
struct Camera;
class GLCanvas3D;
class Mouse3DController
{
- class State
- {
- public:
- static const double DefaultTranslationScale;
- static const double MaxTranslationDeadzone;
- static const double DefaultTranslationDeadzone;
- static const float DefaultRotationScale;
- static const float MaxRotationDeadzone;
- static const float DefaultRotationDeadzone;
- static const double DefaultZoomScale;
+ // Parameters, which are configured by the ImGUI dialog when pressing Ctrl+M.
+ // The UI thread modifies a copy of the parameters and indicates to the background thread that there was a change
+ // to copy the parameters.
+ struct Params
+ {
+ static constexpr double DefaultTranslationScale = 2.5;
+ static constexpr double MaxTranslationDeadzone = 0.2;
+ static constexpr double DefaultTranslationDeadzone = 0.5 * MaxTranslationDeadzone;
+ static constexpr float DefaultRotationScale = 1.0f;
+ static constexpr float MaxRotationDeadzone = 0.2f;
+ static constexpr float DefaultRotationDeadzone = 0.5f * MaxRotationDeadzone;
+ static constexpr double DefaultZoomScale = 0.1;
- private:
template
struct CustomParameters
{
Number scale;
Number deadzone;
-
- CustomParameters(Number scale, Number deadzone) : scale(scale), deadzone(deadzone) {}
};
- template
- struct InputQueue
- {
- size_t max_size;
- std::queue queue;
+ CustomParameters translation { DefaultTranslationScale, DefaultTranslationDeadzone };
+ CustomParameters rotation { DefaultRotationScale, DefaultRotationDeadzone };
+ CustomParameters zoom { DefaultZoomScale, 0.0 };
+ // Do not process button presses from 3DConnexion device, let the user map the 3DConnexion keys in 3DConnexion driver.
+ bool buttons_enabled { false };
+ // The default value of 15 for max_size seems to work fine on all platforms
+ // The effects of changing this value can be tested by setting ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT to 1
+ // and playing with the imgui dialog which shows by pressing CTRL+M
+ size_t input_queue_max_size { 15 };
+ // Whether to swap Y/Z axes or not.
+ bool swap_yz{ false };
+ };
- // The default value of 5 for max_size seems to work fine on all platforms
- // The effects of changing this value can be tested by setting ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT to 1
- // and playing with the imgui dialog which shows by pressing CTRL+M
- InputQueue() : max_size(5) {}
+ // Queue of the 3DConnexion input events (translations, rotations, button presses).
+ class State
+ {
+ public:
+ struct QueueItem {
+ static QueueItem translation(const Vec3d &translation) { QueueItem out; out.vector = translation; out.type_or_buttons = TranslationType; return out; }
+ static QueueItem rotation(const Vec3d &rotation) { QueueItem out; out.vector = rotation; out.type_or_buttons = RotationType; return out; }
+ static QueueItem buttons(unsigned int buttons) { QueueItem out; out.type_or_buttons = buttons; return out; }
+
+ bool is_translation() const { return this->type_or_buttons == TranslationType; }
+ bool is_rotation() const { return this->type_or_buttons == RotationType; }
+ bool is_buttons() const { return ! this->is_translation() && ! this->is_rotation(); }
+
+ Vec3d vector;
+ unsigned int type_or_buttons;
+
+ static constexpr unsigned int TranslationType = std::numeric_limits::max();
+ static constexpr unsigned int RotationType = TranslationType - 1;
};
- InputQueue m_translation;
- InputQueue m_rotation;
- std::queue m_buttons;
-
- bool m_buttons_enabled;
-
- CustomParameters m_translation_params;
- CustomParameters m_rotation_params;
- CustomParameters m_zoom_params;
+ private:
+ // m_input_queue is accessed by the background thread and by the UI thread. Access to m_input_queue
+ // is guarded with m_input_queue_mutex.
+ std::deque m_input_queue;
+ mutable tbb::mutex m_input_queue_mutex;
+#ifdef WIN32
// When the 3Dconnexion driver is running the system gets, by default, mouse wheel events when rotations around the X axis are detected.
// We want to filter these out because we are getting the data directly from the device, bypassing the driver, and those mouse wheel events interfere
// by triggering unwanted zoom in/out of the scene
@@ -78,131 +94,142 @@ class Mouse3DController
// Mouse3DController::collect_input() through the call to the append_rotation() method
// GLCanvas3D::on_mouse_wheel() through the call to the process_mouse_wheel() method
// GLCanvas3D::on_idle() through the call to the apply() method
- std::atomic m_mouse_wheel_counter;
-
-#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
- size_t m_translation_queue_max_size;
- size_t m_rotation_queue_max_size;
- size_t m_buttons_queue_max_size;
-#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ unsigned int m_mouse_wheel_counter { 0 };
+#endif /* WIN32 */
public:
- State();
-
- void append_translation(const Vec3d& translation);
- void append_rotation(const Vec3f& rotation);
- void append_button(unsigned int id);
-
- bool has_translation() const { return !m_translation.queue.empty(); }
- bool has_rotation() const { return !m_rotation.queue.empty(); }
- bool has_button() const { return !m_buttons.empty(); }
+ // Called by the background thread or by by Mouse3DHandlerMac.mm when a new event is received from 3DConnexion device.
+ void append_translation(const Vec3d& translation, size_t input_queue_max_size);
+ void append_rotation(const Vec3f& rotation, size_t input_queue_max_size);
+ void append_button(unsigned int id, size_t input_queue_max_size);
+#ifdef WIN32
+ // Called by GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
+ // to filter out spurious mouse scroll events produced by the 3DConnexion driver on Windows.
bool process_mouse_wheel();
-
- double get_translation_scale() const { return m_translation_params.scale; }
- void set_translation_scale(double scale) { m_translation_params.scale = scale; }
-
- float get_rotation_scale() const { return m_rotation_params.scale; }
- void set_rotation_scale(float scale) { m_rotation_params.scale = scale; }
-
- double get_zoom_scale() const { return m_zoom_params.scale; }
- void set_zoom_scale(double scale) { m_zoom_params.scale = scale; }
-
- double get_translation_deadzone() const { return m_translation_params.deadzone; }
- void set_translation_deadzone(double deadzone) { m_translation_params.deadzone = deadzone; }
-
- float get_rotation_deadzone() const { return m_rotation_params.deadzone; }
- void set_rotation_deadzone(float deadzone) { m_rotation_params.deadzone = deadzone; }
+#endif /* WIN32 */
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
- Vec3d get_translation() const { return has_translation() ? m_translation.queue.front() : Vec3d::Zero(); }
- Vec3f get_rotation() const { return has_rotation() ? m_rotation.queue.front() : Vec3f::Zero(); }
- unsigned int get_button() const { return has_button() ? m_buttons.front() : 0; }
-
- unsigned int get_translation_queue_size() const { return (unsigned int)m_translation.queue.size(); }
- unsigned int get_rotation_queue_size() const { return (unsigned int)m_rotation.queue.size(); }
- unsigned int get_buttons_queue_size() const { return (unsigned int)m_buttons.size(); }
-
- size_t get_translation_queue_max_size() const { return m_translation_queue_max_size; }
- size_t get_rotation_queue_max_size() const { return m_rotation_queue_max_size; }
- size_t get_buttons_queue_max_size() const { return m_buttons_queue_max_size; }
+ Vec3d get_first_vector_of_type(unsigned int type) const {
+ tbb::mutex::scoped_lock lock(m_input_queue_mutex);
+ auto it = std::find_if(m_input_queue.begin(), m_input_queue.end(), [type](const QueueItem& item) { return item.type_or_buttons == type; });
+ return (it == m_input_queue.end()) ? Vec3d::Zero() : it->vector;
+ }
+ size_t input_queue_size_current() const {
+ tbb::mutex::scoped_lock lock(m_input_queue_mutex);
+ return m_input_queue.size();
+ }
+ std::atomic input_queue_max_size_achieved;
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
- size_t get_queues_max_size() const { return m_translation.max_size; }
- void set_queues_max_size(size_t size);
-
- // return true if any change to the camera took place
- bool apply(Camera& camera);
+ // Apply the 3DConnexion events stored in the input queue, reset the input queue.
+ // Returns true if any change to the camera took place.
+ bool apply(const Params ¶ms, Camera& camera);
};
- bool m_initialized;
- mutable State m_state;
- std::thread m_thread;
- hid_device* m_device;
- std::string m_device_str;
- bool m_running;
- bool m_mac_mouse_connected;
- mutable bool m_show_settings_dialog;
- // set to true when ther user closes the dialog by clicking on [X] or [Close] buttons
- mutable bool m_settings_dialog_closed_by_user;
- std::chrono::time_point m_last_time;
+ // Background thread works with this copy.
+ Params m_params;
+ // UI thread will read / write this copy.
+ Params m_params_ui;
+ bool m_params_ui_changed { false };
+ mutable tbb::mutex m_params_ui_mutex;
+
+ // This is a database of parametes of all 3DConnexion devices ever connected.
+ // This database is loaded from AppConfig on application start and it is stored to AppConfig on application exit.
+ // We need to do that as the AppConfig is not thread safe and we need read the parameters on device connect / disconnect,
+ // which is now done by a background thread.
+ std::map m_params_by_device;
+
+ mutable State m_state;
+ std::atomic m_connected { false };
+ std::string m_device_str;
+
+#if ! __APPLE__
+ // Worker thread for enumerating devices, connecting, reading data from the device and closing the device.
+ std::thread m_thread;
+ hid_device* m_device { nullptr };
+ // Using m_stop_condition_mutex to synchronize m_stop.
+ bool m_stop { false };
+#ifdef _WIN32
+ std::atomic m_wakeup { false };
+#endif /* _WIN32 */
+ // Mutex and condition variable for sleeping during the detection of 3DConnexion devices by polling while allowing
+ // cancellation before the end of the polling interval.
+ std::mutex m_stop_condition_mutex;
+ std::condition_variable m_stop_condition;
+#endif
+
+ // Is the ImGUI dialog shown? Accessed from UI thread only.
+ mutable bool m_show_settings_dialog { false };
+ // Set to true when ther user closes the dialog by clicking on [X] or [Close] buttons. Accessed from UI thread only.
+ mutable bool m_settings_dialog_closed_by_user { false };
public:
- Mouse3DController();
-
+ // Load the device parameter database from appconfig. To be called on application startup.
+ void load_config(const AppConfig &appconfig);
+ // Store the device parameter database back to appconfig. To be called on application closeup.
+ void save_config(AppConfig &appconfig) const;
+ // Start the background thread to detect and connect to a HID device (Windows and Linux).
+ // Connect to a 3DConnextion driver (OSX).
+ // Call load_config() before init().
void init();
+ // Stop the background thread (Windows and Linux).
+ // Disconnect from a 3DConnextion driver (OSX).
+ // Call save_config() after shutdown().
void shutdown();
- bool is_device_connected() const { return m_device != nullptr || m_mac_mouse_connected; }
- bool is_running() const { return m_running || m_mac_mouse_connected; }
+ bool connected() const { return m_connected; }
- void set_mac_mouse_connected(bool b){m_mac_mouse_connected = b;};
-
+#if __APPLE__
+ // Interfacing with the Objective C code (MouseHandlerMac.mm)
+ void connected(std::string device_name);
+ void disconnected();
+ typedef std::array DataPacketAxis;
+ // Unpack a 3DConnexion packet provided by the 3DConnexion driver into m_state. Called by Mouse3DHandlerMac.mm
+ bool handle_input(const DataPacketAxis& packet);
+#endif // __APPLE__
+
+#ifdef WIN32
+ // Called by Win32 HID enumeration callback.
+ void device_attached(const std::string &device);
+
+ // On Windows, the 3DConnexion driver sends out mouse wheel rotation events to an active application
+ // if the application does not register at the driver. This is a workaround to ignore these superfluous
+ // mouse wheel events.
bool process_mouse_wheel() { return m_state.process_mouse_wheel(); }
+#endif // WIN32
+ // Apply the received 3DConnexion mouse events to the camera. Called from the UI rendering thread.
bool apply(Camera& camera);
bool is_settings_dialog_shown() const { return m_show_settings_dialog; }
- void show_settings_dialog(bool show) { m_show_settings_dialog = show && is_running(); }
+ void show_settings_dialog(bool show) { m_show_settings_dialog = show && this->connected(); }
void render_settings_dialog(GLCanvas3D& canvas) const;
- typedef std::array DataPacketAxis;
- void handle_input_axis(const DataPacketAxis& packet);
+#if ! __APPLE__
private:
bool connect_device();
void disconnect_device();
- void start();
- void stop() { m_running = false; }
- typedef std::array DataPacketRaw;
// secondary thread methods
void run();
void collect_input();
- void handle_input(const DataPacketRaw& packet, const int packet_lenght);
- bool handle_packet(const DataPacketRaw& packet);
- bool handle_wireless_packet(const DataPacketRaw& packet);
- bool handle_packet_translation(const DataPacketRaw& packet);
- bool handle_packet_rotation(const DataPacketRaw& packet, unsigned int first_byte);
- bool handle_packet_button(const DataPacketRaw& packet, unsigned int packet_size);
-#if __APPLE__
- Mouse3DHandlerMac* m_handler_mac;
-#endif//__APPLE__
+ typedef std::array DataPacketRaw;
+
+ // Unpack raw 3DConnexion HID packet of a wired 3D mouse into m_state. Called by the worker thread.
+ static bool handle_input(const DataPacketRaw& packet, const int packet_lenght, const Params ¶ms, State &state_in_out);
+ // The following is called by handle_input() from the worker thread.
+ static bool handle_packet(const DataPacketRaw& packet, const Params ¶ms, State &state_in_out);
+ static bool handle_wireless_packet(const DataPacketRaw& packet, const Params ¶ms, State &state_in_out);
+ static bool handle_packet_translation(const DataPacketRaw& packet, const Params ¶ms, State &state_in_out);
+ static bool handle_packet_rotation(const DataPacketRaw& packet, unsigned int first_byte, const Params ¶ms, State &state_in_out);
+ static bool handle_packet_button(const DataPacketRaw& packet, unsigned int packet_size, const Params ¶ms, State &state_in_out);
+#endif /* __APPLE__ */
};
-#if __APPLE__
-class Mouse3DHandlerMac{
- public:
- Mouse3DHandlerMac(Mouse3DController* controller);
- ~Mouse3DHandlerMac();
-
- bool available();
-};
-#endif//__APPLE__
-
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_Mouse3DController_hpp_
-
diff --git a/src/slic3r/GUI/Mouse3DHandlerMac.mm b/src/slic3r/GUI/Mouse3DHandlerMac.mm
index 60006b8c1c..b2d07a4e0b 100644
--- a/src/slic3r/GUI/Mouse3DHandlerMac.mm
+++ b/src/slic3r/GUI/Mouse3DHandlerMac.mm
@@ -1,4 +1,3 @@
-
#include "Mouse3DController.hpp"
#include
@@ -10,7 +9,7 @@
#include
-static Slic3r::GUI::Mouse3DController* mouse_3d_controller = NULL;
+static Slic3r::GUI::Mouse3DController* mouse_3d_controller = nullptr;
static uint16_t clientID = 0;
@@ -65,7 +64,7 @@ typedef int16_t (*ConnexionClientControl_ptr)(uint16_t clientID,
int32_t param,
int32_t *result);
-#define DECLARE_FUNC(name) name##_ptr name = NULL
+#define DECLARE_FUNC(name) name##_ptr name = nullptr
DECLARE_FUNC(SetConnexionHandlers);
DECLARE_FUNC(InstallConnexionHandlers);
@@ -79,15 +78,12 @@ static void *load_func(void *module, const char *func_name)
{
void *func = dlsym(module, func_name);
-//#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
if (func) {
BOOST_LOG_TRIVIAL(info) << func_name <<" loaded";
}
else {
- //printf(" %s\n", dlerror());
BOOST_LOG_TRIVIAL(error) <<"loading 3dx drivers dlsym error: "<< dlerror();
}
-//#endif
return func;
}
@@ -98,9 +94,8 @@ static void *module; // handle to the whole driver
static bool load_driver_functions()
{
- if (driver_loaded) {
+ if (driver_loaded)
return true;
- }
module = dlopen("/Library/Frameworks/3DconnexionClient.framework/3DconnexionClient",
RTLD_LAZY | RTLD_LOCAL);
@@ -109,15 +104,14 @@ static bool load_driver_functions()
BOOST_LOG_TRIVIAL(info) << "loading 3dx drivers";
LOAD_FUNC(SetConnexionHandlers);
- if (SetConnexionHandlers != NULL) {
+ if (SetConnexionHandlers != nullptr) {
driver_loaded = true;
has_new_driver = true;
}
else {
- BOOST_LOG_TRIVIAL(info) << "installing 3dx drivers";
+ BOOST_LOG_TRIVIAL(info) << "installing 3dx drivers";
LOAD_FUNC(InstallConnexionHandlers);
-
- driver_loaded = (InstallConnexionHandlers != NULL);
+ driver_loaded = (InstallConnexionHandlers != nullptr);
}
if (driver_loaded) {
@@ -128,17 +122,11 @@ static bool load_driver_functions()
LOAD_FUNC(ConnexionClientControl);
}
}
- else {
+ else {
BOOST_LOG_TRIVIAL(error) << "3dx drivers module loading error: "<< dlerror() ;
-#if DENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
- printf(" %s\n", dlerror());
-#endif
}
-#if DENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
- printf("loaded: %s\n", driver_loaded ? "YES" : "NO");
- printf("new: %s\n", has_new_driver ? "YES" : "NO");
-#endif
- BOOST_LOG_TRIVIAL(info) << "3dx drivers loaded: "<< driver_loaded ? "YES" : "NO" ;
+
+ BOOST_LOG_TRIVIAL(info) << "3dx drivers loaded: " << (driver_loaded ? (has_new_driver ? "YES, new" : "YES, old") : "NO");
return driver_loaded;
}
@@ -149,29 +137,25 @@ static void unload_driver()
static void DeviceAdded(uint32_t unused)
{
-#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
- std::cout<<"3D device added"<> 16;
- int16_t productID = result & 0xffff;
-
+ int vendorID = result >> 16;
+ int productID = result & 0xffff;
//TODO: verify device
-
-
- mouse_3d_controller->set_mac_mouse_connected(true);
+ char buf[64];
+ sprintf(buf, "VID%04X,PID%04X", vendorID, productID);
+ mouse_3d_controller->connected(buf);
}
static void DeviceRemoved(uint32_t unused)
{
-#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
- printf("3d device removed\n");
-#endif
BOOST_LOG_TRIVIAL(info) << "3dx device removed\n";
- mouse_3d_controller->set_mac_mouse_connected(true);
+// not accessible in a free function
+// assert(m_connected);
+// assert(! m_device_str.empty());
+ mouse_3d_controller->disconnected();
}
static void DeviceEvent(uint32_t unused, uint32_t msg_type, void *msg_arg)
@@ -181,21 +165,16 @@ static void DeviceEvent(uint32_t unused, uint32_t msg_type, void *msg_arg)
if (s->client == clientID) {
switch (s->command) {
case kConnexionCmdHandleAxis: {
- /*
- The axis field is an array of 6 signed 16-bit integers corresponding to the 6 device axes. Data is ordered as Tx, Tz, Ty, Rx, Rz, Ry. The values reported are scaled by the driver according to the speed slider settings on the 3Dconnexion preference panel. At maximum speed, the range is - 1024 to 1024. Typical range that you should optimize your application for should be -500 to 500.
- */
- //Actually we are getting values way over 1024. Max is probably 2048 now.
- std::array packet;
- for (int i = 0; i < 6; i++) {
+ // The axis field is an array of 6 signed 16-bit integers corresponding to the 6 device axes. Data is ordered as Tx, Tz, Ty, Rx, Rz, Ry. The values reported are scaled by the driver according to the speed slider settings on the 3Dconnexion preference panel. At maximum speed, the range is - 1024 to 1024. Typical range that you should optimize your application for should be -500 to 500.
+ // Actually we are getting values way over 1024. Max is probably 2048 now.
+ Slic3r::GUI::Mouse3DController::DataPacketAxis packet;
+ for (int i = 0; i < 6; ++ i)
packet[i] = (double)s->axis[i]/350.0;//wanted to divide by 500 but 350 is used at raw input so i used same value.
- }
- mouse_3d_controller->handle_input_axis(packet);
-
-
+ mouse_3d_controller->handle_input(packet);
break;
}
case kConnexionCmdHandleButtons:
- break;
+ break;
case kConnexionCmdAppSpecific:
break;
default:
@@ -203,53 +182,44 @@ static void DeviceEvent(uint32_t unused, uint32_t msg_type, void *msg_arg)
}
}
}
-
}
namespace Slic3r {
namespace GUI {
-Mouse3DHandlerMac::Mouse3DHandlerMac(Mouse3DController* controller)
+
+// Initialize the application.
+void Mouse3DController::init()
{
- BOOST_LOG_TRIVIAL(info) << "3dx mac handler starts";
+ BOOST_LOG_TRIVIAL(info) << "3dx mac handler starts";
if (load_driver_functions()) {
- mouse_3d_controller = controller;
+ mouse_3d_controller = this;
- uint16_t error;
- if (has_new_driver) {
- error = SetConnexionHandlers(DeviceEvent, DeviceAdded, DeviceRemoved, false);
- }
- else {
- error = InstallConnexionHandlers(DeviceEvent, DeviceAdded, DeviceRemoved);
- }
+ uint16_t error = has_new_driver ?
+ SetConnexionHandlers(DeviceEvent, DeviceAdded, DeviceRemoved, false) :
+ InstallConnexionHandlers(DeviceEvent, DeviceAdded, DeviceRemoved);
- if (error) {
- return;
+ if (! error) {
+ // Registration is done either by 4letter constant (CFBundleSignature - obsolete
+ //and we dont have that) or Executable name in pascal string(first byte is string lenght).
+ //If no packets are recieved the name might be different - check cmake. If debugging try commenting
+ // set_target_properties(PrusaSlicer PROPERTIES OUTPUT_NAME "prusa-slicer")
+ clientID = RegisterConnexionClient(
+ 0, "\013PrusaSlicer", kConnexionClientModeTakeOver, kConnexionMaskAxis);
+ BOOST_LOG_TRIVIAL(info) << "3dx mac handler registered";
}
-
- // Registration is done either by 4letter constant (CFBundleSignature - obsolete
- //and we dont have that) or Executable name in pascal string(first byte is string lenght).
- //If no packets are recieved the name might be different - check cmake. If debugging try commenting
- // set_target_properties(PrusaSlicer PROPERTIES OUTPUT_NAME "prusa-slicer")
-
- clientID = RegisterConnexionClient(
- 0, "\013PrusaSlicer", kConnexionClientModeTakeOver, kConnexionMaskAxis);
- BOOST_LOG_TRIVIAL(info) << "3dx mac handler registered";
}
}
-Mouse3DHandlerMac::~Mouse3DHandlerMac()
+void Mouse3DController::shutdown()
{
if (driver_loaded) {
UnregisterConnexionClient(clientID);
CleanupConnexionHandlers();
unload_driver();
}
+ // Copy the current parameters to parameter database, mark the device as disconnected.
+ this->disconnected();
mouse_3d_controller = nullptr;
}
-bool Mouse3DHandlerMac::available()
-{
- return driver_loaded;
-}
-
}}//namespace Slic3r::GUI
diff --git a/src/slic3r/GUI/MsgDialog.cpp b/src/slic3r/GUI/MsgDialog.cpp
index d4a82a03d6..5b583ba2a3 100644
--- a/src/slic3r/GUI/MsgDialog.cpp
+++ b/src/slic3r/GUI/MsgDialog.cpp
@@ -81,7 +81,7 @@ ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg)
html->SetMinSize(wxSize(40 * wxGetApp().em_unit(), -1));
wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
wxColour text_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
- wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_FRAMEBK); // wxSYS_COLOUR_WINDOW
+ wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue());
auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue());
const int font_size = font.GetPointSize()-1;
diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp
index f0b108bd7e..207d42b5b3 100644
--- a/src/slic3r/GUI/OptionsGroup.cpp
+++ b/src/slic3r/GUI/OptionsGroup.cpp
@@ -113,7 +113,11 @@ void OptionsGroup::add_undo_buttuns_to_sizer(wxSizer* sizer, const t_field& fiel
}
void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = nullptr*/) {
- if ( (line.sizer != nullptr || line.widget != nullptr) && line.full_width) {
+ if ( line.full_width && (
+ line.sizer != nullptr ||
+ line.widget != nullptr ||
+ !line.get_extra_widgets().empty() )
+ ) {
if (line.sizer != nullptr) {
sizer->Add(line.sizer, 0, wxEXPAND | wxALL, wxOSX ? 0 : 15);
return;
@@ -122,6 +126,17 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n
sizer->Add(line.widget(this->ctrl_parent()), 0, wxEXPAND | wxALL, wxOSX ? 0 : 15);
return;
}
+ if (!line.get_extra_widgets().empty()) {
+ const auto h_sizer = new wxBoxSizer(wxHORIZONTAL);
+ sizer->Add(h_sizer, 1, wxEXPAND | wxALL, wxOSX ? 0 : 15);
+
+ bool is_first_item = true;
+ for (auto extra_widget : line.get_extra_widgets()) {
+ h_sizer->Add(extra_widget(this->ctrl_parent()), is_first_item ? 1 : 0, wxLEFT, 15);
+ is_first_item = false;
+ }
+ return;
+ }
}
auto option_set = line.get_options();
@@ -412,7 +427,9 @@ void ConfigOptionsGroup::back_to_config_value(const DynamicPrintConfig& config,
auto *nozzle_diameter = dynamic_cast(config.option("nozzle_diameter"));
value = int(nozzle_diameter->values.size());
}
- else if (m_opt_map.find(opt_key) == m_opt_map.end() || opt_key == "bed_shape") {
+ else if (m_opt_map.find(opt_key) == m_opt_map.end() ||
+ // This option don't have corresponded field
+ opt_key == "bed_shape" || opt_key == "compatible_printers" || opt_key == "compatible_prints" ) {
value = get_config_value(config, opt_key);
change_opt_value(*m_config, opt_key, value);
return;
@@ -629,7 +646,7 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config
ret = static_cast(config.opt_string(opt_key));
break;
case coStrings:
- if (opt_key.compare("compatible_printers") == 0) {
+ if (opt_key == "compatible_printers" || opt_key == "compatible_prints") {
ret = config.option(opt_key)->values;
break;
}
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index 7432f66336..5aabfc9a2e 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -295,15 +295,20 @@ PresetBitmapComboBox(parent, wxSize(15 * wxGetApp().em_unit(), -1)),
if (preset_type == Slic3r::Preset::TYPE_FILAMENT)
{
Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &event) {
- int shifl_Left = 0;
+ PresetBundle* preset_bundle = wxGetApp().preset_bundle;
+ const Preset* selected_preset = preset_bundle->filaments.find_preset(preset_bundle->filament_presets[extruder_idx]);
+ // Wide icons are shown if the currently selected preset is not compatible with the current printer,
+ // and red flag is drown in front of the selected preset.
+ bool wide_icons = selected_preset != nullptr && !selected_preset->is_compatible;
float scale = m_em_unit*0.1f;
+
+ int shifl_Left = wide_icons ? int(scale * 16 + 0.5) : 0;
#if defined(wxBITMAPCOMBOBOX_OWNERDRAWN_BASED)
- shifl_Left = int(scale * 4 + 0.5f); // IMAGE_SPACING_RIGHT = 4 for wxBitmapComboBox -> Space left of image
+ shifl_Left += int(scale * 4 + 0.5f); // IMAGE_SPACING_RIGHT = 4 for wxBitmapComboBox -> Space left of image
#endif
- int icon_right_pos = int(scale * (24+4) + 0.5);
+ int icon_right_pos = shifl_Left + int(scale * (24+4) + 0.5);
int mouse_pos = event.GetLogicalPosition(wxClientDC(this)).x;
-// if (extruder_idx < 0 || event.GetLogicalPosition(wxClientDC(this)).x > 24) {
- if ( extruder_idx < 0 || mouse_pos < shifl_Left || mouse_pos > icon_right_pos ) {
+ if (mouse_pos < shifl_Left || mouse_pos > icon_right_pos ) {
// Let the combo box process the mouse click.
event.Skip();
return;
@@ -332,7 +337,7 @@ PresetBitmapComboBox(parent, wxSize(15 * wxGetApp().em_unit(), -1)),
cfg_new.set_key_value("extruder_colour", colors);
wxGetApp().get_tab(Preset::TYPE_PRINTER)->load_config(cfg_new);
- wxGetApp().preset_bundle->update_plater_filament_ui(extruder_idx, this);
+ preset_bundle->update_plater_filament_ui(extruder_idx, this);
wxGetApp().plater()->on_config_change(cfg_new);
}
});
@@ -875,8 +880,8 @@ Sidebar::Sidebar(Plater *parent)
};
init_scalable_btn(&p->btn_send_gcode , "export_gcode", _(L("Send to printer")) + "\tCtrl+Shift+G");
- init_scalable_btn(&p->btn_remove_device, "eject_sd" , _(L("Remove device")));
- init_scalable_btn(&p->btn_export_gcode_removable, "export_to_sd", _(L("Export to SD card / Flash drive")));
+ init_scalable_btn(&p->btn_remove_device, "eject_sd" , _(L("Remove device")) + "\tCtrl+T");
+ init_scalable_btn(&p->btn_export_gcode_removable, "export_to_sd", _(L("Export to SD card / Flash drive")) + "\tCtrl+U");
// regular buttons "Slice now" and "Export G-code"
@@ -1187,7 +1192,7 @@ void Sidebar::update_sliced_info_sizer()
if (p->plater->printer_technology() == ptSLA)
{
const SLAPrintStatistics& ps = p->plater->sla_print().print_statistics();
- wxString new_label = _(L("Used Material (ml)")) + " :";
+ wxString new_label = _(L("Used Material (ml)")) + ":";
const bool is_supports = ps.support_used_material > 0.0;
if (is_supports)
new_label += from_u8((boost::format("\n - %s\n - %s") % _utf8(L("object(s)")) % _utf8(L("supports and pad"))).str());
@@ -1212,7 +1217,7 @@ void Sidebar::update_sliced_info_sizer()
p->sliced_info->SetTextAndShow(siCost, str_total_cost, "Cost");
wxString t_est = std::isnan(ps.estimated_print_time) ? "N/A" : get_time_dhms(float(ps.estimated_print_time));
- p->sliced_info->SetTextAndShow(siEstimatedTime, t_est, _(L("Estimated printing time")) + " :");
+ p->sliced_info->SetTextAndShow(siEstimatedTime, t_est, _(L("Estimated printing time")) + ":");
// Hide non-SLA sliced info parameters
p->sliced_info->SetTextAndShow(siFilament_m, "N/A");
@@ -1227,7 +1232,7 @@ void Sidebar::update_sliced_info_sizer()
wxString new_label = _(L("Used Filament (m)"));
if (is_wipe_tower)
- new_label += from_u8((boost::format(" :\n - %1%\n - %2%") % _utf8(L("objects")) % _utf8(L("wipe tower"))).str());
+ new_label += from_u8((boost::format(":\n - %1%\n - %2%") % _utf8(L("objects")) % _utf8(L("wipe tower"))).str());
wxString info_text = is_wipe_tower ?
wxString::Format("%.2f \n%.2f \n%.2f", ps.total_used_filament / 1000,
@@ -1241,7 +1246,7 @@ void Sidebar::update_sliced_info_sizer()
new_label = _(L("Cost"));
if (is_wipe_tower)
- new_label += from_u8((boost::format(" :\n - %1%\n - %2%") % _utf8(L("objects")) % _utf8(L("wipe tower"))).str());
+ new_label += from_u8((boost::format(":\n - %1%\n - %2%") % _utf8(L("objects")) % _utf8(L("wipe tower"))).str());
info_text = ps.total_cost == 0.0 ? "N/A" :
is_wipe_tower ?
@@ -1254,7 +1259,7 @@ void Sidebar::update_sliced_info_sizer()
if (ps.estimated_normal_print_time == "N/A" && ps.estimated_silent_print_time == "N/A")
p->sliced_info->SetTextAndShow(siEstimatedTime, "N/A");
else {
- new_label = _(L("Estimated printing time")) +" :";
+ new_label = _(L("Estimated printing time")) +":";
info_text = "";
wxString str_color = _(L("Color"));
wxString str_pause = _(L("Pause"));
@@ -1503,6 +1508,7 @@ struct Plater::priv
ret.poly.contour = std::move(p);
ret.translation = scaled(m_pos);
ret.rotation = m_rotation;
+ ret.priority++;
return ret;
}
} wipetower;
@@ -1565,18 +1571,23 @@ struct Plater::priv
// the current bed width.
static const constexpr double LOGICAL_BED_GAP = 1. / 5.;
- ArrangePolygons m_selected, m_unselected;
+ ArrangePolygons m_selected, m_unselected, m_unprintable;
// clear m_selected and m_unselected, reserve space for next usage
void clear_input() {
const Model &model = plater().model;
- size_t count = 0; // To know how much space to reserve
- for (auto obj : model.objects) count += obj->instances.size();
+ size_t count = 0, cunprint = 0; // To know how much space to reserve
+ for (auto obj : model.objects)
+ for (auto mi : obj->instances)
+ mi->printable ? count++ : cunprint++;
+
m_selected.clear();
m_unselected.clear();
+ m_unprintable.clear();
m_selected.reserve(count + 1 /* for optional wti */);
m_unselected.reserve(count + 1 /* for optional wti */);
+ m_unprintable.reserve(cunprint /* for optional wti */);
}
// Stride between logical beds
@@ -1605,8 +1616,10 @@ struct Plater::priv
clear_input();
for (ModelObject *obj: plater().model.objects)
- for (ModelInstance *mi : obj->instances)
- m_selected.emplace_back(get_arrange_poly(mi));
+ for (ModelInstance *mi : obj->instances) {
+ ArrangePolygons & cont = mi->printable ? m_selected : m_unprintable;
+ cont.emplace_back(get_arrange_poly(mi));
+ }
auto& wti = plater().updated_wipe_tower();
if (wti) m_selected.emplace_back(get_arrange_poly(&wti));
@@ -1641,9 +1654,12 @@ struct Plater::priv
for (size_t i = 0; i < inst_sel.size(); ++i) {
ArrangePolygon &&ap = get_arrange_poly(mo->instances[i]);
- inst_sel[i] ?
- m_selected.emplace_back(std::move(ap)) :
- m_unselected.emplace_back(std::move(ap));
+ ArrangePolygons &cont = mo->instances[i]->printable ?
+ (inst_sel[i] ? m_selected :
+ m_unselected) :
+ m_unprintable;
+
+ cont.emplace_back(std::move(ap));
}
}
@@ -1675,16 +1691,35 @@ struct Plater::priv
public:
using PlaterJob::PlaterJob;
- int status_range() const override { return int(m_selected.size()); }
+ int status_range() const override
+ {
+ return int(m_selected.size() + m_unprintable.size());
+ }
void process() override;
void finalize() override {
// Ignore the arrange result if aborted.
if (was_canceled()) return;
-
+
+ // Unprintable items go to the last virtual bed
+ int beds = 0;
+
// Apply the arrange result to all selected objects
- for (ArrangePolygon &ap : m_selected) ap.apply();
+ for (ArrangePolygon &ap : m_selected) {
+ beds = std::max(ap.bed_idx, beds);
+ ap.apply();
+ }
+
+ // Get the virtual beds from the unselected items
+ for (ArrangePolygon &ap : m_unselected)
+ beds = std::max(ap.bed_idx, beds);
+
+ // Move the unprintable items to the last virtual bed.
+ for (ArrangePolygon &ap : m_unprintable) {
+ ap.bed_idx += beds + 1;
+ ap.apply();
+ }
plater().update();
}
@@ -1873,7 +1908,7 @@ struct Plater::priv
GUI::show_error(this->q, msg);
}
}
- void export_gcode(fs::path output_path, PrintHostJob upload_job);
+ void export_gcode(fs::path output_path, bool output_path_on_removable_media, PrintHostJob upload_job);
void reload_from_disk();
void reload_all_from_disk();
void fix_through_netfabb(const int obj_idx, const int vol_idx = -1);
@@ -1932,6 +1967,12 @@ struct Plater::priv
wxString get_project_filename(const wxString& extension = wxEmptyString) const;
void set_project_filename(const wxString& filename);
+ // Caching last value of show_action_buttons parameter for show_action_buttons(), so that a callback which does not know this state will not override it.
+ mutable bool ready_to_slice = { false };
+ // Flag indicating that the G-code export targets a removable device, therefore the show_action_buttons() needs to be called at any case when the background processing finishes.
+ bool writing_to_removable_device = { false };
+ bool inside_snapshot_capture() { return m_prevent_snapshots != 0; }
+
private:
bool init_object_menu();
bool init_common_menu(wxMenu* menu, const bool is_part = false);
@@ -1957,8 +1998,8 @@ private:
* we should call tack_snapshot just ones
* instead of calls for each action separately
* */
- std::string m_last_fff_printer_profile_name;
- std::string m_last_sla_printer_profile_name;
+ std::string m_last_fff_printer_profile_name;
+ std::string m_last_sla_printer_profile_name;
};
const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)", std::regex::icase);
@@ -2124,19 +2165,44 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
// updates camera type from .ini file
camera.set_type(get_config("use_perspective_camera"));
+ // Load the 3DConnexion device database.
+ mouse3d_controller.load_config(*wxGetApp().app_config);
+ // Start the background thread to detect and connect to a HID device (Windows and Linux).
+ // Connect to a 3DConnextion driver (OSX).
mouse3d_controller.init();
+#ifdef _WIN32
+ // Register an USB HID (Human Interface Device) attach event. evt contains Win32 path to the USB device containing VID, PID and other info.
+ // This event wakes up the Mouse3DController's background thread to enumerate HID devices, if the VID of the callback event
+ // is one of the 3D Mouse vendors (3DConnexion or Logitech).
+ this->q->Bind(EVT_HID_DEVICE_ATTACHED, [this](HIDDeviceAttachedEvent &evt) {
+ mouse3d_controller.device_attached(evt.data);
+ });
+#endif /* _WIN32 */
+
+ this->q->Bind(EVT_REMOVABLE_DRIVE_EJECTED, [this](RemovableDriveEjectEvent &evt) {
+ if (evt.data.second) {
+ this->show_action_buttons(this->ready_to_slice);
+ Slic3r::GUI::show_info(this->q, (boost::format(_utf8(L("Unmounting successful. The device %s(%s) can now be safely removed from the computer.")))
+ % evt.data.first.name % evt.data.first.path).str());
+ } else
+ Slic3r::GUI::show_info(this->q, (boost::format(_utf8(L("Ejecting of device %s(%s) has failed.")))
+ % evt.data.first.name % evt.data.first.path).str());
+ });
+ this->q->Bind(EVT_REMOVABLE_DRIVES_CHANGED, [this](RemovableDrivesChangedEvent &) { this->show_action_buttons(this->ready_to_slice); });
+ // Start the background thread and register this window as a target for update events.
+ wxGetApp().removable_drive_manager()->init(this->q);
+#ifdef _WIN32
+ // Trigger enumeration of removable media on Win32 notification.
+ this->q->Bind(EVT_VOLUME_ATTACHED, [this](VolumeAttachedEvent &evt) { wxGetApp().removable_drive_manager()->volumes_changed(); });
+ this->q->Bind(EVT_VOLUME_DETACHED, [this](VolumeDetachedEvent &evt) { wxGetApp().removable_drive_manager()->volumes_changed(); });
+#endif /* _WIN32 */
// Initialize the Undo / Redo stack with a first snapshot.
this->take_snapshot(_(L("New Project")));
-
- //void Plater::priv::show_action_buttons(const bool is_ready_to_slice) const
- RemovableDriveManager::get_instance().set_drive_count_changed_callback(std::bind(&Plater::priv::show_action_buttons, this, std::placeholders::_1));
}
Plater::priv::~priv()
{
- mouse3d_controller.shutdown();
-
if (config != nullptr)
delete config;
}
@@ -2199,12 +2265,9 @@ void Plater::priv::reset_all_gizmos()
// Update the UI based on the current preferences.
void Plater::priv::update_ui_from_settings()
{
- // TODO: (?)
- // my ($self) = @_;
- // if (defined($self->{btn_reslice}) && $self->{buttons_sizer}->IsShown($self->{btn_reslice}) != (! wxTheApp->{app_config}->get("background_processing"))) {
- // $self->{buttons_sizer}->Show($self->{btn_reslice}, ! wxTheApp->{app_config}->get("background_processing"));
- // $self->{buttons_sizer}->Layout;
- // }
+ camera.set_type(wxGetApp().app_config->get("use_perspective_camera"));
+ if (wxGetApp().app_config->get("use_free_camera") != "1")
+ camera.recover_from_free_camera();
view3D->get_canvas3d()->update_ui_from_settings();
preview->get_canvas3d()->update_ui_from_settings();
@@ -2418,7 +2481,7 @@ std::vector Plater::priv::load_files(const std::vector& input_
selection.add_object((unsigned int)idx, false);
}
- if (view3D->get_canvas3d()->get_gizmos_manager().is_running())
+ if (view3D->get_canvas3d()->get_gizmos_manager().is_enabled())
// this is required because the selected object changed and the flatten on face an sla support gizmos need to be updated accordingly
view3D->get_canvas3d()->update_gizmos_on_off_state();
}
@@ -2794,16 +2857,21 @@ void Plater::priv::ArrangeJob::process() {
}
coord_t min_d = scaled(dist);
- auto count = unsigned(m_selected.size());
+ auto count = unsigned(m_selected.size() + m_unprintable.size());
arrangement::BedShapeHint bedshape = plater().get_bed_shape_hint();
-
+
+ auto stopfn = [this]() { return was_canceled(); };
+
try {
arrangement::arrange(m_selected, m_unselected, min_d, bedshape,
- [this, count](unsigned st) {
- if (st > 0) // will not finalize after last one
- update_status(int(count - st), arrangestr);
- },
- [this]() { return was_canceled(); });
+ [this, count](unsigned st) {
+ st += m_unprintable.size();
+ if (st > 0) update_status(int(count - st), arrangestr);
+ }, stopfn);
+ arrangement::arrange(m_unprintable, {}, min_d, bedshape,
+ [this, count](unsigned st) {
+ if (st > 0) update_status(int(count - st), arrangestr);
+ }, stopfn);
} catch (std::exception & /*e*/) {
GUI::show_error(plater().q,
_(L("Could not arrange model objects! "
@@ -3072,7 +3140,7 @@ bool Plater::priv::restart_background_process(unsigned int state)
return false;
}
-void Plater::priv::export_gcode(fs::path output_path, PrintHostJob upload_job)
+void Plater::priv::export_gcode(fs::path output_path, bool output_path_on_removable_media, PrintHostJob upload_job)
{
wxCHECK_RET(!(output_path.empty() && upload_job.empty()), "export_gcode: output_path and upload_job empty");
@@ -3093,7 +3161,7 @@ void Plater::priv::export_gcode(fs::path output_path, PrintHostJob upload_job)
return;
if (! output_path.empty()) {
- background_process.schedule_export(output_path.string());
+ background_process.schedule_export(output_path.string(), output_path_on_removable_media);
} else {
background_process.schedule_upload(std::move(upload_job));
}
@@ -3481,8 +3549,8 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
//! instead of
//! combo->GetStringSelection().ToUTF8().data());
- const std::string& selected_string = combo->GetString(combo->GetSelection()).ToUTF8().data();
- const std::string preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias(preset_type, selected_string);
+ const std::string preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias(preset_type,
+ Preset::remove_suffix_modified(combo->GetString(combo->GetSelection()).ToUTF8().data()));
if (preset_type == Preset::TYPE_FILAMENT) {
wxGetApp().preset_bundle->set_filament_preset(idx, preset_name);
@@ -3575,7 +3643,12 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt)
wxString message = evt.GetString();
if (message.IsEmpty())
message = _(L("Export failed"));
- show_error(q, message);
+ if (q->m_tracking_popup_menu)
+ // We don't want to pop-up a message box when tracking a pop-up menu.
+ // We postpone the error message instead.
+ q->m_tracking_popup_menu_error_message = message;
+ else
+ show_error(q, message);
this->statusbar()->set_status_text(message);
}
if (canceled)
@@ -3608,14 +3681,9 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt)
sidebar->set_btn_label(ActionButtonType::abReslice, "Slice now");
show_action_buttons(true);
}
- else if (wxGetApp().get_mode() == comSimple)
- show_action_buttons(false);
-
- if(!canceled && RemovableDriveManager::get_instance().get_is_writing())
- {
- RemovableDriveManager::get_instance().set_is_writing(false);
- show_action_buttons(false);
- }
+ else if (this->writing_to_removable_device || wxGetApp().get_mode() == comSimple)
+ show_action_buttons(false);
+ this->writing_to_removable_device = false;
}
void Plater::priv::on_layer_editing_toggled(bool enable)
@@ -4175,32 +4243,36 @@ void Plater::priv::update_object_menu()
sidebar->obj_list()->append_menu_items_add_volume(&object_menu);
}
-void Plater::priv::show_action_buttons(const bool is_ready_to_slice) const
+void Plater::priv::show_action_buttons(const bool ready_to_slice) const
{
- RemovableDriveManager::get_instance().set_plater_ready_to_slice(is_ready_to_slice);
+ // Cache this value, so that the callbacks from the RemovableDriveManager may repeat that value when calling show_action_buttons().
+ this->ready_to_slice = ready_to_slice;
+
wxWindowUpdateLocker noUpdater(sidebar);
const auto prin_host_opt = config->option("print_host");
const bool send_gcode_shown = prin_host_opt != nullptr && !prin_host_opt->value.empty();
- bool disconnect_shown = !RemovableDriveManager::get_instance().is_last_drive_removed();
- bool export_removable_shown = RemovableDriveManager::get_instance().get_drives_count() > 0;
// when a background processing is ON, export_btn and/or send_btn are showing
if (wxGetApp().app_config->get("background_processing") == "1")
{
+ RemovableDriveManager::RemovableDrivesStatus removable_media_status = wxGetApp().removable_drive_manager()->status();
if (sidebar->show_reslice(false) |
sidebar->show_export(true) |
sidebar->show_send(send_gcode_shown) |
- sidebar->show_export_removable(export_removable_shown) |
- sidebar->show_disconnect(disconnect_shown))
+ sidebar->show_export_removable(removable_media_status.has_removable_drives) |
+ sidebar->show_disconnect(removable_media_status.has_eject))
sidebar->Layout();
}
else
{
- if (sidebar->show_reslice(is_ready_to_slice) |
- sidebar->show_export(!is_ready_to_slice) |
- sidebar->show_send(send_gcode_shown && !is_ready_to_slice) |
- sidebar->show_export_removable(export_removable_shown && !is_ready_to_slice) |
- sidebar->show_disconnect(disconnect_shown && !is_ready_to_slice))
+ RemovableDriveManager::RemovableDrivesStatus removable_media_status;
+ if (! ready_to_slice)
+ removable_media_status = wxGetApp().removable_drive_manager()->status();
+ if (sidebar->show_reslice(ready_to_slice) |
+ sidebar->show_export(!ready_to_slice) |
+ sidebar->show_send(send_gcode_shown && !ready_to_slice) |
+ sidebar->show_export_removable(!ready_to_slice && removable_media_status.has_removable_drives) |
+ sidebar->show_disconnect(!ready_to_slice && removable_media_status.has_eject))
sidebar->Layout();
}
}
@@ -4733,51 +4805,38 @@ void Plater::export_gcode(bool prefer_removable)
return;
}
default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string()));
- auto start_dir = wxGetApp().app_config->get_last_output_dir(default_output_file.parent_path().string());
- bool removable_drives_connected = GUI::RemovableDriveManager::get_instance().update();
- if(prefer_removable)
- {
- if(removable_drives_connected)
- {
- auto start_dir_removable = wxGetApp().app_config->get_last_output_dir(default_output_file.parent_path().string(), true);
- if (RemovableDriveManager::get_instance().is_path_on_removable_drive(start_dir_removable))
- {
- start_dir = start_dir_removable;
- }else
- {
- start_dir = RemovableDriveManager::get_instance().get_drive_path();
- }
- }
+ AppConfig &appconfig = *wxGetApp().app_config;
+ RemovableDriveManager &removable_drive_manager = *wxGetApp().removable_drive_manager();
+ // Get a last save path, either to removable media or to an internal media.
+ std::string start_dir = appconfig.get_last_output_dir(default_output_file.parent_path().string(), prefer_removable);
+ if (prefer_removable) {
+ // Returns a path to a removable media if it exists, prefering start_dir. Update the internal removable drives database.
+ start_dir = removable_drive_manager.get_removable_drive_path(start_dir);
+ if (start_dir.empty())
+ // Direct user to the last internal media.
+ start_dir = appconfig.get_last_output_dir(default_output_file.parent_path().string(), false);
}
- wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _(L("Save G-code file as:")) : _(L("Save SL1 file as:")),
- start_dir,
- from_path(default_output_file.filename()),
- GUI::file_wildcards((printer_technology() == ptFFF) ? FT_GCODE : FT_PNGZIP, default_output_file.extension().string()),
- wxFD_SAVE | wxFD_OVERWRITE_PROMPT
- );
fs::path output_path;
- if (dlg.ShowModal() == wxID_OK) {
- fs::path path = into_path(dlg.GetPath());
- wxGetApp().app_config->update_last_output_dir(path.parent_path().string(), RemovableDriveManager::get_instance().is_path_on_removable_drive(path.parent_path().string()));
- output_path = std::move(path);
+ {
+ wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _(L("Save G-code file as:")) : _(L("Save SL1 file as:")),
+ start_dir,
+ from_path(default_output_file.filename()),
+ GUI::file_wildcards((printer_technology() == ptFFF) ? FT_GCODE : FT_PNGZIP, default_output_file.extension().string()),
+ wxFD_SAVE | wxFD_OVERWRITE_PROMPT
+ );
+ if (dlg.ShowModal() == wxID_OK)
+ output_path = into_path(dlg.GetPath());
}
- if (! output_path.empty())
- {
- std::string path = output_path.string();
- p->export_gcode(std::move(output_path), PrintHostJob());
- RemovableDriveManager::get_instance().update(0, false);
- RemovableDriveManager::get_instance().set_last_save_path(path);
- RemovableDriveManager::get_instance().verify_last_save_path();
-
- if(!RemovableDriveManager::get_instance().is_last_drive_removed())
- {
- RemovableDriveManager::get_instance().set_is_writing(true);
- RemovableDriveManager::get_instance().erase_callbacks();
- RemovableDriveManager::get_instance().add_remove_callback(std::bind(&Plater::drive_ejected_callback, this));
- }
-
+ if (! output_path.empty()) {
+ bool path_on_removable_media = removable_drive_manager.set_and_verify_last_save_path(output_path.string());
+ p->export_gcode(output_path, path_on_removable_media, PrintHostJob());
+ // Storing a path to AppConfig either as path to removable media or a path to internal media.
+ // is_path_on_removable_drive() is called with the "true" parameter to update its internal database as the user may have shuffled the external drives
+ // while the dialog was open.
+ appconfig.update_last_output_dir(output_path.parent_path().string(), path_on_removable_media);
+ p->writing_to_removable_device = path_on_removable_media;
}
}
@@ -5091,32 +5150,16 @@ void Plater::send_gcode()
upload_job.upload_data.upload_path = dlg.filename();
upload_job.upload_data.start_print = dlg.start_print();
- p->export_gcode(fs::path(), std::move(upload_job));
+ p->export_gcode(fs::path(), false, std::move(upload_job));
}
}
+// Called when the Eject button is pressed.
void Plater::eject_drive()
{
- RemovableDriveManager::get_instance().update(0, true);
- RemovableDriveManager::get_instance().erase_callbacks();
- RemovableDriveManager::get_instance().add_remove_callback(std::bind(&Plater::drive_ejected_callback, this));
- RemovableDriveManager::get_instance().eject_drive(RemovableDriveManager::get_instance().get_last_save_path());
-
+ wxBusyCursor wait;
+ wxGetApp().removable_drive_manager()->eject_drive();
}
-void Plater::drive_ejected_callback()
-{
- if (RemovableDriveManager::get_instance().get_did_eject())
- {
- RemovableDriveManager::get_instance().set_did_eject(false);
- show_info(this,
- (boost::format(_utf8(L("Unmounting successful. The device %s(%s) can now be safely removed from the computer.")))
- % RemovableDriveManager::get_instance().get_ejected_name()
- % RemovableDriveManager::get_instance().get_ejected_path()).str());
- }
- p->show_action_buttons(false);
-}
-
-
void Plater::take_snapshot(const std::string &snapshot_name) { p->take_snapshot(snapshot_name); }
void Plater::take_snapshot(const wxString &snapshot_name) { p->take_snapshot(snapshot_name); }
@@ -5483,7 +5526,7 @@ void Plater::schedule_background_process(bool schedule/* = true*/)
this->p->suppressed_backround_processing_update = false;
}
-bool Plater::is_background_process_running() const
+bool Plater::is_background_process_update_scheduled() const
{
return this->p->background_process_timer.IsRunning();
}
@@ -5499,7 +5542,7 @@ void Plater::suppress_background_process(const bool stop_background_process)
void Plater::fix_through_netfabb(const int obj_idx, const int vol_idx/* = -1*/) { p->fix_through_netfabb(obj_idx, vol_idx); }
void Plater::update_object_menu() { p->update_object_menu(); }
-void Plater::show_action_buttons(const bool is_ready_to_slice) const { p->show_action_buttons(is_ready_to_slice); }
+void Plater::show_action_buttons(const bool ready_to_slice) const { p->show_action_buttons(ready_to_slice); }
void Plater::copy_selection_to_clipboard()
{
@@ -5540,6 +5583,11 @@ const Camera& Plater::get_camera() const
return p->camera;
}
+Camera& Plater::get_camera()
+{
+ return p->camera;
+}
+
const Mouse3DController& Plater::get_mouse3d_controller() const
{
return p->mouse3d_controller;
@@ -5599,16 +5647,36 @@ bool Plater::can_reload_from_disk() const { return p->can_reload_from_disk(); }
const UndoRedo::Stack& Plater::undo_redo_stack_main() const { return p->undo_redo_stack_main(); }
void Plater::enter_gizmos_stack() { p->enter_gizmos_stack(); }
void Plater::leave_gizmos_stack() { p->leave_gizmos_stack(); }
+bool Plater::inside_snapshot_capture() { return p->inside_snapshot_capture(); }
+
+// Wrapper around wxWindow::PopupMenu to suppress error messages popping out while tracking the popup menu.
+bool Plater::PopupMenu(wxMenu *menu, const wxPoint& pos)
+{
+ // Don't want to wake up and trigger reslicing while tracking the pop-up menu.
+ SuppressBackgroundProcessingUpdate sbpu;
+ // When tracking a pop-up menu, postpone error messages from the slicing result.
+ m_tracking_popup_menu = true;
+ bool out = this->wxPanel::PopupMenu(menu, pos);
+ m_tracking_popup_menu = false;
+ if (! m_tracking_popup_menu_error_message.empty()) {
+ // Don't know whether the CallAfter is necessary, but it should not hurt.
+ // The menus likely sends out some commands, so we may be safer if the dialog is shown after the menu command is processed.
+ wxString message = std::move(m_tracking_popup_menu_error_message);
+ wxTheApp->CallAfter([message, this]() { show_error(this, message); });
+ m_tracking_popup_menu_error_message.clear();
+ }
+ return out;
+}
SuppressBackgroundProcessingUpdate::SuppressBackgroundProcessingUpdate() :
- m_was_running(wxGetApp().plater()->is_background_process_running())
+ m_was_scheduled(wxGetApp().plater()->is_background_process_update_scheduled())
{
- wxGetApp().plater()->suppress_background_process(m_was_running);
+ wxGetApp().plater()->suppress_background_process(m_was_scheduled);
}
SuppressBackgroundProcessingUpdate::~SuppressBackgroundProcessingUpdate()
{
- wxGetApp().plater()->schedule_background_process(m_was_running);
+ wxGetApp().plater()->schedule_background_process(m_was_scheduled);
}
}} // namespace Slic3r::GUI
diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp
index f3b0181a01..fc6f839faa 100644
--- a/src/slic3r/GUI/Plater.hpp
+++ b/src/slic3r/GUI/Plater.hpp
@@ -205,12 +205,11 @@ public:
void changed_object(int obj_idx);
void changed_objects(const std::vector& object_idxs);
void schedule_background_process(bool schedule = true);
- bool is_background_process_running() const;
+ bool is_background_process_update_scheduled() const;
void suppress_background_process(const bool stop_background_process) ;
void fix_through_netfabb(const int obj_idx, const int vol_idx = -1);
void send_gcode();
void eject_drive();
- void drive_ejected_callback();
void take_snapshot(const std::string &snapshot_name);
void take_snapshot(const wxString &snapshot_name);
@@ -278,6 +277,7 @@ public:
bool init_view_toolbar();
const Camera& get_camera() const;
+ Camera& get_camera();
const Mouse3DController& get_mouse3d_controller() const;
Mouse3DController& get_mouse3d_controller();
@@ -316,10 +316,22 @@ public:
Plater *m_plater;
};
+ bool inside_snapshot_capture();
+
+ // Wrapper around wxWindow::PopupMenu to suppress error messages popping out while tracking the popup menu.
+ bool PopupMenu(wxMenu *menu, const wxPoint& pos = wxDefaultPosition);
+ bool PopupMenu(wxMenu *menu, int x, int y) { return this->PopupMenu(menu, wxPoint(x, y)); }
+
private:
struct priv;
std::unique_ptr p;
+ // Set true during PopupMenu() tracking to suppress immediate error message boxes.
+ // The error messages are collected to m_tracking_popup_menu_error_message instead and these error messages
+ // are shown after the pop-up dialog closes.
+ bool m_tracking_popup_menu = false;
+ wxString m_tracking_popup_menu_error_message;
+
void suppress_snapshots();
void allow_snapshots();
@@ -332,7 +344,7 @@ public:
SuppressBackgroundProcessingUpdate();
~SuppressBackgroundProcessingUpdate();
private:
- bool m_was_running;
+ bool m_was_scheduled;
};
}}
diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp
index e73133fdff..a5d8b23958 100644
--- a/src/slic3r/GUI/Preset.cpp
+++ b/src/slic3r/GUI/Preset.cpp
@@ -189,6 +189,9 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem
default_materials_field = section.second.get("default_filaments", "");
if (Slic3r::unescape_strings_cstyle(default_materials_field, model.default_materials)) {
Slic3r::sort_remove_duplicates(model.default_materials);
+ if (! model.default_materials.empty() && model.default_materials.front().empty())
+ // An empty material was inserted into the list of default materials. Remove it.
+ model.default_materials.erase(model.default_materials.begin());
} else {
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: Malformed default_materials field: `%2%`") % id % default_materials_field;
}
@@ -378,7 +381,7 @@ void Preset::set_visible_from_appconfig(const AppConfig &app_config)
return;
is_visible = app_config.get_variant(vendor->id, model, variant);
} else if (type == TYPE_FILAMENT || type == TYPE_SLA_MATERIAL) {
- const char *section_name = (type == TYPE_FILAMENT) ? "filaments" : "sla_materials";
+ const std::string §ion_name = (type == TYPE_FILAMENT) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS;
if (app_config.has_section(section_name)) {
// Check whether this profile is marked as "installed" in PrusaSlicer.ini,
// or whether a profile is marked as "installed", which this profile may have been renamed from.
@@ -410,7 +413,7 @@ const std::vector& Preset::print_options()
"perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "infill_speed", "solid_infill_speed",
"top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed",
"bridge_speed", "gap_fill_speed", "travel_speed", "first_layer_speed", "perimeter_acceleration", "infill_acceleration",
- "bridge_acceleration", "first_layer_acceleration", "default_acceleration", "skirts", "skirt_distance", "skirt_height",
+ "bridge_acceleration", "first_layer_acceleration", "default_acceleration", "skirts", "skirt_distance", "skirt_height", "draft_shield",
"min_skirt_length", "brim_width", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers",
"raft_layers", "support_material_pattern", "support_material_with_sheath", "support_material_spacing",
"support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers",
@@ -851,7 +854,7 @@ Preset& PresetCollection::load_preset(const std::string &path, const std::string
return preset;
}
-void PresetCollection::save_current_preset(const std::string &new_name)
+void PresetCollection::save_current_preset(const std::string &new_name, bool detach)
{
// 1) Find the preset with a new_name or create a new one,
// initialize it with the edited config.
@@ -866,6 +869,13 @@ void PresetCollection::save_current_preset(const std::string &new_name)
preset.config = std::move(m_edited_preset.config);
// The newly saved preset will be activated -> make it visible.
preset.is_visible = true;
+ if (detach) {
+ // Clear the link to the parent profile.
+ preset.vendor = nullptr;
+ preset.inherits().clear();
+ preset.alias.clear();
+ preset.renamed_from.clear();
+ }
} else {
// Creating a new preset.
Preset &preset = *m_presets.insert(it, m_edited_preset);
@@ -874,7 +884,12 @@ void PresetCollection::save_current_preset(const std::string &new_name)
preset.name = new_name;
preset.file = this->path_from_name(new_name);
preset.vendor = nullptr;
- if (preset.is_system) {
+ preset.alias.clear();
+ preset.renamed_from.clear();
+ if (detach) {
+ // Clear the link to the parent profile.
+ inherits.clear();
+ } else if (preset.is_system) {
// Inheriting from a system preset.
inherits = /* preset.vendor->name + "/" + */ old_name;
} else if (inherits.empty()) {
@@ -1017,6 +1032,14 @@ const std::string& PresetCollection::get_preset_name_by_alias(const std::string&
return alias;
}
+const std::string* PresetCollection::get_preset_name_renamed(const std::string &old_name) const
+{
+ auto it_renamed = m_map_system_profile_renamed.find(old_name);
+ if (it_renamed != m_map_system_profile_renamed.end())
+ return &it_renamed->second;
+ return nullptr;
+}
+
const std::string& PresetCollection::get_suffix_modified() {
return g_suffix_modified;
}
@@ -1061,6 +1084,7 @@ size_t PresetCollection::update_compatible_internal(const PresetWithVendorProfil
const ConfigOption *opt = active_printer.preset.config.option("nozzle_diameter");
if (opt)
config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast(opt)->values.size()));
+ bool some_compatible = false;
for (size_t idx_preset = m_num_default_presets; idx_preset < m_presets.size(); ++ idx_preset) {
bool selected = idx_preset == m_idx_selected;
Preset &preset_selected = m_presets[idx_preset];
@@ -1069,6 +1093,7 @@ size_t PresetCollection::update_compatible_internal(const PresetWithVendorProfil
const PresetWithVendorProfile this_preset_with_vendor_profile = this->get_preset_with_vendor_profile(preset_edited);
bool was_compatible = preset_edited.is_compatible;
preset_edited.is_compatible = is_compatible_with_printer(this_preset_with_vendor_profile, active_printer, &config);
+ some_compatible |= preset_edited.is_compatible;
if (active_print != nullptr)
preset_edited.is_compatible &= is_compatible_with_print(this_preset_with_vendor_profile, *active_print, active_printer);
if (! preset_edited.is_compatible && selected &&
@@ -1077,6 +1102,10 @@ size_t PresetCollection::update_compatible_internal(const PresetWithVendorProfil
if (selected)
preset_selected.is_compatible = preset_edited.is_compatible;
}
+ // Update visibility of the default profiles here if the defaults are suppressed, the current profile is not compatible and we don't want to select another compatible profile.
+ if (m_idx_selected >= m_num_default_presets && m_default_suppressed)
+ for (size_t i = 0; i < m_num_default_presets; ++ i)
+ m_presets[i].is_visible = ! some_compatible;
return m_idx_selected;
}
diff --git a/src/slic3r/GUI/Preset.hpp b/src/slic3r/GUI/Preset.hpp
index 224adaeaa1..9c2cebd50c 100644
--- a/src/slic3r/GUI/Preset.hpp
+++ b/src/slic3r/GUI/Preset.hpp
@@ -237,6 +237,7 @@ public:
static void update_suffix_modified();
static const std::string& suffix_modified();
+ static std::string remove_suffix_modified(const std::string& name);
static void normalize(DynamicPrintConfig &config);
// Report configuration fields, which are misplaced into a wrong group, remove them from the config.
static std::string remove_invalid_keys(DynamicPrintConfig &config, const DynamicPrintConfig &default_config);
@@ -244,7 +245,6 @@ public:
protected:
friend class PresetCollection;
friend class PresetBundle;
- static std::string remove_suffix_modified(const std::string &name);
};
bool is_compatible_with_print (const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_print, const PresetWithVendorProfile &active_printer);
@@ -312,7 +312,7 @@ public:
// Save the preset under a new name. If the name is different from the old one,
// a new preset is stored into the list of presets.
// All presets are marked as not modified and the new preset is activated.
- void save_current_preset(const std::string &new_name);
+ void save_current_preset(const std::string &new_name, bool detach = false);
// Delete the current preset, activate the first visible preset.
// returns true if the preset was deleted successfully.
@@ -361,7 +361,8 @@ public:
PresetWithVendorProfile get_preset_with_vendor_profile(const Preset &preset) const;
PresetWithVendorProfile get_edited_preset_with_vendor_profile() const { return this->get_preset_with_vendor_profile(this->get_edited_preset()); }
- const std::string& get_preset_name_by_alias(const std::string& alias) const;
+ const std::string& get_preset_name_by_alias(const std::string& alias) const;
+ const std::string* get_preset_name_renamed(const std::string &old_name) const;
// used to update preset_choice from Tab
const std::deque& get_presets() const { return m_presets; }
diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/slic3r/GUI/PresetBundle.cpp
index e36c85d685..84003c9777 100644
--- a/src/slic3r/GUI/PresetBundle.cpp
+++ b/src/slic3r/GUI/PresetBundle.cpp
@@ -291,17 +291,7 @@ std::string PresetBundle::load_system_presets()
this->reset(false);
}
- this->prints .update_map_system_profile_renamed();
- this->sla_prints .update_map_system_profile_renamed();
- this->filaments .update_map_system_profile_renamed();
- this->sla_materials.update_map_system_profile_renamed();
- this->printers .update_map_system_profile_renamed();
-
- this->prints .update_map_alias_to_profile_name();
- this->sla_prints .update_map_alias_to_profile_name();
- this->filaments .update_map_alias_to_profile_name();
- this->sla_materials.update_map_alias_to_profile_name();
-
+ this->update_system_maps();
return errors_cummulative;
}
@@ -326,6 +316,20 @@ std::vector PresetBundle::merge_presets(PresetBundle &&other)
return duplicate_prints;
}
+void PresetBundle::update_system_maps()
+{
+ this->prints .update_map_system_profile_renamed();
+ this->sla_prints .update_map_system_profile_renamed();
+ this->filaments .update_map_system_profile_renamed();
+ this->sla_materials.update_map_system_profile_renamed();
+ this->printers .update_map_system_profile_renamed();
+
+ this->prints .update_map_alias_to_profile_name();
+ this->sla_prints .update_map_alias_to_profile_name();
+ this->filaments .update_map_alias_to_profile_name();
+ this->sla_materials.update_map_alias_to_profile_name();
+}
+
static inline std::string remove_ini_suffix(const std::string &name)
{
std::string out = name;
@@ -339,9 +343,9 @@ static inline std::string remove_ini_suffix(const std::string &name)
// If the "vendor" section is missing, enable all models and variants of the particular vendor.
void PresetBundle::load_installed_printers(const AppConfig &config)
{
- for (auto &preset : printers) {
+ this->update_system_maps();
+ for (auto &preset : printers)
preset.set_visible_from_appconfig(config);
- }
}
const std::string& PresetBundle::get_preset_name_by_alias( const Preset::Type& preset_type, const std::string& alias) const
@@ -369,7 +373,7 @@ void PresetBundle::load_installed_filaments(AppConfig &config)
if (printer.is_visible && printer.printer_technology() == ptFFF) {
const PresetWithVendorProfile printer_with_vendor_profile = printers.get_preset_with_vendor_profile(printer);
for (const Preset &filament : filaments)
- if (is_compatible_with_printer(filaments.get_preset_with_vendor_profile(filament), printer_with_vendor_profile))
+ if (filament.is_system && is_compatible_with_printer(filaments.get_preset_with_vendor_profile(filament), printer_with_vendor_profile))
compatible_filaments.insert(&filament);
}
// and mark these filaments as installed, therefore this code will not be executed at the next start of the application.
@@ -392,7 +396,7 @@ void PresetBundle::load_installed_sla_materials(AppConfig &config)
if (printer.is_visible && printer.printer_technology() == ptSLA) {
const PresetWithVendorProfile printer_with_vendor_profile = printers.get_preset_with_vendor_profile(printer);
for (const Preset &material : sla_materials)
- if (is_compatible_with_printer(sla_materials.get_preset_with_vendor_profile(material), printer_with_vendor_profile))
+ if (material.is_system && is_compatible_with_printer(sla_materials.get_preset_with_vendor_profile(material), printer_with_vendor_profile))
comp_sla_materials.insert(&material);
}
// and mark these SLA materials as installed, therefore this code will not be executed at the next start of the application.
@@ -705,7 +709,10 @@ void PresetBundle::load_config_file(const std::string &path)
boost::nowide::ifstream ifs(path);
boost::property_tree::read_ini(ifs, tree);
} catch (const std::ifstream::failure &err) {
- throw std::runtime_error(std::string("The config file cannot be loaded: ") + path + "\n\tReason: " + err.what());
+ throw std::runtime_error(std::string("The Config Bundle cannot be loaded: ") + path + "\n\tReason: " + err.what());
+ } catch (const boost::property_tree::file_parser_error &err) {
+ throw std::runtime_error((boost::format("Failed loading the Config Bundle \"%1%\": %2% at line %3%")
+ % err.filename() % err.message() % err.line()).str());
} catch (const std::runtime_error &err) {
throw std::runtime_error(std::string("Failed loading the preset file: ") + path + "\n\tReason: " + err.what());
}
@@ -1066,7 +1073,11 @@ static void flatten_configbundle_hierarchy(boost::property_tree::ptree &tree, co
// Iterate in a reverse order, so the last change will be placed first in merged.
for (auto it_inherits = prst->inherits.rbegin(); it_inherits != prst->inherits.rend(); ++ it_inherits)
for (auto it = (*it_inherits)->node->begin(); it != (*it_inherits)->node->end(); ++ it)
- if (prst->node->find(it->first) == prst->node->not_found())
+ if (it->first == "renamed_from") {
+ // Don't inherit "renamed_from" flag, it does not make sense. The "renamed_from" flag only makes sense for a concrete preset.
+ if (boost::starts_with((*it_inherits)->name, "*"))
+ BOOST_LOG_TRIVIAL(error) << boost::format("Nonpublic intermediate preset %1% contains a \"renamed_from\" field, which is ignored") % (*it_inherits)->name;
+ } else if (prst->node->find(it->first) == prst->node->not_found())
prst->node->add_child(it->first, it->second);
}
@@ -1109,8 +1120,13 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
const VendorProfile *vendor_profile = nullptr;
if (flags & (LOAD_CFGBNDLE_SYSTEM | LOAD_CFGBUNDLE_VENDOR_ONLY)) {
auto vp = VendorProfile::from_ini(tree, path);
- if (vp.num_variants() == 0)
+ if (vp.models.size() == 0) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No printer model defined.") % path;
return 0;
+ } else if (vp.num_variants() == 0) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No printer variant defined") % path;
+ return 0;
+ }
vendor_profile = &this->vendors.insert({vp.id, vp}).first->second;
}
@@ -1599,21 +1615,23 @@ void PresetBundle::update_plater_filament_ui(unsigned int idx_extruder, GUI::Pre
// To avoid asserts, each added bitmap to wxBitmapCombobox should be the same size, so
// set a bitmap height to m_bitmapLock->GetHeight()
- // Note, under OSX we should use a ScaledHeight because of Retina scale
+ //
+ // To avoid asserts, each added bitmap to wxBitmapCombobox should be the same size.
+ // But for some display scaling (for example 125% or 175%) normal_icon_width differs from icon width.
+ // So:
+ // for nonsystem presets set a width of empty bitmap to m_bitmapLock->GetWidth()
+ // for compatible presets set a width of empty bitmap to m_bitmapIncompatible->GetWidth()
+ //
+ // Note, under OSX we should use a Scaled Height/Width because of Retina scale
#ifdef __APPLE__
const int icon_height = m_bitmapLock->GetScaledHeight();
+ const int lock_icon_width = m_bitmapLock->GetScaledWidth();
+ const int flag_icon_width = m_bitmapIncompatible->GetScaledWidth();
#else
const int icon_height = m_bitmapLock->GetHeight();
-#endif
-
- /* To avoid asserts, each added bitmap to wxBitmapCombobox should be the same size.
- * But for some display scaling (for example 125% or 175%) normal_icon_width differs from icon width.
- * So:
- * for nonsystem presets set a width of empty bitmap to m_bitmapLock->GetWidth()
- * for compatible presets set a width of empty bitmap to m_bitmapIncompatible->GetWidth()
- **/
const int lock_icon_width = m_bitmapLock->GetWidth();
const int flag_icon_width = m_bitmapIncompatible->GetWidth();
+#endif
wxString tooltip = "";
diff --git a/src/slic3r/GUI/PresetBundle.hpp b/src/slic3r/GUI/PresetBundle.hpp
index 33c9d5ff4a..bf1bba21db 100644
--- a/src/slic3r/GUI/PresetBundle.hpp
+++ b/src/slic3r/GUI/PresetBundle.hpp
@@ -42,6 +42,8 @@ public:
PresetCollection sla_prints;
PresetCollection filaments;
PresetCollection sla_materials;
+ PresetCollection& materials(PrinterTechnology pt) { return pt == ptFFF ? this->filaments : this->sla_materials; }
+ const PresetCollection& materials(PrinterTechnology pt) const { return pt == ptFFF ? this->filaments : this->sla_materials; }
PrinterPresetCollection printers;
// Filament preset names for a multi-extruder or multi-material print.
// extruders.size() should be the same as printers.get_edited_preset().config.nozzle_diameter.size()
@@ -144,6 +146,8 @@ private:
std::string load_system_presets();
// Merge one vendor's presets with the other vendor's presets, report duplicates.
std::vector merge_presets(PresetBundle &&other);
+ // Update renamed_from and alias maps of system profiles.
+ void update_system_maps();
// Set the is_visible flag for filaments and sla materials,
// apply defaults based on enabled printers when no filaments/materials are installed.
diff --git a/src/slic3r/GUI/RemovableDriveManager.cpp b/src/slic3r/GUI/RemovableDriveManager.cpp
index aa7a3d6a31..cdb1c1d458 100644
--- a/src/slic3r/GUI/RemovableDriveManager.cpp
+++ b/src/slic3r/GUI/RemovableDriveManager.cpp
@@ -1,6 +1,8 @@
#include "RemovableDriveManager.hpp"
-#include
-#include "boost/nowide/convert.hpp"
+#include
+
+#include
+#include
#if _WIN32
#include
@@ -9,11 +11,9 @@
#include
#include
-GUID WceusbshGUID = { 0x25dbce51, 0x6c8f, 0x4a72,
- 0x8a,0x6d,0xb5,0x4c,0x2b,0x4f,0xc8,0x35 };
#else
-//linux includes
+// unix, linux & OSX includes
#include
#include
#include
@@ -21,590 +21,461 @@ GUID WceusbshGUID = { 0x25dbce51, 0x6c8f, 0x4a72,
#include
#include
#include
+#include
#endif
namespace Slic3r {
namespace GUI {
+wxDEFINE_EVENT(EVT_REMOVABLE_DRIVE_EJECTED, RemovableDriveEjectEvent);
+wxDEFINE_EVENT(EVT_REMOVABLE_DRIVES_CHANGED, RemovableDrivesChangedEvent);
+
#if _WIN32
-/* currently not used, left for possible future use
-INT_PTR WINAPI WinProcCallback(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
-*/
-void RemovableDriveManager::search_for_drives()
+std::vector RemovableDriveManager::search_for_removable_drives() const
{
- m_current_drives.clear();
//get logical drives flags by letter in alphabetical order
- DWORD drives_mask = GetLogicalDrives();
- for (size_t i = 0; i < 26; i++)
- {
- if(drives_mask & (1 << i))
- {
- std::string path (1,(char)('A' + i));
- path+=":";
- UINT drive_type = GetDriveTypeA(path.c_str());
+ DWORD drives_mask = ::GetLogicalDrives();
+
+ // Allocate the buffers before the loop.
+ std::wstring volume_name;
+ std::wstring file_system_name;
+ // Iterate the Windows drives from 'A' to 'Z'
+ std::vector current_drives;
+ for (size_t i = 0; i < 26; ++ i)
+ if (drives_mask & (1 << i)) {
+ std::string path { char('A' + i), ':' };
+ UINT drive_type = ::GetDriveTypeA(path.c_str());
// DRIVE_REMOVABLE on W are sd cards and usb thumbnails (not usb harddrives)
- if (drive_type == DRIVE_REMOVABLE)
- {
+ if (drive_type == DRIVE_REMOVABLE) {
// get name of drive
std::wstring wpath = boost::nowide::widen(path);
- std::wstring volume_name;
- volume_name.resize(1024);
- std::wstring file_system_name;
- file_system_name.resize(1024);
- LPWSTR lp_volume_name_buffer = new wchar_t;
- BOOL error = GetVolumeInformationW(wpath.c_str(), &volume_name[0], sizeof(volume_name), NULL, NULL, NULL, &file_system_name[0], sizeof(file_system_name));
- if(error != 0)
- {
- volume_name.erase(std::find(volume_name.begin(), volume_name.end(), '\0'), volume_name.end());
- if (file_system_name != L"")
- {
+ volume_name.resize(MAX_PATH + 1);
+ file_system_name.resize(MAX_PATH + 1);
+ BOOL error = ::GetVolumeInformationW(wpath.c_str(), volume_name.data(), sizeof(volume_name), nullptr, nullptr, nullptr, file_system_name.data(), sizeof(file_system_name));
+ if (error != 0) {
+ volume_name.erase(volume_name.begin() + wcslen(volume_name.c_str()), volume_name.end());
+ if (! file_system_name.empty()) {
ULARGE_INTEGER free_space;
- GetDiskFreeSpaceExA(path.c_str(), &free_space, NULL, NULL);
- if (free_space.QuadPart > 0)
- {
+ ::GetDiskFreeSpaceExW(wpath.c_str(), &free_space, nullptr, nullptr);
+ if (free_space.QuadPart > 0) {
path += "\\";
- m_current_drives.push_back(DriveData(boost::nowide::narrow(volume_name), path));
+ current_drives.emplace_back(DriveData{ boost::nowide::narrow(volume_name), path });
}
}
}
}
}
- }
+ return current_drives;
}
-void RemovableDriveManager::eject_drive(const std::string &path)
+
+// Called from UI therefore it blocks the UI thread.
+// It also blocks updates at the worker thread.
+// Win32 implementation.
+void RemovableDriveManager::eject_drive()
{
- if(m_current_drives.empty())
+ if (m_last_save_path.empty())
return;
- for (auto it = m_current_drives.begin(); it != m_current_drives.end(); ++it)
- {
- if ((*it).path == path)
- {
- // get handle to device
- std::string mpath = "\\\\.\\" + path;
- mpath = mpath.substr(0, mpath.size() - 1);
- HANDLE handle = CreateFileA(mpath.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
- if (handle == INVALID_HANDLE_VALUE)
- {
- std::cerr << "Ejecting " << mpath << " failed " << GetLastError() << " \n";
- return;
- }
- DWORD deviceControlRetVal(0);
- //these 3 commands should eject device safely but they dont, the device does disappear from file explorer but the "device was safely remove" notification doesnt trigger.
- //sd cards does trigger WM_DEVICECHANGE messege, usb drives dont
-
- DeviceIoControl(handle, FSCTL_LOCK_VOLUME, nullptr, 0, nullptr, 0, &deviceControlRetVal, nullptr);
- DeviceIoControl(handle, FSCTL_DISMOUNT_VOLUME, nullptr, 0, nullptr, 0, &deviceControlRetVal, nullptr);
- // some implemenatations also calls IOCTL_STORAGE_MEDIA_REMOVAL here but it returns error to me
- BOOL error = DeviceIoControl(handle, IOCTL_STORAGE_EJECT_MEDIA, nullptr, 0, nullptr, 0, &deviceControlRetVal, nullptr);
- if (error == 0)
- {
- CloseHandle(handle);
- std::cerr << "Ejecting " << mpath << " failed " << deviceControlRetVal << " " << GetLastError() << " \n";
- return;
- }
+
+#ifndef REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS
+ this->update();
+#endif // REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS
+
+ tbb::mutex::scoped_lock lock(m_drives_mutex);
+ auto it_drive_data = this->find_last_save_path_drive_data();
+ if (it_drive_data != m_current_drives.end()) {
+ // get handle to device
+ std::string mpath = "\\\\.\\" + m_last_save_path;
+ mpath = mpath.substr(0, mpath.size() - 1);
+ HANDLE handle = CreateFileW(boost::nowide::widen(mpath).c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
+ if (handle == INVALID_HANDLE_VALUE) {
+ std::cerr << "Ejecting " << mpath << " failed " << GetLastError() << " \n";
+ assert(m_callback_evt_handler);
+ if (m_callback_evt_handler)
+ wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair(*it_drive_data, false)));
+ return;
+ }
+ DWORD deviceControlRetVal(0);
+ //these 3 commands should eject device safely but they dont, the device does disappear from file explorer but the "device was safely remove" notification doesnt trigger.
+ //sd cards does trigger WM_DEVICECHANGE messege, usb drives dont
+ DeviceIoControl(handle, FSCTL_LOCK_VOLUME, nullptr, 0, nullptr, 0, &deviceControlRetVal, nullptr);
+ DeviceIoControl(handle, FSCTL_DISMOUNT_VOLUME, nullptr, 0, nullptr, 0, &deviceControlRetVal, nullptr);
+ // some implemenatations also calls IOCTL_STORAGE_MEDIA_REMOVAL here but it returns error to me
+ BOOL error = DeviceIoControl(handle, IOCTL_STORAGE_EJECT_MEDIA, nullptr, 0, nullptr, 0, &deviceControlRetVal, nullptr);
+ if (error == 0) {
CloseHandle(handle);
- m_did_eject = true;
- m_current_drives.erase(it);
- m_ejected_path = m_last_save_path;
- m_ejected_name = m_last_save_name;
- break;
+ BOOST_LOG_TRIVIAL(error) << "Ejecting " << mpath << " failed " << deviceControlRetVal << " " << GetLastError() << " \n";
+ assert(m_callback_evt_handler);
+ if (m_callback_evt_handler)
+ wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair(*it_drive_data, false)));
+ return;
}
+ CloseHandle(handle);
+ assert(m_callback_evt_handler);
+ if (m_callback_evt_handler)
+ wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair< DriveData, bool >(std::move(*it_drive_data), true)));
+ m_current_drives.erase(it_drive_data);
}
}
-bool RemovableDriveManager::is_path_on_removable_drive(const std::string &path)
+
+std::string RemovableDriveManager::get_removable_drive_path(const std::string &path)
{
+#ifndef REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS
+ this->update();
+#endif // REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS
+
+ tbb::mutex::scoped_lock lock(m_drives_mutex);
if (m_current_drives.empty())
- return false;
+ return std::string();
std::size_t found = path.find_last_of("\\");
std::string new_path = path.substr(0, found);
- int letter = PathGetDriveNumberA(new_path.c_str());
- for (auto it = m_current_drives.begin(); it != m_current_drives.end(); ++it)
- {
- char drive = (*it).path[0];
- if (drive == ('A' + letter))
- return true;
+ int letter = PathGetDriveNumberW(boost::nowide::widen(new_path).c_str());
+ for (const DriveData &drive_data : m_current_drives) {
+ char drive = drive_data.path[0];
+ if (drive == 'A' + letter)
+ return path;
}
- return false;
+ return m_current_drives.front().path;
}
-std::string RemovableDriveManager::get_drive_from_path(const std::string& path)
+
+std::string RemovableDriveManager::get_removable_drive_from_path(const std::string& path)
{
+ tbb::mutex::scoped_lock lock(m_drives_mutex);
std::size_t found = path.find_last_of("\\");
std::string new_path = path.substr(0, found);
- int letter = PathGetDriveNumberA(new_path.c_str());
- for (auto it = m_current_drives.begin(); it != m_current_drives.end(); ++it)
- {
- char drive = (*it).path[0];
- if (drive == ('A' + letter))
- return (*it).path;
+ int letter = PathGetDriveNumberW(boost::nowide::widen(new_path).c_str());
+ for (const DriveData &drive_data : m_current_drives) {
+ assert(! drive_data.path.empty());
+ if (drive_data.path.front() == 'A' + letter)
+ return drive_data.path;
}
- return "";
+ return std::string();
}
-void RemovableDriveManager::register_window()
-{
- //creates new unvisible window that is recieving callbacks from system
- // structure to register
- /* currently not used, left for possible future use
- WNDCLASSEX wndClass;
- wndClass.cbSize = sizeof(WNDCLASSEX);
- wndClass.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
- wndClass.hInstance = reinterpret_cast(GetModuleHandle(0));
- wndClass.lpfnWndProc = reinterpret_cast(WinProcCallback);//this is callback
- wndClass.cbClsExtra = 0;
- wndClass.cbWndExtra = 0;
- wndClass.hIcon = LoadIcon(0, IDI_APPLICATION);
- wndClass.hbrBackground = CreateSolidBrush(RGB(192, 192, 192));
- wndClass.hCursor = LoadCursor(0, IDC_ARROW);
- wndClass.lpszClassName = L"PrusaSlicer_aux_class";
- wndClass.lpszMenuName = NULL;
- wndClass.hIconSm = wndClass.hIcon;
- if(!RegisterClassEx(&wndClass))
- {
- DWORD err = GetLastError();
- return;
- }
- HWND hWnd = CreateWindowEx(
- WS_EX_NOACTIVATE,
- L"PrusaSlicer_aux_class",
- L"PrusaSlicer_aux_wnd",
- WS_DISABLED, // style
- CW_USEDEFAULT, 0,
- 640, 480,
- NULL, NULL,
- GetModuleHandle(NULL),
- NULL);
- if(hWnd == NULL)
- {
- DWORD err = GetLastError();
+// Called by Win32 Volume arrived / detached callback.
+void RemovableDriveManager::volumes_changed()
+{
+ if (m_initialized) {
+ // Signal the worker thread to wake up and enumerate removable drives.
+ m_wakeup = true;
+ m_thread_stop_condition.notify_all();
}
- //ShowWindow(hWnd, SW_SHOWNORMAL);
- UpdateWindow(hWnd);
- */
}
-/* currently not used, left for possible future use
-INT_PTR WINAPI WinProcCallback(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
+
+#else
+
+namespace search_for_drives_internal
{
- // here we need to catch messeges about device removal
- // problem is that when ejecting usb (how is it implemented above) there is no messege dispached. Only after physical removal of the device.
- //uncomment register_window() in init() to register and comment update() in GUI_App.cpp (only for windows!) to stop recieving periodical updates
-
- LRESULT lRet = 1;
- static HDEVNOTIFY hDeviceNotify;
-
- switch (message)
+ static bool compare_filesystem_id(const std::string &path_a, const std::string &path_b)
{
- case WM_CREATE:
- DEV_BROADCAST_DEVICEINTERFACE NotificationFilter;
+ struct stat buf;
+ stat(path_a.c_str() ,&buf);
+ dev_t id_a = buf.st_dev;
+ stat(path_b.c_str() ,&buf);
+ dev_t id_b = buf.st_dev;
+ return id_a == id_b;
+ }
- ZeroMemory(&NotificationFilter, sizeof(NotificationFilter));
- NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
- NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
- NotificationFilter.dbcc_classguid = WceusbshGUID;
-
- hDeviceNotify = RegisterDeviceNotification(hWnd, &NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE);
- break;
-
- case WM_DEVICECHANGE:
+ void inspect_file(const std::string &path, const std::string &parent_path, std::vector &out)
{
- // here is the important
- if(wParam == DBT_DEVICEREMOVECOMPLETE)
- {
-- RemovableDriveManager::get_instance().update(0, true);
+ //confirms if the file is removable drive and adds it to vector
+
+ //if not same file system - could be removable drive
+ if (! compare_filesystem_id(path, parent_path)) {
+ //free space
+ boost::filesystem::space_info si = boost::filesystem::space(path);
+ if (si.available != 0) {
+ //user id
+ struct stat buf;
+ stat(path.c_str(), &buf);
+ uid_t uid = buf.st_uid;
+ std::string username(std::getenv("USER"));
+ struct passwd *pw = getpwuid(uid);
+ if (pw != 0 && pw->pw_name == username)
+ out.emplace_back(DriveData{ boost::filesystem::basename(boost::filesystem::path(path)), path });
+ }
}
}
- break;
-
- default:
- // Send all other messages on to the default windows handler.
- lRet = DefWindowProc(hWnd, message, wParam, lParam);
- break;
- }
- return lRet;
-
-}
-*/
-#else
-void RemovableDriveManager::search_for_drives()
-{
-
- m_current_drives.clear();
-
-#if __APPLE__
- // if on macos obj-c class will enumerate
- if(m_rdmmm)
- {
- m_rdmmm->list_devices();
- }
-#else
+ static void search_path(const std::string &path, const std::string &parent_path, std::vector &out)
+ {
+ glob_t globbuf;
+ globbuf.gl_offs = 2;
+ int error = glob(path.c_str(), GLOB_TILDE, NULL, &globbuf);
+ if (error == 0) {
+ for (size_t i = 0; i < globbuf.gl_pathc; ++ i)
+ inspect_file(globbuf.gl_pathv[i], parent_path, out);
+ } else {
+ //if error - path probably doesnt exists so function just exits
+ //std::cout<<"glob error "<< error<< "\n";
+ }
+ globfree(&globbuf);
+ }
+}
+
+std::vector RemovableDriveManager::search_for_removable_drives() const
+{
+ std::vector current_drives;
+
+#if __APPLE__
+
+ this->list_devices(current_drives);
+
+#else
//search /media/* folder
- search_path("/media/*", "/media");
+ search_for_drives_internal::search_path("/media/*", "/media", current_drives);
//search_path("/Volumes/*", "/Volumes");
std::string path(std::getenv("USER"));
std::string pp(path);
- {
- //search /media/USERNAME/* folder
- pp = "/media/"+pp;
- path = "/media/" + path + "/*";
- search_path(path, pp);
+ //search /media/USERNAME/* folder
+ pp = "/media/"+pp;
+ path = "/media/" + path + "/*";
+ search_for_drives_internal::search_path(path, pp, current_drives);
- //search /run/media/USERNAME/* folder
- path = "/run" + path;
- pp = "/run"+pp;
- search_path(path, pp);
-
- }
+ //search /run/media/USERNAME/* folder
+ path = "/run" + path;
+ pp = "/run"+pp;
+ search_for_drives_internal::search_path(path, pp, current_drives);
#endif
-}
-void RemovableDriveManager::search_path(const std::string &path,const std::string &parent_path)
-{
- glob_t globbuf;
- globbuf.gl_offs = 2;
- int error = glob(path.c_str(), GLOB_TILDE, NULL, &globbuf);
- if(error == 0)
- {
- for(size_t i = 0; i < globbuf.gl_pathc; i++)
- {
- inspect_file(globbuf.gl_pathv[i], parent_path);
- }
- }else
- {
- //if error - path probably doesnt exists so function just exits
- //std::cout<<"glob error "<< error<< "\n";
- }
-
- globfree(&globbuf);
-}
-void RemovableDriveManager::inspect_file(const std::string &path, const std::string &parent_path)
-{
- //confirms if the file is removable drive and adds it to vector
- //if not same file system - could be removable drive
- if(!compare_filesystem_id(path, parent_path))
- {
- //free space
- boost::filesystem::space_info si = boost::filesystem::space(path);
- if(si.available != 0)
- {
- //user id
- struct stat buf;
- stat(path.c_str(), &buf);
- uid_t uid = buf.st_uid;
- std::string username(std::getenv("USER"));
- struct passwd *pw = getpwuid(uid);
- if (pw != 0 && pw->pw_name == username)
- m_current_drives.push_back(DriveData(boost::filesystem::basename(boost::filesystem::path(path)), path));
- }
-
- }
+ return current_drives;
}
-bool RemovableDriveManager::compare_filesystem_id(const std::string &path_a, const std::string &path_b)
+
+// Called from UI therefore it blocks the UI thread.
+// It also blocks updates at the worker thread.
+// Unix & OSX implementation.
+void RemovableDriveManager::eject_drive()
{
- struct stat buf;
- stat(path_a.c_str() ,&buf);
- dev_t id_a = buf.st_dev;
- stat(path_b.c_str() ,&buf);
- dev_t id_b = buf.st_dev;
- return id_a == id_b;
-}
-void RemovableDriveManager::eject_drive(const std::string &path)
-{
- if (m_current_drives.empty())
+ if (m_last_save_path.empty())
return;
- for (auto it = m_current_drives.begin(); it != m_current_drives.end(); ++it)
- {
- if((*it).path == path)
- {
-
- std::string correct_path(path);
- for (size_t i = 0; i < correct_path.size(); ++i)
- {
- if(correct_path[i]==' ')
- {
- correct_path = correct_path.insert(i,1,'\\');
- i++;
- }
- }
- //std::cout<<"Ejecting "<<(*it).name<<" from "<< correct_path<<"\n";
-// there is no usable command in c++ so terminal command is used instead
-// but neither triggers "succesful safe removal messege"
- std::string command = "";
-#if __APPLE__
- //m_rdmmm->eject_device(path);
- command = "diskutil unmount ";
-#else
- command = "umount ";
+#ifndef REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS
+ this->update();
+#endif // REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS
+
+ tbb::mutex::scoped_lock lock(m_drives_mutex);
+ auto it_drive_data = this->find_last_save_path_drive_data();
+ if (it_drive_data != m_current_drives.end()) {
+ std::string correct_path(m_last_save_path);
+#ifndef __APPLE__
+ for (size_t i = 0; i < correct_path.size(); ++i)
+ if (correct_path[i]==' ') {
+ correct_path = correct_path.insert(i,1,'\\');
+ ++ i;
+ }
#endif
- command += correct_path;
- int err = system(command.c_str());
- if(err)
- {
- std::cerr<<"Ejecting failed\n";
- return;
- }
-
- m_did_eject = true;
- m_current_drives.erase(it);
- m_ejected_path = m_last_save_path;
- m_ejected_name = m_last_save_name;
- break;
+ //std::cout<<"Ejecting "<<(*it).name<<" from "<< correct_path<<"\n";
+ // there is no usable command in c++ so terminal command is used instead
+ // but neither triggers "succesful safe removal messege"
+ BOOST_LOG_TRIVIAL(info) << "Ejecting started";
+ boost::process::ipstream istd_err;
+ boost::process::child child(
+#if __APPLE__
+ boost::process::search_path("diskutil"), "eject", correct_path.c_str(), (boost::process::std_out & boost::process::std_err) > istd_err);
+ //Another option how to eject at mac. Currently not working.
+ //used insted of system() command;
+ //this->eject_device(correct_path);
+#else
+ boost::process::search_path("umount"), correct_path.c_str(), (boost::process::std_out & boost::process::std_err) > istd_err);
+#endif
+ std::string line;
+ while (child.running() && std::getline(istd_err, line)) {
+ BOOST_LOG_TRIVIAL(trace) << line;
}
+ // wait for command to finnish (blocks ui thread)
+ child.wait();
+ int err = child.exit_code();
+ if (err) {
+ BOOST_LOG_TRIVIAL(error) << "Ejecting failed";
+ assert(m_callback_evt_handler);
+ if (m_callback_evt_handler)
+ wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair(*it_drive_data, false)));
+ return;
+ }
+ BOOST_LOG_TRIVIAL(info) << "Ejecting finished";
+ assert(m_callback_evt_handler);
+ if (m_callback_evt_handler)
+ wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair(std::move(*it_drive_data), true)));
+ m_current_drives.erase(it_drive_data);
}
-
}
-bool RemovableDriveManager::is_path_on_removable_drive(const std::string &path)
+
+std::string RemovableDriveManager::get_removable_drive_path(const std::string &path)
{
- if (m_current_drives.empty())
- return false;
+#ifndef REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS
+ this->update();
+#endif // REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS
+
std::size_t found = path.find_last_of("/");
std::string new_path = found == path.size() - 1 ? path.substr(0, found) : path;
- for (auto it = m_current_drives.begin(); it != m_current_drives.end(); ++it)
- {
- if(compare_filesystem_id(new_path, (*it).path))
- return true;
- }
- return false;
+
+ tbb::mutex::scoped_lock lock(m_drives_mutex);
+ for (const DriveData &data : m_current_drives)
+ if (search_for_drives_internal::compare_filesystem_id(new_path, data.path))
+ return path;
+ return m_current_drives.empty() ? std::string() : m_current_drives.front().path;
}
-std::string RemovableDriveManager::get_drive_from_path(const std::string& path)
+
+std::string RemovableDriveManager::get_removable_drive_from_path(const std::string& path)
{
std::size_t found = path.find_last_of("/");
std::string new_path = found == path.size() - 1 ? path.substr(0, found) : path;
-
// trim the filename
found = new_path.find_last_of("/");
new_path = new_path.substr(0, found);
- //check if same filesystem
- for (auto it = m_current_drives.begin(); it != m_current_drives.end(); ++it)
- {
- if (compare_filesystem_id(new_path, (*it).path))
- return (*it).path;
- }
- return "";
+ // check if same filesystem
+ tbb::mutex::scoped_lock lock(m_drives_mutex);
+ for (const DriveData &drive_data : m_current_drives)
+ if (search_for_drives_internal::compare_filesystem_id(new_path, drive_data.path))
+ return drive_data.path;
+ return std::string();
}
#endif
-RemovableDriveManager::RemovableDriveManager():
- m_drives_count(0),
- m_last_update(0),
- m_last_save_path(""),
- m_last_save_name(""),
- m_last_save_path_verified(false),
- m_is_writing(false),
- m_did_eject(false),
- m_plater_ready_to_slice(true),
- m_ejected_path(""),
- m_ejected_name("")
+void RemovableDriveManager::init(wxEvtHandler *callback_evt_handler)
+{
+ assert(! m_initialized);
+ assert(m_callback_evt_handler == nullptr);
+
+ if (m_initialized)
+ return;
+
+ m_initialized = true;
+ m_callback_evt_handler = callback_evt_handler;
+
#if __APPLE__
- , m_rdmmm(new RDMMMWrapper())
+ this->register_window_osx();
#endif
-{}
-RemovableDriveManager::~RemovableDriveManager()
-{
-#if __APPLE__
- delete m_rdmmm;
-#endif
-}
-void RemovableDriveManager::init()
-{
- //add_callback([](void) { RemovableDriveManager::get_instance().print(); });
-#if _WIN32
- //register_window();
-#elif __APPLE__
- m_rdmmm->register_window();
-#endif
- update(0, true);
-}
-bool RemovableDriveManager::update(const long time,const bool check)
-{
- if(time != 0) //time = 0 is forced update
- {
- long diff = m_last_update - time;
- if(diff <= -2)
- {
- m_last_update = time;
- }else
- {
- return false; // return value shouldnt matter if update didnt run
- }
- }
- search_for_drives();
- if (m_drives_count != m_current_drives.size())
- {
- if (check)
- {
- check_and_notify();
- }
- m_drives_count = m_current_drives.size();
- }
- return !m_current_drives.empty();
+
+#ifdef REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS
+ this->update();
+#else // REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS
+ // Don't call update() manually, as the UI triggered APIs call this->update() anyways.
+ m_thread = boost::thread((boost::bind(&RemovableDriveManager::thread_proc, this)));
+#endif // REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS
}
-bool RemovableDriveManager::is_drive_mounted(const std::string &path) const
+void RemovableDriveManager::shutdown()
{
- for (auto it = m_current_drives.begin(); it != m_current_drives.end(); ++it)
- {
- if ((*it).path == path)
+#if __APPLE__
+ this->unregister_window_osx();
+#endif
+
+#ifndef REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS
+ if (m_thread.joinable()) {
+ // Stop the worker thread, if running.
{
- return true;
+ // Notify the worker thread to cancel wait on detection polling.
+ std::lock_guard lck(m_thread_stop_mutex);
+ m_stop = true;
}
+ m_thread_stop_condition.notify_all();
+ // Wait for the worker thread to stop.
+ m_thread.join();
+ m_stop = false;
}
- return false;
+#endif // REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS
+
+ m_initialized = false;
+ m_callback_evt_handler = nullptr;
}
-std::string RemovableDriveManager::get_drive_path()
+
+bool RemovableDriveManager::set_and_verify_last_save_path(const std::string &path)
{
- if (m_current_drives.size() == 0)
+#ifndef REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS
+ this->update();
+#endif // REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS
+
+ m_last_save_path = this->get_removable_drive_from_path(path);
+ return ! m_last_save_path.empty();
+}
+
+RemovableDriveManager::RemovableDrivesStatus RemovableDriveManager::status()
+{
+
+ RemovableDriveManager::RemovableDrivesStatus out;
{
- reset_last_save_path();
- return "";
+ tbb::mutex::scoped_lock lock(m_drives_mutex);
+ out.has_eject = this->find_last_save_path_drive_data() != m_current_drives.end();
+ out.has_removable_drives = ! m_current_drives.empty();
}
- if (m_last_save_path_verified)
- return m_last_save_path;
- return m_current_drives.back().path;
+ if (! out.has_eject)
+ m_last_save_path.clear();
+ return out;
}
-std::string RemovableDriveManager::get_last_save_path() const
+
+// Update is called from thread_proc() and from most of the public methods on demand.
+void RemovableDriveManager::update()
{
- if (!m_last_save_path_verified)
- return "";
- return m_last_save_path;
-}
-std::string RemovableDriveManager::get_last_save_name() const
-{
- return m_last_save_name;
-}
-std::vector RemovableDriveManager::get_all_drives() const
-{
- return m_current_drives;
-}
-void RemovableDriveManager::check_and_notify()
-{
- if(m_drive_count_changed_callback)
- {
- m_drive_count_changed_callback(m_plater_ready_to_slice);
- }
- if(m_callbacks.size() != 0 && m_drives_count > m_current_drives.size() && !is_drive_mounted(m_last_save_path))
- {
- for (auto it = m_callbacks.begin(); it != m_callbacks.end(); ++it)
- {
- (*it)();
+ tbb::mutex::scoped_lock inside_update_lock;
+#ifdef _WIN32
+ // All wake up calls up to now are now consumed when the drive enumeration starts.
+ m_wakeup = false;
+#endif // _WIN32
+ if (inside_update_lock.try_acquire(m_inside_update_mutex)) {
+ // Got the lock without waiting. That means, the update was not running.
+ // Run the update.
+ std::vector current_drives = this->search_for_removable_drives();
+ // Post update events.
+ tbb::mutex::scoped_lock lock(m_drives_mutex);
+ std::sort(current_drives.begin(), current_drives.end());
+ if (current_drives != m_current_drives) {
+ assert(m_callback_evt_handler);
+ if (m_callback_evt_handler)
+ wxPostEvent(m_callback_evt_handler, RemovableDrivesChangedEvent(EVT_REMOVABLE_DRIVES_CHANGED));
}
+ m_current_drives = std::move(current_drives);
+ } else {
+ // Acquiring the m_iniside_update lock failed, therefore another update is running.
+ // Just block until the other instance of update() finishes.
+ inside_update_lock.acquire(m_inside_update_mutex);
}
}
-void RemovableDriveManager::add_remove_callback(std::function callback)
+
+#ifndef REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS
+void RemovableDriveManager::thread_proc()
{
- m_callbacks.push_back(callback);
-}
-void RemovableDriveManager::erase_callbacks()
-{
- m_callbacks.clear();
-}
-void RemovableDriveManager::set_drive_count_changed_callback(std::function callback)
-{
- m_drive_count_changed_callback = callback;
-}
-void RemovableDriveManager::set_plater_ready_to_slice(bool b)
-{
- m_plater_ready_to_slice = b;
-}
-void RemovableDriveManager::set_last_save_path(const std::string& path)
-{
- if(m_last_save_path_verified)// if old path is on drive
- {
- if(get_drive_from_path(path) != "") //and new is too, rewrite the path
+ // Signal the worker thread to update initially.
+#ifdef _WIN32
+ m_wakeup = true;
+#endif // _WIN32
+
+ for (;;) {
+ // Wait for 2 seconds before running the disk enumeration.
+ // Cancellable.
{
- m_last_save_path_verified = false;
- m_last_save_path = path;
- }//else do nothing
- }else
- {
- m_last_save_path = path;
- }
-}
-void RemovableDriveManager::verify_last_save_path()
-{
- std::string last_drive = get_drive_from_path(m_last_save_path);
- if (last_drive != "")
- {
- m_last_save_path_verified = true;
- m_last_save_path = last_drive;
- m_last_save_name = get_drive_name(last_drive);
- }else
- {
- reset_last_save_path();
- }
-}
-std::string RemovableDriveManager::get_drive_name(const std::string& path) const
-{
- if (m_current_drives.size() == 0)
- return "";
- for (auto it = m_current_drives.begin(); it != m_current_drives.end(); ++it)
- {
- if ((*it).path == path)
- {
- return (*it).name;
+ std::unique_lock lck(m_thread_stop_mutex);
+#ifdef _WIN32
+ // Windows do not send an update on insert / eject of an SD card into an external SD card reader.
+ // Windows also do not send an update on software eject of a FLASH drive.
+ // We can likely use the Windows WMI API, but it will be quite time consuming to implement.
+ // https://www.codeproject.com/Articles/10539/Making-WMI-Queries-In-C
+ // https://docs.microsoft.com/en-us/windows/win32/wmisdk/wmi-start-page
+ // https://docs.microsoft.com/en-us/windows/win32/wmisdk/com-api-for-wmi
+ // https://docs.microsoft.com/en-us/windows/win32/wmisdk/example--receiving-event-notifications-through-wmi-
+ m_thread_stop_condition.wait_for(lck, std::chrono::seconds(2), [this]{ return m_stop || m_wakeup; });
+#else
+ m_thread_stop_condition.wait_for(lck, std::chrono::seconds(2), [this]{ return m_stop; });
+#endif
}
- }
- return "";
-}
-bool RemovableDriveManager::is_last_drive_removed()
-{
- if(!m_last_save_path_verified)
- {
- return true;
- }
- bool r = !is_drive_mounted(m_last_save_path);
- if (r)
- {
- reset_last_save_path();
- }
- return r;
-}
-bool RemovableDriveManager::is_last_drive_removed_with_update(const long time)
-{
- update(time, false);
- return is_last_drive_removed();
-}
-void RemovableDriveManager::reset_last_save_path()
-{
- m_last_save_path_verified = false;
- m_last_save_path = "";
- m_last_save_name = "";
-}
-void RemovableDriveManager::set_is_writing(const bool b)
-{
- m_is_writing = b;
- if (b)
- {
- m_did_eject = false;
+ if (m_stop)
+ // Stop the worker thread.
+ break;
+ // Update m_current drives and send out update events.
+ this->update();
}
}
-bool RemovableDriveManager::get_is_writing() const
+#endif // REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS
+
+std::vector::const_iterator RemovableDriveManager::find_last_save_path_drive_data() const
{
- return m_is_writing;
+ return Slic3r::binary_find_by_predicate(m_current_drives.begin(), m_current_drives.end(),
+ [this](const DriveData &data){ return data.path < m_last_save_path; },
+ [this](const DriveData &data){ return data.path == m_last_save_path; });
}
-bool RemovableDriveManager::get_did_eject() const
-{
- return m_did_eject;
-}
-void RemovableDriveManager::set_did_eject(const bool b)
-{
- m_did_eject = b;
-}
-size_t RemovableDriveManager::get_drives_count() const
-{
- return m_current_drives.size();
-}
-std::string RemovableDriveManager::get_ejected_path() const
-{
- return m_ejected_path;
-}
-std::string RemovableDriveManager::get_ejected_name() const
-{
- return m_ejected_name;
-}
-}}//namespace Slicer::Gui
+
+}} // namespace Slic3r::GUI
diff --git a/src/slic3r/GUI/RemovableDriveManager.hpp b/src/slic3r/GUI/RemovableDriveManager.hpp
index 032eef682b..e1a8d6faf1 100644
--- a/src/slic3r/GUI/RemovableDriveManager.hpp
+++ b/src/slic3r/GUI/RemovableDriveManager.hpp
@@ -3,119 +3,136 @@
#include
#include
-#include
+
+#include
+#include
+#include