diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 43b3968e0e..8b50c742bf 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -22,6 +22,8 @@ #include #include +#include "libslic3r/MultipleBeds.hpp" + // #define SLAPRINT_DO_BENCHMARK #ifdef SLAPRINT_DO_BENCHMARK @@ -298,6 +300,16 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con if (! material_diff.empty()) update_apply_status(this->invalidate_state_by_config_options(material_diff, invalidate_all_model_objects)); + // Multiple beds hack: We currently use one SLAPrint for all beds. It must be invalidated + // when beds are switched. If not done explicitly, supports from previously sliced object + // might end up with wrong offset. + static int last_bed_idx = s_multiple_beds.get_active_bed(); + int current_bed = s_multiple_beds.get_active_bed(); + if (current_bed != last_bed_idx) { + invalidate_all_model_objects = true; + last_bed_idx = current_bed; + } + // Apply variables to placeholder parser. The placeholder parser is currently used // only to generate the output file name. if (! placeholder_parser_diff.empty()) { diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 4ecf572ab5..3808325b6b 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -34,6 +34,7 @@ #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/Tesselate.hpp" #include "libslic3r/PrintConfig.hpp" +#include "libslic3r/MultipleBeds.hpp" #include #include @@ -656,6 +657,7 @@ void GLVolumeCollection::load_object_auxiliary( std::shared_ptr preview_mesh_ptr = print_object->get_mesh_to_print(); if (preview_mesh_ptr != nullptr) backend_mesh = TriangleMesh(*preview_mesh_ptr); + backend_mesh.translate(s_multiple_beds.get_bed_translation(s_multiple_beds.get_active_bed()).cast()); if (!backend_mesh.empty()) { backend_mesh.transform(mesh_trafo_inv); TriangleMesh convex_hull = backend_mesh.convex_hull_3d(); @@ -670,6 +672,7 @@ void GLVolumeCollection::load_object_auxiliary( // Get the support mesh. if (milestone == SLAPrintObjectStep::slaposSupportTree) { TriangleMesh supports_mesh = print_object->support_mesh(); + supports_mesh.translate(s_multiple_beds.get_bed_translation(s_multiple_beds.get_active_bed()).cast()); if (!supports_mesh.empty()) { supports_mesh.transform(mesh_trafo_inv); TriangleMesh convex_hull = supports_mesh.convex_hull_3d(); @@ -683,6 +686,7 @@ void GLVolumeCollection::load_object_auxiliary( // Get the pad mesh. if (milestone == SLAPrintObjectStep::slaposPad) { TriangleMesh pad_mesh = print_object->pad_mesh(); + pad_mesh.translate(s_multiple_beds.get_bed_translation(s_multiple_beds.get_active_bed()).cast()); if (!pad_mesh.empty()) { pad_mesh.transform(mesh_trafo_inv); TriangleMesh convex_hull = pad_mesh.convex_hull_3d(); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index bc544e388e..a7f0bc25b0 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -109,10 +109,22 @@ void GLCanvas3D::select_bed(int i, bool triggered_by_user) int old_bed = s_multiple_beds.get_active_bed(); if ((i == old_bed && !s_multiple_beds.is_autoslicing()) || i == -1) return; + + if (current_printer_technology() == ptSLA) { + // Close SlaSupports or Hollow gizmos before switching beds. They rely on having access to SLAPrintObject to work. + if (GLGizmosManager::EType cur_giz = get_gizmos_manager().get_current_type(); + cur_giz == GLGizmosManager::EType::SlaSupports || cur_giz == GLGizmosManager::EType::Hollow) { + if (! get_gizmos_manager().open_gizmo(get_gizmos_manager().get_current_type())) + return; + } + } wxGetApp().plater()->canvas3D()->m_process->stop(); m_sequential_print_clearance.m_evaluating = true; reset_sequential_print_clearance(); + + + // The stop call above schedules some events that would be processed after the switch. // Among else, on_process_completed would be called, which would stop slicing of // the new bed. We need to stop the process, pump all the events out of the queue @@ -6085,6 +6097,12 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type) m_volumes.set_show_sinking_contours(! m_gizmos.is_hiding_instances()); m_volumes.set_show_non_manifold_edges(!m_gizmos.is_hiding_instances() && m_gizmos.get_current_type() != GLGizmosManager::Simplify); + const Camera& camera = wxGetApp().plater()->get_camera(); + auto trafo = camera.get_view_matrix(); + if (current_printer_technology() == ptSLA && wxGetApp().plater()->is_preview_shown()) { + trafo.translate(s_multiple_beds.get_bed_translation(s_multiple_beds.get_active_bed())); + } + GLShaderProgram* shader = wxGetApp().get_shader("gouraud"); if (shader != nullptr) { shader->start_using(); @@ -6096,8 +6114,8 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type) { if (m_picking_enabled && !m_gizmos.is_dragging() && m_layers_editing.is_enabled() && (m_layers_editing.last_object_id != -1) && (m_layers_editing.object_max_z() > 0.0f)) { int object_id = m_layers_editing.last_object_id; - const Camera& camera = wxGetApp().plater()->get_camera(); - m_volumes.render(type, false, camera.get_view_matrix(), camera.get_projection_matrix(), [object_id](const GLVolume& volume) { + + m_volumes.render(type, false, trafo, camera.get_projection_matrix(), [object_id](const GLVolume& volume) { // Which volume to paint without the layer height profile shader? return volume.is_active && (volume.is_modifier || volume.composite_id.object_id != object_id); }); @@ -6107,7 +6125,7 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type) else { // do not cull backfaces to show broken geometry, if any const Camera& camera = wxGetApp().plater()->get_camera(); - m_volumes.render(type, m_picking_enabled, camera.get_view_matrix(), camera.get_projection_matrix(), [this](const GLVolume& volume) { + m_volumes.render(type, m_picking_enabled, trafo, camera.get_projection_matrix(), [this](const GLVolume& volume) { return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0); }); } @@ -6129,7 +6147,7 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type) case GLVolumeCollection::ERenderType::Transparent: { const Camera& camera = wxGetApp().plater()->get_camera(); - m_volumes.render(type, false, camera.get_view_matrix(), camera.get_projection_matrix()); + m_volumes.render(type, false, trafo, camera.get_projection_matrix()); break; } } @@ -6382,6 +6400,7 @@ void Slic3r::GUI::GLCanvas3D::_render_bed_selector() height = win_size.y; wxGetApp().imgui()->set_requires_extra_frame(); } + m_bed_selector_current_height = height; float max_width = win_x_pos; if (is_legend_shown()) @@ -6739,7 +6758,7 @@ void GLCanvas3D::_render_camera_target_validation_box() } #endif // ENABLE_SHOW_CAMERA_TARGET -static void render_sla_layer_legend(const SLAPrint& print, int layer_idx, int cnv_width) +static void render_sla_layer_legend(const SLAPrint& print, int layer_idx, int cnv_width, float bed_sel_height) { const std::vector& areas = print.print_statistics().layers_areas; const std::vector& times = print.print_statistics().layers_times_running_total; @@ -6750,7 +6769,7 @@ static void render_sla_layer_legend(const SLAPrint& print, int layer_idx, int cn const double time_until_layer = times[layer_idx]; ImGuiWrapper& imgui = *wxGetApp().imgui(); - ImGuiPureWrap::set_next_window_pos(float(cnv_width) - imgui.get_style_scaling() * 5.f, 5.f, ImGuiCond_Always, 1.0f, 0.0f); + ImGuiPureWrap::set_next_window_pos(float(cnv_width) - imgui.get_style_scaling() * 5.f, 5.f + bed_sel_height, ImGuiCond_Always, 1.0f, 0.0f); ImGui::SetNextWindowBgAlpha(0.6f); ImGuiPureWrap::begin(_u8L("Layer statistics"), ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoFocusOnAppearing); @@ -6786,7 +6805,7 @@ void GLCanvas3D::_render_sla_slices() double slider_width = 0.; if (const Preview* preview = dynamic_cast(m_canvas->GetParent())) slider_width = preview->get_layers_slider_width(); - render_sla_layer_legend(*print, m_layer_slider_index, get_canvas_size().get_width() - slider_width); + render_sla_layer_legend(*print, m_layer_slider_index, get_canvas_size().get_width() - slider_width, m_bed_selector_current_height); } double clip_min_z = -m_clipping_planes[0].get_data()[3]; @@ -6887,6 +6906,7 @@ void GLCanvas3D::_render_sla_slices() for (const SLAPrintObject::Instance& inst : obj->instances()) { const Camera& camera = wxGetApp().plater()->get_camera(); Transform3d view_model_matrix = camera.get_view_matrix() * + Geometry::translation_transform(s_multiple_beds.get_bed_translation(s_multiple_beds.get_active_bed())) * Geometry::translation_transform({ unscale(inst.shift.x()), unscale(inst.shift.y()), 0.0 }) * Geometry::rotation_transform(inst.rotation * Vec3d::UnitZ()); if (obj->is_left_handed()) diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index ed812cf208..528681630d 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -512,6 +512,7 @@ private: // see request_extra_frame() bool m_extra_frame_requested; bool m_event_handlers_bound{ false }; + float m_bed_selector_current_height = 0.f; GLVolumeCollection m_volumes; #if SLIC3R_OPENGL_ES diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 47c60ac1bb..39441462df 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -81,6 +81,12 @@ void GLGizmoHollow::data_changed(bool is_serializing) void GLGizmoHollow::on_render() { + if (! selected_print_object_exists(m_parent, wxEmptyString)) { + wxGetApp().CallAfter([this]() { + // Close current gizmo. + m_parent.get_gizmos_manager().open_gizmo(m_parent.get_gizmos_manager().get_current_type()); + }); + } const Selection& selection = m_parent.get_selection(); const CommonGizmosDataObjects::SelectionInfo* sel_info = m_c->selection_info(); @@ -137,7 +143,7 @@ void GLGizmoHollow::render_points(const Selection& selection) if (!inst) return; - double shift_z = m_c->selection_info()->print_object()->get_current_elevation(); + double shift_z = m_c->selection_info()->print_object() ? m_c->selection_info()->print_object()->get_current_elevation() : 0.; Transform3d trafo(inst->get_transformation().get_matrix()); trafo.translation()(2) += shift_z; const Geometry::Transformation transformation{trafo}; @@ -849,6 +855,14 @@ void GLGizmoHollow::on_set_state() if (m_state == m_old_state) return; + if (m_state == On) { + // Make sure that current object is on current bed. Refuse to turn on otherwise. + if (! selected_print_object_exists(m_parent, _L("Selected object has to be on the active bed."))) { + m_state = Off; + return; + } + } + if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off m_parent.post_event(SimpleEvent(EVT_GLCANVAS_FORCE_UPDATE)); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.cpp index c113368fff..ffc49784d3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.cpp @@ -9,6 +9,10 @@ #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" +#include "slic3r/GUI/MsgDialog.hpp" +#include "slic3r/GUI/MainFrame.hpp" + +#include "libslic3r/MultipleBeds.hpp" namespace Slic3r { namespace GUI { @@ -21,6 +25,22 @@ GLGizmoSlaBase::GLGizmoSlaBase(GLCanvas3D& parent, const std::string& icon_filen , m_min_sla_print_object_step((int)min_step) {} +/*static*/ bool GLGizmoSlaBase::selected_print_object_exists(const GLCanvas3D& canvas, const wxString& text) +{ + if (const Selection& sel = canvas.get_selection(); !sel.is_single_full_instance() || !sel.get_model()->objects[sel.get_object_idx()] + || ! canvas.sla_print()->get_print_object_by_model_object_id(sel.get_model()->objects[sel.get_object_idx()]->id())) + { + if (! text.IsEmpty()) + wxGetApp().CallAfter([text]() { + MessageDialog dlg(GUI::wxGetApp().mainframe, text, + _L("Bed selection mismatch"), wxICON_INFORMATION | wxOK); + dlg.ShowModal(); + }); + return false; + } + return true; +} + void GLGizmoSlaBase::reslice_until_step(SLAPrintObjectStep step, bool postpone_error_messages) { wxGetApp().CallAfter([this, step, postpone_error_messages]() { @@ -94,12 +114,14 @@ void GLGizmoSlaBase::update_volumes() const Transform3d po_trafo_inverse = po->trafo().inverse(); // main mesh + backend_mesh.translate(s_multiple_beds.get_bed_translation(s_multiple_beds.get_active_bed()).cast()); backend_mesh.transform(po_trafo_inverse); add_volume(backend_mesh, 0, true); // supports mesh TriangleMesh supports_mesh = po->support_mesh(); if (!supports_mesh.empty()) { + supports_mesh.translate(s_multiple_beds.get_bed_translation(s_multiple_beds.get_active_bed()).cast()); supports_mesh.transform(po_trafo_inverse); add_volume(supports_mesh, -int(slaposSupportTree)); } @@ -107,6 +129,7 @@ void GLGizmoSlaBase::update_volumes() // pad mesh TriangleMesh pad_mesh = po->pad_mesh(); if (!pad_mesh.empty()) { + pad_mesh.translate(s_multiple_beds.get_bed_translation(s_multiple_beds.get_active_bed()).cast()); pad_mesh.transform(po_trafo_inverse); add_volume(pad_mesh, -int(slaposPad)); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.hpp index 03c941153e..fe06ecdb32 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaBase.hpp @@ -49,6 +49,8 @@ protected: const GLVolumeCollection &volumes() const { return m_volumes; } + static bool selected_print_object_exists(const GLCanvas3D& canvas, const wxString& text); + private: GLVolumeCollection m_volumes; bool m_input_enabled{ false }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 081f216a75..fdba58a62b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -103,6 +103,13 @@ void GLGizmoSlaSupports::data_changed(bool is_serializing) void GLGizmoSlaSupports::on_render() { + if (! selected_print_object_exists(m_parent, wxEmptyString)) { + wxGetApp().CallAfter([this]() { + // Close current gizmo. + m_parent.get_gizmos_manager().open_gizmo(m_parent.get_gizmos_manager().get_current_type()); + }); + } + if (m_state == On) { // This gizmo is showing the object elevated. Tell the common // SelectionInfo object to lie about the actual shift. @@ -843,6 +850,13 @@ bool GLGizmoSlaSupports::ask_about_changes(std::function on_yes, std::fu void GLGizmoSlaSupports::on_set_state() { if (m_state == On) { // the gizmo was just turned on + + // Make sure that current object is on current bed. Refuse to turn on otherwise. + if (! selected_print_object_exists(m_parent, _L("Selected object has to be on the active bed."))) { + m_state = Off; + return; + } + // Set default head diameter from config. const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; m_new_point_head_diameter = static_cast(cfg.option("support_head_front_diameter"))->value; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 5f1aaa1969..8cab8c17fa 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2129,12 +2129,12 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool int active_bed = s_multiple_beds.get_active_bed(); background_process.set_temp_output_path(active_bed); - background_process.set_fff_print(fff_prints[active_bed].get()); - background_process.set_sla_print(sla_prints[active_bed].get()); + background_process.set_fff_print(&q->active_fff_print()); + background_process.set_sla_print(&q->active_sla_print()); background_process.set_gcode_result(&gcode_results[active_bed]); background_process.select_technology(this->printer_technology); - if (s_beds_just_switched) { + if (s_beds_just_switched && printer_technology == ptFFF) { PrintBase::SlicingStatus status(q->active_fff_print(), -1); SlicingStatusEvent evt(EVT_SLICING_UPDATE, 0, status); on_slicing_update(evt); @@ -3077,10 +3077,10 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) warning_steps.clear(); if (flags == PrintBase::SlicingStatus::UPDATE_PRINT_STEP_WARNINGS) { int i = 0; - while (i < int(psCount)) { warning_steps.push_back(i); ++i; } + while (i < int(printer_technology == ptFFF ? psCount : slapsCount)) { warning_steps.push_back(i); ++i; } } else { int i = 0; - while (i < int(posCount)) { warning_steps.push_back(i); ++i; } + while (i < int(printer_technology == ptFFF ? posCount : slaposCount)) { warning_steps.push_back(i); ++i; } for (const PrintObject* po : wxGetApp().plater()->active_fff_print().objects()) object_ids.push_back(po->id()); } @@ -5652,7 +5652,7 @@ void Plater::export_gcode(bool prefer_removable) start_dir, from_path(default_output_file.filename()), printer_technology() == ptFFF ? GUI::file_wildcards(FT_GCODE, ext) : - GUI::sla_wildcards(p->sla_prints.front()->printer_config().sla_archive_format.value.c_str(), ext), + GUI::sla_wildcards(active_sla_print().printer_config().sla_archive_format.value.c_str(), ext), wxFD_SAVE | wxFD_OVERWRITE_PROMPT ); if (dlg.ShowModal() == wxID_OK) { @@ -5769,7 +5769,7 @@ void Plater::export_stl_obj(bool extended, bool selection_only) auto mesh_to_export_sla = [&, this](const ModelObject& mo, int instance_id) { TriangleMesh mesh; - const SLAPrintObject *object; // LUKAS = this->p->sla_print.get_print_object_by_model_object_id(mo.id()); + const SLAPrintObject *object = this->active_sla_print().get_print_object_by_model_object_id(mo.id()); if (!object || !object->get_mesh_to_print() || object->get_mesh_to_print()->empty()) { if (!extended) @@ -7330,7 +7330,11 @@ wxMenu* Plater::multi_selection_menu() { return p->menus.multi_selection_menu() Print& Plater::active_fff_print() { return *p->fff_prints[s_multiple_beds.get_active_bed()]; } -SLAPrint& Plater::active_sla_print() { return *p->sla_prints[s_multiple_beds.get_active_bed()]; } +//SLAPrint& Plater::active_sla_print() { return *p->sla_prints[s_multiple_beds.get_active_bed()]; } + +// For now, only use the first SLAPrint for all the beds - it means reslicing +// everything when a bed is changed. +SLAPrint& Plater::active_sla_print() { return *p->sla_prints.front(); } SuppressBackgroundProcessingUpdate::SuppressBackgroundProcessingUpdate() :