Store shape as svg into 3mf

Without obfuscation of svg.
This commit is contained in:
Filip Sykala - NTB T15p 2023-08-02 22:34:33 +02:00
parent f36330df4f
commit 480c571499
6 changed files with 267 additions and 32 deletions

View File

@ -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<NSVGimage> image = nullptr;
// Loaded string data from file
std::shared_ptr<char[]> file_data = nullptr;
};
SvgFile svg_file;

View File

@ -39,6 +39,8 @@ namespace pt = boost::property_tree;
#include "EmbossShape.hpp"
#include "ExPolygonSerialize.hpp"
#include "NSVGUtils.hpp"
#include <fast_float/fast_float.h>
// 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<int, CutObjectInfo> IdToCutObjectInfoMap;
typedef std::map<int, std::vector<sla::SupportPoint>> IdToSlaSupportPointsMap;
typedef std::map<int, std::vector<sla::DrainHole>> IdToSlaDrainHolesMap;
using PathToEmbossShapeFileMap = std::map<std::string, std::shared_ptr<char[]>>;
// 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<char[]> 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<EmbossShape> &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<EmbossShape> 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<EmbossShape> &es = volume->emboss_shape;
es.has_value())
to_xml(stream, *es, *volume);
to_xml(stream, *es, *volume, archive);
if (const std::optional<TextConfiguration> &tc = volume->text_configuration;
tc.has_value())
@ -3681,22 +3738,38 @@ Transform3d create_fix(const std::optional<Transform3d> &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<EmbossShape> read_emboss_shape(const char **attributes, unsigned int num_attributes) {
std::optional<EmbossShape> 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<EmbossShape> 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)};
}

View File

@ -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<char[]> 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, "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>");
fseek(fp, 0, SEEK_END);
size_t size = ftell(fp);
fseek(fp, 0, SEEK_SET);
std::unique_ptr<char[]> 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<char[]> 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<char[]> 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 << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>";
// tl .. top left
Vec2f tl(std::numeric_limits<float>::max(), std::numeric_limits<float>::max());
@ -76,9 +106,11 @@ bool save(const NSVGimage &image, const std::string &svg_file_path)
Vec2f s = br - tl;
Point size = s.cast<Point::coord_type>();
fprintf(file, "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"%dmm\" height=\"%dmm\" viewBox=\"0 0 %d %d\" >\n",
size.x(), size.y(), size.x(), size.y());
fprintf(file, "<!-- Created with PrusaSlicer (https://www.prusa3d.com/prusaslicer/) -->\n");
data << "<svg xmlns=\"http://www.w3.org/2000/svg\" "
<< "width=\"" << size.x() << "mm\" "
<< "height=\"" << size.y() << "mm\" "
<< "viewBox=\"0 0 " << size.x() << " " << size.y() << "\" >\n";
data << "<!-- Created with PrusaSlicer (https://www.prusa3d.com/prusaslicer/) -->\n";
std::array<char, 128> 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, "<path fill=\"#D2D2D2\" d=\"%s\" />\n", d.c_str());
data << "<path fill=\"#D2D2D2\" d=\"" << d << "\" />\n";
}
fprintf(file, "</svg>\n");
fclose(file);
data << "</svg>\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

View File

@ -3,6 +3,7 @@
#include <memory>
#include <string>
#include <sstream>
#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<char[]> read_from_disk(const std::string &path);
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);
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_

View File

@ -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());

View File

@ -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<SvgFile*>;
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;
}
/// <summary>
/// Function to secure private data before store to 3mf
/// </summary>
/// <param name="model">Data(also private) to clean before publishing</param>
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;