From a5ea46a83abaf4d114f2591a8a89e27b47acc4e2 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 7 Mar 2023 16:47:12 +0100 Subject: [PATCH] Add SVG gizmo - buildable --- src/libslic3r/Geometry.hpp | 6 + src/libslic3r/Point.hpp | 21 + src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 214 +-------- src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 4 - src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp | 613 ++++++++++++++++++++++++ src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp | 172 +++++++ src/slic3r/GUI/ImGuiWrapper.cpp | 42 ++ src/slic3r/GUI/ImGuiWrapper.hpp | 9 + src/slic3r/GUI/SurfaceDrag.cpp | 79 +++ src/slic3r/GUI/SurfaceDrag.hpp | 26 + 11 files changed, 995 insertions(+), 193 deletions(-) create mode 100644 src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp create mode 100644 src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index ba7e7a4b2a..4ccf0f86b2 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -306,6 +306,12 @@ template T angle_to_0_2PI(T angle) return angle; } +template void to_range_pi_pi(T &angle){ + if (angle > T(PI) || angle < -T(PI)) { + int count = static_cast(std::round(angle / (2 * PI))); + angle -= static_cast(count * 2 * PI); + } +} void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval); diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index d53352f28e..d97f1b32d0 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -539,6 +539,27 @@ inline coord_t align_to_grid(coord_t coord, coord_t spacing, coord_t base) inline Point align_to_grid(Point coord, Point spacing, Point base) { return Point(align_to_grid(coord.x(), spacing.x(), base.x()), align_to_grid(coord.y(), spacing.y(), base.y())); } +// MinMaxLimits +template struct MinMax { T min; T max;}; +template +static bool apply(std::optional &val, const MinMax &limit) { + if (!val.has_value()) return false; + return apply(*val, limit); +} +template +static bool apply(T &val, const MinMax &limit) +{ + if (val > limit.max) { + val = limit.max; + return true; + } + if (val < limit.min) { + val = limit.min; + return true; + } + return false; +} + } // namespace Slic3r // start Boost diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 2ca1998e31..6db1456e3d 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -65,6 +65,8 @@ set(SLIC3R_GUI_SOURCES GUI/Gizmos/GLGizmoSeam.hpp GUI/Gizmos/GLGizmoSimplify.cpp GUI/Gizmos/GLGizmoSimplify.hpp + GUI/Gizmos/GLGizmoSVG.cpp + GUI/Gizmos/GLGizmoSVG.hpp GUI/Gizmos/GLGizmoMmuSegmentation.cpp GUI/Gizmos/GLGizmoMmuSegmentation.hpp GUI/Gizmos/GLGizmoMeasure.cpp diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 6deda777e5..82b83dec6d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -67,7 +67,6 @@ using namespace Slic3r::GUI; using namespace Slic3r::GUI::Emboss; namespace priv { -template struct MinMax { T min; T max;}; template struct Limit { // Limitation for view slider range in GUI MinMax gui; @@ -86,40 +85,8 @@ static const struct Limits MinMax line_gap{-20000, 20000}; // in font points // distance text object from surface MinMax angle{-180.f, 180.f}; // in degrees - - template - static bool apply(std::optional &val, const MinMax &limit) { - if (val.has_value()) - return apply(*val, limit); - return false; - } - template - static bool apply(T &val, const MinMax &limit) - { - if (val > limit.max) { - val = limit.max; - return true; - } - if (val < limit.min) { - val = limit.min; - return true; - } - return false; - } - } limits; -// Define where is up vector on model -constexpr double up_limit = 0.9; - -// Normalize radian angle from -PI to PI -template void to_range_pi_pi(T& angle) -{ - if (angle > PI || angle < -PI) { - int count = static_cast(std::round(angle / (2 * PI))); - angle -= static_cast(count * 2 * PI); - } -} } // namespace priv using namespace priv; @@ -232,15 +199,6 @@ enum class IconState : unsigned { activable = 0, hovered /*1*/, disabled /*2*/ } const IconManager::Icon &get_icon(const IconManager::VIcons& icons, IconType type, IconState state); // short call of Slic3r::GUI::button static bool draw_button(const IconManager::VIcons& icons, IconType type, bool disable = false); - -/// -/// Apply camera direction for emboss direction -/// -/// Define view vector -/// Containe Selected Model to modify -/// True when apply change otherwise false -static bool apply_camera_dir(const Camera &camera, GLCanvas3D &canvas); - } // namespace priv void GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mouse_pos) @@ -342,7 +300,7 @@ bool GLGizmoEmboss::on_mouse_for_rotation(const wxMouseEvent &mouse_event) angle += *m_rotate_start_angle; // move to range <-M_PI, M_PI> - priv::to_range_pi_pi(angle); + Geometry::to_range_pi_pi(angle); // set into activ style assert(m_style_manager.is_active_font()); @@ -363,7 +321,7 @@ bool GLGizmoEmboss::on_mouse_for_translate(const wxMouseEvent &mouse_event) return false; std::optional up_limit; - if (m_keep_up) up_limit = priv::up_limit; + if (m_keep_up) up_limit = Slic3r::GUI::up_limit; const Camera &camera = wxGetApp().plater()->get_camera(); bool was_dragging = m_surface_drag.has_value(); bool res = on_mouse_surface_drag(mouse_event, camera, m_surface_drag, m_parent, m_raycast_manager, up_limit); @@ -399,7 +357,7 @@ bool GLGizmoEmboss::on_mouse_for_translate(const wxMouseEvent &mouse_event) if (gl_volume == nullptr || !m_style_manager.is_active_font()) return res; - m_style_manager.get_style().prop.angle = calc_up(gl_volume->world_matrix(), priv::up_limit); + m_style_manager.get_style().prop.angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit); } } return res; @@ -622,12 +580,6 @@ namespace priv { /// static ImVec2 calc_fine_position(const Selection &selection, const ImVec2 &windows_size, const Size &canvas_size); -/// -/// Change position of emboss window -/// -/// -/// When True Only move to be full visible otherwise reset position -static void change_window_position(std::optional &output_window_offset, bool try_to_fix); } // namespace priv void GLGizmoEmboss::on_set_state() @@ -679,7 +631,7 @@ void GLGizmoEmboss::on_set_state() m_set_window_offset = priv::calc_fine_position(m_parent.get_selection(), get_minimal_window_size(), m_parent.get_canvas_size()); } else { if (m_gui_cfg.has_value()) - priv::change_window_position(m_set_window_offset, false); + m_set_window_offset = ImGuiWrapper::change_window_position(on_get_name().c_str(), false); else m_set_window_offset = ImVec2(-1, -1); } @@ -712,7 +664,7 @@ void GLGizmoEmboss::on_stop_dragging() assert(m_style_manager.is_active_font()); assert(gl_volume != nullptr); if (m_style_manager.is_active_font() && gl_volume != nullptr) - m_style_manager.get_font_prop().angle = calc_up(gl_volume->world_matrix(), priv::up_limit); + m_style_manager.get_font_prop().angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit); m_rotate_start_angle.reset(); @@ -1029,7 +981,7 @@ void GLGizmoEmboss::set_volume_by_selection() // Calculate current angle of up vector assert(m_style_manager.is_active_font()); if (m_style_manager.is_active_font()) - m_style_manager.get_font_prop().angle = calc_up(gl_volume->world_matrix(), priv::up_limit); + m_style_manager.get_font_prop().angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit); // calculate scale for height and depth inside of scaled object instance calculate_scale(); @@ -2228,7 +2180,7 @@ void GLGizmoEmboss::fix_transformation(const FontProp &from, // fix rotation float f_angle = f_angle_opt.has_value() ? *f_angle_opt : .0f; float t_angle = t_angle_opt.has_value() ? *t_angle_opt : .0f; - do_rotate(t_angle - f_angle); + do_local_z_rotate(m_parent, t_angle - f_angle); } // fix distance (Z move) when exists difference in styles @@ -2237,7 +2189,7 @@ void GLGizmoEmboss::fix_transformation(const FontProp &from, if (!is_approx(f_move_opt, t_move_opt)) { float f_move = f_move_opt.has_value() ? *f_move_opt : .0f; float t_move = t_move_opt.has_value() ? *t_move_opt : .0f; - do_translate(Vec3d::UnitZ() * (t_move - f_move)); + do_local_z_move(m_parent, t_move - f_move); } } @@ -2669,7 +2621,7 @@ bool GLGizmoEmboss::set_height() { float &value = m_style_manager.get_style().prop.size_in_mm; // size can't be zero or negative - priv::Limits::apply(value, priv::limits.size_in_mm); + apply(value, priv::limits.size_in_mm); if (m_volume == nullptr || !m_volume->text_configuration.has_value()) { assert(false); @@ -2710,7 +2662,7 @@ bool GLGizmoEmboss::set_depth() float &value = m_style_manager.get_style().prop.emboss; // size can't be zero or negative - priv::Limits::apply(value, priv::limits.emboss); + apply(value, priv::limits.emboss); // only different value need process return !is_approx(value, m_volume->text_configuration->style.prop.emboss); @@ -2795,39 +2747,6 @@ bool GLGizmoEmboss::rev_slider(const std::string &name, undo_tooltip, undo_offset, draw_slider_float); } -void GLGizmoEmboss::do_translate(const Vec3d &relative_move) -{ - assert(m_volume != nullptr); - assert(m_volume->text_configuration.has_value()); - Selection &selection = m_parent.get_selection(); - assert(!selection.is_empty()); - selection.setup_cache(); - selection.translate(relative_move, TransformationType::Local); - - std::string snapshot_name; // empty mean no store undo / redo - // NOTE: it use L instead of _L macro because prefix _ is appended inside - // function do_move - // snapshot_name = L("Set surface distance"); - m_parent.do_move(snapshot_name); -} - -void GLGizmoEmboss::do_rotate(float relative_z_angle) -{ - assert(m_volume != nullptr); - assert(m_volume->text_configuration.has_value()); - Selection &selection = m_parent.get_selection(); - assert(!selection.is_empty()); - selection.setup_cache(); - TransformationType transformation_type = TransformationType::Local_Relative_Joint; - selection.rotate(Vec3d(0., 0., relative_z_angle), transformation_type); - - std::string snapshot_name; // empty meand no store undo / redo - // NOTE: it use L instead of _L macro because prefix _ is appended - // inside function do_move - // snapshot_name = L("Set text rotation"); - m_parent.do_rotate(snapshot_name); -} - void GLGizmoEmboss::draw_advanced() { const auto &ff = m_style_manager.get_font_file_with_cache(); @@ -2898,7 +2817,7 @@ void GLGizmoEmboss::draw_advanced() if (rev_slider(tr.char_gap, font_prop.char_gap, def_char_gap, _u8L("Revert gap between letters"), min_char_gap, max_char_gap, units_fmt, _L("Distance between letters"))){ // Condition prevent recalculation when insertint out of limits value by imgui input - if (!priv::Limits::apply(font_prop.char_gap, priv::limits.char_gap) || + if (!apply(font_prop.char_gap, priv::limits.char_gap) || !m_volume->text_configuration->style.prop.char_gap.has_value() || m_volume->text_configuration->style.prop.char_gap != font_prop.char_gap) { // char gap is stored inside of imgui font atlas @@ -2914,7 +2833,7 @@ void GLGizmoEmboss::draw_advanced() if (rev_slider(tr.line_gap, font_prop.line_gap, def_line_gap, _u8L("Revert gap between lines"), min_line_gap, max_line_gap, units_fmt, _L("Distance between lines"))){ // Condition prevent recalculation when insertint out of limits value by imgui input - if (!priv::Limits::apply(font_prop.line_gap, priv::limits.line_gap) || + if (!apply(font_prop.line_gap, priv::limits.line_gap) || !m_volume->text_configuration->style.prop.line_gap.has_value() || m_volume->text_configuration->style.prop.line_gap != font_prop.line_gap) { // line gap is planed to be stored inside of imgui font atlas @@ -2928,7 +2847,7 @@ void GLGizmoEmboss::draw_advanced() &stored_style->prop.boldness : nullptr; if (rev_slider(tr.boldness, font_prop.boldness, def_boldness, _u8L("Undo boldness"), priv::limits.boldness.gui.min, priv::limits.boldness.gui.max, units_fmt, _L("Tiny / Wide glyphs"))){ - if (!priv::Limits::apply(font_prop.boldness, priv::limits.boldness.values) || + if (!apply(font_prop.boldness, priv::limits.boldness.values) || !m_volume->text_configuration->style.prop.boldness.has_value() || m_volume->text_configuration->style.prop.boldness != font_prop.boldness) exist_change = true; @@ -2939,7 +2858,7 @@ void GLGizmoEmboss::draw_advanced() &stored_style->prop.skew : nullptr; if (rev_slider(tr.skew_ration, font_prop.skew, def_skew, _u8L("Undo letter's skew"), priv::limits.skew.gui.min, priv::limits.skew.gui.max, "%.2f", _L("Italic strength ratio"))){ - if (!priv::Limits::apply(font_prop.skew, priv::limits.skew.values) || + if (!apply(font_prop.skew, priv::limits.skew.values) || !m_volume->text_configuration->style.prop.skew.has_value() || m_volume->text_configuration->style.prop.skew != font_prop.skew) exist_change = true; @@ -2987,7 +2906,7 @@ void GLGizmoEmboss::draw_advanced() if (is_moved){ m_volume->text_configuration->style.prop.distance = font_prop.distance; float act_distance = font_prop.distance.has_value() ? *font_prop.distance : .0f; - do_translate(Vec3d::UnitZ() * (act_distance - prev_distance)); + do_local_z_move(m_parent, act_distance - prev_distance); } m_imgui->disabled_end(); @@ -3007,19 +2926,18 @@ void GLGizmoEmboss::draw_advanced() priv::limits.angle.min, priv::limits.angle.max, u8"%.2f °", _L("Rotate text Clock-wise."))) { // convert back to radians and CCW - float angle_rad = static_cast(-angle_deg * M_PI / 180.0); - priv::to_range_pi_pi(angle_rad); - + double angle_rad = -angle_deg * M_PI / 180.0; + Geometry::to_range_pi_pi(angle_rad); - float diff_angle = angle_rad - angle; - do_rotate(diff_angle); + double diff_angle = angle_rad - angle; + do_local_z_rotate(m_parent, diff_angle); // calc angle after rotation const GLVolume *gl_volume = get_selected_gl_volume(m_parent.get_selection()); assert(gl_volume != nullptr); assert(m_style_manager.is_active_font()); if (m_style_manager.is_active_font() && gl_volume != nullptr) - m_style_manager.get_font_prop().angle = calc_up(gl_volume->world_matrix(), priv::up_limit); + m_style_manager.get_font_prop().angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit); // recalculate for surface cut if (font_prop.use_surface) @@ -3070,7 +2988,7 @@ void GLGizmoEmboss::draw_advanced() assert(get_selected_volume(m_parent.get_selection()) == m_volume); const Camera &cam = wxGetApp().plater()->get_camera(); bool use_surface = m_style_manager.get_style().prop.use_surface; - if (priv::apply_camera_dir(cam, m_parent) && use_surface) + if (face_selected_volume_to_camera(cam, m_parent) && use_surface) process(); } else if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", _u8L("Use camera direction for text orientation").c_str()); @@ -3103,9 +3021,9 @@ void GLGizmoEmboss::set_minimal_window_size(bool is_advance_edit_style) float diff_y = window_size.y - min_win_size_prev.y; m_is_advanced_edit_style = is_advance_edit_style; const ImVec2 &min_win_size = get_minimal_window_size(); - ImGui::SetWindowSize(ImVec2(0.f, min_win_size.y + diff_y), - ImGuiCond_Always); - priv::change_window_position(m_set_window_offset, true); + ImVec2 new_window_size(0.f, min_win_size.y + diff_y); + ImGui::SetWindowSize(new_window_size, ImGuiCond_Always); + m_set_window_offset = ImGuiWrapper::change_window_position(on_get_name().c_str(), true); } ImVec2 GLGizmoEmboss::get_minimal_window_size() const @@ -3478,7 +3396,7 @@ bool priv::start_create_volume_on_surface_job( Transform3d instance = gl_volume->get_instance_transformation().get_matrix(); // Create result volume transformation - Transform3d surface_trmat = create_transformation_onto_surface(hit->position, hit->normal, priv::up_limit); + Transform3d surface_trmat = create_transformation_onto_surface(hit->position, hit->normal, Slic3r::GUI::up_limit); const FontProp &font_prop = emboss_data.text_configuration.style.prop; apply_transformation(font_prop, surface_trmat); // new transformation in world coor is surface_trmat @@ -3540,87 +3458,5 @@ ImVec2 priv::calc_fine_position(const Selection &selection, const ImVec2 &window return offset; } -// Need internals to get window -#include "imgui/imgui_internal.h" -void priv::change_window_position(std::optional& output_window_offset, bool try_to_fix) { - const char* name = "Emboss"; - ImGuiWindow *window = ImGui::FindWindowByName(name); - // is window just created - if (window == NULL) - return; - - // position of window on screen - ImVec2 position = window->Pos; - ImVec2 size = window->SizeFull; - - // screen size - ImVec2 screen = ImGui::GetMainViewport()->Size; - - if (position.x < 0) { - if (position.y < 0) - output_window_offset = ImVec2(0, 0); - else - output_window_offset = ImVec2(0, position.y); - } else if (position.y < 0) { - output_window_offset = ImVec2(position.x, 0); - } else if (screen.x < (position.x + size.x)) { - if (screen.y < (position.y + size.y)) - output_window_offset = ImVec2(screen.x - size.x, screen.y - size.y); - else - output_window_offset = ImVec2(screen.x - size.x, position.y); - } else if (screen.y < (position.y + size.y)) { - output_window_offset = ImVec2(position.x, screen.y - size.y); - } - - if (!try_to_fix && output_window_offset.has_value()) - output_window_offset = ImVec2(-1, -1); // Cannot -} - - -bool priv::apply_camera_dir(const Camera &camera, GLCanvas3D &canvas) { - const Vec3d &cam_dir = camera.get_dir_forward(); - - Selection &sel = canvas.get_selection(); - if (sel.is_empty()) return false; - - // camera direction transformed into volume coordinate system - Transform3d to_world = world_matrix_fixed(sel); - Vec3d cam_dir_tr = to_world.inverse().linear() * cam_dir; - cam_dir_tr.normalize(); - - Vec3d emboss_dir(0., 0., -1.); - - // check wether cam_dir is already used - if (is_approx(cam_dir_tr, emboss_dir)) return false; - - assert(sel.get_volume_idxs().size() == 1); - GLVolume *gl_volume = sel.get_volume(*sel.get_volume_idxs().begin()); - - Transform3d vol_rot; - Transform3d vol_tr = gl_volume->get_volume_transformation().get_matrix(); - // check whether cam_dir is opposit to emboss dir - if (is_approx(cam_dir_tr, -emboss_dir)) { - // rotate 180 DEG by y - vol_rot = Eigen::AngleAxis(M_PI_2, Vec3d(0., 1., 0.)); - } else { - // calc params for rotation - Vec3d axe = emboss_dir.cross(cam_dir_tr); - axe.normalize(); - double angle = std::acos(emboss_dir.dot(cam_dir_tr)); - vol_rot = Eigen::AngleAxis(angle, axe); - } - - Vec3d offset = vol_tr * Vec3d::Zero(); - Vec3d offset_inv = vol_rot.inverse() * offset; - Transform3d res = vol_tr * - Eigen::Translation(-offset) * - vol_rot * - Eigen::Translation(offset_inv); - //Transform3d res = vol_tr * vol_rot; - gl_volume->set_volume_transformation(Geometry::Transformation(res)); - get_model_volume(*gl_volume, sel.get_model()->objects)->set_transformation(res); - return true; -} - // any existing icon filename to not influence GUI const std::string GLGizmoEmboss::M_ICON_FILENAME = "cut.svg"; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index 7e7c2aa161..af92e58f54 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -117,10 +117,6 @@ private: void draw_advanced(); bool select_facename(const wxString& facename); - - void do_translate(const Vec3d& relative_move); - void do_rotate(float relative_z_angle); - bool rev_input_mm(const std::string &name, float &value, const float *default_value, const std::string &undo_tooltip, float step, float step_fast, const char *format, bool use_inch, const std::optional& scale); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp new file mode 100644 index 0000000000..20ff2a4435 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp @@ -0,0 +1,613 @@ +#include "GLGizmoSVG.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/GUI_ObjectList.hpp" +#include "slic3r/GUI/GUI_ObjectManipulation.hpp" +#include "slic3r/GUI/MainFrame.hpp" // to update title when add text +#include "slic3r/GUI/NotificationManager.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/MsgDialog.hpp" +#include "slic3r/GUI/format.hpp" +#include "slic3r/GUI/CameraUtils.hpp" +#include "slic3r/GUI/Jobs/EmbossJob.hpp" +#include "slic3r/GUI/Jobs/NotificationProgressIndicator.hpp" +#include "slic3r/Utils/UndoRedo.hpp" + +#include "libslic3r/SVG.hpp" // debug store +#include "libslic3r/Geometry.hpp" // covex hull 2d +#include "libslic3r/Timer.hpp" // covex hull 2d + +#include "libslic3r/NSVGUtils.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/ClipperUtils.hpp" // union_ex + +#include "imgui/imgui_stdlib.h" // using std::string for inputs +#include "nanosvg/nanosvg.h" // load SVG file + +#include // detection of change DPI +#include + +#include +#include // measure enumeration of fonts + +using namespace Slic3r; +using namespace Slic3r::Emboss; +using namespace Slic3r::GUI; +using namespace Slic3r::GUI::Emboss; + +namespace priv { +// Variable keep limits for variables +static const struct Limits +{ + MinMax emboss{0.01f, 1e4f}; // in mm + // distance text object from surface + MinMax angle{-180.f, 180.f}; // in degrees +} limits; +} // namespace priv + +GLGizmoSVG::GLGizmoSVG(GLCanvas3D &parent) + : GLGizmoBase(parent, M_ICON_FILENAME, -3) + , m_volume(nullptr) + , m_rotate_gizmo(parent, GLGizmoRotate::Axis::Z) // grab id = 2 (Z axis) + , m_job_cancel(nullptr) +{ + m_rotate_gizmo.set_group_id(0); + m_rotate_gizmo.set_force_local_coordinate(true); +} + +void GLGizmoSVG::create_volume(const std::string &svg_file_path, ModelVolumeType volume_type, const Vec2d &mouse_pos) { +} + +void GLGizmoSVG::create_volume(const std::string &svg_file_path, ModelVolumeType volume_type) { +} + +bool GLGizmoSVG::is_svg(const ModelVolume *volume) { + if (volume == nullptr) + return false; + return volume->emboss_shape.has_value(); +} + +bool GLGizmoSVG::is_svg_object(const ModelVolume *volume) { + if (volume == nullptr) return false; + if (!volume->emboss_shape.has_value()) return false; + if (volume->type() != ModelVolumeType::MODEL_PART) return false; + for (const ModelVolume *v : volume->get_object()->volumes) { + if (v == volume) continue; + if (v->type() == ModelVolumeType::MODEL_PART) return false; + } + return true; +} + +bool GLGizmoSVG::on_mouse_for_rotation(const wxMouseEvent &mouse_event) +{ + if (mouse_event.Moving()) return false; + + bool used = use_grabbers(mouse_event); + if (!m_dragging) return used; + + if (mouse_event.Dragging()) { + auto &angle_opt = m_volume->text_configuration->style.prop.angle; + if (!m_rotate_start_angle.has_value()) + m_rotate_start_angle = angle_opt.has_value() ? *angle_opt : 0.f; + double angle = m_rotate_gizmo.get_angle(); + angle -= PI / 2; // Grabber is upward + + // temporary rotation + const TransformationType transformation_type = m_parent.get_selection().is_single_text() ? + TransformationType::Local_Relative_Joint : TransformationType::World_Relative_Joint; + m_parent.get_selection().rotate(Vec3d(0., 0., angle), transformation_type); + + angle += *m_rotate_start_angle; + // move to range <-M_PI, M_PI> + Geometry::to_range_pi_pi(angle); + // propagate angle into property + angle_opt = static_cast(angle); + + // do not store zero + if (is_approx(*angle_opt, 0.f)) + angle_opt.reset(); + } + return used; +} + +bool GLGizmoSVG::on_mouse_for_translate(const wxMouseEvent &mouse_event) +{ + // exist selected volume? + if (m_volume == nullptr) + return false; + + std::optional up_limit; + if (m_keep_up) + up_limit = Slic3r::GUI::up_limit; + const Camera &camera = wxGetApp().plater()->get_camera(); + bool was_dragging = m_surface_drag.has_value(); + bool res = on_mouse_surface_drag(mouse_event, camera, m_surface_drag, m_parent, m_raycast_manager, up_limit); + bool is_dragging = m_surface_drag.has_value(); + + // End with surface dragging? + if (was_dragging && !is_dragging) { + // Update surface by new position + if (m_volume->text_configuration->style.prop.use_surface) + process(); + + // Show correct value of height & depth inside of inputs + calculate_scale(); + } + + // Start with dragging + else if (!was_dragging && is_dragging) { + // Cancel job to prevent interuption of dragging (duplicit result) + if (m_job_cancel != nullptr) + m_job_cancel->store(true); + } + + // during drag + else if (was_dragging && is_dragging) { + // update scale of selected volume --> should be approx the same + calculate_scale(); + + // Recalculate angle for GUI + if (!m_keep_up) { + const GLVolume *gl_volume = get_selected_gl_volume(m_parent.get_selection()); + assert(gl_volume != nullptr); + m_angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit); + } + } + return res; +} + +bool GLGizmoSVG::on_mouse(const wxMouseEvent &mouse_event) +{ + // not selected volume + if (m_volume == nullptr || + get_model_volume(m_volume_id, m_parent.get_selection().get_model()->objects) == nullptr || + !m_volume->text_configuration.has_value()) return false; + + if (on_mouse_for_rotation(mouse_event)) return true; + if (on_mouse_for_translate(mouse_event)) return true; + + return false; +} + +bool GLGizmoSVG::on_init() +{ + m_rotate_gizmo.init(); + ColorRGBA gray_color(.6f, .6f, .6f, .3f); + m_rotate_gizmo.set_highlight_color(gray_color); + + // No shortCut + // m_shortcut_key = WXK_CONTROL_T; + + // Set rotation gizmo upwardrotate + m_rotate_gizmo.set_angle(PI / 2); + return true; +} + +std::string GLGizmoSVG::on_get_name() const { return _u8L("SVG"); } + +void GLGizmoSVG::on_render() { + // no volume selected + if (m_volume == nullptr || + get_model_volume(m_volume_id, m_parent.get_selection().get_model()->objects) == nullptr) + return; + + Selection &selection = m_parent.get_selection(); + if (selection.is_empty()) return; + + // prevent get local coordinate system on multi volumes + if (!selection.is_single_volume_or_modifier() && + !selection.is_single_volume_instance()) return; + bool is_surface_dragging = m_surface_drag.has_value(); + bool is_parent_dragging = m_parent.is_mouse_dragging(); + // Do NOT render rotation grabbers when dragging object + bool is_rotate_by_grabbers = m_dragging; + if (is_rotate_by_grabbers || + (!is_surface_dragging && !is_parent_dragging)) { + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); + m_rotate_gizmo.render(); + } +} + +void GLGizmoSVG::on_register_raycasters_for_picking(){ + m_rotate_gizmo.register_raycasters_for_picking(); +} +void GLGizmoSVG::on_unregister_raycasters_for_picking(){ + m_rotate_gizmo.unregister_raycasters_for_picking(); +} + +void GLGizmoSVG::on_render_input_window(float x, float y, float bottom_limit) +{ + set_volume_by_selection(); + + // Configuration creation + double screen_scale = wxDisplay(wxGetApp().plater()).GetScaleFactor(); + float main_toolbar_height = m_parent.get_main_toolbar_height(); + if (!m_gui_cfg.has_value() || // Exist configuration - first run + m_gui_cfg->screen_scale != screen_scale || // change of DPI + m_gui_cfg->main_toolbar_height != main_toolbar_height // change size of view port + ) { + // Create cache for gui offsets + GuiCfg cfg = create_gui_configuration(); + cfg.screen_scale = screen_scale; + cfg.main_toolbar_height = main_toolbar_height; + m_gui_cfg.emplace(std::move(cfg)); + // set position near toolbar + m_set_window_offset = ImVec2(-1.f, -1.f); + } + + const ImVec2 &min_window_size = m_gui_cfg->minimal_window_size; + ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, min_window_size); + + // Draw origin position of text during dragging + if (m_surface_drag.has_value()) { + ImVec2 mouse_pos = ImGui::GetMousePos(); + ImVec2 center( + mouse_pos.x + m_surface_drag->mouse_offset.x(), + mouse_pos.y + m_surface_drag->mouse_offset.y()); + ImU32 color = ImGui::GetColorU32( + m_surface_drag->exist_hit ? + ImVec4(1.f, 1.f, 1.f, .75f) : // transparent white + ImVec4(1.f, .3f, .3f, .75f) + ); // Warning color + const float radius = 16.f; + ImGuiWrapper::draw_cross_hair(center, radius, color); + } + + // check if is set window offset + if (m_set_window_offset.has_value()) { + if (m_set_window_offset->y < 0) + // position near toolbar + m_set_window_offset = ImVec2(x, std::min(y, bottom_limit - min_window_size.y)); + + ImGui::SetNextWindowPos(*m_set_window_offset, ImGuiCond_Always); + m_set_window_offset.reset(); + } + + bool is_opened = true; + ImGuiWindowFlags flag = ImGuiWindowFlags_NoCollapse; + if (ImGui::Begin(on_get_name().c_str(), &is_opened, flag)) { + // Need to pop var before draw window + ImGui::PopStyleVar(); // WindowMinSize + draw_window(); + } else { + ImGui::PopStyleVar(); // WindowMinSize + } + + ImGui::End(); + if (!is_opened) + close(); +} + +void GLGizmoSVG::on_set_state() +{ + // enable / disable bed from picking + // Rotation gizmo must work through bed + m_parent.set_raycaster_gizmos_on_top(GLGizmoBase::m_state == GLGizmoBase::On); + + m_rotate_gizmo.set_state(GLGizmoBase::m_state); + + // Closing gizmo. e.g. selecting another one + if (GLGizmoBase::m_state == GLGizmoBase::Off) { + // refuse outgoing during text preview + if (false) { + GLGizmoBase::m_state = GLGizmoBase::On; + auto notification_manager = wxGetApp().plater()->get_notification_manager(); + notification_manager->push_notification( + NotificationType::CustomNotification, + NotificationManager::NotificationLevel::RegularNotificationLevel, + _u8L("ERROR: Wait until ends or Cancel process.")); + return; + } + reset_volume(); + } else if (GLGizmoBase::m_state == GLGizmoBase::On) { + // Try(when exist) set text configuration by volume + set_volume_by_selection(); + + if (!m_gui_cfg.has_value()) + m_set_window_offset = ImGuiWrapper::change_window_position(on_get_name().c_str(), false); + else + m_set_window_offset = ImVec2(-1, -1); + + // when open by hyperlink it needs to show up + // or after key 'T' windows doesn't appear + // m_parent.set_as_dirty(); + } +} + +void GLGizmoSVG::data_changed() { + set_volume_by_selection(); +} + +void GLGizmoSVG::on_start_dragging() { m_rotate_gizmo.start_dragging(); } +void GLGizmoSVG::on_stop_dragging() +{ + m_rotate_gizmo.stop_dragging(); + + // TODO: when start second rotatiton previous rotation rotate draggers + // This is fast fix for second try to rotate + // When fixing, move grabber above text (not on side) + m_rotate_gizmo.set_angle(PI/2); + + // apply rotation + m_parent.do_rotate(L("Text-Rotate")); + + m_rotate_start_angle.reset(); + + // recalculate for surface cut + if (m_volume != nullptr && + m_volume->emboss_shape.has_value() && + m_volume->emboss_shape->use_surface) + process(); +} +void GLGizmoSVG::on_dragging(const UpdateData &data) { m_rotate_gizmo.dragging(data); } + +GLGizmoSVG::GuiCfg GLGizmoSVG::create_gui_configuration() +{ + GuiCfg cfg; // initialize by default values; + + + return cfg; +} + +void GLGizmoSVG::set_volume_by_selection() +{ + const Selection &selection = m_parent.get_selection(); + const GLVolume *gl_volume = get_selected_gl_volume(selection); + if (gl_volume == nullptr) + return reset_volume(); + + const ModelObjectPtrs &objects = selection.get_model()->objects; + ModelVolume *volume =get_model_volume(*gl_volume, objects); + if (volume == nullptr) + return reset_volume(); + + // is same volume as actual selected? + if (volume->id() == m_volume_id) + return; + + // Do not use focused input value when switch volume(it must swith value) + if (m_volume != nullptr && + m_volume != volume) // when update volume it changed id BUT not pointer + ImGuiWrapper::left_inputs(); + + // is valid svg volume? + if (!is_svg(volume)) + return reset_volume(); + + // cancel previous job + if (m_job_cancel != nullptr) { + m_job_cancel->store(true); + m_job_cancel = nullptr; + } + + m_volume = volume; + m_volume_id = volume->id(); + + // Calculate current angle of up vector + m_angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit); + + // calculate scale for height and depth inside of scaled object instance + calculate_scale(); +} + +void GLGizmoSVG::reset_volume() +{ + if (m_volume == nullptr) + return; // already reseted + + m_volume = nullptr; + m_volume_id.id = 0; +} + +void GLGizmoSVG::calculate_scale() { + Transform3d to_world = m_parent.get_selection().get_first_volume()->world_matrix(); + auto to_world_linear = to_world.linear(); + auto calc = [&to_world_linear](const Vec3d &axe, std::optional& scale)->bool { + Vec3d axe_world = to_world_linear * axe; + double norm_sq = axe_world.squaredNorm(); + if (is_approx(norm_sq, 1.)) { + if (scale.has_value()) + scale.reset(); + else + return false; + } else { + scale = sqrt(norm_sq); + } + return true; + }; + + calc(Vec3d::UnitY(), m_scale_height); + calc(Vec3d::UnitZ(), m_scale_depth); +} + +bool GLGizmoSVG::process() +{ + // no volume is selected -> selection from right panel + assert(m_volume != nullptr); + if (m_volume == nullptr) return false; + + //DataUpdate data{priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel), m_volume->id()}; + + //std::unique_ptr job = nullptr; + + //// check cutting from source mesh + //bool &use_surface = data.text_configuration.style.prop.use_surface; + //bool is_object = m_volume->get_object()->volumes.size() == 1; + //if (use_surface && is_object) + // use_surface = false; + // + //if (use_surface) { + // // Model to cut surface from. + // SurfaceVolumeData::ModelSources sources = create_volume_sources(m_volume); + // if (sources.empty()) return false; + + // Transform3d text_tr = m_volume->get_matrix(); + // auto& fix_3mf = m_volume->text_configuration->fix_3mf_tr; + // if (fix_3mf.has_value()) + // text_tr = text_tr * fix_3mf->inverse(); + + // // when it is new applying of use surface than move origin onto surfaca + // if (!m_volume->text_configuration->style.prop.use_surface) { + // auto offset = priv::calc_surface_offset(*m_volume, m_raycast_manager, m_parent.get_selection()); + // if (offset.has_value()) + // text_tr *= Eigen::Translation(*offset); + // } + + // bool is_outside = m_volume->is_model_part(); + // // check that there is not unexpected volume type + // assert(is_outside || m_volume->is_negative_volume() || + // m_volume->is_modifier()); + // UpdateSurfaceVolumeData surface_data{std::move(data), {text_tr, is_outside, std::move(sources)}}; + // job = std::make_unique(std::move(surface_data)); + //} else { + // job = std::make_unique(std::move(data)); + //} + + //auto &worker = wxGetApp().plater()->get_ui_job_worker(); + //queue_job(worker, std::move(job)); + + //// notification is removed befor object is changed by job + //remove_notification_not_valid_font(); + return true; +} + +void GLGizmoSVG::close() +{ + // close gizmo == open it again + auto& mng = m_parent.get_gizmos_manager(); + if (mng.get_current_type() == GLGizmosManager::Emboss) + mng.open_gizmo(GLGizmosManager::Emboss); +} + +void GLGizmoSVG::draw_window() +{ + ImGui::Text("Preview of svg image."); + + if (ImGui::Button("choose svg file")) choose_svg_file(); + } + +void GLGizmoSVG::draw_model_type() +{ + bool is_last_solid_part = is_svg_object(m_volume); + std::string title = _u8L("SVG is to object"); + if (is_last_solid_part) { + ImVec4 color{.5f, .5f, .5f, 1.f}; + m_imgui->text_colored(color, title.c_str()); + } else { + ImGui::Text("%s", title.c_str()); + } + + std::optional new_type; + ModelVolumeType modifier = ModelVolumeType::PARAMETER_MODIFIER; + ModelVolumeType negative = ModelVolumeType::NEGATIVE_VOLUME; + ModelVolumeType part = ModelVolumeType::MODEL_PART; + ModelVolumeType type = m_volume->type(); + + if (ImGui::RadioButton(_u8L("Added").c_str(), type == part)) + new_type = part; + else if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", _u8L("Click to change text into object part.").c_str()); + ImGui::SameLine(); + + std::string last_solid_part_hint = _u8L("You can't change a type of the last solid part of the object."); + if (ImGui::RadioButton(_u8L("Subtracted").c_str(), type == negative)) + new_type = negative; + else if (ImGui::IsItemHovered()) { + if (is_last_solid_part) + ImGui::SetTooltip("%s", last_solid_part_hint.c_str()); + else if (type != negative) + ImGui::SetTooltip("%s", _u8L("Click to change part type into negative volume.").c_str()); + } + + // In simple mode are not modifiers + if (wxGetApp().plater()->printer_technology() != ptSLA && wxGetApp().get_mode() != ConfigOptionMode::comSimple) { + ImGui::SameLine(); + if (ImGui::RadioButton(_u8L("Modifier").c_str(), type == modifier)) + new_type = modifier; + else if (ImGui::IsItemHovered()) { + if (is_last_solid_part) + ImGui::SetTooltip("%s", last_solid_part_hint.c_str()); + else if (type != modifier) + ImGui::SetTooltip("%s", _u8L("Click to change part type into modifier.").c_str()); + } + } + + if (m_volume != nullptr && new_type.has_value() && !is_last_solid_part) { + GUI_App &app = wxGetApp(); + Plater * plater = app.plater(); + Plater::TakeSnapshot snapshot(plater, _L("Change Text Type"), UndoRedo::SnapshotType::GizmoAction); + m_volume->set_type(*new_type); + + // Update volume position when switch from part or into part + if (m_volume->text_configuration->style.prop.use_surface) { + // move inside + bool is_volume_move_inside = (type == part); + bool is_volume_move_outside = (*new_type == part); + if (is_volume_move_inside || is_volume_move_outside) process(); + } + + // inspiration in ObjectList::change_part_type() + // how to view correct side panel with objects + ObjectList *obj_list = app.obj_list(); + wxDataViewItemArray sel = obj_list->reorder_volumes_and_get_selection( + obj_list->get_selected_obj_idx(), + [volume = m_volume](const ModelVolume *vol) { return vol == volume; }); + if (!sel.IsEmpty()) obj_list->select_item(sel.front()); + + // NOTE: on linux, function reorder_volumes_and_get_selection call GLCanvas3D::reload_scene(refresh_immediately = false) + // which discard m_volume pointer and set it to nullptr also selection is cleared so gizmo is automaticaly closed + auto &mng = m_parent.get_gizmos_manager(); + if (mng.get_current_type() != GLGizmosManager::Emboss) + mng.open_gizmo(GLGizmosManager::Emboss); + // TODO: select volume back - Ask @Sasa + } +} +namespace priv { + +std::string get_file_name(const std::string &file_path) +{ + size_t pos_last_delimiter = file_path.find_last_of("/\\"); + size_t pos_point = file_path.find_last_of('.'); + size_t offset = pos_last_delimiter + 1; + size_t count = pos_point - pos_last_delimiter - 1; + return file_path.substr(offset, count); +} + +} + +bool GLGizmoSVG::choose_svg_file() +{ + wxArrayString input_files; + wxString fontDir = wxEmptyString; + wxString selectedFile = wxEmptyString; + wxFileDialog dialog(nullptr, _L("Choose SVG file:"), fontDir, + selectedFile, file_wildcards(FT_SVG), + wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (dialog.ShowModal() == wxID_OK) dialog.GetPaths(input_files); + if (input_files.IsEmpty()) return false; + if (input_files.size() != 1) return false; + auto & input_file = input_files.front(); + std::string path = std::string(input_file.c_str()); + std::string name = priv::get_file_name(path); + + NSVGimage *image = nsvgParseFromFile(path.c_str(), "mm", 96.0f); + ExPolygons polys = NSVGUtils::to_ExPolygons(image); + nsvgDelete(image); + + BoundingBox bb; + for (const auto &p : polys) bb.merge(p.contour.points); + + double scale = 1e-4; + auto project = std::make_unique( + std::make_unique(10 / scale), scale); + indexed_triangle_set its = polygons2model(polys, *project); + return false; + // test store: + // for (auto &poly : polys) poly.scale(1e5); + // SVG svg("converted.svg", BoundingBox(polys.front().contour.points)); + // svg.draw(polys); + //return add_volume(name, its); +} + +// any existing icon filename to not influence GUI +const std::string GLGizmoSVG::M_ICON_FILENAME = "cut.svg"; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp new file mode 100644 index 0000000000..96519daa5e --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp @@ -0,0 +1,172 @@ +#ifndef slic3r_GLGizmoSVG_hpp_ +#define slic3r_GLGizmoSVG_hpp_ + +// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, +// which overrides our localization "L" macro. +#include "GLGizmoBase.hpp" +#include "GLGizmoRotate.hpp" +#include "slic3r/GUI/SurfaceDrag.hpp" +#include "slic3r/Utils/RaycastManager.hpp" + +#include +#include +#include + +#include "libslic3r/Emboss.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Model.hpp" + +#include +#include + +namespace Slic3r{ + //class AppConfig; + class GLVolume; + class ModelVolume; + enum class ModelVolumeType : int; +} + +namespace Slic3r::GUI { +class GLGizmoSVG : public GLGizmoBase +{ +public: + GLGizmoSVG(GLCanvas3D &parent); + + /// + /// Create new embossed text volume by type on position of mouse + /// + /// Object part / Negative volume / Modifier + /// Define position of new volume + void create_volume(const std::string& svg_file_path, ModelVolumeType volume_type, const Vec2d &mouse_pos); + + /// + /// Create new text without given position + /// + /// Object part / Negative volume / Modifier + void create_volume(const std::string &svg_file_path, ModelVolumeType volume_type); + + /// + /// Check whether volume is object containing only emboss volume + /// + /// Pointer to volume + /// True when object otherwise False + static bool is_svg_object(const ModelVolume *volume); + + /// + /// Check whether volume has emboss data + /// + /// Pointer to volume + /// True when constain emboss data otherwise False + static bool is_svg(const ModelVolume *volume); + +protected: + bool on_init() override; + std::string on_get_name() const override; + void on_render() override; + virtual void on_register_raycasters_for_picking() override; + virtual void on_unregister_raycasters_for_picking() override; + void on_render_input_window(float x, float y, float bottom_limit) override; + bool on_is_activable() const override { return true; } + bool on_is_selectable() const override { return false; } + void on_set_state() override; + void data_changed() override; // selection changed + void on_set_hover_id() override{ m_rotate_gizmo.set_hover_id(m_hover_id); } + void on_enable_grabber(unsigned int id) override { m_rotate_gizmo.enable_grabber(); } + void on_disable_grabber(unsigned int id) override { m_rotate_gizmo.disable_grabber(); } + void on_start_dragging() override; + void on_stop_dragging() override; + void on_dragging(const UpdateData &data) override; + + /// + /// Rotate by text on dragging rotate grabers + /// + /// Information about mouse + /// Propagete normaly return false. + bool on_mouse(const wxMouseEvent &mouse_event) override; + + bool wants_enter_leave_snapshots() const override { return true; } + std::string get_gizmo_entering_text() const override { return _u8L("Enter SVG gizmo"); } + std::string get_gizmo_leaving_text() const override { return _u8L("Leave SVG gizmo"); } + std::string get_action_snapshot_name() override { return _u8L("SVG actions"); } +private: + void set_volume_by_selection(); + void reset_volume(); + + // create volume from text - main functionality + bool process(); + void close(); + void draw_window(); + void draw_model_type(); + + // process mouse event + bool on_mouse_for_rotation(const wxMouseEvent &mouse_event); + bool on_mouse_for_translate(const wxMouseEvent &mouse_event); + + bool choose_svg_file(); + + // 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 + { + // Detect invalid config values when change monitor DPI + double screen_scale; + float main_toolbar_height; + + // Zero means it is calculated in init function + ImVec2 minimal_window_size = ImVec2(0, 0); + + float input_width = 0.f; + float input_offset = 0.f; + + // Only translations needed for calc GUI size + struct Translations + { + std::string font; + }; + Translations translations; + }; + std::optional m_gui_cfg; + static GuiCfg create_gui_configuration(); + + // actual selected only one volume - with emboss data + ModelVolume *m_volume; + + // When work with undo redo stack there could be situation that + // m_volume point to unexisting volume so One need also objectID + ObjectID m_volume_id; + + // cancel for previous update of volume to cancel finalize part + std::shared_ptr> m_job_cancel; + + // Rotation gizmo + GLGizmoRotate m_rotate_gizmo; + std::optional m_angle; + // Value is set only when dragging rotation to calculate actual angle + std::optional m_rotate_start_angle; + + // TODO: it should be accessible by other gizmo too. + // May be move to plater? + RaycastManager m_raycast_manager; + + // When true keep up vector otherwise relative rotation + bool m_keep_up = true; + + // setted only when wanted to use - not all the time + std::optional m_set_window_offset; + + // Keep data about dragging only during drag&drop + std::optional m_surface_drag; + + // For volume on scaled objects + std::optional m_scale_height; + std::optional m_scale_depth; + void calculate_scale(); + + // only temporary solution + static const std::string M_ICON_FILENAME; +}; + +} // namespace Slic3r::GUI + +#endif // slic3r_GLGizmoSVG_hpp_ diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 55bf576028..e22c45940e 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -1369,6 +1369,48 @@ bool ImGuiWrapper::slider_optional_int(const char *label, } else return false; } +std::optional ImGuiWrapper::change_window_position(const char *window_name, bool try_to_fix) { + ImGuiWindow *window = ImGui::FindWindowByName(window_name); + // is window just created + if (window == NULL) + return {}; + + // position of window on screen + ImVec2 position = window->Pos; + ImVec2 size = window->SizeFull; + + // screen size + ImVec2 screen = ImGui::GetMainViewport()->Size; + + std::optional output_window_offset; + if (position.x < 0) { + if (position.y < 0) + // top left + output_window_offset = ImVec2(0, 0); + else + // only left + output_window_offset = ImVec2(0, position.y); + } else if (position.y < 0) { + // only top + output_window_offset = ImVec2(position.x, 0); + } else if (screen.x < (position.x + size.x)) { + if (screen.y < (position.y + size.y)) + // right bottom + output_window_offset = ImVec2(screen.x - size.x, screen.y - size.y); + else + // only right + output_window_offset = ImVec2(screen.x - size.x, position.y); + } else if (screen.y < (position.y + size.y)) { + // only bottom + output_window_offset = ImVec2(position.x, screen.y - size.y); + } + + if (!try_to_fix && output_window_offset.has_value()) + output_window_offset = ImVec2(-1, -1); // Put on default position + + return output_window_offset; +} + void ImGuiWrapper::left_inputs() { ImGui::ClearActiveID(); } diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index 077bf568de..8ece7b3ea0 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -146,6 +146,15 @@ public: // Extended function ImGuiWrapper::slider_float to work with std::optional, when value == def_val than optional release its value bool slider_optional_int(const char* label, std::optional &v, int v_min, int v_max, const char* format = "%.3f", float power = 1.0f, bool clamp = true, const wxString& tooltip = {}, bool show_edit_btn = true, int def_val = 0); + /// + /// Change position of imgui window + /// + /// ImGui identifier of window + /// [output] optional + /// When True Only move to be full visible otherwise reset position + /// New offset of window for function ImGui::SetNextWindowPos + static std::optional change_window_position(const char *window_name, bool try_to_fix); + /// /// Use ImGui internals to unactivate (lose focus) in input. /// When input is activ it can't change value by application. diff --git a/src/slic3r/GUI/SurfaceDrag.cpp b/src/slic3r/GUI/SurfaceDrag.cpp index 0f2f1706ae..8e07adbea9 100644 --- a/src/slic3r/GUI/SurfaceDrag.cpp +++ b/src/slic3r/GUI/SurfaceDrag.cpp @@ -337,4 +337,83 @@ Transform3d world_matrix_fixed(const Selection &selection) return world_matrix_fixed(*gl_volume, selection.get_model()->objects); } +bool face_selected_volume_to_camera(const Camera &camera, GLCanvas3D &canvas) +{ + const Vec3d &cam_dir = camera.get_dir_forward(); + Selection &sel = canvas.get_selection(); + if (sel.is_empty()) + return false; + + // camera direction transformed into volume coordinate system + Transform3d to_world = world_matrix_fixed(sel); + Vec3d cam_dir_tr = to_world.inverse().linear() * cam_dir; + cam_dir_tr.normalize(); + + Vec3d emboss_dir(0., 0., -1.); + + // check wether cam_dir is already used + if (is_approx(cam_dir_tr, emboss_dir)) + return false; + + assert(sel.get_volume_idxs().size() == 1); + GLVolume *gl_volume = sel.get_volume(*sel.get_volume_idxs().begin()); + + Transform3d vol_rot; + Transform3d vol_tr = gl_volume->get_volume_transformation().get_matrix(); + // check whether cam_dir is opposit to emboss dir + if (is_approx(cam_dir_tr, -emboss_dir)) { + // rotate 180 DEG by y + vol_rot = Eigen::AngleAxis(M_PI_2, Vec3d(0., 1., 0.)); + } else { + // calc params for rotation + Vec3d axe = emboss_dir.cross(cam_dir_tr); + axe.normalize(); + double angle = std::acos(emboss_dir.dot(cam_dir_tr)); + vol_rot = Eigen::AngleAxis(angle, axe); + } + + Vec3d offset = vol_tr * Vec3d::Zero(); + Vec3d offset_inv = vol_rot.inverse() * offset; + Transform3d res = vol_tr * Eigen::Translation(-offset) * vol_rot * Eigen::Translation(offset_inv); + // Transform3d res = vol_tr * vol_rot; + gl_volume->set_volume_transformation(Geometry::Transformation(res)); + get_model_volume(*gl_volume, sel.get_model()->objects)->set_transformation(res); + return true; +} + +void do_local_z_rotate(GLCanvas3D &canvas, double relative_angle) +{ + Selection &selection = canvas.get_selection(); + + assert(!selection.is_empty()); + if(selection.is_empty()) return; + + selection.setup_cache(); + TransformationType transformation_type = TransformationType::Local_Relative_Joint; + selection.rotate(Vec3d(0., 0., relative_angle), transformation_type); + + std::string snapshot_name; // empty meand no store undo / redo + // NOTE: it use L instead of _L macro because prefix _ is appended + // inside function do_move + // snapshot_name = L("Set text rotation"); + canvas.do_rotate(snapshot_name); +} + +void do_local_z_move(GLCanvas3D &canvas, double relative_move) { + + Selection &selection = canvas.get_selection(); + assert(!selection.is_empty()); + if (selection.is_empty()) return; + + selection.setup_cache(); + Vec3d translate = Vec3d::UnitZ() * relative_move; + selection.translate(translate, TransformationType::Local); + + std::string snapshot_name; // empty mean no store undo / redo + // NOTE: it use L instead of _L macro because prefix _ is appended inside + // function do_move + // snapshot_name = L("Set surface distance"); + canvas.do_move(snapshot_name); +} + } // namespace Slic3r::GUI \ No newline at end of file diff --git a/src/slic3r/GUI/SurfaceDrag.hpp b/src/slic3r/GUI/SurfaceDrag.hpp index bb2600c28f..19fc1c795f 100644 --- a/src/slic3r/GUI/SurfaceDrag.hpp +++ b/src/slic3r/GUI/SurfaceDrag.hpp @@ -41,6 +41,10 @@ struct SurfaceDrag bool exist_hit = true; }; +// Limit direction of up vector on model +// Between side and top surface +constexpr double up_limit = 0.9; + /// /// Mouse event handler, when move(drag&drop) volume over model surface /// NOTE: Dragged volume has to be selected. And also has to be hovered on start of dragging. @@ -86,5 +90,27 @@ Transform3d world_matrix_fixed(const GLVolume &gl_volume, const ModelObjectPtrs& /// Fixed Transformation of selected volume in selection Transform3d world_matrix_fixed(const Selection &selection); +/// +/// Apply camera direction for emboss direction +/// +/// Define view vector +/// Containe Selected ModelVolume to modify orientation +/// True when apply change otherwise false +bool face_selected_volume_to_camera(const Camera &camera, GLCanvas3D &canvas); + +/// +/// Rotation around z Axis(emboss direction) +/// +/// Selected volume for rotation +/// Relative angle to rotate around emboss direction +void do_local_z_rotate(GLCanvas3D &canvas, double relative_angle); + +/// +/// Translation along local z Axis (emboss direction) +/// +/// Selected volume for translate +/// Relative move along emboss direction +void do_local_z_move(GLCanvas3D &canvas, double relative_move); + } // namespace Slic3r::GUI #endif // slic3r_SurfaceDrag_hpp_ \ No newline at end of file