Merge branch 'master' into wipe_tower_improvements

This commit is contained in:
Lukas Matena
2017-12-21 13:47:33 +01:00
126 changed files with 5240 additions and 607 deletions

View File

@@ -243,7 +243,7 @@ bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, con
for (const auto &opt : def->options) {
for (const t_config_option_key &opt_key2 : opt.second.aliases) {
if (opt_key2 == opt_key) {
opt_key = opt_key2;
opt_key = opt.first;
optdef = &opt.second;
break;
}

View File

@@ -30,6 +30,13 @@
#include <assert.h>
namespace Slic3r {
// Only add a newline in case the current G-code does not end with a newline.
static inline void check_add_eol(std::string &gcode)
{
if (! gcode.empty() && gcode.back() != '\n')
gcode += '\n';
}
// Plan a travel move while minimizing the number of perimeter crossings.
// point is in unscaled coordinates, in the coordinate system of the current active object
@@ -157,6 +164,8 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T
{
std::string gcode;
// Disable linear advance for the wipe tower operations.
gcode += "M900 K0\n";
// Move over the wipe tower.
// Retract for a tool change, using the toolchange retract value and setting the priming extra length.
gcode += gcodegen.retract(true);
@@ -173,6 +182,15 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T
// Let the m_writer know the current extruder_id, but ignore the generated G-code.
if (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id))
gcodegen.writer().toolchange(new_extruder_id);
// Always append the filament start G-code even if the extruder did not switch,
// because the wipe tower resets the linear advance and we want it to be re-enabled.
const std::string &start_filament_gcode = gcodegen.config().start_filament_gcode.get_at(new_extruder_id);
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", start_filament_gcode, new_extruder_id);
check_add_eol(gcode);
}
// A phony move to the end position at the wipe tower.
gcodegen.writer().travel_to_xy(Pointf(tcr.end_pos.x, tcr.end_pos.y));
gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, tcr.end_pos));
@@ -199,15 +217,18 @@ std::string WipeTowerIntegration::prime(GCode &gcodegen)
std::string gcode;
if (&m_priming != nullptr && ! m_priming.extrusions.empty()) {
// Disable linear advance for the wipe tower operations.
gcode += "M900 K0\n";
// Let the tool change be executed by the wipe tower class.
// Inform the G-code writer about the changes done behind its back.
gcode += m_priming.gcode;
// Let the m_writer know the current extruder_id, but ignore the generated G-code.
gcodegen.writer().toolchange(m_priming.extrusions.back().tool);
unsigned int current_extruder_id = m_priming.extrusions.back().tool;
gcodegen.writer().toolchange(current_extruder_id);
gcodegen.placeholder_parser().set("current_extruder", current_extruder_id);
// A phony move to the end position at the wipe tower.
gcodegen.writer().travel_to_xy(Pointf(m_priming.end_pos.x, m_priming.end_pos.y));
gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, m_priming.end_pos));
// Prepare a future wipe.
gcodegen.m_wipe.path.points.clear();
// Start the wipe at the current position.
@@ -220,19 +241,6 @@ std::string WipeTowerIntegration::prime(GCode &gcodegen)
return gcode;
}
std::string WipeTowerIntegration::prime_single_color_print(const Print & /* print */, unsigned int initial_tool, GCode & /* gcodegen */)
{
std::string gcode = "\
G1 Z0.250 F7200.000\n\
G1 X50.0 E80.0 F1000.0\n\
G1 X160.0 E20.0 F1000.0\n\
G1 Z0.200 F7200.000\n\
G1 X220.0 E13 F1000.0\n\
G1 X240.0 E0 F1000.0\n\
G1 E-4 F1000.0\n";
return gcode;
}
std::string WipeTowerIntegration::tool_change(GCode &gcodegen, int extruder_id, bool finish_layer)
{
std::string gcode;
@@ -354,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);
@@ -364,23 +372,36 @@ 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);
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)
this->m_placeholder_parser_failed_templates.clear();
this->_do_export(*print, file);
fflush(file);
if (ferror(file)) {
fclose(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");
}
fclose(file);
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.
@@ -480,23 +501,21 @@ bool GCode::_do_export(Print &print, FILE *file)
fprintf(file, "\n");
}
// Write some terse information on the slicing parameters.
{
const PrintObject *first_object = print.objects.front();
const double layer_height = first_object->config.layer_height.value;
const double first_layer_height = first_object->config.first_layer_height.get_abs_value(layer_height);
for (size_t region_id = 0; region_id < print.regions.size(); ++ region_id) {
auto region = print.regions[region_id];
fprintf(file, "; external perimeters extrusion width = %.2fmm\n", region->flow(frExternalPerimeter, layer_height, false, false, -1., *first_object).width);
fprintf(file, "; perimeters extrusion width = %.2fmm\n", region->flow(frPerimeter, layer_height, false, false, -1., *first_object).width);
fprintf(file, "; infill extrusion width = %.2fmm\n", region->flow(frInfill, layer_height, false, false, -1., *first_object).width);
fprintf(file, "; solid infill extrusion width = %.2fmm\n", region->flow(frSolidInfill, layer_height, false, false, -1., *first_object).width);
fprintf(file, "; top infill extrusion width = %.2fmm\n", region->flow(frTopSolidInfill, layer_height, false, false, -1., *first_object).width);
if (print.has_support_material())
fprintf(file, "; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width);
if (print.config.first_layer_extrusion_width.value > 0)
fprintf(file, "; first layer extrusion width = %.2fmm\n", region->flow(frPerimeter, first_layer_height, false, true, -1., *first_object).width);
fprintf(file, "\n");
}
const PrintObject *first_object = print.objects.front();
const double layer_height = first_object->config.layer_height.value;
const double first_layer_height = first_object->config.first_layer_height.get_abs_value(layer_height);
for (size_t region_id = 0; region_id < print.regions.size(); ++ region_id) {
auto region = print.regions[region_id];
fprintf(file, "; external perimeters extrusion width = %.2fmm\n", region->flow(frExternalPerimeter, layer_height, false, false, -1., *first_object).width);
fprintf(file, "; perimeters extrusion width = %.2fmm\n", region->flow(frPerimeter, layer_height, false, false, -1., *first_object).width);
fprintf(file, "; infill extrusion width = %.2fmm\n", region->flow(frInfill, layer_height, false, false, -1., *first_object).width);
fprintf(file, "; solid infill extrusion width = %.2fmm\n", region->flow(frSolidInfill, layer_height, false, false, -1., *first_object).width);
fprintf(file, "; top infill extrusion width = %.2fmm\n", region->flow(frTopSolidInfill, layer_height, false, false, -1., *first_object).width);
if (print.has_support_material())
fprintf(file, "; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width);
if (print.config.first_layer_extrusion_width.value > 0)
fprintf(file, "; first layer extrusion width = %.2fmm\n", region->flow(frPerimeter, first_layer_height, false, true, -1., *first_object).width);
fprintf(file, "\n");
}
// Prepare the helper object for replacing placeholders in custom G-code and output filename.
@@ -509,6 +528,7 @@ bool GCode::_do_export(Print &print, FILE *file)
unsigned int initial_extruder_id = (unsigned int)-1;
unsigned int final_extruder_id = (unsigned int)-1;
size_t initial_print_object_id = 0;
bool has_wipe_tower = false;
if (print.config.complete_objects.value) {
// Find the 1st printing object, find its tool ordering and the initial extruder ID.
for (; initial_print_object_id < print.objects.size(); ++initial_print_object_id) {
@@ -523,6 +543,7 @@ bool GCode::_do_export(Print &print, FILE *file)
ToolOrdering(print, initial_extruder_id) :
print.m_tool_ordering;
initial_extruder_id = tool_ordering.first_extruder();
has_wipe_tower = print.has_wipe_tower() && tool_ordering.has_wipe_tower();
}
if (initial_extruder_id == (unsigned int)-1) {
// Nothing to print!
@@ -545,7 +566,9 @@ bool GCode::_do_export(Print &print, FILE *file)
m_placeholder_parser.set("current_extruder", initial_extruder_id);
// Useful for sequential prints.
m_placeholder_parser.set("current_object_idx", 0);
std::string start_gcode = m_placeholder_parser.process(print.config.start_gcode.value, initial_extruder_id);
// 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 = 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);
@@ -554,8 +577,17 @@ bool GCode::_do_export(Print &print, FILE *file)
// Write the custom start G-code
writeln(file, start_gcode);
// Process filament-specific gcode in extruder order.
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())));
if (print.config.single_extruder_multi_material) {
if (has_wipe_tower) {
// Wipe tower will control the extruder switching, it will call the start_filament_gcode.
} else {
// Only initialize the initial extruder.
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, 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);
// Set other general things.
@@ -647,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);
@@ -682,32 +714,30 @@ bool GCode::_do_export(Print &print, FILE *file)
// All extrusion moves with the same top layer height are extruded uninterrupted.
std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> layers_to_print = collect_layers_to_print(print);
// Prusa Multi-Material wipe tower.
if (print.has_wipe_tower() && ! layers_to_print.empty()) {
if (tool_ordering.has_wipe_tower()) {
m_wipe_tower.reset(new WipeTowerIntegration(print.config, *print.m_wipe_tower_priming.get(), print.m_wipe_tower_tool_changes, *print.m_wipe_tower_final_purge.get()));
write(file, m_wipe_tower->prime(*this));
// Verify, whether the print overaps the priming extrusions.
BoundingBoxf bbox_print(get_print_extrusions_extents(print));
coordf_t twolayers_printz = ((layers_to_print.size() == 1) ? layers_to_print.front() : layers_to_print[1]).first + EPSILON;
for (const PrintObject *print_object : print.objects)
bbox_print.merge(get_print_object_extrusions_extents(*print_object, twolayers_printz));
bbox_print.merge(get_wipe_tower_extrusions_extents(print, twolayers_printz));
BoundingBoxf bbox_prime(get_wipe_tower_priming_extrusions_extents(print));
bbox_prime.offset(0.5f);
// Beep for 500ms, tone 800Hz. Yet better, play some Morse.
write(file, this->retract());
fprintf(file, "M300 S800 P500\n");
if (bbox_prime.overlap(bbox_print)) {
// Wait for the user to remove the priming extrusions, otherwise they would
// get covered by the print.
fprintf(file, "M1 Remove priming towers and click button.\n");
} else {
// Just wait for a bit to let the user check, that the priming succeeded.
//TODO Add a message explaining what the printer is waiting for. This needs a firmware fix.
fprintf(file, "M1 S10\n");
}
} else
write(file, WipeTowerIntegration::prime_single_color_print(print, initial_extruder_id, *this));
if (has_wipe_tower && ! layers_to_print.empty()) {
m_wipe_tower.reset(new WipeTowerIntegration(print.config, *print.m_wipe_tower_priming.get(), print.m_wipe_tower_tool_changes, *print.m_wipe_tower_final_purge.get()));
write(file, m_writer.travel_to_z(first_layer_height + m_config.z_offset.value, "Move to the first layer height"));
write(file, m_wipe_tower->prime(*this));
// Verify, whether the print overaps the priming extrusions.
BoundingBoxf bbox_print(get_print_extrusions_extents(print));
coordf_t twolayers_printz = ((layers_to_print.size() == 1) ? layers_to_print.front() : layers_to_print[1]).first + EPSILON;
for (const PrintObject *print_object : print.objects)
bbox_print.merge(get_print_object_extrusions_extents(*print_object, twolayers_printz));
bbox_print.merge(get_wipe_tower_extrusions_extents(print, twolayers_printz));
BoundingBoxf bbox_prime(get_wipe_tower_priming_extrusions_extents(print));
bbox_prime.offset(0.5f);
// Beep for 500ms, tone 800Hz. Yet better, play some Morse.
write(file, this->retract());
fprintf(file, "M300 S800 P500\n");
if (bbox_prime.overlap(bbox_print)) {
// Wait for the user to remove the priming extrusions, otherwise they would
// get covered by the print.
fprintf(file, "M1 Remove priming towers and click button.\n");
} else {
// Just wait for a bit to let the user check, that the priming succeeded.
//TODO Add a message explaining what the printer is waiting for. This needs a firmware fix.
fprintf(file, "M1 S10\n");
}
}
// Extrude the layers.
for (auto &layer : layers_to_print) {
@@ -727,9 +757,14 @@ bool GCode::_do_export(Print &print, FILE *file)
write(file, this->retract());
write(file, m_writer.set_fan(false));
// Process filament-specific gcode in extruder order.
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, m_placeholder_parser.process(print.config.end_gcode, m_writer.extruder()->id()));
if (print.config.single_extruder_multi_material) {
// Process the end_filament_gcode for the active filament only.
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, this->placeholder_parser_process("end_gcode", end_gcode, (unsigned int)(&end_gcode - &print.config.end_filament_gcode.values.front())));
}
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());
@@ -766,11 +801,25 @@ bool GCode::_do_export(Print &print, FILE *file)
for (size_t i = 0; i < sizeof(configs) / sizeof(configs[0]); ++ i) {
StaticPrintConfig *cfg = configs[i];
for (const std::string &key : cfg->keys())
fprintf(file, "; %s = %s\n", key.c_str(), cfg->serialize(key).c_str());
if (key != "compatible_printers")
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("\n!!!!! 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\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.
@@ -838,12 +887,12 @@ void GCode::_print_first_layer_bed_temperature(FILE *file, Print &print, const s
// Is the bed temperature set by the provided custom G-code?
int temp_by_gcode = -1;
bool temp_set_by_gcode = custom_gcode_sets_temperature(gcode, 140, 190, temp_by_gcode);
if (temp_by_gcode >= 0 && temp_by_gcode < 1000)
if (temp_set_by_gcode && temp_by_gcode >= 0 && temp_by_gcode < 1000)
temp = temp_by_gcode;
// Always call m_writer.set_bed_temperature() so it will set the internal "current" state of the bed temp as if
// the custom start G-code emited these.
std::string set_temp_gcode = m_writer.set_bed_temperature(temp, wait);
if (! temp_by_gcode)
if (! temp_set_by_gcode)
write(file, set_temp_gcode);
}
@@ -973,7 +1022,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";
}
@@ -983,7 +1032,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";
}
@@ -2162,13 +2211,14 @@ GCode::retract(bool toolchange)
std::string GCode::set_extruder(unsigned int extruder_id)
{
m_placeholder_parser.set("current_extruder", extruder_id);
if (!m_writer.need_toolchange(extruder_id))
return "";
// if we are running a single-extruder setup, just set the extruder and return nothing
if (!m_writer.multiple_extruders)
if (!m_writer.multiple_extruders) {
m_placeholder_parser.set("current_extruder", extruder_id);
return m_writer.toolchange(extruder_id);
}
// prepend retraction on the current extruder
std::string gcode = this->retract(true);
@@ -2176,23 +2226,41 @@ std::string GCode::set_extruder(unsigned int extruder_id)
// Always reset the extrusion path, even if the tool change retract is set to zero.
m_wipe.reset_path();
// append custom toolchange G-code
if (m_writer.extruder() != nullptr && !m_config.toolchange_gcode.value.empty()) {
if (m_writer.extruder() != nullptr) {
// Process the custom end_filament_gcode in case of single_extruder_multi_material.
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 += placeholder_parser_process("end_filament_gcode", end_filament_gcode, old_extruder_id);
check_add_eol(gcode);
}
}
m_placeholder_parser.set("current_extruder", extruder_id);
if (m_writer.extruder() != nullptr && ! m_config.toolchange_gcode.value.empty()) {
// Process the custom toolchange_gcode.
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)
+ '\n';
gcode += placeholder_parser_process("toolchange_gcode", m_config.toolchange_gcode.value, extruder_id, &config);
check_add_eol(gcode);
}
// if ooze prevention is enabled, park current extruder in the nearest
// standby point and set it to the standby temperature
// If ooze prevention is enabled, park current extruder in the nearest
// standby point and set it to the standby temperature.
if (m_ooze_prevention.enable && m_writer.extruder() != nullptr)
gcode += m_ooze_prevention.pre_toolchange(*this);
// append the toolchange command
// Append the toolchange command.
gcode += m_writer.toolchange(extruder_id);
// set the new extruder to the operating temperature
// Append the filament start G-code for single_extruder_multi_material.
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 += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id);
check_add_eol(gcode);
}
// Set the new extruder to the operating temperature.
if (m_ooze_prevention.enable)
gcode += m_ooze_prevention.post_toolchange(*this);

