Add icons(refresh and burn) to svg emboss

Add NSVG image to shape
This commit is contained in:
Filip Sykala - NTB T15p 2023-07-19 12:02:28 +02:00
parent ed10fefba8
commit d530831e35
8 changed files with 118 additions and 85 deletions

4
resources/icons/burn.svg Normal file
View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16px" height="16px">
<path fill="#808080" d="M 10.324265,12.637091 C 9.7126185,11.801826 9.4455652,11.319769 9.3860031,10.943437 9.3387131,10.644653 9.2775165,10.67725 9.032793,11.131535 8.8565876,11.45863 8.806364,11.904627 8.9008009,12.303679 8.330577,11.894019 7.6015175,10.031363 7.7096999,9.1284851 5.381808,10.598103 4.7271155,13.939753 5,16 2.9751051,14.608227 1.2851951,13.046842 1.3078808,10.57598 1.3305665,8.1051179 3.8432395,6.4490194 3.8697024,4.1713523 3.8727624,3.3745518 3.8421277,3.3793755 4.5118024,4.0701844 5.3014406,5.073513 5.8791344,5.964439 5.6521467,7.0731381 6.1486239,6.623741 6.3341015,5.940722 6.5401623,5.4070849 6.8689362,3.4222537 6.7021829,1.5642078 6,0 c 2.5998224,0.67409817 4.550997,3.5888298 4.623306,5.6887828 l 9.3e-4,0.4921817 C 11.260843,5.1624272 11.966175,4.3725405 13,4 12.774748,6.4272659 14.752236,8.539235 14.683585,10.819702 14.614934,13.100169 13.178608,15.41894 11,16 11.750123,14.792953 10.874074,13.393198 10.324265,12.637091 Z" />
<path fill="#ed6b21" d="M 9.032793,11.131535 C 8.8565876,11.45863 8.806364,11.904627 8.9008009,12.303679 8.2684185,12.02479 7.7175594,10.368983 7.6751289,9.4545414 7.7066738,9.2878597 7.7222018,9.1411316 7.7097002,9.1284851 8.5239771,6.6708553 5.893441,7.0819045 6.5401623,5.4070849 6.8689362,3.4222537 6.7021829,1.5642078 6,0 c 2.5998224,0.67409817 4.550997,3.5888298 4.623306,5.6887828 l 9.3e-4,0.4921817 c -0.04706,1.5271028 -0.04706,2.5643146 -1.591443,4.9505705 z" />
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16px" height="16px">
<path fill="#ed6b21" d="m 0.80996126,4.2067338 c 0,0 1.45662384,-2.2486082 3.76076774,-3.45466511 2.3041438,-1.2060569 5.1439072,-0.85800021 7.005512,0.31082271 1.861604,1.1704417 2.488671,2.1563326 2.488671,2.1563326 l 1.603592,-0.9146606 c 0,0 0.331496,-0.1651245 0.331496,0.2185472 v 5.7388883 c 0,0 0,0.5115624 -0.387017,0.3286302 C 15.288019,8.4368366 11.764034,6.4326776 10.655236,5.801319 10.046132,5.5293491 10.581752,5.309183 10.581752,5.309183 l 1.548071,-0.8855209 c 0,0 -0.883446,-1.1056871 -2.1751379,-1.6917175 C 8.5715456,2.0147859 7.2765874,1.9289859 5.6909576,2.5279672 4.6572773,2.9181146 3.4390694,3.9185752 2.5621557,5.3966019 Z" />
<path fill="#808080" d="m 15.19004,11.792751 c 0,0 -1.456624,2.248608 -3.760768,3.454665 C 9.125128,16.453472 6.2853646,16.107035 4.4237602,14.936593 2.5621557,13.766151 1.935089,12.78026 1.935089,12.78026 l -1.60359274,0.913042 c 0,0 -0.3314962496,0.165125 -0.3314962496,-0.218547 V 7.7342477 c 0,0 0,-0.5115623 0.3870177796,-0.3286303 0.32496429,0.1537925 3.84894891,2.1579516 4.95774671,2.7893106 0.609104,0.271969 0.073485,0.492136 0.073485,0.492136 l -1.5480712,0.88552 c 0,0 0.8834456,1.105688 2.1751379,1.691718 1.3831395,0.720396 2.6780976,0.806196 4.2637278,0.207215 1.033681,-0.390147 2.251888,-1.390608 3.128802,-2.868635 z" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -3,6 +3,7 @@
#include <string>
#include <optional>
#include <memory> // unique_ptr
#include <cereal/cereal.hpp>
#include <cereal/types/string.hpp>
#include <cereal/types/vector.hpp>
@ -11,6 +12,7 @@
#include "Point.hpp" // Transform3d
#include "ExPolygon.hpp"
#include "ExPolygonSerialize.hpp"
#include "nanosvg/nanosvg.h" // NSVGimage
namespace Slic3r {
@ -83,19 +85,31 @@ struct EmbossShape
// Stored_Transform3d * fix_3mf_tr = Transform3d_before_store_to_3mf
std::optional<Slic3r::Transform3d> fix_3mf_tr;
// file(.svg) path to source of shape
// When empty can't reload from disk
std::string svg_file_path;
struct SvgFile {
// File(.svg) path on local computer
// When empty can't reload from disk
std::string path;
// File path into .3mf(.zip)
// When empty svg is not stored into .3mf file yet.
// and will create dialog to delete private data on save.
std::string path_in_3mf;
// Loaded svg file data.
// !!! It is not serialized on undo/redo stack
std::shared_ptr<NSVGimage> image;
};
SvgFile svg_file;
// undo / redo stack recovery
template<class Archive> void save(Archive &ar) const
{
ar(shapes_with_ids, scale, projection, svg_file_path);
ar(shapes_with_ids, scale, projection, svg_file.path, svg_file.path_in_3mf);
cereal::save(ar, fix_3mf_tr);
}
template<class Archive> void load(Archive &ar)
{
ar(shapes_with_ids, scale, projection, svg_file_path);
ar(shapes_with_ids, scale, projection, svg_file.path, svg_file.path_in_3mf);
cereal::load(ar, fix_3mf_tr);
}
};

