Implemented extension of the G-code post-processor framework:

1) New environment variable SLIC3R_PP_HOST contains one of
   "File", "PrusaLink", "Repetier", "SL1Host", "OctoPrint", "FlashAir", "Duet", "AstroBox" ...
2) New environment variable SLIC3R_PP_OUTPUT_NAME contains the name
   of the G-code file including path (for SLIC3R_PP_HOST == "File")
   or a name of the file after upload to the host (PrusaLink, Octoprint ...)
3) The post-processing script may suggest a new output file name
   (likely based on SLIC3R_PP_OUTPUT_NAME) by saving it as a single line
   into a new "output name" temp file. The "output name" file name is
   created by suffixing the input G-code file name with ".output_name".

Please note that the G-code viewer visualizes G-code before post-processing.

Fixes Broken PostProcessing when script changes out-filename #6042
This commit is contained in:
Vojtech Bubnik 2021-08-10 15:21:53 +02:00
parent 9e32a00efc
commit cf32b56454
5 changed files with 207 additions and 83 deletions

View File

@ -337,9 +337,9 @@ namespace Slic3r {
std::string printer; std::string printer;
void reset() { void reset() {
print = ""; print.clear();
filament = std::vector<std::string>(); filament.clear();
printer = ""; printer.clear();
} }
}; };
std::string filename; std::string filename;

View File

