diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index ff3cf777d..ee40adb56 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -51,6 +51,7 @@ const std::string MODEL_CONFIG_FILE = "Metadata/Slic3r_PE_model.config"; const std::string LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights_profile.txt"; const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/Prusa_Slicer_layer_config_ranges.xml"; const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt"; +const std::string SLA_DRAIN_HOLES_FILE = "Metadata/Slic3r_PE_sla_drain_holes.txt"; const char* MODEL_TAG = "model"; const char* RESOURCES_TAG = "resources"; @@ -227,6 +228,15 @@ bool is_valid_object_type(const std::string& type) return false; } +template std::string string_printf(const char *const fmt, Args &&...args) +{ + int bufflen = snprintf(nullptr, 0, fmt, std::forward(args)...); + std::vector buffer(size_t(bufflen), '\0'); + + snprintf(buffer.data(), buffer.size(), fmt, std::forward(args)...); + return std::string(buffer.begin(), buffer.end()); +} + namespace Slic3r { //! macro used to mark string used at localization, @@ -379,6 +389,7 @@ namespace Slic3r { typedef std::map> IdToLayerHeightsProfileMap; typedef std::map IdToLayerConfigRangesMap; typedef std::map> IdToSlaSupportPointsMap; + typedef std::map> IdToSlaDrainHolesMap; // Version of the 3mf file unsigned int m_version; @@ -397,6 +408,7 @@ namespace Slic3r { IdToLayerHeightsProfileMap m_layer_heights_profiles; IdToLayerConfigRangesMap m_layer_config_ranges; IdToSlaSupportPointsMap m_sla_support_points; + IdToSlaDrainHolesMap m_sla_drain_holes; std::string m_curr_metadata_name; std::string m_curr_characters; std::string m_name; @@ -416,6 +428,7 @@ namespace Slic3r { void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); + void _extract_sla_drain_holes_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, const std::string& archive_filename); bool _extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model); @@ -621,6 +634,11 @@ namespace Slic3r { // extract sla support points file _extract_sla_support_points_from_archive(archive, stat); } + else if (boost::algorithm::iequals(name, SLA_DRAIN_HOLES_FILE)) + { + // extract sla support points file + _extract_sla_drain_holes_from_archive(archive, stat); + } else if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE)) { // extract slic3r print config file @@ -670,6 +688,11 @@ namespace Slic3r { model_object->sla_support_points = obj_sla_support_points->second; model_object->sla_points_status = sla::PointsStatus::UserModified; } + + IdToSlaDrainHolesMap::iterator obj_drain_holes = m_sla_drain_holes.find(object.second + 1); + if (obj_drain_holes != m_sla_drain_holes.end() && !obj_drain_holes->second.empty()) { + model_object->sla_drain_holes = obj_drain_holes->second; + } IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first); if (obj_metadata != m_objects_metadata.end()) @@ -942,8 +965,9 @@ namespace Slic3r { // Info on format versioning - see 3mf.hpp int version = 0; - if (!objects.empty() && objects[0].find("support_points_format_version=") != std::string::npos) { - objects[0].erase(objects[0].begin(), objects[0].begin() + 30); // removes the string + std::string key("support_points_format_version="); + if (!objects.empty() && objects[0].find(key) != std::string::npos) { + objects[0].erase(objects[0].begin(), objects[0].begin() + long(key.size())); // removes the string version = std::stoi(objects[0]); objects.erase(objects.begin()); // pop the header } @@ -1009,6 +1033,90 @@ namespace Slic3r { } } } + + void _3MF_Importer::_extract_sla_drain_holes_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) + { + if (stat.m_uncomp_size > 0) + { + std::string buffer(size_t(stat.m_uncomp_size), 0); + mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == 0) + { + add_error("Error while reading sla support points data to buffer"); + return; + } + + if (buffer.back() == '\n') + buffer.pop_back(); + + std::vector objects; + boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off); + + // Info on format versioning - see 3mf.hpp + int version = 0; + std::string key("drain_holes_format_version="); + if (!objects.empty() && objects[0].find(key) != std::string::npos) { + objects[0].erase(objects[0].begin(), objects[0].begin() + long(key.size())); // removes the string + version = std::stoi(objects[0]); + objects.erase(objects.begin()); // pop the header + } + + for (const std::string& object : objects) + { + std::vector object_data; + boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off); + + if (object_data.size() != 2) + { + add_error("Error while reading object data"); + continue; + } + + std::vector object_data_id; + boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off); + if (object_data_id.size() != 2) + { + add_error("Error while reading object id"); + continue; + } + + int object_id = std::atoi(object_data_id[1].c_str()); + if (object_id == 0) + { + add_error("Found invalid object id"); + continue; + } + + IdToSlaDrainHolesMap::iterator object_item = m_sla_drain_holes.find(object_id); + if (object_item != m_sla_drain_holes.end()) + { + add_error("Found duplicated SLA drain holes"); + continue; + } + + std::vector object_data_points; + boost::split(object_data_points, object_data[1], boost::is_any_of(" "), boost::token_compress_off); + + sla::DrainHoles sla_drain_holes; + + if (version == 1) { + for (unsigned int i=0; isla_drain_holes; + if (!drain_holes.empty()) + { + out += string_printf(fmt, count); + + // Store the layer height profile as a single space separated list. + for (size_t i = 0; i < drain_holes.size(); ++i) + out += string_printf((i == 0 ? "%f %f %f %f %f %f %f %f" : " %f %f %f %f %f %f %f %f"), + drain_holes[i].pos(0), + drain_holes[i].pos(1), + drain_holes[i].pos(2), + drain_holes[i].normal(0), + drain_holes[i].normal(1), + drain_holes[i].normal(2), + drain_holes[i].radius, + drain_holes[i].height); + + out += "\n"; + } + } + + if (!out.empty()) + { + // Adds version header at the beginning: + out = std::string("drain_holes_format_version=") + std::to_string(drain_holes_format_version) + std::string("\n") + out; + + if (!mz_zip_writer_add_mem(&archive, SLA_DRAIN_HOLES_FILE.c_str(), static_cast(out.data()), out.length(), mz_uint(MZ_DEFAULT_COMPRESSION))) + { + add_error("Unable to add sla support points file to archive"); + return false; + } + } + return true; + } bool _3MF_Exporter::_add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config) { diff --git a/src/libslic3r/Format/3mf.hpp b/src/libslic3r/Format/3mf.hpp index 2e85b7f59..7da0ce7fc 100644 --- a/src/libslic3r/Format/3mf.hpp +++ b/src/libslic3r/Format/3mf.hpp @@ -19,6 +19,10 @@ namespace Slic3r { enum { support_points_format_version = 1 }; + + enum { + drain_holes_format_version = 1 + }; class Model; class DynamicPrintConfig; diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index afbf94fa3..f2dd00114 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -183,6 +183,22 @@ static inline bool is_approx(Number value, Number test_value) return std::fabs(double(value) - double(test_value)) < double(EPSILON); } +template +std::string string_printf(const char *const fmt, Args &&...args) +{ + static const size_t INITIAL_LEN = 1024; + std::vector buffer(INITIAL_LEN, '\0'); + + int bufflen = snprintf(buffer.data(), INITIAL_LEN - 1, fmt, std::forward(args)...); + + if (bufflen >= int(INITIAL_LEN)) { + buffer.resize(size_t(bufflen) + 1); + snprintf(buffer.data(), buffer.size(), fmt, std::forward(args)...); + } + + return std::string(buffer.begin(), buffer.begin() + bufflen); +} + } // namespace Slic3r #endif diff --git a/tests/libslic3r/libslic3r_tests.cpp b/tests/libslic3r/libslic3r_tests.cpp index caf5b3b9a..b9c615d90 100644 --- a/tests/libslic3r/libslic3r_tests.cpp +++ b/tests/libslic3r/libslic3r_tests.cpp @@ -11,4 +11,31 @@ TEST_CASE("sort_remove_duplicates", "[utils]") { REQUIRE(data_src == data_dst); } +TEST_CASE("string_printf", "[utils]") { + SECTION("Empty format with empty data should return empty string") { + std::string outs = Slic3r::string_printf(""); + REQUIRE(outs.empty()); + } + + SECTION("String output length should be the same as input") { + std::string outs = Slic3r::string_printf("1234"); + REQUIRE(outs.size() == 4); + } + + SECTION("String format should be interpreted as with sprintf") { + std::string outs = Slic3r::string_printf("%d %f %s", 10, 11.4, " This is a string"); + char buffer[1024]; + + sprintf(buffer, "%d %f %s", 10, 11.4, " This is a string"); + + REQUIRE(outs.compare(buffer) == 0); + } + + SECTION("String format should survive large input data") { + std::string input(2048, 'A'); + std::string outs = Slic3r::string_printf("%s", input.c_str()); + REQUIRE(outs.compare(input) == 0); + } +} + }