From 8746f84fa2b8585270d36b4070693a188e722e33 Mon Sep 17 00:00:00 2001 From: bubnikv <bubnikv@gmail.com> Date: Tue, 5 Dec 2017 15:54:24 +0100 Subject: [PATCH] Improved error reporting of the PlaceholderParser. The PlaceholderParser is currently used by the GCode.cpp and by Printer.cpp to generate a new name for the exported G-code or SVG file. The PlaceholderParser::process() will throw a runtime_error with a comprehensive error message. The G-code export will include these error messages into the G-code text with !!!!!! separators, and the GUI will inform the user, that the G-code export failed. --- lib/Slic3r/GUI/MainFrame.pm | 1 + lib/Slic3r/GUI/Plater.pm | 9 +- lib/Slic3r/Print.pm | 10 +- slic3r.pl | 2 + xs/src/libslic3r/GCode.cpp | 78 ++++++---- xs/src/libslic3r/GCode.hpp | 10 +- xs/src/libslic3r/PlaceholderParser.cpp | 188 +++++++++++-------------- xs/src/libslic3r/Print.cpp | 6 +- xs/xsp/GCode.xsp | 9 +- xs/xsp/PlaceholderParser.xsp | 8 +- xs/xsp/Print.xsp | 10 +- 11 files changed, 190 insertions(+), 141 deletions(-) diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 370bac2ed..5ae6ac08f 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -422,6 +422,7 @@ sub quick_slice { if ($params{reslice}) { $output_file = $qs_last_output_file if defined $qs_last_output_file; } elsif ($params{save_as}) { + # The following line may die if the output_filename_format template substitution fails. $output_file = $sprint->output_filepath; $output_file =~ s/\.[gG][cC][oO][dD][eE]$/.svg/ if $params{export_svg}; my $dlg = Wx::FileDialog->new($self, 'Save ' . ($params{export_svg} ? 'SVG' : 'G-code') . ' file as:', diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index aeefbe6a3..0cb433309 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1291,9 +1291,11 @@ sub export_gcode { # select output file if ($output_file) { - $self->{export_gcode_output_file} = $self->{print}->output_filepath($output_file); + $self->{export_gcode_output_file} = eval { $self->{print}->output_filepath($output_file) }; + Slic3r::GUI::catch_error($self) and return; } else { - my $default_output_file = $self->{print}->output_filepath($main::opt{output} // ''); + my $default_output_file = eval { $self->{print}->output_filepath($main::opt{output} // '') }; + Slic3r::GUI::catch_error($self) and return; my $dlg = Wx::FileDialog->new($self, 'Save G-code file as:', wxTheApp->{app_config}->get_last_output_dir(dirname($default_output_file)), basename($default_output_file), &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); @@ -1544,7 +1546,8 @@ sub export_amf { sub _get_export_file { my ($self, $format) = @_; my $suffix = $format eq 'STL' ? '.stl' : '.amf.xml'; - my $output_file = $self->{print}->output_filepath($main::opt{output} // ''); + my $output_file = eval { $self->{print}->output_filepath($main::opt{output} // '') }; + Slic3r::GUI::catch_error($self) and return undef; $output_file =~ s/\.[gG][cC][oO][dD][eE]$/$suffix/; my $dlg = Wx::FileDialog->new($self, "Save $format file as:", dirname($output_file), basename($output_file), &Slic3r::GUI::MODEL_WILDCARD, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 1613b7e45..12ad2f12f 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -65,6 +65,9 @@ sub process { } # G-code export process, running at a background thread. +# The export_gcode may die for various reasons (fails to process output_filename_format, +# write error into the G-code, cannot execute post-processing scripts). +# It is up to the caller to show an error message. sub export_gcode { my $self = shift; my %params = @_; @@ -73,11 +76,12 @@ sub export_gcode { $self->process; # output everything to a G-code file + # The following call may die if the output_filename_format template substitution fails. my $output_file = $self->output_filepath($params{output_file} // ''); $self->status_cb->(90, "Exporting G-code" . ($output_file ? " to $output_file" : "")); - die "G-code export to " . $output_file . " failed\n" - if ! Slic3r::GCode->new->do_export($self, $output_file); + # The following line may die for multiple reasons. + Slic3r::GCode->new->do_export($self, $output_file); # run post-processing scripts if (@{$self->config->post_process}) { @@ -99,6 +103,7 @@ sub export_gcode { } # Export SVG slices for the offline SLA printing. +# The export_svg is expected to be executed inside an eval block. sub export_svg { my $self = shift; my %params = @_; @@ -107,6 +112,7 @@ sub export_svg { my $fh = $params{output_fh}; if (!$fh) { + # The following line may die if the output_filename_format template substitution fails. my $output_file = $self->output_filepath($params{output_file}); $output_file =~ s/\.[gG][cC][oO][dD][eE]$/.svg/; Slic3r::open(\$fh, ">", $output_file) or die "Failed to open $output_file for writing\n"; diff --git a/slic3r.pl b/slic3r.pl index afddc77d3..c2bee2219 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -219,6 +219,8 @@ if (@ARGV) { # slicing from command line $sprint->export_svg; } else { my $t0 = [gettimeofday]; + # The following call may die if the output_filename_format template substitution fails, + # if the file cannot be written into, or if the post processing scripts cannot be executed. $sprint->export_gcode; # output some statistics diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index bc3a86993..b5f186630 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -187,7 +187,7 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T if (! start_filament_gcode.empty()) { // Process the start_filament_gcode for the active filament only. gcodegen.placeholder_parser().set("current_extruder", new_extruder_id); - gcode += gcodegen.placeholder_parser().process(start_filament_gcode, new_extruder_id); + gcode += gcodegen.placeholder_parser_process("start_filament_gcode", start_filament_gcode, new_extruder_id); check_add_eol(gcode); } } @@ -362,7 +362,7 @@ std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collec return layers_to_print; } -bool GCode::do_export(Print *print, const char *path) +void GCode::do_export(Print *print, const char *path) { // Remove the old g-code if it exists. boost::nowide::remove(path); @@ -372,23 +372,34 @@ bool GCode::do_export(Print *print, const char *path) FILE *file = boost::nowide::fopen(path_tmp.c_str(), "wb"); if (file == nullptr) - return false; + throw std::runtime_error(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n"); - bool result = this->_do_export(*print, file); + this->m_placeholder_parser_failed_templates.clear(); + this->_do_export(*print, file); fclose(file); - - if (result && boost::nowide::rename(path_tmp.c_str(), path) != 0) { - boost::nowide::cerr << "Failed to remove the output G-code file from " << path_tmp << " to " << path - << ". Is " << path_tmp << " locked?" << std::endl; - result = false; - } - - if (! result) + if (ferror(file)) { boost::nowide::remove(path_tmp.c_str()); - return result; + throw std::runtime_error(std::string("G-code export to ") + path + " failed\nIs the disk full?\n"); + } + if (! this->m_placeholder_parser_failed_templates.empty()) { + // G-code export proceeded, but some of the PlaceholderParser substitutions failed. + std::string msg = std::string("G-code export to ") + path + " failed due to invalid custom G-code sections:\n\n"; + for (const std::string &name : this->m_placeholder_parser_failed_templates) + msg += std::string("\t") + name + "\n"; + msg += "\nPlease inspect the file "; + msg += path_tmp + " for error messages enclosed between\n"; + msg += " !!!!! Failed to process the custom G-code template ...\n"; + msg += "and\n"; + msg += " !!!!! End of an error report for the custom G-code template ...\n"; + throw std::runtime_error(msg); + } + if (boost::nowide::rename(path_tmp.c_str(), path) != 0) + throw std::runtime_error( + std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + path + '\n' + + "Is " + path_tmp + " locked?" + '\n'); } -bool GCode::_do_export(Print &print, FILE *file) +void GCode::_do_export(Print &print, FILE *file) { // How many times will be change_layer() called? // change_layer() in turn increments the progress bar status. @@ -557,7 +568,7 @@ bool GCode::_do_export(Print &print, FILE *file) m_placeholder_parser.set("current_object_idx", 0); // For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided. m_placeholder_parser.set("has_wipe_tower", has_wipe_tower); - std::string start_gcode = m_placeholder_parser.process(print.config.start_gcode.value, initial_extruder_id); + std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config.start_gcode.value, initial_extruder_id); // Set bed temperature if the start G-code does not contain any bed temp control G-codes. this->_print_first_layer_bed_temperature(file, print, start_gcode, initial_extruder_id, true); @@ -571,11 +582,11 @@ bool GCode::_do_export(Print &print, FILE *file) // Wipe tower will control the extruder switching, it will call the start_filament_gcode. } else { // Only initialize the initial extruder. - writeln(file, m_placeholder_parser.process(print.config.start_filament_gcode.values[initial_extruder_id], initial_extruder_id)); + writeln(file, this->placeholder_parser_process("start_filament_gcode", print.config.start_filament_gcode.values[initial_extruder_id], initial_extruder_id)); } } else { for (const std::string &start_gcode : print.config.start_filament_gcode.values) - writeln(file, m_placeholder_parser.process(start_gcode, (unsigned int)(&start_gcode - &print.config.start_filament_gcode.values.front()))); + writeln(file, this->placeholder_parser_process("start_gcode", start_gcode, (unsigned int)(&start_gcode - &print.config.start_filament_gcode.values.front()))); } this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, true); @@ -668,7 +679,7 @@ bool GCode::_do_export(Print &print, FILE *file) // another one, set first layer temperatures. This happens before the Z move // is triggered, so machine has more time to reach such temperatures. m_placeholder_parser.set("current_object_idx", int(finished_objects)); - std::string between_objects_gcode = m_placeholder_parser.process(print.config.between_objects_gcode.value, initial_extruder_id); + std::string between_objects_gcode = this->placeholder_parser_process("between_objects_gcode", print.config.between_objects_gcode.value, initial_extruder_id); // Set first layer bed and extruder temperatures, don't wait for it to reach the temperature. this->_print_first_layer_bed_temperature(file, print, between_objects_gcode, initial_extruder_id, false); this->_print_first_layer_extruder_temperatures(file, print, between_objects_gcode, initial_extruder_id, false); @@ -747,12 +758,12 @@ bool GCode::_do_export(Print &print, FILE *file) // Process filament-specific gcode in extruder order. if (print.config.single_extruder_multi_material) { // Process the end_filament_gcode for the active filament only. - writeln(file, m_placeholder_parser.process(print.config.end_filament_gcode.get_at(m_writer.extruder()->id()), m_writer.extruder()->id())); + writeln(file, this->placeholder_parser_process("end_filament_gcode", print.config.end_filament_gcode.get_at(m_writer.extruder()->id()), m_writer.extruder()->id())); } else { for (const std::string &end_gcode : print.config.end_filament_gcode.values) - writeln(file, m_placeholder_parser.process(end_gcode, (unsigned int)(&end_gcode - &print.config.end_filament_gcode.values.front()))); + writeln(file, this->placeholder_parser_process("end_gcode", end_gcode, (unsigned int)(&end_gcode - &print.config.end_filament_gcode.values.front()))); } - writeln(file, m_placeholder_parser.process(print.config.end_gcode, m_writer.extruder()->id())); + writeln(file, this->placeholder_parser_process("end_gcode", print.config.end_gcode, m_writer.extruder()->id())); write(file, m_writer.update_progress(m_layer_count, m_layer_count, true)); // 100% write(file, m_writer.postamble()); @@ -793,8 +804,21 @@ bool GCode::_do_export(Print &print, FILE *file) fprintf(file, "; %s = %s\n", key.c_str(), cfg->serialize(key).c_str()); } } +} - return true; +std::string GCode::placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override) +{ + try { + return m_placeholder_parser.process(templ, current_extruder_id, config_override); + } catch (std::runtime_error &err) { + // Collect the names of failed template substitutions for error reporting. + this->m_placeholder_parser_failed_templates.insert(name); + // Insert the macro error message into the G-code. + return + std::string("!!!!! Failed to process the custom G-code template ") + name + "\n" + + err.what() + + "!!!!! End of an error report for the custom G-code template " + name + "\n"; + } } // Parse the custom G-code, try to find mcode_set_temp_dont_wait and mcode_set_temp_and_wait inside the custom G-code. @@ -997,7 +1021,7 @@ void GCode::process_layer( DynamicConfig config; config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index + 1)); config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); - gcode += m_placeholder_parser.process( + gcode += this->placeholder_parser_process("before_layer_gcode", print.config.before_layer_gcode.value, m_writer.extruder()->id(), &config) + "\n"; } @@ -1007,7 +1031,7 @@ void GCode::process_layer( DynamicConfig config; config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); - gcode += m_placeholder_parser.process( + gcode += this->placeholder_parser_process("layer_gcode", print.config.layer_gcode.value, m_writer.extruder()->id(), &config) + "\n"; } @@ -2206,7 +2230,7 @@ std::string GCode::set_extruder(unsigned int extruder_id) unsigned int old_extruder_id = m_writer.extruder()->id(); const std::string &end_filament_gcode = m_config.end_filament_gcode.get_at(old_extruder_id); if (m_config.single_extruder_multi_material && ! end_filament_gcode.empty()) { - gcode += m_placeholder_parser.process(end_filament_gcode, old_extruder_id); + gcode += placeholder_parser_process("end_filament_gcode", end_filament_gcode, old_extruder_id); check_add_eol(gcode); } } @@ -2218,7 +2242,7 @@ std::string GCode::set_extruder(unsigned int extruder_id) DynamicConfig config; config.set_key_value("previous_extruder", new ConfigOptionInt((int)m_writer.extruder()->id())); config.set_key_value("next_extruder", new ConfigOptionInt((int)extruder_id)); - gcode += m_placeholder_parser.process(m_config.toolchange_gcode.value, extruder_id, &config); + gcode += placeholder_parser_process("toolchange_gcode", m_config.toolchange_gcode.value, extruder_id, &config); check_add_eol(gcode); } @@ -2232,7 +2256,7 @@ std::string GCode::set_extruder(unsigned int extruder_id) const std::string &start_filament_gcode = m_config.start_filament_gcode.get_at(extruder_id); if (m_config.single_extruder_multi_material && ! start_filament_gcode.empty()) { // Process the start_filament_gcode for the active filament only. - gcode += m_placeholder_parser.process(start_filament_gcode, extruder_id); + gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id); check_add_eol(gcode); } // Set the new extruder to the operating temperature. diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp index 72c39fe38..2fd3b39d3 100644 --- a/xs/src/libslic3r/GCode.hpp +++ b/xs/src/libslic3r/GCode.hpp @@ -130,7 +130,8 @@ public: {} ~GCode() {} - bool do_export(Print *print, const char *path); + // throws std::runtime_exception + void do_export(Print *print, const char *path); // Exported for the helper classes (OozePrevention, Wipe) and for the Perl binding for unit tests. const Pointf& origin() const { return m_origin; } @@ -143,6 +144,9 @@ public: const Layer* layer() const { return m_layer; } GCodeWriter& writer() { return m_writer; } PlaceholderParser& placeholder_parser() { return m_placeholder_parser; } + // Process a template through the placeholder parser, collect error messages to be reported + // inside the generated string and after the G-code export finishes. + std::string placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override = nullptr); bool enable_cooling_markers() const { return m_enable_cooling_markers; } // For Perl bindings, to be used exclusively by unit tests. @@ -151,7 +155,7 @@ public: void apply_print_config(const PrintConfig &print_config); protected: - bool _do_export(Print &print, FILE *file); + void _do_export(Print &print, FILE *file); // Object and support extrusions of the same PrintObject at the same print_z. struct LayerToPrint @@ -223,6 +227,8 @@ protected: FullPrintConfig m_config; GCodeWriter m_writer; PlaceholderParser m_placeholder_parser; + // Collection of templates, on which the placeholder substitution failed. + std::set<std::string> m_placeholder_parser_failed_templates; OozePrevention m_ooze_prevention; Wipe m_wipe; AvoidCrossingPerimeters m_avoid_crossing_perimeters; diff --git a/xs/src/libslic3r/PlaceholderParser.cpp b/xs/src/libslic3r/PlaceholderParser.cpp index b09e06133..f9f72a9e9 100644 --- a/xs/src/libslic3r/PlaceholderParser.cpp +++ b/xs/src/libslic3r/PlaceholderParser.cpp @@ -368,7 +368,7 @@ namespace client value = lhs.to_string() == rhs.to_string(); } else { boost::throw_exception(qi::expectation_failure<Iterator>( - lhs.it_range.begin(), rhs.it_range.end(), spirit::info("Cannot compare the types."))); + lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot compare the types."))); } lhs.type = TYPE_BOOL; lhs.data.b = (op == '=') ? value : !value; @@ -387,7 +387,7 @@ namespace client void throw_exception(const char *message) const { boost::throw_exception(qi::expectation_failure<Iterator>( - this->it_range.begin(), this->it_range.end(), spirit::info(message))); + this->it_range.begin(), this->it_range.end(), spirit::info(std::string("*") + message))); } void throw_if_not_numeric(const char *message) const @@ -417,6 +417,7 @@ namespace client const PlaceholderParser *pp = nullptr; const DynamicConfig *config_override = nullptr; const size_t current_extruder_id = 0; + std::string error_message; const ConfigOption* resolve_symbol(const std::string &opt_key) const { @@ -444,26 +445,22 @@ namespace client opt = ctx->resolve_symbol(opt_key_str.substr(0, idx)); if (opt != nullptr) { if (! opt->is_vector()) - boost::throw_exception(qi::expectation_failure<Iterator>( - opt_key.begin(), opt_key.end(), spirit::info("Trying to index a scalar variable"))); + ctx->throw_exception("Trying to index a scalar variable", opt_key); char *endptr = nullptr; idx = strtol(opt_key_str.c_str() + idx + 1, &endptr, 10); if (endptr == nullptr || *endptr != 0) - boost::throw_exception(qi::expectation_failure<Iterator>( - opt_key.begin() + idx + 1, opt_key.end(), spirit::info("Invalid vector index"))); + ctx->throw_exception("Invalid vector index", boost::iterator_range<Iterator>(opt_key.begin() + idx + 1, opt_key.end())); } } } if (opt == nullptr) - boost::throw_exception(qi::expectation_failure<Iterator>( - opt_key.begin(), opt_key.end(), spirit::info("Variable does not exist"))); + ctx->throw_exception("Variable does not exist", boost::iterator_range<Iterator>(opt_key.begin(), opt_key.end())); if (opt->is_scalar()) output = opt->serialize(); else { const ConfigOptionVectorBase *vec = static_cast<const ConfigOptionVectorBase*>(opt); if (vec->empty()) - boost::throw_exception(qi::expectation_failure<Iterator>( - opt_key.begin(), opt_key.end(), spirit::info("Indexing an empty vector variable"))); + ctx->throw_exception("Indexing an empty vector variable", opt_key); output = vec->vserialize()[(idx >= vec->size()) ? 0 : idx]; } } @@ -484,23 +481,18 @@ namespace client opt = ctx->resolve_symbol(opt_key_str); } if (! opt->is_vector()) - boost::throw_exception(qi::expectation_failure<Iterator>( - opt_key.begin(), opt_key.end(), spirit::info("Trying to index a scalar variable"))); + ctx->throw_exception("Trying to index a scalar variable", opt_key); const ConfigOptionVectorBase *vec = static_cast<const ConfigOptionVectorBase*>(opt); if (vec->empty()) - boost::throw_exception(qi::expectation_failure<Iterator>( - opt_key.begin(), opt_key.end(), spirit::info("Indexing an empty vector variable"))); + ctx->throw_exception("Indexing an empty vector variable", boost::iterator_range<Iterator>(opt_key.begin(), opt_key.end())); const ConfigOption *opt_index = ctx->resolve_symbol(std::string(opt_vector_index.begin(), opt_vector_index.end())); if (opt_index == nullptr) - boost::throw_exception(qi::expectation_failure<Iterator>( - opt_key.begin(), opt_key.end(), spirit::info("Variable does not exist"))); + ctx->throw_exception("Variable does not exist", opt_key); if (opt_index->type() != coInt) - boost::throw_exception(qi::expectation_failure<Iterator>( - opt_key.begin(), opt_key.end(), spirit::info("Indexing variable has to be integer"))); + ctx->throw_exception("Indexing variable has to be integer", opt_key); int idx = opt_index->getInt(); if (idx < 0) - boost::throw_exception(qi::expectation_failure<Iterator>( - opt_key.begin(), opt_key.end(), spirit::info("Negative vector index"))); + ctx->throw_exception("Negative vector index", opt_key); output = vec->vserialize()[(idx >= (int)vec->size()) ? 0 : idx]; } @@ -512,8 +504,7 @@ namespace client { const ConfigOption *opt = ctx->resolve_symbol(std::string(opt_key.begin(), opt_key.end())); if (opt == nullptr) - boost::throw_exception(qi::expectation_failure<Iterator>( - opt_key.begin(), opt_key.end(), spirit::info("Not a variable name"))); + ctx->throw_exception("Not a variable name", opt_key); output.opt = opt; output.it_range = opt_key; } @@ -525,8 +516,7 @@ namespace client expr<Iterator> &output) { if (opt.opt->is_vector()) - boost::throw_exception(qi::expectation_failure<Iterator>( - opt.it_range.begin(), opt.it_range.end(), spirit::info("Referencing a scalar variable in a vector context"))); + ctx->throw_exception("Referencing a scalar variable in a vector context", opt.it_range); switch (opt.opt->type()) { case coFloat: output.set_d(opt.opt->getFloat()); break; case coInt: output.set_i(opt.opt->getInt()); break; @@ -535,11 +525,9 @@ namespace client case coPoint: output.set_s(opt.opt->serialize()); break; case coBool: output.set_b(opt.opt->getBool()); break; case coFloatOrPercent: - boost::throw_exception(qi::expectation_failure<Iterator>( - opt.it_range.begin(), opt.it_range.end(), spirit::info("FloatOrPercent variables are not supported"))); + ctx->throw_exception("FloatOrPercent variables are not supported", opt.it_range); default: - boost::throw_exception(qi::expectation_failure<Iterator>( - opt.it_range.begin(), opt.it_range.end(), spirit::info("Unknown scalar variable type"))); + ctx->throw_exception("Unknown scalar variable type", opt.it_range); } output.it_range = opt.it_range; } @@ -553,12 +541,10 @@ namespace client expr<Iterator> &output) { if (opt.opt->is_scalar()) - boost::throw_exception(qi::expectation_failure<Iterator>( - opt.it_range.begin(), opt.it_range.end(), spirit::info("Referencing a vector variable in a scalar context"))); + ctx->throw_exception("Referencing a vector variable in a scalar context", opt.it_range); const ConfigOptionVectorBase *vec = static_cast<const ConfigOptionVectorBase*>(opt.opt); if (vec->empty()) - boost::throw_exception(qi::expectation_failure<Iterator>( - opt.it_range.begin(), opt.it_range.end(), spirit::info("Indexing an empty vector variable"))); + ctx->throw_exception("Indexing an empty vector variable", opt.it_range); size_t idx = (index < 0) ? 0 : (index >= int(vec->size())) ? 0 : size_t(index); switch (opt.opt->type()) { case coFloats: output.set_d(static_cast<const ConfigOptionFloats *>(opt.opt)->values[idx]); break; @@ -568,8 +554,7 @@ namespace client case coPoints: output.set_s(static_cast<const ConfigOptionPoints *>(opt.opt)->values[idx].dump_perl()); break; case coBools: output.set_b(static_cast<const ConfigOptionBools *>(opt.opt)->values[idx] != 0); break; default: - boost::throw_exception(qi::expectation_failure<Iterator>( - opt.it_range.begin(), opt.it_range.end(), spirit::info("Unknown vector variable type"))); + ctx->throw_exception("Unknown vector variable type", opt.it_range); } output.it_range = boost::iterator_range<Iterator>(opt.it_range.begin(), it_end); } @@ -579,10 +564,54 @@ namespace client template <typename Iterator> static void evaluate_index(expr<Iterator> &expr_index, int &output) { - if (expr_index.type != expr<Iterator>::TYPE_INT) + if (expr_index.type != expr<Iterator>::TYPE_INT) expr_index.throw_exception("Non-integer index is not allowed to address a vector variable."); output = expr_index.i(); } + + template <typename Iterator> + static void throw_exception(const std::string &msg, const boost::iterator_range<Iterator> &it_range) + { + // An asterix is added to the start of the string to differentiate the boost::spirit::info::tag content + // between the grammer terminal / non-terminal symbol name and a free-form error message. + boost::throw_exception(qi::expectation_failure<Iterator>(it_range.begin(), it_range.end(), spirit::info(std::string("*") + msg))); + } + + template <typename Iterator> + static void process_error_message(const MyContext *context, const boost::spirit::info &info, const Iterator &it_begin, const Iterator &it_end) + { + struct expectation_printer + { + expectation_printer(std::string &msg) : result(msg) {} + std::string &result; + void element(std::string const& tag, std::string const& value, int depth) + { + // Indent to depth. + for (int i = 0; i < depth * 4; ++ i) + result += ' '; + if (tag.empty() || tag.front() != '*') { + if (depth == 0) + this->result += "Expecting "; + this->result += "tag: "; + this->result += tag; + } else + this->result += tag.substr(1); + if (! value.empty()) { + this->result += ", value: "; + this->result += value; + } + this->result += '\n'; + } + }; + + std::string &msg = const_cast<MyContext*>(context)->error_message; + msg += "Error! "; + expectation_printer ep(msg); + spirit::basic_info_walker<expectation_printer> walker(ep, info.tag, 0); + boost::apply_visitor(walker, info.value); + msg += " got: \""; + msg += std::string(it_begin, it_end) + "\"\n"; + } }; // For debugging the boost::spirit parsers. Print out the string enclosed in it_range. @@ -651,7 +680,8 @@ namespace client first = it; return true; err: - boost::throw_exception(qi::expectation_failure<Iterator>(first, last, spirit::info("Invalid utf8 sequence"))); + MyContext::throw_exception("Invalid utf8 sequence", boost::iterator_range<Iterator>(first, last)); + return false; } // This function is called during error handling to create a human readable string for the error context. @@ -692,6 +722,8 @@ namespace client qi::_val_type _val; qi::_1_type _1; qi::_2_type _2; + qi::_3_type _3; + qi::_4_type _4; qi::_a_type _a; qi::_b_type _b; qi::_r1_type _r1; @@ -701,6 +733,7 @@ namespace client // Without it, some of the errors would not trigger the error handler. start = eps > text_block(_r1); start.name("start"); + qi::on_error<qi::fail>(start, px::bind(&MyContext::process_error_message<Iterator>, _r1, _4, _3, _2)); text_block = *( text [_val+=_1] @@ -831,16 +864,6 @@ namespace client variable_reference = identifier [ px::bind(&MyContext::resolve_variable<Iterator>, _r1, _1, _val) ]; variable_reference.name("variable reference"); -/* - qi::on_error<qi::fail>(start, - phx::ref(std::cout) - << "Error! Expecting " - << qi::_4 - << " here: '" - << px::construct<std::string>(qi::_3, qi::_2) - << "'\n" - ); -*/ keywords.add ("and") @@ -907,69 +930,30 @@ namespace client }; } -struct printer -{ - typedef spirit::utf8_string string; - - void element(string const& tag, string const& value, int depth) const - { - for (int i = 0; i < (depth*4); ++i) // indent to depth - std::cout << ' '; - std::cout << "tag: " << tag; - if (value != "") - std::cout << ", value: " << value; - std::cout << std::endl; - } -}; - -void print_info(spirit::info const& what) -{ - using spirit::basic_info_walker; - printer pr; - basic_info_walker<printer> walker(pr, what.tag, 0); - boost::apply_visitor(walker, what.value); -} - std::string PlaceholderParser::process(const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override) const { typedef std::string::const_iterator iterator_type; typedef client::calculator<iterator_type> calculator; - spirit::ascii::space_type space; // Our skipper - calculator calc; // Our grammar - + // Our whitespace skipper. + spirit::ascii::space_type space; + // Our grammar. + calculator calc; + // Iterators over the source template. std::string::const_iterator iter = templ.begin(); - std::string::const_iterator end = templ.end(); - //std::string result; - std::string result; - bool r = false; - try { - client::MyContext context; - context.pp = this; - context.config_override = config_override; - r = phrase_parse(iter, end, calc(&context), space, result); - } catch (qi::expectation_failure<iterator_type> const& x) { - std::cout << "expected: "; print_info(x.what_); - std::cout << "got: \"" << std::string(x.first, x.last) << '"' << std::endl; + std::string::const_iterator end = templ.end(); + // Accumulator for the processed template. + std::string output; + client::MyContext context; + context.pp = this; + context.config_override = config_override; + bool res = phrase_parse(iter, end, calc(&context), space, output); + if (! context.error_message.empty()) { + if (context.error_message.back() != '\n' && context.error_message.back() != '\r') + context.error_message += '\n'; + throw std::runtime_error(context.error_message); } - - if (r && iter == end) - { -// std::cout << "-------------------------\n"; -// std::cout << "Parsing succeeded\n"; -// std::cout << "result = " << result << std::endl; -// std::cout << "-------------------------\n"; - } - else - { - std::string rest(iter, end); - std::cout << "-------------------------\n"; - std::cout << "Parsing failed\n"; - std::cout << "stopped at: \" " << rest << "\"\n"; - std::cout << "source: \n" << templ; - std::cout << "-------------------------\n"; - } - return result; + return output; } } diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index c25e5de7d..190fbfe6e 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -1075,7 +1075,11 @@ void Print::_make_wipe_tower() std::string Print::output_filename() { this->placeholder_parser.update_timestamp(); - return this->placeholder_parser.process(this->config.output_filename_format.value, 0); + try { + return this->placeholder_parser.process(this->config.output_filename_format.value, 0); + } catch (std::runtime_error &err) { + throw std::runtime_error(std::string("Failed processing of the output_filename_format template.\n") + err.what()); + } } std::string Print::output_filepath(const std::string &path) diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp index 92341394f..9e6df85dc 100644 --- a/xs/xsp/GCode.xsp +++ b/xs/xsp/GCode.xsp @@ -17,7 +17,14 @@ %name{Slic3r::GCode} class GCode { GCode(); ~GCode(); - std::string do_export(Print *print, const char *path); + void do_export(Print *print, const char *path) + %code%{ + try { + THIS->do_export(print, path); + } catch (std::exception& e) { + croak(e.what()); + } + %}; Ref<Pointf> origin() %code{% RETVAL = &(THIS->origin()); %}; diff --git a/xs/xsp/PlaceholderParser.xsp b/xs/xsp/PlaceholderParser.xsp index 08630f9d5..ab06450f9 100644 --- a/xs/xsp/PlaceholderParser.xsp +++ b/xs/xsp/PlaceholderParser.xsp @@ -14,5 +14,11 @@ %code%{ THIS->apply_config(*config); %}; void set(std::string key, int value); std::string process(std::string str) const - %code%{ RETVAL = THIS->process(str, 0); %}; + %code%{ + try { + RETVAL = THIS->process(str, 0); + } catch (std::exception& e) { + croak(e.what()); + } + %}; }; diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index bdf7b8991..1148fd4aa 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -206,8 +206,14 @@ _constant() double max_allowed_layer_height() const; bool has_support_material() const; void auto_assign_extruders(ModelObject* model_object); - std::string output_filename(); - std::string output_filepath(std::string path = ""); + std::string output_filepath(std::string path = "") + %code%{ + try { + RETVAL = THIS->output_filepath(path); + } catch (std::exception& e) { + croak(e.what()); + } + %}; void add_model_object(ModelObject* model_object, int idx = -1); bool apply_config(DynamicPrintConfig* config)