mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-22 08:11:11 -06:00
Merge branch 'master' into wipe_tower_improvements
This commit is contained in:
commit
4583d62edd
126 changed files with 5240 additions and 607 deletions
|
@ -440,7 +440,7 @@ if(SLIC3R_STATIC)
|
|||
# Use boost libraries linked statically to the C++ runtime.
|
||||
# set(Boost_USE_STATIC_RUNTIME ON)
|
||||
endif()
|
||||
find_package(Boost REQUIRED COMPONENTS system filesystem thread log locale)
|
||||
find_package(Boost REQUIRED COMPONENTS system filesystem thread log locale regex)
|
||||
if(Boost_FOUND)
|
||||
include_directories(${Boost_INCLUDE_DIRS})
|
||||
target_link_libraries(XS ${Boost_LIBRARIES})
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 <_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 <_extra = *m_layer_tools.insert(m_layer_tools.begin() + j, lt_new);
|
||||
LayerTools <_prev = m_layer_tools[j - 1];
|
||||
LayerTools <_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;
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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() {}
|
||||
|
|
|
@ -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 ¬_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";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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 < = 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 < = 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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 &&
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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 §ion, const std::string &name);
|
||||
|
||||
extern std::string encode_path(const char *src);
|
||||
extern std::string decode_path(const char *src);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 §ion, 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
|
||||
|
|
|
@ -45,6 +45,10 @@ void AppConfig::set_defaults()
|
|||
// Version check is enabled by default in the config, but it is not implemented yet.
|
||||
if (get("version_check").empty())
|
||||
set("version_check", "1");
|
||||
// Use OpenGL 1.1 even if OpenGL 2.0 is available. This is mainly to support some buggy Intel HD Graphics drivers.
|
||||
// https://github.com/prusa3d/Slic3r/issues/233
|
||||
if (get("use_legacy_opengl").empty())
|
||||
set("use_legacy_opengl", "0");
|
||||
}
|
||||
|
||||
void AppConfig::load()
|
||||
|
|
|
@ -13,6 +13,14 @@
|
|||
#pragma comment(lib, "user32.lib")
|
||||
#endif
|
||||
|
||||
#include <wx/app.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/frame.h>
|
||||
#include <wx/menu.h>
|
||||
#include <wx/notebook.h>
|
||||
#include <wx/panel.h>
|
||||
#include <wx/sizer.h>
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
#if __APPLE__
|
||||
|
@ -134,4 +142,45 @@ void break_to_debugger()
|
|||
#endif /* _WIN32 */
|
||||
}
|
||||
|
||||
// Passing the wxWidgets GUI classes instantiated by the Perl part to C++.
|
||||
wxApp *g_wxApp = nullptr;
|
||||
wxFrame *g_wxMainFrame = nullptr;
|
||||
wxNotebook *g_wxTabPanel = nullptr;
|
||||
|
||||
void set_wxapp(wxApp *app)
|
||||
{
|
||||
g_wxApp = app;
|
||||
}
|
||||
|
||||
void set_main_frame(wxFrame *main_frame)
|
||||
{
|
||||
g_wxMainFrame = main_frame;
|
||||
}
|
||||
|
||||
void set_tab_panel(wxNotebook *tab_panel)
|
||||
{
|
||||
g_wxTabPanel = tab_panel;
|
||||
}
|
||||
|
||||
void add_debug_menu(wxMenuBar *menu)
|
||||
{
|
||||
#if 0
|
||||
auto debug_menu = new wxMenu();
|
||||
debug_menu->Append(wxWindow::NewControlId(1), "Some debug");
|
||||
menu->Append(debug_menu, _T("&Debug"));
|
||||
#endif
|
||||
}
|
||||
|
||||
void create_preset_tab(const char *name)
|
||||
{
|
||||
auto *panel = new wxPanel(g_wxTabPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL);
|
||||
// Vertical sizer to hold the choice menu and the rest of the page.
|
||||
auto *sizer = new wxBoxSizer(wxVERTICAL);
|
||||
sizer->SetSizeHints(panel);
|
||||
panel->SetSizer(sizer);
|
||||
auto *button = new wxButton(panel, wxID_ANY, "Hello World", wxDefaultPosition, wxDefaultSize, 0);
|
||||
sizer->Add(button, 0, 0, 0);
|
||||
g_wxTabPanel->AddPage(panel, name);
|
||||
}
|
||||
|
||||
} }
|
||||
|
|
|
@ -4,6 +4,11 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class wxApp;
|
||||
class wxFrame;
|
||||
class wxMenuBar;
|
||||
class wxNotebook;
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
void disable_screensaver();
|
||||
|
@ -12,6 +17,16 @@ std::vector<std::string> scan_serial_ports();
|
|||
bool debugged();
|
||||
void break_to_debugger();
|
||||
|
||||
// Passing the wxWidgets GUI classes instantiated by the Perl part to C++.
|
||||
void set_wxapp(wxApp *app);
|
||||
void set_main_frame(wxFrame *main_frame);
|
||||
void set_tab_panel(wxNotebook *tab_panel);
|
||||
|
||||
void add_debug_menu(wxMenuBar *menu);
|
||||
// Create a new preset tab (print, filament or printer),
|
||||
// add it at the end of the tab panel.
|
||||
void create_preset_tab(const char *name);
|
||||
|
||||
} }
|
||||
|
||||
#endif
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
#include "../../libslic3r/libslic3r.h"
|
||||
#include "../../libslic3r/Utils.hpp"
|
||||
#include "../../libslic3r/PlaceholderParser.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
@ -79,7 +80,8 @@ void Preset::set_num_extruders(DynamicPrintConfig &config, unsigned int num_extr
|
|||
auto *opt = config.option(key, false);
|
||||
assert(opt != nullptr);
|
||||
assert(opt->is_vector());
|
||||
static_cast<ConfigOptionVectorBase*>(opt)->resize(num_extruders, defaults.option(key));
|
||||
if (opt != nullptr && opt->is_vector())
|
||||
static_cast<ConfigOptionVectorBase*>(opt)->resize(num_extruders, defaults.option(key));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,18 +142,37 @@ std::string Preset::label() const
|
|||
return this->name + (this->is_dirty ? g_suffix_modified : "");
|
||||
}
|
||||
|
||||
bool Preset::is_compatible_with_printer(const std::string &active_printer) const
|
||||
bool Preset::is_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config) const
|
||||
{
|
||||
auto *compatible_printers = dynamic_cast<const ConfigOptionStrings*>(this->config.option("compatible_printers"));
|
||||
return this->is_default || active_printer.empty() ||
|
||||
compatible_printers == nullptr || compatible_printers->values.empty() ||
|
||||
std::find(compatible_printers->values.begin(), compatible_printers->values.end(), active_printer) !=
|
||||
auto *condition = dynamic_cast<const ConfigOptionString*>(this->config.option("compatible_printers_condition"));
|
||||
auto *compatible_printers = dynamic_cast<const ConfigOptionStrings*>(this->config.option("compatible_printers"));
|
||||
bool has_compatible_printers = compatible_printers != nullptr && ! compatible_printers->values.empty();
|
||||
if (! has_compatible_printers && condition != nullptr && ! condition->value.empty()) {
|
||||
try {
|
||||
return PlaceholderParser::evaluate_boolean_expression(condition->value, active_printer.config, extra_config);
|
||||
} catch (const std::runtime_error &err) {
|
||||
//FIXME in case of an error, return "compatible with everything".
|
||||
printf("Preset::is_compatible_with_printer - parsing error of compatible_printers_condition %s:\n%s\n", active_printer.name.c_str(), err.what());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return this->is_default || active_printer.name.empty() || ! has_compatible_printers ||
|
||||
std::find(compatible_printers->values.begin(), compatible_printers->values.end(), active_printer.name) !=
|
||||
compatible_printers->values.end();
|
||||
}
|
||||
|
||||
bool Preset::update_compatible_with_printer(const std::string &active_printer)
|
||||
bool Preset::is_compatible_with_printer(const Preset &active_printer) const
|
||||
{
|
||||
return this->is_compatible = is_compatible_with_printer(active_printer);
|
||||
DynamicPrintConfig config;
|
||||
config.set_key_value("printer_preset", new ConfigOptionString(active_printer.name));
|
||||
config.set_key_value("num_extruders", new ConfigOptionInt(
|
||||
(int)static_cast<const ConfigOptionFloats*>(active_printer.config.option("nozzle_diameter"))->values.size()));
|
||||
return this->is_compatible_with_printer(active_printer, &config);
|
||||
}
|
||||
|
||||
bool Preset::update_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config)
|
||||
{
|
||||
return this->is_compatible = is_compatible_with_printer(active_printer, extra_config);
|
||||
}
|
||||
|
||||
const std::vector<std::string>& Preset::print_options()
|
||||
|
@ -179,7 +200,8 @@ const std::vector<std::string>& Preset::print_options()
|
|||
"top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "bridge_flow_ratio", "clip_multipart_objects",
|
||||
"elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y",
|
||||
"wipe_tower_width", "wipe_tower_per_color_wipe", "wipe_tower_rotation_angle",
|
||||
"compatible_printers"
|
||||
"compatible_printers", "compatible_printers_condition"
|
||||
|
||||
};
|
||||
return s_opts;
|
||||
}
|
||||
|
@ -192,7 +214,7 @@ const std::vector<std::string>& Preset::filament_options()
|
|||
"first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed", "max_fan_speed", "bridge_fan_speed",
|
||||
"disable_fan_first_layers", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed", "start_filament_gcode",
|
||||
"end_filament_gcode",
|
||||
"compatible_printers"
|
||||
"compatible_printers", "compatible_printers_condition"
|
||||
};
|
||||
return s_opts;
|
||||
}
|
||||
|
@ -242,6 +264,21 @@ PresetCollection::~PresetCollection()
|
|||
m_bitmap_main_frame = nullptr;
|
||||
}
|
||||
|
||||
void PresetCollection::reset(bool delete_files)
|
||||
{
|
||||
if (m_presets.size() > 1) {
|
||||
if (delete_files) {
|
||||
// Erase the preset files.
|
||||
for (Preset &preset : m_presets)
|
||||
if (! preset.is_default && ! preset.is_external)
|
||||
boost::nowide::remove(preset.file.c_str());
|
||||
}
|
||||
// Don't use m_presets.resize() here as it requires a default constructor for Preset.
|
||||
m_presets.erase(m_presets.begin() + 1, m_presets.end());
|
||||
this->select_preset(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Load all presets found in dir_path.
|
||||
// Throws an exception on error.
|
||||
void PresetCollection::load_presets(const std::string &dir_path, const std::string &subdir)
|
||||
|
@ -283,10 +320,11 @@ Preset& PresetCollection::load_preset(const std::string &path, const std::string
|
|||
|
||||
Preset& PresetCollection::load_preset(const std::string &path, const std::string &name, DynamicPrintConfig &&config, bool select)
|
||||
{
|
||||
Preset key(m_type, name);
|
||||
auto it = std::lower_bound(m_presets.begin(), m_presets.end(), key);
|
||||
if (it == m_presets.end() || it->name != name)
|
||||
auto it = this->find_preset_internal(name);
|
||||
if (it == m_presets.end() || it->name != name) {
|
||||
// The preset was not found. Create a new preset.
|
||||
it = m_presets.emplace(it, Preset(m_type, name, false));
|
||||
}
|
||||
Preset &preset = *it;
|
||||
preset.file = path;
|
||||
preset.config = std::move(config);
|
||||
|
@ -301,9 +339,8 @@ void PresetCollection::save_current_preset(const std::string &new_name)
|
|||
{
|
||||
// 1) Find the preset with a new_name or create a new one,
|
||||
// initialize it with the edited config.
|
||||
Preset key(m_type, new_name, false);
|
||||
auto it = std::lower_bound(m_presets.begin(), m_presets.end(), key);
|
||||
if (it != m_presets.end() && it->name == key.name) {
|
||||
auto it = this->find_preset_internal(new_name);
|
||||
if (it != m_presets.end() && it->name == new_name) {
|
||||
// Preset with the same name found.
|
||||
Preset &preset = *it;
|
||||
if (preset.is_default)
|
||||
|
@ -314,11 +351,8 @@ void PresetCollection::save_current_preset(const std::string &new_name)
|
|||
} else {
|
||||
// Creating a new preset.
|
||||
Preset &preset = *m_presets.insert(it, m_edited_preset);
|
||||
std::string file_name = new_name;
|
||||
if (! boost::iends_with(file_name, ".ini"))
|
||||
file_name += ".ini";
|
||||
preset.name = new_name;
|
||||
preset.file = (boost::filesystem::path(m_dir_path) / file_name).make_preferred().string();
|
||||
preset.file = this->path_from_name(new_name);
|
||||
}
|
||||
// 2) Activate the saved preset.
|
||||
this->select_preset_by_name(new_name, true);
|
||||
|
@ -356,7 +390,7 @@ bool PresetCollection::load_bitmap_default(const std::string &file_name)
|
|||
Preset* PresetCollection::find_preset(const std::string &name, bool first_visible_if_not_found)
|
||||
{
|
||||
Preset key(m_type, name, false);
|
||||
auto it = std::lower_bound(m_presets.begin(), m_presets.end(), key);
|
||||
auto it = this->find_preset_internal(name);
|
||||
// Ensure that a temporary copy is returned if the preset found is currently selected.
|
||||
return (it != m_presets.end() && it->name == key.name) ? &this->preset(it - m_presets.begin()) :
|
||||
first_visible_if_not_found ? &this->first_visible() : nullptr;
|
||||
|
@ -394,31 +428,25 @@ void PresetCollection::set_default_suppressed(bool default_suppressed)
|
|||
}
|
||||
}
|
||||
|
||||
void PresetCollection::update_compatible_with_printer(const std::string &active_printer, bool select_other_if_incompatible)
|
||||
void PresetCollection::update_compatible_with_printer(const Preset &active_printer, bool select_other_if_incompatible)
|
||||
{
|
||||
size_t num_visible = 0;
|
||||
DynamicPrintConfig config;
|
||||
config.set_key_value("printer_preset", new ConfigOptionString(active_printer.name));
|
||||
config.set_key_value("num_extruders", new ConfigOptionInt(
|
||||
(int)static_cast<const ConfigOptionFloats*>(active_printer.config.option("nozzle_diameter"))->values.size()));
|
||||
for (size_t idx_preset = 1; idx_preset < m_presets.size(); ++ idx_preset) {
|
||||
bool selected = idx_preset == m_idx_selected;
|
||||
Preset &preset_selected = m_presets[idx_preset];
|
||||
Preset &preset_edited = selected ? m_edited_preset : preset_selected;
|
||||
if (preset_edited.update_compatible_with_printer(active_printer))
|
||||
// Mark compatible presets as visible.
|
||||
preset_selected.is_visible = true;
|
||||
else if (selected && select_other_if_incompatible) {
|
||||
preset_selected.is_visible = false;
|
||||
if (! preset_edited.update_compatible_with_printer(active_printer, &config) &&
|
||||
selected && select_other_if_incompatible)
|
||||
m_idx_selected = (size_t)-1;
|
||||
}
|
||||
if (selected)
|
||||
preset_selected.is_compatible = preset_edited.is_compatible;
|
||||
if (preset_selected.is_visible)
|
||||
++ num_visible;
|
||||
}
|
||||
if (m_idx_selected == (size_t)-1)
|
||||
// Find some other visible preset.
|
||||
this->select_preset(first_visible_idx());
|
||||
else if (num_visible == 0)
|
||||
// Show the "-- default --" preset.
|
||||
m_presets.front().is_visible = true;
|
||||
// Find some other compatible preset, or the "-- default --" preset.
|
||||
this->select_preset(first_compatible_idx());
|
||||
}
|
||||
|
||||
// Save the preset under a new name. If the name is different from the old one,
|
||||
|
@ -459,7 +487,7 @@ void PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompatibl
|
|||
ui->Clear();
|
||||
for (size_t i = this->m_presets.front().is_visible ? 0 : 1; i < this->m_presets.size(); ++ i) {
|
||||
const Preset &preset = this->m_presets[i];
|
||||
if (! show_incompatible && ! preset.is_compatible && i != m_idx_selected)
|
||||
if (! preset.is_visible || (! show_incompatible && ! preset.is_compatible && i != m_idx_selected))
|
||||
continue;
|
||||
const wxBitmap *bmp = preset.is_compatible ? m_bitmap_compatible : m_bitmap_incompatible;
|
||||
ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()),
|
||||
|
@ -486,13 +514,36 @@ bool PresetCollection::update_dirty_ui(wxBitmapComboBox *ui)
|
|||
std::string preset_name = Preset::remove_suffix_modified(old_label);
|
||||
const Preset *preset = this->find_preset(preset_name, false);
|
||||
assert(preset != nullptr);
|
||||
std::string new_label = preset->is_dirty ? preset->name + g_suffix_modified : preset->name;
|
||||
if (old_label != new_label)
|
||||
ui->SetString(ui_id, wxString::FromUTF8(new_label.c_str()));
|
||||
if (preset != nullptr) {
|
||||
std::string new_label = preset->is_dirty ? preset->name + g_suffix_modified : preset->name;
|
||||
if (old_label != new_label)
|
||||
ui->SetString(ui_id, wxString::FromUTF8(new_label.c_str()));
|
||||
}
|
||||
}
|
||||
#ifdef __APPLE__
|
||||
// wxWidgets on OSX do not upload the text of the combo box line automatically.
|
||||
// Force it to update by re-selecting.
|
||||
ui->SetSelection(ui->GetSelection());
|
||||
#endif /* __APPLE __ */
|
||||
return was_dirty != is_dirty;
|
||||
}
|
||||
|
||||
std::vector<std::string> PresetCollection::current_dirty_options() const
|
||||
{
|
||||
std::vector<std::string> changed = this->get_selected_preset().config.diff(this->get_edited_preset().config);
|
||||
// The "compatible_printers" option key is handled differently from the others:
|
||||
// It is not mandatory. If the key is missing, it means it is compatible with any printer.
|
||||
// If the key exists and it is empty, it means it is compatible with no printer.
|
||||
std::initializer_list<const char*> optional_keys { "compatible_printers", "compatible_printers_condition" };
|
||||
for (auto &opt_key : optional_keys) {
|
||||
if (this->get_selected_preset().config.has(opt_key) != this->get_edited_preset().config.has(opt_key))
|
||||
changed.emplace_back(opt_key);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
// Select a new preset. This resets all the edits done to the currently selected preset.
|
||||
// If the preset with index idx does not exist, a first visible preset is selected.
|
||||
Preset& PresetCollection::select_preset(size_t idx)
|
||||
{
|
||||
for (Preset &preset : m_presets)
|
||||
|
@ -509,10 +560,9 @@ bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, b
|
|||
{
|
||||
std::string name = Preset::remove_suffix_modified(name_w_suffix);
|
||||
// 1) Try to find the preset by its name.
|
||||
Preset key(m_type, name, false);
|
||||
auto it = std::lower_bound(m_presets.begin(), m_presets.end(), key);
|
||||
auto it = this->find_preset_internal(name);
|
||||
size_t idx = 0;
|
||||
if (it != m_presets.end() && it->name == key.name)
|
||||
if (it != m_presets.end() && it->name == name)
|
||||
// Preset found by its name.
|
||||
idx = it - m_presets.begin();
|
||||
else {
|
||||
|
@ -544,4 +594,11 @@ std::string PresetCollection::name() const
|
|||
}
|
||||
}
|
||||
|
||||
// Generate a file path from a profile name. Add the ".ini" suffix if it is missing.
|
||||
std::string PresetCollection::path_from_name(const std::string &new_name) const
|
||||
{
|
||||
std::string file_name = boost::iends_with(new_name, ".ini") ? new_name : (new_name + ".ini");
|
||||
return (boost::filesystem::path(m_dir_path) / file_name).make_preferred().string();
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
|
|
@ -79,9 +79,11 @@ public:
|
|||
void set_dirty(bool dirty = true) { this->is_dirty = dirty; }
|
||||
void reset_dirty() { this->is_dirty = false; }
|
||||
|
||||
bool is_compatible_with_printer(const std::string &active_printer) const;
|
||||
bool is_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config) const;
|
||||
bool is_compatible_with_printer(const Preset &active_printer) const;
|
||||
|
||||
// Mark this preset as compatible if it is compatible with active_printer.
|
||||
bool update_compatible_with_printer(const std::string &active_printer);
|
||||
bool update_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config);
|
||||
|
||||
// Resize the extruder specific fields, initialize them with the content of the 1st extruder.
|
||||
void set_num_extruders(unsigned int n) { set_num_extruders(this->config, n); }
|
||||
|
@ -114,6 +116,8 @@ public:
|
|||
PresetCollection(Preset::Type type, const std::vector<std::string> &keys);
|
||||
~PresetCollection();
|
||||
|
||||
void reset(bool delete_files);
|
||||
|
||||
Preset::Type type() const { return m_type; }
|
||||
std::string name() const;
|
||||
const std::deque<Preset>& operator()() const { return m_presets; }
|
||||
|
@ -159,7 +163,7 @@ public:
|
|||
// Return a preset by an index. If the preset is active, a temporary copy is returned.
|
||||
Preset& preset(size_t idx) { return (int(idx) == m_idx_selected) ? m_edited_preset : m_presets[idx]; }
|
||||
const Preset& preset(size_t idx) const { return const_cast<PresetCollection*>(this)->preset(idx); }
|
||||
void discard_current_changes() { m_edited_preset = m_presets[m_idx_selected]; }
|
||||
void discard_current_changes() { m_presets[m_idx_selected].reset_dirty(); m_edited_preset = m_presets[m_idx_selected]; }
|
||||
|
||||
// Return a preset by its name. If the preset is active, a temporary copy is returned.
|
||||
// If a preset is not found by its name, null is returned.
|
||||
|
@ -180,14 +184,14 @@ public:
|
|||
size_t size() const { return this->m_presets.size(); }
|
||||
|
||||
// For Print / Filament presets, disable those, which are not compatible with the printer.
|
||||
void update_compatible_with_printer(const std::string &active_printer, bool select_other_if_incompatible);
|
||||
void update_compatible_with_printer(const Preset &active_printer, bool select_other_if_incompatible);
|
||||
|
||||
size_t num_visible() const { return std::count_if(m_presets.begin(), m_presets.end(), [](const Preset &preset){return preset.is_visible;}); }
|
||||
|
||||
// Compare the content of get_selected_preset() with get_edited_preset() configs, return true if they differ.
|
||||
bool current_is_dirty() { return ! this->current_dirty_options().empty(); }
|
||||
bool current_is_dirty() const { return ! this->current_dirty_options().empty(); }
|
||||
// Compare the content of get_selected_preset() with get_edited_preset() configs, return the list of keys where they differ.
|
||||
std::vector<std::string> current_dirty_options() { return this->get_selected_preset().config.diff(this->get_edited_preset().config); }
|
||||
std::vector<std::string> current_dirty_options() const;
|
||||
|
||||
// Update the choice UI from the list of presets.
|
||||
// If show_incompatible, all presets are shown, otherwise only the compatible presets are shown.
|
||||
|
@ -207,11 +211,26 @@ public:
|
|||
// With force, the changes are reverted if the new index is the same as the old index.
|
||||
bool select_preset_by_name(const std::string &name, bool force);
|
||||
|
||||
// Generate a file path from a profile name. Add the ".ini" suffix if it is missing.
|
||||
std::string path_from_name(const std::string &new_name) const;
|
||||
|
||||
private:
|
||||
PresetCollection();
|
||||
PresetCollection(const PresetCollection &other);
|
||||
PresetCollection& operator=(const PresetCollection &other);
|
||||
|
||||
// Find a preset in the sorted list of presets.
|
||||
// The "-- default -- " preset is always the first, so it needs
|
||||
// to be handled differently.
|
||||
std::deque<Preset>::iterator find_preset_internal(const std::string &name)
|
||||
{
|
||||
Preset key(m_type, name);
|
||||
auto it = std::lower_bound(m_presets.begin() + 1, m_presets.end(), key);
|
||||
return (it == m_presets.end() && m_presets.front().name == name) ? m_presets.begin() : it;
|
||||
}
|
||||
std::deque<Preset>::const_iterator find_preset_internal(const std::string &name) const
|
||||
{ return const_cast<PresetCollection*>(this)->find_preset_internal(name); }
|
||||
|
||||
// Type of this PresetCollection: TYPE_PRINT, TYPE_FILAMENT or TYPE_PRINTER.
|
||||
Preset::Type m_type;
|
||||
// List of presets, starting with the "- default -" preset.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//#undef NDEBUGc
|
||||
//#undef NDEBUG
|
||||
#include <cassert>
|
||||
|
||||
#include "PresetBundle.hpp"
|
||||
|
@ -25,6 +25,10 @@
|
|||
#include "../../libslic3r/PlaceholderParser.hpp"
|
||||
#include "../../libslic3r/Utils.hpp"
|
||||
|
||||
// Store the print/filament/printer presets into a "presets" subdirectory of the Slic3rPE config dir.
|
||||
// This breaks compatibility with the upstream Slic3r if the --datadir is used to switch between the two versions.
|
||||
// #define SLIC3R_PROFILE_USE_PRESETS_SUBDIR
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
PresetBundle::PresetBundle() :
|
||||
|
@ -34,7 +38,8 @@ PresetBundle::PresetBundle() :
|
|||
m_bitmapCompatible(new wxBitmap),
|
||||
m_bitmapIncompatible(new wxBitmap)
|
||||
{
|
||||
::wxInitAllImageHandlers();
|
||||
if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr)
|
||||
wxImage::AddHandler(new wxPNGHandler);
|
||||
|
||||
// Create the ID config keys, as they are not part of the Static print config classes.
|
||||
this->prints.preset(0).config.opt_string("print_settings_id", true);
|
||||
|
@ -42,7 +47,9 @@ PresetBundle::PresetBundle() :
|
|||
this->printers.preset(0).config.opt_string("print_settings_id", true);
|
||||
// Create the "compatible printers" keys, as they are not part of the Static print config classes.
|
||||
this->filaments.preset(0).config.optptr("compatible_printers", true);
|
||||
this->filaments.preset(0).config.optptr("compatible_printers_condition", true);
|
||||
this->prints.preset(0).config.optptr("compatible_printers", true);
|
||||
this->prints.preset(0).config.optptr("compatible_printers_condition", true);
|
||||
|
||||
this->prints .load_bitmap_default("cog.png");
|
||||
this->filaments.load_bitmap_default("spool.png");
|
||||
|
@ -62,23 +69,54 @@ PresetBundle::~PresetBundle()
|
|||
delete bitmap.second;
|
||||
}
|
||||
|
||||
void PresetBundle::reset(bool delete_files)
|
||||
{
|
||||
// Clear the existing presets, delete their respective files.
|
||||
this->prints .reset(delete_files);
|
||||
this->filaments.reset(delete_files);
|
||||
this->printers .reset(delete_files);
|
||||
this->filament_presets.clear();
|
||||
this->filament_presets.emplace_back(this->filaments.get_selected_preset().name);
|
||||
}
|
||||
|
||||
void PresetBundle::setup_directories()
|
||||
{
|
||||
boost::filesystem::path dir = boost::filesystem::canonical(Slic3r::data_dir());
|
||||
if (! boost::filesystem::is_directory(dir))
|
||||
throw std::runtime_error(std::string("datadir does not exist: ") + Slic3r::data_dir());
|
||||
std::initializer_list<const char*> names = { "print", "filament", "printer" };
|
||||
for (const char *name : names) {
|
||||
boost::filesystem::path subdir = (dir / name).make_preferred();
|
||||
boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir());
|
||||
std::initializer_list<boost::filesystem::path> paths = {
|
||||
data_dir,
|
||||
#ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR
|
||||
// Store the print/filament/printer presets into a "presets" directory.
|
||||
data_dir / "presets",
|
||||
data_dir / "presets" / "print",
|
||||
data_dir / "presets" / "filament",
|
||||
data_dir / "presets" / "printer"
|
||||
#else
|
||||
// Store the print/filament/printer presets at the same location as the upstream Slic3r.
|
||||
data_dir / "print",
|
||||
data_dir / "filament",
|
||||
data_dir / "printer"
|
||||
#endif
|
||||
};
|
||||
for (const boost::filesystem::path &path : paths) {
|
||||
boost::filesystem::path subdir = path;
|
||||
subdir.make_preferred();
|
||||
if (! boost::filesystem::is_directory(subdir) &&
|
||||
! boost::filesystem::create_directory(subdir))
|
||||
throw std::runtime_error(std::string("Slic3r was unable to create its data directory at ") + subdir.string());
|
||||
}
|
||||
}
|
||||
|
||||
void PresetBundle::load_presets(const std::string &dir_path)
|
||||
void PresetBundle::load_presets()
|
||||
{
|
||||
std::string errors_cummulative;
|
||||
const std::string dir_path = data_dir()
|
||||
#ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR
|
||||
// Store the print/filament/printer presets into a "presets" directory.
|
||||
+ "/presets"
|
||||
#else
|
||||
// Store the print/filament/printer presets at the same location as the upstream Slic3r.
|
||||
#endif
|
||||
;
|
||||
try {
|
||||
this->prints.load_presets(dir_path, "print");
|
||||
} catch (const std::runtime_error &err) {
|
||||
|
@ -95,6 +133,7 @@ void PresetBundle::load_presets(const std::string &dir_path)
|
|||
errors_cummulative += err.what();
|
||||
}
|
||||
this->update_multi_material_filament_presets();
|
||||
this->update_compatible_with_printer(false);
|
||||
if (! errors_cummulative.empty())
|
||||
throw std::runtime_error(errors_cummulative);
|
||||
}
|
||||
|
@ -125,8 +164,10 @@ void PresetBundle::load_selections(const AppConfig &config)
|
|||
this->set_filament_preset(i, remove_ini_suffix(config.get("presets", name)));
|
||||
}
|
||||
// Update visibility of presets based on their compatibility with the active printer.
|
||||
// This will switch the print or filament presets to compatible if the active presets are incompatible.
|
||||
this->update_compatible_with_printer(false);
|
||||
// Always try to select a compatible print and filament preset to the current printer preset,
|
||||
// as the application may have been closed with an active "external" preset, which does not
|
||||
// exist.
|
||||
this->update_compatible_with_printer(true);
|
||||
}
|
||||
|
||||
// Export selections (current print, current filaments, current printer) into config.ini
|
||||
|
@ -216,6 +257,8 @@ DynamicPrintConfig PresetBundle::full_config() const
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
out.erase("compatible_printers");
|
||||
|
||||
static const char *keys[] = { "perimeter", "infill", "solid_infill", "support_material", "support_material_interface" };
|
||||
for (size_t i = 0; i < sizeof(keys) / sizeof(keys[0]); ++ i) {
|
||||
|
@ -239,7 +282,7 @@ void PresetBundle::load_config_file(const std::string &path)
|
|||
config.apply(FullPrintConfig::defaults());
|
||||
config.load_from_gcode(path);
|
||||
Preset::normalize(config);
|
||||
load_config_file_config(path, std::move(config));
|
||||
load_config_file_config(path, true, std::move(config));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -268,7 +311,7 @@ void PresetBundle::load_config_file(const std::string &path)
|
|||
config.apply(FullPrintConfig::defaults());
|
||||
config.load(tree);
|
||||
Preset::normalize(config);
|
||||
load_config_file_config(path, std::move(config));
|
||||
load_config_file_config(path, true, std::move(config));
|
||||
break;
|
||||
}
|
||||
case CONFIG_FILE_TYPE_CONFIG_BUNDLE:
|
||||
|
@ -278,17 +321,32 @@ void PresetBundle::load_config_file(const std::string &path)
|
|||
}
|
||||
|
||||
// Load a config file from a boost property_tree. This is a private method called from load_config_file.
|
||||
void PresetBundle::load_config_file_config(const std::string &path, const DynamicPrintConfig &config)
|
||||
void PresetBundle::load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config)
|
||||
{
|
||||
// The "compatible_printers" field should not have been exported into a config.ini or a G-code anyway,
|
||||
// but some of the alpha versions of Slic3r did.
|
||||
{
|
||||
ConfigOption *opt_compatible = config.optptr("compatible_printers");
|
||||
if (opt_compatible != nullptr) {
|
||||
assert(opt_compatible->type() == coStrings);
|
||||
if (opt_compatible->type() == coStrings)
|
||||
static_cast<ConfigOptionStrings*>(opt_compatible)->values.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// 1) Create a name from the file name.
|
||||
// Keep the suffix (.ini, .gcode, .amf, .3mf etc) to differentiate it from the normal profiles.
|
||||
std::string name = boost::filesystem::path(path).filename().string();
|
||||
std::string name = is_external ? boost::filesystem::path(name_or_path).filename().string() : name_or_path;
|
||||
|
||||
// 2) If the loading succeeded, split and load the config into print / filament / printer settings.
|
||||
// First load the print and printer presets.
|
||||
for (size_t i_group = 0; i_group < 2; ++ i_group) {
|
||||
PresetCollection &presets = (i_group == 0) ? this->prints : this->printers;
|
||||
presets.load_preset(path, name, config).is_external = true;
|
||||
Preset &preset = presets.load_preset(is_external ? name_or_path : presets.path_from_name(name), name, config);
|
||||
if (is_external)
|
||||
preset.is_external = true;
|
||||
else
|
||||
preset.save();
|
||||
}
|
||||
|
||||
// 3) Now load the filaments. If there are multiple filament presets, split them and load them.
|
||||
|
@ -296,7 +354,12 @@ void PresetBundle::load_config_file_config(const std::string &path, const Dynami
|
|||
auto *filament_diameter = dynamic_cast<const ConfigOptionFloats*>(config.option("filament_diameter"));
|
||||
size_t num_extruders = std::min(nozzle_diameter->values.size(), filament_diameter->values.size());
|
||||
if (num_extruders <= 1) {
|
||||
this->filaments.load_preset(path, name, config).is_external = true;
|
||||
Preset &preset = this->filaments.load_preset(
|
||||
is_external ? name_or_path : this->filaments.path_from_name(name), name, config);
|
||||
if (is_external)
|
||||
preset.is_external = true;
|
||||
else
|
||||
preset.save();
|
||||
this->filament_presets.clear();
|
||||
this->filament_presets.emplace_back(name);
|
||||
} else {
|
||||
|
@ -310,7 +373,7 @@ void PresetBundle::load_config_file_config(const std::string &path, const Dynami
|
|||
if (other_opt->is_scalar()) {
|
||||
for (size_t i = 0; i < configs.size(); ++ i)
|
||||
configs[i].option(key, false)->set(other_opt);
|
||||
} else {
|
||||
} else if (key != "compatible_printers") {
|
||||
for (size_t i = 0; i < configs.size(); ++ i)
|
||||
static_cast<ConfigOptionVectorBase*>(configs[i].option(key, false))->set_at(other_opt, 0, i);
|
||||
}
|
||||
|
@ -323,11 +386,20 @@ void PresetBundle::load_config_file_config(const std::string &path, const Dynami
|
|||
suffix[0] = 0;
|
||||
else
|
||||
sprintf(suffix, " (%d)", i);
|
||||
std::string new_name = name + suffix;
|
||||
// Load all filament presets, but only select the first one in the preset dialog.
|
||||
this->filaments.load_preset(path, name + suffix, std::move(configs[i]), i == 0).is_external = true;
|
||||
this->filament_presets.emplace_back(name + suffix);
|
||||
Preset &preset = this->filaments.load_preset(
|
||||
is_external ? name_or_path : this->filaments.path_from_name(new_name),
|
||||
new_name, std::move(configs[i]), i == 0);
|
||||
if (is_external)
|
||||
preset.is_external = true;
|
||||
else
|
||||
preset.save();
|
||||
this->filament_presets.emplace_back(new_name);
|
||||
}
|
||||
}
|
||||
|
||||
this->update_compatible_with_printer(false);
|
||||
}
|
||||
|
||||
// Load the active configuration of a config bundle from a boost property_tree. This is a private method called from load_config_file.
|
||||
|
@ -335,7 +407,8 @@ void PresetBundle::load_config_file_config_bundle(const std::string &path, const
|
|||
{
|
||||
// 1) Load the config bundle into a temp data.
|
||||
PresetBundle tmp_bundle;
|
||||
tmp_bundle.load_configbundle(path);
|
||||
// Load the config bundle, don't save the loaded presets to user profile directory.
|
||||
tmp_bundle.load_configbundle(path, 0);
|
||||
std::string bundle_name = std::string(" - ") + boost::filesystem::path(path).filename().string();
|
||||
|
||||
// 2) Extract active configs from the config bundle, copy them and activate them in this bundle.
|
||||
|
@ -368,6 +441,14 @@ void PresetBundle::load_config_file_config_bundle(const std::string &path, const
|
|||
}
|
||||
assert(! preset_name_dst.empty());
|
||||
// Save preset_src->config into collection_dst under preset_name_dst.
|
||||
// The "compatible_printers" field should not have been exported into a config.ini or a G-code anyway,
|
||||
// but some of the alpha versions of Slic3r did.
|
||||
ConfigOption *opt_compatible = preset_src->config.optptr("compatible_printers");
|
||||
if (opt_compatible != nullptr) {
|
||||
assert(opt_compatible->type() == coStrings);
|
||||
if (opt_compatible->type() == coStrings)
|
||||
static_cast<ConfigOptionStrings*>(opt_compatible)->values.clear();
|
||||
}
|
||||
collection_dst.load_preset(path, preset_name_dst, std::move(preset_src->config), activate).is_external = true;
|
||||
return preset_name_dst;
|
||||
};
|
||||
|
@ -377,12 +458,17 @@ void PresetBundle::load_config_file_config_bundle(const std::string &path, const
|
|||
this->update_multi_material_filament_presets();
|
||||
for (size_t i = 1; i < std::min(tmp_bundle.filament_presets.size(), this->filament_presets.size()); ++ i)
|
||||
this->filament_presets[i] = load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filament_presets[i], false);
|
||||
|
||||
this->update_compatible_with_printer(false);
|
||||
}
|
||||
|
||||
// Load a config bundle file, into presets and store the loaded presets into separate files
|
||||
// of the local configuration directory.
|
||||
size_t PresetBundle::load_configbundle(const std::string &path)
|
||||
size_t PresetBundle::load_configbundle(const std::string &path, unsigned int flags)
|
||||
{
|
||||
if (flags & LOAD_CFGBNDLE_RESET_USER_PROFILE)
|
||||
this->reset(flags & LOAD_CFGBNDLE_SAVE);
|
||||
|
||||
// 1) Read the complete config file into a boost::property_tree.
|
||||
namespace pt = boost::property_tree;
|
||||
pt::ptree tree;
|
||||
|
@ -444,8 +530,20 @@ size_t PresetBundle::load_configbundle(const std::string &path)
|
|||
for (auto &kvp : section.second)
|
||||
config.set_deserialize(kvp.first, kvp.second.data());
|
||||
Preset::normalize(config);
|
||||
// Decide a full path to this .ini file.
|
||||
auto file_name = boost::algorithm::iends_with(preset_name, ".ini") ? preset_name : preset_name + ".ini";
|
||||
auto file_path = (boost::filesystem::path(data_dir())
|
||||
#ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR
|
||||
// Store the print/filament/printer presets into a "presets" directory.
|
||||
/ "presets"
|
||||
#else
|
||||
// Store the print/filament/printer presets at the same location as the upstream Slic3r.
|
||||
#endif
|
||||
/ presets->name() / file_name).make_preferred();
|
||||
// Load the preset into the list of presets, save it to disk.
|
||||
presets->load_preset(Slic3r::config_path(presets->name(), preset_name), preset_name, std::move(config), false).save();
|
||||
Preset &loaded = presets->load_preset(file_path.string(), preset_name, std::move(config), false);
|
||||
if (flags & LOAD_CFGBNDLE_SAVE)
|
||||
loaded.save();
|
||||
++ presets_loaded;
|
||||
}
|
||||
}
|
||||
|
@ -462,6 +560,8 @@ size_t PresetBundle::load_configbundle(const std::string &path)
|
|||
this->update_multi_material_filament_presets();
|
||||
for (size_t i = 0; i < std::min(this->filament_presets.size(), active_filaments.size()); ++ i)
|
||||
this->filament_presets[i] = filaments.find_preset(active_filaments[i], true)->name;
|
||||
|
||||
this->update_compatible_with_printer(false);
|
||||
return presets_loaded;
|
||||
}
|
||||
|
||||
|
@ -480,8 +580,8 @@ void PresetBundle::update_multi_material_filament_presets()
|
|||
|
||||
void PresetBundle::update_compatible_with_printer(bool select_other_if_incompatible)
|
||||
{
|
||||
this->prints.update_compatible_with_printer(this->printers.get_selected_preset().name, select_other_if_incompatible);
|
||||
this->filaments.update_compatible_with_printer(this->printers.get_selected_preset().name, select_other_if_incompatible);
|
||||
this->prints.update_compatible_with_printer(this->printers.get_edited_preset(), select_other_if_incompatible);
|
||||
this->filaments.update_compatible_with_printer(this->printers.get_edited_preset(), select_other_if_incompatible);
|
||||
if (select_other_if_incompatible) {
|
||||
// Verify validity of the current filament presets.
|
||||
for (std::string &filament_name : this->filament_presets) {
|
||||
|
@ -557,9 +657,9 @@ static inline int hex_digit_to_int(const char c)
|
|||
static inline bool parse_color(const std::string &scolor, unsigned char *rgb_out)
|
||||
{
|
||||
rgb_out[0] = rgb_out[1] = rgb_out[2] = 0;
|
||||
const char *c = scolor.data() + 1;
|
||||
if (scolor.size() != 7 || scolor.front() != '#')
|
||||
return false;
|
||||
const char *c = scolor.data() + 1;
|
||||
for (size_t i = 0; i < 3; ++ i) {
|
||||
int digit1 = hex_digit_to_int(*c ++);
|
||||
int digit2 = hex_digit_to_int(*c ++);
|
||||
|
|
|
@ -15,10 +15,14 @@ public:
|
|||
PresetBundle();
|
||||
~PresetBundle();
|
||||
|
||||
// Remove all the presets but the "-- default --".
|
||||
// Optionally remove all the files referenced by the presets from the user profile directory.
|
||||
void reset(bool delete_files);
|
||||
|
||||
void setup_directories();
|
||||
|
||||
// Load ini files of all types (print, filament, printer) from the provided directory path.
|
||||
void load_presets(const std::string &dir_path);
|
||||
// Load ini files of all types (print, filament, printer) from Slic3r::data_dir() / presets.
|
||||
void load_presets();
|
||||
|
||||
// Load selections (current print, current filaments, current printer) from config.ini
|
||||
// This is done just once on application start up.
|
||||
|
@ -40,6 +44,11 @@ public:
|
|||
|
||||
DynamicPrintConfig full_config() const;
|
||||
|
||||
// Load user configuration and store it into the user profiles.
|
||||
// This method is called by the configuration wizard.
|
||||
void load_config(const std::string &name, DynamicPrintConfig config)
|
||||
{ this->load_config_file_config(name, false, std::move(config)); }
|
||||
|
||||
// Load an external config file containing the print, filament and printer presets.
|
||||
// Instead of a config file, a G-code may be loaded containing the full set of parameters.
|
||||
// In the future the configuration will likely be read from an AMF file as well.
|
||||
|
@ -51,7 +60,14 @@ public:
|
|||
// Load settings into the provided settings instance.
|
||||
// Activate the presets stored in the config bundle.
|
||||
// Returns the number of presets loaded successfully.
|
||||
size_t load_configbundle(const std::string &path);
|
||||
enum {
|
||||
// Save the profiles, which have been loaded.
|
||||
LOAD_CFGBNDLE_SAVE = 1,
|
||||
// Delete all old config profiles before loading.
|
||||
LOAD_CFGBNDLE_RESET_USER_PROFILE = 2
|
||||
};
|
||||
// Load the config bundle, store it to the user profile directory by default.
|
||||
size_t load_configbundle(const std::string &path, unsigned int flags = LOAD_CFGBNDLE_SAVE);
|
||||
|
||||
// Export a config bundle file containing all the presets and the names of the active presets.
|
||||
void export_configbundle(const std::string &path); // , const DynamicPrintConfig &settings);
|
||||
|
@ -78,7 +94,10 @@ public:
|
|||
void update_compatible_with_printer(bool select_other_if_incompatible);
|
||||
|
||||
private:
|
||||
void load_config_file_config(const std::string &path, const DynamicPrintConfig &config);
|
||||
// Load print, filament & printer presets from a config. If it is an external config, then the name is extracted from the external path.
|
||||
// and the external config is just referenced, not stored into user profile directory.
|
||||
// If it is not an external config, then the config will be stored into the user profile directory.
|
||||
void load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config);
|
||||
void load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree);
|
||||
bool load_compatible_bitmaps();
|
||||
|
||||
|
|
|
@ -55,11 +55,6 @@ static const ConfigOptionFloatOrPercent& first_positive(const ConfigOptionFloatO
|
|||
return (v1 != nullptr && v1->value > 0) ? *v1 : ((v2.value > 0) ? v2 : v3);
|
||||
}
|
||||
|
||||
static double first_positive(double v1, double v2)
|
||||
{
|
||||
return (v1 > 0.) ? v1 : v2;
|
||||
}
|
||||
|
||||
std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle &preset_bundle)
|
||||
{
|
||||
// Find out, to which nozzle index is the current filament profile assigned.
|
||||
|
@ -106,6 +101,7 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
|
|||
const auto &solid_infill_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("solid_infill_extrusion_width");
|
||||
const auto &support_material_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("support_material_extrusion_width");
|
||||
const auto &top_infill_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("top_infill_extrusion_width");
|
||||
const auto &first_layer_speed = *print_config.option<ConfigOptionFloatOrPercent>("first_layer_speed");
|
||||
|
||||
// Index of an extruder assigned to a feature. If set to 0, an active extruder will be used for a multi-material print.
|
||||
// If different from idx_extruder, it will not be taken into account for this hint.
|
||||
|
@ -136,12 +132,18 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
|
|||
const float bfr = bridging ? bridge_flow_ratio : 0.f;
|
||||
double max_flow = 0.;
|
||||
std::string max_flow_extrusion_type;
|
||||
auto limit_by_first_layer_speed = [&first_layer_speed, first_layer](double speed_normal, double speed_max) {
|
||||
if (first_layer && first_layer_speed.value > 0)
|
||||
// Apply the first layer limit.
|
||||
speed_normal = first_layer_speed.get_abs_value(speed_normal);
|
||||
return (speed_normal > 0.) ? speed_normal : speed_max;
|
||||
};
|
||||
if (perimeter_extruder_active) {
|
||||
double external_perimeter_rate = Flow::new_from_config_width(frExternalPerimeter,
|
||||
first_positive(first_layer_extrusion_width_ptr, external_perimeter_extrusion_width, extrusion_width),
|
||||
nozzle_diameter, lh, bfr).mm3_per_mm() *
|
||||
(bridging ? bridge_speed :
|
||||
first_positive(std::max(external_perimeter_speed, small_perimeter_speed), max_print_speed));
|
||||
limit_by_first_layer_speed(std::max(external_perimeter_speed, small_perimeter_speed), max_print_speed));
|
||||
if (max_flow < external_perimeter_rate) {
|
||||
max_flow = external_perimeter_rate;
|
||||
max_flow_extrusion_type = "external perimeters";
|
||||
|
@ -150,7 +152,7 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
|
|||
first_positive(first_layer_extrusion_width_ptr, perimeter_extrusion_width, extrusion_width),
|
||||
nozzle_diameter, lh, bfr).mm3_per_mm() *
|
||||
(bridging ? bridge_speed :
|
||||
first_positive(std::max(perimeter_speed, small_perimeter_speed), max_print_speed));
|
||||
limit_by_first_layer_speed(std::max(perimeter_speed, small_perimeter_speed), max_print_speed));
|
||||
if (max_flow < perimeter_rate) {
|
||||
max_flow = perimeter_rate;
|
||||
max_flow_extrusion_type = "perimeters";
|
||||
|
@ -159,7 +161,7 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
|
|||
if (! bridging && infill_extruder_active) {
|
||||
double infill_rate = Flow::new_from_config_width(frInfill,
|
||||
first_positive(first_layer_extrusion_width_ptr, infill_extrusion_width, extrusion_width),
|
||||
nozzle_diameter, lh, bfr).mm3_per_mm() * first_positive(infill_speed, max_print_speed);
|
||||
nozzle_diameter, lh, bfr).mm3_per_mm() * limit_by_first_layer_speed(infill_speed, max_print_speed);
|
||||
if (max_flow < infill_rate) {
|
||||
max_flow = infill_rate;
|
||||
max_flow_extrusion_type = "infill";
|
||||
|
@ -169,7 +171,7 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
|
|||
double solid_infill_rate = Flow::new_from_config_width(frInfill,
|
||||
first_positive(first_layer_extrusion_width_ptr, solid_infill_extrusion_width, extrusion_width),
|
||||
nozzle_diameter, lh, 0).mm3_per_mm() *
|
||||
(bridging ? bridge_speed : first_positive(solid_infill_speed, max_print_speed));
|
||||
(bridging ? bridge_speed : limit_by_first_layer_speed(solid_infill_speed, max_print_speed));
|
||||
if (max_flow < solid_infill_rate) {
|
||||
max_flow = solid_infill_rate;
|
||||
max_flow_extrusion_type = "solid infill";
|
||||
|
@ -177,7 +179,7 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
|
|||
if (! bridging) {
|
||||
double top_solid_infill_rate = Flow::new_from_config_width(frInfill,
|
||||
first_positive(first_layer_extrusion_width_ptr, top_infill_extrusion_width, extrusion_width),
|
||||
nozzle_diameter, lh, bfr).mm3_per_mm() * first_positive(top_solid_infill_speed, max_print_speed);
|
||||
nozzle_diameter, lh, bfr).mm3_per_mm() * limit_by_first_layer_speed(top_solid_infill_speed, max_print_speed);
|
||||
if (max_flow < top_solid_infill_rate) {
|
||||
max_flow = top_solid_infill_rate;
|
||||
max_flow_extrusion_type = "top solid infill";
|
||||
|
@ -188,7 +190,7 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
|
|||
double support_material_rate = Flow::new_from_config_width(frSupportMaterial,
|
||||
first_positive(first_layer_extrusion_width_ptr, support_material_extrusion_width, extrusion_width),
|
||||
nozzle_diameter, lh, bfr).mm3_per_mm() *
|
||||
(bridging ? bridge_speed : first_positive(support_material_speed, max_print_speed));
|
||||
(bridging ? bridge_speed : limit_by_first_layer_speed(support_material_speed, max_print_speed));
|
||||
if (max_flow < support_material_rate) {
|
||||
max_flow = support_material_rate;
|
||||
max_flow_extrusion_type = "support";
|
||||
|
@ -198,23 +200,23 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
|
|||
double support_material_interface_rate = Flow::new_from_config_width(frSupportMaterialInterface,
|
||||
first_positive(first_layer_extrusion_width_ptr, support_material_extrusion_width, extrusion_width),
|
||||
nozzle_diameter, lh, bfr).mm3_per_mm() *
|
||||
(bridging ? bridge_speed : first_positive(support_material_interface_speed, max_print_speed));
|
||||
(bridging ? bridge_speed : limit_by_first_layer_speed(support_material_interface_speed, max_print_speed));
|
||||
if (max_flow < support_material_interface_rate) {
|
||||
max_flow = support_material_interface_rate;
|
||||
max_flow_extrusion_type = "support interface";
|
||||
}
|
||||
}
|
||||
|
||||
//FIXME handle gap_fill_speed
|
||||
if (! out.empty())
|
||||
out += "\n";
|
||||
out += (first_layer ? "First layer volumetric" : (bridging ? "Bridging volumetric" : "Volumetric"));
|
||||
out += " flow rate is maximized ";
|
||||
out += ((max_volumetric_speed > 0 && max_volumetric_speed < max_flow) ?
|
||||
bool limited_by_max_volumetric_speed = max_volumetric_speed > 0 && max_volumetric_speed < max_flow;
|
||||
out += (limited_by_max_volumetric_speed ?
|
||||
"by the print profile maximum" :
|
||||
("when printing " + max_flow_extrusion_type))
|
||||
("when printing " + max_flow_extrusion_type))
|
||||
+ " with a volumetric rate ";
|
||||
if (max_volumetric_speed > 0 && max_volumetric_speed < max_flow)
|
||||
if (limited_by_max_volumetric_speed)
|
||||
max_flow = max_volumetric_speed;
|
||||
char buf[2048];
|
||||
sprintf(buf, "%3.2f mm³/s", max_flow);
|
||||
|
|
|
@ -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()); %};
|
||||
|
|
|
@ -22,3 +22,18 @@ bool debugged()
|
|||
|
||||
void break_to_debugger()
|
||||
%code{% Slic3r::GUI::break_to_debugger(); %};
|
||||
|
||||
void set_wxapp(SV *ui)
|
||||
%code%{ Slic3r::GUI::set_wxapp((wxApp*)wxPli_sv_2_object(aTHX_ ui, "Wx::App")); %};
|
||||
|
||||
void set_main_frame(SV *ui)
|
||||
%code%{ Slic3r::GUI::set_main_frame((wxFrame*)wxPli_sv_2_object(aTHX_ ui, "Wx::Frame")); %};
|
||||
|
||||
void set_tab_panel(SV *ui)
|
||||
%code%{ Slic3r::GUI::set_tab_panel((wxNotebook*)wxPli_sv_2_object(aTHX_ ui, "Wx::Notebook")); %};
|
||||
|
||||
void add_debug_menu(SV *ui)
|
||||
%code%{ Slic3r::GUI::add_debug_menu((wxMenuBar*)wxPli_sv_2_object(aTHX_ ui, "Wx::MenuBar")); %};
|
||||
|
||||
void create_preset_tab(const char *name)
|
||||
%code%{ Slic3r::GUI::create_preset_tab(name); %};
|
||||
|
|
|
@ -14,7 +14,9 @@
|
|||
bool external() %code%{ RETVAL = THIS->is_external; %};
|
||||
bool visible() %code%{ RETVAL = THIS->is_visible; %};
|
||||
bool dirty() %code%{ RETVAL = THIS->is_dirty; %};
|
||||
bool is_compatible_with_printer(char *active_printer) const;
|
||||
bool compatible() %code%{ RETVAL = THIS->is_compatible; %};
|
||||
bool is_compatible_with_printer(Preset *active_printer)
|
||||
%code%{ RETVAL = THIS->is_compatible_with_printer(*active_printer); %};
|
||||
|
||||
const char* name() %code%{ RETVAL = THIS->name.c_str(); %};
|
||||
const char* file() %code%{ RETVAL = THIS->file.c_str(); %};
|
||||
|
@ -98,6 +100,8 @@ PresetCollection::arrayref()
|
|||
PresetBundle();
|
||||
~PresetBundle();
|
||||
|
||||
void reset(bool delete_files);
|
||||
|
||||
void setup_directories()
|
||||
%code%{
|
||||
try {
|
||||
|
@ -106,12 +110,21 @@ PresetCollection::arrayref()
|
|||
croak("Cannot create configuration directories:\n%s\n", e.what());
|
||||
}
|
||||
%};
|
||||
void load_presets(const char *dir_path)
|
||||
void load_presets()
|
||||
%code%{
|
||||
try {
|
||||
THIS->load_presets(dir_path);
|
||||
THIS->load_presets();
|
||||
} catch (std::exception& e) {
|
||||
croak("Loading of Slic3r presets from %s failed.\n\n%s\n", dir_path, e.what());
|
||||
croak("Loading of Slic3r presets from %s failed.\n\n%s\n",
|
||||
Slic3r::data_dir().c_str(), e.what());
|
||||
}
|
||||
%};
|
||||
void load_config(const char *name, DynamicPrintConfig *config)
|
||||
%code%{
|
||||
try {
|
||||
THIS->load_config(name, *config);
|
||||
} catch (std::exception& e) {
|
||||
croak("Loading a configuration %s failed:\n%s\n", name, e.what());
|
||||
}
|
||||
%};
|
||||
void load_config_file(const char *path)
|
||||
|
@ -125,7 +138,7 @@ PresetCollection::arrayref()
|
|||
size_t load_configbundle(const char *path)
|
||||
%code%{
|
||||
try {
|
||||
RETVAL = THIS->load_configbundle(path);
|
||||
RETVAL = THIS->load_configbundle(path, PresetBundle::LOAD_CFGBNDLE_SAVE);
|
||||
} catch (std::exception& e) {
|
||||
croak("Loading of a config bundle %s failed:\n%s\n", path, e.what());
|
||||
}
|
||||
|
|
|
@ -50,10 +50,6 @@
|
|||
int id();
|
||||
void set_id(int id);
|
||||
Ref<PrintObject> object();
|
||||
Ref<Layer> upper_layer()
|
||||
%code%{ RETVAL = THIS->upper_layer; %};
|
||||
Ref<Layer> lower_layer()
|
||||
%code%{ RETVAL = THIS->lower_layer; %};
|
||||
bool slicing_errors()
|
||||
%code%{ RETVAL = THIS->slicing_errors; %};
|
||||
coordf_t slice_z()
|
||||
|
@ -63,15 +59,6 @@
|
|||
coordf_t height()
|
||||
%code%{ RETVAL = THIS->height; %};
|
||||
|
||||
void set_upper_layer(Layer *layer)
|
||||
%code%{ THIS->upper_layer = layer; %};
|
||||
void set_lower_layer(Layer *layer)
|
||||
%code%{ THIS->lower_layer = layer; %};
|
||||
bool has_upper_layer()
|
||||
%code%{ RETVAL = (THIS->upper_layer != NULL); %};
|
||||
bool has_lower_layer()
|
||||
%code%{ RETVAL = (THIS->lower_layer != NULL); %};
|
||||
|
||||
size_t region_count();
|
||||
Ref<LayerRegion> get_region(int idx);
|
||||
Ref<LayerRegion> add_region(PrintRegion* print_region);
|
||||
|
@ -112,10 +99,6 @@
|
|||
int id();
|
||||
void set_id(int id);
|
||||
Ref<PrintObject> object();
|
||||
Ref<SupportLayer> upper_layer()
|
||||
%code%{ RETVAL = (SupportLayer*)THIS->upper_layer; %};
|
||||
Ref<SupportLayer> lower_layer()
|
||||
%code%{ RETVAL = (SupportLayer*)THIS->lower_layer; %};
|
||||
bool slicing_errors()
|
||||
%code%{ RETVAL = THIS->slicing_errors; %};
|
||||
coordf_t slice_z()
|
||||
|
@ -125,15 +108,6 @@
|
|||
coordf_t height()
|
||||
%code%{ RETVAL = THIS->height; %};
|
||||
|
||||
void set_upper_layer(SupportLayer *layer)
|
||||
%code%{ THIS->upper_layer = layer; %};
|
||||
void set_lower_layer(SupportLayer *layer)
|
||||
%code%{ THIS->lower_layer = layer; %};
|
||||
bool has_upper_layer()
|
||||
%code%{ RETVAL = (THIS->upper_layer != NULL); %};
|
||||
bool has_lower_layer()
|
||||
%code%{ RETVAL = (THIS->lower_layer != NULL); %};
|
||||
|
||||
size_t region_count();
|
||||
Ref<LayerRegion> get_region(int idx);
|
||||
Ref<LayerRegion> add_region(PrintRegion* print_region);
|
||||
|
|
|
@ -14,5 +14,20 @@
|
|||
%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());
|
||||
}
|
||||
%};
|
||||
|
||||
bool evaluate_boolean_expression(const char *str) const
|
||||
%code%{
|
||||
try {
|
||||
RETVAL = THIS->evaluate_boolean_expression(str, THIS->config());
|
||||
} catch (std::exception& e) {
|
||||
croak(e.what());
|
||||
}
|
||||
%};
|
||||
};
|
||||
|
|
|
@ -91,7 +91,6 @@ _constant()
|
|||
size_t support_layer_count();
|
||||
void clear_support_layers();
|
||||
Ref<SupportLayer> get_support_layer(int idx);
|
||||
Ref<SupportLayer> add_support_layer(int id, coordf_t height, coordf_t print_z);
|
||||
|
||||
bool step_done(PrintObjectStep step)
|
||||
%code%{ RETVAL = THIS->state.is_done(step); %};
|
||||
|
@ -206,8 +205,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)
|
||||
|
@ -215,10 +220,11 @@ _constant()
|
|||
bool has_infinite_skirt();
|
||||
bool has_skirt();
|
||||
std::vector<unsigned int> extruders() const;
|
||||
void validate() %code%{
|
||||
int validate() %code%{
|
||||
std::string err = THIS->validate();
|
||||
if (! err.empty())
|
||||
throw std::invalid_argument(err.c_str());
|
||||
if (! err.empty())
|
||||
croak("Configuration is not valid: %s\n", err.c_str());
|
||||
RETVAL = 1;
|
||||
%};
|
||||
Clone<BoundingBox> bounding_box();
|
||||
Clone<BoundingBox> total_bounding_box();
|
||||
|
|
|
@ -60,6 +60,18 @@ var_dir()
|
|||
RETVAL = const_cast<char*>(Slic3r::var_dir().c_str());
|
||||
OUTPUT: RETVAL
|
||||
|
||||
void
|
||||
set_resources_dir(dir)
|
||||
char *dir;
|
||||
CODE:
|
||||
Slic3r::set_resources_dir(dir);
|
||||
|
||||
char*
|
||||
resources_dir()
|
||||
CODE:
|
||||
RETVAL = const_cast<char*>(Slic3r::resources_dir().c_str());
|
||||
OUTPUT: RETVAL
|
||||
|
||||
std::string
|
||||
var(file_name)
|
||||
const char *file_name;
|
||||
|
@ -79,14 +91,6 @@ data_dir()
|
|||
RETVAL = const_cast<char*>(Slic3r::data_dir().c_str());
|
||||
OUTPUT: RETVAL
|
||||
|
||||
std::string
|
||||
config_path(section, name)
|
||||
const char *section;
|
||||
const char *name;
|
||||
CODE:
|
||||
RETVAL = Slic3r::config_path(section, name);
|
||||
OUTPUT: RETVAL
|
||||
|
||||
std::string
|
||||
encode_path(src)
|
||||
const char *src;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue