From 270ae086fbca576b75901313959c92cbfb913db6 Mon Sep 17 00:00:00 2001 From: "lane.wei" Date: Sat, 19 Oct 2024 12:43:49 +0800 Subject: [PATCH] ENH: Scene: check object position error in 3DScene for multi-extruder for some obvious error, we identified it and show to user JIRA: no-jira Change-Id: Id0365e89c4121ccccb9b5627a98428704432ab58 --- src/slic3r/GUI/3DScene.cpp | 147 ++++++++++++++++++++++--- src/slic3r/GUI/3DScene.hpp | 17 ++- src/slic3r/GUI/GLCanvas3D.cpp | 67 ++++++++--- src/slic3r/GUI/GLCanvas3D.hpp | 3 +- src/slic3r/GUI/NotificationManager.cpp | 16 ++- src/slic3r/GUI/NotificationManager.hpp | 5 + src/slic3r/GUI/Plater.cpp | 16 ++- 7 files changed, 230 insertions(+), 41 deletions(-) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index a7e6df80a..083ef01b0 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -1687,7 +1687,7 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, glsafe(::glDisable(GL_BLEND)); } -bool GLVolumeCollection::check_outside_state(const BuildVolume &build_volume, ModelInstanceEPrintVolumeState *out_state) const +bool GLVolumeCollection::check_outside_state(const BuildVolume &build_volume, ModelInstanceEPrintVolumeState *out_state, ObjectFilamentResults* object_results) const { if (GUI::wxGetApp().plater() == NULL) { @@ -1720,9 +1720,14 @@ bool GLVolumeCollection::check_outside_state(const BuildVolume &build_volume, Mo BuildVolume plate_build_volume(pp_bed_shape, build_volume.printable_height(), build_volume.extruder_areas()); const std::vector& exclude_areas = curr_plate->get_exclude_areas(); - std::vector> unprintable_filament_ids; + std::map>> objects_unprintable_filaments; + int extruder_count = build_volume.get_extruder_area_count(); + std::vector> unprintable_filament_ids(extruder_count, std::set()); + std::set partly_objects_set; + const ModelObjectPtrs &model_objects = model.objects; for (GLVolume* volume : this->volumes) { + std::vector inside_extruders; if (! volume->is_modifier && (volume->shader_outside_printer_detection_enabled || (! volume->is_wipe_tower && volume->composite_id.volume_id >= 0))) { BuildVolume::ObjectState state; const BoundingBoxf3& bb = volume_bbox(*volume); @@ -1733,22 +1738,9 @@ bool GLVolumeCollection::check_outside_state(const BuildVolume &build_volume, Mo case BuildVolume::Type::Rectangle: //FIXME this test does not evaluate collision of a build volume bounding box with non-convex objects. state = plate_build_volume.volume_state_bbox(bb); - if ((state == BuildVolume::ObjectState::Inside) && (build_volume.get_extruder_area_count() > 0)) + if ((state == BuildVolume::ObjectState::Inside) && (extruder_count > 1)) { - std::vector inside_extruders; state = plate_build_volume.check_volume_bbox_state_with_extruder_areas(bb, inside_extruders); - if (state == BuildVolume::ObjectState::Limited) { - unprintable_filament_ids.resize(inside_extruders.size()); - const ModelObjectPtrs &model_objects = model.objects; - ModelObject *model_object = model_objects[volume->object_idx()]; - ModelVolume *model_volume = model_object->volumes[volume->volume_idx()]; - for (size_t i = 0; i < inside_extruders.size(); ++i) { - if (!inside_extruders[i]) { - std::vector extruders = model_volume->get_extruders(); - unprintable_filament_ids[i].insert(extruders.begin(), extruders.end()); - } - } - } } break; case BuildVolume::Type::Circle: @@ -1759,9 +1751,8 @@ bool GLVolumeCollection::check_outside_state(const BuildVolume &build_volume, Mo const indexed_triangle_set& convex_mesh_it = volume_convex_mesh(*volume).its; const Transform3f trafo = volume->world_matrix().cast(); state = plate_build_volume.object_state(convex_mesh_it, trafo, volume_sinking(*volume)); - if ((state == BuildVolume::ObjectState::Inside) && (build_volume.get_extruder_area_count() > 0)) + if ((state == BuildVolume::ObjectState::Inside) && (extruder_count > 1)) { - std::vector inside_extruders; state = plate_build_volume.check_object_state_with_extruder_areas(convex_mesh_it, trafo, inside_extruders); } break; @@ -1772,6 +1763,23 @@ bool GLVolumeCollection::check_outside_state(const BuildVolume &build_volume, Mo break; } assert(state != BuildVolume::ObjectState::Below); + + if (state == BuildVolume::ObjectState::Limited) { + //unprintable_filament_ids.resize(inside_extruders.size()); + ModelObject *model_object = model_objects[volume->object_idx()]; + ModelVolume *model_volume = model_object->volumes[volume->volume_idx()]; + for (size_t i = 0; i < inside_extruders.size(); ++i) { + if (!inside_extruders[i]) { + std::vector filaments = model_volume->get_extruders(); + unprintable_filament_ids[i].insert(filaments.begin(), filaments.end()); + if (object_results) { + std::map>& obj_extruder_filament_maps = objects_unprintable_filaments[model_object]; + std::set& obj_extruder_filaments = obj_extruder_filament_maps[i+1]; + obj_extruder_filaments.insert(filaments.begin(), filaments.end()); + } + } + } + } } //int64_t comp_id = ((int64_t)volume->composite_id.object_id << 32) | ((int64_t)volume->composite_id.instance_id); @@ -1781,6 +1789,7 @@ bool GLVolumeCollection::check_outside_state(const BuildVolume &build_volume, Mo if (state == BuildVolume::ObjectState::Colliding) { overall_state = ModelInstancePVS_Partly_Outside; + partly_objects_set.emplace(model_objects[volume->object_idx()]); } else if ((state == BuildVolume::ObjectState::Limited) && (overall_state != ModelInstancePVS_Partly_Outside)) overall_state = ModelInstancePVS_Limited; @@ -1830,6 +1839,108 @@ bool GLVolumeCollection::check_outside_state(const BuildVolume &build_volume, Mo curr_plate->set_unprintable_filament_ids(unprintable_filament_vec); + if (object_results && !partly_objects_set.empty()) { + object_results->partly_outside_objects = std::vector(partly_objects_set.begin(), partly_objects_set.end()); + } + + //check per-object error for extruder areas + if (object_results && (extruder_count > 1)) + { + object_results->mode = curr_plate->get_filament_map_mode(); + if (object_results->mode == FilamentMapMode::fmmAuto) + { + std::vector conflict_filament_vector; + for (int index = 0; index < extruder_count; index++ ) + { + if (!unprintable_filament_vec[index].empty()) + { + std::sort (unprintable_filament_vec[index].begin(), unprintable_filament_vec[index].end()); + if (index == 0) + conflict_filament_vector = unprintable_filament_vec[index]; + else + { + std::vector result_filaments; + //result_filaments.reserve(conflict_filaments.size()); + std::set_intersection (conflict_filament_vector.begin(), conflict_filament_vector.end(), unprintable_filament_vec[index].begin(), unprintable_filament_vec[index].end(), insert_iterator>(result_filaments, result_filaments.begin())); + conflict_filament_vector = result_filaments; + } + } + else + { + conflict_filament_vector.clear(); + break; + } + } + + if (!conflict_filament_vector.empty()) + { + std::set conflict_filaments_set(conflict_filament_vector.begin(), conflict_filament_vector.end()); + object_results->filaments = conflict_filament_vector; + + for (auto& object_map: objects_unprintable_filaments) + { + ModelObject *model_object = object_map.first; + std::map>& obj_extruder_filament_maps = object_map.second; + std::set obj_filaments_set; + ObjectFilamentInfo object_filament_info; + object_filament_info.object = model_object; + + for (std::map>::iterator extruder_map_iter = obj_extruder_filament_maps.begin(); extruder_map_iter != obj_extruder_filament_maps.end(); extruder_map_iter++ ) + { + int extruder_id = extruder_map_iter->first; + std::set& filaments_set = extruder_map_iter->second; + + for (int filament: filaments_set) + { + if (conflict_filaments_set.find(filament) != conflict_filaments_set.end()) + { + obj_filaments_set.emplace(filament); + } + } + } + if (!obj_filaments_set.empty()) { + object_filament_info.auto_filaments = std::vector(obj_filaments_set.begin(), obj_filaments_set.end()); + object_results->object_filaments.push_back(std::move(object_filament_info)); + } + } + } + } + else + { + std::set conflict_filaments_set; + std::vector filament_maps = curr_plate->get_filament_maps(); + for (auto& object_map: objects_unprintable_filaments) + { + ModelObject *model_object = object_map.first; + std::map>& obj_extruder_filament_maps = object_map.second; + ObjectFilamentInfo object_filament_info; + object_filament_info.object = model_object; + + for (std::map>::iterator extruder_map_iter = obj_extruder_filament_maps.begin(); extruder_map_iter != obj_extruder_filament_maps.end(); extruder_map_iter++ ) + { + int extruder_id = extruder_map_iter->first; + std::set& filaments_set = extruder_map_iter->second; + + for (int filament: filaments_set) + { + if (filament_maps[filament - 1] == extruder_id) + { + object_filament_info.manual_filaments.emplace(filament, extruder_id); + conflict_filaments_set.emplace(filament); + } + } + } + if (!object_filament_info.manual_filaments.empty()) + { + object_results->object_filaments.push_back(std::move(object_filament_info)); + } + } + if (!conflict_filaments_set.empty()) { + object_results->filaments = std::vector(conflict_filaments_set.begin(), conflict_filaments_set.end()); + } + } + } + /*for (GLVolume* volume : this->volumes) { if (! volume->is_modifier && (volume->shader_outside_printer_detection_enabled || (! volume->is_wipe_tower && volume->composite_id.volume_id >= 0))) diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 50244fba5..4f96c05c0 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -53,6 +53,21 @@ enum ModelInstanceEPrintVolumeState : unsigned char; using ModelObjectPtrs = std::vector; +struct ObjectFilamentInfo { + ModelObject* object; + std::map manual_filaments; //manual mode: filament id -> extruder id can not be printed + + std::vector auto_filaments; //auto mode: filaments in all extruder's outside area +}; + +struct ObjectFilamentResults { + FilamentMapMode mode; + std::vector filaments; //filaments has conflicts + std::vector partly_outside_objects; //partly outside objects + + std::vector object_filaments; +}; + // Return appropriate color based on the ModelVolume. std::array color_from_model_volume(const ModelVolume& model_volume); @@ -748,7 +763,7 @@ public: // returns true if all the volumes are completely contained in the print volume // returns the containment state in the given out_state, if non-null - bool check_outside_state(const Slic3r::BuildVolume& build_volume, ModelInstanceEPrintVolumeState* out_state) const; + bool check_outside_state(const Slic3r::BuildVolume& build_volume, ModelInstanceEPrintVolumeState* out_state, ObjectFilamentResults* object_results) const; void reset_outside_state(); void update_colors_by_extruder(const DynamicPrintConfig *config, bool is_update_alpha = true); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 52d2d2344..4cb5c0c93 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -125,6 +125,11 @@ float RetinaHelper::get_scale_factor() { return float(m_window->GetContentScaleF #undef Convex #endif +std::string object_limited_text = _u8L("An object is laid on the left/right extruder only area.\n" + "Please make sure the filaments used by this object on this area are not mapped to the other extruders."); +std::string object_clashed_text = _u8L("An object is laid over the boundary of plate or exceeds the height limit.\n" + "Please solve the problem by moving it totally on or off the plate, and confirming that the height is within the build volume."); + GLCanvas3D::LayersEditing::~LayersEditing() { if (m_z_texture_id != 0) { @@ -1388,7 +1393,38 @@ BoundingBoxf3 GLCanvas3D::_get_current_partplate_print_volume() return test_volume; } -ModelInstanceEPrintVolumeState GLCanvas3D::check_volumes_outside_state() const +void GLCanvas3D::construct_error_string(ObjectFilamentResults& object_result, std::string& error_string) +{ + error_string.clear(); + if (!object_result.partly_outside_objects.empty()) { + error_string += _u8L("Following objects are laid over the boundary of plate or exceeds the height limit:\n"); + for(auto& object: object_result.partly_outside_objects) + { + error_string += object->name; + error_string += "\n"; + } + error_string += _u8L("Please solve the problem by moving it totally on or off the plate, and confirming that the height is within the build volume.\n"); + } + + if (!object_result.filaments.empty()) { + if (object_result.mode == FilamentMapMode::fmmAuto) { + error_string += _u8L("In the Filament auto-matching mode, Filament "); + for (auto& filament: object_result.filaments) + error_string += std::to_string(filament) + " "; + error_string += "are placed in the unprintable area of all extruders, making it impossible to match them with a suitable extruder. This may be caused by the following objects:\n"; + for(ObjectFilamentInfo& object_filament: object_result.object_filaments) + { + error_string += object_filament.object->name; + error_string += "\n"; + } + error_string += _u8L("Please solve the problem by moving them within the build volume.\n"); + } + else { + } + } +} + +ModelInstanceEPrintVolumeState GLCanvas3D::check_volumes_outside_state(ObjectFilamentResults* object_results) const { //BBS: if not initialized, return inside directly insteadof assert if (!m_initialized) { @@ -1397,7 +1433,9 @@ ModelInstanceEPrintVolumeState GLCanvas3D::check_volumes_outside_state() const //assert(m_initialized); ModelInstanceEPrintVolumeState state; - m_volumes.check_outside_state(m_bed.build_volume(), &state); + m_volumes.check_outside_state(m_bed.build_volume(), &state, object_results); + + construct_error_string(*object_results, object_clashed_text); return state; } @@ -2823,26 +2861,30 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re // checks for geometry outside the print volume to render it accordingly if (!m_volumes.empty()) { ModelInstanceEPrintVolumeState state; - const bool contained_min_one = m_volumes.check_outside_state(m_bed.build_volume(), &state); + ObjectFilamentResults object_results; + const bool contained_min_one = m_volumes.check_outside_state(m_bed.build_volume(), &state, &object_results); const bool partlyOut = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Partly_Outside); const bool fullyOut = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Fully_Outside); const bool objectLimited = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Limited); - _set_warning_notification(EWarning::ObjectClashed, partlyOut); + construct_error_string(object_results, object_clashed_text); + + _set_warning_notification(EWarning::ObjectClashed, partlyOut || !object_results.filaments.empty()); _set_warning_notification(EWarning::ObjectLimited, objectLimited); //BBS: turn off the warning when fully outside //_set_warning_notification(EWarning::ObjectOutside, fullyOut); - if (printer_technology != ptSLA || !contained_min_one) - _set_warning_notification(EWarning::SlaSupportsOutside, false); + //if (printer_technology != ptSLA || !contained_min_one) + // _set_warning_notification(EWarning::SlaSupportsOutside, false); - post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, - contained_min_one && !m_model->objects.empty() && !partlyOut)); + bool model_fits = contained_min_one && !m_model->objects.empty() && !partlyOut && object_results.filaments.empty(); + post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, model_fits)); + ppl.get_curr_plate()->update_slice_ready_status(model_fits); } else { _set_warning_notification(EWarning::ObjectOutside, false); _set_warning_notification(EWarning::ObjectClashed, false); _set_warning_notification(EWarning::ObjectLimited, false); - _set_warning_notification(EWarning::SlaSupportsOutside, false); + //_set_warning_notification(EWarning::SlaSupportsOutside, false); post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, false)); } } @@ -7225,7 +7267,7 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with } } if (m_requires_check_outside_state) { - m_volumes.check_outside_state(build_volume, nullptr); + m_volumes.check_outside_state(build_volume, nullptr, nullptr); m_requires_check_outside_state = false; } } @@ -9592,11 +9634,6 @@ std::vector> GLCanvas3D::_parse_colors(const std::vectorselected; } const float get_scale() const; diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 08185ff12..1f08785d6 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -1725,7 +1725,21 @@ void NotificationManager::push_plater_error_notification(const std::string& text void NotificationManager::close_plater_error_notification(const std::string& text) { for (std::unique_ptr ¬ification : m_pop_notifications) { - if (notification->get_type() == NotificationType::PlaterError && notification->compare_text(_u8L("Error:") + "\n" + text)) { + if (notification->get_type() == NotificationType::PlaterError) { + notification->close(); + } + } +} + +void NotificationManager::push_general_error_notification(const std::string& text) +{ + push_notification_data({ NotificationType::BBLGeneralError, NotificationLevel::ErrorNotificationLevel, 0, _u8L("Error:") + "\n" + text }, 0); +} + +void NotificationManager::close_general_error_notification(const std::string& text) +{ + for (std::unique_ptr ¬ification : m_pop_notifications) { + if (notification->get_type() == NotificationType::BBLGeneralError && notification->compare_text(_u8L("Error:") + "\n" + text)) { notification->close(); } } diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 9b7881214..751e2c266 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -80,6 +80,8 @@ enum class NotificationType // Slicing warnings, issued by the slicing process. // Slicing warnings are registered for a particular Print milestone or a PrintObject and its milestone. SlicingWarning, + // BBL: general error + BBLGeneralError, // Object partially outside the print volume. Cannot slice. PlaterError, // Object fully outside the print volume, or extrusion outside the print volume. Slicing is not disabled. @@ -220,6 +222,9 @@ public: // Release those slicing warnings, which refer to an ObjectID, which is not in the list. // living_oids is expected to be sorted. void remove_slicing_warnings_of_released_objects(const std::vector& living_oids); + // general error message + void push_general_error_notification(const std::string& text); + void close_general_error_notification(const std::string& text); // Object partially outside of the printer working space, cannot print. No fade out. void push_plater_error_notification(const std::string& text); // Object fully out of the printer working space and such. No fade out. diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index b2b68b2e8..fbb183ceb 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5173,7 +5173,9 @@ void Plater::priv::object_list_changed() const bool export_in_progress = this->background_process.is_export_scheduled(); // || ! send_gcode_file.empty()); // XXX: is this right? //const bool model_fits = view3D->get_canvas3d()->check_volumes_outside_state() == ModelInstancePVS_Inside; - const bool model_fits = view3D->get_canvas3d()->check_volumes_outside_state() != ModelInstancePVS_Partly_Outside; + ObjectFilamentResults object_results; + bool model_fits = view3D->get_canvas3d()->check_volumes_outside_state(&object_results) != ModelInstancePVS_Partly_Outside; + model_fits = model_fits && object_results.filaments.empty(); PartPlate* part_plate = partplate_list.get_curr_plate(); @@ -6572,7 +6574,9 @@ void Plater::priv::set_current_panel(wxPanel* panel, bool no_slice) // see: Plater::priv::object_list_changed() // FIXME: it may be better to have a single function making this check and let it be called wherever needed bool export_in_progress = this->background_process.is_export_scheduled(); - bool model_fits = this->view3D->get_canvas3d()->check_volumes_outside_state() != ModelInstancePVS_Partly_Outside; + ObjectFilamentResults object_results; + bool model_fits = this->view3D->get_canvas3d()->check_volumes_outside_state(&object_results) != ModelInstancePVS_Partly_Outside; + model_fits = model_fits&&object_results.filaments.empty(); //BBS: add partplate logic PartPlate * current_plate = this->partplate_list.get_curr_plate(); bool only_has_gcode_need_preview = false; @@ -12222,7 +12226,7 @@ void Plater::export_stl(bool extended, bool selection_only, bool multi_stls) const SLAPrintObject *object = this->p->sla_print.get_print_object_by_model_object_id(mo.id()); if (auto m = object->get_mesh_to_print(); m.empty()) - mesh = combine_mesh_fff(mo, instance_id, [this](const std::string& msg) {return get_notification_manager()->push_plater_error_notification(msg); }); + mesh = combine_mesh_fff(mo, instance_id, [this](const std::string& msg) {return get_notification_manager()->push_general_error_notification(msg); }); else { const Transform3d mesh_trafo_inv = object->trafo().inverse(); const bool is_left_handed = object->is_left_handed(); @@ -12289,7 +12293,7 @@ void Plater::export_stl(bool extended, bool selection_only, bool multi_stls) if (p->printer_technology == ptFFF){ if (export_with_boolean) { mesh_to_export = [this](const ModelObject& mo, int instance_id) {return Plater::combine_mesh_fff(mo, instance_id, - [this](const std::string& msg) {return get_notification_manager()->push_plater_error_notification(msg); }); }; + [this](const std::string& msg) {return get_notification_manager()->push_general_error_notification(msg); }); }; } else { mesh_to_export = mesh_to_export_fff_no_boolean; } @@ -14342,8 +14346,10 @@ extern std::string object_limited_text; extern std::string object_clashed_text; void Plater::validate_current_plate(bool& model_fits, bool& validate_error) { - ModelInstanceEPrintVolumeState state = p->view3D->get_canvas3d()->check_volumes_outside_state(); + ObjectFilamentResults object_results; + ModelInstanceEPrintVolumeState state = p->view3D->get_canvas3d()->check_volumes_outside_state(&object_results); model_fits = (state != ModelInstancePVS_Partly_Outside); + model_fits = model_fits && object_results.filaments.empty(); validate_error = false; if (p->printer_technology == ptFFF) { //std::string plater_text = _u8L("An object is laid over the boundary of plate or exceeds the height limit.\n"