mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-14 09:46:05 +08:00
Emboss Stroke(contour of paths from svg)
Do not support yet: - Markers(start sybol, end symbol, middle symbols) - Dashes
This commit is contained in:
parent
f5d5ebe418
commit
96618d684f
@ -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)
|
||||
|
@ -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); }
|
||||
|
@ -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);
|
||||
|
@ -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_
|
||||
|
@ -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);
|
||||
|
@ -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 ¶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<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 ¶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<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 ¶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<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 ¶m)
|
||||
{
|
||||
// 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
|
@ -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 ¶m);
|
||||
|
||||
// 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 ¶m);
|
||||
|
||||
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
|
||||
|
@ -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())
|
||||
|
@ -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;
|
||||
|
@ -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())
|
||||
|
@ -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)
|
||||
|
@ -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()) {
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user