BambuStudio/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp

631 lines
21 KiB
C++

// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
#include "GLGizmoAdvancedCut.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include <GL/glew.h>
#include <wx/button.h>
#include <wx/checkbox.h>
#include <wx/stattext.h>
#include <wx/sizer.h>
#include <algorithm>
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "libslic3r/AppConfig.hpp"
#include <imgui/imgui_internal.h>
namespace Slic3r {
namespace GUI {
static inline void rotate_point_2d(double& x, double& y, const double c, const double s)
{
double xold = x;
double yold = y;
x = c * xold - s * yold;
y = s * xold + c * yold;
}
static void rotate_x_3d(std::array<Vec3d, 4>& verts, float radian_angle)
{
double c = cos(radian_angle);
double s = sin(radian_angle);
for (uint32_t i = 0; i < verts.size(); ++i)
rotate_point_2d(verts[i](1), verts[i](2), c, s);
}
static void rotate_y_3d(std::array<Vec3d, 4>& verts, float radian_angle)
{
double c = cos(radian_angle);
double s = sin(radian_angle);
for (uint32_t i = 0; i < verts.size(); ++i)
rotate_point_2d(verts[i](2), verts[i](0), c, s);
}
static void rotate_z_3d(std::array<Vec3d, 4>& verts, float radian_angle)
{
double c = cos(radian_angle);
double s = sin(radian_angle);
for (uint32_t i = 0; i < verts.size(); ++i)
rotate_point_2d(verts[i](0), verts[i](1), c, s);
}
const double GLGizmoAdvancedCut::Offset = 10.0;
const double GLGizmoAdvancedCut::Margin = 20.0;
const std::array<float, 4> GLGizmoAdvancedCut::GrabberColor = { 1.0, 1.0, 0.0, 1.0 };
const std::array<float, 4> GLGizmoAdvancedCut::GrabberHoverColor = { 0.7, 0.7, 0.0, 1.0};
GLGizmoAdvancedCut::GLGizmoAdvancedCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoRotate3D(parent, icon_filename, sprite_id, nullptr)
, m_movement(0.0)
, m_buffered_movement(0.0)
, m_last_active_id(0)
, m_keep_upper(true)
, m_keep_lower(true)
, m_rotate_lower(false)
, m_cut_to_parts(false)
, m_do_segment(false)
, m_segment_smoothing_alpha(0.5)
, m_segment_number(5)
{
for (int i = 0; i < 4; i++)
m_cut_plane_points[i] = { 0., 0., 0. };
set_group_id(m_gizmos.size());
m_rotation.setZero();
//m_current_base_rotation.setZero();
m_rotate_cmds.clear();
m_buffered_rotation.setZero();
}
std::string GLGizmoAdvancedCut::get_tooltip() const
{
return "";
}
void GLGizmoAdvancedCut::update_plane_points()
{
Vec3d plane_center = get_plane_center();
std::array<Vec3d, 4> plane_points_rot;
for (int i = 0; i < plane_points_rot.size(); i++) {
plane_points_rot[i] = m_cut_plane_points[i] - plane_center;
}
if (m_rotation(0) > EPSILON) {
rotate_x_3d(plane_points_rot, m_rotation(0));
m_rotate_cmds.emplace(m_rotate_cmds.begin(), m_rotation(0), X);
}
if (m_rotation(1) > EPSILON) {
rotate_y_3d(plane_points_rot, m_rotation(1));
m_rotate_cmds.emplace(m_rotate_cmds.begin(), m_rotation(1), Y);
}
if (m_rotation(2) > EPSILON) {
rotate_z_3d(plane_points_rot, m_rotation(2));
m_rotate_cmds.emplace(m_rotate_cmds.begin(), m_rotation(2), Z);
}
Vec3d plane_normal = calc_plane_normal(plane_points_rot);
if (m_movement == 0 && m_height_delta != 0)
m_movement = plane_normal(2) * m_height_delta;// plane_normal.dot(Vec3d(0, 0, m_height_delta))
for (int i = 0; i < plane_points_rot.size(); i++) {
m_cut_plane_points[i] = plane_points_rot[i] + plane_center + plane_normal * m_movement;
}
//m_current_base_rotation += m_rotation;
m_rotation.setZero();
m_movement = 0.0;
m_height_delta = 0;
}
std::array<Vec3d, 4> GLGizmoAdvancedCut::get_plane_points() const
{
return m_cut_plane_points;
}
std::array<Vec3d, 4> GLGizmoAdvancedCut::get_plane_points_world_coord() const
{
std::array<Vec3d, 4> plane_world_coord = m_cut_plane_points;
const Selection& selection = m_parent.get_selection();
const BoundingBoxf3& box = selection.get_bounding_box();
Vec3d object_offset = box.center();
for (Vec3d& point : plane_world_coord) {
point += object_offset;
}
return plane_world_coord;
}
void GLGizmoAdvancedCut::reset_cut_plane()
{
const Selection& selection = m_parent.get_selection();
const BoundingBoxf3& box = selection.get_bounding_box();
const float max_x = box.size()(0) / 2.0 + Margin;
const float min_x = -max_x;
const float max_y = box.size()(1) / 2.0 + Margin;
const float min_y = -max_y;
m_cut_plane_points[0] = { min_x, min_y, 0 };
m_cut_plane_points[1] = { max_x, min_y, 0 };
m_cut_plane_points[2] = { max_x, max_y, 0 };
m_cut_plane_points[3] = { min_x, max_y, 0 };
m_movement = 0.0;
m_height = box.size()[2] / 2.0;
m_height_delta = 0;
m_rotation.setZero();
//m_current_base_rotation.setZero();
m_rotate_cmds.clear();
m_buffered_movement = 0.0;
m_buffered_height = m_height;
m_buffered_rotation.setZero();
}
void GLGizmoAdvancedCut::reset_all()
{
reset_cut_plane();
m_keep_upper = true;
m_keep_lower = true;
m_cut_to_parts = false;
}
bool GLGizmoAdvancedCut::on_init()
{
if (!GLGizmoRotate3D::on_init())
return false;
m_shortcut_key = WXK_CONTROL_C;
return true;
}
std::string GLGizmoAdvancedCut::on_get_name() const
{
return (_(L("Cut"))).ToUTF8().data();
}
void GLGizmoAdvancedCut::on_set_state()
{
GLGizmoRotate3D::on_set_state();
// Reset m_cut_z on gizmo activation
if (get_state() == On) {
reset_cut_plane();
}
}
bool GLGizmoAdvancedCut::on_is_activable() const
{
const Selection& selection = m_parent.get_selection();
return selection.is_single_full_instance() && !selection.is_wipe_tower();
}
void GLGizmoAdvancedCut::on_start_dragging()
{
for (auto gizmo : m_gizmos) {
if (m_hover_id == gizmo.get_group_id()) {
gizmo.start_dragging();
return;
}
}
if (m_hover_id != get_group_id())
return;
const Selection& selection = m_parent.get_selection();
const BoundingBoxf3& box = selection.get_bounding_box();
m_start_movement = m_movement;
m_start_height = m_height;
m_drag_pos = m_move_grabber.center;
}
void GLGizmoAdvancedCut::on_update(const UpdateData& data)
{
GLGizmoRotate3D::on_update(data);
Vec3d rotation;
for (int i = 0; i < 3; i++)
{
rotation(i) = m_gizmos[i].get_angle();
if (rotation(i) < 0)
rotation(i) = 2*PI + rotation(i);
}
m_rotation = rotation;
//m_move_grabber.angles = m_current_base_rotation + m_rotation;
if (m_hover_id == get_group_id()) {
double move = calc_projection(data.mouse_ray);
set_movement(m_start_movement + move);
Vec3d plane_normal = get_plane_normal();
m_height = m_start_height + plane_normal(2) * move;
}
}
void GLGizmoAdvancedCut::on_render()
{
const Selection& selection = m_parent.get_selection();
const BoundingBoxf3& box = selection.get_bounding_box();
// box center is the coord of object in the world coordinate
Vec3d object_offset = box.center();
// plane points is in object coordinate
Vec3d plane_center = get_plane_center();
// rotate plane
std::array<Vec3d, 4> plane_points_rot;
{
for (int i = 0; i < plane_points_rot.size(); i++) {
plane_points_rot[i] = m_cut_plane_points[i] - plane_center;
}
if (m_rotation(0) > EPSILON)
rotate_x_3d(plane_points_rot, m_rotation(0));
if (m_rotation(1) > EPSILON)
rotate_y_3d(plane_points_rot, m_rotation(1));
if (m_rotation(2) > EPSILON)
rotate_z_3d(plane_points_rot, m_rotation(2));
for (int i = 0; i < plane_points_rot.size(); i++) {
plane_points_rot[i] += plane_center;
}
}
// move plane
Vec3d plane_normal_rot = calc_plane_normal(plane_points_rot);
for (int i = 0; i < plane_points_rot.size(); i++) {
plane_points_rot[i] += plane_normal_rot * m_movement;
}
// transfer from object coordindate to the world coordinate
for (Vec3d& point : plane_points_rot) {
point += object_offset;
}
// draw plane
glsafe(::glEnable(GL_DEPTH_TEST));
glsafe(::glDisable(GL_CULL_FACE));
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
::glBegin(GL_QUADS);
::glColor4f(0.8f, 0.8f, 0.8f, 0.5f);
for (const Vec3d& point : plane_points_rot) {
::glVertex3f(point(0), point(1), point(2));
}
glsafe(::glEnd());
glsafe(::glEnable(GL_CULL_FACE));
glsafe(::glDisable(GL_BLEND));
// Draw the grabber and the connecting line
Vec3d plane_center_rot = calc_plane_center(plane_points_rot);
m_move_grabber.center = plane_center_rot + plane_normal_rot * Offset;
//m_move_grabber.angles = m_current_base_rotation + m_rotation;
glsafe(::glDisable(GL_DEPTH_TEST));
glsafe(::glLineWidth(m_hover_id != -1 ? 2.0f : 1.5f));
glsafe(::glColor3f(1.0, 1.0, 0.0));
glLineStipple(1, 0x0FFF);
glEnable(GL_LINE_STIPPLE);
::glBegin(GL_LINES);
::glVertex3dv(plane_center_rot.data());
::glVertex3dv(m_move_grabber.center.data());
glsafe(::glEnd());
glDisable(GL_LINE_STIPPLE);
//std::copy(std::begin(GrabberColor), std::end(GrabberColor), m_move_grabber.color);
//m_move_grabber.color = GrabberColor;
//m_move_grabber.hover_color = GrabberHoverColor;
//m_move_grabber.render(m_hover_id == get_group_id(), (float)((box.size()(0) + box.size()(1) + box.size()(2)) / 3.0));
bool hover = (m_hover_id == get_group_id());
std::array<float, 4> render_color;
if (hover) {
render_color = GrabberHoverColor;
}
else
render_color = GrabberColor;
const GLModel& cube = m_move_grabber.get_cube();
//BBS set to fixed size grabber
//float fullsize = 2 * (dragging ? get_dragging_half_size(size) : get_half_size(size));
float fullsize = 8.0f;
if (GLGizmoBase::INV_ZOOM > 0) {
fullsize = m_move_grabber.FixedGrabberSize * GLGizmoBase::INV_ZOOM;
}
const_cast<GLModel*>(&cube)->set_color(-1, render_color);
glsafe(::glPushMatrix());
glsafe(::glTranslated(m_move_grabber.center.x(), m_move_grabber.center.y(), m_move_grabber.center.z()));
if (m_rotation(0) > EPSILON)
glsafe(::glRotated(Geometry::rad2deg(m_rotation(0)), 1.0, 0.0, 0.0));
if (m_rotation(1) > EPSILON)
glsafe(::glRotated(Geometry::rad2deg(m_rotation(1)), 0.0, 1.0, 0.0));
if (m_rotation(2) > EPSILON)
glsafe(::glRotated(Geometry::rad2deg(m_rotation(2)), 0.0, 0.0, 1.0));
for (int index = 0; index < m_rotate_cmds.size(); index ++)
{
Rotate_data& data = m_rotate_cmds[index];
if (data.ax == X)
glsafe(::glRotated(Geometry::rad2deg(data.angle), 1.0, 0.0, 0.0));
else if (data.ax == Y)
glsafe(::glRotated(Geometry::rad2deg(data.angle), 0.0, 1.0, 0.0));
else if (data.ax == Z)
glsafe(::glRotated(Geometry::rad2deg(data.angle), 0.0, 0.0, 1.0));
}
//glsafe(::glRotated(Geometry::rad2deg(angles.z()), 0.0, 0.0, 1.0));
//glsafe(::glRotated(Geometry::rad2deg(angles.y()), 0.0, 1.0, 0.0));
//glsafe(::glRotated(Geometry::rad2deg(angles.x()), 1.0, 0.0, 0.0));
glsafe(::glScaled(fullsize, fullsize, fullsize));
cube.render();
glsafe(::glPopMatrix());
// Should be placed at last, because GLGizmoRotate3D clears depth buffer
GLGizmoRotate3D::on_render();
}
void GLGizmoAdvancedCut::on_render_for_picking()
{
GLGizmoRotate3D::on_render_for_picking();
glsafe(::glDisable(GL_DEPTH_TEST));
BoundingBoxf3 box = m_parent.get_selection().get_bounding_box();
#if ENABLE_FIXED_GRABBER
float mean_size = (float)(GLGizmoBase::Grabber::FixedGrabberSize);
#else
float mean_size = (float)((box.size().x() + box.size().y() + box.size().z()) / 3.0);
#endif
std::array<float, 4> color = picking_color_component(0);
m_move_grabber.color[0] = color[0];
m_move_grabber.color[1] = color[1];
m_move_grabber.color[2] = color[2];
m_move_grabber.color[3] = color[3];
m_move_grabber.render_for_picking(mean_size);
}
void GLGizmoAdvancedCut::on_render_input_window(float x, float y, float bottom_limit)
{
//float unit_size = m_imgui->get_style_scaling() * 48.0f;
float space_size = m_imgui->get_style_scaling() * 8;
float movement_cap = m_imgui->calc_text_size(_L("Movement:")).x;
float rotate_cap = m_imgui->calc_text_size(_L("Rotate")).x;
float caption_size = std::max(movement_cap, rotate_cap) + 2 * space_size;
bool imperial_units = wxGetApp().app_config->get("use_inches") == "1";
unsigned int current_active_id = ImGui::GetActiveID();
Vec3d rotation = {Geometry::rad2deg(m_rotation(0)), Geometry::rad2deg(m_rotation(1)), Geometry::rad2deg(m_rotation(2))};
char buf[3][64];
float buf_size[3];
float vec_max = 0, unit_size = 0;
for (int i = 0; i < 3; i++) {
ImGui::DataTypeFormatString(buf[i], IM_ARRAYSIZE(buf[i]), ImGuiDataType_Double, (void *) &rotation[i], "%.2f");
buf_size[i] = ImGui::CalcTextSize(buf[i]).x;
vec_max = std::max(buf_size[i], vec_max);
}
float buf_size_max = ImGui::CalcTextSize("-100.00").x ;
if (vec_max < buf_size_max){
unit_size = buf_size_max + ImGui::GetStyle().FramePadding.x * 2.0f;
} else {
unit_size = vec_max + ImGui::GetStyle().FramePadding.x * 2.0f;
}
GizmoImguiSetNextWIndowPos(x, y, ImGuiCond_Always, 0.0f, 0.0f);
ImGuiWrapper::push_toolbar_style(m_parent.get_scale());
GizmoImguiBegin(on_get_name(), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
ImGui::PushItemWidth(caption_size);
ImGui::Dummy(ImVec2(caption_size, -1));
ImGui::SameLine(caption_size + 1 * space_size);
ImGui::PushItemWidth(unit_size);
ImGui::TextAlignCenter("X");
ImGui::SameLine(caption_size + 1 * unit_size + 2 * space_size);
ImGui::PushItemWidth(unit_size);
ImGui::TextAlignCenter("Y");
ImGui::SameLine(caption_size + 2 * unit_size + 3 * space_size);
ImGui::PushItemWidth(unit_size);
ImGui::TextAlignCenter("Z");
ImGui::AlignTextToFramePadding();
// Rotation input box
ImGui::PushItemWidth(caption_size);
m_imgui->text(_L("Rotation") + " ");
ImGui::SameLine(caption_size + 1 * space_size);
ImGui::PushItemWidth(unit_size);
ImGui::BBLInputDouble("##cut_rotation_x", &rotation[0], 0.0f, 0.0f, "%.2f");
ImGui::SameLine(caption_size + 1 * unit_size + 2 * space_size);
ImGui::PushItemWidth(unit_size);
ImGui::BBLInputDouble("##cut_rotation_y", &rotation[1], 0.0f, 0.0f, "%.2f");
ImGui::SameLine(caption_size + 2 * unit_size + 3 * space_size);
ImGui::PushItemWidth(unit_size);
ImGui::BBLInputDouble("##cut_rotation_z", &rotation[2], 0.0f, 0.0f, "%.2f");
if (current_active_id != m_last_active_id) {
if (std::abs(Geometry::rad2deg(m_rotation(0)) - m_buffered_rotation(0)) > EPSILON ||
std::abs(Geometry::rad2deg(m_rotation(1)) - m_buffered_rotation(1)) > EPSILON ||
std::abs(Geometry::rad2deg(m_rotation(2)) - m_buffered_rotation(2)) > EPSILON)
{
m_rotation = m_buffered_rotation;
m_buffered_rotation.setZero();
update_plane_points();
m_parent.post_event(SimpleEvent(wxEVT_PAINT));
}
}
else {
m_buffered_rotation(0) = Geometry::deg2rad(rotation(0));
m_buffered_rotation(1) = Geometry::deg2rad(rotation(1));
m_buffered_rotation(2) = Geometry::deg2rad(rotation(2));
}
ImGui::Separator();
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4.0f, 10.0f));
// Movement input box
double movement = m_movement;
ImGui::PushItemWidth(caption_size);
ImGui::AlignTextToFramePadding();
m_imgui->text(_L("Movement") + " ");
ImGui::SameLine(caption_size + 1 * space_size);
ImGui::PushItemWidth(3 * unit_size + 2 * space_size);
ImGui::BBLInputDouble("##cut_movement", &movement, 0.0f, 0.0f, "%.2f");
if (current_active_id != m_last_active_id) {
if (std::abs(m_buffered_movement - m_movement) > EPSILON) {
m_movement = m_buffered_movement;
m_buffered_movement = 0.0;
// update absolute height
Vec3d plane_normal = get_plane_normal();
m_height_delta = plane_normal(2) * m_movement;
m_height += m_height_delta;
m_buffered_height = m_height;
update_plane_points();
m_parent.post_event(SimpleEvent(wxEVT_PAINT));
}
} else {
m_buffered_movement = movement;
}
// height input box
double height = m_height;
ImGui::PushItemWidth(caption_size);
ImGui::AlignTextToFramePadding();
m_imgui->text(_L("Height") + " ");
ImGui::SameLine(caption_size + 1 * space_size);
ImGui::PushItemWidth(3 * unit_size + 2 * space_size);
ImGui::BBLInputDouble("##cut_height", &height, 0.0f, 0.0f, "%.2f");
if (current_active_id != m_last_active_id) {
if (std::abs(m_buffered_height - m_height) > EPSILON) {
m_height_delta = m_buffered_height - m_height;
m_height = m_buffered_height;
update_plane_points();
m_parent.post_event(SimpleEvent(wxEVT_PAINT));
}
}
else {
m_buffered_height = height;
}
ImGui::PopStyleVar(1);
ImGui::Separator();
// Part selection
m_imgui->bbl_checkbox(_L("Keep upper part"), m_keep_upper);
m_imgui->bbl_checkbox(_L("Keep lower part"), m_keep_lower);
m_imgui->bbl_checkbox(_L("Cut to parts"), m_cut_to_parts);
#if 0
// Auto segment input
ImGui::PushItemWidth(m_imgui->get_style_scaling() * 150.0);
m_imgui->checkbox(_L("Auto Segment"), m_do_segment);
m_imgui->disabled_begin(!m_do_segment);
ImGui::InputDouble("smoothing_alpha", &m_segment_smoothing_alpha, 0.0f, 0.0f, "%.2f");
m_segment_smoothing_alpha = std::max(0.1, std::min(100.0, m_segment_smoothing_alpha));
ImGui::InputInt("segment number", &m_segment_number);
m_segment_number = std::max(1, m_segment_number);
m_imgui->disabled_end();
ImGui::Separator();
#endif
// Cut button
m_imgui->disabled_begin((!m_keep_upper && !m_keep_lower && !m_cut_to_parts && !m_do_segment));
const bool cut_clicked = m_imgui->button(_L("Perform cut"));
m_imgui->disabled_end();
ImGui::SameLine();
const bool reset_clicked = m_imgui->button(_L("Reset"));
if (reset_clicked) { reset_all(); }
GizmoImguiEnd();
ImGuiWrapper::pop_toolbar_style();
// Perform cut
if (cut_clicked && (m_keep_upper || m_keep_lower || m_cut_to_parts || m_do_segment))
perform_cut(m_parent.get_selection());
m_last_active_id = current_active_id;
}
void GLGizmoAdvancedCut::set_movement(double movement) const
{
m_movement = movement;
}
void GLGizmoAdvancedCut::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, "GLGizmoAdvancedCut: Invalid object selection");
// m_cut_z is the distance from the bed. Subtract possible SLA elevation.
const GLVolume* first_glvolume = selection.get_volume(*selection.get_volume_idxs().begin());
// BBS: do segment
if (m_do_segment)
{
wxGetApp().plater()->segment(object_idx, instance_idx, m_segment_smoothing_alpha, m_segment_number);
}
else {
wxGetApp().plater()->cut(object_idx, instance_idx, get_plane_points_world_coord(),
only_if(m_keep_upper, ModelObjectCutAttribute::KeepUpper) |
only_if(m_keep_lower, ModelObjectCutAttribute::KeepLower) |
only_if(m_cut_to_parts, ModelObjectCutAttribute::CutToParts));
}
}
Vec3d GLGizmoAdvancedCut::calc_plane_normal(const std::array<Vec3d, 4>& plane_points) const
{
Vec3d v01 = plane_points[1] - plane_points[0];
Vec3d v12 = plane_points[2] - plane_points[1];
Vec3d plane_normal = v01.cross(v12);
plane_normal.normalize();
return plane_normal;
}
Vec3d GLGizmoAdvancedCut::calc_plane_center(const std::array<Vec3d, 4>& plane_points) const
{
Vec3d plane_center;
plane_center.setZero();
for (const Vec3d& point : plane_points)
plane_center = plane_center + point;
return plane_center / (float)m_cut_plane_points.size();
}
double GLGizmoAdvancedCut::calc_projection(const Linef3& mouse_ray) const
{
Vec3d mouse_dir = mouse_ray.unit_vector();
Vec3d inters = mouse_ray.a + (m_drag_pos - mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir;
Vec3d inters_vec = inters - m_drag_pos;
Vec3d plane_normal = get_plane_normal();
return inters_vec.dot(plane_normal);
}
Vec3d GLGizmoAdvancedCut::get_plane_normal() const
{
return calc_plane_normal(m_cut_plane_points);
}
Vec3d GLGizmoAdvancedCut::get_plane_center() const
{
return calc_plane_center(m_cut_plane_points);
}
void GLGizmoAdvancedCut::finish_rotation()
{
for (int i = 0; i < 3; i++) {
m_gizmos[i].set_angle(0.);
}
update_plane_points();
}
} // namespace GUI
} // namespace Slic3r