View File

@ -3691,7 +3691,7 @@ std::string to_string(const ExPolygonsWithIds &shapes)
void to_xml(std::stringstream &stream, const EmbossShape &es, const ModelVolume &volume) {
stream << " <" << SHAPE_TAG << " ";
stream << SVG_FILE_PATH_ATTR << "=\"" << xml_escape_double_quotes_attribute_value(es.svg_file_path) << "\" ";
stream << SVG_FILE_PATH_ATTR << "=\"" << xml_escape_double_quotes_attribute_value(es.svg_file.path) << "\" ";
stream << SHAPE_SCALE_ATTR << "=\"" << es.scale << "\" ";
std::string expolygons_str = to_string(es.shapes_with_ids); // cereal serialize expolygons

View File

@ -23,10 +23,10 @@ Point::coord_type to_coor(float val, float scale) { return static_cast<Point::co
} // namespace
namespace Slic3r {
Polygons to_polygons(NSVGimage *image, float tessTol, int max_level, float scale, bool is_y_negative)
Polygons to_polygons(const NSVGimage &image, float tessTol, int max_level, float scale, bool is_y_negative)
{
Polygons polygons;
for (NSVGshape *shape = image->shapes; shape != NULL; shape = shape->next) {
for (NSVGshape *shape = image.shapes; shape != NULL; shape = shape->next) {
if (!(shape->flags & NSVG_FLAGS_VISIBLE))
continue;
if (shape->fill.type == NSVG_PAINT_NONE)
@ -64,10 +64,16 @@ Polygons to_polygons(NSVGimage *image, float tessTol, int max_level, float scale
return polygons;
}
ExPolygons to_expolygons(NSVGimage *image, float tessTol, int max_level, float scale, bool is_y_negative){
ExPolygons to_expolygons(const NSVGimage &image, float tessTol, int max_level, float scale, bool is_y_negative){
return union_ex(to_polygons(image, tessTol, max_level, scale, is_y_negative));
}
NSVGimage_ptr nsvgParseFromFile(const std::string &filename, const char *units, float dpi)
{
NSVGimage *image = ::nsvgParseFromFile(filename.c_str(), units, dpi);
return {image, ::nsvgDelete};
}
} // namespace Slic3r
namespace {

View File

@ -1,6 +1,8 @@
#ifndef slic3r_NSVGUtils_hpp_
#define slic3r_NSVGUtils_hpp_
#include <memory>
#include <string>
#include "Polygon.hpp"
#include "ExPolygon.hpp"
#include "nanosvg/nanosvg.h" // load SVG file
@ -19,8 +21,11 @@ namespace Slic3r {
/// NOTE: Every point coor from image(float) is multiplied by scale and rounded to integer</param>
/// <param name="is_y_negative">Flag is y negative, when true than y coor is multiplied by -1</param>
/// <returns>Polygons extracted from svg</returns>
Polygons to_polygons(NSVGimage *image, float tessTol = 10., int max_level = 10, float scale = 1.f, bool is_y_negative = true);
ExPolygons to_expolygons(NSVGimage *image, float tessTol = 10., int max_level = 10, float scale = 1.f, bool is_y_negative = true);
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);
using NSVGimage_ptr = std::unique_ptr<NSVGimage, void (*)(NSVGimage*)>;
NSVGimage_ptr nsvgParseFromFile(const std::string& filename, const char *units = "mm", float dpi = 96.0f);
} // namespace Slic3r
#endif // slic3r_NSVGUtils_hpp_

View File

@ -68,7 +68,7 @@ std::string choose_svg_file();
/// Let user to choose file with (S)calable (V)ector (G)raphics - SVG.
/// Than let select contour
/// </summary>
/// <param name="filepath">SVG file path</param>
/// <param name="filepath">SVG file path, when empty promt user to select one</param>
/// <returns>EmbossShape to create</returns>
EmbossShape select_shape(std::string_view filepath = "");
@ -80,14 +80,6 @@ EmbossShape select_shape(std::string_view filepath = "");
/// <returns>Base data for emboss SVG</returns>
DataBasePtr create_emboss_data_base(std::shared_ptr<std::atomic<bool>> &cancel, std::string_view filepath = "");
/// <summary>
/// Create symbol '?' as default shape
/// without source file
/// with size 2cm
/// </summary>
/// <returns>Default shape to emboss</returns>
ExPolygons default_shape();
/// <summary>
/// Separate file name from file path.
/// String after last delimiter and before last point
@ -115,6 +107,12 @@ CreateVolumeParams create_input(GLCanvas3D &canvas, RaycastManager &raycaster, M
enum class IconType : unsigned {
reset_value,
reset_value_hover,
refresh,
refresh_hover,
change_file,
change_file_hover,
bake,
bake_hover,
lock,
lock_hover,
unlock,
@ -126,6 +124,8 @@ enum class IconType : unsigned {
// automatic calc of icon's count
_count
};
// Do not forgot add loading of file in funtion:
// IconManager::Icons init_icons(
const IconManager::Icon &get_icon(const IconManager::Icons &icons, IconType type) {
return *icons[static_cast<unsigned>(type)]; }
@ -140,7 +140,7 @@ struct GuiCfg
float main_toolbar_height;
// Define bigger size(width or height)
unsigned texture_max_size_px = 64;
unsigned texture_max_size_px = 256;
// Zero means it is calculated in init function
ImVec2 minimal_window_size = ImVec2(0, 0);
@ -385,6 +385,12 @@ IconManager::Icons init_icons(IconManager &mng, const GuiCfg &cfg)
IconManager::InitTypes init_types{
{"undo.svg", size, IconManager::RasterType::white_only_data}, // undo
{"undo.svg", size, IconManager::RasterType::color}, // undo_hovered
{"refresh.svg", size, IconManager::RasterType::white_only_data}, // refresh
{"refresh.svg", size, IconManager::RasterType::color}, // refresh_hovered
{"open.svg", size, IconManager::RasterType::white_only_data}, // changhe_file
{"open.svg", size, IconManager::RasterType::color}, // changhe_file_hovered
{"burn.svg", size, IconManager::RasterType::white_only_data}, // bake_file
{"burn.svg", size, IconManager::RasterType::color}, // bake_hovered
{"lock_closed.svg", size, IconManager::RasterType::white_only_data}, // lock
{"lock_open_f.svg", size, IconManager::RasterType::white_only_data}, // lock_hovered
{"lock_open.svg", size, IconManager::RasterType::white_only_data}, // unlock
@ -554,7 +560,7 @@ bool init_texture(Texture &texture, const ModelVolume &mv, unsigned max_size_px)
return false;
const EmbossShape &es = *mv.emboss_shape;
const std::string &filepath = es.svg_file_path;
const std::string &filepath = es.svg_file.path;
if (filepath.empty())
return false;
@ -566,7 +572,7 @@ bool init_texture(Texture &texture, const ModelVolume &mv, unsigned max_size_px)
ScopeGuard sg_image([image]() { nsvgDelete(image); });
// NOTE: Can not use es.shape --> it is aligned and one need offset in svg
ExPolygons shape = to_expolygons(image);
ExPolygons shape = to_expolygons(*image);
if (shape.empty())
return false;
@ -793,14 +799,10 @@ bool GLGizmoSVG::draw_preview(){
if (m_filename_preview.empty()){
// create filename preview
m_filename_preview = get_file_name(m_volume->emboss_shape->svg_file_path);
m_filename_preview = get_file_name(m_volume->emboss_shape->svg_file.path);
m_filename_preview = ImGuiWrapper::trunc(m_filename_preview, m_gui_cfg->input_width);
}
ImGui::SameLine();
ImGui::BeginGroup();
ScopeGuard sg_group([]() { ImGui::EndGroup(); });
// Remove space between filename and gray suffix ".svg"
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
@ -812,19 +814,19 @@ bool GLGizmoSVG::draw_preview(){
is_hovered |= ImGui::IsItemHovered();
if (is_hovered) {
std::string tooltip = GUI::format(_L("SVG file path is \"%1%\" "), m_volume->emboss_shape->svg_file_path);
std::string tooltip = GUI::format(_L("SVG file path is \"%1%\" "), m_volume->emboss_shape->svg_file.path);
ImGui::SetTooltip("%s", tooltip.c_str());
}
// Re-Load button
bool can_reload = !m_volume_shape.svg_file_path.empty();
bool can_reload = !m_volume_shape.svg_file.path.empty();
if (can_reload) {
ImGui::SameLine();
if (clickable(get_icon(m_icons, IconType::reset_value), get_icon(m_icons, IconType::reset_value_hover))) {
if (!boost::filesystem::exists(m_volume_shape.svg_file_path)) {
m_volume_shape.svg_file_path.clear();
if (clickable(get_icon(m_icons, IconType::refresh), get_icon(m_icons, IconType::refresh_hover))) {
if (!boost::filesystem::exists(m_volume_shape.svg_file.path)) {
m_volume_shape.svg_file.path.clear();
} else {
m_volume_shape.shapes_with_ids = select_shape(m_volume_shape.svg_file_path).shapes_with_ids;
m_volume_shape.shapes_with_ids = select_shape(m_volume_shape.svg_file.path).shapes_with_ids;
init_texture(m_texture, *m_volume, m_gui_cfg->texture_max_size_px);
process();
}
@ -832,19 +834,22 @@ bool GLGizmoSVG::draw_preview(){
ImGui::SetTooltip("%s", _u8L("Re-load SVG file from disk.").c_str());
}
if (ImGui::Button(_u8L("Change file").c_str())) {
ImGui::SameLine();
if (clickable(get_icon(m_icons, IconType::change_file), get_icon(m_icons, IconType::change_file_hover))) {
m_volume_shape.shapes_with_ids = select_shape().shapes_with_ids;
init_texture(m_texture, *m_volume, m_gui_cfg->texture_max_size_px);
process();
} else if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s", _u8L("Change to another .svg file").c_str());
}
ImGui::SameLine();
if (ImGui::Button(_u8L("Bake").c_str())) {
if (clickable(get_icon(m_icons, IconType::bake), get_icon(m_icons, IconType::bake_hover))) {
m_volume->emboss_shape.reset();
close();
return false;
} else if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s", _u8L("Remove connection to source file to take care about copyright").c_str());
ImGui::SetTooltip("%s", _u8L("Bake to uneditable part and save copyright of svg").c_str());
}
return true;
}
@ -1221,7 +1226,7 @@ std::string get_file_name(const std::string &file_path)
std::string volume_name(const EmbossShape &shape)
{
std::string file_name = get_file_name(shape.svg_file_path);
std::string file_name = get_file_name(shape.svg_file.path);
if (!file_name.empty())
return file_name;
return "SVG shape";
@ -1244,7 +1249,7 @@ GuiCfg create_gui_configuration() {
float space = line_height_with_spacing - line_height;
cfg.icon_width = std::floor(line_height/8)*8;
cfg.icon_width = std::max(std::round(line_height/8)*8, 8.f);
GuiCfg::Translations &tr = cfg.translations;
@ -1271,7 +1276,7 @@ GuiCfg create_gui_configuration() {
ImVec2 letter_m_size = ImGui::CalcTextSize("M");
const float count_letter_M_in_input = 12.f;
cfg.input_width = letter_m_size.x * count_letter_M_in_input;
cfg.texture_max_size_px = std::round((cfg.input_width + cfg.input_offset + cfg.icon_width +space)/8) * 8;
return cfg;
}
@ -1299,8 +1304,19 @@ std::string choose_svg_file()
if (input_files.size() != 1)
BOOST_LOG_TRIVIAL(warning) << "SVG file dialog result contain multiple files but only first is used.";
auto &input_file = input_files.front();
std::string path = std::string(input_file.c_str());
auto &input_file = input_files.front();
std::string path = std::string(input_file.c_str());
if (!boost::filesystem::exists(path)) {
BOOST_LOG_TRIVIAL(warning) << "SVG file dialog return invalid path.";
return {};
}
if (!boost::algorithm::iends_with(path, ".svg")) {
BOOST_LOG_TRIVIAL(warning) << "SVG file dialog return path without '.svg' tail";
return {};
}
return path;
}
@ -1309,32 +1325,6 @@ void translate(ExPolygons &expolys, const Point &p) {
expoly.translate(p);
}
NSVGimage *parse_from_file(const char *filepath){
const char *unit_mm{"mm"};
// common used DPI is 96 or 72
float dpi = 96.0f;
return nsvgParseFromFile(filepath, unit_mm, dpi);
}
ExPolygons default_shape()
{
std::string file = Slic3r::resources_dir() + "/icons/question.svg";
assert(boost::filesystem::exists(file));
NSVGimage *image = parse_from_file(file.c_str());
assert(image != nullptr);
// tesselation tolerance
float tol = 1e-2f;
int max_level = 10;
float scale = static_cast<float>(2. / DEFAULT_SCALE);
bool is_y_negative = true;
ExPolygons shape = to_expolygons(image, tol, max_level, scale, is_y_negative);
assert(!shape.empty());
nsvgDelete(image);
return shape;
}
EmbossShape select_shape(std::string_view filepath)
{
EmbossShape shape;
@ -1342,32 +1332,42 @@ EmbossShape select_shape(std::string_view filepath)
shape.projection.use_surface = false;
if (filepath.empty()) {
shape.svg_file_path = choose_svg_file();
if (shape.svg_file_path.empty())
return {};
// When empty open file dialog
shape.svg_file.path = choose_svg_file();
if (shape.svg_file.path.empty())
return {}; // file was not selected
} else {
shape.svg_file_path = filepath; // copy
shape.svg_file.path = filepath; // copy
}
if (!boost::filesystem::exists(shape.svg_file.path)) {
show_error(nullptr, GUI::format(_u8L("File(%1%) does NOT exists."), shape.svg_file.path));
return {};
}
if (!boost::filesystem::exists(shape.svg_file_path) ||
!boost::algorithm::iends_with(shape.svg_file_path, ".svg"))
if (!boost::algorithm::iends_with(shape.svg_file.path, ".svg")){
show_error(nullptr, GUI::format(_u8L("File has to end with \".svg\" but you select: %1%"), shape.svg_file.path));
return {};
}
NSVGimage *image = parse_from_file(shape.svg_file_path.c_str());
if (image == nullptr) return {};
ScopeGuard sg([image]() { nsvgDelete(image); });
shape.svg_file.image = nsvgParseFromFile(shape.svg_file.path);
if (shape.svg_file.image.get() == nullptr) {
show_error(nullptr, GUI::format(_u8L("Nano SVG parser can't load from file(%1%)."), shape.svg_file.path));
return {};
}
shape.scale = DEFAULT_SCALE; // loaded in mm
constexpr float tesselation_tolerance = 1e-2f;
int max_level = 10;
float scale = static_cast<float>(1 / shape.scale);
bool is_y_negative = true;
ExPolygons expoly = to_expolygons(image, tesselation_tolerance, max_level, scale, is_y_negative);
ExPolygons expoly = to_expolygons(*shape.svg_file.image, tesselation_tolerance, max_level, scale, is_y_negative);
// Must contain some shapes !!!
if (expoly.empty())
expoly = default_shape();
if (expoly.empty()) {
show_error(nullptr, GUI::format(_u8L("SVG file(%1%) do NOT contain path to be able embossed."), shape.svg_file.path));
return {};
}
// SVG is used as centered
// Do not disturb user by settings of pivot position

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);
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);
nsvgDelete(image);
REQUIRE(polygons.size() == 1);
Polygon polygon = polygons.front();
@ -536,7 +536,7 @@ TEST_CASE("UndoRedo EmbossShape serialization", "[Emboss]")
emboss.projection.depth = 5.;
emboss.projection.use_surface = true;
emboss.fix_3mf_tr = Transform3d::Identity();
emboss.svg_file_path = "Everything starts somewhere, though many physicists disagree.\
emboss.svg_file.path = "Everything starts somewhere, though many physicists disagree.\
But people have always been dimly aware of the problem with the start of things.\
They wonder how the snowplough driver gets to work,\
or how the makers of dictionaries look up the spelling of words.";