From 480c571499c2b31da1cd261f4cc0595b35602c19 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Wed, 2 Aug 2023 22:34:33 +0200 Subject: [PATCH] Store shape as svg into 3mf Without obfuscation of svg. --- src/libslic3r/EmbossShape.hpp | 5 +- src/libslic3r/Format/3mf.cpp | 115 ++++++++++++++++++++++----- src/libslic3r/NSVGUtils.cpp | 62 ++++++++++++--- src/libslic3r/NSVGUtils.hpp | 7 ++ src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp | 9 ++- src/slic3r/GUI/Plater.cpp | 101 +++++++++++++++++++++++ 6 files changed, 267 insertions(+), 32 deletions(-) diff --git a/src/libslic3r/EmbossShape.hpp b/src/libslic3r/EmbossShape.hpp index f61c9844fc..3c35bf2b64 100644 --- a/src/libslic3r/EmbossShape.hpp +++ b/src/libslic3r/EmbossShape.hpp @@ -73,7 +73,7 @@ struct EmbossShape ExPolygonsWithIds shapes_with_ids; // scale of shape, multiplier to get 3d point in mm from integer shape - double scale = 1.; + double scale = SCALING_FACTOR; // Define how to emboss shape EmbossProjection projection; @@ -98,6 +98,9 @@ struct EmbossShape // Loaded svg file data. // !!! It is not serialized on undo/redo stack std::shared_ptr image = nullptr; + + // Loaded string data from file + std::shared_ptr file_data = nullptr; }; SvgFile svg_file; diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 5576b5e68d..02ab5d9723 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -39,6 +39,8 @@ namespace pt = boost::property_tree; #include "EmbossShape.hpp" #include "ExPolygonSerialize.hpp" +#include "NSVGUtils.hpp" + #include // Slightly faster than sprintf("%.9g"), but there is an issue with the karma floating point formatter, @@ -173,9 +175,9 @@ static constexpr const char *FONT_WEIGHT_ATTR = "weight"; // Store / load of EmbossShape static constexpr const char *SHAPE_TAG = "slic3rpe:shape"; -static constexpr const char *SHAPE_EXPOLYS_ATTR = "expolygons"; static constexpr const char *SHAPE_SCALE_ATTR = "scale"; static constexpr const char *SVG_FILE_PATH_ATTR = "filepath"; +static constexpr const char *SVG_FILE_PATH_IN_3MF_ATTR = "filepath3mf"; // EmbossProjection static constexpr const char *DEPTH_ATTR = "depth"; @@ -467,7 +469,7 @@ namespace Slic3r { typedef std::map IdToCutObjectInfoMap; typedef std::map> IdToSlaSupportPointsMap; typedef std::map> IdToSlaDrainHolesMap; - + using PathToEmbossShapeFileMap = std::map>; // Version of the 3mf file unsigned int m_version; bool m_check_version; @@ -497,6 +499,7 @@ namespace Slic3r { IdToLayerConfigRangesMap m_layer_config_ranges; IdToSlaSupportPointsMap m_sla_support_points; IdToSlaDrainHolesMap m_sla_drain_holes; + PathToEmbossShapeFileMap m_path_to_emboss_shape_files; std::string m_curr_metadata_name; std::string m_curr_characters; std::string m_name; @@ -523,6 +526,7 @@ namespace Slic3r { } bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions); + bool _is_svg_shape_file(const std::string &filename); bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_cut_information_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions); void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); @@ -534,6 +538,7 @@ namespace Slic3r { void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, ConfigSubstitutionContext& subs_context, const std::string& archive_filename); bool _extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model); + void _extract_embossed_svg_shape_file(const std::string &filename, mz_zip_archive &archive, const mz_zip_archive_file_stat &stat); // handlers to parse the .model file void _handle_start_model_xml_element(const char* name, const char** attributes); @@ -761,6 +766,9 @@ namespace Slic3r { add_error("Archive does not contain a valid model config"); return false; } + } + else if (_is_svg_shape_file(name)) { + _extract_embossed_svg_shape_file(name, archive, stat); } } } @@ -936,6 +944,10 @@ namespace Slic3r { return true; } + bool _3MF_Importer::_is_svg_shape_file(const std::string &name) { + return name._Starts_with(MODEL_FOLDER) && boost::algorithm::ends_with(name, ".svg"); + } + bool _3MF_Importer::_extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) { if (stat.m_uncomp_size == 0) { @@ -1368,6 +1380,36 @@ namespace Slic3r { } } + void _3MF_Importer::_extract_embossed_svg_shape_file(const std::string &filename, mz_zip_archive &archive, const mz_zip_archive_file_stat &stat){ + assert(m_path_to_emboss_shape_files.find(filename) == m_path_to_emboss_shape_files.end()); + + std::unique_ptr file{new char[stat.m_uncomp_size + 1]}; + if (file == nullptr){ + add_error("Cannot alocate space for SVG file."); + return; + } + + mz_bool res = mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, (void *) file.get(), (size_t) stat.m_uncomp_size, 0); + if (res == 0) { + add_error("Error while reading svg shape for emboss"); + return; + } + file.get()[stat.m_uncomp_size] = '\0'; // Must be null terminated. + + // store for case svg is loaded before volume + m_path_to_emboss_shape_files[filename] = std::move(file); + + // find embossed volume, for case svg is loaded after volume + for (ModelObject* object : m_model->objects) + for (ModelVolume *volume : object->volumes) { + std::optional &es = volume->emboss_shape; + if (!es.has_value()) + continue; + if (filename.compare(es->svg_file.path_in_3mf) == 0) + es->svg_file.file_data = m_path_to_emboss_shape_files[filename]; + } + } + bool _3MF_Importer::_extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model) { if (stat.m_uncomp_size == 0) { @@ -1977,7 +2019,7 @@ namespace Slic3r { } // Definition of read/write method for EmbossShape - static void to_xml(std::stringstream &stream, const EmbossShape &es, const ModelVolume& volume); + static void to_xml(std::stringstream &stream, const EmbossShape &es, const ModelVolume &volume, mz_zip_archive &archive); static std::optional read_emboss_shape(const char **attributes, unsigned int num_attributes); bool _3MF_Importer::_handle_start_shape_configuration(const char **attributes, unsigned int num_attributes) @@ -1994,7 +2036,21 @@ namespace Slic3r { } ObjectMetadata::VolumeMetadata &volume = volumes.back(); volume.shape_configuration = read_emboss_shape(attributes, num_attributes); - return volume.shape_configuration.has_value(); + if (!volume.shape_configuration.has_value()) + return false; + + // Fill svg file content into shape_configuration + EmbossShape::SvgFile &svg = volume.shape_configuration->svg_file; + const std::string &path = svg.path_in_3mf; + if (path.empty()) // do not contain svg file + return true; + + auto it = m_path_to_emboss_shape_files.find(path); + if (it == m_path_to_emboss_shape_files.end()) + return true; // svg file is not loaded yet + + svg.file_data = it->second; + return true; } bool _3MF_Importer::_create_object_instance(int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter) @@ -2413,6 +2469,7 @@ namespace Slic3r { bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64); static void add_transformation(std::stringstream &stream, const Transform3d &tr); private: + void _publish(Model &model); bool _save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data); bool _add_content_types_file_to_archive(mz_zip_archive& archive); bool _add_thumbnail_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data); @@ -3359,7 +3416,7 @@ namespace Slic3r { if (const std::optional &es = volume->emboss_shape; es.has_value()) - to_xml(stream, *es, *volume); + to_xml(stream, *es, *volume, archive); if (const std::optional &tc = volume->text_configuration; tc.has_value()) @@ -3681,22 +3738,38 @@ Transform3d create_fix(const std::optional &prev, const ModelVolume return *prev * fix_trmat; } -std::string to_string(const ExPolygonsWithIds &shapes) -{ - // TODO: Need to implement - return {}; +bool to_xml(std::stringstream &stream, const EmbossShape::SvgFile &svg, const ModelVolume &volume, mz_zip_archive &archive){ + assert(!svg.path_in_3mf.empty()); + if (svg.path_in_3mf.empty()) + return false; // unwanted store .svg file into .3mf (protection of copyRight) + + if (!svg.path.empty()) + stream << SVG_FILE_PATH_ATTR << "=\"" << xml_escape_double_quotes_attribute_value(svg.path) << "\" "; + stream << SVG_FILE_PATH_IN_3MF_ATTR << "=\"" << xml_escape_double_quotes_attribute_value(svg.path_in_3mf) << "\" "; + + char *data = svg.file_data.get(); + assert(data != nullptr); + if (data == nullptr) + return false; + + // NOTE: file data must be null terminated + size_t size = 0; + for (char *c = data; *c != '\0'; ++c) ++size; + if (!mz_zip_writer_add_mem(&archive, svg.path_in_3mf.c_str(), (const void *) data, size, MZ_DEFAULT_COMPRESSION)) + return false; + return true; } } // namespace -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) << "\" "; +void to_xml(std::stringstream &stream, const EmbossShape &es, const ModelVolume &volume, mz_zip_archive &archive) +{ + stream << " <" << SHAPE_TAG << " "; + if(!to_xml(stream, es.svg_file, volume, archive)) + BOOST_LOG_TRIVIAL(warning) << "Can't write svg file defiden embossed shape into 3mf"; + stream << SHAPE_SCALE_ATTR << "=\"" << es.scale << "\" "; - std::string expolygons_str = to_string(es.shapes_with_ids); // cereal serialize expolygons - stream << SHAPE_EXPOLYS_ATTR << "=\"" << xml_escape_double_quotes_attribute_value(expolygons_str) << "\" "; - // projection const EmbossProjection &p = es.projection; stream << DEPTH_ATTR << "=\"" << p.depth << "\" "; @@ -3708,11 +3781,11 @@ void to_xml(std::stringstream &stream, const EmbossShape &es, const ModelVolume stream << TRANSFORM_ATTR << "=\""; _3MF_Exporter::add_transformation(stream, fix); stream << "\" "; - stream << "/>\n"; // end SHAPE_TAG + + stream << "/>\n"; // end SHAPE_TAG } -std::optional read_emboss_shape(const char **attributes, unsigned int num_attributes) { - +std::optional read_emboss_shape(const char **attributes, unsigned int num_attributes) { double scale = get_attribute_value_float(attributes, num_attributes, SHAPE_SCALE_ATTR); EmbossProjection projection; @@ -3726,9 +3799,13 @@ std::optional read_emboss_shape(const char **attributes, unsigned i if (!fix_tr_mat_str.empty()) { fix_tr_mat = get_transform_from_3mf_specs_string(fix_tr_mat_str); } + std::string file_path = get_attribute_value_string(attributes, num_attributes, SVG_FILE_PATH_ATTR); + std::string file_path_3mf = get_attribute_value_string(attributes, num_attributes, SVG_FILE_PATH_IN_3MF_ATTR); ExPolygonsWithIds shapes; // TODO: need to implement - return EmbossShape{shapes, scale, std::move(projection), std::move(fix_tr_mat), std::move(file_path)}; + + EmbossShape::SvgFile svg{file_path, file_path_3mf}; + return EmbossShape{shapes, scale, std::move(projection), std::move(fix_tr_mat), std::move(svg)}; } diff --git a/src/libslic3r/NSVGUtils.cpp b/src/libslic3r/NSVGUtils.cpp index c3546aaf9b..9b742a9030 100644 --- a/src/libslic3r/NSVGUtils.cpp +++ b/src/libslic3r/NSVGUtils.cpp @@ -54,13 +54,43 @@ NSVGimage_ptr nsvgParseFromFile(const std::string &filename, const char *units, return {image, ::nsvgDelete}; } -bool save(const NSVGimage &image, const std::string &svg_file_path) +std::unique_ptr read_from_disk(const std::string& path) { - FILE * file = boost::nowide::fopen(svg_file_path.c_str(), "w"); - if (file == NULL) - return false; + FILE *fp = boost::nowide::fopen(path.c_str(), "rb"); + if (!fp) + return nullptr; - fprintf(file, ""); + fseek(fp, 0, SEEK_END); + size_t size = ftell(fp); + fseek(fp, 0, SEEK_SET); + + std::unique_ptr result{new char[size + 1]}; + if (result == nullptr) + return nullptr; + + if (fread(result.get(), 1, size, fp) != size) + return nullptr; + + result.get()[size] = '\0'; // Must be null terminated. + fclose(fp); + return result; +} + +NSVGimage_ptr nsvgParse(const std::shared_ptr file_data, const char *units, float dpi){ + size_t size = 0; + for (char *c = file_data.get(); *c != '\0'; ++c) + ++size; + + // NOTE: nsvg parser consume data from pointer + std::unique_ptr data_copy(new char[size]); + memcpy(data_copy.get(), file_data.get(), size); + NSVGimage *image = ::nsvgParse(data_copy.get(), units, dpi); + return {image, ::nsvgDelete}; +} + +void save(const NSVGimage &image, std::ostream &data) +{ + data << ""; // tl .. top left Vec2f tl(std::numeric_limits::max(), std::numeric_limits::max()); @@ -76,9 +106,11 @@ bool save(const NSVGimage &image, const std::string &svg_file_path) Vec2f s = br - tl; Point size = s.cast(); - fprintf(file, "\n", - size.x(), size.y(), size.x(), size.y()); - fprintf(file, "\n"); + data << "\n"; + data << "\n"; std::array buffer; auto write_point = [&tl, &buffer](std::string &d, const float *p) { @@ -143,10 +175,18 @@ bool save(const NSVGimage &image, const std::string &svg_file_path) type = Type::close; d += "Z"; // closed path } - fprintf(file, "\n", d.c_str()); + data << "\n"; } - fprintf(file, "\n"); - fclose(file); + data << "\n"; +} + +bool save(const NSVGimage &image, const std::string &svg_file_path) +{ + std::ofstream file{svg_file_path}; + if (!file.is_open()) + return false; + save(image, file); + return true; } } // namespace Slic3r diff --git a/src/libslic3r/NSVGUtils.hpp b/src/libslic3r/NSVGUtils.hpp index d3f815acf4..69f685874e 100644 --- a/src/libslic3r/NSVGUtils.hpp +++ b/src/libslic3r/NSVGUtils.hpp @@ -3,6 +3,7 @@ #include #include +#include #include "Polygon.hpp" #include "ExPolygon.hpp" #include "nanosvg/nanosvg.h" // load SVG file @@ -26,8 +27,14 @@ ExPolygons to_expolygons(const NSVGimage &image, float tessTol = 10., int max_le void bounds(const NSVGimage &image, Vec2f &min, Vec2f &max); +// read text data from file +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::shared_ptr file_data, const char *units = "mm", float dpi = 96.0f); + +void save(const NSVGimage &image, std::ostream &data); bool save(const NSVGimage &image, const std::string &svg_file_path); } // namespace Slic3r #endif // slic3r_NSVGUtils_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp index 7caed87778..89b4d0c5d2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp @@ -1229,7 +1229,14 @@ bool GLGizmoSVG::draw_preview(){ if (dlg.ShowModal() == wxID_OK ){ wxString out_path = dlg.GetPath(); std::string path{out_path.c_str()}; - Slic3r::save(*m_volume_shape.svg_file.image, path); + //Slic3r::save(*m_volume_shape.svg_file.image, path); + + std::ofstream stream(path); + if (stream.is_open()){ + stream << svg.file_data.get(); + } else { + BOOST_LOG_TRIVIAL(error) << "Opening file: \"" << path << "\" Failed"; + } } } else if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", _u8L("Save as '.svg' file").c_str()); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 3ba8bfcd50..312d6572c7 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -6666,6 +6666,103 @@ void Plater::export_amf() } } +namespace { +std::string get_file_name(const std::string &file_path) +{ + size_t pos_last_delimiter = file_path.find_last_of("/\\"); + size_t pos_point = file_path.find_last_of('.'); + size_t offset = pos_last_delimiter + 1; + size_t count = pos_point - pos_last_delimiter - 1; + return file_path.substr(offset, count); +} +using SvgFile = EmbossShape::SvgFile; +using SvgFiles = std::vector; +std::string create_unique_3mf_filepath(const std::string &file, const SvgFiles svgs) +{ + // const std::string MODEL_FOLDER = "3D/"; // copy from file 3mf.cpp + std::string path_in_3mf = "3D/" + file + ".svg"; + size_t suffix_number = 0; + bool is_unique = false; + do{ + is_unique = true; + path_in_3mf = "3D/" + file + ((suffix_number++)? ("_" + std::to_string(suffix_number)) : "") + ".svg"; + for (SvgFile *svgfile : svgs) { + if (svgfile->path_in_3mf.empty()) + continue; + if (svgfile->path_in_3mf.compare(path_in_3mf) == 0) { + is_unique = false; + break; + } + } + } while (!is_unique); + return path_in_3mf; +} + +bool set_by_local_path(SvgFile &svg, const SvgFiles& svgs) +{ + // Try to find already used svg file + for (SvgFile *svg_ : svgs) { + if (svg_->path_in_3mf.empty()) + continue; + if (svg.path.compare(svg_->path) == 0) { + svg.path_in_3mf = svg_->path_in_3mf; + return true; + } + } + return false; +} + +/// +/// Function to secure private data before store to 3mf +/// +/// Data(also private) to clean before publishing +void publish(Model &model) { + + // SVG file publishing + bool exist_new = false; + SvgFiles svgfiles; + for (ModelObject *object: model.objects){ + for (ModelVolume *volume : object->volumes) { + if (!volume->emboss_shape.has_value()) + continue; + SvgFile* svg = &volume->emboss_shape->svg_file; + + if (svg->path_in_3mf.empty()) + exist_new = true; + svgfiles.push_back(svg); + } + } + + if (exist_new){ + MessageDialog dialog(nullptr, + _L("Are you sure you want to store original SVGs with their local path into .3mf ?\n " + "When you hit 'NO', all Embossed shape will not be editable any more."), + _L("Private protection"), wxYES_NO | wxICON_QUESTION); + if (dialog.ShowModal() == wxID_NO){ + for (ModelObject *object : model.objects) + for (ModelVolume *volume : object->volumes) + if (volume->emboss_shape.has_value()) + volume->emboss_shape.reset(); + } + } + + for (SvgFile* svgfile : svgfiles){ + if (!svgfile->path_in_3mf.empty()) + continue; // already suggested path (previous save) + + // create unique name for svgs, when local path differ + std::string filename = "unknown"; + if (!svgfile->path.empty()) { + if (set_by_local_path(*svgfile, svgfiles)) + continue; + // check whether original filename is already in: + filename = get_file_name(svgfile->path); + } + svgfile->path_in_3mf = create_unique_3mf_filepath(filename, svgfiles); + } +} +} + bool Plater::export_3mf(const boost::filesystem::path& output_path) { if (p->model.objects.empty()) { @@ -6686,6 +6783,10 @@ bool Plater::export_3mf(const boost::filesystem::path& output_path) if (!path.Lower().EndsWith(".3mf")) return false; + // take care about private data stored into .3mf + // modify model + publish(p->model); + DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure(); const std::string path_u8 = into_u8(path); wxBusyCursor wait;