From ae7b205e1007191aaafd38d5a4dddcc939c1085a Mon Sep 17 00:00:00 2001 From: "lane.wei" Date: Sat, 9 Sep 2023 18:57:35 +0800 Subject: [PATCH] ENH: 3mf: refine the rels to correct link JIRA: STUDIO-4231 Change-Id: I02ff6343124ef2ea3642e5799469249538a4b97f --- src/BambuStudio.cpp | 71 ++++++++++++++++++++++++++++++-- src/BambuStudio.hpp | 2 +- src/libslic3r/Format/bbs_3mf.cpp | 59 +++++++++++++++++--------- src/libslic3r/PNGReadWrite.cpp | 70 ++++++++++++++++++++++++++++++- src/libslic3r/PNGReadWrite.hpp | 50 ++++++++++++++-------- 5 files changed, 208 insertions(+), 44 deletions(-) diff --git a/src/BambuStudio.cpp b/src/BambuStudio.cpp index f320ca6bb..a6aa9d788 100644 --- a/src/BambuStudio.cpp +++ b/src/BambuStudio.cpp @@ -67,6 +67,7 @@ using namespace nlohmann; #include "libslic3r/FlushVolCalc.hpp" #include "libslic3r/Orient.hpp" +#include "libslic3r/PNGReadWrite.hpp" #include "BambuStudio.hpp" //BBS: add exception handler for win32 @@ -422,6 +423,39 @@ void record_exit_reson(std::string outputdir, int code, int plate_id, std::strin #endif } +static int decode_png_to_thumbnail(std::string png_file, ThumbnailData& thumbnail_data) +{ + if (!boost::filesystem::exists(png_file)) + { + BOOST_LOG_TRIVIAL(error) << boost::format("can not find file %1%")%png_file; + return -1; + } + + const std::size_t &size = boost::filesystem::file_size(png_file); + std::string png_buffer(size, '\0'); + png_buffer.reserve(size); + + boost::filesystem::ifstream ifs(png_file, std::ios::binary); + ifs.read(png_buffer.data(), png_buffer.size()); + ifs.close(); + + Slic3r::png::ImageColorscale img; + Slic3r::png::ReadBuf rb{png_buffer.data(), png_buffer.size()}; + BOOST_LOG_TRIVIAL(info) << boost::format("read png file %1%, size %2%")%png_file %size; + + if ( !Slic3r::png::decode_colored_png(rb, img)) + { + BOOST_LOG_TRIVIAL(error) << boost::format("decode png file %1% failed")%png_file; + return -2; + } + + thumbnail_data.width = img.cols; + thumbnail_data.height = img.rows; + thumbnail_data.pixels = std::move(img.buf); + + return 0; +} + static void glfw_callback(int error_code, const char* description) { BOOST_LOG_TRIVIAL(error) << "error_code " <plate_thumbnail.reset(); } - else + else { BOOST_LOG_TRIVIAL(info) << boost::format("plate %1% has a valid thumbnail, width %2%, height %3%, directly using it")%(i+1) %plate_data->plate_thumbnail.width %plate_data->plate_thumbnail.height; + } } else if (!plate_data->thumbnail_file.empty() && (boost::filesystem::exists(plate_data->thumbnail_file))) { @@ -3625,8 +3660,17 @@ int CLI::run(int argc, char **argv) BOOST_LOG_TRIVIAL(info) << boost::format("Line %1%: regenerate thumbnail, clear plate %2%'s thumbnail file path to empty.")%__LINE__%(i+1); plate_data->thumbnail_file.clear(); } - else + else { BOOST_LOG_TRIVIAL(info) << boost::format("plate %1% has a valid thumbnail %2% extracted from 3mf, directly using it")%(i+1) %plate_data->thumbnail_file; + int dec_ret = decode_png_to_thumbnail(plate_data->thumbnail_file, plate_data->plate_thumbnail); + if (!dec_ret) + { + BOOST_LOG_TRIVIAL(info) << boost::format("decode png to mem sucess."); + } + else { + BOOST_LOG_TRIVIAL(warning) << boost::format("decode png to mem failed."); + } + } } else { ThumbnailData* thumbnail_data = &plate_data->plate_thumbnail; @@ -3774,6 +3818,24 @@ int CLI::run(int argc, char **argv) plate_data->top_file.clear(); plate_data->pick_file.clear(); } + else if (!plate_data->plate_thumbnail.is_valid() && !plate_data->thumbnail_file.empty() && (boost::filesystem::exists(plate_data->thumbnail_file))) + { + BOOST_LOG_TRIVIAL(info) << boost::format("no need to generate: plate %1% has a valid thumbnail %2% extracted from 3mf, convert to data")%(i+1) %plate_data->thumbnail_file; + int dec_ret = decode_png_to_thumbnail(plate_data->thumbnail_file, plate_data->plate_thumbnail); + if (!dec_ret) + { + BOOST_LOG_TRIVIAL(info) << boost::format("decode png to mem sucess."); + need_create_thumbnail_group = true; + } + else { + BOOST_LOG_TRIVIAL(warning) << boost::format("decode png to mem failed."); + } + } + } + + for (int i = 0; i < partplate_list.get_plate_count(); i++) { + PlateData *plate_data = plate_data_list[i]; + Slic3r::GUI::PartPlate *part_plate = partplate_list.get_plate(i); if (need_create_thumbnail_group) { thumbnails.push_back(&plate_data->plate_thumbnail); @@ -3919,7 +3981,7 @@ int CLI::run(int argc, char **argv) BOOST_LOG_TRIVIAL(info) << "will export 3mf to " << export_3mf_file << std::endl; if (! this->export_project(&m_models[0], export_3mf_file, plate_data_list, project_presets, thumbnails, top_thumbnails, pick_thumbnails, - calibration_thumbnails, plate_bboxes, &m_print_config, minimum_save)) + calibration_thumbnails, plate_bboxes, &m_print_config, minimum_save, plate_to_slice - 1)) { release_PlateData_list(plate_data_list); record_exit_reson(outfile_dir, CLI_EXPORT_3MF_ERROR, 0, cli_errors[CLI_EXPORT_3MF_ERROR], sliced_info); @@ -4141,7 +4203,7 @@ bool CLI::export_models(IO::ExportFormat format) //BBS: add export_project function bool CLI::export_project(Model *model, std::string& path, PlateDataPtrs &partplate_data, std::vector& project_presets, std::vector& thumbnails, std::vector& top_thumbnails, std::vector& pick_thumbnails, - std::vector& calibration_thumbnails, std::vector& plate_bboxes, const DynamicPrintConfig* config, bool minimum_save) + std::vector& calibration_thumbnails, std::vector& plate_bboxes, const DynamicPrintConfig* config, bool minimum_save, int plate_to_export) { //const std::string path = this->output_filepath(*model, IO::TMF); bool success = false; @@ -4158,6 +4220,7 @@ bool CLI::export_project(Model *model, std::string& path, PlateDataPtrs &partpla store_params.calibration_thumbnail_data = calibration_thumbnails; store_params.id_bboxes = plate_bboxes; store_params.strategy = SaveStrategy::Silence|SaveStrategy::WithGcode|SaveStrategy::SplitModel|SaveStrategy::UseLoadedId|SaveStrategy::ShareMesh; + store_params.export_plate_idx = plate_to_export; if (minimum_save) store_params.strategy = store_params.strategy | SaveStrategy::SkipModel; diff --git a/src/BambuStudio.hpp b/src/BambuStudio.hpp index ba4fcf969..66356e6d5 100644 --- a/src/BambuStudio.hpp +++ b/src/BambuStudio.hpp @@ -41,7 +41,7 @@ private: bool export_project(Model *model, std::string& path, PlateDataPtrs &partplate_data, std::vector& project_presets, std::vector& thumbnails, std::vector& top_thumbnails, std::vector& pick_thumbnails, std::vector& calibration_thumbnails, - std::vector& plate_bboxes, const DynamicPrintConfig* config, bool minimum_save); + std::vector& plate_bboxes, const DynamicPrintConfig* config, bool minimum_save, int plate_to_export = -1); bool has_print_action() const { return m_config.opt_bool("export_gcode") || m_config.opt_bool("export_sla"); } diff --git a/src/libslic3r/Format/bbs_3mf.cpp b/src/libslic3r/Format/bbs_3mf.cpp index d7aeb7da6..50342b0bd 100644 --- a/src/libslic3r/Format/bbs_3mf.cpp +++ b/src/libslic3r/Format/bbs_3mf.cpp @@ -3181,7 +3181,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) m_curr_object->name = bbs_get_attribute_value_string(attributes, num_attributes, NAME_ATTR); m_curr_object->uuid = bbs_get_attribute_value_string(attributes, num_attributes, PUUID_ATTR); - if (m_curr_object->uuid.empty()) { + if (m_curr_object->uuid.empty()) { m_curr_object->uuid = bbs_get_attribute_value_string(attributes, num_attributes, PUUID_LOWER_ATTR); } m_curr_object->pid = bbs_get_attribute_value_int(attributes, num_attributes, PID_ATTR); @@ -4722,8 +4722,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) current_object->name = bbs_get_attribute_value_string(attributes, num_attributes, NAME_ATTR); current_object->uuid = bbs_get_attribute_value_string(attributes, num_attributes, PUUID_ATTR); - if (current_object->uuid.empty()) { - current_object->uuid = bbs_get_attribute_value_string(attributes, num_attributes, PUUID_LOWER_ATTR); + if (current_object->uuid.empty()) { + current_object->uuid = bbs_get_attribute_value_string(attributes, num_attributes, PUUID_LOWER_ATTR); } current_object->pid = bbs_get_attribute_value_int(attributes, num_attributes, PID_ATTR); } @@ -5951,27 +5951,46 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) if (from.empty()) { stream << " \n"; - if (data._3mf_thumbnail.empty()) { - if (export_plate_idx < 0) { - stream << " \n"; + if (export_plate_idx < 0) { + //use cover image if have + if (data._3mf_thumbnail.empty()) { + stream << " \n"; } else { - std::string thumbnail_file_str = (boost::format("Metadata/plate_%1%.png") % (export_plate_idx + 1)).str(); - stream << " \n"; + stream << " \n"; } - } else { - stream << " \n"; - } - if (!data._3mf_printer_thumbnail_middle.empty()) { - stream << " \n"; + if (data._3mf_printer_thumbnail_middle.empty()) { + stream << " \n"; + } else { + stream << " \n"; + } + + if (data._3mf_printer_thumbnail_small.empty()) { + stream << "\n"; + } else { + stream << " \n"; + } + } + else { + //always use plate thumbnails + std::string thumbnail_file_str = (boost::format("Metadata/plate_%1%.png") % (export_plate_idx + 1)).str(); + stream << " \n"; + + thumbnail_file_str = (boost::format("Metadata/plate_%1%.png") % (export_plate_idx + 1)).str(); + stream << " \n"; + + thumbnail_file_str = (boost::format("Metadata/plate_%1%_small.png") % (export_plate_idx + 1)).str(); + stream << " \n"; } - if (!data._3mf_printer_thumbnail_small.empty()) - stream << " \n"; } else if (targets.empty()) { return false; diff --git a/src/libslic3r/PNGReadWrite.cpp b/src/libslic3r/PNGReadWrite.cpp index 51bf7de7c..a28e1ef36 100644 --- a/src/libslic3r/PNGReadWrite.cpp +++ b/src/libslic3r/PNGReadWrite.cpp @@ -100,6 +100,74 @@ bool decode_png(IStream &in_buf, ImageGreyscale &out_img) return true; } +bool decode_colored_png(IStream &in_buf, ImageColorscale &out_img) +{ + static const constexpr int PNG_SIG_BYTES = 8; + + std::vector sig(PNG_SIG_BYTES, 0); + in_buf.read(sig.data(), PNG_SIG_BYTES); + if (!png_check_sig(sig.data(), PNG_SIG_BYTES)) { + BOOST_LOG_TRIVIAL(error) << boost::format("decode_colored_png: png_check_sig failed"); + return false; + } + + PNGDescr dsc; + dsc.png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, + nullptr); + + if(!dsc.png) { + BOOST_LOG_TRIVIAL(error) << boost::format("decode_colored_png: png_create_read_struct failed"); + return false; + } + + dsc.info = png_create_info_struct(dsc.png); + if(!dsc.info) { + BOOST_LOG_TRIVIAL(error) << boost::format("decode_colored_png: png_create_info_struct failed"); + return false; + } + + png_set_read_fn(dsc.png, static_cast(&in_buf), png_read_callback); + + // Tell that we have already read the first bytes to check the signature + png_set_sig_bytes(dsc.png, PNG_SIG_BYTES); + + png_read_info(dsc.png, dsc.info); + + out_img.cols = png_get_image_width(dsc.png, dsc.info); + out_img.rows = png_get_image_height(dsc.png, dsc.info); + size_t color_type = png_get_color_type(dsc.png, dsc.info); + size_t bit_depth = png_get_bit_depth(dsc.png, dsc.info); + + switch(color_type) + { + case PNG_COLOR_TYPE_RGB: + out_img.bytes_per_pixel = 3; + break; + case PNG_COLOR_TYPE_RGB_ALPHA: + out_img.bytes_per_pixel = 4; + break; + default: //not supported currently + return false; + } + + BOOST_LOG_TRIVIAL(info) << boost::format("png's cols %1%, rows %2%, color_type %3%, bit_depth %4%, bytes_per_pixel %5%")%out_img.cols %out_img.rows %color_type %bit_depth %out_img.bytes_per_pixel; + out_img.buf.resize(out_img.rows * out_img.cols * out_img.bytes_per_pixel); + + auto readbuf = static_cast(out_img.buf.data()); + for (size_t r = 0; r < out_img.rows; ++r) + png_read_row(dsc.png, readbuf + r * out_img.cols * out_img.bytes_per_pixel, nullptr); + + return true; +} + +bool decode_colored_png(const ReadBuf &in_buf, ImageColorscale &out_img) +{ + struct ReadBufStream stream{in_buf}; + + return decode_colored_png(stream, out_img); +} + + // Down to earth function to store a packed RGB image to file. Mostly useful for debugging purposes. // Based on https://www.lemoda.net/c/write-png/ // png_color_type is PNG_COLOR_TYPE_RGB or PNG_COLOR_TYPE_GRAY @@ -112,7 +180,7 @@ static bool write_rgb_or_gray_to_file(const char *file_name_utf8, size_t width, png_structp png_ptr = nullptr; png_infop info_ptr = nullptr; png_byte **row_pointers = nullptr; - + FILE *fp = boost::nowide::fopen(file_name_utf8, "wb"); if (! fp) { BOOST_LOG_TRIVIAL(error) << "write_png_file: File could not be opened for writing: " << file_name_utf8; diff --git a/src/libslic3r/PNGReadWrite.hpp b/src/libslic3r/PNGReadWrite.hpp index 01e1f4745..c76d7b168 100644 --- a/src/libslic3r/PNGReadWrite.hpp +++ b/src/libslic3r/PNGReadWrite.hpp @@ -23,11 +23,19 @@ template struct Image { }; using ImageGreyscale = Image; +struct ImageColorscale:Image +{ + int bytes_per_pixel; +}; + // Only decodes true 8 bit grayscale png images. Returns false for other formats // TODO (if needed): implement transformation of rgb images into grayscale... bool decode_png(IStream &stream, ImageGreyscale &out_img); +//BBS: decode png for other format +bool decode_colored_png(IStream &in_buf, ImageColorscale &out_img); + // TODO (if needed) // struct RGB { uint8_t r, g, b; }; // using ImageRGB = Image; @@ -39,30 +47,36 @@ struct ReadBuf { const void *buf = nullptr; const size_t sz = 0; }; bool is_png(const ReadBuf &pngbuf); +struct ReadBufStream: public IStream { + const ReadBuf &rbuf_ref; + size_t pos = 0; + + explicit ReadBufStream(const ReadBuf &buf): rbuf_ref{buf} {} + + size_t read(std::uint8_t *outp, size_t amount) override + { + if (amount > rbuf_ref.sz - pos) return 0; + + auto buf = static_cast(rbuf_ref.buf); + std::copy(buf + pos, buf + (pos + amount), outp); + pos += amount; + + return amount; + } + + bool is_ok() const override { return pos < rbuf_ref.sz; } +}; + template bool decode_png(const ReadBuf &in_buf, Img &out_img) { - struct ReadBufStream: public IStream { - const ReadBuf &rbuf_ref; size_t pos = 0; - - explicit ReadBufStream(const ReadBuf &buf): rbuf_ref{buf} {} - - size_t read(std::uint8_t *outp, size_t amount) override - { - if (amount > rbuf_ref.sz - pos) return 0; - - auto buf = static_cast(rbuf_ref.buf); - std::copy(buf + pos, buf + (pos + amount), outp); - pos += amount; - - return amount; - } - - bool is_ok() const override { return pos < rbuf_ref.sz; } - } stream{in_buf}; + struct ReadBufStream stream{in_buf}; return decode_png(stream, out_img); } +bool decode_colored_png(const ReadBuf &in_buf, ImageColorscale &out_img); + + // TODO: std::istream of FILE* could be similarly adapted in case its needed...