mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-07-13 07:41:50 +08:00
Add SVG gizmo - buildable
This commit is contained in:
parent
ae0f4a7c8b
commit
a5ea46a83a
@ -306,6 +306,12 @@ template<typename T> T angle_to_0_2PI(T angle)
|
||||
|
||||
return angle;
|
||||
}
|
||||
template<typename T> void to_range_pi_pi(T &angle){
|
||||
if (angle > T(PI) || angle < -T(PI)) {
|
||||
int count = static_cast<int>(std::round(angle / (2 * PI)));
|
||||
angle -= static_cast<T>(count * 2 * PI);
|
||||
}
|
||||
}
|
||||
|
||||
void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval);
|
||||
|
||||
|
@ -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<typename T> struct MinMax { T min; T max;};
|
||||
template<typename T>
|
||||
static bool apply(std::optional<T> &val, const MinMax<T> &limit) {
|
||||
if (!val.has_value()) return false;
|
||||
return apply<T>(*val, limit);
|
||||
}
|
||||
template<typename T>
|
||||
static bool apply(T &val, const MinMax<T> &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
|
||||
|
@ -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
|
||||
|
@ -67,7 +67,6 @@ using namespace Slic3r::GUI;
|
||||
using namespace Slic3r::GUI::Emboss;
|
||||
|
||||
namespace priv {
|
||||
template<typename T> struct MinMax { T min; T max;};
|
||||
template<typename T> struct Limit {
|
||||
// Limitation for view slider range in GUI
|
||||
MinMax<T> gui;
|
||||
@ -86,40 +85,8 @@ static const struct Limits
|
||||
MinMax<int> line_gap{-20000, 20000}; // in font points
|
||||
// distance text object from surface
|
||||
MinMax<float> angle{-180.f, 180.f}; // in degrees
|
||||
|
||||
template<typename T>
|
||||
static bool apply(std::optional<T> &val, const MinMax<T> &limit) {
|
||||
if (val.has_value())
|
||||
return apply<T>(*val, limit);
|
||||
return false;
|
||||
}
|
||||
template<typename T>
|
||||
static bool apply(T &val, const MinMax<T> &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<typename T> void to_range_pi_pi(T& angle)
|
||||
{
|
||||
if (angle > PI || angle < -PI) {
|
||||
int count = static_cast<int>(std::round(angle / (2 * PI)));
|
||||
angle -= static_cast<T>(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);
|
||||
|
||||
/// <summary>
|
||||
/// Apply camera direction for emboss direction
|
||||
/// </summary>
|
||||
/// <param name="camera">Define view vector</param>
|
||||
/// <param name="canvas">Containe Selected Model to modify</param>
|
||||
/// <returns>True when apply change otherwise false</returns>
|
||||
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<double> 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 {
|
||||
/// </summary>
|
||||
static ImVec2 calc_fine_position(const Selection &selection, const ImVec2 &windows_size, const Size &canvas_size);
|
||||
|
||||
/// <summary>
|
||||
/// Change position of emboss window
|
||||
/// </summary>
|
||||
/// <param name="output_window_offset"></param>
|
||||
/// <param name="try_to_fix">When True Only move to be full visible otherwise reset position</param>
|
||||
static void change_window_position(std::optional<ImVec2> &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<float>(-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<ImVec2>& 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<double, 3>(-offset) *
|
||||
vol_rot *
|
||||
Eigen::Translation<double, 3>(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";
|
||||
|
@ -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<float>& scale);
|
||||
|
613
src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp
Normal file
613
src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp
Normal file
@ -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 <wx/display.h> // detection of change DPI
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <GL/glew.h>
|
||||
#include <chrono> // 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<float> emboss{0.01f, 1e4f}; // in mm
|
||||
// distance text object from surface
|
||||
MinMax<float> 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<float>(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<double> 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<float>& 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> 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<double, 3>(*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<UpdateSurfaceVolumeJob>(std::move(surface_data));
|
||||
//} else {
|
||||
// job = std::make_unique<UpdateJob>(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<ModelVolumeType> 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<ProjectScale>(
|
||||
std::make_unique<ProjectZ>(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";
|
172
src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp
Normal file
172
src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp
Normal file
@ -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 <optional>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
|
||||
#include "libslic3r/Emboss.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
#include <GL/glew.h>
|
||||
|
||||
namespace Slic3r{
|
||||
//class AppConfig;
|
||||
class GLVolume;
|
||||
class ModelVolume;
|
||||
enum class ModelVolumeType : int;
|
||||
}
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
class GLGizmoSVG : public GLGizmoBase
|
||||
{
|
||||
public:
|
||||
GLGizmoSVG(GLCanvas3D &parent);
|
||||
|
||||
/// <summary>
|
||||
/// Create new embossed text volume by type on position of mouse
|
||||
/// </summary>
|
||||
/// <param name="volume_type">Object part / Negative volume / Modifier</param>
|
||||
/// <param name="mouse_pos">Define position of new volume</param>
|
||||
void create_volume(const std::string& svg_file_path, ModelVolumeType volume_type, const Vec2d &mouse_pos);
|
||||
|
||||
/// <summary>
|
||||
/// Create new text without given position
|
||||
/// </summary>
|
||||
/// <param name="volume_type">Object part / Negative volume / Modifier</param>
|
||||
void create_volume(const std::string &svg_file_path, ModelVolumeType volume_type);
|
||||
|
||||
/// <summary>
|
||||
/// Check whether volume is object containing only emboss volume
|
||||
/// </summary>
|
||||
/// <param name="volume">Pointer to volume</param>
|
||||
/// <returns>True when object otherwise False</returns>
|
||||
static bool is_svg_object(const ModelVolume *volume);
|
||||
|
||||
/// <summary>
|
||||
/// Check whether volume has emboss data
|
||||
/// </summary>
|
||||
/// <param name="volume">Pointer to volume</param>
|
||||
/// <returns>True when constain emboss data otherwise False</returns>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Rotate by text on dragging rotate grabers
|
||||
/// </summary>
|
||||
/// <param name="mouse_event">Information about mouse</param>
|
||||
/// <returns>Propagete normaly return false.</returns>
|
||||
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<const GuiCfg> 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<std::atomic<bool>> m_job_cancel;
|
||||
|
||||
// Rotation gizmo
|
||||
GLGizmoRotate m_rotate_gizmo;
|
||||
std::optional<float> m_angle;
|
||||
// Value is set only when dragging rotation to calculate actual angle
|
||||
std::optional<float> 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<ImVec2> m_set_window_offset;
|
||||
|
||||
// Keep data about dragging only during drag&drop
|
||||
std::optional<SurfaceDrag> m_surface_drag;
|
||||
|
||||
// For volume on scaled objects
|
||||
std::optional<float> m_scale_height;
|
||||
std::optional<float> m_scale_depth;
|
||||
void calculate_scale();
|
||||
|
||||
// only temporary solution
|
||||
static const std::string M_ICON_FILENAME;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
|
||||
#endif // slic3r_GLGizmoSVG_hpp_
|
@ -1369,6 +1369,48 @@ bool ImGuiWrapper::slider_optional_int(const char *label,
|
||||
} else return false;
|
||||
}
|
||||
|
||||
std::optional<ImVec2> 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<ImVec2> 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();
|
||||
}
|
||||
|
@ -146,6 +146,15 @@ public:
|
||||
// Extended function ImGuiWrapper::slider_float to work with std::optional<int>, when value == def_val than optional release its value
|
||||
bool slider_optional_int(const char* label, std::optional<int> &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);
|
||||
|
||||
/// <summary>
|
||||
/// Change position of imgui window
|
||||
/// </summary>
|
||||
/// <param name="window_name">ImGui identifier of window</param>
|
||||
/// <param name="output_window_offset">[output] optional </param>
|
||||
/// <param name="try_to_fix">When True Only move to be full visible otherwise reset position</param>
|
||||
/// <returns>New offset of window for function ImGui::SetNextWindowPos</returns>
|
||||
static std::optional<ImVec2> change_window_position(const char *window_name, bool try_to_fix);
|
||||
|
||||
/// <summary>
|
||||
/// Use ImGui internals to unactivate (lose focus) in input.
|
||||
/// When input is activ it can't change value by application.
|
||||
|
@ -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<double, 3>(-offset) * vol_rot * Eigen::Translation<double, 3>(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
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 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&
|
||||
/// <returns>Fixed Transformation of selected volume in selection</returns>
|
||||
Transform3d world_matrix_fixed(const Selection &selection);
|
||||
|
||||
/// <summary>
|
||||
/// Apply camera direction for emboss direction
|
||||
/// </summary>
|
||||
/// <param name="camera">Define view vector</param>
|
||||
/// <param name="canvas">Containe Selected ModelVolume to modify orientation</param>
|
||||
/// <returns>True when apply change otherwise false</returns>
|
||||
bool face_selected_volume_to_camera(const Camera &camera, GLCanvas3D &canvas);
|
||||
|
||||
/// <summary>
|
||||
/// Rotation around z Axis(emboss direction)
|
||||
/// </summary>
|
||||
/// <param name="canvas">Selected volume for rotation</param>
|
||||
/// <param name="relative_angle">Relative angle to rotate around emboss direction</param>
|
||||
void do_local_z_rotate(GLCanvas3D &canvas, double relative_angle);
|
||||
|
||||
/// <summary>
|
||||
/// Translation along local z Axis (emboss direction)
|
||||
/// </summary>
|
||||
/// <param name="canvas">Selected volume for translate</param>
|
||||
/// <param name="relative_move">Relative move along emboss direction</param>
|
||||
void do_local_z_move(GLCanvas3D &canvas, double relative_move);
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
#endif // slic3r_SurfaceDrag_hpp_
|
Loading…
x
Reference in New Issue
Block a user