mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-07-31 02:21:58 +08:00
SPE-1784: New compressed (binary) gcode format integration
1st installment as part of tech ENABLE_BINARIZED_GCODE Still missing GCode Block save/load
This commit is contained in:
parent
d6c721b42e
commit
ecb1a23edd
@ -162,6 +162,8 @@ set(SLIC3R_SOURCES
|
||||
GCode/WipeTower.hpp
|
||||
GCode/GCodeProcessor.cpp
|
||||
GCode/GCodeProcessor.hpp
|
||||
GCode/GCodeBinarizer.cpp
|
||||
GCode/GCodeBinarizer.hpp
|
||||
GCode/AvoidCrossingPerimeters.cpp
|
||||
GCode/AvoidCrossingPerimeters.hpp
|
||||
GCode.cpp
|
||||
|
@ -19,7 +19,10 @@
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include "LocalesUtils.hpp"
|
||||
#include "libslic3r/format.hpp"
|
||||
#include "format.hpp"
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
#include "libslic3r_version.h"
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
@ -837,6 +840,9 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
|
||||
|
||||
m_processor.initialize(path_tmp);
|
||||
m_processor.set_print(print);
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
m_processor.get_binary_data().reset();
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
GCodeOutputStream file(boost::nowide::fopen(path_tmp.c_str(), "wb"), m_processor);
|
||||
if (! file.is_open())
|
||||
throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n");
|
||||
@ -969,35 +975,47 @@ namespace DoExport {
|
||||
}
|
||||
|
||||
// Fill in print_statistics and return formatted string containing filament statistics to be inserted into G-code comment section.
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
static std::string update_print_stats_and_format_filament_stats(
|
||||
const bool has_wipe_tower,
|
||||
const WipeTowerData &wipe_tower_data,
|
||||
const WipeTowerData &wipe_tower_data,
|
||||
const FullPrintConfig &config,
|
||||
const std::vector<Extruder> &extruders,
|
||||
unsigned int initial_extruder_id,
|
||||
PrintStatistics &print_statistics,
|
||||
bool export_binary_data,
|
||||
BinaryGCode::BinaryData &binary_data)
|
||||
#else
|
||||
static std::string update_print_stats_and_format_filament_stats(
|
||||
const bool has_wipe_tower,
|
||||
const WipeTowerData &wipe_tower_data,
|
||||
const FullPrintConfig &config,
|
||||
const std::vector<Extruder> &extruders,
|
||||
const std::vector<Extruder> &extruders,
|
||||
unsigned int initial_extruder_id,
|
||||
PrintStatistics &print_statistics)
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
{
|
||||
std::string filament_stats_string_out;
|
||||
std::string filament_stats_string_out;
|
||||
|
||||
print_statistics.clear();
|
||||
print_statistics.clear();
|
||||
print_statistics.total_toolchanges = std::max(0, wipe_tower_data.number_of_toolchanges);
|
||||
print_statistics.initial_extruder_id = initial_extruder_id;
|
||||
std::vector<std::string> filament_types;
|
||||
if (! extruders.empty()) {
|
||||
if (! extruders.empty()) {
|
||||
std::pair<std::string, unsigned int> out_filament_used_mm ("; filament used [mm] = ", 0);
|
||||
std::pair<std::string, unsigned int> out_filament_used_cm3("; filament used [cm3] = ", 0);
|
||||
std::pair<std::string, unsigned int> out_filament_used_g ("; filament used [g] = ", 0);
|
||||
std::pair<std::string, unsigned int> out_filament_cost ("; filament cost = ", 0);
|
||||
for (const Extruder &extruder : extruders) {
|
||||
print_statistics.printing_extruders.emplace_back(extruder.id());
|
||||
filament_types.emplace_back(config.filament_type.get_at(extruder.id()));
|
||||
print_statistics.printing_extruders.emplace_back(extruder.id());
|
||||
filament_types.emplace_back(config.filament_type.get_at(extruder.id()));
|
||||
|
||||
double used_filament = extruder.used_filament() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] : 0.f);
|
||||
double extruded_volume = extruder.extruded_volume() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] * 2.4052f : 0.f); // assumes 1.75mm filament diameter
|
||||
double filament_weight = extruded_volume * extruder.filament_density() * 0.001;
|
||||
double filament_cost = filament_weight * extruder.filament_cost() * 0.001;
|
||||
auto append = [&extruder](std::pair<std::string, unsigned int> &dst, const char *tmpl, double value) {
|
||||
assert(is_decimal_separator_point());
|
||||
auto append = [&extruder](std::pair<std::string, unsigned int> &dst, const char *tmpl, double value) {
|
||||
assert(is_decimal_separator_point());
|
||||
while (dst.second < extruder.id()) {
|
||||
// Fill in the non-printing extruders with zeros.
|
||||
dst.first += (dst.second > 0) ? ", 0" : "0";
|
||||
@ -1006,18 +1024,47 @@ namespace DoExport {
|
||||
if (dst.second > 0)
|
||||
dst.first += ", ";
|
||||
char buf[64];
|
||||
sprintf(buf, tmpl, value);
|
||||
sprintf(buf, tmpl, value);
|
||||
dst.first += buf;
|
||||
++ dst.second;
|
||||
};
|
||||
append(out_filament_used_mm, "%.2lf", used_filament);
|
||||
append(out_filament_used_cm3, "%.2lf", extruded_volume * 0.001);
|
||||
if (filament_weight > 0.) {
|
||||
};
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
if (export_binary_data) {
|
||||
char buf[128];
|
||||
sprintf(buf, "%.2lf", used_filament);
|
||||
binary_data.print_metadata.raw_data.push_back({ "filament used [mm]", std::string(buf) });
|
||||
sprintf(buf, "%.2lf", extruded_volume * 0.001);
|
||||
binary_data.print_metadata.raw_data.push_back({ "filament used [cm3]", std::string(buf) });
|
||||
}
|
||||
else {
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
append(out_filament_used_mm, "%.2lf", used_filament);
|
||||
append(out_filament_used_cm3, "%.2lf", extruded_volume * 0.001);
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
}
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
if (filament_weight > 0.) {
|
||||
print_statistics.total_weight = print_statistics.total_weight + filament_weight;
|
||||
append(out_filament_used_g, "%.2lf", filament_weight);
|
||||
if (filament_cost > 0.) {
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
if (export_binary_data) {
|
||||
char buf[128];
|
||||
sprintf(buf, "%.2lf", filament_weight);
|
||||
binary_data.print_metadata.raw_data.push_back({ "filament used [g]", std::string(buf) });
|
||||
}
|
||||
else
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
append(out_filament_used_g, "%.2lf", filament_weight);
|
||||
if (filament_cost > 0.) {
|
||||
print_statistics.total_cost = print_statistics.total_cost + filament_cost;
|
||||
append(out_filament_cost, "%.2lf", filament_cost);
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
if (export_binary_data) {
|
||||
char buf[128];
|
||||
sprintf(buf, "%.2lf", filament_cost);
|
||||
binary_data.print_metadata.raw_data.push_back({ "filament cost", std::string(buf) });
|
||||
}
|
||||
else
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
append(out_filament_cost, "%.2lf", filament_cost);
|
||||
}
|
||||
}
|
||||
print_statistics.total_used_filament += used_filament;
|
||||
@ -1026,18 +1073,18 @@ namespace DoExport {
|
||||
print_statistics.total_wipe_tower_cost += has_wipe_tower ? (extruded_volume - extruder.extruded_volume())* extruder.filament_density() * 0.001 * extruder.filament_cost() * 0.001 : 0.;
|
||||
}
|
||||
filament_stats_string_out += out_filament_used_mm.first;
|
||||
filament_stats_string_out += "\n" + out_filament_used_cm3.first;
|
||||
if (out_filament_used_g.second)
|
||||
filament_stats_string_out += "\n" + out_filament_used_g.first;
|
||||
if (out_filament_cost.second)
|
||||
filament_stats_string_out += "\n" + out_filament_cost.first;
|
||||
print_statistics.initial_filament_type = config.filament_type.get_at(initial_extruder_id);
|
||||
std::sort(filament_types.begin(), filament_types.end());
|
||||
print_statistics.printing_filament_types = filament_types.front();
|
||||
for (size_t i = 1; i < filament_types.size(); ++ i) {
|
||||
print_statistics.printing_filament_types += ",";
|
||||
print_statistics.printing_filament_types += filament_types[i];
|
||||
}
|
||||
filament_stats_string_out += "\n" + out_filament_used_cm3.first;
|
||||
if (out_filament_used_g.second)
|
||||
filament_stats_string_out += "\n" + out_filament_used_g.first;
|
||||
if (out_filament_cost.second)
|
||||
filament_stats_string_out += "\n" + out_filament_cost.first;
|
||||
print_statistics.initial_filament_type = config.filament_type.get_at(initial_extruder_id);
|
||||
std::sort(filament_types.begin(), filament_types.end());
|
||||
print_statistics.printing_filament_types = filament_types.front();
|
||||
for (size_t i = 1; i < filament_types.size(); ++ i) {
|
||||
print_statistics.printing_filament_types += ",";
|
||||
print_statistics.printing_filament_types += filament_types[i];
|
||||
}
|
||||
}
|
||||
return filament_stats_string_out;
|
||||
}
|
||||
@ -1082,8 +1129,42 @@ std::vector<const PrintInstance*> sort_object_instances_by_model_order(const Pri
|
||||
|
||||
void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb)
|
||||
{
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
const bool export_to_binary_gcode = print.full_print_config().option<ConfigOptionBool>("gcode_binary")->value;
|
||||
// if exporting gcode in binary format:
|
||||
// we generate here the data to be passed to the post-processor, who is responsible to export them to file
|
||||
// 1) generate the thumbnails
|
||||
// 2) collect the config data
|
||||
if (export_to_binary_gcode) {
|
||||
BinaryGCode::BinaryData& binary_data = m_processor.get_binary_data();
|
||||
|
||||
// Unit tests or command line slicing may not define "thumbnails" or "thumbnails_format".
|
||||
// If "thumbnails_format" is not defined, export to PNG.
|
||||
if (const auto [thumbnails, thumbnails_format] = std::make_pair(
|
||||
print.full_print_config().option<ConfigOptionPoints>("thumbnails"),
|
||||
print.full_print_config().option<ConfigOptionEnum<GCodeThumbnailsFormat>>("thumbnails_format"));
|
||||
thumbnails) {
|
||||
GCodeThumbnails::generate_binary_thumbnails(
|
||||
thumbnail_cb, binary_data.thumbnails, thumbnails->values, thumbnails_format ? thumbnails_format->value : GCodeThumbnailsFormat::PNG,
|
||||
[&print]() { print.throw_if_canceled(); });
|
||||
}
|
||||
|
||||
// file data
|
||||
binary_data.file_metadata.encoding_type = (uint16_t)BinaryGCode::EMetadataEncodingType::INI;
|
||||
binary_data.file_metadata.raw_data.emplace_back("Producer", std::string(SLIC3R_APP_NAME) + " " + std::string(SLIC3R_VERSION));
|
||||
// add here other key/value pairs
|
||||
|
||||
// config data
|
||||
binary_data.slicer_metadata.encoding_type = (uint16_t)BinaryGCode::EMetadataEncodingType::INI;
|
||||
encode_full_config(print, binary_data.slicer_metadata.raw_data);
|
||||
}
|
||||
|
||||
// modifies m_silent_time_estimator_enabled
|
||||
DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled);
|
||||
#else
|
||||
// modifies m_silent_time_estimator_enabled
|
||||
DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled);
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
|
||||
if (! print.config().gcode_substitutions.values.empty()) {
|
||||
m_find_replace = make_unique<GCodeFindReplace>(print.config());
|
||||
@ -1138,16 +1219,23 @@ 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());
|
||||
|
||||
// Unit tests or command line slicing may not define "thumbnails" or "thumbnails_format".
|
||||
// If "thumbnails_format" is not defined, export to PNG.
|
||||
if (const auto [thumbnails, thumbnails_format] = std::make_pair(
|
||||
print.full_print_config().option<ConfigOptionPoints>("thumbnails"),
|
||||
print.full_print_config().option<ConfigOptionEnum<GCodeThumbnailsFormat>>("thumbnails_format"));
|
||||
thumbnails)
|
||||
GCodeThumbnails::export_thumbnails_to_file(
|
||||
thumbnail_cb, thumbnails->values, thumbnails_format ? thumbnails_format->value : GCodeThumbnailsFormat::PNG,
|
||||
[&file](const char* sz) { file.write(sz); },
|
||||
[&print]() { print.throw_if_canceled(); });
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
// if exporting gcode in ascii format, generate the thumbnails here
|
||||
if (!export_to_binary_gcode) {
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
// Unit tests or command line slicing may not define "thumbnails" or "thumbnails_format".
|
||||
// If "thumbnails_format" is not defined, export to PNG.
|
||||
if (const auto [thumbnails, thumbnails_format] = std::make_pair(
|
||||
print.full_print_config().option<ConfigOptionPoints>("thumbnails"),
|
||||
print.full_print_config().option<ConfigOptionEnum<GCodeThumbnailsFormat>>("thumbnails_format"));
|
||||
thumbnails)
|
||||
GCodeThumbnails::export_thumbnails_to_file(
|
||||
thumbnail_cb, thumbnails->values, thumbnails_format ? thumbnails_format->value : GCodeThumbnailsFormat::PNG,
|
||||
[&file](const char* sz) { file.write(sz); },
|
||||
[&print]() { print.throw_if_canceled(); });
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
}
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
|
||||
// Write notes (content of the Print Settings tab -> Notes)
|
||||
{
|
||||
@ -1169,20 +1257,26 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
const double layer_height = first_object->config().layer_height.value;
|
||||
assert(! print.config().first_layer_height.percent);
|
||||
const double first_layer_height = print.config().first_layer_height.value;
|
||||
for (size_t region_id = 0; region_id < print.num_print_regions(); ++ region_id) {
|
||||
const PrintRegion ®ion = print.get_print_region(region_id);
|
||||
file.write_format("; external perimeters extrusion width = %.2fmm\n", region.flow(*first_object, frExternalPerimeter, layer_height).width());
|
||||
file.write_format("; perimeters extrusion width = %.2fmm\n", region.flow(*first_object, frPerimeter, layer_height).width());
|
||||
file.write_format("; infill extrusion width = %.2fmm\n", region.flow(*first_object, frInfill, layer_height).width());
|
||||
file.write_format("; solid infill extrusion width = %.2fmm\n", region.flow(*first_object, frSolidInfill, layer_height).width());
|
||||
file.write_format("; top infill extrusion width = %.2fmm\n", region.flow(*first_object, frTopSolidInfill, layer_height).width());
|
||||
if (print.has_support_material())
|
||||
file.write_format("; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width());
|
||||
if (print.config().first_layer_extrusion_width.value > 0)
|
||||
file.write_format("; first layer extrusion width = %.2fmm\n", region.flow(*first_object, frPerimeter, first_layer_height, true).width());
|
||||
file.write_format("\n");
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
if (!export_to_binary_gcode) {
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
for (size_t region_id = 0; region_id < print.num_print_regions(); ++ region_id) {
|
||||
const PrintRegion ®ion = print.get_print_region(region_id);
|
||||
file.write_format("; external perimeters extrusion width = %.2fmm\n", region.flow(*first_object, frExternalPerimeter, layer_height).width());
|
||||
file.write_format("; perimeters extrusion width = %.2fmm\n", region.flow(*first_object, frPerimeter, layer_height).width());
|
||||
file.write_format("; infill extrusion width = %.2fmm\n", region.flow(*first_object, frInfill, layer_height).width());
|
||||
file.write_format("; solid infill extrusion width = %.2fmm\n", region.flow(*first_object, frSolidInfill, layer_height).width());
|
||||
file.write_format("; top infill extrusion width = %.2fmm\n", region.flow(*first_object, frTopSolidInfill, layer_height).width());
|
||||
if (print.has_support_material())
|
||||
file.write_format("; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width());
|
||||
if (print.config().first_layer_extrusion_width.value > 0)
|
||||
file.write_format("; first layer extrusion width = %.2fmm\n", region.flow(*first_object, frPerimeter, first_layer_height, true).width());
|
||||
file.write_format("\n");
|
||||
}
|
||||
print.throw_if_canceled();
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
}
|
||||
print.throw_if_canceled();
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
|
||||
// adds tags for time estimators
|
||||
if (print.config().remaining_times.value)
|
||||
@ -1487,31 +1581,66 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
print.throw_if_canceled();
|
||||
|
||||
// Get filament stats.
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
file.write(DoExport::update_print_stats_and_format_filament_stats(
|
||||
// Const inputs
|
||||
// Const inputs
|
||||
has_wipe_tower, print.wipe_tower_data(),
|
||||
this->config(),
|
||||
m_writer.extruders(),
|
||||
initial_extruder_id,
|
||||
// Modifies
|
||||
print.m_print_statistics));
|
||||
file.write("\n");
|
||||
file.write_format("; total filament used [g] = %.2lf\n", print.m_print_statistics.total_weight);
|
||||
file.write_format("; total filament cost = %.2lf\n", print.m_print_statistics.total_cost);
|
||||
if (print.m_print_statistics.total_toolchanges > 0)
|
||||
file.write_format("; total toolchanges = %i\n", print.m_print_statistics.total_toolchanges);
|
||||
file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str());
|
||||
print.m_print_statistics,
|
||||
export_to_binary_gcode,
|
||||
m_processor.get_binary_data()
|
||||
));
|
||||
|
||||
// Append full config, delimited by two 'phony' configuration keys prusaslicer_config = begin and prusaslicer_config = end.
|
||||
// The delimiters are structured as configuration key / value pairs to be parsable by older versions of PrusaSlicer G-code viewer.
|
||||
{
|
||||
file.write("\n; prusaslicer_config = begin\n");
|
||||
std::string full_config;
|
||||
append_full_config(print, full_config);
|
||||
if (!full_config.empty())
|
||||
file.write(full_config);
|
||||
file.write("; prusaslicer_config = end\n");
|
||||
if (export_to_binary_gcode) {
|
||||
BinaryGCode::BinaryData& binary_data = m_processor.get_binary_data();
|
||||
char buf[128];
|
||||
sprintf(buf, "%.2lf", print.m_print_statistics.total_weight);
|
||||
binary_data.print_metadata.raw_data.push_back({ "total filament used [g]", std::string(buf) });
|
||||
sprintf(buf, "%.2lf", print.m_print_statistics.total_cost);
|
||||
binary_data.print_metadata.raw_data.push_back({ "total filament cost", std::string(buf) });
|
||||
if (print.m_print_statistics.total_toolchanges > 0)
|
||||
binary_data.print_metadata.raw_data.push_back({ "total toolchanges", std::to_string(print.m_print_statistics.total_toolchanges) });
|
||||
}
|
||||
else {
|
||||
#else
|
||||
file.write(DoExport::update_print_stats_and_format_filament_stats(
|
||||
// Const inputs
|
||||
has_wipe_tower, print.wipe_tower_data(),
|
||||
this->config(),
|
||||
m_writer.extruders(),
|
||||
initial_extruder_id,
|
||||
// Modifies
|
||||
print.m_print_statistics));
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
// if exporting gcode in ascii format, statistics export is done here
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
file.write("\n");
|
||||
file.write_format("; total filament used [g] = %.2lf\n", print.m_print_statistics.total_weight);
|
||||
file.write_format("; total filament cost = %.2lf\n", print.m_print_statistics.total_cost);
|
||||
if (print.m_print_statistics.total_toolchanges > 0)
|
||||
file.write_format("; total toolchanges = %i\n", print.m_print_statistics.total_toolchanges);
|
||||
file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str());
|
||||
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
// if exporting gcode in ascii format, config export is done here
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
// Append full config, delimited by two 'phony' configuration keys prusaslicer_config = begin and prusaslicer_config = end.
|
||||
// The delimiters are structured as configuration key / value pairs to be parsable by older versions of PrusaSlicer G-code viewer.
|
||||
{
|
||||
file.write("\n; prusaslicer_config = begin\n");
|
||||
std::string full_config;
|
||||
append_full_config(print, full_config);
|
||||
if (!full_config.empty())
|
||||
file.write(full_config);
|
||||
file.write("; prusaslicer_config = end\n");
|
||||
}
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
}
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
print.throw_if_canceled();
|
||||
}
|
||||
|
||||
@ -2571,6 +2700,13 @@ void GCode::apply_print_config(const PrintConfig &print_config)
|
||||
|
||||
void GCode::append_full_config(const Print &print, std::string &str)
|
||||
{
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
std::vector<std::pair<std::string, std::string>> config;
|
||||
encode_full_config(print, config);
|
||||
for (const auto& [key, value] : config) {
|
||||
str += "; " + key + " = " + value + "\n";
|
||||
}
|
||||
#else
|
||||
const DynamicPrintConfig &cfg = print.full_print_config();
|
||||
// Sorted list of config keys, which shall not be stored into the G-code. Initializer list.
|
||||
static constexpr auto banned_keys = {
|
||||
@ -2588,8 +2724,35 @@ void GCode::append_full_config(const Print &print, std::string &str)
|
||||
for (const std::string &key : cfg.keys())
|
||||
if (! is_banned(key) && ! cfg.option(key)->is_nil())
|
||||
str += "; " + key + " = " + cfg.opt_serialize(key) + "\n";
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
}
|
||||
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
void GCode::encode_full_config(const Print& print, std::vector<std::pair<std::string, std::string>>& config)
|
||||
{
|
||||
const DynamicPrintConfig& cfg = print.full_print_config();
|
||||
// Sorted list of config keys, which shall not be stored into the G-code. Initializer list.
|
||||
static constexpr auto banned_keys = {
|
||||
"compatible_printers"sv,
|
||||
"compatible_prints"sv,
|
||||
//FIXME The print host keys should not be exported to full_print_config anymore. The following keys may likely be removed.
|
||||
"print_host"sv,
|
||||
"printhost_apikey"sv,
|
||||
"printhost_cafile"sv
|
||||
};
|
||||
assert(std::is_sorted(banned_keys.begin(), banned_keys.end()));
|
||||
auto is_banned = [](const std::string& key) {
|
||||
return std::binary_search(banned_keys.begin(), banned_keys.end(), key);
|
||||
};
|
||||
config.reserve(config.size() + cfg.keys().size());
|
||||
for (const std::string& key : cfg.keys()) {
|
||||
if (!is_banned(key) && !cfg.option(key)->is_nil())
|
||||
config.emplace_back(key, cfg.opt_serialize(key));
|
||||
}
|
||||
config.shrink_to_fit();
|
||||
}
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
|
||||
void GCode::set_extruders(const std::vector<unsigned int> &extruder_ids)
|
||||
{
|
||||
m_writer.set_extruders(extruder_ids);
|
||||
|
@ -21,6 +21,9 @@
|
||||
#include "GCode/GCodeProcessor.hpp"
|
||||
#include "EdgeGrid.hpp"
|
||||
#include "GCode/ThumbnailData.hpp"
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
#include "GCode/GCodeBinarizer.hpp"
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
|
||||
#include <memory>
|
||||
#include <map>
|
||||
@ -187,6 +190,10 @@ public:
|
||||
|
||||
// append full config to the given string
|
||||
static void append_full_config(const Print& print, std::string& str);
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
// translate full config into a list of <key, value> items
|
||||
static void encode_full_config(const Print& print, std::vector<std::pair<std::string, std::string>>& config);
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
|
||||
// Object and support extrusions of the same PrintObject at the same print_z.
|
||||
// public, so that it could be accessed by free helper functions from GCode.cpp
|
||||
|
909
src/libslic3r/GCode/GCodeBinarizer.cpp
Normal file
909
src/libslic3r/GCode/GCodeBinarizer.cpp
Normal file
@ -0,0 +1,909 @@
|
||||
#include "GCodeBinarizer.hpp"
|
||||
|
||||
#if ENABLE_BINARIZED_GCODE_DEBUG
|
||||
#define NOMINMAX
|
||||
#include <windows.h>
|
||||
#include <debugapi.h>
|
||||
#endif // ENABLE_BINARIZED_GCODE_DEBUG
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
|
||||
namespace BinaryGCode {
|
||||
|
||||
static size_t g_checksum_max_cache_size = 65536;
|
||||
|
||||
std::string translate_result(BinaryGCode::EResult result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case BinaryGCode::EResult::Success: { return "Success"; }
|
||||
case BinaryGCode::EResult::ReadError: { return "Read error"; }
|
||||
case BinaryGCode::EResult::WriteError: { return "Write error"; }
|
||||
case BinaryGCode::EResult::InvalidMagicNumber: { return "Invalid magic number"; }
|
||||
case BinaryGCode::EResult::InvalidVersionNumber: { return "Invalid version number"; }
|
||||
case BinaryGCode::EResult::InvalidChecksumType: { return "Invalid checksum type"; }
|
||||
case BinaryGCode::EResult::InvalidBlockType: { return "Invalid block type"; }
|
||||
case BinaryGCode::EResult::InvalidCompressionType: { return "Invalid compression type"; }
|
||||
case BinaryGCode::EResult::InvalidMetadataEncodingType: { return "Invalid metadata encoding type"; }
|
||||
case BinaryGCode::EResult::DataCompressionError: { return "Data compression error"; }
|
||||
case BinaryGCode::EResult::DataUncompressionError: { return "Data uncompression error"; }
|
||||
case BinaryGCode::EResult::MetadataEncodingError: { return "Data encoding error"; }
|
||||
case BinaryGCode::EResult::MetadataDecodingError: { return "Data decoding error"; }
|
||||
case BinaryGCode::EResult::BlockNotFound: { return "Block not found"; }
|
||||
case BinaryGCode::EResult::InvalidChecksum: { return "Invalid checksum"; }
|
||||
case BinaryGCode::EResult::InvalidThumbnailFormat: { return "Invalid thumbnail format"; }
|
||||
case BinaryGCode::EResult::InvalidThumbnailWidth: { return "Invalid thumbnail width"; }
|
||||
case BinaryGCode::EResult::InvalidThumbnailHeight: { return "Invalid thumbnail height"; }
|
||||
case BinaryGCode::EResult::InvalidThumbnailDataSize: { return "Invalid thumbnail data size"; }
|
||||
}
|
||||
return std::string();
|
||||
}
|
||||
|
||||
size_t get_checksum_max_cache_size() { return g_checksum_max_cache_size; }
|
||||
void set_checksum_max_cache_size(size_t size) { g_checksum_max_cache_size = size; }
|
||||
|
||||
static uint16_t checksum_types_count() { return 1 + (uint16_t)EChecksumType::CRC32; }
|
||||
static uint16_t block_types_count() { return 1 + (uint16_t)EBlockType::Thumbnail; }
|
||||
static uint16_t compression_types_count() { return 1 + (uint16_t)ECompressionType::None; }
|
||||
static uint16_t thumbnail_formats_count() { return 1 + (uint16_t)EThumbnailFormat::QOI; }
|
||||
static uint16_t metadata_encoding_types_count() { return 1 + (uint16_t)EMetadataEncodingType::INI; }
|
||||
|
||||
static bool write_to_file(FILE& file, const void* data, size_t data_size)
|
||||
{
|
||||
fwrite(data, 1, data_size, &file);
|
||||
return !ferror(&file);
|
||||
}
|
||||
|
||||
static bool read_from_file(FILE& file, void* data, size_t data_size)
|
||||
{
|
||||
fread(data, 1, data_size, &file);
|
||||
return !ferror(&file);
|
||||
}
|
||||
|
||||
static bool encode_metadata(const std::vector<std::pair<std::string, std::string>>& data_in, std::vector<uint8_t>& data_out,
|
||||
EMetadataEncodingType encoding_type)
|
||||
{
|
||||
for (const auto& [key, value] : data_in) {
|
||||
switch (encoding_type)
|
||||
{
|
||||
case EMetadataEncodingType::INI:
|
||||
{
|
||||
data_out.insert(data_out.end(), key.begin(), key.end());
|
||||
data_out.emplace_back('=');
|
||||
data_out.insert(data_out.end(), value.begin(), value.end());
|
||||
data_out.emplace_back('\n');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool decode_metadata(const std::vector<uint8_t>& data_in, std::vector<std::pair<std::string, std::string>>& data_out,
|
||||
EMetadataEncodingType encoding_type)
|
||||
{
|
||||
switch (encoding_type)
|
||||
{
|
||||
case EMetadataEncodingType::INI:
|
||||
{
|
||||
auto start_it = data_in.begin();
|
||||
auto end_it = data_in.begin();
|
||||
while (end_it != data_in.end()) {
|
||||
while (end_it != data_in.end() && *end_it != '\n') {
|
||||
++end_it;
|
||||
}
|
||||
const std::string item(start_it, end_it);
|
||||
const size_t pos = item.find_first_of('=');
|
||||
if (pos != std::string::npos) {
|
||||
data_out.emplace_back(std::make_pair(item.substr(0, pos), item.substr(pos + 1)));
|
||||
start_it = ++end_it;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool compress(const std::vector<uint8_t>& data_in, std::vector<uint8_t>& data_out, ECompressionType compression_type)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool uncompress(const std::vector<uint8_t>& data_in, std::vector<uint8_t>& data_out, ECompressionType compression_type)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint32_t crc32_sw(const uint8_t* buffer, uint32_t length, uint32_t crc)
|
||||
{
|
||||
uint32_t value = crc ^ 0xFFFFFFFF;
|
||||
while (length--) {
|
||||
value ^= (uint32_t)*buffer++;
|
||||
for (int bit = 0; bit < 8; bit++) {
|
||||
if (value & 1)
|
||||
value = (value >> 1) ^ 0xEDB88320;
|
||||
else
|
||||
value >>= 1;
|
||||
}
|
||||
}
|
||||
value ^= 0xFFFFFFFF;
|
||||
return value;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> encode(const void* data, size_t data_size)
|
||||
{
|
||||
std::vector<uint8_t> ret(data_size);
|
||||
memcpy(ret.data(), data, data_size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
Checksum::Checksum(EChecksumType type)
|
||||
: m_type(type)
|
||||
{
|
||||
if (m_type != EChecksumType::None)
|
||||
m_checksum = std::vector<uint8_t>(checksum_size(m_type), '\0');
|
||||
}
|
||||
|
||||
EChecksumType Checksum::get_type() const
|
||||
{
|
||||
return m_type;
|
||||
}
|
||||
|
||||
void Checksum::append(const std::vector<uint8_t>& data)
|
||||
{
|
||||
size_t remaining_data_size = std::distance(data.begin(), data.end());
|
||||
auto it_begin = data.begin();
|
||||
while (remaining_data_size + m_cache.size() > g_checksum_max_cache_size) {
|
||||
update();
|
||||
if (remaining_data_size > g_checksum_max_cache_size) {
|
||||
m_cache.insert(m_cache.end(), it_begin, it_begin + g_checksum_max_cache_size);
|
||||
it_begin += g_checksum_max_cache_size;
|
||||
remaining_data_size -= g_checksum_max_cache_size;
|
||||
}
|
||||
}
|
||||
|
||||
m_cache.insert(m_cache.end(), it_begin, data.end());
|
||||
}
|
||||
|
||||
bool Checksum::matches(Checksum& other)
|
||||
{
|
||||
update();
|
||||
other.update();
|
||||
return m_checksum == other.m_checksum;
|
||||
}
|
||||
|
||||
EResult Checksum::write(FILE& file)
|
||||
{
|
||||
if (m_type != EChecksumType::None) {
|
||||
update();
|
||||
if (!write_to_file(file, (const void*)m_checksum.data(), m_checksum.size()))
|
||||
return EResult::WriteError;
|
||||
}
|
||||
return EResult::Success;
|
||||
}
|
||||
|
||||
EResult Checksum::read(FILE& file)
|
||||
{
|
||||
if (m_type != EChecksumType::None) {
|
||||
if (!read_from_file(file, (void*)m_checksum.data(), m_checksum.size()))
|
||||
return EResult::ReadError;
|
||||
}
|
||||
return EResult::Success;
|
||||
}
|
||||
|
||||
void Checksum::update()
|
||||
{
|
||||
if (m_cache.empty())
|
||||
return;
|
||||
|
||||
switch (m_type)
|
||||
{
|
||||
case EChecksumType::None:
|
||||
{
|
||||
break;
|
||||
}
|
||||
case EChecksumType::CRC32:
|
||||
{
|
||||
const uint32_t old_crc = *(uint32_t*)m_checksum.data();
|
||||
const uint32_t new_crc = crc32_sw(m_cache.data(), (uint32_t)m_cache.size(), old_crc);
|
||||
*(uint32_t*)m_checksum.data() = new_crc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_cache.clear();
|
||||
}
|
||||
|
||||
EResult FileHeader::write(FILE& file) const
|
||||
{
|
||||
if (magic != *(uint32_t*)(MAGIC.data()))
|
||||
return EResult::InvalidMagicNumber;
|
||||
if (checksum_type >= checksum_types_count())
|
||||
return EResult::InvalidChecksumType;
|
||||
|
||||
if (!write_to_file(file, (const void*)&magic, sizeof(magic)))
|
||||
return EResult::WriteError;
|
||||
if (!write_to_file(file, (const void*)&version, sizeof(version)))
|
||||
return EResult::WriteError;
|
||||
if (!write_to_file(file, (const void*)&checksum_type, sizeof(checksum_type)))
|
||||
return EResult::WriteError;
|
||||
|
||||
return EResult::Success;
|
||||
}
|
||||
|
||||
EResult FileHeader::read(FILE& file, const uint32_t* const max_version)
|
||||
{
|
||||
if (!read_from_file(file, (void*)&magic, sizeof(magic)))
|
||||
return EResult::ReadError;
|
||||
if (magic != *(uint32_t*)(MAGIC.data()))
|
||||
return EResult::InvalidMagicNumber;
|
||||
|
||||
if (!read_from_file(file, (void*)&version, sizeof(version)))
|
||||
return EResult::ReadError;
|
||||
if (max_version != nullptr && version > *max_version)
|
||||
return EResult::InvalidVersionNumber;
|
||||
|
||||
if (!read_from_file(file, (void*)&checksum_type, sizeof(checksum_type)))
|
||||
return EResult::ReadError;
|
||||
if (checksum_type >= checksum_types_count())
|
||||
return EResult::InvalidChecksumType;
|
||||
|
||||
return EResult::Success;
|
||||
}
|
||||
|
||||
void BlockHeader::update_checksum(Checksum& checksum) const
|
||||
{
|
||||
checksum.append(encode((const void*)&type, sizeof(type)));
|
||||
checksum.append(encode((const void*)&compression, sizeof(compression)));
|
||||
checksum.append(encode((const void*)&uncompressed_size, sizeof(uncompressed_size)));
|
||||
if (compression != (uint16_t)ECompressionType::None)
|
||||
checksum.append(encode((const void*)&compressed_size, sizeof(compressed_size)));
|
||||
}
|
||||
|
||||
EResult BlockHeader::write(FILE& file) const
|
||||
{
|
||||
if (!write_to_file(file, (const void*)&type, sizeof(type)))
|
||||
return EResult::WriteError;
|
||||
if (!write_to_file(file, (const void*)&compression, sizeof(compression)))
|
||||
return EResult::WriteError;
|
||||
if (!write_to_file(file, (const void*)&uncompressed_size, sizeof(uncompressed_size)))
|
||||
return EResult::WriteError;
|
||||
if (compression != (uint16_t)ECompressionType::None) {
|
||||
if (!write_to_file(file, (const void*)&compressed_size, sizeof(compressed_size)))
|
||||
return EResult::WriteError;
|
||||
}
|
||||
return EResult::Success;
|
||||
}
|
||||
|
||||
EResult BlockHeader::read(FILE& file)
|
||||
{
|
||||
if (!read_from_file(file, (void*)&type, sizeof(type)))
|
||||
return EResult::ReadError;
|
||||
if (type >= block_types_count())
|
||||
return EResult::InvalidBlockType;
|
||||
|
||||
if (!read_from_file(file, (void*)&compression, sizeof(compression)))
|
||||
return EResult::ReadError;
|
||||
if (compression >= compression_types_count())
|
||||
return EResult::InvalidCompressionType;
|
||||
|
||||
if (!read_from_file(file, (void*)&uncompressed_size, sizeof(uncompressed_size)))
|
||||
return EResult::ReadError;
|
||||
if (compression != (uint16_t)ECompressionType::None) {
|
||||
if (!read_from_file(file, (void*)&compressed_size, sizeof(compressed_size)))
|
||||
return EResult::ReadError;
|
||||
}
|
||||
|
||||
return EResult::Success;
|
||||
}
|
||||
|
||||
EResult BaseMetadataBlock::write(FILE& file, EBlockType block_type, ECompressionType compression_type, Checksum& checksum) const
|
||||
{
|
||||
if (encoding_type > metadata_encoding_types_count())
|
||||
return EResult::InvalidMetadataEncodingType;
|
||||
|
||||
BlockHeader block_header = { (uint16_t)block_type, (uint16_t)compression_type, (uint32_t)0 };
|
||||
std::vector<uint8_t> out_data;
|
||||
if (!raw_data.empty()) {
|
||||
// process payload encoding
|
||||
std::vector<uint8_t> uncompressed_data;
|
||||
if (!encode_metadata(raw_data, uncompressed_data, (EMetadataEncodingType)encoding_type))
|
||||
return EResult::MetadataEncodingError;
|
||||
// process payload compression
|
||||
block_header.uncompressed_size = (uint32_t)uncompressed_data.size();
|
||||
std::vector<uint8_t> compressed_data;
|
||||
if (compression_type != ECompressionType::None) {
|
||||
if (!compress(uncompressed_data, compressed_data, compression_type))
|
||||
return EResult::DataCompressionError;
|
||||
block_header.compressed_size = (uint32_t)compressed_data.size();
|
||||
}
|
||||
out_data.swap((compression_type == ECompressionType::None) ? uncompressed_data : compressed_data);
|
||||
}
|
||||
|
||||
// write block header
|
||||
EResult res = block_header.write(file);
|
||||
if (res != EResult::Success)
|
||||
// propagate error
|
||||
return res;
|
||||
|
||||
// write block payload
|
||||
if (!write_to_file(file, (const void*)&encoding_type, sizeof(encoding_type)))
|
||||
return EResult::WriteError;
|
||||
if (!out_data.empty()) {
|
||||
if (!write_to_file(file, (const void*)out_data.data(), out_data.size()))
|
||||
return EResult::WriteError;
|
||||
}
|
||||
|
||||
if (checksum.get_type() != EChecksumType::None) {
|
||||
// update checksum with block header
|
||||
block_header.update_checksum(checksum);
|
||||
// update checksum with block payload
|
||||
checksum.append(encode((const void*)&encoding_type, sizeof(encoding_type)));
|
||||
if (!out_data.empty())
|
||||
checksum.append(out_data);
|
||||
}
|
||||
|
||||
return EResult::Success;
|
||||
}
|
||||
|
||||
EResult BaseMetadataBlock::read_data(FILE& file, const BlockHeader& block_header)
|
||||
{
|
||||
const ECompressionType compression_type = (ECompressionType)block_header.compression;
|
||||
|
||||
if (!read_from_file(file, (void*)&encoding_type, sizeof(encoding_type)))
|
||||
return EResult::ReadError;
|
||||
if (encoding_type > metadata_encoding_types_count())
|
||||
// Found invalid metadata encoding type
|
||||
return EResult::InvalidMetadataEncodingType;
|
||||
|
||||
std::vector<uint8_t> data;
|
||||
const size_t data_size = (compression_type == ECompressionType::None) ? block_header.uncompressed_size : block_header.compressed_size;
|
||||
if (data_size > 0) {
|
||||
data.resize(data_size);
|
||||
if (!read_from_file(file, (void*)data.data(), data_size))
|
||||
return EResult::ReadError;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> uncompressed_data;
|
||||
if (compression_type != ECompressionType::None) {
|
||||
if (!uncompress(data, uncompressed_data, compression_type))
|
||||
return EResult::DataUncompressionError;
|
||||
}
|
||||
|
||||
if (!decode_metadata((compression_type == ECompressionType::None) ? data : uncompressed_data, raw_data, (EMetadataEncodingType)encoding_type))
|
||||
return EResult::MetadataDecodingError;
|
||||
|
||||
return EResult::Success;
|
||||
}
|
||||
|
||||
EResult FileMetadataBlock::write(FILE& file, ECompressionType compression_type, EChecksumType checksum_type) const
|
||||
{
|
||||
Checksum cs(checksum_type);
|
||||
|
||||
// write block header, payload
|
||||
EResult res = BaseMetadataBlock::write(file, EBlockType::FileMetadata, compression_type, cs);
|
||||
if (res != EResult::Success)
|
||||
// propagate error
|
||||
return res;
|
||||
|
||||
// write block checksum
|
||||
if (checksum_type != EChecksumType::None)
|
||||
return cs.write(file);
|
||||
|
||||
return EResult::Success;
|
||||
}
|
||||
|
||||
EResult FileMetadataBlock::read_data(FILE& file, const FileHeader& file_header, const BlockHeader& block_header)
|
||||
{
|
||||
// read block payload
|
||||
EResult res = BaseMetadataBlock::read_data(file, block_header);
|
||||
if (res != EResult::Success)
|
||||
// propagate error
|
||||
return res;
|
||||
|
||||
const EChecksumType checksum_type = (EChecksumType)file_header.checksum_type;
|
||||
if (checksum_type != EChecksumType::None) {
|
||||
// read block checksum
|
||||
Checksum cs(checksum_type);
|
||||
res = cs.read(file);
|
||||
if (res != EResult::Success)
|
||||
// propagate error
|
||||
return res;
|
||||
}
|
||||
|
||||
return EResult::Success;
|
||||
}
|
||||
|
||||
EResult ThumbnailBlock::write(FILE& file, EChecksumType checksum_type) const
|
||||
{
|
||||
if (format >= thumbnail_formats_count())
|
||||
return EResult::InvalidThumbnailFormat;
|
||||
if (width == 0)
|
||||
return EResult::InvalidThumbnailWidth;
|
||||
if (height == 0)
|
||||
return EResult::InvalidThumbnailHeight;
|
||||
if (data.size() == 0)
|
||||
return EResult::InvalidThumbnailDataSize;
|
||||
|
||||
// write block header
|
||||
const BlockHeader block_header = { (uint16_t)EBlockType::Thumbnail, (uint16_t)ECompressionType::None, (uint32_t)data.size() };
|
||||
EResult res = block_header.write(file);
|
||||
if (res != EResult::Success)
|
||||
// propagate error
|
||||
return res;
|
||||
|
||||
// write block payload
|
||||
if (!write_to_file(file, (const void*)&format, sizeof(format)))
|
||||
return EResult::WriteError;
|
||||
if (!write_to_file(file, (const void*)&width, sizeof(width)))
|
||||
return EResult::WriteError;
|
||||
if (!write_to_file(file, (const void*)&height, sizeof(height)))
|
||||
return EResult::WriteError;
|
||||
if (!write_to_file(file, (const void*)data.data(), data.size()))
|
||||
return EResult::WriteError;
|
||||
|
||||
if (checksum_type != EChecksumType::None) {
|
||||
Checksum cs(checksum_type);
|
||||
// update checksum with block header
|
||||
block_header.update_checksum(cs);
|
||||
// update checksum with block payload
|
||||
update_checksum(cs);
|
||||
// write block checksum
|
||||
res = cs.write(file);
|
||||
if (res != EResult::Success)
|
||||
// propagate error
|
||||
return res;
|
||||
}
|
||||
|
||||
return EResult::Success;
|
||||
}
|
||||
|
||||
EResult ThumbnailBlock::read_data(FILE& file, const FileHeader& file_header, const BlockHeader& block_header)
|
||||
{
|
||||
// read block payload
|
||||
if (!read_from_file(file, (void*)&format, sizeof(format)))
|
||||
return EResult::ReadError;
|
||||
if (format >= thumbnail_formats_count())
|
||||
return EResult::InvalidThumbnailFormat;
|
||||
if (!read_from_file(file, (void*)&width, sizeof(width)))
|
||||
return EResult::ReadError;
|
||||
if (width == 0)
|
||||
return EResult::InvalidThumbnailWidth;
|
||||
if (!read_from_file(file, (void*)&height, sizeof(height)))
|
||||
return EResult::ReadError;
|
||||
if (height == 0)
|
||||
return EResult::InvalidThumbnailHeight;
|
||||
if (block_header.uncompressed_size == 0)
|
||||
return EResult::InvalidThumbnailDataSize;
|
||||
data.resize(block_header.uncompressed_size);
|
||||
if (!read_from_file(file, (void*)data.data(), block_header.uncompressed_size))
|
||||
return EResult::ReadError;
|
||||
|
||||
const EChecksumType checksum_type = (EChecksumType)file_header.checksum_type;
|
||||
if (checksum_type != EChecksumType::None) {
|
||||
// read block checksum
|
||||
Checksum cs(checksum_type);
|
||||
const EResult res = cs.read(file);
|
||||
if (res != EResult::Success)
|
||||
// propagate error
|
||||
return res;
|
||||
}
|
||||
|
||||
return EResult::Success;
|
||||
}
|
||||
|
||||
void ThumbnailBlock::update_checksum(Checksum& checksum) const
|
||||
{
|
||||
checksum.append(encode((const void*)&format, sizeof(format)));
|
||||
checksum.append(encode((const void*)&width, sizeof(width)));
|
||||
checksum.append(encode((const void*)&height, sizeof(height)));
|
||||
checksum.append(data);
|
||||
}
|
||||
|
||||
EResult PrinterMetadataBlock::write(FILE& file, ECompressionType compression_type, EChecksumType checksum_type) const
|
||||
{
|
||||
Checksum cs(checksum_type);
|
||||
|
||||
// write block header, payload
|
||||
EResult res = BaseMetadataBlock::write(file, EBlockType::PrinterMetadata, compression_type, cs);
|
||||
if (res != EResult::Success)
|
||||
// propagate error
|
||||
return res;
|
||||
|
||||
// write block checksum
|
||||
if (checksum_type != EChecksumType::None)
|
||||
return cs.write(file);
|
||||
|
||||
return EResult::Success;
|
||||
}
|
||||
|
||||
EResult PrinterMetadataBlock::read_data(FILE& file, const FileHeader& file_header, const BlockHeader& block_header)
|
||||
{
|
||||
// read block payload
|
||||
EResult res = BaseMetadataBlock::read_data(file, block_header);
|
||||
if (res != EResult::Success)
|
||||
// propagate error
|
||||
return res;
|
||||
|
||||
const EChecksumType checksum_type = (EChecksumType)file_header.checksum_type;
|
||||
if (checksum_type != EChecksumType::None) {
|
||||
// read block checksum
|
||||
Checksum cs(checksum_type);
|
||||
res = cs.read(file);
|
||||
if (res != EResult::Success)
|
||||
// propagate error
|
||||
return res;
|
||||
}
|
||||
|
||||
return EResult::Success;
|
||||
}
|
||||
|
||||
EResult PrintMetadataBlock::write(FILE& file, ECompressionType compression_type, EChecksumType checksum_type) const
|
||||
{
|
||||
Checksum cs(checksum_type);
|
||||
|
||||
// write block header, payload
|
||||
EResult res = BaseMetadataBlock::write(file, EBlockType::PrintMetadata, compression_type, cs);
|
||||
if (res != EResult::Success)
|
||||
// propagate error
|
||||
return res;
|
||||
|
||||
// write block checksum
|
||||
if (checksum_type != EChecksumType::None)
|
||||
return cs.write(file);
|
||||
|
||||
return EResult::Success;
|
||||
}
|
||||
|
||||
EResult PrintMetadataBlock::read_data(FILE& file, const FileHeader& file_header, const BlockHeader& block_header)
|
||||
{
|
||||
// read block payload
|
||||
EResult res = BaseMetadataBlock::read_data(file, block_header);
|
||||
if (res != EResult::Success)
|
||||
// propagate error
|
||||
return res;
|
||||
|
||||
const EChecksumType checksum_type = (EChecksumType)file_header.checksum_type;
|
||||
if (checksum_type != EChecksumType::None) {
|
||||
// read block checksum
|
||||
Checksum cs(checksum_type);
|
||||
res = cs.read(file);
|
||||
if (res != EResult::Success)
|
||||
// propagate error
|
||||
return res;
|
||||
}
|
||||
|
||||
return EResult::Success;
|
||||
}
|
||||
|
||||
EResult SlicerMetadataBlock::write(FILE& file, ECompressionType compression_type, EChecksumType checksum_type) const
|
||||
{
|
||||
Checksum cs(checksum_type);
|
||||
|
||||
// write block header, payload
|
||||
EResult res = BaseMetadataBlock::write(file, EBlockType::SlicerMetadata, compression_type, cs);
|
||||
if (res != EResult::Success)
|
||||
// propagate error
|
||||
return res;
|
||||
|
||||
// write block checksum
|
||||
if (checksum_type != EChecksumType::None)
|
||||
return cs.write(file);
|
||||
|
||||
return EResult::Success;
|
||||
}
|
||||
|
||||
EResult SlicerMetadataBlock::read_data(FILE& file, const FileHeader& file_header, const BlockHeader& block_header)
|
||||
{
|
||||
// read block payload
|
||||
EResult res = BaseMetadataBlock::read_data(file, block_header);
|
||||
if (res != EResult::Success)
|
||||
// propagate error
|
||||
return res;
|
||||
|
||||
const EChecksumType checksum_type = (EChecksumType)file_header.checksum_type;
|
||||
if (checksum_type != EChecksumType::None) {
|
||||
// read block checksum
|
||||
Checksum cs(checksum_type);
|
||||
res = cs.read(file);
|
||||
if (res != EResult::Success)
|
||||
// propagate error
|
||||
return res;
|
||||
}
|
||||
|
||||
return EResult::Success;
|
||||
}
|
||||
|
||||
EResult GCodeBlock::write(FILE& file, ECompressionType compression_type, EChecksumType checksum_type) const
|
||||
{
|
||||
return EResult::Success;
|
||||
}
|
||||
|
||||
EResult GCodeBlock::read_data(FILE& file, const FileHeader& file_header, const BlockHeader& block_header)
|
||||
{
|
||||
return EResult::Success;
|
||||
}
|
||||
|
||||
#if ENABLE_CHECKSUM_BLOCK
|
||||
EResult ChecksumBlock::write(FILE& file) const
|
||||
{
|
||||
if (!data.empty()) {
|
||||
const BlockHeader block_header = { (uint16_t)EBlockType::Checksum, (uint16_t)ECompressionType::None, (uint32_t)data.size() };
|
||||
// write block header
|
||||
const EResult res = block_header.write(file);
|
||||
if (res != EResult::Success)
|
||||
// propagate error
|
||||
return res;
|
||||
// write block payload
|
||||
if (!write_to_file(file, (const void*)data.data(), data.size()))
|
||||
return EResult::WriteError;
|
||||
}
|
||||
|
||||
return EResult::Success;
|
||||
}
|
||||
|
||||
EResult ChecksumBlock::read_data(FILE& file, const BlockHeader& block_header)
|
||||
{
|
||||
if (block_header.uncompressed_size > 0) {
|
||||
data.resize(block_header.uncompressed_size);
|
||||
if (!read_from_file(file, (void*)data.data(), block_header.uncompressed_size))
|
||||
return EResult::ReadError;
|
||||
}
|
||||
else
|
||||
data.clear();
|
||||
|
||||
return EResult::Success;
|
||||
}
|
||||
#endif // ENABLE_CHECKSUM_BLOCK
|
||||
|
||||
EResult Binarizer::initialize(FILE& file, EChecksumType checksum_type)
|
||||
{
|
||||
if (!m_enabled)
|
||||
return EResult::Success;
|
||||
|
||||
// initialize checksum
|
||||
m_checksum_type = checksum_type;
|
||||
#if ENABLE_CHECKSUM_BLOCK
|
||||
m_checksum = ChecksumBlock();
|
||||
#endif // ENABLE_CHECKSUM_BLOCK
|
||||
|
||||
// save header
|
||||
FileHeader file_header;
|
||||
file_header.checksum_type = (uint16_t)m_checksum_type;
|
||||
EResult res = file_header.write(file);
|
||||
if (res != EResult::Success)
|
||||
return res;
|
||||
|
||||
// save file metadata block
|
||||
res = m_binary_data.file_metadata.write(file, ECompressionType::None, m_checksum_type);
|
||||
if (res != EResult::Success)
|
||||
return res;
|
||||
|
||||
// save printer metadata block
|
||||
res = m_binary_data.printer_metadata.write(file, ECompressionType::None, m_checksum_type);
|
||||
if (res != EResult::Success)
|
||||
return res;
|
||||
|
||||
// save thumbnail blocks
|
||||
for (const ThumbnailBlock& block : m_binary_data.thumbnails) {
|
||||
res = block.write(file, m_checksum_type);
|
||||
if (res != EResult::Success)
|
||||
return res;
|
||||
}
|
||||
|
||||
// save slicer metadata block
|
||||
res = m_binary_data.slicer_metadata.write(file, ECompressionType::None, m_checksum_type);
|
||||
if (res != EResult::Success)
|
||||
return res;
|
||||
|
||||
// save gcode block
|
||||
|
||||
return EResult::Success;
|
||||
}
|
||||
|
||||
EResult Binarizer::finalize(FILE& file)
|
||||
{
|
||||
if (!m_enabled)
|
||||
return EResult::Success;
|
||||
|
||||
// save print metadata block
|
||||
EResult res = m_binary_data.print_metadata.write(file, ECompressionType::None, m_checksum_type);
|
||||
if (res != EResult::Success)
|
||||
return res;
|
||||
|
||||
#if ENABLE_CHECKSUM_BLOCK
|
||||
if (m_checksum_type != EChecksumType::None) {
|
||||
// save checksum
|
||||
// dummy checksum until it is not properly implemented
|
||||
switch (m_checksum_type)
|
||||
{
|
||||
case EChecksumType::CRC32:
|
||||
case EChecksumType::MD5:
|
||||
{
|
||||
m_checksum.data.clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
res = m_checksum.write(file);
|
||||
if (res != EResult::Success)
|
||||
return res;
|
||||
}
|
||||
#endif // ENABLE_CHECKSUM_BLOCK
|
||||
|
||||
return EResult::Success;
|
||||
}
|
||||
|
||||
bool is_valid_binary_gcode(FILE& file)
|
||||
{
|
||||
// cache file position
|
||||
const long curr_pos = ftell(&file);
|
||||
rewind(&file);
|
||||
|
||||
std::array<uint8_t, 4> magic;
|
||||
fread((void*)magic.data(), 1, magic.size(), &file);
|
||||
if (ferror(&file))
|
||||
return false;
|
||||
else {
|
||||
// restore file position
|
||||
fseek(&file, curr_pos, SEEK_SET);
|
||||
return magic == MAGIC;
|
||||
}
|
||||
}
|
||||
|
||||
EResult read_header(FILE& file, FileHeader& header, const uint32_t* const max_version)
|
||||
{
|
||||
rewind(&file);
|
||||
return header.read(file, max_version);
|
||||
}
|
||||
|
||||
static EResult checksums_match(FILE& file, const FileHeader& file_header, const BlockHeader& block_header)
|
||||
{
|
||||
// cache file position
|
||||
const long curr_pos = ftell(&file);
|
||||
|
||||
Checksum curr_cs((EChecksumType)file_header.checksum_type);
|
||||
// update block checksum block header
|
||||
block_header.update_checksum(curr_cs);
|
||||
|
||||
// read block payload
|
||||
size_t remaining_payload_size = block_payload_size(block_header);
|
||||
while (remaining_payload_size > 0) {
|
||||
const size_t size_to_read = std::min(remaining_payload_size, g_checksum_max_cache_size);
|
||||
std::vector<uint8_t> payload(size_to_read);
|
||||
if (!read_from_file(file, payload.data(), payload.size()))
|
||||
return EResult::ReadError;
|
||||
curr_cs.append(payload);
|
||||
remaining_payload_size -= size_to_read;
|
||||
}
|
||||
|
||||
// read checksum
|
||||
Checksum read_cs((EChecksumType)file_header.checksum_type);
|
||||
EResult res = read_cs.read(file);
|
||||
if (res != EResult::Success)
|
||||
// propagate error
|
||||
return res;
|
||||
|
||||
// Verify checksum
|
||||
if (!curr_cs.matches(read_cs))
|
||||
return EResult::InvalidChecksum;
|
||||
|
||||
// restore file position
|
||||
fseek(&file, curr_pos, SEEK_SET);
|
||||
|
||||
return EResult::Success;
|
||||
}
|
||||
|
||||
EResult read_next_block_header(FILE& file, const FileHeader& file_header, BlockHeader& block_header, bool verify_checksum)
|
||||
{
|
||||
if (verify_checksum && (EChecksumType)file_header.checksum_type != EChecksumType::None) {
|
||||
const EResult res = block_header.read(file);
|
||||
if (res != EResult::Success)
|
||||
// propagate error
|
||||
return res;
|
||||
|
||||
return checksums_match(file, file_header, block_header);
|
||||
}
|
||||
else
|
||||
return block_header.read(file);
|
||||
}
|
||||
|
||||
EResult read_next_block_header(FILE& file, const FileHeader& file_header, BlockHeader& block_header, EBlockType type, bool verify_checksum)
|
||||
{
|
||||
// cache file position
|
||||
const long curr_pos = ftell(&file);
|
||||
|
||||
do {
|
||||
EResult res = read_next_block_header(file, file_header, block_header, false);
|
||||
if (res != EResult::Success)
|
||||
// propagate error
|
||||
return res;
|
||||
else if (feof(&file)) {
|
||||
// block not found
|
||||
// restore file position
|
||||
fseek(&file, curr_pos, SEEK_SET);
|
||||
return EResult::BlockNotFound;
|
||||
}
|
||||
else if ((EBlockType)block_header.type == type) {
|
||||
// block found
|
||||
if (verify_checksum) {
|
||||
res = checksums_match(file, file_header, block_header);
|
||||
if (res != EResult::Success)
|
||||
// propagate error
|
||||
return res;
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!feof(&file)) {
|
||||
res = skip_block_content(file, file_header, block_header);
|
||||
if (res != EResult::Success)
|
||||
// propagate error
|
||||
return res;
|
||||
}
|
||||
} while (true);
|
||||
|
||||
return EResult::Success;
|
||||
}
|
||||
|
||||
EResult skip_block_payload(FILE& file, const BlockHeader& block_header)
|
||||
{
|
||||
fseek(&file, (long)block_payload_size(block_header), SEEK_CUR);
|
||||
return ferror(&file) ? EResult::ReadError : EResult::Success;
|
||||
}
|
||||
|
||||
EResult skip_block_content(FILE& file, const FileHeader& file_header, const BlockHeader& block_header)
|
||||
{
|
||||
fseek(&file, (long)block_content_size(file_header, block_header), SEEK_CUR);
|
||||
return ferror(&file) ? EResult::ReadError : EResult::Success;
|
||||
}
|
||||
|
||||
size_t block_parameters_size(EBlockType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case EBlockType::FileMetadata: { return FileMetadataBlock::get_parameters_size(); }
|
||||
case EBlockType::GCode: { return GCodeBlock::get_parameters_size(); }
|
||||
case EBlockType::SlicerMetadata: { return SlicerMetadataBlock::get_parameters_size(); }
|
||||
case EBlockType::PrinterMetadata: { return PrinterMetadataBlock::get_parameters_size(); }
|
||||
case EBlockType::PrintMetadata: { return PrintMetadataBlock::get_parameters_size(); }
|
||||
case EBlockType::Thumbnail: { return ThumbnailBlock::get_parameters_size(); }
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t block_payload_size(const BlockHeader& block_header)
|
||||
{
|
||||
size_t ret = block_parameters_size((EBlockType)block_header.type);
|
||||
ret += ((ECompressionType)block_header.compression == ECompressionType::None) ?
|
||||
block_header.uncompressed_size : block_header.compressed_size;
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t checksum_size(EChecksumType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case EChecksumType::None: { return 0; }
|
||||
case EChecksumType::CRC32: { return 4; }
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern size_t block_content_size(const FileHeader& file_header, const BlockHeader& block_header)
|
||||
{
|
||||
#if ENABLE_CHECKSUM_BLOCK
|
||||
return ((EBlockType)block_header.type == EBlockType::Checksum) ?
|
||||
block_payload_size(block_header) : block_payload_size(block_header) + checksum_size((EChecksumType)file_header.checksum_type);
|
||||
#else
|
||||
return block_payload_size(block_header) + checksum_size((EChecksumType)file_header.checksum_type);
|
||||
#endif // ENABLE_CHECKSUM_BLOCK
|
||||
}
|
||||
|
||||
} // namespace BinaryGCode
|
||||
|
330
src/libslic3r/GCode/GCodeBinarizer.hpp
Normal file
330
src/libslic3r/GCode/GCodeBinarizer.hpp
Normal file
@ -0,0 +1,330 @@
|
||||
#ifndef slic3r_GCode_GCodeBinarizer_hpp_
|
||||
#define slic3r_GCode_GCodeBinarizer_hpp_
|
||||
|
||||
#ifdef _WIN32
|
||||
#define ENABLE_BINARIZED_GCODE_DEBUG 1
|
||||
#endif // _WIN32
|
||||
|
||||
#define ENABLE_CHECKSUM_BLOCK 0
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cstdio>
|
||||
#include <functional>
|
||||
|
||||
namespace BinaryGCode {
|
||||
|
||||
static const std::array<uint8_t, 4> MAGIC{ 'G', 'C', 'D', 'E' };
|
||||
static const uint32_t VERSION = 1;
|
||||
|
||||
enum class EResult : uint16_t
|
||||
{
|
||||
Success,
|
||||
ReadError,
|
||||
WriteError,
|
||||
InvalidMagicNumber,
|
||||
InvalidVersionNumber,
|
||||
InvalidChecksumType,
|
||||
InvalidBlockType,
|
||||
InvalidCompressionType,
|
||||
InvalidMetadataEncodingType,
|
||||
DataCompressionError,
|
||||
DataUncompressionError,
|
||||
MetadataEncodingError,
|
||||
MetadataDecodingError,
|
||||
BlockNotFound,
|
||||
InvalidChecksum,
|
||||
InvalidThumbnailFormat,
|
||||
InvalidThumbnailWidth,
|
||||
InvalidThumbnailHeight,
|
||||
InvalidThumbnailDataSize
|
||||
};
|
||||
|
||||
// Returns a string description of the given result
|
||||
extern std::string translate_result(BinaryGCode::EResult result);
|
||||
|
||||
enum class EChecksumType : uint16_t
|
||||
{
|
||||
None,
|
||||
CRC32
|
||||
};
|
||||
|
||||
class Checksum
|
||||
{
|
||||
public:
|
||||
// Constructs a checksum of the given type.
|
||||
// The checksum data are sized accordingly.
|
||||
explicit Checksum(EChecksumType type);
|
||||
|
||||
EChecksumType get_type() const;
|
||||
|
||||
// Appends the given data to the cache and performs a checksum update if
|
||||
// the size of the cache exceeds the max checksum cache size.
|
||||
void append(const std::vector<uint8_t>& data);
|
||||
// Returns true if the given checksum is equal to this one
|
||||
bool matches(Checksum& other);
|
||||
|
||||
EResult write(FILE& file);
|
||||
EResult read(FILE& file);
|
||||
|
||||
private:
|
||||
EChecksumType m_type;
|
||||
std::vector<uint8_t> m_cache;
|
||||
std::vector<uint8_t> m_checksum;
|
||||
|
||||
void update();
|
||||
};
|
||||
|
||||
struct FileHeader
|
||||
{
|
||||
uint32_t magic{ *(uint32_t*)(MAGIC.data()) };
|
||||
uint32_t version{ VERSION };
|
||||
uint16_t checksum_type{ (uint16_t)EChecksumType::None };
|
||||
|
||||
EResult write(FILE& file) const;
|
||||
EResult read(FILE& file, const uint32_t* const max_version);
|
||||
};
|
||||
|
||||
enum class EBlockType : uint16_t
|
||||
{
|
||||
#if ENABLE_CHECKSUM_BLOCK
|
||||
Checksum,
|
||||
#endif // ENABLE_CHECKSUM_BLOCK
|
||||
FileMetadata,
|
||||
GCode,
|
||||
SlicerMetadata,
|
||||
PrinterMetadata,
|
||||
PrintMetadata,
|
||||
Thumbnail
|
||||
};
|
||||
|
||||
enum class ECompressionType : uint16_t
|
||||
{
|
||||
None,
|
||||
};
|
||||
|
||||
struct BlockHeader
|
||||
{
|
||||
uint16_t type{ 0 };
|
||||
uint16_t compression{ 0 };
|
||||
uint32_t uncompressed_size{ 0 };
|
||||
uint32_t compressed_size{ 0 };
|
||||
|
||||
// Updates the given checksum with the data of this BlockHeader
|
||||
void update_checksum(Checksum& checksum) const;
|
||||
|
||||
EResult write(FILE& file) const;
|
||||
EResult read(FILE& file);
|
||||
};
|
||||
|
||||
enum class EMetadataEncodingType : uint16_t
|
||||
{
|
||||
INI,
|
||||
};
|
||||
|
||||
struct BaseMetadataBlock
|
||||
{
|
||||
// type of data encoding
|
||||
uint16_t encoding_type{ 0 };
|
||||
// data in key/value form
|
||||
std::vector<std::pair<std::string, std::string>> raw_data;
|
||||
|
||||
// write block header and data in encoded format
|
||||
EResult write(FILE& file, EBlockType block_type, ECompressionType compression_type, Checksum& checksum) const;
|
||||
// read block data in encoded format
|
||||
EResult read_data(FILE& file, const BlockHeader& block_header);
|
||||
|
||||
static size_t get_parameters_size() { return sizeof(encoding_type); }
|
||||
};
|
||||
|
||||
struct FileMetadataBlock : public BaseMetadataBlock
|
||||
{
|
||||
// write block header and data
|
||||
EResult write(FILE& file, ECompressionType compression_type, EChecksumType checksum_type) const;
|
||||
// read block data
|
||||
EResult read_data(FILE& file, const FileHeader& file_header, const BlockHeader& block_header);
|
||||
};
|
||||
|
||||
enum class EThumbnailFormat : uint16_t
|
||||
{
|
||||
PNG,
|
||||
JPG,
|
||||
QOI
|
||||
};
|
||||
|
||||
struct ThumbnailBlock
|
||||
{
|
||||
uint16_t format{ 0 };
|
||||
uint16_t width{ 0 };
|
||||
uint16_t height{ 0 };
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
// write block header and data
|
||||
EResult write(FILE& file, EChecksumType checksum_type) const;
|
||||
// read block data
|
||||
EResult read_data(FILE& file, const FileHeader& file_header, const BlockHeader& block_header);
|
||||
|
||||
static size_t get_parameters_size() { return sizeof(format) + sizeof(width) + sizeof(height); }
|
||||
|
||||
private:
|
||||
void update_checksum(Checksum& checksum) const;
|
||||
};
|
||||
|
||||
struct PrinterMetadataBlock : public BaseMetadataBlock
|
||||
{
|
||||
// write block header and data
|
||||
EResult write(FILE& file, ECompressionType compression_type, EChecksumType checksum_type) const;
|
||||
// read block data
|
||||
EResult read_data(FILE& file, const FileHeader& file_header, const BlockHeader& block_header);
|
||||
};
|
||||
|
||||
struct PrintMetadataBlock : public BaseMetadataBlock
|
||||
{
|
||||
// write block header and data
|
||||
EResult write(FILE& file, ECompressionType compression_type, EChecksumType checksum_type) const;
|
||||
// read block data
|
||||
EResult read_data(FILE& file, const FileHeader& file_header, const BlockHeader& block_header);
|
||||
};
|
||||
|
||||
struct SlicerMetadataBlock : public BaseMetadataBlock
|
||||
{
|
||||
// write block header and data
|
||||
EResult write(FILE& file, ECompressionType compression_type, EChecksumType checksum_type) const;
|
||||
// read block data
|
||||
EResult read_data(FILE& file, const FileHeader& file_header, const BlockHeader& block_header);
|
||||
};
|
||||
|
||||
struct GCodeBlock : public BaseMetadataBlock
|
||||
{
|
||||
// write block header and data
|
||||
EResult write(FILE& file, ECompressionType compression_type, EChecksumType checksum_type) const;
|
||||
// read block data
|
||||
EResult read_data(FILE& file, const FileHeader& file_header, const BlockHeader& block_header);
|
||||
};
|
||||
|
||||
#if ENABLE_CHECKSUM_BLOCK
|
||||
struct ChecksumBlock
|
||||
{
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
// write block header and data
|
||||
EResult write(FILE& file) const;
|
||||
// read block data
|
||||
EResult read_data(FILE& file, const BlockHeader& block_header);
|
||||
};
|
||||
#endif // ENABLE_CHECKSUM_BLOCK
|
||||
|
||||
//=====================================================================================================================================
|
||||
//
|
||||
// PRUSASLICER INTERFACE
|
||||
//
|
||||
//=====================================================================================================================================
|
||||
|
||||
struct BinaryData
|
||||
{
|
||||
FileMetadataBlock file_metadata;
|
||||
PrinterMetadataBlock printer_metadata;
|
||||
std::vector<ThumbnailBlock> thumbnails;
|
||||
SlicerMetadataBlock slicer_metadata;
|
||||
PrintMetadataBlock print_metadata;
|
||||
|
||||
void reset() {
|
||||
file_metadata.raw_data.clear();
|
||||
printer_metadata.raw_data.clear();
|
||||
thumbnails.clear();
|
||||
slicer_metadata.raw_data.clear();
|
||||
print_metadata.raw_data.clear();
|
||||
}
|
||||
};
|
||||
|
||||
class Binarizer
|
||||
{
|
||||
public:
|
||||
bool is_enabled() const { return m_enabled; }
|
||||
void set_enabled(bool enable) { m_enabled = enable; }
|
||||
void set_compression_type(ECompressionType type) { m_compression_type = type; }
|
||||
|
||||
BinaryData& get_binary_data() { return m_binary_data; }
|
||||
const BinaryData& get_binary_data() const { return m_binary_data; }
|
||||
|
||||
EResult initialize(FILE& file, EChecksumType checksum_type);
|
||||
EResult finalize(FILE& file);
|
||||
|
||||
private:
|
||||
bool m_enabled{ false };
|
||||
|
||||
EChecksumType m_checksum_type{ EChecksumType::None };
|
||||
ECompressionType m_compression_type{ ECompressionType::None };
|
||||
|
||||
BinaryData m_binary_data;
|
||||
#if ENABLE_CHECKSUM_BLOCK
|
||||
ChecksumBlock m_checksum;
|
||||
#endif // ENABLE_CHECKSUM_BLOCK
|
||||
};
|
||||
|
||||
//=====================================================================================================================================
|
||||
//
|
||||
// FIRMWARE INTERFACE
|
||||
//
|
||||
//=====================================================================================================================================
|
||||
|
||||
// Get the max size of the cache used to calculate checksums, in bytes
|
||||
size_t get_checksum_max_cache_size();
|
||||
// Set the max size of the cache used to calculate checksums, in bytes
|
||||
void set_checksum_max_cache_size(size_t size);
|
||||
|
||||
// Returns true if the given file is a valid binary gcode
|
||||
// Does not modify the file position
|
||||
extern bool is_valid_binary_gcode(FILE& file);
|
||||
|
||||
// Reads the file header.
|
||||
// If max_version is not null, version is checked against the passed value
|
||||
// If return == EResult::Success:
|
||||
// - header will contain the file header
|
||||
// - file position will be set at the start of the 1st block header
|
||||
extern EResult read_header(FILE& file, FileHeader& header, const uint32_t* const max_version);
|
||||
|
||||
// Reads next block header from the current file position.
|
||||
// File position must be at the start of a block header.
|
||||
// If return == EResult::Success:
|
||||
// - block_header will contain the header of the block
|
||||
// - file position will be set at the start of the block parameters data
|
||||
extern EResult read_next_block_header(FILE& file, const FileHeader& file_header, BlockHeader& block_header, bool verify_checksum);
|
||||
|
||||
// Searches and reads next block header with the given type from the current file position.
|
||||
// File position must be at the start of a block header.
|
||||
// If return == EResult::Success:
|
||||
// - block_header will contain the header of the block with the required type
|
||||
// - file position will be set at the start of the block parameters data
|
||||
// otherwise:
|
||||
// - file position will keep the current value
|
||||
extern EResult read_next_block_header(FILE& file, const FileHeader& file_header, BlockHeader& block_header, EBlockType type, bool verify_checksum);
|
||||
|
||||
// Skips the payload (parameters + data) of the block with the given block header.
|
||||
// File position must be at the start of the block parameters.
|
||||
// If return == EResult::Success:
|
||||
// - file position will be set at the start of the block checksum, if present, or of next block header
|
||||
extern EResult skip_block_payload(FILE& file, const BlockHeader& block_header);
|
||||
|
||||
// Skips the content (parameters + data + checksum) of the block with the given block header.
|
||||
// File position must be at the start of the block parameters.
|
||||
// If return == EResult::Success:
|
||||
// - file position will be set at the start of the next block header
|
||||
extern EResult skip_block_content(FILE& file, const FileHeader& file_header, const BlockHeader& block_header);
|
||||
|
||||
// Returns the size of the parameters of the given block type, in bytes.
|
||||
extern size_t block_parameters_size(EBlockType type);
|
||||
|
||||
// Returns the size of the payload (parameters + data) of the block with the given header, in bytes.
|
||||
extern size_t block_payload_size(const BlockHeader& block_header);
|
||||
|
||||
// Returns the size of the checksum of the given type, in bytes.
|
||||
extern size_t checksum_size(EChecksumType type);
|
||||
|
||||
// Returns the size of the content (parameters + data + checksum) of the block with the given header, in bytes.
|
||||
extern size_t block_content_size(const FileHeader& file_header, const BlockHeader& block_header);
|
||||
|
||||
} // namespace BinaryGCode
|
||||
|
||||
#endif // slic3r_GCode_GCodeBinarizer_hpp_
|
@ -25,6 +25,10 @@
|
||||
#endif
|
||||
|
||||
#include <chrono>
|
||||
#if ENABLE_BINARIZED_GCODE_DEBUG
|
||||
#include <windows.h>
|
||||
#include <debugapi.h>
|
||||
#endif // ENABLE_BINARIZED_GCODE_DEBUG
|
||||
|
||||
static const float DEFAULT_TOOLPATH_WIDTH = 0.4f;
|
||||
static const float DEFAULT_TOOLPATH_HEIGHT = 0.2f;
|
||||
@ -554,6 +558,10 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
|
||||
{
|
||||
m_parser.apply_config(config);
|
||||
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
m_binarizer.set_enabled(config.gcode_binary);
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
|
||||
m_producer = EProducer::PrusaSlicer;
|
||||
m_flavor = config.gcode_flavor;
|
||||
|
||||
@ -1023,6 +1031,23 @@ static inline const char* remove_eols(const char *begin, const char *end) {
|
||||
// Load a G-code into a stand-alone G-code viewer.
|
||||
// throws CanceledException through print->throw_if_canceled() (sent by the caller as callback).
|
||||
void GCodeProcessor::process_file(const std::string& filename, std::function<void()> cancel_callback)
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
{
|
||||
FILE* file = boost::nowide::fopen(filename.c_str(), "rb");
|
||||
if (file == nullptr)
|
||||
throw Slic3r::RuntimeError("Error opening the file: " + filename + "\n");
|
||||
|
||||
const bool is_binary = BinaryGCode::is_valid_binary_gcode(*file);
|
||||
fclose(file);
|
||||
|
||||
if (is_binary)
|
||||
process_binary_file(filename, cancel_callback);
|
||||
else
|
||||
process_ascii_file(filename, cancel_callback);
|
||||
}
|
||||
|
||||
void GCodeProcessor::process_ascii_file(const std::string& filename, std::function<void()> cancel_callback)
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
{
|
||||
CNumericLocalesSetter locales_setter;
|
||||
|
||||
@ -1090,6 +1115,163 @@ void GCodeProcessor::process_file(const std::string& filename, std::function<voi
|
||||
this->finalize(false);
|
||||
}
|
||||
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
void GCodeProcessor::process_binary_file(const std::string& filename, std::function<void()> cancel_callback)
|
||||
{
|
||||
class ScopedFile
|
||||
{
|
||||
public:
|
||||
explicit ScopedFile(FILE* file) : m_file(file) {}
|
||||
~ScopedFile() { if (m_file != nullptr) fclose(m_file); }
|
||||
private:
|
||||
FILE* m_file{ nullptr };
|
||||
};
|
||||
|
||||
FILE* file = boost::nowide::fopen(filename.c_str(), "rb");
|
||||
if (file == nullptr)
|
||||
throw Slic3r::RuntimeError("Unable to open file: " + filename + "\n");
|
||||
|
||||
ScopedFile scoped_file(file);
|
||||
|
||||
BinaryGCode::set_checksum_max_cache_size(1024);
|
||||
|
||||
// read file header
|
||||
BinaryGCode::FileHeader file_header;
|
||||
BinaryGCode::EResult res = BinaryGCode::read_header(*file, file_header, &BinaryGCode::VERSION);
|
||||
if (res != BinaryGCode::EResult::Success)
|
||||
throw Slic3r::RuntimeError("File: " + filename + "does not contain a valid binary gcode\n Error: " + BinaryGCode::translate_result(res) + "\n");
|
||||
|
||||
const long first_block_header_position = ftell(file);
|
||||
const bool verify_checksum = true;
|
||||
|
||||
// read file metadata block
|
||||
BinaryGCode::BlockHeader block_header;
|
||||
res = BinaryGCode::read_next_block_header(*file, file_header, block_header, verify_checksum);
|
||||
if (res != BinaryGCode::EResult::Success)
|
||||
throw Slic3r::RuntimeError("Error while reading file: " + filename + ": " + BinaryGCode::translate_result(res) + "\n");
|
||||
if ((BinaryGCode::EBlockType)block_header.type != BinaryGCode::EBlockType::FileMetadata)
|
||||
throw Slic3r::RuntimeError("Unable to find file metadata block in file: " + filename + "\n");
|
||||
BinaryGCode::FileMetadataBlock file_metadata_block;
|
||||
res = file_metadata_block.read_data(*file, file_header, block_header);
|
||||
if (res != BinaryGCode::EResult::Success)
|
||||
throw Slic3r::RuntimeError("Error while reading file: " + filename + ": " + BinaryGCode::translate_result(res) + "\n");
|
||||
auto producer_it = std::find_if(file_metadata_block.raw_data.begin(), file_metadata_block.raw_data.end(),
|
||||
[](const std::pair<std::string, std::string>& item) { return item.first == "Producer"; });
|
||||
if (producer_it != file_metadata_block.raw_data.end() && boost::starts_with(producer_it->second, std::string(SLIC3R_APP_NAME)))
|
||||
m_producer = EProducer::PrusaSlicer;
|
||||
else
|
||||
m_producer = EProducer::Unknown;
|
||||
|
||||
// read printer metadata block
|
||||
res = BinaryGCode::read_next_block_header(*file, file_header, block_header, verify_checksum);
|
||||
if (res != BinaryGCode::EResult::Success)
|
||||
throw Slic3r::RuntimeError("Error while reading file: " + filename + ": " + BinaryGCode::translate_result(res) + "\n");
|
||||
if ((BinaryGCode::EBlockType)block_header.type != BinaryGCode::EBlockType::PrinterMetadata)
|
||||
throw Slic3r::RuntimeError("Unable to find printer metadata block in file: " + filename + "\n");
|
||||
BinaryGCode::PrinterMetadataBlock printer_metadata_block;
|
||||
res = printer_metadata_block.read_data(*file, file_header, block_header);
|
||||
if (res != BinaryGCode::EResult::Success)
|
||||
throw Slic3r::RuntimeError("Error while reading file: " + filename + ": " + BinaryGCode::translate_result(res) + "\n");
|
||||
#if ENABLE_BINARIZED_GCODE_DEBUG
|
||||
OutputDebugStringA("Printer metadata:\n");
|
||||
for (const auto& [key, value] : printer_metadata_block.raw_data) {
|
||||
OutputDebugStringA(key.c_str());
|
||||
OutputDebugStringA("->");
|
||||
OutputDebugStringA(value.c_str());
|
||||
OutputDebugStringA("\n");
|
||||
}
|
||||
#endif // ENABLE_BINARIZED_GCODE_DEBUG
|
||||
|
||||
// read thumbnail blocks
|
||||
res = BinaryGCode::read_next_block_header(*file, file_header, block_header, verify_checksum);
|
||||
if (res != BinaryGCode::EResult::Success)
|
||||
throw Slic3r::RuntimeError("Error while reading file: " + filename + ": " + BinaryGCode::translate_result(res) + "\n");
|
||||
|
||||
while ((BinaryGCode::EBlockType)block_header.type == BinaryGCode::EBlockType::Thumbnail) {
|
||||
BinaryGCode::ThumbnailBlock thumbnail_block;
|
||||
res = thumbnail_block.read_data(*file, file_header, block_header);
|
||||
if (res != BinaryGCode::EResult::Success)
|
||||
throw Slic3r::RuntimeError("Error while reading file: " + filename + ": " + BinaryGCode::translate_result(res) + "\n");
|
||||
|
||||
#if ENABLE_BINARIZED_GCODE_DEBUG
|
||||
if (thumbnail_block.data.size() > 0) {
|
||||
auto format_filename = [](const std::string& stem, const BinaryGCode::ThumbnailBlock& block) {
|
||||
std::string ret = stem + "_" + std::to_string(block.width) + "x" + std::to_string(block.height);
|
||||
switch ((BinaryGCode::EThumbnailFormat)block.format)
|
||||
{
|
||||
case BinaryGCode::EThumbnailFormat::PNG: { ret += ".png"; break; }
|
||||
case BinaryGCode::EThumbnailFormat::JPG: { ret += ".jpg"; break; }
|
||||
case BinaryGCode::EThumbnailFormat::QOI: { ret += ".qoi"; break; }
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
const boost::filesystem::path path(filename);
|
||||
const std::string out_path = path.parent_path().string();
|
||||
const std::string out_filename = out_path + "\\" + format_filename(path.stem().string(), thumbnail_block);
|
||||
FILE* outfile = boost::nowide::fopen(out_filename.c_str(), "wb");
|
||||
if (outfile != nullptr) {
|
||||
fwrite((const void*)thumbnail_block.data.data(), 1, thumbnail_block.data.size(), outfile);
|
||||
fclose(outfile);
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_BINARIZED_GCODE_DEBUG
|
||||
|
||||
res = BinaryGCode::read_next_block_header(*file, file_header, block_header, verify_checksum);
|
||||
if (res != BinaryGCode::EResult::Success)
|
||||
throw Slic3r::RuntimeError("Error while reading file: " + filename + ": " + BinaryGCode::translate_result(res) + "\n");
|
||||
}
|
||||
|
||||
// read slicer metadata block
|
||||
if ((BinaryGCode::EBlockType)block_header.type != BinaryGCode::EBlockType::SlicerMetadata)
|
||||
throw Slic3r::RuntimeError("Unable to find slicer metadata block in file: " + filename + "\n");
|
||||
BinaryGCode::SlicerMetadataBlock slicer_metadata_block;
|
||||
res = slicer_metadata_block.read_data(*file, file_header, block_header);
|
||||
if (res != BinaryGCode::EResult::Success)
|
||||
throw Slic3r::RuntimeError("Error while reading file: " + filename + ": " + BinaryGCode::translate_result(res) + "\n");
|
||||
#if ENABLE_BINARIZED_GCODE_DEBUG
|
||||
OutputDebugStringA("Slicer metadata:\n");
|
||||
for (const auto& [key, value] : slicer_metadata_block.raw_data) {
|
||||
OutputDebugStringA(key.c_str());
|
||||
OutputDebugStringA("->");
|
||||
OutputDebugStringA(value.c_str());
|
||||
OutputDebugStringA("\n");
|
||||
}
|
||||
#endif // ENABLE_BINARIZED_GCODE_DEBUG
|
||||
DynamicPrintConfig config;
|
||||
config.apply(FullPrintConfig::defaults());
|
||||
std::string str;
|
||||
for (const auto& [key, value] : slicer_metadata_block.raw_data) {
|
||||
str += key + " = " + value + "\n";
|
||||
}
|
||||
// Silently substitute unknown values by new ones for loading configurations from PrusaSlicer's own G-code.
|
||||
// Showing substitution log or errors may make sense, but we are not really reading many values from the G-code config,
|
||||
// thus a probability of incorrect substitution is low and the G-code viewer is a consumer-only anyways.
|
||||
config.load_from_ini_string(str, ForwardCompatibilitySubstitutionRule::EnableSilent);
|
||||
apply_config(config);
|
||||
|
||||
// read print metadata block
|
||||
res = BinaryGCode::read_next_block_header(*file, file_header, block_header, verify_checksum);
|
||||
if (res != BinaryGCode::EResult::Success)
|
||||
throw Slic3r::RuntimeError("Error while reading file: " + filename + ": " + BinaryGCode::translate_result(res) + "\n");
|
||||
if ((BinaryGCode::EBlockType)block_header.type != BinaryGCode::EBlockType::PrintMetadata)
|
||||
throw Slic3r::RuntimeError("Unable to find print metadata block in file: " + filename + "\n");
|
||||
BinaryGCode::PrintMetadataBlock print_metadata_block;
|
||||
res = print_metadata_block.read_data(*file, file_header, block_header);
|
||||
if (res != BinaryGCode::EResult::Success)
|
||||
throw Slic3r::RuntimeError("Error while reading file: " + filename + ": " + BinaryGCode::translate_result(res) + "\n");
|
||||
#if ENABLE_BINARIZED_GCODE_DEBUG
|
||||
OutputDebugStringA("Print metadata:\n");
|
||||
for (const auto& [key, value] : print_metadata_block.raw_data) {
|
||||
OutputDebugStringA(key.c_str());
|
||||
OutputDebugStringA("->");
|
||||
OutputDebugStringA(value.c_str());
|
||||
OutputDebugStringA("\n");
|
||||
}
|
||||
#endif // ENABLE_BINARIZED_GCODE_DEBUG
|
||||
}
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
|
||||
void GCodeProcessor::initialize(const std::string& filename)
|
||||
{
|
||||
assert(is_decimal_separator_point());
|
||||
@ -3439,6 +3621,15 @@ void GCodeProcessor::post_process()
|
||||
throw Slic3r::RuntimeError(std::string("GCode processor post process export failed.\nCannot open file for writing.\n"));
|
||||
}
|
||||
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
if (m_binarizer.is_enabled()) {
|
||||
BinaryGCode::set_checksum_max_cache_size(4096);
|
||||
const BinaryGCode::EResult res = m_binarizer.initialize(*out.f, BinaryGCode::EChecksumType::CRC32);
|
||||
if (res != BinaryGCode::EResult::Success)
|
||||
throw Slic3r::RuntimeError(std::string("Unable to initialize the gcode binarizer.\n"));
|
||||
}
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
|
||||
auto time_in_minutes = [](float time_in_seconds) {
|
||||
assert(time_in_seconds >= 0.f);
|
||||
return int((time_in_seconds + 0.5f) / 60.0f);
|
||||
@ -3555,13 +3746,26 @@ void GCodeProcessor::post_process()
|
||||
size_t m_curr_g1_id{ 0 };
|
||||
size_t m_out_file_pos{ 0 };
|
||||
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
BinaryGCode::Binarizer& m_binarizer;
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
|
||||
public:
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
ExportLines(BinaryGCode::Binarizer& binarizer, EWriteType type, TimeMachine& machine)
|
||||
#ifndef NDEBUG
|
||||
: m_statistics(*this), m_binarizer(binarizer), m_write_type(type), m_machine(machine) {}
|
||||
#else
|
||||
: m_binarizer(binarizer), m_write_type(type), m_machine(machine) {}
|
||||
#endif // NDEBUG
|
||||
#else
|
||||
ExportLines(EWriteType type, TimeMachine& machine)
|
||||
#ifndef NDEBUG
|
||||
: m_statistics(*this), m_write_type(type), m_machine(machine) {}
|
||||
#else
|
||||
: m_write_type(type), m_machine(machine) {}
|
||||
#endif // NDEBUG
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
|
||||
void update(size_t lines_counter, size_t g1_lines_counter) {
|
||||
m_gcode_lines_map.push_back({ lines_counter, 0 });
|
||||
@ -3707,12 +3911,18 @@ void GCodeProcessor::post_process()
|
||||
private:
|
||||
void write_to_file(FilePtr& out, const std::string& out_string, GCodeProcessorResult& result, const std::string& out_path) {
|
||||
if (!out_string.empty()) {
|
||||
fwrite((const void*)out_string.c_str(), 1, out_string.length(), out.f);
|
||||
if (ferror(out.f)) {
|
||||
out.close();
|
||||
boost::nowide::remove(out_path.c_str());
|
||||
throw Slic3r::RuntimeError(std::string("GCode processor post process export failed.\nIs the disk full?\n"));
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
if (!m_binarizer.is_enabled()) {
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
fwrite((const void*)out_string.c_str(), 1, out_string.length(), out.f);
|
||||
if (ferror(out.f)) {
|
||||
out.close();
|
||||
boost::nowide::remove(out_path.c_str());
|
||||
throw Slic3r::RuntimeError(std::string("GCode processor post process export failed.\nIs the disk full?\n"));
|
||||
}
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
}
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
for (size_t i = 0; i < out_string.size(); ++i) {
|
||||
if (out_string[i] == '\n')
|
||||
result.lines_ends.emplace_back(m_out_file_pos + i + 1);
|
||||
@ -3722,7 +3932,11 @@ void GCodeProcessor::post_process()
|
||||
}
|
||||
};
|
||||
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
ExportLines export_lines(m_binarizer, m_result.backtrace_enabled ? ExportLines::EWriteType::ByTime : ExportLines::EWriteType::BySize, m_time_processor.machines[0]);
|
||||
#else
|
||||
ExportLines export_lines(m_result.backtrace_enabled ? ExportLines::EWriteType::ByTime : ExportLines::EWriteType::BySize, m_time_processor.machines[0]);
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
|
||||
// replace placeholder lines with the proper final value
|
||||
// gcode_line is in/out parameter, to reduce expensive memory allocation
|
||||
@ -4035,6 +4249,46 @@ void GCodeProcessor::post_process()
|
||||
|
||||
export_lines.flush(out, m_result, out_path);
|
||||
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
if (m_binarizer.is_enabled()) {
|
||||
// update print metadata
|
||||
auto update_value = [](std::string& value, const std::vector<double>& values) {
|
||||
char buf[1024];
|
||||
value.clear();
|
||||
for (size_t i = 0; i < values.size(); ++i) {
|
||||
sprintf(buf, i == values.size() - 1 ? " %.2lf" : " %.2lf,", values[i]);
|
||||
value += buf;
|
||||
}
|
||||
};
|
||||
|
||||
// update binary data
|
||||
BinaryGCode::BinaryData& binary_data = m_binarizer.get_binary_data();
|
||||
for (auto& [key, value] : binary_data.print_metadata.raw_data) {
|
||||
if (key == "filament used [mm]") update_value(value, filament_mm);
|
||||
else if (key == "filament used [g]") update_value(value, filament_g);
|
||||
else if (key == "total filament used [g]") update_value(value, { filament_total_g });
|
||||
else if (key == "filament used [cm3]") update_value(value, filament_cm3);
|
||||
else if (key == "filament cost") update_value(value, filament_cost);
|
||||
else if (key == "total filament cost") update_value(value, { filament_total_cost });
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count); ++i) {
|
||||
const TimeMachine& machine = m_time_processor.machines[i];
|
||||
PrintEstimatedStatistics::ETimeMode mode = static_cast<PrintEstimatedStatistics::ETimeMode>(i);
|
||||
if (mode == PrintEstimatedStatistics::ETimeMode::Normal || machine.enabled) {
|
||||
char buf[128];
|
||||
sprintf(buf, "(%s mode)", (mode == PrintEstimatedStatistics::ETimeMode::Normal) ? "normal" : "silent");
|
||||
binary_data.print_metadata.raw_data.push_back({ "estimated printing time " + std::string(buf), get_time_dhms(machine.time) });
|
||||
binary_data.print_metadata.raw_data.push_back({ "estimated first layer printing time " + std::string(buf), get_time_dhms(machine.layers_time.empty() ? 0.f : machine.layers_time.front()) });
|
||||
}
|
||||
}
|
||||
|
||||
const BinaryGCode::EResult res = m_binarizer.finalize(*out.f);
|
||||
if (res != BinaryGCode::EResult::Success)
|
||||
throw Slic3r::RuntimeError(std::string("Error while finalizing the gcode binarizer.\n"));
|
||||
}
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
|
||||
out.close();
|
||||
in.close();
|
||||
|
||||
|
@ -7,6 +7,10 @@
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/CustomGCode.hpp"
|
||||
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
#include "GCodeBinarizer.hpp"
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
@ -525,6 +529,9 @@ namespace Slic3r {
|
||||
|
||||
private:
|
||||
GCodeReader m_parser;
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
BinaryGCode::Binarizer m_binarizer;
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
|
||||
EUnits m_units;
|
||||
EPositioningType m_global_positioning_type;
|
||||
@ -622,6 +629,10 @@ namespace Slic3r {
|
||||
|
||||
void apply_config(const PrintConfig& config);
|
||||
void set_print(Print* print) { m_print = print; }
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
BinaryGCode::BinaryData& get_binary_data() { return m_binarizer.get_binary_data(); }
|
||||
const BinaryGCode::BinaryData& get_binary_data() const { return m_binarizer.get_binary_data(); }
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
|
||||
void enable_stealth_time_estimator(bool enabled);
|
||||
bool is_stealth_time_estimator_enabled() const {
|
||||
@ -664,6 +675,11 @@ namespace Slic3r {
|
||||
void apply_config_kissslicer(const std::string& filename);
|
||||
void process_gcode_line(const GCodeReader::GCodeLine& line, bool producers_enabled);
|
||||
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
void process_ascii_file(const std::string& filename, std::function<void()> cancel_callback = nullptr);
|
||||
void process_binary_file(const std::string& filename, std::function<void()> cancel_callback = nullptr);
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
|
||||
// Process tags embedded into comments
|
||||
void process_tags(const std::string_view comment, bool producers_enabled);
|
||||
bool process_producers_tags(const std::string_view comment);
|
||||
|
@ -4,6 +4,9 @@
|
||||
#include "../Point.hpp"
|
||||
#include "../PrintConfig.hpp"
|
||||
#include "ThumbnailData.hpp"
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
#include "GCode/GCodeBinarizer.hpp"
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
@ -55,6 +58,35 @@ inline void export_thumbnails_to_file(ThumbnailsGeneratorCallback &thumbnail_cb,
|
||||
}
|
||||
}
|
||||
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
template<typename ThrowIfCanceledCallback>
|
||||
inline void generate_binary_thumbnails(ThumbnailsGeneratorCallback& thumbnail_cb, std::vector<BinaryGCode::ThumbnailBlock>& out_thumbnails,
|
||||
const std::vector<Vec2d>& sizes, GCodeThumbnailsFormat format, ThrowIfCanceledCallback throw_if_canceled)
|
||||
{
|
||||
out_thumbnails.clear();
|
||||
if (thumbnail_cb != nullptr) {
|
||||
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 != nullptr && compressed->size > 0) {
|
||||
BinaryGCode::ThumbnailBlock& block = out_thumbnails.emplace_back(BinaryGCode::ThumbnailBlock());
|
||||
block.width = (uint16_t)data.width;
|
||||
block.height = (uint16_t)data.height;
|
||||
switch (format) {
|
||||
case GCodeThumbnailsFormat::PNG: { block.format = (uint16_t)BinaryGCode::EThumbnailFormat::PNG; break; }
|
||||
case GCodeThumbnailsFormat::JPG: { block.format = (uint16_t)BinaryGCode::EThumbnailFormat::JPG; break; }
|
||||
case GCodeThumbnailsFormat::QOI: { block.format = (uint16_t)BinaryGCode::EThumbnailFormat::QOI; break; }
|
||||
}
|
||||
block.data.resize(compressed->size);
|
||||
memcpy(block.data.data(), compressed->data, compressed->size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
|
||||
} // namespace Slic3r::GCodeThumbnails
|
||||
|
||||
#endif // slic3r_GCodeThumbnails_hpp_
|
||||
|
@ -449,7 +449,11 @@ static std::vector<std::string> s_Preset_print_options {
|
||||
"support_tree_angle", "support_tree_angle_slow", "support_tree_branch_diameter", "support_tree_branch_diameter_angle", "support_tree_branch_diameter_double_wall",
|
||||
"support_tree_top_rate", "support_tree_branch_distance", "support_tree_tip_diameter",
|
||||
"dont_support_bridges", "thick_bridges", "notes", "complete_objects", "extruder_clearance_radius",
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
"extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "gcode_substitutions", "gcode_binary", "perimeter_extruder",
|
||||
#else
|
||||
"extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "gcode_substitutions", "perimeter_extruder",
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
"infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder",
|
||||
"ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width",
|
||||
"perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width",
|
||||
|
@ -117,6 +117,9 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
||||
"perimeter_acceleration",
|
||||
"post_process",
|
||||
"gcode_substitutions",
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
"gcode_binary",
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
"printer_notes",
|
||||
"retract_before_travel",
|
||||
"retract_before_wipe",
|
||||
|
@ -1431,6 +1431,14 @@ void PrintConfigDef::init_fff_params()
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionStrings());
|
||||
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
def = this->add("gcode_binary", coBool);
|
||||
def->label = L("Export as binary G-code");
|
||||
def->tooltip = L("Exports the G-code in binary format.");
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionBool(0));
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
|
||||
def = this->add("high_current_on_filament_swap", coBool);
|
||||
def->label = L("High extruder current on filament swap");
|
||||
def->tooltip = L("It may be beneficial to increase the extruder motor current during the filament exchange"
|
||||
|
@ -698,6 +698,9 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
// i - case insensitive
|
||||
// w - whole word
|
||||
((ConfigOptionStrings, gcode_substitutions))
|
||||
//#if ENABLE_BINARIZED_GCODE
|
||||
((ConfigOptionBool, gcode_binary))
|
||||
//#endif // ENABLE_BINARIZED_GCODE
|
||||
((ConfigOptionString, layer_gcode))
|
||||
((ConfigOptionFloat, max_print_speed))
|
||||
((ConfigOptionFloat, max_volumetric_speed))
|
||||
|
@ -62,4 +62,13 @@
|
||||
#define ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR (1 && ENABLE_2_6_0_ALPHA1)
|
||||
|
||||
|
||||
//====================
|
||||
// 2.6.1.alpha1 techs
|
||||
//====================
|
||||
#define ENABLE_2_6_1_ALPHA1 1
|
||||
|
||||
// Enable export of binarized gcode
|
||||
#define ENABLE_BINARIZED_GCODE (1 && ENABLE_2_6_1_ALPHA1)
|
||||
|
||||
|
||||
#endif // _prusaslicer_technologies_h_
|
||||
|
@ -1673,6 +1673,9 @@ void TabPrint::build()
|
||||
Option option = optgroup->get_option("output_filename_format");
|
||||
option.opt.full_width = true;
|
||||
optgroup->append_single_option_line(option);
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
optgroup->append_single_option_line("gcode_binary");
|
||||
#endif // ENABLE_BINARIZED_GCODE
|
||||
|
||||
optgroup = page->new_optgroup(L("Other"));
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user