View File

@@ -90,7 +90,6 @@ public:
m_brim_done(false) {}
std::string prime(GCode &gcodegen);
static std::string prime_single_color_print(const Print & /* print */, unsigned int initial_tool, GCode & /* gcodegen */);
void next_layer() { ++ m_layer_idx; m_tool_change_idx = 0; }
std::string tool_change(GCode &gcodegen, int extruder_id, bool finish_layer);
std::string finalize(GCode &gcodegen);
@@ -131,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 +143,10 @@ public:
const FullPrintConfig &config() const { return m_config; }
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;

View File

@@ -1,7 +1,16 @@
#include "Print.hpp"
#include "ToolOrdering.hpp"
#include <assert.h>
// #define SLIC3R_DEBUG
// Make assert active if SLIC3R_DEBUG
#ifdef SLIC3R_DEBUG
#define DEBUG
#define _DEBUG
#undef NDEBUG
#endif
#include <cassert>
#include <limits>
namespace Slic3r {
@@ -256,12 +265,19 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_
// Insert one additional wipe tower layer between lh.print_z and lt_object.print_z.
LayerTools lt_new(0.5f * (lt.print_z + lt_object.print_z));
// Find the 1st layer above lt_new.
for (j = i + 1; j < m_layer_tools.size() && m_layer_tools[j].print_z < lt_new.print_z; ++ j);
LayerTools &lt_extra = (m_layer_tools[j].print_z == lt_new.print_z) ?
m_layer_tools[j] :
*m_layer_tools.insert(m_layer_tools.begin() + j, lt_new);
lt_extra.has_wipe_tower = true;
lt_extra.wipe_tower_partitions = lt_object.wipe_tower_partitions;
for (j = i + 1; j < m_layer_tools.size() && m_layer_tools[j].print_z < lt_new.print_z - EPSILON; ++ j);
if (std::abs(m_layer_tools[j].print_z - lt_new.print_z) < EPSILON) {
m_layer_tools[j].has_wipe_tower = true;
} else {
LayerTools &lt_extra = *m_layer_tools.insert(m_layer_tools.begin() + j, lt_new);
LayerTools &lt_prev = m_layer_tools[j - 1];
LayerTools &lt_next = m_layer_tools[j + 1];
assert(! lt_prev.extruders.empty() && ! lt_next.extruders.empty());
assert(lt_prev.extruders.back() == lt_next.extruders.front());
lt_extra.has_wipe_tower = true;
lt_extra.extruders.push_back(lt_next.extruders.front());
lt_extra.wipe_tower_partitions = lt_next.wipe_tower_partitions;
}
}
}
break;

