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
This commit is contained in:
lane.wei 2024-10-19 12:43:49 +08:00
parent c6ddd329e3
commit 270ae086fb
7 changed files with 230 additions and 41 deletions

View File

@ -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<BoundingBoxf3>& exclude_areas = curr_plate->get_exclude_areas();
std::vector<std::set<int>> unprintable_filament_ids;
std::map<ModelObject*, std::map<int, std::set<int>>> objects_unprintable_filaments;
int extruder_count = build_volume.get_extruder_area_count();
std::vector<std::set<int>> unprintable_filament_ids(extruder_count, std::set<int>());
std::set<ModelObject*> partly_objects_set;
const ModelObjectPtrs &model_objects = model.objects;
for (GLVolume* volume : this->volumes)
{
std::vector<bool> 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<bool> 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<int> 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<float>();
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<bool> 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<int> filaments = model_volume->get_extruders();
unprintable_filament_ids[i].insert(filaments.begin(), filaments.end());
if (object_results) {
std::map<int, std::set<int>>& obj_extruder_filament_maps = objects_unprintable_filaments[model_object];
std::set<int>& 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<ModelObject*>(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<int> 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<int> 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<vector<int>>(result_filaments, result_filaments.begin()));
conflict_filament_vector = result_filaments;
}
}
else
{
conflict_filament_vector.clear();
break;
}
}
if (!conflict_filament_vector.empty())
{
std::set<int> 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<int, std::set<int>>& obj_extruder_filament_maps = object_map.second;
std::set<int> obj_filaments_set;
ObjectFilamentInfo object_filament_info;
object_filament_info.object = model_object;
for (std::map<int, std::set<int>>::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<int>& 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<int>(obj_filaments_set.begin(), obj_filaments_set.end());
object_results->object_filaments.push_back(std::move(object_filament_info));
}
}
}
}
else
{
std::set<int> conflict_filaments_set;
std::vector<int> filament_maps = curr_plate->get_filament_maps();
for (auto& object_map: objects_unprintable_filaments)
{
ModelObject *model_object = object_map.first;
std::map<int, std::set<int>>& obj_extruder_filament_maps = object_map.second;
ObjectFilamentInfo object_filament_info;
object_filament_info.object = model_object;
for (std::map<int, std::set<int>>::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<int>& 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<int>(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)))

View File

@ -53,6 +53,21 @@ enum ModelInstanceEPrintVolumeState : unsigned char;
using ModelObjectPtrs = std::vector<ModelObject*>;
struct ObjectFilamentInfo {
ModelObject* object;
std::map<int, int> manual_filaments; //manual mode: filament id -> extruder id can not be printed
std::vector<int> auto_filaments; //auto mode: filaments in all extruder's outside area
};
struct ObjectFilamentResults {
FilamentMapMode mode;
std::vector<int> filaments; //filaments has conflicts
std::vector<ModelObject*> partly_outside_objects; //partly outside objects
std::vector<ObjectFilamentInfo> object_filaments;
};
// Return appropriate color based on the ModelVolume.
std::array<float, 4> 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);

View File

@ -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<bool>(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<bool>(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<bool>(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<std::array<float, 4>> GLCanvas3D::_parse_colors(const std::vector<st
return output;
}
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.");
void GLCanvas3D::_set_warning_notification(EWarning warning, bool state)
{
enum ErrorType{

View File

@ -727,7 +727,8 @@ public:
unsigned int get_volumes_count() const;
const GLVolumeCollection& get_volumes() const { return m_volumes; }
void reset_volumes();
ModelInstanceEPrintVolumeState check_volumes_outside_state() const;
static void construct_error_string(ObjectFilamentResults& object_result, std::string& error_string);
ModelInstanceEPrintVolumeState check_volumes_outside_state(ObjectFilamentResults* object_results = nullptr) const;
bool is_all_plates_selected() { return m_sel_plate_toolbar.m_all_plates_stats_item && m_sel_plate_toolbar.m_all_plates_stats_item->selected; }
const float get_scale() const;

View File

@ -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<PopNotification> &notification : 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<PopNotification> &notification : m_pop_notifications) {
if (notification->get_type() == NotificationType::BBLGeneralError && notification->compare_text(_u8L("Error:") + "\n" + text)) {
notification->close();
}
}

View File

@ -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<ObjectID>& 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.

View File

@ -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"