diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 085cae66cb..87849b67da 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -146,6 +146,8 @@ set(SLIC3R_SOURCES Format/AnycubicSLA.cpp Format/STEP.hpp Format/STEP.cpp + Format/SVG.hpp + Format/SVG.cpp Format/SLAArchiveFormatRegistry.hpp Format/SLAArchiveFormatRegistry.cpp GCode/ThumbnailData.cpp diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index 4ae153f847..baf6fe72ff 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -421,7 +421,10 @@ HealedExPolygons Emboss::heal_polygons(const Polygons &shape, bool is_non_zero, Polygons polygons = to_polygons(paths); polygons.erase(std::remove_if(polygons.begin(), polygons.end(), [](const Polygon &p) { return p.size() < 3; }), polygons.end()); - + + if (polygons.empty()) + return {{}, false}; + // Do not remove all duplicates but do it better way // Overlap all duplicit points by rectangle 3x3 Points duplicits = collect_duplicates(to_points(polygons)); @@ -1241,23 +1244,8 @@ ExPolygons letter2shapes( const int CANCEL_CHECK = 10; } // namespace -/// Union shape defined by glyphs -HealedExPolygons Slic3r::union_ex(const ExPolygonsWithIds &shapes, unsigned max_heal_iteration) -{ - // unify to one expolygon - ExPolygons result; - for (const ExPolygonsWithId &shape : shapes) { - if (shape.expoly.empty()) - continue; - expolygons_append(result, shape.expoly); - } - result = union_ex(result); - - bool is_healed = heal_expolygons(result, max_heal_iteration); - return {result, is_healed}; -} - -HealedExPolygons Slic3r::union_with_delta(const ExPolygonsWithIds &shapes, float delta, unsigned max_heal_iteration) +namespace { +HealedExPolygons union_with_delta(const ExPolygonsWithIds &shapes, float delta, unsigned max_heal_iteration) { // unify to one expolygons ExPolygons expolygons; @@ -1267,10 +1255,26 @@ HealedExPolygons Slic3r::union_with_delta(const ExPolygonsWithIds &shapes, float expolygons_append(expolygons, offset_ex(shape.expoly, delta)); } ExPolygons result = union_ex(expolygons); - result = offset_ex(result, -delta); - bool is_healed = heal_expolygons(result, max_heal_iteration); + result = offset_ex(result, -delta); + bool is_healed = heal_expolygons(result, max_heal_iteration); return {result, is_healed}; } +} // namespace + +ExPolygons Slic3r::union_with_delta(EmbossShape &shape, float delta, unsigned max_heal_iteration) +{ + if (!shape.final_shape.empty()) + return shape.final_shape; + + HealedExPolygons result = ::union_with_delta(shape.shapes_with_ids, delta, max_heal_iteration); + shape.is_healed = result.is_healed; + for (const ExPolygonsWithId &e : shape.shapes_with_ids) + if (!e.is_healed) + shape.is_healed = false; + shape.final_shape = std::move(result.expolygons); // cached + + return shape.final_shape; +} void Slic3r::translate(ExPolygonsWithIds &expolygons_with_ids, const Point &p) { @@ -1298,7 +1302,7 @@ HealedExPolygons Emboss::text2shapes(FontFileWithCache &font_with_cache, const c ExPolygonsWithIds vshapes = text2vshapes(font_with_cache, text_w, font_prop, was_canceled); float delta = static_cast(1. / SHAPE_SCALE); - return union_with_delta(vshapes, delta, MAX_HEAL_ITERATION_OF_TEXT); + return ::union_with_delta(vshapes, delta, MAX_HEAL_ITERATION_OF_TEXT); } namespace { diff --git a/src/libslic3r/Emboss.hpp b/src/libslic3r/Emboss.hpp index a498b60afb..fb1264cfc0 100644 --- a/src/libslic3r/Emboss.hpp +++ b/src/libslic3r/Emboss.hpp @@ -31,6 +31,9 @@ struct HealedExPolygons{ /// namespace Emboss { + static const float UNION_DELTA = 50.0f; // [approx in nano meters depends on volume scale] + static const unsigned UNION_MAX_ITERATIN = 10; // [count] + /// /// Collect fonts registred inside OS /// @@ -474,9 +477,8 @@ namespace Emboss void translate(ExPolygonsWithIds &e, const Point &p); BoundingBox get_extents(const ExPolygonsWithIds &e); void center(ExPolygonsWithIds &e); -HealedExPolygons union_ex(const ExPolygonsWithIds &shapes, unsigned max_heal_iteration); // delta .. safe offset before union (use as boolean close) // NOTE: remove unprintable spaces between neighbor curves (made by linearization of curve) -HealedExPolygons union_with_delta(const ExPolygonsWithIds &shapes, float delta, unsigned max_heal_iteration); +ExPolygons union_with_delta(EmbossShape &shape, float delta, unsigned max_heal_iteration); } // namespace Slic3r #endif // slic3r_Emboss_hpp_ diff --git a/src/libslic3r/EmbossShape.hpp b/src/libslic3r/EmbossShape.hpp index 1c7fdfe795..62b724583a 100644 --- a/src/libslic3r/EmbossShape.hpp +++ b/src/libslic3r/EmbossShape.hpp @@ -74,6 +74,7 @@ struct EmbossShape { // shapes to to emboss separately over surface ExPolygonsWithIds shapes_with_ids; + ExPolygons final_shape; // When not set it is calculated from ExPolygonsWithIds // scale of shape, multiplier to get 3d point in mm from integer shape double scale = SCALING_FACTOR; diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 998218a2f9..eb30163753 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -3899,7 +3899,7 @@ std::optional read_emboss_shape(const char **attributes, unsigned i ExPolygonsWithIds shapes; // TODO: need to implement EmbossShape::SvgFile svg{file_path, file_path_3mf}; - return EmbossShape{shapes, scale, std::move(projection), std::move(fix_tr_mat), std::move(svg), is_healed}; + return EmbossShape{shapes, {}, scale, std::move(projection), std::move(fix_tr_mat), std::move(svg), is_healed}; } diff --git a/src/libslic3r/Format/SVG.cpp b/src/libslic3r/Format/SVG.cpp new file mode 100644 index 0000000000..5aa48bdb51 --- /dev/null +++ b/src/libslic3r/Format/SVG.cpp @@ -0,0 +1,105 @@ +///|/ Copyright (c) Prusa Research 2017 - 2021 Enrico Turri @enricoturri1966, Vojtěch Bubník @bubnikv, Tomáš Mészáros @tamasmeszaros +///|/ +///|/ ported from lib/Slic3r/Format/OBJ.pm: +///|/ Copyright (c) Prusa Research 2017 Vojtěch Bubník @bubnikv +///|/ Copyright (c) Slic3r 2012 - 2014 Alessandro Ranellucci @alranel +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ +#include "../libslic3r.h" +#include "../Model.hpp" +#include "../TriangleMesh.hpp" +#include "../NSVGUtils.hpp" +#include "../Emboss.hpp" + +#include + +namespace { +std::string get_file_name(const std::string &file_path) +{ + if (file_path.empty()) + return file_path; + + size_t pos_last_delimiter = file_path.find_last_of("/\\"); + if (pos_last_delimiter == std::string::npos) { + // should not happend that in path is not delimiter + assert(false); + pos_last_delimiter = 0; + } + + size_t pos_point = file_path.find_last_of('.'); + if (pos_point == std::string::npos || pos_point < pos_last_delimiter // last point is inside of directory path + ) { + // there is no extension + assert(false); + pos_point = file_path.size(); + } + + size_t offset = pos_last_delimiter + 1; // result should not contain last delimiter ( +1 ) + size_t count = pos_point - pos_last_delimiter - 1; // result should not contain extension point ( -1 ) + return file_path.substr(offset, count); +} +} + +namespace Slic3r { + +bool load_svg(const std::string &input_file, Model &output_model) +{ + EmbossShape::SvgFile svg_file{input_file}; + const NSVGimage* image = init_image(svg_file); + if (image == nullptr) { + BOOST_LOG_TRIVIAL(error) << "SVG file(\"" << input_file << "\") couldn't be parsed by nano svg."; + return false; + } + + double tesselation_tolerance = 1e10; + NSVGLineParams params(tesselation_tolerance); + ExPolygonsWithIds shapes = create_shape_with_ids(*image, params); + if (shapes.empty()) { + BOOST_LOG_TRIVIAL(error) << "SVG file(\"" << input_file << "\") do not contain embossedabled shape."; + return false; // No shapes in svg + } + + double depth_in_mm = 10.; // in mm + bool use_surface = false; + EmbossProjection emboss_projection{depth_in_mm, use_surface}; + + EmbossShape emboss_shape; + emboss_shape.shapes_with_ids = std::move(shapes); + emboss_shape.projection = std::move(emboss_projection); + emboss_shape.svg_file = std::move(svg_file); + + // unify to one expolygons + // EmbossJob.cpp --> ExPolygons create_shape(DataBase &input, Fnc was_canceled) { + ExPolygons union_shape = union_with_delta(emboss_shape, Emboss::UNION_DELTA, Emboss::UNION_MAX_ITERATIN); + + // create projection + double scale = emboss_shape.scale; + double depth = emboss_shape.projection.depth / scale; + auto projectZ = std::make_unique(depth); + Transform3d tr{Eigen::Scaling(scale)}; + Emboss::ProjectTransform project(std::move(projectZ), tr); + + // convert 2d shape to 3d triangles + indexed_triangle_set its = Emboss::polygons2model(union_shape, project); + TriangleMesh triangl_mesh(std::move(its)); + + // add mesh to model + ModelObject *object = output_model.add_object(); + assert(object != nullptr); + if (object == nullptr) + return false; + object->name = get_file_name(input_file); + ModelVolume* volume = object->add_volume(std::move(triangl_mesh)); + assert(volume != nullptr); + if (volume == nullptr) { + output_model.delete_object(object); + return false; + } + volume->name = object->name; // copy + volume->emboss_shape = std::move(emboss_shape); + object->invalidate_bounding_box(); + return true; +} + +}; // namespace Slic3r diff --git a/src/libslic3r/Format/SVG.hpp b/src/libslic3r/Format/SVG.hpp new file mode 100644 index 0000000000..13a362648b --- /dev/null +++ b/src/libslic3r/Format/SVG.hpp @@ -0,0 +1,17 @@ +///|/ Copyright (c) Prusa Research 2023 - 2023 +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ +#ifndef slic3r_Format_SVG_hpp_ +#define slic3r_Format_SVG_hpp_ + +#include + +namespace Slic3r { + +class Model; +// Load an SVG file as embossed shape into a provided model. +bool load_svg(const std::string &input_file, Model &output_model); + +}; // namespace Slic3r + +#endif /* slic3r_Format_SVG_hpp_ */ diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index d636c41fb6..6878c0c6b1 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -27,6 +27,7 @@ #include "Format/STL.hpp" #include "Format/3mf.hpp" #include "Format/STEP.hpp" +#include "Format/SVG.hpp" #include @@ -137,8 +138,10 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c else if (boost::algorithm::iends_with(input_file, ".3mf") || boost::algorithm::iends_with(input_file, ".zip")) //FIXME options & LoadAttribute::CheckVersion ? result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, false); + else if (boost::algorithm::iends_with(input_file, ".svg")) + result = load_svg(input_file, model); else - throw Slic3r::RuntimeError("Unknown file format. Input file must have .stl, .obj, .amf(.xml), .prusa or .step/.stp extension."); + throw Slic3r::RuntimeError("Unknown file format. Input file must have .stl, .obj, .step/.stp, .svg, .amf(.xml) or extension .3mf(.zip)."); if (! result) throw Slic3r::RuntimeError("Loading of a model file failed."); diff --git a/src/libslic3r/NSVGUtils.cpp b/src/libslic3r/NSVGUtils.cpp index b2ca975b30..6bfb89b74e 100644 --- a/src/libslic3r/NSVGUtils.cpp +++ b/src/libslic3r/NSVGUtils.cpp @@ -121,6 +121,28 @@ NSVGimage_ptr nsvgParse(const std::string& file_data, const char *units, float d return {image, &nsvgDelete}; } +NSVGimage *init_image(EmbossShape::SvgFile &svg_file){ + // is already initialized? + if (svg_file.image.get() != nullptr) + return svg_file.image.get(); + + if (svg_file.file_data == nullptr) { + // chech if path is known + if (svg_file.path.empty()) + return nullptr; + svg_file.file_data = read_from_disk(svg_file.path); + if (svg_file.file_data == nullptr) + return nullptr; + } + + // init svg image + svg_file.image = nsvgParse(*svg_file.file_data); + if (svg_file.image.get() == NULL) + return nullptr; + + return svg_file.image.get(); +} + size_t get_shapes_count(const NSVGimage &image) { size_t count = 0; diff --git a/src/libslic3r/NSVGUtils.hpp b/src/libslic3r/NSVGUtils.hpp index 8c3a27d40f..fe76fa0459 100644 --- a/src/libslic3r/NSVGUtils.hpp +++ b/src/libslic3r/NSVGUtils.hpp @@ -71,6 +71,7 @@ std::unique_ptr read_from_disk(const std::string &path); using NSVGimage_ptr = std::unique_ptr; NSVGimage_ptr nsvgParseFromFile(const std::string &svg_file_path, const char *units = "mm", float dpi = 96.0f); NSVGimage_ptr nsvgParse(const std::string& file_data, const char *units = "mm", float dpi = 96.0f); +NSVGimage *init_image(EmbossShape::SvgFile &svg_file); /// /// Iterate over shapes and calculate count diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 071e8000cc..c7f4a3be69 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -489,7 +489,7 @@ static const FileWildcards file_wildcards_by_type[FT_SIZE] = { /* FT_AMF */ { "AMF files"sv, { ".amf"sv, ".zip.amf"sv, ".xml"sv } }, /* FT_3MF */ { "3MF files"sv, { ".3mf"sv } }, /* FT_GCODE */ { "G-code files"sv, { ".gcode"sv, ".gco"sv, ".bgcode"sv, ".bgc"sv, ".g"sv, ".ngc"sv } }, - /* FT_MODEL */ { "Known files"sv, { ".stl"sv, ".obj"sv, ".3mf"sv, ".amf"sv, ".zip.amf"sv, ".xml"sv, ".step"sv, ".stp"sv } }, + /* FT_MODEL */ { "Known files"sv, { ".stl"sv, ".obj"sv, ".3mf"sv, ".amf"sv, ".zip.amf"sv, ".xml"sv, ".step"sv, ".stp"sv, ".svg"sv } }, /* FT_PROJECT */ { "Project files"sv, { ".3mf"sv, ".amf"sv, ".zip.amf"sv } }, /* FT_FONTS */ { "Font files"sv, { ".ttc"sv, ".ttf"sv } }, /* FT_GALLERY */ { "Known files"sv, { ".stl"sv, ".obj"sv } }, diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp index bbdd3c2014..3f754245b5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp @@ -583,32 +583,6 @@ void GLGizmoSVG::on_dragging(const UpdateData &data) { m_rotate_gizmo.dragging(d #include "libslic3r/AABBTreeLines.hpp" // aabb lines for draw filled expolygon namespace{ -NSVGimage* init_image(EmbossShape::SvgFile &svg_file) { - // is already initialized? - if (svg_file.image.get() != nullptr) - return svg_file.image.get(); - - - if (svg_file.file_data == nullptr){ - // chech if path is known - if (svg_file.path.empty()) - return nullptr; - svg_file.file_data = read_from_disk(svg_file.path); - if (svg_file.file_data == nullptr) - return nullptr; - } - - // init svg image - svg_file.image = nsvgParse(*svg_file.file_data); - if (svg_file.image.get() == NULL) - return nullptr; - - // Disable stroke - //for (NSVGshape *shape = svg_file.image->shapes; shape != NULL; shape = shape->next) - // shape->stroke.type = 0; - - return svg_file.image.get(); -} // inspired by Xiaolin Wu's line algorithm - https://en.wikipedia.org/wiki/Xiaolin_Wu's_line_algorithm // Draw inner part of polygon CCW line as full brightness(edge of expolygon) @@ -959,6 +933,19 @@ void draw_filled(const ExPolygons &shape, const std::array& co } } +/// Union shape defined by glyphs +ExPolygons union_ex(const ExPolygonsWithIds &shapes) +{ + // unify to one expolygon + ExPolygons result; + for (const ExPolygonsWithId &shape : shapes) { + if (shape.expoly.empty()) + continue; + expolygons_append(result, shape.expoly); + } + return union_ex(result); +} + // init texture by draw expolygons into texture bool init_texture(Texture &texture, const ExPolygonsWithIds& shapes_with_ids, unsigned max_size_px, const std::vector& shape_warnings){ BoundingBox bb = get_extents(shapes_with_ids); @@ -985,10 +972,7 @@ bool init_texture(Texture &texture, const ExPolygonsWithIds& shapes_with_ids, un std::vector data(n_pixels * channels_count, {0}); // Union All shapes - ExPolygons shape; - for (const ExPolygonsWithId &shapes_with_id : shapes_with_ids) - expolygons_append(shape, shapes_with_id.expoly); - shape = union_ex(shape); + ExPolygons shape = union_ex(shapes_with_ids); // align to texture translate(shape, -bb.min); diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index 08fc0501bc..2bd85054ba 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -788,14 +788,10 @@ bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread) template ExPolygons create_shape(DataBase &input, Fnc was_canceled) { EmbossShape &es = input.create_shape(); - float delta = 50.f; - unsigned max_heal_iteration = 10; - HealedExPolygons result = union_with_delta(es.shapes_with_ids, delta, max_heal_iteration); - es.is_healed = result.is_healed; - for (const ExPolygonsWithId &e : es.shapes_with_ids) - if (!e.is_healed) - es.is_healed = false; - return result.expolygons; + // TODO: improve to use real size of volume + // ... need world matrix for volume + // ... printer resolution will be fine too + return union_with_delta(es, UNION_DELTA, UNION_MAX_ITERATIN); } //#define STORE_SAMPLING