diff --git a/cmake/modules/FindTBB.cmake.in b/cmake/modules/FindTBB.cmake.in index a7eafa545f..49e405c184 100644 --- a/cmake/modules/FindTBB.cmake.in +++ b/cmake/modules/FindTBB.cmake.in @@ -293,7 +293,7 @@ if(NOT TBB_FOUND) # Create targets ################################## - if(NOT CMAKE_VERSION VERSION_LESS 3.0 AND TBB_FOUND) + if(NOT CMAKE_VERSION VERSION_LESS 3.0 AND TBB_FOUND AND NOT TARGET TBB::tbb) add_library(TBB::tbb UNKNOWN IMPORTED) set_target_properties(TBB::tbb PROPERTIES INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS}" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9e89e82f67..ab1c7b9645 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -14,6 +14,7 @@ add_subdirectory(Shiny) add_subdirectory(semver) add_subdirectory(libigl) add_subdirectory(hints) +add_subdirectory(qoi) # Adding libnest2d project for bin packing... add_subdirectory(libnest2d) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 2648fba9e2..5bde6c1288 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -498,8 +498,6 @@ int CLI::run(int argc, char **argv) std::string outfile = m_config.opt_string("output"); Print fff_print; SLAPrint sla_print; - SL1Archive sla_archive(sla_print.printer_config()); - sla_print.set_printer(&sla_archive); sla_print.set_status_callback( [](const PrintBase::SlicingStatus& s) { @@ -539,7 +537,7 @@ int CLI::run(int argc, char **argv) outfile = sla_print.output_filepath(outfile); // We need to finalize the filename beforehand because the export function sets the filename inside the zip metadata outfile_final = sla_print.print_statistics().finalize_output_path(outfile); - sla_archive.export_print(outfile_final, sla_print); + sla_print.export_print(outfile_final); } if (outfile != outfile_final) { if (Slic3r::rename_file(outfile, outfile_final)) { diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 39bc1aa474..d4c8d7edc1 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -96,8 +96,12 @@ set(SLIC3R_SOURCES Format/STL.hpp Format/SL1.hpp Format/SL1.cpp + Format/SL1_SVG.hpp + Format/SL1_SVG.cpp GCode/ThumbnailData.cpp GCode/ThumbnailData.hpp + GCode/Thumbnails.cpp + GCode/Thumbnails.hpp GCode/CoolingBuffer.cpp GCode/CoolingBuffer.hpp GCode/FindReplace.cpp @@ -344,6 +348,9 @@ encoding_check(libslic3r) target_compile_definitions(libslic3r PUBLIC -DUSE_TBB -DTBB_USE_CAPTURED_EXCEPTION=0) target_include_directories(libslic3r PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) target_include_directories(libslic3r PUBLIC ${EXPAT_INCLUDE_DIRS}) + +find_package(JPEG REQUIRED) + target_link_libraries(libslic3r libnest2d admesh @@ -362,6 +369,8 @@ target_link_libraries(libslic3r ${CMAKE_DL_LIBS} PNG::PNG ZLIB::ZLIB + JPEG::JPEG + qoi ) if (TARGET OpenVDB::openvdb) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 0e44cc14ea..4259562aa8 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -121,18 +121,21 @@ static constexpr const char* LAST_TRIANGLE_ID_ATTR = "lastid"; static constexpr const char* OBJECT_TYPE = "object"; static constexpr const char* VOLUME_TYPE = "volume"; -static constexpr const char* NAME_KEY = "name"; -static constexpr const char* MODIFIER_KEY = "modifier"; +static constexpr const char* NAME_KEY = "name"; +static constexpr const char* MODIFIER_KEY = "modifier"; static constexpr const char* VOLUME_TYPE_KEY = "volume_type"; -static constexpr const char* MATRIX_KEY = "matrix"; -static constexpr const char* SOURCE_FILE_KEY = "source_file"; -static constexpr const char* SOURCE_OBJECT_ID_KEY = "source_object_id"; -static constexpr const char* SOURCE_VOLUME_ID_KEY = "source_volume_id"; -static constexpr const char* SOURCE_OFFSET_X_KEY = "source_offset_x"; -static constexpr const char* SOURCE_OFFSET_Y_KEY = "source_offset_y"; -static constexpr const char* SOURCE_OFFSET_Z_KEY = "source_offset_z"; -static constexpr const char* SOURCE_IN_INCHES = "source_in_inches"; -static constexpr const char* SOURCE_IN_METERS = "source_in_meters"; +static constexpr const char* MATRIX_KEY = "matrix"; +static constexpr const char* SOURCE_FILE_KEY = "source_file"; +static constexpr const char* SOURCE_OBJECT_ID_KEY = "source_object_id"; +static constexpr const char* SOURCE_VOLUME_ID_KEY = "source_volume_id"; +static constexpr const char* SOURCE_OFFSET_X_KEY = "source_offset_x"; +static constexpr const char* SOURCE_OFFSET_Y_KEY = "source_offset_y"; +static constexpr const char* SOURCE_OFFSET_Z_KEY = "source_offset_z"; +static constexpr const char* SOURCE_IN_INCHES_KEY = "source_in_inches"; +static constexpr const char* SOURCE_IN_METERS_KEY = "source_in_meters"; +#if ENABLE_RELOAD_FROM_DISK_REWORK +static constexpr const char* SOURCE_IS_BUILTIN_VOLUME_KEY = "source_is_builtin_volume"; +#endif // ENABLE_RELOAD_FROM_DISK_REWORK static constexpr const char* MESH_STAT_EDGES_FIXED = "edges_fixed"; static constexpr const char* MESH_STAT_DEGENERATED_FACETS = "degenerate_facets"; @@ -816,6 +819,20 @@ namespace Slic3r { return false; } +#if ENABLE_RELOAD_FROM_DISK_REWORK + for (int obj_id = 0; obj_id < int(model.objects.size()); ++obj_id) { + ModelObject* o = model.objects[obj_id]; + for (int vol_id = 0; vol_id < int(o->volumes.size()); ++vol_id) { + ModelVolume* v = o->volumes[vol_id]; + if (v->source.input_file.empty()) + v->source.input_file = v->name.empty() ? filename : v->name; + if (v->source.volume_idx == -1) + v->source.volume_idx = vol_id; + if (v->source.object_idx == -1) + v->source.object_idx = obj_id; + } + } +#else int object_idx = 0; for (ModelObject* o : model.objects) { int volume_idx = 0; @@ -831,6 +848,7 @@ namespace Slic3r { } ++object_idx; } +#endif // ENABLE_RELOAD_FROM_DISK_REWORK // // fixes the min z of the model if negative // model.adjust_min_z(); @@ -2052,15 +2070,19 @@ namespace Slic3r { else if (metadata.key == SOURCE_VOLUME_ID_KEY) volume->source.volume_idx = ::atoi(metadata.value.c_str()); else if (metadata.key == SOURCE_OFFSET_X_KEY) - volume->source.mesh_offset(0) = ::atof(metadata.value.c_str()); + volume->source.mesh_offset.x() = ::atof(metadata.value.c_str()); else if (metadata.key == SOURCE_OFFSET_Y_KEY) - volume->source.mesh_offset(1) = ::atof(metadata.value.c_str()); + volume->source.mesh_offset.y() = ::atof(metadata.value.c_str()); else if (metadata.key == SOURCE_OFFSET_Z_KEY) - volume->source.mesh_offset(2) = ::atof(metadata.value.c_str()); - else if (metadata.key == SOURCE_IN_INCHES) + volume->source.mesh_offset.z() = ::atof(metadata.value.c_str()); + else if (metadata.key == SOURCE_IN_INCHES_KEY) volume->source.is_converted_from_inches = metadata.value == "1"; - else if (metadata.key == SOURCE_IN_METERS) + else if (metadata.key == SOURCE_IN_METERS_KEY) volume->source.is_converted_from_meters = metadata.value == "1"; +#if ENABLE_RELOAD_FROM_DISK_REWORK + else if (metadata.key == SOURCE_IS_BUILTIN_VOLUME_KEY) + volume->source.is_from_builtin_objects = metadata.value == "1"; +#endif // ENABLE_RELOAD_FROM_DISK_REWORK else volume->config.set_deserialize(metadata.key, metadata.value, config_substitutions); } @@ -2981,7 +3003,7 @@ namespace Slic3r { // stores volume's local matrix stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << MATRIX_KEY << "\" " << VALUE_ATTR << "=\""; - Transform3d matrix = volume->get_matrix() * volume->source.transform.get_matrix(); + const Transform3d matrix = volume->get_matrix() * volume->source.transform.get_matrix(); for (int r = 0; r < 4; ++r) { for (int c = 0; c < 4; ++c) { stream << matrix(r, c); @@ -3005,9 +3027,13 @@ namespace Slic3r { } assert(! volume->source.is_converted_from_inches || ! volume->source.is_converted_from_meters); if (volume->source.is_converted_from_inches) - stream << prefix << SOURCE_IN_INCHES << "\" " << VALUE_ATTR << "=\"1\"/>\n"; + stream << prefix << SOURCE_IN_INCHES_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; else if (volume->source.is_converted_from_meters) - stream << prefix << SOURCE_IN_METERS << "\" " << VALUE_ATTR << "=\"1\"/>\n"; + stream << prefix << SOURCE_IN_METERS_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; +#if ENABLE_RELOAD_FROM_DISK_REWORK + if (volume->source.is_from_builtin_objects) + stream << prefix << SOURCE_IS_BUILTIN_VOLUME_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; +#endif // ENABLE_RELOAD_FROM_DISK_REWORK } // stores volume's config data @@ -3110,6 +3136,36 @@ static void handle_legacy_project_loaded(unsigned int version_project_file, Dyna } } +bool is_project_3mf(const std::string& filename) +{ + mz_zip_archive archive; + mz_zip_zero_struct(&archive); + + if (!open_zip_reader(&archive, filename)) + return false; + + mz_uint num_entries = mz_zip_reader_get_num_files(&archive); + + // loop the entries to search for config + mz_zip_archive_file_stat stat; + bool config_found = false; + for (mz_uint i = 0; i < num_entries; ++i) { + if (mz_zip_reader_file_stat(&archive, i, &stat)) { + std::string name(stat.m_filename); + std::replace(name.begin(), name.end(), '\\', '/'); + + if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE)) { + config_found = true; + break; + } + } + } + + close_zip_reader(&archive); + + return config_found; +} + bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model* model, bool check_version) { if (path == nullptr || model == nullptr) diff --git a/src/libslic3r/Format/3mf.hpp b/src/libslic3r/Format/3mf.hpp index b91e90da74..5c2a2e672a 100644 --- a/src/libslic3r/Format/3mf.hpp +++ b/src/libslic3r/Format/3mf.hpp @@ -29,6 +29,9 @@ namespace Slic3r { class DynamicPrintConfig; struct ThumbnailData; + // Returns true if the 3mf file with the given filename is a PrusaSlicer project file (i.e. if it contains a config). + extern bool is_project_3mf(const std::string& filename); + // Load the content of a 3mf file into the given model and preset bundle. extern bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model* model, bool check_version); diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index a8b66b15b1..f2a902758e 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -657,11 +657,16 @@ void AMFParserContext::endElement(const char * /* name */) if (bool has_transform = !m_volume_transform.isApprox(Transform3d::Identity(), 1e-10); has_transform) m_volume->source.transform = Slic3r::Geometry::Transformation(m_volume_transform); - if (m_volume->source.input_file.empty() && (m_volume->type() == ModelVolumeType::MODEL_PART)) { +#if ENABLE_RELOAD_FROM_DISK_REWORK + if (m_volume->source.input_file.empty()) { +#else + if (m_volume->source.input_file.empty() && m_volume->type() == ModelVolumeType::MODEL_PART) { +#endif // ENABLE_RELOAD_FROM_DISK_REWORK m_volume->source.object_idx = (int)m_model.objects.size() - 1; m_volume->source.volume_idx = (int)m_model.objects.back()->volumes.size() - 1; m_volume->center_geometry_after_creation(); - } else + } + else // pass false if the mesh offset has been already taken from the data m_volume->center_geometry_after_creation(m_volume->source.input_file.empty()); @@ -792,46 +797,44 @@ void AMFParserContext::endElement(const char * /* name */) // Is this volume a modifier volume? // "modifier" flag comes first in the XML file, so it may be later overwritten by the "type" flag. m_volume->set_type((atoi(m_value[1].c_str()) == 1) ? ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART); - } else if (strcmp(opt_key, "volume_type") == 0) { + } + else if (strcmp(opt_key, "volume_type") == 0) m_volume->set_type(ModelVolume::type_from_string(m_value[1])); - } - else if (strcmp(opt_key, "matrix") == 0) { + else if (strcmp(opt_key, "matrix") == 0) m_volume_transform = Slic3r::Geometry::transform3d_from_string(m_value[1]); - } - else if (strcmp(opt_key, "source_file") == 0) { + else if (strcmp(opt_key, "source_file") == 0) m_volume->source.input_file = m_value[1]; - } - else if (strcmp(opt_key, "source_object_id") == 0) { + else if (strcmp(opt_key, "source_object_id") == 0) m_volume->source.object_idx = ::atoi(m_value[1].c_str()); - } - else if (strcmp(opt_key, "source_volume_id") == 0) { + else if (strcmp(opt_key, "source_volume_id") == 0) m_volume->source.volume_idx = ::atoi(m_value[1].c_str()); - } - else if (strcmp(opt_key, "source_offset_x") == 0) { - m_volume->source.mesh_offset(0) = ::atof(m_value[1].c_str()); - } - else if (strcmp(opt_key, "source_offset_y") == 0) { - m_volume->source.mesh_offset(1) = ::atof(m_value[1].c_str()); - } - else if (strcmp(opt_key, "source_offset_z") == 0) { - m_volume->source.mesh_offset(2) = ::atof(m_value[1].c_str()); - } - else if (strcmp(opt_key, "source_in_inches") == 0) { + else if (strcmp(opt_key, "source_offset_x") == 0) + m_volume->source.mesh_offset.x() = ::atof(m_value[1].c_str()); + else if (strcmp(opt_key, "source_offset_y") == 0) + m_volume->source.mesh_offset.y() = ::atof(m_value[1].c_str()); + else if (strcmp(opt_key, "source_offset_z") == 0) + m_volume->source.mesh_offset.z() = ::atof(m_value[1].c_str()); + else if (strcmp(opt_key, "source_in_inches") == 0) m_volume->source.is_converted_from_inches = m_value[1] == "1"; - } - else if (strcmp(opt_key, "source_in_meters") == 0) { + else if (strcmp(opt_key, "source_in_meters") == 0) m_volume->source.is_converted_from_meters = m_value[1] == "1"; - } +#if ENABLE_RELOAD_FROM_DISK_REWORK + else if (strcmp(opt_key, "source_is_builtin_volume") == 0) + m_volume->source.is_from_builtin_objects = m_value[1] == "1"; +#endif // ENABLE_RELOAD_FROM_DISK_REWORK } - } else if (m_path.size() == 3) { + } + else if (m_path.size() == 3) { if (m_path[1] == NODE_TYPE_MATERIAL) { if (m_material) m_material->attributes[m_value[0]] = m_value[1]; - } else if (m_path[1] == NODE_TYPE_OBJECT) { + } + else if (m_path[1] == NODE_TYPE_OBJECT) { if (m_object && m_value[0] == "name") m_object->name = std::move(m_value[1]); } - } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME) { + } + else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME) { if (m_volume && m_value[0] == "name") m_volume->name = std::move(m_value[1]); } @@ -919,7 +922,11 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, ConfigSubstitut unsigned int counter = 0; for (ModelVolume* v : o->volumes) { ++counter; +#if ENABLE_RELOAD_FROM_DISK_REWORK + if (v->source.input_file.empty()) +#else if (v->source.input_file.empty() && v->type() == ModelVolumeType::MODEL_PART) +#endif // ENABLE_RELOAD_FROM_DISK_REWORK v->source.input_file = path; if (v->name.empty()) { v->name = o->name; @@ -1068,7 +1075,11 @@ bool load_amf_archive(const char* path, DynamicPrintConfig* config, ConfigSubsti for (ModelObject *o : model->objects) for (ModelVolume *v : o->volumes) - if (v->source.input_file.empty() && (v->type() == ModelVolumeType::MODEL_PART)) +#if ENABLE_RELOAD_FROM_DISK_REWORK + if (v->source.input_file.empty()) +#else + if (v->source.input_file.empty() && v->type() == ModelVolumeType::MODEL_PART) +#endif // ENABLE_RELOAD_FROM_DISK_REWORK v->source.input_file = path; return true; @@ -1237,18 +1248,15 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, stream << " "; const Transform3d& matrix = volume->get_matrix() * volume->source.transform.get_matrix(); stream << std::setprecision(std::numeric_limits::max_digits10); - for (int r = 0; r < 4; ++r) - { - for (int c = 0; c < 4; ++c) - { + for (int r = 0; r < 4; ++r) { + for (int c = 0; c < 4; ++c) { stream << matrix(r, c); - if ((r != 3) || (c != 3)) + if (r != 3 || c != 3) stream << " "; } } stream << "\n"; - if (!volume->source.input_file.empty()) - { + if (!volume->source.input_file.empty()) { std::string input_file = xml_escape(fullpath_sources ? volume->source.input_file : boost::filesystem::path(volume->source.input_file).filename().string()); stream << " " << input_file << "\n"; stream << " " << volume->source.object_idx << "\n"; @@ -1262,12 +1270,16 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, stream << " 1\n"; else if (volume->source.is_converted_from_meters) stream << " 1\n"; - stream << std::setprecision(std::numeric_limits::max_digits10); +#if ENABLE_RELOAD_FROM_DISK_REWORK + if (volume->source.is_from_builtin_objects) + stream << " 1\n"; +#endif // ENABLE_RELOAD_FROM_DISK_REWORK + stream << std::setprecision(std::numeric_limits::max_digits10); const indexed_triangle_set &its = volume->mesh().its; for (size_t i = 0; i < its.indices.size(); ++i) { stream << " \n"; for (int j = 0; j < 3; ++j) - stream << " " << its.indices[i][j] + vertices_offset << "\n"; + stream << " " << its.indices[i][j] + vertices_offset << "\n"; stream << " \n"; } stream << " \n"; diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp index 6ed8c5ebed..6f1e95528a 100644 --- a/src/libslic3r/Format/SL1.cpp +++ b/src/libslic3r/Format/SL1.cpp @@ -446,8 +446,8 @@ void fill_slicerconf(ConfMap &m, const SLAPrint &print) std::unique_ptr SL1Archive::create_raster() const { - sla::RasterBase::Resolution res; - sla::RasterBase::PixelDim pxdim; + sla::Resolution res; + sla::PixelDim pxdim; std::array mirror; double w = m_cfg.display_width.getFloat(); @@ -468,8 +468,8 @@ std::unique_ptr SL1Archive::create_raster() const std::swap(pw, ph); } - res = sla::RasterBase::Resolution{pw, ph}; - pxdim = sla::RasterBase::PixelDim{w / pw, h / ph}; + res = sla::Resolution{pw, ph}; + pxdim = sla::PixelDim{w / pw, h / ph}; sla::RasterBase::Trafo tr{orientation, mirror}; double gamma = m_cfg.gamma_correction.getFloat(); diff --git a/src/libslic3r/Format/SL1.hpp b/src/libslic3r/Format/SL1.hpp index 46a82e1b85..e6c0ff0896 100644 --- a/src/libslic3r/Format/SL1.hpp +++ b/src/libslic3r/Format/SL1.hpp @@ -15,27 +15,16 @@ protected: std::unique_ptr create_raster() const override; sla::RasterEncoder get_encoder() const override; + SLAPrinterConfig & cfg() { return m_cfg; } + const SLAPrinterConfig & cfg() const { return m_cfg; } + public: SL1Archive() = default; explicit SL1Archive(const SLAPrinterConfig &cfg): m_cfg(cfg) {} explicit SL1Archive(SLAPrinterConfig &&cfg): m_cfg(std::move(cfg)) {} - void export_print(Zipper &zipper, const SLAPrint &print, const std::string &projectname = ""); - void export_print(const std::string &fname, const SLAPrint &print, const std::string &projectname = "") - { - Zipper zipper(fname); - export_print(zipper, print, projectname); - } - - void apply(const SLAPrinterConfig &cfg) override - { - auto diff = m_cfg.diff(cfg); - if (!diff.empty()) { - m_cfg.apply_only(cfg, diff); - m_layers = {}; - } - } + void export_print(Zipper &zipper, const SLAPrint &print, const std::string &projectname = "") override; }; ConfigSubstitutions import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out); diff --git a/src/libslic3r/Format/SL1_SVG.cpp b/src/libslic3r/Format/SL1_SVG.cpp new file mode 100644 index 0000000000..0ea2306119 --- /dev/null +++ b/src/libslic3r/Format/SL1_SVG.cpp @@ -0,0 +1,221 @@ +#include "SL1_SVG.hpp" +#include "SLA/RasterBase.hpp" +#include "libslic3r/LocalesUtils.hpp" +#include "libslic3r/ClipperUtils.hpp" + +#include +#include +#include + +namespace Slic3r { + +namespace { + +size_t constexpr coord_t_bufsize = 40; + +char const* decimal_from(coord_t snumber, char* buffer) +{ + std::make_unsigned_t number = 0; + + char* ret = buffer; + + if( snumber < 0 ) { + *buffer++ = '-'; + number = -snumber; + } else + number = snumber; + + if( number == 0 ) { + *buffer++ = '0'; + } else { + char* p_first = buffer; + while( number != 0 ) { + *buffer++ = '0' + number % 10; + number /= 10; + } + std::reverse( p_first, buffer ); + } + + *buffer = '\0'; + + return ret; +} + +inline std::string coord2str(coord_t crd) +{ + char buf[coord_t_bufsize]; + return decimal_from(crd, buf); +} + +void transform(ExPolygon &ep, const sla::RasterBase::Trafo &tr, const BoundingBox &bb) +{ + if (tr.flipXY) { + for (auto &p : ep.contour.points) std::swap(p.x(), p.y()); + for (auto &h : ep.holes) + for (auto &p : h.points) std::swap(p.x(), p.y()); + } + + if (tr.mirror_x){ + for (auto &p : ep.contour.points) p.x() = bb.max.x() - p.x() + bb.min.x(); + for (auto &h : ep.holes) + for (auto &p : h.points) p.x() = bb.max.x() - p.x() + bb.min.x(); + } + + if (tr.mirror_y){ + for (auto &p : ep.contour.points) p.y() = bb.max.y() - p.y() + bb.min.y(); + for (auto &h : ep.holes) + for (auto &p : h.points) p.y() = bb.max.y() - p.y() + bb.min.y(); + } +} + +void append_svg(std::string &buf, const Polygon &poly) +{ + if (poly.points.empty()) + return; + + auto c = poly.points.front(); + + char intbuf[coord_t_bufsize]; + + buf += std::string("(m_bb.size().x())); + std::string hf = float_to_string_decimal_point(unscaled(m_bb.size().y())); + std::string w = coord2str(coord_t(m_res.width_px)); + std::string h = coord2str(coord_t(m_res.height_px)); + + // Notice the header also defines the fill-rule as nonzero which should + // generate correct results for our ExPolygons. + + // Add svg header. + m_svg = + "\n" + "\n" + "\n"; + + // Add black background; + m_svg += "\n"; + } + + void draw(const ExPolygon& poly) override + { + auto cpoly = poly; + + double tol = std::min(m_bb.size().x() / double(m_res.width_px), + m_bb.size().y() / double(m_res.height_px)); + + ExPolygons cpolys = poly.simplify(tol); + + for (auto &cpoly : cpolys) { + transform(cpoly, m_trafo, m_bb); + + for (auto &p : cpoly.contour.points) + p = {std::round(p.x() * m_sc.x()), std::round(p.y() * m_sc.y())}; + + for (auto &h : cpoly.holes) + for (auto &p : h) + p = {std::round(p.x() * m_sc.x()), std::round(p.y() * m_sc.y())}; + + append_svg(m_svg, cpoly.contour); + for (auto &h : cpoly.holes) + append_svg(m_svg, h); + } + } + + Trafo trafo() const override { return m_trafo; } + + sla::EncodedRaster encode(sla::RasterEncoder /*encoder*/) const override + { + std::vector data; + constexpr const char finish[] = "\n"; + + data.reserve(m_svg.size() + std::size(finish)); + + std::copy(m_svg.begin(), m_svg.end(), std::back_inserter(data)); + std::copy(finish, finish + std::size(finish) - 1, std::back_inserter(data)); + + return sla::EncodedRaster{std::move(data), "svg"}; + } +}; + +std::unique_ptr SL1_SVGArchive::create_raster() const +{ + auto w = cfg().display_width.getFloat(); + auto h = cfg().display_height.getFloat(); + +// auto res_x = size_t(cfg().display_pixels_x.getInt()); +// auto res_y = size_t(cfg().display_pixels_y.getInt()); + float precision_nm = scaled(cfg().sla_output_precision.getFloat()); + size_t res_x = std::round(scaled(w) / precision_nm); + size_t res_y = std::round(scaled(h) / precision_nm); + + std::array mirror; + + mirror[X] = cfg().display_mirror_x.getBool(); + mirror[Y] = cfg().display_mirror_y.getBool(); + + auto ro = cfg().display_orientation.getInt(); + sla::RasterBase::Orientation orientation = + ro == sla::RasterBase::roPortrait ? sla::RasterBase::roPortrait : + sla::RasterBase::roLandscape; + + if (orientation == sla::RasterBase::roPortrait) { + std::swap(w, h); + std::swap(res_x, res_y); + } + + BoundingBox svgarea{{0, 0}, {scaled(w), scaled(h)}}; + + sla::RasterBase::Trafo tr{orientation, mirror}; + + // Gamma does not really make sense in an svg, right? + // double gamma = cfg().gamma_correction.getFloat(); + return std::make_unique(svgarea, sla::Resolution{res_x, res_y}, tr); +} + +sla::RasterEncoder SL1_SVGArchive::get_encoder() const +{ + return nullptr; +} + +} // namespace Slic3r diff --git a/src/libslic3r/Format/SL1_SVG.hpp b/src/libslic3r/Format/SL1_SVG.hpp new file mode 100644 index 0000000000..a3afbcdfff --- /dev/null +++ b/src/libslic3r/Format/SL1_SVG.hpp @@ -0,0 +1,22 @@ +#ifndef SL1_SVG_HPP +#define SL1_SVG_HPP + +#include "SL1.hpp" + +namespace Slic3r { + +class SL1_SVGArchive: public SL1Archive { +protected: + + // Override the factory methods to produce svg instead of a real raster. + std::unique_ptr create_raster() const override; + sla::RasterEncoder get_encoder() const override; + +public: + + using SL1Archive::SL1Archive; +}; + +} // namespace Slic3r + +#endif // SL1_SVG_HPP diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 5fac4b8229..ba51196ddc 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -6,6 +6,7 @@ #include "EdgeGrid.hpp" #include "Geometry/ConvexHull.hpp" #include "GCode/PrintExtents.hpp" +#include "GCode/Thumbnails.hpp" #include "GCode/WipeTower.hpp" #include "ShortestPath.hpp" #include "Print.hpp" @@ -54,8 +55,6 @@ #include -#include "miniz_extension.hpp" - using namespace std::literals::string_view_literals; #if 0 @@ -937,49 +936,6 @@ namespace DoExport { } } - template - static void export_thumbnails_to_file(ThumbnailsGeneratorCallback &thumbnail_cb, const std::vector &sizes, WriteToOutput output, ThrowIfCanceledCallback throw_if_canceled) - { - // Write thumbnails using base64 encoding - if (thumbnail_cb != nullptr) - { - const size_t max_row_length = 78; - ThumbnailsList thumbnails = thumbnail_cb(ThumbnailsParams{ sizes, true, true, true, true }); - for (const ThumbnailData& data : thumbnails) - { - if (data.is_valid()) - { - size_t png_size = 0; - void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)data.pixels.data(), data.width, data.height, 4, &png_size, MZ_DEFAULT_LEVEL, 1); - if (png_data != nullptr) - { - std::string encoded; - encoded.resize(boost::beast::detail::base64::encoded_size(png_size)); - encoded.resize(boost::beast::detail::base64::encode((void*)&encoded[0], (const void*)png_data, png_size)); - - output((boost::format("\n;\n; thumbnail begin %dx%d %d\n") % data.width % data.height % encoded.size()).str().c_str()); - - unsigned int row_count = 0; - while (encoded.size() > max_row_length) - { - output((boost::format("; %s\n") % encoded.substr(0, max_row_length)).str().c_str()); - encoded = encoded.substr(max_row_length); - ++row_count; - } - - if (encoded.size() > 0) - output((boost::format("; %s\n") % encoded).str().c_str()); - - output("; thumbnail end\n;\n"); - - mz_free(png_data); - } - } - throw_if_canceled(); - } - } - } - // Fill in print_statistics and return formatted string containing filament statistics to be inserted into G-code comment section. static std::string update_print_stats_and_format_filament_stats( const bool has_wipe_tower, @@ -1163,7 +1119,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // Write information on the generator. file.write_format("; %s\n\n", Slic3r::header_slic3r_generated().c_str()); - DoExport::export_thumbnails_to_file(thumbnail_cb, print.full_print_config().option("thumbnails")->values, + GCodeThumbnails::export_thumbnails_to_file(thumbnail_cb, + print.full_print_config().option("thumbnails")->values, + print.full_print_config().opt_enum("thumbnails_format"), [&file](const char* sz) { file.write(sz); }, [&print]() { print.throw_if_canceled(); }); diff --git a/src/libslic3r/GCode/Thumbnails.cpp b/src/libslic3r/GCode/Thumbnails.cpp new file mode 100644 index 0000000000..8d70539b7c --- /dev/null +++ b/src/libslic3r/GCode/Thumbnails.cpp @@ -0,0 +1,119 @@ +#include "Thumbnails.hpp" +#include "../miniz_extension.hpp" + +#include +#include +#include + +namespace Slic3r::GCodeThumbnails { + +using namespace std::literals; + +struct CompressedPNG : CompressedImageBuffer +{ + ~CompressedPNG() override { if (data) mz_free(data); } + std::string_view tag() const override { return "thumbnail"sv; } +}; + +struct CompressedJPG : CompressedImageBuffer +{ + ~CompressedJPG() override { free(data); } + std::string_view tag() const override { return "thumbnail_JPG"sv; } +}; + +struct CompressedQOI : CompressedImageBuffer +{ + ~CompressedQOI() override { free(data); } + std::string_view tag() const override { return "thumbnail_QOI"sv; } +}; + +std::unique_ptr compress_thumbnail_png(const ThumbnailData &data) +{ + auto out = std::make_unique(); + out->data = tdefl_write_image_to_png_file_in_memory_ex((const void*)data.pixels.data(), data.width, data.height, 4, &out->size, MZ_DEFAULT_LEVEL, 1); + return out; +} + +std::unique_ptr compress_thumbnail_jpg(const ThumbnailData& data) +{ + // Take vector of RGBA pixels and flip the image vertically + std::vector rgba_pixels(data.pixels.size()); + const unsigned int row_size = data.width * 4; + for (unsigned int y = 0; y < data.height; ++y) { + ::memcpy(rgba_pixels.data() + (data.height - y - 1) * row_size, data.pixels.data() + y * row_size, row_size); + } + + // Store pointers to scanlines start for later use + std::vector rows_ptrs; + rows_ptrs.reserve(data.height); + for (unsigned int y = 0; y < data.height; ++y) { + rows_ptrs.emplace_back(&rgba_pixels[y * row_size]); + } + + std::vector compressed_data(data.pixels.size()); + unsigned char* compressed_data_ptr = compressed_data.data(); + unsigned long compressed_data_size = data.pixels.size(); + + jpeg_error_mgr err; + jpeg_compress_struct info; + info.err = jpeg_std_error(&err); + jpeg_create_compress(&info); + jpeg_mem_dest(&info, &compressed_data_ptr, &compressed_data_size); + + info.image_width = data.width; + info.image_height = data.height; + info.input_components = 4; + info.in_color_space = JCS_EXT_RGBA; + + jpeg_set_defaults(&info); + jpeg_set_quality(&info, 85, TRUE); + jpeg_start_compress(&info, TRUE); + + jpeg_write_scanlines(&info, rows_ptrs.data(), data.height); + jpeg_finish_compress(&info); + jpeg_destroy_compress(&info); + + // FIXME -> Add error checking + + auto out = std::make_unique(); + out->data = malloc(compressed_data_size); + out->size = size_t(compressed_data_size); + ::memcpy(out->data, (const void*)compressed_data.data(), out->size); + return out; +} + +std::unique_ptr compress_thumbnail_qoi(const ThumbnailData &data) +{ + qoi_desc desc; + desc.width = data.width; + desc.height = data.height; + desc.channels = 4; + desc.colorspace = QOI_SRGB; + + // Take vector of RGBA pixels and flip the image vertically + std::vector rgba_pixels(data.pixels.size() * 4); + size_t row_size = data.width * 4; + for (size_t y = 0; y < data.height; ++ y) + memcpy(rgba_pixels.data() + (data.height - y - 1) * row_size, data.pixels.data() + y * row_size, row_size); + + auto out = std::make_unique(); + int size; + out->data = qoi_encode((const void*)rgba_pixels.data(), &desc, &size); + out->size = size; + return out; +} + +std::unique_ptr compress_thumbnail(const ThumbnailData &data, GCodeThumbnailsFormat format) +{ + switch (format) { + case GCodeThumbnailsFormat::PNG: + default: + return compress_thumbnail_png(data); + case GCodeThumbnailsFormat::JPG: + return compress_thumbnail_jpg(data); + case GCodeThumbnailsFormat::QOI: + return compress_thumbnail_qoi(data); + } +} + +} // namespace Slic3r::GCodeThumbnails diff --git a/src/libslic3r/GCode/Thumbnails.hpp b/src/libslic3r/GCode/Thumbnails.hpp new file mode 100644 index 0000000000..30bb6b653b --- /dev/null +++ b/src/libslic3r/GCode/Thumbnails.hpp @@ -0,0 +1,60 @@ +#ifndef slic3r_GCodeThumbnails_hpp_ +#define slic3r_GCodeThumbnails_hpp_ + +#include "../Point.hpp" +#include "../PrintConfig.hpp" +#include "ThumbnailData.hpp" + +#include +#include +#include + +#include + +namespace Slic3r::GCodeThumbnails { + +struct CompressedImageBuffer +{ + void *data { nullptr }; + size_t size { 0 }; + virtual ~CompressedImageBuffer() {} + virtual std::string_view tag() const = 0; +}; + +std::unique_ptr compress_thumbnail(const ThumbnailData &data, GCodeThumbnailsFormat format); + +template +inline void export_thumbnails_to_file(ThumbnailsGeneratorCallback &thumbnail_cb, const std::vector &sizes, GCodeThumbnailsFormat format, WriteToOutput output, ThrowIfCanceledCallback throw_if_canceled) +{ + // Write thumbnails using base64 encoding + if (thumbnail_cb != nullptr) { + static constexpr const size_t max_row_length = 78; + ThumbnailsList thumbnails = thumbnail_cb(ThumbnailsParams{ sizes, true, true, true, true }); + for (const ThumbnailData& data : thumbnails) + if (data.is_valid()) { + auto compressed = compress_thumbnail(data, format); + if (compressed->data && compressed->size) { + std::string encoded; + encoded.resize(boost::beast::detail::base64::encoded_size(compressed->size)); + encoded.resize(boost::beast::detail::base64::encode((void*)encoded.data(), (const void*)compressed->data, compressed->size)); + + output((boost::format("\n;\n; %s begin %dx%d %d\n") % compressed->tag() % data.width % data.height % encoded.size()).str().c_str()); + + while (encoded.size() > max_row_length) { + output((boost::format("; %s\n") % encoded.substr(0, max_row_length)).str().c_str()); + encoded = encoded.substr(max_row_length); + } + + if (encoded.size() > 0) + output((boost::format("; %s\n") % encoded).str().c_str()); + + output((boost::format("; %s end\n;\n") % compressed->tag()).str().c_str()); + } + throw_if_canceled(); + } + } +} + +} // namespace Slic3r::GCodeThumbnails + +#endif // slic3r_GCodeThumbnails_hpp_ diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index e65b604df1..ca1363da24 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -484,7 +484,7 @@ static std::vector s_Preset_printer_options { "cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "max_print_height", "default_print_profile", "inherits", "remaining_times", "silent_mode", - "machine_limits_usage", "thumbnails" + "machine_limits_usage", "thumbnails", "thumbnails_format" }; static std::vector s_Preset_sla_print_options { @@ -573,7 +573,7 @@ static std::vector s_Preset_sla_printer_options { "elefant_foot_min_width", "gamma_correction", "min_exposure_time", "max_exposure_time", - "min_initial_exposure_time", "max_initial_exposure_time", + "min_initial_exposure_time", "max_initial_exposure_time", "sla_archive_format", "sla_output_precision", //FIXME the print host keys are left here just for conversion from the Printer preset to Physical Printer preset. "print_host", "printhost_apikey", "printhost_cafile", "printer_notes", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index dc47b382da..9f848b49b6 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -130,7 +130,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "start_gcode", "start_filament_gcode", "toolchange_gcode", - "threads", + "thumbnails", + "thumbnails_format", "use_firmware_retraction", "use_relative_e_distances", "use_volumetric_e", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 1aaf8c1dde..5a674da846 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -43,7 +43,7 @@ static t_config_enum_values s_keys_map_PrinterTechnology { }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrinterTechnology) -static t_config_enum_values s_keys_map_GCodeFlavor { +static const t_config_enum_values s_keys_map_GCodeFlavor { { "reprap", gcfRepRapSprinter }, { "reprapfirmware", gcfRepRapFirmware }, { "repetier", gcfRepetier }, @@ -59,14 +59,14 @@ static t_config_enum_values s_keys_map_GCodeFlavor { }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(GCodeFlavor) -static t_config_enum_values s_keys_map_MachineLimitsUsage { +static const t_config_enum_values s_keys_map_MachineLimitsUsage { { "emit_to_gcode", int(MachineLimitsUsage::EmitToGCode) }, { "time_estimate_only", int(MachineLimitsUsage::TimeEstimateOnly) }, { "ignore", int(MachineLimitsUsage::Ignore) } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(MachineLimitsUsage) -static t_config_enum_values s_keys_map_PrintHostType { +static const t_config_enum_values s_keys_map_PrintHostType { { "prusalink", htPrusaLink }, { "octoprint", htOctoPrint }, { "duet", htDuet }, @@ -77,20 +77,20 @@ static t_config_enum_values s_keys_map_PrintHostType { }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrintHostType) -static t_config_enum_values s_keys_map_AuthorizationType { +static const t_config_enum_values s_keys_map_AuthorizationType { { "key", atKeyPassword }, { "user", atUserPassword } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(AuthorizationType) -static t_config_enum_values s_keys_map_FuzzySkinType { +static const t_config_enum_values s_keys_map_FuzzySkinType { { "none", int(FuzzySkinType::None) }, { "external", int(FuzzySkinType::External) }, { "all", int(FuzzySkinType::All) } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(FuzzySkinType) -static t_config_enum_values s_keys_map_InfillPattern { +static const t_config_enum_values s_keys_map_InfillPattern { { "rectilinear", ipRectilinear }, { "monotonic", ipMonotonic }, { "alignedrectilinear", ipAlignedRectilinear }, @@ -114,41 +114,41 @@ static t_config_enum_values s_keys_map_InfillPattern { }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(InfillPattern) -static t_config_enum_values s_keys_map_IroningType { +static const t_config_enum_values s_keys_map_IroningType { { "top", int(IroningType::TopSurfaces) }, { "topmost", int(IroningType::TopmostOnly) }, { "solid", int(IroningType::AllSolid) } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(IroningType) -static t_config_enum_values s_keys_map_SlicingMode { +static const t_config_enum_values s_keys_map_SlicingMode { { "regular", int(SlicingMode::Regular) }, { "even_odd", int(SlicingMode::EvenOdd) }, { "close_holes", int(SlicingMode::CloseHoles) } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SlicingMode) -static t_config_enum_values s_keys_map_SupportMaterialPattern { +static const t_config_enum_values s_keys_map_SupportMaterialPattern { { "rectilinear", smpRectilinear }, { "rectilinear-grid", smpRectilinearGrid }, { "honeycomb", smpHoneycomb } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialPattern) -static t_config_enum_values s_keys_map_SupportMaterialStyle { +static const t_config_enum_values s_keys_map_SupportMaterialStyle { { "grid", smsGrid }, { "snug", smsSnug } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialStyle) -static t_config_enum_values s_keys_map_SupportMaterialInterfacePattern { +static const t_config_enum_values s_keys_map_SupportMaterialInterfacePattern { { "auto", smipAuto }, { "rectilinear", smipRectilinear }, { "concentric", smipConcentric } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialInterfacePattern) -static t_config_enum_values s_keys_map_SeamPosition { +static const t_config_enum_values s_keys_map_SeamPosition { { "random", spRandom }, { "nearest", spNearest }, { "aligned", spAligned }, @@ -190,6 +190,13 @@ static const t_config_enum_values s_keys_map_DraftShield = { }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(DraftShield) +static const t_config_enum_values s_keys_map_GCodeThumbnailsFormat = { + { "PNG", int(GCodeThumbnailsFormat::PNG) }, + { "JPG", int(GCodeThumbnailsFormat::JPG) }, + { "QOI", int(GCodeThumbnailsFormat::QOI) } +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(GCodeThumbnailsFormat) + static const t_config_enum_values s_keys_map_ForwardCompatibilitySubstitutionRule = { { "disable", ForwardCompatibilitySubstitutionRule::Disable }, { "enable", ForwardCompatibilitySubstitutionRule::Enable }, @@ -259,6 +266,16 @@ void PrintConfigDef::init_common_params() def->gui_type = ConfigOptionDef::GUIType::one_string; def->set_default_value(new ConfigOptionPoints()); + def = this->add("thumbnails_format", coEnum); + def->label = L("Format of G-code thumbnails"); + def->tooltip = L("Format of G-code thumbnails: PNG for best quality, JPG for smallest size, QOI for low memory firmware"); + def->mode = comExpert; + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("PNG"); + def->enum_values.push_back("JPG"); + def->enum_values.push_back("QOI"); + def->set_default_value(new ConfigOptionEnum(GCodeThumbnailsFormat::PNG)); + def = this->add("layer_height", coFloat); def->label = L("Layer height"); def->category = L("Layers and Perimeters"); @@ -3783,6 +3800,19 @@ void PrintConfigDef::init_sla_params() def->enum_labels.push_back(L("Fast")); def->mode = comAdvanced; def->set_default_value(new ConfigOptionEnum(slamsFast)); + + def = this->add("sla_archive_format", coString); + def->label = L("Format of the output SLA archive"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionString("SL1")); + + def = this->add("sla_output_precision", coFloat); + def->label = L("SLA output precision"); + def->tooltip = L("Minimum resolution in nanometers"); + def->sidetext = L("mm"); + def->min = SCALING_FACTOR; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(0.001)); } void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value) diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 2cc758e7be..b97168e84e 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -131,6 +131,10 @@ enum DraftShield { dsDisabled, dsLimited, dsEnabled }; +enum class GCodeThumbnailsFormat { + PNG, JPG, QOI +}; + #define CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(NAME) \ template<> const t_config_enum_names& ConfigOptionEnum::get_enum_names(); \ template<> const t_config_enum_values& ConfigOptionEnum::get_enum_values(); @@ -152,6 +156,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLADisplayOrientation) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLAPillarConnectionMode) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BrimType) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(DraftShield) +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeThumbnailsFormat) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule) #undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS @@ -757,6 +762,8 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionInt, standby_temperature_delta)) ((ConfigOptionInts, temperature)) ((ConfigOptionInt, threads)) + ((ConfigOptionPoints, thumbnails)) + ((ConfigOptionEnum, thumbnails_format)) ((ConfigOptionBools, wipe)) ((ConfigOptionBool, wipe_tower)) ((ConfigOptionFloat, wipe_tower_x)) @@ -975,6 +982,8 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, max_exposure_time)) ((ConfigOptionFloat, min_initial_exposure_time)) ((ConfigOptionFloat, max_initial_exposure_time)) + ((ConfigOptionString, sla_archive_format)) + ((ConfigOptionFloat, sla_output_precision)) ) PRINT_CONFIG_CLASS_DERIVED_DEFINE0( diff --git a/src/libslic3r/SLA/AGGRaster.hpp b/src/libslic3r/SLA/AGGRaster.hpp index bc68cd3778..7c8e71c2ae 100644 --- a/src/libslic3r/SLA/AGGRaster.hpp +++ b/src/libslic3r/SLA/AGGRaster.hpp @@ -41,7 +41,7 @@ public: using TValue = typename TColor::value_type; using TPixel = typename PixelRenderer::pixel_type; using TRawBuffer = agg::rendering_buffer; - + protected: Resolution m_resolution; @@ -153,8 +153,8 @@ public: } Trafo trafo() const override { return m_trafo; } - Resolution resolution() const override { return m_resolution; } - PixelDim pixel_dimensions() const override + Resolution resolution() const { return m_resolution; } + PixelDim pixel_dimensions() const { return {SCALING_FACTOR / m_pxdim_scaled.w_mm, SCALING_FACTOR / m_pxdim_scaled.h_mm}; @@ -186,11 +186,15 @@ class RasterGrayscaleAA : public _RasterGrayscaleAA { using typename Base::TValue; public: template - RasterGrayscaleAA(const RasterBase::Resolution &res, - const RasterBase::PixelDim & pd, - const RasterBase::Trafo & trafo, - GammaFn && fn) - : Base(res, pd, trafo, Colors::White, Colors::Black, + RasterGrayscaleAA(const Resolution &res, + const PixelDim &pd, + const RasterBase::Trafo &trafo, + GammaFn &&fn) + : Base(res, + pd, + trafo, + Colors::White, + Colors::Black, std::forward(fn)) {} @@ -208,10 +212,10 @@ public: class RasterGrayscaleAAGammaPower: public RasterGrayscaleAA { public: - RasterGrayscaleAAGammaPower(const RasterBase::Resolution &res, - const RasterBase::PixelDim & pd, - const RasterBase::Trafo & trafo, - double gamma = 1.) + RasterGrayscaleAAGammaPower(const Resolution &res, + const PixelDim &pd, + const RasterBase::Trafo &trafo, + double gamma = 1.) : RasterGrayscaleAA(res, pd, trafo, agg::gamma_power(gamma)) {} }; diff --git a/src/libslic3r/SLA/RasterBase.cpp b/src/libslic3r/SLA/RasterBase.cpp index cc9aca0274..0b6c45eff3 100644 --- a/src/libslic3r/SLA/RasterBase.cpp +++ b/src/libslic3r/SLA/RasterBase.cpp @@ -68,10 +68,10 @@ EncodedRaster PPMRasterEncoder::operator()(const void *ptr, size_t w, size_t h, } std::unique_ptr create_raster_grayscale_aa( - const RasterBase::Resolution &res, - const RasterBase::PixelDim & pxdim, - double gamma, - const RasterBase::Trafo & tr) + const Resolution &res, + const PixelDim &pxdim, + double gamma, + const RasterBase::Trafo &tr) { std::unique_ptr rst; diff --git a/src/libslic3r/SLA/RasterBase.hpp b/src/libslic3r/SLA/RasterBase.hpp index 6439830fe8..657fc865c2 100644 --- a/src/libslic3r/SLA/RasterBase.hpp +++ b/src/libslic3r/SLA/RasterBase.hpp @@ -31,6 +31,27 @@ public: const char * extension() const { return m_ext.c_str(); } }; +/// Type that represents a resolution in pixels. +struct Resolution { + size_t width_px = 0; + size_t height_px = 0; + + Resolution() = default; + Resolution(size_t w, size_t h) : width_px(w), height_px(h) {} + size_t pixels() const { return width_px * height_px; } +}; + +/// Types that represents the dimension of a pixel in millimeters. +struct PixelDim { + double w_mm = 1.; + double h_mm = 1.; + + PixelDim() = default; + PixelDim(double px_width_mm, double px_height_mm) + : w_mm(px_width_mm), h_mm(px_height_mm) + {} +}; + using RasterEncoder = std::function; @@ -63,35 +84,14 @@ public: Point get_center() const { return {center_x, center_y}; } }; - /// Type that represents a resolution in pixels. - struct Resolution { - size_t width_px = 0; - size_t height_px = 0; - - Resolution() = default; - Resolution(size_t w, size_t h) : width_px(w), height_px(h) {} - size_t pixels() const { return width_px * height_px; } - }; - - /// Types that represents the dimension of a pixel in millimeters. - struct PixelDim { - double w_mm = 1.; - double h_mm = 1.; - - PixelDim() = default; - PixelDim(double px_width_mm, double px_height_mm) - : w_mm(px_width_mm), h_mm(px_height_mm) - {} - }; - virtual ~RasterBase() = default; /// Draw a polygon with holes. virtual void draw(const ExPolygon& poly) = 0; /// Get the resolution of the raster. - virtual Resolution resolution() const = 0; - virtual PixelDim pixel_dimensions() const = 0; +// virtual Resolution resolution() const = 0; +// virtual PixelDim pixel_dimensions() const = 0; virtual Trafo trafo() const = 0; virtual EncodedRaster encode(RasterEncoder encoder) const = 0; @@ -109,10 +109,10 @@ std::ostream& operator<<(std::ostream &stream, const EncodedRaster &bytes); // If gamma is zero, thresholding will be performed which disables AA. std::unique_ptr create_raster_grayscale_aa( - const RasterBase::Resolution &res, - const RasterBase::PixelDim & pxdim, - double gamma = 1.0, - const RasterBase::Trafo & tr = {}); + const Resolution &res, + const PixelDim &pxdim, + double gamma = 1.0, + const RasterBase::Trafo &tr = {}); }} // namespace Slic3r::sla diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 55acd38465..7b78dfea2f 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -1,6 +1,9 @@ #include "SLAPrint.hpp" #include "SLAPrintSteps.hpp" +#include "Format/SL1.hpp" +#include "Format/SL1_SVG.hpp" + #include "ClipperUtils.hpp" #include "Geometry.hpp" #include "MTUtils.hpp" @@ -240,8 +243,13 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con m_material_config.apply_only(config, material_diff, true); // Handle changes to object config defaults m_default_object_config.apply_only(config, object_diff, true); - - if (m_printer) m_printer->apply(m_printer_config); + + if (!m_archiver || !printer_diff.empty()) { + if (m_printer_config.sla_archive_format.value == "SL1") + m_archiver = std::make_unique(m_printer_config); + else if (m_printer_config.sla_archive_format.value == "SL2") + m_archiver = std::make_unique(m_printer_config); + } struct ModelObjectStatus { enum Status { @@ -670,12 +678,6 @@ std::string SLAPrint::validate(std::string*) const return ""; } -void SLAPrint::set_printer(SLAArchive *arch) -{ - invalidate_step(slapsRasterize); - m_printer = arch; -} - bool SLAPrint::invalidate_step(SLAPrintStep step) { bool invalidated = Inherited::invalidate_step(step); @@ -835,7 +837,9 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector steps_ignore = { diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 0622bec4e5..b2df0c4e92 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -399,8 +399,6 @@ protected: public: virtual ~SLAArchive() = default; - virtual void apply(const SLAPrinterConfig &cfg) = 0; - // Fn have to be thread safe: void(sla::RasterBase& raster, size_t lyrid); template void draw_layers( @@ -422,6 +420,14 @@ public: }, execution::max_concurrency(ep)); } + + // Export the print into an archive using the provided zipper. + // TODO: Use an archive writer interface instead of Zipper. + // This is quite limiting as the Zipper is a complete class, not an interface. + // The output can only be a zip archive. + virtual void export_print(Zipper &zipper, + const SLAPrint &print, + const std::string &projectname = "") = 0; }; /** @@ -527,8 +533,17 @@ public: // The aggregated and leveled print records from various objects. // TODO: use this structure for the preview in the future. const std::vector& print_layers() const { return m_printer_input; } - - void set_printer(SLAArchive *archiver); + + void export_print(Zipper &zipper, const std::string &projectname = "") + { + m_archiver->export_print(zipper, *this, projectname); + } + + void export_print(const std::string &fname, const std::string &projectname = "") + { + Zipper zipper(fname); + export_print(zipper, projectname); + } private: @@ -550,7 +565,7 @@ private: std::vector m_printer_input; // The archive object which collects the raster images after slicing - SLAArchive *m_printer = nullptr; + std::unique_ptr m_archiver; // Estimated print time, material consumed. SLAPrintStatistics m_print_statistics; diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index fa7348781d..435e8c8e39 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -1044,7 +1044,7 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { // Rasterizing the model objects, and their supports void SLAPrint::Steps::rasterize() { - if(canceled() || !m_print->m_printer) return; + if(canceled() || !m_print->m_archiver) return; // coefficient to map the rasterization state (0-99) to the allocated // portion (slot) of the process state @@ -1089,7 +1089,7 @@ void SLAPrint::Steps::rasterize() if(canceled()) return; // Print all the layers in parallel - m_print->m_printer->draw_layers(m_print->m_printer_input.size(), lvlfn, + m_print->m_archiver->draw_layers(m_print->m_printer_input.size(), lvlfn, [this]() { return canceled(); }, ex_tbb); } diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 9a2f690a10..e0a8f93693 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -70,6 +70,8 @@ #define ENABLE_GLBEGIN_GLEND_REMOVAL (1 && ENABLE_2_5_0_ALPHA1) // Enable show non-manifold edges #define ENABLE_SHOW_NON_MANIFOLD_EDGES (1 && ENABLE_2_5_0_ALPHA1) +// Enable rework of Reload from disk command +#define ENABLE_RELOAD_FROM_DISK_REWORK (1 && ENABLE_2_5_0_ALPHA1) // Enable editing volumes transformation in world coordinates and instances in local coordinates #define ENABLE_WORLD_COORDINATE (1 && ENABLE_2_5_0_ALPHA1) // Enable showing world coordinates of volumes' offset relative to the instance containing them diff --git a/src/qoi/CMakeLists.txt b/src/qoi/CMakeLists.txt new file mode 100644 index 0000000000..af8bf2e051 --- /dev/null +++ b/src/qoi/CMakeLists.txt @@ -0,0 +1,9 @@ +# PrusaSlicer specific CMake + +cmake_minimum_required(VERSION 2.8.12) +project(qoi) + +add_library(qoi STATIC + qoi.h + qoilib.c +) diff --git a/src/qoi/README.md b/src/qoi/README.md new file mode 100644 index 0000000000..4006c38507 --- /dev/null +++ b/src/qoi/README.md @@ -0,0 +1,108 @@ +Bundled with PrusaSlicer: commit 6c0831f91ffde5dfe2ceef32cbaff91d62b0e0ee +Original README follows: + + +![QOI Logo](https://qoiformat.org/qoi-logo.svg) + +# QOI - The “Quite OK Image Format” for fast, lossless image compression + +Single-file MIT licensed library for C/C++ + +See [qoi.h](https://github.com/phoboslab/qoi/blob/master/qoi.h) for +the documentation and format specification. + +More info at https://qoiformat.org + + +## Why? + +Compared to stb_image and stb_image_write QOI offers 20x-50x faster encoding, +3x-4x faster decoding and 20% better compression. It's also stupidly simple and +fits in about 300 lines of C. + + +## Example Usage + +- [qoiconv.c](https://github.com/phoboslab/qoi/blob/master/qoiconv.c) +converts between png <> qoi + - [qoibench.c](https://github.com/phoboslab/qoi/blob/master/qoibench.c) +a simple wrapper to benchmark stbi, libpng and qoi + + +## Limitations + +The QOI file format allows for huge images with up to 18 exa-pixels. A streaming +en-/decoder can handle these with minimal RAM requirements, assuming there is +enough storage space. + +This particular implementation of QOI however is limited to images with a +maximum size of 400 million pixels. It will safely refuse to en-/decode anything +larger than that. This is not a streaming en-/decoder. It loads the whole image +file into RAM before doing any work and is not extensively optimized for +performance (but it's still very fast). + +If this is a limitation for your use case, please look into any of the other +implementations listed below. + + +## Tools + +- https://github.com/floooh/qoiview - native QOI viewer +- https://github.com/pfusik/qoi-ci/releases/tag/qoi-ci-1.1.0 - QOI Plugin installer for GIMP, Imagine, Paint.NET and XnView MP +- https://github.com/iOrange/QoiFileTypeNet/releases/tag/v0.2 - QOI Plugin for Paint.NET +- https://github.com/iOrange/QOIThumbnailProvider - Add thumbnails for QOI images in Windows Explorer +- https://github.com/Tom94/tev - another native QOI viewer (allows pixel peeping and comparison with other image formats) +- https://apps.apple.com/br/app/qoiconverterx/id1602159820 QOI <=> PNG converter available on the Mac App Store +- https://github.com/kaetemi/qoi-max - QOI Bitmap I/O Plugin for 3ds Max +- https://raylibtech.itch.io/rtexviewer - texture viewer, supports QOI + + +## Implementations & Bindings of QOI + +- https://github.com/pfusik/qoi-ci (Ć, transpiled to C, C++, C#, Java, JavaScript, Python and Swift) +- https://github.com/kodonnell/qoi (Python) +- https://github.com/Cr4xy/lua-qoi (Lua) +- https://github.com/superzazu/SDL_QOI (C, SDL2 bindings) +- https://github.com/saharNooby/qoi-java (Java) +- https://github.com/MasterQ32/zig-qoi (Zig) +- https://github.com/rbino/qoix (Elixir) +- https://github.com/NUlliiON/QoiSharp (C#) +- https://github.com/aldanor/qoi-rust (Rust) +- https://github.com/zakarumych/rapid-qoi (Rust) +- https://github.com/takeyourhatoff/qoi (Go) +- https://github.com/DosWorld/pasqoi (Pascal) +- https://github.com/elihwyma/Swift-QOI (Swift) +- https://github.com/xfmoulet/qoi (Go) +- https://erratique.ch/software/qoic (OCaml) +- https://github.com/arian/go-qoi (Go) +- https://github.com/kchapelier/qoijs (JavaScript) +- https://github.com/KristofferC/QOI.jl (Julia) +- https://github.com/shadowMitia/libqoi/ (C++) +- https://github.com/MKCG/php-qoi (PHP) +- https://github.com/LightHouseSoftware/qoiformats (D) +- https://github.com/mhoward540/qoi-nim (Nim) + + +## QOI Support in Other Software + +- [SerenityOS](https://github.com/SerenityOS/serenity) supports decoding QOI system wide through a custom [cpp implementation in LibGfx](https://github.com/SerenityOS/serenity/blob/master/Userland/Libraries/LibGfx/QOILoader.h) +- [Raylib](https://github.com/raysan5/raylib) supports decoding and encoding QOI textures through its [rtextures module](https://github.com/raysan5/raylib/blob/master/src/rtextures.c) +- [Rebol3](https://github.com/Oldes/Rebol3/issues/39) supports decoding and encoding QOI using a native codec +- [c-ray](https://github.com/vkoskiv/c-ray) supports QOI natively +- [SAIL](https://github.com/HappySeaFox/sail) image decoding library, supports decoding and encoding QOI images +- [Orx](https://github.com/orx/orx) 2D game engine, supports QOI natively + + +## Packages + +[AUR](https://aur.archlinux.org/pkgbase/qoi-git/) - system-wide qoi.h, qoiconv and qoibench install as split packages. + + +## Implementations not yet conforming to the final specification + +These implementations are based on the pre-release version of QOI. Resulting files are not compatible with the current version. + +- https://github.com/ChevyRay/qoi_rs (Rust) +- https://github.com/panzi/jsqoi (TypeScript) +- https://github.com/0xd34df00d/hsqoi (Haskell) + diff --git a/src/qoi/qoi.h b/src/qoi/qoi.h new file mode 100644 index 0000000000..988f9edcb4 --- /dev/null +++ b/src/qoi/qoi.h @@ -0,0 +1,671 @@ +/* + +QOI - The "Quite OK Image" format for fast, lossless image compression + +Dominic Szablewski - https://phoboslab.org + + +-- LICENSE: The MIT License(MIT) + +Copyright(c) 2021 Dominic Szablewski + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files(the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions : +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +-- About + +QOI encodes and decodes images in a lossless format. Compared to stb_image and +stb_image_write QOI offers 20x-50x faster encoding, 3x-4x faster decoding and +20% better compression. + + +-- Synopsis + +// Define `QOI_IMPLEMENTATION` in *one* C/C++ file before including this +// library to create the implementation. + +#define QOI_IMPLEMENTATION +#include "qoi.h" + +// Encode and store an RGBA buffer to the file system. The qoi_desc describes +// the input pixel data. +qoi_write("image_new.qoi", rgba_pixels, &(qoi_desc){ + .width = 1920, + .height = 1080, + .channels = 4, + .colorspace = QOI_SRGB +}); + +// Load and decode a QOI image from the file system into a 32bbp RGBA buffer. +// The qoi_desc struct will be filled with the width, height, number of channels +// and colorspace read from the file header. +qoi_desc desc; +void *rgba_pixels = qoi_read("image.qoi", &desc, 4); + + + +-- Documentation + +This library provides the following functions; +- qoi_read -- read and decode a QOI file +- qoi_decode -- decode the raw bytes of a QOI image from memory +- qoi_write -- encode and write a QOI file +- qoi_encode -- encode an rgba buffer into a QOI image in memory + +See the function declaration below for the signature and more information. + +If you don't want/need the qoi_read and qoi_write functions, you can define +QOI_NO_STDIO before including this library. + +This library uses malloc() and free(). To supply your own malloc implementation +you can define QOI_MALLOC and QOI_FREE before including this library. + +This library uses memset() to zero-initialize the index. To supply your own +implementation you can define QOI_ZEROARR before including this library. + + +-- Data Format + +A QOI file has a 14 byte header, followed by any number of data "chunks" and an +8-byte end marker. + +struct qoi_header_t { + char magic[4]; // magic bytes "qoif" + uint32_t width; // image width in pixels (BE) + uint32_t height; // image height in pixels (BE) + uint8_t channels; // 3 = RGB, 4 = RGBA + uint8_t colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear +}; + +Images are encoded row by row, left to right, top to bottom. The decoder and +encoder start with {r: 0, g: 0, b: 0, a: 255} as the previous pixel value. An +image is complete when all pixels specified by width * height have been covered. + +Pixels are encoded as + - a run of the previous pixel + - an index into an array of previously seen pixels + - a difference to the previous pixel value in r,g,b + - full r,g,b or r,g,b,a values + +The color channels are assumed to not be premultiplied with the alpha channel +("un-premultiplied alpha"). + +A running array[64] (zero-initialized) of previously seen pixel values is +maintained by the encoder and decoder. Each pixel that is seen by the encoder +and decoder is put into this array at the position formed by a hash function of +the color value. In the encoder, if the pixel value at the index matches the +current pixel, this index position is written to the stream as QOI_OP_INDEX. +The hash function for the index is: + + index_position = (r * 3 + g * 5 + b * 7 + a * 11) % 64 + +Each chunk starts with a 2- or 8-bit tag, followed by a number of data bits. The +bit length of chunks is divisible by 8 - i.e. all chunks are byte aligned. All +values encoded in these data bits have the most significant bit on the left. + +The 8-bit tags have precedence over the 2-bit tags. A decoder must check for the +presence of an 8-bit tag first. + +The byte stream's end is marked with 7 0x00 bytes followed a single 0x01 byte. + + +The possible chunks are: + + +.- QOI_OP_INDEX ----------. +| Byte[0] | +| 7 6 5 4 3 2 1 0 | +|-------+-----------------| +| 0 0 | index | +`-------------------------` +2-bit tag b00 +6-bit index into the color index array: 0..63 + +A valid encoder must not issue 2 or more consecutive QOI_OP_INDEX chunks to the +same index. QOI_OP_RUN should be used instead. + + +.- QOI_OP_DIFF -----------. +| Byte[0] | +| 7 6 5 4 3 2 1 0 | +|-------+-----+-----+-----| +| 0 1 | dr | dg | db | +`-------------------------` +2-bit tag b01 +2-bit red channel difference from the previous pixel between -2..1 +2-bit green channel difference from the previous pixel between -2..1 +2-bit blue channel difference from the previous pixel between -2..1 + +The difference to the current channel values are using a wraparound operation, +so "1 - 2" will result in 255, while "255 + 1" will result in 0. + +Values are stored as unsigned integers with a bias of 2. E.g. -2 is stored as +0 (b00). 1 is stored as 3 (b11). + +The alpha value remains unchanged from the previous pixel. + + +.- QOI_OP_LUMA -------------------------------------. +| Byte[0] | Byte[1] | +| 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 | +|-------+-----------------+-------------+-----------| +| 1 0 | green diff | dr - dg | db - dg | +`---------------------------------------------------` +2-bit tag b10 +6-bit green channel difference from the previous pixel -32..31 +4-bit red channel difference minus green channel difference -8..7 +4-bit blue channel difference minus green channel difference -8..7 + +The green channel is used to indicate the general direction of change and is +encoded in 6 bits. The red and blue channels (dr and db) base their diffs off +of the green channel difference and are encoded in 4 bits. I.e.: + dr_dg = (cur_px.r - prev_px.r) - (cur_px.g - prev_px.g) + db_dg = (cur_px.b - prev_px.b) - (cur_px.g - prev_px.g) + +The difference to the current channel values are using a wraparound operation, +so "10 - 13" will result in 253, while "250 + 7" will result in 1. + +Values are stored as unsigned integers with a bias of 32 for the green channel +and a bias of 8 for the red and blue channel. + +The alpha value remains unchanged from the previous pixel. + + +.- QOI_OP_RUN ------------. +| Byte[0] | +| 7 6 5 4 3 2 1 0 | +|-------+-----------------| +| 1 1 | run | +`-------------------------` +2-bit tag b11 +6-bit run-length repeating the previous pixel: 1..62 + +The run-length is stored with a bias of -1. Note that the run-lengths 63 and 64 +(b111110 and b111111) are illegal as they are occupied by the QOI_OP_RGB and +QOI_OP_RGBA tags. + + +.- QOI_OP_RGB ------------------------------------------. +| Byte[0] | Byte[1] | Byte[2] | Byte[3] | +| 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | +|-------------------------+---------+---------+---------| +| 1 1 1 1 1 1 1 0 | red | green | blue | +`-------------------------------------------------------` +8-bit tag b11111110 +8-bit red channel value +8-bit green channel value +8-bit blue channel value + +The alpha value remains unchanged from the previous pixel. + + +.- QOI_OP_RGBA ---------------------------------------------------. +| Byte[0] | Byte[1] | Byte[2] | Byte[3] | Byte[4] | +| 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | +|-------------------------+---------+---------+---------+---------| +| 1 1 1 1 1 1 1 1 | red | green | blue | alpha | +`-----------------------------------------------------------------` +8-bit tag b11111111 +8-bit red channel value +8-bit green channel value +8-bit blue channel value +8-bit alpha channel value + +*/ + + +/* ----------------------------------------------------------------------------- +Header - Public functions */ + +#ifndef QOI_H +#define QOI_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* A pointer to a qoi_desc struct has to be supplied to all of qoi's functions. +It describes either the input format (for qoi_write and qoi_encode), or is +filled with the description read from the file header (for qoi_read and +qoi_decode). + +The colorspace in this qoi_desc is an enum where + 0 = sRGB, i.e. gamma scaled RGB channels and a linear alpha channel + 1 = all channels are linear +You may use the constants QOI_SRGB or QOI_LINEAR. The colorspace is purely +informative. It will be saved to the file header, but does not affect +how chunks are en-/decoded. */ + +#define QOI_SRGB 0 +#define QOI_LINEAR 1 + +typedef struct { + unsigned int width; + unsigned int height; + unsigned char channels; + unsigned char colorspace; +} qoi_desc; + +#ifndef QOI_NO_STDIO + +/* Encode raw RGB or RGBA pixels into a QOI image and write it to the file +system. The qoi_desc struct must be filled with the image width, height, +number of channels (3 = RGB, 4 = RGBA) and the colorspace. + +The function returns 0 on failure (invalid parameters, or fopen or malloc +failed) or the number of bytes written on success. */ + +int qoi_write(const char *filename, const void *data, const qoi_desc *desc); + + +/* Read and decode a QOI image from the file system. If channels is 0, the +number of channels from the file header is used. If channels is 3 or 4 the +output format will be forced into this number of channels. + +The function either returns NULL on failure (invalid data, or malloc or fopen +failed) or a pointer to the decoded pixels. On success, the qoi_desc struct +will be filled with the description from the file header. + +The returned pixel data should be free()d after use. */ + +void *qoi_read(const char *filename, qoi_desc *desc, int channels); + +#endif /* QOI_NO_STDIO */ + + +/* Encode raw RGB or RGBA pixels into a QOI image in memory. + +The function either returns NULL on failure (invalid parameters or malloc +failed) or a pointer to the encoded data on success. On success the out_len +is set to the size in bytes of the encoded data. + +The returned qoi data should be free()d after use. */ + +void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len); + + +/* Decode a QOI image from memory. + +The function either returns NULL on failure (invalid parameters or malloc +failed) or a pointer to the decoded pixels. On success, the qoi_desc struct +is filled with the description from the file header. + +The returned pixel data should be free()d after use. */ + +void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels); + + +#ifdef __cplusplus +} +#endif +#endif /* QOI_H */ + + +/* ----------------------------------------------------------------------------- +Implementation */ + +#ifdef QOI_IMPLEMENTATION +#include +#include + +#ifndef QOI_MALLOC + #define QOI_MALLOC(sz) malloc(sz) + #define QOI_FREE(p) free(p) +#endif +#ifndef QOI_ZEROARR + #define QOI_ZEROARR(a) memset((a),0,sizeof(a)) +#endif + +#define QOI_OP_INDEX 0x00 /* 00xxxxxx */ +#define QOI_OP_DIFF 0x40 /* 01xxxxxx */ +#define QOI_OP_LUMA 0x80 /* 10xxxxxx */ +#define QOI_OP_RUN 0xc0 /* 11xxxxxx */ +#define QOI_OP_RGB 0xfe /* 11111110 */ +#define QOI_OP_RGBA 0xff /* 11111111 */ + +#define QOI_MASK_2 0xc0 /* 11000000 */ + +#define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11) +#define QOI_MAGIC \ + (((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \ + ((unsigned int)'i') << 8 | ((unsigned int)'f')) +#define QOI_HEADER_SIZE 14 + +/* 2GB is the max file size that this implementation can safely handle. We guard +against anything larger than that, assuming the worst case with 5 bytes per +pixel, rounded down to a nice clean value. 400 million pixels ought to be +enough for anybody. */ +#define QOI_PIXELS_MAX ((unsigned int)400000000) + +typedef union { + struct { unsigned char r, g, b, a; } rgba; + unsigned int v; +} qoi_rgba_t; + +static const unsigned char qoi_padding[8] = {0,0,0,0,0,0,0,1}; + +static void qoi_write_32(unsigned char *bytes, int *p, unsigned int v) { + bytes[(*p)++] = (0xff000000 & v) >> 24; + bytes[(*p)++] = (0x00ff0000 & v) >> 16; + bytes[(*p)++] = (0x0000ff00 & v) >> 8; + bytes[(*p)++] = (0x000000ff & v); +} + +static unsigned int qoi_read_32(const unsigned char *bytes, int *p) { + unsigned int a = bytes[(*p)++]; + unsigned int b = bytes[(*p)++]; + unsigned int c = bytes[(*p)++]; + unsigned int d = bytes[(*p)++]; + return a << 24 | b << 16 | c << 8 | d; +} + +void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) { + int i, max_size, p, run; + int px_len, px_end, px_pos, channels; + unsigned char *bytes; + const unsigned char *pixels; + qoi_rgba_t index[64]; + qoi_rgba_t px, px_prev; + + if ( + data == NULL || out_len == NULL || desc == NULL || + desc->width == 0 || desc->height == 0 || + desc->channels < 3 || desc->channels > 4 || + desc->colorspace > 1 || + desc->height >= QOI_PIXELS_MAX / desc->width + ) { + return NULL; + } + + max_size = + desc->width * desc->height * (desc->channels + 1) + + QOI_HEADER_SIZE + sizeof(qoi_padding); + + p = 0; + bytes = (unsigned char *) QOI_MALLOC(max_size); + if (!bytes) { + return NULL; + } + + qoi_write_32(bytes, &p, QOI_MAGIC); + qoi_write_32(bytes, &p, desc->width); + qoi_write_32(bytes, &p, desc->height); + bytes[p++] = desc->channels; + bytes[p++] = desc->colorspace; + + + pixels = (const unsigned char *)data; + + QOI_ZEROARR(index); + + run = 0; + px_prev.rgba.r = 0; + px_prev.rgba.g = 0; + px_prev.rgba.b = 0; + px_prev.rgba.a = 255; + px = px_prev; + + px_len = desc->width * desc->height * desc->channels; + px_end = px_len - desc->channels; + channels = desc->channels; + + for (px_pos = 0; px_pos < px_len; px_pos += channels) { + if (channels == 4) { + px = *(qoi_rgba_t *)(pixels + px_pos); + } + else { + px.rgba.r = pixels[px_pos + 0]; + px.rgba.g = pixels[px_pos + 1]; + px.rgba.b = pixels[px_pos + 2]; + } + + if (px.v == px_prev.v) { + run++; + if (run == 62 || px_pos == px_end) { + bytes[p++] = QOI_OP_RUN | (run - 1); + run = 0; + } + } + else { + int index_pos; + + if (run > 0) { + bytes[p++] = QOI_OP_RUN | (run - 1); + run = 0; + } + + index_pos = QOI_COLOR_HASH(px) % 64; + + if (index[index_pos].v == px.v) { + bytes[p++] = QOI_OP_INDEX | index_pos; + } + else { + index[index_pos] = px; + + if (px.rgba.a == px_prev.rgba.a) { + signed char vr = px.rgba.r - px_prev.rgba.r; + signed char vg = px.rgba.g - px_prev.rgba.g; + signed char vb = px.rgba.b - px_prev.rgba.b; + + signed char vg_r = vr - vg; + signed char vg_b = vb - vg; + + if ( + vr > -3 && vr < 2 && + vg > -3 && vg < 2 && + vb > -3 && vb < 2 + ) { + bytes[p++] = QOI_OP_DIFF | (vr + 2) << 4 | (vg + 2) << 2 | (vb + 2); + } + else if ( + vg_r > -9 && vg_r < 8 && + vg > -33 && vg < 32 && + vg_b > -9 && vg_b < 8 + ) { + bytes[p++] = QOI_OP_LUMA | (vg + 32); + bytes[p++] = (vg_r + 8) << 4 | (vg_b + 8); + } + else { + bytes[p++] = QOI_OP_RGB; + bytes[p++] = px.rgba.r; + bytes[p++] = px.rgba.g; + bytes[p++] = px.rgba.b; + } + } + else { + bytes[p++] = QOI_OP_RGBA; + bytes[p++] = px.rgba.r; + bytes[p++] = px.rgba.g; + bytes[p++] = px.rgba.b; + bytes[p++] = px.rgba.a; + } + } + } + px_prev = px; + } + + for (i = 0; i < (int)sizeof(qoi_padding); i++) { + bytes[p++] = qoi_padding[i]; + } + + *out_len = p; + return bytes; +} + +void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) { + const unsigned char *bytes; + unsigned int header_magic; + unsigned char *pixels; + qoi_rgba_t index[64]; + qoi_rgba_t px; + int px_len, chunks_len, px_pos; + int p = 0, run = 0; + + if ( + data == NULL || desc == NULL || + (channels != 0 && channels != 3 && channels != 4) || + size < QOI_HEADER_SIZE + (int)sizeof(qoi_padding) + ) { + return NULL; + } + + bytes = (const unsigned char *)data; + + header_magic = qoi_read_32(bytes, &p); + desc->width = qoi_read_32(bytes, &p); + desc->height = qoi_read_32(bytes, &p); + desc->channels = bytes[p++]; + desc->colorspace = bytes[p++]; + + if ( + desc->width == 0 || desc->height == 0 || + desc->channels < 3 || desc->channels > 4 || + desc->colorspace > 1 || + header_magic != QOI_MAGIC || + desc->height >= QOI_PIXELS_MAX / desc->width + ) { + return NULL; + } + + if (channels == 0) { + channels = desc->channels; + } + + px_len = desc->width * desc->height * channels; + pixels = (unsigned char *) QOI_MALLOC(px_len); + if (!pixels) { + return NULL; + } + + QOI_ZEROARR(index); + px.rgba.r = 0; + px.rgba.g = 0; + px.rgba.b = 0; + px.rgba.a = 255; + + chunks_len = size - (int)sizeof(qoi_padding); + for (px_pos = 0; px_pos < px_len; px_pos += channels) { + if (run > 0) { + run--; + } + else if (p < chunks_len) { + int b1 = bytes[p++]; + + if (b1 == QOI_OP_RGB) { + px.rgba.r = bytes[p++]; + px.rgba.g = bytes[p++]; + px.rgba.b = bytes[p++]; + } + else if (b1 == QOI_OP_RGBA) { + px.rgba.r = bytes[p++]; + px.rgba.g = bytes[p++]; + px.rgba.b = bytes[p++]; + px.rgba.a = bytes[p++]; + } + else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) { + px = index[b1]; + } + else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) { + px.rgba.r += ((b1 >> 4) & 0x03) - 2; + px.rgba.g += ((b1 >> 2) & 0x03) - 2; + px.rgba.b += ( b1 & 0x03) - 2; + } + else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) { + int b2 = bytes[p++]; + int vg = (b1 & 0x3f) - 32; + px.rgba.r += vg - 8 + ((b2 >> 4) & 0x0f); + px.rgba.g += vg; + px.rgba.b += vg - 8 + (b2 & 0x0f); + } + else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) { + run = (b1 & 0x3f); + } + + index[QOI_COLOR_HASH(px) % 64] = px; + } + + if (channels == 4) { + *(qoi_rgba_t*)(pixels + px_pos) = px; + } + else { + pixels[px_pos + 0] = px.rgba.r; + pixels[px_pos + 1] = px.rgba.g; + pixels[px_pos + 2] = px.rgba.b; + } + } + + return pixels; +} + +#ifndef QOI_NO_STDIO +#include + +int qoi_write(const char *filename, const void *data, const qoi_desc *desc) { + FILE *f = fopen(filename, "wb"); + int size; + void *encoded; + + if (!f) { + return 0; + } + + encoded = qoi_encode(data, desc, &size); + if (!encoded) { + fclose(f); + return 0; + } + + fwrite(encoded, 1, size, f); + fclose(f); + + QOI_FREE(encoded); + return size; +} + +void *qoi_read(const char *filename, qoi_desc *desc, int channels) { + FILE *f = fopen(filename, "rb"); + int size, bytes_read; + void *pixels, *data; + + if (!f) { + return NULL; + } + + fseek(f, 0, SEEK_END); + size = ftell(f); + if (size <= 0) { + fclose(f); + return NULL; + } + fseek(f, 0, SEEK_SET); + + data = QOI_MALLOC(size); + if (!data) { + fclose(f); + return NULL; + } + + bytes_read = fread(data, 1, size, f); + fclose(f); + + pixels = qoi_decode(data, bytes_read, desc, channels); + QOI_FREE(data); + return pixels; +} + +#endif /* QOI_NO_STDIO */ +#endif /* QOI_IMPLEMENTATION */ diff --git a/src/qoi/qoilib.c b/src/qoi/qoilib.c new file mode 100644 index 0000000000..e3aa809c74 --- /dev/null +++ b/src/qoi/qoilib.c @@ -0,0 +1,6 @@ +// PrusaSlicer specific: +// Include and compile QOI library. + +#define QOI_IMPLEMENTATION +#define QOI_NO_STDIO +#include "qoi.h" diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 5f7b4e8d3f..5d3d47c202 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -190,7 +190,7 @@ void BackgroundSlicingProcess::process_sla() ThumbnailsParams{current_print()->full_print_config().option("thumbnails")->values, true, true, true, true}); Zipper zipper(export_path); - m_sla_archive.export_print(zipper, *m_sla_print); // true, false, true, true); // renders also supports and pad + m_sla_print->export_print(zipper); for (const ThumbnailData& data : thumbnails) if (data.is_valid()) write_thumbnail(zipper, data); @@ -741,7 +741,7 @@ void BackgroundSlicingProcess::prepare_upload() ThumbnailsParams{current_print()->full_print_config().option("thumbnails")->values, true, true, true, true}); // true, false, true, true); // renders also supports and pad Zipper zipper{source_path.string()}; - m_sla_archive.export_print(zipper, *m_sla_print, m_upload_job.upload_data.upload_path.string()); + m_sla_print->export_print(zipper, m_upload_job.upload_data.upload_path.string()); for (const ThumbnailData& data : thumbnails) if (data.is_valid()) write_thumbnail(zipper, data); diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index 5fba237e35..00a3ab6d05 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -11,7 +11,6 @@ #include "libslic3r/PrintBase.hpp" #include "libslic3r/GCode/ThumbnailData.hpp" -#include "libslic3r/Format/SL1.hpp" #include "slic3r/Utils/PrintHost.hpp" #include "libslic3r/GCode/GCodeProcessor.hpp" @@ -84,7 +83,7 @@ public: ~BackgroundSlicingProcess(); void set_fff_print(Print *print) { m_fff_print = print; } - void set_sla_print(SLAPrint *print) { m_sla_print = print; m_sla_print->set_printer(&m_sla_archive); } + void set_sla_print(SLAPrint *print) { m_sla_print = print; } void set_thumbnail_cb(ThumbnailsGeneratorCallback cb) { m_thumbnail_cb = cb; } void set_gcode_result(GCodeProcessorResult* result) { m_gcode_result = result; } @@ -218,9 +217,9 @@ private: // Data structure, to which the G-code export writes its annotations. GCodeProcessorResult *m_gcode_result = nullptr; // Callback function, used to write thumbnails into gcode. - ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr; - SL1Archive m_sla_archive; - // Temporary G-code, there is one defined for the BackgroundSlicingProcess, differentiated from the other processes by a process ID. + ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr; + // Temporary G-code, there is one defined for the BackgroundSlicingProcess, + // differentiated from the other processes by a process ID. std::string m_temp_output_path; // Output path provided by the user. The output path may be set even if the slicing is running, // but once set, it cannot be re-set. diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index ee5aacb191..b5201d8618 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -1085,6 +1085,8 @@ void Control::Ruler::update(wxWindow* win, const std::vector& values, do } long_step = step == 0 ? -1.0 : (double)step* std::pow(10, pow); + if (long_step < 0) + short_step = long_step; } void Control::draw_ruler(wxDC& dc) @@ -1103,44 +1105,76 @@ void Control::draw_ruler(wxDC& dc) wxColour old_clr = dc.GetTextForeground(); dc.SetTextForeground(GREY_PEN.GetColour()); - if (m_ruler.long_step < 0) - for (size_t tick = 1; tick < m_values.size(); tick++) { - wxCoord pos = get_position_from_value(tick); - draw_ticks_pair(dc, pos, mid, 5); - draw_tick_text(dc, wxPoint(mid, pos), tick); + auto draw_short_ticks = [this, mid](wxDC& dc, double& current_tick, int max_tick) { + if (m_ruler.short_step <= 0.0) + return; + while (current_tick < max_tick) { + wxCoord pos = get_position_from_value(lround(current_tick)); + draw_ticks_pair(dc, pos, mid, 2); + current_tick += m_ruler.short_step; + if (current_tick > m_max_value) + break; } - else { - auto draw_short_ticks = [this, mid](wxDC& dc, double& current_tick, int max_tick) { - while (current_tick < max_tick) { - wxCoord pos = get_position_from_value(lround(current_tick)); - draw_ticks_pair(dc, pos, mid, 2); - current_tick += m_ruler.short_step; - if (current_tick > m_max_value) + }; + + double short_tick = std::nan(""); + int tick = 0; + double value = 0.0; + size_t sequence = 0; + int prev_y_pos = -1; + wxCoord label_height = dc.GetMultiLineTextExtent("0").y - 2; + int values_size = (int)m_values.size(); + + if (m_ruler.long_step < 0) { + // sequential print when long_step wasn't detected because of a lot of printed objects + if (m_ruler.max_values.size() > 1) { + while (tick <= m_max_value && sequence < m_ruler.count()) { + // draw just ticks with max value + value = m_ruler.max_values[sequence]; + short_tick = tick; + + for (; tick < values_size; tick++) { + if (m_values[tick] == value) + break; + if (m_values[tick] > value) { + if (tick > 0) + tick--; + break; + } + } + if (tick > m_max_value) break; + + wxCoord pos = get_position_from_value(tick); + draw_ticks_pair(dc, pos, mid, 5); + if (prev_y_pos < 0 || prev_y_pos - pos >= label_height) { + draw_tick_text(dc, wxPoint(mid, pos), tick); + prev_y_pos = pos; + } + draw_short_ticks(dc, short_tick, tick); + + sequence++; + tick++; } - }; - - double short_tick = std::nan(""); - int tick = 0; - double value = 0.0; - size_t sequence = 0; - - int prev_y_pos = -1; - wxCoord label_height = dc.GetMultiLineTextExtent("0").y - 2; - int values_size = (int)m_values.size(); - + } + // very short object or some non-trivial ruler with non-regular step (see https://github.com/prusa3d/PrusaSlicer/issues/7263) + else { + if (get_scroll_step() < 1) // step less then 1 px indicates very tall object with non-regular laayer step (probably in vase mode) + return; + for (size_t tick = 1; tick < m_values.size(); tick++) { + wxCoord pos = get_position_from_value(tick); + draw_ticks_pair(dc, pos, mid, 5); + draw_tick_text(dc, wxPoint(mid, pos), tick); + } + } + } + else { while (tick <= m_max_value) { value += m_ruler.long_step; - if (value > m_ruler.max_values[sequence] && sequence < m_ruler.count()) { - value = m_ruler.long_step; - for (; tick < values_size; tick++) - if (m_values[tick] < value) - break; - // short ticks from the last tick to the end of current sequence - assert(! std::isnan(short_tick)); - draw_short_ticks(dc, short_tick, tick); - sequence++; - } + + if (sequence < m_ruler.count() && value > m_ruler.max_values[sequence]) + value = m_ruler.max_values[sequence]; + short_tick = tick; for (; tick < values_size; tick++) { @@ -1164,7 +1198,7 @@ void Control::draw_ruler(wxDC& dc) draw_short_ticks(dc, short_tick, tick); - if (value == m_ruler.max_values[sequence] && sequence < m_ruler.count()) { + if (sequence < m_ruler.count() && value == m_ruler.max_values[sequence]) { value = 0.0; sequence++; tick++; diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index cbf760ff48..3b99397ad5 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -211,7 +211,7 @@ bool GLTexture::load_from_svg_files_as_sprites_array(const std::vectorwidth, image->height); // offset by 1 to leave the first pixel empty (both in x and y) - nsvgRasterize(rast, image, 1, 1, scale, sprite_data.data(), sprite_size_px, sprite_size_px, sprite_stride); + nsvgRasterize(rast, image, 1, 1, scale, sprite_data.data(), sprite_size_px_ex, sprite_size_px_ex, sprite_stride); // makes white only copy of the sprite ::memcpy((void*)sprite_white_only_data.data(), (const void*)sprite_data.data(), sprite_bytes); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 8dd91759ad..766c48becf 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1691,16 +1691,16 @@ void ObjectList::load_shape_object(const std::string& type_name) if (selection.get_object_idx() != -1) return; - const int obj_idx = m_objects->size(); - if (obj_idx < 0) - return; - take_snapshot(_L("Add Shape")); // Create mesh BoundingBoxf3 bb; TriangleMesh mesh = create_mesh(type_name, bb); load_mesh_object(mesh, _L("Shape") + "-" + _(type_name)); +#if ENABLE_RELOAD_FROM_DISK_REWORK + if (!m_objects->empty()) + m_objects->back()->volumes.front()->source.is_from_builtin_objects = true; +#endif // ENABLE_RELOAD_FROM_DISK_REWORK wxGetApp().mainframe->update_title(); } diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 4ee7882d25..69e41e8065 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -1438,8 +1438,8 @@ ManipulationEditor::ManipulationEditor(ObjectManipulation* parent, parent->set_focused_editor(nullptr); #if ENABLE_OBJECT_MANIPULATOR_FOCUS - // if the widget loosing focus is not a manipulator field, call kill_focus - if (dynamic_cast(e.GetWindow()) == nullptr) + // if the widgets exchanging focus are both manipulator fields, call kill_focus + if (dynamic_cast(e.GetEventObject()) != nullptr && dynamic_cast(e.GetWindow()) != nullptr) #else if (!m_enter_pressed) #endif // ENABLE_OBJECT_MANIPULATOR_FOCUS diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 0c6de27924..7f35f5a516 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3539,8 +3539,45 @@ void Plater::priv::replace_with_stl() } } +#if ENABLE_RELOAD_FROM_DISK_REWORK +static std::vector> reloadable_volumes(const Model& model, const Selection& selection) +{ + std::vector> ret; + const std::set& selected_volumes_idxs = selection.get_volume_idxs(); + for (unsigned int idx : selected_volumes_idxs) { + const GLVolume& v = *selection.get_volume(idx); + const int o_idx = v.object_idx(); + if (0 <= o_idx && o_idx < int(model.objects.size())) { + const ModelObject* obj = model.objects[o_idx]; + const int v_idx = v.volume_idx(); + if (0 <= v_idx && v_idx < int(obj->volumes.size())) { + const ModelVolume* vol = obj->volumes[v_idx]; + if (!vol->source.is_from_builtin_objects && !vol->source.input_file.empty() && + !fs::path(vol->source.input_file).extension().string().empty()) + ret.push_back({ o_idx, v_idx }); + } + } + } + return ret; +} +#endif // ENABLE_RELOAD_FROM_DISK_REWORK + void Plater::priv::reload_from_disk() { +#if ENABLE_RELOAD_FROM_DISK_REWORK + // collect selected reloadable ModelVolumes + std::vector> selected_volumes = reloadable_volumes(model, get_selection()); + + // nothing to reload, return + if (selected_volumes.empty()) + return; + + std::sort(selected_volumes.begin(), selected_volumes.end(), [](const std::pair& v1, const std::pair& v2) { + return (v1.first < v2.first) || (v1.first == v2.first && v1.second < v2.second); + }); + selected_volumes.erase(std::unique(selected_volumes.begin(), selected_volumes.end(), [](const std::pair& v1, const std::pair& v2) { + return (v1.first == v2.first) && (v1.second == v2.second); }), selected_volumes.end()); +#else Plater::TakeSnapshot snapshot(q, _L("Reload from disk")); const Selection& selection = get_selection(); @@ -3573,10 +3610,36 @@ void Plater::priv::reload_from_disk() } std::sort(selected_volumes.begin(), selected_volumes.end()); selected_volumes.erase(std::unique(selected_volumes.begin(), selected_volumes.end()), selected_volumes.end()); +#endif // ENABLE_RELOAD_FROM_DISK_REWORK // collects paths of files to load std::vector input_paths; std::vector missing_input_paths; +#if ENABLE_RELOAD_FROM_DISK_REWORK + std::vector> replace_paths; + for (auto [obj_idx, vol_idx] : selected_volumes) { + const ModelObject* object = model.objects[obj_idx]; + const ModelVolume* volume = object->volumes[vol_idx]; + if (fs::exists(volume->source.input_file)) + input_paths.push_back(volume->source.input_file); + else { + // searches the source in the same folder containing the object + bool found = false; + if (!object->input_file.empty()) { + fs::path object_path = fs::path(object->input_file).remove_filename(); + if (!object_path.empty()) { + object_path /= fs::path(volume->source.input_file).filename(); + if (fs::exists(object_path)) { + input_paths.push_back(object_path); + found = true; + } + } + } + if (!found) + missing_input_paths.push_back(volume->source.input_file); + } + } +#else std::vector replace_paths; for (const SelectedVolume& v : selected_volumes) { const ModelObject* object = model.objects[v.object_idx]; @@ -3606,6 +3669,7 @@ void Plater::priv::reload_from_disk() else if (!object->input_file.empty() && volume->is_model_part() && !volume->name.empty() && !volume->source.is_from_builtin_objects) missing_input_paths.push_back(volume->name); } +#endif // ENABLE_RELOAD_FROM_DISK_REWORK std::sort(missing_input_paths.begin(), missing_input_paths.end()); missing_input_paths.erase(std::unique(missing_input_paths.begin(), missing_input_paths.end()), missing_input_paths.end()); @@ -3649,7 +3713,11 @@ void Plater::priv::reload_from_disk() //wxMessageDialog dlg(q, message, wxMessageBoxCaptionStr, wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION); MessageDialog dlg(q, message, wxMessageBoxCaptionStr, wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION); if (dlg.ShowModal() == wxID_YES) - replace_paths.push_back(sel_filename_path); +#if ENABLE_RELOAD_FROM_DISK_REWORK + replace_paths.emplace_back(search, sel_filename_path); +#else + replace_paths.emplace_back(sel_filename_path); +#endif // ENABLE_RELOAD_FROM_DISK_REWORK missing_input_paths.pop_back(); } } @@ -3660,6 +3728,10 @@ void Plater::priv::reload_from_disk() std::sort(replace_paths.begin(), replace_paths.end()); replace_paths.erase(std::unique(replace_paths.begin(), replace_paths.end()), replace_paths.end()); +#if ENABLE_RELOAD_FROM_DISK_REWORK + Plater::TakeSnapshot snapshot(q, _L("Reload from disk")); +#endif // ENABLE_RELOAD_FROM_DISK_REWORK + std::vector fail_list; Busy busy(_L("Reload from:"), q->get_current_canvas3D()->get_wxglcanvas()); @@ -3688,6 +3760,86 @@ void Plater::priv::reload_from_disk() } // update the selected volumes whose source is the current file +#if ENABLE_RELOAD_FROM_DISK_REWORK + for (auto [obj_idx, vol_idx] : selected_volumes) { + ModelObject* old_model_object = model.objects[obj_idx]; + ModelVolume* old_volume = old_model_object->volumes[vol_idx]; + + bool sinking = old_model_object->bounding_box().min.z() < SINKING_Z_THRESHOLD; + + bool has_source = !old_volume->source.input_file.empty() && boost::algorithm::iequals(fs::path(old_volume->source.input_file).filename().string(), fs::path(path).filename().string()); + bool has_name = !old_volume->name.empty() && boost::algorithm::iequals(old_volume->name, fs::path(path).filename().string()); + if (has_source || has_name) { + int new_volume_idx = -1; + int new_object_idx = -1; + bool match_found = false; + // take idxs from the matching volume + if (has_source && old_volume->source.object_idx < int(new_model.objects.size())) { + const ModelObject* obj = new_model.objects[old_volume->source.object_idx]; + if (old_volume->source.volume_idx < int(obj->volumes.size())) { + if (obj->volumes[old_volume->source.volume_idx]->name == old_volume->name) { + new_volume_idx = old_volume->source.volume_idx; + new_object_idx = old_volume->source.object_idx; + match_found = true; + } + } + } + + if (!match_found && has_name) { + // take idxs from the 1st matching volume + for (size_t o = 0; o < new_model.objects.size(); ++o) { + ModelObject* obj = new_model.objects[o]; + bool found = false; + for (size_t v = 0; v < obj->volumes.size(); ++v) { + if (obj->volumes[v]->name == old_volume->name) { + new_volume_idx = (int)v; + new_object_idx = (int)o; + found = true; + break; + } + } + if (found) + break; + } + } + + if (new_object_idx < 0 || int(new_model.objects.size()) <= new_object_idx) { + fail_list.push_back(from_u8(has_source ? old_volume->source.input_file : old_volume->name)); + continue; + } + ModelObject* new_model_object = new_model.objects[new_object_idx]; + if (new_volume_idx < 0 || int(new_model_object->volumes.size()) <= new_volume_idx) { + fail_list.push_back(from_u8(has_source ? old_volume->source.input_file : old_volume->name)); + continue; + } + + old_model_object->add_volume(*new_model_object->volumes[new_volume_idx]); + ModelVolume* new_volume = old_model_object->volumes.back(); + new_volume->set_new_unique_id(); + new_volume->config.apply(old_volume->config); + new_volume->set_type(old_volume->type()); + new_volume->set_material_id(old_volume->material_id()); + new_volume->set_transformation(Geometry::assemble_transform(old_volume->source.transform.get_offset()) * + old_volume->get_transformation().get_matrix(true) * + old_volume->source.transform.get_matrix(true)); + new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); + new_volume->source.object_idx = old_volume->source.object_idx; + new_volume->source.volume_idx = old_volume->source.volume_idx; + assert(!old_volume->source.is_converted_from_inches || !old_volume->source.is_converted_from_meters); + if (old_volume->source.is_converted_from_inches) + new_volume->convert_from_imperial_units(); + else if (old_volume->source.is_converted_from_meters) + new_volume->convert_from_meters(); + std::swap(old_model_object->volumes[vol_idx], old_model_object->volumes.back()); + old_model_object->delete_volume(old_model_object->volumes.size() - 1); + if (!sinking) + old_model_object->ensure_on_bed(); + old_model_object->sort_volumes(wxGetApp().app_config->get("order_volumes") == "1"); + + sla::reproject_points_and_holes(old_model_object); + } + } +#else for (const SelectedVolume& sel_v : selected_volumes) { ModelObject* old_model_object = model.objects[sel_v.object_idx]; ModelVolume* old_volume = old_model_object->volumes[sel_v.volume_idx]; @@ -3764,10 +3916,19 @@ void Plater::priv::reload_from_disk() sla::reproject_points_and_holes(old_model_object); } } +#endif // ENABLE_RELOAD_FROM_DISK_REWORK } busy.reset(); +#if ENABLE_RELOAD_FROM_DISK_REWORK + for (auto [src, dest] : replace_paths) { + for (auto [obj_idx, vol_idx] : selected_volumes) { + if (boost::algorithm::iequals(model.objects[obj_idx]->volumes[vol_idx]->source.input_file, src.string())) + replace_volume_with_stl(obj_idx, vol_idx, dest, ""); + } + } +#else for (size_t i = 0; i < replace_paths.size(); ++i) { const auto& path = replace_paths[i].string(); for (const SelectedVolume& sel_v : selected_volumes) { @@ -3777,6 +3938,7 @@ void Plater::priv::reload_from_disk() replace_volume_with_stl(sel_v.object_idx, sel_v.volume_idx, path, ""); } } +#endif // ENABLE_RELOAD_FROM_DISK_REWORK if (!fail_list.empty()) { wxString message = _L("Unable to reload:") + "\n"; @@ -4568,6 +4730,13 @@ bool Plater::priv::can_replace_with_stl() const bool Plater::priv::can_reload_from_disk() const { +#if ENABLE_RELOAD_FROM_DISK_REWORK + // collect selected reloadable ModelVolumes + std::vector> selected_volumes = reloadable_volumes(model, get_selection()); + // nothing to reload, return + if (selected_volumes.empty()) + return false; +#else // struct to hold selected ModelVolumes by their indices struct SelectedVolume { @@ -4593,6 +4762,21 @@ bool Plater::priv::can_reload_from_disk() const selected_volumes.push_back({ o_idx, v_idx }); } } +#endif // ENABLE_RELOAD_FROM_DISK_REWORK + +#if ENABLE_RELOAD_FROM_DISK_REWORK + std::sort(selected_volumes.begin(), selected_volumes.end(), [](const std::pair& v1, const std::pair& v2) { + return (v1.first < v2.first) || (v1.first == v2.first && v1.second < v2.second); + }); + selected_volumes.erase(std::unique(selected_volumes.begin(), selected_volumes.end(), [](const std::pair& v1, const std::pair& v2) { + return (v1.first == v2.first) && (v1.second == v2.second); }), selected_volumes.end()); + + // collects paths of files to load + std::vector paths; + for (auto [obj_idx, vol_idx] : selected_volumes) { + paths.push_back(model.objects[obj_idx]->volumes[vol_idx]->source.input_file); + } +#else std::sort(selected_volumes.begin(), selected_volumes.end()); selected_volumes.erase(std::unique(selected_volumes.begin(), selected_volumes.end()), selected_volumes.end()); @@ -4606,6 +4790,7 @@ bool Plater::priv::can_reload_from_disk() const else if (!object->input_file.empty() && !volume->name.empty() && !volume->source.is_from_builtin_objects) paths.push_back(volume->name); } +#endif // ENABLE_RELOAD_FROM_DISK_REWORK std::sort(paths.begin(), paths.end()); paths.erase(std::unique(paths.begin(), paths.end()), paths.end()); @@ -5371,17 +5556,22 @@ bool Plater::load_files(const wxArrayString& filenames) if (boost::algorithm::iends_with(filename, ".3mf") || boost::algorithm::iends_with(filename, ".amf")) { LoadType load_type = LoadType::Unknown; if (!model().objects.empty()) { - if (wxGetApp().app_config->get("show_drop_project_dialog") == "1") { - ProjectDropDialog dlg(filename); - if (dlg.ShowModal() == wxID_OK) { - int choice = dlg.get_action(); - load_type = static_cast(choice); - wxGetApp().app_config->set("drop_project_action", std::to_string(choice)); + if ((boost::algorithm::iends_with(filename, ".3mf") && !is_project_3mf(it->string())) || + (boost::algorithm::iends_with(filename, ".amf") && !boost::algorithm::iends_with(filename, ".zip.amf"))) + load_type = LoadType::OpenProject; + else { + if (wxGetApp().app_config->get("show_drop_project_dialog") == "1") { + ProjectDropDialog dlg(filename); + if (dlg.ShowModal() == wxID_OK) { + int choice = dlg.get_action(); + load_type = static_cast(choice); + wxGetApp().app_config->set("drop_project_action", std::to_string(choice)); + } } + else + load_type = static_cast(std::clamp(std::stoi(wxGetApp().app_config->get("drop_project_action")), + static_cast(LoadType::OpenProject), static_cast(LoadType::LoadConfig))); } - else - load_type = static_cast(std::clamp(std::stoi(wxGetApp().app_config->get("drop_project_action")), - static_cast(LoadType::OpenProject), static_cast(LoadType::LoadConfig))); } else load_type = LoadType::OpenProject; diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 2af008a52b..75c5c116fd 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -790,10 +790,10 @@ void PreferencesDialog::create_settings_mode_widget() } std::string opt_key = "settings_layout_mode"; - m_blinkers[opt_key] = new BlinkingBitmap(this); + m_blinkers[opt_key] = new BlinkingBitmap(parent); auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(m_blinkers[opt_key], 0, wxALIGN_CENTER_VERTICAL); + sizer->Add(m_blinkers[opt_key], 0, wxRIGHT, 2); sizer->Add(stb_sizer, 1, wxALIGN_CENTER_VERTICAL); m_optgroup_gui->sizer->Add(sizer, 0, wxEXPAND | wxTOP, em_unit()); @@ -810,13 +810,13 @@ void PreferencesDialog::create_settings_text_color_widget() if (!wxOSX) stb->SetBackgroundStyle(wxBG_STYLE_PAINT); std::string opt_key = "text_colors"; - m_blinkers[opt_key] = new BlinkingBitmap(this); + m_blinkers[opt_key] = new BlinkingBitmap(parent); wxSizer* stb_sizer = new wxStaticBoxSizer(stb, wxVERTICAL); ButtonsDescription::FillSizerWithTextColorDescriptions(stb_sizer, parent, &m_sys_colour, &m_mod_colour); auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(m_blinkers[opt_key], 0, wxALIGN_CENTER_VERTICAL); + sizer->Add(m_blinkers[opt_key], 0, wxRIGHT, 2); sizer->Add(stb_sizer, 1, wxALIGN_CENTER_VERTICAL); m_optgroup_gui->sizer->Add(sizer, 0, wxEXPAND | wxTOP, em_unit()); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 4f19ad8067..a20bdb8310 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2324,6 +2324,7 @@ void TabPrinter::build_fff() option = optgroup->get_option("thumbnails"); option.opt.full_width = true; optgroup->append_single_option_line(option); + optgroup->append_single_option_line("thumbnails_format"); optgroup->append_single_option_line("silent_mode"); optgroup->append_single_option_line("remaining_times"); @@ -2517,6 +2518,11 @@ void TabPrinter::build_sla() optgroup->append_single_option_line("min_initial_exposure_time"); optgroup->append_single_option_line("max_initial_exposure_time"); + + optgroup = page->new_optgroup(L("Output")); + optgroup->append_single_option_line("sla_archive_format"); + optgroup->append_single_option_line("sla_output_precision"); + build_print_host_upload_group(page.get()); const int notes_field_height = 25; // 250 diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index d49b4b1d34..c62977bbaf 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -1030,7 +1030,7 @@ bool UnsavedChangesDialog::save(PresetCollection* dependent_presets, bool show_s wxString get_string_from_enum(const std::string& opt_key, const DynamicPrintConfig& config, bool is_infill = false) { const ConfigOptionDef& def = config.def()->options.at(opt_key); - const std::vector& names = def.enum_labels;//ConfigOptionEnum::get_enum_names(); + const std::vector& names = def.enum_labels.empty() ? def.enum_values : def.enum_labels; int val = config.option(opt_key)->getInt(); // Each infill doesn't use all list of infill declared in PrintConfig.hpp. diff --git a/tests/catch_main.hpp b/tests/catch_main.hpp index 5ab71fdd74..ca5b47da84 100644 --- a/tests/catch_main.hpp +++ b/tests/catch_main.hpp @@ -3,7 +3,7 @@ #define CATCH_CONFIG_EXTERNAL_INTERFACES #define CATCH_CONFIG_MAIN -#define CATCH_CONFIG_DEFAULT_REPORTER "verboseconsole" +// #define CATCH_CONFIG_DEFAULT_REPORTER "verboseconsole" #include namespace Catch { diff --git a/tests/libslic3r/test_marchingsquares.cpp b/tests/libslic3r/test_marchingsquares.cpp index 3553697acd..32b137175a 100644 --- a/tests/libslic3r/test_marchingsquares.cpp +++ b/tests/libslic3r/test_marchingsquares.cpp @@ -20,17 +20,17 @@ using namespace Slic3r; -static double area(const sla::RasterBase::PixelDim &pxd) +static double area(const sla::PixelDim &pxd) { return pxd.w_mm * pxd.h_mm; } static Slic3r::sla::RasterGrayscaleAA create_raster( - const sla::RasterBase::Resolution &res, + const sla::Resolution &res, double disp_w = 100., double disp_h = 100.) { - sla::RasterBase::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; + sla::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)}); sla::RasterBase::Trafo trafo; @@ -107,7 +107,7 @@ static void test_expolys(Rst && rst, svg.Close(); double max_rel_err = 0.1; - sla::RasterBase::PixelDim pxd = rst.pixel_dimensions(); + sla::PixelDim pxd = rst.pixel_dimensions(); double max_abs_err = area(pxd) * scaled(1.) * scaled(1.); BoundingBox ref_bb; @@ -175,7 +175,7 @@ TEST_CASE("Fully covered raster should result in a rectangle", "[MarchingSquares TEST_CASE("4x4 raster with one ring", "[MarchingSquares]") { - sla::RasterBase::PixelDim pixdim{1, 1}; + sla::PixelDim pixdim{1, 1}; // We need one additional row and column to detect edges sla::RasterGrayscaleAA rst{{4, 4}, pixdim, {}, agg::gamma_threshold(.5)}; @@ -205,7 +205,7 @@ TEST_CASE("4x4 raster with one ring", "[MarchingSquares]") { TEST_CASE("4x4 raster with two rings", "[MarchingSquares]") { - sla::RasterBase::PixelDim pixdim{1, 1}; + sla::PixelDim pixdim{1, 1}; // We need one additional row and column to detect edges sla::RasterGrayscaleAA rst{{5, 5}, pixdim, {}, agg::gamma_threshold(.5)}; @@ -321,7 +321,7 @@ static void recreate_object_from_rasters(const std::string &objname, float lh) { std::vector layers = slice_mesh_ex(mesh.its, grid(float(bb.min.z()) + lh, float(bb.max.z()), lh)); - sla::RasterBase::Resolution res{2560, 1440}; + sla::Resolution res{2560, 1440}; double disp_w = 120.96; double disp_h = 68.04; diff --git a/tests/libslic3r/test_png_io.cpp b/tests/libslic3r/test_png_io.cpp index b4fcd6255d..e8229b7163 100644 --- a/tests/libslic3r/test_png_io.cpp +++ b/tests/libslic3r/test_png_io.cpp @@ -9,9 +9,9 @@ using namespace Slic3r; -static sla::RasterGrayscaleAA create_raster(const sla::RasterBase::Resolution &res) +static sla::RasterGrayscaleAA create_raster(const sla::Resolution &res) { - sla::RasterBase::PixelDim pixdim{1., 1.}; + sla::PixelDim pixdim{1., 1.}; auto bb = BoundingBox({0, 0}, {scaled(1.), scaled(1.)}); sla::RasterBase::Trafo trafo; diff --git a/tests/sla_print/sla_print_tests.cpp b/tests/sla_print/sla_print_tests.cpp index db8c5e93ec..f7b0df339a 100644 --- a/tests/sla_print/sla_print_tests.cpp +++ b/tests/sla_print/sla_print_tests.cpp @@ -159,8 +159,8 @@ TEST_CASE("FloorSupportsDoNotPierceModel", "[SLASupportGeneration]") { TEST_CASE("InitializedRasterShouldBeNONEmpty", "[SLARasterOutput]") { // Default Prusa SL1 display parameters - sla::RasterBase::Resolution res{2560, 1440}; - sla::RasterBase::PixelDim pixdim{120. / res.width_px, 68. / res.height_px}; + sla::Resolution res{2560, 1440}; + sla::PixelDim pixdim{120. / res.width_px, 68. / res.height_px}; sla::RasterGrayscaleAAGammaPower raster(res, pixdim, {}, 1.); REQUIRE(raster.resolution().width_px == res.width_px); @@ -186,8 +186,8 @@ TEST_CASE("MirroringShouldBeCorrect", "[SLARasterOutput]") { TEST_CASE("RasterizedPolygonAreaShouldMatch", "[SLARasterOutput]") { double disp_w = 120., disp_h = 68.; - sla::RasterBase::Resolution res{2560, 1440}; - sla::RasterBase::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; + sla::Resolution res{2560, 1440}; + sla::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; double gamma = 1.; sla::RasterGrayscaleAAGammaPower raster(res, pixdim, {}, gamma); diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index 1082df2007..d98a920372 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -307,8 +307,8 @@ void check_validity(const TriangleMesh &input_mesh, int flags) void check_raster_transformations(sla::RasterBase::Orientation o, sla::RasterBase::TMirroring mirroring) { double disp_w = 120., disp_h = 68.; - sla::RasterBase::Resolution res{2560, 1440}; - sla::RasterBase::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; + sla::Resolution res{2560, 1440}; + sla::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)}); sla::RasterBase::Trafo trafo{o, mirroring}; @@ -400,7 +400,7 @@ double raster_white_area(const sla::RasterGrayscaleAA &raster) return a; } -double predict_error(const ExPolygon &p, const sla::RasterBase::PixelDim &pd) +double predict_error(const ExPolygon &p, const sla::PixelDim &pd) { auto lines = p.lines(); double pix_err = pixel_area(FullWhite, pd) / 2.; diff --git a/tests/sla_print/sla_test_utils.hpp b/tests/sla_print/sla_test_utils.hpp index 2264ad856c..7171914d43 100644 --- a/tests/sla_print/sla_test_utils.hpp +++ b/tests/sla_print/sla_test_utils.hpp @@ -175,7 +175,7 @@ void check_raster_transformations(sla::RasterBase::Orientation o, ExPolygon square_with_hole(double v); -inline double pixel_area(TPixel px, const sla::RasterBase::PixelDim &pxdim) +inline double pixel_area(TPixel px, const sla::PixelDim &pxdim) { return (pxdim.h_mm * pxdim.w_mm) * px * 1. / (FullWhite - FullBlack); } @@ -183,7 +183,7 @@ inline double pixel_area(TPixel px, const sla::RasterBase::PixelDim &pxdim) double raster_white_area(const sla::RasterGrayscaleAA &raster); long raster_pxsum(const sla::RasterGrayscaleAA &raster); -double predict_error(const ExPolygon &p, const sla::RasterBase::PixelDim &pd); +double predict_error(const ExPolygon &p, const sla::PixelDim &pd); sla::SupportPoints calc_support_pts( const TriangleMesh & mesh,