From 96618d684f753870b3d2e72a24ffb4a0c6981f2e Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Thu, 31 Aug 2023 16:41:21 +0200 Subject: [PATCH] Emboss Stroke(contour of paths from svg) Do not support yet: - Markers(start sybol, end symbol, middle symbols) - Dashes --- src/libslic3r/ClipperUtils.cpp | 25 ++- src/libslic3r/ClipperUtils.hpp | 11 +- src/libslic3r/Emboss.cpp | 21 ++- src/libslic3r/Emboss.hpp | 7 +- src/libslic3r/ExPolygon.hpp | 5 + src/libslic3r/NSVGUtils.cpp | 223 +++++++++++++++++-------- src/libslic3r/NSVGUtils.hpp | 55 ++++++- src/libslic3r/Polygon.cpp | 2 +- src/libslic3r/Polygon.hpp | 14 +- src/libslic3r/Polyline.cpp | 27 +++ src/libslic3r/Polyline.hpp | 4 + src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp | 236 +++++++++++---------------- src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp | 2 +- tests/libslic3r/test_emboss.cpp | 4 +- 14 files changed, 402 insertions(+), 234 deletions(-) diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index e942423947..dfbb757b8c 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -271,8 +271,8 @@ bool has_duplicate_points(const ClipperLib::PolyTree &polytree) // Offset CCW contours outside, CW contours (holes) inside. // Don't calculate union of the output paths. -template -static ClipperLib::Paths raw_offset(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit) +template +static ClipperLib::Paths raw_offset(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::EndType endType = ClipperLib::etClosedPolygon) { CLIPPER_UTILS_TIME_LIMIT_MILLIS(CLIPPER_UTILS_TIME_LIMIT_DEFAULT); @@ -364,11 +364,11 @@ inline ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &inpu return PolyTreeToExPolygons(clipper_union(input, do_union ? ClipperLib::pftNonZero : ClipperLib::pftEvenOdd)); } -template -static ClipperLib::Paths raw_offset_polyline(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit) +template +static ClipperLib::Paths raw_offset_polyline(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::EndType end_type = ClipperLib::etOpenButt) { assert(offset > 0); - return raw_offset(std::forward(paths), offset, joinType, miterLimit); + return raw_offset(std::forward(paths), offset, joinType, miterLimit, end_type); } template @@ -421,10 +421,17 @@ Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, Cli Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) { return PolyTreeToExPolygons(offset_paths(ClipperUtils::PolygonsProvider(polygons), delta, joinType, miterLimit)); } -Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType, double miterLimit) - { assert(delta > 0); return to_polygons(clipper_union(raw_offset_polyline(ClipperUtils::SinglePathProvider(polyline.points), delta, joinType, miterLimit))); } -Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType, double miterLimit) - { assert(delta > 0); return to_polygons(clipper_union(raw_offset_polyline(ClipperUtils::PolylinesProvider(polylines), delta, joinType, miterLimit))); } +Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::EndType end_type) + { assert(delta > 0); return to_polygons(clipper_union(raw_offset_polyline(ClipperUtils::SinglePathProvider(polyline.points), delta, joinType, miterLimit, end_type))); } +Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::EndType end_type) + { assert(delta > 0); return to_polygons(clipper_union(raw_offset_polyline(ClipperUtils::PolylinesProvider(polylines), delta, joinType, miterLimit, end_type))); } + +Polygons contour_to_polygons(const Polygon &polygon, const float line_width, ClipperLib::JoinType join_type, double miter_limit){ + assert(line_width > 1.f); return to_polygons(clipper_union( + raw_offset(ClipperUtils::SinglePathProvider(polygon.points), line_width/2, join_type, miter_limit, ClipperLib::etClosedLine)));} +Polygons contour_to_polygons(const Polygons &polygons, const float line_width, ClipperLib::JoinType join_type, double miter_limit){ + assert(line_width > 1.f); return to_polygons(clipper_union( + raw_offset(ClipperUtils::PolygonsProvider(polygons), line_width/2, join_type, miter_limit, ClipperLib::etClosedLine)));} // returns number of expolygons collected (0 or 1). static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::Paths &out) diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index 7935034b7b..637a9b183f 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -29,6 +29,9 @@ class BoundingBox; static constexpr const float ClipperSafetyOffset = 10.f; static constexpr const Slic3r::ClipperLib::JoinType DefaultJoinType = Slic3r::ClipperLib::jtMiter; + +static constexpr const Slic3r::ClipperLib::EndType DefaultEndType = Slic3r::ClipperLib::etOpenButt; + //FIXME evaluate the default miter limit. 3 seems to be extreme, Cura uses 1.2. // Mitter Limit 3 is useful for perimeter generator, where sharp corners are extruded without needing a gap fill. // However such a high limit causes issues with large positive or negative offsets, where a sharp corner @@ -336,8 +339,8 @@ Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, Clipp // offset Polylines // Wherever applicable, please use the expand() / shrink() variants instead, they convey their purpose better. // Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons. -Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit); -Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit); +Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit, ClipperLib::EndType end_type = DefaultEndType); +Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit, ClipperLib::EndType end_type = DefaultEndType); Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); @@ -349,6 +352,10 @@ Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float d Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); Slic3r::ExPolygons offset_ex(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +// convert stroke to path by offsetting of contour +Polygons contour_to_polygons(const Polygon &polygon, const float line_width, ClipperLib::JoinType join_type = DefaultJoinType, double miter_limit = DefaultMiterLimit); +Polygons contour_to_polygons(const Polygons &polygon, const float line_width, ClipperLib::JoinType join_type = DefaultJoinType, double miter_limit = DefaultMiterLimit); + inline Slic3r::Polygons union_safety_offset (const Slic3r::Polygons &polygons) { return offset (polygons, ClipperSafetyOffset); } inline Slic3r::Polygons union_safety_offset (const Slic3r::ExPolygons &expolygons) { return offset (expolygons, ClipperSafetyOffset); } inline Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons) { return offset_ex(polygons, ClipperSafetyOffset); } diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index 05181a2e1f..86ab94051c 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -1223,7 +1223,6 @@ ExPolygons letter2shapes( const int CANCEL_CHECK = 10; } // namespace - /// Union shape defined by glyphs ExPolygons Slic3r::union_ex(const ExPolygonsWithIds &shapes) { @@ -1239,6 +1238,26 @@ ExPolygons Slic3r::union_ex(const ExPolygonsWithIds &shapes) return result; } +void Slic3r::translate(ExPolygonsWithIds &e, const Point &p) +{ + for (auto &[id, expoly] : e) + translate(expoly, p); +} + +BoundingBox Slic3r::get_extents(const ExPolygonsWithIds &e) +{ + BoundingBox bb; + for (auto &[id, expoly] : e) + bb.merge(get_extents(expoly)); + return bb; +} + +void Slic3r::center(ExPolygonsWithIds &e) +{ + BoundingBox bb = get_extents(e); + translate(e, -bb.center()); +} + ExPolygons Emboss::text2shapes(FontFileWithCache &font_with_cache, const char *text, const FontProp &font_prop, const std::function& was_canceled) { std::wstring text_w = boost::nowide::widen(text); diff --git a/src/libslic3r/Emboss.hpp b/src/libslic3r/Emboss.hpp index 784411d2fe..37e0a0bd5a 100644 --- a/src/libslic3r/Emboss.hpp +++ b/src/libslic3r/Emboss.hpp @@ -458,7 +458,12 @@ namespace Emboss } // namespace Emboss - +/////////////////////// +// Move to ExPolygonsWithIds Utils +void translate(ExPolygonsWithIds &e, const Point &p); +BoundingBox get_extents(const ExPolygonsWithIds &e); +void center(ExPolygonsWithIds &e); ExPolygons union_ex(const ExPolygonsWithIds &shapes); + } // namespace Slic3r #endif // slic3r_Emboss_hpp_ diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index 047c30b4a4..5907e723b8 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -372,6 +372,11 @@ inline Points to_points(const ExPolygon &expoly) return out; } +inline void translate(ExPolygons &expolys, const Point &p) { + for (ExPolygon &expoly : expolys) + expoly.translate(p); +} + inline void polygons_append(Polygons &dst, const ExPolygon &src) { dst.reserve(dst.size() + src.holes.size() + 1); diff --git a/src/libslic3r/NSVGUtils.cpp b/src/libslic3r/NSVGUtils.cpp index 7a13eb2abc..ffa0f258e6 100644 --- a/src/libslic3r/NSVGUtils.cpp +++ b/src/libslic3r/NSVGUtils.cpp @@ -3,39 +3,77 @@ #include // to_chars #include "ClipperUtils.hpp" +#include "Emboss.hpp" // heal for shape -namespace { -Slic3r::Polygons to_polygons(const NSVGshape &shape, float tessTol, int max_level, float scale, bool is_y_negative); - +namespace { +using namespace Slic3r; // Polygon // see function nsvg__lineTo(NSVGparser* p, float x, float y) -bool is_line(const float *p, float precision = 1e-4); - +bool is_line(const float *p, float precision = 1e-4f); +// convert curve in path to lines +struct LinesPath{ + Polygons polygons; + Polylines polylines; }; +LinesPath linearize_path(NSVGpath *first_path, const NSVGLineParams ¶m); +ExPolygons fill_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape); +ExPolygons stroke_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams ¶m); } // namespace namespace Slic3r { -Polygons to_polygons(const NSVGimage &image, float tessTol, int max_level, float scale, bool is_y_negative) +ExPolygonsWithIds create_shape_with_ids(const NSVGimage &image, const NSVGLineParams ¶m) { - Polygons polygons; - 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){ - 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()) + ExPolygonsWithIds result; + size_t shape_id = 0; + for (NSVGshape *shape_ptr = image.shapes; shape_ptr != NULL; shape_ptr = shape_ptr->next, ++shape_id) { + const NSVGshape &shape = *shape_ptr; + if (!(shape.flags & NSVG_FLAGS_VISIBLE)) continue; - ClipperLib::PolyFillType fill_type = ClipperLib::pftNonZero; - //if (shape->fillRule == NSVGfillRule::NSVG_FILLRULE_NONZERO) - if (shape->fillRule == NSVGfillRule::NSVG_FILLRULE_EVENODD) - fill_type = ClipperLib::pftEvenOdd; - expolygons_append(expolygons, union_ex(polygons, fill_type)); + bool is_fill_used = shape.fill.type != NSVG_PAINT_NONE; + bool is_stroke_used = + shape.stroke.type != NSVG_PAINT_NONE && + shape.strokeWidth > 1e-5f; + + if (!is_fill_used && !is_stroke_used) + continue; + + const LinesPath lines_path = linearize_path(shape.paths, param); + + if (is_fill_used) { + unsigned unique_id = static_cast(2 * shape_id); + result.push_back({unique_id, fill_to_expolygons(lines_path, shape)}); + } + if (is_stroke_used) { + unsigned unique_id = static_cast(2 * shape_id + 1); + result.push_back({unique_id, stroke_to_expolygons(lines_path, shape, param)}); + } } - return expolygons; + + // heal shapes + if (param.max_heal_iteration > 0) + for (auto &[id, expoly] : result) + Slic3r::Emboss::heal_shape(expoly, param.max_heal_iteration); + + // SVG is used as centered + // Do not disturb user by settings of pivot position + center(result); + return result; +} + +Polygons to_polygons(const NSVGimage &image, const NSVGLineParams ¶m) +{ + Polygons result; + 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; + const LinesPath lines_path = linearize_path(shape->paths, param); + polygons_append(result, lines_path.polygons); + // close polyline to create polygon + polygons_append(result, to_polygons(lines_path.polylines)); + } + return result; } void bounds(const NSVGimage &image, Vec2f& min, Vec2f &max) @@ -93,6 +131,14 @@ NSVGimage_ptr nsvgParse(const std::shared_ptr file_data, const char *uni return {image, ::nsvgDelete}; } +size_t get_shapes_count(const NSVGimage &image) +{ + size_t count = 0; + for (NSVGshape * s = image.shapes; s != NULL; s = s->next) + ++count; + return count; +} + void save(const NSVGimage &image, std::ostream &data) { data << ""; @@ -177,7 +223,7 @@ void save(const NSVGimage &image, std::ostream &data) } } if (type != Type::close) { - type = Type::close; + //type = Type::close; d += "Z"; // closed path } data << "\n"; @@ -198,18 +244,7 @@ bool save(const NSVGimage &image, const std::string &svg_file_path) namespace { 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)); } - - +Point::coord_type to_coor(float val, double 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 @@ -256,12 +291,12 @@ bool is_line(const float *p, float precision){ /// Curve point /// Curve point /// Actual depth of recursion -void flatten_cubic_bez(Polygon &polygon, float tessTol, const Vec2f& p1, const Vec2f& p2, const Vec2f& p3, const Vec2f& p4, int level) +void flatten_cubic_bez(Points &points, float tessTol, const Vec2f& p1, const Vec2f& p2, const Vec2f& p3, const Vec2f& p4, int level) { 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); + points.emplace_back(x, y); return; } @@ -275,60 +310,104 @@ 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); - flatten_cubic_bez(polygon, tessTol, p1234, p234, p34, p4, level); + flatten_cubic_bez(points, tessTol, p1, p12, p123, p1234, level); + flatten_cubic_bez(points, tessTol, p1234, p234, p34, p4, level); } -Polygons to_polygons(NSVGpath *first_path, float tessTol, int max_level, float scale) +LinesPath linearize_path(NSVGpath *first_path, const NSVGLineParams ¶m) { - Polygons polygons; - Polygon polygon; + LinesPath result; + Polygons &polygons = result.polygons; + Polylines &polylines = result.polylines; + + // multiple use of allocated memmory for points between paths + Points points; 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); + Point::coord_type x = to_coor(path->pts[0], param.scale); + Point::coord_type y = to_coor(path->pts[1], param.scale); + 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]; 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); + Point::coord_type xx = to_coor(p[6], param.scale); + Point::coord_type yy = to_coor(p[7], param.scale); + points.emplace_back(xx, yy); 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); + flatten_cubic_bez(points, param.tesselation_tolerance, + p1 * param.scale, p2 * param.scale, p3 * param.scale, p4 * param.scale, + param.max_level); } - if (path->closed && !polygon.empty()) { - polygons.push_back(polygon); - polygon = Polygon(); - } - } - if (!polygon.empty()) - polygons.push_back(polygon); + assert(!points.empty()); + if (points.empty()) + continue; - remove_same_neighbor(polygons); - 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) + if (param.is_y_negative) + for (Point &p : points) p.y() = -p.y(); - return polygons; + if (path->closed) { + polygons.emplace_back(points); + } else { + polylines.emplace_back(points); + } + // prepare for new path - recycle alocated memory + points.clear(); + } + remove_same_neighbor(polygons); + remove_same_neighbor(polylines); + return result; +} + +ExPolygons fill_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape) +{ + Polygons fill = lines_path.polygons; // copy + + // close polyline to create polygon + polygons_append(fill, to_polygons(lines_path.polylines)); + if (fill.empty()) + return {}; + + ClipperLib::PolyFillType fill_type = ClipperLib::pftNonZero; + // if (shape->fillRule == NSVGfillRule::NSVG_FILLRULE_NONZERO) + if (shape.fillRule == NSVGfillRule::NSVG_FILLRULE_EVENODD) + fill_type = ClipperLib::pftEvenOdd; + return union_ex(fill, fill_type); +} + +ExPolygons stroke_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams ¶m) +{ + // convert stroke to polygon + ClipperLib::JoinType join_type = ClipperLib::JoinType::jtSquare; + switch (static_cast(shape.strokeLineJoin)) { + case NSVGlineJoin::NSVG_JOIN_BEVEL: join_type = ClipperLib::JoinType::jtSquare; break; + case NSVGlineJoin::NSVG_JOIN_MITER: join_type = ClipperLib::JoinType::jtMiter; break; + case NSVGlineJoin::NSVG_JOIN_ROUND: join_type = ClipperLib::JoinType::jtRound; break; + } + + double mitter = shape.miterLimit * param.scale; + if (join_type == ClipperLib::JoinType::jtRound) { + // mitter is ArcTolerance + mitter = std::pow(param.tesselation_tolerance, 1/3.); + } + float stroke_width = static_cast(shape.strokeWidth * param.scale); + Polygons result = contour_to_polygons(lines_path.polygons, stroke_width, join_type, mitter); + + ClipperLib::EndType end_type = ClipperLib::EndType::etOpenButt; + switch (static_cast(shape.strokeLineCap)) { + case NSVGlineCap::NSVG_CAP_BUTT: end_type = ClipperLib::EndType::etOpenButt; break; + case NSVGlineCap::NSVG_CAP_ROUND: end_type = ClipperLib::EndType::etOpenRound; break; + case NSVGlineCap::NSVG_CAP_SQUARE: end_type = ClipperLib::EndType::etOpenSquare; break; + } + polygons_append(result, offset(lines_path.polylines, stroke_width / 2, join_type, mitter, end_type)); + return union_ex(result, ClipperLib::pftNonZero); } } // namespace \ No newline at end of file diff --git a/src/libslic3r/NSVGUtils.hpp b/src/libslic3r/NSVGUtils.hpp index 69f685874e..3523a70317 100644 --- a/src/libslic3r/NSVGUtils.hpp +++ b/src/libslic3r/NSVGUtils.hpp @@ -6,24 +6,58 @@ #include #include "Polygon.hpp" #include "ExPolygon.hpp" +#include "EmbossShape.hpp" // ExPolygonsWithIds #include "nanosvg/nanosvg.h" // load SVG file // Helper function to work with nano svg namespace Slic3r { /// -/// Convert .svg opened by nanoSvg to Polygons +/// Paramreters for conversion curve from SVG to lines in Polygon /// -/// Opened file -/// Tesselation tolerance +struct NSVGLineParams +{ + // Smaller will divide curve to more lines + // NOTE: Value is in image scale + double tesselation_tolerance = 10.f; + + // Maximal depth of recursion for conversion curve to lines + int max_level = 10; + + // Multiplicator of point coors + // NOTE: Every point coor from image(float) is multiplied by scale and rounded to integer --> Slic3r::Point + double scale = 1. / SCALING_FACTOR; + + // count of iteration to heal shape + unsigned max_heal_iteration = 10; + + // Flag wether y is negative, when true than y coor is multiplied by -1 + bool is_y_negative = true; + + // Is used only with rounded Stroke + double arc_tolerance = 1.; + + explicit NSVGLineParams(double tesselation_tolerance): + tesselation_tolerance(tesselation_tolerance), + arc_tolerance(std::pow(tesselation_tolerance, 1/3.)) + {} +}; + +/// +/// Convert .svg opened by nanoSvg to shapes stored in expolygons with ids +/// +/// Parsed svg file by NanoSvg +/// Smaller will divide curve to more lines /// NOTE: Value is in image scale -/// Maximal depth +/// Maximal depth for conversion curve to lines /// Multiplicator of point coors /// NOTE: Every point coor from image(float) is multiplied by scale and rounded to integer +/// Shapes from svg image - fill + stroke +ExPolygonsWithIds create_shape_with_ids(const NSVGimage &image, const NSVGLineParams ¶m); + +// help functions - prepare to be tested /// Flag is y negative, when true than y coor is multiplied by -1 -/// Polygons extracted from svg -Polygons to_polygons(const NSVGimage &image, float tessTol = 10., int max_level = 10, float scale = 1.f, bool is_y_negative = true); -ExPolygons to_expolygons(const NSVGimage &image, float tessTol = 10., int max_level = 10, float scale = 1.f, bool is_y_negative = true); +Polygons to_polygons(const NSVGimage &image, const NSVGLineParams ¶m); void bounds(const NSVGimage &image, Vec2f &min, Vec2f &max); @@ -34,6 +68,13 @@ using NSVGimage_ptr = std::unique_ptr; NSVGimage_ptr nsvgParseFromFile(const std::string &svg_file_path, const char *units = "mm", float dpi = 96.0f); NSVGimage_ptr nsvgParse(const std::shared_ptr file_data, const char *units = "mm", float dpi = 96.0f); +/// +/// Iterate over shapes and calculate count +/// +/// Contain pointer to first shape +/// Count of shapes +size_t get_shapes_count(const NSVGimage &image); + void save(const NSVGimage &image, std::ostream &data); bool save(const NSVGimage &image, const std::string &svg_file_path); } // namespace Slic3r diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp index 31074d246d..98219f2314 100644 --- a/src/libslic3r/Polygon.cpp +++ b/src/libslic3r/Polygon.cpp @@ -437,7 +437,7 @@ bool has_duplicate_points(const Polygons &polys) #endif } -bool remove_same_neighbor(Slic3r::Polygon &polygon) +bool remove_same_neighbor(Polygon &polygon) { Points &points = polygon.points; if (points.empty()) diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index a70c7f33b9..b9cf550225 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -110,7 +110,7 @@ inline bool has_duplicate_points(const Polygon &poly) { return has_duplicate_poi bool has_duplicate_points(const Polygons &polys); // Return True when erase some otherwise False. -bool remove_same_neighbor(Polygon &points); +bool remove_same_neighbor(Polygon &polygon); bool remove_same_neighbor(Polygons &polygons); inline double total_length(const Polygons &polylines) { @@ -247,6 +247,18 @@ inline Polylines to_polylines(Polygons &&polys) return polylines; } +// close polyline to polygon (connect first and last point in polyline) +inline Polygons to_polygons(const Polylines &polylines) +{ + Polygons out; + out.reserve(polylines.size()); + for (const Polyline &polyline : polylines) { + if (polyline.size()) + out.emplace_back(polyline.points); + } + return out; +} + inline Polygons to_polygons(const VecOfPoints &paths) { Polygons out; diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index 5261a8cfce..9a8b64e55a 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -200,6 +200,33 @@ BoundingBox get_extents(const Polylines &polylines) return bb; } +// Return True when erase some otherwise False. +bool remove_same_neighbor(Polyline &polyline) { + Points &points = polyline.points; + if (points.empty()) + return false; + auto last = std::unique(points.begin(), points.end()); + + // no duplicits + if (last == points.end()) + return false; + + points.erase(last, points.end()); + return true; +} + +bool remove_same_neighbor(Polylines &polylines){ + if (polylines.empty()) + return false; + bool exist = false; + for (Polyline &polyline : polylines) + exist |= remove_same_neighbor(polyline); + // remove empty polylines + polylines.erase(std::remove_if(polylines.begin(), polylines.end(), [](const Polyline &p) { return p.points.size() <= 1; }), polylines.end()); + return exist; +} + + const Point& leftmost_point(const Polylines &polylines) { if (polylines.empty()) diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index 0e6dcff041..194f8245ce 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -89,6 +89,10 @@ inline bool operator!=(const Polyline &lhs, const Polyline &rhs) { return lhs.po extern BoundingBox get_extents(const Polyline &polyline); extern BoundingBox get_extents(const Polylines &polylines); +// Return True when erase some otherwise False. +bool remove_same_neighbor(Polyline &polyline); +bool remove_same_neighbor(Polylines &polylines); + inline double total_length(const Polylines &polylines) { double total = 0; for (const Polyline &pl : polylines) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp index 8f40bea927..675e567ca9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp @@ -30,6 +30,7 @@ #include #include // measure enumeration of fonts +#include // save for svg using namespace Slic3r; using namespace Slic3r::Emboss; @@ -67,7 +68,6 @@ constexpr double get_tesselation_tolerance(double scale){ 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. @@ -143,8 +143,8 @@ const IconManager::Icon &get_icon(const IconManager::Icons &icons, IconType type struct GuiCfg { // Detect invalid config values when change monitor DPI - double screen_scale; - float main_toolbar_height; + double screen_scale = -1.; + float main_toolbar_height = -1.f; // Define bigger size(width or height) unsigned texture_max_size_px = 256; @@ -178,16 +178,6 @@ GuiCfg create_gui_configuration(); // use private definition struct GLGizmoSVG::GuiCfg: public ::GuiCfg{}; -namespace Slic3r { -BoundingBox get_extents(const ExPolygonsWithIds &expoly_ids) -{ - BoundingBox result; - for (const ExPolygonsWithId &expoly_id : expoly_ids) - result.merge(get_extents(expoly_id.expoly)); - return result; -} -} // namespace Slic3r - bool GLGizmoSVG::create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos) { CreateVolumeParams input = create_input(m_parent, m_raycast_manager, volume_type); @@ -585,8 +575,8 @@ NSVGimage* init_image(EmbossShape::SvgFile &svg_file) { return nullptr; // Disable stroke - for (NSVGshape *shape = svg_file.image->shapes; shape != NULL; shape = shape->next) - shape->stroke.type = 0; + //for (NSVGshape *shape = svg_file.image->shapes; shape != NULL; shape = shape->next) + // shape->stroke.type = 0; return svg_file.image.get(); } @@ -922,93 +912,92 @@ bool is_closed(NSVGpath *path){ return false; } -std::string create_shape_warnings(const NSVGimage& image){ - const float preccission = 1e-4f; - std::string warnings; - for (NSVGshape *shape = image.shapes; shape != NULL; shape = shape->next) { - std::string warning; - auto add_warning = [&warning](const std::string& w){ - if (!warning.empty()) - warning += ", "; - warning += w; - }; +void add_comma_separated(std::string &result, const std::string &add){ + if (!result.empty()) + result += ", "; + result += add; +} - if (shape->opacity <= preccission) - add_warning(GUI::format(_L("Opacity (%1%)"), shape->opacity)); +const float warning_preccission = 1e-4f; +std::string create_fill_warning(const NSVGshape &shape) { + std::string warning; + if (shape.opacity <= warning_preccission) + add_comma_separated(warning, GUI::format(_L("Opacity (%1%)"), shape.opacity)); - // if(shape->flags != NSVG_FLAGS_VISIBLE) add_warning(_u8L("Visibility flag")); - bool is_fill_gradient = shape->fillGradient[0] != '\0'; - if (is_fill_gradient) - add_warning(GUI::format(_L("Fill gradient (%1%)"), shape->fillGradient)); - bool is_stroke_gradient = shape->strokeGradient[0] != '\0'; - if (is_stroke_gradient) - add_warning(GUI::format(_L("Stroke gradient (%1%)"), shape->strokeGradient)); + // if(shape->flags != NSVG_FLAGS_VISIBLE) add_warning(_u8L("Visibility flag")); + bool is_fill_gradient = shape.fillGradient[0] != '\0'; + if (is_fill_gradient) + add_comma_separated(warning, GUI::format(_L("Fill gradient (%1%)"), shape.fillGradient)); - // identity matrix - nsvg__xformIdentity - //const std::array identity = {1.f, 0.f, 0.f, 1.f, 0.f, 0.f}; - //bool is_identity = true; - //for (size_t i = 0; i < 6; ++i) - // if (!is_approx(shape->xform[i], identity[i], preccission)){ - // is_identity = false; - // break; - // } - //if (!is_identity) { - // std::stringstream ss; - // for (size_t i = 0; i < 6; ++i) { - // if (i != 0) - // ss << ", "; - // ss << shape->xform[i]; - // } - // add_warning(GUI::format(_L("XForm(%1%)"), ss.str())); - //} + switch (shape.fill.type) { + case NSVG_PAINT_UNDEF: add_comma_separated(warning, _u8L("Undefined fill type")); break; + case NSVG_PAINT_LINEAR_GRADIENT: + if (!is_fill_gradient) + add_comma_separated(warning, _u8L("Linear fill gradient")); + break; + case NSVG_PAINT_RADIAL_GRADIENT: + if (!is_fill_gradient) + add_comma_separated(warning, _u8L("Radial fill gradient")); + break; + // case NSVG_PAINT_NONE: + // case NSVG_PAINT_COLOR: + // default: break; + } - switch (shape->fill.type){ - case NSVG_PAINT_UNDEF: - add_warning(_u8L("Undefined fill type")); - break; - case NSVG_PAINT_LINEAR_GRADIENT: - if (!is_fill_gradient) - add_warning(_u8L("Linear fill gradient")); - break; - case NSVG_PAINT_RADIAL_GRADIENT: - if (!is_fill_gradient) - add_warning(_u8L("Radial fill gradient")); - break; - case NSVG_PAINT_NONE: - case NSVG_PAINT_COLOR: - default: break; - } + // Unfilled is only line which could be opened + if (shape.fill.type != NSVG_PAINT_NONE && !is_closed(shape.paths)) + add_comma_separated(warning, _u8L("Open filled path")); + return warning; +} - // Unfilled is only line which could be opened - if (shape->fill.type != NSVG_PAINT_NONE && - !is_closed(shape->paths)) - add_warning(_u8L("Open filled path")); +std::string create_stroke_warning(const NSVGshape &shape) { + std::string warning; + bool is_stroke_gradient = shape.strokeGradient[0] != '\0'; + if (is_stroke_gradient) + add_comma_separated(warning, GUI::format(_L("Stroke gradient (%1%)"), shape.strokeGradient)); - switch (shape->stroke.type){ - case NSVG_PAINT_UNDEF: - add_warning(_u8L("Undefined stroke type")); - break; - case NSVG_PAINT_LINEAR_GRADIENT: - if (!is_stroke_gradient) - add_warning(_u8L("Linear stroke gradient")); - break; - case NSVG_PAINT_RADIAL_GRADIENT: - if (!is_stroke_gradient) - add_warning(_u8L("Radial stroke gradient")); - break; - case NSVG_PAINT_COLOR: - case NSVG_PAINT_NONE: - default: break; + switch (shape.stroke.type) { + case NSVG_PAINT_UNDEF: add_comma_separated(warning, _u8L("Undefined stroke type")); break; + case NSVG_PAINT_LINEAR_GRADIENT: + if (!is_stroke_gradient) + add_comma_separated(warning, _u8L("Linear stroke gradient")); + break; + case NSVG_PAINT_RADIAL_GRADIENT: + if (!is_stroke_gradient) + add_comma_separated(warning, _u8L("Radial stroke gradient")); + break; + // case NSVG_PAINT_COLOR: + // case NSVG_PAINT_NONE: + // default: break; + } + return warning; +} + +/// +/// Create warnings about shape +/// +/// Input svg loaded to shapes +/// Vector of warnings with same size as EmbossShape::shapes_with_ids +/// or Empty when no warnings -> for fast checking that every thing is all right(more common case) +std::vector create_shape_warnings(const NSVGimage &image){ + std::vector result; + size_t shape_index = 0; + for (NSVGshape *shape = image.shapes; shape != NULL; shape = shape->next, ++shape_index) { + std::string fill_warning = create_fill_warning(*shape); + if (!fill_warning.empty()) { + if (result.empty()) + result = std::vector(get_shapes_count(image) * 2); + result[shape_index * 2] = GUI::format(_L("Fill of shape(%1%) contain unsupported: %2% "), shape->id, fill_warning); } - if (!warning.empty()){ - if (!warnings.empty()) - warnings += "\n"; - - warnings += GUI::format(_L("Shape(%1%) contain unsupported: %2% "), shape->id, warning); + std::string stroke_warning = create_stroke_warning(*shape); + if (!stroke_warning.empty()) { + if (result.empty()) + result = std::vector(get_shapes_count(image) * 2); + result[shape_index * 2 + 1] = GUI::format(_L("Stroke of shape(%1%) contain unsupported: %2% "), shape->id, stroke_warning); } } - return warnings; + return result; } @@ -1055,8 +1044,8 @@ void GLGizmoSVG::set_volume_by_selection() NSVGimage* image = init_image(es.svg_file); assert(image != nullptr); if (image != nullptr){ - double tes_tol = get_tesselation_tolerance(std::max(m_scale_width.value_or(1.f), m_scale_height.value_or(1.f))); - shape_ids = create_shape_with_ids(*image, tes_tol); + NSVGLineParams params{get_tesselation_tolerance(std::max(m_scale_width.value_or(1.f), m_scale_height.value_or(1.f)))}; + shape_ids = create_shape_with_ids(*image, params); } } @@ -1203,13 +1192,6 @@ void GLGizmoSVG::draw_window() } } 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(); @@ -1255,14 +1237,12 @@ void GLGizmoSVG::draw_preview(){ ImGui::Image(id, s); if(ImGui::IsItemHovered()){ const EmbossShape &es = *m_volume->emboss_shape; - size_t count_shapes = ::count(*es.svg_file.image->shapes); + size_t count_shapes = get_shapes_count(*es.svg_file.image); ImGui::SetTooltip("%d count shapes", count_shapes); } } } -#include - void GLGizmoSVG::draw_filename(){ const EmbossShape &es = *m_volume->emboss_shape; if (m_filename_preview.empty()){ @@ -1283,8 +1263,17 @@ void GLGizmoSVG::draw_filename(){ if (!m_shape_warnings.empty()){ draw(get_icon(m_icons, IconType::exclamation)); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("%s", m_shape_warnings.c_str()); + if (ImGui::IsItemHovered()) { + std::string tooltip; + for (const std::string &w: m_shape_warnings){ + if (w.empty()) + continue; + if (!tooltip.empty()) + tooltip += "\n"; + tooltip += w; + } + ImGui::SetTooltip("%s", tooltip.c_str()); + } ImGui::SameLine(); } @@ -1595,8 +1584,8 @@ void GLGizmoSVG::draw_size() 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); + NSVGLineParams params{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, params); process(); } } @@ -1958,34 +1947,6 @@ std::string choose_svg_file() return path; } -void translate(ExPolygons &expolys, const Point &p) { - for (ExPolygon &expoly : expolys) - expoly.translate(p); -} - -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 {}; - - expoly = union_ex(expoly); - unsigned max_iteration = 10; - if (!Slic3r::Emboss::heal_shape(expoly, max_iteration)) - return {}; - - // SVG is used as centered - // Do not disturb user by settings of pivot position - BoundingBox bb = get_extents(expoly); - translate(expoly, -bb.center()); - - // Preparation for multi shape in svg - unsigned id = 0; - return {{id, expoly}}; -} - EmbossShape select_shape(std::string_view filepath, double tesselation_tolerance) { EmbossShape shape; @@ -2018,7 +1979,8 @@ EmbossShape select_shape(std::string_view filepath, double tesselation_tolerance } // Set default and unchanging scale - shape.shapes_with_ids = create_shape_with_ids(*shape.svg_file.image, tesselation_tolerance); + NSVGLineParams params{tesselation_tolerance}; + shape.shapes_with_ids = create_shape_with_ids(*shape.svg_file.image, params); // Must contain some shapes !!! if (shape.shapes_with_ids.empty()) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp index 36a849ecaf..88f2d079be 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp @@ -141,7 +141,7 @@ private: EmbossShape m_volume_shape; // copy from m_volume for edit // same index as volumes in - std::string m_shape_warnings; + std::vector m_shape_warnings; // When work with undo redo stack there could be situation that // m_volume point to unexisting volume so One need also objectID diff --git a/tests/libslic3r/test_emboss.cpp b/tests/libslic3r/test_emboss.cpp index 0c599b0ebf..1062e9e856 100644 --- a/tests/libslic3r/test_emboss.cpp +++ b/tests/libslic3r/test_emboss.cpp @@ -244,7 +244,7 @@ void scale(Polygons &polygons, double multiplicator) { Polygons load_polygons(const std::string &svg_file) { std::string file_path = TEST_DATA_DIR PATH_SEPARATOR + svg_file; NSVGimage *image = nsvgParseFromFile(file_path.c_str(), "px", 96.0f); - Polygons polygons = to_polygons(*image); + Polygons polygons = to_polygons(*image, NSVGLineParams{1000}); nsvgDelete(image); return polygons; } @@ -289,7 +289,7 @@ TEST_CASE("Heal of points close to line", "[Emboss]") std::string file_name = "points_close_to_line.svg"; std::string file_path = TEST_DATA_DIR PATH_SEPARATOR + file_name; NSVGimage *image = nsvgParseFromFile(file_path.c_str(), "px", 96.0f); - Polygons polygons = to_polygons(*image); + Polygons polygons = to_polygons(*image, NSVGLineParams{1000}); nsvgDelete(image); REQUIRE(polygons.size() == 1); Polygon polygon = polygons.front();