View File

@@ -53,14 +53,14 @@ public:
void clear() { m_layer_tools.clear(); }
// Get the first extruder printing the layer_tools, returns -1 if there is no layer printed.
// Get the first extruder printing, including the extruder priming areas, returns -1 if there is no layer printed.
unsigned int first_extruder() const { return m_first_printing_extruder; }
// Get the first extruder printing the layer_tools, returns -1 if there is no layer printed.
unsigned int last_extruder() const { return m_last_printing_extruder; }
// For a multi-material print, the printing extruders are ordered in the order they shall be primed.
std::vector<unsigned int> all_extruders() const { return m_all_printing_extruders; }
const std::vector<unsigned int>& all_extruders() const { return m_all_printing_extruders; }
// Find LayerTools with the closest print_z.
LayerTools& tools_for_layer(coordf_t print_z);
@@ -69,6 +69,8 @@ public:
const LayerTools& front() const { return m_layer_tools.front(); }
const LayerTools& back() const { return m_layer_tools.back(); }
std::vector<LayerTools>::const_iterator begin() const { return m_layer_tools.begin(); }
std::vector<LayerTools>::const_iterator end() const { return m_layer_tools.end(); }
bool empty() const { return m_layer_tools.empty(); }
const std::vector<LayerTools>& layer_tools() const { return m_layer_tools; }
bool has_wipe_tower() const { return ! m_layer_tools.empty() && m_first_printing_extruder != (unsigned int)-1 && m_layer_tools.front().wipe_tower_partitions > 0; }

View File

@@ -122,7 +122,7 @@ public:
// print_z of the first layer.
float first_layer_height,
// Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object.
std::vector<unsigned int> tools,
const std::vector<unsigned int> &tools,
// If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower.
// If false, the last priming are will be large enough to wipe the last extruder sufficiently.
bool last_wipe_inside_wipe_tower,

View File

@@ -389,7 +389,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime(
// print_z of the first layer.
float first_layer_height,
// Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object.
std::vector<unsigned int> tools,
const std::vector<unsigned int> &tools,
// If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower.
// If false, the last priming are will be large enough to wipe the last extruder sufficiently.
bool last_wipe_inside_wipe_tower,
@@ -615,7 +615,8 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo
.extrude(box.ld, 3200).extrude(box.rd)
.extrude(box.ru).extrude(box.lu);
// Wipe the nozzle.
if (purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE)
//if (purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE)
// Always wipe the nozzle with a long wipe to reduce stringing when moving away from the wipe tower.
writer.travel(box.ru, 7200)
.travel(box.lu);
} else
@@ -723,8 +724,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(Purpose purpose, b
// Move to the front left corner.
writer.travel(wipeTower_box.ld, 7000);
if (purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE)
//if (purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE)
// Wipe along the front edge.
// Always wipe the nozzle with a long wipe to reduce stringing when moving away from the wipe tower.
writer.travel(wipeTower_box.rd)
.travel(wipeTower_box.ld);
@@ -1083,8 +1085,10 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer(Purpose purpose)
.extrude(fill_box.ru + xy(-m_perimeter_width, -m_perimeter_width));
}
if (purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE)
// if (purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE)
if (true)
// Wipe along the front side of the current wiping box.
// Always wipe the nozzle with a long wipe to reduce stringing when moving away from the wipe tower.
writer.travel(fill_box.ld + xy( m_perimeter_width, m_perimeter_width / 2), 7200)
.travel(fill_box.rd + xy(- m_perimeter_width, m_perimeter_width / 2));
else

View File

@@ -151,7 +151,7 @@ public:
// print_z of the first layer.
float first_layer_height,
// Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object.
std::vector<unsigned int> tools,
const std::vector<unsigned int> &tools,
// If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower.
// If false, the last priming are will be large enough to wipe the last extruder sufficiently.
bool last_wipe_inside_wipe_tower,

View File

@@ -161,7 +161,9 @@ public:
// Is there any valid extrusion assigned to this LayerRegion?
virtual bool has_extrusions() const { return ! support_fills.empty(); }
protected:
//protected:
// The constructor has been made public to be able to insert additional support layers for the skirt or a wipe tower
// between the raft and the object first layer.
SupportLayer(size_t id, PrintObject *object, coordf_t height, coordf_t print_z, coordf_t slice_z) :
Layer(id, object, height, print_z, slice_z) {}
virtual ~SupportLayer() {}

View File

