Files
OrcaSlicer/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp
T

427 lines
19 KiB
C++

#include "GLGizmoFuzzySkin.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/Print.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/ImGuiWrapper.hpp"
#include "slic3r/GUI/MsgDialog.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/Utils/UndoRedo.hpp"
#include "GLGizmoUtils.hpp"
#include <glad/gl.h>
#include <algorithm>
namespace Slic3r::GUI {
void GLGizmoFuzzySkin::on_shutdown()
{
m_parent.use_slope(false);
m_parent.toggle_model_objects_visibility(true);
}
std::string GLGizmoFuzzySkin::on_get_name() const
{
return _u8L("Paint-on fuzzy skin");
}
bool GLGizmoFuzzySkin::on_init()
{
m_shortcut_key = WXK_CONTROL_H;
const wxString ctrl = GUI::shortkey_ctrl_prefix();
const wxString alt = GUI::shortkey_alt_prefix();
const wxString shift = GUI::shortkey_shift_prefix();
m_desc["reset_direction"] = _L("Reset direction");
m_desc["remove_all"] = _L("Erase all");
m_desc["circle"] = _L("Circle");
m_desc["sphere"] = _L("Sphere");
m_desc["pointer"] = _L("Triangles");
m_desc["tool_type"] = _L("Tool type");
m_desc["tool_brush"] = _L("Brush");
m_desc["tool_smart_fill"] = _L("Smart fill");
m_desc["clipping_of_view"] = _L("Section view");
m_desc["cursor_size"] = _L("Brush size");
m_desc["add_fuzzy_skin"] = _L("Add fuzzy skin");
m_desc["remove_fuzzy_skin"] = _L("Remove fuzzy skin");
m_desc["smart_fill_angle"] = _L("Smart fill angle");
std::pair<wxString, wxString> add_fuzzy_skin_shortcut = {_L("Left mouse button"), m_desc["add_fuzzy_skin"]};
std::pair<wxString, wxString> remove_fuzzy_skin_shortcut = {shift + _L("Left mouse button"), m_desc["remove_fuzzy_skin"]};
std::pair<wxString, wxString> clipping_shortcut = {alt + _L("Mouse wheel"), m_desc["clipping_of_view"]};
m_shortcuts_brush = {
add_fuzzy_skin_shortcut,
remove_fuzzy_skin_shortcut,
{ctrl + _L("Mouse wheel"), m_desc["cursor_size"]},
clipping_shortcut
};
m_shortcuts_triangle = {
add_fuzzy_skin_shortcut,
remove_fuzzy_skin_shortcut,
clipping_shortcut
};
m_shortcuts_smart_fill = {
add_fuzzy_skin_shortcut,
remove_fuzzy_skin_shortcut,
{ctrl + _L("Mouse wheel"), m_desc["smart_fill_angle"]},
clipping_shortcut
};
return true;
}
GLGizmoFuzzySkin::GLGizmoFuzzySkin(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoPainterBase(parent, icon_filename, sprite_id), m_current_tool(ImGui::CircleButtonIcon)
{
}
void GLGizmoFuzzySkin::render_painter_gizmo()
{
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 GLGizmoFuzzySkin::render_tooltip_button(float x, float y)
{
auto get_shortcuts = [this]() -> std::vector<std::pair<wxString, wxString>> {
switch (m_tool_type) {
case ToolType::BRUSH: return m_cursor_type == TriangleSelector::POINTER ? m_shortcuts_triangle : m_shortcuts_brush;
case ToolType::SMART_FILL: return m_shortcuts_smart_fill;
default: return {};
}
};
GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, get_shortcuts(), x, y);
}
void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_limit)
{
ModelObject *mo = m_c->selection_info()->model_object();
if (!mo)
return;
float scale = m_parent.get_scale();
#ifdef WIN32
int dpi = get_dpi_for_window(wxGetApp().GetTopWindow());
scale *= (float) dpi / (float) DPI_DEFAULT;
#endif // WIN32
const DynamicPrintConfig &obj_cfg = mo->config.get();
const float approx_height = m_imgui->scaled(22.f);
y = std::min(y, bottom_limit - approx_height);
//BBS: GUI refactor: move gizmo to the right
#if BBS_TOOLBAR_ON_TOP
GizmoImguiSetNextWIndowPos(x, y, ImGuiCond_Always, 0.0f, 0.0f);
#else
GizmoImguiSetNextWIndowPos(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_parent.get_scale());
float f_scale = m_parent.get_gizmos_manager().get_layout_scale();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale));
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 space_size = m_imgui->get_style_scaling() * 8;
const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x + m_imgui->scaled(1.5f),
m_imgui->calc_text_size(m_desc.at("reset_direction")).x + m_imgui->scaled(1.5f) + ImGui::GetStyle().FramePadding.x * 2);
const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.5f);
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 cursor_type_radio_circle = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f);
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_pointer = m_imgui->calc_text_size(m_desc["pointer"]).x + m_imgui->scaled(2.5f);
const float buttons_width = m_imgui->scaled(0.5f);
const float minimal_slider_width = m_imgui->scaled(4.f);
const float tool_type_radio_left = m_imgui->calc_text_size(m_desc["tool_type"]).x + m_imgui->scaled(1.f);
const float tool_type_radio_brush = m_imgui->calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f);
const float tool_type_radio_smart_fill = m_imgui->calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f);
float caption_max = 0.f;
float total_text_max = 0.f;
for (const std::string t : {"add_fuzzy_skin", "remove_fuzzy_skin"}) {
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 circle_max_width = std::max(clipping_slider_left, cursor_slider_left);
const float sliders_left_width = std::max(smart_fill_slider_left, std::max(cursor_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 float empty_button_width = m_imgui->calc_button_size("").x;
window_width = std::max(window_width, total_text_max);
window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer);
window_width = std::max(window_width, tool_type_radio_left + tool_type_radio_brush + tool_type_radio_smart_fill);
window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f));
const float sliders_width = m_imgui->scaled(7.0f);
const float drag_left_width = ImGui::GetStyle().WindowPadding.x + sliders_width - space_size;
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc["tool_type"]);
std::array<wchar_t, 4> tool_ids;
tool_ids = { ImGui::CircleButtonIcon, ImGui::SphereButtonIcon, ImGui::TriangleButtonIcon, ImGui::FillButtonIcon };
std::array<wchar_t, 4> icons;
if (m_is_dark_mode)
icons = { ImGui::CircleButtonDarkIcon, ImGui::SphereButtonDarkIcon, ImGui::TriangleButtonDarkIcon, ImGui::FillButtonDarkIcon };
else
icons = { ImGui::CircleButtonIcon, ImGui::SphereButtonIcon, ImGui::TriangleButtonIcon, ImGui::FillButtonIcon };
std::array<wxString, 4> tool_tips = { _L("Circle"), _L("Sphere"), _L("Triangle"), _L("Fill") };
for (int i = 0; i < tool_ids.size(); i++) {
//std::string str_label = std::string("");
//std::wstring btn_name = 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.5f));
bool is_active = m_current_tool == tool_ids[i];
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.f);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding , 3.f * scale);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding , ImVec2(4.f * scale, 4.f * scale));
ImGui::PushStyleColor(ImGuiCol_Text , ImVec4(1,1,1,1)); // ORCA Fixes icon rendered without colors while using Light theme
ImGui::PushStyleColor(ImGuiCol_Button , is_active ? ImVec4(0.f, .59f, .53f, .25f) : ImVec4(0,0,0,0)); // ORCA
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, is_active ? ImVec4(0.f, .59f, .53f, .25f) : ImVec4(.6f,.6f,.6f,.2f)); // ORCA
ImGui::PushStyleColor(ImGuiCol_ButtonActive , is_active ? ImVec4(0.f, .59f, .53f, .30f) : ImVec4(0,0,0,0)); // ORCA
ImGui::PushStyleColor(ImGuiCol_Border , is_active ? ImGuiWrapper::COL_ORCA : ImVec4(0,0,0,0)); // ORCA
ImGui::PushStyleColor(ImGuiCol_BorderActive , is_active ? ImGuiWrapper::COL_ORCA : ImVec4(0,0,0,0)); // ORCA matched color for fixing flicker on click
bool btn_clicked = m_imgui->glyph_button(icons[i], ImVec2(16.f * scale, 16.f * scale)); // ORCA glyph_button for fixing unequal paddings
ImGui::PopStyleColor(6);
ImGui::PopStyleVar(3);
if (btn_clicked && m_current_tool != tool_ids[i]) {
m_current_tool = tool_ids[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 == ImGui::CircleButtonIcon || m_current_tool == ImGui::SphereButtonIcon) {
if (m_current_tool == ImGui::CircleButtonIcon)
m_cursor_type = TriangleSelector::CursorType::CIRCLE;
else
m_cursor_type = TriangleSelector::CursorType::SPHERE;
m_tool_type = ToolType::BRUSH;
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("cursor_size"));
ImGui::SameLine(circle_max_width);
ImGui::PushItemWidth(sliders_width);
m_imgui->bbl_slider_float_style("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true);
ImGui::SameLine(drag_left_width + circle_max_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 {
assert(m_current_tool == ImGui::FillButtonIcon);
m_cursor_type = TriangleSelector::CursorType::POINTER;
m_tool_type = ToolType::SMART_FILL;
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(sliders_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(drag_left_width + sliders_left_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");
}
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_by_ratio(-1., false);
});
}
}
auto clp_dist = float(m_c->object_clipper()->get_position());
ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(sliders_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(drag_left_width + sliders_left_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_by_ratio(clp_dist, true); }
ImGui::Separator();
render_tooltip_button(x, y);
ImGui::SameLine();
m_imgui->disabled_begin(mo->is_fuzzy_skin_painted() == false);
if (m_imgui->button(m_desc.at("remove_all"))) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _u8L("Reset selection"), UndoRedo::SnapshotType::GizmoAction);
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();
}
m_imgui->disabled_end();
ImGui::SameLine();
GLGizmoUtils::begin_right_aligned_buttons({_L("Done")});
if (m_imgui->button(_L("Done"))) {
m_parent.reset_all_gizmos();
}
const DynamicPrintConfig &glb_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config;
const bool has_object_fuzzy_override = obj_cfg.option("fuzzy_skin");
const FuzzySkinType effective_fuzzy_skin_state = has_object_fuzzy_override ? obj_cfg.opt_enum<FuzzySkinType>("fuzzy_skin")
: glb_cfg.opt_enum<FuzzySkinType>("fuzzy_skin");
if (effective_fuzzy_skin_state == FuzzySkinType::Disabled_fuzzy) {
float font_size = ImGui::GetFontSize();
auto link_text = [&]() {
ImColor HyperColor = ImGuiWrapper::COL_ORCA;
ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_WARNING);
float parent_width = ImGui::GetContentRegionAvail().x;
m_imgui->text_wrapped(_L("Warning: Fuzzy skin is disabled, painted fuzzy skin will not take effect!"), parent_width);
ImGui::PopStyleColor();
ImGui::PushStyleColor(ImGuiCol_Text, HyperColor.Value);
ImGui::Dummy(ImVec2(font_size * 1.8f, font_size * 1.3f));
ImGui::SameLine();
m_imgui->bold_text(_u8L("Enable painted fuzzy skin for this object"));
ImGui::PopStyleColor();
ImVec2 line_end = ImGui::GetItemRectMax();
line_end.y -= 2.0f;
ImVec2 line_start = line_end;
line_start.x = ImGui::GetItemRectMin().x;
ImGui::GetWindowDrawList()->AddLine(line_start, line_end, HyperColor);
if (ImGui::IsMouseHoveringRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), true)
&& ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
DynamicPrintConfig new_conf = obj_cfg;
new_conf.set_key_value("fuzzy_skin", new ConfigOptionEnum<FuzzySkinType>(FuzzySkinType::None));
mo->config.assign_config(new_conf);
}
};
ImGui::Separator();
link_text();
}
ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding
GizmoImguiEnd();
// BBS
ImGuiWrapper::pop_toolbar_style();
}
void GLGizmoFuzzySkin::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->fuzzy_skin_facets.set(*m_triangle_selectors[idx]);
}
if (updated) {
const ModelObjectPtrs &mos = wxGetApp().model().objects;
wxGetApp().obj_list()->update_info_items(std::find(mos.begin(), mos.end(), mo) - mos.begin());
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
}
}
void GLGizmoFuzzySkin::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<ColorRGBA> 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->fuzzy_skin_facets.get_data(), false);
m_triangle_selectors.back()->request_update_render_data();
}
}
PainterGizmoType GLGizmoFuzzySkin::get_painter_type() const
{
return PainterGizmoType::FUZZY_SKIN;
}
wxString GLGizmoFuzzySkin::handle_snapshot_action_name(bool shift_down, GLGizmoPainterBase::Button button_down) const
{
return shift_down ? _L("Remove fuzzy skin") : _L("Add fuzzy skin");
}
} // namespace Slic3r::GUI