From c63f9f3e1158fa845a61723e1d734910bfa6607d Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 25 Jul 2023 11:27:18 +0200 Subject: [PATCH] Unify scale and suggest tesselation tolerance for svg curves --- src/libslic3r/NSVGUtils.cpp | 104 ++++++++--- src/libslic3r/NSVGUtils.hpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp | 268 +++++++++++++++++++-------- src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp | 7 +- 4 files changed, 278 insertions(+), 105 deletions(-) diff --git a/src/libslic3r/NSVGUtils.cpp b/src/libslic3r/NSVGUtils.cpp index 860d137acb..058d205380 100644 --- a/src/libslic3r/NSVGUtils.cpp +++ b/src/libslic3r/NSVGUtils.cpp @@ -32,6 +32,29 @@ NSVGimage_ptr nsvgParseFromFile(const std::string &filename, const char *units, return {image, ::nsvgDelete}; } +bool save(const NSVGimage &image, const std::string &svg_file_path) +{ + //BoundingBox bb = get_extents(to_polygons(image)); + FILE * file = boost::nowide::fopen(svg_file_path.c_str(), "w"); + if (file == NULL) + return false; + + fprintf(file, "\n"); + for (const NSVGshape *shape = image.shapes; shape != NULL; shape = shape->next) { + std::string d = "M "; // move on start point + for (const NSVGpath *path = shape->paths; path != NULL; path = path->next) { + 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]; + d += std::to_string(p[0]) + "," + std::to_string(p[1]) + " "; + } + } + d += "Z"; // closed path + fprintf(file, " \n", d.c_str()); + } + fprintf(file, "\n"); + fclose(file); +} } // namespace Slic3r namespace { @@ -48,6 +71,41 @@ bool is_useable(const NSVGshape &shape) Point::coord_type to_coor(float val, float scale) { return static_cast(std::round(val * scale)); } + + +bool need_flattening(float tessTol, const Vec2f &p1, const Vec2f &p2, const Vec2f &p3, const Vec2f &p4) { + // 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; + + return (d23 * d23) >= tessTol * pd.squaredNorm(); +} + +// see function nsvg__lineTo(NSVGparser* p, float x, float y) +bool is_line(const float *p, float precision = 1e-4){ + //Vec2f p1(p[0], p[1]); + //Vec2f p2(p[2], p[3]); + //Vec2f p3(p[4], p[5]); + //Vec2f p4(p[6], p[7]); + float dx_3 = (p[6] - p[0]) / 3.f; + float dy_3 = (p[7] - p[1]) / 3.f; + + return + is_approx(p[2], p[0] + dx_3, precision) && + is_approx(p[4], p[6] - dx_3, precision) && + is_approx(p[3], p[1] + dy_3, precision) && + is_approx(p[5], p[7] - dy_3, precision); +} + /// /// Convert cubic curve to lines /// Inspired by nanosvgrast.h function nsvgRasterize -> nsvg__flattenShape -> nsvg__flattenCubicBez @@ -60,26 +118,11 @@ Point::coord_type to_coor(float val, float scale) { return static_castCurve 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) +void flatten_cubic_bez(Polygon &polygon, float tessTol, const Vec2f& p1, const Vec2f& p2, const Vec2f& p3, const Vec2f& p4, int level) { - // 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); + if (!need_flattening(tessTol, p1, p2, p3, p4)) { + Point::coord_type x = static_cast(std::round(p4.x())); + Point::coord_type y = static_cast(std::round(p4.y())); polygon.points.emplace_back(x, y); return; } @@ -94,8 +137,8 @@ void flatten_cubic_bez(Polygon &polygon, float tessTol, const Vec2f& p1, const V 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); + flatten_cubic_bez(polygon, tessTol, p1, p12, p123, p1234, level); + flatten_cubic_bez(polygon, tessTol, p1234, p234, p34, p4, level); } Polygons to_polygons(NSVGpath *first_path, float tessTol, int max_level, float scale) @@ -110,11 +153,18 @@ Polygons to_polygons(NSVGpath *first_path, float tessTol, int max_level, float s 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]); - 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 (is_line(p)) { + // point p4 + Point::coord_type x = to_coor(p[6], scale); + Point::coord_type y = to_coor(p[7], scale); + polygon.points.emplace_back(x, y); + continue; + } + 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 * scale, p2 * scale, p3 * scale, p4 * scale, max_level); } if (path->closed && !polygon.empty()) { polygons.push_back(polygon); @@ -123,6 +173,8 @@ Polygons to_polygons(NSVGpath *first_path, float tessTol, int max_level, float s } if (!polygon.empty()) polygons.push_back(polygon); + + remove_same_neighbor(polygons); return polygons; } diff --git a/src/libslic3r/NSVGUtils.hpp b/src/libslic3r/NSVGUtils.hpp index 06f2faf0bb..adadee6952 100644 --- a/src/libslic3r/NSVGUtils.hpp +++ b/src/libslic3r/NSVGUtils.hpp @@ -25,7 +25,7 @@ Polygons to_polygons(const NSVGimage &image, float tessTol = 10., int max_leve ExPolygons to_expolygons(const NSVGimage &image, float tessTol = 10., int max_level = 10, float scale = 1.f, bool is_y_negative = true); using NSVGimage_ptr = std::unique_ptr; -NSVGimage_ptr nsvgParseFromFile(const std::string& filename, const char *units = "mm", float dpi = 96.0f); - +NSVGimage_ptr nsvgParseFromFile(const std::string &svg_file_path, const char *units = "mm", float dpi = 96.0f); +bool save(const NSVGimage &image, const std::string &svg_file_path); } // namespace Slic3r #endif // slic3r_NSVGUtils_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp index 5e80923bde..15f01beef2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp @@ -47,9 +47,6 @@ GLGizmoSVG::GLGizmoSVG(GLCanvas3D &parent) // Private functions to create emboss volume namespace{ -// Default scale of SVG image -static constexpr double DEFAULT_SCALE = 1e-2; - // Variable keep limits for variables const struct Limits { @@ -64,13 +61,20 @@ const struct Limits /// File path to svg std::string choose_svg_file(); +constexpr double get_tesselation_tolerance(double scale){ + constexpr double tesselation_tolerance_in_mm = .1; //8e-2; + constexpr double tesselation_tolerance_scaled = (tesselation_tolerance_in_mm*tesselation_tolerance_in_mm) / SCALING_FACTOR / SCALING_FACTOR; + return tesselation_tolerance_scaled / scale / scale; +} +ExPolygonsWithIds create_shape_with_ids(const NSVGimage &image, double tesselation_tolerance); + /// /// Let user to choose file with (S)calable (V)ector (G)raphics - SVG. /// Than let select contour /// /// SVG file path, when empty promt user to select one /// EmbossShape to create -EmbossShape select_shape(std::string_view filepath = ""); +EmbossShape select_shape(std::string_view filepath = "", double tesselation_tolerance_in_mm = get_tesselation_tolerance(1.)); /// /// Create new embos data @@ -114,6 +118,8 @@ enum class IconType : unsigned { change_file_hover, bake, bake_hover, + save, + save_hover, lock, lock_hover, unlock, @@ -392,6 +398,8 @@ IconManager::Icons init_icons(IconManager &mng, const GuiCfg &cfg) {"open.svg", size, IconManager::RasterType::color}, // changhe_file_hovered {"burn.svg", size, IconManager::RasterType::white_only_data}, // bake_file {"burn.svg", size, IconManager::RasterType::color}, // bake_hovered + {"save.svg", size, IconManager::RasterType::white_only_data}, // save + {"save.svg", size, IconManager::RasterType::color}, // save_hovered {"lock_closed.svg", size, IconManager::RasterType::white_only_data}, // lock {"lock_open_f.svg", size, IconManager::RasterType::white_only_data}, // lock_hovered {"lock_open.svg", size, IconManager::RasterType::white_only_data}, // unlock @@ -566,7 +574,14 @@ NSVGimage* init_image(EmbossShape::SvgFile &svg_file) { // init svg image svg_file.image = nsvgParseFromFile(svg_file.path); - + + if (svg_file.image.get() == nullptr) + return nullptr; + + // Disable stroke + for (NSVGshape *shape = svg_file.image->shapes; shape != NULL; shape = shape->next) + shape->stroke.type = 0; + return svg_file.image.get(); } @@ -584,7 +599,7 @@ bool init_texture(Texture &texture, ModelVolume &mv, unsigned max_size_px) // GLTexture::load_from_svg_file(filepath, false, false, false, max_size_px); // NOTE: Can not use es.shape --> it is aligned and one need offset in svg - ExPolygons shape = to_expolygons(*image); + Polygons shape = to_polygons(*image); if (shape.empty()) return false; @@ -618,7 +633,7 @@ bool init_texture(Texture &texture, ModelVolume &mv, unsigned max_size_px) float tx = static_cast(-bb.min.x() * scale); float ty = static_cast(bb.max.y() * scale); // Reverse direction of y int stride = texture.width * channels_count; - nsvgRasterizeXY(rast, image, tx, ty, scale, scale, data.data(), texture.width, texture.height, stride); + nsvgRasterize(rast, image, tx, ty, scale, data.data(), texture.width, texture.height, stride); // fill by monotone color std::vector fill_color = {201, 201, 201}; // RGB and keep same alpha @@ -629,6 +644,8 @@ bool init_texture(Texture &texture, ModelVolume &mv, unsigned max_size_px) // sends data to gpu glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); + if (texture.id != 0) + glsafe(::glDeleteTextures(1, &texture.id)); glsafe(::glGenTextures(1, &texture.id)); glsafe(::glBindTexture(GL_TEXTURE_2D, texture.id)); glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei) texture.width, (GLsizei) texture.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void *) data.data())); @@ -683,11 +700,20 @@ void GLGizmoSVG::set_volume_by_selection() // Calculate current angle of up vector m_angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit); m_distance = calc_distance(*gl_volume, m_raycast_manager, m_parent); + + m_shape_bb = get_extents(m_volume_shape.shapes_with_ids); // calculate scale for height and depth inside of scaled object instance calculate_scale(); } - +namespace { +void delete_texture(Texture& texture){ + if (texture.id != 0) { + glsafe(::glDeleteTextures(1, &texture.id)); + texture.id = 0; + } +} +} void GLGizmoSVG::reset_volume() { if (m_volume == nullptr) @@ -697,11 +723,7 @@ void GLGizmoSVG::reset_volume() m_volume_id.id = 0; m_volume_shape.shapes_with_ids.clear(); m_filename_preview.clear(); - - if (m_texture.id != 0) { - glsafe(::glDeleteTextures(1, &m_texture.id)); - m_texture.id = 0; - } + delete_texture(m_texture); } void GLGizmoSVG::calculate_scale() { @@ -721,6 +743,7 @@ void GLGizmoSVG::calculate_scale() { return true; }; + calc(Vec3d::UnitX(), m_scale_width); calc(Vec3d::UnitY(), m_scale_height); calc(Vec3d::UnitZ(), m_scale_depth); } @@ -797,7 +820,42 @@ void GLGizmoSVG::draw_window() draw_model_type(); } } +namespace { +size_t count(const NSVGshape &shape){ + size_t res = 0; + for (const NSVGshape *shape_ptr = &shape; shape_ptr != NULL; shape_ptr = shape_ptr->next) + ++res; + return res; +} +void draw(const ExPolygonsWithIds& shapes_with_ids, unsigned max_size) +{ + ImVec2 actual_pos = ImGui::GetCursorPos(); + // draw shapes + BoundingBox bb; + for (const ExPolygonsWithId &shape : shapes_with_ids) + bb.merge(get_extents(shape.expoly)); + + Point bb_size = bb.size(); + double scale = max_size / (double) std::max(bb_size.x(), bb_size.y()); + ImVec2 win_offset = ImGui::GetWindowPos(); + Point offset(win_offset.x + actual_pos.x, win_offset.y + actual_pos.y); + offset += bb_size / 2 * scale; + auto draw_polygon = [&scale, offset](Slic3r::Polygon p) { + p.scale(scale, -scale); // Y mirror + p.translate(offset); + ImGuiWrapper::draw(p); + }; + + for (const ExPolygonsWithId &shape : shapes_with_ids) { + for (const ExPolygon &expoly : shape.expoly) { + draw_polygon(expoly.contour); + for (const Slic3r::Polygon &hole : expoly.holes) + draw_polygon(hole); + } + } +} +} bool GLGizmoSVG::draw_preview(){ assert(m_volume->emboss_shape.has_value()); if (!m_volume->emboss_shape.has_value()) { @@ -808,12 +866,18 @@ bool GLGizmoSVG::draw_preview(){ // init texture when not initialized yet. // drag&drop is out of rendering scope so texture must be created on this place if (m_texture.id == 0) - init_texture(m_texture, *m_volume, m_gui_cfg->texture_max_size_px); + init_texture(m_texture, *m_volume, m_gui_cfg->texture_max_size_px); + + ::draw(m_volume_shape.shapes_with_ids, m_gui_cfg->texture_max_size_px); if (m_texture.id != 0) { ImTextureID id = (void *) static_cast(m_texture.id); ImVec2 s(m_texture.width, m_texture.height); ImGui::Image(id, s); + if(ImGui::IsItemHovered()){ + size_t count_shapes = count(*m_volume->emboss_shape->svg_file.image->shapes); + ImGui::SetTooltip("%d count shapes", count_shapes); + } } if (m_filename_preview.empty()){ @@ -837,6 +901,8 @@ bool GLGizmoSVG::draw_preview(){ ImGui::SetTooltip("%s", tooltip.c_str()); } + bool file_changed = false; + // Re-Load button bool can_reload = !m_volume_shape.svg_file.path.empty(); if (can_reload) { @@ -845,9 +911,7 @@ bool GLGizmoSVG::draw_preview(){ if (!boost::filesystem::exists(m_volume_shape.svg_file.path)) { m_volume_shape.svg_file.path.clear(); } else { - m_volume_shape.shapes_with_ids = select_shape(m_volume_shape.svg_file.path).shapes_with_ids; - init_texture(m_texture, *m_volume, m_gui_cfg->texture_max_size_px); - process(); + file_changed = true; } } else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("Re-load SVG file from disk.").c_str()); @@ -855,13 +919,24 @@ bool GLGizmoSVG::draw_preview(){ ImGui::SameLine(); if (clickable(get_icon(m_icons, IconType::change_file), get_icon(m_icons, IconType::change_file_hover))) { - m_volume_shape.shapes_with_ids = select_shape().shapes_with_ids; - init_texture(m_texture, *m_volume, m_gui_cfg->texture_max_size_px); - process(); + std::string new_path = choose_svg_file(); + if (!new_path.empty()) { + file_changed = true; + m_volume_shape.svg_file.path = new_path; + } } else if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", _u8L("Change to another .svg file").c_str()); } + if (file_changed) { + double tes_tol = get_tesselation_tolerance(std::max(m_scale_width.value_or(1.f), m_scale_height.value_or(1.f))); + EmbossShape es = select_shape(m_volume_shape.svg_file.path, tes_tol); + m_volume_shape.svg_file.image = es.svg_file.image; + m_volume_shape.shapes_with_ids = es.shapes_with_ids; + init_texture(m_texture, *m_volume, m_gui_cfg->texture_max_size_px); + process(); + } + ImGui::SameLine(); if (clickable(get_icon(m_icons, IconType::bake), get_icon(m_icons, IconType::bake_hover))) { m_volume->emboss_shape.reset(); @@ -870,6 +945,13 @@ bool GLGizmoSVG::draw_preview(){ } else if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", _u8L("Bake to uneditable part and save copyright of svg").c_str()); } + + ImGui::SameLine(); + if (clickable(get_icon(m_icons, IconType::save), get_icon(m_icons, IconType::save_hover))) { + Slic3r::save(*m_volume_shape.svg_file.image, "C:/data/temp/saved.svg"); + } else if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", _u8L("Save as svg file").c_str()); + } return true; } @@ -883,9 +965,16 @@ void GLGizmoSVG::draw_depth() double &value = m_volume_shape.projection.depth; if (use_inch) { const char *size_format = "%.2f in"; - double value_inch = value * ObjectManipulation::mm_to_in; + double value_inch = value * ObjectManipulation::mm_to_in * m_scale_depth.value_or(1.f); if (ImGui::InputDouble("##depth", &value_inch, 1., 10., size_format)) { - value = value_inch * ObjectManipulation::in_to_mm; + value = value_inch * ObjectManipulation::in_to_mm / m_scale_depth.value_or(1.f); + process(); + } + } else if (m_scale_depth.has_value()) { + const char *size_format = "%.1f mm"; + double value_mm = value * (*m_scale_depth); + if (ImGui::InputDouble("##depth", &value_mm, 1., 10., size_format)) { + value = value_mm / (*m_scale_depth); process(); } } else { @@ -903,15 +992,14 @@ void GLGizmoSVG::draw_size() bool use_inch = wxGetApp().app_config->get_bool("use_inches"); - // TODO: cache it - BoundingBox bb = get_extents(m_volume_shape.shapes_with_ids); - Point size = bb.size(); - - double width = size.x() * m_volume_shape.scale; + Point size = m_shape_bb.size(); + double width = size.x() * m_volume_shape.scale * m_scale_width.value_or(1.f); if (use_inch) width *= ObjectManipulation::mm_to_in; - double height = size.y() * m_volume_shape.scale; + double height = size.y() * m_volume_shape.scale * m_scale_height.value_or(1.f); if (use_inch) height *= ObjectManipulation::mm_to_in; + std::optional new_absolute_scale; + if (m_keep_ratio) { std::stringstream ss; ss << std::setprecision(2) << width << " x " << height << " " << (use_inch ? "in" : "mm"); @@ -919,12 +1007,15 @@ void GLGizmoSVG::draw_size() ImGui::SameLine(m_gui_cfg->input_offset); ImGui::SetNextItemWidth(m_gui_cfg->input_width); + // convert to float for slider float width_f = width; if (m_imgui->slider_float("##width_size_slider", &width_f, 5.f, 100.f, ss.str().c_str())) { - if (use_inch) - width_f *= ObjectManipulation::in_to_mm; - m_volume_shape.scale = width_f / size.x(); - process(); + double width_ratio = width_f / width; + if (std::fabs(width_ratio - 1.) > 1e-4) { + m_scale_width = m_scale_width.value_or(1.f) * width_ratio; + m_scale_height = m_scale_height.value_or(1.f) * width_ratio; + new_absolute_scale = Vec3d(*m_scale_width, *m_scale_height, 1.); + } } } else { ImGuiInputTextFlags flags = 0; @@ -939,49 +1030,69 @@ void GLGizmoSVG::draw_size() ImGui::SameLine(m_gui_cfg->input_offset); ImGui::SetNextItemWidth(input_width); + double prev_width = width; if (ImGui::InputDouble("##width", &width, step, fast_step, size_format, flags)) { - if (use_inch) - width *= ObjectManipulation::in_to_mm; - m_volume_shape.scale = width / size.x(); - process(); + double width_ratio = width / prev_width; + m_scale_width = m_scale_width.value_or(1.f) * width_ratio; + new_absolute_scale = Vec3d(*m_scale_width, m_scale_height.value_or(1.f), 1.); } if (ImGui::IsItemHovered()) - ImGui::SetTooltip("%s", _u8L("Width of SVG.").c_str()); + ImGui::SetTooltip("%s", "Width of SVG."); ImGui::SameLine(second_offset); ImGui::SetNextItemWidth(input_width); + double prev_height = height; if (ImGui::InputDouble("##height", &height, step, fast_step, size_format, flags)) { - if (use_inch) - height *= ObjectManipulation::in_to_mm; - m_volume_shape.scale = height / size.y(); - process(); + double height_ratio = height / prev_height; + m_scale_height = m_scale_height.value_or(1.f) * height_ratio; + new_absolute_scale = Vec3d(m_scale_width.value_or(1.f), *m_scale_height, 1.); } if (ImGui::IsItemHovered()) - ImGui::SetTooltip("%s", _u8L("Height of SVG.").c_str()); + ImGui::SetTooltip("%s", "Height of SVG."); } // Lock on ratio m_keep_ratio - //ImGui::SameLine(m_gui_cfg->lock_offset); - //const IconManager::Icon &icon = get_icon(m_icons, m_keep_ratio ? IconType::lock : IconType::unlock); - //const IconManager::Icon &icon_hover = get_icon(m_icons, m_keep_ratio ? IconType::lock_hover : IconType::unlock_hover); - //if (button(icon, icon_hover, icon)) - // m_keep_ratio = !m_keep_ratio; - //if (ImGui::IsItemHovered()) - // ImGui::SetTooltip("%s", (m_keep_ratio ? - // _u8L("Free set of width and height value."): - // _u8L("Keep same ratio of width to height.") - // ).c_str()); + ImGui::SameLine(m_gui_cfg->lock_offset); + const IconManager::Icon &icon = get_icon(m_icons, m_keep_ratio ? IconType::lock : IconType::unlock); + const IconManager::Icon &icon_hover = get_icon(m_icons, m_keep_ratio ? IconType::lock_hover : IconType::unlock_hover); + if (button(icon, icon_hover, icon)) + m_keep_ratio = !m_keep_ratio; + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", (m_keep_ratio ? + _u8L("Free set of width and height value."): + _u8L("Keep same ratio of width to height.") + ).c_str()); // reset button - bool can_reset = !is_approx(m_volume_shape.scale, DEFAULT_SCALE); + bool can_reset = m_scale_width.has_value() || m_scale_height.has_value() || m_scale_depth.has_value(); if (can_reset) { if (reset_button(m_icons)) { - m_volume_shape.scale = DEFAULT_SCALE; - process(); + new_absolute_scale = Vec3d(1., 1., 1.); } else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("Reset scale to loaded one from the SVG").c_str()); } + + if (new_absolute_scale.has_value()){ + Selection &selection = m_parent.get_selection(); + selection.setup_cache(); + TransformationType type = m_volume->is_the_only_one_part() ? + TransformationType::Instance_Absolute_Independent : + TransformationType::Local_Absolute_Independent; + selection.scale(*new_absolute_scale, type); + m_parent.do_scale(L("Resize")); + wxGetApp().obj_manipul()->set_dirty(); + // should be the almost same + calculate_scale(); + + NSVGimage *img = m_volume_shape.svg_file.image.get(); + assert(img != NULL); + if (img != NULL){ + double tes_tol = get_tesselation_tolerance(std::max(m_scale_width.value_or(1.f), m_scale_height.value_or(1.f))); + m_volume_shape.shapes_with_ids = create_shape_with_ids(*img, tes_tol); + process(); + } + } } void GLGizmoSVG::draw_use_surface() @@ -1275,14 +1386,14 @@ GuiCfg create_gui_configuration() { float lock_width = cfg.icon_width + 3 * space; tr.depth = _u8L("Depth"); - tr.size = _u8L("Width / Height"); + tr.size = _u8L("Size"); tr.use_surface = _u8L("Use surface"); tr.distance = _u8L("From surface"); tr.rotation = _u8L("Rotation"); tr.reflection = _u8L("Reflection"); float max_tr_width = std::max({ ImGui::CalcTextSize(tr.depth.c_str()).x, - ImGui::CalcTextSize(tr.size.c_str()).x, + ImGui::CalcTextSize(tr.size.c_str()).x + lock_width, ImGui::CalcTextSize(tr.use_surface.c_str()).x, ImGui::CalcTextSize(tr.distance.c_str()).x, ImGui::CalcTextSize(tr.rotation.c_str()).x + lock_width, @@ -1345,7 +1456,24 @@ void translate(ExPolygons &expolys, const Point &p) { expoly.translate(p); } -EmbossShape select_shape(std::string_view filepath) +ExPolygonsWithIds create_shape_with_ids(const NSVGimage &image, double tesselation_tolerance) +{ + int max_level = 10; + bool is_y_negative = true; + ExPolygons expoly = to_expolygons(image, tesselation_tolerance, max_level, 1.0/SCALING_FACTOR, is_y_negative); + if (expoly.empty()) + return {}; + + // SVG is used as centered + // Do not disturb user by settings of pivot position + BoundingBox bb = get_extents(expoly); + translate(expoly, -bb.center()); + + unsigned id = 0; + return {{id, expoly}}; +} + +EmbossShape select_shape(std::string_view filepath, double tesselation_tolerance) { EmbossShape shape; shape.projection.depth = 10.; @@ -1370,33 +1498,21 @@ EmbossShape select_shape(std::string_view filepath) return {}; } - shape.svg_file.image = nsvgParseFromFile(shape.svg_file.path); + init_image(shape.svg_file); if (shape.svg_file.image.get() == NULL) { show_error(nullptr, GUI::format(_u8L("Nano SVG parser can't load from file(%1%)."), shape.svg_file.path)); return {}; } - shape.scale = DEFAULT_SCALE; // 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; - ExPolygons expoly = to_expolygons(*shape.svg_file.image, tesselation_tolerance, max_level, scale, is_y_negative); - + // Set default and unchanging scale + shape.scale = SCALING_FACTOR; + shape.shapes_with_ids = create_shape_with_ids(*shape.svg_file.image, tesselation_tolerance); + // Must contain some shapes !!! - if (expoly.empty()) { + if (shape.shapes_with_ids.empty()) { show_error(nullptr, GUI::format(_u8L("SVG file(%1%) do NOT contain path to be able embossed."), shape.svg_file.path)); return {}; } - - // SVG is used as centered - // Do not disturb user by settings of pivot position - BoundingBox bb = get_extents(expoly); - translate(expoly, -bb.center()); - - unsigned id = 0; - shape.shapes_with_ids = {{id, expoly}}; - return shape; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp index 4e5aa121df..ce7783ce83 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp @@ -158,7 +158,7 @@ private: // When true keep up vector otherwise relative rotation bool m_keep_up = true; - + // Keep size aspect ratio when True. bool m_keep_ratio = true; // setted only when wanted to use - not all the time @@ -168,6 +168,7 @@ private: std::optional m_surface_drag; // For volume on scaled objects + std::optional m_scale_width; std::optional m_scale_height; std::optional m_scale_depth; void calculate_scale(); @@ -175,6 +176,10 @@ private: // keep SVG data rendered on GPU Texture m_texture; + // bounding box of shape + // Note: Scaled mm to int value by m_volume_shape.scale + BoundingBox m_shape_bb; + std::string m_filename_preview; IconManager m_icon_manager;