// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. #include "GLGizmoCut.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include #include #include #include #include #include #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GUI_ObjectManipulation.hpp" #include "slic3r/Utils/UndoRedo.hpp" #include "libslic3r/AppConfig.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/TriangleMeshSlicer.hpp" #include "imgui/imgui_internal.h" namespace Slic3r { namespace GUI { static const double Margin = 20.0; static const ColorRGBA GRABBER_COLOR = ColorRGBA::YELLOW(); const unsigned int AngleResolution = 64; const unsigned int ScaleStepsCount = 72; const float ScaleStepRad = 2.0f * float(PI) / ScaleStepsCount; const unsigned int ScaleLongEvery = 2; const float ScaleLongTooth = 0.1f; // in percent of radius const unsigned int SnapRegionsCount = 8; // Generates mesh for a line static GLModel::Geometry its_make_line(Vec3f beg_pos, Vec3f end_pos) { GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; init_data.reserve_vertices(2); init_data.reserve_indices(2); // vertices init_data.add_vertex(beg_pos); init_data.add_vertex(end_pos); // indices init_data.add_line(0, 1); return init_data; } //! -- #ysFIXME those functions bodies are ported from GizmoRotation // Generates mesh for a circle static void init_from_circle(GLModel& model, double radius) { GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::LineLoop, GLModel::Geometry::EVertexLayout::P3 }; init_data.reserve_vertices(ScaleStepsCount); init_data.reserve_indices(ScaleStepsCount); // vertices + indices for (unsigned int i = 0; i < ScaleStepsCount; ++i) { const float angle = float(i * ScaleStepRad); init_data.add_vertex(Vec3f(::cos(angle) * radius, ::sin(angle) * radius, 0.0f)); init_data.add_index(i); } model.init_from(std::move(init_data)); model.set_color(ColorRGBA::WHITE()); } // Generates mesh for a scale static void init_from_scale(GLModel& model, double radius) { const float out_radius_long = radius * (1.0f + ScaleLongTooth); const float out_radius_short = radius * (1.0f + 0.5f * ScaleLongTooth); GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; init_data.reserve_vertices(2 * ScaleStepsCount); init_data.reserve_indices(2 * ScaleStepsCount); // vertices + indices for (unsigned int i = 0; i < ScaleStepsCount; ++i) { const float angle = float(i * ScaleStepRad); const float cosa = ::cos(angle); const float sina = ::sin(angle); const float in_x = cosa * radius; const float in_y = sina * radius; const float out_x = (i % ScaleLongEvery == 0) ? cosa * out_radius_long : cosa * out_radius_short; const float out_y = (i % ScaleLongEvery == 0) ? sina * out_radius_long : sina * out_radius_short; // vertices init_data.add_vertex(Vec3f(in_x, in_y, 0.0f)); init_data.add_vertex(Vec3f(out_x, out_y, 0.0f)); // indices init_data.add_line(i * 2, i * 2 + 1); } model.init_from(std::move(init_data)); model.set_color(ColorRGBA::WHITE()); } // Generates mesh for a snap_radii static void init_from_snap_radii(GLModel& model, double radius) { const float step = 2.0f * float(PI) / float(SnapRegionsCount); const float in_radius = radius / 3.0f; const float out_radius = 2.0f * in_radius; GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; init_data.reserve_vertices(2 * ScaleStepsCount); init_data.reserve_indices(2 * ScaleStepsCount); // vertices + indices for (unsigned int i = 0; i < ScaleStepsCount; ++i) { const float angle = float(i * step); const float cosa = ::cos(angle); const float sina = ::sin(angle); const float in_x = cosa * in_radius; const float in_y = sina * in_radius; const float out_x = cosa * out_radius; const float out_y = sina * out_radius; // vertices init_data.add_vertex(Vec3f(in_x, in_y, 0.0f)); init_data.add_vertex(Vec3f(out_x, out_y, 0.0f)); // indices init_data.add_line(i * 2, i * 2 + 1); } model.init_from(std::move(init_data)); model.set_color(ColorRGBA::WHITE()); } // Generates mesh for a angle_arc static void init_from_angle_arc(GLModel& model, double angle, double radius) { model.reset(); const float step_angle = float(angle) / float(AngleResolution); const float ex_radius = radius; GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::LineStrip, GLModel::Geometry::EVertexLayout::P3 }; init_data.reserve_vertices(1 + AngleResolution); init_data.reserve_indices(1 + AngleResolution); // vertices + indices for (unsigned int i = 0; i <= AngleResolution; ++i) { const float angle = float(i) * step_angle; init_data.add_vertex(Vec3f(::cos(angle) * ex_radius, ::sin(angle) * ex_radius, 0.0f)); init_data.add_index(i); } model.init_from(std::move(init_data)); } //! -- GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) , m_connector_type (CutConnectorType::Plug) , m_connector_style (size_t(CutConnectorStyle::Prizm)) , m_connector_shape_id (size_t(CutConnectorShape::Circle)) , m_rotation_matrix(Slic3r::Matrix3d::Identity()) , m_connectors_group_id (3) { m_modes = { _u8L("Planar")//, _u8L("Grid") // , _u8L("Radial"), _u8L("Modular") }; m_connector_modes = { _u8L("Auto"), _u8L("Manual") }; m_connector_types = { _u8L("Plug"), _u8L("Dowel") }; m_connector_styles = { _u8L("Prizm"), _u8L("Frustum") // , _u8L("Claw") }; m_connector_shapes = { _u8L("Triangle"), _u8L("Square"), _u8L("Hexagon"), _u8L("Circle") // , _u8L("D-shape") }; m_axis_names = { "X", "Y", "Z" }; update_connector_shape(); } std::string GLGizmoCut3D::get_tooltip() const { std::string tooltip; if (m_hover_id == Z) { double koef = m_imperial_units ? ObjectManipulation::mm_to_in : 1.0; std::string unit_str = " " + (m_imperial_units ? _u8L("inch") : _u8L("mm")); const BoundingBoxf3 tbb = transformed_bounding_box(); if (tbb.max.z() >= 0.0) { double top = (tbb.min.z() <= 0.0 ? tbb.max.z() : tbb.size().z()) * koef; tooltip += format(top, 2) + " " + unit_str + " (" + _u8L("Top part") + ")"; if (tbb.min.z() <= 0.0) tooltip += "\n"; } if (tbb.min.z() <= 0.0) { double bottom = (tbb.max.z() <= 0.0 ? tbb.size().z() : (tbb.min.z() * (-1))) * koef; tooltip += format(bottom, 2) + " " + unit_str + " (" + _u8L("Bottom part") + ")"; } return tooltip; } if (tooltip.empty() && (m_hover_id == X || m_hover_id == Y)) { std::string axis = m_hover_id == X ? "X" : "Y"; // return axis + ": " + format(float(Geometry::rad2deg(Geometry::Transformation(m_rotation_m).get_rotation()[m_hover_id])), 1) + _u8L("°"); return axis + ": " + format(float(Geometry::rad2deg(m_angle)), 1) + _u8L("°"); } return tooltip; } bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) { Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY()); Vec2d mouse_pos = mouse_coord.cast(); if (mouse_event.ShiftDown() && mouse_event.LeftDown()) return gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown()); if (cut_line_processing()) { if (mouse_event.ShiftDown()) { if (mouse_event.Moving()|| mouse_event.Dragging()) return gizmo_event(SLAGizmoEventType::Moving, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown()); if (mouse_event.LeftUp()) return gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown()); } discard_cut_line_processing(); } else if (mouse_event.Moving()) return false; if (use_grabbers(mouse_event)) { if (m_hover_id >= m_connectors_group_id) { if (mouse_event.LeftDown()) { std::fill(m_selected.begin(), m_selected.end(), false); m_selected_count = 0; } if (mouse_event.LeftUp() && !mouse_event.ShiftDown()) gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown()); } return true; } static bool pending_right_up = false; if (mouse_event.LeftDown()) { bool grabber_contains_mouse = (get_hover_id() != -1); bool control_down = mouse_event.CmdDown(); if ((!control_down || grabber_contains_mouse) && gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) return true; } else if (mouse_event.Dragging()) { bool control_down = mouse_event.CmdDown(); if (m_parent.get_move_volume_id() != -1) { // don't allow dragging objects with the Sla gizmo on return true; } else if (!control_down && gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) { // the gizmo got the event and took some action, no need to do // anything more here m_parent.set_as_dirty(); return true; } else if (control_down && (mouse_event.LeftIsDown() || mouse_event.RightIsDown())) { // CTRL has been pressed while already dragging -> stop current action if (mouse_event.LeftIsDown()) gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true); else if (mouse_event.RightIsDown()) pending_right_up = false; } } else if (mouse_event.LeftUp() && !m_parent.is_mouse_dragging()) { // in case SLA/FDM gizmo is selected, we just pass the LeftUp event // and stop processing - neither object moving or selecting is // suppressed in that case gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown()); return true; } else if (mouse_event.RightDown()) { if (m_parent.get_selection().get_object_idx() != -1 && gizmo_event(SLAGizmoEventType::RightDown, mouse_pos, false, false, false)) { // we need to set the following right up as processed to avoid showing // the context menu if the user release the mouse over the object pending_right_up = true; // event was taken care of by the SlaSupports gizmo return true; } } else if (pending_right_up && mouse_event.RightUp()) { pending_right_up = false; return true; } return false; } void GLGizmoCut3D::shift_cut_z(double delta) { Vec3d new_cut_center = m_plane_center; new_cut_center[Z] += delta; set_center(new_cut_center); } void GLGizmoCut3D::rotate_vec3d_around_center(Vec3d& vec, const Vec3d& angles, const Vec3d& center) { if (m_rotations != angles) { m_rotation_matrix = Eigen::AngleAxisd(angles[Z], Vec3d::UnitZ()) * Eigen::AngleAxisd(angles[Y], Vec3d::UnitY()) * Eigen::AngleAxisd(angles[X], Vec3d::UnitX()); m_rotations = angles; } vec -= center; vec = m_rotation_matrix * vec; vec += center; } void GLGizmoCut3D::put_connetors_on_cut_plane(const Vec3d& cp_normal, double cp_offset) { ModelObject* mo = m_c->selection_info()->model_object(); if (CutConnectors& connectors = mo->cut_connectors; !connectors.empty()) { const float sla_shift = m_c->selection_info()->get_sla_shift(); const Vec3d& instance_offset = mo->instances[m_c->selection_info()->get_active_instance()]->get_offset(); for (auto& connector : connectors) { // convert connetor pos to the world coordinates Vec3d pos = connector.pos + instance_offset; pos[Z] += sla_shift; // scalar distance from point to plane along the normal double distance = -cp_normal.dot(pos) + cp_offset; // move connector connector.pos += distance * cp_normal; } } } void GLGizmoCut3D::update_clipper() { const Vec3d angles = Geometry::Transformation(m_rotation_m).get_rotation(); BoundingBoxf3 box = bounding_box(); double radius = box.radius(); Vec3d beg, end = beg = m_plane_center; beg[Z] = box.center().z() - radius;//box.min.z(); end[Z] = box.center().z() + radius;//box.max.z(); rotate_vec3d_around_center(beg, angles, m_plane_center); rotate_vec3d_around_center(end, angles, m_plane_center); double dist = (m_plane_center - beg).norm(); // calculate normal and offset for clipping plane Vec3d normal = end - beg; if (normal == Vec3d::Zero()) return; dist = std::clamp(dist, 0.0001, normal.norm()); normal.normalize(); const double offset = normal.dot(beg) + dist; m_c->object_clipper()->set_range_and_pos(normal, offset, dist); put_connetors_on_cut_plane(normal, offset); if (m_raycasters.empty()) on_register_raycasters_for_picking(); else update_raycasters_for_picking_transform(); } void GLGizmoCut3D::update_clipper_on_render() { update_clipper(); force_update_clipper_on_render = false; } void GLGizmoCut3D::set_center(const Vec3d& center) { set_center_pos(center); update_clipper(); } bool GLGizmoCut3D::render_combo(const std::string& label, const std::vector& lines, size_t& selection_idx) { ImGui::AlignTextToFramePadding(); m_imgui->text(label); ImGui::SameLine(m_label_width); ImGui::PushItemWidth(m_control_width); size_t selection_out = selection_idx; // It is necessary to use BeginGroup(). Otherwise, when using SameLine() is called, then other items will be drawn inside the combobox. ImGui::BeginGroup(); ImVec2 combo_pos = ImGui::GetCursorScreenPos(); if (ImGui::BeginCombo(("##"+label).c_str(), "")) { for (size_t line_idx = 0; line_idx < lines.size(); ++line_idx) { ImGui::PushID(int(line_idx)); if (ImGui::Selectable("", line_idx == selection_idx)) selection_out = line_idx; ImGui::SameLine(); ImGui::Text("%s", lines[line_idx].c_str()); ImGui::PopID(); } ImGui::EndCombo(); } ImVec2 backup_pos = ImGui::GetCursorScreenPos(); ImGuiStyle& style = ImGui::GetStyle(); ImGui::SetCursorScreenPos(ImVec2(combo_pos.x + style.FramePadding.x, combo_pos.y + style.FramePadding.y)); ImGui::Text("%s", lines[selection_out].c_str()); ImGui::SetCursorScreenPos(backup_pos); ImGui::EndGroup(); bool is_changed = selection_idx != selection_out; selection_idx = selection_out; if (is_changed) update_connector_shape(); return is_changed; } bool GLGizmoCut3D::render_double_input(const std::string& label, double& value_in) { ImGui::AlignTextToFramePadding(); m_imgui->text(label); ImGui::SameLine(m_label_width); ImGui::PushItemWidth(m_control_width); double value = value_in; if (m_imperial_units) value *= ObjectManipulation::mm_to_in; double old_val = value; ImGui::InputDouble(("##" + label).c_str(), &value, 0.0f, 0.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal); ImGui::SameLine(); m_imgui->text(m_imperial_units ? _L("in") : _L("mm")); value_in = value * (m_imperial_units ? ObjectManipulation::in_to_mm : 1.0); return old_val != value; } bool GLGizmoCut3D::render_slider_double_input(const std::string& label, double& value_in, int& tolerance_in) { ImGui::AlignTextToFramePadding(); m_imgui->text(label); ImGui::SameLine(m_label_width); ImGui::PushItemWidth(m_control_width * 0.85f); float value = (float)value_in; if (m_imperial_units) value *= ObjectManipulation::mm_to_in; float old_val = value; const BoundingBoxf3 bbox = bounding_box(); float mean_size = float((bbox.size().x() + bbox.size().y() + bbox.size().z()) / 9.0); float min_size = 1.f; if (m_imperial_units) { mean_size *= ObjectManipulation::mm_to_in; min_size *= ObjectManipulation::mm_to_in; } std::string format = m_imperial_units ? "%.4f " + _u8L("in") : "%.2f " + _u8L("mm"); m_imgui->slider_float(("##" + label).c_str(), &value, min_size, mean_size, format.c_str()); value_in = (double)(value * (m_imperial_units ? ObjectManipulation::in_to_mm : 1.0)); ImGui::SameLine(m_label_width + m_control_width + 3); ImGui::PushItemWidth(m_control_width * 0.3f); float old_tolerance, tolerance = old_tolerance = (float)tolerance_in; m_imgui->slider_float(("##tolerance_" + label).c_str(), &tolerance, 1.f, 20.f, "%.f %%", 1.f, true, _L("Tolerance")); tolerance_in = (int)tolerance; return old_val != value || old_tolerance != tolerance; } void GLGizmoCut3D::render_move_center_input(int axis) { ImGui::AlignTextToFramePadding(); m_imgui->text(m_axis_names[axis]+":"); ImGui::SameLine(); ImGui::PushItemWidth(0.3*m_control_width); Vec3d move = m_plane_center; double in_val, value = in_val = move[axis]; if (m_imperial_units) value *= ObjectManipulation::mm_to_in; ImGui::InputDouble(("##move_" + m_axis_names[axis]).c_str(), &value, 0.0f, 0.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal); ImGui::SameLine(); double val = value * (m_imperial_units ? ObjectManipulation::in_to_mm : 1.0); if (in_val != val) { move[axis] = val; set_center(move); } } bool GLGizmoCut3D::render_connect_type_radio_button(CutConnectorType type) { ImGui::SameLine(type == CutConnectorType::Plug ? m_label_width : 2*m_label_width); ImGui::PushItemWidth(m_control_width); if (m_imgui->radio_button(m_connector_types[size_t(type)], m_connector_type == type)) { m_connector_type = type; update_connector_shape(); return true; } return false; } void GLGizmoCut3D::render_connect_mode_radio_button(CutConnectorMode mode) { ImGui::SameLine(mode == CutConnectorMode::Auto ? m_label_width : 2*m_label_width); ImGui::PushItemWidth(m_control_width); if (m_imgui->radio_button(m_connector_modes[int(mode)], m_connector_mode == mode)) m_connector_mode = mode; } bool GLGizmoCut3D::render_revert_button(const std::string& label_id) { const ImGuiStyle& style = ImGui::GetStyle(); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, { 1, style.ItemSpacing.y }); ImGui::SameLine(m_label_width); ImGui::PushStyleColor(ImGuiCol_Button, { 0.25f, 0.25f, 0.25f, 0.0f }); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, { 0.4f, 0.4f, 0.4f, 1.0f }); ImGui::PushStyleColor(ImGuiCol_ButtonActive, { 0.4f, 0.4f, 0.4f, 1.0f }); std::string label; label += ImGui::RevertButton; bool revert = ImGui::Button((label + "##" + label_id).c_str()); ImGui::PopStyleColor(3); if (ImGui::IsItemHovered()) m_imgui->tooltip(into_u8(_L("Revert")).c_str(), ImGui::GetFontSize() * 20.0f); ImGui::PopStyleVar(); ImGui::SameLine(); return revert; } void GLGizmoCut3D::render_cut_plane() { if (cut_line_processing()) return; GLShaderProgram* shader = wxGetApp().get_shader("flat"); if (shader == nullptr) return; glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glDisable(GL_CULL_FACE)); glsafe(::glEnable(GL_BLEND)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); shader->start_using(); const Camera& camera = wxGetApp().plater()->get_camera(); const Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::assemble_transform( m_plane_center, Geometry::Transformation(m_rotation_m).get_rotation(), Vec3d::Ones(), Vec3d::Ones() ); shader->set_uniform("view_model_matrix", view_model_matrix); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); if (!m_plane.is_initialized()) { GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; // init_data.color = { 0.8f, 0.8f, 0.8f, 0.5f }; init_data.reserve_vertices(4); init_data.reserve_indices(6); // vertices float radius = (float)bounding_box().radius(); init_data.add_vertex(Vec3f(-radius, -radius, 0.0)); init_data.add_vertex(Vec3f( radius, -radius, 0.0)); init_data.add_vertex(Vec3f( radius, radius, 0.0)); init_data.add_vertex(Vec3f(-radius, radius, 0.0)); // indices init_data.add_triangle(0, 1, 2); init_data.add_triangle(2, 3, 0); m_plane.init_from(std::move(init_data)); } if (can_perform_cut()) m_plane.set_color({ 0.8f, 0.8f, 0.8f, 0.5f }); else m_plane.set_color({ 1.0f, 0.8f, 0.8f, 0.5f }); m_plane.render(); glsafe(::glEnable(GL_CULL_FACE)); glsafe(::glDisable(GL_BLEND)); shader->stop_using(); } void GLGizmoCut3D::render_cut_center_graber() { glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); if (!shader) return; ColorRGBA color = m_hover_id == Z ? complementary(GRABBER_COLOR) : GRABBER_COLOR; const Camera& camera = wxGetApp().plater()->get_camera(); const Grabber& graber = m_grabbers.front(); const BoundingBoxf3 box = bounding_box(); const double mean_size = float((box.size().x() + box.size().y() + box.size().z()) / 3.0); double size = m_dragging && m_hover_id == Z ? double(graber.get_dragging_half_size(mean_size)) : double(graber.get_half_size(mean_size)); Vec3d cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); Vec3d offset = 1.25 * size * Vec3d::UnitZ(); shader->start_using(); shader->set_uniform("emission_factor", 0.1f); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); const Transform3d view_matrix = camera.get_view_matrix() * Geometry::translation_transform(m_plane_center) * m_rotation_m; auto render = [shader, this](GLModel& model, const ColorRGBA& color, Transform3d view_model_matrix) { shader->set_uniform("view_model_matrix", view_model_matrix); shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); model.set_color(color); model.render(); }; auto render_grabber_connection = [shader, camera, view_matrix, this](const ColorRGBA& color) { shader->stop_using(); GLShaderProgram* line_shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); if (!line_shader) return; line_shader->start_using(); line_shader->set_uniform("emission_factor", 0.1f); line_shader->set_uniform("projection_matrix", camera.get_projection_matrix()); const Transform3d trafo = view_matrix * Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d(1.0, 1.0, m_grabber_connection_len)); line_shader->set_uniform("view_model_matrix", trafo); line_shader->set_uniform("normal_matrix", (Matrix3d)trafo.matrix().block(0, 0, 3, 3).inverse().transpose()); line_shader->set_uniform("width", 0.2f); m_grabber_connection.set_color(color); m_grabber_connection.render(); line_shader->stop_using(); shader->start_using(); }; auto render_rotation_snapping = [shader, camera, this](Axis axis, const ColorRGBA& color) { Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::translation_transform(m_plane_center) * m_start_dragging_m; if (axis == X) view_model_matrix = view_model_matrix * Geometry::rotation_transform(0.5 * PI * Vec3d::UnitY()) * Geometry::rotation_transform(-PI * Vec3d::UnitZ()); else view_model_matrix = view_model_matrix * Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitZ()) * Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitY()); shader->stop_using(); GLShaderProgram* line_shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); if (!line_shader) return; line_shader->start_using(); line_shader->set_uniform("emission_factor", 0.1f); line_shader->set_uniform("projection_matrix", camera.get_projection_matrix()); line_shader->set_uniform("view_model_matrix", view_model_matrix); line_shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); line_shader->set_uniform("width", 0.25f); m_circle.render(); m_scale.render(); m_snap_radii.render(); m_reference_radius.render(); if (m_dragging) { line_shader->set_uniform("width", 1.5f); m_angle_arc.set_color(color); m_angle_arc.render(); } line_shader->stop_using(); shader->start_using(); }; // render Z grabber if ((!m_dragging && m_hover_id < 0)) render_grabber_connection(color); render(m_sphere.model, color, view_matrix * Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), size * Vec3d::Ones())); if (!m_dragging && m_hover_id < 0 || m_hover_id == Z) { const BoundingBoxf3 tbb = transformed_bounding_box(); if (tbb.min.z() <= 0.0) render(m_cone.model, color, view_matrix * Geometry::assemble_transform(-offset, PI * Vec3d::UnitX(), cone_scale)); if (tbb.max.z() >= 0.0) render(m_cone.model, color, view_matrix * Geometry::assemble_transform(offset, Vec3d::Zero(), cone_scale)); } // render top sphere for X/Y grabbers if ((!m_dragging && m_hover_id < 0) || m_hover_id == X || m_hover_id == Y) { size = m_dragging ? double(graber.get_dragging_half_size(mean_size)) : double(graber.get_half_size(mean_size)); color = m_hover_id == Y ? complementary(ColorRGBA::GREEN()) : m_hover_id == X ? complementary(ColorRGBA::RED()) : ColorRGBA::GRAY(); render(m_sphere.model, color, view_matrix * Geometry::assemble_transform(m_grabber_connection_len * Vec3d::UnitZ(), Vec3d::Zero(), size * Vec3d::Ones())); } // render X grabber if ((!m_dragging && m_hover_id < 0) || m_hover_id == X) { size = m_dragging && m_hover_id == X ? double(graber.get_dragging_half_size(mean_size)) : double(graber.get_half_size(mean_size)); cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); color = m_hover_id == X ? complementary(ColorRGBA::RED()) : ColorRGBA::RED(); if (m_hover_id == X) { render_grabber_connection(color); render_rotation_snapping(X, color); } offset = Vec3d(0.0, 1.25 * size, m_grabber_connection_len); render(m_cone.model, color, view_matrix * Geometry::assemble_transform(offset, -0.5 * PI * Vec3d::UnitX(), cone_scale)); offset = Vec3d(0.0, -1.25 * size, m_grabber_connection_len); render(m_cone.model, color, view_matrix * Geometry::assemble_transform(offset, 0.5 * PI * Vec3d::UnitX(), cone_scale)); } // render Y grabber if ((!m_dragging && m_hover_id < 0) || m_hover_id == Y) { size = m_dragging && m_hover_id == Y ? double(graber.get_dragging_half_size(mean_size)) : double(graber.get_half_size(mean_size)); cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); color = m_hover_id == Y ? complementary(ColorRGBA::GREEN()) : ColorRGBA::GREEN(); if (m_hover_id == Y) { render_grabber_connection(color); render_rotation_snapping(Y, color); } offset = Vec3d(1.25 * size, 0.0, m_grabber_connection_len); render(m_cone.model, color, view_matrix * Geometry::assemble_transform(offset, 0.5 * PI * Vec3d::UnitY(), cone_scale)); offset = Vec3d(-1.25 * size, 0.0, m_grabber_connection_len); render(m_cone.model, color, view_matrix * Geometry::assemble_transform(offset, -0.5 * PI * Vec3d::UnitY(), cone_scale)); } shader->stop_using(); } void GLGizmoCut3D::render_cut_line() { if (!cut_line_processing() || m_line_end == Vec3d::Zero()) return; glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); if (shader != nullptr) { shader->start_using(); const Camera& camera = wxGetApp().plater()->get_camera(); shader->set_uniform("view_model_matrix", camera.get_view_matrix()); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); shader->set_uniform("width", 0.25f); m_cut_line.reset(); m_cut_line.init_from(its_make_line((Vec3f)m_line_beg.cast(), (Vec3f)m_line_end.cast())); m_cut_line.set_color(GRABBER_COLOR); m_cut_line.render(); shader->stop_using(); } } bool GLGizmoCut3D::on_init() { m_grabbers.emplace_back(); m_shortcut_key = WXK_CONTROL_C; // initiate info shortcuts const wxString ctrl = GUI::shortkey_ctrl_prefix(); const wxString alt = GUI::shortkey_alt_prefix(); m_shortcuts.push_back(std::make_pair(_L("Left click"), _L("Add connector"))); m_shortcuts.push_back(std::make_pair(_L("Right click"), _L("Remove connector"))); m_shortcuts.push_back(std::make_pair(_L("Drag"), _L("Move connector"))); m_shortcuts.push_back(std::make_pair(ctrl + _L("Left click"), _L("Add connector to selection"))); m_shortcuts.push_back(std::make_pair(alt + _L("Left click"), _L("Remove connector from selection"))); m_shortcuts.push_back(std::make_pair(ctrl + "A", _L("Select all connectors"))); return true; } void GLGizmoCut3D::on_load(cereal::BinaryInputArchive& ar) { ar( m_keep_upper, m_keep_lower, m_rotate_lower, m_rotate_upper, m_hide_cut_plane, m_mode, m_connectors_editing,//m_selected, // m_connector_depth_ratio, m_connector_size, m_connector_mode, m_connector_type, m_connector_style, m_connector_shape_id, m_ar_plane_center, m_ar_rotations); set_center_pos(m_ar_plane_center, true); m_rotation_m = Geometry::rotation_transform(m_ar_rotations); force_update_clipper_on_render = true; m_parent.request_extra_frame(); } void GLGizmoCut3D::on_save(cereal::BinaryOutputArchive& ar) const { ar( m_keep_upper, m_keep_lower, m_rotate_lower, m_rotate_upper, m_hide_cut_plane, m_mode, m_connectors_editing,//m_selected, // m_connector_depth_ratio, m_connector_size, m_connector_mode, m_connector_type, m_connector_style, m_connector_shape_id, m_ar_plane_center, m_ar_rotations); } std::string GLGizmoCut3D::on_get_name() const { return _u8L("Cut"); } void GLGizmoCut3D::on_set_state() { if (m_state == On) { update_bb(); // initiate archived values m_ar_plane_center = m_plane_center; m_ar_rotations = Geometry::Transformation(m_rotation_m).get_rotation(); m_parent.request_extra_frame(); } else m_c->object_clipper()->release(); force_update_clipper_on_render = m_state == On; } void GLGizmoCut3D::on_register_raycasters_for_picking() { assert(m_raycasters.empty()); set_volumes_picking_state(false); init_picking_models(); if (m_connectors_editing) { if (CommonGizmosDataObjects::SelectionInfo* si = m_c->selection_info()) { const CutConnectors& connectors = si->model_object()->cut_connectors; for (size_t i = 0; i < connectors.size(); ++i) m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, i + m_connectors_group_id, *(m_shapes[connectors[i].attribs]).mesh_raycaster, Transform3d::Identity())); } } else { m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, X, *m_cone.mesh_raycaster, Transform3d::Identity())); m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, X, *m_cone.mesh_raycaster, Transform3d::Identity())); m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, Y, *m_cone.mesh_raycaster, Transform3d::Identity())); m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, Y, *m_cone.mesh_raycaster, Transform3d::Identity())); m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, Z, *m_sphere.mesh_raycaster, Transform3d::Identity())); m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, Z, *m_cone.mesh_raycaster, Transform3d::Identity())); m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, Z, *m_cone.mesh_raycaster, Transform3d::Identity())); } update_raycasters_for_picking_transform(); } void GLGizmoCut3D::on_unregister_raycasters_for_picking() { m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo); m_raycasters.clear(); set_volumes_picking_state(true); } void GLGizmoCut3D::update_raycasters_for_picking() { on_unregister_raycasters_for_picking(); on_register_raycasters_for_picking(); } void GLGizmoCut3D::set_volumes_picking_state(bool state) { std::vector>* raycasters = m_parent.get_raycasters_for_picking(SceneRaycaster::EType::Volume); if (raycasters != nullptr) { const Selection& selection = m_parent.get_selection(); const Selection::IndicesList ids = selection.get_volume_idxs(); for (unsigned int id : ids) { const GLVolume* v = selection.get_volume(id); auto it = std::find_if(raycasters->begin(), raycasters->end(), [v](std::shared_ptr item) { return item->get_raycaster() == v->mesh_raycaster.get(); }); if (it != raycasters->end()) (*it)->set_active(state); } } } void GLGizmoCut3D::update_raycasters_for_picking_transform() { if (m_connectors_editing) { CommonGizmosDataObjects::SelectionInfo* si = m_c->selection_info(); if (!si) return; const ModelObject* mo = si->model_object(); const CutConnectors& connectors = mo->cut_connectors; if (connectors.empty()) return; auto inst_id = m_c->selection_info()->get_active_instance(); if (inst_id < 0) return; const Vec3d& instance_offset = mo->instances[inst_id]->get_offset(); const float sla_shift = m_c->selection_info()->get_sla_shift(); const ClippingPlane* cp = m_c->object_clipper()->get_clipping_plane(); const Vec3d& normal = cp && cp->is_active() ? cp->get_normal() : m_clp_normal; for (size_t i = 0; i < connectors.size(); ++i) { const CutConnector& connector = connectors[i]; double height = connector.height; // recalculate connector position to world position Vec3d pos = connector.pos + instance_offset; if (connector.attribs.type == CutConnectorType::Dowel && connector.attribs.style == CutConnectorStyle::Prizm) { pos -= height * normal; height *= 2; } pos[Z] += sla_shift; m_raycasters[i]->set_transform(Geometry::assemble_transform( pos, Geometry::Transformation(m_rotation_m).get_rotation(), Vec3d(connector.radius, connector.radius, height) )); } } else { const Transform3d trafo = Geometry::translation_transform(m_plane_center) * m_rotation_m; const BoundingBoxf3 box = bounding_box(); const double mean_size = float((box.size().x() + box.size().y() + box.size().z()) / 6.0); double size = double(m_grabbers.front().get_half_size(mean_size)); Vec3d scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); Vec3d offset = Vec3d(0.0, 1.25 * size, m_grabber_connection_len); m_raycasters[0]->set_transform(trafo * Geometry::assemble_transform(offset, -0.5 * PI * Vec3d::UnitX(), scale)); offset = Vec3d(0.0, -1.25 * size, m_grabber_connection_len); m_raycasters[1]->set_transform(trafo * Geometry::assemble_transform(offset, 0.5 * PI * Vec3d::UnitX(), scale)); offset = Vec3d(1.25 * size, 0.0, m_grabber_connection_len); m_raycasters[2]->set_transform(trafo * Geometry::assemble_transform(offset, 0.5 * PI * Vec3d::UnitY(), scale)); offset = Vec3d(-1.25 * size, 0.0, m_grabber_connection_len); m_raycasters[3]->set_transform(trafo * Geometry::assemble_transform(offset, -0.5 * PI * Vec3d::UnitY(), scale)); offset = 1.25 * size * Vec3d::UnitZ(); m_raycasters[4]->set_transform(trafo * Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), size * Vec3d::Ones())); m_raycasters[5]->set_transform(trafo * Geometry::assemble_transform(-offset, PI * Vec3d::UnitX(), scale)); m_raycasters[6]->set_transform(trafo * Geometry::assemble_transform(offset, Vec3d::Zero(), scale)); } } void GLGizmoCut3D::on_set_hover_id() { } bool GLGizmoCut3D::on_is_activable() const { // This is assumed in GLCanvas3D::do_rotate, do not change this // without updating that function too. return m_parent.get_selection().is_single_full_instance(); } Vec3d GLGizmoCut3D::mouse_position_in_local_plane(Axis axis, const Linef3& mouse_ray) const { double half_pi = 0.5 * PI; Transform3d m = Transform3d::Identity(); switch (axis) { case X: { m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitZ())); m.rotate(Eigen::AngleAxisd(-half_pi, Vec3d::UnitY())); break; } case Y: { m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitY())); m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitZ())); break; } default: case Z: { // no rotation applied break; } } m = m * m_start_dragging_m.inverse(); m.translate(-m_plane_center); return transform(mouse_ray, m).intersect_plane(0.0); } void GLGizmoCut3D::on_dragging(const UpdateData& data) { if (m_hover_id < 0) return; CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; if (m_hover_id == Z) { Vec3d starting_box_center = m_plane_center - Vec3d::UnitZ();// some Margin rotate_vec3d_around_center(starting_box_center, Geometry::Transformation(m_rotation_m).get_rotation(), m_plane_center); const Vec3d& starting_drag_position = m_plane_center; double projection = 0.0; Vec3d starting_vec = starting_drag_position - starting_box_center; if (starting_vec.norm() != 0.0) { Vec3d mouse_dir = data.mouse_ray.unit_vector(); // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form // in our case plane normal and ray direction are the same (orthogonal view) // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal Vec3d inters = data.mouse_ray.a + (starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; // vector from the starting position to the found intersection Vec3d inters_vec = inters - starting_drag_position; starting_vec.normalize(); // finds projection of the vector along the staring direction projection = inters_vec.dot(starting_vec); } if (wxGetKeyState(WXK_SHIFT)) projection = m_snap_step * (double)std::round(projection / m_snap_step); Vec3d shift = starting_vec * projection; // move cut plane center set_center(m_plane_center + shift); } else if (m_hover_id == X || m_hover_id == Y) { Vec3d rotation = Vec3d::Zero(); const Vec2d mouse_pos = to_2d(mouse_position_in_local_plane((Axis)m_hover_id, data.mouse_ray)); const Vec2d orig_dir = Vec2d::UnitX(); const Vec2d new_dir = mouse_pos.normalized(); const double two_pi = 2.0 * PI; double theta = ::acos(std::clamp(new_dir.dot(orig_dir), -1.0, 1.0)); if (cross2(orig_dir, new_dir) < 0.0) theta = two_pi - theta; const double len = mouse_pos.norm(); // snap to coarse snap region if (m_snap_coarse_in_radius <= len && len <= m_snap_coarse_out_radius) { const double step = two_pi / double(SnapRegionsCount); theta = step * std::round(theta / step); } else { // snap to fine snap region (scale) if (m_snap_fine_in_radius <= len && len <= m_snap_fine_out_radius) { const double step = two_pi / double(ScaleStepsCount); theta = step * std::round(theta / step); } } if (theta == two_pi) theta = 0.0; if (m_hover_id == X) theta += 0.5 * PI; rotation[m_hover_id] = theta; m_rotation_m = m_start_dragging_m * Geometry::rotation_transform(rotation); m_angle = theta; while (m_angle > two_pi) m_angle -= two_pi; if (m_angle < 0.0) m_angle += two_pi; update_clipper(); } else if (m_hover_id >= m_connectors_group_id && m_connector_mode == CutConnectorMode::Manual) { std::pair pos_and_normal; Vec3d pos_world; if (!unproject_on_cut_plane(data.mouse_pos.cast(), pos_and_normal, pos_world)) return; connectors[m_hover_id - m_connectors_group_id].pos = pos_and_normal.first; update_raycasters_for_picking_transform(); } } void GLGizmoCut3D::on_start_dragging() { m_angle = 0.0; if (m_hover_id >= m_connectors_group_id && m_connector_mode == CutConnectorMode::Manual) Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move connector"), UndoRedo::SnapshotType::GizmoAction); if (m_hover_id == X || m_hover_id == Y) m_start_dragging_m = m_rotation_m; } void GLGizmoCut3D::on_stop_dragging() { if (m_hover_id == X || m_hover_id == Y) { m_angle_arc.reset(); m_angle = 0.0; Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Rotate cut plane"), UndoRedo::SnapshotType::GizmoAction); m_ar_rotations = Geometry::Transformation(m_rotation_m).get_rotation(); m_start_dragging_m = m_rotation_m; } else if (m_hover_id == Z) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move cut plane"), UndoRedo::SnapshotType::GizmoAction); m_ar_plane_center = m_plane_center; } } void GLGizmoCut3D::set_center_pos(const Vec3d& center_pos, bool force/* = false*/) { if (force || transformed_bounding_box(true).contains(center_pos)) { m_plane_center = center_pos; m_center_offset = m_plane_center - m_bb_center; } } BoundingBoxf3 GLGizmoCut3D::bounding_box() const { BoundingBoxf3 ret; const Selection& selection = m_parent.get_selection(); const Selection::IndicesList& idxs = selection.get_volume_idxs(); for (unsigned int i : idxs) { const GLVolume* volume = selection.get_volume(i); // respect just to the solid parts for FFF and ignore pad and supports for SLA if (!volume->is_modifier && !volume->is_sla_pad() && !volume->is_sla_support()) ret.merge(volume->transformed_convex_hull_bounding_box()); } return ret; } BoundingBoxf3 GLGizmoCut3D::transformed_bounding_box(bool revert_move /*= false*/) const { // #ysFIXME !!! BoundingBoxf3 ret; const ModelObject* mo = m_c->selection_info()->model_object(); if (!mo) return ret; const int instance_idx = m_c->selection_info()->get_active_instance(); if (instance_idx < 0) return ret; const ModelInstance* mi = mo->instances[instance_idx]; const Vec3d& instance_offset = mi->get_offset(); Vec3d cut_center_offset = m_plane_center - instance_offset; cut_center_offset[Z] -= m_c->selection_info()->get_sla_shift(); const auto move = Geometry::assemble_transform(-cut_center_offset); const auto move2 = Geometry::assemble_transform(m_plane_center); const auto cut_matrix = (revert_move ? move2 : Transform3d::Identity()) * m_rotation_m.inverse() * move; const Selection& selection = m_parent.get_selection(); const Selection::IndicesList& idxs = selection.get_volume_idxs(); for (unsigned int i : idxs) { const GLVolume* volume = selection.get_volume(i); // respect just to the solid parts for FFF and ignore pad and supports for SLA if (!volume->is_modifier && !volume->is_sla_pad() && !volume->is_sla_support()) { const auto instance_matrix = Geometry::assemble_transform( Vec3d::Zero(), // don't apply offset volume->get_instance_rotation().cwiseProduct(Vec3d(1.0, 1.0, 1.0)), volume->get_instance_scaling_factor(), volume->get_instance_mirror() ); auto volume_travo = instance_matrix * volume->get_volume_transformation().get_matrix(); auto volume_offset = Geometry::Transformation(volume_travo).get_offset(); ret.merge(volume->transformed_convex_hull_bounding_box(cut_matrix * volume_travo)); } } return ret; } bool GLGizmoCut3D::update_bb() { const BoundingBoxf3 box = bounding_box(); if (m_max_pos != box.max && m_min_pos != box.min) { m_max_pos = box.max; m_min_pos = box.min; m_bb_center = box.center(); if (box.contains(m_center_offset)) set_center_pos(m_bb_center + m_center_offset, true); else set_center_pos(m_bb_center, true); m_radius = box.radius(); m_grabber_connection_len = 0.75 * m_radius;// std::min(0.75 * m_radius, 35.0); m_grabber_radius = m_grabber_connection_len * 0.85; m_snap_coarse_in_radius = m_grabber_radius / 3.0f; m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius; m_snap_fine_in_radius = m_grabber_connection_len * 0.85; m_snap_fine_out_radius = m_grabber_connection_len * 1.15f; m_plane.reset(); m_cone.reset(); m_sphere.reset(); m_grabber_connection.reset(); m_circle.reset(); m_scale.reset(); m_snap_radii.reset(); m_reference_radius.reset(); on_unregister_raycasters_for_picking(); if (CommonGizmosDataObjects::SelectionInfo* selection = m_c->selection_info()) { m_selected.clear(); m_selected.resize(selection->model_object()->cut_connectors.size(), false); } return true; } return false; } void GLGizmoCut3D::init_picking_models() { if (!m_cone.model.is_initialized()) { indexed_triangle_set its = its_make_cone(1.0, 1.0, PI / 12.0); m_cone.model.init_from(its); m_cone.mesh_raycaster = std::make_unique(std::make_shared(std::move(its))); } if (!m_sphere.model.is_initialized()) { indexed_triangle_set its = its_make_sphere(1.0, PI / 12.0); m_sphere.model.init_from(its); m_sphere.mesh_raycaster = std::make_unique(std::make_shared(std::move(its))); } } void GLGizmoCut3D::on_render() { if (update_bb() || force_update_clipper_on_render) { update_clipper_on_render(); m_c->object_clipper()->set_behavior(m_connectors_editing, m_connectors_editing, 0.4f); } init_picking_models(); if (!m_grabber_connection.is_initialized()) m_grabber_connection.init_from(its_make_line(Vec3f::Zero(), Vec3f::UnitZ())); if (!m_circle.is_initialized()) init_from_circle(m_circle, m_grabber_radius); if (!m_scale.is_initialized()) init_from_scale(m_scale, m_grabber_radius); if (!m_snap_radii.is_initialized()) init_from_snap_radii(m_snap_radii, m_grabber_radius); if (!m_reference_radius.is_initialized()) { m_reference_radius.init_from(its_make_line(Vec3f::Zero(), m_grabber_connection_len * Vec3f::UnitX())); m_reference_radius.set_color(ColorRGBA::WHITE()); } if (!m_angle_arc.is_initialized() || m_angle != 0.0) init_from_angle_arc(m_angle_arc, m_angle, m_grabber_connection_len); render_connectors(); if (! m_connectors_editing) ::glDisable(GL_DEPTH_TEST); m_c->object_clipper()->render_cut(); if (! m_connectors_editing) ::glEnable(GL_DEPTH_TEST); if (!m_hide_cut_plane && !m_connectors_editing) { render_cut_plane(); render_cut_center_graber(); } render_cut_line(); } void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) { static float last_y = 0.0f; static float last_h = 0.0f; m_imgui->begin(_L("Cut"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); m_imperial_units = wxGetApp().app_config->get("use_inches") == "1"; m_label_width = m_imgui->get_style_scaling() * 100.0f; m_control_width = m_imgui->get_style_scaling() * 150.0f; // adjust window position to avoid overlap the view toolbar const float win_h = ImGui::GetWindowHeight(); y = std::min(y, bottom_limit - win_h); ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always); if (last_h != win_h || last_y != y) { // ask canvas for another frame to render the window in the correct position m_imgui->set_requires_extra_frame(); if (last_h != win_h) last_h = win_h; if (last_y != y) last_y = y; } // render_combo(_u8L("Mode"), m_modes, m_mode); CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; bool cut_clicked = false; bool revert_move{ false }; bool revert_rotation{ false }; bool fff_printer = wxGetApp().plater()->printer_technology() == ptFFF; if (! m_connectors_editing) { if (m_mode == size_t(CutMode::cutPlanar)) { ImGui::AlignTextToFramePadding(); m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Hold SHIFT key and connect some two points of an object to cut by line")); ImGui::Separator(); //////// double koef = m_imperial_units ? ObjectManipulation::mm_to_in : 1.0; std::string unit_str = m_imperial_units ? _u8L("inch") : _u8L("mm"); const BoundingBoxf3 tbb = transformed_bounding_box(); double top = (tbb.min.z() <= 0.0 ? tbb.max.z() : tbb.size().z()) * koef; double bottom = (tbb.max.z() <= 0.0 ? tbb.size().z() : (tbb.min.z() * (-1))) * koef; Vec3d tbb_sz = tbb.size(); wxString size = "X: " + double_to_string(tbb_sz.x() * koef, 2) + unit_str + ", Y: " + double_to_string(tbb_sz.y() * koef, 2) + unit_str + ", Z: " + double_to_string(tbb_sz.z() * koef, 2) + unit_str; ImGui::AlignTextToFramePadding(); m_imgui->text(_L("Build size")); ImGui::SameLine(m_label_width); m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, size); //static float v = 0.; // TODO: connect to cutting plane position m_imgui->text(_L("Cut position: ")); render_move_center_input(Z); //m_imgui->input_double(unit_str, v); //v = std::clamp(v, 0.f, float(bottom+top)); if (m_imgui->button("Reset cutting plane")) { set_center(bounding_box().center()); m_rotation_m = Transform3d::Identity(); m_angle_arc.reset(); update_clipper(); } } if (m_mode == size_t(CutMode::cutPlanar)) { ImGui::Separator(); ImGui::AlignTextToFramePadding(); m_imgui->text(_L("After cut") + ": "); bool keep = true; ImGui::SameLine(m_label_width); m_imgui->text(_L("Upper part")); ImGui::SameLine(2 * m_label_width); m_imgui->disabled_begin(!connectors.empty()); m_imgui->checkbox(_L("Keep") + "##upper", connectors.empty() ? m_keep_upper : keep); m_imgui->disabled_end(); ImGui::SameLine(3 * m_label_width); m_imgui->disabled_begin(!m_keep_upper); m_imgui->disabled_begin(is_approx(Geometry::Transformation(m_rotation_m).get_rotation().x(), 0.) && is_approx(Geometry::Transformation(m_rotation_m).get_rotation().y(), 0.)); if (m_imgui->checkbox(_L("Place on cut") + "##upper", m_place_on_cut_upper)) m_rotate_upper = false; m_imgui->disabled_end(); ImGui::SameLine(); if (m_imgui->checkbox(_L("Flip") + "##upper", m_rotate_upper)) m_place_on_cut_upper = false; m_imgui->disabled_end(); m_imgui->text(""); ImGui::SameLine(m_label_width); m_imgui->text(_L("Lower part")); ImGui::SameLine(2 * m_label_width); m_imgui->disabled_begin(!connectors.empty()); m_imgui->checkbox(_L("Keep") + "##lower", connectors.empty() ? m_keep_lower : keep); m_imgui->disabled_end(); ImGui::SameLine(3 * m_label_width); m_imgui->disabled_begin(!m_keep_lower); if (m_imgui->checkbox(_L("Place on cut") + "##lower", m_place_on_cut_lower)) m_rotate_lower = false; ImGui::SameLine(); if (m_imgui->checkbox(_L("Flip") + "##lower", m_rotate_lower)) m_place_on_cut_lower = false; m_imgui->disabled_end(); } if (fff_printer) { ImGui::Separator(); m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower); if (m_imgui->button(_L("Add/Edit connectors"))) { m_connectors_editing = true; update_raycasters_for_picking(); } m_imgui->disabled_end(); } } else { // connectors mode if (m_imgui->button("? " + (m_show_shortcuts ? wxString(ImGui::CollapseBtn) : wxString(ImGui::ExpandBtn)))) m_show_shortcuts = !m_show_shortcuts; if (m_show_shortcuts) for (const auto& shortcut : m_shortcuts ){ m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, shortcut.first); ImGui::SameLine(m_label_width); m_imgui->text(shortcut.second); } m_imgui->disabled_begin(!m_keep_lower || !m_keep_upper); // Connectors section ImGui::Separator(); ImGui::AlignTextToFramePadding(); m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Connectors")); m_imgui->disabled_begin(connectors.empty()); ImGui::SameLine(m_label_width); if (m_imgui->button(" " + _L("Reset") + " ##connectors")) reset_connectors(); m_imgui->disabled_end(); /* m_imgui->text(_L("Mode")); render_connect_mode_radio_button(CutConnectorMode::Auto); render_connect_mode_radio_button(CutConnectorMode::Manual); */ if (m_selected_count == 1) for (size_t idx = 0; idx < m_selected.size(); idx++) if (m_selected[idx]) { auto& connector = connectors[idx]; m_connector_depth_ratio = connector.height; m_connector_depth_ratio_tolerance = 100 * connector.height_tolerance; m_connector_size = 2. * connector.radius; m_connector_size_tolerance = 100 * connector.radius_tolerance; m_connector_type = connector.attribs.type; m_connector_style = size_t(connector.attribs.style); m_connector_shape_id = size_t(connector.attribs.shape); break; } m_imgui->text(_L("Type")); bool type_changed = render_connect_type_radio_button(CutConnectorType::Plug); type_changed |= render_connect_type_radio_button(CutConnectorType::Dowel); if (type_changed) for (size_t idx = 0; idx < m_selected.size() ; idx++) if (m_selected[idx]) connectors[idx].attribs.type = CutConnectorType(m_connector_type); if (render_combo(_u8L("Style"), m_connector_styles, m_connector_style)) for (size_t idx = 0; idx < m_selected.size() ; idx++) if (m_selected[idx]) connectors[idx].attribs.style = CutConnectorStyle(m_connector_style) ; if (render_combo(_u8L("Shape"), m_connector_shapes, m_connector_shape_id)) for (size_t idx = 0; idx < m_selected.size() ; idx++) if (m_selected[idx]) connectors[idx].attribs.shape = CutConnectorShape(m_connector_shape_id); if (render_slider_double_input(_u8L("Depth ratio"), m_connector_depth_ratio, m_connector_depth_ratio_tolerance)) for (size_t idx = 0; idx < m_selected.size() ; idx++) if (m_selected[idx]) { auto& connector = connectors[idx]; connector.height = float(m_connector_depth_ratio); connector.height_tolerance = 0.01f * m_connector_depth_ratio_tolerance; } if (render_slider_double_input(_u8L("Size"), m_connector_size, m_connector_size_tolerance)) for (size_t idx = 0; idx < m_selected.size(); idx++) if (m_selected[idx]) { auto& connector = connectors[idx]; connector.radius = float(m_connector_size * 0.5); connector.radius_tolerance = 0.01f * m_connector_size_tolerance; } m_imgui->disabled_end(); if (m_imgui->button(_L("Confirm connectors"))) { m_clp_normal = m_c->object_clipper()->get_clipping_plane()->get_normal(); m_connectors_editing = false; update_raycasters_for_picking(); std::fill(m_selected.begin(), m_selected.end(), false); m_selected_count = 0; } m_parent.request_extra_frame(); } ImGui::Separator(); if (fff_printer) m_imgui->text(m_has_invalid_connector ? wxString(ImGui::WarningMarkerSmall) + _L("Invalid connectors detected.") : wxString()); if (!m_connectors_editing) { m_imgui->disabled_begin(!can_perform_cut()); cut_clicked = m_imgui->button(_L("Perform cut")); m_imgui->disabled_end(); } m_imgui->end(); //////// m_imgui->begin(wxString("DEBUG")); static bool hide_clipped = false; static bool fill_cut = false; static float contour_width = 0.4f; m_imgui->checkbox(_L("Hide cut plane and grabbers"), m_hide_cut_plane); if (m_imgui->checkbox("hide_clipped", hide_clipped) && !hide_clipped) m_clp_normal = m_c->object_clipper()->get_clipping_plane()->get_normal(); m_imgui->checkbox("fill_cut", fill_cut); m_imgui->slider_float("contour_width", &contour_width, 0.f, 3.f); m_c->object_clipper()->set_behavior(hide_clipped || m_connectors_editing, fill_cut || m_connectors_editing, contour_width); m_imgui->end(); //////// if (cut_clicked && (m_keep_upper || m_keep_lower)) perform_cut(m_parent.get_selection()); if (revert_move) set_center(bounding_box().center()); if (revert_rotation) { m_rotation_m = Transform3d::Identity(); m_angle_arc.reset(); update_clipper(); } } // get volume transformation regarding to the "border". Border is related from the size of connectors Transform3d GLGizmoCut3D::get_volume_transformation(const ModelVolume* volume) const { bool is_prizm_dowel = m_connector_type == CutConnectorType::Dowel && m_connector_style == size_t(CutConnectorStyle::Prizm); const Transform3d connector_trafo = Geometry::assemble_transform( is_prizm_dowel ? Vec3d(0.0, 0.0, -m_connector_depth_ratio) : Vec3d::Zero(), Geometry::Transformation(m_rotation_m).get_rotation(), Vec3d(0.5*m_connector_size, 0.5*m_connector_size, is_prizm_dowel ? 2 * m_connector_depth_ratio : m_connector_depth_ratio), Vec3d::Ones()); const Vec3d connector_bb = m_connector_mesh.transformed_bounding_box(connector_trafo).size(); const Vec3d bb = volume->mesh().bounding_box().size(); // calculate an unused border - part of the the volume, where we can't put connectors const Vec3d border_scale(connector_bb.x() / bb.x(), connector_bb.y() / bb.y(), connector_bb.z() / bb.z()); const Transform3d vol_matrix = volume->get_matrix(); const Vec3d vol_trans = vol_matrix.translation(); // offset of the volume will be changed after scaling, so calculate the needed offset and set it to a volume_trafo const Vec3d offset(vol_trans.x() * border_scale.x(), vol_trans.y() * border_scale.y(), vol_trans.z() * border_scale.z()); // scale and translate volume to suppress to put connectors too close to the border return Geometry::assemble_transform(offset, Vec3d::Zero(), Vec3d::Ones() - border_scale, Vec3d::Ones()) * vol_matrix; } void GLGizmoCut3D::render_connectors() { if (!m_connectors_editing) return; ::glEnable(GL_DEPTH_TEST); if (cut_line_processing() || m_connector_mode == CutConnectorMode::Auto || !m_c->selection_info()) return; const ModelObject* mo = m_c->selection_info()->model_object(); auto inst_id = m_c->selection_info()->get_active_instance(); if (inst_id < 0) return; const CutConnectors& connectors = mo->cut_connectors; if (connectors.size() != m_selected.size()) { // #ysFIXME m_selected.clear(); m_selected.resize(connectors.size(), false); } GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); if (shader == nullptr) return; shader->start_using(); ScopeGuard guard([shader]() { shader->stop_using(); }); const Camera& camera = wxGetApp().plater()->get_camera(); ColorRGBA render_color; const ModelInstance* mi = mo->instances[inst_id]; const Vec3d& instance_offset = mi->get_offset(); const float sla_shift = m_c->selection_info()->get_sla_shift(); const ClippingPlane* cp = m_c->object_clipper()->get_clipping_plane(); const Vec3d& normal = cp && cp->is_active() ? cp->get_normal() : m_clp_normal; const Transform3d instance_trafo = Geometry::assemble_transform(Vec3d(0.0, 0.0, sla_shift)) * mi->get_transformation().get_matrix(); m_has_invalid_connector = false; for (size_t i = 0; i < connectors.size(); ++i) { const CutConnector& connector = connectors[i]; double height = connector.height; // recalculate connector position to world position Vec3d pos = connector.pos + instance_offset; if (connector.attribs.type == CutConnectorType::Dowel && connector.attribs.style == CutConnectorStyle::Prizm) { pos -= height * normal; height *= 2; } pos[Z] += sla_shift; // First decide about the color of the point. if (size_t(m_hover_id- m_connectors_group_id) == i) render_color = ColorRGBA::CYAN(); else if (m_selected[i]) render_color = ColorRGBA::DARK_GRAY(); else // neither hover nor picking render_color = m_connectors_editing ? ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f) : ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f); // ! #ysFIXME rework get_volume_transformation if (0) { // else { // neither hover nor picking int mesh_id = -1; for (const ModelVolume* mv : mo->volumes) { ++mesh_id; if (!mv->is_model_part()) continue; const Transform3d volume_trafo = get_volume_transformation(mv); if (m_c->raycaster()->raycasters()[mesh_id]->is_valid_intersection(pos, -normal, instance_trafo * volume_trafo)) { render_color = m_connectors_editing ? ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f) : /*ColorRGBA(0.5f, 0.5f, 0.5f, 1.f)*/ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f); break; } render_color = ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f); m_has_invalid_connector = true; } } m_shapes[connector.attribs].model.set_color(render_color); const Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::assemble_transform( pos, Geometry::Transformation(m_rotation_m).get_rotation(), Vec3d(connector.radius, connector.radius, height) ); shader->set_uniform("view_model_matrix", view_model_matrix); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); m_shapes[connector.attribs].model.render(); } } bool GLGizmoCut3D::can_perform_cut() const { if (m_has_invalid_connector || (!m_keep_upper && !m_keep_lower) || m_connectors_editing) return false; const BoundingBoxf3 tbb = transformed_bounding_box(true); return tbb.contains(m_plane_center); } void GLGizmoCut3D::perform_cut(const Selection& selection) { const int instance_idx = selection.get_instance_idx(); const int object_idx = selection.get_object_idx(); wxCHECK_RET(instance_idx >= 0 && object_idx >= 0, "GLGizmoCut: Invalid object selection"); // m_cut_z is the distance from the bed. Subtract possible SLA elevation. const GLVolume* first_glvolume = selection.get_first_volume(); const double object_cut_z = m_plane_center.z() - first_glvolume->get_sla_shift_z(); const Vec3d& instance_offset = wxGetApp().plater()->model().objects[object_idx]->instances[instance_idx]->get_offset(); Vec3d cut_center_offset = m_plane_center - instance_offset; cut_center_offset[Z] -= first_glvolume->get_sla_shift_z(); Plater* plater = wxGetApp().plater(); bool create_dowels_as_separate_object = false; if (0.0 < object_cut_z && can_perform_cut()) { ModelObject* mo = plater->model().objects[object_idx]; if(!mo) return; Vec3d rotation = Geometry::Transformation(m_rotation_m).get_rotation(); const bool has_connectors = !mo->cut_connectors.empty(); { Plater::TakeSnapshot snapshot(plater, _L("Cut by Plane")); // update connectors pos as offset of its center before cut performing if (has_connectors && m_connector_mode == CutConnectorMode::Manual) { m_selected.clear(); for (CutConnector& connector : mo->cut_connectors) { connector.rotation = rotation; if (connector.attribs.type == CutConnectorType::Dowel) { if (connector.attribs.style == CutConnectorStyle::Prizm) connector.height *= 2; create_dowels_as_separate_object = true; } else { // culculate shift of the connector center regarding to the position on the cut plane Vec3d shifted_center = m_plane_center + Vec3d::UnitZ(); rotate_vec3d_around_center(shifted_center, rotation, m_plane_center); Vec3d norm = (shifted_center - m_plane_center).normalized(); connector.pos += norm * (0.5 * connector.height); } } mo->apply_cut_connectors(_u8L("Connector")); } } plater->cut(object_idx, instance_idx, cut_center_offset, rotation, only_if(has_connectors ? true : m_keep_upper, ModelObjectCutAttribute::KeepUpper) | only_if(has_connectors ? true : m_keep_lower, ModelObjectCutAttribute::KeepLower) | only_if(m_place_on_cut_upper, ModelObjectCutAttribute::PlaceOnCutUpper) | only_if(m_place_on_cut_lower, ModelObjectCutAttribute::PlaceOnCutLower) | only_if(m_rotate_upper, ModelObjectCutAttribute::FlipUpper) | only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower) | only_if(create_dowels_as_separate_object, ModelObjectCutAttribute::CreateDowels)); } else { // the object is SLA-elevated and the plane is under it. } } // 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 GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, std::pair& pos_and_normal, Vec3d& pos_world) { const float sla_shift = m_c->selection_info()->get_sla_shift(); const ModelObject* mo = m_c->selection_info()->model_object(); const ModelInstance* mi = mo->instances[m_c->selection_info()->get_active_instance()]; const Transform3d instance_trafo = sla_shift > 0.0 ? Geometry::assemble_transform(Vec3d(0.0, 0.0, sla_shift)) * mi->get_transformation().get_matrix() : mi->get_transformation().get_matrix(); const Camera& camera = wxGetApp().plater()->get_camera(); int mesh_id = -1; for (const ModelVolume* mv : mo->volumes) { ++mesh_id; if (!mv->is_model_part()) continue; Vec3f normal; Vec3f hit; bool clipping_plane_was_hit = false; // const Transform3d volume_trafo = get_volume_transformation(mv); const Transform3d volume_trafo = mv->get_transformation().get_matrix(); m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh(mouse_position, instance_trafo * volume_trafo, camera, hit, normal, m_c->object_clipper()->get_clipping_plane(true), nullptr, &clipping_plane_was_hit); if (clipping_plane_was_hit) { // recalculate hit to object's local position Vec3d hit_d = hit.cast(); hit_d -= mi->get_offset(); hit_d[Z] -= sla_shift; // Return both the point and the facet normal. pos_and_normal = std::make_pair(hit_d, normal.cast()); pos_world = hit.cast(); return true; } } return false; } void GLGizmoCut3D::reset_connectors() { m_c->selection_info()->model_object()->cut_connectors.clear(); update_model_object(); m_selected.clear(); } void GLGizmoCut3D::update_connector_shape() { CutConnectorAttributes attribs = { m_connector_type, CutConnectorStyle(m_connector_style), CutConnectorShape(m_connector_shape_id) }; if (m_shapes.find(attribs) == m_shapes.end()) { const indexed_triangle_set its = ModelObject::get_connector_mesh(attribs); m_shapes[attribs].model.init_from(its); m_shapes[attribs].mesh_raycaster = std::make_unique(std::make_shared(std::move(its)));; } const indexed_triangle_set its = ModelObject::get_connector_mesh(attribs); m_connector_mesh.clear(); m_connector_mesh = TriangleMesh(its); } void GLGizmoCut3D::update_model_object() { const ModelObjectPtrs& mos = wxGetApp().model().objects; ModelObject* mo = m_c->selection_info()->model_object(); wxGetApp().obj_list()->update_info_items(std::find(mos.begin(), mos.end(), mo) - mos.begin()); m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); update_raycasters_for_picking(); } bool GLGizmoCut3D::cut_line_processing() const { return m_line_beg != Vec3d::Zero(); } void GLGizmoCut3D::discard_cut_line_processing() { m_line_beg = m_line_end = Vec3d::Zero(); } bool GLGizmoCut3D::process_cut_line(SLAGizmoEventType action, const Vec2d& mouse_position) { const float sla_shift = m_c->selection_info()->get_sla_shift(); const ModelObject* mo = m_c->selection_info()->model_object(); const ModelInstance* mi = mo->instances[m_c->selection_info()->get_active_instance()]; Transform3d inst_trafo = sla_shift > 0.0 ? Geometry::assemble_transform(Vec3d(0.0, 0.0, sla_shift)) * mi->get_transformation().get_matrix() : mi->get_transformation().get_matrix(); const Camera& camera = wxGetApp().plater()->get_camera(); Vec3d pt; Vec3d dir; MeshRaycaster::line_from_mouse_pos(mouse_position, Transform3d::Identity(), camera, pt, dir); dir.normalize(); pt += dir; // Move the pt along dir so it is not clipped. if (action == SLAGizmoEventType::LeftDown && !cut_line_processing()) { m_line_beg = pt; m_line_end = pt; on_unregister_raycasters_for_picking(); return true; } if (cut_line_processing()) { m_line_end = pt; if (action == SLAGizmoEventType::LeftDown || action == SLAGizmoEventType::LeftUp) { Vec3d line_dir = m_line_end - m_line_beg; if (line_dir.norm() < 3.0) return true; Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Cut by line"), UndoRedo::SnapshotType::GizmoAction); Vec3d cross_dir = line_dir.cross(dir).normalized(); Eigen::Quaterniond q; Transform3d m = Transform3d::Identity(); m.matrix().block(0, 0, 3, 3) = q.setFromTwoVectors(Vec3d::UnitZ(), cross_dir).toRotationMatrix(); m_rotation_m = m; m_angle_arc.reset(); set_center(m_plane_center + cross_dir * (cross_dir.dot(pt - m_plane_center))); discard_cut_line_processing(); } else if (action == SLAGizmoEventType::Moving) this->set_dirty(); return true; } return false; } bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) { if (is_dragging() || m_connector_mode == CutConnectorMode::Auto || (!m_keep_upper || !m_keep_lower)) return false; if ( m_hover_id < 0 && shift_down && ! m_connectors_editing && (action == SLAGizmoEventType::LeftDown || action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::Moving) ) return process_cut_line(action, mouse_position); CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; if (action == SLAGizmoEventType::LeftDown && !shift_down) { // If there is no selection and no hovering, add new point if (m_hover_id == -1 && !control_down && !alt_down) { std::pair pos_and_normal; Vec3d pos_world; if (unproject_on_cut_plane(mouse_position.cast(), pos_and_normal, pos_world)) { const Vec3d& hit = pos_and_normal.first; if (m_connectors_editing) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add connector"), UndoRedo::SnapshotType::GizmoAction); connectors.emplace_back(hit, Geometry::Transformation(m_rotation_m).get_rotation(), float(m_connector_size * 0.5), float(m_connector_depth_ratio), float(0.01f * m_connector_size_tolerance), float(0.01f * m_connector_depth_ratio_tolerance), CutConnectorAttributes( CutConnectorType(m_connector_type), CutConnectorStyle(m_connector_style), CutConnectorShape(m_connector_shape_id))); std::fill(m_selected.begin(), m_selected.end(), false); m_selected.push_back(true); assert(m_selected.size() == connectors.size()); update_model_object(); m_parent.set_as_dirty(); } else { // Following would inform the clipper about the mouse click, so it can // toggle the respective contour as disabled. //m_c->object_clipper()->pass_mouse_click(pos_world); } return true; } return false; } return true; } else if (action == SLAGizmoEventType::LeftUp && !shift_down && m_connectors_editing) { if (m_hover_id >= m_connectors_group_id) { if (alt_down) { m_selected[m_hover_id - m_connectors_group_id] = false; --m_selected_count; } else { if (!control_down) { std::fill(m_selected.begin(), m_selected.end(), false); m_selected_count = 0; } m_selected[m_hover_id - m_connectors_group_id] = true; ++m_selected_count; } return true; } } else if (action == SLAGizmoEventType::RightDown && !shift_down && m_connectors_editing) { // If any point is in hover state, this should initiate its move - return control back to GLCanvas: if (m_hover_id < m_connectors_group_id) return false; Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Delete connector"), UndoRedo::SnapshotType::GizmoAction); size_t connector_id = m_hover_id - m_connectors_group_id; connectors.erase(connectors.begin() + connector_id); m_selected.erase(m_selected.begin() + connector_id); assert(m_selected.size() == connectors.size()); update_model_object(); m_parent.set_as_dirty(); return true; } else if (action == SLAGizmoEventType::SelectAll) { std::fill(m_selected.begin(), m_selected.end(), true); return true; } return false; } CommonGizmosDataID GLGizmoCut3D::on_get_requirements() const { return CommonGizmosDataID( int(CommonGizmosDataID::SelectionInfo) | int(CommonGizmosDataID::InstancesHider) | int(CommonGizmosDataID::ObjectClipper) | int(CommonGizmosDataID::Raycaster)); } } // namespace GUI } // namespace Slic3r