diff --git a/src/libslic3r/EmbossShape.hpp b/src/libslic3r/EmbossShape.hpp index 897cf134c1..f61c9844fc 100644 --- a/src/libslic3r/EmbossShape.hpp +++ b/src/libslic3r/EmbossShape.hpp @@ -97,7 +97,7 @@ struct EmbossShape // Loaded svg file data. // !!! It is not serialized on undo/redo stack - std::shared_ptr image; + std::shared_ptr image = nullptr; }; SvgFile svg_file; diff --git a/src/libslic3r/NSVGUtils.cpp b/src/libslic3r/NSVGUtils.cpp index 30256378d8..860d137acb 100644 --- a/src/libslic3r/NSVGUtils.cpp +++ b/src/libslic3r/NSVGUtils.cpp @@ -2,70 +2,28 @@ #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)); } - +Slic3r::Polygons to_polygons(const NSVGshape &shape, float tessTol, int max_level, float scale, bool is_y_negative); } // namespace namespace Slic3r { + Polygons to_polygons(const NSVGimage &image, float tessTol, int max_level, float scale, bool is_y_negative) { Polygons polygons; - for (NSVGshape *shape = image.shapes; shape != NULL; shape = shape->next) { - if (!(shape->flags & NSVG_FLAGS_VISIBLE)) - continue; - if (shape->fill.type == NSVG_PAINT_NONE) - continue; - - Polygon polygon; - for (NSVGpath *path = shape->paths; path != NULL; path = path->next) { - // Flatten path - 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]); - 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()) - polygons.push_back(polygon); - } - - if (is_y_negative) - for (Polygon &polygon : polygons) - for (Point &p : polygon.points) - p.y() = -p.y(); - + for (NSVGshape *shape = image.shapes; shape != NULL; shape = shape->next) + polygons_append(polygons, ::to_polygons(*shape, tessTol, max_level, scale, is_y_negative)); return polygons; } ExPolygons to_expolygons(const 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)); + ExPolygons expolygons; + for (NSVGshape *shape = image.shapes; shape != NULL; shape = shape->next) { + Polygons polygons = ::to_polygons(*shape, tessTol, max_level, scale, is_y_negative); + if (polygons.empty()) + continue; + expolygons_append(expolygons, union_ex(polygons)); + } + return union_ex(expolygons); } NSVGimage_ptr nsvgParseFromFile(const std::string &filename, const char *units, float dpi) @@ -77,8 +35,33 @@ NSVGimage_ptr nsvgParseFromFile(const std::string &filename, const char *units, } // namespace Slic3r namespace { -// inspired by nanosvgrast.h function nsvgRasterize -> nsvg__flattenShape -> nsvg__flattenCubicBez -// https://github.com/memononen/nanosvg/blob/f0a3e1034dd22e2e87e5db22401e44998383124e/src/nanosvgrast.h#L335 +using namespace Slic3r; // Polygon + Vec2f + +bool is_useable(const NSVGshape &shape) +{ + if (!(shape.flags & NSVG_FLAGS_VISIBLE)) + return false; + if (shape.fill.type == NSVG_PAINT_NONE) + return false; + return true; +} + +Point::coord_type to_coor(float val, float scale) { return static_cast(std::round(val * scale)); } + +/// +/// Convert cubic curve to lines +/// Inspired by nanosvgrast.h function nsvgRasterize -> nsvg__flattenShape -> nsvg__flattenCubicBez +/// https://github.com/memononen/nanosvg/blob/f0a3e1034dd22e2e87e5db22401e44998383124e/src/nanosvgrast.h#L335 +/// +/// 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) { // f .. first @@ -114,4 +97,48 @@ void flatten_cubic_bez(Polygon &polygon, float tessTol, const Vec2f& p1, const V flatten_cubic_bez(polygon, tessTol, p1, p12, p123, p1234, level, scale); flatten_cubic_bez(polygon, tessTol, p1234, p234, p34, p4, level, scale); } + +Polygons to_polygons(NSVGpath *first_path, float tessTol, int max_level, float scale) +{ + Polygons polygons; + Polygon polygon; + for (NSVGpath *path = first_path; path != NULL; path = path->next) { + // Flatten path + 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]); + 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()) + polygons.push_back(polygon); + return polygons; +} + +Polygons to_polygons(const NSVGshape &shape, float tessTol, int max_level, float scale, bool is_y_negative) +{ + if (!is_useable(shape)) + return {}; + + Polygons polygons = to_polygons(shape.paths, tessTol, max_level, scale); + + if (is_y_negative) + for (Polygon &polygon : polygons) + for (Point &p : polygon.points) + p.y() = -p.y(); + + return polygons; +} + } // namespace \ No newline at end of file diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp index 42b5efcb7c..5e80923bde 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp @@ -76,9 +76,10 @@ EmbossShape select_shape(std::string_view filepath = ""); /// Create new embos data /// /// Cancel for previous job +/// To distiquish whether it is outside of model /// SVG file path /// Base data for emboss SVG -DataBasePtr create_emboss_data_base(std::shared_ptr> &cancel, std::string_view filepath = ""); +DataBasePtr create_emboss_data_base(std::shared_ptr> &cancel, ModelVolumeType volume_type, std::string_view filepath = ""); /// /// Separate file name from file path. @@ -184,24 +185,24 @@ BoundingBox get_extents(const ExPolygonsWithIds &expoly_ids) bool GLGizmoSVG::create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos) { CreateVolumeParams input = create_input(m_parent, m_raycast_manager, volume_type); - DataBasePtr base = create_emboss_data_base(m_job_cancel); - base->is_outside = volume_type == ModelVolumeType::MODEL_PART; + DataBasePtr base = create_emboss_data_base(m_job_cancel, volume_type); + if (!base) return false; // Uninterpretable svg return start_create_volume(input, std::move(base), mouse_pos); } bool GLGizmoSVG::create_volume(ModelVolumeType volume_type) { CreateVolumeParams input = create_input(m_parent, m_raycast_manager, volume_type); - DataBasePtr base = create_emboss_data_base(m_job_cancel); - base->is_outside = volume_type == ModelVolumeType::MODEL_PART; + DataBasePtr base = create_emboss_data_base(m_job_cancel,volume_type); + if (!base) return false; // Uninterpretable svg return start_create_volume_without_position(input, std::move(base)); } bool GLGizmoSVG::create_volume(std::string_view svg_file, ModelVolumeType volume_type, const Vec2d &mouse_pos) { CreateVolumeParams input = create_input(m_parent, m_raycast_manager, volume_type); - DataBasePtr base = create_emboss_data_base(m_job_cancel, svg_file); - base->is_outside = volume_type == ModelVolumeType::MODEL_PART; + DataBasePtr base = create_emboss_data_base(m_job_cancel, volume_type, svg_file); + if (!base) return false; // Uninterpretable svg // is not a number || is infinity if (mouse_pos.x() != mouse_pos.x() || mouse_pos.y() != mouse_pos.y()) @@ -554,22 +555,33 @@ void GLGizmoSVG::on_dragging(const UpdateData &data) { m_rotate_gizmo.dragging(d #include "slic3r/GUI/BitmapCache.hpp" #include "nanosvg/nanosvgrast.h" namespace{ -bool init_texture(Texture &texture, const ModelVolume &mv, unsigned max_size_px) +NSVGimage* init_image(EmbossShape::SvgFile &svg_file) { + // is already initialized? + if (svg_file.image.get() != nullptr) + return svg_file.image.get(); + + // chech if path is known + if (svg_file.path.empty()) + return nullptr; + + // init svg image + svg_file.image = nsvgParseFromFile(svg_file.path); + + return svg_file.image.get(); +} + +bool init_texture(Texture &texture, ModelVolume &mv, unsigned max_size_px) { if (!mv.emboss_shape.has_value()) return false; - const EmbossShape &es = *mv.emboss_shape; - const std::string &filepath = es.svg_file.path; - if (filepath.empty()) + EmbossShape &es = *mv.emboss_shape; + NSVGimage *image = init_image(es.svg_file); + if (image == nullptr) return false; // inspired by: // GLTexture::load_from_svg_file(filepath, false, false, false, max_size_px); - NSVGimage *image = BitmapCache::nsvgParseFromFileWithReplace(filepath.c_str(), "px", 96.0f, {}); - if (image == nullptr) - return false; - ScopeGuard sg_image([image]() { nsvgDelete(image); }); // NOTE: Can not use es.shape --> it is aligned and one need offset in svg ExPolygons shape = to_expolygons(*image); @@ -601,13 +613,20 @@ bool init_texture(Texture &texture, const ModelVolume &mv, unsigned max_size_px) return false; ScopeGuard sg_rast([rast]() { nsvgDeleteRasterizer(rast); }); - int channels_count = 4; + constexpr int channels_count = 4; std::vector data(n_pixels * channels_count, 0); 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); + // fill by monotone color + std::vector fill_color = {201, 201, 201}; // RGB and keep same alpha + for (size_t i = 0; i+2 < data.size(); i += channels_count) + if (data[i] != 0 || data[i + 1] != 0 || data[i + 2] != 0) + for (size_t j = 0; j < fill_color.size(); j++) + data[i + j] = fill_color[j]; + // sends data to gpu glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); glsafe(::glGenTextures(1, &texture.id)); @@ -1352,7 +1371,7 @@ EmbossShape select_shape(std::string_view filepath) } shape.svg_file.image = nsvgParseFromFile(shape.svg_file.path); - if (shape.svg_file.image.get() == nullptr) { + 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 {}; } @@ -1381,7 +1400,7 @@ EmbossShape select_shape(std::string_view filepath) return shape; } -DataBasePtr create_emboss_data_base(std::shared_ptr> &cancel, std::string_view filepath) +DataBasePtr create_emboss_data_base(std::shared_ptr> &cancel, ModelVolumeType volume_type, std::string_view filepath) { EmbossShape shape = select_shape(filepath); @@ -1399,7 +1418,9 @@ DataBasePtr create_emboss_data_base(std::shared_ptr> &cancel, std::string name = volume_name(shape); - return std::make_unique(name, cancel /*copy*/, std::move(shape)); + auto result = std::make_unique(name, cancel /*copy*/, std::move(shape)); + result->is_outside = volume_type == ModelVolumeType::MODEL_PART; + return result; } } // namespace