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.
// Don't calculate union of the output paths.
template<typename PathsProvider, ClipperLib::EndType endType = ClipperLib::etClosedPolygon>
static ClipperLib::Paths raw_offset(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
template<typename PathsProvider>
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<ClipperLib::PolyTree>(input, do_union ? ClipperLib::pftNonZero : ClipperLib::pftEvenOdd));
}
template<typename PathsProvider, ClipperLib::EndType endType = ClipperLib::etClosedPolygon>
static ClipperLib::Paths raw_offset_polyline(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
template<typename PathsProvider>
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<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>
@ -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<ClipperLib::PolyTree>(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<ClipperLib::Paths>(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<ClipperLib::Paths>(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<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, ClipperLib::EndType end_type)
{ 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).
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 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); }

View File

@ -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<bool()>& was_canceled)
{
std::wstring text_w = boost::nowide::widen(text);

View File

@ -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_

View File

@ -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);

View File

@ -3,39 +3,77 @@
#include <charconv> // 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 &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 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;
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<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)
@ -93,6 +131,14 @@ NSVGimage_ptr nsvgParse(const std::shared_ptr<char[]> 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 << "<?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) {
type = Type::close;
//type = Type::close;
d += "Z"; // closed path
}
data << "<path fill=\"#D2D2D2\" d=\"" << d << "\" />\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<Point::coord_type>(std::round(val * scale)); }
Point::coord_type to_coor(float val, double 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) {
// f .. first
@ -256,12 +291,12 @@ bool is_line(const float *p, float precision){
/// <param name="p3">Curve point</param>
/// <param name="p4">Curve point</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)) {
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()));
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 &param)
{
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<size_t>(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 &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

View File

@ -6,24 +6,58 @@
#include <sstream>
#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 {
/// <summary>
/// Convert .svg opened by nanoSvg to Polygons
/// Paramreters for conversion curve from SVG to lines in Polygon
/// </summary>
/// <param name="image">Opened file</param>
/// <param name="tessTol">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.))
{}
};
/// <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>
/// <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
/// 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>
/// <returns>Polygons extracted from svg</returns>
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 &param);
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 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);
bool save(const NSVGimage &image, const std::string &svg_file_path);
} // namespace Slic3r

View File

@ -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())

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);
// 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;

View File

@ -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())

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 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)

View File

@ -30,6 +30,7 @@
#include <GL/glew.h>
#include <chrono> // measure enumeration of fonts
#include <sstream> // 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);
/// <summary>
/// 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<float, 6> 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;
}
/// <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()){
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<std::string>(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 <sstream>
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()) {

View File

@ -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<std::string> 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

View File

@ -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();