2018-09-17 12:01:02 +02:00
#include "PostProcessor.hpp"
2019-01-18 10:16:53 +01:00
#include <boost/algorithm/string.hpp>
2018-12-10 09:52:22 +01:00
#include <boost/log/trivial.hpp>
2019-03-11 09:48:24 +01:00
#include <boost/format.hpp>
2019-01-17 20:34:19 +01:00
#include <boost/filesystem.hpp>
2018-12-10 09:52:22 +01:00
2018-12-09 14:47:49 -07:00
#ifdef WIN32
2018-09-17 12:01:02 +02:00
2019-01-17 20:34:19 +01:00
// The standard Windows includes.
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
2018-09-17 15:32:54 +02:00
2019-01-17 20:34:19 +01:00
// https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
// This routine appends the given argument to a command line such that CommandLineToArgvW will return the argument string unchanged.
// Arguments in a command line should be separated by spaces; this function does not add these spaces.
// Argument - Supplies the argument to encode.
// CommandLine - Supplies the command line to which we append the encoded argument string.
static void quote_argv_winapi ( const std :: wstring & argument , std :: wstring & commmand_line_out )
2018-09-17 12:01:02 +02:00
{
2019-01-17 20:34:19 +01:00
// Don't quote unless we actually need to do so --- hopefully avoid problems if programs won't parse quotes properly.
if ( argument . empty () == false && argument . find_first_of ( L " \t\n\v\" " ) == argument . npos )
commmand_line_out . append ( argument );
else {
commmand_line_out . push_back ( L '"' );
for ( auto it = argument . begin (); ; ++ it ) {
unsigned number_backslashes = 0 ;
while ( it != argument . end () && * it == L '\\' ) {
++ it ;
++ number_backslashes ;
}
if ( it == argument . end ()) {
// Escape all backslashes, but let the terminating double quotation mark we add below be interpreted as a metacharacter.
commmand_line_out . append ( number_backslashes * 2 , L '\\' );
break ;
} else if ( * it == L '"' ) {
// Escape all backslashes and the following double quotation mark.
commmand_line_out . append ( number_backslashes * 2 + 1 , L '\\' );
commmand_line_out . push_back ( * it );
} else {
// Backslashes aren't special here.
commmand_line_out . append ( number_backslashes , L '\\' );
commmand_line_out . push_back ( * it );
}
}
commmand_line_out . push_back ( L '"' );
}
2018-09-17 12:01:02 +02:00
}
2019-01-17 20:34:19 +01:00
static DWORD execute_process_winapi ( const std :: wstring & command_line )
{
// Extract the current environment to be passed to the child process.
std :: wstring envstr ;
{
wchar_t * env = GetEnvironmentStrings ();
assert ( env != nullptr );
const wchar_t * var = env ;
size_t totallen = 0 ;
size_t len ;
while (( len = wcslen ( var )) > 0 ) {
totallen += len + 1 ;
var += len + 1 ;
}
envstr = std :: wstring ( env , totallen );
FreeEnvironmentStrings ( env );
}
STARTUPINFOW startup_info ;
memset ( & startup_info , 0 , sizeof ( startup_info ));
startup_info . cb = sizeof ( STARTUPINFO );
#if 0
startup_info.dwFlags = STARTF_USESHOWWINDOW;
startup_info.wShowWindow = SW_HIDE;
#endif
PROCESS_INFORMATION process_info ;
if ( ! :: CreateProcessW (
nullptr /* lpApplicationName */ , ( LPWSTR ) command_line . c_str (), nullptr /* lpProcessAttributes */ , nullptr /* lpThreadAttributes */ , false /* bInheritHandles */ ,
CREATE_UNICODE_ENVIRONMENT /* | CREATE_NEW_CONSOLE */ /* dwCreationFlags */ , ( LPVOID ) envstr . c_str (), nullptr /* lpCurrentDirectory */ , & startup_info , & process_info ))
throw std :: runtime_error ( std :: string ( "Failed starting the script " ) + boost :: nowide :: narrow ( command_line ) + ", Win32 error: " + std :: to_string ( int ( :: GetLastError ())));
:: WaitForSingleObject ( process_info . hProcess , INFINITE );
ULONG rc = 0 ;
:: GetExitCodeProcess ( process_info . hProcess , & rc );
:: CloseHandle ( process_info . hThread );
:: CloseHandle ( process_info . hProcess );
return rc ;
}
// Run the script. If it is a perl script, run it through the bundled perl interpreter.
// If it is a batch file, run it through the cmd.exe.
// Otherwise run it directly.
2019-03-11 09:48:24 +01:00
static int run_script ( const std :: string & script , const std :: string & gcode , std :: string & /*std_err*/ )
2019-01-17 20:34:19 +01:00
{
// Unpack the argument list provided by the user.
int nArgs ;
LPWSTR * szArglist = CommandLineToArgvW ( boost :: nowide :: widen ( script ). c_str (), & nArgs );
if ( szArglist == nullptr || nArgs <= 0 ) {
// CommandLineToArgvW failed. Maybe the command line escapment is invalid?
throw std :: runtime_error ( std :: string ( "Post processing script " ) + script + " on file " + gcode + " failed. CommandLineToArgvW() refused to parse the command line path." );
}
std :: wstring command_line ;
std :: wstring command = szArglist [ 0 ];
if ( ! boost :: filesystem :: exists ( boost :: filesystem :: path ( command )))
throw std :: runtime_error ( std :: string ( "The configured post-processing script does not exist: " ) + boost :: nowide :: narrow ( command ));
if ( boost :: iends_with ( command , L ".pl" )) {
// This is a perl script. Run it through the perl interpreter.
// The current process may be slic3r.exe or slic3r-console.exe.
// Find the path of the process:
wchar_t wpath_exe [ _MAX_PATH + 1 ];
:: GetModuleFileNameW ( nullptr , wpath_exe , _MAX_PATH );
boost :: filesystem :: path path_exe ( wpath_exe );
boost :: filesystem :: path path_perl = path_exe . parent_path () / "perl" / "perl.exe" ;
if ( ! boost :: filesystem :: exists ( path_perl )) {
LocalFree ( szArglist );
throw std :: runtime_error ( std :: string ( "Perl interpreter " ) + path_perl . string () + " does not exist." );
}
// Replace it with the current perl interpreter.
quote_argv_winapi ( boost :: nowide :: widen ( path_perl . string ()), command_line );
command_line += L " " ;
} else if ( boost :: iends_with ( command , ".bat" )) {
// Run a batch file through the command line interpreter.
command_line = L "cmd.exe /C " ;
}
for ( int i = 0 ; i < nArgs ; ++ i ) {
quote_argv_winapi ( szArglist [ i ], command_line );
command_line += L " " ;
}
LocalFree ( szArglist );
quote_argv_winapi ( boost :: nowide :: widen ( gcode ), command_line );
return ( int ) execute_process_winapi ( command_line );
}
2018-09-17 15:32:54 +02:00
2018-09-17 12:01:02 +02:00
#else
2019-03-11 09:48:24 +01:00
// POSIX
#include <cstdlib> // getenv()
#include <sstream>
#include <boost/process.hpp>
namespace process = boost :: process ;
static int run_script ( const std :: string & script , const std :: string & gcode , std :: string & std_err )
{
// Try to obtain user's default shell
const char * shell = :: getenv ( "SHELL" );
if ( shell == nullptr ) { shell = "sh" ; }
// Quote and escape the gcode path argument
std :: string command { script };
command . append ( " '" );
for ( char c : gcode ) {
if ( c == '\'' ) { command . append ( "' \\ ''" ); }
else { command . push_back ( c ); }
}
command . push_back ( '\'' );
BOOST_LOG_TRIVIAL ( debug ) << boost :: format ( "Executing script, shell: %1%, command: %2%" ) % shell % command ;
process :: ipstream istd_err ;
process :: child child ( shell , "-c" , command , process :: std_err > istd_err );
std_err . clear ();
std :: string line ;
while ( child . running () && std :: getline ( istd_err , line )) {
std_err . append ( line );
std_err . push_back ( '\n' );
}
child . wait ();
return child . exit_code ();
}
2018-12-09 14:47:49 -07:00
#endif
2018-09-17 12:01:02 +02:00
2018-09-17 15:32:54 +02:00
namespace Slic3r {
2018-09-17 12:01:02 +02:00
void run_post_process_scripts ( const std :: string & path , const PrintConfig & config )
{
if ( config . post_process . values . empty ())
return ;
2018-12-10 09:52:22 +01:00
config . setenv_ ();
2018-12-09 14:47:49 -07:00
auto gcode_file = boost :: filesystem :: path ( path );
2018-12-10 09:52:22 +01:00
if ( ! boost :: filesystem :: exists ( gcode_file ))
2018-12-09 14:47:49 -07:00
throw std :: runtime_error ( std :: string ( "Post-processor can't find exported gcode file" ));
2019-01-17 20:34:19 +01:00
for ( const std :: string & scripts : config . 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 ;
2019-03-11 09:48:24 +01:00
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. \n Error code: %3%" ) % script % path % result ). str ()
: ( boost :: format ( "Post-processing script %1% on file %2% failed. \n Error code: %3% \n Output: \n %4%" ) % script % path % result % std_err ). str ();
BOOST_LOG_TRIVIAL ( error ) << msg ;
throw std :: runtime_error ( msg );
}
2019-01-17 20:34:19 +01:00
}
2018-09-17 12:01:02 +02:00
}
}
} // namespace Slic3r