Add the full source of BambuStudio

using version 1.0.10
This commit is contained in:
lane.wei 2022-07-15 23:37:19 +08:00 committed by Lane.Wei
parent 30bcadab3e
commit 1555904bef
3771 changed files with 1251328 additions and 0 deletions

View file

@ -0,0 +1,620 @@
// 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();
}
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_NONE;
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();
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_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;
//m_buffered_height = 0.0;
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

View file

@ -0,0 +1,115 @@
#ifndef slic3r_GLGizmoAdvancedCut_hpp_
#define slic3r_GLGizmoAdvancedCut_hpp_
#include "GLGizmoBase.hpp"
#include "GLGizmoRotate.hpp"
namespace Slic3r {
namespace GUI {
class GLGizmoAdvancedCut : public GLGizmoRotate3D
{
struct Rotate_data {
double angle;
Axis ax;
Rotate_data(double an, Axis a)
: angle(an), ax(a)
{
}
};
private:
static const double Offset;
static const double Margin;
static const std::array<float, 4> GrabberColor;
static const std::array<float, 4> GrabberHoverColor;
mutable double m_movement;
mutable double m_height; // height of cut plane to heatbed
mutable double m_height_delta; // height of cut plane to heatbed
double m_start_movement;
double m_start_height;
Vec3d m_rotation;
//Vec3d m_current_base_rotation;
std::vector<Rotate_data> m_rotate_cmds;
Vec3d m_buffered_rotation;
double m_buffered_movement;
double m_buffered_height;
Vec3d m_drag_pos;
bool m_keep_upper;
bool m_keep_lower;
bool m_cut_to_parts;
bool m_rotate_lower;
bool m_do_segment;
double m_segment_smoothing_alpha;
int m_segment_number;
std::array<Vec3d, 4> m_cut_plane_points;
mutable Grabber m_move_grabber;
unsigned int m_last_active_id;
public:
GLGizmoAdvancedCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
double get_movement() const { return m_movement; }
void set_movement(double movement) const;
void finish_rotation();
std::string get_tooltip() const override;
protected:
virtual bool on_init();
virtual std::string on_get_name() const;
virtual void on_set_state();
virtual bool on_is_activable() const;
virtual void on_start_dragging();
virtual void on_update(const UpdateData& data);
virtual void on_render();
virtual void on_render_for_picking();
virtual void on_render_input_window(float x, float y, float bottom_limit);
virtual void on_enable_grabber(unsigned int id)
{
if (id < 3)
m_gizmos[id].enable_grabber(0);
else if (id == 3)
this->enable_grabber(0);
}
virtual void on_disable_grabber(unsigned int id)
{
if (id < 3)
m_gizmos[id].disable_grabber(0);
else if (id == 3)
this->disable_grabber(0);
}
virtual void on_set_hover_id()
{
for (int i = 0; i < 3; ++i)
m_gizmos[i].set_hover_id((m_hover_id == i) ? 0 : -1);
}
private:
void perform_cut(const Selection& selection);
double calc_projection(const Linef3& mouse_ray) const;
Vec3d calc_plane_normal(const std::array<Vec3d, 4>& plane_points) const;
Vec3d calc_plane_center(const std::array<Vec3d, 4>& plane_points) const;
Vec3d get_plane_normal() const;
Vec3d get_plane_center() const;
void update_plane_points();
std::array<Vec3d, 4> get_plane_points() const;
std::array<Vec3d, 4> get_plane_points_world_coord() const;
void reset_cut_plane();
void reset_all();
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLGizmoAdvancedCut_hpp_

View file

@ -0,0 +1,372 @@
#include "GLGizmoBase.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include <GL/glew.h>
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/GUI_Colors.hpp"
// TODO: Display tooltips quicker on Linux
namespace Slic3r {
namespace GUI {
float GLGizmoBase::INV_ZOOM = 1.0f;
const float GLGizmoBase::Grabber::SizeFactor = 0.05f;
const float GLGizmoBase::Grabber::MinHalfSize = 4.0f;
const float GLGizmoBase::Grabber::DraggingScaleFactor = 1.25f;
const float GLGizmoBase::Grabber::FixedGrabberSize = 16.0f;
const float GLGizmoBase::Grabber::FixedRadiusSize = 80.0f;
std::array<float, 4> GLGizmoBase::DEFAULT_BASE_COLOR = { 0.625f, 0.625f, 0.625f, 1.0f };
std::array<float, 4> GLGizmoBase::DEFAULT_DRAG_COLOR = { 1.0f, 1.0f, 1.0f, 1.0f };
std::array<float, 4> GLGizmoBase::DEFAULT_HIGHLIGHT_COLOR = { 1.0f, 0.38f, 0.0f, 1.0f };
std::array<std::array<float, 4>, 3> GLGizmoBase::AXES_HOVER_COLOR = {{
{ 0.7f, 0.0f, 0.0f, 1.0f },
{ 0.0f, 0.7f, 0.0f, 1.0f },
{ 0.0f, 0.0f, 0.7f, 1.0f }
}};
std::array<std::array<float, 4>, 3> GLGizmoBase::AXES_COLOR = { {
{ 1.0, 0.0f, 0.0f, 1.0f },
{ 0.0f, 1.0f, 0.0f, 1.0f },
{ 0.0f, 0.0f, 1.0f, 1.0f }
}};
std::array<float, 4> GLGizmoBase::CONSTRAINED_COLOR = { 0.5f, 0.5f, 0.5f, 1.0f };
std::array<float, 4> GLGizmoBase::FLATTEN_COLOR = { 0.96f, 0.93f, 0.93f, 0.5f };
std::array<float, 4> GLGizmoBase::FLATTEN_HOVER_COLOR = { 1.0f, 1.0f, 1.0f, 0.75f };
// new style color
std::array<float, 4> GLGizmoBase::GRABBER_NORMAL_COL = {1.0f, 1.0f, 1.0f, 1.0f};
std::array<float, 4> GLGizmoBase::GRABBER_HOVER_COL = {0.863f, 0.125f, 0.063f, 1.0f};
std::array<float, 4> GLGizmoBase::GRABBER_UNIFORM_COL = {0, 1.0, 1.0, 1.0f};
std::array<float, 4> GLGizmoBase::GRABBER_UNIFORM_HOVER_COL = {0, 0.7, 0.7, 1.0f};
void GLGizmoBase::update_render_colors()
{
GLGizmoBase::AXES_COLOR = { {
GLColor(RenderColor::colors[RenderCol_Grabber_X]),
GLColor(RenderColor::colors[RenderCol_Grabber_Y]),
GLColor(RenderColor::colors[RenderCol_Grabber_Z])
} };
GLGizmoBase::FLATTEN_COLOR = GLColor(RenderColor::colors[RenderCol_Flatten_Plane]);
GLGizmoBase::FLATTEN_HOVER_COLOR = GLColor(RenderColor::colors[RenderCol_Flatten_Plane_Hover]);
}
void GLGizmoBase::load_render_colors()
{
RenderColor::colors[RenderCol_Grabber_X] = IMColor(GLGizmoBase::AXES_COLOR[0]);
RenderColor::colors[RenderCol_Grabber_Y] = IMColor(GLGizmoBase::AXES_COLOR[1]);
RenderColor::colors[RenderCol_Grabber_Z] = IMColor(GLGizmoBase::AXES_COLOR[2]);
RenderColor::colors[RenderCol_Flatten_Plane] = IMColor(GLGizmoBase::FLATTEN_COLOR);
RenderColor::colors[RenderCol_Flatten_Plane_Hover] = IMColor(GLGizmoBase::FLATTEN_HOVER_COLOR);
}
GLGizmoBase::Grabber::Grabber()
: center(Vec3d::Zero())
, angles(Vec3d::Zero())
, dragging(false)
, enabled(true)
{
color = GRABBER_NORMAL_COL;
hover_color = GRABBER_HOVER_COL;
}
void GLGizmoBase::Grabber::render(bool hover, float size) const
{
std::array<float, 4> render_color;
if (hover) {
render_color = hover_color;
}
else
render_color = color;
render(size, render_color, false);
}
float GLGizmoBase::Grabber::get_half_size(float size) const
{
return std::max(size * SizeFactor, MinHalfSize);
}
float GLGizmoBase::Grabber::get_dragging_half_size(float size) const
{
return get_half_size(size) * DraggingScaleFactor;
}
const GLModel& GLGizmoBase::Grabber::get_cube() const
{
if (! cube_initialized) {
// This cannot be done in constructor, OpenGL is not yet
// initialized at that point (on Linux at least).
indexed_triangle_set mesh = its_make_cube(1., 1., 1.);
its_translate(mesh, Vec3f(-0.5, -0.5, -0.5));
const_cast<GLModel&>(cube).init_from(mesh, BoundingBoxf3{ { -0.5, -0.5, -0.5 }, { 0.5, 0.5, 0.5 } });
const_cast<bool&>(cube_initialized) = true;
}
return cube;
}
void GLGizmoBase::Grabber::render(float size, const std::array<float, 4>& render_color, bool picking) const
{
if (! cube_initialized) {
// This cannot be done in constructor, OpenGL is not yet
// initialized at that point (on Linux at least).
indexed_triangle_set mesh = its_make_cube(1., 1., 1.);
its_translate(mesh, Vec3f(-0.5, -0.5, -0.5));
const_cast<GLModel&>(cube).init_from(mesh, BoundingBoxf3{ { -0.5, -0.5, -0.5 }, { 0.5, 0.5, 0.5 } });
const_cast<bool&>(cube_initialized) = true;
}
//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 = FixedGrabberSize * GLGizmoBase::INV_ZOOM;
}
const_cast<GLModel*>(&cube)->set_color(-1, render_color);
glsafe(::glPushMatrix());
glsafe(::glTranslated(center.x(), center.y(), center.z()));
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());
}
GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: m_parent(parent)
, m_group_id(-1)
, m_state(Off)
, m_shortcut_key(0)
, m_icon_filename(icon_filename)
, m_sprite_id(sprite_id)
, m_hover_id(-1)
, m_dragging(false)
, m_imgui(wxGetApp().imgui())
, m_first_input_window_render(true)
, m_dirty(false)
{
m_base_color = DEFAULT_BASE_COLOR;
m_drag_color = DEFAULT_DRAG_COLOR;
m_highlight_color = DEFAULT_HIGHLIGHT_COLOR;
m_cone.init_from(its_make_cone(1., 1., 2 * PI / 24));
m_sphere.init_from(its_make_sphere(1., (2 * M_PI) / 24.));
m_cylinder.init_from(its_make_cylinder(1., 1., 2 * PI / 24.));
}
void GLGizmoBase::set_hover_id(int id)
{
if (m_grabbers.empty() || (id < (int)m_grabbers.size()))
{
m_hover_id = id;
on_set_hover_id();
}
}
void GLGizmoBase::set_highlight_color(const std::array<float, 4>& color)
{
m_highlight_color = color;
}
void GLGizmoBase::enable_grabber(unsigned int id)
{
if (id < m_grabbers.size())
m_grabbers[id].enabled = true;
on_enable_grabber(id);
}
void GLGizmoBase::disable_grabber(unsigned int id)
{
if (id < m_grabbers.size())
m_grabbers[id].enabled = false;
on_disable_grabber(id);
}
void GLGizmoBase::start_dragging()
{
m_dragging = true;
for (int i = 0; i < (int)m_grabbers.size(); ++i)
{
m_grabbers[i].dragging = (m_hover_id == i);
}
on_start_dragging();
//BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("this %1%, m_hover_id=%2%\n")%this %m_hover_id;
}
void GLGizmoBase::stop_dragging()
{
m_dragging = false;
for (int i = 0; i < (int)m_grabbers.size(); ++i)
{
m_grabbers[i].dragging = false;
}
on_stop_dragging();
//BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("this %1%, m_hover_id=%2%\n")%this %m_hover_id;
}
void GLGizmoBase::update(const UpdateData& data)
{
if (m_hover_id != -1)
on_update(data);
}
bool GLGizmoBase::update_items_state()
{
bool res = m_dirty;
m_dirty = false;
return res;
};
bool GLGizmoBase::GizmoImguiBegin(const std::string &name, int flags)
{
return m_imgui->begin(name, flags);
}
void GLGizmoBase::GizmoImguiEnd()
{
last_input_window_width = ImGui::GetWindowWidth();
m_imgui->end();
}
void GLGizmoBase::GizmoImguiSetNextWIndowPos(float x, float y, int flag, float pivot_x, float pivot_y)
{
if (abs(last_input_window_width) > 0.01f) {
if (x + last_input_window_width > m_parent.get_canvas_size().get_width()) {
if (last_input_window_width > m_parent.get_canvas_size().get_width()) {
x = 0;
} else {
x = m_parent.get_canvas_size().get_width() - last_input_window_width;
}
}
}
m_imgui->set_next_window_pos(x, y, flag, pivot_x, pivot_y);
}
std::array<float, 4> GLGizmoBase::picking_color_component(unsigned int id) const
{
static const float INV_255 = 1.0f / 255.0f;
id = BASE_ID - id;
if (m_group_id > -1)
id -= m_group_id;
// color components are encoded to match the calculation of volume_id made into GLCanvas3D::_picking_pass()
return std::array<float, 4> {
float((id >> 0) & 0xff) * INV_255, // red
float((id >> 8) & 0xff) * INV_255, // green
float((id >> 16) & 0xff) * INV_255, // blue
float(picking_checksum_alpha_channel(id & 0xff, (id >> 8) & 0xff, (id >> 16) & 0xff))* INV_255 // checksum for validating against unwanted alpha blending and multi sampling
};
}
void GLGizmoBase::render_grabbers(const BoundingBoxf3& box) const
{
#if ENABLE_FIXED_GRABBER
render_grabbers((float)(GLGizmoBase::Grabber::FixedGrabberSize));
#else
render_grabbers((float)((box.size().x() + box.size().y() + box.size().z()) / 3.0));
#endif
}
void GLGizmoBase::render_grabbers(float size) const
{
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
if (shader == nullptr)
return;
shader->start_using();
shader->set_uniform("emission_factor", 0.1f);
for (int i = 0; i < (int)m_grabbers.size(); ++i) {
if (m_grabbers[i].enabled)
m_grabbers[i].render(m_hover_id == i, size);
}
shader->stop_using();
}
void GLGizmoBase::render_grabbers_for_picking(const BoundingBoxf3& box) const
{
#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
for (unsigned int i = 0; i < (unsigned int)m_grabbers.size(); ++i) {
if (m_grabbers[i].enabled) {
std::array<float, 4> color = picking_color_component(i);
m_grabbers[i].color = color;
m_grabbers[i].render_for_picking(mean_size);
}
}
}
std::string GLGizmoBase::format(float value, unsigned int decimals) const
{
return Slic3r::string_printf("%.*f", decimals, value);
}
void GLGizmoBase::set_dirty() {
m_dirty = true;
}
void GLGizmoBase::render_input_window(float x, float y, float bottom_limit)
{
on_render_input_window(x, y, bottom_limit);
if (m_first_input_window_render) {
// for some reason, the imgui dialogs are not shown on screen in the 1st frame where they are rendered, but show up only with the 2nd rendered frame
// so, we forces another frame rendering the first time the imgui window is shown
m_parent.set_as_dirty();
m_first_input_window_render = false;
}
}
std::string GLGizmoBase::get_name(bool include_shortcut) const
{
int key = get_shortcut_key();
std::string out = on_get_name();
if (include_shortcut && key >= WXK_CONTROL_A && key <= WXK_CONTROL_Z)
out += std::string(" [") + char(int('A') + key - int(WXK_CONTROL_A)) + "]";
return out;
}
// Produce an alpha channel checksum for the red green blue components. The alpha channel may then be used to verify, whether the rgb components
// were not interpolated by alpha blending or multi sampling.
unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue)
{
// 8 bit hash for the color
unsigned char b = ((((37 * red) + green) & 0x0ff) * 37 + blue) & 0x0ff;
// Increase enthropy by a bit reversal
b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
// Flip every second bit to increase the enthropy even more.
b ^= 0x55;
return b;
}
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,235 @@
#ifndef slic3r_GLGizmoBase_hpp_
#define slic3r_GLGizmoBase_hpp_
#include "libslic3r/Point.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/GLModel.hpp"
#include <cereal/archives/binary.hpp>
#define ENABLE_FIXED_GRABBER 1
class wxWindow;
namespace Slic3r {
class BoundingBoxf3;
class Linef3;
class ModelObject;
namespace GUI {
class ImGuiWrapper;
class GLCanvas3D;
enum class CommonGizmosDataID;
class CommonGizmosDataPool;
class Selection;
class GLGizmoBase
{
public:
// Starting value for ids to avoid clashing with ids used by GLVolumes
// (254 is choosen to leave some space for forward compatibility)
static const unsigned int BASE_ID = 255 * 255 * 254;
static float INV_ZOOM;
//BBS colors
static std::array<float, 4> DEFAULT_BASE_COLOR;
static std::array<float, 4> DEFAULT_DRAG_COLOR;
static std::array<float, 4> DEFAULT_HIGHLIGHT_COLOR;
static std::array<std::array<float, 4>, 3> AXES_COLOR;
static std::array<std::array<float, 4>, 3> AXES_HOVER_COLOR;
static std::array<float, 4> CONSTRAINED_COLOR;
static std::array<float, 4> FLATTEN_COLOR;
static std::array<float, 4> FLATTEN_HOVER_COLOR;
static std::array<float, 4> GRABBER_NORMAL_COL;
static std::array<float, 4> GRABBER_HOVER_COL;
static std::array<float, 4> GRABBER_UNIFORM_COL;
static std::array<float, 4> GRABBER_UNIFORM_HOVER_COL;
static void update_render_colors();
static void load_render_colors();
protected:
struct Grabber
{
static const float SizeFactor;
static const float MinHalfSize;
static const float DraggingScaleFactor;
static const float FixedGrabberSize;
static const float FixedRadiusSize;
Vec3d center;
Vec3d angles;
std::array<float, 4> color;
std::array<float, 4> hover_color;
bool enabled;
bool dragging;
Grabber();
void render(bool hover, float size) const;
void render_for_picking(float size) const { render(size, color, true); }
float get_half_size(float size) const;
float get_dragging_half_size(float size) const;
const GLModel& get_cube() const;
private:
void render(float size, const std::array<float, 4>& render_color, bool picking) const;
GLModel cube;
bool cube_initialized = false;
};
public:
enum EState
{
Off,
On,
Num_States
};
struct UpdateData
{
const Linef3& mouse_ray;
const Point& mouse_pos;
UpdateData(const Linef3& mouse_ray, const Point& mouse_pos)
: mouse_ray(mouse_ray), mouse_pos(mouse_pos)
{}
};
protected:
GLCanvas3D& m_parent;
int m_group_id;
EState m_state;
int m_shortcut_key;
std::string m_icon_filename;
unsigned int m_sprite_id;
int m_hover_id;
bool m_dragging;
std::array<float, 4> m_base_color;
std::array<float, 4> m_drag_color;
std::array<float, 4> m_highlight_color;
mutable std::vector<Grabber> m_grabbers;
ImGuiWrapper* m_imgui;
bool m_first_input_window_render;
mutable std::string m_tooltip;
CommonGizmosDataPool* m_c;
GLModel m_cone;
GLModel m_cylinder;
GLModel m_sphere;
public:
GLGizmoBase(GLCanvas3D& parent,
const std::string& icon_filename,
unsigned int sprite_id);
virtual ~GLGizmoBase() {}
bool init() { return on_init(); }
void load(cereal::BinaryInputArchive& ar) { m_state = On; on_load(ar); }
void save(cereal::BinaryOutputArchive& ar) const { on_save(ar); }
std::string get_name(bool include_shortcut = true) const;
int get_group_id() const { return m_group_id; }
void set_group_id(int id) { m_group_id = id; }
EState get_state() const { return m_state; }
void set_state(EState state) { m_state = state; on_set_state(); }
int get_shortcut_key() const { return m_shortcut_key; }
const std::string& get_icon_filename() const { return m_icon_filename; }
bool is_activable() const { return on_is_activable(); }
bool is_selectable() const { return on_is_selectable(); }
CommonGizmosDataID get_requirements() const { return on_get_requirements(); }
virtual bool wants_enter_leave_snapshots() const { return false; }
virtual std::string get_gizmo_entering_text() const { assert(false); return ""; }
virtual std::string get_gizmo_leaving_text() const { assert(false); return ""; }
virtual std::string get_action_snapshot_name() { return _u8L("Gizmo action"); }
void set_common_data_pool(CommonGizmosDataPool* ptr) { m_c = ptr; }
unsigned int get_sprite_id() const { return m_sprite_id; }
int get_hover_id() const { return m_hover_id; }
void set_hover_id(int id);
void set_highlight_color(const std::array<float, 4>& color);
void enable_grabber(unsigned int id);
void disable_grabber(unsigned int id);
void start_dragging();
void stop_dragging();
bool is_dragging() const { return m_dragging; }
void update(const UpdateData& data);
// returns True when Gizmo changed its state
bool update_items_state();
void render() { m_tooltip.clear(); on_render(); }
void render_for_picking() { on_render_for_picking(); }
void render_input_window(float x, float y, float bottom_limit);
virtual std::string get_tooltip() const { return ""; }
protected:
float last_input_window_width = 0;
virtual bool on_init() = 0;
virtual void on_load(cereal::BinaryInputArchive& ar) {}
virtual void on_save(cereal::BinaryOutputArchive& ar) const {}
virtual std::string on_get_name() const = 0;
virtual void on_set_state() {}
virtual void on_set_hover_id() {}
virtual bool on_is_activable() const { return true; }
virtual bool on_is_selectable() const { return true; }
virtual CommonGizmosDataID on_get_requirements() const { return CommonGizmosDataID(0); }
virtual void on_enable_grabber(unsigned int id) {}
virtual void on_disable_grabber(unsigned int id) {}
virtual void on_start_dragging() {}
virtual void on_stop_dragging() {}
virtual void on_update(const UpdateData& data) {}
virtual void on_render() = 0;
virtual void on_render_for_picking() = 0;
virtual void on_render_input_window(float x, float y, float bottom_limit) {}
bool GizmoImguiBegin(const std::string& name, int flags);
void GizmoImguiEnd();
void GizmoImguiSetNextWIndowPos(float x, float y, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f);
// Returns the picking color for the given id, based on the BASE_ID constant
// No check is made for clashing with other picking color (i.e. GLVolumes)
std::array<float, 4> picking_color_component(unsigned int id) const;
void render_grabbers(const BoundingBoxf3& box) const;
void render_grabbers(float size) const;
void render_grabbers_for_picking(const BoundingBoxf3& box) const;
std::string format(float value, unsigned int decimals) const;
// Mark gizmo as dirty to Re-Render when idle()
void set_dirty();
private:
// Flag for dirty visible state of Gizmo
// When True then need new rendering
bool m_dirty;
};
// Produce an alpha channel checksum for the red green blue components. The alpha channel may then be used to verify, whether the rgb components
// were not interpolated by alpha blending or multi sampling.
extern unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue);
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLGizmoBase_hpp_

View file

@ -0,0 +1,343 @@
// 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 <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 "slic3r/GUI/GUI_ObjectManipulation.hpp"
#include "libslic3r/AppConfig.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/TriangleMeshSlicer.hpp"
namespace Slic3r {
namespace GUI {
const double GLGizmoCut::Offset = 10.0;
const double GLGizmoCut::Margin = 20.0;
const std::array<float, 4> GLGizmoCut::GrabberColor = { 1.0, 0.5, 0.0, 1.0 };
GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoBase(parent, icon_filename, sprite_id)
{}
std::string GLGizmoCut::get_tooltip() const
{
double cut_z = m_cut_z;
if (wxGetApp().app_config->get("use_inches") == "1")
cut_z *= ObjectManipulation::mm_to_in;
return (m_hover_id == 0 || m_grabbers[0].dragging) ? "Z: " + format(cut_z, 2) : "";
}
bool GLGizmoCut::on_init()
{
m_grabbers.emplace_back();
m_shortcut_key = WXK_CONTROL_C;
return true;
}
std::string GLGizmoCut::on_get_name() const
{
return _u8L("Cut");
}
void GLGizmoCut::on_set_state()
{
// Reset m_cut_z on gizmo activation
if (get_state() == On)
m_cut_z = bounding_box().center().z();
}
bool GLGizmoCut::on_is_activable() const
{
const Selection& selection = m_parent.get_selection();
return selection.is_single_full_instance() && !selection.is_wipe_tower();
}
void GLGizmoCut::on_start_dragging()
{
if (m_hover_id == -1)
return;
const BoundingBoxf3 box = bounding_box();
m_max_z = box.max.z();
m_start_z = m_cut_z;
m_drag_pos = m_grabbers[m_hover_id].center;
m_drag_center = box.center();
m_drag_center.z() = m_cut_z;
}
void GLGizmoCut::on_update(const UpdateData& data)
{
if (m_hover_id != -1)
set_cut_z(m_start_z + calc_projection(data.mouse_ray));
}
void GLGizmoCut::on_render()
{
const BoundingBoxf3 box = bounding_box();
Vec3d plane_center = box.center();
plane_center.z() = m_cut_z;
m_max_z = box.max.z();
set_cut_z(m_cut_z);
update_contours();
const float min_x = box.min.x() - Margin;
const float max_x = box.max.x() + Margin;
const float min_y = box.min.y() - Margin;
const float max_y = box.max.y() + Margin;
glsafe(::glEnable(GL_DEPTH_TEST));
glsafe(::glDisable(GL_CULL_FACE));
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
// Draw the cutting plane
::glBegin(GL_QUADS);
::glColor4f(0.8f, 0.8f, 0.8f, 0.5f);
::glVertex3f(min_x, min_y, plane_center.z());
::glVertex3f(max_x, min_y, plane_center.z());
::glVertex3f(max_x, max_y, plane_center.z());
::glVertex3f(min_x, max_y, plane_center.z());
glsafe(::glEnd());
glsafe(::glEnable(GL_CULL_FACE));
glsafe(::glDisable(GL_BLEND));
// TODO: draw cut part contour?
// Draw the grabber and the connecting line
m_grabbers[0].center = plane_center;
m_grabbers[0].center.z() = plane_center.z() + Offset;
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
glsafe(::glLineWidth(m_hover_id != -1 ? 2.0f : 1.5f));
glsafe(::glColor3f(1.0, 1.0, 0.0));
::glBegin(GL_LINES);
::glVertex3dv(plane_center.data());
::glVertex3dv(m_grabbers[0].center.data());
glsafe(::glEnd());
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
if (shader == nullptr)
return;
shader->start_using();
shader->set_uniform("emission_factor", 0.1f);
m_grabbers[0].color = GrabberColor;
m_grabbers[0].render(m_hover_id == 0, (float)((box.size().x() + box.size().y() + box.size().z()) / 3.0));
shader->stop_using();
glsafe(::glPushMatrix());
glsafe(::glTranslated(m_cut_contours.shift.x(), m_cut_contours.shift.y(), m_cut_contours.shift.z()));
glsafe(::glLineWidth(2.0f));
m_cut_contours.contours.render();
glsafe(::glPopMatrix());
}
void GLGizmoCut::on_render_for_picking()
{
glsafe(::glDisable(GL_DEPTH_TEST));
render_grabbers_for_picking(m_parent.get_selection().get_bounding_box());
}
void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit)
{
//static float last_y = 0.0f;
//static float last_h = 0.0f;
//BBS: GUI refactor: move gizmo to the right
#if BBS_TOOLBAR_ON_TOP
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always, 0.5f, 0.0f);
#else
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always, 1.0f, 0.0f);
#endif
//BBS
ImGuiWrapper::push_toolbar_style();
m_imgui->begin(_L("Cut"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
const bool imperial_units = wxGetApp().app_config->get("use_inches") == "1";
// 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
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
m_imgui->set_requires_extra_frame();
#else
m_parent.request_extra_frame();
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
if (last_h != win_h)
last_h = win_h;
if (last_y != y)
last_y = y;
}*/
ImGui::AlignTextToFramePadding();
m_imgui->text("Z");
ImGui::SameLine();
ImGui::PushItemWidth(m_imgui->get_style_scaling() * 150.0f);
double cut_z = m_cut_z;
if (imperial_units)
cut_z *= ObjectManipulation::mm_to_in;
ImGui::InputDouble("", &cut_z, 0.0f, 0.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal);
ImGui::SameLine();
m_imgui->text(imperial_units ? _L("in") : _L("mm"));
m_cut_z = cut_z * (imperial_units ? ObjectManipulation::in_to_mm : 1.0);
ImGui::Separator();
m_imgui->checkbox(_L("Keep upper part"), m_keep_upper);
m_imgui->checkbox(_L("Keep lower part"), m_keep_lower);
m_imgui->checkbox(_L("Cut to parts"), m_cut_to_parts); // BBS
m_imgui->checkbox(_L("Rotate lower part upwards"), m_rotate_lower);
// BBS
ImGui::Separator();
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();
m_imgui->disabled_begin((!m_keep_upper && !m_keep_lower && !m_do_segment) || m_cut_z <= 0.0 || m_max_z <= m_cut_z);
const bool cut_clicked = m_imgui->button(_L("Perform cut"));
m_imgui->disabled_end();
m_imgui->end();
//BBS
ImGuiWrapper::pop_toolbar_style();
// BBS: m_do_segment
if (cut_clicked && (m_keep_upper || m_keep_lower || m_do_segment))
perform_cut(m_parent.get_selection());
}
void GLGizmoCut::set_cut_z(double cut_z)
{
// Clamp the plane to the object's bounding box
m_cut_z = std::clamp(cut_z, 0.0, m_max_z);
}
void GLGizmoCut::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_volume(*selection.get_volume_idxs().begin());
const double object_cut_z = m_cut_z - first_glvolume->get_sla_shift_z();
// BBS: do segment
if (m_do_segment)
{
wxGetApp().plater()->segment(object_idx, instance_idx, m_segment_smoothing_alpha, m_segment_number);
}
else if (0.0 < object_cut_z && object_cut_z < m_max_z) {
//wxGetApp().plater()->cut(object_idx, instance_idx, object_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower, m_cut_to_parts);
}
else {
// the object is SLA-elevated and the plane is under it.
}
}
double GLGizmoCut::calc_projection(const Linef3& mouse_ray) const
{
double projection = 0.0;
const Vec3d starting_vec = m_drag_pos - m_drag_center;
const double len_starting_vec = starting_vec.norm();
if (len_starting_vec != 0.0) {
const Vec3d mouse_dir = 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
const Vec3d inters = mouse_ray.a + (m_drag_pos - mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir;
// vector from the starting position to the found intersection
const Vec3d inters_vec = inters - m_drag_pos;
// finds projection of the vector along the staring direction
projection = inters_vec.dot(starting_vec.normalized());
}
return projection;
}
BoundingBoxf3 GLGizmoCut::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);
if (!volume->is_modifier)
ret.merge(volume->transformed_convex_hull_bounding_box());
}
return ret;
}
void GLGizmoCut::update_contours()
{
const Selection& selection = m_parent.get_selection();
const GLVolume* first_glvolume = selection.get_volume(*selection.get_volume_idxs().begin());
const BoundingBoxf3& box = first_glvolume->transformed_convex_hull_bounding_box();
const ModelObject* model_object = wxGetApp().model().objects[selection.get_object_idx()];
const int instance_idx = selection.get_instance_idx();
if (0.0 < m_cut_z && m_cut_z < m_max_z) {
if (m_cut_contours.cut_z != m_cut_z || m_cut_contours.object_id != model_object->id() || m_cut_contours.instance_idx != instance_idx) {
m_cut_contours.cut_z = m_cut_z;
if (m_cut_contours.object_id != model_object->id())
m_cut_contours.mesh = model_object->raw_mesh();
m_cut_contours.position = box.center();
m_cut_contours.shift = Vec3d::Zero();
m_cut_contours.object_id = model_object->id();
m_cut_contours.instance_idx = instance_idx;
m_cut_contours.contours.reset();
MeshSlicingParams slicing_params;
slicing_params.trafo = first_glvolume->get_instance_transformation().get_matrix();
const Polygons polys = slice_mesh(m_cut_contours.mesh.its, m_cut_z, slicing_params);
if (!polys.empty()) {
m_cut_contours.contours.init_from(polys, static_cast<float>(m_cut_z));
m_cut_contours.contours.set_color(-1, { 1.0f, 1.0f, 1.0f, 1.0f });
}
}
else if (box.center() != m_cut_contours.position) {
m_cut_contours.shift = box.center() - m_cut_contours.position;
}
}
else
m_cut_contours.contours.reset();
}
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,76 @@
#ifndef slic3r_GLGizmoCut_hpp_
#define slic3r_GLGizmoCut_hpp_
#include "GLGizmoBase.hpp"
#include "slic3r/GUI/GLModel.hpp"
#include "libslic3r/TriangleMesh.hpp"
#include "libslic3r/ObjectID.hpp"
namespace Slic3r {
namespace GUI {
class GLGizmoCut : public GLGizmoBase
{
static const double Offset;
static const double Margin;
static const std::array<float, 4> GrabberColor;
double m_cut_z{ 0.0 };
double m_max_z{ 0.0 };
double m_start_z{ 0.0 };
Vec3d m_drag_pos;
Vec3d m_drag_center;
bool m_keep_upper{ true };
bool m_keep_lower{ true };
bool m_rotate_lower{ false };
// BBS: m_do_segment
bool m_cut_to_parts {false};
bool m_do_segment{ false };
double m_segment_smoothing_alpha{ 0.5 };
int m_segment_number{ 5 };
struct CutContours
{
TriangleMesh mesh;
GLModel contours;
double cut_z{ 0.0 };
Vec3d position{ Vec3d::Zero() };
Vec3d shift{ Vec3d::Zero() };
ObjectID object_id;
int instance_idx{ -1 };
};
CutContours m_cut_contours;
public:
GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
double get_cut_z() const { return m_cut_z; }
void set_cut_z(double cut_z);
std::string get_tooltip() const override;
protected:
virtual bool on_init() override;
virtual void on_load(cereal::BinaryInputArchive& ar) override { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); }
virtual void on_save(cereal::BinaryOutputArchive& ar) const override { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); }
virtual std::string on_get_name() const override;
virtual void on_set_state() override;
virtual bool on_is_activable() const override;
virtual void on_start_dragging() override;
virtual void on_update(const UpdateData& data) override;
virtual void on_render() override;
virtual void on_render_for_picking() override;
virtual void on_render_input_window(float x, float y, float bottom_limit) override;
private:
void perform_cut(const Selection& selection);
double calc_projection(const Linef3& mouse_ray) const;
BoundingBoxf3 bounding_box() const;
void update_contours();
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLGizmoCut_hpp_

View file

@ -0,0 +1,134 @@
#include "GLGizmoFaceDetector.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/SLA/IndexedMesh.hpp"
#include "libslic3r/FaceDetector.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/ImGuiWrapper.hpp"
#include "slic3r/GUI/Plater.hpp"
#include <GL/glew.h>
#ifdef __WINDOWS__
#include <windows.h>
#include <stdio.h>
#endif
namespace Slic3r {
namespace GUI {
bool GLGizmoFaceDetector::on_init()
{
return true;
}
std::string GLGizmoFaceDetector::on_get_name() const
{
return (_L("Face recognition") + " [P]").ToUTF8().data();
}
void GLGizmoFaceDetector::on_render()
{
if (m_iva.has_VBOs()) {
::glColor4f(0.f, 0.f, 1.f, 0.4f);
m_iva.render();
}
}
void GLGizmoFaceDetector::on_render_input_window(float x, float y, float bottom_limit)
{
#if 0
if (!m_c->selection_info() || !m_c->selection_info()->model_object())
return;
const float approx_height = m_imgui->scaled(14.0f);
y = std::min(y, bottom_limit - approx_height);
//BBS: GUI refactor: move gizmo to the right
#if BBS_TOOLBAR_ON_TOP
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always, 0.5f, 0.0f);
#else
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always, 1.0f, 0.0f);
#endif
ImGuiWrapper::push_toolbar_style();
m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
ImGui::PushItemWidth(m_imgui->get_style_scaling() * 150);
ImGui::InputDouble("Sample interval", &m_sample_interval, 0.0f, 0.0f, "%.2f");
bool btn_clicked = m_imgui->button(_L("Perform Recognition"));
if (btn_clicked) {
perform_recognition(m_parent.get_selection());
}
m_imgui->end();
ImGuiWrapper::pop_toolbar_style();
#endif
}
void GLGizmoFaceDetector::on_set_state()
{
if (get_state() == On) {
m_iva.release_geometry();
display_exterior_face();
}
}
bool GLGizmoFaceDetector::on_is_activable() const
{
const Selection& selection = m_parent.get_selection();
return selection.is_single_full_instance() && !selection.is_wipe_tower();
}
void GLGizmoFaceDetector::perform_recognition(const Selection& selection)
{
ModelObject* mo = m_c->selection_info()->model_object();
//FaceDetector face_detector(mo, m_sample_interval);
//face_detector.detect_exterior_face();
}
void GLGizmoFaceDetector::display_exterior_face()
{
int cnt = 0;
m_iva.release_geometry();
const ModelObjectPtrs& objects = wxGetApp().model().objects;
for (ModelObject* mo : objects) {
const ModelInstance* mi = mo->instances[0];
Transform3d inst_transfo = mi->get_matrix();
for (ModelVolume* mv : mo->volumes) {
TriangleMesh mesh_temp = mv->mesh();
mesh_temp.transform(mv->get_matrix() * inst_transfo);
indexed_triangle_set& mv_its = mesh_temp.its;
for (int facet_idx = 0; facet_idx < mv_its.indices.size(); facet_idx++) {
const stl_triangle_vertex_indices& facet_vert_idxs = mv_its.indices[facet_idx];
if (mv_its.get_property(facet_idx).type != eExteriorAppearance)
continue;
for (int i = 0; i < 3; ++i) {
m_iva.push_geometry(double(mv_its.vertices[facet_vert_idxs[i]](0)),
double(mv_its.vertices[facet_vert_idxs[i]](1)),
double(mv_its.vertices[facet_vert_idxs[i]](2)),
0., 0., 1.);
}
m_iva.push_triangle(cnt, cnt + 1, cnt + 2);
cnt += 3;
}
}
}
m_iva.finalize_geometry(true);
}
CommonGizmosDataID GLGizmoFaceDetector::on_get_requirements() const
{
return CommonGizmosDataID::SelectionInfo;
}
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,39 @@
#ifndef slic3r_GLGizmoFaceDetector_hpp_
#define slic3r_GLGizmoFaceDetector_hpp_
#include "GLGizmoBase.hpp"
#include "slic3r/GUI/3DScene.hpp"
namespace Slic3r {
namespace GUI {
class GLGizmoFaceDetector : public GLGizmoBase
{
public:
GLGizmoFaceDetector(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoBase(parent, icon_filename, sprite_id) {}
protected:
void on_render() override;
void on_render_for_picking() override {}
void on_render_input_window(float x, float y, float bottom_limit) override;
std::string on_get_name() const override;
void on_set_state() override;
bool on_is_activable() const override;
CommonGizmosDataID on_get_requirements() const override;
private:
bool on_init() override;
void perform_recognition(const Selection& selection);
void display_exterior_face();
GLIndexedVertexArray m_iva;
double m_sample_interval = {0.5};
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLGizmoFaceDetector_hpp_

View file

@ -0,0 +1,886 @@
#include "GLGizmoFdmSupports.hpp"
#include "libslic3r/Model.hpp"
//BBS
#include "libslic3r/Layer.hpp"
#include "libslic3r/Thread.hpp"
//#include "slic3r/GUI/3DScene.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/ImGuiWrapper.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/format.hpp"
#include "slic3r/Utils/UndoRedo.hpp"
#include <GL/glew.h>
namespace Slic3r::GUI {
GLGizmoFdmSupports::GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoPainterBase(parent, icon_filename, sprite_id), m_current_tool(ImGui::CircleButtonIcon)
{
m_tool_type = ToolType::BRUSH;
m_cursor_type = TriangleSelector::CursorType::CIRCLE;
}
void GLGizmoFdmSupports::on_shutdown()
{
//BBS
//wait the thread
if (m_thread.joinable()) {
Print *print = m_print_instance.print_object->print();
if (print) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "cancel the print";
print->cancel();
}
//join the thread
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "try to join thread for 2000 ms";
auto ret = m_thread.try_join_for(boost::chrono::milliseconds(2000));
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "join thread returns "<<ret;
}
m_print_instance.print_object = NULL;
m_print_instance.model_instance = NULL;
m_highlight_by_angle_threshold_deg = 0.f;
m_parent.use_slope(false);
m_parent.toggle_model_objects_visibility(true);
}
//BBS: add on_open
void GLGizmoFdmSupports::on_opening()
{
m_angle_threshold_deg = 40;
m_parent.set_slope_normal_angle(90.f - m_angle_threshold_deg);
if (! m_parent.is_using_slope()) {
m_parent.use_slope(true);
m_parent.set_as_dirty();
}
m_print_instance.print_object = NULL;
m_print_instance.model_instance = NULL;
m_edit_state = state_idle;
m_volume_ready = false;
m_volume_valid = false;
}
std::string GLGizmoFdmSupports::on_get_name() const
{
return _u8L("Supports Painting");
}
bool GLGizmoFdmSupports::on_init()
{
// BBS
m_shortcut_key = WXK_NONE;
m_desc["clipping_of_view"] = _L("Section view") + ": ";
m_desc["cursor_size"] = _L("Pen size") + ": ";
m_desc["enforce_caption"] = _L("Left mouse button") + ": ";
m_desc["enforce"] = _L("Enforce supports");
m_desc["block_caption"] = _L("Right mouse button") + ": ";
m_desc["block"] = _L("Block supports");
m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": ";
m_desc["remove"] = _L("Erase painting");
m_desc["remove_all"] = _L("Erase all painting");
m_desc["highlight_by_angle"] = _L("Highlight overhang areas") + ": ";
m_desc["fragment_filter"] = _L("Fragment filter");
m_desc["perform_filter"] = _L("Perform");
m_desc["fragment_area"] = _L("Fragment area");
m_desc["brush_size"] = _L("Set pen size");
m_desc["brush_size_caption"] = _L("Ctrl + Mouse wheel") + ": ";
m_desc["tool_type"] = _L("Tool type");
m_desc["smart_fill_angle"] = _L("Smart fill angle") + ": ";
memset(&m_print_instance, sizeof(m_print_instance), 0);
return true;
}
void GLGizmoFdmSupports::render_painter_gizmo() const
{
const Selection& selection = m_parent.get_selection();
glsafe(::glEnable(GL_BLEND));
glsafe(::glEnable(GL_DEPTH_TEST));
render_triangles(selection);
//BBS: draw support volumes
if (m_volume_ready && m_support_volume && (m_edit_state != state_generating))
{
//m_support_volume->set_render_color();
::glColor4f(0.f, 0.7f, 0.f, 0.7f);
m_support_volume->render();
}
m_c->object_clipper()->render_cut();
m_c->instances_hider()->render_cut();
render_cursor();
glsafe(::glDisable(GL_BLEND));
}
// BBS
void GLGizmoFdmSupports::render_triangles(const Selection& selection) const
{
ClippingPlaneDataWrapper clp_data = this->get_clipping_plane_data();
auto* shader = wxGetApp().get_shader("mm_gouraud");
if (!shader)
return;
shader->start_using();
shader->set_uniform("clipping_plane", clp_data.clp_dataf);
shader->set_uniform("z_range", clp_data.z_range);
ScopeGuard guard([shader]() { if (shader) shader->stop_using(); });
const ModelObject* mo = m_c->selection_info()->model_object();
int mesh_id = -1;
for (const ModelVolume* mv : mo->volumes) {
if (!mv->is_model_part())
continue;
++mesh_id;
const Transform3d trafo_matrix = mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix() * mv->get_matrix();
bool is_left_handed = trafo_matrix.matrix().determinant() < 0.;
if (is_left_handed)
glsafe(::glFrontFace(GL_CW));
glsafe(::glPushMatrix());
glsafe(::glMultMatrixd(trafo_matrix.data()));
float normal_z = -::cos(Geometry::deg2rad(m_highlight_by_angle_threshold_deg));
Matrix3f normal_matrix = static_cast<Matrix3f>(trafo_matrix.matrix().block(0, 0, 3, 3).inverse().transpose().cast<float>());
shader->set_uniform("volume_world_matrix", trafo_matrix);
shader->set_uniform("volume_mirrored", is_left_handed);
shader->set_uniform("slope.actived", m_parent.is_using_slope());
shader->set_uniform("slope.volume_world_normal_matrix", static_cast<Matrix3f>(trafo_matrix.matrix().block(0, 0, 3, 3).inverse().transpose().cast<float>()));
shader->set_uniform("slope.normal_z", normal_z);
m_triangle_selectors[mesh_id]->render(m_imgui);
glsafe(::glPopMatrix());
if (is_left_handed)
glsafe(::glFrontFace(GL_CCW));
}
}
void GLGizmoFdmSupports::on_set_state()
{
GLGizmoPainterBase::on_set_state();
if (get_state() == On) {
m_support_threshold_angle = -1;
}
}
static std::string into_u8(const wxString& str)
{
auto buffer_utf8 = str.utf8_str();
return std::string(buffer_utf8.data());
}
void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_limit)
{
init_print_instance();
if (! m_c->selection_info()->model_object())
return;
// BBS
wchar_t old_tool = m_current_tool;
int support_threshold_angle = get_selection_support_threshold_angle();
// when support painting tool is on, reset highlight threshold angle
if (m_support_threshold_angle == -1) {
m_highlight_by_angle_threshold_deg = support_threshold_angle;
m_parent.set_slope_normal_angle(90.f - m_highlight_by_angle_threshold_deg);
}
m_support_threshold_angle = support_threshold_angle;
const float approx_height = m_imgui->scaled(23.f);
y = std::min(y, bottom_limit - approx_height);
GizmoImguiSetNextWIndowPos(x, y, ImGuiCond_Always, 0.0f, 0.0f);
//BBS
ImGuiWrapper::push_toolbar_style();
GizmoImguiBegin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
// 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 clipping_slider_left = m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x + m_imgui->scaled(1.5f);
const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.5f);
const float fragment_filter_slider_left = m_imgui->calc_text_size(m_desc.at("fragment_filter")).x + m_imgui->scaled(1.5f);
const float highlight_slider_left = m_imgui->calc_text_size(m_desc.at("highlight_by_angle")).x + m_imgui->scaled(1.5f);
const float remove_btn_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.5f);
const float filter_btn_width = m_imgui->calc_text_size(m_desc.at("perform_filter")).x + m_imgui->scaled(1.5f);
const float buttons_width = remove_btn_width + filter_btn_width + m_imgui->scaled(1.5f);
const float empty_button_width = m_imgui->calc_button_size("").x;
const float tips_width = m_imgui->calc_text_size(_L("Auto support threshold angle: ") + " 90 ").x + m_imgui->scaled(1.5f);
const float minimal_slider_width = m_imgui->scaled(4.f);
float slider_width_times = 1.5;
float caption_max = 0.f;
float total_text_max = 0.f;
for (const auto &t : std::array<std::string, 4>{"enforce", "block", "remove", "brush_size"}) {
caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x);
total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x);
}
total_text_max += caption_max + m_imgui->scaled(1.f);
caption_max += m_imgui->scaled(1.f);
const float sliders_left_width = std::max(std::max(cursor_slider_left, clipping_slider_left), std::max(highlight_slider_left, fragment_filter_slider_left));
const float slider_icon_width = m_imgui->get_slider_icon_size().x;
float window_width = minimal_slider_width + sliders_left_width + slider_icon_width;
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
window_width = std::max(window_width, total_text_max);
window_width = std::max(window_width, buttons_width);
window_width = std::max(window_width, tips_width);
float drag_pos_times = 0.7;
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("tool_type"));
std::array<wchar_t, 4> tool_icons = { ImGui::CircleButtonIcon, ImGui::SphereButtonIcon, ImGui::FillButtonIcon, ImGui::FragmentFilterIcon };
std::array<wxString, 4> tool_tips = { _L("Circle"), _L("Sphere"), _L("Fill"), _L("Fragment Filter") };
for (int i = 0; i < tool_icons.size(); i++) {
std::string str_label = std::string("##");
std::wstring btn_name = tool_icons[i] + boost::nowide::widen(str_label);
if (i != 0) ImGui::SameLine((empty_button_width + m_imgui->scaled(1.75f)) * i + m_imgui->scaled(1.3f));
if (m_current_tool == tool_icons[i]) {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.81f, 0.81f, 0.81f, 1.0f)); // r, g, b, a
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.81f, 0.81f, 0.81f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.81f, 0.81f, 0.81f, 1.0f));
}
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0);
bool btn_clicked = ImGui::Button(into_u8(btn_name).c_str());
ImGui::PopStyleVar(1);
if (m_current_tool == tool_icons[i])ImGui::PopStyleColor(3);
if (btn_clicked && m_current_tool != tool_icons[i]) {
m_current_tool = tool_icons[i];
for (auto& triangle_selector : m_triangle_selectors) {
triangle_selector->seed_fill_unselect_all_triangles();
triangle_selector->request_update_render_data();
}
}
if (ImGui::IsItemHovered()) {
m_imgui->tooltip(tool_tips[i], max_tooltip_width);
}
}
if (m_current_tool != old_tool)
this->tool_changed(old_tool, m_current_tool);
ImGui::Dummy(ImVec2(0.0f, ImGui::GetFontSize() * 0.1));
if (m_current_tool == ImGui::CircleButtonIcon) {
m_cursor_type = TriangleSelector::CursorType::CIRCLE;
m_tool_type = ToolType::BRUSH;
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("cursor_size"));
ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
m_imgui->bbl_slider_float_style("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true);
ImGui::SameLine(window_width - drag_pos_times * slider_icon_width);
ImGui::PushItemWidth(1.5 * slider_icon_width);
ImGui::BBLDragFloat("##cursor_radius_input", &m_cursor_radius, 0.05f, 0.0f, 0.0f, "%.2f");
} else if (m_current_tool == ImGui::SphereButtonIcon) {
m_cursor_type = TriangleSelector::CursorType::SPHERE;
m_tool_type = ToolType::BRUSH;
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("cursor_size"));
ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
m_imgui->bbl_slider_float_style("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true);
ImGui::SameLine(window_width - drag_pos_times * slider_icon_width);
ImGui::PushItemWidth(1.5 * slider_icon_width);
ImGui::BBLDragFloat("##cursor_radius_input", &m_cursor_radius, 0.05f, 0.0f, 0.0f, "%.2f");
} else if (m_current_tool == ImGui::FillButtonIcon) {
m_cursor_type = TriangleSelector::CursorType::POINTER;
m_tool_type = ToolType::SMART_FILL;
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("smart_fill_angle"));
std::string format_str = std::string("%.f") + I18N::translate_utf8("", "Face angle threshold, placed after the number with no whitespace in between.");
ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
if (m_imgui->bbl_slider_float_style("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data(), 1.0f, true))
for (auto& triangle_selector : m_triangle_selectors) {
triangle_selector->seed_fill_unselect_all_triangles();
triangle_selector->request_update_render_data();
}
ImGui::SameLine(window_width - drag_pos_times * slider_icon_width);
ImGui::PushItemWidth(1.5 * slider_icon_width);
ImGui::BBLDragFloat("##smart_fill_angle_input", &m_smart_fill_angle, 0.05f, 0.0f, 0.0f, "%.2f");
} else if (m_current_tool == ImGui::FragmentFilterIcon) {
m_tool_type = ToolType::FRAGMENT_FILTER;
m_cursor_type = TriangleSelector::CursorType::POINTER;
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc["fragment_area"] + ":");
ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
std::string format_str = std::string("%.2f") + I18N::translate_utf8("", "Triangle patch area threshold,""triangle patch will be merged to neighbor if its area is less than threshold");
m_imgui->bbl_slider_float_style("##fragment_area", &TriangleSelectorPatch::fragment_area, TriangleSelectorPatch::FragmentAreaMin, TriangleSelectorPatch::FragmentAreaMax, format_str.data(), 1.0f, true);
ImGui::SameLine(window_width - drag_pos_times * slider_icon_width);
ImGui::PushItemWidth(1.5 * slider_icon_width);
ImGui::BBLDragFloat("##fragment_area_input", &TriangleSelectorPatch::fragment_area, 0.05f, 0.0f, 0.0f, "%.2f");
}
float position_before_text_y = ImGui::GetCursorPos().y;
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc["highlight_by_angle"]);
ImGui::AlignTextToFramePadding();
float position_after_text_y = ImGui::GetCursorPos().y;
ImGui::SameLine(sliders_left_width);
float slider_height = m_imgui->get_slider_float_height();
// Makes slider to be aligned to bottom of the multi-line text.
float slider_start_position_y = std::max(position_before_text_y, position_after_text_y - slider_height);
ImGui::SetCursorPosY(slider_start_position_y);
std::string format_str = std::string("%.f");
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
wxString tooltip = _L("Highlight faces according to overhang angle.");
if (m_imgui->bbl_slider_float_style("##angle_threshold_deg", &m_highlight_by_angle_threshold_deg, 0.f, 90.f, format_str.data(), 1.0f, true, tooltip)) {
m_parent.set_slope_normal_angle(90.f - m_highlight_by_angle_threshold_deg);
if (!m_parent.is_using_slope()) {
m_parent.use_slope(true);
m_parent.set_as_dirty();
}
}
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
if (m_support_threshold_angle != 0) {
wxString str_tooltip = (_L("Auto support threshold angle: ") + std::to_string(m_support_threshold_angle));
m_imgui->text_colored(ImGuiWrapper::COL_WINDOW_BG, str_tooltip);
} else {
wxString s_tooltip = (_L("No auto support"));
m_imgui->text_colored(ImGuiWrapper::COL_WINDOW_BG, s_tooltip);
}
ImGui::EndTooltip();
}
ImGui::SameLine(window_width - drag_pos_times * slider_icon_width);
ImGui::PushItemWidth(1.5 * slider_icon_width);
ImGui::BBLDragFloat("##angle_threshold_deg_input", &m_highlight_by_angle_threshold_deg, 0.05f, 0.0f, 0.0f, "%.2f");
if (m_current_tool != ImGui::FragmentFilterIcon) {
ImGui::Separator();
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("clipping_of_view"));
auto clp_dist = float(m_c->object_clipper()->get_position());
ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
bool b_bbl_slider_float = m_imgui->bbl_slider_float_style("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true);
ImGui::SameLine(window_width - drag_pos_times * slider_icon_width);
ImGui::PushItemWidth(1.5 * slider_icon_width);
bool b_drag_input = ImGui::BBLDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f");
if (b_bbl_slider_float || b_drag_input) m_c->object_clipper()->set_position(clp_dist, true);
}
ImGui::Separator();
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f));
float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y;
show_tooltip_information(caption_max, x, get_cur_y);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 5.0f));
ImGui::SameLine();
if (m_imgui->button(m_desc.at("perform_filter"))) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Reset selection", UndoRedo::SnapshotType::GizmoAction);
for (int i = 0; i < m_triangle_selectors.size(); i++) {
TriangleSelectorPatch *ts_mm = dynamic_cast<TriangleSelectorPatch *>(m_triangle_selectors[i].get());
ts_mm->update_selector_triangles();
ts_mm->request_update_render_data(true);
}
update_model_object();
m_parent.set_as_dirty();
}
ImGui::SameLine();
if (m_imgui->button(m_desc.at("remove_all"))) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Reset selection", UndoRedo::SnapshotType::GizmoAction);
ModelObject * mo = m_c->selection_info()->model_object();
int idx = -1;
for (ModelVolume *mv : mo->volumes)
if (mv->is_model_part()) {
++idx;
m_triangle_selectors[idx]->reset();
m_triangle_selectors[idx]->request_update_render_data(true);
}
update_model_object();
m_parent.set_as_dirty();
}
ImGui::PopStyleVar(2);
GizmoImguiEnd();
// BBS
ImGuiWrapper::pop_toolbar_style();
}
void GLGizmoFdmSupports::tool_changed(wchar_t old_tool, wchar_t new_tool)
{
if ((old_tool == ImGui::FragmentFilterIcon && new_tool == ImGui::FragmentFilterIcon) ||
(old_tool != ImGui::FragmentFilterIcon && new_tool != ImGui::FragmentFilterIcon))
return;
for (auto& selector_ptr : m_triangle_selectors) {
TriangleSelectorPatch* tsp = dynamic_cast<TriangleSelectorPatch*>(selector_ptr.get());
tsp->set_filter_state(new_tool == ImGui::FragmentFilterIcon);
}
}
void GLGizmoFdmSupports::show_tooltip_information(float caption_max, float x, float y)
{
ImTextureID normal_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP);
ImTextureID hover_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER);
float font_size = ImGui::GetFontSize();
ImVec2 button_size = ImVec2(font_size * 1.8, font_size * 1.3);
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
ImGui::ImageButton3(normal_id, hover_id, button_size);
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip2(ImVec2(x, y));
auto draw_text_with_caption = [this, &caption_max](const wxString &caption, const wxString &text) {
// BBS
m_imgui->text_colored(ImGuiWrapper::COL_ACTIVE, caption);
ImGui::SameLine(caption_max);
m_imgui->text_colored(ImGuiWrapper::COL_WINDOW_BG, text);
};
for (const auto &t : std::array<std::string, 4>{"enforce", "block", "remove", "brush_size"}) draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t));
ImGui::EndTooltip();
}
ImGui::PopStyleVar(1);
}
// BBS
int GLGizmoFdmSupports::get_selection_support_threshold_angle()
{
auto sel_info = m_c->selection_info();
if (sel_info == nullptr)
return -1;
const DynamicPrintConfig& obj_cfg = sel_info->model_object()->config.get();
const DynamicPrintConfig& glb_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config;
bool enable_support = obj_cfg.option("enable_support") ? obj_cfg.opt_bool("enable_support") : glb_cfg.opt_bool("enable_support");
SupportType support_type = obj_cfg.option("support_type") ? obj_cfg.opt_enum<SupportType>("support_type") : glb_cfg.opt_enum<SupportType>("support_type");
int support_threshold_angle = obj_cfg.option("support_threshold_angle") ? obj_cfg.opt_int("support_threshold_angle") : glb_cfg.opt_int("support_threshold_angle");
bool auto_support = enable_support &&
(support_type == SupportType::stHybridAuto ||
support_type == SupportType::stTreeAuto ||
support_type == SupportType::stNormalAuto);
return auto_support ? support_threshold_angle : 0;
}
void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block)
{
float threshold = (float(M_PI)/180.f)*threshold_deg;
const Selection& selection = m_parent.get_selection();
const ModelObject* mo = m_c->selection_info()->model_object();
const ModelInstance* mi = mo->instances[selection.get_instance_idx()];
int mesh_id = -1;
for (const ModelVolume* mv : mo->volumes) {
if (! mv->is_model_part())
continue;
++mesh_id;
const Transform3d trafo_matrix = mi->get_matrix(true) * mv->get_matrix(true);
Vec3f down = (trafo_matrix.inverse() * (-Vec3d::UnitZ())).cast<float>().normalized();
Vec3f limit = (trafo_matrix.inverse() * Vec3d(std::sin(threshold), 0, -std::cos(threshold))).cast<float>().normalized();
float dot_limit = limit.dot(down);
// Now calculate dot product of vert_direction and facets' normals.
int idx = 0;
const indexed_triangle_set &its = mv->mesh().its;
for (const stl_triangle_vertex_indices &face : its.indices) {
if (its_face_normal(its, face).dot(down) > dot_limit) {
m_triangle_selectors[mesh_id]->set_facet(idx, block ? EnforcerBlockerType::BLOCKER : EnforcerBlockerType::ENFORCER);
m_triangle_selectors.back()->request_update_render_data();
}
++ idx;
}
}
Plater::TakeSnapshot snapshot(wxGetApp().plater(), block ? "Block supports by angle"
: "Add supports by angle");
update_model_object();
m_parent.set_as_dirty();
}
//BBS: remove const
void GLGizmoFdmSupports::update_model_object()
{
bool updated = false;
ModelObject* mo = m_c->selection_info()->model_object();
int idx = -1;
for (ModelVolume* mv : mo->volumes) {
if (! mv->is_model_part())
continue;
++idx;
updated |= mv->supported_facets.set(*m_triangle_selectors[idx].get());
}
if (updated) {
const ModelObjectPtrs& mos = wxGetApp().model().objects;
wxGetApp().obj_list()->update_info_items(std::find(mos.begin(), mos.end(), mo) - mos.begin());
// BBS: backup
Slic3r::save_object_mesh(*mo);
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
}
//BBS: invalid volume_support status
invalid_support_volumes(true);
}
//BBS
void GLGizmoFdmSupports::update_from_model_object(bool first_update)
{
wxBusyCursor wait;
const ModelObject* mo = m_c->selection_info()->model_object();
m_triangle_selectors.clear();
//BBS: add timestamp logic
m_volume_timestamps.clear();
int volume_id = -1;
std::vector<std::array<float, 4>> ebt_colors;
ebt_colors.push_back(GLVolume::NEUTRAL_COLOR);
ebt_colors.push_back(TriangleSelectorGUI::enforcers_color);
ebt_colors.push_back(TriangleSelectorGUI::blockers_color);
for (const ModelVolume* mv : mo->volumes) {
if (! mv->is_model_part())
continue;
++volume_id;
// This mesh does not account for the possible Z up SLA offset.
const TriangleMesh* mesh = &mv->mesh();
m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorPatch>(*mesh, ebt_colors));
// Reset of TriangleSelector is done inside TriangleSelectorGUI's constructor, so we don't need it to perform it again in deserialize().
m_triangle_selectors.back()->deserialize(mv->supported_facets.get_data(), false);
m_triangle_selectors.back()->request_update_render_data();
//BBS: add timestamp logic
m_volume_timestamps.emplace_back(mv->supported_facets.timestamp());
}
//BBS: invalid volume_support status
invalid_support_volumes(true);
}
PainterGizmoType GLGizmoFdmSupports::get_painter_type() const
{
return PainterGizmoType::FDM_SUPPORTS;
}
wxString GLGizmoFdmSupports::handle_snapshot_action_name(bool shift_down, GLGizmoPainterBase::Button button_down) const
{
// BBS remove _L()
wxString action_name;
if (shift_down)
action_name = ("Unselect all");
else {
if (button_down == Button::Left)
action_name = ("Enforce supports");
else
action_name = ("Block supports");
}
return action_name;
}
//BBS
void GLGizmoFdmSupports::init_print_instance()
{
const PrintObject* print_object = NULL;
PrintInstance print_instance = { 0 };
const Print *print = m_parent.fff_print();
if (!m_c->selection_info() || (m_print_instance.print_object))
{
//no selection or already got a print instance before
return;
}
const ModelObject* model_object = m_c->selection_info()->model_object();
int instance_index = m_c->selection_info()->get_active_instance();
const ModelInstance* model_instance = model_object->instances[instance_index];
//check the print
if (!print)
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ",print invalid\n";
return;
}
for (const PrintObject* object : print->objects())
{
if (object->model_object()->id() == model_object->id())
{
BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << ",found a PrintObject, id is" << model_object->id().id;
print_object = object;
break;
}
}
//check the pring object
if (!print_object)
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ",can not find a PrintObject\n";
return;
}
//find the print instance
for (const PrintInstance &instance : print_object->instances())
{
if (instance.model_instance->id() == model_instance->id())
{
BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << ",found a PrintInstance, id is" << model_instance->id().id;
m_print_instance = instance;
break;
}
}
//check the pring object
if (!m_print_instance.print_object)
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ",can not find a PrintInstance\n";
return;
}
const PrintObjectConfig& config = m_print_instance.print_object->config();
m_angle_threshold_deg = config.support_angle;
m_is_tree_support = config.enable_support.value &&
(config.support_type.value == stTreeAuto || config.support_type.value == stTree || config.support_type.value==stHybridAuto);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ",get support_angle "<< m_angle_threshold_deg<<", is_tree "<<m_is_tree_support;
return;
}
void GLGizmoFdmSupports::invalid_support_volumes(bool invalid_step)
{
std::unique_lock<std::mutex> lck(m_mutex);
m_volume_valid = false;
if ((invalid_step) && (m_edit_state == state_generating) && m_print_instance.print_object)
{
Print *print = m_print_instance.print_object->print();
if (print) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "cancel the print";
print->cancel();
}
}
m_edit_state = state_idle;
lck.unlock();
return;
}
bool GLGizmoFdmSupports::need_regenerate_support_volumes()
{
if (!m_support_volume)
return true;
const ModelObject* mo = m_c->selection_info()->model_object();
if (m_object_id != m_print_instance.print_object->id().id)
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ",object_id changed from " << m_object_id << " to " << m_print_instance.print_object->id().id << ", need to regenerate";
return true;
}
int volume_id = -1;
for (const ModelVolume* mv : mo->volumes) {
if (! mv->is_model_part())
continue;
++volume_id;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ",volume_id "<<volume_id<<", record_timestamp "<< m_volume_timestamps[volume_id]
<<", current_timestamp "<<mv->supported_facets.timestamp();
if (m_volume_timestamps[volume_id] != mv->supported_facets.timestamp())
{
return true;
}
}
return false;
}
void GLGizmoFdmSupports::update_support_volumes()
{
//PrintInstance m_print_instance = get_current_print_instance();
if ((!m_print_instance.print_object))
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ",invalid param, m_volume_ready="<< m_volume_ready;
return;
}
if (m_volume_valid || !need_regenerate_support_volumes())
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ",no need to regenerate support volume, return directly";
std::unique_lock<std::mutex> lck(m_mutex);
m_volume_ready = true;
m_volume_valid = true;
m_edit_state = state_ready;
lck.unlock();
return;
}
//generate_support_preview in async mode
std::unique_lock<std::mutex> lck(m_mutex);
m_volume_ready = false;
//destroy previous support volume
if (m_support_volume)
{
delete m_support_volume;
m_support_volume = NULL;
}
lck.unlock();
if (m_thread.joinable()) {
//join the thread in ui thread
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "try to join thread for 100 ms";
auto ret = m_thread.try_join_for(boost::chrono::milliseconds(100));
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "join thread returns "<<ret;
}
m_cancel = false;
m_thread = create_thread([this]{this->run_thread();});
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ",created thread to generate support volumes";
return;
}
void GLGizmoFdmSupports::run_thread()
{
try {
Print *print = m_print_instance.print_object->print();
print->restart();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ",before generate_support_preview";
m_print_instance.print_object->generate_support_preview();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ",after generate_support_preview";
if (m_cancel)
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", cancelled";
goto _finished;
}
std::unique_lock<std::mutex> lck(m_mutex);
m_support_volume = new GLVolume(0.5f, 0.5f, 0.5f, 0.5f);
//m_support_volume->is_support_part = true;
m_support_volume->force_native_color = true;
m_support_volume->set_render_color();
lck.unlock();
auto record_timestamp = [this]()
{
const ModelObject* mo = m_c->selection_info()->model_object();
int volume_id = -1;
for (const ModelVolume* mv : mo->volumes) {
if (!mv->is_model_part())
continue;
++volume_id;
m_volume_timestamps[volume_id] = mv->supported_facets.timestamp();
}
};
if (m_cancel)
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", cancelled";
goto _finished;
}
if (m_is_tree_support)
{
if (!m_print_instance.print_object->tree_support_layers().size())
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ",no tree support layer found, update status to 100%\n";
print->set_status(100, L("Support Generated"));
goto _finished;
}
for (const TreeSupportLayer *support_layer : m_print_instance.print_object->tree_support_layers())
{
for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities)
{
_3DScene::extrusionentity_to_verts(extrusion_entity, float(support_layer->print_z), m_print_instance.shift, *m_support_volume);
}
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished extrusionentity_to_verts, update status to 100%";
print->set_status(100, L("Support Generated"));
}
else
{
if (!m_print_instance.print_object->support_layers().size())
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ",no support layer found, update status to 100%\n";
print->set_status(100, L("Support Generated"));
goto _finished;
}
for (const SupportLayer *support_layer : m_print_instance.print_object->support_layers())
{
for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities)
{
_3DScene::extrusionentity_to_verts(extrusion_entity, float(support_layer->print_z), m_print_instance.shift, *m_support_volume);
}
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished extrusionentity_to_verts, update status to 100%";
print->set_status(100, L("Support Generated"));
}
record_timestamp();
}
catch (...) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ",exception catched, mostly cancelling from gui!";
//wxTheApp->OnUnhandledException();
}
_finished:
std::unique_lock<std::mutex> lck(m_mutex);
if (m_edit_state == state_generating)
m_edit_state = state_ready;
lck.unlock();
m_parent.set_as_dirty();
m_parent.post_event(SimpleEvent(wxEVT_PAINT));
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished all";
return;
}
void GLGizmoFdmSupports::generate_support_volume()
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ",before finalize_geometry";
m_support_volume->indexed_vertex_array.finalize_geometry(m_parent.is_initialized());
std::unique_lock<std::mutex> lck(m_mutex);
m_volume_ready = true;
m_volume_valid = true;
m_object_id = m_print_instance.print_object->id().id;
lck.unlock();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ",finished finalize_geometry";
}
} // namespace Slic3r::GUI

View file

@ -0,0 +1,98 @@
#ifndef slic3r_GLGizmoFdmSupports_hpp_
#define slic3r_GLGizmoFdmSupports_hpp_
#include "GLGizmoPainterBase.hpp"
//BBS
#include "libslic3r/Print.hpp"
#include "libslic3r/ObjectID.hpp"
#include <boost/thread.hpp>
namespace Slic3r::GUI {
class GLGizmoFdmSupports : public GLGizmoPainterBase
{
public:
GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
void render_painter_gizmo() const override;
//BBS: add edit state
enum EditState {
state_idle = 0,
state_generating = 1,
state_ready
};
protected:
void on_render_input_window(float x, float y, float bottom_limit) override;
std::string on_get_name() const override;
// BBS
void render_triangles(const Selection& selection) const override;
void on_set_state() override;
void show_tooltip_information(float caption_max, float x, float y);
wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override;
std::string get_gizmo_entering_text() const override { return _u8L("Entering Paint-on supports"); }
std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Paint-on supports"); }
std::string get_action_snapshot_name() override { return _u8L("Paint-on supports editing"); }
// BBS
wchar_t m_current_tool = 0;
private:
bool on_init() override;
//BBS: remove const
void update_model_object() override;
//BBS: add logic to distinguish the first_time_update and later_update
void update_from_model_object(bool first_update) override;
void tool_changed(wchar_t old_tool, wchar_t new_tool);
void on_opening() override;
void on_shutdown() override;
PainterGizmoType get_painter_type() const override;
void select_facets_by_angle(float threshold, bool block);
// BBS
int get_selection_support_threshold_angle();
int m_support_threshold_angle = -1;
//BBS: add support preview logic
void init_print_instance();
void update_support_volumes();
void invalid_support_volumes(bool invalid_step = false);
bool need_regenerate_support_volumes();
void generate_support_volume();
void run_thread();
float m_angle_threshold_deg = 40.f;
bool m_volume_valid = false;
GLVolume *m_support_volume = NULL;
mutable bool m_volume_ready = false;
bool m_is_tree_support = false;
bool m_cancel = false;
size_t m_object_id;
std::vector<ObjectBase::Timestamp> m_volume_timestamps;
PrintInstance m_print_instance;
mutable EditState m_edit_state;
//thread
boost::thread m_thread;
// Mutex and condition variable to synchronize m_thread with the UI thread.
std::mutex m_mutex;
int m_generate_count;
// This map holds all translated description texts, so they can be easily referenced during layout calculations
// etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.
std::map<std::string, wxString> m_desc;
};
} // namespace Slic3r::GUI
#endif // slic3r_GLGizmoFdmSupports_hpp_

View file

@ -0,0 +1,388 @@
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
#include "GLGizmoFlatten.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp"
#include "libslic3r/Geometry/ConvexHull.hpp"
#include "libslic3r/Model.hpp"
#include <numeric>
#include <GL/glew.h>
namespace Slic3r {
namespace GUI {
static double g_normal_precise = 0.0015;
GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoBase(parent, icon_filename, sprite_id)
, m_normal(Vec3d::Zero())
, m_starting_center(Vec3d::Zero())
{
}
bool GLGizmoFlatten::on_init()
{
// BBS
m_shortcut_key = WXK_NONE;
return true;
}
void GLGizmoFlatten::on_set_state()
{
}
CommonGizmosDataID GLGizmoFlatten::on_get_requirements() const
{
return CommonGizmosDataID::SelectionInfo;
}
std::string GLGizmoFlatten::on_get_name() const
{
return _u8L("Lay on face");
}
bool GLGizmoFlatten::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();
}
void GLGizmoFlatten::on_start_dragging()
{
if (m_hover_id != -1) {
assert(m_planes_valid);
m_normal = m_planes[m_hover_id].normal;
m_starting_center = m_parent.get_selection().get_bounding_box().center();
}
}
void GLGizmoFlatten::on_render()
{
const Selection& selection = m_parent.get_selection();
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
glsafe(::glEnable(GL_DEPTH_TEST));
glsafe(::glEnable(GL_BLEND));
if (selection.is_single_full_instance()) {
const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix();
glsafe(::glPushMatrix());
glsafe(::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z()));
glsafe(::glMultMatrixd(m.data()));
if (this->is_plane_update_necessary())
update_planes();
for (int i = 0; i < (int)m_planes.size(); ++i) {
if (i == m_hover_id)
glsafe(::glColor4fv(GLGizmoBase::FLATTEN_HOVER_COLOR.data()));
else
glsafe(::glColor4fv(GLGizmoBase::FLATTEN_COLOR.data()));
if (m_planes[i].vbo.has_VBOs())
m_planes[i].vbo.render();
}
glsafe(::glPopMatrix());
}
glsafe(::glEnable(GL_CULL_FACE));
glsafe(::glDisable(GL_BLEND));
}
void GLGizmoFlatten::on_render_for_picking()
{
const Selection& selection = m_parent.get_selection();
glsafe(::glDisable(GL_DEPTH_TEST));
glsafe(::glDisable(GL_BLEND));
if (selection.is_single_full_instance() && !wxGetKeyState(WXK_CONTROL)) {
const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix();
glsafe(::glPushMatrix());
glsafe(::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z()));
glsafe(::glMultMatrixd(m.data()));
if (this->is_plane_update_necessary())
update_planes();
for (int i = 0; i < (int)m_planes.size(); ++i) {
glsafe(::glColor4fv(picking_color_component(i).data()));
m_planes[i].vbo.render();
}
glsafe(::glPopMatrix());
}
glsafe(::glEnable(GL_CULL_FACE));
}
void GLGizmoFlatten::set_flattening_data(const ModelObject* model_object)
{
m_starting_center = Vec3d::Zero();
if (model_object != m_old_model_object) {
m_planes.clear();
m_planes_valid = false;
}
}
void GLGizmoFlatten::update_planes()
{
const ModelObject* mo = m_c->selection_info()->model_object();
TriangleMesh ch;
for (const ModelVolume* vol : mo->volumes) {
if (vol->type() != ModelVolumeType::MODEL_PART)
continue;
TriangleMesh vol_ch = vol->get_convex_hull();
vol_ch.transform(vol->get_matrix());
ch.merge(vol_ch);
}
ch = ch.convex_hull_3d();
m_planes.clear();
const Transform3d& inst_matrix = mo->instances.front()->get_matrix(true);
// Following constants are used for discarding too small polygons.
const float minimal_area = 4.f; // in square mm (world coordinates)
const float minimal_side = 2.f; // mm
// Now we'll go through all the facets and append Points of facets sharing the same normal.
// This part is still performed in mesh coordinate system.
const int num_of_facets = ch.facets_count();
const std::vector<Vec3f> face_normals = its_face_normals(ch.its);
const std::vector<Vec3i> face_neighbors = its_face_neighbors(ch.its);
std::vector<int> facet_queue(num_of_facets, 0);
std::vector<bool> facet_visited(num_of_facets, false);
int facet_queue_cnt = 0;
const stl_normal* normal_ptr = nullptr;
while (1) {
// Find next unvisited triangle:
int facet_idx = 0;
for (; facet_idx < num_of_facets; ++ facet_idx)
if (!facet_visited[facet_idx]) {
facet_queue[facet_queue_cnt ++] = facet_idx;
facet_visited[facet_idx] = true;
normal_ptr = &face_normals[facet_idx];
m_planes.emplace_back();
break;
}
if (facet_idx == num_of_facets)
break; // Everything was visited already
while (facet_queue_cnt > 0) {
int facet_idx = facet_queue[-- facet_queue_cnt];
const stl_normal& this_normal = face_normals[facet_idx];
if (std::abs(this_normal(0) - (*normal_ptr)(0)) <= g_normal_precise && std::abs(this_normal(1) - (*normal_ptr)(1)) <= g_normal_precise && std::abs(this_normal(2) - (*normal_ptr)(2)) <= g_normal_precise) {
const Vec3i face = ch.its.indices[facet_idx];
for (int j=0; j<3; ++j)
m_planes.back().vertices.emplace_back(ch.its.vertices[face[j]].cast<double>());
facet_visited[facet_idx] = true;
for (int j = 0; j < 3; ++ j)
if (int neighbor_idx = face_neighbors[facet_idx][j]; neighbor_idx >= 0 && ! facet_visited[neighbor_idx])
facet_queue[facet_queue_cnt ++] = neighbor_idx;
}
}
m_planes.back().normal = normal_ptr->cast<double>();
Pointf3s& verts = m_planes.back().vertices;
// Now we'll transform all the points into world coordinates, so that the areas, angles and distances
// make real sense.
verts = transform(verts, inst_matrix);
// if this is a just a very small triangle, remove it to speed up further calculations (it would be rejected later anyway):
if (verts.size() == 3 &&
((verts[0] - verts[1]).norm() < minimal_side
|| (verts[0] - verts[2]).norm() < minimal_side
|| (verts[1] - verts[2]).norm() < minimal_side))
m_planes.pop_back();
}
// Let's prepare transformation of the normal vector from mesh to instance coordinates.
Geometry::Transformation t(inst_matrix);
Vec3d scaling = t.get_scaling_factor();
t.set_scaling_factor(Vec3d(1./scaling(0), 1./scaling(1), 1./scaling(2)));
// Now we'll go through all the polygons, transform the points into xy plane to process them:
for (unsigned int polygon_id=0; polygon_id < m_planes.size(); ++polygon_id) {
Pointf3s& polygon = m_planes[polygon_id].vertices;
const Vec3d& normal = m_planes[polygon_id].normal;
// transform the normal according to the instance matrix:
Vec3d normal_transformed = t.get_matrix() * normal;
// We are going to rotate about z and y to flatten the plane
Eigen::Quaterniond q;
Transform3d m = Transform3d::Identity();
m.matrix().block(0, 0, 3, 3) = q.setFromTwoVectors(normal_transformed, Vec3d::UnitZ()).toRotationMatrix();
polygon = transform(polygon, m);
// Now to remove the inner points. We'll misuse Geometry::convex_hull for that, but since
// it works in fixed point representation, we will rescale the polygon to avoid overflows.
// And yes, it is a nasty thing to do. Whoever has time is free to refactor.
Vec3d bb_size = BoundingBoxf3(polygon).size();
float sf = std::min(1./bb_size(0), 1./bb_size(1));
Transform3d tr = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d(sf, sf, 1.f));
polygon = transform(polygon, tr);
polygon = Slic3r::Geometry::convex_hull(polygon);
polygon = transform(polygon, tr.inverse());
// Calculate area of the polygons and discard ones that are too small
float& area = m_planes[polygon_id].area;
area = 0.f;
for (unsigned int i = 0; i < polygon.size(); i++) // Shoelace formula
area += polygon[i](0)*polygon[i + 1 < polygon.size() ? i + 1 : 0](1) - polygon[i + 1 < polygon.size() ? i + 1 : 0](0)*polygon[i](1);
area = 0.5f * std::abs(area);
bool discard = false;
if (area < minimal_area)
discard = true;
else {
// We also check the inner angles and discard polygons with angles smaller than the following threshold
const double angle_threshold = ::cos(9.0 * (double)PI / 180.0);
int count = 0, side_count = polygon.size();
for (unsigned int i = 0; i < side_count; ++i) {
const Vec3d& prec = polygon[(i == 0) ? polygon.size() - 1 : i - 1];
const Vec3d& curr = polygon[i];
const Vec3d& next = polygon[(i == polygon.size() - 1) ? 0 : i + 1];
double len = (next - curr).norm();
if (len >= minimal_side)
count ++;
if ((prec - curr).normalized().dot((next - curr).normalized()) > angle_threshold) {
discard = true;
break;
}
}
if (!discard
//&& ((side_count == 3) || (side_count == 5))
&& (side_count <= 5)
&& (count <= 2)) {
discard = true;
}
}
if (discard) {
m_planes.erase(m_planes.begin() + (polygon_id--));
continue;
}
// We will shrink the polygon a little bit so it does not touch the object edges:
Vec3d centroid = std::accumulate(polygon.begin(), polygon.end(), Vec3d(0.0, 0.0, 0.0));
centroid /= (double)polygon.size();
for (auto& vertex : polygon)
vertex = 0.95f*vertex + 0.05f*centroid;
// Polygon is now simple and convex, we'll round the corners to make them look nicer.
// The algorithm takes a vertex, calculates middles of respective sides and moves the vertex
// towards their average (controlled by 'aggressivity'). This is repeated k times.
// In next iterations, the neighbours are not always taken at the middle (to increase the
// rounding effect at the corners, where we need it most).
/*const unsigned int k = 10; // number of iterations
const float aggressivity = 0.2f; // agressivity
const unsigned int N = polygon.size();
std::vector<std::pair<unsigned int, unsigned int>> neighbours;
if (k != 0) {
Pointf3s points_out(2*k*N); // vector long enough to store the future vertices
for (unsigned int j=0; j<N; ++j) {
points_out[j*2*k] = polygon[j];
neighbours.push_back(std::make_pair((int)(j*2*k-k) < 0 ? (N-1)*2*k+k : j*2*k-k, j*2*k+k));
}
for (unsigned int i=0; i<k; ++i) {
// Calculate middle of each edge so that neighbours points to something useful:
for (unsigned int j=0; j<N; ++j)
if (i==0)
points_out[j*2*k+k] = 0.5f * (points_out[j*2*k] + points_out[j==N-1 ? 0 : (j+1)*2*k]);
else {
float r = 0.2+0.3/(k-1)*i; // the neighbours are not always taken in the middle
points_out[neighbours[j].first] = r*points_out[j*2*k] + (1-r) * points_out[neighbours[j].first-1];
points_out[neighbours[j].second] = r*points_out[j*2*k] + (1-r) * points_out[neighbours[j].second+1];
}
// Now we have a triangle and valid neighbours, we can do an iteration:
for (unsigned int j=0; j<N; ++j)
points_out[2*k*j] = (1-aggressivity) * points_out[2*k*j] +
aggressivity*0.5f*(points_out[neighbours[j].first] + points_out[neighbours[j].second]);
for (auto& n : neighbours) {
++n.first;
--n.second;
}
}
polygon = points_out; // replace the coarse polygon with the smooth one that we just created
}*/
// Raise a bit above the object surface to avoid flickering:
for (auto& b : polygon)
b(2) += 0.1f;
// Transform back to 3D (and also back to mesh coordinates)
polygon = transform(polygon, inst_matrix.inverse() * m.inverse());
}
// We'll sort the planes by area and only keep the 254 largest ones (because of the picking pass limitations):
std::sort(m_planes.rbegin(), m_planes.rend(), [](const PlaneData& a, const PlaneData& b) { return a.area < b.area; });
m_planes.resize(std::min((int)m_planes.size(), 512));
// Planes are finished - let's save what we calculated it from:
m_volumes_matrices.clear();
m_volumes_types.clear();
for (const ModelVolume* vol : mo->volumes) {
m_volumes_matrices.push_back(vol->get_matrix());
m_volumes_types.push_back(vol->type());
}
m_first_instance_scale = mo->instances.front()->get_scaling_factor();
m_first_instance_mirror = mo->instances.front()->get_mirror();
m_old_model_object = mo;
// And finally create respective VBOs. The polygon is convex with
// the vertices in order, so triangulation is trivial.
for (auto& plane : m_planes) {
plane.vbo.reserve(plane.vertices.size());
for (const auto& vert : plane.vertices)
plane.vbo.push_geometry(vert, plane.normal);
for (size_t i=1; i<plane.vertices.size()-1; ++i)
plane.vbo.push_triangle(0, i, i+1); // triangle fan
plane.vbo.finalize_geometry(true);
// FIXME: vertices should really be local, they need not
// persist now when we use VBOs
plane.vertices.clear();
plane.vertices.shrink_to_fit();
}
m_planes_valid = true;
}
bool GLGizmoFlatten::is_plane_update_necessary() const
{
const ModelObject* mo = m_c->selection_info()->model_object();
if (m_state != On || ! mo || mo->instances.empty())
return false;
if (! m_planes_valid || mo != m_old_model_object
|| mo->volumes.size() != m_volumes_matrices.size())
return true;
// We want to recalculate when the scale changes - some planes could (dis)appear.
if (! mo->instances.front()->get_scaling_factor().isApprox(m_first_instance_scale)
|| ! mo->instances.front()->get_mirror().isApprox(m_first_instance_mirror))
return true;
for (unsigned int i=0; i < mo->volumes.size(); ++i)
if (! mo->volumes[i]->get_matrix().isApprox(m_volumes_matrices[i])
|| mo->volumes[i]->type() != m_volumes_types[i])
return true;
return false;
}
Vec3d GLGizmoFlatten::get_flattening_normal() const
{
Vec3d out = m_normal;
m_normal = Vec3d::Zero();
m_starting_center = Vec3d::Zero();
return out;
}
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,65 @@
#ifndef slic3r_GLGizmoFlatten_hpp_
#define slic3r_GLGizmoFlatten_hpp_
#include "GLGizmoBase.hpp"
#include "slic3r/GUI/3DScene.hpp"
namespace Slic3r {
enum class ModelVolumeType : int;
namespace GUI {
class GLGizmoFlatten : public GLGizmoBase
{
// This gizmo does not use grabbers. The m_hover_id relates to polygon managed by the class itself.
private:
mutable Vec3d m_normal;
struct PlaneData {
std::vector<Vec3d> vertices; // should be in fact local in update_planes()
GLIndexedVertexArray vbo;
Vec3d normal;
float area;
};
// This holds information to decide whether recalculation is necessary:
std::vector<Transform3d> m_volumes_matrices;
std::vector<ModelVolumeType> m_volumes_types;
Vec3d m_first_instance_scale;
Vec3d m_first_instance_mirror;
std::vector<PlaneData> m_planes;
bool m_planes_valid = false;
mutable Vec3d m_starting_center;
const ModelObject* m_old_model_object = nullptr;
std::vector<const Transform3d*> instances_matrices;
void update_planes();
bool is_plane_update_necessary() const;
public:
GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
void set_flattening_data(const ModelObject* model_object);
Vec3d get_flattening_normal() const;
protected:
virtual bool on_init() override;
virtual std::string on_get_name() const override;
virtual bool on_is_activable() const override;
virtual void on_start_dragging() override;
virtual void on_render() override;
virtual void on_render_for_picking() override;
virtual void on_set_state() override;
virtual CommonGizmosDataID on_get_requirements() const override;
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLGizmoFlatten_hpp_

View file

@ -0,0 +1,901 @@
#include "GLGizmoHollow.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/Camera.hpp"
#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp"
#include <GL/glew.h>
#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(its_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<float, 4> 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<double>()))
continue;
// First decide about the color of the point.
if (picking) {
std::array<float, 4> 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<GLModel*>(&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<double>());
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<Vec3f, Vec3f>& 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<Vec3f, Vec3f> pos_and_normal;
if (unproject_on_mesh(mouse_position, pos_and_normal)) { // we got an intersection
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "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<Vec3d> points;
for (unsigned int i=0; i<mo->sla_drain_holes.size(); ++i)
points.push_back(trafo.get_matrix() * mo->sla_drain_holes[i].pos.cast<double>());
// Now ask the rectangle which of the points are inside.
std::vector<Vec3f> points_inside;
std::vector<unsigned int> points_idxs = m_selection_rectangle.stop_dragging(m_parent, points);
for (size_t idx : points_idxs)
points_inside.push_back(points[idx].cast<float>());
// 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(), "Delete drainage hole");
sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes;
for (unsigned int idx=0; idx<drain_holes.size(); ++idx) {
if (m_selected[idx]) {
m_selected.erase(m_selected.begin()+idx);
drain_holes.erase(drain_holes.begin() + (idx--));
}
}
select_point(NoPoints);
}
void GLGizmoHollow::on_update(const UpdateData& data)
{
sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes;
if (m_hover_id != -1) {
std::pair<Vec3f, Vec3f> pos_and_normal;
if (! unproject_on_mesh(data.mouse_pos.cast<double>(), 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<std::pair<const ConfigOption*, const ConfigOptionDef*>>
GLGizmoHollow::get_config_options(const std::vector<std::string>& keys) const
{
std::vector<std::pair<const ConfigOption*, const ConfigOptionDef*>> 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<DynamicPrintConfig> 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<std::string> opts_keys = {"hollowing_min_thickness", "hollowing_quality", "hollowing_closing_distance"};
auto opts = get_config_options(opts_keys);
auto* offset_cfg = static_cast<const ConfigOptionFloat*>(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<const ConfigOptionFloat*>(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<const ConfigOptionFloat*>(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(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 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(0.5f);
const float settings_sliders_left =
std::max(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(0.5f), clipping_slider_left);
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);
const float button_preview_width = m_imgui->calc_button_size(m_desc.at("preview")).x;
float window_width = minimal_slider_width + std::max({settings_sliders_left, clipping_slider_left, diameter_slider_left});
window_width = std::max(window_width, button_preview_width);
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<const ConfigOptionBool*>(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;
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("offset"));
ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x);
ImGui::PushItemWidth(window_width - settings_sliders_left);
m_imgui->slider_float("##offset", &offset, offset_min, offset_max, "%.1f mm");
if (m_imgui->get_last_slider_status().hovered)
m_imgui->tooltip((_utf8(opts[0].second->tooltip)).c_str(), max_tooltip_width);
bool slider_clicked = m_imgui->get_last_slider_status().clicked; // someone clicked the slider
bool slider_edited =m_imgui->get_last_slider_status().edited; // someone is dragging the slider
bool slider_released =m_imgui->get_last_slider_status().deactivated_after_edit; // someone has just released the slider
if (current_mode >= quality_mode) {
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("quality"));
ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x);
m_imgui->slider_float("##quality", &quality, quality_min, quality_max, "%.1f");
if (m_imgui->get_last_slider_status().hovered)
m_imgui->tooltip((_utf8(opts[1].second->tooltip)).c_str(), max_tooltip_width);
slider_clicked |= m_imgui->get_last_slider_status().clicked;
slider_edited |= m_imgui->get_last_slider_status().edited;
slider_released |= m_imgui->get_last_slider_status().deactivated_after_edit;
}
if (current_mode >= closing_d_mode) {
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("closing_distance"));
ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x);
m_imgui->slider_float("##closing_distance", &closing_d, closing_d_min, closing_d_max, "%.1f mm");
if (m_imgui->get_last_slider_status().hovered)
m_imgui->tooltip((_utf8(opts[2].second->tooltip)).c_str(), max_tooltip_width);
slider_clicked |= m_imgui->get_last_slider_status().clicked;
slider_edited |= m_imgui->get_last_slider_status().edited;
slider_released |= m_imgui->get_last_slider_status().deactivated_after_edit;
}
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(), "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;
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("hole_diameter"));
ImGui::SameLine(diameter_slider_left, m_imgui->get_item_spacing().x);
ImGui::PushItemWidth(window_width - diameter_slider_left);
float diam = 2.f * m_new_hole_radius;
m_imgui->slider_float("##hole_diameter", &diam, 1.f, 25.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 = m_imgui->get_last_slider_status().clicked;
bool edited = m_imgui->get_last_slider_status().edited;
bool deactivated = m_imgui->get_last_slider_status().deactivated_after_edit;
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc["hole_depth"]);
ImGui::SameLine(diameter_slider_left, m_imgui->get_item_spacing().x);
m_imgui->slider_float("##hole_depth", &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 |= m_imgui->get_last_slider_status().clicked;
edited |= m_imgui->get_last_slider_status().edited;
deactivated |= m_imgui->get_last_slider_status().deactivated_after_edit;;
// 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; idx<m_selected.size(); ++idx)
if (m_selected[idx]) {
mo->sla_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; i<m_holes_stash.size(); ++i) {
if (m_selected[i]) {
m_new_hole_radius = m_holes_stash[i].radius;
m_new_hole_height = m_holes_stash[i].height;
break;
}
}
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Change drainage hole diameter");
m_new_hole_radius = backup_rad;
m_new_hole_height = backup_hei;
mo->sla_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) {
ImGui::AlignTextToFramePadding();
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(settings_sliders_left, m_imgui->get_item_spacing().x);
ImGui::PushItemWidth(window_width - settings_sliders_left);
float clp_dist = m_c->object_clipper()->get_position();
if (m_imgui->slider_float("##clp_dist", &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 _u8L("Hollow and drill");
}
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(), "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

View file

@ -0,0 +1,111 @@
#ifndef slic3r_GLGizmoHollow_hpp_
#define slic3r_GLGizmoHollow_hpp_
#include "GLGizmoBase.hpp"
#include "slic3r/GUI/GLSelectionRectangle.hpp"
#include <libslic3r/SLA/Hollowing.hpp>
#include <libslic3r/ObjectID.hpp>
#include <wx/dialog.h>
#include <cereal/types/vector.hpp>
namespace Slic3r {
class ConfigOption;
class ConfigOptionDef;
namespace GUI {
enum class SLAGizmoEventType : unsigned char;
class GLGizmoHollow : public GLGizmoBase
{
private:
bool unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec3f, Vec3f>& pos_and_normal);
public:
GLGizmoHollow(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
virtual ~GLGizmoHollow() = default;
void set_sla_support_data(ModelObject* model_object, const Selection& selection);
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
void delete_selected_points();
bool is_selection_rectangle_dragging() const {
return m_selection_rectangle.is_dragging();
}
private:
bool on_init() override;
void on_update(const UpdateData& data) override;
void on_render() override;
void on_render_for_picking() override;
void render_points(const Selection& selection, bool picking = false) const;
void hollow_mesh(bool postpone_error_messages = false);
bool unsaved_changes() const;
ObjectID m_old_mo_id = -1;
GLModel m_vbo_cylinder;
float m_new_hole_radius = 2.f; // Size of a new hole.
float m_new_hole_height = 6.f;
mutable std::vector<bool> m_selected; // which holes are currently selected
bool m_enable_hollowing = true;
// Stashes to keep data for undo redo. Is taken after the editing
// is done, the data are updated continuously.
float m_offset_stash = 3.0f;
float m_quality_stash = 0.5f;
float m_closing_d_stash = 2.f;
Vec3f m_hole_before_drag = Vec3f::Zero();
sla::DrainHoles m_holes_in_drilled_mesh;
sla::DrainHoles m_holes_stash;
// This map holds all translated description texts, so they can be easily referenced during layout calculations
// etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.
std::map<std::string, wxString> m_desc;
GLSelectionRectangle m_selection_rectangle;
bool m_wait_for_up_event = false;
bool m_selection_empty = true;
EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state)
std::vector<std::pair<const ConfigOption*, const ConfigOptionDef*>> get_config_options(const std::vector<std::string>& keys) const;
bool is_mesh_point_clipped(const Vec3d& point) const;
// Methods that do the model_object and editing cache synchronization,
// editing mode selection, etc:
enum {
AllPoints = -2,
NoPoints,
};
void select_point(int i);
void unselect_point(int i);
void reload_cache();
protected:
void on_set_state() override;
void on_set_hover_id() override;
void on_start_dragging() override;
void on_stop_dragging() override;
void on_render_input_window(float x, float y, float bottom_limit) override;
virtual CommonGizmosDataID on_get_requirements() const override;
std::string on_get_name() const override;
bool on_is_activable() const override;
bool on_is_selectable() const override;
void on_load(cereal::BinaryInputArchive& ar) override;
void on_save(cereal::BinaryOutputArchive& ar) const override;
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLGizmoHollow_hpp_

View file

@ -0,0 +1,791 @@
#include "GLGizmoMmuSegmentation.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/ImGuiWrapper.hpp"
#include "slic3r/GUI/Camera.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/BitmapCache.hpp"
#include "slic3r/GUI/format.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/NotificationManager.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/Model.hpp"
#include "slic3r/Utils/UndoRedo.hpp"
#include <GL/glew.h>
namespace Slic3r::GUI {
static inline void show_notification_extruders_limit_exceeded()
{
wxGetApp()
.plater()
->get_notification_manager()
->push_notification(NotificationType::MmSegmentationExceededExtrudersLimit, NotificationManager::NotificationLevel::PrintInfoNotificationLevel,
GUI::format(_L("Filament count exceeds the maximum number that painting tool supports. only the "
"first %1% filaments will be available in painting tool."), GLGizmoMmuSegmentation::EXTRUDERS_LIMIT));
}
void GLGizmoMmuSegmentation::on_opening()
{
if (wxGetApp().filaments_cnt() > int(GLGizmoMmuSegmentation::EXTRUDERS_LIMIT))
show_notification_extruders_limit_exceeded();
}
void GLGizmoMmuSegmentation::on_shutdown()
{
m_parent.use_slope(false);
m_parent.toggle_model_objects_visibility(true);
}
std::string GLGizmoMmuSegmentation::on_get_name() const
{
return _u8L("Color Painting");
}
bool GLGizmoMmuSegmentation::on_is_selectable() const
{
return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF
&& /*wxGetApp().get_mode() != comSimple && */wxGetApp().filaments_cnt() > 1);
}
bool GLGizmoMmuSegmentation::on_is_activable() const
{
return GLGizmoPainterBase::on_is_activable() && wxGetApp().filaments_cnt() > 1;
}
//BBS: use the global one in 3DScene.cpp
/*static std::vector<std::array<float, 4>> get_extruders_colors()
{
unsigned char rgb_color[3] = {};
std::vector<std::string> colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config();
std::vector<std::array<float, 4>> colors_out(colors.size());
for (const std::string &color : colors) {
Slic3r::GUI::BitmapCache::parse_color(color, rgb_color);
size_t color_idx = &color - &colors.front();
colors_out[color_idx] = {float(rgb_color[0]) / 255.f, float(rgb_color[1]) / 255.f, float(rgb_color[2]) / 255.f, 1.f};
}
return colors_out;
}*/
static std::vector<int> get_extruder_id_for_volumes(const ModelObject &model_object)
{
std::vector<int> extruders_idx;
extruders_idx.reserve(model_object.volumes.size());
for (const ModelVolume *model_volume : model_object.volumes) {
if (!model_volume->is_model_part())
continue;
extruders_idx.emplace_back(model_volume->extruder_id());
}
return extruders_idx;
}
void GLGizmoMmuSegmentation::init_extruders_data()
{
m_extruders_colors = get_extruders_colors();
m_selected_extruder_idx = 0;
}
bool GLGizmoMmuSegmentation::on_init()
{
// BBS
m_shortcut_key = WXK_NONE;
m_desc["clipping_of_view"] = _L("Section view") + ": ";
m_desc["cursor_size"] = _L("Pen size") + ": ";
m_desc["cursor_type"] = _L("Pen shape");
// BBS
m_desc["paint_caption"] = _L("Left mouse button") + ": ";
m_desc["paint"] = _L("Paint");
m_desc["erase_caption"] = _L("Right mouse button") + ": ";
m_desc["erase"] = _L("Erase");
m_desc["shortcut_key_caption"] = _L("Key 1~9") + ": ";
m_desc["shortcut_key"] = _L("Choose filament");
m_desc["edge_detection"] = _L("Edge detection");
m_desc["fragment_area"] = _L("Fragment area");
m_desc["perform_filter"] = _L("Perform");
m_desc["remove_all"] = _L("Clear all");
m_desc["circle"] = _L("Circle");
m_desc["sphere"] = _L("Sphere");
m_desc["pointer"] = _L("Triangles");
m_desc["filaments"] = _L("Filaments");
m_desc["tool_type"] = _L("Tool type");
m_desc["tool_brush"] = _L("Brush");
m_desc["tool_smart_fill"] = _L("Smart fill");
m_desc["tool_bucket_fill"] = _L("Bucket fill");
m_desc["smart_fill_angle"] = _L("Smart fill angle");
m_desc["brush_size"] = _L("Set pen size");
m_desc["brush_size_caption"] = _L("Ctrl + Mouse wheel") + ": ";
// BBS
m_desc["height_range"] = _L("Height range");
init_extruders_data();
return true;
}
GLGizmoMmuSegmentation::GLGizmoMmuSegmentation(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoPainterBase(parent, icon_filename, sprite_id), m_current_tool(ImGui::CircleButtonIcon)
{
}
void GLGizmoMmuSegmentation::render_painter_gizmo() const
{
const Selection& selection = m_parent.get_selection();
glsafe(::glEnable(GL_BLEND));
glsafe(::glEnable(GL_DEPTH_TEST));
render_triangles(selection);
m_c->object_clipper()->render_cut();
m_c->instances_hider()->render_cut();
render_cursor();
glsafe(::glDisable(GL_BLEND));
}
void GLGizmoMmuSegmentation::set_painter_gizmo_data(const Selection &selection)
{
GLGizmoPainterBase::set_painter_gizmo_data(selection);
if (m_state != On || wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptFFF || wxGetApp().filaments_cnt() <= 1)
return;
ModelObject* model_object = m_c->selection_info()->model_object();
int prev_extruders_count = int(m_extruders_colors.size());
if (prev_extruders_count != wxGetApp().filaments_cnt()) {
if (wxGetApp().filaments_cnt() > int(GLGizmoMmuSegmentation::EXTRUDERS_LIMIT))
show_notification_extruders_limit_exceeded();
this->init_extruders_data();
// Reinitialize triangle selectors because of change of extruder count need also change the size of GLIndexedVertexArray
if (prev_extruders_count != wxGetApp().filaments_cnt())
this->init_model_triangle_selectors();
}
else if (get_extruders_colors() != m_extruders_colors) {
this->init_extruders_data();
this->update_triangle_selectors_colors();
}
else if (model_object != nullptr && get_extruder_id_for_volumes(*model_object) != m_volumes_extruder_idxs) {
this->init_model_triangle_selectors();
}
}
void GLGizmoMmuSegmentation::render_triangles(const Selection &selection) const
{
ClippingPlaneDataWrapper clp_data = this->get_clipping_plane_data();
auto *shader = wxGetApp().get_shader("mm_gouraud");
if (!shader)
return;
shader->start_using();
shader->set_uniform("clipping_plane", clp_data.clp_dataf);
shader->set_uniform("z_range", clp_data.z_range);
shader->set_uniform("slope.actived", m_parent.is_using_slope());
ScopeGuard guard([shader]() { if (shader) shader->stop_using(); });
//BBS: to improve the random white pixel issue
glsafe(::glDisable(GL_CULL_FACE));
const ModelObject *mo = m_c->selection_info()->model_object();
int mesh_id = -1;
for (const ModelVolume *mv : mo->volumes) {
if (!mv->is_model_part())
continue;
++mesh_id;
const Transform3d trafo_matrix = mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix() * mv->get_matrix();
bool is_left_handed = trafo_matrix.matrix().determinant() < 0.;
if (is_left_handed)
glsafe(::glFrontFace(GL_CW));
glsafe(::glPushMatrix());
glsafe(::glMultMatrixd(trafo_matrix.data()));
shader->set_uniform("volume_world_matrix", trafo_matrix);
shader->set_uniform("volume_mirrored", is_left_handed);
m_triangle_selectors[mesh_id]->render(m_imgui);
glsafe(::glPopMatrix());
if (is_left_handed)
glsafe(::glFrontFace(GL_CCW));
}
}
// BBS
bool GLGizmoMmuSegmentation::on_number_key_down(int number)
{
int extruder_idx = number - 1;
if (extruder_idx < m_extruders_colors.size())
m_selected_extruder_idx = extruder_idx;
return true;
}
static void render_extruders_combo(const std::string &label,
const std::vector<std::string> &extruders,
const std::vector<std::array<float, 4>> &extruders_colors,
size_t &selection_idx)
{
assert(!extruders_colors.empty());
assert(extruders_colors.size() == extruders_colors.size());
auto convert_to_imu32 = [](const std::array<float, 4> &color) -> ImU32 {
return IM_COL32(uint8_t(color[0] * 255.f), uint8_t(color[1] * 255.f), uint8_t(color[2] * 255.f), uint8_t(color[3] * 255.f));
};
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 extruder_idx = 0; extruder_idx < std::min(extruders.size(), GLGizmoMmuSegmentation::EXTRUDERS_LIMIT); ++extruder_idx) {
ImGui::PushID(int(extruder_idx));
ImVec2 start_position = ImGui::GetCursorScreenPos();
if (ImGui::Selectable("", extruder_idx == selection_idx))
selection_out = extruder_idx;
ImGui::SameLine();
ImGuiStyle &style = ImGui::GetStyle();
float height = ImGui::GetTextLineHeight();
ImGui::GetWindowDrawList()->AddRectFilled(start_position, ImVec2(start_position.x + height + height / 2, start_position.y + height), convert_to_imu32(extruders_colors[extruder_idx]));
ImGui::GetWindowDrawList()->AddRect(start_position, ImVec2(start_position.x + height + height / 2, start_position.y + height), IM_COL32_BLACK);
ImGui::SetCursorScreenPos(ImVec2(start_position.x + height + height / 2 + style.FramePadding.x, start_position.y));
ImGui::Text("%s", extruders[extruder_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));
ImVec2 p = ImGui::GetCursorScreenPos();
float height = ImGui::GetTextLineHeight();
ImGui::GetWindowDrawList()->AddRectFilled(p, ImVec2(p.x + height + height / 2, p.y + height), convert_to_imu32(extruders_colors[selection_idx]));
ImGui::GetWindowDrawList()->AddRect(p, ImVec2(p.x + height + height / 2, p.y + height), IM_COL32_BLACK);
ImGui::SetCursorScreenPos(ImVec2(p.x + height + height / 2 + style.FramePadding.x, p.y));
ImGui::Text("%s", extruders[selection_out].c_str());
ImGui::SetCursorScreenPos(backup_pos);
ImGui::EndGroup();
selection_idx = selection_out;
}
static std::string into_u8(const wxString& str)
{
auto buffer_utf8 = str.utf8_str();
return std::string(buffer_utf8.data());
}
void GLGizmoMmuSegmentation::show_tooltip_information(float caption_max, float x, float y)
{
ImTextureID normal_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP);
ImTextureID hover_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER);
float font_size = ImGui::GetFontSize();
ImVec2 button_size = ImVec2(font_size * 1.8, font_size * 1.3);
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
ImGui::ImageButton3(normal_id, hover_id, button_size);
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip2(ImVec2(x, y));
auto draw_text_with_caption = [this, &caption_max](const wxString &caption, const wxString &text) {
m_imgui->text_colored(ImGuiWrapper::COL_ACTIVE, caption);
ImGui::SameLine(caption_max);
m_imgui->text_colored(ImGuiWrapper::COL_WINDOW_BG, text);
};
for (const auto &t : std::array<std::string, 3>{"paint", "erase", "brush_size"}) draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t));
ImGui::EndTooltip();
}
ImGui::PopStyleVar(1);
}
void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bottom_limit)
{
if (!m_c->selection_info()->model_object()) return;
const float approx_height = m_imgui->scaled(22.0f);
y = std::min(y, bottom_limit - approx_height);
GizmoImguiSetNextWIndowPos(x, y, ImGuiCond_Always);
wchar_t old_tool = m_current_tool;
// BBS
ImGuiWrapper::push_toolbar_style();
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.0f, 16.0f));
GizmoImguiBegin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
// 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 clipping_slider_left = m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x + m_imgui->scaled(1.5f);
const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f);
const float smart_fill_slider_left = m_imgui->calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.5f);
const float edge_detect_slider_left = m_imgui->calc_text_size(m_desc.at("edge_detection")).x + m_imgui->scaled(1.f);
const float fragment_area_slider_left = m_imgui->calc_text_size(m_desc.at("fragment_area")).x + m_imgui->scaled(1.f);
const float height_range_slider_left = m_imgui->calc_text_size(m_desc.at("height_range")).x + m_imgui->scaled(1.f);
const float remove_btn_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f);
const float filter_btn_width = m_imgui->calc_text_size(m_desc.at("perform_filter")).x + m_imgui->scaled(1.f);
const float buttons_width = remove_btn_width + filter_btn_width + m_imgui->scaled(1.f);
const float minimal_slider_width = m_imgui->scaled(4.f);
const float color_button_width = m_imgui->calc_text_size("").x + m_imgui->scaled(1.75f);
float caption_max = 0.f;
float total_text_max = 0.f;
for (const auto &t : std::array<std::string, 3>{"paint", "erase", "brush_size"}) {
caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x);
total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x);
}
total_text_max += caption_max + m_imgui->scaled(1.f);
caption_max += m_imgui->scaled(1.f);
const float sliders_left_width = std::max(smart_fill_slider_left,
std::max(cursor_slider_left, std::max(edge_detect_slider_left, std::max(fragment_area_slider_left, std::max(height_range_slider_left,
clipping_slider_left)))));
const float slider_icon_width = m_imgui->get_slider_icon_size().x;
float window_width = minimal_slider_width + sliders_left_width + slider_icon_width;
const int max_filament_items_per_line = 8;
const float empty_button_width = m_imgui->calc_button_size("").x;
const float filament_item_width = empty_button_width + m_imgui->scaled(1.5f);
window_width = std::max(window_width, total_text_max);
window_width = std::max(window_width, buttons_width);
window_width = std::max(window_width, max_filament_items_per_line * filament_item_width + +m_imgui->scaled(0.5f));
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
float slider_width_times = 1.5;
ImDrawList * draw_list = ImGui::GetWindowDrawList();
ImVec2 pos = ImGui::GetCursorScreenPos();
static float color_button_high = 25.0;
draw_list->AddRectFilled({pos.x - 10.0f, pos.y - 7.0f}, {pos.x + window_width + ImGui::GetFrameHeight(), pos.y + color_button_high}, ImGui::GetColorU32(ImGuiCol_FrameBgActive, 1.0f), 5.0f);
float color_button = ImGui::GetCursorPos().y;
m_imgui->text(m_desc.at("filaments"));
float start_pos_x = ImGui::GetCursorPos().x;
const ImVec2 max_label_size = ImGui::CalcTextSize("99", NULL, true);
const float item_spacing = m_imgui->scaled(0.8f);
for (int extruder_idx = 0; extruder_idx < m_extruders_colors.size(); extruder_idx++) {
const std::array<float, 4> &extruder_color = m_extruders_colors[extruder_idx];
ImVec4 color_vec(extruder_color[0], extruder_color[1], extruder_color[2], extruder_color[3]);
std::string color_label = std::string("##extruder color ") + std::to_string(extruder_idx);
std::string item_text = std::to_string(extruder_idx + 1);
const ImVec2 label_size = ImGui::CalcTextSize(item_text.c_str(), NULL, true);
const ImVec2 button_size(max_label_size.x + m_imgui->scaled(0.5f), 0.f);
float button_offset = start_pos_x;
if (extruder_idx % max_filament_items_per_line != 0) {
button_offset += filament_item_width * (extruder_idx % max_filament_items_per_line);
ImGui::SameLine(button_offset);
}
// draw filament background
ImGuiColorEditFlags flags = ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_NoTooltip;
if (m_selected_extruder_idx != extruder_idx) flags |= ImGuiColorEditFlags_NoBorder;
bool color_picked = ImGui::ColorButton(color_label.c_str(), color_vec, flags, button_size);
color_button_high = ImGui::GetCursorPos().y - color_button - 2.0;
if (color_picked) { m_selected_extruder_idx = extruder_idx; }
if (extruder_idx < 9 && ImGui::IsItemHovered()) m_imgui->tooltip(_L("Shortcut Key ") + std::to_string(extruder_idx + 1), max_tooltip_width);
// draw filament id
float gray = 0.299 * extruder_color[0] + 0.587 * extruder_color[1] + 0.114 * extruder_color[2];
ImGui::SameLine(button_offset + (button_size.x - label_size.x) / 2.f);
if (gray * 255.f < 80.f)
ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 1.0f), item_text.c_str());
else
ImGui::TextColored(ImVec4(0.0f, 0.0f, 0.0f, 1.0f), item_text.c_str());
}
//ImGui::NewLine();
ImGui::Dummy(ImVec2(0.0f, ImGui::GetFontSize() * 0.1));
m_imgui->text(m_desc.at("tool_type"));
std::array<wchar_t, 6> tool_icons = { ImGui::CircleButtonIcon,ImGui::SphereButtonIcon, ImGui::TriangleButtonIcon, ImGui::HeightRangeIcon, ImGui::FillButtonIcon, ImGui::FragmentFilterIcon };
std::array<wxString, 6> tool_tips = { _L("Circle"), _L("Sphere"), _L("Triangle"), _L("Height Range"), _L("Fill"), _L("Fragment Filter") };
for (int i = 0; i < tool_icons.size(); i++) {
std::string str_label = std::string("##");
std::wstring btn_name = tool_icons[i] + boost::nowide::widen(str_label);
if (i != 0) ImGui::SameLine((empty_button_width + m_imgui->scaled(1.75f)) * i + m_imgui->scaled(0.5f));
if (m_current_tool == tool_icons[i]) {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.81f, 0.81f, 0.81f, 1.0f)); // r, g, b, a
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.81f, 0.81f, 0.81f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.81f, 0.81f, 0.81f, 1.0f));
}
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0);
bool btn_clicked = ImGui::Button(into_u8(btn_name).c_str());
ImGui::PopStyleVar(1);
if (m_current_tool == tool_icons[i]) ImGui::PopStyleColor(3);
if (btn_clicked && m_current_tool != tool_icons[i]) {
m_current_tool = tool_icons[i];
for (auto &triangle_selector : m_triangle_selectors) {
triangle_selector->seed_fill_unselect_all_triangles();
triangle_selector->request_update_render_data();
}
}
if (ImGui::IsItemHovered()) {
m_imgui->tooltip(tool_tips[i], max_tooltip_width);
}
}
ImGui::Dummy(ImVec2(0.0f, ImGui::GetFontSize() * 0.1));
if (m_current_tool != old_tool)
this->tool_changed(old_tool, m_current_tool);
if (m_current_tool == ImGui::CircleButtonIcon) {
m_cursor_type = TriangleSelector::CursorType::CIRCLE;
m_tool_type = ToolType::BRUSH;
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("cursor_size"));
ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(window_width - sliders_left_width - slider_width_times * slider_icon_width);
m_imgui->bbl_slider_float_style("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true);
ImGui::SameLine(window_width - slider_icon_width);
ImGui::PushItemWidth(1.5 * slider_icon_width);
ImGui::BBLDragFloat("##cursor_radius_input", &m_cursor_radius, 0.05f, 0.0f, 0.0f, "%.2f");
} else if (m_current_tool == ImGui::SphereButtonIcon){
m_cursor_type = TriangleSelector::CursorType::SPHERE;
m_tool_type = ToolType::BRUSH;
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("cursor_size"));
ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(window_width - sliders_left_width - slider_width_times * slider_icon_width);
m_imgui->bbl_slider_float_style("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true);
ImGui::SameLine(window_width - slider_icon_width);
ImGui::PushItemWidth(1.5 * slider_icon_width);
ImGui::BBLDragFloat("##cursor_radius_input", &m_cursor_radius, 0.05f, 0.0f, 0.0f, "%.2f");
} else if (m_current_tool == ImGui::TriangleButtonIcon) {
m_cursor_type = TriangleSelector::CursorType::POINTER;
m_tool_type = ToolType::BRUSH;
} else if (m_current_tool == ImGui::FillButtonIcon) {
m_cursor_type = TriangleSelector::CursorType::POINTER;
m_imgui->bbl_checkbox(m_desc["edge_detection"], m_detect_geometry_edge);
m_tool_type = ToolType::BUCKET_FILL;
if (m_detect_geometry_edge) {
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc["smart_fill_angle"] + ":");
std::string format_str = std::string("%.f") + I18N::translate_utf8("°", "Face angle threshold,"
"placed after the number with no whitespace in between.");
ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(window_width - sliders_left_width - slider_width_times * slider_icon_width);
if (m_imgui->bbl_slider_float_style("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data(), 1.0f, true))
for (auto &triangle_selector : m_triangle_selectors) {
triangle_selector->seed_fill_unselect_all_triangles();
triangle_selector->request_update_render_data();
}
ImGui::SameLine(window_width - slider_icon_width);
ImGui::PushItemWidth(1.5 * slider_icon_width);
ImGui::BBLDragFloat("##smart_fill_angle_input", &m_smart_fill_angle, 0.05f, 0.0f, 0.0f, "%.2f");
} else {
// set to negative value to disable edge detection
m_smart_fill_angle = -1.f;
}
} else if (m_current_tool == ImGui::HeightRangeIcon) {
m_tool_type = ToolType::BRUSH;
m_cursor_type = TriangleSelector::CursorType::HEIGHT_RANGE;
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc["height_range"] + ":");
ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(window_width - sliders_left_width - slider_width_times * slider_icon_width);
std::string format_str = std::string("%.2f") + I18N::translate_utf8("mm", "Heigh range," "Facet in [cursor z, cursor z + height] will be selected.");
m_imgui->bbl_slider_float_style("##cursor_height", &m_cursor_height, CursorHeightMin, CursorHeightMax, format_str.data(), 1.0f, true);
ImGui::SameLine(window_width - slider_icon_width);
ImGui::PushItemWidth(1.5 * slider_icon_width);
ImGui::BBLDragFloat("##cursor_height_input", &m_cursor_height, 0.05f, 0.0f, 0.0f, "%.2f");
}
else if (m_current_tool == ImGui::FragmentFilterIcon) {
m_tool_type = ToolType::FRAGMENT_FILTER;
m_cursor_type = TriangleSelector::CursorType::POINTER;
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc["fragment_area"] + ":");
ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(window_width - sliders_left_width - slider_width_times * slider_icon_width);
std::string format_str = std::string("%.2f") + I18N::translate_utf8("", "Triangle patch area threshold,""triangle patch will be merged to neighbor if its area is less than threshold");
m_imgui->bbl_slider_float_style("##fragment_area", &TriangleSelectorPatch::fragment_area, TriangleSelectorPatch::FragmentAreaMin, TriangleSelectorPatch::FragmentAreaMax, format_str.data(), 1.0f, true);
ImGui::SameLine(window_width - slider_icon_width);
ImGui::PushItemWidth(1.5 * slider_icon_width);
ImGui::BBLDragFloat("##fragment_area_input", &TriangleSelectorPatch::fragment_area, 0.05f, 0.0f, 0.0f, "%.2f");
}
if (m_current_tool != ImGui::FragmentFilterIcon) {
ImGui::Separator();
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("clipping_of_view"));
auto clp_dist = float(m_c->object_clipper()->get_position());
ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(window_width - sliders_left_width - slider_width_times * slider_icon_width);
bool slider_clp_dist = m_imgui->bbl_slider_float_style("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true);
ImGui::SameLine(window_width - slider_icon_width);
ImGui::PushItemWidth(1.5 * slider_icon_width);
bool b_clp_dist_input = ImGui::BBLDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f");
if (slider_clp_dist || b_clp_dist_input) { m_c->object_clipper()->set_position(clp_dist, true); }
}
ImGui::Separator();
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 5.0f));
float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y;
show_tooltip_information(caption_max, x, get_cur_y);
ImGui::SameLine();
if (m_current_tool == ImGui::FragmentFilterIcon) {
if (m_imgui->button(m_desc.at("perform_filter"))) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Filter fragment", UndoRedo::SnapshotType::GizmoAction);
for (int i = 0; i < m_triangle_selectors.size(); i++) {
TriangleSelectorPatch* ts_mm = dynamic_cast<TriangleSelectorPatch*>(m_triangle_selectors[i].get());
ts_mm->update_selector_triangles();
ts_mm->request_update_render_data(true);
}
update_model_object();
m_parent.set_as_dirty();
}
ImGui::SameLine();
}
if (m_imgui->button(m_desc.at("remove_all"))) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Reset selection", UndoRedo::SnapshotType::GizmoAction);
ModelObject * mo = m_c->selection_info()->model_object();
int idx = -1;
for (ModelVolume *mv : mo->volumes)
if (mv->is_model_part()) {
++idx;
m_triangle_selectors[idx]->reset();
m_triangle_selectors[idx]->request_update_render_data(true);
}
update_model_object();
m_parent.set_as_dirty();
}
ImGui::PopStyleVar(2);
GizmoImguiEnd();
// BBS
ImGui::PopStyleVar(1);
ImGuiWrapper::pop_toolbar_style();
}
void GLGizmoMmuSegmentation::update_model_object()
{
bool updated = false;
ModelObject* mo = m_c->selection_info()->model_object();
int idx = -1;
for (ModelVolume* mv : mo->volumes) {
if (! mv->is_model_part())
continue;
++idx;
updated |= mv->mmu_segmentation_facets.set(*m_triangle_selectors[idx].get());
}
if (updated) {
const ModelObjectPtrs &mos = wxGetApp().model().objects;
wxGetApp().obj_list()->update_info_items(std::find(mos.begin(), mos.end(), mo) - mos.begin());
// BBS: backup
Slic3r::save_object_mesh(*mo);
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
// BBS
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_FORCE_UPDATE));
}
}
void GLGizmoMmuSegmentation::init_model_triangle_selectors()
{
const ModelObject *mo = m_c->selection_info()->model_object();
m_triangle_selectors.clear();
m_volumes_extruder_idxs.clear();
// Don't continue when extruders colors are not initialized
if(m_extruders_colors.empty())
return;
// BBS: Don't continue when model object is null
if (mo == nullptr)
return;
for (const ModelVolume *mv : mo->volumes) {
if (!mv->is_model_part())
continue;
int extruder_idx = (mv->extruder_id() > 0) ? mv->extruder_id() - 1 : 0;
std::vector<std::array<float, 4>> ebt_colors;
ebt_colors.push_back(m_extruders_colors[size_t(extruder_idx)]);
ebt_colors.insert(ebt_colors.end(), m_extruders_colors.begin(), m_extruders_colors.end());
// This mesh does not account for the possible Z up SLA offset.
const TriangleMesh* mesh = &mv->mesh();
m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorPatch>(*mesh, ebt_colors, 0.2));
// Reset of TriangleSelector is done inside TriangleSelectorMmGUI's constructor, so we don't need it to perform it again in deserialize().
m_triangle_selectors.back()->deserialize(mv->mmu_segmentation_facets.get_data(), false);
m_triangle_selectors.back()->request_update_render_data();
m_volumes_extruder_idxs.push_back(mv->extruder_id());
}
}
void GLGizmoMmuSegmentation::update_triangle_selectors_colors()
{
for (int i = 0; i < m_triangle_selectors.size(); i++) {
TriangleSelectorPatch* selector = dynamic_cast<TriangleSelectorPatch*>(m_triangle_selectors[i].get());
int extruder_idx = m_volumes_extruder_idxs[i];
int extruder_color_idx = std::max(0, extruder_idx - 1);
std::vector<std::array<float, 4>> ebt_colors;
ebt_colors.push_back(m_extruders_colors[extruder_color_idx]);
ebt_colors.insert(ebt_colors.end(), m_extruders_colors.begin(), m_extruders_colors.end());
selector->set_ebt_colors(ebt_colors);
}
}
void GLGizmoMmuSegmentation::update_from_model_object(bool first_update)
{
wxBusyCursor wait;
// Extruder colors need to be reloaded before calling init_model_triangle_selectors to render painted triangles
// using colors from loaded 3MF and not from printer profile in Slicer.
if (int prev_extruders_count = int(m_extruders_colors.size());
prev_extruders_count != wxGetApp().filaments_cnt() || get_extruders_colors() != m_extruders_colors)
this->init_extruders_data();
this->init_model_triangle_selectors();
}
void GLGizmoMmuSegmentation::tool_changed(wchar_t old_tool, wchar_t new_tool)
{
if ((old_tool == ImGui::FragmentFilterIcon && new_tool == ImGui::FragmentFilterIcon) ||
(old_tool != ImGui::FragmentFilterIcon && new_tool != ImGui::FragmentFilterIcon))
return;
for (auto& selector_ptr : m_triangle_selectors) {
TriangleSelectorPatch* tsp = dynamic_cast<TriangleSelectorPatch*>(selector_ptr.get());
tsp->set_filter_state(new_tool == ImGui::FragmentFilterIcon);
}
}
PainterGizmoType GLGizmoMmuSegmentation::get_painter_type() const
{
return PainterGizmoType::MMU_SEGMENTATION;
}
// BBS
std::array<float, 4> GLGizmoMmuSegmentation::get_cursor_hover_color() const
{
if (m_selected_extruder_idx < m_extruders_colors.size())
return m_extruders_colors[m_selected_extruder_idx];
else
return m_extruders_colors[0];
}
wxString GLGizmoMmuSegmentation::handle_snapshot_action_name(bool shift_down, GLGizmoPainterBase::Button button_down) const
{
wxString action_name;
if (shift_down)
action_name = _L("Remove painted color");
else {
action_name = GUI::format(_L("Painted using: Filament %1%"), m_selected_extruder_idx);
}
return action_name;
}
void GLMmSegmentationGizmo3DScene::release_geometry() {
if (this->vertices_VBO_id) {
glsafe(::glDeleteBuffers(1, &this->vertices_VBO_id));
this->vertices_VBO_id = 0;
}
for(auto &triangle_indices_VBO_id : triangle_indices_VBO_ids) {
glsafe(::glDeleteBuffers(1, &triangle_indices_VBO_id));
triangle_indices_VBO_id = 0;
}
this->clear();
}
void GLMmSegmentationGizmo3DScene::render(size_t triangle_indices_idx) const
{
assert(triangle_indices_idx < this->triangle_indices_VBO_ids.size());
assert(this->triangle_patches.size() == this->triangle_indices_VBO_ids.size());
assert(this->vertices_VBO_id != 0);
assert(this->triangle_indices_VBO_ids[triangle_indices_idx] != 0);
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_VBO_id));
glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), (const void*)(0 * sizeof(float))));
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
// Render using the Vertex Buffer Objects.
if (this->triangle_indices_sizes[triangle_indices_idx] > 0) {
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_ids[triangle_indices_idx]));
glsafe(::glDrawElements(GL_TRIANGLES, GLsizei(this->triangle_indices_sizes[triangle_indices_idx]), GL_UNSIGNED_INT, nullptr));
glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
}
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
}
void GLMmSegmentationGizmo3DScene::finalize_vertices()
{
assert(this->vertices_VBO_id == 0);
if (!this->vertices.empty()) {
glsafe(::glGenBuffers(1, &this->vertices_VBO_id));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_VBO_id));
glsafe(::glBufferData(GL_ARRAY_BUFFER, this->vertices.size() * sizeof(float), this->vertices.data(), GL_STATIC_DRAW));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
this->vertices.clear();
}
}
void GLMmSegmentationGizmo3DScene::finalize_triangle_indices()
{
triangle_indices_VBO_ids.resize(this->triangle_patches.size());
triangle_indices_sizes.resize(this->triangle_patches.size());
assert(std::all_of(triangle_indices_VBO_ids.cbegin(), triangle_indices_VBO_ids.cend(), [](const auto &ti_VBO_id) { return ti_VBO_id == 0; }));
for (size_t buffer_idx = 0; buffer_idx < this->triangle_patches.size(); ++buffer_idx) {
std::vector<int>& triangle_indices = this->triangle_patches[buffer_idx].triangle_indices;
triangle_indices_sizes[buffer_idx] = triangle_indices.size();
if (!triangle_indices.empty()) {
glsafe(::glGenBuffers(1, &this->triangle_indices_VBO_ids[buffer_idx]));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_ids[buffer_idx]));
glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, triangle_indices.size() * sizeof(int), triangle_indices.data(), GL_STATIC_DRAW));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
triangle_indices.clear();
}
}
}
} // namespace Slic3r

View file

@ -0,0 +1,144 @@
#ifndef slic3r_GLGizmoMmuSegmentation_hpp_
#define slic3r_GLGizmoMmuSegmentation_hpp_
#include "GLGizmoPainterBase.hpp"
namespace Slic3r::GUI {
class GLMmSegmentationGizmo3DScene
{
public:
GLMmSegmentationGizmo3DScene() = delete;
explicit GLMmSegmentationGizmo3DScene(size_t triangle_indices_buffers_count)
{
}
virtual ~GLMmSegmentationGizmo3DScene() { release_geometry(); }
[[nodiscard]] inline bool has_VBOs(size_t triangle_indices_idx) const
{
assert(triangle_indices_idx < this->triangle_patches.size());
return this->triangle_indices_VBO_ids[triangle_indices_idx] != 0;
}
// Release the geometry data, release OpenGL VBOs.
void release_geometry();
// Finalize the initialization of the geometry, upload the geometry to OpenGL VBO objects
// and possibly releasing it if it has been loaded into the VBOs.
void finalize_vertices();
// Finalize the initialization of the indices, upload the indices to OpenGL VBO objects
// and possibly releasing it if it has been loaded into the VBOs.
void finalize_triangle_indices();
void clear()
{
this->vertices.clear();
// BBS
this->triangle_indices_VBO_ids.clear();
this->triangle_indices_sizes.clear();
for (TrianglePatch& patch : this->triangle_patches)
patch.triangle_indices.clear();
this->triangle_patches.clear();
}
void render(size_t triangle_indices_idx) const;
std::vector<float> vertices;
//std::vector<std::vector<int>> triangle_indices;
// BBS
std::vector<TrianglePatch> triangle_patches;
// When the triangle indices are loaded into the graphics card as Vertex Buffer Objects,
// the above mentioned std::vectors are cleared and the following variables keep their original length.
std::vector<size_t> triangle_indices_sizes;
// IDs of the Vertex Array Objects, into which the geometry has been loaded.
// Zero if the VBOs are not sent to GPU yet.
unsigned int vertices_VBO_id{0};
std::vector<unsigned int> triangle_indices_VBO_ids;
};
class GLGizmoMmuSegmentation : public GLGizmoPainterBase
{
public:
GLGizmoMmuSegmentation(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
~GLGizmoMmuSegmentation() override = default;
void render_painter_gizmo() const override;
void set_painter_gizmo_data(const Selection& selection) override;
void render_triangles(const Selection& selection) const override;
// TriangleSelector::serialization/deserialization has a limit to store 19 different states.
// EXTRUDER_LIMIT + 1 states are used to storing the painting because also uncolored triangles are stored.
// When increasing EXTRUDER_LIMIT, it needs to ensure that TriangleSelector::serialization/deserialization
// will be also extended to support additional states, requiring at least one state to remain free out of 19 states.
static const constexpr size_t EXTRUDERS_LIMIT = 16;
const float get_cursor_radius_min() const override { return CursorRadiusMin; }
// BBS
bool on_number_key_down(int number);
protected:
// BBS
std::array<float, 4> get_cursor_hover_color() const override;
EnforcerBlockerType get_left_button_state_type() const override { return EnforcerBlockerType(m_selected_extruder_idx + 1); }
EnforcerBlockerType get_right_button_state_type() const override { return EnforcerBlockerType::NONE; }
void on_render_input_window(float x, float y, float bottom_limit) override;
std::string on_get_name() const override;
void show_tooltip_information(float caption_max, float x, float y);
bool on_is_selectable() const override;
bool on_is_activable() const override;
wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override;
std::string get_gizmo_entering_text() const override { return _u8L("Entering color painting"); }
std::string get_gizmo_leaving_text() const override { return _u8L("Leaving color painting"); }
std::string get_action_snapshot_name() override { return _u8L("Color painting editing"); }
// BBS
size_t m_selected_extruder_idx = 0;
std::vector<std::array<float, 4>> m_extruders_colors;
std::vector<int> m_volumes_extruder_idxs;
// BBS
wchar_t m_current_tool = 0;
bool m_detect_geometry_edge = true;
static const constexpr float CursorRadiusMin = 0.1f; // cannot be zero
private:
bool on_init() override;
// BBS. remove const.
void update_model_object() override;
//BBS: add logic to distinguish the first_time_update and later_update
void update_from_model_object(bool first_update = false) override;
void tool_changed(wchar_t old_tool, wchar_t new_tool);
void on_opening() override;
void on_shutdown() override;
PainterGizmoType get_painter_type() const override;
void init_model_triangle_selectors();
// BBS
void update_triangle_selectors_colors();
void init_extruders_data();
// This map holds all translated description texts, so they can be easily referenced during layout calculations
// etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.
std::map<std::string, wxString> m_desc;
};
} // namespace Slic3r
#endif // slic3r_GLGizmoMmuSegmentation_hpp_

View file

@ -0,0 +1,138 @@
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
#include "GLGizmoModifier.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/ImGuiWrapper.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp"
#include "libslic3r/Geometry/ConvexHull.hpp"
#include "libslic3r/Model.hpp"
#include <numeric>
#include <GL/glew.h>
namespace Slic3r {
namespace GUI {
const int SHAPE_IMAGE_SIZE = 34;
const std::vector<std::pair<std::string, std::string>> GLGizmoModifier::MODIFIER_SHAPES = {
{L("Cube"), "toolbar_modifier_cube.svg" },
{L("Cylinder"), "toolbar_modifier_cylinder.svg" },
{L("Sphere"), "toolbar_modifier_sphere.svg" },
{L("Cone"), "toolbar_modifier_cone.svg" },
{L("Timelapse Wipe Tower"), "toolbar_modifier_cube.svg"},
};
GLGizmoModifier::GLGizmoModifier(GLCanvas3D &parent, const std::string &icon_filename, unsigned int sprite_id)
: GLGizmoBase(parent, icon_filename, sprite_id)
{
}
bool GLGizmoModifier::on_init()
{
bool result = true;
texture_ids.clear();
for (auto item: MODIFIER_SHAPES) {
result = result && init_shape_texture(item.second);
}
// BBS
m_shortcut_key = WXK_NONE;
return result;
}
bool GLGizmoModifier::init_shape_texture(std::string image_name)
{
// init shapes image
bool compress = false;
GLint last_texture;
unsigned m_image_texture{0};
std::string path = resources_dir() + "/images/";
std::string file_name = path + image_name;
ThumbnailData data;
if (!get_data_from_svg(file_name, SHAPE_IMAGE_SIZE, data)) return false;
unsigned char *pixels = (unsigned char *) (&data.pixels[0]);
glsafe(::glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture));
glsafe(::glGenTextures(1, &m_image_texture));
glsafe(::glBindTexture(GL_TEXTURE_2D, m_image_texture));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
glsafe(::glPixelStorei(GL_UNPACK_ROW_LENGTH, 0));
if (compress && GLEW_EXT_texture_compression_s3tc)
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, data.width, data.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels));
else
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, data.width, data.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels));
// Store our identifier
ImTextureID texture_id = (ImTextureID) (intptr_t) m_image_texture;
texture_ids.push_back(texture_id);
// Restore state
glsafe(::glBindTexture(GL_TEXTURE_2D, last_texture));
return true;
}
void GLGizmoModifier::on_set_state()
{
}
void GLGizmoModifier::on_render_input_window(float x, float y, float bottom_limit)
{
// BBS: GUI refactor: move gizmo to the right
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always, 0.f, 0.0f);
// BBS
ImGuiWrapper::push_toolbar_style();
std::string name = "Add Modifier##Modifier";
m_imgui->begin(_L(name), ImGuiWrapper::TOOLBAR_WINDOW_FLAGS);
for (int i = 0; i < MODIFIER_SHAPES.size(); i++) {
if (ImGui::ImageButton(texture_ids[i], ImVec2(34.0f, 34.0f))) {
wxGetApp().obj_list()->load_generic_subobject(MODIFIER_SHAPES[i].first, ModelVolumeType::PARAMETER_MODIFIER);
}
ImGui::SameLine();
}
m_imgui->end();
ImGuiWrapper::pop_toolbar_style();
}
CommonGizmosDataID GLGizmoModifier::on_get_requirements() const
{
return CommonGizmosDataID::SelectionInfo;
}
std::string GLGizmoModifier::on_get_name() const
{
return _u8L("Add Modifier");
}
bool GLGizmoModifier::on_is_activable() const
{
return m_parent.get_selection().is_single_full_instance();
}
void GLGizmoModifier::on_start_dragging()
{
;
}
void GLGizmoModifier::on_render()
{
;
}
void GLGizmoModifier::on_render_for_picking()
{
;
}
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,42 @@
#ifndef slic3r_GLGizmoModifier_hpp_
#define slic3r_GLGizmoModifier_hpp_
#include "GLGizmoBase.hpp"
#include "slic3r/GUI/3DScene.hpp"
namespace Slic3r {
enum class ModelVolumeType : int;
namespace GUI {
class GLGizmoModifier : public GLGizmoBase
{
// This gizmo does not use grabbers. The m_hover_id relates to polygon managed by the class itself.
private:
std::vector<void*> texture_ids;
public:
static const std::vector<std::pair<std::string, std::string>> MODIFIER_SHAPES;
GLGizmoModifier(GLCanvas3D &parent, const std::string &icon_filename, unsigned int sprite_id);
protected:
virtual bool on_init() override;
virtual std::string on_get_name() const override;
virtual bool on_is_activable() const override;
virtual void on_start_dragging() override;
virtual void on_render() override;
virtual void on_render_for_picking() override;
virtual void on_set_state() override;
virtual void on_render_input_window(float x, float y, float bottom_limit);
virtual CommonGizmosDataID on_get_requirements() const override;
bool init_shape_texture(std::string image_name);
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLGizmoFlatten_hpp_

View file

@ -0,0 +1,263 @@
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
#include "GLGizmoMove.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
//BBS: GUI refactor
#include "slic3r/GUI/Plater.hpp"
#include "libslic3r/AppConfig.hpp"
#include <GL/glew.h>
#include <wx/utils.h>
namespace Slic3r {
namespace GUI {
#if ENABLE_FIXED_GRABBER
const double GLGizmoMove3D::Offset = 50.0;
#else
const double GLGizmoMove3D::Offset = 10.0;
#endif
//BBS: GUI refactor: add obj manipulation
GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id, GizmoObjectManipulation* obj_manipulation)
: GLGizmoBase(parent, icon_filename, sprite_id)
, m_displacement(Vec3d::Zero())
, m_snap_step(1.0)
, m_starting_drag_position(Vec3d::Zero())
, m_starting_box_center(Vec3d::Zero())
, m_starting_box_bottom_center(Vec3d::Zero())
//BBS: GUI refactor: add obj manipulation
, m_object_manipulation(obj_manipulation)
{
m_vbo_cone.init_from(its_make_cone(1., 1., 2*PI/36));
}
std::string GLGizmoMove3D::get_tooltip() const
{
const Selection& selection = m_parent.get_selection();
bool show_position = selection.is_single_full_instance();
const Vec3d& position = selection.get_bounding_box().center();
if (m_hover_id == 0 || m_grabbers[0].dragging)
return "X: " + format(show_position ? position(0) : m_displacement(0), 2);
else if (m_hover_id == 1 || m_grabbers[1].dragging)
return "Y: " + format(show_position ? position(1) : m_displacement(1), 2);
else if (m_hover_id == 2 || m_grabbers[2].dragging)
return "Z: " + format(show_position ? position(2) : m_displacement(2), 2);
else
return "";
}
bool GLGizmoMove3D::on_init()
{
for (int i = 0; i < 3; ++i) {
m_grabbers.push_back(Grabber());
}
m_shortcut_key = WXK_NONE;
return true;
}
std::string GLGizmoMove3D::on_get_name() const
{
return _u8L("Move");
}
bool GLGizmoMove3D::on_is_activable() const
{
return !m_parent.get_selection().is_empty();
}
void GLGizmoMove3D::on_start_dragging()
{
if (m_hover_id != -1) {
m_displacement = Vec3d::Zero();
const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box();
m_starting_drag_position = m_grabbers[m_hover_id].center;
m_starting_box_center = box.center();
m_starting_box_bottom_center = box.center();
m_starting_box_bottom_center(2) = box.min(2);
}
}
void GLGizmoMove3D::on_stop_dragging()
{
m_displacement = Vec3d::Zero();
}
void GLGizmoMove3D::on_update(const UpdateData& data)
{
if (m_hover_id == 0)
m_displacement.x() = calc_projection(data);
else if (m_hover_id == 1)
m_displacement.y() = calc_projection(data);
else if (m_hover_id == 2)
m_displacement.z() = calc_projection(data);
}
void GLGizmoMove3D::on_render()
{
const Selection& selection = m_parent.get_selection();
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
glsafe(::glEnable(GL_DEPTH_TEST));
const BoundingBoxf3& box = selection.get_bounding_box();
const Vec3d& center = box.center();
float space_size = 20.f *INV_ZOOM;
#if ENABLE_FIXED_GRABBER
// x axis
m_grabbers[0].center = { box.max.x() + space_size, center.y(), center.z() };
// y axis
m_grabbers[1].center = { center.x(), box.max.y() + space_size, center.z() };
// z axis
m_grabbers[2].center = { center.x(), center.y(), box.max.z() + space_size };
for (int i = 0; i < 3; ++i) {
m_grabbers[i].color = AXES_COLOR[i];
m_grabbers[i].hover_color = AXES_HOVER_COLOR[i];
}
#else
// x axis
m_grabbers[0].center = { box.max.x() + Offset, center.y(), center.z() };
m_grabbers[0].color = AXES_COLOR[0];
// y axis
m_grabbers[1].center = { center.x(), box.max.y() + Offset, center.z() };
m_grabbers[1].color = AXES_COLOR[1];
// z axis
m_grabbers[2].center = { center.x(), center.y(), box.max.z() + Offset };
m_grabbers[2].color = AXES_COLOR[2];
#endif
glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f));
// draw grabbers
for (unsigned int i = 0; i < 3; ++i) {
if (m_grabbers[i].enabled) render_grabber_extension((Axis) i, box, false);
}
// draw axes line
// draw axes
for (unsigned int i = 0; i < 3; ++i) {
if (m_grabbers[i].enabled) {
glsafe(::glColor4fv(AXES_COLOR[i].data()));
glLineStipple(1, 0x0FFF);
glEnable(GL_LINE_STIPPLE);
::glBegin(GL_LINES);
::glVertex3dv(center.data());
// use extension center
::glVertex3dv(m_grabbers[i].center.data());
glsafe(::glEnd());
glDisable(GL_LINE_STIPPLE);
}
}
}
void GLGizmoMove3D::on_render_for_picking()
{
glsafe(::glDisable(GL_DEPTH_TEST));
const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box();
//BBS donot render base grabber for picking
//render_grabbers_for_picking(box);
//get picking colors only
for (unsigned int i = 0; i < (unsigned int) m_grabbers.size(); ++i) {
if (m_grabbers[i].enabled) {
std::array<float, 4> color = picking_color_component(i);
m_grabbers[i].color = color;
}
}
render_grabber_extension(X, box, true);
render_grabber_extension(Y, box, true);
render_grabber_extension(Z, box, true);
}
//BBS: add input window for move
void GLGizmoMove3D::on_render_input_window(float x, float y, float bottom_limit)
{
if (m_object_manipulation)
m_object_manipulation->do_render_move_window(m_imgui, "Move", x, y, bottom_limit);
}
double GLGizmoMove3D::calc_projection(const UpdateData& data) const
{
double projection = 0.0;
Vec3d starting_vec = m_starting_drag_position - m_starting_box_center;
double len_starting_vec = starting_vec.norm();
if (len_starting_vec != 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 + (m_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 - m_starting_drag_position;
// finds projection of the vector along the staring direction
projection = inters_vec.dot(starting_vec.normalized());
}
if (wxGetKeyState(WXK_SHIFT))
projection = m_snap_step * (double)std::round(projection / m_snap_step);
return projection;
}
void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking) const
{
#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
double size = 0.75 * GLGizmoBase::Grabber::FixedGrabberSize * GLGizmoBase::INV_ZOOM;
std::array<float, 4> color = m_grabbers[axis].color;
if (!picking && m_hover_id != -1) {
if (m_hover_id == axis) {
color = m_grabbers[axis].hover_color;
}
}
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
if (shader == nullptr)
return;
const_cast<GLModel*>(&m_vbo_cone)->set_color(-1, color);
if (!picking) {
shader->start_using();
shader->set_uniform("emission_factor", 0.1f);
}
glsafe(::glPushMatrix());
glsafe(::glTranslated(m_grabbers[axis].center.x(), m_grabbers[axis].center.y(), m_grabbers[axis].center.z()));
if (axis == X)
glsafe(::glRotated(90.0, 0.0, 1.0, 0.0));
else if (axis == Y)
glsafe(::glRotated(-90.0, 1.0, 0.0, 0.0));
//glsafe(::glTranslated(0.0, 0.0, 2.0 * size));
glsafe(::glScaled(0.75 * size, 0.75 * size, 2.0 * size));
m_vbo_cone.render();
glsafe(::glPopMatrix());
if (! picking)
shader->stop_using();
}
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,66 @@
#ifndef slic3r_GLGizmoMove_hpp_
#define slic3r_GLGizmoMove_hpp_
#include "GLGizmoBase.hpp"
//BBS: add size adjust related
#include "GizmoObjectManipulation.hpp"
namespace Slic3r {
namespace GUI {
//BBS: GUI refactor: add object manipulation
class GizmoObjectManipulation;
class GLGizmoMove3D : public GLGizmoBase
{
static const double Offset;
Vec3d m_displacement;
double m_snap_step;
Vec3d m_starting_drag_position;
Vec3d m_starting_box_center;
Vec3d m_starting_box_bottom_center;
GLModel m_vbo_cone;
//BBS: add size adjust related
GizmoObjectManipulation* m_object_manipulation;
public:
//BBS: add obj manipulation logic
//GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id, GizmoObjectManipulation* obj_manipulation);
virtual ~GLGizmoMove3D() = default;
double get_snap_step(double step) const { return m_snap_step; }
void set_snap_step(double step) { m_snap_step = step; }
const Vec3d& get_displacement() const { return m_displacement; }
std::string get_tooltip() const override;
protected:
virtual bool on_init() override;
virtual std::string on_get_name() const override;
virtual bool on_is_activable() const override;
virtual void on_start_dragging() override;
virtual void on_stop_dragging() override;
virtual void on_update(const UpdateData& data) override;
virtual void on_render() override;
virtual void on_render_for_picking() override;
//BBS: GUI refactor: add object manipulation
virtual void on_render_input_window(float x, float y, float bottom_limit);
private:
double calc_projection(const UpdateData& data) const;
void render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking) const;
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLGizmoMove_hpp_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,387 @@
#ifndef slic3r_GLGizmoPainterBase_hpp_
#define slic3r_GLGizmoPainterBase_hpp_
#include "GLGizmoBase.hpp"
#include "slic3r/GUI/3DScene.hpp"
#include "libslic3r/ObjectID.hpp"
#include "libslic3r/TriangleSelector.hpp"
#include "libslic3r/Model.hpp"
#include <cereal/types/vector.hpp>
#include <GL/glew.h>
namespace Slic3r::GUI {
enum class SLAGizmoEventType : unsigned char;
class ClippingPlane;
struct Camera;
class GLGizmoMmuSegmentation;
enum class PainterGizmoType {
FDM_SUPPORTS,
SEAM,
MMU_SEGMENTATION
};
class GLPaintContour
{
public:
GLPaintContour() = default;
void render() const;
inline bool has_VBO() const { return this->m_contour_EBO_id != 0; }
// Release the geometry data, release OpenGL VBOs.
void release_geometry();
// Finalize the initialization of the contour geometry and the indices, upload both to OpenGL VBO objects
// and possibly releasing it if it has been loaded into the VBOs.
void finalize_geometry();
void clear()
{
this->contour_vertices.clear();
this->contour_indices.clear();
this->contour_indices_size = 0;
}
std::vector<float> contour_vertices;
std::vector<int> contour_indices;
// When the triangle indices are loaded into the graphics card as Vertex Buffer Objects,
// the above mentioned std::vectors are cleared and the following variables keep their original length.
size_t contour_indices_size{0};
// IDs of the Vertex Array Objects, into which the geometry has been loaded.
// Zero if the VBOs are not sent to GPU yet.
GLuint m_contour_VBO_id{0};
GLuint m_contour_EBO_id{0};
};
class TriangleSelectorGUI : public TriangleSelector {
public:
explicit TriangleSelectorGUI(const TriangleMesh& mesh, float edge_limit = 0.6f)
: TriangleSelector(mesh, edge_limit) {}
virtual ~TriangleSelectorGUI() = default;
// Render current selection. Transformation matrices are supposed
// to be already set.
virtual void render(ImGuiWrapper *imgui);
void render() { this->render(nullptr); }
// BBS
void request_update_render_data(bool paint_changed = false)
{
m_update_render_data = true;
m_paint_changed = paint_changed;
};
// BBS
static constexpr std::array<float, 4> enforcers_color{ 0.5f, 1.f, 0.5f, 1.f };
static constexpr std::array<float, 4> blockers_color{ 1.f, 0.5f, 0.5f, 1.f };
#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
void render_debug(ImGuiWrapper* imgui);
bool m_show_triangles{false};
bool m_show_invalid{false};
#endif
protected:
bool m_update_render_data = false;
// BBS
bool m_paint_changed = true;
static std::array<float, 4> get_seed_fill_color(const std::array<float, 4> &base_color);
private:
void update_render_data();
GLIndexedVertexArray m_iva_enforcers;
GLIndexedVertexArray m_iva_blockers;
std::array<GLIndexedVertexArray, 3> m_iva_seed_fills;
std::array<GLIndexedVertexArray, 3> m_varrays;
protected:
GLPaintContour m_paint_contour;
};
// BBS
struct TrianglePatch {
std::vector<int> triangle_indices;
std::vector<int> facet_indices;
EnforcerBlockerType type = EnforcerBlockerType::NONE;
std::set<EnforcerBlockerType> neighbor_types;
// if area is larger than FragmentAreaMax, stop accumulate left triangle areas to improve performance
float area = 0.f;
bool is_fragment() const;
};
class TriangleSelectorPatch : public TriangleSelectorGUI {
public:
explicit TriangleSelectorPatch(const TriangleMesh& mesh, const std::vector<std::array<float, 4>> ebt_colors, float edge_limit = 0.6f)
: TriangleSelectorGUI(mesh, edge_limit), m_ebt_colors(ebt_colors) {}
virtual ~TriangleSelectorPatch() = default;
// Render current selection. Transformation matrices are supposed
// to be already set.
void render(ImGuiWrapper* imgui) override;
// TriangleSelector.m_triangles => m_gizmo_scene.triangle_patches
void update_triangles_per_type();
// m_gizmo_scene.triangle_patches => TriangleSelector.m_triangles
void update_selector_triangles();
void update_triangles_per_patch();
void set_ebt_colors(const std::vector<std::array<float, 4>> ebt_colors) { m_ebt_colors = ebt_colors; }
void set_filter_state(bool is_filter_state);
constexpr static float FragmentAreaMin = 0.f;
constexpr static float FragmentAreaMax = 5.f;
// BBS: fix me
static float fragment_area;
protected:
// Release the geometry data, release OpenGL VBOs.
void release_geometry();
// Finalize the initialization of the geometry, upload the geometry to OpenGL VBO objects
// and possibly releasing it if it has been loaded into the VBOs.
void finalize_vertices();
// Finalize the initialization of the indices, upload the indices to OpenGL VBO objects
// and possibly releasing it if it has been loaded into the VBOs.
void finalize_triangle_indices();
void clear()
{
// BBS
this->m_triangle_indices_VBO_ids.clear();
this->m_triangle_indices_sizes.clear();
for (TrianglePatch& patch : this->m_triangle_patches)
patch.triangle_indices.clear();
this->m_triangle_patches.clear();
}
[[nodiscard]] inline bool has_VBOs(size_t triangle_indices_idx) const
{
assert(triangle_indices_idx < this->m_triangle_patches.size());
return this->m_triangle_indices_VBO_ids[triangle_indices_idx] != 0;
}
std::vector<float> m_patch_vertices;
std::vector<TrianglePatch> m_triangle_patches;
// When the triangle indices are loaded into the graphics card as Vertex Buffer Objects,
// the above mentioned std::vectors are cleared and the following variables keep their original length.
std::vector<size_t> m_triangle_indices_sizes;
// IDs of the Vertex Array Objects, into which the geometry has been loaded.
// Zero if the VBOs are not sent to GPU yet.
unsigned int m_vertices_VBO_id{ 0 };
std::vector<unsigned int> m_triangle_indices_VBO_ids;
std::vector<std::array<float, 4>> m_ebt_colors;
bool m_filter_state = false;
private:
void update_render_data();
void render(int buffer_idx);
};
// Following class is a base class for a gizmo with ability to paint on mesh
// using circular blush (such as FDM supports gizmo and seam painting gizmo).
// The purpose is not to duplicate code related to mesh painting.
class GLGizmoPainterBase : public GLGizmoBase
{
private:
ObjectID m_old_mo_id;
size_t m_old_volumes_size = 0;
void on_render() override {}
void on_render_for_picking() override {}
public:
GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
~GLGizmoPainterBase() override = default;
virtual void set_painter_gizmo_data(const Selection& selection);
virtual bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
// Following function renders the triangles and cursor. Having this separated
// from usual on_render method allows to render them before transparent
// objects, so they can be seen inside them. The usual on_render is called
// after all volumes (including transparent ones) are rendered.
virtual void render_painter_gizmo() const = 0;
virtual const float get_cursor_radius_min() const { return CursorRadiusMin; }
virtual const float get_cursor_radius_max() const { return CursorRadiusMax; }
virtual const float get_cursor_radius_step() const { return CursorRadiusStep; }
// BBS: just for CursorType::HeightRange
virtual const float get_cursor_height_min() const { return CursorHeightMin; }
virtual const float get_cursor_height_max() const { return CursorHeightMax; }
virtual const float get_cursor_height_step() const { return CursorHeightStep; }
protected:
virtual void render_triangles(const Selection& selection) const;
void render_cursor() const;
void render_cursor_circle() const;
void render_cursor_sphere(const Transform3d& trafo) const;
// BBS
void render_cursor_height_range(const Transform3d& trafo) const;
//BBS: add logic to distinguish the first_time_update and later_update
virtual void update_model_object() = 0;
virtual void update_from_model_object(bool first_update) = 0;
virtual std::array<float, 4> get_cursor_sphere_left_button_color() const { return {0.f, 0.f, 1.f, 0.25f}; }
virtual std::array<float, 4> get_cursor_sphere_right_button_color() const { return {1.f, 0.f, 0.f, 0.25f}; }
// BBS
virtual std::array<float, 4> get_cursor_hover_color() const { return { 0.f, 0.f, 0.f, 0.25f }; }
virtual EnforcerBlockerType get_left_button_state_type() const { return EnforcerBlockerType::ENFORCER; }
virtual EnforcerBlockerType get_right_button_state_type() const { return EnforcerBlockerType::BLOCKER; }
float m_cursor_radius = 1.f;
// BBS
float m_cursor_height = 0.2f;
static constexpr float CursorRadiusMin = 0.4f; // cannot be zero
static constexpr float CursorRadiusMax = 8.f;
static constexpr float CursorRadiusStep = 0.2f;
static constexpr float CursorHeightMin = 0.2f; // cannot be zero
static constexpr float CursorHeightMax = 8.f;
static constexpr float CursorHeightStep = 0.2f;
// For each model-part volume, store status and division of the triangles.
std::vector<std::unique_ptr<TriangleSelectorGUI>> m_triangle_selectors;
TriangleSelector::CursorType m_cursor_type = TriangleSelector::SPHERE;
enum class ToolType {
BRUSH,
BUCKET_FILL,
SMART_FILL,
// BBS
FRAGMENT_FILTER,
};
struct ProjectedMousePosition
{
Vec3f mesh_hit;
int mesh_idx;
size_t facet_idx;
};
// BBS: projected result of mouse height range for a mesh
struct ProjectedHeightRange
{
float z_world;
int mesh_idx;
size_t first_facet_idx;
};
bool m_triangle_splitting_enabled = true;
ToolType m_tool_type = ToolType::BRUSH;
float m_smart_fill_angle = 30.f;
bool m_paint_on_overhangs_only = false;
float m_highlight_by_angle_threshold_deg = 0.f;
static constexpr float SmartFillAngleMin = 0.0f;
static constexpr float SmartFillAngleMax = 90.f;
static constexpr float SmartFillAngleStep = 1.f;
// It stores the value of the previous mesh_id to which the seed fill was applied.
// It is used to detect when the mouse has moved from one volume to another one.
int m_seed_fill_last_mesh_id = -1;
enum class Button {
None,
Left,
Right
};
struct ClippingPlaneDataWrapper
{
std::array<float, 4> clp_dataf;
std::array<float, 2> z_range;
};
ClippingPlaneDataWrapper get_clipping_plane_data() const;
TriangleSelector::ClippingPlane get_clipping_plane_in_volume_coordinates(const Transform3d &trafo) const;
private:
std::vector<std::vector<ProjectedMousePosition>> get_projected_mouse_positions(const Vec2d &mouse_position, double resolution, const std::vector<Transform3d> &trafo_matrices) const;
std::vector<ProjectedHeightRange> get_projected_height_range(const Vec2d& mouse_position, double resolution, const std::vector<const ModelVolume*>& part_volumes, const std::vector<Transform3d>& trafo_matrices) const;
bool is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const;
void update_raycast_cache(const Vec2d& mouse_position,
const Camera& camera,
const std::vector<Transform3d>& trafo_matrices) const;
GLIndexedVertexArray m_vbo_sphere;
bool m_internal_stack_active = false;
bool m_schedule_update = false;
Vec2d m_last_mouse_click = Vec2d::Zero();
Button m_button_down = Button::None;
EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state)
// Following cache holds result of a raycast query. The queries are asked
// during rendering the sphere cursor and painting, this saves repeated
// raycasts when the mouse position is the same as before.
struct RaycastResult {
Vec2d mouse_position;
int mesh_id;
Vec3f hit;
size_t facet;
};
mutable RaycastResult m_rr;
// BBS
struct CutContours
{
TriangleMesh mesh;
GLModel contours;
double cut_z{ 0.0 };
Vec3d position{ Vec3d::Zero() };
Vec3d shift{ Vec3d::Zero() };
ObjectID object_id;
int instance_idx{ -1 };
};
mutable CutContours m_cut_contours;
BoundingBoxf3 bounding_box() const;
void update_contours(float cursor_z, float max_z) const;
protected:
void on_set_state() override;
void on_start_dragging() override {}
void on_stop_dragging() override {}
virtual void on_opening() = 0;
virtual void on_shutdown() = 0;
virtual PainterGizmoType get_painter_type() const = 0;
bool on_is_activable() const override;
bool on_is_selectable() const override;
void on_load(cereal::BinaryInputArchive& ar) override;
void on_save(cereal::BinaryOutputArchive& ar) const override {}
CommonGizmosDataID on_get_requirements() const override;
bool wants_enter_leave_snapshots() const override { return true; }
virtual wxString handle_snapshot_action_name(bool shift_down, Button button_down) const = 0;
friend class ::Slic3r::GUI::GLGizmoMmuSegmentation;
};
} // namespace Slic3r::GUI
#endif // slic3r_GLGizmoPainterBase_hpp_

View file

@ -0,0 +1,587 @@
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
#include "GLGizmoRotate.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/ImGuiWrapper.hpp"
#include <GL/glew.h>
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "slic3r/GUI/Jobs/RotoptimizeJob.hpp"
namespace Slic3r {
namespace GUI {
const float GLGizmoRotate::Offset = 5.0f;
const unsigned int GLGizmoRotate::CircleResolution = 64;
const unsigned int GLGizmoRotate::AngleResolution = 64;
const unsigned int GLGizmoRotate::ScaleStepsCount = 72;
const float GLGizmoRotate::ScaleStepRad = 2.0f * (float)PI / GLGizmoRotate::ScaleStepsCount;
const unsigned int GLGizmoRotate::ScaleLongEvery = 2;
const float GLGizmoRotate::ScaleLongTooth = 0.1f; // in percent of radius
const unsigned int GLGizmoRotate::SnapRegionsCount = 8;
const float GLGizmoRotate::GrabberOffset = 0.15f; // in percent of radius
GLGizmoRotate::GLGizmoRotate(GLCanvas3D& parent, GLGizmoRotate::Axis axis)
: GLGizmoBase(parent, "", -1)
, m_axis(axis)
, m_angle(0.0)
, m_center(0.0, 0.0, 0.0)
, m_radius(0.0f)
, m_snap_coarse_in_radius(0.0f)
, m_snap_coarse_out_radius(0.0f)
, m_snap_fine_in_radius(0.0f)
, m_snap_fine_out_radius(0.0f)
{
}
GLGizmoRotate::GLGizmoRotate(const GLGizmoRotate& other)
: GLGizmoBase(other.m_parent, other.m_icon_filename, other.m_sprite_id)
, m_axis(other.m_axis)
, m_angle(other.m_angle)
, m_center(other.m_center)
, m_radius(other.m_radius)
, m_snap_coarse_in_radius(other.m_snap_coarse_in_radius)
, m_snap_coarse_out_radius(other.m_snap_coarse_out_radius)
, m_snap_fine_in_radius(other.m_snap_fine_in_radius)
, m_snap_fine_out_radius(other.m_snap_fine_out_radius)
{
}
void GLGizmoRotate::set_angle(double angle)
{
if (std::abs(angle - 2.0 * (double)PI) < EPSILON)
angle = 0.0;
m_angle = angle;
}
std::string GLGizmoRotate::get_tooltip() const
{
std::string axis;
switch (m_axis)
{
case X: { axis = "X"; break; }
case Y: { axis = "Y"; break; }
case Z: { axis = "Z"; break; }
}
return (m_hover_id == 0 || m_grabbers[0].dragging) ? axis + ": " + format((float)Geometry::rad2deg(m_angle), 2) : "";
}
bool GLGizmoRotate::on_init()
{
m_grabbers.push_back(Grabber());
return true;
}
void GLGizmoRotate::on_start_dragging()
{
const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box();
m_center = box.center();
m_radius = Offset + box.radius();
m_snap_coarse_in_radius = m_radius / 3.0f;
m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius;
m_snap_fine_in_radius = m_radius;
m_snap_fine_out_radius = m_snap_fine_in_radius + m_radius * ScaleLongTooth;
}
void GLGizmoRotate::on_update(const UpdateData& data)
{
Vec2d mouse_pos = to_2d(mouse_position_in_local_plane(data.mouse_ray, m_parent.get_selection()));
Vec2d orig_dir = Vec2d::UnitX();
Vec2d new_dir = mouse_pos.normalized();
double theta = ::acos(std::clamp(new_dir.dot(orig_dir), -1.0, 1.0));
if (cross2(orig_dir, new_dir) < 0.0)
theta = 2.0 * (double)PI - theta;
double len = mouse_pos.norm();
// snap to coarse snap region
if ((m_snap_coarse_in_radius <= len) && (len <= m_snap_coarse_out_radius))
{
double step = 2.0 * (double)PI / (double)SnapRegionsCount;
theta = step * (double)std::round(theta / step);
}
else
{
// snap to fine snap region (scale)
if ((m_snap_fine_in_radius <= len) && (len <= m_snap_fine_out_radius))
{
double step = 2.0 * (double)PI / (double)ScaleStepsCount;
theta = step * (double)std::round(theta / step);
}
}
if (theta == 2.0 * (double)PI)
theta = 0.0;
m_angle = theta;
}
void GLGizmoRotate::on_render()
{
if (!m_grabbers[0].enabled)
return;
const Selection& selection = m_parent.get_selection();
const BoundingBoxf3& box = selection.get_bounding_box();
if (m_hover_id != 0 && !m_grabbers[0].dragging) {
m_center = box.center();
m_radius = Offset + box.radius();
m_snap_coarse_in_radius = m_radius / 3.0f;
m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius;
m_snap_fine_in_radius = m_radius;
m_snap_fine_out_radius = m_radius * (1.0f + ScaleLongTooth);
}
glsafe(::glEnable(GL_DEPTH_TEST));
glsafe(::glPushMatrix());
transform_to_local(selection);
glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f));
glsafe(::glColor4fv((m_hover_id != -1) ? m_drag_color.data() : m_highlight_color.data()));
render_circle();
if (m_hover_id != -1) {
render_scale();
render_snap_radii();
render_reference_radius();
}
glsafe(::glColor4fv(m_highlight_color.data()));
if (m_hover_id != -1)
render_angle();
render_grabber(box);
render_grabber_extension(box, false);
glsafe(::glPopMatrix());
}
void GLGizmoRotate::on_render_for_picking()
{
const Selection& selection = m_parent.get_selection();
glsafe(::glDisable(GL_DEPTH_TEST));
glsafe(::glPushMatrix());
transform_to_local(selection);
const BoundingBoxf3& box = selection.get_bounding_box();
render_grabbers_for_picking(box);
render_grabber_extension(box, true);
glsafe(::glPopMatrix());
}
//BBS: add input window for move
void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limit)
{
//if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA)
// return;
if (m_object_manipulation)
m_object_manipulation->do_render_rotate_window(m_imgui, "Rotate", x, y, bottom_limit);
//RotoptimzeWindow popup{m_imgui, m_rotoptimizewin_state, {x, y, bottom_limit}};
}
void GLGizmoRotate3D::load_rotoptimize_state()
{
std::string accuracy_str =
wxGetApp().app_config->get("sla_auto_rotate", "accuracy");
std::string method_str =
wxGetApp().app_config->get("sla_auto_rotate", "method_id");
if (!accuracy_str.empty()) {
float accuracy = std::stof(accuracy_str);
accuracy = std::max(0.f, std::min(accuracy, 1.f));
m_rotoptimizewin_state.accuracy = accuracy;
}
if (!method_str.empty()) {
int method_id = std::stoi(method_str);
if (method_id < int(RotoptimizeJob::get_methods_count()))
m_rotoptimizewin_state.method_id = method_id;
}
}
void GLGizmoRotate::render_circle() const
{
::glBegin(GL_LINE_LOOP);
for (unsigned int i = 0; i < ScaleStepsCount; ++i)
{
float angle = (float)i * ScaleStepRad;
float x = ::cos(angle) * m_radius;
float y = ::sin(angle) * m_radius;
float z = 0.0f;
::glVertex3f((GLfloat)x, (GLfloat)y, (GLfloat)z);
}
glsafe(::glEnd());
}
void GLGizmoRotate::render_scale() const
{
float out_radius_long = m_snap_fine_out_radius;
float out_radius_short = m_radius * (1.0f + 0.5f * ScaleLongTooth);
::glBegin(GL_LINES);
for (unsigned int i = 0; i < ScaleStepsCount; ++i)
{
float angle = (float)i * ScaleStepRad;
float cosa = ::cos(angle);
float sina = ::sin(angle);
float in_x = cosa * m_radius;
float in_y = sina * m_radius;
float in_z = 0.0f;
float out_x = (i % ScaleLongEvery == 0) ? cosa * out_radius_long : cosa * out_radius_short;
float out_y = (i % ScaleLongEvery == 0) ? sina * out_radius_long : sina * out_radius_short;
float out_z = 0.0f;
::glVertex3f((GLfloat)in_x, (GLfloat)in_y, (GLfloat)in_z);
::glVertex3f((GLfloat)out_x, (GLfloat)out_y, (GLfloat)out_z);
}
glsafe(::glEnd());
}
void GLGizmoRotate::render_snap_radii() const
{
float step = 2.0f * (float)PI / (float)SnapRegionsCount;
float in_radius = m_radius / 3.0f;
float out_radius = 2.0f * in_radius;
::glBegin(GL_LINES);
for (unsigned int i = 0; i < SnapRegionsCount; ++i)
{
float angle = (float)i * step;
float cosa = ::cos(angle);
float sina = ::sin(angle);
float in_x = cosa * in_radius;
float in_y = sina * in_radius;
float in_z = 0.0f;
float out_x = cosa * out_radius;
float out_y = sina * out_radius;
float out_z = 0.0f;
::glVertex3f((GLfloat)in_x, (GLfloat)in_y, (GLfloat)in_z);
::glVertex3f((GLfloat)out_x, (GLfloat)out_y, (GLfloat)out_z);
}
glsafe(::glEnd());
}
void GLGizmoRotate::render_reference_radius() const
{
::glBegin(GL_LINES);
::glVertex3f(0.0f, 0.0f, 0.0f);
::glVertex3f((GLfloat)(m_radius * (1.0f + GrabberOffset)), 0.0f, 0.0f);
glsafe(::glEnd());
}
void GLGizmoRotate::render_angle() const
{
float step_angle = (float)m_angle / AngleResolution;
float ex_radius = m_radius * (1.0f + GrabberOffset);
::glBegin(GL_LINE_STRIP);
for (unsigned int i = 0; i <= AngleResolution; ++i)
{
float angle = (float)i * step_angle;
float x = ::cos(angle) * ex_radius;
float y = ::sin(angle) * ex_radius;
float z = 0.0f;
::glVertex3f((GLfloat)x, (GLfloat)y, (GLfloat)z);
}
glsafe(::glEnd());
}
void GLGizmoRotate::render_grabber(const BoundingBoxf3& box) const
{
double grabber_radius = (double)m_radius * (1.0 + (double)GrabberOffset);
m_grabbers[0].center = Vec3d(::cos(m_angle) * grabber_radius, ::sin(m_angle) * grabber_radius, 0.0);
m_grabbers[0].angles(2) = m_angle;
m_grabbers[0].color = AXES_COLOR[m_axis];
m_grabbers[0].hover_color = AXES_HOVER_COLOR[m_axis];
glsafe(::glColor4fv((m_hover_id != -1) ? m_drag_color.data() : m_highlight_color.data()));
::glBegin(GL_LINES);
::glVertex3f(0.0f, 0.0f, 0.0f);
::glVertex3dv(m_grabbers[0].center.data());
glsafe(::glEnd());
m_grabbers[0].color = m_highlight_color;
render_grabbers(box);
}
void GLGizmoRotate::render_grabber_extension(const BoundingBoxf3& box, bool picking) const
{
double size = 0.75 * GLGizmoBase::Grabber::FixedGrabberSize * GLGizmoBase::INV_ZOOM;
//float mean_size = (float)((box.size()(0) + box.size()(1) + box.size()(2)) / 3.0);
//double size = m_dragging ? (double)m_grabbers[0].get_dragging_half_size(mean_size) : (double)m_grabbers[0].get_half_size(mean_size);
std::array<float, 4> color = m_grabbers[0].color;
if (!picking && m_hover_id != -1) {
color = m_grabbers[0].hover_color;
//color[0] = 1.0f - color[0];
//color[1] = 1.0f - color[1];
//color[2] = 1.0f - color[2];
}
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
if (shader == nullptr)
return;
const_cast<GLModel*>(&m_cone)->set_color(-1, color);
if (!picking) {
shader->start_using();
shader->set_uniform("emission_factor", 0.1f);
}
glsafe(::glPushMatrix());
glsafe(::glTranslated(m_grabbers[0].center.x(), m_grabbers[0].center.y(), m_grabbers[0].center.z()));
glsafe(::glRotated(Geometry::rad2deg(m_angle), 0.0, 0.0, 1.0));
glsafe(::glRotated(90.0, 1.0, 0.0, 0.0));
glsafe(::glTranslated(0.0, 0.0, 1.5 * size));
glsafe(::glScaled(0.75 * size, 0.75 * size, 3.0 * size));
m_cone.render();
glsafe(::glPopMatrix());
glsafe(::glPushMatrix());
glsafe(::glTranslated(m_grabbers[0].center.x(), m_grabbers[0].center.y(), m_grabbers[0].center.z()));
glsafe(::glRotated(Geometry::rad2deg(m_angle), 0.0, 0.0, 1.0));
glsafe(::glRotated(-90.0, 1.0, 0.0, 0.0));
glsafe(::glTranslated(0.0, 0.0, 1.5 * size));
glsafe(::glScaled(0.75 * size, 0.75 * size, 3.0 * size));
m_cone.render();
glsafe(::glPopMatrix());
if (! picking)
shader->stop_using();
}
void GLGizmoRotate::transform_to_local(const Selection& selection) const
{
glsafe(::glTranslated(m_center(0), m_center(1), m_center(2)));
if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes()) {
Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true);
glsafe(::glMultMatrixd(orient_matrix.data()));
}
switch (m_axis)
{
case X:
{
glsafe(::glRotatef(90.0f, 0.0f, 1.0f, 0.0f));
glsafe(::glRotatef(-90.0f, 0.0f, 0.0f, 1.0f));
break;
}
case Y:
{
glsafe(::glRotatef(-90.0f, 0.0f, 0.0f, 1.0f));
glsafe(::glRotatef(-90.0f, 0.0f, 1.0f, 0.0f));
break;
}
default:
case Z:
{
// no rotation
break;
}
}
}
Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray, const Selection& selection) const
{
double half_pi = 0.5 * (double)PI;
Transform3d m = Transform3d::Identity();
switch (m_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;
}
}
if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes())
m = m * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true).inverse();
m.translate(-m_center);
return transform(mouse_ray, m).intersect_plane(0.0);
}
//BBS: GUI refactor: add obj manipulation
GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id, GizmoObjectManipulation* obj_manipulation)
: GLGizmoBase(parent, icon_filename, sprite_id)
//BBS: GUI refactor: add obj manipulation
, m_object_manipulation(obj_manipulation)
{
m_gizmos.emplace_back(parent, GLGizmoRotate::X);
m_gizmos.emplace_back(parent, GLGizmoRotate::Y);
m_gizmos.emplace_back(parent, GLGizmoRotate::Z);
for (unsigned int i = 0; i < 3; ++i) {
m_gizmos[i].set_group_id(i);
}
load_rotoptimize_state();
}
bool GLGizmoRotate3D::on_init()
{
for (GLGizmoRotate& g : m_gizmos) {
if (!g.init())
return false;
}
for (unsigned int i = 0; i < 3; ++i) {
m_gizmos[i].set_highlight_color(AXES_COLOR[i]);
}
m_shortcut_key = WXK_NONE;
return true;
}
std::string GLGizmoRotate3D::on_get_name() const
{
return _u8L("Rotate");
}
bool GLGizmoRotate3D::on_is_activable() const
{
// BBS: don't support rotate wipe tower
const Selection& selection = m_parent.get_selection();
return !m_parent.get_selection().is_empty() && !selection.is_wipe_tower();
}
void GLGizmoRotate3D::on_start_dragging()
{
if ((0 <= m_hover_id) && (m_hover_id < 3))
m_gizmos[m_hover_id].start_dragging();
}
void GLGizmoRotate3D::on_stop_dragging()
{
if ((0 <= m_hover_id) && (m_hover_id < 3))
m_gizmos[m_hover_id].stop_dragging();
}
void GLGizmoRotate3D::on_render()
{
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
if (m_hover_id == -1 || m_hover_id == 0)
m_gizmos[X].render();
if (m_hover_id == -1 || m_hover_id == 1)
m_gizmos[Y].render();
if (m_hover_id == -1 || m_hover_id == 2)
m_gizmos[Z].render();
}
GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui,
State & state,
const Alignment &alignment)
: m_imgui{imgui}
{
imgui->begin(_L("Optimize orientation"), ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_NoCollapse);
// adjust window position to avoid overlap the view toolbar
float win_h = ImGui::GetWindowHeight();
float x = alignment.x, y = alignment.y;
y = std::min(y, alignment.bottom_limit - win_h);
ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always);
float max_text_w = 0.;
auto padding = ImGui::GetStyle().FramePadding;
padding.x *= 2.f;
padding.y *= 2.f;
for (size_t i = 0; i < RotoptimizeJob::get_methods_count(); ++i) {
float w =
ImGui::CalcTextSize(RotoptimizeJob::get_method_name(i).c_str()).x +
padding.x + ImGui::GetFrameHeight();
max_text_w = std::max(w, max_text_w);
}
ImGui::PushItemWidth(max_text_w);
if (ImGui::BeginCombo("", RotoptimizeJob::get_method_name(state.method_id).c_str())) {
for (size_t i = 0; i < RotoptimizeJob::get_methods_count(); ++i) {
if (ImGui::Selectable(RotoptimizeJob::get_method_name(i).c_str())) {
state.method_id = i;
#ifdef SUPPORT_SLA_AUTO_ROTATE
wxGetApp().app_config->set("sla_auto_rotate",
"method_id",
std::to_string(state.method_id));
#endif SUPPORT_SLA_AUTO_ROTATE
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", RotoptimizeJob::get_method_description(i).c_str());
}
ImGui::EndCombo();
}
ImVec2 sz = ImGui::GetItemRectSize();
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", RotoptimizeJob::get_method_description(state.method_id).c_str());
ImGui::Separator();
auto btn_txt = _L("Apply");
auto btn_txt_sz = ImGui::CalcTextSize(btn_txt.c_str());
ImVec2 button_sz = {btn_txt_sz.x + padding.x, btn_txt_sz.y + padding.y};
ImGui::SetCursorPosX(padding.x + sz.x - button_sz.x);
if (wxGetApp().plater()->is_any_job_running())
imgui->disabled_begin(true);
if ( imgui->button(btn_txt) ) {
wxGetApp().plater()->optimize_rotation();
}
imgui->disabled_end();
}
GLGizmoRotate3D::RotoptimzeWindow::~RotoptimzeWindow()
{
m_imgui->end();
}
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,179 @@
#ifndef slic3r_GLGizmoRotate_hpp_
#define slic3r_GLGizmoRotate_hpp_
#include "GLGizmoBase.hpp"
#include "../Jobs/RotoptimizeJob.hpp"
//BBS: add size adjust related
#include "GizmoObjectManipulation.hpp"
namespace Slic3r {
namespace GUI {
class GLGizmoRotate : public GLGizmoBase
{
static const float Offset;
static const unsigned int CircleResolution;
static const unsigned int AngleResolution;
static const unsigned int ScaleStepsCount;
static const float ScaleStepRad;
static const unsigned int ScaleLongEvery;
static const float ScaleLongTooth;
static const unsigned int SnapRegionsCount;
static const float GrabberOffset;
public:
enum Axis : unsigned char
{
X,
Y,
Z
};
private:
Axis m_axis;
double m_angle;
mutable Vec3d m_center;
mutable float m_radius;
mutable float m_snap_coarse_in_radius;
mutable float m_snap_coarse_out_radius;
mutable float m_snap_fine_in_radius;
mutable float m_snap_fine_out_radius;
public:
GLGizmoRotate(GLCanvas3D& parent, Axis axis);
GLGizmoRotate(const GLGizmoRotate& other);
virtual ~GLGizmoRotate() = default;
double get_angle() const { return m_angle; }
void set_angle(double angle);
std::string get_tooltip() const override;
protected:
bool on_init() override;
std::string on_get_name() const override { return ""; }
void on_start_dragging() override;
void on_update(const UpdateData& data) override;
void on_render() override;
void on_render_for_picking() override;
private:
void render_circle() const;
void render_scale() const;
void render_snap_radii() const;
void render_reference_radius() const;
void render_angle() const;
void render_grabber(const BoundingBoxf3& box) const;
void render_grabber_extension(const BoundingBoxf3& box, bool picking) const;
void transform_to_local(const Selection& selection) const;
// returns the intersection of the mouse ray with the plane perpendicular to the gizmo axis, in local coordinate
Vec3d mouse_position_in_local_plane(const Linef3& mouse_ray, const Selection& selection) const;
};
class GLGizmoRotate3D : public GLGizmoBase
{
// BBS: change to protected for subclass access
protected:
std::vector<GLGizmoRotate> m_gizmos;
//BBS: add size adjust related
GizmoObjectManipulation* m_object_manipulation;
public:
//BBS: add obj manipulation logic
//GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id, GizmoObjectManipulation* obj_manipulation);
Vec3d get_rotation() const { return Vec3d(m_gizmos[X].get_angle(), m_gizmos[Y].get_angle(), m_gizmos[Z].get_angle()); }
void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation(0)); m_gizmos[Y].set_angle(rotation(1)); m_gizmos[Z].set_angle(rotation(2)); }
std::string get_tooltip() const override
{
std::string tooltip = m_gizmos[X].get_tooltip();
if (tooltip.empty())
tooltip = m_gizmos[Y].get_tooltip();
if (tooltip.empty())
tooltip = m_gizmos[Z].get_tooltip();
return tooltip;
}
protected:
bool on_init() override;
std::string on_get_name() const override;
void on_set_state() override
{
for (GLGizmoRotate& g : m_gizmos)
g.set_state(m_state);
}
void on_set_hover_id() override
{
for (int i = 0; i < 3; ++i)
m_gizmos[i].set_hover_id((m_hover_id == i) ? 0 : -1);
}
void on_enable_grabber(unsigned int id) override
{
if (id < 3)
m_gizmos[id].enable_grabber(0);
}
void on_disable_grabber(unsigned int id) override
{
if (id < 3)
m_gizmos[id].disable_grabber(0);
}
bool on_is_activable() const override;
void on_start_dragging() override;
void on_stop_dragging() override;
void on_update(const UpdateData& data) override
{
for (GLGizmoRotate& g : m_gizmos) {
g.update(data);
}
}
void on_render() override;
void on_render_for_picking() override
{
for (GLGizmoRotate& g : m_gizmos) {
g.render_for_picking();
}
}
void on_render_input_window(float x, float y, float bottom_limit) override;
private:
class RotoptimzeWindow {
ImGuiWrapper *m_imgui = nullptr;
public:
struct State {
float accuracy = 1.f;
int method_id = 0;
};
struct Alignment { float x, y, bottom_limit; };
RotoptimzeWindow(ImGuiWrapper * imgui,
State & state,
const Alignment &bottom_limit);
~RotoptimzeWindow();
RotoptimzeWindow(const RotoptimzeWindow&) = delete;
RotoptimzeWindow(RotoptimzeWindow &&) = delete;
RotoptimzeWindow& operator=(const RotoptimzeWindow &) = delete;
RotoptimzeWindow& operator=(RotoptimzeWindow &&) = delete;
};
RotoptimzeWindow::State m_rotoptimizewin_state = {};
void load_rotoptimize_state();
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLGizmoRotate_hpp_

View file

@ -0,0 +1,334 @@
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
#include "GLGizmoScale.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include <GL/glew.h>
#include <wx/utils.h>
namespace Slic3r {
namespace GUI {
const float GLGizmoScale3D::Offset = 5.0f;
//BBS: GUI refactor: add obj manipulation
GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id, GizmoObjectManipulation* obj_manipulation)
: GLGizmoBase(parent, icon_filename, sprite_id)
, m_scale(Vec3d::Ones())
, m_offset(Vec3d::Zero())
, m_snap_step(0.05)
//BBS: GUI refactor: add obj manipulation
, m_object_manipulation(obj_manipulation)
{
}
std::string GLGizmoScale3D::get_tooltip() const
{
const Selection& selection = m_parent.get_selection();
bool single_instance = selection.is_single_full_instance();
bool single_volume = selection.is_single_modifier() || selection.is_single_volume();
Vec3f scale = 100.0f * Vec3f::Ones();
if (single_instance)
scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor().cast<float>();
else if (single_volume)
scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_scaling_factor().cast<float>();
if (m_hover_id == 0 || m_hover_id == 1 || m_grabbers[0].dragging || m_grabbers[1].dragging)
return "X: " + format(scale(0), 4) + "%";
else if (m_hover_id == 2 || m_hover_id == 3 || m_grabbers[2].dragging || m_grabbers[3].dragging)
return "Y: " + format(scale(1), 4) + "%";
else if (m_hover_id == 4 || m_hover_id == 5 || m_grabbers[4].dragging || m_grabbers[5].dragging)
return "Z: " + format(scale(2), 4) + "%";
else if (m_hover_id == 6 || m_hover_id == 7 || m_hover_id == 8 || m_hover_id == 9 ||
m_grabbers[6].dragging || m_grabbers[7].dragging || m_grabbers[8].dragging || m_grabbers[9].dragging)
{
std::string tooltip = "X: " + format(scale(0), 2) + "%\n";
tooltip += "Y: " + format(scale(1), 2) + "%\n";
tooltip += "Z: " + format(scale(2), 2) + "%";
return tooltip;
}
else
return "";
}
bool GLGizmoScale3D::on_init()
{
for (int i = 0; i < 10; ++i)
{
m_grabbers.push_back(Grabber());
}
double half_pi = 0.5 * (double)PI;
// x axis
m_grabbers[0].angles(1) = half_pi;
m_grabbers[1].angles(1) = half_pi;
// y axis
m_grabbers[2].angles(0) = half_pi;
m_grabbers[3].angles(0) = half_pi;
// BBS
m_grabbers[4].enabled = false;
m_shortcut_key = WXK_NONE;
return true;
}
std::string GLGizmoScale3D::on_get_name() const
{
return _u8L("Scale");
}
bool GLGizmoScale3D::on_is_activable() const
{
const Selection& selection = m_parent.get_selection();
return !selection.is_empty() && !selection.is_wipe_tower();
}
void GLGizmoScale3D::on_start_dragging()
{
if (m_hover_id != -1)
{
m_starting.drag_position = m_grabbers[m_hover_id].center;
m_starting.ctrl_down = wxGetKeyState(WXK_CONTROL);
m_starting.box = (m_starting.ctrl_down && (m_hover_id < 6)) ? m_box : m_parent.get_selection().get_bounding_box();
const Vec3d& center = m_starting.box.center();
m_starting.pivots[0] = m_transform * Vec3d(m_starting.box.max(0), center(1), center(2));
m_starting.pivots[1] = m_transform * Vec3d(m_starting.box.min(0), center(1), center(2));
m_starting.pivots[2] = m_transform * Vec3d(center(0), m_starting.box.max(1), center(2));
m_starting.pivots[3] = m_transform * Vec3d(center(0), m_starting.box.min(1), center(2));
m_starting.pivots[4] = m_transform * Vec3d(center(0), center(1), m_starting.box.max(2));
m_starting.pivots[5] = m_transform * Vec3d(center(0), center(1), m_starting.box.min(2));
}
}
void GLGizmoScale3D::on_update(const UpdateData& data)
{
if ((m_hover_id == 0) || (m_hover_id == 1))
do_scale_along_axis(X, data);
else if ((m_hover_id == 2) || (m_hover_id == 3))
do_scale_along_axis(Y, data);
else if ((m_hover_id == 4) || (m_hover_id == 5))
do_scale_along_axis(Z, data);
else if (m_hover_id >= 6)
do_scale_uniform(data);
}
void GLGizmoScale3D::on_render()
{
const Selection& selection = m_parent.get_selection();
bool single_instance = selection.is_single_full_instance();
bool single_volume = selection.is_single_modifier() || selection.is_single_volume();
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
glsafe(::glEnable(GL_DEPTH_TEST));
m_box.reset();
m_transform = Transform3d::Identity();
// Transforms grabbers' offsets to world refefence system
Transform3d offsets_transform = Transform3d::Identity();
m_offsets_transform = Transform3d::Identity();
Vec3d angles = Vec3d::Zero();
if (single_instance) {
// calculate bounding box in instance local reference system
const Selection::IndicesList& idxs = selection.get_volume_idxs();
for (unsigned int idx : idxs) {
const GLVolume* vol = selection.get_volume(idx);
m_box.merge(vol->bounding_box().transformed(vol->get_volume_transformation().get_matrix()));
}
// gets transform from first selected volume
const GLVolume* v = selection.get_volume(*idxs.begin());
m_transform = v->get_instance_transformation().get_matrix();
// gets angles from first selected volume
angles = v->get_instance_rotation();
// consider rotation+mirror only components of the transform for offsets
offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror());
m_offsets_transform = offsets_transform;
}
else if (single_volume) {
const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin());
m_box = v->bounding_box();
m_transform = v->world_matrix();
angles = Geometry::extract_euler_angles(m_transform);
// consider rotation+mirror only components of the transform for offsets
offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror());
m_offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), v->get_volume_rotation(), Vec3d::Ones(), v->get_volume_mirror());
}
else
m_box = selection.get_bounding_box();
const Vec3d& center = m_box.center();
Vec3d offset_x = offsets_transform * Vec3d((double)Offset, 0.0, 0.0);
Vec3d offset_y = offsets_transform * Vec3d(0.0, (double)Offset, 0.0);
Vec3d offset_z = offsets_transform * Vec3d(0.0, 0.0, (double)Offset);
bool ctrl_down = (m_dragging && m_starting.ctrl_down) || (!m_dragging && wxGetKeyState(WXK_CONTROL));
// x axis
m_grabbers[0].center = m_transform * Vec3d(m_box.min(0), center(1), m_box.min(2));
m_grabbers[1].center = m_transform * Vec3d(m_box.max(0), center(1), m_box.min(2));
// y axis
m_grabbers[2].center = m_transform * Vec3d(center(0), m_box.min(1), m_box.min(2));
m_grabbers[3].center = m_transform * Vec3d(center(0), m_box.max(1), m_box.min(2));
// z axis do not show 4
m_grabbers[4].center = m_transform * Vec3d(center(0), center(1), m_box.min(2));
m_grabbers[4].enabled = false;
m_grabbers[5].center = m_transform * Vec3d(center(0), center(1), m_box.max(2));
// uniform
m_grabbers[6].center = m_transform * Vec3d(m_box.min(0), m_box.min(1), m_box.min(2));
m_grabbers[7].center = m_transform * Vec3d(m_box.max(0), m_box.min(1), m_box.min(2));
m_grabbers[8].center = m_transform * Vec3d(m_box.max(0), m_box.max(1), m_box.min(2));
m_grabbers[9].center = m_transform * Vec3d(m_box.min(0), m_box.max(1), m_box.min(2));
for (int i = 0; i < 6; ++i) {
m_grabbers[i].color = AXES_COLOR[i/2];
m_grabbers[i].hover_color = AXES_HOVER_COLOR[i/2];
}
for (int i = 6; i < 10; ++i) {
m_grabbers[i].color = GRABBER_UNIFORM_COL;
m_grabbers[i].hover_color = GRABBER_UNIFORM_HOVER_COL;
}
// sets grabbers orientation
for (int i = 0; i < 10; ++i) {
m_grabbers[i].angles = angles;
}
glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f));
const BoundingBoxf3& selection_box = selection.get_bounding_box();
float grabber_mean_size = (float)((selection_box.size()(0) + selection_box.size()(1) + selection_box.size()(2)) / 3.0);
//draw connections
if (single_instance || single_volume) {
glsafe(::glColor4fv(m_grabbers[4].color.data()));
render_grabbers_connection(4, 5);
}
glsafe(::glColor4fv(m_grabbers[2].color.data()));
render_grabbers_connection(6, 7);
render_grabbers_connection(8, 9);
glsafe(::glColor4fv(m_grabbers[0].color.data()));
render_grabbers_connection(7, 8);
render_grabbers_connection(9, 6);
// draw grabbers
render_grabbers(grabber_mean_size);
}
void GLGizmoScale3D::on_render_for_picking()
{
glsafe(::glDisable(GL_DEPTH_TEST));
render_grabbers_for_picking(m_parent.get_selection().get_bounding_box());
}
void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int id_2) const
{
unsigned int grabbers_count = (unsigned int)m_grabbers.size();
if ((id_1 < grabbers_count) && (id_2 < grabbers_count))
{
glLineStipple(1, 0x0FFF);
glEnable(GL_LINE_STIPPLE);
::glBegin(GL_LINES);
::glVertex3dv(m_grabbers[id_1].center.data());
::glVertex3dv(m_grabbers[id_2].center.data());
glsafe(::glEnd());
glDisable(GL_LINE_STIPPLE);
}
}
//BBS: add input window for move
void GLGizmoScale3D::on_render_input_window(float x, float y, float bottom_limit)
{
if (m_object_manipulation)
m_object_manipulation->do_render_scale_input_window(m_imgui, "Scale", x, y, bottom_limit);
}
void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data)
{
double ratio = calc_ratio(data);
if (ratio > 0.0)
{
m_scale(axis) = m_starting.scale(axis) * ratio;
if (m_starting.ctrl_down)
{
double local_offset = 0.5 * (m_scale(axis) - m_starting.scale(axis)) * m_starting.box.size()(axis);
if (m_hover_id == 2 * axis)
local_offset *= -1.0;
Vec3d local_offset_vec;
switch (axis)
{
case X: { local_offset_vec = local_offset * Vec3d::UnitX(); break; }
case Y: { local_offset_vec = local_offset * Vec3d::UnitY(); break; }
case Z: { local_offset_vec = local_offset * Vec3d::UnitZ(); break; }
default: break;
}
m_offset = m_offsets_transform * local_offset_vec;
}
else
m_offset = Vec3d::Zero();
}
}
void GLGizmoScale3D::do_scale_uniform(const UpdateData& data)
{
double ratio = calc_ratio(data);
if (ratio > 0.0)
{
m_scale = m_starting.scale * ratio;
m_offset = Vec3d::Zero();
}
}
double GLGizmoScale3D::calc_ratio(const UpdateData& data) const
{
double ratio = 0.0;
Vec3d pivot = (m_starting.ctrl_down && (m_hover_id < 6)) ? m_starting.pivots[m_hover_id] : m_starting.box.center();
Vec3d starting_vec = m_starting.drag_position - pivot;
double len_starting_vec = starting_vec.norm();
if (len_starting_vec != 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 + (m_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 - m_starting.drag_position;
// finds projection of the vector along the staring direction
double proj = inters_vec.dot(starting_vec.normalized());
ratio = (len_starting_vec + proj) / len_starting_vec;
}
if (wxGetKeyState(WXK_SHIFT))
ratio = m_snap_step * (double)std::round(ratio / m_snap_step);
return ratio;
}
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,80 @@
#ifndef slic3r_GLGizmoScale_hpp_
#define slic3r_GLGizmoScale_hpp_
#include "GLGizmoBase.hpp"
//BBS: add size adjust related
#include "GizmoObjectManipulation.hpp"
#include "libslic3r/BoundingBox.hpp"
namespace Slic3r {
namespace GUI {
class GLGizmoScale3D : public GLGizmoBase
{
static const float Offset;
struct StartingData
{
Vec3d scale;
Vec3d drag_position;
BoundingBoxf3 box;
Vec3d pivots[6];
bool ctrl_down;
StartingData() : scale(Vec3d::Ones()), drag_position(Vec3d::Zero()), ctrl_down(false) { for (int i = 0; i < 5; ++i) { pivots[i] = Vec3d::Zero(); } }
};
mutable BoundingBoxf3 m_box;
mutable Transform3d m_transform;
// Transforms grabbers offsets to the proper reference system (world for instances, instance for volumes)
mutable Transform3d m_offsets_transform;
Vec3d m_scale;
Vec3d m_offset;
double m_snap_step;
StartingData m_starting;
//BBS: add size adjust related
GizmoObjectManipulation* m_object_manipulation;
public:
//BBS: add obj manipulation logic
//GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id, GizmoObjectManipulation* obj_manipulation);
double get_snap_step(double step) const { return m_snap_step; }
void set_snap_step(double step) { m_snap_step = step; }
const Vec3d& get_scale() const { return m_scale; }
void set_scale(const Vec3d& scale) { m_starting.scale = scale; m_scale = scale; }
const Vec3d& get_offset() const { return m_offset; }
std::string get_tooltip() const override;
protected:
virtual bool on_init() override;
virtual std::string on_get_name() const override;
virtual bool on_is_activable() const override;
virtual void on_start_dragging() override;
virtual void on_update(const UpdateData& data) override;
virtual void on_render() override;
virtual void on_render_for_picking() override;
//BBS: GUI refactor: add object manipulation
virtual void on_render_input_window(float x, float y, float bottom_limit);
private:
void render_grabbers_connection(unsigned int id_1, unsigned int id_2) const;
void do_scale_along_axis(Axis axis, const UpdateData& data);
void do_scale_uniform(const UpdateData& data);
double calc_ratio(const UpdateData& data) const;
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLGizmoScale_hpp_

View file

@ -0,0 +1,341 @@
#include "GLGizmoSeam.hpp"
#include "libslic3r/Model.hpp"
//#include "slic3r/GUI/3DScene.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/ImGuiWrapper.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/Utils/UndoRedo.hpp"
#include <GL/glew.h>
namespace Slic3r::GUI {
void GLGizmoSeam::on_shutdown()
{
m_parent.toggle_model_objects_visibility(true);
}
bool GLGizmoSeam::on_init()
{
m_shortcut_key = WXK_CONTROL_P;
m_desc["clipping_of_view"] = _L("Clipping of view") + ": ";
m_desc["reset_direction"] = _L("Reset direction");
m_desc["cursor_size"] = _L("Brush size") + ": ";
m_desc["cursor_type"] = _L("Brush shape") + ": ";
m_desc["enforce_caption"] = _L("Left mouse button") + ": ";
m_desc["enforce"] = _L("Enforce seam");
m_desc["block_caption"] = _L("Right mouse button") + ": ";
m_desc["block"] = _L("Block seam");
m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": ";
m_desc["remove"] = _L("Remove selection");
m_desc["remove_all"] = _L("Remove all selection");
m_desc["circle"] = _L("Circle");
m_desc["sphere"] = _L("Sphere");
return true;
}
std::string GLGizmoSeam::on_get_name() const
{
return _u8L("Seam painting");
}
void GLGizmoSeam::render_painter_gizmo() const
{
const Selection& selection = m_parent.get_selection();
glsafe(::glEnable(GL_BLEND));
glsafe(::glEnable(GL_DEPTH_TEST));
render_triangles(selection);
m_c->object_clipper()->render_cut();
m_c->instances_hider()->render_cut();
render_cursor();
glsafe(::glDisable(GL_BLEND));
}
void GLGizmoSeam::render_triangles(const Selection& selection) const
{
ClippingPlaneDataWrapper clp_data = this->get_clipping_plane_data();
auto* shader = wxGetApp().get_shader("mm_gouraud");
if (!shader)
return;
shader->start_using();
shader->set_uniform("clipping_plane", clp_data.clp_dataf);
shader->set_uniform("z_range", clp_data.z_range);
ScopeGuard guard([shader]() { if (shader) shader->stop_using(); });
const ModelObject* mo = m_c->selection_info()->model_object();
int mesh_id = -1;
for (const ModelVolume* mv : mo->volumes) {
if (!mv->is_model_part())
continue;
++mesh_id;
const Transform3d trafo_matrix = mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix() * mv->get_matrix();
bool is_left_handed = trafo_matrix.matrix().determinant() < 0.;
if (is_left_handed)
glsafe(::glFrontFace(GL_CW));
glsafe(::glPushMatrix());
glsafe(::glMultMatrixd(trafo_matrix.data()));
float normal_z = -::cos(Geometry::deg2rad(m_highlight_by_angle_threshold_deg));
Matrix3f normal_matrix = static_cast<Matrix3f>(trafo_matrix.matrix().block(0, 0, 3, 3).inverse().transpose().cast<float>());
shader->set_uniform("volume_world_matrix", trafo_matrix);
shader->set_uniform("volume_mirrored", is_left_handed);
shader->set_uniform("slope.actived", m_parent.is_using_slope());
shader->set_uniform("slope.volume_world_normal_matrix", static_cast<Matrix3f>(trafo_matrix.matrix().block(0, 0, 3, 3).inverse().transpose().cast<float>()));
shader->set_uniform("slope.normal_z", normal_z);
m_triangle_selectors[mesh_id]->render(m_imgui);
glsafe(::glPopMatrix());
if (is_left_handed)
glsafe(::glFrontFace(GL_CCW));
}
}
void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
{
if (! m_c->selection_info()->model_object())
return;
const float approx_height = m_imgui->scaled(12.5f);
y = std::min(y, bottom_limit - approx_height);
//BBS: GUI refactor: move gizmo to the right
#if BBS_TOOLBAR_ON_TOP
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always, 0.0f, 0.0f);
#else
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always, 1.0f, 0.0f);
#endif
//m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
//BBS
ImGuiWrapper::push_toolbar_style();
m_imgui->begin(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 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 cursor_size_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f);
const float cursor_type_radio_left = m_imgui->calc_text_size(m_desc["cursor_type"]).x + m_imgui->scaled(1.f);
const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f);
const float cursor_type_radio_circle = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f);
const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f);
const float minimal_slider_width = m_imgui->scaled(4.f);
float caption_max = 0.f;
float total_text_max = 0.f;
for (const auto &t : std::array<std::string, 3>{"enforce", "block", "remove"}) {
caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x);
total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x);
}
total_text_max += caption_max + m_imgui->scaled(1.f);
caption_max += m_imgui->scaled(1.f);
const float sliders_left_width = std::max(cursor_size_slider_left, clipping_slider_left);
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
const float slider_icon_width = m_imgui->get_slider_icon_size().x;
float window_width = minimal_slider_width + sliders_left_width + slider_icon_width;
#else
float window_width = minimal_slider_width + sliders_left_width;
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
window_width = std::max(window_width, total_text_max);
window_width = std::max(window_width, button_width);
window_width = std::max(window_width, cursor_type_radio_left + cursor_type_radio_sphere + cursor_type_radio_circle);
auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) {
//BBS set text colored to BLUE_LIGHT
m_imgui->text_colored(ImGuiWrapper::COL_BLUE_LIGHT, caption);
ImGui::SameLine(caption_max);
m_imgui->text(text);
};
for (const auto &t : std::array<std::string, 3>{"enforce", "block", "remove"})
draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t));
ImGui::Separator();
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("cursor_size"));
ImGui::SameLine(sliders_left_width);
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true, _L("Alt + Mouse wheel"));
#else
ImGui::PushItemWidth(window_width - sliders_left_width);
m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f");
if (ImGui::IsItemHovered())
m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width);
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("cursor_type"));
float cursor_type_offset = cursor_type_radio_left + (window_width - cursor_type_radio_left - cursor_type_radio_sphere - cursor_type_radio_circle + m_imgui->scaled(0.5f)) / 2.f;
ImGui::SameLine(cursor_type_offset);
ImGui::PushItemWidth(cursor_type_radio_sphere);
if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE))
m_cursor_type = TriangleSelector::CursorType::SPHERE;
if (ImGui::IsItemHovered())
m_imgui->tooltip(_L("Paints all facets inside, regardless of their orientation."), max_tooltip_width);
ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere);
ImGui::PushItemWidth(cursor_type_radio_circle);
if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE))
m_cursor_type = TriangleSelector::CursorType::CIRCLE;
if (ImGui::IsItemHovered())
m_imgui->tooltip(_L("Ignores facets facing away from the camera."), max_tooltip_width);
ImGui::Separator();
if (m_c->object_clipper()->get_position() == 0.f) {
ImGui::AlignTextToFramePadding();
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);
});
}
}
auto clp_dist = float(m_c->object_clipper()->get_position());
ImGui::SameLine(sliders_left_width);
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, _L("Ctrl + Mouse wheel")))
m_c->object_clipper()->set_position(clp_dist, true);
#else
ImGui::PushItemWidth(window_width - sliders_left_width);
if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f"))
m_c->object_clipper()->set_position(clp_dist, true);
if (ImGui::IsItemHovered())
m_imgui->tooltip(_L("Ctrl + Mouse wheel"), max_tooltip_width);
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
ImGui::Separator();
if (m_imgui->button(m_desc.at("remove_all"))) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Reset selection", UndoRedo::SnapshotType::GizmoAction);
ModelObject *mo = m_c->selection_info()->model_object();
int idx = -1;
for (ModelVolume *mv : mo->volumes)
if (mv->is_model_part()) {
++idx;
m_triangle_selectors[idx]->reset();
m_triangle_selectors[idx]->request_update_render_data();
}
update_model_object();
m_parent.set_as_dirty();
}
m_imgui->end();
//BBS
ImGuiWrapper::pop_toolbar_style();
}
//BBS: remove const
void GLGizmoSeam::update_model_object()
{
bool updated = false;
ModelObject* mo = m_c->selection_info()->model_object();
int idx = -1;
for (ModelVolume* mv : mo->volumes) {
if (! mv->is_model_part())
continue;
++idx;
updated |= mv->seam_facets.set(*m_triangle_selectors[idx].get());
}
if (updated) {
const ModelObjectPtrs& mos = wxGetApp().model().objects;
wxGetApp().obj_list()->update_info_items(std::find(mos.begin(), mos.end(), mo) - mos.begin());
// BBS: backup
Slic3r::save_object_mesh(*mo);
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
}
}
//BBS: add logic to distinguish the first_time_update and later_update
void GLGizmoSeam::update_from_model_object(bool first_update)
{
wxBusyCursor wait;
const ModelObject* mo = m_c->selection_info()->model_object();
m_triangle_selectors.clear();
int volume_id = -1;
std::vector<std::array<float, 4>> ebt_colors;
ebt_colors.push_back(GLVolume::NEUTRAL_COLOR);
ebt_colors.push_back(TriangleSelectorGUI::enforcers_color);
ebt_colors.push_back(TriangleSelectorGUI::blockers_color);
for (const ModelVolume* mv : mo->volumes) {
if (! mv->is_model_part())
continue;
++volume_id;
// This mesh does not account for the possible Z up SLA offset.
const TriangleMesh* mesh = &mv->mesh();
m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorPatch>(*mesh, ebt_colors));
// Reset of TriangleSelector is done inside TriangleSelectorGUI's constructor, so we don't need it to perform it again in deserialize().
m_triangle_selectors.back()->deserialize(mv->seam_facets.get_data(), false);
m_triangle_selectors.back()->request_update_render_data();
}
}
PainterGizmoType GLGizmoSeam::get_painter_type() const
{
return PainterGizmoType::SEAM;
}
wxString GLGizmoSeam::handle_snapshot_action_name(bool shift_down, GLGizmoPainterBase::Button button_down) const
{
wxString action_name;
if (shift_down)
action_name = _L("Remove selection");
else {
if (button_down == Button::Left)
action_name = _L("Enforce seam");
else
action_name = _L("Block seam");
}
return action_name;
}
} // namespace Slic3r::GUI

View file

@ -0,0 +1,50 @@
#ifndef slic3r_GLGizmoSeam_hpp_
#define slic3r_GLGizmoSeam_hpp_
#include "GLGizmoPainterBase.hpp"
namespace Slic3r::GUI {
class GLGizmoSeam : public GLGizmoPainterBase
{
public:
GLGizmoSeam(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoPainterBase(parent, icon_filename, sprite_id) {}
void render_painter_gizmo() const override;
protected:
void on_render_input_window(float x, float y, float bottom_limit) override;
std::string on_get_name() const override;
PainterGizmoType get_painter_type() const override;
void render_triangles(const Selection& selection) const override;
wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override;
std::string get_gizmo_entering_text() const override { return _u8L("Entering Seam painting"); }
std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Seam painting"); }
std::string get_action_snapshot_name() override { return _u8L("Paint-on seam editing"); }
private:
bool on_init() override;
//BBS:remove const
void update_model_object() override;
//BBS: add logic to distinguish the first_time_update and later_update
void update_from_model_object(bool first_update = false) override;
void on_opening() override {}
void on_shutdown() override;
// This map holds all translated description texts, so they can be easily referenced during layout calculations
// etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.
std::map<std::string, wxString> m_desc;
};
} // namespace Slic3r::GUI
#endif // slic3r_GLGizmoSeam_hpp_

View file

@ -0,0 +1,714 @@
#include "GLGizmoSimplify.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/MsgDialog.hpp"
#include "slic3r/GUI/NotificationManager.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/format.hpp"
#include "libslic3r/AppConfig.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/QuadricEdgeCollapse.hpp"
#include <GL/glew.h>
#include <thread>
namespace Slic3r::GUI {
// Extend call after only when Simplify gizmo is still alive
static void call_after_if_active(std::function<void()> fn, GUI_App* app = &wxGetApp())
{
// check application GUI
if (app == nullptr) return;
app->CallAfter([fn, app]() {
// app must exist because it call this
// if (app == nullptr) return;
const Plater *plater = app->plater();
if (plater == nullptr) return;
const GLCanvas3D *canvas = plater->canvas3D();
if (canvas == nullptr) return;
const GLGizmosManager &mng = canvas->get_gizmos_manager();
// check if simplify is still activ gizmo
if (mng.get_current_type() != GLGizmosManager::Simplify) return;
fn();
});
}
static ModelVolume* get_model_volume(const Selection& selection, Model& model)
{
const Selection::IndicesList& idxs = selection.get_volume_idxs();
// only one selected volume
if (idxs.size() != 1)
return nullptr;
const GLVolume* selected_volume = selection.get_volume(*idxs.begin());
if (selected_volume == nullptr)
return nullptr;
const GLVolume::CompositeID& cid = selected_volume->composite_id;
const ModelObjectPtrs& objs = model.objects;
if (cid.object_id < 0 || objs.size() <= static_cast<size_t>(cid.object_id))
return nullptr;
const ModelObject* obj = objs[cid.object_id];
if (cid.volume_id < 0 || obj->volumes.size() <= static_cast<size_t>(cid.volume_id))
return nullptr;
return obj->volumes[cid.volume_id];
}
GLGizmoSimplify::GLGizmoSimplify(GLCanvas3D & parent,
const std::string &icon_filename,
unsigned int sprite_id)
: GLGizmoBase(parent, icon_filename, -1)
, m_volume(nullptr)
, m_show_wireframe(false)
, m_move_to_center(false)
// translation for GUI size
, tr_mesh_name(_u8L("Mesh name"))
, tr_triangles(_u8L("Triangles"))
, tr_detail_level(_u8L("Detail level"))
, tr_decimate_ratio(_u8L("Decimate ratio"))
{}
GLGizmoSimplify::~GLGizmoSimplify()
{
stop_worker_thread_request();
if (m_worker.joinable())
m_worker.join();
m_glmodel.reset();
}
bool GLGizmoSimplify::on_esc_key_down() {
return false;
/*if (!m_is_worker_running)
return false;
stop_worker_thread_request();
return true;*/
}
// while opening needs GLGizmoSimplify to set window position
void GLGizmoSimplify::add_simplify_suggestion_notification(
const std::vector<size_t> &object_ids,
const std::vector<ModelObject*>& objects,
NotificationManager & manager)
{
std::vector<size_t> big_ids;
big_ids.reserve(object_ids.size());
auto is_big_object = [&objects](size_t object_id) {
const uint32_t triangles_to_suggest_simplify = 1000000;
if (object_id >= objects.size()) return false; // out of object index
ModelVolumePtrs &volumes = objects[object_id]->volumes;
if (volumes.size() != 1) return false; // not only one volume
size_t triangle_count = volumes.front()->mesh().its.indices.size();
if (triangle_count < triangles_to_suggest_simplify)
return false; // small volume
return true;
};
std::copy_if(object_ids.begin(), object_ids.end(),
std::back_inserter(big_ids), is_big_object);
if (big_ids.empty()) return;
for (size_t object_id : big_ids) {
std::string t = GUI::format(_L(
"Processing model '%1%' with more than 1M triangles "
"could be slow. It is highly recommended to simplify the model."), objects[object_id]->name);
std::string hypertext = _u8L("Simplify model");
std::function<bool(wxEvtHandler *)> open_simplify =
[object_id](wxEvtHandler *) {
auto plater = wxGetApp().plater();
if (object_id >= plater->model().objects.size()) return true;
Selection &selection = plater->canvas3D()->get_selection();
selection.clear();
selection.add_object((unsigned int) object_id);
auto &manager = plater->get_view3D_canvas3D()->get_gizmos_manager();
bool close_notification = true;
if(!manager.open_gizmo(GLGizmosManager::Simplify))
return close_notification;
GLGizmoSimplify* simplify = dynamic_cast<GLGizmoSimplify*>(manager.get_current());
if (simplify == nullptr) return close_notification;
simplify->set_center_position();
return close_notification;
};
manager.push_simplify_suggestion_notification(
t, objects[object_id]->id(), hypertext, open_simplify);
}
}
std::string GLGizmoSimplify::on_get_name() const
{
return _u8L("Simplify");
}
void GLGizmoSimplify::push_simplify_style()
{
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20.0f, 10.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowTitleAlign, ImVec2(0.05f, 0.50f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 3.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(50 / 255.0f, 58 / 255.0f, 61 / 255.0f, 1.00f)); // 1
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImGuiWrapper::COL_WINDOW_BG); // 2
ImGui::PushStyleColor(ImGuiCol_TitleBg, ImGuiWrapper::COL_TITLE_BG); // 3
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, ImGuiWrapper::COL_TITLE_BG); // 4
ImGui::PushStyleColor(ImGuiCol_Separator, ImGuiWrapper::COL_SEPARATOR); // 5
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.00f, 1.00f, 1.00f, 1.00f)); // 6
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.00f, 0.68f, 0.26f, 1.00f)); // 7
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.00f, 0.68f, 0.26f, 1.00f)); // 8
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(238 / 255.0f, 238 / 255.0f, 238 / 255.0f, 1.00f)); // 9
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(238 / 255.0f, 238 / 255.0f, 238 / 255.0f, 1.00f)); // 10
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(238 / 255.0f, 238 / 255.0f, 238 / 255.0f, 0.00f)); // 11
ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(1.00f, 1.00f, 1.00f, 1.00f)); // 12
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, ImGuiWrapper::COL_GREEN_LIGHT);
ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.00f, 0.68f, 0.26f, 1.00f));
}
void GLGizmoSimplify::pop_simplify_style()
{
ImGui::PopStyleColor(14);
ImGui::PopStyleVar(5);
}
void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limit)
{
create_gui_cfg();
const Selection &selection = m_parent.get_selection();
const ModelVolume *act_volume = get_model_volume(selection, wxGetApp().plater()->model());
if (act_volume == nullptr) {
stop_worker_thread_request();
close();
if (! m_parent.get_selection().is_single_volume()) {
MessageDialog msg((wxWindow*)wxGetApp().mainframe,
_L("Simplification is currently only allowed when a single part is selected"),
_L("Error"));
msg.ShowModal();
}
return;
}
bool is_cancelling = false;
bool is_worker_running = false;
bool is_result_ready = false;
int progress = 0;
{
std::lock_guard lk(m_state_mutex);
is_cancelling = m_state.status == State::cancelling;
is_worker_running = m_state.status == State::running;
is_result_ready = bool(m_state.result);
progress = m_state.progress;
}
// Whether to trigger calculation after rendering is done.
bool start_process = false;
// Check selection of new volume
// Do not reselect object when processing
if (act_volume != m_volume) {
bool change_window_position = (m_volume == nullptr);
// select different model
// close suggestion notification
auto notification_manager = wxGetApp().plater()->get_notification_manager();
notification_manager->remove_simplify_suggestion_with_id(act_volume->get_object()->id());
m_volume = act_volume;
m_configuration.decimate_ratio = 50.; // default value
m_configuration.fix_count_by_ratio(m_volume->mesh().its.indices.size());
init_model(m_volume->mesh().its);
// Start processing. If we switched from another object, process will
// stop the background thread and it will restart itself later.
start_process = true;
// set window position
if (m_move_to_center && change_window_position) {
m_move_to_center = false;
auto parent_size = m_parent.get_canvas_size();
ImVec2 pos(parent_size.get_width() / 2 - m_gui_cfg->window_offset_x,
parent_size.get_height() / 2 - m_gui_cfg->window_offset_y);
ImGui::SetNextWindowPos(pos, ImGuiCond_Always);
}else if (change_window_position) {
ImVec2 pos = ImGui::GetMousePos();
pos.x -= m_gui_cfg->window_offset_x;
pos.y -= m_gui_cfg->window_offset_y;
// minimal top left value
ImVec2 tl(m_gui_cfg->window_padding, m_gui_cfg->window_padding + m_parent.get_main_toolbar_height());
if (pos.x < tl.x) pos.x = tl.x;
if (pos.y < tl.y) pos.y = tl.y;
// maximal bottom right value
auto parent_size = m_parent.get_canvas_size();
ImVec2 br(
parent_size.get_width() - (2 * m_gui_cfg->window_offset_x + m_gui_cfg->window_padding),
parent_size.get_height() - (2 * m_gui_cfg->window_offset_y + m_gui_cfg->window_padding));
if (pos.x > br.x) pos.x = br.x;
if (pos.y > br.y) pos.y = br.y;
ImGui::SetNextWindowPos(pos, ImGuiCond_Always);
}
}
float space_size = m_imgui->get_style_scaling() * 8;
float mesh_size = m_imgui->calc_text_size(_L("Mesh name")).x + m_imgui->scaled(2.0f);
float triangle_size = m_imgui->calc_text_size(_L("Triangles")).x + m_imgui->scaled(2.0f);
float text_left_width = std::max(triangle_size,mesh_size);
float detail_size = m_imgui->calc_text_size(_L("Detail level")).x + m_imgui->scaled(2.0f);
float decimate_size = m_imgui->calc_text_size(_L("Decimate ratio")).x + m_imgui->scaled(2.0f);
float circle_size = m_imgui->scaled(2.0f);
float bottom_left_width = std::max(detail_size, decimate_size) + circle_size;
float slider_width = m_imgui->scaled(5.0f);
push_simplify_style();
int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoCollapse;
m_imgui->begin(on_get_name(), flag);
m_imgui->text_colored(ImVec4(0.15f, 0.18f, 0.19f, 1.00f), tr_mesh_name + ":");
// BBS: somehow the calculated utf8 width is too narrow, have to add 35 here
ImGui::SameLine(text_left_width + space_size);
std::string name = m_volume->name;
if (name.length() > m_gui_cfg->max_char_in_name)
name = name.substr(0, m_gui_cfg->max_char_in_name - 3) + "...";
m_imgui->text_colored(ImVec4(0.42f, 0.42f, 0.42f, 1.00f), name);
m_imgui->text_colored(ImVec4(0.15f, 0.18f, 0.19f, 1.00f), tr_triangles + ":");
ImGui::SameLine(text_left_width + space_size);
size_t orig_triangle_count = m_volume->mesh().its.indices.size();
m_imgui->text_colored(ImVec4(0.42f, 0.42f, 0.42f, 1.00f), std::to_string(orig_triangle_count));
ImGui::Separator();
if (m_imgui->bbl_radio_button("##use_error", !m_configuration.use_count)) {
m_configuration.use_count = !m_configuration.use_count;
start_process = true;
}
ImGui::SameLine();
m_imgui->disabled_begin(m_configuration.use_count);
ImGui::Text("%s", tr_detail_level.c_str());
std::vector<std::string> reduce_captions = {
static_cast<std::string>(_u8L("Extra high")),
static_cast<std::string>(_u8L("High")),
static_cast<std::string>(_u8L("Medium")),
static_cast<std::string>(_u8L("Low")),
static_cast<std::string>(_u8L("Extra low"))
};
ImGui::SameLine(bottom_left_width);
ImGui::PushItemWidth(bottom_left_width - space_size);
static int reduction = 2;
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.81f, 0.81f, 0.81f, 1.00f));
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.81f, 0.81f, 0.81f, 1.00f));
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.81f, 0.81f, 0.81f, 1.00f));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.00f, 0.68f, 0.26f, 1.00f));
ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(0.00f, 0.68f, 0.26f, 1.00f));
if (m_imgui->bbl_sliderin("##ReductionLevel", &reduction, 0, 4, reduce_captions[reduction].c_str())) {
if (reduction < 0) reduction = 0;
if (reduction > 4) reduction = 4;
switch (reduction) {
case 0: m_configuration.max_error = 1e-3f; break;
case 1: m_configuration.max_error = 1e-2f; break;
case 2: m_configuration.max_error = 0.1f; break;
case 3: m_configuration.max_error = 0.5f; break;
case 4: m_configuration.max_error = 1.f; break;
}
start_process = true;
}
ImGui::PopStyleColor(5);
m_imgui->disabled_end(); // !use_count
if (m_imgui->bbl_radio_button("##use_count", m_configuration.use_count)) {
m_configuration.use_count = !m_configuration.use_count;
start_process = true;
}
ImGui::SameLine();
// show preview result triangle count (percent)
if (!m_configuration.use_count) {
m_configuration.wanted_count = static_cast<uint32_t>(m_triangle_count);
m_configuration.decimate_ratio =
(1.0f - (m_configuration.wanted_count / (float) orig_triangle_count)) * 100.f;
}
m_imgui->disabled_begin(!m_configuration.use_count);
ImGui::Text("%s", tr_decimate_ratio.c_str());
ImGui::SameLine(bottom_left_width);
const char * format = (m_configuration.decimate_ratio > 10)? "%.0f %%":
((m_configuration.decimate_ratio > 1)? "%.1f %%":"%.2f %%");
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0);
ImGui::PushItemWidth(slider_width + space_size);
if (m_imgui->bbl_slider_float_style("##decimate_ratio", &m_configuration.decimate_ratio, 0.f, 100.f, format)) {
if (m_configuration.decimate_ratio < 0.f)
m_configuration.decimate_ratio = 0.01f;
if (m_configuration.decimate_ratio > 100.f)
m_configuration.decimate_ratio = 100.f;
m_configuration.fix_count_by_ratio(orig_triangle_count);
start_process = true;
}
ImGui::SameLine();
ImGui::PopStyleVar(1);
ImGui::PushItemWidth(ImGui::CalcTextSize("100.00%").x + space_size);
ImGui::BBLDragFloat("##decimate_ratio_input", &m_configuration.decimate_ratio, 0.05f, 0.0f, 0.0f, "%.2f%%");
ImGui::NewLine();
ImGui::SameLine(bottom_left_width + space_size);
ImGui::Text(_u8L("%d triangles").c_str(), m_configuration.wanted_count);
m_imgui->disabled_end(); // use_count
m_imgui->bbl_checkbox(_L("Show wireframe").c_str(), m_show_wireframe);
// draw progress bar
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding,12);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0,0));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(10,20));
if (is_worker_running) { // apply or preview
// draw progress bar
std::string progress_text = GUI::format(_L("%1%"), std::to_string(progress)) + "%%";
ImVec2 progress_size(bottom_left_width - space_size, 0.0f);
ImGui::BBLProgressBar2(progress / 100., progress_size);
ImGui::SameLine();
ImGui::AlignTextToFramePadding();
ImGui::TextColored(ImVec4(0.42f, 0.42f, 0.42f, 1.00f), progress_text.c_str());
ImGui::SameLine(bottom_left_width + slider_width + m_imgui->scaled(1.0f));
} else {
ImGui::Dummy(ImVec2(bottom_left_width - space_size, -1));
ImGui::SameLine(bottom_left_width + slider_width + m_imgui->scaled(1.0f));
}
ImGui::PopStyleVar(3);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding,12);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(10,3));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(10,0));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.15f, 0.18f, 0.19f, 1.00f));
m_imgui->disabled_begin(is_worker_running || ! is_result_ready);
if (m_imgui->bbl_button(_L("Apply"))) {
apply_simplify();
}
else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_worker_running) {
ImGui::SetTooltip("%s", _u8L("Can't apply when proccess preview.").c_str());
}
m_imgui->disabled_end(); // state !settings
ImGui::SameLine();
m_imgui->disabled_begin(is_cancelling);
if (m_imgui->bbl_button(_L("Cancel"))) {
close();
}
else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_cancelling)
ImGui::SetTooltip("%s", _u8L("Operation already cancelling. Please wait few seconds.").c_str());
m_imgui->disabled_end(); // state cancelling
ImGui::PopStyleVar(3);
ImGui::PopStyleColor(1);
m_imgui->end();
pop_simplify_style();
if (start_process)
process();
}
void GLGizmoSimplify::close() {
// close gizmo == open it again
GLGizmosManager &gizmos_mgr = m_parent.get_gizmos_manager();
gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify);
}
void GLGizmoSimplify::stop_worker_thread_request()
{
std::lock_guard lk(m_state_mutex);
if (m_state.status == State::running)
m_state.status = State::Status::cancelling;
}
// Following is called from a UI thread when the worker terminates
// worker calls it through a CallAfter.
void GLGizmoSimplify::worker_finished()
{
{
std::lock_guard lk(m_state_mutex);
if (m_state.status == State::running) {
// Someone started the worker again, before this callback
// was called. Do nothing.
return;
}
}
if (m_worker.joinable())
m_worker.join();
if (GLGizmoBase::m_state == Off)
return;
if (m_state.result)
init_model(*m_state.result);
if (m_state.config != m_configuration || m_state.mv != m_volume) {
// Settings were changed, restart the worker immediately.
process();
}
request_rerender(true);
}
void GLGizmoSimplify::process()
{
if (m_volume == nullptr || m_volume->mesh().its.indices.empty())
return;
bool configs_match = false;
bool result_valid = false;
bool is_worker_running = false;
{
std::lock_guard lk(m_state_mutex);
configs_match = (m_volume == m_state.mv && m_state.config == m_configuration);
result_valid = bool(m_state.result);
is_worker_running = m_state.status == State::running;
}
if ((result_valid || is_worker_running) && configs_match) {
// Either finished or waiting for result already. Nothing to do.
return;
}
if (is_worker_running && ! configs_match) {
// Worker is running with outdated config. Stop it. It will
// restart itself when cancellation is done.
stop_worker_thread_request();
return;
}
if (m_worker.joinable()) {
// This can happen when process() is called after previous worker terminated,
// but before the worker_finished callback was called. In this case, just join the thread,
// the callback will check this and do nothing.
m_worker.join();
}
// Copy configuration that will be used.
m_state.config = m_configuration;
m_state.mv = m_volume;
m_state.status = State::running;
// Create a copy of current mesh to pass to the worker thread.
// Using unique_ptr instead of pass-by-value to avoid an extra
// copy (which would happen when passing to std::thread).
auto its = std::make_unique<indexed_triangle_set>(m_volume->mesh().its);
m_worker = std::thread([this](std::unique_ptr<indexed_triangle_set> its) {
// Checks that the UI thread did not request cancellation, throws if so.
std::function<void(void)> throw_on_cancel = [this]() {
std::lock_guard lk(m_state_mutex);
if (m_state.status == State::cancelling)
throw SimplifyCanceledException();
};
// Called by worker thread, updates progress bar.
// Using CallAfter so the rerequest function is run in UI thread.
std::function<void(int)> statusfn = [this](int percent) {
std::lock_guard lk(m_state_mutex);
m_state.progress = percent;
call_after_if_active([this]() { request_rerender(); });
};
// Initialize.
uint32_t triangle_count = 0;
float max_error = std::numeric_limits<float>::max();
{
std::lock_guard lk(m_state_mutex);
if (m_state.config.use_count)
triangle_count = m_state.config.wanted_count;
if (! m_state.config.use_count)
max_error = m_state.config.max_error;
m_state.progress = 0;
m_state.result.reset();
m_state.status = State::Status::running;
}
// Start the actual calculation.
try {
its_quadric_edge_collapse(*its, triangle_count, &max_error, throw_on_cancel, statusfn);
} catch (SimplifyCanceledException &) {
std::lock_guard lk(m_state_mutex);
m_state.status = State::idle;
}
std::lock_guard lk(m_state_mutex);
if (m_state.status == State::Status::running) {
// We were not cancelled, the result is valid.
m_state.status = State::Status::idle;
m_state.result = std::move(its);
}
// Update UI. Use CallAfter so the function is run on UI thread.
call_after_if_active([this]() { worker_finished(); });
}, std::move(its));
}
void GLGizmoSimplify::apply_simplify() {
const Selection& selection = m_parent.get_selection();
int object_idx = selection.get_object_idx();
auto plater = wxGetApp().plater();
plater->take_snapshot(GUI::format("Simplify %1%", m_volume->name));
plater->clear_before_change_mesh(object_idx);
ModelVolume* mv = get_model_volume(selection, wxGetApp().model());
assert(mv == m_volume);
mv->set_mesh(std::move(*m_state.result));
m_state.result.reset();
mv->calculate_convex_hull();
mv->set_new_unique_id();
mv->get_object()->invalidate_bounding_box();
mv->get_object()->ensure_on_bed(true); // allow negative z
// fix hollowing, sla support points, modifiers, ...
plater->changed_mesh(object_idx);
// Fix warning icon in object list
wxGetApp().obj_list()->update_item_error_icon(object_idx, -1);
close();
}
bool GLGizmoSimplify::on_is_activable() const
{
return m_parent.get_selection().is_single_full_object() ||
m_parent.get_selection().is_single_volume();
}
void GLGizmoSimplify::on_set_state()
{
// Closing gizmo. e.g. selecting another one
if (GLGizmoBase::m_state == GLGizmoBase::Off) {
m_parent.toggle_model_objects_visibility(true);
stop_worker_thread_request();
m_volume = nullptr; // invalidate selected model
m_glmodel.reset();
} else if (GLGizmoBase::m_state == GLGizmoBase::On) {
// when open by hyperlink it needs to show up
request_rerender();
}
}
void GLGizmoSimplify::create_gui_cfg() {
if (m_gui_cfg.has_value()) return;
int space_size = m_imgui->calc_text_size(":MM").x;
GuiCfg cfg;
cfg.top_left_width = std::max(m_imgui->calc_text_size(tr_mesh_name).x,
m_imgui->calc_text_size(tr_triangles).x)
+ space_size;
const float radio_size = ImGui::GetFrameHeight();
cfg.bottom_left_width =
std::max(m_imgui->calc_text_size(tr_detail_level).x,
m_imgui->calc_text_size(tr_decimate_ratio).x) +
space_size + radio_size;
cfg.input_width = cfg.bottom_left_width * 1.5;
cfg.window_offset_x = (cfg.bottom_left_width + cfg.input_width)/2;
cfg.window_offset_y = ImGui::GetTextLineHeightWithSpacing() * 5;
m_gui_cfg = cfg;
}
void GLGizmoSimplify::request_rerender(bool force) {
int64_t now = m_parent.timestamp_now();
if (force || now > m_last_rerender_timestamp + 250) { // 250 ms
set_dirty();
m_parent.schedule_extra_frame(0);
m_last_rerender_timestamp = now;
}
}
void GLGizmoSimplify::set_center_position() {
m_move_to_center = true;
}
void GLGizmoSimplify::init_model(const indexed_triangle_set& its)
{
if (its.indices.empty())
return;
m_glmodel.reset();
m_glmodel.init_from(its);
m_parent.toggle_model_objects_visibility(true); // selected volume may have changed
m_parent.toggle_model_objects_visibility(false, m_c->selection_info()->model_object(),
m_c->selection_info()->get_active_instance(), m_volume);
if (const Selection&sel = m_parent.get_selection(); sel.get_volume_idxs().size() == 1)
m_glmodel.set_color(-1, sel.get_volume(*sel.get_volume_idxs().begin())->color);
m_triangle_count = its.indices.size();
}
void GLGizmoSimplify::on_render()
{
if (! m_glmodel.is_initialized())
return;
const auto& selection = m_parent.get_selection();
const auto& volume_idxs = selection.get_volume_idxs();
if (volume_idxs.empty() || volume_idxs.size() != 1) return;
const GLVolume *selected_volume = selection.get_volume(*volume_idxs.begin());
// Check that the GLVolume still belongs to the ModelObject we work on.
if (m_volume != get_model_volume(selection, wxGetApp().model()))
return;
const Transform3d trafo_matrix = selected_volume->world_matrix();
glsafe(::glPushMatrix());
glsafe(::glMultMatrixd(trafo_matrix.data()));
auto *gouraud_shader = wxGetApp().get_shader("gouraud_light");
glsafe(::glPushAttrib(GL_DEPTH_TEST));
glsafe(::glEnable(GL_DEPTH_TEST));
gouraud_shader->start_using();
m_glmodel.render();
gouraud_shader->stop_using();
if (m_show_wireframe) {
auto* contour_shader = wxGetApp().get_shader("mm_contour");
contour_shader->start_using();
glsafe(::glLineWidth(1.0f));
glsafe(::glPolygonMode(GL_FRONT_AND_BACK, GL_LINE));
//ScopeGuard offset_fill_guard([]() { glsafe(::glDisable(GL_POLYGON_OFFSET_FILL)); });
//glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
//glsafe(::glPolygonOffset(5.0, 5.0));
m_glmodel.render();
glsafe(::glPolygonMode(GL_FRONT_AND_BACK, GL_FILL));
contour_shader->stop_using();
}
glsafe(::glPopAttrib());
glsafe(::glPopMatrix());
}
CommonGizmosDataID GLGizmoSimplify::on_get_requirements() const
{
return CommonGizmosDataID(
int(CommonGizmosDataID::SelectionInfo));
}
void GLGizmoSimplify::Configuration::fix_count_by_ratio(size_t triangle_count)
{
if (decimate_ratio <= 0.f)
wanted_count = static_cast<uint32_t>(triangle_count);
else if (decimate_ratio >= 100.f)
wanted_count = 0;
else
wanted_count = static_cast<uint32_t>(std::round(
triangle_count * (100.f - decimate_ratio) / 100.f));
}
} // namespace Slic3r::GUI

View file

@ -0,0 +1,152 @@
#ifndef slic3r_GLGizmoSimplify_hpp_
#define slic3r_GLGizmoSimplify_hpp_
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code,
// which overrides our localization "L" macro.
#include "GLGizmoBase.hpp"
#include "slic3r/GUI/3DScene.hpp"
#include "admesh/stl.h" // indexed_triangle_set
#include <mutex>
#include <thread>
namespace Slic3r {
class ModelVolume;
class Model;
namespace GUI {
class NotificationManager; // for simplify suggestion
class GLGizmoSimplify: public GLGizmoBase
{
public:
GLGizmoSimplify(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
virtual ~GLGizmoSimplify();
bool on_esc_key_down();
static void add_simplify_suggestion_notification(
const std::vector<size_t> &object_ids,
const std::vector<ModelObject*> & objects,
NotificationManager & manager);
protected:
virtual std::string on_get_name() const override;
virtual void on_render_input_window(float x, float y, float bottom_limit) override;
virtual bool on_is_activable() const override;
virtual bool on_is_selectable() const override { return false; }
virtual void on_set_state() override;
// must implement
virtual bool on_init() override { return true;};
virtual void on_render() override;
virtual void on_render_for_picking() override{};
CommonGizmosDataID on_get_requirements() const override;
private:
void apply_simplify();
void close();
void process();
void stop_worker_thread_request();
void worker_finished();
void push_simplify_style();
void pop_simplify_style();
void create_gui_cfg();
void request_rerender(bool force = false);
void init_model(const indexed_triangle_set& its);
void set_center_position();
struct Configuration
{
bool use_count = false;
float decimate_ratio = 50.f; // in percent
uint32_t wanted_count = 0; // initialize by percents
float max_error = 1.; // maximal quadric error
void fix_count_by_ratio(size_t triangle_count);
bool operator==(const Configuration& rhs) {
return (use_count == rhs.use_count && decimate_ratio == rhs.decimate_ratio
&& wanted_count == rhs.wanted_count && max_error == rhs.max_error);
}
bool operator!=(const Configuration& rhs) {
return ! (*this == rhs);
}
};
Configuration m_configuration;
bool m_move_to_center; // opening gizmo
const ModelVolume *m_volume; // keep pointer to actual working volume
bool m_show_wireframe;
GLModel m_glmodel;
size_t m_triangle_count; // triangle count of the model currently shown
// Timestamp of the last rerender request. Only accessed from UI thread.
int64_t m_last_rerender_timestamp = std::numeric_limits<int64_t>::min();
// Following struct is accessed by both UI and worker thread.
// Accesses protected by a mutex.
struct State {
enum Status {
idle,
running,
cancelling
};
Status status = idle;
int progress = 0; // percent of done work
Configuration config; // Configuration we started with.
const ModelVolume* mv = nullptr;
std::unique_ptr<indexed_triangle_set> result;
};
std::thread m_worker;
std::mutex m_state_mutex; // guards m_state
State m_state; // accessed by both threads
// This configs holds GUI layout size given by translated texts.
// etc. When language changes, GUI is recreated and this class constructed again,
// so the change takes effect. (info by GLGizmoFdmSupports.hpp)
struct GuiCfg
{
int top_left_width = 100;
int bottom_left_width = 100;
int input_width = 100;
int window_offset_x = 100;
int window_offset_y = 100;
int window_padding = 0;
// trunc model name when longer
size_t max_char_in_name = 30;
// to prevent freezing when move in gui
// delay before process in [ms]
std::chrono::duration<long int, std::milli> prcess_delay = std::chrono::milliseconds(250);
};
std::optional<GuiCfg> m_gui_cfg;
// translations used for calc window size
const std::string tr_mesh_name;
const std::string tr_triangles;
const std::string tr_detail_level;
const std::string tr_decimate_ratio;
// cancel exception
class SimplifyCanceledException: public std::exception
{
public:
const char *what() const throw()
{
return L("Model simplification has been canceled");
}
};
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLGizmoSimplify_hpp_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,156 @@
#ifndef slic3r_GLGizmoSlaSupports_hpp_
#define slic3r_GLGizmoSlaSupports_hpp_
#include "GLGizmoBase.hpp"
#include "slic3r/GUI/GLSelectionRectangle.hpp"
#include "libslic3r/SLA/SupportPoint.hpp"
#include "libslic3r/ObjectID.hpp"
#include <wx/dialog.h>
#include <cereal/types/vector.hpp>
namespace Slic3r {
class ConfigOption;
namespace GUI {
enum class SLAGizmoEventType : unsigned char;
class GLGizmoSlaSupports : public GLGizmoBase
{
private:
bool unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec3f, Vec3f>& pos_and_normal);
const float RenderPointScale = 1.f;
class CacheEntry {
public:
CacheEntry() :
support_point(sla::SupportPoint()), selected(false), normal(Vec3f::Zero()) {}
CacheEntry(const sla::SupportPoint& point, bool sel = false, const Vec3f& norm = Vec3f::Zero()) :
support_point(point), selected(sel), normal(norm) {}
bool operator==(const CacheEntry& rhs) const {
return (support_point == rhs.support_point);
}
bool operator!=(const CacheEntry& rhs) const {
return ! ((*this) == rhs);
}
sla::SupportPoint support_point;
bool selected; // whether the point is selected
Vec3f normal;
template<class Archive>
void serialize(Archive & ar)
{
ar(support_point, selected, normal);
}
};
public:
GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
virtual ~GLGizmoSlaSupports() = default;
void set_sla_support_data(ModelObject* model_object, const Selection& selection);
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
void delete_selected_points(bool force = false);
//ClippingPlane get_sla_clipping_plane() const;
bool is_in_editing_mode() const { return m_editing_mode; }
bool is_selection_rectangle_dragging() const { return m_selection_rectangle.is_dragging(); }
bool has_backend_supports() const;
void reslice_SLA_supports(bool postpone_error_messages = false) const;
bool wants_enter_leave_snapshots() const override { return true; }
std::string get_gizmo_entering_text() const override { return _u8L("Entering SLA support points"); }
std::string get_gizmo_leaving_text() const override { return _u8L("Leaving SLA support points"); }
private:
bool on_init() override;
void on_update(const UpdateData& data) override;
void on_render() override;
void on_render_for_picking() override;
void render_points(const Selection& selection, bool picking = false) const;
bool unsaved_changes() const;
bool m_lock_unique_islands = false;
bool m_editing_mode = false; // Is editing mode active?
float m_new_point_head_diameter; // Size of a new point.
CacheEntry m_point_before_drag; // undo/redo - so we know what state was edited
float m_old_point_head_diameter = 0.; // the same
float m_minimal_point_distance_stash = 0.f; // and again
float m_density_stash = 0.f; // and again
mutable std::vector<CacheEntry> m_editing_cache; // a support point and whether it is currently selected
std::vector<sla::SupportPoint> m_normal_cache; // to restore after discarding changes or undo/redo
ObjectID m_old_mo_id;
// This map holds all translated description texts, so they can be easily referenced during layout calculations
// etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.
std::map<std::string, wxString> m_desc;
GLSelectionRectangle m_selection_rectangle;
bool m_wait_for_up_event = false;
bool m_selection_empty = true;
EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state)
std::vector<const ConfigOption*> get_config_options(const std::vector<std::string>& keys) const;
bool is_mesh_point_clipped(const Vec3d& point) const;
bool is_point_in_hole(const Vec3f& pt) const;
//void find_intersecting_facets(const igl::AABB<Eigen::MatrixXf, 3>* aabb, const Vec3f& normal, double offset, std::vector<unsigned int>& out) const;
// Methods that do the model_object and editing cache synchronization,
// editing mode selection, etc:
enum {
AllPoints = -2,
NoPoints,
};
void select_point(int i);
void unselect_point(int i);
void editing_mode_apply_changes();
void editing_mode_discard_changes();
void reload_cache();
void get_data_from_backend();
void auto_generate();
void switch_to_editing_mode();
void disable_editing_mode();
void ask_about_changes_call_after(std::function<void()> on_yes, std::function<void()> on_no);
protected:
void on_set_state() override;
void on_set_hover_id() override
{
if (! m_editing_mode || (int)m_editing_cache.size() <= m_hover_id)
m_hover_id = -1;
}
void on_start_dragging() override;
void on_stop_dragging() override;
void on_render_input_window(float x, float y, float bottom_limit) override;
std::string on_get_name() const override;
bool on_is_activable() const override;
bool on_is_selectable() const override;
virtual CommonGizmosDataID on_get_requirements() const override;
void on_load(cereal::BinaryInputArchive& ar) override;
void on_save(cereal::BinaryOutputArchive& ar) const override;
};
class SlaGizmoHelpDialog : public wxDialog
{
public:
SlaGizmoHelpDialog();
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLGizmoSlaSupports_hpp_

View file

@ -0,0 +1,40 @@
#ifndef slic3r_GLGizmos_hpp_
#define slic3r_GLGizmos_hpp_
// this describes events being passed from GLCanvas3D to SlaSupport gizmo
namespace Slic3r {
namespace GUI {
enum class SLAGizmoEventType : unsigned char {
LeftDown = 1,
LeftUp,
RightDown,
Dragging,
Delete,
SelectAll,
ShiftUp,
AltUp,
ApplyChanges,
DiscardChanges,
AutomaticGeneration,
ManualEditing,
MouseWheelUp,
MouseWheelDown,
ResetClippingPlane
};
} // namespace GUI
} // namespace Slic3r
// BBS
#include "slic3r/GUI/Gizmos/GLGizmoMoveScale.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoRotate.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoFlatten.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp"
// BBS
#include "slic3r/GUI/Gizmos/GLGizmoAdvancedCut.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp"
#endif //slic3r_GLGizmos_hpp_

View file

@ -0,0 +1,545 @@
#include "GLGizmosCommon.hpp"
#include <cassert>
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "libslic3r/SLAPrint.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Camera.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "libslic3r/PresetBundle.hpp"
#include <GL/glew.h>
namespace Slic3r {
namespace GUI {
using namespace CommonGizmosDataObjects;
CommonGizmosDataPool::CommonGizmosDataPool(GLCanvas3D* canvas)
: m_canvas(canvas)
{
using c = CommonGizmosDataID;
m_data[c::SelectionInfo].reset( new SelectionInfo(this));
m_data[c::InstancesHider].reset( new InstancesHider(this));
m_data[c::HollowedMesh].reset( new HollowedMesh(this));
m_data[c::Raycaster].reset( new Raycaster(this));
m_data[c::ObjectClipper].reset( new ObjectClipper(this));
m_data[c::SupportsClipper].reset( new SupportsClipper(this));
}
void CommonGizmosDataPool::update(CommonGizmosDataID required)
{
assert(check_dependencies(required));
for (auto& [id, data] : m_data) {
if (int(required) & int(CommonGizmosDataID(id)))
data->update();
else
if (data->is_valid())
data->release();
}
}
SelectionInfo* CommonGizmosDataPool::selection_info() const
{
SelectionInfo* sel_info = dynamic_cast<SelectionInfo*>(m_data.at(CommonGizmosDataID::SelectionInfo).get());
assert(sel_info);
return sel_info->is_valid() ? sel_info : nullptr;
}
InstancesHider* CommonGizmosDataPool::instances_hider() const
{
InstancesHider* inst_hider = dynamic_cast<InstancesHider*>(m_data.at(CommonGizmosDataID::InstancesHider).get());
assert(inst_hider);
return inst_hider->is_valid() ? inst_hider : nullptr;
}
HollowedMesh* CommonGizmosDataPool::hollowed_mesh() const
{
HollowedMesh* hol_mesh = dynamic_cast<HollowedMesh*>(m_data.at(CommonGizmosDataID::HollowedMesh).get());
assert(hol_mesh);
return hol_mesh->is_valid() ? hol_mesh : nullptr;
}
Raycaster* CommonGizmosDataPool::raycaster() const
{
Raycaster* rc = dynamic_cast<Raycaster*>(m_data.at(CommonGizmosDataID::Raycaster).get());
assert(rc);
return rc->is_valid() ? rc : nullptr;
}
ObjectClipper* CommonGizmosDataPool::object_clipper() const
{
ObjectClipper* oc = dynamic_cast<ObjectClipper*>(m_data.at(CommonGizmosDataID::ObjectClipper).get());
// ObjectClipper is used from outside the gizmos to report current clipping plane.
// This function can be called when oc is nullptr.
return (oc && oc->is_valid()) ? oc : nullptr;
}
SupportsClipper* CommonGizmosDataPool::supports_clipper() const
{
SupportsClipper* sc = dynamic_cast<SupportsClipper*>(m_data.at(CommonGizmosDataID::SupportsClipper).get());
assert(sc);
return sc->is_valid() ? sc : nullptr;
}
#ifndef NDEBUG
// Check the required resources one by one and return true if all
// dependencies are met.
bool CommonGizmosDataPool::check_dependencies(CommonGizmosDataID required) const
{
// This should iterate over currently required data. Each of them should
// be asked about its dependencies and it must check that all dependencies
// are also in required and before the current one.
for (auto& [id, data] : m_data) {
// in case we don't use this, the deps are irrelevant
if (! (int(required) & int(CommonGizmosDataID(id))))
continue;
CommonGizmosDataID deps = data->get_dependencies();
assert(int(deps) == (int(deps) & int(required)));
}
return true;
}
#endif // NDEBUG
void SelectionInfo::on_update()
{
const Selection& selection = get_pool()->get_canvas()->get_selection();
if (selection.is_single_full_instance()) {
m_model_object = selection.get_model()->objects[selection.get_object_idx()];
m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z();
}
else
m_model_object = nullptr;
}
void SelectionInfo::on_release()
{
m_model_object = nullptr;
}
int SelectionInfo::get_active_instance() const
{
const Selection& selection = get_pool()->get_canvas()->get_selection();
return selection.get_instance_idx();
}
void InstancesHider::on_update()
{
const ModelObject* mo = get_pool()->selection_info()->model_object();
int active_inst = get_pool()->selection_info()->get_active_instance();
GLCanvas3D* canvas = get_pool()->get_canvas();
if (mo && active_inst != -1) {
canvas->toggle_model_objects_visibility(false);
canvas->toggle_model_objects_visibility(true, mo, active_inst);
canvas->toggle_sla_auxiliaries_visibility(m_show_supports, mo, active_inst);
canvas->set_use_clipping_planes(true);
// Some objects may be sinking, do not show whatever is below the bed.
canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD));
canvas->set_clipping_plane(1, ClippingPlane(-Vec3d::UnitZ(), std::numeric_limits<double>::max()));
std::vector<const TriangleMesh*> meshes;
for (const ModelVolume* mv : mo->volumes)
meshes.push_back(&mv->mesh());
if (meshes != m_old_meshes) {
m_clippers.clear();
for (const TriangleMesh* mesh : meshes) {
m_clippers.emplace_back(new MeshClipper);
m_clippers.back()->set_plane(ClippingPlane(-Vec3d::UnitZ(), -SINKING_Z_THRESHOLD));
m_clippers.back()->set_mesh(*mesh);
}
m_old_meshes = meshes;
}
}
else
canvas->toggle_model_objects_visibility(true);
}
void InstancesHider::on_release()
{
get_pool()->get_canvas()->toggle_model_objects_visibility(true);
get_pool()->get_canvas()->set_use_clipping_planes(false);
m_old_meshes.clear();
m_clippers.clear();
}
void InstancesHider::show_supports(bool show) {
if (m_show_supports != show) {
m_show_supports = show;
on_update();
}
}
void InstancesHider::render_cut() const
{
const SelectionInfo* sel_info = get_pool()->selection_info();
const ModelObject* mo = sel_info->model_object();
Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation();
size_t clipper_id = 0;
for (const ModelVolume* mv : mo->volumes) {
Geometry::Transformation vol_trafo = mv->get_transformation();
Geometry::Transformation trafo = inst_trafo * vol_trafo;
trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift()));
auto& clipper = m_clippers[clipper_id];
clipper->set_transformation(trafo);
const ObjectClipper* obj_clipper = get_pool()->object_clipper();
if (obj_clipper->is_valid() && obj_clipper->get_clipping_plane()
&& obj_clipper->get_position() != 0.) {
ClippingPlane clp = *get_pool()->object_clipper()->get_clipping_plane();
clp.set_normal(-clp.get_normal());
clipper->set_limiting_plane(clp);
} else
clipper->set_limiting_plane(ClippingPlane::ClipsNothing());
glsafe(::glPushMatrix());
if (mv->is_model_part())
glsafe(::glColor3f(0.8f, 0.3f, 0.0f));
else {
const std::array<float, 4>& c = color_from_model_volume(*mv);
glsafe(::glColor4f(c[0], c[1], c[2], c[3]));
}
glsafe(::glPushAttrib(GL_DEPTH_TEST));
glsafe(::glDisable(GL_DEPTH_TEST));
clipper->render_cut();
glsafe(::glPopAttrib());
glsafe(::glPopMatrix());
++clipper_id;
}
}
void HollowedMesh::on_update()
{
const ModelObject* mo = get_pool()->selection_info()->model_object();
bool is_sla = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA;
if (! mo || ! is_sla)
return;
const GLCanvas3D* canvas = get_pool()->get_canvas();
const PrintObjects& print_objects = canvas->sla_print()->objects();
const SLAPrintObject* print_object = m_print_object_idx != -1
? print_objects[m_print_object_idx]
: nullptr;
// Find the respective SLAPrintObject.
if (m_print_object_idx < 0 || m_print_objects_count != int(print_objects.size())) {
m_print_objects_count = print_objects.size();
m_print_object_idx = -1;
for (const SLAPrintObject* po : print_objects) {
++m_print_object_idx;
if (po->model_object()->id() == mo->id()) {
print_object = po;
break;
}
}
}
// If there is a valid SLAPrintObject, check state of Hollowing step.
if (print_object) {
if (print_object->is_step_done(slaposDrillHoles) && print_object->has_mesh(slaposDrillHoles)) {
size_t timestamp = print_object->step_state_with_timestamp(slaposDrillHoles).timestamp;
if (timestamp > m_old_hollowing_timestamp) {
const TriangleMesh& backend_mesh = print_object->get_mesh_to_slice();
if (! backend_mesh.empty()) {
m_hollowed_mesh_transformed.reset(new TriangleMesh(backend_mesh));
Transform3d trafo_inv = canvas->sla_print()->sla_trafo(*mo).inverse();
m_hollowed_mesh_transformed->transform(trafo_inv);
m_drainholes = print_object->model_object()->sla_drain_holes;
m_old_hollowing_timestamp = timestamp;
indexed_triangle_set interior = print_object->hollowed_interior_mesh();
its_flip_triangles(interior);
m_hollowed_interior_transformed = std::make_unique<TriangleMesh>(std::move(interior));
m_hollowed_interior_transformed->transform(trafo_inv);
}
else {
m_hollowed_mesh_transformed.reset(nullptr);
}
}
}
else
m_hollowed_mesh_transformed.reset(nullptr);
}
}
void HollowedMesh::on_release()
{
m_hollowed_mesh_transformed.reset();
m_old_hollowing_timestamp = 0;
m_print_object_idx = -1;
}
const TriangleMesh* HollowedMesh::get_hollowed_mesh() const
{
return m_hollowed_mesh_transformed.get();
}
const TriangleMesh* HollowedMesh::get_hollowed_interior() const
{
return m_hollowed_interior_transformed.get();
}
void Raycaster::on_update()
{
wxBusyCursor wait;
const ModelObject* mo = get_pool()->selection_info()->model_object();
if (! mo)
return;
std::vector<const TriangleMesh*> meshes;
const std::vector<ModelVolume*>& mvs = mo->volumes;
if (mvs.size() == 1) {
assert(mvs.front()->is_model_part());
const HollowedMesh* hollowed_mesh_tracker = get_pool()->hollowed_mesh();
if (hollowed_mesh_tracker && hollowed_mesh_tracker->get_hollowed_mesh())
meshes.push_back(hollowed_mesh_tracker->get_hollowed_mesh());
}
if (meshes.empty()) {
for (const ModelVolume* mv : mvs) {
if (mv->is_model_part())
meshes.push_back(&mv->mesh());
}
}
if (meshes != m_old_meshes) {
m_raycasters.clear();
for (const TriangleMesh* mesh : meshes)
m_raycasters.emplace_back(new MeshRaycaster(*mesh));
m_old_meshes = meshes;
}
}
void Raycaster::on_release()
{
m_raycasters.clear();
m_old_meshes.clear();
}
std::vector<const MeshRaycaster*> Raycaster::raycasters() const
{
std::vector<const MeshRaycaster*> mrcs;
for (const auto& raycaster_unique_ptr : m_raycasters)
mrcs.push_back(raycaster_unique_ptr.get());
return mrcs;
}
void ObjectClipper::on_update()
{
const ModelObject* mo = get_pool()->selection_info()->model_object();
if (! mo)
return;
// which mesh should be cut?
std::vector<const TriangleMesh*> meshes;
bool has_hollowed = get_pool()->hollowed_mesh() && get_pool()->hollowed_mesh()->get_hollowed_mesh();
if (has_hollowed)
meshes.push_back(get_pool()->hollowed_mesh()->get_hollowed_mesh());
if (meshes.empty())
for (const ModelVolume* mv : mo->volumes)
meshes.push_back(&mv->mesh());
if (meshes != m_old_meshes) {
m_clippers.clear();
for (const TriangleMesh* mesh : meshes) {
m_clippers.emplace_back(new MeshClipper);
m_clippers.back()->set_mesh(*mesh);
}
m_old_meshes = meshes;
if (has_hollowed)
m_clippers.front()->set_negative_mesh(*get_pool()->hollowed_mesh()->get_hollowed_interior());
m_active_inst_bb_radius =
mo->instance_bounding_box(get_pool()->selection_info()->get_active_instance()).radius();
}
}
void ObjectClipper::on_release()
{
m_clippers.clear();
m_old_meshes.clear();
m_clp.reset();
m_clp_ratio = 0.;
}
void ObjectClipper::render_cut() const
{
if (m_clp_ratio == 0.)
return;
const SelectionInfo* sel_info = get_pool()->selection_info();
const ModelObject* mo = sel_info->model_object();
Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation();
size_t clipper_id = 0;
for (const ModelVolume* mv : mo->volumes) {
Geometry::Transformation vol_trafo = mv->get_transformation();
Geometry::Transformation trafo = inst_trafo * vol_trafo;
trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift()));
auto& clipper = m_clippers[clipper_id];
clipper->set_plane(*m_clp);
clipper->set_transformation(trafo);
clipper->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD));
glsafe(::glPushMatrix());
// BBS
glsafe(::glColor3f(0.25f, 0.25f, 0.25f));
clipper->render_cut();
glsafe(::glPopMatrix());
++clipper_id;
}
}
void ObjectClipper::set_position(double pos, bool keep_normal)
{
const ModelObject* mo = get_pool()->selection_info()->model_object();
int active_inst = get_pool()->selection_info()->get_active_instance();
double z_shift = get_pool()->selection_info()->get_sla_shift();
Vec3d camera_dir = wxGetApp().plater()->get_camera().get_dir_forward();
if (abs(camera_dir(0)) > EPSILON || abs(camera_dir(1)) > EPSILON)
camera_dir(2) = 0;
Vec3d normal = (keep_normal && m_clp) ? m_clp->get_normal() : -camera_dir;
const Vec3d& center = mo->instances[active_inst]->get_offset() + Vec3d(0., 0., z_shift);
float dist = normal.dot(center);
if (pos < 0.)
pos = m_clp_ratio;
m_clp_ratio = pos;
m_clp.reset(new ClippingPlane(normal, (dist - (-m_active_inst_bb_radius) - m_clp_ratio * 2*m_active_inst_bb_radius)));
get_pool()->get_canvas()->set_as_dirty();
}
void SupportsClipper::on_update()
{
const ModelObject* mo = get_pool()->selection_info()->model_object();
bool is_sla = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA;
if (! mo || ! is_sla)
return;
const GLCanvas3D* canvas = get_pool()->get_canvas();
const PrintObjects& print_objects = canvas->sla_print()->objects();
const SLAPrintObject* print_object = m_print_object_idx != -1
? print_objects[m_print_object_idx]
: nullptr;
// Find the respective SLAPrintObject.
if (m_print_object_idx < 0 || m_print_objects_count != int(print_objects.size())) {
m_print_objects_count = print_objects.size();
m_print_object_idx = -1;
for (const SLAPrintObject* po : print_objects) {
++m_print_object_idx;
if (po->model_object()->id() == mo->id()) {
print_object = po;
break;
}
}
}
if (print_object
&& print_object->is_step_done(slaposSupportTree)
&& ! print_object->support_mesh().empty())
{
// If the supports are already calculated, save the timestamp of the respective step
// so we can later tell they were recalculated.
size_t timestamp = print_object->step_state_with_timestamp(slaposSupportTree).timestamp;
if (! m_clipper || timestamp != m_old_timestamp) {
// The timestamp has changed.
m_clipper.reset(new MeshClipper);
// The mesh should already have the shared vertices calculated.
m_clipper->set_mesh(print_object->support_mesh());
m_old_timestamp = timestamp;
}
}
else
// The supports are not valid. We better dump the cached data.
m_clipper.reset();
}
void SupportsClipper::on_release()
{
m_clipper.reset();
m_old_timestamp = 0;
m_print_object_idx = -1;
}
void SupportsClipper::render_cut() const
{
const CommonGizmosDataObjects::ObjectClipper* ocl = get_pool()->object_clipper();
if (ocl->get_position() == 0.
|| ! get_pool()->instances_hider()->are_supports_shown()
|| ! m_clipper)
return;
const SelectionInfo* sel_info = get_pool()->selection_info();
const ModelObject* mo = sel_info->model_object();
Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation();
//Geometry::Transformation vol_trafo = mo->volumes.front()->get_transformation();
Geometry::Transformation trafo = inst_trafo;// * vol_trafo;
trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift()));
// Get transformation of supports
Geometry::Transformation supports_trafo = trafo;
supports_trafo.set_scaling_factor(Vec3d::Ones());
supports_trafo.set_offset(Vec3d(trafo.get_offset()(0), trafo.get_offset()(1), sel_info->get_sla_shift()));
supports_trafo.set_rotation(Vec3d(0., 0., trafo.get_rotation()(2)));
// I don't know why, but following seems to be correct.
supports_trafo.set_mirror(Vec3d(trafo.get_mirror()(0) * trafo.get_mirror()(1) * trafo.get_mirror()(2),
1,
1.));
m_clipper->set_plane(*ocl->get_clipping_plane());
m_clipper->set_transformation(supports_trafo);
glsafe(::glPushMatrix());
glsafe(::glColor3f(1.0f, 0.f, 0.37f));
m_clipper->render_cut();
glsafe(::glPopMatrix());
}
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,317 @@
#ifndef slic3r_GUI_GLGizmosCommon_hpp_
#define slic3r_GUI_GLGizmosCommon_hpp_
#include <memory>
#include <map>
#include "slic3r/GUI/MeshUtils.hpp"
#include "libslic3r/SLA/Hollowing.hpp"
namespace Slic3r {
class ModelObject;
namespace GUI {
class GLCanvas3D;
enum class SLAGizmoEventType : unsigned char {
LeftDown = 1,
LeftUp,
RightDown,
RightUp,
Dragging,
Delete,
SelectAll,
ShiftUp,
AltUp,
ApplyChanges,
DiscardChanges,
AutomaticGeneration,
ManualEditing,
MouseWheelUp,
MouseWheelDown,
ResetClippingPlane,
Moving
};
class CommonGizmosDataBase;
namespace CommonGizmosDataObjects {
class SelectionInfo;
class InstancesHider;
class HollowedMesh;
class Raycaster;
class ObjectClipper;
class SupportsClipper;
}
// Some of the gizmos use the same data that need to be updated ocassionally.
// It is also desirable that the data are not recalculated when the gizmos
// are just switched, but on the other hand, they should be released when
// they are not in use by any gizmo anymore.
// Enumeration of various data types that the data pool can contain.
// Each gizmo can tell which of the data it wants to use through
// on_get_requirements() method.
enum class CommonGizmosDataID {
None = 0,
SelectionInfo = 1 << 0,
InstancesHider = 1 << 1,
HollowedMesh = 1 << 2,
Raycaster = 1 << 3,
ObjectClipper = 1 << 4,
SupportsClipper = 1 << 5,
};
// Following class holds pointers to the common data objects and triggers
// their updating/releasing. There is just one object of this type (managed
// by GLGizmoManager, the gizmos keep a pointer to it.
class CommonGizmosDataPool {
public:
CommonGizmosDataPool(GLCanvas3D* canvas);
// Update all resources and release what is not used.
// Accepts a bitmask of currently required resources.
void update(CommonGizmosDataID required);
// Getters for the data that need to be accessed from the gizmos directly.
CommonGizmosDataObjects::SelectionInfo* selection_info() const;
CommonGizmosDataObjects::InstancesHider* instances_hider() const;
CommonGizmosDataObjects::HollowedMesh* hollowed_mesh() const;
CommonGizmosDataObjects::Raycaster* raycaster() const;
CommonGizmosDataObjects::ObjectClipper* object_clipper() const;
CommonGizmosDataObjects::SupportsClipper* supports_clipper() const;
GLCanvas3D* get_canvas() const { return m_canvas; }
private:
std::map<CommonGizmosDataID, std::unique_ptr<CommonGizmosDataBase>> m_data;
GLCanvas3D* m_canvas;
#ifndef NDEBUG
bool check_dependencies(CommonGizmosDataID required) const;
#endif
};
// Base class for a wrapper object managing a single resource.
// Each of the enum values above (safe None) will have an object of this kind.
class CommonGizmosDataBase {
public:
// Pass a backpointer to the pool, so the individual
// objects can communicate with one another.
explicit CommonGizmosDataBase(CommonGizmosDataPool* cgdp)
: m_common{cgdp} {}
virtual ~CommonGizmosDataBase() {}
// Update the resource.
void update() { on_update(); m_is_valid = true; }
// Release any data that are stored internally.
void release() { on_release(); m_is_valid = false; }
// Returns whether the resource is currently maintained.
bool is_valid() const { return m_is_valid; }
#ifndef NDEBUG
// Return a bitmask of all resources that this one relies on.
// The dependent resource must have higher ID than the one
// it depends on.
virtual CommonGizmosDataID get_dependencies() const { return CommonGizmosDataID::None; }
#endif // NDEBUG
protected:
virtual void on_release() = 0;
virtual void on_update() = 0;
CommonGizmosDataPool* get_pool() const { return m_common; }
private:
bool m_is_valid = false;
CommonGizmosDataPool* m_common = nullptr;
};
// The specializations of the CommonGizmosDataBase class live in this
// namespace to avoid clashes in GUI namespace.
namespace CommonGizmosDataObjects
{
class SelectionInfo : public CommonGizmosDataBase
{
public:
explicit SelectionInfo(CommonGizmosDataPool* cgdp)
: CommonGizmosDataBase(cgdp) {}
ModelObject* model_object() const { return m_model_object; }
int get_active_instance() const;
float get_sla_shift() const { return m_z_shift; }
protected:
void on_update() override;
void on_release() override;
private:
ModelObject* m_model_object = nullptr;
// int m_active_inst = -1;
float m_z_shift = 0.f;
};
class InstancesHider : public CommonGizmosDataBase
{
public:
explicit InstancesHider(CommonGizmosDataPool* cgdp)
: CommonGizmosDataBase(cgdp) {}
#ifndef NDEBUG
CommonGizmosDataID get_dependencies() const override { return CommonGizmosDataID::SelectionInfo; }
#endif // NDEBUG
void show_supports(bool show);
bool are_supports_shown() const { return m_show_supports; }
void render_cut() const;
protected:
void on_update() override;
void on_release() override;
private:
bool m_show_supports = false;
std::vector<const TriangleMesh*> m_old_meshes;
std::vector<std::unique_ptr<MeshClipper>> m_clippers;
};
class HollowedMesh : public CommonGizmosDataBase
{
public:
explicit HollowedMesh(CommonGizmosDataPool* cgdp)
: CommonGizmosDataBase(cgdp) {}
#ifndef NDEBUG
CommonGizmosDataID get_dependencies() const override { return CommonGizmosDataID::SelectionInfo; }
#endif // NDEBUG
const sla::DrainHoles &get_drainholes() const { return m_drainholes; }
const TriangleMesh* get_hollowed_mesh() const;
const TriangleMesh* get_hollowed_interior() const;
protected:
void on_update() override;
void on_release() override;
private:
std::unique_ptr<TriangleMesh> m_hollowed_mesh_transformed;
std::unique_ptr<TriangleMesh> m_hollowed_interior_transformed;
size_t m_old_hollowing_timestamp = 0;
int m_print_object_idx = -1;
int m_print_objects_count = 0;
sla::DrainHoles m_drainholes;
};
class Raycaster : public CommonGizmosDataBase
{
public:
explicit Raycaster(CommonGizmosDataPool* cgdp)
: CommonGizmosDataBase(cgdp) {}
#ifndef NDEBUG
CommonGizmosDataID get_dependencies() const override { return CommonGizmosDataID::SelectionInfo; }
#endif // NDEBUG
const MeshRaycaster* raycaster() const { assert(m_raycasters.size() == 1); return m_raycasters.front().get(); }
std::vector<const MeshRaycaster*> raycasters() const;
protected:
void on_update() override;
void on_release() override;
private:
std::vector<std::unique_ptr<MeshRaycaster>> m_raycasters;
std::vector<const TriangleMesh*> m_old_meshes;
};
class ObjectClipper : public CommonGizmosDataBase
{
public:
explicit ObjectClipper(CommonGizmosDataPool* cgdp)
: CommonGizmosDataBase(cgdp) {}
#ifndef NDEBUG
CommonGizmosDataID get_dependencies() const override { return CommonGizmosDataID::SelectionInfo; }
#endif // NDEBUG
void set_position(double pos, bool keep_normal);
double get_position() const { return m_clp_ratio; }
ClippingPlane* get_clipping_plane() const { return m_clp.get(); }
void render_cut() const;
protected:
void on_update() override;
void on_release() override;
private:
std::vector<const TriangleMesh*> m_old_meshes;
std::vector<std::unique_ptr<MeshClipper>> m_clippers;
std::unique_ptr<ClippingPlane> m_clp;
double m_clp_ratio = 0.;
double m_active_inst_bb_radius = 0.;
};
class SupportsClipper : public CommonGizmosDataBase
{
public:
explicit SupportsClipper(CommonGizmosDataPool* cgdp)
: CommonGizmosDataBase(cgdp) {}
#ifndef NDEBUG
CommonGizmosDataID get_dependencies() const override {
return CommonGizmosDataID(
int(CommonGizmosDataID::SelectionInfo)
| int(CommonGizmosDataID::ObjectClipper)
);
}
#endif // NDEBUG
void render_cut() const;
protected:
void on_update() override;
void on_release() override;
private:
size_t m_old_timestamp = 0;
int m_print_object_idx = -1;
int m_print_objects_count = 0;
std::unique_ptr<MeshClipper> m_clipper;
};
} // namespace CommonGizmosDataObjects
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GUI_GLGizmosCommon_hpp_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,316 @@
#ifndef slic3r_GUI_GLGizmosManager_hpp_
#define slic3r_GUI_GLGizmosManager_hpp_
#include "slic3r/GUI/GLTexture.hpp"
#include "slic3r/GUI/GLToolbar.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoBase.hpp"
#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp"
//BBS: GUI refactor: add object manipulation
#include "slic3r/GUI/Gizmos/GizmoObjectManipulation.hpp"
#include "libslic3r/ObjectID.hpp"
#include <map>
//BBS: GUI refactor: to support top layout
#define BBS_TOOLBAR_ON_TOP 1
namespace Slic3r {
namespace UndoRedo {
struct Snapshot;
}
namespace GUI {
class GLCanvas3D;
class ClippingPlane;
enum class SLAGizmoEventType : unsigned char;
class CommonGizmosDataPool;
//BBS: GUI refactor: add object manipulation
class GizmoObjectManipulation;
class Rect
{
float m_left;
float m_top;
float m_right;
float m_bottom;
public:
Rect() : m_left(0.0f) , m_top(0.0f) , m_right(0.0f) , m_bottom(0.0f) {}
Rect(float left, float top, float right, float bottom) : m_left(left) , m_top(top) , m_right(right) , m_bottom(bottom) {}
float get_left() const { return m_left; }
void set_left(float left) { m_left = left; }
float get_top() const { return m_top; }
void set_top(float top) { m_top = top; }
float get_right() const { return m_right; }
void set_right(float right) { m_right = right; }
float get_bottom() const { return m_bottom; }
void set_bottom(float bottom) { m_bottom = bottom; }
float get_width() const { return m_right - m_left; }
float get_height() const { return m_top - m_bottom; }
};
class GLGizmosManager : public Slic3r::ObjectBase
{
public:
static const float Default_Icons_Size;
enum EType : unsigned char
{
// Order must match index in m_gizmos!
Move,
Rotate,
Scale,
Flatten,
Cut,
FdmSupports,
MmuSegmentation,
Simplify,
Modifier,
Seam,
SlaSupports,
// BBS
//FaceRecognition,
Hollow,
Undefined,
};
private:
struct Layout
{
float scale{ 1.0f };
float icons_size{ Default_Icons_Size };
float border{ 5.0f };
float gap_y{ 5.0f };
//BBS: GUI refactor: to support top layout
float gap_x{ 5.0f };
float stride_x() const { return icons_size + gap_x;}
float scaled_gap_x() const { return scale * gap_x; }
float scaled_stride_x() const { return scale * stride_x(); }
float stride_y() const { return icons_size + gap_y;}
float scaled_icons_size() const { return scale * icons_size; }
float scaled_border() const { return scale * border; }
float scaled_gap_y() const { return scale * gap_y; }
float scaled_stride_y() const { return scale * stride_y(); }
};
GLCanvas3D& m_parent;
bool m_enabled;
std::vector<std::unique_ptr<GLGizmoBase>> m_gizmos;
mutable GLTexture m_icons_texture;
mutable bool m_icons_texture_dirty;
BackgroundTexture m_background_texture;
BackgroundTexture m_arrow_texture;
Layout m_layout;
EType m_current;
EType m_hover;
std::pair<EType, bool> m_highlight; // bool true = higlightedShown, false = highlightedHidden
//BBS: GUI refactor: add object manipulation
GizmoObjectManipulation m_object_manipulation;
std::vector<size_t> get_selectable_idxs() const;
size_t get_gizmo_idx_from_mouse(const Vec2d& mouse_pos) const;
bool activate_gizmo(EType type);
struct MouseCapture
{
bool left;
bool middle;
bool right;
GLCanvas3D* parent;
MouseCapture() { reset(); }
bool any() const { return left || middle || right; }
void reset() { left = middle = right = false; parent = nullptr; }
};
MouseCapture m_mouse_capture;
std::string m_tooltip;
bool m_serializing;
std::unique_ptr<CommonGizmosDataPool> m_common_gizmos_data;
// key MENU_ICON_NAME, value = ImtextureID
std::map<int, void*> icon_list;
public:
enum MENU_ICON_NAME {
IC_TOOLBAR_RESET = 0,
IC_TOOLBAR_RESET_HOVER,
IC_TOOLBAR_TOOLTIP,
IC_TOOLBAR_TOOLTIP_HOVER,
IC_NAME_COUNT,
};
explicit GLGizmosManager(GLCanvas3D& parent);
bool init();
bool init_icon_textures();
bool init_arrow(const BackgroundTexture::Metadata& arrow_texture);
template<class Archive>
void load(Archive& ar)
{
if (!m_enabled)
return;
m_serializing = true;
// Following is needed to know which to be turn on, but not actually modify
// m_current prematurely, so activate_gizmo is not confused.
EType old_current = m_current;
ar(m_current);
EType new_current = m_current;
m_current = old_current;
// activate_gizmo call sets m_current and calls set_state for the gizmo
// it does nothing in case the gizmo is already activated
// it can safely be called for Undefined gizmo
activate_gizmo(new_current);
if (m_current != Undefined)
m_gizmos[m_current]->load(ar);
}
template<class Archive>
void save(Archive& ar) const
{
if (!m_enabled)
return;
ar(m_current);
if (m_current != Undefined && !m_gizmos.empty())
m_gizmos[m_current]->save(ar);
}
bool is_enabled() const { return m_enabled; }
void set_enabled(bool enable) { m_enabled = enable; }
void set_overlay_icon_size(float size);
void set_overlay_scale(float scale);
void refresh_on_off_state();
void reset_all_states();
bool is_serializing() const { return m_serializing; }
bool open_gizmo(EType type);
bool check_gizmos_closed_except(EType) const;
void set_hover_id(int id);
void enable_grabber(EType type, unsigned int id, bool enable);
void update(const Linef3& mouse_ray, const Point& mouse_pos);
void update_data();
EType get_current_type() const { return m_current; }
GLGizmoBase* get_current() const;
EType get_gizmo_from_name(const std::string& gizmo_name) const;
bool is_running() const;
bool handle_shortcut(int key);
bool is_dragging() const;
void start_dragging();
void stop_dragging();
Vec3d get_displacement() const;
Vec3d get_scale() const;
void set_scale(const Vec3d& scale);
Vec3d get_scale_offset() const;
Vec3d get_rotation() const;
void set_rotation(const Vec3d& rotation);
// BBS
void finish_cut_rotation();
//BBS
void* get_icon_texture_id(MENU_ICON_NAME icon) {
if (icon_list.find((int)icon) != icon_list.end())
return icon_list[icon];
else
return nullptr;
}
Vec3d get_flattening_normal() const;
void set_flattening_data(const ModelObject* model_object);
void set_sla_support_data(ModelObject* model_object);
void set_painter_gizmo_data();
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position = Vec2d::Zero(), bool shift_down = false, bool alt_down = false, bool control_down = false);
ClippingPlane get_clipping_plane() const;
bool wants_reslice_supports_on_undo() const;
bool is_in_editing_mode(bool error_notification = false) const;
bool is_hiding_instances() const;
void render_current_gizmo() const;
void render_current_gizmo_for_picking_pass() const;
void render_painter_gizmo() const;
void render_overlay() const;
void render_arrow(const GLCanvas3D& parent, EType highlighted_type) const;
std::string get_tooltip() const;
bool on_mouse(wxMouseEvent& evt);
bool on_mouse_wheel(wxMouseEvent& evt);
bool on_char(wxKeyEvent& evt);
bool on_key(wxKeyEvent& evt);
void update_after_undo_redo(const UndoRedo::Snapshot& snapshot);
int get_selectable_icons_cnt() const { return get_selectable_idxs().size(); }
int get_shortcut_key(GLGizmosManager::EType) const;
// To end highlight set gizmo = undefined
void set_highlight(EType gizmo, bool highlight_shown) { m_highlight = std::pair<EType, bool>(gizmo, highlight_shown); }
bool get_highlight_state() const { return m_highlight.second; }
//BBS: GUI refactor: GLToolbar adjust
float get_scaled_total_height() const;
float get_scaled_total_width() const;
//GizmoObjectManipulation& get_object_manipulation() { return m_object_manipulation; }
bool get_uniform_scaling() const { return m_object_manipulation.get_uniform_scaling();}
private:
void render_background(float left, float top, float right, float bottom, float border) const;
void do_render_overlay() const;
bool generate_icons_texture() const;
void update_on_off_state(const Vec2d& mouse_pos);
std::string update_hover_state(const Vec2d& mouse_pos);
bool grabber_contains_mouse() const;
};
} // namespace GUI
} // namespace Slic3r
namespace cereal
{
template <class Archive> struct specialize<Archive, Slic3r::GUI::GLGizmosManager, cereal::specialization::member_load_save> {};
}
#endif // slic3r_GUI_GLGizmosManager_hpp_

View file

@ -0,0 +1,952 @@
#include "slic3r/GUI/ImGuiWrapper.hpp"
#include <imgui/imgui_internal.h>
#include "GizmoObjectManipulation.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
//#include "I18N.hpp"
#include "GLGizmosManager.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "libslic3r/AppConfig.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/Geometry.hpp"
#include "slic3r/GUI/Selection.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/MainFrame.hpp"
#include <boost/algorithm/string.hpp>
namespace Slic3r
{
namespace GUI
{
const double GizmoObjectManipulation::in_to_mm = 25.4;
const double GizmoObjectManipulation::mm_to_in = 0.0393700787;
// Helper function to be used by drop to bed button. Returns lowest point of this
// volume in world coordinate system.
static double get_volume_min_z(const GLVolume* volume)
{
const Transform3f& world_matrix = volume->world_matrix().cast<float>();
// need to get the ModelVolume pointer
const ModelObject* mo = wxGetApp().model().objects[volume->composite_id.object_id];
const ModelVolume* mv = mo->volumes[volume->composite_id.volume_id];
const TriangleMesh& hull = mv->get_convex_hull();
float min_z = std::numeric_limits<float>::max();
for (const stl_vertex& vert : hull.its.vertices) {
min_z = std::min(min_z, Vec3f::UnitZ().dot(world_matrix * vert));
}
return min_z;
}
GizmoObjectManipulation::GizmoObjectManipulation(GLCanvas3D& glcanvas)
: m_glcanvas(glcanvas)
{
m_imperial_units = wxGetApp().app_config->get("use_inches") == "1";
m_new_unit_string = m_imperial_units ? L("in") : L("mm");
}
void GizmoObjectManipulation::UpdateAndShow(const bool show)
{
if (show) {
this->set_dirty();
this->update_if_dirty();
}
}
void GizmoObjectManipulation::update_ui_from_settings()
{
if (m_imperial_units != (wxGetApp().app_config->get("use_inches") == "1")) {
m_imperial_units = wxGetApp().app_config->get("use_inches") == "1";
m_new_unit_string = m_imperial_units ? L("in") : L("mm");
update_buffered_value();
}
}
void GizmoObjectManipulation::update_settings_value(const Selection& selection)
{
m_new_move_label_string = L("Position");
m_new_rotate_label_string = L("Rotation");
m_new_scale_label_string = L("Scale ratios");
m_world_coordinates = true;
ObjectList* obj_list = wxGetApp().obj_list();
if (selection.is_single_full_instance()) {
// all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
m_new_position = volume->get_instance_offset();
// Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible.
if (m_world_coordinates && ! m_uniform_scale &&
! Geometry::is_rotation_ninety_degrees(volume->get_instance_rotation())) {
// Manipulating an instance in the world coordinate system, rotation is not multiples of ninety degrees, therefore enforce uniform scaling.
m_uniform_scale = true;
}
if (m_world_coordinates) {
m_new_rotate_label_string = L("Rotate");
m_new_rotation = Vec3d::Zero();
m_new_size = selection.get_scaled_instance_bounding_box().size();
m_new_scale = m_new_size.cwiseProduct(selection.get_unscaled_instance_bounding_box().size().cwiseInverse()) * 100.;
}
else {
m_new_rotation = volume->get_instance_rotation() * (180. / M_PI);
m_new_size = volume->get_instance_transformation().get_scaling_factor().cwiseProduct(wxGetApp().model().objects[volume->object_idx()]->raw_mesh_bounding_box().size());
m_new_scale = volume->get_instance_scaling_factor() * 100.;
}
m_new_enabled = true;
// BBS: change "Instance Operations" to "Object Operations"
m_new_title_string = L("Object Operations");
}
else if (selection.is_single_full_object() && obj_list->is_selected(itObject)) {
const BoundingBoxf3& box = selection.get_bounding_box();
m_new_position = box.center();
m_new_rotation = Vec3d::Zero();
m_new_scale = Vec3d(100., 100., 100.);
m_new_size = box.size();
m_new_rotate_label_string = L("Rotate");
m_new_scale_label_string = L("Scale");
m_new_enabled = true;
m_new_title_string = L("Object Operations");
}
else if (selection.is_single_modifier() || selection.is_single_volume()) {
// the selection contains a single volume
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
m_new_position = volume->get_volume_offset();
m_new_rotation = volume->get_volume_rotation() * (180. / M_PI);
m_new_scale = volume->get_volume_scaling_factor() * 100.;
m_new_size = volume->get_instance_transformation().get_scaling_factor().cwiseProduct(volume->get_volume_transformation().get_scaling_factor().cwiseProduct(volume->bounding_box().size()));
m_new_enabled = true;
m_new_title_string = L("Volume Operations");
}
else if (obj_list->multiple_selection() || obj_list->is_selected(itInstanceRoot)) {
reset_settings_value();
m_new_move_label_string = L("Translate");
m_new_rotate_label_string = L("Rotate");
m_new_scale_label_string = L("Scale");
m_new_size = selection.get_bounding_box().size();
m_new_enabled = true;
m_new_title_string = L("Group Operations");
}
else {
// No selection, reset the cache.
// assert(selection.is_empty());
reset_settings_value();
}
}
void GizmoObjectManipulation::update_buffered_value()
{
if (this->m_imperial_units)
m_buffered_position = this->m_new_position * this->mm_to_in;
else
m_buffered_position = this->m_new_position;
m_buffered_rotation = this->m_new_rotation;
m_buffered_scale = this->m_new_scale;
if (this->m_imperial_units)
m_buffered_size = this->m_new_size * this->mm_to_in;
else
m_buffered_size = this->m_new_size;
}
void GizmoObjectManipulation::update_if_dirty()
{
if (! m_dirty)
return;
const Selection &selection = m_glcanvas.get_selection();
this->update_settings_value(selection);
this->update_buffered_value();
auto update_label = [](wxString &label_cache, const std::string &new_label) {
wxString new_label_localized = _(new_label) + ":";
if (label_cache != new_label_localized) {
label_cache = new_label_localized;
}
};
update_label(m_cache.move_label_string, m_new_move_label_string);
update_label(m_cache.rotate_label_string, m_new_rotate_label_string);
update_label(m_cache.scale_label_string, m_new_scale_label_string);
enum ManipulationEditorKey
{
mePosition = 0,
meRotation,
meScale,
meSize
};
for (int i = 0; i < 3; ++ i) {
auto update = [this, i](Vec3d &cached, Vec3d &cached_rounded, const Vec3d &new_value) {
//wxString new_text = double_to_string(new_value(i), 2);
double new_rounded = round(new_value(i)*100)/100.0;
//new_text.ToDouble(&new_rounded);
if (std::abs(cached_rounded(i) - new_rounded) > EPSILON) {
cached_rounded(i) = new_rounded;
//const int id = key_id*3+i;
//if (m_imperial_units && (key_id == mePosition || key_id == meSize))
// new_text = double_to_string(new_value(i)*mm_to_in, 2);
//if (id >= 0) m_editors[id]->set_value(new_text);
}
cached(i) = new_value(i);
};
update(m_cache.position, m_cache.position_rounded, m_new_position);
update(m_cache.scale, m_cache.scale_rounded, m_new_scale);
update(m_cache.size, m_cache.size_rounded, m_new_size);
update(m_cache.rotation, m_cache.rotation_rounded, m_new_rotation);
}
if (selection.requires_uniform_scale()) {
m_uniform_scale = true;
}
update_reset_buttons_visibility();
//update_mirror_buttons_visibility();
m_dirty = false;
}
void GizmoObjectManipulation::update_reset_buttons_visibility()
{
const Selection& selection = m_glcanvas.get_selection();
if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) {
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
Vec3d rotation;
Vec3d scale;
double min_z = 0.;
if (selection.is_single_full_instance()) {
rotation = volume->get_instance_rotation();
scale = volume->get_instance_scaling_factor();
}
else {
rotation = volume->get_volume_rotation();
scale = volume->get_volume_scaling_factor();
min_z = get_volume_min_z(volume);
}
m_show_clear_rotation = !rotation.isApprox(Vec3d::Zero());
m_show_clear_scale = !scale.isApprox(Vec3d::Ones(), EPSILON);
m_show_drop_to_bed = (std::abs(min_z) > EPSILON);
}
}
void GizmoObjectManipulation::reset_settings_value()
{
m_new_position = Vec3d::Zero();
m_new_rotation = Vec3d::Zero();
m_new_scale = Vec3d::Ones() * 100.;
m_new_size = Vec3d::Zero();
m_new_enabled = false;
// no need to set the dirty flag here as this method is called from update_settings_value(),
// which is called from update_if_dirty(), which resets the dirty flag anyways.
// m_dirty = true;
}
void GizmoObjectManipulation::change_position_value(int axis, double value)
{
if (std::abs(m_cache.position_rounded(axis) - value) < EPSILON)
return;
Vec3d position = m_cache.position;
position(axis) = value;
Selection& selection = m_glcanvas.get_selection();
selection.start_dragging();
selection.translate(position - m_cache.position, selection.requires_local_axes());
m_glcanvas.do_move(L("Set Position"));
m_cache.position = position;
m_cache.position_rounded(axis) = DBL_MAX;
this->UpdateAndShow(true);
}
void GizmoObjectManipulation::change_rotation_value(int axis, double value)
{
if (std::abs(m_cache.rotation_rounded(axis) - value) < EPSILON)
return;
Vec3d rotation = m_cache.rotation;
rotation(axis) = value;
Selection& selection = m_glcanvas.get_selection();
TransformationType transformation_type(TransformationType::World_Relative_Joint);
if (selection.is_single_full_instance() || selection.requires_local_axes())
transformation_type.set_independent();
if (selection.is_single_full_instance() && ! m_world_coordinates) {
//FIXME Selection::rotate() does not process absoulte rotations correctly: It does not recognize the axis index, which was changed.
// transformation_type.set_absolute();
transformation_type.set_local();
}
selection.start_dragging();
selection.rotate(
(M_PI / 180.0) * (transformation_type.absolute() ? rotation : rotation - m_cache.rotation),
transformation_type);
m_glcanvas.do_rotate(L("Set Orientation"));
m_cache.rotation = rotation;
m_cache.rotation_rounded(axis) = DBL_MAX;
this->UpdateAndShow(true);
}
void GizmoObjectManipulation::change_scale_value(int axis, double value)
{
if (std::abs(m_cache.scale_rounded(axis) - value) < EPSILON)
return;
Vec3d scale = m_cache.scale;
scale(axis) = value;
this->do_scale(axis, scale);
m_cache.scale = scale;
m_cache.scale_rounded(axis) = DBL_MAX;
this->UpdateAndShow(true);
}
void GizmoObjectManipulation::change_size_value(int axis, double value)
{
if (std::abs(m_cache.size_rounded(axis) - value) < EPSILON)
return;
Vec3d size = m_cache.size;
size(axis) = value;
const Selection& selection = m_glcanvas.get_selection();
Vec3d ref_size = m_cache.size;
if (selection.is_single_volume() || selection.is_single_modifier())
ref_size = selection.get_volume(*selection.get_volume_idxs().begin())->bounding_box().size();
else if (selection.is_single_full_instance())
ref_size = m_world_coordinates ?
selection.get_unscaled_instance_bounding_box().size() :
wxGetApp().model().objects[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size();
this->do_scale(axis, 100. * Vec3d(size(0) / ref_size(0), size(1) / ref_size(1), size(2) / ref_size(2)));
m_cache.size = size;
m_cache.size_rounded(axis) = DBL_MAX;
this->UpdateAndShow(true);
}
void GizmoObjectManipulation::do_scale(int axis, const Vec3d &scale) const
{
Selection& selection = m_glcanvas.get_selection();
Vec3d scaling_factor = scale;
TransformationType transformation_type(TransformationType::World_Relative_Joint);
if (selection.is_single_full_instance()) {
transformation_type.set_absolute();
if (! m_world_coordinates)
transformation_type.set_local();
}
if (m_uniform_scale || selection.requires_uniform_scale())
scaling_factor = scale(axis) * Vec3d::Ones();
selection.start_dragging();
selection.scale(scaling_factor * 0.01, transformation_type);
m_glcanvas.do_scale(L("Set Scale"));
}
void GizmoObjectManipulation::on_change(const std::string& opt_key, int axis, double new_value)
{
if (!m_cache.is_valid())
return;
if (m_imperial_units && (opt_key == "position" || opt_key == "size"))
new_value *= in_to_mm;
if (opt_key == "position")
change_position_value(axis, new_value);
else if (opt_key == "rotation")
change_rotation_value(axis, new_value);
else if (opt_key == "scale")
change_scale_value(axis, new_value);
else if (opt_key == "size")
change_size_value(axis, new_value);
}
void GizmoObjectManipulation::reset_position_value()
{
Selection& selection = m_glcanvas.get_selection();
if (selection.is_single_volume() || selection.is_single_modifier()) {
GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(*selection.get_volume_idxs().begin()));
volume->set_volume_offset(Vec3d::Zero());
}
else if (selection.is_single_full_instance()) {
for (unsigned int idx : selection.get_volume_idxs()) {
GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(idx));
volume->set_instance_offset(Vec3d::Zero());
}
}
else
return;
// Copy position values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing.
m_glcanvas.do_move(L("Reset Position"));
UpdateAndShow(true);
}
void GizmoObjectManipulation::reset_rotation_value()
{
Selection& selection = m_glcanvas.get_selection();
if (selection.is_single_volume() || selection.is_single_modifier()) {
GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(*selection.get_volume_idxs().begin()));
volume->set_volume_rotation(Vec3d::Zero());
}
else if (selection.is_single_full_instance()) {
for (unsigned int idx : selection.get_volume_idxs()) {
GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(idx));
volume->set_instance_rotation(Vec3d::Zero());
}
}
else
return;
// Update rotation at the GLVolumes.
selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL);
selection.synchronize_unselected_volumes();
// Copy rotation values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing.
m_glcanvas.do_rotate(L("Reset Rotation"));
UpdateAndShow(true);
}
void GizmoObjectManipulation::reset_scale_value()
{
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Reset scale");
change_scale_value(0, 100.);
change_scale_value(1, 100.);
change_scale_value(2, 100.);
}
void GizmoObjectManipulation::set_uniform_scaling(const bool new_value)
{
const Selection &selection = m_glcanvas.get_selection();
if (selection.is_single_full_instance() && m_world_coordinates && !new_value) {
// Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible.
// all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
// Is the angle close to a multiple of 90 degrees?
if (! Geometry::is_rotation_ninety_degrees(volume->get_instance_rotation())) {
// Cannot apply scaling in the world coordinate system.
// BBS: remove tilt prompt dialog
// Bake the rotation into the meshes of the object.
wxGetApp().model().objects[volume->composite_id.object_id]->bake_xy_rotation_into_meshes(volume->composite_id.instance_id);
// Update the 3D scene, selections etc.
wxGetApp().plater()->update();
// Recalculate cached values at this panel, refresh the screen.
this->UpdateAndShow(true);
}
}
m_uniform_scale = new_value;
}
static const char* label_values[2][3] = {
{ "##position_x", "##position_y", "##position_z"},
{ "##rotation_x", "##rotation_y", "##rotation_z"}
};
static const char* label_scale_values[2][3] = {
{ "##scale_x", "##scale_y", "##scale_z"},
{ "##size_x", "##size_y", "##size_z"}
};
bool GizmoObjectManipulation::reset_button(ImGuiWrapper *imgui_wrapper, float caption_max, float unit_size, float space_size, float end_text_size)
{
bool pressed = false;
ImTextureID normal_id = m_glcanvas.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_RESET);
ImTextureID hover_id = m_glcanvas.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_RESET_HOVER);
float font_size = ImGui::GetFontSize();
ImVec2 button_size = ImVec2(font_size, font_size);
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
pressed = ImGui::ImageButton3(normal_id, hover_id, button_size);
ImGui::PopStyleVar(1);
return pressed;
}
float GizmoObjectManipulation::max_unit_size(int number, Vec3d &vec1, Vec3d &vec2,std::string str)
{
if (number <= 1) return -1;
Vec3d vec[2] = {vec1, vec2};
float nuit_max[4] = {0};
float vec_max = 0, unit_size = 0;
for (int i = 0; i < number; i++)
{
char buf[3][64] = {0};
float buf_size[3] = {0};
for (int j = 0; j < 3; j++) {
ImGui::DataTypeFormatString(buf[j], IM_ARRAYSIZE(buf[j]), ImGuiDataType_Double, (void *) &vec[i][j], "%.2f");
buf_size[j] = ImGui::CalcTextSize(buf[j]).x;
vec_max = std::max(buf_size[j], vec_max);
nuit_max[i] = vec_max;
}
unit_size = std::max(nuit_max[i], unit_size);
}
for (int i = 0; i < 3; i++)
{
if (str == "scale") {
if (vec1[i] > 39062.46)vec1[i] = 39062.46;
if (vec2[i] > 9999.99)vec2[i] = 9999.99;
}
if (str == "move") {
if (vec1[i] > 9999.99)vec1[i] = 9999.99;
if (vec2[i] > 9999.99)vec2[i] = 9999.99;
}
}
return unit_size + 8.0;
}
void GizmoObjectManipulation::do_render_move_window(ImGuiWrapper *imgui_wrapper, std::string window_name, float x, float y, float bottom_limit)
{
// BBS: GUI refactor: move gizmo to the right
if (abs(last_move_input_window_width) > 0.01f) {
if (x + last_move_input_window_width > m_glcanvas.get_canvas_size().get_width()) {
if (last_move_input_window_width > m_glcanvas.get_canvas_size().get_width())
x = 0;
else
x = m_glcanvas.get_canvas_size().get_width() - last_move_input_window_width;
}
}
#if BBS_TOOLBAR_ON_TOP
imgui_wrapper->set_next_window_pos(x, y, ImGuiCond_Always, 0.f, 0.0f);
#else
imgui_wrapper->set_next_window_pos(x, y, ImGuiCond_Always, 1.0f, 0.0f);
#endif
// BBS
ImGuiWrapper::push_toolbar_style();
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0, 6.0));
std::string name = this->m_new_title_string + "##" + window_name;
imgui_wrapper->begin(_L(name), ImGuiWrapper::TOOLBAR_WINDOW_FLAGS);
auto update = [this](unsigned int active_id, std::string opt_key, Vec3d original_value, Vec3d new_value) -> int {
for (int i = 0; i < 3; i++) {
if (original_value[i] != new_value[i]) {
if (active_id != m_last_active_item) {
on_change(opt_key, i, new_value[i]);
return i;
}
}
}
return -1;
};
float space_size = imgui_wrapper->get_style_scaling() * 8;
float position_size = imgui_wrapper->calc_text_size(_L("Position")).x + space_size;
float World_size = imgui_wrapper->calc_text_size(_L("World coordinates")).x + space_size;
float caption_max = std::max(position_size, World_size) + 2 * space_size;
float end_text_size = imgui_wrapper->calc_text_size(this->m_new_unit_string).x;
// position
Vec3d original_position;
if (this->m_imperial_units)
original_position = this->m_new_position * this->mm_to_in;
else
original_position = this->m_new_position;
Vec3d display_position = m_buffered_position;
// Rotation
Vec3d rotation = this->m_buffered_rotation;
float unit_size = max_unit_size(2, display_position, display_position, "move") + space_size;;
int index = 1;
int index_unit = 1;
ImGui::AlignTextToFramePadding();
unsigned int current_active_id = ImGui::GetActiveID();
ImGui::PushItemWidth(caption_max);
imgui_wrapper->text(_L("World coordinates"));
ImGui::SameLine(caption_max + index * space_size);
ImGui::PushItemWidth(unit_size);
ImGui::TextAlignCenter("X");
ImGui::SameLine(caption_max + unit_size + (++index) * space_size);
ImGui::PushItemWidth(unit_size);
ImGui::TextAlignCenter("Y");
ImGui::SameLine(caption_max + (++index_unit) * unit_size + (++index) * space_size);
ImGui::PushItemWidth(unit_size);
ImGui::TextAlignCenter("Z");
index = 1;
index_unit = 1;
ImGui::AlignTextToFramePadding();
imgui_wrapper->text(_L("Position"));
ImGui::SameLine(caption_max + index * space_size);
ImGui::PushItemWidth(unit_size);
ImGui::BBLInputDouble(label_values[0][0], &display_position[0], 0.0f, 0.0f, "%.2f");
ImGui::SameLine(caption_max + unit_size + (++index) * space_size);
ImGui::PushItemWidth(unit_size);
ImGui::BBLInputDouble(label_values[0][1], &display_position[1], 0.0f, 0.0f, "%.2f");
ImGui::SameLine(caption_max + (++index_unit) * unit_size + (++index) * space_size);
ImGui::PushItemWidth(unit_size);
ImGui::BBLInputDouble(label_values[0][2], &display_position[2], 0.0f, 0.0f, "%.2f");
ImGui::SameLine(caption_max + (++index_unit) * unit_size + (++index) * space_size);
imgui_wrapper->text(this->m_new_unit_string);
m_buffered_position = display_position;
update(current_active_id, "position", original_position, m_buffered_position);
// the init position values are not zero, won't add reset button
// send focus to m_glcanvas
bool focued_on_text = false;
for (int j = 0; j < 3; j++) {
unsigned int id = ImGui::GetID(label_values[0][j]);
if (current_active_id == id) {
m_glcanvas.handle_sidebar_focus_event(label_values[0][j] + 2, true);
focued_on_text = true;
break;
}
}
if (!focued_on_text) m_glcanvas.handle_sidebar_focus_event("", false);
m_last_active_item = current_active_id;
last_move_input_window_width = ImGui::GetWindowWidth();
imgui_wrapper->end();
ImGui::PopStyleVar(1);
ImGuiWrapper::pop_toolbar_style();
}
void GizmoObjectManipulation::do_render_rotate_window(ImGuiWrapper *imgui_wrapper, std::string window_name, float x, float y, float bottom_limit)
{
// BBS: GUI refactor: move gizmo to the right
if (abs(last_rotate_input_window_width) > 0.01f) {
if (x + last_rotate_input_window_width > m_glcanvas.get_canvas_size().get_width()) {
if (last_rotate_input_window_width > m_glcanvas.get_canvas_size().get_width())
x = 0;
else
x = m_glcanvas.get_canvas_size().get_width() - last_rotate_input_window_width;
}
}
#if BBS_TOOLBAR_ON_TOP
imgui_wrapper->set_next_window_pos(x, y, ImGuiCond_Always, 0.f, 0.0f);
#else
imgui_wrapper->set_next_window_pos(x, y, ImGuiCond_Always, 1.0f, 0.0f);
#endif
// BBS
ImGuiWrapper::push_toolbar_style();
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0, 6.0));
std::string name = this->m_new_title_string + "##" + window_name;
imgui_wrapper->begin(_L(name), ImGuiWrapper::TOOLBAR_WINDOW_FLAGS);
auto update = [this](unsigned int active_id, std::string opt_key, Vec3d original_value, Vec3d new_value) -> int {
for (int i = 0; i < 3; i++) {
if (original_value[i] != new_value[i]) {
if (active_id != m_last_active_item) {
on_change(opt_key, i, new_value[i]);
return i;
}
}
}
return -1;
};
float space_size = imgui_wrapper->get_style_scaling() * 8;
float position_size = imgui_wrapper->calc_text_size(_L("Position")).x + space_size;
float World_size = imgui_wrapper->calc_text_size(_L("World coordinates")).x + space_size;
float caption_max = std::max(position_size, World_size) + 2 * space_size;
float end_text_size = imgui_wrapper->calc_text_size(this->m_new_unit_string).x;
// position
Vec3d original_position;
if (this->m_imperial_units)
original_position = this->m_new_position * this->mm_to_in;
else
original_position = this->m_new_position;
Vec3d display_position = m_buffered_position;
// Rotation
Vec3d rotation = this->m_buffered_rotation;
float unit_size = max_unit_size(2, rotation, rotation, "rotate") + space_size * 2;
int index = 1;
int index_unit = 1;
ImGui::AlignTextToFramePadding();
unsigned int current_active_id = ImGui::GetActiveID();
ImGui::PushItemWidth(caption_max);
imgui_wrapper->text(_L("World coordinates"));
ImGui::SameLine(caption_max + index * space_size);
ImGui::PushItemWidth(unit_size);
ImGui::TextAlignCenter("X");
ImGui::SameLine(caption_max + unit_size + (++index) * space_size);
ImGui::PushItemWidth(unit_size);
ImGui::TextAlignCenter("Y");
ImGui::SameLine(caption_max + (++index_unit) * unit_size + (++index) * space_size);
ImGui::PushItemWidth(unit_size);
ImGui::TextAlignCenter("Z");
index = 1;
index_unit = 1;
// ImGui::PushItemWidth(unit_size * 2);
ImGui::AlignTextToFramePadding();
imgui_wrapper->text(_L("Rotation"));
ImGui::SameLine(caption_max + index * space_size);
ImGui::PushItemWidth(unit_size);
ImGui::BBLInputDouble(label_values[1][0], &rotation[0], 0.0f, 0.0f, "%.2f");
ImGui::SameLine(caption_max + unit_size + (++index) * space_size);
ImGui::PushItemWidth(unit_size);
ImGui::BBLInputDouble(label_values[1][1], &rotation[1], 0.0f, 0.0f, "%.2f");
ImGui::SameLine(caption_max + (++index_unit) * unit_size + (++index) * space_size);
ImGui::PushItemWidth(unit_size);
ImGui::BBLInputDouble(label_values[1][2], &rotation[2], 0.0f, 0.0f, "%.2f");
ImGui::SameLine(caption_max + (++index_unit) * unit_size + (++index) * space_size);
imgui_wrapper->text(_L("°"));
m_buffered_rotation = rotation;
update(current_active_id, "rotation", this->m_new_rotation, m_buffered_rotation);
if (m_show_clear_rotation) {
ImGui::SameLine(caption_max + 3 * unit_size + 4 * space_size + end_text_size);
if (reset_button(imgui_wrapper, caption_max, unit_size, space_size, end_text_size)) { reset_rotation_value(); }
} else {
ImGui::SameLine(caption_max + 3 * unit_size + 5 * space_size + end_text_size);
ImGui::InvisibleButton("", ImVec2(ImGui::GetFontSize(), ImGui::GetFontSize()));
}
// send focus to m_glcanvas
bool focued_on_text = false;
for (int j = 0; j < 3; j++) {
unsigned int id = ImGui::GetID(label_values[1][j]);
if (current_active_id == id) {
m_glcanvas.handle_sidebar_focus_event(label_values[1][j] + 2, true);
focued_on_text = true;
break;
}
}
if (!focued_on_text) m_glcanvas.handle_sidebar_focus_event("", false);
m_last_active_item = current_active_id;
last_rotate_input_window_width = ImGui::GetWindowWidth();
imgui_wrapper->end();
// BBS
ImGui::PopStyleVar(1);
ImGuiWrapper::pop_toolbar_style();
}
void GizmoObjectManipulation::do_render_scale_input_window(ImGuiWrapper* imgui_wrapper, std::string window_name, float x, float y, float bottom_limit)
{
//BBS: GUI refactor: move gizmo to the right
if (abs(last_scale_input_window_width) > 0.01f) {
if (x + last_scale_input_window_width > m_glcanvas.get_canvas_size().get_width()) {
if (last_scale_input_window_width > m_glcanvas.get_canvas_size().get_width())
x = 0;
else
x = m_glcanvas.get_canvas_size().get_width() - last_scale_input_window_width;
}
}
#if BBS_TOOLBAR_ON_TOP
imgui_wrapper->set_next_window_pos(x, y, ImGuiCond_Always, 0.f, 0.0f);
#else
imgui_wrapper->set_next_window_pos(x, y, ImGuiCond_Always, 1.0f, 0.0f);
#endif
//BBS
ImGuiWrapper::push_toolbar_style();
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0, 6.0));
std::string name = this->m_new_title_string + "##" + window_name;
imgui_wrapper->begin(_L(name), ImGuiWrapper::TOOLBAR_WINDOW_FLAGS);
auto update = [this](unsigned int active_id, std::string opt_key, Vec3d original_value, Vec3d new_value)->int {
for (int i = 0; i < 3; i++)
{
if (original_value[i] != new_value[i])
{
if (active_id != m_last_active_item)
{
on_change(opt_key, i, new_value[i]);
return i;
}
}
}
return -1;
};
float space_size = imgui_wrapper->get_style_scaling() * 8;
float caption_max = imgui_wrapper->calc_text_size(_L("Position:")).x + space_size;
float end_text_size = imgui_wrapper->calc_text_size(this->m_new_unit_string).x;
ImGui::AlignTextToFramePadding();
unsigned int current_active_id = ImGui::GetActiveID();
Vec3d scale = m_buffered_scale;
Vec3d display_size = m_buffered_size;
Vec3d display_position = m_buffered_position;
float unit_size = max_unit_size(2, scale, display_size, "scale") + space_size;
bool imperial_units = this->m_imperial_units;
int index = 2;
int index_unit = 1;
ImGui::PushItemWidth(caption_max);
ImGui::Dummy(ImVec2(caption_max, -1));
//imgui_wrapper->text(_L(" "));
//ImGui::PushItemWidth(unit_size * 1.5);
ImGui::SameLine(caption_max + space_size);
ImGui::PushItemWidth(unit_size);
ImGui::TextAlignCenter("X");
ImGui::SameLine(caption_max + unit_size + index * space_size);
ImGui::PushItemWidth(unit_size);
ImGui::TextAlignCenter("Y");
ImGui::SameLine(caption_max + (++index_unit) * unit_size + (++index) * space_size);
ImGui::PushItemWidth(unit_size);
ImGui::TextAlignCenter("Z");
index = 2;
index_unit = 1;
//ImGui::PushItemWidth(unit_size * 2);
ImGui::AlignTextToFramePadding();
imgui_wrapper->text(_L("Scale"));
ImGui::SameLine(caption_max + space_size);
ImGui::PushItemWidth(unit_size);
ImGui::BBLInputDouble(label_scale_values[0][0], &scale[0], 0.0f, 0.0f, "%.2f");
ImGui::SameLine(caption_max + unit_size + index * space_size);
ImGui::PushItemWidth(unit_size);
ImGui::BBLInputDouble(label_scale_values[0][1], &scale[1], 0.0f, 0.0f, "%.2f");
ImGui::SameLine(caption_max + (++index_unit) *unit_size + (++index) * space_size);
ImGui::PushItemWidth(unit_size);
ImGui::BBLInputDouble(label_scale_values[0][2], &scale[2], 0.0f, 0.0f, "%.2f");
ImGui::SameLine(caption_max + (++index_unit) *unit_size + (++index) * space_size);
imgui_wrapper->text(_L("%"));
m_buffered_scale = scale;
if (m_show_clear_scale) {
ImGui::SameLine(caption_max + 3 * unit_size + 4 * space_size + end_text_size);
if (reset_button(imgui_wrapper, caption_max, unit_size, space_size, end_text_size))
reset_scale_value();
} else {
ImGui::SameLine(caption_max + 3 * unit_size + 5 * space_size + end_text_size);
ImGui::InvisibleButton("", ImVec2(ImGui::GetFontSize(), ImGui::GetFontSize()));
}
//Size
Vec3d original_size;
if (this->m_imperial_units)
original_size = this->m_new_size * this->mm_to_in;
else
original_size = this->m_new_size;
index = 2;
index_unit = 1;
//ImGui::PushItemWidth(unit_size * 2);
ImGui::AlignTextToFramePadding();
imgui_wrapper->text(_L("Size"));
ImGui::SameLine(caption_max + space_size);
ImGui::PushItemWidth(unit_size);
ImGui::BBLInputDouble(label_scale_values[1][0], &display_size[0], 0.0f, 0.0f, "%.2f");
ImGui::SameLine(caption_max + unit_size + index * space_size);
ImGui::PushItemWidth(unit_size);
ImGui::BBLInputDouble(label_scale_values[1][1], &display_size[1], 0.0f, 0.0f, "%.2f");
ImGui::SameLine(caption_max + (++index_unit) *unit_size + (++index) * space_size);
ImGui::PushItemWidth(unit_size);
ImGui::BBLInputDouble(label_scale_values[1][2], &display_size[2], 0.0f, 0.0f, "%.2f");
ImGui::SameLine(caption_max + (++index_unit) *unit_size + (++index) * space_size);
imgui_wrapper->text(this->m_new_unit_string);
m_buffered_size = display_size;
int size_sel = update(current_active_id, "size", original_size, m_buffered_size);
ImGui::PopStyleVar(1);
ImGui::Separator();
bool uniform_scale = this->m_uniform_scale;
const Selection &selection = m_glcanvas.get_selection();
bool uniform_scale_only = selection.is_multiple_full_object() || selection.is_multiple_full_instance() || selection.is_mixed() || selection.is_multiple_volume() || selection.is_multiple_modifier();
if (uniform_scale_only) {
imgui_wrapper->disabled_begin(true);
imgui_wrapper->bbl_checkbox(_L("uniform scale"), uniform_scale_only);
imgui_wrapper->disabled_end();
} else {
imgui_wrapper->bbl_checkbox(_L("uniform scale"), uniform_scale);
}
if (uniform_scale != this->m_uniform_scale) { this->set_uniform_scaling(uniform_scale); }
// for (int index = 0; index < 3; index++)
// BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ",before_index="<<index <<boost::format(",scale %1%, buffered %2%, original_id %3%, new_id %4%\n") %
// this->m_new_scale[index] % m_buffered_scale[index] % m_last_active_item % current_active_id;
int scale_sel = update(current_active_id, "scale", this->m_new_scale, m_buffered_scale);
if ((scale_sel >= 0)) {
// for (int index = 0; index < 3; index++)
// BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ",after_index="<<index <<boost::format(",scale %1%, buffered %2%, original_id %3%, new_id %4%\n") %
// this->m_new_scale[index] % m_buffered_scale[index] % m_last_active_item % current_active_id;
for (int i = 0; i < 3; ++i) {
if (i != scale_sel) ImGui::ClearInputTextInitialData(label_scale_values[0][i], m_buffered_scale[i]);
ImGui::ClearInputTextInitialData(label_scale_values[1][i], m_buffered_size[i]);
}
}
if ((size_sel >= 0)) {
// for (int index = 0; index < 3; index++)
// BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ",after_index="<<index <<boost::format(",scale %1%, buffered %2%, original_id %3%, new_id %4%\n") %
// this->m_new_scale[index] % m_buffered_scale[index] % m_last_active_item % current_active_id;
for (int i = 0; i < 3; ++i) {
ImGui::ClearInputTextInitialData(label_scale_values[0][i], m_buffered_scale[i]);
if (i != size_sel) ImGui::ClearInputTextInitialData(label_scale_values[1][i], m_buffered_size[i]);
}
}
//send focus to m_glcanvas
bool focued_on_text = false;
for (int i = 0; i < 2; i++)
for (int j = 0; j < 3; j++)
{
unsigned int id = ImGui::GetID(label_scale_values[i][j]);
if (current_active_id == id)
{
m_glcanvas.handle_sidebar_focus_event(label_scale_values[i][j] + 2, true);
focued_on_text = true;
break;
}
}
if (!focued_on_text)
m_glcanvas.handle_sidebar_focus_event("", false);
m_last_active_item = current_active_id;
last_scale_input_window_width = ImGui::GetWindowWidth();
imgui_wrapper->end();
//BBS
ImGuiWrapper::pop_toolbar_style();
}
} //namespace GUI
} //namespace Slic3r

View file

@ -0,0 +1,147 @@
#ifndef slic3r_GizmoObjectManipulation_hpp_
#define slic3r_GizmoObjectManipulation_hpp_
#include <memory>
#include "libslic3r/Point.hpp"
#include <float.h>
//#include "slic3r/GUI/GLCanvas3D.hpp"
namespace Slic3r {
namespace GUI {
class Selection;
class GLCanvas3D;
class GizmoObjectManipulation
{
public:
static const double in_to_mm;
static const double mm_to_in;
struct Cache
{
Vec3d position;
Vec3d position_rounded;
Vec3d rotation;
Vec3d rotation_rounded;
Vec3d scale;
Vec3d scale_rounded;
Vec3d size;
Vec3d size_rounded;
wxString move_label_string;
wxString rotate_label_string;
wxString scale_label_string;
Cache() { reset(); }
void reset()
{
position = position_rounded = Vec3d(DBL_MAX, DBL_MAX, DBL_MAX);
rotation = rotation_rounded = Vec3d(DBL_MAX, DBL_MAX, DBL_MAX);
scale = scale_rounded = Vec3d(DBL_MAX, DBL_MAX, DBL_MAX);
size = size_rounded = Vec3d(DBL_MAX, DBL_MAX, DBL_MAX);
move_label_string = wxString();
rotate_label_string = wxString();
scale_label_string = wxString();
}
bool is_valid() const { return position != Vec3d(DBL_MAX, DBL_MAX, DBL_MAX); }
};
Cache m_cache;
bool m_imperial_units { false };
// Mirroring buttons and their current state
//enum MirrorButtonState {
// mbHidden,
// mbShown,
// mbActive
//};
//std::array<std::pair<ScalableButton*, MirrorButtonState>, 3> m_mirror_buttons;
// Needs to be updated from OnIdle?
bool m_dirty = false;
// Cached labels for the delayed update, not localized!
std::string m_new_title_string;
std::string m_new_move_label_string;
std::string m_new_rotate_label_string;
std::string m_new_scale_label_string;
std::string m_new_unit_string;
Vec3d m_new_position;
Vec3d m_new_rotation;
Vec3d m_new_scale;
Vec3d m_new_size;
Vec3d m_buffered_position;
Vec3d m_buffered_rotation;
Vec3d m_buffered_scale;
Vec3d m_buffered_size;
bool m_new_enabled {true};
bool m_uniform_scale {true};
// Does the object manipulation panel work in World or Local coordinates?
bool m_world_coordinates = true;
bool m_show_clear_rotation { false };
bool m_show_clear_scale { false };
bool m_show_drop_to_bed { false };
protected:
float last_move_input_window_width = 0.0f;
float last_rotate_input_window_width = 0.0f;
float last_scale_input_window_width = 0.0f;
public:
GizmoObjectManipulation(GLCanvas3D& glcanvas);
~GizmoObjectManipulation() {}
bool IsShown();
void UpdateAndShow(const bool show);
void update_ui_from_settings();
void set_dirty() { m_dirty = true; }
// Called from the App to update the UI if dirty.
void update_if_dirty();
void set_uniform_scaling(const bool uniform_scale);
bool get_uniform_scaling() const { return m_uniform_scale; }
// Does the object manipulation panel work in World or Local coordinates?
void set_world_coordinates(const bool world_coordinates) { m_world_coordinates = world_coordinates; this->UpdateAndShow(true); }
bool get_world_coordinates() const { return m_world_coordinates; }
void reset_cache() { m_cache.reset(); }
void on_change(const std::string& opt_key, int axis, double new_value);
void do_render_move_window(ImGuiWrapper *imgui_wrapper, std::string window_name, float x, float y, float bottom_limit);
void do_render_rotate_window(ImGuiWrapper *imgui_wrapper, std::string window_name, float x, float y, float bottom_limit);
void do_render_scale_input_window(ImGuiWrapper* imgui_wrapper, std::string window_name, float x, float y, float bottom_limit);
float max_unit_size(int number, Vec3d &vec1, Vec3d &vec2,std::string str);
bool reset_button(ImGuiWrapper *imgui_wrapper, float caption_max, float unit_size, float space_size, float end_text_size);
private:
void reset_settings_value();
void update_settings_value(const Selection& selection);
void update_buffered_value();
// Show or hide scale/rotation reset buttons if needed
void update_reset_buttons_visibility();
//Show or hide mirror buttons
//void update_mirror_buttons_visibility();
// change values
void change_position_value(int axis, double value);
void change_rotation_value(int axis, double value);
void change_scale_value(int axis, double value);
void change_size_value(int axis, double value);
void do_scale(int axis, const Vec3d &scale) const;
void reset_position_value();
void reset_rotation_value();
void reset_scale_value();
GLCanvas3D& m_glcanvas;
unsigned int m_last_active_item { 0 };
};
}}
#endif // slic3r_GizmoObjectManipulation_hpp_