@@ -50,11 +50,20 @@
#include <iostream>
#include <string>
// #define USE_CPP11_REGEX
#ifdef USE_CPP11_REGEX
#include <regex>
#define SLIC3R_REGEX_NAMESPACE std
#else /* USE_CPP11_REGEX */
#include <boost/regex.hpp>
#define SLIC3R_REGEX_NAMESPACE boost
#endif /* USE_CPP11_REGEX */
namespace Slic3r {
PlaceholderParser::PlaceholderParser()
{
this->set("version", SLIC3R_VERSION);
this->set("version", std::string(SLIC3R_VERSION));
this->apply_env_variables();
this->update_timestamp();
}
@@ -278,22 +287,31 @@ namespace client
expr &operator+=(const expr &rhs)
{
const char *err_msg = "Cannot multiply with non-numeric type.";
this->throw_if_not_numeric(err_msg);
rhs.throw_if_not_numeric(err_msg);
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) {
double d = this->as_d() + rhs.as_d();
this->data.d = d;
this->type = TYPE_DOUBLE;
} else
this->data.i += rhs.i();
if (this->type == TYPE_STRING) {
// Convert the right hand side to string and append.
*this->data.s += rhs.to_string();
} else if (rhs.type == TYPE_STRING) {
// Conver the left hand side to string, append rhs.
this->data.s = new std::string(this->to_string() + rhs.s());
this->type = TYPE_STRING;
} else {
const char *err_msg = "Cannot add non-numeric types.";
this->throw_if_not_numeric(err_msg);
rhs.throw_if_not_numeric(err_msg);
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) {
double d = this->as_d() + rhs.as_d();
this->data.d = d;
this->type = TYPE_DOUBLE;
} else
this->data.i += rhs.i();
}
this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
return *this;
}
expr &operator-=(const expr &rhs)
{
const char *err_msg = "Cannot multiply with non-numeric type.";
const char *err_msg = "Cannot subtract non-numeric types.";
this->throw_if_not_numeric(err_msg);
rhs.throw_if_not_numeric(err_msg);
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) {
@@ -349,30 +367,112 @@ namespace client
out = self.b();
}
static void evaluate_boolean_to_string(expr &self, std::string &out)
{
if (self.type != TYPE_BOOL)
self.throw_exception("Not a boolean expression");
out = self.b() ? "true" : "false";
}
// Is lhs==rhs? Store the result into lhs.
static void compare_op(expr &lhs, expr &rhs, char op)
static void compare_op(expr &lhs, expr &rhs, char op, bool invert)
{
bool value = false;
if ((lhs.type == TYPE_INT || lhs.type == TYPE_DOUBLE) &&
(rhs.type == TYPE_INT || rhs.type == TYPE_DOUBLE)) {
// Both types are numeric.
value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ?
(lhs.as_d() == rhs.as_d()) : (lhs.i() == rhs.i());
switch (op) {
case '=':
value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ?
(std::abs(lhs.as_d() - rhs.as_d()) < 1e-8) : (lhs.i() == rhs.i());
break;
case '<':
value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ?
(lhs.as_d() < rhs.as_d()) : (lhs.i() < rhs.i());
break;
case '>':
default:
value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ?
(lhs.as_d() > rhs.as_d()) : (lhs.i() > rhs.i());
break;
}
} else if (lhs.type == TYPE_BOOL && rhs.type == TYPE_BOOL) {
// Both type are bool.
if (op != '=')
boost::throw_exception(qi::expectation_failure<Iterator>(
lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot compare the types.")));
value = lhs.b() == rhs.b();
} else if (lhs.type == TYPE_STRING || rhs.type == TYPE_STRING) {
// One type is string, the other could be converted to string.
value = lhs.to_string() == rhs.to_string();
value = (op == '=') ? (lhs.to_string() == rhs.to_string()) :
(op == '<') ? (lhs.to_string() < rhs.to_string()) : (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;
lhs.data.b = invert ? ! value : value;
}
static void equal (expr &lhs, expr &rhs) { compare_op(lhs, rhs, '=', false); }
static void not_equal(expr &lhs, expr &rhs) { compare_op(lhs, rhs, '=', true ); }
static void lower (expr &lhs, expr &rhs) { compare_op(lhs, rhs, '<', false); }
static void greater (expr &lhs, expr &rhs) { compare_op(lhs, rhs, '>', false); }
static void leq (expr &lhs, expr &rhs) { compare_op(lhs, rhs, '>', true ); }
static void geq (expr &lhs, expr &rhs) { compare_op(lhs, rhs, '<', true ); }
static void regex_op(expr &lhs, boost::iterator_range<Iterator> &rhs, char op)
{
const std::string *subject = nullptr;
const std::string *mask = nullptr;
if (lhs.type == TYPE_STRING) {
// One type is string, the other could be converted to string.
subject = &lhs.s();
} else {
lhs.throw_exception("Left hand side of a regex match must be a string.");
}
try {
std::string pattern(++ rhs.begin(), -- rhs.end());
bool result = SLIC3R_REGEX_NAMESPACE::regex_match(*subject, SLIC3R_REGEX_NAMESPACE::regex(pattern));
if (op == '!')
result = ! result;
lhs.reset();
lhs.type = TYPE_BOOL;
lhs.data.b = result;
} catch (SLIC3R_REGEX_NAMESPACE::regex_error &ex) {
// Syntax error in the regular expression
boost::throw_exception(qi::expectation_failure<Iterator>(
rhs.begin(), rhs.end(), spirit::info(std::string("*Regular expression compilation failed: ") + ex.what())));
}
}
static void regex_matches (expr &lhs, boost::iterator_range<Iterator> &rhs) { return regex_op(lhs, rhs, '='); }
static void regex_doesnt_match(expr &lhs, boost::iterator_range<Iterator> &rhs) { return regex_op(lhs, rhs, '!'); }
static void logical_op(expr &lhs, expr &rhs, char op)
{
bool value = false;
if (lhs.type == TYPE_BOOL && rhs.type == TYPE_BOOL) {
value = (op == '|') ? (lhs.b() || rhs.b()) : (lhs.b() && rhs.b());
} else {
boost::throw_exception(qi::expectation_failure<Iterator>(
lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot apply logical operation to non-boolean operators.")));
}
lhs.type = TYPE_BOOL;
lhs.data.b = value;
}
static void logical_or (expr &lhs, expr &rhs) { logical_op(lhs, rhs, '|'); }
static void logical_and(expr &lhs, expr &rhs) { logical_op(lhs, rhs, '&'); }
static void ternary_op(expr &lhs, expr &rhs1, expr &rhs2)
{
bool value = false;
if (lhs.type != TYPE_BOOL)
lhs.throw_exception("Not a boolean expression");
if (lhs.b())
lhs = std::move(rhs1);
else
lhs = std::move(rhs2);
}
static void equal(expr &lhs, expr &rhs) { compare_op(lhs, rhs, '='); }
static void not_equal(expr &lhs, expr &rhs) { compare_op(lhs, rhs, '!'); }
static void set_if(bool &cond, bool &not_yet_consumed, std::string &str_in, std::string &str_out)
{
@@ -385,7 +485,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
@@ -412,9 +512,15 @@ namespace client
}
struct MyContext {
const PlaceholderParser *pp = nullptr;
const DynamicConfig *config_override = nullptr;
const size_t current_extruder_id = 0;
const DynamicConfig *config = nullptr;
const DynamicConfig *config_override = nullptr;
size_t current_extruder_id = 0;
// If false, the macro_processor will evaluate a full macro.
// If true, the macro processor will evaluate just a boolean condition using the full expressive power of the macro processor.
bool just_boolean_expression = false;
std::string error_message;
static void evaluate_full_macro(const MyContext *ctx, bool &result) { result = ! ctx->just_boolean_expression; }
const ConfigOption* resolve_symbol(const std::string &opt_key) const
{
@@ -422,7 +528,7 @@ namespace client
if (config_override != nullptr)
opt = config_override->option(opt_key);
if (opt == nullptr)
opt = pp->option(opt_key);
opt = config->option(opt_key);
return opt;
}
@@ -442,26 +548,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];
}
}
@@ -482,23 +584,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];
}
@@ -510,8 +607,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;
}
@@ -523,8 +619,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;
@@ -533,11 +628,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;
}
@@ -551,12 +644,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;
@@ -566,8 +657,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);
}
@@ -577,10 +667,57 @@ 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, const Iterator &it_error)
{
std::string &msg = const_cast<MyContext*>(context)->error_message;
std::string first(it_begin, it_error);
std::string last(it_error, it_end);
auto first_pos = first.rfind('\n');
auto last_pos = last.find('\n');
int line_nr = 1;
if (first_pos == std::string::npos)
first_pos = 0;
else {
// Calculate the current line number.
for (size_t i = 0; i <= first_pos; ++ i)
if (first[i] == '\n')
++ line_nr;
++ first_pos;
}
auto error_line = std::string(first, first_pos) + std::string(last, 0, last_pos);
// Position of the it_error from the start of its line.
auto error_pos = (it_error - it_begin) - first_pos;
msg += "Parsing error at line " + std::to_string(line_nr);
if (! info.tag.empty() && info.tag.front() == '*') {
// The gat contains an explanatory string.
msg += ": ";
msg += info.tag.substr(1);
} else {
// A generic error report based on the nonterminal or terminal symbol name.
msg += ". Expecting tag ";
msg += info.tag;
}
msg += '\n';
msg += error_line;
msg += '\n';
for (size_t i = 0; i < error_pos; ++ i)
msg += ' ';
msg += "^\n";
}
};
// For debugging the boost::spirit parsers. Print out the string enclosed in it_range.
@@ -598,14 +735,77 @@ namespace client
template <typename It, typename Attr> static bool parse_inf(It&, It const&, Attr&) { return false; }
};
// This parser is to be used inside a raw[] directive to accept a single valid UTF-8 character.
// If an invalid UTF-8 sequence is encountered, a qi::expectation_failure is thrown.
struct utf8_char_skipper_parser : qi::primitive_parser<utf8_char_skipper_parser>
{
// Define the attribute type exposed by this parser component
template <typename Context, typename Iterator>
struct attribute
{
typedef wchar_t type;
};
// This function is called during the actual parsing process
template <typename Iterator, typename Context , typename Skipper, typename Attribute>
bool parse(Iterator& first, Iterator const& last, Context& context, Skipper const& skipper, Attribute& attr) const
{
// The skipper shall always be empty, any white space will be accepted.
// skip_over(first, last, skipper);
if (first == last)
return false;
// Iterator over the UTF-8 sequence.
auto it = first;
// Read the first byte of the UTF-8 sequence.
unsigned char c = static_cast<boost::uint8_t>(*it ++);
unsigned int cnt = 0;
// UTF-8 sequence must not start with a continuation character:
if ((c & 0xC0) == 0x80)
goto err;
// Skip high surrogate first if there is one.
// If the most significant bit with a zero in it is in position
// 8-N then there are N bytes in this UTF-8 sequence:
{
unsigned char mask = 0x80u;
unsigned int result = 0;
while (c & mask) {
++ result;
mask >>= 1;
}
cnt = (result == 0) ? 1 : ((result > 4) ? 4 : result);
}
// Since we haven't read in a value, we need to validate the code points:
for (-- cnt; cnt > 0; -- cnt) {
if (it == last)
goto err;
c = static_cast<boost::uint8_t>(*it ++);
// We must have a continuation byte:
if (cnt > 1 && (c & 0xC0) != 0x80)
goto err;
}
first = it;
return true;
err:
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.
template <typename Context>
spirit::info what(Context&) const
{
return spirit::info("unicode_char");
}
};
///////////////////////////////////////////////////////////////////////////
// Our calculator grammar
// Our macro_processor grammar
///////////////////////////////////////////////////////////////////////////
// Inspired by the C grammar rules https://www.lysator.liu.se/c/ANSI-C-grammar-y.html
template <typename Iterator>
struct calculator : qi::grammar<Iterator, std::string(const MyContext*), spirit::ascii::space_type>
struct macro_processor : qi::grammar<Iterator, std::string(const MyContext*), qi::locals<bool>, spirit::ascii::space_type>
{
calculator() : calculator::base_type(start)
macro_processor() : macro_processor::base_type(start)
{
using namespace qi::labels;
qi::alpha_type alpha;
@@ -617,6 +817,7 @@ namespace client
qi::no_skip_type no_skip;
qi::real_parser<double, strict_real_policies_without_nan_inf> strict_double;
spirit::ascii::char_type char_;
utf8_char_skipper_parser utf8char;
spirit::bool_type bool_;
spirit::int_type int_;
spirit::double_type double_;
@@ -627,6 +828,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;
@@ -634,8 +837,15 @@ namespace client
// Starting symbol of the grammer.
// The leading eps is required by the "expectation point" operator ">".
// Without it, some of the errors would not trigger the error handler.
start = eps > text_block(_r1);
// Also the start symbol switches between the "full macro syntax" and a "boolean expression only",
// depending on the context->just_boolean_expression flag. This way a single static expression parser
// could serve both purposes.
start = eps[px::bind(&MyContext::evaluate_full_macro, _r1, _a)] >
( eps(_a==true) > text_block(_r1) [_val=_1]
| conditional_expression(_r1) [ px::bind(&expr<Iterator>::evaluate_boolean_to_string, _1, _val) ]
);
start.name("start");
qi::on_error<qi::fail>(start, px::bind(&MyContext::process_error_message<Iterator>, _r1, _4, _1, _2, _3));
text_block = *(
text [_val+=_1]
@@ -649,7 +859,7 @@ namespace client
// Free-form text up to a first brace, including spaces and newlines.
// The free-form text will be inserted into the processed text without a modification.
text = no_skip[raw[+(char_ - '[' - '{')]];
text = no_skip[raw[+(utf8char - char_('[') - char_('{'))]];
text.name("text");
// New style of macro expansion.
@@ -695,32 +905,54 @@ namespace client
raw[lexeme[(alpha | '_') >> *(alnum | '_')]];
identifier.name("identifier");
bool_expr =
additive_expression(_r1) [_val = _1]
>> *( ("==" > additive_expression(_r1) ) [px::bind(&expr<Iterator>::equal, _val, _1)]
| ("!=" > additive_expression(_r1) ) [px::bind(&expr<Iterator>::not_equal, _val, _1)]
| ("<>" > additive_expression(_r1) ) [px::bind(&expr<Iterator>::not_equal, _val, _1)]
conditional_expression =
logical_or_expression(_r1) [_val = _1]
>> -('?' > conditional_expression(_r1) > ':' > conditional_expression(_r1)) [px::bind(&expr<Iterator>::ternary_op, _val, _1, _2)];
logical_or_expression =
logical_and_expression(_r1) [_val = _1]
>> *( ((kw["or"] | "||") > logical_and_expression(_r1) ) [px::bind(&expr<Iterator>::logical_or, _val, _1)] );
logical_and_expression =
equality_expression(_r1) [_val = _1]
>> *( ((kw["and"] | "&&") > equality_expression(_r1) ) [px::bind(&expr<Iterator>::logical_and, _val, _1)] );
equality_expression =
relational_expression(_r1) [_val = _1]
>> *( ("==" > relational_expression(_r1) ) [px::bind(&expr<Iterator>::equal, _val, _1)]
| ("!=" > relational_expression(_r1) ) [px::bind(&expr<Iterator>::not_equal, _val, _1)]
| ("<>" > relational_expression(_r1) ) [px::bind(&expr<Iterator>::not_equal, _val, _1)]
| ("=~" > regular_expression ) [px::bind(&expr<Iterator>::regex_matches, _val, _1)]
| ("!~" > regular_expression ) [px::bind(&expr<Iterator>::regex_doesnt_match, _val, _1)]
);
bool_expr.name("bool expression");
equality_expression.name("bool expression");
// Evaluate a boolean expression stored as expr into a boolean value.
// Throw if the bool_expr does not produce a expr of boolean type.
bool_expr_eval = bool_expr(_r1) [ px::bind(&expr<Iterator>::evaluate_boolean, _1, _val) ];
// Throw if the equality_expression does not produce a expr of boolean type.
bool_expr_eval = conditional_expression(_r1) [ px::bind(&expr<Iterator>::evaluate_boolean, _1, _val) ];
bool_expr_eval.name("bool_expr_eval");
relational_expression =
additive_expression(_r1) [_val = _1]
>> *( (lit('<') > additive_expression(_r1) ) [px::bind(&expr<Iterator>::lower, _val, _1)]
| (lit('>') > additive_expression(_r1) ) [px::bind(&expr<Iterator>::greater, _val, _1)]
| ("<=" > additive_expression(_r1) ) [px::bind(&expr<Iterator>::leq, _val, _1)]
| (">=" > additive_expression(_r1) ) [px::bind(&expr<Iterator>::geq, _val, _1)]
);
additive_expression =
term(_r1) [_val = _1]
>> *( (lit('+') > term(_r1) ) [_val += _1]
| (lit('-') > term(_r1) ) [_val -= _1]
multiplicative_expression(_r1) [_val = _1]
>> *( (lit('+') > multiplicative_expression(_r1) ) [_val += _1]
| (lit('-') > multiplicative_expression(_r1) ) [_val -= _1]
);
additive_expression.name("additive_expression");
term =
factor(_r1) [_val = _1]
>> *( (lit('*') > factor(_r1) ) [_val *= _1]
| (lit('/') > factor(_r1) ) [_val /= _1]
multiplicative_expression =
unary_expression(_r1) [_val = _1]
>> *( (lit('*') > unary_expression(_r1) ) [_val *= _1]
| (lit('/') > unary_expression(_r1) ) [_val /= _1]
);
term.name("term");
multiplicative_expression.name("multiplicative_expression");
struct FactorActions {
static void set_start_pos(Iterator &start_pos, expr<Iterator> &out)
@@ -740,19 +972,19 @@ namespace client
static void not_(expr<Iterator> &value, expr<Iterator> &out)
{ out = value.unary_not(out.it_range.begin()); }
};
factor = iter_pos[px::bind(&FactorActions::set_start_pos, _1, _val)] >> (
scalar_variable_reference(_r1) [ _val = _1 ]
| (lit('(') > additive_expression(_r1) > ')' > iter_pos) [ px::bind(&FactorActions::expr_, _1, _2, _val) ]
| (lit('-') > factor(_r1) ) [ px::bind(&FactorActions::minus_, _1, _val) ]
| (lit('+') > factor(_r1) > iter_pos) [ px::bind(&FactorActions::expr_, _1, _2, _val) ]
| ((kw["not"] | '!') > factor(_r1) > iter_pos) [ px::bind(&FactorActions::not_, _1, _val) ]
| (strict_double > iter_pos) [ px::bind(&FactorActions::double_, _1, _2, _val) ]
| (int_ > iter_pos) [ px::bind(&FactorActions::int_, _1, _2, _val) ]
| (kw[bool_] > iter_pos) [ px::bind(&FactorActions::bool_, _1, _2, _val) ]
| raw[lexeme['"' > *((char_ - char_('\\') - char_('"')) | ('\\' > char_)) > '"']]
[ px::bind(&FactorActions::string_, _1, _val) ]
unary_expression = iter_pos[px::bind(&FactorActions::set_start_pos, _1, _val)] >> (
scalar_variable_reference(_r1) [ _val = _1 ]
| (lit('(') > conditional_expression(_r1) > ')' > iter_pos) [ px::bind(&FactorActions::expr_, _1, _2, _val) ]
| (lit('-') > unary_expression(_r1) ) [ px::bind(&FactorActions::minus_, _1, _val) ]
| (lit('+') > unary_expression(_r1) > iter_pos) [ px::bind(&FactorActions::expr_, _1, _2, _val) ]
| ((kw["not"] | '!') > unary_expression(_r1) > iter_pos) [ px::bind(&FactorActions::not_, _1, _val) ]
| (strict_double > iter_pos) [ px::bind(&FactorActions::double_, _1, _2, _val) ]
| (int_ > iter_pos) [ px::bind(&FactorActions::int_, _1, _2, _val) ]
| (kw[bool_] > iter_pos) [ px::bind(&FactorActions::bool_, _1, _2, _val) ]
| raw[lexeme['"' > *((utf8char - char_('\\') - char_('"')) | ('\\' > char_)) > '"']]
[ px::bind(&FactorActions::string_, _1, _val) ]
);
factor.name("factor");
unary_expression.name("unary_expression");
scalar_variable_reference =
variable_reference(_r1)[_a=_1] >>
@@ -766,16 +998,9 @@ 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"
);
*/
regular_expression = raw[lexeme['/' > *((utf8char - char_('\\') - char_('/')) | ('\\' > char_)) > '/']];
regular_expression.name("regular_expression");
keywords.add
("and")
@@ -798,18 +1023,26 @@ namespace client
debug(switch_output);
debug(legacy_variable_expansion);
debug(identifier);
debug(bool_expr);
debug(conditional_expression);
debug(logical_or_expression);
debug(logical_and_expression);
debug(equality_expression);
debug(bool_expr_eval);
debug(relational_expression);
debug(additive_expression);
debug(term);
debug(factor);
debug(multiplicative_expression);
debug(unary_expression);
debug(scalar_variable_reference);
debug(variable_reference);
debug(regular_expression);
}
}
// Generic expression over expr<Iterator>.
typedef qi::rule<Iterator, expr<Iterator>(const MyContext*), spirit::ascii::space_type> RuleExpression;
// The start of the grammar.
qi::rule<Iterator, std::string(const MyContext*), spirit::ascii::space_type> start;
qi::rule<Iterator, std::string(const MyContext*), qi::locals<bool>, spirit::ascii::space_type> start;
// A free-form text.
qi::rule<Iterator, std::string(), spirit::ascii::space_type> text;
// A free-form text, possibly empty, possibly containing macro expansions.
@@ -819,17 +1052,27 @@ namespace client
// Legacy variable expansion of the original Slic3r, in the form of [scalar_variable] or [vector_variable_index].
qi::rule<Iterator, std::string(const MyContext*), spirit::ascii::space_type> legacy_variable_expansion;
// Parsed identifier name.
qi::rule<Iterator, boost::iterator_range<Iterator>(), spirit::ascii::space_type> identifier;
// Math expression consisting of +- operators over terms.
qi::rule<Iterator, expr<Iterator>(const MyContext*), spirit::ascii::space_type> additive_expression;
qi::rule<Iterator, boost::iterator_range<Iterator>(), spirit::ascii::space_type> identifier;
// Ternary operator (?:) over logical_or_expression.
RuleExpression conditional_expression;
// Logical or over logical_and_expressions.
RuleExpression logical_or_expression;
// Logical and over relational_expressions.
RuleExpression logical_and_expression;
// <, >, <=, >=
RuleExpression relational_expression;
// Math expression consisting of +- operators over multiplicative_expressions.
RuleExpression additive_expression;
// Boolean expressions over expressions.
qi::rule<Iterator, expr<Iterator>(const MyContext*), spirit::ascii::space_type> bool_expr;
RuleExpression equality_expression;
// Math expression consisting of */ operators over factors.
RuleExpression multiplicative_expression;
// Number literals, functions, braced expressions, variable references, variable indexing references.
RuleExpression unary_expression;
// Rule to capture a regular expression enclosed in //.
qi::rule<Iterator, boost::iterator_range<Iterator>(), spirit::ascii::space_type> regular_expression;
// Evaluate boolean expression into bool.
qi::rule<Iterator, bool(const MyContext*), spirit::ascii::space_type> bool_expr_eval;
// Math expression consisting of */ operators over factors.
qi::rule<Iterator, expr<Iterator>(const MyContext*), spirit::ascii::space_type> term;
// Number literals, functions, braced expressions, variable references, variable indexing references.
qi::rule<Iterator, expr<Iterator>(const MyContext*), spirit::ascii::space_type> factor;
// Reference of a scalar variable, or reference to a field of a vector variable.
qi::rule<Iterator, expr<Iterator>(const MyContext*), qi::locals<OptWithPos<Iterator>, int>, spirit::ascii::space_type> scalar_variable_reference;
// Rule to translate an identifier to a ConfigOption, or to fail.
@@ -842,69 +1085,50 @@ namespace client
};
}
struct printer
static std::string process_macro(const std::string &templ, client::MyContext &context)
{
typedef spirit::utf8_string string;
typedef std::string::const_iterator iterator_type;
typedef client::macro_processor<iterator_type> macro_processor;
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;
// Our whitespace skipper.
spirit::ascii::space_type space;
// Our grammar, statically allocated inside the method, meaning it will be allocated the first time
// PlaceholderParser::process() runs.
//FIXME this kind of initialization is not thread safe!
static macro_processor macro_processor_instance;
// Iterators over the source template.
std::string::const_iterator iter = templ.begin();
std::string::const_iterator end = templ.end();
// Accumulator for the processed template.
std::string output;
bool res = phrase_parse(iter, end, macro_processor_instance(&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);
}
};
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);
return output;
}
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;
client::MyContext context;
context.config = &this->config();
context.config_override = config_override;
context.current_extruder_id = current_extruder_id;
return process_macro(templ, context);
}
spirit::ascii::space_type space; // Our skipper
calculator calc; // Our grammar
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;
}
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;
// Evaluate a boolean expression using the full expressive power of the PlaceholderParser boolean expression syntax.
// Throws std::runtime_error on syntax or runtime error.
bool PlaceholderParser::evaluate_boolean_expression(const std::string &templ, const DynamicConfig &config, const DynamicConfig *config_override)
{
client::MyContext context;
context.config = &config;
context.config_override = config_override;
// Let the macro processor parse just a boolean expression, not the full macro language.
context.just_boolean_expression = true;
return process_macro(templ, context) == "true";
}
}

