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("\n";
+}
+
+} // namespace
+
+// A fake raster from SVG
+class SVGRaster : public sla::RasterBase {
+ // Resolution here will be used for svg boundaries
+ BoundingBox m_bb;
+ sla::Resolution m_res;
+ Trafo m_trafo;
+ Vec2d m_sc;
+
+ std::string m_svg;
+
+public:
+ SVGRaster(const BoundingBox &svgarea, sla::Resolution res, Trafo tr = {})
+ : m_bb{svgarea}
+ , m_res{res}
+ , m_trafo{tr}
+ , m_sc{double(m_res.width_px) / m_bb.size().x(), double(m_res.height_px) / m_bb.size().y()}
+ {
+ // Inside the svg header, the boundaries will be defined in mm to
+ // the actual bed size. The viewport is then defined to work with our
+ // scaled coordinates. All the exported polygons will be in these scaled
+ // coordinates but svg rendering software will interpret them correctly
+ // in mm due to the header's definition.
+ std::string wf = float_to_string_decimal_point(unscaled(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";
+
+ 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 - 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,