@ -1,5 +1,7 @@
#include "PostProcessor.hpp" #include "PostProcessor.hpp"
#include "libslic3r/Utils.hpp"
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <boost/format.hpp> #include <boost/format.hpp>
@ -179,41 +181,125 @@ static int run_script(const std::string &script, const std::string &gcode, std::
namespace Slic3r { namespace Slic3r {
void run_post_process_scripts(const std::string &path, const DynamicPrintConfig &config) // Run post processing script / scripts if defined.
// Returns true if a post-processing script was executed.
// Returns false if no post-processing script was defined.
// Throws an exception on error.
// host is one of "File", "PrusaLink", "Repetier", "SL1Host", "OctoPrint", "FlashAir", "Duet", "AstroBox" ...
// For a "File" target, a temp file will be created for src_path by adding a ".pp" suffix and src_path will be updated.
// In that case the caller is responsible to delete the temp file created.
// output_name is the final name of the G-code on SD card or when uploaded to PrusaLink or OctoPrint.
// If uploading to PrusaLink or OctoPrint, then the file will be renamed to output_name first on the target host.
// The post-processing script may change the output_name.
bool run_post_process_scripts(std::string &src_path, bool make_copy, const std::string &host, std::string &output_name, const DynamicPrintConfig &config)
{ {
const auto* post_process = config.opt<ConfigOptionStrings>("post_process"); const auto *post_process = config.opt<ConfigOptionStrings>("post_process");
if (// likely running in SLA mode if (// likely running in SLA mode
post_process == nullptr || post_process == nullptr ||
// no post-processing script // no post-processing script
post_process->values.empty()) post_process->values.empty())
return; return false;
std::string path;
if (make_copy) {
// Don't run the post-processing script on the input file, it will be memory mapped by the G-code viewer.
// Make a copy.
path = src_path + ".pp";
// First delete an old file if it exists.
try {
if (boost::filesystem::exists(path))
boost::filesystem::remove(path);
} catch (const std::exception &err) {
BOOST_LOG_TRIVIAL(error) << Slic3r::format("Failed deleting an old temporary file %1% before running a post-processing script: %2%", path, err.what());
}
// Second make a copy.
std::string error_message;
if (copy_file(src_path, path, error_message, false) != SUCCESS)
throw Slic3r::RuntimeError(Slic3r::format("Failed making a temporary copy of G-code file %1% before running a post-processing script: %2%", src_path, error_message));
} else {
// Don't make a copy of the G-code before running the post-processing script.
path = src_path;
}
auto delete_copy = [&path, &src_path, make_copy]() {
if (make_copy)
try {
if (boost::filesystem::exists(path))
boost::filesystem::remove(path);
} catch (const std::exception &err) {
BOOST_LOG_TRIVIAL(error) << Slic3r::format("Failed deleting a temporary copy %1% of a G-code file %2% : %3%", path, src_path, err.what());
}
};
// Store print configuration into environment variables.
config.setenv_();
auto gcode_file = boost::filesystem::path(path); auto gcode_file = boost::filesystem::path(path);
if (! boost::filesystem::exists(gcode_file)) if (! boost::filesystem::exists(gcode_file))
throw Slic3r::RuntimeError(std::string("Post-processor can't find exported gcode file")); throw Slic3r::RuntimeError(std::string("Post-processor can't find exported gcode file"));
for (const std::string &scripts : post_process->values) { // Store print configuration into environment variables.
std::vector<std::string> lines; config.setenv_();
boost::split(lines, scripts, boost::is_any_of("\r\n")); // Let the post-processing script know the target host ("File", "PrusaLink", "Repetier", "SL1Host", "OctoPrint", "FlashAir", "Duet", "AstroBox" ...)
for (std::string script : lines) { boost::nowide::setenv("SLIC3R_PP_HOST", host.c_str(), 1);
// Ignore empty post processing script lines. // Let the post-processing script know the final file name. For "File" host, it is a full path of the target file name and its location, for example pointing to an SD card.
boost::trim(script); // For "PrusaLink" or "OctoPrint", it is a file name optionally with a directory on the target host.
if (script.empty()) boost::nowide::setenv("SLIC3R_PP_OUTPUT_NAME", output_name.c_str(), 1);
continue;
BOOST_LOG_TRIVIAL(info) << "Executing script " << script << " on file " << path;
std::string std_err; // Path to an optional file that the post-processing script may create and populate it with a single line containing the output_name replacement.
const int result = run_script(script, gcode_file.string(), std_err); std::string path_output_name = src_path + ".output_name";
if (result != 0) { auto remove_output_name_file = [&path_output_name, &src_path]() {
const std::string msg = std_err.empty() ? (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%") % script % path % result).str() try {
: (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%\nOutput:\n%4%") % script % path % result % std_err).str(); if (boost::filesystem::exists(path_output_name))
BOOST_LOG_TRIVIAL(error) << msg; boost::filesystem::remove(path_output_name);
throw Slic3r::RuntimeError(msg); } catch (const std::exception &err) {
BOOST_LOG_TRIVIAL(error) << Slic3r::format("Failed deleting a file %1% carrying the final name / path of a G-code file %2%: %3%", path_output_name, src_path, err.what());
}
};
// Remove possible stalled path_output_name of the previous run.
remove_output_name_file();
try {
for (const std::string &scripts : post_process->values) {
std::vector<std::string> lines;
boost::split(lines, scripts, boost::is_any_of("\r\n"));
for (std::string script : lines) {
// Ignore empty post processing script lines.
boost::trim(script);
if (script.empty())
continue;
BOOST_LOG_TRIVIAL(info) << "Executing script " << script << " on file " << path;
std::string std_err;
const int result = run_script(script, gcode_file.string(), std_err);
if (result != 0) {
const std::string msg = std_err.empty() ? (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%") % script % path % result).str()
: (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%\nOutput:\n%4%") % script % path % result % std_err).str();
BOOST_LOG_TRIVIAL(error) << msg;
delete_copy();
throw Slic3r::RuntimeError(msg);
}
} }
} }
if (boost::filesystem::exists(path_output_name)) {
try {
// Read a single line from path_output_name, which should contain the new output name of the post-processed G-code.
boost::nowide::fstream f;
f.open(path_output_name, std::ios::in);
std::string new_output_name;
std::getline(f, new_output_name);
f.close();
BOOST_LOG_TRIVIAL(trace) << "Post-processing script changed the file name from " << output_name << " to " << new_output_name;
output_name = new_output_name;
} catch (const std::exception &err) {
BOOST_LOG_TRIVIAL(error) << Slic3r::format("Failed reading a file %1% carrying the final name / path of a G-code file: %2%", path_output_name, err.what());
}
remove_output_name_file();
}
} catch (...) {
remove_output_name_file();
delete_copy();
throw;
} }
src_path = std::move(path);
return true;
} }
} // namespace Slic3r } // namespace Slic3r

View File

@ -8,7 +8,23 @@
namespace Slic3r { namespace Slic3r {
extern void run_post_process_scripts(const std::string &path, const DynamicPrintConfig &config); // Run post processing script / scripts if defined.
// Returns true if a post-processing script was executed.
// Returns false if no post-processing script was defined.
// Throws an exception on error.
// host is one of "File", "PrusaLink", "Repetier", "SL1Host", "OctoPrint", "FlashAir", "Duet", "AstroBox" ...
// If make_copy, then a temp file will be created for src_path by adding a ".pp" suffix and src_path will be updated.
// In that case the caller is responsible to delete the temp file created.
// output_name is the final name of the G-code on SD card or when uploaded to PrusaLink or OctoPrint.
// If uploading to PrusaLink or OctoPrint, then the file will be renamed to output_name first on the target host.
// The post-processing script may change the output_name.
extern bool run_post_process_scripts(std::string &src_path, bool make_copy, const std::string &host, std::string &output_name, const DynamicPrintConfig &config);
inline bool run_post_process_scripts(std::string &src_path, const DynamicPrintConfig &config)
{
std::string src_path_name = src_path;
return run_post_process_scripts(src_path, false, "File", src_path_name, config);
}
} // namespace Slic3r } // namespace Slic3r

View File

@ -154,54 +154,7 @@ void BackgroundSlicingProcess::process_fff()
if (this->set_step_started(bspsGCodeFinalize)) { if (this->set_step_started(bspsGCodeFinalize)) {
if (! m_export_path.empty()) { if (! m_export_path.empty()) {
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id)); wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
finalize_gcode();
// let the gcode window to unmap the temporary .gcode file (m_temp_output_path)
// because the scripts may want to modify it
GUI::wxGetApp().plater()->stop_mapping_gcode_window();
m_print->set_status(95, _utf8(L("Running post-processing scripts")));
run_post_process_scripts(m_temp_output_path, m_fff_print->full_print_config());
// let the gcode window to reload and remap the temporary .gcode file (m_temp_output_path)
GUI::wxGetApp().plater()->start_mapping_gcode_window();
//FIXME localize the messages
// Perform the final post-processing of the export path by applying the print statistics over the file name.
std::string export_path = m_fff_print->print_statistics().finalize_output_path(m_export_path);
std::string error_message;
int copy_ret_val = CopyFileResult::SUCCESS;
try
{
copy_ret_val = copy_file(m_temp_output_path, export_path, error_message, m_export_path_on_removable_media);
}
catch (...)
{
throw Slic3r::ExportError(_utf8(L("Unknown error occured during exporting G-code.")));
}
switch (copy_ret_val) {
case CopyFileResult::SUCCESS: break; // no error
case CopyFileResult::FAIL_COPY_FILE:
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?\nError message: %1%"))) % error_message).str());
break;
case CopyFileResult::FAIL_FILES_DIFFERENT:
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code to the output G-code failed. There might be problem with target device, please try exporting again or using different device. The corrupted output G-code is at %1%.tmp."))) % export_path).str());
break;
case CopyFileResult::FAIL_RENAMING:
throw Slic3r::ExportError((boost::format(_utf8(L("Renaming of the G-code after copying to the selected destination folder has failed. Current path is %1%.tmp. Please try exporting again."))) % export_path).str());
break;
case CopyFileResult::FAIL_CHECK_ORIGIN_NOT_OPENED:
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code has finished but the original code at %1% couldn't be opened during copy check. The output G-code is at %2%.tmp."))) % m_temp_output_path % export_path).str());
break;
case CopyFileResult::FAIL_CHECK_TARGET_NOT_OPENED:
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code has finished but the exported code couldn't be opened during copy check. The output G-code is at %1%.tmp."))) % export_path).str());
break;
default:
throw Slic3r::ExportError(_utf8(L("Unknown error occured during exporting G-code.")));
BOOST_LOG_TRIVIAL(error) << "Unexpected fail code(" << (int)copy_ret_val << ") durring copy_file() to " << export_path << ".";
break;
}
m_print->set_status(100, (boost::format(_utf8(L("G-code file exported to %1%"))) % export_path).str());
} else if (! m_upload_job.empty()) { } else if (! m_upload_job.empty()) {
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id)); wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
prepare_upload(); prepare_upload();
@ -621,8 +574,11 @@ Print::ApplyStatus BackgroundSlicingProcess::apply(const Model &model, const Dyn
// Some FFF status was invalidated, and the G-code was not exported yet. // Some FFF status was invalidated, and the G-code was not exported yet.
// Let the G-code preview UI know that the final G-code preview is not valid. // Let the G-code preview UI know that the final G-code preview is not valid.
// In addition, this early memory deallocation reduces memory footprint. // In addition, this early memory deallocation reduces memory footprint.
if (m_gcode_result != nullptr) if (m_gcode_result != nullptr) {
//FIXME calling platter from here is not a staple of a good architecture.
GUI::wxGetApp().plater()->stop_mapping_gcode_window();
m_gcode_result->reset(); m_gcode_result->reset();
}
} }
return invalidated; return invalidated;
} }
@ -698,12 +654,73 @@ bool BackgroundSlicingProcess::invalidate_all_steps()
return m_step_state.invalidate_all([this](){ this->stop_internal(); }); return m_step_state.invalidate_all([this](){ this->stop_internal(); });
} }
// G-code is generated in m_temp_output_path.
// Optionally run a post-processing script on a copy of m_temp_output_path.
// Copy the final G-code to target location (possibly a SD card, if it is a removable media, then verify that the file was written without an error).
void BackgroundSlicingProcess::finalize_gcode()
{
m_print->set_status(95, _utf8(L("Running post-processing scripts")));
// Perform the final post-processing of the export path by applying the print statistics over the file name.
std::string export_path = m_fff_print->print_statistics().finalize_output_path(m_export_path);
std::string output_path = m_temp_output_path;
// Both output_path and export_path ar in-out parameters.
// If post processed, output_path will differ from m_temp_output_path as run_post_process_scripts() will make a copy of the G-code to not
// collide with the G-code viewer memory mapping of the unprocessed G-code. G-code viewer maps unprocessed G-code, because m_gcode_result
// is calculated for the unprocessed G-code and it references lines in the memory mapped G-code file by line numbers.
// export_path may be changed by the post-processing script as well if the post processing script decides so, see GH #6042.
bool post_processed = run_post_process_scripts(output_path, true, "File", export_path, m_fff_print->full_print_config());
auto remove_post_processed_temp_file = [post_processed, &output_path]() {
if (post_processed)
try {
boost::filesystem::remove(output_path);
} catch (const std::exception &ex) {
BOOST_LOG_TRIVIAL(error) << "Failed to remove temp file " << output_path << ": " << ex.what();
}
};
//FIXME localize the messages
std::string error_message;
int copy_ret_val = CopyFileResult::SUCCESS;
try
{
copy_ret_val = copy_file(output_path, export_path, error_message, m_export_path_on_removable_media);
remove_post_processed_temp_file();
}
catch (...)
{
remove_post_processed_temp_file();
throw Slic3r::ExportError(_utf8(L("Unknown error occured during exporting G-code.")));
}
switch (copy_ret_val) {
case CopyFileResult::SUCCESS: break; // no error
case CopyFileResult::FAIL_COPY_FILE:
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?\nError message: %1%"))) % error_message).str());
break;
case CopyFileResult::FAIL_FILES_DIFFERENT:
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code to the output G-code failed. There might be problem with target device, please try exporting again or using different device. The corrupted output G-code is at %1%.tmp."))) % export_path).str());
break;
case CopyFileResult::FAIL_RENAMING:
throw Slic3r::ExportError((boost::format(_utf8(L("Renaming of the G-code after copying to the selected destination folder has failed. Current path is %1%.tmp. Please try exporting again."))) % export_path).str());
break;
case CopyFileResult::FAIL_CHECK_ORIGIN_NOT_OPENED:
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code has finished but the original code at %1% couldn't be opened during copy check. The output G-code is at %2%.tmp."))) % output_path % export_path).str());
break;
case CopyFileResult::FAIL_CHECK_TARGET_NOT_OPENED:
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code has finished but the exported code couldn't be opened during copy check. The output G-code is at %1%.tmp."))) % export_path).str());
break;
default:
throw Slic3r::ExportError(_utf8(L("Unknown error occured during exporting G-code.")));
BOOST_LOG_TRIVIAL(error) << "Unexpected fail code(" << (int)copy_ret_val << ") durring copy_file() to " << export_path << ".";
break;
}
m_print->set_status(100, (boost::format(_utf8(L("G-code file exported to %1%"))) % export_path).str());
}
// A print host upload job has been scheduled, enqueue it to the printhost job queue
void BackgroundSlicingProcess::prepare_upload() void BackgroundSlicingProcess::prepare_upload()
{ {
// A print host upload job has been scheduled, enqueue it to the printhost job queue
// XXX: is fs::path::string() right?
// Generate a unique temp path to which the gcode/zip file is copied/exported // Generate a unique temp path to which the gcode/zip file is copied/exported
boost::filesystem::path source_path = boost::filesystem::temp_directory_path() boost::filesystem::path source_path = boost::filesystem::temp_directory_path()
/ boost::filesystem::unique_path("." SLIC3R_APP_KEY ".upload.%%%%-%%%%-%%%%-%%%%"); / boost::filesystem::unique_path("." SLIC3R_APP_KEY ".upload.%%%%-%%%%-%%%%-%%%%");
@ -711,11 +728,15 @@ void BackgroundSlicingProcess::prepare_upload()
if (m_print == m_fff_print) { if (m_print == m_fff_print) {
m_print->set_status(95, _utf8(L("Running post-processing scripts"))); m_print->set_status(95, _utf8(L("Running post-processing scripts")));
std::string error_message; std::string error_message;
if (copy_file(m_temp_output_path, source_path.string(), error_message) != SUCCESS) { if (copy_file(m_temp_output_path, source_path.string(), error_message) != SUCCESS)
throw Slic3r::RuntimeError(_utf8(L("Copying of the temporary G-code to the output G-code failed"))); throw Slic3r::RuntimeError(_utf8(L("Copying of the temporary G-code to the output G-code failed")));
}
run_post_process_scripts(source_path.string(), m_fff_print->full_print_config());
m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string()); m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
// Make a copy of the source path, as run_post_process_scripts() is allowed to change it when making a copy of the source file
// (not here, but when the final target is a file).
std::string source_path_str = source_path.string();
std::string output_name_str = m_upload_job.upload_data.upload_path.string();
if (run_post_process_scripts(source_path_str, false, m_upload_job.printhost->get_name(), output_name_str, m_fff_print->full_print_config()))
m_upload_job.upload_data.upload_path = output_name_str;
} else { } else {
m_upload_job.upload_data.upload_path = m_sla_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string()); m_upload_job.upload_data.upload_path = m_sla_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());

View File

@ -216,9 +216,9 @@ private:
Print *m_fff_print = nullptr; Print *m_fff_print = nullptr;
SLAPrint *m_sla_print = nullptr; SLAPrint *m_sla_print = nullptr;
// Data structure, to which the G-code export writes its annotations. // Data structure, to which the G-code export writes its annotations.
GCodeProcessor::Result *m_gcode_result = nullptr; GCodeProcessor::Result *m_gcode_result = nullptr;
// Callback function, used to write thumbnails into gcode. // Callback function, used to write thumbnails into gcode.
ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr; ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr;
SL1Archive m_sla_archive; SL1Archive m_sla_archive;
// Temporary G-code, there is one defined for the BackgroundSlicingProcess, differentiated from the other processes by a process ID. // Temporary G-code, there is one defined for the BackgroundSlicingProcess, differentiated from the other processes by a process ID.
std::string m_temp_output_path; std::string m_temp_output_path;
@ -262,6 +262,7 @@ private:
bool invalidate_all_steps(); bool invalidate_all_steps();
// If the background processing stop was requested, throw CanceledException. // If the background processing stop was requested, throw CanceledException.
void throw_if_canceled() const { if (m_print->canceled()) throw CanceledException(); } void throw_if_canceled() const { if (m_print->canceled()) throw CanceledException(); }
void finalize_gcode();
void prepare_upload(); void prepare_upload();
// To be executed at the background thread. // To be executed at the background thread.
ThumbnailsList render_thumbnails(const ThumbnailsParams &params); ThumbnailsList render_thumbnails(const ThumbnailsParams &params);