Merge branch 'fs_import_svg_SPE-1984' into fs_svg_SPE-1517

This commit is contained in:
Filip Sykala - NTB T15p 2023-10-27 09:11:11 +02:00
commit 7184b9e1a1
13 changed files with 201 additions and 64 deletions

View File

@ -146,6 +146,8 @@ set(SLIC3R_SOURCES
Format/AnycubicSLA.cpp Format/AnycubicSLA.cpp
Format/STEP.hpp Format/STEP.hpp
Format/STEP.cpp Format/STEP.cpp
Format/SVG.hpp
Format/SVG.cpp
Format/SLAArchiveFormatRegistry.hpp Format/SLAArchiveFormatRegistry.hpp
Format/SLAArchiveFormatRegistry.cpp Format/SLAArchiveFormatRegistry.cpp
GCode/ThumbnailData.cpp GCode/ThumbnailData.cpp

View File

@ -422,6 +422,9 @@ HealedExPolygons Emboss::heal_polygons(const Polygons &shape, bool is_non_zero,
polygons.erase(std::remove_if(polygons.begin(), polygons.end(), polygons.erase(std::remove_if(polygons.begin(), polygons.end(),
[](const Polygon &p) { return p.size() < 3; }), 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 // Do not remove all duplicates but do it better way
// Overlap all duplicit points by rectangle 3x3 // Overlap all duplicit points by rectangle 3x3
Points duplicits = collect_duplicates(to_points(polygons)); Points duplicits = collect_duplicates(to_points(polygons));
@ -1241,23 +1244,8 @@ ExPolygons letter2shapes(
const int CANCEL_CHECK = 10; const int CANCEL_CHECK = 10;
} // namespace } // namespace
/// Union shape defined by glyphs namespace {
HealedExPolygons Slic3r::union_ex(const ExPolygonsWithIds &shapes, unsigned max_heal_iteration) HealedExPolygons union_with_delta(const ExPolygonsWithIds &shapes, float delta, 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)
{ {
// unify to one expolygons // unify to one expolygons
ExPolygons expolygons; ExPolygons expolygons;
@ -1271,6 +1259,22 @@ HealedExPolygons Slic3r::union_with_delta(const ExPolygonsWithIds &shapes, float
bool is_healed = heal_expolygons(result, max_heal_iteration); bool is_healed = heal_expolygons(result, max_heal_iteration);
return {result, is_healed}; 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) 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); ExPolygonsWithIds vshapes = text2vshapes(font_with_cache, text_w, font_prop, was_canceled);
float delta = static_cast<float>(1. / SHAPE_SCALE); float delta = static_cast<float>(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 { namespace {

View File

@ -31,6 +31,9 @@ struct HealedExPolygons{
/// </summary> /// </summary>
namespace Emboss 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]
/// <summary> /// <summary>
/// Collect fonts registred inside OS /// Collect fonts registred inside OS
/// </summary> /// </summary>
@ -474,9 +477,8 @@ namespace Emboss
void translate(ExPolygonsWithIds &e, const Point &p); void translate(ExPolygonsWithIds &e, const Point &p);
BoundingBox get_extents(const ExPolygonsWithIds &e); BoundingBox get_extents(const ExPolygonsWithIds &e);
void center(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) // delta .. safe offset before union (use as boolean close)
// NOTE: remove unprintable spaces between neighbor curves (made by linearization of curve) // 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 } // namespace Slic3r
#endif // slic3r_Emboss_hpp_ #endif // slic3r_Emboss_hpp_

View File

@ -74,6 +74,7 @@ struct EmbossShape
{ {
// shapes to to emboss separately over surface // shapes to to emboss separately over surface
ExPolygonsWithIds shapes_with_ids; 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 // scale of shape, multiplier to get 3d point in mm from integer shape
double scale = SCALING_FACTOR; double scale = SCALING_FACTOR;

View File

@ -3899,7 +3899,7 @@ std::optional<EmbossShape> read_emboss_shape(const char **attributes, unsigned i
ExPolygonsWithIds shapes; // TODO: need to implement ExPolygonsWithIds shapes; // TODO: need to implement
EmbossShape::SvgFile svg{file_path, file_path_3mf}; 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};
} }

View File

@ -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 <boost/log/trivial.hpp>
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<Emboss::ProjectZ>(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

View File

@ -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 <string>
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_ */

View File

@ -27,6 +27,7 @@
#include "Format/STL.hpp" #include "Format/STL.hpp"
#include "Format/3mf.hpp" #include "Format/3mf.hpp"
#include "Format/STEP.hpp" #include "Format/STEP.hpp"
#include "Format/SVG.hpp"
#include <float.h> #include <float.h>
@ -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")) else if (boost::algorithm::iends_with(input_file, ".3mf") || boost::algorithm::iends_with(input_file, ".zip"))
//FIXME options & LoadAttribute::CheckVersion ? //FIXME options & LoadAttribute::CheckVersion ?
result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, false); 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 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) if (! result)
throw Slic3r::RuntimeError("Loading of a model file failed."); throw Slic3r::RuntimeError("Loading of a model file failed.");

View File

@ -121,6 +121,28 @@ NSVGimage_ptr nsvgParse(const std::string& file_data, const char *units, float d
return {image, &nsvgDelete}; 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 get_shapes_count(const NSVGimage &image)
{ {
size_t count = 0; size_t count = 0;

View File

@ -71,6 +71,7 @@ std::unique_ptr<std::string> read_from_disk(const std::string &path);
using NSVGimage_ptr = std::unique_ptr<NSVGimage, void (*)(NSVGimage*)>; using NSVGimage_ptr = std::unique_ptr<NSVGimage, void (*)(NSVGimage*)>;
NSVGimage_ptr nsvgParseFromFile(const std::string &svg_file_path, const char *units = "mm", float dpi = 96.0f); NSVGimage_ptr nsvgParseFromFile(const std::string &svg_file_path, const char *units = "mm", float dpi = 96.0f);
NSVGimage_ptr nsvgParse(const std::string& file_data, 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);
/// <summary> /// <summary>
/// Iterate over shapes and calculate count /// Iterate over shapes and calculate count

View File

@ -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_AMF */ { "AMF files"sv, { ".amf"sv, ".zip.amf"sv, ".xml"sv } },
/* FT_3MF */ { "3MF files"sv, { ".3mf"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_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_PROJECT */ { "Project files"sv, { ".3mf"sv, ".amf"sv, ".zip.amf"sv } },
/* FT_FONTS */ { "Font files"sv, { ".ttc"sv, ".ttf"sv } }, /* FT_FONTS */ { "Font files"sv, { ".ttc"sv, ".ttf"sv } },
/* FT_GALLERY */ { "Known files"sv, { ".stl"sv, ".obj"sv } }, /* FT_GALLERY */ { "Known files"sv, { ".stl"sv, ".obj"sv } },

View File

@ -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 #include "libslic3r/AABBTreeLines.hpp" // aabb lines for draw filled expolygon
namespace{ 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 // 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) // 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<unsigned char, N>& 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 // init texture by draw expolygons into texture
bool init_texture(Texture &texture, const ExPolygonsWithIds& shapes_with_ids, unsigned max_size_px, const std::vector<std::string>& shape_warnings){ bool init_texture(Texture &texture, const ExPolygonsWithIds& shapes_with_ids, unsigned max_size_px, const std::vector<std::string>& shape_warnings){
BoundingBox bb = get_extents(shapes_with_ids); BoundingBox bb = get_extents(shapes_with_ids);
@ -985,10 +972,7 @@ bool init_texture(Texture &texture, const ExPolygonsWithIds& shapes_with_ids, un
std::vector<unsigned char> data(n_pixels * channels_count, {0}); std::vector<unsigned char> data(n_pixels * channels_count, {0});
// Union All shapes // Union All shapes
ExPolygons shape; ExPolygons shape = union_ex(shapes_with_ids);
for (const ExPolygonsWithId &shapes_with_id : shapes_with_ids)
expolygons_append(shape, shapes_with_id.expoly);
shape = union_ex(shape);
// align to texture // align to texture
translate(shape, -bb.min); translate(shape, -bb.min);

View File

@ -788,14 +788,10 @@ bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread)
template<typename Fnc> template<typename Fnc>
ExPolygons create_shape(DataBase &input, Fnc was_canceled) { ExPolygons create_shape(DataBase &input, Fnc was_canceled) {
EmbossShape &es = input.create_shape(); EmbossShape &es = input.create_shape();
float delta = 50.f; // TODO: improve to use real size of volume
unsigned max_heal_iteration = 10; // ... need world matrix for volume
HealedExPolygons result = union_with_delta(es.shapes_with_ids, delta, max_heal_iteration); // ... printer resolution will be fine too
es.is_healed = result.is_healed; return union_with_delta(es, UNION_DELTA, UNION_MAX_ITERATIN);
for (const ExPolygonsWithId &e : es.shapes_with_ids)
if (!e.is_healed)
es.is_healed = false;
return result.expolygons;
} }
//#define STORE_SAMPLING //#define STORE_SAMPLING