View File

@@ -22,14 +22,21 @@ public:
void set(const std::string &key, const std::string &value) { this->set(key, new ConfigOptionString(value)); }
void set(const std::string &key, int value) { this->set(key, new ConfigOptionInt(value)); }
void set(const std::string &key, unsigned int value) { this->set(key, int(value)); }
void set(const std::string &key, bool value) { this->set(key, new ConfigOptionBool(value)); }
void set(const std::string &key, double value) { this->set(key, new ConfigOptionFloat(value)); }
void set(const std::string &key, const std::vector<std::string> &values) { this->set(key, new ConfigOptionStrings(values)); }
void set(const std::string &key, ConfigOption *opt) { m_config.set_key_value(key, opt); }
const ConfigOption* option(const std::string &key) const { return m_config.option(key); }
const DynamicConfig& config() const { return m_config; }
const ConfigOption* option(const std::string &key) const { return m_config.option(key); }
// Fill in the template.
// Fill in the template using a macro processing language.
// Throws std::runtime_error on syntax or runtime error.
std::string process(const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override = nullptr) const;
// Evaluate a boolean expression using the full expressive power of the PlaceholderParser boolean expression syntax.
// Throws std::runtime_error on syntax or runtime error.
static bool evaluate_boolean_expression(const std::string &templ, const DynamicConfig &config, const DynamicConfig *config_override = nullptr);
private:
DynamicConfig m_config;
};

