Merge branch 'dev' of https://github.com/prusa3d/PrusaSlicer into dev
This commit is contained in:
commit
d09a0cea83
@ -139,7 +139,7 @@ documentation_link= https://help.prusa3d.com/en/article/per-model-settings_1674
|
||||
disabled_tags = SLA
|
||||
|
||||
[hint:Solid infill threshold area]
|
||||
text = Solid infill threshold area\nDid you know that you can make parts of your model with a small cross-section be filled with solid infill automatically? Set the<a>Solid infill threshold area</a>.(Expert mode only.)
|
||||
text = Solid infill threshold area\nDid you know that you can make parts of your model with a small cross-section be filled with solid infill automatically? Set the<a>Solid infill threshold area.</a>(Expert mode only.)
|
||||
hypertext_type = settings
|
||||
hypertext_settings_opt = solid_infill_below_area
|
||||
hypertext_settings_type = 1
|
||||
@ -197,7 +197,7 @@ documentation_link = https://help.prusa3d.com/en/article/insert-pause-or-custom-
|
||||
disabled_tags = SLA
|
||||
|
||||
[hint:Configuration snapshots]
|
||||
text = Configuration snapshots\nDid you know that roll back to a complete backup of all system and user profiles? You can view and move back and forth between snapshots using the Configuration - <a>Configuration snapshots menu</a>.
|
||||
text = Configuration snapshots\nDid you know that you can roll back to a complete backup of all system and user profiles? You can view and move back and forth between snapshots using the Configuration - <a>Configuration snapshots menu</a>.
|
||||
documentation_link = https://help.prusa3d.com/en/article/configuration-snapshots_1776
|
||||
hypertext_type = menubar
|
||||
hypertext_menubar_menu_name = Configuration
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -100,6 +100,8 @@ set(SLIC3R_SOURCES
|
||||
GCode/ThumbnailData.hpp
|
||||
GCode/CoolingBuffer.cpp
|
||||
GCode/CoolingBuffer.hpp
|
||||
GCode/FindReplace.cpp
|
||||
GCode/FindReplace.hpp
|
||||
GCode/PostProcessor.cpp
|
||||
GCode/PostProcessor.hpp
|
||||
# GCode/PressureEqualizer.cpp
|
||||
|
@ -1099,6 +1099,11 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
// modifies m_silent_time_estimator_enabled
|
||||
DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled);
|
||||
|
||||
if (! print.config().gcode_substitutions.values.empty()) {
|
||||
m_find_replace = make_unique<GCodeFindReplace>(print.config());
|
||||
file.set_find_replace(m_find_replace.get());
|
||||
}
|
||||
|
||||
// resets analyzer's tracking data
|
||||
m_last_height = 0.f;
|
||||
m_last_layer_z = 0.f;
|
||||
@ -1560,15 +1565,25 @@ void GCode::process_layers(
|
||||
[&cooling_buffer = *this->m_cooling_buffer.get()](GCode::LayerResult in) -> std::string {
|
||||
return cooling_buffer.process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush);
|
||||
});
|
||||
const auto find_replace = tbb::make_filter<std::string, std::string>(slic3r_tbb_filtermode::serial_in_order,
|
||||
[&self = *this->m_find_replace.get()](std::string s) -> std::string {
|
||||
return self.process_layer(std::move(s));
|
||||
});
|
||||
const auto output = tbb::make_filter<std::string, void>(slic3r_tbb_filtermode::serial_in_order,
|
||||
[&output_stream](std::string s) { output_stream.write(s); }
|
||||
);
|
||||
|
||||
// The pipeline elements are joined using const references, thus no copying is performed.
|
||||
if (m_spiral_vase)
|
||||
output_stream.set_find_replace(nullptr);
|
||||
if (m_spiral_vase && m_find_replace)
|
||||
tbb::parallel_pipeline(12, generator & spiral_vase & cooling & find_replace & output);
|
||||
else if (m_spiral_vase)
|
||||
tbb::parallel_pipeline(12, generator & spiral_vase & cooling & output);
|
||||
else if (m_find_replace)
|
||||
tbb::parallel_pipeline(12, generator & cooling & find_replace & output);
|
||||
else
|
||||
tbb::parallel_pipeline(12, generator & cooling & output);
|
||||
output_stream.set_find_replace(m_find_replace.get());
|
||||
}
|
||||
|
||||
// Process all layers of a single object instance (sequential mode) with a parallel pipeline:
|
||||
@ -1603,15 +1618,25 @@ void GCode::process_layers(
|
||||
[&cooling_buffer = *this->m_cooling_buffer.get()](GCode::LayerResult in)->std::string {
|
||||
return cooling_buffer.process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush);
|
||||
});
|
||||
const auto find_replace = tbb::make_filter<std::string, std::string>(slic3r_tbb_filtermode::serial_in_order,
|
||||
[&self = *this->m_find_replace.get()](std::string s) -> std::string {
|
||||
return self.process_layer(std::move(s));
|
||||
});
|
||||
const auto output = tbb::make_filter<std::string, void>(slic3r_tbb_filtermode::serial_in_order,
|
||||
[&output_stream](std::string s) { output_stream.write(s); }
|
||||
);
|
||||
|
||||
// The pipeline elements are joined using const references, thus no copying is performed.
|
||||
if (m_spiral_vase)
|
||||
output_stream.set_find_replace(nullptr);
|
||||
if (m_spiral_vase && m_find_replace)
|
||||
tbb::parallel_pipeline(12, generator & spiral_vase & cooling & find_replace & output);
|
||||
else if (m_spiral_vase)
|
||||
tbb::parallel_pipeline(12, generator & spiral_vase & cooling & output);
|
||||
else if (m_find_replace)
|
||||
tbb::parallel_pipeline(12, generator & cooling & find_replace & output);
|
||||
else
|
||||
tbb::parallel_pipeline(12, generator & cooling & output);
|
||||
output_stream.set_find_replace(m_find_replace.get());
|
||||
}
|
||||
|
||||
std::string GCode::placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override)
|
||||
@ -2824,11 +2849,11 @@ void GCode::GCodeOutputStream::close()
|
||||
void GCode::GCodeOutputStream::write(const char *what)
|
||||
{
|
||||
if (what != nullptr) {
|
||||
const char* gcode = what;
|
||||
// writes string to file
|
||||
fwrite(gcode, 1, ::strlen(gcode), this->f);
|
||||
//FIXME don't allocate a string, maybe process a batch of lines?
|
||||
m_processor.process_buffer(std::string(gcode));
|
||||
std::string gcode(m_find_replace ? m_find_replace->process_layer(what) : what);
|
||||
// writes string to file
|
||||
fwrite(gcode.c_str(), 1, gcode.size(), this->f);
|
||||
m_processor.process_buffer(gcode);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "PrintConfig.hpp"
|
||||
#include "GCode/AvoidCrossingPerimeters.hpp"
|
||||
#include "GCode/CoolingBuffer.hpp"
|
||||
#include "GCode/FindReplace.hpp"
|
||||
#include "GCode/SpiralVase.hpp"
|
||||
#include "GCode/ToolOrdering.hpp"
|
||||
#include "GCode/WipeTower.hpp"
|
||||
@ -189,6 +190,11 @@ private:
|
||||
GCodeOutputStream(FILE *f, GCodeProcessor &processor) : f(f), m_processor(processor) {}
|
||||
~GCodeOutputStream() { this->close(); }
|
||||
|
||||
// Set a find-replace post-processor to modify the G-code before GCodePostProcessor.
|
||||
// It is being set to null inside process_layers(), because the find-replace process
|
||||
// is being called on a secondary thread to improve performance.
|
||||
void set_find_replace(GCodeFindReplace *find_replace) { m_find_replace = find_replace; }
|
||||
|
||||
bool is_open() const { return f; }
|
||||
bool is_error() const;
|
||||
|
||||
@ -208,7 +214,9 @@ private:
|
||||
void write_format(const char* format, ...);
|
||||
|
||||
private:
|
||||
FILE *f = nullptr;
|
||||
FILE *f { nullptr };
|
||||
// Find-replace post-processor to be called before GCodePostProcessor.
|
||||
GCodeFindReplace *m_find_replace { nullptr };
|
||||
GCodeProcessor &m_processor;
|
||||
};
|
||||
void _do_export(Print &print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb);
|
||||
@ -393,6 +401,7 @@ private:
|
||||
|
||||
std::unique_ptr<CoolingBuffer> m_cooling_buffer;
|
||||
std::unique_ptr<SpiralVase> m_spiral_vase;
|
||||
std::unique_ptr<GCodeFindReplace> m_find_replace;
|
||||
#ifdef HAS_PRESSURE_EQUALIZER
|
||||
std::unique_ptr<PressureEqualizer> m_pressure_equalizer;
|
||||
#endif /* HAS_PRESSURE_EQUALIZER */
|
||||
|
@ -439,7 +439,15 @@ static std::vector<TravelPoint> simplify_travel(const AvoidCrossingPerimeters::B
|
||||
visitor.pt_current = ¤t_point;
|
||||
|
||||
if (!next.do_not_remove)
|
||||
for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size() && !travel[point_idx_2].do_not_remove; ++point_idx_2) {
|
||||
for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) {
|
||||
// Workaround for some issue in MSVC 19.29.30037 32-bit compiler.
|
||||
#if defined(_WIN32) && !defined(_WIN64)
|
||||
if (bool volatile do_not_remove = travel[point_idx_2].do_not_remove; do_not_remove)
|
||||
break;
|
||||
#else
|
||||
if (travel[point_idx_2].do_not_remove)
|
||||
break;
|
||||
#endif
|
||||
if (travel[point_idx_2].point == current_point) {
|
||||
next = travel[point_idx_2];
|
||||
point_idx = point_idx_2;
|
||||
|
153
src/libslic3r/GCode/FindReplace.cpp
Normal file
153
src/libslic3r/GCode/FindReplace.cpp
Normal file
@ -0,0 +1,153 @@
|
||||
#include "FindReplace.hpp"
|
||||
#include "../Utils.hpp"
|
||||
|
||||
#include <cctype> // isalpha
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Similar to https://npp-user-manual.org/docs/searching/#extended-search-mode
|
||||
const void unescape_extended_search_mode(std::string &s)
|
||||
{
|
||||
boost::replace_all(s, "\\n", "\n"); // Line Feed control character LF (ASCII 0x0A)
|
||||
boost::replace_all(s, "\\r", "\r"); // Carriage Return control character CR (ASCII 0x0D)
|
||||
boost::replace_all(s, "\\t", "\t"); // TAB control character (ASCII 0x09)
|
||||
boost::replace_all(s, "\\0", "\0x00"); // NUL control character (ASCII 0x00)
|
||||
boost::replace_all(s, "\\\\", "\\"); // Backslash character (ASCII 0x5C)
|
||||
|
||||
// Notepad++ also supports:
|
||||
// \o: the octal representation of a byte, made of 3 digits in the 0-7 range
|
||||
// \d: the decimal representation of a byte, made of 3 digits in the 0-9 range
|
||||
// \x: the hexadecimal representation of a byte, made of 2 digits in the 0-9, A-F/a-f range.
|
||||
// \u: The hexadecimal representation of a two-byte character, made of 4 digits in the 0-9, A-F/a-f range.
|
||||
}
|
||||
|
||||
GCodeFindReplace::GCodeFindReplace(const std::vector<std::string> &gcode_substitutions)
|
||||
{
|
||||
if ((gcode_substitutions.size() % 3) != 0)
|
||||
throw RuntimeError("Invalid length of gcode_substitutions parameter");
|
||||
|
||||
m_substitutions.reserve(gcode_substitutions.size() / 3);
|
||||
for (size_t i = 0; i < gcode_substitutions.size(); i += 3) {
|
||||
Substitution out;
|
||||
try {
|
||||
out.plain_pattern = gcode_substitutions[i];
|
||||
out.format = gcode_substitutions[i + 1];
|
||||
const std::string ¶ms = gcode_substitutions[i + 2];
|
||||
out.regexp = strchr(params.c_str(), 'r') != nullptr || strchr(params.c_str(), 'R') != nullptr;
|
||||
out.case_insensitive = strchr(params.c_str(), 'i') != nullptr || strchr(params.c_str(), 'I') != nullptr;
|
||||
out.whole_word = strchr(params.c_str(), 'w') != nullptr || strchr(params.c_str(), 'W') != nullptr;
|
||||
if (out.regexp) {
|
||||
out.regexp_pattern.assign(
|
||||
out.whole_word ?
|
||||
std::string("\\b") + out.plain_pattern + "\\b" :
|
||||
out.plain_pattern,
|
||||
(out.case_insensitive ? boost::regex::icase : 0) | boost::regex::optimize);
|
||||
} else {
|
||||
unescape_extended_search_mode(out.plain_pattern);
|
||||
unescape_extended_search_mode(out.format);
|
||||
}
|
||||
} catch (const std::exception &ex) {
|
||||
throw RuntimeError(std::string("Invalid gcode_substitutions parameter, failed to compile regular expression: ") + ex.what());
|
||||
}
|
||||
m_substitutions.emplace_back(std::move(out));
|
||||
}
|
||||
}
|
||||
|
||||
class ToStringIterator
|
||||
{
|
||||
public:
|
||||
using iterator_category = std::output_iterator_tag;
|
||||
using value_type = void;
|
||||
using difference_type = void;
|
||||
using pointer = void;
|
||||
using reference = void;
|
||||
|
||||
ToStringIterator(std::string &data) : m_data(&data) {}
|
||||
|
||||
ToStringIterator& operator=(const char val) {
|
||||
size_t needs = m_data->size() + 1;
|
||||
if (m_data->capacity() < needs)
|
||||
m_data->reserve(next_highest_power_of_2(needs));
|
||||
m_data->push_back(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ToStringIterator& operator*() { return *this; }
|
||||
ToStringIterator& operator++() { return *this; }
|
||||
ToStringIterator operator++(int) { return *this; }
|
||||
|
||||
private:
|
||||
std::string *m_data;
|
||||
};
|
||||
|
||||
template<typename FindFn>
|
||||
static void find_and_replace_whole_word(std::string &inout, const std::string &match, const std::string &replace, FindFn find_fn)
|
||||
{
|
||||
if (! match.empty() && inout.size() >= match.size() && match != replace) {
|
||||
std::string out;
|
||||
auto [i, j] = find_fn(inout, 0, match);
|
||||
size_t k = 0;
|
||||
for (; i != std::string::npos; std::tie(i, j) = find_fn(inout, i, match)) {
|
||||
if ((i == 0 || ! std::isalnum(inout[i - 1])) && (j == inout.size() || ! std::isalnum(inout[j]))) {
|
||||
out.reserve(inout.size());
|
||||
out.append(inout, k, i - k);
|
||||
out.append(replace);
|
||||
i = k = j;
|
||||
} else
|
||||
i += match.size();
|
||||
}
|
||||
if (k > 0) {
|
||||
out.append(inout, k, inout.size() - k);
|
||||
inout.swap(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string GCodeFindReplace::process_layer(const std::string &ain)
|
||||
{
|
||||
std::string out;
|
||||
const std::string *in = &ain;
|
||||
std::string temp;
|
||||
temp.reserve(in->size());
|
||||
|
||||
for (const Substitution &substitution : m_substitutions) {
|
||||
if (substitution.regexp) {
|
||||
temp.clear();
|
||||
temp.reserve(in->size());
|
||||
boost::regex_replace(ToStringIterator(temp), in->begin(), in->end(),
|
||||
substitution.regexp_pattern, substitution.format, boost::match_default | boost::match_not_dot_newline | boost::match_not_dot_null | boost::format_all);
|
||||
std::swap(out, temp);
|
||||
} else {
|
||||
if (in == &ain)
|
||||
out = ain;
|
||||
// Plain substitution
|
||||
if (substitution.case_insensitive) {
|
||||
if (substitution.whole_word)
|
||||
find_and_replace_whole_word(out, substitution.plain_pattern, substitution.format,
|
||||
[](const std::string &str, size_t start_pos, const std::string &match) {
|
||||
auto begin = str.begin() + start_pos;
|
||||
boost::iterator_range<std::string::const_iterator> r1(begin, str.end());
|
||||
boost::iterator_range<std::string::const_iterator> r2(match.begin(), match.end());
|
||||
auto res = boost::ifind_first(r1, r2);
|
||||
return res ? std::make_pair(size_t(res.begin() - str.begin()), size_t(res.end() - str.begin())) : std::make_pair(std::string::npos, std::string::npos);
|
||||
});
|
||||
else
|
||||
boost::ireplace_all(out, substitution.plain_pattern, substitution.format);
|
||||
} else {
|
||||
if (substitution.whole_word)
|
||||
find_and_replace_whole_word(out, substitution.plain_pattern, substitution.format,
|
||||
[](const std::string &str, size_t start_pos, const std::string &match) {
|
||||
size_t pos = str.find(match, start_pos);
|
||||
return std::make_pair(pos, pos + (pos == std::string::npos ? 0 : match.size()));
|
||||
});
|
||||
else
|
||||
boost::replace_all(out, substitution.plain_pattern, substitution.format);
|
||||
}
|
||||
}
|
||||
in = &out;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
}
|
33
src/libslic3r/GCode/FindReplace.hpp
Normal file
33
src/libslic3r/GCode/FindReplace.hpp
Normal file
@ -0,0 +1,33 @@
|
||||
#ifndef slic3r_FindReplace_hpp_
|
||||
#define slic3r_FindReplace_hpp_
|
||||
|
||||
#include "../PrintConfig.hpp"
|
||||
|
||||
#include <boost/regex.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class GCodeFindReplace {
|
||||
public:
|
||||
GCodeFindReplace(const PrintConfig &print_config) : GCodeFindReplace(print_config.gcode_substitutions.values) {}
|
||||
GCodeFindReplace(const std::vector<std::string> &gcode_substitutions);
|
||||
|
||||
|
||||
std::string process_layer(const std::string &gcode);
|
||||
|
||||
private:
|
||||
struct Substitution {
|
||||
std::string plain_pattern;
|
||||
boost::regex regexp_pattern;
|
||||
std::string format;
|
||||
|
||||
bool regexp { false };
|
||||
bool case_insensitive { false };
|
||||
bool whole_word { false };
|
||||
};
|
||||
std::vector<Substitution> m_substitutions;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // slic3r_FindReplace_hpp_
|
@ -439,7 +439,7 @@ static std::vector<std::string> s_Preset_print_options {
|
||||
"support_material_interface_pattern", "support_material_interface_spacing", "support_material_interface_contact_loops",
|
||||
"support_material_contact_distance", "support_material_bottom_contact_distance",
|
||||
"support_material_buildplate_only", "dont_support_bridges", "thick_bridges", "notes", "complete_objects", "extruder_clearance_radius",
|
||||
"extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "perimeter_extruder",
|
||||
"extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "gcode_substitutions", "perimeter_extruder",
|
||||
"infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder",
|
||||
"ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width",
|
||||
"perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width",
|
||||
@ -1133,6 +1133,20 @@ void add_correct_opts_to_diff(const std::string &opt_key, t_config_option_keys&
|
||||
}
|
||||
}
|
||||
|
||||
// list of options with vector variable, which is independent from number of extruders
|
||||
static const std::vector<std::string> independent_from_extruder_number_options = {
|
||||
"bed_shape",
|
||||
"filament_ramming_parameters",
|
||||
"gcode_substitutions",
|
||||
"compatible_prints",
|
||||
"compatible_printers"
|
||||
};
|
||||
|
||||
bool PresetCollection::is_independent_from_extruder_number_option(const std::string& opt_key)
|
||||
{
|
||||
return std::find(independent_from_extruder_number_options.begin(), independent_from_extruder_number_options.end(), opt_key) != independent_from_extruder_number_options.end();
|
||||
}
|
||||
|
||||
// Use deep_diff to correct return of changed options, considering individual options for each extruder.
|
||||
inline t_config_option_keys deep_diff(const ConfigBase &config_this, const ConfigBase &config_other)
|
||||
{
|
||||
@ -1142,7 +1156,7 @@ inline t_config_option_keys deep_diff(const ConfigBase &config_this, const Confi
|
||||
const ConfigOption *other_opt = config_other.option(opt_key);
|
||||
if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt)
|
||||
{
|
||||
if (opt_key == "bed_shape" || opt_key == "thumbnails" || opt_key == "compatible_prints" || opt_key == "compatible_printers") {
|
||||
if (PresetCollection::is_independent_from_extruder_number_option(opt_key)) {
|
||||
// Scalar variable, or a vector variable, which is independent from number of extruders,
|
||||
// thus the vector is presented to the user as a single input.
|
||||
diff.emplace_back(opt_key);
|
||||
|
@ -558,6 +558,7 @@ private:
|
||||
public:
|
||||
static bool is_dirty(const Preset *edited, const Preset *reference);
|
||||
static std::vector<std::string> dirty_options(const Preset *edited, const Preset *reference, const bool deep_compare = false);
|
||||
static bool is_independent_from_extruder_number_option(const std::string& opt_key);
|
||||
private:
|
||||
// Type of this PresetCollection: TYPE_PRINT, TYPE_FILAMENT or TYPE_PRINTER.
|
||||
Preset::Type m_type;
|
||||
|
@ -110,6 +110,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
||||
"output_filename_format",
|
||||
"perimeter_acceleration",
|
||||
"post_process",
|
||||
"gcode_substitutions",
|
||||
"printer_notes",
|
||||
"retract_before_travel",
|
||||
"retract_before_wipe",
|
||||
|
@ -1357,6 +1357,12 @@ void PrintConfigDef::init_fff_params()
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(0));
|
||||
|
||||
def = this->add("gcode_substitutions", coStrings);
|
||||
def->label = L("G-code substitutions");
|
||||
def->tooltip = L("Find / replace patterns in G-code lines and substitute them.");
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionStrings());
|
||||
|
||||
def = this->add("high_current_on_filament_swap", coBool);
|
||||
def->label = L("High extruder current on filament swap");
|
||||
def->tooltip = L("It may be beneficial to increase the extruder motor current during the filament exchange"
|
||||
|
@ -641,6 +641,12 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionBool, gcode_comments))
|
||||
((ConfigOptionEnum<GCodeFlavor>, gcode_flavor))
|
||||
((ConfigOptionBool, gcode_label_objects))
|
||||
// Triples of strings: "search pattern", "replace with pattern", "attribs"
|
||||
// where "attribs" are one of:
|
||||
// r - regular expression
|
||||
// i - case insensitive
|
||||
// w - whole word
|
||||
((ConfigOptionStrings, gcode_substitutions))
|
||||
((ConfigOptionString, layer_gcode))
|
||||
((ConfigOptionFloat, max_print_speed))
|
||||
((ConfigOptionFloat, max_volumetric_speed))
|
||||
|
@ -143,7 +143,7 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt
|
||||
config.set_key_value(opt_key, new ConfigOptionString(boost::any_cast<std::string>(value)));
|
||||
break;
|
||||
case coStrings:{
|
||||
if (opt_key == "compatible_prints" || opt_key == "compatible_printers") {
|
||||
if (opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "gcode_substitutions") {
|
||||
config.option<ConfigOptionStrings>(opt_key)->values =
|
||||
boost::any_cast<std::vector<std::string>>(value);
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "libslic3r/Exception.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "libslic3r/Preset.hpp"
|
||||
#include "I18N.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
@ -596,8 +597,7 @@ void ConfigOptionsGroup::back_to_config_value(const DynamicPrintConfig& config,
|
||||
}
|
||||
else if (m_opt_map.find(opt_key) == m_opt_map.end() ||
|
||||
// This option don't have corresponded field
|
||||
opt_key == "bed_shape" || opt_key == "filament_ramming_parameters" ||
|
||||
opt_key == "compatible_printers" || opt_key == "compatible_prints" ) {
|
||||
PresetCollection::is_independent_from_extruder_number_option(opt_key) ) {
|
||||
value = get_config_value(config, opt_key);
|
||||
this->change_opt_value(opt_key, value);
|
||||
OptionsGroup::on_change_OG(opt_key, value);
|
||||
@ -876,7 +876,7 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config
|
||||
ret = from_u8(config.opt_string(opt_key));
|
||||
break;
|
||||
case coStrings:
|
||||
if (opt_key == "compatible_printers" || opt_key == "compatible_prints") {
|
||||
if (opt_key == "compatible_printers" || opt_key == "compatible_prints" || opt_key == "gcode_substitutions") {
|
||||
ret = config.option<ConfigOptionStrings>(opt_key)->values;
|
||||
break;
|
||||
}
|
||||
|
@ -76,7 +76,8 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUplo
|
||||
txt_filename->SetValue(recent_path);
|
||||
txt_filename->SetFocus();
|
||||
|
||||
m_valid_suffix = recent_path.substr(recent_path.find_last_of('.'));
|
||||
if (size_t extension_start = recent_path.find_last_of('.'); extension_start != std::string::npos)
|
||||
m_valid_suffix = recent_path.substr(extension_start);
|
||||
// .gcode suffix control
|
||||
auto validate_path = [this](const wxString &path) -> bool {
|
||||
if (! path.Lower().EndsWith(m_valid_suffix.Lower())) {
|
||||
|
@ -479,8 +479,7 @@ void Tab::update_label_colours()
|
||||
else
|
||||
color = &m_modified_label_clr;
|
||||
}
|
||||
if (opt.first == "bed_shape" || opt.first == "filament_ramming_parameters" ||
|
||||
opt.first == "compatible_prints" || opt.first == "compatible_printers" ) {
|
||||
if (PresetCollection::is_independent_from_extruder_number_option(opt.first)) {
|
||||
if (Line* line = get_line(opt.first))
|
||||
line->set_label_colour(color);
|
||||
continue;
|
||||
@ -521,8 +520,7 @@ void Tab::decorate()
|
||||
Field* field = nullptr;
|
||||
bool option_without_field = false;
|
||||
|
||||
if (opt.first == "bed_shape" || opt.first == "filament_ramming_parameters" ||
|
||||
opt.first == "compatible_prints" || opt.first == "compatible_printers")
|
||||
if(PresetCollection::is_independent_from_extruder_number_option(opt.first))
|
||||
option_without_field = true;
|
||||
|
||||
if (!option_without_field) {
|
||||
@ -812,6 +810,12 @@ void Tab::on_roll_back_value(const bool to_sys /*= true*/)
|
||||
if ((m_options_list["filament_ramming_parameters"] & os) == 0)
|
||||
to_sys ? group->back_to_sys_value("filament_ramming_parameters") : group->back_to_initial_value("filament_ramming_parameters");
|
||||
}
|
||||
if (group->title == "G-code Substitutions") {
|
||||
if ((m_options_list["gcode_substitutions"] & os) == 0) {
|
||||
to_sys ? group->back_to_sys_value("gcode_substitutions") : group->back_to_initial_value("gcode_substitutions");
|
||||
load_key_value("gcode_substitutions", true/*some value*/, true);
|
||||
}
|
||||
}
|
||||
if (group->title == "Profile dependencies") {
|
||||
// "compatible_printers" option doesn't exists in Printer Settimgs Tab
|
||||
if (m_type != Preset::TYPE_PRINTER && (m_options_list["compatible_printers"] & os) == 0) {
|
||||
@ -906,7 +910,7 @@ void Tab::update_visibility()
|
||||
page->update_visibility(m_mode, page.get() == m_active_page);
|
||||
rebuild_page_tree();
|
||||
|
||||
if (m_type == Preset::TYPE_SLA_PRINT)
|
||||
if (m_type == Preset::TYPE_SLA_PRINT || m_type == Preset::TYPE_PRINT)
|
||||
update_description_lines();
|
||||
|
||||
Layout();
|
||||
@ -1655,6 +1659,18 @@ void TabPrint::build()
|
||||
option.opt.height = 5;//50;
|
||||
optgroup->append_single_option_line(option);
|
||||
|
||||
optgroup = page->new_optgroup(L("G-code Substitutions"));
|
||||
|
||||
create_line_with_widget(optgroup.get(), "gcode_substitutions", "", [this](wxWindow* parent) {
|
||||
return create_manage_substitution_widget(parent);
|
||||
});
|
||||
line = { "", "" };
|
||||
line.full_width = 1;
|
||||
line.widget = [this](wxWindow* parent) {
|
||||
return create_substitutions_widget(parent);
|
||||
};
|
||||
optgroup->append_line(line);
|
||||
|
||||
page = add_options_page(L("Notes"), "note.png");
|
||||
optgroup = page->new_optgroup(L("Notes"), 0);
|
||||
option = optgroup->get_option("notes");
|
||||
@ -1699,13 +1715,21 @@ void TabPrint::update_description_lines()
|
||||
from_u8(PresetHints::top_bottom_shell_thickness_explanation(*m_preset_bundle)));
|
||||
}
|
||||
|
||||
if (m_active_page && m_active_page->title() == "Output options" && m_post_process_explanation) {
|
||||
if (m_active_page && m_active_page->title() == "Output options") {
|
||||
if (m_post_process_explanation) {
|
||||
m_post_process_explanation->SetText(
|
||||
_L("Post processing scripts shall modify G-code file in place."));
|
||||
#ifndef __linux__
|
||||
m_post_process_explanation->SetPathEnd("post-processing-scripts_283913");
|
||||
#endif // __linux__
|
||||
}
|
||||
// upadte G-code substitutions from the current configuration
|
||||
{
|
||||
m_subst_manager.update_from_config();
|
||||
if (m_del_all_substitutions_btn)
|
||||
m_del_all_substitutions_btn->Show(!m_subst_manager.is_empty_substitutions());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TabPrint::toggle_options()
|
||||
@ -3824,6 +3848,244 @@ wxSizer* Tab::compatible_widget_create(wxWindow* parent, PresetDependencies &dep
|
||||
return sizer;
|
||||
}
|
||||
|
||||
// G-code substitutions
|
||||
|
||||
void SubstitutionManager::init(DynamicPrintConfig* config, wxWindow* parent, wxFlexGridSizer* grid_sizer)
|
||||
{
|
||||
m_config = config;
|
||||
m_parent = parent;
|
||||
m_grid_sizer = grid_sizer;
|
||||
m_em = em_unit(parent);
|
||||
}
|
||||
|
||||
void SubstitutionManager::create_legend()
|
||||
{
|
||||
if (!m_grid_sizer->IsEmpty())
|
||||
return;
|
||||
// name of the first column is empty
|
||||
m_grid_sizer->Add(new wxStaticText(m_parent, wxID_ANY, wxEmptyString));
|
||||
// Legend for another columns
|
||||
for (const std::string col : { L("Plain pattern"), L("Format"), L("Params") }) {
|
||||
auto temp = new wxStaticText(m_parent, wxID_ANY, _(col), wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_MIDDLE);
|
||||
// temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
m_grid_sizer->Add(temp);
|
||||
}
|
||||
}
|
||||
|
||||
// delete substitution_id from substitutions
|
||||
void SubstitutionManager::delete_substitution(int substitution_id)
|
||||
{
|
||||
// delete substitution
|
||||
std::vector<std::string>& substitutions = m_config->option<ConfigOptionStrings>("gcode_substitutions")->values;
|
||||
if ((substitutions.size() % 3) != 0)
|
||||
throw RuntimeError("Invalid length of gcode_substitutions parameter");
|
||||
|
||||
if ((substitutions.size() / 3) < substitution_id)
|
||||
throw RuntimeError("Invalid substitution_id to delete");
|
||||
|
||||
substitutions.erase(std::next(substitutions.begin(), substitution_id * 3), std::next(substitutions.begin(), substitution_id * 3 + 3));
|
||||
call_ui_update();
|
||||
|
||||
// update grid_sizer
|
||||
update_from_config();
|
||||
}
|
||||
|
||||
// Add substitution line
|
||||
void SubstitutionManager::add_substitution(int substitution_id, const std::string& plain_pattern, const std::string& format, const std::string& params)
|
||||
{
|
||||
bool call_after_layout = false;
|
||||
|
||||
if (substitution_id < 0) {
|
||||
if (m_grid_sizer->IsEmpty()) {
|
||||
create_legend();
|
||||
substitution_id = 0;
|
||||
}
|
||||
substitution_id = m_grid_sizer->GetEffectiveRowsCount() - 1;
|
||||
|
||||
// create new substitution
|
||||
// it have to be added toconfig too
|
||||
std::vector<std::string>& substitutions = m_config->option<ConfigOptionStrings>("gcode_substitutions")->values;
|
||||
for (size_t i = 0; i < 3; i ++)
|
||||
substitutions.push_back(std::string());
|
||||
|
||||
call_after_layout = true;
|
||||
}
|
||||
|
||||
auto del_btn = new ScalableButton(m_parent, wxID_ANY, "cross");
|
||||
del_btn->Bind(wxEVT_BUTTON, [substitution_id, this](wxEvent&) {
|
||||
delete_substitution(substitution_id);
|
||||
});
|
||||
|
||||
m_grid_sizer->Add(del_btn, 0, wxRIGHT | wxLEFT, m_em);
|
||||
|
||||
auto add_text_editor = [substitution_id, this](const wxString& value, int opt_pos) {
|
||||
auto editor = new wxTextCtrl(m_parent, wxID_ANY, value, wxDefaultPosition, wxSize(15 * m_em, wxDefaultCoord), wxTE_PROCESS_ENTER
|
||||
#ifdef _WIN32
|
||||
| wxBORDER_SIMPLE
|
||||
#endif
|
||||
);
|
||||
|
||||
editor->SetFont(wxGetApp().normal_font());
|
||||
wxGetApp().UpdateDarkUI(editor);
|
||||
m_grid_sizer->Add(editor, 0, wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
editor->Bind(wxEVT_TEXT_ENTER, [this, editor, substitution_id, opt_pos](wxEvent& e) {
|
||||
#if !defined(__WXGTK__)
|
||||
e.Skip();
|
||||
#endif // __WXGTK__
|
||||
edit_substitution(substitution_id, opt_pos, into_u8(editor->GetValue()));
|
||||
});
|
||||
|
||||
editor->Bind(wxEVT_KILL_FOCUS, [this, editor, substitution_id, opt_pos](wxEvent& e) {
|
||||
e.Skip();
|
||||
edit_substitution(substitution_id, opt_pos, into_u8(editor->GetValue()));
|
||||
});
|
||||
};
|
||||
|
||||
add_text_editor(from_u8(plain_pattern), 0);
|
||||
add_text_editor(from_u8(format), 1);
|
||||
|
||||
auto params_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
bool regexp = strchr(params.c_str(), 'r') != nullptr || strchr(params.c_str(), 'R') != nullptr;
|
||||
bool case_insensitive = strchr(params.c_str(), 'i') != nullptr || strchr(params.c_str(), 'I') != nullptr;
|
||||
bool whole_word = strchr(params.c_str(), 'w') != nullptr || strchr(params.c_str(), 'W') != nullptr;
|
||||
|
||||
auto chb_regexp = new wxCheckBox(m_parent, wxID_ANY, wxString("Regular expression"));
|
||||
chb_regexp->SetValue(regexp);
|
||||
params_sizer->Add(chb_regexp, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, m_em);
|
||||
|
||||
auto chb_case_insensitive = new wxCheckBox(m_parent, wxID_ANY, wxString("Case insensitive"));
|
||||
chb_case_insensitive->SetValue(case_insensitive);
|
||||
params_sizer->Add(chb_case_insensitive, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT | wxLEFT, m_em);
|
||||
|
||||
auto chb_whole_word = new wxCheckBox(m_parent, wxID_ANY, wxString("Whole word"));
|
||||
chb_whole_word->SetValue(whole_word);
|
||||
params_sizer->Add(chb_whole_word, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT | wxLEFT, m_em);
|
||||
|
||||
for (wxCheckBox* chb : std::initializer_list<wxCheckBox*>{ chb_regexp, chb_case_insensitive, chb_whole_word }) {
|
||||
chb->SetFont(wxGetApp().normal_font());
|
||||
chb->Bind(wxEVT_CHECKBOX, [this, substitution_id, chb_regexp, chb_case_insensitive, chb_whole_word](wxCommandEvent e) {
|
||||
std::string value = std::string();
|
||||
if (chb_regexp->GetValue())
|
||||
value += "r";
|
||||
if (chb_case_insensitive->GetValue())
|
||||
value += "i";
|
||||
if (chb_whole_word->GetValue())
|
||||
value += "w";
|
||||
edit_substitution(substitution_id, 2, value);
|
||||
});
|
||||
}
|
||||
|
||||
m_grid_sizer->Add(params_sizer);
|
||||
|
||||
if (call_after_layout) {
|
||||
m_parent->GetParent()->Layout();
|
||||
call_ui_update();
|
||||
}
|
||||
}
|
||||
|
||||
void SubstitutionManager::update_from_config()
|
||||
{
|
||||
if (!m_grid_sizer->IsEmpty())
|
||||
m_grid_sizer->Clear(true);
|
||||
|
||||
std::vector<std::string>& subst = m_config->option<ConfigOptionStrings>("gcode_substitutions")->values;
|
||||
if (!subst.empty())
|
||||
create_legend();
|
||||
|
||||
if ((subst.size() % 3) != 0)
|
||||
throw RuntimeError("Invalid length of gcode_substitutions parameter");
|
||||
|
||||
int subst_id = 0;
|
||||
for (size_t i = 0; i < subst.size(); i += 3)
|
||||
add_substitution(subst_id++, subst[i], subst[i + 1], subst[i + 2]);
|
||||
|
||||
m_parent->GetParent()->Layout();
|
||||
}
|
||||
|
||||
void SubstitutionManager::delete_all()
|
||||
{
|
||||
m_config->option<ConfigOptionStrings>("gcode_substitutions")->values.clear();
|
||||
call_ui_update();
|
||||
|
||||
if (!m_grid_sizer->IsEmpty())
|
||||
m_grid_sizer->Clear(true);
|
||||
|
||||
m_parent->GetParent()->Layout();
|
||||
}
|
||||
|
||||
void SubstitutionManager::edit_substitution(int substitution_id, int opt_pos, const std::string& value)
|
||||
{
|
||||
std::vector<std::string>& substitutions = m_config->option<ConfigOptionStrings>("gcode_substitutions")->values;
|
||||
|
||||
if ((substitutions.size() % 3) != 0)
|
||||
throw RuntimeError("Invalid length of gcode_substitutions parameter");
|
||||
|
||||
if ((substitutions.size() / 3) != m_grid_sizer->GetEffectiveRowsCount()-1)
|
||||
throw RuntimeError("Invalid compatibility between UI and BE");
|
||||
|
||||
if ((substitutions.size() / 3) < substitution_id)
|
||||
throw RuntimeError("Invalid substitution_id to edit");
|
||||
|
||||
substitutions[substitution_id * 3 + opt_pos] = value;
|
||||
|
||||
call_ui_update();
|
||||
}
|
||||
|
||||
bool SubstitutionManager::is_empty_substitutions()
|
||||
{
|
||||
return m_config->option<ConfigOptionStrings>("gcode_substitutions")->values.empty();
|
||||
}
|
||||
|
||||
// Return a callback to create a TabPrint widget to edit G-code substitutions
|
||||
wxSizer* TabPrint::create_manage_substitution_widget(wxWindow* parent)
|
||||
{
|
||||
auto create_btn = [parent](ScalableButton** btn, const wxString& label, const std::string& icon_name) {
|
||||
*btn = new ScalableButton(parent, wxID_ANY, icon_name, " " + label + " ", wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT, true);
|
||||
(*btn)->SetFont(wxGetApp().normal_font());
|
||||
(*btn)->SetSize((*btn)->GetBestSize());
|
||||
};
|
||||
|
||||
ScalableButton* add_substitution_btn;
|
||||
create_btn(&add_substitution_btn, _L("Add G-code substitution"), "add_copies");
|
||||
add_substitution_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent e) {
|
||||
m_subst_manager.add_substitution();
|
||||
m_del_all_substitutions_btn->Show();
|
||||
});
|
||||
|
||||
create_btn(&m_del_all_substitutions_btn, _L("Delete all G-code substitution"), "cross");
|
||||
m_del_all_substitutions_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent e) {
|
||||
m_subst_manager.delete_all();
|
||||
m_del_all_substitutions_btn->Hide();
|
||||
});
|
||||
|
||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
sizer->Add(add_substitution_btn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT | wxLEFT, em_unit(parent));
|
||||
sizer->Add(m_del_all_substitutions_btn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT | wxLEFT, em_unit(parent));
|
||||
|
||||
parent->GetParent()->Layout();
|
||||
return sizer;
|
||||
}
|
||||
|
||||
// Return a callback to create a TabPrint widget to edit G-code substitutions
|
||||
wxSizer* TabPrint::create_substitutions_widget(wxWindow* parent)
|
||||
{
|
||||
wxFlexGridSizer* grid_sizer = new wxFlexGridSizer(4, 5, wxGetApp().em_unit()); // delete_button, "Old val", "New val", "Params"
|
||||
grid_sizer->SetFlexibleDirection(wxHORIZONTAL);
|
||||
|
||||
m_subst_manager.init(m_config, parent, grid_sizer);
|
||||
m_subst_manager.set_cb_edited_substitution([this]() {
|
||||
update_dirty();
|
||||
wxGetApp().mainframe->on_config_changed(m_config); // invalidate print
|
||||
});
|
||||
|
||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
sizer->Add(grid_sizer, 0, wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
parent->GetParent()->Layout();
|
||||
return sizer;
|
||||
}
|
||||
|
||||
// Return a callback to create a TabPrinter widget to edit bed shape
|
||||
wxSizer* TabPrinter::create_bed_shape_widget(wxWindow* parent)
|
||||
{
|
||||
|
@ -43,6 +43,44 @@ namespace GUI {
|
||||
class TabPresetComboBox;
|
||||
class OG_CustomCtrl;
|
||||
|
||||
// G-code substitutions
|
||||
|
||||
// Substitution Manager - helper for manipuation of the substitutions
|
||||
class SubstitutionManager
|
||||
{
|
||||
DynamicPrintConfig* m_config{ nullptr };
|
||||
wxWindow* m_parent{ nullptr };
|
||||
wxFlexGridSizer* m_grid_sizer{ nullptr };
|
||||
|
||||
int m_em{10};
|
||||
std::function<void()> m_cb_edited_substitution{ nullptr };
|
||||
|
||||
public:
|
||||
SubstitutionManager() {};
|
||||
~SubstitutionManager() {};
|
||||
|
||||
void init(DynamicPrintConfig* config, wxWindow* parent, wxFlexGridSizer* grid_sizer);
|
||||
void create_legend();
|
||||
void delete_substitution(int substitution_id);
|
||||
void add_substitution( int substitution_id = -1,
|
||||
const std::string& plain_pattern = std::string(),
|
||||
const std::string& format = std::string(),
|
||||
const std::string& params = std::string());
|
||||
void update_from_config();
|
||||
void delete_all();
|
||||
void edit_substitution(int substitution_id,
|
||||
int opt_pos, // option position insubstitution [0, 2]
|
||||
const std::string& value);
|
||||
void set_cb_edited_substitution(std::function<void()> cb_edited_substitution) {
|
||||
m_cb_edited_substitution = cb_edited_substitution;
|
||||
}
|
||||
void call_ui_update() {
|
||||
if (m_cb_edited_substitution)
|
||||
m_cb_edited_substitution();
|
||||
}
|
||||
bool is_empty_substitutions();
|
||||
};
|
||||
|
||||
// Single Tab page containing a{ vsizer } of{ optgroups }
|
||||
// package Slic3r::GUI::Tab::Page;
|
||||
using ConfigOptionsGroupShp = std::shared_ptr<ConfigOptionsGroup>;
|
||||
@ -368,11 +406,15 @@ public:
|
||||
void update() override;
|
||||
void clear_pages() override;
|
||||
bool supports_printer_technology(const PrinterTechnology tech) const override { return tech == ptFFF; }
|
||||
wxSizer* create_manage_substitution_widget(wxWindow* parent);
|
||||
wxSizer* create_substitutions_widget(wxWindow* parent);
|
||||
|
||||
private:
|
||||
ogStaticText* m_recommended_thin_wall_thickness_description_line = nullptr;
|
||||
ogStaticText* m_top_bottom_shell_thickness_explanation = nullptr;
|
||||
ogStaticText* m_post_process_explanation = nullptr;
|
||||
ScalableButton* m_del_all_substitutions_btn{nullptr};
|
||||
SubstitutionManager m_subst_manager;
|
||||
};
|
||||
|
||||
class TabFilament : public Tab
|
||||
|
@ -1156,6 +1156,14 @@ static wxString get_string_value(std::string opt_key, const DynamicPrintConfig&
|
||||
out.RemoveLast(1);
|
||||
return out;
|
||||
}
|
||||
if (opt_key == "gcode_substitutions") {
|
||||
if (!strings->empty())
|
||||
for (size_t id = 0; id < strings->size(); id += 3)
|
||||
out += from_u8(strings->get_at(id)) + ";\t" +
|
||||
from_u8(strings->get_at(id + 1)) + ";\t" +
|
||||
from_u8(strings->get_at(id + 2)) + ";\n";
|
||||
return out;
|
||||
}
|
||||
if (!strings->empty() && opt_idx < strings->values.size())
|
||||
return from_u8(strings->get_at(opt_idx));
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_fill.cpp
|
||||
test_flow.cpp
|
||||
test_gcode.cpp
|
||||
test_gcodefindreplace.cpp
|
||||
test_gcodewriter.cpp
|
||||
test_model.cpp
|
||||
test_print.cpp
|
||||
|
218
tests/fff_print/test_gcodefindreplace.cpp
Normal file
218
tests/fff_print/test_gcodefindreplace.cpp
Normal file
@ -0,0 +1,218 @@
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "libslic3r/GCode/FindReplace.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
SCENARIO("Find/Replace with plain text", "[GCodeFindReplace]") {
|
||||
GIVEN("G-code") {
|
||||
const std::string gcode =
|
||||
"G1 Z0; home\n"
|
||||
"G1 Z1; move up\n"
|
||||
"G1 X0 Y1 Z1; perimeter\n"
|
||||
"G1 X13 Y32 Z1; infill\n"
|
||||
"G1 X13 Y32 Z1; wipe\n";
|
||||
WHEN("Replace \"move up\" with \"move down\", case sensitive") {
|
||||
GCodeFindReplace find_replace({ "move up", "move down", "" });
|
||||
REQUIRE(find_replace.process_layer(gcode) ==
|
||||
"G1 Z0; home\n"
|
||||
// substituted
|
||||
"G1 Z1; move down\n"
|
||||
"G1 X0 Y1 Z1; perimeter\n"
|
||||
"G1 X13 Y32 Z1; infill\n"
|
||||
"G1 X13 Y32 Z1; wipe\n");
|
||||
}
|
||||
WHEN("Replace \"move up\" with \"move down\", case insensitive") {
|
||||
GCodeFindReplace find_replace({ "move up", "move down", "i" });
|
||||
REQUIRE(find_replace.process_layer(gcode) ==
|
||||
"G1 Z0; home\n"
|
||||
// substituted
|
||||
"G1 Z1; move down\n"
|
||||
"G1 X0 Y1 Z1; perimeter\n"
|
||||
"G1 X13 Y32 Z1; infill\n"
|
||||
"G1 X13 Y32 Z1; wipe\n");
|
||||
}
|
||||
WHEN("Replace \"move UP\" with \"move down\", case insensitive") {
|
||||
GCodeFindReplace find_replace({ "move UP", "move down", "i" });
|
||||
REQUIRE(find_replace.process_layer(gcode) ==
|
||||
"G1 Z0; home\n"
|
||||
// substituted
|
||||
"G1 Z1; move down\n"
|
||||
"G1 X0 Y1 Z1; perimeter\n"
|
||||
"G1 X13 Y32 Z1; infill\n"
|
||||
"G1 X13 Y32 Z1; wipe\n");
|
||||
}
|
||||
WHEN("Replace \"move up\" with \"move down\", case sensitive") {
|
||||
GCodeFindReplace find_replace({ "move UP", "move down", "" });
|
||||
REQUIRE(find_replace.process_layer(gcode) == gcode);
|
||||
}
|
||||
|
||||
// Whole word
|
||||
WHEN("Replace \"move up\" with \"move down\", whole word") {
|
||||
GCodeFindReplace find_replace({ "move up", "move down", "w" });
|
||||
REQUIRE(find_replace.process_layer(gcode) ==
|
||||
"G1 Z0; home\n"
|
||||
// substituted
|
||||
"G1 Z1; move down\n"
|
||||
"G1 X0 Y1 Z1; perimeter\n"
|
||||
"G1 X13 Y32 Z1; infill\n"
|
||||
"G1 X13 Y32 Z1; wipe\n");
|
||||
}
|
||||
WHEN("Replace \"move u\" with \"move down\", whole word") {
|
||||
GCodeFindReplace find_replace({ "move u", "move down", "w" });
|
||||
REQUIRE(find_replace.process_layer(gcode) == gcode);
|
||||
}
|
||||
WHEN("Replace \"ove up\" with \"move down\", whole word") {
|
||||
GCodeFindReplace find_replace({ "move u", "move down", "w" });
|
||||
REQUIRE(find_replace.process_layer(gcode) == gcode);
|
||||
}
|
||||
|
||||
// Multi-line replace
|
||||
WHEN("Replace \"move up\\nG1 X0 \" with \"move down\\nG0 X1 \"") {
|
||||
GCodeFindReplace find_replace({ "move up\\nG1 X0 ", "move down\\nG0 X1 ", "" });
|
||||
REQUIRE(find_replace.process_layer(gcode) ==
|
||||
"G1 Z0; home\n"
|
||||
// substituted
|
||||
"G1 Z1; move down\n"
|
||||
"G0 X1 Y1 Z1; perimeter\n"
|
||||
"G1 X13 Y32 Z1; infill\n"
|
||||
"G1 X13 Y32 Z1; wipe\n");
|
||||
}
|
||||
// Multi-line replace, whole word.
|
||||
WHEN("Replace \"move up\\nG1 X0\" with \"move down\\nG0 X1\", whole word") {
|
||||
GCodeFindReplace find_replace({ "move up\\nG1 X0", "move down\\nG0 X1", "w" });
|
||||
REQUIRE(find_replace.process_layer(gcode) ==
|
||||
"G1 Z0; home\n"
|
||||
// substituted
|
||||
"G1 Z1; move down\n"
|
||||
"G0 X1 Y1 Z1; perimeter\n"
|
||||
"G1 X13 Y32 Z1; infill\n"
|
||||
"G1 X13 Y32 Z1; wipe\n");
|
||||
}
|
||||
// Multi-line replace, whole word, fails.
|
||||
WHEN("Replace \"move up\\nG1 X\" with \"move down\\nG0 X\", whole word") {
|
||||
GCodeFindReplace find_replace({ "move up\\nG1 X", "move down\\nG0 X", "w" });
|
||||
REQUIRE(find_replace.process_layer(gcode) == gcode);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("G-code with decimals") {
|
||||
const std::string gcode =
|
||||
"G1 Z0.123; home\n"
|
||||
"G1 Z1.21; move up\n"
|
||||
"G1 X0 Y.33 Z.431 E1.2; perimeter\n";
|
||||
WHEN("Regular expression NOT processed in non-regex mode") {
|
||||
GCodeFindReplace find_replace({ "( [XYZEF]-?)\\.([0-9]+)", "\\10.\\2", "" });
|
||||
REQUIRE(find_replace.process_layer(gcode) == gcode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("Find/Replace with regexp", "[GCodeFindReplace]") {
|
||||
GIVEN("G-code") {
|
||||
const std::string gcode =
|
||||
"G1 Z0; home\n"
|
||||
"G1 Z1; move up\n"
|
||||
"G1 X0 Y1 Z1; perimeter\n"
|
||||
"G1 X13 Y32 Z1; infill\n"
|
||||
"G1 X13 Y32 Z1; wipe\n";
|
||||
WHEN("Replace \"move up\" with \"move down\", case sensitive") {
|
||||
GCodeFindReplace find_replace({ "move up", "move down", "r" });
|
||||
REQUIRE(find_replace.process_layer(gcode) ==
|
||||
"G1 Z0; home\n"
|
||||
// substituted
|
||||
"G1 Z1; move down\n"
|
||||
"G1 X0 Y1 Z1; perimeter\n"
|
||||
"G1 X13 Y32 Z1; infill\n"
|
||||
"G1 X13 Y32 Z1; wipe\n");
|
||||
}
|
||||
WHEN("Replace \"move up\" with \"move down\", case insensitive") {
|
||||
GCodeFindReplace find_replace({ "move up", "move down", "ri" });
|
||||
REQUIRE(find_replace.process_layer(gcode) ==
|
||||
"G1 Z0; home\n"
|
||||
// substituted
|
||||
"G1 Z1; move down\n"
|
||||
"G1 X0 Y1 Z1; perimeter\n"
|
||||
"G1 X13 Y32 Z1; infill\n"
|
||||
"G1 X13 Y32 Z1; wipe\n");
|
||||
}
|
||||
WHEN("Replace \"move UP\" with \"move down\", case insensitive") {
|
||||
GCodeFindReplace find_replace({ "move UP", "move down", "ri" });
|
||||
REQUIRE(find_replace.process_layer(gcode) ==
|
||||
"G1 Z0; home\n"
|
||||
// substituted
|
||||
"G1 Z1; move down\n"
|
||||
"G1 X0 Y1 Z1; perimeter\n"
|
||||
"G1 X13 Y32 Z1; infill\n"
|
||||
"G1 X13 Y32 Z1; wipe\n");
|
||||
}
|
||||
WHEN("Replace \"move up\" with \"move down\", case sensitive") {
|
||||
GCodeFindReplace find_replace({ "move UP", "move down", "r" });
|
||||
REQUIRE(find_replace.process_layer(gcode) == gcode);
|
||||
}
|
||||
|
||||
// Whole word
|
||||
WHEN("Replace \"move up\" with \"move down\", whole word") {
|
||||
GCodeFindReplace find_replace({ "move up", "move down", "rw" });
|
||||
REQUIRE(find_replace.process_layer(gcode) ==
|
||||
"G1 Z0; home\n"
|
||||
// substituted
|
||||
"G1 Z1; move down\n"
|
||||
"G1 X0 Y1 Z1; perimeter\n"
|
||||
"G1 X13 Y32 Z1; infill\n"
|
||||
"G1 X13 Y32 Z1; wipe\n");
|
||||
}
|
||||
WHEN("Replace \"move u\" with \"move down\", whole word") {
|
||||
GCodeFindReplace find_replace({ "move u", "move down", "rw" });
|
||||
REQUIRE(find_replace.process_layer(gcode) == gcode);
|
||||
}
|
||||
WHEN("Replace \"ove up\" with \"move down\", whole word") {
|
||||
GCodeFindReplace find_replace({ "move u", "move down", "rw" });
|
||||
REQUIRE(find_replace.process_layer(gcode) == gcode);
|
||||
}
|
||||
|
||||
// Multi-line replace
|
||||
WHEN("Replace \"move up\\nG1 X0 \" with \"move down\\nG0 X1 \"") {
|
||||
GCodeFindReplace find_replace({ "move up\\nG1 X0 ", "move down\\nG0 X1 ", "r" });
|
||||
REQUIRE(find_replace.process_layer(gcode) ==
|
||||
"G1 Z0; home\n"
|
||||
// substituted
|
||||
"G1 Z1; move down\n"
|
||||
"G0 X1 Y1 Z1; perimeter\n"
|
||||
"G1 X13 Y32 Z1; infill\n"
|
||||
"G1 X13 Y32 Z1; wipe\n");
|
||||
}
|
||||
// Multi-line replace, whole word.
|
||||
WHEN("Replace \"move up\\nG1 X0\" with \"move down\\nG0 X1\", whole word") {
|
||||
GCodeFindReplace find_replace({ "move up\\nG1 X0", "move down\\nG0 X1", "rw" });
|
||||
REQUIRE(find_replace.process_layer(gcode) ==
|
||||
"G1 Z0; home\n"
|
||||
// substituted
|
||||
"G1 Z1; move down\n"
|
||||
"G0 X1 Y1 Z1; perimeter\n"
|
||||
"G1 X13 Y32 Z1; infill\n"
|
||||
"G1 X13 Y32 Z1; wipe\n");
|
||||
}
|
||||
// Multi-line replace, whole word, fails.
|
||||
WHEN("Replace \"move up\\nG1 X\" with \"move down\\nG0 X\", whole word") {
|
||||
GCodeFindReplace find_replace({ "move up\\nG1 X", "move down\\nG0 X", "rw" });
|
||||
REQUIRE(find_replace.process_layer(gcode) == gcode);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("G-code with decimals") {
|
||||
const std::string gcode =
|
||||
"G1 Z0.123; home\n"
|
||||
"G1 Z1.21; move up\n"
|
||||
"G1 X0 Y.33 Z.431 E1.2; perimeter\n";
|
||||
WHEN("Missing zeros before dot filled in") {
|
||||
GCodeFindReplace find_replace({ "( [XYZEF]-?)\\.([0-9]+)", "\\10.\\2", "r" });
|
||||
REQUIRE(find_replace.process_layer(gcode) ==
|
||||
"G1 Z0.123; home\n"
|
||||
"G1 Z1.21; move up\n"
|
||||
"G1 X0 Y0.33 Z0.431 E1.2; perimeter\n");
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user