diff --git a/src/libslic3r/NSVGUtils.cpp b/src/libslic3r/NSVGUtils.cpp index e56333d0a3..53dfda3cb2 100644 --- a/src/libslic3r/NSVGUtils.cpp +++ b/src/libslic3r/NSVGUtils.cpp @@ -1,40 +1,31 @@ #include "NSVGUtils.hpp" #include "ClipperUtils.hpp" + +namespace { +using namespace Slic3r; // Polygon + Vec2f +/// +/// Convert cubic curve to lines +/// Inspired by nanosvgrast.h function nsvgRasterize->nsvg__flattenShape +/// +/// Result points +/// Tesselation tolerance +/// Curve point +/// Curve point +/// Curve point +/// Curve point +/// Actual depth of recursion +/// Scale of point - multiplicator +/// NOTE: increase preccission by number greater than 1. +void flatten_cubic_bez(Polygon &polygon, float tessTol, const Vec2f& p1, const Vec2f& p2, const Vec2f& p3, const Vec2f& p4, int level, float scale); + +Point::coord_type to_coor(float val, float scale) { return static_cast(std::round(val * scale)); } + +} // namespace + namespace Slic3r { -// inspired by nanosvgrast.h function nsvgRasterize -> nsvg__flattenShape -> nsvg__flattenCubicBez -// https://github.com/memononen/nanosvg/blob/f0a3e1034dd22e2e87e5db22401e44998383124e/src/nanosvgrast.h#L335 -void flatten_cubic_bez(Polygon &polygon, float tessTol, Vec2f p1, Vec2f p2, Vec2f p3, Vec2f p4, int level) +Polygons to_polygons(NSVGimage *image, float tessTol, int max_level, float scale, bool is_y_negative) { - Vec2f p12 = (p1 + p2) * 0.5f; - Vec2f p23 = (p2 + p3) * 0.5f; - Vec2f p34 = (p3 + p4) * 0.5f; - Vec2f p123 = (p12 + p23) * 0.5f; - - Vec2f pd = p4 - p1; - Vec2f pd2 = p2 - p4; - float d2 = std::abs(pd2.x() * pd.y() - pd2.y() * pd.x()); - Vec2f pd3 = p3 - p4; - float d3 = std::abs(pd3.x() * pd.y() - pd3.y() * pd.x()); - float d23 = d2 + d3; - - if ((d23 * d23) < tessTol * (pd.x() * pd.x() + pd.y() * pd.y())) { - polygon.points.emplace_back(p4.x(), p4.y()); - return; - } - - --level; - if (level == 0) - return; - Vec2f p234 = (p23 + p34) * 0.5f; - Vec2f p1234 = (p123 + p234) * 0.5f; - flatten_cubic_bez(polygon, tessTol, p1, p12, p123, p1234, level); - flatten_cubic_bez(polygon, tessTol, p1234, p234, p34, p4, level); -} - -Polygons to_polygons(NSVGimage *image, float tessTol, int max_level, bool is_y_negative) -{ - const float y_sign = is_y_negative ? -1.f : 1.f; - Polygons polygons; + Polygons polygons; for (NSVGshape *shape = image->shapes; shape != NULL; shape = shape->next) { if (!(shape->flags & NSVG_FLAGS_VISIBLE)) continue; @@ -44,27 +35,77 @@ Polygons to_polygons(NSVGimage *image, float tessTol, int max_level, bool is_y_n Polygon polygon; for (NSVGpath *path = shape->paths; path != NULL; path = path->next) { // Flatten path - polygon.points.emplace_back(path->pts[0], y_sign * path->pts[1]); + Point::coord_type x = to_coor(path->pts[0], scale); + Point::coord_type y = to_coor(path->pts[1], scale); + polygon.points.emplace_back(x, y); size_t path_size = (path->npts > 1) ? static_cast(path->npts - 1) : 0; for (size_t i = 0; i < path_size; i += 3) { const float *p = &path->pts[i * 2]; - Vec2f p1(p[0], p[1]), p2(p[2], p[3]), p3(p[4], p[5]), p4(p[6], p[7]); - flatten_cubic_bez(polygon, tessTol, p1, p2, p3, p4, max_level); + Vec2f p1(p[0], p[1]); + Vec2f p2(p[2], p[3]); + Vec2f p3(p[4], p[5]); + Vec2f p4(p[6], p[7]); + flatten_cubic_bez(polygon, tessTol, p1, p2, p3, p4, max_level, scale); } if (path->closed && !polygon.empty()) { polygons.push_back(polygon); polygon = Polygon(); } } - if (!polygon.empty()) + if (!polygon.empty()) polygons.push_back(polygon); } + + if (is_y_negative) + for (Polygon &polygon : polygons) + for (Point &p : polygon.points) + p.y() = -p.y(); + return polygons; } -ExPolygons to_expolygons(NSVGimage *image, float tessTol, int max_level, bool is_y_negative) -{ - return union_ex(to_polygons(image, tessTol, max_level, is_y_negative)); +ExPolygons to_expolygons(NSVGimage *image, float tessTol, int max_level, float scale, bool is_y_negative){ + return union_ex(to_polygons(image, tessTol, max_level, scale, is_y_negative)); } -} // namespace Slic3r \ No newline at end of file +} // namespace Slic3r + +namespace { +// inspired by nanosvgrast.h function nsvgRasterize -> nsvg__flattenShape -> nsvg__flattenCubicBez +// https://github.com/memononen/nanosvg/blob/f0a3e1034dd22e2e87e5db22401e44998383124e/src/nanosvgrast.h#L335 +void flatten_cubic_bez(Polygon &polygon, float tessTol, const Vec2f& p1, const Vec2f& p2, const Vec2f& p3, const Vec2f& p4, int level, float scale) +{ + // f .. first + // s .. second + auto det = [](const Vec2f &f, const Vec2f &s) { + return std::fabs(f.x() * s.y() - f.y() * s.x()); + }; + + Vec2f pd = p4 - p1; + Vec2f pd2 = p2 - p4; + float d2 = det(pd2, pd); + Vec2f pd3 = p3 - p4; + float d3 = det(pd3, pd); + float d23 = d2 + d3; + + if ((d23 * d23) < tessTol * pd.squaredNorm()) { + Point::coord_type x = to_coor(p4.x(), scale); + Point::coord_type y = to_coor(p4.y(), scale); + polygon.points.emplace_back(x, y); + return; + } + + --level; + if (level == 0) + return; + + Vec2f p12 = (p1 + p2) * 0.5f; + Vec2f p23 = (p2 + p3) * 0.5f; + Vec2f p34 = (p3 + p4) * 0.5f; + Vec2f p123 = (p12 + p23) * 0.5f; + Vec2f p234 = (p23 + p34) * 0.5f; + Vec2f p1234 = (p123 + p234) * 0.5f; + flatten_cubic_bez(polygon, tessTol, p1, p12, p123, p1234, level, scale); + flatten_cubic_bez(polygon, tessTol, p1234, p234, p34, p4, level, scale); +} +} // namespace \ No newline at end of file diff --git a/src/libslic3r/NSVGUtils.hpp b/src/libslic3r/NSVGUtils.hpp index 83a83cd8e4..d379e90908 100644 --- a/src/libslic3r/NSVGUtils.hpp +++ b/src/libslic3r/NSVGUtils.hpp @@ -6,31 +6,21 @@ #include "nanosvg/nanosvg.h" // load SVG file // Helper function to work with nano svg -namespace Slic3r { - -/// -/// Convert cubic curve to lines -/// Inspired by nanosvgrast.h function nsvgRasterize->nsvg__flattenShape -/// -/// Result points -/// Tesselation tolerance -/// Curve point -/// Curve point -/// Curve point -/// Curve point -/// -void flatten_cubic_bez(Polygon &polygon, float tessTol, Vec2f p1, Vec2f p2, Vec2f p3, Vec2f p4, int level); +namespace Slic3r { /// /// Convert .svg opened by nanoSvg to Polygons /// /// Opened file -/// Tesselation tolerance +/// Tesselation tolerance +/// NOTE: Value is in image scale /// Maximal depth +/// Multiplicator of point coors +/// NOTE: Every point coor from image(float) is multiplied by scale and rounded to integer /// Flag is y negative, when true than y coor is multiplied by -1 /// Polygons extracted from svg -Polygons to_polygons(NSVGimage *image, float tessTol = 10., int max_level = 10, bool is_y_negative = true); -ExPolygons to_expolygons(NSVGimage *image, float tessTol = 10., int max_level = 10, bool is_y_negative = true); +Polygons to_polygons(NSVGimage *image, float tessTol = 10., int max_level = 10, float scale = 1.f, bool is_y_negative = true); +ExPolygons to_expolygons(NSVGimage *image, float tessTol = 10., int max_level = 10, float scale = 1.f, bool is_y_negative = true); } // namespace Slic3r #endif // slic3r_NSVGUtils_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp index 55bbc18a31..46744d03b3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp @@ -1,4 +1,4 @@ -#include "GLGizmoSVG.hpp" +#include "GLGizmoSVG.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" @@ -555,14 +555,110 @@ void GLGizmoSVG::draw_window() { if (m_volume != nullptr && m_volume->emboss_shape.has_value()) ImGui::Text("SVG file path is %s", m_volume->emboss_shape->svg_file_path.c_str()); + //draw_use_surface(); + draw_distance(); + draw_rotation(); if (ImGui::Button("change file")) { auto data = create_emboss_data_base(m_job_cancel); std::string file = choose_svg_file(); } + + ImGui::Separator(); draw_model_type(); } +void GLGizmoSVG::draw_distance() +{ + if (m_volume == nullptr) + return; + + const EmbossProjection& projection = m_volume->emboss_shape->projection; + bool use_surface = projection.use_surface; + bool allowe_surface_distance = !use_surface && !m_volume->is_the_only_one_part(); + + float prev_distance = m_distance.value_or(.0f); + float min_distance = static_cast(-2 * projection.depth); + float max_distance = static_cast(2 * projection.depth); + + m_imgui->disabled_begin(!allowe_surface_distance); + ScopeGuard sg([imgui = m_imgui]() { imgui->disabled_end(); }); + + ImGuiWrapper::text(_L("distance")); + bool use_inch = wxGetApp().app_config->get_bool("use_inches"); + const wxString move_tooltip = _L("Distance of the center of the text to the model surface."); + bool is_moved = false; + if (use_inch) { + std::optional distance_inch; + if (m_distance.has_value()) distance_inch = (*m_distance * ObjectManipulation::mm_to_in); + min_distance = static_cast(min_distance * ObjectManipulation::mm_to_in); + max_distance = static_cast(max_distance * ObjectManipulation::mm_to_in); + if (m_imgui->slider_optional_float("##distance", m_distance, min_distance, max_distance, "%.3f in", 1.f, false, move_tooltip)) { + if (distance_inch.has_value()) { + m_distance = *distance_inch * ObjectManipulation::in_to_mm; + } else { + m_distance.reset(); + } + is_moved = true; + } + } else { + if (m_imgui->slider_optional_float("##distance", m_distance, min_distance, max_distance, "%.2f mm", 1.f, false, move_tooltip)) + is_moved = true; + } + + float undo_offset = ImGui::GetStyle().FramePadding.x; + ImGui::SameLine(undo_offset); + if (ImGui::Button("R##distance_reset")){ + m_distance.reset(); + is_moved = true; + } else if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", _u8L("Reset distance to zero value")); + + if (is_moved) + do_local_z_move(m_parent, m_distance.value_or(.0f) - prev_distance); +} + +void GLGizmoSVG::draw_rotation() +{ + if (m_volume == nullptr) + return; + // slider for Clock-wise angle in degress + // stored angle is optional CCW and in radians + // Convert stored value to degress + // minus create clock-wise roation from CCW + float angle = m_angle.value_or(0.f); + float angle_deg = static_cast(-angle * 180 / M_PI); + if (m_imgui->slider_float("##angle", &angle, limits.angle.min, limits.angle.max, u8"%.2f DEG", 1.f, false, _L("Rotate text Clock-wise."))){ + // convert back to radians and CCW + double angle_rad = -angle_deg * M_PI / 180.0; + Geometry::to_range_pi_pi(angle_rad); + + 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()); + m_angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit); + + // recalculate for surface cut + if (m_volume->emboss_shape->projection.use_surface) + process(); + } + + // Keep up - lock button icon + //ImGui::SameLine(m_gui_cfg->lock_offset); + //const IconManager::Icon &icon = get_icon(m_icons, m_keep_up ? IconType::lock : IconType::unlock, IconState::activable); + //const IconManager::Icon &icon_hover = get_icon(m_icons, m_keep_up ? IconType::lock_bold : IconType::unlock_bold, IconState::activable); + //const IconManager::Icon &icon_disable = get_icon(m_icons, m_keep_up ? IconType::lock : IconType::unlock, IconState::disabled); + //if (button(icon, icon_hover, icon_disable)) + // m_keep_up = !m_keep_up; + //if (ImGui::IsItemHovered()) + // ImGui::SetTooltip("%s", (m_keep_up? + // _u8L("Unlock the text's rotation when moving text along the object's surface."): + // _u8L("Lock the text's rotation when moving text along the object's surface.") + // ).c_str()); +} + void GLGizmoSVG::draw_model_type() { assert(m_volume != nullptr); @@ -740,12 +836,25 @@ EmbossShape select_shape() if (shape.svg_file_path.empty()) return {}; - NSVGimage *image = nsvgParseFromFile(shape.svg_file_path.c_str(), "mm", 96.0f); - ScopeGuard sg([image]() { nsvgDelete(image); }); - shape.shapes = to_expolygons(image); + // select units + bool use_inch = wxGetApp().app_config->get_bool("use_inches"); + const char *unit_mm{"mm"}; + const char *unit_in{"in"}; + const char *unit = use_inch ?unit_in : unit_mm; - // TODO: get scale from file - shape.scale = 1.; + // common used DPI is 96 or 72 + float dpi = 96.0f; + NSVGimage *image = nsvgParseFromFile(shape.svg_file_path.c_str(), unit, dpi); + ScopeGuard sg([image]() { nsvgDelete(image); }); + + + shape.scale = 1e-2; // loaded in mm + + constexpr float tesselation_tolerance = 1e-2f; + int max_level = 10; + float scale = static_cast(1 / shape.scale); + bool is_y_negative = true; + shape.shapes = to_expolygons(image, tesselation_tolerance, max_level, scale, is_y_negative); // Must contain some shapes !!! if (shape.shapes.empty()) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp index eb4ef06ac5..dbd80e6192 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp @@ -98,6 +98,8 @@ private: bool process(); void close(); void draw_window(); + void draw_distance(); + void draw_rotation(); void draw_model_type(); // process mouse event @@ -142,6 +144,8 @@ private: // Rotation gizmo GLGizmoRotate m_rotate_gizmo; std::optional m_angle; + std::optional m_distance; + // Value is set only when dragging rotation to calculate actual angle std::optional m_rotate_start_angle; @@ -151,6 +155,7 @@ private: // 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;