Emboss Stroke(contour of paths from svg)

Do not support yet:
 - Markers(start sybol, end symbol, middle symbols)
 - Dashes
This commit is contained in:
Filip Sykala - NTB T15p 2023-08-31 16:41:21 +02:00
parent f5d5ebe418
commit 96618d684f
14 changed files with 402 additions and 234 deletions

View File

@ -271,8 +271,8 @@ bool has_duplicate_points(const ClipperLib::PolyTree &polytree)
// Offset CCW contours outside, CW contours (holes) inside. // Offset CCW contours outside, CW contours (holes) inside.
// Don't calculate union of the output paths. // Don't calculate union of the output paths.
template<typename PathsProvider, ClipperLib::EndType endType = ClipperLib::etClosedPolygon> template<typename PathsProvider>
static ClipperLib::Paths raw_offset(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit) 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); 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<ClipperLib::PolyTree>(input, do_union ? ClipperLib::pftNonZero : ClipperLib::pftEvenOdd)); return PolyTreeToExPolygons(clipper_union<ClipperLib::PolyTree>(input, do_union ? ClipperLib::pftNonZero : ClipperLib::pftEvenOdd));
} }
template<typename PathsProvider, ClipperLib::EndType endType = ClipperLib::etClosedPolygon> template<typename PathsProvider>
static ClipperLib::Paths raw_offset_polyline(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit) static ClipperLib::Paths raw_offset_polyline(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::EndType end_type = ClipperLib::etOpenButt)
{ {
assert(offset > 0); assert(offset > 0);
return raw_offset<PathsProvider, ClipperLib::etOpenButt>(std::forward<PathsProvider>(paths), offset, joinType, miterLimit); return raw_offset<PathsProvider>(std::forward<PathsProvider>(paths), offset, joinType, miterLimit, end_type);
} }
template<class TResult, typename PathsProvider> template<class TResult, typename PathsProvider>
@ -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) Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ return PolyTreeToExPolygons(offset_paths<ClipperLib::PolyTree>(ClipperUtils::PolygonsProvider(polygons), delta, joinType, miterLimit)); } { return PolyTreeToExPolygons(offset_paths<ClipperLib::PolyTree>(ClipperUtils::PolygonsProvider(polygons), delta, joinType, miterLimit)); }
Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType, double 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<ClipperLib::Paths>(raw_offset_polyline(ClipperUtils::SinglePathProvider(polyline.points), delta, joinType, miterLimit))); } { assert(delta > 0); return to_polygons(clipper_union<ClipperLib::Paths>(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) 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<ClipperLib::Paths>(raw_offset_polyline(ClipperUtils::PolylinesProvider(polylines), delta, joinType, miterLimit))); } { assert(delta > 0); return to_polygons(clipper_union<ClipperLib::Paths>(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<ClipperLib::Paths>(
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<ClipperLib::Paths>(
raw_offset(ClipperUtils::PolygonsProvider(polygons), line_width/2, join_type, miter_limit, ClipperLib::etClosedLine)));}
// returns number of expolygons collected (0 or 1). // 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) static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::Paths &out)

View File

@ -29,6 +29,9 @@ class BoundingBox;
static constexpr const float ClipperSafetyOffset = 10.f; static constexpr const float ClipperSafetyOffset = 10.f;
static constexpr const Slic3r::ClipperLib::JoinType DefaultJoinType = Slic3r::ClipperLib::jtMiter; 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. //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. // 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 // 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 // offset Polylines
// Wherever applicable, please use the expand() / shrink() variants instead, they convey their purpose better. // 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. // 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::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); 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::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::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); 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::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); 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::Polygons &polygons) { return offset (polygons, ClipperSafetyOffset); }
inline Slic3r::Polygons union_safety_offset (const Slic3r::ExPolygons &expolygons) { return offset (expolygons, 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); } inline Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons) { return offset_ex(polygons, ClipperSafetyOffset); }

View File

@ -1223,7 +1223,6 @@ ExPolygons letter2shapes(
const int CANCEL_CHECK = 10; const int CANCEL_CHECK = 10;
} // namespace } // namespace
/// Union shape defined by glyphs /// Union shape defined by glyphs
ExPolygons Slic3r::union_ex(const ExPolygonsWithIds &shapes) ExPolygons Slic3r::union_ex(const ExPolygonsWithIds &shapes)
{ {
@ -1239,6 +1238,26 @@ ExPolygons Slic3r::union_ex(const ExPolygonsWithIds &shapes)
return result; 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<bool()>& was_canceled) ExPolygons Emboss::text2shapes(FontFileWithCache &font_with_cache, const char *text, const FontProp &font_prop, const std::function<bool()>& was_canceled)
{ {
std::wstring text_w = boost::nowide::widen(text); std::wstring text_w = boost::nowide::widen(text);

View File

@ -458,7 +458,12 @@ namespace Emboss
} // 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); ExPolygons union_ex(const ExPolygonsWithIds &shapes);
} // namespace Slic3r } // namespace Slic3r
#endif // slic3r_Emboss_hpp_ #endif // slic3r_Emboss_hpp_

View File

@ -372,6 +372,11 @@ inline Points to_points(const ExPolygon &expoly)
return out; 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) inline void polygons_append(Polygons &dst, const ExPolygon &src)
{ {
dst.reserve(dst.size() + src.holes.size() + 1); dst.reserve(dst.size() + src.holes.size() + 1);

View File

@ -3,39 +3,77 @@
#include <charconv> // to_chars #include <charconv> // to_chars
#include "ClipperUtils.hpp" #include "ClipperUtils.hpp"
#include "Emboss.hpp" // heal for shape
namespace { namespace {
Slic3r::Polygons to_polygons(const NSVGshape &shape, float tessTol, int max_level, float scale, bool is_y_negative); using namespace Slic3r; // Polygon
// see function nsvg__lineTo(NSVGparser* p, float x, float y) // 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 &param);
ExPolygons fill_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape);
ExPolygons stroke_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams &param);
} // namespace } // namespace
namespace Slic3r { 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 &param)
{ {
Polygons polygons; ExPolygonsWithIds result;
for (NSVGshape *shape = image.shapes; shape != NULL; shape = shape->next) size_t shape_id = 0;
polygons_append(polygons, ::to_polygons(*shape, tessTol, max_level, scale, is_y_negative)); for (NSVGshape *shape_ptr = image.shapes; shape_ptr != NULL; shape_ptr = shape_ptr->next, ++shape_id) {
return polygons; const NSVGshape &shape = *shape_ptr;
} if (!(shape.flags & NSVG_FLAGS_VISIBLE))
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())
continue; continue;
ClipperLib::PolyFillType fill_type = ClipperLib::pftNonZero; bool is_fill_used = shape.fill.type != NSVG_PAINT_NONE;
//if (shape->fillRule == NSVGfillRule::NSVG_FILLRULE_NONZERO) bool is_stroke_used =
if (shape->fillRule == NSVGfillRule::NSVG_FILLRULE_EVENODD) shape.stroke.type != NSVG_PAINT_NONE &&
fill_type = ClipperLib::pftEvenOdd; shape.strokeWidth > 1e-5f;
expolygons_append(expolygons, union_ex(polygons, fill_type));
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<unsigned>(2 * shape_id);
result.push_back({unique_id, fill_to_expolygons(lines_path, shape)});
}
if (is_stroke_used) {
unsigned unique_id = static_cast<unsigned>(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 &param)
{
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) void bounds(const NSVGimage &image, Vec2f& min, Vec2f &max)
@ -93,6 +131,14 @@ NSVGimage_ptr nsvgParse(const std::shared_ptr<char[]> file_data, const char *uni
return {image, ::nsvgDelete}; 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) void save(const NSVGimage &image, std::ostream &data)
{ {
data << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>"; data << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>";
@ -177,7 +223,7 @@ void save(const NSVGimage &image, std::ostream &data)
} }
} }
if (type != Type::close) { if (type != Type::close) {
type = Type::close; //type = Type::close;
d += "Z"; // closed path d += "Z"; // closed path
} }
data << "<path fill=\"#D2D2D2\" d=\"" << d << "\" />\n"; data << "<path fill=\"#D2D2D2\" d=\"" << d << "\" />\n";
@ -198,18 +244,7 @@ bool save(const NSVGimage &image, const std::string &svg_file_path)
namespace { namespace {
using namespace Slic3r; // Polygon + Vec2f using namespace Slic3r; // Polygon + Vec2f
bool is_useable(const NSVGshape &shape) Point::coord_type to_coor(float val, double scale) { return static_cast<Point::coord_type>(std::round(val * scale)); }
{
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<Point::coord_type>(std::round(val * scale)); }
bool need_flattening(float tessTol, const Vec2f &p1, const Vec2f &p2, const Vec2f &p3, const Vec2f &p4) { bool need_flattening(float tessTol, const Vec2f &p1, const Vec2f &p2, const Vec2f &p3, const Vec2f &p4) {
// f .. first // f .. first
@ -256,12 +291,12 @@ bool is_line(const float *p, float precision){
/// <param name="p3">Curve point</param> /// <param name="p3">Curve point</param>
/// <param name="p4">Curve point</param> /// <param name="p4">Curve point</param>
/// <param name="level">Actual depth of recursion</param> /// <param name="level">Actual depth of recursion</param>
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)) { if (!need_flattening(tessTol, p1, p2, p3, p4)) {
Point::coord_type x = static_cast<Point::coord_type>(std::round(p4.x())); Point::coord_type x = static_cast<Point::coord_type>(std::round(p4.x()));
Point::coord_type y = static_cast<Point::coord_type>(std::round(p4.y())); Point::coord_type y = static_cast<Point::coord_type>(std::round(p4.y()));
polygon.points.emplace_back(x, y); points.emplace_back(x, y);
return; 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 p123 = (p12 + p23) * 0.5f;
Vec2f p234 = (p23 + p34) * 0.5f; Vec2f p234 = (p23 + p34) * 0.5f;
Vec2f p1234 = (p123 + p234) * 0.5f; Vec2f p1234 = (p123 + p234) * 0.5f;
flatten_cubic_bez(polygon, tessTol, p1, p12, p123, p1234, level); flatten_cubic_bez(points, tessTol, p1, p12, p123, p1234, level);
flatten_cubic_bez(polygon, tessTol, p1234, p234, p34, p4, 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 &param)
{ {
Polygons polygons; LinesPath result;
Polygon polygon; 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) { for (NSVGpath *path = first_path; path != NULL; path = path->next) {
// Flatten path // Flatten path
Point::coord_type x = to_coor(path->pts[0], scale); Point::coord_type x = to_coor(path->pts[0], param.scale);
Point::coord_type y = to_coor(path->pts[1], scale); Point::coord_type y = to_coor(path->pts[1], param.scale);
polygon.points.emplace_back(x, y); points.emplace_back(x, y);
size_t path_size = (path->npts > 1) ? static_cast<size_t>(path->npts - 1) : 0; size_t path_size = (path->npts > 1) ? static_cast<size_t>(path->npts - 1) : 0;
for (size_t i = 0; i < path_size; i += 3) { for (size_t i = 0; i < path_size; i += 3) {
const float *p = &path->pts[i * 2]; const float *p = &path->pts[i * 2];
if (is_line(p)) { if (is_line(p)) {
// point p4 // point p4
Point::coord_type x = to_coor(p[6], scale); Point::coord_type xx = to_coor(p[6], param.scale);
Point::coord_type y = to_coor(p[7], scale); Point::coord_type yy = to_coor(p[7], param.scale);
polygon.points.emplace_back(x, y); points.emplace_back(xx, yy);
continue; continue;
} }
Vec2f p1(p[0], p[1]); Vec2f p1(p[0], p[1]);
Vec2f p2(p[2], p[3]); Vec2f p2(p[2], p[3]);
Vec2f p3(p[4], p[5]); Vec2f p3(p[4], p[5]);
Vec2f p4(p[6], p[7]); 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()) { assert(!points.empty());
polygons.push_back(polygon); if (points.empty())
polygon = Polygon(); continue;
}
}
if (!polygon.empty())
polygons.push_back(polygon);
remove_same_neighbor(polygons); if (param.is_y_negative)
return polygons; for (Point &p : points)
}
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(); 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 &param)
{
// convert stroke to polygon
ClipperLib::JoinType join_type = ClipperLib::JoinType::jtSquare;
switch (static_cast<NSVGlineJoin>(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<float>(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<NSVGlineCap>(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 } // namespace

View File

@ -6,24 +6,58 @@
#include <sstream> #include <sstream>
#include "Polygon.hpp" #include "Polygon.hpp"
#include "ExPolygon.hpp" #include "ExPolygon.hpp"
#include "EmbossShape.hpp" // ExPolygonsWithIds
#include "nanosvg/nanosvg.h" // load SVG file #include "nanosvg/nanosvg.h" // load SVG file
// Helper function to work with nano svg // Helper function to work with nano svg
namespace Slic3r { namespace Slic3r {
/// <summary> /// <summary>
/// Convert .svg opened by nanoSvg to Polygons /// Paramreters for conversion curve from SVG to lines in Polygon
/// </summary> /// </summary>
/// <param name="image">Opened file</param> struct NSVGLineParams
/// <param name="tessTol">Tesselation tolerance {
// 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.))
{}
};
/// <summary>
/// Convert .svg opened by nanoSvg to shapes stored in expolygons with ids
/// </summary>
/// <param name="image">Parsed svg file by NanoSvg</param>
/// <param name="tesselation_tolerance">Smaller will divide curve to more lines
/// NOTE: Value is in image scale</param> /// NOTE: Value is in image scale</param>
/// <param name="max_level">Maximal depth</param> /// <param name="max_level">Maximal depth for conversion curve to lines</param>
/// <param name="scale">Multiplicator of point coors /// <param name="scale">Multiplicator of point coors
/// NOTE: Every point coor from image(float) is multiplied by scale and rounded to integer</param> /// NOTE: Every point coor from image(float) is multiplied by scale and rounded to integer</param>
/// <returns>Shapes from svg image - fill + stroke</returns>
ExPolygonsWithIds create_shape_with_ids(const NSVGimage &image, const NSVGLineParams &param);
// help functions - prepare to be tested
/// <param name="is_y_negative">Flag is y negative, when true than y coor is multiplied by -1</param> /// <param name="is_y_negative">Flag is y negative, when true than y coor is multiplied by -1</param>
/// <returns>Polygons extracted from svg</returns> Polygons to_polygons(const NSVGimage &image, const NSVGLineParams &param);
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);
void bounds(const NSVGimage &image, Vec2f &min, Vec2f &max); void bounds(const NSVGimage &image, Vec2f &min, Vec2f &max);
@ -34,6 +68,13 @@ using NSVGimage_ptr = std::unique_ptr<NSVGimage, void (*)(NSVGimage*)>;
NSVGimage_ptr nsvgParseFromFile(const std::string &svg_file_path, 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);
NSVGimage_ptr nsvgParse(const std::shared_ptr<char[]> file_data, const char *units = "mm", float dpi = 96.0f); NSVGimage_ptr nsvgParse(const std::shared_ptr<char[]> file_data, const char *units = "mm", float dpi = 96.0f);
/// <summary>
/// Iterate over shapes and calculate count
/// </summary>
/// <param name="image">Contain pointer to first shape</param>
/// <returns>Count of shapes</returns>
size_t get_shapes_count(const NSVGimage &image);
void save(const NSVGimage &image, std::ostream &data); void save(const NSVGimage &image, std::ostream &data);
bool save(const NSVGimage &image, const std::string &svg_file_path); bool save(const NSVGimage &image, const std::string &svg_file_path);
} // namespace Slic3r } // namespace Slic3r

View File

@ -437,7 +437,7 @@ bool has_duplicate_points(const Polygons &polys)
#endif #endif
} }
bool remove_same_neighbor(Slic3r::Polygon &polygon) bool remove_same_neighbor(Polygon &polygon)
{ {
Points &points = polygon.points; Points &points = polygon.points;
if (points.empty()) if (points.empty())

View File

@ -110,7 +110,7 @@ inline bool has_duplicate_points(const Polygon &poly) { return has_duplicate_poi
bool has_duplicate_points(const Polygons &polys); bool has_duplicate_points(const Polygons &polys);
// Return True when erase some otherwise False. // 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); bool remove_same_neighbor(Polygons &polygons);
inline double total_length(const Polygons &polylines) { inline double total_length(const Polygons &polylines) {
@ -247,6 +247,18 @@ inline Polylines to_polylines(Polygons &&polys)
return polylines; 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) inline Polygons to_polygons(const VecOfPoints &paths)
{ {
Polygons out; Polygons out;

View File

@ -200,6 +200,33 @@ BoundingBox get_extents(const Polylines &polylines)
return bb; 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) const Point& leftmost_point(const Polylines &polylines)
{ {
if (polylines.empty()) if (polylines.empty())

View File

@ -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 Polyline &polyline);
extern BoundingBox get_extents(const Polylines &polylines); 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) { inline double total_length(const Polylines &polylines) {
double total = 0; double total = 0;
for (const Polyline &pl : polylines) for (const Polyline &pl : polylines)

View File

@ -30,6 +30,7 @@
#include <GL/glew.h> #include <GL/glew.h>
#include <chrono> // measure enumeration of fonts #include <chrono> // measure enumeration of fonts
#include <sstream> // save for svg
using namespace Slic3r; using namespace Slic3r;
using namespace Slic3r::Emboss; 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; constexpr double tesselation_tolerance_scaled = (tesselation_tolerance_in_mm*tesselation_tolerance_in_mm) / SCALING_FACTOR / SCALING_FACTOR;
return tesselation_tolerance_scaled / scale / scale; return tesselation_tolerance_scaled / scale / scale;
} }
ExPolygonsWithIds create_shape_with_ids(const NSVGimage &image, double tesselation_tolerance);
/// <summary> /// <summary>
/// Let user to choose file with (S)calable (V)ector (G)raphics - SVG. /// 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 struct GuiCfg
{ {
// Detect invalid config values when change monitor DPI // Detect invalid config values when change monitor DPI
double screen_scale; double screen_scale = -1.;
float main_toolbar_height; float main_toolbar_height = -1.f;
// Define bigger size(width or height) // Define bigger size(width or height)
unsigned texture_max_size_px = 256; unsigned texture_max_size_px = 256;
@ -178,16 +178,6 @@ GuiCfg create_gui_configuration();
// use private definition // use private definition
struct GLGizmoSVG::GuiCfg: public ::GuiCfg{}; 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) bool GLGizmoSVG::create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos)
{ {
CreateVolumeParams input = create_input(m_parent, m_raycast_manager, volume_type); CreateVolumeParams input = create_input(m_parent, m_raycast_manager, volume_type);
@ -585,8 +575,8 @@ NSVGimage* init_image(EmbossShape::SvgFile &svg_file) {
return nullptr; return nullptr;
// Disable stroke // Disable stroke
for (NSVGshape *shape = svg_file.image->shapes; shape != NULL; shape = shape->next) //for (NSVGshape *shape = svg_file.image->shapes; shape != NULL; shape = shape->next)
shape->stroke.type = 0; // shape->stroke.type = 0;
return svg_file.image.get(); return svg_file.image.get();
} }
@ -922,93 +912,92 @@ bool is_closed(NSVGpath *path){
return false; return false;
} }
std::string create_shape_warnings(const NSVGimage& image){ void add_comma_separated(std::string &result, const std::string &add){
const float preccission = 1e-4f; if (!result.empty())
std::string warnings; result += ", ";
for (NSVGshape *shape = image.shapes; shape != NULL; shape = shape->next) { result += add;
std::string warning; }
auto add_warning = [&warning](const std::string& w){
if (!warning.empty())
warning += ", ";
warning += w;
};
if (shape->opacity <= preccission) const float warning_preccission = 1e-4f;
add_warning(GUI::format(_L("Opacity (%1%)"), shape->opacity)); 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")); // if(shape->flags != NSVG_FLAGS_VISIBLE) add_warning(_u8L("Visibility flag"));
bool is_fill_gradient = shape->fillGradient[0] != '\0'; bool is_fill_gradient = shape.fillGradient[0] != '\0';
if (is_fill_gradient) if (is_fill_gradient)
add_warning(GUI::format(_L("Fill gradient (%1%)"), shape->fillGradient)); add_comma_separated(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));
// identity matrix - nsvg__xformIdentity switch (shape.fill.type) {
//const std::array<float, 6> identity = {1.f, 0.f, 0.f, 1.f, 0.f, 0.f}; case NSVG_PAINT_UNDEF: add_comma_separated(warning, _u8L("Undefined fill type")); break;
//bool is_identity = true; case NSVG_PAINT_LINEAR_GRADIENT:
//for (size_t i = 0; i < 6; ++i) if (!is_fill_gradient)
// if (!is_approx(shape->xform[i], identity[i], preccission)){ add_comma_separated(warning, _u8L("Linear fill gradient"));
// is_identity = false; break;
// break; case NSVG_PAINT_RADIAL_GRADIENT:
// } if (!is_fill_gradient)
//if (!is_identity) { add_comma_separated(warning, _u8L("Radial fill gradient"));
// std::stringstream ss; break;
// for (size_t i = 0; i < 6; ++i) { // case NSVG_PAINT_NONE:
// if (i != 0) // case NSVG_PAINT_COLOR:
// ss << ", "; // default: break;
// ss << shape->xform[i]; }
// }
// add_warning(GUI::format(_L("XForm(%1%)"), ss.str()));
//}
switch (shape->fill.type){ // Unfilled is only line which could be opened
case NSVG_PAINT_UNDEF: if (shape.fill.type != NSVG_PAINT_NONE && !is_closed(shape.paths))
add_warning(_u8L("Undefined fill type")); add_comma_separated(warning, _u8L("Open filled path"));
break; return warning;
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 std::string create_stroke_warning(const NSVGshape &shape) {
if (shape->fill.type != NSVG_PAINT_NONE && std::string warning;
!is_closed(shape->paths)) bool is_stroke_gradient = shape.strokeGradient[0] != '\0';
add_warning(_u8L("Open filled path")); if (is_stroke_gradient)
add_comma_separated(warning, GUI::format(_L("Stroke gradient (%1%)"), shape.strokeGradient));
switch (shape->stroke.type){ switch (shape.stroke.type) {
case NSVG_PAINT_UNDEF: case NSVG_PAINT_UNDEF: add_comma_separated(warning, _u8L("Undefined stroke type")); break;
add_warning(_u8L("Undefined stroke type")); case NSVG_PAINT_LINEAR_GRADIENT:
break; if (!is_stroke_gradient)
case NSVG_PAINT_LINEAR_GRADIENT: add_comma_separated(warning, _u8L("Linear stroke gradient"));
if (!is_stroke_gradient) break;
add_warning(_u8L("Linear stroke gradient")); case NSVG_PAINT_RADIAL_GRADIENT:
break; if (!is_stroke_gradient)
case NSVG_PAINT_RADIAL_GRADIENT: add_comma_separated(warning, _u8L("Radial stroke gradient"));
if (!is_stroke_gradient) break;
add_warning(_u8L("Radial stroke gradient")); // case NSVG_PAINT_COLOR:
break; // case NSVG_PAINT_NONE:
case NSVG_PAINT_COLOR: // default: break;
case NSVG_PAINT_NONE: }
default: break; return warning;
}
/// <summary>
/// Create warnings about shape
/// </summary>
/// <param name="image">Input svg loaded to shapes</param>
/// <returns>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) </returns>
std::vector<std::string> create_shape_warnings(const NSVGimage &image){
std::vector<std::string> 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<std::string>(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()){ std::string stroke_warning = create_stroke_warning(*shape);
if (!warnings.empty()) if (!stroke_warning.empty()) {
warnings += "\n"; if (result.empty())
result = std::vector<std::string>(get_shapes_count(image) * 2);
warnings += GUI::format(_L("Shape(%1%) contain unsupported: %2% "), shape->id, warning); 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); NSVGimage* image = init_image(es.svg_file);
assert(image != nullptr); assert(image != nullptr);
if (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))); 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, tes_tol); shape_ids = create_shape_with_ids(*image, params);
} }
} }
@ -1203,13 +1192,6 @@ void GLGizmoSVG::draw_window()
} }
} }
namespace { 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) void draw(const ExPolygonsWithIds& shapes_with_ids, unsigned max_size)
{ {
ImVec2 actual_pos = ImGui::GetCursorPos(); ImVec2 actual_pos = ImGui::GetCursorPos();
@ -1255,14 +1237,12 @@ void GLGizmoSVG::draw_preview(){
ImGui::Image(id, s); ImGui::Image(id, s);
if(ImGui::IsItemHovered()){ if(ImGui::IsItemHovered()){
const EmbossShape &es = *m_volume->emboss_shape; 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); ImGui::SetTooltip("%d count shapes", count_shapes);
} }
} }
} }
#include <sstream>
void GLGizmoSVG::draw_filename(){ void GLGizmoSVG::draw_filename(){
const EmbossShape &es = *m_volume->emboss_shape; const EmbossShape &es = *m_volume->emboss_shape;
if (m_filename_preview.empty()){ if (m_filename_preview.empty()){
@ -1283,8 +1263,17 @@ void GLGizmoSVG::draw_filename(){
if (!m_shape_warnings.empty()){ if (!m_shape_warnings.empty()){
draw(get_icon(m_icons, IconType::exclamation)); draw(get_icon(m_icons, IconType::exclamation));
if (ImGui::IsItemHovered()) if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s", m_shape_warnings.c_str()); 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(); ImGui::SameLine();
} }
@ -1595,8 +1584,8 @@ void GLGizmoSVG::draw_size()
NSVGimage *img = m_volume_shape.svg_file.image.get(); NSVGimage *img = m_volume_shape.svg_file.image.get();
assert(img != NULL); assert(img != NULL);
if (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))); 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, tes_tol); m_volume_shape.shapes_with_ids = create_shape_with_ids(*img, params);
process(); process();
} }
} }
@ -1958,34 +1947,6 @@ std::string choose_svg_file()
return path; 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 select_shape(std::string_view filepath, double tesselation_tolerance)
{ {
EmbossShape shape; EmbossShape shape;
@ -2018,7 +1979,8 @@ EmbossShape select_shape(std::string_view filepath, double tesselation_tolerance
} }
// Set default and unchanging scale // 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 !!! // Must contain some shapes !!!
if (shape.shapes_with_ids.empty()) { if (shape.shapes_with_ids.empty()) {

View File

@ -141,7 +141,7 @@ private:
EmbossShape m_volume_shape; // copy from m_volume for edit EmbossShape m_volume_shape; // copy from m_volume for edit
// same index as volumes in // same index as volumes in
std::string m_shape_warnings; std::vector<std::string> m_shape_warnings;
// When work with undo redo stack there could be situation that // When work with undo redo stack there could be situation that
// m_volume point to unexisting volume so One need also objectID // m_volume point to unexisting volume so One need also objectID

View File

@ -244,7 +244,7 @@ void scale(Polygons &polygons, double multiplicator) {
Polygons load_polygons(const std::string &svg_file) { Polygons load_polygons(const std::string &svg_file) {
std::string file_path = TEST_DATA_DIR PATH_SEPARATOR + svg_file; std::string file_path = TEST_DATA_DIR PATH_SEPARATOR + svg_file;
NSVGimage *image = nsvgParseFromFile(file_path.c_str(), "px", 96.0f); NSVGimage *image = nsvgParseFromFile(file_path.c_str(), "px", 96.0f);
Polygons polygons = to_polygons(*image); Polygons polygons = to_polygons(*image, NSVGLineParams{1000});
nsvgDelete(image); nsvgDelete(image);
return polygons; 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_name = "points_close_to_line.svg";
std::string file_path = TEST_DATA_DIR PATH_SEPARATOR + file_name; std::string file_path = TEST_DATA_DIR PATH_SEPARATOR + file_name;
NSVGimage *image = nsvgParseFromFile(file_path.c_str(), "px", 96.0f); NSVGimage *image = nsvgParseFromFile(file_path.c_str(), "px", 96.0f);
Polygons polygons = to_polygons(*image); Polygons polygons = to_polygons(*image, NSVGLineParams{1000});
nsvgDelete(image); nsvgDelete(image);
REQUIRE(polygons.size() == 1); REQUIRE(polygons.size() == 1);
Polygon polygon = polygons.front(); Polygon polygon = polygons.front();