diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 3800571f0..fbcc3a04b 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1516,6 +1516,50 @@ std::string ModelObject::get_export_filename() const return ret; } +stl_stats ModelObject::get_object_stl_stats() const +{ + if (this->volumes.size() == 1) + return this->volumes[0]->mesh.stl.stats; + + stl_stats full_stats = this->volumes[0]->mesh.stl.stats; + + // fill full_stats from all objet's meshes + for (ModelVolume* volume : this->volumes) + { + if (volume->id() == this->volumes[0]->id()) + continue; + + const stl_stats& stats = volume->mesh.stl.stats; + + // initialize full_stats (for repaired errors) + full_stats.degenerate_facets += stats.degenerate_facets; + full_stats.edges_fixed += stats.edges_fixed; + full_stats.facets_removed += stats.facets_removed; + full_stats.facets_added += stats.facets_added; + full_stats.facets_reversed += stats.facets_reversed; + full_stats.backwards_edges += stats.backwards_edges; + + // another used satistics value + if (volume->is_model_part()) { + full_stats.volume += stats.volume; + full_stats.number_of_parts += stats.number_of_parts; + } + } + + return full_stats; +} + +int ModelObject::get_mesh_errors_count(const int vol_idx /*= -1*/) const +{ + if (vol_idx >= 0) + return this->volumes[vol_idx]->get_mesh_errors_count(); + + const stl_stats& stats = get_object_stl_stats(); + + return stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + + stats.facets_added + stats.facets_reversed + stats.backwards_edges; +} + void ModelVolume::set_material_id(t_model_material_id material_id) { m_material_id = material_id; @@ -1581,6 +1625,14 @@ void ModelVolume::calculate_convex_hull() m_convex_hull = mesh.convex_hull_3d(); } +int ModelVolume::get_mesh_errors_count() const +{ + const stl_stats& stats = this->mesh.stl.stats; + + return stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + + stats.facets_added + stats.facets_reversed + stats.backwards_edges; +} + const TriangleMesh& ModelVolume::get_convex_hull() const { return m_convex_hull; diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index c48979e97..a11c50300 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -289,6 +289,11 @@ public: std::string get_export_filename() const; + // Get full stl statistics for all object's meshes + stl_stats get_object_stl_stats() const; + // Get count of errors in the mesh( or all object's meshes, if volume index isn't defined) + int get_mesh_errors_count(const int vol_idx = -1) const; + protected: friend class Print; friend class SLAPrint; @@ -382,6 +387,8 @@ public: void calculate_convex_hull(); const TriangleMesh& get_convex_hull() const; + // Get count of errors in the mesh + int get_mesh_errors_count() const; // Helpers for loading / storing into AMF / 3MF files. static ModelVolumeType type_from_string(const std::string &s); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 595a968bd..47141a664 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -303,13 +303,13 @@ bool GUI_App::dark_mode_menus() void GUI_App::init_label_colours() { if (dark_mode()) { - m_color_label_modified = wxColour(252, 77, 1); - m_color_label_sys = wxColour(26, 132, 57); - } - else { m_color_label_modified = wxColour(253, 111, 40); m_color_label_sys = wxColour(115, 220, 103); } + else { + m_color_label_modified = wxColour(252, 77, 1); + m_color_label_sys = wxColour(26, 132, 57); + } m_color_label_default = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); } diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index ece46f05a..368ccc977 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -211,6 +211,76 @@ void ObjectList::create_popup_menus() create_instance_popupmenu(&m_menu_instance); } +void ObjectList::get_selected_item_indexes(int& obj_idx, int& vol_idx, const wxDataViewItem& input_item/* = wxDataViewItem(0)*/) +{ + const wxDataViewItem item = input_item == wxDataViewItem(0) ? GetSelection() : input_item; + + if (!item) + { + obj_idx = vol_idx = -1; + return; + } + + const ItemType type = m_objects_model->GetItemType(item); + + obj_idx = type & itObject ? m_objects_model->GetIdByItem(item) : + type & itVolume ? m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)) : -1; + + vol_idx = type & itVolume ? m_objects_model->GetVolumeIdByItem(item) : -1; +} + +int ObjectList::get_mesh_errors_count(const int obj_idx, const int vol_idx /*= -1*/) const +{ + if (obj_idx < 0) + return 0; + + return (*m_objects)[obj_idx]->get_mesh_errors_count(vol_idx); +} + +wxString ObjectList::get_mesh_errors_list(const int obj_idx, const int vol_idx /*= -1*/) const +{ + const int errors = get_mesh_errors_count(obj_idx, vol_idx); + + if (errors == 0) + return ""; // hide tooltip + + // Create tooltip string, if there are errors + wxString tooltip = wxString::Format(_(L("Auto-repaired (%d errors):\n")), errors); + + const stl_stats& stats = vol_idx == -1 ? + (*m_objects)[obj_idx]->get_object_stl_stats() : + (*m_objects)[obj_idx]->volumes[vol_idx]->mesh.stl.stats; + + std::map error_msg = { + { L("degenerate facets"), stats.degenerate_facets }, + { L("edges fixed"), stats.edges_fixed }, + { L("facets removed"), stats.facets_removed }, + { L("facets added"), stats.facets_added }, + { L("facets reversed"), stats.facets_reversed }, + { L("backwards edges"), stats.backwards_edges } + }; + + for (const auto& error : error_msg) + if (error.second > 0) + tooltip += wxString::Format("\t%d %s\n", error.second, _(error.first)); + + if (is_windows10()) + tooltip += _(L("Right button click the icon to fix STL through Netfabb")); + + return tooltip; +} + +wxString ObjectList::get_mesh_errors_list() +{ + if (!GetSelection()) + return ""; + + int obj_idx, vol_idx; + get_selected_item_indexes(obj_idx, vol_idx); + + return get_mesh_errors_list(obj_idx, vol_idx); +} + void ObjectList::set_tooltip_for_item(const wxPoint& pt) { wxDataViewItem item; @@ -218,40 +288,24 @@ void ObjectList::set_tooltip_for_item(const wxPoint& pt) HitTest(pt, item, col); if (!item) return; + /* GetMainWindow() return window, associated with wxDataViewCtrl. + * And for this window we should to set tooltips. + * Just this->SetToolTip(tooltip) => has no effect. + */ + if (col->GetTitle() == " " && GetSelectedItemsCount()<2) GetMainWindow()->SetToolTip(_(L("Right button click the icon to change the object settings"))); - else if (col->GetTitle() == _("Name") && - m_objects_model->GetBitmap(item).GetRefData() == m_bmp_manifold_warning.bmp().GetRefData()) { - int obj_idx = m_objects_model->GetIdByItem(item); - auto& stats = (*m_objects)[obj_idx]->volumes[0]->mesh.stl.stats; - int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + - stats.facets_added + stats.facets_reversed + stats.backwards_edges; - - wxString tooltip = wxString::Format(_(L("Auto-repaired (%d errors):\n")), errors); - - std::map error_msg; - error_msg[L("degenerate facets")] = stats.degenerate_facets; - error_msg[L("edges fixed")] = stats.edges_fixed; - error_msg[L("facets removed")] = stats.facets_removed; - error_msg[L("facets added")] = stats.facets_added; - error_msg[L("facets reversed")] = stats.facets_reversed; - error_msg[L("backwards edges")] = stats.backwards_edges; - - for (auto error : error_msg) - { - if (error.second > 0) - tooltip += wxString::Format(_("\t%d %s\n"), error.second, error.first); + else if (col->GetTitle() == _("Name")) + { +#ifdef __WXMSW__ + if (pt.x < 2 * wxGetApp().em_unit() || pt.x > 4 * wxGetApp().em_unit()) { + GetMainWindow()->SetToolTip(""); // hide tooltip + return; } -// OR -// tooltip += wxString::Format(_(L("%d degenerate facets, %d edges fixed, %d facets removed, " -// "%d facets added, %d facets reversed, %d backwards edges")), -// stats.degenerate_facets, stats.edges_fixed, stats.facets_removed, -// stats.facets_added, stats.facets_reversed, stats.backwards_edges); - - if (is_windows10()) - tooltip += _(L("Right button click the icon to fix STL through Netfabb")); - - GetMainWindow()->SetToolTip(tooltip); +#endif //__WXMSW__ + int obj_idx, vol_idx; + get_selected_item_indexes(obj_idx, vol_idx, item); + GetMainWindow()->SetToolTip(get_mesh_errors_list(obj_idx, vol_idx)); } else GetMainWindow()->SetToolTip(""); // hide tooltip @@ -413,8 +467,8 @@ void ObjectList::update_name_in_model(const wxDataViewItem& item) const void ObjectList::init_icons() { - m_bmp_modifiermesh = ScalableBitmap(nullptr, "add_modifier"); // Add part - m_bmp_solidmesh = ScalableBitmap(nullptr, "add_part"); // Add modifier + m_bmp_solidmesh = ScalableBitmap(nullptr, "add_part"); // Add part + m_bmp_modifiermesh = ScalableBitmap(nullptr, "add_modifier"); // Add modifier m_bmp_support_enforcer = ScalableBitmap(nullptr, "support_enforcer");// Add support enforcer m_bmp_support_blocker = ScalableBitmap(nullptr, "support_blocker"); // Add support blocker @@ -430,6 +484,8 @@ void ObjectList::init_icons() // init icon for manifold warning m_bmp_manifold_warning = ScalableBitmap(nullptr, "exclamation"); + // Set warning bitmap for the model + m_objects_model->SetWarningBitmap(&m_bmp_manifold_warning.bmp()); // init bitmap for "Split to sub-objects" context menu m_bmp_split = ScalableBitmap(nullptr, "split_parts_SMALL"); @@ -443,8 +499,8 @@ void ObjectList::rescale_icons() m_bmp_vector.clear(); m_bmp_vector.reserve(4); // bitmaps for different types of parts for (ScalableBitmap* bitmap : std::vector { - &m_bmp_modifiermesh, // Add part - &m_bmp_solidmesh, // Add modifier + &m_bmp_solidmesh, // Add part + &m_bmp_modifiermesh, // Add modifier &m_bmp_support_enforcer, // Add support enforcer &m_bmp_support_blocker }) // Add support blocker { @@ -455,6 +511,9 @@ void ObjectList::rescale_icons() m_objects_model->SetVolumeBitmaps(m_bmp_vector); m_bmp_manifold_warning.msw_rescale(); + // Set warning bitmap for the model + m_objects_model->SetWarningBitmap(&m_bmp_manifold_warning.bmp()); + m_bmp_split.msw_rescale(); m_bmp_cog.msw_rescale(); @@ -516,7 +575,8 @@ void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& vol for (const ModelVolume* volume : volumes) { - auto vol_item = m_objects_model->AddVolumeChild(object_item, volume->name, volume->type(), + const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(object_item, volume->name, volume->type(), + volume->get_mesh_errors_count()>0 , volume->config.has("extruder") ? volume->config.option("extruder")->value : 0); auto opt_keys = volume->config.keys(); if (!opt_keys.empty() && !((opt_keys.size() == 1) && (opt_keys[0] == "extruder"))) @@ -592,10 +652,13 @@ void ObjectList::OnContextMenu(wxDataViewEvent&) if (title == " ") show_context_menu(); - else if (title == _("Name") && pt.x >15 && - m_objects_model->GetBitmap(item).GetRefData() == m_bmp_manifold_warning.bmp().GetRefData()) + else if (title == _("Name")) { - if (is_windows10()) + int obj_idx, vol_idx; + get_selected_item_indexes(obj_idx, vol_idx, item); + + if (is_windows10() && get_mesh_errors_count(obj_idx, vol_idx) > 0 && + pt.x > 2*wxGetApp().em_unit() && pt.x < 4*wxGetApp().em_unit() ) fix_through_netfabb(); } @@ -955,6 +1018,7 @@ void ObjectList::get_freq_settings_choice(const wxString& bundle_name) { const std::vector& options = get_options_for_bundle(bundle_name); + assert(m_config); auto opt_keys = m_config->keys(); const DynamicPrintConfig& from_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; @@ -984,6 +1048,10 @@ void ObjectList::update_settings_item() const auto settings_item = m_objects_model->IsSettingsItem(item) ? item : m_objects_model->GetSettingsItem(item); select_item(settings_item ? settings_item : m_objects_model->AddSettingsChild(item)); + + // update object selection on Plater + if (!m_prevent_canvas_selection_update) + update_selections_on_canvas(); } else { auto panel = wxGetApp().sidebar().scrolled_panel(); @@ -1153,13 +1221,15 @@ void ObjectList::append_menu_items_osx(wxMenu* menu) menu->AppendSeparator(); } -void ObjectList::append_menu_item_fix_through_netfabb(wxMenu* menu) +wxMenuItem* ObjectList::append_menu_item_fix_through_netfabb(wxMenu* menu) { if (!is_windows10()) - return; - append_menu_item(menu, wxID_ANY, _(L("Fix through the Netfabb")), "", + return nullptr; + wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _(L("Fix through the Netfabb")), "", [this](wxCommandEvent&) { fix_through_netfabb(); }, "", menu); menu->AppendSeparator(); + + return menu_item; } void ObjectList::append_menu_item_export_stl(wxMenu* menu) const @@ -1345,21 +1415,23 @@ void ObjectList::load_subobject(ModelVolumeType type) int obj_idx = m_objects_model->GetIdByItem(item); if (obj_idx < 0) return; - wxArrayString part_names; - load_part((*m_objects)[obj_idx], part_names, type); + + std::vector> volumes_info; + load_part((*m_objects)[obj_idx], volumes_info, type); + changed_object(obj_idx); - for (int i = 0; i < part_names.size(); ++i) { - const wxDataViewItem sel_item = m_objects_model->AddVolumeChild(item, part_names.Item(i), type); - - if (i == part_names.size() - 1) - select_item(sel_item); - } + wxDataViewItem sel_item; + for (const auto& volume : volumes_info ) + sel_item = m_objects_model->AddVolumeChild(item, volume.first, type, volume.second); + + if (sel_item) + select_item(sel_item); } void ObjectList::load_part( ModelObject* model_object, - wxArrayString& part_names, + std::vector> &volumes_info, ModelVolumeType type) { wxWindow* parent = wxGetApp().tab_panel()->GetPage(0); @@ -1395,7 +1467,7 @@ void ObjectList::load_part( ModelObject* model_object, new_volume->set_type(type); new_volume->name = boost::filesystem::path(input_file).filename().string(); - part_names.Add(from_u8(new_volume->name)); + volumes_info.push_back(std::make_pair(from_u8(new_volume->name), new_volume->get_mesh_errors_count()>0)); // set a default extruder value, since user can't add it manually new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); @@ -1544,7 +1616,8 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode changed_object(obj_idx); const auto object_item = m_objects_model->GetTopParent(GetSelection()); - select_item(m_objects_model->AddVolumeChild(object_item, name, type)); + select_item(m_objects_model->AddVolumeChild(object_item, name, type, + new_volume->get_mesh_errors_count()>0)); #ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME selection_changed(); #endif //no __WXOSX__ //__WXMSW__ @@ -1576,6 +1649,10 @@ void ObjectList::del_subobject_item(wxDataViewItem& item) else if (!del_subobject_from_object(obj_idx, idx, type)) return; + // If last volume item with warning was deleted, unmark object item + if (type == itVolume && (*m_objects)[obj_idx]->get_mesh_errors_count() == 0) + m_objects_model->DeleteWarningIcon(m_objects_model->GetParent(item)); + m_objects_model->Delete(item); } @@ -1682,18 +1759,18 @@ void ObjectList::split() else parent = item; - for (auto id = 0; id < model_object->volumes.size(); id++) { - const auto vol_item = m_objects_model->AddVolumeChild(parent, from_u8(model_object->volumes[id]->name), - model_object->volumes[id]->is_modifier() ? - ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART, - model_object->volumes[id]->config.has("extruder") ? - model_object->volumes[id]->config.option("extruder")->value : 0, + for (const ModelVolume* volume : model_object->volumes) { + const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(parent, from_u8(volume->name), + volume->is_modifier() ? ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART, + volume->get_mesh_errors_count()>0, + volume->config.has("extruder") ? + volume->config.option("extruder")->value : 0, false); // add settings to the part, if it has those - auto opt_keys = model_object->volumes[id]->config.keys(); + auto opt_keys = volume->config.keys(); if ( !(opt_keys.size() == 1 && opt_keys[0] == "extruder") ) { select_item(m_objects_model->AddSettingsChild(vol_item)); - /*Collapse*/Expand(vol_item); + Expand(vol_item); } } @@ -1766,6 +1843,7 @@ void ObjectList::changed_object(const int obj_idx/* = -1*/) const void ObjectList::part_selection_changed() { int obj_idx = -1; + int volume_id = -1; m_config = nullptr; wxString og_name = wxEmptyString; @@ -1812,7 +1890,7 @@ void ObjectList::part_selection_changed() } else if (m_objects_model->GetItemType(item) == itVolume) { og_name = _(L("Part manipulation")); - const auto volume_id = m_objects_model->GetVolumeIdByItem(item); + volume_id = m_objects_model->GetVolumeIdByItem(item); m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; update_and_show_manipulations = true; } @@ -1832,7 +1910,11 @@ void ObjectList::part_selection_changed() if (update_and_show_manipulations) { wxGetApp().obj_manipul()->get_og()->set_name(" " + og_name + " "); - wxGetApp().obj_manipul()->get_og()->set_value("object_name", m_objects_model->GetName(GetSelection())); + + if (item) { + wxGetApp().obj_manipul()->get_og()->set_value("object_name", m_objects_model->GetName(item)); + wxGetApp().obj_manipul()->update_warning_icon_state(get_mesh_errors_list(obj_idx, volume_id)); + } } if (update_and_show_settings) @@ -1852,34 +1934,26 @@ void ObjectList::part_selection_changed() void ObjectList::add_object_to_list(size_t obj_idx) { auto model_object = (*m_objects)[obj_idx]; - wxString item_name = from_u8(model_object->name); + const wxString& item_name = from_u8(model_object->name); const auto item = m_objects_model->Add(item_name, !model_object->config.has("extruder") ? 0 : - model_object->config.option("extruder")->value); - - // Add error icon if detected auto-repaire - auto stats = model_object->volumes[0]->mesh.stl.stats; - int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + - stats.facets_added + stats.facets_reversed + stats.backwards_edges; - if (errors > 0) { - wxVariant variant; - variant << DataViewBitmapText(item_name, m_bmp_manifold_warning.bmp()); - m_objects_model->SetValue(variant, item, 0); - } + model_object->config.option("extruder")->value, + get_mesh_errors_count(obj_idx) > 0); // add volumes to the object if (model_object->volumes.size() > 1) { - for (auto id = 0; id < model_object->volumes.size(); id++) { - auto vol_item = m_objects_model->AddVolumeChild(item, - from_u8(model_object->volumes[id]->name), - model_object->volumes[id]->type(), - !model_object->volumes[id]->config.has("extruder") ? 0 : - model_object->volumes[id]->config.option("extruder")->value, + for (const ModelVolume* volume : model_object->volumes) { + const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(item, + from_u8(volume->name), + volume->type(), + volume->get_mesh_errors_count()>0, + !volume->config.has("extruder") ? 0 : + volume->config.option("extruder")->value, false); - auto opt_keys = model_object->volumes[id]->config.keys(); + auto opt_keys = volume->config.keys(); if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) { select_item(m_objects_model->AddSettingsChild(vol_item)); - /*Collapse*/Expand(vol_item); + Expand(vol_item); } } Expand(item); @@ -1893,7 +1967,7 @@ void ObjectList::add_object_to_list(size_t obj_idx) auto opt_keys = model_object->config.keys(); if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) { select_item(m_objects_model->AddSettingsChild(item)); - /*Collapse*/Expand(item); + Expand(item); } #ifndef __WXOSX__ @@ -2087,6 +2161,11 @@ void ObjectList::update_selections() if (m_objects_model->GetVolumeIdByItem(m_objects_model->GetParent(item)) == gl_vol->volume_idx()) return; } + + // but if there is selected only one of several instances by context menu, + // then select this instance in ObjectList + if (selection.is_single_full_instance()) + sels.Add(m_objects_model->GetItemByInstanceId(selection.get_object_idx(), selection.get_instance_idx())); } else if (selection.is_single_full_object() || selection.is_multiple_full_object()) { @@ -2677,18 +2756,10 @@ void ObjectList::rename_item() update_name_in_model(item); } -void ObjectList::fix_through_netfabb() const +void ObjectList::fix_through_netfabb() { - const wxDataViewItem item = GetSelection(); - if (!item) - return; - - const ItemType type = m_objects_model->GetItemType(item); - - const int obj_idx = type & itObject ? m_objects_model->GetIdByItem(item) : - type & itVolume ? m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)) : -1; - - const int vol_idx = type & itVolume ? m_objects_model->GetVolumeIdByItem(item) : -1; + int obj_idx, vol_idx; + get_selected_item_indexes(obj_idx, vol_idx); wxGetApp().plater()->fix_through_netfabb(obj_idx, vol_idx); @@ -2702,28 +2773,27 @@ void ObjectList::update_item_error_icon(const int obj_idx, const int vol_idx) co if (!item) return; - auto model_object = (*m_objects)[obj_idx]; - - const stl_stats& stats = model_object->volumes[vol_idx<0 ? 0 : vol_idx]->mesh.stl.stats; - const int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + - stats.facets_added + stats.facets_reversed + stats.backwards_edges; - - if (errors == 0) { - // delete Error_icon if all errors are fixed - wxVariant variant; - variant << DataViewBitmapText(from_u8(model_object->name), wxNullBitmap); - m_objects_model->SetValue(variant, item, 0); + if (get_mesh_errors_count(obj_idx, vol_idx) == 0) + { + // if whole object has no errors more, + if (get_mesh_errors_count(obj_idx) == 0) + // unmark all items in the object + m_objects_model->DeleteWarningIcon(vol_idx >= 0 ? m_objects_model->GetParent(item) : item, true); + else + // unmark fixed item only + m_objects_model->DeleteWarningIcon(item); } } void ObjectList::msw_rescale() { + const int em = wxGetApp().em_unit(); // update min size !!! A width of control shouldn't be a wxDefaultCoord - SetMinSize(wxSize(1, 15 * wxGetApp().em_unit())); + SetMinSize(wxSize(1, 15 * em)); - GetColumn(0)->SetWidth(19 * wxGetApp().em_unit()); - GetColumn(1)->SetWidth(8 * wxGetApp().em_unit()); - GetColumn(2)->SetWidth(int(2 * wxGetApp().em_unit())); + GetColumn(0)->SetWidth(19 * em); + GetColumn(1)->SetWidth( 8 * em); + GetColumn(2)->SetWidth( 2 * em); // rescale all icons, used by ObjectList rescale_icons(); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index da082d1d4..1ca358cd9 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -175,6 +175,16 @@ public: void init_icons(); void rescale_icons(); + // Get obj_idx and vol_idx values for the selected (by default) or an adjusted item + void get_selected_item_indexes(int& obj_idx, int& vol_idx, const wxDataViewItem& item = wxDataViewItem(0)); + // Get count of errors in the mesh + int get_mesh_errors_count(const int obj_idx, const int vol_idx = -1) const; + /* Get list of errors in the mesh. Return value is a string, used for the tooltip + * Function without parameters is for a call from Manipulation panel, + * when we don't know parameters of selected item + */ + wxString get_mesh_errors_list(const int obj_idx, const int vol_idx = -1) const; + wxString get_mesh_errors_list(); void set_tooltip_for_item(const wxPoint& pt); void selection_changed(); @@ -192,7 +202,7 @@ public: wxMenuItem* append_menu_item_change_type(wxMenu* menu); wxMenuItem* append_menu_item_instance_to_object(wxMenu* menu); void append_menu_items_osx(wxMenu* menu); - void append_menu_item_fix_through_netfabb(wxMenu* menu); + wxMenuItem* append_menu_item_fix_through_netfabb(wxMenu* menu); void append_menu_item_export_stl(wxMenu* menu) const ; void append_menu_item_change_extruder(wxMenu* menu) const; void append_menu_item_delete(wxMenu* menu); @@ -206,7 +216,7 @@ public: void update_opt_keys(t_config_option_keys& t_optopt_keys); void load_subobject(ModelVolumeType type); - void load_part(ModelObject* model_object, wxArrayString& part_names, ModelVolumeType type); + void load_part(ModelObject* model_object, std::vector> &volumes_info, ModelVolumeType type); void load_generic_subobject(const std::string& type_name, const ModelVolumeType type); void del_object(const int obj_idx); void del_subobject_item(wxDataViewItem& item); @@ -280,7 +290,7 @@ public: void instances_to_separated_objects(const int obj_idx); void split_instances(); void rename_item(); - void fix_through_netfabb() const; + void fix_through_netfabb(); void update_item_error_icon(const int obj_idx, int vol_idx) const ; void paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& volumes); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 36b356c5d..96a69e464 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -10,6 +10,7 @@ #include "Selection.hpp" #include +#include "slic3r/Utils/FixModelByWin10.hpp" namespace Slic3r { @@ -66,6 +67,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : , m_focused_option("") #endif // __APPLE__ { + m_manifold_warning_bmp = ScalableBitmap(parent, "exclamation"); m_og->set_name(_(L("Object Manipulation"))); m_og->label_width = 12;//125; m_og->set_grid_vgap(5); @@ -86,17 +88,51 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : ConfigOptionDef def; // Objects(sub-objects) name - def.label = L("Name"); +// def.label = L("Name"); +// def.gui_type = "legend"; +// def.tooltip = L("Object name"); +// def.width = 21 * wxGetApp().em_unit(); +// def.default_value = new ConfigOptionString{ " " }; +// m_og->append_single_option_line(Option(def, "object_name")); + + Line line = Line{ "Name", "Object name" }; + + auto manifold_warning_icon = [this](wxWindow* parent) { + m_fix_throught_netfab_bitmap = new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(m_fix_throught_netfab_bitmap); + + if (is_windows10()) + m_fix_throught_netfab_bitmap->Bind(wxEVT_CONTEXT_MENU, [this](wxCommandEvent &e) + { + // if object/sub-object has no errors + if (m_fix_throught_netfab_bitmap->GetBitmap().GetRefData() == wxNullBitmap.GetRefData()) + return; + + wxGetApp().obj_list()->fix_through_netfabb(); + update_warning_icon_state(wxGetApp().obj_list()->get_mesh_errors_list()); + }); + + return sizer; + }; + + line.append_widget(manifold_warning_icon); + def.label = ""; def.gui_type = "legend"; def.tooltip = L("Object name"); +#ifdef __APPLE__ + def.width = 19; +#else def.width = 21; +#endif def.set_default_value(new ConfigOptionString{ " " }); - m_og->append_single_option_line(Option(def, "object_name")); + line.append_option(Option(def, "object_name")); + m_og->append_line(line); const int field_width = 5; // Legend for object modification - auto line = Line{ "", "" }; + line = Line{ "", "" }; def.label = ""; def.type = coString; def.width = field_width/*50*/; @@ -353,6 +389,12 @@ void ObjectManipulation::emulate_kill_focus() } #endif // __APPLE__ +void ObjectManipulation::update_warning_icon_state(const wxString& tooltip) +{ + m_fix_throught_netfab_bitmap->SetBitmap(tooltip.IsEmpty() ? wxNullBitmap : m_manifold_warning_bmp.bmp()); + m_fix_throught_netfab_bitmap->SetToolTip(tooltip); +} + void ObjectManipulation::reset_settings_value() { m_new_position = Vec3d::Zero(); @@ -568,5 +610,13 @@ void ObjectManipulation::set_uniform_scaling(const bool new_value) m_uniform_scale = new_value; } +void ObjectManipulation::msw_rescale() +{ + m_manifold_warning_bmp.msw_rescale(); + m_fix_throught_netfab_bitmap->SetBitmap(m_manifold_warning_bmp.bmp()); + + get_og()->msw_rescale(); +} + } //namespace GUI } //namespace Slic3r diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index 833070e03..7c359f3d7 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -9,6 +9,7 @@ class wxBitmapComboBox; class wxStaticText; class LockButton; +class wxStaticBitmap; namespace Slic3r { namespace GUI { @@ -69,6 +70,9 @@ class ObjectManipulation : public OG_Settings LockButton* m_lock_bnt{ nullptr }; wxBitmapComboBox* m_word_local_combo = nullptr; + ScalableBitmap m_manifold_warning_bmp; + wxStaticBitmap* m_fix_throught_netfab_bitmap; + #ifndef __APPLE__ // Currently focused option name (empty if none) std::string m_focused_option; @@ -100,6 +104,9 @@ public: void emulate_kill_focus(); #endif // __APPLE__ + void update_warning_icon_state(const wxString& tooltip); + void msw_rescale(); + private: void reset_settings_value(); void update_settings_value(const Selection& selection); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 292d47d91..184857a5e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -731,7 +731,7 @@ Sidebar::Sidebar(Plater *parent) auto init_btn = [this](wxButton **btn, wxString label) { *btn = new wxButton(this, wxID_ANY, label, wxDefaultPosition, - wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); + wxDefaultSize, wxBU_EXACTFIT); (*btn)->SetFont(wxGetApp().bold_font()); }; @@ -903,8 +903,7 @@ void Sidebar::msw_rescale() p->frequently_changed_parameters->get_og(false)->msw_rescale(); p->object_list->msw_rescale(); - - p->object_manipulation->get_og()->msw_rescale(); + p->object_manipulation->msw_rescale(); p->object_settings->msw_rescale(); p->object_info->msw_rescale(); @@ -977,7 +976,7 @@ void Sidebar::show_info_sizer() p->object_info->info_size->SetLabel(wxString::Format("%.2f x %.2f x %.2f",size(0), size(1), size(2))); p->object_info->info_materials->SetLabel(wxString::Format("%d", static_cast(model_object->materials_count()))); - auto& stats = model_object->volumes.front()->mesh.stl.stats; + const auto& stats = model_object->get_object_stl_stats();//model_object->volumes.front()->mesh.stl.stats; p->object_info->info_volume->SetLabel(wxString::Format("%.2f", stats.volume)); p->object_info->info_facets->SetLabel(wxString::Format(_(L("%d (%d shells)")), static_cast(model_object->facets_count()), stats.number_of_parts)); @@ -1335,6 +1334,7 @@ struct Plater::priv bool can_split_to_volumes() const; bool can_arrange() const; bool can_layers_editing() const; + bool can_fix_through_netfabb() const; private: bool init_object_menu(); @@ -2937,7 +2937,7 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ menu->AppendSeparator(); } - sidebar->obj_list()->append_menu_item_fix_through_netfabb(menu); + wxMenuItem* item_fix_through_netfabb = sidebar->obj_list()->append_menu_item_fix_through_netfabb(menu); wxMenu* mirror_menu = new wxMenu(); if (mirror_menu == nullptr) @@ -2957,6 +2957,8 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ { q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_mirror()); }, item_mirror->GetId()); q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_delete()); }, item_delete->GetId()); + if (item_fix_through_netfabb) + q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_fix_through_netfabb()); }, item_fix_through_netfabb->GetId()); } return true; @@ -3126,6 +3128,15 @@ bool Plater::priv::can_delete_all() const return !model.objects.empty(); } +bool Plater::priv::can_fix_through_netfabb() const +{ + int obj_idx = get_selected_object_idx(); + if (obj_idx < 0) + return false; + + return model.objects[obj_idx]->get_mesh_errors_count() > 0; +} + bool Plater::priv::can_increase_instances() const { if (arranging || rotoptimizing) { diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 00d37d664..1d4dd8f88 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -420,14 +420,21 @@ ObjectDataViewModel::~ObjectDataViewModel() m_bitmap_cache = nullptr; } -wxDataViewItem ObjectDataViewModel::Add(const wxString &name, const int extruder) +wxDataViewItem ObjectDataViewModel::Add(const wxString &name, + const int extruder, + const bool has_errors/* = false*/) { const wxString extruder_str = extruder == 0 ? "default" : wxString::Format("%d", extruder); auto root = new ObjectDataViewModelNode(name, extruder_str); + // Add error icon if detected auto-repaire + if (has_errors) + root->m_bmp = *m_warning_bmp; + m_objects.push_back(root); // notify control wxDataViewItem child((void*)root); wxDataViewItem parent((void*)NULL); + ItemAdded(parent, child); return child; } @@ -435,6 +442,7 @@ wxDataViewItem ObjectDataViewModel::Add(const wxString &name, const int extruder wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent_item, const wxString &name, const Slic3r::ModelVolumeType volume_type, + const bool has_errors/* = false*/, const int extruder/* = 0*/, const bool create_frst_child/* = true*/) { @@ -448,9 +456,14 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent if (insert_position < 0 || root->GetNthChild(insert_position)->m_type != itInstanceRoot) insert_position = -1; + const bool obj_errors = root->m_bmp.IsOk(); + if (create_frst_child && root->m_volumes_cnt == 0) { - const auto node = new ObjectDataViewModelNode(root, root->m_name, *m_volume_bmps[0], extruder_str, 0); + const Slic3r::ModelVolumeType type = Slic3r::ModelVolumeType::MODEL_PART; + const auto node = new ObjectDataViewModelNode(root, root->m_name, GetVolumeIcon(type, obj_errors), extruder_str, 0); + node->m_volume_type = type; + insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position); // notify control const wxDataViewItem child((void*)node); @@ -458,12 +471,15 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent root->m_volumes_cnt++; if (insert_position > 0) insert_position++; - - node->m_volume_type = volume_type; } - const auto node = new ObjectDataViewModelNode(root, name, *m_volume_bmps[int(volume_type)], extruder_str, root->m_volumes_cnt); + const auto node = new ObjectDataViewModelNode(root, name, GetVolumeIcon(volume_type, has_errors), extruder_str, root->m_volumes_cnt); insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position); + + // if part with errors is added, but object wasn't marked, then mark it + if (!obj_errors && has_errors) + root->SetBitmap(*m_warning_bmp); + // notify control const wxDataViewItem child((void*)node); ItemAdded(parent_item, child); @@ -1228,15 +1244,61 @@ void ObjectDataViewModel::Rescale() node->msw_rescale(); if (node->m_type & itVolume) - node->m_bmp = *m_volume_bmps[node->volume_type()]; + node->m_bmp = GetVolumeIcon(node->m_volume_type, node->m_bmp.GetWidth() != node->m_bmp.GetHeight()); if (node->m_type & itObject && node->m_bmp.IsOk()) - node->m_bmp = create_scaled_bitmap(nullptr, "exclamation"); + node->m_bmp = *m_warning_bmp; ItemChanged(item); } } +wxBitmap ObjectDataViewModel::GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, const bool is_marked/* = false*/) +{ + if (!is_marked) + return *m_volume_bmps[static_cast(vol_type)]; + + std::string scaled_bitmap_name = "warning" + std::to_string(static_cast(vol_type)); + scaled_bitmap_name += "-em" + std::to_string(Slic3r::GUI::wxGetApp().em_unit()); + + wxBitmap *bmp = m_bitmap_cache->find(scaled_bitmap_name); + if (bmp == nullptr) { + std::vector bmps; + + bmps.emplace_back(*m_warning_bmp); + bmps.emplace_back(*m_volume_bmps[static_cast(vol_type)]); + + bmp = m_bitmap_cache->insert(scaled_bitmap_name, bmps); + } + + return *bmp; +} + +void ObjectDataViewModel::DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object/* = false*/) +{ + if (!item.IsOk()) + return; + + ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); + + if (!node->GetBitmap().IsOk() || !(node->GetType() & (itVolume | itObject))) + return; + + if (node->GetType() & itVolume) { + node->SetBitmap(*m_volume_bmps[static_cast(node->volume_type())]); + return; + } + + node->SetBitmap(wxNullBitmap); + if (unmark_object) + { + wxDataViewItemArray children; + GetChildren(item, children); + for (const wxDataViewItem& child : children) + DeleteWarningIcon(child); + } +} + //----------------------------------------------------------------------------- // PrusaDataViewBitmapText //----------------------------------------------------------------------------- @@ -2379,11 +2441,7 @@ ModeSizer::ModeSizer(wxWindow *parent, int hgap/* = 10*/) : m_mode_btns.reserve(3); for (const auto& button : buttons) { -// int x, y; -// parent->GetTextExtent(button.first, &x, &y, nullptr, nullptr, &Slic3r::GUI::wxGetApp().bold_font()); -// const wxSize size = wxSize(x + button.second.GetWidth() + Slic3r::GUI::wxGetApp().em_unit(), -// y + Slic3r::GUI::wxGetApp().em_unit()); - m_mode_btns.push_back(new ModeButton(parent, wxID_ANY, button.second, button.first/*, size*/)); + m_mode_btns.push_back(new ModeButton(parent, wxID_ANY, button.second, button.first)); } for (auto btn : m_mode_btns) diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 589317f2b..8527e5425 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -323,14 +323,10 @@ public: return false; } - void SetBitmap(const wxBitmap &icon) - { - m_bmp = icon; - } - - ItemType GetType() const { - return m_type; - } + void SetBitmap(const wxBitmap &icon) { m_bmp = icon; } + const wxBitmap& GetBitmap() const { return m_bmp; } + const wxString& GetName() const { return m_name; } + ItemType GetType() const { return m_type; } void SetIdx(const int& idx) { m_idx = idx; @@ -339,9 +335,7 @@ public: m_name = wxString::Format("Instance_%d", m_idx + 1); } - int GetIdx() const { - return m_idx; - } + int GetIdx() const { return m_idx; } // use this function only for childrens void AssignAllVal(ObjectDataViewModelNode& from_node) @@ -374,10 +368,10 @@ public: // Set action icons for node void set_action_icon(); - void update_settings_digest_bitmaps(); - bool update_settings_digest(const std::vector& categories); - int volume_type() const { return int(m_volume_type); } - void msw_rescale(); + void update_settings_digest_bitmaps(); + bool update_settings_digest(const std::vector& categories); + int volume_type() const { return int(m_volume_type); } + void msw_rescale(); private: friend class ObjectDataViewModel; }; @@ -393,6 +387,7 @@ class ObjectDataViewModel :public wxDataViewModel { std::vector m_objects; std::vector m_volume_bmps; + wxBitmap* m_warning_bmp; wxDataViewCtrl* m_ctrl{ nullptr }; @@ -400,10 +395,13 @@ public: ObjectDataViewModel(); ~ObjectDataViewModel(); - wxDataViewItem Add(const wxString &name, const int extruder); + wxDataViewItem Add( const wxString &name, + const int extruder, + const bool has_errors = false); wxDataViewItem AddVolumeChild( const wxDataViewItem &parent_item, const wxString &name, const Slic3r::ModelVolumeType volume_type, + const bool has_errors = false, const int extruder = 0, const bool create_frst_child = true); wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item); @@ -475,11 +473,16 @@ public: const std::vector& categories); void SetVolumeBitmaps(const std::vector& volume_bmps) { m_volume_bmps = volume_bmps; } + void SetWarningBitmap(wxBitmap* bitmap) { m_warning_bmp = bitmap; } void SetVolumeType(const wxDataViewItem &item, const Slic3r::ModelVolumeType type); void SetAssociatedControl(wxDataViewCtrl* ctrl) { m_ctrl = ctrl; } // Rescale bitmaps for existing Items void Rescale(); + + wxBitmap GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, + const bool is_marked = false); + void DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object = false); }; // ----------------------------------------------------------------------------