View File

@@ -975,16 +975,52 @@ void Print::_make_wipe_tower()
// Let the ToolOrdering class know there will be initial priming extrusions at the start of the print.
m_tool_ordering = ToolOrdering(*this, (unsigned int)-1, true);
unsigned int initial_extruder_id = m_tool_ordering.first_extruder();
if (! m_tool_ordering.has_wipe_tower())
// Don't generate any wipe tower.
return;
// Check whether there are any layers in m_tool_ordering, which are marked with has_wipe_tower,
// they print neither object, nor support. These layers are above the raft and below the object, and they
// shall be added to the support layers to be printed.
// see https://github.com/prusa3d/Slic3r/issues/607
{
size_t idx_begin = size_t(-1);
size_t idx_end = m_tool_ordering.layer_tools().size();
// Find the first wipe tower layer, which does not have a counterpart in an object or a support layer.
for (size_t i = 0; i < idx_end; ++ i) {
const ToolOrdering::LayerTools &lt = m_tool_ordering.layer_tools()[i];
if (lt.has_wipe_tower && ! lt.has_object && ! lt.has_support) {
idx_begin = i;
break;
}
}
if (idx_begin != size_t(-1)) {
// Find the position in this->objects.first()->support_layers to insert these new support layers.
double wipe_tower_new_layer_print_z_first = m_tool_ordering.layer_tools()[idx_begin].print_z;
SupportLayerPtrs::iterator it_layer = this->objects.front()->support_layers.begin();
for (; (*it_layer)->print_z - EPSILON < wipe_tower_new_layer_print_z_first; ++ it_layer) ;
// Find the stopper of the sequence of wipe tower layers, which do not have a counterpart in an object or a support layer.
for (size_t i = idx_begin; i < idx_end; ++ i) {
ToolOrdering::LayerTools &lt = const_cast<ToolOrdering::LayerTools&>(m_tool_ordering.layer_tools()[i]);
if (! (lt.has_wipe_tower && ! lt.has_object && ! lt.has_support))
break;
lt.has_support = true;
// Insert the new support layer.
//FIXME the support layer ID is duplicated, but Vojtech hopes it is not being used anywhere anyway.
double height = lt.print_z - m_tool_ordering.layer_tools()[i-1].print_z;
auto *new_layer = new SupportLayer((*it_layer)->id(), this->objects.front(),
height, lt.print_z, lt.print_z - 0.5 * height);
it_layer = this->objects.front()->support_layers.insert(it_layer, new_layer);
++ it_layer;
}
}
}
// Initialize the wipe tower.
WipeTowerPrusaMM wipe_tower(
float(this->config.wipe_tower_x.value), float(this->config.wipe_tower_y.value),
float(this->config.wipe_tower_width.value), float(this->config.wipe_tower_per_color_wipe.value),
float(this->config.wipe_tower_rotation_angle.value), initial_extruder_id);
float(this->config.wipe_tower_rotation_angle.value), m_tool_ordering.first_extruder());
//wipe_tower.set_retract();
//wipe_tower.set_zhop();
@@ -1029,9 +1065,12 @@ void Print::_make_wipe_tower()
// Generate the wipe tower layers.
m_wipe_tower_tool_changes.reserve(m_tool_ordering.layer_tools().size());
wipe_tower.generate(m_wipe_tower_tool_changes);
/*unsigned int current_extruder_id = initial_extruder_id;
// Set current_extruder_id to the last extruder primed.
unsigned int current_extruder_id = m_tool_ordering.all_extruders().back();
for (const ToolOrdering::LayerTools &layer_tools : m_tool_ordering.layer_tools()) {
if (! layer_tools.has_wipe_tower)
// This is a support only layer, or the wipe tower does not reach to this height.
@@ -1046,7 +1085,11 @@ void Print::_make_wipe_tower()
last_layer);
std::vector<WipeTower::ToolChangeResult> tool_changes;
for (unsigned int extruder_id : layer_tools.extruders)
if ((first_layer && extruder_id == initial_extruder_id) || extruder_id != current_extruder_id) {
// Call the wipe_tower.tool_change() at the first layer for the initial extruder
// to extrude the wipe tower brim,
if ((first_layer && extruder_id == m_tool_ordering.all_extruders().back()) ||
// or when an extruder shall be switched.
extruder_id != current_extruder_id) {
tool_changes.emplace_back(wipe_tower.tool_change(extruder_id, extruder_id == layer_tools.extruders.back(), WipeTower::PURPOSE_EXTRUDE));
current_extruder_id = extruder_id;
}
@@ -1096,7 +1139,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)

View File

@@ -14,6 +14,9 @@ PrintConfigDef::PrintConfigDef()
t_optiondef_map &Options = this->options;
ConfigOptionDef* def;
// Maximum extruder temperature, bumped to 1500 to support printing of glass.
const int max_temp = 1500;
def = this->add("avoid_crossing_perimeters", coBool);
def->label = "Avoid crossing perimeters";
@@ -136,6 +139,13 @@ PrintConfigDef::PrintConfigDef()
def->label = "Compatible printers";
def->default_value = new ConfigOptionStrings();
def = this->add("compatible_printers_condition", coString);
def->label = "Compatible printers condition";
def->tooltip = "A boolean expression using the configuration values of an active printer profile. "
"If this expression evaluates to true, this profile is considered compatible "
"with the active printer profile.";
def->default_value = new ConfigOptionString();
def = this->add("complete_objects", coBool);
def->label = "Complete individual objects";
def->tooltip = "When printing multiple objects or copies, this feature will complete "
@@ -245,6 +255,7 @@ PrintConfigDef::PrintConfigDef()
def->enum_labels.push_back("Hilbert Curve");
def->enum_labels.push_back("Archimedean Chords");
def->enum_labels.push_back("Octagram Spiral");
// solid_fill_pattern is an obsolete equivalent to external_fill_pattern.
def->aliases.push_back("solid_fill_pattern");
def->default_value = new ConfigOptionEnum<InfillPattern>(ipRectilinear);
@@ -610,7 +621,7 @@ PrintConfigDef::PrintConfigDef()
"during print, set this to zero to disable temperature control commands in the output file.";
def->cli = "first-layer-temperature=i@";
def->min = 0;
def->max = 500;
def->max = max_temp;
def->default_value = new ConfigOptionInts { 200 };
def = this->add("gap_fill_speed", coFloat);
@@ -1314,8 +1325,8 @@ PrintConfigDef::PrintConfigDef()
"Enables a full-height \"sacrificial\" skirt on which the nozzles are periodically wiped.";
def->sidetext = "∆°C";
def->cli = "standby-temperature-delta=i";
def->min = -500;
def->max = 500;
def->min = -max_temp;
def->max = max_temp;
def->default_value = new ConfigOptionInt(-5);
def = this->add("start_gcode", coString);
@@ -1555,7 +1566,7 @@ PrintConfigDef::PrintConfigDef()
def->cli = "temperature=i@";
def->full_label = "Temperature";
def->max = 0;
def->max = 500;
def->max = max_temp;
def->default_value = new ConfigOptionInts { 200 };
def = this->add("thin_walls", coBool);

View File

@@ -447,7 +447,8 @@ void PrintObject::detect_surfaces_type()
to_polygons(upper_layer->get_region(idx_region)->slices.surfaces) :
to_polygons(upper_layer->slices);
surfaces_append(top,
offset2_ex(diff(layerm_slices_surfaces, upper_slices, true), -offset, offset),
//FIXME implement offset2_ex working over ExPolygons, that should be a bit more efficient than calling offset_ex twice.
offset_ex(offset_ex(diff_ex(layerm_slices_surfaces, upper_slices, true), -offset), offset),
stTop);
} else {
// if no upper layer, all surfaces of this one are solid

View File

@@ -103,7 +103,7 @@ inline bool equal_layering(const SlicingParameters &sp1, const SlicingParameters
sp1.layer_height == sp2.layer_height &&
sp1.min_layer_height == sp2.min_layer_height &&
sp1.max_layer_height == sp2.max_layer_height &&
sp1.max_suport_layer_height == sp2.max_suport_layer_height &&
// sp1.max_suport_layer_height == sp2.max_suport_layer_height &&
sp1.first_print_layer_height == sp2.first_print_layer_height &&
sp1.first_object_layer_height == sp2.first_object_layer_height &&
sp1.first_object_layer_bridging == sp2.first_object_layer_bridging &&

View File

@@ -373,15 +373,9 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
height_min = std::min(height_min, layer.height);
}
if (! empty) {
object.add_support_layer(layer_id, height_min, zavg);
if (layer_id > 0) {
// Inter-link the support layers into a linked list.
SupportLayer *sl1 = object.support_layers[object.support_layer_count() - 2];
SupportLayer *sl2 = object.support_layers.back();
sl1->upper_layer = sl2;
sl2->lower_layer = sl1;
}
++layer_id;
// Here the upper_layer and lower_layer pointers are left to null at the support layers,
// as they are never used. These pointers are candidates for removal.
object.add_support_layer(layer_id ++, height_min, zavg);
}
i = j;
}
@@ -2569,7 +2563,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
// TODO: use brim ordering algorithm
to_infill_polygons = to_polygons(to_infill);
// TODO: use offset2_ex()
to_infill = offset_ex(to_infill, float(- flow.scaled_spacing()));
to_infill = offset_ex(to_infill, float(- 0.4 * flow.scaled_spacing()));
extrusion_entities_append_paths(
support_layer.support_fills.entities,
to_polylines(STDMOVE(to_infill_polygons)),
@@ -2602,7 +2596,8 @@ void PrintObjectSupportMaterial::generate_toolpaths(
// Base flange.
filler->angle = raft_angle_1st_layer;
filler->spacing = m_first_layer_flow.spacing();
density = 0.5f;
// 70% of density on the 1st layer.
density = 0.7f;
} else if (support_layer_id >= m_slicing_params.base_raft_layers) {
filler->angle = raft_angle_interface;
// We don't use $base_flow->spacing because we need a constant spacing
@@ -2776,7 +2771,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
// TODO: use brim ordering algorithm
Polygons to_infill_polygons = to_polygons(to_infill);
// TODO: use offset2_ex()
to_infill = offset_ex(to_infill, - float(flow.scaled_spacing()));
to_infill = offset_ex(to_infill, - 0.4 * float(flow.scaled_spacing()));
extrusion_entities_append_paths(
base_layer.extrusions,
to_polylines(STDMOVE(to_infill_polygons)),

View File

@@ -15,15 +15,15 @@ const std::string& var_dir();
// Return a full resource path for a file_name.
std::string var(const std::string &file_name);
// Set a path with various static definition data (for example the initial config bundles).
void set_resources_dir(const std::string &path);
// Return a full path to the resources directory.
const std::string& resources_dir();
// Set a path with preset files.
void set_data_dir(const std::string &path);
// Return a full path to the GUI resource files.
const std::string& data_dir();
// Return a full path to a configuration file given its file name..
std::string config_path(const std::string &file_name);
// Return a full path to a configuration file given the section and name.
// The suffix ".ini" will be added if it is missing in the name.
std::string config_path(const std::string &section, const std::string &name);
extern std::string encode_path(const char *src);
extern std::string decode_path(const char *src);

View File

@@ -14,7 +14,7 @@
#include <boost/thread.hpp>
#define SLIC3R_FORK_NAME "Slic3r Prusa Edition"
#define SLIC3R_VERSION "1.38.2"
#define SLIC3R_VERSION "1.38.4"
#define SLIC3R_BUILD "UNKNOWN"
typedef long coord_t;

View File

@@ -89,6 +89,18 @@ std::string var(const std::string &file_name)
return file.string();
}
static std::string g_resources_dir;
void set_resources_dir(const std::string &dir)
{
g_resources_dir = dir;
}
const std::string& resources_dir()
{
return g_resources_dir;
}
static std::string g_data_dir;
void set_data_dir(const std::string &dir)
@@ -101,19 +113,6 @@ const std::string& data_dir()
return g_data_dir;
}
std::string config_path(const std::string &file_name)
{
auto file = (boost::filesystem::path(g_data_dir) / file_name).make_preferred();
return file.string();
}
std::string config_path(const std::string &section, const std::string &name)
{
auto file_name = boost::algorithm::iends_with(name, ".ini") ? name : name + ".ini";
auto file = (boost::filesystem::path(g_data_dir) / section / file_name).make_preferred();
return file.string();
}
} // namespace Slic3r
#ifdef SLIC3R_HAS_BROKEN_CROAK
@@ -194,6 +193,7 @@ confess_at(const char *file, int line, const char *func,
namespace Slic3r {
// Encode an UTF-8 string to the local code page.
std::string encode_path(const char *src)
{
#ifdef WIN32
@@ -211,6 +211,7 @@ std::string encode_path(const char *src)
#endif /* WIN32 */
}
// Encode an 8-bit string from a local code page to UTF-8.
std::string decode_path(const char *src)
{
#ifdef WIN32