#include "GLGizmoHollow.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/Camera.hpp" #include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" #include #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI_ObjectSettings.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/Plater.hpp" #include "libslic3r/PresetBundle.hpp" #include "libslic3r/Model.hpp" namespace Slic3r { namespace GUI { GLGizmoHollow::GLGizmoHollow(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) { m_vbo_cylinder.init_from(make_cylinder(1., 1.)); } bool GLGizmoHollow::on_init() { m_shortcut_key = WXK_CONTROL_H; m_desc["enable"] = _(L("Hollow this object")); m_desc["preview"] = _(L("Preview hollowed and drilled model")); m_desc["offset"] = _(L("Offset")) + ": "; m_desc["quality"] = _(L("Quality")) + ": "; m_desc["closing_distance"] = _(L("Closing distance")) + ": "; m_desc["hole_diameter"] = _(L("Hole diameter")) + ": "; m_desc["hole_depth"] = _(L("Hole depth")) + ": "; m_desc["remove_selected"] = _(L("Remove selected holes")); m_desc["remove_all"] = _(L("Remove all holes")); m_desc["clipping_of_view"] = _(L("Clipping of view"))+ ": "; m_desc["reset_direction"] = _(L("Reset direction")); m_desc["show_supports"] = _(L("Show supports")); return true; } void GLGizmoHollow::set_sla_support_data(ModelObject*, const Selection&) { if (! m_c->selection_info()) return; const ModelObject* mo = m_c->selection_info()->model_object(); if (m_state == On && mo) { if (m_old_mo_id != mo->id()) { reload_cache(); m_old_mo_id = mo->id(); } if (m_c->hollowed_mesh() && m_c->hollowed_mesh()->get_hollowed_mesh()) m_holes_in_drilled_mesh = mo->sla_drain_holes; } } void GLGizmoHollow::on_render() { const Selection& selection = m_parent.get_selection(); const CommonGizmosDataObjects::SelectionInfo* sel_info = m_c->selection_info(); // If current m_c->m_model_object does not match selection, ask GLCanvas3D to turn us off if (m_state == On && (sel_info->model_object() != selection.get_model()->objects[selection.get_object_idx()] || sel_info->get_active_instance() != selection.get_instance_idx())) { m_parent.post_event(SimpleEvent(EVT_GLCANVAS_RESETGIZMOS)); return; } glsafe(::glEnable(GL_BLEND)); glsafe(::glEnable(GL_DEPTH_TEST)); if (selection.is_from_single_instance()) render_points(selection, false); m_selection_rectangle.render(m_parent); m_c->object_clipper()->render_cut(); m_c->supports_clipper()->render_cut(); glsafe(::glDisable(GL_BLEND)); } void GLGizmoHollow::on_render_for_picking() { const Selection& selection = m_parent.get_selection(); //#if ENABLE_RENDER_PICKING_PASS // m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z(); //#endif glsafe(::glEnable(GL_DEPTH_TEST)); render_points(selection, true); } void GLGizmoHollow::render_points(const Selection& selection, bool picking) const { GLShaderProgram* shader = picking ? nullptr : wxGetApp().get_shader("gouraud_light"); if (shader) shader->start_using(); ScopeGuard guard([shader]() { if (shader) shader->stop_using(); }); const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); const Transform3d& instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse(); const Transform3d& instance_matrix = vol->get_instance_transformation().get_matrix(); glsafe(::glPushMatrix()); glsafe(::glTranslated(0.0, 0.0, m_c->selection_info()->get_sla_shift())); glsafe(::glMultMatrixd(instance_matrix.data())); std::array render_color; const sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; size_t cache_size = drain_holes.size(); for (size_t i = 0; i < cache_size; ++i) { const sla::DrainHole& drain_hole = drain_holes[i]; const bool& point_selected = m_selected[i]; if (is_mesh_point_clipped(drain_hole.pos.cast())) continue; // First decide about the color of the point. if (picking) { std::array color = picking_color_component(i); render_color = color; } else { if (size_t(m_hover_id) == i) { render_color = {0.f, 1.f, 1.f, 1.f}; } else if (m_c->hollowed_mesh() && i < m_c->hollowed_mesh()->get_drainholes().size() && m_c->hollowed_mesh()->get_drainholes()[i].failed) { render_color = {1.f, 0.f, 0.f, .5f}; } else { // neigher hover nor picking render_color[0] = point_selected ? 1.0f : 1.f; render_color[1] = point_selected ? 0.3f : 1.f; render_color[2] = point_selected ? 0.3f : 1.f; render_color[3] = 0.5f; } } const_cast(&m_vbo_cylinder)->set_color(-1, render_color); // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. glsafe(::glPushMatrix()); glsafe(::glTranslatef(drain_hole.pos(0), drain_hole.pos(1), drain_hole.pos(2))); glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data())); if (vol->is_left_handed()) glFrontFace(GL_CW); // Matrices set, we can render the point mark now. Eigen::Quaterniond q; q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * (-drain_hole.normal).cast()); Eigen::AngleAxisd aa(q); glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2))); glsafe(::glPushMatrix()); glsafe(::glTranslated(0., 0., -drain_hole.height)); glsafe(::glScaled(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength)); m_vbo_cylinder.render(); glsafe(::glPopMatrix()); if (vol->is_left_handed()) glFrontFace(GL_CCW); glsafe(::glPopMatrix()); } glsafe(::glPopMatrix()); } bool GLGizmoHollow::is_mesh_point_clipped(const Vec3d& point) const { if (m_c->object_clipper()->get_position() == 0.) return false; auto sel_info = m_c->selection_info(); int active_inst = m_c->selection_info()->get_active_instance(); const ModelInstance* mi = sel_info->model_object()->instances[active_inst]; const Transform3d& trafo = mi->get_transformation().get_matrix(); Vec3d transformed_point = trafo * point; transformed_point(2) += sel_info->get_sla_shift(); return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point); } // Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal // Return false if no intersection was found, true otherwise. bool GLGizmoHollow::unproject_on_mesh(const Vec2d& mouse_pos, std::pair& pos_and_normal) { if (! m_c->raycaster()->raycaster()) return false; const Camera& camera = wxGetApp().plater()->get_camera(); const Selection& selection = m_parent.get_selection(); const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); Geometry::Transformation trafo = volume->get_instance_transformation(); trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift())); double clp_dist = m_c->object_clipper()->get_position(); const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane(); // The raycaster query Vec3f hit; Vec3f normal; if (m_c->raycaster()->raycaster()->unproject_on_mesh( mouse_pos, trafo.get_matrix(), camera, hit, normal, clp_dist != 0. ? clp : nullptr)) { if (m_c->hollowed_mesh() && m_c->hollowed_mesh()->get_hollowed_mesh()) { // in this case the raycaster sees the hollowed and drilled mesh. // if the point lies on the surface created by the hole, we want // to ignore it. for (const sla::DrainHole& hole : m_holes_in_drilled_mesh) { sla::DrainHole outer(hole); outer.radius *= 1.001f; outer.height *= 1.001f; if (outer.is_inside(hit)) return false; } } // Return both the point and the facet normal. pos_and_normal = std::make_pair(hit, normal); return true; } else return false; } // Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. // The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is // aware that the event was reacted to and stops trying to make different sense of it. If the gizmo // concludes that the event was not intended for it, it should return false. bool GLGizmoHollow::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) { ModelObject* mo = m_c->selection_info()->model_object(); int active_inst = m_c->selection_info()->get_active_instance(); // left down with shift - show the selection rectangle: if (action == SLAGizmoEventType::LeftDown && (shift_down || alt_down || control_down)) { if (m_hover_id == -1) { if (shift_down || alt_down) { m_selection_rectangle.start_dragging(mouse_position, shift_down ? GLSelectionRectangle::Select : GLSelectionRectangle::Deselect); } } else { if (m_selected[m_hover_id]) unselect_point(m_hover_id); else { if (!alt_down) select_point(m_hover_id); } } return true; } // left down without selection rectangle - place point on the mesh: if (action == SLAGizmoEventType::LeftDown && !m_selection_rectangle.is_dragging() && !shift_down) { // If any point is in hover state, this should initiate its move - return control back to GLCanvas: if (m_hover_id != -1) return false; // If there is some selection, don't add new point and deselect everything instead. if (m_selection_empty) { std::pair pos_and_normal; if (unproject_on_mesh(mouse_position, pos_and_normal)) { // we got an intersection Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Add drainage hole"))); mo->sla_drain_holes.emplace_back(pos_and_normal.first, -pos_and_normal.second, m_new_hole_radius, m_new_hole_height); m_selected.push_back(false); assert(m_selected.size() == mo->sla_drain_holes.size()); m_parent.set_as_dirty(); m_wait_for_up_event = true; } else return false; } else select_point(NoPoints); return true; } // left up with selection rectangle - select points inside the rectangle: if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp || action == SLAGizmoEventType::AltUp) && m_selection_rectangle.is_dragging()) { // Is this a selection or deselection rectangle? GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state(); // First collect positions of all the points in world coordinates. Geometry::Transformation trafo = mo->instances[active_inst]->get_transformation(); trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift())); std::vector points; for (unsigned int i=0; isla_drain_holes.size(); ++i) points.push_back(trafo.get_matrix() * mo->sla_drain_holes[i].pos.cast()); // Now ask the rectangle which of the points are inside. std::vector points_inside; std::vector points_idxs = m_selection_rectangle.stop_dragging(m_parent, points); for (size_t idx : points_idxs) points_inside.push_back(points[idx].cast()); // Only select/deselect points that are actually visible for (size_t idx : m_c->raycaster()->raycaster()->get_unobscured_idxs( trafo, wxGetApp().plater()->get_camera(), points_inside, m_c->object_clipper()->get_clipping_plane())) { if (rectangle_status == GLSelectionRectangle::Deselect) unselect_point(points_idxs[idx]); else select_point(points_idxs[idx]); } return true; } // left up with no selection rectangle if (action == SLAGizmoEventType::LeftUp) { if (m_wait_for_up_event) { m_wait_for_up_event = false; return true; } } // dragging the selection rectangle: if (action == SLAGizmoEventType::Dragging) { if (m_wait_for_up_event) return true; // point has been placed and the button not released yet // this prevents GLCanvas from starting scene rotation if (m_selection_rectangle.is_dragging()) { m_selection_rectangle.dragging(mouse_position); return true; } return false; } if (action == SLAGizmoEventType::Delete) { // delete key pressed delete_selected_points(); return true; } if (action == SLAGizmoEventType::RightDown) { if (m_hover_id != -1) { select_point(NoPoints); select_point(m_hover_id); delete_selected_points(); return true; } return false; } if (action == SLAGizmoEventType::SelectAll) { select_point(AllPoints); return true; } if (action == SLAGizmoEventType::MouseWheelUp && control_down) { double pos = m_c->object_clipper()->get_position(); pos = std::min(1., pos + 0.01); m_c->object_clipper()->set_position(pos, true); return true; } if (action == SLAGizmoEventType::MouseWheelDown && control_down) { double pos = m_c->object_clipper()->get_position(); pos = std::max(0., pos - 0.01); m_c->object_clipper()->set_position(pos, true); return true; } if (action == SLAGizmoEventType::ResetClippingPlane) { m_c->object_clipper()->set_position(-1., false); return true; } return false; } void GLGizmoHollow::delete_selected_points() { Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Delete drainage hole"))); sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; for (unsigned int idx=0; idxselection_info()->model_object()->sla_drain_holes; if (m_hover_id != -1) { std::pair pos_and_normal; if (! unproject_on_mesh(data.mouse_pos.cast(), pos_and_normal)) return; drain_holes[m_hover_id].pos = pos_and_normal.first; drain_holes[m_hover_id].normal = -pos_and_normal.second; } } void GLGizmoHollow::hollow_mesh(bool postpone_error_messages) { wxGetApp().CallAfter([this, postpone_error_messages]() { wxGetApp().plater()->reslice_SLA_hollowing( *m_c->selection_info()->model_object(), postpone_error_messages); }); } std::vector> GLGizmoHollow::get_config_options(const std::vector& keys) const { std::vector> out; const ModelObject* mo = m_c->selection_info()->model_object(); if (! mo) return out; const DynamicPrintConfig& object_cfg = mo->config.get(); const DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; std::unique_ptr default_cfg = nullptr; for (const std::string& key : keys) { if (object_cfg.has(key)) out.emplace_back(object_cfg.option(key), &object_cfg.def()->options.at(key)); // at() needed for const map else if (print_cfg.has(key)) out.emplace_back(print_cfg.option(key), &print_cfg.def()->options.at(key)); else { // we must get it from defaults if (default_cfg == nullptr) default_cfg.reset(DynamicPrintConfig::new_from_defaults_keys(keys)); out.emplace_back(default_cfg->option(key), &default_cfg->def()->options.at(key)); } } return out; } void GLGizmoHollow::on_render_input_window(float x, float y, float bottom_limit) { ModelObject* mo = m_c->selection_info()->model_object(); if (! mo) return; bool first_run = true; // This is a hack to redraw the button when all points are removed, // so it is not delayed until the background process finishes. ConfigOptionMode current_mode = wxGetApp().get_mode(); std::vector opts_keys = {"hollowing_min_thickness", "hollowing_quality", "hollowing_closing_distance"}; auto opts = get_config_options(opts_keys); auto* offset_cfg = static_cast(opts[0].first); float offset = offset_cfg->value; double offset_min = opts[0].second->min; double offset_max = opts[0].second->max; auto* quality_cfg = static_cast(opts[1].first); float quality = quality_cfg->value; double quality_min = opts[1].second->min; double quality_max = opts[1].second->max; ConfigOptionMode quality_mode = opts[1].second->mode; auto* closing_d_cfg = static_cast(opts[2].first); float closing_d = closing_d_cfg->value; double closing_d_min = opts[2].second->min; double closing_d_max = opts[2].second->max; ConfigOptionMode closing_d_mode = opts[2].second->mode; m_desc["offset"] = _(opts[0].second->label) + ":"; m_desc["quality"] = _(opts[1].second->label) + ":"; m_desc["closing_distance"] = _(opts[2].second->label) + ":"; RENDER_AGAIN: const float approx_height = m_imgui->scaled(20.0f); y = std::min(y, bottom_limit - approx_height); m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: const float settings_sliders_left = std::max({m_imgui->calc_text_size(m_desc.at("offset")).x, m_imgui->calc_text_size(m_desc.at("quality")).x, m_imgui->calc_text_size(m_desc.at("closing_distance")).x, m_imgui->calc_text_size(m_desc.at("hole_diameter")).x, m_imgui->calc_text_size(m_desc.at("hole_depth")).x}) + m_imgui->scaled(1.f); const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); const float diameter_slider_left = settings_sliders_left; //m_imgui->calc_text_size(m_desc.at("hole_diameter")).x + m_imgui->scaled(1.f); const float minimal_slider_width = m_imgui->scaled(4.f); float window_width = minimal_slider_width + std::max({settings_sliders_left, clipping_slider_left, diameter_slider_left}); window_width = std::max(window_width, m_imgui->calc_text_size(m_desc.at("preview")).x); if (m_imgui->button(m_desc["preview"])) hollow_mesh(); bool config_changed = false; ImGui::Separator(); { auto opts = get_config_options({"hollowing_enable"}); m_enable_hollowing = static_cast(opts[0].first)->value; if (m_imgui->checkbox(m_desc["enable"], m_enable_hollowing)) { mo->config.set("hollowing_enable", m_enable_hollowing); wxGetApp().obj_list()->update_and_show_object_settings_item(); config_changed = true; } } m_imgui->disabled_begin(! m_enable_hollowing); float max_tooltip_width = ImGui::GetFontSize() * 20.0f; m_imgui->text(m_desc.at("offset")); ImGui::SameLine(settings_sliders_left); ImGui::PushItemWidth(window_width - settings_sliders_left); m_imgui->slider_float(" ", &offset, offset_min, offset_max, "%.1f mm"); if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::PushTextWrapPos(max_tooltip_width); ImGui::TextUnformatted((_utf8(opts[0].second->tooltip)).c_str()); ImGui::PopTextWrapPos(); ImGui::EndTooltip(); } bool slider_clicked = ImGui::IsItemClicked(); // someone clicked the slider bool slider_edited = ImGui::IsItemEdited(); // someone is dragging the slider bool slider_released = ImGui::IsItemDeactivatedAfterEdit(); // someone has just released the slider if (current_mode >= quality_mode) { m_imgui->text(m_desc.at("quality")); ImGui::SameLine(settings_sliders_left); m_imgui->slider_float(" ", &quality, quality_min, quality_max, "%.1f"); if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::PushTextWrapPos(max_tooltip_width); ImGui::TextUnformatted((_utf8(opts[1].second->tooltip)).c_str()); ImGui::PopTextWrapPos(); ImGui::EndTooltip(); } slider_clicked |= ImGui::IsItemClicked(); slider_edited |= ImGui::IsItemEdited(); slider_released |= ImGui::IsItemDeactivatedAfterEdit(); } if (current_mode >= closing_d_mode) { m_imgui->text(m_desc.at("closing_distance")); ImGui::SameLine(settings_sliders_left); m_imgui->slider_float(" ", &closing_d, closing_d_min, closing_d_max, "%.1f mm"); if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::PushTextWrapPos(max_tooltip_width); ImGui::TextUnformatted((_utf8(opts[2].second->tooltip)).c_str()); ImGui::PopTextWrapPos(); ImGui::EndTooltip(); } slider_clicked |= ImGui::IsItemClicked(); slider_edited |= ImGui::IsItemEdited(); slider_released |= ImGui::IsItemDeactivatedAfterEdit(); } if (slider_clicked) { m_offset_stash = offset; m_quality_stash = quality; m_closing_d_stash = closing_d; } if (slider_edited || slider_released) { if (slider_released) { mo->config.set("hollowing_min_thickness", m_offset_stash); mo->config.set("hollowing_quality", m_quality_stash); mo->config.set("hollowing_closing_distance", m_closing_d_stash); Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Hollowing parameter change"))); } mo->config.set("hollowing_min_thickness", offset); mo->config.set("hollowing_quality", quality); mo->config.set("hollowing_closing_distance", closing_d); if (slider_released) { wxGetApp().obj_list()->update_and_show_object_settings_item(); config_changed = true; } } m_imgui->disabled_end(); bool force_refresh = false; bool remove_selected = false; bool remove_all = false; ImGui::Separator(); float diameter_upper_cap = 60.; if (m_new_hole_radius * 2.f > diameter_upper_cap) m_new_hole_radius = diameter_upper_cap / 2.f; m_imgui->text(m_desc.at("hole_diameter")); ImGui::SameLine(diameter_slider_left); ImGui::PushItemWidth(window_width - diameter_slider_left); float diam = 2.f * m_new_hole_radius; m_imgui->slider_float("", &diam, 1.f, 15.f, "%.1f mm", 1.f, false); // Let's clamp the value (which could have been entered by keyboard) to a larger range // than the slider. This allows entering off-scale values and still protects against //complete non-sense. diam = std::clamp(diam, 0.1f, diameter_upper_cap); m_new_hole_radius = diam / 2.f; bool clicked = ImGui::IsItemClicked(); bool edited = ImGui::IsItemEdited(); bool deactivated = ImGui::IsItemDeactivatedAfterEdit(); m_imgui->text(m_desc["hole_depth"]); ImGui::SameLine(diameter_slider_left); m_imgui->slider_float(" ", &m_new_hole_height, 0.f, 10.f, "%.1f mm", 1.f, false); // Same as above: m_new_hole_height = std::clamp(m_new_hole_height, 0.f, 100.f); clicked |= ImGui::IsItemClicked(); edited |= ImGui::IsItemEdited(); deactivated |= ImGui::IsItemDeactivatedAfterEdit(); // Following is a nasty way to: // - save the initial value of the slider before one starts messing with it // - keep updating the head radius during sliding so it is continuosly refreshed in 3D scene // - take correct undo/redo snapshot after the user is done with moving the slider if (! m_selection_empty) { if (clicked) { m_holes_stash = mo->sla_drain_holes; } if (edited) { for (size_t idx=0; idxsla_drain_holes[idx].radius = m_new_hole_radius; mo->sla_drain_holes[idx].height = m_new_hole_height; } } if (deactivated) { // momentarily restore the old value to take snapshot sla::DrainHoles new_holes = mo->sla_drain_holes; mo->sla_drain_holes = m_holes_stash; float backup_rad = m_new_hole_radius; float backup_hei = m_new_hole_height; for (size_t i=0; isla_drain_holes = new_holes; } } m_imgui->disabled_begin(m_selection_empty); remove_selected = m_imgui->button(m_desc.at("remove_selected")); m_imgui->disabled_end(); m_imgui->disabled_begin(mo->sla_drain_holes.empty()); remove_all = m_imgui->button(m_desc.at("remove_all")); m_imgui->disabled_end(); // Following is rendered in both editing and non-editing mode: // m_imgui->text(""); ImGui::Separator(); if (m_c->object_clipper()->get_position() == 0.f) m_imgui->text(m_desc.at("clipping_of_view")); else { if (m_imgui->button(m_desc.at("reset_direction"))) { wxGetApp().CallAfter([this](){ m_c->object_clipper()->set_position(-1., false); }); } } ImGui::SameLine(clipping_slider_left); ImGui::PushItemWidth(window_width - clipping_slider_left); float clp_dist = m_c->object_clipper()->get_position(); if (m_imgui->slider_float(" ", &clp_dist, 0.f, 1.f, "%.2f")) m_c->object_clipper()->set_position(clp_dist, true); // make sure supports are shown/hidden as appropriate bool show_sups = m_c->instances_hider()->are_supports_shown(); if (m_imgui->checkbox(m_desc["show_supports"], show_sups)) { m_c->instances_hider()->show_supports(show_sups); force_refresh = true; } m_imgui->end(); if (remove_selected || remove_all) { force_refresh = false; m_parent.set_as_dirty(); if (remove_all) { select_point(AllPoints); delete_selected_points(); } if (remove_selected) delete_selected_points(); if (first_run) { first_run = false; goto RENDER_AGAIN; } } if (force_refresh) m_parent.set_as_dirty(); if (config_changed) m_parent.post_event(SimpleEvent(EVT_GLCANVAS_FORCE_UPDATE)); } bool GLGizmoHollow::on_is_activable() const { const Selection& selection = m_parent.get_selection(); if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA || !selection.is_from_single_instance()) return false; // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside. const Selection::IndicesList& list = selection.get_volume_idxs(); for (const auto& idx : list) if (selection.get_volume(idx)->is_outside && selection.get_volume(idx)->composite_id.volume_id >= 0) return false; return true; } bool GLGizmoHollow::on_is_selectable() const { return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA); } std::string GLGizmoHollow::on_get_name() const { return (_(L("Hollow and drill")) + " [H]").ToUTF8().data(); } CommonGizmosDataID GLGizmoHollow::on_get_requirements() const { return CommonGizmosDataID( int(CommonGizmosDataID::SelectionInfo) | int(CommonGizmosDataID::InstancesHider) | int(CommonGizmosDataID::Raycaster) | int(CommonGizmosDataID::HollowedMesh) | int(CommonGizmosDataID::ObjectClipper) | int(CommonGizmosDataID::SupportsClipper)); } void GLGizmoHollow::on_set_state() { if (m_state == m_old_state) return; if (m_state == Off && m_old_state != Off) // the gizmo was just turned Off m_parent.post_event(SimpleEvent(EVT_GLCANVAS_FORCE_UPDATE)); m_old_state = m_state; } void GLGizmoHollow::on_start_dragging() { if (m_hover_id != -1) { select_point(NoPoints); select_point(m_hover_id); m_hole_before_drag = m_c->selection_info()->model_object()->sla_drain_holes[m_hover_id].pos; } else m_hole_before_drag = Vec3f::Zero(); } void GLGizmoHollow::on_stop_dragging() { sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; if (m_hover_id != -1) { Vec3f backup = drain_holes[m_hover_id].pos; if (m_hole_before_drag != Vec3f::Zero() // some point was touched && backup != m_hole_before_drag) // and it was moved, not just selected { drain_holes[m_hover_id].pos = m_hole_before_drag; Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Move drainage hole"))); drain_holes[m_hover_id].pos = backup; } } m_hole_before_drag = Vec3f::Zero(); } void GLGizmoHollow::on_load(cereal::BinaryInputArchive& ar) { ar(m_new_hole_radius, m_new_hole_height, m_selected, m_selection_empty ); } void GLGizmoHollow::on_save(cereal::BinaryOutputArchive& ar) const { ar(m_new_hole_radius, m_new_hole_height, m_selected, m_selection_empty ); } void GLGizmoHollow::select_point(int i) { const sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; if (i == AllPoints || i == NoPoints) { m_selected.assign(m_selected.size(), i == AllPoints); m_selection_empty = (i == NoPoints); if (i == AllPoints) { m_new_hole_radius = drain_holes[0].radius; m_new_hole_height = drain_holes[0].height; } } else { while (size_t(i) >= m_selected.size()) m_selected.push_back(false); m_selected[i] = true; m_selection_empty = false; m_new_hole_radius = drain_holes[i].radius; m_new_hole_height = drain_holes[i].height; } } void GLGizmoHollow::unselect_point(int i) { m_selected[i] = false; m_selection_empty = true; for (const bool sel : m_selected) { if (sel) { m_selection_empty = false; break; } } } void GLGizmoHollow::reload_cache() { m_selected.clear(); m_selected.assign(m_c->selection_info()->model_object()->sla_drain_holes.size(), false); } void GLGizmoHollow::on_set_hover_id() { if (int(m_c->selection_info()->model_object()->sla_drain_holes.size()) <= m_hover_id) m_hover_id = -1; } } // namespace GUI } // namespace Slic3r